pax_global_header00006660000000000000000000000064141704715070014520gustar00rootroot0000000000000052 comment=ad235d44ce65a8605ba0aa71b7bdf67453f51dc7 jdim-0.7.0/000077500000000000000000000000001417047150700124475ustar00rootroot00000000000000jdim-0.7.0/.clang-format000066400000000000000000000065741417047150700150360ustar00rootroot00000000000000--- Language: Cpp # BasedOnStyle: LLVM AccessModifierOffset: -2 AlignAfterOpenBracket: Align AlignConsecutiveAssignments: false AlignConsecutiveDeclarations: false AlignEscapedNewlines: DontAlign AlignOperands: true AlignTrailingComments: true AllowAllParametersOfDeclarationOnNextLine: true AllowShortBlocksOnASingleLine: false AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: Inline AllowShortIfStatementsOnASingleLine: true AllowShortLoopsOnASingleLine: false AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: No BinPackArguments: true BinPackParameters: true BraceWrapping: AfterClass: true AfterControlStatement: false AfterEnum: true AfterFunction: true AfterNamespace: true AfterObjCDeclaration: false AfterStruct: true AfterUnion: true AfterExternBlock: false BeforeCatch: true BeforeElse: true IndentBraces: false SplitEmptyFunction: false SplitEmptyRecord: false SplitEmptyNamespace: false BreakBeforeBinaryOperators: NonAssignment BreakBeforeBraces: Custom BreakBeforeInheritanceComma: true BreakInheritanceList: BeforeComma BreakBeforeTernaryOperators: true BreakConstructorInitializersBeforeComma: false BreakConstructorInitializers: BeforeComma BreakAfterJavaFieldAnnotations: false BreakStringLiterals: false ColumnLimit: 0 CommentPragmas: '^ IWYU pragma:' CompactNamespaces: false ConstructorInitializerAllOnOneLineOrOnePerLine: false ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 Cpp11BracedListStyle: false DerivePointerAlignment: false DisableFormat: false ExperimentalAutoDetectBinPacking: false FixNamespaceComments: false ForEachMacros: - foreach - Q_FOREACH - BOOST_FOREACH IncludeBlocks: Preserve IncludeCategories: - Regex: '^"(llvm|llvm-c|clang|clang-c)/' Priority: 2 - Regex: '^(<|"(gtest|gmock|isl|json)/)' Priority: 3 - Regex: '.*' Priority: 1 IncludeIsMainRegex: '(Test)?$' IndentCaseLabels: true IndentPPDirectives: None IndentWidth: 4 IndentWrappedFunctionNames: false JavaScriptQuotes: Leave JavaScriptWrapImports: true KeepEmptyLinesAtTheStartOfBlocks: true MacroBlockBegin: '' MacroBlockEnd: '' MaxEmptyLinesToKeep: 3 NamespaceIndentation: All ObjCBinPackProtocolList: Auto ObjCBlockIndentWidth: 2 ObjCSpaceAfterProperty: false ObjCSpaceBeforeProtocolList: true PenaltyBreakAssignment: 2 PenaltyBreakBeforeFirstCallParameter: 19 PenaltyBreakComment: 300 PenaltyBreakFirstLessLess: 120 PenaltyBreakString: 1000 PenaltyBreakTemplateDeclaration: 10 PenaltyExcessCharacter: 1000000 PenaltyReturnTypeOnItsOwnLine: 60 PointerAlignment: Left ReflowComments: true SortIncludes: true SortUsingDeclarations: true SpaceAfterCStyleCast: false SpaceAfterTemplateKeyword: true SpaceBeforeAssignmentOperators: true SpaceBeforeCpp11BracedList: false SpaceBeforeCtorInitializerColon: true SpaceBeforeInheritanceColon: true SpaceBeforeParens: Never SpaceBeforeRangeBasedForLoopColon: false SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 1 SpacesInAngles: true SpacesInContainerLiterals: true SpacesInCStyleCastParentheses: false SpacesInParentheses: true SpacesInSquareBrackets: true Standard: Cpp11 StatementMacros: - Q_UNUSED - QT_REQUIRE_VERSION TabWidth: 8 UseTab: Never ... jdim-0.7.0/.github/000077500000000000000000000000001417047150700140075ustar00rootroot00000000000000jdim-0.7.0/.github/ISSUE_TEMPLATE/000077500000000000000000000000001417047150700161725ustar00rootroot00000000000000jdim-0.7.0/.github/ISSUE_TEMPLATE/bug-report.md000066400000000000000000000017211417047150700206030ustar00rootroot00000000000000--- name: 不具合(バグ)の報告 about: 正常でない動作を見つけたらこちら title: '' labels: bug assignees: '' --- **バグの説明** **再現の方法** **やりたかったこと・期待する結果** **スクリーンショット** **動作環境** **追加の情報** jdim-0.7.0/.github/ISSUE_TEMPLATE/feature-request.md000066400000000000000000000006731417047150700216430ustar00rootroot00000000000000--- name: 機能の変更 about: 機能の追加や削除をしたいときはこちら title: '' labels: feature assignees: '' --- **背景や動機** **解決方法** **代替案** **追加の情報** jdim-0.7.0/.github/workflows/000077500000000000000000000000001417047150700160445ustar00rootroot00000000000000jdim-0.7.0/.github/workflows/ccpp.yml000066400000000000000000000157451417047150700175300ustar00rootroot00000000000000name: CI on: push: branches: - master pull_request: branches: - master jobs: compiler-18: runs-on: ubuntu-18.04 env: CC: ${{ matrix.sets.cc }} CXX: ${{ matrix.sets.cxx }} strategy: matrix: sets: - cc: gcc-6 cxx: g++-6 package: g++-6 - cc: gcc-7 cxx: g++-7 package: g++-7 - cc: gcc-8 cxx: g++-8 package: g++-8 - cc: clang-5.0 cxx: clang++-5.0 package: clang-5.0 - cc: clang-6.0 cxx: clang++-6.0 package: clang-6.0 - cc: clang-7 cxx: clang++-7 package: clang-7 # failed by SIGABRT on Ubuntu 20.04 # https://github.com/JDimproved/JDim/runs/2589523269?check_suite_focus=true - cc: clang-9 cxx: clang++-9 package: clang-9 steps: - uses: actions/checkout@v2 - uses: actions/setup-python@v2 with: python-version: '3.6' - name: dependencies installation run: | sudo apt update sudo apt install libgnutls28-dev libgtest-dev libgtkmm-3.0-dev libltdl-dev libtool ninja-build zlib1g-dev ${{ matrix.sets.package }} - name: install meson==0.49.0 run: | python -m pip install --upgrade pip setuptools wheel pip install meson==0.49.0 - name: meson builddir -Dbuildtype=debug -Dcpp_args="-D_DEBUG" run: meson builddir -Dbuildtype=debug -Dcpp_args="-D_DEBUG" # `compile` subcommand requires Meson 0.54 or later. - name: ninja -C builddir run: ninja -C builddir # Since Meson 0.57, `test` subcommand will only rebuild test program. - name: meson test -C builddir run: meson test -C builddir - name: ./builddir/src/jdim -V run: ./builddir/src/jdim -V compiler-20: runs-on: ubuntu-20.04 env: CC: ${{ matrix.sets.cc }} CXX: ${{ matrix.sets.cxx }} strategy: matrix: sets: - cc: gcc-9 cxx: g++-9 package: g++-9 - cc: gcc-10 cxx: g++-10 package: g++-10 - cc: clang-8 cxx: clang++-8 package: clang-8 - cc: clang-10 cxx: clang++-10 package: clang-10 steps: - uses: actions/checkout@v2 - name: dependencies installation run: | sudo apt update sudo apt install libgnutls28-dev libgtest-dev libgtkmm-3.0-dev libltdl-dev libtool meson zlib1g-dev ${{ matrix.sets.package }} - name: meson builddir -Dbuildtype=debug -Dcpp_args="-D_DEBUG" run: meson builddir -Dbuildtype=debug -Dcpp_args="-D_DEBUG" - name: ninja -C builddir run: ninja -C builddir - name: meson test -C builddir run: meson test -C builddir - name: ./builddir/src/jdim -V run: ./builddir/src/jdim -V autotools: runs-on: ${{ matrix.sets.os }} env: CC: ${{ matrix.sets.cc }} CXX: ${{ matrix.sets.cxx }} CXXFLAGS: -Og -pipe GTEST_SRCDIR: /usr/src/googletest strategy: matrix: sets: - os: ubuntu-18.04 cc: gcc-7 cxx: g++-7 package: g++-7 - os: ubuntu-20.04 cc: gcc-9 cxx: g++-9 package: g++-9 steps: - uses: actions/checkout@v2 - name: dependencies installation run: | sudo apt update sudo apt install autoconf-archive libgnutls28-dev libgtest-dev libgtkmm-3.0-dev libltdl-dev libtool zlib1g-dev ${{ matrix.sets.package }} - name: autoreconf -i run: autoreconf -i - name: ./configure run: ./configure - name: make -j$(nproc) run: make -j$(nproc) - name: make check -j$(nproc) run: make check -j$(nproc) - name: ./src/jdim -V run: ./src/jdim -V library-18: runs-on: ubuntu-18.04 env: CC: gcc-7 CXX: g++-7 CXXFLAGS: -Og -pipe -D_DEBUG GTEST_SRCDIR: /usr/src/googletest strategy: matrix: deps: - config_args: --with-tls=gnutls --with-regex=oniguruma --with-sessionlib=xsmp --with-migemo --with-alsa --with-pangolayout packages: libgnutls28-dev libonig-dev libmigemo-dev libasound2-dev - config_args: --with-tls=openssl --with-regex=glib --with-sessionlib=no --with-migemo --disable-compat-cache-dir packages: libssl-dev libmigemo-dev - config_args: --with-tls=openssl --with-regex=oniguruma --with-sessionlib=xsmp --with-alsa --with-pangolayout packages: libssl-dev libonig-dev libasound2-dev steps: - uses: actions/checkout@v2 - name: dependencies installation (${{ matrix.deps.packages }}) run: | sudo apt update sudo apt install autoconf-archive libgtest-dev libtool libltdl-dev libgtkmm-3.0-dev ${{ matrix.deps.packages }} g++-7 - name: autoreconf -i run: autoreconf -i - name: ./configure ${{ matrix.deps.config_args }} run: ./configure ${{ matrix.deps.config_args }} - name: make -j$(nproc) run: make -j$(nproc) - name: make check -j$(nproc) run: make check -j$(nproc) - name: ./src/jdim -V run: ./src/jdim -V library-20: runs-on: ubuntu-20.04 env: CC: gcc-9 CXX: g++-9 strategy: matrix: deps: - config_args: -Dtls=gnutls -Dregex=oniguruma -Dsessionlib=xsmp -Dmigemo=enabled -Dalsa=enabled -Dpangolayout=enabled packages: libgnutls28-dev libonig-dev libmigemo-dev libasound2-dev - config_args: -Dtls=openssl -Dregex=glib -Dsessionlib=no -Dmigemo=enabled -Dcompat_cache_dir=disabled packages: libssl-dev libmigemo-dev - config_args: -Dtls=openssl -Dregex=oniguruma -Dsessionlib=xsmp -Dalsa=enabled -Dpangolayout=enabled packages: libssl-dev libonig-dev libasound2-dev steps: - uses: actions/checkout@v2 - name: dependencies installation (${{ matrix.deps.packages }}) run: | sudo apt update sudo apt install meson libgtest-dev libtool libltdl-dev libgtkmm-3.0-dev ${{ matrix.deps.packages }} g++-9 - name: meson builddir -Dbuildtype=debug -Dcpp_args="-D_DEBUG" ${{ matrix.deps.config_args }} run: meson builddir -Dbuildtype=debug -Dcpp_args="-D_DEBUG" ${{ matrix.deps.config_args }} - name: ninja -C builddir run: ninja -C builddir - name: meson test -C builddir run: meson test -C builddir - name: ./builddir/src/jdim -V run: ./builddir/src/jdim -V manual: runs-on: ubuntu-18.04 env: NOKOGIRI_USE_SYSTEM_LIBRARIES: true steps: - uses: actions/checkout@v2 - name: dependencies installation run: | sudo apt update sudo apt install ruby-dev ruby-bundler libcurl4-openssl-dev libxslt1-dev - name: make -j$(nproc) -C docs build run: make -j$(nproc) -C docs build jdim-0.7.0/.gitignore000066400000000000000000000013471417047150700144440ustar00rootroot00000000000000buildinfo.h src/jdim src/**/*.a src/icons/*.h !src/icons/iconmanager.h !src/icons/iconid.h !src/icons/iconfiles.h # generated by autoreconf -i Makefile Makefile.in aclocal.m4 autom4te.cache/ compile config.guess config.h.in config.sub configure depcomp install-sh ltmain.sh missing # generated by ./configure config.h* config.log config.status libtool src/.deps/ src/article/.deps/ src/bbslist/.deps/ src/board/.deps/ src/config/.deps/ src/control/.deps/ src/dbimg/.deps/ src/dbtree/.deps/ src/history/.deps/ src/icons/.deps/ src/image/.deps/ src/jdlib/.deps/ src/message/.deps/ src/skeleton/.deps/ src/sound/.deps/ src/xml/.deps/ stamp-h1 test-driver test/.deps # generated by make *.o # docs/ docs/.bundle/ docs/Gemfile.lock docs/_site/ jdim-0.7.0/AUTHORS000066400000000000000000000000741417047150700135200ustar00rootroot00000000000000https://jdimproved.github.io/JDim/ を見てください。 jdim-0.7.0/CONTRIBUTING.md000066400000000000000000000111101417047150700146720ustar00rootroot00000000000000# JDimへコントリビュートする はじめに、ガイドを読む時間を取っていただきありがとうございます。:+1: 私達はJDimを開発・保守してくためにパッチを歓迎しています!:revolving_hearts: JDimへのコントリビュート方法を案内します。 * :beginner: まずはJDimproved/JDimをforkしてリポジトリを用意し、ローカルにcloneしてJDimをビルドしてみましょう。 → [README.md][readme-md] * :mag: [Issues][issues]や[Pull requests][pull-requests]を確認すると興味関心のあるトピックが見つかるかもしれません。 * :earth_asia: [Linux板@5ちゃんねる][linux-5ch]のJD/JDimスレで質問やバグ報告をすることもできます。 * :heart_decoration: JDimはボランティアによって開発・保守されています。 そのため速やかに返信することが難しい場合がありますが予めご了承ください。 ## :question: 質問をしたい Discussionsを開いて質問をします。 → [New discussion][new-discussion] 使い方や設定方法の情報は[オンラインマニュアル][manual]や 2ch/5chのスレッド・過去ログにありますのでそちらも参照してください。 ## :beetle: バグを報告したい Issueを開いてバグを報告します。 → [Issue: 不具合(バグ)の報告][new-bug-report] 以下の情報は原因特定の手がかりになりますのでご報告くださいますようお願いいたします。 * 動作環境(クリップボードへのコピーを利用してください) * やりたいこと・期待する動作 * バグが現れるまでの手順・操作 * 実際の結果・エラーメッセージ バグなのかはっきりしない場合はDiscussionsで質問してみてください。 → [New discussion][new-discussion] ## :muscle: 機能のリクエストをしたい Issueを開いて欲しい機能の要望を出しフィードバックを集めます。 → [Issue: 機能の変更][new-feature-request] 可能ならコードを書き始めてください。実証用でもコードがあると具体的なフィードバックが集まりやすいです。 ## :mailbox_with_mail: Pull requestを提出する Pull requestは`master`ブランチに対してお願いいたします。 * 文書やソースコードのタイプミス修正、バグ修正、文書の改善、機能の改善などは直接PRを受け付けています。 * ユーザーインタフェースの変更や互換性に影響が出る修正は最初にissueを開いて意見・要望をお伝えいただければ幸いです。 * 影響が大きい変更は[RFCプロセス][rfcs]が必要になる場合があります。 * オンラインマニュアルの編集については [docs/README.md][docs-readme] を参照してください。 * テストケースを追加して動作をチェックすることもできます。詳細は [test/README.md][test-readme] を参照してください。 #### :pencil: C++ソースコードを修正するときの注意 * C++14の機能を使う。迷ったときは[C++ Core Guidelines][isocpp]を参考にする。 * C++17の機能はgcc-6が[サポート][support] [\[1\]][lang] [\[2\]][lib]していれば使ってもよい。(メンテナーと相談) * コーディングスタイルは周囲のコードになるべく合わせる。 * ソースコードを修正したときはビルド可能なことチェックする。 * 修正前よりコンパイル時警告を増やさないように気をつける。 [readme-md]: https://github.com/JDimproved/JDim/tree/master/README.md [issues]: https://github.com/JDimproved/JDim/issues [pull-requests]: https://github.com/JDimproved/JDim/pulls [linux-5ch]: https://mao.5ch.net/linux/ [new-discussion]: https://github.com/JDimproved/JDim/discussions/new [new-bug-report]: https://github.com/JDimproved/JDim/issues/new?assignees=&labels=bug&template=bug-report.md&title= [new-feature-request]: https://github.com/JDimproved/JDim/issues/new?assignees=&labels=feature&template=feature-request.md&title= [manual]: https://jdimproved.github.io/JDim/ [rfcs]: https://github.com/JDimproved/rfcs [docs-readme]: https://github.com/JDimproved/JDim/tree/master/docs/README.md [test-readme]: https://github.com/JDimproved/JDim/tree/master/test/README.md [isocpp]: https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines [support]: https://en.cppreference.com/w/Template:cpp/compiler_support/17 [lang]: https://gcc.gnu.org/projects/cxx-status.html#cxx17 [lib]: https://gcc.gnu.org/onlinedocs/libstdc++/manual/status.html#status.iso.2017 jdim-0.7.0/COPYING000066400000000000000000000431101417047150700135010ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. jdim-0.7.0/ChangeLog000066400000000000000000000013041417047150700142170ustar00rootroot00000000000000[ 更新履歴 ] 以前の更新履歴は"https://jdimproved.github.io/JDim/"を参照してください。 svnのコミットログを知りたい場合は svn log -r 1000:HEAD http://svn.sourceforge.jp/svnroot/jd4linux/jd/trunk などの様にしてsvnサーバから直接取得してください。 (1000のところは適当なリビジョン番号にしてください) ------------------------------------------------------------------------ 2.8.9-150226 - 2015/02/26(木) ------------------------------------------------------------------------ * リロード待ち状態でスレが更新されないままになる問題を修正した * タブ表示をさらに最適化した jdim-0.7.0/INSTALL000066400000000000000000000131271417047150700135040ustar00rootroot00000000000000[ make、実行方法について ] * 動作環境 必須環境 ・gtkmm 3.22.0 以上 ・glibmm 2.50.0 以上 ( 2.56.0 未満のサポートは将来のリリースで廃止される ) ・zlib 1.2 以上 ・gnutls 3.5.8 以上 ( 3.5.18 未満のサポートは将来のリリースで廃止される ) 推奨環境 ・Linux Kernel 3.10 以上 ( 4.4.0 未満は将来のリリースで推奨環境から外れる ) ・gtkmm 3.24.0 以上 ・UTF-8環境 ( EUC環境では LANG="ja_JP.UTF-8" を指定する必要がある ) ※ GTK2版はv0.4.0リリースをもって廃止 ( https://github.com/JDimproved/JDim/issues/229 を参照 ) * makeに必要なツール、ライブラリ 必須 ・autoconf ・autoconf-archive ・automake ・g++ 6 以上、または clang++ 5.0 以上 ( 将来のリリースで g++ 7 以上、または clang++ 6.0 以上になる ) ・gnutls ・gtkmm ・libtool ・make ・zlib オプション ・meson 0.49.0 以上 ・alsa-lib (--with-alsa) ・openssl (--with-tls=openssl, バージョン 1.1.0 未満のサポートは将来のリリースで廃止される ) ・oniguruma (--with-regex=oniguruma, 廃止予定) ・migemo (--with-migemo) ・googletest (`test/RADME.md`を参照) 画像表示に必要なパッケージ インストールされていない環境では`.webp`や`.avif`で終わるURLは通常リンクになる。 ・libwebp, webp-pixbuf-loader (WebP) ・libavif (AVIF) OSやディストリビューション別の解説は https://github.com/JDimproved/JDim/discussions/592 を参照。 configure のかわりに meson を使ってビルドする方法は https://github.com/JDimproved/JDim/discussions/556 を参照。 (v0.4.0+から暫定サポート) GTKがデフォルトでサポートしていないWebPやAVIF形式の画像を表示する方法は https://github.com/JDimproved/JDim/discussions/737 を参照。(v0.5.0+からサポート) * ビルド方法( configure + make の場合 ) 1. autoreconf -i ( 又は ./autogen.sh ) 2. ./configure 3. make 4. (お好みで) strip src/jdim * ビルド方法( meson の場合 ) 1. meson builddir 2. meson compile -C builddir ( 又は ninja -C builddir ) 3. 起動は ./builddir/src/jdim mesonのビルドオプション - meson builddir -Dregex=glib のように指定する。 - オプションの一覧は meson configure を実行してProject optionsの段落を参照する。 * configureオプション --with-sessionlib=xsmp|no XSMP を使ってセッション管理をするには「xsmp」を、セッション管理を無効にするには「no」を選択する。 デフォルトでは XSMP を使用する。 --with-pangolayout 描画にPangoLayoutを使う。デフォルトでは PangoGlyphString を使用する。 スレビューのテキスト表示に問題があるときはこのオプションを試してみてください。 --with-migemo migemoによる検索が有効になる。migemoがUTF-8の辞書でインストールされている必要がある。 有効にすると正規表現のメタ文字が期待通りに動作しない場合があるので注意すること。 --with-migemodict=PATH (--with-migemo 限定) migemo の辞書ファイルの場所を設定する。 about:config で変更が可能、空欄にした場合は migemo が無効になる。(変更後は要再起動) --with-native CPUに合わせた最適化 CPUを指定する場合は ./configure CXXFLAGS="-march=ARCH" を利用してください。 --with-tls=[gnutls|openssl] 使用するSSL/TLSライブラリを設定する。デフォルトでは GnuTLS を使用する。 --with-tls=openssl GnuTLS のかわりに OpenSSL を使用する。ライセンス上バイナリ配布が出来なくなることに注意すること。 --with-alsa ALSAによる効果音再生機能を有効にする。詳しくは"https://jdimproved.github.io/JDim/"の項を参照すること。 --enable-gprof gprofによるプロファイリングを行う。コンパイルオプションに -pg が付き、JDimを実行すると gmon.out が出来るのでgprof./jdgmon.out で解析できる。CPUの最適化は効かなくなるので注意する。 --with-regex=oniguruma|glib 使用する正規表現ライブラリを設定する。 デフォルトでは Glib Regex(GRegex) を使用する。(v0.4.0+から変更) 非推奨: 正規表現ライブラリを選択するオプションは将来廃止される。 https://github.com/JDimproved/JDim/issues/697 を参照すること。 --with-regex=oniguruma 非推奨: GRegex のかわりに鬼車を使用する。 鬼車はBSDライセンスなのでJDimをバイナリ配布する場合には注意すること(ライセンスはGPLになる)。 --with-regex=glib Perl互換の正規表現なので、従来の POSIX 拡張の正規表現から設定変更が必要になる場合がある。 (v0.3.0+から追加) --disable-compat-cache-dir JDのキャッシュディレクトリ ~/.jd を読み込む互換機能を無効化する。 * メモ 最近のディストリビューションの場合は autogen.sh よりも autoreconf -i の方を推奨。 実行するには直接 src/jdim を起動するか手動で /usr/bin あたりに src/jdim を cp する。 以上の操作でmakeが通らなかったり動作が変な時は configure のオプションを変更する。 jdim-0.7.0/Makefile.am000066400000000000000000000046301417047150700145060ustar00rootroot00000000000000SUBDIRS = src test desktopdir= $(datadir)/applications dist_desktop_DATA = jdim.desktop icondir = $(datadir)/icons/hicolor/48x48/apps dist_icon_DATA = jdim.png iconsvgdir = $(datadir)/icons/hicolor/scalable/apps dist_iconsvg_DATA = jdim.svg metainfodir = $(datadir)/metainfo dist_metainfo_DATA = jdim.metainfo.xml EXTRA_DIST = AUTHORS TODO README configure jdim_BUILDINFO_HEADER = buildinfo.h jdim_CONFIGURE_ARGS = @ac_configure_args@ all: $(jdim_BUILDINFO_HEADER) test: all $(MAKE) -C $@ $(MAKECMDGOALS) .PHONY: test $(jdim_BUILDINFO_HEADER) $(jdim_BUILDINFO_HEADER): @-rm -f $@.new @echo '/* This file is generated from Makefile by make. */' >> $@.new @echo '#ifndef _BUILDINFO_H' >> $@.new @echo '#define _BUILDINFO_H' >> $@.new @echo '' >> $@.new @echo '// Build information.' >> $@.new @if test -n "$(jdim_CONFIGURE_ARGS)"; \ then \ echo "#define CONFIGURE_ARGS \"$(jdim_CONFIGURE_ARGS)\"" >> $@.new; \ fi @echo '' >> $@.new @echo '// Version information of GIT.' >> $@.new @GIT_HASH="" ; \ GIT_DATE="" ; \ GIT_DIRTY=0; \ GIT_STATUS="" ; \ if test -n "$(GIT)" ; \ then \ WORK_TREE="$(abs_top_srcdir)" ; \ GIT_HASH=`LANG=C.utf8 "$(GIT)" -C "$${WORK_TREE}" log --pretty=format:%h --abbrev=10 -n 1 2>/dev/null`; \ GIT_DATE=`LANG=C.utf8 "$(GIT)" -C "$${WORK_TREE}" log --pretty=format:%ad --date=format:%Y%m%d -n 1 2>/dev/null`; \ GIT_STATUS="`LANG=C.utf8 "$(GIT)" -C "$${WORK_TREE}" status -uno -s 2>/dev/null | head -n 1`" ; \ if test -n "$${GIT_HASH}" ; \ then \ echo "GIT: Hash = \"$${GIT_HASH}\"" ; \ fi ; \ if test -n "$${GIT_DATE}" ; \ then \ echo "GIT: Date = \"$${GIT_DATE}\"" ; \ fi ; \ if test -n "$${GIT_STATUS}" ; \ then \ echo "GIT: There are changes not committed yet." ; \ GIT_DIRTY=1 ; \ fi \ fi ; \ echo "#define GIT_HASH \"$${GIT_HASH}\"" >> $@.new ; \ echo "#define GIT_DATE \"$${GIT_DATE}\"" >> $@.new ; \ echo "#define GIT_DIRTY $${GIT_DIRTY}" >> $@.new @echo '' >> $@.new @echo '#endif' >> $@.new @if test ! -e $@; \ then \ echo '$@: Created.'; \ mv -f $@.new $@; \ elif test -z "$(XSUM)"; \ then \ echo '$@: Refreshed.'; \ mv -f $@.new $@; \ else \ HASH1=`cat $@ | $(XSUM)`; \ HASH2=`cat $@.new | $(XSUM)`; \ if test "$${HASH1}" = "$${HASH2}"; \ then \ echo '$@: Not modified.'; \ rm -f $@.new; \ else \ echo '$@: Modified.'; \ mv -f $@.new $@; \ fi \ fi jdim-0.7.0/NEWS000066400000000000000000000000741417047150700131470ustar00rootroot00000000000000https://jdimproved.github.io/JDim/ を見てください。 jdim-0.7.0/README000066400000000000000000000000421417047150700133230ustar00rootroot00000000000000README.mdを見てください。 jdim-0.7.0/README.md000066400000000000000000000344721417047150700137400ustar00rootroot00000000000000# JDim - 2ch browser for linux ![GitHub Actions CI](https://github.com/JDimproved/JDim/workflows/CI/badge.svg) [![Snap Store](https://snapcraft.io/jdim/badge.svg)](https://snapcraft.io/jdim) ここに書かれていない詳細は [オンラインマニュアル][manual] を、 開発に参加するための手順については [CONTRIBUTING][contrib] や [RFC][rfcs] を参照してください。 [manual]: https://jdimproved.github.io/JDim/ [repository]: https://github.com/JDimproved/JDim [rfcs]: https://github.com/JDimproved/rfcs "Request for Comments" * [概要](#概要) * [動作プラットフォーム](#動作プラットフォーム) * [導入方法](#導入方法) * [事前準備](#事前準備) * [ビルド](#ビルド) * [Snapパッケージ](#Snapパッケージ) * [通常の起動](#通常の起動) * [コマンドライン オプション](#コマンドライン-オプション) * [多重起動について](#多重起動について) * [実行時の注意事項](#precaution) * [JDとの互換性](#JDとの互換性) * [JDから移行する](#JDから移行する) * [著作権](#著作権) * [ライセンス](#ライセンス) * [連絡先](#連絡先) ## 概要 JDim (JD improved) は gtkmm/GTK+ を使用した"2ちゃんねる"型マルチスレッドBBSを閲覧するためのブラウザです。 JDim は GPLv2 の下で公開されている [JD][jd-project] からforkしたソフトウェアであり、 ルック・アンド・フィールや環境設定は JD と互換性があります。 **注意: JDim本体は5ch.netのAPIに対応しておりません。** ご不便をおかけして申し訳ありませんが、5ch.netにアクセスする場合はWebブラウザなどをご使用ください。 [jd-project]: https://jd4linux.osdn.jp/ ## 動作プラットフォーム LinuxなどのUnixライクなOS(FreeBSD,OpenBSD,Nexenta,MacOSXでも動作報告例があります)。 ##### サポートの最新情報 gccのバージョンが7未満のプラットフォームはサポートを終了する予定となりました。 Ubuntu 18.04(2018年)より前にリリースされたディストリビューションを利用されている場合は更新をお願いいたします。 メンテナンスの都合によりWindows(MinGW)版のサポートは[終了][#445]しました。 [#445]: https://github.com/JDimproved/JDim/issues/445 ## 導入方法 ソースコードからJDimをビルドします。**GTK3版がビルド**されますのでご注意ください。 詳細は [INSTALL](./INSTALL) にも書いてあります。 ビルドツール [meson][mesonbuild] を暫定的にサポートしています。 configure のかわりに meson を使ってビルドする方法は [Discussions][dis556] を参照してください。 [mesonbuild]: https://mesonbuild.com [dis556]: https://github.com/JDimproved/JDim/discussions/556 "Mesonを使ってJDimをビルドする方法 - Discussions #556" ### 事前準備 ツールチェーンとライブラリをインストールします。一度インストールすれば次回から事前準備はいりません。 #### Redhat系 ```sh dnf install gtkmm30-devel gnutls-devel libSM-devel libtool automake autoconf-archive git ``` #### Debian (stretch-backportsあるいはbuster以降) ```sh sudo apt install libc6-dev make gcc g++ git sudo vi /etc/apt/sources.list # ↑エディタは何でも良い。deb-src行でstretch-backportsあるいはbuster以降を有効にする sudo apt update sudo apt build-dep jdim ``` #### Ubuntu 18.04 開発環境が入っていない場合は、 ```sh sudo apt install build-essential automake autoconf-archive git libtool ``` 必要なライブラリを入れます。(抜けがあるかも) ```sh sudo apt install libgtkmm-3.0-dev libmigemo1 libasound2-data libltdl-dev libasound2-dev libgnutls28-dev ``` ### ビルド ```sh git clone -b master --depth 1 https://github.com/JDimproved/JDim.git jdim cd jdim autoreconf -i ./configure make ``` 実行するには直接 src/jdim を起動するか手動で /usr/bin あたりに src/jdim を cp します。 #### Arch Linux ビルドファイルはAURで公開されています。(Thanks to @naniwaKun.) https://aur.archlinux.org/packages/jdim-git/ AUR Helper [yay](https://github.com/Jguer/yay) でインストール ```sh yay -S jdim-git ``` ### 参考 詳しいインストールの方法は [本家のwiki](https://ja.osdn.net/projects/jd4linux/wiki/OS%2f%E3%83%87%E3%82%A3%E3%82%B9%E3%83%88%E3%83%AA%E3%83%93%E3%83%A5%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E5%88%A5%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB%E6%96%B9%E6%B3%95) を参照してください。 ### Tips * **buildの高速化** make するときに `-j job数`(並列処理の数) を指定すれば高速にコンパイルできます。 使用するCPUのコア数と相談して決めてください。 * **CPUに合わせた最適化** `./configure`を実行するときにCPUの種類(`-march=ARCH`や`-mcpu=CPU`)と最適化レベル(`-O`)を`CXXFLAGS`に設定します。 ###### 例 (第2世代Coreプロセッサー) ```sh ./configure CXXFLAGS="-march=sandybridge -O2" ``` マシンのCPUは下のコマンドで調べることができます。([GCCの最適化][gentoo-gcc] - Gentoo Wikiより) ```sh gcc -Q -c -march=native --help=target -o /dev/null | grep "march\|mtune\|mcpu" ``` * **configureチェック中に `AX_CXX_COMPILE_STDCXX(17, noext, mandatory)` に関連したエラーがでた場合** ubuntuでは `autoconf-archive` をインストールして `autoreconf -i` からやり直してみてください。 パッケージが見つからないまたはエラーが消えない場合は以下の手順を試してみてください。 または`./configure`のかわりにMesonを利用してビルドする方法があります。([GitHub][dis556]を参照) 1. `configure.ac` の `AX_CXX_COMPILE_STDCXX([17], [noext], [mandatory])` の行を削除する。 2. `autoreconf -i` で `configure` を作りconfigureチェックをやり直す。 3. `make CXXFLAGS+="-std=c++1z"` でビルドする。 もしこれで駄目な場合はgccのversionが古すぎるので、 gccのバージョンアップをするか、ディストリをバージョンアップしてください。 [gentoo-gcc]: https://wiki.gentoo.org/wiki/GCC_optimization/ja#-march ### Snapパッケージ JDim はSnapパッケージとして[Snap Storeで公開][snapcraft]されています。 詳細は[マニュアル][manual-snap]を参照してください。 [snapcraft]: https://snapcraft.io/jdim [manual-snap]: https://jdimproved.github.io/JDim/start#snap ## 通常の起動 使い方は以下のとおりです。 ```sh $ jdim [OPTION] [URL,FILE] ``` 引数にURLを付けて起動する事も出来るので、他のアプリケーションから外部コマンドとしてURLを開く事などが出来ます。 (JDimが扱う事の出来るURLでない場合は設定されているWebブラウザに渡されます) ```sh $ jdim http://pc99.2ch.net/test/read.cgi/linux/1234567890/ ``` ローカルにあるdatファイルを指定して、一時的にスレビュー表示させることも出来ます。 ```sh $ jdim ./12345.dat ``` 環境変数 `JDIM_CACHE` でキャッシュディレクトリの位置を変更・指定することが可能です。 指定しなければ下記の[優先順位](#キャッシュディレクトリの優先順位)の通りに決まります。 ```sh $ JDIM_CACHE=~/.mycache jdim ``` 環境変数 `JDIM_LOCK` でロックファイルの位置を変更・指定することが可能です。 指定しなければ `<キャッシュディレクトリ>/JDLOCK` がロックファイルになります。 ```sh $ JDIM_LOCK=~/mylock jdim ``` #### キャッシュディレクトリの優先順位 | `~/.jd` | `$XDG_CACHE_HOME/jdim` | 使われるのは… | | --- | --- | --- | | 存在する | any | `~/.jd` | | 存在しない | any | `$XDG_CACHE_HOME/jdim` | | any (無効化) | any | `$XDG_CACHE_HOME/jdim` | NOTE: - 環境変数 `XDG_CACHE_HOME` が未設定または空のときはかわりに `$HOME/.cache/jdim` が使われます。 - `~/.jd` が無効化されている場合は `jdim --version` の出力に `--disable-compat-cache-dir` が追加されます。 - キャッシュディレクトリはJDimを起動すると作成されます。 ### コマンドライン オプション オプション | 説明 --- | --- -h, --help | ヘルプを表示 -m, --multi | 多重起動時のサブプロセスであっても終了しない -s, --skip-setup | 初回起動時の設定ダイアログを表示しない -l, --logfile | エラーなどのメッセージをファイル(キャッシュディレクトリのlog/msglog)に出力する -g, --geometry WxH-X+Y | 幅(W)高さ(H)横位置(X)縦位置(Y)の指定。WxHは省略可能(例: -g 100x40-10+30, -g -20+100 ) -V, --version | バージョン及びconfigureオプションを全て表示 ## 多重起動について [マニュアル][manual-multiple]を参照してください。 [manual-multiple]: https://jdimproved.github.io/JDim/start/#multiple "起動について | JDim" ## 実行時の注意事項 廃止されたGTK2版同様のルック・アンド・フィールになるように実装していますが、 技術的な問題やテスト不足から完全な再現はできていません。 もしお気づきの点などがございましたらご指摘いただけると幸いです。 ### Wayland対応 JDim はWayland環境で起動しますが動作は安定していません。 GTKのバックエンドにWaylandを使うかわりに互換レイヤーのXWaylandをインストールして使うことをお薦めします。 環境変数 `GDK_BACKEND=x11` を設定してjdimを起動してください。 ```sh # シェルからJDimを起動する場合 GDK_BACKEND=x11 ./src/jdim ``` WaylandやXWaylandではX11限定の機能を使うことができないため注意してください。 ### GTK2版から変更/追加された部分 * GTK+ 3.14以上の環境でタッチスクリーンによる操作に対応した。 スレビューのタッチ操作については[マニュアル][manual-touch]を参照。 * 書き込みビューの配色にGTKテーマを使う設定が追加された。 1. メニューバーの`設定(C) > フォントと色(F) > 詳細設定(R)...`からフォントと色の詳細設定を開く 2. `色の設定`タブにある`書き込みビューの配色設定に GTKテーマ を用いる(W)`をチェックして適用する * GTK 3.16以上の環境で書き込みビューのダブルクリックによる単語の範囲選択、 トリプルクリックによる行の範囲選択に対応した。 [manual-touch]: https://jdimproved.github.io/JDim/operation/#threadview_touch "操作方法について | JDim" ### 既知の問題 * タブのドラッグ・アンド・ドロップの矢印ポップアップの背景が透過しない環境がある。 (アルファチャンネルが利用できない環境) * Wayland上で起動したときポップアップ内のアンカーからポップアップを出すとマウスポインターから離れた位置に表示される。 * Weston(Waylandコンポジタ)環境でXWaylandをバックエンドに指定して起動した場合、右クリックしながらポップアップ内に マウスポインターを動かすとポップアップ内容ではなくポップアップに隠れたスレビューに反応する。 * 32bit OSの古いMATE環境(バージョン[1.10][mate-1-10]から[1.16][mate-1-16]?)でJDim GTK3版を実行したとき スレビューの上に別のウインドウを重ねて移動させると残像でスレビュー内のみ描画が乱れる。 スレビューをスクロール等させて再描画すると直る。([背景事情][mate-background]) * Wayland環境では画像ビュー(ウインドウ表示)のフォーカスが外れたら折りたたむ機能が正常に動作しない。 [mate-1-10]: https://mate-desktop.org/blog/2015-06-11-mate-1-10-released/ "GTK3の実験的なサポート追加" [mate-1-16]: https://mate-desktop.org/blog/2016-09-21-mate-1-16-released/ "GTK3に移行途中" [mate-background]: https://github.com/JDimproved/JDim/commit/ffbce60ede#commitcomment-40911816 "別の不具合が再発する" ## JDとの互換性 JDimの環境設定はJDからフォーマットを継承しているので後方互換性があります。 また、ユーザーインタフェースの変更は今のところありません。 JDimで追加された不具合や機能の修正については[Pull requests][pr-merged]を見てください。 [pr-merged]: https://github.com/JDimproved/JDim/pulls?q=is%3Apr+is%3Amerged ### JDから移行する * 後方互換性としてJDのキャッシュディレクトリ(`~/.jd`)はそのまま使うことができます。 ただし、オプション`--disable-compat-cache-dir`が指定されたビルドでは互換機能は無効化されます。 * 互換機能が使えないときは `$XDG_CACHE_HOME/jdim`(`~/.cache/jdim`) にキャッシュディレクトリを移動してください。 ```bash $ mv ~/.jd ~/.cache/jdim ``` * 環境変数`JD_CACHE`でキャッシュディレクトリを設定している場合はかわりに`JDIM_CACHE`を使用してください。 * JDとJDimを併存させる(データや設定を共有しない)ためには環境変数によるキャッシュディレクトリの設定が必要です。 ([通常の起動](#通常の起動)を参照) ## 著作権 © 2017-2019 yama-natuki [https://github.com/yama-natuki/JD] © 2019-2022 JDimproved project [https://github.com/JDimproved/JDim] パッチやファイルを取り込んだ場合、それらのコピーライトは「JDimproved project」に統一します。 fork元の JD: © 2006-2015 JD project [https://ja.osdn.net/projects/jd4linux/] ## ライセンス JDim は GPLv2 の下で公開されています。 [GNU General Public License, version 2][lisence] [lisence]: https://github.com/JDimproved/JDim/blob/master/COPYING ## 連絡先 バグ報告その他は [Linux板@5ちゃんねる](http://mao.5ch.net/linux/) のJD/JDimスレ、 またはJDimのリポジトリにて行ってください。詳しい方法は[ガイド][contrib]をご覧ください。 [contrib]: https://github.com/JDimproved/JDim/tree/master/CONTRIBUTING.md jdim-0.7.0/TODO000066400000000000000000000000001417047150700131250ustar00rootroot00000000000000jdim-0.7.0/autogen.sh000077500000000000000000000037311417047150700144540ustar00rootroot00000000000000#!/bin/sh if test ! -f install-sh ; then touch install-sh ; fi MAKE=`which gnumake` if test ! -x "$MAKE" ; then MAKE=`which gmake` ; fi if test ! -x "$MAKE" ; then MAKE=`which make` ; fi HAVE_GNU_MAKE=`$MAKE --version|grep -c "Free Software Foundation"` if test "$HAVE_GNU_MAKE" != "1"; then echo Only non-GNU make found: $MAKE else echo `$MAKE --version | head -1` found fi if which autoconf2.50 >/dev/null 2>&1 then AC_POSTFIX=2.50 elif which autoconf259 >/dev/null 2>&1 then AC_POSTFIX=259 elif which autoconf >/dev/null 2>&1 then AC_POSTFIX="" else echo 'you need autoconfig (2.58+ recommended) to generate the Makefile' exit 1 fi echo checking autoconf$AC_POSTFIX ... echo `autoconf$AC_POSTFIX --version | head -1` found unset AM_POSTFIX for num in 10 9 8 7 ; do if which automake-1.$num > /dev/null 2>&1 ; then AM_POSTFIX=-1.$num break fi done if test -z "$AM_POSTFIX" ; then for num in 19 18 17 ; do if which automake$num > /dev/null 2>&1 ; then AM_POSTFIX=$num break fi done fi if test -z "$AM_POSTFIX" ; then if ! which automake > /dev/null 2>&1 ; then echo 'you need automake (1.8.3+ recommended) to generate the Makefile' exit 1 fi fi echo checking automake$AM_POSTFIX ... echo `automake$AM_POSTFIX --version | head -1` found if which libtoolize15 >/dev/null 2>&1 then LB_POSTFIX=15 elif which libtoolize >/dev/null 2>&1 then LB_POSTFIX="" else echo 'you need libtoolize to generate the Makefile' exit 1 fi echo checking libtoolize$LB_POSTFIX ... echo `libtoolize$LB_POSTFIX --version | head -1` found if test -d /usr/local/share/aclocal ; then ACLOCAL_INCLUDE="-I /usr/local/share/aclocal" fi echo This script runs configure and make... echo You did remember necessary arguments for configure, right? # autoreconf$AC_POSTFIX -fim _might_ do the trick, too. # chose to your taste aclocal$AM_POSTFIX $ACLOCAL_INCLUDE libtoolize$LB_POSTFIX --force --copy autoheader$AC_POSTFIX automake$AM_POSTFIX --add-missing --copy --gnu autoconf$AC_POSTFIX #./configure $* && $MAKE jdim-0.7.0/configure.ac000066400000000000000000000177251417047150700147510ustar00rootroot00000000000000dnl dnl JDim用 configure.ac dnl AC_PREREQ([2.69]) AC_INIT([jdim],[1.0]) AM_INIT_AUTOMAKE AC_CONFIG_HEADERS(config.h) AC_PROG_CC AC_PROG_CPP AC_PROG_CXX AC_PROG_CXXCPP LT_INIT AC_PROG_MKDIR_P AC_C_BIGENDIAN AC_LANG([C++]) AX_CXX_COMPILE_STDCXX([17], [noext], [mandatory]) dnl dnl buildinfo.h dnl AC_DEFINE(HAVE_BUILDINFO_H, 1, Define to 1 if you have the 'buildinfo.h' header file.) AC_PATH_PROG(GIT, git) AC_PATH_PROG(XSUM, md5sum, [cksum]) AC_SUBST(ac_configure_args) dnl --------------------------------------------------- dnl --------------------------------------------------- dnl dnl ユーザー設定 dnl dnl 追加コンパイルオプション dnl -Wextraで有効になる-Wunused-parameterは修正方法の検討が必要なので暫定的に無効 CXXFLAGS="-ggdb -Wall -Wextra -Wno-unused-parameter -pedantic $CXXFLAGS" CPPFLAGS="-DGTK_DOMAIN='\"gtk30\"' $CPPFLAGS" dnl --------------------------------------------------- dnl --------------------------------------------------- LIBSM_CFLAGS="" LIBSM_LIBS="" dnl dnl パッケージのチェック dnl dnl dnl gtkmm dnl PKG_CHECK_MODULES(GTKMM, [gtkmm-3.0 >= 3.22.0]) AC_SUBST(GTKMM_CFLAGS) AC_SUBST(GTKMM_LIBS) dnl dnl crypt dnl AC_CHECK_LIB(crypt,crypt) AC_CHECK_LIB(crypt, crypt_r, [AC_DEFINE(HAVE_CRYPT_R, [1], [Define to 1 if you have the 'crypt_r' function])], ) dnl dnl zlib dnl AC_CHECK_HEADERS([zlib.h]) AC_CHECK_LIB(z,inflate) dnl dnl packages dependent on platform dnl dnl dnl any other POSIX systems dnl AC_CHECK_HEADERS([socket.h]) AC_CHECK_LIB(socket,socket) AC_CHECK_FUNC([timegm], [], [AC_DEFINE(NO_TIMEGM, 1, [Define to 1 if you do not have the 'timegm' function])]) dnl dnl X関連ライブラリ dnl PKG_CHECK_MODULES(X11, x11) AC_SUBST(X11_CFLAGS) AC_SUBST(X11_LIBS) dnl dnl セッション管理 dnl AC_MSG_CHECKING(for --with-sessionlib) AC_ARG_WITH(sessionlib, AS_HELP_STRING([--with-sessionlib=xsmp|no],[use session control library @<:@default=xsmp@:>@]), [with_sessionlib="$withval"], [with_sessionlib=xsmp]) AC_MSG_RESULT($with_sessionlib) AS_IF( [test "x$with_sessionlib" = xno], [], dnl XSMPを使ってセッション管理をする。libSMとlibICEが必要。無ければXSMPは無効になる [test "x$with_sessionlib" = xxsmp], [PKG_CHECK_MODULES(LIBSM, [sm >= 1.2], [], [ac_sm_ice_found=no]) PKG_CHECK_MODULES(LIBICE, [ice >= 1.0], [], [ac_sm_ice_found=no]) AS_IF( [test "x$ac_sm_ice_found" = xno], [AC_MSG_NOTICE([Disable XSMP due to LIBSM or LIBICE not found])], [LIBSM_CFLAGS="$LIBSM_CFLAGS $LIBICE_CFLAGS" LIBSM_LIBS="$LIBSM_LIBS $LIBICE_LIBS" AC_DEFINE(USE_XSMP, , [use xsmp]) AC_SUBST(LIBSM_CFLAGS) AC_SUBST(LIBSM_LIBS)] )], [AC_MSG_ERROR([session control library not found])] ) dnl dnl 正規表現ライブラリ dnl AC_MSG_CHECKING(for --with-regex) AC_ARG_WITH(regex, AS_HELP_STRING([--with-regex=oniguruma|glib],[use regular expression library @<:@default=glib@:>@]), [], [with_regex=glib]) AC_MSG_RESULT($with_regex) AS_IF( [test "x$with_regex" = xoniguruma], [PKG_CHECK_MODULES(ONIG, [oniguruma]) AC_SUBST(ONIG_CFLAGS) AC_SUBST(ONIG_LIBS) AC_CHECK_HEADERS([onigposix.h], , [AC_MSG_ERROR([onigposix.h not found])]) AC_CHECK_LIB([onig], [regexec], , [AC_MSG_ERROR([libonig.a not found])]) AC_MSG_WARN([--with-regex=onigruma is deprecated. See https://github.com/JDimproved/JDim/issues/697])], [test "x$with_regex" = xglib], [PKG_CHECK_MODULES(GLIB, [glib-2.0 >= 2.14.0])], [AC_MSG_ERROR([regular expression library not found])] ) dnl dnl TLS dnl AC_MSG_CHECKING(for --with-tls) AC_ARG_WITH(tls, AS_HELP_STRING([--with-tls=@<:@gnutls|openssl@:>@],[use TLS library @<:@default=gnutls@:>@]), [with_tls="$withval"], [with_tls=gnutls]) AC_MSG_RESULT($with_tls) AS_IF( [test "x$with_tls" = xgnutls], [PKG_CHECK_MODULES(GNUTLS, [gnutls >= 3.5.8]) AC_DEFINE(USE_GNUTLS, , [use gnutls]) AC_SUBST(GNUTLS_CFLAGS) AC_SUBST(GNUTLS_LIBS)], [test "x$with_tls" = xopenssl], [PKG_CHECK_MODULES(OPENSSL, [openssl >= 0.9]) AC_DEFINE(USE_OPENSSL, , [use openssl]) AC_SUBST(OPENSSL_CFLAGS) AC_SUBST(OPENSSL_LIBS)], [AC_MSG_ERROR([TLS library not found])] ) dnl dnl enable gprof dnl AC_MSG_CHECKING(for --enable-gprof) AC_ARG_ENABLE(gprof, AS_HELP_STRING([--enable-gprof],[enable gprof]), [enable_gprof="$enableval"], [enable_gprof=no]) AC_MSG_RESULT($enable_gprof) AS_IF( [test "x$enable_gprof" = xyes], [CXXFLAGS="$CXXFLAGS -pg"] ) dnl dnl checking migemo dnl AC_MSG_CHECKING(for --with-migemo) AC_ARG_WITH(migemo, AS_HELP_STRING([--with-migemo],[enable migemo search]), [with_migemo="$withval"], [with_migemo=no]) AC_MSG_RESULT($with_migemo) AS_IF( [test "x$with_migemo" = xyes], [AC_CHECK_HEADERS([migemo.h]) AC_CHECK_LIB(migemo, migemo_open)] ) AC_MSG_CHECKING(for --with-migemodict) AC_ARG_WITH(migemodict, AS_HELP_STRING([--with-migemodict=PATH],[specifiy the path of migemo dictionary]), [with_migemodict="$withval"], [with_migemodict=]) AC_MSG_RESULT($with_migemodict) AS_IF( [test -n "$with_migemodict"], [AC_DEFINE_UNQUOTED(MIGEMODICT, "$with_migemodict", [migemodict])] ) dnl dnl checking alsa dnl case "${host_os}" in linux*|*linux) AC_MSG_CHECKING(for --with-alsa) AC_ARG_WITH(alsa, AS_HELP_STRING([--with-alsa],[enable alsa]), [with_alsa="$withval"], [with_alsa=no]) AC_MSG_RESULT($with_alsa) AS_IF( [test "x$with_alsa" = xyes], [PKG_CHECK_MODULES(ALSA, [alsa >= 1.0]) AC_DEFINE(USE_ALSA, , [use alsa]) AC_SUBST(ALSA_CFLAGS) AC_SUBST(ALSA_LIBS)] ) ;; esac dnl dnl checking googletest dnl dnl テストプログラムは明示的にコマンド(make test)を実行しなければビルドされない dnl 1. 環境変数 GTEST_SRCDIR が設定されていればgoogletestのソースコードをコンパイルして使う dnl 2. インストールされたパッケージがあればライブラリを使う dnl 3. それ以外のときはmake testが失敗する AM_CONDITIONAL([USE_GTEST_SOURCES], [test -d "$GTEST_SRCDIR"]) dnl PKG_CHECK_MODULES()の4番目の引数(action-if-not-found)を空欄にすると dnl パッケージが見つからないときエラーになるためダミーの処理を入れる AM_COND_IF([USE_GTEST_SOURCES], [], [PKG_CHECK_MODULES(GTEST, [gtest_main >= 1.10.0], [], [ac_gtest_main_found=no])]) AC_SUBST(GTEST_CFLAGS) AC_SUBST(GTEST_LIBS) dnl dnl checking pangolayout dnl AC_MSG_CHECKING(for --with-pangolayout) AC_ARG_WITH(pangolayout, AS_HELP_STRING([--with-pangolayout],[use pangolayout]), [with_pangolayout="$withval"], [with_pangolayout=no]) AC_MSG_RESULT($with_pangolayout) AS_IF( [test "x$with_pangolayout" = xyes], [AC_DEFINE(USE_PANGOLAYOUT, , [use pangolayout])] ) dnl dnl checking compatible cache directory dnl AC_MSG_CHECKING(for --disable-compat-cache-dir) AC_ARG_ENABLE(compat-cache-dir, AS_HELP_STRING([--disable-compat-cache-dir],[disable compatible cache directory]), [enable_compat_cache_dir="$enableval"], [enable_compat_cache_dir=yes]) AS_IF( [test "x$enable_compat_cache_dir" = xyes], [AC_MSG_RESULT(no) AC_DEFINE(ENABLE_COMPAT_CACHE_DIR, 1, [Support for compatible cache directory.])], [AC_MSG_RESULT(yes)] ) dnl dnl CPUの最適化オプション dnl AS_IF( dnl gprofを利用する場合最適化オプションは無視される [test "x$enable_gprof" = xno], [AC_MSG_CHECKING(for --with-native) AC_ARG_WITH(native, AS_HELP_STRING([--with-native],[produce code optimized for the local machine]), [with_native="$withval"], [with_native=no]) AC_MSG_RESULT($with_native) AS_IF([test "x$with_native" != xno], [CXXFLAGS="$CXXFLAGS -march=native"])] ) AC_ARG_VAR(GTEST_SRCDIR, [path for googletest framework source directory]) AC_CONFIG_FILES([Makefile src/Makefile src/skeleton/Makefile src/jdlib/Makefile src/dbtree/Makefile src/dbimg/Makefile src/bbslist/Makefile src/board/Makefile src/article/Makefile src/image/Makefile src/message/Makefile src/history/Makefile src/config/Makefile src/icons/Makefile src/sound/Makefile src/xml/Makefile src/control/Makefile test/Makefile]) AC_OUTPUT jdim-0.7.0/docs/000077500000000000000000000000001417047150700133775ustar00rootroot00000000000000jdim-0.7.0/docs/Gemfile000066400000000000000000000002121417047150700146650ustar00rootroot00000000000000source 'https://rubygems.org' gem 'github-pages', group: :jekyll_plugins gem 'jekyll-redirect-from' gem 'jekyll-theme-cayman', '~> 0.1.1' jdim-0.7.0/docs/Makefile000066400000000000000000000010321417047150700150330ustar00rootroot00000000000000BUNDLE := bundle BUNDLE_DIR := .bundle/ GEMFILE_LOCK := Gemfile.lock JEKYLL := $(BUNDLE) exec jekyll DEST_DIR := _site/ .PHONY: all build clean distclean install run all: run build: $(BUNDLE_DIR) $(JEKYLL) build -d $(DEST_DIR) clean: $(JEKYLL) clean -d $(DEST_DIR) distclean: clean $(RM) -rf $(BUNDLE_DIR) $(RM) $(GEMFILE_LOCK) install: $(GEMFILE_LOCK) run: $(BUNDLE_DIR) $(JEKYLL) serve --watch -d $(DEST_DIR) $(GEMFILE_LOCK): $(BUNDLE) install --jobs $(shell nproc) --path $(BUNDLE_DIR) $(BUNDLE_DIR): $(GEMFILE_LOCK) jdim-0.7.0/docs/README.md000066400000000000000000000126241417047150700146630ustar00rootroot00000000000000# オンラインマニュアルのメンテナンスについて **※この項目は草案の段階です。** - [概要](#概要) - [ポリシー](#ポリシー) - [内容の更新について](#内容の更新について) - [JDimリリース時の作業について](#jdimリリース時の作業について) ## 概要 JDimのマニュアルは更新作業を簡素化するためMarkdownで記述し、 [GitHub pages][gh-pages]の機能([jekyll][jekyll])を利用してHTMLに変換する方法をとっています。 また、この文書は https://jd4linux.osdn.jp/maintenance_of_manual.html のコピーを起点としています。 ## ポリシー 一部のWebブラウザだけではなく、多くの環境で閲覧出来る事を目標にしています。 #### Markdownの利用 軽量マークアップ言語 [Markdown][gh-markdown] を使ってマニュアルを記述します。 HTMLのタグによるマークアップはなるべく使わないようにします。 #### スタイルについて スタイルはGitHub pagesで用意されているjekyllテーマを使用します。 ## 内容の更新について 1. gitリポジトリ `docs/` ディレクトリ以下にある `*.md` ファイルを修正します。 2. ローカルでjekyllを動かして修正したページの表示を確認してください。 jekyllの導入や実行は [Quickstart (jekyll)][jekyll-quickstart] を参照してください。
デフォルトの設定では `http://localhost:4000/JDim/` にサイトが公開されます。 3. Pull requestを提出します。([CONTRIBUTING.md][contributing]を参照) 4. マージされたらWebブラウザでアクセスして確認します。https://jdimproved.github.io/JDim/ #### 新規にファイルを追加する 他のファイルを参考に`docs/manual/`ディレクトリの中に作成して下さい。 #### 更新履歴を書く これ以降の`YYYY`は2011などの年数に読み替えて下さい。 `docs/manual/YYYY.md`のunreleasedの中に一行ずつ簡潔に書きます。 また、関連のIssueやPull requestへのリンクがあると参照しやすいです。例: ```markdown - Always include crypt.h header for crypt function ←PRのタイトル ([#1](https://github.com/JDimproved/JDim/pull/1)) ←PRの番号とURL ``` `api.github.com`からPull requestのデータを取得して変更履歴を作るコマンド (curl, jq, sed を使った例) ```sh # マージされた最新100件のPRから変更履歴を作る generate_changelogs () { API='https://api.github.com/repos/JDimproved/JDim/pulls?state=closed&base=master&per_page=100' QUERY='.[] | select(.merged_at != null) | .title, .html_url' curl "$API" | jq -r "$QUERY" | sed -e '1~2s/^ */- /' -e '2~2s%^.\+/\(.\+\)$% ([#\1](&))%' } generate_changelogs ``` ```sh # 特定のPRから変更履歴を作る generate_changelog () { API="https://api.github.com/repos/JDimproved/JDim/pulls/$1" curl "$API" | jq -r '.title, .html_url' | sed -e '1~2s/^ */- /' -e '2~2s%^.\+/\(.\+\)$% ([#\1](&))%' } generate_changelog 1 ``` #### 年が変わる場合 更新履歴は年ごとに分けているので、年が変わる場合は以下の作業をします。 1. 前年のフォーマットを参考に`docs/manual/YYYY.md`を作成する。 2. 前年のunreleasedを新年のファイルへ移動する。 2. `docs/manual/history.md`のリストに以下を追加する。 ```markdown - [YYYY年]({{ site.baseurl }}/YYYY/) ``` ## JDimリリース時の作業について JDimがリリースされた時には以下の作業を行います。 1. `docs/manual/YYYY.md`にある先頭の見出しをコピーします。例: ```markdown ### [0.2.0-unreleased](https://github.com/JDimproved/JDim/compare/JDim-v0.1.0...master) (unreleased) ### [0.2.0-unreleased](https://github.com/JDimproved/JDim/compare/JDim-v0.1.0...master) (unreleased) ... ``` 2. バージョン番号、日付、リンクを修正します。例: ```markdown ### [0.3.0-unreleased](https://github.com/JDimproved/JDim/compare/JDim-v0.2.0...master) (unreleased) ### [0.2.0-20190720](https://github.com/JDimproved/JDim/compare/JDim-v0.1.0...JDim-v0.2.0) (2019-07-20) ... ``` 3. リリースの見出しとリンクを追加します。例: ```markdown ### [0.3.0-unreleased](https://github.com/JDimproved/JDim/compare/JDim-v0.2.0...master) (unreleased) ### [**JDim-v0.2.0** Release](https://github.com/JDimproved/JDim/releases/tag/JDim-v0.2.0) (2019-07-20) ### [0.2.0-20190720](https://github.com/JDimproved/JDim/compare/JDim-v0.1.0...JDim-v0.2.0) (2019-07-20) ... ``` 4. 前のバージョンのマニュアルを更新します。
リンク集のファイル `link-YYYYMMDD` をコピーして日付やバージョン番号、URLなどを改めます。 そして `index.md` の「前のバージョンのマニュアル (GitHubリンク)」に新しいページへのリンクを追加します。 [gh-pages]: https://pages.github.com/ [jekyll]: https://jekyllrb.com/ [gh-markdown]: https://guides.github.com/features/mastering-markdown/ [jekyll-quickstart]: https://jekyllrb.com/docs/ [contributing]: https://github.com/JDimproved/JDim/tree/master/CONTRIBUTING.md jdim-0.7.0/docs/_config.yml000066400000000000000000000004611417047150700155270ustar00rootroot00000000000000theme: jekyll-theme-cayman plugins: - jekyll-redirect-from exclude: - Gemfile* - Makefile - README.md - backup_bund/ruby - vendor/bundle - vendor/cache - vendor/gems - vendor/ruby lang: ja baseurl: /JDim defaults: - scope: path: "manual" values: permalink: /:basename/ jdim-0.7.0/docs/_layouts/000077500000000000000000000000001417047150700152365ustar00rootroot00000000000000jdim-0.7.0/docs/_layouts/redirected.html000066400000000000000000000006431417047150700202410ustar00rootroot00000000000000

Redirecting...

Click here if you are not redirected. jdim-0.7.0/docs/assets/000077500000000000000000000000001417047150700147015ustar00rootroot00000000000000jdim-0.7.0/docs/assets/bkmark.png000066400000000000000000000002641417047150700166600ustar00rootroot00000000000000PNG  IHDRa{IDAT8 C鲏ڑOWu#d\TT@RmDH4?{9n)́q$ @o29B2HW%6^AT2 2bg8)dicncW(IENDB`jdim-0.7.0/docs/assets/bkmark_broken_subject.png000066400000000000000000000002671417047150700217420ustar00rootroot00000000000000PNG  IHDRa~IDAT8 C0 ONB( QC-P~!_.-A CL(m!1kV&y3 bD5O[X5O8^c GG^h~IENDB`jdim-0.7.0/docs/assets/bkmark_update.png000066400000000000000000000002531417047150700202200ustar00rootroot00000000000000PNG  IHDRarIDAT8Q0 BxzrZ\KoMxa$L$Z@ rTŔ p @܁, \HFRWҦz]s p 5 3PBfh=m]ݎ"X6qIENDB`jdim-0.7.0/docs/assets/board.png000066400000000000000000000003231417047150700164740ustar00rootroot00000000000000PNG  IHDRaIDAT8c?%HдЃ\&3z&7l a` !7o 9sv 12bP k0dg,/ p;xe w\p `DFe(ܡGVU$IENDB`jdim-0.7.0/docs/assets/board_update.png000066400000000000000000000004151417047150700200400ustar00rootroot00000000000000PNG  IHDRaIDAT8 0EL,$ұ 3`@C"IDG(+\c y}ޝ- ISѻ42'@DRoc樺"&_` !8벜(Wge|;0mj$d&NvEɡ*DP^?<ݓ3z(GP 4^N6#LGۋݕfڦ^; K'Djx#SmIENDB`jdim-0.7.0/docs/assets/board_updated.png000066400000000000000000000004521417047150700202050ustar00rootroot00000000000000PNG  IHDRaIDAT8S!@%hj*%4~@rcGR45\u%EЖ.4-vv'@d:՘5Db8U[dDmQ~Fd#wTm[TB /gD ̚YȊ;a5@.Q. (rEr^/U"6٤)4JIENDB`jdim-0.7.0/docs/assets/check.png000066400000000000000000000003271417047150700164660ustar00rootroot00000000000000PNG  IHDRaIDAT8 C_Sw̧sd_ k`TUZHıMINdf Xg- HH :⫱^q69K W}B.k\$af^%F [s C.>ܯ q XW7=+2,\7D^DIENDB`jdim-0.7.0/docs/assets/down.png000066400000000000000000000002631417047150700163570ustar00rootroot00000000000000PNG  IHDRazIDAT8 L 7dDl Ue7q  81ffc&gwVBZrv= ڦHZ!O`E )oIENDB`jdim-0.7.0/docs/assets/jd.css000066400000000000000000000041331417047150700160110ustar00rootroot00000000000000body{ background-color: #fdfdf5; padding-left: 1em; padding-right: 1em; padding-top: 1em; padding-bottom: 1.5em; } .res{ margin-bottom: 1.5em; padding-left: 2px; padding-right: 2px; padding-top: 2px; padding-bottom: 2px; border-style: solid; border-left-width: 2px; border-right-width: 2px; border-top-width: 2px; border-bottom-width: 2px; border-left-color: black; border-right-color: black; border-top-color: black; border-bottom-color: black; } .title{ background-color: #ccccff; padding-left: 4px; padding-right: 4px; padding-top: 4px; padding-bottom: 4px; border-style: solid; border-left-width: 2px; border-right-width: 2px; border-top-width: 2px; border-bottom-width: 2px; border-left-color: black; border-right-color: black; border-top-color: black; border-bottom-color: black; } .mes{ padding-left: 0.5em; padding-right: 0.5em; padding-top: 0.5em; padding-bottom: 0.5em; border-style: solid; border-left-width: 2px; border-right-width: 2px; border-top-width: 0px; border-bottom-width: 0px; border-left-color: black; border-right-color: black; border-top-color: black; border-bottom-color: black; } .id{ text-align: right; padding-left: 4px; padding-right: 4px; padding-top: 4px; padding-bottom: 4px; border-style: solid; border-left-width: 2px; border-right-width: 2px; border-left-color: black; border-right-color: black; } .date{ text-align: right; padding-left: 4px; padding-right: 4px; padding-top: 4px; padding-bottom: 4px; border-style: solid; border-left-width: 2px; border-right-width: 2px; border-bottom-width: 2px; border-left-color: black; border-right-color: black; border-bottom-color: black; } .separator{ text-align: center; color: white; background-color: #ff6666; padding-top: 4px; padding-bottom: 4px; margin-bottom: 1.5em; } imgpopup{ background-color: #fdfdf5; border-width: 2px; border-color: red; } jdim-0.7.0/docs/assets/jd.png000066400000000000000000000032561417047150700160120ustar00rootroot00000000000000PNG  IHDR``w8sBIT|d pHYstEXtSoftwarewww.inkscape.org<XtEXtCopyrightCC0 Public Domain Dedication http://creativecommons.org/publicdomain/zero/1.0/IDATxKiǿ3 P-IkĢD B)CN KR춋Ⱥe/{E"а @n^ V?ڄLy33S|nywyx?qי x/dH@%y!$I6-moohTsl%V+*++3I19K0VӧO4|| m^ةxAqZ%P J?jrD*y@2"!u P7޽{cH`٪' HP `dbb#YbΐΐTVVh4=#O͆҄6I%`~~?mrrMtx^ttt$swGoooSxpA^|gYqpp86$)wwwU,>}FW, ,+ρP(PH}R ⌡hݮطQ^^nH޽bߥڊ6ž@ ;wdHHfzihrѣGI/L.@Etuuw*q\I`ZOU>~FN'QTTo381jkkqg ̓ ,..*mnn㗛NSq1xsZz@ oPWW8nzz 1U S\+;?P^^gϞi c޽۷o+y_444p([ZZǏْDQLz!E`6mMMMiW¼2$3$3$3G΢Z ޿od.9j$app$ : &j5.F~,J)#rIDYD.#B_;\E  \]`!Ps~@Iu[xE&cW\AYYb6|)wss3~sUU ǭ9p\+@j%`f>r*s$-?g IENDB`jdim-0.7.0/docs/assets/newthread.png000066400000000000000000000002421417047150700173660ustar00rootroot00000000000000PNG  IHDRaiIDAT8 0 ψ[HmR?<7e;l2{ʱѴ 7C3_x pttRvŤ$yZ"(;!:'pE+ IENDB`jdim-0.7.0/docs/assets/newthread_hour.png000066400000000000000000000002351417047150700204250ustar00rootroot00000000000000PNG  IHDRadIDAT8͒A 0Ǽ|z,m4!`630p`X,%}pG {fk/s)GXx? V)W ܖf_vIENDB`jdim-0.7.0/docs/assets/skin_sample.png000066400000000000000000016470171417047150700177340ustar00rootroot00000000000000PNG  IHDR^sBIT|dtEXtSoftwaregnome-screenshot> IDATxwU> u P$aM-łclĈ7Hz>;}K~k-w=gOs9;ZB!B!B-o5[5}!tn~> !B!B!zjq(]b://'B!B!BEUUutJ!B!B!j|kվyO!B!B!Ըu(١mǦPRuهi]J2Z5xAyK, Zf_RG2}~+tB!B!b˒8>skEƢ8FG](gV!um @}gArm[[kq*+dggcyۥE&T%M8UԨAHPDsR)**HIq*,LXaK_{0us]\ iblc?h7IU$IR*D^U5t.>մCj0PN]!B!BLǞQSSvaϦSN5 VTFlmSo1ZUkwA׮E JJYv˗Ӡ U79o}ڵ޽{_P@Nv6h*-[k,Jrss0 C ϶T& Tl|qyɯz dc/};Ln=> A %Ig/*d ݃<õ@Z;TՐ4BמC6eۘuG&Akwk+WLYξ Q0Cahd"e;X7Π;_A<ߝ},{8nƿȇS 0HoئH$a5 'S%`5r[={pHVi^.[k09>M2aȱ28l4J 4h~n_P$oǀҿg/яERElX ɽY2m6+Ntۏl~v ҋ^ÌG_d"hgH9.Hxzp/1-5^p^t>,~{|3ٌvۦ׻B!B!Orft2ᐏyx.ZӒF+rme/88LpV]23UM6$3cN: (哲|vz|u̡P5y*lKu\ UAzJf=yr<9K0ebtˆMKXŴ 89Î)KIx>v6pmz 4mLXM LHg 4up]دYnꨋ?/0j#9%LҁڅCV2y ECPTZ]d#k ^2m{ y$b6R@;W.B!BSPj;tx^:RMv+#o}XP7әs[JBb!w2S D86~wfgfe~xb8~F8wޙm}'naPMَCo}v +3YLT$3>5vԥ1a/sۋIW5>[]bNWN9}O#8יN ]R/ f9Լw-Q{u!}xam]gv#t-5kQ*\.ʴgqZu vln>!Yi9A ßM{sT7unm ʧ)5 n{agYGK9xEpTzp"C<7泣=tNaO=]GM_ymu<<{M\jl3@nć][):ΘAa|2cvvm7Cs Vu &!0h ]L;e@J.f0]۶0֗9>LZ9)37ǯ w-׭0ܶ cG"A^Ņ˼iQ mߝ1 x!Cq\k^D7 |CQi`vAckCv~;u]3Kkp+DB>L6kkp1[̌=jV.Jclw'.B!BsaLVԌ֙Ax%.λ1\1 C,Hqח>]N/+1Jut)B"uap&nZjN[QTԅk$vjW=C3{{ض=vV}KZ7z8ۺnW;0h\zPv??5> 'hOM4i@a}i=jwN=mG㬱ӹrf`ўx\3Зѽ73Gάbci7J1u>HDIj0b xo ɘAXTMIMiK L?&`'\Rָ  \.RNij׎`fʣ} C^Dְv2F(ܛ0P}>Z̻iN2eEڂk~ƕ zghBbƝuywag h6(aeB/Aj-KTbׯK֖0{Mw3%xO别&I+k< jFk+g8g-<6Fzw%|<>]`4 /#ҡsAj,vL٤􄯐>T̢Q t7eg|Ia`k>5فhmul¬'D *o%<:-q|kUc]!B!DZ]a4h"XXVjr,?~-gz3NՔ"Yn~R>t[MזNPVgJxu@p` =4i0i;lb=096rv0 ?L7ik4k9y|yn{~ގn0B+ȡ}J䉷Kv™+\ƫOas8~D16=uMCuID&DN.ǿGi[Jgp;kСrP gC[\3m⇯7WӉgH;P^ CTg+1.B!B#m{5AEcѪ /c@y2 $4$Z*e0jȺ:ѵjg?' <~xb"^$)J܆QFTzIR7>XnEAWCy{҆aS㓭۴h2[NcOeTΖp)4nދv=7;w̒D87Dp_&(ݰ aprکHONS1&Cuyn }n]hFl1skUgp׳ } mgӵkX  |گ|Zͼ7kw|f5FizysX/ɣas޷W2yQ~1qose/63xh xt~+T]:swye<,:^ȣƒ6wjßi.i8i (ח2< t{id~9jaӮdڇ;-mS&F7)[ojw!B!B]oh`Iu=|s}R\|~ %71?7*Ȳ$SJ)***Bk0];: {J)3h àiq3(jؑZj2¨Ki-`b=ҁTj'5xб-xqΙŗf]C8n[_⣧M5'']u>e/_/g0}AgpӠڅqd nz4n-Rv@{g) cH+?*K3Q& W5 La݁%,0FsǃgнC 6wÏq_ce7s :ŹcjOc|>v"Ig%UEi\6X᣹9gpov54<DY;ѻ[xH< >0<3A]r)X*xlTF7 o=ͿeާAf¶vIB!B!ďd;h{tKcD\r`8kmWv;%h2bfݨFif¸RaF0uhǦMҴPh |ywulw5^@ZƗ߮'ݏSJ4't&alV8 76V\ {fcy˩ <Ǡqr@xkz*_̬ʶȣ5Ξ|Uv0I _W69zu66Oۭ(kVV.jv9Ǎa@| /sFp _=ʨ݋20u,Z)yXf1G_x}l~NQ #7^xBAC70e2a??+0sQ73@tO;Ͼ̷/3pa~5e˖le =}G>;$۹0Z)M͊Tӝ^<WgXyќ^•8 qh\ +^"=*2LיB<* m4ϘY4ړsԮ w*qөVzt92nú9/3ci.B!B!ďhU~7)6L<'AQ(85pu h%, _y5&sk|ęeeoO0OV6kfָҳ+*)/HϠa;k=1MnrztztOss)SFݾ']'YGL(մL9]v1GMgH9@(Cke2=*,@kE ;4 M8 Om;oNaɍ@b|iĢ]^ˀ @fsoUo?mCN6MOZR $ޙkfgq9sZF{6ʾ !B!B;VJ pڎ>}6xV.@Km`<*S}Mf!+mŋQTԙH$^sRģ5_G}[v,+Œ%K)*L8<\Q7ޞR˚5kIRvtF?ǭnrnf?ͼ yk2i  OS杜n-6ٿRea~lV9pC3Vc5X>\zbqfev@}Og^qp4(#{4+y)/u΅_Gay$4[l6&ۘ Vw|ݴ g x.m5eK.'1n[)|~_Qs4`~ LO/5c($)3eǨU f!]T IDAT3g]ҐC;)m/c%`B!B!5nh mC5?J fn<ϬF3 @ksB(eG2$Z<Ш/gkF+ҏm'??, hJcYhؔRp0ӎqi% @tIF>V;}&.qH13S)TָJmEsm*|-[CahѤ66J$h_4?4bk`hZ~'ݝxKsB!B!ّk՗ӀGU4M׫\Fi7mکKu}B)ڞJ-B!B!\vvdt4yke^5hn:Dm4n~tS!B!B!Nm!?pQB!B!B eYB!B!BrF !B!B!$B!B!BRSC!B!B!l"0:aԣB!B!B-)0P8SE!B!B!i0 )-B!B!b1 PRC!B!B!Įc(O蟔eؖ?'eضM /,h ";;@@B!B!v2 |?2HC6ŭ:#+rxun۳ߡlx zГL@oR4 Z;˷Dt}GtLC', j+l^VV4s9Z69wr?m.-;F)>|jT!%sj\?ֽyhl0* ,ǟD!B!,a2ZmyGu<Ƞڳr^ Fn^6lvZk**@)l᧳ʩ^mcnad2퇝!E 񔖖ca[^?ǜu$o}!K+R>(ϫ{ 3g6VR{Jow]1~wAeXy$]I_ey_^H,~^{xG"$HRXXPG, :]>v;a"7[D'yDWړ48 ' +#~AN:{^xS☦npCP{9#;/_};N<'!&O/4gJe9!4Z;'@3ʊ i*DvWb ߇+Cڝ5 XgHf/ LY$S)ټ" dlIښ)!0i"n jXvF_6cJeVS8㸶9?jj*){n.{D>X"aNym23FmE߇@!CF"lrԤuK&FcqRT]PkL6=GZbY6l/Wx'I5&f馁aG<3p~/Kd}4//Q) QȀN4USlq?EaOzxUԇўƳc Mǟ~S3 GEy f,70{\z: (ՎCN8y% ?E㞲5^\r ߞT߱ ǭ{xRfFojeت3~&^pwxkvuQG<7.齥O0Im|ݎѵ-^7O,?5?=V X"F<'ɡ_s؍̙WNw:1>}g>f^Zt$g.b8p_P&Rfir¨iRl^ y)fmʪ= [B  m;8[9— tGK+Cy+G8/hqG'癋c殸!B!e:Ff;:8֐L䎽{2L)l"~~χ ˿fYKI8.1r&͜nf34a㵾~8'I/R P=(`F{[jM[ő'KWa6nkݭA;,_`~yjJA*eLP%7 ]cT}^­lيcF>5O,H. ɗacd{.{s]Y^4k X[!@3&qdu@)m*>dҮs{B^)3МtnV/<k9]z#j>( rR}-?³w 7;jp lV#fXܚe1)s'{ڏv6O9x/@G4=UF3e2Li#d?5qϴ~'~7[w}w7 |Ha$66#@]˒5*/b._ǪߞLgY:tԍFS4$ļx}uX6xˠB&EK>=^ٸ9|ݟaG #R1vDfj7x98<$: cS|5;^6ZNWY.A#kf_1L~L>u/,&BLaG;J2Z<.{1 `Ï2cuP;>۟m7nr&ʡEJE%HAdo_&qmuQsugu_*@[\2A)J)YUPAtB!B!~e:Tjn7gONH)E.HK )WQf2q#)W)HHtD&\|"}6|΂0kֺa:< )*{wEfh(X4FyƴG(& *A{bl_W:l7}` Y0]z!h8^ \7~za%, *<0p}]9"Vc;ڌ 8f+^%ƌ[>_o?d") =*gĕ"aϡPʌQYJLZZJe3CPduwwܷ1gtdSj.=o=7x d5aa<|?BG)L/#rټ%IJs%\rŒ{ݬҳ-r5+ytrMwظh1Ϟmc+n?9|&ΞZ7^1{]L+wsIy:l^ܸu!BoE"|a; |5^[\gb7cXUNd5K[^WB!B!e1ƌmI$'зl6O4y}crċ_VTT(7_ Nw,w?mYXT:-?)y)ۮz/BؒRM>̴?ܯ<*$ʪJUD"".{p1{cK2ld60MpqS͒O?dTǠ %fBC!]X#Mfm4Rߙ͆#aܠb8ba]221t_Wߐɷ]Hͽ̹wpzJJFn,|0aF48RC9?܇=<o{fTt.Nf޻1mlJR^ruI}|0q!L}pՏ.=AVn^̛ ~ЦV~ۘrRw/;7 G}F'U1Fo؞MufTWdegt}9u߬$T^Um?r酛x0)\W}7_uj lx#/;kҟxd9Getll2UyZhqMݫB!Bb,ӱ%ɤx.="jc 8mTi R4?xe1EBuhǁv}.Mef Ǔ'1Ay_lTAדX w<>XfgUAejY6 ?%~g-\$)̮#1rw%y4_ĄAsmRjN$l c',kP5bFy5Gz?=:/?_0nL+9?ߣ:4celT fM/ύym}.~/הǦjVnd x اQ=ðXRS$_Ɔ2g:Zvs2 *}am>~Ccas_`S8HhVWkMu"c?Kc38b}^[ehFl'I9*:nλ/خI\qy5c8it gg3@'\z6Ǟ%v)a8(:+tl?onl+{!TnF%n?,ZdgHox''6:[LZ}#sŭ'k2eTZu?[`{m|] !B!ct$)6Glrؽækin]5eB/x/Ю9j{x1ͧJ5Kc-79s2.QLB %ˉtYwkjjXGP;4N݃WeŒ2Rz=O;?%-m}. ZÊgU*Bywg]TKG]4I[}"~Y4nqܱK^f/@B~XIř6c է3=y;h>n*])M׏?g A7o>[OɒFѤQ'B!B!E1=e:֤Re1\ޫc?3J,Nn _MQW,K4]Nz3n?p0wi 3fonKc/?dn{n-seY uΦ8*VWT}S_q\3P6O}UC"ijVa^ {-be5ɺYE"8o9?Yx-lÌ}cKk9gHT2q&Lޏq 爓t*Ư-^}֚J*K G*d~qO;f:H&STǃ $8/z3ֵ]m}}lK۝Ey s_B@fy[GSѸi~?+&cjqըif-L;-hg|4;گH VK*oά|j9.A=Y erhq̙*UzF{4ΒyϙG|6g;%tCۋS#IԚS¦iYV}50ï3ec,\9cm#{~MI|[_whfrZjK48/O\}e|H_}3#ς/>`܄ili,Z˖}蝘O- `u͓㞟vU]iu$;sfd`QC+>,4?5q_ȡ4L믻YlA {xl?vܵi$Ț; T&MW;rMO%) ñ#*'I#zI0>G] |;}N?yϞarbk-cnqRI\}_7MRtzwFS<&sxo7P2.5cꋓYm=nn{^lW~2 u˱}ӭE|Ic 7{{+OHR$'Sو 8!@څlRCXC0g,68fCEI|8;nrզqrvmwd8o+qǮ8<4nך P;)pLvQH&:ğ=~""""""""E60n*ge% ♑DZ0v9=e)(p}|FF9yy9̕5)q[qn_T s]2We:#T9T 00'T0plpЇNp(!"""""""""R[ɜ9-_.$8WlR4 w+U(+sg8 |}2o=0qpmVSZ l''ͦcSLBq*M'*Ɣ)S3pgv}:ig1x_D/x^^ܟp+6~7USԑ}5Tۼ1X\y\]ې0c>/õOy'm_=NrĦ`<65o Uj72 I8Έ2K$q)& <2n) `Z-%KsJ%Ĵ]qK !!WBBh[ǔNRGj/0`rڴiQºX;G|βsg+޾~}4ͳr܇_ (.8_ȊFwnUN5 }Ȕ%| Z 7暎`p?UDDDDDع3&W'`~V~v~&~jiD{FڎLGqwaSUV1*8! ѡCgp O=„a;04C+#\p|qBz.{7#{à=H4X57f4u4IrV0}^~L[c&؋KxV6l I?v̕P8}ʃ+?͋]1O=3VG,͎rz%ed[H^îTwGzߗcp4ieC?S!w9^zw-'p۸SNGkKDDDDDЗX@YeqM 'IB:lxabFLTWb#.8S+x2/X IUJOF`TʊM0:&=?.Q J9cڑ[=Ȇoɉkig2:qïrDVf/ݖO ?̫zܾXv;m&bXl|bgzXH85+ZE 2"޷UU՘dn }?vXv hU46ccN G^!==Z)lۺ9Y5N% s^}^[,xSsɲ Q\|=\ٳVI2c;W2W+gIF?̘j{"q ?\һ6n1Wž3n1$,Ư_tO,5{n/,S1[usrmҗsokWff'/޸f&.k5^uLq=ハ1=GGu!ܵw6"Xۙs}.~h=Ou<=A6L~'$sa==5vwEoy88w<?}L8/a$.A9.Mp{R[+O2-:&:dt3\yn644%o_9\[""""""r5iSԿV `WPm},>Qq-|20qѸnܕn)ٶx\n׋F]v&N_W_ƿ[(2/{hV} {}?om]d|37/mv?S|_xOQ͔Sj5˖~\ Z(`[w8{k ζmG]\#m1o!_Cu"pƏ޵Rŷ r~401S%ɫc&?pԯ掵l[{bɴ٬{#ݤ01 HvQ9KHI06CzQF`:9{GpjkN4(w#*:,y?螊g{sokVQ,a^r]#ûO}G7oű׷xx<3w|9)\,^{nz//01^V}ggOI &]i,^W@з?wiۯ5Fi?nO)N6R.ͳ>в.|=k6/iF(m5=z_ȡOt~wv$V /}p Na!KJm鐪)Y]\'p۶0h_C aȐ!7n222h޼yƴ fQ(k vV^^Tb[cB7+'Bw ݄-[J|(ӤG ȎdHNϣ_M#ͳ&ۋǺ&J}ン̙_\/Ч[s\1TRn'G1[SM~W4ck'bo~ň[}w]f/1Mzq7maBk+oNC_榁@3~sL+}ʞJΊ9&K>tiAPb޻V;;gT<~ 4;Ц6k{s?%_ym4]ZyJҿWbΥ oO\E1<6#Qs)|^Nkَ +psӤ'}{u&4=3^NfY'sd<9Sna{Ɠ:Y,wÌc;]D~-wbظ|,`&KR+S lC0^+[ Utnc`[vIhrHLVWt\nJԌzwL@Ϝ;Gݳ#A>j+t[3iYG\יrl/JTo{ =KdT81sb:䕸G/Й Zv;݉dg-F<]v%.ΥcqvU1G[E2>xe1]0p2(Ck]FK{iw"[x!l߸) QX@L6ۦttԌ6B5 l)0י)ZQh%#]] h*`ڴwtntja·o355Hv)g{a33^"㻱YȉOMmq,8Ci_$t+5rS׾\{A#?Gco挶&S:BPmג:5n љe{f~xll{C mЎHMgpյd?-*jg4ܭr˄{x;{4W 8$7Os*7]*'ft>բyX< bk㪠b(f1;Ҁﻓj̭)5W}&\mލ[Lz}:G1x@wgcnb` d%Q1H9s$rlWEN,8_XʻñwQY~-i G߳<3HM =^Ԋɯ?-o4kZ]Xj1dOJ}Ϟ9qY,EI[?*""""""7äY=FRx, ;`s8msx IGJX`R_iUcWmgҝDi۶1\fhpvO/NVv"|ڴh46kѷiGL!8kw(*׷ N9n雈Iч|K[:P7^~Lh?Ez۸/kzZ `Fk(oǿRWXysڱg:;;y*~qy1td^}cWx2q};`&8$˞fVr^[T؏oR~~C^L[!Tݡ1Tu-.㑡[xQE/4y!E^y0IhЉ~-s eۆyh~7_ɖMwrHb(3Í w9yœ _/i6ru9}+>_#sW}C^ĽPPar-K7$A=>Mxn,o ~wιan*ϫ| t8BZX`aN.:g]Q{i`hK(w$ϿJw>̲CT_jLt}iDsdeZ>y:{]C1q lm0་WGgddмys֬J\PgrҩW)S0`Zv-˖-c_lM4jܔ½??/Ӧr)nmVU1+<-@;Wrvpzņ.`׃!\W__/vgr45C*"""""rdvֹasv6|)D1L֭* GH>̈w×YaUY;I0_-?w/2qlhmü9*ݚmc;..vJWuꖙ>h5ub#.`׃!}`iV/#+&#Dnk+c+i IDAT˜fh^EDDDDD Z9#77ϓطډLǾ&y+kVcQTj#AJ~Ȩ|NQ'G$Ȕ)S"E5ͺ1l}s0>Xj1[bit}F~> ͫÌo_ƘHF0 x9Kj [~^.s~M.]tض =hjBXL2qX0o'?u1 x֯[d~a:X.4MHtHnRa a%If01Mf&0\Eߛi1 3taZOm`nZ6jmx.M7e5dL#DDDDDDDDDa,_Va00q=%;+hШQQ)y 끄şNFgVFC_51 ͚ 2^/72o:$Bll[ni*QSL$)9}XUm5~Æ4n 0ݤi35o Z/""""""""8`V0ȁa&n9a`.ZnGmǶ,IDbYu2VXDDDDDDDDD:)-""""""""""54f&DHS2ZDDDDDDDDDDj;6n`úxD0Cz ߰сEDDDDDDDD ;vlg˦ؙ)(˲Xn-k2EE\;@$""""""""AlݚTCIM04%EDDDDDDD֌&N=E884hԘܜ'akF\.la4TId4NїHua%l2:VHDUDDDDDDDD/WFS4=/lL?;qu9mى+%Vf6fiEIx/\3rBHN@rҙ&ڝԗb=ٲ`b*I{R ;bVvEܾQ:ZDDDDDDDpR2}Pn0 z} cB%ْɏX5aA=Þ&&[~|W\˷u½;!oCn5$ЯW*ұįXqQJe՟O\F oF;UKnچ=_OcVxReLp.RUE י<37<=S0 ?^:FU~wy[VDyNO|*)n/,XUq&Im6)0:1a4CH^XEV\#Y*==GaaAگxᙙS\S=i4<o¼ƃxh9{?Eu=I*_n;_tJ_}^ݒ[N og>N(_{< {Um? zZy-ګUDDDDDDDD+(fMvo3fw-޼y=*+S`[A,``6S~ܿr % ~{I+{0]ٛ]Zq-&`q=nMԮ]RmVLG]!C0dƍ׾4o޼nw!Gr9-{;ޟ̦ ,w6;O>xtwC־Or+iOg;|uC+^3;^эii_WA=_vbW Fqc` # 1\%M+'Mx 7deKK0bZ|4).v~l]fnɶFgpڐ;XĒ~#萿O>F^sCh_s 3y~jrx⫣pEANOMц-x.~8 :Tkij`mkbx7GqT_JE5kJ=ojՊ( IKK㨣t"LGU2Ieg]5VZ+-RVn`g|#M=\p"h>02&'m6}گp=ߐZݔc̈́\yGw_ll3[Mam[<}Qz<0N?aWF=88k]?uw a8 mPn,~pG{٭f2j)|)i:T=P3Ǟv9`dθwXq \.:z\ *1UDDDDDDDD.gq:[Fnz4ω'P"KF!}k2P^iM^^6&ҁR-:bߌ#Nϊ-p 7|'l=yv/;B>_ݯf^&ތ5%V~gV\x&{z`N)>-^RtEb(xWLmY8TS}6?7^5Mb~qv|V.G ڜ|H-}nG{wss-y?`QgR;˅gt/_ǟ7Qjầ$t'/Bw.-{=w2L=YD jaDէs lmz*bϥ\[oMZJ8VM#/Q1ɋU ˄vK'򿿏N1՗p1sܷĝ6:6m5w ]=1NM]1/ *4QgH9/fܳ8T Ngqx̤vt&IGrVG9lߘOL=NIbc虓o3Jv81fL;Gq`uѤ nOi6Y[H~-O?'Εz'Q=OOj/)CsdZ@ao㯛8PN7[ٰ yswvL}!/?]i:d.jD^EpRʺ`|l[Qe_PUDDDDDDDDUڵ9O`Р*(6sX^w3i6*w,~<~&cuSG~@?sr:@J>}yAf'/nȣ&3.~as{нy[5LN42^Ks&л!%=M8tJ]Zx N[mA䔔20ݱԪiɪz Olˢ7pOykg.?-5Yl2V"-V'm;pl}[an\e`.&S/2b`THqvO OԍGq~âb;#׆\1Q  Oĉa+1|5{ݮۘ]PV} ;gا """""""""SN*)#Ue[|='d؞wm]C.B`90&,ji]?W1\@M$hzp;OmT`& 57tI~CEdeX`&#_6ѥ5𦶡sj#fqc27xAu(H@taZ~=&3o';k_w^t5?&-[Ѣ}zz&ƾ+n쳨JH'$`eU&4Kޕ|mp`/=3G˭gሆGU6 3 Gۯܚٓ&jFW}C)V.HF}*~tDxapU7aLyoaeo8 ь+xyxbb].B&JYtɥ ˊ9|+ t | `hzi~*ODoEW9n%.yw-5bZ0COX83hu_ҙ leq|4/=/Q'q#HN6ZCssKgmb׷zs|9ML}.Kzs{dt[5#5r7'|_jv^gzٹ#> 71 JPPf2a>;nu{µ\B]s^@jgysfUSd*_i!wΎ%6ٳv\)vVGnl.Hhz ,g0鍗`sF6kxf n]A~Q Co`99RHnՊi#߹ n`}[!w׼5vtx8ӔQ-ccQ|`}[;cp~fÈ"пiwqoq@lzH2qsرr*bYM>y3hVj~<3tNhz8\9r8'zeuʪ6~E}zT] F}2as禗?XEDDDDDDD䟯5222/J3t-ύv?kc#V:DGc!hӣ>G_yP17>ČUs|W?)k=z^ ,K_ Kb^/;>`i%Ɓ4iqm DdYL{?1ǣ]~cՎt| Zz7xLgW ՚`A֊_j'PJ׎99"I~pww>/PڌOL&S&9GMͳycy*T# o㏑ko9<;=~d)PX՜_\5 F#O>1o?8eoXr7a:dYroO_*iۖ_`k zHPý^V^s`y9J4v8g l@$ISt@(ҔzMӬO4H%ϧs5zH6 IDATu4B} TUM;;;-WWWzM(+zY [7+)aGdxƓpcLrY4,=V5ժ&H`0`ww -q-IP5i-_j~J%HQ!M=:yZ;E/K~~̪.1sr|'rc^bji IN5Gc|}{ԋ?O""-^/$I gX^7/LkITN+Y.~E,8{K<-G Ȳ֫%M+r$Ax^G4+uVJȋ*I*?Z@ӮY׀E+'A0&UB۶GKIߧΑ954MCϙm Ie89$If8?OǧiJ{T" A`A"|<шZbښ1uӟ^+ Z`M, @mݑЀ 5H{@!U nǫ_D@:GLc|H$?8>fg^}hYm%G:ں%^cc=mP !@Y4MC@@? E&Sn+.^ r3-Ō,Q,iG󧜟ӄ,+PYE?pw`E)1?r[ҝ`E׫+&7W^͹ߐ90Ә@ף-iQ`lqO{{o<>$ô /3[hZ+k#(JbM'$4MȲ } >XK;c/^0 N [3z0\cZ2%7 %I4yBpqug {jb>C1cZ95Usú5X)y9<ܥ1 ,K@*.ί)!,^ U@" :aujjTK\2_F3~w|?AL=BHYVT>C93,laLg>{W\BRB$b4ݒEP55[6"")kL$i4{zk 2*L4UUm[38@%w!ZDRYv{ϡkH#B/eYsmR%^#!lE{/{f7t(1B I-w$ɖo:NWiK)$ɺEʪihGf"pXX.x"eYIF A[щ"U>H-cr#b @ KdN>\sS{-Iuz&){#>>~B"ĭmC0nٛ/rq_H\H!"AE3+DP>%-J0h֚bx/&5BE A 8+1l@n"tCI;ruD@#5`I"edI)m]E5 ֫ZQ:@[im0:`褠`mh BQ]%#҅D^E$MK!ZLo|{; Q:Kx[hHĭ(A*!;❘ȁ#aѵ ^ *`{WHPwqŶDPTQ!RDrxkt7H8o~%7w*wgr+<7>U6,yǿ9Dp|J0}LS^?S-Պ{`0b0\hcn)c p2ӗ\]] UxAX MWfiDuQUضTh2^y|79_OXU7\MHLY. {;XujD[ QE+D؉x H<"'IXq$:c4=>RkϟU$y_t,6Ui3HXk )$ˣzԴ$Yi"Qvȳb1#iAG!N`X0^36" ݶ>,-]b7؉݆ޓz+Qƴ`"%U7Zb:19#puuWqt!7SUsQl`+,4O(>5AEUvkں!MHuE%QZGc!wRzI{\Zޥ%ȕ Aw(BK,E6{DMayQDŽUJ2yHcmɲ$we<3mhۆ,KY+ i-BБ,I`(iҶ,+[ZS5"Ϣjg9Jb"~d N2Q cY@~S)̾@ fr:Њ')" oSI2))^Ƞrvv8;y^9ۄƓI2^S`Ro/|v,( 6q-TnB|s,LX!Bw $7D$I+<J(>B3xt9ZlT_A}}}U|(ɍ L,ےZ*%EZ)"P |޶!=^RkzE׿5^_4b\L[H'K8U=p.EI El^:P]3n\l$k JD[ö-"7Hۻj(bט6ViE6,oIRA?Nak֍-I»ö́Ut>nU-D[5]5l{19 Ru֦>~.㬡552xwxv- {b&X)RUBmب".ng d?ߒkx%oɒ􊦍j۶:>D{ƶݢLk>.̦WH=|ϟ229@ǟ?frN@C8?TRe?Z)Isb[/ϧL^1?ͳg F='T)Z vv{eRirp (ŵ2, ={Q9|zzrdX0ypz?P5kx$It| )JƇ( lX,q6Ƒ)e*VUE/`sNWr ;c{DgAeEȀ6?z/I+Y!䃏>&}|w@qOv[q/(}NNH %ق\'dYhP7X4k}.?ϱ!5V3~LI~﷿kxzw=ZS i4Kx٧?Q!ß[m18'PW+z=ʲd>3R5f>cԌv||ŋemZI`8/J>zJ;Zp. hgw)eYru~bd6]P j_Uܗ('IBjE*feGYAɀJ4o[ԫ0g\ϧ$*e[od9_1_.BvJUƘɄTkTs 㰦EG`Ib/.X^jQ B1ܐ="g杯. v/m͔ވa'OѬh)~4XBJ$mpg]RشQNe*P*! 0:OHT& e YƋ4[nuÚܶ(kjV9s۟t B8ƽt\Ӵ$U$9u~rk"yR'5W]qqk΃ "7@'t*1n`:O>RPZ(Bpttİ7穪5uB7b̗+VֵHۢBTYEk)cY3QQbQIhA1i \ ;jPXEwmA"&xg گ-Ć@4i =AXa4 \ Uk{w^{Ȼ7 ͊i BGbΰ?mkDrESߪ@ QM$EGHK"@tTu>lx$xT5}+وmocܬ+V96,;iVf%"#Eڝ;~ü?+Pj]N|Ѯ#ܪ~gFv?_ 28f+9<<䣏>mT;dd]-Y-mkUnnf3^^\qhJֵŇ$볷7ŋsmrPrtt:e2ږ4YjѰ-ISWK ZzmHku 98Lx/'c'?&Ղ"OxGٓU/%YPdfkvvGqrr_lzC^#@S5$=RKKmqTy; m /H˄΀WKZ yb@xGB!pITQZKjx\/AAq%Ϟ}#|rq}A] ~G?O?o Lϧ<T%'#NO 찿;䳟~@P}%Np!A|9Zx_jjlAdgcɇ胏НrtV$dog[w˨;{'{oc cP:z2jxiQmCs}sdq'yLqttSmMcw4YiL!b\b]C\Of,SwRrt|;ۄ'4%R*uMT ҠTBYX 0)!6xl7_8msT +O´4^-˼jÑ7E$RIt=7‘hn[IhH"ڂ)!lQUu&I ]Q<7Du%1qSOI"Bd>kEk!VY<ː^ңt){{'$YrZ#`aZm=H%lQQi2xx- ֎#MSF-}°kFu*i qуQe;|7S(U{>>(lȘ)!%ͺ2!W^HnЕc!ׇ[RiSBlh5 WH l۴ {&@)l$qlK11q_TxG6!$R'HQZGn@u뚭-toLHB[:viI4ڦ) V=nyqZ)wT+d`r=CeZKcMv3?IΤƈߩEqP )-ek,:k!+_ ">u !rf m ^<{u"su9i^F^,IDk\s1 }_/vyn~=(x3}ʽ%7~&G yQ͚XqFk :ͶޝMVu-ł<>{{; GktΖSMMJ-xg`oq]GOUᡳ\-nW./7Ϟ>O?`woN<Ýh?g*-9b\ՀT+& ْǟ<ƺ;C&S?t:omXxGMLfsoH2Z EV4MCR{2ԁ2z%M$MdUЩ om݀"]K݉HvmDeJ{ .6Ӌ @~UYWW7opgA_ ӊQdxt#2/q5G!dr~qޠGZd9;{}Vp맧mt>磏>y>ARqv|GoP55/=Ox%/_7`988 K5"-yEQp~s.z+0_L.g|)=yL>(|H~$4шZacZƵmKQU577xJ"/AB IDATD{EcY-HY訄wcI-rG &?,Kί8UeΈlr1cS"5ez9:;4k?gY(z=QӔ$ .ۦ Hސ)+Қ% WF7FPi}ֳ[> ,xazYss5Yfӊ8_1/9:C֋5\>㗌5=-Bg!$Qq_xIIBn'VIˮvEj#qrzrBhuUl$.ł 8XGiv (! [ҒCT{qO΃YmcCZ ^(|7wS|O?hLC irobQm-3 [3\GUQ$YXvWpIo!1_fѩ4RWwj! 햅e_BLE3f & %QedPb׈9uP f԰{_g20"DTj9Z)2UU1LYLg~;mϰ\8mwחe#xħ?ea%o`0/?sN1SN=j[~/sV9r ;qm8=<|GrҲ3dxԫՔԍ%Yo~GCAi.npB{pJ)?/|_k[L2NNn ,E% dYPxuSf'O3~:%kOc TՊb|5Z,Ȳ -yQ5qfPI=zIJOizKFi, YV5LuKVRJj ϸ9?|D^\Oy0>~W]p%nbAX̘\k_\M.i놛M/-%U]oeY"f6u+A$uB(ƼN;L45T~.z+OYwbg-G';|p)b^uTm [!VĽ[eZca>jiZS)ۜ"+,o Yͯ+˲{/=p;o;ⱌTyY {Tg4_z}e2Zq5P!EEeyI7rR(k,ZEK 6TlMf ' &7H-Ƴl|^=jكo!UxwpG$Iڶ-^dIwUU[Dg[=Idݒћų^`{4͖>[xSؼG 0*():}!AƉ۪s8/P_&bejw.DWPQ^]g:!qػ $Q$l[4Һ#[m[ Y-)Th|"i8X0WlHўi)slV muα^1ޑudt@ AbA %Rm-ND׽tp9 E@p!ljƃ>YNv{o*aRUzjxvqb9RJ2, ZSiӋ=BH$:C 6~E!uKΟ>,)IHEq 1Nb7ʭTIT #n1}o Ώւ(Nt!ЉB k !zbm鮲+{> dmYdZ' uDcEISyJa'O3EkG"V <+88ܡ{OruuӧO)YVPRj&^kc Ƹ(`8hΨ 9./_|ócgHM˼5,VK4eo"E 0d>b䂛%ŀhXj`r'}JL$2u/P>;CeJ˗d7@ ɪ]C* c<Y^?(2Hl3!@UUmQGՉu`"ณ}|h|s'&xK4MC۶BgzAIiV 0\D5R*Ty/QJam7v-lrh D1p}zH:{ztw&^`#snfg-iq7W&z.tC,X`ҌjR,J8T"qFw:)Y/Vd2h}}~N=kHL"^43y|tZ jgJ@ԐeEQ@75ϺOpVш,lrkBM)*.F|6 چmh㰣Z* SٶM *':Ͼ_hZ "+h!-!E􊖑\pdsD#vL ZiYKfOy[Ξke-͂ 7S-WfVRBvH+hM4`fzz%+/YՍ( Ω<|{ʒk F8P uՠTvǣLWz{#:ZIӴH/%҃{b q"EDQrpp$I HPWeilћazW$0ny1QB#=Q /,Rx:bX =Ǝ,ńoos`֫Ϟjǟ?_wh1,֚ޚQ8P|_g,΅M@ /aWТط^n[ܛ!<2p1GRP,MR .0W, 8aB "| #Rޞ^{w\JU1se6x{\ƞUeJBF#F k$I?s7l8+!QLs߯3hw ȉ5j^A㗭߯_I~s)219kʮ(V{Ѵ=.%܎lՒ/_)mUs||D]!E eNO#RZFԍaY#DC#P^qtt9i2ϸY0GX ݆=fueE\ih6.TITh#XVuE',Nȳ٭ȋ Yqst4#=]א9yʚ>hD|Һyoa}sLAKN'~dŞjst-ZIP*:#UNӌ4Y5?\{8" 1i?:Ο{似^\]7?S5[:!BOP5?zE䛔oYarɓ=z"X(c6* 1rCAn[6][O>)"pJӔV錪LG9/] |rċ eۑ%kK^n Ng'-)j/! 3mI%<9 8F AUxEQ`\OS8iL&c"JFc/%xA9 I^9Ɉf\6o|pDE JHꦥ7o&{rxM[:4)%i:8kߛIDͧ uӡtwt/hl̻I~qMx'x{,ס J~}'6eGwF i@:?_gGx̶j1X/p.mwe5U?ӣ9o?ۿ+O/o8}xͦȠܔ4wh[oE<{lPJ28}ﰶ cITՎ(>leNs028" ӎ:s7xrm!Cp^3,9UՀ%b%)n隶iA)EEc>GCk[a4U%g/^$qCC&pn`.oqR,D!ex_z箐7zGdf3NNNᴿLbt&xo{p.r)4jfӳp4Y$z۾m5"{qdQvt%)[QHta18sq䋌*EE%<0AXi3ԍ͊Ŝ4Ei"J ܬKe1:.p^ƘEhIGs/Ҙa˙jд=E6uQ8})yӯWl֐|pW+9 $Js^䂫bDW<}}b29ӣ)xaq|'?=y|LOXH|HyF ےIRa8'''O"Eg dJW;>3|er4/)9jkDŒ]W4K;FnW>w шdBUUmM1(˒򊲩I15=~ gܻwJ>xjsN%q)d8I:S=Id2E(XT͒< \AێmÔC4ure2>B$)G8Nԕ5gL)i,黚jph .闩wY`}=~<|xfEHnOORruiwX22Ֆq1!-rRm$s`G|)˚~|gdT AUU\]]9햶1%J<cmȗ#*:@t= Hqo i p-Vrp2o  yH`QD5Z>ӝC[uݝ);z>=]WS:lR#wq{v41W-m¦hKk;=!R!dA ݐI`[D1^o嚮3IA:Z0<~)$*&TPv~ËO‘:1}2V[G̝Qk-Άagk UPZwz(źզ"y>] Tu-JKРܔ^E{1Da5kv-1] oM7Vpi @ rh#\ka7rbh V1/Ơuh %RGsFdT{7Żo qZA$Q@8^\R8N!aTh[bPm:\j!D k d%" g`ƺ!Z7*7\ yDu-v o J] ̄j( g} R#ɲ(>0]T5kñ4'"Вd oƲ-Ye?ŋL䓔S_`l(_Ȑ:f>Ͼ`4q#fGL6gvr+G?Dt5i8]P5IsyQ0 me 1Pȋ̊ϟ?e\r!IVc֛k /6-G t:zg:b^uӗ<-؎<(& B qUڎfMVEbfYNי p 脫ͺe2;K./ʎuB`6TH4N-cQ0&tm4()鍥[CS,!{29u|'#!qszw [Dc\hR)B(vk?cAH5K֫G1miZ .%K $o33՚Y6a~ 7\Q_\g{{XTxo # ɀ·qT!Q(p'::ѣ8pyKZeIg2"61’NR;A~>>经{aUsWJ1LFfV 咲,V?_]y6 BE8qU&IXy2g!Es,6_Zk[8c㈶l7%]gMFy>ΏO_\(F"$ȷ7,+5=/y) vh􀯴suDM 2NpXu-[n+qE <!e41咼(lnJmG_fE'T Hb F9BII:"#)9y`j+NIRg BcZXYh;X%XB(!4EG-%b$-zFYNUU$f:0ѯ[/.x%I4N)VՆrt嚑|YS<'HҌO*fͮi*be͆xmۦ˫sTGG$y;,˸Yɗ46Go6g_rz4f}䭇YMHId̃{GlK./jw~w8:^е,ˈ !}.Y'³Oy}w~ /I)Uaèa4g N^kC@48$Q$[v(Ԏ~9Jf8jX-uԛzSoMr\iPʳ.4EJKƢujXb (ᚸGv}7!8cݲږtV0̎͘Mf{0 ؾކg1 8𝡮{SJƤiY7n|<ݮI93Q1c)EQ ,L=H\Eb7L&Gww%|y}w^]Zͳ%f$!~cBA淧! Pk"%cKY$, U{H3Fy1 B(LS{1?^pyu9 MӰݖށWX#NG._/WXqOn+T'!cY.8 aJFw HΈ$CmnxqHȦ)=4mMF,<􆾳""2G7m8פ oQB'јŽ9*Uęu)<[ z8EZ1t( >#ٴ$Vް! ֚4m~!$MU9uS~sfW,=Bvmv_>cgv3,ί6\\ZPWc~Uop]E5t;iL,>,9;d;[g]K94T+,8وqs~ao-䊗 /+{|wD9RATU*)Pj fX"{D2w}nn0" q!NJur<=m F*x^9sqB~xL&أ~ou](hږ(0}#Vz5 ݚ7H,brs[>,.vH8)%:R(#؏c(q^a· [G#)/T-7$ћq7!EM4uR.k^\\3+~Oy ]p^$?)ms_ 2o}`wS%dĺHq߭yӏYĢCFYTaG8<1LyFoZvGY4S>Pih-qӌhĮ,ɳlb`4^BzM4jrWRhGHpyq_89;cv4G( rc=:]i߲5!$ASlwKFYE!ZPgGلHtØjMFJa}$A Mն" AQ5h~1%Y&qER6mzCkPЙmpHtDjϞ?F&O>{LQd<=÷(MYL@uhA ziRא {;ugmu.D^D 4 }gU5BEi> u~8J%)q cIV&ash\}]!X0">mKeWx۶$^t5rw673nC 3l p8 b_[toǫHqx   U:ߡp^XF@UdA[ȣA +JiƂrh%pc}sE1:1a$2uxO$ױ;8Áj1Fw8 [y4~+s{p?`\ρoo& KP7ԛzSdޥFiG Հ xGttF:tA"pZ$%؊!B 7%)!պb(*cEA8r" )t2cG/[u=Q|lbzch:eBZ%mO_%i:G|4և$IH4-U}S)ۊI,[ۭW_4,Zm1V9Mc%s4UKߴXI~I - T"IJ_Ԧ,#99B+4!sPag (Г_!B_ 8GD@[H0|Sa3BlٽSvXcZ:aLF`cX߁mUUp̳Ԭ^py~ͷ~لſ243OU%J V- -DqfG|dzDy^`떛/G=bM?BX$xQ%MYe4,yg|!ۆzA"0=ґx8%Kb+o{NCH'x!8|qNԛzSoM_$l*%PrPFHO4Rko5~6(50DFDG2\ǜ003զx(cׄqv!pz8"y[Kc4wtmI$OYL߷i4bh MU ,EqP[M%Z)̎r"QI#*n,jD,uoJKUtMӚFJBAha+TmM]{H-&Dx& fkua#uYHӔ(J¿6-|XStA t6u%XMCGg8N֔M9˗L&aSS4MCi zyI4t]za4q^#gr~uŮae>3NO0ag\;(&p|<{k0=omlYU4[+)]ex NO)1r@9;;cOmKrq + }q<[פ#e´BMy]a{cn›rG$f1"Nٶ-YSd1. ;V5irpAHF 1WK8(Y"?SLQ52O_>gOhs49#}RfGgA5#O"H$>]gx<;xMl|oq8⣟|D%aJcT_p4 >jB;Cb1 (sVM@WtG}]<qqFNJcޓ1q:aW(9::xKW G'HeBYxxA]t+>|/ϩ떺ji iZ`"\O8F {rG۔H\p^Yl7UO>oNj3lCd9J("c|`' .m %Q64n횚lDb됃ᴵHҔ<;v5J(IC_X*Z/haqzF]ݶ4 oޛ=qei~{ AVY[kl$ld&ӳLOeYkU$Q$@@"÷zD@Hp`s6 eg  @wZkđMΜfeZ֫0on͵tUcX-נ4qR?iDExt33vDn RDv%uq,Bn& FVZf$kr(%-' B-ɽw=7]U0ˍڂi#87i5l(BGQȳ27$Ήnj_-6r^pBښH%1"1(U oh#zJVyJQ˺g,1(,׬&.JRbRFMoU$yE8Y ޲~%DMg&Y&a:aV[UDAsȔ+H uCP 0֜HH␄8ׂNĶBmom670%JKR1:Rm9(%tDD$!MqH?VW|o8ߣk"ې VvXN%1[^0\\-8 8ZQkڲdt~#kv_ܟާ}o߾i!_ûf*DG9ٔX"Ҙ$6C۵j J(em .(3)8foo`@DIB$Vcsm)|E l7n_iF2oS[kjF{ܺup5R(fQB7QDԤ!EzNgu]2{E5/7rJsB5ih*2ϿhiC{Gyڥl"rjk(%ꇶrEcb8}>G)%a$s] a1_,Kf%7bz>aq5(V{=,we:Ywy|0D)^Kt}1?o;)ٽ|?#ꂇ]Pg"S,\̘\s>D ^pqF݉I >1>b/23~N2K%]Vݻ9v3t?'OAjJ 'u]qzzGcޘY\-jT\N)ʆ$[2;I3vb}~aDS,*\sx 2{ItFi9eU1WWr3HSYeI :u`=Xc:I(cT|CH ;KCDq#ʺb(mX,/%6ܻwĸ[qJy{Rs`'NBJ@DY(:.u]ij4a-Zic3Z 8 :nD ڳ? j nμX%8%; [vHᄠ(+MĨX%80 +)_?B8nM דc Ȑ6k/'!Z+w93b@ xg1Ƒ%qˆvxoZtspB\"U{YmсeV~DD|omC - &uG C² l5urVp89?9B(Gg)u}8KHℬ*"d&V n`hr|gToab>;N(bEN ' hGA'z(nXNwx~rƣOy KfdILUX,':&R3$ {cf5KoXW]NuStXA8lJS6hkeK8,Ni *J il[H1~ ƦUzì`XWu`H?ulw/0=͌ou]ϩk\uuB"ut<ܺss.w:k%ggg[ƻ~5:8c4<{{叏d2CNጡZW8cwWXm8$IAץ۽ 4KSo1-Q78j@Gf N_ErVqvzs0}<|tǟ3~AxkiHXLWg[ "&8k,DщӰ6 &t`, O=nhj z<&bh@{tHDw3aX')'K:#Nc^0])CQ5–^i*>@G'NtJ/bjJ4F:lyN:}޺ŰQ .4E5'OPT%g Ƒ1>ge7g- s5Ypq1T.vw)%狒x~:eYV8gpJZ\ΖbS=zJE썹ܽ.^F} `0x>]6Aϟ?'֊Uz e~F*./).U #1uS]5L&VE7;q&7o&s 2 Z*^x8'"Ι\<~7n<QXi?RGHTW8C 4h-}J4DDRR5eY\$1eASUEhQἡ,˖r"i`gNq; ?+|ˈyL+)Na. wDZ2 9x[In79Gd0255e}Ù`@Xc rdJvQ F>#κ8ohLMTPao٠ F¸>h( Xnc:@Q[Y.Q*~vk;""Y DmZ6/o_lK Fkr cq':-*ziu6 AlN#MlKܶpR`hݐ; lYDl"ync->@+u;FmRXV2'qTqȬyom6B%#DU wu`DlC\K78|Vc}k|h-?f1b~L;bE46. -ϐc7,X*qX24`vⅤ1xQNY;NN_SF}wFik:b|7o0&$oDQ JF0N{{ܿ}~H'U|1%cSEZ=8j|NCa!Ϻ$*/R dDS:& {"%ьYRZBx!I5JKÛ0z\78^۠eY\okt5k ^X"!q^fNZ@Ƞ4 I֚|ꊢ(H〷 Z\M.(˒jIiiB]!4֒&9Bl9؝(#QZ8u-S%6i/-qZmE((V-64**~~CFt; iQ!"Itme18b6lrceU4$Il$ ֌F10%{{#(r]f0ZTGI"t n%[ \|s5Kc]#m U7r!Ƽm--r F~#.aL ioJLQ+4hPm+A[Y0hḘEݴK#/P VTJ8`\d#m J(q [c=qH.SfUFnBBQ[gl!-OH+b{XܱX,\?hp;I-:߂om6]m0繭^\`r2)mm(:gq.7fN!Ł5ϧ(!(K _p9]?6 ye 2i 󤩩5s!~1p$ B1\ajTp61$Z Ea1 Hv<Ѝ<9B5n3O)eY+jj*-:JĚ'XiHB;YmaY z/B%Ik ˠpW¿H|Kob!6ۍ? oJ"F*M1gX쥖!ֻ>k@[m_ׁTUv-S)C릤vJ 8<1s8F)la018tuwg*\],I9 H/PM1-᪍+MbZ&jJ=|#.Y^tDzAM15I^Ҭ 9PhØ:YiE눺9e)N?3NrxD*,S-aV:$8c!Z-&)WSqBEe:1iVbbjF]Nc:f(-Η9zUR5 F25JJʺIm`e>"x0g"hEΰ8RX9߰E`Q{p^`nn|ӂϊob8 M^Ɇ۲ދ-M pRJ#>MP$ƺj,5 C: nXK`] Nӂ|KX.H7x*at}a˻ϼʌ~:z+ٱR_wj@NR=xuZJ.E`̗LRP$Lrmq6?-MC{;9gg|SzGGGdT `f3q~6Ř`4+D(R^ ??Ͽ׿|#&+""??qrrB咺ly&s`t:Xk;?/?;\S9ZIxp!`r5ΐạ?tx@>gs2z}]\.iL:ܑXy(A(oq^pܹ{%i.LĽ6xǏx|}T̗k8<%ɄlJմ#O{2kj*s-E]i*@gL"H4a^}xe|^\`֚~C:M1u`FQDk{ ]z=|\\\S~;Cg4))=Bm7%4EjGee$~K, &WWupp8`XMYW%քΦU}x:aMqYrk[bk#, J{ zB(V+(y/ZdY*:(\[cLCUΐ9nB*,3t:pfz5 -usAk&pc-I;mJJנ0wy1c@ McX dk~g8 6?@B)n!Uc:ҡpZa D6C۷kX矈 F:ow5k0%H!`hp^#̻Gx yJt !ce00Z.8?ag9qㅠ .(@14M[B U=P r![<0(+)ٜjE:%{!J$i2zc}aXi>+Ŀv[xomo|7t44yc!Z(MDgU@Ƈ܃ !Rm<;?1:TUI]AD%<D*t`TDS(HBgҼ$J:elD4 WsS"xA/!J9J;KESy״Fqb]p4 KrFq71c#f%|%'+Reɴgpd6-m2P$$ -K,|ez<#5EKZQܿ&Za.aZ0\{ՎjMx o~B-/,?u@u!T.v6dhCVih5"X)Բh,%)V~py~EQT` 4a1/$Abp(Z.]pX@yכg۽waKv ^pcwڻ>r[ܼM8bfx}M41BTUAUU|gGܸq-5łbEc-!$Kz@[]z9=ɣOXd݄?z| 3C8źVO,VQ:{? -i'NO,{1?|= łN}VˆrRʢ揟>a؛ȇ )%_ɧs~qh<;L/Ԁ1AwU+7nb0S /\+QfKbɒdZ3qq9aoi͚^YUyF,M\r$pX\qiwWsguI}w?~O8yA֞j;tƫuMf|<{;ܸqA7b:r>!i/gupoQcXL1} $ܿw3$rTLX,hbZ816BOk>XT6>1N/< +NF,24!txkEZ㣣Cn8;쏲ic"^wٴ <'75,?w>׿ )眜$Qah% U]yOh mijmG1;sj! X|RCi}i򌪮yy^Wsx0$M&330|'MHMD"!IIA4\]I*UI`TY, Պ,KR*!|>VM5oePLjK[S5փl}ӤI&axLYŊ~?y-3-hm}ܠ.KlSiI7 Cf(% z 9tw:koT8FE b\$˫ ?Jpvq:D'&Oc$b81Li\Aaw>UJ,5₦iō,x;*!-]_o+R7Iц:{ZsM]lX {p6,xCE1:N MXN.Q*1:XBPmiIyY^dDn2B Zh2O @n'B _Y _KQ20p%MYkfL(WklZ;PDJ.֬l9 vr #mۗFm6v (2d :ă# \a66ABజ^qvyA'O1EH6cPՎ7A$ :MB֊~3 "HiC5s")b Ϟ}A$bDHb-Z=vw) m bSKLUSقrISuXU`#˹sأý.Q}NL*ث-:mhJya1g|h?˒vS#1J#Ef5")p8a.[fۢ ADe΃uJ1\r?]^_  'Y߷y޶Զә a FD9~<$q1uXQTT-Z\JEۅrrB+EQaYp)w%fA[73\HӴ],۵ӆ @3_gDڼhpl˯w=\% t_Wfv,ybm)eY/++L=ϯGEYʭiJC.+NNNf@4EQP%q`K+͍7*F E4t:9wrrB9:0kELouv; G}fgkTu#!7D"+@IVb2H#Kb-X1'g34>~{Y./PZ" ƶ?!J28%: c}{.J)iU:shZ8;j2㽇R͟$i*HVqJ4R%6-8jGTa KeTd.0O(lU 6M(ƮV+tԡ9I(/=Co۷r@46?xOXkլ Ee zn TgVosq(Ycd]ckr2vu`}ЌtBaPEvH )5isppsnMcQQ(t~fٔ9p(/ItLGip4b0$Ip"đ1!2ҐV&&ҏܺ}xg>ജGG)|9*JY IaIDQTKr#9$ŵ~=dp XwLyN Le;Ɛ*[3 Xj_oAn5 AlP{aFueBo^$TiQ &Y Hoj-BjN1^R֎8NCŶL* 1 "IyA>AKBε::Jtp>Ce4I، F>xײ7ZW $Ck˂byɋ3&b) jቴX}XtZh,Z^6h﷊!Az맷6xNcW,>!HE+ 2j@e]0*XU%'ܿy_[x)8IAt':hC$< utĔ+Sh4"snv`0.I4q"Y;0",.ФqDUZơ1)˚,JX( i*={@,!჈\5/.XM+=OdݺOFKh-H2c 9??%$$xo[2 ܰdW @Ynbk5_+aoJ7Ǜϫh:ܫo>-1$qAƘSŊb\/Y}>X_p .(Ok-Euoha6:J Fǃ[:s372#Q~u}^Acw|kv_lcM[.c.S+l|3dY1I4,ضeExlXGDw;ω۷oSR,ևy %$Q!/Y.5{Oeܺu/Nq9`ѓO9r1W3G]OhrœOF#dXO;97o~M'ǏeW9\|d&$]]5@-?V A'6HvfPӭ.uaѦ ]{/,fWI`G2=drEIQw=')qӿylV Dјi2h>K /^$MCv:J憛 zC\`bAvV+B^2e<=}X.il%t.xr3McZ2 W7<:}Ȥquy]SA&ii"] 7/ל> 愇LJh!sq2& lo5Ry"ir!z_vHUS]jH*!'HG:ۆ$MP#&2ӃEx'i*k%)Nnku]_bw (!㈮iN僵(I/㽠k0GXzy (%'%шc,,Kslˊ[f1LHbb>fg!u|8#w75Y^_qqX8K&STUS '{]C1&xX 삅5qƳHGqwvL$; .=2c FO7vew"ZY;#8HD[,'O:S-O,vLwvK%ıHUb : "N|')N.'wjLh/tɻ X%$al\T€uT\™Q0€5&p]nӈddQn7Σ olq!deY=`BCȰp.̫ֈD7_?q2ft`2[)U)ۚdrp|@ՔivEIJXlb$7לΩ_}g~[kETUR#$ù&&H1Q(f,yԁ5}sݴ&Y>h9^Sps5]oh4M8pv~EB]RQrRw.ru48g0V`B{ѣXod,H(؜y,\߰cv IDATwŴKBվMyz?zjo8w׉_Cx_K]Z|] Zu nk͚)MCYmz[Pu]6xfA= ^Kmɿ>AĜ!0cH44wYiϵo۰gI9tV+T5]6.@q_}*7< {;F9hsqA99MCY7Q:BﳷP ! -!t-E11-ox1'G'<}_~%Q`uPlT|L3׬6K -^X,i_%$q<g1&fq0T|q8i[Me8{פiʓ'?F OOYoZ֛R~/x?C>3$ӧ|knnnd:໖G'#Ƌ 횗_9*XaYQg<}Y6W?//"_ ӧ(J0Qok=|W^o+/_W|)?g\<=EP[yZP{&x{oIcH5#/H)/^]PFc=<%Iڪ$N?3淿$Nxt>a(1ٌ$G wQ"I#E.Η,f[YbG$ST|?盋%x](5Ҋ]p+Z,F6kudiN4$&,~~="c>s}{1h-3tzzSu]Smi"v8xx|̃Ð$XKp6444ӧ}5 jCܖ+,5۰F,\/G\@H ҃𞮮9n(B1] g,IPB\; L4XiM4<!j\/N#H*4C,O5]>f ˶D ߃aQb)V8q]@(tTlKb4=k1䎆BF ][ҖK(&$D.5-^<dIJAxIG΃RclpT -eϭ&%AEK6 Pw8Lb)g/$I8iH.K*YO$j{W]wB!/02Ԏ a#[0B wu]6 ΂Tw[!VL>K>cڲb8>ydzr{OOɧc~eY2!Q[*<<[5ۛk7L8VW8_1NxSsRbچÃ9εX#ѩØ5SlK &q&Rq~*l² Ice1&lnY&P#g8XL%7ezyA1Zpx$ID$%IU}MF:NA`e5f@S:) &AQ`FCoe5{f@yx{6|w}Zt i^gA[ݾƀ⼥kZڶZ*lΛ-vK];6TѨ Ft `i׳4= u=c_}yٮWc?Æzi6=Q$riJ;jYbCgL߯c>9ۘq|TmBt7g g#VM9742vvM4Vm!zdYSm;Fil6puqŤ Lgc/ftFS7?x˗Qx"%6k,pnul Y,),ukn>s6((i5iHY?WLQUQ!{O!Q{/*2BxO"9GG Y`Oh'I"&G k5Yd4E YV ݾ3A%g Bz(ɒGyzzijg@kHp.鈺ޒf.CeY6u+NNNH ۶Wm iITw0v:$xzkc{![/s֫-J)~]#]Dmϵ?`乷h6BXOKC#@5 W%#::'Kdq,lz)6f2$dz (t!4A3!Q#p>$^Ƥ2xee BxHŲBtydqH~5vHkqք)#w uUFZ ozW]wt%qF@@h4Mhf>x&R\8 ligI8 ~R>XPO c_OA̶ii~h2csFX]Π]|Ͳ"?A ^w,bs|)O{v_?_YLVuamjU+nZCڰ@Fp}DJL&ĉb:$ u]Q%]q8UtBx(U<~0C:.+:%ǧ83m "Mm JGg-y6%дր5/{x<ˎ !GX/#dkPc2PKǷ24}' Oު޼^{>!m(f+(poBa e*꺦*B%]؜5 4 ]6w"161H VϜ2Q5m[u;o#[v{ss7_}:Yuuߌ 88$eX,v!g0kZx@>yh Oٿ[cz{K^L9((kb1_qX:㸾YT+%$MaE5DՊՌ5R TjUYsxq~W&L]AfA"К C!tu h:X8'*[f O?d<uCuLT@#| uۄR56 Q2h[,mHui1]k8>9?THc|M2z݃|qg $I8bA|##t:钪No} -,Q|eTH2n8ϑ~Mהº VJtv. v^5Ȇ ,ێJ$JKE> ` T+tTJ`@5SBX%$yr4[WdRBۚޠ8Hڦ폺z4{(ϩ;d `@D8p}}Ƀ#nf:=hs{jE"@8A ]Y0j;L=$ z i fpΑqٯ5}o3V_"ICHu{7ɡbFqB Mۆ~J^$0H(O H`Mk#He#d.x 0u]M6!b0*FwJC WT i. ^XE8bӃcbqji倁r_ϡ*m]wտed/s ˗^Pcpqr)BH!b0/خW\ޮox1.ʆWW䣂>NJ8d )I޳lجrjrNr8f^#0[J]/9X˗/mpyq̓cnoLc$*,Mx,K<#T$jR2:ӇȲbjT$u 1yʇO0M8/7b g na11<}4MhJDjL.RWzGYS55I^<~!i6)<~!1ϧ`ֆlF$Z: *SA?n<1;Zn.Ï)7[^cqTiM,$Vx"MSR!yuqƶՊK &LwۿC.1mSVH8Mm,G&1޾=#2HvCY ( 5lkxrr7Ͽm4T$ y2!%TĸHPѵ<ަDĚ((^8EfMv"& ^u]qq~knהơlk-o>m5b&I i3rp)R,g4C)ޙ}z!aV@'k-ggg׿f\(;&i\__3(¡Pq"R?~ tj[G?am48"M%Fהۖ GmW5~ `p()%mqVY$Yƃ`<>}@<~tʶ\CḹGYXtyoۯk$I>; 4MS g6Ll6;O4J`Iq`;hSP`F;:ۡHV͖"ɤDXG5-7_KmWc\RE*F!}pOX^p{i:{G\ܬ JmK^LݴX'Kl-Ӵ-ӓUKܳ#JFZWq>X@*ĢH#Z-Qo/}ObOhq{-k-XHzbi'#,Kr",::Mɝcb!PQDgH%FNw]MU !+&8BD4$D(!8yIcBp iB#3`E!d2]wwW$;P}b" ɖ!7F6fR6Qz3Ikt`&A$@(!bWQ57k_~5WKy斋˗m? \LHN]e`;K"=ޙy`V+6 eY供͆je]n6%UU6I{~&(c@s-B(aC;FCkK2aCDam<Y,f3<c;}g:r||xłXzyKUAk: GOQ3N=bq4a<ϩ#~A5Mkxu~=ǣ#Fc1xI!Fttln$Il>h(My1<}ǧ:<{-Bm9CE0=eY՞<F+/o*£rE\(LQ(vSRҲI2F#RcMVLVl2R*&"8i5UEcr↫K6xAEH@.\`(;rߑC0~s^)%BbP%e E,-xn KwbD'mDžj AՁToSyE1I$ G|귿A[o?5G'\s"ǜ1vK۶<+- IbFj{I-pm<&&[EQKGڮ IDAT{oLXӑ!7n:-0㳯b-"&$FH<‰6n٬8H7w3J+yԝgPbwdWo[ `IL)(z2k;did\0ʳ>@Pb`nЛmnޱLG$¢뒛[m{{[ш9sS&zzOMSS1/.OUC)dsq~l>G{ "Rn!gTU1>H ېQI(AEY (!-ur;p&]Ir[ FgYFɷuŇk-@ Q #L:)n˫s(Xƚ9OHiu{D$FH:F*Jvx( IRxtWEE^DI$ }`URЮ R ?F@w>\]]4QD"0H ;ӎ(VH!xlx~1hڒdYa B$w`zW]} >~{Pa>S*,F{?bǒ /(re>;_rqvI*#{r}sb6ռtZn0J iinZ ͆z#8%Β066u7f3Di\(6wygBCg4RFd$IqC;&n$tI\{>ZA2ԈCiw?iFfo>>'w5;yQ-b׎ӛmk.ꖺŻ5Qd2UUqqqݱnooٞI`eŊ;`67+lh:"IÐ&xffs>K[yEH(pŢfIΫ!`$" /*ض`θ s$g[8 ҶMi5D0ব'V6Nczo(ٓ ߼&ܖcMv3$\V(lj][Y0~D$K nݴdIB*C#.$۪fYҵ Ib;@[G$D޲e_.ɋYל|HNTe<3 E1MFX"IgT%))F4,dB9IRL+0ZcL7הIR6 JCUb9J@b S\5Bvkݹә 8F㓇1LH0 -~zXֶ-/(v?ֽY9b SW)y6ǟ0O~‡>m[.ί|wrАKq;d:1[Bߦlt-$ [5Ǽ<{grMA~- ?Nݏ"|7Gm$e).TjmBsZ!Ƹ5ѡi"A&y୵W[y6T"Guw(}HY5tp>c6[1i,"ITM5P1$N)%]"v*u֐mCxfZcRӋbwU pRGpt_\ 7Be`q=sC:Œ,v!w ]z{KM)e/pA/ ,7K Rc0C*ADILU76H4, 'tuM+ d1IӜm\w!Ƕ-U,HF!V X&3D*4tOeZm-uJ|bTpջzW]}v1BJlՒ0K*oj4H\`%fR%H"jxS%dPng?c2S H$LS6-%@Bؖ[sH4NLfSa2J9Oe]xh4""l6|/Eش cr`V,MY9 ()/ho-g~)Sw;>3e|RP8S^j(94Qqƃ+ndBuTH7%Mc1H8G(rR4]^bdZ,#0ʾazOQDQLx"ﺝoлw\߶+\__nq3S 'acM]הeId{Xk<`qxHٖK,$=a>_<|?!BIbb|u.c !!̂t=D1I" 2 Sg (ܕWHeT5/UF?8XL_\^d)Y R0"dJ\Rmn M31(,KȲȩʰͦ,3nʱ$R:f4XӡB[y`:# M8lɓtҵu,AH6kGZp4c$}?*/cl1L&j`X>p.1{@fFHMLen_ejn>N ?.yX?!} {n/"潕r7mϕץ7nh6쁫Cf8|?Эgh:|ŜC@,ǬM-y.0yŦn@$UD|vWS%eYPcFxd:-G(uƨbK?%Nl¢YnY^mX7,Xۡ$Ii$M a)0PUը@Mېmۖ C66+<]ӲbAڶ{saLFTӌ]e\O&rG?d4M (-4^%y۱ޭe[6׼'3>3, 9;9NעW}.R^oJ^3O t}3;o~!/!+2)"vb>EK<Ϩf#bZ)Ѷ\?{8OƸN[tISoIeM=7W+]܂sTѴ@ \'!3nXmZmh@^Ty f>um|vpMBO!Zhm b'GdU?wd]F $Mdߏ_ѴDH.a+U5ƻ9>>@"Ī wy3cMak yt-u$&HxFgs5JIۆnjsvMk&gni-5xv;>S=|HCI?EON%W )v!b<Մߟ0:RW+Chj|7{XhrcxO(\=}Nj9͊%Ũ 4Du((rb1x4JPEj]SnG9n7&I&xLsE$-tծf<jzf;\&g@smֱmmnl놶nn)u{NP.vubJmKUbMGYQ[K Cdۡ_i DP(%'(c@)k{I7eɔbTdǼzc ]Ӑ*|3cDaIl]Al153z9I y_pit~}( Bz۳ɤLzV+dGxLkS0lLM2*B xk^"%U?5~ϡ 2B(Tud")K~p=%!H. IE𖫫+&)l҇Od "JH0~(Fѳ(- eybH9A4ml/gdyxcC#I%ˊ*5&*_%d?`}Ƕ[5;_S|MJ ]B6횪(Gcb+VL#JYv;(cZF\C^ 4Aۦfagmʈц}~||9>t:-\v\?\{=ym( <12hv50D ͎4Tl%O/,/0:G ,s)6xOþԠr e4.g{U#NyWtRi+hZKt>bx' ! a ?:1jup]dE+1vex eYoaw?%1(s4;jY_^27M!BcLz ۺ4mz-F $!'DK9"R<}((TPLfc%ˋ征jrMcyj<#/GXklkG;*Ig"fEe4 *SLFc)(TdŘݶյJ2Jr"|~1.X~ 6t]Pu$5(a(.9Vpu;hB3lv;V7Z +J!TE G5!wn[&umYo8T)6d(e@ M[Fi\L+f&QZMMw_`TC'lxr 5JРB vm khָw-RHrchBӈs\-Wɧs씮gDPJ0_LEN"&IFyRt.Xh;\a]wT-*K]h-Y1vm2w9"*4&ҠdZh]n,GDQ*O.bkh\/Rm(G3c,nPr<:3Mx[SݵH]ӢSNl?gqpmb8s6.s lg_|ruPwcZvݫkz )ˋ%= JbV+پ^F&?*b4Pp}f-dݮeܬ.qy~>o.w85~1!sΟRnEl3[={GRXQV+^i$d^syz7>0ڃ;؇ua-T>ҶR7>P"İ~ D1RHUH.z\ha#P"jR83TM'HOfD$M1 $M2lhTѶ5 9ctN95[uE@6I9yxʛK)F,sOM(5b]k1EQl6lv([{nj6 <(2c0Rb=@vA5,g􋜁%b໎\TLmv khw[UJƮf>bur\Ҵ;l4qdDSgx(1٘rT UbhC9D׵cI)kY^Ld~mN,ȽoNdUI&%ˋgܬ6rt`<勩уvi ^zY/[5&b -n9*)Mb z%KfCJ։=eyclyNT* r!Qa`ܢa״tC'q]7z>~0X&dJid:F炶تT)B*y\AhvHצ"y g9@0,9::B)ֵ&3{p6˾>+1L>mkO15o<{['FݗHAڂq_&u}~Vd?/}Wb]R;3T<~k89^0eLjJ52&=~y(@_2ex?%TZĀG'+E)=;^RD d%Df9^ Z^w=v|mC$ʈL's"z``6Tecqz|bn#ׂQQ2i8vt!mԽvnQ Q(ڶU/ijd#X^^/jyQZ&ս)L)B֛cn5k;֦bnZKY,Rtʣ'y/)z,|oXp|ze|Л |K^{oGrn} Q;#ao.W|njQ||pIO\+ȳ1cyuţ'l izOhqVϯs'D!KÍ.D'HeM&q||gwɲda%?z7yoA*fKv\@4~%efxɌY4'I'l62JJlYQ3mׂ R^e\.)d"<#:w5$[7cpҝsd2;TaPExGtlmKLIv$ yY,Y]ߠa1MNT1Z} z\gf3| E"+QRE&#JM,ȝ% |A irH9fk[B 42 !Au69YRYeZſ(,) >>@&ٰͲl?~<.^zY/e.}Y3F1uBl~{-ƨJj|!"-=xZzZۦ+ $;;~_e$׊I1)KÇ?\^߸ǽWNʜffȴ1KG<nv+zoΝ{l+FIƌn:2) FCG hQdYAg-2SLg qzhEzÆhTYج!]b~lW[Ԝs|., J|lh Mь%99qy?Nl# "5vM-'pZHH!(F*vh <5,MЬFHv͎!ª4X&h#>,#118v5WOzvAY %m躆ٱ^YoV4\j*i9N`߰]gͧ{%toUEQ,S󺟓ZH[%Frӫ#eG.z}Z͘ ޶Qt: wb`8i__#x 6x^{w|:)gmv,&S|.O%`y)m:$gn-w^dz7{~Rg C)rl;W=x&Z R^!n H R=w d) l?!?я1 lT!$LO?o~jD «5X' !],d9Ft./_}!q> nnR#ɓ'im)R'1(uvot,ӽ(KSr)&y:o6_zG9+Grv׵Տ~rïlh6Nl&lW,og3kUu?cNOO{.Ϟ=a|;'?YCn;XVTU0&S2uhtxwe}X%KB3K HLyokp#/x(G nU}*uO`ud2xBV9۵dDe^l)t-njD)h[K& "G9JlY(wk$`EM)XNmx2_ӡ:е"]< C&X51z##NNZbHYQ2&,DŽ,}W``TZ'x1I"CL0@}z[K@dx4MCtmY_߰^]etfYF92=:BW۶#ownKmq"BzLb{lb;k;ڶUyy.|t>:OS?"B$s[9:*r";\W`t4 0rMy.D)%hƘlCv-^\Rq11 iv&+@}Qf'U"H.5U~(cr}?|l>d^wK< _M@@蹺x/\K״(iRxdG!d BU}%>$B*HJЃEk}RDDiER+Z+YMLt_%Dd$^bpx!>2Rٺ%:y~}lĶ v}:G˲Ĩ4& r%yoV\]%fp~QkӱUu6[\gUY(Ğ62/6_ i8Jb\Gc$797˛6$E Œ,whL`hֳ.WY >©(=%s|_,7ܽ{o}[}m>䣯=f<^E2My8>>1YJb|c g}t qru7]jKmXw9 )кul7 0:G+62p=?X$6 wi&|FDꌬLÇYoF p}8I~ӟ#.| _<|bF}<߄zrJquuBm TܳvF(G3,H&S+dHI )77K.hոdo3L?%JP{"GOz\p(+1YFS7 6D|m7K^}k;~ ?/yW_e=l]<}ʧ?kшWϊ}g<u ]%Pz6)Q lл#N8(CkM}AI'5b!zODg&UoS%`G6`K&/:뛿ZKM$WgϞq}} Bل-3<Ĉ'&ecL)b0ێg)k2#ɳ2Y{*5#Ԙ, P]mK>s B܂<d8?@]L#Eʚjl7O{|'ӟɖ ( ]:Ory&½WfwL_%f374/vpuuEQ#W^ym`lsVNOO=67mX xC3i=`!z D g@ +Jƣ2W\iږ|T6[vMi=LqcWL&FUbP\!.h۶'8zMUmǝ;wȮh-mbc涙UCˢR(c0&VvMڧ !}h)k`I C 7au NɲA*24n7R{8B;DRԶ"1x%e^3Rz,R s1"urJ+FezlL-St])@j7kơMkQҠAJXIY(1-H !l9!iUҀ|^pxq=h2_b^zY(),J'b$8Egqm!7& RMA=cщe6 pmz]sڰY7OtΣݵw5ZgHkƔՄdS;mx݊x>zomS3Mȝwٵ &@@PIгiPуE2 ݳvkmѣ %ضHI锧.Xid2n[:Wz"SSVcd&*QK@dB$rf''FyD@0ZPe^&OK(As*@zEI<ъ҆'ٖb kq]µmj4tMKpP I4uR-jp!ҹ$Y1Ӛy $R}XkalyQ UҼ'8~jf'Z3޸&ggg˂_Bp(QZ{`  4^dH/֡E!9VzE}>|/lkUVϫa#HՇ.9YnRW-o F#jkEI^. 5L(w|plҿ%[H!8Kݲ^^qu՚jqٽW !)kl[(t 1Mt=o%~l B#K"`aZa'O/nBk@bbuf^'vvYX`srrF J޲^ռfO$ȣg%DJfھHOzdL&$$%q鮻="3\[=ll2#3"<{9{>O|f]Ѷ!*}C GeNk^xzf>)}5[',&E'k3f9e)2|YC?٧L3Knwrp EI cտ6\<ᏸE˰nŚhm68|ۿOOK<1͆DƧ ycoM=;#h`D~c_Y".0:PMLAkLb웚fJv tW?u;_ @tb\}dޒeYLK,,gDȳݮX (natX&+l6;vۄ1NpmuRY0O)6uv,Q u]b$`t4oL 5=,,x.i]š&R<( w(j}GHldZRLKdf%PiK%'MI08`2őE"j .DZYv_ܛ$$>ψγi=]7!21$ 4c_W}Gbn_vA&lF9Jb&1S-I&D7^0R1( Ȗ~޵w]{޵ߤ)HM2w%ƽߘJ:< vjAKWu ]b:H1T'QV<o3!1)аH[LHv &y@X1TeYb1g`qd}TZv-D={L4Vtb'UhL$L4=m,dʓޤLB:pӴݎ 2'TamJu4^R>#̒49{򔿻gmƲ@놪d#e4тS HS泒٤N2;:G`섶כjacl=J+:@ Į!N趦6um8Mu=c#XTMulw[FcA?h,8~s`1ЃH~XpCNy'I")ZvʚxX/K,#u]DWm~}7o& 148::b:)%( Ց=)gd쓾 }ۃo/6z/mW]Y@l4Vc'{.IӔU2>zX-0ĪuR*>0@AJHb t=wߡ\`elksBR 9_3췔2Mþ=`9azQ>Y-Iގ<fP~s t>GT=]ȬP2v!>4L 2fO㔦s45DD>,l6#@=A!{W+Jrd?>{#||}RDB0f̦%WWW{tbNmQqowZmNJLL&Se^Ӻj%j՞_aH$91Z%4c0EZ>:gu{j&YFI7d唠 gyu m,ڤlwfzSQGb0P)U1pC<ƈPFV?Y>⊬ܣk^|)| 9/_~tS~~8B[C/i;Q- EU+$A]> ~Hmg/^k <tlἣ\ِX,:~ӹ[{,#&bPsVeѶIbP|BF&E6>n6GEW) =Z폿)%#w3? p,k2w>T]ףm Zq||%|G?dJ);gٰX,PJk=r:oFQ)d W_K`2=u[ޱKCpea&$#6Akc: 2vK[ռ1Is\z"P LNK212EA"7lYvIWNԍcۉ,@cmSN!*Mt~l// >c6 2ڻkoܬ5EJA*YQ9a1l@VTL!8SiaZ#9ԒDŮq"u]anRw$ ^ Zڮѓ\*1"lɣ#`l9i_0|heAe9-QHYP#IlҳL1T[UF4/h6.؞q==;[^~by#>} K[XmLSnL>S ZfY( l/ȣc!?!K Gt1L Ħ)RtIa1&ufT?{Ȳ{l]7~ȏ+v!և!v_?d?m;@g09_|Y9!Ms Ҽi=3tI7ǩۺ/:p=Hd2\m$OP¹٣f:puuEf4FA\\\p~~֐%MG$ ڱ7}>!UV c4nOY5*y.B>L9ŝm9G7|}Wsswf>b4$Zm"xIi2%b h"gk!-RODm1Ƣ=T׶ֲln6̦bZk4ɓ'zmj5]t/ķGga("zt|4PZ !*9izC{3\( B(4<,F)_:+hWJAW|N3 F%ZhMmGLhBŖs_\b2h w[V5AEY>a-'`l1#Rv-wL~ٜ$Fz&X;oo/b3&z!zb6ec_5LV5ݎHG1DT,#VW6(&:ms0=a>]0N87 &1H턳'<:{B۶|(nnĺbZtkϦXEA秧&פy1:|vwwųXٔo->{Gqq'6ۖ|.riއ; !]綋 ./_͏qTܐ9Zk=zxwb13|{?(iT lVAeTUä2}hu'\_ݓk3j:v_|AQdgKNN/kgXً L؀BBKbJ炐Z\qOKEQi|LtαX,xEolܡuYd=2u#tD㛁Q5ERx&)fѶgI»Fy+`$@VZdE\)d,k3ڦl$8"d:3#D5~ЅB6-Z%F&g ݆D8G"mTdDmda:< 䤮B7gڻkڻ+6V2B HSKOTE h1DL1]K iSJa]$+nm Xv~1$Khqj҄5Y('ˣϗ=:hw[L0VDӳx*:Ll7JgQ¶?dLmE&R5Qkpa6?b1# m2nV5"ggi] ^jzdǧrB7*Pf)2'K$Stb006# Sگ_pk0q8^(LR[ kk ͊5ݎݶl6uD|M"'K !?k-&tlP Xn94xzB"$=yF\o׃D\zfKA/AU۬,i{%EQ6 GĤ(~}|9{`+{~@yYɇ@5 u=!(}F >W >H'Ox٧b8>#&YN6` VM+hгx䙱֠LmYbGu BFI@bXw\6J0=fyt{ϞQgm5Q)MKVL'? d(-H]G4#+srhnV\Ĥ$َ̏{7״Z3YN{? jȴtbQ1}ձZ} T(m?v=֊5($.5Rf#{%mݙd 'i+fG͊vW/_p}}M&#D H~I_+#@8N9::GatYk6HLFb 眜;-?{_ Aj]7ɳ b?*NNN7;jvhHaVXYl]x(.ƈx%?~L& Z0r?79e!٤W_>_PI-tl1t 7|7ړ0r^H@k݃G bd6eYOӄodY%FE-݃0 a1xP8nu ]`Mm(i$$iN ma3h IDAT[nP=\^i$T:*  XVZ 0εEJQ-bdG T<կvQ ]'_Pؔj?~۶eXR%Z*l6d'!c IZ0-MԪtZKb 6&F1)2 -Fc Nn$lf(*+mU(&rêz'KRMK\ۍd2aX?c%H[P&I낀cYRQ#]Ls|;Ʉs D-f8,24'Ё!s(no|kڻkگkj% v$jII #i¤Qh#^I/gYY3LV` ͞ߡ`J)&t=o:-w]g9$aVXjN0D N4F|(JdXR(t`y$'ƈE`Z%X\Jiiښ蚖lVP()%Nj9V$Ikn5sIɺȧ $'AknYo7=fOoIbJt Sǧ̏)%Kw(ߢ!K Fyb hmʠE+)Y_3@ԢARxxt?0W*/@ 5*:m5͊;n.|InW 8݇Y5ll@8༧nI)x%"Ԣz@UU 0ƠJ@؍ĺC(Y”ڮΏ$z@G++BpY!@a]\GZLp^Q#>}>T{]oE/86Q-oCᾅ_q,x`Fm Cex!{m0m܏`4:D ѣ||2Ië㞸ceɲU|@hJ_uB%XX'Q۬VhLBͦD8r_=pfȼ^9a/A\]ۑ6J;h[ 3|!:kuT:GY:EE{tt O-H!6u4]Zaf٭w,Ku{lawssC&6; ,mNZM@֫1 ܭ6,O<=a?Zˣ >EuŒ>^TY%~xŋ&%SQcvEZTd֒& ݯYKTT$yJX< zevnvI֡/N|G%7zջ ߣRukWWٟ9'')Ϟ=Er*[㢡V8{:PbtTf?6\ެvZWfz noﹸ$ϦopsG2 :XUGZXNTMͫ bA}A0?"(CTZ fC0 Rw]/̲tJ$~kI޵w]{޵$ 0Aޏ3 $& (]hNmBVd} hQE6i$HH'%LH3w5VE PQ6⚛9U>:+֛ԑ3̶ І]U7~kʲ$oHӜ|[ozF͟_3ч?<a`$ 8"DgF(gKBoAi5ZlX#DĪPE0 ָ(b@9RQiw;ba"hQ੪(3:(7d"|6< /7/`2B3Fۗ<쏽iZw#+Q6R#EŜi,q}f^wBJiԲo](Oddt}{`1ey2mێ3Mqf'''o(3\ 4]Kjm$NłzY۴#p4B`Z1}pq}}M鄺s嫶z' ֤$BP>$) +s4$ϩ[VIHyQ"RvOgLo肢r[oE-?7 F5 >qt2)2^OsN&SQ6Jh+~|9;;c]\^-t:a2YVg/9?;zG[.qt9;;J-sV4 X,9??HӜϟ}wD8LR6m8Ƈ;/??%/^ zMt|̙J={FfsvՆV#O-g~e㓂Ǐn*֝ؾieFjuG*Dc3Zy" CF1sZhQJt:xq)ijyc꾨)wo\\\4#vGՕY}?5■T$ƀ-ElfSo~ ;yN[%9H식H W{AA Q:st4g1d}>eR(ɂrP-m㸻[Zm%3ć<+YoWD&FZa-$iF!M36i1t$q]1 `plqrzz:VE+`dPDHfxmҵkepQ2dA')M+Ҧ$4i:$w{Cť:unAa^%%I !/ u8׎1n'|45=-ݯm$9.D( eQ)1vVN%17I6MJ:$ݷ2X4Md2帇w]{޵w]u-+Qttƫ !,j tta!I4ס"&)LˌY9UФ "&ft}UԘHPW- "m4Y'D 6T%"lv=-2#=cPWmKݵQK,#v M=e[q@a2ok%oQIXI1a13) eD-];Iw.i12fhjlzw cmǰ,X,P*t- 1t1#[B%Ey ʰ@'#7=OsoY|ԭGM5c "¬6L򒦪y g{G<~+6`,+h[$xSWwDqV;!j4&>|Sc\Ql+)Nwcf&(, C":*"L}_xmhڊ˘\WXj66!x92Aƺnv1n,V3b7ݲuEZl&ˤ )QsPX|+^3Ѵܼ^֔1- ΉMQO:G{b_u4#[8#d,NMOL9 FI햫+&I= CiwF%5y)>k;QF*M"$ K{/V@)I2䉾ûn4ֳ,jQQުDkZߨ|(!Gm0x ),5{*LbXMLZ{j J~  °>>>+ڻkڻO5c Fh;GI_sa ƪs-$R,ǀR,feπ4h$й=MȌ& ćMvYՁMyrzIJ̘ G kl9;srr2#K5DH(J)5F迯V` ]tѓsBHʅU+ :.TRc|$Q7l&p>j!{,+(3o%_،r`&+ィ,+X.O8{tryBQH y*TphzK=$yFxcbٽ}%ƯxC0bn8CHۯx|)ڈm 5kn/ju`V0H1& 2FIR(jmF@ƱwQ=bT;{`\rP؁tx55sN6N>眜.Jr!pmܓw y8~2>W|rz ~[v"wJmA kL)I9^S|gz/_Q M#6b9Qi N0Cԧy{X\cƹh늦G L:_Uf~{,u]Q觫~? mzTN kTԲyUQXHSkfiApE sZ=m#sפIm-ަZMJ-ǰֲX,g<ˤ8]bЇeƬ7ƒ̦cFm[d>czٙ'~*ŷ}Տo裏v~={*4e۱8JP<(_6}PKĈ۫Nb ZܛVb1AKa -z+ V>-)%FþX*$H/1#y>ޓOfݭP'ߣ,Kеl6|N-u%V?G' OA ^|Ij놺 #3DvMfkʲ1V%KQ1>8::lv|kM1}\pttDY/cyɫW׼)Lg1k&y ^~zu !XI€bs#xE~&Or?:ev4M0i®ݳ_Ht {gSܮvyړ +݇ۄ }duݸ$9pgOXklDx|ٳgH>(n sl hB;Aa`݃R@???kf7wժ!㥟c{ZKk7*mI@yzng:-jl&@4ٔlAKѴ}Mɪ@)E%d ;32g?AͲlwk0\Ak +#QݶAňQ"I%'\k yȊ4HSer] '7y@ cRKn4!̵¾ E,>:I߇"YQJzul7+#I5a!hADy޵w]{޵ߤ9.:a}H׵uM P۞yLjIPޛTiP9e9a6:'9׫]q@7n " :RDgϞEʼL5|S?:b>p]v\\D'$0E.>AẆ:,`Fh}1FF^* s@4/x+>u샲R(]Me~}'>$h:rquzT=U`lfOvD%4GctC/x-IQku.P38??xI>)Hcvs@G:F,d9mRT $WgF! w%p6F?8)cت^k>~lJ,Rj>{3Ύg$}j抺n'h ٤Ħb |^ aFv/&8G [Ev=E^N'G=xFq_vXJE+}cbn[c$IMͮp{X7iὄs| ^\x D ճ!tUDŎ|D0H $瞽k=9gggdyT:2jg,Wf3"n=u~~sY\2,=߻k<ۺB #j@UO_h⺢dt5JEm >pZr~89'x[>!aS֤%o>ٵYцX0*a:#}FĚfܹ۷oV5u>S/0R1O'`bgTPqyqY\kȾ!.) A'_^oXAw9<<8Qۛ1żq$&Ԡ"iژ"%U]Ò$'w~oÃ}./QܳwD63=.H#zm[ X|󟡅 bBG~CPi\1 8lGHPD>4mGl:]b`]_\Y )M!ڶɳ(@#%IDծ0M ڎ²zm;bYj$Iu:@^^^"ܦVU(k mⴎ"冮ItPIu6TMmRLb$)`ҤqRImZd۶aMZ:ʲd0LJ:7x[\C {/DuWwZZCMͦmPbz((خE` P/lMȰCvRdy=^x=^kTg(|Qu#8RhRC Sghu(!I"7,4>)%(-88ܣZ^,k{ap^xux$t.uH \KjsZE8O"FDzaY!x6*`uౄ RdIE0jVIl| T .hlG>fݰؔi #&e )&j숫u˲r4~ɺj) YzbYkGOyw,&*TA4 Q" '8 "U]bb#n -7kzaQ.mzNxs_|/D~-~ 6Q~;D%ExتYKP$iA?deru-W>m0 C zXl輧ڔ&"u|#ϸ(؛FEz\7}1 ȻQ.bFҀ!k7s)sxxmF`3Zgc}E$[y{{{M5d:-Hz$5@ۘa!&c6f ܺu 9ʦ޺#8f36M& 2!fX,*>+ڶelO]\'8OѭK[GG,K\g1Zo*u!8suzb6'`41Xo*\__GHdVƂq *Z#(FtJ fԁ-Y,Z(rU24}gKt(着( :!Ismc4 ۵<~\݂QJ&)(L[H3FYj~_b G4)ӽܰZnNʲ瞏1}AX8 P |>yw9=m[)~sncBBg#%Mc'X4@SiFغ?DOSl6%'%bI3L:hB(Ɠ_}of2.o~%{{HUٰZIiTVYq|ry%"ŀf !MGU}oK؍ p6:Ű?JvɺYcдy ^_$]B@?џdTTAkbC]YqzŪ6$۰ZE [$$IS]MSl6ʦd7!MUeB(}q#:!0nL+úqt& ]c1&~?4W=SE@mkJmAABn7u)%F(hrU:!!$KRNɲ$-.Q}(@$$੫`qGIJ]iMve4W߽K2 aRZpnլ׬F#2ITBj O:-m0;?*Wx@ylgAkBx b^nzz#tf6uh, ,3tȂLb!pqâC'sٛbb`Z!WmYN  F Ġ bajHrh֌M8ѡ!15Bxlp|toKOhpE HMt)Wd H$'hMp ua6GKQ"!d9K/EEyG$0\M6ZC$m$V1Ubaw8cZ2aEIG$}Fh#hd]rdqyA]wp~;:emsAS[{XpquS >$,OFށC%$. }JHo-2@8rնu>]jwΓu$J`>8MtTAm{ڦ cցmbWEhۊ<eMIk=Mpptw^,iۖ>{=$7Di1G`]\ѭC(v}4쌧GSZlP*v9縸̣e 0*.fs$aX[&wo;Wv64|g)x1E1|Tt7tCXR%ur~~k_[x3Ү$MɳiBhԚZD6,E@ݶcȐ e`(3")r\0غ 'G(1&vcpmٻRT%e1Oi;R 7F(\6"Gu­YǏID(sFعvqyƧeO :Pú\]\Rߺ(98~Z(Ҹ!RBn]DNH4MvMq謡uo=-YbPJl $] 4&z ֡Q\<ą+9U.̨鰭# |Z'M{jLYkN] P}H1 i8aۆ\SnVI )z%xӁ%uѥ1}Su4młuY.+ AmkR ST6bKw2ڦ锃%֭1iBʺ&o[tbMe$$j]rqrqEQybMjD:LsѬȶ&u1Mt]t=omK:zhz,mi+$(K6Fi@J0*jw]4ڔx7GGG6W rM9$%ISLI" Rj)8mɍb\ܽ}$+fz"I4ZŤhOt鈑P/.Y.. r6Dgg1>)ARC!b ݻ?CzGp8lifݐI^[d$DeBMej$NK% 2͒ggԚIeNF] KA0#jM\+...xvzRy=|_~%HrA#{;P/zrf6\~S\g)˒íK1Mm1T'2N/OlBY;vEh}nK A4H#(Տ8,OG,[yw0]m z]fkb6|}q>thpE)E3߾ ]l!^o"zѥN%BnLTMjS')dH0OްLEv V!5VDkW\X\Q 2-IM02Zmؔdbrkhb)p 1e`A]Zmige=8wZTAD$҅.ŽS;}xGp.:0>zr$,כ̮2LZ䱻Xbϒ,yS,|4BM&s#mY\]m9Ym?׈) ItIT$XBY6CT%oQL,-46vw\__X,{33f:^BLtᄫ42(7-e hHqAQV-uEU5xYѮamBʈ(7{cL9|>ߺ˲)7)&1,;ss\_Ŏ$E)ݻ;M|>g:S2~%?rqqA۶$:\7 ѮMӰٴH["$ztOsw4]":wڶ}aVZspp@X=/Yv]3B_(hȧ}x<=;6t>M ><mc PKBhN!IVDto=6wːa?MIFclJp]Uq(bp =w8n%|!8W*2ILHʪeS5am xK@ M;,DOd:Ÿ'X\c.ISmzSQ5-D$rku߭'#Z`zx_qӶ=Q @lJ$J*k-U..Ĵf硬Ymjk0t?}N7U<|P7 !\cC@cElq\/pFRR65~[jq3E\Xq>I#َ6b&ISC|dBD9%4` k@{=^x=q]]KBM{^Eժo{H8uDqdꖦ흵RMnR: BLJjϲڀq!P;%5֮QRKL2ai(TCd<9GewxRt-ͺ!If hY ,(Md4d:fI4?t`Ayt8Iq n#7??}%{|7[L[.[͛oI&2F9O>e2p>}ܷr1`q;E.0%ؖsdE~{Kb Ey"&W,>p[Յ<쩇K !Eo IDATˈ_?F a\rp=&C~~k|t!ȐےB⽢(z^d ]łbA۶]P-={%6s0Vn9y>BGAJݻwٛzGQ4RѴU, ;݊z޼Ki>8|*Mb=A|~vQ w4uE׵ƭ|Bet6łXenJud@(r8g SĖdt0!L=ڶoZ㽺.{WsB,w>/50Ӝo=6yt$ٌGY$i]Bꊫ~Wi.B{5!Rs~n ؊ZwP-B(b5$KZ6.b[~SwX+ENŰ& Ai!lYt+˹D8fhXxzk;>o'}A >NPb:7ƶ-587g]먚զj$/TjM>uUY~W<{16fC-y0'x2$h .hb[\ѶEGp<<-/Y +O JKB B|aS&Ǯb egzz6÷58;#ɵafhkq]Ntd'3CD)ƭTU'>ZH\pLqt됳% <;orrrkɊbᜥ(@>U]d>'\]qyy)}&IX,,lʶ^Be u] Ŗ\oX6:P%Bj (to{,k5xDsD˲왑k "* f<}rJsp( LKjU>Ak;"z_X,8:8$2Ɠ,7Ҵ~ wNg[W߮V@A/t5MCD0^&2t߽BAཽ-L[u:?c+*:Y^x:QiJ1u, .uhRd9/{)w3Y\/ &{}NOxW|~[qd:cz7bI6*8;;㏾|DŽXV|<~ӭH~tt6xn`-uMc  V . Q7;G$dTv>> j!U8GkQëKfw ޔ@ ܍+o)Zro^w|+R V rp6,*&7o˜~3=d7\sp[qzp("5xyGhcJ!1\7fMU I /E?$]SZ/9;; MT-:jg[wl6`IzDk[o3z{{aFyFdzzZ yUUm]C0cXx{Ot9r Jћk{J<ֶTeI8Ed$E c1 T γXl<[s@kd2^@Ef+0RmJ)ڶeջՇupw]e1"cLJו(݃ME&j8;;OR &-r~Q99y|Γ'O>vO^Y,5);<_s| B!=T:6mk|!(4AEE|֖UKZ0GΗk"p?$q_Q_]j[Qt{=qttW_=|!q1qq"dtC5dI׿\5@CQN㏹urxt~ b11:v p?;;//x{֒MUEAdZGm/?ł!^ t"Jg2l۱^]qfwY_qxCwnGUU(,(+C:/u;w4G>#~:ijͶp^Dm= Hx߇@68Bd=Ƨi ."-H1D\ghw8St xj/ f2'b{Zle)d;/]] Av7BqCѺCPA-ζخM)tMEn&v`Ʉl W Wk&=nܡ>>zUc2A</^ÿXuXM @"G$&)5ud٬`<"XpuMdRl׻GDLCBL>>TEݱ˔ݸ az I>V1i$$!c[l0-rD H`Nn4F &(mbNUE 2q0Lg4]<(KA}kSfEItFEju1">Ed K &H!of:Ҷ-W%t B2{Ht "^nVqcp}qߜd'}EQ5oܽǦ^-Y.H)y =y"7˨ Xӹ"/ Byl*!H>}f]y&I`@JCIFL-","#^ ^72wh-*rB5V@-/LZi-Fرߞ<$(Ig#4"C yEYmdyQ R,rd+Ѷ58K%t]zmzuzSp)& Նjպ;Rjv y! "O~)Z%clV4!ɢW3mg&>c5ދAg; mׂhEQHL㈏MUqyq5M!eNkInpI+Zx#uM$:ڋ?grޱHRHUctf<{B7&\dM"w,αz;뵸hLr~WX2 sY o.d<C_ǟ>>.]NRʺd9kI@ gU B|l,Ij躎ztG,J&`<2c T%X$uGՒ#Vˋ( C)"FMH@r)QJEǷ/i QAU5QjF=.>Р꣔Aϰx=~K)~ t]sH;<{FyNk9DPZFZQw, He5Yc'td:Dzb6)FqhE.cbgg1'i۸o"P7 m[w$]D.*XSVbь|)n鮗$٘>SNNN8urՊOxۼ;נtb cbL.T͚EhSwԭ#&q~)mx\*A&nlMp]+69-]ܤjR¦whc7{oXЫx=^x=~Q$"ТP:oYAe$»{Q[Ql<\zuXWk22$a(:AJA; DRRTv$HQ1XD A״4UKg>5W1}2SUzZ"auiחW,()>\.YK+PtxGՒ/~0Aeu_}7X-RQdy$=[cPeJRqtAc0,'Ks\pVƴ,U5ury!c oo]_i"MV[.[{vڦXV,5]^z18 ſƋ] JDTC^m265CJba>Sv,+ـqZnݺEul+={O(}%cz?97ý{p6O1 } <Ç?f<Dl4Mw>eYZHt,+FMs{.gggTUE]G@e[mW~s/׋&Gmcpmu VG+QxOuw sPGcEtC݋e){vrGJ`"w ]/׭ˮkxx#C};`}@ޜxB6!ߢk;ꦣɜ!dHS:f 1"ίtwds9] dXKR\`hjEUUu+ϧZ`FQ^J1$yAl64M=cv݀hغwü۴ZSm6CɄiXV@QiVlDbbtuE6 :^%F<]FWZ<%/br=akuh};uau,8;;Z˺*qLDl֔e8s.HO>gI?yA[Wb嫌}18y0=CoX4h!Q( :?hfڦdجhR{4n=>u:z jc$€2Y-7ܽstOZ>C6{ @## <zMngk-k>#b2?dXggóG!DTf\ k[:J3ۧn[=~Be:ú#j苏>l^'tՆrsŷWϙc̓,9oVVגݥ^ jd$#"flƌCa0m!`†c،1F,Ai-{UumYU9g8|UFbDddUv |~豱,s7 Ðm>O"sH)Y]g&MsW{|~Gyg!&ClޡԖl>cM.]JMH]>Gf!X9{I2ccc7V}:@..áS톮ŚV#[ӖEy1V!,g>wd2h|=E2IUޟvL10feR`1 1]({񸞋E^sbQ8vT=HZkU &oJkg-Bz֢p\a7yٌD"4m9u4w qV8FEY4 & n4 dϜxA@ y^8cnR (̳mf/^79<'Z Ky$3'Z^H?&׊t*5Yn=qyL'Okl ,H3 я28} Ʉ8v0 {1`}}11gΜ<^z%w6@G + ?%ܰu@"U5A/]Pn:Vk)yr!`B R86r \ @iJ2K8.UؠPBk, IDATt3XE( v{Q,n|pl>w6Cx [jbE0m6\v:w:ө z}a$ľz[Rζ u'@WnޜKF 7\=0U+xÄpz^X#U$N'Pq}lncLI&fsoЈKx<S"uN9[sÐ4!2ȍMΪ5b@Uewn ¢eѝN:$sBU12&jxj(*mSE<%/jtIm+ pRn_B^~qbchJtEzm>8–ɘdgϞ;;yͽ[Pk׸c=?Qx nghp}1 lƫ_j8駟f^$-֕Q1:_z!/"f1u(gd$ c&RWп,ɋp{d.5TDZcV^Âc1K8kK畝I&1f )vm3R3>)`ih] kr`L ,6,EǼB1P\_Z9>ua(ogE2ku 5ǟPfVBRKE!BH@6 ]fZc2"AW2;8<: Vũg0ݳ43Mi ._#fLDfYYRn3:|YLͦFfEs9\ ղ֖8-+Wli!/3ʴ$Me';Ww;!reIĉ9sVILIG!v\^NyJj>GC'h4vzZ- 9WWH)ݏ]E7})xCy ]8jMp>g ?l&kȍ|鹰ߢtjA!3)[-$Qv?Dɓ' m|>cb=_[q@vK|A&/nݪ +FXJVkm\de4ce%",*\xZ$cK1႐dq," kWuR|*2YYp&3^*x Vfa{u([Eش,?f=;pj X!Aey]@=D^Y>ezضboՌ . K:YXWk aD a&̔& +W ]J+0GUgSi)Ҕd6A(bǂxat`;νfɄȉ6Q$Mnp`kvϪ1O1^s+ku|OmR:8.;q賹JipŌV='!qt0Og:<kO1zEGA.~i,aDaE {41ŋyIf)J/pE"[yN)%'ˠ+%^HhV%If_˳<'/Jb,fB/^԰ygvlg:.j,3j+ tʀp!F;`UW1R3GXZ<+9Jy䪉64c^};7LGHp) ][4I@#0i- c|/DD " 43avw[ Ƌ ܺ~&4#jwhf)nӧ7|C1FW4=g.JSγW P/kO,[Zh}%{Ϟ/.=?ط?FK鬀0NW;G0_7{6&64[zġ߽fg\돟6!Jvd3~~qI{qm"8x| »%xV¿{~薓-^U&L/o[*ɨMl,2.ƿC_o|_~ FO}I[9}C:$X=~mv?}+&|KjK-><??Z >}AK}q[˻~|q¿'o%?6Q  |IġOTm  A<ܱ:+=|%ۛB3;ҥ+vk҂vVkR |<}>V~2ҚҀ_aP73iDwo6U@S_U9;,Z6ʊZnjiӄy^PhDk'vXcy{8ًcFW.]FGGGloof{N|<<1W?O~^8?5ma {x*/]A:av}<ϸ t< /^GEsjt],L&4-679u ш .8&䎽@u_i!"`jE̾sAܚ ]51-cFZ?SQe9nWۈ0q k,+g\^l]>ޚhTuvbx;mi?ǖ$^~ҁBRa'@.B\{uZe ^~N/l <QA|+{!iT{!=")Je.r~uuc4!yYR7h6 &tyGV]JFfX7N>pF`/h>̲R_][.l$f~OOn. B,s 7wd6G+9= 8qfeme0ֺk9e.EI9,ed`0@JaX9<"s\Tr˪8(Jfp8dmmVmC&a5U!'w~@FA C^FC7EhS#|Jʚ6 Ig4:]օ*CdPj ^v‹ms l#v}gyvOY8q]U.^!n}??ï=|੏b *aA&ۢØJQ5~hvW~ՍM*e@f%hg"ڽP&S% %! 9}^ʹ{L&3vljV"\ #A*ڝHoaձu]ٌy:c=awh}t:mn:g_jnN6Ƹ"K^8%@]|(+vs@ۤ ;9gVyLS2~R8 = pǗ%`2`* [-*IYg[|FGE` b^/BjܚrQJQ%ƚMإEWJ*,0%:/Q:f ?;yFӧӊ>>1Y2㑇G[HFF!}li6 F ϙ>qӥZřCHE2Ogl*^fIY(%[j{ M oQ)7{ *[XUcϷzQoXp[PiK3xY~~_qiBb%-a_|7J I[GW?"_}3?|iS>Ç>2Gin|ׯ>Ώ[Vw_]?[|ɏ(t^Z_Ml}?x;O*,n|rFoOoq@?"w=<n~9x _E;`Y[8Hok[Sx;~iJcʠw|7_%Ow?=woYފ?0.1li{1 :?>ǿjd<|ׄG?InIg_דxJ IDAT{W'06ցB S D)|#'G`||E *X,+H͜d2MG$ɜ,W(nf(JK9ʥ]#EQM n#4L %td+d!(,P޽@sc No5k{ h! GJKZV$]aepc'?/ ([BzQ)ZxRNQd%Irq+[[[hힹ5h1dZx=:>^F^8w >{ExRR)(rPYfZ:ubay|BkXzօb,kٌf* z* |_amCm`' y0z6/^Brp7)CQX& y^.RJZZ[|_Q9EW +]6,x\w*'midPdZ;G#ަwՕ) .)KrBC )R>R`68{@*IzsM:5zsۗ F/tH)VH6ˡzpk||ET?)MK B7)gMQdbJѨ b|*i'Vw;\=(A[t㐵lnXSȈ( 4 0$ #|d* h#\7HӔk)BHNqbsFKW1bVh(D[CQ*h€[6Y,{w/yW6ŧ^=9ϴ^[3-ww|@=K*&o\eS{,=Wuo</׊_B'y%<.X~?~y?[;y_ϳ>ků7,6=de`p@f;}I9{Ц{~J=><zzX\s^Y`Zs6v7͞3m14y+osm3>~3oKկ}gop?⾸>%?;)w;??[\2?˂ۼO6+|sz]h>V^?y[s}fL`(‚Յ+z ܆1NnMlnn"GmV#=cF3E4q^Jx n ZrNƄuPunxdEQ&3^HwijVR)绵k% F֠),#Z[ VMQ=cJ0OҨm8=?PeLZoJ.:^^ÁcΫ(عz|.L) <ß713(f~;^!~]O9w)dB9TW*֖ ueZL{ʧyX59壱Q@£tmc[BPnRE8[0tc|xJѴ |%ٝy@$e:MlnhF#ݼt6g8%ݕ[q.5cIGHk\&S)^UNsdBgaƪY^rz p8ҥK4y!Z-FgVV7$bٌ^d 䙳u!l[.-x,kAl'+j qRa|;rpJ,w-'OVW:ܼ~N1(? ttcYQB lZo7[.T^y7?nq+O-g?h3=o> oO"7x8k h萣IFt^.t$dퟛ &k` PfեPk|{k+!PQ"E`Ѻ,sg @dyBn98 O$ix?j1фe6,c)#uV fZ$iJ4„gS2'h"+F%z1b(%;!,.|WQڝ:neVYXo`X:%/g82K31R \ (bl<0 ?;ZOshQEŲe05/^!$Kݸ!`0! 6ݖmٖ-ilf޹yIZF}ˀr^XWH], YMe> T ,E!ye9|h(#S= (кF)G(8 c8R8^{78'}vaAK VBM9G닟;`7MA]yt>,N% ̀GkR{G){gNʒp61rpGJ/m첣ydY­>}O$B_TTu`|??6'>{Ke1":iOqa0b<+pʂ>EQ[nb0aksǯa20_Z9p n}'Il-X +!aרjƸ/:g;N/Z;ckە +K cNk .9|'O#ܨcTmp{gL% K t8.C4"v!ƹ4*|q,@Jyt%y4 ˂E`#auAUNuTzV d Kˇ?Q7tŅ~);a2nkЛf={Sgbj{{+*X qT$dn@lՂ,T fkkL)U]a!!RV9RX qcL++l0,̥1G Tsdcp z=zK iHaeuJdI8:.W^q %d2W#xۡyf^^F$ýjB׷b6QA IWZȽfkGTZtҔq^`o2!bf%|#= ܇2č2j|ʦhg1R5}9D!ȳD'Cg, F69Ua[2u,-_:̴eh8d3D8_fK(%> {|¼YT!Rk"%US!$樇8t`믹n'(]hsYZkWa$rOyڍ%\A}{­X1>1ED\rtmr/y#7_ Vf|cA/\J-8xW߿GW>:sW5|͖c`>\o~|p;|wG_!^㷱$$>^lWr3#:tln敯{cw|%ljsc4_𛂌yN":" /qkx3A`p[O{#|_y뿑Mg]䑪fGx;?KzCl*^]֧/ؕp_|;׽ gZ%NkYOfʕ\ ,Ũ"*~¶)Co{^]oɎv{{ڛӟ"jSnRJT1Ə4>iZϞ* 08 7vqR`ed;lnM+dOeybl#2b6iReay]eǎ27#%a@D E#xLn3IӔ$ R$T 1I9z2$:DY'#B`g|: u)2MZ!єŃIF(.9tԙL<0E}9z.x+"GjBdQb.Y^,ǯb:芪STL(ᅭ0hoOH#Xva$D#5cZPǂē2AZԉևoYq@^ʍknjcVJ27`d`S4ogkcSNF7밼6p0 X;2g ԃ><Q䙐֍9%=run뮻{=ٳgg˂gLOٳt:8&$\wqZ >s']w~F3 X+o7f*3pfնD6 >X(_,QԞu-cvo<[&`' })swc:SU)P `L뺾dsg IDAT{"0IzG]rEsj~4(mm}[R{oB Wq(-Nxd (=pو ͽ?jk]Pc/Yǵ3_hhmR彄gL}-:$ 'Cz'f#z`y@ Q, _U`0`{l}ąBfV< T%QL糢k1~MDʈ͍ì="I0 fAPŌ忿'D!a %,KVW2/.1 X[[ܹs Ni~-d%q\)(fG-C),-?DUk毩Pe>K?~89q+8@8 f,ͳ4,Bx+ړ.] 4mTeY=9GDiJ N8ɴːMфqhZ`QUV>uW" řLҕԥV/f% ab^4"7Pđ0ƚk={49a!F*koRd$rm(fC։,.aennn'jj,ٜˌfOf8tx8c) s,/-vzYRz')%'ODCX;I|2++PB"jTVA@GIJuHcEQL~X=xBQQ|W6 7ˉ)r~BxߛmҳR ܞĢ]p]m[hk  o$z& F:G8]cĩe3I'/ QI:,FO`21ݦ]Ft%CJf/FZC7X2 C/[dek$&mϑv9T*)*@+Thnm `gX?=V?dMONv /ɯ/џ֛yOKc/[P|_''V ^9{1x !Ę?+x3=7s_W^cO?uٓe|N?Q8=.z]n;x֫pF*qzQN6;R?3=7!<9w7 })_I֜˟uo$2=p} .%G)O7w|߳{ Ќ=pa> p089^ ѿoqO4yǟC_>8'Λ u_7K|;_{< R[y_oكT}قcD J\Ϳoa/Mf <^+y_voq? eRzEo?]8<# UH!gN1m=ð보iz2 FcƓ~>Jqa8FҮg0RZ>m{r=W\BS$BȊ@=D(bzŝTSΟ[㑇O1ݥ\ #=V8ei0ց ]FytH5tB"bRԕ&;t!ۄIpZbEQADUi}(i2٦*;2rѕT$2 $EfaqGeuu]UlnlPOh8aP|:a:P9:~S@^HˌaĭemYOb) TlG;~n^یP3 ׄu>*O|A̙:B(>s'g .BvL i$D "yIJ_ۣ]=b$܏G/=LcGbKe)E!X,z}lϾW meD3NCFF#68Ӱr;«`h(ڳ@ 뵮}xy\#[PI=-DDO >Wbvoձzp@3FvV0Ơ"l21?س_6Y`ȕ{ 30=gX!v{Q]8cg]k$tP*&06-l%Rx4Q׆6hU!1߹k$h)1QF0bE'׀װ.=H60v/.A@}`0 I#,,hj1u5w ʈ BEyV ʇѰi0#CQjЮ2L5.]q a΢M3d0u = 2<V^sKc%:&IJU9D V6[{A~?H=D"`)v$IHӔi^RIܥ6kـ̠ {<)-*NVA(Цn@hGU@kVy0Ei@6`8)N"E`5yY^d;M`.v:a:2[U67רtveavEEG?{j:+\~)4e4 $Y&JXN!(+ 9 )|u͸`4 u\߶=ڷXmLd<뾋_AQso|?zU^RkDgJ^3ny_uhJ7w|Oӽл BaSƏPPQԞr֢ɸ o:OK8={I&a}>|E3;GcsZGm=o}'ۏy|\t/D3g)'8ʨ䁷6w_+^z)\hP0/u5gm ~p[k,ӵிm>/i{G G9q GTI[xx ;8%/[Ĝ}`|pOP:~?Ə}3Xx37s_k:/F'C>o/Hy)=`uuI.;z9yI^UL7jM SYEA>W^x8ܹSlmmquO{gϞcc<nίuٳk0(.VWgr}3Rџcw0d:-HZ׮l7@4M*h4Ac=nbhK,5 eY50Aƀ +Қ1'0sR]Ս'x"aʂp1Iq@UT$!Pabho{~7;M:͸pK{|Fri h{~h|VM~l2׎Y@ZZ|1>Zo`l=x`ku]7$&JY }6tn߷WPOi7xtZ`Y[c(Pt:>@u1MQ\GI9뚳^Y^35ְs@2 BPkM>-)z]T27 vC$СUVVV|^AضF܃mi«NJ]fYӬW]Ǯ ICƓ]6c2ynnosS( c_`&'Ŕ$ )zM`o[n s|`0`4˲G)%E1%˺^6EGgO(6668txkƳ5d:J$Ex@,|A t vv/9~ÇV<`H v~!Ec Ȁ(J5JI1/B|Xs ]K]Uݬya l{%,71 {RNFC?A PB[CY䓜`Z_#$ \{*V;⮻#M$q6G!y|%:>e F!ZF4fyq@/(/TSO|α@P!ESO ɘ^U!0hC97BK3t2"(@`>FA;M5JxEB뺱XJS¾!bISkIK#s8x8 EllO4!J:x X?wCCg_{vFhdc[NQgU 8U,KX]^ /*k!3`5R("˧ Fwp&Ek㘍 >2kYBI]*Jc#I1c:FUXs}F݆?HA5X_u(Eju2wň]S1PlJM|3Q gL+K7j<OqŔHטɀN~y]678?ي,4X]I&&1*!L:L+Kx=IfL0=Z~jIY{ŶǓ ufDt`6߀*0 7{/Mץ#>{}/a9CK%Dq^^Gǭ]] ۱cǸ>UW]E >bk'l9om'ZOw]qV 90yM/b堖/!k7/)ԇo?T y&ټ18 {{皟Mrǹi-w+:7%;٭O{|ۮ^دwo?Z~8?һ;x-Q 4|!}֋e`])!˾۸_e_Jt}u܇x*?7,v;<_xcyisSmi@k]9!5H q¢?8U0%BN#b/K& 3,40N20̋=GmUiϸvH:TUc.劫.',t{rYiaJWLw'8ma{{(Ht2⑇Oj@P5:b;Kz=q 3LK$i=&h8M" CO&e.[7KK.iIW^ɴXA$)E>&ut9K5E !,(ɭ! `0qQҳfO>MS>,- Q5[{BTDznX]ls6`tn,`Fu΃~=@(/-By 6g=s 4/{yիW_餠pOe0 'Ocq0;cјYxw6ln$qNf Iu#4 (c@\cs>Rq᣼r-*:ulm6(`0&ˤ 9@ѦV*_m+tk[a0ȓI D x{0 g0[}e뭉0$i0NPU%e1ֳk]%)ք310ݟqC*/ɞ9Uq^ɘh$$/h wBF!kWktЃujVT՞-Ǟ(>H@v,tr')lZ i9tA|%q[D9! C/nYIU1|Mö`ƘR2O7g(9uYeRҿքRy^pL(D@@~7THʢ&FR]S%. "h2%I<[%:y bLu+^mSꊺ|֌t1 ֹҐ|LguYsR ̴h@z}U) cDž} 2vvvY^';sNFH=|~& +sDԨ4׷(򚺂CbzsȠaox/ز ?T$"`#y.u8vQjSP1;;;lmmx8F jn%aatmr,28TX]QTEDNLmgMp:`neǯpǝe}k vwT&v}VWPAN@^DqLQ{lll\tqxVR8MB ڇ(-̂áϸmV(kC3-j Z[jklo k z6~8,(R!j5n/c2Z4ELc[hmnв IDATRh )6XY^呇5%Hř3YW[RDhcFUmQVR?NO10reQ5Q#e}Ɠ!-N;CC>l ۋmq'dD+#*wDH5DZ(Gܧ('.N&$I-%oOUU,,, {NR9AKl PJi\gO>LUUp@/FSo`Qar3ɏ|Dvٝh+=/Z%/& :ivևP !E \>A(|0gQ9`2aiX ;,ڐ4(ScFSTCH")6YHAcu9#э20 $?Qv{'+@Qך!:gӤQfPfcȠ 0N2 TcB6(8Um a=x3޼"(/X8@A?7+c&Q"D,u5Qbܟl]MLuY]Edx!)SjHnY`:@|7J G}( uQ`? S"iM(EYQ; (e-/C>O>~lx_ޫX#|y8Y"v/D0h I.ܜ46`\%5.<Ԍ ;y/[~X_Wl}8o&){gtǭ>s;O>=1]oA }[#v ePmcv2W!, tQ1y:~ #iIm-HIj% G(+/y_[YYYT#Aimvtd.3BQN looD1iAdUTIܥ"/D Ӽd{8aOIO#T:Lc=`=탸.iGᄭ Nג8NӟQ bQP`';LΒtb2% @d>s ,8BQL]PC\"U 9[OVd]vwu TQ%քFlëSpg2m?btR&.րuÜ4vZx&sq>"KRT>(= +t:Iz\}sӌFYk,˚z: 5! )thMG$H_bB#gzڹMlnnSNg+n:. ʢbksg<'xeEU,#z,ϱecs]6Q?l=VCznA+}~RD'*B|pXS h%{ag_LkmtBFԍf0 AI` tmtX]g=XP9ƉCᤗ+͘iùZs}Zk8 H^V_8q,w/f1%[6v˄Zg?;ٽ77Y BuY[/xpELŬ9?ZC_+zJmj~*poYeY{ִj$֮CD1KKP\tE1 h-Q|pn'im5ݴ4i$}<MEر4F 53^/)% vvv8NY^^}-X;wѐ$I1׃ "Γu &w{H!y*BP9&a:1y:&BMѡK^5͘(NM֘51t^ ?ω}㷵BݳjtkA5-]k1gI'|e\{x1[xvӞ-J5 3[l j8|?#G*|F ӵWXFDm YxKY48 BS8a]pRA?m VSnP 769rhhCfY2y !ȫxLQQ\~Ubq n8#|}1<çϐfsh[sYDW%#Fɸ֨8gY4\ Aj*m*$|:eán^DFiD?JX3@LF7ES8F0lVfF jW^׬5g87plGFH)1 {¸Y}':gwg@lll%uQ`~HG4y%~|+s"q kjL=jP=](mRŊ\@Y/Gh$Jz~A%hJJDEf}rd!eSÀ8K^M~ffGkJ {]N ,̧3RYHeIbJ|EB% "*3@5j!}ezCM_TW˻o{[w>77C7#(\Gx/%7~ywp9{=8fu)1i|m?eԿ+.?-կ~zb)4! ~ -?grɧ?~}&&9P W 5nC>X^< R 5_wn]yʮo;s0A`J`,R(ND1 62L\0!HIh( 4Nwn9m=%<{󞫑4w$gny~-nh i&spwwȃ z5{_=|&__C?ci'`au1x?9=G3#g} 4:USRWچnHp6Z-hl)gyMfy%BX@@zȇ0>O~7p@cB"$1Ȗg20w ᢲGLd>ԍ1w޳=Kڠ,&AYiOcAD x[ Q7),:Jб@jNf8'ѱ LaPZ5FLvA'ny1avR-(,Q% $Z]5bmcuŜÃD.VZOc %l2o'#"p%Jts^Jn.4&nD`  y` !n;|H&\,Xe<}G*td(x޳6ƚc 4Q+ Xi Ds<198:&l G9{ɫ^*T$S˂-n(UދeQ3[v)<{$kIԏp\)@ ] y1S!D>fRnc0-IYDJ k'$ԁ,ӔY6~886MI28"P#QRyx+,u)w}PVm6>=WU:^{Nw^{#e1b3<ßO89>ŋlooy 6 _BLm8:::YK8gU8bG&"bM TMli@4,)<~JXĚG{ywJAey8{ l˽>ci(PpK3л>!|I%(Il3PRPׁm)`mm `>ʈ, p9lJ], ^!0`quR@XX+Co}mfY\ΙS,w}:x HҴUy(HjӰH] < F4e2pt<7Br\舃Mʦ.jJT>.^<bm2M D)gNfvѭ@JM6˝e<,§n$2I0cH)|"(uRrOl1z3 ?&$ՙqmBlU](c y{fcXO'jeA'2BtST5q~N"Dxxp|XaoxFwr"MJƘ^yu~A'''cև1PRiHAV*ѥRKlm{w=eYSc THL]sh)OV#_ua<ʢњ'DiF> mU6pE!$UUb]yb6gz:6DR$Itk)Y>(C4a]ڬwl!>MMX ̅({Re_D'_+yP;w=1[Ks1 ~swȏ1o~?/ahWD`o⟼{/+K[ \|ek__+WWuꅶ_?Ǥ66 //{xmTɭ?Y)4q"yԘx/G.=KL}A[mA2O0<;? ?ɵJY̤<?]яI5JK|ƗލP][yw_Ҟ[?tz߈.dwfm{s;NrZ֭|ÿo ?'lƿz==孿._'@{\/hC* fpG=􅏁÷3Wn}GッWpA=/Uֵq<(%kֲ,I ,tE]\.9:9ڍ\z,^i=.JCPSAsᵯ}-/}KCȚ(Rh!72($YN()O>sshD"Mw.ဣ)F' zfG3@oXcBE$F#!lhͦs 9I9yQ*L6ݣ gXHbGϒR%[ۛ$A#dfG",#˲p* w$qJD_ ~ >^|t`X "EhҚ!L$L @z N9gta߅CػplFXnܸy{1Xt7͗YBe&i_s4x :Ϣ,B(y1$* ]T{݌ N&`=7 锓E_wlg<@W^#3.*FKE$aZpk(zX"%JCbU`OrZJ+)'MwΪ΀ĵSރ(!jNݦ#su~] %u`'wq8f:> kEZ+AUO3t{[~ 8wʈ^\ <ݻt?ߝUDZ"oxwߺ3!Ї"2;cTp<W;3$Mw7n2MiLEQ-Zqq&kPrXR 38Ps}ϙOtlo\DQ¥;`pA3mϲ Uviw=5 3m.#Msi;;k=U]f9ɄxLi4 ~2ۦipP59!Jt@&l0XM6pΝc8jqV6s1!%?8kpNIJ:HC5g> F}k uw8NIfnx4EwN8J0'K>RkOjT {cNgDbTmçiN@^,(p*f@ߎ%1lH$x'Dst8Wz!DӽξKŒ W{fp[ QFIϼY0Qjvq(0FT tZ}enJb OEԕaw\(x:uZ^鋃G{Q+94R")pxa@BxSSp̪9gٿy$VhNq.\,zKEf`c?8 1NUU'lh@2a^r/i{q\ɂËUH!|S;>w. (K #H<$p{HwvM٣sNjf4 ;-`,[63! *b>dx!JnBgڄ=T!c 6$I vYzwcg8::b5O )5kkkT }?%]2kt ^4ǟ8ƙ %"e/""fGbv8^-T_!¿6l2i -BQ" %ZHHG)eSc1Ɔ %$2JBR48Қa! %t*aq$Jra8<մ,/=ʃkl̲hd8 5,& |Cݔ`v( IDAT׹6Ci"H9VxH)xƵw8L"meU;0_S=?%~_ S^3 Wy{ ;os?\:~d"_=?W'υ[k8*Gx#^7?^,> ߼vh7Ac> jzhI!Ʀo>nafs^H}>< ~g~? 74?68*1UQsg{/wO9|駿|&_Ι=g[刏۾:w})ozP Z?}nĹz@{~'߽)&oF?qoKL-lqk;ww'Ǜ?>dK*y?fT?z=1MϧaW9+tBq[GY?piqcAR6*-"'ڏF6KlnDٲi~*>$>~'sRx*#X8牤 s&s/^!ɥKw, n2[aߢs58KBB EeB؍2Flm@VfxX騼1 Q)ZzG i42yx< 3\yq({e~F$yp2fm~)iM L㰶@IMed)CrQ0-ϗh@%I@z ld!D*×A6ۊyR0>-$2j^c$);?B)u]PD~9Ų'!(Sb~WXriZ&qw"*!^Jtm]߯|`NNҹ^Q p/d5Z߆/غa,Z^wH뒃7|2!lS:c\[8:#t95|kU6R -cFAG^6$0ڰ%/eݔs۬mp˗A&ulP0EQQ |x}n1Btmsܹd逪E>ż`Y,P4[,Qw`ơTvo}x;m<l:˔eI5@U/a6-%MӞMAQ;]0j$iҺ  FC<eYN'$Yփ tu9üdL g{ln[ ; L۰(X0i *iZ:=5ILѻdc* y UmKZb"!$8P(R0c:rxp4qZRAjS ǘF-eI?,Mx[V{BԆ$9,Ø_ʪƴףJLmB8g\TU u˲ Mhv^VXkCgw!LP+9R>U^Ekyo-Y@҃N\wMQטΧԦBXq}bUϾ.k <Ӝ=|t_Tm(e?WӲ[[W޷Jk@z(yPQ q[/H#D3=ؚ, }}=llnb!Dz0蘽= P -ꘇ8³b苣{.R;%:j!#|irN!+rm5- 6_tx'yQ"{lldOOdN:цno F@#}V*:S `t$\)3c|}={m-g;*w(ᶛ'T\/_,KְF0-(8r8kZkX E+HE^ \VR)nJ$$qù-Gˏ-r-?*`t'8*d01V!(3c U *xx$Bj(8hBe3I%&,CGߴ8<۴Xk\S BTA ZG)q:XU4MIHIH#EimESS6(A wv"V4V( iB\ :7Bpq00xn !M.QTwrR-;m+[o|!D8ʗl?^qle~-?| D+f>ov"Ueѷ> _ Rwm'ǿ|?~җu?p77 q_S߮{wI,z?? -\g$*ٟgӿ_po_篪dw;_|׷}K>(K&zk#?I_wYuV Ѳ;ζP@ B,c^Uo'W6I_~Ϗu+HoN_C6oowZ9uODCv_ͼ{?btnI8yqW>ꅅz ttHc[`9@84`Y i`tL>3d F$ TU8DزĶ,|=\p}7{{{y]lmm1Ilm*!TDjBiqKiF, [jn8wEƱl.u􀞔ƆC'(d64X#*+  Y> /Qbl4b0\c0d<&IŲY0dng$!h%O3sȽo}-֞z^5*dYOi"ޠC8AX,$(Z#+|;U[7y%Ha#D"#BFHKqa[,*YP( W^MNfS$x;ɵW v-MåKݻ.{/}yvT%i::34-k.0иƚVYu$iobQ0MlArr<^yf#\kDz(J .< ׭S%I[*Hwi3<{gEJȞ!wk[Uump'0?$hl%֭aX,8::iCIDhzl.0O C<';moϲg &wlZtj!VVyάw^ӱ؍U=XlLeXxꌧt=%sj]П$ s0bY,5gLp|ͲI1_pqhqOf<گOJVV,P8Uuceݳ;:x|1eYj RhP C26 #77H(UGq Sߪ\ OUMi-ol3 w4i2xHBr@H:X8 F8JTkuVm٩Ɔ5/`6_2_\\#Sʪ X,,3)X,&"E um;G]Wj۳JYUުX~;"clMłɌ/&~9:ާ 68۾pm k%$իW984 'P|i3@Eȥ譙Rؾ]-8,H  #-I'NSӠ"``3Qx 89H)~:Q:ٌx|xAX;wpk,Kr:6?q %#6CI BIij@;yYH$XkDzIü'MR4ƃM6vҗrzG]Vm*"ؚTUuhUDJ*dWݺ Aaq~NhkBzW|'888@)ō73[I<(܍31!-FPj8&Y OBش$iE(Ъ\'M~ws矿EG Fm]gW }raws.mpR"ƴ yHjm(ωtBB1Qb$@$K(NP`%qNRTUC:Qum0B:K(o Aiemm ߔxB8Km#mk)%Rwh, P'/N}2;fI0iH oĵ7hɄmJǘ# D+*.lc'[D10Z?G:^x9d(+,GDg}}k" '  i3_`{ 5D Z Xxk*cij/O3],9wŜw]svϗ_A1MXz5~>Zk677ecc4NXP`}C,gld U hf}>֚999a8 ^>X>~rX'''Ub6=2U8͙- p֚OqELmW|d}AO:٪ghǒ Vl)8ʿ{V!D`y'D{8 /|*Nb[H&UЭ4a(yUEʔ ʲd6 9H)+k|yՊ.g{2u7=z;{Э%ѫuwX}U}Q i')4(: t)eoA%Is H<2_xqLgid؂ Kkbam<5eؒ>;_DQx'?7 #x\ly]4)%,[`)McY,Bh[YVX۰Ic$YF&%ɄdB60>KS[wvĭԻ2 uQD(r)8e9B-K`,2uEQ4,CE 1E)ISVKU->wzkC I!Xsrrl> yEzt iI!o=oNpF0 9YBjm\ oۑ",c!;3ciiP8q>)%WqS$դii<5$śӣ+O{ǿ>Ο?>b0ҥKn+˒d6Ux<ӯ,#M" %TXS/Κ4R:uc) qj6iJR77J$iXT ^w@S̩k&.l__JNuB5蒡WmX@Q2DJVW8$ F4 E5j+ijWVr)K&,By"n-id lIΤw$I>"0;er`UfUn`R i-)H>o&2)@1m>W^|#̨Hu-yJ0b_99>0(^hz TއItzA[,ju|(B!)r9y;]wxmq" -#Hq6B X iSDٜɈ7Ҙ,0&qp?y7{3=9f:=$Rc,7h'I$bz|BQˊi4x $RD˒!fΩEJ ɆMFTsNNNώ#"q.Znj7wٟWDNqŻ׷)^㐔nJŜڈzBy666XH_d`톃Dxv"pv/`^0s}+'>mm>lm x2;jYx$ kkklooJn>ׯ_ҥKhyCr@ǝQQT GocI3XWGsL89/Xf8vB!XN<07 ulVj=N]$I(˒|ޓfk+ˊݭ> mY[llXTDqt?AND"L]I{+s4 'hPLlv%FBi- Bi`0,pDOYm/ k|[Rք> d|>gN& UB, s QA0'J IDAT+b$⮻b4Y. 9-d K rHH%)Iv!E`4 $.أѫU]S%EYK& EyHXb%hPs=FC۞݂u%9}tՙ 8w\֍|P5;w=$Ν穧bv|2~Bٜ54Mj+Z]>oP ۞caqY"lQUaԘJ' ֔rw vɀQA6(˲߳prr:ZkiX;/uNyTR'looizG/?.{\z32]IjU@6$ Έ"3&ZPq`ae ziL`OY,]{{唨jXW ;/R{|Y2nVUU<-ֆ܀P6z)XV}ᾳDvMcퟵBUբpFk'a_݆&ԦiQ<(k <3J@ -5NxL&TƔ-ȵe7K")Y>w_U!x\RJvbANHBlIݔ눢cmHo'1J6[*BHI%V(^cC8ND2T%1`,^;): ڴ>g(dR99l?DX8Od1 B;XaHC'ZKax$CS{(EQe< 2[c(,m&[:pOݡvŽ1cccccl0R5]G!а4 mmCLU5hXlm,jOm *Sa `'BpUNFlm6V^ !h '| jSF),gDf80Mc*YX^2\vXz*)xMLcg~u|Ebe2X,:f0L>[m*5]Q-*B*K,)e) f9Ӡu}l~d0JX8,1>g[vmU8Y.>tk:`cO~x7MC,"<_9eY LュדO?b/llro$MX__15Y6`}=Fi̢lHag3x4 ?g6 {$Fc+5ku$Ӻ:c [[qL7AoBZG !Y\.C2׏ŮI60GJ)BSsHHgS!u,jNSbccC`0k6J vv3fGhLZ4MT->de͛7|2AN"gw@pP ,ˢhz [W87k$9_b=kUM$L g86+Lw21ڬ!9$ 4U]{n'/ȓ.Uk˪Bs"<"$ zG[m5S0%e9I\rsRsvvx:'EA0 <^4X%[?1I(,Wl6\2?:DkESoTQv>e4k9 苗~kJt]N*EM-kD1-IbqM%2ͩcY€E4 ia6/v`xܱ{)wK;ABZkH+jc9??򊓓3?zB|@Yu!?%)ß"BBz0VJs~~xcA YxO&#r ʡfyA-1$s7n@ _dyDD6}HD6Ja#dhibT`9 Ƣ#M'!,(IqRnT݆Vŷ )a\xM)%Q1qFw[5mCZ_禭)-+4up5M(jzWEzw$S^|ɓ/2g(i=K.<{1 ţG98:({p~qEQdYt:gܱnhT UU1Ayo Vq5M&q=/N mR5 1&t/_C͒d\c$X׼~!nЃU%\G>ֈcXtA * ®p!Z;D7nǁ'"N}~geFg?$7D]pֶP5jO|EW^q}}ͽ{trZD쾸һ֘ bsH0βlq& U]v͆ nxcQY]^6d 0g4zͦR R%>wi1шh4uMt@qКZwMs>(J4MuJ1턓"Q{<ֹsQ}t1 qvquuE+8A ^_3 |YknB'TUxRE8.43/_GѽAp5iqu{1P5 UU#cfCc(}5O>,HD'w! E鹹sF)Ň~t:e<Se]@N.y$IȲiadJʺFYzj>a440キ-ZWՇٔi$4MfA]m&M?1Պ.Nǜ8<8%&օn)-eztpLkjCNOիE0Yxy7uh;!8K3T|pJPxJJU܉!-uK芊 [C Ix$w |T&dUˢ( *??!Rk#~w=lޤD*Fkc"C7 ΅MiX")İN[#K3ځYm6D"pzTM`j}!Fr\߻پZt:e:ݰX@H$KնDm-t_b!O\n"tx@:Ϲb+^bHUe"BkD.45%5>5QQjio,Cn>VZ)`\s.{IQ5]VGYw~\`=G{^1\E~q?Vt߳޺Wn< !3}~|$$|ge狢v5Q$C!z(vш;Z Wo9[>k |IiHSMSbkVU7q)%EհUddbySÐCB) 4Y3)gog]O?00D:\MS!dYP0(Xk :7(²l(Fa={S I 1: kZ "fǡfrxÇI㌟'x g'W,8be0u Dg@*tP-ݒ,98:n=Ozu]srr\-s<{GGANd<5m֯jp%ue?yEP/;zy8ٕŝyYuJEEvݶDJES5шѲ,g!lp;+9겠mþ( aL&Yƒ.1ZuA~Ł[(5;.7pm[3W{E$, }7h%o} y^b˚ ƛEM$CGv1p=yunDIٱKW4Ֆik&d㘪2J8[ұZm|Ō;Y/tt@x t<Ez~n;z_10'#D2;95[_W k-uk(l.9_}s6;k DyZS+Ny;Yn8N0M+4[yrw}%/_駟VZO@-c ϙf!wHtjyP2ġtLUHCɀjƅ8R_{΄XX4>?c1u>]PUU4e7N)8998f@quܔNs;}-At_+8{Űmܓr=͢d@E{4!bI 펿⻿cCOIf_ P ؾ0BJŰy#>p{Pv6qR q!x눤x,rpA΂8ѷIQsغj5{TG?n-,{z<Ǜ!pۅҏa$n'A\\]rxxwO7\g-yx7G!hˆ /#j@D81=fdL>+nk5 h 4tT9B%N&rIbTo 1 4l+=[SW4uE],lϾqBYDvecyŧ|ͫW?S6뗤IXĎ0=#E갡j3:/1;pj]1 !Ȏ!y|̻sbEuU&)R!B"!~;8?%UۀL.|Gq|zGt?358MY/QQ ߸mv_G3O`'R`h%8I7X7^E9ٌwy2eϏ~NOOǁ :e|g懴m}>g?.CQT\o:F#:QMXʛƈ]n~&st߅׋pop#;,;H /m,31Ʊ^t<)ZE$i-m76|.pkcpBH ;c}j8J4%[A g>o1\WWc̝0۶x1~ڡg ]F^OxЖ}𞵖a)B`ڃnuK+Q2#RbGS>RH!5jɓ4"q6\/Y\^!<#CSRm-mkǝTi-,D(Wb ξ[tAO XM]Z5S:$֚ fwj-QB\j-$҃p4Cqn kPQ1%%gǴd2AJ9ojJcֳJcq1N(fE8*D1QvgϞrJqzzJeDq28iPQjpYbX{L$` ?g,WpgQNI4B(lLUĤIBwWrW֠%T"|5gH;KZb\2qϟs !D8"65'v7p5ŗ7}"v֓f;m(?NY7,O26xsuyrqc4|V/PUaC(b50쬠BQ@)vss>k ZJS,ܭ(5EaEZ"%Jquϟ/&ce\3Lt>cǏd4qztfa<^/ZPgR7 B)<|Oh:?i5BxQ`E]]Km.hq:/0Clj/tws0<{ &M)C7/1><"Œ(dYpg#rQmꂲPXC]g_P/qbopwa_bs~i X].K5.//yш]rTuN-$~eB:ݻ(U.Xv˟-_ybt_ _N{p iP;G$%Yۦj "Ul\UKk,8۠gy*ж[iqΠT5NQP:*&he<$b)+6[$iw79#xʦhTƣa`uyQ0>8vLkG4ڲ@NXE6{QѷExpDE1psssvovhc=.dKbt6G9Uxh*z|sR{ Eg1C"uG""X#U51N kT`|UW5VHt i@M`@'DQOۀNnjeLzXKxp1Un:f8IBq dI, ֋KV7W\, "dTgu-rLJsfłzrUppNF9?ɿjd<*ŷ?&W(a4ȳDGZdh@=Pqw Ba 2HnBd@.ĝ댢h42$%VBqW19s+ڶ'Bn N{=ayyMmZl[ 8±|MhfGl[#?Vٔb}?|{_ bA@`:P*l,[6-ƃ0pqb1i7?>Gz s|;%Ib >ÇQmp,Vןqn["uBfmAZ֒Q\a n8`s׋M_wwk~N_u$g]dوhBQ\\\G O&U~jN,q#ԡhex1 o:{q;H:h+z@sw>GMlۆnޭ[AwWGQԱ)þc^SEJX,Pꢤ,^#<qzrMM|yM4Ι1?:4oB$O2pCO*6jI*ґ1q'OHeͮ| sVP2 ,# B mwFbC/a~YkΝ^S鄶5mԪ$5hPeߝ` #=#:[XQEsv椱DnîBqY7$qF;\k:<뺡W^ӟ'_|Ǐc Y`֧Yޗ1M#B)֒zkDQLiUSfatzIg%ZǤijj ۉE[\Y+B<.=݆j ғd,kBJ ]Cg< A@ӠȍGu_>iݳO{VP[ǧl6y&b:Ҕ{@,.IݮD[~tAQֹq>?d4cLxt5 Y(EҌ8Iu*k!R8Ez%Y<{(NZ|S_"} \\\FpO-Y?޿Z0Ʉ*;vz}xv0%>2!I'~U$]!M)8?Q] 1 fgiG*)Cb('IB,#CH*ᇏp+ϵXAb.Xݷ*D'{"I 4 MJSJG1 o ʺ"4#N8IV UUP;LӢ\[k; lC 9(f~p7WxW?9k~[o}{1Uw,$e+"kdVbW=$IC%/N30unWfx'P: lPJ( jC_~Q^*v<%ޜovo gZu'88Pi4lLH-p>,"c-Ib|GdӖ̠֘WW\^-֑9gf34]ggg8o d-!WB*m ojLUE z"n‘%N"zs6m&1d>?a.<,#GhѹuyST];C`*babIްZ[泄X%a^kNO6jp˟a4 W!֒qzTEeI[|d72\-WkiZMK׉dL'DIlC& N~$ߦ[W[As6w"ޣ3X^/Ynxڌ錣vKOSd|c 9/h?$qI\g|[_NNςa` no!KAc,dh U‡dmY / ?_g PI"qrzI`Z8nFa6)Pj7gonU뿧7W \? -u( ֚`qՋQ'!mkԃn?5IR6;55 i'l[Bx:^?k3)oxpފ@wÌnnB=t!؛$nWcЋQycR'I2|>FAY}Qv\zIXNfTm8{. U5md}O>eI<oss 90 9JQErwIZ=au&rIUUz?͆$s(Dc.,ˆs#98Ǔ;`:;8IZ1\#|L,͈f\_u|x ݜ@w"Ƴt4bm,ζKZ?9ÕRyNQwv5{{ rP="jLE8k),8<">yzzW_!uRDZ9hBy.,ԇpLr}}gEZYC͞fd18Meu-RBg_nlfNO'( nnVdو+=puy}oog˿<}<׳ e%)EQAHm xmgLsRGpz%^$^vY0>ܓ o`޻ł8QBg*l(Hi% I!Rq!{bdoM_h E<@JY@n;#=)~pO'͆o}[rO_\f+*bn҉Nt(qxw]RI2XߘvIimCGlk_'1I{RY6uIW_ybtDQ4!H٥ڇNg[ /$"ux4!&$hǣ 3Hq8.Ir6 7^$:NPQX5dQD|a-ӗ8Yqt8ev!V)kSӔ[bB8o+"sl w| <&Oc Ud4ɒnhi€aww1bYiNx;ގ*cX92D)9::`FjڶY F1*jQڂx݂I,GoodZ ^pO&ܻ=ш5bbIGTH SATaƅ0tQsK$O>?ÿbkvSʲ |s3#.//F9Zl[낦5(4A\T:&R*T o:íznH:wTEр)I[1Ϳ_wgkZETu3`zQikF.rdڠ Bҡ f ~?- e/h & qiPP8"&t(q6rWptk-?C\\]?'x/Ӷ-b3`(&NvJʲ MSF`F٘t.Wʮ!:W>:B V!@Y~-Z|(eCaBDy N%GGGUKU 4 +)ё"u!:~UPFgi6(K-itd2g %mp |oΌn[Lےe90m)U]3͆ dB4IaNj#!ǵbm H$)h~>hcr="*%1*vTnX8L瀤Ι`Ƥ)Z'}$O8z[^,dZp4DqLuS IDAT` &ӌ6# B*.68.W<$\]]2EAہBt/8: \N+voQT;J)6yQd1P%5Bj-Ys4tsW@kj6Ś4`HOK3AaB >1: ]Ɗh%/%!ms슊ϞݮzM]VC*f BZ4VApk]hܵ-(t>#iG'J2P6K67 b%͏ ծ4mjV52͚CvWW-`mSc}Dak+ GTdy2y8!bE\nuDƬցwJXp5&0IeBxv,kV)=X=m s%Rm[PRryH'2*yCߵA VkAZkM뮬>@KPA"k\c"Mt5"\iI/<^y{ MӐĚ$Qס$I'WWoL`Qbu3} ߈cmkozT^'ܕ4eCg$s=[4')«Aɒrò{x`>)5͛$ZʺU'O=l8N#(50p8,B9lDht-ZkfIP%8HJ$4+'ToR{skp-qv}z}n+_g~T _8%Uݐ&1kFp~25ڲAhفq PeP,YP*$ !=}/Q̲9o)˒rR@, V\#IB8/X.7u`m+k!t>SU咇ݻw3T)Jnoocx q!U4X}Y!;Z_HA$%I}V'<8 kFK&5,0BGAg󔶭тi[Ǿr~~N6$y(Hѱ~xg|/[fk҆:l:nnn.[6c1} @ۃO'4{ UH'iL'85Xx<̴, $kS}QMjb{SW sJr<NNN@%Tuh`d9C#T`4RCv,g'kQ,v: ]5Q,:7yI~ X i; .Xq|aKڴ!D%!DIr XICg-cVd^qBNg]XzQ$I6#Y`3h{%\1B~`v ©( ;a'JkM4v[`#9c<{~bYk~ObNul6w8ѣ `LK5DiHbi?W_ao?Je޹Z_l.9lkŚ7/)oX_s巿$pș%17 ttmEQmW[xC(lT $S=YG:nӠY0G,&\;0T"$fgYLV ]gl|f@be`rNu]SWAlS,gϞ4 ?o_r{{?,tm˳gx-'k-yX-}juBe?O(ꚫEȓ>k\ %RJnnnnh ~QN&ky\wVthP@׬!RJI%jGOɃC8pqF2tCs*񞣔BMLO3( !Qښ ;F5&sH XʮA'9USGa^*2ޝsH!0Ybq }}0KEDU|=tx( n!XOxk:Bsv C`1;L7 }a\׉X"ݖF8ҪD *OШ0E#$4bǮ,Hg`P{lm@IP(7u#hᠰ.GjAhRHңTD7^/Au˂pt%bg=><R } >#fP}pe~gs3K$*hrX8FpH@IIc8@"7I"g۰.fY: LtF7IҌ(QQT%Z,&97Ej^#qd{Y{l1cִeZk(Zꢤ( ϾPь,ABH M-&N4Ifnpp.}6MIN>}'-˓yZ߳\.RۛkZ1WWW#%E Yj۞'ny{}r,kvŊ:Mw`c/X!~F(HQ4o CoGo7oǀ1h +#Qi$$iJ]Ut]`- NR؛:|{%h$Ih.BL!K ; 0X:ף#1IGvX^f!( ;5>}KgZǚ7hy,E= &at7Zsww՛p&ܫ"h=ncZkmv,ĈVBb,aB"ZʦFiP8GuxiJ >05HOk` <=nk4T";鱤c>8⒛;8eMSQԘ *&CK(pR4nJnoCWP i aO'TUr<ϹvCݔHy\O9;;GɄ`_\. [5^}%5ۡ MX'yoo=U!&VG[Ib6D06AAs/ϠԚrM},UCcՠ#6-t14^u V3pX [RˣmqN]~>!uPFa϶gݰnK<'5SNO9{h ERDJ qeTyN]a;QPE:Aio'e؛dcͳO͛ ՜kt䨺;<j`8㉉ཧ ?f6NEHr5IIB[wh c\hq5CC#:u/Qf)2NpN[G5HZ:Yә7(ˣY#Q;mmNELrw{}K֒VR"o@( !R I$ȇMq#KqpX Ap)#eItDQD]\\Vxx}?2=b{6iS8>h4=HmOBDzNҔ<iALyEQ "bC2(.~Zk4ȫlqcߏKloěwA<*,O3oQfl>gO!WѣG Sv*]%$^u|>7햢(ϗo_/+RJ֫f Ȳׯ^b鸹d nZQ55?3)uLdM:amGhf{?d`g=/֚޳Oj˱}bZo|NjbI}(v=QLF$Xt]7݁ |*JwWW˩ax%k ~G_t0M@-FgW7M`h]#4P#p{Ȉs\ lL7Ν`EGHbdwD&_YWd">8;=,n)IӔ7k 4S{8H|V3!CMǀpq_}&Q#K0q9apd$quK4ZkVA q~'ٟ9G'D`($R1#D(Eo(Z 8c>pMSe]q>i+a\ӿ[vUU>|+fu]Pifeߢf{A7又3 8j# eE[wxʺc$z08K*RPzjn¡a y y>,g8< JEl#&i4aoLÚPJ, JtvlaF=N` a:)Cڞ恛k}5Yx)˚vs ʐ*wC t8dy3Xcu> $<)+)f$IQF[?'1~ ؗݖ(3oo\h#s1TU5&8JKTuA&@J7m }ISUx$uл.$ lki~qn`G!ȇ8 KR}w|$$ثe};Nڪ2.HylO/`!Cj|9Q<Ͱ]BI6w1,:7^sNNNlYNp~dkOQ(Fq|qI$%#dġq2TH6w$)I"MFdNTN3grh*)G\>zc˙<_8mp:BYHTP:ɏIp-傶mY,| ̯Ǐq} _5Ji4!JvJB*EQP weNR  IDATQ_z-gKV딦t1痴m5_s87.22ᑮ+4mf^)JUMXT2g@g9:Mx`f$Z~`mVx(MX,, Y\Sl(~sA∲Q'Ϟ~64ωD43_,'F'8$EP-}Kۿ%cf9nYV|7!S):svq|>۷\^^R7qejBE1wdy//yWCCt-?["0#*\0w~wn@5d|\]P%c8;;#"&FERt2;A$Y:={4,jn?}ق {X׶g!v\;.JizLv;VJ9n@1m90dA?J]Dː1f gKXxɜO> HqΓduI"ƁR Az΅0ZguPIt$Q)3=w{k1w싯FL k;K΃NMB<:CT`7Iٹ7y@4!M<ڻ4`. r%hG׌džsɭMQ`}pGI2M;);1!'"vl `||.(ullnP8o 9-w.XU91\swC۾/UPEPe|6cZP7=uk1])e8g0Z*CC=M-T!4Y"-AcaFjeɓ/=~k0zDC7Pθ)3naS&7uMGeY#HvL20,pYBHJx,Kt&P Kc>vh]r9#uP`4C}Ced{|q|2p9 9<<((P !2 !Uw\y ?&U.nb...(8 |M6Mv{u]S5MӐ/Te3:қ SclGރyFDZ1{]S~,ڏcVob1_ezL`|$|x`%s팣j:&0[iKpۋY>gZgdi۞}QFjc{"lDZ#/K4}a?=RY3}9޳,M|~5MC?H,%ZvDi^cx^9g LEQPTf9y<'1'lL,}Z<dzgl2ƹ82?>ǜ?0]óДJCHSf8|: ~'o<+(b8EF恵u˓s,A P2k]Kے,t䳈Gk-={NDqr$S`AHaEQL4Mvs.../>$IX,(suu6Ellui x$TɊww|w׷uA;"uP1:0Ŵbڧg,qX2f"볾:P2*?{ L`no8dfG9Ŋ<#=S>TXbKJB$Z b-#i[޽믿G;vۂnjgˋOx5} 2es0#;7]D UՄ@J\ gPW+H)R{0*֓f9im,R,'ϸ{nDMկagkqCmp`a؀ݕe]htlvۛsTU$IE!"cN(4ECf"9C6,)LV1~_KoՂKZcLu2s|wFYf$ve%t !%#OR$%{bKE,yauH'NM -Z8-eQ \ЄCl貄Y`/pnp%(l 98Sv8Y,Tj YJn J@96-"tr(;|R)8>8*H('PV{dD.y`aF J"4I~;cc>!=V^Vu8z*z$mb2i>yHx<~Bacpظšxͣ&n.^ Aben ^ 8l$ ,)ދv%mK b1BURkx9urss7wG@18.Ƭ60.D黎^Il}-Glon(ZN̎ }Z( :ذ00Ewvvk9(nG!Pn;Xv;}34$e) &tʲi @R*CSKx"H6 ^`8`5 !]y8ov]G׶Cs,CGr0pRz>GbX)I+GE P c^+ʢ"f8SNOOŋE1y'IA >24&j)YFȁխxRlʊ߼7 Ŏv (;gAeh15FbsHJk~bcc[(I9cSj ySX,(;|[6wl7l7$l6 쌓-sPb>y )0#t9o0m ? | v³|}fȑ˚Y}.))s(lƺ4sz~BQ0gg}Sҡ1@l-yBU`u}P s'|hTUÞyxu)D@ FzaZ%1Y$ٜ(J(QAomߣn:ڦ mgsegە4Mǩc" EH~?[,͐sw{C3"B8YCwsyȒ'8k{wMӶOQ/Y,fH)cKX),Јi;fCI$HȒpȲ8=x  S4隊'''̲+D' :MR*l|b3/+IӜ#U2J~OoXn1vz?8>lFPDi4]o9RQ#F8;O8cX/p^b#pl>fǟ 8)%p㘢(O,1,=w̍}O̜ /{ eY45r{ܱ5.N8;9 o8V:=9露5(%I%2h:7!c*hLEq4ȉ#M&,9V% ]#t&|.(+=H'/ֆ1еۇ8p^X _ǓqJ)GHv)J nK["g{ ?}J/g!Pkކ(+}B8/..(˒%i'~< peYruu?~ 4g>_E`ajӧOϟt)O|Bݴ|||Sޡ?~f|on,1I6CD i}MBQK1= ab5>Azh>}ǯjk-Qc cLMcZ͂|AyOV5дuж=M[Xu]O_g@exbwC3Cqbb%3˽yߓ q9'xjXk^  J :4ȡq -]LZ")%jZ@Ugzڮ {vj]d6ՅSyooo w eG(IӵӞ^g3~LGl۶t]wdkgSǍ s(;f3%BFQd$)9@JG<qEQbʊx%Xkoؗ5\/~۫WdtAQT{iUǏtkx-Xd*;v=iRu`Fi, ĉaR{T3gggݹ?> )%͆|W{nl3fm??}/,Uې"u "/ۿyMhKMU? w󸷎j㳕cۀc€1%d~ԡxPdyvlpޫ85Ғ,͛WprkXO>Y&Zck. ??~^kpI;(Xk%av[8^nx(Z(B0A YW*me[UJSa&DQBpMbpX$AcdI3aDNU}mO&냌*#U~mJ6{R7R]u>5X/)nV+v;YPJ7;no쫚?o.9?iZi>kڶ&fql)tݶ87؍ JJBp]+&a2LԔky ǖOՎo7Ͽ7k1?)1]qqqA\^^ٳ{Sr{{bx"FQd9Z, AX֟}/^ۈX^28rF:I1Q,ɪ.XCۻZ[1=IFѻ}9浖nzb3\aӧTUkj8Zr(p|| A:8&{-X1'(JX qa}wU4ev+,O$7OQJ6AhaXpss3Իݎ& #"z_}2EV=Ҥ!4'`,6lbz@&BMrS m :4"5#2YZ-kDmK B=A ji>(vĩߢ(0yx#0-Ȳ _aggX,朝Dқi>dn- *g싒i1F.~/_j^|-?'Ijo~Y61$<<;{lٳgq-/?x%W7w9GQnRQ >ֻ]APjEUȾ*8/M5h+>hEf':lI&LuA|zm7ߵʲ4MGb>F\)8(7[6,GXm1߶ IDAT晘jh5hi*91Epu{fb61?:f2wΫ,ޜ0tEk7n ҁ<)ʝȹ|u5I"/LG:LA D4:ph;MYl64e% p))fQ Ym7 ^?մmC %I"Qv!lS 6P݉;qP eY289;vuU),k5M' ZT֗\6@Y6!OEYc) qbN3|niF-0XZme0VAA(7/_)_P5b_ƇrAU..ްۯ \ې/f}3@ޥ ukL4Mд-VHe:bNtxhJ{f~4HjNy m'V5QDD$qbb-UUVY+BgqD,G4Mv|*@pY~ޒ'vc([kZg:OCfE-v:@d.WcܠHҘ4KhjaY_|5Œv?-=zz٨OhGS6[퉌du"r&^ްX,zES,sNs{nn/z_|Ax/y<8_1}zzFeMӔD_~Meݰ^__-Ϟ=CpQߗTeCe̤m4)χ2U { ;!p5/"lϐ{Nk8A鴗bj?~U}G'r^R4͹c2[%(VA[7\]:Ϯ,ZGQմN<'{>*qg9>Yrq<"SKuA؆׸Nc0XC?/nբG@|(b6|t`_U`bp~d ~h k%DqNlu|;C+߳pw^k66I, ޶5|ߥJ@CH4Mz`WbprvjeH)b z=iBn>>? x̕#.ϖ1[5.@6ݯy֎l!=3{`vޏmKA{g<'Zī(o;,ϰVS]߀=ӎ1Y@UzQ9jd6%w/AZUu-!9Iy:l-G5@}`\[ Y <-}҉Gaq>(qm+~^!Wc4٭oy51Ӭ-痗e`>mK2 `,{uY&$6hpgiy-www$Yp}uۯyBRXuMdk-ht]C')M[58WW7dǏ&9͎bEvQARY2/{I kd"!A4ctU)MSQlwyR)777<8>A)\TO)\<(4Q8ʹj=sZa8Lm]NJ2C;%Vy=~'''EAFĉm[v8G}(ǀ&Is&)t$?/yCɄ'qww <~T99PȠ(Nώu<|􈲻m 5Mͧ?ɗt-T?eS (c(y-GËpY;t@LYt$bY'$}`-x:!&hAiae*%t}w7B9^WAajp A|2KWztlH7˹p4MãGx1-~kc"֫[f)77r&l6|\]]) oL&|{~mY:WTUyژX'Dq|:a9q\bMLCƠhMxNjڢmw56e9_\qvG̗$IFj5-MYQ v lq4g4har5.xi75G,3M]__p}}M~GX咦ǀ#k-iSPBPq5Ʉhe 4ud:g\r}}ɾV^uGy[V$&R>y5MΗ\݉Լh@s||>"ͧs^k%=MSЖ[LdY8VVbS罀86Auo UOUZPzjע4Hf yis>ޖ$Ѕ- ihmw#<+OA0itV1bAZa@C4MKb)j+/!vË Vx8=$!<.<d!Rw7'\oDo#=mQ\4Zc#M ~B xGK7 wtuCSVVQbYA%DŌ,q N HF\ =3[kCɝsՊ}*цi'M?zrGS^~s͚(꒠dES:dZpB~b}B}}9nGux9;묫+9!~񳟳8Zx\\\gK%ZRMGPHPZ k'PhA+Ȱ<>,(eV+֑wI,JŨ4ź@-lYqX<:#αQ%$n/$kǞۯ+cq0Nf$q6KF ,$ZC/H&9?%eYM'(H՚fVW'2N?>'c~_nnnHӔ,Kmٳ03^>>euղ{|Cc^s~~_4'J2ZIlyL,ʢx1"R|{ܽ%EKPj] uX,pn@뻠!;< O-0 ɿ9lB&Htݎn7՝sAbqb{zVagX0=7kfnCi<%{٬3^xס"M\KUdF{ 1{v]G[7Jx뙗^ݏCÇV1Vibc!ґO"8g8IIf|O2{z;>14/?ӵ),kL&ۭm8y>R򄾱ne@\]MώNP *"N]vgRig36*(88mD"(Oqج.m$I~e61Peݠ{.!Y+ElqodY";mk3p]lw-uUMC(SR]AEp~S2M"еyxP>F^lC=y=~rq ǢyiZGlv|Ͼ]rr_#B=;e.?(H&-y~Jb`ݒ9Y:_9 kŇr!ϋ7+|?c77,2bo%IBhe˃D◔9h6,v5nAk4Z=Њdsxv0Lf9˓cAD{S5uۑXBSwm ^++5qsssf"K#>̄n\Уp1өbWtE=Jf;Z9$eY?ţL, ...g5.sMz_}_i#%6:PF.>q=pbu5xO*T${)G F3ԡ]a 3:C!64ne#=h9mD2lh{*c|fj_61eQpss$:NNp;_ko.IӔnn08'T,'֊lhF2ح$Ig,HebBP(0&"hHҜ b[b"(GǞV|DPXbcI 6QT͖݆fOSN<ߵ>Ghɲtl 0G#a<_~ɦ.n-\ʓX.?$+-e]#./ & ͎8m[nV+l"Lqް^KHյuonYm$Y={7P,4@F|'heձ\;GH}6JWq^Fwzପ zz*.|pmsҜoKW!"4g]KkzI=8c=mIMB%(`#k;ꦠ ifqmG𖧏N3j/ csbZ1 ''Dqdj0&fKqN9a|f3?=x&FVy宪(NX#^}wuXEfc+v*J=}φ3 %)ᙧ^~o5[<׆iRDS=83]wJة1wMY,˒IN{1 %1ٔ$ɰ6(vM+ ,XoRHFr) i\$i7_Bl(v%rz}ۂtAUD\XE]64UK[wep_ jQ./GInur1k,s] :Ϳľm3 M&8 Ӫ~ aۊu+wO0q46#Hִs*$`&W!9IѴ9LW8L8hPs]Ni^| W-FFf#uw(fE)ݞ)^`_'~sJljWh$B횚4||̓'8:^O>fq;ʢ%Rv |KYwęf[acGS>0h/nX%wM;XmG#{*LaѼ㇞gSnp.l tZ,mۆY)cVw;QuXq3IL hnwyFݵyJ]yd2Fu+hcZZ/AS6 ]/bNkj(' D7(RL&!MmZlH."zf80>nIFz_}K ?6#R; Ҡ#HxPr/6+6جؐx 믓KNp>nI=>^]E4QAsqq1Ȫ>-]^?G?opuq\א FxfK)+@S6XuDIqt|DYR! <(aFIJEkF)6(:+b :0 VHؗҽx8Pw-Um;ʢ`lDK?e("aM:O]% 3\Q[G6(kkBdL&N&czd??063k=Cj (v i&i{S[=eQ5^>s:ƭ}+vE땪g෯ZZ{5J& z(Oɺ>LJ}!yD+v3>F8ŊCy>={Q   ^iGQ)E|pG<|}A509_XTܮIbKi"ck@$M8^Ֆu`"xKw%VxgST`[f!r(kU e,nb nTDM&%&ª-jEUU3ϹDi?'@{v=UHb}'^ٌ-FkN3BT7Tl6\'a9i:=z5uΓYHx=LA5ȷ !9xz_߯4`&HI7 L[a>+#`A059cFMx:ofw >t;ip^\ס$FSki(7%ꚣ>^x)횋 SI\%_}kMfBr>ils.qK J1q QW栙Q8Q`ȁTatcDkt8>9&`ig\md2Σh,/??H#?}PK:"nook0OdmOb\۱츾4/_|=fk3jNMD=z㰪ꦢm:9ǡpE}Yw.PǾuz-[.oOﹸd[J)=)E1 =(5Zg|u]BbGǽv0/`-zQJɕE)BJJf(lz+bhێ&QDHγ<9aZ{zOsss -m[dIN,[$fWȲzALq0,mt8papyynVB Q1ι~;(OS$jCa3%Ko7,S<$0J3Ι.LgG#y 33XXa{>\6Bb<.&z/67ZC:/K%(큚Cv\ ps#ߗ4 &8Z,]mxUQ3.$(͇|?!ݎ'?{veXb:Ջo,GB=}||4J_~݃Bk5x34. s?Nv; =3p{w|:u fG'tγe"hv~  %ކv\Zq蒷=qh `pssa '$jwGJY۲/ WuTgSz<ՆbDŽZl|>ٗ Z :6t]U ҲX1[.=\Ŋ"0] +vl 0Ri%|Ha20&z Gm$qZ^~L2(z$H;8(^6S hʯm ibvº6B5# Q1Ҷ-M4wwwHIWdP;t fȳZlp("L#Ȏ kh}͍Q9G@9m+*(Oxw{5C&9rn*ŎjEpœӌiSqͫ7 V"@`1uc74;B`dFp}go`5H)]!RB mQ:|M_U XC ]Ln_uSrj1\ ߉ QEBV)׷Av*'@Qdu(V+"Ac~ǿoeZ{8|>nهnخ7lݚ}Y1MۢO?4xpr5WWWdIsk?ެٲ$Mz}{>Sĉ!ʬ.u[@CH Ӎ+0;fLUU̬33yy;kU]UWf'Oyzp(# # =ˠmMGJyN׵(`y*Jʪ&Hb0"KDD * CEpEd`dFMú '1LQaLjwZ]Z2wi|>F :Rwxq\6v]W$)RvCO>csʲ`ݒ9yq%c.VH_wg?Pz8K!suk(Y̦˰Z4n]mjWTX'R)'LFO4θ|qI]מk_8Ew\_0$˗Ard2PU튢CFH8x։&KFb^;C',KґC2H)yqqIߑlʺa4f$*uI-9sN]!_%RJ% #G50eK9ט]yi Am&]'B;B+( (tm]A#^;6k,ʎ P5VH$E_=ƿO'DAB %\?'E;=~p_ќ]r8*l3N#W7׀?Z2/x%m <}] n sY_\-ֲIˆ1L(҄@VK`Ocf2850HSn6;(F7 Z{1T8aYemHX@aŗ(鼃38 37cwqEۻloӱ7mIL!dzxnjTYʙ/ uM1qP9~ Y1ϙNqL UB JcFiJYt![z^4E ta{]R9ٔ눼E}`bJ[7X "~DPW 阮i=8#Gp$݊Mu ;CRw5yy g&0X, Ea#K焾HJK"4Vk("JeA[W(ġjhr$N3T#> 8HiX눢 LTMV::>fX }[ˆAv"kwk9#1\wDiWXl1 <ӄIԚ0Tp!e@YL6HӘ0 "|g|3hO)@ԵⰥ)vضBPYpO>,K1R9vvҹ4cuhDUY[Ok-!'?LlhٟW_a<~uՅkX,٭7<~/^0?ܵ uQNv C/lמq;l4uK]NCBT(C.4ڒJ?pB8) 5nxӶr)Ԉpp?"7ŀڐ"@=mn;RcarXGNf!"PC'$BAٳ`ޠZ\<( dꂐmkڭ=i&5})%c ϠjE$ܻJ$ yɗO7>.VW|\g\ZA$lK0 S$k: vA6oҔuV,: L]KT0#ִ.*whm hw@+ EQpss*k0L0F۞@ŌGCCQ6uh OŝfCp4eY9]qqqJZooƛfoƯ=sd/ph±)F .c¡4"]aBѰ!¿9A =l4- EI{cz&u|5}-|]Q9. ux"GHfq%eYC+?4 GCruyu mאdEmНs9,5Vt6@-ƅQ꙾n/w0!{w¹_w7^>s mN@I(0hֻ=JA{)kO9m!\5I%ohhNnt 87Mzw [n m9(Jm4Ha\eSqA!S5Y91Tv%Wt^!y^ D/T<~wykOd{n! !LY2 hςY|kgpBuBsUn'R'pX6s+eP\^;g]Se؇+@!Bwv0[d])'д/?BPT% 3dƒybxzA |tpIB?> ݻ 9ݺ+lP': bsg[Wml AQXw滹#eR== jyϴA6J^ QE|39Ž8+յktGZ G)*ts:WJ9O)i { 5IIfX·uح]vnKTs<=k0tnu$ubȝ# .eKH$eK5ͮb8f~t!/R2O٬|W7d:z#rYecQ E;G`d:<_zg:䍢ծ~=nCgxmC>+CYhбGri:9XǠI"SB׀Ju7ݱ "/|Ÿ'vjN3@CILSV%{ls|r֟ǿOG.Lg#L9}l[c<;Cj)GQJtJp8 J:mԅ ,tC{N#a mgR( nJz6Z\!;yS6yοɟ?/>w%7gfZ)g€ rEvCQ̢$fXpy}Eh4@;G R~[xw677kOâIi4i*qf@ Ћ>Zˡɦ#=s>'_Zڶӯ|w)9}5Q0UՖoqd(s~_{btUUih4΢(Ayxo*|h( .//p!EQpuu5p C!eH! 3tmwcDWMK[WX!i͚ Kg]ʽ>5u2mcF +-00@eep$KPMǘ΂Hl :d͖xDYeN2B{6Pu|ru8瞭vfoƛfj,PaLBfHM/ZzIxd~wۭc_.C@/_h/߲ŰpbsY .4C[Zc("6~Okѻ#D[k GGoѵb΋/{0A[AW]Kq:F#h@Cnh:4B#3JB0ࡻnϤL~|Q  n_5-ej@pz:?/sR5 ٔzS0?:&J Ieszٌ ž:ʶvQ 5d}unnYNvPݡ JnnVH)\Fct881ЅFuCFEQquux~.3C0^1ύ0N4zj(/|B pQ8qzA-m=#gV3Ůz 0=÷c˲59Jn[o ֿMsEayN(與,Wyڬ Ht( <^om1m-CuLߛѷl0n߯k?xp../Hׂ56uR E7)M&sdކ@,yb GCp}}a=i ;%1Qt:%I}7 j޹o}Y*ne۱X\mMbD -mB?cLn{ɈxnyQkFI;}6`Y.|>f6 wm #\y2$++J]|x<>>nKQ+(gv;h7(AnoNsu<$WX :~Ԯhێm Ӿ=6-]~ bT1>y^2M-mIF<|ԹWH{qpB ! $5"p*zfp{ˆ8Mh4ve!]@ 2R(-@w{O|gL]/thnE1t2 g6r)%z(^__8d%u]a#$gG$Iⴷ|ikW;uMP5#3%\^Uwm榠;N"hH]f$ %8Z7*4 H$J@ =__VcMB025h#p?+;v֐]s`MɳϹ|8y9??x50"Dk͆/~AcC4PNeY}*vf6S`Md2;|wIIƉ|r8 k5BZ8d:7?~}e~rÝ-X#☯O?cf htũ%liZMAqn-Ψ @zGplœ|ܣ궛:Anϙe,oM'^,E)uӐe5li;ӌfuniL{ic<:>+^ 8\0cOs:|z)8ĩީ~qqM2P@_C~CTkMedY1v;|B;p-q;wde}wѱÄ.tBxQ81ON(ުw;H Mz0LWǒENo.ۈu3ƝYq]u?;kL\xhײkZ!CX:mHDEh# 2(b4r~!T8W֣m1CZ Jb`pm)b85;֋сowwIh }4M;n]븾s-1F$qDqD⯙\vuHŲjjuC(ۺ.!+༎( w |X^ԔAlt9˛F[d11;'Pн3|<h"谝 JNШd½{|kq0`xߓ)qhC&=gd6(Z,"oQ- $G6 m[ulI54UMSXmDnj2 `U<ꦣ[V#d@es6-a!8M [glJ'?w狯?@戮 ,X7Bɘdf:įm[>UeH)QaVlvXb@ ( BaMiڌ;@XH{ɐ TUit۰=̧c1d\6+f9''̧c& ~GQ^y₋kV$Q5ERu(n;F *0%$unx>G|𝷹w2AI hVyY\myq 7;^S8k5AdD&P[0`E.B@%LN$*vþauUc=~k1:Mӡz ЇJ)Ե9to=|a{Kz=x0;x]+^R M(HEL `[ʪc=1Vv4Z:c\ءH1+ծ:*1JTUE9A>&pUeKBϞq3`F;Xݡۊ~ "d6h#)Mu ] \y;JG|>r8v_bz3ތ7x3ތoC@9NXaskWz"[%&0}1 |n dUU (pP됖!Ȧs0N09e:MyoXX#8Ke8b#ˆ}^DU%0BwEv^4M~S%d V|ׁwϜ}z{ﺁ-\&"_я!BR%uQblGDI&I IDAT{\-x1.'<8G6+ Pε&l[ϙ-l6#KE~p-Zd)!'MF$IB۽;c]{;,C8Zkd \ݡE^)] WO!@ NOOp8M6CRj,zÇ]f64 I30;&á8>avcnb,9QZ9. ,.tpJcgCVbq`i - ʁpWuǃCf0=8fMPz\^]Ŝ -P)eY x6vH E͆>>Ra#%xttt:ꚶ"%C^:Q{4Jhg AuQw0Zsׯw؇$}INfr}+u]v; Mc7}7t:fϝ\;7x>'C;k*sA=o$IHt*Yp!%ڣnscz45 r0m tYvHشvnrM;\U](0J"Bp=SHd~qh;0_zrCM?z B󯸸ѣG|kxǤYa >ap8T4saHs1 +:ʢŔrb-z35ÜNw}wr#R&uQ6Fnt]G>Kq;dv8gn}Gyb!}6̬H |ޮH ѐݱFܸ < 8?9&I30f:na~]d 5v0 999ϟn}Ar#JRHu8#vu ҁq(;Q+|m!ʯypR眏^G3X~{<{o{QcW?~ 9\7d2XVudѺ>cnV8\?Bj|fPX2bZm>?O认*az)i"64Æ&1Z[QM`L6VBWF`CR0LsFmoخ$r2?`.WNz~ *0,UnAȈN],!a\.9%///j=Belu AݔT !ݐkHXu-I_]~;oBw@ 8%Ϟ=z,5W7>~O_ 1F ?}77[>٧Nٮ)|iir㔏>/[os,i$( $뺁ܶ/a䵍$>ofoVCKx: [gκ~29VH np.MS VoVoktƆq2F x< D2JCFYgÇV+^<JQxǜ/Xh,KKr~k׶9iƤiJ @ E(&Ա,+1ё'1*\aw7Puհ2&[a -|`ょÂc u]#=֥:X1YAi'Xш0 ]6˘d+-шdфӓ3>!BywyO<i1mGu_4]Ƃ~mgCXG6B|~ԣh"n1Y!;JH3/_zr@];,Mc/7{DQ͝#D{}^l6b@<I5g(ж~/ B_haiDȀOW|[o%S5iQ%PHDhMSNys;g)㌶ј]d(w㝇_{btvև7髵Rul_i}Xa_)_DQ4T)\h(IǴuqBmV$)HlDOYެA떲MPoPA2Fҧj ͆P*2$ɈC ]laCPe{3ތ7x3ތ_56h554H1&C(B4#S,R nM}EVrݟA[vbymXBݨ?A8XVm{hMQ !HۭN?y/_e۳UʒUYMK~SiPAvx% $ [QcL+|wrkkO_a8"zhqzG7v((,ΘLfqHLn?vy}nc bd2R7;6 펳Ouع[j D!wԹ>R:F]q"ufrHkX,Ts'K;߿ֆ#8??磏>{|Y.yJeʊoƽ.aQʢ"/:Ytg{6QxFﶴNs6v^hf՚^vE3HkqK+׆[yҿlz`ʽӟIz`UkF#. (azO+'`pۦ[mow*!c՝ y1(IKry5ggg$I68G#W EY84vu>uGy [׳֔xvmQXM_㫢YtO[ yghl>P.ah;UUqqqp J50+fy5tc[kK\^^X,PaHrs&7$>㮹]SdsEI(D#ʒ=c۰Wg6YX _$C#Q")I-v\n5nčUlj_~@!*ǒ8q8omz!OY rΫ$qsvJ7t. -9骟zn;ְ MJթ 3N9r}jǜQix4iL?t,{{29& эe6%ᐺ)FGYEwSz8rd%~W^ƭM\TElaزfƾo3 Ӟ̙$ F7DA!څ8 [)Ʃυ#Kk7{A DY%eVNi逨iJxʭo4)QoPزY,kBMZ8d0R#Yxki!'cw1߽r%NQ {鐯~گ()̞?o6?GgK*$ҋʣi4~iVK[Wy_syG}eS嚪j^P,_S|SыŢAIQ(gfYևtQ]J*-wZiRW;[8r\\\Z\)CvRTv`DGʏ4>]˭ϓHXR֖hLX2׿NNX.슋~c,i6#P$uW(@Α [(15p4M18s𣘤}BI߱(H`1#M,VK]垸U,WNNY6&{,+pr⳵DɄǧg-1EU2_- CH )ш_/_9Fㄺ*Phymw׿fq1ϰ2|ј $hMSn\jp.OU9X#0,;|v'\0h4`8B5|_1 mg& -A1MvJڎoDUW fe$mx|(/IP4:sr:c<Q~3+:yK'Oy=G)!hbZ( l$5 ck RYctգF-,t <0KW_g7_ hy^LjςXcv(˲ a0`24ƭ_;0=\"m;%}t6 |#P(k%|,p5d5)ghpikUO??O'/ Z~<}eɍ[7IҔzMek=^V̗>-zM^q׮1 ) ǒͲ*{oŪ]>{uT˒xdJ+QT%UUkĵ7uFHAL$c5X +\IJzZ/0. )s CRWǭ{908ׇ=/2ݻh4_tRݦ}7o50 IDAT֚/h4BˀY7(Ȋ)F|w LF#0+ց5aN)%oٌ޿.5O0w]u޳ 6@P)y*۸k4b8 Uv B{ #L,w֏3k4EwvssكON3Ny&lfRN!ij ӏM)F7M&o]V^ lH%WYv\ͩA 0߲Ƙ'fކfܰXzdc R^y% j5QJl dClkMwN =sNN)[x̧^ hڑDee5h3XT MUc |vjWd7YNI2]VE=5˲D͗np֢!37o$\yZW+ :DZv%M"Awᗬwzf|7]tXy}ELS^H%8e<,݄xt:e8NBRYO,˲- x3mHln+rb*jitk㳿߃FWxJ0Ϳ|/G6hDq>&;UV b`pttRdGPZ|_!lxIRA]J$A)]u8&ʶqmtUSIAdٺ_"Q #}lM嬖[(X߿{S999fWH)zhm?:<__Ww}ݻ&gOcꆋ~s<|t xrFaş y_o{(oauk͆o~o_Eޡ Eĩ ?O Fw J', Vp0l.q^SUSapy"k>t@!|ySG+0hQ/-rBu?(iNyΟR*gwJխTxkb>EB2/9==EF4yԬK4P*,~]G>{S\;&MSNRfdG޻>&o8L9H' &x^@5yAUA65lZ7,+⋷wIo1pj<,οk$x,r,#U<X7;@i `Ya& C&Aҏ=Ub-{{ʒ8N(lwAzIY44*Zo|o};4V#{fZ#ۆA+Ȟ*E(@I6(8rXcc+!d1B"<׸q$RT?Mh4iUWu@8c:F>c?yȽ{8?pi' >}^OUU}0{nu+V>5hpv:ݻ|;⵾JEa{Z2<9kG|3oZ,YV,uհYg}XUk5B h8lzMm>ku uߗAߎQ>XZD2 CL ʆF!`5JR^y67n\;? ]!e^QyӖ#ah?(yWèoZcUaNR5mki&QT=9vxs]pq~yL B0Yg (.?-^K0׊PA#0Nx NN8] 8vȭIҘN9w0~~g?z_!Àl{ǩP E}l €}'[}xzʛo?OAzܸydBQzz]|fQ{ٌ7nqc~()t{DMggؽxݛmVYA',HZ8==ŝ944 uU|bx)r1)‚H=+c bSLzZ%. ?lv_#a-B7.>T qSJE:U8>>rP7|iQ䀭rIQı [;fldg2w{,:Vy^yŗO#u4"2b2pM'o )M=ͫtCϘvc4uk7&A]~о>A|u]OS!qk0+W*|<Befe0rrr;x"*[)5F)ofZQU%q#=_R0٬sle0#\]?narTF~~jNQTylznyYͺWktci]c eQQ7 'OZ7rxzPqroUՎ}skS1#Q9@)8<< ÐS9.8In7L'6]ݻ?o2ʚQ)+ʲ;ʪ"l=ݎ$I/}{m^xUTehp޻jR܅]}xhh,12 |'l ۬xJWUy!_K,Wk׿| -qB+Mu,[ƽ=lE7ƯUe~ǭr4$iBٌ݂$^00L&ā;vU̝/>>&B꺤*Y].jA6FMdB]l(jlq21׮cݿT/ /q;l|jAY!.@=rnm]5)8$MCdoui౷7q*Qk7ʺ}6@1ч!(.+Q95Ӎ7x7Y/X.ιwR A ']7IStXܻceP l 9>: ] ѵk^^0B7ITSFEc'8a-~T~dY,( r ~4+wg4[CG( |zRD!kG'PǮ<;9kGG3Og\~Sh_%{ӱSHpUnT纵o:54m!׮]^ni =yα*ggy e0dWl)]8~0  Dk0qRV Ŷ HO5VFhH} 58E+/& #V%uCYoкJKkuϲZc&wzmnVN9ePrx )-jɓF\O FOSRggktUܸquEN`0`4q~~΃ܢv1ij&u<)1m0Baʺvxwc?PJQk1>OXϢ|kz )45Ø|;ܺ"ł'l y饗69јl7SeYQ%G{{c8DylOi4n$eeU; 6['=;S5g>t,Ur΋ vAvMDz^Ϫ w(#BH |ֆݶpiIJ_ZH!i++ B<뀒KgpɒvmpTsGڱZQ>VKv_vr'ꁀ8v|΍7XhI[{עc^eF.h*ݰ]CP^@Uk\xJppxbD ^!\Mx)z=?ka}?|Z7sFsp|DDiWlKd`9xՒX}>ISFS 'gٟk.%*pnRQ{t'_hT?8cq~؞1h܍Scrڵy¿'Bn]HgpYE)Ղ4M"*tʍ7`\29cwR}7nA{vp>D\r~~w^֭[qLQx:bZqTKH=VW=h;֗@MCf\m(t#DۧaZˠqF(ykmZ׸|\6Da Q%R80ZY6'M]sFXNԻmJX-s$W^[1^%36 UkI!qVVC<9mDtrgqORyv,xwvAj|ײwwl[m(!"t׻S'; r>R>7n?ytI8`Ph U{> T9ƌ AMcD߾u+G7n{69׎|$B(ovd8tQoF\v1VPc5/QV>ASyY%z䧩{拏=f:Yf1ծp]T$ FWZYq^}NtT]@h7w>eY" IDAT8 Юn3@"EU$QE `8Ŏ9t]!gbz(i)v  x &)5k_;`o:lvNU<|{ɜui4MMz-p{hO$1_y1}-4[nq~~Ϋ֫Q͛.кfcl.~v./zP=s||YCيW_1?8 Zz-ֻli BhHFFa֎$5VE}J^aB//Bnݦ:J߄uy iƺl壔XV98d8p hvk4ID4Fs1_"rBhɈ6R"Tz=w]IU6-P%劇O8>bee "MD4Z4ZyɤS9vَ8v!Ɠk_᭷BH'S $ w~7ϼ)A]d=|%'9<'MB]k~DͿ'??Xp躩U|>vp!.@䤷FR1pSh'O{q(j}6CJ$(v6j\hn^yA!*EnLׯvU7Ly=8vRoSt54j%ROBYM<$q ɋ+2DH9~5lGc$]oCt$=?N Mel6c'y=wU$;vJ~x5xڃZNҶ r8ٰPĊKyޯ,Kǎb'iAKr"Iss׮IQ׺ew}8c5Mb8zWe|C0" <$oS;*)XqD"Y`'ѽܺ: t~SwL[)::[S7%rxŌdUp2ƪ1\.-}:}96f|cE`P6P5hEH٦P7WzDohɳ:5J;fS[,il$ITg, MӖ_PV5q#G>yxbDgXoUu՞A>{ { t2iѦm4`uu{r+e%]˃3/q೰)ŶvjJ̲ށ}> xGXk]K:=<~D-Bwϳ,I}|NS߿ϼdu%AuT=qk@Φ؆rBy\feYc tØpD]VԻ%u݆kJw ?,KNO }}@UNx}3vvqGtJr7l ݜBO_4j ֢xHc,Hȱb^J2Hbc:`XHqsx睷Ӕ4M>PO%j֦p/C{[/Z"Mٌ 8=}BL^r5 ͺ, &M8>>vQkkga|_~'OflMq_|rJ!]!ƠoI? evARp1;ɓ9 BG cQΩuݻ9ƍeZ\ͩ+f! 5 Cs0$" Ӽg޲fT(y|r |<|ٌmV쳔TpN96h4"Lb( Q֚ihfʆ:S!|p_hsSAQ-*ƈ膺]׌C\BɊ۷nG'g LG=$\?>O/9:8rt0;om4nH!o| gI^}UF>S,p4Ƀ~zM\<~j0wΗg8ċ vɄ7nL\jaoo(sd3ch+(*8bW5HF1e΋+–$yy>Ҵ2zMb`45{ܹs=\JJ]-Q#e Xoz~rd̵GMS@{x{D#w)˂hx_"`2{cgP[,הŀx|-{VIg)cW%2j01 =8ۜg}{eFws%3?N }Fg`*0NǽfS4s< paQcJ؁SkNyNY\ySYm[na:`:X,Ȳ#^|Vq>n08Gl\fpE!\@^{)YuoYk\7 ]6@'۾jѱ=s5hυ:j!J4t %$ޛX睿8r%ʬZIjR -Y,ق<f|`f``0Mز40dQ&[fUwג{{g?!9U"e6o.f<'n}㺺ا0O-].V PMd8nYl&=aGOvt(vE[>_y>ERulٌKIVѬsH0)UAh~Bme@XR$ɆLa:r(YZ-ؖE;laR .8a)ʌ(NN{tvS͙{EuG}j,3R&Ҕ$RQ%-hц!ިBk[=#^;gâP,"LUGbS]M'0Yp]mry%ingle9XbaEt:=?~5Y%PK}=Lv) }n;lca`/*iđ!]y!Dqjmw[-,Sn/J]4Za(KEi(Ĵ~$R\&97( Z~dXY/y>'ICV >dq3e٠\\bJ+沋Z<.~fiVP{+fAS*mlҲ,czuE9~#ǯso}{7΀_u ۴UjmOㄗ':t{z6Eigci;84`z%P6K~#NSϱ,j{c]h1iS=A1775r}aj~\{|AeYhK1 U@;N8j~Ǐ}SBu°ͳ/9=yb( J0IJ,6wvxmU0+o":i"BtBgҭ`T)5&@TGB),ǁЮKӬB=@%ѭv6IS}h65±$yՎm2G"(ƃpö Ͱ-ӶIzWu9Gm5փJ]խ¶h!wv96BIJ71۟~E iN1eu@is1fDuFOnΰO)j2_pzrjaդ˂2MڎWqQ *yBɯJi.]m-)TR(rŭм!f9HU (DsW8:hLgyh [)+׎Dv\KYF)%Ih܆$Y:*Ψvt:Pw7f4)"|c>4x}!*Nn0L$o zMYj9Vzʲ JR6 ZX\׍݃j9v`@uD8e+?U֬vGjHDQR*NQyS8Z^vLdI6rxxGBhuyZY@8Gq[k,¨XZEqlz;];J5l4cܽ{8,t~v?SF#U]4znPA00MYj1Ja#mZ||V+pNuQMs^2y'jْ|ց lSU7b{ԬIQWƔVgض:x%k3mBl]%e#$ C.//uia%]Ϸz{G+)E5A幄aHНMc7,5GZpvL4NBVåfaYn7PMq۱ }YĔ2%Is,竟wv@Sz.Y1Lh!劽**0 N^8b7`_՚]Ep%S\aaz 4bdYtrŝ;w8;=\4ps3Bmx[6TZ2M(6J)jMY.?V8$!4sy!N4 % }Ϟ=cZnwq](}$2Bw`<t%s`sO97 F,9!YF7Mq%8&t$RpsLO -Z=6ӳ)p* |'rZ3 %Q7K;>Us {cC۔mnF<9m e|u*Gh p6?RJ (m"q,VV)JEQf\\b2?Ǵ$͒A_ G8HvT3菸'8r + ϠӂqWKsuqJ0 7?ۿ-j,ӋjtbQe+wjuvͫkwBќ?OkYѾoo,*,KAP:Jʲ,&x'Zt:~eYx> I/'d$SD_w5_ zVնy[ET3n8NŵFa.$IQb07BơXa^R9 v i(Q4{joyLԨ:utZ=`n;:ʨo?NLq_S\$,"R07HC맟 TZ*w4,ڡ<ϖ& $VX9(ux eAc44\IT(aP:^5jB tp!QdgƟTnkYBYJ!喗) aRHE8g`(BbnD1QaZVj1{t40}{EYF5@Zf5J)JS2s9Xo N*1\ N}n;T6^uHyXu"akݕaY:46qژzrhzYquuU1# guhZ067 I4f~Z괱VwNG݊*xZ&a9vQdф^kfiIkRI[s=eӮwҡWg_r _hNi8A l:QEl];MOZۘv[ٕ>aLk]4zT;5hB65.߰-3ϙL& q@CG:ҐN_|^32@%T9B;ju,o5;C~\6ZLěSf)ǜ0:e80Bp~q;KX,gw=7~g/D%=̦svvv-4-vL86Y"K2өzo^mI+o=F9b&ytϾ|װ݀)f`2ٮ&DL >~,Z9„>_|CJ ^-Q" |CIAiClnVkzJ`Zr (OwCwKiD&ə<%^J ߧ9 `ggm[Dъ|R9y.q ۶,K6x((TF\ls`dY,'M+#Mk vH{-^m9~u³?#Qjܻ3OhwwMvq"XLWqִ,e@A޸:|3͌`տC˗3z~ź\QْzܻG2l)O^> s-F J-} ܿO?gl66VIb۠խ锣#uZiVTkV謓+K֛˫k֛-V0]\EÂL-V GcCXɫ36S!ռVMr)իGGGo :jEa:b6v(ttlqk899a1_a54Il8n7ؓۛ`]Q鮞:x7ލwx7~q=z&aM8A{D-P:Ɠ |]_Y)f%\T\d :Iha(04ղ0͢q"ikY~`4abviyպ #ǴMY!*'B`ff!NSLƲJTkTͨ <ϣ0tx +m!m!'}}# vvv\w9d<%-K݀UōbAsrbyOH,Qf_+- D3Y]- J CUH !Lj*00M0upB)Uz%4BJIV乢(v E+vq,,qU-BK *ϟxR d%+} f7U)J4;%Bo΅ZX9"Yzn:=0)ͦ9.d9ҫAm€N|NaY뿵,./<a-Hvhhq-N~)m7GeRiG2iMD =V|vͷ~fD?00U(Y{;xOܬYDxk;d5Ͼ lj+u`ڄ8 %ٔ4D[eJN2_-Y,np]Ã}]4I3'%(EQ"V-Q" ?la9687I%%СbQ iB.a` B O6qLۦ q=z`XW3yq͖dx8 | b??aQ~7Yoxq>9.ysu$bJ˓W/eT2ѣئɠa6;U'ITESC96JY8CSoYaϟ?c>O?ŰB?ao:\rLG]aG)X.)᷾ɰ ' /; ,f1{ϟ?i<~DpqqɌ;{ $Ղh;#$az3mS?'"9d<R% FiT8~q]qtkܹG _u/[tzc>ǟ1jt{l6 d>lt!X(%[lVfNӔuv閛J#fWr>Ylsۄ8X,7,YRxA"bLl\iS 9A۶Hݽ1Gv{C{Xa( #9="K8l7{<9nsiseŗ/*c Yh8??_-~;H?$JɒǨ%7'+AVSA(w0`l|c2S 2b_[nMroj8enuSn6}~_#(v7۶0 ?oVÇY,ijwzNo1;J =lڞ?x<{ 0-%ق]:EQ!qLixp$ )u-TIjt4vl6k|>00l6ܻw,˘fmvvvp² y"+ZLMfmywx7ލwO8vi0 9??'^,vB5'0Gf)7E‘# qSZ:_fuWm~} "7!^3Jo<4i۬V9qWmY㶮[TRa` ƲlLF ARidk0a\bUoԮQЬѰhH`)a"noO4l}5޾_uLgKB1[IҔ(+Y" ΐ(K׋Yoc%ta,u8y}lǤ(2hKQZD201 i e%E'K@ -i,KM.),ekm LD S(\pjL%oHMm긹Y eAa4z>o㘬G!rMWVt%~.eQDJ!406@`Yk:Chǩx)dY90k_fWsQ`@ CJIEt:FQ"]x^,k 3a2Ŷm.Nxni8H)]Bu,s\c>Se-YwCI I#E =hځzmZk14MH $SJ3.VoXUFsٖEӡnm1slD3. IDAT ^iRl#(B,^;5(=g'^,nr'jyb rѣGmz #޽{rӧ|g8K5ݶ*1Lx>O;tC lk*S,S?'ygUScvwwG0Ca2L,O.j=goSpG?s0}Ĵ%-+7,'x&8~_#gt[- Qw߬l`U2 z],$4q (*Ua[tܐrver5e4A5ma-Ҽ$t," *h={vh!b$! dxgيEIg1O&醯6qTp|6Qvz@)n?F)ד3!B(Z\r<9&r c>*'-v{=6ypGTL?\kEQ4hv UZQ\]] GreYMjt9p]`,SE^Xo -uڎj')E!KJRՁfU7MzŽ{8=9na4,뽽ꊝ\%Ilf6np; _hXxYMvSP%EQ^ofdjɊ(C-o-wiwGsɄ /Y/W}7ލwx7ލ7|wy I\tprr%(i2!MS^za 1ݢo~t@Zɲ̊k>++V-V:~ͺ=s݊ jBŭ4#ϋ]W`Y6IVqԭeYR(l~z!/rxGD Т3>J%,a_&{[دn$o:.yuzF%SQ̖ :1,61Hh\QF 錋S, ^yԑ}4jڑ/u4v-( I& rB*a!1մURUTvrZi1h_bqiYB¯ >F40$M$S7-NOO[J,-==9R !chJ)@:Z45c4jniaזBuΏsGQh"C0zojjK-Ԏh2hZUfAcuHzfqcnY&ݠR! 0-lF'X.6VT]nեYo27ǿfcc.|1/Ķm1&I677i۽IBjZF,x Sex="E\9*&#e{wKw„hW"=f8Kݦ^3nrxxvww<$:Pc'xN[o1TkJǴ[.s毿5o u7@oNt 8Ų2H}P>rX,Sﱹ߳N1 7R WN$Tm[g2yztlooTBvj?gevJgr:e<>kp||zbѬn[# jB^Hݡ4؇?o T LJϱZ*EɄFeZ5jD0_\sؾw!0dޡӳ8=zMn!sy~ƒ;,Ws,ˠөହ<>9Z#`6sy~qAès26yݷ_qocxo-FaMJa|>'I666e䄍赵50d20}xaufY@t]DQp8dXU30p*:LhԨV뜞1-8:>NzfÇloo3LmͰ*[ors@qHwUQT h6mLZF:_܊rt:-ӷ4-7vv] }nnnÇeEQ%^0 mzh!V>ë,*jhaW7Ğ ?Kw~ػ׻_ޔ[qu:gy2z+fB(: gaQ / q4Ơ^sssSbFQ*pCUH2Q,Lʴch{Pg(VvU0CRiJcB?2$(TR _2Ld,sro_=__&@T)czkk,V3Ͽ@I-gJ?UP:&N8&c$Bp5լc)k4rAMJ'R"ec2af$UqEDaBf b 2eeؖvQ4@I?HLAH Qidń;;;:Aim TTf<{dVu }nrs@_rX`LrhHS.D8Ig vwMFQ۹Y{0R,! 0 EB$qoF=^RJR+-1ShcD)-"9ò򞤯O}/VT5(RSA)q;,WL4Haxm"k&e2{c&7 zTPԪv⬢21$syq^;v񂐛?p HfޡZsyyd|ZD14Lm*;iSkIH*?Ԣub"wHK٘fB7aջ+ ш!N @XX.DQDfscÓg_\Q;'ST Uj3YbJg9J|EV "T67ңdFh4b<ss3);;Ni}+aZ0vgNXB~?_rȲ _f>k?~)$ ۜjHZql嚋vaW\???ϻbHߋ:0 Y)5:ZZ;ַiڥDN^w4x۶ }Y.Rϣirvv]䍦_xqxDe>WW_y$4,cNFdd:7#-lg? o5&7co|~zd2VmbX{=~tlLB%+L` Ŝ/~3s5CT*UZ V%b0A:U6Pr|b oHˆ4GG',s,b}}^ﯸVk4?Jr~~Jlo0X_d:[K&q`e_p34Vi7DӖ<PFסjY򛯮hیF#|gkKW<ϣR] ʅy\y^n(ځ^yQ^=ɓ'z=9oZȓh<^x=^K$I)|B☦nN2)aIT UmJ$J,i I22E,P$"ADpW4$i8T60ETIabJ0HvZ5-i$B`-D` lڍ}=Z0~eiDU⸈\ϧLS|?nhҐ(/B3L2Yv]+k!RL*C@dxLIOrW~U9fY{LC"-7vF !Jq 1ZD_z=+sft}Cd}c p*IOfTU}0JX[ CZq魑f!G?&LJx eV9<1pU8ecIomgQWC ַ6hA8ƑtpS! B,+uL77?h_g!D u=m@XGՑ 2&ʹ[E$a"c9ҹ+iJ3*i74a>g1vxgL&jtjHx:g{s/V*|_a0\po>M']>x#83 ~ 3 C `0pvvF4I"VQ+#S\'0m~a8Ug}soARc4qv~˕v.K2]AV^Z.( U]sv>袞 fήfX4Mi4t]?x-.^Hf1]`H~?޿cˡ^m&`[TsvzAŭ3-5Z^HyAHݦrrrFA}_2X.d"tE^Vh4^^+ZVZvRH=w4 VAH?`Nu봙NxGZNO裏כ ڕ^0 yajDSV@t:kgJ3: Tp8ԎA/B6?z]Ֆeѻ|c./mG+ av! **qqq|14eI *b>/z1 5.Ύ i:w1|7ZDQéq9tef:hpog˿W;\.;w""WxUX*Ua%=fIJ@۴>Q! z]uNK%| Jh|bd>_hF8Tj5p+k>?qyT5HM Ჳy*b/bX.a:Y\b귉U͍F vk0\q-o SxI(%>ftqD!Lh5x9[[ⓟ暍yS:^ }*5獃7x ZkLIZpIJzuu\ FTTX__' C%یc.//Y,z=l.yˎN O?c:u)q%ɔƅq'|NmqH臧ݶ~7(Ӱ1œOiZ\^^b1T*uDQ۝Ff\]_!aN:! Rz.R 8b2\.!5|?qy;==dZ?y7xrZ~=^x=~ea`J2Yb '$QȽvU)Ӣ`mB,[Zқ` C̤+ 4yMܘxp.6~gva-ߐa]Tv+js $z*gs’V.$(s)قnŨUg4a6 .o.V*)P[E1(K#30~|wbUh/uկ㫌4aZagx,=aA@; ab;lˢװI%$w&[d9:B(K +II=iHL)IB(AKKb-Js\D1LCj&4 6$BR$aiYv4>,{Ue 8/gQm+E;e*7|"w+%1ڠZ2/TMt,-f3-Nj jU#L "kwp]7/VCk_v|:4BbBgю`K܅^B>蛦)"\kC޺_xuUF/᥊gU_*g0 kM du>? 5abW\ZZש0O!MrA:(KЅB4M v Y3 d[XMFd v0j4d\b4JiDR!40DZ'\Gj5&x%J%8cuӰlC:[d2Lg*EIr\ Pd*}>! EIL=l6#>[5)Ne1-Q<+8NòH8J Xf r88 ИFɉ. 5RJhH$In7ӟ| ף[$^CfTO:OBᐭ>WS&Y#Zu>)[aZ^jҿfs~,gd2 {~Z |IŴ[Uk1I"9La0;a0_νmא =x9K?/%IdțT6~ o9a<!O~#"z&7kQɊpcRj,aYL+2u1d&S=vLL8/9##ͣit,fSnnnXVG= Q咣#*|1nm8 /x܌_K܃XyȺt9>{99~k1,"ٷnSl}_/6E6MSnnnNpzzd2)+nqfyaɷCjb`6aYѨt:-4jAR38&\\\3qYSJ^4)qq㺮meoofOx~l+z :n0 q;>>ցzk(bV]0ϱlR?ض|>GGGdB[/ / )_(+;Q{ŦpwN=W 0T P~.B,UU@+^ݎv:onP4r<s~~c}/Ej 5AlP!M$%[Ex^%ş:|eNw_<ݝe`魈Zx:l7$*iT\[c/0PnIO,MQ魳7-^ 0C7LU&E8H&},A_VJh \ )P6)L\ )Cd)(M)̌Vl̜1*IӴ1ܙR$e,䟩<(0BKn_4o9 ?B%}Y鷈i-KТ49Js%c}vCxrJa&QSVN cQ`00T˲X,4Z8^nw0L|I$,Zs3m{#͠N.˯<D^8 /s'ǣ{m@g{Mw8 AW!Im*.NLj~oaX-j-H%F%}VAf[>|Hc*1W|_0^X+pŽxpô,m+*# xFm. DR)K}~ַ-)WWMQr,NO 2C6Uv:d V^EYqqy)tR cs}x#-Ѯcs7v|^X t:g޽s#HwoZ/Qd}}ZvK$BK47'C?|.0+]獇z~Ə?q _[EO42h,E~6a\.ːBhNuuARVeYyBJq]Fy@F0MEN/لmj> bAuX juw9n7͹l P[( 00^,YWlP  kz \r,oqJWO<~)3 X}\ Uѹ`!s^lɔ+*.ws"/xs/`~.sG)a8mh6a`Q>QkxQ`(thiZF\9؎Q"YDHQ@ 40C(ҭH-: KH+"Q4F #b(HHP*B4Ɍ$U$X0,l_$YR;ɱiIA3+ H;gF|iDzBH'\!ǖH-,+!=" [ݞ_B*L0Pf/ D6^˲@,c>dR)i8??g}}Csr[-666} |Ow<:@y1lr~q%kؖvdFEVKjZNf?s~XKR"w Z`J3У^c.}CӢѨjA MSm\m[F96_ |iQҰTl%!f |4Bɔ`T4M媜n`8Ȓ[1:24!N:5RN@~"LW)@ 3|:%jVd2ASޮ/wg+,d0mggԛ5:Fd2|'i[ӣҪ7SS9I h 5(%nZf9JdDIH "SȃAekU4hIQm% g}|@]X,HK b%yq]w9qm;kyvM^0.<|Z֣78??5-666 IJl}~ӟL'2NhuZ̗s#d?࣏>"LtlQ[s%P#lq=awwEZ4 Z붩7.O:`6"dt:'UIQo4ːk b:#(e\zQis|9d\37,"hѧ{%e{{3 N! ecfLd\Oh4Ql3mjnce\ assg&)FglRi™M^8>aZ)uk }]7x''={ZJ$I.}1?ODVlcMFv!Njc'oL8>{J],Q_fԍ&1`MM1KK~,M{L / 1?j7 L'Kf;UO_Faj6qEDztqӷAD, WWbLM$K8{rO__8/Y&X17syO~͹8</70NKUQ}SJ,gϞb *f3^ b%N(bz+h71шV".{{{\\\2qY___/.iNhrvvQVFmZ-Oo^c JTH{*x=^x=~PJ% QU{Y^V[i5Ak1\XuX.x+MU *nQ~ş(;͵e/ /T93Ng:[ N41M-i"@j|Z޶m֫ع1ɴ8ZX-5@I.P)0$3M 0*yJhwT3T\ϝL;sM0DHcHB!HH"hUe !0M`S*E$CZP9D gH ,ZdIhyXKk PR!0+JV٭P-^nSLU\`(~D )1F̵24Mȅg(hGJ 0>rvߠ)Dz= M. Pw}N]@V(z-]w~u*W+:n|IĐ(x/D`IR4l"#BHya U"XNlV[A[2d8b`$Um%>rI;M$j AbZZxGcWPYlB.V!$>7Ou"c+U,%#P'ma uͦ6%H^uaXiDxJ;סY#X_y?]U.j J@l%c1 X!ݭ]%oG׸Ǐuk^eQ)&}IQe UU瞙y9`B]EĜ׌k55`5-%ar?AAtszϏ?.%%aX̒lV; V ņZ Dc Z|PXXHIA͆|$nwcsl},8D"1JܴF<VORbD(z+p;TUh 5cp F B }|UJ8F0D4F0ew qe ;N4v291¡ uVjRѣ;EdeRN u:ha_}U ͊a''˅JO@ U5&pgyYdݺu$"Qu$6\^ fBQv.+Ay|8?%=z-XmNkeA[or SCc s' SP0ws}FqAihhYaW-,q9E.GmAjժp8L^^~:CSSh"*U*kUf56OFF/.+]F8mm-Ap'봵41[(--M|՗Df{FŨ*6ؔ[V3sEvY,1a\. .RWWGn())j=ӣGd|innN׹F kzt؞SSSc~IPͮ4776l ##\B6lvGϞ=YhB!*N/iJXy XzL7%,_AKs---dff3$)m)(ecH5]3utP0 u-ܼGa$:͗q gW՚^n%B,eCd(kWSZZJCCu吙fʕfCC4H7ME1B,@ N]]& '^J $gۼmĮ477 .'YlV+ 79yJ[K;~3@8Xђ%f:^ qCGÙ&éajX1 4C!nѱ,8cf2J  *C1@Uf5KoXTbuUPU܅ › Aj&˲h n=F"$tp,I-(̰+Ar0ЎX@ГJ*#:9 bIyjt//-K =fO30*=>7]IUb1np8҉8@f3 466i%%%(Buu 6MSI$KV{466)**T ø\f}frhBQQSYY.; hmm, hjjJv PVVFAQazH$BYY UUU8N\'Uߟ딇B!sf]u5YZPTdOdlC''f+e(:暦@GSG\墴Kcs36Hz#Ԙ`$Le±0e奸3s= ߏ ì֊neSN  Q k.rsDfيxbSJxU6;,hG<AK^KMӒ݊99jP$-ÕlPҍpp`̪WUX $A/m'EEEdzXVzMff&֭7ϹNaA1,֭$;;'pڵYt)=VaÇS]]McKzbʕx^Z1P "(RXion6Z[p;mx<=YՓKcٽ1H wK,';;6X,ĕIYY9ٓ2Sg%G:V%d{Yov._D=_>IyiEd8!i afwb5=evIc{3V#Xq6TVjr['"ikm#ظ;,3zEkinnNx -(V' ,g`+441#3#bB>b"rJxgSҽ?mAƍ=}{SW[@NN7no†UX,*̢r}\>ah͊?Ux]6VWQ(Q hhh>0Xvm/93,$ZOfhll$/ 6 -ԫV2Hŋ:!C?GJbV\i~8L'!+MOnn.H h#_J0ڂҥK),CX.mmm~.g:xzT&(YjV5yM51Sd3NMNYljFtf?lXN$SPPBs};6J0$?/%F>%df Z^M E[>D  ( م:|wZfw557P\\n#Ѐߊrq:,Xm 16TW[himdd8?K^d՛Wldee`Zٳ;{#͚{m?RÕzn}u⧥nӛJi,=hjBPrL:7NFe`ex=9QUC!orغ=fq׬2/ Gq/pvE9;{\`e?mxX[s.u_1FP=ɣ||nWnA='\y r=ǭܼǏCN) awwy}82~Lde}A7k7䩸Oy3ڍb!ʱ +ɹ n> Oo{xq?W>F3pO'rg#p>[>dBhgeݸl^`[>QB!B!~]nT6Ǧ~dLOPq# uD@T.7([u)_ƷpǷy/ 7\x/^_ 78`ҭpj/sޟ\~{Zռs2y<<>ajQZ|Q 'hckobk#w3\N' _xR^U;KO+`0{)w fS>gr G{`3ӗ3}y(>_h}4 ԅdŝ,@%+l.{/@6ێ;h}:ASbtlZi=_k;u%`傧i% z)K,Cз72!}-gNs/5wrwv:{F]Ob[)쫆38s  L^^7w}w練_~3|ε>dwxbCQ@_nߍr=VЂ, iZ1|c*(+6Fkp=×KN4cϽKv}+(.5 5N>Czx$oR]~PA_ȵ<) rGx 9aM3n̽gp̙wE;U!B!Y{EEŔwfinn={i#;]V׽ W M3ÃB$q.巙"/[w?IQaQyAI])ٖS3q {NӤXRʦauƷ Y<vn(-vև9b _߀$8+ƜKlp ui k'dՒS"bMf%|4v[@=K?KgczZbq{J>BX#)Ԟfox>ZF,s?s43]]mrځ?p ‘ZB~T[!N/Z91t v 7[)9{cPilh7Q0öTǾ0f&'Iwp]mA 9әĝI^~!kWrziye+ӌʿ|66]Nnkwߡla&=@c]+R2VZ|*YYz6Ÿ~2g2fWMdl=Co9CW=d;@<8p[4 AT *_6kE.&iUAU|:`%86gv/D^Ƥsxq? IDATap>=0GFͧ'_bBz<󯸚?ݞh~XO{Ε2B!B!t\ kptDo.dr9+3z bV'+V/9V]msv+nɪ\wWejԭ>ŎPŔxun ?8\=ؖ[L`?OqӲ[VC10-8l+;ţoѷoj/] \I#vB!B!vXEy?Zm܄˵)*!wǟsOɿ^!B!B:?%e[ŰZ;zsrgB!B!Btn<@WqÍ7guznc4K0Z!B!B!6ѴM+Oc)ʖ#e:RB!B!BpBޝ1xѯr +=(9$K(p0ߥ&q'ո8=,pT$8cvQ9n!B!B!ԙm~_6RŴXwQw7H%:.A| ?Axym==N uKp:[DB!B!:[ V 'sp#7C J`ƼMe3$ XDhUX *}on/oUWnK!B!B!v`3~j wsTmNַ(̻A[xo6fg[NEo6Հ^ʨB c A6oV{%B!B!d;u0:E9Ίgچm+9:#0_bpFo.4xyord;04PU@]͌U!wޖB!B!B 6,Sz!d)sot3üya( \f>Nhʏ> W,ΰB0a# AܖB!B!BY~~_w\k]=MEx|)s;R]*/h#ω9毁966xteT8laqiN7)\=B!B!B?:3:^cʱv9S0柛KXB3\`޺R1i4&Kb?PHReE Fom !B!B!NhQЎq:O'5v=iuFy5|{B' /\eCuΔnQCʫUa074m !B!B!Nv s_>mGlV{x Scv]dl+oZEϳ#G8zN'X geC{J xw#B!B!tJywdVeX &=fd9:LK(40WV' !B!B!C;u0ZSfa+B!B!B۩|):m˾f6}2Ӄ6꾍T' (Z׽h[+l"޴J7zlW_6{.Oߋk!B!B?0KxG=+{/b]'p=fyuGg70Q(nGraS7{و% (J4oM{+dBjϓzݣgpGuvS4_ejY漁 ,өZc}s~7Gxr^]wO>Kݡ24y>5“6z[;j__eg5r),J6 vz\:W5~̥ǽͫ`|V/2q3Vlz纃ryٶ=+B!BC@:߿/-'8)]ץ!ko7o*~9pY9υhO JGRSh M ٟݸG13}[<^˫'Wj~6yN8DT`HyYO\^?YX۫8vzAۏO6Kt-4_ix8e1\~^M_{KUܿ a=z1k8/^ϾKo4}g>QY? }(cMs阜i)yGAxle}o2캮7 ]L*DB1ּ]#@j;\}92)UWgٜ&B|?!I͒}ü'gٗ1ѡtMQfuq_!r5Jf\Ѳ8IQÜ:[i2ھ# yVh&x\-SW?" 8xYsnb^Mn&ax`C~ 'W;z~|^3=XŢ!%_%sc3>ZRr9+׮eJ֮Y˚5kYu7R1m'<>z, ;P ZW"T'mn{_9-B!B;y0 \yǷNdybC*Q2Zp {7d2 8!b8vGumcӗxW,h Pqc<7e'>ᤡ'^A'x_nfO{>.AS}$ZzڛlDW'i8rU0|,y] 9p,(7C\+G9<1Zp=sk+yW8k8ۭ]W0,EVYYYc^C{wmd7w׳}ߙ|5NhpsJ+~3Y;oxtCg>>9.26֯pz=ռdx0S,P] ǣWC|Tr\Μ+Sӣ˙>?&_fCY˥n7D/9gѷhӼNޱr«y\<^T΂0!x+o}y8ay[\K}_vF!} ` '<ur h|T&p[G3XH%jO05B!B!_ln+"[ Z(?4XN<2."J֞\~~}%Wv=FrP7:G<g%w:Ny*/X@J?sg]"ryoۙIcXsO=xwr]ռt"\̪ń?1hO*a;3&"qŢ\,eCF7Z ?}1&~a]r8~|9Bn /McFzZˆBe-.i%G[6u[q'S\={:X?tdWs1" 9 9OpT|2ɹ]Ŭ~^e [>AÇpZ{~ +}_d܌iųS޽{\¨#|%1VaZr 'i+ 2YsŔ4r.X*&p7r1]f 1{v\'o\~\ Y(p5Zy -9T@h΃/ݜq?xx5ow|/ξ %rij&w] 1V3#ʼvX޼Gz!ݫuS|&2~*YŤfpRj f3xMV7KW螮\3;?87yk2|Ocr?ree^=HSKB$C8rԯ~@oVַw(ruXs's^Y ?< z*:9F={\}gq^!+=#٧l+E\9Ω?Y3绨k`EJQZU@OnagPz_|9cS,^g˜{L_^.jCGq~!Ӿx6z取Skda* ;b(Àa{q8s 00('ϹNwb;?ړN9`P50h|/Jw[0K>/|Pv|tG? GNa WLmІeD`Tj*89c0o-]BV[{_N⋱u Xwߋ,\CӅӝ v%J'_;p}6s˦-N7sXA*4^Ghd]{.v`Yv9-B!B;y0l}ɋʦxTtOq׏&3\ k{!y?=ʻd*c݂nYE;s_ۻ<~^)GhNj)g/}q>i ~㿽6gq^7517۳C`<ʪGrpbs/1/ҋ>.?+au:ޏ[!>2:-%W3}"x]jø͂nJ8ws]qNi]F7O\Ϭ&B^I7,{uOֽzOa醮gw9-B!B;}0Q14si/+ib}R!-7sh3xl8 *4{y<{9[9u\<889Df޻?%׮cׄ,Z!J8Hs~.9[ vޏ+f.J&O!?gW2"Ə'/ќg /]ˉsp͠\2z?ts#ڶ"3$XoY3H-KO'9Tu,6dFkh,U߼xjnp XAe u)8N]Q7.K{4k'9051>Na]8yA?>] WuJ_ !~_*W<ʄ)[@񮎪ɼqGfbŃpuF; `yсFQ=\sְoY:nA%{Jj-#m2k]ɹٖ~K{?%ߟ;X=;sB뭠YT@g62#u %<agg;vjuYǩd1f2Juz|+ۢĬ/㼂2m?2=B!B?cFqHA{Wc6ezrmW {g_SOs/xK }m9z6YHڏ9㝦:>^2=!C J[αD7bgn(<P+8Kxh|?^^iѶc>y\]/pč8w1]v0K]z&tϱBIW.՝!'k_bh\Q~9>8fv[C17+t& Й"o<|OGz_0fTqjmvaw]1_p&]K׎[Q8o v@Xڿh kyɝǁX%}.H?CʵꂏXqF|˟ qgQ3[[KUyPskrO8^2țfcbh^blAsԆӑ! IDATۯ_*WqظS*G$_Ɓd>,sT9\1e+D0JOlٸv`,]Q7 J@p0r OjڜP~凷#u'o Su b/3?JB7Nu f1ncCskaBY'S"N쑇Y> JgkO:Eπu0=>ѝlN,H3FnCTDr5oK|5Mb_?2:N@uZE\ ,ZTFZtӉFbDil~dMH͏SS漶127;~a,uݸ:]cAyߠq Ku%eF۲ф%1tM]Huؿ~h 6rd$ ,Gݐ.?zרoŽNJnO08Xwq`bpȃ}g?.HѸPDYIe)Yg27yQY#dh("e=OfX#t!lʓowC!sqGzDW4{Op9 :{A썘8yRqᇡ<'L\.f XxpGNĉ4^.>5EZÿl`,W/ y|_:ə*LRWiwC{v,;-c=r85$fNV})>ڶE+d{(u_cz?4a%Rn9'*pf썾y8NOZCYCUԂxcv,Kw9iUz(Tawd5v潱/й]zvFN*|dxy*2xcT&6o{Q^|u:عq2o.Ƶ5+|Ͷ% E߯ܔl5q] vswѧ'm*Rԩڒ T{4.q|7gm{v1C1>E;)^Mδη/u_[}S<"=eLo;o;P,>mt uJP~+\z\G^#_oV:?.H% Sq2f7~o~p*+V'%W$}|S@AvMjOUJ993'4Y7m$\Rf0Zz],O~T5KR%t O=M w/95,LHڝ)s=M'<Ԗ5OѦZ%BQmԖ?mPџ浍5e6C.tsvcy,d0|˶i/]#埡Qښ猤U3֌׶#wql"ckgQ4ALxplЙc+"D;F\Sy*Ҟjmgs"7ӑZuppi~^L~xx-TG<ْrݬ8^p T $gnӟÜ$\9έm& kWf+X'v.,~'7cw3L(RqyYg1{ V2l5y)=WBqKcɯ(όgtOv2'bbD̳qzn'jδ'(f^""֊iyyZ Obk/оw{_exoQ⫖=գ(f43T%+ls:Ri=Xt rjD@"NNCEDDDDDDDD{Le4`LܪgE7!QTOD;ʢGбNYr[~ _å[2ucNppۏ|7# EJOžeoӽvA\wN"y4L٩3z=S!nD7/ JՠU^4uMq\\ 1!v`&q7jF͡ ˸dXcrQtY*V@xKZëߥG06Bhԣ4 `q:e8l;i&G4m4jW"qjR+Pdmv v{4,mgKvW)jU]?E{vL"""""""""2`$ы4|99 Nՠq]BҾs?C"و>.У>Vω3^>wn|t^Bۘ&s>n܉ݽ]ha buP`=إ>v Go+baIapj V}WbN〛=ػ2;k5]Mi֏*C^WUu?9Ьe;^hD¾P*]RtǺWR35%8'(ﺂj>y N9wfi5`$b$w+Eֲ},k62ϼʑNؽdglY:~9L9R`|8#cw{Ϲb.IZDDDDDDDDDoym̢wYћ$!y}+_2RQCM gwpj b34 kyfj$d gYm |ƅ,lY?)ca|sɧu-@2 tl,{g/&Hp#Ĵfʛ;/;?Σy Dw^ћ;Y9O慸-Υ{c8;3q X.]"-hvXQ/eI}K. 9CyjXʩ_H?ЍkXZv5yё@lT.p9l% ZOϰh?JR7` ŧ?:%CCRKx= T`>_{rr L_AWKlB&2䋺t*]<'zf*ze`?XO}FQt؈*% Fʅyeόotp_rs?`3v_/25ӨӶ iC8~Jņ^X6#d4y`mgZ'ˎg| )Xn# AhTx_bCGO{ݱguNf^搥!ӗORoՈ7Y9^fZS q$0c6FEuɲ -ߨinٴM[pd{rZLn [z2W#Itq/ l*t?䮯dWԊ~ߙ}cGNrյ8UJjE[C-PvKH}~2עႇAAxas`4?/ :Z+)Y phLgN@!o WF`"(Obh쓋bOAtctntǕMH縇DpcC/P-""""""""""Nad:""""""""""FHS-""""""""""Nad:""""""""""FHS-""""""""""Nad:""""""""""FHS-""""""""""NaxwloHf?8/e;ż_UTo[2i!Z=*zex>V7ԢOiW5F2rml~eC77=N"3C~[Y9^7chT_nɡIoY$Ue d1hLי*YY3kgp9vk Ge\1>8wzgE3kgOsZ;-ټk,Xޫ8.ob_!tV/<)ݽ3y,b(" v;u#OQƂ rX^Z˳P+G`M>w{s,zm˼̗n;ofSxg`YhY7uqDBxeY/KH ݯ lMi;1ҹk23ɲt5'dѳޔ E䴹0=-\Nt<4)|S6s8"2m.lEi ~~S|΀3~~JY z+4| ZZy}+ ;xgK'35F; 9P-"""""""""PJrrP8;uCvjl4E-f=Kn[B<^ۧ[V-ޠV 1Sʎ 9w1|G[E,^!dKKQvp-‹_LUm(ZYULz~gB1foć0#3V/eG$?OT ”"|ڃKy)Jtx:پ͎.Q:{ N|w3,{Ge2{QJj"^y{',QY8| uT3XŤ݋3h@roM/`k/RB(/U/Yw/5xaSqӈF]sVރEDDDDDDDDDa+o! {ܬ~5⛯00c&WO>Abz۱&ēH*C6d@W3z(m*cHv>jo ?IdK1`Zdp͂ Oڂt˛=yҽyuƒ}f_9[2kavoKeה?fApm{dvV5Hktן錏3A+Oq q{?g')3'r|,{e;== eݵ{uK:{kʯ^B镼ձJWA)v5=\)"""""""""ƩYvZ=OggGTt9ż_UTo KsQ#;緥7Ý[/tOo̟DT/{*[ۇ||{:!UzP@roGG)m?mOa5LUi:^Zނ CgWYRc\؝fpvGuIqh{zs=FC6jCf$E&J" 8q} ӌxI;u!װt gѻw\NmN2S?sG鎷qt Fԕ-ds/wiF"{ӗ{}H1{Ux7$f@lݐEF0eYȗjnkiƗ{DV~{cZP GvUj|ԒЛ1eTޞcǘh^ &{#k~Rq;9m.L|nZb.v:n!>}_b4xɛꃯ͟xgW/Ⱥvރ R1-)=mQMxn>H66دU.EJ^l b\NnG;%bw0$k08ʡ6겑e`3W}tFdӥPZ7pt<"[ROR5x/;W0ZDDDDDDDDqq0Zu&s{R b.b ![KQ?pvmg`vT `qɺ;47lift#,W䡠p Ng=;cdns0\iise EDDDDDDDD[d'seVrJJ.``02SgS/P[bgHf1 :3? fBP7`+p)Е O?Y_d)T@ >'0}AÞߦ6"""""""""Y9_O.sIϭ0P}g4O>0۹:ڇϣl EDDDDDDDD+~ٲNW7Uy(s:1sCJ2tHS-""""""""""GoSedGRm[iEDDDDDDDDDQed2Z=EDDDDDDDDDRed2Z=EDDDDDDDDDSed:""""""""""FHS-""""""""""Nad:""""""""""FHS-""""""""""Nad:""""""""""FHS-""""""""""Nad:""""""""""FHS-""""""""""Nad:""""""""""FHS-""""""""""Nad:""""""""""FH3'1ӈHv#=*EDDDDDDDDD$+ѷ<]DDDDDDDDDǥs*EDDDDDDDDD$ѷ<.I֩B""""""""""Ie-;w:EDDDDDDDDDsʗ7<%J""""""""""FHS-""""""""""Na8|!}vZr5֞]+OFCrIFc1Iƍ5l88rQ>b47|akּ7[et8/-9LqO0h".7gu!nQ6~ L?[DCIuXIS:=~XJ:g}8w|/e|F>. ; 8 s"Vsz 9͘#KǸ`~0]PP|R%њq7p3_QqQq78 qGa& W.3qM&-Ada]m<y ;p#83| np+rE.B $TC &[Ḻ o~Xb=yF2E}uΗ,FvZi}.n33Zwpn^]dp9g*+Ñi+c3nLEp Ym/Q1_4waRxRĩeP땣p@G\ipoźmF-H<}+P#aw{6ϞDEw}k9liI Q`S$9E\iI0ZK1,^{?dU Fs=鸄)h\k D +'siHyОnfNoS/VkE dE\ }yΟU;na?J9Һ׮TQX5b؟ OVMl˷$+jCg1gBNNgRk0<(8!r>$8sS5 . `B97{ aC_!.֪hOY+aN EDDDDDDDe_})88;{B|HtT% Õ5 88m;MI[b >71gn z#>fOUQb]*}˖f1xE+t%o?=:Fb ̮;spd^{W&oCFF_[.;&E0ϗ/b76=)lpsa(/mلWJUΔ'$FEϳ-z2ei82|l)ׂ?}rb?՛~Ƿu+||g8c' ֓Dq ??B|!LD̍@~zWmEP:=z.u7kmgLwQUkLO@BBIAH`굠ر.b 6A@.^C%g&3sGL2^|9e;;tl!ۍ!ĺVeMd¦n[hzGN-ak>'ͨC<ߢeILcx@v=Eæ^KeFBk=ś3mu'ffPBk̳c01Яg'?bk%z>;>^5  Q] kXOdlaY_f8#gu4sd}ĩ07NVԗ~\yk@ߡtX{c^0ƴmVSfP0̀&FjACK\1$F gDj*pp"%ڕh>{t{)Lkt` iFA5)^!B!Bq:-jjdHTP @!0{D Ⱦr㫮~p2OM۹o@S\.<>fml[9 S&=DcJ`mL~IPi@Z7 %}U\)O ̟v_`tNz?Eeɼiy\׳|iG;5Ϝb:ƦmtgɘsV=n>wx¼̍S^ 6b(FHx,MeQ5k0@%,^]KgS,XМIt&ztHŌI^Gi}m:{/"cyH,}4km35bܡ<ۀyw5I7K<0Jmj[;tmt7:Kό;gʞ̽sdܗ1>[q.;-~)AU5Z o {r^bnAL|hDXa.T*hۜ:i٩Q1DzCO1Hw'?$lp[k<;a\I ;}Z3L#l@Sp{6QOc6*. Ǎ%O9؛=y84?@BVP!j y/oy͹ihhGؑlG:h w|w$Z>@'#IFN5uJX;_WHbɌyL6cyS'jw^4 ̜x<5?{F@K%& CkRcb¢G=ZжnkB!B! Fk@0EChHOPEȜ)7B!A5iS(-y 3ɗφB{ S{;L v ,}9 /o!VVGs7աg}_ Ͱ$4AϞ/3ޞQ W F* EdǨ0`dAQ ?sViN<(+u!VEQ1(x:vHNvIY==MCk2=ô\'{݃G|{ufxxok&T4\ΣU4>5M i9qݡc C)6DR,=43۞a'BG(pP oxܸ ؂oի|BJtehBJ@ ~N_ ePשgMa ֊_$2+2iin\US XsT8R+:B!B!>.`|gJ*d/Nv'`{'0.UWh*xtȸ\p O\PX{՚=˿?d<4d}k^ϑ2*K/ĵ扙Jnra+qd2_m63$%'uE 1L3&7YiKXR/p?ҞMO]'qguDSWtK;K<8ɥW4^U]G 8(ˑ$Ԯ= W)ٸ4p9#Mչ}q~I2wZ ss(GW.I $X9cg 䱙b2u0cӶWHO7Ojwi:3.ߎj beQ!E ʮ!o1}|MӫQЁʗ(;rRA֊_͇)t XgԹ ] {pTR7dP:#tMACG*/B!B!۸X̺'B88p@}6Hl~}8xVKxVݨIuLQKܱt vCg X'> I?Ry.ޝ$ńܒAxFr3|x4S逎)tgaeGdEQPpq Lݍ=&Ӊ z~غ8ڀA$Tl\>nE#Ň Ndf ]5SZl&~yeB!B!+W1;zQ`HXp7P@o 0Ȏ9 )^9m^C׼{&o. H‚U:w0ه@_z qթAQ0ؿ]/`?w-)$iQtkځSNtgUߦ("uʽm$LkΜϏt]:[JBIfNQxfyx!3/S\2>^9sQ>=MM׸ݼa3ҝzx&\FNGx᷉fqͧĉv s$pѿdOO Vaj_<i}'-`zwURKZ'oyh:F%PAa3c+cj 6zu}&gAmSݰvi:y%]3 ½11t}ē-f+ y,F+ ؛}l6k*͎=x}[?M^!B!Bqٻ68PgkaWW8x`A@<^u#@Ϡƫ%:‘Gs!i\G!Aם|mhjSWnPlGcW"&̙gph^c_mˆh',0uN 3my)p+{ f5utZN{ib#C^LfevoY3}8⑵cF-k@%(N6JCW\zUWPUIh֟hzY'?SuSY+2]6`h8~q쵭; (Z%+|}cO7Og1Z=mFR;1|qBHμ\{MFǟWݶ6=&"c1Xn0O^XW>20S'}*"?ǂj_Odu?8ZDc@1Cׅ:VQ+"rr%1DWڽWV>q6|B!B!+ htHm)j!(:h~X' _]o 4{;kV\/8YJ}E;/(d?ƽ`l˧VnІO OW{+meӁrbƱlG $x(t<<~:"<$#F6'aᑞw:|lZ~ `[Etkrbd ¦zV ^0-z_d\n,~u +,ݖ0)ǎɿeOYM|5N؝uנ9/ddQ5`"Pjp[vWPճٵe6g&'m 3MDdtf*GO,q\#!R]Rn٨j\k4j=P VVA/ =C@t-L{L-\0 "M *,.Jta!jRϟGCaa2.a,ϸEn̶8jX,?}䮵d>]ȯyB;c}-/2ݍB!B!mQx/ւ_мEKEEQ2?Z$kk(((VFW+PߠQ\)rU?kUp :?A l?FE !?Qkըn(E5":WbK\u@ԛ1&nŦ ?}㤵Yyv!B!B񗹬Bg#vWRGqYsB!B! B!B!B $-B!B!/?Mq6B!B!BdeB!B!BteS^!B!B!1MJVF !B!B!I0Z!B!B!_NB!B!B!rB!B!B`B!B!BB!B!B$-B!B!/ F!B!B!Pu.+svاy\gݤ,Ng¸FX'zˬ W饋zF_)&rg8`]}~LR +@hKn2vm⠹0Tָ|~ >/ ~!B!B!o;GL<|[hד~ORMy8. $ƚ iR9Kr='6>Sm k>45mɒ FD踲OqL!nw^5;Av<;>QPELq% D !B!B!f-Z9a/*?Y8xsi+jZ!)pQ&hQr4I'9U:m!`gㄮ j"߱>DTTM~`MVØנQ&4mژ&KO@F{yꡱ MSZ7K[tܹ8z^K[ߠH˯Τx-ǐ`ppt~ƣIy+݀5Ȋ,04wվ5Qm<ں0`68 qw5 &wcHz!B!B!˧נ1Yh|ϧ⨸wI nIhFԧO }cn1Pcyn$ߎUwaW y:B!B!7HӡԠ;HGF2(7Ynen!sz۴tz< s+3f.؊y+'yQ\>ͣ7h΃;&-7|Du\Qvv 7/J['AbϽrj~3GMufI薵0 ɜw'y]kt6uK w*q9z~Ȑg>z+B&wDtZ3fB[&xW˺P&m$Ewt 0e3Y,k:+o(9[?IMgє9??7ZgikcѮu3p)3 !B!B!Ii:+:/}_n&'fm-C{Ql cӎS6b، ϓ,pշd\z>Y~<Jo_B*(JfɒTM̹4ua! jSBHD˞}c(,exs50.lz-U m&Ң-W.,(B7[1yK9ł/vPsP[zCJ:fS:LaXUB!B!B"6kѺEE|` u}l<"Y }  Z%b 1N֬C͚uؘ݀׌nDǧb )s=}ϸwC?Z޹IUb& k3Ou;w̤,Eٶhn UB!B!ĹHesojLdL?m_P*1e.~}0wv )Hu,w̓ml>fڴ3^wFj$MsLoi4p0`m+p8\~eRS%Jx3pWYwAy1G ˥dcc:-_N hyli^^[qcI!B!B!(^jկ~ WnhNk3ٵk> o!c;\xyou'[* /Ԡ\7Y ׊嵕hޮFghLUPġ/_,S=}-SG`:sMG"m%Eg z_@0}}̀)Lp B!B!Bpyk|=\-|dM֞= =UiYqE2𞯘< 7{5d % PpW~\|W5Z(Hae6Ot'F>˾Tr7I z‷LGF857fR6ffҽ͡)AB!B!$U.qu+9xzYq賬 ;w'myW'}?@-9ef=GJ";o9F ,9֝pl<Gmۿىa̯IL^RzXh0z=K۳µ\}<5o&|߱O^]M% y nlv^B!B!?KYGȺM&1uW_s]+T|._3ݐ Oܹ]S攙\: )歽$^|3~w~KusV1Tu;+,YKy3{HN 3frB8;׋رg æϠrSpO.vZN)_ɏݟ_/&B!B.`aYb<~\lu krQ/ܟ߇RWr/ z2xng4_ 1Kc~ vU@ľ%t]|-xY$~S%|Fq,p5A'ɮV~]Ǹͭ c~OU?W3xoFf'puCa\![qGB&2Rχk}?;y5=&'19O _ݬx+ cO_S_@FHLq?c~vU @whˆ~"Ŧn\fcDWni"xw@:~Ŧ15(ԯUvo]/~MSTЛ8ßQ5wC#Z3:[ 5hoձЀLMkZ.߭ڇz?u?MǪaǕhj/Sl h^%ۄB!Be2^G݃mXN.G88Vĩ<ɔ$~:j. NOÞftT|5]y>ti?4>NV f=4NVX&57zgr8@c:\iS|:yi8IZɪFG~:+O{lKfEzIKU4S>atc )aSlwS~g8|Cg;wSȶ|˳(0=@sT* ?;d ׹̳*<ŏaWbZI l $X+®1m90Y ]^STO;V28y:6W{=:`ە-b`l+ IIONDv+_s9|OA"rs*53lD0$xmY>" O2==E_r捉8BwX֪/8AXQ.Z5瓎k/3+Εr"mޙB!B!82^bܲ?{ #gWi7t/53N2ahJ`֍Bqnv5уFnR] X3~"K*x9Qˍ3WRlXȓ#BZIߺfצyt]M0̹&/5Μ߾+o%A"uvn+\;lޟ5bI~37ZYܡ!?9KnFMΩ i;gBñrH`\^n;|( Ƙ\Dy;ٮiw(&Y{3k9 09?⹘Ƽ,3{?ZL{M4gѶPtAPZPRkԌu[Xqǰ#]J:jgnRZq =CsMNɨ6(Ŏγ氯 mKia{lU9Ul^ɪ@Fԧo\>'e3god5W6{S͹Ut|Z6&).Z@UԊ;eT!t :LX6zZu.YGsگ> \L^GD7Z1T+ZxUB!B!yq0D~ױ,hWiŦRl.SpyW9cJઁ 6qwB0yG{WY' V)~ X'z-ƈ+ olԆUW`rfN:@0LGZb.=tG}l;T/sw[L!B!BwYʄ 7b Nq%` Pո>me"qCzEW~\9G2-hߪ5075L&ݩbkYFpG`6SvSHqO9hм)-}Šj׎kU ~5٤-P- 2rLo}<݂fNl*իWHbk:D8\Ess;^ѤFm"q椳1y.uJtf\/0_EU}Nuo5bB>'۫yRنc0K (6k;Eѵn Yyث1V#U.g^',S,szW=1gvUh#4mUO.kwKZ|g/6 {k sq?JpO fъZ9Ti,1 ۋjE)&@1Q/\Q)3?ӦpZ_1!ʹLx(|WsB GH4W~w4[#)tZ;}ϡjo|{:q3O\L~[_(ZR;`K ̸عe>=f޸^T]>^XШ.JG9E `LxbT-hRyW`4:J+s0TQɃyސɶġk;dY 3iZVl'ڐi{6`b8(,-"m ¤U_x\DN~+})dl=[&c sŇ*MߙB!B!8]0]GGXД.|.EU1cA *RPU*lQv|pt[SOⓍD\7jKWTd?xh7J(j0wj~ɾHrRIΪ|yS_UŠ{ʎa*|ݧ tJzӟU̕l* Vz.J&ZhejWzŚ,g)]e,fc9YIe5B6J%yq~ 1 ƻVJ5斯9PyL'3'~Sv)wSy+a(XТ+3[+8["H*UA-~5LP2( FFvCiyqjç>6Wʺw3B!B?M?Ůqݝ NqAט@vиEm">_U]hi: UCFW7(^۔`rTSRP[,7NJe<~\wx/6Yc,ts;&#CR||S>[p \gX>wSxyVFي˘/ 4q{4%.vI栫]՚CP\t~U t>Yե hθU HӊS0CG}XWO8VU^sRŜ4G0$'lp椰WGv2+]5xs#7!L>N˷[7Ҧu$9ϭƳ7aU5G';+t|[&㎩Gsb~G?**_aʲxoh_U.VwB!BwU&ܹ)leLrZ6o.Lyde 6.\3*(S|]-^W:_U]z<KyT!{Wk fkSȸofsߞTR:΂LBxc6[Ң x9Tѻe=[poK5G⿚n^T58T"_3Ekт5T636&F/Zăi$el_OjvCUΕ*>#U=Šsb /9ۋW&>T#}%e1c**1iYōUժT9'Faߊ?x5)$^X-nd2=1lW!h'o_s:UUfP m5Yzu";rsXkq C}cwdeƖĭܶ2mqQ~Q!ཱི?[Ou)*ޙB!B!82Oӡ] 7na_$:=skb ML+k z0CVtR,Z12PcxM?¼ͯ6I?Qw:}pdj1S`mÉtnzP Dk>VQ U- (SXgZQY|n *'.ҍ ku 3.ӞjJTp0RL4 U,D]k[P|u}i)MhKɿ |?9Oo7+?K$^oػ㮱e?+N#>O?]~9wGsOk,ΟRǏh_o61o9|yivw'VAAAAxPFg~?IIbx[#B򊵽+O~[DŽ;ֹֿ)VyȆa'昼_}w~WrpOg/7ה?o<;|Ko?T'?>6rb?~q`Uڣ6+M{x/7^P\55e0#wtlaux_?n[Gu+wز8Z~ +,GA|/} n    \c5w~4J?i> 7l6om_+ ?J   CT1b'U 鏞/|?l-   gh1pV GG}l_AAA3/WAAAAA^AAAAA/AAAAA/AAAAA/AAAAA/AAAAA/AAAAA/AAAAA/AAAAA/AAAAA/AAAAA/AAAAA/AAAAA/AAAAA/nmMAMAAAAFOPJ.Zη>Nofs/(" KCp>t*Bf֚ 5ھًK.^n֏*zՖ/_b*-j{ET"nC /?aٰn1;S/}, R>V,i\_T1FQ`Msn#>8XeQJa!HĘ91FQ:A0 8l6}''O0P~!aj:V] ]ὧ:(>{vbvZzgϞ1 1x{OXkSz(hE0 t]7M>3V?eЩOB4n"Ez(,ڤ6MBk}H!)^Q*g'` KUUXkDkzˋc MӤbӽtϽ/p.EA]:>&?9@dp}if{?t.Ҷ-O>ZK#gsιkIa֚~h!t݋5apc>~)Z[M}Jτ, N' ]O8:ٟ#   ec'RY|㶣(7nv$V mG1 ĝ5`>`kA)(eʂ'$u#_k= sH c%eYH9GG)C,FAnQY %ƈIBe](hf&_oa3|8>(0lk-_i?w7Io}[|'<~eYN}]ƘI˜k !x?~ѳ3ryy/PXpzzwhiv,vg]kr0tTUb0]z:NxzmLyLXk'y~q?˙$xm=m޾<8{AT|j֚vHk !0 (PZ5g~q8&   6Ƌǘ Kߴuvbu( T-1"0L֨@ĨpCro6}]JzEW IDAT9mcC8ƀI$ڏH"Z'ha+\ 8:Z5ĈR;3M"z4]ف)BOQl6,K>s4M?ˋ/XY.e98uɥ=*9jG9f-kפ1}zU{c{{I&1,lnvyc',υᛥñ#߇gcz(η0k\o?3: 3}q< LjmL;mOpɇ{%b    o'oRy]acbԾX.8u\c?7n"FPa0}Nfs)i(*F3H5aav~@qzm>$-->smYSUG]1^}H.Z0Yb>sn؝2={F], קu͓'O[D]Xb,lP<)9GUUc~Lns:|g}AX) aъc(u=:$0ў]0 } _3! ǣ=~ (*mv~!aTRŽ>H,Ru"<AAAAb1o ĺyUEWq-Zk k4ک)WzlY')gyWmXI9,H"1me 0 ~'Tj3<@ΏWqv?r\ i(0 80#3 9zKN"0 SVb`?NQmz)GQ CEA:ʲĹ5Պahc ?r FZkʐ$"'a@Vdz2vO㮉{WiM繭ߑ뺍C1=}><\R6/ AAAAxyh"9Ww4.;2;/!;2!~89=(ꝨrqcRs(xygEŅwbtr: h* wS}ק\^m E?@frg8]cE_7*s 푅aesEc4z F{Ҋ $P-={JEA׵<6EeIQ,I.˒/^PZNNNh-(0FM|ڶk%咮xӘj H91p((lj}|N9wD~N.Y NFqƱ39B~p`yƝK@0#NW#:4'PL}~I9yLAAAA7^䰼͙8wZ|U(˒;B18* A\x7ED(/_n޹o?&*bP)(g-hMJ@C;Fg(Bx|S=]MoێQ ;/T\oPe ԋ#w{7sCUU,}GquuEY_'?Ǐ8VO'''c˱>{ݏ8v{_䜋͇89@o ?'v$vyr$*cibEb>l\dM(8}{9    | CQfw~Q평wEdx^orLONI`&51BYpI)ZEh1( |(TӶ,98.FO>}Rrzz6h< jrY_hۖx9MLǏsrڹ_#;N↋ł !lPm2+EHd'n|&w}41b.ޛ&qvj1OZc_υYϲuڋ0N_aSS)=/}CA|ޗYܞAAAAVx1&98ڡsݫ>7m*gZC(HJq;BhAiV C{;V|$͋)FN u$zd0&M)5zT8ϘcTq={rl1ijQ,j./6XS2 i4MC]cuOߏq% ʲ{aHױ\.(5t]C]ef!Ç(]IL}bi6Xk)R/5^<">N.>'?#y{b_=p]DkK8|.w ~pG9z>ncY7    /Fs94}wqL`;zApcF ufyFYX0@!4vh!Z.(1Q8QMlwDЅ&x~HΠ0F$fwJ0cH"Q/՘Fk Y)(dZbv¶RAD0EmK yRF .k =XlZV""RlBQѵ-18Rێmbg8'qu,K=];Z-h TP{S~wyIQB3 *`L9xSvhcdDyNrbmeGk(\pƜoBrNS4 _Me 0+?%Z+"Z;GhS@{ *>PZ;劣St]Z͋jD]tG` #ךӵygn1xȶ09 OFHQFӴ[AAAAƋsEtsߡ}s19-FgQ0NG֚Z8{OQXk5XwSFs@ CN'HnFL·4 ~LŪFQ> 9c_SQ Às'i kb먫s5v /^^܆*Qa*k|sua<\]׍B"929Y!Ǖ$xvW税°19SZkyyy7Ӏs=MӤh?ar 3_w{1}i[zp[wlAs[[/t<|ozo&Gr:>tx?n8AAAAxRs1u/#>sѷoIıZ*fCQ-j =̮yl1frZk)| &7t:0n_SP~|rƨ@5#~eE$!aع(;rDGt5eYu0pWEruz}ʏ}|k_ùwѣ'<}~pPOW,W+b9G۶ePc˻ =*aЬŇ|dd('ڱ' K?|6eFo‚8ޏM5ߗb7 0N6,)vg&F`Iv    o#o}SL|/t6?1,Wy-bT˜ҁaعxa}WubOXBs;f1;?@Q,h/Ykcfq h dY67mr4'}sz+b<|ϟ{7 ќX|yyAwx^5Ð@[}ʝ,J$>zG7o=l,&MwzR>#!Ʃm=#;v&ƒr؊<+ٞOg#].>^ôL$ԟ)Fd_ο HWy=!   6Ƌp]չj0;#7Puܗu#}'HU[naH꺦Zb@V c cX/]ΣU* zzjrIǰ(L\ns~UsAgހ9w?}jr?"Lb*>}J4|Q=ejo4(jaNEr =mWO{g c b]c]al;9REjhbwIx(4}Iat~eٶ-HQTXS2 JiU*WVzo y}b\}M89&Nu!{|؏Oöř`}ώsNN;A:Oθ0 =8Ue$u\tqAAAA/_ 1n#8߸}qxu$r1oLkM])f# n1csSf.;obm@95~fLϷKch2?Zϊ&3{茾AAAAtUj\m??֮j5 3DM˗/i.%QZkH.L}D~l=Cgisž |skn."V+ڶ9r,KV+:4Kmuѥ4MvMY3r-5mmж^q1ZRtq~~γghۖ(ŋusX.|v;m[*Yt߿C~>6rLd=}[ ymp츯_YsV><4woW    x7Ẉu]Nnosl,6sL+!tBoapKPJᜣ(Kmujcmڋ=*C̝Ix~C}M1Z2IgruZk b9W:1Fڶrc 1]}/zݻkrIm M0tg/.Bk{B'Z)Xj)lw~Jפx/. 9Ң=y)_z*6M.]cًu햫6Nq;EӧOyηy1GH\i' r$J˼XDiYvcµkMa:ncknos69fǹ v{ eYN(*?X; Z9֣,Kg\t,@' esvRdhZ=(l=M2ƂD=eLvMӰ^.0 '%{./.Ea C*ؘ0WW[?IUUXb9mI$a1UUJU磏>K 6, cCBv,'suur}`^b{֊=, BpX|_RQ? 3s|(Rv&q[nܸwlk:_vS^۱bHDAAAmL$f?aXN,gűPb2Ma=Pʐ$Zf4HQt}jj#Mkcrs1<m:!FS30LQk0( apԫQ4m,FAіXm| R0ɋhBTb4eU%״frNOPkѣ@\.wM<"PUEr$ƯW#;8ǹ$7:<rSV N\.Z6{Zqu\߹YϞ}BYF>=BӇH7EQrݠ+)$Dv`%FY3 媦1݇?L.EM^Upqt{cVͲѤ :=QG">eHJToO4v*hyJ@1d\sуBCkޑP! muJbtTwԊ"MbVT4>C(@>. L%iϩQ!Rt_QJC:#Svk֌RE   6ƋљWɂfߡ3_fmMk((wy:RK]<~a EŞ IDAT!,H].30psV0 M!b(=99>UDu?Q֒=ZKQb&sx&vnvO !ZKUULoItna$j7MCc5VQ߿,3^ Q:^_]nPJZ/meYEQLsH/%!%1 ~rrM|yA4Vˠ&ܵ^A=s|X\L| ۜ7q8M:?دڪcb    o+o}=_z1nE'a2/Z~']Cc½Hs|c{~|yAAAAxxcl>&" lǖt웎y~OߥT}n]֬;kXa VXS\L5ƠԜ }OaNf➂(Ejp $izNq!?;'i~*IP$A5  ˳gπzZUl[Զbrзl[l6 SL5u]r{oz)jWeYNBkQü:5?;?tΏ>I66n-]a~w-IC^t=oov뻽I>vl"    o;o}LDmN0{?~&W AZ??o,[x|?;oʢU$!6%V%΍NexhݦaF2: M7 E4BA=r1 IM`_2zu1mCv7+)B|a?`ж!QNl۶aB~e]^qvvDih{|駬O8==O>!8 !ж-UU4 ,K\Y΂`֓v[–i0 ԋB`ʲ}9;7@ $*p㻖"} 咶ml6hj<4\,c|ދ(!x̕B : űd{{2od|<p<&䉇\57F\qU~qḍ4b=SA/Ey#   qwe1? >su(Snw~{B`tM^f "L}߳X,jtxvf2SL8*"$oXLq)&#TTDk5%aw} [퉧or#3&\=֖ۘo{3}3oOzABĠvm5%j1.d]s~e    _vxgk^L\>&"u-B3:BH˒[&g3\Iܢ}߳^Qu]S\AEV):9dM$FAjߡ|rq΅='a.pX OOLj1(u=a)'ٹH9^+!GhM4򒓓 0ExnxГЯ4ڳS60t#M"B?3?pzz87P0hP&Nn̓14 l#Fœ'/(!vL͢tB$FQ׎3߇9F$UERZkXwΏ&%?y'@ q*θ{/ 5{rk^dqMz!gyǘrqf_?󵥱s    -bM$_X!Uڱ/&Az\n*GHyI .NbJk>}z|uuEUU }0o{q"mAw'B}Y2SCPsC+rfn(%wu)#%btl6\^^0Vtc̔}4Ctj^V=ڂXܻwy">I[!>躎(Rłf3:;ׅ裟]{dž16!cj5?82{懼m(:sQ|btɭ4It1-r<*HAAAmK!Fj<.8&k35(Vɝ}J HڒI֓(ѭk'a8-g14mQI !<}Al~=M駏x޻4M3ƞI ʰ'Ybmjtn?c;8O$;.mV5eQg{JqA. 섫 +t]C/^RU%}ܭnK@۶(E0|⪪om5=g:!rgbϠ&Altr{?O=<<(9w aJq AO"֚@v7ObIݳѳp,_0x '=IQ1B לƓszo)_iSQgk2Ap(f    btZQ@˅!e:Eg09-1nʾ*kL̵&+3 QcpTu-Z[%/0:bBLU:at]*W1s1$7=F)!b.V\;BϢ^ǂvYx|H)J!QQT~,֧!@h5FMs([C *1e9p> n#@S|GY-hE xa_^,99]=%~nrA۶wvV'-EZoFLyuV,꒪nQVkX,lxyq{@yzw0Apj$ jw1 PX3w'UtJB*0JZ[\ר18 \(Te|7K#Xc E\رMA'bN"{@~_#>x<asbD~SOs|hPFG規!`ƾQ6O"Rs=}/R֤pEKAAAmK Fsmwۤ9Y:<盖cծv~,c bȁ,i. YSMP$eɦRU}0ڢPnk-Fiً`z.tA¶J f]$v]7D~ػ>?~VZKacARDPV5[ұemV&E0 Xk9??SU$V+a H]\]]am{a n6\u)jOԟ{bb,(,mpr.+)9Gsw,K'??F_E)t8P'9OX%0Ʀ$Tųzǜ]!cCDt<5V%4x7:3`S~HbQфpBQ)!<0r}*Bӟ`T:jO.iI;j 31 +L[Q&(k0*]wxw8ҽKZ F+AAAA/-o:XU9tD޶u,|&ρl6$a(˒af:NOOǂz6eXL.§ugc w%T۔ :,o:_{reEQ%于UkMe ]&Q{?n\,,K,Ku]'qZ|y^uYM"s) $4hŒ+R,2f}hi[Bkfa?~rd۵ԛ OGqrrk gkv==nz3ǬV8sJDxtP}<8Qq6a!ľW!9#7G[+[<3z4NH7F%]T̑YQRٳg,a:f%8=Zk8ѣ"w!Oٯ7ϭ    /F$. ݴ<ƱbzQ}`YDS5J$9?ˢkQy,.]`m9i n`nƘP͐Ƀlr'AZKNNc؉|ى퇞Cb,1&$G?tD?ĹaIMI&pg^OmZӶ-kSt͆R$r>}:]ׁJ9V+-ާTerDc:(ƻ͆ %/_vTUy![6 |VﱨKm/p~b> VM$<ǘ,* Vk~ h!sZ'w 8PCEV vt1(Ӥx?X|GWrD]hUsݧ1F :HQ#zQBv$qRtct@فcrP])O!&1@Xpni"F3FtIQ CD^LkRLJc@5'ѩtn?3ZAAA/\9#ujjqiۻ]qscS%<!^7BoU/Yל;;ڶm[lۆ~c6?(=F,Řn)qCrf'V7]|fcPBL^@b$z]Qv7&v~(}t%|΃VEATU5f7h3FSf\^n("E(){=zXz5 Ur:#3MP?$lv_m2jӧOw?{o+K={Lg;T5")J"Z5АӪz#@H Aк-! @@REʬw8c n s8ܬ${7yfxk5="lpq e>ʛV``$q<}6Ft͑'R5Crيxc<06BaD6PY Yd@&uR̀)QY76R3$s+976)b%b@;H)?Yİ\)***************zKoF0|? y&CӘmT/6̍1 Iʿfx7JWG~sa]ld&s~VrH"Q`lc9R;hkL~pjl$dsbuT^w]i,'"3Ϙus>}:'s\-MCJ O("D tm W\|ٳgt}O__7џġo F99YҶ-W߂{5Jpq,ǀ|Z4bȦ)L=Ԍ󸎊"`G‥P`~rD#.jZ`Țc$CΦ؇wb:D3.cDf~6/=Vf=RNa#9oE %P,4>ODOUgZ8y) VX٣nR&$Z&E5BĘ|?nJModžO?1~ZU" [HzV\.8!HNOF1&y RUմ}VOks":na۱\1gg4Mve>sww~?/^>g^c斺iӧO1DC崶7S L#zd7yI"#od ['81d|۾vǔíE Ɇ}zy}72y<>=/ܯ3.ρD}w왹޴֒b6G#:)e}h}QCӹ%37TqFS6SpovA-|FS(!'"> R$IG;n'"@Z;$m{| wox)l[6~>Lim[crxZMf|1Vt:~C k픐>4Л #dn#Ȼ>$'?5}9@p|WWWY3~?G?>|}~aCOJ̱!8?1hwH*y"I/G㚌C2&016i|X*{xg8th@Ϥ%3>ɏxP0eb]c0Po "]K.޺`?ic̞wE'Jқ|y), 7HGSk4\(Z:EǤoÄUkQBwLƭhj>+BkتtǺ_ IDAT@LnI)P5ulVZ!|SU4DRڷdloUb d 1BifI9%񱝒)%_ʇ~'$Z hແ6Z.l6}>c '''}Ou{*9K6OD%}@Hˡndu:H.⧊C4aBBI*$NQY!' bs&df*$9l-ȋWdCOp ùZktD$~;˅-Q{K: l MSvŊ.**************:KoF`Ie  2Dۗ6SoKSXS ?)<`c&1f3*kQr4_JTUfm\eQwwkiRkqs`H)1~co6~̈́Sp̦"cX\4}d$-{bPU>E9BdN)1Ql6wu=ݣ | ۉy,.G&5Ϟ={1!^_]\.quD3X>yBߵ9ݎ3NOOO~B䂌ZvMSJ6ǏNpPN}S}oΡ!INeLj\ qxޏ37覚\0~8cO!/ }DtHpJraM92yϒF cAM^_Q;45ɂ/***************ZKoFq4,4`8,foL '|4̒U"cLt@ zC=hjT,V,%ۄb+0ZD m\ڜVCL$|G05A!f``lFTv1֣md('`D0bLxq6 C05$bɓh;" JؔSC2ohdIܗ8:4uϷ`tHrEPH'FJDDGPcahI`@p Q$97$KBx7gFX1t`j7'BGϢq||3릯}V3߆쾩:oM7+}ʾ_ա?y?fɐcA0CZ8: ]M,xXoLIh^Veƈhcȉv9ILNf35)%nnnj ZkvK]bIi4:nw\\ç|z}(}fV[5I4BE&g?G<ɓ'y ד8ω1c4 /_ٳgsfο'^w?1wwq#H.鈙|_,$o{ [ 꺚aEb ^k 1"TXš41nw}KJa8?!ĈEQl|5PR%xM) ⽫s5ʔV#{ZfX#fZ:9b h]0+uMME2}xFqHɩY,gܭo0}#'OU5>#:fFRl{|ck3d^ǘ\PU3BC*|>uU5~ӧkrF48==chیX,<~)bL)r9?!slJ[lFrRw@^dyPUfчfQ_S֐ Ћ"My WTGƴa*7HfbEUZ#5Zs S-1񹈠FuĮυQҀ )f0e0Ӑ??X&;rG3>H7牛3hQ3EEEEEEEEEEEEEEEE_7}%/\m8ۗ>#CGyLg>I̮MݪИ2Yc.\gR"=˕RI܀Xgivby%b"]ױl4fSUts1bf-WYD\.ƌ1rE}k$>q{R{F ^,ԲXGUUuɴ_Vv;Bhd`7??~ng+.//1BU:0vm60 iSϩϟ\.nYV4qm6ԯyYV@6{?$%+2CjtݖeJ[D.guL$!>xr` N 7&D,1ϣd} uC̩8uUNmv1Đ4OARd1o:Akۙ a!͘ MJ@-[~FɁv>4GM6cc<~m9;;m[5Ջ, `Zf3.//P_})g|;ssBy]#\ v1P]Z.899m{v((ӓS~Q5!%nG]88;;:~4?;9'hL͈Rmese>ϼ".0?FAej{d AMJ#ﴊ[Ы;uIʙ@I$>e^J 3RF}O.z,gWHcsbK?B۞qV+̬Fj TSSu iߵ DhF DULFugةOmEdJFO9#!ght}0ߦ{АoK(mcC 1>!7qjYQRUܶ׀rqXS93qeqךc] P1n#&& >y\` ԪTǼ%<9,{LNoG()35b+pvײ JH Y3O-j+c,gHؙD0\X15h 55T htFe aJ{~m'V&0z|q6yVTTTTTTTTTTTTTTTUחތ>dQo+vٛivT4Nq<<4'S9 Hx1l]i0kbmwTf*7e*\82gc̆k4G3-sܶu;8puE{%"x~>4JI;cbmGz{|Gt]G*MжM]B8G]EN5v;._:fvΛ]G]ͰvC.nɻ:l2k<}W^Q5;vNV nhoQt|u>9}Oif5jpαl&;n_B߱ќY\w{ODD:=ewT!bRj6 YAGlbGdzl>cUBDK1vj?%V,O4Od( u'kv|3"AIg vçT3bJfI%.D`R)DRT4Qy%M DZc̓@81*b_ϒ6?mg}AEEEEEEEEEEEEEEE_?}L凒)h%3!;,蜣l=S8 bR컻;fm3o].^ 9j29l^iۖ{...&՞-|d 23| 1)B"iN'g6s %Ek8F4[o%41Tx$%Գvlwk拆 3SFk-clY:9O<1<9C*l1zbh $6\4sfNC1]6g͔vu]e tn|m1z^qP"}OmsSWt]vCTM[GCo{bRdOp/o8cNS xz-U3JU5ga8#6yiGz\'ѣxQUHPI c*N# =V̭CCĥq$It޲B}{FtD@ȈrQ–̏6XDs0?!d  =ɠ"^hSҩmv`2V#7Mo;vvCqlG`waZ>~>N%_hD!{1FbG~8&RX[/ NXY- 旟w̌N ba\\\p۰52aH',T|nU\1g4oyyT`+:m3)zG/`Oh">$߳sAAsό?v?4cp'?҇(1]TTTTTTTTTTTTTTTuӗތ&obN-؎l1"f{sNv]j13k\d&z_Mh}FOٸ G2v}6"&rnGK1Ҁron#$0 Eěǹn-F40B ~j7Y= *~%gggf3Mþ&J]bA54 ]gAטڶVsl@ NNOhwkVgg֜hu9M_/<Tnl>Gu::$՜{d娂Ư'AtnCu~ zHyA0:kX0$p;2xJUesnqM鸚7M,Yfž%Ŝd#ɬjwPtP1sQc6orazھˌ߁ jp7m6a>d^3x'SWS./sz!E$%x֬N.\gg |07*)"l6 ꘩e4hn0@Lc,#۷kRh5H\}w.65 J zLex|_򗄾H)nIB6S]2 ɓ̚9q#͎| @uh Xz1HfG޶<~ᖎ(1c ="S邧&I\M}Фg!AS58 nooquzH\^.6&8Kr1A[9z"RV_FP7[+m\\6JGm+Eo }Jȳ;JLyfa ~u͉K\^HjZ}$:Kc,vbuu!jA073y3Ç\$E1D6w7C.ܗNl:#:1ǂ]25) R R$rRu[S7PW =$lr3^-im B= 8Fp6Klrbzhl6Lok*I)쐴lprs}MUU3S׎v4Mj0t?Bs*v777sz~joX#'tvjzsǏcdQ$vNNxSRJ94q63Ew8KkLfg""X0 3gE7&oV )O~B~A%`PR"3/}h ?m ^22a!NpqCÕfJ o[IucdaDryB߷m)͆nGH=Fl \NtҜ=! #cd\$n71ʦæHa0|SNFXGm[1,<8*FsX[e{\I)%sc3 ףIw5m?b!Fvin7kєѷe\5%hJ\z>gՀqWS>-fs4&R z k-mgMSqw&gn}X,nVܭԫ̭S*Ue6n1VuM=DcĩM!!(q/P)ZDȱv`>aSJֹ $ڹM ~l!-1yDrf1gx1ݎ;UMT|}{1== ~B/h TnQ73υnG:DMu70Q9??gٰlX1c[P2O@Ll6c -N/ޡm[0A.P D枏t> )ceՂi9͏W?]S'k:aCo!*MFI lcs6D$ )@/jيtk*[IT@Zl-:U brO$ $Z|BEF!x,8b3Fvd *'xl'%=09jC$xi4udpMs[AC^u]ׄzH0}P @=jM`C&7U` nf3 0͸] Y3F)b*mS |liR ƈZׯ[srr2];mAɈ ))Ir[%gϞ1ϙfqӄ8!LE7{JW$ 3I &48|N!~{owWHḼ̌%0Ho.8cє"dܺM_6B%8;͜vKE铒D䂛ێ#Z c'CqCJq7LV+c6&=qc(J1,G6994|—<S_VU'39QQ!䢃Z.:WU Fv1 }*y4!Ud cf9-]Uyz`1q57&USf\2sX#؃:2ma0c:7S$i1o'"'S?oI*Cj{?;lͶk5|2GU7 ڔ|Hݑ0`rQRk,Ex<}c+b Z5i(sWlL梉n#!\Ff3vGJLsEΈX'g|oXĂjD:{(}06OtԍC>%0%{ RLhȸ k*32j&}}3inw]loo8?}LZf]mo Q=FYp{sG_B4`&j`jӬC'.%YylC ɫ+.>Ue0V){Me0ƢF!̀\L<1%m K0Ӑ4äިaUBϝf_`_`Ȝַz}#0%|\0'!Vcb9ƹ'ݛ*j2]=v6ojE6 ŁKS֙E,)C9(9%C @}ﺁqnjxvLD?:ai6f3kyL3 ܎؏DL;"&"jjH9;0 Yn۱\.nue. طHR*c0uC:;C5iJnHE7hoy6y#1S"Be͐PC$ SJxsb-,<bV̖ \s.:i% E'gu1)~"Z?"S!D71 bP &e~qJ׵oY8=NO?b{yGϹc憓9}LNJǡ$%`J zI&*Wb$MTãMbIF )_szql6Tz>\&3}/IݿA!XrR~,bJ }?xL\Q͚a/=!CFL˧am/߆ȦSL8g-|sqxwLq۶̇@68~Jlt\/e`-mEs F}c:om2`BO8&C6Ƹ2Ә֞a!Ds L1k5Ʌ! rWU6X,mlJ>\.\\\з[\Sqw}=έde-|dF5khw놢|}NVlX4ML1#7{@zAhqZsٚw?&gyr® 뻜<6?~Lvxlvl6 rEy9y${\=c<5&Cr`ssCHl &h{?O}Qb Nt(pzzjnoiBkƊir2zXN߳ZCFR q:nrmN<"w$X:Uǧ|G^ܾlhq*+[s% g,I2Fnh3 @OOlo1IS©C5!Dvc vhQe|s[9h[Fs:=i"iٸ1fEEEEEEEEEEEEEEEE_v}T)_|N:pnd0D< U7$"0 \;^~ӧO?|laP3G*i7{>g@P)"őe)f4#iN(Ep &{]xoA8_S^l}?./oo= e!-j:Ug@<@VP>%l}lA8"X6%b1Qbg0H)%R$ii*QAaź γ7o$ Y,yrr/G'  eY>YYxwmFwBOˮ7¯):u:7q \ ڡq$QA }mwN/):OV;"p:$Ral4z]~pǶlmeYSvo&/[J/X^YBlnאR^6hFBV>Ϧ\X,Α h Նb{g#&E>;/ǝ;q8d$ y6p|ԯT㬍 d}g}\'{wV"U"xqAPso/o;FiNY7a75E#<"b= ri=^>&M .E ܯACWu>sL1:tu```````````````g" y;Ros9Ito*B8;rIv]֑jh gOI9>})vF}" X]Zhzt6חVlF =BDHm'NcvJ%:^ikf xsfN3BP Eq~~㇏(FǏ2 e!URlD*5 \qzrdTU_)H ob`>W9<<Td2iK4!I4.8ѿ##95|8YOm?(xH*}W8Vi Y$n0HHEtw gq!`wJ#=Z c|MXA!Mσoor!l-ō$\~rCg[3G>au~\y)}M\&?y 1l62 h(Wk֫Uk7o gTU\;aS8W$ 7oގZ>~=o nݺd2#PȪ d88R@7rˋܞ3篶$Z((UY/Ji>?(Q , zjg5|(rB-㚚9<>j2XB M\گkn}퀁0Y'>]x5}׃ gp)2N,n.T@$ĕ+b J<mn笔B I]h 끹1ȩ4+jÓs޿S84=2Ǐ3͊=|>ۚ$Iz[ Λw/j'nlW?漻|McR ,uFlڠ>.)%kȲIXq^)!`2q&{&*!ԌcLS7]Q5 M]JfVRJϱW^ú?|,QZj)ҚhJǦ4jTDH <!"5(7+lS7L ?P5U]Bbrycc6+/[s/S9!I>%:%m4ew $-Z<ѷv#Ox0/9:ʯ5b>GiDE|9cQ}K^blhYs ь''A~k$o ]̹=,֯J྽24>|hx5R,O3pjWwŠ0).HL^B@ZjƘ^хU!c409MiIlF@Thhdk-pSj|xGw\,OUUhi4yND,r !}I1"KnBg, &LJ^-!7G%$ґ&QI =>X1g8yᬡ6qle/.mv IDAT"l}Ό,ٙ4ME4e4<9 |:a\dl+9Gk1nCQ(=?^z%oD'u>b:!EwRMO^"ͽ0F'1eE8d+Ŧ2<u!pJm LFN# R~\i%& B!e>\iv#lsh{sl2Rϡ0zI]q%5R$ɘdB1C GYh%q/£?@5^]~px|>g,-HGcjӐdc>>5,j;ᅢ=t*]I1Lβ^/PJ /a~?$x!#}]>gKvG˭[oއ|t6ƍ[@Jm}Cyp`EV"u '8"7? ۰HnTrC z #|@ EeZ]?ѬKx*m?YkյϝJN{\J3z```````````````̧>~gg5QtWj /@^)  j˯pof1mPm@W,ج'; zˏgӛdWuk=>|z;y[V}MpVt#[M` g}ZjU{vwFcaDtAd&O41Qr%cy<Z-$l. s b5ͷ:5cx܉Vk߾܅aa(aej\*11B@Ef)YQmJOOz`$|x!)jуeK(8@RS7Bl*0Oɟ_?gӿϛ?xf,{LSQ/O.B0_~tktg u2 % pM ,@HPBmyJqb}k qEt>Ɯ>i*>!dl9B(<$!yqڜ806xcٟY/p!*c5YpH,B* $#IelxCm7n@yzÓp.}?9}1hK_<<~LҤ`gwb,˘g\\\PȆ(Ӝ.}<ͪd;A(% 9yZw1ֱ6 ܺ}꯾k /@/J"'Q)Bx'e_DR“%H' A.y/<2I0HRHuA@T e( =URJ&e4ݠE! km6Ԑ?>by%O3h/mVWB R>d4!a]9.s$Gǐ['JZ+=zHl y1Jf|vwvΦ, ,Ezcoܸؼ~5 RJw<\?_ת\?'% kϫ;Iiߪ^׭aÅ@T`Y,."-e9vyN\?a6ZFݾy7pkZB;L16wߞS!\n+x4ƂR (SʲbW_ZKys7oa|돹}ߞ3Ň@kvf#gOhLMe!ݤn)?_BrI ]sIӔzʄlwatu3穧4\46//[]ؤ`2Og}H{~"zkj}E?Vo$}+8؈v9i7k:6)%$PWg$ehEHd"0 } e!8w{aw.LN?p{K8 Z3PEԎTyGE@^e\3:Uc si MS[1шٌfI1Ǐy)iJ^qrFIB[g󜍮#<U(+53} k`.g3d5%{xϟ><GScVZ ^{o+ŖO.y!yl<9;/PUZuC>_o=ʗ(1iOUopֶ B!MdgzvGF,꒽۷XOT̋v^bTJ`/82'*ctf$0 H[}ضlA]nh.>PA h3&y>a<\չƟvw2|66u=(ف6TJ݅3>;j&v~;DM a<4 b@w4u5₦i3N89چ.YPMSOe1 X) l'kj\iT8 DJ }ba^bѪQ4 wH )E 8x;Z%$yixZPB)[ʍC8' Ij 4nNkR3JSF$:C\\\ pwQ;qxx\<_2G>_8}߹Hs_f\!iBdhGGGhl$[^ud !:S2ߙ0e ;!tYl lPU%z__2pttk-Y\ :Om >xP-`&k+<ϾlcpO57B60S='IdKR`C&"oK\q?TR=uo:^y%5Я㦩^# aH>|&m{f(ŏt۷<=E7$bzuTF9 1;m9&]@>Koh,9y߷?oD@koku!vTئϢ:0zcϟp?u/vv`kORkj0.CmqTLwmh}EGwt7G:i yl>c Yld}4Mُ=Zkyp.~ /v{ZH m{Mord::CO !s19cj,q4IHdY?,]S}Q;^Dȫ~쪮j^elo,DrSf#gOع A֚4p\G>\WO|&~pVGk$pY{}\G A,oZ }T~l^ y%LI H qPoPJ!qB6 ٬Q;d1h3&:}6M# -lݐb d.]^OpN]x|tAtЅ]t;?5]ׇ11m|?nRT'RmmjBLSƐ9S..x)jlzKg k?C$hBQV(qXw.~!Xw'_Gz˿~uW;ob%LSdM2Jx-$ W%տ#0u@+E#-N)ֵ?|>GD'Irevy4[ ueW7>|&OjC?_ϺݳB< L+h >tiAb:2b ^;iPh9{])/B^iKbl4Oȳѕ.ןg70<ݚ>c?x_h(1޴y{`[`̗U='8|8(F u]cI1ܰ;9Er! ː1XGP2:Ń$׆p!uV۰@%%dyҫX3xrrsśdk o<`oor6 ʟr`2-8;;6Z9.qQơ,g%?|-% ϸX}Yptbic2St·?η=/2E>[Dl6& `G(1O_ӂ\~9&{m!xdھݾ| k@2fo7 e%cLdLk]EAZ"t hN3OۛQX5xBޔW#%$ H3fYlևIl@]0DT2Ns`<2LMßGz6[E1k(ql 4P7q;w@ bwYޥ$ER |X#=$Bb4Z _)u{{om(}~4QL&w- D m4G׹& !$)GhI)?>ajc[ '/Ͼ- jM?7Z&/N !.sbiZBb sK% mf pDc%>( !!C.Lv4_ IDAT~B.|>߶^!\mjo;v7?I^+ۯw 4#PZ#BLgcaSmXJsqqA11#9Y\2o.XzŃ1&zŲJ)*Zs)7of`҂4͹y6ǤYN3Ř$I888`<ߣ*kO+_ Mr_x6S,c6!QcK<2X/ $Y^p $F%)p||cg#q||啗HUmȲ_'Rz@"C A#l6%gO+%@h,A "u[GRd`vKR)!<4ͰQ}molo]Ap}w4zO}\zVY3Ǐssl,{U m\{|50Ok6 :t=uEQ HtF8oHr8`P{T!GTEwiHR|s__p>ۡjUao⪲ l˺SAiڪG2\R)RiJYkiWUM䪪x| T$:/#iKcJ뚽y<*oyQnjphZRU  zX Bd\f@ł Fh$ >+X-V$=ƣԜ,)#%+&]KE6QLckIc^-Hb>ۡLSu;[7 5$ {[Mcg r[Gqxc?|-Fb78OBrA Ie IQp:e6.wϊبKtDLPYʹ[)*H$ [(sq3,)0k!eELc6|+DFO}."vf$zk/D^!#%ZmåDruhaN.V$"&G3Hd2A ȲjHb$%Ȑ%q@1.+0MqMz XJHxL0M%AW!$QKa[-H". ^gⱵ.z m96dY,R3cXmښDvmRA$TP5:0~\l6H)BA8FiFU&…t ֭[gh[oKbl ZKB@KY˜:E%ɳ1ITΐ 5I+dJ \$%YZ` 5 MJC9@T#rB!H!AI0qAhu6xQ={mϧV~4U+ibڂ4*p.7>o|h fdo7k!~uಡ+o᫚ <2]LsL HՆ2G@PǮB m3Sva agղdAJٷŵރ,8S!j T^to,2M6#zD/nJ[tio/ԽjC^ĿEu<։քo|h&':E58 E@A Apwۮ =)c+hCos xCA[C ChG*7bPkk_4Ėb#i,~mawJ)ixk[v՘:YMP%MP9g#!'ُ,iz|[DZUL ͡8LҔ,2uO ɶ  $833>[թ%_DfU_4i4NV.e^_>/c?>W>| ؇{ܷN3Z9 RfX4 O<9(2\K·3\MΉBaeRUu]ӷnӢU9ΙP FM6 M$e3m{0皃Dku*bt] -@k=JiFfܬ쳱Z"љ)̲ʶ{߱ZH)KЙKwZR/EmvHgs{-Me.& }Hq"%O~)۷|j۷?<UU{r]9_|b>ZK] bl-!r~ >>*|Vmqf:^WMӰQ59GLXW1o@c JkLw>{B!S@b )fB.z)c ׇ!w'aj6J6(p'.a/mUw#U!UFiAe4J@%d28a*BiM@I)$ j.ٱR<+HʤL3QU ʐ!Ѝ1b6Ԏ]؞.:쵇XB8I=)~*K=o po _΂%#qKfu +Fp8 OzVTUEYo.%g֜_+'lWcŒ(zNԾF)ŢQ""7[\ ) ~]ZqkBW// MI1%CHBY/*?: E~K>'λ2TY q&V2.st9j| d4 bikRP*tszM9~K@DmGΑwe%D|iꆪs\RUi;R\]ho,LLLLLLLLLLLLLLLL|_·џ_ -; J[yMAQ:8KpɈʥez7=`;uJ %z[#9"8LJU1Њm+k+j?$@訪8m>4(RRxl6Eycy}ιy|ܸOk}0ۤ6(C;<9s:zkS;k˱NGe]ZfnYO9-劾x:*C)eb tݞvBN+~Cg$[TʴW_< .i|_Cj1ZYs7qr|#* WAW|>g\Wn;O7^x/7;?}3~<\Zps if͂'Oqy M?FISQ9Z[f}L,O_|MI%;{Z @UYZ)L`}߳kVfKp* Jeff-Ғ>~1y1kN׆f>g>c\sk&;Hkn~NCZ'&&&&&&&&&&&&&&&'|hY;;L"xI|ۿ*ʔkYY@r8?ɝ <^2;h&(:l5 jtYo؊!D\3k䎮c]7)FsހØB 0o ^a\2bGlowoW"o^ #K&3Ԯ&P_sz򀧏a]U̕'sHgxlv{RJf v;5gY̖N/bX 1|Ï1G۔u1_R927[p BJ۶ر-09afm;k)#@UT>"KJisXI)Cc0RFFuLCl`X e,4KP7-f{K;ಾI111111111111111};Fx=G-o:MB c3:TB[wK sh.|ϭTFjCQ%@ȃc@^sxF* FP, oCu׿ %pעn.1l}n;65AaqhQqh8תyTAyp>E2R%&p0ر (Т119+N7\\}mz5cÀ#]f59Gc"iS|aEno޲tqC^D%Hޑ@-.ʰs{k=Fe!]}Nn/Q8 (ʂD9{Dn˯/ל=|3g;^]b+SBiwjw7_2_-+K3[\zM~O=,szb+~_/57X,f5 øwnO۶Y,gK_aFA|1R=M`^ ^_IZjx?> Jp\J)2" h)2:@mz{nPȐT!l`(QF㱍]y0aX;OxkU\\)F&}ߗtUu1#xu >&Bm#-U4uJƺ WHBT uĸ9=9YbHC1 @a-k*6z14R_,Rʢa{Af\1v;1̗sW/!B;x|S4Kbe\qz~^`>!ȳr>+^x7_?/1pα"b]@cqP)xpg̛2o SJ2 빺G`f5Z[TDc`DPʐs]aqI"BXnEIFcFѠձa'H}}uZdMQT ڀhU<0ZI8N[y8 MJG EJ jBZFM:Nd!.g !aCJ)abL%2-E ڷl![c0s> 8m\c )QR%*LceQCP`ǣVceXZv ŵHʄezcd8:c83tAD J#SU裋m[<}ij'^.1qQzj-sgsssؖi[Xey5nCe؞1,#,68@8ybCbϢQ9}`p/+, آTahCԓ{r|1;yɒ|726'{-4|%ggls./yW |1_[}YuNvf<}ǏKۃ'l{cf9'%}_iZU £4MCh7٬,d߷eE)x xbɭ,Oq̠P6p>*^#vnn0J]. d7὚Iv_~{Cul67l[BJPYqsu]Ω}lzJ'&&&&&&&&&&&&&&|h1! -tVߺ8Ę8mjr [Pj D/D: AQ PjDYFui:5Tڙ$bNv{l]uQ:Fހ-a't-ɇz9֥Smsiʔ1ʇZc=h8r(8$%z8nV%cQJ;r6xJB1b91iI6HIh{vǼBSnϮAri_k#s{AѲ|oo ݆JS7C X=n"Xx$'*1Y4BNh\&G[E DYp2$E{t5+zk֏af;Dif {vڶ{޾}l`STJ|WO-}3ͮ\gruu΂s5o/j|(- r^nq!%lh9_?u8mpT Gܣ6Fk` 1 E'h*gbjRvb,gN,XmH5\kv$RWc IDAT3(}PäDb7Z{Xdu{Մ蹽%~w=MUDh=]raI:zOve߽|hAr5[]?s 3Cs7i;&grC+1=>H1poшyF)yhE̙<?̐d}^\)↦'qWy| +j}gn②ًCW/NaQ:I Rd2% rwF;D,N/{%"!%\f~DŽ&y0c-VzrK8 .O?)/|;T #h3Nn9y10 tͬ(]uĀʪ $FY!cjтX!Ƣ@J@gt%1hф6uQ_/ui$Վk>X q5nO{63W+fKfz o]b0캖szzNOOIga~z8G*'0ui,8=]RfsBR77v ,h7EY2[cQ\\_4.}d\b#RgM+S]& r_}uSq c 7Wuͼ^z :N tfo/"MLLLLLLLLLLLLLLL|·2 ۺc<=U86eXyġHQw(soAKw_˦w19a|eDY+HilZ[cc82(!(()Aő]⻞C[f־?qI)cDX}$DH >RUJ肧:NaP+$Uļ~O!E.riݞ'B'-B; vْ &{BڀR.cF˾; <4cи/!Vv* 1!þ1-QkfhD\c0ܲm}9~}9Z5(8??uJ=/_ϱT<~7'hb4\_0P)3, fCUUx\f9KNC>y{suETieBP4)er&-R.pc_rm33C*SJMN̪˛|s$%o.9-m3vQ Y(kg|9 xP;݀4!8MUB+)j1z\ZfW߹{asVt6~HCn3JȒȱ(՚H]B:sHdU$>e$e֖B-jP ˸cT ǰ.S3]ǹciզ:wx m# `=MX"h~sA8U!E B "=obޡGqrVs:sK6i"%C0) X;[ Y@J]&c41;LHk*HB އHH4EMfQ;o1,DJeXJf3K<|]?{O//1z\ci"2US,gs قf1?l՚ݶ-zTUE{>z)M%.׷HT밥"e5`6)t͆Dg7/}3ɢ}!fV=ͦh9\MTĘ",hm9=;GkMԡh3gVW}o$߳|ZV2v7W~3{}/^3;Rܠ+G],hE8nð1P6AipB8pt-dMi'j?﹗˟J;KzENefT㢵>;r\,[mmͭ:<ݿapl[{Oʶ{ﹹfË_'ݮKUUeؤ@NU-N4*{$ztERBiP$Lhv1F%DVz~0GKΞ9wn@[&g~{M軁U1J#C{:LN~>)tlȏW B rȹ4~rb"hE#a81T]=cδ-\ FC=*/Cռ$Ԯ7/)Z޼~z믿ɓg}k[՜OE0F1αȩMh0z87*N)PU{|X-xߡdg;V^.I\bs}C}_|Z}97o{ܿx񂫫+yX2\O!'aOh~S1j4F׳huP-!1Q1|Ion8rz9% wB߻)DߡA(kȒ$D!|K0*$4lAt 9`uQ9$ Ml(aUUZ! &[oC裢\3Vs~|z/h4zx3BM@ YWhUSCR\^ߔ(ت_~#fƇ~D7o.Os<ʹɧ?&HKC,,4tūǹ> -v)8YqS(RΈނR4USZ].UnHVse!J+\ t["USTз|syG}O"_}+)VӵjW?3@B>4ۖ/1V}ݕ_} _'~JNLLLLLLLLLLLLLLLn/o[c`O:vyv(O[ Գ:'&&&&&&&&&&&&&&&O|?6 <6KV0C{h&cHRBSҢ80ro)-CHԡ1˝`00 =4U;o&@vC1 hQsDhusJXRlfN@4}ߣ#Y!A!ɣ'%;M$@Ctij_ːa܆_s~}ʏ> U5GR, "!mdߗ7Ͽd{}>NunGN-VVl|)7{(8ZCia!I sv~E$m"Dk5ZJSYk@ݢFc9.wcopxk1 z4Lu]W_pr˗V+nn1RYK eł-JJ9ݵ{_[3)zξ߳ -!iljnXt ]>:<~l3#7 r F;W)am9vGHdmowx#ͬBkMr=|Lݱ^_\]B ; [\9o_];Fy?UG@fjA( A*OFYtCA-`5m5k)MԻc<c؍!baPR6vi*kI)uZҠ7l[-Aa$uYU )tb)C yܾ6KqoGД\zf)hEjb*ƭ/s.wD =] }K9O>s h1eҡ6wc+قm 1%0Tkw|ODdHBHhm7{ hRD&xA Ax[q+@~8.t} H"|H֪"/L}NCUUz!=h?UB&( Զ(/]EXjG_kk}ʏ}O\Mc9*{̨Q0&g-24HC2!LNC͝&r9J Ja.s䡙=sC|;{ )oࢆҶK-heP.aXUwXSk{$ DEžyUT"VeȞ=! 4EAQpcJ*6EȎEQq !*NOOQY:8Q,hYDH\\!&="m'ŀcW$ *tVꚔ5T<}f4˭2h,Lwꆐִ=֦EAAr( 9 "99}DLJrDхM=??Et}B9IdGdh}(XfkJb+Rߕ#yH;cϨҘNg^uU]aa\ׁf1b&HZMߦ~b""86*(mI& VA'v9S-K v>'gpJ"{|v)6Czĕ-9R;4 b1_S&ӳ5=yCjLc6=~Sabbbbbbbbbbbbbbb{w>剀$Ѓa$.])JÒcXý9DZG:*nK 0 }BT8g껚g)^蔰Q^ۋsY0d ipۡ>nnWe;SXWB܏4HOw|{v0Ø5xZ4^Ȟńh*E#buFuŃ3;,/Z &R9$l˙"WhSAi.FJXVDd,vf!1+iKJBq%vb4g )%J<%8,,0( qh4'@e)ERᜅC1; X/_}qn7|UsY$Wm>-.]sx$FPEw} njq.$rǁ}V{7 `Tʠ.ܘmk* 1!)CG.CpRF(IG[BqاT^PzO2m!d~2j6$ 2HKhUġŜ6ùs-$(|KARa?QCi[Bc]MJaPP1m2Pdxqo[~O޶(Wԗ82)eReaHwvJvhV'`+|M)[l hB!%b$h늶# Z6()9K4!F1z'gw;bl6͎ahr]80 jLWh@ N[svP!UYHdB(>o/nx!)?YeYz|k=!"rΪ=l[4P)dAdC 'ޗ  BH%&lMv*3c:kQلnl  qŻ|SG,Ge.//D̉n9sttDƟٟwޡW>Z45sd7 ԗ'Ԍ`;A[ mzS ̙}_ f+ˣc]qݨ\yva` HˈS)}[Gd2Ef-ı[UcԶBN=u]ӯΟ?a~%#cs8|p?ˑXS7j ٬ l}kMusTgД u*8)NqeCwH)h ߫\>!] ϸ41{$|ee6x5jHcX,)q0KT#ylOYwi0@z`&jz^jh+lVk|K;gmh&XI*1gG%aR IDATI`YyK\3\TJWW. iN3) ~ELvJPg<}!gϞCF]:N8Z3Uy3_Rf{N+6SF*Qû zDcK//s]f9g+>|Uu۷3YOf8;i988tlbbbbbbbbbbbbbbbg>AھÏ!j%5Cۺv,_J#r_׃C#_(>jiH EJaRQ.ry8>jaLJAM[˳OG+_&bޑ4!כo9lg~BHP\iP׾7"@ժJ hBSE":y%6lJҽYDahb!`)TsKX/(OAc\K VA^Im8p8}_.\'TmPZwʗS@<-;zP9gNnq"$UthTkLᜧmg:ÂM];o(U"|1ْWWϕ9ѯJQLjVqCk6 ms_#jl&8)MFڅVCy6uDsRfF68 p{ƶ{n0-pHG'$gx3kgrm7l{&=3[<{7˰:y}΢Ċ3Y!}.[qb4vS UW jp.eeE]P<҆N9sƲawV g8qK8} T8%#O.I}wZlh}gPC;(0rѦ@nJTփgT$8)K<+i''P/GMTeqm;G]אf2w[bס>뚦mqU;g6[8go\Q9ݻwRU8HN@hBC~aOɱn3*f.zxD}GϸGqjZ\Qpd&xB zǨ2ԱlVXk\mQaj(GUs]Oڌxi|mF0zv N\ -U1KibrIUU.J[?ܶ]nV#E=j1%zK'.[s݆ܗfǬr!Qx)D?Fe<9l] 5}A^O76'6xfsvă]9h폛lP LsD4uVR9!x wl[gėv[ӥL# FKn&TD+BqRUvFn*ԑrt mqd!t5iBsb;ئ[mq/׶s#TH1ux %ќsx9"D+ 7zxvzέ7hf-fGjvw:?hyKEl؏ꒄYǬiyhM70 LIK[B,96[ׇZQb8o{p*?r}Dij )baܻlJ=lVF=}öef]|FKNUuqdׁuJȍ2$g˸-AsiqjK...ϸ[h1 #09 %gcdhєp *F2Ox)B퓛y|fmSfuqAV|Q[<~f9ZsVΛgܹy, DIC0Qg#'&bW>cas^6111111111111111yF; Eeh6(7eJX,"(WҮ:GyhЖ+[s%(7B=d"R5S6 f' T3:}>J=_g!{HQp.+ڶkM]K5-~KU{m9G9zQYɺXTA7Tf$au#닯)U9^qBNjz~Gvlj36]rMĻlOsa\橪Byk֫g\|ᇼxWI&'#r.ws4&|| 8!^_s@EÅ+>'>auA u] !AE1:ˠ:g vk#UUSvᣳkы("tw5> pa> }J߭Ο~eܺ@ .⠣`à265m4u9NF lˈI rUPjW ʉ=j D/s>H!mic~*.ۓrF%kB7,$4* bN`Жw@=O\{GG,+f3?Dk 9Fj_ $C\ίs.+&fy D= TBP*Sfl;/|Zmc Y1q )Le"#27Ox1q~[G7iLJ I=?k{|+nYfR*˰I+5͌NCc±?'c1 lP:;Y@v,36 zIO"> \:.uM׀n{fEgT3oqzqʏ~>UhO~W)O^J8zɍvyx#>^~v,q+׽*CQ O_pA?I񢁭>a4FJw=# !‡jZE!MFcp{l2l6͖wOH"52uãxLJ{-҇?~}80EѬS,!F,r+ w::S)h0!kI&嵟in~klSƷ-(qC{w=%T=_fxS-[ּӖ竜CBzM=?!e߸[_"7.՚Gl6CG3RRG5R{4uRs?{x>r.8?;p4Y.XRLAܴl8683;-e%,xWqYh0|.-{=1$U>AQ $+y7P T8nΖ{^nkV<}ʫʭ[ER$u6oHmGܺsmnC<`v /ι:9{'9oA'&&&&&&&&&&&&&&&>o|h3+[؅D@Nc+Ӷm^٠pWCƫPÆo@~Kcz>]S#9˿̏e)-wxE͑|u܁֗xGhp&kq0?lgS'e\J=f~A3)o13.i H._R[iktJ Y%LREci+o=ID앪o7Ooa[P^ 0  =B"3]Vz ( k'=(v0هc(ˊ]@hj=:L{y_s6D!$ 4b$X51tM[`2@y%9F箆UȻТEJ T]vVL#Y<#R6Ŝ kN?~č%CvAE1n[UK ^H1%EL1Z|~ gKRœYIlx@17h;jIfBe0x-@25/}8*6 bd+l>_ngO?a6}i"9 òҭ7ł34wšēDPLJ3.8?=# K (dYOJ5܃MxBJD4ԁFQC_)m`*A]Wd5ꪢc&8vLj,@2%i}s* /oxV+JSzIȭKfwo\[MV=%pqybqDlqL3:p ~n|>'(r\W[{>\?)M|}ȡ.PkctJc Rn஄=cH]p0<\Z{ϫLvW o>3+_ڗa|4!ZY T1FO4f6u{Wlx<*W7\ѓb5nmWȕw(mޔ;拻N\>S%i[ 00CNi7,/#H9'aeŞ[˗Ӝo3HVX?~ޣ->BH`**$qOdy,p)^m^} vF Ϟ!"AC~b޿ϼmx [;8Y,I >7o57os1Pq|tv& #w*xvuR!\<< q$0Oi&t7+_;A+bFe5.-Q#xO-S"KظRT,? ̫ZaU3"~ќ>xmG4 UQ n9*OUQX 飯jY瀪?U~ %y]WۻECk?P޿^ǀuO!c~}k_R^3a>3US8P30ˑUǝ;wXЫAEƸ~X^;/pU16Cw@]-T!hǢG?$WPk7pޠiÝ{yk|-}!H9jHN=5qnКp:{SNe@;6HF4mM@ho%x BUO<JYeUs\K }VҦR!fض\f22,`Ƭ \m88wΠR̘@+y*`(Y!ae1#+Nr oU%ĨŨ7J_)s%~%C+$04yG k[T< ͬ%i>rmOJCu`0B;r9xb\l9ݟ4|}qBqWcS jyW; Qaׇ1:à0PO\ v;CH%šC @+ٰ)g3V'"|E'$_51&D"sp:6t΅q=ԫ@i0Dŋr3zh9 [i۠ƶ8P=1zw>"RnߌՁ*d|1{b#:3B%DTlzS\,a&*6 #l!Uz]lĜ8_ĸ;w3l^{rrai#`ݐ.6<~JmrM% 煷 ^yufΣ'sNn#+DSU4KxQѣmj %U!TC5[/mQUsXoHp$*MNO-KW_7EXMC#;>?/~􈋋vLTij햓gOyG9\.ygxwY O?✓7r$۔sDtX?9ߦDc+M$|̊0" )U WDոL$FJ9GƓH6:O\8$'1#G379~W8uX8W.N?C%_X<]Şjֲ=Nn 58+Ҫ]SWȸPCk^yUWCT'g|o#mݝkU 1@C~n2ԐO??zJSW/Kcz脾B7(4er-躾(,mDox .ԄMGcbvVcbp~[w^c~;3DXCq_J}u_u%kp%v.L61u IDATρHfu;r*%;^=m)aK-m;Pi+\~E-<]|>,!-@l7e"e[")4%θqX$g)vY6 \9Ov@4M נGRgp# eGjry{ԎE=}^Uk4}i{ALMH)!& 7PeL` k*LΑJmpj53<'YP 9gNؚ-tsqN/!3y޳JKŷɋ98a8}_s'6ÇܹsolV4UѬy|>m[rJ/BpAcBϛm%xEas8|"cbbbbbbbbbbbbbbbg>:pwߒ=MJpz/ XK^-];χM s'7@FapL?RGJs5J'. GsWfxZ=>;%e,e\S˥go8kiVmG E=BFsƒQ8K_sTZnw3r+7̶8ŻGf-O?_Y,ԡyw| sr6GӶsB gsx+K3Dsc<OY0O'K[grFOLLLLLLLLLLLLLL|̇c\d]fJiH M݊·A"n #9A`j]9V?0rnha:3PDo^r|?d>fb6bgWB@qJR&KF)aa0,) ;..G 6Ps 0 _596)*KH{"*@xn̈8*lؾ֣Qa8`+Ae:jRlc֡} ) ɵ_x1G5cr^s|;o8&.ּtc< "%;f9v*>'j860_sm C}pN  Aql;3.鏙k,+g03nS:q$`M16[,'9','` %Vu*23yn1c>B`}x1}{7%L]yRO&*:;n|Y[UŽ0$zSKDul u q<Q@\Λf: ٰlX* =,xrpаDZԈF*0LhB(SĔhJք e6GdΣpw8W%q<<.hd#3q)'k?k84X@x %L rNw>_%?/s?8bYᝲ/gښrNhs2Lҍ KxQf|5d|}Y䉩̣ǡ)F-!ݠ)Avubbbbbbbbbbbbbbbw>a3zu>{PɣП}MV޷GMay2ۡ倫v3>?1 !*c AmGG̙P /pֳ]_Cqܿy¯/}dh1(TāAɞ]AXQƬJtpWX/q m ԁ[H\ӌH.\9ccWԙ'% ̍[',3跬?bCN5!xA#9073*JQ D_T9<{WSES" Cs䨻wugmFcJ4Gb/ΛJwqJܬज़'mB;+AT&fLPaܼe9C,ZI\ex~@{IH1ȱK*:ybRLjrxAԎX.<"焙b9⭴gJRC3jt1LjC(+y@m̸_Eo:gC=o6w믿^ZY J췈 0$B =K.5#8b0coPlɜ '`45'@W&8ъ箟q݌xɨsO=r2W8%-: J#VɌ(BJUU+4sGTA=_y[oW dqVҴ)ܞ-9i[?W^_x;@RL1 uP͑$y-\Z{w^g,6l0  xƣ)dK[,gyDCiy3##v>#Du;eIpB.\p)lNl./x]׍|2x"L5U}1mE ap!x26 r1Lr@EW)n)"bwcǚ:4J!p΃~d@Hb]14i=ᩛ"{t {l N" іE &ٗ\.x[Ȓ! US$䁂e9Snm"zC¨ɣ{(%k,h5(OxMB||RJhes\cspާ#A) r[adJ'>΋JP =sWRC"o~t>vs<$!R$ C`{D"Ղ=SŠՑĀׂWB5[ E=(!.Bf "Ƞ` y hQP1bEcG)GR)LVZL>?וkGJIHDA!*APhE-!dGӿ ´`BiĈڞ5tNhMK/ȫeIQh=: >RU3>}޽rrxQ!ཧ,>zcLE蜏}vHoC {]*n_0 λqMy*kD -O.p)CS8PudddddddddddddddƗ>~"< rHm6޶-!;vr:sn_ldoͨp2IYnTi()͙^QXkW$͒mcu`>G+{0p "#UȍQ;xUmQ(Q! #)RCC{9Z5D %7pg'&U14Fqv.&(]C[kC"}臖Gk͐>*( "i>o=iwXkI K C|ocY٬b!kMYg5ҬV(:lV(b >+}O=hChrS]r !^4;/k [mNm!Ni`88"LT tcLqEԙ!ڐN7BDHJ3c!<YS)SjůUUV5jrn{ yRfw4%mHl:~]q\9#7PXC}̗C\bC GFFFFFFFFFFFFFF|A/2 zr&bx^Ex-bkzیn@矵hn>Bsdk0 [HKڱ7u8؛R}h1  ^UCG<=>^(ytrYQW-{BI>s`-G:+S ǴMt:,K 킮pDEQaP4MCY ;gAB@Bu-|H.tH@}GD)CJ)ެ(P%< tu!"f>b>3k4(Œb[m]fY=:'6{3V}_t)ˍ"FRuEmC8tja"R[i˵h),) c)JA.ZFIFf( #F @iKP.zT] - -u6Zk~?|p/̯̓BuuLAm ,V+&唾O$eQ>S$i}yu{$D s"ߺJ}@X"iXjPJEI[J3תFFFFFFFFFFFFFFFFsKFoÚ_7c9XŶ]p̠RēuasU_ɕh1 "D-ҭgMO1k0b"QyAF(c1Z|Ķ4PDlZ.pՌMHGiE uCtJ߳PZ}$ Vw48=߰Zm( nܼB!"\r}^|E& >OY)(BKbkPVW'v86[& %[wj4 %[$ϘkB5NCYC"B'jF PT3WV9?}f6{{hMR͚( 'ۖ=Ib-`vo;Hf35 h!zub+EJɶ;E=[`Ώ:Lf4Q1׎`,Jan #opDf%B- $=t0yACR.EP1zޕ#"XǹT儿|=.XlV+I]c "TrCUMI!q\Ϩ&&l8=& w;z>pXraM PhP(mAaYE_|.ӱ=22222222222222UKF1ݲ xMhE.{U^'4Cs'mgمH*gRV,.gYU!rCSyB>\BDQhG)a\] !knPxHnf6xa?_k7\=g}xꅧB(m>$ =lI@e%GRIAfpH'UbbbϔSrTQ(cBP=BՉ.}<2)(!8'i5K4`3xdo):tUPSN  .o /(y E@4X$!!%5b'?{>pUܸqv |~YU-}J(4Y 8Y ]3bX1!"г\y03;B{1fwO*{bF|҇d#";sFH6 rٜ(\< ?9n_Dxk 9u v6`TV3iA"7 K!"FhgI!~?zCdmhIM|24EbjӲ?ۧų\7]au( }icci0d]ĔoX5XATVPزfE 5XkV!78Y IYa ū<ˆ+i ye!VY1VP*3ca&S0l։(>7C'VgrH_KPZuD /=hЦbj2Cs}#f{{ptJ;uō+G4| ]qj;?nrxw}W]E]'*%-E7 B•8CN?lք}i瞾|b˅hddddddddddddddd—> qmԈ`&аœ^\O%Sj@;RJ١k o1c,Q r*p:}4 DqV /oHAh+R{}˯cyx$$MKG@qp嘄i{fa6(hzج{AH{f)]`mMRP5Z HˈO ʧGeY2=4>aZW,M$e7lR)EuYi.PjmKe501(oH 1&J>#mߓŎm~_ztB` gVOH 5$4! (j8^oOqΐ&!$T HD= Tȃ#S5?P^g,Er{AA"_K;p"^1Qg ʐ45O1ٛsx)0 w6 7&yɂ%eˋ/'w0st|?/ssqZ$aCb8y[xx7988`:G}BWmS&%^o() Q }c["ϹeYR<<"ICeY]DCڣicdddddddddddddddƗ>QcI)ڂ"9rֲm';Y1}(BE^)A )ڡaӧeosp- b}T$T6`Th^R(iI mC @aXo6X"J@ӆ@.IWW#9\m{?>>g{R$z|A#}L45 a\,rt6!-]1BTC8l4ﯙ}k_Y1( Ƣ{$y@ZDath)pͺ( ք퀿_alr+9 >mGDRfC1BS/4(I pL^Wd2eh쓏я~Ȧi,ܾy*QpA&)E-6q o[ol$[`z$Ebi5&&9nV}O ~ ׇSDF6}ƐPPxƀsiS8BϘQ%{G QJP%?Xs3'o+7O~cfo7PQ!) Kw$%B|={7oLYgopSMz*U P%!BHtǹ:K&u7Z߳Zvt9?8>4}Y9Ɛ|^bC>_2j6*T9%]*Ai,5kh??ehح\bEh<8RzMEt(rZe^Mm݀¾σ 1DdoVCIsxta V,hKZZ NѤ(<׈Q06+R|rJb?%ܿ+NOOyxAP8X#I$QU5auC-yƵ+ߢ kf+|A9*E&bO~OcJ되EQwyG?)~kOK/Ywc~7Ž_⅛CZ=ޛQ)M詊IO?/Y *Y٬ K 9V͆]$q|||Rמ9C{r!%8yh>U;;6 MP4}|}Tu91sd smtd煾O\j 0 5( (c4F M{BۢP8#!D z~ Ù!TcUA=ʒ.(% .A0TX&D}MftFk6DjC?ΝO? BT|&)!$us8 DIx :uI=X(gY74]Yaåƻ- O|J)TUEO\)-KC14Kܸ,V7֠00JP60QWFW(hkCj(Tڲ^9;?x -Vhpᩧn⌢{.!PClG6L|_1| qJO蛖 Os!g!G]bp$IʠaQ$*+B"d1{W?p7;Ʉ&I]5~$$!H|K"?ˣ8CBD$bS,K^=ggg c z"&O1T8sᗯ*+X!<R/R81(v!tJ鉡EQgSb }Ƹ"z`822222222222222UKFxR110BQhABg,rR~=1Xc}zEnckc?@'-1F#HQEAutME2fC=b:E$e4"=@+bJEvQ g-ԣiYwu4'MHCޯZsS{Ǖ09(!%N7L]Ф sjb ,M۱Zj>IEuC''1Ź n<B҄ȟs=J]8UHr#ZDRkd:&<_SLֈhRT=%9b4Zi(c(U,9\nlViLܰW| -ggsҊ(LfyYw9u{<ܳ| s}^`uZ3MJc:ڳշjDTȱ54V_c !zhkv\ْf 1~o|bI`07gVyWHy5|YHԉbo'J{gp]{9|ȵ+y 8<=u]Cs8=}L KV+{ȵ[81•EUc>Gr~vJnPZe?葑 _0l>+.y6hܶ/"="(uqDJC }ʀh:AZaup6YYJ#S@E:۴XkLfX[&ܒ*r4y 'Zl놇nR*ʘ=Z F[zQV&?#4@]N">z(@Ω^{:qu8{+SKB@ph]D17]z:AB2;(1Wf0Ρ$0PֻY=N4 b:[[L's\]RMj"1%|恇@q׮BYCk8J .'GP6;˃2ZKaJ)"NNxOQW1NIhT3>CfӒ^|gGgserĹ%j2 eC)"JfP_آb>/iA{wk|U҇.XVʓ0F X=Bdsg}YalZY͡.KZX5V$gR \"@EH{uܬ^./M "Hyh]QPO' * Y:鄮*ꚺ1C] ]GQTGO gKq 8ka|@@_ňqu{HL }{{Ah?ʤk,뚪ʋDrsa;0ЭY#'#yE|wQ( g#ߓ_V쌌|҇"BLy +a6$۶/Ƙ](dAhL_tJG!5hRs<ߣ4P:N#v-Ai SyYw(rsrۆjUl.}nJ )w uYP9V̪{QTvêmmC9qd k{Ơ|~ @,Jkbis`{·$xk/Ҷxs\0a-~[?wr^(!/$ԮyV:L*,zlRYGTzA808p}~,d8I(,+c2 \mڶE[u.9G ռZBL)r8ۣi; q|](+_}D@ 4a ,C P:֐4 i6횲,ٿzWbn6VY* B"]k>3#>\a^{^}vvw¥ˍ!Jӫujvcu>(KJ(Mg?f05, RkjbZ 㟲X^j=m7|;s~[7q=&MYtk=9_F]Lg{EvxKRe=׬jHBq気BJՄY-ax|2}ldddddddddddddddė?fZM/zZE$/NA6Lihvd]:q00SgP1$R䖮DBڂJ| UcQ(R 8 ޳^-s3f-1%aDtvhflu<і!%1RveAJml]>>ŚtRPVGXk)|J(z8NaE Mdtkhba:)蚆7nphηxӇb 1 ͆zRrttbm{AE^hKBR:%G!nkX|: &ӊRNj[t3&Y6ͺ˺[#ZcCO߶HO%7ccXPڊ!YPLtoQ~/~11t3 DRBL')CDC Gis@v=촞PXlqդmqF#ʠ!M]YAD\Yz4BѬIf>?$5__W_m}sR s||ȭG\=tVcjNkQyq(rPY˦kM0(F*P Osw}k9h?!d=4y QfG:@6 #Px|_fٴ:>d61bB#SXmc t/g+ܹÇ_!ׯ?3hձY#ϋ(r|:ٜP5&%5Lws(bΆBO|G^Tz1w:65###############_=a֚rKp Y;8v3 "(4-))h F(, GJ59{J*M*sX% (("P0}ߣA"PEìpvv͛7qQ5<\{1I)tm+S%M}$Vy>CDHYY዇{ZXrA!@QX>#]+!}۷x뭟uA(ehjpFQlH1(@!Faj(!knYTc"xIh2P5s%<ڸy"[3RaPޛ=[vy7igy& "-Q@Xh9خ(E.RTJ+7[CE2hF$A@w>}=rn%S}{k=y9e8kY bzϽRkx Np||½;3Iys&1$1L4BL@gdYm:tOԄN>ꀐプ 4yO9ܿB93k|U.FhZiso5 I ɨW+fz >i.ih|w|E &('"o6Z+ڶ<F.r>/"F97_Ziq&\F&=ʲ˜<-*ň0aCCNE5FlԻo؜:JN!t<$ x5)%J] !vIYlR!!F AâdƂQ!4_42ĻHQS  I=JI rqXbL ezFMh:['zufyA&ŒdC HS7dm@HBeA#K\T#2\j0bꖻp~rGnm29cq\0X^\v dfEEe cn]0hԆ֦sZi@<#/(a&%^ n[t!>Ϣ4HpC{)"">1c6nhK9dYm"&PJY5"4K64meYdǍsfeEQ9{۷?o5>"' cz=bP&G \@(t"BT1DBjEkfkgxcO>J䣏/ Fd \kgjPjMi~Gܭ/8| !;PY k"7~7X͈!gCUc?'g|RgO׆"9_-"PgW~rBl)Q:5:H69x39GٟRRAsl!4Χԩ_N0A Kl繠 -7]!4n3V Cp)2Ie`it,.ΐc!yt!%R\~t웶)|Rux$u6\hPB kkТ4h+pR^$"]ېR Q$Rv6f!P  e%@A)dZcF :Jh!UmB  %kAf/D*E u{Ne*LJB G݂(sr IXqvv*ܸy^|~j>˧z)Al2TJTJHBm b)/7i|h`m[a㰥.KHVӇI󡒮ùSMe685DOk-REL*Z&C b 4m!D A}u,&`Z!`>#6$g,BLJ DٖnxMNOO g8Ʉp}@jB34ƽ0 c OO;G mlGYXq~~t:e<$́Nxn|N9(<,Sc[謤 Ό28M0Fe[G<T""FwtC,eY" TuVä``\]dJ=1"Cu+OQQr`koDS)n˦e^-mh5g\r{o;Q شj'D>o1;>.k~YV[ǿF5ʸX-)-=8:6Mήi: -+,c6Ϧܻ޽Kt]7㟾 ?Axrb!pH$yY|bV#[\/! x'?\?ʏ i̫1_|3+s8癕#(GyvFmBbR(N\~(6D%/{HLo~yH nX`ے܁Y<&Nj;CWӵL{6>.K0p``````````````߉06C(.M0y%J |0Yj.Ch#[m~j {AЮUUN4u;Ve4^ڤP &L'c5?!sD? /KbXP*u[rT1CͪzU9k[d:%DR4xY1!$5 Zږ3O G?<zx4E*uSdՈjd2buj\:eo:cݶT˰.tv:֡3MtD"ZK`d#mȍHbץ}VL`n25-$u]3hۚU:ʲUU6, L"9Ĥq0Yg/޿Gck)$b@gJgIuuD1 b@!f5OB㘜mkoK/qH+Bf{,VK{yC ͦ,WgL΀ PhZ\+>~_zTQryݷ%¾|lanɂ"Ǵ>f2*15qo\Bk;_7= 3&{D,HP`/G|#Zet68Gm-<"Z-Vѱ>úA;#Q b45h_?ɗe 񘋋5?Ъ?z)!R[?iR>Y|;i .r6mDDD(bV0-!t!R7^6MCHd笚 U rѶ-m"D6c(IшSRTUw^Ye4;0BL4:y6. \S,|jMhDӴD1M"I=g[FSJOS{m]FckZkYk)28::"gBl۞9 xcN.Zv&3Z"޽ :&0+ vMQhkJ(|t !SYf07MA`f&iK,h鈺Yg]+jEUVIb}ǩIk7n>FQdBۆcxpr!iޱP+s6w~n>&1:yy)f o3Sd?UyI]{{wso**I6*g_"XV/qtZ*bv= *gXk~j1͞7v`Yo֧&pyw;~k7 RQ_Kf```````````````>6y8& bga=#}&W`a=(D.R֒mVt]diRa~JBv谮e2ҬkRYuӒG@t2gh-$Fں\+=iKweZQ傦hv-RRT%zcئ]RX:è"5#Պ88h2ȪnX65G7'+&ƨzDꂢ3Mj]K/RHDX-ZHޫ֚st %2{SUL9c4*1bbҩ;p^0ҋwt7-tMs*7t"E(s`' GJX@ b @r( /ox_|}ez9n~':ӟ(-qӟL?TgV:CT%0yFD8h6[=sSr{Fq]G=i5ϿWbR'otmC,H-A,B(bHC(mPʠUvn &fxU}|a/ p'nl8000000000000000A?oj!nB. 2 S2Ɉ:pD<'bCEAY(1AxO@Y(%?X*6;b2aLLdY1:+(1yB`?V* |Ugse|t-$yjޮV+^uFU^q<-nK k^{5}yT&np+奣 )%3O>9ȕ+W mq10MMnm>T:3wx>F\t%l>+˲א7w5 !l5rwl^SټxG HH IDAT$oFK!6@iCdl&"];5twKnUc]c M dƨQȘ128RᅇKar& r:\({DRR%J)"&8O[#Y.nZ gtm˺n1yh:>8Y>:lBKE)$9K!|NzgU4, FH :r |) 7\) eX40yFV@W7hhZ*g{frJmN@kT,.p=bI!$yQ8?mS d2Aj VGw4B ?'YS2-"LSCkI]RdYWHMS@*iE@ BƀBD! "FACһ!CB")_!Z!%igt 2^7vbt\;O!uu!Ȳ\ |_ LCc?Hnqcl |7~e}xU )Fr?t0S*d"Gp<) %!ӒYs~r "l5RJf9Zk5r!\HJ(.l[֦ۗ|mdPQ*, ]Yl^-hkBbX/h*ѝ4y=p@5px*X5X s||LA}qqBۮ,s2! seʂn.DiP]1888@gj4"J8==/y@>1;wcڵk4Mcjuf3), n}'\A)Ce:5HޥVt orū rӪ}ԽlJbHo1L1Y\4\|]n1{qRޝx !k| , IrvS)PR#3/dvhw'gyo/Jk5" ,[%<i}qyѪ{mc)y=000000000000000IcߌNNրso6*BxZ# D;2 ,">Juvxk^1kk|gb@ &,K}Ktivn +/.N(%0" k=ˮeu@b#r.w{'x.Dڶ"<f{{{t=.`ޫ۵ IjvMt:8:c>w֢3ŭ[hۆO4|/ _4//U&cm,#} k7_g<8;E (G:k/:szn^>]b6ZrcȴA|jz(VJuѭI-t֢Lݼ ]#ԢͻlzMȮvvq;u~v끁Oftet?0YHRiWtTdRd""Eۏqѧ8ར(&s[A`;RNHջ#D IN<:=[FE-; ޡI[r~vBQTT >W+ =de40>k-YK׶("YFe Ec`O'~=4uMۥz=ƒ,Y_,9OVkj2"%Ӑŋ n_c m[Z#h`:)R'RRv4EYVv,K %ʘm{/ka^X.Y.[C/5RJʲ)H)麎d#r+ܪR8y' x(v!C_9/vl;~y:?>PW 𳟿9zg?6Wa$n3lyA=ܽ˲^cmK]h%xoģq#()Q"| y* tMK4۠8;.g|ҝwυD@g HPz{ǼYr=qvܕ!D| ׫}TY,uQ_') Ra0iaZP,NdL!юj)*AFirQ7i ]Vike2)x bK8LAVƠ"/KFzLg-11ޑ)o[Bp Zh=9JU<89CHS/00O1Z Mt?FKx:mZT:~~u]Sʲ4nJl_#%ֶE{wx9;;{p-3Q, 3O{RdEڶwxd\?~65fe12(\gxYQ)1`Qx %$Y)$UUq=ƓɬKڶZۀ\x,Y-WhQ%9DE4 Ԓ%:@hɧ{EkoWnCj!kZ*s؄iY$b]1W57>"_d^QY">l__/|<7)2ߧ )_|sKr 1rPB2Ft]GUi !\ 80JqG#Ls HPV65J۶I^.ufy6}?8茛7wa69.xbyCfsxu͕+W8_.ϦHV\J^rɭYf}|ԌL;҆9tf QK|W4L2$˨t[-ܪN!R'...(r;P^a2w)3rDQ8??5>\kIH2ȓf$gH+b]ȲLK Gff\=E$y3ӇxEbAmK|*_oy^k899SzK}/Ӻm")qC`RVUop}|hq% eFa$w8t?Ԋ]R?tNYu_zMaP5!D=hE>7I%-DO+ )S[>41h-{FyNr$'TUt>Y\,V(0YZLh5~1YR(ڥBPefym3tfPBrt||{G(vMA4Yӽ9Ũ"AXFSgt!Ei"F*id2bqFZszqέ[X_A(Vy W+Pw1ɈJ5>uhMmʫՊ,Kno{@1cۂNejj=䓎 ؜-t@@ɀDEHkv:¶\2DbpEYQnC%vuRUUDΣY1JmXQB)"mS,1Xp5]Pd:X &/eE4mV[Ьk$ZKWe+ Ҡ7R sp5I~p"/ߧ3NNN8<ʭ#2y]9%e*w!ϓG8+#7yp| Z۰h[XTE к@D_M4y1!=AI#F9]R ʦ1A8Ƴ)wݥNx鑛D7#H]׸F,VKF1eYbNmޥֳq.T ܿ,Quc2]:eRGL&SڶeyOQ ZeMMӀVh#Xpe^x'%os. )JهI  ?JbLxWz'0?3执nOp4L=C@ksє/"[$H,//+)lQ ih#t]hM{2n1H1!0¢(^mu"B#BDL~ބЛ]\6j͝ctE-Ȳl;iu۶f_mM0{:000000000000000>ޓ9wDۡrŨ*%F;ʼHzB XB*3D,KM8 <ԢRy |Yh] V,{qQThc6ت.fIp,u.5K_\>SI_ެWtz)Z{jW:Uv>`a-PmqF538 5k)w⍈̵ }(ⷕkAmN>fk-#n'b- -7k]uv!XV(@5.DXw]K t1ΰ>"8-5B9G' -l4rQ0;op`֢p8lp,WdYׯ}=F'R"#8 VKtN ݎ,1L-rYv=lK/. 7~yKzޔ=Wv9OF=1ŢBHv#1H~~??{f[JbNƽ=A!$>)ɹ9]a "]ץM@[xs]78wJ?suyj]˻bTY1gΜ9s̙3gΜ9svG ϋeh]C߶,e@$~@H jQQ%^)Hjl@ t~7FdRH}҃{'B[ۂH__W i1 {V ۳X!xUj>mǻ{N`,K2M:ڙUUY88ѵ=cSuy{wv嚢(cp8u]CjnzflZȲ,A^h)o2XKe yYus,:GuyP/.s2c1 sC^!|kիW<yY*3 WԐ{4lpQpٞФMNk- ^䪪Rh^zsk{ņDנL˗w>`ɧs pUqsv/T Rxۣ7{^'>w~_#J%wRFi"%Pʐ`dh>$3@U1l"aXt5u fQ*0p;L ENuZP=t(Q3OBRjc;z\ `eEQN C L|\)A#q\03z~Hv=Xy:gΜ9s̙3gΜ9s̙g0Gr2ʀ=?z|(R. L *xȊԠZH0*F@a b$:G칸Lзuk%ѻԔUV#Hp)gl[|p9_{?3t$5 (% !"JQ! }cWc)%18ेOTldi(jh> mZ>`m>PzM~pN-] b7"ӞRFBpk;2S{Z;804Scz! S_!N <–l()q'kbƜ9s̙3gΜ9s̙3gη)xmPѢDDƀ .i0.ZE)p40Ԕhɦ˲m[vyrmVGԔ sDVclۖc)jdZ[7 0u;0`B;!hN<9s̙3gΜ9s̙3gΜoK0zir%!ж=H 2Bhۧa .?1BS{q_v hL*IYB Q2Lb#!Mhٰjd8lhc^$K Nf1s>qI)Ӏ;s5'5&sRH<ܠ !LmT}!utEivz̜ :s̙3gΜ9s̙3gΜoc0}=@ b0Z{tkbHPں E4uɳ9%@X2)"FH&;o>6A+bk}LhF\zQ, LaqLbk;;$ v;>1,7k䋷(\^ ٷ&pH q8G=ELz)%W״}:MӐe9R) ׯ^ry8psuuEk8xۧ=MI`ʂ㡞gQ\ju+:SDQ$ qj 0wǤvJ^"ah.7)"wes8l.i#s7+ 53YjXh= =3=Ȱ0m'GE>p8 :@;]z4h2jIߦag-4Z*VBzMϟkX@gBy}Μ9s̙3gΜ9s̙3ەo<-P1:O۶ͩ=&iz[2=<,ʊdvaUfERym&p|lHc2-|yrGF\.yxx G TS]˗o+v{(hmɊm]'[B߱^-Q,"֯$2lyQrC}Hq%2ȓ7䈶eeYr[>xCKmYQ zǺn:6 yqjjn..{0.Nϡ{sfPuG*_ҴBLQ8ďb ߹ q y7AjQ|)( )5ޟ   )ɀ "9Wczjy?Ozp Y7S9F?}:1 YSǤ(jyJG$0cN*27̙3gΜ9s̙3gΜ9߾|at>+IG(Ej.8<6rֈ&Bu0]ZOH}M#'GseD<1e1';ixIyjkv1l-I13Z!}U8 NՒWxய:cɋ4лbA.ac&4 dT%1 꺦*=t]R(21|qK{ֱ.2yN&asBno-OO;|$7YB>?F_|@\E!www9jEQS{^__,m ktL{3頻zUUM|VI.Tk{?%.d$ $XOȳ;ϻw}Dy M;y"fwAq6η/@> =|~<3˒:pZ ;ӏvB@}N9s̙3gΜ9s̙3gη1x [ޓ4LLgJD\o|6 ,"kԐv EZ5-ۖN 8Y7 QmrrIm'wXX٠qq{  [6՚%)~"Œ|f\q84mK^4^~=rn9bG.//BprIVv{, :ϸZ,tDx&5_KAԡ >(sz}S IXV%!2Hy q}K^zEAFZ׵X~n OOOE5e4%XWqssC紶g4erƒk풻{rgԚVJѶ=:K r9)BR"d$+*2S! m#?я.Fd@$Ll@ Jɘ\F eA,ڼ1 Kuyam7p>5!w1s.6x]|L*seV c-"ikWL$ŅZXJZ.$WtpnhJBvO^dĨy||dZ{4(J DD) EX&p Zt-ZuA^5yd@b4|MδR`"QJ6jl],jbaQU- AjUNe[TUB@9^>)9.+Zd Y! \IQ:S,Ă\u wH08nooY*i Rl6n,}ovC VB &Us,˒#xeZEO,P{HiMSGz?HN>(z'}8 B!Rߣ3EDȤ!J($h3*#ʯ~'x=1&<#9;$/Ze3-B:AK{<$K{8{=\>R{~t̙3gΜ9s̙3gΜ9s}2ϩ[G82; @@QD)ab 'N JBĵ=Yd b՝wUi4Ef>i(=&1w,Amh-kRc]"'N{ȫ$1=)躖}ksicxwo岢 oޱlJO ,a l>/8QR AK>9-=-,lRm4BurP#PBٞ\\Qixs4p{HMk4: [f.h#QRVC(4o o>qv:"9Pyv{^N?Hu "ղ.TԴv ER۳ZmRq?QU^hrVXJI ZED&LM[k!+J|bQ!JMS Mz| UY=u}( bZ^|#r9ܯaH'ot ,]ߦ8neAQyNs !lG^\_ DAvX/D_4Rpc|Z@ B %bcFp]r#iz.Ow[NE }) ~5>DB rdbiԮ .3]ۣن͢k{b{B^^/5H]b'usJ~%WTɴ" k;te86-{E{ ㏱\`^!eRxEA :Oն' 1۞Ñrɧw"eҦ"Td,1P%wo`ꆧ5EDIU&9-ϯ_7l{)?|dX2o?fUUDhEi>dY.vl),Kڶ`QPEDA(w/2y9HEt"z  D30;OpUJsj, V+@bm h=qjќ4 Qq,8l JR5;LC@0U] o{; >gw8 ' \Ɏ 1FE%o=OGITU" (z@v^ὣ's̙3gΜ9s̙3gΜ9ߦ|UD PIMX68@(!"BT3vlHhfZeH.:)!|p"jh8[k5۶gMZlZam=w1 C&h2=5A{ ZeGOu-mWvuj\z%á)o5GO>k'\5&/>Vv:\^&X|<R CmDϔnqAߍwBu 2zc2d۱^Й?|y#gY-ղ:Q`xm5|VIM6 eXaX,qmC4oO_Uh)SИ1z5Om@۶9)xGsb|"$]JDaQbࠥ@ m$&S(qGz?̙3gΜ9s̙3gΜ9s} ȱeG2#iqv%24m=Dc006X"mَ|; h ifFTPf!T]S%yT&k=?qjA{Yry>W_C i[KQ6-H-M0ojU*JEΒ}Rx1%OO[.^ ҵ-ixMбujeU"5QϿ^\.Iy6'v'i\]1"Œ'˗/yzzJz]{MY{ H4,ח4|=^_ǯo%˿ (a˲@n{d|4+J=B(ŤX,>563.H ϛi+ e;}O :FŤǯϵ=}Қ@>lRAVBő!o(@>6 M$"G HcjGz1AnigU*\LʄGtQi{nnnr~"R 8M @<ǻbzM$bFxKe\^]:_\^q<Y,M$<ۂ1YʓŋI/^ZЂϲ(Á+:4 W/q۷4MCc \W<>>RnɊcѻ.-*ti{֊܇K D8ȴ5uګqM߉9i9s֨IM?!yc[*9<'McA .9@?{ۚu&YHu)t|hEqu>X躎vW/hi1H4hdEI]UEu85 Zk^~=m_)69? >CΨ떗p8`4tE(I^MǗ;>|Պ yGRdRd,423;I^zӱ̲ Acŋ86}Xk~Ek9!g5G;с>6\=al%rŸ cF؞ͤHPqiڟI3kiq{OQD6I 5鵕#љ`h%Erv8{(y9s̙3gΜ9s̙3ۗő  I33 NFbv߷ýxnIBr !&1 &qnj+ A=k25 *dقx8$hEc;{1'X>$m6hBӖ_wqαZzKtyzhcj 9I[r{{;cXmO]B# yOYU]Z$1{בi[51X`\r1$_&D\\\?nnnЙ|ET.\ap\< Po۩,]M[inCICeym'( ІP*A<ݎrIӿ|j(*" $2yFE5@Ӱ> 𸪪䠎f?RUWWWH)fz:w==~}<c鱜w6}ϟ1?YTtޏ*!I% x <.-͙3gΜ9s̙3gΜ9s|"Њ JaI;Np7g(aNQ*OU i~GQp9xOuؾG*gcjؒhn*1kDO."xAV^`ko°,'Q;T2fHb "PeœWRkCzsw3LY j&m{GeV+=~)}[jAm@XXXn. sHѣFJzEeUUccLFDR-Vy-e\^LF[V* ZbtHɊ = \.״}O%}B6h6޾}ͫsG6_l@s+\OO;V(RsZ:;W P(kBKʤwMQHA"i1#gRKzl5[kQF㜧*...&~zoL>C:mƸ~3 rz~ibΘ!JIP&]!cDkP%e m'cZ{)Bqosw^Ꮜ0>@|Μ9s̙3gΜ9s̙3۔o<6RFoiȟu=L^!B @PІ*䧍""Ɩ}zj8lp@uHhT(mۦa BHBJ%6m:r{ȾG8amwlvEzG:dZҶ-6x(`yxx"f&t# inGY4ȯmSpH>W^ C: yxx5ON A%8NAzp=yޠ(h5jvc2<92岨nq}}M^m<,Vk.G=C\^4 uZt-yS.:s}nI0*B"B)2C[UKV `rCB(p10$On:_F=ϳqMNsssIc 9@:n$ih nS?"ǗߞϷyΏ˻ ,h۴`8YaҥL #&z?#"} @顑->|!J1>c8gΜ9s̙3gΜ9s̙m7F eMFz6FDޛ4IiZϙU2oޛwnz`ņ O`6%HCumL,GTI d`jxΫgf%U#mk .\Zb(Y~ǁׯ_w8;0#R}:="a=Ue v;'\o* : 9K{r(mTp"O\XgiFb\O^! AH,JT$A[HAд2OO'Ҍ~Brl1Fv0ygaqӧLo?^3o^@ ';4tM?=ଢi3H_p=I B<<|mL :?6giZNnw??W_X&v] psXs-O嘥)jg9ȉ'~r:4yr"5Y'tݫ25l ĔZ#r& 6S4~o M)sQ8ٹvz4쒮E~D9 4ߕ-} Eܪ3恤)/.T?n7grDL΢ j9Z{PYDN %K:ҒN9@mIn(|~Ix?ŬviV5 Bh7Cv[ri~mhZua1p [{rQ䘈9?JMn˅J)qZ4;kZ>v: \7@隖)$^_ȿLi,SNX3,C$Yˑ1d9ZKk3w%i BU=wB  FePx1G 3V "17ZS ۭ.qqt⧟~BJIk~躎ϟ?cmYxv{>~nCW〵s`|S-4M0 0êuG[s-Bi@hE^as$$1mkwm,hgm`n U,weyy.y"M#x޾~u~;6mԆs$(wQ(Ȕ]$4ቖ#z%/?˚CyEq3HRHkMoc3p9Lj$3>=t A4-i"L̓ g74sS0Ҹ4Yc*WRi 6#m[/ ,g+]!zI' yo{w5q&Zя+APni_9,=yw}zxD $!L%9w+m.C t Z*Ϥ09G1~3Պ}y eTĘ8vsww˧k9 |,-@jkFU><<9 }qsI?NeeۢUK{P Ahlt{Ҟdu 4`#R͆V7yQBJ! no5 W Iy1$T{&lRʫ\|-Ʋb@_܉ "2P2>r4DN❏bٲi9ǰ@s9曇RJb.`PI )QnτQeyn BClZNL^X]7ӊVC4DmJkrSl2) 43|Ҫ4q읦)=MzZS8]1Nҟж #\9G08Am> ()B/~$@-ǧvrvky<)rwؗ6g;ڠYW.db#ak2)DQ(:#@KSm nfjsǜx5>|uYR&aXkiy "9ΖR"D2qQqBfcb p=h64j'e1΋Eۗ.D NJ~wu{t瘾/E˂ھl]/6kK$r'n[S"ȹm= y9?/=J)!zy\_Cyvtʙ&qdqBSnBz_gRH)\RJ Eq^Weih7q^`uȝ2g[z-OW AO>M7Goÿ8GB&#AIH!jA׻1"3M=YH2COx8_hOOŽ,^ϳ1(`Gәai:$?a¹%τcB ȱ83wԗt:vLB"3Mi躎0EN  20|qQ0 1 D04$!4 Ō DB+U@t(H˄ w=KvXwDe٢1+R (eoPz^?_re[x,m[']J3Ji)'fιgpxM|o%x_iyݳ4 (O)$J< cas.-I9`Y N)M:Ͽ]b9Ob"ju|9'ZlF.J^@0g/1⽟wq<93MB{_j[/o_fAhWCU<==Rt]ǔ"1% ka_G|t tNj*cj$')ʾ7McdG0Ҵ-M5!4sǸ!ˠ>ema`>_]q3Sd)ֶmXZgZ}q<GYW3D2B+w{]8_W-}3 >|`&_A8ky04 l\%2J!)31 xi=a=Ch3x?ǐŰO4ԗc_s oFq*J8cnϰ8\B) $z]Afi*%1xJ)<8& 9l)_B;\1'BzmRе:l&tͥs Rp =^>0#hzÇ3Yh%"o5?+߼{q` ~x=FIN B(rNt MJC<~_`d#^<#?1 #!Џ,eDki ( !)h;pw\.N'L8t:_\|R+&3ǻ;B{?w<>>/s qae,,x+a{RJ|Vz1;=(p|-./a˟,lv;7["#Ӽ5D=/UiKq]K.mcUJ1N-dYr1n-10i.$εe!&R Ԋ$` 1)^5j1e^밽y VzH 4>3Dnv{TG,MXkR7WyXl)e,vKx<^luR(3 {i+ٖv1k_kv#J/K~/..4iCe<23TeyILJ?#aIqypjI$)=0/-@拆@U83kŚat @f)R"sBI̐|XARJ % ̋+6D~aS,P4Hc.M@m dfgq䇽Z 'NW}|ti:IBL8dR;s-EπҘC s8-Ogm|pXT1H"z~ ??rm)ae{~p~Z=߿G+>|?DZH.*-!$s%8cs - w(w?b=WߣݎmZk@2$9aVpMzѫ,\SҠfpsw]Wqi>}D`G ~-mJ[Yg/.,Vua/{Vg97aɱCc?c,C>%Fpa.*ώʹ2F!dhyo 05Z=Z[_Ky-s Ȗ[@6 Pmsnm:@A۶@i>m]z:V R,)4% E ! + )~T#nۖaʱq$Z<]F:k8=\x!e4â2cf"m $ù\58WtÎcmr *DvÇ'rqG͘&{&#rrl4Yi1~u6<A˕G\2d2%.pٿ?O\WݎJΙ?~Ķ+fg5zea4.BO9_&4G7;qM5AK Yb<04bpk=kW m9[ȼ`ؽڭb>N7}?q:= 6_bV󾔁}|nڎLE;ex`^c c@BFqDP4GƱ_hPZbH gʖs ^f"ĭfOη蚚cyZY[3κ<mД ߬E#B+BNMȁaЪG(i")1;`%4M+.-|iIG!zm.Ƕ ]\ u !b$C% Z}dNhegqFͧϟg0 sέ@l9|cעqY4Bd~,PD % 9@i@Ȍ5 +3ӼCZ!b^K]׭ sq7Ot]a@J2!5JiNk@L[c;ȖJPs7 hcs30lC.- ظ4XlgR5+/cN(t]pJ•z-Ƚ9=i\p~F'HL~~!&bscگ? HYX씤%?;39G>1-4ͳE[^SSSSSSSSSSSSSSSs7}Nh$0CޯަB"$LL0N9 G_fW/8 %L#b\<ξl'\!,e=t]G m ~ h9qxqH:X#Mcv;@̥ihh Ƴ?n~nӇ?amCr"qezZc^a'] R!^|wz"KEBK(k wcSBi:={5'2o w8gci3 WBNXte(kidm8NHH"I#Y%R_~ \C*ʗkɢ sfLiDO}ba:lǵ=b⇎B"gPRdL !(:v]xWA2%1Ő0B5/28p;Ҝnc >(]\뇻= n߫VCo8}"#Yuُm{iGpKgRhļS$QDHH-ILY>/w-H)I3`j]reE))ٱ2qݟ綺/=Ϳ1FjYÚ_yB@i 4!(֌LstGx]snC!Bt6tKyy[C4 ~xVsz*#7g^ȧimy@cJ moZRra1 ZmI_[ =LqB;K$ϠW! s-jB uHʥ jB'^4 &ʠ?myhM&H%0B!{ ^I)cY4 À !Lˈ6 $ʂqr鯴rGw<<<`w|D[C4"S F!$2Tr9aN䜘BJ6f<1he6U_6l7oۺ/A\lz(u[xٶFآؾ?<|_aL%XE"sZC/4if,硁RjOd)YWo^ݻҦ E\4Ⱦ=Rjci|~nCBNwϓBrѣT$~G93ECg#=p<W@ ^W+ !~G쪍1ӟGy! -,u(OOOc;'m˟'޼yzErm,8|/>ߚa(MҖf3 XAn q ϟ9|ftd@(z-b}0Bxhۢs%D|\w8%>}\ v}weX`Xόcs)KxF 8tn/ۇ޼y1?r5?&r>v݁yg6}0 ǻM傱ea4ϗ20E,]Wqp|>cMi-ET4 ubb?⇞':9H":H6EYDHKPF9}w` ,jܛO4 (ߓC`w<ǏD q򹺆,s^KZq"&mHʤqJ┤u rk4F ^1W*"e:rC)Cv} quHwhm2JK9G5~%I)5!@4<&|VtfG0vȍBEe) b<#BIاgֽ9`zrΗPҬmE|!mS 7ݮMk-!@U)nki/Ѷ5^_ꗭK:QXsSY$X2ض%fЯ G󚚚a2PeZF44)ivݬȘ&\!`|}M([^g `"KvkXP [/Yes98, 1-X\/eU2ZnC膤 ! RISiݿ5~eJh1-,zj+HBЏӕOO}lL\Jƶe rQZ#O<3/r ^KqmP/^ťK}z/ZX4|=N_ݎ궖߲_vTJTt"}{0]!LH@i)BY2S@JW3H~9|r}*;+\ϯ[yBAJRZ<4Lti t]L~`Gvw0am~4 >Nx8h뚢Әg 41\v3epv;.}OZ>ۭ`lqj 1@ǵ={^֪a]'&?v|x8s8C-)ft!3vrܡ[LvBqS$EOĀ-W ~xq ɇJ+T|z|bL:/g~|ŇLx+>$45:ޢQ*f }SQHaur]n=KkxU{ݾ%ۆ6[XzucqIopW}GUi]=uAg>q&2h3G.g2Z*Q\r'Pr>)rsj E&Eg _N&u)E_.c7a2| q7}i )1OimC^A@ƺ`1X[HQ^":@jUږCO@b7;iB @BpS2q1F(6p^i5K ,aPF 6i"H AȌ뜛yiVX fd>?<0LAkr8זvtӐZ+k r V; A ɀ)RP9kOHr`'W"@d>Oci#! kA΂FrmBH4y253Zq߰sۜ9e}5555555555555555?|0z7]Rj*z%LSn{IEIIL~ǥ꧉PnߟFs(44V6CiV@ڣFkA S^Ai8Nl$G.3Gi2# ym9~Wr4P 1r8{!ꎖRR)YROH!$/<{s9@c,!'h{laź=mpCi2nC_Z9bՔ)S"# c"Yk}kv K*N)`r13 ^T[Ӌfdu IDAT SJLQOˀO>Z+~?"8+Yv)Z)BȐnPv;T1F.,S9GZ**6H4mWDK;ĩ ]f2roiTO!)mQL1YS}oN8H1e!ʂ8M$2!555555555555555?|0Z[})Ƶc8<)ܚܼf8кi2 imGPF>kZC~◾ \K)fv|cp82p0#mWr@)4 -x~}?7/XsӴ}O皵Mm E)4yMӬM?}4M4f_Zxk4N(t77GeMӠԏɺ}$ Ѫg: QnNG2\#R*y:]Bljӹ'$hYZJgywwww0ۗg"vπrͯfPbA$ E)'f74RXrK'Ecy,߳f07]Lo6 5+m[r^i#9å 8pyh"Bv]iiRvו8^n]!{nc{v#P\8d9@̄B"7op8yM0cS +c b# 9R̭Rx(}M8N2LSHh=9H̴ ś9swwG;д-ƺPjȲA$B(槙IY\ԠIm Mrg2 F^S 8=cJA2TP+nc?imi?7 4h֢A-FGwPxꬹȹfTj"\TtJcyg>3حnwl!G <ӁY)Fn)mcgr+t9XBA)4Brl%3X)LRR,!IޗŞ'ym6۹_%zP>SW)_$\ZaZaO\xjjjjjjjjjjjjjjjj~natyV5($ũLJ#5G8Ҷ-燧u~/p)FsքIcbn:V)%4bD3gY] [G*3t0X> F,{pɲL]Þ" YP#PN-4WDQD+ڨ\dER̓yN {Xkk;N%XdsĉرcǷx%78΅Q9R4{988(YN*Q b! <8X0VD) ɓ, )M1v#G+\+fy*[ޡ-y\P[L V֓3ҙ [NqFmAjE+(+CB:r+N ˶X \{O]s='N՘,gQEA^FebUcuFf [[[\tхL& e &ӨȊ^Ǒe,-ikɊ:Pc"7TcHRVt7sׁ.m|>ԩSb( 3:qѼ=5ۦe:uU5ĩ:tEY 'SVذ~=dS븾Lz>9nh7MGl) Bi蚺WstCYIʏ<& Z_+K^ӹH=8rU/0:U ۨpg    @0փrk#cZ J)v&;C{?)mKYC朣bpe4ip[10B6;c-,]EBr1OSBV+fY|=ZW%u|M&s"0W\{NfB!nvpMvxE30L>ZU?=ɄZ]6u;]UU tMMi FLf]p>jQLɄŜCiAiln( u \EnkrT,SI Zۜ6ǏQxㅇرc;vGω&YEQ₉3͆&1ũ;]<\>9u 7dBQa?txOn.`m>\ȲiY'Os {{"ͪoY PPЙc'=p(9#>}1Bא0C|w cB7VlQ=u]No(g    @0͗KLߞ@*CiM0B+2vιsn5gܦ:bv#gsԜkQ,s0Y]#FH.&U\O&j$@aZ`c۪:'j'C[2 :C4};7n2cx640)!u1L8<{Gr>Kɑt4I?T7z9w<˘ͪFfq/Nk?shۖl,( $1/38\pM{OfZ(qvh/;)?Dw]7\J VeYR0q2ZUqmc"&): CXV(/6]AAAYFwcA~QǠۖUPTt.`ml;e ,&?h6hmzl1mk>Hr77P5y^`og 6i:1Xk/-zіMvd5_0LhcQYzɲ8Oۍ(04$5xϩͪiT XChCIX,(ˊz>)ٚr9AMlE (Y*(t+kG+¤r}@!EjvP8ؖ50Q2++:>6R')Ayc cs1]ϘlZQVct &BK`*EMqk:5{O1fC1m`Þ걗:;&i)!3%1zJn3\ײZ4edôCkz K bq.޷j~m&s)rǻܺŎAkhnã^k:FIeXc`.] (zCѵQ| ʕPg:jfZP(>    ->YA)ݮXsvh#eY4-KVԋmyTi,W%Z m8xN"2,˚Bl Q M2Աau4G[N;!،VL(4aJ0zA|x Dd Gp-^y{ew1t|65z(QJedeFUU䯆U1@Mfvx7TIWX,ueEQCTrIt} ΅jP ni;߲XB|$FibtEyGZ2ӂcl^.5sBT-O4/@TUsLL4kд)],7!5!2mihȧoWOje.&ȧ ӅcܦAAAApևH+ݖf>Zct*jDU`Z0 nb1 9KRy8'&Zk၆{E{9udReEp_Zܐ冲ʇc4t`y1D? F@yBۍ6tHnbSyY\.)CH>}F~:95vޯ.x a    <0!w>t|Bnmm4C;ЙVK[ں{ǯ?AU;zf`Gf,`t6*ңqX*pn1ɲ1=ڬDNZ-hۆ6EQ(WKrJj*~Z<О\Vhe\!(E^FE}d m Q!`krk!뚲,9ujlFasn}V-N3$9t0(V}h;;ȳU,2p:ʼD+b(՜jVcb(mt>EghChAGW}sT ֢槔A)>`d&댠<.x|;IEj7R} kGru+֞eh4릻c/s"ǡ]Ʉ(X.Cӳ*)_=Ӷ-;;;c@ ~00!tXβl|>gcǎł'N5^wgS;TA!,in-:(cTd]bsɊ)eTsܙCZ> jmMJ%tR >jJ*67hCBlmUcF  A" ͠\Xk-d2o Sn֯GxϠH1Va{gF۸84֮Wu1'nގx}.J)C9g:qmê^6khV5Ǐ_|>g2)T%usBdCf>[C1AAAAn at `,3u$zd~a`aQlj+˲&G]F RuMuloo_OmUr䢭$I;0N@9@B?j=+RP8P+\.鼣Lp/)<8oq'50;,+ f2Yljz|JȊUi䤳~s\ x( Q]ϛo+z\~J'R7N;} aC^ù99մ=J9o5溎^\suX.pMY8/8'n:~}'Uڕ5a]'Nt:ni]9&Bw1Jz`dy5|EyvY,fd劮@VtCnvTE1ih:GӴ @<\=n WFw}Z,98r5k>C,Y#/ Iɹe20T岎-e\iV[[SڶC \.i6a/}E!HQfq` nc@"Uc|w ɤ IDATAE%Jn7z=z㢋    ->vtHnت휣sKߐ4똙r6MSk|VuuiwC4uX[>(VuK* {yXan,`j\^QN&tmε<0\/5HSej3͆vz2u2:cCf!5?ڂd;V[bJ9kNbHk=Cb|bܻO_pfrL"03zqh? o6иl9Jqcey,5dp^ :? roE|6>9:߁j'?vU^0LMkB 'N`8kaspp EYٌ͑ k5ViE:ideku4{09ԍx@cj§p#zh2vx̌YT8bȫ50Uqx`VSsFg 9u((5:?A rZ?akF7zrG+P0R    R8h`l{ɤ)Za:t-ƿMf~hX 6McVm ˬqJ!GYމy{{vUC g"j1zd2|q\KQB}v>t|V輢S ؀Q1k:OڵrALh& K^ A@bpY:Ò`{hF3 ,zď9)|P}uXBо"JtoR [k|JTڛ` )=nlmm.}VW:f[S,Σ cX-kuCoi `rPdYև:sT)nfo/F=C HH{%{M\S7#~S&3X*0a]RjJ u%    09rdvq(4lvƮ92i1zܰL6qQgYphߓ9vM 5@T!aUU4nX;=Go 8GC[zFTzLjtڼIB)’MȫmL>dU TltkkbWy wc:ax_i86b}o aN|NL=У@u|u0i#?sthCKpc b[ׁU=npQ̶Zk9z02YZ;L>H Z)6Z\8tͰ^LIG J=yM9^Ji)Z>KAAAA%sևu]suע‹.#jjѐe9KyzPQ<*]&ZEʁ81 aKacǎ rdVA)JM t{ Eu][Nb]xX ]jU`9G8KӴ7jޱfBapީ{ڮcRY|ص <t6P% )'[mLQ06L;  @QUb3;~aDᦱF]t%>saNt V" MߨŠxjF)0Ǎ:vƠ˜ߥ6uZȎw̰CUhښ:;ZfVEYbmNUUx:>(Dkjxo<χk.Vmn/}}Q~snc8a:|(m C8lX *6:Xt9|bЫslAկF|P@teY֌nuz!    7g09;_㰶vEńB(}wFjmzUJB #1Z$ijEQTUշDۍK knYei^1ZW0p{{X/i,#8?4@q ʚ>6-M_<.xtfSly؂2n`:zC?ۤ75ߓjad: Lx5] T&7A۫fz̸‡[٧{ b°Z£p3Y,2ׂQ'z%Lf)j)s=r~<դZG|Bf~۴kUJt]3,(u [t)י!HŬߘYVu50l)?S["Rh k>C+BΣF[M?GM?w^m)H   pK9UU9 e4RQEV _O_VMa>P!&he*?}T =:~`͍±~$m]E{ 70L|t]u c ySNaa\Ɔh,ѽ7@,cYtY,}\΅dx)s),+ fBV] Sma(mƠ]Ǫo60908m3c|NGmtD a6Ǿ %qzxX/Vq0cu) >P@TP(|I/inEݶyEν >w;7V5щu0/(BKacF800)j ZѱL;9cH !B#uO0ߵW7RC`ѹs.65ÅFa7/~yB!9TrmbQC,'AAAA=g}ݵ0¾;uї۶X*F%m .2k >I놶q+Ő)[)4=vA}_VlooZe\!hYFee𶷷lFuM6MӇٖ8؟v1Ghkq Nh[thm*{?IBlj~j0T`#鼌à ;;=n`7 'ckn㟃jڶ*s4Mu-8-1ЫChca~-8˲$3qHgnͺmrӯlQu_}mZuk/ZiŢiٙUm[Őkkpq8iێmNZ}<(CB?0~=&}+24 ;;wpm7kk-T\Vxlmmu<ރeb NZ-yR7KzAie0BAtmMGޘ96L\kp"ˢsжPSde,ʙyl 3KJsȳ}Q Z)+,zĒh :}C;U T ׁEt{̅ D%HHǏuؙ~JQdq:5_Hgt::ond@S/YVE/Wo]K,mMUek2l?,/w.JCc3|K    ܂8bzPYjnwhcPaY>l#d)Du6j*:T{Bf#:hwf[ ZZ 0.66ۨjhe6rf>3mj^_Gj&W$Uꊠ%h|j@뢿6W-΃dy9 ~-| =8|q{A|1Mk@ӍTJ[_ڊY {ۖjʼn'ù *ٺU\3O5Eo3;,@vq>@ի0BtW O7<ʨvөi߇ոܸfSث/Jāumo9\B|)ta#u&8׿3dد!ZGӵAAAAnIatQe6zN[T66)=TU9S|> !izY!_ f`ڮ+:O9EQ1LGEǮ4Xʲַ>'˜ Yqp? rKAtJjڊz8ԮwcӺahu{e FcAgEW9Zm0yQa e \oC{6Fz 6U8,vlo~f zFA1bja R>x>iCL7<پVx\WjڏMC7%AAAYFU`6ABAx),JY C{ҹ8#'s4r.0n)l0jm<ϙQ\zI,L&ַ#cc%ϣ7i2ǘlxm)[.m;mE)6zRȚ!yGPxc(4A &C,LQh469cP၀ x8܆NFo;]B =w6Sy8馡m[Ov5[pZ7 ڊ"IZxV+P:>WSP7F}3;^l>かM ;n܆#9Z3BλLq<h8m|.!kҏezO.tz_EGE O3%=N{jAAAAnNat:ѣxb aCpLuvصs2, BC"5*#t:N,ys#G1f6xJ)jb`wwrɑ#Gb9v0a,)k |q0?Mw(Ki״dtqh<{A4 ipg:w#p-aO~bx2_+|jb|G]/ ʲJUѶ喣G2L躎;,.8F-GZ\/{DZ6դ)!`& ǫkC`B@jc M6Zk _ ÚIꑨt]G?pXgni`]ף &OۏjZ1YuE^ߢPtzp; Ӵ+sEAAAA89={>߻p8x ' Xq-ؽoA,Oś߷>/{!    ;sVIAAAAA/EAAAAAmH-     q$AAAAA8F     g AAAAA3т     GhAAAAA#a     pƑ0ZAAAAA8H-     q$AAAAA8F     g AAAAA3т     GhAAAAA#a     pƑ0ZAAAAA8H-     q$AAAAA8F     g AAAAA3т     !VןƏ4,x{"_. Oi?|^7~̈́9z3xȽ}h7}?ǟ;/懟s*^̇rK.Ko~ko}M<ⵟ8p#1oxB9bV˞b>H5|u?.Jʏ?BhAAAAA Wyᕟ斫żmװw'y=o]>F'^q9~))Oz G~7~ÍoJ>C_wѭ7豜Gq N;;׼7^ۮ a44˙y^G>2s/# &q{T.{9yQռaeJ{AAAAA S9_B @ux7|}-G8~çou[v,}`y|?Q>Y+¡=^;u^_М|:ܜO_Q>W4[>y5W}fGu['Wy9g9|$>_.ᏼ).;7_{Swт     |ќ{8~Jv/I\~>Ox3÷~盟_a })U<.nm{ pm_:nwwE'Mӯ~7pz[|sgK1 3RA|j{GÖ|%wήWYͤ-    -6\z~ccTχ?7G,W݇K^.۹v_⢤-W0w׾//%Mp/!y˿TP|z\0SYт     |Q;|_Ξ:>=pѭ܊hEyBvu-Lryryǯku~.Vuz[/Tp%O~yoK(s8RZxW5:n> AAAAA8 h iCK5+x?/}S 4? IDAT~ZλxֳKEN'?G0o`.r:_spWC=x[z^GpĽo C Yy)w<_|(S7cTXKnu˂s7gOAAAAAŲʓs?/>wG7#~ 9rc7 3?cw4f\U{s+x/gW;-o0?|˹zcF>q~Q/eOw-6o>y0暕et}&MVGsp3A|6=F4     8WVrO|-+U;X}8C⑷-8盿~(w?>㩼،co=W}-x͝Tn᫖I? ^_=ר/;"g#Ϸ<«'/y69/v۫_ΣL׸mvʽ~.s_x w{x}_}_,wO]]xo>< {[lyx+O,Ͼx oO{_p+:?ɽ=.'0g/Ǎ+a     gwUA};r^ͯx/rIdGSh}=g h@s#_K{y h^r,oy=}> L3\ ͝c,}įy7{𣯸; +/y|r݌?%to_*.y㿀^>?( ;l]v7.x».nsхUU/Ou|/M;h]AAAA_¯9l7{_s򳛵T5U7>Te2ޣ.wW}tF    pF3-m~!@)Oa LD!&GAoF.dƐ %ItjH_cފ;<5~'.LQ}k~_]Z1CvAG~wlo=v:ׇPЍgPzg~u};FTΙehc@+4ٖ`h{0I|Fmwɡt;v}O0>;ؓCy}Y,}uց_^izpEƺ>5Dy%8~kom\aY鳩S5'37~ʈgRm>rC9z,5% EDDDDDDDD# @!;z?l=qha{ TJf{K^)5,Pz1&[ۏ1f]ÛJޖZ}<*,zfD_mZemnNgkC sǗ11uk"!%ELVXss?s nVe?w$j6M ܜܦGQaTH+9nNfǠq-!/+=7[-bsٟv{gN%L=[3㫿z{8LbFo;Aihna-`.z lTfF0)s9v8: (5ai]^`(""""""""r<<6MNKbw~] +ggXYL\*[EcCF+W~'fD?hfW{I6q'~vB8ɾ9o.P }k;5_cr7-`5G65mjI^r t;*&ejW<ؖ}Ȯh L_6"""""""""Ps#="Ak]z n-yO^3O6zn[{|7.~1ڡs|V>w-x']B|o Әg&%e]:DnZ!2 ݸrHJJ.Cïu#(޻ώ_wԾ; j* O&1u\$Ϧ[SB_RMpH0YYp-0 , XJ0ZDDDDDDDDLӏ\e<-ҏf+^L$#3sT,5kDw}wsp[3.!>2J9Y&͝Nw1Ã?g$LbCpb9gX=_y9i>P9 \;|=. p*\{'q}'*_jƮ;hݶ-GZCf ̣/5X-ܾR/?<^'_ ;/igrzȿ p|;Yw x ęE%wYgݫI88 )u/гJiWM_}599 mM7ۍrr:q|?oqqqtؙ"""""""""5zAtE㝉r6 o` +DeexMŻJI򱱜kR?6'++ b(Xpatb/5^a? |3s$M| o9?F(&M⭳ך2ܷeNM7x ~֭YMVVfqtiF#۴-7V=EDDDDDDDDI ?}_mºO\FC>L%>tڝHb=lc~~]i8?֭.5VaTf}DOjAaX Vp^;ؓ\Տ~LlƜ0[EDDDDDDDDDM/0wJvF\""""""""""rgTˢŋ;w.yyyGsՐ!k똓FG?_=)**bΜ9ˠA* A;EDDDDDDDDDL0 svٳgsWеKRוuRhg3 \C;W/ ^"GIRDDDDDDDDDDHN:Px  #- RFO 4dUz]Qa!S];q""""""""""S\xBBXj%9^),,dP;atnIEDDDDDDDDD0 ujaܱͪ,\.$?b( nJ_`4m_=?:vDRSSؗłn#0(DFօfffJnnnatF۳bvVIY}E֭Kdݺ^a&999$8@\RR+#"")*( i>vnZ#EȿOPp0 ]6M@tL,15^{|sV+0ZDDDDDDDDDDjhq EDDDDDDDDD)˭"""""Rҳ&ۻT[ԪU am1 '""""""""""|9Yط۷>脦$""""""""""Rc{0ZDDDDDDDDDDjhq EDDDDDDDDD)0ZDDDDDDDDDDjhq EDDDDDDDDDƝ0,H%q2扚qgmru{i9[<ɼfgאn6嶎;?Y6.WRX0S}֟/!^5ǜĒٌz~_ܙ1Xb9=s5sW[6$yX+p,@ٕCTpC0nNfaB_3o+_X@NKse=}Oyt{+?}w}k6 X=|@)0^O.4|E4QƜUDѮ̭[|(潉Xwߔ&[oXϴ{y4 )ԝ|#&%Q3y[tpoA<#nڧqU0⚆>[Nӡc{j ì[ ѪvZWZ~ZE.5_hI|͉:ʯ'#سN;^yزڜן6ZMtן&e wm@yQ9r<Ily|F6֚1kE&\ua$6Zk/s>^B}՝ŎkȲD X˂6N&.qaF qFHx\5v PUK7$\šntgEZBJYhՔcպҷ%J|F%= ⻐`]ĖMi8ǔ[[ek2 ?HsQs/0G4cVXwb ;v&Ȳ/v{&5b[Ty\ܧVdHdP#v瓑Za/JTGx F&z ':ڙIuAWy]0wk ZDDDDDDDyI IDATDjіΌ`?,JU ]N uqS s&!AxA}zGٱkEB[ZD[Ѩ·H_~Epֈt\asҪ}mAY2"hw@.WuH܏iыa#{PS-HlLCEϠvWaGkIMA_ թ OȧKAWѦÂfQG:3qNr2shd[p-Bӹ6xuGE|{MFv\L6ozx Ƶ<{P;8\vvtV6hA~pfJa'{G6*]͂|3m)x81q庅G¹M/}nΥ# s| 3/+}|Y9{:u~NB\V89kPdFӼetk0s7&`MяrY:_5N_ƌo4[t]YQq e"""""""""mCy䝭9#ɻ@&r{g/;D}qV?IfWpP +ﱫqEi$M**~O˛5kw}BʶX:~p3 *]9^SBBi9Nd,Rs!nH*H];~YDM0Pιz.C@\t aX,X~ EXyY=s>ɝcyFea^waQZ|k.fGäaL{+!ը\;ay?LGճ_y;ȿqea]ŮǎRc 2tj0Pnşz8-/vn:zKpeUj ohTIbVʞ nTq0Jg[oa"wl3n!=$ Y_ޘ̌ĮyK(M{nEقVMyќfNV- $oJv8C2\* Lh#  ~3. Ӧ=G8L RDdN#g֒s] nfͻO?q7t&M&[?c;;h3qnY&u/cxI᱿g$<:KN'ZxEϑ0?1nV&"""""""""nn+]HfZOϗS5v u/i[Jb w]B&֓D,UEJeů{ l*ھJwgsz<#kʒ7V܍΢nŜ)8+w=xoL%D@ g1pήw.$ HJyx,N%}z;!nkϑklD^=8@5ٹb Wn0jz)E3qMAKyt@UcܝҏPe\E⮬㆝X"l>y]5WT/K.j 쵢ŝVzʲɚ+/L! \ܰ&""""""""")B/Ugo" bف>m}헤0wĽ\L@!>4h؜WPT }W*fh[+ ')m_nJE)~W+F?sg` 6F7%%~Q՛esHmiyv {_ݏiN닸4(L&l'ie}?85P{0gtmɯp9q_`%iJfdmXՈݣH^_K6hqZ<5I0J.Jf^7y=NԢϣ/pc{UZ_|Ht7v?'/|I'4s0+4gQ\=e6cؔV*EDDDDDDDD* Mg./cAHBa$:G2&aM+|D"m13q-=䩎>4eӶ<ƅ^]UV58%t܄Npcn0t+ Vhßͻ3ywV{ToEAVuF׫8!oGc}͑)!t)aX;ҍDҧ-~TجW+*[Eǵf`:]s (Jb^FcOm5"""""""""'jԨ o,IENDB`jdim-0.7.0/docs/assets/skin_sample_thumb.png000066400000000000000000000403471417047150700211230ustar00rootroot00000000000000PNG  IHDRs΂ bKGDԂ pHYsHHFk> vpAgs(?IDATxidu˼{UhlM$ M-)J,,E8&b'43c{P##˖ek(qDE@Xڷg·zh~U]ܥ*+ϽI}CR)$ER 41 Uho;4B _kpkǽ RB1b[Vo|˴L㡙/-vlo0??ǵkL19Vlpsm$}~i~|?H5?,#ob~ⳟg<9 zy,Bɩ1\YБe>°c`M:&8n$t$t{xaCfEt*^Z֚^.0 )DqBǔ%._AY"c<;5 mЬF ++غ.xhĉOB!l=(AHp}wM[m}5b[EJx샏hmC;CFx R"D!m(<fJd` TB*0¶ (q=qc&4@H(! iRbJƁi0´~L6GG۾eY$qChq2!x3 fan ͰI!Rbcőv ^~7JXqOg}cbyWNSۼN/6!r:°&($Ҵyiaz96kxF1ez& }^ 40㗏uϢҘNf#G8$CmL)$A{h♣{/>./ylq N.259bfNE,.-$]x qjpxaqD೴H4)ѣ\r0 T*lml0;7M0]J<:Cl FGe:Qح>p>igbzvIg&D~-,;Tf3*bVDz=\/éϐuߢx#@E!rpjc! y+ȤMƓ5nJ>Mo/px9~]Z}\zLa[^òB8qhmr&!S>A:F~0 *h:XΒ13h%,2 IJ)r$WV s SG $&~C0-{I:loL&) 7$-È6.!AS+M$oakYq9yk[:_Gķ]kĈ5bܸqÐ8v?J)k]榀,j-& C.]vYcJi4DqLmg!j+,PŴliJRHmNe|nSjy>%@ݡZbHv>_w~PBmΝ&/\hh_oj6߯REsos>;[-ڝnvNCfbb{ ߡa֚N/Lq 0S)ŊN$12"QiR8)f95XO_< 7G&°@ipseFy(uz5mvI&Fѣ5E$Y6BYD@lH&DzjmzAB磏|O*WujR%89?A1Rx$:AH@'Z061ý}?f:?" S-!H:t؇WR"ĉ<#1}y#5BԎ?Fui`C(B'"$S&'V@ ADgS3g. bLKb;Yz<ɫ}U6^-5f)A'x;xCG>diJ mpM˸T) 4So-`,8_Z3gq̐Vs-T*(\ Lpt&6:IxqW~ȏyxNOaHOpsEmG&c#A~DT@tD3gp}_ />ǸDT&1jC{;Wij4) ?LBFJاo7۶qMK䤑̏#0Qp =~)P;t#UQJa|{f-05ً;\hR~O-88F1ɓE܅AG|i0#E:Ǐ##zN RW#IaN5HT*b>ՙ*.OښW֯&{XEy>N 67ㄢ1+sS* 8$Y&s/#?^xElP[H)XX(2V)P% YcbMͳWo"kH!񌔏~A6i)24HPdKR6hvC*yw_06O/]RtH}t"^RȘes\Zߣ\ܥXq( lի;^sd e_ z-4a T~g"@5)QiBiڬ\ȋ?/DAB&C}|G> (N0?K })l C ,0Q5~A0F?Qs4}A Jr1Ak p /a9,/_^ܽX0ĖIƶ^NP*!_,{>WΟPȗ'˗_K4 @"Sz~~QQo94Im/D)w՛4;J{>c2o30HXC&cjx!MDocF`Ne}(P)ͫ?c{?1  vΣmuÇ8+}}2}١rFɃ{0ë/<˯̅WM@$qd$*pw6i7j<}lBH 0  Rdr" Oa&R^q 2qICc0uT=v6p,I; HnKQp`vj!ILKu"K& L,p ǎWHTT ?ͯ>_}>a0W6W,f?`kiH$b0ıL.b E;`bl17WVds+ ?Z&$:¬Mm$7:XKa<6:4߇AdY6YO7/cYQDBnCH,PPG¶3a8u1V)E)$M/񭧿]$GDIJ*d(n4jX~ })%aƲm,}8Im:vSwxOSoQjTۧ<J^<6) Nd dvwkM3C!$R&Wnf LMϲr3gO5=sbOk0b U10db*G`24q]~O[# lmKk{' ñ* R&ι__,O,s)0<&QǏ3hnX~$WoxbKS{w/&!Ia^nhuR>#\_&Q!J zKjd֢{u=ResmD)al XC6)Vm^HC)aW7.NDZ P"R)1a4=>$V('&hA@6$"ut""egsN=>)/,[O255^m.>> dܰ+37燩R< ~/6=Rĵ ҔZnٱvF6/0" "F-CgO1hYkj1YWg:P&/;ɓgv2Nˋm]r 7WuCA+$Wu,s\>ѣN{=?j}N>ND.]abbJ>\Ƭos;>>=By+G 0k#Eerv+;8v{9ŷ_')z9JSt0wHvɗx_ƕ< GO> .'EiA? %XId 6tUim`@6$iJh$)3GQSNb``mcT |3UJܸy\>ÇgӲ(ln2=90Ha$ uth7€AW,Ǚ`兗脊 ;7&?'>͏ǟ}(sGO333OI?3que eZbl9fi 塱DZY:&"ELn D5R $c fggX]YZ M">Pn_frxUډ# ?!LӲml1RthDC(S, Au|J !B A;s<[~'>gn~An{;z|G?Hb&=~o\~ΰ6M:{ %~rU^|5Ijcƶ,>Sȗy ױ'm: S$Ę4;=8F2 Q_}ϙcw/rQ"Du\Ťm~1LOX-3.ve\yAqGXؖc]RJiaډ5WeMC CY8|m÷N>c;>u^mzz /~4,ff^Cx^MrXFӟQDb.6,03_S!OZ)FoՓ8 *+ MWw^667˥+t=Rĉ4i#PZӏbry\.Ghnb:r"a  llΡXQۭ{T* }Dq9HR!}S=|ɱziiy %F(L#at V4% |ÜFsOwVV%t n~g;%R}o ȏC(׀N+ SAhu7 42)LO\vz߬3pthwXOu$XY! 6^FC#odmӓ?gׯqȸDaJ߷zaI6.Sk057Ojdl)Ao0Jc$N~0XM{!5hKH sF2S0 'N-WvxqHtRäl{h&Bm( {LNFAFg_`glcGgpl xz$IB+Ln{l<ܷY]]٘8Ah'XwaƆ6!@)N, 0uē|_g\wQ*S^~rAg^::N}4ZNM4UIJTĉ"IslÐZáz.;"i>i>>q rH !q,քS-[IHXʑ(&ׯApL* Mvvv)W9Ji(UGÛcEDqD5lGOaM8>0GxV@4>d{cN&^s[uvwwX[_|?%4Io} hivCN.bH Ďܷq.ooXڧ'3yJa0uC/W`g~K_kPohm Ε,g~g vuv]taQZS噚fww^R&$P1 2{(= bVǰLzɍW9Уŗ7d\_Y!xS3s8GDJCmQ(0M,BW_KY*#<V|B?Tkׯ/LU9$>r/SSLNN1?ٳg À /p ̹i^N`ET ĸ~i.]~|]eF^vJa8y9׹)P)o7|J%i:TYYOrd=ı-z_?TݢP x7^1E a`&|}?D6_eeuO<'H0 c;=|9v1 Hb a լp!n޼6TQ 634:ef(KLOi3uHN.q!8 "nPﳹg8CH7}?K= `zEYVo(@sm;5넁bzrGRK{ s3=ʱco?$Sc =G a9`a2Kd31VsW(yZ%KYgvʵk+ s5* :[옷 \lq`:NeJ\@?g9qQZ݀*?VGQX:TƑRpCh!8+09(MP:5ezRfoF>|([bzr.= g]JEn,0f3$Sl&%*Dl|xǔ'$F!e@ a\_ӥ0%v(!I4U?ITDFr8RSZRN5ld@fD͠g)m iq1RDfLx%֊0Q( qB>LKc>NpW0-$I-Rifvo~Sp1 DDu7(8eaLF$M>t9\Sa+@L?)R4iZ47)4cw]GG¿W~f( x-4_xcn6;{3aD^[^g@7;{[[OM}gðunnN_K˴ a )"HR(݄4dsNs=}$I^D)jݚ{~NF|ZV bw' M{Qdp6׌dX)~X)$ҰHca\8y,͑(ET4;LMO~I>%IL;ˡYAzHCVֶX:|~4W ϣiX2q m26Vawgm kkt;Q) 719C_~yS_&]?GβK5iri TJE?#&X/p16,LO07?_:K\< waj7ֶ}<(3K lrZXk؅*gO:1_巸"7wLTMqN#GF bs}MO0M'>qL } wOC(T>irJ5rݍur-g#_b( \xCfޠP,Ʋᙚ(W1&s <) b 6)T&ZZbYb#U~ʯ#z~w'@Vʥ2곹g K90:E Rabbs \.3cmN=y%:u CX:73sw~e@uب^ϼ>] X. BƢ41C15=++K9Xej~՛a c>ybE߅FGxw40eμaG|vFZ9z6+Zkvhwhlܼ+`529j;4Zvv^ڵ8dry]ƊRi3f&g1*byd#s'&m]ezvg"M$ H:B!w;K(\#b#!1$Qϸ 0 )KQJ(m|!{b@;th'Au722*.ϼp9VodƋy:6΀i666H$nr4DAK׶X>|jp:GF0]Bs 3UcS 6dl6GXĶmF/Γ&1B9No0mA bp>C?B.;,EdCٽ1ǖ&K17ˉ{'!Ye{Ǐ ϩ i&(aǹ8|8yKF$B[=o0  v* Fޔn0 ym)@u^t~ܔ@0|ӿ>Lҭ*ˤf 0.;^#ޣ ;n7G?0rqXEav$ RZ&ZiLI 3 .x1Lo ]x'9Ǐ<(e5iUfq>ǎUeȕ9}(㼕4b;iޡ{cbvLdNNP{lnטP7_\`yMRq]RIF|T)blCbjMaj}kTVvc>(M)KREfGx;W^ث1<7Ta}چ+6l=qB+%(j&1}i? :6f't[ PtngBSieln6[-MA'Q+>QH).Q24r\f=n߰F߸Jэ""E8gV96$f/Nv1-QLl )82BbbJ0$(l0A4#I4Ji#d]VLDB;`ыcʮc %J+^m4#jd#lLM=ᾉ_ɉp#w4V}1F|rGw- ӲLiH(%*5A{"RTM$83\J)Mn 2V" CwOIBY׵iu d2.*I'ɢ%~;݆#%bKp%G WJ4`D`i Ҙ4JOW f:S+4 5V؀#%FbCZ#[klŨBRJ0 4R A9i@Civ2aZi7w{5b`0>VRhkqB!$MF0p R1vZe9T* FP:AJp8 `'Hsd1M)ҸuׇaPK ba(p@ۦmS,m4IxxLX0Mqt5ZHR 4BHր Q"#&w Zѷ a (1 ש{QòI˔d8<$D[BT#GHb'BBc!)"D@nq^ h$)M,"MB_+&:w ! t>:˴X\\buaekI ~OF l7&k&r[ , 8X[bAGxtS h!cSh4& 4J,"&ƒ6iSD%1ڰ۠UۡT'҉,fx8| /جn3:H!У?mr9^W(" CHgȲm`8dg )PQ˶p vSLEMRK8/"*EW7RrL.N߈8oGAt~tBqa(=\J* $pwô4 l7G..13H0ؾsO_~ݠ8c?*zr6]]HTC|.ǔa}\/K~4#!JD|К !]AReopmuiGm:nũ2τx>amR(xo{;q@I1o8BS NZ m$qJ WVXNn} ǒ $RcY]R߾p+NTO *k؃}*!MF:f7qGXy4_z)4R 1P!sEf>L$ aZC0q=!$;>f%4]+Mbw9N<~/SOqzvҔ4%W,vVN߈8wByd!ah4rщ"|t" vݝu\DcXEwaƏ.qlpzǨomro͗Y_Wl5e}rS34뻴Z-6ַأo;2^:L\PJ!A@'J++S(XX:F!eP&RXA_A'Йlvs%T$pt B;qYEJIXZ"-7=ΝaR`Zz9 !r׈} à׬QMR2>J|gh4z~c<(/[Q# 67/yW$%ԚdP`&KEhґnMԠз/AU @@ vm,gH)1ML6G^33CjHVb +,] Hi BM̢GT0$*5Fo7C˃J CQPu > ,#eqjcέoS۸]![(!!U =G|O)6rE>z4!]µ\ 1A!Qo7B d5 # 5c ]מxd!q3pL0az$),A׋Qsz?|7"T '0m0㰵gwM)#QJ`XY MGbo7 YH4$2 N!ǣ vVϓ7yj|V%,Hu(=?5g#DUB'MbΜ9Ήo=[;~#㼉 TJXC#c H 蔨IJ-^51%Y T3. cyNoi\qhnC{mͱYVjI@"gT3K, `&IH 4q!ShGE:MU:mekD6TtI<~BARLCdÍB0ɡ1Lfo3Xza195ih4Mq1x{,4xQB#DEZA5:I0$Q6L!VҘ H̀apr. T؞ 'H)lR,UdC vcY!}b=x{*(@.PmTYK0B~g)JsiM$hR,,L-/ӴPZÊsqt ˠ" txs!PSE40M6Z <}}Xؘ;tl &19E&Aa9(e!Q58^nS,um)BA Iצn İ1vhIz]d1\T$?Nf>yv %${:WZRP{hFH4QLh̳truӲ>e:Hv,!fsTk$qDX(MoRjH"JcyA.S)/-0lf/d̵)rBE RQ9g(N Jk0Ķ4-lnX+5~V !RJ5Fxܹ:tخy>Ҋ(I]B>=:PjhV爥u0N+,sc3)1 BEfHA@ߥاA%>abvs@Hmc a )&Z&)IS1]paG1΍\ꑩsS#}lE+MEXE&A[댍W1L0 moҭoY$UT"#6w4TQiY0" HMNBG!Պ4N.8<G˓8(&Nbyq B je{7^F6HQɀ$ID%iJ22o;FHІ9t$/SHJpi#R4^>=?{KefJtZMz&/= ^ɉ1Lĵ%I*Ew*HGF=h]-Q5ML$S԰^a$% -m\$"Ji4xY8!b_U2ﲵ`kli' Jh$NBJKH`h#F ؃OHջ:;rGYz;#?xj?5FI.]5ˬ_HLbd>rJ.ߡ0Vv3w<F'IUJѤPbadaie|!(rNj>L~z?2MN}Sw|;tnNblln=2.j{;<9ffdL7Z]^kAѣGz2(:Y_Y:)T ;ƒ$A3xѨl;09I6WkEa.m1İ|>(,/|gw!OReY|[qڍwF| V^>wUsU vw˲EXaY7ŋ8ΰNp%:}$N#Fp*)/ Gu=zA LBlojyPp.Zkkw#!qc 刿] >fCĸz^kXkb&UVue`@&sĈ[SO=βBՠ06CKכ<裼ͧ8~vJy|0 Y~(]c)s~ cizQӨeib X\\xQ(ː&"籽$GNq9r(%}h*K1s|.CbR;x*N`0`NB]G%tEXtdate:create2010-12-28T22:42:29+09:00S.%tEXtdate:modify2010-12-28T22:42:29+09:00"s 1tEXtSoftwaregnome-screenshot>IENDB`jdim-0.7.0/docs/assets/thread.png000066400000000000000000000003411417047150700166540ustar00rootroot00000000000000PNG  IHDRaIDAT8͓A E<G16޴;[nM<߅BHJZčxJ/i M$feZ7$af  C/j9@-RHTݟ8 ཏuܒ& Jcfz_UP9ͨ`mqV]"S *y& Sߘ^CWK` RjIENDB`jdim-0.7.0/docs/assets/thread_old.png000066400000000000000000000004351417047150700175160ustar00rootroot00000000000000PNG  IHDRaIDAT80Dgܨ%RK\ DjID `c~DJkIv3{%3y 1-/L!|gۙ3z IOL9)2N`FıGpIENDB`jdim-0.7.0/docs/index.md000066400000000000000000000037501417047150700150350ustar00rootroot00000000000000--- title: JDim オンラインマニュアル layout: default --- # JDim オンラインマニュアル - [JDimについて]({{ site.baseurl }}/about/) - [make、実行方法について]({{ site.baseurl }}/make/) - [起動について]({{ site.baseurl }}/start/) - [OS/ディストリビューション別インストール方法][dis592] (GitHub discussions) - [datファイルのインポート、エクスポートについて]({{ site.baseurl }}/dat/) - [バックアップ、アンインストールについて]({{ site.baseurl }}/backup/) - [操作方法について]({{ site.baseurl }}/operation/) - [マウスジェスチャについて]({{ site.baseurl }}/mouse/) - [お気に入りについて]({{ site.baseurl }}/favorite/) - [外部板について]({{ site.baseurl }}/external/) - [実況モードについて]({{ site.baseurl }}/live/) - [ユーザーコマンド、リンクフィルタについて]({{ site.baseurl }}/usrcmd/) - [アスキーアート(AA)の入力について]({{ site.baseurl }}/asciiart/) - [次スレ検索について]({{ site.baseurl }}/next/) - [FAQ][wiki-faq] (JD wiki) - [Tips][wiki-tips] (JD wiki) - [開発ドキュメント][wiki-rfcs] (GitHub wiki) - [その他]({{ site.baseurl }}/info/) - [更新履歴]({{ site.baseurl }}/history/) ### 前のバージョンのマニュアル (GitHubリンク) - [JDim 0.7.0-20220115](./link-20220115) - [JDim 0.6.0-20210710](./link-20210710) - [JDim 0.5.0-20210109](./link-20210109) - [JDim 0.4.0-20200718](./link-20200718) - [JDim 0.3.0-20200118](./link-20200118) - [JDim 0.2.0-20190720](./link-20190720) - [JDim 0.1.0-20190122](./link-20190122) - [JD 2.8.9-150226][jd-289] (jd4linux) © 2019-2022 JDimproved project [dis592]: https://github.com/JDimproved/JDim/discussions/592 [wiki-faq]: https://ja.osdn.net/projects/jd4linux/wiki/FAQ [wiki-tips]: https://ja.osdn.net/projects/jd4linux/wiki/Tips [wiki-rfcs]: https://github.com/JDimproved/rfcs/wiki/rfc-index "Request for Comments" [jd-289]: https://jd4linux.osdn.jp/manual/289/ jdim-0.7.0/docs/manual/000077500000000000000000000000001417047150700146545ustar00rootroot00000000000000jdim-0.7.0/docs/manual/2006.md000066400000000000000000000743471417047150700156040ustar00rootroot00000000000000--- title: 更新履歴(2006年) layout: default --- > [Top](../) > [更新履歴]({{ site.baseurl }}/history/) > {{ page.title }} ## {{ page.title }} ### [1.8.5-beta061227](https://github.com/JDimproved/JDim/compare/b5f77c9735...f8b1874884) (2006-12-27) - 設定ファイルをjd.confに移行 - キャッシュフォルダの指定を環境変数 JD_CACHE で行うようにした - スレビューでBSキーでジャンプ元のレスに戻れるようにした - 書き込みビューでundo実装 - 書き込みビューでC-npfbdaeのショートカット実装 - スレ一覧でのマルチキーソーティング実装 - スレ一覧でのスレのブックマーク機能実装 - スレ一覧で規定のレス数を越えていて全レスを読んだスレを下にする機能実装 - 設定メニューに「フォント幅の近似計算を厳密におこなう」を追加 - クリックでポップアップウィンドウにマウスポインタを動かせるようにした - 設定メニューに「シングルクリックで多重ポップアップモードに移行する」を追加 - 板別にプロキシの設定をできるようにした - レス番号指定であぼーんできるようにした ### [1.8.1-061217](https://github.com/JDimproved/JDim/compare/1240fb4fb3...b5f77c9735) (2006-12-17) - &nbsp; をスペース(0x20)に置き換えた ### [1.8.1-rc061213](https://github.com/JDimproved/JDim/compare/f1d5ecf597...1240fb4fb3) (2006-12-13) - gtkmm-24環境でリンクの下線がずれるバグを修正 - スレビューで折り返しがあるときに描画されない時があるバグを修正 - .jdrcでhistory_size = 0とすると落ちるバグを修正 - スレ表示でボールド文字の右側が欠けるバグを修正 - 暫定的にチルトスクロールに対応 - .jdrcでtab_min_str より少ない文字数のタブの最後の文字が消えるバグを修正 - タッチパッドからの数字入力でジャンプ出来なかったバグを修正 - 折り返しの禁則処理の問題を修正 - etc.txtの一番上が表示されなかったバグを修正 - フォントによってスレの文字がずれるバグをある程度修正( strict_char_width ) - サイドバーの挙動が変だったバグを修正 - 書き込みビューに長い文字列をコピーすると落ちるバグを修正 ### [1.8.1-beta061202](https://github.com/JDimproved/JDim/compare/a2abcf37f4...f1d5ecf597) (2006-12-02) - ツリービューで選択範囲外の行をクリックしても選択が解除されないバグを修正 - margin_popup = 0 とすると画像ポップアップが表示されないバグを修正 - スレ一覧で複数(30〜40以上)のスレを選択して同時に開くと落ちるバグを修正 - ・スレ一覧で選択した行数をステータスに表示するようにした - 文字参照処理を作りなおした( spchar_tbl.h 追加) - お気に入りで列を削除した時にフォーカスを次の列に移すようにした - ツリービューで複数選択したときに選択数をステータスに表示するようにした - お気に入りでディレクトリを移動する時にフォーカスが変になるバグを修正 - ビューのフォーカスの切り替え処理部をまじめに書き直した - MISC::iconv を MISC::Iconv に変更(FreeBSDのiconv.h対策) - スレ一覧を表示するまでの時間を短縮した - 右クリックメニューや設定メニューを見直してシンプルにした - ホイールマウスジェスチャが正常に動作しない時があるバグを修正 - スレ一覧で新着スレにマークを付けるようにした( netthread.png 追加 ) - 画像ビューで強制再読み込みをしたときにアイコンの表示が変わらないバグを修正 - 起動を若干高速化 - フォーカス状態をきちんとセッション管理するようにした - アンカークリックで他スレを開くときに開いているタブの左に開くようにした - 新着があるとログを削除してもマークが消えないバグを修正 - datファイル保存を右クリックメニューに追加 - automake-1.10に対応 - configureのオプションに `--with-core2duo` 追加 - 書き込み中ダイアログを出さないようにする設定追加( hide_writing_dialog ) - 横幅が16よりも小さい画像を開くと落ちるバグを修正 - 文字の描画でPangoLayoutではなくてPangoGlyphStringを使うようにした - スレ一覧で並び替えをするとフォーカスが外れるバグを修正 - スレ一覧とスレビューで数字入力でジャンプする機能を追加 ### [1.8.0-061114](https://github.com/JDimproved/JDim/compare/6d937eb7d4...a2abcf37f4) (2006-11-14) - アクセス規制などで書き込めないときにエラーメッセージが表示されないバグを修正 - 書き込みエラー時にサーバーからのメッセージをコンソールに表示するようにした - セットアップダイアログをタスクトレイに表示するようにした - 画像の保存ダイアログでEnterキーを押して保存できるようにした ### [1.8.0-rc061108](https://github.com/JDimproved/JDim/compare/23cac32e93...6d937eb7d4) (2006-11-08) - 適当に処理していたDAT落ち判定をまじめにするようにした - HTTP304が返ると壊れているスレでも壊れているという表示がでないバグを修正 - 書き込みプレビューでレス番号をクリックしてもポップアップメニューなどが出ないようにした - 板の移転があったときにお気に入りのURLも変更するようにした - 表示しているスレを閉じるときにひとつ前のページに一瞬だけフォーカスが移るバグを修正 - スレあぼーんでNGワードでスレを全てあぼーんした後に解除すると表示が戻らないバグを修正 - 画像のドラッグスクロールが効かなくなる時があるバグを修正 ### [1.8.0-beta061103](https://github.com/JDimproved/JDim/compare/8e1022d0bf...23cac32e93) (2006-11-03) - 画像のキャッシュをまとめて消すときにビューが閉じなかったバグを修正 - ユニコード文字参照で#と;の間が数字でないときに変な文字が出てくるバグを修正 - configure.inのlibSMのサーチパスに/usr/X11R6/lib64 /usr/lib64を追加 - 書き込みウインドウにスレのタイトルを表示するようにした。 - desktop-file-utilsの使用変更に対応するためjd.specとjd.desktopを更新 - ユーザコマンドと外部板設定ファイルで空白行とコメント行を入れられるようにした - 互換板でもSETTING.TXTをダウンロードするようにした - デフォルト名無しで無いときにレスの「名前」のところをクリックしてNG名前登録できるようにした - あぼーんしたレスをクリックして透明あぼーん設定が出来るようにした - 書き込み時に名前欄に<>が含まれていると書き込みプレビューが壊れるバグを修正 - メール欄でsageの前に文字列が入っているとageとして処理されるバグを修正 - 名前の抽出とポップアップを可能にした - jd.desktopにName[ja]とComment[ja]の行を追加 - IDや名前で抽出したときに荒らし報告ように抽出したレスのURLを表示できるようにした。 - 書き込みの時に行数、バイト数をオーバーしてたら警告を出すようにした ### [1.8.0-beta061023](https://github.com/JDimproved/JDim/compare/de645ea11c...8e1022d0bf) (2006-10-23) - 書き込みウィンドウに現在の行数や最大行数などを表示した - リンクにime.nu/などが含まれているときに削除するようにした - ユーザーコマンドを設定出来るようにした - webブラウザ設定のリンク置換文字を%sから %LINKに変更(一応 %sでも置換可能 ) - 期間を指定して画像キャッシュを消せるようにした - レス番号クリックの「レスをコピー」でコピーするときにで板名やスレ名も含めるようにした - 参照コピー時に先頭に付ける文字列を変更できるようにした(ref_prefix) - タブメニューにタイトルとURLのコピーを追加 - 開く画像の最大サイズを設定できるようにした( max_img_size ) - 改行直後の空白の取扱いが変だったのを修正 - ageているレスのメール欄に色を付けられるようにした( `color_char_age_{RGB}` ) - レスあぼーんでNGワードや正規表現に改行が入っていても無視されるバグを修正 - 暫定的に書き込みログ保存機能を実装 ### [1.8.0-beta061009](https://github.com/JDimproved/JDim/compare/f610f899d5...de645ea11c) (2006-10-09) - ログイン機能実装 - 右クリック+ホイール回転ででタブ切り替え出来るようにした - 新着が透明あぼーんされているときスクロールがずれるバグを修正 - 表示メニューにサイドバー表示を追加 - サイドバーを消したまま閉じて起動したときも閉じた状態のままにするようにした - F9キーでサイドバー表示/非表示切り替え出来るようにした - 書き込みプレビューでトリップを表示出来るようにした ### [1.7.0-060927](https://github.com/JDimproved/JDim/compare/42f1cf28ec...f610f899d5) (2006-09-27) - 書き込み中表示のダイアログのラベルがフォーカスされているバグを修正 ### [1.7.0-rc060921](https://github.com/JDimproved/JDim/compare/5bd8d05ddb...42f1cf28ec) (2006-09-21) - specファイルのバージョンの付け方をFedora風にした - Fedora向けにjd.specとjd.desktop改良 - レス内容のコピーをしたときにIDの後ろに0000000という文字列が入るバグを修正 - ウィンドウサイズを変更したときに現在見ているレスがずれるバグを修正 - 板一覧をクリックすると左ペーンが閉じる時があるバグを修正 - 新規の先頭レスが透明あぼーんされていると新着セパレータが表示されないバグを修正 - 抽出結果などのタブをお気に入りにドロップすると空白行が出来るバグを修正 - オフラインのときに複数選択で板谷スレを開くとリロードアイコンが表示されるバグを修正 - 板を複数選択を開いたときに現在開いている板のフォーカスが奪われるバグを修正 - タブメニューに更新キャンセルを追加 - オートリロードモード中にオフラインモードにしてもアイコンが変わらないバグを修正 - スレ一覧をスクロールしている時に列幅を変更するとスレが開くバグを一応修正 - 画像の右クリックメニューで変だったところを修正 - 連鎖あぼーんのアルゴリズム変更(全てのアンカーがあぼーんしていたらあぼーんする) - 透明/連鎖あぼーんを切り替えると新着セパレータが消えるバグを修正 ### [1.70b.060914](https://github.com/JDimproved/JDim/compare/5718b45cb8...5bd8d05ddb) (2006-09-14) - 更新した点が多いのでバージョンを1.52ではなくて1.70とした - スクロールモードになったときにタブのスクロールボタンが効かないバグを修正 - スクロールモードになったときにタブのクリック動作が変だった問題を修正 - マウスボタン設定にDragStartButton,TreeRowSelectionButtonを追加 - 環境によってはお気に入りでフォルダとその中のアイテムを範囲選択して移動すると落ちるバグを修正 - ウィンドウサイズを変更するとタブの動作が変になるバグを(とりあえず)修正 - 板一覧でディレクトリのアイコンやラベルをクリックして開けるようにした - ツリービューで複数選択しているときに選択範囲をクリックすると全てをタブで開くようにした - ツリービューで複数選択しているときに選択範囲外をクリックすると選択解除するようにした - ツリービューで再起動したときに変な場所にスクロールするときがあるバグを修正 - ツリービューでフォーカスを外したまま再起動すると変な所がフォーカスされるバグを修正 - ステータスバーのの文字の色が薄い問題を修正(gtkmm2.6以降) - タブアイコンを非表示にできるようにした(.jdrcの show_tab_icon ) - 板一覧のメニューに「ディレクトリ内全選択」項目を追加 - スクロールモードでタブをクリックしたときタブ番号の取得に失敗するバグを修正 - タブ関係の処理の大幅なリファクタリング - タブメニューに「移動」の項目を追加 ### [1.52b.060903](https://github.com/JDimproved/JDim/compare/ca5ce41577...5718b45cb8) (2006-09-03) - 書き込みウィンドウのフォント設定を実装 - 2ch互換板のBASIC認証対応 - migemoパッチ取り込み - 株が<a href="http://2ch.se/">株</a>と表示されるバグを修正 - 名前欄の先頭に数字があるときだけアンカーにするようにアルゴリズムを変更 - 50=10のようなアンカーで=の後ろのレスが表示されなかったバグを修正 - スレビューにスレの速度と最終書き込み日時を表示 - ウインドウタイトルに開いているビューの名前を表示 - xgl環境でポップアップをクリックするとポップアップが閉じるバグを修正 - タブに表示する文字列の最小文字数設定追加( .jdrcの tab_min_str ) ### [1.52b.060827](https://github.com/JDimproved/JDim/compare/e6df057476...ca5ce41577) (2006-08-27) - gccのバージョンによってmake出来なかった問題を解決 - 履歴の文字数が多い時はカットして「...」を付けるようにした - ポップアップや抽出表示からのアンカージャンプが効かないバグを修正 - ヘルプに「2chスレ過去ログ」表示を追加 - 右クリックメニューからタブでビューを開いたときにフォーカスが移らないバグを修正 - スレ一覧のアイコンがGNOMEのテーマによっては変な形になっていたので自作して統一した - スレタイが空白のときタイトルが「壊れています」になるバグの修正 - 板一覧と右ペーンの間のセパレータをクリックして板一覧を開いたり閉じたりする機能を追加 - ftp://のリンクがhttp://として扱われるバグを修正 - URLとして認識する文字をRFCに合わせた( `[-a-zA-Z0-9!#$%&'()~=@;+:*,./?_]` ) - リンクの下線を消せるようにした( .jdrc の draw_underline ) - 板とスレの履歴を分離した - タブにアイコンを表示した - 最適化してスレ一覧からスレを開くスピードを上げた - IDの横に発言数を表示した - 全てのタブの更新機能を実装 - メニュー表示のショートカット追加( Shift+F10 及び Ctrl+m ) ### [1.52b.060803](https://github.com/JDimproved/JDim/compare/16924dcfd3...e6df057476) (2006-08-03) - あぼーんされたレスからの参照は参照数としてカウントしないようにした - 発言数にあぼーんされたレスをカウントしないようにした - 板一覧の列名を日本語に直した - 透明あぼーん実装 - 連鎖あぼーん実装 - ポップアップの最終行を範囲選択していると再描画がかからないバグを修正 - 範囲選択で画面の上下にカーソルを動かしてもスクロールしないときがあるバグを修正 - 1つのレスの中で同じレスに何回もレスしていると対象のレス番号が赤くなるバグを修正 - サーバ/スレッド/xxx,xxx,xxx という形式のアンカーをポップアップできるようにした - xxx+xxx+xxx という形式のアンカーを連結してポップアップできるようにした - treeviewでpageupとpagedownキーを有効にした。 - スレッドあぼーん実装 - スレビューのの「板を開く」ボタンをクリックしたときにリロードしないで開く機能を実装 - 「右、左のタブを閉じる」をタブメニューに追加 - 再起動時にスレ一覧やスレの復元をしてもフォーカスが移らなかったバグを修正 - リンクの最後に"がくると"と表示されてリンクされるバグを修正 - リンクの最後が()で終わっている場合はリンクにしないようにした ### [1.51.060717](https://github.com/JDimproved/JDim/compare/0d64104dd5...16924dcfd3) (2006-07-17) - スレのキャッシュを削除したときにスレ一覧のアイコンにごみが残るバグを修正 - READMEを更新した ### [1.51rc.060712](https://github.com/JDimproved/JDim/compare/efb432b073...0d64104dd5) (2006-07-12) - https://〜ではじまるリンクを開けなかったバグを修正 - あぼーんしたレスをポップアップすると assertion が出る問題を修正 - ロードした画像を削除してもリンクが赤いままになるバグを修正 - お気に入りビューで板、スレ、画像のプロパティを開けるようにした - スレ一覧で画像のプロパティを開けるようにした - スペースキーを押しっぱなしにすると再描画を繰り返して負荷が高くなるバグを修正 - 長い数列を範囲選択すると暴走するバグを修正 - タブで開くボタンの入れ替えをするとツリービューでドラッグ複数選択が出来ないバグを修正 - FreeBSDでautogen出来ない問題を応急処置で修正 - 日付のフォーマットを 01/01 01:01 みたいに2桁で揃えて0で埋めるようにした ### [1.51b.060707](https://github.com/JDimproved/JDim/compare/83119ad334...efb432b073) (2006-07-07) - 内部的にオートリロード機能を実装 - スレ一覧で「選択した行を開く」機能を右クリックメニューに追加 - スレ一覧などのツリービューでshiftクリックしても複数選択されないバグを修正 - 板、スレ一覧で中ボタンドラッグで範囲選択出来るようにした - 板一覧とお気に入りで「選択した行を開く」機能を右クリックメニューに追加 - 板一覧とスレ一覧のツリーでフォントを別々に設定出来るようにした - 設定メニューに「デフォルトでタブで開く」設定を追加 ### [1.51b.060628](https://github.com/JDimproved/JDim/compare/6e64fc528e...83119ad334) (2006-06-28) - NG Wordによるあぼ〜ん実装 - 正規表現によるあぼ〜ん実装 - あぼ〜ん関係の処理を最適化 - PrefDiagFactory追加 - スレ情報が保存されなかった時があったバグを修正 - bbsreleaseのタブが縮んだままになるバグを修正 - スレ一覧からスペースでスレを開くとWIDGET_REALIZED_FOR_EVENT assertが出るバグを修正 - ブラウザ設定をコンボボックスを使うように改良 - メニューなどの英語を日本語に直した - httpsで始まる文字列がリンクされなかったバグを修正 ### [1.51b.060617](https://github.com/JDimproved/JDim/compare/e8e9260646...6e64fc528e) (2006-06-17) - 名無しがfusianasanの板の時は名前欄が空白だと書き込めないようにした - FreeBSD対応 - NOUSE_MS932 を使わないようにした - 板のプロパティに「名前欄が空白の時は書き込まない」チェックボタンを追加 - 画像キャッシュ削除ダイアログに現在のキャッシュサイズを表示するようにした - 大昔(2000年頃)に建てられたスレを開いたときスレ一覧の既読マークが表示されないバグを修正 ### [1.51b.060612](https://github.com/JDimproved/JDim/compare/4089941b80...e8e9260646) (2006-06-12) - gtkmm24でコンパイル出来なくなったバグを修正 - GTK_CHECK_VERSION マクロを取り除いた - スレのプロパティにサイズや更新日時などを表示するようにした ### [1.51b.060611](https://github.com/JDimproved/JDim/compare/ad1b13b564...4089941b80) (2006-06-11) - 64bit対応パッチを当てた - articleviewで検索したときに再描画がかからない時があるバグを修正 - articleviewで検索対象の文字列が最後のレスに含まれているとリンク下線がずれるバグを修正 - 一度articleviewを閉じないと最初にブックマークしたレスにジャンプ出来ないバグを修正 - ダブルクリックで板やスレを開けないバグを修正 - articleviewの行間幅を真面目に計算で求めるようにした - 手動で下線や改行位置を調節できるようにした( adjust_underline_pos, adjust_line_space ) - 画像をオリジナルサイズで最初から開けるようにした( zoom_to_fit ) - 板のプロパティでhanaの確認と削除を出来るようにした - cookieを真面目に解析して作成するようにした(PON,NAME,MAILを取得) - ヘルプメニューからサポートBBSにアクセス出来るようにした ### [1.50.060601](https://github.com/JDimproved/JDim/tree/ad1b13b564) (2006-06-01) - 誤った正規表現を入力すると落ちるバグを修正 - 正式版リリース ### 1.5rc.060528 (2006-05-28) - hana仕様変更対応 ### 1.5rc.060527 (2006-05-27) - タブを中クリックで全て閉じると落ちるバグを修正 - hanaに暫定対応 - テスト項目消化 ### 1.5b.060522 (2006-05-22) - フォーカスアウト周りの処理をKDEなどの環境に合わせて変えるのを止めた
→GdkEventCrossingのmodeをみてフォーカスアウトするかどうか決定 ### 1.5b.060518 (2006-05-18) - KDE環境でリンクをクリックしても反応が無い問題を修正 - KDE環境で多重ポップアップ出来ない問題を修正 - まちBBSでレスの引用書き込みが出来なかったバグを修正 - gtk2-2.9のツリービューのパス取得の仕様が変わったせいで固まるバグを修正 ### 1.5b.060514 フィーチャーフリーズ (2006-05-14) - キーワードやIDなどを抽出するときは抽出元のスレの隣にタブを開くようにした - マウスジェスチャやショートカットキーボタンの設定を細かく出来るようにした - 名前がデフォルトの名無しの時は数字をアンカーにしないようにした - sprintfになっていた部分をsnprintfに直した - FreeBSDでhistoryがセグフォ出していた問題を修正 - 書き込み確認のダイアログを表示するようにした( always_write_ok ) - 書き込み中に「書き込み中」と表示するようにした - 書き込みがタイムアウトしたら再読みしろというダイアログを出すようにした - FreeBSDでもautogen.shが通るようにした - 履歴を板とスレで分けた - 履歴の保持数を設定できるようにした( history_size ) - 板一覧でカテゴリ全体をまとめてお気に入りに登録できるようにした - (追加変更した操作) - タブをダブルクリック : 再読み込み - ダブルクリック : 画像再読み込み - alt + q : 書き込みをキャンセル ( alt + c から変更 ) ### 1.5b.060415 (2006-04-15) - configure.inを修正 - libgnomeuiでは無くてXSMPをデフォルトで使用するようにした - ポップアップ中にマウスホイールを回したらポップアップの方をスクロールさせるようにした - ポップアップとカーソルの間のマージンを設定出来るようにした( margin_popup ) - 板一覧でカテゴリをひとつだけしか開かないように設定可能にした( open_one_category ) - メニューにショートカットキーを表示するようにした - 設定ファイル(keyconf.xml)でキーバインディングを設定出来るようにした - (追加変更したショートカットキー) - alt + c : 書き込みをキャンセル - alt + w : 書き込み実行 - F3 : 新着へ移動が次検索と被っていたのでF4に変更 ### 1.5b.060401 (2006-04-01) - gnomeセッションを終了時にセッション情報を保存するようにした(要libgnomeui-devel) - configure.in.vineを消してconfigure.inひとつに統一した - コンパイルオプションに-Wallを付けて出てきた警告を全部消した ### 1.5b.060319 (2006-03-19) - 自前のツールチップの表示部を作りなおした(背景色をテーマに合わせるようにした) - タブの色がテーマによっては変になるバグを修正 - 「123-124」みたいなアンカーがあるとき、半角の「-」も認識するようにした - いつの間にかポップアップでスクロールバーが出なくなっていたので修正 - SIGINTやSIGHUPを受け取ったときにお気に入りをバックアップするようにした(テスト不十分) - ローダがget_addrinfoに失敗すると落ちるバグを修正 ### 1.5b.060311 (2006-03-11) - 他のスレをポップアップした直後にそのスレを消して再ポップアップすると落ちるバグを修正 - ロード開始直後にすぐスレを閉じると落ちるバグを修正 - キャッシュのルートディレクトリがうまく設定できないバグを修正 - 正規表現クラス実装 - rawモード読み込み対応(?) - まちBBSの読み書きに対応(スレ立てはテスト出来ないので未実装) - bbsrelease,board,articleビューで正規表現による抽出・検索機能実装 - 範囲指定のみでXのバッファに文字列をコピーするようにした - 多重起動防止実装( ロックファイルは (キャッシュルート)/JDLOCK ) - ファイルローダでsendしたときのエラーチェックがいい加減だった問題を修正 - zlib1.1系に対応(-DUSE_OLDZLIB) - その他ファイルローダの安定化 - articleビューでスクロール時の負荷を大幅に減らした - HTMLパーサを少しだけ最適化 - リンクの上を範囲選択してマウスを上げると開いてしまうバグを修正 - オートスクロール中にキーを押すとスクロールが止まるバグを修正 - 画像インジケータのアイコン画像の比率が変だったバグを修正 - 板別に最後にソートした列を記録するようにした - windowを最大化/最大化解除したときにタブの幅を調整するようにした - jd.spec, jd.png、jd.desktopを付属( thanks to 189, 190氏 ) - その他細かい修正 ### 1.5b.060219 (2006-02-19) - ファイルロード時にconnectに失敗していてもsendしていた問題を修正 ### 1.5b.060214 (2006-02-14) - アイコンバーのアイコンを並び替えるとスクロールが変になるバグを修正 - 画像のロード中に画像保存ダイアログを開くとロードが停止するバグを修正 - 名前欄のトリップの中の数字をリンクしないようにした - Fileメニューにお気に入り保存を追加 - (追加、変更したショートカットキー、マウスジェスチャ) - F3 : 新着へ移動 - →↓→ : 新着へ移動 - z: 元の画像サイズ - x: 画像を画面にフィットさせる - +: 画像の拡大 - -: 画像の縮小 - c: 画像のモザイク解除 ( x から変更、gthumbに合わせるため ) ### 1.5b.060207 (2006-02-07) - ファイルローダで一度タイムアウトすると再読み込みしなくなるバグを修正 - 縦3paneモード実装 ### 1.5b.060205 (2006-02-05) - subject.txtが壊れていると落ちるバグを修正 - ファイルローダでsend時にタイムアウトしているのにエラーチェックを素通りしていたバグを修正 - ファイルローダをhttps対応にした - ファイルロード中にロード停止したときの反応を良くした - Gtk::Stock::MEDIA_RECORDを使うのを止めた ### 1.5b.060204 (2006-02-04) - コードを大幅整理(特にスレ標示部) - ユニコード文字参照に対応 - 書き込み中にarticleビューの書き込みボタンを押してもmessageを閉じないようにした ### 1.5b.060115 (2006-01-15) - 履歴をクリアした後板を開くと落ちるバグを修正 - ツリービューのスクロール処理を自前でするようにした( .jdrcのtree_scroll_sizeで調整 ) - ダブルクリックで文字列を範囲選択出来るようにした ### 1.5b.060113 (2006-01-13) - GLIBのスレッド初期化忘れを修正 - コンパイルオプションに -DNOUSE_MS932 を与えるととCP932で文字コード変換するようにした - iconvでMS932からUTF8にマッピング出来なかった文字を□に変換するようにした - 起動時のimageの復元機能実装 - SETTING.TXTのローダ作成 - 操作性向上(主にタブ操作、画像操作まわり) ### 1.5b.060106 (2006-01-06) - favoriteで名前が変更出来ないバグを修正 - 画像を保存した後に画像をダウンロードすると固まるバグを修正 - 起動時のboard,articleの復元機能実装 - history機能実装 - 文字参照の一部を実装(thinsp, ensp, emsp, etc.) ### 1.0-060101 (2006-01-01) - リリース jdim-0.7.0/docs/manual/2007.md000066400000000000000000000561451417047150700156010ustar00rootroot00000000000000--- title: 更新履歴(2007年) layout: default --- > [Top](../) > [更新履歴]({{ site.baseurl }}/history/) > {{ page.title }} ## {{ page.title }} ### [1.9.8-071228](https://github.com/JDimproved/JDim/compare/27d140cab7...4c35cda400) (2007-12-28) - お気に入りでアイテムが消えない時があったバグを修正 ### [1.9.8-rc071223](https://github.com/JDimproved/JDim/compare/23c7db918f...27d140cab7) (2007-12-23) - テーマによってはLabelの背景色が変になるバグを修正 - バージョン2.4のgtkmmでビルド出来なかった問題を修正 - 旧設定ファイル(.jdrc)が存在するとキャッシュディレクトリが出来ない時があるバグを修正 ### [1.9.8-beta071218](https://github.com/JDimproved/JDim/compare/5f25d7d12a...23c7db918f) (2007-12-18) - テーマによってはLabelEntryの背景色が変になるバグを修正 - dat落ちしたスレを開いたとき、タブに「???」と表示するようにした - ウィンドウのリサイズ直後にスレの範囲選択の位置がズレる時があったバグを修正 - レスにUTF-8直書きされているときにスレが壊れていると判定されるバグを修正 - スレの連鎖あぼーん設定のチェックが再起動すると外れるバグを修正 - テーマによってはタブの色が変になるバグを修正 ### [1.9.8-beta071210](https://github.com/JDimproved/JDim/compare/c48d699a18...5f25d7d12a) (2007-12-10) - RH系のブラウザをhtmlviewからxdg-openに変更した - メッセージダイアログでtabでラベルにフォーカスが移るバグを修正 - リロード後に新着に自動的に移動する設定追加( jump_new_after_reload ) - key.confに Scroll\*Board と Scroll\*Image を追加 - 起動時に画像ウィンドウも復元するとスレビューが表示されなかったバグを修正 - スレビューでn,pで前後のレスに移動できるようにした - 書き込みビューのファイル選択画面でキャンセルを押すとビューが閉じるバグを修正 - ログ検索中に他の検索ビューを閉じると検索が終了するバグを修正 - タブを中クリックで閉じる動作をキャンセルできるようにした - ポップアップフレームとオートスクロールマーカの色を設定できるようにした - お気に入りの最終行がディレクトリの時に最終行へのジャンプが正常に動作しないバグを修正 - リンクの上でコンテキストメニューを開いてからマウスを移動してクリックするとリンクが開くバグを修正 - デフォルトではOpenSSLではなくてGNUTLSを使用するようにした( `--with-openssl` でOpenSSL使用 ) - テーマを変更したときにスレビューのラベルの背景色が変わらなかったバグを修正 ### [1.9.7-071122](https://github.com/JDimproved/JDim/compare/0b62eec74e...c48d699a18) (2007-11-22) - configureに `--with-athlon64` オプションを追加 - $LANGが取得出来なかった場合に落ちるバグを修正 - SVN版で作業コピーでない場合にコンパイル出来なくなっていたバグを修正 ### [1.9.7-rc071115](https://github.com/JDimproved/JDim/compare/e80cdb065c...0b62eec74e) (2007-11-15) - 画像ビューのメニュー項目の一部にマウスジェスチャが表示されていなかったバグを修正 - 板一覧にフォーカスがあるときに板一覧再読み込みするとサイドバーが閉じるバグを修正 - 起動時にjd.confが読み込めない時はjd.confが存在するか確認するようにした - スレビューで他スレをポップアップ表示したあとキャッシュ削除してもポップアップ表示されるバグを修正 - 「画像を全て保存」でモザイクを外していない画像まで保存していた問題を修正 - お気に入りで ディレクトリを削除するとキーボードフォーカスが外れるバグを修正 ### [1.9.7-beta071109](https://github.com/JDimproved/JDim/compare/bbafc44f4d...e80cdb065c) (2007-11-09) - 「画像ポップアップを表示する」設定を追加 - スレビューでリロード後に末尾に飛ぶ設定を追加( jump_after_reload ) - スレビューの検索に「WEB検索」を追加( menu_search_web, url_search_web ) - 画像ビューを開いたまま閉じて再起動するとビューが展開されずに落ちるバグを修正 - 起動中やスレ表示前にスレビューの上をマウス移動すると落ちるバグを修正 - ツールバー(リスト)項目設定のダイアログを充実させた - 画像関係の表示メニューを設定メニューから表示メニューに移した ### [1.9.7-beta071101](https://github.com/JDimproved/JDim/compare/447c79aaeb...bbafc44f4d) (2007-11-01) - BMP画像表示に対応 - スレタイ検索機能実装( menu_search_title, url_search_title, regex_search_title ) - BEログイン実装 - BEのマーク表示をするようにした - メインツールバーと各ビューのツールバーの項目を編集出来るようにした - 起動、シャットダウンを高速化した - 板一覧を開くときにリダイレクトされたら移転のチェックをするようにした - メインメニューにキーアクセラレータとショートカットを追加 - 「JDの動作環境」の一部に「/etc/issue.net」値を取得して表示するようにした - 書き込みプレビューでURLに含まれる「&」が「&amp;」と表示されてしまう問題を修正 - 「IPv6使用」設定をメニューに追加 ### [1.9.6-071005](https://github.com/JDimproved/JDim/compare/3edeb2457e...447c79aaeb) (2007-10-05) - 書き込みメッセージを保存した際にダイアログが再び出てしまうバグを修正 - ログ検索中にビューを閉じても検索スレッドが停止しないバグを修正 ### [1.9.6-rc070930](https://github.com/JDimproved/JDim/compare/790ee740da...3edeb2457e) (2007-09-30) - お気に入りで新規ディレクトリの追加が出来なかったバグを修正 - 板一覧とお気に入りで前検索すると固まることがあるバグを修正 ### [1.9.6-beta070918](https://github.com/JDimproved/JDim/compare/b8b13b78ac...790ee740da) (2007-09-18) - スレ一覧の表示項目を設定できるようにした - 非アクティブのときに書き込みウィンドウを折り畳むようにした - GNOME環境でウィンドウを閉じたときに最大化が時々勝手に解除されるバグを修正 - 各ビューで全て選択(Ctrl+a)実装 - 書き込みビューのコンテキストメニューにAA入力の項目を追加 - 書き込みビューを閉じるときに編集中の文章を保存できるようにした - 画像あぼーんの情報を削除できるようにした - 画像ウィンドウでお気に入り追加ダイアログを開くとウィンドウが畳まれるバグを修正 - アンカーのコンテキストメニューでジャンプが動作しなくなっていたバグを修正 - 全お気に入り更新チェックを追加 - メインメニューに「ツール」の項目を追加 - 履歴メニューの項目もEnterキーを押してアクティブに出来るようにした - aboutダイアログ追加( パッチスレ71 ) - button.confでDbl\*の設定が効かなかったバグを修正 - スレ一覧のログ検索ボタンを削除してツールメニューにログ検索移動 - 発言数のチェックを外すオプション追加( check_id = 1 ) - スレをお気に入りに登録したときに自動でブックマーク設定するようにした - レス参照で色を変える回数を設定できるようにした( num_reference_high=3, num_reference_low=1 ) - 発言数で色を変える回数を変更できるようにした( num_id_high=4, num_id_low=2 ) - 画像のピクセル数が大きいときは警告するようにした( max_img_pixel = 20 ) - タブのロック機能を実装 - メインメニューからJDホームページの項目を削除(aboutダイアログから可能) - 書き込みビューのコンテキストメニューに「クリップボードから引用」の項目を追加 - 書き込みビューのコンテキストメニューに「JDの動作環境を記入」の項目を追加 - バージョン表示でSVN版のリビジョン番号を表示できるようにした - スレの再取得をするときにあぼーんなどのスレ情報を削除しないようにした - スレがDAT落ちしているときにラベルの色を青くするようにした - 設定メニューに板、スレ、画像プロパティ項目追加 - タブメニューに「プロパティ」追加 - 表示設定からタブの非表示が出来るようにした ### [1.9.6-beta070804](https://github.com/JDimproved/JDim/compare/e8886a7b89...b8b13b78ac) (2007-08-04) - 更新チェック機能実装 - 更新チェックのタイムアウト値の設定追加(loader_timeout_checkupdate) - スレが壊れているときにラベルの色を赤くするようにした - スレ一覧のポップアップが表示されないときがあるバグを修正 - お気に入りでディレクトリ以外の行でも左キーで開いているフォルダを畳めるようにした - migemo検索の高速化パッチを適用 - スレ一覧でインクリメンタル検索が出来るようにした ( inc_search_board = 1 ) - メニュー項目でダイアログを開く行に「...」を付けるパッチを当てた - 板を開いたまま再起動時するとSETTING.TXTを勝手にロードするバグを修正 - SETTING.TXTの取得時に更新時刻を送るようにした - 板のプロパティにローカルルールを表示 - 画像ビュアーで302の画像もダウンロードするようにした - 画像のボタン設定追加(CloseImageButton,ScrollImageButton,CancelMosaicButton) - 画像の拡張子と実際の形式が違うときはモザイク解除の時に警告するようにした ### [1.9.5-070630](https://github.com/JDimproved/JDim/compare/123e99532a...e8886a7b89) (2007-06-30) - AAメニューでカーソル上下で誤動作するバグを修正 ### [1.9.5-rc070625](https://github.com/JDimproved/JDim/compare/a77ef76cdc...123e99532a) (2007-06-25) - メール指定が無いときの名前欄の文字色を選択できるようにした - 画像まわりの細かいバグをまとめて修正した ### [1.9.5-beta070616](https://github.com/JDimproved/JDim/compare/24bb552ecd...a77ef76cdc) (2007-06-16) - bbsmenu.html内にあるリンクは全て板とみなす設定追加( use_link_as_board ) - アニメーションGIFの表示処理を軽減化 ### [1.9.5-beta070611](https://github.com/JDimproved/JDim/compare/fe2fcf21a3...24bb552ecd) (2007-06-11) - XML処理の変更に伴い、\*.xmlファイルの書式をXMLの仕様に従うように変更 - BackspEditコントロール設定を追加 - 書き込みビューに最終書き込み日を表示 - 連続書き込み規制(書き込みビューに再書き込み可能になるまでの時間を表示) - スレビューで再読込ボタンで全タブを更新する機能追加( reload_allthreads ) - JD ホームページのアドレスを設定できようにした( url_jdhp ) - 画像の横幅や縦幅が長い場合に落ちるバグを修正 - 画像のファイルサイズが0byteの時に画像ビューが落ちるバグを修正 - AAメニューの履歴数を変更できるようにした( aahistory_size ) ### [1.9.5-beta070528](https://github.com/JDimproved/JDim/compare/91febcb109...fe2fcf21a3) (2007-05-28) - 右ペーンが空の時にサイドバーを最大化する設定(expand_sidebar)追加 - ポップアップと画像カーソルの間のマージン設定(margin_imgpopup)追加 - スレビューの削除メニューなどに「削除して再読み込み」を追加 - スレを再読み込みして再起動するとスレ一覧の新着がマイナスになる問題を修正 - 水平タブ(0x09)が文字化けする問題を修正 - スレビューで上下キーのスクロール速度を調整可能にした( key_scroll_size ) - スレ内で検索をしたときにヒット数を表示するようにした - configureのオプションに `--with-migemodict` を追加 - 検索用補完Entryクラス実装 - 最近閉じたスレの履歴を実装 - 設定にプライバシーメニュー追加 ### [1.9.5-beta070516](https://github.com/JDimproved/JDim/compare/96442f1e96...91febcb109) (2007-05-16) - スレビューの描画エンジンを最適化 - 新着しおり(ここまで読んだ)をブロック要素化 - css対応( jd.css ) - 画像表示関係を高速化 - レススキン対応( Res.html ) - インライン画像実装 - インライン画像のon/off設定をメニューに追加 - お気に入りが空のとき最後に移動すると異常終了するバグを修正 - 板レベルのあぼーん設定を追加 - 画像キャッシュクリアを高速化 - 画像キャッシュ削除中にダイアログを出してキャンセル可能にした - 画像プロパティにサイズやキャッシュ保護のチェックなどを追加 - スレッドのデフォルトスタックサイズを減らした - 画像あぼーん実装 - 履歴右クリックでポップアップ表示して特定の履歴を削除出来るようにした - Res.htmlにNAMELINKを追加(「名前」に置換する) - IDあぼーんしたら板の方にも自動で登録するようにした - タブ右クリックメニューの複数のタブを閉じる機能をまとめた - スレ一覧とスレビューのツールバー表示/非表示設定追加 - 板一覧とスレ一覧のテキストカラー変更設定追加 - 板一覧とスレ一覧の偶数行の色設定追加 - 画像ポップアップのマージン設定追加( jd.css の .imgpopup の margin ) - サイドバーの表示切り替えの挙動がおかしかった問題を修正 - bbsmenu.htmlの解釈がおかしかった問題を修正 - スレのプロバティの番号あぼーんでマイナスの数を入れると落ちるバグを修正 - スレのプロバティの番号あぼーんで12-34のような書式に対応した ### [1.8.8-070403](https://github.com/JDimproved/JDim/compare/fb2b7e66c7...96442f1e96) (2007-04-03) - スレタイトルに特殊文字が入っているときにデコードされてなかったバグを応急処置で修正 ### [1.8.8-rc070330](https://github.com/JDimproved/JDim/compare/cb3339cf9c...fb2b7e66c7) (2007-03-30) - 移転処理時にスレビューに対象板内のスレを開いていると再読み込み出来なくなるバグを修正 - 書き込みプレビュー画面でCtrl+g(検索)を押すと落ちるバグを修正 - フォントの詳細設定ダイアログに行高さ調整の項目追加 - フォントの詳細設定ダイアログに下線位置調整の項目追加 - 書き込みエディタの文字や背景色を変更できるようにした - スレビューでShift+キーにスクロール機能を割り当てると2回スクロールするバグを修正 - 細かいバグの修正 - その他微調整 ### [1.8.8-beta070324](https://github.com/JDimproved/JDim/compare/8726987d2c...cb3339cf9c) (2007-03-24) - 画像ウィンドウ表示時に画像の参照元レスを開いてもウィンドウが閉じないバグを修正 - 画像ウィンドウ表示時にマウスジェスチャが表示されないバグを修正 - 画像ウインドウが表示される前にqを連打してウィンドウを閉じると落ちるバグを修正 - KDE環境で画像ウィンドウ上でダイアログを閉じるとフォーカスが外れるバグを修正 - GIF画像の読み込みに失敗すると落ちるバグを修正 - 強制画像再読み込みをしてもアイコン表示が変わらないバグを修正 - 埋め込み書き込みビューがフォーカスされてもURLとタイトルが変わらないバグを修正 - 埋め込み書き込みビューを開いたまま再起動するとフォーカスが外れるバグを修正 - スレ一覧の読み込み中にスレを読んでいると読み込み終了時にスレ一覧に戻るバグを修正 - スレビューにフォーカスがあるときはスレビューをクリックしても再描画をかけないようにした - AAポップアップメニューにAAが表示されないときがあるバグを修正 - セットアップウィザード追加 - 初起動直後の初期フォントをシステムで利用可能な物から選ぶようにした ### [1.8.8-beta070317](https://github.com/JDimproved/JDim/compare/ea2c522ed6...8726987d2c) (2007-03-17) - ヘルプファイルをXML化した( thank to tamagodake氏) - フォント、色詳細設定ダイアログ追加( thank to tamagodake氏) - スレ一覧の速度の計算アルゴリズムを変えた - Debian GNU/kFreeBSD に configure.inを対応した - ダイアログボックスのフォーカス処理をきちんとするようにした - 書き込みビューを開いているときにスレビューの「レス」「参照してレス」、および「引用してレス」 - を選択すると書き込みビューに引用できるようにした( thank to tamagodake氏) - 書き込みビューに「書き込み後に閉じない」ボタン追加 - メニュー項目の見直しと整理 - utf8環境で起動していないときは警告を出すようにした( thank to tamagodake氏) - メニューバーの表示設定追加(F8) - スレビューでのホイールスクロール速度設定追加( jd.confのscroll_size ) - RFC規定外の文字(^など)もURL判定に用いる設定追加( jd.confのloose_url) - ロックファイルにPIDを保存して起動済み判定を厳密にした - 書き込みビューのタブを取り除いてプレビューボタンを追加した - ローダ実行中にあるタイミングで別のローダを起動すると落ちるバグを修正( class Dispatchable 追加 ) - ツリービューの行間スペース設定追加( jd.confのtree_ypad ) - 書き込み先がスレビューで開いているスレと異なるときは警告を出すようにした - ツリービューのハイライト色の設定追加 - make時にx86_64で警告が出る問題を修正( thanks to 180氏) - ipv6使用オプション追加( jd.confのuse_ipv6) - 書き込みビューにファイル挿入ボタン追加 ### [1.8.8-beta070218](https://github.com/JDimproved/JDim/compare/62b73373b8...ea2c522ed6) (2007-02-18) - 表示されていないビューの切り替えボタンやメニューは非アクティブにした - 「選択範囲を引用してレス」を右クリックメニューに追加 - 2pane, 3paneモードの切り替えなどを再起動無しでできるようにした - ツールバーの位置を再起動無しで切り替えられるようにした - 複数のスレを表示しているときにウィンドウのリサイズを高速化 - 3pane表示のときに開いていないビューを隠すようにした - 埋め込み書き込みビュー実装 - 画像ウィンドウ分離表示機能実装 - 板移転時にメッセージのjavascriptを解析して移転情報を更新する機能を実装 - ユーザコマンドに$DATURLと$LOCALDATを追加 - http://info.2ch.net を板として認識していたバグを修正 - idpopup_by_mo, refpopup_by_mo,namepopup_by_mo 設定追加 - 名前欄のbタグで囲まれたトリップなどの文字列に色を付けられるようにした( thank to tamagodake氏) ### [1.8.5-070203](https://github.com/JDimproved/JDim/compare/a2f7992fe5...62b73373b8) (2007-02-03) - 参照文字に&yen;追加 - スレ一覧を全部閉じてから板一覧を閉じてもスレにフォーカスが移らないバグを修正 - 初回起動時に板一覧ではなくてお気に入りにフォーカスがあるバグを修正 - ビューの切り替え方法を説明したダイアログを表示するようにした - 移転情報テーブルが循環しているとお気に入りのアドレスが空白になるバグを修正 ### [1.8.5-rc070121](https://github.com/JDimproved/JDim/compare/8fc07bac6a...a2f7992fe5) (2007-01-21) - 書き込みビューでCtrl+n,pでカーソルを移動した時にスクロールしないバグを修正 - 書き込みプレビューで画像をダウンロードしてもリンクの色が変わらないバグを修正 - 書き込みプレビューで画像を削除してもリンクの色が変わらないバグを修正 - 設定メニューに「書き込みビューでEmacs風のキーバインドにする」の項目を追加 - 2.4以下のgtkmmでスピンボックスの値が変わらなかったバグを修正 - 板一覧とお気に入りをコンボボックスで切り替えられるようにした - ツリービューの背景色設定にgtkrcを用いる設定を追加( use_tree_gtkrc ) - 画像ビューを開かないようにする設定を追加( use_image_view ) - 選択不可のユーザコマンドを非表示にする設定追加( hide_usrcmd ) - 指定した数( max_show_usrcmd )以上の時はサブメニュー表示にする設定追加 ### [1.8.5-beta070114](https://github.com/JDimproved/JDim/compare/f8b1874884...8fc07bac6a) (2007-01-14) - 生のdatファイルに\0などの制御文字が含まれていると表示が壊れるバグを修正 - スレ一覧のマークでのソーティングアルゴリズムを変更 - キャッシュのログ検索を暫定的に実装 - ツールバーにサイドバーの切り替えボタンを付けた - 移転処理した時に移転した板を表示するダイアログでTextViewを使用した - 移転処理をしたときにSETTING.TXTをロードしなくなる時があるバグを修正 - 移転したときに履歴メニューのURLが変更されていなかったバグを修正 - 外部板ビューを廃止して外部板は板一覧の先頭に表示するようにした - 板一覧とお気に入りをサイドバー形式に変更 - スレ一覧、スレ表示、画像タブを廃止 - ビュー切り替えボタンをツールバーに追加 - ツールバーの位置を切り替えられるようにした - 書き込み時のデフォルトの名前とメールアドレスを板別に設定できるようにした - 書き込みビューにundoボタンを付けた - 画像ビューでキーボードでスクロール出来るようにした - チルトボタンの設定を button.conf に追加 jdim-0.7.0/docs/manual/2008.md000066400000000000000000000426611417047150700156000ustar00rootroot00000000000000--- title: 更新履歴(2008年) layout: default --- > [Top](../) > [更新履歴]({{ site.baseurl }}/history/) > {{ page.title }} ## {{ page.title }} ### [2.1.0-081228](https://github.com/JDimproved/JDim/compare/d078f67593...65130226e7) (2008-12-28) - 動作環境にconfigureオプションを表示するようにした - 「JDについて」のダイアログに動作環境を追加した - 鬼車を使用可能にした( `./configure --with-oniguruma` ) - ユーザープロフィールの様に途中でhttp://が入っているアドレスが板として認識されるバグを修正 - 次スレ検索時にしおりが付いているスレの次スレにもしおりを付けるようにした - about:configに「スレをお気に入りに追加した時にしおりをセットする」設定を追加 - お気に入りに登録されているスレのキャッシュを削除した時にお気に入りも削除するようにした - ログアウト時にタイミングによってjd.confが壊れるバグを修正 - プロキシのホスト名にhttp://などが付いているとロード出来ないバグを修正 - 実況等で書き込みと読み込みのタイミングによって書き込み判定に失敗するバグを修正 - 一行目の改行前にスペースを沢山入れた書き込みの書き込み判定に失敗するバグを修正 ### [2.1.0-beta081216](https://github.com/JDimproved/JDim/compare/2c244310aa...d078f67593) (2008-12-16) - 次スレ検索機能を実装 - 次スレ移行時にお気に入りの名前とアドレスも更新するようにした - お気に入りビューの更新マークを再起動しても消えないようにした - お気に入り項目の編集直後に板かスレを開いたときにお気に入りの状態を保存するようにした - proxyのbasic認証に対応した - お気に入り挿入先の選択ダイアログを作り直した - お気に入りのショートカット(Ctrl+d)追加 - スレビューで適当なダイアログを開いて閉じた後にスクロールが変になるバグを修正 - 書き込みログ表示で再読み込みをすると空白のビューが表示されるバグを修正 - 板一覧の読み込みに失敗した時にダイアログを表示するようにした - お気に入りで空白行にもコメントを入れられるようにした - お気に入り編集ウィンドウを作成( ツールメニューに「お気に入りの編集」を追加 ) - 適当にやっていたDnD処理をきちんと作り直した - ダイアログ上でスレッドからのメッセージがDispatchされない問題をタイマーを使って解決した - squid3経由でスレを差分ダウンロードするとtimeoutする問題を解決(Content-Lengthを考慮した) - スレの差分ダウンロード時にプロキシがrangeを無視して先頭からdatを送って来る場合に対処 ### [2.0.3-081124](https://github.com/JDimproved/JDim/compare/dda4a740a5...2c244310aa) (2008-11-24) - 書き込み履歴を削除したときに、固定書き込み名とメールも消去するようにした。 ### [2.0.3-rc081117](https://github.com/JDimproved/JDim/compare/eef78753fb...dda4a740a5) (2008-11-17) - 「ここまで読んだ」が表示されていると「リロード後に新着レスに自動的に移動」が効かない問題を修正 - gcc4.4対応 - 更新チェック時にスレビューのタブのアイコンが正しく更新されない時があるバグを修正 ### [2.0.3-beta081110](https://github.com/JDimproved/JDim/compare/7062bd297d...eef78753fb) (2008-11-10) - 書き込みビューでTabキーで書き込みボタンがフォーカスされなかったバグを修正 - configureのオプションに「`--with-ppc74x0`」オプションを追加 - gtkmm-2.4 でコンパイルが通るようにした - uimの文字変換中にCtrl+qを押すとキーアクセレートが優先されてJDが終了するバグを修正 - boards.xmlの中に同じ板のアドレスが多重登録されていると起動時に落ちるバグを修正 - FreeBSDの場合はtimegmの代わりにmktimeを使うようにした。 - board.infoが空ファイルの時に落ちるバグを修正 - 復元しない設定で画像ビューを開いたまま閉じると再起動時に画像ビューが表示されるバグを修正 - モザイクのかけ具合をabout:configで調整できるようにした。 - vip2chの読み書きに対応した - ショートカットキー設定をGUI化した - マウスジェスチャ設定をGUI化した - ボタン設定をGUI化した - Beログイン時にBeアイコンが表示されるため自分の書き込みの判定に失敗するバグを修正 - Beアイコンを表示出来るようにした - アクセシビリティをonにすると画像削除でフリーズするバグを修正 - navi2ch互換スレ情報ファイルが壊れているとJDが落ちるバグを修正 - スレのプロパティに書き込み履歴クリアボタンを追加 - 板のプロパティに板内の全スレの書き込み履歴クリアボタンを追加 - プライバシー情報の削除に書き込みログの削除と書き込み履歴削除を追加 - スレビューが縦に非常に長い時に下の方にあるリンクがクリック不可になるバグを修正 - 設定メニューに書き込み履歴(鉛筆マーク)を保存しない設定を追加 - サイドバーのツールバーに「お気に入り更新チェックボタン」を追加出きるようにした - 色の詳細設定を表形式にした - 画像ビューで画像サイズをウィンドウの横幅のみに合わせられるモードを追加(xキーでモード切り替え) - BASIC認証有りの外部板のSETTING.txtとhead.txtのロード時も認証するようにした ### [2.0.2-080919](https://github.com/JDimproved/JDim/compare/d020f01212...7062bd297d) (2008-09-19) - 書き込み時にhana=mogeraやsuka=pontanの様なキーワードをきちんと取得するようにした - レス中の「<hr>」にスキンが適用されていなかったバグを修正 ### [2.0.1-080914](https://github.com/JDimproved/JDim/compare/65dc5275dd...d020f01212) (2008-09-14) - デフォルトフォントにMona-VLGothicを追加 - セットアップウィザードにペイン設定を追加 - スレ削除、板移転確認ダイアログに「表示しない」のチェックボタンを追加 - configureのオプションに「`--with-xdgopen`」を追加 - configureのオプションに「`--with-atom`」を追加 ### [2.0.1-beta080901](https://github.com/JDimproved/JDim/compare/55d6e4b5a7...65dc5275dd) (2008-09-01) - ユーザコマンドのGUI設定と階層化を実装 - jd.confのget_max_show_usrcmdを廃止 - 板移転時に落ちるときがあるバグを修正 - 他スレポップアップ時に落ちる時があるバグを修正 - ダウンロードしたファイルが画像でないときに Content-Typeを表示するようにした - 起動中にスレビューの余白の上でマウスを動かすと落ちるときがあるバグを修正 - Auroraなどの特定のテーマでスレ一覧のスクロールバーが描画されない時があるバグを修正 - リンクフィルタ機能実装 - スレ一覧で前回開いた時点以降に立てられたスレをアイコン表示するようにした - about:configに「お気に入りでカテゴリを常にひとつだけ開く」を追加 - スレのプロパティに更新日時のクリアボタンを追加 - ubuntuなど環境によってはプロパティの更新日時表示が正しくなかったバグを修正 - レスの引用コピーでアドレスもコピーするようにした。 - 書き込みビューに長い文章をコピペすると落ちるバグを修正 - 書き込みビューで改行や特殊文字の文字数を考慮するようにした - key.confでExecWrite = Shift+Enterとして書き込みをすると改行が入るバグを修正 - スレビューの検索、スレ一覧、板一覧の検索の補完記録を分離した - about:configに「3ペーン時にスレ一覧やスレビューを最大化する」を追加 - ユーザコマンドにBBSNAME,DATNAME,HOST,HOSTL,TITLE,BOARDNAME,LOGPATH,NUMBER 追加 - ユーザコマンドにCACHEDIMG,DIALOG,ONLY 追加 - ユーザコマンドにTEXTI, TEXTIU, TEXTIX, TEXTIE, INPUT, INPUTU, INPUTX, INPUTE 追加 - 外部ブラウザを用いてp2経由で書き込む機能を実装 - 画像ダウンロード時にリダイレクトが起きると反応が無くなる時があるバグを修正 - 2chログイン中は書き込み秒数規制をしないようにした - スレビューや画像ビューでエラー画像のエラーコードも削除できるようにした - まちBBSでread.cgi形式のアドレス表記に対応した - 多重ポップアップに移行するときフォーカスが外れるとポップアップが閉じるバグを修正 - BEにログインしているときは書き込み規制秒数ダイアログを表示しないようにした - solarisでもソースの修正無しでコンパイルが通るようにした ### [2.0.0-080722](https://github.com/JDimproved/JDim/compare/d6de55cff3...55d6e4b5a7) (2008-07-22) - 閉じるボタンを押してビューを閉じた後に再び開くとボタンの枠が表示されたままになるバグを修正 - テンキーの「Home」や「Pg Dn」などでも操作出来るようにした - about:configに「自前でウィンドウ配置を管理する」を追加 ### [2.0.0-rc080714](https://github.com/JDimproved/JDim/compare/affc455059...d6de55cff3) (2008-07-14) - 検索バーをescapeで閉じるようにした。 - 非表示の状態からサイドバーをドラッグして表示させるとXの閉じるボタンを押しても閉じないバグを修正 - 画像と書き込みビューを埋め込み表示にすると画像を表示した時に書き込みツールバーが現れるバグを修正 - 書き込みプレビューボタンをトグルキーにした - 自動板移転処理後に板一覧を更新すると自動移転処理できなくなるバグを修正 - 板のプロパティに更新日時の表示とリセットを追加 - 書き込みビューが画面の下側にあるときにAAポップアップの表示領域を広げた - お気に入り登録ダイアログをqやESCキーで閉じられるようにした - テーマによってはツールバーが描画されないバグを修正 - compiz環境で起動する度にウィンドウが右下にずれていくバグを修正 - gtkrcを利用してツリービューの背景色を描画する時に偶数行が描画されないバグを修正 ### [2.0.0-beta080702](https://github.com/JDimproved/JDim/compare/ccf7bf5f92...affc455059) (2008-07-02) - 「前回開いていた各ビューを起動時に復元する」のチェックを外してもロックしていたタブを復元 - about:config 実装 - キャッシュが無く、かつdat落ちしているスレを2回開いたときにタブが空白になるバグを修正 - 自分の書き込みを抽出できるようにした。 - HRタグ実装 - スレビューの埋め込み画像の枠の下部が表示されないときがあるバグを修正 - スクロール時の埋め込み画像の表示を高速化 - 書き込みログを一覧表示できるようにした(ツールメニューから) - 書き込みログが最大サイズ(バイト)を越えたら分割するようにした(about:configでバイト設定) - 自分の書き込んだレスにマークをつけることができるようにした(表示の詳細設定から) - 自分の書き込みに対するレスにマークをつけることができるようにした(表示の詳細設定から) - スレビューのしおりをアイコン表示に変更 - 板一覧のコメントの文字色を設定できるようにした(色の詳細設定から) - 色の詳細設定にリセットボタンを追加 ### [2.0.0-beta080601](https://github.com/JDimproved/JDim/compare/7b70d7f0da...ccf7bf5f92) (2008-06-01) - 簡易実況モード追加( ショートカット = F6 ) - スムーススクロール時のカクカクを軽減(描画タイマーを通常描画と別にした) - ツールバーとしてGtk::Toolbarをカスタマイズして使用する事にした - 各ビューと枠との間の余白を設定できるようにした( view_margin ) - 高速スクロールの速度調整設定追加( key_fastscroll_size ) - 書き込みメッセージのスペース/改行チェックの実装を取りやめた - 画像ビューの拡大縮小でテンキーの「+-」も使えるようにした - 多重ポップアップの際にポップアップの最大幅が1つ前の参照元の幅になっていたのを修正 - スレタイ検索で数字選択してポップアップさせると落ちるバグを修正 - スレタイ検索でキーワード抽出などをすると落ちるバグを修正 - 効果音再生機能を実装( `./configure --with-alsa` ) - スレ読み込み中にビューを閉じると落ちる時があるバグを修正 - スレビューのダブル、トリプルクリックによる範囲選択機能を強化 ### [2.0.0-beta080418](https://github.com/JDimproved/JDim/compare/4c35cda400...7b70d7f0da) (2008-04-18) - お気に入りの名前変更でスペースが入力できないバグを修正 - $TEXTでURLエンコードされていたバグを修正 - 書き込み中止のショートカットをAlt+q->Alt+wからAlt+q->Alt+qに変更 - jump_new_after_reloadを有効にするとスレ再取得後に元のレスにジャンプしないバグを修正 - 右ボタン+ホイール回転のタブ移動で2つおきに移動するバグを修正 - URLのリンクにマウスポインタを乗せた時にURLをステータスバーに表示するようにした - スレビューのテキスト上ではカーソルを I に変えるようにした - 起動時にログやスレタイ検索ビューも復元するようにした(検索は再実行しない) - スレ一覧やスレビューの進むと戻るを実装 - ツールバーを各ビューから独立分離した - スレ一覧、スレビューのツールバーの項目設定にロックボタンを追加 - gnutlsの必須バージョンを1.2以降に変更した - インストール直後の書き込みウィンドウのフォントがスレフォントと異なるバグを修正 - g++4.3対応 - 名前欄に b タグが二つ以上含まれたときに後ろのタグが無視されるバグを修正 - ツールバー項目選択ダイアログでアイテムを追加したときに出るassertion表示を無くした - タブをドラッグ移動するときに矢印を表示するようにした - サイズの大きいスレを開こうとすると落ちる時があるバグを修正 - リンクをクリックして他板のスレを開くと壊れていると判定されるバグを修正 - 画像ポップアップのメモリリークを修正 - 巨大サイズ画像を表示する時に固まったようになる問題を修正 - 横に細長い画像をモザイク表示すると落ちるバグを修正 - jump_after_reload、jump_new_after_reloadを有効にしたとき挙動が変だったバグを修正 - 誤爆注意ダイアログで×ボタンでウィンドウを閉じると書き込みするバグを修正 - お気に入りと混同すると指摘があったのでスレやレスの「ブックマーク」を「しおり」に改名 - 書き込みビューでAA入力するときに出ていたWARNINGが出ないようにした - 書き込みプレビューでスペースキーなどでスクロールできない問題を修正 - 文字が折り返していないレスのポップアップ表示で必ずスクロールバーが付くバグを修正 - 埋め込み画像があるレスのポップアップ表示で必ずスクロールバーが付くバグを修正 - 書き込みビューをemacs風操作にするとuim環境などで誤動作する問題を修正 - 最大化を解除する時に時々勝手にリサイズするバグを修正 - 外部板を板一覧から追加/編集できるようにした - a hrefの解釈でURLが "" に囲まれている場合とそうでない場合で処理を分けた - 起動時の引数処理を追加(既に動作中のJDにURLを渡して開ける機能など) - ボタンなど、できるだけwidgetの枠を除いて画面をすっきりさせた - サイドバーのコンボボックスを無くしてすっきりさせた - 書き込みメッセージの最後がスペースか改行で終わっている場合は確認するようにした - 板一覧の外部板の項目が空になった場合、etc.txtに保存されないバグを修正 - 一時的にDAT落ちになったスレが復活しても、お気に入りの表示のアイコンが戻らないバグを修正 - ツールバーに埋め込んでいたScrolledWindowをHBoxに変更した - レスポップアップの最大幅をスレビューの幅に制限するようにした - 「表示→詳細設定→一般→ボタンをフラット表示」を追加 jdim-0.7.0/docs/manual/2009.md000066400000000000000000000346571417047150700156070ustar00rootroot00000000000000--- title: 更新履歴(2009年) layout: default --- > [Top](../) > [更新履歴]({{ site.baseurl }}/history/) > {{ page.title }} ## {{ page.title }} ### [2.5.5-091228](https://github.com/JDimproved/JDim/compare/54f56db3d7...c3d339be60) (2009-12-28) - 書き込み履歴のあるスレを削除する時の確認ダイアログを非表示に出来るようにした ### [2.5.5-rc091225](https://github.com/JDimproved/JDim/compare/324e52e0ff...54f56db3d7) (2009-12-25) - about:configに「スレ一覧をロードする前にキャッシュにある一覧を表示する」を追加 - about:configに「お知らせスレ(924)のアイコンを表示する」を追加 - 色の詳細設定でデフォルトと異なる設定の行の背景色を変えるようにした - about:configの「true」「false」を「はい」「いいえ」表記に変更 - 書き込み履歴のあるスレを削除する時はダイアログで問い合わせるようにした ### [2.5.5-beta091220](https://github.com/JDimproved/JDim/compare/4877e5bd42...324e52e0ff) (2009-12-20) - スレ一覧でスレッド924(お知らせスレ)をアイコン表示するようにした - スレビューでIDの重複数のカウント処理を高速化した - スレビューで描画処理を少しだけ最適化した - スレ一覧の読み込みと表示処理を改良した - HTTP201の画像を開けないバグを修正 - about:configに「ビューを閉じても書き込み欄の日本語のON/OFF状態を保つ」を追加 - p2のログイン処理の仕様変更に対応 ( submit_member -> submit_userlogin ) - キャッシュの画像の読み込みに失敗すると画像表示が乱れるバグを修正 ### [2.5.0-091206](https://github.com/JDimproved/JDim/compare/2da6142f86...4877e5bd42) (2009-12-06) - 初回起動時のデフォルトフォント候補に小夏フォントを追加 - エラーなどのメッセージがファイルに出力される機能を起動オプションとして追加 ### [2.5.0-beta091123](https://github.com/JDimproved/JDim/compare/fd71704179...2da6142f86) (2009-11-23) - 更新チェック中に対象スレを手動でロードすると更新チェックが停止する時があるバグを修正 - お気に入り追加ダイアログで名前を変更してもお気に入りに反映されないバグを修正 - 板一覧再読み込みで更新量が多いと固まったようになるバグを修正 - 画像ローダのメモリキャッシュ機能を実装(デフォルトキャッシュ数 3 ) ### [2.5.0-beta091103](https://github.com/JDimproved/JDim/compare/7a0a639930...fd71704179) (2009-11-03) - 書き込みビューが空のときに対応するスレを閉じたら書き込みビューも閉じるようにした - スレ一覧の since、最終書込の表示形式を表示メニューから変更できるようにした - 画像ポップアップ上でショートカットキーを使えるようにした - 画像ポップアップ上でマウスジェスチャを使えるようにした - 画像ポップアップ上でコンテキストメニューを表示できるようにした - フォントと色の詳細設定をタブで分けた - 色の詳細設定に「スレビューの選択範囲の色設定に gtkrc を用いる」を追加 - 色の詳細設定にabout:configから「ツリービューの背景色設定に gtkrc を用いる」を移動 - 色の詳細設定で複数行を選択可能にした - about:configに「起動時にお気に入りを自動でチェックする」を追加 - HTML化された過去ログの読み込みに対応 - コンテキストメニューの表示がキャンセルされるとポップアップが消えなくなるバグを修正 - 画像ビューでタブスクロールボタンを押すとキーボードフォーカスが外れるバグを修正 - スレを閉じるときに無駄なディスクアクセスが生じていた問題を修正 - スレ一覧の列幅を調整するときに時々一番上のスレを開くバグを修正 - ログ一覧の表示機能を実装 ### [2.4.2-090927](https://github.com/JDimproved/JDim/compare/83458b61e8...7a0a639930) (2009-09-27) - 画像ビューが埋め込み表示の時にビューを開くとスレビューのスクロール位置がズレる問題を修正 ### [2.4.2-beta090914](https://github.com/JDimproved/JDim/compare/d9316e69d7...83458b61e8) (2009-09-14) - 書き込みビューにスペースと&nbsp;を相互に変換する機能を追加 - スレ一覧でツールバー非表示の時にCtrl+fや/で検索バーを表示できるようにした - 画像ビューにユーザコマンドメニューを追加 - スレビューのコンテキストメニューに「選択範囲のレスをあぼ〜ん」を追加 - DAT落ちしたスレを開いた時にダイアログを表示するようにした - タブメニューの移動の項目にアイコンを表示するようにした - スレ一覧でスレを複数指定してからDatの保存が出来るようにした - subject.txtのレス数よりも実際のレス数が多い時スレ一覧にアイコンを表示するようにした - スレ一覧で!を押したときに現在のソートのモードをステータスバーに表示するようにした - 更新済タブへ移動のショートカットキーとマウスジェスチャ(デフォルト未設定)を追加 ### [2.4.2-beta090806](https://github.com/JDimproved/JDim/compare/3ce0d1ebdf...d9316e69d7) (2009-08-06) - 板一覧の更新時にエラー等で新規データが空になった場合でも更新されてしまう問題を修正 - サイドバーのアイコン表示切り替えアルゴリズムを効率化 - サイドバーの項目をクリックしてもアイコン表示が戻らない時があったバグを修正 - サイドバーの更新チェックのキャンセルが効かなかったバグを修正 - サイドバーに履歴ビューを実装 - サイドバーの更新チェック機能を改良 - 履歴メニューを改良 - スレ一覧の!のソートモードにモード3を追加 - スレビューのコンテキストメニューの項目を編集できるようにした - スレビューのコンテキストメニューに「再読み込み」を追加 - スレビューのコンテキストメニューに「タイトルとURLをコピー」を追加 - スレビューのコンテキストメニューに「お気に入りに追加」を追加 - ツールバー項目設定の「元に戻す」ボタンを押してもデフォルト状態に戻らない問題を修正 - レスポップアップで行数の少ないレスでもスクロールさせないと全文が読めないバグを修正 ### [2.4.1-090712](https://github.com/JDimproved/JDim/compare/ded5cd0461...3ce0d1ebdf) (2009-07-12) - タブ切り替えメニューにタブのアイコンを表示 - お気に入りの次スレ自動更新時に確認ダイアログを表示するようにした - 表示メニュー→詳細→一般に「ツールバーの背景を描画する」を追加 ### [2.4.1-beta090628](https://github.com/JDimproved/JDim/compare/99a5bf2a46...ded5cd0461) (2009-06-28) - 水平線(HR)の左端をテキストの字下げ位置に合わせた - スレ一覧でキャッシュが無いスレは「dat保存」メニューを選択できないようにした - datファイルのサイズ表記を k から K に変更 - 画像メニューに「エラー画像を閉じる」を追加(パッチ124) - jd.confの設定値が異常な場合の安全策を追加 - 現在表示中のタブの表示が変(右端が切れる時がある)な問題を修正 - タブ上でのホイール回転でタブを循環表示するようにした - タブ切り替えボタンの枠がフォーカス時にはみ出るバグを修正 - ローダの仕様を変更(詳しくはJDスレ7-613参照) - 表示位置でタブを選択出来るキーバインドを追加 - win32対応 - スレビューの画像ポップアップメニューに「モザイクで開く」を追加 - 書き込みプレビューで新方式のトリップに対応 - 画像ポップアップ表示時にポインタがポップアップと重なるとポップアップが消えるバグを修正 ### [2.4.0-090522](https://github.com/JDimproved/JDim/compare/f2c2da668c...99a5bf2a46) (2009-05-22) - 細かいバグをいくつか修正 ### [2.4.0-rc090516](https://github.com/JDimproved/JDim/compare/6a4ae3ea31...f2c2da668c) (2009-05-16) - スレにフォントに含まれない文字がある時にコンソールに大量のエラーが表示される問題を修正 - Dat落ちしたスレの更新可能アイコン表示が消えないバグを修正 - 3ペーン表示時にタブをクリックしてもスレ一覧とスレビュー間のフォーカスが移らないバグを修正 - 3ペーン表示時にタブ上でホイールを回転してもスレ一覧とスレビュー間のフォーカスが移らないバグを修正 - タブ切り替えボタンでタブを切り替えたときにスレタイやステータスが変わらないバグを修正 - タブメニューの移動サブメニューでタブを切り替えたときにスレタイやステータスが変わらないバグを修正 ### [2.4.0-beta090510](https://github.com/JDimproved/JDim/compare/f151889baf...6a4ae3ea31) (2009-05-10) - gtk+-2.4辺りのバージョンのgtkを使用するとスレビューのツールバーのボタンが押せないバグを修正 - ハッシュの導入によるDBTREE::BoardBase::get_article関数の高速化 - configureオプションに「`--with-native`」を追加(gcc>=4.2, x86 or x86_64) - スレビューの描画領域の高さが1以下の時にスレの読み込みをすると落ちるバグを修正 - `-s` ( `--skip-setup` ) 起動オプション追加 ### [2.4.0-beta090429](https://github.com/JDimproved/JDim/compare/366a66a2c1...f151889baf) (2009-04-29) - スレのプロパティのあぼーん設定に「sage以外あぼーん」を追加 - まちBBSのサブジェクトをtitleタグから取得するようにした( offlawモードでない時 ) - まちBBSのofflaw.cgi読み込みモードを実装( 設定メニューの一般項目で設定) - 動作環境のconfigureオプションとして表示する内容を一部に限定した - p2にログインして直接書き込みできるようにした - 鬼車使用時に改行が入ると正規表現のマッチングに失敗する問題を修正 - about:configに画像のスムージングレベルの設定を追加 - datファイルのインポート機能を実装 - datファイルの一時表示機能を実装 - JBBSの文字コードをEUC-JPからEUCJP-WINに変更(丸付き数字の文字化け対策) - 次スレ移行時に古いスレタイが新スレタイに変わらない時があるバグを修正 - 16進数の文字参照を実装 - スレ一覧更新時にDat落ちしたスレをスレッドあぼーんのリストから取り除ける様にした(ダイアログ表示) - まちBBSのトップページをWebブラウザで開く事が出来ない問題を修正 - フォント幅の計算時にメモリの割り当て領域外にアクセスする場合があったバグを修正 - 板(スレ一覧)の更新チェック機能を実装 - タブ切り替えボタンをタブの右側に追加 ### [2.3.0-090305](https://github.com/JDimproved/JDim/compare/626f220688...366a66a2c1) (2009-03-05) - したらばに書き込めなくなった問題に対応( application/x-www-form-urlencoded ) - まちBBSの仕様変更に対応 ( read.pl -> read.cgi ) - スレタイの先頭がスペースで始まっている場合にスレ一覧で先頭のスペースが省略表示されるバグを修正 - タブが含まれる書き込みで鉛筆マークが付かないバグを修正 - gtkrcでnotebookのythicknessを0にしてスレ一覧を開くとCPUが100%になるバグを修正 - 他のビューにタブをドロップするとタブ移動の矢印が消えなかったバグを修正 - 画像ダウンロード中のツールチップの文字色をスレビューの文字色と合わせた - サイドバーに「お気に入り更新チェックしてタブで開く」ボタンを追加出きるようにした - web検索のショートカットキーを追加 (Ctrl+k) - スレタイ検索のショートカットキーを追加( Ctrl+t) - ローカルログ検索のショートカットキーを追加( Ctrl+Enter ) - 全ログ検索のショートカットキーを設定出きるようにした( デフォルト無効 ) - about:configに「Ctrl+qでウィンドウを閉じない」を追加 ### [2.2.0-090212](https://github.com/JDimproved/JDim/compare/46775b83b2...626f220688) (2009-02-12) - キャッシュのsubject.txtが古いと次スレのリンクをクリックした時にdat落ちになるバグを修正 - キャッシュのsubject.txtが無いと次スレのリンクをクリックした時にloading表示が消えないバグを修正 - キャッシュのsubject.txtが無い時に次スレのリンクをクリックした時のスレタイ類似度計算に - レーベンシュタイン距離を使うようにした - 次スレ移行時にお気に入りの名前が編集されているときは新スレの名前にしないようにした - 書き込み時にWAVE DASH 問題のため書き込みマークが付かない問題を修正 - 「スレ情報を消さずにスレ再取得」でタブのロックが外れるバグを修正 - 「スレ情報を消さずにスレ再取得」でタブの位置がズレるバグを修正 ### [2.1.1-beta090128](https://github.com/JDimproved/JDim/compare/65130226e7...46775b83b2) (2009-01-28) - お気に入りの編集に対するUNDOとREDOを実装した - お気に入り編集ウィンドウに検索窓を付けた - 書き込みしたレスに"が含まれていると鉛筆マークが付かないバグを修正 - p2経由で書き込みをチェックしてもbbspink.comではブラウザが開かないバグを修正 jdim-0.7.0/docs/manual/2010.md000066400000000000000000000433111417047150700155620ustar00rootroot00000000000000--- title: 更新履歴(2010年) layout: default --- > [Top](../) > [更新履歴]({{ site.baseurl }}/history/) > {{ page.title }} ## {{ page.title }} ### [2.7.5-101228](https://github.com/JDimproved/JDim/compare/5d1534c773...ba564b8d78) (2010-12-28) - キーワード抽出やログ検索で()をエスケープする様にした - 検索テキストボックスで漢字変換中にEscを押すとボックスが閉じるバグを修正 - 検索テキストボックスで補完候補ポップアップの選択行をCtrl+n, Ctrl+pで上下移動出来るようにした - 「選択範囲の画像を開く」で画像を開くとあぼーんしている画像も開いてしまうバグを修正 ### [2.7.5-beta101213](https://github.com/JDimproved/JDim/compare/51d1ac67d0...5d1534c773) (2010-12-13) - 512Kを越えたスレに書き込みしようとした時、書き込みビューを開く前に警告ダイアログを出すようにした - マウスボタン設定に「画像保存」を追加 - マウスボタン設定に「画像サイズ調整」を追加 - about:configのウィンドウの欄に「メニューバーを非表示にした時にダイアログを表示」を追加 - about:configのスレビューの欄に「画像ポップアップとカーソルの間の水平間隔(ピクセル)」を追加 - about:configのお気に入りの欄に「登録時に挿入先ダイアログ表示」を追加 - about:configの画像の欄に「deleteを押したときに確認ダイアログを表示する」を追加 - about:configの画像の欄に「インライン画像の最大幅」を追加 - about:configの画像の欄に「インライン画像の最大高さ」を追加 - about:configの書き込みビューの欄に「編集中のメッセージの保存確認ダイアログを表示する」を追加 - about:configのその他の欄に「数字入力ジャンプの待ち時間(ミリ秒)」を追加 - マウスジェスチャに「メニューバー表示」を追加 - マウスジェスチャに「メインツールバー表示」を追加 - マウスジェスチャに「JD終了」を追加 - マウスジェスチャに「全てのタブを閉じる」を追加 - マウスジェスチャに「他のタブを閉じる」を追加 - マウスジェスチャに「全ての更新されたタブを再読み込み」を追加 - マウスジェスチャに「最大化 / 最大化解除」を追加 - マウスジェスチャに「最小化」を追加 - ショートカットキーに「前のディレクトリに移動」を追加(デフォルト } ) - ショートカットキーに「次のディレクトリに移動」を追加(デフォルト { ) - ビューが表示されてない領域でもマウスジェスチャを認識するようにした - ポップアップのマージンを0に設定できないバグを修正 - 書き込み時に書込秒数規制の残り秒数をステータスバーの一番左に表示 - 書き込み時に書込秒数規制の残り秒数をウィンドウタイトルに表示 - スレが壊れている時などに、ラベルだけでなくステータスバーの色も変えるようにした - 書き込み規制中や行数オーバー時に書き込みウィンドウのラベルやステータスバーの色を変えるようにした - 全画面モードを暫定実装 (表示メニュー → 「全画面」 で切り替え) ### [2.7.5-beta101104](https://github.com/JDimproved/JDim/compare/4398cdccbc...51d1ac67d0) (2010-11-04) - p2と●を同時ログインしているとdat落ちした過去ログを取得できないバグを修正 - 画像ビューのコンテキストメニューの「複数の画像を閉じる」にエラー関係の設定を追加(404のみを閉じる等) - IDの末尾を判定してIDポップアップやID抽出に端末の種類を表示するようにした - ファイルメニューの「お気に入り保存」を廃止して「セッション保存」を追加 - SIGHUP、SIGINT、SIGQUITの割り込み時にバックアップファイルを作成しないでセッションを保存するようにした - about:configのその他に「指定した分ごとにセッションを自動保存」を追加 - about:configのお気に入りの所に「重複項目を登録する」を追加 - about:configのツリービューの行間スペースを0にしても板一覧に適用されないバグを修正 - キーワードやIDなどの抽出ビューの表示を更新ボタンやショートカット(F5, s)で更新出来るようにした - 過去のレスに対してレス参照をカウントするようにした - 表示メニューの詳細の一般に「ステータスバー表示」を追加 - 「ブラウザで開く」で画像を開くとき、キャッシュではなくて元のアドレスを開くようにした - 「(画像の)キャッシュをブラウザで開く」をコンテキストメニューに追加 - ショートカットキーに「元のスレを開く」を追加(デフォルト Shift+F5,S) - IDの代わりにIPが表示される板ではIPをカウントするようにした - セットアップウィザードで3ペインを選択してもメニューの表示では2ペインのままになっているバグを修正 - ユーザーコマンドに $OLDHOSTNAME, $OLDHOST, $OLDHOSTNAMEL, $OLDHOSTL, $BBSNAMEL, $DATNAMEL, $LOCALDATL 追加 - 「リロード後に新着レスに移動する」時に実況モードにするとリロードする度に新着セパレータに戻るバグを修正 - レスポップアップを使ってAAの騙し絵を表示できるようにした - 画像の拡張子がgifでないのにリダイレクト先の拡張子がgifになっていると偽装判定に失敗するバグを修正 - SETTING.txtのBBS_UNICODEがpassである板でのunicode文字書き込みに対応した - スレ一覧に「増分」の列を追加(前回スレ一覧を開いた時からのスレ数の増分) - 表示→詳細設定→書き込み設定→テキストを折り返し表示を追加(デフォルトON) ### [2.7.0-100823](https://github.com/JDimproved/JDim/compare/0ea7d7a3db...4398cdccbc) (2010-08-23) - p2ログイン時のIDとパスワードをURLエンコードしていなかったバグを修正 - 書き込みビューやサイドバーを閉じている時に境界をドラッグしたら境界を元の位置に戻すようにした ### [2.7.0-beta100808](https://github.com/JDimproved/JDim/compare/abf810d790...0ea7d7a3db) (2010-08-08) - スレビューで範囲選択していないときにCtrl+tで空のスレタイ検索ビューを開けない問題を修正 - about:configのその他に「マウスジェスチャを有効にする」を追加 - about:configのツリービューに「ツリービューのエクスパンダを表示する」を追加 - about:configのツリービューに「ツリービューのレベルインデント調整量(ピクセル)」を追加 - about:configのスレビューに「ポップアップが消えるまでの時間(ミリ秒)」を追加 - about:configのネットワークに「同一ホストに対する最大コネクション数」を追加 - about:configの履歴ビューに「ペーン境界をクリックしてサイドバーを開け閉めする」を追加 - about:configのスレ一覧の所に「dat落ちしたスレを表示する」を追加 - 画像ビューをクリックする度に元サイズ画像とウィンドウに合わせた画像を切り替えるようにした。 - 実況時にスレの読み込みのタイミングに合わせて書き込みビューを開くとビューが正しく開かないバグを修正 - 「次検索」と「次のレスへ移動」のショートカットキー(n)が被っていたので「次検索」から除いた - 表示メニューの詳細設定のツールバー表示のメインツールバーの表示切り替え項目をチェック形式に変更した - ショートカットキーの設定ダイアログに「メインツールバー表示」を追加(デフォルトキーは未設定) - 設定から「スレ一覧に過去ログも表示する」の設定を廃止して板別に表示設定出来るようにした - スレビューのレスポップアップの抽出に「ジャンプ」を追加 - スレビューから板レベルのNGワードやNGネームを追加した時、その板を開かないと情報が保存されないバグを修正 - ホイールスクロール直後にポインタの下がリンクの時はマウスを少し動かさないとポップアップしないようにした - ログ検索時に正規表現メタ文字が含まれていたらエスケープするようにした - スレビューで選択中の文字をキーワード抽出する時に正規表現メタ文字が含まれていたらエスケープするようにした - 拡張子が偽装されている画像のモザイクを外すとき、既に外してあっても警告ダイアログが表示されるバグを修正 - スレを削除する時に画像キャッシュも削除するかダイアログを表示するようにした - IDの後ろに●が付いているレスが発言数にカウントされないバグを修正 - 起動オプションに `-n/--norestore` を追加 ### [2.7.0-beta100627](https://github.com/JDimproved/JDim/compare/6fd0027336...abf810d790) (2010-06-27) - お気に入りのコンテキストメニューに「並び替え」項目を追加 - お気に入り登録時に同一スレや板が登録されている場合はダイアログを出すようにした - ショートカットキーとマウスジェスチャの設定項目にサイドバーの更新チェックを追加 - ローカルルールのHTTPコードの戻り値が302の時にプロパティに内容が表示されないバグを修正 - 板ボタンの▼をクリックした時のメニューにローカルルール表示と板のプロパティ表示の項目を追加 - 投稿確認ダイアログにローカルルールを表示するようにした - about:configに「画像ビューのフォーカスが外れたら折りたたむ」を追加 - about:configに「スレビューのスクロールバーを左に配置する」を追加 - JD再起動時に全キャッシュ検索や書き込みログのタブが復元されないバグを修正 - アンカーやリンクを含んだ行をトリプルクリックで行全体選択出来ないバグを修正 - 書き込んでもスレ一覧の「最終書込」が更新されないバグを修正 - あぼーんしたレスの ID も同一 ID の発言数にカウントするようにした - スレ一覧のコンテキストメニューに「スレ情報を消さずに再取得」を追加 - スレ削除時の警告ダイアログで close ボタンや esc キーで閉じてもスレを削除するバグを修正 - 画像ロード等でHTTP301に対応 - ログ検索ツールバーに「しおり」チェックボックスを追加 - スレ一覧、スレビューの検索やキーワード抽出で全角英数字や半角カナを区別しない様にした - キーワード抽出で抽出したURLのレス番号が細かく分かれるバグを修正 - スレビューの検索でDOMノードを越えて検索出来るようにした - フォントサイズが大きいとスレ一覧のポップアップが一瞬で消えるバグを修正 - 「サイドバー表示」のマウスジェスチャ設定を追加 - 書き込みウィンドウの右クリックメニューの選択時の挙動を微調整 - お気に入りの更新チェックが行われない時があるバグを修正 ### [2.6.5-100425](https://github.com/JDimproved/JDim/compare/dbca691403...6fd0027336) (2010-04-25) - 板のプロパティのNGタイトルのページに「dat落ちしたスレのタイトルを削除する」ボタンを追加 - 環境変数 JD_LOCK でロックファイルのパスを指定出来るようにした - ローカルにあるdatファイルを起動時に開いた後で再起動すると落ちるバグを修正 - 書き込みビューを開いてからスレビューを全て閉じてプレビューでポップアップ表示すると落ちるバグを修正 - スレビューのコンテキストメニューに「選択範囲の画像をあぼ〜んする」を追加 ### [2.6.5-beta100411](https://github.com/JDimproved/JDim/compare/1b0bf46c99...dbca691403) (2010-04-11) - 次/前の書き込みへの移動を追加(Shift+F2/Ctrl+Shift+F2) - スレ一覧のコンテキストメニュー項目を編集可能にした - スレビューのコンテキスメニューに「選択範囲の画像を開く」を追加 - スレビューのコンテキスメニューに「選択範囲の画像を削除」を追加 - ツールバー/リスト項目設定における不適切な配色のしかたを修正 - JBBSのスレは更新チェックを行えないようにした(Last-Modifiedを取得できないため) - まちBBSでofflawモードでない時は更新チェックを行えないようにした(HTTP500が返るため) - まちBBSでロード時にdat落ち判定を可能にした
(→ 更新チェック時の判定は常にHTTP200が返るので無理。JBBSは常にHTTP200、0バイトが返るので無理) - 板一覧やスレ一覧のマウスボタンの設定を変えるとお気に入りのディレクトリがクリックで開かないバグを修正 - Webブラウザ設定にchromeを追加 - 板のプロパティで最大レス数を指定可能にした - 実況モードでスレビューのフォーカスを外している時にビューの再描画が実行されない時があるバグを修正 - URL末尾の文字参照が正しく認識されないバグを修正 - FIFOにエラーがあった場合のダイアログに「今後表示しない」を追加 - お気に入りの仮想板機能を実装 - お気に入りに登録しているスレの次スレを開いた時に表示されるダイアログに「お気に入り追加」を追加 - about:configに「dat落ちしたスレをNGスレタイトルから除く」を追加 - 実況のスクロール速度を0に設定出きるようにした。 - `>>111`の様に途中に全角文字を含む数字を書き込むと正しくアンカー処理されないバグを修正 - スレ一覧でスレをあぼーんする度にスレ一覧を再描画していた問題を修正 - スレのプロパティのNGスレ番号に取得レス数以上の数字を入れると落ちるバグを修正 - 書き込みビューを閉じたときにUNDOのバッファが残ったままになっていたバグを修正 - 設定ダイアログのテキストボックスに文字を入れてエンターキーを押すとOKで閉じるようにした - 外部板の書き込み時にクッキーを送らない時がある問題を修正 - オートスクロールのマークがスレビューのスクロールバーにかかっていると落ちるバグを修正 ### [2.6.0-100208](https://github.com/JDimproved/JDim/compare/61155e314d...1b0bf46c99) (2010-02-08) - 画像ビューでお気に入り追加ダイアログを閉じると画像ビューのフォーカスが外れるバグを修正 - gtk2.18以降でタブにスクロールボタンの矢印が表示されたままになる時があるバグを修正 - WAVEDASH問題を修正 ### [2.6.0-rc100130](https://github.com/JDimproved/JDim/compare/606c2fbb11...61155e314d) (2010-01-30) - 2chにログインしているとHTML化された過去ログを読めなくなるバグを修正 - スレ一覧のコンテキストメニューに「次スレ検索」を追加 - 書き込み欄を埋め込み表示してる時、スレビューを開いていないと新スレ作成欄が表示されないバグを修正 - 板一覧やスレ一覧を表示した状態でCompizのスケールを起動してJDを選択するとスレ一覧やスレが開くバグを修正 ### [2.6.0-beta100123](https://github.com/JDimproved/JDim/compare/c3d339be60...606c2fbb11) (2010-01-23) - スレビューのスクロール処理を高速化 - キー押しっぱなしでスレビューをスクロールすると止まらなくなる事があるバグを修正 - スレ一覧を復元した時にdat落ちしたスレがスレ一覧に表示されないバグを修正 - 画像読み込み中に画像ビューの他の画像を削除すると読み込み中の画像が閉じるバグを修正 - 画像の強制再読み込みをしても埋め込み画像の表示が変わらないバグを修正 - 状況によってはArticleHash::it_get()でセグフォで落ちる場合があるバグを修正 - dat落ちスレのロード中にタブを切り替えて、タブを閉じますかのダイアログで切り替えたスレが閉じるバグを修正 - 画像ポップアップの表示サイズを大きくしたときに常に画面内に表示されるようにした(クリックかESCで閉じる) - subject.txtが正しいフォーマットでないと落ちる時があるバグを修正 - スレビューのあぼーんをクリックしたときに出てくるポップアップメニューにあぼーん設定を追加 - gtkでEmacsのキーバインドをしているときAAメニューの項目が誤動作するバグを修正 - about:config → ツリービュー → "カテゴリを開いたときにスクロールする"を追加 - マウスボタン詳細設定 → スレビュー → "アンカーをクリックでジャンプ" を追加 - 「URLを開く」ダイアログを追加 ( Ctrl+o ) jdim-0.7.0/docs/manual/2011.md000066400000000000000000000150031417047150700155600ustar00rootroot00000000000000--- title: 更新履歴(2011年) layout: default --- > [Top](../) > [更新履歴]({{ site.baseurl }}/history/) > {{ page.title }} ## {{ page.title }} ### [2.8.2-110808](https://github.com/JDimproved/JDim/compare/122e58e792...a7df368213) (2011-08-08) - スレタイ検索(Ctrl+t)のショートカットをスレビュー以外でも効くようにした - 画像ビューの「複数の画像を閉じる」でステータスの[m/n]表示が正しく更新されないバグを修正 - datファイルのmtimeをサーバ上のmtimeに一致させるようにした - Youtube動画のサムネイル画像をインライン表示出来るようにした - about:configの「ウィンドウ」に「タブ上でマウスホイールを回転してタブを切り替える」を追加 - スレのプロパティのあぼーん項目に「板/全体レベルでのあぼ〜んを有効にする」のチェックを追加 ### [2.8.2-beta110724](https://github.com/JDimproved/JDim/compare/2b40cc4b7e...122e58e792) (2011-07-24) - 画像を削除すると画像ビューのキャッシュに無い画像が全て閉じるバグを修正 - プレビュー画面で画像をポップアップ表示させてる時にdeleteを押してもポップアップが消えないバグを修正 - 色の設定に「複数発言したIDの文字色」を追加 - 色の設定に「多く発言したIDの文字色」を追加 - 色の設定に「参照されていないレス番号の文字色」を追加 - 画像ビューのステータスバーに[タブ番号/タブ数]と[画像ファイル名]を表示 - アルファベットの大文字とマウスパッドのキーコードがぶつかっていたバグを修正 (例: K と KP_left) - スレタイ検索でタイトル中に<が入っているとその後の文字が消えるバグを修正 - ファイルダイアログで日付の列が日本語ロケールにならないバグを修正 - about:configの「画像」の「埋め込み画像ビューを閉じたときにタブも閉じる」を追加 - スレビューのコンテキスメニューに「削除」を追加 - 起動オプションに `-g/--geometry WxH-X+Y` を追加 - 複数のスレビューを閉じる時、タブの再描画をしないようにして高速化 - スレ一覧ツールバーに「ハイライト解除」ボタンを追加出来るようにした - 埋め込み画像ビューのタブ上のサムネイルの間をクリックするとサイドバーが閉じるバグを修正 - ログ/スレタイ検索のツールバーの並びを編集出来るようにした - 全画面表示のショートカットキーを追加(F11) - あぼーんしたスレがdat落ちするとスレ一覧のNGスレタイトルのリスト順が並び替えられるバグを修正 - 複数画像の保存時にファイル名が重複した時、画像キャッシュ内のファイル名で保存出来るようにした - スレビューでdeleteを押したときの確認ダイアログに「今後表示しない」チェックを追加 - マウスジェスチャに「削除」を追加 (デフォルト↓→↓) - ubuntu11.04でポップアップ表示時にスレビューをクリックするとポップアップが消えるバグを修正 - リンクの末尾から"()[]"を特別に除外する仕様を廃止 - 閉じた板の履歴を履歴メニューとサイドバーに追加 - サイドバーで複数選択した複数画像も開けるようにした ### [2.8.1-110312](https://github.com/JDimproved/JDim/compare/22e977fd5f...2b40cc4b7e) (2011-03-12) - スレ内にあるリンクの文字列が丁度256文字の時にバッファオーバフローしていた問題を修正 - about:configの書き込みビューの欄に「デフォルトの書き込み名」を追加 - about:configの書き込みビューの欄に「デフォルトのメールアドレス」を追加 - 設定メニューのあぼーん項目に「NG正規表現で大小と全半角文字の違いを無視する」を追加 - 範囲選択した文字列を抽出する時に、文字列に空白が含まれているなら前後に""を付けるようにした - 書き込み失敗時に出るダイアログで詳細を表示出来るようにした。 - スレ一覧のある行を長押ししてから素早くクリックし直すとそのスレが2度読みされるバグを修正 - オフラインモードなのにキャッシュの無い画像が「範囲選択の画像を開く」でダウンロードされるバグを修正 - ショートカットキー詳細設定の「編集」に「改行」を追加(デフォルト 無し) ### [2.8.1-beta110214](https://github.com/JDimproved/JDim/compare/819fd49060...22e977fd5f) (2011-02-14) - スレ一覧でスレタイが512文字を越えると端末にPango-WARNINGで表示されて落ちるバグを修正(1024文字まで拡張) - 2chでクッキーHAPを保存して送るようにした(いわゆる忍法帖対応) ### [2.8.0-110203](https://github.com/JDimproved/JDim/compare/e9f3eeb83e...819fd49060) (2011-02-03) - タブ幅を縮める際にタブ幅の計算を正確に行うようにした - 新しくスレを開いたときのタブ幅の計算回数を減らした - 機能してなかった $NUMBER ユーザコマンドをきちんと作った。 ### [2.8.0-beta110118](https://github.com/JDimproved/JDim/compare/ba564b8d78...e9f3eeb83e) (2011-01-18) - BEの新ログイン形式に対応した - アイコンテーマを実装 - スキンファイルのデフォルトディレクトリを~/.jd 直下から ~/.jd/theme に変更 - サイドバーと履歴メニューに「最近閉じた画像」を追加した - 設定 → プロパティ → 板一覧のプロパティを追加 - ショートカットキーに「最後に閉じたタブを復元」を追加(デフォルト Ctrl+T) - ショートカットキーに「sageのON/OFF切り替え」を追加(デフォルト Alt+s) - マウスジェスチャに「最後に閉じたタブを復元」を追加(デフォルト →←) - スレ一覧とスレビューのタブメニューの「複数のタブを閉じる」に「同じアイコンのタブ」を追加 - about:configのウィンドウ欄に「状態変更時にメインステータスバーの色を変える」を追加 - 排他処理をきちんとして、まれにリロード時に反応が無くなるバグを修正 jdim-0.7.0/docs/manual/2012.md000066400000000000000000000031441417047150700155640ustar00rootroot00000000000000--- title: 更新履歴(2012年) layout: default --- > [Top](../) > [更新履歴]({{ site.baseurl }}/history/) > {{ page.title }} ## {{ page.title }} ### [2.8.5-120826](https://github.com/JDimproved/JDim/compare/35818ddd51...591ad36134) (2012-08-26) - リンクフィルタのコマンドでの置換文字に\0〜\9を追加 - Youtubeのサムネイルが表示されない問題を修正 - 正規表現ライブラリとしてPOSIX regex の代わりにPCREを使用できるようにした - スレ一覧の時刻表示オプション「年/月/日 時:分:秒」を追加 - スレ一覧に、スレを最後にロードした最終取得時刻を表示できるようにした - 数値文字参照(&#169;など)は「;」なしでも表示できるようにした ### [2.8.5-beta120206](https://github.com/JDimproved/JDim/compare/a7df368213...35818ddd51) (2012-02-06) - svn版のabout:config に「ビュー内から他のビューを開いたときのタブの位置」を追加 - HTTPステータスコードが301のリダイレクトに対応 - cssでTABの行が無視されていたバグを修正 - 書き込みビューのプレビューアイコンを変更しても反映されないバグを修正 - 環境に依存しやすいシステムコール(utimensat)を使用していた問題を修正 - 非2chにアクセスする際のデフォルトUAを暫定的に「Monazilla/1.00 JD」にした - p2.2ch.net→w2.p2.2ch.netのリダイレクトに対応 - gnutls>=2.12.0においてSSL接続が出来なくなっていたバグを修正 jdim-0.7.0/docs/manual/2013.md000066400000000000000000000052061417047150700155660ustar00rootroot00000000000000--- title: 更新履歴(2013年) layout: default --- > [Top](../) > [更新履歴]({{ site.baseurl }}/history/) > {{ page.title }} ## {{ page.title }} ### [2.8.6-130518](https://github.com/JDimproved/JDim/compare/9ac7c2079b...5cc6041855) (2013-05-18) - タブを閉じたとき、タイトルとURLが変更されないことがあるバグを修正 ### [2.8.6-rc130414](https://github.com/JDimproved/JDim/compare/22d5584e97...9ac7c2079b) (2013-04-14) - URL変換に$THUMBNAILオプションを追加した - youtubeのサムネイル表示を統合し、urlreplace.confファイルに設定するようにした - 連続投稿が忍法帖で規制された場合にも、Samba24の規制時間を取得するようにした - 正規表現ライブラリとしてPCREを使用しているとき、「.」が改行に一致するよう変更 - WAVEDASH問題を修正 - アスキーアート用のフォントを設定したときのレイアウトの崩れを修正 ### [2.8.6-beta130304](https://github.com/JDimproved/JDim/compare/591ad36134...22d5584e97) (2013-03-04) - &が&amp;に変換されているdatファイルに対応した - automake-1.13に対応した - アスキーアート用のフォントを設定できるようにした - URLを変換(urlreplace.conf)できるようにした - 実況モードでポップアップ表示中はスクロールを一時停止するようにした - 書き込みマークを設定/解除できるようにした - 次スレ検索を開くときのタブの位置を変更できるようにした - 複数のスレや画像をロードするときの待ち間隔を短く変更した - datを保存に、名前を付けて保存するショートカットキーを割り当てた - 表示中のビューのプロパティを開くショートカットキー(Ctrl+P(大文字))を追加 - 選択範囲の画像を開くショートカットキー(Ctrl+I(大文字))を追加 - 選択範囲のレスをあぼーん、画像を消す・あぼ〜んにショートカットキー設定を追加 - 保護した画像は、選択範囲の画像を消す・あぼ〜んの対象外に変更 - 画像のエラー情報(404エラーなど)は、選択範囲の画像を消す対象に変更 - ショートカットキーは、CapsLock状態に影響しないように変更 - レスに長いURLを含むスレを開くと、落ちるバグを修正 - subject.txtのレス数が読み込めない場合などに、落ちるバグを修正 - スレビューの描画で高負荷時に、落ちることがあるバグを修正 jdim-0.7.0/docs/manual/2014.md000066400000000000000000000051411417047150700155650ustar00rootroot00000000000000--- title: 更新履歴(2014年) layout: default --- > [Top](../) > [更新履歴]({{ site.baseurl }}/history/) > {{ page.title }} ## {{ page.title }} ### [2.8.8-140601](https://github.com/JDimproved/JDim/compare/0cc6c39b70...90b6d7e325) (2014-06-01) - 2chからのエラーコードに追加対応した - 2ch、2ch互換、まちBBSなど、板の種類が異なるときに移転処理を行わないようにした - 「about:config」メニューを上位に移動した - バージョン2.8.8-betaでのスレタイ検索のURL変更を取り消し、find.2ch.netに対応した - p2のURLをp2.2ch.scに変更 - 過去ログの取得時に、「壊れています」と誤判定することがあるバグを修正 - 同じスレを複数登録した場合などに、サイドバーの選択がずれることがあるバグを修正 - subject.txtが不当な形式の時に、落ちるバグを修正 ### [2.8.8-beta140329](https://github.com/JDimproved/JDim/compare/4a759065ae...0cc6c39b70) (2014-03-29) - 2chにログインした場合、過去ログをrokkaシステムで取得するようにした - 2chの過去ログを、offlaw2.soで取得できるように設定を追加した - 改行文字がない通知スレッドの表示に対応した - サイドバー/スレ一覧の選択を表示中のビューと同期するメニューを追加 - スレ一覧の最終取得の時刻形式を個別に設定するオプションを追加 - 「ツリービューで選択したビューを開くときのタブの位置」を追加 - 「他のビューを開くときのタブの位置」を適用する操作を追加 - findおよびp2のドメインをmoritapo.jpに変更 - 2chにログインするパスワードを、浪人にあわせ「秘密鍵」に表記変更 - バージョン2.8.7で、画像ビューをオフに設定できなくなっていたバグを修正 ### [2.8.7-140104](https://github.com/JDimproved/JDim/compare/5cc6041855...4a759065ae) (2014-01-04) - したらば(JBBS)のドメイン変更に対応した - 表示中のサイドバーやスレ一覧の選択を、表示中のビューと同期するようにした - 「終了」「オンラインマニュアル」のショートカットキーを変更できるようにした - Ctrl+qでウィンドウを閉じない設定は、ショートカットキー設定に統合した - 画像ビューメニューをラジオボタンに変更 - 「AAレスと判定する正規表現」を空に設定したり、リセットできないバグを修正 jdim-0.7.0/docs/manual/2015.md000066400000000000000000000015101417047150700155620ustar00rootroot00000000000000--- title: 更新履歴(2015年) layout: default --- > [Top](../) > [更新履歴]({{ site.baseurl }}/history/) > {{ page.title }} ## {{ page.title }} ### [2.8.9-150226](https://github.com/JDimproved/JDim/compare/3ed63d419b...5c05922bb5) (2015-02-26) - リロード待ち状態でスレが更新されないままになる問題を修正した - タブ表示をさらに最適化した ### [2.8.9-rc150201](https://github.com/JDimproved/JDim/compare/90b6d7e325...3ed63d419b) (2015-02-01) - エモーションの表示に対応した - スレタイ検索のURLをdig.2ch.netに変更した - いわゆる一行スキン用に<MESSAGE br="no"/>の指定を追加した - タブ表示を最適化した - 特定のスレタイで落ちるバグを修正 jdim-0.7.0/docs/manual/2017.md000066400000000000000000000066111417047150700155730ustar00rootroot00000000000000--- title: 更新履歴(2017年) layout: default --- > [Top](../) > [更新履歴]({{ site.baseurl }}/history/) > {{ page.title }} ## {{ page.title }} ### [2.8.9-171124](https://github.com/JDimproved/JDim/compare/ab77d02541...be3d051dc5) (2017-11-24) - 絵文字(> U+FFFF)をサポートする ([5f1ba8dd76](https://github.com/JDimproved/JDim/commit/5f1ba8dd76f50f2b8943507d7a84843739267f96)) - README.mdにビルドに関するTipsを追加する ([4363a7560b](https://github.com/JDimproved/JDim/commit/4363a7560b726fa814823a1897ece54ea70288e7)) ### [2.8.9-171003](https://github.com/JDimproved/JDim/compare/c4efac5bd2...ab77d02541) (2017-10-03) - 5ch.netに対応する ([5367fc361d](https://github.com/JDimproved/JDim/commit/5367fc361d77cad50c6a63ac16696ab2e8d69ed1)) - README.mdを作成してインストール方法の説明を追加する ([07e581f115](https://github.com/JDimproved/JDim/commit/07e581f11530970c20ffbd540cd58ff8f45bab6b), [af1ac02d26](https://github.com/JDimproved/JDim/commit/af1ac02d269c09830814725134a8d6d39a0dd8bc), [abe2fb78f3](https://github.com/JDimproved/JDim/commit/abe2fb78f3d15c55c398ce8bec0d83e7c2a94141), [aa2b0edc1a](https://github.com/JDimproved/JDim/commit/aa2b0edc1a48802503c733c802e7ab93c047dd0a), [f8d91689b5](https://github.com/JDimproved/JDim/commit/f8d91689b5d253a427820fbd082c99c9c8dd2851)) ### [2.8.9-170418](https://github.com/JDimproved/JDim/compare/57b5f412b1...c4efac5bd2) (2017-04-18) - 一部HTTPSサイト(SNI拡張を使ってるサイト)への接続が失敗するのを修正する ([d8712b97c7](https://github.com/JDimproved/JDim/commit/d8712b97c7fa8c434ad12a8621d98b3c41e8f139)) - C++11のコンパイラオプションの自動追加 (autoconf-archive パッケージが必要) ([5462869964](https://github.com/JDimproved/JDim/commit/54628699641f8595ffb96c70987a8d7e3dbf28f4)) ### [2.8.9-170416](https://github.com/JDimproved/JDim/compare/66f0eea81c...57b5f412b1) (2017-04-16) - proxyを介した2ちゃんねる系サイトへのhttpsアクセスを修正する ([fd5e01d7ef](https://github.com/JDimproved/JDim/commit/fd5e01d7efe643915c963c663d633799db9b5293), [e94065247d](https://github.com/JDimproved/JDim/commit/e94065247db8be67ba4a3b19bab830fb5f363f5e), [470d1e42bb](https://github.com/JDimproved/JDim/commit/470d1e42bbaeae8132270014c6d353de8e2b5521), [dc7d540a9b](https://github.com/JDimproved/JDim/commit/dc7d540a9bf072c7d1f934a7f5541d19f3f0c4d1)) ### [2.8.9-170411](https://github.com/JDimproved/JDim/compare/5c05922bb5...66f0eea81c) (2017-04-11) - `https`の実験的なサポート ([a191ea4a06](https://github.com/JDimproved/JDim/commit/a191ea4a069f22ca904835f514f5a1360d37f08f)) - ビルドの問題を修正する ([169f9932a4](https://github.com/JDimproved/JDim/commit/169f9932a4b58c83a26fa32e5d0427c778510dda)) - 暗黙的なboolへの変換によるコンパイルエラーを修正する ([6b79cd4a8c](https://github.com/JDimproved/JDim/commit/6b79cd4a8c9e398b781021b9bb54c9a98c0ee77e)) - 文字列のDOM解析を修正する ([518d74f06c](https://github.com/JDimproved/JDim/commit/518d74f06cd8fc4a95674da4f866a09e6b858d19)) - ローダーの無限ループ発生を修正する ([d4aa5f1cc9](https://github.com/JDimproved/JDim/commit/d4aa5f1cc9869bc98b8be6e939599eecf54d6a52)) jdim-0.7.0/docs/manual/2018.md000066400000000000000000000040031417047150700155650ustar00rootroot00000000000000--- title: 更新履歴(2018年) layout: default --- > [Top](../) > [更新履歴]({{ site.baseurl }}/history/) > {{ page.title }} ## {{ page.title }} ### [2.8.9-20181023](https://github.com/JDimproved/JDim/compare/c1c9d64a85...bb608f24b1) (2018-10-23) バージョンの日付に[コミット作成日][envnote]を使うようになった。 [envnote]: {{ site.baseurl }}/environment/#note "動作環境の記入について | JDim" - Fix hash length for git revision information ([#14](https://github.com/JDimproved/JDim/pull/14)) - Fix behaviors ([#13](https://github.com/JDimproved/JDim/pull/13)) - Add support for std::thread ([#12](https://github.com/JDimproved/JDim/pull/12)) - Add git revision information to env info ([#11](https://github.com/JDimproved/JDim/pull/11)) - Replace incompatible features ([#10](https://github.com/JDimproved/JDim/pull/10)) - Use new features ([#9](https://github.com/JDimproved/JDim/pull/9)) - Replace deprecated features ([#8](https://github.com/JDimproved/JDim/pull/8)) - Add: ubuntu 18.04 install section. ([5fc7b8b7fd](https://github.com/JDimproved/JDim/commit/5fc7b8b7fd840659900e443872b1c50149638f39)) - Deprecate usage of GLib memory profiler on 2.46 and above ([#6](https://github.com/JDimproved/JDim/pull/6)) ### [2.8.9-180424](https://github.com/JDimproved/JDim/compare/9e692ad2ba...c1c9d64a85) (2018-04-24) - Fix Loader::skip_chunk. ([#5](https://github.com/JDimproved/JDim/pull/5)) ### [2.8.9-180217](https://github.com/JDimproved/JDim/compare/be3d051dc5...9e692ad2ba) (2018-02-17) - std:stringアクセスの別修正 ([#4](https://github.com/JDimproved/JDim/pull/4)) - std:stringアクセスのチェックの修正 ([#3](https://github.com/JDimproved/JDim/pull/3)) - Check array member access more strictly ([#2](https://github.com/JDimproved/JDim/pull/2)) - Always include crypt.h header for crypt function ([#1](https://github.com/JDimproved/JDim/pull/1)) jdim-0.7.0/docs/manual/2019.md000066400000000000000000000251001417047150700155670ustar00rootroot00000000000000--- title: 更新履歴(2019年) layout: default --- > [Top](../) > [更新履歴]({{ site.baseurl }}/history/) > {{ page.title }} ## {{ page.title }} ### [0.2.0-20191027](https://github.com/JDimproved/JDim/compare/JDim-v0.2.0...362b797d53f) (2019-10-27) - Remove deprecated cpu optimization options ([#140](https://github.com/JDimproved/JDim/pull/140)) - Fix scrollbar behaviors for thread view on GTK3 ([#136](https://github.com/JDimproved/JDim/pull/136)) - Set ellipsize to dialog info label ([#135](https://github.com/JDimproved/JDim/pull/135)) - Use Gtk::FontChooserDialog instead of Gtk::FileSelectionDialog on GTK3 ([#134](https://github.com/JDimproved/JDim/pull/134)) - Improve displaying tooltip for bbs list/thread list ([#133](https://github.com/JDimproved/JDim/pull/133)) - Improve displaying manual on github.com ([#132](https://github.com/JDimproved/JDim/pull/132)) - Fix GTKMM_CHECK_VERSION macro ([#131](https://github.com/JDimproved/JDim/pull/131)) - Fix tooltips which are not near mouse pointer on bbs list/thread list for GTK3 ([#128](https://github.com/JDimproved/JDim/pull/128)) - Add github links for previous version manuals ([#130](https://github.com/JDimproved/JDim/pull/130)) - Revert "[WORKAROUND] Fix build error for manual" ([#129](https://github.com/JDimproved/JDim/pull/129)) - Use range-based for ([#126](https://github.com/JDimproved/JDim/pull/126)) - Refactor string operation ([#125](https://github.com/JDimproved/JDim/pull/125)) - Update requirements for dependencies (gtkmm >= 2.18) ([#124](https://github.com/JDimproved/JDim/pull/124)) - [WORKAROUND] Fix build error for manual ([#127](https://github.com/JDimproved/JDim/pull/127)) - Fix mouse button config to set Button5 correctly ([#123](https://github.com/JDimproved/JDim/pull/123)) - Implement tilt wheel for DragTreeView on GTK3 ([#122](https://github.com/JDimproved/JDim/pull/122)) - Fix bug which is applied ascii art font falsely on GTK3 ([#121](https://github.com/JDimproved/JDim/pull/121)) - Replace `gcry_md_hash_buffer` with `gnutls_hash_fast` ([#120](https://github.com/JDimproved/JDim/pull/120)) - Add a note of bug which is applied AA font falsely to thread view ([#119](https://github.com/JDimproved/JDim/pull/119)) - Add snapcraft.yaml ([#118](https://github.com/JDimproved/JDim/pull/118)) - Improve tab switch by mouse wheel for image view ([#117](https://github.com/JDimproved/JDim/pull/117)) - Implement tab switch by mouse wheel for GTK3 ([#114](https://github.com/JDimproved/JDim/pull/114)) - Add smooth scroll event to enable mouse wheel on GTK3 ([#113](https://github.com/JDimproved/JDim/pull/113)) - Fix configure args info to include "without" and "disable" options ([#112](https://github.com/JDimproved/JDim/pull/112)) - Fix #109: Remove cache root config providing compatible with old version ([#111](https://github.com/JDimproved/JDim/pull/111)) - Fix markdown format for the table of cache directory priority ([#115](https://github.com/JDimproved/JDim/pull/115)) - Fix #104: Implement XDG Base Directory support for cache ([#108](https://github.com/JDimproved/JDim/pull/108)) - Fix #103: Add jdim.metainfo.xml ([#107](https://github.com/JDimproved/JDim/pull/107)) - Fix #102: Update application icon installation ([#106](https://github.com/JDimproved/JDim/pull/106)) - Fix #101 Remove subversion support ([#105](https://github.com/JDimproved/JDim/pull/105)) - add CPPFLAGS, CXXFLAGS and LDFLAGS for test ([#100](https://github.com/JDimproved/JDim/pull/100)) - Fix #98: Add travis-ci.com configuration ([#99](https://github.com/JDimproved/JDim/pull/99)) ### [**JDim-v0.2.0** Release](https://github.com/JDimproved/JDim/releases/tag/JDim-v0.2.0) (2019-07-20) 主な変更点 - GTK3版をビルドするオプションを追加した(デフォルトはGTK2版) - 最大表示可能レス数を固定(11000)から変更可能にした - スレタイ検索のデフォルト設定を変更した(ff5ch.syoboi.jp) - 動作環境の表示にTLSライブラリを追加した - 旧設定ファイル(version < 1.9.5)のサポートを削除した ### [0.2.0-20190720](https://github.com/JDimproved/JDim/compare/79e90c8b2d...JDim-v0.2.0) (2019-07-20) - Release 0.2.0 ([#97](https://github.com/JDimproved/JDim/pull/97)) - Minor fixes ([#96](https://github.com/JDimproved/JDim/pull/96)) - Fix #90: Implement ENVIRONMENT::get_tlslib_version ([#94](https://github.com/JDimproved/JDim/pull/94)) - Tweak UI and configuration ([#93](https://github.com/JDimproved/JDim/pull/93)) - Add const qualifier to member functions ([#92](https://github.com/JDimproved/JDim/pull/92)) - Refactor SKELETON namespace ([#89](https://github.com/JDimproved/JDim/pull/89)) - Update constractor and destructor ([#88](https://github.com/JDimproved/JDim/pull/88)) - Improve ENVIRONMENT::get_wm() and ENVIRONMENT::get_wm_str() ([#87](https://github.com/JDimproved/JDim/pull/87)) - Fix test program build ([#85](https://github.com/JDimproved/JDim/pull/85)) - Fix #81: Add grouped configure option ([#84](https://github.com/JDimproved/JDim/pull/84)) - Fix #80: Deprecate configure option for CPU optimization ([#83](https://github.com/JDimproved/JDim/pull/83)) - Fix #79: Remove support for old version config files ([#82](https://github.com/JDimproved/JDim/pull/82)) - [DRAFT] Fix #45: Add unittest (Google Test) ([#78](https://github.com/JDimproved/JDim/pull/78)) - README.mdの整理 ([#77](https://github.com/JDimproved/JDim/pull/77)) - Update manual to remove login and simplify history ([#75](https://github.com/JDimproved/JDim/pull/75)) - Fix to save thread list column width for GTK3 ([#74](https://github.com/JDimproved/JDim/pull/74)) - Fix manual link (a.k.a. Create online manual [2/2]) ([#72](https://github.com/JDimproved/JDim/pull/72)) - Create online manual [1/2] ([#69](https://github.com/JDimproved/JDim/pull/69)) ### [0.1.0-20190430](https://github.com/JDimproved/JDim/compare/JDim-v0.1.0...79e90c8b2d) (2019-04-30) - Fix #71: Deprecate gtkmm version less than 2.18 ([#73](https://github.com/JDimproved/JDim/pull/73)) - Fix check button for usrcmdpref ([#70](https://github.com/JDimproved/JDim/pull/70)) - Fix \*BSD compile error ([#68](https://github.com/JDimproved/JDim/pull/68)) - Fix #60: Change default thread title search site (ff5ch.syoboi.jp) ([#65](https://github.com/JDimproved/JDim/pull/65)) - Tweak config dialog layout for GTK+3 ([#64](https://github.com/JDimproved/JDim/pull/64)) - Use range-based for instead of MISC::count_chr() ([#63](https://github.com/JDimproved/JDim/pull/63)) - Fix #53: 長いスレッド(>11000レス?)を開くとクラッシュする不具合の修正 ([#62](https://github.com/JDimproved/JDim/pull/62)) - Use std::unordered_set instead of std::vector for res information ([#61](https://github.com/JDimproved/JDim/pull/61)) - Fix released-event misfire for GtkGestureMultiPress ([#58](https://github.com/JDimproved/JDim/pull/58)) - Update function parameters for MISC::asc() ([#57](https://github.com/JDimproved/JDim/pull/57)) - Sanitize numeric character reference ([#56](https://github.com/JDimproved/JDim/pull/56)) - Fix #40: Make --with-gthread deprecated ([#55](https://github.com/JDimproved/JDim/pull/55)) - Add downloading dat from old URL ([#54](https://github.com/JDimproved/JDim/pull/54)) - Fix runtime warning for opening a thread since GTK+ 3.20 ([#52](https://github.com/JDimproved/JDim/pull/52)) - Fix oniguruma detection for configure script ([#51](https://github.com/JDimproved/JDim/pull/51)) - font.cpp内のmallocをnewに置き換える ([#49](https://github.com/JDimproved/JDim/pull/49)) - Fix #46: キーワード抽出を行うとクラッシュする不具合の修正 ([#48](https://github.com/JDimproved/JDim/pull/48)) - Avoid crash on extraction ([#47](https://github.com/JDimproved/JDim/pull/47)) - Add MATE and Cinnamon to ENVIRONMENT::get_wm() ([#44](https://github.com/JDimproved/JDim/pull/44)) - Replace JDLIB::hash_set_thread with std::unordered_set ([#43](https://github.com/JDimproved/JDim/pull/43)) - Fix variable type for assigning from std::string::find() ([#42](https://github.com/JDimproved/JDim/pull/42)) - Add touchscreen support (a.k.a. Add gtk3 support [2/2]) ([#39](https://github.com/JDimproved/JDim/pull/39)) - Tweak JDLIB::JDSSL::connect() ([#38](https://github.com/JDimproved/JDim/pull/38)) - Set ellipsize to window status label since GTK+ 2.6 ([#37](https://github.com/JDimproved/JDim/pull/37)) - Create CONTRIBUTING.md ([#36](https://github.com/JDimproved/JDim/pull/36)) - Fix gnutls reception ([#35](https://github.com/JDimproved/JDim/pull/35)) - Fix return types ([#34](https://github.com/JDimproved/JDim/pull/34)) - Debianでの開発環境について更新 ([#33](https://github.com/JDimproved/JDim/pull/33)) - Fix JBBS board URL for setting board info ([#31](https://github.com/JDimproved/JDim/pull/31)) - Add gtk3 support [1/2] ([#30](https://github.com/JDimproved/JDim/pull/30)) ### [**JDim-v0.1.0** Release](https://github.com/JDimproved/JDim/releases/tag/JDim-v0.1.0) (2019-01-31) ### [0.1.0-20190129](https://github.com/JDimproved/JDim/compare/f6390b7f97...JDim-v0.1.0) (2019-01-29) - Fix progname and docs ([#29](https://github.com/JDimproved/JDim/pull/29)) - Merge test branch into master ([#28](https://github.com/JDimproved/JDim/pull/28)) - Fix undefined behavior in JDLIB::hash_set_thread::get_key ([#27](https://github.com/JDimproved/JDim/pull/27)) - Add progname info ([#26](https://github.com/JDimproved/JDim/pull/26)) - Fix enabling digitlink in NodeTreeBase::parse_name ([#25](https://github.com/JDimproved/JDim/pull/25)) ### [0.1.0-20190123](https://github.com/JDimproved/JDim/compare/bb608f24b1...f6390b7f97) (2019-01-23) JDimへの名称変更に合わせてバージョン番号のリセットを行った。 - Update project ([#24](https://github.com/JDimproved/JDim/pull/24)) - Use -Wextra option ([#23](https://github.com/JDimproved/JDim/pull/23)) - Use override keyword ([#22](https://github.com/JDimproved/JDim/pull/22)) - Use compiler option "-pedantic" instead of "-pedantic-errors" ([#21](https://github.com/JDimproved/JDim/pull/21)) - Use flag pedantic errors ([#20](https://github.com/JDimproved/JDim/pull/20)) - Replace container type ([#19](https://github.com/JDimproved/JDim/pull/19)) - Fix compile warning ([#18](https://github.com/JDimproved/JDim/pull/18)) - Fix access violation in DBTREE::NodeTreeBase::receive_data() ([#17](https://github.com/JDimproved/JDim/pull/17)) jdim-0.7.0/docs/manual/2020.md000066400000000000000000001013771417047150700155720ustar00rootroot00000000000000--- title: 更新履歴(2020年) layout: default --- > [Top](../) > [更新履歴]({{ site.baseurl }}/history/) > {{ page.title }} ## {{ page.title }} ### [0.4.0-20201003](https://github.com/JDimproved/JDim/compare/JDim-v0.4.0...1fee8b4327) (2020-10-03) - Fix posting to JD support BBS ([#478](https://github.com/JDimproved/JDim/pull/478)) - Use `Gtk::IconTheme` instead of `Gtk::Widget::render_icon_pixbuf()` ([#477](https://github.com/JDimproved/JDim/pull/477)) - Use `Gtk::ColorChooserDialog` instead of `Gtk::ColorSelectionDialog` ([#476](https://github.com/JDimproved/JDim/pull/476)) - Use `GIConv` instead of the underlying iconv implementation ([#475](https://github.com/JDimproved/JDim/pull/475)) - `DrawAreaBase`: Remove deprecated `Gtk::Widget::signal_visibility_notify_event()` ([#473](https://github.com/JDimproved/JDim/pull/473)) - setupwizard: Use Gtk::Grid instead of `Gtk::Table` ([#472](https://github.com/JDimproved/JDim/pull/472)) - Remove unused macros for windows support ([#468](https://github.com/JDimproved/JDim/pull/468)) - Use `Gtk::Dialog::get_content_area()` instead of `get_vbox()` part3 ([#467](https://github.com/JDimproved/JDim/pull/467)) - Use `Gtk::Dialog::get_content_area()` instead of `get_vbox()` part2 ([#466](https://github.com/JDimproved/JDim/pull/466)) - Use `Gtk::Dialog::get_content_area()` instead of `get_vbox()` part1 ([#465](https://github.com/JDimproved/JDim/pull/465)) - Remove Windows support ([#464](https://github.com/JDimproved/JDim/pull/464)) - Fix posting to 2ch ([#463](https://github.com/JDimproved/JDim/pull/463)) - Use `Gtk::Widget::set_[hv]align()` instead of `Gtk::Misc::set_alignment()` ([#462](https://github.com/JDimproved/JDim/pull/462)) - Use `Gtk::Label::set_xalign()` instead of `Gtk::Misc::set_alignment()` ([#461](https://github.com/JDimproved/JDim/pull/461)) - Use `Gtk::Widget::property_margin()` instead of `Gtk::Misc::set_padding()` ([#460](https://github.com/JDimproved/JDim/pull/460)) - `Board2chCompati`: Cut html source for analyzing form data ([#459](https://github.com/JDimproved/JDim/pull/459)) - `Board2ch`: Fix clearing keyword for new article ([#458](https://github.com/JDimproved/JDim/pull/458)) - Get rid of snap build badge from README.md ([#457](https://github.com/JDimproved/JDim/pull/457)) - Update histories ([#456](https://github.com/JDimproved/JDim/pull/456)) - Use `Gtk::Paned` instead of `Gtk::VPaned` ([#455](https://github.com/JDimproved/JDim/pull/455)) - Use `Gtk::Paned` instead of `Gtk::HPaned` ([#454](https://github.com/JDimproved/JDim/pull/454)) - Use `Gtk::GestureMultiPress` instead of `Gtk::Button::signal_pressed/released` ([#453](https://github.com/JDimproved/JDim/pull/453)) - Use icon theme instead of `Gtk::Arrow` ([#452](https://github.com/JDimproved/JDim/pull/452)) - Change default regex library to Glib Regex ([#451](https://github.com/JDimproved/JDim/pull/451)) - Deprecate gtkmm version less than 3.22 ([#450](https://github.com/JDimproved/JDim/pull/450)) - Use cursor name instead of `Gdk::CursorType` ([#449](https://github.com/JDimproved/JDim/pull/449)) - Use `Gdk::RGBA` instead of `Gdk::Color` ([#448](https://github.com/JDimproved/JDim/pull/448)) - Use `Gtk::Scrollbar` instead of `Gtk::VScrollbar` ([#447](https://github.com/JDimproved/JDim/pull/447)) - message: Add missing headers for Makefile.am ([#446](https://github.com/JDimproved/JDim/pull/446)) - `DrawAreaBase`: Remove compile condition for touchscreen ([#442](https://github.com/JDimproved/JDim/pull/442)) - `DrawAreaBase`: Remove compile conditions for drawing thread view ([#441](https://github.com/JDimproved/JDim/pull/441)) - `DrawAreaBase`: Remove double bufferd processing for gtk < 3.9.2 ([#440](https://github.com/JDimproved/JDim/pull/440)) - `EditTextView`: Remove compile condition for word/line selection ([#439](https://github.com/JDimproved/JDim/pull/439)) - `Core`: Remove wrapper class which is no longer needed ([#438](https://github.com/JDimproved/JDim/pull/438)) - `ICON_Manager`: Remove compile condition for rendering icon ([#437](https://github.com/JDimproved/JDim/pull/437)) - Remove compile conditions for style setting ([#436](https://github.com/JDimproved/JDim/pull/436)) - Improve compile conditions for `MISC::WarpPointer()` ([#435](https://github.com/JDimproved/JDim/pull/435)) - Remove compile conditions for value getting/setting part5 ([#434](https://github.com/JDimproved/JDim/pull/434)) - Remove compile conditions for value getting/setting part4 ([#433](https://github.com/JDimproved/JDim/pull/433)) - Remove compile conditions for value getting/setting part3 ([#432](https://github.com/JDimproved/JDim/pull/432)) - Remove compile conditions for value getting/setting part2 ([#431](https://github.com/JDimproved/JDim/pull/431)) - Remove compile conditions for value getting/setting part1 ([#430](https://github.com/JDimproved/JDim/pull/430)) - prefdiag: Remove compile conditions for adding member function ([#429](https://github.com/JDimproved/JDim/pull/429)) - `DelImgCacheDiag`: Remove compile conditions for member function ([#428](https://github.com/JDimproved/JDim/pull/428)) - control: Remove compile conditions for iterating elements ([#427](https://github.com/JDimproved/JDim/pull/427)) - cache: Remove compile conditions for local variables ([#426](https://github.com/JDimproved/JDim/pull/426)) - Remove compile conditions for supporting smooth scroll ([#425](https://github.com/JDimproved/JDim/pull/425)) - `ViewNotebook`: Remove member functions which are no longer in use ([#424](https://github.com/JDimproved/JDim/pull/424)) - `ToolBarNotebook`: Remove member function which is no longer in use ([#423](https://github.com/JDimproved/JDim/pull/423)) - Add note in the case of running on old MATE desktop ([#422](https://github.com/JDimproved/JDim/pull/422)) - Add note for configure option `--with-pangolayout` ([#421](https://github.com/JDimproved/JDim/pull/421)) - `TabSwtichButton`: Remove member function which are no longer in use ([#420](https://github.com/JDimproved/JDim/pull/420)) - `TabNotebook`: Remove member functions which are no longer in use ([#419](https://github.com/JDimproved/JDim/pull/419)) - `PopupWinBase`: Remove member functions which are no longer in use ([#418](https://github.com/JDimproved/JDim/pull/418)) - `JDToolbar`: Remove member function which is no longer in use ([#417](https://github.com/JDimproved/JDim/pull/417)) - `DragableNoteBook`: Remove member functions which are no longer in use ([#416](https://github.com/JDimproved/JDim/pull/416)) - `Core`: Remove member functions which are no longer in use ([#415](https://github.com/JDimproved/JDim/pull/415)) - Replace `GtkNotebookPage*` with `Gtk::Widget*` ([#414](https://github.com/JDimproved/JDim/pull/414)) - `CookieManager`: Accept empty value Cookie ([#413](https://github.com/JDimproved/JDim/pull/413)) - Remove `JDLIB::Thread` implementations for pthread and gthread ([#411](https://github.com/JDimproved/JDim/pull/411)) - Change buildinfo header generation to do every time ([#410](https://github.com/JDimproved/JDim/pull/410)) - Update version requirement for GnuTLS (>= 3.4.10) ([#409](https://github.com/JDimproved/JDim/pull/409)) - Remove deprecated configure option `--with-regex=pcre` ([#408](https://github.com/JDimproved/JDim/pull/408)) - Remove deprecated configure option `--with-xdgopen` ([#407](https://github.com/JDimproved/JDim/pull/407)) - Remove configure option `--with-gtkmm3` ([#406](https://github.com/JDimproved/JDim/pull/406)) - Remove deprecated configure options for thread library ([#405](https://github.com/JDimproved/JDim/pull/405)) - Remove deprecated configure options `--with-[oniguruma|pcre]` ([#404](https://github.com/JDimproved/JDim/pull/404)) - Remove deprecated configure option `--with-openssl` ([#403](https://github.com/JDimproved/JDim/pull/403)) ### [**JDim-v0.4.0** Release](https://github.com/JDimproved/JDim/releases/tag/JDim-v0.4.0) (2020-07-18) 主な変更点 - GTK2版は廃止されGTK3版がデフォルトになる - スレビューのメール欄フォント設定を追加した - 高参照レス抽出(赤レス抽出)を追加した - 掲示板サイトのhttps化に対する対応を進めた - 正規表現ライブラリGlib Regexのサポートを追加した - Python製のビルドツール [meson](https://mesonbuild.com) のサポートを追加した(実験的な機能) ### [0.4.0-20200718](https://github.com/JDimproved/JDim/compare/e6e4e5bc37...JDim-v0.4.0) (2020-07-18) - Release 0.4.0 ([#402](https://github.com/JDimproved/JDim/pull/402)) - Add more specific description for building by meson ([#401](https://github.com/JDimproved/JDim/pull/401)) - `BBSListViewBase`: Use const reference to make alias for local variable ([#400](https://github.com/JDimproved/JDim/pull/400)) - Replace `snprintf` with string format function ([#399](https://github.com/JDimproved/JDim/pull/399)) - `ArticleBase`: Fix integer overflow ([#398](https://github.com/JDimproved/JDim/pull/398)) - Snap: Fix wrong keyword "runs-on" to "run-on" for snapcraft.yaml ([#397](https://github.com/JDimproved/JDim/pull/397)) - Snap: Fix architectures settings ([#396](https://github.com/JDimproved/JDim/pull/396)) - Snap: Drop architectures i386, ppc64el and s390x ([#395](https://github.com/JDimproved/JDim/pull/395)) - Snap: Update dependencies to use GNOME 3.34 snapcraft extension ([#394](https://github.com/JDimproved/JDim/pull/394)) - Tweak meson.build ([#393](https://github.com/JDimproved/JDim/pull/393)) - `JDSSL`: Fix dead assignment ([#392](https://github.com/JDimproved/JDim/pull/392)) - `environment`: Move local variable to inner scope ([#391](https://github.com/JDimproved/JDim/pull/391)) - `post`: Fix compiler waring for `-Wnon-virtual-dtor` ([#390](https://github.com/JDimproved/JDim/pull/390)) - `EditTextView`: Fix bounds check ([#389](https://github.com/JDimproved/JDim/pull/389)) - Add meson build for experimental support ([#388](https://github.com/JDimproved/JDim/pull/388)) - `DrawAreaBase`: Add null check before dereferencing pointer ([#387](https://github.com/JDimproved/JDim/pull/387)) - `DrawAreaBase`: Fix dead increment ([#386](https://github.com/JDimproved/JDim/pull/386)) - `Loader`: Fix dead assignment ([#385](https://github.com/JDimproved/JDim/pull/385)) - `ImageAdmin`: Add null check before dereferencing pointer ([#384](https://github.com/JDimproved/JDim/pull/384)) - `MessageViewBase`: Remove redundant null check ([#383](https://github.com/JDimproved/JDim/pull/383)) - `JDWinMain`: Unite if-statement blocks for same condition ([#382](https://github.com/JDimproved/JDim/pull/382)) - `JDWinMain`: Fix calling virtual function from the ctor and dtor ([#381](https://github.com/JDimproved/JDim/pull/381)) - `EditTextView`: Fix redundant initialization ([#380](https://github.com/JDimproved/JDim/pull/380)) - Revert "Update snapcraft.yaml to bundle the gnome-3-34 extension" ([#379](https://github.com/JDimproved/JDim/pull/379)) - Update snapcraft.yaml to bundle the gnome-3-34 extension ([#378](https://github.com/JDimproved/JDim/pull/378)) - `Loader`: Fix dead assignment ([#377](https://github.com/JDimproved/JDim/pull/377)) - `Core`: Fix member initialization ([#376](https://github.com/JDimproved/JDim/pull/376)) - `Core`: Add const qualifier to function parameters ([#375](https://github.com/JDimproved/JDim/pull/375)) - Rename local variables to avoid shadowing ([#374](https://github.com/JDimproved/JDim/pull/374)) - Add explicit keyword to constructors which have one argument ([#373](https://github.com/JDimproved/JDim/pull/373)) - Root: Use `std::find_if()` instead of iterator loop ([#372](https://github.com/JDimproved/JDim/pull/372)) - `mousekeypref`: Modify loop statements ([#371](https://github.com/JDimproved/JDim/pull/371)) - Remove local variable to avoid unread value ([#370](https://github.com/JDimproved/JDim/pull/370)) - Move local variables to inner scope to avoid unused value ([#369](https://github.com/JDimproved/JDim/pull/369)) - Add void cast for unread variables ([#368](https://github.com/JDimproved/JDim/pull/368)) - Bump version to 0.4.0-beta ([#367](https://github.com/JDimproved/JDim/pull/367)) - Fix make rule to not recompile all test codes ([#365](https://github.com/JDimproved/JDim/pull/365)) - Add description for migemo build option ([#366](https://github.com/JDimproved/JDim/pull/366)) - Remove preprocessor for zlib version check ([#364](https://github.com/JDimproved/JDim/pull/364)) - Deprecate configure option for PCRE ([#363](https://github.com/JDimproved/JDim/pull/363)) - Add configure option for Glib Regex ([#362](https://github.com/JDimproved/JDim/pull/362)) - `MouseKeyDiag`: Use range-based for statements to avoid shadowing ([#360](https://github.com/JDimproved/JDim/pull/360)) - `Dom`: Move local variable to inner scope ([#359](https://github.com/JDimproved/JDim/pull/359)) - `JDTreeViewBase`: Move local variable to inner scope ([#358](https://github.com/JDimproved/JDim/pull/358)) - misc: Move local variable to inner scope ([#357](https://github.com/JDimproved/JDim/pull/357)) - `NodeTreeBase`: Move local variable to inner scope ([#356](https://github.com/JDimproved/JDim/pull/356)) - `ImgLoader`: Move local variable to inner scope ([#355](https://github.com/JDimproved/JDim/pull/355)) - `ImageAdmin`: Move local variable to inner scope ([#354](https://github.com/JDimproved/JDim/pull/354)) - `Root`: Modify local variable to avoid shadowing ([#353](https://github.com/JDimproved/JDim/pull/353)) - `DrawAreaBase`: Move local variable to inner scope ([#352](https://github.com/JDimproved/JDim/pull/352)) - `ImageViewBase`: Move local variables to inner scope to avoid shadowing ([#351](https://github.com/JDimproved/JDim/pull/351)) - Remove local var declaration to avoid shadowing ([#350](https://github.com/JDimproved/JDim/pull/350)) - Rename local variable to avoid shadowing ([#349](https://github.com/JDimproved/JDim/pull/349)) - Modify default bbsmenu URL to https ([#348](https://github.com/JDimproved/JDim/pull/348)) - Support processing for moving bbsmenu by 301 Moved Permanently ([#347](https://github.com/JDimproved/JDim/pull/347)) - Support processing for moving board by 301 Moved Permanently ([#346](https://github.com/JDimproved/JDim/pull/346)) - `Root`: Change parameters to pass by value ([#345](https://github.com/JDimproved/JDim/pull/345)) - Modify parameter type from reference to value ([#344](https://github.com/JDimproved/JDim/pull/344)) - Add const qualifier to function parameters ([#343](https://github.com/JDimproved/JDim/pull/343)) - Unite if-statement blocks for same condition ([#342](https://github.com/JDimproved/JDim/pull/342)) - Improve https hack for proxy connection ([#341](https://github.com/JDimproved/JDim/pull/341)) - Apply proxy settings to loading bbslist ([#340](https://github.com/JDimproved/JDim/pull/340)) - Remove unused local variables ([#339](https://github.com/JDimproved/JDim/pull/339)) - Remove unnecessary condition for assigning value ([#338](https://github.com/JDimproved/JDim/pull/338)) - Fix re-assign value before the old one is used ([#337](https://github.com/JDimproved/JDim/pull/337)) - Remove invariable condition expression ([#336](https://github.com/JDimproved/JDim/pull/336)) - Use STL Algorithm for raw loops ([#335](https://github.com/JDimproved/JDim/pull/335)) - `DrawAreaBase`: Use standard library math functions ([#334](https://github.com/JDimproved/JDim/pull/334)) - Deprecate version less then 3.18 for gtkmm ([#333](https://github.com/JDimproved/JDim/pull/333)) - `IOMonitor`: Add compile condition to private member function ([#331](https://github.com/JDimproved/JDim/pull/331)) - `Dom`: Fix comparison condition for unsigned integer ([#330](https://github.com/JDimproved/JDim/pull/330)) - Rename local variable names to avoid shadowing ([#329](https://github.com/JDimproved/JDim/pull/329)) - misc: Remove redundant condition for NUL character check ([#328](https://github.com/JDimproved/JDim/pull/328)) - `JDEntry`: Use member initializer ([#327](https://github.com/JDimproved/JDim/pull/327)) - image: Replace printf format for image size with `std::to_string()` ([#326](https://github.com/JDimproved/JDim/pull/326)) - `Iconv`: Fix printf format for print debug ([#325](https://github.com/JDimproved/JDim/pull/325)) - Update HTTP request for new article creation ([#324](https://github.com/JDimproved/JDim/pull/324)) - Improve analyzation of keywords for writing and newarticle ([#323](https://github.com/JDimproved/JDim/pull/323)) - main: Fix C-style pointer casting ([#322](https://github.com/JDimproved/JDim/pull/322)) - `DrawAreaBase`: Fix bitwise OR operation to logical OR for if condition ([#321](https://github.com/JDimproved/JDim/pull/321)) - Remove unnecessary using declaration for member function ([#320](https://github.com/JDimproved/JDim/pull/320)) - Separate cookie generation for posting ([#319](https://github.com/JDimproved/JDim/pull/319)) - Rename cookie functions ([#318](https://github.com/JDimproved/JDim/pull/318)) - Fix thread title search for intial setting (2020-06) ([#316](https://github.com/JDimproved/JDim/pull/316)) - Fix function parameter to const reference ([#312](https://github.com/JDimproved/JDim/pull/312)) - Fix constructor parameter to const reference ([#311](https://github.com/JDimproved/JDim/pull/311)) - Fix to use member initializer lists ([#310](https://github.com/JDimproved/JDim/pull/310)) - Fix std::set insertation to avoid double searching ([#309](https://github.com/JDimproved/JDim/pull/309)) - `ImgProvider`: Fix iterator loop to use `std::find_if()` ([#308](https://github.com/JDimproved/JDim/pull/308)) - Fix array bounds checking ([#307](https://github.com/JDimproved/JDim/pull/307)) - Update documents ([#306](https://github.com/JDimproved/JDim/pull/306)) - `Dom`: Fix accessing first character for `std::string` ([#305](https://github.com/JDimproved/JDim/pull/305)) - `ToolMenuButton`: Fix mutable member function in assert macro ([#304](https://github.com/JDimproved/JDim/pull/304)) - `Admin`: Fix the class to be compliant with rule of three ([#303](https://github.com/JDimproved/JDim/pull/303)) - Implement `SimpleCookieManager` ([#302](https://github.com/JDimproved/JDim/pull/302)) - Fix loading local dat file specified by command line arguments ([#301](https://github.com/JDimproved/JDim/pull/301)) - Fix displaying tab icon for extraction tab ([#300](https://github.com/JDimproved/JDim/pull/300)) - Fix mouse wheel scrolling for thread list ([#299](https://github.com/JDimproved/JDim/pull/299)) - Fix unexpected jump by mouse over url ([#298](https://github.com/JDimproved/JDim/pull/298)) - Fix unmatched paren for `MISC::getenv_limited()` ([#293](https://github.com/JDimproved/JDim/pull/293)) - `MessageAdmin`: Fix if-statement which is always true ([#292](https://github.com/JDimproved/JDim/pull/292)) - Fix dangling pointer for logging file ([#291](https://github.com/JDimproved/JDim/pull/291)) - Fix ignored return value for `MISC::get_hostname()` ([#290](https://github.com/JDimproved/JDim/pull/290)) - Replace char buffer with `std::string` for `DBTREE::Board` classes ([#289](https://github.com/JDimproved/JDim/pull/289)) - `JDWindow`: Fix calling virtual function from the ctor and dtor ([#288](https://github.com/JDimproved/JDim/pull/288)) - `JDWindow`: Fix calling virtual function from the destructor ([#287](https://github.com/JDimproved/JDim/pull/287)) - `Admin`: Fix calling virtual function from the destructor ([#286](https://github.com/JDimproved/JDim/pull/286)) - `RuleLoader`: Fix calling virtual function from the ctor and dtor ([#285](https://github.com/JDimproved/JDim/pull/285)) - `ArticleBase`: Fix calling virtual function from the destructor ([#284](https://github.com/JDimproved/JDim/pull/284)) - `ArticleViewMain`: Fix calling virtual function from the destructor ([#283](https://github.com/JDimproved/JDim/pull/283)) - `View`: Fix calling virtual function from the destructor ([#282](https://github.com/JDimproved/JDim/pull/282)) - `ToolBar`: Fix calling virtual function from the constructor ([#281](https://github.com/JDimproved/JDim/pull/281)) - `ArticleViewSearch`: Fix calling virtual function from the destructor ([#280](https://github.com/JDimproved/JDim/pull/280)) - `NodeTreeBase`: Fix calling virtual function from the destructor ([#279](https://github.com/JDimproved/JDim/pull/279)) - `ViewHistory`: Fix `ArticleBase::get_current_url()` to const member ([#278](https://github.com/JDimproved/JDim/pull/278)) - Fix memory leak for `JDLIB::Timeout::connect()` ([#277](https://github.com/JDimproved/JDim/pull/277)) - Fix thread title search for the initial setting ([#275](https://github.com/JDimproved/JDim/pull/275)) - `ArticleBase`: Fix `ArticleBase::empty()` to const member ([#274](https://github.com/JDimproved/JDim/pull/274)) - `ArticleHash`: Fix the class to be compliant with rule of three ([#273](https://github.com/JDimproved/JDim/pull/273)) - `DrawAreaBase`: Fix redundant condition ([#272](https://github.com/JDimproved/JDim/pull/272)) - `ConfigItems`: Fix member initialization ([#271](https://github.com/JDimproved/JDim/pull/271)) - `Iconv`: Fix member initialization ([#270](https://github.com/JDimproved/JDim/pull/270)) - `ArticleHash`: Fix member initialization ([#269](https://github.com/JDimproved/JDim/pull/269)) - Add known issues for Wayland to documents ([#268](https://github.com/JDimproved/JDim/pull/268)) - Remove p2.2ch.sc login ([#266](https://github.com/JDimproved/JDim/pull/266)) - Fix crash by click anchor for GTK3 on Wayland ([#265](https://github.com/JDimproved/JDim/pull/265)) - Deprecate `--with-xdgopen` ([#264](https://github.com/JDimproved/JDim/pull/264)) - `Search_Manager`: Fix member initialization ([#263](https://github.com/JDimproved/JDim/pull/263)) - `CheckUpdate_Manager`: Fix member initialization ([#262](https://github.com/JDimproved/JDim/pull/262)) - `JDWindow`: Fix member initialization ([#261](https://github.com/JDimproved/JDim/pull/261)) - `SKELETON::View`: Fix member initialization ([#260](https://github.com/JDimproved/JDim/pull/260)) - `Play_Sound`: Fix member initialization ([#259](https://github.com/JDimproved/JDim/pull/259)) - Split history sections ([#258](https://github.com/JDimproved/JDim/pull/258)) ### [0.3.0-20200426](https://github.com/JDimproved/JDim/compare/JDim-v0.3.0...e6e4e5bc37) (2020-04-26) - Update histories ([#255](https://github.com/JDimproved/JDim/pull/255)) - `TabLabel`: Fix member initialization ([#254](https://github.com/JDimproved/JDim/pull/254)) - Pre-notify updating recommended requirements to GTK 3.22+ ([#251](https://github.com/JDimproved/JDim/pull/251)) - Remove deprecated `g_mem_set_vtable()` ([#250](https://github.com/JDimproved/JDim/pull/250)) - Fix compiler warning for `MISC::recover_path()` ([#249](https://github.com/JDimproved/JDim/pull/249)) - `PaneControl`: Fix member initialization ([#248](https://github.com/JDimproved/JDim/pull/248)) - `ImgLoader`: Fix member initialization ([#247](https://github.com/JDimproved/JDim/pull/247)) - Add RFC repository link to documents ([#246](https://github.com/JDimproved/JDim/pull/246)) - Remove the description about external board machi bbs ([#245](https://github.com/JDimproved/JDim/pull/245)) - Deprecate gtk2 version ([#244](https://github.com/JDimproved/JDim/pull/244)) - Remove the option to fetch kako logs using offlaw2 ([#243](https://github.com/JDimproved/JDim/pull/243)) - `ImageAreaIcon`: Fix member initialization ([#241](https://github.com/JDimproved/JDim/pull/241)) - Refactor migemo functions ([#240](https://github.com/JDimproved/JDim/pull/240)) - `EditTreeView`: Fix member initialization ([#239](https://github.com/JDimproved/JDim/pull/239)) - `MsgDiag`: Fix member initialization ([#238](https://github.com/JDimproved/JDim/pull/238)) - Fix buffer overrun for `NodeTreeBase::parse_html()` ([#237](https://github.com/JDimproved/JDim/pull/237)) - Deprecate thread configure options except for std ([#236](https://github.com/JDimproved/JDim/pull/236)) - Fix checking sssp scheme ([#235](https://github.com/JDimproved/JDim/pull/235)) - Add const qualifier to `XML::Dom*` variables ([#234](https://github.com/JDimproved/JDim/pull/234)) - `ImageViewBase`: Fix member initialization ([#233](https://github.com/JDimproved/JDim/pull/233)) - Change button config name "ブックマーク" with "しおりの設定/解除" ([#232](https://github.com/JDimproved/JDim/pull/232)) - Add the toggle command for posted mark to mouse button config ([#231](https://github.com/JDimproved/JDim/pull/231)) - `ImageViewMain`: Fix member initialization ([#227](https://github.com/JDimproved/JDim/pull/227)) - Replace char buffer with `std::string` for `SKELETON::TextLoader` ([#226](https://github.com/JDimproved/JDim/pull/226)) - `XML::Dom`: Change behavior for `insertBefore()` ([#225](https://github.com/JDimproved/JDim/pull/225)) - Refactor `XML::Dom` part2 ([#224](https://github.com/JDimproved/JDim/pull/224)) - Replace char buffer with `std::string` for `CACHE::jdcopy()` ([#223](https://github.com/JDimproved/JDim/pull/223)) - `ArticleViewBase`: Fix member initialization ([#222](https://github.com/JDimproved/JDim/pull/222)) - Refactor `XML::Dom` ([#221](https://github.com/JDimproved/JDim/pull/221)) - Replace char buffer with `std::string` for `DBTREE::NodeTreeBase` [2/2] ([#220](https://github.com/JDimproved/JDim/pull/220)) - `ArticleViewMain`: Fix member initialization ([#219](https://github.com/JDimproved/JDim/pull/219)) - Fix thread view font configuration for GTK2 ([#218](https://github.com/JDimproved/JDim/pull/218)) - snap: Remove dbus slot from snapcraft.yaml ([#217](https://github.com/JDimproved/JDim/pull/217)) - `Iconv::convert`: handle emoji subdivision flags sequence ([#216](https://github.com/JDimproved/JDim/pull/216)) - Implement high reference extraction ([#215](https://github.com/JDimproved/JDim/pull/215)) - Replace char buffer with `std::string` for `DBTREE::NodeTreeBase` [1/2] ([#212](https://github.com/JDimproved/JDim/pull/212)) - Fix compiler warning -Wformat-truncation= for `MISC::timettostr()` ([#211](https://github.com/JDimproved/JDim/pull/211)) - Fix wordings for about:config favorite category to directory ([#213](https://github.com/JDimproved/JDim/pull/213)) - Replace `XMLDomList` with `std::list` ([#210](https://github.com/JDimproved/JDim/pull/210)) - `DrawAreaBase`: Fix member initialization ([#208](https://github.com/JDimproved/JDim/pull/208)) - Initialize some `bool` members for search mode ([#207](https://github.com/JDimproved/JDim/pull/207)) - Update manual ([#205](https://github.com/JDimproved/JDim/pull/205)) - `layout_one_text_node`: `reset br_size` after writing characters ([#206](https://github.com/JDimproved/JDim/pull/206)) - Reduce memory usage for font width caches ([#204](https://github.com/JDimproved/JDim/pull/204)) - Implement mouse gesture wheel for GTK3 ([#203](https://github.com/JDimproved/JDim/pull/203)) - Fix thread view popup window scrolling for GTK3 ([#202](https://github.com/JDimproved/JDim/pull/202)) - Add autoconf-archive to build requirement ([#201](https://github.com/JDimproved/JDim/pull/201)) - Fix out-of-range for `JDLIB::Regex::exec()` ([#199](https://github.com/JDimproved/JDim/pull/199)) - `MISC::asc`: Fix crash when searching title on Machi BBS ([#197](https://github.com/JDimproved/JDim/pull/197)) - Root: Fix parsing bbsmenu for 2ch.sc and next2ch.net ([#196](https://github.com/JDimproved/JDim/pull/196)) - Separate mail font ([#195](https://github.com/JDimproved/JDim/pull/195)) - Remove legacy gtk2 codes for less than version 2.24 ([#194](https://github.com/JDimproved/JDim/pull/194)) - Remove unused `NodeTreeBase::get_raw_res_str()` ([#193](https://github.com/JDimproved/JDim/pull/193)) - Improve `JDLIB::HEAP` to return pointer aligned by specified type ([#192](https://github.com/JDimproved/JDim/pull/192)) - Fix not setting result to the out parameter of `NodeTreeMachi::raw2dat()` ([#191](https://github.com/JDimproved/JDim/pull/191)) - Replace char buffer with `std::string` for `DBTREE::NodeTreeMachi` ([#190](https://github.com/JDimproved/JDim/pull/190)) - Remove zlib client codes for version < 1.2.0 ([#189](https://github.com/JDimproved/JDim/pull/189)) - Fix buffer overrun for `MISC::is_url_scheme_impl()` test ([#188](https://github.com/JDimproved/JDim/pull/188)) - Fix a `-Wstringop-overflow` compiler warning in `create_trip_newtype()` ([#187](https://github.com/JDimproved/JDim/pull/187)) - Replace char buffer with `std::string` for `DBTREE::NodeTreeJBBS` ([#186](https://github.com/JDimproved/JDim/pull/186)) - Fix number of bytes for `strncpy` argument of `Css_Manager::create_textnode()` ([#185](https://github.com/JDimproved/JDim/pull/185)) - `HEAP::heap_alloc()`: adjust alignment ([#184](https://github.com/JDimproved/JDim/pull/184)) - `CONTROL::get_keyconfig` use `snprintf` and set copying buffer size correctly ([#181](https://github.com/JDimproved/JDim/pull/181)) - Replace dynamic allocation with local variable for `struct utsname` ([#179](https://github.com/JDimproved/JDim/pull/179)) - Remove deprecated `--with-sessionlib=gnomeui` option ([#178](https://github.com/JDimproved/JDim/pull/178)) - Update requirements for dependencies (gtkmm >= 2.24) ([#177](https://github.com/JDimproved/JDim/pull/177)) - `MISC::remove_space`: handle the case str consists of just spaces ([#176](https://github.com/JDimproved/JDim/pull/176)) - Migrate snap package from legacy helper to gnome extension ([#174](https://github.com/JDimproved/JDim/pull/174)) - Set std::thread as default for configure script ([#173](https://github.com/JDimproved/JDim/pull/173)) - Set gtkmm3 as default for configure script ([#172](https://github.com/JDimproved/JDim/pull/172)) ### [**JDim-v0.3.0** Release](https://github.com/JDimproved/JDim/releases/tag/JDim-v0.3.0) (2020-01-18) 主な変更点 - GTK3版の安定性が向上した - freedesktop.org規格の対応を進めた - JDのキャッシュディレクトリ(`~/.jd`)を使わないようにするconfigureオプションを追加した - ロゴを更新した - Snapパッケージを公開した (GTK3版) - 古いCPUに合わせて最適化するconfigureオプションを削除した ### [0.3.0-20200118](https://github.com/JDimproved/JDim/compare/362b797d53f...JDim-v0.3.0) (2020-01-18) - Release 0.3.0 ([#169](https://github.com/JDimproved/JDim/pull/169)) - Refactor `ARTICLE::DrawAreaBase` ([#168](https://github.com/JDimproved/JDim/pull/168)) - Replace char buffer with `std::string` for `DBTREE::BoardBase` ([#167](https://github.com/JDimproved/JDim/pull/167)) - Update snapcraft grade to stable ([#165](https://github.com/JDimproved/JDim/pull/165)) - Replace char buffer with `std::string` for `MISC::Iconv()` ([#166](https://github.com/JDimproved/JDim/pull/166)) - Update year to 2020 ([#164](https://github.com/JDimproved/JDim/pull/164)) - Add a description for undocumented operation to the manual ([#163](https://github.com/JDimproved/JDim/pull/163)) - Replace char buffer with `std::string` part2 ([#162](https://github.com/JDimproved/JDim/pull/162)) - Unset 404 link on the GitHub Actions CI badge ([#161](https://github.com/JDimproved/JDim/pull/161)) - Add GitHub Actions configuration for CI ([#160](https://github.com/JDimproved/JDim/pull/160)) - Replace char buffer with `std::string` ([#159](https://github.com/JDimproved/JDim/pull/159)) - Implement MessageView word/line selection by mouse click for gtk3.16+ ([#158](https://github.com/JDimproved/JDim/pull/158)) - Fix logo color for "im" ([#156](https://github.com/JDimproved/JDim/pull/156)) - Remove legacy gtk2 codes for less than version 2.18 ([#155](https://github.com/JDimproved/JDim/pull/155)) - Fix icon background to transparency ([#154](https://github.com/JDimproved/JDim/pull/154)) - Fix deprecated warning for including `` ([#153](https://github.com/JDimproved/JDim/pull/153)) - Update logo ([#152](https://github.com/JDimproved/JDim/pull/152)) - Fix menu item labels for toolbar overflow menu ([#151](https://github.com/JDimproved/JDim/pull/151)) - Replace `NULL` and `0` for being assigned to pointer with `nullptr` ([#150](https://github.com/JDimproved/JDim/pull/150)) - Remove make rule which generates unused compiler info ([#149](https://github.com/JDimproved/JDim/pull/149)) - Use `std::to_string` instead of `MISC::itostr` ([#148](https://github.com/JDimproved/JDim/pull/148)) - Fix unable for initialization by `JDIM_CACHE` on compat mode ([#147](https://github.com/JDimproved/JDim/pull/147)) - Update history ([#143](https://github.com/JDimproved/JDim/pull/143)) - Deprecate `--with-sessionlib=gnomeui` option ([#142](https://github.com/JDimproved/JDim/pull/142)) - Deprecate gtkmm version less than 2.24 ([#141](https://github.com/JDimproved/JDim/pull/141)) jdim-0.7.0/docs/manual/2021.md000066400000000000000000001110411417047150700155600ustar00rootroot00000000000000--- title: 更新履歴(2021年) layout: default --- > [Top](../) > [更新履歴]({{ site.baseurl }}/history/) > {{ page.title }} ## {{ page.title }} ### [0.6.0-20211106](https://github.com/JDimproved/JDim/compare/JDim-v0.6.0...344da3bdc1) (2021-11-06) - Update histories ([#850](https://github.com/JDimproved/JDim/pull/850)) - Fix URL redirection for bbsmenu and board to handle 302 Found ([#849](https://github.com/JDimproved/JDim/pull/849)) - `LoginBe`: Update iterator for loop ([#847](https://github.com/JDimproved/JDim/pull/847)) - `LinkFilterPref`: Update iterator for loop ([#846](https://github.com/JDimproved/JDim/pull/846)) - tfidf: Update iterator for loop ([#845](https://github.com/JDimproved/JDim/pull/845)) - Improve named color support for `jd.css` ([#844](https://github.com/JDimproved/JDim/pull/844)) - `Loader`: Fix assignment in loop ([#843](https://github.com/JDimproved/JDim/pull/843)) - miscutil: Update iterator for loop ([#842](https://github.com/JDimproved/JDim/pull/842)) - miscgtk: Update iterator for loop ([#841](https://github.com/JDimproved/JDim/pull/841)) - `Loader`: Update iterator for loop ([#840](https://github.com/JDimproved/JDim/pull/840)) - Autotools: Update generation of test/Makefile ([#839](https://github.com/JDimproved/JDim/pull/839)) - `SettingLoader`: Set max res number by `BBS_THREAD_STOP` from SETTING.TXT ([#838](https://github.com/JDimproved/JDim/pull/838)) - `ImageViewBase`: Update iterator for loop ([#837](https://github.com/JDimproved/JDim/pull/837)) - `ICON_Manager`: Update iterator for loop ([#836](https://github.com/JDimproved/JDim/pull/836)) - `History_Manager`: Update iterator for loop ([#835](https://github.com/JDimproved/JDim/pull/835)) - `NodeTreeBase::parse_html` / `check_link_impl`: modify length check ([#834](https://github.com/JDimproved/JDim/pull/834)) - `GlobalAbonePref`: Update iterator for loop ([#832](https://github.com/JDimproved/JDim/pull/832)) - `FontColorPref`: Update iterator for loop ([#831](https://github.com/JDimproved/JDim/pull/831)) - environment: Update iterator for loop ([#830](https://github.com/JDimproved/JDim/pull/830)) - Add abone config for default name and no id ([#829](https://github.com/JDimproved/JDim/pull/829)) - `DispatchManager`: Update iterator for loop ([#828](https://github.com/JDimproved/JDim/pull/828)) - dbtree: Update iterator for loop part2 ([#827](https://github.com/JDimproved/JDim/pull/827)) - `Root`: Update iterator for loop part5 ([#826](https://github.com/JDimproved/JDim/pull/826)) - `Root`: Update iterator for loop part4 ([#825](https://github.com/JDimproved/JDim/pull/825)) - `Root`: Update iterator for loop part3 ([#824](https://github.com/JDimproved/JDim/pull/824)) - Add thread abone config for low number of res ([#823](https://github.com/JDimproved/JDim/pull/823)) - `Root`: Update iterator for loop part2 ([#822](https://github.com/JDimproved/JDim/pull/822)) - `Root`: Update iterator for loop part1 ([#821](https://github.com/JDimproved/JDim/pull/821)) - `BoardViewBase`: Improve update of thread speed ([#819](https://github.com/JDimproved/JDim/pull/819)) - `NodeTreeMachi`: Update iterator for loop ([#818](https://github.com/JDimproved/JDim/pull/818)) - `NodeTreeBase`: Update iterator for loop part3 ([#817](https://github.com/JDimproved/JDim/pull/817)) - meson: Change current directory to get build information ([#816](https://github.com/JDimproved/JDim/pull/816)) - `NodeTreeBase`: Update iterator for loop part2 ([#815](https://github.com/JDimproved/JDim/pull/815)) - `NodeTreeBase`: Update `NodeTreeBase::get_imglinks()` ([#814](https://github.com/JDimproved/JDim/pull/814)) - `Root`: Correct board URLs which are missing scheme ([#813](https://github.com/JDimproved/JDim/pull/813)) - `Root`: Update regex for URL ([#812](https://github.com/JDimproved/JDim/pull/812)) - `NodeTreeBase`: Update iterator for loop part1 ([#811](https://github.com/JDimproved/JDim/pull/811)) - `BoardBase`: Update iterator for loop ([#810](https://github.com/JDimproved/JDim/pull/810)) - `Admin`: Drop virtual function from `SKELETON::Admin::close_view(View*)` ([#809](https://github.com/JDimproved/JDim/pull/809)) - `Root`: Fix check for URL scheme ([#808](https://github.com/JDimproved/JDim/pull/808)) - Improve miscellaneous codes part1 ([#807](https://github.com/JDimproved/JDim/pull/807)) - dbtree: Update iterator for loop part1 ([#806](https://github.com/JDimproved/JDim/pull/806)) - `ArticleBase`: Update iterator for loop ([#805](https://github.com/JDimproved/JDim/pull/805)) - dbimg: Update iterator for loop ([#804](https://github.com/JDimproved/JDim/pull/804)) - `DelImgCacheDiag`: Update iterator for loop ([#803](https://github.com/JDimproved/JDim/pull/803)) - `Css_Manager`: Update iterator for loop ([#802](https://github.com/JDimproved/JDim/pull/802)) - `Core`: update iter for loop ([#801](https://github.com/JDimproved/JDim/pull/801)) - `MouseKeyPref`: Update iterator for loop ([#800](https://github.com/JDimproved/JDim/pull/800)) - `MouseKeyConf`: Update iterator for loop ([#799](https://github.com/JDimproved/JDim/pull/799)) - controlutil: Update iterator for loop ([#798](https://github.com/JDimproved/JDim/pull/798)) - `Control`: Update iterator for loop ([#797](https://github.com/JDimproved/JDim/pull/797)) - board: Update iterator for loop ([#796](https://github.com/JDimproved/JDim/pull/796)) - `Loader`: Fix HTTP header analyzing to ignoring case ([#795](https://github.com/JDimproved/JDim/pull/795)) - `BoardViewBase`: Update iterator for loop ([#794](https://github.com/JDimproved/JDim/pull/794)) - bbslist: Update iterator for loop ([#793](https://github.com/JDimproved/JDim/pull/793)) - `BBSListViewMain`: Update iterator for loop ([#792](https://github.com/JDimproved/JDim/pull/792)) - `ARTICLE::Preferences`: Update iterator for loop ([#790](https://github.com/JDimproved/JDim/pull/790)) - `LayoutTree`: Update iterator for loop ([#789](https://github.com/JDimproved/JDim/pull/789)) - font: Update iterator for loop ([#788](https://github.com/JDimproved/JDim/pull/788)) - `DrawAreaBase`: Update iterator for loop ([#787](https://github.com/JDimproved/JDim/pull/787)) - `ArticleViewSearch`: Update iterator for loop ([#786](https://github.com/JDimproved/JDim/pull/786)) - `ArticleViewBase`: Update iterator for loop ([#785](https://github.com/JDimproved/JDim/pull/785)) - `ArticleAdmin`: Update iterator for loop ([#784](https://github.com/JDimproved/JDim/pull/784)) - `AAManager`: Update iterator for loop ([#783](https://github.com/JDimproved/JDim/pull/783)) - Deprecate platforms where gcc version less than 7 ([#781](https://github.com/JDimproved/JDim/pull/781)) - Deprecate oniguruma regex option ([#780](https://github.com/JDimproved/JDim/pull/780)) - Remove deprecated command-line option `--norestore` ([#779](https://github.com/JDimproved/JDim/pull/779)) - Remove obsolete `--with-regex=posix` for ./configure ([#778](https://github.com/JDimproved/JDim/pull/778)) ### [**JDim-v0.6.0** Release](https://github.com/JDimproved/JDim/releases/tag/JDim-v0.6.0) (2021-07-10) 主な変更点 - ダイアログにClient-Side Decoration(CSD)のサポートを追加した - スレ一覧にソートの優先順を変更するショートカットキーを追加した (設定が必要) - 画像フォーマット _WebP_ と _AVIF_ に対応した (対応するローダーのインストールが必要) - 正規表現ライブラリPOSIX regex(`--with-regex=posix`)のサポートを廃止した ### [0.6.0-20210710](https://github.com/JDimproved/JDim/compare/948a10e9b44...JDim-v0.6.0) (2021-07-10) - Release 0.6.0 ([#775](https://github.com/JDimproved/JDim/pull/775)) - Deprecate command-line option `--norestore` ([#774](https://github.com/JDimproved/JDim/pull/774)) - Restore snapcraft configuration (2021-06) ([#773](https://github.com/JDimproved/JDim/pull/773)) - Set snapcraft config for i386 (2021-06) ([#771](https://github.com/JDimproved/JDim/pull/771)) - Optimize finding start string part11 ([#770](https://github.com/JDimproved/JDim/pull/770)) - skeleton: Optimize finding start string ([#769](https://github.com/JDimproved/JDim/pull/769)) - message: Optimize finding start string ([#768](https://github.com/JDimproved/JDim/pull/768)) - jdlib: Optimize finding start string ([#767](https://github.com/JDimproved/JDim/pull/767)) - Bump version to 0.6.0-beta ([#766](https://github.com/JDimproved/JDim/pull/766)) - message: Improve error messages for incomplete input ([#765](https://github.com/JDimproved/JDim/pull/765)) - environment: Optimize finding start string ([#764](https://github.com/JDimproved/JDim/pull/764)) - dbtree: Optimize finding start string ([#763](https://github.com/JDimproved/JDim/pull/763)) - `Css_Manager`: Optimize finding start string ([#762](https://github.com/JDimproved/JDim/pull/762)) - `Core`: Optimize finding start string ([#760](https://github.com/JDimproved/JDim/pull/760)) - Optimize finding start string part3 ([#759](https://github.com/JDimproved/JDim/pull/759)) - `DrawAreaBase`: Optimize finding start string ([#758](https://github.com/JDimproved/JDim/pull/758)) - `ArticleViewBase`: Optimize finding start string ([#757](https://github.com/JDimproved/JDim/pull/757)) - `AAManager`: Optimize finding start string ([#756](https://github.com/JDimproved/JDim/pull/756)) - `EditTextView`: Replace space conversion ` ` with ` ` ([#755](https://github.com/JDimproved/JDim/pull/755)) - Update documents ([#754](https://github.com/JDimproved/JDim/pull/754)) - Convert line separator to whitespace for drawing thread view tidily ([#753](https://github.com/JDimproved/JDim/pull/753)) - `Img`: Add error message for unsupported image with fake extension ([#752](https://github.com/JDimproved/JDim/pull/752)) - `Img`: Add image/webp, image/avif to Accept header for genuine URL if supported ([#751](https://github.com/JDimproved/JDim/pull/751)) - `MouseKeyConf`: Use member variables to backup instead of global vars ([#750](https://github.com/JDimproved/JDim/pull/750)) - `SKELETON::Toolbar`: Update close button flat style ([#747](https://github.com/JDimproved/JDim/pull/747)) - `Img`: Get rid of image/webp from Accept header for requesting image ([#746](https://github.com/JDimproved/JDim/pull/746)) - `ArticleBase`: Add noexcept qualifier to member function ([#745](https://github.com/JDimproved/JDim/pull/745)) - `ArticleBase`: Add const qualifier to member function part7 ([#744](https://github.com/JDimproved/JDim/pull/744)) - `ArticleBase`: Add const qualifier to member function part6 ([#743](https://github.com/JDimproved/JDim/pull/743)) - `ArticleBase`: Add const qualifier to member function part5 ([#742](https://github.com/JDimproved/JDim/pull/742)) - `ArticleBase`: Add const qualifier to member function part4 ([#741](https://github.com/JDimproved/JDim/pull/741)) - `ArticleBase`: Add const qualifier to member function part3 ([#740](https://github.com/JDimproved/JDim/pull/740)) - `ArticleBase`: Add const qualifier to member function part2 ([#739](https://github.com/JDimproved/JDim/pull/739)) - `ArticleBase`: Add const qualifier to member function part1 ([#738](https://github.com/JDimproved/JDim/pull/738)) - Add WebP and AVIF support ([#736](https://github.com/JDimproved/JDim/pull/736)) - `NodeTreeBase`: Add const qualifier to member function part5 ([#735](https://github.com/JDimproved/JDim/pull/735)) - `NodeTreeBase`: Add const qualifier to member function part4 ([#734](https://github.com/JDimproved/JDim/pull/734)) - `NodeTreeBase`: Add const qualifier to member function part3 ([#733](https://github.com/JDimproved/JDim/pull/733)) - `NodeTreeBase`: Add const qualifier to member function part2 ([#732](https://github.com/JDimproved/JDim/pull/732)) - `BBSListViewBase`: Add const qualifier to member function argument ([#731](https://github.com/JDimproved/JDim/pull/731)) - `NodeTreeBase`: Add const qualifier to member function part1 ([#730](https://github.com/JDimproved/JDim/pull/730)) - `NodeTreeBase`: Add static keyword to member function ([#729](https://github.com/JDimproved/JDim/pull/729)) - `NodeTreeBase`: Add delete declaration to unimplemented member function ([#728](https://github.com/JDimproved/JDim/pull/728)) - `NodeTreeBase`: Add noexcept qualifier to member function ([#726](https://github.com/JDimproved/JDim/pull/726)) - `Play_Sound`: Fix loading WAV file on big endian machine ([#725](https://github.com/JDimproved/JDim/pull/725)) - misctrip: Add configure check for thread-safe crypt_r ([#724](https://github.com/JDimproved/JDim/pull/724)) - image: Add const qualifier member function ([#723](https://github.com/JDimproved/JDim/pull/723)) - message: Add const qualifier member function ([#722](https://github.com/JDimproved/JDim/pull/722)) - `SKELETON::ToolBar`: Remove unused member function ([#721](https://github.com/JDimproved/JDim/pull/721)) - `TreeViewBase`: Add const qualifier to member function ([#720](https://github.com/JDimproved/JDim/pull/720)) - `PaneControl`: Add const qualifier to member function ([#719](https://github.com/JDimproved/JDim/pull/719)) - `DragableNoteBook`: Add const qualifier to member function ([#718](https://github.com/JDimproved/JDim/pull/718)) - `AboutDiag`: Add const qualifier to member function ([#717](https://github.com/JDimproved/JDim/pull/717)) - `MessageAdmin`: Fix unexpected mouse cursor on text selection ([#716](https://github.com/JDimproved/JDim/pull/716)) - skeleton: Add const qualifier member function part1 ([#715](https://github.com/JDimproved/JDim/pull/715)) - `Loader`: Get rid of DNT: 1 from HTTP request header ([#713](https://github.com/JDimproved/JDim/pull/713)) - `JDWindow`: Add const qualifier to member function ([#712](https://github.com/JDimproved/JDim/pull/712)) - `MISC::get_pointer_at_window()`: Add const qualifier to argument ([#711](https://github.com/JDimproved/JDim/pull/711)) - `ArticleHash`: Add const qualifier to member function ([#710](https://github.com/JDimproved/JDim/pull/710)) - filtering: Add const qualifier member function ([#709](https://github.com/JDimproved/JDim/pull/709)) - jdlib: Add const qualifier member function ([#708](https://github.com/JDimproved/JDim/pull/708)) - `IOMonitor`: Add const qualifier to member function ([#707](https://github.com/JDimproved/JDim/pull/707)) - `Root`: Add const qualifier to member function ([#706](https://github.com/JDimproved/JDim/pull/706)) - `TextLoader`: Add const qualifier to member function ([#705](https://github.com/JDimproved/JDim/pull/705)) - `ImgRoot`: Add const qualifier to member function ([#704](https://github.com/JDimproved/JDim/pull/704)) - `Img`: Add const qualifier to member function ([#703](https://github.com/JDimproved/JDim/pull/703)) - `DelImgCacheDiag`: Add static keyword to member function ([#702](https://github.com/JDimproved/JDim/pull/702)) - `Core`: Add const qualifier to member function ([#701](https://github.com/JDimproved/JDim/pull/701)) - `InputDiag`: Add const qualifier to member function ([#700](https://github.com/JDimproved/JDim/pull/700)) - `KeyConfig`: Add const qualifier to member function ([#699](https://github.com/JDimproved/JDim/pull/699)) - `ButtonConfig`: Add const qualifier to member function ([#698](https://github.com/JDimproved/JDim/pull/698)) - Add shortcut key configurations for switching board view column sort ([#696](https://github.com/JDimproved/JDim/pull/696)) - `MouseKeyConf`: Add const qualifier to member function part2 ([#695](https://github.com/JDimproved/JDim/pull/695)) - `MouseKeyConf`: Add const qualifier to member function part1 ([#694](https://github.com/JDimproved/JDim/pull/694)) - `LayoutTree`: Set font IDs for abone layout nodes ([#693](https://github.com/JDimproved/JDim/pull/693)) - `MouseKeyItem`: Add const qualifier to member function ([#692](https://github.com/JDimproved/JDim/pull/692)) - `BoardViewBase`: Add const qualifier to member function ([#691](https://github.com/JDimproved/JDim/pull/691)) - `BoardViewBase`: Remove not implemented member function ([#690](https://github.com/JDimproved/JDim/pull/690)) - `SelectListDialog`: Add const qualifier to member function ([#689](https://github.com/JDimproved/JDim/pull/689)) - `BBSListViewBase`: Add const qualifier to member function ([#688](https://github.com/JDimproved/JDim/pull/688)) - `ArticleToolBar`: Remove not implemented member functions ([#687](https://github.com/JDimproved/JDim/pull/687)) - `DrawAreaBase`: Add const qualifier to member function ([#686](https://github.com/JDimproved/JDim/pull/686)) - `LayoutTree`: Add const qualifier to member function ([#685](https://github.com/JDimproved/JDim/pull/685)) - `CARET_POSITION`: Add const qualifier to member function ([#684](https://github.com/JDimproved/JDim/pull/684)) - `ArticleViewBase`: Add const qualifier to member function ([#683](https://github.com/JDimproved/JDim/pull/683)) - `AAManager`: Add const qualifier to member function ([#682](https://github.com/JDimproved/JDim/pull/682)) - Fix initial settings for thread title search (2021-04) ([#680](https://github.com/JDimproved/JDim/pull/680)) - `DragableNoteBook`: Fix DnD destination mark position on Wayland ([#678](https://github.com/JDimproved/JDim/pull/678)) - `Post`: Fix error message for HTTP response ([#677](https://github.com/JDimproved/JDim/pull/677)) - Add several missing headers to HTTP request ([#676](https://github.com/JDimproved/JDim/pull/676)) - `BoardViewBase`: Simplify if statement condition ([#673](https://github.com/JDimproved/JDim/pull/673)) - Replace `Gtk::Menu::popup()` with new API ([#672](https://github.com/JDimproved/JDim/pull/672)) - Refactor `Dom` part3 ([#671](https://github.com/JDimproved/JDim/pull/671)) - `Dom`: Implement member function `size()` ([#670](https://github.com/JDimproved/JDim/pull/670)) - dialog: Add Client-Side Decoration support ([#668](https://github.com/JDimproved/JDim/pull/668)) - Remove `ConstPtr` which represents unowned pointer ([#667](https://github.com/JDimproved/JDim/pull/667)) - `BoardFactory`: Use `std::unique_ptr` instead of raw pointer ([#666](https://github.com/JDimproved/JDim/pull/666)) - `Root`: Use `std::find_if()` instead of range based for statement ([#665](https://github.com/JDimproved/JDim/pull/665)) - `Root`: Use `std::unique_ptr` instead of raw pointer ([#664](https://github.com/JDimproved/JDim/pull/664)) - `ImageAdmin`: Use `std::unique_ptr` instead of raw pointer ([#663](https://github.com/JDimproved/JDim/pull/663)) - `ArticleHash`: Use `std::unique_ptr` instead of raw pointer ([#662](https://github.com/JDimproved/JDim/pull/662)) - `BoardBase`: Use concrete type member instead of dynamic allocation ([#661](https://github.com/JDimproved/JDim/pull/661)) - notebook: Use `Gtk::manage()` instead of operator delete ([#660](https://github.com/JDimproved/JDim/pull/660)) - `PopupWinBase`: Uss css setting instead of drawing border lines ([#659](https://github.com/JDimproved/JDim/pull/659)) - Switch css class name instead of reload css ([#658](https://github.com/JDimproved/JDim/pull/658)) ### [0.5.0-20210404](https://github.com/JDimproved/JDim/compare/JDim-v0.5.0...948a10e9b44) (2021-04-04) - Update histories ([#656](https://github.com/JDimproved/JDim/pull/656)) - `DrawAreaBase`: Use incomplete type support for `std::list` ([#655](https://github.com/JDimproved/JDim/pull/655)) - `History_Manager`: Use incomplete type support for `std::list` ([#654](https://github.com/JDimproved/JDim/pull/654)) - `Log_Manager`: Use incomplete type support for `std::list` ([#653](https://github.com/JDimproved/JDim/pull/653)) - `Completion_Manager`: Simplify memory allocation for `std::vector` elements ([#652](https://github.com/JDimproved/JDim/pull/652)) - `ViewHistory`: Use `std::unique_ptr` instead of new/delete ([#651](https://github.com/JDimproved/JDim/pull/651)) - `ImgRoot`: Use `std::unique_ptr` instead of new/delete ([#650](https://github.com/JDimproved/JDim/pull/650)) - `ArticleHash`: Remove unnecessary member variable ([#649](https://github.com/JDimproved/JDim/pull/649)) - skeleton: Use `std::unique_ptr` instead of new/delete part2 ([#648](https://github.com/JDimproved/JDim/pull/648)) - `JDWindow`: Fix expanding image view window by focus-in ([#647](https://github.com/JDimproved/JDim/pull/647)) - `JDWindow`: remove dummy window for transient setting ([#646](https://github.com/JDimproved/JDim/pull/646)) - Use static allocation for local dialog var instead of new/delete ([#644](https://github.com/JDimproved/JDim/pull/644)) - Use static allocation for local iconv var instead of new/delete ([#643](https://github.com/JDimproved/JDim/pull/643)) - `Admin`: Remove unnecessary if-statements ([#642](https://github.com/JDimproved/JDim/pull/642)) - skeleton: Use delegating constructors instead of setup function ([#641](https://github.com/JDimproved/JDim/pull/641)) - `EditListWin`: Set window display position to center ([#640](https://github.com/JDimproved/JDim/pull/640)) - skeleton: Use `std::unique_ptr` instead of new/delete ([#639](https://github.com/JDimproved/JDim/pull/639)) - `MessageViewBase`: Use std::unique_ptr instead of new/delete ([#638](https://github.com/JDimproved/JDim/pull/638)) - `ImageViewPopup`: Use `std::unique_ptr` instead of new/delete ([#637](https://github.com/JDimproved/JDim/pull/637)) - `HistoryManager`: Use `std::unique_ptr` instead of new/delete ([#636](https://github.com/JDimproved/JDim/pull/636)) - `MouseKeyPref`: Use `std::unique_ptr` instead of new/delete ([#635](https://github.com/JDimproved/JDim/pull/635)) - board: Use `std::unique_ptr` instead of new/delete ([#634](https://github.com/JDimproved/JDim/pull/634)) - bbslist: Use `std::unique_ptr` instead of new/delete ([#633](https://github.com/JDimproved/JDim/pull/633)) - `Core`: Use `std::unique_ptr` instead of new/delete ([#632](https://github.com/JDimproved/JDim/pull/632)) - `JDWinMain`: Use `std::unique_ptr` instead of new/delete ([#631](https://github.com/JDimproved/JDim/pull/631)) - article: Use `std::unique_ptr` instead of new/delete ([#630](https://github.com/JDimproved/JDim/pull/630)) - `ArticleBase`: Use `std::time_t` instead of `struct timeval` ([#629](https://github.com/JDimproved/JDim/pull/629)) - Fix compile error on FreeBSD ([#628](https://github.com/JDimproved/JDim/pull/628)) - misctime: Use `std::chrono::steady_clock` instead of `gettimeofday()` ([#627](https://github.com/JDimproved/JDim/pull/627)) - `DrawAreaBase`: Use `std::chrono::steady_clock` instead of `gettimeofday()` ([#626](https://github.com/JDimproved/JDim/pull/626)) - `BoardBase`: Use `std::time_t` instead of `struct timeval` ([#624](https://github.com/JDimproved/JDim/pull/624)) - Use `std::time()` instead of `gettimeofday()` ([#623](https://github.com/JDimproved/JDim/pull/623)) - `PrefDiagFactory`: Use `std::unique_ptr` instead of new/delete ([#622](https://github.com/JDimproved/JDim/pull/622)) - `Search_Manager`: Use `std::unique_ptr` instead of new/delete ([#621](https://github.com/JDimproved/JDim/pull/621)) - message: Use `std::unique_ptr` instead of new/delete ([#620](https://github.com/JDimproved/JDim/pull/620)) - dbtree: Use `std::unique_ptr` instead of new/delete ([#619](https://github.com/JDimproved/JDim/pull/619)) - Replace char buffer with `std::string` for `CORE::Login2ch/Be` ([#618](https://github.com/JDimproved/JDim/pull/618)) - Remove unused `MISC::get_sec_str()` ([#617](https://github.com/JDimproved/JDim/pull/617)) - `Play_Sound`: Use std::vector instead of new/delete ([#616](https://github.com/JDimproved/JDim/pull/616)) - Update `MISC::datetotime()` to remove conditional compilation ([#615](https://github.com/JDimproved/JDim/pull/615)) - Fix build error for meson 0.57 ([#614](https://github.com/JDimproved/JDim/pull/614)) - skeleton: Fix member initialization ([#612](https://github.com/JDimproved/JDim/pull/612)) - `AboutDiag`: Remove empty member function ([#610](https://github.com/JDimproved/JDim/pull/610)) - `LogItem`: Fix off-by-one error for buffer copy ([#609](https://github.com/JDimproved/JDim/pull/609)) - `JDWinMain`: Fix member initialization ([#608](https://github.com/JDimproved/JDim/pull/608)) - `Dom`: Fix member initialization ([#607](https://github.com/JDimproved/JDim/pull/607)) - `AAMenu`: Fix member initialization ([#606](https://github.com/JDimproved/JDim/pull/606)) - message: Fix member initialization ([#605](https://github.com/JDimproved/JDim/pull/605)) - jdlib: Fix member initialization ([#604](https://github.com/JDimproved/JDim/pull/604)) - image: Fix member initialization ([#603](https://github.com/JDimproved/JDim/pull/603)) - `IOMonitor`: Fix member initialization ([#601](https://github.com/JDimproved/JDim/pull/601)) - history: Fix member initialization ([#600](https://github.com/JDimproved/JDim/pull/600)) - `DND_Manager`: Fix member initialization ([#599](https://github.com/JDimproved/JDim/pull/599)) - dbtree: Fix member initialization ([#598](https://github.com/JDimproved/JDim/pull/598)) - dbimg: Fix member initialization ([#597](https://github.com/JDimproved/JDim/pull/597)) - `Css_Manager`: Fix member initialization ([#596](https://github.com/JDimproved/JDim/pull/596)) - `MouseKeyDiag`: Fix member initialization ([#595](https://github.com/JDimproved/JDim/pull/595)) - board: Fix member initialization ([#594](https://github.com/JDimproved/JDim/pull/594)) - bbslist: Fix member initialization ([#593](https://github.com/JDimproved/JDim/pull/593)) - `SearchToolBar`: Fix member initialization ([#590](https://github.com/JDimproved/JDim/pull/590)) - `ArticleToolBar`: Fix member initialization ([#589](https://github.com/JDimproved/JDim/pull/589)) - `LayoutTree`: Fix member initialization ([#588](https://github.com/JDimproved/JDim/pull/588)) - `CARET_POSITION`: Fix member initialization ([#587](https://github.com/JDimproved/JDim/pull/587)) - `ArticleViewSearch`: Fix member initialization ([#586](https://github.com/JDimproved/JDim/pull/586)) - `ArticleAdmin`: Fix member initialization ([#585](https://github.com/JDimproved/JDim/pull/585)) - `MessageViewBase`: Fix member initialization ([#584](https://github.com/JDimproved/JDim/pull/584)) - `ArticleBase`: Fix member initialization ([#583](https://github.com/JDimproved/JDim/pull/583)) - `Loader`: Fix member initialization ([#582](https://github.com/JDimproved/JDim/pull/582)) - `BoardBase`: Fix member initialization ([#581](https://github.com/JDimproved/JDim/pull/581)) - Change size setting for dialog box to use child natural size ([#580](https://github.com/JDimproved/JDim/pull/580)) - `SetupWidzard`: Delegate dialog size and position to desktop environment ([#579](https://github.com/JDimproved/JDim/pull/579)) - `AAMenu`: Fix display position for ascii art preview on Wayland ([#578](https://github.com/JDimproved/JDim/pull/578)) - `TabNotebook`: Fix tab click ([#577](https://github.com/JDimproved/JDim/pull/577)) - Use `Gdk::Window::get_device_position()` instead of `Gtk::Widget::get_pointer()` ([#574](https://github.com/JDimproved/JDim/pull/574)) - `PopupWin`: Use `Gdk::Seat` instead of `Gdk::DeviceManager` ([#573](https://github.com/JDimproved/JDim/pull/573)) - Get rid of calling deprecated `Gtk::TreeView::set_rules_hint()` ([#572](https://github.com/JDimproved/JDim/pull/572)) - Use `__has_include()` instead of config macro ([#571](https://github.com/JDimproved/JDim/pull/571)) - Migrate cpp\_std to c++17 ([#570](https://github.com/JDimproved/JDim/pull/570)) - Remove obsolete gtk codes for less than version 3.22 ([#569](https://github.com/JDimproved/JDim/pull/569)) - Remove deprecated build option `--with-regex=posix` ([#568](https://github.com/JDimproved/JDim/pull/568)) - Remove obsolete configure options ([#567](https://github.com/JDimproved/JDim/pull/567)) - Update requirements for dependencies (gtkmm >= 3.22) ([#566](https://github.com/JDimproved/JDim/pull/566)) - Update CI settings ([#565](https://github.com/JDimproved/JDim/pull/565)) ### [**JDim-v0.5.0** Release](https://github.com/JDimproved/JDim/releases/tag/JDim-v0.5.0) (2021-01-09) 主な変更点 - GTK2版のサポートを廃止した - Windows(MinGW)のサポートを終了した - 正規表現PCREの対応を廃止した - デフォルトの正規表現ライブラリをGlib Regexに変更した - したらば掲示板のデフォルト名無しに対応した - [文字列置換](https://jdimproved.github.io/JDim/replacestr/)の機能と設定ダイアログを実装した ### [0.5.0-20210109](https://github.com/JDimproved/JDim/compare/1fee8b4327...JDim-v0.5.0) (2021-01-09) - Release 0.5.0 ([#564](https://github.com/JDimproved/JDim/pull/564)) - Update issue templates for feature request ([#563](https://github.com/JDimproved/JDim/pull/563)) - Fix bug report link for CONTRIBUTING.md ([#562](https://github.com/JDimproved/JDim/pull/562)) - Update issue templates for bug report ([#561](https://github.com/JDimproved/JDim/pull/561)) - metadata: Fix AppStream component-ID to lowercase ([#560](https://github.com/JDimproved/JDim/pull/560)) - Fix updating thread title for replacing favorite ([#559](https://github.com/JDimproved/JDim/pull/559)) - Change tips link for building by Meson ([#558](https://github.com/JDimproved/JDim/pull/558)) - Remove travis-ci badge from README.md ([#557](https://github.com/JDimproved/JDim/pull/557)) - Update year to 2021 ([#555](https://github.com/JDimproved/JDim/pull/555)) - `DrawAreaBase`: Fix warning while closing thread view ([#554](https://github.com/JDimproved/JDim/pull/554)) - `Root`: Use `std::find_if()` instead of range based for statement ([#553](https://github.com/JDimproved/JDim/pull/553)) - Fix compiler warning for unused const variable ([#552](https://github.com/JDimproved/JDim/pull/552)) - Fix compiler warning for null possibility ([#551](https://github.com/JDimproved/JDim/pull/551)) - Restore snapcraft configuration ([#550](https://github.com/JDimproved/JDim/pull/550)) - Set snapcraft config for i386 ([#549](https://github.com/JDimproved/JDim/pull/549)) - Remove travis.yml ([#548](https://github.com/JDimproved/JDim/pull/548)) - board: Add const qualifier to member function ([#547](https://github.com/JDimproved/JDim/pull/547)) - Use `Gtk::Dialog::get_content_area()` instead of `get_vbox()` part4 ([#546](https://github.com/JDimproved/JDim/pull/546)) - Remove out-of-date topics for known issues ([#545](https://github.com/JDimproved/JDim/pull/545)) - Update contributing guide for using discussions ([#544](https://github.com/JDimproved/JDim/pull/544)) - Fix button label text without language pack ([#542](https://github.com/JDimproved/JDim/pull/542)) - Add meson description to test/README.md ([#541](https://github.com/JDimproved/JDim/pull/541)) - Bump version to 0.5.0-beta ([#540](https://github.com/JDimproved/JDim/pull/540)) - Modify dialog message for multiple start ([#539](https://github.com/JDimproved/JDim/pull/539)) - board: Fix cache filename checking ([#538](https://github.com/JDimproved/JDim/pull/538)) - `NodeTreeBase`, `DragTreeView`: Fix member initialization ([#537](https://github.com/JDimproved/JDim/pull/537)) - `NodeTreeBase`: Fix out of bounds error ([#536](https://github.com/JDimproved/JDim/pull/536)) - Revert "Use `Gtk::ColorChooserDialog` instead of `Gtk::ColorSelectionDialog`" ([#535](https://github.com/JDimproved/JDim/pull/535)) - Update compiler requirement to clang-5.0 since version 0.5.0 ([#533](https://github.com/JDimproved/JDim/pull/533)) - Implement replacing string feature for thread ([#532](https://github.com/JDimproved/JDim/pull/532)) - meson: Update support to provisional ([#531](https://github.com/JDimproved/JDim/pull/531)) - `NodeTreeBase`: Update DAT parsing to try the old format if failure ([#530](https://github.com/JDimproved/JDim/pull/530)) - Improve regex class ([#529](https://github.com/JDimproved/JDim/pull/529)) - Deprecate posix regex option ([#528](https://github.com/JDimproved/JDim/pull/528)) - Implement loading local rule and settings for JBBS ([#526](https://github.com/JDimproved/JDim/pull/526)) - `BBSListViewBase`: Get URL from `TreeView` instead of `DBTREE` interfaces ([#525](https://github.com/JDimproved/JDim/pull/525)) - Use boardbase URL instead of subject.txt URL for identifier ([#524](https://github.com/JDimproved/JDim/pull/524)) - meson: Fall back requirement to version 0.49.0 ([#523](https://github.com/JDimproved/JDim/pull/523)) - `TabLabel`: Fix null pointer check for debug print ([#522](https://github.com/JDimproved/JDim/pull/522)) - meson: Add summary for build configuration ([#520](https://github.com/JDimproved/JDim/pull/520)) - meson: Add `build_tests` option ([#519](https://github.com/JDimproved/JDim/pull/519)) - `Loader`: Fix typo for error message ([#518](https://github.com/JDimproved/JDim/pull/518)) - setupwizard: Use `Gtk::Grid` instead of `Gtk::VBox` for `PageEnd` ([#517](https://github.com/JDimproved/JDim/pull/517)) - setupwizard: Use `Gtk::Grid` instead of `Gtk::VBox` for `PagePane` ([#516](https://github.com/JDimproved/JDim/pull/516)) - setupwizard: Fix setting mnemonic widget for `PageFont` ([#515](https://github.com/JDimproved/JDim/pull/515)) - setupwizard: Use `Gtk::Grid` instead of `Gtk::VBox` for `PageFont` ([#514](https://github.com/JDimproved/JDim/pull/514)) - setupwizard: Set parent window to pref dialogs for `PageNet` ([#513](https://github.com/JDimproved/JDim/pull/513)) - setupwizard: Use `Gtk::Grid` instead of `Gtk::VBox` for `PageNet` ([#512](https://github.com/JDimproved/JDim/pull/512)) - setupwizard: Use `Gtk::Grid` instead of `Gtk::VBox` for `PageStart` ([#511](https://github.com/JDimproved/JDim/pull/511)) - Remove unused `ImgButton` class ([#510](https://github.com/JDimproved/JDim/pull/510)) - Remove unused `ImgToggleButton` class ([#509](https://github.com/JDimproved/JDim/pull/509)) - Remove unused `MsgDiag::add_default_button(const Gtk::StockID&, const int)` ([#508](https://github.com/JDimproved/JDim/pull/508)) - manual: Update histories ([#507](https://github.com/JDimproved/JDim/pull/507)) - `Root`: Fix null pointer redundant check ([#505](https://github.com/JDimproved/JDim/pull/505)) - miscutil: Fix known condition true/false ([#504](https://github.com/JDimproved/JDim/pull/504)) - miscutil: Fix return dangling pointer ([#503](https://github.com/JDimproved/JDim/pull/503)) - `ImageAdmin`: Fix null pointer redundant check ([#502](https://github.com/JDimproved/JDim/pull/502)) - Use i18n text and nemed icon instead of `Gtk::Stock` for buttons ([#501](https://github.com/JDimproved/JDim/pull/501)) - Use named icons instead of `Gtk::Stock::GO_(FORWARD|BACK)` for button ([#500](https://github.com/JDimproved/JDim/pull/500)) - Remove `Gtk::Stock::REFRESH` icon from button ([#499](https://github.com/JDimproved/JDim/pull/499)) - Use i18n text instead of `Gtk::Stock::REVERT_TO_SAVED` for button label ([#498](https://github.com/JDimproved/JDim/pull/498)) - Use i18n text instead of `Gtk::Stock::SAVE` for button label ([#497](https://github.com/JDimproved/JDim/pull/497)) - Use i18n text instead of `Gtk::Stock::OPEN` for button label ([#496](https://github.com/JDimproved/JDim/pull/496)) - Use i18n text instead of `Gtk::Stock::(ADD|DELETE)` for button label ([#495](https://github.com/JDimproved/JDim/pull/495)) - Use i18n text instead of `Gtk::Stock::REMOVE` for button label ([#494](https://github.com/JDimproved/JDim/pull/494)) - Use i18n text instead of `Gtk::Stock::CLOSE` for button label ([#493](https://github.com/JDimproved/JDim/pull/493)) - Use i18n text instead of `Gtk::Stock::APPLY` for button label ([#492](https://github.com/JDimproved/JDim/pull/492)) - manual: Add supplement for environment input ([#491](https://github.com/JDimproved/JDim/pull/491)) - Fix documents for meson command example ([#489](https://github.com/JDimproved/JDim/pull/489)) - Use i18n text instead of `Gtk::Stock::CANCEL` for button label ([#488](https://github.com/JDimproved/JDim/pull/488)) - Use i18n text instead of `Gtk::Stock::OK` for button label ([#487](https://github.com/JDimproved/JDim/pull/487)) - Use i18n text instead of `Gtk::Stock::NO` for button label ([#486](https://github.com/JDimproved/JDim/pull/486)) - Use i18n text instead of `Gtk::Stock::YES` for button label ([#485](https://github.com/JDimproved/JDim/pull/485)) - Add `JDLIB::Iconv` test cases ([#484](https://github.com/JDimproved/JDim/pull/484)) - snapcraft: Get rid of `libsigc++-2.0` from stage-packages ([#483](https://github.com/JDimproved/JDim/pull/483)) - Fix argument order for `JDLIB::Iconv` constructor ([#481](https://github.com/JDimproved/JDim/pull/481)) - Remove outdated rpm spec file ([#480](https://github.com/JDimproved/JDim/pull/480)) - Update histories ([#479](https://github.com/JDimproved/JDim/pull/479)) jdim-0.7.0/docs/manual/2022.md000066400000000000000000000127071417047150700155720ustar00rootroot00000000000000--- title: 更新履歴(2022年) layout: default --- > [Top](../) > [更新履歴]({{ site.baseurl }}/history/) > {{ page.title }} ## {{ page.title }} ### [0.8.0-unreleased](https://github.com/JDimproved/JDim/compare/JDim-v0.7.0...master) (unreleased) ### [**JDim-v0.7.0** Release](https://github.com/JDimproved/JDim/releases/tag/JDim-v0.7.0) (2022-01-15) 主な変更点 - 板のプロパティにUser-Agent(UA)の設定を追加した - 板にある未取得のスレッドのうち指定レス以下をあぼ〜んにする設定を板のプロパティに追加した - デフォルト名無しとID無しのレスをあぼ〜んにする設定をスレのプロパティに追加した - 以前の修正で動作しなくなったコマンドラインオプション`--norestore`を廃止した ### [0.7.0-20220115](https://github.com/JDimproved/JDim/compare/344da3bdc1...JDim-v0.7.0) (2022-01-15) - Release 0.7.0 ([#895](https://github.com/JDimproved/JDim/pull/895)) - Revert "Set snapcraft config for i386 (2022-01) (#893)" ([#894](https://github.com/JDimproved/JDim/pull/894)) - Set snapcraft config for i386 (2022-01) ([#893](https://github.com/JDimproved/JDim/pull/893)) - `EditColumns`: Remove unused member function `copy_row()` ([#892](https://github.com/JDimproved/JDim/pull/892)) - Add error message for XSMP initialization failure to console log ([#891](https://github.com/JDimproved/JDim/pull/891)) - Update year to 2022 ([#889](https://github.com/JDimproved/JDim/pull/889)) - Bump version to 0.7.0-beta ([#888](https://github.com/JDimproved/JDim/pull/888)) - readme: Update example cpu for compiler optimization ([#887](https://github.com/JDimproved/JDim/pull/887)) - `Post`: Fix cookie check for posting ([#886](https://github.com/JDimproved/JDim/pull/886)) - miscutil: Fix percent encoding to use uppercase ([#885](https://github.com/JDimproved/JDim/pull/885)) - `Loader`: Correct HTTP header position in request ([#884](https://github.com/JDimproved/JDim/pull/884)) - `History_Manager`: Remove unused `replace_current_url_viewhistory()` ([#883](https://github.com/JDimproved/JDim/pull/883)) - `BoardViewBase`: Remove unused member function `redraw_scrollbar()` ([#882](https://github.com/JDimproved/JDim/pull/882)) - `DrawAreaBase`: Remove unused member function `is_separator_on_screen()` ([#881](https://github.com/JDimproved/JDim/pull/881)) - `Loader`: Use `reinterpret_cast` instead of C-style pointer casting ([#880](https://github.com/JDimproved/JDim/pull/880)) - `Root`: Rename local variable to avoid shadow function ([#879](https://github.com/JDimproved/JDim/pull/879)) - miscutil: Fix dereference invalid iterator ([#878](https://github.com/JDimproved/JDim/pull/878)) - `BBSListViewBase`: Use `std::find_if()` instead of range based for ([#877](https://github.com/JDimproved/JDim/pull/877)) - manual: Add note for cache directory creation ([#876](https://github.com/JDimproved/JDim/pull/876)) - board: Improve dialog layout for displaying cookie and keyword ([#875](https://github.com/JDimproved/JDim/pull/875)) - Implement User-Agent configuration for board ([#874](https://github.com/JDimproved/JDim/pull/874)) - `EditTreeView`: Add const qualifier to function parameter ([#873](https://github.com/JDimproved/JDim/pull/873)) - Add const qualifier to local variable found by cppcheck ([#871](https://github.com/JDimproved/JDim/pull/871)) - Use `set_value` function instead of `operator[]` part2 ([#870](https://github.com/JDimproved/JDim/pull/870)) - miscutil: Fix heap buffer overflow ([#869](https://github.com/JDimproved/JDim/pull/869)) - `MouseKeyPref`: Use `set_value` function instead of `operator[]` ([#868](https://github.com/JDimproved/JDim/pull/868)) - `Root`: Fix known condition true/false ([#867](https://github.com/JDimproved/JDim/pull/867)) - `BoardViewLog`: Fix known condition true/false ([#866](https://github.com/JDimproved/JDim/pull/866)) - font: Fix known condition true/false ([#865](https://github.com/JDimproved/JDim/pull/865)) - `CheckUpdate_Manager`: Update iterator for loop ([#864](https://github.com/JDimproved/JDim/pull/864)) - `JDTreeViewBase`: Update iterator for loop ([#863](https://github.com/JDimproved/JDim/pull/863)) - `SKELETON::ToolBar`: Update iterator for loop ([#862](https://github.com/JDimproved/JDim/pull/862)) - `EditTreeView`: Update iterator for loop ([#861](https://github.com/JDimproved/JDim/pull/861)) - `CompletionEntry`: Update iterator for loop ([#860](https://github.com/JDimproved/JDim/pull/860)) - `BackForwardButton`: Update iterator for loop ([#859](https://github.com/JDimproved/JDim/pull/859)) - `Admin`: Update iterator for loop ([#858](https://github.com/JDimproved/JDim/pull/858)) - `DrawAreaBase`: Improve performance for drawing `Pango::GlyphString` ([#857](https://github.com/JDimproved/JDim/pull/857)) - session: Update iterator for loop ([#856](https://github.com/JDimproved/JDim/pull/856)) - `Search_Manager`: Update iterator for loop ([#855](https://github.com/JDimproved/JDim/pull/855)) - `Post`: Update iterator for loop ([#854](https://github.com/JDimproved/JDim/pull/854)) - `Log_Manager`: Fix iterator loop ([#853](https://github.com/JDimproved/JDim/pull/853)) - menuslots: Update iterator for loop ([#852](https://github.com/JDimproved/JDim/pull/852)) - playsound: Remove `__has_include` macro for `` ([#851](https://github.com/JDimproved/JDim/pull/851)) jdim-0.7.0/docs/manual/about.md000066400000000000000000000042171417047150700163140ustar00rootroot00000000000000--- title: JDimについて layout: default --- > [Top](../) > {{ page.title }} ## {{ page.title }} - [概要](#abstract) - [著作権](#copyright) - [ライセンス](#license) - [連絡先](#contact) - [動作プラットフォーム](#platform) ### 概要 JDim (JD improved)はGTK+(gtkmm)を使用した[5ちゃんねる][]型マルチスレッドBBSを閲覧するためのブラウザです。 JDimはGPLv2の下で公開されている [JD][] からforkしたソフトウェアであり、 ルック・アンド・フィールや環境設定はJDと[互換性][]があります。 **注意: JDim本体は5ch.netのAPIに対応しておりません。** ご不便をおかけして申し訳ありませんが、5ch.netにアクセスする場合はWebブラウザなどをご使用ください。 ### 著作権 © 2017-2019 [yama-natuki][] © 2019-2022 [JDimproved project][repository] パッチやファイルを取り込んだ場合、それらのコピーライトは「JDimproved project」に統一します。 ##### fork元 © 2006-2015 [JD project][JD] ### ライセンス [GNU General Public License, version 2][gpl2] ### 連絡先 バグ報告その他は[Linux板@5ちゃんねる][linux]のJD/JDimスレ、または[JDimのリポジトリ][repository]にて行ってください。 ### 動作プラットフォーム LinuxなどのUnixライクなOS(FreeBSD,OpenBSD,Nexenta,MacOSXでも動作報告例があります)。 メンテナンスの都合によりWindows(MinGW)版のサポートは[終了][#445]しました。 [5ちゃんねる]: https://5ch.net [JD]: https://ja.osdn.net/projects/jd4linux/ "JD for Linux プロジェクト日本語トップページ" [yama-natuki]: https://github.com/yama-natuki [repository]: https://github.com/JDimproved/JDim [gpl2]: https://ja.osdn.net/projects/opensource/wiki/licenses%2FGNU_General_Public_License [linux]: https://mao.5ch.net/linux/ [#445]: https://github.com/JDimproved/JDim/issues/445 [互換性]: {{ site.baseurl }}/start/#compatibility "起動について \| JDim" jdim-0.7.0/docs/manual/asciiart.md000066400000000000000000000036261417047150700170040ustar00rootroot00000000000000--- title: アスキーアート(AA)の入力について layout: default --- > [Top](../) > {{ page.title }} ## {{ page.title }} - [操作方法](#operation) - [登録方法](#register) ### 操作方法 書き込みビューでアスキーアート(AA)メニューを表示してAAを入力することができる。
Alt+A
AAメニューを表示
🡱, J, Ctrl+P
選択項目を上に移動
🡳, K, Ctrl+N, Space
選択項目を下に移動
Enter
決定
Esc
キャンセル
なお、AAメニューの左側の`[]`の内部にある文字はキーボードショートカットを表していて、 キーを押すと対応するAAが入力される(数字→英小文字→英大文字の順にキーが割り当てられる。 ただし `h`,`i`,`j`,`k` は除く )。 ### 登録方法 AAはキャッシュディレクトリ(デフォルトでは`$XDG_CACHE_HOME/jdim/`)に **aalist.txt**というテキストファイルを作成して登録する。 一行AAはそのままaalist.txtの各行に登録する。 複数行AAはキャッシュディレクトリに「`aa`」というディレクトリを作成し、 その下に複数行AAを保存したテキストファイル(\*.txt)をおき、 aalist.txtに「\*ファイル名」という形式で登録する。なお拡張子(.txt)は省略することが出来る。 なお先頭のアスタリスクを「\*\*」と2つつなげるとファイル名ではなくて文字の「\*」一文字に変換される。 #### aalist.txt 登録例 ``` キタ━━━━━━(゚∀゚)━━━━━━ !! *mona ``` #### $XDG_CACHE_HOME/jdim/aa/mona.txtの内容 ```   ∧_∧  ( ´∀`)  (    )  | |  |  (__)_) ``` jdim-0.7.0/docs/manual/backup.md000066400000000000000000000012141417047150700164410ustar00rootroot00000000000000--- title: バックアップ、アンインストールについて layout: default --- > [Top](../) > {{ page.title }} ## {{ page.title }} - [バックアップ方法](#backup) - [アンインストール方法( 手動の場合 )](#uninstall-manual) ### バックアップ方法 キャッシュのバックアップはキャッシュディレクトリ(デフォルトでは `$XDG_CACHE_HOME/jdim` ) 以下を保存するだけでよい。 ### アンインストール方法( 手動の場合 ) ``` $ rm (インストールパス)/jdim $ rm -rf <キャッシュディレクトリ> ``` jdim-0.7.0/docs/manual/dat.md000066400000000000000000000026251417047150700157530ustar00rootroot00000000000000--- title: datファイルのインポート、エクスポートについて layout: default --- > [Top](../) > {{ page.title }} ## datファイルのインポート、エクスポートについて - [インポートの方法](#import) - [エクスポートの方法](#export) ### インポートの方法 以下の方法がある 1. 対象板のスレ一覧を開き、デスクトップやnautilusからdatファイルをスレ一覧にドロップする(ファイルは複数選択可)。 2. 対象板のスレ一覧を開き、ツールメニューから 「`表示中の板にdatをインポート`」 を選ぶ。 するとファイル選択ダイアログが開くのでdatを選択する(ファイルは複数選択可)。 3. 板一覧やお気に入りの対象板の上で右クリックし、コンテキストメニューから 「`datのインポート`」 を選ぶ。 するとファイル選択ダイアログが開くのでdatを選択する(ファイルは複数選択可)。 ### エクスポートの方法 以下の方法がある 1. スレ一覧を開き、右クリックしてコンテキストメニューから 「`datを保存`」 を選択 ( 範囲選択して複数のスレのdat保存も可 )。 2. スレを開き、右クリックしてコンテキストメニューから 「`その他`→`datを保存`」 を選択。 jdim-0.7.0/docs/manual/environment.md000066400000000000000000000056001417047150700175430ustar00rootroot00000000000000--- title: 動作環境の記入について layout: default --- > [Top](../) > [その他]({{ site.baseurl }}/info/) > {{ page.title }} ## {{ page.title }} - [概要](#abstract) - [注意](#note) - [記載例](#example) ### 概要 書き込みビューのコンテキストメニューからJDimの動作環境が記入できる。不具合報告などの際に活用すること。 #### 自動的に入力される物の例 ``` [バージョン] JDim 0.1.0-20190331(git:1baa555550) [ディストリ ] Example Linux [パッケージ] バイナリ/ソース( <配布元> ) [ DE/WM ] GNOME [ gtkmm  ] 2.24.5 [ glibmm  ] 2.56.0 [ TLS lib ] GnuTLS 3.6.5 [オプション ] '--with-alsa' [ そ の 他 ] ``` バージョン等は自動で入力されるが、他の項目については追加/変更すること。 ### 注意 - 「`[バージョン]`」はgitのコミットハッシュが取得できた場合はコミット作成日と `(git:〜)` が追加される。 このときローカルのソースコードに変更があるときはマーク(`:M`)が付く。 ハッシュを取得できなかったときは [ヘッダーファイル (jdversion.h)][jdversion] の日付が追加される。 - 「`[パッケージ]`」の項目に自動で表示される物はテンプレートなので明らかに不要と考えられる場合を除いて変更する事。 - 「`[ DE/WM ]`」の項目は環境変数から判別出来た場合のみ入る。 - 「`[ TLS lib ]`」の項目はバージョン 0.2.0 から追加。 - 「`[オプション ]`」の項目はconfigureオプションの一部が入る。( 無い場合は省略 ) - makeはソースのルートディレクトリ(top\_builddir)で実行しないとリビジョン番号が取得/更新されないので注意。 ### 記載例 引用: [5ch ブラウザ JD 21][thread] (5chスレ) ``` [バージョン] JDim 0.1.0-20190126(git:71f2266d0f) [ディストリ ] Linux Mint 19.1 (x86_64) [パッケージ] バイナリ/ソース( <配布元> ) [ DE/WM ] GNOME [ gtkmm  ] 2.24.5 [ glibmm  ] 2.56.0 [ そ の 他 ] ``` ``` [バージョン] JDim 0.1.0-20190217(git:e8cc28c993) [ディストリ ] PCLinuxOS 2019 (x86_64) [パッケージ] バイナリ/ソース( <配布元> ) [ DE/WM ] KDE [ gtkmm  ] 2.24.5 [ glibmm  ] 2.56.1 [ そ の 他 ] LANG = en_US.UTF-8 ``` ``` [バージョン] JDim 0.1.0-20190302(git:b959c45b2a) [ディストリ ] Manjaro Linux (x86_64) [パッケージ] バイナリ/ソース( <配布元> ) [ DE/WM ] XFCE [ gtkmm  ] 3.24.0 [ glibmm  ] 2.58.0 [オプション ] '--with-stdthread' '--with-openssl''--with-gtkmm3' [ そ の 他 ] ``` [jdversion]: https://github.com/JDimproved/JDim/tree/master/src/jdversion.h "JDim/jdversion.h at master" [thread]: https://mao.5ch.net/test/read.cgi/linux/1540656394/ jdim-0.7.0/docs/manual/external.md000066400000000000000000000033751417047150700170300ustar00rootroot00000000000000--- title: 外部板について layout: default --- > [Top](../) > {{ page.title }} ## {{ page.title }} - [現在の対応板](#support) - [外部板の登録方法](#register) - [まちBBSのofflaw形式対応について](#machi_offlaw) ### 現在の対応板 - 2ch互換板 - [したらば掲示板](https://rentalbbs.shitaraba.com/) - [まちBBS](https://machi.to/) ### 外部板の登録方法 外部板を登録するには、サイドバーの板一覧の一番上にある「`外部板`」フォルダを右クリックして 「`外部板追加`」を選択し、板名とアドレスを入力する。Basic認証が必要な場合はIDとパスワードも登録する。 後から板のアドレスや名前を変更する場合は対象の板の上で右クリックすると出てくる「`編集`」メニューからおこなう。 手動で直接登録したい場合はetc.txtを編集する。etc.txtはNavi2ch互換仕様で上からタイトル、アドレス、IDを順に書く。 また、最大レス数が1000で無い場合は板のプロパティを開き、一般タブの最大レス数を変更する。 ### まちBBSのofflaw形式対応について まちBBSでofflaw形式の読み込みを行うには、 `設定`メニュー → `一般` → `まちBBSでofflaw.cgiを使用する`をチェックする。 offlaw形式での読み込みには以下のような長短がある。 ○ 読み込みが速い
○ ネットワーク負荷が小さい
○ お気に入りで更新チェックが出来る × リモートホストが取得できなくなった
× offlaw形式で読み込んだログは過去のバージョンのJDimでは読めない jdim-0.7.0/docs/manual/favorite.md000066400000000000000000000077121417047150700170240ustar00rootroot00000000000000--- title: お気に入りについて layout: default --- > [Top](../) > {{ page.title }} ## {{ page.title }} - [表示](#display) - [編集](#edit) - [登録](#register) - [削除](#delete) - [仮想板](#virtual) ### 表示 板、スレ、画像に関してお気に入りの登録が可能。 左サイドバーのコンボボックスでお気に入りを選ぶか、 メインツールバーのお気に入りボタンを押すとお気に入りビューが表示される。 ### 編集 お気に入りはドラッグ&ドロップで直接編集可能。 Ctrl+クリック、または中ボタンドラッグで複数行を選択することが出来る。 編集内容はCtrl+Z、又はCtrl+/で元に戻す(Undo)、Ctrl+Shift+Zでやり直し(Redo)が可能。 また、`ツール`メニュー → `お気に入りの編集`からお気に入り編集ウィンドウを開く。 お気に入り編集ウィンドウ上では項目をクリックしてもビューを開かないので簡単にお気に入りを編集することができる。 さらに、お気に入り編集ウィンドウとサイドバーのお気に入りの間で相互にドラッグ&ドロップすることも可能。 なお、お気に入りの設定ファイル(キャッシュディレクトリ内にあるbookmark.xml)は XML形式なので手動で編集することも可能だが推奨しない。 ### 登録 以下のいずれかを行う。 - スレ一覧やスレなどの各ビューのタブをサイドバーのお気に入り(または編集ウィンドウ)にドラッグ&ドロップ - 各ビューのツールバーにある「`お気に入りに追加`」ボタンを押す。またはCtrl+Dを押す。 すると挿入先ダイアログが表示されるので挿入先を選んで`OK`を押す。 - スレ一覧でCtrl+クリック(または中ボタンドラッグ)で複数のスレを選択 -> サイドバーのお気に入り(または編集ウィンドウ)にドラッグ&ドロップ - スレ一覧でCtrl+クリック(または中ボタンドラッグ)で複数のスレを選択 -> 右クリック -> 「`お気に入りに追加`」を選択 - 画像アイコンをサイドバーのお気に入り(または編集ウィンドウ)にドラッグ&ドロップ - 画像ビューで右クリック -> 「`お気に入りに追加`」を選択 - 画像ビューでCtrl+Dを押して挿入先ダイアログを表示 ### 削除 削除したい行を選択してから右クリックして`削除`を選択するかDeleteを押す。 複数の行を削除したい場合は以下のいずれかを行う。 - サイドバーのお気に入り(または編集ウィンドウ)にでCtrl+クリック(または中ボタンドラッグ)で複数行を選択 -> 右クリック -> `削除` - サイドバーのお気に入り(または編集ウィンドウ)にでCtrl+クリック(または中ボタンドラッグ)で複数行を選択 -> Delete キー ### 仮想板 お気に入りに登録したスレ(及びスレ履歴と閉じたスレ履歴)は仮想板としてスレ一覧に一覧表示することが可能である。 お気に入りに登録されたスレを全て表示するには`ツール`メニューの「`サイドバーをスレ一覧に表示`」を選択する。 また「`サイドバーの仮想板を作成`」を選択するとお気に入りにショートカットを作成して普通の板の様に操作することが出来る。 お気に入りのディレクトリの中に登録したスレをスレ一覧に表示するには、ディレクトリの上で右クリックしてメニューを表示し、 「`ディレクトリをスレ一覧に表示`」を選択する。 また「`仮想板作成`」を選択するとお気に入りにショートカットを作成して普通の板の様に操作することが出来る。 jdim-0.7.0/docs/manual/history.md000066400000000000000000000014021417047150700166740ustar00rootroot00000000000000--- title: 更新履歴 layout: default --- > [Top](../) > {{ page.title }} ## {{ page.title }} - [2022年]({{ site.baseurl }}/2022/) - [2021年]({{ site.baseurl }}/2021/) - [2020年]({{ site.baseurl }}/2020/) - [2019年]({{ site.baseurl }}/2019/) ... JDimproved project - [2018年]({{ site.baseurl }}/2018/) - [2017年]({{ site.baseurl }}/2017/) ... yama-natuki/JD - [2015年]({{ site.baseurl }}/2015/) - [2014年]({{ site.baseurl }}/2014/) - [2013年]({{ site.baseurl }}/2013/) - [2012年]({{ site.baseurl }}/2012/) - [2011年]({{ site.baseurl }}/2011/) - [2010年]({{ site.baseurl }}/2010/) - [2009年]({{ site.baseurl }}/2009/) - [2008年]({{ site.baseurl }}/2008/) - [2007年]({{ site.baseurl }}/2007/) - [2006年]({{ site.baseurl }}/2006/) ... JD project jdim-0.7.0/docs/manual/index.md000066400000000000000000000004141417047150700163040ustar00rootroot00000000000000--- # You don't need to edit this file, it's empty on purpose. # Edit theme's home layout instead if you wanna make some changes # See: https://jekyllrb.com/docs/themes/#overriding-theme-defaults layout: redirected sitemap: false permalink: manual/ redirect_to: / --- jdim-0.7.0/docs/manual/info.md000066400000000000000000000012641417047150700161340ustar00rootroot00000000000000--- title: その他 layout: default --- > [Top](../) > {{ page.title }} ## {{ page.title }} - [板移転について]({{ site.baseurl }}/move/) - [ユーザーコマンド設定集][wiki-usrcmd] (JD wiki) - [テーマについて]({{ site.baseurl }}/skin/) - [動作環境の記入について]({{ site.baseurl }}/environment/) - [効果音の再生について]({{ site.baseurl }}/sound/) - [URL変換について]({{ site.baseurl }}/urlreplace/) - [置換文字列の設定(ReplaceStr)]({{ site.baseurl }}/replacestr/) [wiki-usrcmd]: https://ja.osdn.net/projects/jd4linux/wiki/%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E8%A8%AD%E5%AE%9A%E9%9B%86 jdim-0.7.0/docs/manual/link-20190122.md000066400000000000000000000113661417047150700170400ustar00rootroot00000000000000--- title: JDim 0.1.0-20190122 layout: default --- > [Top](../) > 前のバージョンのマニュアル (GitHubリンク) > {{ page.title }} ## 前のバージョンのマニュアル ( {{ page.title }} ) 括弧書きのないリンクは[GitHubリポジトリ][gh]のページです。
**注意**: 原稿のMarkdownはHTMLに変換する前提で書かれているので一部の表示やリンクが機能しません。 - [README.md][readme] - [COPYING][copying] - [INSTALL][install] - [docs/README.md][docs-readme] - [test/README.md][test-readme] - [CONTRIBUTING.md][contributing] --- - [JDimについて][about] - [make、実行方法について][make] - [OS/ディストリビューション別インストール方法][jdwiki-install] (JD wiki) - [起動について][start] - [datファイルのインポート、エクスポートについて][dat] - [バックアップ、アンインストールについて][backup] - [操作方法について][operation] - [マウスジェスチャについて][mouse] - [お気に入りについて][favorite] - [外部板について][external] - [実況モードについて][live] - [ユーザーコマンド、リンクフィルタについて][usrcmd] - [アスキーアート(AA)の入力について][asciiart] - [次スレ検索について][next] - [FAQ][jdwiki-faq] (JD wiki) - [Tips][jdwiki-tips] (JD wiki) - その他 - [板移転について][move] - [ユーザーコマンド設定集][jdwiki-usrcmd] (JD wiki) - [テーマについて][skin] - [動作環境の記入について][environment] - [効果音の再生について][sound] - [URL変換について][urlreplace] [gh]: https://github.com/JDimproved/JDim/tree/JDim-v0.1.0 [readme]: https://github.com/JDimproved/JDim/blob/JDim-v0.1.0/README.md [copying]: https://github.com/JDimproved/JDim/blob/JDim-v0.1.0/COPYING [install]: https://github.com/JDimproved/JDim/blob/JDim-v0.1.0/INSTALL [docs-readme]: https://github.com/JDimproved/JDim/blob/a279663014d8ddd621b95a8c5fab22c83f6484c8/docs/README.md [test-readme]: https://github.com/JDimproved/JDim/blob/923ea662c47cef6ff9874e1d33f87197e0a98eb7/test/README.md [contributing]: https://github.com/JDimproved/JDim/blob/e8cc28c993039363153c3e0631cc76b61ed38566/CONTRIBUTING.md [about]: https://github.com/JDimproved/JDim/blob/a279663014d8ddd621b95a8c5fab22c83f6484c8/docs/manual/about.md [make]: https://github.com/JDimproved/JDim/blob/a279663014d8ddd621b95a8c5fab22c83f6484c8/docs/manual/make.md [jdwiki-install]: https://ja.osdn.net/projects/jd4linux/wiki/OS%2F%E3%83%87%E3%82%A3%E3%82%B9%E3%83%88%E3%83%AA%E3%83%93%E3%83%A5%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E5%88%A5%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB%E6%96%B9%E6%B3%95 [start]: https://github.com/JDimproved/JDim/blob/a279663014d8ddd621b95a8c5fab22c83f6484c8/docs/manual/start.md [dat]: https://github.com/JDimproved/JDim/blob/a279663014d8ddd621b95a8c5fab22c83f6484c8/docs/manual/dat.md [backup]: https://github.com/JDimproved/JDim/blob/a279663014d8ddd621b95a8c5fab22c83f6484c8/docs/manual/backup.md [operation]: https://github.com/JDimproved/JDim/blob/a279663014d8ddd621b95a8c5fab22c83f6484c8/docs/manual/operation.md [mouse]: https://github.com/JDimproved/JDim/blob/a279663014d8ddd621b95a8c5fab22c83f6484c8/docs/manual/mouse.md [favorite]: https://github.com/JDimproved/JDim/blob/a279663014d8ddd621b95a8c5fab22c83f6484c8/docs/manual/favorite.md [external]: https://github.com/JDimproved/JDim/blob/a279663014d8ddd621b95a8c5fab22c83f6484c8/docs/manual/external.md [live]: https://github.com/JDimproved/JDim/blob/a279663014d8ddd621b95a8c5fab22c83f6484c8/docs/manual/live.md [usrcmd]: https://github.com/JDimproved/JDim/blob/a279663014d8ddd621b95a8c5fab22c83f6484c8/docs/manual/usrcmd.md [asciiart]: https://github.com/JDimproved/JDim/blob/a279663014d8ddd621b95a8c5fab22c83f6484c8/docs/manual/asciiart.md [next]: https://github.com/JDimproved/JDim/blob/a279663014d8ddd621b95a8c5fab22c83f6484c8/docs/manual/next.md [jdwiki-faq]: https://ja.osdn.net/projects/jd4linux/wiki/FAQ [jdwiki-tips]: https://ja.osdn.net/projects/jd4linux/wiki/Tips [move]: https://github.com/JDimproved/JDim/blob/a279663014d8ddd621b95a8c5fab22c83f6484c8/docs/manual/move.md [jdwiki-usrcmd]: https://ja.osdn.net/projects/jd4linux/wiki/%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E8%A8%AD%E5%AE%9A%E9%9B%86 [skin]: https://github.com/JDimproved/JDim/blob/a279663014d8ddd621b95a8c5fab22c83f6484c8/docs/manual/skin.md [environment]: https://github.com/JDimproved/JDim/blob/a279663014d8ddd621b95a8c5fab22c83f6484c8/docs/manual/environment.md [sound]: https://github.com/JDimproved/JDim/blob/a279663014d8ddd621b95a8c5fab22c83f6484c8/docs/manual/sound.md [urlreplace]: https://github.com/JDimproved/JDim/blob/a279663014d8ddd621b95a8c5fab22c83f6484c8/docs/manual/urlreplace.md jdim-0.7.0/docs/manual/link-20190720.md000066400000000000000000000104331417047150700170360ustar00rootroot00000000000000--- title: JDim 0.2.0-20190720 layout: default --- > [Top](../) > 前のバージョンのマニュアル (GitHubリンク) > {{ page.title }} ## 前のバージョンのマニュアル ( {{ page.title }} ) 括弧書きのないリンクは[GitHubリポジトリ][gh]のページです。
**注意**: 原稿のMarkdownはHTMLに変換する前提で書かれているので一部の表示やリンクが機能しません。 - [リリースノート][release-note] (GitHub Releases) - [README.md][readme] - [COPYING][copying] - [INSTALL][install] - [docs/README.md][docs-readme] - [test/README.md][test-readme] - [CONTRIBUTING.md][contributing] --- - [JDimについて][about] - [make、実行方法について][make] - [OS/ディストリビューション別インストール方法][jdwiki-install] (JD wiki) - [起動について][start] - [datファイルのインポート、エクスポートについて][dat] - [バックアップ、アンインストールについて][backup] - [操作方法について][operation] - [マウスジェスチャについて][mouse] - [お気に入りについて][favorite] - [外部板について][external] - [実況モードについて][live] - [ユーザーコマンド、リンクフィルタについて][usrcmd] - [アスキーアート(AA)の入力について][asciiart] - [次スレ検索について][next] - [FAQ][jdwiki-faq] (JD wiki) - [Tips][jdwiki-tips] (JD wiki) - その他 - [板移転について][move] - [ユーザーコマンド設定集][jdwiki-usrcmd] (JD wiki) - [テーマについて][skin] - [動作環境の記入について][environment] - [効果音の再生について][sound] - [URL変換について][urlreplace] [gh]: https://github.com/JDimproved/JDim/tree/JDim-v0.2.0 [release-note]: https://github.com/JDimproved/JDim/releases/tag/JDim-v0.2.0 [readme]: https://github.com/JDimproved/JDim/blob/JDim-v0.2.0/README.md [copying]: https://github.com/JDimproved/JDim/blob/JDim-v0.2.0/COPYING [install]: https://github.com/JDimproved/JDim/blob/JDim-v0.2.0/INSTALL [docs-readme]: https://github.com/JDimproved/JDim/blob/JDim-v0.2.0/docs/README.md [test-readme]: https://github.com/JDimproved/JDim/blob/JDim-v0.2.0/test/README.md [contributing]: https://github.com/JDimproved/JDim/blob/JDim-v0.2.0/CONTRIBUTING.md [about]: https://github.com/JDimproved/JDim/blob/JDim-v0.2.0/docs/manual/about.md [make]: https://github.com/JDimproved/JDim/blob/JDim-v0.2.0/docs/manual/make.md [jdwiki-install]: https://ja.osdn.net/projects/jd4linux/wiki/OS%2F%E3%83%87%E3%82%A3%E3%82%B9%E3%83%88%E3%83%AA%E3%83%93%E3%83%A5%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E5%88%A5%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB%E6%96%B9%E6%B3%95 [start]: https://github.com/JDimproved/JDim/blob/JDim-v0.2.0/docs/manual/start.md [dat]: https://github.com/JDimproved/JDim/blob/JDim-v0.2.0/docs/manual/dat.md [backup]: https://github.com/JDimproved/JDim/blob/JDim-v0.2.0/docs/manual/backup.md [operation]: https://github.com/JDimproved/JDim/blob/JDim-v0.2.0/docs/manual/operation.md [mouse]: https://github.com/JDimproved/JDim/blob/JDim-v0.2.0/docs/manual/mouse.md [favorite]: https://github.com/JDimproved/JDim/blob/JDim-v0.2.0/docs/manual/favorite.md [external]: https://github.com/JDimproved/JDim/blob/JDim-v0.2.0/docs/manual/external.md [live]: https://github.com/JDimproved/JDim/blob/JDim-v0.2.0/docs/manual/live.md [usrcmd]: https://github.com/JDimproved/JDim/blob/JDim-v0.2.0/docs/manual/usrcmd.md [asciiart]: https://github.com/JDimproved/JDim/blob/JDim-v0.2.0/docs/manual/asciiart.md [next]: https://github.com/JDimproved/JDim/blob/JDim-v0.2.0/docs/manual/next.md [jdwiki-faq]: https://ja.osdn.net/projects/jd4linux/wiki/FAQ [jdwiki-tips]: https://ja.osdn.net/projects/jd4linux/wiki/Tips [move]: https://github.com/JDimproved/JDim/blob/JDim-v0.2.0/docs/manual/move.md [jdwiki-usrcmd]: https://ja.osdn.net/projects/jd4linux/wiki/%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E8%A8%AD%E5%AE%9A%E9%9B%86 [skin]: https://github.com/JDimproved/JDim/blob/JDim-v0.2.0/docs/manual/skin.md [environment]: https://github.com/JDimproved/JDim/blob/JDim-v0.2.0/docs/manual/environment.md [sound]: https://github.com/JDimproved/JDim/blob/JDim-v0.2.0/docs/manual/sound.md [urlreplace]: https://github.com/JDimproved/JDim/blob/JDim-v0.2.0/docs/manual/urlreplace.md jdim-0.7.0/docs/manual/link-20200118.md000066400000000000000000000104331417047150700170270ustar00rootroot00000000000000--- title: JDim 0.3.0-20200118 layout: default --- > [Top](../) > 前のバージョンのマニュアル (GitHubリンク) > {{ page.title }} ## 前のバージョンのマニュアル ( {{ page.title }} ) 括弧書きのないリンクは[GitHubリポジトリ][gh]のページです。
**注意**: 原稿のMarkdownはHTMLに変換する前提で書かれているので一部の表示やリンクが機能しません。 - [リリースノート][release-note] (GitHub Releases) - [README.md][readme] - [COPYING][copying] - [INSTALL][install] - [docs/README.md][docs-readme] - [test/README.md][test-readme] - [CONTRIBUTING.md][contributing] --- - [JDimについて][about] - [make、実行方法について][make] - [OS/ディストリビューション別インストール方法][jdwiki-install] (JD wiki) - [起動について][start] - [datファイルのインポート、エクスポートについて][dat] - [バックアップ、アンインストールについて][backup] - [操作方法について][operation] - [マウスジェスチャについて][mouse] - [お気に入りについて][favorite] - [外部板について][external] - [実況モードについて][live] - [ユーザーコマンド、リンクフィルタについて][usrcmd] - [アスキーアート(AA)の入力について][asciiart] - [次スレ検索について][next] - [FAQ][jdwiki-faq] (JD wiki) - [Tips][jdwiki-tips] (JD wiki) - その他 - [板移転について][move] - [ユーザーコマンド設定集][jdwiki-usrcmd] (JD wiki) - [テーマについて][skin] - [動作環境の記入について][environment] - [効果音の再生について][sound] - [URL変換について][urlreplace] [gh]: https://github.com/JDimproved/JDim/tree/JDim-v0.3.0 [release-note]: https://github.com/JDimproved/JDim/releases/tag/JDim-v0.3.0 [readme]: https://github.com/JDimproved/JDim/blob/JDim-v0.3.0/README.md [copying]: https://github.com/JDimproved/JDim/blob/JDim-v0.3.0/COPYING [install]: https://github.com/JDimproved/JDim/blob/JDim-v0.3.0/INSTALL [docs-readme]: https://github.com/JDimproved/JDim/blob/JDim-v0.3.0/docs/README.md [test-readme]: https://github.com/JDimproved/JDim/blob/JDim-v0.3.0/test/README.md [contributing]: https://github.com/JDimproved/JDim/blob/JDim-v0.3.0/CONTRIBUTING.md [about]: https://github.com/JDimproved/JDim/blob/JDim-v0.3.0/docs/manual/about.md [make]: https://github.com/JDimproved/JDim/blob/JDim-v0.3.0/docs/manual/make.md [jdwiki-install]: https://ja.osdn.net/projects/jd4linux/wiki/OS%2F%E3%83%87%E3%82%A3%E3%82%B9%E3%83%88%E3%83%AA%E3%83%93%E3%83%A5%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E5%88%A5%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB%E6%96%B9%E6%B3%95 [start]: https://github.com/JDimproved/JDim/blob/JDim-v0.3.0/docs/manual/start.md [dat]: https://github.com/JDimproved/JDim/blob/JDim-v0.3.0/docs/manual/dat.md [backup]: https://github.com/JDimproved/JDim/blob/JDim-v0.3.0/docs/manual/backup.md [operation]: https://github.com/JDimproved/JDim/blob/JDim-v0.3.0/docs/manual/operation.md [mouse]: https://github.com/JDimproved/JDim/blob/JDim-v0.3.0/docs/manual/mouse.md [favorite]: https://github.com/JDimproved/JDim/blob/JDim-v0.3.0/docs/manual/favorite.md [external]: https://github.com/JDimproved/JDim/blob/JDim-v0.3.0/docs/manual/external.md [live]: https://github.com/JDimproved/JDim/blob/JDim-v0.3.0/docs/manual/live.md [usrcmd]: https://github.com/JDimproved/JDim/blob/JDim-v0.3.0/docs/manual/usrcmd.md [asciiart]: https://github.com/JDimproved/JDim/blob/JDim-v0.3.0/docs/manual/asciiart.md [next]: https://github.com/JDimproved/JDim/blob/JDim-v0.3.0/docs/manual/next.md [jdwiki-faq]: https://ja.osdn.net/projects/jd4linux/wiki/FAQ [jdwiki-tips]: https://ja.osdn.net/projects/jd4linux/wiki/Tips [move]: https://github.com/JDimproved/JDim/blob/JDim-v0.3.0/docs/manual/move.md [jdwiki-usrcmd]: https://ja.osdn.net/projects/jd4linux/wiki/%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E8%A8%AD%E5%AE%9A%E9%9B%86 [skin]: https://github.com/JDimproved/JDim/blob/JDim-v0.3.0/docs/manual/skin.md [environment]: https://github.com/JDimproved/JDim/blob/JDim-v0.3.0/docs/manual/environment.md [sound]: https://github.com/JDimproved/JDim/blob/JDim-v0.3.0/docs/manual/sound.md [urlreplace]: https://github.com/JDimproved/JDim/blob/JDim-v0.3.0/docs/manual/urlreplace.md jdim-0.7.0/docs/manual/link-20200718.md000066400000000000000000000104331417047150700170350ustar00rootroot00000000000000--- title: JDim 0.4.0-20200718 layout: default --- > [Top](../) > 前のバージョンのマニュアル (GitHubリンク) > {{ page.title }} ## 前のバージョンのマニュアル ( {{ page.title }} ) 括弧書きのないリンクは[GitHubリポジトリ][gh]のページです。
**注意**: 原稿のMarkdownはHTMLに変換する前提で書かれているので一部の表示やリンクが機能しません。 - [リリースノート][release-note] (GitHub Releases) - [README.md][readme] - [COPYING][copying] - [INSTALL][install] - [docs/README.md][docs-readme] - [test/README.md][test-readme] - [CONTRIBUTING.md][contributing] --- - [JDimについて][about] - [make、実行方法について][make] - [OS/ディストリビューション別インストール方法][jdwiki-install] (JD wiki) - [起動について][start] - [datファイルのインポート、エクスポートについて][dat] - [バックアップ、アンインストールについて][backup] - [操作方法について][operation] - [マウスジェスチャについて][mouse] - [お気に入りについて][favorite] - [外部板について][external] - [実況モードについて][live] - [ユーザーコマンド、リンクフィルタについて][usrcmd] - [アスキーアート(AA)の入力について][asciiart] - [次スレ検索について][next] - [FAQ][jdwiki-faq] (JD wiki) - [Tips][jdwiki-tips] (JD wiki) - その他 - [板移転について][move] - [ユーザーコマンド設定集][jdwiki-usrcmd] (JD wiki) - [テーマについて][skin] - [動作環境の記入について][environment] - [効果音の再生について][sound] - [URL変換について][urlreplace] [gh]: https://github.com/JDimproved/JDim/tree/JDim-v0.4.0 [release-note]: https://github.com/JDimproved/JDim/releases/tag/JDim-v0.4.0 [readme]: https://github.com/JDimproved/JDim/blob/JDim-v0.4.0/README.md [copying]: https://github.com/JDimproved/JDim/blob/JDim-v0.4.0/COPYING [install]: https://github.com/JDimproved/JDim/blob/JDim-v0.4.0/INSTALL [docs-readme]: https://github.com/JDimproved/JDim/blob/JDim-v0.4.0/docs/README.md [test-readme]: https://github.com/JDimproved/JDim/blob/JDim-v0.4.0/test/README.md [contributing]: https://github.com/JDimproved/JDim/blob/JDim-v0.4.0/CONTRIBUTING.md [about]: https://github.com/JDimproved/JDim/blob/JDim-v0.4.0/docs/manual/about.md [make]: https://github.com/JDimproved/JDim/blob/JDim-v0.4.0/docs/manual/make.md [jdwiki-install]: https://ja.osdn.net/projects/jd4linux/wiki/OS%2F%E3%83%87%E3%82%A3%E3%82%B9%E3%83%88%E3%83%AA%E3%83%93%E3%83%A5%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E5%88%A5%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB%E6%96%B9%E6%B3%95 [start]: https://github.com/JDimproved/JDim/blob/JDim-v0.4.0/docs/manual/start.md [dat]: https://github.com/JDimproved/JDim/blob/JDim-v0.4.0/docs/manual/dat.md [backup]: https://github.com/JDimproved/JDim/blob/JDim-v0.4.0/docs/manual/backup.md [operation]: https://github.com/JDimproved/JDim/blob/JDim-v0.4.0/docs/manual/operation.md [mouse]: https://github.com/JDimproved/JDim/blob/JDim-v0.4.0/docs/manual/mouse.md [favorite]: https://github.com/JDimproved/JDim/blob/JDim-v0.4.0/docs/manual/favorite.md [external]: https://github.com/JDimproved/JDim/blob/JDim-v0.4.0/docs/manual/external.md [live]: https://github.com/JDimproved/JDim/blob/JDim-v0.4.0/docs/manual/live.md [usrcmd]: https://github.com/JDimproved/JDim/blob/JDim-v0.4.0/docs/manual/usrcmd.md [asciiart]: https://github.com/JDimproved/JDim/blob/JDim-v0.4.0/docs/manual/asciiart.md [next]: https://github.com/JDimproved/JDim/blob/JDim-v0.4.0/docs/manual/next.md [jdwiki-faq]: https://ja.osdn.net/projects/jd4linux/wiki/FAQ [jdwiki-tips]: https://ja.osdn.net/projects/jd4linux/wiki/Tips [move]: https://github.com/JDimproved/JDim/blob/JDim-v0.4.0/docs/manual/move.md [jdwiki-usrcmd]: https://ja.osdn.net/projects/jd4linux/wiki/%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E8%A8%AD%E5%AE%9A%E9%9B%86 [skin]: https://github.com/JDimproved/JDim/blob/JDim-v0.4.0/docs/manual/skin.md [environment]: https://github.com/JDimproved/JDim/blob/JDim-v0.4.0/docs/manual/environment.md [sound]: https://github.com/JDimproved/JDim/blob/JDim-v0.4.0/docs/manual/sound.md [urlreplace]: https://github.com/JDimproved/JDim/blob/JDim-v0.4.0/docs/manual/urlreplace.md jdim-0.7.0/docs/manual/link-20210109.md000066400000000000000000000106561417047150700170370ustar00rootroot00000000000000--- title: JDim 0.5.0-20210109 layout: default --- > [Top](../) > 前のバージョンのマニュアル (GitHubリンク) > {{ page.title }} ## 前のバージョンのマニュアル ( {{ page.title }} ) 括弧書きのないリンクは[GitHubリポジトリ][gh]のページです。
**注意**: 原稿のMarkdownはHTMLに変換する前提で書かれているので一部の表示やリンクが機能しません。 - [リリースノート][release-note] (GitHub Releases) - [README.md][readme] - [COPYING][copying] - [INSTALL][install] - [docs/README.md][docs-readme] - [test/README.md][test-readme] - [CONTRIBUTING.md][contributing] --- - [JDimについて][about] - [make、実行方法について][make] - [OS/ディストリビューション別インストール方法][jdwiki-install] (JD wiki) - [起動について][start] - [datファイルのインポート、エクスポートについて][dat] - [バックアップ、アンインストールについて][backup] - [操作方法について][operation] - [マウスジェスチャについて][mouse] - [お気に入りについて][favorite] - [外部板について][external] - [実況モードについて][live] - [ユーザーコマンド、リンクフィルタについて][usrcmd] - [アスキーアート(AA)の入力について][asciiart] - [次スレ検索について][next] - [FAQ][jdwiki-faq] (JD wiki) - [Tips][jdwiki-tips] (JD wiki) - その他 - [板移転について][move] - [ユーザーコマンド設定集][jdwiki-usrcmd] (JD wiki) - [テーマについて][skin] - [動作環境の記入について][environment] - [効果音の再生について][sound] - [URL変換について][urlreplace] - [置換文字列の設定(ReplaceStr)][replacestr] [gh]: https://github.com/JDimproved/JDim/tree/JDim-v0.5.0 [release-note]: https://github.com/JDimproved/JDim/releases/tag/JDim-v0.5.0 [readme]: https://github.com/JDimproved/JDim/blob/JDim-v0.5.0/README.md [copying]: https://github.com/JDimproved/JDim/blob/JDim-v0.5.0/COPYING [install]: https://github.com/JDimproved/JDim/blob/JDim-v0.5.0/INSTALL [docs-readme]: https://github.com/JDimproved/JDim/blob/JDim-v0.5.0/docs/README.md [test-readme]: https://github.com/JDimproved/JDim/blob/JDim-v0.5.0/test/README.md [contributing]: https://github.com/JDimproved/JDim/blob/JDim-v0.5.0/CONTRIBUTING.md [about]: https://github.com/JDimproved/JDim/blob/JDim-v0.5.0/docs/manual/about.md [make]: https://github.com/JDimproved/JDim/blob/JDim-v0.5.0/docs/manual/make.md [jdwiki-install]: https://ja.osdn.net/projects/jd4linux/wiki/OS%2F%E3%83%87%E3%82%A3%E3%82%B9%E3%83%88%E3%83%AA%E3%83%93%E3%83%A5%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E5%88%A5%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB%E6%96%B9%E6%B3%95 [start]: https://github.com/JDimproved/JDim/blob/JDim-v0.5.0/docs/manual/start.md [dat]: https://github.com/JDimproved/JDim/blob/JDim-v0.5.0/docs/manual/dat.md [backup]: https://github.com/JDimproved/JDim/blob/JDim-v0.5.0/docs/manual/backup.md [operation]: https://github.com/JDimproved/JDim/blob/JDim-v0.5.0/docs/manual/operation.md [mouse]: https://github.com/JDimproved/JDim/blob/JDim-v0.5.0/docs/manual/mouse.md [favorite]: https://github.com/JDimproved/JDim/blob/JDim-v0.5.0/docs/manual/favorite.md [external]: https://github.com/JDimproved/JDim/blob/JDim-v0.5.0/docs/manual/external.md [live]: https://github.com/JDimproved/JDim/blob/JDim-v0.5.0/docs/manual/live.md [usrcmd]: https://github.com/JDimproved/JDim/blob/JDim-v0.5.0/docs/manual/usrcmd.md [asciiart]: https://github.com/JDimproved/JDim/blob/JDim-v0.5.0/docs/manual/asciiart.md [next]: https://github.com/JDimproved/JDim/blob/JDim-v0.5.0/docs/manual/next.md [jdwiki-faq]: https://ja.osdn.net/projects/jd4linux/wiki/FAQ [jdwiki-tips]: https://ja.osdn.net/projects/jd4linux/wiki/Tips [move]: https://github.com/JDimproved/JDim/blob/JDim-v0.5.0/docs/manual/move.md [jdwiki-usrcmd]: https://ja.osdn.net/projects/jd4linux/wiki/%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E8%A8%AD%E5%AE%9A%E9%9B%86 [skin]: https://github.com/JDimproved/JDim/blob/JDim-v0.5.0/docs/manual/skin.md [environment]: https://github.com/JDimproved/JDim/blob/JDim-v0.5.0/docs/manual/environment.md [sound]: https://github.com/JDimproved/JDim/blob/JDim-v0.5.0/docs/manual/sound.md [urlreplace]: https://github.com/JDimproved/JDim/blob/JDim-v0.5.0/docs/manual/urlreplace.md [replacestr]: https://github.com/JDimproved/JDim/blob/JDim-v0.5.0/docs/manual/replacestr.md jdim-0.7.0/docs/manual/link-20210710.md000066400000000000000000000103671417047150700170340ustar00rootroot00000000000000--- title: JDim 0.6.0-20210710 layout: default --- > [Top](../) > 前のバージョンのマニュアル (GitHubリンク) > {{ page.title }} ## 前のバージョンのマニュアル ( {{ page.title }} ) 括弧書きのないリンクは[GitHubリポジトリ][gh]のページです。
**注意**: 原稿のMarkdownはHTMLに変換する前提で書かれているので一部の表示やリンクが機能しません。 - [リリースノート][release-note] (GitHub Releases) - [README.md][readme] - [COPYING][copying] - [INSTALL][install] - [docs/README.md][docs-readme] - [test/README.md][test-readme] - [CONTRIBUTING.md][contributing] --- - [JDimについて][about] - [make、実行方法について][make] - [OS/ディストリビューション別インストール方法][dis592] (GitHub discussions) - [起動について][start] - [datファイルのインポート、エクスポートについて][dat] - [バックアップ、アンインストールについて][backup] - [操作方法について][operation] - [マウスジェスチャについて][mouse] - [お気に入りについて][favorite] - [外部板について][external] - [実況モードについて][live] - [ユーザーコマンド、リンクフィルタについて][usrcmd] - [アスキーアート(AA)の入力について][asciiart] - [次スレ検索について][next] - [FAQ][jdwiki-faq] (JD wiki) - [Tips][jdwiki-tips] (JD wiki) - その他 - [板移転について][move] - [ユーザーコマンド設定集][jdwiki-usrcmd] (JD wiki) - [テーマについて][skin] - [動作環境の記入について][environment] - [効果音の再生について][sound] - [URL変換について][urlreplace] - [置換文字列の設定(ReplaceStr)][replacestr] [gh]: https://github.com/JDimproved/JDim/tree/JDim-v0.6.0 [release-note]: https://github.com/JDimproved/JDim/releases/tag/JDim-v0.6.0 [readme]: https://github.com/JDimproved/JDim/blob/JDim-v0.6.0/README.md [copying]: https://github.com/JDimproved/JDim/blob/JDim-v0.6.0/COPYING [install]: https://github.com/JDimproved/JDim/blob/JDim-v0.6.0/INSTALL [docs-readme]: https://github.com/JDimproved/JDim/blob/JDim-v0.6.0/docs/README.md [test-readme]: https://github.com/JDimproved/JDim/blob/JDim-v0.6.0/test/README.md [contributing]: https://github.com/JDimproved/JDim/blob/JDim-v0.6.0/CONTRIBUTING.md [about]: https://github.com/JDimproved/JDim/blob/JDim-v0.6.0/docs/manual/about.md [make]: https://github.com/JDimproved/JDim/blob/JDim-v0.6.0/docs/manual/make.md [dis592]: https://github.com/JDimproved/JDim/discussions/592 [start]: https://github.com/JDimproved/JDim/blob/JDim-v0.6.0/docs/manual/start.md [dat]: https://github.com/JDimproved/JDim/blob/JDim-v0.6.0/docs/manual/dat.md [backup]: https://github.com/JDimproved/JDim/blob/JDim-v0.6.0/docs/manual/backup.md [operation]: https://github.com/JDimproved/JDim/blob/JDim-v0.6.0/docs/manual/operation.md [mouse]: https://github.com/JDimproved/JDim/blob/JDim-v0.6.0/docs/manual/mouse.md [favorite]: https://github.com/JDimproved/JDim/blob/JDim-v0.6.0/docs/manual/favorite.md [external]: https://github.com/JDimproved/JDim/blob/JDim-v0.6.0/docs/manual/external.md [live]: https://github.com/JDimproved/JDim/blob/JDim-v0.6.0/docs/manual/live.md [usrcmd]: https://github.com/JDimproved/JDim/blob/JDim-v0.6.0/docs/manual/usrcmd.md [asciiart]: https://github.com/JDimproved/JDim/blob/JDim-v0.6.0/docs/manual/asciiart.md [next]: https://github.com/JDimproved/JDim/blob/JDim-v0.6.0/docs/manual/next.md [jdwiki-faq]: https://ja.osdn.net/projects/jd4linux/wiki/FAQ [jdwiki-tips]: https://ja.osdn.net/projects/jd4linux/wiki/Tips [move]: https://github.com/JDimproved/JDim/blob/JDim-v0.6.0/docs/manual/move.md [jdwiki-usrcmd]: https://ja.osdn.net/projects/jd4linux/wiki/%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E8%A8%AD%E5%AE%9A%E9%9B%86 [skin]: https://github.com/JDimproved/JDim/blob/JDim-v0.6.0/docs/manual/skin.md [environment]: https://github.com/JDimproved/JDim/blob/JDim-v0.6.0/docs/manual/environment.md [sound]: https://github.com/JDimproved/JDim/blob/JDim-v0.6.0/docs/manual/sound.md [urlreplace]: https://github.com/JDimproved/JDim/blob/JDim-v0.6.0/docs/manual/urlreplace.md [replacestr]: https://github.com/JDimproved/JDim/blob/JDim-v0.6.0/docs/manual/replacestr.md jdim-0.7.0/docs/manual/link-20220115.md000066400000000000000000000103671417047150700170340ustar00rootroot00000000000000--- title: JDim 0.7.0-20220115 layout: default --- > [Top](../) > 前のバージョンのマニュアル (GitHubリンク) > {{ page.title }} ## 前のバージョンのマニュアル ( {{ page.title }} ) 括弧書きのないリンクは[GitHubリポジトリ][gh]のページです。
**注意**: 原稿のMarkdownはHTMLに変換する前提で書かれているので一部の表示やリンクが機能しません。 - [リリースノート][release-note] (GitHub Releases) - [README.md][readme] - [COPYING][copying] - [INSTALL][install] - [docs/README.md][docs-readme] - [test/README.md][test-readme] - [CONTRIBUTING.md][contributing] --- - [JDimについて][about] - [make、実行方法について][make] - [OS/ディストリビューション別インストール方法][dis592] (GitHub discussions) - [起動について][start] - [datファイルのインポート、エクスポートについて][dat] - [バックアップ、アンインストールについて][backup] - [操作方法について][operation] - [マウスジェスチャについて][mouse] - [お気に入りについて][favorite] - [外部板について][external] - [実況モードについて][live] - [ユーザーコマンド、リンクフィルタについて][usrcmd] - [アスキーアート(AA)の入力について][asciiart] - [次スレ検索について][next] - [FAQ][jdwiki-faq] (JD wiki) - [Tips][jdwiki-tips] (JD wiki) - その他 - [板移転について][move] - [ユーザーコマンド設定集][jdwiki-usrcmd] (JD wiki) - [テーマについて][skin] - [動作環境の記入について][environment] - [効果音の再生について][sound] - [URL変換について][urlreplace] - [置換文字列の設定(ReplaceStr)][replacestr] [gh]: https://github.com/JDimproved/JDim/tree/JDim-v0.7.0 [release-note]: https://github.com/JDimproved/JDim/releases/tag/JDim-v0.7.0 [readme]: https://github.com/JDimproved/JDim/blob/JDim-v0.7.0/README.md [copying]: https://github.com/JDimproved/JDim/blob/JDim-v0.7.0/COPYING [install]: https://github.com/JDimproved/JDim/blob/JDim-v0.7.0/INSTALL [docs-readme]: https://github.com/JDimproved/JDim/blob/JDim-v0.7.0/docs/README.md [test-readme]: https://github.com/JDimproved/JDim/blob/JDim-v0.7.0/test/README.md [contributing]: https://github.com/JDimproved/JDim/blob/JDim-v0.7.0/CONTRIBUTING.md [about]: https://github.com/JDimproved/JDim/blob/JDim-v0.7.0/docs/manual/about.md [make]: https://github.com/JDimproved/JDim/blob/JDim-v0.7.0/docs/manual/make.md [dis592]: https://github.com/JDimproved/JDim/discussions/592 [start]: https://github.com/JDimproved/JDim/blob/JDim-v0.7.0/docs/manual/start.md [dat]: https://github.com/JDimproved/JDim/blob/JDim-v0.7.0/docs/manual/dat.md [backup]: https://github.com/JDimproved/JDim/blob/JDim-v0.7.0/docs/manual/backup.md [operation]: https://github.com/JDimproved/JDim/blob/JDim-v0.7.0/docs/manual/operation.md [mouse]: https://github.com/JDimproved/JDim/blob/JDim-v0.7.0/docs/manual/mouse.md [favorite]: https://github.com/JDimproved/JDim/blob/JDim-v0.7.0/docs/manual/favorite.md [external]: https://github.com/JDimproved/JDim/blob/JDim-v0.7.0/docs/manual/external.md [live]: https://github.com/JDimproved/JDim/blob/JDim-v0.7.0/docs/manual/live.md [usrcmd]: https://github.com/JDimproved/JDim/blob/JDim-v0.7.0/docs/manual/usrcmd.md [asciiart]: https://github.com/JDimproved/JDim/blob/JDim-v0.7.0/docs/manual/asciiart.md [next]: https://github.com/JDimproved/JDim/blob/JDim-v0.7.0/docs/manual/next.md [jdwiki-faq]: https://ja.osdn.net/projects/jd4linux/wiki/FAQ [jdwiki-tips]: https://ja.osdn.net/projects/jd4linux/wiki/Tips [move]: https://github.com/JDimproved/JDim/blob/JDim-v0.7.0/docs/manual/move.md [jdwiki-usrcmd]: https://ja.osdn.net/projects/jd4linux/wiki/%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E8%A8%AD%E5%AE%9A%E9%9B%86 [skin]: https://github.com/JDimproved/JDim/blob/JDim-v0.7.0/docs/manual/skin.md [environment]: https://github.com/JDimproved/JDim/blob/JDim-v0.7.0/docs/manual/environment.md [sound]: https://github.com/JDimproved/JDim/blob/JDim-v0.7.0/docs/manual/sound.md [urlreplace]: https://github.com/JDimproved/JDim/blob/JDim-v0.7.0/docs/manual/urlreplace.md [replacestr]: https://github.com/JDimproved/JDim/blob/JDim-v0.7.0/docs/manual/replacestr.md jdim-0.7.0/docs/manual/live.md000066400000000000000000000053501417047150700161400ustar00rootroot00000000000000--- title: 実況モードについて layout: default --- > [Top](../) > {{ page.title }} ## {{ page.title }} - [開始と停止方法](#start_stop) - [更新間隔](#interval) - [オートスクロール](#auto_scroll) - [書き込みビューについて](#message_view) - [実況終了スレの自動削除](#auto_remove) ### 開始と停止方法 デフォルトでは実況モードは使用出来ないようになっている。 実況したい板のプロパティを開いて「`実況する`」をチェックすることで実況可能になる。 実況可能にしたら、ツールメニューから実況サブメニューを選択するか、スレビュー上でF6キーを押すか、 表示設定からスレビューのツールバーに実況ボタンを表示させてから実況ボタンをクリックすると実況が始まる。 再び実況サブメニューを選択するか、F6キーを押すか、実況ボタンをクリックするか、 スレビューの適当な場所をクリックすると停止する。 ### 更新間隔 更新間隔は各板別に板のプロパティで設定する。最低更新間隔は **10** 秒で、更新が無い場合は間隔が5秒ずつ増えて行く。 5回更新が無いときは停止する。 ### オートスクロール オートスクロールの速度などは全板共通でメニューの「`設定`」→「`その他`」→「`実況設定`」から設定する。 スクロールモードには「`スクロール速度を実況速度に合わせて可変させるモード`」 「`常に一定速度でスクロールさせるモード`」の二通りある。 スクロール速度/実況遅れが設定したしきい値を越えると行単位でのスクロールに切り替わる。 マウスオンなどでポップアップを表示している間は、オートスクロールを一時停止する。 ### 書き込みビューについて 書き込みビューが表示されているときは書き込みビューが常にフォーカスされる。 書き込みビューを埋め込み表示にして「`書き込み後に閉じない`」ボタンを押して使うと実況向けとなる。 ### 実況終了スレの自動削除 実況状態のまま閉じたスレはJDim終了時に自動でログを削除する。 削除をキャンセルするには実況を停止してからスレを閉じるか、もう一度スレを開く(ただし実況モードにはしない)。 なお、スレ一覧でしおりをつけたスレや、スレビューで任意のレスにひとつでもしおりをつけたスレは削除しない。 jdim-0.7.0/docs/manual/make.md000066400000000000000000000155021417047150700161160ustar00rootroot00000000000000--- title: make、実行方法について layout: default --- > [Top](../) > {{ page.title }} ## {{ page.title }} - [動作環境](#environment) - [makeに必要なツール、ライブラリ](#requirement) - [ビルド方法( configure + make の場合 )](#make-configure) - [ビルド方法( meson の場合 )](#build-meson) - [configureオプション](#configure-option) - [メモ](#memo) ### 動作環境 #### 必須環境 - gtkmm 3.22.0 以上 - glibmm 2.50.0 以上 ( 2.56.0 未満のサポートは将来のリリースで廃止される ) - zlib 1.2 以上 - gnutls 3.5.8 以上 ( 3.5.18 未満のサポートは将来のリリースで廃止される ) #### 推奨環境 - Linux Kernel 3.10 以上 ( 4.4.0 未満は将来のリリースで推奨環境から外れる ) - gtkmm 3.24.0 以上 - UTF-8環境 ( EUC環境では `LANG="ja_JP.UTF-8"` を指定する必要がある ) ※ GTK2版はv0.4.0リリースをもって廃止 ( を参照 ) ### makeに必要なツール、ライブラリ #### 必須 - autoconf - autoconf-archive - automake - g++ 6 以上、または clang++ 5.0 以上 ( 将来のリリースで g++ 7 以上、または clang++ 6.0 以上になる ) - gnutls - gtkmm - libtool - make - zlib #### オプション - meson 0.49.0 以上 - alsa-lib (`--with-alsa`) - openssl (`--with-tls=openssl`, バージョン 1.1.0 未満のサポートは将来のリリースで廃止される ) - oniguruma (`--with-regex=oniguruma`, 廃止予定) - migemo (`--with-migemo`) - googletest ([test/RADME.md][testreadme]を参照) #### 画像表示に必要なパッケージ インストールされていない環境では`.webp`や`.avif`で終わるURLは通常リンクになる。 - libwebp, webp-pixbuf-loader (WebP) - libavif (AVIF) OSやディストリビューション別の解説は [#592][dis592] を参照。 configure のかわりに [meson] を使ってビルドする方法は [#556][dis556] を参照。 (v0.4.0+から暫定サポート) WebPやAVIF形式の画像を表示する方法は [#737][dis737] を参照。 (v0.5.0+からサポート) ### ビルド方法( configure + make の場合 ) 1. `autoreconf -i` ( 又は `./autogen.sh` ) 2. `./configure` 3. `make` 4. (お好みで) `strip src/jdim` ### ビルド方法( meson の場合 ) 1. `meson builddir` 2. `meson compile -C builddir` ( 又は `ninja -C builddir` ) 3. 起動は `./builddir/src/jdim` #### mesonのビルドオプション - `meson builddir -Dregex=glib` のように指定する。 - オプションの一覧は `meson configure` を実行してProject optionsの段落を参照する。 ### configureオプション
--with-sessionlib=xsmp|no
XSMP を使ってセッション管理をするには xsmp を、 セッション管理を無効にするには no を選択する。デフォルトでは XSMP を使用する。
--with-pangolayout
描画に PangoLayout を使う。デフォルトでは PangoGlyphString を使用する。 スレビューのテキスト表示に問題があるときはこのオプションを試してみてください。
--with-migemo
migemo による検索が有効になる。migemoがUTF-8の辞書でインストールされている必要がある。 有効にすると正規表現のメタ文字が期待通りに動作しない場合があるので注意すること。
--with-migemodict=PATH
(--with-migemo 限定) migemo の辞書ファイルの場所を設定する。 about:config で変更が可能、空欄にした場合は migemo が無効になる。(変更後は要再起動)
--with-native
CPUに合わせた最適化。CPUを指定する場合は ./configure CXXFLAGS="-march=ARCH" を利用する。
--with-tls=[gnutls|openssl]
使用するSSL/TLSライブラリを設定する。デフォルトでは GnuTLS を使用する。
--with-tls=openssl
GnuTLS のかわりに OpenSSL を使用する。ライセンス上バイナリ配布が出来なくなることに注意すること。
--with-alsa
ALSA による効果音再生機能を有効にする。 詳しくは効果音の再生の項を参照すること。
--enable-gprof
gprof によるプロファイリングを行う。 コンパイルオプションに -pg が付き、JDimを実行すると gmon.out が出来るので gprof ./jdim gmon.out で解析できる。CPUの最適化は効かなくなるので注意する。
--with-regex=oniguruma|glib
使用する正規表現ライブラリを設定する。 デフォルトでは Glib Regex(GRegex) を使用する。(v0.4.0+から変更)
非推奨: 正規表現ライブラリを選択するオプションは将来廃止される。 #697 を参照すること。
--with-regex=oniguruma
非推奨: GRegex のかわりに鬼車を使用する。 鬼車はBSDライセンスなのでJDimをバイナリ配布する場合には注意すること(ライセンスはGPLになる)。
--with-regex=glib
Perl互換の正規表現なので、従来の POSIX 拡張の正規表現から設定変更が必要になる場合がある。 (v0.3.0+から追加)
--disable-compat-cache-dir
JDのキャッシュディレクトリ ~/.jd を読み込む互換機能を無効化する。
### メモ 最近のディストリビューションの場合は `autogen.sh` よりも `autoreconf -i` の方を推奨。 実行するには直接 `src/jdim` を起動するか手動で `/usr/bin` あたりに `src/jdim` を `cp` する。 以上の操作でmakeが通らなかったり動作が変な時は configure のオプションを変更する。 [testreadme]: https://github.com/JDimproved/JDim/blob/master/test/README.md [meson]: https://mesonbuild.com [dis556]: https://github.com/JDimproved/JDim/discussions/556 "Mesonを使ってJDimをビルドする方法 - Discussions #556" [dis592]: https://github.com/JDimproved/JDim/discussions/592 "OS/ディストリビューション別インストール方法 - Discussions #592" [dis737]: https://github.com/JDimproved/JDim/discussions/737 "[v0.5.0+] WebPやAVIF形式の画像を表示する方法 - Discussions #737" jdim-0.7.0/docs/manual/mouse.md000066400000000000000000000031721417047150700163310ustar00rootroot00000000000000--- title: マウスジェスチャについて layout: default --- > [Top](../) > {{ page.title }} ## {{ page.title }} ### 操作 マウスの右ボタンを押しながら下のジェスチャを入力しボタンを放すとコマンドが実行される。 | ジェスチャ | 操作 | | --- | --- | | 🠄 | 前のビューに戻る | | 🠆 | 次のビューに進む | | 🠇🠆 | 閉じる | | 🠇🠆🠇 | 削除 | | 🠅🠇 | 再読込 | | 🠅 | ロード停止(Escape) | | 🠇 | スレ一覧ビュー ⟷ スレビュー切替 (2ペーンモード時) | | 🠅🠄 | 左のタブへ移動 | | 🠅🠆 | 右のタブへ移動 | | 🠆🠅 | Home | | 🠆🠇 | End | | 🠆🠇🠆 | 新着へ移動 | | 🠇🠅 | [次スレ検索]({{ site.baseurl }}/next/) (スレビュー) | | 🠇🠄 | 書き込み | | 🠇🠅 | 画像のモザイク解除 (画像ビュー) | | 🠄🠅 | サイドバーの更新チェック | | 🠄🠇 | サイドバーを更新チェックしてタブで開く | また、右ボタンを押しながらマウスホイールを回転させるとタブを切り替える。 マウスジェスチャの割り当ては`設定`メニュー → `マウス/キーボード` → `マウスジェスチャ詳細設定`から変更できる。 なお、マウスジェスチャ設定の最大ストロークは5で、判定開始半径を変えるには `about:config` の「`マウスジェスチャの判定開始半径`」の値を変更する jdim-0.7.0/docs/manual/move.md000066400000000000000000000013441417047150700161460ustar00rootroot00000000000000--- title: 板移転について layout: default --- > [Top](../) > [その他]({{ site.baseurl }}/info/) > {{ page.title }} ## {{ page.title }} ### 移転処理 「`ファイル`」メニューから板リストを再読込したときに移転した板があるときは自動で移転処理する。 なお移転処理をしたあとは一度JDimを再起動することを推奨する。 移転情報テーブルはキャッシュディレクトリ内の「`move.info`」ファイルに記録されるので もしテーブルが壊れたときは手動で修正すること。 #### move.infoの形式 ``` 旧サーバー 新サーバー /板ID ``` #### 実際の例 ``` http://pc2.2ch.net http://pc8.2ch.net /linux ``` jdim-0.7.0/docs/manual/next.md000066400000000000000000000023721417047150700161600ustar00rootroot00000000000000--- title: 次スレ検索について layout: default --- > [Top](../) > {{ page.title }} ## {{ page.title }} ### 使い方 スレビューで右クリックしてコンテキストメニューを表示し、 `検索`→`次スレ検索`でスレタイトルの類似度を計算して次スレ候補の一覧を表示する。 候補一覧からスレを開くと、あぼーん情報や書き込み時の名前、メールなどの情報が次スレに引き継がれる。 ただしNGIDは1日ごとにIDがリセットされる事から引き継がないので注意。 ショートカットはスレビュー上で Ctrl+Space、マウスジェスチャは🠇🠅 また、次スレ誘導リンクをクリックしてスレを開いたときも、 タイトルの類似度を計算して次スレかどうかを判断し、似ていればスレ情報が引き継がれる。 また、新スレを立てたときや、スレタイトルが変わって次スレ検索に失敗した時などには 新スレの`>>1`に貼り付けてある前スレへの誘導アドレス上で右クリックしてコンテキストメニューを表示し、 `その他`→`スレ情報を引き継ぐ`で前スレの情報を引き継ぐことが出来る。 jdim-0.7.0/docs/manual/operation.md000066400000000000000000000726721417047150700172140ustar00rootroot00000000000000--- title: 操作方法について layout: default --- > [Top](../) > {{ page.title }} ## {{ page.title }} - [共通の操作](#common) - [サイドバー(板一覧、外部板、お気に入り、各履歴)の操作](#sidebar) - [スレ一覧の操作](#threadlist) - [スレビューの操作](#threadview) - [書き込みビューの操作](#messageview) - [画像ビューの操作](#imageview) - [2ペーン表示時のスレ一覧とスレビュー の切り替え方法](#2pane) - [ログ検索](#searchlog) - [スレタイ検索](#searchtitle) - [更新チェック](#checkupdate) - [カスタマイズ方法](#customize) ### 共通の操作 #### タブ操作
タブをドラッグ → タブにドロップ
タブの入れ替え
タブをドラッグ → お気に入りにドロップ
お気に入り登録
タブをダブルクリック
タブの再読み込み
タブを中クリック
タブを閉じる
Q, Ctrl+W
タブを閉じる
Ctrl+L, Ctrl+🡲, Ctrl+PageDown, Ctrl+Tab
右のタブへ移動
Ctrl+H, Ctrl+🡰, Ctrl+PageUp, Ctrl+Shift+Tab
左のタブへ移動
Ctrl+Shift+L, Ctrl+Shift+🡲, Ctrl+Shift+PageDown, ]
右の更新済みタブへ移動
Ctrl+Shift+H, Ctrl+Shift+🡰, Ctrl+Shift+PageUp, [
左の更新済みタブへ移動
Alt+1 〜 Alt+9
左からn番目(1〜9)のタブへ移動
Alt+🡰
同じタブで直前に開いていた内容に戻る
Alt+🡲
Alt+🡰で戻る前に開いていた内容に進む
Ctrl+Shift+T
最後に閉じたタブを復元
#### 基本操作
L, 🡲
次のビューに進む(例えばスレ一覧ならスレビューに進む)
H, 🡰
前のビューに戻る(例えばスレビューならスレ一覧に戻る)
Shift+F10, Ctrl+M, Menu
ポップアップメニュー表示
F8
メニューバー表示切り替え
(デフォルト無し)
メインツールバー表示切り替え
F9
サイドバー表示切り替え
Ctrl+O
URLを開く
Ctrl+C
コピー
Ctrl+A
全て選択
Ctrl+D
お気に入りに追加
S, F5, マウスのボタン4
再読み込み
Esc
読み込み中止
Ctrl+T
スレタイ検索画面を開く
Ctrl+Shift+P
表示中のビューのプロパティを開く
(デフォルト無し)
サイドバー更新チェック
(デフォルト無し)
サイドバー更新チェックして更新された板やスレを開く
#### スレ一覧、スレビュー内での検索操作 スレ一覧、スレビューの検索やキーワード抽出ではデフォルトで正規表現による検索が有効になっているので、 (や\*を検索する場合は前に\を付ける必要がある。 スレ一覧、スレビューの検索やキーワード抽出では英数字やカナの全角半角を区別しない。 スレビューの検索では単語をスペースで分けて検索すると全ての単語を検索する。
Ctrl+F, /
検索ボックスにフォーカスを移して後方検索開始
?
検索ボックスにフォーカスを移して前方検索開始
検索ボックスに検索文字列を入れてEnter
検索開始
( スレビューのみ )検索ボックスに検索文字列入れてCtrl+Enter
レスの抽出
Esc
検索中止
Enter, F3, Ctrl+G
次検索
Shift+Enter, Ctrl+F3, Ctrl+Shift+G, N
前検索
### サイドバー(板一覧、外部板、お気に入り、各履歴)の操作 共通操作に加えて以下の操作をおこなえる。
ツールバー上のタイトルクリック
表示内容切り替え
ツールバー上のタイトル上でマウス回転
表示内容切り替え
サイドバーとスレ一覧/スレビューの間のしきいをクリック
サイドバーの折り畳み/展開
ディレクトリをクリック
ディレクトリを開閉する
ディレクトリ以外の行をクリック
板やスレなどを開く
ディレクトリ以外の行を中ボタンクリック
板やスレなどをタブで開く
右クリック
メニュー表示
Ctrl+クリック
複数行選択
Shift+クリック
複数行を範囲選択
中ボタンドラッグ
複数行を範囲選択
複数行選択中に任意の行をクリック
選択解除
Ctrl+L, Ctrl+🡲, Ctrl+PageDown, Ctrl+Tab
次の内容を表示
Ctrl+H, Ctrl+🡰, Ctrl+PageUp, Ctrl+Shift+Tab
前の内容を表示
J, 🡳
1行下
K, 🡱
1行上
(ディレクトリ上で) H, 🡰
ディレクトリ閉じる
(ディレクトリ上で) L, 🡲
ディレクトリを開く
PageUp
上のページに移動
PageDown
下のページに移動
{
前のディレクトリに移動
}
次のディレクトリに移動
Home, G, <
先頭へ移動
End, Shift+G, >
最後へ移動
(ディレクトリ上で) Space
ディレクトリの開閉
(ディレクトリ以外で) Space
板やスレを開く
(ディレクトリ以外で) Ctrl+Space
板やスレをタブで開く
(板一覧以外で) Delete
選択行削除
(お気に入りで編集後に) Ctrl+Z, Ctrl+/
元に戻す(Undo)
(お気に入りでUndo後に) Ctrl+Shift+Z
やり直し(Redo)
### スレ一覧の操作 #### 基本操作 共通操作に加えて以下の操作をおこなえる。
列名クリック
ソートの優先順を変更
(デフォルトのキー設定無し)
指定した列でソートする
クリック
スレを開く
中ボタンクリック
スレをタブで開く
右クリック
メニュー表示
Ctrl+クリック
複数行選択
Shift+クリック
複数行を範囲選択
中ボタンドラッグ
複数行を範囲選択
複数行選択中に任意の行をクリック
選択解除
J, 🡳
1行下
K, 🡱
1行上
PageUp
上のページに移動
PageDown
下のページに移動
Home, G, <
先頭へ移動
End, Shift+G, >
最後へ移動
Space
スレを開く
Ctrl+Space
スレをタブで開く
Delete
選択したスレのキャッシュ削除
選択スレをドラッグ → お気に入りにドロップ
選択したスレをお気に入りに登録
マウスのチルトボタン左, Shift+H, Shift+🡰
(スクロールバーが表示されているとき)左にスクロール
マウスのチルトボタン右, Shift+L, Shift+🡲
(スクロールバーが表示されているとき)左にスクロール
キーボードで数字を入力
入力した番号のスレにジャンプする
#### 表示されるマークについて マーク行の列名「!」をクリックするとマーク別にソートする。 | | マークの意味 | | --- | --- | | ![][bkmark_update] | しおりを付けたスレ(更新あり) | | ![][bkmark_broken_subject] | しおりを付けたスレ(エラー(下の注を参照)) | | ![][bkmark] | しおりを付けたスレ(更新無し) | | ![][update] | キャッシュにあるスレ(更新あり) | | ![][broken_subject] | キャッシュにあるスレ(エラー(下の注を参照)) | | ![][check] | キャッシュにあるスレ(更新無し) | | ![][newthread] | 前回スレ一覧を開いた後に立てられた新着スレ | ![][newthread_hour] | 24時間以内に立てられたスレ | ![][info] | お知らせスレ(スレッド924) | ␣ | ① キャッシュに無い通常スレ | | ␣ | ② 既読スレ(1000まで読んだスレ) | | ![][down] | キャッシュにあるDAT落ちしたスレ | (注) まちBBSなどでスレ一覧(subject.txt)にあるレス数よりも、実際にスレを取得したときのスレ数が多い場合に表示される 「!」をクリックする度に以下の順にスレ一覧の並び替えモードを切り替える。 1. ![][bkmark_update] →![][bkmark_broken_subject] →![][bkmark] →![][update] →![][broken_subject] →![][check] →![][newthread] →![][newthread_hour] →![][info] →① →② →![][down] 2. ![][bkmark_update] →![][bkmark_broken_subject] →![][bkmark] →![][newthread] →![][update] →![][broken_subject] →![][check] →![][newthread_hour] →![][info] →① →② →![][down] 3. ![][newthread] →![][newthread_hour] →![][bkmark_update] →![][bkmark_broken_subject] →![][bkmark] →![][update] →![][broken_subject] →![][check] →![][info] →① →② →![][down] 4. ![][info] →![][newthread_hour] →![][newthread] →![][check] →![][broken_subject] →![][update] →![][bkmark] →![][bkmark_broken_subject] →![][bkmark_update] →① →② →![][down] #### タブに表示されるアイコンについて | | タブアイコンの意味 | | --- | --- | | ![][board] | 通常状態 | | ![][board_update] | (更新チェックを行って) 更新可能 | | ![][board_updated] | (全てのタブの再読み込みを行って) 更新済み | #### その他 スレにしおりを付けるには右クリックメニューから「`しおりを設定/解除`」を選ぶ。 また、基本はA列でソートしたいが、A列の値が同じグループの中ではB列で ソートしたいというときはB列をクリックしてソートしてからA列をクリックする。 例えば「マークでソートするが、新着スレなどのグループ内では スレ立て時刻でソートしたい」という場合は「since」をクリックしてから「!」 をクリックする。 列幅変更の保存は、最後に閉じたスレ一覧の幅が記憶されるので他のスレ一覧を 全て閉じてからおこなうこと。 ### スレビューの操作 #### 基本の操作 共通操作に加えて以下の操作をおこなえる。
本文中の数字を範囲選択してマウスポインタをその上に移動
対応したレスをポップアップ表示
本文中の"ID:〜"を範囲選択してマウスポインタを上に移動
ID抽出してポップアップ表示
何もないところで中クリック
オートスクロール開始/解除
何もないところで右クリック
通常メニュー表示
レスアンカーの上でクリック
アンカーメニュー表示
レスアンカーの上で中クリック
対象の周辺のレスを抽出して別のタブに表示
レスアンカーの上で右クリック
アンカーメニュー表示
レス番号の上でクリック
レスメニュー表示
レス番号の上で中クリック
しおりの設定/解除
レス番号の上でCtrl+クリック
書き込みマークの設定/解除 ( v0.3.0-20200408から追加 )
レス番号の上で右クリック
参照しているレスを抽出してポップアップ表示
名前の文字の上でクリック
名前メニュー表示
名前の文字の上で中クリック
名前抽出して別のタブに表示
名前の文字の上で右クリック
名前抽出してポップアップ表示
IDの上でクリック
IDメニュー表示
IDの上で中クリック
ID抽出して別のタブに表示
IDの上で右クリック
ID抽出してポップアップ表示
リンクの上でクリック
開く
リンクの上で右クリック
通常メニュー表示
画像リンクの上でクリック
開く
画像リンクの上で中クリック, Ctrl+クリック
バックグラウンドで開く
画像リンクの上で右クリック
通常メニュー表示
(ポップアップ表示時に) Esc
ポップアップ表示を消す
Space, PageDown
1ページ下
D
半ページ下
J, 🡳
1行下
B, PageUp
1ページ上
U
半ページ上
K, 🡱
1行上
P
ひとつ前のレスに移動
N
ひとつ後のレスに移動
F2
次のしおりへ移動
Ctrl+F2
前のしおりへ移動
Shift+F2
次の書き込みに移動
Shift+Ctrl+F2
前の書き込みに移動
W, Alt+W
書き込みウィンドウ表示
Home, G, <
先頭へ移動
End, Shift+G, >
最後へ移動
F4
新着へ移動
F6
実況開始、停止
Ctrl+Space
次スレ検索
Ctrl+S
datを保存
Delete
表示しているスレのキャッシュを削除してタブを閉じる
(範囲選択してから) Ctrl+K
Web検索(google)
(範囲選択してから) Ctrl+T
スレタイ検索
(範囲選択してから) Ctrl+Enter
スレが属する板内を対象ログ検索
(デフォルト無し)
全板内を対象としたログ検索
BackSpace
アンカーなどでジャンプした時にジャンプ元のレスに戻る
数字入力
対象のレスにジャンプする
(範囲選択してから) (デフォルト無し)
選択範囲のレスをあぼ〜ん
(範囲選択してから) Ctrl+Shift+I
選択範囲の画像を開く
(範囲選択してから) (デフォルト無し)
選択範囲の画像を削除
(範囲選択してから) (デフォルト無し)
選択範囲の画像をあぼ〜ん
(IDや名前抽出したタブ上で) Shift+F5, S
元のスレを開く
#### タッチ操作 ( GTK+ 3.14以上 )
何もないところで2本指タップ
通常メニュー表示
レスアンカーの上でタップ
アンカーメニュー表示
レスアンカーの上で2本指タップ
アンカーメニュー表示
レス番号の上でタップ
レスメニュー表示
レス番号の上で2本指タップ
参照しているレスを抽出してポップアップ表示
名前の文字の上でタップ
名前メニュー表示
名前の文字の上で2本指タップ
名前抽出してポップアップ表示
IDの上でタップ
IDメニュー表示
IDの上で2本指タップ
ID抽出してポップアップ表示
リンクの上でタップ
開く
リンクの上で2本指タップ
通常メニュー表示
画像リンクの上でタップ
開く
画像リンクの上で2本指タップ
通常メニュー表示
本文の上でダブルタップ
単語を範囲選択
本文の上でドラッグ
範囲選択+スクロール
縦スワイプ
スクロール+慣性スクロール
#### タブに表示されるアイコンについて | | タブアイコンの意味 | | --- | --- | | ![][thread] | 通常状態 | | ![][thread_old] | dat落ち | | ![][thread_update] | (更新チェックを行って) 更新可能 | | ![][thread_updated] | (全てのタブの再読み込みを行って) 更新済み | #### 多段ポップアップの方法 ポップアップ表示されている時にマウスの右ボタンをクリックし、 そのままボタンを離さずにカーソルを動かすと、 ポップアップが固定されて閉じなくなるのでカーソルを他のホップアップの上に移動できる。 ポップアップの外にカーソルが出ると閉じる。 #### 画像ポップアップ上での操作
Ctrl+W, Q, Esc, クリック, 中ボタンクリック, 🠇🠆(マウスジェスチャ)
閉じる
右クリック
コンテキストメニュー表示
C, 🠇🠅(マウスジェスチャ)
画像のモザイク解除
Ctrl+S
保存
Delete
削除
※ 画像ポップアップの上にマウスポインタを移動しなくてもポップアップが表示されているならキーボード操作を行える。 ### 書き込みビューの操作 #### 基本操作
単語をダブルクリック (GTK 3.16以上)
単語を範囲選択
行をトリプルクリック (GTK 3.16以上)
行を範囲選択
Alt+A
アスキーアート入力のメニューを表示
Alt+Q
ビューを閉じる
Alt+W
書き込む
Tab
書き込みボタンにフォーカスを移す
Ctrl+Z, Ctrl+/
Undo
Ctrl+H, Ctrl+🡰, Ctrl+PageUp, Ctrl+Shift+Tab
書き込み欄とプレビューの表示切り替え
Ctrl+L, Ctrl+🡲, Ctrl+PageDown, Ctrl+Tab
書き込み欄とプレビューの表示切り替え
Alt+S
sageのON/OFF切り替え
#### emacs風の操作 設定メニューのキーボード設定からキーボード操作をemacs風にすることが出来る。
Ctrl+P, 🡱
カーソルを上に移動
Ctrl+N, 🡳
カーソルを下に移動
Ctrl+F, 🡲
カーソルを右に移動
Ctrl+B, 🡰
カーソルを左に移動
Ctrl+A
行の先頭に移動
Ctrl+E
行の最後に移動
Ctrl+D
一文字削除
#### その他 コンテキストメニューから「`クリップボードから引用`」を選択すると、 クリップボードの内容に引用記号を付けて貼り付ける事ができる。 「JDimの動作環境を記入」を選択する と、不具合報告や質問時に書くテンプレートが記入される。 ### 画像ビューの操作 共通操作に加えて以下の操作をおこなえる。
マウスでドラッグ
画面スクロール
クリック
元の画像サイズ表示とウィンドウに合わせた画像サイズ表示の切り替え
中クリック
閉じる
右クリック
メニュー表示
ダブルクリック
画像再読み込み
Shift+K, K, Shift+🡱, 🡱
上にスクロール
Shift+J, J, Shift+🡳, 🡳
下にスクロール
Shift+L, Shift+🡰
左にスクロール
Shift+H, Shift+🡲
右にスクロール
Z
元の画像サイズ
X
画像サイズをウィンドウに合わせる(xを押す度に縦横を合わせるモードと横のみを合わせるモードを切り替える)
+
画像の拡大
-
画像の縮小
C
画像のモザイク解除
Ctrl+S
保存
Delete
削除
### 2ペーン表示時のスレ一覧とスレビュー の切り替え方法 2ペーン表示時にスレ一覧とスレビューを切り替えたいときは以下のいずれかを行う。 1. マウスジェスチャ(マウス右ボタンを押しながら 🠇 に動かしてボタンを放す)を使う 2. 🡰🡲キー、又は H, L キー又は Alt+X で切り替える 3. マウスのボタン5 を押す 4. ツールバーのアイコンで切り替える 5. 「`表示`」メニューで切り替える ### ログ検索 キャッシュ内にある全ログの中からキーワードを検索する場合は「`ツール`」 メニューから「`キャッシュ内ログ検索`」→「`キャッシュ内の全ログ検索`」 を選択すると現れる検索ビューにキーワードを入力してEnterを押す。 「`しおり`」をチェックすると、しおりが付けられているスレ (それ自体にしおりがついているスレ、又は含まれているレスに一つでもしおりがついているスレ)を対象に検索を行う。 キーワードに何も入れないで「`しおり`」だけチェックして検索するとしおりが付けられているスレを全て抽出する。 ある特定の板の中にあるログだけを対象にして検索をする場合は、スレ一覧を 開いてスレ一覧検索ボックスにキーワードを入力してからCtrl+Enterを押すか、 「`ツール`」メニューから「`キャッシュ内ログ検索`」→「`現在開いている板のログを検索`」 を選択すると現れる検索ビューにキーワードを入力してEnterを押す。 またスレビューで範囲選択をしてから Ctrl+Enter 又は右クリック→「`検索`」→「`ログ検索(対象: 板)`」 で板内のログを検索することもできる。 同様に、右クリック→「`検索`」→「`ログ検索(対象: 全ログ)`」でキャッシュ内の全てのログを検索することもできる。 検索ビューの操作方法はスレビューの操作方法と同じである。 ### スレタイ検索 現行の全スレッドからタイトルを検索する場合は「`ツール`」メニューから 「`スレタイ検索`」を選択すると現れる検索ビューにキーワードを入力してEnterを押す。 またスレビューで範囲選択をしてから Ctrl+T 又は右クリック→「`検索`」→「`スレタイ検索`」で検索することもできる。 検索ビューの操作方法はスレビューの操作方法と同じである。 ### 更新チェック 更新チェックをおこなって板やスレに更新があるときはサイドバーのアイコンやスレ一覧、 スレビューのタブに更新マークが表示される。 #### 操作方法 1. スレ一覧やスレビューのタブを右クリックしてメニューを開く→「`全てのタブの再読み込み`」→「`更新チェック`」 2. サイドバーのお気に入りや履歴で複数行選択→右クリックでメニューを開き「`更新チェック`」 3. サイドバーのお気に入りや履歴でディレクトリを右クリックしてメニューを開き「`更新チェック`」 4. サイドバーのお気に入りや履歴でディレクトリを中ボタンクリックするとディレクトリ内の更新チェックをし、 さらに更新があった板やスレを開く 5. 「`ツール`」メニュー→「`サイドバーの更新チェック`」でサイドバーに表示中のお気に入りや 履歴内の全ての板やスレに対して更新チェックをおこなう 6. 表示メニューの詳細設定からサイドバーのツールバーに更新チェックボタンを表示させて押す #### 注意点 - 板の更新チェックを実行すると、その板に含まれる既読かつ新着数が0のスレを速度の速い順にチェックしていって、 ひとつでも更新していれば残りのスレのチェックを打ちきってその板に更新マークを付ける。 - サイドバーのお気に入りや履歴での更新チェックはデフォルトでは板の更新をチェックしない。 チェックしたい場合は `about:config`の「`更新チェック時に板の更新もチェックする`」を「はい」にする。 - したらばのスレはLast-Modifiedを取得できないため更新チェック出来ない(常にHTTPコード200が返る)。 - まちBBSのスレは`設定`メニューの`一般`から「`まちBBSでofflaw.cgiを使用する`」を有効にしないとチェックできない ( 常にHTTPコード 500 が返るため)。ただしこの設定を有効にするとリモートホストのIPが表示されなくなる。 - 複数のスレをチェックするときは、同時にチェックしないで前のスレのチェックが終わってから順次チェックするため 若干時間がかる。従って、ある特定の板のスレがお気に入りに数多く登録されている場合はその板を直接開いた方が 速くてサーバに対する負荷も小さい。 - 一度更新チェックやロードしたスレは300秒経過するまで再チェックできない。 ### カスタマイズ方法 ショートカットキーの割り当ては`設定`メニュー → `マウス/キーボード` → `ショートカットキー詳細設定`から変更できる。 ショートカットキー設定は操作が重複していても操作モードが異なれば同じ操作を登録できる。 例えば共通操作モードの上移動にUp(上カーソル)を割り当ている時に 同じ共通操作モードの下移動にUpを割り当てることは出来ないが、スレビュー操作モードの書き込みにUpを割り当てる事ができる。 この場合は、スレビューで上カーソルキーを押すと上スクロールせずに書き込みビューが表示される。 マウスボタンの割り当ては`設定`メニュー → `マウス/キーボード` → `マウスボタン詳細設定`から変更できる。 ボタン設定は操作が重複していても登録できるが、操作には優先順位があるので希望どおりに動作しない場合がある。 例えばスレ一覧でCtrl+クリックにスレを開くを割り当てても、ツリービューの行選択操作が優先されるため スレを開くことが出来ない。 コンテキストメニューの項目は`表示`メニュー → `詳細設定` → `コンテキストメニュー項目設定`から編集できる。 項目設定ダイアログで非表示指定した項目はコンテキストメニューの「`その他`」に表示される。 [bkmark_update]: ../assets/bkmark_update.png [bkmark_broken_subject]: ../assets/bkmark_broken_subject.png [bkmark]: ../assets/bkmark.png [update]: ../assets/update.png [broken_subject]: ../assets/broken_subject.png [check]: ../assets/check.png [newthread]: ../assets/newthread.png [newthread_hour]: ../assets/newthread_hour.png [info]: ../assets/info.png [down]: ../assets/down.png [board]: ../assets/board.png [board_update]: ../assets/board_update.png [board_updated]: ../assets/board_updated.png [thread]: ../assets/thread.png [thread_old]: ../assets/thread_old.png [thread_update]: ../assets/thread_update.png [thread_updated]: ../assets/thread_updated.png jdim-0.7.0/docs/manual/replacestr.md000066400000000000000000000065121417047150700173460ustar00rootroot00000000000000--- title: 置換文字列の設定(ReplaceStr) layout: default --- > [Top](../) > [その他]({{ site.baseurl }}/info/) > {{ page.title }} ## {{ page.title }} - [置換処理について](#replacement) - [置換条件設定](#config) - [設定例](#example) ### 置換処理について _v0.4.0+から追加_ スレビューに表示するテキストに対して条件に応じた置換処理を設定する。 設定はメニューバーの `設定(C)`>`その他(O)`>`置換文字列の編集(R)` からダイアログを開いて行う。   | 特徴 --- | --- 置換対象の区分 | 名前欄、メール欄、日付/ID、レス本文 置換のタイミング | 対象を区分けした後、IDやリンクなどを解析する前に実行する 置換の順序 | 登録した置換条件をリストの上から順に実行していく 置換の方法 | シンプルな**キーワード置き換え**、または高度な**正規表現**が利用できる 一時的な処理 | 置換処理の結果はキャッシュデータ(DATファイル)に**保存しない** #### 対象の置換前に文字参照をデコード 有効にするとHTMLとして意味のある文字(`"&<>`)を除き文字参照(e.g. `♥`, `♥`, `♥`)を置換前にデコードする (置換条件の登録が無くても実行)。これにより文字参照でリンク生成を回避したURLなどを解釈することができる。 #### 設定ファイル 置換文字列の設定はキャッシュディレクトリの「**replacestr.xml**」ファイルにダイアログのOKを押したとき保存される。 設定ファイルが存在しないときはデフォルト設定が無効の状態で追加される。 #### 注意 - 設定ファイルを直接編集するときはJDimを終了(&バックアップ)してから行うこと - ファイルが壊れているときは読み込んだときに設定がすべて消去される - ファイルのフォーマットは予告なく変更する場合がある ### 置換条件設定 項目 | 説明 --- | --- 有効/無効 | 無効にした置換条件は実行しない 正規表現 | 正規表現を使用するかどうか 大文字小文字 ※ | 大文字小文字を区別しない 置換パターン | 置換するテキストのパターン 置換文字列 | マッチしたテキストを置き換える内容 ※ 正規表現を使わないときは効果無し 正規表現を使うと一致した文字列をもとに、以下の置換文字を使用できる。 置換文字 | 内容 | --- | --- | `\0` | 正規表現にマッチした文字列 | `\1`〜`\9` | 正規表現で「(...)」にマッチした部分文字列 | #### この設定をクリップボードにコピー 設定をXML要素としてシリアライズしてコピーする。 ### 設定例 #### `ps://` から始まるテキストを `https://` にする 項目 | 設定 --- | --- 置換対象 | 本文 正規表現 | ON 置換パターン | `(? ### スタイルシート設定 (jd.css) JDimはスタイルシートによるスレビュー表示のカスタマイズをサポートしている。 スタイルシートは[キャッシュディレクトリ][cachepriority]内にテーマフォルダ(`theme/`)を作成し その中に「**jd.css**」という名前のファイルを作って設定する。 スタイルシートを変更したときはJDimを再起動する。 #### 対応しているプロバティ | プロパティ名 | 説明 | 備考 | | --- | --- | --- | | color | 文字色 | リンクなどJDimの設定メニューから設定する物を除く | | background-color | 背景色 | | | border-color | 枠線色 | border-left-color など個別指定も可能 | | border-style | 枠線形 | solid のみに対応 | | border-width | 枠線幅 | border-left-width など個別指定も可能 | | margin | 外余白 | margin-left など個別指定も可能 | | padding | 内余白 | padding-left など個別指定も可能 | | text-align | 位置 | left, center, right のみに対応 | **注意**: スタイルシートはfont系のプロパティに対応していない。 [フォント設定](#fonts)はメニューバーからダイアログを開いておこなう。 #### 色の指定方法 `black`や`white`などの定義済み色名か「`#RRGGBB`」形式で指定する。定義済み色名の種類は次の通り。 - CSS Level 1 - red - fuchsia - purple - maroon - yellow - lime - green - olive - blue - aqua - teal - navy - white - silver - gray - black - CSS Level 2 (v0.6.0+からサポート) - orange - CSS Colors Level 3 (v0.6.0+からサポート) - X11色, 数が多いため下記リンク参照 - CSS Colors Level 4 (v0.6.0+からサポート) - rebeccapurple 外部リンク: [<color> - CSS: カスケーディングスタイルシート | MDN][color_value] #### 単位 `px`, `em` のみに対応。単位を省略すると `px` になる。 #### レスの構造 スレ内のひとつひとつのレスは以下のような構造となっている。 `NUMBER`や`NAME`などの要素については「[レス構造設定](#reshtml)」の項で説明する。 ```html
``` #### 定義済みのセレクタ **注意**: jd.css でクラス指定する際は `div.res{}` と書かずに `.res{}` と要素名(div)を書かずにクラス名だけを指定すること。
body
スレビューのbody要素
.res
ひとつのレスの要素
.title
ひとつのレスのヘッダ行
.mes
ひとつのレスの本文
.separator
ここまで読んだ。<div class="separator">ここまで読んだ</div> という構造になっている。
.comment
コメントブロック。<div class="comment">任意のコメント</div> という構造になっている。
imgpopup
画像ポップアップのbody要素。color, background-color, border-color, border-width, margin プロバティのみ(単位はpxのみ)に対応。
--- ### レス構造設定 (Res.html) スレ内のひとつひとつのレスは「[レスの構造](#resstructure)」の項で示した構造となっている。 レスの構造は[キャッシュディレクトリ][cachepriority]内にテーマフォルダ(`theme/`)を作成し その中に「**Res.html**」という名前のファイルを作って指定することが出来る。 レスの構造を変更したときはJDimを再起動する。 #### 使用可能な要素 `
` のみ #### 置換可能な定義済み要素 Res.htmlでは次のように定義済み要素を指定して文字列の置換ができる。 | 定義済み要素 | 説明 | | --- | --- | | <NUMBER/> | レス番号 | | <NAMELINK/> | 名前メニュー表示のリンク。「名前」の文字列に置換される。 | | <NAME/> | 名前 | | <MAIL/> | メール | | <DATE/> | 日付 | | <ID/> | ID | | <MESSAGE/> | 本文。「br="no"」属性を指定すると本文の改行をしない。 | | <IMAGE/> | インライン画像 | #### 注意点 - `
`はRes.htmlで指定する必要が無い。 つまり Res.htmlに"hoge"とだけ指定した場合、レスの構造は以下のようになる。 ```html
hoge
``` - div の中に div を配置することは出来ない( 上の `
` は例外 )。 - クラス名は定義済みの物だけではなくて自由に指定できる。 --- ### フォント設定 フォント設定はメニューバーの`設定(C)`>`フォントと色(F)`から設定ダイアログを開いておこなう。 | 項目 | 適用範囲 | --- | --- | スレビュー | スレビューのレス本文 | メール欄 ※1 | スレビューやポップアップのレス番号、名前、メール、日付、ID | ポップアップ | ポップアップウインドウのレス本文 | アスキーアート | スレビューやポップアップのアスキーアートと判定されたレス本文 | 板一覧/お気に入り | 板一覧/お気に入りなどサイドバーの項目 | スレ一覧 | スレ一覧の列名と項目 | 書き込みビュー | 書き込みビューの名前欄、メール欄、レス欄 - ※1 -- バージョン0.3.0-20200301から追加。それより前は「スレビュー」の適用範囲に含まれる。 --- ### アイコン設定 [キャッシュディレクトリ][cachepriority]内にアイコンテーマのフォルダ(`theme/icons/`)を作成し その中に対応するアイコン画像を置くとJDimのアイコンが置き換わる。 アイコン画像を変更したときはJDimを再起動する。 - 画像形式は一般的な物なら大体使用可能 ( png, jpg, svg, gif, bmp, その他) - 画像サイズは任意のサイズで良い( 16x16 がデフォルトサイズ) - ファイル名は「アイコン名.拡張子 」(例) reload.jpg、 quit.png - ファイル名の一覧はgitリポジトリにある [ヘッダファイル(iconfiles.h)][iconfiles] を参照すること --- ### 使用例 [![スキンサンプル][skin_sample_thumb]][skin_sample] クリックで拡大 [キャッシュディレクトリ][cachepriority]内にテーマフォルダ(`theme/`)を作成し その中にRes.html(下)と[jd.css][jdcss]をコピーしてJDimを起動する。 ```html
``` [color_value]: https://developer.mozilla.org/ja/docs/Web/CSS/color_value#color_keywords [iconfiles]: https://github.com/JDimproved/JDim/blob/master/src/icons/iconfiles.h "JDim/iconfiles.h at master" [skin_sample_thumb]: ../assets/skin_sample_thumb.png [skin_sample]: ../assets/skin_sample.png "サンプルのスクリーンショット" [jdcss]: ../assets/jd.css "サンプルのcss" [cachepriority]: ../start/#cachepriority "キャッシュディレクトリの優先順位" jdim-0.7.0/docs/manual/sound.md000066400000000000000000000012271417047150700163300ustar00rootroot00000000000000--- title: 効果音の再生について layout: default --- > [Top](../) > [その他]({{ site.baseurl }}/info/) > {{ page.title }} ## {{ page.title }} ### 使い方 ALSAによる効果音再生機能を有効にするには「configure」実行時に「**\-\-with-alsa**」オプションを付けてから make する。 さらにキャッシュディレクトリ( `$XDG_CACHE_HOME/jdim` )に **sound/** ディレクトリを作り、以下のwavファイルをコピーする。 | ファイル名 | 説明 | | --- | --- | | new.wav | 新規スレを開いた | | res.wav | 更新がある | | no.wav | 更新がない | | err.wav | エラー | jdim-0.7.0/docs/manual/start.md000066400000000000000000000225511417047150700163400ustar00rootroot00000000000000--- title: 起動について layout: default --- > [Top](../) > {{ page.title }} ## {{ page.title }} - [通常の起動](#run) - [多重起動について](#multiple) - [実行時の注意事項](#precaution) - [JDとの互換性](#compatibility) - [Snapパッケージ](#snap) ### 通常の起動 使い方は以下のとおり。 ``` $ jdim [OPTION] [URL,FILE] ``` 引数にURLを付けて起動する事も出来るので、他のアプリケーションから外部 コマンドとしてURLを開く事などが出来る。(JDimが扱う事の出来るURLでない場 合は設定されているWebブラウザに渡される) ``` $ jdim http://pc99.2ch.net/test/read.cgi/linux/1234567890/ ``` ローカルにあるdatファイルを指定して、一時的にスレビュー表示させることも出来る。 ``` $ jdim ./12345.dat ``` 環境変数 `JDIM_CACHE` でキャッシュディレクトリの位置を変更・指定することが可能。 指定しなければ下記の[優先順位](#cachepriority)の通りに決まる。 ``` $ JDIM_CACHE=~/.mycache jdim ``` 環境変数 `JDIM_LOCK` でロックファイルの位置を変更・指定することが可能。 指定しなければ `<キャッシュディレクトリ>/JDLOCK` がロックファイルになる。 ``` $ JDIM_LOCK=~/mylock jdim ``` #### キャッシュディレクトリの優先順位 | `~/.jd` | `$XDG_CACHE_HOME/jdim` | 使われるのは… | | --- | --- | --- | | 存在する | any | `~/.jd` | | 存在しない | any | `$XDG_CACHE_HOME/jdim` | | any (無効化) | any | `$XDG_CACHE_HOME/jdim` | NOTE: - 環境変数 `XDG_CACHE_HOME` が未設定または空のときはかわりに `$HOME/.cache/jdim` が使われる。 - `~/.jd` が無効化されている場合は `jdim --version` の出力に `--disable-compat-cache-dir` が追加される。 - キャッシュディレクトリはJDimを起動すると作成される。 #### オプション
-h, --help
ヘルプを表示
-m, --multi
多重起動時のサブプロセスであっても終了しない
-s, --skip-setup
初回起動時の設定ダイアログを表示しない
-l, --logfile
エラーなどのメッセージをファイル(キャッシュディレクトリのlog/msglog)に出力する
-g, --geometry WxH-X+Y
幅(W)高さ(H)横位置(X)縦位置(Y)の指定。 WxHは省略可能(例: -g 100x40-10+30, -g -20+100 )
-V, --version
バージョン及びconfigureオプションを全て表示
### 多重起動について JDimはメインプロセス/サブプロセスという関係で動作する。 - メインプロセス: 指令を受け取る事が出来るプロセス - サブプロセス : 指令を出す事が出来るプロセス 通常は最初に起動した物がメインプロセスとなり、メインプロセスは1つだけ存 在する事が出来る。メインプロセスが存在する状態で起動したプロセスはサブ プロセスとして扱われ、複数存在させる事も可能。なお、指令を受け取るのは メインプロセスのみなので、指令を出す側のサブプロセスでURLは開かれない。 以下のコマンドを使い分ける事でサブプロセスの起動のしかたをコントロール出来る。 - 起動するかどうか確認してサブプロセスを起動 ``` $ jdim ``` - 確認せずにサブプロセスを起動 ``` $ jdim -m ``` - メインプロセスにURLを渡してサブプロセスを起動 ``` $ jdim -m http://pc99.2ch.net/test/read.cgi/linux/1234567890/ ``` 注: サブプロセスを残したままメインプロセスを終了していた場合は次に起動 したプロセスがメインプロセスとなる。 ### 実行時の注意事項 廃止されたGTK2版同様のルック・アンド・フィールになるように実装しているが、 技術的な問題やテスト不足から完全な再現はできていない。 #### Wayland対応 JDim はWayland環境で起動するが動作は安定していない。 GTKのバックエンドにWaylandを使うかわりに互換レイヤーのXWaylandをインストールして使う。 環境変数 `GDK_BACKEND=x11` を設定してjdimを起動する。 ```sh # シェルからJDimを起動する場合 GDK_BACKEND=x11 ./src/jdim ``` WaylandやXWaylandではX11限定の機能を使うことができないため注意すること。 #### GTK2版から変更/追加された部分 * GTK+ 3.14以上の環境でタッチスクリーンによる操作に対応した。 スレビューのタッチ操作については[操作方法について][]を参照。 * 書き込みビューの配色にGTKテーマを使う設定が追加された。 1. メニューバーの`設定(C) > フォントと色(F) > 詳細設定(R)...`からフォントと色の詳細設定を開く 2. `色の設定`タブにある`書き込みビューの配色設定に GTKテーマ を用いる(W)`をチェックして適用する * GTK 3.16以上の環境で書き込みビューのダブルクリックによる単語の範囲選択、 トリプルクリックによる行の範囲選択に対応した。 #### 既知の問題 * タブのドラッグ・アンド・ドロップの矢印ポップアップの背景が透過しない環境がある。 (アルファチャンネルが利用できない環境) * Wayland上で起動したときポップアップ内のアンカーからポップアップを出すとマウスポインターから離れた位置に表示される。 * Weston(Waylandコンポジタ)環境でXWaylandをバックエンドに指定して起動した場合、右クリックしながらポップアップ内に マウスポインターを動かすとポップアップ内容ではなくポップアップに隠れたスレビューに反応する。 * 32bit OSの古いMATE環境(バージョン[1.10][mate-1-10]から[1.16][mate-1-16]?)でJDim GTK3版を実行したとき スレビューの上に別のウインドウを重ねて移動させると残像でスレビュー内のみ描画が乱れる。 スレビューをスクロール等させて再描画すると直る。([背景事情][mate-background]) * Wayland環境では画像ビュー(ウインドウ表示)のフォーカスが外れたら折りたたむ機能が正常に動作しない。 [mate-1-10]: https://mate-desktop.org/blog/2015-06-11-mate-1-10-released/ "GTK3の実験的なサポート追加" [mate-1-16]: https://mate-desktop.org/blog/2016-09-21-mate-1-16-released/ "GTK3に移行途中" [mate-background]: https://github.com/JDimproved/JDim/commit/ffbce60ede#commitcomment-40911816 "別の不具合が再発する" ### JDとの互換性 JDimの環境設定はJDからフォーマットを継承しているので後方互換性がある。 また、ユーザーインタフェースは今のところ変更されていない。 JDimで追加された不具合や機能の修正については[Pull requests][pr-merged]から見ることができる。 #### JDから移行する * 後方互換性としてJDのキャッシュディレクトリ(`~/.jd`)はそのまま使うことができる。 ただし、オプション`--disable-compat-cache-dir`が指定されたビルドでは互換機能は無効化される。 * 互換機能が使えないときは `$XDG_CACHE_HOME/jdim`(`~/.cache/jdim`) にキャッシュディレクトリを移動する。 ```bash $ mv ~/.jd ~/.cache/jdim ``` * 環境変数`JD_CACHE`でキャッシュディレクトリを設定している場合はかわりに`JDIM_CACHE`を使用する。 * JDとJDimを併存させる(データや設定を共有しない)ためには環境変数によるキャッシュディレクトリの設定が要る。 ([通常の起動](#run)を参照) ### Snapパッケージ JDim はSnapパッケージとして[Snap Storeで公開][snapcraft]されている。 `snap`コマンドやwebページからインストールすることでコマンドやデスクトップ環境のメニューから起動できる。 ```sh sudo snap install jdim ``` Snapパッケージ版はアクセス制限が導入されているため通常のパッケージやビルドと異なる点がある。 - JDimを起動したとき作成されるキャッシュディレクトリの場所が異なる (`~/snap/jdim/common/.cache/jdim/`) - **上記Snap版のキャッシュディレクトリはパッケージを削除(アンインストール)すると消去される** - JDのキャッシュディレクトリ (`~/.jd`) は使えない - 隠しファイル(ドットファイル)のキャッシュディレクトリは使えない - 外部コマンドの呼び出しは制限される - 環境によってはGTKテーマ、アイコン、マウスカーソルがうまく表示されない場合がある
環境変数 (`GTK_THEME`) やGTKの設定ファイル (`$XDG_CONFIG_HOME/gtk-3.0/settings.ini`) を調整することで改善できるかもしれない [pr-merged]: https://github.com/JDimproved/JDim/pulls?q=is%3Apr+is%3Amerged [snapcraft]: https://snapcraft.io/jdim [操作方法について]: {{ site.baseurl }}/operation/#threadview_touch "操作方法について \| JDim" jdim-0.7.0/docs/manual/urlreplace.md000066400000000000000000000074501417047150700173420ustar00rootroot00000000000000--- title: URL変換について layout: default --- > [Top](../) > [その他]({{ site.baseurl }}/info/) > {{ page.title }} ## {{ page.title }} - [URL変換](#replacement) - [設定ファイル](#configfile) - [設定例](#example) ### URL変換 書き込みされたURLと実際に表示する画像のURLが異なる場合に、 URLを正規表現で変換することで直接画像にアクセスできる。 また、リファラを送信したり、拡張子のないURLを強制的に画像と認識させることができる。 ### 設定ファイル URL変換機能を使う場合は、キャッシュディレクトリの「**urlreplace.conf**」ファイルに設定する。 設定ファイルを変更したあとは、一度JDimを再起動することで設定内容が有効になる。 youtubeのサムネイルをインライン画像として表示するための変換が、デフォルトで設定される。 なお、設定内容をリセットしたい場合は、「urlreplace.conf」ファイルを削除してからJDimを起動すると、 ファイルが自動で再作成される。 #### 設定ファイルの書式 ``` 正規表現<タブ>変換後URL<タブ>リファラURL<タブ>制御文字 ``` 制御ファイルに指定した、正規表現はファイルの先頭から順に評価される。 また、リファラURLおよび制御文字は、変換した後のURLが、正規表現に一致したときに有効になる。 #### 変換後URLおよびリファラURL 正規表現に一致した文字列をもとに、以下の置換文字を使用できる。 | 置換文字 | 内容 | | --- | --- | | `\0` または `$0` | 正規表現にマッチした文字列 | | `\1`〜`\9` または `$1`〜`$9` | 正規表現で「(...)」にマッチした部分文字列 | #### 制御文字
$IMAGE
拡張子がない場合でも画像として扱う。
$BROWSER
拡張子があっても画像として扱わない。
$GENUINE
JDimは拡張子と実際の画像形式が不一致のときに、画像が偽装されていると判断して モザイクで表示する。 これを指定すると、拡張子の偽装をチェックせず、モザイクで表示しない。
また、WebPやAVIFを表示できる環境ではwebサイトが元画像のかわりに送ることを容認する。(v0.5.0+)
$BREAK
正規表現に一致したら以降の判定を行わず、評価を終了する。
$THUMBNAIL
変換した後のURLが、動画投稿サイトなどのサムネイル画像として扱う。 画像表示設定でインライン画像を表示しているときに有効となる。
### 設定例 リファラURLを送信したり制御文字を有効にするには、変換後URLが正規表現に一致するように表現する。 変換後のURLを変換用の条件とリファラ用の条件を、2行に分けて書いてもよい。
1行でまとめて書く場合
http://www\.foobar\.com/(view/|img\.php\?id=)([0-9]+)	http://www.foobar.com/view/$2	$0	$IMAGE
2行に分けて書く場合
http://www\.foobar\.com/img\.php\?id=([0-9]+)	http://www.foobar.com/view/$1
http://www\.foobar\.com/view/([0-9]+)	$0	$0	$IMAGE
imgurの拡張子無しURLを画像リンク(jpg)にする
^https?://imgur\.com/([0-9A-Za-z]{7})$	https://i.imgur.com/$1.jpg		$IMAGE
imgurの画像URLは偽装チェックしない
^https?://i\.imgur\.com/([^#&=/]+)$	$0		$GENUINE
jdim-0.7.0/docs/manual/usrcmd.md000066400000000000000000000237401417047150700165010ustar00rootroot00000000000000--- title: ユーザーコマンド、リンクフィルタについて layout: default --- > [Top](../) > {{ page.title }} ## {{ page.title }} - [登録](#register) - [編集](#edit) - [置換文字一覧](#replacement) - [リンクフィルタ](#filter) ### 登録 スレビューや画像ビューで右クリックしたときに表示されるコンテキストメニューにユーザコマンドを登録できる。 登録するにはメニューの「`設定`」→「`その他`」→「`ユーザコマンドの編集`」から設定ダイアログを開き、 右クリックしてコンテキストメニューから「`新規コマンド`」を選択する。同様にディレクトリや区切り線も作成できる。 「`選択不可のユーザコマンドを非表示にする`」をチェックすると選択出来ないコマンドは表示されなくなる。 ### 編集 一度設定したコマンドを編集する場合は、設定ダイアログで対象のコマンドをダブルクリックする。 また、行をドラッグして位置を変えたり階層構造にすることが出来る。 行を削除する場合はクリックしてからDeleteキーを押すかコンテキストメニューから削除する。 なお、実行するコマンドでは下記のような置換文字列を指定出来る。 また、コマンドを手動で編集したい場合はキャッシュディレクトリにある "usrcmd.xml"というXMLファイルを編集する。 ### 置換文字一覧 ユーザコマンドの具体的な使いかたは [ユーザーコマンド設定集][wiki-usrcmd] (JD wiki) を参照すること。 以下の説明はスレビューでユーザーコマンドを使用する場合であるので、画像ビューで使用する場合は `$URL` 等は画像が貼ってあったスレのアドレス、`$LINK` 等は画像のアドレス、`$CACHEDIMG` は画像キャッシュのパスに読み替える。
$VIEW
ネットワーク設定で指定されたwebブラウザで開く ( 例: $VIEW $LINK でリンクをブラウザで開く )
$DIALOG
$DIALOGの後の文字列をダイアログ表示
$ONLY
スレのURLが$ONLYの後の文字列(正規表現)を含む時だけメニューを表示 (下記参照)
$URL
スレのURL ( 例: http://hibari.2ch.net/test/read.cgi/linux/1276299375/ )
$DATURL
サーバー上のdatファイルのURL ( 例: http://hibari.2ch.net/linux/dat/0123456789.dat )
$SERVER
http://などのプロトコルを含むスレのURLから取り出したサーバー名 ( 例: http://hibari.2ch.net )
$HOSTNAME
http://などのプロトコルを除くスレのURLから取り出したサーバー名 ( 例: hibari.2ch.net )
$HOST
$HOSTNAMEと同じ
$OLDHOSTNAME
$HOSTNAMEと同じだが、もしスレが板移転前に立てられたものなら移転前のサーバー名を使用する
$OLDHOST
$OLDHOSTNAMEと同じ
$BBSNAME
スレが属する板のID ( 例: http://hibari.2ch.net/linux なら linux )
$DATNAME
スレのID ( 例: http://hibari.2ch.net/test/read.cgi/linux/1276299375/ なら 1276299375 )
$LINK
マウスの下にリンクがあればそのURL
$SERVERL
http://などのプロトコルを含むリンクから取り出したサーバー名( 例: http://www.google.co.jp )
$HOSTNAMEL
http://などのプロトコルを除くリンクから取り出したサーバー名( 例: www.google.co.jp )
$HOSTL
$HOSTNAMELと同じ
$OLDHOSTNAMEL
$HOSTNAMELと同じだが、もしリンク先が2chのスレの場合は板移転前のサーバー名を使用する
$OLDHOSTL
$OLDHOSTNAMELと同じ
$BBSNAMEL
もしリンク先が2chのスレの場合は、そのスレが属する板のID ( 例: http://hibari.2ch.net/linux なら linux )
$DATNAMEL
もしリンク先が2chのスレの場合は、そのスレのID ( 例: http://hibari.2ch.net/test/read.cgi/linux/1276299375/ なら 1276299375 )
$CACHEDIMG
リンクが画像でかつキャッシュされている時は画像キャッシュの場所
$LOGPATH
キャッシュディレクトリの場所 ( 例: $XDG_CACHE_HOME/jdim/ )
$LOCALDAT
キャッシュディレクトリにあるdatファイルの場所 ( 例: $XDG_CACHE_HOME/jdim/hibari.2ch.net/linux/1276299375.dat )
$LOCALDATL
もしリンク先が2chのスレの場合は、キャッシュディレクトリにあるdatファイルの場所
$TITLE
スレのタイトル(UTF-8)
$NUMBER
スレビューの場合はマウスカーソルの下にあるレス番号、画像ビューの場合は参照元のレス番号
$BOARDNAME
スレが属する板の名前(UTF-8)
$TEXT
範囲選択した文字列(UTF-8)
$TEXTI
範囲選択した文字列(UTF-8)、選択がないときは入力ダイアログを表示する
$TEXTU
範囲選択した文字列(UTF-8)をURLエンコード
$TEXTIU
範囲選択した文字列(UTF-8)をURLエンコード、選択がないときは入力ダイアログを表示する
$TEXTX
範囲選択した文字列をEUCに変換してURLエンコード
$TEXTIX
範囲選択した文字列をEUCに変換してURLエンコード、選択がないときは入力ダイアログを表示する
$TEXTE
範囲選択した文字列をSJIS(MS932)に変換してURLエンコード
$TEXTIE
範囲選択した文字列をSJIS(MS932)に変換してURLエンコード、選択がないときは入力ダイアログを表示する
$INPUT
入力ダイアログを表示(UTF-8)
$INPUTU
入力ダイアログを表示してURLエンコード(UTF-8)
$INPUTX
入力ダイアログを表示しEUCに変換してURLエンコード
$INPUTE
入力ダイアログを表示しSJIS(MS932)に変換してURLエンコード
#### $ONLYの例 以下のように設定すると、スレのURLにlinuxを含むときのみメニューに表示されて「Linux」とダイアログ表示する。
コマンド名
linuxのみ
実行するコマンド
$ONLY linux $DIALOG Linux
以下のコマンドはスレのURLにnewsを含むときのみメニューに表示されて「ニュース」とダイアログ表示する。
コマンド名
newsのみ
実行するコマンド
$ONLY news $DIALOG ニュース
### リンクフィルタ スレビューでリンクをクリックした時、アドレス毎にフィルタリングを行って動作を変えることが出来る。 アドレスは正規表現として解釈され、フィルタの上の方からマッチングしていく。 #### 設定と編集 フィルタの条件を設定するにはメニューの「`設定`」→「`その他`」→「`リンクフィルタの編集`」から設定ダイアログを開き、 追加ボタンを押してアドレスと実行するコマンドを指定する。 一度設定した条件を編集する場合は対象の条件をダブルクリックする。 削除する場合は対象の条件をクリックしてからDeleteキーを押すか削除ボタンを押す。 順番を変えるには行を選択してから上端ボタンや上へボタンなどを押す。 コマンドには上のユーザコマンドと同じ置換文字を使える。 また、リンクフィルタのコマンドでは以下の置換文字を使用できる。 | 置換文字 | 内容 | | --- | --- | | `\0` | アドレスの正規表現にマッチした文字列 | | `\1`〜`\9` | アドレスの正規表現で「(...)」にマッチした部分文字列 | なお、コマンドを手動で編集したい場合はキャッシュディレクトリにある「linkfilter.xml」というXMLファイルを編集する。 #### 使用例 以下のように設定すると hoge.net のjpg画像はeogで開くが、 その他の hoge.net 上にある画像やファイルは外部ブラウザで開く。
アドレス
http://hoge\.net/.*jpg$
実行するコマンド
eog $LINK
アドレス
http://hoge\.net/
実行するコマンド
$VIEW $LINK
また、以下の例ではパスにburakuraを含むリンクをクリックすると「ブラクラ」というダイアログを表示する。 どの条件にもマッチしなかった場合は通常の動作になる。
アドレス
burakura
実行するコマンド
$DIALOG ブラクラ
[wiki-usrcmd]: https://ja.osdn.net/projects/jd4linux/wiki/%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E8%A8%AD%E5%AE%9A%E9%9B%86 jdim-0.7.0/jdim.desktop000066400000000000000000000004031417047150700147620ustar00rootroot00000000000000[Desktop Entry] Exec=jdim Icon=jdim Terminal=false Type=Application Name=JDim 2ch browser Name[ja]=2ch ブラウザ JDim Comment=JDim is a 2ch browser based on gtkmm. Comment[ja]=gtkmm を利用した 2ch ブラウザ Categories=Network; Keywords=2ch;jd;bbs; jdim-0.7.0/jdim.metainfo.xml000066400000000000000000000074721417047150700157270ustar00rootroot00000000000000 com.github.jdimproved.jdim FSFAP GPL-2.0 JDim 2ch browser for Linux

JDim (JD improved) is a browser for Japanese textboard "2channel". The software has been forked from JD released under the terms of GPL-2.0, having look-and-feel and environment settings compatible.

Various features which are better viewing 2ch: Fetching updated posts, Hide NG word posts, Message posting, ASCII art input, Play-by-play mode, Mouse gestures, Display linked images, Bookmark and History, Filters for URL, Thread title search, Keyword search in thread, External boards support, Highly customizations and more...

jdim.desktop Marking a post (post ID 49) https://user-images.githubusercontent.com/15698961/72191765-39568200-33fb-11ea-9b2d-f8a997cf9ae8.png Extracting a keyword (highlight "肥料") https://user-images.githubusercontent.com/15698961/148783880-8ca23f37-f111-4d80-ba3d-4e3178fe372b.png Editing message to post https://user-images.githubusercontent.com/15698961/87845409-edcce580-c901-11ea-9a35-28ae3242c42b.png Extracting posts which have much referenced https://user-images.githubusercontent.com/15698961/148783874-66aec37b-b492-4199-9749-f69022031783.png https://github.com/JDimproved/JDim https://github.com/JDimproved/JDim/issues https://jdimproved.github.io/JDim/ JDimproved project jdim

Implement User-Agent configuration for board

https://github.com/JDimproved/JDim/releases/tag/JDim-v0.7.0

Add initial support for Client-Side Decoration

https://github.com/JDimproved/JDim/releases/tag/JDim-v0.6.0

End-of-life for GTK2 support

https://github.com/JDimproved/JDim/releases/tag/JDim-v0.5.0

Switch default version to GTK3

https://github.com/JDimproved/JDim/releases/tag/JDim-v0.4.0

Improve GTK3 support

https://github.com/JDimproved/JDim/releases/tag/JDim-v0.3.0

Support for GTK3 (optional)

https://github.com/JDimproved/JDim/releases/tag/JDim-v0.2.0
Network intense
jdim-0.7.0/jdim.png000066400000000000000000000022711417047150700141020ustar00rootroot00000000000000PNG  IHDR00WsBIT|d pHYsbb_'StEXtSoftwarewww.inkscape.org<XtEXtCopyrightCC0 Public Domain Dedication http://creativecommons.org/publicdomain/zero/1.0/IDAThKrYT"M FvrٶMHԢZӈ E6"E@fDQ4*1@͢bo~A{﹞{(@oKB>DA+`0p||#^l6*!0~LmIPB-I7`)WBm0Ĕ$=inL&ǔPqc tuu5&͖*Zߣt8NƘz@?QNcppX!D^*xxxV),,P(Tn!ʱVtb888vʀ$I lnnb144nggg'+HdjjUVpļ'rJKt:zzzH̼jS'HBF~i ZUUb`qq1[7!) rz^ɳ>6_K | d/l%mFwwwSL% ȲL +2www}Ը+++Φ\!^&&&7'''%̛L`X 0??j,󯮮Ae Bfsɤl|>jkkLz^" e&BKSSSRƤf COp--- connAnoo?>>rqqjK26WBB+{{{ 3::Jii)۸\.n&@gg'V5fϛ8J|>v;VU~D%%FAѤ image/svg+xml jdim-0.7.0/meson.build000066400000000000000000000200751417047150700146150ustar00rootroot00000000000000# JDim用 meson.build # NOTE: 暫定的なサポートのため変更の可能性がある # Autotoolsからの移行はGNOMEのガイドラインを参考にする # https://wiki.gnome.org/Initiatives/GnomeGoals/MesonPorting # mesonを使ってJDimをビルドする方法 # # Fedora # ディストロのパッケージをインストールする # ``` # dnf install git libtool meson # dnf install gnutls-devel gtkmm30-devel libSM-devel # ``` # # Debian Buster, Ubuntu 20.04 # ディストロのパッケージをインストールする # ``` # sudo apt install build-essential git libtool meson # sudo apt install libgnutls28-dev libgtkmm-3.0-dev libltdl-dev # ``` # # Debian Stretch, Ubuntu 18.04 # ディストロのmesonパッケージはバージョンが古いためpypiからインストールする # $HOME/.local/bin にパスを通す # ``` # sudo apt install build-essential git libtool ninja-build python3 python3-pip # pip3 install --user meson # sudo apt install libgnutls28-dev libgtkmm-3.0-dev libltdl-dev # ``` # ビルドの手順 # ``` # git clone -b master --depth 1 https://github.com/JDimproved/JDim.git jdim # cd jdim # meson builddir # cd builddir # ninja # ``` # Tips # - JDimのビルドオプションは `meson configure` を実行してProject optionsの段落を確認する # または meson_options.txt を見る # - ビルドオプションは `meson builddir -Dregex=glib` のように指定する # - 生成された実行ファイルの場所は builddir/src/jdim project('jdim', 'cpp', version : '0.7.0', license : 'GPL2', meson_version : '>= 0.49.0', default_options : ['warning_level=3', 'cpp_std=c++1z']) # 追加コンパイルオプション add_project_arguments('-DHAVE_CONFIG_H=1', language : 'cpp') add_project_arguments('-DGTK_DOMAIN="gtk30"', language : 'cpp') # -Wextraで有効になる-Wunused-parameterは修正方法の検討が必要なので暫定的に無効 add_project_arguments('-Wno-unused-parameter', language : 'cpp') cpp_compiler = meson.get_compiler('cpp') conf = configuration_data() # オプションの表示にはconfigureスタイル('--with-foo')が必要なので注意 configure_args = [] # # ビルドされたバイナリが実行されるマシンの情報 # if host_machine.endian() == 'big' conf.set('WORDS_BIGENDIAN', 1) endif # # 必須パッケージのチェック # gtkmm_dep = dependency('gtkmm-3.0', version : '>= 3.22.0') threads_dep = dependency('threads') x11_dep = dependency('x11') zlib_dep = dependency('zlib', version : '>= 1.2.0') # crypt crypt_dep = cpp_compiler.find_library('crypt', required : false) if not crypt_dep.found() crypt_dep = dependency('libcrypt', required : false) endif if not crypt_dep.found() crypt_dep = dependency('libxcrypt') endif if cpp_compiler.has_function('crypt_r', dependencies : crypt_dep) conf.set('HAVE_CRYPT_R', 1) endif # socket if cpp_compiler.has_header('sys/socket.h') socket_dep = dependency('', required : false) else socket_dep = cpp_compiler.find_library('socket') endif if not cpp_compiler.has_function('timegm') conf.set('NO_TIMEGM', 1) endif # # オプションのパッケージ # # セッション管理 sessionlib_opt = get_option('sessionlib') if sessionlib_opt == 'xsmp' sm_dep = dependency('sm', version : '>= 1.2.0') ice_dep = dependency('ice', version : '>= 1.0.0') conf.set('USE_XSMP', 1) elif sessionlib_opt == 'no' sm_dep = dependency('', required : false) ice_dep = dependency('', required : false) configure_args += '\'--with-sessionlib=no\'' endif # 正規表現ライブラリ regex_opt = get_option('regex') if regex_opt == 'oniguruma' regex_dep = dependency('oniguruma') if not cpp_compiler.has_header('onigposix.h') error('onigposix.h not found') endif conf.set('HAVE_ONIGPOSIX_H', 1) configure_args += '\'--with-regex=oniguruma\'' warning('-Dregex=oniguruma is deprecated. See https://github.com/JDimproved/JDim/issues/697') elif regex_opt == 'glib' regex_dep = dependency('glib-2.0', version : '>= 2.14.0') endif # TLS tls_opt = get_option('tls') if tls_opt == 'gnutls' tls_dep = dependency('gnutls', version : '>= 3.5.8') conf.set('USE_GNUTLS', 1) elif tls_opt == 'openssl' tls_dep = dependency('openssl', version : '>= 0.9.0') conf.set('USE_OPENSSL', 1) configure_args += '\'--with-tls=openssl\'' endif # migemo migemo_dep = cpp_compiler.find_library('migemo', required : get_option('migemo')) if migemo_dep.found() and cpp_compiler.has_header('migemo.h') conf.set('HAVE_MIGEMO_H', 1) configure_args += '\'--with-migemo\'' endif migemodict = get_option('migemodict') if migemodict != '' conf.set_quoted('MIGEMODICT', migemodict) configure_args += '\'--with-migemodict=@0@\''.format(migemodict) message('Default path for migemo dictionary file: @0@'.format(migemodict)) endif # alsa alsa_dep = dependency('alsa', version : '>= 1.0.0', required : get_option('alsa')) if alsa_dep.found() conf.set('USE_ALSA', 1) configure_args += '\'--with-alsa\'' endif # googletestが見つからない場合はテストはしない build_tests_opt = get_option('build_tests') if not build_tests_opt.disabled() # ディストロのパッケージとmeson wrapに対応 gtest_main_dep = dependency('gtest', main : true, fallback : ['gtest', 'gtest_main_dep'], required : build_tests_opt) build_tests = gtest_main_dep.found() else build_tests = false endif # # オプションの機能 # # Use PangoLayout instead of PangoGlyphString pangolayout = get_option('pangolayout').enabled() if pangolayout conf.set('USE_PANGOLAYOUT', 1) configure_args += '\'--with-pangolayout\'' message('Render text by PangoLayout: YES') endif # compatible cache directory compat_cache_dir = get_option('compat_cache_dir').enabled() if compat_cache_dir conf.set('ENABLE_COMPAT_CACHE_DIR', 1) message('Use compatible cache directory: YES') else configure_args += '\'--disable-compat-cache-dir\'' message('Use compatible cache directory: NO') endif # # コンパイラーの追加オプション # # gprof support gprof = get_option('gprof').enabled() if gprof args = ['-pg'] if cpp_compiler.has_multi_arguments(args) add_project_arguments(args, language: 'cpp') add_project_link_arguments(args, language: 'cpp') configure_args += '\'--enable-gprof\'' else error('not support -pg') endif message('Output profile information for gprof : YES') endif # CPUの最適化オプション native = get_option('native').enabled() if native args = ['-march=native'] if cpp_compiler.has_multi_arguments(args) add_project_arguments(args, language : 'cpp') configure_args += '\'--with-native\'' else error('not support -march=native') endif message('Optimize to your machine: YES') endif # # ビルドの情報 # conf.set('HAVE_BUILDINFO_H', 1) if configure_args.length() > 0 conf.set_quoted('CONFIGURE_ARGS', ' '.join(configure_args)) endif subdir('src') if build_tests subdir('test', if_found : gtest_main_dep) endif # TODO: meson 0.53 から summary() が使用できる message('*** Configuration ***') message('alsa = @0@'.format(alsa_dep.found())) message('build_tests = @0@'.format(build_tests)) message('compat_cache_dir = @0@'.format(compat_cache_dir)) message('gprof = @0@'.format(gprof)) message('migemo = @0@'.format(migemo_dep.found())) message('migemodict = @0@'.format(migemodict)) message('native = @0@'.format(native)) message('pangolayout = @0@'.format(pangolayout)) message('regex = @0@'.format(regex_opt)) message('sessionlib = @0@'.format(sessionlib_opt)) message('tls = @0@'.format(tls_opt)) message('*********************') # # プログラムと一緒にインストールするアイコンや設定など # install_data('jdim.png', install_dir : get_option('datadir') / 'icons/hicolor/48x48/apps') install_data('jdim.svg', install_dir : get_option('datadir') / 'icons/hicolor/scalable/apps') install_data('jdim.desktop', install_dir : get_option('datadir') / 'applications') install_data('jdim.metainfo.xml', install_dir : get_option('datadir') / 'metainfo') jdim-0.7.0/meson_options.txt000066400000000000000000000022411417047150700161030ustar00rootroot00000000000000option('alsa', type : 'feature', value : 'disabled', description : 'Use sound effects') option('build_tests', type : 'feature', value : 'auto', description : 'Build tests if gtest is found') option('compat_cache_dir', type : 'feature', value : 'enabled', description : 'Use compatible cache directory') option('gprof', type : 'feature', value : 'disabled', description : 'Output profile information for gprof') option('migemo', type : 'feature', value : 'disabled', description : 'Use text search by romaji') option('migemodict', type : 'string', value : '', description : 'Default path for migemo dictionary file') option('native', type : 'feature', value : 'disabled', description : 'Optimize to your machine') option('pangolayout', type : 'feature', value : 'disabled', description : 'Render text by PangoLayout') option('regex', type : 'combo', choices : ['oniguruma', 'glib'], value : 'glib', description : 'Regex library to use') option('sessionlib', type : 'combo', choices : ['xsmp', 'no'], value : 'xsmp', description : 'Use Session Management Protocol') option('tls', type : 'combo', choices : ['gnutls', 'openssl'], value : 'gnutls', description : 'SSL/TLS library to use') jdim-0.7.0/snap/000077500000000000000000000000001417047150700134105ustar00rootroot00000000000000jdim-0.7.0/snap/snapcraft.yaml000066400000000000000000000040351417047150700162570ustar00rootroot00000000000000name: jdim adopt-info: jdim license: "GPL-2.0" confinement: strict base: core18 grade: stable icon: jdim.png # https://snapcraft.io/gnome-3-34-1804 # Snap Store does not provide gnome-3-34-1804 package for i386, ppc64el and s390x architectures: - build-on: amd64 run-on: amd64 - build-on: arm64 run-on: arm64 - build-on: armhf run-on: armhf # https://snapcraft.io/blog/gnome-3-34-snapcraft-extension parts: jdim: plugin: meson source: https://github.com/JDimproved/JDim.git source-type: git source-branch: master source-depth: 1 meson-parameters: # Use dpkg-buildflags to set compiler flags instead of meson options - --buildtype=plain - --prefix=/ - -Dbuild_tests=disabled - -Dcompat_cache_dir=disabled - -Dpangolayout=enabled build-environment: # https://wiki.debian.org/Hardening # Use -isystem to suppress compiler warning: # /snap/gnome-3-34-1804-sdk/current/usr/include/limits.h:124:3: warning: #include_next is a GCC extension - CPPFLAGS: "$(dpkg-buildflags --get CPPFLAGS) -isystem /snap/gnome-3-34-1804-sdk/current/usr/include" - CXXFLAGS: "$(dpkg-buildflags --get CXXFLAGS)" - LDFLAGS: "$(dpkg-buildflags --get LDFLAGS)" build-packages: - libgnutls28-dev - libsigc++-2.0-dev override-build: | set -eu snapcraftctl build strip -s ${SNAPCRAFT_PART_INSTALL}/bin/jdim VER="$(${SNAPCRAFT_PART_INSTALL}/bin/jdim -V | sed -n -e '1s%^[^0-9]*\([^-]\+\)-\([^(]\+\)(git:\([0-9a-f]\+\).*$%\1-\2-\3%p')" echo "version ${VER}" snapcraftctl set-version "${VER}" override-prime: | set -eu snapcraftctl prime sed --in-place -e 's|^Icon=.*|Icon=\${SNAP}/share/icons/hicolor/48x48/apps/jdim.png|' \ ${SNAPCRAFT_PRIME}/share/applications/jdim.desktop parse-info: [jdim.metainfo.xml] apps: jdim: command: bin/jdim common-id: com.github.jdimproved.jdim desktop: share/applications/jdim.desktop extensions: [gnome-3-34] plugs: - home - network jdim-0.7.0/src/000077500000000000000000000000001417047150700132365ustar00rootroot00000000000000jdim-0.7.0/src/Makefile.am000066400000000000000000000050201417047150700152670ustar00rootroot00000000000000SUBDIRS = dbtree dbimg bbslist board article image message jdlib skeleton history config icons sound xml control bin_PROGRAMS = jdim jdim_LDADD = \ ./dbimg/libdbimg.a \ ./bbslist/libbbslist.a \ ./board/libboard.a \ ./article/libarticle.a \ ./dbtree/libdbtree.a \ ./image/libimage.a \ ./message/libmessage.a \ ./skeleton/libskeleton.a \ ./history/libhistory.a \ ./config/libconfig.a \ ./jdlib/libjdlib.a \ ./icons/libicon.a \ ./sound/libsound.a \ ./xml/libxml.a \ ./control/libcontrol.a \ @LIBS@ @GTKMM_LIBS@ @GNUTLS_LIBS@ @OPENSSL_LIBS@ @LIBSM_LIBS@ @ALSA_LIBS@ @ONIG_LIBS@ @X11_LIBS@ jdim_SOURCES = \ main.cpp \ winmain.cpp \ core.cpp \ menuslots.cpp \ maintoolbar.cpp \ cache.cpp \ command.cpp \ sharedbuffer.cpp \ dndmanager.cpp \ usrcmdmanager.cpp \ linkfiltermanager.cpp \ replacestrmanager.cpp \ urlreplacemanager.cpp \ compmanager.cpp \ searchmanager.cpp \ searchloader.cpp \ aamanager.cpp \ dispatchmanager.cpp \ cssmanager.cpp \ updatemanager.cpp \ browsers.cpp \ setupwizard.cpp \ \ session.cpp \ login2ch.cpp \ loginbe.cpp \ \ viewfactory.cpp \ \ prefdiagfactory.cpp \ mainitempref.cpp \ boarditempref.cpp \ articleitempref.cpp \ articleitemmenupref.cpp \ searchitempref.cpp \ boarditemmenupref.cpp \ msgitempref.cpp \ sidebaritempref.cpp \ fontcolorpref.cpp \ usrcmdpref.cpp \ linkfilterpref.cpp \ replacestrpref.cpp \ livepref.cpp \ openurldiag.cpp \ \ iomonitor.cpp \ environment.cpp noinst_HEADERS = \ winmain.h \ httpcode.h \ core.h \ maintoolbar.h \ cache.h \ jddebug.h \ global.h \ jdversion.h \ command.h \ sharedbuffer.h \ dndmanager.h \ usrcmdmanager.h \ linkfiltermanager.h \ replacestrmanager.h \ urlreplacemanager.h \ compmanager.h \ searchmanager.h \ searchloader.h \ aamanager.h \ dispatchmanager.h \ cssmanager.h \ updatemanager.h \ colorid.h \ fontid.h \ command_args.h \ browsers.h \ setupwizard.h \ sign.h \ \ session.h \ login2ch.h \ loginbe.h \ \ viewfactory.h \ \ prefdiagfactory.h \ proxypref.h \ browserpref.h \ globalabonepref.h \ globalabonethreadpref.h \ passwdpref.h \ privacypref.h \ mainitempref.h \ boarditempref.h \ articleitempref.h \ articleitemmenupref.h \ searchitempref.h \ boarditemmenupref.h \ msgitempref.h \ sidebaritempref.h \ fontcolorpref.h \ usrcmdpref.h \ linkfilterpref.h \ replacestrpref.h \ livepref.h \ openurldiag.h \ \ iomonitor.h \ environment.h \ boardcolumnsid.h \ data_info.h \ gtkmmversion.h \ type.h AM_CXXFLAGS = @GTKMM_CFLAGS@ @GNUTLS_CFLAGS@ @OPENSSL_CFLAGS@ @LIBSM_CFLAGS@ jdim-0.7.0/src/aamanager.cpp000066400000000000000000000174101417047150700156610ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "aamanager.h" #include "jdlib/miscutil.h" #include "xml/document.h" #include "xml/tools.h" #include "config/globalconf.h" #include "cache.h" #include "type.h" CORE::AAManager* instance_aamanager = nullptr; CORE::AAManager* CORE::get_aamanager() { if( ! instance_aamanager ) instance_aamanager = new CORE::AAManager(); return instance_aamanager; } void CORE::delete_aamanager() { if( instance_aamanager ) delete instance_aamanager; instance_aamanager = nullptr; } //////////////////////////////////// // ルート要素名 #define ROOT_NODE_NAME "history" enum { AA_LIMIT = 4096 }; using namespace CORE; AAManager::AAManager() { #ifdef _DEBUG std::cout << "AAManager::AAManager\n"; #endif load_label(); load_history(); } AAManager::~AAManager() { #ifdef _DEBUG std::cout << "AAManager::~AAManager\n"; #endif } // // ラベル、AA読み込み // void AAManager::load_label() { std::string aa_lines; if( CACHE::load_rawdata( CACHE::path_aalist(), aa_lines ) ){ std::list< std::string > list_label = MISC::get_lines( aa_lines ); list_label = MISC::remove_nullline_from_list( list_label ); std::list< std::string >::iterator it = list_label.begin(); for( int id = 0 ; it != list_label.end() ; ++it, ++id ) { std::string asciiart = *it; m_vec_label.push_back( asciiart ); #ifdef _DEBUG std::cout << "id = " << id << " label = " << asciiart << std::endl; #endif // 先頭に"**"がある場合は、"*"を一つ取り除いて一行AAとして扱う if( asciiart.rfind( "**", 0 ) == 0 ) { // **example -> *example asciiart.erase( 0, 1 ); } // "*"が一つだけある場合は、"*"を取り除いてファイル名とみなす else if( asciiart.rfind( '*', 0 ) == 0 ) { // *example -> example asciiart.erase( 0, 1 ); // example -> .jd/aa/example std::string aafile_path = CACHE::path_aadir().append( asciiart ); #ifdef _DEBUG std::cout << "load : " << aafile_path << std::endl; #endif // ファイルが存在しなければ".txt"を追加( .jd/aa/example -> .jd/aa/example.txt ) if( CACHE::file_exists( aafile_path ) != CACHE::EXIST_FILE ) aafile_path.append( ".txt" ); // ファイル読み込み if( CACHE::file_exists( aafile_path ) != CACHE::EXIST_FILE ) asciiart = aafile_path + " が存在しません"; else if( CACHE::get_filesize( aafile_path ) > AA_LIMIT ) asciiart = "ファイルサイズが大きすぎます"; else CACHE::load_rawdata( aafile_path, asciiart ); } #ifdef _DEBUG std::cout << "AA : " << asciiart << std::endl; #endif m_vec_aa.push_back( asciiart ); // ショートカット char shortcut = 0; const int base_a = 10; const int base_A = base_a + 'z' - 'a' -4 + 1; if( id <= 9 ) shortcut = '0' + id; else if( id <= base_a + 'z' - 'a' -4 ){ shortcut = 'a' + id - base_a; if( shortcut >= 'h' ) ++shortcut; // hを除く if( shortcut >= 'j' ) shortcut += 3; // j,k,lを除く } else if( id <= base_A + 'Z' - 'A' ){ shortcut = 'A' + id - base_A; } if( shortcut ) m_map_shortcut.insert( std::make_pair( id, shortcut ) ); } } } // // 履歴読み込み // void AAManager::load_history() { #ifdef _DEBUG std::cout << "AAManager::load_history\n"; #endif std::string xml; if( ! CACHE::load_rawdata( CACHE::path_aahistory(), xml ) ) return; #ifdef _DEBUG std::cout << xml << std::endl; #endif const XML::Document document( xml ); const XML::Dom* root = document.get_root_element( std::string( ROOT_NODE_NAME ) ); if( ! root ) return; std::list< std::string > tmp_history; for( const XML::Dom* child : *root ) { if( static_cast( tmp_history.size() ) >= CONFIG::get_aahistory_size() ) break; if( child->nodeType() == XML::NODE_TYPE_ELEMENT ){ const int type = XML::get_type( child->nodeName() ); std::string name = child->getAttribute( "name" ); if( type == TYPE_AA && ! name.empty() ) tmp_history.push_back( std::move( name ) ); } } std::vector< bool > tmp_vec; tmp_vec.resize( get_size() ); for( const std::string& hist : tmp_history ) { for( int i = 0; i < get_size() ; ++i ){ if( ! tmp_vec[ i ] && m_vec_label[ i ] == hist ){ m_history.push_back( i ); tmp_vec[ i ] = true; break; } } } #ifdef _DEBUG for( const int index : m_history ) std::cout << index << std::endl; #endif } // // 履歴保存 // void AAManager::save_history() { #ifdef _DEBUG std::cout << "AAManager::save_history\n"; #endif XML::Document document; XML::Dom* root = document.appendChild( XML::NODE_TYPE_ELEMENT, std::string( ROOT_NODE_NAME ) ); for( const int index : m_history ) { const std::string& name = m_vec_label[ index ]; if( ! name.empty() ){ XML::Dom* node = root->appendChild( XML::NODE_TYPE_ELEMENT, XML::get_name( TYPE_AA ) ); node->setAttribute( "name", name ); } } std::string xml; if( root->hasChildNodes() ) xml = document.get_xml(); #ifdef _DEBUG std::cout << xml << std::endl; #endif if( ! xml.empty() ) CACHE::save_rawdata( CACHE::path_aahistory(), xml ); } // ラベル、AA取得 std::string AAManager::get_label( const int id ) const { if( id >= (int) m_vec_label.size() ) return std::string(); return m_vec_label[ id ]; } std::string AAManager::get_aa( const int id ) const { if( id >= (int) m_vec_aa.size() ) return std::string(); return m_vec_aa[ id ]; } // // ショートカットキー取得 // std::string AAManager::id2shortcut( const int id ) { if( id >= (int) m_map_shortcut.size() ) return std::string(); #ifdef _DEBUG std::cout << "AAManager::id2shortcut id = " << id << std::endl; #endif int key = m_map_shortcut[ id ]; if( !key ) return std::string(); char tmpchar[2]; tmpchar[0] = key; tmpchar[1] = '\0'; return std::string( tmpchar ); } // ショートカットからid取得 int AAManager::shortcut2id( const char key ) const { if( key == '\0' ) return -1; auto it = std::find_if( m_map_shortcut.cbegin(), m_map_shortcut.cend(), [key]( const auto& id_key ) { return id_key.second == key; } ); if( it != m_map_shortcut.cend() ) { return it->first; } return -1; } // id 番を履歴に追加 void AAManager::append_history( const int id ) { if( id >= 0 && id < get_size() ){ // 既に履歴に含まれている場合 auto it = std::find( m_history.cbegin(), m_history.cend(), id ); if( it != m_history.end() ) { m_history.splice( m_history.cbegin(), m_history, it ); return; } // 含まれていない場合 const int size = CONFIG::get_aahistory_size(); if( size > 0 ) { if( static_cast( m_history.size() ) >= size ) m_history.resize( size - 1 ); m_history.push_front( id ); } } } // num 番目の履歴をIDに変換 int AAManager::history2id( const int num ) const { if( num < 0 || num >= get_historysize() ) return -1; auto it = std::next( m_history.cbegin(), num ); #ifdef _DEBUG std::cout << "AAManager::conv_history2id " << num << " -> " << *it << std::endl; #endif return *it; } jdim-0.7.0/src/aamanager.h000066400000000000000000000025431417047150700153270ustar00rootroot00000000000000// ライセンス: GPL2 // // AA 管理クラス // #ifndef _AAMANAGER_H #define _AAMANAGER_H #include #include #include #include namespace CORE { class AAManager { std::list< int > m_history; // 履歴 std::vector< std::string > m_vec_label; // メニューに表示するラベル std::vector< std::string > m_vec_aa; // AA (1行AAの場合はラベルと同じ) std::map< int, char > m_map_shortcut; // ショートカットキー public: AAManager(); virtual ~AAManager(); int get_size() const noexcept { return m_vec_label.size(); } int get_historysize() const noexcept { return m_history.size(); } std::string get_label( const int id ) const; std::string get_aa( const int id ) const; // ショートカットキー取得 std::string id2shortcut( const int id ); // ショートカットからid取得 int shortcut2id( const char key ) const; // id 番を履歴に追加 void append_history( const int id ); // num 番目の履歴をIDに変換 int history2id( const int num ) const; void save_history(); private: void load_label(); void load_history(); }; CORE::AAManager* get_aamanager(); void delete_aamanager(); } #endif jdim-0.7.0/src/article/000077500000000000000000000000001417047150700146615ustar00rootroot00000000000000jdim-0.7.0/src/article/Makefile.am000066400000000000000000000016251417047150700167210ustar00rootroot00000000000000noinst_LIBRARIES = libarticle.a libarticle_a_SOURCES = \ articleadmin.cpp \ \ drawareabase.cpp \ drawareamain.cpp \ drawareainfo.cpp \ drawareapopup.cpp \ embeddedimage.cpp \ \ articleviewbase.cpp \ articleview.cpp \ articleviewsearch.cpp \ articleviewpreview.cpp \ articleviewinfo.cpp \ articleviewpopup.cpp \ articleviewetc.cpp \ \ layouttree.cpp \ font.cpp \ preference.cpp \ toolbar.cpp \ toolbarsimple.cpp \ toolbarsearch.cpp noinst_HEADERS = \ articleadmin.h \ \ drawareabase.h \ drawareamain.h \ drawareainfo.h \ drawareapopup.h \ embeddedimage.h \ \ articleviewbase.h \ articleview.h \ articleviewsearch.h \ articleviewpreview.h \ articleviewinfo.h \ articleviewpopup.h \ articleviewetc.h \ \ layouttree.h \ font.h \ preference.h \ caret.h \ scrollinfo.h \ toolbar.h \ toolbarsimple.h \ toolbarsearch.h AM_CXXFLAGS = @GTKMM_CFLAGS@ AM_CPPFLAGS = -I$(top_srcdir)/src jdim-0.7.0/src/article/articleadmin.cpp000066400000000000000000000564361417047150700200370ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "articleadmin.h" #include "articleviewbase.h" #include "font.h" #include "toolbar.h" #include "toolbarsimple.h" #include "toolbarsearch.h" #include "dbtree/interface.h" #include "jdlib/miscutil.h" #include "jdlib/jdregex.h" #include "jdlib/miscmsg.h" #include "jdlib/timeout.h" #include "skeleton/view.h" #include "skeleton/dragnote.h" #include "icons/iconmanager.h" #include "history/historymanager.h" #include "global.h" #include "type.h" #include "viewfactory.h" #include "sharedbuffer.h" #include "session.h" #include "command.h" #include "config/globalconf.h" #include "dndmanager.h" ARTICLE::ArticleAdmin *instance_articleadmin = nullptr; ARTICLE::ArticleAdmin* ARTICLE::get_admin() { if( ! instance_articleadmin ) instance_articleadmin = new ARTICLE::ArticleAdmin( URL_ARTICLEADMIN ); assert( instance_articleadmin ); return instance_articleadmin; } void ARTICLE::delete_admin() { if( instance_articleadmin ) delete instance_articleadmin; instance_articleadmin = nullptr; } using namespace ARTICLE; ArticleAdmin::ArticleAdmin( const std::string& url ) : SKELETON::Admin( url ) { set_use_viewhistory( true ); set_use_switchhistory( true ); ARTICLE::init_font(); get_notebook()->set_dragable( true ); get_notebook()->set_fixtab( false ); if( ! SESSION::get_show_article_tab() ) get_notebook()->set_show_tabs( false ); setup_menu(); // スムーススクロール用タイマセット // オートスクロール時など、スムースにスクロールをするため描画用タイマーを // メインタイマと別にする。DrawAreaBase::clock_in_smooth_scroll() も参照すること sigc::slot< bool > slot_timeout = sigc::bind( sigc::mem_fun(*this, &ArticleAdmin::clock_in_smooth_scroll ), 0 ); m_conn_timer = JDLIB::Timeout::connect( slot_timeout, TIMER_TIMEOUT_SMOOTH_SCROLL ); } ArticleAdmin::~ArticleAdmin() { #ifdef _DEBUG std::cout << "ArticleAdmin::~ArticleAdmin\n"; #endif ARTICLE::init_font(); } void ArticleAdmin::save_session() { Admin::save_session(); SESSION::set_article_URLs( get_URLs() ); SESSION::set_article_locked( get_locked() ); SESSION::set_article_switchhistory( get_switchhistory() ); SESSION::set_article_page( get_current_page() ); } bool ArticleAdmin::clock_in_smooth_scroll( int timer_number ) { // アクティブなビューにクロックを送る ArticleViewBase* view = dynamic_cast< ArticleViewBase* >( get_current_view() ); if( view ) view->clock_in_smooth_scroll(); return true; } // 前回開いていたURLを復元 void ArticleAdmin::restore( const bool only_locked ) { #ifdef _DEBUG std::cout << "ArticleAdmin::restore\n"; #endif int set_page_num = 0; const bool online = SESSION::is_online(); SESSION::set_online( false ); const std::list< std::string >& list_url = SESSION::get_article_URLs(); std::list< std::string >::const_iterator it_url = list_url.begin(); std::list< std::string > list_switchhistory = SESSION::get_article_switchhistory(); const std::list< bool >& list_locked = SESSION::get_article_locked(); std::list< bool >::const_iterator it_locked = list_locked.begin(); for( int page = 0; it_url != list_url.end(); ++it_url, ++page ){ // タブのロック状態 bool lock = false; if( it_locked != list_locked.end() ){ if( (*it_locked ) ) lock = true; ++it_locked; } // ロックされているものだけ表示 if( only_locked && ! lock ){ list_switchhistory.remove( *it_url ); continue; } if( page == SESSION::article_page() ) set_page_num = get_tab_nums(); COMMAND_ARGS command_arg = url_to_openarg( *it_url, true, lock ); // 板がDBに登録されていない場合は表示しない if( command_arg.url != URL_SEARCH_ALLBOARD && command_arg.arg4 != "SEARCHTITLE" && command_arg.arg4 != "POSTLOG" && DBTREE::url_boardbase( *it_url ).empty() ){ MISC::ERRMSG( *it_url + " is not registered" ); list_switchhistory.remove( *it_url ); continue; } if( command_arg.arg4 == "MAIN" && DBTREE::url_dat( *it_url ).empty() ){ list_switchhistory.remove( *it_url ); continue; } // Admin::open_view() 中の create_viewhistory()やappend_viewhistory()を実行しない // Admin::Open_view()も参照すること const bool use_history = get_use_viewhistory(); set_use_viewhistory( false ); open_view( command_arg ); set_use_viewhistory( use_history ); } set_switchhistory( list_switchhistory ); SESSION::set_online( online ); if( get_tab_nums() ) set_command( "set_page", std::string(), std::to_string( set_page_num ) ); } COMMAND_ARGS ArticleAdmin::url_to_openarg( const std::string& url, const bool tab, const bool lock ) { JDLIB::Regex regex; const size_t offset = 0; const bool icase = false; const bool newline = true; const bool usemigemo = false; const bool wchar = false; COMMAND_ARGS command_arg; command_arg.command = "open_view"; command_arg.url = std::string(); #ifdef _DEBUG std::cout << "ArticleAdmin::url_to_openarg url = " << url << std::endl; #endif // レス抽出 if( regex.exec( std::string( "(.*)" ) + ARTICLE_SIGN + RES_SIGN + "(.*)" + CENTER_SIGN + "(.*)", url, offset, icase, newline, usemigemo, wchar )){ command_arg.url = regex.str( 1 ); if( tab ) command_arg.arg1 = "true"; // タブで開く command_arg.arg2 = "false"; // command.url を開いてるかチェックする if( lock ) command_arg.arg3 = "lock"; command_arg.arg4 = "RES"; command_arg.arg5 = regex.str( 2 ); if( regex.str( 3 ) != "0" ) command_arg.arg6 = regex.str( 3 ); } // 名前抽出 else if( regex.exec( std::string( "(.*)" ) + ARTICLE_SIGN + NAME_SIGN + "(.*)", url, offset, icase, newline, usemigemo, wchar )){ command_arg.url = regex.str( 1 ); if( tab ) command_arg.arg1 = "true"; // タブで開く command_arg.arg2 = "false"; // command.url を開いてるかチェックする if( lock ) command_arg.arg3 = "lock"; command_arg.arg4 = "NAME"; command_arg.arg5 = regex.str( 2 ); } // ID抽出 else if( regex.exec( std::string( "(.*)" ) + ARTICLE_SIGN + ID_SIGN + "(.*)", url, offset, icase, newline, usemigemo, wchar )){ command_arg.url = regex.str( 1 ); if( tab ) command_arg.arg1 = "true"; // タブで開く command_arg.arg2 = "false"; // command.url を開いてるかチェックする if( lock ) command_arg.arg3 = "lock"; command_arg.arg4 = "ID"; command_arg.arg5 = regex.str( 2 ); } // ブックマーク抽出 else if( regex.exec( std::string( "(.*)" ) + ARTICLE_SIGN + BOOKMK_SIGN, url, offset, icase, newline, usemigemo, wchar )){ command_arg.url = regex.str( 1 ); if( tab ) command_arg.arg1 = "true"; // タブで開く command_arg.arg2 = "false"; // command.url を開いてるかチェックする if( lock ) command_arg.arg3 = "lock"; command_arg.arg4 = "BM"; } // 書き込み抽出 else if( regex.exec( std::string( "(.*)" ) + ARTICLE_SIGN + POST_SIGN, url, offset, icase, newline, usemigemo, wchar )){ command_arg.url = regex.str( 1 ); if( tab ) command_arg.arg1 = "true"; // タブで開く command_arg.arg2 = "false"; // command.url を開いてるかチェックする if( lock ) command_arg.arg3 = "lock"; command_arg.arg4 = "POST"; } // 高参照レス抽出 else if( regex.exec( std::string( "(.*)" ) + ARTICLE_SIGN + HIGHREFRES_SIGN, url, offset, icase, newline, usemigemo, wchar )){ command_arg.url = regex.str( 1 ); if( tab ) command_arg.arg1 = "true"; // タブで開く command_arg.arg2 = "false"; // command.url を開いてるかチェックする if( lock ) command_arg.arg3 = "lock"; command_arg.arg4 = "HIGHREFRES"; } // URL抽出 else if( regex.exec( std::string( "(.*)" ) + ARTICLE_SIGN + URL_SIGN, url, offset, icase, newline, usemigemo, wchar )){ command_arg.url = regex.str( 1 ); if( tab ) command_arg.arg1 = "true"; // タブで開く command_arg.arg2 = "false"; // command.url を開いてるかチェックする if( lock ) command_arg.arg3 = "lock"; command_arg.arg4 = "URL"; } // 参照 else if( regex.exec( std::string( "(.*)" ) + ARTICLE_SIGN + REFER_SIGN + "(.*)", url, offset, icase, newline, usemigemo, wchar )){ command_arg.url = regex.str( 1 ); if( tab ) command_arg.arg1 = "true"; // タブで開く command_arg.arg2 = "false"; // command.url を開いてるかチェックする if( lock ) command_arg.arg3 = "lock"; command_arg.arg4 = "REF"; command_arg.arg5 = regex.str( 2 ); } // キーワード else if( regex.exec( std::string( "(.*)" ) + ARTICLE_SIGN + KEYWORD_SIGN + "(.*)" + ORMODE_SIGN + "(.*)", url, offset, icase, newline, usemigemo, wchar )){ command_arg.url = regex.str( 1 ); if( tab ) command_arg.arg1 = "true"; // タブで開く command_arg.arg2 = "false"; // command.url を開いてるかチェックする if( lock ) command_arg.arg3 = "lock"; if( regex.str( 3 ) == "1" ) command_arg.arg4 = "KEYWORD_OR"; else command_arg.arg4 = "KEYWORD"; command_arg.arg5 = regex.str( 2 ); } // 書き込みログ表示 else if( regex.exec( std::string( "(.*)" ) + POSTLOG_SIGN + "(.*)", url, offset, icase, newline, usemigemo, wchar ) ){ command_arg.url = regex.str( 1 ); if( tab ) command_arg.arg1 = "true"; // タブで開く command_arg.arg2 = "false"; // command.url を開いてるかチェックする if( lock ) command_arg.arg3 = "lock"; command_arg.arg4 = "POSTLOG"; command_arg.arg5 = regex.str( 2 ); // ログ番号 } // キャッシュのログ検索 else if( regex.exec( std::string( "(.*)" ) + BOARD_SIGN + KEYWORD_SIGN + "(.*)" + ORMODE_SIGN + "(.*)" + BOOKMK_SIGN + "(.*)", url, offset, icase, newline, usemigemo, wchar )){ command_arg.url = regex.str( 1 ); if( tab ) command_arg.arg1 = "true"; // タブで開く command_arg.arg2 = "false"; // command.url を開いてるかチェックする if( lock ) command_arg.arg3 = "lock"; if( command_arg.url == URL_SEARCH_ALLBOARD ) command_arg.arg4 = "SEARCHALLLOG"; else command_arg.arg4 = "SEARCHLOG"; command_arg.arg5 = regex.str( 2 ); // query command_arg.arg6 = "noexec"; // Viewを開いた直後に検索を実行しない if( regex.str( 3 ) == "1" ) command_arg.arg7 = "OR"; if( regex.str( 4 ) == "1" ) command_arg.arg8 = "BM"; } // スレタイ検索 else if( regex.exec( std::string( "(.*)" ) + TITLE_SIGN + KEYWORD_SIGN + "(.*)", url, offset, icase, newline, usemigemo, wchar )){ command_arg.url = regex.str( 1 ); if( tab ) command_arg.arg1 = "true"; // タブで開く command_arg.arg2 = "false"; // command.url を開いてるかチェックする if( lock ) command_arg.arg3 = "lock"; command_arg.arg4 = "SEARCHTITLE"; command_arg.arg5 = regex.str( 2 ); // query command_arg.arg6 = "noexec"; // Viewを開いた直後に検索を実行しない } // 通常のスレ else if( !url.empty() ){ command_arg.url = url; if( tab ) command_arg.arg1 = "true"; // タブで開く command_arg.arg2 = "false"; // 既に開いているかチェック if( lock ) command_arg.arg3 = "lock"; command_arg.arg4 = "MAIN"; } #ifdef _DEBUG std::cout << command_arg.url << std::endl << command_arg.arg1 << std::endl << command_arg.arg2 << std::endl << command_arg.arg3 << std::endl << command_arg.arg4 << std::endl << command_arg.arg5 << std::endl << command_arg.arg6 << std::endl << command_arg.arg7 << std::endl << command_arg.arg8 << std::endl << std::endl; #endif return command_arg; } std::string ArticleAdmin::command_to_url( const COMMAND_ARGS& command ) { std::string url; if( command.arg4 == "RES" ){ url = command.url + ARTICLE_SIGN + RES_SIGN + command.arg5 + CENTER_SIGN; if( ! command.arg6.empty() ) url += command.arg6; else url += "0"; return url; } if( command.arg4 == "NAME" ) return command.url + ARTICLE_SIGN + NAME_SIGN + command.arg5; if( command.arg4 == "ID" ) return command.url + ARTICLE_SIGN + ID_SIGN + command.arg5; if( command.arg4 == "BM" ) return command.url + ARTICLE_SIGN + BOOKMK_SIGN; if( command.arg4 == "POST" ) return command.url + ARTICLE_SIGN + POST_SIGN; if( command.arg4 == "HIGHREFRES" ) return command.url + ARTICLE_SIGN + HIGHREFRES_SIGN; if( command.arg4 == "URL" ) return command.url + ARTICLE_SIGN + URL_SIGN; if( command.arg4 == "REF" ) return command.url + ARTICLE_SIGN + REFER_SIGN + command.arg5; if( command.arg4 == "KEYWORD" ) return command.url + ARTICLE_SIGN + KEYWORD_SIGN + command.arg5 + ORMODE_SIGN + "0"; if( command.arg4 == "KEYWORD_OR" ) return command.url + ARTICLE_SIGN + KEYWORD_SIGN + command.arg5 + ORMODE_SIGN + "1"; if( command.arg4 == "POSTLOG" ) return command.url + POSTLOG_SIGN + command.arg5; if( command.arg4 == "SEARCHALLLOG" || command.arg4 == "SEARCHLOG" ){ url = command.url + BOARD_SIGN + KEYWORD_SIGN + command.arg5; url += ORMODE_SIGN; if( command.arg7 == "OR" ) url += "1"; else url += "0"; url += BOOKMK_SIGN; if( command.arg8 == "BM" ) url += "1"; else url += "0"; return url; } if( command.arg4 == "SEARCHTITLE" ) return command.url + TITLE_SIGN + KEYWORD_SIGN + command.arg5; return command.url; } void ArticleAdmin::switch_admin() { if( ! has_focus() ) CORE::core_set_command( "switch_article" ); } void ArticleAdmin::restore_lasttab() { HISTORY::restore_history( URL_HISTCLOSEVIEW ); } // // リストで与えられたページをタブで連続して開くとき(Admin::open_list())の引数セット // COMMAND_ARGS ArticleAdmin::get_open_list_args( const std::string& url, const COMMAND_ARGS& command_list ) { COMMAND_ARGS command_arg; command_arg.arg4 = "MAIN"; return command_arg; } // // カレントビューでポップアップ表示していたら消す // void ArticleAdmin::delete_popup() { SKELETON::View* view = get_current_view(); if( view ) view->set_command( "delete_popup" ); } // // 全ポップアップを消す // void ArticleAdmin::delete_all_popups() { std::list< SKELETON::View* > list_view = get_list_view(); for( SKELETON::View* view : list_view ) { if( view ) view->set_command( "delete_popup" ); } } // // view の作成 // SKELETON::View* ArticleAdmin::create_view( const COMMAND_ARGS& command ) { #ifdef _DEBUG std::cout << "ArticleAdmin::create_view : " << command.arg4 << std::endl; #endif delete_popup(); int type = CORE::VIEW_NONE; CORE::VIEWFACTORY_ARGS view_args; // メインビュー if( command.arg4 == "MAIN" ){ type = CORE::VIEW_ARTICLEVIEW; } // レス抽出ビュー else if( command.arg4 == "RES" ){ type = CORE::VIEW_ARTICLERES; } // 名前抽出ビュー else if( command.arg4 == "NAME" ){ type = CORE::VIEW_ARTICLENAME; } // ID 抽出ビュー else if( command.arg4 == "ID" ){ type = CORE::VIEW_ARTICLEID; } // ブックマーク抽出ビュー else if( command.arg4 == "BM" ){ type = CORE::VIEW_ARTICLEBM; } // 書き込み抽出ビュー else if( command.arg4 == "POST" ){ type = CORE::VIEW_ARTICLEPOST; } // 高参照レス抽出ビュー else if( command.arg4 == "HIGHREFRES" ){ type = CORE::VIEW_ARTICLEHIGHREFRES; } // URL抽出ビュー else if( command.arg4 == "URL" ){ type = CORE::VIEW_ARTICLEURL; } // 参照抽出ビュー else if( command.arg4 == "REF" ){ type = CORE::VIEW_ARTICLEREFER; } // キーワード抽出ビュー else if( command.arg4 == "KEYWORD" || command.arg4 == "KEYWORD_OR" ){ type = CORE::VIEW_ARTICLEDRAWOUT; } // 書き込みログ表示 else if( command.arg4 == "POSTLOG" ){ type = CORE::VIEW_ARTICLEPOSTLOG; } // ログ検索 else if( command.arg4 == "SEARCHLOG" ){ type = CORE::VIEW_ARTICLESEARCHLOG; view_args.arg1 = command.arg6; // exec } // 全キャッシュログ検索 else if( command.arg4 == "SEARCHALLLOG" ){ type = CORE::VIEW_ARTICLESEARCHALLLOG; view_args.arg1 = command.arg6; // exec } // スレタイ検索 else if( command.arg4 == "SEARCHTITLE" ){ type = CORE::VIEW_ARTICLESEARCHTITLE; view_args.arg1 = command.arg6; // exec } else return nullptr; SKELETON::View* view = CORE::ViewFactory( type, command_to_url( command ), view_args ); assert( view != nullptr ); return view; } // // ツールバー表示 // void ArticleAdmin::show_toolbar() { // まだ作成されていない場合は作成する if( ! m_toolbar ){ // 通常のツールバー( TOOLBAR_ARTICLE ) m_toolbar = std::make_unique(); get_notebook()->append_toolbar( *m_toolbar ); // 簡易版ツールバー( TOOLBAR_SIMPLE ) m_toolbarsimple = std::make_unique(); get_notebook()->append_toolbar( *m_toolbarsimple ); // ログ検索などのツールバー( TOOLBAR_SEARCH ) m_search_toolbar = std::make_unique(); get_notebook()->append_toolbar( *m_search_toolbar ); if( SESSION::get_show_article_toolbar() ){ m_toolbar->open_buttonbar(); m_toolbarsimple->open_buttonbar(); m_search_toolbar->open_buttonbar(); } } get_notebook()->show_toolbar(); } // // ツールバー表示/非表示切り替え // void ArticleAdmin::toggle_toolbar() { if( ! m_toolbar ) return; if( SESSION::get_show_article_toolbar() ){ m_toolbar->open_buttonbar(); m_toolbarsimple->open_buttonbar(); m_search_toolbar->open_buttonbar(); switch( get_notebook()->get_current_toolbar() ){ case TOOLBAR_ARTICLE: m_toolbar->show_toolbar(); break; case TOOLBAR_SIMPLE: m_toolbarsimple->show_toolbar(); break; case TOOLBAR_SEARCH: m_search_toolbar->show_toolbar(); break; } } else{ m_toolbar->close_buttonbar(); m_toolbarsimple->close_buttonbar(); m_search_toolbar->close_buttonbar(); } } // // 検索バー表示 // void ArticleAdmin::open_searchbar() { if( ! m_toolbar ) return; SKELETON::View* view = get_current_view(); if( ! view ) return; m_toolbar->open_searchbar(); m_toolbarsimple->open_searchbar(); m_search_toolbar->open_searchbar(); switch( get_notebook()->get_current_toolbar() ){ case TOOLBAR_ARTICLE: m_toolbar->show_toolbar(); m_toolbar->focus_entry_search(); break; case TOOLBAR_SIMPLE: m_toolbarsimple->show_toolbar(); m_toolbarsimple->focus_entry_search(); break; case TOOLBAR_SEARCH: m_search_toolbar->show_toolbar(); m_search_toolbar->focus_entry_search(); break; } } // // 検索バー非表示 // void ArticleAdmin::close_searchbar() { if( ! m_toolbar ) return; m_toolbar->close_searchbar(); m_toolbarsimple->close_searchbar(); m_search_toolbar->close_searchbar(); } // // ローカルなコマンド // void ArticleAdmin::command_local( const COMMAND_ARGS& command ) { if( command.command == "goto_num" ){ SKELETON::View* view = get_view( command.url ); if( view ) view->set_command( "goto_num", command.arg1, command.arg2 ); } // ポップアップを消去 else if( command.command == "delete_popup" ) delete_popup(); // 全ポップアップを消去 else if( command.command == "delete_all_popups" ) delete_all_popups(); // ポップアップメニューの再作成 else if( command.command == "reset_popupmenu" ){ std::list< SKELETON::View* > list_view = get_list_view(); for( SKELETON::View* view : list_view ) { if( view ) view->set_command( "reset_popupmenu" ); } } // command.url を含むビューを全て再レイアウト else if( command.command == "relayout_views" ){ std::list< SKELETON::View* > list_view = get_list_view( command.url ); for( SKELETON::View* view : list_view ) { if( view ) view->relayout(); } } // フォント初期化 else if( command.command == "init_font" ) ARTICLE::init_font(); // ハイライト解除 else if( command.command == "clear_highlight" ){ SKELETON::View* view = get_view( command.url ); if( view ) view->set_command( "clear_highlight" ); } // 実況開始/停止 else if( command.command == "live_start_stop" ){ SKELETON::View* view = get_view( command.url ); if( view ){ view->set_command( "live_start_stop" ); // ツールバー表示更新 get_notebook()->set_current_toolbar( view->get_id_toolbar(), view ); } } // 実況停止 else if( command.command == "live_stop" ){ SKELETON::View* view = get_view( command.url ); if( view ){ view->set_command( "live_stop" ); // ツールバー表示更新 get_notebook()->set_current_toolbar( view->get_id_toolbar(), view ); } } // 検索ビューなどで、URL とツールバーの状態を一致させる else if( command.command == "set_toolbar_from_url" ){ SKELETON::View* view = get_view( command.url ); if( view ) view->set_command( "set_toolbar_from_url" ); } } // // タブをお気に入りにドロップした時にお気に入りがデータ送信を要求してきた // void ArticleAdmin::slot_drag_data_get( Gtk::SelectionData& selection_data, const int page ) { #ifdef _DEBUG std::cout << "ArticleAdmin::slot_drag_data_get page = " << page << std::endl; #endif SKELETON::View* view = ( SKELETON::View* )get_notebook()->get_nth_page( page ); if( ! view ) return; const std::string url = view->get_url(); CORE::DATA_INFO info; info.type = TYPE_THREAD; info.url = DBTREE::url_readcgi( url, 0, 0 ); info.name = DBTREE::article_subject( info.url ); info.path = Gtk::TreePath( "0" ).to_string(); if( info.url.empty() ) return; #ifdef _DEBUG std::cout << "name = " << info.name << std::endl; #endif CORE::DATA_INFO_LIST list_info; list_info.push_back( info ); CORE::SBUF_set_list( list_info ); selection_data.set( DNDTARGET_FAVORITE, get_url() ); } jdim-0.7.0/src/article/articleadmin.h000066400000000000000000000040611417047150700174670ustar00rootroot00000000000000// ライセンス: GPL2 // // 記事の管理クラス // #ifndef _ARTICLEADMIN_H #define _ARTICLEADMIN_H #include "skeleton/admin.h" #include "sign.h" #include #include #include namespace JDLIB { class Timeout; } namespace ARTICLE { class ArticleToolBar; class ArticleToolBarSimple; class SearchToolBar; enum { TOOLBAR_ARTICLE = 0, TOOLBAR_SIMPLE, TOOLBAR_SEARCH }; class ArticleAdmin : public SKELETON::Admin { std::unique_ptr m_toolbar; std::unique_ptr m_toolbarsimple; std::unique_ptr m_search_toolbar; std::unique_ptr m_conn_timer; public: explicit ArticleAdmin( const std::string& url ); ~ArticleAdmin(); void save_session() override; protected: COMMAND_ARGS get_open_list_args( const std::string& url, const COMMAND_ARGS& command_list ) override; SKELETON::View* create_view( const COMMAND_ARGS& command ) override; // ツールバー void show_toolbar() override; void toggle_toolbar() override; void open_searchbar() override; void close_searchbar() override; void command_local( const COMMAND_ARGS& command ) override; void restore( const bool only_locked ) override; COMMAND_ARGS url_to_openarg( const std::string& url, const bool tab, const bool lock ) override; std::string command_to_url( const COMMAND_ARGS& command ) override; void switch_admin() override; void restore_lasttab() override; private: bool clock_in_smooth_scroll( int timer_number ); void delete_popup(); void delete_all_popups(); // タブをお気に入りにドロップした時にお気に入りがデータ送信を要求してきた void slot_drag_data_get( Gtk::SelectionData& selection_data, const int page ) override; }; ARTICLE::ArticleAdmin* get_admin(); void delete_admin(); } #endif jdim-0.7.0/src/article/articleview.cpp000066400000000000000000000544341417047150700177150ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "articleadmin.h" #include "articleview.h" #include "drawareamain.h" #include "skeleton/msgdiag.h" #include "message/messageadmin.h" #include "dbtree/interface.h" #include "dbtree/articlebase.h" #include "jdlib/miscutil.h" #include "jdlib/misctime.h" #include "config/globalconf.h" #include "sound/soundmanager.h" #include "control/controlid.h" #include "history/historymanager.h" #include "command.h" #include "global.h" #include "httpcode.h" #include "session.h" #include using namespace ARTICLE; enum { CANCEL_RELOAD = 800, // msec 連続リロード防止用カウンタ LIVE_SEC_PLUS = 5, // 実況で更新失敗/成功ごとに増減する更新間隔(秒) LIVE_MAX_RELOAD = 5 // 実況でこの回数連続でリロードに失敗したら実況停止 }; // メインビュー ArticleViewMain::ArticleViewMain( const std::string& url ) : ArticleViewBase( url, url ) { #ifdef _DEBUG std::cout << "ArticleViewMain::ArticleViewMain " << get_url() << " url_article = " << url_article() << std::endl; #endif // オートリロード可 set_enable_autoreload( true ); // 実況可能 set_enable_live( true ); // 実況したままスレを閉じたらJD終了後にスレを削除する // キャンセルするにはもう一度スレを開いて実況しないで閉じる // ArticleViewBase::set_command()も参照 SESSION::remove_delete_list( url_article() ); setup_view(); } ArticleViewMain::~ArticleViewMain() { #ifdef _DEBUG std::cout << "ArticleViewMain::~ArticleViewMain : " << get_url() << " url_article = " << url_article() << std::endl; #endif ArticleViewMain::save_session(); // 閉じたタブ履歴更新 HISTORY::append_history( URL_HISTCLOSEVIEW, DBTREE::url_dat( get_url() ), DBTREE::article_subject( get_url() ), TYPE_THREAD ); CORE::core_set_command( "close_message" ,url_article() ); if( get_live() ) ArticleViewMain::live_stop(); } void ArticleViewMain::save_session() { const int seen = drawarea()->get_seen_current(); #ifdef _DEBUG std::cout << "set seen to " << seen << std::endl; #endif if( seen >= 1 ) get_article()->set_number_seen( seen ); } // // クロック入力 // // virtual void ArticleViewMain::clock_in() { ArticleViewBase::clock_in(); // 実況モードでリロード if( get_live() && ! is_loading() && inc_autoreload_counter() ) exec_reload(); // 更新チェック中にshow_view()が呼び出された場合は // チェックが終わってから改めてロードする if( m_reload_reserve ){ if( ! get_article()->is_checking_update() ){ m_reload_reserve = false; show_view(); } } } // // クロック入力 // clock_in_always()は常に呼び出されるので重い処理を含めてはいけない // // virtual void ArticleViewMain::clock_in_always() { ArticleViewBase::clock_in_always(); if( m_cancel_reload_counter ) --m_cancel_reload_counter; } // // num 番にジャンプ // // ローディング中ならジャンプ予約をしてロード後に update_finish() の中で改めて goto_num() を呼び出す // void ArticleViewMain::goto_num( const int num_to, const int num_from ) { #ifdef _DEBUG std::cout << "ArticleViewMain::goto_num num = " << num_to << " num_from " << num_from << " gotonum_seen = " << m_gotonum_seen << " gotonum_reserve = " << m_gotonum_reserve_to << std::endl; #endif m_gotonum_seen = 0; // m_gotonum_reserve_to を優先させる m_gotonum_reserve_to = num_to; m_gotonum_reserve_from = num_from; if( get_article()->get_number_load() < num_to && is_loading() ){ #ifdef _DEBUG std::cout << "reserve\n"; #endif return; } #ifdef _DEBUG std::cout << "jump\n"; #endif ArticleViewBase::goto_num( num_to, num_from ); } // ロード中 bool ArticleViewMain::is_loading() const { return get_article()->is_loading(); } // 更新した bool ArticleViewMain::is_updated() const { #ifdef _DEBUG std::cout << "ArticleViewMain::is_updated " << url_article() << " " << ( get_article()->get_status() & STATUS_UPDATED ) << std::endl; #endif return ( get_article()->get_status() & STATUS_UPDATED ); } // 更新チェックして更新可能か bool ArticleViewMain::is_check_update() const { #ifdef _DEBUG std::cout << "ArticleViewMain::is_check_update " << url_article() << " " << ( get_article()->get_status() & STATUS_UPDATE ) << std::endl; #endif return ( get_article()->get_status() & STATUS_UPDATE ); } // 古いデータか bool ArticleViewMain::is_old() const { return ( get_article()->get_status() & STATUS_OLD ); } // 壊れているか bool ArticleViewMain::is_broken() const { return ( get_article()->get_status() & STATUS_BROKEN ); } //レス数が最大表示可能数以上か bool ArticleViewMain::is_overflow() const noexcept { return ( get_article()->get_status() & STATUS_OVERFLOW ); } // // 再読み込み実行 // // virtual void ArticleViewMain::exec_reload() { #ifdef _DEBUG std::cout << "ArticleViewMain::exec_reload\n"; #endif // オフライン if( ! SESSION::is_online() ){ SKELETON::MsgDiag mdiag( get_parent_win(), "オフラインです" ); mdiag.run(); if( get_live() ) ARTICLE::get_admin()->set_command( "live_stop", get_url() ); return; } show_view(); } // // キャッシュ表示 & 差分ロード開始 // void ArticleViewMain::show_view() { // 更新チェック中の場合はチェックが終わってからclock_in()でロードする if( get_article()->is_checking_update() ){ m_reload_reserve = true; return; } if( is_loading() ) return; if( m_cancel_reload_counter ){ #ifdef _DEBUG std::cout << "cancel reload\n"; #endif // オートリロードのカウンタを0にする reset_autoreload_counter(); // タブのアイコン状態を更新 ARTICLE::get_admin()->set_command( "toggle_icon", get_url() ); return; } // キャッシュを削除してからスレを再読み込み if( SESSION::is_online() && get_reget() ){ int jump_to = drawarea()->get_seen_current(); if( ! jump_to ) jump_to = get_article()->get_number_seen(); #ifdef _DEBUG std::cout << "ArticleViewMain::show_view reget url_article = " << url_article() << " jump_to = " << jump_to << std::endl; #endif set_reget( false ); CORE::core_set_command( "delete_article", url_article(), "reget", std::to_string( jump_to ) ); return; } m_gotonum_reserve_to = 0; m_gotonum_reserve_from = 0; m_gotonum_seen = 0; m_set_history = false; m_show_instdialog = false; m_playsound = false; m_show_closedialog = false; // オートリロードのカウンタを0にする reset_autoreload_counter(); #ifdef _DEBUG std::cout << "ArticleViewMain::show_view " << url_article() << std::endl; #endif if( get_url().empty() ){ set_status( "invalid URL" ); ARTICLE::get_admin()->set_command( "set_status", get_url(), get_status() ); return; } // 負荷を減らすため update_view() や update_finish() が呼び出されるまで描画不可にしておく drawarea()->set_enable_draw( false ); // articleクラスがまだキャッシュにあるdatを解析していないときに // drawarea()->append_res()を呼ぶと、 nodetree が作られて update_finish() が // コールバックされるので、 キャッシュをまだ読み込んでない時は show_view() の中で // update_finish()を呼ばないようにする。動作をまとめると次のようになる。 // // オフライン かつ // キャッシュを読み込んでいない場合 -> articleでnodetreeが作られた時に update_finish がコールバックされる // キャッシュを読み込んでいる場合 -> show_viewから直接 update_finish を呼ぶ // // オンライン かつ // キャッシュを読み込んでいない場合 -> articleでnodetreeが作られた時に update_finish がコールバックされる //                     ロード終了時にもupdate_finish がコールバックされる // キャッシュを読み込んでいる場合 -> show_viewから直接 update_finish を呼ぶ //                     ロード終了時にもupdate_finish がコールバックされる const bool call_update_finish = get_article()->is_cache_read(); // キャッシュに含まれているレスを表示 const int from_num = drawarea()->max_number() + 1; const int to_num = get_article()->get_number_load(); if( from_num <= to_num ){ drawarea()->append_res( from_num, to_num ); // update_finish()を呼び出したときに以前見ていたところにジャンプ m_gotonum_seen = get_article()->get_number_seen(); } // セパレータを最後に移動 drawarea()->set_separator_new( to_num + 1 ); // update_finish() を呼んでキャッシュの分を描画 if( call_update_finish ){ #ifdef _DEBUG std::cout << "call_update_finish\n"; #endif // update_finish()後に一番最後や新着にジャンプしないように設定を一時的に解除する const bool jump_bottom = CONFIG::get_jump_after_reload(); const bool jump_new = CONFIG::get_jump_new_after_reload(); CONFIG::set_jump_after_reload( false ); CONFIG::set_jump_new_after_reload( false ); // 一時的に実況モード解除 const bool live = get_live(); set_live( false ); if( ! SESSION::is_online() ) m_set_history = true; update_finish(); CONFIG::set_jump_after_reload( jump_bottom ); CONFIG::set_jump_new_after_reload( jump_new ); set_live( live ); } else{ // キャッシュにログが無く、かつオフラインで開くとラベルが表示されないので // ラベルとタブのアイコン状態を更新しておく if( ! SESSION::is_online() ) update_finish(); } m_set_history = true; // オフラインならダウンロードを開始しない if( ! SESSION::is_online() ) return; // 板一覧との切り替え方法説明ダイアログ表示 if( CONFIG::get_instruct_tglart() && SESSION::get_mode_pane() == SESSION::MODE_2PANE ){ m_show_instdialog = true; } m_show_closedialog = true; clear_highlight(); if( ! get_live() && SESSION::is_online() ) m_playsound = true; // 差分 download 開始 const bool check_update = false; get_article()->download_dat( check_update ); if( is_loading() ){ #ifdef _DEBUG std::cout << "loading start\n"; #endif set_status( "loading..." ); ARTICLE::get_admin()->set_command( "set_status", get_url(), get_status(), ( get_live() ? "force" : "" ) ); // タブのアイコン状態を更新 ARTICLE::get_admin()->set_command( "toggle_icon", get_url() ); // スレ一覧などを素早くクリックした時などに2回リロードされるのを防ぐ m_cancel_reload_counter = CANCEL_RELOAD / TIMER_TIMEOUT; } } // // ロード中にノード構造が変わったら呼ばれる // void ArticleViewMain::update_view() { const int code = DBTREE::article_code( url_article() ); const int num_from = drawarea()->max_number() + 1; const int num_to = get_article()->get_number_load(); #ifdef _DEBUG std::cout << "ArticleViewMain::update_view : from " << num_from << " to " << num_to << " play = " << m_playsound << " code = " << code << std::endl; #endif // 音を鳴らす if( m_playsound ){ if( num_to >= num_from ){ // 新着 if( num_from == 1 ) SOUND::play( SOUND::SOUND_NEW ); // 更新 else SOUND::play( SOUND::SOUND_RES ); m_playsound = false; } else{ // 更新無し if( code == HTTP_NOT_MODIFIED ){ SOUND::play( SOUND::SOUND_NO ); m_playsound = false; } // エラー else if( code != HTTP_INIT ){ SOUND::play( SOUND::SOUND_ERR ); m_playsound = false; } } } if( num_from > num_to ) return; #ifdef _DEBUG std::cout << "append " << num_from << " to " << num_to << std::endl; #endif drawarea()->append_res( num_from, num_to ); drawarea()->set_enable_draw( true ); drawarea()->redraw_view(); } // // ロードが終わったときに呼ばれる // void ArticleViewMain::update_finish() { // スレラベルセット std::string str_tablabel; if( is_broken() ) str_tablabel = "[ 壊れています ] "; else if( is_old() ) str_tablabel = "[ DAT落ち ] "; else if( is_overflow() ) str_tablabel = "[ レス数が最大表示可能数以上です ] "; if( get_label().empty() || ! str_tablabel.empty() ) set_label( str_tablabel + DBTREE::article_subject( url_article() ) ); ARTICLE::get_admin()->set_command( "redraw_toolbar" ); // タブのラベルセット std::string str_label = DBTREE::article_subject( url_article() ); if( str_label.empty() ) str_label = "???"; ARTICLE::get_admin()->set_command( "set_tablabel", get_url(), str_label ); // タブのアイコン状態を更新 ARTICLE::get_admin()->set_command( "toggle_icon", get_url() ); #ifdef _DEBUG const int code = DBTREE::article_code( url_article() ); std::cout << "ArticleViewMain::update_finish " << str_label << " code = " << code << std::endl;; #endif // 新着セパレータを消す const int number_load = DBTREE::article_number_load( url_article() ); const int number_new = DBTREE::article_number_new( url_article() ); if( ! number_new ) drawarea()->hide_separator_new(); // ステータス更新 (実況中はフォーカスされてなくても表示) std::string force; if( get_live() ) force = "force"; create_status_message(); ARTICLE::get_admin()->set_command( "set_status", get_url(), get_status(), force ); ARTICLE::get_admin()->set_command( "set_status_color", get_url(), get_color(), force ); // タイトルセット set_title( DBTREE::article_subject( url_article() ) ); ARTICLE::get_admin()->set_command( "set_title", get_url(), get_title() ); drawarea()->set_enable_draw( true ); // ロード中に goto_num() が明示的に呼び出された場合はgoto_num()を呼びつづける if( m_gotonum_reserve_to ){ #ifdef _DEBUG std::cout << "reserve\n"; #endif goto_num( m_gotonum_reserve_to, m_gotonum_reserve_from ); } // 前回見ていた所にジャンプ else if( m_gotonum_seen && number_load >= m_gotonum_seen ){ #ifdef _DEBUG std::cout << "goto_seen\n"; #endif ArticleViewBase::goto_num( m_gotonum_seen, 0 ); m_gotonum_seen = 0; } // ロード後に末尾ジャンプ else if( ! get_live() && CONFIG::get_jump_after_reload() && number_new ){ #ifdef _DEBUG std::cout << "jump_after_reload\n"; #endif goto_bottom(); } // ロード後に新着へジャンプ else if( ! get_live() && CONFIG::get_jump_new_after_reload() && number_new ){ #ifdef _DEBUG std::cout << "jump_new_after_reload\n"; #endif goto_new(); } // 全体再描画 else{ #ifdef _DEBUG std::cout << "redraw\n"; #endif drawarea()->redraw_view(); } // 実況モードで新着がない場合はリロード間隔を空ける if( get_live() ){ const int live_sec = DBTREE::board_get_live_sec( get_url() ); // 新着無し if( ! number_new ){ set_autoreload_sec( get_autoreload_sec() + LIVE_SEC_PLUS ); // 何回かリロードに失敗したら実況モード停止 if( get_autoreload_sec() >= live_sec + LIVE_MAX_RELOAD * LIVE_SEC_PLUS ){ ARTICLE::get_admin()->set_command( "live_stop", get_url() ); } // DAT 落ちしていたら停止 if( is_old() ) ARTICLE::get_admin()->set_command( "live_stop", get_url() ); } // 新着あり else{ set_autoreload_sec( MAX( live_sec, get_autoreload_sec() - LIVE_SEC_PLUS ) ); // messageビューが出ているときはフォーカスを移す if( ! MESSAGE::get_admin()->empty() ){ // 実況モードかつポップアップが表示されている状態で、 "switch_message" コマンドを発行すると、 // 埋め込みメッセージモードだと、 MessageAdmin に対して "focus_current_view" されるので、 // ArticleView に focus_out イベントが発生して、ポップアップが消えてしまう // ポップアップが表示されているときは、フォーカスを移さない if( ! is_popup_shown() ){ CORE::core_set_command( "switch_message" ); } } } drawarea()->update_live_speed( get_autoreload_sec() ); } // 履歴に登録 if( m_set_history ) HISTORY::append_history( URL_HISTTHREADVIEW, DBTREE::url_dat( get_url() ), DBTREE::article_subject( get_url() ), TYPE_THREAD ); if( m_show_instdialog ) show_instruct_diag(); if( m_show_closedialog && !number_load && is_old() ){ SKELETON::MsgDiag mdiag( get_parent_win(), "DAT落ちしたためスレッドを取得できませんでした。\n\nタブを閉じますか?", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO ); mdiag.set_default_response( Gtk::RESPONSE_YES ); if( mdiag.run() == Gtk::RESPONSE_YES ) close_view(); } } // // ステータスに表示する文字列作成 // void ArticleViewMain::create_status_message() { #ifdef _DEBUG std::cout << "ArticleViewMain::create_status_message\n"; #endif const int number_load = DBTREE::article_number_load( url_article() ); const int number_new = DBTREE::article_number_new( url_article() ); std::ostringstream ss_tmp; ss_tmp << DBTREE::article_str_code( url_article() ) << " [ 全 " << number_load << " / 新着 " << number_new; const time_t wtime = DBTREE::article_write_time( url_article() ); if( wtime ) ss_tmp << " / 最終書込 " << ( MISC::timettostr( wtime, MISC::TIME_WEEK ) + " ( " + MISC::timettostr( wtime, MISC::TIME_PASSED ) + " )" ); std::string str_stat; if( is_old() ) str_stat = "[ DAT落ち 又は 移転しました ] "; if( is_check_update() ) str_stat += "[ 更新可能です ] "; if( is_broken() ) str_stat += "[ 壊れています ] "; if( is_overflow() ) str_stat += "[ レス数が最大表示可能数以上です ] "; if( ! DBTREE::article_ext_err( url_article() ).empty() ) str_stat += "[ " + DBTREE::article_ext_err( url_article() ) + " ] "; ss_tmp << " / 速度 " << DBTREE::article_get_speed( url_article() ) << " / " << DBTREE::article_lng_dat( url_article() )/1024 << " K ] " << str_stat; set_status( ss_tmp.str() ); } // // 板一覧との切り替え方法説明ダイアログ表示 // void ArticleViewMain::show_instruct_diag() { m_show_instdialog = false; SKELETON::MsgCheckDiag mdiag( get_parent_win(), "スレビューからスレ一覧表示に戻る方法として\n\n(1) マウスジェスチャを使う\n(マウス右ボタンを押しながら左または下にドラッグして右ボタンを離す)\n\n(2) マウスの5ボタンを押す\n\n(3) Alt+x か h か ← を押す\n\n(4) ツールバーのスレ一覧アイコンを押す\n\n(5) 表示メニューからスレ一覧を選ぶ\n\nなどがあります。詳しくはオンラインマニュアルを参照してください。" , "今後表示しない(_D)" ); mdiag.set_title( "ヒント" ); mdiag.run(); if( mdiag.get_chkbutton().get_active() ) CONFIG::set_instruct_tglart( false ); } // // 画面を消してレイアウトやりなおし & 再描画 // void ArticleViewMain::relayout() { #ifdef _DEBUG std::cout << "ArticleViewMain::relayout " << DBTREE::article_subject( url_article() ) << std::endl;; #endif hide_popup( true ); int seen = drawarea()->get_seen_current(); int num_reserve = drawarea()->get_goto_num_reserve(); int separator_new = drawarea()->get_separator_new(); drawarea()->clear_screen(); drawarea()->set_separator_new( separator_new ); drawarea()->append_res( 1, get_article()->get_number_load() ); if( num_reserve ) drawarea()->goto_num( num_reserve ); else if( seen ) drawarea()->goto_num( seen ); drawarea()->redraw_view(); // ステータス更新 create_status_message(); ARTICLE::get_admin()->set_command( "set_status", get_url(), get_status() ); } // // 実況開始 // // virtual void ArticleViewMain::live_start() { if( get_live() ) return; // オフライン if( ! SESSION::is_online() ){ SKELETON::MsgDiag mdiag( get_parent_win(), "オフラインです" ); mdiag.run(); return; } #ifdef _DEBUG std::cout << "ArticleViewMain::live_start\n"; #endif const int live_sec = DBTREE::board_get_live_sec( get_url() ); set_live( true ); ARTICLE::get_admin()->set_command_immediately( "start_autoreload", get_url(), "on", std::to_string( live_sec ) ); set_autoreload_counter( live_sec * 1000/TIMER_TIMEOUT ); drawarea()->live_start(); drawarea()->update_live_speed( live_sec ); goto_bottom(); } // // 実況停止 // // virtual void ArticleViewMain::live_stop() { if( ! get_live() ) return; #ifdef _DEBUG std::cout << "ArticleViewMain::live_stop\n"; #endif set_live( false ); ARTICLE::get_admin()->set_command_immediately( "stop_autoreload", get_url() ); drawarea()->live_stop(); set_status( "実況停止" ); ARTICLE::get_admin()->set_command( "set_status", get_url(), get_status(), "force" ); } jdim-0.7.0/src/article/articleview.h000066400000000000000000000033721417047150700173550ustar00rootroot00000000000000// ライセンス: GPL2 // // メインビュー // #ifndef _ARTICLEVIEW_H #define _ARTICLEVIEW_H #include "articleviewbase.h" namespace ARTICLE { class ArticleViewMain : public ArticleViewBase { // ジャンプ予約, goto_num() のコメント参照 int m_gotonum_reserve_to{}; int m_gotonum_reserve_from{}; int m_gotonum_seen{}; // 前回見ていた場所へのジャンプ用 bool m_set_history{}; // update_finish() で履歴を登録する bool m_show_instdialog{}; bool m_playsound{}; bool m_show_closedialog{}; bool m_reload_reserve{}; // 連続リロード防止用 int m_cancel_reload_counter{}; public: explicit ArticleViewMain( const std::string& url ); ~ArticleViewMain(); void clock_in() override; void clock_in_always() override; void goto_num( const int num_to, const int num_from ) override; // SKELETON::View の関数のオーバロード void save_session() override; bool is_loading() const override; bool is_updated() const override; bool is_check_update() const override; bool is_old() const override; bool is_broken() const override; bool is_overflow() const noexcept override; void show_view() override; void update_view() override; void update_finish() override; void relayout() override; protected: // 実況 void live_start() override; void live_stop() override; private: void exec_reload() override; // ステータスに表示する文字列作成 void create_status_message(); void show_instruct_diag(); }; } #endif jdim-0.7.0/src/article/articleviewbase.cpp000066400000000000000000003723361417047150700205540ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "gtkmmversion.h" #include "articleadmin.h" #include "articleviewbase.h" #include "drawareamain.h" #include "skeleton/msgdiag.h" #include "jdlib/miscutil.h" #include "jdlib/miscgtk.h" #include "jdlib/miscx.h" #include "jdlib/misccharcode.h" #include "dbtree/articlebase.h" #include "dbtree/interface.h" #include "dbimg/imginterface.h" #include "skeleton/popupwin.h" #include "config/globalconf.h" #include "history/historymanager.h" #include "message/logmanager.h" #include "image/imageviewpopup.h" #include "xml/document.h" #include "xml/tools.h" #include "control/controlutil.h" #include "control/controlid.h" #include "global.h" #include "type.h" #include "httpcode.h" #include "command.h" #include "session.h" #include "viewfactory.h" #include "sharedbuffer.h" #include "prefdiagfactory.h" #include "usrcmdmanager.h" #include "linkfiltermanager.h" #include "compmanager.h" #include "icons/iconmanager.h" #include #include #include #ifndef MAX #define MAX( a, b ) ( a > b ? a : b ) #endif #ifndef MIN #define MIN( a, b ) ( a < b ? a : b ) #endif using namespace ARTICLE; #define PROTO_URL4REPORT "url4report://" ArticleViewBase::ArticleViewBase( const std::string& url, const std::string& url_article ) : SKELETON::View( url ) , m_url_article( url_article ) , m_enable_menuslot{ true } { #ifdef _DEBUG std::cout << "ArticleViewBase::ArticleViewBase : " << get_url() << " : " << m_url_article << std::endl; #endif set_id_toolbar( TOOLBAR_ARTICLE ); // マウスジェスチャ可能 set_enable_mg( true ); // コントロールモード設定 get_control().add_mode( CONTROL::MODE_ARTICLE ); // 板名セット update_boardname(); } ArticleViewBase::~ArticleViewBase() { #ifdef _DEBUG std::cout << "ArticleViewBase::~ArticleViewBase : " << get_url() << std::endl; #endif hide_popup( true ); delete_popup(); } SKELETON::Admin* ArticleViewBase::get_admin() { return ARTICLE::get_admin(); } // // コピー用URL( readcgi型 ) // // メインウィンドウのURLバーなどの表示用にも使う // std::string ArticleViewBase::url_for_copy() const { return DBTREE::url_readcgi( m_url_article, 0, 0 ); } DrawAreaBase* ArticleViewBase::create_drawarea() { return Gtk::manage( new ARTICLE::DrawAreaMain( m_url_article ) ); } // // セットアップ // // 各派生ビューで初期設定が済んだ後に呼ばれる // void ArticleViewBase::setup_view() { #ifdef _DEBUG std::cout << "ArticleViewBase::setup_view " << get_url() << " url_article = " << m_url_article << std::endl; #endif m_article = DBTREE::get_article( m_url_article ); m_drawarea = create_drawarea(); assert( m_article ); assert( m_drawarea ); m_drawarea->add_events( Gdk::SMOOTH_SCROLL_MASK ); m_drawarea->sig_button_press().connect( sigc::mem_fun( *this, &ArticleViewBase::slot_button_press )); m_drawarea->sig_button_release().connect( sigc::mem_fun( *this, &ArticleViewBase::slot_button_release )); m_drawarea->sig_motion_notify().connect( sigc::mem_fun( *this, &ArticleViewBase::slot_motion_notify ) ); m_drawarea->sig_key_press().connect( sigc::mem_fun(*this, &ArticleViewBase::slot_key_press ) ); m_drawarea->sig_key_release().connect( sigc::mem_fun(*this, &ArticleViewBase::slot_key_release ) ); m_drawarea->sig_scroll_event().connect( sigc::mem_fun( *this, &ArticleViewBase::slot_scroll_event )); m_drawarea->sig_leave_notify().connect( sigc::mem_fun( *this, &ArticleViewBase::slot_leave_notify ) ); m_drawarea->sig_on_url().connect( sigc::mem_fun(*this, &ArticleViewBase::slot_on_url ) ); m_drawarea->sig_leave_url().connect( sigc::mem_fun(*this, &ArticleViewBase::slot_leave_url ) ); pack_start( *m_drawarea, Gtk::PACK_EXPAND_WIDGET ); setup_action(); show_all_children(); } // // アクション初期化 // void ArticleViewBase::setup_action() { #ifdef _DEBUG std::cout << "ArticleViewBase::setup_action\n"; #endif // アクショングループを作ってUIマネージャに登録 action_group().reset(); action_group() = Gtk::ActionGroup::create(); action_group()->add( Gtk::Action::create( "BookMark", "しおりを設定/解除(_B)"), sigc::mem_fun( *this, &ArticleViewBase::slot_bookmark ) ); action_group()->add( Gtk::Action::create( "PostedMark", "書き込みマークを設定/解除(_P)"), sigc::mem_fun( *this, &ArticleViewBase::slot_postedmark ) ); action_group()->add( Gtk::Action::create( "OpenBrowser", ITEM_NAME_OPEN_BROWSER "(_W)" ), sigc::mem_fun( *this, &ArticleViewBase::slot_open_browser ) ); action_group()->add( Gtk::Action::create( "OpenBrowserRes", ITEM_NAME_OPEN_BROWSER "(_S)" ), // レスをクリックした時のメニュー用 sigc::mem_fun( *this, &ArticleViewBase::slot_open_browser ) ); action_group()->add( Gtk::Action::create( "OpenCacheBrowser", ITEM_NAME_OPEN_CACHE_BROWSER "(_X)" ), sigc::mem_fun( *this, &ArticleViewBase::slot_open_cache_browser ) ); action_group()->add( Gtk::Action::create( "CopyURL", ITEM_NAME_COPY_URL "(_U)" ), sigc::mem_fun( *this, &ArticleViewBase::slot_copy_current_url ) ); action_group()->add( Gtk::Action::create( "CopyTitleURL", ITEM_NAME_COPY_TITLE_URL_THREAD "(_L)" ), sigc::mem_fun( *this, &ArticleViewBase::slot_copy_title_url ) ); action_group()->add( Gtk::Action::create( "CopyNAME", "名前コピー(_N)"), sigc::mem_fun( *this, &ArticleViewBase::slot_copy_name ) ); action_group()->add( Gtk::Action::create( "CopyID", "IDコピー(_D)"), sigc::mem_fun( *this, &ArticleViewBase::slot_copy_id ) ); action_group()->add( Gtk::Action::create( "Copy", "Copy"), sigc::mem_fun( *this, &ArticleViewBase::slot_copy_selection_str ) ); action_group()->add( Gtk::Action::create( "WriteRes", "レスする(_W)" ),sigc::mem_fun( *this, &ArticleViewBase::slot_write_res ) ); action_group()->add( Gtk::Action::create( "QuoteRes", "引用してレスする(_Q)"),sigc::mem_fun( *this, &ArticleViewBase::slot_quote_res ) ); action_group()->add( Gtk::Action::create( "QuoteSelectionRes", ITEM_NAME_QUOTE_SELECTION "(_Q)" ), sigc::mem_fun( *this, &ArticleViewBase::slot_quote_selection_res ) ); action_group()->add( Gtk::Action::create( "CopyRes", "レスをコピー(_R)"), sigc::bind< bool >( sigc::mem_fun( *this, &ArticleViewBase::slot_copy_res ), false ) ); action_group()->add( Gtk::Action::create( "CopyResRef", "引用コピー(_F)"), sigc::bind< bool >( sigc::mem_fun( *this, &ArticleViewBase::slot_copy_res ), true ) ); action_group()->add( Gtk::Action::create( "Delete_Menu", "削除(_D)" ) ); action_group()->add( Gtk::Action::create( "Delete", "Delete"), sigc::mem_fun( *this, &ArticleViewBase::exec_delete ) ); action_group()->add( Gtk::Action::create( "DeleteOpen", "スレ情報を消さずにスレ再取得(_R)"), sigc::mem_fun( *this, &ArticleViewBase::delete_open_view ) ); action_group()->add( Gtk::Action::create( "AppendFavorite", "AppendFavorite"), sigc::mem_fun( *this, &ArticleViewBase::set_favorite ) ); action_group()->add( Gtk::Action::create( "Reload", "Reload"), sigc::mem_fun( *this, &ArticleViewBase::exec_reload ) ); action_group()->add( Gtk::Action::create( "PreferenceArticle", "PreferenceArticle" ), sigc::mem_fun( *this, &ArticleViewBase::show_preference ) ); action_group()->add( Gtk::Action::create( "PreferenceImage", ITEM_NAME_PREF_IMAGE "(_M)..." ), sigc::mem_fun( *this, &ArticleViewBase::slot_preferences_image ) ); // 検索 action_group()->add( Gtk::Action::create( "Search_Menu", ITEM_NAME_SEARCH "(_H)" ) ); action_group()->add( Gtk::Action::create( "SearchNextArticle", "SearchNextArticle"), sigc::mem_fun( *this, &ArticleViewBase::slot_search_next ) ); action_group()->add( Gtk::Action::create( "SearchWeb", "SearchWeb" ), sigc::mem_fun( *this, &ArticleViewBase::slot_search_web ) ); action_group()->add( Gtk::Action::create( "SearchCacheLocal", "SearchCacheLocal" ), sigc::mem_fun( *this, &ArticleViewBase::slot_search_cachelocal ) ); action_group()->add( Gtk::Action::create( "SearchCacheAll", "SearchCacheAll") ); action_group()->add( Gtk::Action::create( "ExecSearchCacheAll", "検索する(_E)"), sigc::mem_fun( *this, &ArticleViewBase::slot_search_cacheall ) ); action_group()->add( Gtk::Action::create( "SearchTitle", "SearchTitle" ), sigc::mem_fun( *this, &ArticleViewBase::slot_search_title ) ); // 抽出系 action_group()->add( Gtk::Action::create( "Drawout_Menu", ITEM_NAME_DRAWOUT "(_E)" ) ); action_group()->add( Gtk::Action::create( "DrawoutWord", "キーワード抽出(_K)"), sigc::mem_fun( *this, &ArticleViewBase::slot_drawout_selection_str ) ); action_group()->add( Gtk::Action::create( "DrawoutRes", "レス抽出(_R)"), sigc::mem_fun( *this, &ArticleViewBase::slot_drawout_res ) ); action_group()->add( Gtk::Action::create( "DrawoutNAME", "名前抽出(_E)"), sigc::mem_fun( *this, &ArticleViewBase::slot_drawout_name ) ); action_group()->add( Gtk::Action::create( "DrawoutID", "ID抽出(_I)"), sigc::mem_fun( *this, &ArticleViewBase::slot_drawout_id ) ); action_group()->add( Gtk::Action::create( "DrawoutBM", "しおり抽出(_B)"), sigc::mem_fun( *this, &ArticleViewBase::slot_drawout_bm ) ); action_group()->add( Gtk::Action::create( "DrawoutPost", "書き込み抽出(_W)"), sigc::mem_fun( *this, &ArticleViewBase::slot_drawout_post ) ); action_group()->add( Gtk::Action::create( "DrawoutHighRefRes", "高参照レス抽出(_H)"), sigc::mem_fun( *this, &ArticleViewBase::slot_drawout_highly_referenced_res ) ); action_group()->add( Gtk::Action::create( "DrawoutURL", "URL抽出(_U)"), sigc::mem_fun( *this, &ArticleViewBase::slot_drawout_url ) ); action_group()->add( Gtk::Action::create( "DrawoutRefer", "参照抽出(_E)"), sigc::mem_fun( *this, &ArticleViewBase::slot_drawout_refer ) ); action_group()->add( Gtk::Action::create( "DrawoutAround", "周辺抽出(_A)"), sigc::mem_fun( *this, &ArticleViewBase::slot_drawout_around ) ); action_group()->add( Gtk::Action::create( "DrawoutTmp", "テンプレート抽出(_T)"), sigc::mem_fun( *this, &ArticleViewBase::slot_drawout_tmp ) ); // あぼーん系 action_group()->add( Gtk::Action::create( "AboneWord_Menu", ITEM_NAME_NGWORD "(_N)" ) ); action_group()->add( Gtk::Action::create( "AboneRes", "レスをあぼ〜んする(_A)"), sigc::mem_fun( *this, &ArticleViewBase::slot_abone_res ) ); action_group()->add( Gtk::Action::create( "AboneSelectionRes", "AboneSelectionRes" ), sigc::mem_fun( *this, &ArticleViewBase::slot_abone_selection_res ) ); action_group()->add( Gtk::Action::create( "AboneID", "NG IDに追加(_G)"), sigc::mem_fun( *this, &ArticleViewBase::slot_abone_id ) ); action_group()->add( Gtk::Action::create( "AboneName", "NG 名前に追加 (対象: ローカル)(_L)"), sigc::mem_fun( *this, &ArticleViewBase::slot_abone_name ) ); action_group()->add( Gtk::Action::create( "AboneWord", "NG ワードに追加 (対象: ローカル)(_L)"), sigc::mem_fun( *this, &ArticleViewBase::slot_abone_word ) ); action_group()->add( Gtk::Action::create( "AboneNameBoard", "NG 名前に追加 (対象: 板)(_B)" ) ); action_group()->add( Gtk::Action::create( "SetAboneNameBoard", "追加する(_A)"), sigc::mem_fun( *this, &ArticleViewBase::slot_abone_name_board ) ); action_group()->add( Gtk::Action::create( "AboneWordBoard", "NG ワードに追加 (対象: 板)(_B)" ) ); action_group()->add( Gtk::Action::create( "SetAboneWordBoard", "追加する(_A)"), sigc::mem_fun( *this, &ArticleViewBase::slot_abone_word_board ) ); action_group()->add( Gtk::Action::create( "GlobalAboneName", "NG 名前に追加 (対象: 全体)(_A)" ) ); action_group()->add( Gtk::Action::create( "SetGlobalAboneName", "追加する(_A)"), sigc::mem_fun( *this, &ArticleViewBase::slot_global_abone_name ) ); action_group()->add( Gtk::Action::create( "GlobalAboneWord", "NG ワードに追加 (対象: 全体)(_A)" ) ); action_group()->add( Gtk::Action::create( "SetGlobalAboneWord", "追加する(_A)"), sigc::mem_fun( *this, &ArticleViewBase::slot_global_abone_word ) ); action_group()->add( Gtk::ToggleAction::create( "TranspAbone", "透明あぼ〜ん(_T)", std::string(), false ), sigc::mem_fun( *this, &ArticleViewBase::slot_toggle_abone_transp ) ); action_group()->add( Gtk::ToggleAction::create( "TranspChainAbone", "透明/連鎖あぼ〜ん(_C)", std::string(), false ), sigc::mem_fun( *this, &ArticleViewBase::slot_toggle_abone_transp_chain ) ); action_group()->add( Gtk::Action::create( "SetupAbone", "あぼ〜ん設定(対象: ローカル)(_L)..."), sigc::mem_fun( *this, &ArticleViewBase::slot_setup_abone ) ); action_group()->add( Gtk::Action::create( "SetupAboneBoard", "あぼ〜ん設定(対象: 板)(_B)..." ), sigc::mem_fun( *this, &ArticleViewBase::slot_setup_abone_board ) ); action_group()->add( Gtk::Action::create( "SetupAboneAll", "あぼ〜ん設定(対象: 全体)(_A)..." ), sigc::mem_fun( *this, &ArticleViewBase::slot_setup_abone_all ) ); // 移動系 action_group()->add( Gtk::Action::create( "Move_Menu", ITEM_NAME_GO "(_M)" ) ); action_group()->add( Gtk::Action::create( "Home", "Home"), sigc::mem_fun( *this, &ArticleViewBase::goto_top ) ); action_group()->add( Gtk::Action::create( "GotoNew", "GotoNew"), sigc::mem_fun( *this, &ArticleViewBase::goto_new ) ); action_group()->add( Gtk::Action::create( "End", "End"), sigc::mem_fun( *this, &ArticleViewBase::goto_bottom ) ); action_group()->add( Gtk::Action::create( "PreBookMark", "PreBookMark"), sigc::mem_fun( *this, &ArticleViewBase::slot_pre_bm ) ); action_group()->add( Gtk::Action::create( "NextBookMark", "NextBookMark"), sigc::mem_fun( *this, &ArticleViewBase::slot_next_bm ) ); action_group()->add( Gtk::Action::create( "PrePost", "PrePost"), sigc::mem_fun( *this, &ArticleViewBase::slot_pre_post ) ); action_group()->add( Gtk::Action::create( "NextPost", "NextPost"), sigc::mem_fun( *this, &ArticleViewBase::slot_next_post ) ); action_group()->add( Gtk::Action::create( "Jump", "ジャンプ(_J)"), sigc::mem_fun( *this, &ArticleViewBase::slot_jump ) ); action_group()->add( Gtk::Action::create( "PrevView", "PrevView"), sigc::bind< int >( sigc::mem_fun( *this, &ArticleViewBase::back_viewhistory ), 1 ) ); action_group()->add( Gtk::Action::create( "NextView", "NextView"), sigc::bind< int >( sigc::mem_fun( *this, &ArticleViewBase::forward_viewhistory ), 1 ) ); // 画像系 action_group()->add( Gtk::Action::create( "Cancel_Mosaic", "モザイク解除(_C)"), sigc::mem_fun( *this, &ArticleViewBase::slot_cancel_mosaic ) ); action_group()->add( Gtk::Action::create( "Show_Mosaic", "モザイクで開く(_M)"), sigc::mem_fun( *this, &ArticleViewBase::slot_show_image_with_mosaic ) ); action_group()->add( Gtk::Action::create( "ShowSelectImage", "ShowSelectImage" ) , sigc::mem_fun( *this, &ArticleViewBase::slot_show_selection_images ) ); action_group()->add( Gtk::Action::create( "DeleteSelectImage_Menu", ITEM_NAME_SELECTDELIMG "(_T)" ) ); action_group()->add( Gtk::Action::create( "DeleteSelectImage", "DeleteSelectImage"), sigc::mem_fun( *this, &ArticleViewBase::slot_delete_selection_images ) ); action_group()->add( Gtk::Action::create( "AboneSelectImage_Menu", ITEM_NAME_SELECTABONEIMG "(_B)" ) ); action_group()->add( Gtk::Action::create( "AboneSelectImage", "AboneSelectImage"), sigc::mem_fun( *this, &ArticleViewBase::slot_abone_selection_images ) ); action_group()->add( Gtk::Action::create( "ShowLargeImg", "サイズが大きい画像を表示(_L)"), sigc::mem_fun( *this, &ArticleViewBase::slot_show_large_img ) ); action_group()->add( Gtk::ToggleAction::create( "ProtectImage", "キャッシュを保護する(_P)", std::string(), false ), sigc::mem_fun( *this, &ArticleViewBase::slot_toggle_protectimage ) ); action_group()->add( Gtk::Action::create( "DeleteImage_Menu", "削除(_D)" ) ); action_group()->add( Gtk::Action::create( "DeleteImage", "削除する(_D)"), sigc::mem_fun( *this, &ArticleViewBase::slot_deleteimage ) ); action_group()->add( Gtk::Action::create( "SaveImage", "名前を付けて保存(_S)..."), sigc::mem_fun( *this, &ArticleViewBase::slot_saveimage ) ); action_group()->add( Gtk::ToggleAction::create( "AboneImage", "画像をあぼ〜んする(_A)", std::string(), false ), sigc::mem_fun( *this, &ArticleViewBase::slot_abone_img ) ); // その他 action_group()->add( Gtk::Action::create( "Etc_Menu", ITEM_NAME_ETC "(_O)" ) ); action_group()->add( Gtk::Action::create( "SaveDat", "SaveDat" ), sigc::mem_fun( *this, &ArticleViewBase::slot_save_dat ) ); action_group()->add( Gtk::Action::create( "CopyInfo", ITEM_NAME_COPY_THREAD_INFO "(_I)..." ), sigc::mem_fun( *this, &ArticleViewBase::slot_copy_article_info ) ); m_usrcmd = CORE::get_usrcmd_manager()->create_usrcmd_menu( action_group() ); const int usrcmd_size = CORE::get_usrcmd_manager()->get_size(); for( int i = 0; i < usrcmd_size; ++i ){ Glib::RefPtr< Gtk::Action > act = CORE::get_usrcmd_manager()->get_action( action_group(), i ); if( act ) act->signal_activate().connect( sigc::bind< int >( sigc::mem_fun( *this, &ArticleViewBase::slot_usrcmd ), i ) ); } ui_manager().reset(); ui_manager() = Gtk::UIManager::create(); ui_manager()->insert_action_group( action_group() ); // 削除ボタン押したときのポップアップ const std::string menu_delete = "" "" "" "" ""; // 壊れていますをクリックしたときのポップアップ const std::string menu_broken = "" "" ""; // レス番号をクリックしたときのメニュー const std::string menu_res = "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""; // レスアンカーをクリックしたときのメニュー const std::string menu_anc = "" "" "" "" ""; // IDをクリックしたときのメニュー const std::string menu_id = "" "" "" "" "" ""; // 名前をクリックしたときのメニュー const std::string menu_name = "" "" "" "" "" "" "" "" "" "" "" ""; // あぼーんをクリックしたときのメニュー const std::string menu_abone = "" "" "" "" "" "" "" ""; // 画像メニュー const std::string menu_img = "" "" "" "" "" "" "" + m_usrcmd + std::string( "" "" "" "" "" "" "" "" "" "" "" "" "" "" ); ui_manager()->add_ui_from_string( "" + menu_delete + menu_broken + menu_res + menu_anc + menu_id + menu_name + menu_abone + menu_img + create_context_menu() + "" ); // ポップアップメニューにショートカットキーやマウスジェスチャを表示 Gtk::Menu* popupmenu = dynamic_cast< Gtk::Menu* >( ui_manager()->get_widget( "/popup_menu" ) ); CONTROL::set_menu_motion( popupmenu ); popupmenu = dynamic_cast< Gtk::Menu* >( ui_manager()->get_widget( "/popup_menu_delete" ) ); CONTROL::set_menu_motion( popupmenu ); } // // 通常の右クリックメニューの作成 // std::string ArticleViewBase::create_context_menu() const { std::list< int > list_menu; list_menu.push_back( ITEM_DRAWOUT ); list_menu.push_back( ITEM_GO ); list_menu.push_back( ITEM_SEARCH ); list_menu.push_back( ITEM_NGWORD ); list_menu.push_back( ITEM_QUOTE_SELECTION ); list_menu.push_back( ITEM_OPEN_BROWSER ); list_menu.push_back( ITEM_USER_COMMAND ); list_menu.push_back( ITEM_COPY_URL ); list_menu.push_back( ITEM_COPY ); list_menu.push_back( ITEM_RELOAD ); list_menu.push_back( ITEM_DELETE ); list_menu.push_back( ITEM_COPY_TITLE_URL_THREAD ); list_menu.push_back( ITEM_SAVE_DAT ); list_menu.push_back( ITEM_COPY_THREAD_INFO ); list_menu.push_back( ITEM_APPENDFAVORITE ); list_menu.push_back( ITEM_ABONE_SELECTION ); list_menu.push_back( ITEM_SELECTIMG ); list_menu.push_back( ITEM_SELECTDELIMG ); list_menu.push_back( ITEM_SELECTABONEIMG ); list_menu.push_back( ITEM_PREF_THREAD ); // メニューに含まれていない項目を抜き出して「その他」に含める int num = 0; for(;;){ const int item = SESSION::get_item_article_menu( num ); if( item == ITEM_END ) break; list_menu.remove( item ); ++num; } std::string menu; num = 0; for(;;){ const int item = SESSION::get_item_article_menu( num ); if( item == ITEM_END ) break; else if( item == ITEM_ETC && list_menu.size() ){ menu.append( "" ); for( int etc_item : list_menu ) menu.append( get_menu_item( etc_item ) ); menu.append( "" ); } else menu.append( get_menu_item( item ) ); ++num; } #ifdef _DEBUG std::cout << "menu = " << menu << std::endl; #endif return "" + menu + ""; } const char* ArticleViewBase::get_menu_item( const int item ) const { switch( item ){ // 抽出 case ITEM_DRAWOUT: return "" "" "" "" "" "" "" "" ; // 移動 case ITEM_GO: return "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ; // 検索 case ITEM_SEARCH: return "" "" "" "" "" "" "" "" "" "" "" "" ; // NGワード case ITEM_NGWORD: return "" "" "" "" "" "" "" "" "" ; // 選択範囲のレスをあぼーん case ITEM_ABONE_SELECTION: return "" ; // 引用してレス case ITEM_QUOTE_SELECTION: return ""; // リンクをブラウザで開く case ITEM_OPEN_BROWSER: return ""; // ユーザコマンド case ITEM_USER_COMMAND: return m_usrcmd.c_str(); // リンクのURLをコピー case ITEM_COPY_URL: return ""; // スレのタイトルとURLをコピー case ITEM_COPY_TITLE_URL_THREAD: return ""; // コピー case ITEM_COPY: return ""; // 再読み込み case ITEM_RELOAD: return ""; // dat 保存 case ITEM_SAVE_DAT: return ""; // スレ情報の引き継ぎ case ITEM_COPY_THREAD_INFO: return ""; // お気に入り case ITEM_APPENDFAVORITE: return ""; // プロパティ case ITEM_PREF_THREAD: return ""; // 選択範囲の画像を開く case ITEM_SELECTIMG: return ""; // 選択範囲の画像を削除 case ITEM_SELECTDELIMG: return "" "" ""; // 選択範囲の画像をあぼーん case ITEM_SELECTABONEIMG: return "" "" ""; // 区切り case ITEM_SEPARATOR: return ""; // 削除 case ITEM_DELETE: return "" "" "" ""; } return ""; } // // クライアント領域幅 // int ArticleViewBase::width_client() const { if( m_drawarea ) { const int width_client = m_drawarea->width_client(); #ifdef _DEBUG std::cout << "ArticleViewBase::width_client : " << width_client << std::endl; #endif return width_client; } return SKELETON::View::width_client(); } // // クライアント領高さ // int ArticleViewBase::height_client() const { if( m_drawarea ) { const int height_client = m_drawarea->height_client(); #ifdef _DEBUG std::cout << "ArticleViewBase::height_client : " << height_client << std::endl; #endif return height_client; } return SKELETON::View::height_client(); } // アイコンのID取得 int ArticleViewBase::get_icon( const std::string& iconname ) const { int id = ICON::NONE; if( iconname == "default" ) id = ICON::THREAD; if( iconname == "loading" ) id = ICON::LOADING; if( iconname == "loading_stop" ) id = ICON::LOADING_STOP; if( iconname == "update" ) id = ICON::THREAD_UPDATE; // 更新チェックしで更新があった場合 if( iconname == "updated" ) id = ICON::THREAD_UPDATED; if( iconname == "old" ) id = ICON::THREAD_OLD; #ifdef _DEBUG std::cout << "ArticleViewBase::get_icon : " << iconname << " url = " << get_url() << std::endl; #endif return id; } // // コマンド // bool ArticleViewBase::set_command( const std::string& command, const std::string& arg1, const std::string& arg2 ) { #ifdef _DEBUG std::cout << "ArticleViewBase::set_command " << get_url() << std::endl << "command = " << command << std::endl; #endif if( command == "append_dat" ) append_dat( arg1, -1 ); else if( command == "append_html" ) append_html( arg1 ); else if( command == "clear_screen" ) { if( m_drawarea ) m_drawarea->clear_screen(); } else if( command == "goto_num" ) goto_num( atoi( arg1.c_str() ), atoi( arg2.c_str() ) ); else if( command == "hide_popup" ) hide_popup( true ); else if( command == "delete_popup" ) delete_popup(); else if( command == "clear_highlight" ) clear_highlight(); else if( command == "reset_popupmenu" ) setup_action(); // 実況手動切り替え else if( command == "live_start_stop" ){ if( ! get_enable_live() ){ if( m_enable_live && ! DBTREE::board_get_live_sec( m_url_article ) ){ SKELETON::MsgDiag mdiag( get_parent_win(), "実況を行うには板のプロパティで更新間隔を設定して下さい。" ); mdiag.run(); } return true; } if( get_live() ){ live_stop(); // 実況したスレは終了時に削除する SESSION::remove_delete_list( m_url_article ); } else{ live_start(); // 手動で停止した場合は終了時の削除をキャンセルする SESSION::append_delete_list( m_url_article ); } } // dat落ちや更新が無い場合は実況停止 // 終了時にスレを削除する else if( command == "live_stop" && get_enable_live() ) live_stop(); return true; } // // クロック入力 // // クロックタイマーの本体はコアが持っていて、定期的にadminがアクティブなviewにクロック入力を渡す // // virtual void ArticleViewBase::clock_in() { assert( m_drawarea ); View::clock_in(); // ポップアップを時間差で消す if( m_hidepopup_counter ){ --m_hidepopup_counter; if( ! m_hidepopup_counter ){ // ポインタがポップアップ上に戻っていたらキャンセル if( ! is_mouse_on_popup() ) slot_hide_popup(); } } // ポップアップが出てたらそっちにクロックを回す if( is_popup_shown() && m_popup_win->view() ){ m_popup_win->view()->clock_in(); return; } m_drawarea->clock_in(); } // // スムーススクロール用クロック入力 // // タイマー本体はArticleAdminが持っている // void ArticleViewBase::clock_in_smooth_scroll() { assert( m_drawarea ); // ポップアップが出てたらそっちにクロックを回す if( is_popup_shown() && m_popup_win->view() ){ ArticleViewBase* view = dynamic_cast< ArticleViewBase* >( m_popup_win->view() ); if( view ) view->clock_in_smooth_scroll(); return; } m_drawarea->clock_in_smooth_scroll(); } // // 再読み込みボタンを押した // void ArticleViewBase::reload() { if( m_article->empty() ) return; if( CONFIG::get_reload_allthreads() ) ARTICLE::get_admin()->set_command( "reload_all_tabs" ); else exec_reload(); } // // 再読み込み実行 // // virtual void ArticleViewBase::exec_reload() { reload_article(); } // // 元のスレを開く (shift+s) // void ArticleViewBase::reload_article() { if( m_article->empty() ) return; CORE::core_set_command( "open_article", m_url_article , "newtab", "" ); } // // ロード停止 // void ArticleViewBase::stop() { assert( m_article ); m_article->stop_load(); } // // 再描画 // void ArticleViewBase::redraw_view() { // 起動中とシャットダウン中は処理しない if( SESSION::is_booting() ) return; if( SESSION::is_quitting() ) return; #ifdef _DEBUG std::cout << "ArticleViewBase::redraw_view " << get_url() << std::endl; #endif assert( m_drawarea ); m_drawarea->redraw_view_force(); // ポップアップが表示されていたらポップアップも再描画 if( is_popup_shown() ) m_popup_win->view()->redraw_view(); } // // フォーカスイン // void ArticleViewBase::focus_view() { assert( m_drawarea ); #ifdef _DEBUG std::cout << "ArticleViewBase::focus_view " << get_url() << std::endl; #endif m_drawarea->focus_view(); m_drawarea->redraw_view(); } // // フォーカスアウト // void ArticleViewBase::focus_out() { SKELETON::View::focus_out(); #ifdef _DEBUG std::cout << "ArticleViewBase::focus_out " << get_url() << std::endl; #endif m_drawarea->focus_out(); if( is_popup_shown() ){ // フォーカスアウトした瞬間に、子ポップアップが表示されていて、かつ // ポインタがその上だったらポップアップは消さない if( is_mouse_on_popup() ) return; if( CONFIG::get_hide_popup_msec() ) m_hidepopup_counter = CONFIG::get_hide_popup_msec() / TIMER_TIMEOUT; else hide_popup(); } } // // 閉じる // void ArticleViewBase::close_view() { if( m_article->is_loading() ){ SKELETON::MsgDiag mdiag( get_parent_win(), "読み込み中です" ); mdiag.run(); return; } ARTICLE::get_admin()->set_command( "close_view", get_url() ); } // // 削除ボタンを押した // void ArticleViewBase::delete_view() { show_popupmenu( "popup_menu_delete", false ); } // // 削除実行 // void ArticleViewBase::exec_delete() { CORE::core_set_command( "delete_article", m_url_article ); ARTICLE::get_admin()->set_command( "switch_admin" ); } // // スレ再取得 // void ArticleViewBase::delete_open_view() { if( ! SESSION::is_online() ){ SKELETON::MsgDiag mdiag( get_parent_win(), "オフラインです" ); mdiag.run(); return; } if( DBTREE::article_status( m_url_article ) & STATUS_OLD ){ SKELETON::MsgDiag mdiag( get_parent_win(), "DAT落ちしています。\n\nログが消える恐れがあります。実行しますか?", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO ); mdiag.set_default_response( Gtk::RESPONSE_NO ); if( mdiag.run() != Gtk::RESPONSE_YES ) return; } const std::string str_tab = "false"; const std::string mode = "reget"; CORE::core_set_command( "open_article", m_url_article, str_tab, mode ); } // 実況可能か bool ArticleViewBase::get_enable_live() const { return ( m_enable_live && DBTREE::board_get_live_sec( m_url_article ) ); } // // マウスポインタをポップアップの上に移動する // void ArticleViewBase::warp_pointer_to_popup() { if( is_popup_shown() ){ const int mrg = 32; int x, y; MISC::get_pointer_at_window( m_popup_win->get_window(), x, y ); if( x < mrg ) x = mrg; else if( x > m_popup_win->get_width() - mrg ) x = m_popup_win->get_width() - mrg; if( y < 0 ) y = mrg; else y = m_popup_win->get_height() - mrg; MISC::WarpPointer( get_window(), m_popup_win->get_window(), x, y ); } } // // viewの操作 // bool ArticleViewBase::operate_view( const int control ) { assert( m_drawarea ); // スレタイ検索 // CONTROL::operate_common()の中で処理しないようにCONTROL::operate_common()の前で処理 if( control == CONTROL::SearchTitle ){ slot_search_title(); return true; } if( CONTROL::operate_common( control, get_url(), ARTICLE::get_admin() ) ) return true; if( control == CONTROL::None ) return false;; // スクロール系操作 if( m_drawarea->set_scroll( control ) ) return true; #ifdef _DEBUG std::cout << "ArticleViewBase::operate_view control = " << control << std::endl; #endif // その他の処理 switch( control ){ // リロード case CONTROL::Reload: exec_reload(); break; // 元のスレを開く case CONTROL::ReloadArticle: reload_article(); break; // コピー case CONTROL::Copy: slot_copy_selection_str(); break; // 全て選択 case CONTROL::SelectAll: slot_select_all(); break; // お気に入りに追加 case CONTROL::AppendFavorite: set_favorite(); break; // 検索 case CONTROL::Search: open_searchbar( false ); break; case CONTROL::SearchInvert: open_searchbar( true ); break; case CONTROL::SearchNext: down_search(); break; case CONTROL::SearchPrev: up_search(); break; // datを保存 case CONTROL::Save: slot_save_dat(); break; // 閉じる case CONTROL::Quit: close_view(); break; // 書き込み case CONTROL::WriteMessage: write(); break; // 削除 case CONTROL::Delete: { if( m_article->empty() ) break; if( ! CONFIG::get_show_delartdiag() ){ exec_delete(); break; } SKELETON::MsgCheckDiag mdiag( get_parent_win(), "ログを削除しますか?\n\n「スレ再取得」を押すと\nあぼ〜んなどのスレ情報を削除せずにスレを再取得します。", "今後表示しない(常に削除)(_D)", Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_NONE ); mdiag.add_button( g_dgettext( GTK_DOMAIN, "_No" ), Gtk::RESPONSE_NO ); mdiag.add_default_button( g_dgettext( GTK_DOMAIN, "_Yes" ), Gtk::RESPONSE_YES ); mdiag.add_button( "スレ再取得(_R)", Gtk::RESPONSE_YES + 100 ); mdiag.set_default_response( Gtk::RESPONSE_YES ); int ret = mdiag.run(); if( ret == Gtk::RESPONSE_YES ) exec_delete(); else if( ret == Gtk::RESPONSE_YES + 100 ) delete_open_view(); if( mdiag.get_chkbutton().get_active() ) CONFIG::set_show_delartdiag( false ); break; } // Board に切り替え case CONTROL::Left: CORE::core_set_command( "switch_leftview" ); break; case CONTROL::ToggleArticle: CORE::core_set_command( "toggle_article" ); break; // image に切り替え case CONTROL::Right: CORE::core_set_command( "switch_rightview" ); break; // 戻る、進む case CONTROL::PrevView: back_viewhistory( 1 ); break; case CONTROL::NextView: forward_viewhistory( 1 ); break; // ポップアップメニュー表示 case CONTROL::ShowPopupMenu: show_popupmenu( "", true ); break; // ブックマーク移動 case CONTROL::PreBookMark: slot_pre_bm(); break; case CONTROL::NextBookMark: slot_next_bm(); break; // 書き込み移動 case CONTROL::PrePost: slot_pre_post(); break; case CONTROL::NextPost: slot_next_post(); break; // 親にhideを依頼する and ローディング停止 case CONTROL::StopLoading: stop(); sig_hide_popup().emit(); break; // 実況モード切り替え case CONTROL::LiveStartStop: if( ! m_article->empty() ) ARTICLE::get_admin()->set_command( "live_start_stop", get_url() ); break; // 次スレ検索 case CONTROL::SearchNextArticle: slot_search_next(); break; // Web検索 case CONTROL::SearchWeb: slot_search_web(); break; // ログ検索(板内) case CONTROL::SearchCacheLocal: slot_search_cachelocal(); break; // ログ検索(全て) case CONTROL::SearchCacheAll: slot_search_cacheall(); break; // 選択範囲の画像を開く case CONTROL::ShowSelectImage: slot_show_selection_images(); break; // 選択範囲の画像を削除 case CONTROL::DeleteSelectImage: slot_delete_selection_images(); break; // 選択範囲の画像をあぼ〜ん case CONTROL::AboneSelectImage: slot_abone_selection_images(); break; // 選択範囲のレスをあぼ〜ん case CONTROL::AboneSelectionRes: slot_abone_selection_res(); break; // スレのプロパティ case CONTROL::PreferenceView: show_preference(); break; default: return false; } return true; } // // 一番上へ // void ArticleViewBase::goto_top() { assert( m_drawarea ); m_drawarea->goto_top(); } // // 一番下へ // void ArticleViewBase::goto_bottom() { assert( m_drawarea ); m_drawarea->goto_bottom(); } // // num_from から num_to番へジャンプ // // num_from が 0 の時は m_drawarea->get_seen_current() からジャンプすると見なす // void ArticleViewBase::goto_num( const int num_to, const int num_from ) { assert( m_drawarea ); const int from = ( ( num_from > 0 && num_to != num_from ) ? num_from : m_drawarea->get_seen_current() ); #ifdef _DEBUG std::cout << "ArticleViewBase::goto_num to = " << num_to << " from = " << from << std::endl; #endif m_drawarea->set_jump_history( from ); m_drawarea->goto_num( num_to ); } // // 新着に移動 // void ArticleViewBase::goto_new() { assert( m_drawarea ); m_drawarea->goto_new(); } // // 検索バーを開く // void ArticleViewBase::open_searchbar( bool invert ) { ARTICLE::get_admin()->set_command( "open_searchbar", get_url() ); m_search_invert = invert; } // // 前を検索 // void ArticleViewBase::up_search() { m_search_invert = true; exec_search(); redraw_view(); } // // 次を検索 // void ArticleViewBase::down_search() { m_search_invert = false; exec_search(); redraw_view(); } // // ハイライト解除 // void ArticleViewBase::clear_highlight() { assert( m_drawarea ); if( get_pre_query().empty() ) return; set_pre_query( std::string() ); m_drawarea->clear_highlight(); } // // メッセージ書き込みボタン // void ArticleViewBase::write() { if( ! is_writeable() ) return; CORE::core_set_command( "open_message" ,m_url_article, std::string() ); } // // プロパティ表示 // void ArticleViewBase::show_preference() { #ifdef _DEBUG std::cout << "ArticleViewBase::show_preference\n"; #endif auto pref = CORE::PrefDiagFactory( get_parent_win(), CORE::PREFDIAG_ARTICLE, m_url_article ); pref->run(); } // // 画像プロパティ表示 // void ArticleViewBase::slot_preferences_image() { if( m_url_tmp.empty() ) return; std::string url = m_url_tmp; auto pref = CORE::PrefDiagFactory( get_parent_win(), CORE::PREFDIAG_IMAGE, url ); pref->run(); } // // 戻る // void ArticleViewBase::back_viewhistory( const int count ) { ARTICLE::get_admin()->set_command( "back_viewhistory", get_url(), std::to_string( count ) ); } // // 進む // void ArticleViewBase::forward_viewhistory( const int count ) { ARTICLE::get_admin()->set_command( "forward_viewhistory", get_url(), std::to_string( count ) ); } // // 前のブックマークに移動 // void ArticleViewBase::slot_pre_bm() { assert( m_article ); if( m_current_bm == 0 ) m_current_bm = m_drawarea->get_seen_current(); for( int i = m_current_bm -1 ; i >= 1 ; --i ){ if( m_article->is_bookmarked( i ) ){ goto_num( i, 0 ); m_current_bm = i; return; } } for( int i = m_article->get_number_load() ; i > m_current_bm ; --i ){ if( m_article->is_bookmarked( i ) ){ goto_num( i, 0 ); m_current_bm = i; return; } } } // // 後ろのブックマークに移動 // void ArticleViewBase::slot_next_bm() { assert( m_article ); if( m_current_bm == 0 ) m_current_bm = m_drawarea->get_seen_current(); for( int i = m_current_bm + 1; i <= m_article->get_number_load() ; ++i ){ if( m_article->is_bookmarked( i ) ){ goto_num( i, 0 ); m_current_bm = i; return; } } for( int i = 1; i <= m_current_bm ; ++i ){ if( m_article->is_bookmarked( i ) ){ goto_num( i, 0 ); m_current_bm = i; return; } } } // // 前の書き込みに移動 // void ArticleViewBase::slot_pre_post() { assert( m_article ); if( m_current_post == 0 ) m_current_post = m_drawarea->get_seen_current(); for( int i = m_current_post -1 ; i >= 1 ; --i ){ if( m_article->is_posted( i ) ){ goto_num( i, 0 ); m_current_post = i; return; } } for( int i = m_article->get_number_load() ; i > m_current_post ; --i ){ if( m_article->is_posted( i ) ){ goto_num( i, 0 ); m_current_post = i; return; } } } // // 後ろの書き込みに移動 // void ArticleViewBase::slot_next_post() { assert( m_article ); if( m_current_post == 0 ) m_current_post = m_drawarea->get_seen_current(); for( int i = m_current_post + 1; i <= m_article->get_number_load() ; ++i ){ if( m_article->is_posted( i ) ){ goto_num( i, 0 ); m_current_post = i; return; } } for( int i = 1; i <= m_current_post ; ++i ){ if( m_article->is_posted( i ) ){ goto_num( i, 0 ); m_current_post = i; return; } } } // // ジャンプ // // 呼び出す前に m_jump_to に対象のレス番号を入れておくこと // void ArticleViewBase::slot_jump() { const std::string str_tab = "newtab"; const std::string str_mode = "auto"; CORE::core_set_command( "open_article", m_url_article , str_tab, str_mode, m_jump_to, m_jump_from ); } // // dat 保存 // void ArticleViewBase::slot_save_dat() { m_article->save_dat( std::string() ); } // // スレ情報の引き継ぎ // void ArticleViewBase::slot_copy_article_info() { if( m_url_tmp.empty() ) return; if( ! DBTREE::article_is_cached( m_url_tmp ) ) return; m_article->copy_article_info( m_url_tmp ); CORE::core_set_command( "replace_favorite_thread", "", m_url_tmp, m_url_article ); // 再レイアウト ARTICLE::get_admin()->set_command( "relayout_views", m_url_article ); } // // あぼーん設定 // void ArticleViewBase::slot_setup_abone() { auto pref = CORE::PrefDiagFactory( get_parent_win(), CORE::PREFDIAG_ARTICLE, m_url_article, "show_abone" ); pref->run(); } void ArticleViewBase::slot_setup_abone_board() { auto pref = CORE::PrefDiagFactory( get_parent_win(), CORE::PREFDIAG_BOARD, DBTREE::url_boardbase( m_url_article ), "show_abone_article" ); pref->run(); } void ArticleViewBase::slot_setup_abone_all() { auto pref = CORE::PrefDiagFactory( get_parent_win(), CORE::PREFDIAG_GLOBALABONE, "" ); pref->run(); } // // 荒らし報告用のURLリストをHTML形式で取得 // std::string ArticleViewBase::get_html_url4report( const std::list< int >& list_resnum ) { std::string html; for( const int num : list_resnum ) { const std::string time_str = m_article->get_time_str( num ); const std::string id_str = m_article->get_id_name( num ); html += url_for_copy() + std::to_string( num ); if( ! time_str.empty() ) html += " " + MISC::remove_str_regex( time_str, "\\([^\\)]+\\)" ); // 曜日を取り除く if( ! id_str.empty() ) html += " " + id_str.substr( strlen( PROTO_ID ) ); html += "
"; } return html; } // // レスを抽出して表示 // // num は "from-to" の形式。"a+b"の時はaとbを連結する(bのヘッダ行を取り除く) // // (例) 3から10を表示したいなら "3-10" // 3と4を連結して表示したい時は "3+4" // // show_title == trueの時は 板名、スレ名を表示 // void ArticleViewBase::show_res( const std::string& num, const bool show_title ) { assert( m_article ); #ifdef _DEBUG std::cout << "ArticleViewBase::show_res num = " << num << " show_title = " << show_title << std::endl; #endif // 板名、スレ名 if( show_title ){ std::string html; std::string tmpstr = DBTREE::board_name( m_url_article ); if( ! tmpstr.empty() ) html += "[ " + tmpstr + " ] "; tmpstr = DBTREE::article_subject( m_url_article ); if( ! tmpstr.empty() ) html += tmpstr; if( ! html.empty() ) append_html( html ); } std::list< bool > list_joint; std::list< int > list_resnum = m_article->get_res_str_num( num, list_joint ); if( !list_resnum.empty() ) append_res( list_resnum, list_joint ); else if( !show_title ) append_html( "未取得レス" ); } // // 名前 で抽出して表示 // // show_option = true の時は URL 表示などのオプションが表示される // void ArticleViewBase::show_name( const std::string& name, bool show_option ) { assert( m_article ); #ifdef _DEBUG std::cout << "ArticleViewBase::show_name " << name << std::endl; #endif std::list< int > list_resnum = m_article->get_res_name( name ); std::ostringstream comment; comment << "名前:" << name << " " << list_resnum.size() << " 件
"; comment << "総参照数:" << m_article->get_res_reference( list_resnum ).size() << " 件"; if( show_option && ! list_resnum.empty() ){ if( !m_show_url4report ) comment << "

抽出したレスのURLをリスト表示"; else comment << "

" + get_html_url4report( list_resnum ); comment << "

" << url_for_copy(); if( url_for_copy()[ url_for_copy().size() - 1 ] != '/' ) comment << "/"; comment << MISC::intlisttostr( list_resnum ) << "

"; } append_html( comment.str() ); if( ! list_resnum.empty() ) append_res( list_resnum ); } // // ID で抽出して表示 // // show_option = true の時は URL 表示などのオプションが表示される // void ArticleViewBase::show_id( const std::string& id_name, const bool show_option ) { assert( m_article ); #ifdef _DEBUG std::cout << "ArticleViewBase::show_id " << id_name << std::endl; #endif const std::list< int > list_resnum = m_article->get_res_id_name( id_name ); const std::string raw_id = id_name.substr( strlen( PROTO_ID ) ); std::ostringstream comment; comment << raw_id << " " << list_resnum.size() << " 件
"; comment << "総参照数:" << m_article->get_res_reference( list_resnum ).size() << " 件"; // 末尾判定 if( raw_id.length() == 9 ){ char c = raw_id.c_str()[ 8 ]; switch( c ){ case 'O': comment << "
末尾:" << c << " 携帯"; break; case 'o': comment << "
末尾:" << c << " 携帯(WILLCOM)"; break; case 'Q': comment << "
末尾:" << c << " ibisBrowserDX"; break; case 'I': case 'i': comment << "
末尾:" << c << " iPhone"; break; case 'P': comment << "
末尾:" << c << " p2"; break; case '0': comment << "
末尾:" << c << " PC"; break; } } if( show_option && ! list_resnum.empty() ){ if( !m_show_url4report ) comment << "

抽出したレスのURLをリスト表示"; else comment << "

" + get_html_url4report( list_resnum ); comment << "

" << url_for_copy(); if( url_for_copy()[ url_for_copy().size() - 1 ] != '/' ) comment << "/"; comment << MISC::intlisttostr( list_resnum ) << "

"; } append_html( comment.str() ); if( ! list_resnum.empty() ) append_res( list_resnum ); } // // ブックマークを抽出して表示 // void ArticleViewBase::show_bm() { assert( m_article ); #ifdef _DEBUG std::cout << "ArticleViewBase::show_bm " << std::endl; #endif std::list< int > list_resnum = m_article->get_res_bm(); if( ! list_resnum.empty() ) append_res( list_resnum ); else append_html( "しおりはセットされていません" ); } // // 書き込みを抽出して表示 // void ArticleViewBase::show_post() { assert( m_article ); #ifdef _DEBUG std::cout << "ArticleViewBase::show_post " << std::endl; #endif std::list< int > list_resnum = m_article->get_res_posted(); if( ! list_resnum.empty() ){ std::ostringstream comment; comment << "書き込み数:" << list_resnum.size() << " 件
"; comment << "総参照数:" << m_article->get_res_reference( list_resnum ).size() << " 件"; append_html( comment.str() ); append_res( list_resnum ); } else append_html( "このスレでは書き込みしていません" ); } // // 書き込みログを表示 // void ArticleViewBase::show_postlog( const int num ) { const int maxno = MESSAGE::get_log_manager()->get_max_num_of_log() + 1; std::string html_header; for( int i = 1; i <= maxno; ++i ){ int no; if( i == 1 ) no = 0; else no = maxno - ( i -1 ); if( no == num ) html_header += std::to_string( i ) + " "; else { html_header += std::string( "" + std::to_string( i ) + " "; } } #ifdef _DEBUG std::cout << "ArticleViewBase::show_postlog " << num << " / " << maxno << std::endl << html_header << std::endl; #endif std::string html = MESSAGE::get_log_manager()->get_post_log( num ); if( html.empty() ) html = "書き込みログがありません"; else html = html_header + "
" + html + "

" + html_header; append_html( html ); } // // 高参照レスを抽出して表示 // void ArticleViewBase::show_highly_referenced_res() { assert( m_article ); #ifdef _DEBUG std::cout << "ArticleViewBase::show_highly_referenced_res " << std::endl; #endif std::list< int > list_resnum = m_article->get_highly_referenced_res(); if( ! list_resnum.empty() ){ std::ostringstream comment; comment << "高参照レス数:" << list_resnum.size() << " 件
"; append_html( comment.str() ); append_res( list_resnum ); } else append_html( "このスレでは高参照レスはありません" ); } // // URLを含むレスを抽出して表示 // void ArticleViewBase::show_res_with_url() { assert( m_article ); #ifdef _DEBUG std::cout << "ArticleViewBase::show_res_with_url\n"; #endif std::list< int > list_resnum = m_article->get_res_with_url(); if( ! list_resnum.empty() ) append_res( list_resnum ); else append_html( "リンクを含むレスはありません" ); } // // num 番のレスを参照してるレスを抽出して表示 // void ArticleViewBase::show_refer( int num ) { assert( m_article ); #ifdef _DEBUG std::cout << "ArticleViewBase::show_refer " << num << std::endl; #endif std::list< int > list_resnum = m_article->get_res_reference( num ); std::ostringstream comment; comment << "参照数:" << list_resnum.size() << " 件"; append_html( comment.str() ); // num 番は先頭に必ず表示 list_resnum.push_front( num ); append_res( list_resnum ); } // // キーワードで抽出して表示 // // mode_or = true の時は or 検索 // show_option = true の時は URL 表示などのオプションが表示される // void ArticleViewBase::drawout_keywords( const std::string& query, bool mode_or, bool show_option ) { assert( m_article ); #ifdef _DEBUG std::cout << "ArticleViewBase::drawout_keywords " << query << std::endl; #endif std::list< int > list_resnum = m_article->get_res_query( query, mode_or ); std::ostringstream comment; comment << query << " " << list_resnum.size() << " 件"; if( show_option && ! list_resnum.empty() ){ if( !m_show_url4report ) comment << " 抽出したレスのURLをリスト表示"; else comment << "

" + get_html_url4report( list_resnum ); comment << "

" << url_for_copy(); if( url_for_copy()[ url_for_copy().size() - 1 ] != '/' ) comment << "/"; comment << MISC::intlisttostr( list_resnum ) << "

"; } append_html( comment.str() ); if( ! list_resnum.empty() ){ append_res( list_resnum ); // ハイライト表示 std::list< std::string > list_query = MISC::split_line( query ); m_drawarea->search( list_query, false ); } } // // html をappend // void ArticleViewBase::append_html( const std::string& html ) { #ifdef _DEBUG std::cout << "ArticleViewBase::append_html html = " << html << std::endl; #endif assert( m_drawarea ); m_drawarea->append_html( html ); redraw_view(); } // // dat をレス番号 num 番として append // // num < 0 の時は現在の最大スレ番号の後に追加 // void ArticleViewBase::append_dat( const std::string& dat, int num ) { assert( m_drawarea ); if( num < 0 ) num = get_article()->get_number_load() +1; m_drawarea->append_dat( dat, num ); redraw_view(); } // // リストで指定したレスの表示 // void ArticleViewBase::append_res( const std::list< int >& list_resnum ) { assert( m_drawarea ); m_drawarea->append_res( list_resnum ); redraw_view(); } // // リストで指定したレスを表示(連結情報付き) // // list_joint で連結指定したレスはヘッダを取り除いて前のレスに連結する // void ArticleViewBase::append_res( const std::list< int >& list_resnum, const std::list< bool >& list_joint ) { assert( m_drawarea ); m_drawarea->append_res( list_resnum, list_joint ); redraw_view(); } // // drawareaから出た // bool ArticleViewBase::slot_leave_notify( GdkEventCrossing* event ) { // クリックしたときやホイールを回すと event->mode に GDK_CROSSING_GRAB // か GDK_CROSSING_UNGRAB がセットされてイベントが発生する場合がある if( event->mode == GDK_CROSSING_GRAB ) return false; if( event->mode == GDK_CROSSING_UNGRAB ) return false; // クリックしたりホイールを回すとイベントが発生する時があるのでキャンセルする if( event->state & ( GDK_BUTTON1_MASK | GDK_BUTTON2_MASK | GDK_BUTTON3_MASK | GDK_BUTTON4_MASK | GDK_BUTTON5_MASK ) ) return false; #ifdef _DEBUG std::cout << "ArticleViewBase::slot_leave_drawarea :"; std::cout << " type = " << event->type; std::cout << " mode = " << event->mode; std::cout << " detail = " << event->detail; std::cout << " state = " << event->state << std::endl; #endif focus_out(); // ステータスバーの表示を戻す if( m_url_show_status ) { CORE::core_set_command( "restore_status", m_url_article ); m_url_show_status = false; } return false; } // // drawarea のクリックイベント // bool ArticleViewBase::slot_button_press( const std::string& url, int res_number, GdkEventButton* event ) { #ifdef _DEBUG std::cout << "ArticleViewBase::slot_button_press url = " << get_url() << std::endl; #endif // マウスジェスチャ get_control().MG_start( event ); // ホイールマウスジェスチャ get_control().MG_wheel_start( event ); ARTICLE::get_admin()->set_command( "switch_admin" ); return true; } // // drawarea でのマウスボタンのリリースイベント // bool ArticleViewBase::slot_button_release( std::string url, int res_number, GdkEventButton* event ) { /// マウスジェスチャ const int mg = get_control().MG_end( event ); // ホイールマウスジェスチャ // 実行された場合は何もしない if( get_control().MG_wheel_end( event ) ) return true; #ifdef _DEBUG std::cout << "ArticleViewBase::slot_button_release mg = " << mg << " url = " << get_url() << " url = " << url << " res = " << res_number << std::endl; #endif if( event->type == GDK_BUTTON_RELEASE ){ if( ! is_mouse_on_popup() ){ // マウスジェスチャ if( mg != CONTROL::None && enable_mg() ){ hide_popup(); operate_view( mg ); } // リンクをクリック else if( click_url( url, res_number, event ) ); // コンテキストメニュー表示 else if( get_control().button_alloted( event, CONTROL::PopupmenuButton ) ) show_popupmenu( url, false ); // 実況中の場合は停止 else if( get_control().button_alloted( event, CONTROL::ClickButton ) && get_live() ) ARTICLE::get_admin()->set_command( "live_start_stop", get_url() ); // その他 else operate_view( get_control().button_press( event ) ); } } return true; } // // drawarea でマウスが動いた // bool ArticleViewBase::slot_motion_notify( GdkEventMotion* event ) { /// マウスジェスチャ get_control().MG_motion( event ); return true; } // // drawareaのキープレスイベント // bool ArticleViewBase::slot_key_press( GdkEventKey* event ) { #ifdef _DEBUG std::cout << "ArticleViewBase::slot_key_press\n"; #endif // ポップアップはキーフォーカスを取れないので親からキー入力を送ってやる if( is_popup_shown() ){ ArticleViewBase* popup_article = dynamic_cast< ArticleViewBase* >( m_popup_win->view() ); if( popup_article ){ if( DBTREE::article_is_cached( popup_article->url_article() ) ) return popup_article->slot_key_press( event ); } else{ IMAGE::ImageViewPopup* popup_image = dynamic_cast< IMAGE::ImageViewPopup* >( m_popup_win->view() ); if( popup_image ) return popup_image->slot_key_press( event ); } } int key = get_control().key_press( event ); if( key != CONTROL::None ){ if( operate_view( key ) ) return true; } else if( release_keyjump_key( event->keyval ) ) return true; return false; } // // drawareaのキーリリースイベント // bool ArticleViewBase::slot_key_release( GdkEventKey* event ) { #ifdef _DEBUG std::cout << "ArticleViewBase::slot_key_release\n"; #endif // ポップアップはキーフォーカスを取れないのでキー入力を送ってやる ArticleViewBase* popup_article = nullptr; if( is_popup_shown() ) popup_article = dynamic_cast< ArticleViewBase* >( m_popup_win->view() ); if( popup_article ) return popup_article->slot_key_release( event ); return true; } // // drawareaのマウスホイールイベント // bool ArticleViewBase::slot_scroll_event( GdkEventScroll* event ) { // ポップアップしているときはそちらにイベントを送ってやる ArticleViewBase* popup_article = nullptr; if( is_popup_shown() ) popup_article = dynamic_cast< ArticleViewBase* >( m_popup_win->view() ); if( popup_article ) return popup_article->slot_scroll_event( event ); // ホイールマウスジェスチャ int control = get_control().MG_wheel_scroll( event ); if( enable_mg() && control != CONTROL::None ){ operate_view( control ); return true; } m_drawarea->wheelscroll( event ); return true; } // // リンクの上にポインタが来た // // drawareaのsig_on_url()シグナルとつなぐ // void ArticleViewBase::slot_on_url( const std::string& url, const std::string& imgurl, int res_number ) { #ifdef _DEBUG std::cout << "ArticleViewBase::slot_on_url = " << url << " imgurl = " << imgurl << std::endl; #endif // ポップアップが消えるまでに同じリンク上にポインタを乗せたら // カウンタをリセットする if( m_hidepopup_counter && m_popup_url == url ){ m_hidepopup_counter = 0; return; } CORE::VIEWFACTORY_ARGS args; SKELETON::View* view_popup = nullptr; int margin_popup_x = 0; int margin_popup_y = CONFIG::get_margin_popup(); m_popup_url = url; // 画像ポップアップ if( DBIMG::get_type_ext( imgurl ) != DBIMG::T_UNKNOWN ){ // あぼーん if( DBIMG::get_abone( imgurl ) ){ args.arg1 = "あぼ〜んされています"; view_popup = CORE::ViewFactory( CORE::VIEW_ARTICLEPOPUPHTML, m_url_article, args ); } else if( ( ! DBIMG::is_cached( imgurl ) || CONFIG::get_use_image_popup() ) && ( DBIMG::is_loading( imgurl ) || DBIMG::is_wait( imgurl ) || DBIMG::get_code( imgurl ) != HTTP_INIT ) ){ #ifdef _DEBUG std::cout << "image " << DBIMG::get_code( imgurl) << " " << DBIMG::is_loading( imgurl ) << "\n"; #endif view_popup = CORE::ViewFactory( CORE::VIEW_IMAGEPOPUP, imgurl ); margin_popup_x = CONFIG::get_margin_imgpopup_x(); margin_popup_y = CONFIG::get_margin_imgpopup(); } } // レスポップアップ else if( url.rfind( PROTO_ANCHORE, 0 ) == 0 ){ args.arg1 = url.substr( strlen( PROTO_ANCHORE) ); args.arg2 = "false"; // 板名、スレ名非表示 args.arg3 = "false"; // あぼーんしたレスの内容は非表示(あぼーんと表示) #ifdef _DEBUG std::cout << "anchore = " << args.arg1 << std::endl; #endif view_popup = CORE::ViewFactory( CORE::VIEW_ARTICLEPOPUPRES, m_url_article, args ); } // レス番号 else if( url.rfind( PROTO_RES, 0 ) == 0 ){ args.arg1 = url.substr( strlen( PROTO_RES ) ); int tmp_num = atoi( args.arg1.c_str() ); #ifdef _DEBUG std::cout << "res = " << args.arg1 << std::endl; #endif // あぼーんされたレスの内容をポップアップ表示 if( m_article->get_abone( tmp_num ) ){ args.arg2 = "false"; // 板名、スレ名非表示 args.arg3 = "true"; // あぼーんレスの内容を表示 view_popup = CORE::ViewFactory( CORE::VIEW_ARTICLEPOPUPRES, m_url_article, args ); } // 参照ポップアップ else if( CONFIG::get_refpopup_by_mo() && m_article->get_res_reference( tmp_num ).size() ){ view_popup = CORE::ViewFactory( CORE::VIEW_ARTICLEPOPUPREFER, m_url_article, args ); } } // 抽出(or) else if( url.rfind( PROTO_OR, 0 ) == 0 ){ std::string url_tmp = url.substr( strlen( PROTO_OR ) ); int i = url_tmp.find( KEYWORD_SIGN ); std::string url_dat = DBTREE::url_dat( url_tmp.substr( 0, i ) ); args.arg1 = url_tmp.substr( i + strlen( KEYWORD_SIGN ) ); args.arg2 = "OR"; if( ! url_dat.empty() ){ view_popup = CORE::ViewFactory( CORE::VIEW_ARTICLEPOPUPDRAWOUT, url_dat, args ); } } // しおり else if( url.rfind( PROTO_BM, 0 ) == 0 ){ const std::string url_tmp = url.substr( strlen( PROTO_BM ) ); const std::string url_dat = DBTREE::url_dat( url_tmp ); if( ! url_dat.empty() ){ view_popup = CORE::ViewFactory( CORE::VIEW_ARTICLEPOPUPBM, url_dat ); } } // 名前ポップアップ else if( CONFIG::get_namepopup_by_mo() && url.rfind( PROTO_NAME, 0 ) == 0 ){ int num_name = m_article->get_num_name( res_number ); args.arg1 = m_article->get_name( res_number ); if( num_name >= 1 ){ view_popup = CORE::ViewFactory( CORE::VIEW_ARTICLEPOPUPNAME, m_url_article, args ); } } // IDポップアップ else if( CONFIG::get_idpopup_by_mo() && url.rfind( PROTO_ID, 0 ) == 0 ){ args.arg1 = m_article->get_id_name( res_number ); int num_id = m_article->get_num_id_name( res_number ); if( num_id >= 1 ){ view_popup = CORE::ViewFactory( CORE::VIEW_ARTICLEPOPUPID, m_url_article, args ); } } // ID:〜の範囲選択の上にポインタがあるときIDポップアップ else if( url.rfind( "ID:", 0 ) == 0 ){ args.arg1 = PROTO_ID + url; int num_id = m_article->get_num_id_name( args.arg1 ); #ifdef _DEBUG std::cout << "num_id = " << num_id << std::endl; #endif if( num_id >= 1 ){ view_popup = CORE::ViewFactory( CORE::VIEW_ARTICLEPOPUPID, m_url_article, args ); } } // その他のリンク else{ // dat 又は板の場合 int num_from, num_to; std::string num_str; const std::string url_dat = DBTREE::url_dat( url, num_from, num_to, num_str ); const std::string boardbase = DBTREE::url_boardbase( url ); #ifdef _DEBUG std::cout << "ArticleViewBase::slot_on_url " << url << std::endl; std::cout << "url_dat = " << url_dat << std::endl; std::cout << "boardbase = " << boardbase << std::endl; std::cout << "num_from = " << num_from << std::endl; std::cout << "num_to = " << num_to << std::endl; std::cout << "num = " << num_str << std::endl; #endif // 他スレ if( ! url_dat.empty() ){ if( num_from == 0 ) args.arg1 = "1"; // 最低でも1レス目は表示 else if( num_str.empty() ) args.arg1 = "1"; else args.arg1 = num_str; args.arg2 = "true"; // 板名、スレ名表示 args.arg3 = "false"; // あぼーんしたレスの内容は非表示(あぼーんと表示) view_popup = CORE::ViewFactory( CORE::VIEW_ARTICLEPOPUPRES, url_dat, args ); } // 板 else if( ! boardbase.empty() ){ std::string tmpstr = DBTREE::board_name( url ); args.arg1 = "[ " + tmpstr + " ] "; view_popup = CORE::ViewFactory( CORE::VIEW_ARTICLEPOPUPHTML, m_url_article, args ); } } if( view_popup ) show_popup( view_popup, margin_popup_x, margin_popup_y ); // リンクとして扱うURLをステータスバーに表示する if( MISC::is_url_scheme( url.c_str() ) != MISC::SCHEME_NONE ) { std::string status_url; // デコードが必要な物 if( url.find( '%' ) != std::string::npos ) { std::string tmp = MISC::url_decode( url ); const int char_code = MISC::judge_char_code( tmp ); switch( char_code ) { case MISC::CHARCODE_EUC_JP: status_url = MISC::Iconv( tmp, "UTF-8", "EUC-JP" ); break; case MISC::CHARCODE_JIS: status_url = MISC::Iconv( tmp, "UTF-8", "ISO-2022-JP" ); break; case MISC::CHARCODE_SJIS: status_url = MISC::Iconv( tmp, "UTF-8", "MS932" ); break; case MISC::CHARCODE_ASCII: case MISC::CHARCODE_UTF: status_url = tmp; break; default: status_url = url; } // 改行はスペースに変えておく status_url = MISC::replace_newlines_to_str( status_url, " " ); } // デコードの必要がない物 else { status_url = url; } // 一時的にステータスバーにURLを表示( 長くても全部表示しちゃう? ) CORE::core_set_command( "set_status_temporary", m_url_article, status_url ); m_url_show_status = true; } } // // リンクからマウスが出た // void ArticleViewBase::slot_leave_url() { #ifdef _DEBUG std::cout << "ArticleViewBase::slot_leave_url\n"; #endif if( m_url_show_status ) { // ステータスバーの表示を戻す CORE::core_set_command( "restore_status", m_url_article ); m_url_show_status = false; } if( is_popup_shown() ){ if( CONFIG::get_hide_popup_msec() ) m_hidepopup_counter = CONFIG::get_hide_popup_msec() / TIMER_TIMEOUT; else hide_popup(); } } // // リンクをクリック // bool ArticleViewBase::click_url( std::string url, int res_number, GdkEventButton* event ) { assert( m_article ); if( url.empty() ) return false; CONTROL::Control& control = get_control(); #ifdef _DEBUG std::cout << "ArticleViewBase::click_url " << url << std::endl; #endif // プレビュー画面など、レスが存在しないときはいくつかの機能を無効にする const bool res_exist = ( ! m_article->empty() && m_article->res_header( res_number ) ); // ssspの場合は PROTO_SSSP が前に付いているので取り除く const bool sssp = ( url.rfind( PROTO_SSSP, 0 ) == 0 ); if( sssp ) url = url.substr( strlen( PROTO_SSSP ) ); ///////////////////////////////////////////////////////////////// // ID クリック if( url.rfind( PROTO_ID, 0 ) == 0 ){ if( ! res_exist ) return true; if( is_popup_shown() && control.button_alloted( event, CONTROL::PopupWarpButton ) ) warp_pointer_to_popup(); else{ hide_popup(); const int num_id = m_article->get_num_id_name( res_number ); m_id_name = m_article->get_id_name( res_number ); // ID ポップアップ if( num_id >= 1 && control.button_alloted( event, CONTROL::PopupIDButton ) ){ CORE::VIEWFACTORY_ARGS args; args.arg1 = m_id_name; SKELETON::View* view_popup = CORE::ViewFactory( CORE::VIEW_ARTICLEPOPUPID, m_url_article, args ); const int margin_popup_x = 0; const int margin_popup_y = CONFIG::get_margin_popup(); show_popup( view_popup, margin_popup_x, margin_popup_y ); } else if( control.button_alloted( event, CONTROL::DrawoutIDButton ) ) slot_drawout_id(); else if( control.button_alloted( event, CONTROL::PopupmenuIDButton ) ){ show_popupmenu( url, false ); } } } ///////////////////////////////////////////////////////////////// // 名前クリック else if( url.rfind( PROTO_NAME, 0 ) == 0 ){ if( ! res_exist ) return true; if( is_popup_shown() && control.button_alloted( event, CONTROL::PopupWarpButton ) ) warp_pointer_to_popup(); else{ hide_popup(); int num_name = m_article->get_num_name( res_number ); m_name = m_article->get_name( res_number ); // 名前ポップアップ if( num_name >= 1 && control.button_alloted( event, CONTROL::PopupIDButton ) ){ CORE::VIEWFACTORY_ARGS args; args.arg1 = m_name; SKELETON::View* view_popup = CORE::ViewFactory( CORE::VIEW_ARTICLEPOPUPNAME, m_url_article, args ); const int margin_popup_x = 0; const int margin_popup_y = CONFIG::get_margin_popup(); show_popup( view_popup, margin_popup_x, margin_popup_y ); } else if( control.button_alloted( event, CONTROL::DrawoutIDButton ) ) slot_drawout_name(); else if( control.button_alloted( event, CONTROL::PopupmenuIDButton ) ){ show_popupmenu( url, false ); } } } ///////////////////////////////////////////////////////////////// // あぼーんクリック else if( url.rfind( PROTO_ABONE, 0 ) == 0 ){ hide_popup(); if( control.button_alloted( event, CONTROL::PopupmenuAncButton ) ){ show_popupmenu( url, false ); } } ///////////////////////////////////////////////////////////////// // 壊れていますクリック else if( url.rfind( PROTO_BROKEN, 0 ) == 0 ){ hide_popup(); if( control.button_alloted( event, CONTROL::PopupmenuAncButton ) ){ show_popupmenu( url, false ); } } ///////////////////////////////////////////////////////////////// // 荒らし報告用URL表示クリック else if( url.rfind( PROTO_URL4REPORT, 0 ) == 0 ){ hide_popup(); m_show_url4report = true; relayout(); } ///////////////////////////////////////////////////////////////// // 書き込みログ表示クリック else if( url.rfind( PROTO_POSTLOG, 0 ) == 0 ){ hide_popup(); CORE::core_set_command( "open_article_postlog" ,"", url.substr( strlen( PROTO_POSTLOG ) ) ); } ///////////////////////////////////////////////////////////////// // BE クリック else if( url.rfind( PROTO_BE, 0 ) == 0 ){ if( ! res_exist ) return true; hide_popup(); std::stringstream ssurl; ssurl << "http://be.2ch.net/test/p.php?i=" << url.substr( strlen( PROTO_BE ) ) << "&u=d:" << DBTREE::url_readcgi( m_url_article, res_number, 0 ); #ifdef _DEBUG std::cout << "open " << ssurl.str() << std::endl; #endif if( control.button_alloted( event, CONTROL::OpenBeButton ) ) CORE::core_set_command( "open_url_browser", ssurl.str() ); else if( control.button_alloted( event, CONTROL::PopupmenuBeButton ) ){ show_popupmenu( ssurl.str(), false ); } } ///////////////////////////////////////////////////////////////// // アンカーをクリック else if( url.rfind( PROTO_ANCHORE, 0 ) == 0 ){ if( is_popup_shown() && control.button_alloted( event, CONTROL::PopupWarpButton ) ) warp_pointer_to_popup(); else{ // ジャンプ先セット m_jump_to = url.substr( strlen( PROTO_ANCHORE ) ); m_jump_from = std::to_string( res_number ); m_str_num = m_jump_to; #ifdef _DEBUG std::cout << "anchor num = " << m_str_num << std::endl; #endif hide_popup(); if( control.button_alloted( event, CONTROL::JumpAncButton ) ) slot_jump(); else if( control.button_alloted( event, CONTROL::PopupmenuAncButton ) ) show_popupmenu( url, false ); else if( control.button_alloted( event, CONTROL::DrawoutAncButton ) ) slot_drawout_around(); } } ///////////////////////////////////////////////////////////////// // レス番号クリック else if( url.rfind( PROTO_RES, 0 ) == 0 ){ if( ! res_exist ) return true; if( is_popup_shown() && control.button_alloted( event, CONTROL::PopupWarpButton ) ) warp_pointer_to_popup(); else{ hide_popup(); m_str_num = std::to_string( res_number ); m_url_tmp = DBTREE::url_readcgi( m_url_article, res_number, 0 ); // ジャンプ先セット m_jump_to = m_str_num; m_jump_from = m_str_num; if( control.button_alloted( event, CONTROL::PopupmenuResButton ) ) show_popupmenu( url, false ); // ブックマークセット else if( control.button_alloted( event, CONTROL::BmResButton ) ) slot_bookmark(); // 書き込みマークの設定/解除 else if( control.button_alloted( event, CONTROL::PostedMarkButton ) ) slot_postedmark(); // 参照ポップアップ表示 else if( control.button_alloted( event, CONTROL::ReferResButton ) ){ CORE::VIEWFACTORY_ARGS args; args.arg1 = m_str_num; SKELETON::View* view_popup = CORE::ViewFactory( CORE::VIEW_ARTICLEPOPUPREFER, m_url_article, args ); const int margin_popup_x = 0; const int margin_popup_y = CONFIG::get_margin_popup(); show_popup( view_popup, margin_popup_x, margin_popup_y ); } } } ///////////////////////////////////////////////////////////////// // OR抽出 else if( url.rfind( PROTO_OR, 0 ) == 0 ){ const std::string url_tmp = url.substr( strlen( PROTO_OR ) ); int i = url_tmp.find( KEYWORD_SIGN ); const std::string url_dat = DBTREE::url_dat( url_tmp.substr( 0, i ) ); if( ! url_dat.empty() ){ const std::string query = url_tmp.substr( i + strlen( KEYWORD_SIGN ) ); CORE::core_set_command( "open_article_keyword" ,url_dat, query, "true" ); } } ///////////////////////////////////////////////////////////////// // しおり抽出 else if( url.rfind( PROTO_BM, 0 ) == 0 ){ const std::string url_tmp = url.substr( strlen( PROTO_BM ) ); const std::string url_dat = DBTREE::url_dat( url_tmp ); if( ! url_dat.empty() ){ CORE::core_set_command( "open_article_bm" , url_dat ); } } ///////////////////////////////////////////////////////////////// // リンクフィルタ else if( control.button_alloted( event, CONTROL::ClickButton ) && CORE::get_linkfilter_manager()->exec( m_url_article, url, m_drawarea->str_pre_selection() ) ){} ///////////////////////////////////////////////////////////////// // 画像クリック else if( DBIMG::get_type_ext( url ) != DBIMG::T_UNKNOWN && ( CONFIG::get_use_image_view() || CONFIG::get_use_inline_image() || ( sssp && CONFIG::get_show_ssspicon() ) ) ){ hide_popup(); if( control.button_alloted( event, CONTROL::PopupmenuImageButton ) ){ m_str_num = std::to_string( res_number ); show_popupmenu( url, false ); } else if( ! DBIMG::is_cached( url ) && ! SESSION::is_online() ){ SKELETON::MsgDiag mdiag( get_parent_win(), "オフラインです" ); mdiag.run(); } else if( DBIMG::get_abone( url )){ SKELETON::MsgDiag mdiag( get_parent_win(), "あぼ〜んされています" ); mdiag.run(); } else if( DBIMG::get_type_real( url ) == DBIMG::T_LARGE ){ SKELETON::MsgDiag mdiag( get_parent_win(), "画像サイズが大きすぎます。\n\n表示するにはリンクの上でコンテキストメニューを開いて\n「サイズが大きい画像を表示」をクリックしてください。" ); mdiag.run(); } else{ const bool open_imageview = ( ! sssp // sssp の時は画像ビューを開かない && CONFIG::get_use_image_view() && ( control.button_alloted( event, CONTROL::ClickButton ) || control.button_alloted( event, CONTROL::OpenBackImageButton ) ) ); const bool open_browser = ( ! open_imageview && control.button_alloted( event, CONTROL::ClickButton ) ); const bool mosaic = ( CONFIG::get_use_mosaic() && ! sssp // sssp の時はモザイクをかけない ); // 画像ビューに切り替える const bool switch_image = ( open_imageview && ! control.button_alloted( event, CONTROL::OpenBackImageButton ) ); open_image( url, res_number, open_imageview, open_browser, mosaic, switch_image ); } } ///////////////////////////////////////////////////////////////// // ブラウザで開く else if( control.button_alloted( event, CONTROL::ClickButton ) ){ std::string tmp_url = url; // 相対パス if( url.rfind( "./", 0 ) == 0 ) tmp_url = DBTREE::url_boardbase( m_url_article ) + url.substr( 1 ); // 絶対パス else if( url.rfind( '/', 0 ) == 0 ) tmp_url = DBTREE::url_root( m_url_article ) + url.substr( 1 ); hide_popup(); // tmp_urlが他スレのアドレスなら、datロード終了時に次スレ移行チェックを行う DBTREE::article_set_url_pre_article( tmp_url, m_url_article ); CORE::core_set_command( "open_url", tmp_url ); } ///////////////////////////////////////////////////////////////// // 失敗 else return false; return true; } // // 画像表示 // void ArticleViewBase::open_image( const std::string& url, const int res_number, const bool open_imageview, const bool open_browser, const bool mosaic, const bool switch_image ) { #ifdef _DEBUG std::cout << "ArticleViewBase::open_image url = " << url << " num = " << res_number << " open_view = " << open_imageview << " open_browser = " << open_browser << " mosaic = " << mosaic << " switch_image = " << switch_image << std::endl; #endif bool load = false; // キャッシュに無かったらロード if( ! DBIMG::is_cached( url ) ){ DBIMG::download_img( url, DBTREE::url_readcgi( m_url_article, res_number, 0 ), mosaic ); // ポップアップ表示してダウンロードサイズを表示 hide_popup(); SKELETON::View* view_popup = CORE::ViewFactory( CORE::VIEW_IMAGEPOPUP, url ); const int margin_popup_x = CONFIG::get_margin_imgpopup_x(); const int margin_popup_y = CONFIG::get_margin_imgpopup(); show_popup( view_popup, margin_popup_x, margin_popup_y ); load = true; } // 画像ビューを開く if( open_imageview ){ CORE::core_set_command( "open_image", url ); if( ! load && switch_image ) CORE::core_set_command( "switch_image" ); } // ブラウザで画像を開く else if( ! load && open_browser ) CORE::core_set_command( "open_url", url ); redraw_view(); } // // ポップアップが表示されているか // bool ArticleViewBase::is_popup_shown() const { return ( m_popup_win && m_popup_shown ); } // // ポップアップが表示されていてかつマウスがその上にあるか // bool ArticleViewBase::is_mouse_on_popup() { if( ! is_popup_shown() ) return false; return m_popup_win->view()->is_mouse_on_view(); } // // ポップアップ表示 // // view にあらかじめ内容をセットしてから呼ぶこと // viewは SKELETON::PopupWin のデストラクタで削除される // void ArticleViewBase::show_popup( SKELETON::View* view, const int mrg_x, const int mrg_y ) { hide_popup(); if( !view ) return; delete_popup(); m_popup_win = std::make_unique( this, view, mrg_x, mrg_y ); m_popup_win->signal_leave_notify_event().connect( sigc::mem_fun( *this, &ArticleViewBase::slot_popup_leave_notify_event ) ); m_popup_win->sig_hide_popup().connect( sigc::mem_fun( *this, &ArticleViewBase::slot_hide_popup ) ); m_popup_shown = true; } // // 子 popup windowの外にポインタが出た // bool ArticleViewBase::slot_popup_leave_notify_event( GdkEventCrossing* event ) { #ifdef _DEBUG std::cout << "ArticleViewBase::slot_popup_leave_notify_event\n"; #endif // クリックしたときやホイールを回すと event->mode に GDK_CROSSING_GRAB // か GDK_CROSSING_UNGRAB がセットされてイベントが発生する場合がある if( event->mode == GDK_CROSSING_GRAB ) return false; if( event->mode == GDK_CROSSING_UNGRAB ) return false; // ポインタがポップアップ上だったらポップアップは消さない // 時々ポインタがポップアップの上にあってもイベントが発生する場合がある if( is_mouse_on_popup() ) return false; if( CONFIG::get_hide_popup_msec() ) m_hidepopup_counter = CONFIG::get_hide_popup_msec() / TIMER_TIMEOUT; else slot_hide_popup(); return true; } // // 子 popup windowからhide依頼が来た // void ArticleViewBase::slot_hide_popup() { #ifdef _DEBUG std::cout << "ArticleViewBase::slot_hide_popup\n"; #endif hide_popup(); // ポインタがwidgetの外にあったら親に知らせて自分も閉じてもらう if( ! is_mouse_on_view() ) sig_hide_popup().emit(); } // // popup のhide // // force = true ならチェック無しで強制 hide // void ArticleViewBase::hide_popup( const bool force ) { if( ! is_popup_shown() ) return; m_hidepopup_counter = 0; #ifdef _DEBUG std::cout << "ArticleViewBase::hide_popup force = " << force << " " << get_url() << std::endl; #endif // ArticleView をポップアップ表示している場合 ArticleViewBase* popup_article = nullptr; popup_article = dynamic_cast< ArticleViewBase* >( m_popup_win->view() ); if( popup_article ){ if( ! force ){ // 孫のpopupが表示されてたらhideしない if( popup_article->is_popup_shown() ) return; // ポップアップメニューが表示されてたらhideしない // ( ポップアップメニューがhideしたときにhideする ) if( SESSION::is_popupmenu_shown() ) return; #ifdef _DEBUG std::cout << "target = " << popup_article->get_url() << std::endl; #endif } popup_article->hide_popup( force ); } m_popup_win->hide(); if( m_popup_win->view() ) m_popup_win->view()->stop(); m_popup_shown = false; m_drawarea->redraw_view(); } // // ポップアップの削除 // void ArticleViewBase::delete_popup() { #ifdef _DEBUG std::cout << "ArticleViewBase::delete_popup " << get_url() << std::endl; #endif m_popup_win.reset(); m_popup_shown = false; } // // ポップアップメニューを表示する前にメニューのアクティブ状態を切り替える // // SKELETON::View::show_popupmenu() を参照すること // void ArticleViewBase::activate_act_before_popupmenu( const std::string& url ) { #ifdef _DEBUG std::cout << "ArticleViewBase::activate_act_before_popupmenu url = " << url << std::endl; #endif // toggle アクションを activeにするとスロット関数が呼ばれるので処理しないようにする m_enable_menuslot = false; // 子ポップアップが表示されていて、かつポインタがその上だったら表示しない ArticleViewBase* popup_article = nullptr; if( is_popup_shown() ) popup_article = dynamic_cast< ArticleViewBase* >( m_popup_win->view() ); if( popup_article && popup_article->is_mouse_on_view() ) return; hide_popup(); Glib::RefPtr< Gtk::Action > act, act2; act = action_group()->get_action( "CopyURL" ); act2 = action_group()->get_action( "OpenBrowser" ); // url がセットされてない if( url.empty() ) { if( act ) act->set_sensitive( false ); if( act2 ) act2->set_sensitive( false ); m_url_tmp = std::string(); } // url がセットされている else { if( act ) act->set_sensitive( true ); if( act2 ) act2->set_sensitive( true ); // レス番号クリックの場合 if( url.rfind( PROTO_RES, 0 ) == 0 ){ m_url_tmp = DBTREE::url_readcgi( m_url_article, atoi( url.substr( strlen( PROTO_RES ) ).c_str() ), 0 ); } // アンカークリックの場合 else if( url.rfind( PROTO_ANCHORE, 0 ) == 0 ){ m_url_tmp = DBTREE::url_readcgi( m_url_article, atoi( url.substr( strlen( PROTO_ANCHORE ) ).c_str() ), 0 ); } else m_url_tmp = url; } // 検索ビューや書き込みログ表示などの場合 const bool nourl = DBTREE::url_readcgi( m_url_article, 0, 0 ).empty(); act = action_group()->get_action( "Drawout_Menu" ); if( act ){ if( nourl ) act->set_sensitive( false ); else act->set_sensitive( true ); } act = action_group()->get_action( "SearchCacheLocal" ); if( act ){ if( nourl ) act->set_sensitive( false ); else act->set_sensitive( true ); } act = action_group()->get_action( "SearchCacheAll" ); if( act ){ if( nourl ) act->set_sensitive( false ); else act->set_sensitive( true ); } act = action_group()->get_action( "SearchNextArticle" ); if( act ){ if( nourl ) act->set_sensitive( false ); else act->set_sensitive( true ); } act = action_group()->get_action( "QuoteRes" ); if( act ){ if( nourl ) act->set_sensitive( false ); else act->set_sensitive( true ); } act = action_group()->get_action( "SaveDat" ); if( act ){ if( nourl ) act->set_sensitive( false ); else act->set_sensitive( true ); } act = action_group()->get_action( "PreferenceArticle" ); if( act ){ if( nourl ) act->set_sensitive( false ); else act->set_sensitive( true ); } // 範囲選択されてない const unsigned int max_selection_str = 1024; const unsigned int max_selection_str_quote = 8192; std::string str_select = m_drawarea->str_selection(); act = action_group()->get_action( "QuoteSelectionRes" ); if( act ){ if( nourl || str_select.empty() || str_select.length() > max_selection_str_quote ) act->set_sensitive( false ); else act->set_sensitive( true ); } act = action_group()->get_action( "Copy" ); if( act ){ if( str_select.empty() ) act->set_sensitive( false ); else act->set_sensitive( true ); } act = action_group()->get_action( "DrawoutWord" ); if( act ){ if( str_select.empty() || str_select.length() > max_selection_str ) act->set_sensitive( false ); else act->set_sensitive( true ); } act = action_group()->get_action( "AboneWord_Menu" ); if( act ){ if( nourl || str_select.empty() || str_select.length() > max_selection_str ) act->set_sensitive( false ); else act->set_sensitive( true ); } act = action_group()->get_action( "AboneSelectionRes" ); if( act ){ if( nourl || str_select.empty() ) act->set_sensitive( false ); else act->set_sensitive( true ); } act = action_group()->get_action( "ShowSelectImage" ); if( act ){ if( str_select.empty() || ! m_drawarea->get_selection_imgurls().size() ) act->set_sensitive( false ); else act->set_sensitive( true ); } act = action_group()->get_action( "DeleteSelectImage_Menu" ); if( act ){ if( str_select.empty() || ! m_drawarea->get_selection_imgurls().size() ) act->set_sensitive( false ); else act->set_sensitive( true ); } act = action_group()->get_action( "AboneSelectImage_Menu" ); if( act ){ if( str_select.empty() || ! m_drawarea->get_selection_imgurls().size() ) act->set_sensitive( false ); else act->set_sensitive( true ); } // 検索関係 act = action_group()->get_action( "SearchWeb" ); if( act ){ if( str_select.empty() || str_select.length() > max_selection_str ) act->set_sensitive( false ); else act->set_sensitive( true ); } act = action_group()->get_action( "SearchCacheLocal" ); if( act ){ if( str_select.empty() || str_select.length() > max_selection_str ) act->set_sensitive( false ); else act->set_sensitive( true ); } act = action_group()->get_action( "SearchCacheAll" ); if( act ){ if( str_select.empty() || str_select.length() > max_selection_str ) act->set_sensitive( false ); else act->set_sensitive( true ); } act = action_group()->get_action( "SearchTitle" ); if( act ){ if( str_select.empty() || str_select.length() > max_selection_str ) act->set_sensitive( false ); else act->set_sensitive( true ); } // ユーザコマンド // 選択不可かどうか判断して visible か sensitive にする CORE::get_usrcmd_manager()->toggle_sensitive( action_group(), m_url_article, url, str_select ); // ブックマークがセットされていない act = action_group()->get_action( "DrawoutBM" ); if( act ){ if( m_article->get_num_bookmark() ) act->set_sensitive( true ); else act->set_sensitive( false ); } act = action_group()->get_action( "PreBookMark" ); if( act ){ if( m_article->get_num_bookmark() ) act->set_sensitive( true ); else act->set_sensitive( false ); } act = action_group()->get_action( "NextBookMark" ); if( act ){ if( m_article->get_num_bookmark() ) act->set_sensitive( true ); else act->set_sensitive( false ); } // 書き込みしていない act = action_group()->get_action( "DrawoutPost" ); if( act ){ if( m_article->get_num_posted() ) act->set_sensitive( true ); else act->set_sensitive( false ); } act = action_group()->get_action( "PrePost" ); if( act ){ if( m_article->get_num_posted() ) act->set_sensitive( true ); else act->set_sensitive( false ); } act = action_group()->get_action( "NextPost" ); if( act ){ if( m_article->get_num_posted() ) act->set_sensitive( true ); else act->set_sensitive( false ); } // 高参照レス抽出 act = action_group()->get_action( "DrawoutHighRefRes" ); if( act ){ if( nourl ) act->set_sensitive( false ); else act->set_sensitive( true ); } // 新着移動 act = action_group()->get_action( "GotoNew" ); if( act ){ if( m_article->get_number_new() ) act->set_sensitive( true ); else act->set_sensitive( false ); } // 進む、戻る act = action_group()->get_action( "PrevView" ); if( act ){ if( HISTORY::get_history_manager()->can_back_viewhistory( get_url(), 1 ) ) act->set_sensitive( true ); else act->set_sensitive( false ); } act = action_group()->get_action( "NextView" ); if( act ){ if( HISTORY::get_history_manager()->can_forward_viewhistory( get_url(), 1 ) ) act->set_sensitive( true ); else act->set_sensitive( false ); } // 透明あぼーん act = action_group()->get_action( "TranspAbone" ); if( act ){ Glib::RefPtr< Gtk::ToggleAction > tact = Glib::RefPtr< Gtk::ToggleAction >::cast_dynamic( act ); if( m_article->get_abone_transparent() ) tact->set_active( true ); else tact->set_active( false ); } // 透明/連鎖あぼーん act = action_group()->get_action( "TranspChainAbone" ); if( act ){ Glib::RefPtr< Gtk::ToggleAction > tact = Glib::RefPtr< Gtk::ToggleAction >::cast_dynamic( act ); if( m_article->get_abone_transparent() && m_article->get_abone_chain() ) tact->set_active( true ); else tact->set_active( false ); } // 画像 if( ! url.empty() && DBIMG::get_type_ext( url ) != DBIMG::T_UNKNOWN ){ // モザイク解除 act = action_group()->get_action( "Cancel_Mosaic" ); if( act ){ if( DBIMG::is_cached( url ) && DBIMG::get_mosaic( url ) ) act->set_sensitive( true ); else act->set_sensitive( false ); } // モザイクで開く act = action_group()->get_action( "Show_Mosaic" ); if( act ){ if( ! DBIMG::is_cached( url ) ) act->set_sensitive( true ); else act->set_sensitive( false ); } // サイズの大きい画像を表示 act = action_group()->get_action( "ShowLargeImg" ); if( act ){ if( DBIMG::get_type_real( url ) == DBIMG::T_LARGE ) act->set_sensitive( true ); else act->set_sensitive( false ); } // 保護のトグル切替え act = action_group()->get_action( "ProtectImage" ); if( act ){ if( DBIMG::is_cached( url ) ){ act->set_sensitive( true ); Glib::RefPtr< Gtk::ToggleAction > tact = Glib::RefPtr< Gtk::ToggleAction >::cast_dynamic( act ); if( DBIMG::is_protected( url ) ) tact->set_active( true ); else tact->set_active( false ); } else act->set_sensitive( false ); } // 削除 act = action_group()->get_action( "DeleteImage_Menu" ); if( act ){ if( DBIMG::get_code( url ) != HTTP_INIT && ! DBIMG::is_protected( url ) ) act->set_sensitive( true ); else act->set_sensitive( false ); } // 保存 act = action_group()->get_action( "SaveImage" ); if( act ){ if( DBIMG::is_cached( url ) ) act->set_sensitive( true ); else act->set_sensitive( false ); } // プロパティ act = action_group()->get_action( "PreferenceImage" ); if( act ){ if( DBIMG::is_cached( url ) ) act->set_sensitive( true ); else act->set_sensitive( false ); } // あぼーん act = action_group()->get_action( "AboneImage" ); if( act ){ if( DBIMG::is_protected( url ) ) act->set_sensitive( false ); else{ act->set_sensitive( true ); Glib::RefPtr< Gtk::ToggleAction > tact = Glib::RefPtr< Gtk::ToggleAction >::cast_dynamic( act ); if( DBIMG::get_abone( url ) ) tact->set_active( true ); else tact->set_active( false ); } } // キャッシュをブラウザで開く act = action_group()->get_action( "OpenCacheBrowser" ); if( act ){ if( DBIMG::is_cached( url ) ) act->set_sensitive( true ); else act->set_sensitive( false ); } } // スレ情報の引き継ぎ act = action_group()->get_action( "CopyInfo" ); if( act ){ if( ! url.empty() && ! DBTREE::url_dat( url ).empty() ) act->set_sensitive( true ); else act->set_sensitive( false ); } m_enable_menuslot = true; } // // ポップアップメニュー取得 // // SKELETON::View::show_popupmenu() を参照すること // Gtk::Menu* ArticleViewBase::get_popupmenu( const std::string& url ) { // 表示 Gtk::Menu* popupmenu = nullptr; // 削除サブメニュー if( url == "popup_menu_delete" ){ popupmenu = dynamic_cast< Gtk::Menu* >( ui_manager()->get_widget( "/popup_menu_delete" ) ); } // レス番号ポップアップメニュー else if( url.rfind( PROTO_RES, 0 ) == 0 ){ popupmenu = dynamic_cast< Gtk::Menu* >( ui_manager()->get_widget( "/popup_menu_res" ) ); } // アンカーポップアップメニュー else if( url.rfind( PROTO_ANCHORE, 0 ) == 0 ){ popupmenu = dynamic_cast< Gtk::Menu* >( ui_manager()->get_widget( "/popup_menu_anc" ) ); } // IDポップアップメニュー else if( url.rfind( PROTO_ID, 0 ) == 0 ){ popupmenu = dynamic_cast< Gtk::Menu* >( ui_manager()->get_widget( "/popup_menu_id" ) ); } // 名前ポップアップメニュー else if( url.rfind( PROTO_NAME, 0 ) == 0 ){ popupmenu = dynamic_cast< Gtk::Menu* >( ui_manager()->get_widget( "/popup_menu_name" ) ); } // あぼーんポップアップメニュー else if( url.rfind( PROTO_ABONE, 0 ) == 0 ){ popupmenu = dynamic_cast< Gtk::Menu* >( ui_manager()->get_widget( "/popup_menu_abone" ) ); } // 壊れていますポップアップメニュー else if( url.rfind( PROTO_BROKEN, 0 ) == 0 ){ popupmenu = dynamic_cast< Gtk::Menu* >( ui_manager()->get_widget( "/popup_menu_broken" ) ); } // 画像ポップアップメニュー else if( DBIMG::get_type_ext( url ) != DBIMG::T_UNKNOWN ){ popupmenu = dynamic_cast< Gtk::Menu* >( ui_manager()->get_widget( "/popup_menu_img" ) ); } // 通常メニュー else popupmenu = dynamic_cast< Gtk::Menu* >( ui_manager()->get_widget( "/popup_menu" ) ); return popupmenu; } // // クリップボードに選択文字コピーのメニュー // void ArticleViewBase::slot_copy_selection_str() { #ifdef _DEBUG std::cout << "ArticleViewBase::slot_copy_selection_str " << get_url() << std::endl; #endif if( m_drawarea->str_selection().empty() ) return; MISC::CopyClipboard( m_drawarea->str_selection() ); } // // 全選択 // void ArticleViewBase::slot_select_all() { if( m_drawarea ) m_drawarea->select_all(); } // // 選択して抽出 // void ArticleViewBase::slot_drawout_selection_str() { std::string query = m_drawarea->str_selection(); query = MISC::remove_spaces( query ); query = MISC::replace_str( query, "\n", "" ); if( query.find( ' ' ) != std::string::npos ) query = "\"" + query + "\""; if( query.empty() ) return; const bool escape = false; // \ を エスケープ文字として考慮しない CORE::core_set_command( "open_article_keyword" ,m_url_article, MISC::regex_escape( query, escape ), "false" ); } // // 全ログ検索実行 // void ArticleViewBase::slot_search_cacheall() { std::string query = m_drawarea->str_selection(); query = MISC::replace_str( query, "\n", "" ); if( query.empty() ) return; #ifdef _DEBUG std::cout << "ArticleViewBase::slot_search_cacheall "<< query << std::endl; #endif CORE::core_set_command( "open_article_searchlog", URL_SEARCH_ALLBOARD , query, "exec" ); } // // ログ検索実行 // void ArticleViewBase::slot_search_cachelocal() { std::string query = m_drawarea->str_selection(); query = MISC::replace_str( query, "\n", "" ); if( query.empty() ) return; const std::string url = DBTREE::url_boardbase( m_url_article ); #ifdef _DEBUG std::cout << "ArticleViewBase::slot_search_cachelocal " << url << std::endl << query << std::endl; #endif CORE::core_set_command( "open_article_searchlog", url , query, "exec" ); } // // 次スレ検索 // void ArticleViewBase::slot_search_next() { if( m_article->empty() ) return; CORE::core_set_command( "open_board_next", DBTREE::url_boardbase( m_url_article ), m_url_article ); } // // web検索実行 // void ArticleViewBase::slot_search_web() { std::string query = m_drawarea->str_selection(); query = MISC::replace_str( query, "\n", "" ); if( query.empty() ) return; std::string url = CORE::get_usrcmd_manager()->replace_cmd( CONFIG::get_url_search_web(), "", "", query, 0 ); #ifdef _DEBUG std::cout << "ArticleViewBase::slot_search_web query = " << query << std::endl; std::cout << "url = " << url << std::endl; #endif CORE::core_set_command( "open_url_browser", url ); } // // スレタイ検索実行 // void ArticleViewBase::slot_search_title() { std::string query = m_drawarea->str_selection(); query = MISC::replace_str( query, "\n", "" ); #ifdef _DEBUG std::cout << "ArticleViewBase::slot_search_title query = " << query << std::endl; #endif if( query.empty() ) CORE::core_set_command( "open_article_searchtitle", "", "", "noexec" ); else CORE::core_set_command( "open_article_searchtitle", "" , query, "exec" ); } // // ユーザコマンド実行 // void ArticleViewBase::slot_usrcmd( int num ) { std::string current = "0"; std::string selection; if( m_drawarea ) { current = std::to_string( m_drawarea->get_current_res_num() ); selection = m_drawarea->str_selection(); } CORE::core_set_command( "exec_usr_cmd", m_url_article, std::to_string( num ), m_url_tmp, selection, current ); } // // ブックマーク設定、解除 // // 呼び出す前に m_str_num に対象のレス番号を入れておくこと // void ArticleViewBase::slot_bookmark() { if( m_str_num.empty() ) return; int number = atoi( m_str_num.c_str() ); bool bookmark = ! DBTREE::is_bookmarked( m_url_article, number ); DBTREE::set_bookmark( m_url_article, number, bookmark ); if( bookmark ) m_current_bm = number; redraw_view(); ARTICLE::get_admin()->set_command( "redraw_views", m_url_article ); } // // 書き込みマーク設定、解除 // // 呼び出す前に m_str_num に対象のレス番号を入れておくこと // void ArticleViewBase::slot_postedmark() { if( m_str_num.empty() ) return; int number = atoi( m_str_num.c_str() ); bool postedmark = ! m_article->is_posted( number ); m_article->set_posted( number, postedmark ); redraw_view(); ARTICLE::get_admin()->set_command( "redraw_views", m_url_article ); } // // ポップアップメニューでブラウザで開くを選択 // void ArticleViewBase::slot_open_browser() { if( m_url_tmp.empty() ) return; CORE::core_set_command( "open_url_browser", m_url_tmp ); } // // ポップアップメニューで画像のキャッシュをブラウザで開くを選択 // void ArticleViewBase::slot_open_cache_browser() { if( m_url_tmp.empty() ) return; if( ! DBIMG::is_cached( m_url_tmp ) ) return; const std::string url = "file://" + DBIMG::get_cache_path( m_url_tmp ); CORE::core_set_command( "open_url_browser", url ); } // // レスをする // // 呼び出す前に m_str_num に対象のレス番号を入れておくこと // void ArticleViewBase::slot_write_res() { if( m_article->empty() ) return; if( m_str_num.empty() ) return; #ifdef _DEBUG std::cout << "ArticleViewBase::slot_write_res number = " << m_str_num << std::endl; #endif CORE::core_set_command( "open_message" ,m_url_article, ">>" + m_str_num + "\n" ); } // // 参照レスをする // // 呼び出す前に m_str_num に対象のレス番号を入れておくこと // void ArticleViewBase::slot_quote_res() { assert( m_article ); if( m_str_num.empty() ) return; #ifdef _DEBUG std::cout << "ArticleViewBase::slot_quote_res number = " << m_str_num << std::endl; #endif CORE::core_set_command( "open_message" ,m_url_article, ">>" + m_str_num + "\n" + m_article->get_res_str( atoi( m_str_num.c_str() ), true ) ); } // // 選択部分を引用してレスする // void ArticleViewBase::slot_quote_selection_res() { assert( m_article ); const int num_from = m_drawarea->get_selection_resnum_from(); if( ! num_from ) return; const int num_to = m_drawarea->get_selection_resnum_to(); std::string str_num = std::to_string( num_from ); if( num_from < num_to ) str_num += "-" + std::to_string( num_to ); std::string str_res; str_res = CONFIG::get_ref_prefix(); std::string query = m_drawarea->str_selection(); if( query.empty() ) return; query = MISC::replace_str( query, "\n", "\n" + str_res ); #ifdef _DEBUG std::cout << "ArticleViewBase::slot_quote_selection_res number = " << str_num << std::endl; #endif CORE::core_set_command( "open_message", m_url_article, ">>" + str_num + "\n" + str_res + query + "\n" ); } // // リンクのURLをコピーのメニュー // void ArticleViewBase::slot_copy_current_url() { if( m_url_tmp.empty() ) return; #ifdef _DEBUG std::cout << "ArticleViewBase::slot_copy_current_url url = " << m_url_tmp << std::endl; #endif MISC::CopyClipboard( m_url_tmp ); } // // 名前をコピー // // 呼び出す前に m_name に名前をセットしておくこと // void ArticleViewBase::slot_copy_name() { std::string name = m_name; MISC::CopyClipboard( name ); } // // IDをコピー // // 呼び出す前に m_id_name にIDをセットしておくこと // void ArticleViewBase::slot_copy_id() { std::string id = m_id_name.substr( strlen( PROTO_ID ) ); MISC::CopyClipboard( id ); } // // レス番号クリック時のレスのコピーのメニュー // // 呼び出す前に m_str_num に対象のレス番号を入れておくこと // void ArticleViewBase::slot_copy_res( bool ref ) { assert( m_article ); if( m_str_num.empty() ) return; #ifdef _DEBUG std::cout << "ArticleViewBase::copy_res number = " << m_str_num << std::endl; #endif std::string tmpstr = m_url_tmp + "\n"; if( ref ) tmpstr += CONFIG::get_ref_prefix(); std::string board_name = DBTREE::board_name( m_url_article ); if( ! board_name.empty() ) tmpstr += "[ " + board_name + " ] "; tmpstr += DBTREE::article_subject( m_url_article ) + "\n\n"; tmpstr += m_article->get_res_str( atoi( m_str_num.c_str() ), ref ); MISC::CopyClipboard( tmpstr ); } // // スレのタイトルとURLをコピー // void ArticleViewBase::slot_copy_title_url() { MISC::CopyClipboard( DBTREE::article_subject( m_url_article ) + '\n' + url_for_copy() ); } // // お気に入り登録 // void ArticleViewBase::set_favorite() { if( m_article->empty() ) return; CORE::DATA_INFO info; info.type = TYPE_THREAD; info.parent = ARTICLE::get_admin()->get_win(); info.url = m_url_article;; info.name = DBTREE::article_subject( m_url_article ); info.path = Gtk::TreePath( "0" ).to_string(); CORE::DATA_INFO_LIST list_info; list_info.push_back( info ); CORE::SBUF_set_list( list_info ); CORE::core_set_command( "append_favorite", URL_FAVORITEVIEW ); } // // 別のタブを開いてレス抽出 // // 呼び出す前に m_str_num に抽出するレスをセットしておくこと // void ArticleViewBase::slot_drawout_res() { CORE::core_set_command( "open_article_res" ,m_url_article, m_str_num ); } // // 別のタブを開いて周辺のレスを抽出 // // 呼び出す前に m_str_num に対象のレス番号を入れておくこと // void ArticleViewBase::slot_drawout_around() { const int range = 10; int center = atoi( m_str_num.c_str() ); int from = MAX( 0, center - range ); int to = center + range; std::stringstream ss; ss << from << "-" << to; CORE::core_set_command( "open_article_res" ,m_url_article, ss.str(), m_str_num ); } // // 別のタブを開いてテンプレート表示 // void ArticleViewBase::slot_drawout_tmp() { const int to = 20; std::stringstream ss; ss << "1-" << to; CORE::core_set_command( "open_article_res" ,m_url_article, ss.str() ); } // // 別のタブを開いて名前抽出 // // 呼び出す前に m_name にIDをセットしておくこと // void ArticleViewBase::slot_drawout_name() { CORE::core_set_command( "open_article_name" ,m_url_article, m_name ); } // // 別のタブを開いてID抽出 // // 呼び出す前に m_id_name にIDをセットしておくこと // void ArticleViewBase::slot_drawout_id() { CORE::core_set_command( "open_article_id" ,m_url_article, m_id_name ); } // // 別のタブを開いてブックマーク抽出 // void ArticleViewBase::slot_drawout_bm() { CORE::core_set_command( "open_article_bm" ,m_url_article ); } // // 別のタブを開いて自分の書き込みを抽出 // void ArticleViewBase::slot_drawout_post() { CORE::core_set_command( "open_article_post" ,m_url_article ); } // // 別のタブを開いて高参照レスを抽出 // void ArticleViewBase::slot_drawout_highly_referenced_res() { CORE::core_set_command( "open_article_highly_referened_res", m_url_article ); } // // 別のタブを開いて参照抽出 // // 呼び出す前に m_str_num に対象のレス番号を入れておくこと // void ArticleViewBase::slot_drawout_refer() { CORE::core_set_command( "open_article_refer" ,m_url_article, m_str_num ); } // // 別のタブを開いてURL抽出 // void ArticleViewBase::slot_drawout_url() { CORE::core_set_command( "open_article_url" ,m_url_article ); } // // レス番号であぼ〜ん // // 呼び出す前に m_str_num に対象のレス番号を入れておくこと // void ArticleViewBase::slot_abone_res() { const int number = atoi( m_str_num.c_str() ); DBTREE::set_abone_res( m_url_article, number, number, true ); // 再レイアウト ARTICLE::get_admin()->set_command( "relayout_views", m_url_article ); } // // 選択範囲内のレス番号であぼ〜ん // void ArticleViewBase::slot_abone_selection_res() { const int num_from = m_drawarea->get_selection_resnum_from(); if( ! num_from ) return; const int num_to = m_drawarea->get_selection_resnum_to(); DBTREE::set_abone_res( m_url_article, num_from, num_to, true ); // 再レイアウト ARTICLE::get_admin()->set_command( "relayout_views", m_url_article ); } // // IDであぼ〜ん(ローカル、板(一時的) ) // // 呼び出す前に m_id_name にIDをセットしておくこと // void ArticleViewBase::slot_abone_id() { DBTREE::add_abone_id( m_url_article, m_id_name ); DBTREE::add_abone_id_board( m_url_article, m_id_name ); } // // 名前であぼ〜ん // // 呼び出す前に m_name に名前をセットしておくこと // void ArticleViewBase::slot_abone_name() { DBTREE::add_abone_name( m_url_article, m_name ); // 再レイアウト ARTICLE::get_admin()->set_command( "relayout_views", m_url_article ); } // // 範囲選択した文字列であぼ〜ん // void ArticleViewBase::slot_abone_word() { DBTREE::add_abone_word( m_url_article, m_drawarea->str_selection() ); // 再レイアウト ARTICLE::get_admin()->set_command( "relayout_views", m_url_article ); } // // 名前であぼ〜ん(板レベル) // // 呼び出す前に m_name に名前をセットしておくこと // void ArticleViewBase::slot_abone_name_board() { DBTREE::add_abone_name_board( m_url_article, m_name ); DBTREE::board_save_info( m_url_article ); } // // 範囲選択した文字列であぼ〜ん(板レベル) // void ArticleViewBase::slot_abone_word_board() { DBTREE::add_abone_word_board( m_url_article, m_drawarea->str_selection() ); DBTREE::board_save_info( m_url_article ); } // // 名前であぼ〜ん(全体) // // 呼び出す前に m_name に名前をセットしておくこと // void ArticleViewBase::slot_global_abone_name() { CORE::core_set_command( "set_global_abone_name", "", m_name ); } // // 範囲選択した文字列であぼ〜ん(全体) // void ArticleViewBase::slot_global_abone_word() { CORE::core_set_command( "set_global_abone_word", "", m_drawarea->str_selection() ); } // // 透明あぼーん // void ArticleViewBase::slot_toggle_abone_transp() { if( ! m_enable_menuslot ) return; assert( m_article ); bool setval = true; if( m_article->get_abone_transparent() ) setval = false; m_article->set_abone_transparent( setval ); // 再レイアウト ARTICLE::get_admin()->set_command( "relayout_views", m_url_article ); } // // 透明/連鎖あぼーん // void ArticleViewBase::slot_toggle_abone_transp_chain() { if( ! m_enable_menuslot ) return; assert( m_article ); bool setval = true; if( m_article->get_abone_transparent() && m_article->get_abone_chain() ) setval = false; m_article->set_abone_transparent( setval ); m_article->set_abone_chain( setval ); // 再レイアウト ARTICLE::get_admin()->set_command( "relayout_views", m_url_article ); } // // 画像のモザイク解除 // void ArticleViewBase::slot_cancel_mosaic() { if( ! DBIMG::is_cached( m_url_tmp ) ) return; if( DBIMG::is_fake( m_url_tmp ) ){ SKELETON::MsgDiag mdiag( get_parent_win(), "拡張子が偽装されています。モザイクを解除しますか?", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO ); mdiag.set_default_response( Gtk::RESPONSE_NO ); if( mdiag.run() != Gtk::RESPONSE_YES ) return; } DBIMG::set_mosaic( m_url_tmp, false ); CORE::core_set_command( "redraw", m_url_tmp ); } // // 画像をモザイク付きでダウンロード及び表示 // void ArticleViewBase::slot_show_image_with_mosaic() { #ifdef _DEBUG std::cout << "ArticleViewBase::slot_show_image_with_mosaic url = " << m_url_tmp << " num = " << m_str_num << std::endl; #endif if( DBIMG::is_cached( m_url_tmp ) ) return; const int res_number = atoi( m_str_num.c_str() ); if( ! res_number ) return; const bool open_imageview = CONFIG::get_use_image_view(); const bool open_browser = ! open_imageview; const bool mosaic = true; const bool switch_image = open_imageview; open_image( m_url_tmp, res_number, open_imageview, open_browser, mosaic, switch_image ); } // // 選択範囲の画像を開く // void ArticleViewBase::slot_show_selection_images() { #ifdef _DEBUG std::cout << "ArticleViewBase::slot_show_selection_images\n"; #endif if( m_drawarea->get_selection_imgurls().size() ){ bool first = true; for( const URLINFO& info : m_drawarea->get_selection_imgurls() ) { const std::string& url = info.url; const int res_number = info.res_number; if( DBIMG::get_abone( url ) ) continue; // オフラインでキャッシュが無い場合はスキップ if( ! SESSION::is_online() && ! DBIMG::is_cached( url ) ) continue; const std::string refurl = DBTREE::url_readcgi( m_url_article, res_number, 0 ); const bool open_imageview = CONFIG::get_use_image_view(); const bool mosaic = CONFIG::get_use_mosaic(); #ifdef _DEBUG std::cout << url << " - " << res_number << " : " << refurl << std::endl; #endif if( ! DBIMG::is_cached( url ) ){ DBIMG::download_img_wait( url, refurl, mosaic, first ); first = false; } if( open_imageview ) CORE::core_set_command( "open_image", url ); } redraw_view(); } } // // 選択範囲の画像を削除 // void ArticleViewBase::slot_delete_selection_images() { #ifdef _DEBUG std::cout << "ArticleViewBase::slot_delete_selection_images\n"; #endif if( m_drawarea->get_selection_imgurls().size() ){ for( const URLINFO& info : m_drawarea->get_selection_imgurls() ) { const std::string& url = info.url; if( ! DBIMG::is_protected( url ) ){ CORE::core_set_command( "delete_image", url ); } } redraw_view(); } } // // 選択範囲の画像をあぼーん // void ArticleViewBase::slot_abone_selection_images() { #ifdef _DEBUG std::cout << "ArticleViewBase::slot_abone_selection_images\n"; #endif if( m_drawarea->get_selection_imgurls().size() ){ for( const URLINFO& info : m_drawarea->get_selection_imgurls() ) { const std::string& url = info.url; if( ! DBIMG::is_protected( url ) ){ DBIMG::set_abone( url, true ); CORE::core_set_command( "delete_image", url ); } } redraw_view(); } } // // 大きい画像を表示 // void ArticleViewBase::slot_show_large_img() { DBIMG::show_large_img( m_url_tmp ); } // // 画像削除 // void ArticleViewBase::slot_deleteimage() { if( ! m_url_tmp.empty() ) CORE::core_set_command( "delete_image", m_url_tmp ); } // // 画像保存 // void ArticleViewBase::slot_saveimage() { DBIMG::save( m_url_tmp, nullptr, std::string() ); } // // 画像保護 // void ArticleViewBase::slot_toggle_protectimage() { if( ! m_enable_menuslot ) return; DBIMG::set_protect( m_url_tmp , ! DBIMG::is_protected( m_url_tmp ) ); } // // 画像あぼ〜ん // void ArticleViewBase::slot_abone_img() { if( ! m_enable_menuslot ) return; if( m_url_tmp.empty() ) return; DBIMG::set_abone( m_url_tmp , ! DBIMG::get_abone( m_url_tmp ) ); if( DBIMG::get_abone( m_url_tmp ) ) CORE::core_set_command( "delete_image", m_url_tmp ); redraw_view(); } // // 検索entryでenterを押した // void ArticleViewBase::exec_search() { std::string query = get_search_query(); if( query.empty() ){ clear_highlight(); focus_view(); CORE::core_set_command( "set_info", "", "" ); return; } std::list< std::string > list_query; list_query = MISC::split_line( query ); if( get_pre_query() != query ){ set_pre_query( query ); CORE::get_completion_manager()->set_query( CORE::COMP_SEARCH_ARTICLE, query ); m_drawarea->set_jump_history( m_drawarea->get_seen_current() ); m_drawarea->search( list_query, m_search_invert ); } int hit = m_drawarea->search_move( m_search_invert ); if( ! hit ){ clear_highlight(); CORE::core_set_command( "set_info", "", "検索結果: ヒット無し" ); } else{ focus_view(); CORE::core_set_command( "set_info", "", "検索結果: " + std::to_string( hit ) + "件" ); } } // // 検索entryの操作 // void ArticleViewBase::operate_search( const std::string& controlid ) { const int id = atoi( controlid.c_str() ); if( id == CONTROL::Cancel ){ focus_view(); ARTICLE::get_admin()->set_command( "close_searchbar" ); } // AND 抽出 else if( id == CONTROL::DrawOutAnd ){ std::string query = get_search_query(); if( query.empty() ) return; CORE::core_set_command( "open_article_keyword" ,m_url_article, query, "false" ); } } // // 実況モードのセット // void ArticleViewBase::set_live( const bool live ) { if( ! m_enable_live ) return; m_live = live; if( m_live ) SESSION::append_live( m_url_article ); else SESSION::remove_live( m_url_article ); } jdim-0.7.0/src/article/articleviewbase.h000066400000000000000000000304061417047150700202060ustar00rootroot00000000000000// ライセンス: GPL2 // スレビュークラスの基本クラス #ifndef _ARTICLEVIEWBASE_H #define _ARTICLEVIEWBASE_H #include "skeleton/view.h" #include "jdlib/refptr_lock.h" #include #include #include namespace SKELETON { class Admin; class PopupWin; } namespace DBTREE { class ArticleBase; } namespace XML { class Dom; } namespace ARTICLE { class DrawAreaBase; class ArticleViewBase : public SKELETON::View { // viewに表示するdatファイルのURL ( SKELETON::View::m_url はview自身のURLなのに注意すること ) std::string m_url_article; // ツールバーの板ボタンに表示する文字列 std::string m_label_board; // 高速化のため直接アクセス JDLIB::RefPtr_Lock< DBTREE::ArticleBase > m_article; // widget DrawAreaBase* m_drawarea{}; // slot呼び出し時にURLのやりとりに使う一時変数 std::string m_url_tmp; // url std::string m_str_num; // レス番号 std::string m_jump_to; // ジャンプ先番号 std::string m_jump_from; // ジャンプ元番号 std::string m_id_name; // ID std::string m_name; // 名前 // ポップアップ std::unique_ptr m_popup_win; bool m_popup_shown{}; // 表示されているならtrue, falseでもdeleteしない限りは m_popup_win != nullptrに注意 int m_hidepopup_counter{}; // ポップアップを消すまでのカウンタ std::string m_popup_url; // 表示中のポップアップのアドレス // 検索用 bool m_search_invert{}; // 逆方向検索モード std::string m_pre_query; // 前回の検索で使ったクエリー // ポップアップメニュー表示のときにactivate_act_before_popupmenu()で使う変数 bool m_enable_menuslot; // ブックマーク移動時の現在の位置(レス番号) int m_current_bm{}; // 書き込みマーク移動時の現在の位置(レス番号) int m_current_post{}; // 抽出ビューで荒らし報告用のURLを表示する bool m_show_url4report{}; // URLをステータスバーに表示しているかどうか bool m_url_show_status{}; // 実況モードが可能か bool m_enable_live{}; // ライブモードか bool m_live{}; // メニューのユーザコマンド、 create_usrcmd_menu() で作成 std::string m_usrcmd; public: ArticleViewBase( const std::string& url, const std::string& url_article ); ~ArticleViewBase(); const std::string& url_article() const { return m_url_article; } const std::string& get_label_board() const { return m_label_board; } // SKELETON::View の関数のオーバロード void save_session() override {} std::string url_for_copy() const override; // コピーやURLバー表示用のURL int width_client() const override; int height_client() const override; int get_icon( const std::string& iconname ) const override; bool set_command( const std::string& command, const std::string& arg1 = {}, const std::string& arg2 = {} ) override; void clock_in() override; void clock_in_smooth_scroll(); // キーを押した bool slot_key_press( GdkEventKey* event ) override; void write() override; void reload() override; void stop() override; void redraw_view() override; void focus_view() override; void focus_out() override; void close_view() override; void delete_view() override; void set_favorite() override; bool operate_view( const int control ) override; void goto_top() override; void goto_bottom() override; void goto_num( const int num_to, const int num_from ) override; void show_preference() override; // 進む、戻る void back_viewhistory( const int count ) override; void forward_viewhistory( const int count ) override; // 検索 void exec_search() override; void up_search() override; void down_search() override; void operate_search( const std::string& controlid ) override; void clear_highlight(); // ハイライト解除 // 記事削除 & 再オープン void delete_open_view(); // 実況モードが可能か bool get_enable_live() const; // 実況モードか bool get_live() const { return m_live; } DrawAreaBase* drawarea() const noexcept { return m_drawarea; } protected: // Viewが所属するAdminクラス SKELETON::Admin* get_admin() override; JDLIB::RefPtr_Lock< DBTREE::ArticleBase >& get_article() noexcept { return m_article; }; const JDLIB::RefPtr_Lock< DBTREE::ArticleBase >& get_article() const noexcept { return m_article; }; // ポップアップメニューを表示する前にメニューのアクティブ状態を切り替える void activate_act_before_popupmenu( const std::string& url ) override; // ポップアップメニュー取得 Gtk::Menu* get_popupmenu( const std::string& url ) override; // レスポップアップを隠す void hide_popup( const bool force = false ); // ポップアップが表示されているか bool is_popup_shown() const; // 初期設定 void setup_view(); // 新着に移動 void goto_new(); // レスを抽出して表示 // num は "from-to" の形式 (例) 3から10を抽出したいなら "3-10" // show_title == trueの時は 板名、スレ名を表示 void show_res( const std::string& num, const bool show_title ); // 名前 で抽出して表示 // show_option = true の時は URL 表示などのオプションが表示される void show_name( const std::string& name, bool show_option ); // ID で抽出して表示 // show_option = true の時は URL 表示などのオプションが表示される void show_id( const std::string& id_name, const bool show_option ); // ブックマークを抽出して表示 void show_bm(); // 書き込みを抽出して表示 void show_post(); // 書き込みログを表示 void show_postlog( const int num ); // 高参照レスを抽出して表示 void show_highly_referenced_res(); // URLを含むレスを抽出して表示 void show_res_with_url(); // num 番のレスを参照してるレスを抽出して表示 void show_refer( int num ); // 前回の検索で使ったクエリー void set_pre_query( const std::string& query ){ m_pre_query = query; } const std::string& get_pre_query() const{ return m_pre_query; } // キーワードで抽出して表示 // mode_or = true の時は or 検索 // show_option = true の時は URL 表示などのオプションが表示される void drawout_keywords( const std::string& query, bool mode_or, bool show_option ); // HTML追加 void append_html( const std::string& html ); // dat追加 virtual void append_dat( const std::string& dat, int num ); // リストで指定したレスを表示 void append_res( const std::list< int >& list_resnum ); // リストで指定したレスを表示(連結情報付き) void append_res( const std::list< int >& list_resnum, const std::list< bool >& list_joint ); // 画像プロパティ表示 void slot_preferences_image(); void slot_copy_selection_str(); void slot_select_all(); // 実況用 void set_enable_live( const bool enable ){ m_enable_live = enable; } void set_live( const bool live ); virtual void live_start() {} virtual void live_stop() {} private: virtual DrawAreaBase* create_drawarea(); void setup_action(); // 通常の右クリックメニューの作成 std::string create_context_menu() const; const char* get_menu_item( const int item ) const; virtual void exec_reload(); void reload_article(); void exec_delete(); // 荒らし報告用のURLリストをHTML形式で取得 std::string get_html_url4report( const std::list< int >& list_resnum ); // drawarea の signal を受け取る slots virtual bool slot_button_press( const std::string& url, int res_number, GdkEventButton* event ); bool slot_button_release( std::string url, int res_number, GdkEventButton* event ); bool slot_motion_notify( GdkEventMotion* event ); bool slot_key_release( GdkEventKey* event ); bool slot_scroll_event( GdkEventScroll* event ); bool slot_leave_notify( GdkEventCrossing* ev ); // レスポップアップ関係 // ポップアップが表示されていてかつマウスがその上にあるか bool is_mouse_on_popup(); void show_popup( SKELETON::View* view, const int mrg_x, const int mrg_y ); bool slot_popup_leave_notify_event( GdkEventCrossing* event ); void slot_hide_popup(); void delete_popup(); // ポップアップ強制削除 void warp_pointer_to_popup(); // マウスポインタをポップアップの上に移動する void slot_bookmark(); void slot_postedmark(); void slot_open_browser(); void slot_open_cache_browser(); void slot_write_res(); void slot_quote_res(); void slot_quote_selection_res(); void slot_copy_current_url(); void slot_copy_name(); void slot_copy_id(); void slot_drawout_selection_str(); void slot_search_cacheall(); void slot_search_cachelocal(); void slot_search_next(); void slot_search_web(); void slot_search_title(); void slot_usrcmd( int num ); void slot_copy_res( bool ref ); void slot_copy_title_url(); void slot_drawout_res(); void slot_drawout_around(); void slot_drawout_tmp(); void slot_drawout_name(); void slot_drawout_id(); void slot_drawout_bm(); void slot_drawout_post(); void slot_drawout_highly_referenced_res(); void slot_drawout_refer(); void slot_drawout_url(); void slot_abone_res(); void slot_abone_selection_res(); void slot_abone_id(); void slot_abone_name(); void slot_abone_word(); void slot_abone_name_board(); void slot_abone_word_board(); void slot_global_abone_name(); void slot_global_abone_word(); void slot_toggle_abone_transp(); void slot_toggle_abone_transp_chain(); void slot_pre_bm(); void slot_next_bm(); void slot_pre_post(); void slot_next_post(); void slot_jump(); void slot_save_dat(); void slot_copy_article_info(); // あぼーん設定 void slot_setup_abone(); void slot_setup_abone_board(); void slot_setup_abone_all(); // リンクの処理 virtual void slot_on_url( const std::string& url, const std::string& imgurl, int res_number ); void slot_leave_url(); bool click_url( std::string url, int res_number, GdkEventButton* event ); void open_image( const std::string& url, const int res_number, const bool open_imageview, const bool open_browser, const bool mosaic, const bool switch_image ); // 画像ポップアップメニュー用 void slot_cancel_mosaic(); void slot_show_image_with_mosaic(); void slot_show_selection_images(); void slot_delete_selection_images(); void slot_abone_selection_images(); void slot_show_large_img(); void slot_deleteimage(); void slot_toggle_protectimage(); void slot_saveimage(); void slot_abone_img(); // 検索バーを開く void open_searchbar( bool invert ); }; } #endif jdim-0.7.0/src/article/articleviewetc.cpp000066400000000000000000000373651417047150700204150ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "articleadmin.h" #include "articleviewetc.h" #include "drawareamain.h" #include "dbtree/interface.h" #include "dbtree/articlebase.h" #include "control/controlid.h" #include "global.h" #include using namespace ARTICLE; // レス抽出ビュー ArticleViewRes::ArticleViewRes( const std::string& url ) : ArticleViewBase( url, url.substr( 0, url.find( ARTICLE_SIGN ) ) ) { const int pos0 = url.find( RES_SIGN ) + strlen( RES_SIGN ); const int pos1 = url.find( CENTER_SIGN ); m_str_num = url.substr( pos0, pos1 - pos0 ); m_str_center = url.substr( pos1 + strlen( CENTER_SIGN ) ); #ifdef _DEBUG std::cout << "ArticleViewRes::ArticleViewRes " << get_url() << " num = " << m_str_num << " center = " << m_str_center << std::endl; #endif setup_view(); // ラベル更新 set_label( " [ RES:" + m_str_num + " ] - " + DBTREE::article_subject( url_article() ) ); // タブ更新 ARTICLE::get_admin()->set_command( "set_tablabel", get_url(), get_label() ); // タブのアイコン状態を更新 ARTICLE::get_admin()->set_command( "toggle_icon", get_url() ); } ArticleViewRes::~ArticleViewRes() { #ifdef _DEBUG std::cout << "ArticleViewRes::~ArticleViewRes : " << get_url() << std::endl; #endif } // // 画面を消してレイアウトやりなおし & 再描画 // void ArticleViewRes::relayout() { #ifdef _DEBUG std::cout << "ArticleViewRes::relayout\n"; #endif drawarea()->clear_screen(); show_res( m_str_num, false ); drawarea()->redraw_view(); ARTICLE::get_admin()->set_command( "goto_num", get_url(), m_str_center ); } // // 抽出表示 // void ArticleViewRes::show_view() { relayout(); } // // 再読み込みボタンを押した // void ArticleViewRes::reload() { exec_reload(); } // // 再読み込み実行 // // virtual void ArticleViewRes::exec_reload() { relayout(); } //////////////////////////////////////////////////////////////////////////////////////////////////// // 名前抽出ビュー ArticleViewName::ArticleViewName( const std::string& url ) : ArticleViewBase( url, url.substr( 0, url.find( ARTICLE_SIGN ) ) ) , m_str_name{ url.substr( url.find( NAME_SIGN ) + strlen( NAME_SIGN ) ) } { #ifdef _DEBUG std::cout << "ArticleViewName::ArticleViewName " << get_url() << " name = " << m_str_name << std::endl; #endif setup_view(); // ラベル更新 set_label( " [ 名前:" + m_str_name + " ] - " + DBTREE::article_subject( url_article() )); // タブ更新 ARTICLE::get_admin()->set_command( "set_tablabel", get_url(), get_label() ); // タブのアイコン状態を更新 ARTICLE::get_admin()->set_command( "toggle_icon", get_url() ); } ArticleViewName::~ArticleViewName() { #ifdef _DEBUG std::cout << "ArticleViewName::~ArticleViewName : " << get_url() << std::endl; #endif } // // 画面を消してレイアウトやりなおし & 再描画 // void ArticleViewName::relayout() { #ifdef _DEBUG std::cout << "ArticleViewName::relayout\n"; #endif drawarea()->clear_screen(); show_name( m_str_name, true ); drawarea()->redraw_view(); } // // 抽出表示 // void ArticleViewName::show_view() { relayout(); } // // 再読み込みボタンを押した // void ArticleViewName::reload() { exec_reload(); } // // 再読み込み実行 // // virtual void ArticleViewName::exec_reload() { relayout(); } //////////////////////////////////////////////////////////////////////////////////////////////////// // ID抽出ビュー ArticleViewID::ArticleViewID( const std::string& url ) : ArticleViewBase( url, url.substr( 0, url.find( ARTICLE_SIGN ) ) ) , m_str_id{ url.substr( url.find( ID_SIGN ) + strlen( ID_SIGN ) ) } { #ifdef _DEBUG std::cout << "ArticleViewID::ArticleViewID " << get_url() << " ID = " << m_str_id << std::endl; #endif setup_view(); // ラベル更新 set_label( " [ " + m_str_id.substr( strlen( PROTO_ID ) ) + " ] - " + DBTREE::article_subject( url_article() )); // タブ更新 ARTICLE::get_admin()->set_command( "set_tablabel", get_url(), get_label() ); // タブのアイコン状態を更新 ARTICLE::get_admin()->set_command( "toggle_icon", get_url() ); } ArticleViewID::~ArticleViewID() { #ifdef _DEBUG std::cout << "ArticleViewID::~ArticleViewID : " << get_url() << std::endl; #endif } // // 画面を消してレイアウトやりなおし & 再描画 // void ArticleViewID::relayout() { #ifdef _DEBUG std::cout << "ArticleViewID::relayout\n"; #endif drawarea()->clear_screen(); show_id( m_str_id, true ); drawarea()->redraw_view(); } // // 抽出表示 // void ArticleViewID::show_view() { relayout(); } // // 再読み込みボタンを押した // void ArticleViewID::reload() { exec_reload(); } // // 再読み込み実行 // // virtual void ArticleViewID::exec_reload() { relayout(); } //////////////////////////////////////////////////////////////////////////////////////////////////// // ブックマーク抽出ビュー ArticleViewBM::ArticleViewBM( const std::string& url ) : ArticleViewBase( url, url.substr( 0, url.find( ARTICLE_SIGN ) ) ) { #ifdef _DEBUG std::cout << "ArticleViewBM::ArticleViewBM " << get_url() << std::endl; #endif setup_view(); // ラベル更新 set_label( " [ しおり ] - " + DBTREE::article_subject( url_article() )); // タブ更新 ARTICLE::get_admin()->set_command( "set_tablabel", get_url(), get_label() ); // タブのアイコン状態を更新 ARTICLE::get_admin()->set_command( "toggle_icon", get_url() ); } ArticleViewBM::~ArticleViewBM() { #ifdef _DEBUG std::cout << "ArticleViewBM::~ArticleViewBM : " << get_url() << std::endl; #endif } // // 画面を消してレイアウトやりなおし & 再描画 // void ArticleViewBM::relayout() { #ifdef _DEBUG std::cout << "ArticleViewBM::relayout\n"; #endif drawarea()->clear_screen(); show_bm(); drawarea()->redraw_view(); } // // 抽出表示 // void ArticleViewBM::show_view() { relayout(); } // // 再読み込みボタンを押した // void ArticleViewBM::reload() { exec_reload(); } // // 再読み込み実行 // // virtual void ArticleViewBM::exec_reload() { relayout(); } //////////////////////////////////////////////////////////////////////////////////////////////////// // 書き込み抽出ビュー ArticleViewPost::ArticleViewPost( const std::string& url ) : ArticleViewBase( url, url.substr( 0, url.find( ARTICLE_SIGN ) ) ) { #ifdef _DEBUG std::cout << "ArticleViewPost::ArticleViewPost " << get_url() << std::endl; #endif setup_view(); // ラベル更新 set_label( " [ 書き込み ] - " + DBTREE::article_subject( url_article() )); // タブ更新 ARTICLE::get_admin()->set_command( "set_tablabel", get_url(), get_label() ); // タブのアイコン状態を更新 ARTICLE::get_admin()->set_command( "toggle_icon", get_url() ); } ArticleViewPost::~ArticleViewPost() { #ifdef _DEBUG std::cout << "ArticleViewPost::~ArticleViewPost : " << get_url() << std::endl; #endif } // // 画面を消してレイアウトやりなおし & 再描画 // void ArticleViewPost::relayout() { #ifdef _DEBUG std::cout << "ArticleViewPost::relayout\n"; #endif drawarea()->clear_screen(); show_post(); drawarea()->redraw_view(); } // // 抽出表示 // void ArticleViewPost::show_view() { relayout(); } // // 再読み込みボタンを押した // void ArticleViewPost::reload() { exec_reload(); } // // 再読み込み実行 // // virtual void ArticleViewPost::exec_reload() { relayout(); } //////////////////////////////////////////////////////////////////////////////////////////////////// // 高参照レス抽出ビュー ArticleViewHighRefRes::ArticleViewHighRefRes( const std::string& url ) : ArticleViewBase( url, url.substr( 0, url.find( ARTICLE_SIGN ) ) ) { #ifdef _DEBUG std::cout << "ArticleViewHighRefRes::ArticleViewHighRefRes " << get_url() << std::endl; #endif setup_view(); // ラベル更新 set_label( " [ 高参照レス ] - " + DBTREE::article_subject( url_article() )); // タブ更新 ARTICLE::get_admin()->set_command( "set_tablabel", get_url(), get_label() ); // タブのアイコン状態を更新 ARTICLE::get_admin()->set_command( "toggle_icon", get_url() ); } ArticleViewHighRefRes::~ArticleViewHighRefRes() { #ifdef _DEBUG std::cout << "ArticleViewHighRefRes::~ArticleViewHighRefRes : " << get_url() << std::endl; #endif } // // 画面を消してレイアウトやりなおし & 再描画 // void ArticleViewHighRefRes::relayout() { #ifdef _DEBUG std::cout << "ArticleViewHighRefRes::relayout\n"; #endif drawarea()->clear_screen(); show_highly_referenced_res(); drawarea()->redraw_view(); } // // 抽出表示 // void ArticleViewHighRefRes::show_view() { relayout(); } // // 再読み込みボタンを押した // void ArticleViewHighRefRes::reload() { exec_reload(); } // // 再読み込み実行 // // virtual void ArticleViewHighRefRes::exec_reload() { relayout(); } //////////////////////////////////////////////////////////////////////////////////////////////////// // URL抽出ビュー ArticleViewURL::ArticleViewURL( const std::string& url ) : ArticleViewBase( url, url.substr( 0, url.find( ARTICLE_SIGN ) ) ) { #ifdef _DEBUG std::cout << "ArticleViewURL::ArticleViewURL " << get_url() << std::endl; #endif setup_view(); // ラベル更新 set_label( " [ URL ] - " + DBTREE::article_subject( url_article() )); // タブ更新 ARTICLE::get_admin()->set_command( "set_tablabel", get_url(), get_label() ); // タブのアイコン状態を更新 ARTICLE::get_admin()->set_command( "toggle_icon", get_url() ); } ArticleViewURL::~ArticleViewURL() { #ifdef _DEBUG std::cout << "ArticleViewURL::~ArticleViewURL : " << get_url() << std::endl; #endif } // // 画面を消してレイアウトやりなおし & 再描画 // void ArticleViewURL::relayout() { #ifdef _DEBUG std::cout << "ArticleViewURL::relayout\n"; #endif drawarea()->clear_screen(); show_res_with_url(); drawarea()->redraw_view(); } // // 抽出表示 // void ArticleViewURL::show_view() { relayout(); } // // 再読み込みボタンを押した // void ArticleViewURL::reload() { exec_reload(); } // // 再読み込み実行 // // virtual void ArticleViewURL::exec_reload() { relayout(); } //////////////////////////////////////////////////////////////////////////////////////////////////// // 参照抽出ビュー ArticleViewRefer::ArticleViewRefer( const std::string& url ) : ArticleViewBase( url, url.substr( 0, url.find( ARTICLE_SIGN ) ) ) , m_str_num{ url.substr( url.find( REFER_SIGN ) + strlen( REFER_SIGN ) ) } { #ifdef _DEBUG std::cout << "ArticleViewRefer::ArticleViewRefer " << get_url() << " num = " << m_str_num << std::endl; #endif setup_view(); // ラベル更新 set_label( " [ Re:" + m_str_num + " ] - " + DBTREE::article_subject( url_article() )); // タブ更新 ARTICLE::get_admin()->set_command( "set_tablabel", get_url(), get_label() ); // タブのアイコン状態を更新 ARTICLE::get_admin()->set_command( "toggle_icon", get_url() ); } ArticleViewRefer::~ArticleViewRefer() { #ifdef _DEBUG std::cout << "ArticleViewRefer::~ArticleViewRefer : " << get_url() << std::endl; #endif } // // 画面を消してレイアウトやりなおし & 再描画 // void ArticleViewRefer::relayout() { #ifdef _DEBUG std::cout << "ArticleViewRefer::relayout\n"; #endif drawarea()->clear_screen(); show_refer( atol( m_str_num.c_str() ) ); drawarea()->redraw_view(); } // // 抽出表示 // void ArticleViewRefer::show_view() { relayout(); } // // 再読み込みボタンを押した // void ArticleViewRefer::reload() { exec_reload(); } // // 再読み込み実行 // // virtual void ArticleViewRefer::exec_reload() { relayout(); } //////////////////////////////////////////////////////////////////////////////////////////////////// // キーワード抽出ビュー ArticleViewDrawout::ArticleViewDrawout( const std::string& url ) : ArticleViewBase( url, url.substr( 0, url.find( ARTICLE_SIGN ) ) ) { const int pos0 = url.find( KEYWORD_SIGN ) + strlen( KEYWORD_SIGN ); const int pos1 = url.find( ORMODE_SIGN ); m_query = url.substr( pos0, pos1 - pos0 ); m_mode_or = ( url.substr( pos1 + strlen( ORMODE_SIGN ) ) == "1" ); set_search_query( m_query ); set_pre_query( m_query ); #ifdef _DEBUG std::cout << "ArticleViewDrawout::ArticleViewDrawout " << get_url() << std::endl; #endif setup_view(); // ラベル更新 std::string str_label; if( m_mode_or ) str_label = "[ OR 抽出 ] - "; else str_label = "[ AND 抽出 ] - "; set_label( str_label + DBTREE::article_subject( url_article() ) ); // タブ更新 ARTICLE::get_admin()->set_command( "set_tablabel", get_url(), get_label() ); // タブのアイコン状態を更新 ARTICLE::get_admin()->set_command( "toggle_icon", get_url() ); } ArticleViewDrawout::~ArticleViewDrawout() { #ifdef _DEBUG std::cout << "ArticleViewDrawout::~ArticleViewDrawout : " << get_url() << std::endl; #endif } // // 画面を消してレイアウトやりなおし & 再描画 // void ArticleViewDrawout::relayout() { #ifdef _DEBUG std::cout << "ArticleViewDrawout::relayout\n"; #endif drawarea()->clear_screen(); drawarea()->clear_highlight(); drawout_keywords( m_query, m_mode_or, true ); drawarea()->redraw_view(); } // // 抽出表示 // void ArticleViewDrawout::show_view() { relayout(); } // // 再読み込みボタンを押した // void ArticleViewDrawout::reload() { exec_reload(); } // // 再読み込み実行 // // virtual void ArticleViewDrawout::exec_reload() { relayout(); } //////////////////////////////////////////////////////////////////////////////////////////////////// // 書き込みログ表示ビュー ArticleViewPostlog::ArticleViewPostlog( const std::string& url ) : ArticleViewBase( url, url.substr( 0, url.find( POSTLOG_SIGN ) ) ) { m_num = atoi( url.substr( url.find( POSTLOG_SIGN ) + strlen( POSTLOG_SIGN ) ).c_str() ); #ifdef _DEBUG std::cout << "ArticleViewPostlog::ArticleViewPostlog " << get_url() << " num = " << m_num << std::endl; #endif set_id_toolbar( TOOLBAR_SIMPLE ); set_writeable( false ); setup_view(); // ラベル更新 set_label( " [ 書き込みログ ] " ); // タブ更新 ARTICLE::get_admin()->set_command( "set_tablabel", get_url(), get_label() ); // タブのアイコン状態を更新 ARTICLE::get_admin()->set_command( "toggle_icon", get_url() ); } ArticleViewPostlog::~ArticleViewPostlog() { #ifdef _DEBUG std::cout << "ArticleViewPostlog::~ArticleViewPostlog : " << get_url() << std::endl; #endif } // // 検索entryの操作 // void ArticleViewPostlog::operate_search( const std::string& controlid ) { const int id = atoi( controlid.c_str() ); if( id == CONTROL::Cancel ){ focus_view(); ARTICLE::get_admin()->set_command( "close_searchbar" ); } } // // 画面を消してレイアウトやりなおし & 再描画 // void ArticleViewPostlog::relayout() { #ifdef _DEBUG std::cout << "ArticleViewPostlog::relayout\n"; #endif drawarea()->clear_screen(); show_postlog( m_num ); drawarea()->redraw_view(); goto_bottom(); } // // 抽出表示 // void ArticleViewPostlog::show_view() { relayout(); } // // 再読み込みボタンを押した // void ArticleViewPostlog::reload() { exec_reload(); } // // 再読み込み実行 // void ArticleViewPostlog::exec_reload() { relayout(); } jdim-0.7.0/src/article/articleviewetc.h000066400000000000000000000126441417047150700200530ustar00rootroot00000000000000// ライセンス: GPL2 // // その他のarticle系view // #ifndef _ARTICLEVIEWETC_H #define _ARTICLEVIEWETC_H #include "articleviewbase.h" namespace ARTICLE { // レス抽出ビュー class ArticleViewRes : public ArticleViewBase { std::string m_str_num; std::string m_str_center; public: explicit ArticleViewRes( const std::string& url ); ~ArticleViewRes(); // SKELETON::View の関数のオーバロード void relayout() override; void show_view() override; void reload() override; private: void exec_reload() override; }; ///////////////////////////////////////////////////////////////////////// // 名前抽出ビュー class ArticleViewName : public ArticleViewBase { std::string m_str_name; public: explicit ArticleViewName( const std::string& url ); ~ArticleViewName(); // SKELETON::View の関数のオーバロード void relayout() override; void show_view() override; void reload() override; private: void exec_reload() override; }; ///////////////////////////////////////////////////////////////////////// // ID 抽出ビュー class ArticleViewID : public ArticleViewBase { std::string m_str_id; public: explicit ArticleViewID( const std::string& url ); ~ArticleViewID(); // SKELETON::View の関数のオーバロード void relayout() override; void show_view() override; void reload() override; private: void exec_reload() override; }; ///////////////////////////////////////////////////////////////////////// // ブックマーク抽出ビュー class ArticleViewBM : public ArticleViewBase { std::string m_str_id; public: explicit ArticleViewBM( const std::string& url ); ~ArticleViewBM(); // SKELETON::View の関数のオーバロード void relayout() override; void show_view() override; void reload() override; private: void exec_reload() override; }; ///////////////////////////////////////////////////////////////////////// // 書き込み抽出ビュー class ArticleViewPost : public ArticleViewBase { std::string m_str_id; public: explicit ArticleViewPost( const std::string& url ); ~ArticleViewPost(); // SKELETON::View の関数のオーバロード void relayout() override; void show_view() override; void reload() override; private: void exec_reload() override; }; ///////////////////////////////////////////////////////////////////////// // 高参照レス抽出ビュー class ArticleViewHighRefRes : public ArticleViewBase { std::string m_str_id; public: explicit ArticleViewHighRefRes( const std::string& url ); ~ArticleViewHighRefRes(); // SKELETON::View の関数のオーバロード void relayout() override; void show_view() override; void reload() override; private: void exec_reload() override; }; ///////////////////////////////////////////////////////////////////////// // URL抽出ビュー class ArticleViewURL : public ArticleViewBase { public: explicit ArticleViewURL( const std::string& url ); ~ArticleViewURL(); // SKELETON::View の関数のオーバロード void relayout() override; void show_view() override; void reload() override; private: void exec_reload() override; }; ///////////////////////////////////////////////////////////////////////// // 参照抽出ビュー class ArticleViewRefer : public ArticleViewBase { std::string m_str_num; public: explicit ArticleViewRefer( const std::string& url ); ~ArticleViewRefer(); // SKELETON::View の関数のオーバロード void relayout() override; void show_view() override; void reload() override; private: void exec_reload() override; }; ///////////////////////////////////////////////////////////////////////// // キーワード抽出ビュー class ArticleViewDrawout : public ArticleViewBase { std::string m_query; bool m_mode_or; public: explicit ArticleViewDrawout( const std::string& url ); ~ArticleViewDrawout(); // SKELETON::View の関数のオーバロード void relayout() override; void show_view() override; void reload() override; private: void exec_reload() override; }; ///////////////////////////////////////////////////////////////////////// // 書き込みログ表示ビュー class ArticleViewPostlog : public ArticleViewBase { int m_num; public: explicit ArticleViewPostlog( const std::string& url ); ~ArticleViewPostlog(); // SKELETON::View の関数のオーバロード void relayout() override; void stop() override {} // キャンセル // 検索 void operate_search( const std::string& controlid ) override; void show_view() override; void reload() override; private: void exec_reload() override; }; } #endif jdim-0.7.0/src/article/articleviewinfo.cpp000066400000000000000000000012711417047150700205600ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "articleadmin.h" #include "articleviewinfo.h" #include "drawareainfo.h" using namespace ARTICLE; ArticleViewInfo::ArticleViewInfo( const std::string& url ) : ArticleViewBase( url, url ) { #ifdef _DEBUG std::cout << "ArticleViewInfo::ArticleViewInfo " << get_url() << std::endl; #endif set_writeable( false ); setup_view(); } ArticleViewInfo::~ArticleViewInfo() { #ifdef _DEBUG std::cout << "ArticleViewInfo::~ArticleViewInfo : " << get_url() << std::endl; #endif } DrawAreaBase* ArticleViewInfo::create_drawarea() { return Gtk::manage( new ARTICLE::DrawAreaInfo( url_article() ) ); } jdim-0.7.0/src/article/articleviewinfo.h000066400000000000000000000017541417047150700202330ustar00rootroot00000000000000// ライセンス: GPL2 // // 情報表示用 // #ifndef _ARTICLEVIEWINFO_H #define _ARTICLEVIEWINFO_H #include "articleviewbase.h" namespace ARTICLE { class ArticleViewInfo : public ArticleViewBase { public: explicit ArticleViewInfo( const std::string& url ); ~ArticleViewInfo(); // viewの操作をキャンセル bool operate_view( const int control ) override { return false; } protected: // ポップアップメニューは表示しない Gtk::Menu* get_popupmenu( const std::string& url ) override { return nullptr; } private: // ボタンプレスキャンセル bool slot_button_press( const std::string& url, int res_number, GdkEventButton* ) override { return true; } // ポップアップ表示キャンセル void slot_on_url( const std::string& url, const std::string& imgurl, int res_number ) override {} DrawAreaBase* create_drawarea() override; }; } #endif jdim-0.7.0/src/article/articleviewpopup.cpp000066400000000000000000000026231417047150700207720ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "articleviewpopup.h" #include "drawareapopup.h" #include "global.h" #include "config/globalconf.h" using namespace ARTICLE; // show_abone == true ならあぼーんされたスレも表示 ArticleViewPopup::ArticleViewPopup( const std::string& url, bool show_abone ) : ArticleViewBase( url, url ), m_show_abone( show_abone ) { #ifdef _DEBUG std::cout << "ArticleViewPopup::ArticleViewPupup " << get_url() << " show_abone " << m_show_abone << std::endl; #endif setup_view(); } ArticleViewPopup::~ArticleViewPopup() { #ifdef _DEBUG std::cout << "ArticleViewPopup::~ArticleViewPopup : " << get_url() << std::endl; #endif } // // 多重ポップアップのヒント表示 // void ArticleViewPopup::show_instruct_popup() { if( CONFIG::get_instruct_popup() ) append_html( "ヒント:マウスの右ボタンを押しながらポインタを移動すると多重ポップアップが可能
" "または設定メニューの「シングルクリックで多重ポップアップモードに移行する」
" "をチェックしてからアンカーをクリックする(X11環境のみ)" ); } // // drawareaの作成 // DrawAreaBase* ArticleViewPopup::create_drawarea() { return Gtk::manage( new ARTICLE::DrawAreaPopup( url_article(), m_show_abone ) ); } jdim-0.7.0/src/article/articleviewpopup.h000066400000000000000000000107101417047150700204330ustar00rootroot00000000000000// ライセンス: GPL2 // // ポップアップ系ビュー // #ifndef _ARTICLEVIEWPOPUP_H #define _ARTICLEVIEWPOPUP_H #include "articleviewbase.h" namespace ARTICLE { // ポップアップビューのベース class ArticleViewPopup : public ArticleViewBase { bool m_show_abone; public: ArticleViewPopup( const std::string& url, bool show_abone ); ~ArticleViewPopup(); void stop() override {} protected: void show_instruct_popup(); bool show_abone() const { return m_show_abone; } private: DrawAreaBase* create_drawarea() override; }; ///////////////////////////////////////////////////////////////////////// // HTMLコメントポップアップ class ArticleViewPopupHTML : public ArticleViewPopup { std::string m_html; public: ArticleViewPopupHTML( const std::string& url, const std::string& html ): ArticleViewPopup( url, false ), m_html( html ){} ~ArticleViewPopupHTML() noexcept = default; void show_view() override { append_html( m_html ); } }; ///////////////////////////////////////////////////////////////////////// // レス抽出ポップアップ class ArticleViewPopupRes : public ArticleViewPopup { std::string m_str_num; bool m_show_title; public: ArticleViewPopupRes( const std::string& url, const std::string& num, bool show_title, bool show_abone ) : ArticleViewPopup( url, show_abone ), m_str_num( num ), m_show_title( show_title ){} ~ArticleViewPopupRes() noexcept = default; void show_view() override { show_instruct_popup(); show_res( m_str_num, m_show_title ); } }; ///////////////////////////////////////////////////////////////////////// // 名前抽出ポップアップ class ArticleViewPopupName : public ArticleViewPopup { std::string m_str_name; public: ArticleViewPopupName( const std::string& url, const std::string& name ): ArticleViewPopup( url, false ), m_str_name( name ){} ~ArticleViewPopupName() noexcept = default; void show_view() override { show_instruct_popup(); show_name( m_str_name, false ); } }; ///////////////////////////////////////////////////////////////////////// // ID 抽出ポップアップ class ArticleViewPopupID : public ArticleViewPopup { std::string m_str_id; public: ArticleViewPopupID( const std::string& url, const std::string& id ): ArticleViewPopup( url, false ), m_str_id( id ) {} ~ArticleViewPopupID() noexcept = default; void show_view() override { show_instruct_popup(); show_id( m_str_id, false ); } }; ///////////////////////////////////////////////////////////////////////// // 参照抽出ポップアップ class ArticleViewPopupRefer : public ArticleViewPopup { std::string m_str_num; public: ArticleViewPopupRefer( const std::string& url, const std::string& num ): ArticleViewPopup( url, false ), m_str_num( num ){} ~ArticleViewPopupRefer() noexcept = default; void show_view() override { show_instruct_popup(); show_refer( atol( m_str_num.c_str() ) ); } }; ///////////////////////////////////////////////////////////////////////// // キーワード抽出ビュー class ArticleViewPopupDrawout : public ArticleViewPopup { std::string m_query; bool m_mode_or; public: ArticleViewPopupDrawout( const std::string& url, const std::string& query, bool mode_or ) : ArticleViewPopup( url, false ), m_query( query ), m_mode_or( mode_or ){} ~ArticleViewPopupDrawout() noexcept = default; void show_view() override { show_instruct_popup(); drawout_keywords( m_query, m_mode_or, false ); } }; ///////////////////////////////////////////////////////////////////////// // しおり抽出ポップアップ class ArticleViewPopupBM : public ArticleViewPopup { public: explicit ArticleViewPopupBM( const std::string& url ) : ArticleViewPopup( url, false ){} ~ArticleViewPopupBM() noexcept = default; void show_view() override { show_instruct_popup(); show_bm(); } }; } #endif jdim-0.7.0/src/article/articleviewpreview.cpp000066400000000000000000000052171417047150700213120ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "articleadmin.h" #include "articleviewpreview.h" #include "drawareamain.h" #include "message/messageadmin.h" #include "dbtree/articlebase.h" #include "control/controlid.h" #include "command.h" using namespace ARTICLE; ArticleViewPreview::ArticleViewPreview( const std::string& url ) : ArticleViewBase( url, url ) , m_url_messageview{ url } { #ifdef _DEBUG std::cout << "ArticleViewPreview::ArticleViewPreview " << get_url() << std::endl; #endif set_writeable( false ); setup_view(); // コントロールモード設定 get_control().clear_mode(); get_control().add_mode( CONTROL::MODE_MESSAGE ); get_control().add_mode( CONTROL::MODE_ARTICLE ); } ArticleViewPreview::~ArticleViewPreview() { #ifdef _DEBUG std::cout << "ArticleViewPreview::~ArticleViewPreview : " << get_url() << std::endl; #endif } // // viewの操作 // bool ArticleViewPreview::operate_view( const int control ) { if( control == CONTROL::None ) return false; // スクロール系操作 if( drawarea()->set_scroll( control ) ) return true; switch( control ){ // コピー case CONTROL::Copy: slot_copy_selection_str(); break; // 全て選択 case CONTROL::SelectAll: slot_select_all(); break; // 閉じる case CONTROL::Quit: case CONTROL::CancelWrite: MESSAGE::get_admin()->set_command( "close_currentview" ); break; // 書き込み実行 case CONTROL::ExecWrite: MESSAGE::get_admin()->set_command( "toolbar_write", m_url_messageview ); break; case CONTROL::TabLeft: case CONTROL::TabLeftUpdated: MESSAGE::get_admin()->set_command( "tab_left" ); break; case CONTROL::TabRight: case CONTROL::TabRightUpdated: MESSAGE::get_admin()->set_command( "tab_right" ); break; case CONTROL::FocusWrite: MESSAGE::get_admin()->set_command( "focus_button_write" ); break; default: return false; } return true; } // // drawarea のクリックイベント // // ArticleViewBase::slot_button_press()をオーパロードしてマウスジェスチャを無効にする // bool ArticleViewPreview::slot_button_press( const std::string& url, int res_number, GdkEventButton* event ) { #ifdef _DEBUG std::cout << "ArticleViewPreview::slot_button_press url = " << get_url() << std::endl; #endif MESSAGE::get_admin()->set_command( "switch_admin" ); return true; } jdim-0.7.0/src/article/articleviewpreview.h000066400000000000000000000012311417047150700207470ustar00rootroot00000000000000// ライセンス: GPL2 // // 書き込みなどのプレビュー // #ifndef _ARTICLEVIEWPREVIEW_H #define _ARTICLEVIEWPREVIEW_H #include "articleviewbase.h" namespace ARTICLE { class ArticleViewPreview : public ArticleViewBase { std::string m_url_messageview; public: explicit ArticleViewPreview( const std::string& url ); ~ArticleViewPreview(); bool operate_view( const int control ) override; private: bool slot_button_press( const std::string& url, int res_number, GdkEventButton* event ) override; void goto_num( const int num_to, const int num_from ) override {} }; } #endif jdim-0.7.0/src/article/articleviewsearch.cpp000066400000000000000000000277071417047150700211060ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "articleadmin.h" #include "articleviewsearch.h" #include "drawareamain.h" #include "skeleton/msgdiag.h" #include "dbtree/interface.h" #include "jdlib/miscutil.h" #include "jdlib/jdregex.h" #include "config/globalconf.h" #include "control/controlid.h" #include "global.h" #include "session.h" #include "usrcmdmanager.h" #include using namespace ARTICLE; // ログやスレタイ検索抽出ビュー ArticleViewSearch::ArticleViewSearch( const std::string& url, const bool exec_search ) : ArticleViewBase( url, "dummy" ) , m_searchmode{ CORE::SEARCHMODE_LOG } // set_toolbar_from_url() で検索モードを設定する { set_id_toolbar( TOOLBAR_SEARCH ); set_writeable( false ); #ifdef _DEBUG std::cout << "ArticleViewSearch::ArticleViewSearch " << get_url() << std::endl; #endif setup_view(); CORE::get_search_manager()->sig_search_fin().connect( sigc::mem_fun( *this, &ArticleViewSearch::slot_search_fin ) ); m_cancel_reload = ! exec_search; } ArticleViewSearch::~ArticleViewSearch() { #ifdef _DEBUG std::cout << "ArticleViewSearch::~ArticleViewSearch : " << get_url() << std::endl; #endif ArticleViewSearch::stop(); } // // url から query などを取得してツールバーの状態をセット // void ArticleViewSearch::set_toolbar_from_url() { #ifdef _DEBUG std::cout << "ArticleViewSearch::set_toolbar_from_url url = " << get_url() << std::endl; #endif JDLIB::Regex regex; const size_t offset = 0; const bool icase = false; const bool newline = true; const bool usemigemo = false; const bool wchar = false; if( regex.exec( std::string( "(.*)" ) + BOARD_SIGN + KEYWORD_SIGN + "(.*)" + ORMODE_SIGN + "(.*)" + BOOKMK_SIGN + "(.*)", get_url(), offset, icase, newline, usemigemo, wchar )){ m_url_board = regex.str( 1 ); if( m_url_board == URL_SEARCH_ALLBOARD ) m_searchmode = CORE::SEARCHMODE_ALLLOG; else m_searchmode = CORE::SEARCHMODE_LOG; set_search_query( regex.str( 2 ) ); if( regex.str( 3 ) == "1" ) m_mode_or = true; else m_mode_or = false; if( regex.str( 4 ) == "1" ) m_bm = true; else m_bm = false; m_enable_bm = true; } else if( regex.exec( std::string( "(.*)" ) + TITLE_SIGN + KEYWORD_SIGN + "(.*)", get_url(), offset, icase, newline, usemigemo, wchar )){ m_url_board = regex.str( 1 ); m_searchmode = CORE::SEARCHMODE_TITLE; set_search_query( regex.str( 2 ) ); m_mode_or = false; m_bm = false; m_enable_bm = false; } update_label(); } // // queryなどを変更した時の新しいURL // // queryが変化したときにurlを更新しないと再起動したときのrestoreで // 古いqueryのままになる // std::string ArticleViewSearch::get_new_url() { std::string url_tmp = m_url_board; if( m_searchmode == CORE::SEARCHMODE_TITLE ) url_tmp += TITLE_SIGN; else url_tmp += BOARD_SIGN; url_tmp += KEYWORD_SIGN + get_search_query(); if( m_searchmode != CORE::SEARCHMODE_TITLE ){ url_tmp += ORMODE_SIGN; if( m_mode_or ) url_tmp += "1"; else url_tmp += "0"; url_tmp += BOOKMK_SIGN; if( get_bm() ) url_tmp += "1"; else url_tmp += "0"; } #ifdef _DEBUG std::cout << "ArticleViewSearch::get_new_url " << url_tmp << std::endl; #endif return url_tmp; } // // ラベルやタブを更新 // void ArticleViewSearch::update_label() { std::string label; if( m_searchmode == CORE::SEARCHMODE_TITLE ) label = "スレタイ検索"; else if( m_searchmode == CORE::SEARCHMODE_ALLLOG ) label = "全ログ検索"; else label = "ログ検索"; if( ! get_search_query().empty() ) label += " : " + get_search_query(); set_label( label ); ARTICLE::get_admin()->set_command( "redraw_toolbar" ); // タブ更新 ARTICLE::get_admin()->set_command( "set_tablabel", get_url(), get_label() ); // タブのアイコン状態を更新 ARTICLE::get_admin()->set_command( "toggle_icon", get_url() ); } // // 正規表現メタ文字をエスケープ // void ArticleViewSearch::regex_escape() { m_escaped = false; if( m_searchmode == CORE::SEARCHMODE_LOG || m_searchmode == CORE::SEARCHMODE_ALLLOG ){ const bool escape = false; // \ を エスケープ文字として考慮しない if( MISC::has_regex_metachar( get_search_query(), escape ) ){ m_escaped = true; set_search_query( MISC::regex_escape( get_search_query(), escape ) ); } } } // // コピー用URL( readcgi型 ) // // メインウィンドウのURLバーなどの表示用にも使う // std::string ArticleViewSearch::url_for_copy() const { if( m_searchmode == CORE::SEARCHMODE_TITLE ) return m_url_title; return std::string(); } // // コマンド // bool ArticleViewSearch::set_command( const std::string& command, const std::string& arg1, const std::string& arg2 ) { // URL とツールバーの状態を一致させる if( command == "set_toolbar_from_url" ){ set_toolbar_from_url(); return true; } return ArticleViewBase::set_command( command, arg1, arg2 ); } // // フォーカスイン // void ArticleViewSearch::focus_view() { if( ! m_loading && m_list_searchdata.empty() ){ ARTICLE::get_admin()->set_command( "open_searchbar", get_url() ); } else ArticleViewBase::focus_view(); } // // 表示 // void ArticleViewSearch::show_view() { #ifdef _DEBUG std::cout << "ArticleViewSearch::show_view()\n"; #endif set_toolbar_from_url(); // コンストラクタのパラメータの exec_search が false の時にロードをキャンセル if( ! m_cancel_reload ) reload(); m_cancel_reload = false; relayout(); } // // 画面を消してレイアウトやりなおし & 再描画 // void ArticleViewSearch::relayout() { #ifdef _DEBUG std::cout << "ArticleViewSearch::relayout\n"; #endif drawarea()->clear_screen(); drawarea()->clear_highlight(); std::ostringstream comment; const bool has_query = ! get_search_query().empty(); if( m_searchmode == CORE::SEARCHMODE_ALLLOG ) comment << "検索対象:キャッシュ内の全ログ
"; else if( m_searchmode == CORE::SEARCHMODE_TITLE ) comment << "検索サイト : " + MISC::get_hostname( CONFIG::get_url_search_title() ) + "
"; else comment << "検索対象:" << DBTREE::board_name( m_url_board ) << "
"; if( get_bm() ) comment << "検索条件:しおり
"; if( m_loading ){ comment << "

検索中・・・"; ARTICLE::get_admin()->set_command( "toggle_icon", get_url() ); } else{ if( m_search_executed ){ if( has_query ) comment << get_search_query() << " "; comment << m_list_searchdata.size() << " 件
"; if( m_escaped ) comment << "ログ検索では正規表現は使用出来ません。メタ文字はエスケープされました。
"; } else{ comment << "

検索条件を入れて再検索ボタンを押してください。"; } comment << "
"; if( ! m_list_searchdata.empty() ){ for( const CORE::SEARCHDATA& data : m_list_searchdata ) { // 板名表示 if( m_searchmode == CORE::SEARCHMODE_ALLLOG || m_searchmode == CORE::SEARCHMODE_TITLE ) { comment << "[ " << data.boardname << " ] "; } comment << "" << MISC::html_escape( data.subject ) << ""; if( data.num ) comment << " ( " << data.num << " )"; // queryの抽出表示 if( m_searchmode != CORE::SEARCHMODE_TITLE && has_query ) { comment << "
" << "抽出表示する" << ""; } if( data.bookmarked ) comment << "
スレにしおりが付けられています"; if( data.num_bookmarked ){ comment << "
レスに付けられたしおり " << data.num_bookmarked << "件 " << "抽出表示する" << ""; } comment << "

"; } } } append_html( comment.str() ); drawarea()->redraw_view(); } // // 検索終了 // void ArticleViewSearch::slot_search_fin( const std::string& id ) { if( id != get_url() ) return; #ifdef _DEBUG std::cout << "ArticleViewSearch::slot_search_fin " << get_url() << std::endl; #endif m_loading = false; m_list_searchdata = CORE::get_search_manager()->get_list_data(); relayout(); ARTICLE::get_admin()->set_command( "toggle_icon", get_url() ); } // // 再読み込みボタンを押した // void ArticleViewSearch::reload() { exec_reload(); } // // 再読み込み実行 // void ArticleViewSearch::exec_reload() { if( CORE::get_search_manager()->is_searching() ){ SKELETON::MsgDiag mdiag( get_parent_win(), "他の検索スレッドが実行中です" ); mdiag.run(); return; } #ifdef _DEBUG std::cout << "ArticleViewSearch::exec_reload\n"; #endif // 検索が終わると ArticleViewSearch::slot_search_fin() が呼ばれる if( ! get_search_query().empty() || get_bm() ){ // url 変更 const std::string new_url = get_new_url(); if( new_url != get_url() ){ // 他のタブで既に開いていたら切り替える if( ARTICLE::get_admin()->exist_tab( new_url ) ){ #ifdef _DEBUG std::cout << "switch -> " << new_url << std::endl; #endif // 切り替える前に URL とツールバーの状態を合わせておかないと無限ループになる時がある ARTICLE::get_admin()->set_command_immediately( "set_toolbar_from_url", new_url ); ARTICLE::get_admin()->set_command( "switch_view", new_url ); ARTICLE::get_admin()->set_command( "reload_view", new_url ); return; } set_url( new_url ); update_label(); } regex_escape(); const std::string id = get_url(); #ifdef _DEBUG std::cout << "id = " << id << std::endl; #endif if( m_searchmode == CORE::SEARCHMODE_TITLE ){ if( ! SESSION::is_online() ){ SKELETON::MsgDiag mdiag( get_parent_win(), "オフラインです" ); mdiag.run(); return; } CORE::get_search_manager()->search_title( id, get_search_query() ); m_url_title = CORE::get_usrcmd_manager()->replace_cmd( CONFIG::get_url_search_title(), "", "", get_search_query(), 0 ); } else{ const bool calc_data = true; CORE::get_search_manager()->search( id, m_searchmode, m_url_board, MISC::regex_unescape( get_search_query() ), m_mode_or, get_bm(), calc_data ); } m_search_executed = true; m_loading = true; relayout(); focus_view(); } } // // 検索停止 // void ArticleViewSearch::stop() { CORE::get_search_manager()->stop( get_url() ); } // // 検索entryでenterを押した // void ArticleViewSearch::exec_search() { reload(); } // // 検索entryの操作 // void ArticleViewSearch::operate_search( const std::string& controlid ) { const int id = atoi( controlid.c_str() ); if( id == CONTROL::Cancel ){ focus_view(); ARTICLE::get_admin()->set_command( "close_searchbar" ); } else if( id == CONTROL::DrawOutAnd ) reload(); } jdim-0.7.0/src/article/articleviewsearch.h000066400000000000000000000043641417047150700205450ustar00rootroot00000000000000// ライセンス: GPL2 // // ログやスレタイ検索抽出ビュー // #ifndef _ARTICLEVIEWSEARCH_H #define _ARTICLEVIEWSEARCH_H #include "articleviewbase.h" #include "searchmanager.h" namespace ARTICLE { class ArticleViewSearch : public ArticleViewBase { std::string m_url_title; std::string m_url_board; int m_searchmode; // searchmanager.hで定義した検索モード bool m_mode_or{}; bool m_enable_bm{}; bool m_bm{}; std::list< CORE::SEARCHDATA > m_list_searchdata; bool m_loading{}; bool m_search_executed{}; bool m_escaped{}; bool m_cancel_reload; public: // exec_search == true ならviewを開いてすぐに検索開始 ArticleViewSearch( const std::string& url, const bool exec_search ); ~ArticleViewSearch(); // SKELETON::View の関数のオーバロード std::string url_for_copy() const override; // コピーやURLバー表示用のURL bool set_command( const std::string& command, const std::string& arg1 = {}, const std::string& arg2 = {} ) override; bool is_loading() const override { return m_loading; } void focus_view() override; void show_view() override; void relayout() override; void reload() override; void stop() override; // 検索 void exec_search() override; void operate_search( const std::string& controlid ) override; bool get_enable_bm() const { return m_enable_bm; } bool get_bm() const { return m_bm; } void set_bm( const bool set ){ m_bm = set; } protected: virtual void slot_push_write() {} // 書き込みキャンセル private: // url から query などを取得してツールバーの状態をセット void set_toolbar_from_url(); // queryなどを変更した時の新しいURL std::string get_new_url(); // ラベルやタブを更新 void update_label(); // 正規表現メタ文字をエスケープ void regex_escape(); void slot_search_fin( const std::string& id ); void exec_reload() override; }; } #endif jdim-0.7.0/src/article/caret.h000066400000000000000000000052141417047150700161320ustar00rootroot00000000000000// ライセンス: GPL2 // キャレットの座標とかを計算するクラス #ifndef _CARET_H #define _CARET_H #include "layouttree.h" namespace ARTICLE { class CARET_POSITION { public: long x{}; long y{}; LAYOUT* layout{}; // キャレットの属するレイアウトノードへのポインタ long byte{}; // 何バイト目の文字の「前」か CARET_POSITION() noexcept = default; ~CARET_POSITION() noexcept = default; // キャレット座標計算関数 void set( LAYOUT* _layout, long _byte, // マウスポインタのx座標 long _x = 0, // 文字の座標、幅、バイト数 long char_x = 0, long char_y = 0, long char_width = 0, long byte_char = 0 ){ layout = _layout; byte = _byte; y = char_y; // 文字の真ん中から左にマウスポインタがある if( _x <= char_x + char_width / 2 ){ x = char_x; } // 文字の真ん中から右にマウスポインタあるなら次の文字の前にキャレットセット else{ x = char_x + char_width; byte += byte_char; } } // 後は演算子 bool operator != ( const CARET_POSITION& caret_pos ) const { if( ! layout && ! caret_pos.layout ) return false; if( ! layout || ! caret_pos.layout ) return true; if( layout->id_header != caret_pos.layout->id_header || layout->id != caret_pos.layout->id || byte != caret_pos.byte ) return true; return false; } bool operator == ( const CARET_POSITION& caret_pos ) const { return ! ( *this != caret_pos ); } bool operator > ( const CARET_POSITION& caret_pos ) const { if( ! layout && ! caret_pos.layout ) return true; if( ! layout ) return false; if( ! caret_pos.layout ) return true; if( layout->id_header > caret_pos.layout->id_header ) return true; if( layout->id_header < caret_pos.layout->id_header ) return false; // ブロック同じ if( layout->id > caret_pos.layout->id ) return true; if( layout->id < caret_pos.layout->id ) return false; // ノード同じ if( byte > caret_pos.byte ) return true; return false; } bool operator < ( const CARET_POSITION& caret_pos ) const { return ! ( *this > caret_pos ); } }; } #endif jdim-0.7.0/src/article/drawareabase.cpp000066400000000000000000005202101417047150700200060ustar00rootroot00000000000000// ライセンス: GPL2 #ifdef HAVE_CONFIG_H #include "config.h" #endif //#define _DEBUG //#define _DEBUG_CARETMOVE //#define _DRAW_CARET #include "jddebug.h" #include "gtkmmversion.h" #include "drawareabase.h" #include "layouttree.h" #include "font.h" #include "embeddedimage.h" #include "jdlib/jdregex.h" #include "jdlib/miscgtk.h" #include "jdlib/miscmsg.h" #include "jdlib/miscutil.h" #include "dbtree/articlebase.h" #include "dbtree/node.h" #include "dbtree/interface.h" #include "dbimg/imginterface.h" #include "dbimg/img.h" #include "config/globalconf.h" #include "icons/iconmanager.h" #include "control/controlid.h" #include "global.h" #include "httpcode.h" #include "colorid.h" #include "fontid.h" #include "cache.h" #include "cssmanager.h" #include "session.h" #include #include #include #ifndef USE_PANGOLAYOUT #include #endif using namespace ARTICLE; enum { AUTOSCR_CIRCLE = 24, // オートスクロールの時のサークルの大きさ BIG_HEIGHT = 100000000, LAYOUT_MIN_HEIGHT = 2, // viewの高さがこの値よりも小さい時はリサイズしていないと考える EIMG_ICONSIZE = 32, // 埋め込み画像のアイコンサイズ EIMG_MRG = 8, // 埋め込み画像のアイコンの間隔 EIMG_SSSP_MRG = 2, // SSSP画像のアイコンの間隔 ( 上下マージンのみ ) EIMG_SSSP_FULL = EIMG_SSSP_MRG * 2, WIDTH_FRAME = 1, // フレーム幅 SPACE_TAB = 4, // 水平タブをどれだけ空けるか ( フォントの高さ * SPACE_TAB ) COUNTER_NOMOTION = 16, // ホイールスクロール直後にモーションイベントをキャンセルする時の遊び量 SEARCH_BUFFER_SIZE = 64 * 1024 // 検索のバッファサイズ }; namespace { namespace cursor_names { // カーソル名の入力ミスを予防するため文字列リテラルのかわりに定数を使う // 参考: https://developer.gnome.org/gdk3/stable/gdk3-Cursors.html#gdk-cursor-new-from-name constexpr const char kAllScroll[] = "all-scroll"; constexpr const char kDefault[] = "default"; constexpr const char kPointer[] = "pointer"; constexpr const char kText[] = "text"; } // namespace cursor_names } // namespace #define SCROLLSPEED_FAST ( m_vscrbar ? \ m_vscrbar->get_adjustment()->get_page_size() \ - m_vscrbar->get_adjustment()->get_step_increment()*CONFIG::get_key_fastscroll_size() \ : 0 ) #define SCROLLSPEED_MID ( m_vscrbar ? m_vscrbar->get_adjustment()->get_page_size()/2 : 0 ) #define SCROLLSPEED_SLOW ( m_vscrbar ? m_vscrbar->get_adjustment()->get_step_increment()*CONFIG::get_key_scroll_size() : 0 ) #define IS_ALPHABET( chr ) ( ( chr >= 'a' && chr <= 'z' ) || ( chr >= 'A' && chr <= 'Z' ) ) #define HAS_TEXT(layout) ( ( layout->type == DBTREE::NODE_TEXT || layout->type == DBTREE::NODE_IDNUM || layout->type == DBTREE::NODE_LINK ) && layout->text ) // 検索で使用 struct LAYOUT_TABLE { LAYOUT* layout; size_t offset; }; ////////////////////////////////////////////////////////// DrawAreaBase::DrawAreaBase( const std::string& url ) : m_url( url ) , m_cr( nullptr, cairo_destroy ) , m_backscreen( nullptr, cairo_surface_destroy ) , m_enable_draw{ true } , m_back_frame_top( nullptr, cairo_surface_destroy ) , m_back_frame_bottom( nullptr, cairo_surface_destroy ) , m_pre_pos_y{ -1 } , m_back_marker( nullptr, cairo_surface_destroy ) , m_cursor_type( cursor_names::kDefault ) { #ifdef _DEBUG std::cout << "DrawAreaBase::DrawAreaBase " << m_url << std::endl;; #endif // フォント設定 set_fontid( FONT_MAIN ); set_mailfontid( FONT_MAIL ); // 文字色 set_colorid_text( COLOR_CHAR ); // 背景色 set_colorid_back( COLOR_BACK ); // bodyのcssプロパティ int classid = CORE::get_css_manager()->get_classid( "body" ); m_css_body = CORE::get_css_manager()->get_property( classid ); } DrawAreaBase::~DrawAreaBase() { #ifdef _DEBUG std::cout << "DrawAreaBase::~DrawAreaBase " << m_url << std::endl;; #endif cancel_deceleration(); clear(); } // // セットアップ // // show_abone : あぼーんされたスレも表示 // show_scrbar : スクロールバーを最初から表示 // show_multispace : 連続空白も表示 // void DrawAreaBase::setup( const bool show_abone, const bool show_scrbar, const bool show_multispace ) { clear(); m_article = DBTREE::get_article( m_url ); m_layout_tree = std::make_unique( m_url, show_abone, show_multispace ); // デフォルトではoffになってるイベントを追加 m_view.add_events( Gdk::BUTTON_PRESS_MASK ); m_view.add_events( Gdk::BUTTON_RELEASE_MASK ); m_view.add_events( Gdk::SCROLL_MASK ); m_view.add_events( Gdk::SMOOTH_SCROLL_MASK ); m_view.add_events( Gdk::POINTER_MOTION_MASK ); m_view.add_events( Gdk::LEAVE_NOTIFY_MASK ); m_view.add_events( Gdk::VISIBILITY_NOTIFY_MASK ); // focus 可にセット m_view.set_can_focus( true ); m_view.add_events( Gdk::KEY_PRESS_MASK ); m_view.add_events( Gdk::KEY_RELEASE_MASK ); // イベント接続 m_view.signal_leave_notify_event().connect( sigc::mem_fun( *this, &DrawAreaBase::slot_leave_notify_event ) ); m_view.signal_realize().connect( sigc::mem_fun( *this, &DrawAreaBase::slot_realize )); m_view.signal_configure_event().connect( sigc::mem_fun( *this, &DrawAreaBase::slot_configure_event )); m_view.signal_draw().connect( sigc::mem_fun( *this, &DrawAreaBase::slot_draw ) ); m_view.signal_scroll_event().connect( sigc::mem_fun( *this, &DrawAreaBase::slot_scroll_event )); setup_event_controller(); m_view.signal_motion_notify_event().connect( sigc::mem_fun( *this, &DrawAreaBase::slot_motion_notify_event )); m_view.signal_key_press_event().connect( sigc::mem_fun(*this, &DrawAreaBase::slot_key_press_event )); m_view.signal_key_release_event().connect( sigc::mem_fun(*this, &DrawAreaBase::slot_key_release_event )); pack_start( m_view ); // pango layout 作成 m_pango_layout = m_view.create_pango_layout( "" ); m_pango_layout->set_width( -1 ); // no wrap // フォント初期化 // 色の初期化は realize したとき init_font(); // スクロールバー作成 if( show_scrbar ) create_scrbar(); show_all_children(); } // // 背景色のID( colorid.h にある ID を指定) // int DrawAreaBase::get_colorid_back() const { if( m_css_body.bg_color >= 0 ) return m_css_body.bg_color; return m_colorid_back; } // // 変数初期化 // void DrawAreaBase::clear() { m_scrollinfo.reset(); m_selection.select = false; m_multi_selection.clear(); m_layout_current = nullptr; m_width_client = 0; m_height_client = 0; m_clicked = false; m_drugging = false; m_r_drugging = false; m_pre_pos_y = -1; m_cancel_change_adjust = false; m_key_press = false; m_key_locked = false; m_keyval = 0; m_goto_num_reserve = 0; m_goto_bottom_reserve = false; m_wheel_scroll_time = 0; m_caret_pos = CARET_POSITION(); m_caret_pos_pre = CARET_POSITION(); m_caret_pos_dragstart = CARET_POSITION(); m_drawinfo.draw = false; m_rect_backscreen.y = 0; m_rect_backscreen.height = 0; m_enable_draw = true; m_jump_history.clear(); // 埋め込み画像削除 m_eimgs.clear(); } // // スクロールバー作成とパック // void DrawAreaBase::create_scrbar() { if( m_vscrbar ) return; #ifdef _DEBUG std::cout << "DrawAreaBase::create_scrbar\n"; #endif // そのままHBoxにスクロールバーをパックすると、スクロールしたときに何故かHBox全体が // 再描画されて負荷が高くなるのでEventBoxを間に挟む m_vscrbar = Gtk::manage( new Gtk::Scrollbar( Glib::RefPtr{}, Gtk::ORIENTATION_VERTICAL ) ); m_event = Gtk::manage( new Gtk::EventBox() ); assert( m_vscrbar ); assert( m_event ); if( CONFIG::get_left_scrbar() ) remove( m_view ); m_event->add( *m_vscrbar ); pack_start( *m_event, Gtk::PACK_SHRINK ); if( CONFIG::get_left_scrbar() ) pack_start( m_view ); m_vscrbar->get_adjustment()->signal_value_changed().connect( sigc::mem_fun( *this, &DrawAreaBase::slot_change_adjust ) ); show_all_children(); } // // 色初期化 ( colorid.h 参照 ) // void DrawAreaBase::init_color() { const std::vector< std::string >& colors = CORE::get_css_manager()->get_colors(); const int usrcolor = colors.size(); m_color.resize( END_COLOR_FOR_THREAD + usrcolor ); Glib::RefPtr< Gtk::StyleContext > context = get_style_context(); int i = COLOR_FOR_THREAD +1; for( ; i < END_COLOR_FOR_THREAD; ++i ){ m_color[ i ] = Gdk::RGBA( CONFIG::get_color( i ) ); } // スレビューの選択色でgtkrcの設定を使用 if( CONFIG::get_use_select_gtkrc() ){ const bool fg_ok = context->lookup_color( u8"theme_selected_fg_color", m_color[ COLOR_CHAR_SELECTION ] ); const bool bg_ok = context->lookup_color( u8"theme_selected_bg_color", m_color[ COLOR_BACK_SELECTION ] ); if( !fg_ok || !bg_ok ) { #ifdef _DEBUG std::cout << "ERROR:DrawAreaBase::init_color lookup theme color failed." << std::endl; #endif } } for( const auto& color : colors ) { m_color[ i ] = Gdk::RGBA( color ); ++i; } } // // フォント初期化 // void DrawAreaBase::init_font() { // スレビューで文字幅の近似を厳密にするか m_strict_of_char = CONFIG::get_strict_char_width(); m_context = get_pango_context(); assert( m_context ); std::string fontname = CONFIG::get_fontname( m_defaultfontid ); if( fontname.empty() ) return; init_fontinfo( m_defaultfont, fontname ); if( CONFIG::get_aafont_enabled() ){ m_aafont_initialized = true; std::string aafontname = CONFIG::get_fontname( FONT_AA ); init_fontinfo( m_aafont, aafontname ); #ifdef _DEBUG std::cout << "DrawAreaBase::aa_fontname = " << aafontname << std::endl; #endif } std::string mailfontname = CONFIG::get_fontname( m_defaultmailfontid ); if( ! mailfontname.empty() && mailfontname != fontname ) { m_mailfont_initialized = true; init_fontinfo( m_mailfont, mailfontname ); } else { // メモリ節約のためデフォルトとメール欄のフォントが同じときはデフォルトを利用する m_mailfont_initialized = false; } // layoutにフォントをセット m_font = &m_defaultfont; m_pango_layout->set_font_description( m_font->pfd ); m_context->set_font_description( m_font->pfd ); } // // フォント情報初期化 // void DrawAreaBase::init_fontinfo( FONTINFO& fi, std::string& fontname ) { fi.fontname = fontname; // layoutにフォントをセット fi.pfd = Pango::FontDescription( fontname ); fi.pfd.set_weight( Pango::WEIGHT_NORMAL ); m_pango_layout->set_font_description( fi.pfd ); // フォント情報取得 Pango::FontMetrics metrics = m_context->get_metrics( fi.pfd ); fi.ascent = PANGO_PIXELS( metrics.get_ascent() ); fi.descent = PANGO_PIXELS( metrics.get_descent() ); fi.height = fi.ascent + fi.descent; // 改行高さ ( トップからの距離 ) fi.br_size = ( int )( fi.height * CONFIG::get_adjust_line_space() ); const char* wstr = "あいうえお"; m_pango_layout->set_text( wstr ); // リンクの下線の位置 ( トップからの距離 ) fi.underline_pos = PANGO_PIXELS( ( metrics.get_ascent() - metrics.get_underline_position() ) * CONFIG::get_adjust_underline_pos() ); // 左右padding取得 // マージン幅は真面目にやると大変そうなので文字列 wstr の平均を取る int width = m_pango_layout->get_pixel_ink_extents().get_width() / 5; fi.mrg_right = width /2 * 3; } // // クロック入力 // void DrawAreaBase::clock_in() { if( m_scrollinfo.mode != SCROLL_NOT && ! ( m_scrollinfo.mode == SCROLL_AUTO || m_scrollinfo.live ) ){ // スクロール中にダイアログを開いた場合はスクロールされたままになるのでスクロールを止める if( SESSION::is_dialog_shown() ) focus_out(); else exec_scroll(); } } // // スムーススクロール用クロック入力 // void DrawAreaBase::clock_in_smooth_scroll() { if( m_scrollinfo.mode == SCROLL_AUTO || m_scrollinfo.live ){ // スクロール中にダイアログを開いた場合はスクロールされたままになるのでスクロールを止める if( ! m_scrollinfo.live && SESSION::is_dialog_shown() ){ m_scrollinfo.mode = SCROLL_NOT; focus_out(); } else exec_scroll(); } } // // フォーカス // void DrawAreaBase::focus_view() { m_view.grab_focus(); } // // フォーカス解除 // void DrawAreaBase::focus_out() { // realize していない if( !m_window ) return; #ifdef _DEBUG std::cout << "DrawAreaBase::focus_out\n"; #endif change_cursor( cursor_names::kDefault ); m_key_press = false; m_key_locked = false; m_keyval = 0; if( m_scrollinfo.mode != SCROLL_AUTO ) m_scrollinfo.reset(); } // 新着セパレータのあるレス番号の取得とセット int DrawAreaBase::get_separator_new() const { return m_layout_tree->get_separator_new(); } void DrawAreaBase::set_separator_new( int num ) { m_layout_tree->set_separator_new( num ); } // 新着セパレータを隠す void DrawAreaBase::hide_separator_new() { if( ! get_separator_new() ) return; m_layout_tree->set_separator_new( 0 ); exec_layout(); } // 現在のポインタの下にあるレス番号取得 int DrawAreaBase::get_current_res_num() const { const int y = m_y_pointer + get_vscr_val(); // 先頭のヘッダブロックから順に調べる const LAYOUT* header = m_layout_tree->top_header(); while( header ){ // y が含まれているブロックを探す if( header->rect->y >= y ) return header->res_number -1; // 次のブロックへ header = header->next_header; } return max_number(); } // 範囲選択中の文字列 std::string DrawAreaBase::str_selection() const { if( ! m_selection.select ) return std::string(); return m_selection.str; } // 範囲選択を開始したレス番号 int DrawAreaBase::get_selection_resnum_from() const { if( ! m_selection.select ) return 0; if( ! m_selection.caret_from.layout ) return 0; return m_selection.caret_from.layout->res_number; } // 範囲選択を終了したレス番号 int DrawAreaBase::get_selection_resnum_to() const { if( ! m_selection.select ) return 0; if( ! m_selection.caret_to.layout ) return 0; return m_selection.caret_to.layout->res_number; } // // 表示されている最後のレスの番号 // int DrawAreaBase::max_number() const { assert( m_layout_tree ); return m_layout_tree->max_res_number(); } // // from_num から to_num までレスをappendして再レイアウト // void DrawAreaBase::append_res( const int from_num, const int to_num ) { assert( m_article ); assert( m_layout_tree ); #ifdef _DEBUG std::cout << "DrawAreaBase::append_res from " << from_num << " to " << to_num << std::endl; #endif // スクロールバーが一番下にある(つまり新着スレがappendされた)場合は少しだけスクロールする bool scroll = false; const int pos = get_vscr_val(); if( ! m_layout_tree->get_separator_new() && pos && pos == get_vscr_maxval() && ! m_scrollinfo.live ){ #ifdef _DEBUG std::cout << "on bottom pos = " << pos << std::endl; #endif scroll = true; } for( int num = from_num; num <= to_num; ++num ) m_layout_tree->append_node( m_article->res_header( num ), false ); // クライアント領域のサイズをリセットして再レイアウト m_width_client = 0; m_height_client = 0; exec_layout(); if( scroll ){ CORE::CSS_PROPERTY* css = m_layout_tree->get_separator()->css; RECTANGLE* rect = m_layout_tree->get_separator()->rect; m_scrollinfo.reset(); m_scrollinfo.dy = 0; if( css ) m_scrollinfo.dy += css->mrg_top + css->mrg_bottom; if( rect ) m_scrollinfo.dy += rect->height; if( m_scrollinfo.dy ){ #ifdef _DEBUG std::cout << "exec scroll dy = " << m_scrollinfo.dy << std::endl; #endif m_scrollinfo.mode = SCROLL_NORMAL; exec_scroll(); } } } // // リストで指定したレスをappendして再レイアウト // void DrawAreaBase::append_res( const std::list< int >& list_resnum ) { std::list< bool > list_joint; append_res( list_resnum, list_joint ); } // // リストで指定したレスをappendして再レイアウト( 連結情報付き ) // // list_joint で連結指定したレスはヘッダを取り除いて前のレスに連結する // void DrawAreaBase::append_res( const std::list< int >& list_resnum, const std::list< bool >& list_joint ) { assert( m_article ); assert( m_layout_tree ); if( list_resnum.size() == 0 ) return; #ifdef _DEBUG std::cout << "DrawAreaBase::append_res" << std::endl; #endif const bool use_joint = ( list_joint.size() == list_resnum.size() ); std::list< bool >::const_iterator it_joint = list_joint.begin(); for( const int num : list_resnum ) { bool joint = false; if( use_joint ){ joint = ( *it_joint ); ++it_joint; } #ifdef _DEBUG std::cout << "append no. " << num << " joint = " << joint << std::endl; #endif m_layout_tree->append_node( m_article->res_header( num ), joint ); } // クライアント領域のサイズをリセットして再レイアウト m_width_client = 0; m_height_client = 0; exec_layout(); } // // html をappendして再レイアウト // void DrawAreaBase::append_html( const std::string& html ) { assert( m_layout_tree ); if( html.empty() ) return; #ifdef _DEBUG std::cout << "DrawAreaBase::append_html " << html << std::endl; #endif m_layout_tree->append_html( html ); // クライアント領域のサイズをリセットして再レイアウト m_width_client = 0; m_height_client = 0; exec_layout(); } // // datをappendして再レイアウト // void DrawAreaBase::append_dat( const std::string& dat, int num ) { assert( m_layout_tree ); if( dat.empty() ) return; #ifdef _DEBUG std::cout << "DrawAreaBase::append_dat " << dat << std::endl; #endif m_layout_tree->append_dat( dat, num ); // クライアント領域のサイズをリセットして再レイアウト m_width_client = 0; m_height_client = 0; exec_layout(); } // // 画面初期化 // // 色、フォントを初期化して画面を消す // void DrawAreaBase::clear_screen() { if( ! m_layout_tree ) return; m_layout_tree->clear(); clear(); init_color(); init_font(); if( exec_layout() ) redraw_view_force(); } // // 再描画 // 再レイアウトはしないが configureの予約がある場合は再レイアウトしてから再描画する // void DrawAreaBase::redraw_view() { // 起動中とシャットダウン中は処理しない if( SESSION::is_booting() ) return; if( SESSION::is_quitting() ) return; // タブ操作中は処理しない if( SESSION::is_tab_operating( URL_ARTICLEADMIN ) ) return; #ifdef _DEBUG std::cout << "DrawAreaBase::redraw_view " << m_url << std::endl; #endif configure_impl(); // 実行時の警告を修正する // Gtk-WARNING **: HH:MM:SS.sss: Drawing a gadget with negative dimensions. Did you forget to allocate a size? // (node tab owner gtkmm__GtkNotebook) // FIXME: 起動時に隠れているタブのスレビューを開くとこの警告が出る const auto alloc_view = m_view.get_allocation(); #ifdef _DEBUG std::cout << "DrawAreaBase::redraw_view " << " m_view.x = " << alloc_view.get_x() << " m_view.y = " << alloc_view.get_y() << " m_view.width = " << alloc_view.get_width() << " m_view.height = " << alloc_view.get_height() << std::endl; #endif if( alloc_view.get_x() < 0 || alloc_view.get_y() < 0 ) return; m_view.queue_draw(); } // 強制再描画 void DrawAreaBase::redraw_view_force() { #ifdef _DEBUG std::cout << "DrawAreaBase::redraw_view_force()\n"; #endif m_drawinfo.draw = false; m_rect_backscreen.y = 0; m_rect_backscreen.height = 0; redraw_view(); } // // レイアウト(ノードの座標演算)実行 // bool DrawAreaBase::exec_layout() { return exec_layout_impl( false, 0 ); } // // 先頭ノードから順に全ノードの座標を計算する(描画はしない) // // is_popup = true ならポップアップウィンドウの幅を親ビューの幅とする // offset_y は y 座標の上オフセット行数 // bool DrawAreaBase::exec_layout_impl( const bool is_popup, const int offset_y ) { // 起動中とシャットダウン中は処理しない if( SESSION::is_booting() ) return false; if( SESSION::is_quitting() ) return false; // タブ操作中は処理しない if( SESSION::is_tab_operating( URL_ARTICLEADMIN ) ) return true; // レイアウトがセットされていない if( ! m_layout_tree ) return false; if( ! m_layout_tree->top_header() ) return false; // drawareaのウィンドウサイズ int width_view = m_view.get_width(); const int height_view = m_view.get_height(); int width_base_vscrbar = 0; if( is_popup && SESSION::get_base_drawarea() && SESSION::get_base_drawarea()->get_vscrbar() ){ width_base_vscrbar = SESSION::get_base_drawarea()->get_vscrbar()->get_width(); } #ifdef _DEBUG std::cout << "DrawAreaBase::exec_layout_impl : is_popup = " << is_popup << " url = " << m_url << std::endl; #endif //表示はされてるがまだリサイズしてない状況 if( height_view < LAYOUT_MIN_HEIGHT ){ // ポップアップで無い場合は処理しない if( ! is_popup ){ #ifdef _DEBUG std::cout << "drawarea is not resized yet.\n"; #endif return false; } // ポップアップの場合、横幅が確定出来ないのでメインウィンドウのスレビューの // drawarea の幅を最大幅とする if( SESSION::get_base_drawarea() && SESSION::get_base_drawarea()->get_view() ){ width_view = SESSION::get_base_drawarea()->get_view()->get_width(); } // スレビューを表示していない場合はメインウィンドウサイズを使用 else width_view = SESSION::get_width_win_main(); } #ifdef _DEBUG std::cout << "width_view = " << width_view << " height_view = " << height_view << std::endl; #endif // 新着セパレータの挿入 // 実況時は新着セパレータを表示しない(スクロールがガタガタするから) if( ! m_scrollinfo.live ) m_layout_tree->move_separator(); m_width_client = 0; m_height_client = 0; int y = 0; LAYOUT* header = m_layout_tree->top_header(); // フォント設定 set_node_font( header ); const CORE::Css_Manager *cssmgr = CORE::get_css_manager(); cssmgr->set_size( &m_css_body, m_font->height ); y += offset_y * m_font->br_size; y += m_css_body.padding_top; while( header ){ LAYOUT* layout = header->next_layout; if( ! layout ) break; // フォント設定 set_node_font( header ); // (注) header は div ノードであり、クラス名は "res" // 詳しくは LayoutTree::create_layout_header() を参照せよ cssmgr->set_size( header->css, m_font->height ); // div の位置と幅を計算 // 高さは子ノードのレイアウトが全て済んでから計算 if( ! m_pixbuf_bkmk ) m_pixbuf_bkmk = ICON::get_icon( ICON::BKMARK_THREAD ); // 最低でもブックマークのアイコン分だけ左のスペース開ける int x = MAX( 1 + m_pixbuf_bkmk->get_width() + 1, m_css_body.padding_left + header->css->mrg_left ); y += header->css->mrg_top; if( ! header->rect ) header->rect = m_layout_tree->create_rect(); header->rect->x = x; header->rect->y = y; header->rect->width = width_view - x - ( header->css->mrg_right + m_css_body.padding_right ); // 内部ノードの開始座標 x += header->css->padding_left; y += header->css->padding_top; LAYOUT* current_div = nullptr; int br_size = m_font->br_size; // 現在行の改行サイズ // 先頭の子ノードから順にレイアウトしていく while ( layout ){ // フォント設定 set_node_font( layout ); switch( layout->type ){ // div (注) div の中に div は作れない仕様になっている( header は除く ) case DBTREE::NODE_DIV: // 違うdivに切り替わったら以前のdivの高さを更新 if( current_div ){ y += br_size; br_size = m_font->br_size; // 次の行の改行位置をリセット y += current_div->css->padding_bottom; current_div->rect->height = y - current_div->rect->y; y += current_div->css->mrg_bottom; // align 調整 if( current_div->css->align != CORE::ALIGN_LEFT ) set_align( current_div, layout->id, current_div->css->align ); } current_div = layout; // div の位置と幅を計算 // 高さは違う div に切り替わった時に計算 cssmgr->set_size( current_div->css, m_font->height ); x = header->rect->x + header->css->padding_left; x += current_div->css->mrg_left; y += current_div->css->mrg_top; if( ! current_div->rect ) current_div->rect = m_layout_tree->create_rect(); current_div->rect->x = x; current_div->rect->y = y; current_div->rect->width = header->rect->width - ( header->css->padding_left + current_div->css->mrg_left ) - ( header->css->padding_right + current_div->css->mrg_right ); // divの内部にあるノードの開始座標 x += current_div->css->padding_left; y += current_div->css->padding_top; break; ////////////////////////////////////////// case DBTREE::NODE_IDNUM: // 発言数ノード if( ! set_num_id( layout ) ) break; // fallthrough case DBTREE::NODE_TEXT: // テキスト case DBTREE::NODE_LINK: // リンク // ノードをレイアウトして次のノードの左上座標を計算 // x,y,br_size が参照なので更新された値が戻る layout_one_text_node( layout, x, y, br_size, width_view ); break; ////////////////////////////////////////// case DBTREE::NODE_IMG: // img // レイアウトして次のノードの左上座標を計算 // x,y, br_size が参照なので更新された値が戻る layout_one_img_node( layout, x, y, br_size, m_width_client, is_popup, EIMG_MRG, EIMG_MRG ); break; ////////////////////////////////////////// case DBTREE::NODE_SSSP: // sssp アイコン // テキストノードの途中にインラインで表示する // x,y, br_size が参照なので更新された値が戻る layout_one_img_node( layout, x, y, br_size, m_width_client, is_popup, 0, EIMG_SSSP_FULL ); // 上下マージンあわせた分の余白を、下マージン ( br_size ) に確保しておく // 上マージンの位置は、後でベースライン合わせするときに再調整する break; ////////////////////////////////////////// case DBTREE::NODE_HR: // 水平線 if( ! layout->rect ) layout->rect = m_layout_tree->create_rect(); layout->rect->x = layout->div ? layout->div->rect->x + layout->div->css->padding_left : 0; layout->rect->y = y + br_size; layout->rect->width = layout->div ? layout->div->rect->width - layout->div->css->padding_left : width_view; layout->rect->height = 1; y += 2; // 水平線1px + 余白1px // fallthrough ////////////////////////////////////////// case DBTREE::NODE_BR: // 改行 x = 0; if( layout->div ) x = layout->div->rect->x + layout->div->css->padding_left; y += br_size; br_size = m_font->br_size; // 次の行の改行位置をリセット break; ////////////////////////////////////////// case DBTREE::NODE_ZWSP: // 幅0スペース break; ////////////////////////////////////////// case DBTREE::NODE_HTAB: // 水平タブ x += m_font->height * SPACE_TAB; break; } // クライアント領域の幅を更新 if( layout->type != DBTREE::NODE_DIV && layout->rect ){ int width_tmp = layout->rect->x + layout->rect->width; width_tmp += ( header->css->padding_right + header->css->mrg_right ); // divの中なら右スペースの分も足す if( layout->div ) width_tmp += ( layout->div->css->padding_right + layout->div->css->mrg_right ); width_tmp += m_font->mrg_right + m_css_body.padding_right; if( width_tmp > width_view ) width_tmp = width_view; width_tmp += width_base_vscrbar; if( width_tmp > m_width_client ) m_width_client = width_tmp; } layout = layout->next_layout; } y += br_size; // 同じ行にあるノードのベースラインを調整 adjust_layout_baseline( header ); // 属している div の高さ確定 if( current_div ){ y += current_div->css->padding_bottom; current_div->rect->height = y - current_div->rect->y; y += current_div->css->mrg_bottom; // align 調整 if( current_div->css->align != CORE::ALIGN_LEFT ) set_align( current_div, 0, current_div->css->align ); } // ヘッダブロック高さ確定 y += header->css->padding_bottom; header->rect->height = y - header->rect->y; if( header->next_header ) y += header->css->mrg_bottom; // align 調整 if( header->css->align != CORE::ALIGN_LEFT ) set_align( header, 0, header->css->align ); header = header->next_header; } y += m_css_body.padding_bottom; // クライアント領域の高さ確定 m_height_client = y; #ifdef _DEBUG std::cout << "virtual size of drawarea : m_width_client = " << m_width_client << " m_height_client = " << m_height_client << std::endl; #endif // 実際に画面に表示されてない if( ! is_drawarea_realized() ){ #ifdef _DEBG std::cout << "windows is not shown yet\n"; #endif return false; } // 表示はされてるがまだリサイズしてない状況 if( height_view < LAYOUT_MIN_HEIGHT ){ #ifdef _DEBG std::cout << "windows is not resized yet\n"; #endif return false; } // ポップアップなどでスクロールバーが表示されていないならここで作成 // (注) メインウィンドウのスレビューなどは DrawAreaBase::setup() の show_scrbar // が true 指定されているので、はじめからスクロールバーが表示されている if( ! m_vscrbar && m_height_client > height_view ) create_scrbar(); // adjustment 範囲変更 auto adjust = m_vscrbar ? m_vscrbar->get_adjustment() : decltype( m_vscrbar->get_adjustment() )( nullptr ); if( adjust ){ const double current = adjust->get_value(); const double newpos = MAX( 0, MIN( m_height_client - height_view , current ) ); m_pre_pos_y = -1; adjust->set_lower( 0 ); adjust->set_upper( m_height_client ); adjust->set_page_size( height_view ); adjust->set_step_increment( m_font->br_size ); adjust->set_page_increment( height_view / 2 ); m_cancel_change_adjust = true; adjust->set_value( newpos ); m_cancel_change_adjust = false; } // 裏描画画面作成と初期描画 #ifdef _DEBUG std::cout << "create backscreen : width = " << m_view.get_width() << " height = " << m_view.get_height() << std::endl; #endif m_backscreen.reset( gdk_window_create_similar_surface( m_window->gobj(), CAIRO_CONTENT_COLOR, m_view.get_width(), m_view.get_height() ) ); m_rect_backscreen.y = 0; m_rect_backscreen.height = 0; m_back_frame_top.reset( gdk_window_create_similar_surface( m_window->gobj(), CAIRO_CONTENT_COLOR, m_view.get_width(), WIDTH_FRAME ) ); m_back_frame_bottom.reset( gdk_window_create_similar_surface( m_window->gobj(), CAIRO_CONTENT_COLOR, m_view.get_width(), WIDTH_FRAME ) ); m_ready_back_frame = false; // 予約されているならジャンプ予約を実行 if( m_goto_num_reserve ) goto_num( m_goto_num_reserve ); if( m_goto_bottom_reserve ) goto_bottom(); return true; } // // 同じ行にあるノードのベースラインを調整 // void DrawAreaBase::adjust_layout_baseline( LAYOUT* header ) { int top_y = -1, max_height = 0; LAYOUT* line_layout = nullptr; bool line_onsssp = false; LAYOUT* layout = header->next_layout; while ( layout ){ switch( layout->type ){ case DBTREE::NODE_IDNUM: // 発言数ノード case DBTREE::NODE_TEXT: // テキスト case DBTREE::NODE_LINK: // リンク case DBTREE::NODE_SSSP: // sssp アイコン break; default: layout = layout->next_layout; continue; } RECTANGLE* rect = layout->rect; while ( rect ){ // 行ごとにベースラインを調整する if( top_y < rect->y ){ if( line_onsssp ){ max_height += EIMG_SSSP_MRG; // ssspアイコンの上マージンを加える } while ( line_layout ){ switch( line_layout->type ){ case DBTREE::NODE_IDNUM: // 発言数ノード case DBTREE::NODE_TEXT: // テキスト case DBTREE::NODE_LINK: // リンク case DBTREE::NODE_SSSP: // sssp アイコン break; default: line_layout = line_layout->next_layout; continue; } RECTANGLE* line_rect = line_layout->rect; while ( line_rect ){ if( line_rect == rect ){ goto NEXTLINE; } if( line_rect->y == top_y ){ line_rect->y = top_y + max_height - line_rect->height; } line_rect = line_rect->next_rect; } line_layout = line_layout->next_layout; } NEXTLINE: top_y = rect->y; max_height = rect->height; line_layout = layout; // 行ごとにノードの先頭を覚えておく line_onsssp = false; } if( max_height < rect->height ){ max_height = rect->height; } if( layout->type == DBTREE::NODE_SSSP ){ // sssp アイコン line_onsssp = true; } rect = rect->next_rect; } layout = layout->next_layout; } // 最終行の調整を行う if( line_onsssp ){ max_height += EIMG_SSSP_MRG; // ssspアイコンの上マージンを加える } while ( line_layout ){ switch( line_layout->type ){ case DBTREE::NODE_IDNUM: // 発言数ノード case DBTREE::NODE_TEXT: // テキスト case DBTREE::NODE_LINK: // リンク case DBTREE::NODE_SSSP: // sssp アイコン break; default: line_layout = line_layout->next_layout; continue; } RECTANGLE* line_rect = line_layout->rect; while ( line_rect ){ if( line_rect->y == top_y ){ line_rect->y = top_y + max_height - line_rect->height; } line_rect = line_rect->next_rect; } line_layout = line_layout->next_layout; } } // // ブロック要素のalign設定 // // id_end == 0 の時は最後のノードまでおこなう // void DrawAreaBase::set_align( LAYOUT* div, int id_end, int align ) { #ifdef _DEBUG // std::cout << "DrawAreaBase::set_align width = " << div->rect->width << std::endl; #endif LAYOUT* layout_from = nullptr; LAYOUT* layout_to = nullptr; RECTANGLE* rect_from = nullptr; RECTANGLE* rect_to = nullptr; LAYOUT* layout = div->next_layout; int width_line = 0; while( layout && ( ! id_end || layout->id != id_end ) ){ RECTANGLE* rect = layout->rect; while( rect ){ if( ! layout_from ){ layout_from = layout; rect_from = rect; width_line = 0; } layout_to = layout; rect_to = rect; width_line += rect->width; #ifdef _DEBUG // std::cout << "id = " << layout->id << " w = " << width_line << std::endl; #endif if( rect->end ) break; // wrap set_align_line( div, layout_from, layout_to, rect_from, rect_to, width_line, align ); layout_from = nullptr; rect = rect->next_rect; } // 改行 if( layout_from && ( ! layout->next_layout || layout->type == DBTREE::NODE_BR || layout->id == id_end -1 ) ){ set_align_line( div, layout_from, layout_to, rect_from, rect_to, width_line, align ); layout_from = nullptr; } layout = layout->next_layout; } } void DrawAreaBase::set_align_line( LAYOUT* div, LAYOUT* layout_from, LAYOUT* layout_to, RECTANGLE* rect_from, RECTANGLE* rect_to, int width_line, int align ) { int padding = div->rect->width - div->css->padding_left - div->css->padding_right - width_line; if( align == CORE::ALIGN_CENTER ) padding /= 2; #ifdef _DEBUG // std::cout << "from = " << layout_from->id << " padding = " << padding << std::endl; #endif for(;;){ bool break_for = ( layout_from == layout_to && rect_from == rect_to ); if( rect_from ){ rect_from->x += padding; rect_from = rect_from->next_rect; } if( ! rect_from ){ layout_from = layout_from->next_layout; if( layout_from ) rect_from = layout_from->rect; } if( break_for ) break; } } // // テキストノードの座標を計算する関数 // // x,y (参照) : ノードの初期座標(左上)を渡して、次のノードの左上座標が入って返る // br_size : 改行量 // width_view : 描画領域の幅 // void DrawAreaBase::layout_one_text_node( LAYOUT* layout, int& x, int& y, int& br_size, const int width_view ) { if( ! layout->lng_text ) layout->lng_text = strlen( layout->text ); int byte_to = layout->lng_text; LAYOUT* div = layout->div; // wrap 処理用の閾値計算 // x が border よりも右に来たら wrap する int border = 0; if( div ) border = div->rect->x + div->rect->width - div->css->padding_right; else border = width_view; border -= m_font->mrg_right; // 先頭の RECTANGLE型のメモリ確保 // wrapが起きたらまたRECTANGLE型のメモリを確保してリストの後ろに繋ぐ bool head_rect = true; RECTANGLE* rect = layout->rect; int pos_start = 0; for(;;){ // 横に何文字並べるか計算 char pre_char = 0; bool draw_head = true; // 先頭は最低1文字描画 int pos_to = pos_start; int width_line = 0; int n_byte = 0; int n_ustr = 0; // utfで数えたときの文字数 // この文字列の全角/半角モードの初期値を決定 bool wide_mode = set_init_wide_mode( layout->text, pos_start, byte_to ); // 右端がはみ出るまで文字を足していく while( pos_to < byte_to && ( ! is_wrapped( x + PANGO_PIXELS( width_line ), border, layout->text + pos_to ) || draw_head ) ) { int byte_char; width_line += get_width_of_one_char( layout->text + pos_to, byte_char, pre_char, wide_mode, get_layout_fontid( layout ) ); pos_to += byte_char; n_byte += byte_char; ++n_ustr; draw_head = false; } // 幅確定 width_line = PANGO_PIXELS( width_line ); if( ! width_line ) break; if( layout->bold ) ++width_line; // RECTANGLEのメモリ確保 if( head_rect ){ // 先頭 if( ! rect ) rect = layout->rect = m_layout_tree->create_rect(); head_rect = false; } else{ // wrap したので次のRECTANGLEを確保してリストで繋ぐ rect->end = false; if( ! rect->next_rect ) rect->next_rect = m_layout_tree->create_rect(); rect = rect->next_rect; } // 座標情報更新 rect->end = true; rect->x = x; rect->y = y; rect->width = width_line; rect->height = m_font->br_size; rect->pos_start = pos_start; rect->n_byte = n_byte; rect->n_ustr = n_ustr; #ifdef _DEBUG // std::cout << do_draw << " " << layout->id_header << " " << layout->id // << " x = " << layout->rect->x << " y = " << layout->rect->y // << " w = " << layout->rect->width << " h = " << layout->rect->height << std::endl; #endif // 一文字でも書いたので、改行位置をフォントにあわせて更新 br_size = m_font->br_size; x += rect->width; if( pos_to >= byte_to ) break; // wrap 処理 x = div ? div->rect->x + div->css->padding_left : 0; y += br_size; pos_start = pos_to; } } // // 画像ノードの座標を計算する関数 // // x,y (参照) : ノードの初期座標(左上)を渡して、次のノードの左上座標が入って返る // br_size : 現在行での改行量 // width_view : 描画領域の幅 // init_popupwin : ポップアップウィンドウの初期サイズ計算をおこなう // mrg_right : 右マージン // mrg_bottom : 下マージン // void DrawAreaBase::layout_one_img_node( LAYOUT* layout, int& x, int& y, int& br_size, const int width_view, const bool init_popupwin, const int mrg_right, const int mrg_bottom ) { #ifdef _DEBUG std::cout << "DrawAreaBase::layout_one_img_node link = " << layout->link << std::endl; #endif DBTREE::NODE* node = layout->node; if( ! node ) return; // 座標とサイズのセット RECTANGLE* rect = layout->rect; if( ! rect ) rect = layout->rect = m_layout_tree->create_rect(); rect->x = x; rect->y = y; rect->width = EIMG_ICONSIZE + 2; // +2 は枠の分 rect->height = EIMG_ICONSIZE + 2; // +2 は枠の分 // 既に表示済みの場合 DBIMG::Img* img = node->linkinfo->img; if( !img && init_popupwin ) img = node->linkinfo->img = DBIMG::get_img( layout->link ); if( img && img->is_cached() ){ rect->width = img->get_width_emb() + 2; // +2 は枠の分 rect->height = img->get_height_emb() + 2; // +2 は枠の分 } #ifdef _DEBUG std::cout << "x = " << rect->x << " y = " << rect->y << " w = " << rect->width << " h = " << rect->height << std::endl; #endif // wrap 処理用の閾値計算 // x が border よりも右に来たら wrap する LAYOUT* div = layout->div; int border = 0; if( div ) border = div->rect->x + div->rect->width - div->css->padding_right; else border = width_view; // wrap if( x + ( rect->width + EIMG_MRG ) >= border ){ x = 0; if( div ) x = div->rect->x + div->css->padding_left; y += br_size; br_size = m_font->br_size; // 次の行の改行位置をリセット rect->x = x; rect->y = y; } x += rect->width + mrg_right; if( br_size < rect->height + mrg_bottom ){ br_size = rect->height + mrg_bottom; } } // // 文字列の全角/半角モードの初期値を決定する関数 // bool DrawAreaBase::set_init_wide_mode( const char* str, const int pos_start, const int pos_to ) { if( ! m_strict_of_char ) return false; bool wide_mode = true; int i = pos_start; while( i < pos_to ){ int byte_tmp; MISC::utf8toucs2( str + i, byte_tmp ); // 文字列に全角が含まれていたら全角モードで開始 if( byte_tmp != 1 ) break; // アルファベットが含まれていたら半角モードで開始 if( IS_ALPHABET( str[ i ] ) ){ wide_mode = false; break; } // 数字など、全てアルファベットと全角文字以外の文字で出来ていたら // 全角モードにする i += byte_tmp; } return wide_mode; } // // 一文字の幅を取得 // // utfstr : 入力文字 (UTF-8) // byte : 長さ(バイト) utfstr が ascii なら 1, UTF-8 なら 2 or 3 or 4 を入れて返す // pre_char : 前の文字( 前の文字が全角なら = 0 ) // wide_mode : 全角半角モード( アルファベット以外の文字ではモードにしたがって幅を変える ) // mode : fontid.h で定義されているフォントのID // int DrawAreaBase::get_width_of_one_char( const char* utfstr, int& byte, char& pre_char, bool& wide_mode, const int mode ) { int width = 0; int width_wide = 0; // キャッシュに無かったら幅を調べてキャッシュに登録 if( ! ARTICLE::get_width_of_char( utfstr, byte, pre_char, width, width_wide, mode ) ){ const std::string tmpchar( utfstr, byte ); #ifdef _DEBUG std::cout << "no cache [" << tmpchar << "] " << byte <<" byte "; if( pre_char == 0 ) std::cout << " p =[0] "; else if( pre_char > 0 ) std::cout << " p =[" << pre_char << "] "; #endif // 厳密な幅計算をしない場合 if( ! m_strict_of_char ){ m_pango_layout->set_text( tmpchar ); width = width_wide = m_pango_layout->get_logical_extents().get_width(); } // 厳密に幅計算する場合 else{ // 全角モードでの幅 if( ! width_wide ){ const std::string str_dummy( "ぁ" ); m_pango_layout->set_text( str_dummy + str_dummy ); int width_dummy = m_pango_layout->get_logical_extents().get_width() / 2; m_pango_layout->set_text( str_dummy + tmpchar ); width_wide = m_pango_layout->get_logical_extents().get_width() - width_dummy; if( byte != 1 ) width = width_wide; } // 半角モードでの幅 // 半角モードではひとつ前の文字によって幅が変わることに注意する if( ! width ){ std::string char_dummy( "a" ); if( pre_char && IS_ALPHABET( pre_char ) ) char_dummy[ 0 ] = pre_char; const std::string str_dummy( char_dummy + char_dummy ); m_pango_layout->set_text( str_dummy ); int width_dummy = m_pango_layout->get_logical_extents().get_width() / 2; const std::string str_tmp( char_dummy + tmpchar ); m_pango_layout->set_text( str_tmp ); width = m_pango_layout->get_logical_extents().get_width() - width_dummy; #ifdef _DEBUG std::cout << " dummy = " << str_dummy << " dw = " << PANGO_PIXELS( width_dummy ); std::cout << " str = " << str_tmp << " dw = " << PANGO_PIXELS( m_pango_layout->get_logical_extents().get_width() ); #endif } } #ifdef _DEBUG std::cout << " w = " << PANGO_PIXELS( width ) << " wide = " << PANGO_PIXELS( width_wide ) << std::endl; #endif // フォントが無い if( width_wide <= 0 ){ int byte_tmp; const unsigned int code = MISC::utf8toucs2( tmpchar.c_str(), byte_tmp ); std::stringstream ss_err; ss_err << "unknown font byte = " << byte_tmp << " ucs2 = " << code << " width = " << width; #ifdef _DEBUG std::cout << "DrawAreaBase::get_width_of_one_char " << "byte = " << byte << " byte_tmp = " << byte_tmp << " code = " << code << " [" << tmpchar << "]\n"; #endif MISC::ERRMSG( ss_err.str() ); ARTICLE::set_width_of_char( utfstr, byte, pre_char, -1, -1, mode ); width = width_wide = 0; } else ARTICLE::set_width_of_char( utfstr, byte, pre_char, width, width_wide, mode ); } int ret = 0; // 厳密に計算しない場合 if( ! m_strict_of_char ) ret = width_wide; else{ // 全角文字 if( byte != 1 ){ ret = width_wide; pre_char = 0; wide_mode = true; } // 半角文字 else{ ret = width; pre_char = utfstr[ 0 ]; // アルファベットならモードを半角モードに変更 if( IS_ALPHABET( utfstr[ 0 ] ) ) wide_mode = false; // アルファベット以外の文字では現在の全角/半角モードに // したがって幅を変える。モードは変更しない else if( wide_mode ) ret = width_wide; } } return ret; } // // スクリーン描画 // // y から height の高さ分だけ描画する // height == 0 ならスクロールした分だけ描画( y は無視 ) // bool DrawAreaBase::draw_screen( const int y, const int height ) { if( ! m_enable_draw ) return false; if( ! m_backscreen ) return false; if( ! m_layout_tree ) return false; if( ! m_layout_tree->top_header() ) return false; if( ! m_window ) return false; if( m_view.get_height() < LAYOUT_MIN_HEIGHT ) return false; // まだ画面に表示されていない // スクロールしていない if( ! height && m_pre_pos_y != -1 ){ const int pos_y = get_vscr_val(); const int dy = pos_y - m_pre_pos_y; if( ! dy ) return false; } // キューに expose イベントが溜まっている時は全画面再描画 auto* const updated = gdk_window_get_update_area( m_window->gobj() ); if( updated ) { cairo_region_destroy( updated ); redraw_view_force(); return true; } #ifdef _DEBUG std::cout << "DrawAreaBase::draw_screen " << " y = " << y << " height " << height << std::endl; #endif m_drawinfo.draw = true; m_drawinfo.y = y; m_drawinfo.height = height; // expose イベント経由で exec_draw_screen() を呼び出す // gtk2.18以降は expose イベント内で描画処理しないと正しく描画されない様なので注意 m_view.queue_draw(); return true; } void DrawAreaBase::exec_draw_screen( const int y_redraw, const int height_redraw ) { const int width_view = m_view.get_width(); const int height_view = m_view.get_height(); const int pos_y = get_vscr_val(); // 全画面再描画 // 画面上の描画開始領域のy座標と高さ constexpr int y_screen = 0; const int height_screen = height_view; // 描画範囲の上限、下限 const int upper = pos_y; const int lower = pos_y + height_screen; #ifdef _DEBUG std::cout << "DrawAreaBase::exec_draw_screen " << " y_redraw = " << y_redraw << " height_redraw " << height_redraw << std::endl << "pos_y = " << pos_y << " y_screen = " << y_screen << " h_screen = " << height_screen << " upper = " << upper << " lower = " << lower << " scrollmode = " << m_scrollinfo.mode << " url = " << m_url << std::endl; #endif m_pre_pos_y = pos_y; m_rect_backscreen.x = 0; m_rect_backscreen.y = y_screen; m_rect_backscreen.width = width_view; m_rect_backscreen.height = height_screen; // 一番最後のレスが半分以上表示されていたら最後のレス番号をm_seen_currentにセット m_seen_current = 0; const int max_res_number = m_layout_tree->max_res_number(); int num = max_res_number; const LAYOUT* lastheader = m_layout_tree->get_header_of_res_const( num ); // あぼーんなどで表示されていないときは前のレスを調べる if( !lastheader ){ while( ! m_layout_tree->get_header_of_res_const( num ) && num-- > 0 ); lastheader = m_layout_tree->get_header_of_res_const( num ); } if( lastheader && lastheader->rect && lastheader->rect->y + lastheader->rect->height/2 < pos_y + height_view ){ m_seen_current = m_layout_tree->max_res_number(); } const auto tv_before = Monotonic::now(); // 2分探索で画面に表示されているノードの先頭を探す LAYOUT* header = nullptr; int top = 1; int back = max_res_number; while( top <= back ){ const int pivot = ( top + back )/2; header = m_layout_tree->get_header_of_res( pivot ); if( ! header ) break; /* std::cout << "top = " << top << " back = " << back << " pivot = " << pivot << " pos_y = " << pos_y << " y = " << header->rect->y << " y + height = " << header->rect->y + header->rect->height << std::endl; */ if( header->next_header ){ if( header->rect->y <= pos_y && header->next_header->rect->y >= pos_y ) break; } else{ // 最後のレス if( header->rect->y <= pos_y ) break; } if( header->rect->y > pos_y ) back = pivot -1; else top = pivot + 1; header = nullptr; } if( ! header ){ #ifdef _DEBUG std::cout << "not found\n"; #endif header = m_layout_tree->top_header(); } // バックスクリーンの背景クリア fill_backscreen( get_colorid_back(), 0, y_screen, width_view, height_screen ); // 描画ループ CLIPINFO ci = { width_view, pos_y, upper, lower }; // 描画領域 bool relayout = false; while( header && header->rect->y < pos_y + height_view ){ // フォント設定 set_node_font( header ); // 現在みているレス番号取得 if( ! m_seen_current ){ if( ! header->next_header // 最後のレス || ( header->next_header->rect->y >= ( pos_y + m_font->br_size ) // 改行分下にずらす && header->rect->y <= ( pos_y + m_font->br_size ) ) ){ m_seen_current = header->res_number; } } // ヘッダが描画範囲に含まれてるなら描画 if( header->rect->y + header->rect->height > upper && header->rect->y < lower ){ // ノードが描画範囲に含まれてるなら描画 LAYOUT* layout = header; while ( layout ){ // フォント設定 set_node_font( layout ); RECTANGLE* rect = layout->rect; while( rect ){ if( rect->y + rect->height > upper && rect->y < lower ){ if( draw_one_node( layout, ci ) ) relayout = true; break; } rect = rect->next_rect; } layout = layout->next_layout; } } header = header->next_header; } // 処理落ちが起きていないかチェックする // exec_scroll()を参照せよ if( m_wait_scroll == Monotonic::duration::zero() ) { const auto tv_after = Monotonic::now(); const auto passed = tv_after - tv_before; if( passed > std::chrono::milliseconds{ TIMER_TIMEOUT } ) { m_scroll_time = tv_after; m_wait_scroll = std::chrono::milliseconds{ TIMER_TIMEOUT }; #ifdef _DEBUG std::cout << "DrawAreaBase::draw_screen_core : passed = " << passed.count() << " wait = " << m_wait_scroll.count() << std::endl; #endif } } // 再レイアウト & 再描画 if( relayout ){ #ifdef _DEBUG std::cout << "relayout\n"; #endif if( exec_layout() ){ redraw_view_force(); return; } } // 前回描画したオートスクロールマーカを消す if( m_ready_back_marker ){ m_ready_back_marker = false; cairo_save( m_cr.get() ); cairo_rectangle( m_cr.get(), m_clip_marker.x, m_clip_marker.y, m_clip_marker.width, m_clip_marker.height ); cairo_clip( m_cr.get() ); cairo_set_source_surface( m_cr.get(), m_back_marker.get(), m_clip_marker.x, m_clip_marker.y ); cairo_paint( m_cr.get() ); cairo_restore( m_cr.get() ); } // 前回描画したフレームを消す if( m_ready_back_frame ){ m_ready_back_frame = false; cairo_save( m_cr.get() ); cairo_rectangle( m_cr.get(), 0.0, 0.0, width_view, height_view ); cairo_clip( m_cr.get() ); cairo_set_source_surface( m_cr.get(), m_back_frame_top.get(), 0.0, 0.0 ); cairo_paint( m_cr.get() ); cairo_set_source_surface( m_cr.get(), m_back_frame_bottom.get(), 0.0, height_view - WIDTH_FRAME ); cairo_paint( m_cr.get() ); cairo_restore( m_cr.get() ); } // バックスクリーンをウィンドウにコピー cairo_save( m_cr.get() ); cairo_rectangle( m_cr.get(), 0.0, y_screen, width_view, height_screen ); cairo_clip( m_cr.get() ); cairo_set_source_surface( m_cr.get(), m_backscreen.get(), 0.0, 0.0 ); cairo_paint( m_cr.get() ); cairo_restore( m_cr.get() ); // オートスクロールマーカと枠の描画 draw_marker(); draw_frame(); } // // ノードひとつを描画する関数 // // width_view : 描画領域の幅 // pos_y : 描画領域の開始座標 // // 戻り値 : true なら描画後に再レイアウトを実行する // bool DrawAreaBase::draw_one_node( LAYOUT* layout, const CLIPINFO& ci ) { bool relayout = false; if( ! m_article ) return relayout; // ノード種類別の処理 switch( layout->type ){ // div case DBTREE::NODE_DIV: draw_div( layout, ci ); break; ////////////////////////////////////////// // リンクノード case DBTREE::NODE_LINK: // 画像リンクの場合、実際にリンクが表示される段階でノードツリーに DBIMG::Img // のポインタと色をセットする。 // // 結合度が激しく高くなるがスピードを重視 // if( layout->node && layout->node->linkinfo->image ){ DBTREE::NODE* node = layout->node; // 画像クラスのポインタ取得してノードツリーにセット if( ! node->linkinfo->img ){ node->linkinfo->img = DBIMG::get_img( layout->node->linkinfo->link ); } // 画像クラスが取得されてたら色を指定 DBIMG::Img* img = node->linkinfo->img; if( img ){ if( img->is_loading() ) node->color_text = COLOR_IMG_LOADING; else if( img->is_wait() ) node->color_text = COLOR_IMG_LOADING; else if( img->get_code() == HTTP_OK ) node->color_text = COLOR_IMG_CACHED; else if( img->get_code() == HTTP_INIT ){ if( img->get_abone() ) node->color_text = COLOR_IMG_ERR; else node->color_text = COLOR_IMG_NOCACHE; } else node->color_text = COLOR_IMG_ERR; } } // fallthrough ////////////////////////////////////////// // テキストノード case DBTREE::NODE_TEXT: draw_one_text_node( layout, ci ); break; ////////////////////////////////////////// // 発言回数ノード case DBTREE::NODE_IDNUM: if( set_num_id( layout ) ) draw_one_text_node( layout, ci ); break; ////////////////////////////////////////// // ヘッダ case DBTREE::NODE_HEADER: draw_div( layout, ci ); if( layout->res_number ){ const int y_org = layout->rect->y + layout->css->padding_top; int y = y_org; // ブックマークのマーク描画 if( m_article->is_bookmarked( layout->res_number ) ){ if( ! m_pixbuf_bkmk ) m_pixbuf_bkmk = ICON::get_icon( ICON::BKMARK_THREAD ); const int height_bkmk = m_pixbuf_bkmk->get_height(); y += ( m_font->height - height_bkmk ) / 2; const int s_top = MAX( 0, ci.upper - y ); const int s_bottom = MIN( height_bkmk, ci.lower - y ); const int height = s_bottom - s_top; if( height > 0 ) { paint_backscreen( m_pixbuf_bkmk, 0, s_top, 1, y - ci.pos_y + s_top, m_pixbuf_bkmk->get_width(), height ); } y += height_bkmk; } if( CONFIG::get_show_post_mark() ){ // 書き込みのマーク表示 if( m_article->is_posted( layout->res_number ) ){ if( ! m_pixbuf_post ) m_pixbuf_post = ICON::get_icon( ICON::POST ); const int height_post = m_pixbuf_post->get_height(); if( y == y_org ) y += ( m_font->height - height_post ) / 2; const int s_top = MAX( 0, ci.upper - y ); const int s_bottom = MIN( height_post, ci.lower - y ); const int height = s_bottom - s_top; if( height > 0 ) { paint_backscreen( m_pixbuf_post, 0, s_top, 1, y - ci.pos_y + s_top, m_pixbuf_post->get_width(), height ); } y += height_post; } // 自分の書き込みに対するレスのマーク表示 if( m_article->is_refer_posted( layout->res_number ) ){ if( ! m_pixbuf_refer_post ) m_pixbuf_refer_post = ICON::get_icon( ICON::POST_REFER ); const int height_refer_post = m_pixbuf_refer_post->get_height(); if( y == y_org ) y += ( m_font->height - height_refer_post ) / 2; const int s_top = MAX( 0, ci.upper - y ); const int s_bottom = MIN( height_refer_post, ci.lower - y ); const int height = s_bottom - s_top; if( height > 0 ) { paint_backscreen( m_pixbuf_refer_post, 0, s_top, 1, y - ci.pos_y + s_top, m_pixbuf_refer_post->get_width(), height ); } } } } break; ////////////////////////////////////////// // 画像ノード case DBTREE::NODE_IMG: case DBTREE::NODE_SSSP: if( draw_one_img_node( layout, ci ) ) relayout = true; break; ////////////////////////////////////////// // 水平線ノード case DBTREE::NODE_HR: if( layout->rect ){ const int x = layout->rect->x; const int y = layout->rect->y - ci.pos_y; const int color_text = get_colorid_text(); cairo_t* const cr = cairo_create( m_backscreen.get() ); gdk_cairo_set_source_rgba( cr, m_color[ color_text ].gobj() ); cairo_set_line_width( cr, 1.0 ); cairo_move_to( cr, x, y ); cairo_line_to( cr, x + layout->rect->width - 1.0, y ); cairo_stroke( cr ); cairo_destroy( cr ); } break; ////////////////////////////////////////// // ノードが増えたらここに追加していくこと default: break; } return relayout; } // // div 要素の描画 // void DrawAreaBase::draw_div( LAYOUT* layout_div, const CLIPINFO& ci ) { if( ! ci.lower ) return; int bg_color = layout_div->css->bg_color; int border_left_color = layout_div->css->border_left_color; int border_right_color = layout_div->css->border_right_color; int border_top_color = layout_div->css->border_top_color; int border_bottom_color = layout_div->css->border_bottom_color; if( bg_color < 0 && border_left_color < 0 && border_top_color < 0 && border_bottom_color < 0 ) return; int border_left = layout_div->css->border_left_width; int border_right = layout_div->css->border_right_width; int border_top = layout_div->css->border_top_width;; int border_bottom = layout_div->css->border_bottom_width;; int border_style = layout_div->css->border_style; int y_div = layout_div->rect->y; int height_div = layout_div->rect->height; if( y_div < ci.upper ){ if( border_top && y_div + border_top > ci.upper ) border_top -= ( ci.upper - y_div ); else border_top = 0; height_div -= ( ci.upper - y_div ); y_div = ci.upper; } if( y_div + height_div > ci.lower ){ if( border_bottom && y_div + height_div - border_bottom < ci.lower ) border_bottom -= ( y_div + height_div - ci.lower ); else border_bottom = 0; height_div = ( ci.lower - y_div ); } // 背景 if( bg_color >= 0 ){ fill_backscreen( bg_color, layout_div->rect->x, y_div - ci.pos_y, layout_div->rect->width, height_div ); } // left if( border_style == CORE::BORDER_SOLID && border_left_color >= 0 && border_left ){ fill_backscreen( border_left_color, layout_div->rect->x, y_div - ci.pos_y, border_left, height_div ); } // right if( border_style == CORE::BORDER_SOLID && border_right_color >= 0 && border_right ){ fill_backscreen( border_right_color, layout_div->rect->x + layout_div->rect->width - border_right, y_div - ci.pos_y, border_right, height_div ); } // top if( border_style == CORE::BORDER_SOLID && border_top_color >= 0 && border_top ){ fill_backscreen( border_top_color, layout_div->rect->x, y_div - ci.pos_y, layout_div->rect->width, border_top ); } // bottom if( border_style == CORE::BORDER_SOLID && border_bottom_color >= 0 && border_bottom ){ fill_backscreen( border_bottom_color, layout_div->rect->x, y_div + height_div - border_bottom - ci.pos_y, layout_div->rect->width, border_bottom ); } } // // オートスクロールマーカの描画 // void DrawAreaBase::draw_marker() { if( m_scrollinfo.mode == SCROLL_NOT ) return; if( ! m_scrollinfo.show_marker ) return; const int width_view = m_view.get_width(); const int height_view = m_view.get_height(); const int x_marker = m_scrollinfo.x - AUTOSCR_CIRCLE/2; const int y_marker = m_scrollinfo.y - AUTOSCR_CIRCLE/2; m_clip_marker.x = x_marker; m_clip_marker.y = y_marker; m_clip_marker.width = AUTOSCR_CIRCLE; m_clip_marker.height = AUTOSCR_CIRCLE; if( m_clip_marker.x < 0 ){ m_clip_marker.width += m_clip_marker.x; m_clip_marker.x = 0; } if( m_clip_marker.x + m_clip_marker.width > width_view ){ m_clip_marker.width = width_view - m_clip_marker.x; } if( m_clip_marker.y < 0 ){ m_clip_marker.height += m_clip_marker.y; m_clip_marker.y = 0; } if( m_clip_marker.y + m_clip_marker.height > height_view ){ m_clip_marker.height = height_view - m_clip_marker.y; } // オートスクロールマーカを描く前に背景のバックアップを取っておきスクロールする前に描き直す // exec_draw_screen() 参照 { cairo_t* const cr = cairo_create( m_back_marker.get() ); cairo_rectangle( cr, 0.0, 0.0, m_clip_marker.width, m_clip_marker.height ); cairo_clip( cr ); cairo_set_source_surface( cr, cairo_get_target( m_cr.get() ), -m_clip_marker.x, -m_clip_marker.y ); cairo_paint( cr ); cairo_destroy( cr ); m_ready_back_marker = true; } constexpr const double r = AUTOSCR_CIRCLE / 2.0; cairo_save( m_cr.get() ); cairo_rectangle( m_cr.get(), m_clip_marker.x, m_clip_marker.y, m_clip_marker.width, m_clip_marker.height ); cairo_clip( m_cr.get() ); gdk_cairo_set_source_rgba( m_cr.get(), m_color[ COLOR_MARKER ].gobj() ); cairo_arc( m_cr.get(), x_marker + r, y_marker + r, r - 1.0, 0.0, 2.0 * M_PI ); cairo_stroke( m_cr.get() ); cairo_restore( m_cr.get() ); } // // 枠の描画 // void DrawAreaBase::draw_frame() { if( ! m_draw_frame ) return; const int width_win = m_view.get_width(); const int height_win = m_view.get_height(); cairo_surface_t* const borrowed_sf = cairo_get_target( m_cr.get() ); cairo_t* cr = cairo_create( m_back_frame_top.get() ); cairo_rectangle( cr, 0.0, 0.0, width_win, WIDTH_FRAME ); cairo_clip( cr ); cairo_set_source_surface( cr, borrowed_sf, 0.0, 0.0 ); cairo_paint( cr ); cairo_destroy( cr ); cr = cairo_create( m_back_frame_bottom.get() ); cairo_rectangle( cr, 0.0, 0.0, width_win, WIDTH_FRAME ); cairo_clip( cr ); cairo_set_source_surface( cr, borrowed_sf, 0.0, WIDTH_FRAME - height_win ); cairo_paint( cr ); cairo_destroy( cr ); m_ready_back_frame = true; cairo_save( m_cr.get() ); gdk_cairo_set_source_rgba( m_cr.get(), m_color[ COLOR_FRAME ].gobj() ); cairo_set_line_width( m_cr.get(), 2.0 ); cairo_rectangle( m_cr.get(), 0.0, 0.0, width_win, height_win ); cairo_stroke( m_cr.get() ); cairo_restore( m_cr.get() ); } // // バックスクリーンを矩形で塗りつぶす // void DrawAreaBase::fill_backscreen( const int colorid, int x, int y, int width, int height ) { cairo_t* const cr = cairo_create( m_backscreen.get() ); gdk_cairo_set_source_rgba( cr, m_color[ colorid ].gobj() ); cairo_rectangle( cr, x, y, width, height ); cairo_fill( cr ); cairo_destroy( cr ); } // // Pixbufの内容をバックスクリーンに貼り付ける // void DrawAreaBase::paint_backscreen( const Glib::RefPtr< Gdk::Pixbuf >& pixbuf, int src_x, int src_y, int dest_x, int dest_y, int width, int height ) { // Cairoバージョンではsrc_x, src_yを使わない // 呼び出しをgdkバージョンと揃えるために引数の数合わせをしている static_cast< void >( src_x ); static_cast< void >( src_y ); cairo_t* const cr = cairo_create( m_backscreen.get() ); cairo_rectangle( cr, dest_x, dest_y, width, height ); cairo_clip( cr ); gdk_cairo_set_source_pixbuf( cr, pixbuf->gobj(), dest_x, dest_y ); cairo_paint( cr ); cairo_destroy( cr ); } // // 範囲選択の描画をする必要があるかどうかの判定( draw_one_text_node()で使用 ) // // 戻り値: 描画が必要かとどうか // byte_from : 描画開始位置 // byte_to : 描画終了位置 // bool DrawAreaBase::get_selection_byte( const LAYOUT* layout, const SELECTION& selection, size_t& byte_from, size_t& byte_to ) const { if( ! layout ) return false; if( ! selection.caret_from.layout ) return false; if( ! selection.caret_to.layout ) return false; const int id_header = layout->id_header; const int id = layout->id ; int id_header_from = 0; int id_from = 0; int id_header_to = 0; int id_to = 0; id_header_from = selection.caret_from.layout->id_header; id_from = selection.caret_from.layout->id; id_header_to = selection.caret_to.layout->id_header; id_to = selection.caret_to.layout->id; // 選択開始ノードの場合は selection.caret_from.byte から、それ以外は0バイト目から描画 byte_from = selection.caret_from.byte * ( id_header == id_header_from && id == id_from ); // 選択終了ノードの場合は selection.caret_to.byte から、それ以外は最後まで描画 byte_to = selection.caret_to.byte * ( id_header == id_header_to && id == id_to ); if( byte_to == 0 ) byte_to = strlen( layout->text ); if( byte_from == byte_to // このノードは範囲選択外なので範囲選択の描画をしない || ( id_header < id_header_from ) || ( id_header > id_header_to ) || ( id_header == id_header_from && id < id_from ) || ( id_header == id_header_to && id > id_to ) // キャレットが先頭にあるので範囲選択の描画をしない || ( id_header == id_header_to && id == id_to && selection.caret_to.byte == 0 ) ){ return false; } return true; } // // ノードで使うフォントを得る // char DrawAreaBase::get_layout_fontid( LAYOUT* layout ) const { if( ! layout->node ) return m_fontid; switch( layout->node->fontid ){ case FONT_AA: if( m_aafont_initialized ){ return layout->node->fontid; // AA用フォント情報 } else { return m_defaultfontid; // デフォルトフォント情報 } break; case FONT_MAIL: if( m_mailfont_initialized ) { return layout->node->fontid; // メール欄などのフォント情報 } else { return m_defaultfontid; // デフォルトフォント情報 } break; case FONT_EMPTY: // フォントID未決定 case FONT_DEFAULT: default: return m_defaultfontid; // デフォルトフォント情報 break; } return m_defaultfontid; // デフォルトフォント情報 } // // ノードのフォント設定 // void DrawAreaBase::set_node_font( LAYOUT* layout ) { if( ! layout->node ) return; // フォント設定 char layout_fontid = get_layout_fontid( layout ); if( m_fontid != layout_fontid ){ m_fontid = layout_fontid; // 新しいフォントIDをセット switch( m_fontid ){ case FONT_AA: m_font = &m_aafont; break; case FONT_MAIL: m_font = &m_mailfont; break; default: m_font = &m_defaultfont; break; } #if 0 // _DEBUG std::cout << "DrawAreaBase::set_node_font : fontid = " << (int)layout_fontid << " res = " << layout->header->res_number << " type = " << (int)(layout->node->type) << std::endl; #endif // layoutにフォントをセット m_pango_layout->set_font_description( m_font->pfd ); m_context->set_font_description( m_font->pfd ); } } // // テキストの含まれているノードひとつを描画する関数 // // width_view : 描画領域の幅 // pos_y : 描画領域の開始座標 // void DrawAreaBase::draw_one_text_node( LAYOUT* layout, const CLIPINFO& ci ) { // 範囲選択の描画をする必要があるかどうかの判定 // 二度書きすると重いので、ちょっと式は複雑になるけど同じ所を二度書きしないようにする size_t byte_from = 0; size_t byte_to = 0; const bool draw_selection = ( m_selection.select && get_selection_byte( layout, m_selection, byte_from, byte_to ) ); int color_text = get_colorid_text(); if( layout->color_text && *layout->color_text != COLOR_CHAR ) color_text = *layout->color_text; if( color_text == COLOR_CHAR && layout->div && layout->div->css->color >= 0 ) color_text = layout->div->css->color; int color_back = get_colorid_back(); if( layout->div && layout->div->css->bg_color >= 0 ) color_back = layout->div->css->bg_color; else if( layout->header && layout->header->css->bg_color >= 0 ) color_back = layout->header->css->bg_color; // 通常描画 if( ! draw_selection ) draw_string( layout, ci, color_text, color_back, 0, 0 ); else { // 範囲選択の前後描画 // 前 if( byte_from ) draw_string( layout, ci, color_text, color_back, 0, byte_from ); // 後 if( byte_to != strlen( layout->text ) ) draw_string( layout, ci, color_text, color_back, byte_to, strlen( layout->text ) ); } // 検索結果のハイライト if( m_multi_selection.size() > 0 ){ for( const SELECTION& selection : m_multi_selection ) { size_t byte_from2; size_t byte_to2; if( get_selection_byte( layout, selection, byte_from2, byte_to2 )){ draw_string( layout, ci, COLOR_CHAR_HIGHLIGHT, COLOR_BACK_HIGHLIGHT, byte_from2, byte_to2 ); } } } // 範囲選択部分を描画 if( draw_selection && byte_from != byte_to ){ draw_string( layout, ci, COLOR_CHAR_SELECTION, COLOR_BACK_SELECTION, byte_from, byte_to ); } } // // 画像ノードひとつを描画する関数 // // width_view : 描画領域の幅 // pos_y : 描画領域の開始座標 // // 戻り値 : true なら描画後に再レイアウトを実行する // bool DrawAreaBase::draw_one_img_node( LAYOUT* layout, const CLIPINFO& ci ) { #ifdef _DEBUG std::cout << "DrawAreaBase::draw_one_img_node link = " << layout->link << std::endl; #endif bool relayout = false; if( ! layout->link ) return relayout; const RECTANGLE* rect = layout->rect; if( ! rect ) return relayout; const DBTREE::NODE* node = layout->node; if( ! node ) return relayout; DBIMG::Img* img = node->linkinfo->img; if( ! img ){ img = node->linkinfo->img = DBIMG::get_img( layout->link ); if( ! img ) return relayout; } int color = COLOR_IMG_ERR; const int code = img->get_code(); // 画像が削除された場合、埋め込み画像を削除してノードの座標を再計算 if( layout->eimg && ( img->is_loading() || code == HTTP_INIT ) ){ // 削除対象はアドレスで判定する m_eimgs.remove_if( [p = layout->eimg]( auto& e ) { return p == std::addressof( e ); } ); layout->eimg = nullptr; relayout = true; } if( img->is_loading() || img->is_wait() ) color = COLOR_IMG_LOADING; else if( code == HTTP_INIT ){ if( img->get_abone() ) color = COLOR_IMG_ERR; else color = COLOR_IMG_NOCACHE; } // 画像描画 else if( code == HTTP_OK ){ color = COLOR_IMG_CACHED; // 埋め込み画像を作成 if( ! layout->eimg ){ // EmbeddedImageのリストはDrawAreaBase::clear()でリソースを解放する // NOTE: 挿入削除で参照が無効にならないコンテナが条件 m_eimgs.emplace_back( layout->link ); layout->eimg = std::addressof( m_eimgs.back() ); layout->eimg->show(); // 後のノードの座標を再計算 relayout = true; } // 描画 else{ Glib::RefPtr< Gdk::Pixbuf > pixbuf = layout->eimg->get_pixbuf(); if( pixbuf ){ const int s_top = MAX( 0, ci.upper - ( rect->y + 1 ) ); const int s_bottom = MIN( pixbuf->get_height(), ci.lower - ( rect->y + 1 ) ); const int height = s_bottom - s_top; // モザイク if( img->get_mosaic() ){ const int moswidth = img->get_width_mosaic(); const int mosheight = img->get_height_mosaic(); if( moswidth && mosheight ){ Glib::RefPtr< Gdk::Pixbuf > pixbuf2; pixbuf2 = pixbuf->scale_simple( moswidth, mosheight, Gdk::INTERP_NEAREST ); paint_backscreen( pixbuf2->scale_simple( pixbuf->get_width(), pixbuf->get_height(), Gdk::INTERP_NEAREST ), 0, s_top, rect->x + 1, ( rect->y + 1 ) - ci.pos_y + s_top, pixbuf->get_width(), height ); } } // 通常 else{ paint_backscreen( pixbuf, 0, s_top, rect->x + 1, ( rect->y + 1 ) - ci.pos_y + s_top, pixbuf->get_width(), height ); } } else color = COLOR_IMG_ERR; } } // 枠の描画 { cairo_t* const cr = cairo_create( m_backscreen.get() ); gdk_cairo_set_source_rgba( cr, m_color[ color ].gobj() ); cairo_rectangle( cr, rect->x, rect->y - ci.pos_y, rect->width, rect->height ); cairo_stroke( cr ); cairo_destroy( cr ); } // 右上のアイコン if( code != HTTP_OK || img->is_loading() ){ const int x_tmp = rect->x + rect->width / 10 + 1; const int y_tmp = rect->y + rect->height / 10 + 1; const int width_tmp = rect->width / 4; const int height_tmp = rect->width / 4; fill_backscreen( color, x_tmp, y_tmp - ci.pos_y, width_tmp, height_tmp ); } #ifdef _DEBUG std::cout << "code = " << code << " relayout = " << relayout << std::endl; #endif return relayout; } // // 文字を描画する関数 // // ノードの byte_from バイト目の文字から byte_to バイト目の「ひとつ前」の文字まで描画 // byte_to が 0 なら最後まで描画 // // たとえば node->text = "abcdefg" で byte_from = 1, byte_to = 3 なら "bc" を描画 // void DrawAreaBase::draw_string( LAYOUT* node, const CLIPINFO& ci, const int color, const int color_back, const int byte_from, const int byte_to ) { assert( node->text != nullptr ); assert( m_layout_tree ); if( ! node->lng_text ) return; if( byte_from >= node->lng_text ) return; RECTANGLE* rect = node->rect; while( rect ){ // 描画領域のチェック if( rect->y + rect->height < ci.upper || rect->y > ci.lower ){ if( rect->end ) break; rect = rect->next_rect; continue; } int x = rect->x; const int y = rect->y - ci.pos_y; int width_line = rect->width; int pos_start = rect->pos_start; int n_byte = rect->n_byte; int n_ustr = rect->n_ustr; // 描画する位置が指定されている場合 if( byte_to && ! ( byte_from <= pos_start && pos_start + n_byte <= byte_to ) ){ if( pos_start > byte_to || pos_start + n_byte < byte_from ) width_line = 0; // 指定範囲外は描画しない else{ // この文字列の全角/半角モードの初期値を決定 bool wide_mode = set_init_wide_mode( node->text, pos_start, pos_start + n_byte ); // 左座標計算 char pre_char = 0; int byte_char; while( pos_start < byte_from ){ x += PANGO_PIXELS( get_width_of_one_char( node->text + pos_start, byte_char, pre_char, wide_mode, get_layout_fontid( node ) ) ); pos_start += byte_char; } // 幅とバイト数計算 int pos_to = pos_start; int byte_to_tmp = byte_to; if( rect->pos_start + n_byte < byte_to_tmp ) byte_to_tmp = rect->pos_start + n_byte; width_line = 0; n_byte = 0; n_ustr = 0; while( pos_to < byte_to_tmp ){ width_line += get_width_of_one_char( node->text + pos_to, byte_char, pre_char, wide_mode, get_layout_fontid( node ) ); pos_to += byte_char; n_byte += byte_char; ++n_ustr; } width_line = PANGO_PIXELS( width_line ); } } if( width_line ){ const int xx = x; #ifdef USE_PANGOLAYOUT // Pango::Layout を使って文字を描画 const Gdk::RGBA& fg = m_color[ color ]; const Gdk::RGBA& bg = m_color[ color_back ]; using PA = Pango::Attribute; auto foreground = PA::create_attr_foreground( fg.get_red_u(), fg.get_green_u(), fg.get_blue_u() ); auto background = PA::create_attr_background( bg.get_red_u(), bg.get_green_u(), bg.get_blue_u() ); m_pango_layout->set_text( Glib::ustring( node->text + pos_start, n_ustr ) ); cairo_t* const text_cr = cairo_create( m_backscreen.get() ); Pango::AttrList attr; attr.insert( foreground ); attr.insert( background ); m_pango_layout->set_attributes( attr ); cairo_move_to( text_cr, x, y ); pango_cairo_show_layout( text_cr, m_pango_layout->gobj() ); // Pango::Weight属性を使うと文字幅が変わりレイアウトが乱れる // そこで文字を重ねて描画することで太字を表示する if( node->bold ) { Pango::AttrList overlapping; overlapping.insert( foreground ); m_pango_layout->set_attributes( overlapping ); cairo_move_to( text_cr, x + 1.0, y ); pango_cairo_show_layout( text_cr, m_pango_layout->gobj() ); } #else // Pango::GlyphString を使って文字を描画 assert( m_context ); fill_backscreen( color_back, x, y, width_line, m_font->height ); cairo_t* const text_cr = cairo_create( m_backscreen.get() ); gdk_cairo_set_source_rgba( text_cr, m_color[ color ].gobj() ); Pango::AttrList attr; const auto text = Glib::ustring( node->text + pos_start, n_ustr ); const std::vector list_item = m_context->itemize( text, attr ); Glib::RefPtr< Pango::Font > font; Pango::GlyphString grl; for( const Pango::Item& item : list_item ) { font = item.get_analysis().get_font(); grl = item.shape( item.get_segment( text ) ); const int width = PANGO_PIXELS( grl.get_width() ); cairo_move_to( text_cr, x, y + m_font->ascent ); pango_cairo_show_glyph_string( text_cr, font->gobj(), grl.gobj() ); if( node->bold ) { cairo_move_to( text_cr, x + 1.0, y + m_font->ascent ); pango_cairo_show_glyph_string( text_cr, font->gobj(), grl.gobj() ); } x += width; } // 実際のラインの長さ(x - rect->x)とlayout_one_text_node()で計算した // 近似値(rect->width)を一致させる ( 応急処置 ) if( ! byte_to && abs( ( x - rect->x ) - rect->width ) > 2 ) rect->width = x - rect->x; #endif // USE_PANGOLAYOUT // リンクの時は下線を引く if( node->link && CONFIG::get_draw_underline() ){ gdk_cairo_set_source_rgba( text_cr, m_color[ color ].gobj() ); cairo_set_line_width( text_cr, 1.0 ); cairo_move_to( text_cr, xx, y + m_font->underline_pos ); cairo_line_to( text_cr, xx + width_line, y + m_font->underline_pos ); cairo_stroke( text_cr ); } cairo_destroy( text_cr ); } if( rect->end ) break; rect = rect->next_rect; } } // 整数 -> 文字変換してノードに発言数をセット // 最大4桁を想定 int DrawAreaBase::set_num_id( LAYOUT* layout ) { int pos = 0; int num_id = layout->header->node->headinfo->num_id_name; if( num_id >= 2 ){ const auto &tmp_str = std::string( " (" ) + std::to_string( num_id ) + ")" ; std::memcpy( layout->node->text, tmp_str.c_str(), tmp_str.size() + 1 ); pos = tmp_str.size(); layout->lng_text = pos ; } return pos; } // // スクロール方向指定 // // 実際にスクロールして描画を実行するのは exec_scroll() // bool DrawAreaBase::set_scroll( const int control ) { // スクロール系の操作でないときは関数を抜ける switch( control ){ case CONTROL::DownFast: case CONTROL::DownMid: case CONTROL::Down: case CONTROL::NextRes: case CONTROL::UpFast: case CONTROL::UpMid: case CONTROL::Up: case CONTROL::PrevRes: case CONTROL::Home: case CONTROL::End: case CONTROL::GotoNew: case CONTROL::Back: break; default: return false; } if( !m_vscrbar ){ m_scrollinfo.reset(); return true; } if( m_scrollinfo.mode == SCROLL_NOT ){ double dy = 0; const int y = get_vscr_val(); const bool enable_down = ( y < get_vscr_maxval() ); const bool enable_up = ( y > 0 ); switch( control ){ // 下 case CONTROL::DownFast: if( enable_down ) dy = SCROLLSPEED_FAST; break; case CONTROL::DownMid: if( enable_down ) dy = SCROLLSPEED_MID; break; case CONTROL::Down: if( enable_down ) dy = SCROLLSPEED_SLOW; break; case CONTROL::NextRes: if( enable_down ) goto_next_res(); break; // 上 case CONTROL::UpFast: if( enable_up ) dy = - SCROLLSPEED_FAST; break; case CONTROL::UpMid: if( enable_up )dy = - SCROLLSPEED_MID; break; case CONTROL::Up: if( enable_up )dy = - SCROLLSPEED_SLOW; break; case CONTROL::PrevRes: if( enable_up ) goto_pre_res(); break; // Home, End, New case CONTROL::Home: if( enable_up ) goto_top(); break; case CONTROL::End: if( enable_down ) goto_bottom(); break; case CONTROL::GotoNew: goto_new(); break; // ジャンプ元に戻る case CONTROL::Back: goto_back(); break; } if( dy ){ m_scrollinfo.reset(); m_scrollinfo.dy = ( int ) dy; // キーを押しっぱなしにしてる場合スクロールロックする if( m_key_locked ) m_scrollinfo.mode = SCROLL_LOCKED; // レスポンスを上げるため押した直後はすぐ描画 else{ m_scrollinfo.mode = SCROLL_NORMAL; exec_scroll(); } } } return true; } // // マウスホイールの処理 // void DrawAreaBase::wheelscroll( GdkEventScroll* event ) { const int speed = CONFIG::get_scroll_size(); const int time_cancel = 15; // msec if( !m_vscrbar ) return; // あまり速く動かしたならキャンセル const int time_tmp = event->time - m_wheel_scroll_time; if( ( ! m_wheel_scroll_time || time_tmp >= time_cancel ) && event->type == GDK_SCROLL ){ m_wheel_scroll_time = event->time; if( m_vscrbar && ( m_scrollinfo.mode == SCROLL_NOT || m_scrollinfo.mode == SCROLL_NORMAL ) ){ const auto adjust = m_vscrbar->get_adjustment(); const int current_y = ( int ) adjust->get_value(); if( event->direction == GDK_SCROLL_UP && current_y == 0 ) return; if( event->direction == GDK_SCROLL_DOWN && current_y == adjust->get_upper() - adjust->get_page_size() ) return; m_scrollinfo.reset(); m_scrollinfo.mode = SCROLL_NORMAL; // ホイールのスクロールの場合は必ずスクロール処理を実施する m_wait_scroll = Monotonic::duration::zero(); if( event->direction == GDK_SCROLL_UP ) m_scrollinfo.dy = -( int ) adjust->get_step_increment() * speed; else if( event->direction == GDK_SCROLL_DOWN ) m_scrollinfo.dy = ( int ) adjust->get_step_increment() * speed; else if( event->direction == GDK_SCROLL_SMOOTH ) { constexpr double smooth_scroll_factor = 4.0; m_smooth_dy += smooth_scroll_factor * event->delta_y; if( std::abs( m_smooth_dy ) > 1.0 ) { const double dy = adjust->get_step_increment() * speed; m_scrollinfo.dy = static_cast( std::copysign( dy, m_smooth_dy ) ); m_smooth_dy = 0.0; } } exec_scroll(); // スクロール終了直後にポインタがリンクの上にある時はマウスをある程度動かすまでモーションイベントをキャンセルする // slot_motion_notify_event() を参照 if( m_layout_current && m_layout_current->link ){ m_scrollinfo.counter_nomotion = COUNTER_NOMOTION; m_scrollinfo.x = m_x_pointer; m_scrollinfo.y = m_y_pointer; } } } } // // スクロールやジャンプを実行して再描画 // // clock_in()からクロック入力される度にスクロールする // void DrawAreaBase::exec_scroll() { if( ! m_layout_tree ) return; if( ! m_vscrbar ) return; if( m_scrollinfo.mode == SCROLL_NOT && ! m_scrollinfo.live ) return; // 描画で処理落ちしている時はスクロール処理をキャンセルする constexpr const auto zero = Monotonic::duration::zero(); if( m_wait_scroll != zero ) { const auto current = Monotonic::now(); if( current < m_scroll_time ) { m_wait_scroll = zero; } else { const auto passed = current - m_scroll_time; m_scroll_time = current; m_wait_scroll = std::max( zero, m_wait_scroll - passed ); if( m_wait_scroll != zero ) { #ifdef _DEBUG std::cout << "cancel scroll passed = " << passed.count() << " wait = " << m_wait_scroll.count() << std::endl; #endif return; } } } #ifdef _DEBUG std::cout << "exec scroll\n"; #endif bool redraw_all = false; CARET_POSITION caret_pos; bool selection = false; // 移動後のスクロール位置を計算 int y = 0; auto adjust = m_vscrbar->get_adjustment(); const int current_y = ( int ) adjust->get_value(); switch( m_scrollinfo.mode ){ case SCROLL_TO_NUM: // 指定したレス番号にジャンプ { #ifdef _DEBUG std::cout << "DrawAreaBase::exec_scroll : goto " << m_scrollinfo.res << std::endl; #endif const LAYOUT* layout = m_layout_tree->get_header_of_res_const( m_scrollinfo.res ); if( layout ) y = layout->rect->y; m_scrollinfo.reset(); } break; // 先頭に移動 case SCROLL_TO_TOP: y = 0; m_scrollinfo.reset(); redraw_all = true; break; // 最後に移動 case SCROLL_TO_BOTTOM: y = (int) adjust->get_upper(); m_scrollinfo.reset(); redraw_all = true; break; // y 座標に移動 case SCROLL_TO_Y: y = m_scrollinfo.y; m_scrollinfo.reset(); #ifdef _DEBUG std::cout << "DrawAreaBase::exec_scroll : y = " << y << std::endl; #endif break; case SCROLL_NORMAL: // 1 回だけスクロール y = current_y + m_scrollinfo.dy; m_scrollinfo.reset(); break; case SCROLL_LOCKED: // ロックが外れるまでスクロールを続ける y = current_y + m_scrollinfo.dy; break; case SCROLL_AUTO: // オートスクロールモード { // 現在のポインタの位置取得 int x_point, y_point; MISC::get_pointer_at_window( m_view.get_window(), x_point, y_point ); double dy = m_scrollinfo.y - y_point; if( dy >= 0 && ! m_scrollinfo.enable_up ) dy = 0; else if( dy < 0 && ! m_scrollinfo.enable_down ) dy = 0; else{ // この辺の式は経験的に決定 if( -AUTOSCR_CIRCLE/4 <= dy && dy <= AUTOSCR_CIRCLE/4 ) dy = 0; else { dy = std::copysign( std::fmin( std::expm1( ( std::fabs( dy ) - AUTOSCR_CIRCLE/4 ) /50 ) * 5, adjust->get_page_size() * 3 ), dy ); } if( m_drugging ) dy *= 4; // 範囲選択中ならスピード上げる } y = current_y -( int ) dy; // 範囲選択中ならキャレット移動して選択範囲更新 if( m_drugging ){ int y_tmp = MIN( MAX( 0, y_point ), m_view.get_height() ); set_caret( caret_pos, x_point , y + y_tmp ); selection = true; // スクロールさせてから対応する範囲を描画 } } break; default: // 実況モード if( m_scrollinfo.live ){ // 通常スクロール if( m_scrollinfo.live_speed < CONFIG::get_live_threshold() ){ const int mode = CONFIG::get_live_mode(); if( mode == LIVE_SCRMODE_VARIABLE ) y = ( int ) ( current_y + m_scrollinfo.live_speed ); else if( mode == LIVE_SCRMODE_STEADY ) y = ( int ) ( current_y + CONFIG::get_live_speed() ); } // 行単位スクロール else{ const int step_move = 4; const int step_stop = 10; const double step = ( m_scrollinfo.live_speed * ( step_move + step_stop ) ) / step_move; ++m_scrollinfo.live_counter; // スクロール中 if( m_scrollinfo.live_counter <= step_move ){ y = ( int ) ( current_y + step ); } // 停止中 else{ if( m_scrollinfo.live_counter == step_move + step_stop ) m_scrollinfo.live_counter = 0; return; } } } break; } const int y_new = (int)MAX( 0, MIN( adjust->get_upper() - adjust->get_page_size() , y ) ); if( current_y != y_new ){ m_cancel_change_adjust = true; adjust->set_value( y_new ); m_cancel_change_adjust = false; // キーを押しっぱなしの時に一番上か下に着いたらスクロール停止して全画面再描画 if( m_scrollinfo.mode == SCROLL_LOCKED && ( y_new <= 0 || y_new >= adjust->get_upper() - adjust->get_page_size() ) ){ m_scrollinfo.reset(); redraw_all = true; } } // 選択範囲のセット RECTANGLE rect_selection; if( selection ){ if( ! set_selection( caret_pos, &rect_selection ) ) selection = false; } // 描画 const int height_redraw = ( redraw_all ? m_view.get_height() : 0 ); if( draw_screen( 0, height_redraw ) ){ // 選択範囲描画 if( selection // 既に描画済みならキャンセル && ! ( m_rect_backscreen.y <= rect_selection.y && m_rect_backscreen.y + m_rect_backscreen.height >= rect_selection.y + rect_selection.height ) ){ // 描画済みの範囲を除く if( m_rect_backscreen.y <= rect_selection.y && m_rect_backscreen.y + m_rect_backscreen.height >= rect_selection.y ){ const int dh = ( m_rect_backscreen.y + m_rect_backscreen.height ) - rect_selection.y; rect_selection.y += dh; rect_selection.height -= dh; } else if( m_rect_backscreen.y <= rect_selection.y + rect_selection.height && m_rect_backscreen.y + m_rect_backscreen.height >= rect_selection.y + rect_selection.height ){ const int dh = ( rect_selection.y + rect_selection.height ) - m_rect_backscreen.y; rect_selection.height -= dh; } draw_screen( rect_selection.y, rect_selection.height ); } // カーソル形状の更新 CARET_POSITION unused_caret_pos; m_layout_current = set_caret( unused_caret_pos, m_x_pointer , m_y_pointer + get_vscr_val() ); change_cursor( get_cursor_type() ); } } // // スクロールバーの現在値 // int DrawAreaBase::get_vscr_val() const { if( m_vscrbar ) return ( int ) m_vscrbar->get_adjustment()->get_value(); return 0; } // // スクロールバーの最大値値 // int DrawAreaBase::get_vscr_maxval() const { if( m_vscrbar ) return ( int ) ( m_vscrbar->get_adjustment()->get_upper() - m_vscrbar->get_adjustment()->get_page_size() ); return 0; } // // num 番にジャンプ // void DrawAreaBase::goto_num( int num ) { #ifdef _DEBUG std::cout << "DrawAreaBase::goto_num num = " << num << std::endl; #endif if( num <= 0 ) return; if( ! m_vscrbar ) return; // ジャンプ予約 // まだ初期化中の場合はジャンプの予約をしておいて、初期化が終わったら時点でもう一回呼び出し if( ! m_backscreen ){ m_goto_num_reserve = num; #ifdef _DEBUG std::cout << "reserve goto_num(1) num = " << m_goto_num_reserve << std::endl; #endif return; } // 表示範囲を越えていたら再レイアウトしたときにもう一度呼び出し else if( num > max_number() ){ m_goto_num_reserve = num; #ifdef _DEBUG std::cout << "reserve goto_num(2) num = " << m_goto_num_reserve << std::endl;; #endif return; } else m_goto_num_reserve = 0; // ロード数を越えている場合 int number_load = DBTREE::article_number_load( m_url ); if( number_load < num ) num = number_load; // num番が表示されていないときは近くの番号をセット while( ! m_layout_tree->get_header_of_res_const( num ) && num++ < number_load ); while( ! m_layout_tree->get_header_of_res_const( num ) && num-- > 1 ); #ifdef _DEBUG std::cout << "exec goto_num num = " << num << std::endl; #endif // スクロール実行 m_scrollinfo.reset(); m_scrollinfo.mode = SCROLL_TO_NUM; m_scrollinfo.res = num; exec_scroll(); } // // ジャンプ履歴にスレ番号を登録 // void DrawAreaBase::set_jump_history( const int num ) { m_jump_history.push_back( num ); } // // 次のレスに移動 // void DrawAreaBase::goto_next_res() { if( m_seen_current == max_number() ) goto_bottom(); else goto_num( m_seen_current + 1 ); } // // 前のレスに移動 // void DrawAreaBase::goto_pre_res() { // 表示するレスを検索 const LAYOUT* header; int num = m_seen_current; int pos_y = get_vscr_val(); do{ header = m_layout_tree->get_header_of_res_const( --num ); } while( num && ( ! header || header->rect->y >= pos_y ) ); goto_num( num ); } // // 先頭、新着、最後に移動 // void DrawAreaBase::goto_top() { if( m_vscrbar ){ m_jump_history.push_back( get_seen_current() ); m_scrollinfo.reset(); m_scrollinfo.mode = SCROLL_TO_TOP; exec_scroll(); } } void DrawAreaBase::goto_new() { if( ! m_layout_tree ) return; const int separator_new = m_layout_tree->get_separator_new(); if( separator_new ){ m_jump_history.push_back( get_seen_current() ); const int num = separator_new > 1 ? separator_new -1 : 1; #ifdef _DEBUG std::cout << "DrawAreaBase::goto_new num = " << num << std::endl; #endif goto_num( num ); } } void DrawAreaBase::goto_bottom() { #ifdef _DEBUG std::cout << "DrawAreaBase::goto_bottom\n"; #endif if( m_vscrbar ){ // まだ初期化中の場合はジャンプの予約をしておいて、初期化が終わったら時点でもう一回呼び出し if( ! m_backscreen ){ m_goto_bottom_reserve = true; #ifdef _DEBUG std::cout << "goto_bottom : reserve\n"; #endif return; } m_goto_bottom_reserve = false; m_jump_history.push_back( get_seen_current() ); m_scrollinfo.reset(); m_scrollinfo.mode = SCROLL_TO_BOTTOM; exec_scroll(); } } // // ジャンプした場所に戻る // void DrawAreaBase::goto_back() { if( ! m_jump_history.size() ) return; int num = *m_jump_history.rbegin(); m_jump_history.pop_back(); #ifdef _DEBUG std::cout << "DrawAreaBase::goto_back history = " << m_jump_history.size() << " num = " << num << std::endl; #endif goto_num( num ); } // // 検索実行 // // 戻り値: ヒット数 // int DrawAreaBase::search( const std::list< std::string >& list_query, const bool reverse ) { assert( m_layout_tree ); if( list_query.size() == 0 ) return 0; JDLIB::Regex regex; std::list list_regex; const auto make_pattern = []( const std::string& query ) { constexpr bool icase = true; // 大文字小文字区別しない constexpr bool newline = true; // . に改行をマッチさせない constexpr bool usemigemo = true; // migemo使用 constexpr bool wchar = true; // 全角半角の区別をしない return JDLIB::RegexPattern( query, icase, newline, usemigemo, wchar ); }; #ifdef _DEBUG std::cout << "ArticleViewBase::search size = " << list_query.size() << std::endl; #endif std::transform( list_query.cbegin(), list_query.cend(), std::back_inserter( list_regex ), make_pattern ); m_multi_selection.clear(); std::vector< LAYOUT_TABLE > layout_table; std::string target; // 先頭ノードから順にサーチして m_multi_selection に選択箇所をセットしていく LAYOUT* tmpheader = m_layout_tree->top_header(); while( tmpheader ){ LAYOUT* tmplayout = tmpheader->next_layout; while( tmplayout ){ if( HAS_TEXT( tmplayout ) ){ layout_table.clear(); target.clear(); // 次のノードもテキストを含んでいたら変換テーブルを作成しながらバッファに連結コピー if( tmplayout->next_layout && HAS_TEXT( tmplayout->next_layout ) ){ do{ layout_table.push_back( LAYOUT_TABLE{ tmplayout, target.size() } ); const size_t lng = strlen( tmplayout->text ); if( target.size() + lng >= SEARCH_BUFFER_SIZE ){ MISC::ERRMSG( "DrawAreaBase::search : buffer overflow." ); break; } target.append( tmplayout->text, lng ); tmplayout = tmplayout->next_layout; } while( tmplayout && HAS_TEXT( tmplayout ) ); #ifdef _DEBUG std::cout << "use buffer lng = " << target.size() << std::endl << target << std::endl; #endif } else { target = tmplayout->text; } ///////////////////////////////// size_t offset = 0; for(;;){ int min_offset = -1; int lng = 0; for( const JDLIB::RegexPattern& pattern : list_regex ) { if( regex.match( pattern, target, offset ) ){ if( min_offset == -1 || regex.pos( 0 ) <= min_offset ){ min_offset = regex.pos( 0 ); lng = regex.length( 0 ); } } } if( lng == 0 ) break; offset = min_offset; LAYOUT* layout_from = tmplayout; LAYOUT* layout_to = tmplayout; size_t offset_from = min_offset; size_t offset_to = min_offset + lng; // 変換テーブルを参照して開始位置と終了位置を求める if( layout_table.size() ){ std::vector::const_reverse_iterator it = layout_table.crbegin(); it = std::find_if( it, layout_table.crend(), [offset_to]( const auto& t ) { return t.offset < offset_to; } ); if( it != layout_table.crend() ) { layout_to = it->layout; offset_to -= it->offset; } it = std::find_if( it, layout_table.crend(), [offset_from]( const auto& t ) { return t.offset <= offset_from; } ); if( it != layout_table.crend() ) { layout_from = it->layout; offset_from -= it->offset; } } #ifdef _DEBUG std::cout << "id_from = " << layout_from->id << " offset_from = " << offset_from << " -> id_to = " << layout_to->id << " offset_to = " << offset_to << std::endl; const std::string text_from = std::string( layout_from->text ).substr( offset_from ); const std::string text_to = std::string( layout_to->text ).substr( 0, offset_to ); std::cout << text_from << std::endl << text_to << std::endl; #endif // 選択設定 SELECTION selection; selection.select = false; selection.caret_from.set( layout_from, offset_from ); selection.caret_to.set( layout_to, offset_to ); m_multi_selection.push_back( selection ); offset += lng; } } if( tmplayout ) tmplayout = tmplayout->next_layout; } tmpheader = tmpheader->next_header; } #ifdef _DEBUG std::cout << "m_multi_selection.size = " << m_multi_selection.size() << std::endl; #endif if( m_multi_selection.size() == 0 ) return 0; // 初期位置をセット // selection.select = true のアイテムが現在選択中 auto it = std::find_if_not( m_multi_selection.begin(), m_multi_selection.end(), [this]( const SELECTION& s ) { return s.caret_from < m_caret_pos; } ); if( it != m_multi_selection.end() ) { it->select = true; } else { m_multi_selection.back().select = true; } // search_move でひとつ進めるのでひとつ前に戻しておく if( ! reverse ){ if( it != m_multi_selection.end() ) ( *it ).select = false; if( it == m_multi_selection.begin() ) m_multi_selection.back().select = true; else { --it; it->select = true; } } redraw_view_force(); return m_multi_selection.size(); } // // 次の検索結果に移動 // // 戻り値: ヒット数 // int DrawAreaBase::search_move( const bool reverse ) { #ifdef _DEBUG std::cout << "ArticleViewBase::search_move " << m_multi_selection.size() << std::endl; #endif if( m_multi_selection.size() == 0 ) return 0; if( ! m_vscrbar ) return m_multi_selection.size(); std::list< SELECTION >::iterator it; for( it = m_multi_selection.begin(); it != m_multi_selection.end(); ++it ){ if( ( *it ).select ){ ( *it ).select = false; // 前に移動 if( reverse ){ if( it == m_multi_selection.begin() ) it = m_multi_selection.end(); --it; } // 次に移動 else{ if( ( ++it ) == m_multi_selection.end() ) it = m_multi_selection.begin(); } ( *it ).select = true; // 移動先を範囲選択状態にする m_caret_pos_dragstart = ( *it ).caret_from; set_selection( ( *it ).caret_to ); int y = MAX( 0, ( *it ).caret_from.layout->rect->y - 10 ); #ifdef _DEBUG std::cout << "move to y = " << y << std::endl; #endif auto adjust = m_vscrbar->get_adjustment(); if( ( int ) adjust->get_value() > y || ( int ) adjust->get_value() + ( int ) adjust->get_page_size() - m_font->br_size < y ){ m_cancel_change_adjust = true; adjust->set_value( y ); m_cancel_change_adjust = false; } redraw_view_force(); return m_multi_selection.size(); } } return m_multi_selection.size(); } // // ハイライト解除 // void DrawAreaBase::clear_highlight() { m_multi_selection.clear(); redraw_view_force(); } // // 実況開始 // void DrawAreaBase::live_start() { m_scrollinfo.live = true; } // // 実況停止 // void DrawAreaBase::live_stop() { m_scrollinfo.reset(); m_scrollinfo.live = false; } // // 実況時のスクロール速度更新 // // sec : 更新間隔(秒) // void DrawAreaBase::update_live_speed( const int sec ) { if( ! m_scrollinfo.live ) return; if( sec <= 0 ) return; const int min_live_speed = CONFIG::get_live_speed(); if( ! min_live_speed ) m_scrollinfo.live_speed = 0; else{ double speed = ( get_vscr_maxval() - get_vscr_val() ) / ( sec * 1000/TIMER_TIMEOUT_SMOOTH_SCROLL ); m_scrollinfo.live_speed = MAX( min_live_speed, speed ); } m_scrollinfo.live_counter = 0; #ifdef _DEBUG std::cout << "DrawAreaBase::update_live_speed sec = " << sec << " speed = " << m_scrollinfo.live_speed << std::endl; #endif } // // ポインタがRECTANGLEの上にあるか判定 // // int& pos, int& width_line, int& char_width, int& byte_char // はそれぞれポインタの下の文字の位置(バイト)とその文字までの長さ(ピクセル)、文字の幅(ピクセル)、バイト // bool DrawAreaBase::is_pointer_on_rect( const RECTANGLE* rect, LAYOUT* layout, const char* text, const int pos_start, const int pos_to, const int x, const int y, int& pos, int& width_line, int& char_width, int& byte_char ) { pos = pos_start; width_line = 0; char_width = 0; byte_char = 0; if( ! ( rect->y <= y && y <= rect->y + rect->height ) ) return false; // この文字列の全角/半角モードの初期値を決定 bool wide_mode = set_init_wide_mode( text, pos_start, pos_to ); char pre_char = 0; while( pos < pos_to ){ char_width = get_width_of_one_char( text + pos, byte_char, pre_char, wide_mode, get_layout_fontid( layout ) ); // マウスポインタの下にノードがある場合 if( rect->x + PANGO_PIXELS( width_line ) <= x && x <= rect->x + PANGO_PIXELS( width_line + char_width ) ){ width_line = PANGO_PIXELS( width_line ); char_width = PANGO_PIXELS( char_width ); return true; } // 次の文字へ pos += byte_char; width_line += char_width; } return false; } // // 座標(x,y)を与えてキャレットの位置を計算してCARET_POSITIONに値をセット //、ついでに(x,y)の下にあるレイアウトノードも調べる // // CARET_POSITION& caret_pos : キャレットの位置が計算されて入る // // 戻り値: 座標(x,y)の下のレイアウトノード。ノード外にある場合はnullptr // LAYOUT* DrawAreaBase::set_caret( CARET_POSITION& caret_pos, int x, int y ) { if( ! m_layout_tree ) return nullptr; #ifdef _DEBUG_CARETMOVE std::cout << "DrawAreaBase::set_caret x = " << x << " y = " << y << std::endl; #endif // 先頭のレイアウトブロックから順に調べる LAYOUT* header = m_layout_tree->top_header(); if( ! header ) return nullptr; // まだレイアウト計算していない if( ! header->rect ) return nullptr; if( header->next_header && ! header->next_header->rect ) return nullptr; while( header ){ // y が含まれているヘッダブロックだけチェックする int height_block = header->next_header ? ( header->next_header->rect->y - header->rect->y ) : BIG_HEIGHT; if( ! ( header->rect->y <= y && header->rect->y + height_block >= y ) ){ header = header->next_header; continue; } // ヘッダブロック内のノードを順に調べていく LAYOUT* layout = header->next_layout; while( layout ){ const RECTANGLE* rect = layout->rect; if( ! rect ){ layout = layout->next_layout; continue; } int tmp_x = rect->x; int tmp_y = rect->y; int width = rect->width; int height = rect->height; int pos_start = rect->pos_start; int n_byte = rect->n_byte; // テキストノードでは無い if( ! layout->text ){ layout = layout->next_layout; continue; } // フォント設定 set_node_font( layout ); ////////////////////////////////////////////// // // 現在のテキストノードの中、又は左か右にあるか調べる // for(;;){ int pos; int width_line; int char_width; int byte_char; // テキストノードの中にある場合 if( is_pointer_on_rect( rect, layout, layout->text, pos_start, pos_start + n_byte, x, y, pos, width_line, char_width, byte_char ) ){ #ifdef _DEBUG_CARETMOVE std::cout << "found: on node\n"; std::cout << "header id = " << header->id_header << std::endl; std::cout << "node id = " << layout->id << std::endl; std::cout << "pos = " << pos << std::endl; std::cout << "tmp_x = " << tmp_x << std::endl; std::cout << "tmp_y = " << tmp_y << std::endl; std::cout << layout->text << std::endl; #endif // キャレットをセットして終了 caret_pos.set( layout, pos, x, tmp_x + width_line, tmp_y, char_width, byte_char ); return layout; } else if( tmp_y <= y && y <= tmp_y + height ){ // 左のマージンの上にポインタがある場合 if( x < tmp_x ){ #ifdef _DEBUG_CARETMOVE std::cout << "found: left\n"; std::cout << "header id = " << header->id_header << std::endl; std::cout << "node id = " << layout->id << std::endl; std::cout << "pos = " << pos_start << std::endl; #endif // 左端にキャレットをセットして終了 caret_pos.set( layout, pos_start, x, tmp_x, tmp_y ); return nullptr; } // 右のマージンの上にポインタがある場合 else if( layout->next_layout == nullptr || layout->next_layout->type == DBTREE::NODE_BR ){ #ifdef _DEBUG_CARETMOVE std::cout << "found: right\n"; std::cout << "header id = " << header->id_header << std::endl; std::cout << "node id = " << layout->id << std::endl; std::cout << "pos = " << pos_start + n_byte << std::endl; #endif // 右端にキャレットをセットして終わり caret_pos.set( layout, pos_start + n_byte, x, tmp_x + width, tmp_y ); return nullptr; } } // 折り返し無し if( rect->end ) break; rect = rect->next_rect; if( ! rect ) break; tmp_x = rect->x; tmp_y = rect->y; width = rect->width; height = rect->height; pos_start = rect->pos_start; n_byte = rect->n_byte; } ////////////////////////////////////////////// // // 現在のノードと次のノード、又はヘッダブロックの間にポインタがあるか調べる // // 次のノードを取得する LAYOUT* layout_next = layout->next_layout; while( layout_next && ! ( layout_next->rect && layout_next->text ) ){ // 次のノードが画像ノード場合 // 現在のノードの右端にキャレットをセットして画像ノードを返す if( ( layout_next->type == DBTREE::NODE_IMG || layout_next->type == DBTREE::NODE_SSSP ) && layout_next->rect && ( layout_next->rect->x <= x && x <= layout_next->rect->x + layout_next->rect->width ) && ( layout_next->rect->y <= y && y <= layout_next->rect->y + layout_next->rect->height ) ){ caret_pos.set( layout, pos_start + n_byte, x, tmp_x + width, tmp_y ); return layout_next; } layout_next = layout_next->next_layout; } // 残りのノードのうち、最小のy座標を取得 int next_y = y + BIG_HEIGHT; // 次のノード or ブロックのy座標 bool next_found = false; while( layout_next ){ if( layout_next->rect && next_y > layout_next->rect->y ){ next_y = layout_next->rect->y; if( next_y <= y ){ next_found = true; break; // 小さいy座標のノードが見つかったので、次のノードの処理に進む } } layout_next = layout_next->next_layout; } if( !next_found ) { #ifdef _DEBUG_CARETMOVE std::cout << "found: between\n"; std::cout << "header id = " << header->id_header << std::endl; std::cout << "node id = " << layout->id << std::endl; #endif // 現在のノードの右端にキャレットをセット caret_pos.set( layout, pos_start + n_byte, x, tmp_x + width, tmp_y ); return nullptr; } // 次のノードへ layout = layout->next_layout; } // 次のブロックへ header = header->next_header; } return nullptr; } // // (x,y)地点をダブルクリック時の範囲選択のためのキャレット位置を取得 // // caret_left : 左側のキャレット位置 // caret_left : 右側のキャレット位置 // // 戻り値 : 成功すると true // // 区切り文字 bool is_separate_char( const int ucs2 ) { if( ucs2 == ' ' || ucs2 == '.' || ucs2 == ',' || ucs2 == '(' || ucs2 == ')' || ucs2 == '=' // 全角空白 || ucs2 == 0x3000 // 。、 || ucs2 == 0x3001 || ucs2 == 0x3002 // ., || ucs2 == 0xff0c || ucs2 == 0xff0e // 全角() || ucs2 == 0xff08 || ucs2 == 0xff09 // 全角 = || ucs2 == 0xff1d // 「」 || ucs2 == 0x300c || ucs2 == 0x300d || ucs2 == 0x300e || ucs2 == 0x300f ) return true; return false; } // // ダブルクリック時にキャレット位置を決める // bool DrawAreaBase::set_carets_dclick( CARET_POSITION& caret_left, CARET_POSITION& caret_right, const int x, const int y, const bool triple ) { if( ! m_layout_tree ) return false; #ifdef _DEBUG std::cout << "DrawAreaBase::set_carets_dclick\n"; #endif // 先頭のヘッダブロックから順に調べる LAYOUT* header = m_layout_tree->top_header(); while( header ){ // y が含まれているブロックだけチェックする if( header->rect->y <= y && y <= header->rect->y + header->rect->height ){ LAYOUT* layout = header->next_layout; LAYOUT* layout_before = layout; while( layout ){ RECTANGLE* rect = layout->rect; if( ! layout->text || ! rect ){ layout = layout->next_layout; if( layout && layout->text ) layout_before = layout; else layout_before = nullptr; continue; } // フォント設定 set_node_font( layout ); // ポインタの下にあるノードを探す int pos; while( rect ){ int width_line; int char_width; int byte_char; if( is_pointer_on_rect( rect, layout, layout->text, rect->pos_start, rect->pos_start + rect->n_byte, x, y, pos, width_line, char_width, byte_char ) ) break; rect = rect->next_rect; } if( ! rect ){ layout = layout->next_layout; continue; } // トリプルクリック if( triple ){ LAYOUT* layout_after = layout; while( layout_after->next_layout && layout_after->next_layout->text ) layout_after = layout_after->next_layout; if( layout_before ) caret_left.set( layout_before, 0 ); else caret_left.set( layout, 0 ); caret_right.set( layout_after, layout_after->lng_text ); return true; } int byte_char_pointer; const int ucs2_pointer = MISC::utf8toucs2( layout->text + pos, byte_char_pointer ); const int ucs2mode_pointer = MISC::get_ucs2mode( ucs2_pointer ); #ifdef _DEBUG std::cout << "ucs2 = " << std::hex << ucs2_pointer << std::dec << " mode = " << ucs2mode_pointer << " pos = " << pos << std::endl; #endif // 区切り文字をダブルクリックした if( is_separate_char( ucs2_pointer ) ){ caret_left.set( layout, pos ); caret_right.set( layout, pos + byte_char_pointer ); return true; } // 左位置を求める int pos_left = 0; int pos_tmp = 0; while( pos_tmp < pos ){ int byte_char; const int ucs2 = MISC::utf8toucs2( layout->text + pos_tmp, byte_char ); const int ucs2mode = MISC::get_ucs2mode( ucs2 ); int byte_char_next; const int ucs2_next = MISC::utf8toucs2( layout->text + pos_tmp + byte_char, byte_char_next ); const int ucs2mode_next = MISC::get_ucs2mode( ucs2_next ); // 区切り文字が来たら左位置を移動する if( ucs2_next == '\0' || is_separate_char( ucs2 ) // 文字種が変わった || ( ucs2mode != ucs2mode_pointer && ucs2mode_next == ucs2mode_pointer ) ) pos_left = pos_tmp + byte_char; pos_tmp += ( byte_char ? byte_char : 1 ); } // 右位置を求める int pos_right = pos; while( pos_right < layout->lng_text ){ int byte_char; const int ucs2 = MISC::utf8toucs2( layout->text + pos_right, byte_char ); const int ucs2mode = MISC::get_ucs2mode( ucs2 ); int byte_char_next; const int ucs2_next = MISC::utf8toucs2( layout->text + pos_right + byte_char, byte_char_next ); const int ucs2mode_next = MISC::get_ucs2mode( ucs2_next ); // 区切り文字が来たらbreak if( is_separate_char( ucs2 ) ) break; pos_right += ( byte_char ? byte_char : 1 ); // 文字種が変わった if( ucs2_next == '\0' || ( ucs2mode == ucs2mode_pointer && ucs2mode_next != ucs2mode_pointer ) ) break; } #ifdef _DEBUG std::cout << "pos_left = " << pos_left << " pos_right = " << pos_right << std::endl; #endif // キャレット設定 caret_left.set( layout, pos_left ); caret_right.set( layout, pos_right ); return true; } } // 次のブロックへ header = header->next_header; } return false; } // // 範囲選択の範囲を計算してm_selectionにセット & 範囲選択箇所の再描画 // // caret_left から caret_right まで範囲選択状態にする // bool DrawAreaBase::set_selection( const CARET_POSITION& caret_left, const CARET_POSITION& caret_right ) { m_caret_pos_pre = caret_left; m_caret_pos = caret_left; m_caret_pos_dragstart = caret_left; return set_selection( caret_right, nullptr ); } // // 範囲選択の範囲を計算してm_selectionにセット // // caret_pos : 移動後のキャレット位置、m_caret_pos_pre から caret_pos まで範囲選択状態にする // bool DrawAreaBase::set_selection( const CARET_POSITION& caret_pos ) { return set_selection( caret_pos, nullptr ); } // rect に再描画範囲を計算して入れる( nullptr なら入らない ) // その後 draw_screen( rect.y, rect.height ) で選択範囲を描画する bool DrawAreaBase::set_selection( const CARET_POSITION& caret_pos, RECTANGLE* rect ) { if( ! caret_pos.layout ) return false; if( ! m_caret_pos_dragstart.layout ) return false; // 前回の呼び出しからキャレット位置が変わってない if( m_caret_pos == caret_pos ) return false; m_caret_pos_pre = m_caret_pos;; m_caret_pos = caret_pos; #ifdef _DEBUG std::cout << "DrawAreaBase::set_selection()\n"; std::cout << "start header = " << m_caret_pos_dragstart.layout->id_header << " node = " << m_caret_pos_dragstart.layout->id; std::cout << " byte = " << m_caret_pos_dragstart.byte << std::endl; std::cout << "current header = " << m_caret_pos.layout->id_header << " node = " << m_caret_pos.layout->id; std::cout << " byte = " << m_caret_pos.byte << std::endl; #endif // ドラッグ開始位置と現在のキャレット位置が同じなら選択解除 if( m_caret_pos_dragstart == m_caret_pos ) m_selection.select = false; // 範囲計算 else{ m_selection.select = true; if( m_caret_pos_dragstart > m_caret_pos ){ m_selection.caret_from = m_caret_pos;; m_selection.caret_to = m_caret_pos_dragstart; } else{ m_selection.caret_from = m_caret_pos_dragstart; m_selection.caret_to = m_caret_pos; } } if( ! rect ) return true; // 再描画範囲計算 const int pos_y = get_vscr_val(); const int height_view = m_view.get_height(); int y_redraw = 0; int height_redraw = height_view; LAYOUT* layout = m_caret_pos_pre.layout; LAYOUT* layout_to = m_caret_pos.layout; if( ! layout ) layout = m_caret_pos_dragstart.layout; // layout_toの方が前だったらポインタを入れ換え if( layout_to->id_header < layout->id_header || ( layout_to->id_header == layout->id_header && layout_to->id < layout->id ) ){ LAYOUT* layout_tmp = layout_to; layout_to = layout; layout = layout_tmp; } RECTANGLE* rect_from = layout->rect; RECTANGLE* rect_to = layout_to->rect; if( ! rect_from || ! rect_to ) return false; while( rect_to->next_rect ) rect_to = rect_to->next_rect; // 範囲外 if( rect_from->y > pos_y + height_view ) return false; if( rect_to->y + rect_to->height < pos_y ) return false; if( rect_from->y > pos_y ){ y_redraw = rect_from->y - pos_y; height_redraw = height_view - y_redraw; } if( rect_to->y + rect_to->height < pos_y + height_view ){ height_redraw -= ( pos_y + height_view ) - ( rect_to->y + rect_to->height ); } #ifdef _DEBUG std::cout << "redraw layout from : " << layout->id_header << ":" << layout->id << " to " << layout_to->id_header << ":" << layout_to->id << " y_redraw = " << y_redraw << " height_redraw = " << height_redraw << std::endl; #endif rect->y = y_redraw; rect->height = height_redraw; return true; } // // 範囲選択の文字列取得 // // set_selection()の中で毎回やると重いので、ボタンのリリース時に一回だけ呼び出すこと // bool DrawAreaBase::set_selection_str() { assert( m_layout_tree ); if( ! m_selection.str.empty() ) m_selection.str_pre = m_selection.str; m_selection.str.clear(); m_selection.imgurls.clear(); if( !m_selection.select ) return false; #ifdef _DEBUG std::cout << "DrawAreaBase::set_selection_str\n"; std::cout << "from header = " << m_selection.caret_from.layout->id_header << " node = " << m_selection.caret_from.layout->id; std::cout << " byte = " << m_selection.caret_from.byte << std::endl; std::cout << "to header = " << m_selection.caret_to.layout->id_header << " node = " << m_selection.caret_to.layout->id; std::cout << " byte = " << m_selection.caret_to.byte << std::endl; #endif std::vector< URLINFO > urls; bool start_copy = false; // 面倒臭いんで先頭のレイアウトブロックから順に調べていく LAYOUT* tmpheader = m_layout_tree->top_header(); while( tmpheader ){ // ブロック内のノードを順に調べていく LAYOUT* tmplayout = tmpheader->next_layout; while( tmplayout ){ int copy_from = 0, copy_to = 0; // 開始ノード if( tmplayout == m_selection.caret_from.layout ){ start_copy = true; copy_from = m_selection.caret_from.byte; copy_to = strlen( tmplayout->text ); } // 終了ノード if( tmplayout == m_selection.caret_to.layout ) copy_to = m_selection.caret_to.byte; // 文字列コピー if( start_copy ){ if( tmplayout->type == DBTREE::NODE_BR ) m_selection.str += "\n"; else if( tmplayout->type == DBTREE::NODE_DIV ) m_selection.str += "\n"; else if( tmplayout->type == DBTREE::NODE_HTAB ) m_selection.str += "\t"; else if( tmplayout->text ){ if( copy_from || copy_to ) m_selection.str += std::string( tmplayout->text ).substr( copy_from, copy_to - copy_from ); else m_selection.str += tmplayout->text; if( tmplayout->type == DBTREE::NODE_LINK && tmplayout->link ){ URLINFO urlinfo; urlinfo.url = std::string( tmplayout->link ); urlinfo.res_number = tmplayout->res_number; urls.push_back( urlinfo ); } } } // 終了 if( tmplayout == m_selection.caret_to.layout ){ // 画像のURLだけ抽出する if( urls.size() ){ for( const URLINFO& info : urls ) { if( DBIMG::get_type_ext( info.url ) != DBIMG::T_UNKNOWN ) { const std::string& url = info.url; const bool found = std::any_of( m_selection.imgurls.cbegin(), m_selection.imgurls.cend(), [&url]( const auto& i ) { return url == i.url; } ); if( ! found ) { m_selection.imgurls.push_back( info ); } } } } return true; } tmplayout = tmplayout->next_layout; } tmpheader = tmpheader->next_header; if( start_copy ){ m_selection.str += "\n"; if( tmpheader ) m_selection.str += "\n"; } } return false; } // // caret_pos が範囲選択の上にあるか // // bool DrawAreaBase::is_caret_on_selection( const CARET_POSITION& caret_pos ) const { LAYOUT* layout = caret_pos.layout; if( !layout || ! m_selection.select || m_selection.str.empty() ) return false; if( layout->id_header != m_selection.caret_from.layout->id_header ) return false; const int from_id = m_selection.caret_from.layout->id; const int from_byte = m_selection.caret_from.byte; const int to_id = m_selection.caret_to.layout->id; const int to_byte = m_selection.caret_to.byte; if( to_id < layout->id ) return false; if( to_id == layout->id && to_byte < caret_pos.byte ) return false; if( from_id > layout->id ) return false; if( from_id == layout->id && from_byte > caret_pos.byte ) return false; return true; } // // 範囲選択範囲にcaret_posが含まれていて、かつ条件(IDや数字など)を満たしていたらURLとして範囲選択文字を返す // std::string DrawAreaBase::get_selection_as_url( const CARET_POSITION& caret_pos ) const { std::string url; if( is_caret_on_selection( caret_pos ) ){ size_t n,dig; std::string select_str = MISC::remove_space( m_selection.str ); select_str = MISC::remove_str( select_str, "\n" ); int num = MISC::str_to_uint( select_str.c_str(), dig, n ); // 数字 if( dig && num ){ url = PROTO_ANCHORE + std::to_string( num ); for(;;){ select_str = select_str.substr( n ); if( select_str.empty() ) break; std::string tmpstr, tmpstr2; if( select_str.rfind( '-', 0 ) == 0 ) tmpstr = tmpstr2 = "-"; else if ( select_str.rfind( '=', 0 ) == 0 ) tmpstr = tmpstr2 = "="; else if ( select_str.rfind( ',', 0 ) == 0 ) tmpstr = tmpstr2 = ","; else if( select_str.rfind( "-", 0 ) == 0 ){ tmpstr = "-"; tmpstr2 = "-"; } else if( select_str.rfind( "−", 0 ) == 0 ){ tmpstr = "−"; tmpstr2 = "-"; } else if ( select_str.rfind( "=", 0 ) == 0 ){ tmpstr = "="; tmpstr2 = "="; } else if ( select_str.rfind( ",", 0 ) == 0 ){ tmpstr = ","; tmpstr2 = ","; } select_str = select_str.substr( tmpstr.length() ); num = MISC::str_to_uint( select_str.c_str(), dig, n ); if( dig && num ) url += tmpstr2 + std::to_string( num ); else break; } } // ID else if( select_str.rfind( "ID:", 0 ) == 0 ) url = select_str; } #ifdef _DEBUG if( !url.empty() ) std::cout << "DrawAreaBase::get_selection_as_url : " << url << std::endl; #endif return url; } // 全選択 void DrawAreaBase::select_all() { #ifdef _DEBUG std::cout << "DrawAreaBase::select_all\n"; #endif CARET_POSITION caret_left, caret_right; LAYOUT* layout = nullptr; LAYOUT* layout_back = nullptr; // 先頭 LAYOUT* header = m_layout_tree->top_header(); while( header ){ layout = header->next_layout; while( layout && ! layout->text ){ layout = layout->next_layout; } if( layout ) break; header = header->next_header; } if( ! layout ) return; #ifdef _DEBUG std::cout << "id = " << layout->id_header << "-" << layout->id << " text = " << layout->text << std::endl; #endif caret_left.set( layout, 0 ); // 最後 while( header ){ layout = header->next_layout; while( layout ){ if( layout->text ) layout_back = layout; layout = layout->next_layout; } header = header->next_header; } if( ! layout_back ) return; #ifdef _DEBUG std::cout << "-> id = " << layout_back->id_header << "-" << layout_back->id << " text = " << layout_back->text << std::endl; #endif caret_right.set( layout_back, layout_back->lng_text ); set_selection( caret_left, caret_right ); set_selection_str(); redraw_view_force(); } // // VScrollbar が動いた // void DrawAreaBase::slot_change_adjust() { if( m_cancel_change_adjust ) return; if( m_scrollinfo.mode != SCROLL_NOT ) return; // スクロール中 #ifdef _DEBUG std::cout << "slot_change_adjust\n"; #endif m_scrollinfo.reset(); m_scrollinfo.mode = SCROLL_NORMAL; exec_scroll(); } // // drawarea がリサイズした // bool DrawAreaBase::slot_configure_event( GdkEventConfigure* event ) { // 表示されていないview(is_drawable() != true ) は表示された段階で // redraw_view() したときに configure_impl() を呼び出す m_configure_reserve = true; configure_impl(); return true; } // // drawarea がリサイズ実行 // void DrawAreaBase::configure_impl() { if( ! m_configure_reserve ) return; if( !m_view.get_is_drawable() ) return; m_configure_reserve = false; const int width = m_view.get_width(); const int height = m_view.get_height(); if( height < LAYOUT_MIN_HEIGHT ) return; // サイズがほぼ変わっていないときは再レイアウトしない // 値が微妙に変化していることがあり == による比較ではスクロールしてしまうバグがあった if( std::abs( m_configure_width - width ) < 2 && std::abs( m_configure_height - height ) < 2 ) return; // リサイズする前のレス番号を保存しておいて // redrawした後にジャンプ const int seen_current = m_seen_current; // リサイズ前と横幅が同じ場合はスクロールバーの位置を変えない const int pos_y = ( m_configure_width == width ? get_vscr_val() : 0 ); #ifdef _DEBUG std::cout << "DrawAreaBase::configure_impl : url = " << m_url << std::endl << "seen_current = " << seen_current << " pos_y = " << pos_y << " width = " << width << " heigth = " << height << " pre_width = " << m_configure_width << " pre_height = " << m_configure_height << std::endl; #endif m_configure_width = width; m_configure_height = height; if( exec_layout() ){ // スクロール実行 if( pos_y ){ m_scrollinfo.reset(); m_scrollinfo.mode = SCROLL_TO_Y; m_scrollinfo.y = pos_y; exec_scroll(); } else if( seen_current ) goto_num( seen_current ); else redraw_view_force(); } } // // drawarea の再描画イベント // bool DrawAreaBase::slot_draw( const Cairo::RefPtr< Cairo::Context >& cr ) { double x1, y1, x2, y2; cr->get_clip_extents( x1, y1, x2, y2 ); const double width = x2 - x1; const double height = y2 - y1; // タブ操作中は再描画しない if( SESSION::is_tab_operating( URL_ARTICLEADMIN ) ) { return true; } #ifdef _DEBUG std::cout << "DrawAreaBase::slot_draw" << " y = " << y1 << " height = " << height << " draw_screen = " << m_drawinfo.draw << " url = " << m_url << std::endl; #endif // drawイベントから抜ける前にm_cr.release()を呼び出して所有権を放棄する // ローカル変数やスコープガードなどを使用して安全性を高めるべきか m_cr.reset( cr->cobj() ); // draw_screen からの呼び出し if( m_drawinfo.draw ) { #ifdef _DEBUG std::cout << "draw\n"; #endif m_drawinfo.draw = false; exec_draw_screen( m_drawinfo.y, m_drawinfo.height ); } // バックスクリーンに描画済みならコピー else if( y1 >= m_rect_backscreen.y && y2 <= m_rect_backscreen.y + m_rect_backscreen.height ) { #ifdef _DEBUG std::cout << "copy from backscreen\n"; #endif cairo_save( m_cr.get() ); cairo_rectangle( m_cr.get(), x1, y1, width, height ); cairo_clip( m_cr.get() ); cairo_set_source_surface( m_cr.get(), m_backscreen.get(), x1, y1 ); cairo_paint( m_cr.get() ); cairo_restore( m_cr.get() ); // オートスクロールマーカと枠の描画 draw_marker(); draw_frame(); } // レイアウトがセットされていない or まだリサイズしていない // ( m_backscreen == nullptr )なら画面消去 else if( !m_layout_tree->top_header() || !m_backscreen ) { #ifdef _DEBUG std::cout << "clear window\n"; #endif cairo_save( m_cr.get() ); gdk_cairo_set_source_rgba( m_cr.get(), m_color[ get_colorid_back() ].gobj() ); cairo_fill( m_cr.get() ); cairo_restore( m_cr.get() ); // シグナルハンドラの引数から借りただけなので所有権を放棄しておく m_cr.release(); return false; } // 必要な所だけ再描画 else { #ifdef _DEBUG std::cout << "expose\n"; #endif exec_draw_screen( static_cast< int >( y1 ), static_cast< int >( height ) ); } // シグナルハンドラの引数から借りただけなので所有権を放棄しておく m_cr.release(); return true; } // // drawarea でマウスホイールが動いた // bool DrawAreaBase::slot_scroll_event( GdkEventScroll* event ) { m_sig_scroll_event.emit( event ); return true; } // // マウスが領域外に出た // bool DrawAreaBase::slot_leave_notify_event( GdkEventCrossing* event ) { #ifdef _DEBUG std::cout << "DrawAreaBase::slot_leave_notify_event\n"; #endif // 右ドラッグ中はシグナルを発行しない if( ! m_r_drugging ) m_sig_leave_notify.emit( event ); return false; } // // realize // // GCを作成してレイアウト // void DrawAreaBase::slot_realize() { #ifdef _DEBUG std::cout << "DrawAreaBase::slot_realize()" << std::endl; #endif m_window = m_view.get_window(); assert( m_window ); // 色初期化 init_color(); m_back_marker.reset( gdk_window_create_similar_surface( m_window->gobj(), CAIRO_CONTENT_COLOR, AUTOSCR_CIRCLE, AUTOSCR_CIRCLE ) ); assert( m_back_marker ); exec_layout(); m_view.grab_focus(); } // // マウスボタンを押した // bool DrawAreaBase::slot_button_press_event( GdkEventButton* event ) { m_clicked = true; std::string url; int res_num = 0; bool redraw_force = false; if( m_layout_current && m_layout_current->link ) url = m_layout_current->link; if( m_layout_current ) res_num = m_layout_current->res_number; const int pos = get_vscr_val(); const int x = ( int ) event->x; const int y = ( int ) event->y + pos; CARET_POSITION caret_pos; set_caret( caret_pos, x, y ); m_view.grab_focus(); // オートスクロール中ならオートスクロール解除 if( m_scrollinfo.mode == SCROLL_AUTO ){ m_scrollinfo.reset(); // リンクのクリックを認識させないためオートスクロール解除直後はリンククリックの処理をしない // 直後に slot_button_release_event() が呼び出されて m_scrollinfo がリセットされる m_scrollinfo.autoscroll_finished = true; change_cursor( cursor_names::kDefault ); } else { // 範囲選択解除、及びドラッグ開始 if( m_control.button_alloted( event, CONTROL::ClickButton ) ){ m_drugging = true; m_selection.select = false; if( ! m_selection.str.empty() ) m_selection.str_pre = m_selection.str; m_selection.str.clear(); m_selection.imgurls.clear(); m_caret_pos_pre = m_caret_pos; m_caret_pos = caret_pos; m_caret_pos_dragstart = caret_pos; redraw_force = true; } // マウスジェスチャ(右ドラッグ)開始 else if( m_control.button_alloted( event, CONTROL::GestureButton ) ) m_r_drugging = true; // オートスクロールボタン else if( m_control.button_alloted( event, CONTROL::AutoScrollButton ) ){ if ( ! ( m_layout_current && m_layout_current->link ) ){ // リンク上で無いなら change_cursor( cursor_names::kAllScroll ); m_scrollinfo.reset(); m_scrollinfo.mode = SCROLL_AUTO; m_scrollinfo.show_marker = true; m_scrollinfo.enable_up = true; m_scrollinfo.enable_down = true; m_scrollinfo.x = ( int ) event->x; m_scrollinfo.y = ( int ) event->y; } } // ダブル、トリプルクリックしたら範囲選択 else if( m_control.button_alloted( event, CONTROL::DblClickButton ) || m_control.button_alloted( event, CONTROL::TrpClickButton ) ){ const bool triple = m_control.button_alloted( event, CONTROL::TrpClickButton ); CARET_POSITION caret_left, caret_right; if( set_carets_dclick( caret_left, caret_right, x, y, triple ) ){ set_selection( caret_left, caret_right ); redraw_force = true; // GtkGestureMultiPressは指を離す度にreleasedが発行される // ダブルクリックの途中でfalseに戻らないように設定する m_drugging = true; } } } // 再描画 if( redraw_force ) redraw_view_force(); else redraw_view(); m_sig_button_press.emit( url, res_num, event ); return true; } // // マウスボタンを離した // bool DrawAreaBase::slot_button_release_event( GdkEventButton* event ) { if( ! m_clicked ) return true; m_clicked = false; // リンクの上でコンテキストメニューを表示してからマウスを移動すると // slot_motion_notify_eventが呼び出されず m_layout_current が変わらないため // リンクを開いてしまう CARET_POSITION caret_pos; m_x_pointer = ( int ) event->x; m_y_pointer = ( int ) event->y; m_layout_current = set_caret( caret_pos, m_x_pointer , m_y_pointer + get_vscr_val() ); std::string url; int res_num = 0; if( m_layout_current && m_layout_current->link ){ url = m_layout_current->link; // ssspの場合は PROTO_SSSP を前に付ける if( m_layout_current->type == DBTREE::NODE_SSSP ) url = std::string( PROTO_SSSP ) + url; } if( m_layout_current ) res_num = m_layout_current->res_number; if( event->type == GDK_BUTTON_RELEASE ){ // 範囲選択中だったら選択文字確定 & スクロール停止 if( m_drugging && set_selection_str() ){ // X のコピーバッファにコピー Glib::RefPtr< Gtk::Clipboard > clip = Gtk::Clipboard::get( GDK_SELECTION_PRIMARY ); clip->set_text( m_selection.str ); redraw_view(); m_scrollinfo.reset(); // リンククリック処理をキャンセル url = std::string(); } // リンクのクリックを認識させないためオートスクロール解除直後はリンククリックの処理をしない if( m_scrollinfo.autoscroll_finished ){ m_scrollinfo.reset(); url = std::string(); } m_drugging = false; m_r_drugging = false; // ダブルクリックで数字やID〜を範囲選択したときにon_urlシグナルを出す motion_mouse(); } m_sig_button_release.emit( url, res_num, event ); return true; } // // マウスが動いた // bool DrawAreaBase::slot_motion_notify_event( GdkEventMotion* event ) { if( m_x_pointer == ( int ) event->x && m_y_pointer == ( int ) event->y ) return true; m_x_pointer = ( int ) event->x; m_y_pointer = ( int ) event->y; // ホイールスクロール終了直後はある程度マウスを動かすまでモーションイベントをキャンセル if( m_scrollinfo.counter_nomotion ){ #ifdef _DEBUG std::cout << "counter_nomotion = " << m_scrollinfo.counter_nomotion << " x = " << m_scrollinfo.x << " y = " << m_scrollinfo.x << " xp = " << m_x_pointer << " yp = " << m_x_pointer; #endif m_scrollinfo.counter_nomotion = MAX( 0, m_scrollinfo.counter_nomotion - abs( m_scrollinfo.x - m_x_pointer ) - abs( m_scrollinfo.y - m_y_pointer ) ); m_scrollinfo.x = m_x_pointer; m_scrollinfo.y = m_y_pointer; #ifdef _DEBUG std::cout << " -> " << m_scrollinfo.counter_nomotion << std::endl; #endif if( m_scrollinfo.counter_nomotion ) return true; m_scrollinfo.reset(); } motion_mouse(); m_sig_motion_notify.emit( event ); return true; } // // マウスが動いた時の処理 // bool DrawAreaBase::motion_mouse() { const int pos = get_vscr_val(); CARET_POSITION caret_pos; // 現在のマウスポインタの下にあるレイアウトノードとキャレットの取得 m_layout_current = set_caret( caret_pos, m_x_pointer , m_y_pointer + pos ); int res_num = 0; std::string link_current; if( m_layout_current ){ // 現在のポインタの下にあるレス番号取得 res_num = m_layout_current->res_number; // 現在のポインタの下にあるリンクの文字列を更新 // IDや数字だったらポップアップ表示する // リンクの上にポインタがある if( m_layout_current->link ){ link_current = m_layout_current->link; } // IDや数字などの範囲選択の上にポインタがある else link_current = get_selection_as_url( caret_pos ); } bool link_changed = false; if( link_current != m_link_current ) link_changed = true; // 前回とリンクの文字列が変わった m_link_current = link_current; // ドラッグ中なら範囲選択 if( m_drugging ){ // ポインタが画面外に近かったらオートスクロールを開始する const int mrg = ( int )( (double)m_font->br_size*0.5 ); // スクロールのリセット if ( // スクロールページの一番上 ( pos == 0 && m_y_pointer < m_view.get_height() - mrg ) // スクロールページの一番下 || ( pos >= get_vscr_maxval() && m_y_pointer > mrg ) // ページの中央 || ( m_y_pointer > mrg && m_y_pointer < m_view.get_height() - mrg ) ){ m_scrollinfo.reset(); } // スクロールモードをセット else if( m_scrollinfo.mode == SCROLL_NOT ){ m_scrollinfo.mode = SCROLL_AUTO; m_scrollinfo.show_marker = false; m_scrollinfo.x = m_x_pointer; if( m_y_pointer <= mrg ){ m_scrollinfo.enable_up = true; m_scrollinfo.enable_down = false; m_scrollinfo.y = mrg*6; } else{ m_scrollinfo.enable_up = false; m_scrollinfo.enable_down = true; m_scrollinfo.y = m_view.get_height() - mrg*6; } } // スクロール中の場合は exec_scroll() で選択部分を描画する if( m_scrollinfo.mode == SCROLL_NOT ){ RECTANGLE rect; if( set_selection( caret_pos, &rect ) ) draw_screen( rect.y, rect.height ); } } // 右ドラッグしていないとき // 右ドラッグ中はリンクへの出入りを監視しない else if( ! m_r_drugging ){ if( link_changed ){ m_sig_leave_url.emit(); // (別の)リンクノードに入った if( !m_link_current.empty() && m_scrollinfo.mode == SCROLL_NOT // スクロール中はon_urlシグナルを出さない ){ std::string imgurl; const DBTREE::NODE* node = m_layout_current->node; if( node && node->linkinfo && node->linkinfo->imglink ) imgurl = node->linkinfo->imglink; #ifdef _DEBUG std::cout << "slot_motion_notify_drawarea : enter link = " << m_link_current << " imgurl = " << imgurl << std::endl;; #endif m_sig_on_url.emit( m_link_current, imgurl, res_num ); } // リンクノードからでた else{ #ifdef _DEBUG std::cout << "slot_motion_notify_drawarea : leave\n"; #endif } } } change_cursor( get_cursor_type() ); return true; } // // 現在のポインターの下のノードからカーソルのタイプを決定する // Glib::ustring DrawAreaBase::get_cursor_type() const { Glib::ustring cursor_type = cursor_names::kDefault; if( m_layout_current ){ // テキストの上ではカーソルを I に変える if( m_layout_current->text && ! m_layout_current->link ) cursor_type = cursor_names::kText; // リンクの上にポインタがある if( m_layout_current->link ) cursor_type = cursor_names::kPointer; } return cursor_type; } // // カーソルの形状の変更 // void DrawAreaBase::change_cursor( const Glib::ustring& cursor_type ) { //オートスクロール中 if( m_scrollinfo.mode == SCROLL_AUTO ) return; if( m_cursor_type != cursor_type ){ m_cursor_type = cursor_type; if( m_cursor_type == cursor_names::kDefault ) m_window->set_cursor(); else { m_window->set_cursor( Gdk::Cursor::create( m_window->get_display(), m_cursor_type ) ); } } } // // キーを押した // bool DrawAreaBase::slot_key_press_event( GdkEventKey* event ) { //オートスクロール中なら無視 if( m_scrollinfo.mode == SCROLL_AUTO ) return true; #ifdef _DEBUG std::cout << "DrawAreaBase::slot_key_press_event\n"; #endif if( m_key_press && m_keyval == event->keyval ) m_key_locked = true; m_key_press = true; m_keyval = event->keyval; return m_sig_key_press.emit( event ); } // // キーを離した // bool DrawAreaBase::slot_key_release_event( GdkEventKey* event ) { if( !m_key_press ) return true; #ifdef _DEBUG std::cout << "DrawAreaBase::slot_key_release_event\n"; #endif m_scrollinfo.reset(); if( m_key_locked ) redraw_view(); m_key_press = false; m_key_locked = false; m_keyval = 0; m_sig_key_release.emit( event ); return true; } void DrawAreaBase::setup_event_controller() { m_view.add_events( Gdk::TOUCH_MASK ); m_gesture_multipress = Gtk::GestureMultiPress::create( m_view ); // 既存のシグナルハンドラに処理を委譲するので0を指定してどのボタンもリッスンする m_gesture_multipress->set_button( 0 ); m_gesture_multipress->set_exclusive( true ); m_gesture_multipress->set_touch_only( false ); m_gesture_multipress->signal_pressed().connect( sigc::mem_fun( *this, &DrawAreaBase::slot_multipress_pressed ) ); m_gesture_multipress->signal_released().connect( sigc::mem_fun( *this, &DrawAreaBase::slot_multipress_released ) ); m_gesture_pan = Gtk::GesturePan::create( m_view, Gtk::ORIENTATION_VERTICAL ); m_gesture_pan->set_touch_only( true ); m_gesture_pan->signal_drag_begin().connect( sigc::mem_fun( *this, &DrawAreaBase::slot_pan_begin ) ); m_gesture_pan->signal_drag_update().connect( sigc::mem_fun( *this, &DrawAreaBase::slot_pan_update ) ); m_gesture_pan->signal_end().connect( sigc::mem_fun( *this, &DrawAreaBase::slot_gesture_end ) ); m_gesture_swipe = Gtk::GestureSwipe::create( m_view ); m_gesture_pan->group( m_gesture_swipe ); m_gesture_swipe->set_touch_only( true ); m_gesture_swipe->signal_swipe().connect( sigc::mem_fun( *this, &DrawAreaBase::slot_swipe ) ); } // // イベント構造体を取得してslot_button_(press|release)_eventに処理を委譲する // void DrawAreaBase::slot_multipress_pressed( int n_press, double, double ) { GdkEventSequence* const sequence = m_gesture_multipress->get_current_sequence(); const GdkEvent* const event = m_gesture_multipress->get_last_event( sequence ); if( !event || event->type != GDK_BUTTON_PRESS ) return; const unsigned int n_points = m_gesture_multipress->property_n_points(); const unsigned int button = m_gesture_multipress->get_current_button(); #ifdef _DEBUG std::cout << "DrawAreaBase::slot_multipress_pressed " << "n_press = " << n_press << ", event = " << event->type << ", n_points = " << n_points << ", button = " << button << std::endl; #endif const auto device = m_gesture_multipress->get_device(); GdkEventButton button_event = event->button; if( n_press == 2 ) { button_event.type = GDK_DOUBLE_BUTTON_PRESS; } else if( n_press == 3 && ( device && device->get_source() != Gdk::SOURCE_TOUCHSCREEN ) ) { button_event.type = GDK_TRIPLE_BUTTON_PRESS; } if( n_points == 2 ) { // 2本指タップは右ボタンクリックとして扱う button_event.button = GDK_BUTTON_SECONDARY; } slot_button_press_event( &button_event ); if( button >= 4 ) { // GtkGestureMultiPressを使うとButton4以上のマウスボタンで // button-release-eventの反応が悪くなる環境がある。 // そのためpressedから直接releasedの処理を実行することによって問題を回避する。 button_event.type = GDK_BUTTON_RELEASE; slot_button_release_event( &button_event ); m_gesture_multipress->set_sequence_state( sequence, Gtk::EVENT_SEQUENCE_DENIED ); } } void DrawAreaBase::slot_multipress_released( int n_press, double, double ) { static_cast< void >( n_press ); GdkEventSequence* const sequence = m_gesture_multipress->get_current_sequence(); const GdkEvent* const event = m_gesture_multipress->get_last_event( sequence ); if( !event || event->type != GDK_BUTTON_RELEASE ) return; const unsigned int n_points = m_gesture_multipress->property_n_points(); #ifdef _DEBUG std::cout << "DrawAreaBase::slot_multipress_released " << "n_press = " << n_press << ", event = " << event->type << ", n_points = " << n_points << std::endl; #endif GdkEventButton button_event = event->button; if( n_points == 2 ) { button_event.button = GDK_BUTTON_SECONDARY; } slot_button_release_event( &button_event ); } void DrawAreaBase::slot_pan_begin( double start_x, double start_y ) { static_cast< void >( start_x ); cancel_deceleration(); Gtk::EventSequenceState state; if ( !m_vscrbar ) { state = Gtk::EVENT_SEQUENCE_DENIED; } else { // capture_button_press == true const auto adjust = m_vscrbar->get_adjustment(); m_drag_start_y = adjust->get_value(); state = Gtk::EVENT_SEQUENCE_CLAIMED; } #ifdef _DEBUG std::cout << "DrawAreaBase::slot_pan_begin y = " << m_drag_start_y << std::endl; #endif GdkEventSequence* const sequence = m_gesture_pan->get_current_sequence(); m_gesture_pan->set_sequence_state( sequence, state ); } void DrawAreaBase::slot_pan_update( double offset_x, double offset_y ) { assert( m_vscrbar ); static_cast< void >( offset_x ); GdkEventSequence* const sequence = m_gesture_pan->get_last_updated_sequence(); if( m_gesture_pan->get_sequence_state( sequence ) != Gtk::EVENT_SEQUENCE_CLAIMED ) return; const double y = m_drag_start_y - offset_y; const auto adjust = m_vscrbar->get_adjustment(); #ifdef _DEBUG std::cout << "DrawAreaBase::slot_pan_update y = " << y << std::endl; #endif if( y <= 0 ) return; if( y >= adjust->get_upper() - adjust->get_page_size() ) return; // パンの最中は必ずスクロール処理を実施する m_wait_scroll = Monotonic::duration::zero(); m_scrollinfo.reset(); m_scrollinfo.y = static_cast< int >( y ); m_scrollinfo.mode = SCROLL_TO_Y; exec_scroll(); } void DrawAreaBase::slot_gesture_end( GdkEventSequence* sequence ) { if( !m_gesture_pan->handles_sequence( sequence ) ) { m_gesture_pan->set_state( Gtk::EVENT_SEQUENCE_DENIED ); } } namespace { constexpr double kDecelerationFriction = 4.0; // スクロール速度の係数 constexpr double kDecelerationRatio = 0.000001; // 経過時間を切り下げて減速をゆるやかにする constexpr double kInitialDyScale = 0.01; // pixcels/secからpixcels/frameに換算する係数 } // // 慣性スクロールを実行する (指を離した後も減速しながらスクロールする) // void DrawAreaBase::slot_swipe( double velocity_x, double velocity_y ) { if( !m_vscrbar ) return; static_cast< void >( velocity_x ); GdkEventSequence* const sequence = m_gesture_swipe->get_last_updated_sequence(); if( m_gesture_swipe->get_sequence_state( sequence ) != Gtk::EVENT_SEQUENCE_CLAIMED ) return; #ifdef _DEBUG std::cout << "DrawAreaBase::slot_swipe vx = " << velocity_x << ", vy = " << velocity_y << std::endl; #endif GdkFrameClock* const borrowed_clock = gtk_widget_get_frame_clock( GTK_WIDGET( this->gobj() ) ); m_deceleration.last_time = gdk_frame_clock_get_frame_time( borrowed_clock ); m_deceleration.elapsed = 0.0; m_deceleration.initial_dy = velocity_y * kInitialDyScale; m_deceleration.id = gtk_widget_add_tick_callback( GTK_WIDGET( this->gobj() ), &DrawAreaBase::deceleration_tick_cb, nullptr, nullptr ); } gboolean DrawAreaBase::deceleration_tick_cb( GtkWidget* cwidget, GdkFrameClock* clock, gpointer ) { Gtk::Widget* const widget = Glib::wrap( cwidget ); if( auto area = dynamic_cast( widget ) ) { return area->deceleration_tick_impl( clock ); } return G_SOURCE_REMOVE; } gboolean DrawAreaBase::deceleration_tick_impl( GdkFrameClock* clock ) { const gint64 current_time = gdk_frame_clock_get_frame_time( clock ); m_deceleration.elapsed += ( current_time - m_deceleration.last_time ) * kDecelerationRatio; m_deceleration.last_time = current_time; const double exp_part = std::exp( -kDecelerationFriction * m_deceleration.elapsed ); const double dy = -kDecelerationFriction * exp_part * m_deceleration.initial_dy; #ifdef _DEBUG std::cout << "DrawAreaBase::deceleration_tick_impl dy = " << dy << std::endl; #endif if( std::fabs( dy ) < 1.0 ) { cancel_deceleration(); return G_SOURCE_REMOVE; } // 減速中は必ずスクロール処理を実施する m_wait_scroll = Monotonic::duration::zero(); m_scrollinfo.reset(); m_scrollinfo.dy = static_cast< int >( dy ); m_scrollinfo.mode = SCROLL_NORMAL; exec_scroll(); return G_SOURCE_CONTINUE; } void DrawAreaBase::cancel_deceleration() { if( m_deceleration.id && GTK_IS_WIDGET( this->gobj() ) ) { gtk_widget_remove_tick_callback( GTK_WIDGET( this->gobj() ), m_deceleration.id ); m_deceleration.id = 0; } } jdim-0.7.0/src/article/drawareabase.h000066400000000000000000000516711417047150700174650ustar00rootroot00000000000000// ライセンス: GPL2 // スレ表示部のベースクラス #ifndef _DRAWAREABASE_H #define _DRAWAREABASE_H #include "caret.h" #include "scrollinfo.h" #include "jdlib/refptr_lock.h" #include "control/control.h" #include "colorid.h" #include "cssmanager.h" #include #include #include namespace ARTICLE { struct LAYOUT; class LayoutTree; class EmbeddedImage; // マウスボタンプレスとリリースのシグナル。リリース時にマウスがリンクの上にある時そのURLを渡す typedef sigc::signal< bool, std::string, int, GdkEventButton* > SIG_BUTTON_PRESS; typedef sigc::signal< bool, std::string, int, GdkEventButton* > SIG_BUTTON_RELEASE; typedef sigc::signal< bool, GdkEventCrossing* > SIG_LEAVE_NOTIFY; typedef sigc::signal< bool, GdkEventMotion* > SIG_MOTION_NOTIFY; typedef sigc::signal< bool, GdkEventKey* > SIG_KEY_PRESS; typedef sigc::signal< bool, GdkEventKey* > SIG_KEY_RELEASE; typedef sigc::signal< bool, GdkEventScroll* > SIG_SCROLL_EVENT; typedef sigc::signal< void, std::string, std::string, int > SIG_ON_URL; typedef sigc::signal< void > SIG_LEAVE_URL; struct URLINFO { std::string url; int res_number; }; // 範囲選択用 struct SELECTION { bool select; CARET_POSITION caret_from; CARET_POSITION caret_to; std::string str; // 現在の選択文字列 std::string str_pre; // 一つ前の選択文字列 std::vector< URLINFO > imgurls;// 選択範囲に含まれる画像URL }; // 描画情報 struct DRAWINFO { bool draw; int y; int height; }; // フォント情報 struct FONTINFO { std::string fontname; Pango::FontDescription pfd; int ascent; int descent; int height; int underline_pos; // 下線の位置(文字の上からのピクセル値) int br_size; // 改行量 (ピクセル値) int mrg_right; // wrap計算のときに使用する右マージン幅 (ピクセル値) }; // 描画領域 struct CLIPINFO { int width_view; int pos_y; int upper; // 画面上、小さい値 int lower; // 画面下、大きい値 }; /////////////////////////////////// class DrawAreaBase : public Gtk::HBox { SIG_BUTTON_PRESS m_sig_button_press; SIG_BUTTON_RELEASE m_sig_button_release; SIG_SCROLL_EVENT m_sig_scroll_event; SIG_LEAVE_NOTIFY m_sig_leave_notify; SIG_MOTION_NOTIFY m_sig_motion_notify; SIG_KEY_PRESS m_sig_key_press; SIG_KEY_RELEASE m_sig_key_release; SIG_ON_URL m_sig_on_url; SIG_LEAVE_URL m_sig_leave_url; std::string m_url; // スピードを稼ぐためデータベースに直接アクセスする JDLIB::RefPtr_Lock< DBTREE::ArticleBase > m_article; // HBoxに張り付けるウイジット Gtk::DrawingArea m_view; Gtk::EventBox* m_event{}; Gtk::Scrollbar* m_vscrbar{}; // レイアウトツリー std::unique_ptr m_layout_tree; // 描画領域の幅、高さ( != ウィンドウサイズ ) int m_width_client{}; int m_height_client{}; //現在見ているレスの番号 int m_seen_current{}; // 描画用 Glib::RefPtr< Gdk::Window > m_window; // cairomm 1.12.0 がメモリリークを起こしたので // C API を使うことで問題を回避する std::unique_ptr< cairo_t, void ( * )( cairo_t* ) > m_cr; std::unique_ptr< cairo_surface_t, void ( * )( cairo_surface_t* ) > m_backscreen; Glib::RefPtr< Pango::Layout > m_pango_layout; Glib::RefPtr< Pango::Context > m_context; RECTANGLE m_rect_backscreen{}; // バックスクリーンが描画されている範囲 bool m_enable_draw; DRAWINFO m_drawinfo{}; // キャレット情報 CARET_POSITION m_caret_pos; // 現在のキャレットの位置(クリックやドラッグすると移動) CARET_POSITION m_caret_pos_pre; // 移動前のキャレットの位置(選択範囲の描画などで使用する) CARET_POSITION m_caret_pos_dragstart; // ドラッグを開始したキャレット位置 // 色 int m_colorid_text{}; // デフォルトの文字色 int m_colorid_back{}; // デフォルトの背景色 std::vector< Gdk::RGBA > m_color; // 枠 bool m_draw_frame{}; // 枠を描画する // 枠線に隠れる部分のバックアップを分ける std::unique_ptr< cairo_surface_t, void ( * )( cairo_surface_t* ) > m_back_frame_top; std::unique_ptr< cairo_surface_t, void ( * )( cairo_surface_t* ) > m_back_frame_bottom; bool m_ready_back_frame{}; // 範囲選択 SELECTION m_selection{}; // 検索結果のハイライト範囲 std::list< SELECTION > m_multi_selection; // レイアウト用 CORE::CSS_PROPERTY m_css_body; // body の cssプロパティ int m_fontid{}; int m_defaultfontid{}; int m_mailfontid{}; int m_defaultmailfontid{}; FONTINFO *m_font{}; // カレントフォント情報 FONTINFO m_defaultfont{}; // デフォルトフォント情報 FONTINFO m_aafont{}; // AA用フォント情報 FONTINFO m_mailfont{}; // メールフォント情報 bool m_aafont_initialized{}; bool m_mailfont_initialized{}; // スレビューで文字幅の近似を厳密にするか bool m_strict_of_char{}; // ビューのリサイズ用 bool m_configure_reserve{}; // true の時は再描画する前に再レイアウトする int m_configure_width{}; int m_configure_height{}; // スクロール情報 double m_smooth_dy{}; // GDK_SCROLL_SMOOTH のスクロール変化量 SCROLLINFO m_scrollinfo{}; guint32 m_wheel_scroll_time{}; // 前回ホイールを回した時刻 int m_goto_num_reserve{}; // 初期化時のジャンプ予約(レス番号) bool m_goto_bottom_reserve{}; // 初期化時のジャンプ予約(底) int m_pre_pos_y; // ひとつ前のスクロールバーの位置。スクロールした時の差分量計算に使用する std::vector< int > m_jump_history; // ジャンプ履歴 bool m_cancel_change_adjust{}; // adjust の値変更イベントをキャンセル std::unique_ptr< cairo_surface_t, void ( * )( cairo_surface_t* ) > m_back_marker; RECTANGLE m_clip_marker{}; bool m_ready_back_marker{}; using Monotonic = std::chrono::steady_clock; Monotonic::duration m_wait_scroll{}; // 処理落ちした時にスクロールにウエイトを入れる Monotonic::time_point m_scroll_time; // ウエイト時に最後にスクロールした時刻 // 状態 int m_x_pointer{}; int m_y_pointer{}; // 現在のマウスポインタの位置 bool m_key_press{}; // キーを押している bool m_key_locked{}; // 同じキーを押したままにしている guint m_keyval{}; bool m_clicked{}; bool m_drugging{}; // ドラッグ中 bool m_r_drugging{}; // 右ドラッグ中 std::string m_link_current; // 現在マウスポインタの下にあるリンクの文字列 LAYOUT* m_layout_current{}; // 現在マウスポインタの下にあるlayoutノード(下が空白ならnullptr) Glib::ustring m_cursor_type; // カーソルの形状 // 入力コントローラ CONTROL::Control m_control; // 埋め込み画像を保持するリスト std::list m_eimgs; // ブックマークアイコン Glib::RefPtr< Gdk::Pixbuf > m_pixbuf_bkmk; // 書き込みマークアイコン Glib::RefPtr< Gdk::Pixbuf > m_pixbuf_post; // 自分の書き込みに対するレスマークアイコン Glib::RefPtr< Gdk::Pixbuf > m_pixbuf_refer_post; // マウスのクリックとタッチスクリーンのタップ Glib::RefPtr< Gtk::GestureMultiPress > m_gesture_multipress; // タッチスクリーンのスクロール Glib::RefPtr< Gtk::GesturePan > m_gesture_pan; Glib::RefPtr< Gtk::GestureSwipe > m_gesture_swipe; double m_drag_start_y{}; // 慣性スクロール struct DecelerationInfo { double elapsed; // 換算された経過時間 double initial_dy; // スケーリングされた初速度(pixcels/frame) gint64 last_time; // 前回コールバックが呼び出された時間(frame) guint id; // コールバックのID } m_deceleration{}; public: SIG_BUTTON_PRESS sig_button_press(){ return m_sig_button_press; } SIG_BUTTON_RELEASE sig_button_release(){ return m_sig_button_release; } SIG_SCROLL_EVENT sig_scroll_event(){ return m_sig_scroll_event; } SIG_LEAVE_NOTIFY sig_leave_notify(){ return m_sig_leave_notify; } SIG_MOTION_NOTIFY sig_motion_notify(){ return m_sig_motion_notify; } SIG_KEY_PRESS sig_key_press(){ return m_sig_key_press; } SIG_KEY_RELEASE sig_key_release(){ return m_sig_key_release; } SIG_ON_URL sig_on_url(){ return m_sig_on_url; } SIG_LEAVE_URL sig_leave_url(){ return m_sig_leave_url; } explicit DrawAreaBase( const std::string& url ); ~DrawAreaBase(); const std::string& get_url() const { return m_url; } int width_client() const { return m_width_client; } int height_client() const { return m_height_client; } void clock_in(); void clock_in_smooth_scroll(); void focus_view(); void focus_out(); void set_enable_draw( const bool enable ){ m_enable_draw = enable; } // フォントID( fontid.h にある ID を指定) int get_fontid() const { return m_fontid; } void set_fontid( int id ){ m_fontid = id; m_defaultfontid = id; } int get_mailfontid() const { return m_fontid; } void set_mailfontid( int id ){ m_mailfontid = id; m_defaultmailfontid = id; } // 新着セパレータのあるレス番号の取得とセット int get_separator_new() const; void set_separator_new( int num ); void hide_separator_new(); // 現在のポインタの下にあるレス番号取得 int get_current_res_num() const; // 全選択 void select_all(); // 範囲選択中の文字列 std::string str_selection() const; const std::string& str_pre_selection() const { return m_selection.str_pre; } // 一つ前の選択文字列 // 範囲選択を開始したレス番号 int get_selection_resnum_from() const; // 範囲選択を終了したレス番号 int get_selection_resnum_to() const; // 範囲選択に含まれる画像URLのリスト const std::vector< URLINFO >& get_selection_imgurls() const { return m_selection.imgurls; } int get_seen_current() const { return m_seen_current; } // 現在見ているレスの番号 int get_goto_num_reserve() const { return m_goto_num_reserve; } // 初期化時のジャンプ予約(レス番号) int max_number() const; // 表示されている最後のレスの番号 // レスをappendして再レイアウト void append_res( const int from_num, const int to_num ); // リストで指定したレスをappendして再レイアウト void append_res( const std::list< int >& list_resnum ); // リストで指定したレスをappendして再レイアウト( 連結情報付き ) // list_joint で連結指定したレスはヘッダを取り除いて前のレスに連結する void append_res( const std::list< int >& list_resnum, const std::list< bool >& list_joint ); // html をappendして再レイアウト void append_html( const std::string& html ); // datをappendして再レイアウト void append_dat( const std::string& dat, int num ); // 全画面消去 void clear_screen(); // 再描画 // 再レイアウトはしないが configureの予約がある場合は再レイアウトしてから再描画する void redraw_view_force(); void redraw_view(); // スクロール方向指定 bool set_scroll( const int control ); // マウスホイールの処理 void wheelscroll( GdkEventScroll* event ); // ジャンプ void set_jump_history( const int num ); // ジャンプ履歴にスレ番号を登録 void goto_num( int num ); void goto_next_res(); void goto_pre_res(); void goto_new(); void goto_top(); void goto_bottom(); void goto_back(); // 検索 int search( const std::list< std::string >& list_query, const bool reverse ); int search_move( const bool reverse ); void clear_highlight(); // 実況モード void live_start(); void live_stop(); void update_live_speed( const int sec ); protected: // リアライズしたか // Gtk::Widget::is_realized() はうまく動作しない bool is_drawarea_realized() const noexcept { return static_cast(m_window); } // 文字色のID( colorid.h にある ID を指定) int get_colorid_text() const { return m_colorid_text; } void set_colorid_text( int id ){ m_colorid_text = id; } // 背景色のID( colorid.h にある ID を指定) int get_colorid_back() const; void set_colorid_back( int id ){ m_colorid_back = id; } // 共通セットアップ // show_abone : あぼーんされたスレも表示 // show_scrbar : スクロールバーを最初から表示 // show_multispace : 連続空白も表示 void setup( const bool show_abone, const bool show_scrbar, const bool show_multispace ); // レイアウト処理 virtual bool exec_layout(); bool exec_layout_impl( const bool is_popup, const int offset_y ); // DrawAreaに枠を描画する void set_draw_frame( bool draw_frame ){ m_draw_frame = draw_frame; } // リサイズした virtual bool slot_configure_event( GdkEventConfigure* event ); Gtk::DrawingArea* get_view(){ return &m_view; } Gtk::Scrollbar* get_vscrbar(){ return m_vscrbar; } private: // 初期化関係 void clear(); void create_scrbar(); void init_color(); void init_font(); void init_fontinfo( FONTINFO& fi, std::string& fontname ); // レイアウト処理 void adjust_layout_baseline( LAYOUT* header ); void set_align( LAYOUT* div, int id_end, int align ); void set_align_line( LAYOUT* div, LAYOUT* layout_from, LAYOUT* layout_to, RECTANGLE* rect_from, RECTANGLE* rect_to, int width_line, int align ); void layout_one_text_node( LAYOUT* node, int& node_x, int& node_y, int& br_size, const int width_view ); void layout_one_img_node( LAYOUT* node, int& node_x, int& node_y, int& brsize, const int width_view, const bool init_popupwin, const int mrg_right, const int mrg_bottom ); // 文字の幅などの情報 int get_width_of_one_char( const char* str, int& byte, char& pre_char, bool& wide_mode, const int mode ); bool set_init_wide_mode( const char* str, const int pos_start, const int pos_to ); bool is_wrapped( const int x, const int border, const char* str ) const; // スクリーン描画 // y から height の高さ分だけ描画する // height == 0 ならスクロールした分だけ描画( y は無視 ) bool draw_screen( const int y, const int height ); void exec_draw_screen( const int y_redraw, const int height_redraw ); bool draw_one_node( LAYOUT* layout, const CLIPINFO& ci ); void draw_div( LAYOUT* layout_div, const CLIPINFO& ci ); bool get_selection_byte( const LAYOUT* layout, const SELECTION& selection, size_t& byte_from, size_t& byte_to ) const; void draw_one_text_node( LAYOUT* layout, const CLIPINFO& ci ); void draw_string( LAYOUT* node, const CLIPINFO& ci, const int color, const int color_back, const int byte_from, const int byte_to ); bool draw_one_img_node( LAYOUT* layout, const CLIPINFO& ci ); char get_layout_fontid(LAYOUT *layout ) const; void set_node_font( LAYOUT* layout ); // drawarea がリサイズ実行 void configure_impl(); // 整数 -> 文字変換してノードに発言数をセット int set_num_id( LAYOUT* layout ); // スクロール関係 void exec_scroll(); // スクロールやジャンプを実行して再描画 int get_vscr_val() const; int get_vscr_maxval() const; // キャレット関係 bool is_pointer_on_rect( const RECTANGLE* rect, LAYOUT* layout, const char* text, const int pos_start, const int pos_to, const int x, const int y, int& pos, int& width_line, int& char_width, int& byte_char ); LAYOUT* set_caret( CARET_POSITION& caret_pos, int x, int y ); bool set_carets_dclick( CARET_POSITION& caret_left, CARET_POSITION& caret_right, const int x, const int y, const bool triple ); // 範囲選択関係 bool set_selection( const CARET_POSITION& caret_left, const CARET_POSITION& caret_right ); bool set_selection( const CARET_POSITION& caret_pos ); bool set_selection( const CARET_POSITION& caret_pos, RECTANGLE* rect ); bool set_selection_str(); bool is_caret_on_selection( const CARET_POSITION& caret_pos ) const; std::string get_selection_as_url( const CARET_POSITION& caret_pos ) const; // マウスが動いた時の処理 bool motion_mouse(); // 現在のポインターの下のノードからカーソルのタイプを決定する Glib::ustring get_cursor_type() const; // カーソルの形状の変更 void change_cursor( const Glib::ustring& cursor_type ); // スクロールマーカの描画 void draw_marker(); // 枠の描画 void draw_frame(); // バックスクリーンを矩形で塗りつぶす void fill_backscreen( const int colorid, int x, int y, int width, int height ); // Pixbufの内容をバックスクリーンに貼り付ける void paint_backscreen( const Glib::RefPtr< Gdk::Pixbuf >& pixbuf, int src_x, int src_y, int dest_x, int dest_y, int width, int height ); // スロット void slot_change_adjust(); bool slot_draw( const Cairo::RefPtr< Cairo::Context >& cr ); bool slot_scroll_event( GdkEventScroll* event ); bool slot_leave_notify_event( GdkEventCrossing* event ); void slot_realize(); bool slot_button_press_event( GdkEventButton* event ); bool slot_button_release_event( GdkEventButton* event ); bool slot_motion_notify_event( GdkEventMotion* event ); bool slot_key_press_event( GdkEventKey* event ); bool slot_key_release_event( GdkEventKey* event ); void setup_event_controller(); void slot_multipress_pressed( int n_press, double x, double y ); void slot_multipress_released( int n_press, double x, double y ); void slot_pan_begin( double start_x, double start_y ); void slot_pan_update( double offset_x, double offset_y ); void slot_gesture_end( GdkEventSequence* sequence ); void slot_swipe( double velocity_x, double velocity_y ); // 慣性スクロール static gboolean deceleration_tick_cb( GtkWidget* cwidget, GdkFrameClock* clock, gpointer ); gboolean deceleration_tick_impl( GdkFrameClock* clock ); void cancel_deceleration(); }; // // 文字列を wrap するか判定する関数 // // str != nullptr なら禁則処理も考える // inline bool DrawAreaBase::is_wrapped( const int x, const int border, const char* str ) const { const unsigned char* tmpchar = ( const unsigned char* ) str; if( x < border ) return false; if( ! tmpchar ) return true; // 禁則文字 if( ( tmpchar[ 0 ] == ',' ) || ( tmpchar[ 0 ] == '.' ) // UTF-8で"。" || ( tmpchar[ 0 ] == 0xe3 && tmpchar[ 1 ] == 0x80 && tmpchar[ 2 ] == 0x82 ) // UTF-8で"、" || ( tmpchar[ 0 ] == 0xe3 && tmpchar[ 1 ] == 0x80 && tmpchar[ 2 ] == 0x81 ) ) return false; return true; } } #endif jdim-0.7.0/src/article/drawareainfo.cpp000066400000000000000000000012551417047150700200320ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "drawareainfo.h" #include "fontid.h" using namespace ARTICLE; DrawAreaInfo::DrawAreaInfo( const std::string& url ) : DrawAreaBase( url ) { #ifdef _DEBUG std::cout << "DrawAreaInfo::DrawAreaInfo url = " << url << std::endl; #endif // フォント設定 set_fontid( FONT_ENTRY_DEFAULT ); // 文字色 set_colorid_text( COLOR_CHAR_ENTRY_DEFAULT ); // 背景色 set_colorid_back( COLOR_BACK_ENTRY_DEFAULT ); const bool show_abone = false; const bool show_scrbar = true; const bool show_multispace = false; setup( show_abone, show_scrbar, show_multispace ); } jdim-0.7.0/src/article/drawareainfo.h000066400000000000000000000004441417047150700174760ustar00rootroot00000000000000// ライセンス: GPL2 // 情報表示部 #ifndef _DRAWAREAINFO_H #define _DRAWAREAINFO_H #include "drawareabase.h" namespace ARTICLE { class DrawAreaInfo : public ARTICLE::DrawAreaBase { public: explicit DrawAreaInfo( const std::string& url ); }; } #endif jdim-0.7.0/src/article/drawareamain.cpp000066400000000000000000000007171417047150700200250ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "drawareamain.h" using namespace ARTICLE; DrawAreaMain::DrawAreaMain( const std::string& url ) : DrawAreaBase( url ) { #ifdef _DEBUG std::cout << "DrawAreaMain::DrawAreaMain url = " << url << std::endl; #endif const bool show_abone = false; const bool show_scrbar = true; const bool show_multispace = false; setup( show_abone, show_scrbar, show_multispace ); } jdim-0.7.0/src/article/drawareamain.h000066400000000000000000000004471417047150700174720ustar00rootroot00000000000000// ライセンス: GPL2 // メイン表示部 #ifndef _DRAWAREAMAIN_H #define _DRAWAREAMAIN_H #include "drawareabase.h" namespace ARTICLE { class DrawAreaMain : public ARTICLE::DrawAreaBase { public: explicit DrawAreaMain( const std::string& url ); }; } #endif jdim-0.7.0/src/article/drawareapopup.cpp000066400000000000000000000027611417047150700202450ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "drawareapopup.h" #include "config/globalconf.h" #include "colorid.h" #include "fontid.h" using namespace ARTICLE; // スクロールバーが付くとレイアウトがずれるのでクライアント領域の横幅をその分広げる enum { POPUP_OFFSET_Y = 1 }; // show_abone == true ならあぼーんされたスレも表示 DrawAreaPopup::DrawAreaPopup( const std::string& url, bool show_abone ) : DrawAreaBase( url ) { #ifdef _DEBUG std::cout << "DrawAreaPopup::DrawAreaPopup url = " << url << std::endl; #endif // フォント設定 set_fontid( FONT_POPUP ); // 背景色 set_colorid_back( COLOR_BACK_POPUP ); const bool show_scrbar = false; const bool show_multispace = true; setup( show_abone, show_scrbar, show_multispace ); set_draw_frame( true ); } // // レイアウト実行 // bool DrawAreaPopup::exec_layout() { #ifdef _DEBUG std::cout << "DrawAreaPopup::exec_layout() " << get_url() << std::endl; #endif // is_popup = true return exec_layout_impl( true, POPUP_OFFSET_Y ); } // // drawarea がリサイズした // // ポップアップの場合は先頭に戻る // bool DrawAreaPopup::slot_configure_event( GdkEventConfigure* event ) { if( ! is_drawarea_realized() ) return true; #ifdef _DEBUG std::cout << "DrawAreaPopup::slot_configure_event\n"; #endif if( exec_layout() ) redraw_view(); DrawAreaBase::goto_top(); return true; } jdim-0.7.0/src/article/drawareapopup.h000066400000000000000000000011111417047150700176760ustar00rootroot00000000000000// ライセンス: GPL2 // ポップアップの表示部 #ifndef _DRAWAREAPOPUP_H #define _DRAWAREAPOPUP_H #include "drawareabase.h" namespace ARTICLE { class DrawAreaPopup : public ARTICLE::DrawAreaBase { public: // show_abone == true ならあぼーんされたスレも表示 DrawAreaPopup( const std::string& url, bool show_abone ); protected: // レイアウト実行 bool exec_layout() override; // リサイズした bool slot_configure_event( GdkEventConfigure* event ) override; }; } #endif jdim-0.7.0/src/article/embeddedimage.cpp000066400000000000000000000073731417047150700201330ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "embeddedimage.h" #include "articleadmin.h" #include "jdlib/imgloader.h" #include "jdlib/miscmsg.h" #include "dbimg/imginterface.h" #include "dbimg/img.h" #include "config/globalconf.h" #include "message/messageadmin.h" #include // // スレッドのランチャ // static std::mutex eimg_launcher_mutex; int redraw_counter = 0; // 0 になったとき再描画する void* eimg_launcher( void* dat ) { ++redraw_counter; // 遅いCPUの場合は同時に画像をリサイズしようとすると固まった様になるので // mutexをかけて同時にリサイズしないようにする std::lock_guard< std::mutex > lock( eimg_launcher_mutex ); #ifdef _DEBUG std::cout << "start eimg_launcher" << std::endl; #endif ARTICLE::EmbeddedImage* eimg = (ARTICLE::EmbeddedImage* )( dat ); eimg->resize_thread(); // 再描画 --redraw_counter; if( ! redraw_counter ){ ARTICLE::get_admin()->set_command( "redraw_current_view" ); MESSAGE::get_admin()->set_command( "redraw_current_view" ); } #ifdef _DEBUG std::cout << "end" << std::endl; #endif return nullptr; } ///////////////////////////////// using namespace ARTICLE; EmbeddedImage::EmbeddedImage( const std::string& url ) : m_url( url ) , m_img{ DBIMG::get_img( m_url ) } { assert( m_img ); } EmbeddedImage::~EmbeddedImage() { #ifdef _DEBUG std::cout << "EmbeddedImage::~EmbeddedImage url = " << m_url << std::endl; #endif // デストラクタの中からdispatchを呼ぶと落ちるので dispatch不可にする set_dispatchable( false ); stop(); wait(); } void EmbeddedImage::stop() { #ifdef _DEBUG std::cout << "EmbeddedImage::stop" << std::endl; #endif if( m_imgloader ) m_imgloader->request_stop(); } void EmbeddedImage::wait() { #ifdef _DEBUG std::cout << "EmbeddedImage::wait" << std::endl; #endif m_thread.join(); } void EmbeddedImage::show() { #ifdef _DEBUG std::cout << "EmbeddedImage::show url = " << m_url << std::endl; #endif if( m_thread.is_running() ) return; // 画像読み込み if( ! m_img->is_cached() ) return; const int width = m_img->get_width_emb(); const int height = m_img->get_height_emb(); if( ! width || ! height ) return; // スレッド起動して縮小 if( ! m_thread.create( eimg_launcher, ( void* )this, JDLIB::NODETACH ) ){ MISC::ERRMSG( "EmbeddedImage::show : could not start thread" ); } } // リサイズのスレッド void EmbeddedImage::resize_thread() { #ifdef _DEBUG std::cout << "EmbeddedImage::resize_thread url = " << m_url << std::endl; #endif const int width = m_img->get_width_emb(); const int height = m_img->get_height_emb(); bool pixbufonly = true; if( m_img->get_type() == DBIMG::T_BMP ) pixbufonly = false; // BMP の場合 pixbufonly = true にすると真っ黒になる m_imgloader = JDLIB::ImgLoader::get_loader( m_img->get_cache_path() ); Glib::RefPtr pixbuf = m_imgloader->get_pixbuf( pixbufonly ); if( pixbuf ){ Gdk::InterpType interptype = Gdk::INTERP_NEAREST; if( CONFIG::get_imgemb_interp() == 1 ) interptype = Gdk::INTERP_BILINEAR; else if( CONFIG::get_imgemb_interp() >= 2 ) interptype = Gdk::INTERP_HYPER; m_pixbuf = pixbuf->scale_simple( width, height, interptype ); } m_imgloader.reset(); // メインスレッドにリサイズが終わったことを知らせて // メインスレッドがpthread_join()を呼び出す // そうしないとメモリを食い潰す dispatch(); } // // ディスパッチャのコールバック関数 // void EmbeddedImage::callback_dispatch() { wait(); } jdim-0.7.0/src/article/embeddedimage.h000066400000000000000000000015521417047150700175710ustar00rootroot00000000000000// ライセンス: GPL2 // // 埋め込み画像クラス // #ifndef _EMVEDDEDIMAGE_H #define _EMVEDDEDIMAGE_H #include #include "skeleton/dispatchable.h" #include "jdlib/jdthread.h" #include "jdlib/imgloader.h" namespace DBIMG { class Img; } namespace ARTICLE { class EmbeddedImage : public SKELETON::Dispatchable { std::string m_url; Glib::RefPtr< Gdk::Pixbuf > m_pixbuf; DBIMG::Img* m_img; JDLIB::Thread m_thread; Glib::RefPtr< JDLIB::ImgLoader > m_imgloader; public: explicit EmbeddedImage( const std::string& url ); ~EmbeddedImage(); Glib::RefPtr< Gdk::Pixbuf > get_pixbuf(){ return m_pixbuf; } void show(); void resize_thread(); private: void stop(); void wait(); void callback_dispatch() override; }; } #endif jdim-0.7.0/src/article/font.cpp000066400000000000000000000074671417047150700163510ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "font.h" #include "jdlib/miscutil.h" #include "fontid.h" #include "config/globalconf.h" #include #include struct WIDTH_DATA { // 半角モードの時の幅 unsigned int *width; // 全角モードの時の幅 unsigned int width_wide; }; static WIDTH_DATA* width_of_char[ FONT_NUM ]; static bool strict_of_char = false; // UnicodeのPlane 0 基本多言語面(BMP)からPlane 3 第三漢字面(TIP)までキャッシュを持つ。 // 現状のメモリ消費を抑えるためPlane 4からPlane 13は将来割り当てられたときにキャッシュ対応する。 constexpr int kMaxCacheCodePoint{ 0x40000 }; // // 初期化 // void ARTICLE::init_font() { // スレビューで文字幅の近似を厳密にするか strict_of_char = CONFIG::get_strict_char_width(); for( WIDTH_DATA*& char_data : width_of_char ) { if( char_data ) { for( int j = 0; j < kMaxCacheCodePoint; ++j ){ if( char_data[ j ].width ) delete[] char_data[ j ].width; } delete[] char_data; char_data = nullptr; } } } // // 登録された文字の幅を返す関数 // // utfstr : 入力文字 (UTF-8) // byte : 長さ(バイト) utfstr が ascii なら 1, UTF-8 なら 2 or 3 or 4 を入れて返す // pre_char : ひとつ前の文字 ( 前の文字が全角の場合は 0 ) // width : 半角モードでの幅 // width_wide : 全角モードでの幅 // mode : fontid.h で定義されているフォントのID // 戻り値 : 登録されていればtrue // bool ARTICLE::get_width_of_char( const char* utfstr, int& byte, const char pre_char, int& width, int& width_wide, const int mode ) { byte = 0; width = 0; width_wide = 0; if( ! width_of_char[ mode ] ){ width_of_char[ mode ] = new WIDTH_DATA[ kMaxCacheCodePoint ]{} ; } const int c32 = MISC::utf8toucs2( utfstr, byte ); if( byte > 0 && c32 < kMaxCacheCodePoint ){ // 全角モードの幅 width_wide = width_of_char[ mode ][ c32 ].width_wide; // 半角モードの幅 width = width_wide; // 厳密に求める場合 if( byte == 1 && strict_of_char ){ if( ! width_of_char[ mode ][ c32 ].width ){ width_of_char[ mode ][ c32 ].width = new unsigned int[ 128 ]{} ; } const int pre_char_num = ( int ) pre_char; if( pre_char_num < 128 ) width = width_of_char[ mode ][ c32 ].width[ pre_char_num ]; } } // Plane 14 追加特殊用途面(SSP) // 制御コードが追加されたら条件を追加する else if( 0xE0001 == c32 || ( 0xE0020 <= c32 && c32 <= 0xE007F ) // タグ文字 || ( 0xE0100 <= c32 && c32 <= 0xE01EF ) // 異字体セレクタ ) { width = width_wide = 0; return true; } if( width && width_wide ) return true; else if( width == -1 ){ // フォント幅の取得に失敗した場合 width = width_wide = 0; return true; } return false; } // // 文字幅を登録する関数 // // width == -1 はフォント幅の取得に失敗した場合 // void ARTICLE::set_width_of_char( const char* utfstr, int& byte, const char pre_char, const int width, const int width_wide, const int mode ) { const int c32 = MISC::utf8toucs2( utfstr, byte ); if( ! byte ) return; if( c32 >= kMaxCacheCodePoint ) return; // 半角モードの幅を厳密に求める場合 if( byte == 1 && strict_of_char ){ const int pre_char_num = ( int ) pre_char; if( pre_char_num < 128 ) width_of_char[ mode ][ c32 ].width[ pre_char_num ] = width; } // 全角モードの幅 width_of_char[ mode ][ c32 ].width_wide = width_wide; } jdim-0.7.0/src/article/font.h000066400000000000000000000017721417047150700160070ustar00rootroot00000000000000// ライセンス: GPL2 // 文字の幅とかを記録しておくデータベース #ifndef _FONT_H #define _FONT_H namespace ARTICLE { void init_font(); // 登録された文字の幅を返す関数 // utfstr : 入力文字 (UTF-8) // byte : 長さ(バイト) utfstr が ascii なら 1, UTF-8 なら 2 or 3 or 4 を入れて返す // pre_char : ひとつ前の文字 ( 前の文字が全角の場合は 0 ) // width : 半角モードでの幅 // width_wide : 全角モードでの幅 // mode : fontid.h で定義されているフォントのID // 戻り値 : 登録されていればtrue bool get_width_of_char( const char* utfstr, int& byte, const char pre_char, int& width, int& width_wide, const int mode ); // 文字幅を登録する関数 // width == -1 はフォント幅の取得に失敗した場合 void set_width_of_char( const char* utfstr, int& byte, const char pre_char, const int width, const int width_wide, const int mode ); } #endif jdim-0.7.0/src/article/layouttree.cpp000066400000000000000000000420731417047150700175700ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "layouttree.h" #include "dbtree/interface.h" #include "dbtree/articlebase.h" #include "dbtree/nodetreedummy.h" #include "jdlib/miscutil.h" #include "config/globalconf.h" #include "global.h" #include "colorid.h" #include "fontid.h" #include "cssmanager.h" enum { SIZE_OF_HEAP = 256 * 1024, STEP_ID = 10, STEP_SEPARATOR = 1, MAX_IMGITEM = 512 // struct IMGDATA.item[] のサイズ }; // 埋め込み画像用構造体 namespace ARTICLE { struct IMGITEM { char* link; DBTREE::NODE* node; }; struct IMGDATA { IMGITEM item[ MAX_IMGITEM ]; int num; }; } using namespace ARTICLE; // show_abone : true ならあぼーんしたレスも表示する // show_multispace : true なら連続空白ノードも表示 LayoutTree::LayoutTree( const std::string& url, const bool show_abone, const bool show_multispace ) : m_heap( SIZE_OF_HEAP ) , m_url( url ) , m_show_abone( show_abone ) , m_show_multispace( show_multispace ) { #ifdef _DEBUG std::cout << "LayoutTree::LayoutTree : url = " << url << " show_abone = " << m_show_abone << std::endl; #endif m_article = DBTREE::get_article( m_url ); assert( m_article ); clear(); } LayoutTree::~LayoutTree() { #ifdef _DEBUG std::cout << "LayoutTree::~LayoutTree : url = " << m_url << std::endl; #endif clear(); } void LayoutTree::clear() { m_heap.clear(); m_map_header.clear(); m_last_header = nullptr; m_max_res_number = 0; m_id_header = -STEP_ID; // ルートヘッダのIDが 0 になるように -STEP_ID を入れておく m_root_header = create_layout_header(); m_local_nodetree.reset(); m_last_div = nullptr; // 新着セパレータ作成 m_separator_new = 0; m_separator_new_reserve = 0; m_separator_header = create_separator(); } // RECTANGLE型のメモリ確保 RECTANGLE* LayoutTree::create_rect() { RECTANGLE* rect = m_heap.heap_alloc(); rect->end = true; return rect; } // // 基本レイアウトノード作成 // LAYOUT* LayoutTree::create_layout( const int type ) { LAYOUT* tmplayout = m_heap.heap_alloc(); tmplayout->type = type; tmplayout->id_header = m_id_header; tmplayout->id = m_id_layout++; tmplayout->header = m_last_header; tmplayout->div = m_last_div; return tmplayout; } // // ヘッダノード作成 // // ヘッダ自体もdiv要素 // LAYOUT* LayoutTree::create_layout_header() { m_last_layout = nullptr; m_id_layout = 0; m_id_header += STEP_ID; int classid = CORE::get_css_manager()->get_classid( "res" ); LAYOUT* header = create_layout_div( classid ); header->type = DBTREE::NODE_HEADER; m_last_layout = header; if( m_last_header ) m_last_header->next_header = header; m_last_header = header; header->header = header; return header; } // // テキストノード作成 // LAYOUT* LayoutTree::create_layout_text( const char* text, const unsigned char* color_text, bool bold ) { LAYOUT* tmplayout = create_layout( DBTREE::NODE_TEXT ); m_last_layout->next_layout = tmplayout; m_last_layout = tmplayout; tmplayout->text = text; tmplayout->color_text = color_text; tmplayout->bold = bold; return tmplayout; } // // リンクノード作成 // LAYOUT* LayoutTree::create_layout_link( const char* text, const char* link, const unsigned char* color_text, bool bold ) { LAYOUT* tmplayout = create_layout_text( text, color_text, bold ); tmplayout->type = DBTREE::NODE_LINK; tmplayout->link = link; return tmplayout; } // 発言回数(IDの出現数)ノード // LAYOUT* LayoutTree::create_layout_idnum( const char* text, const unsigned char* color_text, bool bold ) { LAYOUT* tmplayout = create_layout_text( text, color_text, bold ); tmplayout->type = DBTREE::NODE_IDNUM; return tmplayout; } // // 改行ノード作成 // LAYOUT* LayoutTree::create_layout_br( const bool nobr ) { if( nobr ){ return create_layout_text( " ", nullptr, false ); } LAYOUT* tmplayout = create_layout( DBTREE::NODE_BR ); m_last_layout->next_layout = tmplayout; m_last_layout = tmplayout; return tmplayout; } // // 水平線ノード作成 // LAYOUT* LayoutTree::create_layout_hr() { LAYOUT* tmplayout = create_layout( DBTREE::NODE_HR ); m_last_layout->next_layout = tmplayout; m_last_layout = tmplayout; return tmplayout; } // // 水平スペースノード作成 // LAYOUT* LayoutTree::create_layout_hspace( const int type ) { LAYOUT* tmplayout = create_layout( type ); m_last_layout->next_layout = tmplayout; m_last_layout = tmplayout; return tmplayout; } // // divノード作成 // LAYOUT* LayoutTree::create_layout_div( const int id ) { LAYOUT* div = create_layout( DBTREE::NODE_DIV ); if( m_last_layout ) m_last_layout->next_layout = div; m_last_layout = div; m_last_div = div; div->css = m_heap.heap_alloc(); *div->css = CORE::get_css_manager()->get_property( id ); return div; } // // img ノード作成 // LAYOUT* LayoutTree::create_layout_img( const char* link ) { LAYOUT* tmplayout = create_layout( DBTREE::NODE_IMG ); m_last_layout->next_layout = tmplayout; m_last_layout = tmplayout; tmplayout->link = link; return tmplayout; } // // sssp ノード作成 // LAYOUT* LayoutTree::create_layout_sssp( const char* link ) { LAYOUT* tmplayout = create_layout_img( link ); tmplayout->type = DBTREE::NODE_SSSP; return tmplayout; } // // nodetreeのノード構造をコピーし、レイアウトツリーの一番最後に加える // // joint == true の時はヘッダを作らないで、本文を前のツリーの続きに連結する // void LayoutTree::append_node( DBTREE::NODE* node_header, const bool joint ) { if( ! node_header ) return; DBTREE::HEADERINFO* headinfo = node_header->headinfo; if( ! headinfo ) return; const int res_number = node_header->id_header; #ifdef _DEBUG std::cout << "LayoutTree::append_node num = " << res_number << " show_abone = " << m_show_abone << std::endl; #endif // あぼーん if( ! m_article->empty() && ! m_show_abone && m_article->get_abone( res_number ) ){ append_abone_node( node_header ); return; } // 連結モード // 本文ブロックだけ追加 if( joint ){ create_layout_br( m_last_dom_attr & CORE::DOMATTR_NOBR ); append_block( headinfo->block[ DBTREE::BLOCK_MES ], res_number, nullptr, m_last_dom_attr ); } else{ const CORE::DOM* dom = CORE::get_css_manager()->get_dom(); m_last_dom_attr = 0; IMGDATA imgdata; imgdata.num = 0; LAYOUT* header = create_layout_header(); header->res_number = res_number; header->node = node_header; if( res_number > m_max_res_number ) m_max_res_number = res_number; m_map_header[ res_number ] = header; while( dom ){ m_last_dom_attr = dom->attr; switch( dom->nodetype ){ case CORE::DOMNODE_DIV: create_layout_div( dom->dat ); break; case CORE::DOMNODE_BLOCK: append_block( headinfo->block[ dom->dat ], res_number, &imgdata, dom->attr ); break; case CORE::DOMNODE_TEXT: create_layout_text( dom->chardat, nullptr, false ); break; case CORE::DOMNODE_IMAGE: // インライン画像 if( imgdata.num && CONFIG::get_use_inline_image() ){ #ifdef _DEBUG std::cout << "LayoutTree::append_node : create image\n"; #endif create_layout_br(); create_layout_br(); for( int i = 0; i < imgdata.num; ++i ){ #ifdef _DEBUG std::cout << imgdata.item[ i ].link << std::endl; #endif LAYOUT* tmplayout = create_layout_img( imgdata.item[ i ].link ); tmplayout->res_number = res_number; tmplayout->link = imgdata.item[ i ].link; tmplayout->node = imgdata.item[ i ].node; } } break; } dom = dom->next_dom; } } } // // 名前やメールなどのブロックのコピー // void LayoutTree::append_block( DBTREE::NODE* block, const int res_number, IMGDATA* imgdata, const int dom_attr ) { if( ! block ) return; DBTREE::NODE* tmpnode = block->next_node; while( tmpnode ){ LAYOUT* tmplayout = nullptr; switch( tmpnode->type ){ case DBTREE::NODE_TEXT: tmplayout = create_layout_text( tmpnode->text, &tmpnode->color_text, tmpnode->bold ); break; case DBTREE::NODE_LINK: tmplayout = create_layout_link( tmpnode->text, tmpnode->linkinfo->link, &tmpnode->color_text, tmpnode->bold ); // 画像リンクのurlを集める if( imgdata && tmpnode->linkinfo->imglink && imgdata->num < MAX_IMGITEM ){ imgdata->item[ imgdata->num ].link = tmpnode->linkinfo->imglink; imgdata->item[ imgdata->num++ ].node = tmpnode; } break; case DBTREE::NODE_SSSP: if( CONFIG::get_show_ssspicon() ){ tmplayout = create_layout_sssp( tmpnode->linkinfo->link ); } else{ // 次が改行ノードの時は飛ばす if( tmpnode->next_node && tmpnode->next_node->type == DBTREE::NODE_BR ) tmpnode = tmpnode->next_node; } break; case DBTREE::NODE_IDNUM: tmplayout = create_layout_idnum( tmpnode->text, &tmpnode->color_text, tmpnode->bold ); break; case DBTREE::NODE_BR: tmplayout = create_layout_br( dom_attr & CORE::DOMATTR_NOBR ); break; case DBTREE::NODE_HR: tmplayout = create_layout_hr(); break; case DBTREE::NODE_ZWSP: // 幅0スペース tmplayout = create_layout_hspace( tmpnode->type ); break; case DBTREE::NODE_MULTISP: // 連続半角スペース if( m_show_multispace ) tmplayout = create_layout_text( tmpnode->text, &tmpnode->color_text, tmpnode->bold ); break; case DBTREE::NODE_HTAB: // 水平タブ tmplayout = create_layout_hspace( tmpnode->type ); break; } if( tmplayout ){ tmplayout->res_number = res_number; tmplayout->node = tmpnode; } tmpnode = tmpnode->next_node; } } // // レイアウトツリーの一番最後にあぼーんノード追加 // void LayoutTree::append_abone_node( DBTREE::NODE* node_header ) { const int res_number = node_header->id_header; if( res_number > m_max_res_number ) m_max_res_number = res_number; #ifdef _DEBUG std::cout << "LayoutTree::append_abone_node num = " << res_number << std::endl; #endif // 透明あぼーん if( ! m_show_abone && m_article->get_abone_transparent() ) return; LAYOUT* head = create_layout_header(); m_map_header[ res_number ] = head; head->res_number = res_number; int classid = CORE::get_css_manager()->get_classid( "title" ); LAYOUT* layout = create_layout_div( classid ); DBTREE::NODE* node = node_header->headinfo->block[ DBTREE::BLOCK_NUMBER ]->next_node; layout->node = node; // メール欄フォントを設定 create_layout_link( node->text, node->linkinfo->link, &node->color_text, node->bold ); create_layout_text( " ", nullptr, false ); create_layout_link( "あぼ〜ん", PROTO_ABONE, nullptr, false ); classid = CORE::get_css_manager()->get_classid( "mes" ); layout = create_layout_div( classid ); layout->node = m_separator_header->node; // デフォルトフォントを設定 create_layout_link( "あぼ〜ん", PROTO_ABONE, nullptr, false ); } // // HTML追加 // // 一時的にローカルなノードツリーを作ってHTMLをパースして append_node() で作ったノードをコピー // void LayoutTree::append_html( const std::string& html ) { #ifdef _DEBUG std::cout << "LayoutTree::append_html url = " << m_url << " html = " << html << std::endl; #endif if( ! m_local_nodetree ) m_local_nodetree = std::make_unique( m_url ); DBTREE::NODE* node_header = m_local_nodetree->append_html( html ); if( !node_header ) { return; } LAYOUT* header = create_layout_header(); header->node = node_header; int classid = CORE::get_css_manager()->get_classid( "comment" ); *header->css = CORE::get_css_manager()->get_property( classid ); append_block( node_header->headinfo->block[ DBTREE::BLOCK_MES ], 0 ); } // // dat追加 // // 一時的にローカルなノードツリーを作ってdatをパースして append_node() で作ったノードをコピー // num: レス番号、0なら通し番号で // void LayoutTree::append_dat( const std::string& dat, int num ) { if( dat.empty() ) return; if( ! m_local_nodetree ) m_local_nodetree = std::make_unique( m_url, std::string() ); // ダミーのノードを作って番号を調整する int res_num = m_local_nodetree->get_res_number(); if( num && res_num < num ){ for(int i = res_num +1 ; i <= num -1; ++i ) m_local_nodetree->append_dat( "<>broken<>\n" ); } // 改行毎に dat を分割して追加 std::list< std::string > lines = MISC::get_lines( dat ); for( const std::string& line : lines ) { if( ! line.empty() ) { DBTREE::NODE* node = m_local_nodetree->append_dat( line + "\n" ); append_node( node, false ); } } } // // レス番号 number のヘッダを返す // const LAYOUT* LayoutTree::get_header_of_res_const( const int number ){ return get_header_of_res( number ); } LAYOUT* LayoutTree::get_header_of_res( const int number ) { if( m_map_header.empty() ) return nullptr; if( number > m_max_res_number || number <= 0 ) return nullptr; return m_map_header[ number ]; } // // 新着セパレータ作成 // LAYOUT* LayoutTree::create_separator() { m_last_layout = nullptr; m_id_layout = 0; int classid = CORE::get_css_manager()->get_classid( "separator" ); LAYOUT* header = create_layout_div( classid ); header->type = DBTREE::NODE_HEADER; DBTREE::NODE* node = m_heap.heap_alloc(); node->fontid = FONT_DEFAULT; // デフォルトフォントを設定 header->node = node; // あぼーんノードが参照する if( header->css->bg_color < 0 ) header->css->bg_color = COLOR_SEPARATOR_NEW; LAYOUT* layout = create_layout_text( "ここまで読んだ", nullptr, false ); layout->header = header; return header; } // // 新着セパレータ移動 // void LayoutTree::move_separator() { int num = m_separator_new_reserve; #ifdef _DEBUG std::cout << "LayoutTree::set_separator num = " << num << " max = " << m_max_res_number << std::endl; #endif if( num == m_separator_new ) return; // header_before と header_after の間に挿入する LAYOUT* header_before; LAYOUT* header_after; hide_separator(); if( ! num ) return; // 透明あぼーんしているレスは飛ばす int num_tmp = num; while( ! ( header_after = get_header_of_res( num_tmp ) ) && num_tmp++ < m_max_res_number ); if( ! header_after ) return; num_tmp = num-1; while( ! ( header_before = get_header_of_res( num_tmp ) ) && num_tmp-- > 1 ); if( ! header_before ) return; m_separator_new = num; header_before->next_header = m_separator_header; m_separator_header->next_header = header_after; // セパレータのヘッダID変更 int id_header = header_before->id_header + STEP_SEPARATOR; LAYOUT* layout = m_separator_header; while( layout ){ layout->id_header = id_header; layout = layout->next_layout; } #ifdef _DEBUG std::cout << "set before = " << num_tmp << " after = " << m_separator_new << std::endl; #endif } // // 新着セパレータ消去 // void LayoutTree::hide_separator() { // 表示中なら取り除く if( m_separator_new ){ #ifdef _DEBUG std::cout << "LayoutTree::hide_separator num = " << m_separator_new << " next = " << m_separator_header->next_header->res_number << std::endl; #endif // あぼーんしているレスは飛ばす LAYOUT* header_before; int num_tmp = m_separator_new -1;; while( ! ( header_before = get_header_of_res( num_tmp ) ) && num_tmp-- > 1 ); if( header_before ) header_before->next_header = m_separator_header->next_header; } m_separator_new = 0; } jdim-0.7.0/src/article/layouttree.h000066400000000000000000000140641417047150700172340ustar00rootroot00000000000000// ライセンス: GPL2 // 実際の描画レイアウト時に使うノードのツリー構造クラス #ifndef _LAYOUTTREE_H #define _LAYOUTTREE_H #include "jdlib/refptr_lock.h" #include "jdlib/heap.h" #include "cssmanager.h" #include #include #include namespace DBTREE { class ArticleBase; class NodeTreeBase; struct NODE; } namespace ARTICLE { class EmbeddedImage; struct IMGDATA; // 描画時のノードの座標情報 struct RECTANGLE { bool end; RECTANGLE* next_rect; // テキストノードでwrapが起きたらリストで繋ぐ int x; int y; int width; int height; int align; // テキストノードで使用する情報 int pos_start; int n_byte; int n_ustr; }; // 描画レイアウト用ノード struct LAYOUT { unsigned char type; // dbtree/node.hで定義されているノードタイプ int id_header; // ヘッダ番号、コメントなどもあるので必ずしも id_header = res_number とはならない int id; // ヘッダノードから順に 0,1,2,.. int res_number; // スレ内のレス番号、0の時はコメント LAYOUT* header; // 親ヘッダーへのポインタ LAYOUT* div; // 所属するdivへのポインタ LAYOUT* next_layout; // 次のノード、最終ノードではnullptr LAYOUT* next_header; // 次のヘッダノード、ヘッダノード以外ではnullptr RECTANGLE* rect; // 描画時のノード座標、幅、高さ情報 CORE::CSS_PROPERTY* css; // cssプロパティ DBTREE::NODE* node; // 文字情報( 実際にはノード情報(DBTREE::NODE)のtext情報へのポインタ) const char* text; int lng_text; // テキストの長さ const char* link; const unsigned char* color_text; bool bold; // 埋め込み画像のポインタ // deleteは DrawAreaBase::clear()でおこなう EmbeddedImage* eimg; }; // レイアウトツリー class LayoutTree { // 高速化のため直接アクセス JDLIB::RefPtr_Lock< DBTREE::ArticleBase > m_article; JDLIB::HEAP m_heap; std::string m_url; std::unordered_map< int, LAYOUT* > m_map_header; // ヘッダのポインタの連想配列 // コメントノードやプレビュー表示時に使うローカルなノードツリー std::unique_ptr m_local_nodetree; LAYOUT* m_root_header; LAYOUT* m_last_header; LAYOUT* m_last_layout; LAYOUT* m_last_div; // 新着セパレータ LAYOUT* m_separator_header{}; // 新着セパレータの位置(レス番号), 0の時は表示していない int m_separator_new; int m_separator_new_reserve; // これにレス番号をセットしてから move_separator()を呼ぶ // 表示中の最大のレス番号 int m_max_res_number; int m_id_header; int m_id_layout; // true ならあぼーんしたレスも表示 bool m_show_abone; // true なら連続空白ノードも表示 bool m_show_multispace; // 前回のブロックにあった、DOMノードのattribute int m_last_dom_attr{}; public: // show_abone : true ならあぼーんしたレスも表示する // show_multispace : true なら連続空白ノードも表示 LayoutTree( const std::string& url, const bool show_abone, const bool show_multispace ); ~LayoutTree(); void clear(); // RECTANGLE型のメモリ確保 RECTANGLE* create_rect(); LAYOUT* top_header() { return m_root_header->next_header; } const LAYOUT* top_header() const { return m_root_header->next_header; } const LAYOUT* last_header() const { return m_last_header; } int max_res_number() const { return m_max_res_number; } const LAYOUT* get_separator() const{ return m_separator_header; } // nodetreeのノード構造をコピーし、ツリーの一番最後に加える // joint == true の時はヘッダを作らないで、本文を前のツリーの続きに連結する void append_node( DBTREE::NODE* node_header, const bool joint ); void append_block( DBTREE::NODE* block, const int res_number, IMGDATA* imgdata = nullptr, const int dom_attr = 0 ); // html をパースして追加 void append_html( const std::string& html ); // dat をパースして追加 void append_dat( const std::string& dat, int num ); // レス番号 number のヘッダを返す const LAYOUT* get_header_of_res_const( const int number ); LAYOUT* get_header_of_res( const int number ); // 新着セパレータ移動 // set_separator_new()にレス番号をセットしてからmove_separator()を呼ぶ void set_separator_new( int num ){ m_separator_new_reserve = num; } void move_separator(); void hide_separator(); int get_separator_new() const { return m_separator_new; } private: LAYOUT* create_layout( const int type ); LAYOUT* create_layout_header(); LAYOUT* create_layout_text( const char* text, const unsigned char* color_text, bool bold ); LAYOUT* create_layout_link( const char* text, const char* link, const unsigned char* color_text, bool bold ); LAYOUT* create_layout_idnum( const char* text, const unsigned char* color_text, bool bold ); LAYOUT* create_layout_br( const bool nobr = false ); LAYOUT* create_layout_hr(); LAYOUT* create_layout_hspace( const int type ); LAYOUT* create_layout_div( const int id ); LAYOUT* create_layout_img( const char* link ); LAYOUT* create_layout_sssp( const char* link ); void append_abone_node( DBTREE::NODE* node_header ); LAYOUT* create_separator(); }; } #endif jdim-0.7.0/src/article/meson.build000066400000000000000000000011141417047150700170200ustar00rootroot00000000000000sources = [ 'articleadmin.cpp', 'articleview.cpp', 'articleviewbase.cpp', 'articleviewetc.cpp', 'articleviewinfo.cpp', 'articleviewpopup.cpp', 'articleviewpreview.cpp', 'articleviewsearch.cpp', 'drawareabase.cpp', 'drawareainfo.cpp', 'drawareamain.cpp', 'drawareapopup.cpp', 'embeddedimage.cpp', 'font.cpp', 'layouttree.cpp', 'preference.cpp', 'toolbar.cpp', 'toolbarsearch.cpp', 'toolbarsimple.cpp', ] article_lib = static_library( 'article', [sources, config_h], dependencies : gtkmm_dep, include_directories : include_directories('..'), ) jdim-0.7.0/src/article/preference.cpp000066400000000000000000000276641417047150700175220ustar00rootroot00000000000000// ライセンス: GPL2 #include "preference.h" #include "dbtree/interface.h" #include "jdlib/miscutil.h" #include "jdlib/misctime.h" #include "config/globalconf.h" #include "skeleton/msgdiag.h" #include "cache.h" #include "command.h" #include #include #include using namespace ARTICLE; Preferences::Preferences( Gtk::Window* parent, const std::string& url, const std::string& command ) : SKELETON::PrefDiag( parent, url ) ,m_label_name( false, "スレタイトル : ", DBTREE::article_subject( get_url() ) ) ,m_label_url( false, "スレのURL : ", DBTREE:: url_readcgi( get_url(),0,0 ) ) ,m_label_url_dat( false, "DATファイルのURL : ", DBTREE:: url_dat( get_url() ) ) ,m_label_cache( false, "ローカルキャッシュパス : ", std::string() ) ,m_label_size( false, "サイズ( byte / Kbyte ) : ", std::string() ) ,m_check_transpabone( "透明あぼ〜ん" ) ,m_check_chainabone( "連鎖あぼ〜ん" ) ,m_check_ageabone( "sage以外をあぼ〜ん" ) ,m_check_defnameabone( "デフォルト名無しをあぼ〜ん" ) ,m_check_noidabone( "ID無しをあぼ〜ん" ) ,m_check_boardabone( "板レベルでのあぼ〜んを有効にする" ) ,m_check_globalabone( "全体レベルでのあぼ〜んを有効にする" ) ,m_label_since( false, "スレ立て日時 : ", std::string() ) ,m_label_modified( false, "最終更新日時 : ", std::string() ) ,m_button_clearmodified( "日時クリア" ) ,m_label_write( false, "最終書き込み日時 : ", std::string() ) ,m_bt_clear_post_history( "書き込み履歴クリア" ) ,m_label_write_name( false, "名前 : ", std::string() ) ,m_label_write_mail( false, "メール : ", std::string() ) { // 一般 if( DBTREE::article_is_cached( get_url() ) ){ int size = DBTREE::article_lng_dat( get_url() ); m_label_cache.set_text( CACHE::path_dat( get_url() ) ); m_label_size.set_text( std::to_string( size ) + " / " + std::to_string( size/1024 ) ); if( DBTREE::article_date_modified( get_url() ).empty() ) m_label_modified.set_text( "未取得" ); else m_label_modified.set_text( MISC::timettostr( DBTREE::article_time_modified( get_url() ), MISC::TIME_WEEK ) + " ( " + MISC::timettostr( DBTREE::article_time_modified( get_url() ), MISC::TIME_PASSED ) + " )" ); if( DBTREE::article_write_time( get_url() ) ) m_label_write.set_text( MISC::timettostr( DBTREE::article_write_time( get_url() ), MISC::TIME_WEEK ) + " ( " + MISC::timettostr( DBTREE::article_write_time( get_url() ), MISC::TIME_PASSED ) + " )" ); } m_label_since.set_text( MISC::timettostr( DBTREE::article_since_time( get_url() ), MISC::TIME_WEEK ) + " ( " + MISC::timettostr( DBTREE::article_since_time( get_url() ), MISC::TIME_PASSED ) + " )" ); m_button_clearmodified.signal_clicked().connect( sigc::mem_fun(*this, &Preferences::slot_clear_modified ) ); m_hbox_modified.pack_start( m_label_modified ); m_hbox_modified.pack_start( m_button_clearmodified, Gtk::PACK_SHRINK ); m_bt_clear_post_history.signal_clicked().connect( sigc::mem_fun(*this, &Preferences::slot_clear_post_history ) ); m_hbox_write.pack_start( m_label_write ); m_hbox_write.pack_start( m_bt_clear_post_history, Gtk::PACK_SHRINK ); m_vbox_info.set_border_width( 16 ); m_vbox_info.set_spacing( 8 ); m_vbox_info.pack_start( m_label_name, Gtk::PACK_SHRINK ); m_vbox_info.pack_start( m_label_url, Gtk::PACK_SHRINK ); m_vbox_info.pack_start( m_label_url_dat, Gtk::PACK_SHRINK ); m_vbox_info.pack_start( m_label_cache, Gtk::PACK_SHRINK ); m_vbox_info.pack_start( m_label_size, Gtk::PACK_SHRINK ); m_vbox_info.pack_start( m_label_since, Gtk::PACK_SHRINK ); m_vbox_info.pack_start( m_hbox_modified, Gtk::PACK_SHRINK ); m_vbox_info.pack_start( m_hbox_write, Gtk::PACK_SHRINK ); if( DBTREE::write_fixname( get_url() ) ) m_label_write_name.set_text( DBTREE::write_name( get_url() ) ); if( DBTREE::write_fixmail( get_url() ) ) m_label_write_mail.set_text( DBTREE::write_mail( get_url() ) ); m_vbox_info.pack_start( m_label_write_name, Gtk::PACK_SHRINK ); m_vbox_info.pack_start( m_label_write_mail, Gtk::PACK_SHRINK ); // あぼーん設定 m_vbox_abone.set_border_width( 16 ); m_vbox_abone.set_spacing( 8 ); // 透明あぼーん m_check_transpabone.set_active( DBTREE::get_abone_transparent( get_url() ) ); // 連鎖あぼーん m_check_chainabone.set_active( DBTREE::get_abone_chain( get_url() ) ); // ageあぼーん m_check_ageabone.set_active( DBTREE::get_abone_age( get_url() ) ); // デフォルト名無しあぼーん m_check_defnameabone.set_active( DBTREE::get_abone_default_name( get_url() ) ); // ID無しあぼーん m_check_noidabone.set_active( DBTREE::get_abone_noid( get_url() ) ); // 板レベルあぼーん m_check_boardabone.set_active( DBTREE::get_abone_board( get_url() ) ); // 全体レベルあぼーん m_check_globalabone.set_active( DBTREE::get_abone_global( get_url() ) ); if( CONFIG::get_abone_transparent() ) m_check_transpabone.set_sensitive( false ); if( CONFIG::get_abone_chain() ) m_check_chainabone.set_sensitive( false ); m_vbox_abone.pack_start( m_check_transpabone, Gtk::PACK_SHRINK ); m_vbox_abone.pack_start( m_check_chainabone, Gtk::PACK_SHRINK ); m_vbox_abone.pack_start( m_check_ageabone, Gtk::PACK_SHRINK ); m_vbox_abone.pack_start( m_check_defnameabone, Gtk::PACK_SHRINK ); m_vbox_abone.pack_start( m_check_noidabone, Gtk::PACK_SHRINK ); m_vbox_abone.pack_start( m_check_boardabone, Gtk::PACK_SHRINK ); m_vbox_abone.pack_start( m_check_globalabone, Gtk::PACK_SHRINK ); if( CONFIG::get_abone_transparent() || CONFIG::get_abone_chain() ){ m_label_abone.set_text( "チェック出来ない場合は設定メニューから「デフォルトで透明/連鎖あぼ〜ん」を解除して下さい" ); m_label_abone.set_xalign( 0 ); m_vbox_abone.pack_start( m_label_abone, Gtk::PACK_SHRINK ); } if( DBTREE::article_is_cached( get_url() ) ){ std::string str_res; // id std::list< std::string > list_id = DBTREE::get_abone_list_id( get_url() ); m_edit_id.set_text( MISC::concat_with_suffix( list_id, '\n' ) ); // あぼーんレス番号 // 連番は 12-34 の様なフォーマットに変換 const std::unordered_set< int >& set_res = DBTREE::get_abone_reses( get_url() ); // レス番号をソートする const std::set< int > tmp_set{ set_res.begin(), set_res.end() }; int pre_res = 0; int save = 0; for( const int res : tmp_set ) { if( !pre_res ) { save = res; } else if( res - pre_res > 1 ) { str_res.append( std::to_string( save ) ); if( pre_res != save ) str_res.append( '-' + std::to_string( pre_res ) ); str_res.push_back( '\n' ); save = res; } pre_res = res; } if( !tmp_set.empty() ) { str_res.append( std::to_string( save ) ); if( pre_res != save ) str_res.append( '-' + std::to_string( pre_res ) ); str_res.push_back( '\n' ); } m_edit_res.set_text( str_res ); // name std::list< std::string > list_name = DBTREE::get_abone_list_name( get_url() ); m_edit_name.set_text( MISC::concat_with_suffix( list_name, '\n' ) ); // word std::list< std::string > list_word = DBTREE::get_abone_list_word( get_url() ); m_edit_word.set_text( MISC::concat_with_suffix( list_word, '\n' ) ); // regex std::list< std::string > list_regex = DBTREE::get_abone_list_regex( get_url() ); m_edit_regex.set_text( MISC::concat_with_suffix( list_regex, '\n' ) ); } else{ m_edit_id.set_editable( false ); m_edit_res.set_editable( false ); m_edit_name.set_editable( false ); m_edit_word.set_editable( false ); m_edit_regex.set_editable( false ); } m_label_abone_id.set_text( "ここでIDを削除してもレスが表示されない場合は板全体に対してIDがあぼーん指定されている可能性があります。\n板のプロパティのあぼーん設定も確認してください。" ); m_vbox_abone.set_spacing( 8 ); m_vbox_abone_id.pack_start( m_label_abone_id, Gtk::PACK_SHRINK ); m_vbox_abone_id.pack_start( m_edit_id ); m_notebook_abone.append_page( m_vbox_abone, "一般" ); m_notebook_abone.append_page( m_vbox_abone_id, "NG ID" ); m_notebook_abone.append_page( m_edit_res, "NG レス番号" ); m_notebook_abone.append_page( m_edit_name, "NG 名前" ); m_notebook_abone.append_page( m_edit_word, "NG ワード" ); m_notebook_abone.append_page( m_edit_regex, "NG 正規表現" ); m_notebook.append_page( m_vbox_info, "一般" ); const int page_abone = 1; m_notebook.append_page( m_notebook_abone, "あぼ〜ん設定" ); get_content_area()->pack_start( m_notebook ); set_title( "「" + DBTREE::article_subject( get_url() ) + "」のプロパティ" ); resize( 600, 400 ); show_all_children(); if( command == "show_abone" ) m_notebook.set_current_page( page_abone ); } Preferences::~Preferences() noexcept = default; // // OK 押した // void Preferences::slot_ok_clicked() { // あぼーん再設定 std::list< std::string > list_id = MISC::get_lines( m_edit_id.get_text() ); std::list< std::string > list_name = MISC::get_lines( m_edit_name.get_text() ); std::list< std::string > list_word = MISC::get_lines( m_edit_word.get_text() ); std::list< std::string > list_regex = MISC::get_lines( m_edit_regex.get_text() ); // あぼーんレス番号 std::vector< char > vec_abone_res; vec_abone_res.resize( DBTREE::article_number_load( get_url() ) + 1 ); std::list< std::string > list_res = MISC::get_lines( m_edit_res.get_text() ); for( std::string& num_str : list_res ) { int number = atoi( num_str.c_str() ); if( number >= 1 ){ int number_end = number; size_t pos = num_str.find( '-' ); if( pos != std::string::npos ) number_end = MIN( (int)vec_abone_res.size(), MAX( number, atoi( num_str.substr( pos + 1 ).c_str() ) ) ); for( int i = number; i <= number_end; ++i ) vec_abone_res[ i ] = true; } } DBTREE::reset_abone( get_url(), list_id, list_name, list_word, list_regex, vec_abone_res , m_check_transpabone.get_active(), m_check_chainabone.get_active(), m_check_ageabone.get_active(), m_check_defnameabone.get_active(), m_check_noidabone.get_active(), m_check_boardabone.get_active(), m_check_globalabone.get_active() ); // viewの再レイアウト CORE::core_set_command( "relayout_article", get_url() ); } void Preferences::slot_clear_modified() { DBTREE::article_set_date_modified( get_url(), "" ); if( DBTREE::article_date_modified( get_url() ).empty() ) m_label_modified.set_text( "未取得" ); else m_label_modified.set_text( MISC::timettostr( DBTREE::article_time_modified( get_url() ), MISC::TIME_WEEK ) + " ( " + MISC::timettostr( DBTREE::article_time_modified( get_url() ), MISC::TIME_PASSED ) + " )" ); } void Preferences::slot_clear_post_history() { if( m_label_write.get_text().empty() ) return; SKELETON::MsgDiag mdiag( nullptr, "書き込み履歴を削除しますか?", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO ); if( mdiag.run() != Gtk::RESPONSE_YES ) return; DBTREE::article_clear_post_history( get_url() ); m_label_write.set_text( "" ); CORE::core_set_command( "redraw_article" ); // BoardViewの行を更新 CORE::core_set_command( "update_board_item", DBTREE::url_boardbase( get_url() ), DBTREE::article_id( get_url() ) ); } jdim-0.7.0/src/article/preference.h000066400000000000000000000042451417047150700171550ustar00rootroot00000000000000// ライセンス: GPL2 #ifndef _ARTICLE_PREFERENCES_H #define _ARTICLE_PREFERENCES_H #include "skeleton/prefdiag.h" #include "skeleton/editview.h" #include "skeleton/label_entry.h" namespace ARTICLE { class Preferences : public SKELETON::PrefDiag { Gtk::Notebook m_notebook; // 情報 Gtk::VBox m_vbox_info; SKELETON::LabelEntry m_label_name; SKELETON::LabelEntry m_label_url; SKELETON::LabelEntry m_label_url_dat; SKELETON::LabelEntry m_label_cache; SKELETON::LabelEntry m_label_size; // あぼーん Gtk::VBox m_vbox_abone; Gtk::Notebook m_notebook_abone; Gtk::VBox m_vbox_abone_id; Gtk::Label m_label_abone_id; SKELETON::EditView m_edit_id, m_edit_res, m_edit_name, m_edit_word, m_edit_regex; Gtk::Label m_label_abone; // 透明あぼーん Gtk::CheckButton m_check_transpabone; // 連鎖あぼーん Gtk::CheckButton m_check_chainabone; // ageあぼーん Gtk::CheckButton m_check_ageabone; // デフォルト名無しあぼーん Gtk::CheckButton m_check_defnameabone; // ID無しあぼーん Gtk::CheckButton m_check_noidabone; // 板レベルでのあぼーん Gtk::CheckButton m_check_boardabone; // 全体レベルでのあぼーん Gtk::CheckButton m_check_globalabone; SKELETON::LabelEntry m_label_since; // 最終更新日時 Gtk::HBox m_hbox_modified; SKELETON::LabelEntry m_label_modified; Gtk::Button m_button_clearmodified; // 書き込み日時 Gtk::HBox m_hbox_write; SKELETON::LabelEntry m_label_write; Gtk::Button m_bt_clear_post_history; // 名前とメール SKELETON::LabelEntry m_label_write_name; SKELETON::LabelEntry m_label_write_mail; public: Preferences( Gtk::Window* parent, const std::string& url, const std::string& command ); ~Preferences() noexcept; private: void slot_ok_clicked() override; void slot_clear_modified(); void slot_clear_post_history(); }; } #endif jdim-0.7.0/src/article/scrollinfo.h000066400000000000000000000033351417047150700172100ustar00rootroot00000000000000// ライセンス: GPL2 // スクロール情報 #ifndef _SCROLLINFO_H #define _SCROLLINFO_H namespace ARTICLE { // スクロールモード enum { SCROLL_NOT = 0, SCROLL_NORMAL, // dy の量だけ 1 回だけスクロール SCROLL_LOCKED, // 常に dy の量だけスクロール SCROLL_TO_NUM, // レス番号 res にジャンプ SCROLL_TO_TOP, // 先頭にジャンプ SCROLL_TO_BOTTOM, // 最後にジャンプ SCROLL_TO_Y, // y 座標にジャンプ SCROLL_AUTO // マーカを中心にしてオートスクロール }; struct SCROLLINFO { int mode = SCROLL_NOT; int dy; // スクロール量 int res; // レス番号 // オートスクロールモード(マウスの中ボタン押し)用の変数 int x; // 中心のx座標 int y; // 中心のy座標 bool show_marker; // true ならマーカを出す bool enable_up; // true なら上方向にスクロール可 bool enable_down; // true なら下方向にスクロール可 bool autoscroll_finished; // オートスクロールが終わった // > 0 の間はモーションイベントをキャンセルする int counter_nomotion; // 実況モード用変数 bool live; double live_speed; int live_counter; void reset() { mode = SCROLL_NOT; dy = 0; res = 0; x = 0; y = 0; show_marker = false; enable_up = false; enable_down = false; autoscroll_finished = false; counter_nomotion = 0; } }; } #endif jdim-0.7.0/src/article/toolbar.cpp000066400000000000000000000136131417047150700170330ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "toolbar.h" #include "articleadmin.h" #include "articleviewbase.h" #include "control/controlutil.h" #include "control/controlid.h" #include "icons/iconmanager.h" #include "command.h" #include "session.h" #include "compmanager.h" #include "global.h" using namespace ARTICLE; ArticleToolBar::ArticleToolBar() : SKELETON::ToolBar( ARTICLE::get_admin() ) , m_enable_slot( true ) , m_button_drawout_and( ICON::SEARCH_AND, CONTROL::DrawOutAnd ) , m_button_drawout_or( ICON::SEARCH_OR, CONTROL::DrawOutOr ) { // 検索バー set_tooltip( m_button_drawout_and, CONTROL::get_label_motions( CONTROL::DrawOutAnd ) ); set_tooltip( m_button_drawout_or, CONTROL::get_label_motions( CONTROL::DrawOutOr ) ); get_searchbar()->append( *get_tool_search( CORE::COMP_SEARCH_ARTICLE ) ); get_searchbar()->append( *get_button_down_search() ); get_searchbar()->append( *get_button_up_search() ); get_searchbar()->append( m_button_drawout_and ); get_searchbar()->append( m_button_drawout_or ); get_searchbar()->append( *get_button_clear_highlight() ); get_searchbar()->append( *get_button_close_searchbar() ); m_button_drawout_or.signal_clicked().connect( sigc::mem_fun(*this, &ArticleToolBar::slot_drawout_or ) ); m_button_drawout_and.signal_clicked().connect( sigc::mem_fun(*this, &ArticleToolBar::slot_drawout_and ) ); ArticleToolBar::pack_buttons(); } ArticleToolBar::~ArticleToolBar() noexcept = default; // // タブが切り替わった時にDragableNoteBook::set_current_toolbar()から呼び出される( Viewの情報を取得する ) // // virtual void ArticleToolBar::set_view( SKELETON::View * view ) { SKELETON::ToolBar::set_view( view ); m_enable_slot = false; // ArticleViewBase固有の情報をコピー ArticleViewBase* articleview = dynamic_cast< ArticleViewBase* >( view ); if( articleview ){ m_url_article = articleview->url_article(); if( m_button_live_play_stop ){ bool sensitive = true; if( ! articleview->get_enable_live() ) sensitive = false; m_button_live_play_stop->set_sensitive( sensitive ); if( articleview->get_live() ) m_button_live_play_stop->set_active( true ); else m_button_live_play_stop->set_active( false ); } } m_enable_slot = true; } // // ボタンのパッキング // // virtual void ArticleToolBar::pack_buttons() { int num = 0; for(;;){ int item = SESSION::get_item_article_toolbar( num ); if( item == ITEM_END ) break; switch( item ){ case ITEM_WRITEMSG: get_buttonbar().append( *get_button_write() ); break; case ITEM_OPENBOARD: get_buttonbar().append( *get_button_board() ); break; case ITEM_NAME: pack_transparent_separator(); get_buttonbar().append( *get_label() ); pack_transparent_separator(); break; case ITEM_SEARCH: get_buttonbar().append( *get_button_open_searchbar() ); break; case ITEM_RELOAD: get_buttonbar().append( *get_button_reload() ); break; case ITEM_STOPLOADING: get_buttonbar().append( *get_button_stop() ); break; case ITEM_APPENDFAVORITE: get_buttonbar().append( *get_button_favorite() ); set_tooltip( *get_button_favorite(), CONTROL::get_label_motions( CONTROL::AppendFavorite ) + "\n\nスレのタブをお気に入りに直接D&Dしても登録可能" ); break; case ITEM_DELETE: get_buttonbar().append( *get_button_delete() ); break; case ITEM_QUIT: get_buttonbar().append( *get_button_close() ); break; case ITEM_BACK: get_buttonbar().append( *get_button_back() ); break; case ITEM_FORWARD: get_buttonbar().append( *get_button_forward() ); break; case ITEM_LOCK: get_buttonbar().append( *get_button_lock() ); break; case ITEM_LIVE: if( ! m_button_live_play_stop ){ m_button_live_play_stop = Gtk::manage( new SKELETON::ImgToggleToolButton( ICON::LIVE, CONTROL::LiveStartStop ) ); set_tooltip( *m_button_live_play_stop, CONTROL::get_label_motions( CONTROL::LiveStartStop ) ); m_button_live_play_stop->set_label( CONTROL::get_label( CONTROL::LiveStartStop ) ); m_button_live_play_stop->signal_clicked().connect( sigc::mem_fun(*this, &ArticleToolBar::slot_live_play_stop ) ); } get_buttonbar().append( *m_button_live_play_stop ); break; case ITEM_SEPARATOR: pack_separator(); break; } ++num; } set_relief(); show_all_children(); } // // キーワード抽出 (AND) // void ArticleToolBar::slot_drawout_and() { if( ! m_enable_slot ) return; std::string query = get_search_text(); if( query.empty() ) return; CORE::core_set_command( "open_article_keyword" ,m_url_article, query, "false" ); } // // キーワード抽出 (OR) // void ArticleToolBar::slot_drawout_or() { if( ! m_enable_slot ) return; std::string query = get_search_text(); if( query.empty() ) return; CORE::core_set_command( "open_article_keyword" ,m_url_article, query, "true" ); } // // 実況開始/停止 // void ArticleToolBar::slot_live_play_stop() { if( ! m_enable_slot ) return; ARTICLE::get_admin()->set_command( "live_start_stop", get_url() ); } jdim-0.7.0/src/article/toolbar.h000066400000000000000000000020021417047150700164660ustar00rootroot00000000000000// ライセンス: GPL2 // ツールバーのクラス // // ARTICLE::ArticleView* 以外では使わない // #ifndef _ARTICLE_TOOLBAR_H #define _ARTICLE_TOOLBAR_H #include "skeleton/toolbar.h" #include "skeleton/imgtoolbutton.h" namespace ARTICLE { class ArticleToolBar : public SKELETON::ToolBar { std::string m_url_article; bool m_enable_slot; SKELETON::ImgToolButton m_button_drawout_and; SKELETON::ImgToolButton m_button_drawout_or; // 実況 Gtk::ToggleToolButton* m_button_live_play_stop{}; public: ArticleToolBar(); ~ArticleToolBar() noexcept; // タブが切り替わった時に呼び出される( Viewの情報を取得する ) void set_view( SKELETON::View * view ) override; protected: void pack_buttons() override; // ボタンを押したときのslot関数 void slot_drawout_and(); void slot_drawout_or(); void slot_live_play_stop(); }; } #endif jdim-0.7.0/src/article/toolbarsearch.cpp000066400000000000000000000064041417047150700202210ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "toolbarsearch.h" #include "articleadmin.h" #include "articleviewsearch.h" #include "skeleton/compentry.h" #include "control/controlutil.h" #include "control/controlid.h" #include "compmanager.h" #include "session.h" #include "global.h" using namespace ARTICLE; SearchToolBar::SearchToolBar() : SKELETON::ToolBar( ARTICLE::get_admin() ) , m_check_bm( "しおり" ) { m_tool_bm.add( m_check_bm ); m_tool_bm.set_expand( false ); m_check_bm.signal_toggled().connect( sigc::mem_fun(*this, &SearchToolBar::slot_toggle_bm ) ); // 検索バー get_searchbar()->append( *get_tool_search( CORE::COMP_SEARCH_ARTICLE ) ); get_searchbar()->append( m_tool_bm ); get_searchbar()->append( *get_button_close_searchbar() ); SearchToolBar::pack_buttons(); } SearchToolBar::~SearchToolBar() noexcept = default; // // ボタンのパッキング // void SearchToolBar::pack_buttons() { int num = 0; for(;;){ int item = SESSION::get_item_search_toolbar( num ); if( item == ITEM_END ) break; switch( item ){ case ITEM_NAME: pack_transparent_separator(); get_buttonbar().append( *get_label() ); pack_transparent_separator(); break; case ITEM_SEARCH: get_buttonbar().append( *get_button_open_searchbar() ); break; case ITEM_RELOAD: if( auto button = get_button_reload() ) { get_buttonbar().append( *button ); button->set_label( "再検索" ); set_tooltip( *button, "再検索 " + CONTROL::get_str_motions( CONTROL::Reload ) ); } break; case ITEM_STOPLOADING: if( auto button = get_button_stop() ) { get_buttonbar().append( *button ); button->set_label( "検索中止" ); set_tooltip( *button, "検索中止 " + CONTROL::get_str_motions( CONTROL::Reload ) ); } break; case ITEM_QUIT: get_buttonbar().append( *get_button_close() ); break; case ITEM_SEPARATOR: pack_separator(); break; } ++num; } set_relief(); show_all_children(); } // // タブが切り替わった時にDragableNoteBook::set_current_toolbar()から呼び出される( Viewの情報を取得する ) // // virtual void SearchToolBar::set_view( SKELETON::View * view ) { SKELETON::ToolBar::set_view( view ); m_enable_slot = false; // ArticleViewSearch固有の情報をコピー m_searchview = dynamic_cast< ArticleViewSearch* >( view ); if( m_searchview ){ if( m_searchview->get_enable_bm() ){ m_check_bm.set_sensitive( true ); m_check_bm.set_active( m_searchview->get_bm() ); } else{ m_check_bm.set_sensitive( false ); m_check_bm.set_active( false ); } } m_enable_slot = true; } void SearchToolBar::slot_toggle_bm() { if( ! m_enable_slot ) return; if( m_searchview ) m_searchview->set_bm( m_check_bm.get_active() ); } jdim-0.7.0/src/article/toolbarsearch.h000066400000000000000000000015121417047150700176610ustar00rootroot00000000000000// ライセンス: GPL2 // ログ検索などのツールバーのクラス // // ARTICLE::ArticleViewSearch 以外では使わない // #ifndef _ARTICLE_TOOLBARSEARCH_H #define _ARTICLE_TOOLBARSEARCH_H #include "skeleton/toolbar.h" namespace ARTICLE { class ArticleViewSearch; class SearchToolBar : public SKELETON::ToolBar { ArticleViewSearch* m_searchview{}; bool m_enable_slot{}; Gtk::ToolItem m_tool_bm; Gtk::CheckButton m_check_bm; public: SearchToolBar(); ~SearchToolBar() noexcept; // タブが切り替わった時に呼び出される( Viewの情報を取得する ) void set_view( SKELETON::View * view ) override; protected: void pack_buttons() override; private: void slot_toggle_bm(); }; } #endif jdim-0.7.0/src/article/toolbarsimple.cpp000066400000000000000000000020661417047150700202450ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "toolbarsimple.h" #include "articleadmin.h" #include "compmanager.h" using namespace ARTICLE; ArticleToolBarSimple::ArticleToolBarSimple() : SKELETON::ToolBar( ARTICLE::get_admin() ) { // 検索バー get_searchbar()->append( *get_tool_search( CORE::COMP_SEARCH_ARTICLE ) ); get_searchbar()->append( *get_button_down_search() ); get_searchbar()->append( *get_button_up_search() ); get_searchbar()->append( *get_button_close_searchbar() ); ArticleToolBarSimple::pack_buttons(); } ArticleToolBarSimple::~ArticleToolBarSimple() noexcept = default; // // ボタンのパッキング // void ArticleToolBarSimple::pack_buttons() { pack_transparent_separator(); get_buttonbar().append( *get_label() ); get_buttonbar().append( *get_button_open_searchbar() ); get_buttonbar().append( *get_button_reload() ); get_buttonbar().append( *get_button_stop() ); get_buttonbar().append( *get_button_close() ); set_relief(); show_all_children(); } jdim-0.7.0/src/article/toolbarsimple.h000066400000000000000000000006341417047150700177110ustar00rootroot00000000000000// ライセンス: GPL2 // 簡易版ツールバーのクラス #ifndef _ARTICLE_TOOLBARSIMPLE_H #define _ARTICLE_TOOLBARSIMPLE_H #include "skeleton/toolbar.h" namespace ARTICLE { class ArticleToolBarSimple : public SKELETON::ToolBar { public: ArticleToolBarSimple(); ~ArticleToolBarSimple() noexcept; protected: void pack_buttons() override; }; } #endif jdim-0.7.0/src/articleitemmenupref.cpp000066400000000000000000000040411417047150700200050ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "articleitemmenupref.h" #include "icons/iconmanager.h" #include "skeleton/msgdiag.h" #include "global.h" #include "session.h" #include "command.h" using namespace CORE; ArticleItemMenuPref::ArticleItemMenuPref( Gtk::Window* parent, const std::string& url ) : SKELETON::SelectItemPref( parent, url ) { // デフォルトの項目を設定 append_default_pair( ITEM_NAME_DRAWOUT ); append_default_pair( ITEM_NAME_GO ); append_default_pair( ITEM_NAME_SEARCH ); append_default_pair( ITEM_NAME_NGWORD ); append_default_pair( ITEM_NAME_QUOTE_SELECTION ); append_default_pair( ITEM_NAME_OPEN_BROWSER ); append_default_pair( ITEM_NAME_USER_COMMAND ); append_default_pair( ITEM_NAME_COPY_URL ); append_default_pair( ITEM_NAME_COPY_TITLE_URL_THREAD ); append_default_pair( ITEM_NAME_COPY ); append_default_pair( ITEM_NAME_ETC ); append_default_pair( ITEM_NAME_RELOAD ); append_default_pair( ITEM_NAME_DELETE ); append_default_pair( ITEM_NAME_SAVE_DAT ); append_default_pair( ITEM_NAME_COPY_THREAD_INFO ); append_default_pair( ITEM_NAME_APPENDFAVORITE ); append_default_pair( ITEM_NAME_ABONE_SELECTION ); append_default_pair( ITEM_NAME_SELECTIMG ); append_default_pair( ITEM_NAME_SELECTDELIMG ); append_default_pair( ITEM_NAME_SELECTABONEIMG ); append_default_pair( ITEM_NAME_PREF_THREAD ); append_default_pair( ITEM_NAME_SEPARATOR ); // 文字列を元に行を追加 append_rows( SESSION::get_items_article_menu_str() ); set_title( "コンテキストメニュー項目設定(スレビュー)" ); } // // OKを押した // void ArticleItemMenuPref::slot_ok_clicked() { SKELETON::MsgDiag mdiag( nullptr, "次に開いたスレビューから有効になります" ); mdiag.run(); SESSION::set_items_article_menu_str( get_items() ); } // // デフォルトボタン // void ArticleItemMenuPref::slot_default() { append_rows( SESSION::get_items_article_menu_default_str() ); } jdim-0.7.0/src/articleitemmenupref.h000066400000000000000000000011341417047150700174520ustar00rootroot00000000000000// ライセンス: GPL2 // スレビューのコンテキストメニューの表示項目設定 #ifndef _ARTICLEITEMMENUPREF_H #define _ARTICLEITEMMENUPREF_H #include "skeleton/selectitempref.h" namespace CORE { class ArticleItemMenuPref : public SKELETON::SelectItemPref { public: ArticleItemMenuPref( Gtk::Window* parent, const std::string& url ); ~ArticleItemMenuPref() noexcept = default; private: // OKボタン void slot_ok_clicked() override; // デフォルトボタン void slot_default() override; }; } #endif jdim-0.7.0/src/articleitempref.cpp000066400000000000000000000037171417047150700171310ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "articleitempref.h" #include "icons/iconmanager.h" #include "global.h" #include "session.h" #include "command.h" using namespace CORE; ArticleItemPref::ArticleItemPref( Gtk::Window* parent, const std::string& url ) : SKELETON::SelectItemPref( parent, url ) { // デフォルトの項目を設定 append_default_pair( ITEM_NAME_WRITEMSG, ICON::get_icon( ICON::WRITE ) ); append_default_pair( ITEM_NAME_OPENBOARD, ICON::get_icon( ICON::TRANSPARENT ) ); append_default_pair( ITEM_NAME_NAME, ICON::get_icon( ICON::TRANSPARENT ) ); append_default_pair( ITEM_NAME_SEARCH, ICON::get_icon( ICON::SEARCH ) ); append_default_pair( ITEM_NAME_RELOAD, ICON::get_icon( ICON::RELOAD ) ); append_default_pair( ITEM_NAME_STOPLOADING, ICON::get_icon( ICON::STOPLOADING ) ); append_default_pair( ITEM_NAME_APPENDFAVORITE, ICON::get_icon( ICON::APPENDFAVORITE ) ); append_default_pair( ITEM_NAME_DELETE, ICON::get_icon( ICON::DELETE ) ); append_default_pair( ITEM_NAME_QUIT, ICON::get_icon( ICON::QUIT ) ); append_default_pair( ITEM_NAME_BACK, ICON::get_icon( ICON::BACK ) ); append_default_pair( ITEM_NAME_FORWARD, ICON::get_icon( ICON::FORWARD ) ); append_default_pair( ITEM_NAME_LOCK, ICON::get_icon( ICON::LOCK ) ); append_default_pair( ITEM_NAME_LIVE, ICON::get_icon( ICON::LIVE ) ); append_default_pair( ITEM_NAME_SEPARATOR, ICON::get_icon( ICON::TRANSPARENT ) ); // 文字列を元に行を追加 append_rows( SESSION::get_items_article_toolbar_str() ); set_title( "ツールバー項目設定(スレビュー)" ); } // // OKを押した // void ArticleItemPref::slot_ok_clicked() { SESSION::set_items_article_toolbar_str( get_items() ); CORE::core_set_command( "update_article_toolbar_button" ); } // // デフォルトボタン // void ArticleItemPref::slot_default() { append_rows( SESSION::get_items_article_toolbar_default_str() ); } jdim-0.7.0/src/articleitempref.h000066400000000000000000000010701417047150700165640ustar00rootroot00000000000000// ライセンス: GPL2 // スレビューのツールバーの表示項目設定 #ifndef _ARTICLEITEMPREF_H #define _ARTICLEITEMPREF_H #include "skeleton/selectitempref.h" namespace CORE { class ArticleItemPref : public SKELETON::SelectItemPref { public: ArticleItemPref( Gtk::Window* parent, const std::string& url ); ~ArticleItemPref() noexcept = default; private: // OKボタン void slot_ok_clicked() override; // デフォルトボタン void slot_default() override; }; } #endif jdim-0.7.0/src/bbslist/000077500000000000000000000000001417047150700147005ustar00rootroot00000000000000jdim-0.7.0/src/bbslist/Makefile.am000066400000000000000000000010341417047150700167320ustar00rootroot00000000000000noinst_LIBRARIES = libbbslist.a libbbslist_a_SOURCES = \ bbslistadmin.cpp \ bbslistviewbase.cpp \ bbslistview.cpp \ favoriteview.cpp \ selectlistview.cpp \ historyview.cpp \ selectdialog.cpp \ editlistwin.cpp \ addetcdialog.cpp \ columns.cpp \ toolbar.cpp noinst_HEADERS = \ bbslistadmin.h \ bbslistviewbase.h \ bbslistview.h \ favoriteview.h \ selectlistview.h \ historyview.h \ selectdialog.h \ editlistwin.h \ addetcdialog.h \ columns.h \ toolbar.h AM_CXXFLAGS = @GTKMM_CFLAGS@ AM_CPPFLAGS = -I$(top_srcdir)/src jdim-0.7.0/src/bbslist/addetcdialog.cpp000066400000000000000000000024611417047150700200130ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "addetcdialog.h" #include "command.h" #include "session.h" using namespace BBSLIST; AddEtcDialog::AddEtcDialog( const bool move, const std::string& url, const std::string& name, const std::string& id, const std::string& passwd ) : SKELETON::PrefDiag( nullptr, url, true ), m_entry_name( true, "板名(_N):", name ), m_entry_url( true, "アドレス(_U):", url ), m_entry_id( true, "ID(_I):", id ), m_entry_pw( true, "パスワード(_P):", passwd ) { resize( 600, 1 ); m_vbox.set_spacing( 8 ); m_vbox.set_border_width( 8 ); m_vbox.add( m_entry_id ); m_vbox.add( m_entry_pw ); set_activate_entry( m_entry_id ); set_activate_entry( m_entry_pw ); m_frame.set_label( "BASIC認証" ); m_frame.add( m_vbox ); get_content_area()->set_spacing( 8 ); get_content_area()->pack_start( m_entry_name ); get_content_area()->pack_start( m_entry_url ); get_content_area()->pack_start( m_frame ); set_activate_entry( m_entry_name ); set_activate_entry( m_entry_url ); if( move ){ set_title( "外部板編集" ); } else{ set_title( "外部板追加" ); } show_all_children(); } AddEtcDialog::~AddEtcDialog() noexcept = default; jdim-0.7.0/src/bbslist/addetcdialog.h000066400000000000000000000017251417047150700174620ustar00rootroot00000000000000// ライセンス: GPL2 // // 外部板追加ダイアログ // #ifndef _ADDETCDIALOG_H #define _ADDETCDIALOG_H #include "skeleton/prefdiag.h" #include "skeleton/label_entry.h" namespace BBSLIST { class AddEtcDialog : public SKELETON::PrefDiag { SKELETON::LabelEntry m_entry_name; SKELETON::LabelEntry m_entry_url; Gtk::Frame m_frame; Gtk::VBox m_vbox; SKELETON::LabelEntry m_entry_id; SKELETON::LabelEntry m_entry_pw; public: AddEtcDialog( const bool move, const std::string& url, const std::string& _name, const std::string& _id, const std::string& _passwd ); ~AddEtcDialog() noexcept; std::string get_name() const { return m_entry_name.get_text(); } std::string get_url() const { return m_entry_url.get_text(); } std::string get_id() const { return m_entry_id.get_text(); } std::string get_passwd() const { return m_entry_pw.get_text(); } }; } #endif jdim-0.7.0/src/bbslist/bbslistadmin.cpp000066400000000000000000000202311417047150700200550ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "bbslistadmin.h" #include "bbslistviewbase.h" #include "toolbar.h" #include "skeleton/view.h" #include "skeleton/dragnote.h" #include "skeleton/undobuffer.h" #include "global.h" #include "viewfactory.h" #include "session.h" #include "command.h" // お気に入りの共通UNDOバッファ SKELETON::UNDO_BUFFER *instance_undo_buffer_favorite = nullptr; SKELETON::UNDO_BUFFER* BBSLIST::get_undo_buffer_favorite() { if( ! instance_undo_buffer_favorite ) instance_undo_buffer_favorite = new SKELETON::UNDO_BUFFER(); assert( instance_undo_buffer_favorite ); return instance_undo_buffer_favorite; } void BBSLIST::delete_undo_buffer_favorite() { if( instance_undo_buffer_favorite ) delete instance_undo_buffer_favorite; instance_undo_buffer_favorite = nullptr; } ////////////////////////////////////////////// BBSLIST::BBSListAdmin *instance_bbslistadmin = nullptr; BBSLIST::BBSListAdmin* BBSLIST::get_admin() { if( ! instance_bbslistadmin ) instance_bbslistadmin = new BBSLIST::BBSListAdmin( URL_BBSLISTADMIN ); assert( instance_bbslistadmin ); return instance_bbslistadmin; } void BBSLIST::delete_admin() { if( instance_bbslistadmin ) delete instance_bbslistadmin; instance_bbslistadmin = nullptr; } ////////////////////////////////////////////// using namespace BBSLIST; BBSListAdmin::BBSListAdmin( const std::string& url ) : SKELETON::Admin( url ) { get_notebook()->set_dragable( false ); get_notebook()->set_fixtab( true ); } BBSListAdmin::~BBSListAdmin() { #ifdef _DEBUG std::cout << "BBSListAdmin::~BBSListAdmin\n"; #endif BBSLIST::delete_undo_buffer_favorite(); } void BBSListAdmin::save_session() { Admin::save_session(); // bbslistのページの位置保存 SESSION::set_bbslist_page( get_current_page() ); } // 前回開いていたURLを復元 void BBSListAdmin::restore( const bool only_locked ) { COMMAND_ARGS command_arg; // 板一覧 // 初回起動時は板一覧の読み込みもここで行われる command_arg = url_to_openarg( URL_BBSLISTVIEW, true, false ); open_view( command_arg ); // お気に入り command_arg = url_to_openarg( URL_FAVORITEVIEW, true, false ); open_view( command_arg ); // スレ履歴 command_arg = url_to_openarg( URL_HISTTHREADVIEW, true, false ); open_view( command_arg ); // 板履歴 command_arg = url_to_openarg( URL_HISTBOARDVIEW, true, false ); open_view( command_arg ); // 最近閉じたスレ command_arg = url_to_openarg( URL_HISTCLOSEVIEW, true, false ); open_view( command_arg ); // 最近閉じた板 command_arg = url_to_openarg( URL_HISTCLOSEBOARDVIEW, true, false ); open_view( command_arg ); // 最近閉じた画像 command_arg = url_to_openarg( URL_HISTCLOSEIMGVIEW, true, false ); open_view( command_arg ); set_command( "set_page", std::string(), std::to_string( SESSION::bbslist_page() ) ); set_command( "hide_tabs" ); } COMMAND_ARGS BBSListAdmin::url_to_openarg( const std::string& url, const bool tab, const bool lock ) { COMMAND_ARGS command_arg; command_arg.command = "open_view"; command_arg.url = url; if( tab ) command_arg.arg1 = "true"; // タブで開く return command_arg; } void BBSListAdmin::switch_admin() { if( ! has_focus() ) CORE::core_set_command( "switch_sidebar" ); } // // ツールバー表示 // void BBSListAdmin::show_toolbar() { // まだ作成されていない場合は作成する if( ! m_toolbar ){ m_toolbar = std::make_unique(); get_notebook()->append_toolbar( *m_toolbar ); if( SESSION::get_show_bbslist_toolbar() ) m_toolbar->open_buttonbar(); } get_notebook()->show_toolbar(); } // // ツールバー表示/非表示切り替え // void BBSListAdmin::toggle_toolbar() { if( ! m_toolbar ) return; if( SESSION::get_show_bbslist_toolbar() ){ m_toolbar->open_buttonbar(); m_toolbar->show_toolbar(); } else m_toolbar->close_buttonbar(); } SKELETON::View* BBSListAdmin::create_view( const COMMAND_ARGS& command ) { int type = CORE::VIEW_NONE; if( command.url == URL_BBSLISTVIEW ) type = CORE::VIEW_BBSLISTVIEW; if( command.url == URL_FAVORITEVIEW ) type = CORE::VIEW_FAVORITELIST; if( command.url == URL_HISTTHREADVIEW ) type = CORE::VIEW_HISTTHREAD; if( command.url == URL_HISTCLOSEVIEW ) type = CORE::VIEW_HISTCLOSE; if( command.url == URL_HISTBOARDVIEW ) type = CORE::VIEW_HISTBOARD; if( command.url == URL_HISTCLOSEBOARDVIEW ) type = CORE::VIEW_HISTCLOSEBOARD; if( command.url == URL_HISTCLOSEIMGVIEW ) type = CORE::VIEW_HISTCLOSEIMG; CORE::VIEWFACTORY_ARGS view_args; view_args.arg1 = command.arg4; view_args.arg2 = command.arg5; SKELETON::View* view = CORE::ViewFactory( type, command.url, view_args ); return view; } // // ローカルなコマンド // void BBSListAdmin::command_local( const COMMAND_ARGS& command ) { SKELETON::View* view = get_view( command.url ); if( view ){ // アイテム追加 // 共有バッファにコピーデータをセットしておくこと if( command.command == "append_item" ) view->set_command( "append_item" ); // 履歴のセット // 先頭にアイテムを追加する。ツリーにアイテムが含まれている場合は移動する // 共有バッファにコピーデータをセットしておくこと else if( command.command == "append_history" ) view->set_command( "append_history" ); // 項目削除 else if( command.command == "remove_item" ) view->set_command( "remove_item", command.arg1 ); // 先頭項目削除 else if( command.command == "remove_headitem" ) view->set_command( "remove_headitem" ); // 全項目削除 else if( command.command == "remove_allitems" ) view->set_command( "remove_allitems" ); // ツリーの編集 else if( command.command == "edit_tree" ) view->set_command( "edit_tree" ); // お気に入りルート更新チェック else if( command.command == "check_update_root" ) view->set_command( "check_update_root" ); else if( command.command == "check_update_open_root" ) view->set_command( "check_update_open_root" ); else if( command.command == "cancel_check_update" ) view->set_command( "cancel_check_update" ); // お気に入りのスレの url と 名前を変更 else if( command.command == "replace_thread" ) view->set_command( "replace_thread", command.arg1, command.arg2 ); // XML保存 else if( command.command == "save_xml" ) view->set_command( "save_xml" ); // スレのアイコン表示を更新 else if( command.command == "toggle_articleicon" ) view->set_command( "toggle_articleicon", command.arg1 ); // 板のアイコン表示を更新 else if( command.command == "toggle_boardicon" ) view->set_command( "toggle_boardicon", command.arg1 ); } // URLを選択 if( command.command == "select_item" ){ view = get_current_view(); if( view ) view->set_command( "select_item", command.arg1 ); } } // 履歴を DATA_INFO_LIST 型で取得 void BBSListAdmin::get_history( const std::string& url, CORE::DATA_INFO_LIST& info_list ) { info_list.clear(); BBSListViewBase* view = dynamic_cast< BBSListViewBase* >( get_view( url ) ); if( view ) return view->get_history( info_list ); } // サイドバーの指定したidのディレクトリに含まれるスレのアドレスを取得 void BBSListAdmin::get_threads( const std::string& url, const int dirid, std::vector< std::string >& list_url ) { list_url.clear(); BBSListViewBase* view = dynamic_cast< BBSListViewBase* >( get_view( url ) ); if( view ) view->get_threads( dirid, list_url ); } // サイドバーの指定したidのディレクトリの名前を取得 std::string BBSListAdmin::get_dirname( const std::string& url, const int dirid ) { BBSListViewBase* view = dynamic_cast< BBSListViewBase* >( get_view( url ) ); if( view ) return view->get_dirname( dirid ); return std::string(); } jdim-0.7.0/src/bbslist/bbslistadmin.h000066400000000000000000000042031417047150700175230ustar00rootroot00000000000000// ライセンス: GPL2 // // 板の管理クラス // #ifndef _BBSLISTADMIN_H #define _BBSLISTADMIN_H #include "skeleton/admin.h" #include "type.h" #include "data_info.h" #include #include #include namespace SKELETON { class UNDO_BUFFER; } namespace BBSLIST { class BBSListToolBar; class BBSListAdmin : public SKELETON::Admin { std::unique_ptr m_toolbar; public: explicit BBSListAdmin( const std::string& url ); ~BBSListAdmin(); void save_session() override; // 履歴を DATA_INFO_LIST 型で取得 void get_history( const std::string& url, CORE::DATA_INFO_LIST& info_list ); // サイドバーの指定したidのディレクトリに含まれるスレのアドレスを取得 void get_threads( const std::string& url, const int dirid, std::vector< std::string >& list_url ); // サイドバーの指定したidのディレクトリの名前を取得 std::string get_dirname( const std::string& url, const int dirid ); protected: SKELETON::View* create_view( const COMMAND_ARGS& command ) override; // ツールバー void show_toolbar() override; void toggle_toolbar() override; void command_local( const COMMAND_ARGS& command ) override; void restore( const bool only_locked ) override; COMMAND_ARGS url_to_openarg( const std::string& url, const bool tab, const bool lock ) override; void switch_admin() override; // bbslistはクローズしない void close_view( const std::string& url ) override {} void close_all_view( const std::string& url ) override {} // タブの D&D 処理をしない void slot_drag_data_get( Gtk::SelectionData& selection_data, const int page ) override {} // タブメニュー表示キャンセル void slot_tab_menu( int page, int x, int y ) override {} }; BBSLIST::BBSListAdmin* get_admin(); void delete_admin(); SKELETON::UNDO_BUFFER* get_undo_buffer_favorite(); void delete_undo_buffer_favorite(); } #endif jdim-0.7.0/src/bbslist/bbslistview.cpp000066400000000000000000000155531417047150700177520ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "bbslistview.h" #include "bbslistadmin.h" #include "skeleton/msgdiag.h" #include "dbtree/interface.h" #include "config/globalconf.h" #include "jdlib/misctime.h" #include "cache.h" #include "type.h" #include "command.h" using namespace BBSLIST; // ルート要素名( boards.xml ) #define ROOT_NODE_NAME "boardlist" // メインビュー BBSListViewMain::BBSListViewMain( const std::string& url, const std::string& arg1, const std::string& arg2 ) : BBSListViewBase( url, arg1, arg2 ) { set_label( "板一覧" ); set_open_only_onedir( CONFIG::get_open_one_category() ); } BBSListViewMain::~BBSListViewMain() { #ifdef _DEBUG std::cout << "BBSListViewMain::~BBSListViewMain : " << get_url() << std::endl; #endif } // xml保存 void BBSListViewMain::save_xml() { const std::string file = CACHE::path_xml_listmain(); save_xml_impl( file, ROOT_NODE_NAME, SUBDIR_ETCLIST ); } // // 表示 // void BBSListViewMain::show_view() { #ifdef _DEBUG std::cout << "BBSListViewMain::show_view : " << get_url() << std::endl; #endif // BBSListViewBase::m_document に Root::m_xml_document を代入 set_document( DBTREE::get_xml_document() ); if( get_document().hasChildNodes() ) update_view(); // 板一覧のDomノードが空ならサーバからbbsmenuを取得 // 取得が終わったらBBSListViewMain::update_view()が呼び出される else DBTREE::download_bbsmenu(); } // // 表示更新 // void BBSListViewMain::update_view() { XML::Document document; document = DBTREE::get_xml_document(); // 空なら更新しない if( ! document.hasChildNodes() ) return; // BBSListViewBase::m_document に Root::m_document を代入 set_document( document ); // ルート要素を取得 XML::Dom* root = get_document().get_root_element( std::string( ROOT_NODE_NAME ) ); //---------------------------------- // 外部板追加 // を挿入 // ルート要素の有無で処理を分ける( 旧様式=無, 新様式=有 ) XML::Dom* subdir = nullptr; if( root ) subdir = root->emplace_front( XML::NODE_TYPE_ELEMENT, "subdir" ); else subdir = get_document().emplace_front( XML::NODE_TYPE_ELEMENT, "subdir" ); subdir->setAttribute( "name", std::string( SUBDIR_ETCLIST ) ); // 子要素( )を追加 std::list< DBTREE::ETCBOARDINFO > list_etc = DBTREE::get_etcboards(); // 外部板情報( dbtree/etcboardinfo.h ) if( ! list_etc.empty() ) { std::list< DBTREE::ETCBOARDINFO >::iterator it = list_etc.begin(); while( it != list_etc.end() ) { XML::Dom* board = subdir->appendChild( XML::NODE_TYPE_ELEMENT, "board" ); board->setAttribute( "name", (*it).name ); board->setAttribute( "url", (*it).url ); ++it; } } // 外部板追加ここまで //---------------------------------- // BBSListViewBase::xml2tree() m_document -> tree xml2tree( std::string( ROOT_NODE_NAME ) ); set_status( std::string() ); BBSLIST::get_admin()->set_command( "set_status", get_url(), get_status() ); } // // 削除 // // BBSListViewMainの場合は外部板の削除 // void BBSListViewMain::delete_view() { // 選択範囲に通常の板が含まれていないか確認 std::list< Gtk::TreeModel::iterator > list_it = get_treeview().get_selected_iterators(); if( std::any_of( list_it.cbegin(), list_it.cend(), [this]( const Gtk::TreeIter& iter ) { return ! is_etcboard( iter ); } ) ) { SKELETON::MsgDiag mdiag( get_parent_win(), "通常の板は削除出来ません", false, Gtk::MESSAGE_ERROR ); mdiag.run(); return; } SKELETON::MsgDiag mdiag( get_parent_win(), "削除しますか?", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO ); if( mdiag.run() != Gtk::RESPONSE_YES ) return; delete_view_impl(); } // BBSListViewMainの場合は外部板の削除 void BBSListViewMain::delete_view_impl() { #ifdef _DEBUG std::cout << "BBSListViewMain::delete_view_impl\n"; #endif // データベースから外部板削除 std::list< Gtk::TreeModel::iterator > list_it = get_treeview().get_selected_iterators(); for( Gtk::TreeModel::iterator& iter : list_it ) { if( is_etcboard( iter ) ) { Gtk::TreePath path = get_treestore()->get_path( iter ); std::string url = path2rawurl( path ); std::string name = path2name( path ); #ifdef _DEBUG std::cout << "path = " << path.to_string() << std::endl << "url = " << url << std::endl << "name = " << name << std::endl; #endif DBTREE::remove_etc( url , name ); } } const bool force = true; // 強制的に削除 get_treeview().delete_selected_rows( force ); // etc.txt保存 DBTREE::save_etc(); } void BBSListViewMain::show_preference() { std::string modified = "最終更新日時 :"; if( DBTREE::get_date_modified().empty() ) modified += "未取得"; else modified += MISC::timettostr( DBTREE::get_time_modified(), MISC::TIME_WEEK ) + " ( " + MISC::timettostr( DBTREE::get_time_modified(), MISC::TIME_PASSED ) + " )"; SKELETON::MsgDiag mdiag( get_parent_win(), modified, false ); mdiag.set_title( "板一覧のプロパティ" ); mdiag.run(); } // // ポップアップメニュー取得 // // SKELETON::View::show_popupmenu() を参照すること // Gtk::Menu* BBSListViewMain::get_popupmenu( const std::string& url ) { Gtk::Menu* popupmenu = nullptr; if( url.empty() ) return popupmenu; #ifdef _DEBUG std::cout << "BBSListViewMain::get_popupmenu\n"; #endif std::list< Gtk::TreeModel::iterator > list_it = get_treeview().get_selected_iterators(); if( list_it.size() == 1 ){ Gtk::TreePath path = *( get_treeview().get_selection()->get_selected_rows().begin() ); int type = path2type( path ); #ifdef _DEBUG std::cout << "path = " << path.to_string() << " type = " << type << std::endl; #endif if( type == TYPE_DIR ){ if( is_etcdir( path ) ) popupmenu = id2popupmenu( "/popup_menu_etcdir" ); else popupmenu = id2popupmenu( "/popup_menu_dir" ); } else if( type == TYPE_BOARD || type == TYPE_BOARD_UPDATE ){ if( is_etcboard( path ) ) popupmenu = id2popupmenu( "/popup_menu_etc" ); else popupmenu = id2popupmenu( "/popup_menu" ); } } else{ const bool have_etc = std::all_of( list_it.cbegin(), list_it.cend(), [this]( const Gtk::TreeIter& iter ) { return is_etcboard( iter ); } ); if( have_etc ) popupmenu = id2popupmenu( "/popup_menu_mul_etc" ); else popupmenu = id2popupmenu( "/popup_menu_mul" ); } return popupmenu; } jdim-0.7.0/src/bbslist/bbslistview.h000066400000000000000000000014011417047150700174020ustar00rootroot00000000000000// ライセンス: GPL2 // // メインビュー // #ifndef _BBSLISTVIEW_H #define _BBSLISTVIEW_H #include "bbslistviewbase.h" namespace BBSLIST { // メインビュー class BBSListViewMain : public BBSListViewBase { public: explicit BBSListViewMain( const std::string& url, const std::string& arg1 = {}, const std::string& arg2 = {} ); ~BBSListViewMain(); void show_view() override; void update_view() override; void delete_view() override; void show_preference() override; protected: // xml保存 void save_xml() override; Gtk::Menu* get_popupmenu( const std::string& url ) override; private: void delete_view_impl() override; }; } #endif jdim-0.7.0/src/bbslist/bbslistviewbase.cpp000066400000000000000000002643361417047150700206120ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG //#define _DEBUG_XML #include "jddebug.h" #include "gtkmmversion.h" #include "bbslistviewbase.h" #include "bbslistadmin.h" #include "selectdialog.h" #include "editlistwin.h" #include "addetcdialog.h" #include "skeleton/msgdiag.h" #include "jdlib/miscutil.h" #include "jdlib/miscgtk.h" #include "jdlib/jdregex.h" #include "dbtree/interface.h" #include "dbimg/imginterface.h" #include "icons/iconmanager.h" #include "xml/tools.h" #include "config/globalconf.h" #include "control/controlutil.h" #include "control/controlid.h" #include "cache.h" #include "command.h" #include "global.h" #include "httpcode.h" #include "sharedbuffer.h" #include "viewfactory.h" #include "prefdiagfactory.h" #include "colorid.h" #include "fontid.h" #include "updatemanager.h" #include "session.h" #include "compmanager.h" #include "dndmanager.h" #include "sign.h" #include enum { REPLACE_NEXT_NO = 0, REPLACE_NEXT_YES, REPLACE_NEXT_ADD }; // row -> path #define GET_PATH( row ) m_treestore->get_path( row ) // ポップアップメニュー表示 #define SHOW_POPUPMENU(slot) do{\ std::string url = path2url( m_path_selected ); \ if( ! m_path_selected.empty() && url.empty() ) url = "dummy_url"; \ show_popupmenu( url, slot ); \ }while(0) #define POPUPMENU_BOARD1 \ "" \ "" \ "" \ "" \ "" \ "" \ "" \ "" #define POPUPMENU_BOARD2 \ "" \ "" \ "" \ "" \ "" \ "" \ #define POPUPMENU_ARRANGE_BASE \ "" \ "" \ "" \ "" #define POPUPMENU_ARRANGE \ "
" \ POPUPMENU_ARRANGE_BASE #define POPUPMENU_ARRANGEDIR \ "" \ POPUPMENU_ARRANGE_BASE #define POPUPMENU_DELETE \ "" \ "" \ "" #define POPUPMENU_SELECT \ "" \ "" \ "" \ "" \ "" \ "" \ "" \ POPUPMENU_ARRANGE \ "" \ POPUPMENU_DELETE \ "" \ "" \ "" \ "" \ "" \ "" \ "" \ "" using namespace BBSLIST; BBSListViewBase::BBSListViewBase( const std::string& url, const std::string& arg1, const std::string& arg2 ) : SKELETON::View( url ) , m_treeview( url, DNDTARGET_FAVORITE, m_columns, true, CONFIG::get_fontname( FONT_BBS ), COLOR_CHAR_BBS, COLOR_BACK_BBS, COLOR_BACK_BBS_EVEN ) , m_jump_y{ -1 } { m_scrwin.add( m_treeview ); m_scrwin.set_policy( Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC ); pack_start( m_scrwin ); show_all_children(); m_treestore = Gtk::TreeStore::create( m_columns ); // Gtk::TreeStoreでset_fixed_height_mode()を使うとexpandしたときに // スクロールバーが誤動作するので使わないこと /* #if GTK_CHECK_VERSION(2,6,0) // セルを固定の高さにする // append_column する前に columnに対して set_sizing( Gtk::TREE_VIEW_COLUMN_FIXED ) すること m_treeview.set_fixed_height_mode( true ); #ifdef _DEBUG std::cout << "BBSListViewBase::BBSListViewBase : m_treeview.set_fixed_height_mode\n"; #endif #endif */ // 共有UNDOバッファをセット m_treeview.set_undo_buffer( BBSLIST::get_undo_buffer_favorite() ); // 列の登録 m_treeview.create_column( CONFIG::get_tree_ypad() ); m_treeview.set_column_for_height( 0 ); // エクスパンダ表示とレベルインデント m_treeview.set_show_expanders( CONFIG::get_tree_show_expanders() ); m_treeview.set_level_indentation( CONFIG::get_tree_level_indent() ); m_treeview.add_events( Gdk::SMOOTH_SCROLL_MASK ); // treeviewのシグナルにコネクト m_treeview.signal_row_expanded().connect( sigc::mem_fun(*this, &BBSListViewBase::slot_row_exp ) ); m_treeview.signal_row_collapsed().connect( sigc::mem_fun(*this, &BBSListViewBase::slot_row_col ) ); m_treeview.set_has_tooltip( true ); m_treeview.signal_query_tooltip().connect( sigc::mem_fun(*this, &BBSListViewBase::slot_query_tooltip) ); m_treeview.sig_button_press().connect( sigc::mem_fun(*this, &BBSListViewBase::slot_button_press ) ); m_treeview.sig_button_release().connect( sigc::mem_fun(*this, &BBSListViewBase::slot_button_release ) ); m_treeview.sig_motion_notify().connect( sigc::mem_fun(*this, &BBSListViewBase::slot_motion_notify ) ); m_treeview.sig_key_press().connect( sigc::mem_fun(*this, &BBSListViewBase::slot_key_press ) ); m_treeview.sig_key_release().connect( sigc::mem_fun(*this, &BBSListViewBase::slot_key_release ) ); m_treeview.sig_scroll_event().connect( sigc::mem_fun(*this, &BBSListViewBase::slot_scroll_event ) ); m_treeview.sig_dropped_from_other().connect( sigc::mem_fun(*this, &BBSListViewBase::slot_dropped_from_other ) ); /////////////////// // ポップアップメニューの設定 // アクショングループを作ってUIマネージャに登録 action_group() = Gtk::ActionGroup::create(); action_group()->add( Gtk::Action::create( "OpenTab", "タブで開く(_T)"), sigc::mem_fun( *this, &BBSListViewBase::slot_open_tab ) ); action_group()->add( Gtk::Action::create( "OpenBrowser", ITEM_NAME_OPEN_BROWSER "(_W)" ), sigc::mem_fun( *this, &BBSListViewBase::slot_open_browser ) ); action_group()->add( Gtk::Action::create( "OpenCacheBrowser", ITEM_NAME_OPEN_CACHE_BROWSER "(_X)" ), sigc::mem_fun( *this, &BBSListViewBase::slot_open_cache_browser ) ); action_group()->add( Gtk::Action::create( "AppendFavorite", "AppendFavorite"), sigc::mem_fun( *this, &BBSListViewBase::slot_append_favorite ) ); action_group()->add( Gtk::Action::create( "NewDir", "新規ディレクトリ(_N)"), sigc::mem_fun( *this, &BBSListViewBase::slot_newdir ) ); action_group()->add( Gtk::Action::create( "NewCom", "コメント挿入(_I)"), sigc::mem_fun( *this, &BBSListViewBase::slot_newcomment ) ); action_group()->add( Gtk::Action::create( "NewEtc", "外部板追加(_E)..."), sigc::mem_fun( *this, &BBSListViewBase::slot_newetcboard ) ); action_group()->add( Gtk::Action::create( "MoveEtc", "編集(_E)..."), sigc::mem_fun( *this, &BBSListViewBase::slot_moveetcboard ) ); action_group()->add( Gtk::Action::create( "Rename", "名前変更(_R)"), sigc::mem_fun( *this, &BBSListViewBase::slot_rename ) ); action_group()->add( Gtk::Action::create( "Delete_Menu", "Delete" ) ); action_group()->add( Gtk::Action::create( "Delete", "お気に入りから削除する(_D)"), sigc::mem_fun( *this, &BBSListViewBase::delete_view_impl ) ); action_group()->add( Gtk::Action::create( "Delete_etc", "外部板を削除する(_D)"), sigc::mem_fun( *this, &BBSListViewBase::delete_view_impl ) ); action_group()->add( Gtk::Action::create( "Delete_hist", "履歴から削除する(_D)"), sigc::mem_fun( *this, &BBSListViewBase::delete_view_impl ) ); action_group()->add( Gtk::Action::create( "OpenRows", "選択した行を開く(_O)"), sigc::mem_fun( *this, &BBSListViewBase::open_selected_rows ) ); action_group()->add( Gtk::Action::create( "CheckUpdateRows", "更新チェックのみ(_H)"), sigc::mem_fun( *this, &BBSListViewBase::slot_checkupdate_selected_rows ) ); action_group()->add( Gtk::Action::create( "CheckUpdateOpenRows", "更新された行をタブで開く(_E)"), sigc::mem_fun( *this, &BBSListViewBase::slot_checkupdate_open_selected_rows ) ); action_group()->add( Gtk::Action::create( "CopyURL", ITEM_NAME_COPY_URL "(_U)"), sigc::mem_fun( *this, &BBSListViewBase::slot_copy_url ) ); action_group()->add( Gtk::Action::create( "CopyTitleURL", ITEM_NAME_COPY_TITLE_URL "(_L)" ), sigc::mem_fun( *this, &BBSListViewBase::slot_copy_title_url ) ); action_group()->add( Gtk::Action::create( "SelectDir", "全て選択(_A)"), sigc::mem_fun( *this, &BBSListViewBase::slot_select_all_dir ) ); action_group()->add( Gtk::Action::create( "CheckUpdate_Menu", "更新チェック(_M)" ) ); action_group()->add( Gtk::Action::create( "CheckUpdateDir", "更新チェックのみ(_R)"), sigc::mem_fun( *this, &BBSListViewBase::slot_check_update_dir ) ); action_group()->add( Gtk::Action::create( "CheckUpdateOpenDir", "更新された行をタブで開く(_A)"), sigc::mem_fun( *this, &BBSListViewBase::slot_check_update_open_dir ) ); action_group()->add( Gtk::Action::create( "CancelCheckUpdate", "キャンセル(_C)" ), sigc::mem_fun( *this, &BBSListViewBase::stop ) ); action_group()->add( Gtk::Action::create( "Arrange_Menu", "並び替え(_G)" ) ); action_group()->add( Gtk::Action::create( "ArrangeDir_Menu", "ディレクトリ内の並び替え(_G)" ) ); action_group()->add( Gtk::Action::create( "Arrange_Type", "種類順(_T)"), sigc::bind< int >( sigc::mem_fun( *this, &BBSListViewBase::slot_sort ), SKELETON::SORT_BY_TYPE ) ); action_group()->add( Gtk::Action::create( "Arrange_Name", "名前順(_N)"), sigc::bind< int >( sigc::mem_fun( *this, &BBSListViewBase::slot_sort ), SKELETON::SORT_BY_NAME ) ); action_group()->add( Gtk::Action::create( "OpenAsBoard", "ディレクトリをスレ一覧に表示(_B)"), sigc::mem_fun( *this, &BBSListViewBase::slot_opendir_as_board ) ); action_group()->add( Gtk::Action::create( "CreateVBoard", "仮想板作成(_V)"), sigc::mem_fun( *this, &BBSListViewBase::slot_create_vboard ) ); action_group()->add( Gtk::Action::create( "SearchCacheBoard", "キャッシュ内ログ検索(_S)"), sigc::mem_fun( *this, &BBSListViewBase::slot_search_cache_board ) ); action_group()->add( Gtk::Action::create( "ImportDat", "datのインポート(_I)"), sigc::mem_fun( *this, &BBSListViewBase::slot_import_dat ) ); action_group()->add( Gtk::Action::create( "PreferenceArticle", ITEM_NAME_PREF_THREAD "(_P)..." ), sigc::mem_fun( *this, &BBSListViewBase::slot_preferences_article ) ); action_group()->add( Gtk::Action::create( "PreferenceBoard", ITEM_NAME_PREF_BOARD "(_O)..." ), sigc::mem_fun( *this, &BBSListViewBase::slot_preferences_board ) ); action_group()->add( Gtk::Action::create( "PreferenceImage", ITEM_NAME_PREF_IMAGE "(_M)..." ), sigc::mem_fun( *this, &BBSListViewBase::slot_preferences_image ) ); ui_manager() = Gtk::UIManager::create(); ui_manager()->insert_action_group( action_group() ); // ポップアップメニューのレイアウト Glib::ustring str_ui = "" // 板一覧 + 板 "" POPUPMENU_BOARD1 POPUPMENU_BOARD2 // 板一覧 + 外部板 "" POPUPMENU_BOARD1 "" "" "" "" "" "" POPUPMENU_BOARD2 // 板一覧 + 複数選択 "" "" "" "" "" // 板一覧 + 複数選択 + 外部板含む "" "" "" "" "" "" "" "" "" // 板一覧 + ディレクトリ "" "" "" // 板一覧 + 外部板ディレクトリ "" "" "" "" "" ///////////////////////////////////////// // お気に入り "" "" "" "" "" POPUPMENU_SELECT "" // お気に入り + 複数選択 "" "" "" "" "" "" "" "" "" "" POPUPMENU_DELETE "" // お気に入り + 何もないところをクリック "" "" "" "" // お気に入り + ディレクトリ "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" POPUPMENU_ARRANGEDIR "" POPUPMENU_DELETE "" // お気に入り + コメント "" "" "" "" "" POPUPMENU_ARRANGE "" POPUPMENU_DELETE "" // お気に入り + 仮想板 "" "" "" "" "" "" "" POPUPMENU_ARRANGE "" POPUPMENU_DELETE "" ////////////////////////////////////// // 選択(selectlistview) "" POPUPMENU_SELECT "" ///////////////////////////////////////// // 履歴 "" "" "" "" "" \ "" \ "" "" "" \ "" \ "" \ "" \ "" \ "" \ "" \ "" \ "" \ "" \ "" \ "" \ "" // 履歴 + 複数選択 "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" // 履歴 + 仮想板 "" "" "" "" "" "" "" ""; ui_manager()->add_ui_from_string( str_ui ); // ポップアップメニューにキーアクセレータを表示 Gtk::Menu* popupmenu = id2popupmenu( "/popup_menu" ); CONTROL::set_menu_motion( popupmenu ); popupmenu = id2popupmenu( "/popup_menu_etc" ); CONTROL::set_menu_motion( popupmenu ); popupmenu = id2popupmenu( "/popup_menu_mul" ); CONTROL::set_menu_motion( popupmenu ); popupmenu = id2popupmenu( "/popup_menu_mul_etc" ); CONTROL::set_menu_motion( popupmenu ); popupmenu = id2popupmenu( "/popup_menu_dir" ); CONTROL::set_menu_motion( popupmenu ); popupmenu = id2popupmenu( "/popup_menu_favorite" ); CONTROL::set_menu_motion( popupmenu ); popupmenu = id2popupmenu( "/popup_menu_favorite_mul" ); CONTROL::set_menu_motion( popupmenu ); popupmenu = id2popupmenu( "/popup_menu_favorite_space" ); CONTROL::set_menu_motion( popupmenu ); popupmenu = id2popupmenu( "/popup_menu_favorite_dir" ); CONTROL::set_menu_motion( popupmenu ); popupmenu = id2popupmenu( "/popup_menu_favorite_com" ); CONTROL::set_menu_motion( popupmenu ); popupmenu = id2popupmenu( "/popup_menu_favorite_vboard" ); CONTROL::set_menu_motion( popupmenu ); popupmenu = id2popupmenu( "/popup_menu_history" ); CONTROL::set_menu_motion( popupmenu ); popupmenu = id2popupmenu( "/popup_menu_history_mul" ); CONTROL::set_menu_motion( popupmenu ); popupmenu = id2popupmenu( "/popup_menu_history_vboard" ); CONTROL::set_menu_motion( popupmenu ); popupmenu = id2popupmenu( "/popup_menu_select" ); CONTROL::set_menu_motion( popupmenu ); // マウスジェスチャ可 set_enable_mg( true ); // コントロールモード設定 get_control().add_mode( CONTROL::MODE_BBSLIST ); } // EditListWin が不完全型のためヘッダーではデストラクタを定義できない BBSListViewBase::~BBSListViewBase() noexcept = default; void BBSListViewBase::save_session() { save_xml(); } SKELETON::Admin* BBSListViewBase::get_admin() { return BBSLIST::get_admin(); } // // treeviewのD&Dによる編集を可能にする // void BBSListViewBase::set_editable( const bool editable ) { get_treeview().set_editable_view( editable ); } // // 親ウィンドウをセット // void BBSListViewBase::set_parent_win( Gtk::Window* parent_win ) { SKELETON::View::set_parent_win( parent_win ); get_treeview().set_parent_win( parent_win ); } // // コマンド // bool BBSListViewBase::set_command( const std::string& command, const std::string& arg1, const std::string& arg2 ) { if( command == "append_item" ) append_item(); else if( command == "append_history" ) append_history(); else if( command == "remove_item" ) remove_item( arg1 ); else if( command == "remove_headitem" ) remove_headitem(); else if( command == "remove_allitems" ) remove_allitems(); else if( command == "edit_tree" ) edit_tree(); else if( command == "save_xml" ) save_xml(); else if( command == "toggle_articleicon" ) toggle_articleicon( arg1 ); else if( command == "toggle_boardicon" ) toggle_boardicon( arg1 ); else if( command == "replace_thread" ) replace_thread( arg1, arg2 ); else if( command == "hide_popup" ) m_treeview.hide_popup(); else if( command == "check_update_root" ) check_update_dir( true, false ); else if( command == "check_update_open_root" ) check_update_dir( true, true ); else if( command == "cancel_check_update" ) stop(); else if( command == "select_item" ) select_item( arg1 ); return true; } // // クロック入力 // void BBSListViewBase::clock_in() { View::clock_in(); m_treeview.clock_in(); if( m_editlistwin ) m_editlistwin->clock_in(); // スクロールバー移動 // 初期化直後など、まだスクロールバーが表示されてない時があるので表示されるまでジャンプしない if( m_jump_y != -1 ){ auto adjust = m_treeview.get_vadjustment(); if( adjust && adjust->get_upper() > m_jump_y ){ #ifdef _DEBUG std::cout << "BBSListViewBase::clock_in jump to = " << m_jump_y << " upper = " << (int)adjust->get_upper() << std::endl; #endif // 何故か先頭にジャンプ出来ないので 1 にジャンプする if( m_jump_y == 0 ) m_jump_y = 1; adjust->set_value( m_jump_y ); m_jump_y = -1; } } } // // コメント色変更( 再帰用 ) // void BBSListViewBase::set_fgcolor_of_comment( const Gtk::TreeModel::Children& children ) { for( Gtk::TreeModel::Row row : children ) { const int type = row2type( row ); if( type == TYPE_COMMENT ) row[ m_columns.m_fgcolor ] = Gdk::RGBA( CONFIG::get_color( COLOR_CHAR_BBS_COMMENT ) ); else if( type == TYPE_DIR ) set_fgcolor_of_comment( row.children() ); } } // // 再描画 // void BBSListViewBase::redraw_view() { // 起動中とシャットダウン中は処理しない if( SESSION::is_booting() ) return; if( SESSION::is_quitting() ) return; #ifdef _DEBUG std::cout << "BBSListViewBase::ViewBase::redraw_view " << get_url() << std::endl; #endif m_treeview.redraw_view(); } // // 色やフォントなどの変更 // void BBSListViewBase::relayout() { m_treeview.init_color( COLOR_CHAR_BBS, COLOR_BACK_BBS, COLOR_BACK_BBS_EVEN ); m_treeview.init_font( CONFIG::get_fontname( FONT_BBS ) ); set_fgcolor_of_comment( m_treestore->children() ); } // // フォーカス // void BBSListViewBase::focus_view() { // 行の名前を編集中なら何もしない if( m_treeview.is_renaming_row() ) return; #ifdef _DEBUG std::cout << "BBSListViewBase::focus_view url = " << get_url() << std::endl; #endif m_treeview.grab_focus(); } // // フォーカスアウト // void BBSListViewBase::focus_out() { SKELETON::View::focus_out(); m_treeview.hide_popup(); } // // 閉じる // void BBSListViewBase::close_view() { #ifdef _DEBUG std::cout << "BBSListViewBase::close_view\n"; #endif CORE::core_set_command( "toggle_sidebar" ); } // // 削除 // // BBSListViewBaseの場合は選択行の削除 // void BBSListViewBase::delete_view() { SKELETON::MsgDiag mdiag( get_parent_win(), "削除しますか?", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO ); if( mdiag.run() != Gtk::RESPONSE_YES ) return; delete_view_impl(); } // BBSListViewBaseの場合は選択行の削除 void BBSListViewBase::delete_view_impl() { const bool force = true; m_treeview.delete_selected_rows( force ); } // // ツリー内の全ての項目をURLを新しいアドレスに変更 ( id は未使用 ) // void BBSListViewBase::update_item( const std::string& url, const std::string& id ) { #ifdef _DEBUG std::cout << "BBSListViewBase::update_item " << url << " / " << get_url() << std::endl; #endif if( url == get_url() ) update_urls(); } // // viewの操作 // bool BBSListViewBase::operate_view( const int control ) { if( CONTROL::operate_common( control, get_url(), BBSLIST::get_admin() ) ) return true; Gtk::TreePath path = m_treeview.get_current_path(); bool open_tab = false; #ifdef _DEBUG std::cout << "BBSListViewBase::operate_view = " << control << std::endl; #endif switch( control ){ case CONTROL::Down: row_down(); break; case CONTROL::Up: row_up(); break; case CONTROL::PageUp: page_up(); break; case CONTROL::PageDown: page_down(); break; case CONTROL::PrevDir: prev_dir(); break; case CONTROL::NextDir: next_dir(); break; case CONTROL::Home: goto_top(); break; case CONTROL::End: goto_bottom(); break; // 全て選択 case CONTROL::SelectAll: slot_select_all(); break; // 開く case CONTROL::OpenBoardTabButton: if( ! m_path_selected.empty() ){ open_tab = true; // pathがディレクトリでタブで開くボタンを入れ替えている時はディレクトリ開閉にする if( path2type( path ) == TYPE_DIR && CONTROL::is_toggled_tab_button() ) open_tab = false; open_row( path, open_tab ); } break; case CONTROL::OpenBoardButton: if( ! m_path_selected.empty() ){ open_tab = false; // pathがディレクトリでタブで開くボタンを入れ替えている時は更新チェックにする if( path2type( path ) == TYPE_DIR && CONTROL::is_toggled_tab_button() ) open_tab = true; open_row( path, open_tab ); } break; case CONTROL::OpenBoardTab: open_tab = true; // pathがディレクトリでタブで開くキーを入れ替えている時はディレクトリ開閉にする if( path2type( path ) == TYPE_DIR && CONTROL::is_toggled_tab_key() ) open_tab = false; open_row( path, open_tab ); break; case CONTROL::OpenBoard: open_tab = false; // pathがディレクトリでタブで開くキーを入れ替えている時は更新チェックにする if( path2type( path ) == TYPE_DIR && CONTROL::is_toggled_tab_key() ) open_tab = true; open_row( path, open_tab ); break; case CONTROL::Right: if( m_treeview.get_row( path ) ){ if( ! m_treeview.expand_row( path, false ) ) switch_rightview(); } break; case CONTROL::Left: if( const Gtk::TreeModel::Row row = m_treeview.get_row( path ) ) { if( ( path2type( path ) != TYPE_DIR || ! m_treeview.row_expanded( path ) ) && row.parent() ){ path = GET_PATH( row.parent() ); m_treeview.set_cursor( path ); } m_treeview.collapse_row( path ); } break; case CONTROL::ToggleArticle: CORE::core_set_command( "toggle_article" ); break; // 終了 case CONTROL::Quit: close_view(); break; case CONTROL::Reload: reload(); break; case CONTROL::Delete: delete_view(); break; // ポップアップメニュー表示 case CONTROL::ShowPopupMenu: { if( m_treeview.get_selection()->get_selected_rows().size() >= 1 ){ m_path_selected = * (m_treeview.get_selection()->get_selected_rows().begin() ); } SHOW_POPUPMENU(true); break; } // 検索 case CONTROL::Search: set_search_invert( false ); BBSLIST::get_admin()->set_command( "focus_toolbar_search" ); break; case CONTROL::SearchInvert: set_search_invert( true ); BBSLIST::get_admin()->set_command( "focus_toolbar_search" ); break; case CONTROL::SearchNext: down_search(); break; case CONTROL::SearchPrev: up_search(); break; case CONTROL::StopLoading: CORE::core_set_command( "cancel_check_update" ); break; case CONTROL::Undo: undo(); break; case CONTROL::Redo: redo(); break; default: return false; } return true; } // // ポップアップメニューを表示する前にメニューのアクティブ状態を切り替える // // SKELETON::View::show_popupmenu() を参照すること // void BBSListViewBase::activate_act_before_popupmenu( const std::string& url ) { Glib::RefPtr< Gtk::Action > act_search, act_import, act_board, act_article, act_image, act_opencache, act_opentab; act_search = action_group()->get_action( "SearchCacheBoard" ); act_import = action_group()->get_action( "ImportDat" ); act_board = action_group()->get_action( "PreferenceBoard" ); act_article = action_group()->get_action( "PreferenceArticle" ); act_image = action_group()->get_action( "PreferenceImage" ); act_opencache = action_group()->get_action( "OpenCacheBrowser" ); act_opentab = action_group()->get_action( "OpenTab" ); if( act_search ) act_search->set_sensitive( false ); if( act_import ) act_import->set_sensitive( false ); if( act_board ) act_board->set_sensitive( false ); if( act_article ) act_article->set_sensitive( false ); if( act_image ) act_image->set_sensitive( false ); if( act_opencache ) act_opencache->set_sensitive( false ); if( act_opentab ) act_opentab->set_sensitive( true ); int type = path2type( m_path_selected ); switch( type ){ case TYPE_BOARD: case TYPE_BOARD_UPDATE: if( act_search ) act_search->set_sensitive( true ); if( act_import ) act_import->set_sensitive( true ); if( act_board ) act_board->set_sensitive( true ); break; case TYPE_THREAD: case TYPE_THREAD_UPDATE: case TYPE_THREAD_OLD: if( act_article ) act_article->set_sensitive( true ); break; case TYPE_IMAGE: if( act_image && ! DBIMG::get_abone( url ) ) act_image->set_sensitive( true ); if( act_opencache && DBIMG::is_cached( url ) ) act_opencache->set_sensitive( true ); break; case TYPE_DIR: break; case TYPE_LINK: if( act_opentab ) act_opentab->set_sensitive( false ); break; } } // idからポップアップメニュー取得 Gtk::Menu* BBSListViewBase::id2popupmenu( const std::string& id ) { return dynamic_cast< Gtk::Menu* >( ui_manager()->get_widget( id ) ); } // // 先頭に戻る // void BBSListViewBase::goto_top() { m_treeview.goto_top(); show_status(); } // // 一番最後へ // void BBSListViewBase::goto_bottom() { m_treeview.goto_bottom(); show_status(); } // // 上へ移動 // void BBSListViewBase::row_up() { m_treeview.row_up(); show_status(); } // // 下へ移動 // void BBSListViewBase::row_down() { m_treeview.row_down(); show_status(); } // // page up // void BBSListViewBase::page_up() { m_treeview.page_up(); } // // page down // void BBSListViewBase::page_down() { m_treeview.page_down(); } // // 前のディレクトリに移動 // void BBSListViewBase::prev_dir() { m_treeview.prev_dir(); } // // 次のディレクトリに移動 // void BBSListViewBase::next_dir() { m_treeview.next_dir(); } // // 他のviewのtreestoreをcopyして表示 // void BBSListViewBase::copy_treestore( const Glib::RefPtr< Gtk::TreeStore >& store ) { #ifdef _DEBUG std::cout << "BBSListViewBase::copy_treestore\n"; #endif m_treestore = store; m_treeview.set_treestore( m_treestore ); if( m_treestore->children().begin() ){ Gtk::TreePath path = GET_PATH( *( m_treestore->children().begin() ) ); if( m_treeview.get_row( path ) ){ m_treeview.collapse_all(); m_treeview.scroll_to_row( path, 0 ); m_treeview.get_selection()->unselect_all(); m_treeview.set_cursor( path ); } } } // // マウスボタン押した // bool BBSListViewBase::slot_button_press( GdkEventButton* event ) { m_clicked = true; // マウスジェスチャ get_control().MG_start( event ); // ホイールマウスジェスチャ get_control().MG_wheel_start( event ); // ダブルクリック // button_release_eventでは event->type に必ず GDK_BUTTON_RELEASE が入る m_dblclicked = false; if( event->type == GDK_2BUTTON_PRESS ) m_dblclicked = true; // 親ウィンドウがメインウィンドウならフォーカスを移す if( ! get_parent_win() ) BBSLIST::get_admin()->set_command( "switch_admin" ); return true; } // // マウスボタン離した // bool BBSListViewBase::slot_button_release( GdkEventButton* event ) { if( ! m_clicked ) return true; m_clicked = false; /// マウスジェスチャ int mg = get_control().MG_end( event ); // ホイールマウスジェスチャ // 実行された場合は何もしない if( get_control().MG_wheel_end( event ) ) return true; if( mg != CONTROL::None && enable_mg() ){ operate_view( mg ); return true; } show_status(); m_path_selected = m_treeview.get_path_under_xy( (int)event->x, (int)event->y ); // ダブルクリックの処理のため一時的にtypeを切替える GdkEventType type_copy = event->type; if( m_dblclicked ) event->type = GDK_2BUTTON_PRESS; // 行を開く if( get_control().button_alloted( event, CONTROL::OpenBoardButton ) ) operate_view( CONTROL::OpenBoardButton ); // タブで開く else if( get_control().button_alloted( event, CONTROL::OpenBoardTabButton ) ) operate_view( CONTROL::OpenBoardTabButton ); // ポップアップメニューボタン else if( get_control().button_alloted( event, CONTROL::PopupmenuButton ) ) SHOW_POPUPMENU( false ); // その他の操作 else operate_view( get_control().button_press( event ) ); event->type = type_copy; return true; } // // マウス動かした // bool BBSListViewBase::slot_motion_notify( GdkEventMotion* event ) { /// マウスジェスチャ get_control().MG_motion( event ); int x = (int)event->x; int y = (int)event->y; Gtk::TreeModel::Path path; Gtk::TreeView::Column* column; int cell_x; int cell_y; if( m_treeview.get_path_at_pos( x, y, path, column, cell_x, cell_y ) && m_treeview.get_row( path ) ){ Gtk::TreeModel::Row row = m_treeview.get_row( path ); const Glib::ustring& ustr_url = row[ m_columns.m_url ]; int type = row[ m_columns.m_type ]; m_treeview.reset_pre_popupurl( ustr_url.raw() ); // 画像ポップアップ if( type == TYPE_IMAGE ){ if( DBIMG::get_type_ext( ustr_url.raw() ) != DBIMG::T_UNKNOWN && DBIMG::get_code( ustr_url.raw() ) != HTTP_INIT ){ if( m_treeview.pre_popup_url() != ustr_url.raw() ){ SKELETON::View* view = CORE::ViewFactory( CORE::VIEW_IMAGEPOPUP, ustr_url.raw() ); m_treeview.show_popup( ustr_url.raw(), view ); } } else m_treeview.hide_popup(); } // ツールチップはslot_query_tooltipでセットする else{ m_treeview.hide_popup(); } } else{ m_treeview.hide_popup(); } return true; } // // キーを押した // bool BBSListViewBase::slot_key_press( GdkEventKey* event ) { // 行の名前を編集中なら何もしない if( m_treeview.is_renaming_row() ) return false; const int key = get_control().key_press( event ); // キー入力でboardを開くとkey_pressイベントがboadviewに送られて // 一番上のスレが開くので、open_row() は slot_key_release() で処理する if( key == CONTROL::OpenBoard ) return true; if( key == CONTROL::OpenBoardTab ) return true; if( operate_view( key ) ) return true; return false; } // // キー上げた // bool BBSListViewBase::slot_key_release( GdkEventKey* event ) { #ifdef _DEBUG bool ctrl = ( event->state ) & GDK_CONTROL_MASK; bool shift = ( event->state ) & GDK_SHIFT_MASK; std::cout << "BBSListViewBase::slot_key_release key = " << event->keyval << " ctrl = " << ctrl << " shift = " << shift << std::endl; #endif // 行の名前を編集中なら何もしない if( m_treeview.is_renaming_row() ) return false; // キー入力でboardを開くとkey_pressイベントがboadviewに送られて // 一番上のスレが開くので、open_row() は slot_key_release() で処理する int key = get_control().key_press( event ); if( key == CONTROL::OpenBoard || key == CONTROL::OpenBoardTab ) operate_view( key ); return true; } // // マウスホイールイベント // bool BBSListViewBase::slot_scroll_event( GdkEventScroll* event ) { // ホイールマウスジェスチャ int control = get_control().MG_wheel_scroll( event ); if( enable_mg() && control != CONTROL::None ){ operate_view( control ); return true; } m_treeview.wheelscroll( event ); return true; } // // 他のwidgetからドロップされた // void BBSListViewBase::slot_dropped_from_other( const CORE::DATA_INFO_LIST& list_info ) { #ifdef _DEBUG std::cout << "BBSListViewBase:slot_dropped_from_other\n"; #endif for( const CORE::DATA_INFO& info : list_info ) { const int type = info.type; switch( type ){ case TYPE_BOARD: case TYPE_BOARD_UPDATE: m_set_board.insert( DBTREE::url_boardbase( info.url ) ); break; case TYPE_VBOARD: m_set_board.insert( info.url ); break; case TYPE_THREAD: case TYPE_THREAD_UPDATE: case TYPE_THREAD_OLD: if( m_set_bookmark ) DBTREE::set_bookmarked_thread( info.url, true ); m_set_thread.insert( DBTREE::url_dat( info.url ) ); break; case TYPE_IMAGE: if( m_set_bookmark ) DBIMG::set_protect( info.url, true ); m_set_image.insert( info.url ); break; } } } // // popupmenu でタブで開くを選択 // void BBSListViewBase::slot_open_tab() { if( m_path_selected.empty() ) return; open_row( m_path_selected, true ); } // // ブラウザで開く // void BBSListViewBase::slot_open_browser() { if( m_path_selected.empty() ) return; std::string url = path2url( m_path_selected ); CORE::core_set_command( "open_url_browser", url ); } // // 画像キャッシュをブラウザで開く // void BBSListViewBase::slot_open_cache_browser() { if( m_path_selected.empty() ) return; std::string url = path2url( m_path_selected ); if( ! DBIMG::is_cached( url ) ) return; url = "file://" + DBIMG::get_cache_path( url ); CORE::core_set_command( "open_url_browser", url ); } // // 選択行をお気に入りに追加 // void BBSListViewBase::slot_append_favorite() { CORE::DATA_INFO_LIST list_info; const bool dir = true; m_treeview.get_info_in_selection( list_info, dir ); if( list_info.size() ){ CORE::SBUF_set_list( list_info ); CORE::core_set_command( "append_favorite", URL_FAVORITEVIEW ); } } // // メニューでディレクトリを作るを選択 // void BBSListViewBase::slot_newdir() { m_path_selected = m_treeview.create_newdir( m_path_selected ); } // // メニューでコメント挿入を選択 // void BBSListViewBase::slot_newcomment() { m_path_selected = m_treeview.create_newcomment( m_path_selected ); } // // メニューで外部板追加を選択 // void BBSListViewBase::slot_newetcboard() { add_newetcboard( false, "", "", "", "" ); } // // メニューで外部板編集を選択 // void BBSListViewBase::slot_moveetcboard() { add_newetcboard( true, "", "", "", "" ); } // 外部板追加 void BBSListViewBase::add_newetcboard( const bool move, // true なら編集モード const std::string& _url, const std::string& _name, const std::string& _id, const std::string& _passwd ) { if( m_path_selected.empty() ) return; std::string url_old; std::string name_old; std::string url = _url; std::string name = _name; std::string id = _id; std::string passwd = _passwd; std::string basicauth; #ifdef _DEBUG std::cout << "BBSListViewBase::add_newetcboard\n" << "move = " << move << std::endl; #endif if( move ){ url_old = path2url( m_path_selected ); name_old = path2name( m_path_selected ); #ifdef _DEBUG std::cout << "url_old = " << url_old << std::endl << "name_old = " << name_old << std::endl << "board_name = " << DBTREE::board_name( url_old ) << std::endl; #endif if( DBTREE::board_name( url_old ) != name_old ) return; if( url.empty() ) url = url_old; if( name.empty() ) name = name_old; basicauth = DBTREE::board_basicauth( url_old ); size_t i = basicauth.find( ':' ); if( id.empty() && i != std::string::npos ){ id = basicauth.substr( 0, i ); passwd = basicauth.substr( i+1 ); } #ifdef _DEBUG std::cout << "basicauth = " << basicauth << " i = " << i << " id = " << id << "passwd = " << passwd << std::endl; #endif } BBSLIST::AddEtcDialog diag( move, url, name, id, passwd ); if( diag.run() == Gtk::RESPONSE_OK ){ diag.hide(); std::string url_org = MISC::remove_space( diag.get_url() ); name = MISC::remove_space( diag.get_name() ); url = url_org; id = MISC::remove_space( diag.get_id() ); passwd = MISC::remove_space( diag.get_passwd() ); if( ! id.empty() && ! passwd.empty() ) basicauth = id + ":" + passwd; if( name.empty() || url.empty() ){ SKELETON::MsgDiag mdiag( get_parent_win(), "板名またはアドレスが空白です", false, Gtk::MESSAGE_ERROR ); mdiag.run(); mdiag.hide(); add_newetcboard( move, url_org, name, id, passwd ); return; } // http[s] が無ければ付ける if( url.find( "://" ) == std::string::npos ) url = "http://" + url; // .htmlを取り除く JDLIB::Regex regex; const size_t offset = 0; const bool icase = false; const bool newline = true; const bool usemigemo = false; const bool wchar = false; if( regex.exec( "(.*)/[^/]+\\.html?$" , url, offset, icase, newline, usemigemo, wchar ) ) url = regex.str( 1 ); // 末尾の / を取り除く while( url.rfind( '/' ) == url.length() -1 ) url = url.substr( 0, url.length() -1 ); // url の最後に/を付ける url += "/"; // boardid 取得 if( ! regex.exec( "(https?://.*)/([^/]*)/$" , url, offset, icase, newline, usemigemo, wchar ) ){ SKELETON::MsgDiag mdiag( get_parent_win(), "アドレスが不正な形式になっています", false, Gtk::MESSAGE_ERROR ); mdiag.run(); mdiag.hide(); add_newetcboard( move, url_org, name, id, passwd ); return; } #ifdef _DEBUG std::cout << "url_old = " << url_old << std::endl << "name_old = " << name_old << std::endl << "url = " << url << std::endl << "name = " << name << std::endl << "basicauth = " << basicauth << std::endl; #endif std::string boardid = regex.str( 2 ); if( boardid.empty() ){ SKELETON::MsgDiag mdiag( get_parent_win(), "板IDを取得出来ません", false, Gtk::MESSAGE_ERROR ); mdiag.run(); mdiag.hide(); add_newetcboard( move, url_org, name, id, passwd ); return; } #ifdef _DEBUG std::cout << "boardid = " << boardid << std::endl; #endif // 既に登録されているか確認 if( ! move && ! DBTREE::board_name( url ).empty() ){ SKELETON::MsgDiag mdiag( get_parent_win(), name + "\n" + url + "\n\nは既に登録されています", false, Gtk::MESSAGE_ERROR ); mdiag.run(); mdiag.hide(); add_newetcboard( move, url_org, name, id, passwd ); return; } // データベースに登録してツリーに表示 if( ! move && DBTREE::add_etc( url, name, basicauth, boardid ) ){ CORE::DATA_INFO_LIST list_info; CORE::DATA_INFO info; info.type = TYPE_BOARD; info.url = url; info.name = name; info.path = m_path_selected.to_string(); list_info.push_back( info ); const bool before = false; const bool scroll = false; const bool force = true; // 強制的に追加 const bool cancel_undo_commit = false; const int check_dup = 0; // 項目の重複チェックをしない m_treeview.append_info( list_info, m_path_selected, before, scroll, force, cancel_undo_commit, check_dup ); m_path_selected = m_treeview.get_current_path(); // etc.txt保存 DBTREE::save_etc(); } // 編集 else if( move && DBTREE::move_etc( url_old, url, name_old, name, basicauth, boardid ) ){ Gtk::TreeModel::Row row = m_treeview.get_row( m_path_selected ); if( row ){ row[ m_columns.m_url ] = url; row[ m_columns.m_name ] = name; static_cast( row ); // cppcheck: unreadVariable } } else{ SKELETON::MsgDiag mdiag( get_parent_win(), "外部板の登録に失敗しました。アドレスを確認してください", false, Gtk::MESSAGE_ERROR ); mdiag.run(); mdiag.hide(); add_newetcboard( move, url_org, name, id, passwd ); return; } } } // // 名前変更 // void BBSListViewBase::slot_rename() { #ifdef _DEBUG std::cout << "BBSListViewBase::slot_rename\n"; #endif m_treeview.rename_row( m_path_selected ); } // // URLをコピー // void BBSListViewBase::slot_copy_url() { if( m_path_selected.empty() ) return; std::string url = path2url( m_path_selected ); MISC::CopyClipboard( url ); } // 名前とURLをコピー // void BBSListViewBase::slot_copy_title_url() { if( m_path_selected.empty() ) return; const std::string url = path2url( m_path_selected ); const std::string name = path2name( m_path_selected ); MISC::CopyClipboard( name + '\n' + url ); } // // 指定したディレクトリ以下のディレクトリを全て開く // void BBSListViewBase::expand_all_dir( Gtk::TreeModel::Path path ) { if( m_treeview.is_dir( path ) ){ if( ! m_treeview.row_expanded( path ) ){ m_cancel_expand = true; // slot_row_exp()の呼び出しをキャンセル m_treeview.expand_row( path, false ); m_cancel_expand = false; } path.down(); while( m_treeview.get_row( path ) ){ expand_all_dir( path ); path.next(); } } } // // ディレクトリ内を全選択(メニューから呼び出す) // // m_path_selected にパスをセットしておくこと // void BBSListViewBase::slot_select_all_dir() { if( m_path_selected.empty() ) return; m_treeview.select_all_dir( m_path_selected ); } // // 全選択 // void BBSListViewBase::slot_select_all() { SKELETON::MsgDiag mdiag( get_parent_win(), "全ての行を選択しますか?\n\n(注意) ディレクトリは全て展開されます。", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO ); mdiag.set_default_response( Gtk::RESPONSE_NO ); if( mdiag.run() != Gtk::RESPONSE_YES ) return; Gtk::TreeModel::Children child = m_treestore->children(); for( const Gtk::TreeIter& iter : child ) expand_all_dir( m_treestore->get_path( iter ) ); m_treeview.scroll_to_row( m_treestore->get_path( *child.begin() ), 0 ); for( const Gtk::TreeIter& iter : child ) { m_treeview.get_selection()->select( iter ); m_treeview.select_all_dir( m_treestore->get_path( iter ) ); } } // // ディレクトリ以下を更新チェック // // root : true ならルートから検索する。falseの場合は m_path_selected にパスをセットしておくこと // open : チェック後に更新していたら開く // void BBSListViewBase::check_update_dir( const bool root, const bool open ) { if( ! SESSION::is_online() ){ SKELETON::MsgDiag mdiag( get_parent_win(), "オフラインです" ); mdiag.run(); return; } #ifdef _DEBUG std::cout << "BBSListViewBase::check_update_dir root = " << root << std::endl; #endif Gtk::TreePath path; if( ! root ){ if( m_path_selected.empty() ) return; path = m_path_selected; #ifdef _DEBUG std::cout << "path = " << path.to_string() << std::endl; #endif } SKELETON::EditTreeViewIterator it( m_treeview, m_columns, path ); for( ; ! it.end(); ++it ){ Gtk::TreeModel::Row row = *it; const int type = row2type( row ); const std::string url = row2url( row ); #ifdef _DEBUG std::cout << row2name( row ) << std::endl; #endif if( type == TYPE_THREAD || type == TYPE_THREAD_UPDATE ) CORE::get_checkupdate_manager()->push_back( DBTREE::url_dat( url ), open ); else if( CONFIG::get_check_update_board() && ( type == TYPE_BOARD || type == TYPE_BOARD_UPDATE ) ) CORE::get_checkupdate_manager()->push_back( DBTREE::url_boardbase( url ), open ); } CORE::get_checkupdate_manager()->run(); } void BBSListViewBase::slot_check_update_dir() { check_update_dir( false, false ); } void BBSListViewBase::slot_check_update_open_dir() { check_update_dir( false, true ); } // // ディレクトリをスレ一覧に表示 // void BBSListViewBase::slot_opendir_as_board() { if( m_path_selected.empty() ) return; const size_t dirid = m_treeview.path_to_dirid( m_path_selected ); if( ! dirid ) return; const std::string tab = "newtab"; const std::string mode = ""; CORE::core_set_command( "open_sidebar_board", get_url(), tab, mode, std::to_string( dirid ) ); } // // 仮想板作成 // void BBSListViewBase::slot_create_vboard() { if( m_path_selected.empty() ) return; const size_t dirid = m_treeview.path_to_dirid( m_path_selected ); if( ! dirid ) return; CORE::DATA_INFO_LIST list_info; CORE::DATA_INFO info; info.type = TYPE_VBOARD; info.parent = BBSLIST::get_admin()->get_win(); info.url = get_url() + SIDEBAR_SIGN + std::to_string( dirid ); info.name = path2name( m_path_selected ); info.path = m_path_selected.to_string(); list_info.push_back( info ); CORE::SBUF_set_list( list_info ); CORE::core_set_command( "append_favorite", URL_FAVORITEVIEW ); } // // 更新チェックキャンセル // void BBSListViewBase::stop() { #ifdef _DEBUG std::cout << "BBSListViewBase::stop " << get_url() << std::endl; #endif CORE::get_checkupdate_manager()->stop(); } // // キャッシュ内のログ検索 // void BBSListViewBase::slot_search_cache_board() { if( m_path_selected.empty() ) return; std::string url = path2url( m_path_selected ); CORE::core_set_command( "open_article_searchlog", url, "", "noexec" ); } // // datのインポート // void BBSListViewBase::slot_import_dat() { if( m_path_selected.empty() ) return; std::string url = path2url( m_path_selected ); CORE::core_set_command( "import_dat", url, "show_diag" ); } // // 板プロパティ表示 // void BBSListViewBase::slot_preferences_board() { if( m_path_selected.empty() ) return; std::string url = path2url( m_path_selected ); auto pref = CORE::PrefDiagFactory( get_parent_win(), CORE::PREFDIAG_BOARD, DBTREE::url_boardbase( url ) ); pref->run(); } // // スレプロパティ表示 // void BBSListViewBase::slot_preferences_article() { if( m_path_selected.empty() ) return; std::string url = path2url( m_path_selected ); auto pref = CORE::PrefDiagFactory( get_parent_win(), CORE::PREFDIAG_ARTICLE, DBTREE::url_dat( url ) ); pref->run(); } // // 画像プロパティ表示 // void BBSListViewBase::slot_preferences_image() { if( m_path_selected.empty() ) return; std::string url = path2url( m_path_selected ); auto pref = CORE::PrefDiagFactory( get_parent_win(), CORE::PREFDIAG_IMAGE, url ); pref->run(); } // // フォルダを開いた時に呼ばれる // void BBSListViewBase::slot_row_exp( const Gtk::TreeModel::iterator&, const Gtk::TreeModel::Path& path ) { if( m_cancel_expand ) return; if( m_expanding ) return; // 他のフォルダを全て閉じる if( m_open_only_onedir && path.size() == 1 // 子フォルダの時は閉じない ){ m_expanding = true; m_treeview.collapse_all(); m_treeview.expand_row( path, false ); m_expanding = false; } m_treeview.set_cursor( path ); if( CONFIG::get_scroll_tree() ) m_treeview.scroll_to_row( path, 0.1 ); show_status(); } // // フォルダを閉じた時に呼ばれる // void BBSListViewBase::slot_row_col( const Gtk::TreeModel::iterator&, const Gtk::TreeModel::Path& path ) { if( m_expanding ) return; m_treeview.set_cursor( path ); show_status(); } // // 選択した行を開く // bool BBSListViewBase::open_row( Gtk::TreePath& path, const bool tab ) { const Gtk::TreeModel::Row row = m_treeview.get_row( path ); if( ! row ) return false; std::string str_tab = "false"; if( tab ) str_tab = "opentab"; const std::string str_mode = ""; const std::string url = row2url( row ); const int type = row2type( row ); if( type != TYPE_DIR && url.empty() ) return false; switch( type ){ case TYPE_BOARD: case TYPE_BOARD_UPDATE: CORE::core_set_command( "open_board", url, str_tab, str_mode ); break; case TYPE_THREAD_OLD: toggle_articleicon( url ); // break;しない // fallthrough case TYPE_THREAD: case TYPE_THREAD_UPDATE: CORE::core_set_command( "open_article", url, str_tab, str_mode ); break; case TYPE_IMAGE: if( DBIMG::get_abone( url )){ SKELETON::MsgDiag mdiag( get_parent_win(), "あぼ〜んされています" ); mdiag.run(); } else{ CORE::core_set_command( "open_image", url ); CORE::core_set_command( "switch_image" ); } break; case TYPE_LINK: CORE::core_set_command( "open_url_browser", url ); break; case TYPE_DIR: if( tab ) slot_check_update_open_dir(); else if( ! m_treeview.row_expanded( path ) ) m_treeview.expand_row( path, false ); else m_treeview.collapse_row( path ); break; case TYPE_VBOARD: CORE::core_set_command( "open_sidebar_board", url, str_tab, str_mode, "", "set_history" ); break; } #ifdef _DEBUG std::cout << "BBSListViewBase::open_row : path = " << path.to_string() << " tab = " << tab << std::endl; #endif // treeviewが編集されていたらxml保存 if( type != TYPE_DIR && m_treeview.is_updated() ){ save_xml(); m_treeview.set_updated( false ); } return true; } // // 右のビュー(board)に切り替え // void BBSListViewBase::switch_rightview() { CORE::core_set_command( "switch_rightview" ); } // // 選択した行をまとめて開く // void BBSListViewBase::open_selected_rows() { std::string list_url_board; std::string list_url_article; for( Gtk::TreeIter& iter : m_treeview.get_selected_iterators() ) { Gtk::TreeModel::Row row = *iter; const int type = row2type( row ); const std::string url = row2url( row ); switch( type ){ case TYPE_BOARD: case TYPE_BOARD_UPDATE: if( ! list_url_board.empty() ) list_url_board.push_back( ' ' ); list_url_board.append( url ); break; case TYPE_THREAD: case TYPE_THREAD_UPDATE: case TYPE_THREAD_OLD: if( ! list_url_article.empty() ) list_url_article.push_back( ' ' ); list_url_article.append( url ); break; case TYPE_IMAGE: if( ! DBIMG::get_abone( url ) ) CORE::core_set_command( "open_image", url ); break; } } if( !list_url_board.empty() ) CORE::core_set_command( "open_board_list", std::string(), list_url_board ); if( !list_url_article.empty() ) CORE::core_set_command( "open_article_list", std::string(), list_url_article ); } // // 選択したスレを更新チェック // // このあとで CORE::get_checkupdate_manager()->run() を実行すること // void BBSListViewBase::checkupdate_selected_rows( const bool open ) { for( Gtk::TreeIter& iter : m_treeview.get_selected_iterators() ) { Gtk::TreeModel::Row row = *iter; const int type = row2type( row ); const std::string url = row2url( row ); if( type == TYPE_THREAD || type == TYPE_THREAD_UPDATE ) { CORE::get_checkupdate_manager()->push_back( url, open ); } else if( CONFIG::get_check_update_board() && ( type == TYPE_BOARD || type == TYPE_BOARD_UPDATE ) ) { CORE::get_checkupdate_manager()->push_back( url, open ); } } } void BBSListViewBase::slot_checkupdate_selected_rows() { checkupdate_selected_rows( false ); CORE::get_checkupdate_manager()->run(); } void BBSListViewBase::slot_checkupdate_open_selected_rows() { checkupdate_selected_rows( true ); CORE::get_checkupdate_manager()->run(); } void BBSListViewBase::slot_sort( const int mode ) { m_treeview.sort( m_path_selected, mode ); } // // path -> url 変換 // std::string BBSListViewBase::path2rawurl( const Gtk::TreePath& path ) { const Gtk::TreeModel::Row row = m_treeview.get_row( path ); return row2url( row ); } // 移転をチェックするバージョン std::string BBSListViewBase::path2url( const Gtk::TreePath& path ) { const Gtk::TreeModel::Row row = m_treeview.get_row( path ); std::string url = row2url( row ); // url を最新のものに変換しておく switch( row2type( row ) ){ case TYPE_THREAD: case TYPE_THREAD_UPDATE: case TYPE_THREAD_OLD: url = DBTREE::url_readcgi( url, 0, 0 ); break; } return url; } // // row -> url 変換 // // 板の場合は boardbase // スレの場合は dat 型のアドレスを返す // std::string BBSListViewBase::row2url( const Gtk::TreeModel::Row& row ) const { if( ! row ) return {}; const Glib::ustring& ustr_url = row[ m_columns.m_url ]; return ustr_url.raw(); } // // path -> name 変換 // std::string BBSListViewBase::path2name( const Gtk::TreePath& path ) { const Gtk::TreeModel::Row row = m_treeview.get_row( path ); return row2name( row ); } // // row -> name 変換 // std::string BBSListViewBase::row2name( const Gtk::TreeModel::Row& row ) const { if( ! row ) return {}; const Glib::ustring& ustr_name = row[ m_columns.m_name ]; return ustr_name.raw(); } // // path -> type 変換 // int BBSListViewBase::path2type( const Gtk::TreePath& path ) { const Gtk::TreeModel::Row row = m_treeview.get_row( path ); return row2type( row ); } // // row -> type 変換 // int BBSListViewBase::row2type( const Gtk::TreeModel::Row& row ) const { if( ! row ) return TYPE_UNKNOWN; return row[ m_columns.m_type ]; } // // row -> dirid 変換 // size_t BBSListViewBase::row2dirid( const Gtk::TreeModel::Row& row ) const { if( !row ) return 0; return row[ m_columns.m_dirid ]; } // // 外部板のディレクトリか // bool BBSListViewBase::is_etcdir( Gtk::TreePath path ) { std::string name = path2name( path ); #ifdef _DEBUG std::cout << "BBSListViewBase:is_etcdir path = " << path.to_string() << " name = " << name << std::endl; #endif if( name == SUBDIR_ETCLIST ) return true; return false; } // // 外部板か // bool BBSListViewBase::is_etcboard( const Gtk::TreeModel::iterator& it ) { Gtk::TreePath path = get_treestore()->get_path( *it ); return is_etcboard( path ); } bool BBSListViewBase::is_etcboard( Gtk::TreePath path ) { path.up(); return is_etcdir( path ); } // // ステータス表示 // void BBSListViewBase::show_status() { set_status( path2url( m_treeview.get_current_path() ) ); BBSLIST::get_admin()->set_command( "set_status", get_url(), get_status() ); } // // tree -> XML 変換 // // void BBSListViewBase::tree2xml( const std::string& root_name ) { if( ! m_ready_tree ) return; #ifdef _DEBUG std::cout << "BBSListViewBase::tree2xml\n"; #endif m_treeview.tree2xml( m_document, root_name ); // 座標 int y = 0; const auto adjust = m_treeview.get_vadjustment(); if( adjust ) { if( m_jump_y != -1 && adjust->get_upper() > m_jump_y ) y = m_jump_y; else y = ( int ) adjust->get_value(); } else if( m_jump_y != -1 ) y = m_jump_y; // 選択中のパス std::string path; Gtk::TreeModel::Path focused_path = m_treeview.get_current_path(); if( ! focused_path.empty() ) path = focused_path.to_string(); // ルート要素に属性( path, y )の値を設定 XML::Dom* root = m_document.get_root_element( root_name ); if( root ){ if( ! m_date_modified.empty() ) root->setAttribute( "date_modified", m_date_modified ); root->setAttribute( "y", y ); root->setAttribute( "path", path ); } } // // XML -> tree 変換 // void BBSListViewBase::xml2tree( const std::string& root_name, const std::string& xml ) { #ifdef _DEBUG std::cout << "BBSListViewBase::xml2tree\n"; #endif m_ready_tree = false; m_jump_y = 0; // 新規に文字列からDOMノードツリーを作成する場合 if( ! xml.empty() ) m_document.init( xml ); #ifdef _DEBUG std::cout << " ルートノード名=" << root_name; std::cout << " 子ノード数=" << m_document.size() << std::endl; #endif m_treeview.xml2tree( m_document, m_treestore, root_name ); // ルート要素を取り出す const XML::Dom* root = m_document.get_root_element( root_name ); if( root ){ // ルート要素から属性( date_modified, path, y )の値を取得 m_date_modified = root->getAttribute( "date_modified" ); std::string focused_path = root->getAttribute( "path" ); int y = atoi( root->getAttribute( "y" ).c_str() ); // 前回閉じた位置まで移動 if( focused_path.empty() ) { focused_path = "0"; y = 0; } Gtk::TreePath path = Gtk::TreePath( focused_path ); if( m_treeview.get_row( path ) ) m_treeview.set_cursor( path ); else { m_treeview.get_selection()->unselect_all(); y = 0; } // この段階ではまだスクロールバーが表示されてない時があるのでclock_in()で移動する m_jump_y = y; } m_ready_tree = true; } // // 起動時や移転があったときなどに行に含まれるURlを変更する // void BBSListViewBase::update_urls() { if( ! m_ready_tree ) return; if( m_treestore->children().empty() ) return; #ifdef _DEBUG std::cout << "BBSListViewBase::update_urls " << get_url() << std::endl; #endif bool updated = false; // 移転情報の保存( Root::save_movetable() )を無効にする。 // 無効にしないと DBTREE::url_boardbase() -> Root::get_board() 経由で // 繰り返し Root::save_movetable() が実行されてJDが固まったようになる DBTREE::set_enable_save_movetable( false ); m_set_board.clear(); m_set_thread.clear(); m_set_image.clear(); SKELETON::EditTreeViewIterator it( m_treeview, m_columns, Gtk::TreePath() ); for( ; ! it.end(); ++it ){ Gtk::TreeModel::Row row = *it; const Glib::ustring& ustr_url = row[ m_columns.m_url ]; const int type = row[ m_columns.m_type ]; #ifdef _DEBUG std::cout << row2name( row ) << std::endl; #endif std::string url_new; switch( type ){ case TYPE_BOARD: // 板 case TYPE_BOARD_UPDATE: url_new = DBTREE::url_boardbase( ustr_url.raw() ); if( ustr_url.raw() != url_new ){ updated = true; row[ m_columns.m_url ] = url_new; #ifdef _DEBUG std::cout << ustr_url << " -> " << url_new << std::endl; #endif } m_set_board.insert( url_new ); break; case TYPE_VBOARD: m_set_board.insert( ustr_url.raw() ); break; case TYPE_THREAD: // スレ case TYPE_THREAD_UPDATE: case TYPE_THREAD_OLD: url_new = DBTREE::url_dat( ustr_url.raw() ); if( ustr_url.raw() != url_new ){ updated = true; row[ m_columns.m_url ] = url_new; #ifdef _DEBUG std::cout << ustr_url << " -> " << url_new << std::endl; #endif } m_set_thread.insert( url_new ); break; case TYPE_IMAGE: m_set_image.insert( ustr_url.raw() ); break; } } DBTREE::set_enable_save_movetable( true ); if( updated ){ save_xml(); DBTREE::save_movetable(); } } // // アイコン表示(スレ)の切り替え // void BBSListViewBase::toggle_articleicon( const std::string& url ) { if( ! m_ready_tree ) return; if( m_treestore->children().empty() ) return; // ツリーの中に無い場合は処理しない if( m_set_thread.find( url ) == m_set_thread.end() ) return; bool erase = true; int type = TYPE_THREAD; const int status = DBTREE::article_status( url ); if( status & STATUS_OLD ) type = TYPE_THREAD_OLD; else if( status & STATUS_UPDATE ) type = TYPE_THREAD_UPDATE; #ifdef _DEBUG std::cout << "BBSListViewBase::toggle_articleicon url = " << url << " type = " << type << std::endl; #endif SKELETON::EditTreeViewIterator it( m_treeview, m_columns, Gtk::TreePath() ); for( ; ! it.end(); ++it ){ Gtk::TreeModel::Row row = *it; const Glib::ustring& url_row = row[ m_columns.m_url ]; const int type_row = row[ m_columns.m_type ]; if( type_row == TYPE_THREAD || type_row == TYPE_THREAD_UPDATE || type_row == TYPE_THREAD_OLD ){ if( url == url_row.raw() ){ #ifdef _DEBUG std::cout << "hit " << url << " == " << url_row << std::endl; std::cout << row2name( row ) << std::endl; #endif row[ m_columns.m_type ] = type; row[ m_columns.m_image ] = XML::get_icon( type ); erase = false; } } } if( erase ) m_set_thread.erase( url ); } // // アイコン表示(板)の切り替え // void BBSListViewBase::toggle_boardicon( const std::string& url ) { if( ! m_ready_tree ) return; if( m_treestore->children().empty() ) return; const std::string url_boardbase = DBTREE::url_boardbase( url ); // ツリーの中に無い場合は処理しない if( m_set_board.find( url_boardbase ) == m_set_board.end() ) return; bool erase = true; int type = TYPE_BOARD; const int status = DBTREE::board_status( url ); if( status & STATUS_UPDATE ) type = TYPE_BOARD_UPDATE; #ifdef _DEBUG std::cout << "BBSListViewBase::toggle_boardicon url = " << url_boardbase << " type = " << type << std::endl; #endif SKELETON::EditTreeViewIterator it( m_treeview, m_columns, Gtk::TreePath() ); for( ; ! it.end(); ++it ){ Gtk::TreeModel::Row row = *it; const Glib::ustring& url_row = row[ m_columns.m_url ]; const int type_row = row[ m_columns.m_type ]; if( type_row == TYPE_BOARD || type_row == TYPE_BOARD_UPDATE ){ if( url_boardbase == url_row.raw() ){ #ifdef _DEBUG std::cout << "hit " << url_boardbase << " == " << url_row << std::endl; std::cout << row2name( row ) << std::endl; #endif row[ m_columns.m_type ] = type; row[ m_columns.m_image ] = XML::get_icon( type ); erase = false; } } } if( erase ) m_set_board.erase( url_boardbase ); } // // URLを選択 // void BBSListViewBase::select_item( const std::string& url ) { if( ! m_ready_tree ) return; if( m_treestore->children().empty() ) return; std::string url_item; if( m_set_thread.find( url ) != m_set_thread.end() || m_set_image.find( url ) != m_set_image.end()){ // スレまたは画像の場合 url_item = url; } else { // 板以外の履歴は処理しない if( get_url() == URL_HISTTHREADVIEW || get_url() == URL_HISTCLOSEVIEW || get_url() == URL_HISTCLOSEIMGVIEW ) return; // 板の場合 url_item = DBTREE::url_boardbase( url ); // 未登録の画像などで、板が見つからない場合は処理しない if( url_item.empty() ) return; } // 現在選択しているものと一致する場合は何もしない Gtk::TreePath current_path = m_treeview.get_current_path(); if( ! current_path.empty() ){ // 板一覧やお気に入りに、該当するURLが2つある場合がある if( url_item == path2rawurl( current_path ) || url_item == path2url( current_path ) ) return; } Gtk::TreePath closed_path; int closed_found = false; SKELETON::EditTreeViewIterator it( m_treeview, m_columns, Gtk::TreePath() ); for( ; ! it.end(); ++it ){ Gtk::TreeModel::Row row = *it; Gtk::TreePath path = GET_PATH( row ); if( url_item == row[ m_columns.m_url ] || url_item == path2url( path ) ){ // 最初に見つかったものにフォーカスする if( m_treeview.is_expand( path ) ){ // 開いているエントリ m_treeview.get_selection()->unselect_all(); m_treeview.set_cursor( path ); return; } // 最初の閉じたエントリを覚えておく if( ! closed_found ){ closed_found = true; closed_path = path; } } } // 開いたエントリが見つからない if( closed_found && CONFIG::get_select_item_sync() == 2 ){ // 閉じたエントリの上位フォルダを開いてフォーカスする m_treeview.get_selection()->unselect_all(); m_treeview.expand_parents( closed_path ); m_treeview.set_cursor( closed_path ); } } // // 新スレ移行時などにスレの url と 名前を変更 // void BBSListViewBase::replace_thread( const std::string& url, const std::string& url_new ) { #ifdef _DEBUG std::cout << "BBSListViewBase::replace_thread url = " << url << " url_new = " << url_new << std::endl; #endif if( ! m_ready_tree ) return; if( m_treestore->children().empty() ) return; const std::string urldat_new = DBTREE::url_dat( url_new ); if( urldat_new.empty() ) return; const std::string name_new = DBTREE::article_subject( urldat_new ); if( name_new.empty() ) return; bool show_diag = CONFIG::show_diag_replace_favorite(); int mode = CONFIG::get_replace_favorite_next(); if( ! show_diag && mode == REPLACE_NEXT_NO ) return; const std::string urldat = DBTREE::url_dat( url ); const std::string urlcgi = DBTREE::url_readcgi( url, 0, 0 ); const std::string name_old = MISC::remove_space( DBTREE::article_subject( urldat ) ); int type = TYPE_THREAD; const int status = DBTREE::article_status( urldat_new ); if( status & STATUS_OLD ) type = TYPE_THREAD_OLD; if( status & STATUS_UPDATE ) type = TYPE_THREAD_UPDATE; #ifdef _DEBUG std::cout << "name_new = " << name_new << std::endl << "name_old = " << name_old << std::endl << "type = " << type << std::endl; #endif SKELETON::EditTreeViewIterator it( m_treeview, m_columns, Gtk::TreePath() ); for( ; ! it.end(); ++it ){ Gtk::TreeModel::Row row = *it; const Glib::ustring& ustr_url = row[ m_columns.m_url ]; switch( row[ m_columns.m_type ] ){ case TYPE_THREAD: case TYPE_THREAD_UPDATE: case TYPE_THREAD_OLD: if( urldat == ustr_url.raw() ){ if( show_diag ){ show_diag = false; SKELETON::MsgCheckDiag mdiag( get_parent_win(), "お気に入りに前スレが登録されています。\n\n名前とアドレスを新スレの物に置き換えますか?" , "今後表示しない(_D)", Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_NONE ); mdiag.add_default_button( g_dgettext( GTK_DOMAIN, "_No" ), Gtk::RESPONSE_NO ); mdiag.add_button( g_dgettext( GTK_DOMAIN, "_Yes" ), Gtk::RESPONSE_YES ); mdiag.add_button( "新スレをお気に入りに追加(_F)", Gtk::RESPONSE_CANCEL ); mdiag.set_title( "お気に入り更新" ); const int ret = mdiag.run(); switch( ret ){ case Gtk::RESPONSE_YES: mode = REPLACE_NEXT_YES; break; case Gtk::RESPONSE_NO: mode = REPLACE_NEXT_NO; break; case Gtk::RESPONSE_CANCEL: mode = REPLACE_NEXT_ADD; break; } if( mdiag.get_chkbutton().get_active() ){ CONFIG::set_show_diag_replace_favorite( false ); CONFIG::set_replace_favorite_next( mode ); } if( mode == REPLACE_NEXT_NO ) return; } // 行を開いた時にxmlを保存 m_treeview.set_updated( true ); // 置き換え if( mode == REPLACE_NEXT_YES ){ row[ m_columns.m_url ] = urldat_new; // 名前が古いものであったら更新 // 手動で変更されていたらそのまま const Glib::ustring& ustr_name = row[ m_columns.m_name ]; #ifdef _DEBUG std::cout << "name_row = " << ustr_name << std::endl; #endif if( MISC::remove_space( ustr_name ) == name_old ){ #ifdef _DEBUG std::cout << "replace name\n"; #endif row[ m_columns.m_name ] = name_new; } row[ m_columns.m_type ] = type; row[ m_columns.m_image ] = XML::get_icon( type ); } // 追加 else if( mode == REPLACE_NEXT_ADD ){ CORE::DATA_INFO_LIST list_info; CORE::DATA_INFO info; info.type = type; info.parent = BBSLIST::get_admin()->get_win(); info.url = urldat_new; info.name = name_new; info.path = Gtk::TreePath( "0" ).to_string(); list_info.push_back( info ); CORE::SBUF_set_list( list_info ); CORE::core_set_command( "append_favorite", URL_FAVORITEVIEW ); return; } } } } } // // 検索 // // m_search_invert = true なら前方検索 // void BBSListViewBase::exec_search() { CORE::core_set_command( "set_info", "", "" ); std::string query = get_search_query(); if( query.empty() ){ focus_view(); return; } Gtk::TreePath path = m_treeview.get_current_path(); if( !m_treeview.get_row( path ) ){ goto_top(); path = m_treeview.get_current_path(); } // queryが新しいのに更新されたらひとつ前か後から検索をかける if( query != m_pre_query ){ if( !m_search_invert ) path = m_treeview.prev_path( path, false ); else path = m_treeview.next_path( path, false ); m_pre_query = query; CORE::get_completion_manager()->set_query( CORE::COMP_SEARCH_BBSLIST, query ); } Gtk::TreePath path_start = path; #ifdef _DEBUG std::cout << "BBSListViewBase::exec_search() path = " << path.to_string() << " query = " << query << std::endl; #endif constexpr bool icase_name = true; // 大文字小文字区別しない constexpr bool newline_name = true; // . に改行をマッチさせない constexpr bool usemigemo_name = true; // migemo使用 constexpr bool wchar_name = true; // 全角半角の区別をしない const JDLIB::RegexPattern regex_name( query, icase_name, newline_name, usemigemo_name, wchar_name ); constexpr bool icase_url = true; // 大文字小文字区別しない constexpr bool newline_url = true; constexpr bool usemigemo_url = false; constexpr bool wchar_url = false; const JDLIB::RegexPattern regex_url( query, icase_url, newline_url, usemigemo_url, wchar_url ); JDLIB::Regex regex; bool hit = false; for(;;){ // 後方 if( !m_search_invert ){ path = m_treeview.next_path( path, false ); // 一番最後を過ぎたら先頭に戻る if( ! m_treeview.get_row( path ) ) path = GET_PATH( *( m_treestore->children().begin() ) ); } // 前方 else{ // 先頭にいるときは最後に戻る if( path == GET_PATH( *( m_treestore->children().begin() ) ) ){ path = GET_PATH( *( std::prev( m_treestore->children().end() ) ) ); Gtk::TreePath path_tmp = path; while( m_treeview.get_row( path_tmp ) ){ path = path_tmp; path_tmp = m_treeview.next_path( path_tmp, false ); } } else path = m_treeview.prev_path( path, false ); } const std::string name = path2name( path ); const std::string url = path2url( path ); const size_t offset = 0; if( regex.match( regex_name, name, offset ) || regex.match( regex_url, url, offset ) ) hit = true; // 一周したら終わり if( path == path_start ) break; // カーソル移動 if( hit ){ m_treeview.expand_parents( path ); m_treeview.scroll_to_row( path, 0.1 ); m_treeview.set_cursor( path ); show_status(); break; } } if( hit ) focus_view(); else CORE::core_set_command( "set_info", "", "検索結果: ヒット無し" ); } // 前検索 void BBSListViewBase::up_search() { set_search_invert( true ); exec_search(); } // 後検索 void BBSListViewBase::down_search() { set_search_invert( false ); exec_search(); } // // 検索entryの操作 // void BBSListViewBase::operate_search( const std::string& controlid ) { int id = atoi( controlid.c_str() ); if( id == CONTROL::Cancel ) focus_view(); } // // 挿入先ダイアログを表示してアイテム追加 // // あらかじめ共有バッファにデータを入れておくこと // void BBSListViewBase::append_item() { if( m_editlistwin ){ m_editlistwin->append_item(); return; } if( CORE::SBUF_size() == 0 ) return; // 挿入先ダイアログ内で編集を行うとバッファがクリアされてしまうので // バックアップを取っておく CORE::DATA_INFO_LIST list_info_bkup = CORE::SBUF_list_info(); std::string path_str; const int show_diag = CONFIG::get_show_favorite_select_diag(); if( show_diag == 1 ) path_str = "-1"; else if( show_diag == 2 ) path_str = ""; else{ // 挿入先ダイアログ表示 Gtk::Window* parent = ( *list_info_bkup.begin() ).parent; SelectListDialog diag( parent, get_url(), get_treestore() ); if( diag.run() != Gtk::RESPONSE_OK ) return; path_str = diag.get_path(); if( list_info_bkup.size() == 1 && ! diag.get_name().empty() ) ( *list_info_bkup.begin() ).name = diag.get_name(); } bool before = false; Gtk::TreePath path; // 先頭 if( path_str == "-1" ){ path = Gtk::TreePath( "0" ); before = true; } // 最後 else if( path_str.empty() ) path = Gtk::TreePath(); else path = Gtk::TreePath( path_str ); const bool scroll = true; const bool force = false; const bool cancel_undo_commit = false; const int check_dup = CONFIG::get_check_favorite_dup(); // 重複チェックするか const CORE::DATA_INFO_LIST list_info = m_treeview.append_info( list_info_bkup, path, before, scroll, force, cancel_undo_commit, check_dup ); CORE::SBUF_clear_info(); slot_dropped_from_other( list_info ); // 行を開いた時にxmlを保存 m_treeview.set_updated( true ); } // // 履歴のセット // // 先頭にアイテムを追加する。ツリーにアイテムが含まれている場合は移動する // あらかじめ共有バッファにデータを入れておくこと // void BBSListViewBase::append_history() { if( CORE::SBUF_size() == 0 ) return; CORE::DATA_INFO_LIST list_info = CORE::SBUF_list_info(); CORE::DATA_INFO_LIST::iterator it_info = list_info.begin(); ( *it_info ).path = Gtk::TreePath( "0" ).to_string(); #ifdef _DEBUG std::cout << "BBSListViewBase::append_history url = " << ( *it_info ).url << std::endl; #endif // ツリーにアイテムが含まれている場合は削除 // 履歴はサブディレクトリが無いと仮定してサブディレクトリの探査はしない if( ( ( *it_info ).type == TYPE_THREAD && m_set_thread.find( ( *it_info ).url ) != m_set_thread.end() ) || ( ( ( *it_info ).type == TYPE_BOARD || ( *it_info ).type == TYPE_VBOARD ) && m_set_board.find( ( *it_info ).url ) != m_set_board.end() ) || ( ( *it_info ).type == TYPE_IMAGE && m_set_image.find( ( *it_info ).url ) != m_set_image.end() ) ){ std::vector< Gtk::TreePath > del_path; for( Gtk::TreeModel::Row row : m_treestore->children() ) { if( row2url( row ) == ( *it_info ).url ) del_path.push_back( GET_PATH( row ) ); } for( int i = del_path.size() -1; i >= 0 ; --i ){ #ifdef _DEBUG std::cout << "erase " << del_path[ i ].to_string() << std::endl; #endif m_treestore->erase( m_treeview.get_row( del_path[ i ] ) ); } } // 先頭にアイテム追加 Gtk::TreePath path; if( ! m_treestore->children().empty() ) path = Gtk::TreePath( "0" ); const bool before = true; const bool subdir = false; m_treeview.append_one_row( ( *it_info ).url, ( *it_info ).name, ( *it_info ).type, ( *it_info ).dirid, ( *it_info ).data, path, before, subdir ); // サイズが越えていたら最後を削除 const Gtk::TreeNodeChildren children = m_treestore->children(); const int history_size = CONFIG::get_historyview_size(); if( static_cast< int >( children.size() ) > history_size ) { const auto end = children.end(); auto it = std::next( children.begin(), history_size ); while( it != end ) { it = m_treestore->erase( *it ); #ifdef _DEBUG std::cout << "erase bottom\n"; #endif } } goto_top(); CORE::SBUF_clear_info(); slot_dropped_from_other( list_info ); } // // 履歴を DATA_INFO_LIST 型で取得 // void BBSListViewBase::get_history( CORE::DATA_INFO_LIST& info_list ) const { info_list.clear(); CORE::DATA_INFO info; // 履歴はサブディレクトリが無いと仮定してサブディレクトリの探査はしない for( Gtk::TreeModel::Row row : m_treestore->children() ) { info.type = row2type( row ); info.url = row2url( row ); info.name = row2name( row ); info.dirid = row2dirid( row ); info_list.push_back( info ); } } // // 指定したidのディレクトリに含まれるスレのアドレスを取得 // void BBSListViewBase::get_threads( const size_t dirid, std::vector< std::string >& list_url ) { list_url.clear(); #ifdef _DEBUG std::cout << "BBSListViewBase::get_threads " << dirid << std::endl; #endif Gtk::TreePath path = m_treeview.dirid_to_path( dirid ); if( dirid && path.empty() ) return; SKELETON::EditTreeViewIterator it( m_treeview, m_columns, path ); for( ; ! it.end(); ++it ){ Gtk::TreeModel::Row row = *it; const int type = row2type( row ); if( type == TYPE_THREAD || type == TYPE_THREAD_UPDATE || type == TYPE_THREAD_OLD ){ #ifdef _DEBUG std::cout << row2name( row ) << std::endl; #endif list_url.push_back( row2url( row ) ); } } } // 指定したidのディレクトリの名前を取得 std::string BBSListViewBase::get_dirname( const int dirid ) { if( ! dirid ) return get_label(); return path2name( m_treeview.dirid_to_path( dirid ) ); } // // url で指定した項目を削除 // void BBSListViewBase::remove_item( const std::string& url ) { std::string url_target = DBTREE::url_dat( url ); if( url_target.empty() ) url_target = DBTREE::url_boardbase( url ); if( url_target.empty() && DBIMG::get_type_ext( url ) != DBIMG::T_UNKNOWN ) url_target = url; if( url_target.empty() ) return; #ifdef _DEBUG std::cout << "BBSListViewBase::remove_item url = " << url_target << std::endl; #endif std::list< Gtk::TreePath > list_path; SKELETON::EditTreeViewIterator it( m_treeview, m_columns, Gtk::TreePath() ); for( ; ! it.end(); ++it ){ Gtk::TreeModel::Row row = *it; const Glib::ustring& url_row = row[ m_columns.m_url ]; const int type = row[ m_columns.m_type ]; switch( type ){ case TYPE_BOARD: // 板 case TYPE_BOARD_UPDATE: case TYPE_THREAD: // スレ case TYPE_THREAD_UPDATE: case TYPE_THREAD_OLD: case TYPE_IMAGE: // 画像 if( url_row.raw() == url_target ){ #ifdef _DEBUG std::cout << "hit " << url_row << " == " << url_target << std::endl; std::cout << row2name( row ) << std::endl; #endif list_path.push_back( it.get_path() ); } break; } } const bool force = true; m_treeview.delete_path( list_path, force ); } // // 先頭項目を削除 // void BBSListViewBase::remove_headitem() { if( ! m_ready_tree ) return; if( m_treestore->children().empty() ) return; #ifdef _DEBUG std::cout << "BBSListViewBase::remove_headitem\n"; #endif std::list< Gtk::TreePath > list_path; SKELETON::EditTreeViewIterator it( m_treeview, m_columns, Gtk::TreePath() ); list_path.push_back( it.get_path() ); const bool force = true; m_treeview.delete_path( list_path, force ); } // // 全項目を削除 // void BBSListViewBase::remove_allitems() { #ifdef _DEBUG std::cout << "BBSListViewBase::remove_allitems\n"; #endif m_treestore->clear(); m_set_board.clear(); m_set_thread.clear(); m_set_image.clear(); } // // ツリーの編集ウィンドウを開く // void BBSListViewBase::edit_tree() { if( m_editlistwin ) m_editlistwin->present(); else{ m_editlistwin = std::make_unique( get_url(), get_treestore() ); m_editlistwin->signal_hide().connect( sigc::mem_fun(*this, &BBSListViewBase::slot_hide_editlistwin ) ); m_editlistwin->show(); } } // // ツリーの編集ウィンドウが閉じた // void BBSListViewBase::slot_hide_editlistwin() { #ifdef _DEBUG std::cout << "BBSListViewBase::slot_hide_editlistwin\n"; #endif m_editlistwin.reset(); } // // ツールチップのセット // bool BBSListViewBase::slot_query_tooltip( int x, int y, bool keyboard_tooltip, const Glib::RefPtr& tooltip ) { if( keyboard_tooltip ) return false; int bin_x, bin_y; m_treeview.convert_widget_to_bin_window_coords( x, y, bin_x, bin_y ); #ifdef _DEBUG std::cout << "BBSListViewBase::slot_query_tooltip" << " x(" << x << ") y(" << y << ") bin_x(" << bin_x << ") bin_y(" << bin_y << ")" << std::endl; #endif Gtk::TreeModel::Path path; Gtk::TreeView::Column* column; int cell_x, cell_y; if( m_treeview.get_path_at_pos( bin_x, bin_y, path, column, cell_x, cell_y ) ) { m_treeview.set_tooltip_row( tooltip, path ); const Gtk::TreeModel::Row row = m_treeview.get_row( path ); const Glib::ustring& subject = row[ m_columns.m_name ]; const Glib::ustring& url = row[ m_columns.m_url ]; const int type = row[ m_columns.m_type ]; m_treeview.reset_pre_popupurl( url ); // 画像ポップアップはslot_motion_notifyでセットする if( type == TYPE_IMAGE ) return false; Gdk::Rectangle cell_area; m_treeview.get_cell_area( path, *column, cell_area ); const int capacity_width = m_treeview.get_width() - cell_area.get_x(); const auto layout = m_treeview.create_pango_layout( subject ); int pixel_width, ph; layout->get_pixel_size( pixel_width, ph ); constexpr int icon_size{ 16 }; #ifdef _DEBUG std::cout << "BBSListViewBase::slot_query_tooltip" << " pixel_width(" << pixel_width << ") + icon_size(" << icon_size << ")" << " > capacity_width(" << capacity_width << ")" << std::endl; #endif // 板一覧の幅よりセルの内容が長いならツールチップを表示する if( pixel_width + icon_size > capacity_width ) { m_treeview.hide_popup(); tooltip->set_text( subject ); return true; } } m_treeview.hide_popup(); return false; } // // XML保存 // // remove_dir != empty()の時はその名前のディレクトリを削除する // void BBSListViewBase::save_xml_impl( const std::string& file, const std::string& root, const std::string& remove_dir ) { if( file.empty() ) return; if( root.empty() ) return; if( ! get_ready_tree() ) return; #ifdef _DEBUG std::cout << "BBSListViewBase::save_xml file = " << file << " root = " << root << " remove_dir = " << remove_dir << std::endl; #endif tree2xml( root ); // 指定したディレクトリを取り除く if( ! remove_dir.empty() ) { XML::Dom* domroot = m_document.get_root_element( root ); const auto is_remove_dir = [&remove_dir]( const XML::Dom* child ) { return child->nodeName() == "subdir" && child->getAttribute( "name" ) == remove_dir; }; auto it = std::find_if( domroot->begin(), domroot->end(), is_remove_dir ); if( it != domroot->end() ) { domroot->removeChild( *it ); } } CACHE::save_rawdata( file, m_document.get_xml() ); } // undo, redo void BBSListViewBase::undo() { m_treeview.undo(); } void BBSListViewBase::redo() { m_treeview.redo(); } jdim-0.7.0/src/bbslist/bbslistviewbase.h000066400000000000000000000264001417047150700202430ustar00rootroot00000000000000// ライセンス: GPL2 // // 板ビュークラスのベースクラス // #ifndef _BBSLISTVIEWBASE_H #define _BBSLISTVIEWBASE_H #include "skeleton/view.h" #include "skeleton/edittreeview.h" #include "xml/document.h" #include "columns.h" #include #include #include #include namespace SKELETON { class Admin; } #define SUBDIR_ETCLIST "外部板" namespace BBSLIST { class EditListWin; class BBSListViewBase : public SKELETON::View { Glib::RefPtr< Gtk::TreeStore > m_treestore; SKELETON::EditTreeView m_treeview; // DOM共有オブジェクト XML::Document m_document; std::string m_date_modified; bool m_ready_tree{}; // ツリーがセットされているならtrue BBSLIST::TreeColumns m_columns; Gtk::ScrolledWindow m_scrwin; // クリック状態 bool m_clicked{}; // ダブルクリック状態 bool m_dblclicked; // ポップアップメニュー用 Gtk::TreeModel::Path m_path_selected; // クロック入力されたときにtreeview のスクロールバーを指定した位置に移動する // clock_in()の説明を参照 int m_jump_y; // サーチで使う変数 bool m_search_invert{}; std::string m_pre_query; bool m_open_only_onedir{}; // あるフォルダを開いたときに他のフォルダを閉じる bool m_cancel_expand{}; // signal_row_expanded() をキャンセルする bool m_expanding{}; // 行を開いている最中にtrueにしてsignal_row_collapsed()をキャンセルする // ツリーに含まれているスレのURLを入れる hash_set // toggle_articleicon() で使用する std::unordered_set< std::string > m_set_thread; // ツリーに含まれている板のURLを入れる set // toggle_boardicon() で使用する std::set< std::string > m_set_board; // ツリーに含まれている画像のURLを入れる hash_set std::set< std::string > m_set_image; std::unique_ptr m_editlistwin; // スレを追加したときにそのスレにしおりを付ける bool m_set_bookmark{}; protected: // Viewが所属するAdminクラス SKELETON::Admin* get_admin() override; // treeviewのD&Dによる編集を可能にする void set_editable( const bool editable ); void set_search_invert( const bool invert ){ m_search_invert = invert; } // スレを追加したときにそのスレにしおりを付ける void set_bookmark( const bool set ){ m_set_bookmark = set; } // DOM共有オブジェクト XML::Document& get_document(){ return m_document;} void set_document( const XML::Document& document){ m_document = document;} Glib::RefPtr< Gtk::TreeStore >& get_treestore() { return m_treestore; } SKELETON::EditTreeView& get_treeview() { return m_treeview; } bool get_ready_tree() const { return m_ready_tree; } void set_open_only_onedir( const bool set ){ m_open_only_onedir = set; } void activate_act_before_popupmenu( const std::string& url ) override; // tree <-> XML( DOM )変換 void tree2xml( const std::string& root_name ); void xml2tree( const std::string& root_name, const std::string& xml = std::string() ); // 外部板のディレクトリか bool is_etcdir( Gtk::TreePath path ); // 外部板か bool is_etcboard( const Gtk::TreeModel::iterator& it ); bool is_etcboard( Gtk::TreePath path ); // 起動時や移転があったときなどに行に含まれるURlを変更する void update_urls(); // アイコン表示(スレ)の切り替え void toggle_articleicon( const std::string& url ); // アイコン表示(板)の切り替え void toggle_boardicon( const std::string& url ); // URLを選択 void select_item( const std::string& url ); // スレの url と 名前を変更 void replace_thread( const std::string& url, const std::string& url_new ); // path からその行のタイプを取得 int path2type( const Gtk::TreePath& path ); // row からタイプを取得 int row2type( const Gtk::TreeModel::Row& row ) const; // row -> name 変換 std::string row2name( const Gtk::TreeModel::Row& row ) const; // row -> url 変換 // 板の場合は boardbase // スレの場合は dat 型のアドレスを返す std::string row2url( const Gtk::TreeModel::Row& row ) const; // row -> dirid 変換 size_t row2dirid( const Gtk::TreeModel::Row& row ) const; // path からその行の名前を取得 std::string path2name( const Gtk::TreePath& path ); // path からその行のURLを取得 std::string path2rawurl( const Gtk::TreePath& path ); std::string path2url( const Gtk::TreePath& path ); // 移転をチェックするバージョン // url で指定した項目を削除 void remove_item( const std::string& url ); // 先頭項目を削除 void remove_headitem(); // 全項目を削除 void remove_allitems(); // ツリーの編集ウィンドウを開く void edit_tree(); // xml保存 virtual void save_xml() = 0; // remove_dir != empty()の時はその名前のディレクトリを削除する void save_xml_impl( const std::string& file, const std::string& root, const std::string& remove_dir ); // idからポップアップメニュー取得 Gtk::Menu* id2popupmenu( const std::string& id ); public: explicit BBSListViewBase( const std::string& url, const std::string& arg1 = {}, const std::string& arg2 = {} ); ~BBSListViewBase() noexcept; // // SKELETON::View の関数のオーバロード // void save_session() override; // 親ウィンドウをセット void set_parent_win( Gtk::Window* parent_win ) override; std::string url_for_copy() const override { return {}; } bool set_command( const std::string& command, const std::string& arg1 = {}, const std::string& arg2 = {} ) override; void clock_in() override; // キーを押した bool slot_key_press( GdkEventKey* event ) override; void stop() override; void redraw_view() override; void relayout() override; // 色やフォントなどの変更 void focus_view() override; void focus_out() override; void close_view() override; void delete_view() override; // ツリー内の全ての項目をURLを新しいアドレスに変更 ( id は未使用 ) void update_item( const std::string& url, const std::string& id ) override; bool operate_view( const int control ) override; void goto_top() override; void goto_bottom() override; // 検索 void exec_search() override; void up_search() override; void down_search() override; void operate_search( const std::string& controlid ) override; // 挿入先ダイアログを表示してアイテム追加 // あらかじめ共有バッファに追加するデータをセットしておくこと void append_item(); // 履歴のセット // 先頭にアイテムを追加する。ツリーにアイテムが含まれている場合は移動する // あらかじめ共有バッファに追加するデータをセットしておくこと void append_history(); // 履歴を DATA_INFO_LIST 型で取得 void get_history( CORE::DATA_INFO_LIST& info_list ) const; // 指定したidのディレクトリに含まれるスレのアドレスを取得 void get_threads( const size_t dirid, std::vector< std::string >& list_url ); // 指定したidのディレクトリの名前を取得 std::string get_dirname( const int dirid ); // selectdialogで使う Gtk::TreePath get_current_path() { return m_treeview.get_current_path(); } void copy_treestore( const Glib::RefPtr< Gtk::TreeStore >& store ); // undo, redo void undo(); void redo(); private: void set_fgcolor_of_comment( const Gtk::TreeModel::Children& children ); void row_up(); void row_down(); void page_up(); void page_down(); void prev_dir(); void next_dir(); void expand_all_dir( Gtk::TreeModel::Path path ); // ディレクトリ以下を更新チェック // root : true ならルートから検索する。falseの場合は m_path_selected にパスをセットしておくこと // open : チェック後に更新していたら開く void check_update_dir( const bool root, const bool open ); bool slot_button_press( GdkEventButton* event ); bool slot_button_release( GdkEventButton* event ); bool slot_motion_notify( GdkEventMotion* event ); bool slot_key_release( GdkEventKey* event ); bool slot_scroll_event( GdkEventScroll* event ); void slot_dropped_from_other( const CORE::DATA_INFO_LIST& list_info ); void slot_open_tab(); void slot_open_browser(); void slot_open_cache_browser(); void slot_append_favorite(); void slot_newdir(); void slot_newcomment(); void slot_newetcboard(); void slot_moveetcboard(); void slot_rename(); void slot_copy_url(); void slot_copy_title_url(); void slot_select_all_dir(); void slot_select_all(); void slot_check_update_dir(); void slot_check_update_open_dir(); void slot_opendir_as_board(); void slot_create_vboard(); void slot_search_cache_board(); void slot_import_dat(); void slot_preferences_board(); void slot_preferences_article(); void slot_preferences_image(); void slot_row_exp( const Gtk::TreeModel::iterator& it, const Gtk::TreeModel::Path& path ); void slot_row_col( const Gtk::TreeModel::iterator& it, const Gtk::TreeModel::Path& path ); void slot_checkupdate_selected_rows(); void slot_checkupdate_open_selected_rows(); void slot_sort( const int mode ); virtual void delete_view_impl(); // 外部板追加/編集 void add_newetcboard( const bool move, const std::string& _url, const std::string& _name, const std::string& _id, const std::string& _passwd ); // D&D 関係 void slot_drag_drop( Gtk::TreeModel::Path path, const bool after ); virtual bool open_row( Gtk::TreePath& path, const bool tab ); virtual void switch_rightview(); void open_selected_rows(); void checkupdate_selected_rows( const bool open ); void show_status(); // ツリーの編集ウィンドウが閉じた void slot_hide_editlistwin(); bool slot_query_tooltip( int x, int y, bool keyboard_tooltip, const Glib::RefPtr& tooltip ); }; } #endif jdim-0.7.0/src/bbslist/columns.cpp000066400000000000000000000012421417047150700170630ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "columns.h" #include "config/globalconf.h" #include "xml/tools.h" #include "colorid.h" #include "type.h" using namespace BBSLIST; TreeColumns::~TreeColumns() noexcept = default; void TreeColumns::setup_row( Gtk::TreeModel::Row& row, const Glib::ustring url, const Glib::ustring name, const Glib::ustring data, const int type, const size_t dirid ) { SKELETON::EditColumns::setup_row( row, url, name, data, type, dirid ); if( type == TYPE_COMMENT ) row[ m_fgcolor ] = Gdk::RGBA( CONFIG::get_color( COLOR_CHAR_BBS_COMMENT ) ); } jdim-0.7.0/src/bbslist/columns.h000066400000000000000000000010461417047150700165320ustar00rootroot00000000000000// ライセンス: GPL2 // // コラム // #ifndef _BBSLISTCOLUMNS_H #define _BBSLISTCOLUMNS_H #include "skeleton/editcolumns.h" namespace BBSLIST { class TreeColumns : public SKELETON::EditColumns { public: using SKELETON::EditColumns::EditColumns; ~TreeColumns() noexcept; void setup_row( Gtk::TreeModel::Row& row, const Glib::ustring url, const Glib::ustring name, const Glib::ustring data, const int type, const size_t dirid ) override; }; } #endif jdim-0.7.0/src/bbslist/editlistwin.cpp000066400000000000000000000122741417047150700177510ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "editlistwin.h" #include "selectlistview.h" #include "toolbar.h" #include "bbslistadmin.h" #include "skeleton/compentry.h" #include "skeleton/undobuffer.h" #include "viewfactory.h" using namespace BBSLIST; enum { EDITWIN_WIDTH = 800, EDITWIN_HEIGHT = 500 }; EditListWin::EditListWin( const std::string& url, const Glib::RefPtr< Gtk::TreeStore >& treestore ) : Gtk::Window( Gtk::WINDOW_TOPLEVEL ), m_label( "マウスの中ボタンドラッグで行の複数選択が可能です。" ) { // ツールバー m_toolbar = Gtk::manage( new EditListToolBar() ); m_toolbar->open_buttonbar(); m_vbox.pack_start( *m_toolbar, Gtk::PACK_SHRINK ); m_toolbar->show_toolbar(); // Adminクラスが無いのでツールバーのボタン等のシグナルを直接つなぐ m_toolbar->get_button_close()->signal_clicked().connect( sigc::mem_fun( this, &EditListWin::slot_close ) ); m_toolbar->get_entry_search()->signal_changed().connect( sigc::mem_fun( *this, &EditListWin::slot_changed_search ) ); m_toolbar->get_entry_search()->signal_activate().connect( sigc::mem_fun( *this, &EditListWin::slot_active_search ) ); m_toolbar->get_entry_search()->signal_operate().connect( sigc::mem_fun( *this, &EditListWin::slot_operate_search ) ); m_toolbar->get_button_up_search()->signal_clicked().connect( sigc::mem_fun( this, &EditListWin::slot_up_search ) ); m_toolbar->get_button_down_search()->signal_clicked().connect( sigc::mem_fun( this, &EditListWin::slot_down_search ) ); m_toolbar->get_button_undo()->signal_clicked().connect( sigc::mem_fun( this, &EditListWin::slot_undo ) ); m_toolbar->get_button_redo()->signal_clicked().connect( sigc::mem_fun( this, &EditListWin::slot_redo ) ); // ラベル m_vbox.pack_start( m_label, Gtk::PACK_SHRINK ); // ビュー m_selectview = dynamic_cast< SelectListView* > ( Gtk::manage( CORE::ViewFactory( CORE::VIEW_SELECTLIST, url ) ) ); if( m_selectview ){ m_selectview->set_parent_win( this ); m_selectview->copy_treestore( treestore ); m_selectview->sig_close_dialog().connect( sigc::mem_fun(*this, &EditListWin::hide ) ); m_selectview->sig_focus_entry_search().connect( sigc::mem_fun(*this, &EditListWin::slot_focus_entry_search ) ); m_vbox.pack_start( *m_selectview ); m_selectview->focus_view(); } // UNDOバッファの監視 BBSLIST::get_undo_buffer_favorite()->sig_undo().connect( sigc::mem_fun(*this, &EditListWin::slot_undo_buffer_changed ) ); BBSLIST::get_undo_buffer_favorite()->sig_redo().connect( sigc::mem_fun(*this, &EditListWin::slot_undo_buffer_changed ) ); BBSLIST::get_undo_buffer_favorite()->sig_commit().connect( sigc::mem_fun(*this, &EditListWin::slot_undo_buffer_changed ) ); add( m_vbox ); set_title( "お気に入りの編集" ); set_position( Gtk::WIN_POS_CENTER ); resize( EDITWIN_WIDTH, EDITWIN_HEIGHT ); show_all_children(); } EditListWin::~EditListWin() noexcept = default; void EditListWin::clock_in() { if( m_selectview ) m_selectview->clock_in(); } void EditListWin::append_item() { if( m_selectview ) m_selectview->append_item(); } // // 閉じる // void EditListWin::slot_close() { #ifdef _DEBUG std::cout << "EditListWin::slot_close\n"; #endif hide(); } // // 検索関係 // void EditListWin::slot_focus_entry_search() { #ifdef _DEBUG std::cout << "EditListWin::slot_focus_search\n"; #endif if( m_toolbar ) m_toolbar->focus_entry_search(); } void EditListWin::slot_changed_search() { #ifdef _DEBUG std::cout << "EditListWin::slot_changed_search\n"; #endif if( m_selectview && m_toolbar ) m_selectview->set_search_query( m_toolbar->get_search_text() ); } void EditListWin::slot_active_search() { #ifdef _DEBUG std::cout << "EditListWin::slot_active_search\n"; #endif if( m_selectview ) m_selectview->exec_search(); } void EditListWin::slot_operate_search( const int controlid ) { #ifdef _DEBUG std::cout << "EditListWin::slot_operate_search id = " << controlid << std::endl; #endif if( m_selectview ) m_selectview->operate_search( std::to_string( controlid ) ); } void EditListWin::slot_up_search() { #ifdef _DEBUG std::cout << "EditListWin::slot_up_search\n"; #endif if( m_selectview ) m_selectview->up_search(); } void EditListWin::slot_down_search() { #ifdef _DEBUG std::cout << "EditListWin::slot_down_search\n"; #endif if( m_selectview ) m_selectview->down_search(); } void EditListWin::slot_undo() { #ifdef _DEBUG std::cout << "EditListWin::slot_undo\n"; #endif if( m_selectview ) m_selectview->undo(); } void EditListWin::slot_redo() { #ifdef _DEBUG std::cout << "EditListWin::slot_redo\n"; #endif if( m_selectview ) m_selectview->redo(); } // // UNDOバッファを監視してボタン状態を更新する // void EditListWin::slot_undo_buffer_changed() { #ifdef _DEBUG std::cout << "EditListWin::slot_undo_buffer_changed\n"; #endif m_toolbar->get_button_undo()->set_sensitive( BBSLIST::get_undo_buffer_favorite()->get_enable_undo() ); m_toolbar->get_button_redo()->set_sensitive( BBSLIST::get_undo_buffer_favorite()->get_enable_redo() ); } jdim-0.7.0/src/bbslist/editlistwin.h000066400000000000000000000020021417047150700174020ustar00rootroot00000000000000// ライセンス: GPL2 // // お気に入り編集ウィンドウ // #ifndef _EDITLISTWIN_H #define _EDITLISTWIN_H #include namespace BBSLIST { class EditListToolBar; class SelectListView; class EditListWin : public Gtk::Window { SelectListView* m_selectview; Gtk::VBox m_vbox; Gtk::Label m_label; EditListToolBar* m_toolbar; public: EditListWin( const std::string& url, const Glib::RefPtr< Gtk::TreeStore >& treestore ); ~EditListWin() noexcept; void clock_in(); void append_item(); private: // 閉じる void slot_close(); // 検索関係 void slot_focus_entry_search(); void slot_changed_search(); void slot_active_search(); void slot_operate_search( const int controlid ); void slot_up_search(); void slot_down_search(); void slot_undo(); void slot_redo(); void slot_undo_buffer_changed(); }; } #endif jdim-0.7.0/src/bbslist/favoriteview.cpp000066400000000000000000000044421417047150700201220ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "favoriteview.h" #include "toolbar.h" #include "config/globalconf.h" #include "cache.h" #include "type.h" // ルート要素名( bookmark.xml ) #define ROOT_NODE_NAME "favorite" using namespace BBSLIST; FavoriteListView::FavoriteListView( const std::string& url, const std::string& arg1, const std::string& arg2 ) : BBSListViewBase( url, arg1, arg2 ) { set_label( "お気に入り" ); // D&Dで編集可能 set_editable( true ); // スレをお気に入りに追加したらしおりをつけるかどうか set_bookmark( CONFIG::get_bookmark_drop() ); set_open_only_onedir( CONFIG::get_open_one_favorite() ); } FavoriteListView::~FavoriteListView() { #ifdef _DEBUG std::cout << "FavoriteList::~FavoriteList : " << get_url() << std::endl; #endif } // xml保存 void FavoriteListView::save_xml() { const std::string file = CACHE::path_xml_favorite(); save_xml_impl( file, ROOT_NODE_NAME, "" ); } // // 表示 // void FavoriteListView::show_view() { std::string xml; std::string file_in = CACHE::path_xml_favorite(); CACHE::load_rawdata( file_in, xml ); xml2tree( std::string( ROOT_NODE_NAME ), xml ); update_urls(); } // // ポップアップメニュー取得 // // SKELETON::View::show_popupmenu() を参照すること // Gtk::Menu* FavoriteListView::get_popupmenu( const std::string& url ) { Gtk::Menu* popupmenu = nullptr; if( url.empty() ) popupmenu = id2popupmenu( "/popup_menu_favorite_space" ); else{ std::list< Gtk::TreeModel::iterator > list_it = get_treeview().get_selected_iterators(); if( list_it.size() == 1 ){ const int type = path2type( *( get_treeview().get_selection()->get_selected_rows().begin() ) ); if( type == TYPE_DIR ) popupmenu = id2popupmenu( "/popup_menu_favorite_dir" ); else if( type == TYPE_COMMENT ) popupmenu = id2popupmenu( "/popup_menu_favorite_com" ); else if( type == TYPE_VBOARD ) popupmenu = id2popupmenu( "/popup_menu_favorite_vboard" ); else popupmenu = id2popupmenu( "/popup_menu_favorite" ); } else popupmenu = id2popupmenu( "/popup_menu_favorite_mul" ); } return popupmenu; } jdim-0.7.0/src/bbslist/favoriteview.h000066400000000000000000000011361417047150700175640ustar00rootroot00000000000000// ライセンス: GPL2 // お気に入りビュー #ifndef _FAVORITEVIEW_H #define _FAVORITEVIEW_H #include "bbslistviewbase.h" namespace BBSLIST { // お気に入りビュー class FavoriteListView : public BBSListViewBase { public: explicit FavoriteListView( const std::string& url, const std::string& arg1 = {}, const std::string& arg2 = {} ); ~FavoriteListView(); void show_view() override; protected: // xml保存 void save_xml() override; Gtk::Menu* get_popupmenu( const std::string& url ) override; }; } #endif jdim-0.7.0/src/bbslist/historyview.cpp000066400000000000000000000056061417047150700200070ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "historyview.h" #include "toolbar.h" #include "config/globalconf.h" #include "cache.h" #include "type.h" #include "global.h" // ルート要素名 #define ROOT_NODE_NAME "history" using namespace BBSLIST; HistoryViewBase::HistoryViewBase( const std::string& url, const std::string& file_xml, const std::string& arg1, const std::string& arg2 ) : BBSListViewBase( url, arg1, arg2 ), m_file_xml( file_xml ) {} HistoryViewBase::~HistoryViewBase() { #ifdef _DEBUG std::cout << "HistoryViewBase::~HistoryViewBase : " << get_url() << std::endl; #endif } // // 表示 // void HistoryViewBase::show_view() { std::string xml; CACHE::load_rawdata( m_file_xml, xml ); xml2tree( std::string( ROOT_NODE_NAME ), xml ); update_urls(); } // xml保存 void HistoryViewBase::save_xml() { save_xml_impl( m_file_xml, ROOT_NODE_NAME, "" ); } // // ポップアップメニュー取得 // // SKELETON::View::show_popupmenu() を参照すること // Gtk::Menu* HistoryViewBase::get_popupmenu( const std::string& url ) { Gtk::Menu* popupmenu = nullptr; if( ! url.empty() ){ std::list< Gtk::TreeModel::iterator > list_it = get_treeview().get_selected_iterators(); if( list_it.size() == 1 ){ const int type = path2type( *( get_treeview().get_selection()->get_selected_rows().begin() ) ); if( type == TYPE_VBOARD ) popupmenu = id2popupmenu( "/popup_menu_history_vboard" ); else popupmenu = id2popupmenu( "/popup_menu_history" ); } else popupmenu = id2popupmenu( "/popup_menu_history_mul" ); } return popupmenu; } ////////////////////////////////// HistoryThreadView::HistoryThreadView( const std::string& url, const std::string& arg1, const std::string& arg2 ) : HistoryViewBase( url, CACHE::path_xml_history(), arg1, arg2 ) { set_label( ITEM_NAME_HISTVIEW ); } HistoryCloseView::HistoryCloseView( const std::string& url, const std::string& arg1, const std::string& arg2 ) : HistoryViewBase( url, CACHE::path_xml_history_close(), arg1, arg2 ) { set_label( ITEM_NAME_HIST_CLOSEVIEW ); } HistoryBoardView::HistoryBoardView( const std::string& url, const std::string& arg1, const std::string& arg2 ) : HistoryViewBase( url, CACHE::path_xml_history_board(), arg1, arg2 ) { set_label( ITEM_NAME_HIST_BOARDVIEW ); } HistoryCloseBoardView::HistoryCloseBoardView( const std::string& url, const std::string& arg1, const std::string& arg2 ) : HistoryViewBase( url, CACHE::path_xml_history_closeboard(), arg1, arg2 ) { set_label( ITEM_NAME_HIST_CLOSEBOARDVIEW ); } HistoryCloseImgView::HistoryCloseImgView( const std::string& url, const std::string& arg1, const std::string& arg2 ) : HistoryViewBase( url, CACHE::path_xml_history_closeimg(), arg1, arg2 ) { set_label( ITEM_NAME_HIST_CLOSEIMGVIEW ); } jdim-0.7.0/src/bbslist/historyview.h000066400000000000000000000032451417047150700174510ustar00rootroot00000000000000// ライセンス: GPL2 // 履歴ビュー #ifndef _HISTORYVIEW_H #define _HISTORYVIEW_H #include "bbslistviewbase.h" namespace BBSLIST { class HistoryViewBase : public BBSListViewBase { std::string m_file_xml; public: HistoryViewBase( const std::string& url, const std::string& file_xml, const std::string& arg1, const std::string& arg2 ); ~HistoryViewBase(); void show_view() override; protected: // xml保存 void save_xml() override; Gtk::Menu* get_popupmenu( const std::string& url ) override; }; /////////////////////////////////////// class HistoryThreadView : public HistoryViewBase { public: explicit HistoryThreadView( const std::string& url, const std::string& arg1 = {}, const std::string& arg2 = {} ); }; class HistoryCloseView : public HistoryViewBase { public: explicit HistoryCloseView( const std::string& url, const std::string& arg1 = {}, const std::string& arg2 = {} ); }; class HistoryBoardView : public HistoryViewBase { public: explicit HistoryBoardView( const std::string& url, const std::string& arg1 = {}, const std::string& arg2 = {} ); }; class HistoryCloseBoardView : public HistoryViewBase { public: explicit HistoryCloseBoardView( const std::string& url, const std::string& arg1 = {}, const std::string& arg2 = {} ); }; class HistoryCloseImgView : public HistoryViewBase { public: explicit HistoryCloseImgView( const std::string& url, const std::string& arg1 = {}, const std::string& arg2 = {} ); }; } #endif jdim-0.7.0/src/bbslist/meson.build000066400000000000000000000005771417047150700170530ustar00rootroot00000000000000sources = [ 'addetcdialog.cpp', 'bbslistadmin.cpp', 'bbslistview.cpp', 'bbslistviewbase.cpp', 'columns.cpp', 'editlistwin.cpp', 'favoriteview.cpp', 'historyview.cpp', 'selectdialog.cpp', 'selectlistview.cpp', 'toolbar.cpp', ] bbslist_lib = static_library( 'bbslist', sources, dependencies : gtkmm_dep, include_directories : include_directories('..'), ) jdim-0.7.0/src/bbslist/selectdialog.cpp000066400000000000000000000105261417047150700200470ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "selectdialog.h" #include "selectlistview.h" #include "columns.h" #include "viewfactory.h" #include "command.h" #include "session.h" #include "type.h" #include "sharedbuffer.h" using namespace BBSLIST; enum { SELECTDIAG_WIDTH = 600, SELECTDIAG_TREEHEIGHT = 300 }; SelectListDialog::SelectListDialog( Gtk::Window* parent, const std::string& url, Glib::RefPtr< Gtk::TreeStore >& treestore ) : SKELETON::PrefDiag( parent, url, true ) , m_treestore( treestore ) , m_label_name( CORE::SBUF_size() == 1, "名前 :" ) , m_label_dirs( "ディレクトリ :" ) , m_bt_show_tree( "詳細" ) { const int mrg = 8; if( CORE::SBUF_size() ) m_label_name.set_text( ( *CORE::SBUF_list_info().begin() ).name ); set_activate_entry( m_label_name ); m_hbox_dirs.set_spacing( mrg ); m_hbox_dirs.pack_start( m_label_dirs, Gtk::PACK_SHRINK ); m_hbox_dirs.pack_start( m_combo_dirs, Gtk::PACK_EXPAND_WIDGET ); m_hbox_dirs.pack_start( m_bt_show_tree, Gtk::PACK_SHRINK ); // コンボボックスにディレクトリをセット int active_row = 0; Glib::ustring name; name = "お気に入りの先頭に追加"; m_combo_dirs.append( name ); if( name == SESSION::get_dir_select_favorite() ) active_row = m_vec_path.size(); m_vec_path.push_back( "-1" ); name = "お気に入りの最後に追加"; m_combo_dirs.append( name ); if( ! active_row && name == SESSION::get_dir_select_favorite() ) active_row = m_vec_path.size(); m_vec_path.push_back( "" ); BBSLIST::TreeColumns columns; Gtk::TreeModel::Children child = m_treestore->children(); for( Gtk::TreeModel::Row row : child ) { const int type = row[ columns.m_type ]; if( type == TYPE_DIR ){ name = row[ columns.m_name ]; m_combo_dirs.append( name ); if( ! active_row && name == SESSION::get_dir_select_favorite() ) active_row = m_vec_path.size(); Gtk::TreePath path = m_treestore->get_path( row ); m_vec_path.push_back( path.to_string() ); #ifdef _DEBUG std::cout << row[ columns.m_name ] << " path = " << path.to_string() << std::endl; #endif } } #ifdef _DEBUG std::cout << "active_row = " << active_row << std::endl; #endif m_combo_dirs.set_active( active_row ); get_content_area()->pack_start( m_label_name, Gtk::PACK_SHRINK ); get_content_area()->pack_start( m_hbox_dirs, Gtk::PACK_SHRINK ); m_bt_show_tree.signal_clicked().connect( sigc::mem_fun( this, &SelectListDialog::slot_show_tree ) ); set_title( "お気に入り追加先選択" ); resize( SELECTDIAG_WIDTH, 1 ); set_default_response( Gtk::RESPONSE_OK ); grab_ok(); show_all_children(); } // SelectListView が不完全型のためヘッダーではデストラクタを定義できない SelectListDialog::~SelectListDialog() noexcept = default; // // OKを押した // void SelectListDialog::slot_ok_clicked() { if( ! m_selectview ) SESSION::set_dir_select_favorite( m_combo_dirs.get_active_text() ); } std::string SelectListDialog::get_name() { return m_label_name.get_text(); } std::string SelectListDialog::get_path() const { std::string path; if( m_selectview ) path = m_selectview->get_current_path().to_string(); else path = m_vec_path[ m_combo_dirs.get_active_row_number() ]; #ifdef _DEBUG std::cout << "SelectListDialog::get_path path = " << path << std::endl; #endif return path; } void SelectListDialog::slot_show_tree() { #ifdef _DEBUG std::cout << "SelectListDialog::slot_show_tree\n"; #endif if( m_bt_show_tree.get_active() ){ if( ! m_selectview ) m_selectview = std::make_unique( get_url() ); m_selectview->set_parent_win( this ); m_selectview->copy_treestore( m_treestore ); m_selectview->sig_close_dialog().connect( [this] { hide(); } ); get_content_area()->pack_start( *m_selectview ); m_selectview->set_size_request( -1, SELECTDIAG_TREEHEIGHT ); m_selectview->focus_view(); show_all_children(); } else if( m_selectview ){ get_content_area()->remove( *m_selectview ); resize( get_width(), 1 ); m_selectview.reset(); } } void SelectListDialog::timeout() { if( m_selectview ) m_selectview->clock_in(); } jdim-0.7.0/src/bbslist/selectdialog.h000066400000000000000000000020751417047150700175140ustar00rootroot00000000000000// ライセンス: GPL2 // // お気に入り挿入ダイアログ // #ifndef _SELECTDIALOG_H #define _SELECTDIALOG_H #include "skeleton/prefdiag.h" #include "skeleton/label_entry.h" #include #include namespace BBSLIST { class SelectListView; class SelectListDialog : public SKELETON::PrefDiag { Glib::RefPtr< Gtk::TreeStore >& m_treestore; std::vector< std::string > m_vec_path; SKELETON::LabelEntry m_label_name; Gtk::HBox m_hbox_dirs; Gtk::Label m_label_dirs; Gtk::ComboBoxText m_combo_dirs; Gtk::ToggleButton m_bt_show_tree; std::unique_ptr m_selectview; public: SelectListDialog( Gtk::Window* parent, const std::string& url, Glib::RefPtr< Gtk::TreeStore >& treestore ); ~SelectListDialog() noexcept; std::string get_name(); std::string get_path() const; protected: void slot_ok_clicked() override; private: void slot_show_tree(); void timeout() override; }; } #endif jdim-0.7.0/src/bbslist/selectlistview.cpp000066400000000000000000000046501417047150700204570ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "selectlistview.h" #include "toolbar.h" #include "control/controlid.h" #include "type.h" using namespace BBSLIST; SelectListView::SelectListView( const std::string& url, const std::string& arg1, const std::string& arg2) : BBSListViewBase( url, arg1, arg2 ) { // D&Dで編集可能 set_editable( true ); } SelectListView::~SelectListView() noexcept = default; void SelectListView::close_view() { #ifdef _DEBUG std::cout << "SelectListView::close_view\n"; #endif // ダイアログを閉じる m_sig_close_dialog.emit(); } bool SelectListView::operate_view( const int control ) { bool ret = true; // ESCでダイアログを閉じる if( control == CONTROL::Cancel ){ close_view(); } else if( control == CONTROL::Search ){ set_search_invert( false ); m_sig_focus_entry_search.emit(); } else if( control == CONTROL::SearchInvert ){ set_search_invert( true ); m_sig_focus_entry_search.emit(); } else ret = BBSListViewBase::operate_view( control ); return ret; } // // ポップアップメニュー取得 // // SKELETON::View::show_popupmenu() を参照すること // Gtk::Menu* SelectListView::get_popupmenu( const std::string& url ) { Gtk::Menu* popupmenu; if( url.empty() ) popupmenu = id2popupmenu( "/popup_menu_favorite_space" ); else{ std::list< Gtk::TreeModel::iterator > list_it = get_treeview().get_selected_iterators(); if( list_it.size() == 1 ){ int type = path2type( *( get_treeview().get_selection()->get_selected_rows().begin() ) ); if( type == TYPE_DIR ) popupmenu = id2popupmenu( "/popup_menu_favorite_dir" ); else if( type == TYPE_COMMENT ) popupmenu = id2popupmenu( "/popup_menu_favorite_com" ); else popupmenu = id2popupmenu( "/popup_menu_select" ); } else popupmenu = id2popupmenu( "/popup_menu_favorite_mul" ); } return popupmenu; } // // 選択した行を開く // bool SelectListView::open_row( Gtk::TreePath& path, const bool tab ) { if( ! get_treeview().get_row( path ) ) return false; // ディレクトリの開け閉め if( path2type( path ) == TYPE_DIR ){ if( ! get_treeview().row_expanded( path ) ) get_treeview().expand_row( path, false ); else get_treeview().collapse_row( path ); } return true; } jdim-0.7.0/src/bbslist/selectlistview.h000066400000000000000000000023511417047150700201200ustar00rootroot00000000000000// ライセンス: GPL2 // // お気に入り追加の時の選択ビュー // #ifndef _SELECTLISTVIEW_H #define _SELECTLISTVIEW_H #include "bbslistviewbase.h" namespace BBSLIST { // 親の SelectListDialog や EditListWin に送る信号 typedef sigc::signal< void > SIG_CLOSE_DIALOG; typedef sigc::signal< void > SIG_FOCUS_ENTRY_SEARCH; class SelectListView : public BBSListViewBase { SIG_CLOSE_DIALOG m_sig_close_dialog; SIG_FOCUS_ENTRY_SEARCH m_sig_focus_entry_search; public: explicit SelectListView( const std::string& url, const std::string& arg1 = {}, const std::string& arg2 = {} ); ~SelectListView() noexcept; SIG_CLOSE_DIALOG sig_close_dialog() { return m_sig_close_dialog; } SIG_FOCUS_ENTRY_SEARCH sig_focus_entry_search() { return m_sig_focus_entry_search; } void save_xml() override {} void close_view() override; bool operate_view( const int control ) override; private: bool open_row( Gtk::TreePath& path, const bool tab ) override; void switch_rightview() override {} // boardに移動しないようにキャンセル Gtk::Menu* get_popupmenu( const std::string& url ) override; }; } #endif jdim-0.7.0/src/bbslist/toolbar.cpp000066400000000000000000000177321417047150700170600ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "gtkmmversion.h" #include "toolbar.h" #include "bbslistadmin.h" #include "skeleton/view.h" #include "skeleton/menubutton.h" #include "skeleton/imgtoolbutton.h" #include "control/controlutil.h" #include "control/controlid.h" #include "icons/iconmanager.h" #include "config/globalconf.h" #include "command.h" #include "session.h" #include "compmanager.h" #include "global.h" using namespace BBSLIST; BBSListToolBar::BBSListToolBar() : SKELETON::ToolBar( BBSLIST::get_admin() ) , m_button_toggle( "ページ切り替え", true, true, m_label ) { m_button_toggle.get_button()->set_tooltip_arrow( "ページ切り替え\n\nマウスホイール回転でも切り替え可能" ); m_label.set_xalign( 0 ); m_label.set_ellipsize( Pango::ELLIPSIZE_END ); std::vector< std::string > menu; menu.push_back( ITEM_NAME_BBSLISTVIEW ); menu.push_back( ITEM_NAME_FAVORITEVIEW ); menu.push_back( ITEM_NAME_HISTVIEW ); menu.push_back( ITEM_NAME_HIST_BOARDVIEW ); menu.push_back( ITEM_NAME_HIST_CLOSEVIEW ); menu.push_back( ITEM_NAME_HIST_CLOSEBOARDVIEW ); menu.push_back( ITEM_NAME_HIST_CLOSEIMGVIEW ); m_button_toggle.get_button()->append_menu( menu ); m_button_toggle.get_button()->signal_selected().connect( sigc::mem_fun(*this, &BBSListToolBar::slot_toggle ) ); m_button_toggle.get_button()->signal_scroll_event().connect( sigc::mem_fun( *this, &BBSListToolBar::slot_scroll_event )); m_button_toggle.get_button()->set_enable_sig_clicked( false ); m_tool_label.set_icon_size( Gtk::ICON_SIZE_MENU ); m_tool_label.set_toolbar_style( Gtk::TOOLBAR_ICONS ); m_tool_label.append( m_button_toggle ); m_tool_label.append( *get_button_close() ); pack_start( m_tool_label, Gtk::PACK_SHRINK ); BBSListToolBar::pack_buttons(); add_search_control_mode( CONTROL::MODE_BBSLIST ); } BBSListToolBar::~BBSListToolBar() noexcept = default; // // ボタンのパッキング // // virtual void BBSListToolBar::pack_buttons() { int num = 0; for(;;){ int item = SESSION::get_item_sidebar_toolbar( num ); if( item == ITEM_END ) break; switch( item ){ case ITEM_SEARCHBOX: get_buttonbar().append( *get_tool_search( CORE::COMP_SEARCH_BBSLIST ) ); break; case ITEM_CHECK_UPDATE_ROOT: if( ! m_button_check_update_root ){ m_button_check_update_root = Gtk::manage( new SKELETON::ImgToolButton( ICON::CHECK_UPDATE_ROOT, CONTROL::CheckUpdateRoot ) ); m_button_check_update_root->signal_clicked().connect( sigc::mem_fun(*this, &BBSListToolBar::slot_check_update_root ) ); set_tooltip( *m_button_check_update_root, CONTROL::get_label_motions( CONTROL::CheckUpdateRoot ) ); } get_buttonbar().append( *m_button_check_update_root ); break; case ITEM_CHECK_UPDATE_OPEN_ROOT: if( ! m_button_check_update_open_root ){ m_button_check_update_open_root = Gtk::manage( new SKELETON::ImgToolButton( ICON::CHECK_UPDATE_OPEN_ROOT, CONTROL::CheckUpdateOpenRoot ) ); m_button_check_update_open_root->signal_clicked().connect( sigc::mem_fun(*this, &BBSListToolBar::slot_check_update_open_root ) ); set_tooltip( *m_button_check_update_open_root, CONTROL::get_label_motions( CONTROL::CheckUpdateOpenRoot ) ); } get_buttonbar().append( *m_button_check_update_open_root ); break; case ITEM_STOPLOADING: if( ! m_button_stop_check_update ) m_button_stop_check_update = get_button_stop(); get_buttonbar().append( *m_button_stop_check_update ); break; case ITEM_SEARCH_NEXT: get_buttonbar().append( *get_button_down_search() ); break; case ITEM_SEARCH_PREV: get_buttonbar().append( *get_button_up_search() ); break; case ITEM_SEPARATOR: pack_separator(); break; } ++num; } set_relief(); show_all_children(); } // タブが切り替わった時にDragableNoteBook::set_current_toolbar()から呼び出される( Viewの情報を取得する ) // virtual void BBSListToolBar::set_view( SKELETON::View* view ) { ToolBar::set_view( view ); if( view ){ m_label.set_text( view->get_label() ); if( view->get_url() == URL_BBSLISTVIEW || ( ! CONFIG::get_check_update_board() && view->get_url() == URL_HISTBOARDVIEW ) ) { if( m_button_check_update_root ) m_button_check_update_root->set_sensitive( false ); if( m_button_check_update_open_root ) m_button_check_update_open_root->set_sensitive( false ); if( m_button_stop_check_update ) m_button_stop_check_update->set_sensitive( false ); } else{ if( m_button_check_update_root ) m_button_check_update_root->set_sensitive( true ); if( m_button_check_update_open_root ) m_button_check_update_open_root->set_sensitive( true ); if( m_button_stop_check_update ) m_button_stop_check_update->set_sensitive( true ); } } } void BBSListToolBar::slot_toggle( const int i ) { #ifdef _DEBUG std::cout << "BBSListToolBar::slot_toggle = " << get_url() << " i = " << i << std::endl; #endif switch( i ){ case 0: if( get_url() != URL_BBSLISTVIEW ) CORE::core_set_command( "switch_sidebar", URL_BBSLISTVIEW ); break; case 1: if( get_url() != URL_FAVORITEVIEW ) CORE::core_set_command( "switch_sidebar", URL_FAVORITEVIEW ); break; case 2: if( get_url() != URL_HISTTHREADVIEW ) CORE::core_set_command( "switch_sidebar", URL_HISTTHREADVIEW ); break; case 3: if( get_url() != URL_HISTBOARDVIEW ) CORE::core_set_command( "switch_sidebar", URL_HISTBOARDVIEW ); break; case 4: if( get_url() != URL_HISTCLOSEVIEW ) CORE::core_set_command( "switch_sidebar", URL_HISTCLOSEVIEW ); break; case 5: if( get_url() != URL_HISTCLOSEBOARDVIEW ) CORE::core_set_command( "switch_sidebar", URL_HISTCLOSEBOARDVIEW ); break; case 6: if( get_url() != URL_HISTCLOSEIMGVIEW ) CORE::core_set_command( "switch_sidebar", URL_HISTCLOSEIMGVIEW ); break; } } bool BBSListToolBar::slot_scroll_event( GdkEventScroll* event ) { guint direction = event->direction; #ifdef _DEBUG std::cout << "BBSListToolBar::slot_scroll_event dir = " << direction << std::endl; #endif int page = get_admin()->get_current_page(); const int tab_nums = get_admin()->get_tab_nums(); if( direction == GDK_SCROLL_UP ) --page; if( direction == GDK_SCROLL_DOWN ) ++page; if( page < 0 ) page = tab_nums-1; else if( page >= tab_nums ) page = 0; slot_toggle( page ); return true; } void BBSListToolBar::slot_check_update_root() { CORE::core_set_command( "check_update_root", "" ); } void BBSListToolBar::slot_check_update_open_root() { CORE::core_set_command( "check_update_open_root", "" ); } //////////////////////////////////////// EditListToolBar::EditListToolBar() : SKELETON::ToolBar( nullptr ) { EditListToolBar::pack_buttons(); } // // ボタンのパッキング // // virtual void EditListToolBar::pack_buttons() { get_buttonbar().append( *get_tool_search( CORE::COMP_SEARCH_BBSLIST ) ); get_buttonbar().append( *get_button_down_search() ); get_buttonbar().append( *get_button_up_search() ); add_search_control_mode( CONTROL::MODE_BBSLIST ); get_buttonbar().append( *get_button_undo() ); get_buttonbar().append( *get_button_redo() ); get_buttonbar().append( *get_button_close() ); set_relief(); show_all_children(); } jdim-0.7.0/src/bbslist/toolbar.h000066400000000000000000000030331417047150700165120ustar00rootroot00000000000000// ライセンス: GPL2 // ツールバーのクラス #ifndef _BBSLIST_TOOLBAR_H #define _BBSLIST_TOOLBAR_H #include #include "skeleton/toolbar.h" #include "skeleton/jdtoolbar.h" #include "skeleton/toolmenubutton.h" namespace BBSLIST { class BBSListToolBar : public SKELETON::ToolBar { // ラベルバー SKELETON::JDToolbar m_tool_label; Gtk::Label m_label; SKELETON::ToolMenuButton m_button_toggle; Gtk::ToolButton* m_button_check_update_root{}; Gtk::ToolButton* m_button_check_update_open_root{}; Gtk::ToolItem* m_button_stop_check_update{}; public: BBSListToolBar(); ~BBSListToolBar() noexcept; // タブが切り替わった時にDragableNoteBookから呼び出される( Viewの情報を取得する ) void set_view( SKELETON::View * view ) override; protected: void pack_buttons() override; private: void slot_toggle( const int i ); bool slot_scroll_event( GdkEventScroll* event ); void slot_check_update_root(); void slot_check_update_open_root(); }; /////////////////////////////////////// // 編集ウィンドウのツールバー class EditListToolBar : public SKELETON::ToolBar { // ボタン等のシグナルに直接コネクトする friend class EditListWin; public: EditListToolBar(); ~EditListToolBar() noexcept = default; protected: void pack_buttons() override; }; } #endif jdim-0.7.0/src/board/000077500000000000000000000000001417047150700143255ustar00rootroot00000000000000jdim-0.7.0/src/board/Makefile.am000066400000000000000000000006671417047150700163720ustar00rootroot00000000000000noinst_LIBRARIES = libboard.a libboard_a_SOURCES = \ boardadmin.cpp \ boardviewbase.cpp \ boardview.cpp \ boardviewnext.cpp \ boardviewlog.cpp \ boardviewsidebar.cpp \ toolbar.cpp \ preference.cpp noinst_HEADERS = \ boardadmin.h \ boardviewbase.h \ boardview.h \ boardviewnext.h \ boardviewlog.h \ boardviewsidebar.h \ toolbar.h \ columns.h \ preference.h AM_CXXFLAGS = @GTKMM_CFLAGS@ AM_CPPFLAGS = -I$(top_srcdir)/src jdim-0.7.0/src/board/boardadmin.cpp000066400000000000000000000270751417047150700171440ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "boardadmin.h" #include "boardviewnext.h" #include "toolbar.h" #include "dbtree/interface.h" #include "skeleton/admin.h" #include "skeleton/view.h" #include "skeleton/dragnote.h" #include "icons/iconmanager.h" #include "jdlib/jdregex.h" #include "jdlib/miscmsg.h" #include "history/historymanager.h" #include "global.h" #include "type.h" #include "viewfactory.h" #include "sharedbuffer.h" #include "session.h" #include "command.h" #include "dndmanager.h" BOARD::BoardAdmin *instance_boardadmin = nullptr; BOARD::BoardAdmin* BOARD::get_admin() { if( ! instance_boardadmin ) instance_boardadmin = new BOARD::BoardAdmin( URL_BOARDADMIN ); assert( instance_boardadmin ); return instance_boardadmin; } void BOARD::delete_admin() { if( instance_boardadmin ) delete instance_boardadmin; instance_boardadmin = nullptr; } using namespace BOARD; BoardAdmin::BoardAdmin( const std::string& url ) : SKELETON::Admin( url ) { set_use_viewhistory( true ); set_use_switchhistory( true ); get_notebook()->set_dragable( true ); get_notebook()->set_fixtab( false ); if( ! SESSION::get_show_board_tab() ) get_notebook()->set_show_tabs( false ); setup_menu(); } void BoardAdmin::save_session() { Admin::save_session(); // 開いているURLを保存 SESSION::set_board_URLs( get_URLs() ); SESSION::set_board_locked( get_locked() ); SESSION::set_board_switchhistory( get_switchhistory() ); SESSION::set_board_page( get_current_page() ); } // 前回開いていたURLを復元 void BoardAdmin::restore( const bool only_locked ) { int set_page_num = 0; const bool online = SESSION::is_online(); SESSION::set_online( false ); const std::list< std::string >& list_url = SESSION::get_board_URLs(); std::list< std::string >::const_iterator it_url = list_url.begin(); std::list< std::string > list_switchhistory = SESSION::get_board_switchhistory(); std::list< bool > list_locked = SESSION::get_board_locked(); std::list< bool >::iterator it_locked = list_locked.begin(); for( int page = 0; it_url != list_url.end(); ++it_url, ++page ){ // タブのロック状態 bool lock = false; if( it_locked != list_locked.end() ){ if( (*it_locked ) ) lock = true; ++it_locked; } // ロックされているものだけ表示 if( only_locked && ! lock ){ list_switchhistory.remove( *it_url ); continue; } if( page == SESSION::board_page() ) set_page_num = get_tab_nums(); COMMAND_ARGS command_arg = url_to_openarg( *it_url, true, lock ); // 板がDBに登録されていない場合は表示しない if( command_arg.url.empty() && command_arg.arg4 != "SIDEBAR" ){ MISC::ERRMSG( *it_url + " is not registered" ); list_switchhistory.remove( *it_url ); continue; } open_view( command_arg ); } set_switchhistory( list_switchhistory ); SESSION::set_online( online ); if( get_tab_nums() ) set_command( "set_page", std::string(), std::to_string( set_page_num ) ); } COMMAND_ARGS BoardAdmin::url_to_openarg( const std::string& url, const bool tab, const bool lock ) { JDLIB::Regex regex; const size_t offset = 0; const bool icase = false; const bool newline = true; const bool usemigemo = false; const bool wchar = false; COMMAND_ARGS command_arg; command_arg.command = "open_view"; command_arg.url = std::string(); if( tab ) command_arg.arg1 = "true"; // タブで開く command_arg.arg2 = "false"; // 既に開いているかチェック if( lock ) command_arg.arg3 = "lock"; // 開き方のモード ( Admin::open_view 参照 ) #ifdef _DEBUG std::cout << "BoardAdmin::url_to_openarg url = " << url << std::endl; #endif // 次スレ検索 if( regex.exec( std::string( "(.*)" ) + NEXT_SIGN + ARTICLE_SIGN + "(.*)", url, offset, icase, newline, usemigemo, wchar )){ command_arg.url = DBTREE::url_boardbase( regex.str( 1 ) ); command_arg.arg4 = "NEXT"; command_arg.arg5 = regex.str( 2 ); // 前スレのアドレス } // 全ログ一覧 else if( url == URL_ALLLOG ){ command_arg.url = URL_ALLLOG; command_arg.arg4 = "LOG"; } // ログ一覧 else if( regex.exec( std::string( "(.*)" ) + LOG_SIGN, url, offset, icase, newline, usemigemo, wchar )){ command_arg.url = DBTREE::url_boardbase( regex.str( 1 ) ); command_arg.arg4 = "LOG"; } // サイドバー else if( regex.exec( std::string( "(.*)" ) + SIDEBAR_SIGN + "(.*)", url, offset, icase, newline, usemigemo, wchar )){ command_arg.url = DBTREE::url_boardbase( regex.str( 1 ) ); command_arg.arg4 = "SIDEBAR"; command_arg.arg5 = regex.str( 2 ); // ディレクトリID } // スレビュー else{ command_arg.url = DBTREE::url_boardbase( url ); command_arg.arg4 = "MAIN"; } #ifdef _DEBUG std::cout << "open " << command_arg.arg4 << std::endl; #endif return command_arg; } std::string BoardAdmin::command_to_url( const COMMAND_ARGS& command ) { if( command.arg4 == "NEXT" ) return command.url + NEXT_SIGN + ARTICLE_SIGN + command.arg5; else if( command.arg4 == "LOG" ){ if( command.url == URL_ALLLOG ) return URL_ALLLOG; else return command.url + LOG_SIGN; } else if( command.arg4 == "SIDEBAR" ){ if( command.arg5.empty() ) return command.url; return command.url + SIDEBAR_SIGN + command.arg5; } return command.url; } void BoardAdmin::switch_admin() { if( ! has_focus() ) CORE::core_set_command( "switch_board" ); } void BoardAdmin::restore_lasttab() { HISTORY::restore_history( URL_HISTCLOSEBOARDVIEW ); } // // リストで与えられたページをタブで連続して開くとき(Admin::open_list())の引数セット // COMMAND_ARGS BoardAdmin::get_open_list_args( const std::string& url, const COMMAND_ARGS& command_list ) { COMMAND_ARGS command_arg; command_arg.arg4 = "MAIN"; return command_arg; } // // view_modeに該当するページを探す // int BoardAdmin::find_view( const std::string& view_mode ) { if( view_mode.empty() ) return -1; // 検索の基準を、アクティブなタブに仮定 const int page = m_notebook->get_current_page(); const int pages = m_notebook->get_n_pages(); // "boardnext" なら次スレ検索のタブで開く if( view_mode.find( "boardnext" ) != std::string::npos ){ // アクティブな開始タブの右側から順に、すべてのタブをループ int i = page; while( i >= 0 ){ // BoardViewNextクラスのタブを探す BOARD::BoardViewNext* view = dynamic_cast< BOARD::BoardViewNext* >( m_notebook->get_nth_page( i ) ); if( view ){ if( ! view->is_locked() ){ // ページが見つかった return i; } // ロックされていれば次に該当するタブを探す } // 次のタブへインクリメント i++; if( i >= pages ) i = 0; // 右端まで探したら左端から if( i == page ) break; // 一巡しても見つからなかった } } // 該当するタブが見つからない場合 return -1; } // // ツールバー表示 // void BoardAdmin::show_toolbar() { // まだ作成されていない場合は作成する if( ! m_toolbar ){ m_toolbar = std::make_unique(); get_notebook()->append_toolbar( *m_toolbar ); if( SESSION::get_show_board_toolbar() ) m_toolbar->open_buttonbar(); } get_notebook()->show_toolbar(); } // // ツールバー表示/非表示切り替え // void BoardAdmin::toggle_toolbar() { if( ! m_toolbar ) return; #ifdef _DEBUG std::cout << "BoardAdmin::toggle_toolbar\n"; #endif // 検索関係の wiget の位置を変更 m_toolbar->unpack_pack(); if( SESSION::get_show_board_toolbar() ) m_toolbar->open_buttonbar(); else m_toolbar->close_buttonbar(); m_toolbar->close_searchbar(); m_toolbar->show_toolbar(); } // // 検索バー表示 // void BoardAdmin::open_searchbar() { if( ! m_toolbar ) return; // ツールバー表示時は検索関係の wiget はツールバーに表示されている if( ! SESSION::get_show_board_toolbar() ){ m_toolbar->open_searchbar(); m_toolbar->show_toolbar(); } m_toolbar->focus_entry_search(); } // // 検索バー非表示 // void BoardAdmin::close_searchbar() { if( ! m_toolbar ) return; if( ! SESSION::get_show_board_toolbar() ) m_toolbar->close_searchbar(); } SKELETON::View* BoardAdmin::create_view( const COMMAND_ARGS& command ) { int type = CORE::VIEW_NONE; CORE::VIEWFACTORY_ARGS view_args; // メインビュー if( command.arg4 == "MAIN" ){ type = CORE::VIEW_BOARDVIEW; } // 次スレ検索 else if( command.arg4 == "NEXT" ){ type = CORE::VIEW_BOARDNEXT; view_args.arg1 = command.arg5; // 前スレのアドレス } // ログ else if( command.arg4 == "LOG" ){ type = CORE::VIEW_BOARDLOG; } // サイドバー else if( command.arg4 == "SIDEBAR" ){ type = CORE::VIEW_BOARDSIDEBAR; view_args.arg1 = command.arg6; // "set_history" の時は板の履歴に登録する } else return nullptr; SKELETON::View* view = CORE::ViewFactory( type, command_to_url( command ), view_args ); assert( view != nullptr ); return view; } // // ローカルなコマンド // void BoardAdmin::command_local( const COMMAND_ARGS& command ) { // 列項目の更新 if( command.command == "update_columns" ){ std::list< SKELETON::View* > list_view = get_list_view( command.url ); for( SKELETON::View* view : list_view ) { if( view ) view->set_command( "update_columns" ); } } // 指定したスレを強調して表示 else if( command.command == "draw_bg_articles" ){ SKELETON::View* view = get_view( command.url ); if( view ) view->set_command( "draw_bg_articles" ); } // ハイライト解除 else if( command.command == "clear_highlight" ){ SKELETON::View* view = get_view( command.url ); if( view ) view->set_command( "clear_highlight" ); } // URLを選択 else if( command.command == "select_item" ){ SKELETON::View* view = get_current_view(); if( view ) view->set_command( "select_item", command.url ); } } // // タブをサイドバーにドロップした時にお気に入りがデータ送信を要求してきた // void BoardAdmin::slot_drag_data_get( Gtk::SelectionData& selection_data, const int page ) { #ifdef _DEBUG std::cout << "BoardAdmin::slot_drag_data_get page = " << page << std::endl; #endif SKELETON::View* view = ( SKELETON::View* )get_notebook()->get_nth_page( page ); if( ! view ) return; const std::string url = view->get_url(); CORE::DATA_INFO info; info.type = TYPE_BOARD; info.url = DBTREE::url_boardbase( url ); info.name = DBTREE::board_name( info.url ); info.path = Gtk::TreePath( "0" ).to_string(); if( info.url.empty() ) return; #ifdef _DEBUG std::cout << "name = " << info.name << std::endl; #endif CORE::DATA_INFO_LIST list_info; list_info.push_back( info ); CORE::SBUF_set_list( list_info ); selection_data.set( DNDTARGET_FAVORITE, get_url() ); } jdim-0.7.0/src/board/boardadmin.h000066400000000000000000000032321417047150700165760ustar00rootroot00000000000000// ライセンス: GPL2 // // 個別の板の管理クラス // #ifndef _BOARDADMIN_H #define _BOARDADMIN_H #include "skeleton/admin.h" #include "sign.h" #include #include namespace BOARD { class BoardToolBar; class BoardAdmin : public SKELETON::Admin { std::unique_ptr m_toolbar; public: explicit BoardAdmin( const std::string& url ); ~BoardAdmin() = default; void save_session() override; protected: COMMAND_ARGS get_open_list_args( const std::string& url, const COMMAND_ARGS& command_list ) override; SKELETON::View* create_view( const COMMAND_ARGS& command ) override; // view_modeに該当するページを探す int find_view( const std::string& view_mode ) override; // ツールバー void show_toolbar() override; void toggle_toolbar() override; void open_searchbar() override; void close_searchbar() override; void command_local( const COMMAND_ARGS& command ) override; void restore( const bool only_locked ) override; COMMAND_ARGS url_to_openarg( const std::string& url, const bool tab, const bool lock ) override; std::string command_to_url( const COMMAND_ARGS& command ) override; void switch_admin() override; void restore_lasttab() override; private: // タブをお気に入りにドロップした時にお気に入りがデータ送信を要求してきた void slot_drag_data_get( Gtk::SelectionData& selection_data, const int page ) override; }; BoardAdmin* get_admin(); void delete_admin(); } #endif jdim-0.7.0/src/board/boardview.cpp000066400000000000000000000104071417047150700170150ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "boardadmin.h" #include "boardview.h" #include "dbtree/interface.h" #include "skeleton/msgdiag.h" #include "sound/soundmanager.h" #include "history/historymanager.h" #include "config/globalconf.h" #include "session.h" #include "command.h" #include "httpcode.h" #include "global.h" using namespace BOARD; BoardView::BoardView( const std::string& url ) : BoardViewBase( url, false ) { #ifdef _DEBUG std::cout << "BoardView::BoardView : url = " << get_url() << std::endl; #endif // オートリロード可 set_enable_autoreload( true ); // text/url-list のドロップ可 get_treeview().set_enable_drop_uri_list(); } BoardView::~BoardView() { #ifdef _DEBUG std::cout << "BoardView::~BoardView : url = " << get_url() << std::endl; #endif // 閉じたタブ履歴更新 HISTORY::append_history( URL_HISTCLOSEBOARDVIEW, DBTREE::url_boardbase( get_url() ), DBTREE::board_name( get_url() ), TYPE_BOARD ); if( ! SESSION::is_quitting() ) BoardView::save_session(); } void BoardView::save_session() { DBTREE::board_save_info( get_url_board() ); } // 更新した bool BoardView::is_updated() const { const int status = DBTREE::board_status( get_url_board() ); #ifdef _DEBUG std::cout << "BoardView::is_updated " << ( status & STATUS_UPDATED ) << std::endl; #endif return ( status & STATUS_UPDATED ); } // 更新チェックして更新可能か bool BoardView::is_check_update() const { const int status = DBTREE::board_status( get_url_board() ); #ifdef _DEBUG std::cout << "BoardView::is_check_update status = " << ( status & STATUS_UPDATE ) << std::endl; #endif return ( status & STATUS_UPDATE ); } // // リロード // void BoardView::reload() { // オフライン if( ! SESSION::is_online() ){ SKELETON::MsgDiag mdiag( get_parent_win(), "オフラインです" ); mdiag.run(); return; } show_view(); } // // ビュー表示 // void BoardView::show_view() { #ifdef _DEBUG std::cout << "BoardView::show_view " << get_url() << std::endl; #endif BoardViewBase::show_view(); // ロード中にキャッシュにあるスレ一覧を表示する if( CONFIG::get_show_cached_board() && SESSION::is_online() && ! get_row_size() ){ const std::vector< DBTREE::ArticleBase* >& list_article = DBTREE::board_list_subject( get_url_board() ); if( list_article.size() ){ #ifdef _DEBUG std::cout << "append rows\n"; #endif const bool loading_fin = false; update_view_impl( list_article, loading_fin ); } } } // // view更新 // // subject.txtのロードが終わったら呼ばれる // void BoardView::update_view() { const int code = DBTREE::board_code( get_url_board() ); #ifdef _DEBUG std::cout << "BoardView::update_view " << get_url() << " code = " << code << std::endl; #endif // 音を鳴らす if( SESSION::is_online() && code != HTTP_INIT ){ if( code == HTTP_OK ) SOUND::play( SOUND::SOUND_RES ); else if( code == HTTP_NOT_MODIFIED ) SOUND::play( SOUND::SOUND_NO ); else SOUND::play( SOUND::SOUND_ERR ); } // 高速化のためデータベースに直接アクセス const std::vector< DBTREE::ArticleBase* >& list_article = DBTREE::board_list_subject( get_url_board() ); const bool loading_fin = true; update_view_impl( list_article, loading_fin ); // dat落ちしたスレッドをスレあぼーんのリストから取り除く if( code == HTTP_OK ) DBTREE::remove_old_abone_thread( get_url_board() ); // 板の履歴に登録 HISTORY::append_history( URL_HISTBOARDVIEW, DBTREE::url_boardbase( get_url_board() ), DBTREE::board_name( get_url_board() ), TYPE_BOARD ); } // // 板名更新 // void BoardView::update_boardname() { // ウィンドウタイトル表示 set_title( DBTREE::board_name( get_url_board() ) ); BOARD::get_admin()->set_command( "set_title", get_url(), get_title() ); // タブに名前をセット BOARD::get_admin()->set_command( "set_tablabel", get_url(), DBTREE::board_name( get_url_board() ) ); } jdim-0.7.0/src/board/boardview.h000066400000000000000000000012111417047150700164530ustar00rootroot00000000000000// ライセンス: GPL2 // スレ一覧 メインビュー #ifndef _BOARDVIEW_H #define _BOARDVIEW_H #include "boardviewbase.h" namespace BOARD { class BoardView : public BOARD::BoardViewBase { public: explicit BoardView( const std::string& url ); ~BoardView(); // SKELETON::View の関数のオーバロード void save_session() override; bool is_updated() const override; bool is_check_update() const override; void reload() override; void show_view() override; void update_view() override; void update_boardname() override; }; } #endif jdim-0.7.0/src/board/boardviewbase.cpp000066400000000000000000002567451417047150700176710ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "gtkmmversion.h" #include "boardadmin.h" #include "boardview.h" #include "skeleton/msgdiag.h" #include "skeleton/filediag.h" #include "jdlib/miscutil.h" #include "jdlib/miscgtk.h" #include "jdlib/jdregex.h" #include "dbtree/interface.h" #include "dbtree/articlebase.h" #include "config/globalconf.h" #include "control/controlid.h" #include "control/controlutil.h" #include "command.h" #include "cache.h" #include "session.h" #include "sharedbuffer.h" #include "global.h" #include "type.h" #include "prefdiagfactory.h" #include "httpcode.h" #include "colorid.h" #include "fontid.h" #include "compmanager.h" #include "dndmanager.h" #include "icons/iconmanager.h" #include using namespace BOARD; // row -> path #define GET_PATH( row ) m_liststore->get_path( row ) enum{ CANCEL_OPENROW = 500, // msec 連続クリック防止用カウンタ DEFAULT_COLMUN_WIDTH = 50 }; enum{ COL_MARKVAL_OLD = -2, // dat 落ち COL_MARKVAL_FINISHED = -1, // キャッシュあり、新着無し、規定スレ数を越えている COL_MARKVAL_NORMAL = 0, // 通常状態、キャッシュ無し、 COL_MARKVAL_924, // スレッド924、キャッシュ無し COL_MARKVAL_NEWTHREAD_HOUR, // 新スレ( CONFIG::get_newthread_hour 時間以内 )、キャッシュ無し COL_MARKVAL_NEWTHREAD, // 前回の板一覧読み込み時から新しく出来たスレ、キャッシュ無し COL_MARKVAL_CACHED, // キャッシュあり、新着無し COL_MARKVAL_BROKEN_SUBJECT, // キャッシュあり、新着無しだが subject.txt が壊れている可能性がある COL_MARKVAL_UPDATED, // キャッシュあり、新着有り COL_MARKVAL_BKMARKED, // ブックマークされている、新着無し COL_MARKVAL_BKMARKED_BROKEN_SUBJECT, // ブックマークされている、新着無しだが subject.txt が壊れている可能性がある COL_MARKVAL_BKMARKED_UPDATED // ブックマークされている、新着有り }; // 昇順で上か下か enum{ COL_A_UP = -1, COL_B_UP = 1, }; enum { BOOKMARK_AUTO = -1, BOOKMARK_UNSET = 0, BOOKMARK_SET = 1 }; #define DELETE_COLUMN(col) do{ if( ! col ) { delete col; col = nullptr; } }while(0) // set_sizing( Gtk::TREE_VIEW_COLUMN_FIXED ) を指定して append_columnする #define APPEND_COLUMN(col,title,model) do{ \ col = Gtk::manage( new Gtk::TreeViewColumn( title, model ) ); \ col->set_sizing( Gtk::TREE_VIEW_COLUMN_FIXED ); \ m_treeview.append_column( *col ); \ }while(0) BoardViewBase::BoardViewBase( const std::string& url, const bool show_col_board ) : SKELETON::View( url ) , m_treeview( url, DNDTARGET_FAVORITE, true, CONFIG::get_fontname( FONT_BOARD ), COLOR_CHAR_BOARD, COLOR_BACK_BOARD, COLOR_BACK_BOARD_EVEN ) , m_col( COL_NUM_COL ) , m_previous_col( COL_NUM_COL ) , m_sortmode( SORTMODE_ASCEND ) , m_enable_menuslot( true ) , m_load_subject_txt( true ) , m_show_col_board( show_col_board ) { // 次スレ検索ビューのようにURLの途中に http が入っている場合は取り除く size_t pos = url.rfind( "http://" ); if( pos == std::string::npos || pos == 0 ) pos = url.rfind( "https://" ); if( pos != std::string::npos && pos != 0 ) m_url_board = DBTREE::url_boardbase( url.substr( 0, pos ) ); else m_url_board = DBTREE::url_boardbase( url ); m_scrwin.add( m_treeview ); m_scrwin.set_policy( Gtk::POLICY_AUTOMATIC, Gtk::POLICY_ALWAYS ); pack_start( m_scrwin ); show_all_children(); // ツリービュー設定 m_liststore = Gtk::ListStore::create( m_columns ); // セルを固定の高さにする // append_column する前に columnに対して set_sizing( Gtk::TREE_VIEW_COLUMN_FIXED ) すること m_treeview.set_fixed_height_mode( true ); #ifdef _DEBUG std::cout << "BoardViewBase::BoardViewBase : m_treeview.set_fixed_height_mode\n"; #endif // 列のappend update_columns(); // ソート関数セット m_liststore->set_sort_func( COL_MARK, sigc::mem_fun( *this, &BoardViewBase::slot_compare_row ) ); m_liststore->set_sort_func( COL_ID, sigc::mem_fun( *this, &BoardViewBase::slot_compare_row ) ); m_liststore->set_sort_func( COL_BOARD, sigc::mem_fun( *this, &BoardViewBase::slot_compare_row ) ); m_liststore->set_sort_func( COL_SUBJECT, sigc::mem_fun( *this, &BoardViewBase::slot_compare_row ) ); m_liststore->set_sort_func( COL_RES, sigc::mem_fun( *this, &BoardViewBase::slot_compare_row ) ); m_liststore->set_sort_func( COL_STR_LOAD, sigc::mem_fun( *this, &BoardViewBase::slot_compare_row ) ); m_liststore->set_sort_func( COL_STR_NEW, sigc::mem_fun( *this, &BoardViewBase::slot_compare_row ) ); m_liststore->set_sort_func( COL_SINCE, sigc::mem_fun( *this, &BoardViewBase::slot_compare_row ) ); m_liststore->set_sort_func( COL_WRITE, sigc::mem_fun( *this, &BoardViewBase::slot_compare_row ) ); m_liststore->set_sort_func( COL_ACCESS, sigc::mem_fun( *this, &BoardViewBase::slot_compare_row ) ); m_liststore->set_sort_func( COL_SPEED, sigc::mem_fun( *this, &BoardViewBase::slot_compare_row ) ); m_liststore->set_sort_func( COL_DIFF, sigc::mem_fun( *this, &BoardViewBase::slot_compare_row ) ); m_treeview.add_events( Gdk::SMOOTH_SCROLL_MASK ); m_treeview.sig_button_press().connect( sigc::mem_fun(*this, &BoardViewBase::slot_button_press ) ); m_treeview.sig_button_release().connect( sigc::mem_fun(*this, &BoardViewBase::slot_button_release ) ); m_treeview.sig_motion_notify().connect( sigc::mem_fun(*this, &BoardViewBase::slot_motion_notify ) ); m_treeview.sig_key_press().connect( sigc::mem_fun(*this, &BoardViewBase::slot_key_press ) ); m_treeview.sig_key_release().connect( sigc::mem_fun(*this, &BoardViewBase::slot_key_release ) ); m_treeview.sig_scroll_event().connect( sigc::mem_fun(*this, &BoardViewBase::slot_scroll_event ) ); m_treeview.set_has_tooltip( true ); m_treeview.signal_query_tooltip().connect( sigc::mem_fun(*this, &BoardViewBase::slot_query_tooltip) ); m_treeview.signal_drag_data_get().connect( sigc::mem_fun(*this, &BoardViewBase::slot_drag_data_get ) ); m_treeview.sig_dropped_uri_list().connect( sigc::mem_fun(*this, &BoardViewBase::slot_dropped_url_list ) ); // アクション初期化 setup_action(); // マウスジェスチャ可能 set_enable_mg( true ); // コントロールモード設定 get_control().add_mode( CONTROL::MODE_BOARD ); } BoardViewBase::~BoardViewBase() noexcept { #ifdef _DEBUG std::cout << "BoardViewBase::~BoardViewBase : " << get_url() << std::endl; #endif } SKELETON::Admin* BoardViewBase::get_admin() { return BOARD::get_admin(); } // // url 更新 // // 移転があったときなどにadminから呼び出される // void BoardViewBase::update_url( const std::string& url_old, const std::string& url_new ) { if( m_url_board.rfind( url_old, 0 ) == 0 ) m_url_board = url_new + m_url_board.substr( url_old.length() ); SKELETON::View::update_url( url_old, url_new ); } // アイコンのID取得 int BoardViewBase::get_icon( const std::string& iconname ) const { int id = ICON::NONE; if( iconname == "default" ) id = ICON::BOARD; if( iconname == "loading" ) id = ICON::LOADING; if( iconname == "loading_stop" ) id = ICON::LOADING_STOP; if( iconname == "update" ) id = ICON::BOARD_UPDATE; // 更新チェックして更新があった場合 if( iconname == "updated" ) id = ICON::BOARD_UPDATED; #ifdef _DEBUG std::cout << "BoardViewBase::get_icon : " << iconname << " url = " << get_url() << std::endl; #endif return id; } // // コピー用URL(メインウィンドウのURLバーなどに表示する) // std::string BoardViewBase::url_for_copy() const { return get_url_board(); } // // アクション初期化 // void BoardViewBase::setup_action() { #ifdef _DEBUG std::cout << "BoardViewBase::setup_action\n"; #endif // アクショングループを作ってUIマネージャに登録 action_group() = Gtk::ActionGroup::create(); action_group()->add( Gtk::Action::create( "BookMark", ITEM_NAME_BOOKMARK "(_B)" ), sigc::bind< int >( sigc::mem_fun( *this, &BoardViewBase::slot_bookmark ), BOOKMARK_AUTO ) ); action_group()->add( Gtk::Action::create( "SetBookMark", "しおりを設定(_S)" ), // 未使用 sigc::bind< int >( sigc::mem_fun( *this, &BoardViewBase::slot_bookmark ), BOOKMARK_SET ) ); action_group()->add( Gtk::Action::create( "UnsetBookMark", "しおりを解除(_U)" ), // 未使用 sigc::bind< int >( sigc::mem_fun( *this, &BoardViewBase::slot_bookmark ), BOOKMARK_UNSET ) ); action_group()->add( Gtk::Action::create( "OpenTab", "OpenArticleTab" ), sigc::mem_fun( *this, &BoardViewBase::slot_open_tab ) ); action_group()->add( Gtk::Action::create( "RegetArticle", ITEM_NAME_REGETARTICLE "(_R)" ), sigc::mem_fun( *this, &BoardViewBase::slot_reget_article ) ); action_group()->add( Gtk::Action::create( "Favorite_Article", ITEM_NAME_FAVORITE_ARTICLE "(_F)..." ), sigc::mem_fun( *this, &BoardViewBase::slot_favorite_thread ) ); action_group()->add( Gtk::Action::create( "Favorite_Board", "板をお気に入りに追加(_A)" ), sigc::mem_fun( *this, &BoardViewBase::slot_favorite_board ) ); action_group()->add( Gtk::Action::create( "GotoTop", "一番上に移動(_T)" ), sigc::mem_fun( *this, &BoardViewBase::goto_top ) ); action_group()->add( Gtk::Action::create( "GotoBottom", "一番下に移動(_M)" ), sigc::mem_fun( *this, &BoardViewBase::goto_bottom ) ); action_group()->add( Gtk::Action::create( "Delete_Menu", "Delete" ) ); action_group()->add( Gtk::Action::create( "Delete", "選択した行のログを削除する(_D)" ), sigc::mem_fun( *this, &BoardViewBase::slot_delete_logs ) ); action_group()->add( Gtk::Action::create( "OpenRows", "選択したスレを開く(_O)" ), sigc::bind< bool >( sigc::mem_fun( *this, &BoardViewBase::open_selected_rows ), false ) ); action_group()->add( Gtk::Action::create( "RegetRows", "スレ情報を消さずにスレを再取得(_R)" ), sigc::bind< bool >( sigc::mem_fun( *this, &BoardViewBase::open_selected_rows ), true ) ); action_group()->add( Gtk::Action::create( "CopyURL", ITEM_NAME_COPY_URL "(_U)" ), sigc::mem_fun( *this, &BoardViewBase::slot_copy_url ) ); action_group()->add( Gtk::Action::create( "CopyTitleURL", ITEM_NAME_COPY_TITLE_URL "(_L)" ), sigc::mem_fun( *this, &BoardViewBase::slot_copy_title_url ) ); action_group()->add( Gtk::Action::create( "OpenBrowser", ITEM_NAME_OPEN_BROWSER "(_W)" ), sigc::mem_fun( *this, &BoardViewBase::slot_open_browser ) ); action_group()->add( Gtk::Action::create( "AboneThread", ITEM_NAME_ABONE_ARTICLE "(_N)" ), sigc::mem_fun( *this, &BoardViewBase::slot_abone_thread ) ); action_group()->add( Gtk::Action::create( "PreferenceArticle", ITEM_NAME_PREF_THREAD "(_P)..." ), sigc::mem_fun( *this, &BoardViewBase::slot_preferences_article ) ); action_group()->add( Gtk::Action::create( "PreferenceBoard", "PreferenceBoard" ), sigc::mem_fun( *this, &BoardViewBase::show_preference ) ); action_group()->add( Gtk::Action::create( "SaveDat", "SaveDat" ), sigc::mem_fun( *this, &BoardViewBase::slot_save_dat ) ); action_group()->add( Gtk::Action::create( "SearchNextArticle", ITEM_NAME_NEXTARTICLE ), sigc::mem_fun( *this, &BoardViewBase::slot_search_next ) ); // その他 action_group()->add( Gtk::Action::create( "Etc_Menu", ITEM_NAME_ETC "(_O)" ) ); ui_manager().reset(); ui_manager() = Gtk::UIManager::create(); ui_manager()->insert_action_group( action_group() ); // 通常 + 複数 const std::string menu_mul = "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""; // お気に入りボタン押した時のメニュー const std::string menu_favorite = "" "" "" ""; // お気に入りボタン押した時のメニュー( スレのみ ) const std::string menu_favorite_article = "" "" ""; // 削除ボタン押した時のメニュー const std::string menu_delete = "" "" ""; ui_manager()->add_ui_from_string( "" + menu_mul + menu_favorite + menu_favorite_article + menu_delete + create_context_menu() + "" ); // ポップアップメニューにキーアクセレータを表示 Gtk::Menu* popupmenu = dynamic_cast< Gtk::Menu* >( ui_manager()->get_widget( "/popup_menu" ) ); CONTROL::set_menu_motion( popupmenu ); popupmenu = dynamic_cast< Gtk::Menu* >( ui_manager()->get_widget( "/popup_menu_mul" ) ); CONTROL::set_menu_motion( popupmenu ); } // // 通常の右クリックメニューの作成 // std::string BoardViewBase::create_context_menu() const { std::list< int > list_menu; list_menu.push_back( ITEM_BOOKMARK ); list_menu.push_back( ITEM_OPENARTICLETAB ); list_menu.push_back( ITEM_REGETARTICLE ); list_menu.push_back( ITEM_OPEN_BROWSER ); list_menu.push_back( ITEM_COPY_URL ); list_menu.push_back( ITEM_COPY_TITLE_URL_THREAD ); list_menu.push_back( ITEM_SAVE_DAT ); list_menu.push_back( ITEM_FAVORITE_ARTICLE ); list_menu.push_back( ITEM_NEXTARTICLE ); list_menu.push_back( ITEM_ABONE_ARTICLE ); list_menu.push_back( ITEM_DELETE ); list_menu.push_back( ITEM_PREF_THREAD ); list_menu.push_back( ITEM_PREF_BOARD ); // メニューに含まれていない項目を抜き出して「その他」に含める int num = 0; for(;;){ const int item = SESSION::get_item_board_menu( num ); if( item == ITEM_END ) break; list_menu.remove( item ); ++num; } std::string menu; num = 0; for(;;){ const int item = SESSION::get_item_board_menu( num ); if( item == ITEM_END ) break; else if( item == ITEM_ETC && list_menu.size() ){ menu.append( "" ); for( const int etc_item : list_menu ) menu.append( get_menu_item( etc_item ) ); menu.append( "" ); } else menu += get_menu_item( item ); ++num; } #ifdef _DEBUG std::cout << "menu = " << menu << std::endl; #endif return "" + menu + ""; } const char* BoardViewBase::get_menu_item( const int item ) const { switch( item ){ // しおりを設定/解除 case ITEM_BOOKMARK: return ""; // タブでスレを開く case ITEM_OPENARTICLETAB: return ""; // スレ情報を消さずに再取得" case ITEM_REGETARTICLE: return ""; // リンクをブラウザで開く case ITEM_OPEN_BROWSER: return ""; // リンクのURLをコピー case ITEM_COPY_URL: return ""; // スレのタイトルとURLをコピー case ITEM_COPY_TITLE_URL_THREAD: return ""; // dat 保存 case ITEM_SAVE_DAT: return ""; // スレをお気に入りに追加 case ITEM_FAVORITE_ARTICLE: return ""; // 次スレ検索 case ITEM_NEXTARTICLE: return ""; // スレをあぼ〜んする" case ITEM_ABONE_ARTICLE: return ""; // 削除 case ITEM_DELETE: return "" "" ""; // スレのプロパティ case ITEM_PREF_THREAD: return ""; // 板のプロパティ case ITEM_PREF_BOARD: return ""; // 区切り case ITEM_SEPARATOR: return ""; } return ""; } // // 行数 // int BoardViewBase::get_row_size() const { return m_treeview.get_row_size(); } // // 自動ソート抑制 // -2 = DEFAULT_UNSORTED_COLUMN_ID // // 追加や更新などで列に値をセットする前に実行してしておかないと // いちいちソートをかけるので極端に遅くなる // void BoardViewBase::unsorted_column() { m_liststore->set_sort_column( Gtk::TreeSortable::DEFAULT_UNSORTED_COLUMN_ID, Gtk::SortType::SORT_ASCENDING ); } // // url から row を取得 // Gtk::TreeModel::Row BoardViewBase::get_row_from_url( const std::string& url ) const { Gtk::TreeModel::Children child = m_liststore->children(); for( Gtk::TreeModel::Row row : child ) { DBTREE::ArticleBase *art = row[ m_columns.m_col_article ]; if( url == art->get_url() ) return row; } return Gtk::TreeModel::Row(); } // // 列項目の更新 // void BoardViewBase::update_columns() { m_treeview.remove_all_columns(); DELETE_COLUMN( m_col_id ); DELETE_COLUMN( m_col_board ); DELETE_COLUMN( m_col_subject ); DELETE_COLUMN( m_col_res ); DELETE_COLUMN( m_col_str_load ); DELETE_COLUMN( m_col_str_new ); DELETE_COLUMN( m_col_since ); DELETE_COLUMN( m_col_write ); DELETE_COLUMN( m_col_speed ); DELETE_COLUMN( m_col_diff ); DELETE_COLUMN( m_col_access ); int num = 0; // 先頭に「板」列を追加 if( m_show_col_board ){ bool append_board = true; for(;;){ const int item = SESSION::get_item_board_col( num ); if( item == ITEM_BOARD ) append_board = false; if( item == ITEM_END ) break; num++; } if( append_board ) APPEND_COLUMN( m_col_board, ITEM_NAME_BOARD, m_columns.m_col_board ); } m_col_diff_is_shown = false; num = 0; for(;;){ const int item = SESSION::get_item_board_col( num ); if( item == ITEM_END ) break; switch( item ){ case ITEM_MARK: APPEND_COLUMN( m_col_mark, ITEM_NAME_MARK, m_columns.m_col_mark ); break; case ITEM_ID: APPEND_COLUMN( m_col_id, ITEM_NAME_ID, m_columns.m_col_id ); break; case ITEM_BOARD: APPEND_COLUMN( m_col_board, ITEM_NAME_BOARD, m_columns.m_col_board ); break; case ITEM_NAME: APPEND_COLUMN( m_col_subject, ITEM_NAME_NAME, m_columns.m_col_subject ); break; case ITEM_RES: APPEND_COLUMN( m_col_res, ITEM_NAME_RES, m_columns.m_col_res ); break; case ITEM_LOAD: APPEND_COLUMN( m_col_str_load, ITEM_NAME_LOAD, m_columns.m_col_str_load ); break; case ITEM_NEW: APPEND_COLUMN( m_col_str_new, ITEM_NAME_NEW, m_columns.m_col_str_new ); break; case ITEM_SINCE: APPEND_COLUMN( m_col_since, ITEM_NAME_SINCE, m_columns.m_col_since ); break; case ITEM_LASTWRITE: APPEND_COLUMN( m_col_write, ITEM_NAME_LASTWRITE, m_columns.m_col_write ); break; case ITEM_ACCESS: APPEND_COLUMN( m_col_access, ITEM_NAME_ACCESS, m_columns.m_col_access); break; case ITEM_SPEED: APPEND_COLUMN( m_col_speed, ITEM_NAME_SPEED, m_columns.m_col_speed ); break; case ITEM_DIFF: APPEND_COLUMN( m_col_diff, ITEM_NAME_DIFF, m_columns.m_col_diff); m_col_diff_is_shown = true; break; } ++num; } // 高さを取得するためのcolumn番号をリセット m_treeview.set_column_for_height( 0 ); // サイズを調整しつつソートの設定 for( guint i = 0; i < COL_VISIBLE_END; i++ ){ const int id = get_title_id( i ); if( id < 0 ) continue; Gtk::TreeView::Column* column = m_treeview.get_column( i ); if( ! column ) continue; int width = 0; switch( id ){ case COL_MARK: width = SESSION::col_mark(); break; case COL_ID: width = SESSION::col_id(); break; case COL_BOARD: width = SESSION::col_board(); break; case COL_SUBJECT: width = SESSION::col_subject(); m_treeview.set_column_for_height( i ); break; case COL_RES: width = SESSION::col_number(); break; case COL_STR_LOAD: width = SESSION::col_load(); break; case COL_STR_NEW: width = SESSION::col_new(); break; case COL_SINCE: width = SESSION::col_since(); break; case COL_WRITE: width = SESSION::col_write(); break; case COL_ACCESS: width = SESSION::col_access(); break; case COL_SPEED: width = SESSION::col_speed(); break; case COL_DIFF: width = SESSION::col_diff(); break; } if( ! width ) width = DEFAULT_COLMUN_WIDTH; column->set_fixed_width( width ); column->set_resizable( true ); column->set_clickable( true ); // ヘッダをクリックしたときに呼ぶslot column->signal_clicked().connect( sigc::bind< int >( sigc::mem_fun( *this, &BoardViewBase::slot_col_clicked ), id ) ); // 列の幅が変わったらセッション情報に記憶する column->connect_property_changed( "width", [this]{ save_column_width(); } ); // ヘッダの位置 switch( id ){ case COL_MARK: column->set_alignment( 0.5 ); break; case COL_ID: case COL_RES: case COL_STR_LOAD: case COL_STR_NEW: case COL_SPEED: case COL_DIFF: column->set_alignment( 1.0 ); break; default: column->set_alignment( 0.0 ); break; } Gtk::CellRenderer *cell = column->get_first_cell(); // 実際の描画の際に cellrendere のプロパティをセットするスロット関数 if( cell ) column->set_cell_data_func( *cell, sigc::mem_fun( *this, &BoardViewBase::slot_cell_data ) ); Gtk::CellRendererText* rentext = dynamic_cast< Gtk::CellRendererText* >( cell ); if( rentext ){ // 列間スペース rentext->property_xpad() = 4; // 行間スペース rentext->property_ypad() = CONFIG::get_tree_ypad();; // 文字位置 switch( id ){ case COL_ID: case COL_RES: case COL_STR_LOAD: case COL_STR_NEW: case COL_SPEED: case COL_DIFF: rentext->property_xalign() = 1.0; break; default: rentext->property_xalign() = 0.0; } } } } // // i列目のIDを取得 // // 失敗の時は-1を変えす // int BoardViewBase::get_title_id( const int col ) const { const Gtk::TreeView::Column* column = m_treeview.get_column( col ); if( ! column ) return -1; const std::string title = column->get_title(); int id = -1; if( title == ITEM_NAME_MARK ) id = COL_MARK; else if( title == ITEM_NAME_ID ) id = COL_ID; else if( title == ITEM_NAME_BOARD ) id = COL_BOARD; else if( title == ITEM_NAME_NAME ) id = COL_SUBJECT; else if( title == ITEM_NAME_RES ) id = COL_RES; else if( title == ITEM_NAME_LOAD ) id = COL_STR_LOAD; else if( title == ITEM_NAME_NEW ) id = COL_STR_NEW; else if( title == ITEM_NAME_SINCE ) id = COL_SINCE; else if( title == ITEM_NAME_LASTWRITE ) id = COL_WRITE; else if( title == ITEM_NAME_ACCESS ) id = COL_ACCESS; else if( title == ITEM_NAME_SPEED ) id = COL_SPEED; else if( title == ITEM_NAME_DIFF ) id = COL_DIFF; return id; } // // ソート列やソートモードの保存 // void BoardViewBase::save_sort_columns() { #ifdef _DEBUG std::cout << "BoardViewBase::save_sort_columns : url = " << get_url() << std::endl; #endif DBTREE::board_set_view_sort_column( get_url_board(), m_col ); DBTREE::board_set_view_sort_mode( get_url_board(), m_sortmode ); DBTREE::board_set_view_sort_pre_column( get_url_board(), m_previous_col ); DBTREE::board_set_view_sort_pre_mode( get_url_board(), m_previous_sortmode ); } // // 列の幅の保存 // void BoardViewBase::save_column_width() { #ifdef _DEBUG std::cout << "save_column_width " << get_url() << std::endl; #endif for( guint i = 0; i < COL_VISIBLE_END; i++ ){ const int id = get_title_id( i ); if( id < 0 ) continue; Gtk::TreeView::Column* column = m_treeview.get_column( i ); int width = 0; if( column ) width = column->get_width(); if( ! width ) continue; switch( id ){ case COL_MARK: SESSION::set_col_mark( width ); break; case COL_ID: SESSION::set_col_id( width ); break; case COL_BOARD: SESSION::set_col_board( width ); break; case COL_SUBJECT: SESSION::set_col_subject( width ); break; case COL_RES: SESSION::set_col_number( width ); break; case COL_STR_LOAD: SESSION::set_col_load( width ); break; case COL_STR_NEW: SESSION::set_col_new( width ); break; case COL_SINCE: SESSION::set_col_since( width ); break; case COL_WRITE: SESSION::set_col_write( width ); break; case COL_ACCESS: SESSION::set_col_access( width ); break; case COL_SPEED: SESSION::set_col_speed( width ); break; case COL_DIFF: SESSION::set_col_diff( width ); break; } } } // // 実際の描画の際に cellrendere のプロパティをセットするスロット関数 // void BoardViewBase::slot_cell_data( Gtk::CellRenderer* cell, const Gtk::TreeModel::iterator& it ) { Gtk::TreeModel::Row row = *it; // ハイライト色 ( 抽出状態 ) if( row[ m_columns.m_col_drawbg ] ){ cell->property_cell_background() = CONFIG::get_color( COLOR_BACK_HIGHLIGHT_TREE ); cell->property_cell_background_set() = true; } else m_treeview.slot_cell_data( cell, it ); } // // ソート実行 // void BoardViewBase::exec_sort() { #ifdef _DEBUG std::cout << "BoardViewBase::exec_sort url = " << get_url() << std::endl; #endif if( m_col < 0 || m_col >= COL_VISIBLE_END ){ m_col = COL_ID; m_sortmode = SORTMODE_ASCEND; } m_liststore->set_sort_column( -2, Gtk::SORT_ASCENDING ); m_liststore->set_sort_column( m_col, Gtk::SORT_ASCENDING ); } // // ソート状態回復 // void BoardViewBase::restore_sort() { m_search_invert = false; m_col = get_default_sort_column(); m_sortmode = get_default_view_sort_mode(); m_previous_col = get_default_view_sort_pre_column(); m_previous_sortmode = get_default_view_sort_pre_mode(); if( get_row_size() ){ exec_sort(); goto_top(); } } // // デフォルトのソート状態 // int BoardViewBase::get_default_sort_column() const { return DBTREE::board_view_sort_column( get_url_board() ); } int BoardViewBase::get_default_view_sort_mode() const { return DBTREE::board_view_sort_mode( get_url_board() ); } int BoardViewBase::get_default_view_sort_pre_column() const { return DBTREE::board_view_sort_pre_column( get_url_board() ); } int BoardViewBase::get_default_view_sort_pre_mode() const { return DBTREE::board_view_sort_pre_mode( get_url_board() ); } // // ヘッダをクリックしたときのslot関数 // void BoardViewBase::slot_col_clicked( const int col ) { #ifdef _DEBUG std::cout << "BoardViewBase::slot_col_clicked col = " << col << std::endl; #endif if( m_col != col ){ // 前回クリックした列と違う列をクリックした m_previous_col = m_col; m_previous_sortmode = m_sortmode; m_col = col; if( m_col == COL_MARK ) m_sortmode = SORTMODE_MARK1; else if( m_col == COL_ID ) m_sortmode = SORTMODE_ASCEND; else if( m_col == COL_SUBJECT ) m_sortmode = SORTMODE_ASCEND; else m_sortmode = SORTMODE_DESCEND; } else if( m_sortmode == SORTMODE_DESCEND ) m_sortmode = SORTMODE_ASCEND; else if( m_sortmode == SORTMODE_ASCEND ) m_sortmode = SORTMODE_DESCEND; else if( m_sortmode == SORTMODE_MARK1 ) m_sortmode = SORTMODE_MARK2; else if( m_sortmode == SORTMODE_MARK2 ) m_sortmode = SORTMODE_MARK3; else if( m_sortmode == SORTMODE_MARK3 ) m_sortmode = SORTMODE_MARK4; else if( m_sortmode == SORTMODE_MARK4 ) m_sortmode = SORTMODE_MARK1; if( m_col == COL_MARK ){ std::string info; if( m_sortmode == SORTMODE_MARK1 ) info = "モード 1"; if( m_sortmode == SORTMODE_MARK2 ) info = "モード 2"; if( m_sortmode == SORTMODE_MARK3 ) info = "モード 3"; if( m_sortmode == SORTMODE_MARK4 ) info = "モード 4"; CORE::core_set_command( "set_info", "", info ); } /* そろそろ消しても良い? 問題があれば戻す // 旧バージョンとの互換性のため if( m_col == COL_MARK ){ if( m_sortmode == SORTMODE_DESCEND || m_sortmode == SORTMODE_ASCEND ) m_sortmode = SORTMODE_MARK1; } */ save_sort_columns(); exec_sort(); focus_view(); } // // 抽出状態で比較 // // row_a が上か row_b が上かを返す。同じ状態なら 0 // int BoardViewBase::compare_drawbg( const Gtk::TreeModel::Row& row_a, const Gtk::TreeModel::Row& row_b ) const { const bool draw_a = row_a[ m_columns.m_col_drawbg ]; const bool draw_b = row_b[ m_columns.m_col_drawbg ]; if( draw_a && ! draw_b ) return COL_A_UP; else if( draw_b && ! draw_a ) return COL_B_UP; return 0; } // // 列の値によるソート // // row_a が上か row_b が上かを返す。同じなら 0 // int BoardViewBase::compare_col( const int col, const int sortmode, const Gtk::TreeModel::Row& row_a, const Gtk::TreeModel::Row& row_b ) const { int num_a = 0, num_b = 0; int ret = 0; DBTREE::ArticleBase *arta, *artb; const int UP = 1; const int DOWN = 2; switch( col ){ case COL_MARK: { num_a = row_a[ m_columns.m_col_mark_val ]; num_b = row_b[ m_columns.m_col_mark_val ]; if( sortmode == SORTMODE_MARK2 ){ // 新着をキャッシュの上に if( num_a == COL_MARKVAL_NEWTHREAD && ( num_b != COL_MARKVAL_NEWTHREAD && num_b <= COL_MARKVAL_UPDATED ) ){ num_a = DOWN; // 下で ret *= -1 しているので UP と DOWNを逆にする num_b = UP; } else if( num_b == COL_MARKVAL_NEWTHREAD && ( num_a != COL_MARKVAL_NEWTHREAD && num_a <= COL_MARKVAL_UPDATED ) ){ num_a = UP; // 下で ret *= -1 しているので UP と DOWNを逆にする num_b = DOWN; } } else if( sortmode == SORTMODE_MARK3 ){ // 新着を一番上に if( num_a == COL_MARKVAL_NEWTHREAD && num_b != COL_MARKVAL_NEWTHREAD ){ num_a = DOWN; // 下で ret *= -1 しているので UP と DOWNを逆にする num_b = UP; } else if( num_b == COL_MARKVAL_NEWTHREAD && num_a != COL_MARKVAL_NEWTHREAD ){ num_a = UP; // 下で ret *= -1 しているので UP と DOWNを逆にする num_b = DOWN; } else if( num_a == COL_MARKVAL_NEWTHREAD_HOUR && num_b != COL_MARKVAL_NEWTHREAD_HOUR ){ num_a = DOWN; // 下で ret *= -1 しているので UP と DOWNを逆にする num_b = UP; } else if( num_b == COL_MARKVAL_NEWTHREAD_HOUR && num_a != COL_MARKVAL_NEWTHREAD_HOUR ){ num_a = UP; // 下で ret *= -1 しているので UP と DOWNを逆にする num_b = DOWN; } } break; } case COL_ID: num_a = row_a[ m_columns.m_col_id ]; num_b = row_b[ m_columns.m_col_id ]; break; case COL_SUBJECT: { // 文字列の大小を数字に変換 const Glib::ustring name_a = row_a[ m_columns.m_col_subject ]; const Glib::ustring name_b = row_b[ m_columns.m_col_subject ]; if( name_a < name_b ) { num_a = UP; num_b = DOWN; } else if( name_a > name_b ) { num_a = DOWN; num_b = UP; } break; } case COL_RES: num_a = row_a[ m_columns.m_col_res ]; num_b = row_b[ m_columns.m_col_res ]; break; case COL_STR_LOAD: arta = row_a[ m_columns.m_col_article ]; artb = row_b[ m_columns.m_col_article ]; num_a = arta->get_number_load(); num_b = artb->get_number_load(); break; case COL_STR_NEW: num_a = row_a[ m_columns.m_col_new ]; num_b = row_b[ m_columns.m_col_new ]; break; case COL_SINCE: arta = row_a[ m_columns.m_col_article ]; artb = row_b[ m_columns.m_col_article ]; num_a = arta->get_since_time(); num_b = artb->get_since_time(); break; case COL_WRITE: num_a = row_a[ m_columns.m_col_write_t ]; num_b = row_b[ m_columns.m_col_write_t ]; break; case COL_ACCESS: num_a = row_a[ m_columns.m_col_access_t ]; num_b = row_b[ m_columns.m_col_access_t ]; break; case COL_SPEED: num_a = row_a[ m_columns.m_col_speed ]; num_b = row_b[ m_columns.m_col_speed ]; break; case COL_DIFF: num_a = row_a[ m_columns.m_col_diff ]; num_b = row_b[ m_columns.m_col_diff ]; break; case COL_BOARD: { // 文字列の大小を数字に変換 Glib::ustring name_a = row_a[ m_columns.m_col_board ]; Glib::ustring name_b = row_b[ m_columns.m_col_board ]; if( name_a < name_b ) { num_a = UP; num_b = DOWN; } else if( name_a > name_b ) { num_a = DOWN; num_b = UP; } break; } } // 両方とも 0 より大きいか 0 より小さい場合は普通に比較 if( ( num_a > 0 && num_b > 0 ) || ( num_a < 0 && num_b < 0 ) ){ if( num_a < num_b ) ret = COL_A_UP; else if( num_a > num_b ) ret = COL_B_UP; if( sortmode == SORTMODE_DESCEND ) ret *= -1; if( sortmode == SORTMODE_MARK1 || sortmode == SORTMODE_MARK2 || sortmode == SORTMODE_MARK3 ) ret *= -1; } // 0より大きい方を優先 else if( num_a > 0 && num_b <= 0 ) ret = COL_A_UP; else if( num_b > 0 && num_a <= 0 ) ret = COL_B_UP; // 0を優先 else if( num_a == 0 && num_b < 0 ) ret = COL_A_UP; else if( num_b == 0 && num_a < 0 ) ret = COL_B_UP; return ret; } // // ソート関数 // int BoardViewBase::slot_compare_row( const Gtk::TreeModel::iterator& a, const Gtk::TreeModel::iterator& b ) { Gtk::TreeModel::Row row_a = *( a ); Gtk::TreeModel::Row row_b = *( b ); // 抽出状態を最優先 int ret = compare_drawbg( row_a, row_b ); if( ! ret ) ret = compare_col( m_col, m_sortmode, row_a, row_b ); // マルチキーソート if( ! ret ) ret = compare_col( m_previous_col, m_previous_sortmode, row_a, row_b ); if( ! ret ) ret = compare_col( COL_MARK, SORTMODE_ASCEND, row_a, row_b ); if( ! ret ) ret = compare_col( COL_ID, SORTMODE_ASCEND, row_a, row_b ); return ret; } // // コマンド // bool BoardViewBase::set_command( const std::string& command, const std::string& arg1, const std::string& arg2 ) { if( command == "update_columns" ) update_columns(); else if( command == "draw_bg_articles" ) draw_bg_articles(); else if( command == "clear_highlight" ) clear_highlight(); else if( command == "select_item" ) select_item( arg1 ); return true; } // // クロック入力 // void BoardViewBase::clock_in() { View::clock_in(); m_treeview.clock_in(); if( m_cancel_openrow_counter ) --m_cancel_openrow_counter; } // // ロード停止 // void BoardViewBase::stop() { DBTREE::board_stop_load( get_url_board() ); } // // ビュー表示 // void BoardViewBase::show_view() { #ifdef _DEBUG std::cout << "BoardViewBase::show_view " << get_url() << std::endl; #endif if( is_loading() ) return; update_boardname(); m_pre_query = std::string(); m_last_access_time = DBTREE::board_last_access_time( get_url_board() ); // オートリロードのカウンタを0にする reset_autoreload_counter(); if( ! m_load_subject_txt ){ update_view(); return; } if( m_col_diff_is_shown ) DBTREE::board_read_subject_from_cache( get_url_board() ); // download 開始 // 終わったら update_view() が呼ばれる // DBに登録されてない時はロードしない if( get_url_board().empty() ){ set_status( "invalid URL" ); BOARD::get_admin()->set_command( "set_status", get_url(), get_status() ); return; } m_loading = true; DBTREE::board_download_subject( get_url_board(), get_url() ); set_status( "loading..." ); BOARD::get_admin()->set_command( "set_status", get_url(), get_status() ); // タブにアイコンを表示 BOARD::get_admin()->set_command( "toggle_icon", get_url() ); } // // 色、フォント、表示内容の更新 // void BoardViewBase::relayout() { m_treeview.init_color( COLOR_CHAR_BOARD, COLOR_BACK_BOARD, COLOR_BACK_BOARD_EVEN ); m_treeview.init_font( CONFIG::get_fontname( FONT_BOARD ) ); update_item_all(); } // // view更新 // // loading_fin : ロードが完了したら true をセットして呼び出す // void BoardViewBase::update_view_impl( const std::vector< DBTREE::ArticleBase* >& list_article, const bool loading_fin ) { #ifdef _DEBUG const int code = DBTREE::board_code( get_url_board() ); std::cout << "BoardViewBase::update_view_impl " << get_url() << " code = " << code << " size = " << list_article.size() << std::endl; #endif // 画面消去 m_treeview.unset_model(); if( list_article.size() ){ m_liststore->clear(); unsorted_column(); // 行の追加 for( int i = list_article.size()-1; i >= 0; --i ){ DBTREE::ArticleBase* art = list_article[ i ]; prepend_row( art, i + 1 ); } m_treeview.set_model( m_liststore ); if( loading_fin ){ if( m_list_draw_bg_articles.size() ) draw_bg_articles(); else restore_sort(); } } if( loading_fin ){ m_loading = false; update_status(); // タブのアイコン状態を更新 BOARD::get_admin()->set_command( "toggle_icon", get_url() ); } } // // ステータスバー更新 // void BoardViewBase::update_status() { std::ostringstream ss_tmp; if( m_load_subject_txt ) ss_tmp << DBTREE::board_str_code( get_url_board() ) << " "; ss_tmp << "[ 全 " << get_row_size() << " ] "; set_status( ss_tmp.str() ); BOARD::get_admin()->set_command( "set_status", get_url(), get_status() ); } // // URLを選択 // void BoardViewBase::select_item( const std::string& url ) { if( m_url_board != DBTREE::url_boardbase( url ) ) return; const Gtk::TreeModel::Row row = get_row_from_url( url ); if( row ){ Gtk::TreePath path = GET_PATH( row ); m_treeview.get_selection()->unselect_all(); m_treeview.set_cursor( path ); } } void BoardViewBase::focus_view() { #ifdef _DEBUG std::cout << "BoardViewBase::focus_view\n"; #endif m_treeview.grab_focus(); } void BoardViewBase::focus_out() { SKELETON::View::focus_out(); } // // 閉じる // void BoardViewBase::close_view() { BOARD::get_admin()->set_command( "close_currentview" ); } // // 選択した行のログをまとめて削除 // void BoardViewBase::slot_delete_logs() { std::list< Gtk::TreeModel::iterator > list_it = m_treeview.get_selected_iterators(); for( Gtk::TreeModel::iterator& iter : list_it ) { Gtk::TreeModel::Row row = *iter; DBTREE::ArticleBase *art = row[ m_columns.m_col_article ]; CORE::core_set_command( "delete_article", art->get_url() ); } } // // viewの操作 // bool BoardViewBase::operate_view( const int control ) { if( CONTROL::operate_common( control, get_url(), BOARD::get_admin() ) ) return true; bool open_tab = false; Gtk::TreePath path = m_treeview.get_current_path();; switch( control ){ case CONTROL::Down: row_down(); break; case CONTROL::Up: row_up(); break; case CONTROL::PageUp: page_up(); break; case CONTROL::PageDown: page_down(); break; case CONTROL::Home: goto_top(); break; case CONTROL::End: goto_bottom(); break; // 全て選択 case CONTROL::SelectAll: slot_select_all(); break; // お気に入りに追加 case CONTROL::AppendFavorite: { SKELETON::MsgDiag mdiag( get_parent_win(), "板と選択中のスレのどちらを登録しますか?", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_NONE ); mdiag.add_button( "板を登録", Gtk::RESPONSE_NO ); mdiag.add_button( "スレを登録", Gtk::RESPONSE_YES ); mdiag.set_default_response( Gtk::RESPONSE_YES ); int ret = mdiag.run(); if( ret == Gtk::RESPONSE_YES ) slot_favorite_thread(); else slot_favorite_board(); } break; // スレを開く case CONTROL::OpenArticleTab: open_tab = true; // fallthrough case CONTROL::OpenArticle: if( ! path.empty() ) open_row( path, open_tab, false ); break; // Listに戻る case CONTROL::Left: CORE::core_set_command( "switch_leftview" ); break; // 現在の記事を表示 case CONTROL::Right: CORE::core_set_command( "switch_rightview" ); break; case CONTROL::ScrollLeftBoard: scroll_left(); break; case CONTROL::ScrollRightBoard: scroll_right(); break; case CONTROL::ToggleArticle: CORE::core_set_command( "toggle_article" ); break; // 戻る、進む case CONTROL::PrevView: back_viewhistory( 1 ); break; case CONTROL::NextView: forward_viewhistory( 1 ); break; // datを保存 case CONTROL::Save: slot_save_dat(); break; // 閉じる case CONTROL::Quit: close_view(); break; case CONTROL::Reload: reload(); break; case CONTROL::StopLoading: stop(); break; case CONTROL::NewArticle: write(); break; case CONTROL::Delete: { if( CONFIG::get_show_deldiag() ){ SKELETON::MsgCheckDiag mdiag( get_parent_win(), "選択した行のログを削除しますか?", "今後表示しない(常に削除)(_D)", Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO ); mdiag.set_title( "削除確認" ); const int ret = mdiag.run(); if( ret != Gtk::RESPONSE_YES ) return true; if( mdiag.get_chkbutton().get_active() ) CONFIG::set_show_deldiag( false ); } slot_delete_logs(); break; } // ポップアップメニュー表示 case CONTROL::ShowPopupMenu: show_popupmenu( "", true ); break; // 検索 case CONTROL::Search: m_search_invert = false; BOARD::get_admin()->set_command( "open_searchbar", get_url() ); break; case CONTROL::SearchInvert: m_search_invert = true; BOARD::get_admin()->set_command( "open_searchbar", get_url() ); break; case CONTROL::SearchNext: down_search(); break; case CONTROL::SearchPrev: up_search(); break; // 並び替えモードの切り替え case CONTROL::SortColumnMark: slot_col_clicked( COL_MARK ); break; case CONTROL::SortColumnID: slot_col_clicked( COL_ID ); break; case CONTROL::SortColumnBoard: slot_col_clicked( COL_BOARD ); break; case CONTROL::SortColumnSubject: slot_col_clicked( COL_SUBJECT ); break; case CONTROL::SortColumnRes: slot_col_clicked( COL_RES ); break; case CONTROL::SortColumnStrLoad: slot_col_clicked( COL_STR_LOAD ); break; case CONTROL::SortColumnStrNew: slot_col_clicked( COL_STR_NEW ); break; case CONTROL::SortColumnSince: slot_col_clicked( COL_SINCE ); break; case CONTROL::SortColumnWrite: slot_col_clicked( COL_WRITE ); break; case CONTROL::SortColumnAccess: slot_col_clicked( COL_ACCESS ); break; case CONTROL::SortColumnSpeed: slot_col_clicked( COL_SPEED ); break; case CONTROL::SortColumnDiff: slot_col_clicked( COL_DIFF ); break; // 板のプロパティ case CONTROL::PreferenceView: show_preference(); break; default: return false; } return true; } // // 先頭に戻る // void BoardViewBase::goto_top() { m_treeview.goto_top(); } // // 一番最後へ // void BoardViewBase::goto_bottom() { m_treeview.goto_bottom(); } // // 指定したIDのスレに移動 // void BoardViewBase::goto_num( const int num, const int ) { if( ! num ) return; focus_view(); Gtk::TreeModel::Children child = m_liststore->children(); auto it = std::find_if( child.begin(), child.end(), [this, num]( const Gtk::TreeRow& row ) { return row[ m_columns.m_col_id ] == num; } ); if( it != child.end() ) { Gtk::TreePath path = GET_PATH( *it ); m_treeview.scroll_to_row( path ); m_treeview.set_cursor( path ); } } // // 左スクロール // void BoardViewBase::scroll_left() { auto hadjust = m_scrwin.get_hadjustment(); if( !hadjust ) return; hadjust->set_value( MAX( 0, hadjust->get_value() - hadjust->get_step_increment() ) ); } // // 右スクロール // void BoardViewBase::scroll_right() { auto hadjust = m_scrwin.get_hadjustment(); if( !hadjust ) return; hadjust->set_value( MIN( hadjust->get_upper() - hadjust->get_page_size(), hadjust->get_value() + hadjust->get_step_increment() ) ); } // // 上へ移動 // void BoardViewBase::row_up() { m_treeview.row_up(); } // // 下へ移動 // void BoardViewBase::row_down() { m_treeview.row_down(); } // // page up // void BoardViewBase::page_up() { m_treeview.page_up(); } // // page down // void BoardViewBase::page_down() { m_treeview.page_down(); } // // ポップアップメニューを表示する前にメニューのアクティブ状態を切り替える // // SKELETON::View::show_popupmenu() を参照すること // void BoardViewBase::activate_act_before_popupmenu( const std::string& url ) { // toggle アクションを activeにするとスロット関数が呼ばれるので処理しないようにする m_enable_menuslot = false; Glib::RefPtr< Gtk::Action > act; // dat 保存 act = action_group()->get_action( "SaveDat" ); if( act ){ act->set_sensitive( false ); std::list< Gtk::TreeModel::iterator > list_it = m_treeview.get_selected_iterators(); if( list_it.size() ){ for( Gtk::TreeModel::iterator& iter : list_it ) { Gtk::TreeModel::Row row = *iter; DBTREE::ArticleBase *art = row[ m_columns.m_col_article ]; if( art->is_cached() ) { act->set_sensitive( true ); break; } } } } m_enable_menuslot = true; } // // ポップアップメニュー取得 // // SKELETON::View::show_popupmenu() を参照すること // Gtk::Menu* BoardViewBase::get_popupmenu( const std::string& url ) { Gtk::Menu* popupmenu = nullptr; // 削除サブメニュー if( url == "popup_menu_delete" ){ popupmenu = dynamic_cast< Gtk::Menu* >( ui_manager()->get_widget( "/popup_menu_delete" ) ); } // お気に入りサブメニュー else if( url == "popup_menu_favorite" ){ const std::string url_board = path2url_board( m_path_selected ); if( url_board.empty() ) popupmenu = dynamic_cast< Gtk::Menu* >( ui_manager()->get_widget( "/popup_menu_favorite_article" ) ); else popupmenu = dynamic_cast< Gtk::Menu* >( ui_manager()->get_widget( "/popup_menu_favorite" ) ); } // 通常メニュー else if( m_treeview.get_selection()->get_selected_rows().size() == 1 ){ m_path_selected = * (m_treeview.get_selection()->get_selected_rows().begin() ); popupmenu = dynamic_cast< Gtk::Menu* >( ui_manager()->get_widget( "/popup_menu" ) ); } // 複数選択メニュー else{ m_path_selected = Gtk::TreeModel::Path(); popupmenu = dynamic_cast< Gtk::Menu* >( ui_manager()->get_widget( "/popup_menu_mul" ) ); } return popupmenu; } // // 特定の行だけの表示内容更新 // // url : boardbase アドレス // id : DAT の ID(拡張子付き), empty なら全ての行の表示内容を更新する // void BoardViewBase::update_item( const std::string& url, const std::string& id ) { if( is_loading() ) return; if( ! get_row_size() ) return; if( get_url_board() != url ) return; if( id.empty() ){ update_item_all(); return; } const std::string url_dat = DBTREE::url_datbase( url ) + id; #ifdef _DEBUG std::cout << "BoardViewBase::update_item " << get_url() << std::endl << "url = " << url << " id = " << id << " url_dat = " << url_dat << std::endl; #endif unsorted_column(); const Gtk::TreeModel::Row row = get_row_from_url( url_dat ); if( row ) update_row_common( row ); } // // 全ての行の表示内容更新 // void BoardViewBase::update_item_all() { #ifdef _DEBUG std::cout << "BoardViewBase::update_item_all " << get_url() << std::endl; #endif unsorted_column(); Gtk::TreeModel::Children child = m_liststore->children(); for( Gtk::TreeModel::Row row : child ) { if( ! row ) continue; DBTREE::ArticleBase* art = row[ m_columns.m_col_article ]; if( ! art ) continue; update_row_common( row ); } } // // 行を作って内容をセット // Gtk::TreeModel::Row BoardViewBase::prepend_row( DBTREE::ArticleBase* art, const int id ) { Gtk::TreeModel::Row row = *( m_liststore->prepend() ); // append より prepend の方が速いらしい row[ m_columns.m_col_id ] = id; row[ m_columns.m_col_diff ] = art->get_number_diff(); if( m_col_board ) row[ m_columns.m_col_board ] = DBTREE::board_name( art->get_url() ); row[ m_columns.m_col_article ] = art; row[ m_columns.m_col_drawbg ] = false; update_row_common( row ); return row; } // // prepend_row() と update_item() で共通に更新する列 // void BoardViewBase::update_row_common( const Gtk::TreeModel::Row& row ) { DBTREE::ArticleBase* art = row[ m_columns.m_col_article ]; if( art->empty() ) return; const int load = art->get_number_load(); const int res = art->get_number(); // タイトル、レス数、抽出 row[ m_columns.m_col_subject ] = art->get_subject(); row[ m_columns.m_col_res ] = res; // 読み込み数 if( load ){ row[ m_columns.m_col_str_load ] = Glib::ustring::compose( "%1", load ); const int new_arrival = res - load; row[ m_columns.m_col_str_new ] = Glib::ustring::compose( "%1", new_arrival );; row[ m_columns.m_col_new ] = new_arrival; } else{ row[ m_columns.m_col_str_load ] = ""; row[ m_columns.m_col_str_new ] = ""; // キャッシュが無い場合はソートの優先度を // キャッシュがあって新着0の物より下げる row[ m_columns.m_col_new ] = -1; } // 速度 if( ( art->get_status() & STATUS_NORMAL ) && ! art->is_924() ) { row[ m_columns.m_col_speed ] = art->get_speed(); } // // マーク int mark_val; int icon; // ブックマーク if( art->is_bookmarked_thread() ){ // 新着あり if( art->enable_load() ){ mark_val = COL_MARKVAL_BKMARKED_UPDATED; icon = ICON::BKMARK_UPDATE; } // subject.txt が壊れている( subject.txt に示されたレス数よりも実際の取得数の方が多い ) else if( art->is_cached() && ( art->get_status() & STATUS_BROKEN_SUBJECT ) ){ mark_val = COL_MARKVAL_BKMARKED_BROKEN_SUBJECT; icon = ICON::BKMARK_BROKEN_SUBJECT; } else{ mark_val = COL_MARKVAL_BKMARKED; icon = ICON::BKMARK; } } // dat落ち else if( art->get_status() & STATUS_OLD ){ mark_val = COL_MARKVAL_OLD; icon = ICON::OLD; } // キャッシュはあるが規定のレス数を越えていて全てのレスが既読 else if( art->is_finished() ){ mark_val = COL_MARKVAL_FINISHED; // subject.txt が壊れている( subject.txt に示されたレス数よりも実際の取得数の方が多い ) if( art->get_status() & STATUS_BROKEN_SUBJECT ) icon = ICON::BROKEN_SUBJECT; else icon = ICON::CHECK; } // 新着あり else if( art->enable_load() ){ mark_val = COL_MARKVAL_UPDATED; icon = ICON::UPDATE; } // キャッシュあり else if( art->is_cached() ){ // subject.txt が壊れている( subject.txt に示されたレス数よりも実際の取得数の方が多い ) if( art->get_status() & STATUS_BROKEN_SUBJECT ){ mark_val = COL_MARKVAL_BROKEN_SUBJECT; icon = ICON::BROKEN_SUBJECT; } // 新着無し else{ mark_val = COL_MARKVAL_CACHED; icon = ICON::CHECK; } } // スレッド924 else if( art->is_924() ){ if( CONFIG::get_show_924() ){ mark_val = COL_MARKVAL_924; icon = ICON::INFO; } else{ mark_val = COL_MARKVAL_NORMAL; icon = ICON::TRANSPARENT; } } // キャッシュ無し、新着 else if( ! get_url_board().empty() && art->get_since_time() > m_last_access_time ){ mark_val = COL_MARKVAL_NEWTHREAD; icon = ICON::NEWTHREAD; } // キャッシュ無し、新着( CONFIG::get_newthread_hour() 時間以内 ) else if( art->get_hour() < CONFIG::get_newthread_hour() ){ mark_val = COL_MARKVAL_NEWTHREAD_HOUR; icon = ICON::NEWTHREAD_HOUR; } //キャッシュ無し else{ mark_val = COL_MARKVAL_NORMAL; icon = ICON::TRANSPARENT; } row[ m_columns.m_col_mark_val ] = mark_val; row[ m_columns.m_col_mark ] = ICON::get_icon( icon ); // スレ立て時間 if( ! art->is_924() ) row[ m_columns.m_col_since ] = art->get_since_date(); else row[ m_columns.m_col_since ] = std::string(); // 書き込み時間 if( art->get_write_time() ){ row[ m_columns.m_col_write ] = art->get_write_date(); row[ m_columns.m_col_write_t ] = art->get_write_time(); } else{ row[ m_columns.m_col_write ] = std::string(); // DAT落ちしたスレや1000に到達したスレなどは書き込んでいてもソート時の優先度を下げる if( mark_val < COL_MARKVAL_NORMAL ) row[ m_columns.m_col_write_t ] = 0; else row[ m_columns.m_col_write_t ] = -1; } // ユーザが最後にロードした時間 if( art->get_access_time() ){ row[ m_columns.m_col_access ] = art->get_access_date(); row[ m_columns.m_col_access_t ] = art->get_access_time(); } else{ row[ m_columns.m_col_access ] = std::string(); // DAT落ちしたスレや1000に到達したスレなどは書き込んでいてもソート時の優先度を下げる if( mark_val < COL_MARKVAL_NORMAL ) row[ m_columns.m_col_access_t ] = 0; else row[ m_columns.m_col_access_t ] = -1; } } // // マウスボタン押した // bool BoardViewBase::slot_button_press( GdkEventButton* event ) { #ifdef _DEBUG std::cout << "BoardViewBase::slot_button_press\n"; #endif if( m_cancel_openrow_counter ){ #ifdef _DEBUG std::cout << "canceled\n"; #endif return true; } m_clicked = true; // マウスジェスチャ get_control().MG_start( event ); // ホイールマウスジェスチャ get_control().MG_wheel_start( event ); // ダブルクリック // button_release_eventでは event->type に必ず GDK_BUTTON_RELEASE が入る m_dblclicked = false; if( event->type == GDK_2BUTTON_PRESS ) m_dblclicked = true; BOARD::get_admin()->set_command( "switch_admin" ); return true; } // // マウスボタン離した // bool BoardViewBase::slot_button_release( GdkEventButton* event ) { if( ! m_clicked ) return true; m_clicked = false; /// マウスジェスチャ const int mg = get_control().MG_end( event ); // ホイールマウスジェスチャ // 実行された場合は何もしない if( get_control().MG_wheel_end( event ) ) return true; if( mg != CONTROL::None && enable_mg() ){ operate_view( mg ); return true; } // リサイズするときにラベルをドラッグした場合 if( event->window != m_treeview.get_bin_window()->gobj() ){ save_column_width(); return true; } const int x = (int)event->x; const int y = (int)event->y; Gtk::TreeModel::Path path; Gtk::TreeViewColumn* column; int cell_x; int cell_y; // 座標からpath取得 if( m_treeview.get_path_at_pos( x, y, path, column, cell_x, cell_y ) ){ m_path_selected = path; #ifdef _DEBUG std::cout << "BoardViewBase::slot_button_release : path = " << path.to_string() << " title = " << column->get_title() << " x = " << x << " y = " << y << " cellheight = " << m_treeview.get_row_height() << " cell_x = " << cell_x << " cell_y = " << cell_y << std::endl; #endif // ダブルクリックの処理のため一時的にtypeを切替える GdkEventType type_copy = event->type; if( m_dblclicked ) event->type = GDK_2BUTTON_PRESS; // スレを開く bool openarticle = get_control().button_alloted( event, CONTROL::OpenArticleButton ); bool openarticletab = get_control().button_alloted( event, CONTROL::OpenArticleTabButton ); if( openarticle || openarticletab ){ // 複数行選択中 if( m_treeview.get_selected_iterators().size() >= 2 ) open_selected_rows( false ); else open_row( path, openarticletab, false ); } // ポップアップメニューボタン else if( get_control().button_alloted( event, CONTROL::PopupmenuButton ) ){ show_popupmenu( "", false ); } else operate_view( get_control().button_press( event ) ); event->type = type_copy; } return true; } // // マウス動かした // bool BoardViewBase::slot_motion_notify( GdkEventMotion* event ) { /// マウスジェスチャ get_control().MG_motion( event ); return true; } // // キー入力 // bool BoardViewBase::slot_key_press( GdkEventKey* event ) { m_pressed_key = get_control().key_press( event ); if( m_pressed_key != CONTROL::None ){ // キー入力でスレを開くとkey_releaseイベントがboadviewが画面から // 消えてから送られてWIDGET_REALIZED_FOR_EVENT assertionが出るので // OpenArticle(Tab)は slot_key_release() で処理する if( m_pressed_key == CONTROL::OpenArticle ) return true; if( m_pressed_key == CONTROL::OpenArticleTab ) return true; if( operate_view( m_pressed_key ) ) return true; } else if( release_keyjump_key( event->keyval ) ) return true; return false; } // // キーリリースイベント // bool BoardViewBase::slot_key_release( GdkEventKey* event ) { const int key = get_control().key_press( event ); // 押したキーと違うときは処理しない if( key == m_pressed_key ){ // キー入力でスレを開くとkey_releaseイベントがboadviewが画面から // 消えてから送られてWIDGET_REALIZED_FOR_EVENT assertionが出るので // OpenArticle(Tab)はここで処理する if( key == CONTROL::OpenArticle ) operate_view( key ); if( key == CONTROL::OpenArticleTab ) operate_view( key ); } return true; } // // マウスホイールイベント // bool BoardViewBase::slot_scroll_event( GdkEventScroll* event ) { // ホイールマウスジェスチャ const int control = get_control().MG_wheel_scroll( event ); if( enable_mg() && control != CONTROL::None ){ operate_view( control ); return true; } m_treeview.wheelscroll( event ); return true; } // // ツールチップのセット // bool BoardViewBase::slot_query_tooltip( int x, int y, bool keyboard_tooltip, const Glib::RefPtr& tooltip ) { if( keyboard_tooltip ) return false; int bin_x, bin_y; m_treeview.convert_widget_to_bin_window_coords( x, y, bin_x, bin_y ); Gtk::TreeModel::Path path; Gtk::TreeView::Column* column; int cell_x, cell_y; if( m_treeview.get_path_at_pos( bin_x, bin_y, path, column, cell_x, cell_y ) ) { m_treeview.set_tooltip_cell( tooltip, &path, column, nullptr ); const Glib::ustring title = column->get_title(); Glib::ustring cell_text; if( title == ITEM_NAME_BOARD ) cell_text = get_name_of_cell( path, m_columns.m_col_board ); else if( title == ITEM_NAME_NAME ) cell_text = get_name_of_cell( path, m_columns.m_col_subject ); else if( title == ITEM_NAME_SINCE ) cell_text = get_name_of_cell( path, m_columns.m_col_since ); else if( title == ITEM_NAME_LASTWRITE ) cell_text = get_name_of_cell( path, m_columns.m_col_write ); else if( title == ITEM_NAME_ACCESS ) cell_text = get_name_of_cell( path, m_columns.m_col_access ); // セルの内容が空ならツールチップを表示しない if( cell_text.empty() ) return false; const auto layout = m_treeview.create_pango_layout( cell_text ); int pixel_width, ph; layout->get_pixel_size( pixel_width, ph ); constexpr int offset{ 8 }; // セルの内容の幅が列幅より小さいならツールチップを表示しない if( pixel_width + offset < column->get_width() ) return false; // ツールチップにセルの内容をセットする tooltip->set_text( cell_text ); return true; } return false; } // // D&Dで受信側がデータ送信を要求してきた // void BoardViewBase::slot_drag_data_get( const Glib::RefPtr& context, Gtk::SelectionData& selection_data, guint info, guint time ) { #ifdef _DEBUG std::cout << "BoardViewBase::slot_drag_data_get\n"; #endif set_article_to_buffer(); selection_data.set( m_treeview.get_dndtarget(), get_url_board() ); } // // text/url-list がドロップされた // void BoardViewBase::slot_dropped_url_list( const std::list< std::string >& url_list ) { #ifdef _DEBUG std::cout << "BoardViewBase::slot_dropped_url_list\n"; #endif if( ! url_list.size() ) return; // 共有バッファにアドレスをセットしてから import_dat コマンドを発行 CORE::DATA_INFO_LIST list_info; for( const std::string& url : url_list ) { if( url.empty() ) continue; if( url.find( "file://" ) == std::string::npos ) continue; CORE::DATA_INFO info; info.type = TYPE_FILE; info.url = MISC::remove_str( url, "file://" ); list_info.push_back( info ); #ifdef _DEBUG std::cout << "append " << info.url << std::endl; #endif } CORE::SBUF_set_list( list_info ); CORE::core_set_command( "import_dat", get_url_board(), "no_show_diag", "use_sbuf" ); } // // ブックマーク設定、解除 // void BoardViewBase::slot_bookmark( const int bookmark ) { std::list< Gtk::TreeModel::iterator > list_it = m_treeview.get_selected_iterators(); for( Gtk::TreeModel::iterator& iter : list_it ) { Gtk::TreeModel::Row row = *iter; DBTREE::ArticleBase* art = row[ m_columns.m_col_article ]; if( art ){ bool set = bookmark; if( bookmark == BOOKMARK_AUTO ) set = ! art->is_bookmarked_thread(); #ifdef _DEBUG std::cout << "BoardViewBase::slot_bookmark url = " << art->get_url() << " set = " << set << std::endl; #endif art->set_bookmarked_thread( set ); } } } // // popupmenu でタブで開くを選択 // void BoardViewBase::slot_open_tab() { if( ! m_path_selected.empty() ) open_row( m_path_selected, true, false ); } // // popupmenu でスレ情報を消さずに再取得を選択 // void BoardViewBase::slot_reget_article() { if( ! m_path_selected.empty() ) open_row( m_path_selected, true, true ); } // // スレをお気に入りに登録 // // ポップアップメニューのslot // void BoardViewBase::slot_favorite_thread() { // 共有バッファにデータをセットしてから append_favorite コマンド実行 set_article_to_buffer(); CORE::core_set_command( "append_favorite", URL_FAVORITEVIEW ); } // // 板をお気に入りに追加 // void BoardViewBase::slot_favorite_board() { // 共有バッファにデータをセットしてから append_favorite コマンド実行 set_board_to_buffer(); CORE::core_set_command( "append_favorite", URL_FAVORITEVIEW ); } // // 新スレをたてる // void BoardViewBase::write() { CORE::core_set_command( "create_new_thread", get_url_board() ); } // // ツールバーの削除ボタン // void BoardViewBase::delete_view() { show_popupmenu( "popup_menu_delete", false ); } // // ツールバーのお気に入りボタン // void BoardViewBase::set_favorite() { show_popupmenu( "popup_menu_favorite", false ); } // // スレのURLをコピー // void BoardViewBase::slot_copy_url() { if( m_path_selected.empty() ) return; const std::string url = DBTREE::url_readcgi( path2daturl( m_path_selected ), 0, 0 ); MISC::CopyClipboard( url ); } // スレの名前とURLをコピー // void BoardViewBase::slot_copy_title_url() { if( m_path_selected.empty() ) return; const std::string url = DBTREE::url_readcgi( path2daturl( m_path_selected ), 0, 0 ); const std::string name = DBTREE::article_subject( url ); MISC::CopyClipboard( name + '\n' + url ); } // // 全選択 // void BoardViewBase::slot_select_all() { SKELETON::MsgDiag mdiag( get_parent_win(), "全ての行を選択しますか?", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO ); mdiag.set_default_response( Gtk::RESPONSE_NO ); if( mdiag.run() != Gtk::RESPONSE_YES ) return; m_treeview.get_selection()->select_all(); } // // ポップアップメニューでブラウザで開くを選択 // void BoardViewBase::slot_open_browser() { const std::string url = DBTREE::url_readcgi( path2daturl( m_path_selected ), 0, 0 ); CORE::core_set_command( "open_url_browser", url ); } // // 記事を開く // bool BoardViewBase::open_row( const Gtk::TreePath& path, const bool tab, const bool reget ) { std::string str_tab = "false"; if( tab ) str_tab = "opentab"; std::string mode = std::string();; const std::string url_target = path2daturl( path ); #ifdef _DEBUG std::cout << "BoardViewBase::open_row " << url_target << std::endl; #endif if( url_target.empty() ) return false; if( reget ){ if( ! SESSION::is_online() ){ SKELETON::MsgDiag mdiag( get_parent_win(), "オフラインです" ); mdiag.run(); return false; } if( ! DBTREE::article_is_cached( url_target ) ) return false; if( DBTREE::article_status( url_target ) & STATUS_OLD ){ SKELETON::MsgDiag mdiag( get_parent_win(), "DAT落ちしています。\n\nログが消える恐れがあります。実行しますか?", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO ); mdiag.set_default_response( Gtk::RESPONSE_NO ); if( mdiag.run() != Gtk::RESPONSE_YES ) return false; } mode = "reget"; } // datロード終了時に次スレ移行チェックを行う DBTREE::article_set_url_pre_article( url_target, get_url_pre_article() ); CORE::core_set_command( "open_article", url_target, str_tab, mode ); // 行を長押ししてから素早くクリックし直すとslot_button_press()が呼び出されて // スレビューが表示されてから一瞬スレ一覧に切り替わるのを防ぐ m_cancel_openrow_counter = CANCEL_OPENROW / TIMER_TIMEOUT; return true; } // // 選択した行をまとめて開く // void BoardViewBase::open_selected_rows( const bool reget ) { std::string mode = std::string();; std::string list_url; std::list< Gtk::TreeModel::iterator > list_it = m_treeview.get_selected_iterators(); if( reget ){ if( ! SESSION::is_online() ){ SKELETON::MsgDiag mdiag( get_parent_win(), "オフラインです" ); mdiag.run(); return; } for( Gtk::TreeModel::iterator& iter : list_it ) { Gtk::TreeModel::Row row = *iter; DBTREE::ArticleBase *art = row[ m_columns.m_col_article ]; if( ! art->is_cached() ) continue; if( art->get_status() & STATUS_OLD ){ SKELETON::MsgDiag mdiag( get_parent_win(), "DAT落ちしているスレを含んでいます。\n\nログが消える恐れがあります。実行しますか?", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO ); mdiag.set_default_response( Gtk::RESPONSE_NO ); if( mdiag.run() != Gtk::RESPONSE_YES ) return; break; } } mode = "reget"; } for( Gtk::TreeModel::iterator& iter : list_it ) { if( !list_url.empty() ) list_url += " "; Gtk::TreeModel::Row row = *iter; DBTREE::ArticleBase *art = row[ m_columns.m_col_article ]; if( reget && ! art->is_cached() ) continue; list_url += art->get_url(); // datロード終了時に次スレ移行チェックを行う art->set_url_pre_article( get_url_pre_article() ); } if( list_url.size() ) CORE::core_set_command( "open_article_list", std::string(), list_url, mode ); } // // path -> スレッドの(dat型)URL変換 // std::string BoardViewBase::path2daturl( const Gtk::TreePath& path ) { Gtk::TreeModel::Row row = m_treeview.get_row( path ); if( !row ) return std::string(); DBTREE::ArticleBase *art = row[ m_columns.m_col_article ]; return art->get_url(); } // // path -> 板URL変換 // std::string BoardViewBase::path2url_board( const Gtk::TreePath& path ) { if( ! get_url_board().empty() ) return get_url_board(); if( path.empty() ) return std::string(); return DBTREE::url_boardbase( path2daturl( path ) ); } // // 抽出 // bool BoardViewBase::drawout( const bool force_reset ) { int hit = 0; bool reset = false; const std::string query = get_search_query(); // 空の時はリセット if( force_reset || query.empty() ) reset = true; #ifdef _DEBUG std::cout << "BoardViewBase::drawout query = " << query << std::endl; #endif unsorted_column(); JDLIB::Regex regex; JDLIB::RegexPattern regexptn; constexpr bool icase = true; // 大文字小文字区別しない constexpr bool newline = true; // . に改行をマッチさせない constexpr bool usemigemo = true; // migemo使用 constexpr bool wchar = true; // 全角半角の区別をしない Gtk::TreeModel::Children child = m_liststore->children(); if ( ! reset ) regexptn.set( query, icase, newline, usemigemo, wchar ); for( Gtk::TreeModel::Row row : child ) { const Glib::ustring subject = row[ m_columns.m_col_subject ]; if( reset ) row[ m_columns.m_col_drawbg ] = false; else if( regex.match( regexptn, subject, 0 ) ){ row[ m_columns.m_col_drawbg ] = true; ++hit; #ifdef _DEBUG std::cout << subject << " " << row[ m_columns.m_col_mark_val ] << std::endl; #endif } else row[ m_columns.m_col_drawbg ] = false; } restore_sort(); if( reset ) CORE::core_set_command( "set_info", "", "" ); else if( ! hit ) CORE::core_set_command( "set_info", "", "検索結果: ヒット無し" ); else CORE::core_set_command( "set_info", "", "検索結果: " + std::to_string( hit ) + "件" ); return true; } // // 検索ボックスの文字列が変わった // void BoardViewBase::set_search_query( const std::string& query ) { SKELETON::View::set_search_query( query ); #ifdef _DEBUG std::cout << "BoardViewBase::set_search_query query = " << get_search_query() << std::endl; #endif if( CONFIG::get_inc_search_board() ){ drawout( false ); m_pre_query = std::string(); } } // // 検索実行 // void BoardViewBase::exec_search() { const std::string query = get_search_query(); if( m_pre_query != query ){ drawout( false ); focus_view(); m_pre_query = query; CORE::get_completion_manager()->set_query( CORE::COMP_SEARCH_BOARD, query ); return; } focus_view(); if( query.empty() ) return; Gtk::TreePath path = m_treeview.get_current_path();; if( path.empty() ){ if( m_search_invert ) path = GET_PATH( *( m_liststore->children().begin() ) ); else { GET_PATH( *( std::prev( m_liststore->children().end() ) ) ); } } Gtk::TreePath path_start = path; JDLIB::Regex regex; constexpr size_t offset = 0; constexpr bool icase = true; // 大文字小文字区別しない constexpr bool newline = true; // . に改行をマッチさせない constexpr bool usemigemo = true; // migemo使用 constexpr bool wchar = true; // 全角半角の区別をしない #ifdef _DEBUG std::cout << "BoardViewBase::search start = " << path_start.to_string() << " query = " << query << std::endl; #endif for(;;){ if( !m_search_invert ){ // 次へ path.next(); // 先頭に戻る if( ! m_treeview.get_row( path ) ) path = GET_PATH( *( m_liststore->children().begin() ) ); } else{ // 前へ if( ! path.prev() ){ // 一番後へ path = GET_PATH( *( std::prev( m_liststore->children().end() ) ) ); } } if( path == path_start ) break; Glib::ustring subject = get_name_of_cell( path, m_columns.m_col_subject ); if( regex.exec( query, subject, offset, icase, newline, usemigemo, wchar ) ){ m_treeview.scroll_to_row( path, 0 ); m_treeview.set_cursor( path ); return; } } } // 前検索 void BoardViewBase::up_search() { m_search_invert = true; exec_search(); } // 後検索 void BoardViewBase::down_search() { m_search_invert = false; exec_search(); } // // 検索entryの操作 // void BoardViewBase::operate_search( const std::string& controlid ) { const int id = atoi( controlid.c_str() ); if( id == CONTROL::Cancel ) focus_view(); else if( id == CONTROL::SearchCache ) CORE::core_set_command( "open_article_searchlog", get_url_board() , get_search_query(), "exec" ); } // // ハイライト解除 // void BoardViewBase::clear_highlight() { drawout( true ); focus_view(); m_pre_query = std::string(); } // // 板プロパティ表示 // void BoardViewBase::show_preference() { const std::string url_board = path2url_board( m_path_selected ); if( url_board.empty() ) return; auto pref = CORE::PrefDiagFactory( get_parent_win(), CORE::PREFDIAG_BOARD, url_board ); pref->run(); } // // スレプロパティ表示 // void BoardViewBase::slot_preferences_article() { if( m_path_selected.empty() ) return; const std::string url = path2daturl( m_path_selected ); auto pref = CORE::PrefDiagFactory( get_parent_win(), CORE::PREFDIAG_ARTICLE, url ); pref->run(); } // // 戻る // void BoardViewBase::back_viewhistory( const int count ) { BOARD::get_admin()->set_command( "back_viewhistory", get_url(), std::to_string( count ) ); } // // 進む // void BoardViewBase::forward_viewhistory( const int count ) { BOARD::get_admin()->set_command( "forward_viewhistory", get_url(), std::to_string( count ) ); } // // datを保存 // void BoardViewBase::slot_save_dat() { if( ! m_enable_menuslot ) return; std::list< Gtk::TreeModel::iterator > list_it = m_treeview.get_selected_iterators(); #ifdef _DEBUG std::cout << "BoardViewBase::slot_save_dat size = " << list_it.size() << std::endl; #endif if( ! list_it.size() ) return; // ひとつだけ名前を付けて保存 if( list_it.size() == 1 ){ Gtk::TreePath path = m_treeview.get_current_path(); if( path.empty() ) return; const std::string url = path2daturl( path ); DBTREE::article_save_dat( url, std::string() ); return; } // 複数のdatを保存 int overwrite = Gtk::RESPONSE_NO; // ディレクトリ選択 SKELETON::FileDiag diag( get_parent_win(), "保存先選択", Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER ); diag.set_current_folder( SESSION::get_dir_dat() ); if( diag.run() != Gtk::RESPONSE_ACCEPT ) return; diag.hide(); std::string path_dir = MISC::recover_path( diag.get_filename() ); if( path_dir.empty() ) return; if( path_dir.c_str()[ path_dir.length()-1 ] != '/' ) path_dir += "/"; #ifdef _DEBUG std::cout << "dir = " << path_dir << std::endl; #endif if( ! CACHE::jdmkdir( path_dir ) ){ SKELETON::MsgDiag mdiag( get_parent_win(), path_dir + "\n\nの作成に失敗しました。\nハードディスクの容量やパーミッションなどを確認してください。\n\ndatファイルの保存をキャンセルしました。原因を解決してからもう一度保存を行ってください。" ); mdiag.run(); return; } SESSION::set_dir_dat( path_dir ); for( Gtk::TreeModel::iterator& iter : list_it ) { Gtk::TreeModel::Row row = *iter; DBTREE::ArticleBase *art = row[ m_columns.m_col_article ]; if( ! art->is_cached() ) continue; const std::string path_from = CACHE::path_dat( art->get_url() ); const std::string path_to = path_dir + MISC::get_filename( art->get_url() ); #ifdef _DEBUG std::cout << "from = " << path_from << std::endl; std::cout << "to = " << path_to << std::endl; #endif bool copy_file = true; // 既にファイルがある場合 if( CACHE::file_exists( path_to ) == CACHE::EXIST_FILE ){ // すべて上書き if( overwrite == SKELETON::OVERWRITE_YES_ALL ) copy_file = true; // すべていいえ else if( overwrite == SKELETON::OVERWRITE_NO_ALL ) copy_file = false; else{ for(;;){ SKELETON::MsgOverwriteDiag mdiag( get_parent_win() ); overwrite = mdiag.run(); mdiag.hide(); switch( overwrite ){ // すべて上書き case SKELETON::OVERWRITE_YES_ALL: // 上書き case SKELETON::OVERWRITE_YES: copy_file = true; break; // 名前変更 case Gtk::RESPONSE_YES: if( ! art->save_dat( path_to ) ) continue; // fallthrough default: copy_file = false; break; } break; // for(;;) } } } if( copy_file ){ #ifdef _DEBUG std::cout << "copy\n"; #endif if( ! CACHE::jdcopy( path_from, path_to ) ){ SKELETON::MsgDiag mdiag( get_parent_win(), path_to + "\n\nの保存に失敗しました。\nハードディスクの容量やパーミッションなどを確認してください。\n\ndatファイルの保存をキャンセルしました。原因を解決してからもう一度保存を行ってください。" ); mdiag.run(); return; } } } } // // 次スレ検索 // void BoardViewBase::slot_search_next() { if( m_path_selected.empty() ) return; const std::string url = path2daturl( m_path_selected ); CORE::core_set_command( "open_board_next", DBTREE::url_boardbase( url ), url ); } // // 選択したスレをあぼーん // void BoardViewBase::slot_abone_thread() { std::list< Gtk::TreeModel::iterator > list_it = m_treeview.get_selected_iterators(); if( ! list_it.size() ) return; std::list< std::string > threads = DBTREE::get_abone_list_thread( get_url_board() ); for( Gtk::TreeModel::iterator& iter : list_it ) { Gtk::TreeModel::Row row = *iter; Glib::ustring subject = row[ m_columns.m_col_subject ]; threads.push_back( subject ); } // あぼーん情報更新 std::list< std::string > words = DBTREE::get_abone_list_word_thread( get_url_board() ); std::list< std::string > regexs = DBTREE::get_abone_list_regex_thread( get_url_board() ); const int low_number = DBTREE::get_abone_low_number_thread( get_url_board() ); const int high_number = DBTREE::get_abone_high_number_thread( get_url_board() ); const int hour = DBTREE::get_abone_hour_thread( get_url_board() ); const bool redraw = false; // 板の再描画はしない DBTREE::reset_abone_thread( get_url_board(), threads, words, regexs, low_number, high_number, hour, redraw ); m_treeview.delete_selected_rows( true ); } // // path と column からそのセルの内容を取得 // template < typename ColumnType > std::string BoardViewBase::get_name_of_cell( Gtk::TreePath& path, const Gtk::TreeModelColumn< ColumnType >& column ) { Gtk::TreeModel::Row row = m_treeview.get_row( path ); if( !row ) return std::string(); const Glib::ustring name = row[ column ]; return name; } // // 共有バッファに選択中の行を登録する // void BoardViewBase::set_article_to_buffer() { std::list< Gtk::TreeModel::iterator > list_it = m_treeview.get_selected_iterators(); if( list_it.size() ){ CORE::DATA_INFO_LIST list_info; Gtk::TreePath path( "0" ); for( Gtk::TreeModel::iterator& iter : list_it ) { Gtk::TreeModel::Row row = *iter; DBTREE::ArticleBase *art = row[ m_columns.m_col_article ]; const Glib::ustring name = row[ m_columns.m_col_subject ]; CORE::DATA_INFO info; info.type = TYPE_THREAD; info.parent = BOARD::get_admin()->get_win(); info.url = art->get_url(); info.name = name.raw(); info.path = path.to_string(); list_info.push_back( info ); #ifdef _DEBUG std::cout << "append " << info.name << std::endl; #endif path.next(); } CORE::SBUF_set_list( list_info ); } } // // 共有バッファに板を登録する // void BoardViewBase::set_board_to_buffer() { CORE::DATA_INFO_LIST list_info; CORE::DATA_INFO info; const std::string url_board = path2url_board( m_path_selected ); if( url_board.empty() ) return; info.type = TYPE_BOARD; info.parent = BOARD::get_admin()->get_win(); info.url = DBTREE::url_boardbase( url_board ); info.name = DBTREE::board_name( url_board ); info.path = Gtk::TreePath( "0" ).to_string(); list_info.push_back( info ); CORE::SBUF_set_list( list_info ); } // // 指定したスレを強調して表示 // dat 落ち等で表示されていないスレも強制的に表示する // 共有バッファに表示したいスレをセットしてから set_command 経由で呼び出す // void BoardViewBase::draw_bg_articles() { // 共有バッファから追加するスレのURLのリストを作成 if( ! m_list_draw_bg_articles.size() ){ if( CORE::SBUF_size() == 0 ) return; const CORE::DATA_INFO_LIST list_info = CORE::SBUF_list_info(); for( const CORE::DATA_INFO& info : list_info ) { if( info.type != TYPE_THREAD ) continue; m_list_draw_bg_articles.push_back( info.url ); } } if( ! m_list_draw_bg_articles.size() ) return; // ロード中の時はロード後にもう一度呼び出す if( is_loading() ) return; #ifdef _DEBUG std::cout << "BoardViewBase::draw_bg_articles size = " << m_list_draw_bg_articles.size() << std::endl; #endif unsorted_column(); for( const std::string& url : m_list_draw_bg_articles ) { #ifdef _DEBUG std::cout << url << std::endl; #endif Gtk::TreeModel::Row row = get_row_from_url( url ); // row が無ければ作成 if( ! row ){ DBTREE::ArticleBase* art = DBTREE::get_article( url ); row = prepend_row( art, get_row_size() + 1 ); } // 強調表示 row[ m_columns.m_col_drawbg ] = true; } restore_sort(); m_list_draw_bg_articles.clear(); } jdim-0.7.0/src/board/boardviewbase.h000066400000000000000000000236161417047150700173230ustar00rootroot00000000000000// ライセンス: GPL2 // スレ一覧ビューの基底クラス #ifndef _BOARDVIEWBASE_H #define _BOARDVIEWBASE_H #include "skeleton/view.h" #include "skeleton/dragtreeview.h" #include "columns.h" #include #include namespace SKELETON { class Admin; } namespace DBTREE { class ArticleBase; } namespace BOARD { class BoardViewBase : public SKELETON::View { // viewに表示するboardのURL ( SKELETON::View::m_url はview自身のURLなのに注意すること ) std::string m_url_board; SKELETON::DragTreeView m_treeview; BOARD::TreeColumns m_columns; Glib::RefPtr< Gtk::ListStore > m_liststore; Gtk::ScrolledWindow m_scrwin; // 列 Gtk::TreeView::Column* m_col_mark{}; Gtk::TreeView::Column* m_col_id{}; Gtk::TreeView::Column* m_col_board{}; Gtk::TreeView::Column* m_col_subject{}; Gtk::TreeView::Column* m_col_res{}; Gtk::TreeView::Column* m_col_str_load{}; Gtk::TreeView::Column* m_col_str_new{}; Gtk::TreeView::Column* m_col_since{}; Gtk::TreeView::Column* m_col_write{}; Gtk::TreeView::Column* m_col_access{}; Gtk::TreeView::Column* m_col_speed{}; Gtk::TreeView::Column* m_col_diff{}; // クリック状態 bool m_clicked{}; // ダブルクリック状態 bool m_dblclicked{}; // 押したキー int m_pressed_key{}; // ソートで使う変数 int m_col; int m_previous_col; int m_sortmode; int m_previous_sortmode{}; // サーチで使う変数 bool m_search_invert{}; std::string m_pre_query; // ポップアップメニュー用 Gtk::TreeModel::Path m_path_selected; // ロード中 bool m_loading{}; // ロード前の最終アクセス時刻 ( 新着判定用 ) time_t m_last_access_time{}; // ロード中に draw_bg_articles() を呼び出したときに使う一時変数 // draw_bg_articles() を参照せよ std::list< std::string > m_list_draw_bg_articles; // ポップアップメニュー表示のときにactivate_act_before_popupmenu()で使う変数 bool m_enable_menuslot; // subject.txt をロードする bool m_load_subject_txt; // 先頭に「板」列を表示 bool m_show_col_board; // 増分行が表示されている bool m_col_diff_is_shown{}; // 連続クリック防止用カウンタ int m_cancel_openrow_counter{}; public: BoardViewBase( const std::string& url, const bool show_col_board ); ~BoardViewBase() noexcept; const std::string& get_url_board() const { return m_url_board; } std::string url_for_copy() const override; // 行数 int get_row_size() const; // SKELETON::View の関数のオーバロード void save_session() override {} void update_url( const std::string& url_old, const std::string& url_new ) override; int get_icon( const std::string& iconname ) const override; bool is_loading() const override { return m_loading; } bool set_command( const std::string& command, const std::string& arg1 = {}, const std::string& arg2 = {} ) override; void clock_in() override; // キーを押した bool slot_key_press( GdkEventKey* event ) override; void write() override; void stop() override; void show_view() override; void relayout() override; void focus_view() override; void focus_out() override; void close_view() override; void delete_view() override; void set_favorite() override; // 特定の行だけの表示内容更新 // url : subject.txt のアドレス // id : DAT の ID(拡張子付き) // もし ID が empty() なら全ての行の表示内容を更新する void update_item( const std::string& url, const std::string& id ) override; bool operate_view( const int control ) override; void goto_top() override; void goto_bottom() override; void goto_num( const int num_to, const int num_from ) override; void scroll_left() override; void scroll_right() override; void show_preference() override; // 進む、戻る void back_viewhistory( const int count ) override; void forward_viewhistory( const int count ) override; // 検索 void exec_search() override; void up_search() override; void down_search() override; void operate_search( const std::string& controlid ) override; void set_search_query( const std::string& query ) override; void clear_highlight(); void row_up(); void row_down(); void page_up(); void page_down(); protected: // 自動ソート抑制 void unsorted_column(); // url から row を取得 Gtk::TreeModel::Row get_row_from_url( const std::string& url ) const; SKELETON::DragTreeView& get_treeview(){ return m_treeview; } // Viewが所属するAdminクラス SKELETON::Admin* get_admin() override; // ポップアップメニューを表示する前にメニューのアクティブ状態を切り替える void activate_act_before_popupmenu( const std::string& url ) override; // ポップアップメニュー取得 Gtk::Menu* get_popupmenu( const std::string& url ) override; // view更新 void update_view_impl( const std::vector< DBTREE::ArticleBase* >& list_article, const bool loading_fin ); // ステータスバー更新 void update_status(); // URLを選択 void select_item( const std::string& url ); // subject.txt をロードする void set_load_subject_txt( const bool load ){ m_load_subject_txt = load; } // 行を作って内容をセット Gtk::TreeModel::Row prepend_row( DBTREE::ArticleBase* art, const int id ); // デフォルトのソート状態 virtual int get_default_sort_column() const; virtual int get_default_view_sort_mode() const; virtual int get_default_view_sort_pre_column() const; virtual int get_default_view_sort_pre_mode() const; private: void setup_action(); // 通常の右クリックメニューの作成 std::string create_context_menu() const; const char* get_menu_item( const int item ) const; // 次スレ移行処理に使用する前スレのアドレス // BOARD::BoardViewNext と BoardViewBase::open_row()を参照せよ virtual std::string get_url_pre_article() const { return {}; } void update_columns(); int get_title_id( const int col ) const; // ソート列やソートモードの保存 virtual void save_sort_columns(); // 列の幅の保存 virtual void save_column_width(); void slot_cell_data( Gtk::CellRenderer* cell, const Gtk::TreeModel::iterator& it ); // 全ての行の表示内容更新 void update_item_all(); // ソート実行 void exec_sort(); // ソート状態回復 void restore_sort(); // ヘッダをクリックしたときのslot関数 void slot_col_clicked( const int col ); int compare_drawbg( const Gtk::TreeModel::Row& row_a, const Gtk::TreeModel::Row& row_b ) const; int compare_col( const int col, const int sortmode, const Gtk::TreeModel::Row& row_a, const Gtk::TreeModel::Row& row_b ) const; int slot_compare_row( const Gtk::TreeModel::iterator& a, const Gtk::TreeModel::iterator& b ); // UI bool slot_button_press( GdkEventButton* event ); bool slot_button_release( GdkEventButton* event ); bool slot_motion_notify( GdkEventMotion* event ); bool slot_key_release( GdkEventKey* event ); bool slot_scroll_event( GdkEventScroll* event ); bool slot_query_tooltip( int x, int y, bool keyboard_tooltip, const Glib::RefPtr& tooltip ); void slot_bookmark( int bookmark ); void slot_open_tab(); void slot_reget_article(); void slot_favorite_thread(); void slot_favorite_board(); void slot_copy_url(); void slot_copy_title_url(); void slot_select_all(); void slot_open_browser(); void slot_preferences_article(); void slot_save_dat(); void slot_search_next(); virtual void slot_abone_thread(); void slot_delete_logs(); // ドラッグアンドドロップ void slot_drag_data_get( const Glib::RefPtr& context, Gtk::SelectionData& selection_data, guint info, guint time ); void slot_dropped_url_list( const std::list< std::string >& ); bool open_row( const Gtk::TreePath& path, const bool tab, const bool reget ); void open_selected_rows( const bool reget ); std::string path2daturl( const Gtk::TreePath& path ); std::string path2url_board( const Gtk::TreePath& path ); // 検索 bool drawout( const bool force_reset ); void update_row_common( const Gtk::TreeModel::Row& row ); template < typename ColumnType > std::string get_name_of_cell( Gtk::TreePath& path, const Gtk::TreeModelColumn< ColumnType >& column ); void set_article_to_buffer(); void set_board_to_buffer(); // 指定したスレを強調して表示 // dat 落ち等で表示されていないスレも強制的に表示する // 共有バッファに表示したいスレをセットしてから set_command 経由で呼び出す void draw_bg_articles(); }; } #endif jdim-0.7.0/src/board/boardviewlog.cpp000066400000000000000000000123521417047150700175200ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "boardadmin.h" #include "boardviewlog.h" #include "skeleton/msgdiag.h" #include "dbtree/interface.h" #include "dbtree/articlebase.h" #include "searchmanager.h" #include "session.h" #include "global.h" using namespace BOARD; BoardViewLog::BoardViewLog( const std::string& url ) : BoardViewBase( url, ( url == URL_ALLLOG ) ) { set_writeable( false ); set_load_subject_txt( false ); CORE::get_search_manager()->sig_search_fin().connect( sigc::mem_fun( *this, &BoardViewLog::slot_search_fin ) ); #ifdef _DEBUG std::cout << "BoardViewLog::BoardViewLog : url = " << get_url() << std::endl; #endif } BoardViewLog::~BoardViewLog() { #ifdef _DEBUG std::cout << "BoardViewLog::~BoardViewLog : url = " << get_url() << std::endl; #endif } // // 検索停止 // void BoardViewLog::stop() { CORE::get_search_manager()->stop( get_url() ); } // // リロード // void BoardViewLog::reload() { if( CORE::get_search_manager()->is_searching() ){ SKELETON::MsgDiag mdiag( get_parent_win(), "他の検索スレッドが実行中です" ); mdiag.run(); return; } show_view(); } // // ビュー表示 // void BoardViewLog::show_view() { #ifdef _DEBUG std::cout << "BoardViewLog::show_view " << get_url() << std::endl; #endif if( ! SESSION::is_booting() ){ const std::string id = get_url(); int searchmode = CORE::SEARCHMODE_LOG; if( get_url() == URL_ALLLOG ) searchmode = CORE::SEARCHMODE_ALLLOG; const bool mode_or = false; const bool bm = false; const bool calc_data = false; CORE::get_search_manager()->search( id, searchmode, get_url_board(), "", mode_or, bm, calc_data ); } BoardViewBase::show_view(); } // // 検索終了 // void BoardViewLog::slot_search_fin( const std::string& id ) { if( id != get_url() ) return; #ifdef _DEBUG std::cout << "BoardViewLog::slot_search_fin size = " << CORE::get_search_manager()->get_list_article().size() << std::endl; #endif const bool loading_fin = true; const std::vector< DBTREE::ArticleBase* >& list_article = CORE::get_search_manager()->get_list_article(); update_view_impl( list_article, loading_fin ); m_set_thread.clear(); m_set_thread.reserve( list_article.size() ); for( const auto art : list_article ) { m_set_thread.insert( art->get_url() ); } } void BoardViewLog::slot_abone_thread() { SKELETON::MsgDiag mdiag( get_parent_win(), "ログ一覧ではあぼ〜ん出来ません" ); mdiag.run(); return; } // // 板名更新 // void BoardViewLog::update_boardname() { std::string title; if( get_url() == URL_ALLLOG ) title = "[ 全ログ一覧 ]"; else if( ! get_url_board().empty() ) title = "[ ログ一覧 ] - " + DBTREE::board_name( get_url_board() ); // ウィンドウタイトル表示 set_title( title ); BOARD::get_admin()->set_command( "set_title", get_url(), get_title() ); // タブに名前をセット BOARD::get_admin()->set_command( "set_tablabel", get_url(), title ); } // // 特定の行だけの表示内容更新 // // url : subject.txt のアドレス // id : DAT の ID(拡張子付き), empty なら全ての行の表示内容を更新する // void BoardViewLog::update_item( const std::string& url, const std::string& id ) { // url が URL_ALLLOG の時は get_url_board() の戻り値は empty if( get_url() != URL_ALLLOG && get_url_board() != url ) return; if( CORE::get_search_manager()->is_searching( get_url() ) ) return; const std::string url_dat = DBTREE::url_datbase( url ) + id; Gtk::TreeModel::Row row; if( ! id.empty() && m_set_thread.find( url_dat ) != m_set_thread.end() ) { row = get_row_from_url( url_dat ); } #ifdef _DEBUG std::cout << "BoardViewLog::update_item " << get_url() << std::endl << "url = " << url << " id = " << id << " url_dat = " << url_dat << std::endl; #endif if( id.empty() || row ) BoardViewBase::update_item( url, id ); // もし row が無く、かつキャッシュがあるならば行を追加 else { DBTREE::ArticleBase* art = DBTREE::get_article( url_dat ); if( art && art->is_cached() ){ #ifdef _DEBUG std::cout << "prepend\n"; #endif unsorted_column(); prepend_row( art, get_row_size() + 1 ); m_set_thread.insert( art->get_url() ); goto_top(); update_status(); } } } // // デフォルトのソート状態 // int BoardViewLog::get_default_sort_column() const { if( get_url() != URL_ALLLOG ) return BoardViewBase::get_default_sort_column(); return COL_BOARD; } int BoardViewLog::get_default_view_sort_mode() const { if( get_url() != URL_ALLLOG ) return BoardViewBase::get_default_view_sort_mode(); return SORTMODE_ASCEND; } int BoardViewLog::get_default_view_sort_pre_column() const { if( get_url() != URL_ALLLOG ) return BoardViewBase::get_default_view_sort_pre_column(); return COL_ID; } int BoardViewLog::get_default_view_sort_pre_mode() const { if( get_url() != URL_ALLLOG ) return BoardViewBase::get_default_view_sort_pre_mode(); return SORTMODE_ASCEND; } jdim-0.7.0/src/board/boardviewlog.h000066400000000000000000000023711417047150700171650ustar00rootroot00000000000000// ライセンス: GPL2 // ログ一覧ビュー #ifndef _BOARDVIEWLOG_H #define _BOARDVIEWLOG_H #include "boardviewbase.h" #include #include namespace BOARD { class BoardViewLog : public BOARD::BoardViewBase { std::unordered_set< std::string > m_set_thread; public: explicit BoardViewLog( const std::string& url ); ~BoardViewLog(); void stop() override; void reload() override; void show_view() override; void update_boardname() override; void update_item( const std::string& url, const std::string& id ) override; protected: // デフォルトのソート状態 int get_default_sort_column() const override; int get_default_view_sort_mode() const override; int get_default_view_sort_pre_column() const override; int get_default_view_sort_pre_mode() const override; private: void slot_search_fin( const std::string& id ); void slot_abone_thread() override; // ソート列やソートモードの保存 void save_sort_columns() override {} // 保存しない // 列幅の保存 void save_column_width() override {} // 保存しない }; } #endif jdim-0.7.0/src/board/boardviewnext.cpp000066400000000000000000000134761417047150700177250ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "boardadmin.h" #include "boardviewnext.h" #include "dbtree/interface.h" #include "dbtree/articlebase.h" #include "skeleton/msgdiag.h" #include "jdlib/tfidf.h" #include "config/globalconf.h" #include "session.h" #include using namespace BOARD; BoardViewNext::BoardViewNext( const std::string& url, const std::string& url_pre_article ) : BoardViewBase( url, false ), m_url_pre_article( url_pre_article ) { set_writeable( false ); #ifdef _DEBUG std::cout << "BoardViewNext::BoardViewNext : url = " << get_url() << std::endl; #endif } BoardViewNext::~BoardViewNext() { #ifdef _DEBUG std::cout << "BoardViewNext::~BoardViewNext : url = " << get_url() << std::endl; #endif } // // リロード // void BoardViewNext::reload() { // オフライン if( ! SESSION::is_online() ){ SKELETON::MsgDiag mdiag( get_parent_win(), "オフラインです" ); mdiag.run(); return; } show_view(); } // // view更新 // // subject.txtのロードが終わったら呼ばれる // void BoardViewNext::update_view() { std::vector< NEXT_ITEM > next_items; update_by_tfidf( next_items ); std::vector< DBTREE::ArticleBase* >list_article; std::transform( next_items.cbegin(), next_items.cend(), std::back_inserter( list_article ), []( const NEXT_ITEM& i ) { return i.article; } ); const bool loading_fin = true; update_view_impl( list_article, loading_fin ); } // // TFIDFで次スレ検索 // void BoardViewNext::update_by_tfidf( std::vector< NEXT_ITEM >& next_items ) { const Glib::ustring subject_src = DBTREE::article_subject( m_url_pre_article ); const time_t since_src = DBTREE::article_since_time( m_url_pre_article ); #ifdef _DEBUG const int code = DBTREE::board_code( get_url_board() ); std::cout << "BoardViewNext::update_by_tfidf " << get_url() << " code = " << code << " " << subject_src << std::endl << "since_src = " << since_src << std::endl; #endif // 高速化のためデータベースに直接アクセス const std::vector< DBTREE::ArticleBase* >& list_subject = DBTREE::board_list_subject( get_url_board() ); if( ! list_subject.size() ) return; // 単語ベクトル作成 MISC::VEC_WORDS vec_words; MISC::tfidf_create_vec_words( vec_words, subject_src ); // IDFベクトル計算 MISC::VEC_IDF vec_idf; MISC::tfidf_create_vec_idf_from_board( vec_idf, subject_src, list_subject, vec_words ); // subject_src の TFIDFベクトル計算 MISC::VEC_TFIDF vec_tfidf_src; MISC::VEC_TFIDF vec_tfidf; vec_tfidf_src.resize( vec_words.size() ); vec_tfidf.resize( vec_words.size() ); MISC::tfidf_calc_vec_tfifd( vec_tfidf_src, subject_src, vec_idf, vec_words ); // 類似度検索 for( DBTREE::ArticleBase* article : list_subject ) { NEXT_ITEM item; // 読み込み済みのスレは除外 item.article = article; if( item.article->get_number_load() ) continue; item.since = item.article->get_since_time(); const Glib::ustring subject = item.article->get_subject(); MISC::tfidf_calc_vec_tfifd( vec_tfidf, subject, vec_idf, vec_words ); item.value = ( int )( MISC::tfidf_cos_similarity( vec_tfidf_src, vec_tfidf ) * 10 + .5 ); if( item.value >= CONFIG::get_threshold_next() ){ #ifdef _DEBUG std::cout << item.value << " , " << item.since << " | " << subject << std::endl; #endif std::vector< NEXT_ITEM >::iterator it_next_items = next_items.begin(); for( ; it_next_items != next_items.end(); ++it_next_items ){ // next が src よりも以前に立てられて、item が src よりも後に立てられた // 時は item を前に挿入する if( ( *it_next_items ).since < since_src && item.since > since_src ){ next_items.insert( it_next_items, item ); break; } else if( ( ( *it_next_items ).since > since_src && item.since > since_src ) || ( ( *it_next_items ).since < since_src && item.since < since_src ) ){ // value -> since の優先度で挿入 if( ( *it_next_items ).value < item.value ){ next_items.insert( it_next_items, item ); break; } else if( ( *it_next_items ).value == item.value ){ if( ( *it_next_items ).since > item.since ){ next_items.insert( it_next_items, item ); break; } } } } if( it_next_items == next_items.end() ) next_items.push_back( item ); } } } void BoardViewNext::slot_abone_thread() { SKELETON::MsgDiag mdiag( get_parent_win(), "次スレ検索ではあぼ〜ん出来ません" ); mdiag.run(); return; } // // 板名更新 // void BoardViewNext::update_boardname() { const std::string title = "[ 次スレ検索 ] - " + DBTREE::article_subject( m_url_pre_article ); // ウィンドウタイトル表示 set_title( title ); BOARD::get_admin()->set_command( "set_title", get_url(), get_title() ); // タブに名前をセット BOARD::get_admin()->set_command( "set_tablabel", get_url(), title ); } // // デフォルトのソート状態 // int BoardViewNext::get_default_sort_column() const { return COL_ID; } int BoardViewNext::get_default_view_sort_mode() const { return SORTMODE_ASCEND; } int BoardViewNext::get_default_view_sort_pre_column() const { return COL_ID; } int BoardViewNext::get_default_view_sort_pre_mode() const { return SORTMODE_ASCEND; } jdim-0.7.0/src/board/boardviewnext.h000066400000000000000000000030521417047150700173570ustar00rootroot00000000000000// ライセンス: GPL2 // 次スレ検索ビュー #ifndef _BOARDVIEWNEXT_H #define _BOARDVIEWNEXT_H #include "boardviewbase.h" #include namespace BOARD { typedef struct { DBTREE::ArticleBase* article; int value; time_t since; } NEXT_ITEM; class BoardViewNext : public BOARD::BoardViewBase { std::string m_url_pre_article; public: BoardViewNext( const std::string& url, const std::string& url_pre_article ); ~BoardViewNext(); void reload() override; void update_view() override; void update_boardname() override; protected: // デフォルトのソート状態 int get_default_sort_column() const override; int get_default_view_sort_mode() const override; int get_default_view_sort_pre_column() const override; int get_default_view_sort_pre_mode() const override; private: // TFIDFで次スレ検索 void update_by_tfidf( std::vector< NEXT_ITEM >& next_items ); // 次スレ移行処理に使用する前スレのアドレス // 次スレ移行処理に使用する。BoardViewBase::open_row()を参照せよ std::string get_url_pre_article() const override { return m_url_pre_article; } void slot_abone_thread() override; // ソート列やソートモードの保存 void save_sort_columns() override {} // 保存しない // 列幅の保存 void save_column_width() override {} // 保存しない }; } #endif jdim-0.7.0/src/board/boardviewsidebar.cpp000066400000000000000000000101121417047150700203400ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "boardadmin.h" #include "boardviewsidebar.h" #include "skeleton/msgdiag.h" #include "dbtree/interface.h" #include "dbtree/articlebase.h" #include "history/historymanager.h" #include "global.h" #include "session.h" #include "updatemanager.h" #include using namespace BOARD; BoardViewSidebar::BoardViewSidebar( const std::string& url, const bool set_history ) : BoardViewBase( url, true ) , m_sidebar_url{ url.substr( 0, url.find( SIDEBAR_SIGN ) ) } , m_set_history( set_history ) { m_dirid = atoi( url.substr( url.find( SIDEBAR_SIGN ) + strlen( SIDEBAR_SIGN ) ).c_str() ); set_writeable( false ); set_load_subject_txt( false ); #ifdef _DEBUG std::cout << "BoardViewSidebar::BoardViewSidebar : sidebar_url = " << m_sidebar_url << " dirid = " << m_dirid << " url = " << get_url() << std::endl; #endif } BoardViewSidebar::~BoardViewSidebar() { #ifdef _DEBUG std::cout << "BoardViewSidebar::~BoardViewSidebar : url = " << get_url() << std::endl; #endif } // // リロード // void BoardViewSidebar::reload() { show_view(); } // // ビュー表示 // void BoardViewSidebar::show_view() { #ifdef _DEBUG std::cout << "BoardViewSidebar::show_view " << get_url() << std::endl; #endif BoardViewBase::show_view(); std::vector< std::string > list_url; SESSION::get_sidebar_threads( m_sidebar_url, m_dirid, list_url ); if( list_url.empty() ) return; std::vector< DBTREE::ArticleBase* > list_article; m_set_thread.clear(); const size_t size = list_url.size(); list_article.reserve( size ); m_set_thread.reserve( size ); for( const std::string& url : list_url ) { DBTREE::ArticleBase* const art = DBTREE::get_article( url ); const std::string& article_url = art->get_url(); list_article.push_back( art ); m_set_thread.insert( article_url ); if( SESSION::is_online() ) { CORE::get_checkupdate_manager()->push_back( DBTREE::url_dat( article_url ), false ); } } const bool loading_fin = true; update_view_impl( list_article, loading_fin ); // 板の履歴に登録 if( m_set_history ){ HISTORY::append_history( URL_HISTBOARDVIEW, get_url(), get_title(), TYPE_VBOARD ); } // 更新チェック if( SESSION::is_online() ) CORE::get_checkupdate_manager()->run(); } void BoardViewSidebar::slot_abone_thread() { SKELETON::MsgDiag mdiag( get_parent_win(), "お気に入り一覧ではあぼ〜ん出来ません" ); mdiag.run(); return; } // // 板名更新 // void BoardViewSidebar::update_boardname() { const std::string title = SESSION::get_sidebar_dirname( m_sidebar_url, m_dirid ); // ウィンドウタイトル表示 set_title( title ); BOARD::get_admin()->set_command( "set_title", get_url(), get_title() ); // タブに名前をセット BOARD::get_admin()->set_command( "set_tablabel", get_url(), title ); } // // 特定の行だけの表示内容更新 // // url : subject.txt のアドレス // id : DAT の ID(拡張子付き), empty なら全ての行の表示内容を更新する // void BoardViewSidebar::update_item( const std::string& url, const std::string& id ) { const std::string url_dat = DBTREE::url_datbase( url ) + id; if( id.empty() || m_set_thread.find( url_dat ) != m_set_thread.end() ){ #ifdef _DEBUG std::cout << "BoardViewSidebar::update_item " << get_url() << std::endl << "url = " << url << " id = " << id << " url_dat = " << url_dat << std::endl; #endif BoardViewBase::update_item( url, id ); } } // // デフォルトのソート状態 // int BoardViewSidebar::get_default_sort_column() const { return COL_ID; } int BoardViewSidebar::get_default_view_sort_mode() const { return SORTMODE_ASCEND; } int BoardViewSidebar::get_default_view_sort_pre_column() const { return COL_ID; } int BoardViewSidebar::get_default_view_sort_pre_mode() const { return SORTMODE_ASCEND; } jdim-0.7.0/src/board/boardviewsidebar.h000066400000000000000000000025101417047150700200100ustar00rootroot00000000000000// ライセンス: GPL2 // サイドバー一覧ビュー #ifndef _BOARDVIEWSIDEBAR_H #define _BOARDVIEWSIDEBAR_H #include "boardviewbase.h" #include #include namespace BOARD { class BoardViewSidebar : public BOARD::BoardViewBase { std::string m_sidebar_url; size_t m_dirid; bool m_set_history; std::unordered_set< std::string > m_set_thread; public: BoardViewSidebar( const std::string& url, const bool set_history ); ~BoardViewSidebar(); void stop() override {} void reload() override; void show_view() override; void update_boardname() override; void update_item( const std::string& url, const std::string& id ) override; protected: // デフォルトのソート状態 int get_default_sort_column() const override; int get_default_view_sort_mode() const override; int get_default_view_sort_pre_column() const override; int get_default_view_sort_pre_mode() const override; private: void slot_abone_thread() override; // ソート列やソートモードの保存 void save_sort_columns() override {} // 保存しない // 列幅の保存 void save_column_width() override {} // 保存しない }; } #endif jdim-0.7.0/src/board/columns.h000066400000000000000000000041121417047150700161540ustar00rootroot00000000000000// ライセンス: GPL2 // コラム #ifndef _BOARDCOLUMNS_H #define _BOARDCOLUMNS_H #include "boardcolumnsid.h" #include #include namespace DBTREE { class ArticleBase; } namespace BOARD { // 列 class TreeColumns : public Gtk::TreeModel::ColumnRecord { public: Gtk::TreeModelColumn< Glib::RefPtr< Gdk::Pixbuf > > m_col_mark; Gtk::TreeModelColumn< int > m_col_id; Gtk::TreeModelColumn< Glib::ustring > m_col_subject; Gtk::TreeModelColumn< int > m_col_res; Gtk::TreeModelColumn< Glib::ustring > m_col_str_load; Gtk::TreeModelColumn< Glib::ustring > m_col_str_new; Gtk::TreeModelColumn< Glib::ustring > m_col_since; Gtk::TreeModelColumn< Glib::ustring > m_col_write; Gtk::TreeModelColumn< Glib::ustring > m_col_access; Gtk::TreeModelColumn< int > m_col_speed; Gtk::TreeModelColumn< int > m_col_diff; Gtk::TreeModelColumn< Glib::ustring > m_col_board; // 以下は不可視 Gtk::TreeModelColumn< int > m_col_mark_val; Gtk::TreeModelColumn< bool > m_col_drawbg; // true なら背景を塗る Gtk::TreeModelColumn< int > m_col_new; Gtk::TreeModelColumn< time_t > m_col_write_t; Gtk::TreeModelColumn< time_t > m_col_access_t; Gtk::TreeModelColumn< DBTREE::ArticleBase* > m_col_article; TreeColumns(){ add( m_col_mark ); add( m_col_id ); add( m_col_subject ); add( m_col_res ); add( m_col_str_load ); add( m_col_str_new ); add( m_col_since ); add( m_col_write ); add( m_col_access ); add( m_col_speed ); add( m_col_diff ); add( m_col_board ); add( m_col_mark_val ); add( m_col_drawbg ); add( m_col_new ); add( m_col_write_t ); add( m_col_access_t ); add( m_col_article ); } ~TreeColumns() noexcept = default; }; } #endif jdim-0.7.0/src/board/meson.build000066400000000000000000000004731417047150700164730ustar00rootroot00000000000000sources = [ 'boardadmin.cpp', 'boardview.cpp', 'boardviewbase.cpp', 'boardviewlog.cpp', 'boardviewnext.cpp', 'boardviewsidebar.cpp', 'preference.cpp', 'toolbar.cpp', ] board_lib = static_library( 'board', sources, dependencies : gtkmm_dep, include_directories : include_directories('..'), ) jdim-0.7.0/src/board/preference.cpp000066400000000000000000000632161417047150700171570ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "preference.h" #include "dbtree/interface.h" #include "dbtree/boardbase.h" #include "skeleton/msgdiag.h" #include "jdlib/miscutil.h" #include "jdlib/misctime.h" #include "config/globalconf.h" #include "cache.h" #include "command.h" #include "global.h" #include "viewfactory.h" using namespace BOARD; Preferences::Preferences( Gtk::Window* parent, const std::string& url, const std::string& command ) : SKELETON::PrefDiag( parent, url ) , m_frame_write( "書き込み設定" ) , m_entry_writename( true, "名前:" ) , m_entry_writemail( true, "メール:" ) , m_check_noname( "名前欄が空白の時は書き込まない" ) , m_bt_clear_post_history( "この板にある全スレの書き込み履歴クリア" ) , m_bt_set_default_namemail( "デフォルト" ) , m_frame_cookie( "クッキーと書き込みキーワード" ) , m_button_cookie( "削除" ) , m_check_live( "実況する" ) , m_vbox_network{ Gtk::ORIENTATION_VERTICAL, 8 } , m_hbox_agent{ Gtk::ORIENTATION_HORIZONTAL, 2 } , m_label_agent{ "_User-Agent:", true } , m_proxy_frame( "読み込み用" ) , m_proxy_frame_w( "書き込み用" ) , m_label_name( false, "板タイトル:", DBTREE::board_name( get_url() ) ) , m_label_url( false, "板のURL:", DBTREE::url_boardbase( get_url() ) ) , m_label_cache( false, "ローカルキャッシュのルートパス", CACHE::path_board_root( DBTREE::url_boardbase( get_url() ) ) ) , m_label_noname( false, "デフォルト名無し:", DBTREE::default_noname( get_url() ) ) , m_label_max_line( false, "1レスの最大改行数:" ) , m_label_max_byte( false, "1レスの最大バイト数:" ) , m_label_last_access( false, "最終アクセス日時 :" ) , m_label_modified( false, "最終更新日時 :" ) , m_button_clearmodified( "日時クリア" ) , m_label_samba( false, "書き込み規制秒数 (Samba24) :" ) , m_button_clearsamba( "秒数クリア" ) , m_check_oldlog( "過去ログを表示する" ) , m_hbox_low_number{ Gtk::ORIENTATION_HORIZONTAL, 4 } , m_hbox_high_number{ Gtk::ORIENTATION_HORIZONTAL, 4 } , m_button_remove_old_title( "dat落ちしたスレのタイトルを削除する" ) { m_edit_cookies.set_editable( false ); // 書き込み設定 const int samba_sec = DBTREE::board_samba_sec( get_url() ); if( ! samba_sec ) m_label_samba.set_text( "未取得" ); else m_label_samba.set_text( std::to_string( samba_sec ) ); m_button_clearsamba.signal_clicked().connect( sigc::mem_fun(*this, &Preferences::slot_clear_samba ) ); m_hbox_samba.pack_start( m_label_samba ); m_hbox_samba.pack_start( m_button_clearsamba, Gtk::PACK_SHRINK ); m_check_noname.set_active( DBTREE::board_check_noname( get_url() ) ); m_entry_writename.set_text( DBTREE::board_get_write_name( get_url() ) ); if( m_entry_writename.get_text().empty() ) m_entry_writename.set_text( CONFIG::get_write_name() ); // JD_NAME_BLANK の場合空白をセットする else if( m_entry_writename.get_text() == JD_NAME_BLANK ) m_entry_writename.set_text( std::string() ); m_entry_writemail.set_text( DBTREE::board_get_write_mail( get_url() ) ); if( m_entry_writemail.get_text().empty() ) m_entry_writemail.set_text( CONFIG::get_write_mail() ); // JD_MAIL_BLANK の場合空白をセットする else if( m_entry_writemail.get_text() == JD_MAIL_BLANK ) m_entry_writemail.set_text( std::string() ); m_bt_clear_post_history.signal_clicked().connect( sigc::mem_fun(*this, &Preferences::slot_clear_post_history ) ); m_hbox_write1.set_spacing( 8 ); m_hbox_write1.pack_start( m_check_noname ); m_hbox_write1.pack_start( m_bt_clear_post_history ); m_bt_set_default_namemail.signal_clicked().connect( sigc::mem_fun(*this, &Preferences::slot_set_default_namemail ) ); m_hbox_write2.set_spacing( 8 ); m_hbox_write2.pack_start( m_entry_writename ); m_hbox_write2.pack_end( m_bt_set_default_namemail, Gtk::PACK_SHRINK ); m_hbox_write2.pack_end( m_entry_writemail, Gtk::PACK_SHRINK ); m_vbox_write.set_border_width( 8 ); m_vbox_write.set_spacing( 8 ); m_vbox_write.pack_start( m_hbox_samba, Gtk::PACK_SHRINK ); m_vbox_write.pack_start( m_hbox_write1, Gtk::PACK_SHRINK ); m_vbox_write.pack_start( m_hbox_write2, Gtk::PACK_SHRINK ); m_frame_write.add( m_vbox_write ); set_activate_entry( m_entry_writename ); set_activate_entry( m_entry_writemail ); // cookie と 書き込みキーワード の設定 std::string str_cookies; const std::string temp_cookies = DBTREE::board_cookie_by_host( get_url() ); if( temp_cookies.empty() ) { str_cookies = "クッキー:\n未取得\n"; } else { str_cookies = "クッキー:\n" + MISC::Iconv( temp_cookies, "UTF-8", DBTREE::board_charset( get_url() ) ) + "\n"; } std::string keyword = DBTREE::board_keyword_for_write( get_url() ); if( ! keyword.empty() ) str_cookies.append( "\n書き込み用キーワード: " + keyword + "\n" ); keyword = DBTREE::board_keyword_for_newarticle( get_url() ); if( ! keyword.empty() ) str_cookies.append( "\nスレ立て用キーワード: " + keyword + "\n" ); m_edit_cookies.set_hexpand( true ); m_edit_cookies.set_propagate_natural_height( true ); m_edit_cookies.set_text( str_cookies ); m_grid_cookie.property_margin() = 8; m_grid_cookie.attach( m_edit_cookies, 0, 0, 1, 1 ); m_grid_cookie.attach( m_button_cookie, 1, 0, 1, 1 ); m_button_cookie.set_valign( Gtk::ALIGN_END ); m_button_cookie.signal_clicked().connect( sigc::mem_fun(*this, &Preferences::slot_delete_cookie ) ); m_frame_cookie.add( m_grid_cookie ); // 実況 const int live_sec = DBTREE::board_get_live_sec( get_url() ); m_label_live.set_text( "実況時の更新間隔(秒):" ); m_spin_live.set_range( MIN_LIVE_RELOAD_SEC, 1200 ); m_spin_live.set_increments( 1, 1 ); m_spin_live.set_value( MAX( live_sec, MIN_LIVE_RELOAD_SEC ) ); if( live_sec ){ m_check_live.set_active( true ); m_spin_live.set_sensitive( true ); } else{ m_check_live.set_active( false ); m_spin_live.set_sensitive( false ); } m_check_live.signal_toggled().connect( sigc::mem_fun(*this, &Preferences::slot_check_live ) ); m_hbox_live.set_spacing( 4 ); m_hbox_live.pack_start( m_label_live, Gtk::PACK_SHRINK ); m_hbox_live.pack_start( m_spin_live, Gtk::PACK_SHRINK ); m_hbox_live.pack_start( m_check_live, Gtk::PACK_SHRINK ); set_activate_entry( m_spin_live ); // 一般ページのパッキング m_label_max_line.set_text( std::to_string( DBTREE::line_number( get_url() ) * 2 ) ); m_label_max_byte.set_text( std::to_string( DBTREE::message_count( get_url() ) ) ); m_hbox_max.pack_start( m_label_max_line ); m_hbox_max.pack_start( m_label_max_byte ); // 最大レス数 const int max_res = DBTREE::board_get_number_max_res( get_url() ); m_label_maxres.set_text( "最大レス数 (0 : 未設定):" ); m_spin_maxres.set_range( 0, CONFIG::get_max_resnumber() ); m_spin_maxres.set_increments( 1, 1 ); m_spin_maxres.set_value( max_res ); m_spin_maxres.set_sensitive( true ); m_hbox_max.pack_start( m_label_maxres, Gtk::PACK_SHRINK ); m_hbox_max.pack_start( m_spin_maxres, Gtk::PACK_SHRINK ); set_activate_entry( m_spin_maxres ); const time_t last_access = DBTREE::board_last_access_time( get_url() ); if( last_access ) m_label_last_access.set_text( MISC::timettostr( last_access, MISC::TIME_WEEK ) + " ( " + MISC::timettostr( last_access, MISC::TIME_PASSED ) + " )" ); if( DBTREE::board_date_modified( get_url() ).empty() ) m_label_modified.set_text( "未取得" ); else m_label_modified.set_text( MISC::timettostr( DBTREE::board_time_modified( get_url() ), MISC::TIME_WEEK ) + " ( " + MISC::timettostr( DBTREE::board_time_modified( get_url() ), MISC::TIME_PASSED ) + " )" ); m_button_clearmodified.signal_clicked().connect( sigc::mem_fun(*this, &Preferences::slot_clear_modified ) ); m_hbox_modified.pack_start( m_label_modified ); m_hbox_modified.pack_start( m_button_clearmodified, Gtk::PACK_SHRINK ); // 過去ログ表示 if( CONFIG::get_show_oldarticle() ){ m_check_oldlog.set_active( true ); m_check_oldlog.set_sensitive( false ); } else m_check_oldlog.set_active( DBTREE::board_show_oldlog( get_url() ) ); m_vbox.set_border_width( 16 ); m_vbox.set_spacing( 8 ); m_vbox.pack_start( m_label_name, Gtk::PACK_SHRINK ); m_vbox.pack_start( m_label_url, Gtk::PACK_SHRINK ); m_vbox.pack_start( m_label_cache, Gtk::PACK_SHRINK ); m_vbox.pack_start( m_label_noname, Gtk::PACK_SHRINK ); m_vbox.pack_start( m_hbox_max, Gtk::PACK_SHRINK ); m_vbox.pack_start( m_label_last_access, Gtk::PACK_SHRINK ); m_vbox.pack_start( m_hbox_modified, Gtk::PACK_SHRINK ); m_vbox.pack_start( m_hbox_live, Gtk::PACK_SHRINK ); m_vbox.pack_start( m_check_oldlog, Gtk::PACK_SHRINK ); m_vbox.pack_start( m_frame_write, Gtk::PACK_SHRINK ); m_vbox.pack_start( m_frame_cookie, Gtk::PACK_SHRINK ); // ローカルルール m_localrule.reset( CORE::ViewFactory( CORE::VIEW_ARTICLEINFO, get_url() ) ); // ネットワーク設定 m_vbox_network.set_border_width( 16 ); // ユーザーエージェント m_comment_agent.set_text( "通常は about:config で設定したユーザーエージェント(UA)で板にアクセスします\n" "about:config の設定と異なるUAを使用する場合はここで設定してください (最大200字)\n" "空欄または不適切な文字が含まれている状態でOKを押すと設定が消去されます" ); m_label_agent.set_hexpand( false ); m_label_agent.set_mnemonic_widget( m_entry_agent ); m_entry_agent.property_truncate_multiline() = true; m_entry_agent.set_input_purpose( Gtk::INPUT_PURPOSE_ALPHA ); m_entry_agent.set_max_length( 200 ); m_entry_agent.set_placeholder_text( "空欄のときは about:config の設定が使われます" ); const std::string& board_agent = DBTREE::board_get_board_agent( get_url() ); if( ! board_agent.empty() ) { m_entry_agent.set_text( board_agent ); } m_hbox_agent.set_margin_top( 8 ); m_hbox_agent.set_margin_bottom( 12 ); m_hbox_agent.pack_start( m_label_agent, Gtk::PACK_SHRINK ); m_hbox_agent.pack_start( m_entry_agent ); // プロキシ std::string host; m_comment_proxy.set_text( "通常は全体プロキシ設定でプロキシを設定します\n" "全体プロキシ設定と異なるプロキシを使用する場合はここで設定してください" ); switch( DBTREE::board_get_mode_local_proxy( get_url() ) ){ case DBTREE::PROXY_GLOBAL: m_proxy_frame.rd_global.set_active(); break; case DBTREE::PROXY_DISABLE: m_proxy_frame.rd_disable.set_active(); break; case DBTREE::PROXY_LOCAL: m_proxy_frame.rd_local.set_active(); break; } if( DBTREE::board_get_local_proxy_basicauth( get_url() ).empty() ) host = DBTREE::board_get_local_proxy( get_url() ); else host = DBTREE::board_get_local_proxy_basicauth( get_url() ) + "@" + DBTREE::board_get_local_proxy( get_url() ); m_proxy_frame.entry_host.set_text( host ); m_proxy_frame.entry_port.set_text( std::to_string( DBTREE::board_get_local_proxy_port( get_url() ) ) ); switch( DBTREE::board_get_mode_local_proxy_w( get_url() ) ){ case DBTREE::PROXY_GLOBAL: m_proxy_frame_w.rd_global.set_active(); break; case DBTREE::PROXY_DISABLE: m_proxy_frame_w.rd_disable.set_active(); break; case DBTREE::PROXY_LOCAL: m_proxy_frame_w.rd_local.set_active(); break; } if( DBTREE::board_get_local_proxy_basicauth_w( get_url() ).empty() ) host = DBTREE::board_get_local_proxy_w( get_url() ); else host = DBTREE::board_get_local_proxy_basicauth_w( get_url() ) + "@" + DBTREE::board_get_local_proxy_w( get_url() ); m_proxy_frame_w.entry_host.set_text( host ); m_proxy_frame_w.entry_port.set_text( std::to_string( DBTREE::board_get_local_proxy_port_w( get_url() ) ) ); m_vbox_network.pack_start( m_comment_agent, Gtk::PACK_SHRINK ); m_vbox_network.pack_start( m_hbox_agent, Gtk::PACK_SHRINK ); m_vbox_network.pack_start( m_comment_proxy, Gtk::PACK_SHRINK ); m_vbox_network.pack_start( m_proxy_frame, Gtk::PACK_SHRINK ); m_vbox_network.pack_start( m_proxy_frame_w, Gtk::PACK_SHRINK ); set_activate_entry( m_proxy_frame.entry_host ); set_activate_entry( m_proxy_frame.entry_port ); set_activate_entry( m_proxy_frame_w.entry_host ); set_activate_entry( m_proxy_frame_w.entry_port ); // あぼーん // ID std::list< std::string > list_id = DBTREE::get_abone_list_id_board( get_url() ); m_edit_id.set_text( MISC::concat_with_suffix( list_id, '\n' ) ); // name std::list< std::string > list_name = DBTREE::get_abone_list_name_board( get_url() ); m_edit_name.set_text( MISC::concat_with_suffix( list_name, '\n' ) ); // word std::list< std::string > list_word = DBTREE::get_abone_list_word_board( get_url() ); m_edit_word.set_text( MISC::concat_with_suffix( list_word, '\n' ) ); // regex std::list< std::string > list_regex = DBTREE::get_abone_list_regex_board( get_url() ); m_edit_regex.set_text( MISC::concat_with_suffix( list_regex, '\n' ) ); m_label_warning.set_text( "ここでのあぼーん設定は「" + DBTREE::board_name( get_url() ) + "」板の全スレに適用されます。\n\n" "設定のし過ぎは板内の全スレの表示速度を低下させます。\n\n設定のし過ぎに気を付けてください。\n\n" "なおNG IDはJDimを再起動するとリセットされます。" ); m_notebook_abone.append_page( m_label_warning, "注意" ); m_notebook_abone.append_page( m_edit_id, "NG ID" ); m_notebook_abone.append_page( m_edit_name, "NG 名前" ); m_notebook_abone.append_page( m_edit_word, "NG ワード" ); m_notebook_abone.append_page( m_edit_regex, "NG 正規表現" ); // スレッドあぼーん // スレ数、時間 m_label_abone_thread.set_text( "以下の数字が0の時は、設定メニューの全体あぼ〜ん設定で指定した数字が用いられます。\nまたキャッシュにログがあるスレはあぼ〜んされません。\n\n" ); m_label_low_number.set_text( "レス以下のスレをあぼ〜ん" ); m_spin_low_number.set_range( 0, CONFIG::get_max_resnumber() ); m_spin_low_number.set_increments( 1, 1 ); m_spin_low_number.set_value( DBTREE::get_abone_low_number_thread( get_url() ) ); m_hbox_low_number.pack_start( m_spin_low_number, Gtk::PACK_SHRINK ); m_hbox_low_number.pack_start( m_label_low_number, Gtk::PACK_SHRINK ); set_activate_entry( m_spin_low_number ); m_label_high_number.set_text( "レス以上のスレをあぼ〜ん" ); m_spin_high_number.set_range( 0, CONFIG::get_max_resnumber() ); m_spin_high_number.set_increments( 1, 1 ); m_spin_high_number.set_value( DBTREE::get_abone_high_number_thread( get_url() ) ); m_hbox_high_number.pack_start( m_spin_high_number, Gtk::PACK_SHRINK ); m_hbox_high_number.pack_start( m_label_high_number, Gtk::PACK_SHRINK ); set_activate_entry( m_spin_high_number ); m_label_hour.set_text( "時間以上スレ立てから経過したスレをあぼ〜ん" ); m_spin_hour.set_range( 0, 9999 ); m_spin_hour.set_increments( 1, 1 ); m_spin_hour.set_value( DBTREE::get_abone_hour_thread( get_url() ) ); m_hbox_hour.set_spacing( 4 ); m_hbox_hour.pack_start( m_spin_hour, Gtk::PACK_SHRINK ); m_hbox_hour.pack_start( m_label_hour, Gtk::PACK_SHRINK ); set_activate_entry( m_spin_hour ); m_vbox_abone_thread.set_border_width( 16 ); m_vbox_abone_thread.set_spacing( 8 ); m_vbox_abone_thread.pack_start( m_label_abone_thread, Gtk::PACK_SHRINK ); m_vbox_abone_thread.pack_start( m_hbox_low_number, Gtk::PACK_SHRINK ); m_vbox_abone_thread.pack_start( m_hbox_high_number, Gtk::PACK_SHRINK ); m_vbox_abone_thread.pack_start( m_hbox_hour, Gtk::PACK_SHRINK ); // スレあぼーん std::list< std::string > list_thread = DBTREE::get_abone_list_thread( get_url() ); m_edit_thread.set_text( MISC::concat_with_suffix( list_thread, '\n' ) ); m_button_remove_old_title.signal_clicked().connect( sigc::mem_fun(*this, &Preferences::slot_remove_old_title ) ); m_vbox_abone_title.pack_start( m_edit_thread ); m_vbox_abone_title.pack_start( m_button_remove_old_title, Gtk::PACK_SHRINK ); // スレwordあぼーん std::list< std::string > list_word_thread = DBTREE::get_abone_list_word_thread( get_url() ); m_edit_word_thread.set_text( MISC::concat_with_suffix( list_word_thread, '\n' ) ); // スレregexあぼーん std::list< std::string > list_regex_thread = DBTREE::get_abone_list_regex_thread( get_url() ); m_edit_regex_thread.set_text( MISC::concat_with_suffix( list_regex_thread, '\n' ) ); m_notebook_abone_thread.append_page( m_vbox_abone_thread, "一般" ); m_notebook_abone_thread.append_page( m_vbox_abone_title, "NG スレタイトル" ); m_notebook_abone_thread.append_page( m_edit_word_thread, "NG ワード" ); m_notebook_abone_thread.append_page( m_edit_regex_thread, "NG 正規表現" ); // SETTING.TXT m_edit_settingtxt.set_editable( false ); m_edit_settingtxt.set_text( DBTREE::settingtxt( get_url() ) ); m_notebook.append_page( m_vbox, "一般" ); const int page_localrule = 1; m_notebook.append_page( *m_localrule, "ローカルルール" ); m_notebook.append_page( m_vbox_network, "ネットワーク設定" ); const int page_abone_article = 3; m_notebook.append_page( m_notebook_abone, "あぼ〜ん設定(スレビュー)" ); m_notebook.append_page( m_notebook_abone_thread, "あぼ〜ん設定(スレ一覧)" ); m_notebook.append_page( m_edit_settingtxt, "SETTING.TXT" ); m_notebook.signal_switch_page().connect( sigc::mem_fun( *this, &Preferences::slot_switch_page ) ); get_content_area()->pack_start( m_notebook ); set_title( "「" + DBTREE::board_name( get_url() ) + "」のプロパティ" ); resize( 600, 400 ); show_all_children(); if( command == "show_localrule" ) m_notebook.set_current_page( page_localrule ); else if( command == "show_abone_article" ) m_notebook.set_current_page( page_abone_article ); } void Preferences::slot_clear_modified() { DBTREE::board_set_date_modified( get_url(), "" ); if( DBTREE::board_date_modified( get_url() ).empty() ) m_label_modified.set_text( "未取得" ); else m_label_modified.set_text( MISC::timettostr( DBTREE::board_time_modified( get_url() ), MISC::TIME_WEEK ) + " ( " + MISC::timettostr( DBTREE::board_time_modified( get_url() ), MISC::TIME_PASSED ) + " )" ); } void Preferences::slot_clear_samba() { DBTREE::board_set_samba_sec( get_url(), 0 ); const int samba_sec = DBTREE::board_samba_sec( get_url() ); if( ! samba_sec ) m_label_samba.set_text( "未取得" ); else m_label_samba.set_text( std::to_string( samba_sec ) ); } void Preferences::slot_clear_post_history() { SKELETON::MsgDiag mdiag( nullptr, "この板にある全てのスレの書き込み履歴を削除しますか?\n\nスレ数によっては時間がかかります。", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO ); if( mdiag.run() != Gtk::RESPONSE_YES ) return; DBTREE::board_clear_all_post_history( get_url() ); // スレ一覧とスレビューの表示更新 CORE::core_set_command( "update_board", DBTREE::url_boardbase( get_url() ) ); CORE::core_set_command( "redraw_article" ); } void Preferences::slot_set_default_namemail() { m_entry_writename.set_text( CONFIG::get_write_name() ); m_entry_writemail.set_text( CONFIG::get_write_mail() ); } void Preferences::slot_delete_cookie() { DBTREE::board_delete_cookies( get_url() ); DBTREE::board_set_keyword_for_write( get_url(), std::string() ); DBTREE::board_set_keyword_for_newarticle( get_url(), std::string() ); m_edit_cookies.set_text( "クッキー:\n未取得\n" ); } void Preferences::slot_check_live() { if( m_check_live.get_active() ){ m_spin_live.set_sensitive( true ); SKELETON::MsgDiag mdiag( nullptr, "実況を許された板以外では実況しないようにして下さい。\n\n" "実況状態のまま閉じたスレはJDim終了時に削除されます。" "詳しくはマニュアルの実況の項目を参照して下さい。", false, Gtk::MESSAGE_WARNING ); mdiag.run(); } else m_spin_live.set_sensitive( false ); } void Preferences::slot_remove_old_title() { if( ! DBTREE::board_list_subject( get_url() ).size() ){ SKELETON::MsgDiag mdiag( nullptr, "再読み込みしてスレ一覧を更新して下さい。", false, Gtk::MESSAGE_WARNING ); mdiag.run(); return; } const std::list< std::string > list_thread = DBTREE::get_abone_list_thread_remove( get_url() ); m_edit_thread.set_text( MISC::concat_with_suffix( list_thread, '\n' ) ); } void Preferences::slot_switch_page( Gtk::Widget*, guint page ) { if( m_notebook.get_nth_page( page ) == m_localrule.get() ){ m_localrule->set_command( "clear_screen" ); m_localrule->set_command( "append_html", DBTREE::localrule( get_url() ) ); } } // // OK 押した // void Preferences::slot_ok_clicked() { // プロクシ int mode = DBTREE::PROXY_GLOBAL; if( m_proxy_frame.rd_disable.get_active() ) mode = DBTREE::PROXY_DISABLE; else if( m_proxy_frame.rd_local.get_active() ) mode = DBTREE::PROXY_LOCAL; DBTREE::board_set_mode_local_proxy( get_url(), mode ); DBTREE::board_set_local_proxy( get_url(), MISC::remove_space( m_proxy_frame.entry_host.get_text() ) ); DBTREE::board_set_local_proxy_port( get_url(), atoi( m_proxy_frame.entry_port.get_text().c_str() ) ); mode = DBTREE::PROXY_GLOBAL; if( m_proxy_frame_w.rd_disable.get_active() ) mode = DBTREE::PROXY_DISABLE; else if( m_proxy_frame_w.rd_local.get_active() ) mode = DBTREE::PROXY_LOCAL; DBTREE::board_set_mode_local_proxy_w( get_url(), mode ); DBTREE::board_set_local_proxy_w( get_url(), MISC::remove_space( m_proxy_frame_w.entry_host.get_text() ) ); DBTREE::board_set_local_proxy_port_w( get_url(), atoi( m_proxy_frame_w.entry_port.get_text().c_str() ) ); // 書き込み設定 DBTREE::board_set_check_noname( get_url(), m_check_noname.get_active() ); std::string tmpname = m_entry_writename.get_text(); if( tmpname == CONFIG::get_write_name() ) tmpname = std::string(); else if( tmpname.empty() ) tmpname = JD_NAME_BLANK; // 空白の場合 JD_NAME_BLANK をセットする DBTREE::board_set_write_name( get_url(), tmpname ); std::string tmpmail = m_entry_writemail.get_text(); if( tmpmail == CONFIG::get_write_mail() ) tmpmail = std::string(); else if( tmpmail.empty() ) tmpmail = JD_MAIL_BLANK; // 空白の場合 JD_MAIL_BLANK をセットする DBTREE::board_set_write_mail( get_url(), tmpmail ); // 実況間隔 int live_sec = 0; if( m_check_live.get_active() ) live_sec = m_spin_live.get_value_as_int(); DBTREE::board_set_live_sec( get_url(), live_sec ); CORE::core_set_command( "redraw_article_toolbar" ); // 最大レス数 const int number_max_res = m_spin_maxres.get_value_as_int(); DBTREE::board_set_number_max_res( get_url(), number_max_res ); // 過去ログ表示 if( ! CONFIG::get_show_oldarticle() ) DBTREE::board_set_show_oldlog( get_url(), m_check_oldlog.get_active() ); // ユーザーエージェント std::string board_agent; const Glib::ustring agent_text = m_entry_agent.get_text(); if( ! agent_text.empty() ) { // 不適切な文字が含まれてないかチェックと先頭末尾の空白文字を削除する const std::string& raw = agent_text.raw(); if( std::all_of( raw.begin(), raw.end(), []( char c ) { return g_ascii_isprint( c ); } ) ) { board_agent = MISC::remove_spaces( raw ); } } if( board_agent != DBTREE::board_get_board_agent( get_url() ) ) { DBTREE::board_set_board_agent( get_url(), board_agent ); } // あぼーん再設定 std::list< std::string > list_id = MISC::get_lines( m_edit_id.get_text() ); std::list< std::string > list_name = MISC::get_lines( m_edit_name.get_text() ); std::list< std::string > list_word = MISC::get_lines( m_edit_word.get_text() ); std::list< std::string > list_regex = MISC::get_lines( m_edit_regex.get_text() ); DBTREE::reset_abone_board( get_url(), list_id, list_name, list_word, list_regex ); // スレあぼーん再設定 std::list< std::string > list_thread = MISC::get_lines( m_edit_thread.get_text() ); std::list< std::string > list_word_thread = MISC::get_lines( m_edit_word_thread.get_text() ); std::list< std::string > list_regex_thread = MISC::get_lines( m_edit_regex_thread.get_text() ); const int low_number = m_spin_low_number.get_value_as_int(); const int high_number = m_spin_high_number.get_value_as_int(); const int hour = m_spin_hour.get_value_as_int(); const bool redraw = true; // ここでスレ一覧の再描画指定をする DBTREE::reset_abone_thread( get_url(), list_thread, list_word_thread, list_regex_thread, low_number, high_number, hour, redraw ); DBTREE::board_save_info( get_url() ); } void Preferences::timeout() { if( m_localrule ) m_localrule->clock_in(); } jdim-0.7.0/src/board/preference.h000066400000000000000000000123121417047150700166130ustar00rootroot00000000000000// ライセンス: GPL2 #ifndef _BOARD_PREFERENCES_H #define _BOARD_PREFERENCES_H #include "gtkmmversion.h" #include "skeleton/view.h" #include "skeleton/prefdiag.h" #include "skeleton/editview.h" #include "skeleton/label_entry.h" #include namespace BOARD { class ProxyFrame : public Gtk::Frame { Gtk::VBox m_vbox; Gtk::HBox m_hbox; public: Gtk::RadioButton rd_global, rd_disable, rd_local; SKELETON::LabelEntry entry_host; SKELETON::LabelEntry entry_port; explicit ProxyFrame( const std::string& title ) : rd_global( "全体設定を使用する" ), rd_disable( "全体設定を無効にする" ), rd_local( "ローカル設定を使用する" ), entry_host( true, "ホスト:" ), entry_port( true, "ポート:" ) { Gtk::RadioButton::Group grp = rd_global.get_group(); rd_disable.set_group( grp ); rd_local.set_group( grp ); m_hbox.set_spacing( 8 ); m_hbox.set_border_width( 8 ); m_hbox.pack_start( entry_host ); m_hbox.pack_start( entry_port, Gtk::PACK_SHRINK ); m_vbox.set_spacing( 8 ); m_vbox.set_border_width( 8 ); m_vbox.pack_start( rd_global, Gtk::PACK_SHRINK ); m_vbox.pack_start( rd_disable, Gtk::PACK_SHRINK ); m_vbox.pack_start( rd_local, Gtk::PACK_SHRINK ); m_vbox.pack_start( m_hbox, Gtk::PACK_SHRINK ); set_label( title ); set_border_width( 8 ); add( m_vbox ); } }; class Preferences : public SKELETON::PrefDiag { Gtk::Notebook m_notebook; Gtk::VBox m_vbox; // 書き込み時のデフォルト名とメール Gtk::Frame m_frame_write; Gtk::VBox m_vbox_write; Gtk::HBox m_hbox_write1; Gtk::HBox m_hbox_write2; SKELETON::LabelEntry m_entry_writename; SKELETON::LabelEntry m_entry_writemail; Gtk::CheckButton m_check_noname; // 名無し書き込みチェック Gtk::Button m_bt_clear_post_history; Gtk::Button m_bt_set_default_namemail; // クッキー と キーワード表示 Gtk::Frame m_frame_cookie; Gtk::Grid m_grid_cookie; SKELETON::EditView m_edit_cookies; Gtk::Button m_button_cookie; // 実況の更新間隔 Gtk::HBox m_hbox_live; Gtk::Label m_label_live; Gtk::CheckButton m_check_live; Gtk::SpinButton m_spin_live; // ネットワーク設定 Gtk::Box m_vbox_network; // ユーザーエージェント Gtk::Label m_comment_agent; Gtk::Box m_hbox_agent; Gtk::Label m_label_agent; Gtk::Entry m_entry_agent; // プロキシ Gtk::Label m_comment_proxy; ProxyFrame m_proxy_frame; ProxyFrame m_proxy_frame_w; // 情報 SKELETON::LabelEntry m_label_name; SKELETON::LabelEntry m_label_url; SKELETON::LabelEntry m_label_cache; SKELETON::LabelEntry m_label_noname; Gtk::HBox m_hbox_max; SKELETON::LabelEntry m_label_max_line; SKELETON::LabelEntry m_label_max_byte; // 最大レス数 Gtk::Label m_label_maxres; Gtk::SpinButton m_spin_maxres; SKELETON::LabelEntry m_label_last_access; Gtk::HBox m_hbox_modified; SKELETON::LabelEntry m_label_modified; Gtk::Button m_button_clearmodified; // samba24 Gtk::HBox m_hbox_samba; SKELETON::LabelEntry m_label_samba; Gtk::Button m_button_clearsamba; // 過去ログ表示 Gtk::CheckButton m_check_oldlog; // あぼーん Gtk::Notebook m_notebook_abone; Gtk::Label m_label_warning; SKELETON::EditView m_edit_id, m_edit_name, m_edit_word, m_edit_regex; // スレッドあぼーん Gtk::Notebook m_notebook_abone_thread; SKELETON::EditView m_edit_thread, m_edit_word_thread, m_edit_regex_thread; Gtk::VBox m_vbox_abone_thread; Gtk::Label m_label_abone_thread; Gtk::Box m_hbox_low_number; Gtk::Label m_label_low_number; Gtk::SpinButton m_spin_low_number; Gtk::Box m_hbox_high_number; Gtk::Label m_label_high_number; Gtk::SpinButton m_spin_high_number; Gtk::HBox m_hbox_hour; Gtk::Label m_label_hour; Gtk::SpinButton m_spin_hour; Gtk::VBox m_vbox_abone_title; Gtk::Button m_button_remove_old_title; // ローカルルール std::unique_ptr m_localrule; // SETTING.TXT SKELETON::EditView m_edit_settingtxt; public: Preferences( Gtk::Window* parent, const std::string& url, const std::string& command ); ~Preferences() noexcept = default; private: void slot_clear_modified(); void slot_clear_samba(); void slot_clear_post_history(); void slot_set_default_namemail(); void slot_delete_cookie(); void slot_check_live(); void slot_remove_old_title(); void slot_switch_page( Gtk::Widget*, guint page ); void slot_ok_clicked() override; void timeout() override; }; } #endif jdim-0.7.0/src/board/toolbar.cpp000066400000000000000000000075701417047150700165040ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "toolbar.h" #include "boardadmin.h" #include "control/controlutil.h" #include "control/controlid.h" #include "session.h" #include "compmanager.h" #include "global.h" using namespace BOARD; BoardToolBar::BoardToolBar() : SKELETON::ToolBar( BOARD::get_admin() ) { BoardToolBar::pack_buttons(); // JDEntry::on_key_release_event()で // CONTROL::SearchCache を有効にする add_search_control_mode( CONTROL::MODE_BOARD ); } BoardToolBar::~BoardToolBar() noexcept = default; // ボタンのパッキング // virtual void BoardToolBar::pack_buttons() { // ツールバー非表示の場合は検索バーに検索関係の wiget を表示する if( SESSION::get_show_board_toolbar() ) pack_toolbar(); else pack_search_toolbar(); set_relief(); show_all_children(); } void BoardToolBar::pack_toolbar() { #ifdef _DEBUG std::cout << "BoardToolBar::pack_toolbar\n"; #endif int num = 0; for(;;){ int item = SESSION::get_item_board_toolbar( num ); if( item == ITEM_END ) break; switch( item ){ case ITEM_NEWARTICLE: if( auto button = get_button_write() ) { get_buttonbar().append( *button ); button->set_label( CONTROL::get_label( CONTROL::NewArticle ) ); set_tooltip( *button, CONTROL::get_label_motions( CONTROL::NewArticle ) ); } break; case ITEM_SEARCHBOX: get_buttonbar().append( *get_tool_search( CORE::COMP_SEARCH_BOARD ) ); break; case ITEM_SEARCH_NEXT: get_buttonbar().append( *get_button_down_search() ); break; case ITEM_SEARCH_PREV: get_buttonbar().append( *get_button_up_search() ); break; case ITEM_RELOAD: get_buttonbar().append( *get_button_reload() ); break; case ITEM_STOPLOADING: get_buttonbar().append( *get_button_stop() ); break; case ITEM_APPENDFAVORITE: get_buttonbar().append( *get_button_favorite() ); set_tooltip( *get_button_favorite(), CONTROL::get_label_motions( CONTROL::AppendFavorite ) + "\n\nスレ一覧のタブか選択したスレをお気に入りに直接D&Dしても登録可能" ); break; case ITEM_DELETE: get_buttonbar().append( *get_button_delete() ); break; case ITEM_QUIT: get_buttonbar().append( *get_button_close() ); break; case ITEM_BACK: get_buttonbar().append( *get_button_back() ); break; case ITEM_FORWARD: get_buttonbar().append( *get_button_forward() ); break; case ITEM_LOCK: get_buttonbar().append( *get_button_lock() ); break; case ITEM_CLEAR_HIGHLIGHT: get_buttonbar().append( *get_button_clear_highlight() ); break; case ITEM_SEPARATOR: pack_separator(); break; } ++num; } } void BoardToolBar::pack_search_toolbar() { #ifdef _DEBUG std::cout << "BoardToolBar::pack_search_toolbar\n"; #endif get_searchbar()->append( *get_tool_search( CORE::COMP_SEARCH_BOARD ) ); get_searchbar()->append( *get_button_down_search() ); get_searchbar()->append( *get_button_up_search() ); get_searchbar()->append( *get_button_close_searchbar() ); } // ツールバー表示切り替え時に検索関係の wiget の位置を変更する void BoardToolBar::unpack_pack() { unpack_buttons(); unpack_search_buttons(); pack_buttons(); } jdim-0.7.0/src/board/toolbar.h000066400000000000000000000011161417047150700161370ustar00rootroot00000000000000// ライセンス: GPL2 // ツールバーのクラス #ifndef _BOARD_TOOLBAR_H #define _BOARD_TOOLBAR_H #include #include "skeleton/toolbar.h" namespace BOARD { class BoardToolBar : public SKELETON::ToolBar { public: BoardToolBar(); ~BoardToolBar() noexcept; // ツールバー表示切り替え時に検索関係の wiget の位置を変更する void unpack_pack(); protected: void pack_buttons() override; private: void pack_toolbar(); void pack_search_toolbar(); }; } #endif jdim-0.7.0/src/boardcolumnsid.h000066400000000000000000000014661417047150700164230ustar00rootroot00000000000000// スレ一覧の列IDと並び替えモード #ifndef _BOARDCOLUMNS_ID_H #define _BOARDCOLUMNS_ID_H // 列ID enum { COL_MARK = 0, COL_ID, COL_BOARD, COL_SUBJECT, COL_RES, COL_STR_LOAD, COL_STR_NEW, COL_SINCE, COL_WRITE, COL_ACCESS, COL_SPEED, COL_DIFF, COL_VISIBLE_END, // 以下は不可視 COL_MARK_VAL = COL_VISIBLE_END, COL_DRAWBG, COL_NEW, COL_WRITE_T, COL_ACCESS_T, COL_ARTICLE, COL_NUM_COL }; // 並び替えのモード enum { SORTMODE_ASCEND = 0, SORTMODE_DESCEND, SORTMODE_MARK1, // 通常 SORTMODE_MARK2, // 新着をキャッシュの上に。後は通常 SORTMODE_MARK3, // 新着を一番上に。後は通常 SORTMODE_MARK4, // 反転 SORTMODE_NUM }; #endif jdim-0.7.0/src/boarditemmenupref.cpp000066400000000000000000000032771417047150700174630ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "boarditemmenupref.h" #include "icons/iconmanager.h" #include "skeleton/msgdiag.h" #include "global.h" #include "session.h" #include "command.h" using namespace CORE; BoardItemMenuPref::BoardItemMenuPref( Gtk::Window* parent, const std::string& url ) : SKELETON::SelectItemPref( parent, url ) { // デフォルトの項目を設定 append_default_pair( ITEM_NAME_BOOKMARK ); append_default_pair( ITEM_NAME_OPENARTICLETAB ); append_default_pair( ITEM_NAME_OPEN_BROWSER ); append_default_pair( ITEM_NAME_REGETARTICLE ); append_default_pair( ITEM_NAME_COPY_URL ); append_default_pair( ITEM_NAME_COPY_TITLE_URL_THREAD ); append_default_pair( ITEM_NAME_SAVE_DAT ); append_default_pair( ITEM_NAME_FAVORITE_ARTICLE ); append_default_pair( ITEM_NAME_NEXTARTICLE ); append_default_pair( ITEM_NAME_ABONE_ARTICLE ); append_default_pair( ITEM_NAME_DELETE ); append_default_pair( ITEM_NAME_PREF_THREAD ); append_default_pair( ITEM_NAME_PREF_BOARD ); append_default_pair( ITEM_NAME_ETC ); append_default_pair( ITEM_NAME_SEPARATOR ); // 文字列を元に行を追加 append_rows( SESSION::get_items_board_menu_str() ); set_title( "コンテキストメニュー項目設定(スレ一覧)" ); } // // OKを押した // void BoardItemMenuPref::slot_ok_clicked() { SKELETON::MsgDiag mdiag( nullptr, "次に開いたスレ一覧から有効になります" ); mdiag.run(); SESSION::set_items_board_menu_str( get_items() ); } // // デフォルトボタン // void BoardItemMenuPref::slot_default() { append_rows( SESSION::get_items_board_menu_default_str() ); } jdim-0.7.0/src/boarditemmenupref.h000066400000000000000000000011171417047150700171170ustar00rootroot00000000000000// ライセンス: GPL2 // スレ一覧のコンテキストメニューの表示項目設定 #ifndef _BOARDITEMMENUPREF_H #define _BOARDITEMMENUPREF_H #include "skeleton/selectitempref.h" namespace CORE { class BoardItemMenuPref : public SKELETON::SelectItemPref { public: BoardItemMenuPref( Gtk::Window* parent, const std::string& url ); ~BoardItemMenuPref() noexcept = default; private: // OKボタン void slot_ok_clicked() override; // デフォルトボタン void slot_default() override; }; } #endif jdim-0.7.0/src/boarditempref.cpp000066400000000000000000000064221417047150700165710ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "boarditempref.h" #include "icons/iconmanager.h" #include "jdlib/miscutil.h" #include "global.h" #include "session.h" #include "command.h" using namespace CORE; BoardItemColumnPref::BoardItemColumnPref( Gtk::Window* parent, const std::string& url ) : SKELETON::SelectItemPref( parent, url ) { // デフォルトの項目を設定( 無効にする場合には後に", Glib::RefPtr< Gdk::Pixbuf >(), false" を付ける ) append_default_pair( ITEM_NAME_MARK ); append_default_pair( ITEM_NAME_ID ); append_default_pair( ITEM_NAME_BOARD ); append_default_pair( ITEM_NAME_NAME ); append_default_pair( ITEM_NAME_RES ); append_default_pair( ITEM_NAME_LOAD ); append_default_pair( ITEM_NAME_NEW ); append_default_pair( ITEM_NAME_SINCE ); append_default_pair( ITEM_NAME_LASTWRITE ); append_default_pair( ITEM_NAME_ACCESS ); append_default_pair( ITEM_NAME_SPEED ); append_default_pair( ITEM_NAME_DIFF ); // 文字列を元に列を追加 append_rows( SESSION::get_items_board_col_str() ); set_title( "リスト項目設定(スレ一覧)" ); } // OKを押した void BoardItemColumnPref::slot_ok_clicked() { SESSION::set_items_board_col_str( get_items() ); CORE::core_set_command( "update_board_columns" ); } // // デフォルトボタン // void BoardItemColumnPref::slot_default() { append_rows( SESSION::get_items_board_col_default_str() ); } /////////////////////////////////// BoardItemPref::BoardItemPref( Gtk::Window* parent, const std::string& url ) : SKELETON::SelectItemPref( parent, url ) { // デフォルトの項目を設定 append_default_pair( ITEM_NAME_NEWARTICLE, ICON::get_icon( ICON::WRITE ) ); append_default_pair( ITEM_NAME_SEARCHBOX, ICON::get_icon( ICON::TRANSPARENT ) ); append_default_pair( ITEM_NAME_SEARCH_NEXT, ICON::get_icon( ICON::SEARCH_NEXT) ); append_default_pair( ITEM_NAME_SEARCH_PREV, ICON::get_icon( ICON::SEARCH_PREV ) ); append_default_pair( ITEM_NAME_RELOAD, ICON::get_icon( ICON::RELOAD ) ); append_default_pair( ITEM_NAME_STOPLOADING, ICON::get_icon( ICON::STOPLOADING ) ); append_default_pair( ITEM_NAME_APPENDFAVORITE, ICON::get_icon( ICON::APPENDFAVORITE ) ); append_default_pair( ITEM_NAME_DELETE, ICON::get_icon( ICON::DELETE ) ); append_default_pair( ITEM_NAME_QUIT, ICON::get_icon( ICON::QUIT ) ); append_default_pair( ITEM_NAME_BACK, ICON::get_icon( ICON::BACK ) ); append_default_pair( ITEM_NAME_FORWARD, ICON::get_icon( ICON::FORWARD ) ); append_default_pair( ITEM_NAME_LOCK, ICON::get_icon( ICON::LOCK ) ); append_default_pair( ITEM_NAME_CLEAR_HIGHLIGHT, ICON::get_icon( ICON::CLEAR_SEARCH ) ); append_default_pair( ITEM_NAME_SEPARATOR, ICON::get_icon( ICON::TRANSPARENT ) ); // 文字列を元に列を追加 append_rows( SESSION::get_items_board_toolbar_str() ); set_title( "ツールバー項目表示設定(スレ一覧)" ); } // OKを押した void BoardItemPref::slot_ok_clicked() { SESSION::set_items_board_toolbar_str( get_items() ); CORE::core_set_command( "update_board_toolbar_button" ); } // // デフォルトボタン // void BoardItemPref::slot_default() { append_rows( SESSION::get_items_board_toolbar_default_str() ); } jdim-0.7.0/src/boarditempref.h000066400000000000000000000016511417047150700162350ustar00rootroot00000000000000// ライセンス: GPL2 // スレ一覧のツールバーと列の表示項目設定 #ifndef _BOARDITEMPREF_H #define _BOARDITEMPREF_H #include "skeleton/selectitempref.h" namespace CORE { class BoardItemColumnPref : public SKELETON::SelectItemPref { public: BoardItemColumnPref( Gtk::Window* parent, const std::string& url ); ~BoardItemColumnPref() noexcept = default; private: // OK押した void slot_ok_clicked() override; // デフォルトボタン void slot_default() override; }; class BoardItemPref : public SKELETON::SelectItemPref { public: BoardItemPref( Gtk::Window* parent, const std::string& url ); ~BoardItemPref() noexcept = default; private: // OK押した void slot_ok_clicked() override; // デフォルトボタン void slot_default() override; }; } #endif jdim-0.7.0/src/browserpref.h000066400000000000000000000047221417047150700157540ustar00rootroot00000000000000// ライセンス: GPL2 // ブラウザ設定ダイアログ #ifndef _BROWSER_H #define _BROWSER_H #include "skeleton/prefdiag.h" #include "config/globalconf.h" #include "jdlib/miscutil.h" #include "browsers.h" namespace CORE { class BrowserPref : public SKELETON::PrefDiag { Gtk::VBox m_vbox; Gtk::Label m_label_notice; Gtk::ComboBoxText m_combo; Gtk::Frame m_frame; Gtk::HBox m_hbox; Gtk::Entry m_entry_browser; // OK押した void slot_ok_clicked() override { CONFIG::set_browsercombo_id( m_combo.get_active_row_number() ); CONFIG::set_command_openurl( MISC::remove_space( m_entry_browser.get_text() ) ); } // コンボボックスが変わった void slot_changed(){ m_entry_browser.set_text( CORE::get_browser_name( m_combo.get_active_row_number() ) ); } public: BrowserPref( Gtk::Window* parent, const std::string& url ) : SKELETON::PrefDiag( parent, url ), m_label_notice( "使用するWebブラウザを選択して下さい\nリンククリック時に %LINK をURLに置換します" ) { const int mrg = 8; int i = 0; for(;;){ std::string label = CORE::get_browser_label( i++ ); if( label.empty() ) break; m_combo.append( label ); } m_combo.set_active( CONFIG::get_browsercombo_id() ); m_combo.signal_changed().connect( sigc::mem_fun(*this, &BrowserPref::slot_changed ) ); m_entry_browser.set_text( CONFIG::get_command_openurl() ); m_hbox.set_spacing( mrg ); m_hbox.set_border_width( mrg ); m_hbox.add( m_entry_browser ); m_frame.set_label( "ブラウザ起動コマンド" ); m_frame.add( m_hbox ); m_label_notice.set_xalign( 0 ); m_vbox.set_border_width( mrg ); m_vbox.pack_start( m_label_notice, Gtk::PACK_EXPAND_WIDGET, mrg ); m_vbox.pack_start( m_combo, Gtk::PACK_EXPAND_WIDGET, 0 ); m_vbox.pack_start( m_frame, Gtk::PACK_EXPAND_WIDGET, mrg ); get_content_area()->set_spacing( 0 ); get_content_area()->pack_start( m_vbox ); set_activate_entry( m_entry_browser ); set_title( "Webブラウザ設定" ); show_all_children(); resize( 400, 100 ); } }; } #endif jdim-0.7.0/src/browsers.cpp000066400000000000000000000020451417047150700156110ustar00rootroot00000000000000// ライセンス: GPL2 #include "browsers.h" #include "jdlib/miscutil.h" enum { MAX_TEXT = 256, BROWSER_NUM = 8, }; namespace CORE { char browsers[ BROWSER_NUM ][ 2 ][ MAX_TEXT ]={ { "ユーザ設定", "" }, { "標準ブラウザ(xdg-open)", "xdg-open \"%LINK\"" }, { "Firefox", "firefox \"%LINK\"" }, { "konqeror", "konqeror \"%LINK\"" }, { "opera 9.* 以降", "opera -remote \"openURL(%LINK,new-tab)\"" }, { "chrome", "google-chrome \"%LINK\"" }, { "chromium", "chromium \"%LINK\"" }, { "w3m", "w3m \"%LINK\"" } }; std::string get_browser_label( const int num ){ if( num >= BROWSER_NUM ) return std::string(); return browsers[ num ][ 0 ]; } std::string get_browser_name( const int num ){ if( num >= BROWSER_NUM ) return std::string(); return browsers[ num ][ 1 ]; } int get_browser_number(){ return BROWSER_NUM; } } jdim-0.7.0/src/browsers.h000066400000000000000000000004201417047150700152510ustar00rootroot00000000000000// ライセンス: GPL2 // ブラウザ設定 #ifndef _BROWSERS_H #define _BROWSERS_H #include namespace CORE { std::string get_browser_label( const int num ); std::string get_browser_name( const int num ); int get_browser_number(); } #endif jdim-0.7.0/src/buildinfo.h.sh000077500000000000000000000015411417047150700157770ustar00rootroot00000000000000#!/usr/bin/sh # Generate the header file for including git repository HEAD information. GIT="${GIT:-$(command -v git 2>/dev/null)}" GIT_HASH="" GIT_DATE="" GIT_DIRTY=0 if test -x "$GIT" ; then # Change current directory to execute git commands in source directory. if test -d "$1"; then cd "$1" fi GIT_HASH="$($GIT log --pretty=format:%h --abbrev=10 -n 1 2>/dev/null)" GIT_DATE="$($GIT log --pretty=format:%ad --date=format:%Y%m%d -n 1 2>/dev/null)" GIT_STATUS="$($GIT status -uno -s 2>/dev/null | head -n 1)" if test -n "$GIT_STATUS" ; then GIT_DIRTY=1 fi fi echo "/*" echo " * Autogenerated by the Meson build system." echo " * Do not edit, your changes will be lost." echo " */" echo "" echo "#pragma once" echo "" echo "#define GIT_HASH \"${GIT_HASH}\"" echo "#define GIT_DATE \"${GIT_DATE}\"" echo "#define GIT_DIRTY ${GIT_DIRTY}" jdim-0.7.0/src/cache.cpp000066400000000000000000000636261417047150700150220ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "cache.h" #include "skeleton/msgdiag.h" #include "skeleton/filediag.h" #include "config/globalconf.h" #include "jdlib/miscutil.h" #include "jdlib/miscmsg.h" #include "jdlib/misctime.h" #include "dbtree/interface.h" #include #include #include #include #include #include #include #include #include #include enum { MAX_SAFE_PATH = 1024 }; std::string root_path; // 設定ファイル std::string CACHE::path_conf() { return CACHE::path_root() + "jd.conf"; } std::string CACHE::path_conf_bkup() { return CACHE::path_conf() + ".bkup"; } // セッション情報ファイル std::string CACHE::path_session() { return CACHE::path_root() + "session.info"; } // ロックファイル std::string CACHE::path_lock() { const std::string jd_lock = MISC::getenv_limited( "JDIM_LOCK", MAX_SAFE_PATH ); if( ! jd_lock.empty() ) return jd_lock; return CACHE::path_root() + "JDLOCK"; } // パスワード設定ファイル std::string CACHE::path_passwd( const std::string& basename ) { return CACHE::path_root() + basename + ".conf"; } // キャッシュルートの絶対パス std::string CACHE::path_root() { if( root_path.empty() ){ std::string jdim_cache = MISC::getenv_limited( "JDIM_CACHE", MAX_SAFE_PATH ); if( jdim_cache.empty() ) { #ifdef ENABLE_COMPAT_CACHE_DIR root_path = MISC::getenv_limited( ENV_HOME, MAX_SAFE_PATH ) + "/.jd/"; if( CACHE::file_exists( root_path ) != CACHE::EXIST_DIR ) #endif { root_path = Glib::get_user_cache_dir() + "/jdim/"; } } else { root_path = std::move( jdim_cache ); } if( root_path.front() == '~' ) { std::string home = MISC::getenv_limited( ENV_HOME , MAX_SAFE_PATH ); root_path.replace( 0, 1, home ); } if( root_path.back() != '/' ) root_path.push_back( '/' ); } return root_path; } // 板リスト std::string CACHE::path_xml_listmain() { return CACHE::path_root() + "boards.xml"; } // お気に入り std::string CACHE::path_xml_favorite() { return CACHE::path_root() + "bookmark.xml"; } // 外部板設定ファイル( navi2ch 互換 ) std::string CACHE::path_etcboard() { return CACHE::path_root() + "etc.txt"; } // ユーザーコマンド設定ファイル std::string CACHE::path_usrcmd() { return CACHE::path_root() + "usrcmd.xml"; } // リンクフィルタ設定ファイル std::string CACHE::path_linkfilter() { return CACHE::path_root() + "linkfilter.xml"; } // 文字列置換設定ファイル std::string CACHE::path_replacestr() { return CACHE::path_root() + "replacestr.xml"; } // URL変換設定ファイル std::string CACHE::path_urlreplace() { return CACHE::path_root() + "urlreplace.conf"; } // 履歴 std::string CACHE::path_xml_history() { return CACHE::path_root() + "hist.xml"; } // 板履歴 std::string CACHE::path_xml_history_board() { return CACHE::path_root() + "hist_board.xml"; } // 最近閉じたスレの履歴 std::string CACHE::path_xml_history_close() { return CACHE::path_root() + "hist_close.xml"; } // 最近閉じた板の履歴 std::string CACHE::path_xml_history_closeboard() { return CACHE::path_root() + "hist_closeboard.xml"; } // 最近閉じた画像の履歴 std::string CACHE::path_xml_history_closeimg() { return CACHE::path_root() + "hist_closeimg.xml"; } // View履歴 std::string CACHE::path_xml_history_view() { return CACHE::path_root() + "hist_view.xml"; } // 板移転情報 std::string CACHE::path_movetable() { return CACHE::path_root() + "move.info"; } // キーボード設定 std::string CACHE::path_keyconf() { return CACHE::path_root() + "key.conf"; } // マウスジェスチャ設定 std::string CACHE::path_mouseconf() { return CACHE::path_root() + "mouse.conf"; } // マウスボタン設定 std::string CACHE::path_buttonconf() { return CACHE::path_root() + "button.conf"; } // 板のルートパス std::string CACHE::path_board_root( const std::string& url ) { std::string boardbase = DBTREE::url_boardbase( url ); return path_board_root_fast( boardbase ); } // // 板のルートパス(高速版) // // DBTREE::url_boardbase( url ) を使わないであらかじめ boardbase を与える // std::string CACHE::path_board_root_fast( const std::string& boardbase ) { // http:// を取り除く size_t i = boardbase.find( "://" ); if( i == std::string::npos ) return std::string(); return CACHE::path_root() + boardbase.substr( i + 3 ); } std::string CACHE::path_article_summary( const std::string& url ) { return CACHE::path_board_root( url ) + "article-summary"; } // board情報( navi2ch互換用 ) std::string CACHE::path_board_info( const std::string& url ) { return CACHE::path_board_root( url ) + "board.info"; } // board情報( jd 用 ) std::string CACHE::path_jdboard_info( const std::string& url ) { return CACHE::path_board_root( url ) + "jdboard.info"; } std::string CACHE::path_article_info_root( const std::string& url ) { return CACHE::path_board_root( url ) + "info/"; } // スレの情報ファイル( navi2ch互換、実際には使用しない ) std::string CACHE::path_article_info( const std::string& url, const std::string& id ) { std::string id_str = id; // idに拡張子が付いてたら取る size_t i = id.find( '.' ); if( i != std::string::npos ) id_str = id.substr( 0, i ); return CACHE::path_article_info_root( url ) + id_str; } // スレの拡張情報ファイル std::string CACHE::path_article_ext_info( const std::string& url, const std::string& id ) { return CACHE::path_article_info( url, id ) + ".info"; } std::string CACHE::path_dat( const std::string& url ) { // file:// の場合 if( url.c_str()[ 0 ] == 'f' && url.c_str()[ 1 ] == 'i' ) return url.substr( strlen( "file://" ) ); return CACHE::path_board_root( url ) + DBTREE::article_id( url ); } // // 画像キャッシュのルートパス // std::string CACHE::path_img_root() { return CACHE::path_root() + "image/"; } // // 画像キャッシュのinfoファイルのルートパス // std::string CACHE::path_img_info_root() { return path_img_root() + "info/"; } // // 保護画像キャッシュのルートパス // std::string CACHE::path_img_protect_root() { return CACHE::path_root() + "image_protect/"; } // // 保護画像キャッシュのinfoファイルのルートパス // std::string CACHE::path_img_protect_info_root() { return path_img_protect_root() + "info/"; } // // 画像あぼーんinfoファイルのルートパス // std::string CACHE::path_img_abone_root() { return path_root() + "image_abone/"; } // // ログのルートパス // std::string CACHE::path_logroot() { return CACHE::path_root() + "log/"; } // // 書き込みログ // std::string CACHE::path_postlog() { return path_logroot() + "postlog"; } // // メッセージログ(-lオプション) // std::string CACHE::path_msglog() { return path_logroot() + "msglog"; } // // 検索や名前などの補完情報 // std::string CACHE::path_completion( int mode ) { return CACHE::path_root() + "comp" + std::to_string( mode ) + ".info"; } // // サウンドファイルのルートパス // std::string CACHE::path_sound_root() { return CACHE::path_root() + "sound/"; } // // 画像キャッシュファイルの名前 // std::string CACHE::filename_img( const std::string& url ) { std::string file = MISC::tolower_str( url ); file = MISC::replace_str( file, "http://", "" ); file = MISC::replace_str( file, "https://", "" ); file = MISC::replace_str( file, "/", "-" ); file = MISC::url_encode( file.c_str(), file.length() ); file = MISC::replace_str( file, "%", "" ); return file; } // // 画像キャッシュ情報ファイルの名前 // std::string CACHE::filename_img_info( const std::string& url ) { return filename_img( url ) + ".info"; } // // 画像キャッシュファイルのパス // std::string CACHE::path_img( const std::string& url ) { return CACHE::path_img_root() + filename_img( url ); } // // 画像infoファイルのパス // std::string CACHE::path_img_info( const std::string& url ) { return CACHE::path_img_info_root() + filename_img_info( url ); } // // 保護画像キャッシュファイルのパス // std::string CACHE::path_img_protect( const std::string& url ) { return CACHE::path_img_protect_root() + filename_img( url ); } // // 保護画像infoファイルのパス // std::string CACHE::path_img_protect_info( const std::string& url ) { return CACHE::path_img_protect_info_root() + filename_img_info( url ); } // // 画像あぼーんinfoファイルのパス // std::string CACHE::path_img_abone( const std::string& url ) { return CACHE::path_img_abone_root() + filename_img_info( url ); } // // アスキーアートファイル // std::string CACHE::path_aalist() { return CACHE::path_root() + "aalist.txt"; } // // アスキーアートファイル格納用ディレクトリ .jd/aa/ // std::string CACHE::path_aadir() { return CACHE::path_root() + "aa/"; } // // AAの使用履歴ファイル // std::string CACHE::path_aahistory() { return CACHE::path_root() + "hist_aa.xml"; } // // テーマのルートパス // std::string CACHE::path_theme_root() { return CACHE::path_root() + "theme/"; } // // アイコンテーマのルートパス // std::string CACHE::path_theme_icon_root() { return CACHE::path_theme_root() + "icons/"; } // // css // std::string CACHE::path_css() { return CACHE::path_theme_root() + "jd.css"; } // // res.html // std::string CACHE::path_reshtml() { return CACHE::path_theme_root() + "Res.html"; } // // キャッシュのルートディレクトリをmkdir // // 例えば "/home/hoge/.jd/" を作成 // bool CACHE::mkdir_root() { std::string path_root = CACHE::path_root(); if( ! CACHE::jdmkdir( path_root ) ){ MISC::ERRMSG( "can't create " + path_root ); return false; } return true; } // // 画像キャッシュのルートディレクトリをmkdir // // 例えば "/home/hoge/.jd/image/" と "/home/hoge/.jd/image/info/" などを作成 // bool CACHE::mkdir_imgroot() { // root std::string path_img_root = CACHE::path_img_root(); if( ! CACHE::jdmkdir( path_img_root ) ){ MISC::ERRMSG( "can't create " + path_img_root ); return false; } // info ディレクトリ std::string path_info_root = CACHE::path_img_info_root(); if( ! CACHE::jdmkdir( path_info_root ) ){ MISC::ERRMSG( "can't create " + path_info_root ); return false; } // abone ディレクトリ std::string path_abone_root = CACHE::path_img_abone_root(); if( ! CACHE::jdmkdir( path_abone_root ) ){ MISC::ERRMSG( "can't create " + path_abone_root ); return false; } return true; } // // 保護画像キャッシュのルートディレクトリをmkdir // bool CACHE::mkdir_imgroot_favorite() { // root std::string path_img_root = CACHE::path_img_protect_root(); if( ! CACHE::jdmkdir( path_img_root ) ){ MISC::ERRMSG( "can't create " + path_img_root ); return false; } // info ディレクトリ std::string path_info_root = CACHE::path_img_protect_info_root(); if( ! CACHE::jdmkdir( path_info_root ) ){ MISC::ERRMSG( "can't create " + path_info_root ); return false; } return true; } // // キャッシュのひとつ上のディレクトリをmkdir // // 例えばキャッシュのルートディレクトリが "/home/hoge/.jd/hoge.2ch.net/hogeboard/" だったら // "/home/hoge/.jd/hoge.2ch.net/" を作成する // bool CACHE::mkdir_parent_of_board( const std::string& url ) { std::string path_tmp = CACHE::path_board_root( url ); size_t i = path_tmp.rfind( "/", path_tmp.length() -2 ); if( i == std::string::npos ) return false; std::string path_parent = path_tmp.substr( 0, i ); if( ! CACHE::jdmkdir( path_parent ) ){ MISC::ERRMSG( "can't create " + path_parent ); return false; } return true; } // // ある板のキャッシュのルートディレクトリをmkdir // // 例えば "/home/hoge/.jd/hoge.2ch.net/hogeboard/" と "/home/hoge/.jd/hoge.2ch.net/hogeboard/info/" を作成 // bool CACHE::mkdir_boardroot( const std::string& url ) { // root std::string path_board_root = CACHE::path_board_root( url ); if( ! CACHE::jdmkdir( path_board_root ) ){ MISC::ERRMSG( "can't create " + path_board_root ); return false; } // info ディレクトリ std::string path_info_root = CACHE::path_article_info_root( url ); if( ! CACHE::jdmkdir( path_info_root ) ){ MISC::ERRMSG( "can't create " + path_info_root ); return false; } return true; } // // ログのルートディレクトリをmkdir // bool CACHE::mkdir_logroot() { // root std::string path_logroot = CACHE::path_logroot(); if( ! CACHE::jdmkdir( path_logroot ) ){ MISC::ERRMSG( "can't create " + path_logroot ); return false; } return true; } size_t CACHE::load_rawdata( const std::string& path, std::string& str ) { str.clear(); std::ifstream fin; fin.open( to_locale_cstr( path ) ); if( !fin.is_open() ) return 0; getline( fin, str, '\0' ); fin.close(); return str.length(); } size_t CACHE::load_rawdata( const std::string& path, char* data, const size_t n ) { size_t count = 0; std::ifstream fin; fin.open( to_locale_cstr( path ), std::ios::binary ); if( !fin.is_open() ) return 0; fin.read( data, n ); count = fin.gcount(); fin.close(); return count; } size_t CACHE::save_rawdata( const std::string& path, const std::string& str, const bool append ) { return save_rawdata( path, str.c_str(), str.length(), append ); } size_t CACHE::save_rawdata( const std::string& path, const char* data, size_t n, const bool append ) { size_t count = 0; size_t byte = 0; std::ofstream fout; std::ofstream::openmode fmode; if( append ) fmode = std::ios::app | std::ios::ate | std::ios::binary; else fmode = std::ios::binary; fout.open( to_locale_cstr( path ), fmode ); if( !fout.is_open() ){ MISC::ERRMSG( "can't open " + path ); return 0; } if( append ) count = fout.tellp(); #ifdef _DEBUG std::cout << "CACHE::save_rawdata current = " << count << std::endl; #endif fout.write( data, n ); byte = fout.tellp(); byte -= count; fout.close(); #ifdef _DEBUG std::cout << "n = " << n << " byte = " << byte << std::endl; #endif if( n != byte ){ MISC::ERRMSG( "failed to save " + path ); return 0; } return byte; } int CACHE::file_exists( const std::string& path ) { struct stat buf_stat; if( path.empty() ) return EXIST_ERROR; std::string path_s = path; if( stat( to_locale_cstr( path_s ), &buf_stat ) != 0 ) return EXIST_ERROR; if( S_ISREG( buf_stat.st_mode ) ) return EXIST_FILE; if( S_ISDIR( buf_stat.st_mode ) ) return EXIST_DIR; if( S_ISFIFO( buf_stat.st_mode ) ) return EXIST_FIFO; return EXIST; } size_t CACHE::get_filesize( const std::string& path ) { struct stat buf_stat; if( stat( to_locale_cstr( path ), &buf_stat ) != 0 ) return 0; if( S_ISREG( buf_stat.st_mode ) ) return buf_stat.st_size; return 0; } time_t CACHE::get_filemtime( const std::string& path ) { struct stat buf_stat; if( stat( to_locale_cstr( path ), &buf_stat ) != 0 ) return 0; if( S_ISREG( buf_stat.st_mode ) ) return buf_stat.st_mtime; return 0; } bool CACHE::set_filemtime( const std::string& path, const time_t mtime ) { #ifdef _DEBUG std::cout << "CACHE::set_filemtime path = " << path << " mtime = " << MISC::timettostr( mtime, MISC::TIME_NORMAL ) << std::endl; #endif struct stat buf_stat; if( stat( to_locale_cstr( path ), &buf_stat ) != 0 ) return false; if( S_ISREG( buf_stat.st_mode ) ){ struct timeval tv[2]; tv[0].tv_sec = buf_stat.st_atime; tv[0].tv_usec = 0; tv[1].tv_sec = mtime; tv[1].tv_usec = 0; if( ! utimes( to_locale_cstr( path ), tv ) ) return true; } return false; } // // mkdir // bool CACHE::jdmkdir( const std::string& path ) { #ifdef _DEBUG std::cout << "CACHE::jdmkdir : path = " + path << std::endl; #endif if( CACHE::file_exists( path ) == EXIST_DIR ) return true; std::string target = path; if( path.rfind( "~/", 0 ) == 0 ){ std::string homedir = MISC::getenv_limited( ENV_HOME, MAX_SAFE_PATH ); if( homedir.empty() ) return false; target = homedir + path.substr( 2 ); } if( target[ 0 ] != '/' ) return false; if( target[ target.length() -1 ] != '/' ) target += "/"; #ifdef _DEBUG std::cout << "target = " << target << std::endl; #endif // ルートからディレクトリがあるかチェックしていく。無ければ作る size_t i = 0; while( ( i = target.find( '/', i ) ) != std::string::npos ){ ++i; std::string currentdir = target.substr( 0, i ); #ifdef _DEBUG std::cout << "mkdir " << currentdir << std::endl; #endif if( CACHE::file_exists( currentdir ) == EXIST_DIR ) continue; if( mkdir( to_locale_cstr( currentdir ), 0755 ) != 0 ){ MISC::ERRMSG( "mkdir failed " + currentdir ); return false; } } return true; } // // file copy // bool CACHE::jdcopy( const std::string& file_from, const std::string& file_to ) { struct stat buf_stat; if( stat( to_locale_cstr( file_from ), &buf_stat ) != 0 ) return false; #ifdef _DEBUG std::cout << "CACHE::jdcopy : from = " << file_from << std::endl; std::cout << "to = " << file_to << std::endl; std::cout << "read size = " << buf_stat.st_size << std::endl; #endif // 32Mより大きい画像はエラー出す if( buf_stat.st_size > 32 * 1024 * 1024 ){ MISC::ERRMSG( "CACHE::jdcopy: size is too big : " + file_from ); return false; } bool ret = false; std::string data( buf_stat.st_size, '\0' ); const std::size_t readsize = load_rawdata( file_from, &*data.begin(), buf_stat.st_size ); if( readsize ){ const std::size_t savesize = save_rawdata( file_to, data ); if( readsize == savesize ) ret = true; #ifdef _DEBUG std::cout << "save size = " << savesize << std::endl; #endif } return ret; } // // mv // bool CACHE::jdmv( const std::string& file_from, const std::string& file_to ) { if( CACHE::jdcopy( file_from, file_to ) ){ unlink( to_locale_cstr( file_from ) ); return true; } return false; } // // 保存ダイアログを表示して file_from を file_to に保存する // // parent == nullptr の時はメインウィンドウをparentにする // file_toはデフォルトの保存先 // 戻り値は保存先(保存に失敗したらempty()) // std::string CACHE::copy_file( Gtk::Window* parent, const std::string& file_from, const std::string& file_to, const int type ) { if( file_from.empty() ) return std::string(); if( file_to.empty() ) return std::string(); std::string name = MISC::get_filename( file_to ); std::string dir = MISC::get_dir( file_to ); if( dir.empty() ) dir = MISC::get_dir( file_from ); #ifdef _DEBUG std::cout << "CACHE::open_copy_diag\n"; std::cout << "from = " << file_from << std::endl; std::cout << "dir = " << dir << std::endl; std::cout << "name = " << name << std::endl; #endif std::string path_to = CACHE::open_save_diag( parent, dir, name, type ); if( path_to.empty() ) return std::string(); if( CACHE::jdcopy( file_from, path_to ) ) return path_to; else{ SKELETON::MsgDiag mdiag( parent, path_to + "\n\nの保存に失敗しました。\nハードディスクの容量やパーミッションなどを確認してください。" ); mdiag.run(); } return std::string(); } // ファイル選択ダイアログにフィルタ追加 void CACHE::add_filter_to_diag( Gtk::FileChooserDialog& diag, const int type ) { if( type == FILE_TYPE_ALL ) return; Glib::RefPtr< Gtk::FileFilter > filter = Gtk::FileFilter::create(); switch( type ) { case FILE_TYPE_TEXT: filter->set_name( "全てのテキストファイル" ); filter->add_mime_type( "text/plain" ); diag.add_filter( filter ); break; case FILE_TYPE_DAT: filter->set_name( "全てのDATファイル" ); filter->add_pattern( "*.dat" ); diag.add_filter( filter ); break; } Glib::RefPtr< Gtk::FileFilter > all = Gtk::FileFilter::create(); all->set_name( "全てのファイル" ); all->add_pattern( "*" ); diag.add_filter( all ); } // // ファイル選択ダイアログを表示する // // parent == nullptr の時はメインウィンドウをparentにする // open_path はデフォルトの参照先 // multi == true なら複数選択可能 // 戻り値は選択されたファイルのpathのリスト // std::vector< std::string > CACHE::open_load_diag( Gtk::Window* parent, const std::string& open_path, const int type, const bool multi ) { std::string dir = MISC::get_dir( open_path ); if( dir.empty() ) dir = MISC::getenv_limited( ENV_HOME, MAX_SAFE_PATH ); SKELETON::FileDiag diag( parent, "ファイルを開く", Gtk::FILE_CHOOSER_ACTION_OPEN ); diag.set_select_multiple( multi ); diag.set_current_folder( dir ); add_filter_to_diag( diag, type ); if( diag.run() == Gtk::RESPONSE_ACCEPT ) { diag.hide(); return MISC::recover_path( diag.get_filenames() ); } return {}; } // // 保存ファイル選択ダイアログを表示する // // parent == nullptr の時はメインウィンドウをparentにする // dirとnameはデフォルトの参照先 // 戻り値は選択されたファイルのpath // std::string CACHE::open_save_diag( Gtk::Window* parent, const std::string& dir, const std::string& name, const int type ) { #ifdef _DEBUG std::cout << "CACHE::open_save_diag\n"; std::cout << "dir = " << dir << std::endl; std::cout << "name = " << name << std::endl; #endif SKELETON::FileDiag diag( parent, "保存先選択", Gtk::FILE_CHOOSER_ACTION_SAVE ); if( dir.empty() ) { const std::string home = MISC::getenv_limited( ENV_HOME, MAX_SAFE_PATH ); if( ! home.empty() ) diag.set_current_folder( home ); } else { diag.set_current_folder( dir ); } diag.set_current_name( name ); add_filter_to_diag( diag, type ); if( diag.run() != Gtk::RESPONSE_ACCEPT ) return std::string(); diag.hide(); std::string path_to = MISC::recover_path( diag.get_filename() ); #ifdef _DEBUG std::cout << "to = " << path_to << std::endl; #endif // 既にファイルがある場合は問い合わせる if( CACHE::file_exists( path_to ) == CACHE::EXIST_FILE ){ SKELETON::MsgDiag mdiag( parent, "ファイルが存在します。ファイル名を変更して保存しますか?", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_NONE ); mdiag.add_button( g_dgettext( GTK_DOMAIN, "_No" ), Gtk::RESPONSE_NO ); mdiag.add_button( g_dgettext( GTK_DOMAIN, "_Yes" ), Gtk::RESPONSE_YES ); mdiag.add_button( "上書き", Gtk::RESPONSE_YES + 100 ); int ret = mdiag.run(); mdiag.hide(); if( ret == Gtk::RESPONSE_YES ){ return CACHE::open_save_diag( parent, MISC::get_dir( path_to ), MISC::get_filename( path_to ), type ); } else if( ret == Gtk::RESPONSE_NO ) return std::string(); } return path_to; } // // dir ディレクトリ内のレギュラーファイルのリストを取得 // std::list< std::string > CACHE::get_filelist( const std::string& dir ) { std::list< std::string > list_files; #ifdef _DEBUG std::cout << "CACHE::get_filelist " << dir << std::endl; #endif DIR *dirp = opendir( to_locale_cstr( dir ) ); if( !dirp ) return list_files; struct dirent *direntry; while( ( direntry = readdir( dirp ) ) ){ std::string filename = dir + direntry->d_name; if( file_exists( filename ) == EXIST_FILE ){ #ifdef _DEBUG std::cout << filename << std::endl; #endif list_files.push_back( direntry->d_name ); } } closedir( dirp ); return list_files; } // // dir ディレクトリ内のレギュラーファイルの合計サイズを取得 // guint64 CACHE::get_dirsize( const std::string& dir ) { guint64 total_size = 0; #ifdef _DEBUG std::cout << "CACHE::get_dirsize " << dir << std::endl; #endif DIR *dirp = opendir( to_locale_cstr( dir ) ); if( !dirp ) return 0; struct dirent *direntry; while( ( direntry = readdir( dirp ) ) ){ std::string filename = dir + direntry->d_name; total_size += CACHE::get_filesize( filename ); } closedir( dirp ); #ifdef _DEBUG std::cout << "size = " << total_size << std::endl; #endif return total_size; } // 相対パスから絶対パスを取得してファイルが存在すれば絶対パスを返す // ファイルが存在しない場合は std::string() を返す std::string CACHE::get_realpath( const std::string& path ) { std::string path_real; char resolved_path[ PATH_MAX + 1 ]; char* ret = realpath( to_locale_cstr( path ), resolved_path ); if( ret ){ path_real = ret; } else return std::string(); if( CACHE::file_exists( path_real ) == EXIST_ERROR ){ return std::string(); } #ifdef _DEBUG std::cout << "CACHE::get_realpath path = " << path << " real = " << path_real << std::endl; #endif return path_real; } jdim-0.7.0/src/cache.h000066400000000000000000000166441417047150700144650ustar00rootroot00000000000000// ライセンス: GPL2 // キャッシュ、ファイル操作まわり #ifndef _CACHE_H #define _CACHE_H #include #include #include #include #include #define ENV_HOME "HOME" // GNU/Hurd doen't have PATH_MAX #ifndef PATH_MAX #define PATH_MAX 1024 #endif // UTF-8からロケールでエンコードされた文字列に変換 #define to_locale_cstr( path ) (path).c_str() namespace Gtk { class Window; class FileChooserDialog; } namespace CACHE { ///////////////////////////////////////////////// // // 設定ファイルのパス // 設定ファイル std::string path_conf(); std::string path_conf_bkup(); // セッション情報ファイル std::string path_session(); // ロックファイル std::string path_lock(); // パスワード保存ファイル std::string path_passwd( const std::string& basename ); // キャッシュルートの絶対パス std::string path_root(); // 板 std::string path_xml_listmain(); std::string path_xml_listmain_bkup() = delete; // Removed in v0.6.0 (2021-07) // お気に入り std::string path_xml_favorite(); std::string path_xml_favorite_bkup() = delete; // Removed in v0.6.0 (2021-07) // 外部板設定ファイル( navi2ch 互換 ) std::string path_etcboard(); // ユーザーコマンド設定ファイル std::string path_usrcmd(); // リンクフィルタ std::string path_linkfilter(); // 文字列置換 std::string path_replacestr(); // URL変換設定ファイル std::string path_urlreplace(); // スレ履歴 std::string path_xml_history(); // 板履歴 std::string path_xml_history_board(); // 最近閉じたスレの履歴 std::string path_xml_history_close(); // 最近閉じた板の履歴 std::string path_xml_history_closeboard(); // 最近閉じた画像の履歴 std::string path_xml_history_closeimg(); // View履歴 std::string path_xml_history_view(); // 板移転情報 std::string path_movetable(); // キーボード設定 std::string path_keyconf(); // マウスジェスチャ設定 std::string path_mouseconf(); // マウスボタン設定 std::string path_buttonconf(); std::string path_board_root( const std::string& url ); std::string path_board_root_fast( const std::string& boardbase ); std::string path_article_summary( const std::string& url ); std::string path_board_info( const std::string& url ); std::string path_jdboard_info( const std::string& url ); std::string path_article_info_root( const std::string& url ); std::string path_article_info( const std::string& url, const std::string& id ); std::string path_article_ext_info( const std::string& url, const std::string& id ); std::string path_dat( const std::string& url ); // 画像関係 std::string path_img_root(); std::string path_img_info_root(); std::string path_img_protect_root(); std::string path_img_protect_info_root(); std::string path_img_abone_root(); std::string filename_img( const std::string& url ); std::string filename_img_info( const std::string& url ); std::string path_img( const std::string& url ); std::string path_img_info( const std::string& url ); std::string path_img_protect( const std::string& url ); std::string path_img_protect_info( const std::string& url ); std::string path_img_abone( const std::string& url ); // AA std::string path_aalist(); // アスキーアートファイル std::string path_aadir(); // アスキーアートファイル格納用ディレクトリ .jd/aa/ std::string path_aahistory(); // AAの使用履歴ファイル // テーマのルートパス std::string path_theme_root(); // アイコンテーマのルートパス std::string path_theme_icon_root(); // css std::string path_css(); // html std::string path_reshtml(); // ログ std::string path_logroot(); std::string path_postlog(); // 書き込みログ std::string path_msglog(); // メッセージログ(-lオプション) // 検索や名前などの補完情報 std::string path_completion( int mode ); // サウンドファイルのルートパス std::string path_sound_root(); ///////////////////////////////////////////////// // // ユーティリティ関数 // file_exists の戻り値 enum { EXIST_FILE = 0, // ファイル EXIST_DIR, // ディレクトリ EXIST_FIFO, // FIFO EXIST, // 存在しない or 何か存在してる EXIST_ERROR // エラー }; // ファイルタイプ( file_open_diag() で使用 ) enum { FILE_TYPE_ALL = 0, FILE_TYPE_TEXT, FILE_TYPE_DAT }; // キャッシュの mkdir 関係 bool mkdir_root(); bool mkdir_imgroot(); bool mkdir_imgroot_favorite(); bool mkdir_parent_of_board( const std::string& url ); bool mkdir_boardroot( const std::string& url ); bool mkdir_logroot(); // 生データ読み書き size_t load_rawdata( const std::string& path, std::string& str ); size_t load_rawdata( const std::string& path, char* data, const size_t n ); size_t save_rawdata( const std::string& path, const std::string& str, const bool append = false ); size_t save_rawdata( const std::string& path, const char* data, const size_t n, const bool append = false ); // ファイル情報 int file_exists( const std::string& path ); size_t get_filesize( const std::string& path ); time_t get_filemtime( const std::string& path ); bool set_filemtime( const std::string& path, const time_t mtime ); // ファイル操作 bool jdmkdir( const std::string& path ); bool jdcopy( const std::string& file_from, const std::string& file_to ); bool jdmv( const std::string& file_from, const std::string& file_to ); // 保存ダイアログを表示して file_from を file_to に保存する std::string copy_file( Gtk::Window* parent, const std::string& file_from, const std::string& file_to, const int type ); // ファイル選択ダイアログにフィルタ追加 void add_filter_to_diag( Gtk::FileChooserDialog& diag, const int type ); // ファイル選択ダイアログを表示する // parent == nullptr の時はメインウィンドウをparentにする // open_path はデフォルトの参照先 // multi == true なら複数選択可能 // 戻り値は選択されたファイルのpathのリスト std::vector< std::string > open_load_diag( Gtk::Window* parent, const std::string& open_path, const int type, const bool multi ); // 保存ファイル選択ダイアログを表示する std::string open_save_diag( Gtk::Window* parent, const std::string& dir, const std::string& name, const int type ); // dir ディレクトリ内のレギュラーファイルのリストを取得 std::list< std::string > get_filelist( const std::string& dir ); // dir ディレクトリ内のレギュラーファイルの合計サイズを取得 guint64 get_dirsize( const std::string& dir ); // 相対パスから絶対パスを取得してファイルが存在すれば絶対パスを返す // ファイルが存在しない場合は std::string() を返す std::string get_realpath( const std::string& path ); } #endif jdim-0.7.0/src/colorid.h000066400000000000000000000053451417047150700150510ustar00rootroot00000000000000// カラーID #ifndef _COLOR_ID_H #define _COLOR_ID_H enum { COLOR_NONE = 0, // スレビューで使用する色 COLOR_FOR_THREAD = 0, COLOR_CHAR, // スレビューなど基本の文字 COLOR_CHAR_NAME, // 名前欄 COLOR_CHAR_NAME_B, // トリップや fusianasan 等、が含まれている名前欄 COLOR_CHAR_NAME_NOMAIL, // メールが無いときの名前欄 COLOR_CHAR_AGE, // 非sageのメール欄 COLOR_CHAR_SELECTION, // 選択範囲の文字 COLOR_CHAR_HIGHLIGHT, // ハイライトの文字 COLOR_CHAR_LINK, // 通常のリンクの文字色 COLOR_CHAR_LINK_ID_LOW, // 複数発言したIDの文字色 COLOR_CHAR_LINK_ID_HIGH,// 多く発言したIDの文字色 COLOR_CHAR_LINK_RES, // 参照されていないレス番号の文字色 COLOR_CHAR_LINK_LOW, // 他のレスから参照されたレス番号の文字色 COLOR_CHAR_LINK_HIGH, // 参照された数が多いレス番号の文字色 COLOR_CHAR_MESSAGE, // メッセージビューの文字 COLOR_CHAR_MESSAGE_SELECTION, // メッセージビュー(選択範囲)の文字 COLOR_CHAR_ENTRY_DEFAULT, // Gtk::Entryのデフォルトの文字色 COLOR_IMG_NOCACHE, // 画像のリンク(キャッシュ無) COLOR_IMG_CACHED, // 画像のリンク(キャッシュ有) COLOR_IMG_LOADING, // 画像のリンク(ロード中) COLOR_IMG_ERR, // 画像のリンク(エラー) COLOR_BACK, // スレビューなど基本の背景 COLOR_BACK_POPUP, // ポップアップの背景 COLOR_BACK_SELECTION, // 選択範囲 COLOR_BACK_HIGHLIGHT, // ハイライト文字の背景色 COLOR_BACK_HIGHLIGHT_TREE, // ハイライト文字の背景色(ツリー用) COLOR_BACK_MESSAGE, // メッセージビューの背景色 COLOR_BACK_MESSAGE_SELECTION, // メッセージビュー(選択範囲)の背景色 COLOR_BACK_ENTRY_DEFAULT, // Gtk::Entryのデフォルトの背景色 COLOR_SEPARATOR_NEW, // 新着セパレータ COLOR_FRAME, // ポップアップフレーム色 COLOR_MARKER, // オートスクロールのマーク色 END_COLOR_FOR_THREAD, USRCOLOR_BASE = END_COLOR_FOR_THREAD, // cssで使用する色番号のベース // その他の色 COLOR_CHAR_BBS, // 板一覧の文字 COLOR_CHAR_BBS_COMMENT, // 板一覧のコメント COLOR_CHAR_BOARD, // スレ一覧の文字 COLOR_BACK_BBS, // 板一覧の背景 COLOR_BACK_BBS_EVEN, // 板一覧の背景(偶数行) COLOR_BACK_BOARD, // スレ一覧の背景 COLOR_BACK_BOARD_EVEN, // スレ一覧の背景(偶数行) COLOR_NUM }; #endif jdim-0.7.0/src/command.cpp000066400000000000000000000016051417047150700153620ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "command.h" #include "global.h" #include "core.h" void CORE::core_set_command( const std::string& command, const std::string& url, const std::string& arg1, const std::string& arg2, const std::string& arg3, const std::string& arg4, const std::string& arg5, const std::string& arg6 ) { COMMAND_ARGS command_arg; command_arg.command = command; command_arg.url = url; command_arg.arg1 = arg1; command_arg.arg2 = arg2; command_arg.arg3 = arg3; command_arg.arg4 = arg4; command_arg.arg5 = arg5; command_arg.arg6 = arg6; CORE::get_instance()->set_command( command_arg ); } Gtk::Window* CORE::get_mainwindow() { return dynamic_cast< Gtk::Window* >( CORE::get_instance()->get_toplevel() ); } jdim-0.7.0/src/command.h000066400000000000000000000014711417047150700150300ustar00rootroot00000000000000// ライセンス: GPL2 // // コアのインターフェース // #ifndef _COMMAND_H #define _COMMAND_H #include namespace Gtk { class Widget; class Window; } namespace CORE { void core_set_command( const std::string& command, const std::string& url = std::string(), const std::string& arg1 = std::string(), const std::string& arg2 = std::string(), const std::string& arg3 = std::string(), const std::string& arg4 = std::string(), const std::string& arg5 = std::string(), const std::string& arg6 = std::string() ); // メインウィンドウ取得 Gtk::Window* get_mainwindow(); } #endif jdim-0.7.0/src/command_args.h000066400000000000000000000005461417047150700160460ustar00rootroot00000000000000// CoreやAdminクラスで使うコマンド構造体 #ifndef COMMAND_ARGS_H #define COMMAND_ARGS_H struct COMMAND_ARGS { std::string command; std::string url; std::string arg1; std::string arg2; std::string arg3; std::string arg4; std::string arg5; std::string arg6; std::string arg7; std::string arg8; }; #endif jdim-0.7.0/src/compmanager.cpp000066400000000000000000000070341417047150700162370ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "jdlib/miscutil.h" #include "compmanager.h" #include "cache.h" #include enum { MAX_COMPLETION = 50 }; CORE::Completion_Manager* instance_completion_manager = nullptr; CORE::Completion_Manager* CORE::get_completion_manager() { if( ! instance_completion_manager ) instance_completion_manager = new Completion_Manager(); assert( instance_completion_manager ); return instance_completion_manager; } void CORE::delete_completion_manager() { if( instance_completion_manager ) delete instance_completion_manager; instance_completion_manager = nullptr; } /////////////////////////////////////////////// using namespace CORE; Completion_Manager::Completion_Manager() : m_lists( COMP_SIZE ) { for( int i = 0; i < COMP_SIZE; ++i ){ load_info( i ); } } void Completion_Manager::save_session() { for( int i = 0; i < COMP_SIZE; ++i ){ save_info( i ); } } COMPLIST Completion_Manager::get_list( const int mode, const std::string& query ) { COMPLIST complist; if( mode < COMP_SIZE ){ #ifdef _DEBUG std::cout << "Completion_Manager::get_list mode = " << mode << " query = " << query << std::endl; #endif if( query.empty() || query == " " || query == " " // 全角スペース ) { complist = m_lists[ mode ]; } else{ const std::string lower_query = MISC::tolower_str( query ); for( const std::string& str : m_lists[ mode ] ) { const std::string lower_str = MISC::tolower_str( str ); if( lower_str.find( lower_query ) != std::string::npos ) complist.push_back( str ); } } } return complist; } void Completion_Manager::set_query( const int mode, const std::string& query ) { if( ! query.empty() && mode < COMP_SIZE ){ #ifdef _DEBUG std::cout << "Completion_Manager::set_query mode = " << mode << " query = " << query << std::endl; #endif COMPLIST& complist = m_lists[ mode ]; complist.remove( query ); complist.push_front( query ); if( complist.size() > MAX_COMPLETION ) complist.pop_back(); } } void Completion_Manager::clear( const int mode ) { if( mode < COMP_SIZE ){ m_lists[ mode ].clear(); std::string path = CACHE::path_completion( mode ); unlink( to_locale_cstr( path ) ); } } // 情報ファイル読み書き void Completion_Manager::load_info( const int mode ) { std::string path = CACHE::path_completion( mode ); std::string info; CACHE::load_rawdata( path, info ); #ifdef _DEBUG std::cout << "Completion_Manager::load_info path = " << path << std::endl << info << std::endl; #endif if( ! info.empty() ){ m_lists[ mode ] = MISC::get_lines( info ); m_lists[ mode ] = MISC::remove_nullline_from_list( m_lists[ mode ] ); m_lists[ mode ] = MISC::remove_space_from_list( m_lists[ mode ] ); } } void Completion_Manager::save_info( const int mode ) { std::string info; COMPLIST& complist = m_lists[ mode ]; if( ! complist.empty() ) { std::string path = CACHE::path_completion( mode ); for( const std::string& str : complist ) { info.append( str ); info.push_back( '\n' ); } #ifdef _DEBUG std::cout << "Completion_Manager::save_info path = " << path << std::endl << info << std::endl; #endif CACHE::save_rawdata( path, info ); } } jdim-0.7.0/src/compmanager.h000066400000000000000000000022361417047150700157030ustar00rootroot00000000000000// ライセンス: GPL2 // // Entryなどの補完管理クラス // #ifndef _COMPMANAGER_H #define _COMPMANAGER_H #include #include #include namespace CORE { enum { COMP_SEARCH_ARTICLE = 0, COMP_NAME, COMP_MAIL, COMP_SEARCH_BBSLIST, COMP_SEARCH_BOARD, COMP_SIZE }; typedef std::list COMPLIST; typedef std::list::iterator COMPLIST_ITERATOR; class Completion_Manager { std::vector m_lists; public: Completion_Manager(); virtual ~Completion_Manager() noexcept = default; COMPLIST get_list( const int mode, const std::string& query ); void set_query( const int mode, const std::string& query ); void clear( const int mode ); void save_session(); private: // 情報ファイル読み書き void load_info( const int mode ); void save_info( const int mode ); }; /////////////////////////////////////// // インターフェース Completion_Manager* get_completion_manager(); void delete_completion_manager(); } #endif jdim-0.7.0/src/config/000077500000000000000000000000001417047150700145035ustar00rootroot00000000000000jdim-0.7.0/src/config/Makefile.am000066400000000000000000000004331417047150700165370ustar00rootroot00000000000000noinst_LIBRARIES = libconfig.a libconfig_a_SOURCES = \ configitems.cpp globalconf.cpp aboutconfig.cpp aboutconfigdiag.cpp noinst_HEADERS = \ configitems.h globalconf.h defaultconf.h aboutconfig.h aboutconfigdiag.h AM_CXXFLAGS = @GTKMM_CFLAGS@ AM_CPPFLAGS = -I$(top_srcdir)/src jdim-0.7.0/src/config/aboutconfig.cpp000066400000000000000000000615451417047150700175220ustar00rootroot00000000000000// ライセンス: GPL2 #ifdef HAVE_CONFIG_H #include "config.h" #endif //#define _DEBUG #include "jddebug.h" #include "aboutconfig.h" #include "aboutconfigdiag.h" #include "globalconf.h" #include "configitems.h" #include "defaultconf.h" #include "skeleton/msgdiag.h" #include "colorid.h" enum { CONFTYPE_STR = 0, CONFTYPE_INT, CONFTYPE_BOOL, CONFTYPE_COMMENT }; using namespace CONFIG; AboutConfig::AboutConfig( Gtk::Window* parent ) : SKELETON::PrefDiag( parent, "", true ) { CONFIG::bkup_conf(); pack_widgets(); } // // widgetのパック // void AboutConfig::pack_widgets() { m_label.set_text( "動作保証外です。高度な設定を変更するとJDimが誤作動する場合があります。" ); m_liststore = Gtk::ListStore::create( m_columns ); m_treeview.property_fixed_height_mode() = true; m_treeview.set_model( m_liststore ); m_treeview.set_size_request( 700, 400 ); m_treeview.signal_row_activated().connect( sigc::mem_fun( *this, &AboutConfig::slot_row_activated ) ); Gtk::TreeViewColumn* column = Gtk::manage( new Gtk::TreeViewColumn( "設定名", m_columns.m_col_name ) ); column->set_fixed_width( 540 ); column->set_sizing( Gtk::TREE_VIEW_COLUMN_FIXED ); column->set_resizable( true ); m_treeview.append_column( *column ); Gtk::CellRenderer *cell = column->get_first_cell(); if( cell ) column->set_cell_data_func( *cell, sigc::mem_fun( *this, &AboutConfig::slot_cell_data ) ); column = Gtk::manage( new Gtk::TreeViewColumn( "値", m_columns.m_col_value ) ); column->set_sizing( Gtk::TREE_VIEW_COLUMN_FIXED ); column->set_resizable( true ); m_treeview.append_column( *column ); cell = column->get_first_cell(); if( cell ) column->set_cell_data_func( *cell, sigc::mem_fun( *this, &AboutConfig::slot_cell_data ) ); m_scrollwin.add( m_treeview ); m_scrollwin.set_policy( Gtk::POLICY_AUTOMATIC, Gtk::POLICY_ALWAYS ); m_scrollwin.set_propagate_natural_height( true ); m_scrollwin.set_propagate_natural_width( true ); get_content_area()->set_spacing( 8 ); get_content_area()->pack_start( m_label, Gtk::PACK_SHRINK ); get_content_area()->pack_start( m_scrollwin ); set_title( "about:config 高度な設定" ); show_all_children(); append_rows(); } // // OK // void AboutConfig::slot_ok_clicked() { SKELETON::MsgDiag mdiag( nullptr, "一部の設定はJDimを再起動しない限り有効になりません。JDimを再起動してください。" ); mdiag.run(); } // // キャンセルが押されたら設定を戻す // void AboutConfig::slot_cancel_clicked() { CONFIG::restore_conf(); } // // 実際の描画の際に cellrendere のプロパティをセットするスロット関数 // void AboutConfig::slot_cell_data( Gtk::CellRenderer* cell, const Gtk::TreeModel::iterator& it ) { Gtk::TreeModel::Row row = *it; if( row[ m_columns.m_col_drawbg ] ){ cell->property_cell_background() = CONFIG::get_color( COLOR_BACK_HIGHLIGHT_TREE ); cell->property_cell_background_set() = true; } else cell->property_cell_background_set() = false; } void AboutConfig::append_rows() { // ネットワーク append_row( "■ ネットワーク" ); append_row( "JDim ホームページのアドレス", get_confitem()->url_jdimhp, CONF_URL_JDIMHP ); append_row( "板一覧を取得するサーバ", get_confitem()->url_bbsmenu, CONF_URL_BBSMENU ); append_row( "2chログイン認証サーバのアドレス", get_confitem()->url_login2ch, CONF_LOGIN2CH ); append_row( "BE認証サーバのアドレス", get_confitem()->url_loginbe, CONF_LOGINBE ); append_row( "2chにアクセスするときのエージェント名", get_confitem()->agent_for2ch, CONF_AGENT_FOR2CH ); append_row( "2ch以外のサーバにアクセスするときのエージェント名", get_confitem()->agent_for_data, CONF_AGENT_FOR_DATA ); append_row( "2chログイン認証サーバにアクセスするときのエージェント名", get_confitem()->x_2ch_ua, CONF_X_2CH_UA ); append_row( "スレの読み込み時のタイムアウト値(秒)", get_confitem()->loader_timeout, CONF_LOADER_TIMEOUT ); append_row( "書き込み時のタイムアウト値(秒)", get_confitem()->loader_timeout_post, CONF_LOADER_TIMEOUT_POST ); append_row( "画像等のデータのロード時のタイムアウト値(秒)", get_confitem()->loader_timeout_img, CONF_LOADER_TIMEOUT_IMG ); append_row( "更新チェック時のタイムアウト値(秒)", get_confitem()->loader_timeout_checkupdate, CONF_LOADER_TIMEOUT_CHECKUPDATE ); append_row( "一般データのダウンロード時のバッファサイズ(Kbyte)", get_confitem()->loader_bufsize, CONF_LOADER_BUFSIZE ); append_row( "スレ一覧のダウンロード時のバッファサイズ(Kbyte)", get_confitem()->loader_bufsize_board, CONF_LOADER_BUFSIZE_BOARD ); append_row( "同一ホストに対する最大コネクション数( 1 または 2 )", get_confitem()->connection_num, CONF_CONNECTION_NUM ); append_row( "2chのクッキーを保存する", get_confitem()->use_cookie_hap, CONF_USE_COOKIE_HAP ); append_row( "2chのクッキー", get_confitem()->cookie_hap, CONF_COOKIE_HAP ); append_row( "BBSPINKのクッキー", get_confitem()->cookie_hap_bbspink, CONF_COOKIE_HAP_BBSPINK ); // ツリービュー append_row( "" ); append_row( "■ ツリービュー(板一覧、スレ一覧)" ); append_row( "ツリービューでマウスホイールを回したときのスクロール量(行数)", get_confitem()->tree_scroll_size, CONF_TREE_SCROLL_SIZE ); append_row( "ツリービューの行間スペース", get_confitem()->tree_ypad, CONF_TREE_YPAD ); append_row( "ツリービューのエクスパンダを表示する", get_confitem()->tree_show_expanders, CONF_TREE_SHOW_EXPANDERS ); append_row( "ツリービューのレベルインデント調整量(ピクセル)", get_confitem()->tree_level_indent, CONF_TREE_LEVEL_INDENT ); append_row( "カテゴリやディレクトリを開いたときにスクロールする", get_confitem()->scroll_tree, CONF_SCROLL_TREE ); append_row( "ツリービューの選択を表示中のビューと同期する ( 0: 同期しない 1: 同期する 2: 同期する(フォルダを開く) )", get_confitem()->select_item_sync, CONF_SCROLL_TREE ); // 板一覧、履歴ビュー append_row( "" ); append_row( "■ 板一覧、履歴ビュー" ); append_row( "板移転時に確認ダイアログを表示する", get_confitem()->show_movediag, CONF_SHOW_MOVEDIAG ); append_row( "板一覧内にあるリンクを全て板とみなす", get_confitem()->use_link_as_board, CONF_USE_LINK_AS_BOARD ); append_row( "板一覧でカテゴリを常にひとつだけ開く", get_confitem()->open_one_category, CONF_OPEN_ONE_CATEGORY ); append_row( "右ペーンが空の時にサイドバーを最大化する", get_confitem()->expand_sidebar, CONF_EXPAND_SIDEBAR ); append_row( "ペーン境界をクリックしてサイドバーを開け閉めする", get_confitem()->open_sidebar_by_click, CONF_OPEN_SIDEBAR_BY_CLICK ); append_row( "更新チェック時に板の更新もチェックする", get_confitem()->check_update_board, CONF_CHECK_UPDATE_BOARD ); append_row( "履歴ビューの最大表示数", get_confitem()->historyview_size, CONF_HISTORYVIEW_SIZE ); // お気に入り append_row( "" ); append_row( "■ お気に入り" ); append_row( "お気に入りでディレクトリを常にひとつだけ開く", get_confitem()->open_one_favorite, CONF_OPEN_ONE_FAVORITE ); append_row( "スレをお気に入りに追加した時にしおりをセットする", get_confitem()->bookmark_drop, CONF_BOOKMARK_DROP ); append_row( "起動時にお気に入りを自動でチェックする", get_confitem()->check_update_boot, CONF_CHECK_UPDATE_BOOT ); append_row( "重複項目を登録する ( 0: 登録する 1: ダイアログ表示 2: 登録しない )", get_confitem()->check_favorite_dup, CONF_CHECK_FAVORITE_DUP ); append_row( "登録時に挿入先ダイアログ表示 ( 0 : 表示 1: 表示せず先頭に追加 2: 表示せず最後に追加 )", get_confitem()->show_favorite_select_diag, CONF_SHOW_FAVORITE_SELECT_DIAG ); // スレ一覧 append_row( "" ); append_row( "■ スレ一覧" ); append_row( "インクリメント検索をする", get_confitem()->inc_search_board, CONF_INC_SEARCH_BOARD ); append_row( "deleteを押したときに確認ダイアログを表示", get_confitem()->show_deldiag, CONF_SHOW_DELDIAG ); append_row( "指定した値(時間)よりも後に立てられたスレを新着とみなす", get_confitem()->newthread_hour, CONF_NEWTHREAD_HOUR ); append_row( "3ペーン時にスレ一覧やスレビューを最大化する", get_confitem()->expand_rpane, CONF_EXPAND_RPANE ); append_row( "スレ一覧をロードする前にキャッシュにある一覧を表示する", get_confitem()->show_cached_board, CONF_SHOW_CACHED_BOARD ); append_row( "お知らせスレ(924)のアイコンを表示する", get_confitem()->show_924, CONF_SHOW_924 ); append_row( "dat落ちしたスレをNGスレタイトルから除く( 0: ダイアログ表示 1: 除く 2: 除かない )", get_confitem()->remove_old_abone_thread, CONF_REMOVE_OLD_ABONE_THREAD ); append_row( "dat落ちしたスレを表示する", get_confitem()->show_oldarticle, CONF_SHOW_OLDARTICLE ); // スレビュー append_row( "" ); append_row( "■ スレビュー" ); append_row( "マウスホイールを回したときのスクロール速度", get_confitem()->scroll_size, CONF_SCROLL_SIZE ); append_row( "上下キーを押したときのスクロール速度", get_confitem()->key_scroll_size, CONF_KEY_SCROLL_SIZE ); append_row( "pageup、pagedownキーを押したときのスクロール速度", get_confitem()->key_fastscroll_size, CONF_KEY_FASTSCROLL_SIZE ); append_row( "リロード後に末尾に自動的に移動する", get_confitem()->jump_after_reload, CONF_JUMP_AFTER_RELOAD ); append_row( "リロード後に新着レスに自動的に移動する", get_confitem()->jump_new_after_reload, CONF_JUMP_NEW_AFTER_RELOAD ); append_row( "ポップアップとカーソルの間の間隔(ピクセル)", get_confitem()->margin_popup, CONF_MARGIN_POPUP ); append_row( "画像ポップアップとカーソルの間の水平間隔(ピクセル)", get_confitem()->margin_imgpopup_x, CONF_MARGIN_IMGPOPUP_X ); append_row( "画像ポップアップとカーソルの間の垂直間隔(ピクセル)", get_confitem()->margin_imgpopup, CONF_MARGIN_IMGPOPUP ); append_row( "ポップアップが消えるまでの時間(ミリ秒)", get_confitem()->hide_popup_msec, CONF_HIDE_POPUP_MSEC ); append_row( "多重ポップアップの説明を表示する", get_confitem()->instruct_popup, CONF_INSTRUCT_POPUP ); append_row( "レス番号の上にマウスオーバーしたときに参照ポップアップ表示する", get_confitem()->refpopup_by_mo, CONF_REFPOPUP_BY_MO ); append_row( "「名前」の上にマウスオーバーしたときにポップアップ表示する", get_confitem()->namepopup_by_mo, CONF_NAMEPOPUP_BY_MO ); append_row( "IDの上にマウスオーバーしたときにIDをポップアップ表示する", get_confitem()->idpopup_by_mo, CONF_IDPOPUP_BY_MO ); append_row( "スレビューとスレ一覧の切り替え方法説明ダイアログを表示する", get_confitem()->instruct_tglart, CONF_INSTRUCT_TGLART ); append_row( "画像ビューとスレビューの切り替え方法説明ダイアログを表示する", get_confitem()->instruct_tglimg, CONF_INSTRUCT_TGLIMG ); append_row( "deleteを押したときに確認ダイアログを表示", get_confitem()->show_delartdiag, CONF_SHOW_DELARTDIAG ); append_row( "リンクの下線を表示する", get_confitem()->draw_underline, CONF_DRAW_UNDERLINE ); append_row( "レスを引用コピーするときに前に付ける引用文字", get_confitem()->ref_prefix, CONF_REF_PREFIX ); append_row( "引用文字の後のスペース数", get_confitem()->ref_prefix_space, CONF_REF_PREFIX_SPACE ); append_row( "RFC規定外の文字(^など)もURL判定に用いる", get_confitem()->loose_url, CONF_LOOSE_URL ); append_row( "再読み込みボタンを押したときに全タブを更新する", get_confitem()->reload_allthreads, CONF_RELOAD_ALLTHREAD ); append_row( "発言(同一ID)数をカウントする", get_confitem()->check_id, CONF_CHECK_ID ); append_row( "レス参照数で色を変える回数(高)", get_confitem()->num_reference_high, CONF_NUM_REFERENCE_HIGH ); append_row( "レス参照数で色を変える回数(低)", get_confitem()->num_reference_low, CONF_NUM_REFERENCE_LOW ); append_row( "発言数で色を変える回数(高)", get_confitem()->num_id_high, CONF_NUM_ID_HIGH ); append_row( "発言数で色を変える回数(低)", get_confitem()->num_id_low, CONF_NUM_ID_LOW ); append_row( "WEB検索用のメニュー項目名", get_confitem()->menu_search_web, CONF_MENU_SEARCH_WEB ); append_row( "WEB検索用のアドレス", get_confitem()->url_search_web, CONF_URL_SEARCH_WEB ); append_row( "デフォルトで透明あぼーん", get_confitem()->abone_transparent, CONF_ABONE_TRANSPARENT ); append_row( "デフォルトで連鎖あぼーん", get_confitem()->abone_chain, CONF_ABONE_CHAIN ); append_row( "書き込み履歴のあるスレを削除する時にダイアログを表示", get_confitem()->show_del_written_thread_diag, CONF_SHOW_DEL_WRITTEN_THREAD_DIAG ); append_row( "スレを削除する時に画像キャッシュも削除する ( 0: ダイアログ表示 1: 削除 2: 削除しない )", get_confitem()->delete_img_in_thread, CONF_DELETE_IMG_IN_THREAD ); append_row( "最大表示可能レス数", get_confitem()->max_resnumber, CONF_MAX_RESNUMBER ); // 書き込みウィンドウ append_row( "" ); append_row( "■ 書き込みビュー" ); append_row( "デフォルトの書き込み名", get_confitem()->write_name, CONF_WRITE_NAME ); append_row( "デフォルトのメールアドレス", get_confitem()->write_mail, CONF_WRITE_MAIL ); append_row( "書き込み時に確認ダイアログを表示しない", get_confitem()->always_write_ok, CONF_ALWAYS_WRITE_OK ); append_row( "書き込み中ダイアログを表示しない", get_confitem()->hide_writing_dialog, CONF_HIDE_WRITING_DIALOG ); append_row( "編集中のメッセージの保存確認ダイアログを表示する", get_confitem()->show_savemsgdiag, CONF_SHOW_SAVEMSGDIAG ); append_row( "アスキーアートメニューの履歴の保持数", get_confitem()->aahistory_size, CONF_AAHISTORY ); append_row( "書き込みログの最大サイズ(バイト)", get_confitem()->maxsize_postlog, CONF_MAXSIZE_POSTLOG ); append_row( "ビューを閉じても書き込み欄の日本語のON/OFF状態を保つ", get_confitem()->keep_im_status, CONF_KEEP_IM_STATUS ); // 画像 append_row( "" ); append_row( "■ 画像" ); append_row( "画像ポップアップの幅(ピクセル)", get_confitem()->imgpopup_width, CONF_IMGPOPUP_WIDTH ); append_row( "画像ポップアップの高さ(ピクセル)", get_confitem()->imgpopup_height, CONF_IMGPOPUP_HEIGHT ); append_row( "画像ビューを開いたときにウィンドウサイズに合わせる", get_confitem()->zoom_to_fit, CONF_ZOOM_TO_FIT ); append_row( "画像ビューのフォーカスが外れたら折りたたむ", get_confitem()->fold_image, CONF_FOLD_IMAGE ); append_row( "埋め込み画像ビューを閉じたときにタブも閉じる", get_confitem()->hide_imagetab, CONF_HIDE_IMAGETAB ); append_row( "指定したサイズ(M byte)より大きい画像を表示するときに警告する", get_confitem()->max_img_size, CONF_MAX_IMG_SIZE ); append_row( "指定した画素数(M pixel)より大きい画像を表示するときに警告する", get_confitem()->max_img_pixel, CONF_MAX_IMG_PIXEL ); append_row( "モザイクのレベル", get_confitem()->mosaic_size, CONF_MOSAIC_SIZE ); append_row( "画像ビューのスムージングレベル(0-2, 大きい程高画質で低速)", get_confitem()->imgmain_interp, CONF_IMGMAIN_INTERP ); append_row( "インライン画像のスムージングレベル(0-2, 大きい程高画質で低速)", get_confitem()->imgemb_interp, CONF_IMGEMB_INTERP ); append_row( "インライン画像の最大幅", get_confitem()->embimg_width, CONF_EMBIMG_WIDTH ); append_row( "インライン画像の最大高さ", get_confitem()->embimg_height, CONF_EMBIMG_HEIGHT ); append_row( "ポップアップ画像のスムージングレベル(0-2, 大きい程高画質で低速)", get_confitem()->imgpopup_interp, CONF_IMGPOPUP_INTERP ); append_row( "画像のメモリキャッシュ枚数", get_confitem()->imgcache_size, CONF_IMGCACHE_SIZE ); append_row( "deleteを押したときに確認ダイアログを表示", get_confitem()->show_delimgdiag, CONF_SHOW_DELIMGDIAG ); // ウィンドウ append_row( "" ); append_row( "■ ウィンドウ" ); append_row( "タブにアイコンを表示する", get_confitem()->show_tab_icon, CONF_SHOW_TAB_ICON ); append_row( "タブに表示する文字列の最小文字数", get_confitem()->tab_min_str, CONF_TAB_MIN_STR ); append_row( "タブ上でマウスホイールを回転してタブを切り替える", get_confitem()->switchtab_wheel, CONF_SWITCHTAB_WHEEL ); append_row( "他のビューを開くときのタブの位置 ( 0: 一番右端 1:右隣 2:左隣 )", get_confitem()->newtab_pos, CONF_NEWTAB_POS ); append_row( "ツリービューで選択したビューを開くときのタブの位置 ( 0: 一番右端 1:右隣 2:左隣 )", get_confitem()->opentab_pos, CONF_OPENTAB_POS ); append_row( "各ビューと枠との間の余白", get_confitem()->view_margin, CONF_VIEW_MARGIN ); append_row( "自前でウィンドウ配置を管理する", get_confitem()->manage_winpos, CONF_MANAGE_WINPOS ); append_row( "スレビューのスクロールバーを左に配置する", get_confitem()->left_scrbar, CONF_LEFT_SCRBAR ); append_row( "メニューバーを非表示にした時にダイアログを表示", get_confitem()->show_hide_menubar_diag, CONF_SHOW_HIDE_MENUBAR_DIAG ); append_row( "状態変更時にメインステータスバーの色を変える", get_confitem()->change_stastatus_color, CONF_CHANGE_STASTATUS_COLOR ); append_row( "Client-Side Decorationを使うか( 0: 使わない 1: 使う 2: デスクトップに合わせる )", get_confitem()->use_header_bar, CONF_USE_HEADER_BAR ); // 次スレ検索 append_row( "" ); append_row( "■ 次スレ検索" ); append_row( "類似度判定のしきい値(小さいほど判定が甘くなる、最大10)", get_confitem()->threshold_next, CONF_THRESHOLD_NEXT ); append_row( "移行時にお気に入りのアドレスと名前を自動更新する(0: しない, 1:する, 2:追加)", get_confitem()->replace_favorite_next, CONF_REPLACE_FAVORITE_NEXT ); append_row( "お気に入り自動更新の確認ダイアログを表示する", get_confitem()->show_diag_replace_favorite, CONF_SHOW_DIAG_REPLACE_FAVORITE ); append_row( "次スレ検索を開くときのタブの位置 ( 0: 次スレ検索タブ 1:新しいタブ 2:アクティブなタブを置き換え )", get_confitem()->boardnexttab_pos, CONF_BOARDNEXTTAB_POS ); // スレタイ検索 append_row( "" ); append_row( "■ スレタイ検索" ); append_row( "スレタイ検索用のメニュー項目名", get_confitem()->menu_search_title, CONF_MENU_SEARCH_TITLE ); append_row( "スレタイ検索用のアドレス", get_confitem()->url_search_title, CONF_URL_SEARCH_TITLE ); append_row( "スレタイ検索時にアドレスとスレタイを取得する正規表現", get_confitem()->regex_search_title, CONF_REGEX_SEARCH_TITLE ); // その他 append_row( "" ); append_row( "■ その他" ); append_row( "履歴メニューの表示数", get_confitem()->history_size, CONF_HISTORY_SIZE ); append_row( "マウスジェスチャを有効にする", get_confitem()->enable_mg, CONF_ENABLE_MG ); append_row( "マウスジェスチャの判定開始半径", get_confitem()->mouse_radius, CONF_MOUSE_RADIUS ); append_row( "数字入力ジャンプの待ち時間(ミリ秒)", get_confitem()->numberjmp_msec, CONF_NUMBERJMP_MSEC ); append_row( "起動時に開いていたスレ一覧を復元する", get_confitem()->restore_board, CONF_RESTORE_BOARD ); append_row( "起動時に開いていたスレを復元する", get_confitem()->restore_article, CONF_RESTORE_ARTICLE ); append_row( "起動時に開いていた画像を復元する", get_confitem()->restore_image, CONF_RESTORE_IMAGE ); #ifdef HAVE_MIGEMO_H append_row( "migemoの辞書ファイルの場所(migemo使用時のみ)", get_confitem()->migemodict_path, CONF_MIGEMO_PATH ); #endif append_row( "FIFOの作成などにエラーがあったらダイアログを表示する", get_confitem()->show_diag_fifo_error, CONF_SHOW_DIAG_FIFO_ERROR ); append_row( "指定した分ごとにセッションを自動保存 (0: 保存しない)", get_confitem()->save_session, CONF_SAVE_SESSION ); } void AboutConfig::append_row( const std::string& name, std::string& value, const std::string& defaultval ) { Gtk::TreeModel::Row row; row = *( m_liststore->append() ); row[ m_columns.m_col_name ] = name; row[ m_columns.m_col_type ] = CONFTYPE_STR; row[ m_columns.m_col_drawbg ] = false; row[ m_columns.m_col_value_str ] = &value; row[ m_columns.m_col_default_str ] = defaultval; set_value( row, value ); } void AboutConfig::append_row( const std::string& name, int& value, const int defaultval ) { Gtk::TreeModel::Row row; row = *( m_liststore->append() ); row[ m_columns.m_col_name ] = name; row[ m_columns.m_col_type ] = CONFTYPE_INT; row[ m_columns.m_col_drawbg ] = false; row[ m_columns.m_col_value_int ] = &value; row[ m_columns.m_col_default_int ] = defaultval; set_value( row, value ); } void AboutConfig::append_row( const std::string& name, bool& value, const bool defaultval ) { Gtk::TreeModel::Row row; row = *( m_liststore->append() ); row[ m_columns.m_col_name ] = name; row[ m_columns.m_col_type ] = CONFTYPE_BOOL; row[ m_columns.m_col_drawbg ] = false; row[ m_columns.m_col_value_bool ] = &value; row[ m_columns.m_col_default_bool ] = defaultval; set_value( row, value ); } void AboutConfig::append_row( const std::string& comment ) { Gtk::TreeModel::Row row; row = *( m_liststore->append() ); row[ m_columns.m_col_name ] = comment; row[ m_columns.m_col_type ] = CONFTYPE_COMMENT; static_cast( row ); // cppcheck: unreadVariable } void AboutConfig::set_value( Gtk::TreeModel::Row& row, const std::string& value ) { row[ m_columns.m_col_value ] = value; const std::string defaultval = row[ m_columns.m_col_default_str ]; if( value != defaultval ) row[ m_columns.m_col_drawbg ] = true; else row[ m_columns.m_col_drawbg ] = false; } void AboutConfig::set_value( Gtk::TreeModel::Row& row, const int value ) { row[ m_columns.m_col_value ] = std::to_string( value ); const int defaultval = row[ m_columns.m_col_default_int ]; if( value != defaultval ) row[ m_columns.m_col_drawbg ] = true; else row[ m_columns.m_col_drawbg ] = false; } void AboutConfig::set_value( Gtk::TreeModel::Row& row, const bool value ) { if( value ) row[ m_columns.m_col_value ] = "はい"; else row[ m_columns.m_col_value ] = "いいえ"; const bool defaultval = row[ m_columns.m_col_default_bool ]; if( value != defaultval ) row[ m_columns.m_col_drawbg ] = true; else row[ m_columns.m_col_drawbg ] = false; } void AboutConfig::slot_row_activated( const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn* column ) { #ifdef _DEBUG std::cout << "AboutConfig::slot_row_activated path = " << path.to_string() << std::endl; #endif Gtk::TreeModel::Row row = *( m_liststore->get_iter( path ) ); if( ! row ) return; const int type = row[ m_columns.m_col_type ]; if( type == CONFTYPE_COMMENT ) return; else if( type == CONFTYPE_STR ) { AboutConfigDiagStr pref( this, row[ m_columns.m_col_value_str ], row[ m_columns.m_col_default_str ] ); pref.run(); set_value( row, *row[ m_columns.m_col_value_str ] ); } else if( type == CONFTYPE_INT ) { AboutConfigDiagInt pref( this, row[ m_columns.m_col_value_int ], row[ m_columns.m_col_default_int ] ); pref.run(); set_value( row, *row[ m_columns.m_col_value_int ] ); } else if( type == CONFTYPE_BOOL ) { AboutConfigDiagBool pref( this, row[ m_columns.m_col_value_bool ], row[ m_columns.m_col_default_bool ] ); pref.run(); set_value( row, *row[ m_columns.m_col_value_bool ] ); } } jdim-0.7.0/src/config/aboutconfig.h000066400000000000000000000047111417047150700171570ustar00rootroot00000000000000// ライセンス: GPL2 // about:config #ifndef _ABOUTCONFIG_H #define _ABOUTCONFIG_H #include "skeleton/prefdiag.h" namespace CONFIG { class TreeColumn : public Gtk::TreeModel::ColumnRecord { public: Gtk::TreeModelColumn< Glib::ustring > m_col_name; Gtk::TreeModelColumn< Glib::ustring > m_col_value; Gtk::TreeModelColumn< int > m_col_type; Gtk::TreeModelColumn< bool > m_col_drawbg; Gtk::TreeModelColumn< std::string* > m_col_value_str; Gtk::TreeModelColumn< int* > m_col_value_int; Gtk::TreeModelColumn< bool* > m_col_value_bool; Gtk::TreeModelColumn< std::string > m_col_default_str; Gtk::TreeModelColumn< int > m_col_default_int; Gtk::TreeModelColumn< bool > m_col_default_bool; TreeColumn() { add( m_col_name ); add( m_col_value ); add( m_col_type ); add( m_col_drawbg ); add( m_col_value_str ); add( m_col_value_int ); add( m_col_value_bool ); add( m_col_default_str ); add( m_col_default_int ); add( m_col_default_bool ); } }; //////////////////////////////// class AboutConfig : public SKELETON::PrefDiag { Gtk::Label m_label; Gtk::TreeView m_treeview; Glib::RefPtr< Gtk::ListStore > m_liststore; TreeColumn m_columns; Gtk::ScrolledWindow m_scrollwin; public: explicit AboutConfig( Gtk::Window* parent ); ~AboutConfig() noexcept = default; private: void pack_widgets(); void slot_ok_clicked() override; void slot_cancel_clicked() override; void slot_cell_data( Gtk::CellRenderer* cell, const Gtk::TreeModel::iterator& it ); void append_rows(); void append_row( const std::string& name, std::string& value, const std::string& defaultval ); void append_row( const std::string& name, int& value, const int defaultval ); void append_row( const std::string& name, bool& value, const bool defaultval ); void append_row( const std::string& comment ); void set_value( Gtk::TreeModel::Row& row, const std::string& value ); void set_value( Gtk::TreeModel::Row& row, const int value ); void set_value( Gtk::TreeModel::Row& row, const bool value ); void slot_row_activated( const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn* column ); }; } #endif jdim-0.7.0/src/config/aboutconfigdiag.cpp000066400000000000000000000060701417047150700203370ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "aboutconfigdiag.h" using namespace CONFIG; AboutConfigDiagStr::AboutConfigDiagStr( Gtk::Window* parent, std::string* value, const std::string& defaultval ) : SKELETON::PrefDiag( parent, "", true ), m_value( value ), m_defaultval( defaultval ), m_button_default( "デフォルト" ) { resize( 600, 1 ); m_entry.set_text( *value ); m_hbox.pack_start( m_entry ); m_button_default.signal_clicked().connect( sigc::mem_fun( *this, &AboutConfigDiagStr::slot_default ) ); m_hbox.pack_start( m_button_default, Gtk::PACK_SHRINK ); get_content_area()->set_spacing( 8 ); get_content_area()->pack_start( m_hbox ); set_activate_entry( m_entry ); set_title( "文字列設定" ); show_all_children(); } void AboutConfigDiagStr::slot_ok_clicked() { if( m_value ) *m_value = m_entry.get_text(); } void AboutConfigDiagStr::slot_default() { m_entry.set_text( m_defaultval ); } /////////////////////////////////////////// AboutConfigDiagInt::AboutConfigDiagInt( Gtk::Window* parent, int* value, const int defaultval ) : SKELETON::PrefDiag( parent, "", true ), m_value( value ), m_defaultval( defaultval ), m_button_default( "デフォルト" ) { resize( 200, 1 ); m_entry.set_text( std::to_string( *value ) ); m_hbox.pack_start( m_entry ); m_button_default.signal_clicked().connect( sigc::mem_fun( *this, &AboutConfigDiagInt::slot_default ) ); m_hbox.pack_start( m_button_default, Gtk::PACK_SHRINK ); get_content_area()->set_spacing( 8 ); get_content_area()->pack_start( m_hbox ); set_activate_entry( m_entry ); set_title( "整数値設定" ); show_all_children(); } void AboutConfigDiagInt::slot_ok_clicked() { if( m_value ) *m_value = atoi( m_entry.get_text().c_str() ); } void AboutConfigDiagInt::slot_default() { m_entry.set_text( std::to_string( m_defaultval ) ); } /////////////////////////////////////////// AboutConfigDiagBool::AboutConfigDiagBool( Gtk::Window* parent, bool* value, const bool defaultval ) : SKELETON::PrefDiag( parent, "", true ), m_value( value ), m_defaultval( defaultval ), m_radio_true( "はい" ), m_radio_false( "いいえ" ), m_button_default( "デフォルト" ) { m_radio_true.set_group( m_radiogroup ); m_radio_false.set_group( m_radiogroup ); if( *value ) m_radio_true.set_active(); else m_radio_false.set_active(); m_hbox.pack_start( m_radio_true ); m_hbox.pack_start( m_radio_false ); m_button_default.signal_clicked().connect( sigc::mem_fun( *this, &AboutConfigDiagBool::slot_default ) ); m_hbox.pack_start( m_button_default, Gtk::PACK_SHRINK ); get_content_area()->set_spacing( 8 ); get_content_area()->pack_start( m_hbox ); set_title( "真偽値設定" ); show_all_children(); } void AboutConfigDiagBool::slot_ok_clicked() { if( m_value ) *m_value = m_radio_true.get_active(); } void AboutConfigDiagBool::slot_default() { if( m_defaultval ) m_radio_true.set_active(); else m_radio_false.set_active(); } jdim-0.7.0/src/config/aboutconfigdiag.h000066400000000000000000000031711417047150700200030ustar00rootroot00000000000000// ライセンス: GPL2 // about:config から開く設定ダイアログ #ifndef _ABOUTCONFIGDIAG_H #define _ABOUTCONFIGDIAG_H #include "skeleton/prefdiag.h" namespace CONFIG { class AboutConfigDiagStr : public SKELETON::PrefDiag { std::string* m_value; const std::string m_defaultval; Gtk::HBox m_hbox; Gtk::Entry m_entry; Gtk::Button m_button_default; public: AboutConfigDiagStr( Gtk::Window* parent, std::string* value, const std::string& defaultval ); protected: void slot_ok_clicked() override; void slot_default(); }; ///////////////////////////////////////////// class AboutConfigDiagInt : public SKELETON::PrefDiag { int* m_value; const int m_defaultval; Gtk::HBox m_hbox; Gtk::Entry m_entry; Gtk::Button m_button_default; public: AboutConfigDiagInt( Gtk::Window* parent, int* value, const int defaultval ); protected: void slot_ok_clicked() override; void slot_default(); }; ///////////////////////////////////////////// class AboutConfigDiagBool : public SKELETON::PrefDiag { bool* m_value; const bool m_defaultval; Gtk::HBox m_hbox; Gtk::RadioButtonGroup m_radiogroup; Gtk::RadioButton m_radio_true; Gtk::RadioButton m_radio_false; Gtk::Button m_button_default; public: AboutConfigDiagBool( Gtk::Window* parent, bool* value, const bool defaultval ); protected: void slot_ok_clicked() override; void slot_default(); }; } #endif jdim-0.7.0/src/config/configitems.cpp000066400000000000000000001465231417047150700175310ustar00rootroot00000000000000// ライセンス: GPL2 #ifdef HAVE_CONFIG_H #include "config.h" #endif //#define _DEBUG #include "jddebug.h" #include "configitems.h" #include "defaultconf.h" #include "globalconf.h" #include "jdlib/confloader.h" #include "jdlib/miscutil.h" #include "jdlib/miscgtk.h" #include "jdlib/jdregex.h" #include "skeleton/msgdiag.h" #include "colorid.h" #include "fontid.h" #include "global.h" #include "cache.h" #include "browsers.h" #include using namespace CONFIG; // // デフォルトフォント取得 // #define IS_DEFAULT_FONT( name ) do{ if( set_fonts.find( name ) != set_fonts.end() ) return name; } while(0) std::string get_default_font() { std::set< std::string > set_fonts = MISC::get_font_families(); // 上の方が優先順位が高い IS_DEFAULT_FONT( "IPA モナー Pゴシック" ); IS_DEFAULT_FONT( "IPAMonaPGothic" ); IS_DEFAULT_FONT( "Mona-VLGothic" ); IS_DEFAULT_FONT( "Mona" ); IS_DEFAULT_FONT( "Konatu" ); IS_DEFAULT_FONT( "VL Pゴシック" ); IS_DEFAULT_FONT( "VL PGothic" ); IS_DEFAULT_FONT( "さざなみゴシック" ); IS_DEFAULT_FONT( "Sazanami Gothic" ); IS_DEFAULT_FONT( "Sans" ); return std::string(); } //////////////////////////////////////// ConfigItems::ConfigItems() : fontname( FONT_NUM ) , str_color( COLOR_NUM ) {} ConfigItems::~ConfigItems() noexcept = default; // 設定読み込み bool ConfigItems::load( const bool restore ) { // restoreモード const std::string path_conf = restore ? CACHE::path_conf_bkup() : CACHE::path_conf(); JDLIB::ConfLoader cf( path_conf, std::string() ); #ifdef _DEBUG std::cout << "ConfigItems::load" << std::endl << "conffile = " << path_conf << " empty = " << cf.empty() << std::endl; #endif std::string str_tmp; // 色 set_colors( cf ); // 前回開いたviewを復元するか restore_board = cf.get_option_bool( "restore_board", CONF_RESTORE_BOARD ); restore_article = cf.get_option_bool( "restore_article", CONF_RESTORE_ARTICLE ); restore_image = cf.get_option_bool( "restore_image", CONF_RESTORE_IMAGE ); // 自前でウィンドウ配置を管理する manage_winpos = cf.get_option_bool( "manage_winpos", CONF_MANAGE_WINPOS ); // フォント set_fonts( cf ); // レスを参照するときに前に付ける文字 ref_prefix = cf.get_option_str( "ref_prefix", CONF_REF_PREFIX, 16 ); // 参照文字( ref_prefix ) の後のスペースの数 // JDLIB::ConfLoader の中で MISC::remove_space() が呼ばれて空白が消えるので別設定とした ref_prefix_space = cf.get_option_int( "ref_prefix_space", CONF_REF_PREFIX_SPACE, 0, 16 ); for( int i = 0; i < ref_prefix_space; ++i ) ref_prefix_space_str += " "; // レスにアスキーアートがあると判定する正規表現 regex_res_aa = cf.get_option_str( "regex_res_aa", CONF_REGEX_RES_AA ); // 読み込み用プロクシとポート番号 use_proxy_for2ch = cf.get_option_bool( "use_proxy_for2ch", CONF_USE_PROXY_FOR2CH ); send_cookie_to_proxy_for2ch = cf.get_option_bool( "send_cookie_to_proxy_for2ch", CONF_SEND_COOKIE_TO_PROXY_FOR2CH ); str_tmp = cf.get_option_str( "proxy_for2ch", "" ); set_proxy_for2ch( str_tmp ); proxy_port_for2ch = cf.get_option_int( "proxy_port_for2ch", CONF_PROXY_PORT_FOR2CH, 1, 65535 ); // 書き込み用プロクシとポート番号 use_proxy_for2ch_w = cf.get_option_bool( "use_proxy_for2ch_w", CONF_USE_PROXY_FOR2CH_W ); send_cookie_to_proxy_for2ch_w = cf.get_option_bool( "send_cookie_to_proxy_for2ch_w", CONF_SEND_COOKIE_TO_PROXY_FOR2CH_W ); str_tmp = cf.get_option_str( "proxy_for2ch_w", "" ); set_proxy_for2ch_w( str_tmp ); proxy_port_for2ch_w = cf.get_option_int( "proxy_port_for2ch_w", CONF_PROXY_PORT_FOR2CH_W, 1, 65535 ); // 2chの外にアクセスするときのプロクシとポート番号 use_proxy_for_data = cf.get_option_bool( "use_proxy_for_data", CONF_USE_PROXY_FOR_DATA ); send_cookie_to_proxy_for_data = cf.get_option_bool( "send_cookie_to_proxy_for_data", CONF_SEND_COOKIE_TO_PROXY_FOR_DATA ); str_tmp = cf.get_option_str( "proxy_for_data", "" ); set_proxy_for_data( str_tmp ); proxy_port_for_data = cf.get_option_int( "proxy_port_for_data", CONF_PROXY_PORT_FOR_DATA, 1, 65535 ); // 2ch にアクセスするときのエージェント名 agent_for2ch = cf.get_option_str( "agent_for2ch", CONF_AGENT_FOR2CH ); // 2ch外にアクセスするときのエージェント名 agent_for_data = cf.get_option_str( "agent_for_data", CONF_AGENT_FOR_DATA ); // 2ch にログインするときのX-2ch-UA x_2ch_ua = cf.get_option_str( "x_2ch_ua", CONF_X_2CH_UA ); // ローダのバッファサイズ loader_bufsize = cf.get_option_int( "loader_bufsize", CONF_LOADER_BUFSIZE, 1, 4096 ); // 一般 loader_bufsize_board = cf.get_option_int( "loader_bufsize_board", CONF_LOADER_BUFSIZE_BOARD, 1, 4096 ); // スレ一覧用 // ローダのタイムアウト値 loader_timeout = cf.get_option_int( "loader_timeout", CONF_LOADER_TIMEOUT, 1, 120 ); loader_timeout_post = cf.get_option_int( "loader_timeout_post", CONF_LOADER_TIMEOUT_POST, 1, 120 ); // ポスト loader_timeout_img = cf.get_option_int( "loader_timeout_img", CONF_LOADER_TIMEOUT_IMG, 1, 120 ); // 画像 loader_timeout_checkupdate = cf.get_option_int( "loader_timeout_checkupdate", CONF_LOADER_TIMEOUT_CHECKUPDATE, 1, 120 ); // 更新チェック // ipv6使用 use_ipv6 = cf.get_option_bool( "use_ipv6", CONF_USE_IPV6 ); // 同一ホストに対する最大コネクション数( 1 または 2 ) connection_num = cf.get_option_int( "connection_num", CONF_CONNECTION_NUM, 1, 2 ); // 2chのクッキーを保存する (互換性のため設定名は旧名称を使う) use_cookie_hap = cf.get_option_bool( "use_cookie_hap", CONF_USE_COOKIE_HAP ); // 2chのクッキー (互換性のため設定名は旧名称を使う) cookie_hap = cf.get_option_str( "cookie_hap", CONF_COOKIE_HAP ); cookie_hap_bbspink = cf.get_option_str( "cookie_hap_bbspink", CONF_COOKIE_HAP_BBSPINK ); // ブラウザ設定ダイアログのコンボボックスの番号 browsercombo_id = cf.get_option_int( "browsercombo_id", CONF_BROWSER_NO, 0, CORE::get_browser_number() -1 ); // リンクをクリックしたときに実行するコマンド command_openurl = cf.get_option_str( "command_openurl", CORE::get_browser_name( CONF_BROWSER_NO ) ); // レス番号の上にマウスオーバーしたときに参照ポップアップ表示する refpopup_by_mo = cf.get_option_bool( "refpopup_by_mo", CONF_REFPOPUP_BY_MO ); // 名前の上にマウスオーバーしたときにポップアップ表示する namepopup_by_mo = cf.get_option_bool( "namepopup_by_mo", CONF_NAMEPOPUP_BY_MO ); // IDの上にマウスオーバーしたときにIDをポップアップ表示する idpopup_by_mo = cf.get_option_bool( "idpopup_by_mo", CONF_IDPOPUP_BY_MO ); // 画像のスムージングレベル(0-2, 2が最も高画質かつ低速) imgemb_interp = cf.get_option_int( "imgemb_interp", CONF_IMGEMB_INTERP, 0, 2 ); imgmain_interp = cf.get_option_int( "imgmain_interp", CONF_IMGMAIN_INTERP, 0, 2 ); imgpopup_interp = cf.get_option_int( "imgpopup_interp", CONF_IMGPOPUP_INTERP, 0, 2 ); // 画像ポップアップサイズ imgpopup_width = cf.get_option_int( "imgpopup_width", CONF_IMGPOPUP_WIDTH, 16, 8192 ); imgpopup_height = cf.get_option_int( "imgpopup_height", CONF_IMGPOPUP_HEIGHT, 16, 8192 ); // 画像ポップアップを使用する use_image_popup = cf.get_option_bool( "use_image_popup", CONF_USE_IMAGE_POPUP ); // 画像ビューを使用する use_image_view = cf.get_option_bool( "use_image_view", CONF_USE_IMAGE_VIEW ); // インライン画像を表示する use_inline_image = cf.get_option_bool( "use_inline_image", CONF_INLINE_IMG ); // ssspアイコン表示 show_ssspicon = cf.get_option_bool( "show_ssspicon", CONF_SHOW_SSSPICON ); // インライン画像の最大幅と高さ embimg_width = cf.get_option_int( "embimg_width", CONF_EMBIMG_WIDTH, 16, 8192 ); embimg_height = cf.get_option_int( "embimg_height", CONF_EMBIMG_HEIGHT, 16, 8192 ); // 埋め込み画像ビューを閉じたときにタブも閉じる hide_imagetab = cf.get_option_bool( "hide_imagetab", CONF_HIDE_IMAGETAB ); // 画像ビューでdeleteを押したときに確認ダイアログを表示する show_delimgdiag = cf.get_option_bool( "show_delimgdiag", CONF_SHOW_DELIMGDIAG ); // 画像にモザイクをかける use_mosaic = cf.get_option_bool( "use_mosaic", CONF_USE_MOSAIC ); // モザイクの大きさ mosaic_size = cf.get_option_int( "mosaic_size", CONF_MOSAIC_SIZE, 1, 1024 ); // 画像をデフォルトでウィンドウサイズに合わせる zoom_to_fit = cf.get_option_bool( "zoom_to_fit", CONF_ZOOM_TO_FIT ); // 画像キャッシュ削除の日数 del_img_day = cf.get_option_int( "del_img_day", CONF_DEL_IMG_DAY, 0, 65535 ); // 画像あぼーん削除の日数 del_imgabone_day = cf.get_option_int( "del_imgabone_day", CONF_DEL_IMGABONE_DAY, 0, 65535 ); // ダウンロードする画像の最大ファイルサイズ(Mbyte) max_img_size = cf.get_option_int( "max_img_size", CONF_MAX_IMG_SIZE, 1, 1024 ); // 画像の最大サイズ(Mピクセル) max_img_pixel = cf.get_option_int( "max_img_pixel", CONF_MAX_IMG_PIXEL, 1, 1024 ); // 画像のメモリキャッシュ枚数 imgcache_size = cf.get_option_int( "imgcache_size", CONF_IMGCACHE_SIZE, 0, 20 ); // JD ホームページのアドレス url_jdhp = cf.get_option_str( "url_jdhp", CONF_URL_JDHP ); // JDim ホームページのアドレス url_jdimhp = cf.get_option_str( "url_jdimhp", CONF_URL_JDIMHP ); // 2chの認証サーバのアドレス url_login2ch = cf.get_option_str( "url_login2ch", CONF_LOGIN2CH ); // BEの認証サーバのアドレス url_loginbe = cf.get_option_str( "url_loginbe", CONF_LOGINBE ); // bbsmenu.htmlのURL url_bbsmenu = cf.get_option_str( "url_bbsmenu", CONF_URL_BBSMENU ); // bbsmenu.html内にあるリンクは全て板とみなす use_link_as_board = cf.get_option_bool( "use_link_as_board", CONF_USE_LINK_AS_BOARD ); // 板移転時に確認ダイアログを表示する show_movediag = cf.get_option_bool( "show_movediag", CONF_SHOW_MOVEDIAG ); // スレタイ検索用メニュータイトルアドレス menu_search_title = cf.get_option_str( "menu_search_title", CONF_MENU_SEARCH_TITLE ); url_search_title = cf.get_option_str( "url_search_title", CONF_URL_SEARCH_TITLE ); // スレタイ検索用正規表現 regex_search_title = cf.get_option_str( "regex_search_title", CONF_REGEX_SEARCH_TITLE ); // web検索用メニュータイトルアドレス menu_search_web = cf.get_option_str( "menu_search_web", CONF_MENU_SEARCH_WEB ); url_search_web = cf.get_option_str( "url_search_web", CONF_URL_SEARCH_WEB ); // 書き込みビューでGTKテーマの設定を使用するか (GTK3版のみ) use_message_gtktheme = cf.get_option_bool( "use_message_gtktheme", CONF_USE_MESSAGE_GTKTHEME ); // ツリービューでgtkrcの設定を使用するか use_tree_gtkrc = cf.get_option_bool( "use_tree_gtkrc", CONF_USE_TREE_GTKRC ); // スレビューの選択色でgtkrcの設定を使用するか use_select_gtkrc = cf.get_option_bool( "use_select_gtkrc", CONF_USE_SELECT_GTKRC ); // ツリービューの行間スペース tree_ypad = cf.get_option_int( "tree_ypad", CONF_TREE_YPAD, 0, 64 ); // ツリービューにエクスパンダを表示 tree_show_expanders = cf.get_option_bool( "tree_show_expanders", CONF_TREE_SHOW_EXPANDERS ); // ツリービューのレベルインデント調整量(ピクセル) tree_level_indent = cf.get_option_int( "tree_level_indent", CONF_TREE_LEVEL_INDENT, -256, 256 ); // カテゴリやディレクトリを開いたときにツリービューをスクロールする scroll_tree = cf.get_option_bool( "scroll_tree", CONF_SCROLL_TREE ); // ツリービューの選択を表示中のビューと同期する select_item_sync = cf.get_option_int( "select_item_sync", CONF_SELECT_ITEM_SYNC, 0, 2 ); // 各ビューと枠との間の余白 view_margin = cf.get_option_int( "view_margin", CONF_VIEW_MARGIN, 0, 64 ); // スクロールバーを左に配置 left_scrbar = cf.get_option_bool( "left_scrbar", CONF_LEFT_SCRBAR ); // スレ一覧で古いスレも表示 show_oldarticle = cf.get_option_bool( "show_oldarticle", CONF_SHOW_OLDARTICLE ); // スレ一覧で指定した値(時間)よりも後に立てられたスレを新着とみなす newthread_hour = cf.get_option_int( "newthread_hour", CONF_NEWTHREAD_HOUR, 1, 65535 ); // スレ一覧でインクリメント検索をする inc_search_board = cf.get_option_bool( "inc_search_board", CONF_INC_SEARCH_BOARD ); // スレ一覧でdeleteを押したときに確認ダイアログを表示する show_deldiag = cf.get_option_bool( "show_deldiag", CONF_SHOW_DELDIAG ); // スレ一覧をロードする前にキャッシュにある一覧を表示 show_cached_board = cf.get_option_bool( "show_cached_board", CONF_SHOW_CACHED_BOARD ); // スレ一覧でお知らせスレ(924)のアイコンを表示する show_924 = cf.get_option_bool( "show_924", CONF_SHOW_924 ); // ツリービューのスクロール量(行数) tree_scroll_size = cf.get_option_int( "tree_scroll_size", CONF_TREE_SCROLL_SIZE, 1, 64 ); // スレビューのスクロール量 scroll_size = cf.get_option_int( "scroll_size", CONF_SCROLL_SIZE, 1, 64 ); // スレビューのスクロール量(キー上下) key_scroll_size = cf.get_option_int( "key_scroll_size", CONF_KEY_SCROLL_SIZE, 1, 64 ); // スレビューの高速スクロール量(キー上下・ ページ高 - 行高 * key_fastscroll_size ) key_fastscroll_size = cf.get_option_int( "key_fastscroll_size", CONF_KEY_FASTSCROLL_SIZE, 1, 64 ); // スレビューでリロード後に一番下までスクロール jump_after_reload = cf.get_option_bool( "jump_after_reload", CONF_JUMP_AFTER_RELOAD ); // スレビューでリロード後に新着までスクロール jump_new_after_reload = cf.get_option_bool( "jump_new_after_reload", CONF_JUMP_NEW_AFTER_RELOAD ); // 実況モード live_mode = cf.get_option_int( "live_mode", LIVE_SCRMODE_VARIABLE, 0, LIVE_SCRMODE_NUM -1 ); // 実況速度 live_speed = cf.get_option_int( "live_speed", CONF_LIVE_SPEED, 0, 50 ); // 実況のスクロールモードを切り替えるしきい値 live_threshold = cf.get_option_int( "live_threshold", CONF_LIVE_THRESHOLD, 1, 1024 ); // 板一覧でカテゴリを常にひとつだけ開く open_one_category = cf.get_option_bool( "open_one_category", CONF_OPEN_ONE_CATEGORY ); // お気に入りでディレクトリを常にひとつだけ開く open_one_favorite = cf.get_option_bool( "open_one_favorite", CONF_OPEN_ONE_FAVORITE ); // デフォルトの書き込み名 write_name = cf.get_option_str( "write_name", CONF_WRITE_NAME ); // デフォルトのメールアドレス write_mail = cf.get_option_str( "write_mail", CONF_WRITE_MAIL ); // 書き込み時に書き込み確認ダイアログを出さない always_write_ok = cf.get_option_bool( "always_write_ok", CONF_ALWAYS_WRITE_OK ); // 書き込みログを保存 save_postlog = cf.get_option_bool( "save_postlog", CONF_SAVE_POSTLOG ); // 書き込みログの最大サイズ maxsize_postlog = cf.get_option_int( "maxsize_postlog", CONF_MAXSIZE_POSTLOG, 1024, 1024 * 1024 ); // 書き込み履歴を保存 save_posthist = cf.get_option_bool( "save_posthist", CONF_SAVE_POSTHIST ); // 「書き込み中」のダイアログを表示しない hide_writing_dialog = cf.get_option_bool( "hide_writing_dialog", CONF_HIDE_WRITING_DIALOG ); // 編集中のメッセージの保存確認ダイアログを表示する show_savemsgdiag = cf.get_option_bool( "show_savemsgdiag", CONF_SHOW_SAVEMSGDIAG ); // 書き込みビューでテキストを折り返す message_wrap = cf.get_option_bool( "message_wrap", CONF_MESSAGE_WRAP ); // 非アクティブ時に書き込みビューを折りたたむ fold_message = cf.get_option_bool( "fold_message", CONF_FOLD_MESSAGE ); // 非アクティブ時に画像ビューを折りたたむ fold_image = cf.get_option_bool( "fold_image", CONF_FOLD_IMAGE ); // 書き込み欄の日本語のON/OFF状態を保存 keep_im_status = cf.get_option_bool( "keep_im_status", CONF_KEEP_IM_STATUS ); // ポップアップとカーソルの間のマージン margin_popup = cf.get_option_int( "margin_popup", CONF_MARGIN_POPUP, 0, 1024 ); // 画像ポップアップとカーソルの間のマージン margin_imgpopup_x = cf.get_option_int( "margin_imgpopup_x", CONF_MARGIN_IMGPOPUP_X, 0, 1024 ); margin_imgpopup = cf.get_option_int( "margin_imgpopup", CONF_MARGIN_IMGPOPUP, 0, 1024 ); // ポップアップが消えるまでの時間(ミリ秒) hide_popup_msec = cf.get_option_int( "hide_popup_msec", CONF_HIDE_POPUP_MSEC, 0, 2000 ); // マウスジェスチャを有効 enable_mg = cf.get_option_bool( "enable_mg", CONF_ENABLE_MG ); // マウスジェスチャの判定開始半径 mouse_radius = cf.get_option_int( "mouse_radius", CONF_MOUSE_RADIUS, 4, 1024 ); // 数字入力ジャンプの待ち時間(ミリ秒) numberjmp_msec = cf.get_option_int( "numberjmp_msec", CONF_NUMBERJMP_MSEC, 1, 5000 ); // 履歴メニューの表示数 history_size = cf.get_option_int( "history_size", CONF_HISTORY_SIZE, 0, 256 ); // 履歴ビューの表示数 historyview_size = cf.get_option_int( "historyview_size", CONF_HISTORYVIEW_SIZE, 0, 1024 ); // AA履歴の保持数 aahistory_size = cf.get_option_int( "aahistory_size", CONF_AAHISTORY, 0, 65535 ); // 0以上なら多重ポップアップの説明を表示する instruct_popup = cf.get_option_int( "instruct_popup", CONF_INSTRUCT_POPUP, 0, CONF_INSTRUCT_POPUP ); // スレビューを開いたときにスレ一覧との切り替え方法を説明する instruct_tglart = cf.get_option_bool( "instruct_tglart", CONF_INSTRUCT_TGLART ); instruct_tglart_end = false; // 画像ビューを開いたときにスレビューとの切り替え方法を説明する instruct_tglimg = cf.get_option_bool( "instruct_tglimg", CONF_INSTRUCT_TGLIMG ); instruct_tglimg_end = false; // スレビューでdeleteを押したときに確認ダイアログを表示する show_delartdiag = cf.get_option_bool( "show_delartdiag", CONF_SHOW_DELARTDIAG ); // 下線位置 adjust_underline_pos = cf.get_option_double( "adjust_underline_pos", ( double )CONF_ADJUST_UNDERLINE_POS, ( double )0, ( double )64 ); // 行間スペース adjust_line_space = cf.get_option_double( "adjust_line_space", ( double )CONF_ADJUST_LINE_SPACE, ( double )0, ( double )64 ); // リンク下線を表示 draw_underline = cf.get_option_bool( "draw_underline", CONF_DRAW_UNDERLINE ); // スレビューで文字幅の近似を厳密にする strict_char_width = cf.get_option_bool( "strict_char_width", CONF_STRICT_CHAR_WIDTH ); // スレビューで発言数(ID)をカウントする check_id = cf.get_option_bool( "check_id", CONF_CHECK_ID ); // レス参照で色を変える回数 num_reference_high = cf.get_option_int( "num_reference_high", CONF_NUM_REFERENCE_HIGH, 1, 256 ); num_reference_low = cf.get_option_int( "num_reference_low", CONF_NUM_REFERENCE_LOW, 1, 128 ); // 発言数で色を変える回数 num_id_high = cf.get_option_int( "num_id_high", CONF_NUM_ID_HIGH, 1, 256 ); num_id_low = cf.get_option_int( "num_id_low", CONF_NUM_ID_LOW, 1, 128 ); // datのパース時にURL判定を甘くする(^なども含める) loose_url = cf.get_option_bool( "loose_url", CONF_LOOSE_URL ); // ユーザーコマンドで選択できない項目を非表示にする hide_usrcmd = cf.get_option_bool( "hide_usrcmd", CONF_HIDE_USRCMD ); // スレビューで再読み込みボタンを押したときに全タブを更新する reload_allthreads = cf.get_option_bool( "reload_allthreads", CONF_RELOAD_ALLTHREAD ); // タブに表示する文字列の最小値 tab_min_str = cf.get_option_int( "tab_min_str", CONF_TAB_MIN_STR, 1, 256 ); // タブにアイコンを表示するか show_tab_icon = cf.get_option_bool( "show_tab_icon", CONF_SHOW_TAB_ICON ); // タブ上でマウスホイールを回転してタブを切り替える switchtab_wheel = cf.get_option_bool( "switchtab_wheel", CONF_SWITCHTAB_WHEEL ); // 他のビューを開くときのタブの位置 ( 0: 一番右端 1:右隣 2:左隣 ) newtab_pos = cf.get_option_int( "newtab_pos", CONF_NEWTAB_POS, 0, 2 ); // ツリービューで選択したビューを開くときのタブの位置 ( 0: 一番右端 1:右隣 2:左隣 ) opentab_pos = cf.get_option_int( "opentab_pos", CONF_OPENTAB_POS, 0, 2 ); // 次スレ検索を開くときのタブの位置 ( 0: 次スレ検索タブ 1:新しいタブ 2:アクティブなタブを置き換え -1:2.8.5以前の動作 ) boardnexttab_pos = cf.get_option_int( "boardnexttab_pos", CONF_BOARDNEXTTAB_POS, -1, 2 ); // スレビューに書き込みマークを表示するか show_post_mark = cf.get_option_bool( "show_post_mark", CONF_SHOW_POST_MARK ); // ボタンをフラットにするか flat_button = cf.get_option_bool( "flat_button", CONF_FLAT_BUTTON ); // ツールバーの背景描画 draw_toolbarback = cf.get_option_bool( "draw_toolbarback", CONF_DRAW_TOOLBARBACK ); // スレ あぼーん word str_tmp = cf.get_option_str( "abonewordthread", "" ); if( ! str_tmp.empty() ) list_abone_word_thread = MISC::strtolist( str_tmp ); // スレ あぼーん regex str_tmp = cf.get_option_str( "aboneregexthread", "" ); if( ! str_tmp.empty() ) list_abone_regex_thread = MISC::strtolist( str_tmp ); // dat落ちしたスレをNGスレタイトルリストから取り除くか( 0: ダイアログ表示 1: 取り除く 2: 除かない ) remove_old_abone_thread = cf.get_option_int( "remove_old_abone_thread", CONF_REMOVE_OLD_ABONE_THREAD, 0, 2 ); // スレ あぼーん( レス数 ) // abone_number_thread は変数や関数と名前が異なるが互換性のため維持する abone_low_number_thread = cf.get_option_int( "abone_low_number_thread", CONF_ABONE_LOW_NUMBER_THREAD, 0, CONFIG::get_max_resnumber() ); abone_high_number_thread = cf.get_option_int( "abone_number_thread", CONF_ABONE_HIGH_NUMBER_THREAD, 0, CONFIG::get_max_resnumber() ); // スレ あぼーん( スレ立てからの経過時間 ) abone_hour_thread = cf.get_option_int( "abone_hour_thread", CONF_ABONE_HOUR_THREAD, 0, 9999 ); // あぼーん name str_tmp = cf.get_option_str( "abonename", "" ); if( ! str_tmp.empty() ) list_abone_name = MISC::strtolist( str_tmp ); // あぼーん word str_tmp = cf.get_option_str( "aboneword", "" ); if( ! str_tmp.empty() ) list_abone_word = MISC::strtolist( str_tmp ); // あぼーん regex str_tmp = cf.get_option_str( "aboneregex", "" ); if( ! str_tmp.empty() ) list_abone_regex = MISC::strtolist( str_tmp ); // デフォルトで透明、連鎖あぼーんをするか abone_transparent = cf.get_option_bool( "abone_transparent", CONF_ABONE_TRANSPARENT ); abone_chain = cf.get_option_bool( "abone_chain", CONF_ABONE_CHAIN ); // NG正規表現によるあぼーん時に大小と全半角文字の違いを無視 abone_icase = cf.get_option_bool( "abone_icase", CONF_ABONE_ICASE ); abone_wchar = cf.get_option_bool( "abone_wchar", CONF_ABONE_WCHAR ); // 右ペーンが空の時にサイドバーを閉じるか expand_sidebar = cf.get_option_bool( "expand_sidebar", CONF_EXPAND_SIDEBAR ); // 3ペーン時にスレ一覧やスレビューを最大化する expand_rpane = cf.get_option_bool( "expand_rpane", CONF_EXPAND_RPANE ); // ペーンの境界をクリックしてサイドバーを開け閉めする open_sidebar_by_click = cf.get_option_bool( "open_sidebar_by_click", CONF_OPEN_SIDEBAR_BY_CLICK ); // 次スレ検索の類似度のしきい値 threshold_next = cf.get_option_int( "threshold_next", CONF_THRESHOLD_NEXT, 1, 10 ); // 次スレを開いたときにお気に入りのアドレスと名前を自動更新 replace_favorite_next = cf.get_option_int( "replace_favorite_next", CONF_REPLACE_FAVORITE_NEXT, 0, 2 ); // お気に入りの自動更新をするかダイアログを出す show_diag_replace_favorite = cf.get_option_bool( "show_diag_replace_favorite", CONF_SHOW_DIAG_REPLACE_FAVORITE ); // スレをお気に入りに追加したときにしおりをセットする bookmark_drop = cf.get_option_bool( "bookmark_drop", CONF_BOOKMARK_DROP ); // お気に入りの更新チェック時に板の更新もチェックする check_update_board = cf.get_option_bool( "check_update_board", CONF_CHECK_UPDATE_BOARD ); // 起動時にお気に入りを自動でチェックする check_update_boot = cf.get_option_bool( "check_update_boot", CONF_CHECK_UPDATE_BOOT ); // お気に入り登録時に重複項目を登録するか ( 0: 登録する 1: ダイアログ表示 2: 登録しない ) check_favorite_dup = cf.get_option_int( "check_favorite_dup", CONF_CHECK_FAVORITE_DUP, 0, 2 ); // お気に入り登録時に挿入先ダイアログを表示する ( 0 : 表示する 1: 表示せず先頭に追加 2: 表示せず最後に追加 ) show_favorite_select_diag = cf.get_option_int( "show_favorite_select_diag", CONF_SHOW_FAVORITE_SELECT_DIAG, 0, 2 ); // Ctrl+qでウィンドウを閉じない disable_close = cf.get_option_bool( "disable_close", CONF_DISABLE_CLOSE ); // メニューバーを非表示にした時にダイアログを表示 show_hide_menubar_diag = cf.get_option_bool( "show_hide_menubar_diag", CONF_SHOW_HIDE_MENUBAR_DIAG ); // 状態変更時にメインステータスバーの色を変える change_stastatus_color = cf.get_option_bool( "change_stastatus_color", CONF_CHANGE_STASTATUS_COLOR ); // Client-Side Decorationを使うか( 0: 使わない 1: 使う 2: デスクトップに合わせる ) use_header_bar = cf.get_option_int( "use_header_bar", CONF_USE_HEADER_BAR, 0, 2 ); // まちBBSの取得に offlaw.cgi を使用する use_machi_offlaw = cf.get_option_bool( "use_machi_offlaw", CONF_USE_MACHI_OFFLAW ); // 書き込み履歴のあるスレを削除する時にダイアログを表示 show_del_written_thread_diag = cf.get_option_bool( "show_del_written_thread_diag", CONF_SHOW_DEL_WRITTEN_THREAD_DIAG ); // スレを削除する時に画像キャッシュも削除する ( 0: ダイアログ表示 1: 削除 2: 削除しない ) delete_img_in_thread = cf.get_option_int( "delete_img_in_thread", CONF_DELETE_IMG_IN_THREAD, 0, 2 ); //最大表示可能レス数 max_resnumber = cf.get_option_int( "max_resnumber", CONF_MAX_RESNUMBER, 1, std::numeric_limits< int >::max() - 1 ); // FIFOの作成などにエラーがあったらダイアログを表示する show_diag_fifo_error = cf.get_option_bool( "show_diag_fifo_error", CONF_SHOW_DIAG_FIFO_ERROR ); // 指定した分ごとにセッションを自動保存 (0: 保存しない) save_session = cf.get_option_int( "save_session", CONF_SAVE_SESSION, 0, (24*60) ); #ifdef HAVE_MIGEMO_H // migemo-dictの場所 migemodict_path = cf.get_option_str( "migemodict_path", CONF_MIGEMO_PATH ); #endif m_loaded = true; // 設定値に壊れている物がある if( cf.is_broken() ) { std::string msg; // 非resotreモードでバックアップが存在する if( ! restore && CACHE::file_exists( CACHE::path_conf_bkup() ) == CACHE::EXIST_FILE ) { msg = "設定ファイル (" + path_conf + ")に異常な値があります。全設定をバックアップから復元しますか?"; Gtk::MessageDialog mdiag( msg, false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO ); const int ret = mdiag.run(); if( ret != Gtk::RESPONSE_YES ) load( true ); // resotreモードで再帰 } else { msg = "設定ファイル (" + path_conf + ")に異常な値があるため、一部をデフォルトに設定しました。"; Gtk::MessageDialog mdiag( msg ); mdiag.run(); } } // 正常ならバックアップ else if( CACHE::file_exists( CACHE::path_root() ) == CACHE::EXIST_DIR ) { save_impl( CACHE::path_conf_bkup() ); } return ! cf.empty(); } // 保存 void ConfigItems::save() { save_impl( CACHE::path_conf() ); } void ConfigItems::save_impl( const std::string& path ) { if( ! m_loaded ) return; #ifdef _DEBUG std::cout << "ConfigItems::save_impl path = " << path << std::endl; #endif JDLIB::ConfLoader cf( path, std::string() ); cf.update( "restore_board", restore_board ); cf.update( "restore_article", restore_article ); cf.update( "restore_image", restore_image ); cf.update( "manage_winpos", manage_winpos ); cf.update( "url_jdhp", url_jdhp ); cf.update( "url_jdimhp", url_jdimhp ); cf.update( "url_login2ch", url_login2ch ); cf.update( "url_loginbe", url_loginbe ); cf.update( "url_bbsmenu", url_bbsmenu ); cf.update( "use_link_as_board", use_link_as_board ); cf.update( "show_movediag", show_movediag ); cf.update( "menu_search_title", menu_search_title ); cf.update( "url_search_title", url_search_title ); cf.update( "regex_search_title", regex_search_title ); cf.update( "menu_search_web", menu_search_web ); cf.update( "url_search_web", url_search_web ); cf.update( "fontname_main", fontname[ FONT_MAIN ] ); cf.update( "fontname_mail", fontname[ FONT_MAIL ] ); cf.update( "fontname_popup", fontname[ FONT_POPUP ] ); cf.update( "fontname_aa", fontname[ FONT_AA ] ); cf.update( "fontname_bbs", fontname[ FONT_BBS ] ); cf.update( "fontname_board", fontname[ FONT_BOARD ] ); cf.update( "fontname_message", fontname[ FONT_MESSAGE ] ); cf.update( "ref_prefix", ref_prefix ); cf.update( "ref_prefix_space", ref_prefix_space ); cf.update( "regex_res_aa", regex_res_aa ); cf.update( "agent_for2ch", agent_for2ch ); std::string tmp_proxy; if( proxy_basicauth_for2ch.empty() ) tmp_proxy = proxy_for2ch; else tmp_proxy = proxy_basicauth_for2ch + "@" + proxy_for2ch; cf.update( "use_proxy_for2ch", use_proxy_for2ch ); cf.update( "send_cookie_to_proxy_for2ch", send_cookie_to_proxy_for2ch ); cf.update( "proxy_for2ch", tmp_proxy ); cf.update( "proxy_port_for2ch", proxy_port_for2ch ); if( proxy_basicauth_for2ch_w.empty() ) tmp_proxy = proxy_for2ch_w; else tmp_proxy = proxy_basicauth_for2ch_w + "@" + proxy_for2ch_w; cf.update( "use_proxy_for2ch_w", use_proxy_for2ch_w ); cf.update( "send_cookie_to_proxy_for2ch_w", send_cookie_to_proxy_for2ch_w ); cf.update( "proxy_for2ch_w", tmp_proxy ); cf.update( "proxy_port_for2ch_w", proxy_port_for2ch_w ); cf.update( "agent_for_data", agent_for_data ); if( proxy_basicauth_for_data.empty() ) tmp_proxy = proxy_for_data; else tmp_proxy = proxy_basicauth_for_data + "@" + proxy_for_data; cf.update( "use_proxy_for_data", use_proxy_for_data ); cf.update( "send_cookie_to_proxy_for_data", send_cookie_to_proxy_for_data ); cf.update( "proxy_for_data", tmp_proxy ); cf.update( "proxy_port_for_data", proxy_port_for_data ); cf.update( "x_2ch_ua", x_2ch_ua ); cf.update( "loader_bufsize", loader_bufsize ); cf.update( "loader_bufsize_board", loader_bufsize_board ); cf.update( "loader_timeout", loader_timeout ); cf.update( "loader_timeout_post", loader_timeout_post ); cf.update( "loader_timeout_img", loader_timeout_img ); cf.update( "loader_timeout_checkupdate", loader_timeout_checkupdate ); cf.update( "use_ipv6", use_ipv6 ); cf.update( "connection_num", connection_num ); cf.update( "use_cookie_hap", use_cookie_hap ); cf.update( "cookie_hap", cookie_hap ); cf.update( "cookie_hap_bbspink", cookie_hap_bbspink ); cf.update( "command_openurl", command_openurl ); cf.update( "browsercombo_id", browsercombo_id ); cf.update( "refpopup_by_mo", refpopup_by_mo ); cf.update( "namepopup_by_mo", namepopup_by_mo ); cf.update( "idpopup_by_mo", idpopup_by_mo ); cf.update( "imgemb_interp", imgemb_interp ); cf.update( "imgmain_interp", imgmain_interp ); cf.update( "imgpopup_interp", imgpopup_interp ); cf.update( "imgpopup_width", imgpopup_width ); cf.update( "imgpopup_height", imgpopup_height ); cf.update( "use_image_popup", use_image_popup ); cf.update( "use_image_view", use_image_view ); cf.update( "use_inline_image", use_inline_image ); cf.update( "show_ssspicon", show_ssspicon ); cf.update( "embimg_width", embimg_width ); cf.update( "embimg_height", embimg_height ); cf.update( "hide_imagetab", hide_imagetab ); cf.update( "show_delimgdiag", show_delimgdiag ); cf.update( "use_mosaic", use_mosaic ); cf.update( "mosaic_size", mosaic_size ); cf.update( "zoom_to_fit", zoom_to_fit ); cf.update( "del_img_day", del_img_day ); cf.update( "del_imgabone_day", del_imgabone_day ); cf.update( "max_img_size", max_img_size ); cf.update( "max_img_pixel", max_img_pixel ); cf.update( "imgcache_size", imgcache_size ); cf.update( "cl_char", str_color[ COLOR_CHAR ] ); cf.update( "cl_char_name", str_color[ COLOR_CHAR_NAME ] ); cf.update( "cl_char_name_b", str_color[ COLOR_CHAR_NAME_B ] ); cf.update( "cl_char_name_nomail", str_color[ COLOR_CHAR_NAME_NOMAIL ] ); cf.update( "cl_char_age", str_color[ COLOR_CHAR_AGE ] ); cf.update( "cl_char_selection", str_color[ COLOR_CHAR_SELECTION ] ); cf.update( "cl_char_highlight", str_color[ COLOR_CHAR_HIGHLIGHT ] ); cf.update( "cl_char_link", str_color[ COLOR_CHAR_LINK ] ); cf.update( "cl_char_link_id_low", str_color[ COLOR_CHAR_LINK_ID_LOW ] ); cf.update( "cl_char_link_id_high", str_color[ COLOR_CHAR_LINK_ID_HIGH ] ); cf.update( "cl_char_link_res", str_color[ COLOR_CHAR_LINK_RES ] ); cf.update( "cl_char_link_low", str_color[ COLOR_CHAR_LINK_LOW ] ); cf.update( "cl_char_link_high", str_color[ COLOR_CHAR_LINK_HIGH ] ); cf.update( "cl_char_message", str_color[ COLOR_CHAR_MESSAGE ] ); cf.update( "cl_char_message_selection", str_color[ COLOR_CHAR_MESSAGE_SELECTION ] ); cf.update( "cl_img_nocache", str_color[ COLOR_IMG_NOCACHE ] ); cf.update( "cl_img_cached", str_color[ COLOR_IMG_CACHED ] ); cf.update( "cl_img_loading", str_color[ COLOR_IMG_LOADING ] ); cf.update( "cl_img_err", str_color[ COLOR_IMG_ERR ] ); cf.update( "cl_back", str_color[ COLOR_BACK ] ); cf.update( "cl_back_popup", str_color[ COLOR_BACK_POPUP ] ); cf.update( "cl_back_selection", str_color[ COLOR_BACK_SELECTION ] ); cf.update( "cl_back_highlight",str_color[ COLOR_BACK_HIGHLIGHT ] ); cf.update( "cl_back_highlight_tree",str_color[ COLOR_BACK_HIGHLIGHT_TREE ] ); cf.update( "cl_back_message", str_color[ COLOR_BACK_MESSAGE ] ); cf.update( "cl_back_message_selection", str_color[ COLOR_BACK_MESSAGE_SELECTION ] ); cf.update( "cl_sepa_new", str_color[ COLOR_SEPARATOR_NEW ] ); cf.update( "cl_frame", str_color[ COLOR_FRAME ] ); cf.update( "cl_marker", str_color[ COLOR_MARKER ] ); cf.update( "cl_chr_bbs", str_color[ COLOR_CHAR_BBS ] ); cf.update( "cl_chr_bbs_com", str_color[ COLOR_CHAR_BBS_COMMENT ] ); cf.update( "cl_chr_board", str_color[ COLOR_CHAR_BOARD ] ); cf.update( "cl_back_bbs", str_color[ COLOR_BACK_BBS ] ); cf.update( "cl_back_bbs_even", str_color[ COLOR_BACK_BBS_EVEN ] ); cf.update( "cl_back_board", str_color[ COLOR_BACK_BOARD ] ); cf.update( "cl_back_board_even", str_color[ COLOR_BACK_BOARD_EVEN ] ); cf.update( "use_message_gtktheme", use_message_gtktheme ); cf.update( "use_tree_gtkrc", use_tree_gtkrc ); cf.update( "use_select_gtkrc", use_select_gtkrc ); cf.update( "tree_ypad", tree_ypad ); cf.update( "tree_show_expanders", tree_show_expanders ); cf.update( "tree_level_indent", tree_level_indent ); cf.update( "scroll_tree", scroll_tree ); cf.update( "select_item_sync", select_item_sync ); cf.update( "view_margin", view_margin ); cf.update( "left_scrbar", left_scrbar ); cf.update( "show_oldarticle", show_oldarticle ); cf.update( "newthread_hour", newthread_hour ); cf.update( "inc_search_board", inc_search_board ); cf.update( "show_deldiag", show_deldiag ); cf.update( "show_cached_board", show_cached_board ); cf.update( "show_924", show_924 ); cf.update( "tree_scroll_size", tree_scroll_size ); cf.update( "scroll_size", scroll_size ); cf.update( "key_scroll_size", key_scroll_size ); cf.update( "key_fastscroll_size", key_fastscroll_size ); cf.update( "jump_after_reload", jump_after_reload ); cf.update( "jump_new_after_reload", jump_new_after_reload ); cf.update( "live_mode", live_mode ); cf.update( "live_speed", live_speed ); cf.update( "live_threshold", live_threshold ); cf.update( "open_one_category", open_one_category ); cf.update( "open_one_favorite", open_one_favorite ); cf.update( "write_mail", write_mail ); cf.update( "write_name", write_name ); cf.update( "always_write_ok", always_write_ok ); cf.update( "save_postlog", save_postlog ); cf.update( "maxsize_postlog", maxsize_postlog ); cf.update( "save_posthist", save_posthist ); cf.update( "hide_writing_dialog", hide_writing_dialog ); cf.update( "show_savemsgdiag", show_savemsgdiag ); cf.update( "message_wrap", message_wrap ); cf.update( "fold_message", fold_message ); cf.update( "fold_image", fold_image ); cf.update( "keep_im_status", keep_im_status ); cf.update( "margin_popup", margin_popup ); cf.update( "margin_imgpopup_x", margin_imgpopup_x ); cf.update( "margin_imgpopup", margin_imgpopup ); cf.update( "hide_popup_msec", hide_popup_msec ); cf.update( "enable_mg", enable_mg ); cf.update( "mouse_radius", mouse_radius ); cf.update( "numberjmp_msec", numberjmp_msec ); cf.update( "history_size", history_size ); cf.update( "historyview_size", historyview_size ); cf.update( "aahistory_size", aahistory_size ); cf.update( "instruct_popup", instruct_popup ); cf.update( "instruct_tglart", instruct_tglart ); cf.update( "instruct_tglimg", instruct_tglimg ); cf.update( "show_delartdiag", show_delartdiag ); cf.update( "adjust_underline_pos", adjust_underline_pos ); cf.update( "adjust_line_space", adjust_line_space ); cf.update( "draw_underline", draw_underline ); cf.update( "strict_char_width", strict_char_width ); cf.update( "check_id", check_id ); cf.update( "num_reference_high", num_reference_high ); cf.update( "num_reference_low", num_reference_low ); cf.update( "num_id_high", num_id_high ); cf.update( "num_id_low", num_id_low ); cf.update( "loose_url", loose_url ); cf.update( "hide_usrcmd", hide_usrcmd ); cf.update( "reload_allthreads", reload_allthreads ); cf.update( "tab_min_str", tab_min_str ); cf.update( "show_tab_icon", show_tab_icon ); cf.update( "switchtab_wheel", switchtab_wheel ); cf.update( "newtab_pos", newtab_pos ); cf.update( "opentab_pos", opentab_pos ); cf.update( "boardnexttab_pos", boardnexttab_pos ); cf.update( "show_post_mark", show_post_mark ); cf.update( "flat_button", flat_button ); cf.update( "draw_toolbarback", draw_toolbarback ); // スレあぼーん情報 std::string str_abone_word_thread = MISC::listtostr( list_abone_word_thread ); std::string str_abone_regex_thread = MISC::listtostr( list_abone_regex_thread ); cf.update( "abonewordthread", str_abone_word_thread ); cf.update( "aboneregexthread", str_abone_regex_thread ); cf.update( "remove_old_abone_thread", remove_old_abone_thread ); cf.update( "abone_low_number_thread", abone_low_number_thread ); cf.update( "abone_number_thread", abone_high_number_thread ); cf.update( "abone_hour_thread", abone_hour_thread ); // あぼーん情報 std::string str_abone_name = MISC::listtostr( list_abone_name ); std::string str_abone_word = MISC::listtostr( list_abone_word ); std::string str_abone_regex = MISC::listtostr( list_abone_regex ); cf.update( "abonename", str_abone_name ); cf.update( "aboneword", str_abone_word ); cf.update( "aboneregex", str_abone_regex ); cf.update( "abone_transparent", abone_transparent ); cf.update( "abone_chain", abone_chain ); cf.update( "abone_icase", abone_icase ); cf.update( "abone_wchar", abone_wchar ); cf.update( "expand_sidebar", expand_sidebar ); cf.update( "expand_rpane", expand_rpane ); cf.update( "open_sidebar_by_click", open_sidebar_by_click ); cf.update( "threshold_next", threshold_next ); cf.update( "replace_favorite_next", replace_favorite_next ); cf.update( "show_diag_replace_favorite", show_diag_replace_favorite ); cf.update( "bookmark_drop", bookmark_drop ); cf.update( "check_update_board", check_update_board ); cf.update( "check_update_boot", check_update_boot ); cf.update( "check_favorite_dup", check_favorite_dup ); cf.update( "show_favorite_select_diag", show_favorite_select_diag ); cf.update( "disable_close", disable_close ); cf.update( "show_hide_menubar_diag", show_hide_menubar_diag ); cf.update( "change_stastatus_color", change_stastatus_color ); cf.update( "use_header_bar", use_header_bar ); cf.update( "use_machi_offlaw", use_machi_offlaw ); cf.update( "show_del_written_thread_diag", show_del_written_thread_diag ); cf.update( "delete_img_in_thread", delete_img_in_thread ); cf.update( "max_resnumber", max_resnumber ); cf.update( "show_diag_fifo_error", show_diag_fifo_error ); cf.update( "save_session", save_session ); #ifdef HAVE_MIGEMO_H cf.update( "migemodict_path", migemodict_path ); #endif cf.save(); } // // フォントのセット // void ConfigItems::set_fonts( JDLIB::ConfLoader& cf ) { const std::string defaultfont = get_default_font(); // フォント fontname[ FONT_MAIN ] = cf.get_option_str( "fontname_main", defaultfont + " " + std::string( CONF_FONTSIZE_THREAD ) ); fontname[ FONT_MAIL ] = cf.get_option_str( "fontname_mail", defaultfont + " " + std::string( CONF_FONTSIZE_MAIL ) ); // ポップアップのフォント fontname[ FONT_POPUP ] = cf.get_option_str( "fontname_popup", defaultfont + " " + std::string( CONF_FONTSIZE_POPUP ) ); // AA(スレビュー)のフォント fontname[ FONT_AA ] = cf.get_option_str( "fontname_aa", fontname[ FONT_MAIN ] ); aafont_enabled = ( fontname[ FONT_MAIN ] != fontname[ FONT_AA ] ); // スレ一覧のフォント fontname[ FONT_BBS ] = cf.get_option_str( "fontname_bbs", defaultfont + " " + std::string( CONF_FONTSIZE_TREE ) ); // 板一覧のフォント fontname[ FONT_BOARD ] = cf.get_option_str( "fontname_board", fontname[ FONT_BBS ] ); // 書き込みウィンドウのフォント fontname[ FONT_MESSAGE ] = cf.get_option_str( "fontname_message", fontname[ FONT_MAIN ] ); // Gtk::Entryのデフォルトフォント fontname[ FONT_ENTRY_DEFAULT ] = MISC::get_entry_font(); } // // フォントのリセット // void ConfigItems::reset_fonts() { // dummyのConfLoaderをset_fonts()に渡してデフォルト値をセットする JDLIB::ConfLoader cf( "", "dummy = dummy" ); set_fonts( cf ); } // // 色のセット // void ConfigItems::set_colors( JDLIB::ConfLoader& cf ) { // 文字色 13 = "#FFFFFFFFFFFF" or "#%DDx%DDx%DDx" str_color[ COLOR_CHAR ] = cf.get_option_str( "cl_char", CONF_COLOR_CHAR, 13 ); // 名前欄の文字色 str_color[ COLOR_CHAR_NAME ] = cf.get_option_str( "cl_char_name", CONF_COLOR_CHAR_NAME, 13 ); // トリップ等の名前欄の文字色 str_color[ COLOR_CHAR_NAME_B ] = cf.get_option_str( "cl_char_name_b", CONF_COLOR_CHAR_NAME_B, 13 ); // 名前無し時の名前欄の文字色 str_color[ COLOR_CHAR_NAME_NOMAIL ] = cf.get_option_str( "cl_char_name_nomail", CONF_COLOR_CHAR_NAME_NOMAIL, 13 ); // ageの時のメール欄の文字色 str_color[ COLOR_CHAR_AGE ] = cf.get_option_str( "cl_char_age", CONF_COLOR_CHAR_AGE, 13 ); // 選択範囲の文字色 str_color[ COLOR_CHAR_SELECTION ] = cf.get_option_str( "cl_char_selection", CONF_COLOR_CHAR_SELECTION, 13 ); // ハイライトの文字色 str_color[ COLOR_CHAR_HIGHLIGHT ] = cf.get_option_str( "cl_char_highlight", CONF_COLOR_CHAR_HIGHLIGHT, 13 ); // 通常のリンクの文字色 str_color[ COLOR_CHAR_LINK ] = cf.get_option_str( "cl_char_link", CONF_COLOR_CHAR_LINK, 13 ); // 複数発言したIDの文字色 str_color[ COLOR_CHAR_LINK_ID_LOW ] = cf.get_option_str( "cl_char_link_id_low", CONF_COLOR_CHAR_LINK_ID_LOW, 13 ); // 多く発言したIDの文字色 str_color[ COLOR_CHAR_LINK_ID_HIGH ] = cf.get_option_str( "cl_char_link_id_high", CONF_COLOR_CHAR_LINK_ID_HIGH, 13 ); // 参照されていないレス番号の文字色 str_color[ COLOR_CHAR_LINK_RES ] = cf.get_option_str( "cl_char_link_res", CONF_COLOR_CHAR_LINK_RES, 13 ); // 他のレスから参照されたレス番号の文字色 str_color[ COLOR_CHAR_LINK_LOW ] = cf.get_option_str( "cl_char_link_low", CONF_COLOR_CHAR_LINK_LOW, 13 ); // 参照された数が多いレス番号の文字色 str_color[ COLOR_CHAR_LINK_HIGH ] = cf.get_option_str( "cl_char_link_high", CONF_COLOR_CHAR_LINK_HIGH, 13 ); // メッセージビューの文字色 str_color[ COLOR_CHAR_MESSAGE ] = cf.get_option_str( "cl_char_message", CONF_COLOR_CHAR_MESSAGE, 13 ); // メッセージビュー(選択範囲)の文字色 str_color[ COLOR_CHAR_MESSAGE_SELECTION ] = cf.get_option_str( "cl_char_message_selection", CONF_COLOR_CHAR_MESSAGE_SELECTION, 13 ); // Gtk::Entryのデフォルトの文字色 str_color[ COLOR_CHAR_ENTRY_DEFAULT ] = MISC::get_entry_color_text(); // 画像(キャッシュ無)の色 str_color[ COLOR_IMG_NOCACHE ] = cf.get_option_str( "cl_img_nocache", CONF_COLOR_IMG_NOCACHE, 13 ); // 画像(キャッシュ有)の色 str_color[ COLOR_IMG_CACHED ] = cf.get_option_str( "cl_img_cached", CONF_COLOR_IMG_CACHED, 13 ); // 画像(ロード中)の色 str_color[ COLOR_IMG_LOADING ] = cf.get_option_str( "cl_img_loading", CONF_COLOR_IMG_LOADING, 13 ); // 画像(エラー)の色 str_color[ COLOR_IMG_ERR ] = cf.get_option_str( "cl_img_err", CONF_COLOR_IMG_ERR, 13 ); // スレ背景色 str_color[ COLOR_BACK ] = cf.get_option_str( "cl_back", CONF_COLOR_BACK, 13 ); // ポップアップの背景色 str_color[ COLOR_BACK_POPUP ] = cf.get_option_str( "cl_back_popup", CONF_COLOR_BACK_POPUP, 13 ); // 選択範囲の背景色 str_color[ COLOR_BACK_SELECTION ] = cf.get_option_str( "cl_back_selection", CONF_COLOR_BACK_SELECTION, 13 ); // ハイライトの背景色 str_color[ COLOR_BACK_HIGHLIGHT ] = cf.get_option_str( "cl_back_highlight", CONF_COLOR_BACK_HIGHLIGHT, 13 ); // ハイライトの背景色(ツリー用) str_color[ COLOR_BACK_HIGHLIGHT_TREE ] = cf.get_option_str( "cl_back_highlight_tree", CONF_COLOR_BACK_HIGHLIGHT_TREE, 13 ); // メッセージビューの背景色 str_color[ COLOR_BACK_MESSAGE ] = cf.get_option_str( "cl_back_message", CONF_COLOR_BACK_MESSAGE, 13 ); // メッセージビューの選択色 str_color[ COLOR_BACK_MESSAGE_SELECTION ] = cf.get_option_str( "cl_back_message_selection", CONF_COLOR_BACK_MESSAGE_SELECTION, 13 ); // Gtk::Entryのデフォルトの背景色 str_color[ COLOR_BACK_ENTRY_DEFAULT ] = MISC::get_entry_color_base(); // 新着セパレータ str_color[ COLOR_SEPARATOR_NEW ] = cf.get_option_str( "cl_sepa_new", CONF_COLOR_SEPARATOR_NEW, 13 ); // ポップアップフレーム色 str_color[ COLOR_FRAME ] = cf.get_option_str( "cl_frame", CONF_COLOR_FRAME, 13 ); // オートスクロールマーカー色 str_color[ COLOR_MARKER ] = cf.get_option_str( "cl_marker", CONF_COLOR_MARKER, 13 ); // 板一覧の文字 str_color[ COLOR_CHAR_BBS ] = cf.get_option_str( "cl_chr_bbs", CONF_COLOR_CHAR_BBS, 13 ); // 板一覧のコメント str_color[ COLOR_CHAR_BBS_COMMENT ] = cf.get_option_str( "cl_chr_bbs_com", CONF_COLOR_CHAR_BBS_COMMENT, 13 ); // スレ一覧の文字 str_color[ COLOR_CHAR_BOARD ] = cf.get_option_str( "cl_chr_board", CONF_COLOR_CHAR_BOARD, 13 ); // 板一覧の背景色 str_color[ COLOR_BACK_BBS ] = cf.get_option_str( "cl_back_bbs", CONF_COLOR_BACK_BBS, 13 ); // 板一覧の背景色(偶数行) str_color[ COLOR_BACK_BBS_EVEN ] = cf.get_option_str( "cl_back_bbs_even", CONF_COLOR_BACK_BBS_EVEN, 13 ); // スレ一覧の背景色 str_color[ COLOR_BACK_BOARD ] = cf.get_option_str( "cl_back_board", CONF_COLOR_BACK_BOARD, 13 ); // スレ一覧の背景色(偶数行) str_color[ COLOR_BACK_BOARD_EVEN ] = cf.get_option_str( "cl_back_board_even", CONF_COLOR_BACK_BOARD_EVEN, 13 ); } // // 色のリセット // void ConfigItems::reset_colors() { // dummyのConfLoaderをset_colors()に渡してデフォルト値をセットする JDLIB::ConfLoader cf( "", "dummy = dummy" ); set_colors( cf ); } // // プロクシ設定 // void ConfigItems::set_proxy_for2ch( const std::string& proxy ) { proxy_for2ch = proxy; proxy_basicauth_for2ch = std::string(); if( proxy.empty() ) return; // basic認証 JDLIB::Regex regex; const size_t offset = 0; const bool icase = false; const bool newline = true; const bool usemigemo = false; const bool wchar = false; if( regex.exec( "([^/]+:[^/]+@)(.+)$" , proxy, offset, icase, newline, usemigemo, wchar ) ) { proxy_basicauth_for2ch = regex.str( 1 ).substr( 0, regex.length( 1 ) - 1 ); proxy_for2ch = regex.str( 2 ); } } void ConfigItems::set_proxy_for2ch_w( const std::string& proxy ) { proxy_for2ch_w = proxy; proxy_basicauth_for2ch_w = std::string(); if( proxy.empty() ) return; // basic認証 JDLIB::Regex regex; const size_t offset = 0; const bool icase = false; const bool newline = true; const bool usemigemo = false; const bool wchar = false; if( regex.exec( "([^/]+:[^/]+@)(.+)$" , proxy, offset, icase, newline, usemigemo, wchar ) ) { proxy_basicauth_for2ch_w = regex.str( 1 ).substr( 0, regex.length( 1 ) - 1 ); proxy_for2ch_w = regex.str( 2 ); } } void ConfigItems::set_proxy_for_data( const std::string& proxy ) { proxy_for_data = proxy; proxy_basicauth_for_data = std::string(); if( proxy.empty() ) return; // basic認証 JDLIB::Regex regex; const size_t offset = 0; const bool icase = false; const bool newline = true; const bool usemigemo = false; const bool wchar = false; if( regex.exec( "([^/]+:[^/]+@)(.+)$" , proxy, offset, icase, newline, usemigemo, wchar ) ) { proxy_basicauth_for_data = regex.str( 1 ).substr( 0, regex.length( 1 ) - 1 ); proxy_for_data = regex.str( 2 ); } } jdim-0.7.0/src/config/configitems.h000066400000000000000000000440641417047150700171730ustar00rootroot00000000000000// ライセンス: GPL2 // // 設定項目クラス // #ifndef _CONFIGITEMS_H #define _CONFIGITEMS_H #include #include #include namespace JDLIB { class ConfLoader; } namespace CONFIG { class ConfigItems { // 設定をロード済み bool m_loaded{}; public: // 前回開いたviewを復元するか bool restore_board{}; bool restore_article{}; bool restore_image{}; // 自前でウィンドウ配置を管理する bool manage_winpos{}; // フォント std::vector< std::string > fontname; // レスを参照するときに前に付ける文字 std::string ref_prefix; // ref_prefix の後のスペースの数 int ref_prefix_space{}; std::string ref_prefix_space_str; // レスにアスキーアートがあると判定する正規表現 std::string regex_res_aa; bool aafont_enabled{}; // 読み込み用プロクシとポート番号 bool use_proxy_for2ch{}; bool send_cookie_to_proxy_for2ch{}; std::string proxy_for2ch; int proxy_port_for2ch{}; std::string proxy_basicauth_for2ch; // 書き込み用プロクシとポート番号 bool use_proxy_for2ch_w{}; bool send_cookie_to_proxy_for2ch_w{}; std::string proxy_for2ch_w; int proxy_port_for2ch_w{}; std::string proxy_basicauth_for2ch_w; // 2chの外にアクセスするときのプロクシとポート番号 bool use_proxy_for_data{}; bool send_cookie_to_proxy_for_data{}; std::string proxy_for_data; int proxy_port_for_data{}; std::string proxy_basicauth_for_data; // 2ch にアクセスするときのエージェント名 std::string agent_for2ch; // 2ch外にアクセスするときのエージェント名 std::string agent_for_data; // 2ch にログインするときのX-2ch-UA std::string x_2ch_ua; // ローダのバッファサイズ int loader_bufsize{}; // 一般 int loader_bufsize_board{}; // スレ一覧用 // ローダのタイムアウト値 int loader_timeout{}; int loader_timeout_post{}; int loader_timeout_img{}; int loader_timeout_checkupdate{}; // ipv6使用 bool use_ipv6{}; // 同一ホストに対する最大コネクション数( 1 または 2 ) int connection_num{}; // 2chのクッキーを保存する (互換性のため設定名は旧名称を使う) bool use_cookie_hap{}; // 2chのクッキー (互換性のため設定名は旧名称を使う) std::string cookie_hap; std::string cookie_hap_bbspink; enum { use_offlaw2_2ch }; // Removed in v0.3.0 (2020-04) // リンクをクリックしたときに実行するコマンド std::string command_openurl; // ブラウザ設定ダイアログのコンボボックスの番号 int browsercombo_id{}; // レス番号の上にマウスオーバーしたときに参照ポップアップ表示する bool refpopup_by_mo{}; // 名前の上にマウスオーバーしたときにポップアップ表示する bool namepopup_by_mo{}; // IDの上にマウスオーバーしたときにIDをポップアップ表示する bool idpopup_by_mo{}; // 画像のスムージングレベル(0-2, 2が最も高画質かつ低速) int imgemb_interp{}; int imgmain_interp{}; int imgpopup_interp{}; // 画像ポップアップサイズ int imgpopup_width{}; int imgpopup_height{}; // 画像ポップアップを使用する bool use_image_popup{}; // 画像ビューを使用する bool use_image_view{}; // インライン画像を表示する bool use_inline_image{}; // ssspアイコン表示 bool show_ssspicon{}; // インライン画像の最大幅と高さ int embimg_width{}; int embimg_height{}; // 埋め込み画像ビューを閉じたときにタブも閉じる bool hide_imagetab{}; // 画像ビューでdeleteを押したときに確認ダイアログを表示する bool show_delimgdiag{}; // 画像にモザイクかける bool use_mosaic{}; // モザイクの大きさ // 画像を一度mosaic_sizeまで縮めてから表示する // 画像のサイズがmosaic_sizeより小さい場合はモザイクをかけない int mosaic_size{}; // 画像をデフォルトでウィンドウサイズに合わせる bool zoom_to_fit{}; // 画像キャッシュ削除の日数 int del_img_day{}; // 画像あぼーん削除の日数 int del_imgabone_day{}; // ダウンロードする画像の最大ファイルサイズ(Mbyte) int max_img_size{}; // 画像の最大サイズ(Mピクセル) int max_img_pixel{}; // 画像のメモリキャッシュ枚数 int imgcache_size{}; // JD ホームページのアドレス std::string url_jdhp; // JDim ホームページのアドレス std::string url_jdimhp; // 2chの認証サーバのアドレス std::string url_login2ch; enum { url_loginp2 }; // Removed in v0.3.0 (2020-05) // BEの認証サーバのアドレス std::string url_loginbe; // bbsmenu.htmlのURL std::string url_bbsmenu; // bbsmenu.htmlの内にあるリンクは全て板とみなす bool use_link_as_board{}; // 板移転時に確認ダイアログを表示する bool show_movediag{}; // スレタイ検索用メニュータイトルアドレス std::string menu_search_title; std::string url_search_title; // スレタイ検索用正規表現 std::string regex_search_title; // web検索用メニュータイトルアドレス std::string menu_search_web; std::string url_search_web; enum { url_writep2 }; // Removed in v0.3.0 (2020-05) enum { url_resp2 }; // Removed in v0.3.0 (2020-05) // 色 std::vector< std::string > str_color; // 書き込みビューでGTKテーマの設定を使用するか (GTK3版のみ) bool use_message_gtktheme{}; // ツリービューでgtkrcの設定を使用するか bool use_tree_gtkrc{}; // スレビューの選択色でgtkrcの設定を使用するか bool use_select_gtkrc{}; // ツリービューの行間スペース int tree_ypad{}; // ツリービューにエクスパンダを表示 bool tree_show_expanders{}; // ツリービューのレベルインデント調整量(ピクセル) int tree_level_indent{}; // カテゴリやディレクトリを開いたときにツリービューをスクロールする bool scroll_tree{}; // ツリービューの選択を表示中のビューと同期する ( 0: 同期しない 1: 同期する 2: 同期する(フォルダを開く) ) int select_item_sync{}; // 各ビューと枠との間の余白 int view_margin{}; // スクロールバーを左に配置 bool left_scrbar{}; // スレ一覧で古いスレも表示 bool show_oldarticle{}; // スレ一覧で指定した値(時間)よりも後に立てられたスレを新着とみなす int newthread_hour{}; // スレ一覧でインクリメント検索をする bool inc_search_board{}; // スレ一覧でdeleteを押したときに確認ダイアログを表示する bool show_deldiag{}; // スレ一覧をロードする前にキャッシュにある一覧を表示 bool show_cached_board{}; // スレ一覧でお知らせスレ(924)のアイコンを表示する bool show_924{}; // ツリービューのスクロール量(マウスホイール上下・行数) int tree_scroll_size{}; // スレビューのスクロール量(マウスホイール上下・行数) int scroll_size{}; // スレビューのスクロール量(キー上下・行数) int key_scroll_size{}; // スレビューの高速スクロール量(キー上下・ ページ高 - 行高 * key_fastscroll_size ) int key_fastscroll_size{}; // スレビューでリロード後に一番下までスクロール bool jump_after_reload{}; // スレビューでリロード後に新着までスクロール bool jump_new_after_reload{}; // 実況モード int live_mode{}; // 実況速度 int live_speed{}; // 実況のスクロールモードを切り替えるしきい値 int live_threshold{}; // 板一覧でカテゴリを常にひとつだけ開く bool open_one_category{}; // お気に入りでディレクトリを常にひとつだけ開く bool open_one_favorite{}; // デフォルトの書き込み名 std::string write_name; // デフォルトのメールアドレス std::string write_mail; // 書き込み時に書き込み確認ダイアログを出さない bool always_write_ok{}; // 書き込みログを保存 bool save_postlog{}; // 書き込みログの最大サイズ int maxsize_postlog{}; // 書き込み履歴を保存 bool save_posthist{}; // 「書き込み中」のダイアログを表示しない bool hide_writing_dialog{}; // 編集中のメッセージの保存確認ダイアログを表示する bool show_savemsgdiag{}; // 書き込みビューでテキストを折り返す bool message_wrap{}; // 非アクティブ時に書き込みビューを折りたたむ bool fold_message{}; // 非アクティブ時に画像ビューを折りたたむ bool fold_image{}; // 書き込み欄の日本語のON/OFF状態を保存 bool keep_im_status{}; // ポップアップとカーソルの間のマージン int margin_popup{}; // 画像ポップアップとカーソルの間のマージン int margin_imgpopup_x{}; // 水平方向 int margin_imgpopup{}; // 垂直方向 // ポップアップが消えるまでの時間(ミリ秒) int hide_popup_msec{}; // マウスジェスチャを有効 bool enable_mg{}; // マウスジェスチャの判定開始半径 int mouse_radius{}; // 数字入力ジャンプの待ち時間(ミリ秒) int numberjmp_msec{}; // 履歴メニューの表示数 int history_size{}; // 履歴ビューの表示数 int historyview_size{}; // AA履歴の保持数 int aahistory_size{}; // 0以上なら多重ポップアップの説明を表示する int instruct_popup{}; // スレビューを開いたときにスレ一覧との切り替え方法を説明する bool instruct_tglart{}; bool instruct_tglart_end{}; // 画像ビューを開いたときにスレビューとの切り替え方法を説明する bool instruct_tglimg{}; bool instruct_tglimg_end{}; // スレビューでdeleteを押したときに確認ダイアログを表示する bool show_delartdiag{}; // 下線位置 double adjust_underline_pos{}; // 行間スペース double adjust_line_space{}; // リンク下線を表示 bool draw_underline{}; // スレビューで文字幅の近似を厳密にする bool strict_char_width{}; // スレビューで発言数(ID)をカウントする bool check_id{}; // レス参照で色を変える回数 int num_reference_high{}; int num_reference_low{}; // 発言数で色を変える回数 int num_id_high{}; int num_id_low{}; // datのパース時にURL判定を甘くする(^なども含める) bool loose_url{}; // ユーザーコマンドで選択できない項目を非表示にする bool hide_usrcmd{}; // スレビューで再読み込みボタンを押したときに全タブを更新する bool reload_allthreads{}; // タブに表示する文字列の最小値 int tab_min_str{}; // タブにアイコンを表示するか bool show_tab_icon{}; // タブ上でマウスホイールを回転してタブを切り替える bool switchtab_wheel{}; // 他のビューを開くときのタブの位置 ( 0: 一番右端 1:右隣 2:左隣 ) int newtab_pos{}; // ツリービューで選択したビューを開くときのタブの位置 ( 0: 一番右端 1:右隣 2:左隣 ) int opentab_pos{}; // 次スレ検索を開くときのタブの位置 ( 0: 次スレ検索タブ 1:新しいタブ 2:アクティブなタブを置き換え ) int boardnexttab_pos{}; // スレビューに書き込みマークを表示するか bool show_post_mark{}; // ボタンをフラットにするか bool flat_button{}; // ツールバーの背景描画 bool draw_toolbarback{}; // スレ あぼーん word std::list< std::string > list_abone_word_thread; // スレ あぼーん regex std::list< std::string > list_abone_regex_thread; // dat落ちしたスレをNGスレタイトルリストから取り除くか( 0: ダイアログ表示 1: 取り除く 2: 除かない ) int remove_old_abone_thread{}; // スレ あぼーん レス数 int abone_low_number_thread{}; int abone_high_number_thread{}; // スレ あぼーん スレ立てからの経過時間 int abone_hour_thread{}; // あぼーん name std::list< std::string > list_abone_name; // あぼーん word std::list< std::string > list_abone_word; // あぼーん regex std::list< std::string > list_abone_regex; // デフォルトで透明、連鎖あぼーんをするか bool abone_transparent{}; bool abone_chain{}; // NG正規表現によるあぼーん時に大小と全半角文字の違いを無視 bool abone_icase{}; bool abone_wchar{}; // 右ペーンが空の時にサイドバーを閉じるか bool expand_sidebar{}; // 3ペーン時にスレ一覧やスレビューを最大化するか bool expand_rpane{}; // ペーンの境界をクリックしてサイドバーを開け閉めする bool open_sidebar_by_click{}; // 次スレ検索の類似度のしきい値 int threshold_next{}; // 次スレを開いたときにお気に入りのアドレスと名前を自動更新 int replace_favorite_next{}; // お気に入りの自動更新をするかダイアログを出す bool show_diag_replace_favorite{}; // スレをお気に入りに追加したときにしおりをセットする bool bookmark_drop{}; // お気に入りの更新チェック時に板の更新もチェックする bool check_update_board{}; // 起動時にお気に入りを自動でチェックする bool check_update_boot{}; // お気に入り登録時に重複項目を登録するか ( 0: 登録する 1: ダイアログ表示 2: 登録しない ) int check_favorite_dup{}; // お気に入り登録時に挿入先ダイアログを表示する ( 0 : 表示する 1: 表示せず先頭に追加 2: 表示せず最後に追加 ) int show_favorite_select_diag{}; // Ctrl+qでウィンドウを閉じない bool disable_close{}; // メニューバーを非表示にした時にダイアログを表示 bool show_hide_menubar_diag{}; // 状態変更時にメインステータスバーの色を変える bool change_stastatus_color{}; // Client-Side Decorationを使うか( 0: 使わない 1: 使う 2: デスクトップに合わせる ) int use_header_bar{}; // まちBBSの取得に offlaw.cgi を使用する bool use_machi_offlaw{}; // 書き込み履歴のあるスレを削除する時にダイアログを表示 bool show_del_written_thread_diag{}; // スレを削除する時に画像キャッシュも削除する ( 0: ダイアログ表示 1: 削除 2: 削除しない ) int delete_img_in_thread{}; //最大表示可能レス数 int max_resnumber{}; // FIFOの作成などにエラーがあったらダイアログを表示する bool show_diag_fifo_error{}; // 指定した分ごとにセッションを自動保存 (0: 保存しない) int save_session{}; #ifdef HAVE_MIGEMO_H // migemo-dictの場所 std::string migemodict_path; #endif ///////////////////////// ConfigItems(); virtual ~ConfigItems() noexcept; ConfigItems& operator=( const ConfigItems& ) = default; // 設定読み込み bool load( const bool restore = false ); // 保存 void save(); // フォントのリセット void reset_fonts(); // 色のリセット void reset_colors(); // プロクシ設定 void set_proxy_for2ch( const std::string& proxy ); void set_proxy_for2ch_w( const std::string& proxy ); void set_proxy_for_data( const std::string& proxy ); private: void save_impl( const std::string& path ); void set_fonts( JDLIB::ConfLoader& cf ); void set_colors( JDLIB::ConfLoader& cf ); }; } #endif jdim-0.7.0/src/config/defaultconf.h000066400000000000000000000477651417047150700171710ustar00rootroot00000000000000// ライセンス: GPL2 // // 設定のデフォルト値 // #ifndef _DEFAULTCONF_H #define _DEFAULTCONF_H #ifdef HAVE_CONFIG_H #include "config.h" #endif namespace CONFIG { enum{ CONF_RESTORE_BOARD = 0, // スレ一覧を復元 CONF_RESTORE_ARTICLE = 0, // スレを復元 CONF_RESTORE_IMAGE = 0, // 画像を復元 CONF_MANAGE_WINPOS = 1, // 自前でウィンドウ配置を管理する CONF_REF_PREFIX_SPACE = 1, // 参照文字( CONF_REF_PREFIX ) の後のスペースの数 CONF_USE_PROXY_FOR2CH = 0, // 2ch 読み込み用プロクシを使用するか CONF_SEND_COOKIE_TO_PROXY_FOR2CH = 0, // 2ch 読み込み用プロクシにクッキーを送信するか CONF_PROXY_PORT_FOR2CH = 8080, // 2ch 読み込み用プロクシポート番号 CONF_USE_PROXY_FOR2CH_W = 0, // 2ch 書き込み用プロクシを使用するか CONF_SEND_COOKIE_TO_PROXY_FOR2CH_W = 0, // 2ch 書き込み用プロクシにクッキーを送信するか CONF_PROXY_PORT_FOR2CH_W = 8080, // 2ch 書き込み用プロクシポート番号 CONF_USE_PROXY_FOR_DATA = 0, // 2ch 以外にアクセスするときにプロクシを使用するか CONF_SEND_COOKIE_TO_PROXY_FOR_DATA = 0, // 2ch 以外にアクセスするときのプロクシにクッキーを送信するか CONF_PROXY_PORT_FOR_DATA = 8080, // 2ch 以外にアクセスするときのプロクシポート番号 CONF_LOADER_BUFSIZE = 32, // ローダのバッファサイズ (一般) CONF_LOADER_BUFSIZE_BOARD = 2, // ローダのバッファサイズ (スレ一覧用) CONF_LOADER_TIMEOUT = 10, // ローダのタイムアウト値 CONF_LOADER_TIMEOUT_POST = 30, // ポストローダのタイムアウト値 CONF_LOADER_TIMEOUT_IMG = 30, // 画像ローダのタイムアウト値 CONF_LOADER_TIMEOUT_CHECKUPDATE = 10, // 更新チェックのタイムアウト値 CONF_USE_IPV6 = 1, // ipv6使用 CONF_CONNECTION_NUM = 2, // 同一ホストに対する最大コネクション数( 1 または 2 ) CONF_USE_COOKIE_HAP = 0, // 2chのクッキーを保存する (互換性のため設定名は旧名称を使う) CONF_REFPOPUP_BY_MO = 0, // レス番号の上にマウスオーバーしたときに参照ポップアップ表示する CONF_NAMEPOPUP_BY_MO = 0, // 名前の上にマウスオーバーしたときにポップアップ表示する CONF_IDPOPUP_BY_MO = 0, // IDの上にマウスオーバーしたときにIDをポップアップ表示する CONF_USE_MESSAGE_GTKTHEME = 0, // 書き込みビューでGTKテーマの設定を使用するか (GTK3版のみ) CONF_USE_TREE_GTKRC = 0, // ツリービューでgtkrcの設定を使用するか CONF_USE_SELECT_GTKRC = 0, // スレビューの選択色でgtkrcの設定を使用するか CONF_TREE_YPAD = 1, // ツリービューの行間スペース CONF_TREE_SHOW_EXPANDERS = 1, // ツリービューにエクスパンダを表示 CONF_TREE_LEVEL_INDENT = 0, // ツリービューのレベルインデント調整量(ピクセル) CONF_SCROLL_TREE = 1, // カテゴリやディレクトリを開いたときにツリービューをスクロールする CONF_SELECT_ITEM_SYNC = 1, // ツリービューの選択を表示中のビューと同期する ( 0: 同期しない 1: 同期する 2: 同期する(フォルダを開く) ) CONF_VIEW_MARGIN = 0, // 各ビューと枠との間の余白 CONF_LEFT_SCRBAR = 0, // スクロールバーを左に配置 CONF_SHOW_OLDARTICLE = 0, // スレ一覧で古いスレも表示 CONF_NEWTHREAD_HOUR = 24, // スレ一覧で指定した値(時間)よりも後に立てられたスレを新着とみなす CONF_INC_SEARCH_BOARD = 0, // スレ一覧でインクリメント検索をする CONF_SHOW_DELDIAG = 1 , // スレ一覧でdeleteを押したときに確認ダイアログを表示する CONF_SHOW_CACHED_BOARD = 1, // スレ一覧をロードする前にキャッシュにある一覧を表示 CONF_SHOW_924 = 1, // スレ一覧でお知らせスレ(924)のアイコンを表示する CONF_TREE_SCROLL_SIZE = 4, // ツリービューのスクロール量(行数) CONF_SCROLL_SIZE = 3, // スレビューのスクロール量 CONF_KEY_SCROLL_SIZE = 2, // スレビューのスクロール量(キー上下) CONF_KEY_FASTSCROLL_SIZE = 2, // スレビューの高速スクロール量(キー上下・ ページ高 - 行高 * key_fastscroll_size ) CONF_JUMP_AFTER_RELOAD = 0, // スレビューでリロード後に一番下までスクロール CONF_JUMP_NEW_AFTER_RELOAD = 0, // スレビューでリロード後に新着までスクロール CONF_LIVE_SPEED = 2, // 実況速度 CONF_LIVE_THRESHOLD = 10, // 実況のスクロールモードを切り替えるしきい値 CONF_OPEN_ONE_CATEGORY = 0, // 板一覧でカテゴリを常にひとつだけ開く CONF_OPEN_ONE_FAVORITE = 0, // お気に入りでディレクトリを常にひとつだけ開く CONF_ALWAYS_WRITE_OK = 0, // 書き込み時に書き込み確認ダイアログを出さない CONF_SAVE_POSTLOG = 0, //書き込みログを保存 CONF_SAVE_POSTHIST = 1, //書き込み履歴を保存 CONF_MAXSIZE_POSTLOG = ( 256 * 1024 ), // 書き込みログの最大サイズ CONF_HIDE_WRITING_DIALOG = 0, // 「書き込み中」のダイアログを表示しない CONF_SHOW_SAVEMSGDIAG = 1, // 編集中のメッセージの保存確認ダイアログを表示する CONF_MESSAGE_WRAP = 1, // 書き込みビューでテキストを折り返す CONF_FOLD_MESSAGE = 0, // 非アクティブ時に書き込みビューを折りたたむ CONF_FOLD_IMAGE = 1, // 非アクティブ時に画像ビューを折りたたむ CONF_KEEP_IM_STATUS = 0, // 書き込み欄の日本語のON/OFF状態を保存 CONF_MARGIN_POPUP = 30, // レスアンカーとポップアップの間のマージン ( 垂直方向 ) CONF_MARGIN_IMGPOPUP_X = 0, // レスアンカーと画像ポップアップの間のマージン ( 水平方向 ) CONF_MARGIN_IMGPOPUP = CONF_MARGIN_POPUP, // レスアンカーと画像ポップアップの間のマージン ( 垂直方向 ) CONF_HIDE_POPUP_MSEC = 0, // ポップアップが消えるまでの時間(ミリ秒) CONF_ENABLE_MG = 1, // マウスジェスチャを有効 CONF_MOUSE_RADIUS = 25, // マウスジェスチャの判定開始半径 CONF_NUMBERJMP_MSEC = 1000, // 数字入力ジャンプの待ち時間(ミリ秒) CONF_HISTORY_SIZE = 20, // 履歴メニューの表示数 CONF_HISTORYVIEW_SIZE = 100, // 履歴ビューの表示数 CONF_AAHISTORY = 7, // AA履歴の保持数 CONF_INSTRUCT_POPUP = 100, // 0以上なら多重ポップアップの説明を表示する CONF_INSTRUCT_TGLART = 1, // スレビューを開いたときにスレ一覧との切り替え方法を説明する CONF_INSTRUCT_TGLIMG = 1, // 画像ビューを開いたときにスレビューとの切り替え方法を説明する CONF_SHOW_DELARTDIAG = 1, // スレビューでdeleteを押したときに確認ダイアログを表示する CONF_ADJUST_UNDERLINE_POS = 1, // 下線位置 CONF_ADJUST_LINE_SPACE = 1, // 行間スペース CONF_DRAW_UNDERLINE = 1, // リンク下線を表示 CONF_STRICT_CHAR_WIDTH = 0, // スレビューで文字幅の近似を厳密にする CONF_CHECK_ID = 1, // スレビューで発言数(ID)をカウントする CONF_NUM_REFERENCE_HIGH = 3,//レス参照で色を変える回数 (高) CONF_NUM_REFERENCE_LOW = 1, //レス参照で色を変える回数 (低) CONF_NUM_ID_HIGH = 4, // 発言数で色を変える回数 (高) CONF_NUM_ID_LOW = 2, // 発言数で色を変える回数 (低) CONF_LOOSE_URL = 1, // datのパース時にURL判定を甘くする(^なども含める) CONF_HIDE_USRCMD = 0, // ユーザーコマンドで選択できない項目を非表示にする CONF_RELOAD_ALLTHREAD = 0, // スレビューで再読み込みボタンを押したときに全タブを更新する CONF_TAB_MIN_STR = 4, // タブに表示する文字列の最小値 CONF_SHOW_TAB_ICON = 1, // タブにアイコンを表示するか CONF_SWITCHTAB_WHEEL = 1, // タブ上でマウスホイールを回転してタブを切り替える CONF_NEWTAB_POS = 1, // 他のビューを開くときのタブの位置 ( 0: 一番右端 1:右隣 2:左隣 ) CONF_OPENTAB_POS = 0, // ツリービューで選択したビューを開くときのタブの位置 ( 0: 一番右端 1:右隣 2:左隣 ) CONF_BOARDNEXTTAB_POS = 0, // 次スレ検索を開くときのタブの位置 ( 0: 次スレ検索タブ 1:新しいタブ 2:アクティブなタブを置き換え ) CONF_SHOW_POST_MARK = 1, // スレビューに書き込みマークを表示するか CONF_FLAT_BUTTON = 1, // ボタンをフラットにするか CONF_DRAW_TOOLBARBACK = 0, // ツールバーの背景描画 CONF_IMGEMB_INTERP = 0, // 埋め込み画像のスムージングレベル(0-2, 2が最も高画質かつ低速) CONF_IMGMAIN_INTERP = 0, // 画像ビューのスムージングレベル(0-2, 2が最も高画質かつ低速) CONF_IMGPOPUP_INTERP = 0, // 画像ポップアップのスムージングレベル(0-2, 2が最も高画質かつ低速) CONF_IMGPOPUP_WIDTH = 320, // 画像ポップアップ幅 CONF_IMGPOPUP_HEIGHT = 240, // 画像ポップアップ高さ CONF_USE_IMAGE_POPUP = 1, // 画像ポップアップを使用する CONF_USE_IMAGE_VIEW = 1, // 画像ビューを使用する CONF_INLINE_IMG = 0, // インライン画像を表示する CONF_SHOW_SSSPICON = 1, // sssp アイコン表示 CONF_EMBIMG_WIDTH = 100, // インライン画像の最大幅 CONF_EMBIMG_HEIGHT = 100, // インライン画像の最大高さ CONF_HIDE_IMAGETAB = 1, // 埋め込み画像ビューを閉じたときにタブも閉じる CONF_SHOW_DELIMGDIAG = 1 , // 画像ビューでdeleteを押したときに確認ダイアログを表示する CONF_USE_MOSAIC = 1, // 画像にモザイクをかける CONF_MOSAIC_SIZE = 60, // モザイクの大きさ CONF_ZOOM_TO_FIT = 1, // 画像をデフォルトでウィンドウサイズに合わせる CONF_DEL_IMG_DAY = 20, // 画像キャッシュ削除の日数 CONF_DEL_IMGABONE_DAY = 20, // 画像あぼーん削除の日数 CONF_MAX_IMG_SIZE = 16, // ダウンロードする画像の最大サイズ(Mbyte) CONF_MAX_IMG_PIXEL = 20, // 画像の最大サイズ(Mピクセル) CONF_IMGCACHE_SIZE = 3, // 画像のメモリキャッシュ枚数 CONF_USE_LINK_AS_BOARD = 0, // bbsmenu.html内にあるリンクは全て板とみなす CONF_SHOW_MOVEDIAG = 1, // 板移転時に確認ダイアログを表示する CONF_REMOVE_OLD_ABONE_THREAD = 0, // dat落ちしたスレをNGスレタイトルリストから取り除くか( 0: ダイアログ表示 1: 取り除く 2: 除かない ) CONF_ABONE_LOW_NUMBER_THREAD = 0, // nレス以下のスレをあぼーんする CONF_ABONE_HIGH_NUMBER_THREAD = 0, // nレス以上のスレをあぼーんする CONF_ABONE_HOUR_THREAD = 0, // スレあぼーん( スレ立てからの経過時間 ) CONF_ABONE_TRANSPARENT = 0, // デフォルトで透明あぼーんをする CONF_ABONE_CHAIN = 0, // デフォルトで連鎖あぼーんをする CONF_ABONE_ICASE = 0, // NG正規表現によるあぼーん時に大小文字の違いを無視 CONF_ABONE_WCHAR = 0, // NG正規表現によるあぼーん時に全角半角文字の違いを無視 CONF_EXPAND_SIDEBAR = 0, // 右ペーンが空の時にサイドバーを閉じる CONF_EXPAND_RPANE = 1, // 3ペーン時にスレ一覧やスレビューを最大化する CONF_OPEN_SIDEBAR_BY_CLICK = 1, // ペーンの境界をクリックしてサイドバーを開け閉めする CONF_THRESHOLD_NEXT = 5, // 次スレ検索の類似度のしきい値 CONF_REPLACE_FAVORITE_NEXT = 1, // 次スレを開いたときにお気に入りのアドレスと名前を自動更新 CONF_SHOW_DIAG_REPLACE_FAVORITE = 1, // お気に入りの自動更新をするかダイアログを出す CONF_BOOKMARK_DROP = 0, // スレをお気に入りに追加したときにしおりをセットする CONF_CHECK_UPDATE_BOARD = 0, // お気に入りの更新チェック時に板の更新もチェックする CONF_CHECK_UPDATE_BOOT = 0, // 起動時にお気に入りを自動でチェックする CONF_CHECK_FAVORITE_DUP = 1, // お気に入り登録時に重複項目を登録するか ( 0: 登録する 1: ダイアログ表示 2: 登録しない ) CONF_SHOW_FAVORITE_SELECT_DIAG = 0, // お気に入り登録時に挿入先ダイアログを表示する ( 0 : 表示する 1: 表示せず先頭に追加 2: 表示せず最後に追加 ) CONF_DISABLE_CLOSE = 0, // Ctrl+qでウィンドウを閉じない CONF_SHOW_HIDE_MENUBAR_DIAG = 1, // メニューバーを非表示にした時にダイアログを表示 CONF_CHANGE_STASTATUS_COLOR = 1, // 状態変更時にメインステータスバーの色を変える CONF_USE_HEADER_BAR = 2, // Client-Side Decorationを使うか( 0: 使わない 1: 使う 2: デスクトップに合わせる ) CONF_USE_MACHI_OFFLAW = 0, // まちBBSの取得に offlaw.cgi を使用する CONF_SHOW_DEL_WRITTEN_THREAD_DIAG = 1, // 書き込み履歴のあるスレを削除する時にダイアログを表示 CONF_DELETE_IMG_IN_THREAD = 0, // スレを削除する時に画像キャッシュも削除する ( 0: ダイアログ表示 1: 削除 2: 削除しない ) CONF_MAX_RESNUMBER = 65536, //最大表示可能レス数 CONF_SHOW_DIAG_FIFO_ERROR = 1, // FIFOの作成などにエラーがあったらダイアログを表示する CONF_SAVE_SESSION = 0, // 指定した分ごとにセッションを自動保存 (0: 保存しない) }; // browsers.cpp のデフォルトのラベル番号 // browsers.cpp のブラウザの順番に気をつけること enum{ // xdg-open をデフォルトにする CONF_BROWSER_NO = 1 }; #define CONF_FONTSIZE_THREAD "12" #define CONF_FONTSIZE_MAIL "12" #define CONF_FONTSIZE_POPUP "10" #define CONF_FONTSIZE_TREE "10" // レスを参照するときに前に付ける文字 #define CONF_REF_PREFIX ">" // レスにアスキーアートがあると判定する正規表現 #define CONF_REGEX_RES_AA_DEFAULT "  " #define CONF_REGEX_RES_AA "\"" CONF_REGEX_RES_AA_DEFAULT "\"" // 2ch にアクセスするときのエージェント名 #define CONF_AGENT_FOR2CH "Monazilla/1.00 JD" // 2ch外にアクセスするときのエージェント名 #define CONF_AGENT_FOR_DATA "Monazilla/1.00 JD" // 2ch にログインするときのX-2ch-UA #define CONF_X_2CH_UA "Navigator for 2ch 1.7.5" // JD ホームページのアドレス #define CONF_URL_JDHP "https://jd4linux.osdn.jp/" // JDim ホームページのアドレス #define CONF_URL_JDIMHP "https://github.com/JDimproved/JDim" // 2chの認証サーバのアドレス #define CONF_LOGIN2CH "https://2chv.tora3.net/futen.cgi" // BEの認証サーバのアドレス #define CONF_LOGINBE "http://be.2ch.net/test/login.php" // bbsmenu.htmlのURL #define CONF_URL_BBSMENU "https://menu.5ch.net/bbsmenu.html" // スレタイ検索用メニュータイトルアドレス #define CONF_MENU_SEARCH_TITLE "スレタイ検索 (ff5ch.syoboi.jp)" #define CONF_URL_SEARCH_TITLE "https://ff5ch.syoboi.jp/?alt=tsv&q=$TEXTU" // スレタイ検索用正規表現 #define CONF_REGEX_SEARCH_TITLE R"=(^(?=[^\t\n]+\t[^\t\n]+\t([^\t\n]+))([^\t\n]+)\t([^\t\n]+))=" // WEB検索用メニュータイトルアドレス #define CONF_MENU_SEARCH_WEB "WEB検索 (google)" #define CONF_URL_SEARCH_WEB "http://www.google.co.jp/search?hl=ja&q=$TEXTU&btnG=Google+%E6%A4%9C%E7%B4%A2&lr=" // 2chのクッキー (互換性のため設定名は旧名称を使う) #define CONF_COOKIE_HAP "" #define CONF_COOKIE_HAP_BBSPINK "" // 色 #define CONF_COLOR_CHAR "#000000000000" // スレの文字 #define CONF_COLOR_CHAR_NAME "#000064640000" //名前欄の文字色 #define CONF_COLOR_CHAR_NAME_B "#000000008b8b" // トリップ等の名前欄の文字色 #define CONF_COLOR_CHAR_NAME_NOMAIL "#000064640000" //メール無し時の名前欄の文字色 #define CONF_COLOR_CHAR_AGE "#fde800000000" // ageの時のメール欄の文字色 #define CONF_COLOR_CHAR_SELECTION "#ffffffffffff" // 選択範囲の文字色 #define CONF_COLOR_CHAR_HIGHLIGHT CONF_COLOR_CHAR // ハイライトの文字色 #define CONF_COLOR_CHAR_LINK "#00000000ffff" //通常のリンクの文字色 #define CONF_COLOR_CHAR_LINK_ID_LOW CONF_COLOR_CHAR_LINK // 複数発言したIDの文字色 #define CONF_COLOR_CHAR_LINK_ID_HIGH CONF_COLOR_CHAR_AGE // 多く発言したIDの文字色 #define CONF_COLOR_CHAR_LINK_RES CONF_COLOR_CHAR_LINK // 参照されていないレス番号の文字色 #define CONF_COLOR_CHAR_LINK_LOW "#ffff0000ffff" // 他のレスから参照されたレス番号の文字色 #define CONF_COLOR_CHAR_LINK_HIGH CONF_COLOR_CHAR_AGE // 参照された数が多いレス番号の文字色 #define CONF_COLOR_CHAR_MESSAGE CONF_COLOR_CHAR // メッセージビューの文字色 #define CONF_COLOR_CHAR_MESSAGE_SELECTION CONF_COLOR_CHAR_SELECTION // メッセージビュー(選択範囲)の文字色 #define CONF_COLOR_IMG_NOCACHE "#a5a52a2a2a2a" // 画像(キャッシュ無)の色 #define CONF_COLOR_IMG_CACHED "#00008b8b8b8b" // 画像(キャッシュ有)の色 #define CONF_COLOR_IMG_LOADING "#ffff8c8c0000" // 画像(ロード中)の色 #define CONF_COLOR_IMG_ERR CONF_COLOR_CHAR_AGE // 画像(エラー)の色 #define CONF_COLOR_BACK "#fde8fde8f618" // スレ背景色 #define CONF_COLOR_BACK_POPUP CONF_COLOR_BACK // ポップアップ背景色 #define CONF_COLOR_BACK_SELECTION CONF_COLOR_CHAR_LINK // 選択範囲の背景色 #define CONF_COLOR_BACK_HIGHLIGHT "#ffffffff0000" // ハイライト色 #define CONF_COLOR_BACK_HIGHLIGHT_TREE CONF_COLOR_BACK_HIGHLIGHT // ツリーのハイライト色 #define CONF_COLOR_BACK_MESSAGE CONF_COLOR_BACK // メッセージビューの背景色 #define CONF_COLOR_BACK_MESSAGE_SELECTION CONF_COLOR_BACK_SELECTION // メッセージビューの選択色 #define CONF_COLOR_SEPARATOR_NEW "#7d007d007d00" // セパレータ #define CONF_COLOR_FRAME CONF_COLOR_CHAR // ポップアップフレーム色 #define CONF_COLOR_MARKER CONF_COLOR_CHAR // オートスクロールマーカー色 #define CONF_COLOR_CHAR_BBS CONF_COLOR_CHAR // 板一覧の文字 #define CONF_COLOR_CHAR_BBS_COMMENT CONF_COLOR_CHAR // 板一覧のコメント #define CONF_COLOR_CHAR_BOARD CONF_COLOR_CHAR // スレ一覧の文字 #define CONF_COLOR_BACK_BBS CONF_COLOR_BACK // 板一覧の背景色 #define CONF_COLOR_BACK_BBS_EVEN CONF_COLOR_BACK_BBS // 板一覧の背景色(偶数行) #define CONF_COLOR_BACK_BOARD CONF_COLOR_BACK // スレ一覧の背景色 #define CONF_COLOR_BACK_BOARD_EVEN CONF_COLOR_BACK_BOARD // スレ一覧の背景色(偶数行) #define CONF_WRITE_NAME "" // デフォルトの書き込み名 #define CONF_WRITE_MAIL "sage" // デフォルトのメールアドレス // migemo-dictの場所 #ifdef MIGEMODICT #define CONF_MIGEMO_PATH MIGEMODICT #else #define CONF_MIGEMO_PATH "/usr/share/migemo/utf-8/migemo-dict" #endif } #endif jdim-0.7.0/src/config/globalconf.cpp000066400000000000000000000736621417047150700173330ustar00rootroot00000000000000// ライセンス: GPL2 #ifdef HAVE_CONFIG_H #include "config.h" #endif //#define _DEBUG #include "jddebug.h" #include "globalconf.h" #include "configitems.h" #include "jdlib/miscutil.h" CONFIG::ConfigItems* instance_confitem = nullptr; CONFIG::ConfigItems* instance_confitem_bkup = nullptr; CONFIG::ConfigItems* CONFIG::get_confitem() { return instance_confitem; } void CONFIG::delete_confitem() { if( instance_confitem ) delete instance_confitem; instance_confitem = nullptr; if( instance_confitem_bkup ) delete instance_confitem_bkup; instance_confitem_bkup = nullptr; } bool CONFIG::load_conf() { if( ! instance_confitem ) instance_confitem = new CONFIG::ConfigItems(); return get_confitem()->load(); } void CONFIG::save_conf() { get_confitem()->save(); } void CONFIG::bkup_conf() { if( ! instance_confitem_bkup ) instance_confitem_bkup = new CONFIG::ConfigItems(); *instance_confitem_bkup = * instance_confitem; } void CONFIG::restore_conf() { if( ! instance_confitem_bkup ) return; *instance_confitem = * instance_confitem_bkup; } ////////////////////////////////////////////////////////////// bool CONFIG::get_restore_board(){ return get_confitem()->restore_board; } void CONFIG::set_restore_board( const bool restore ){ get_confitem()->restore_board = restore; } bool CONFIG::get_restore_article(){ return get_confitem()->restore_article; } void CONFIG::set_restore_article( const bool restore ){ get_confitem()->restore_article = restore; } bool CONFIG::get_restore_image(){ return get_confitem()->restore_image; } void CONFIG::set_restore_image( const bool restore ){ get_confitem()->restore_image = restore; } bool CONFIG::get_manage_winpos(){ return get_confitem()->manage_winpos; } void CONFIG::set_manage_winpos( const bool manage ){ get_confitem()->manage_winpos = manage; } // 色 const std::string& CONFIG::get_color( const int id ) { return get_confitem()->str_color[ id ]; } void CONFIG::set_color( const int id, const std::string& color ) { get_confitem()->str_color[ id ] = color; } void CONFIG::reset_colors(){ get_confitem()->reset_colors(); } bool CONFIG::get_use_message_gtktheme() { return get_confitem()->use_message_gtktheme; } void CONFIG::set_use_message_gtktheme( const bool use ) { get_confitem()->use_message_gtktheme = use; } bool CONFIG::get_use_tree_gtkrc(){ return get_confitem()->use_tree_gtkrc; } void CONFIG::set_use_tree_gtkrc( const bool use ){ get_confitem()->use_tree_gtkrc = use; } bool CONFIG::get_use_select_gtkrc(){ return get_confitem()->use_select_gtkrc; } void CONFIG::set_use_select_gtkrc( const bool use ){ get_confitem()->use_select_gtkrc = use; } // ツリービューの行間スペース int CONFIG::get_tree_ypad(){ return get_confitem()->tree_ypad; } // ツリービューにエクスパンダを表示 bool CONFIG::get_tree_show_expanders(){ return get_confitem()->tree_show_expanders; } // ツリービューのレベルインデント調整量(ピクセル) int CONFIG::get_tree_level_indent(){ return get_confitem()->tree_level_indent; } // カテゴリやディレクトリを開いたときにツリービューをスクロールする bool CONFIG::get_scroll_tree(){ return get_confitem()->scroll_tree; } // ツリービューの選択を表示中のビューと同期する ( 0: 同期しない 1: 同期する 2: 同期する(フォルダを開く) ) int CONFIG::get_select_item_sync(){ return get_confitem()->select_item_sync; } void CONFIG::set_select_item_sync( const int sync ){ get_confitem()->select_item_sync = sync; } int CONFIG::get_view_margin(){ return get_confitem()->view_margin; } // スクロールバーを左に配置 bool CONFIG::get_left_scrbar(){ return get_confitem()->left_scrbar; } // スレ一覧で古いスレも表示 bool CONFIG::get_show_oldarticle(){ return get_confitem()->show_oldarticle; } // フォント const std::string& CONFIG::get_fontname( const int id ) { return get_confitem()->fontname[ id ]; } void CONFIG::set_fontname( const int id, const std::string& fontname ) { get_confitem()->fontname[ id ] = fontname; } void CONFIG::reset_fonts(){ get_confitem()->reset_fonts(); } bool CONFIG::get_aafont_enabled(){ return get_confitem()->aafont_enabled; } std::string CONFIG::get_ref_prefix(){ return get_confitem()->ref_prefix + get_confitem()->ref_prefix_space_str; } int CONFIG::ref_prefix_space(){ return get_confitem()->ref_prefix_space; } // レスにアスキーアートがあると判定する正規表現 std::string CONFIG::get_regex_res_aa(){ std::string str = get_confitem()->regex_res_aa; int size = str.size(); // ダブルクオートの削除 if( size >= 2 && str[ 0 ] == '"' && str[ size - 1 ] == '"' ){ str = str.substr( 1, size - 2 ); } return str; } void CONFIG::set_regex_res_aa( const std::string& regex ){ get_confitem()->regex_res_aa = "\"" + regex + "\""; } const std::string& CONFIG::get_url_jdhp() { return get_confitem()->url_jdhp; } const std::string& CONFIG::get_url_jdimhp() { return get_confitem()->url_jdimhp; } // 2chの認証サーバのアドレス const std::string& CONFIG::get_url_login2ch() { return get_confitem()->url_login2ch; } // BEの認証サーバのアドレス const std::string& CONFIG::get_url_loginbe() { return get_confitem()->url_loginbe; } const std::string& CONFIG::get_url_bbsmenu() { return get_confitem()->url_bbsmenu; } void CONFIG::set_url_bbsmenu( std::string url ) { get_confitem()->url_bbsmenu = std::move( url ); } bool CONFIG::use_link_as_board(){ return get_confitem()->use_link_as_board; } bool CONFIG::get_show_movediag(){ return get_confitem()->show_movediag; } void CONFIG::set_show_movediag( const bool show ){ get_confitem()->show_movediag = show; } const std::string& CONFIG::get_menu_search_title(){ return get_confitem()->menu_search_title; } const std::string& CONFIG::get_url_search_title(){ return get_confitem()->url_search_title; } const std::string& CONFIG::get_regex_search_title(){ return get_confitem()->regex_search_title; } const std::string& CONFIG::get_menu_search_web(){ return get_confitem()->menu_search_web; } const std::string& CONFIG::get_url_search_web(){ return get_confitem()->url_search_web; } const std::string& CONFIG::get_agent_for2ch() { return get_confitem()->agent_for2ch; } bool CONFIG::get_use_proxy_for2ch() { return get_confitem()->use_proxy_for2ch; } bool CONFIG::get_send_cookie_to_proxy_for2ch() { return get_confitem()->send_cookie_to_proxy_for2ch; } const std::string& CONFIG::get_proxy_for2ch() { return get_confitem()->proxy_for2ch; } int CONFIG::get_proxy_port_for2ch() { return get_confitem()->proxy_port_for2ch; } const std::string& CONFIG::get_proxy_basicauth_for2ch() { return get_confitem()->proxy_basicauth_for2ch; } void CONFIG::set_use_proxy_for2ch( bool set ){ get_confitem()->use_proxy_for2ch = set; } void CONFIG::set_send_cookie_to_proxy_for2ch( bool set ){ get_confitem()->send_cookie_to_proxy_for2ch = set; } void CONFIG::set_proxy_for2ch( const std::string& proxy ){ get_confitem()->set_proxy_for2ch( proxy ); } void CONFIG::set_proxy_port_for2ch( int port ){ get_confitem()->proxy_port_for2ch = port; } bool CONFIG::get_use_proxy_for2ch_w() { return get_confitem()->use_proxy_for2ch_w; } bool CONFIG::get_send_cookie_to_proxy_for2ch_w() { return get_confitem()->send_cookie_to_proxy_for2ch_w; } const std::string& CONFIG::get_proxy_for2ch_w() { return get_confitem()->proxy_for2ch_w; } int CONFIG::get_proxy_port_for2ch_w() { return get_confitem()->proxy_port_for2ch_w; } const std::string& CONFIG::get_proxy_basicauth_for2ch_w() { return get_confitem()->proxy_basicauth_for2ch_w; } void CONFIG::set_use_proxy_for2ch_w( bool set ){ get_confitem()->use_proxy_for2ch_w = set; } void CONFIG::set_send_cookie_to_proxy_for2ch_w( bool set ){ get_confitem()->send_cookie_to_proxy_for2ch_w = set; } void CONFIG::set_proxy_for2ch_w( const std::string& proxy ){ get_confitem()->set_proxy_for2ch_w( proxy ); } void CONFIG::set_proxy_port_for2ch_w( int port ){ get_confitem()->proxy_port_for2ch_w = port; } const std::string& CONFIG::get_agent_for_data() { return get_confitem()->agent_for_data; } bool CONFIG::get_use_proxy_for_data() { return get_confitem()->use_proxy_for_data; } bool CONFIG::get_send_cookie_to_proxy_for_data() { return get_confitem()->send_cookie_to_proxy_for_data; } const std::string& CONFIG::get_proxy_for_data() { return get_confitem()->proxy_for_data; } int CONFIG::get_proxy_port_for_data() { return get_confitem()->proxy_port_for_data; } const std::string& CONFIG::get_proxy_basicauth_for_data() { return get_confitem()->proxy_basicauth_for_data; } const std::string& CONFIG::get_x_2ch_ua() { return get_confitem()->x_2ch_ua; } void CONFIG::set_use_proxy_for_data( bool set ){ get_confitem()->use_proxy_for_data = set; } void CONFIG::set_send_cookie_to_proxy_for_data( bool set ){ get_confitem()->send_cookie_to_proxy_for_data = set; } void CONFIG::set_proxy_for_data( const std::string& proxy ){ get_confitem()->set_proxy_for_data( proxy ); } void CONFIG::set_proxy_port_for_data( int port ){ get_confitem()->proxy_port_for_data = port; } int CONFIG::get_loader_bufsize(){ return get_confitem()->loader_bufsize; } int CONFIG::get_loader_bufsize_board(){ return get_confitem()->loader_bufsize_board; } int CONFIG::get_loader_timeout(){ return get_confitem()->loader_timeout; } int CONFIG::get_loader_timeout_post(){ return get_confitem()->loader_timeout_post; } int CONFIG::get_loader_timeout_data(){ return get_confitem()->loader_timeout_img; } // 旧 get_loader_timeout_img() 関数 int CONFIG::get_loader_timeout_checkupdate(){ return get_confitem()->loader_timeout_checkupdate; } bool CONFIG::get_use_ipv6(){ return get_confitem()->use_ipv6; } void CONFIG::set_use_ipv6( const bool set ){ get_confitem()->use_ipv6 = set; } // 同一ホストに対する最大コネクション数( 1 または 2 ) int CONFIG::get_connection_num(){ return get_confitem()->connection_num; } // 2chのクッキー (互換性のため設定名は旧名称を使う) bool CONFIG::get_use_cookie_hap(){ return get_confitem()->use_cookie_hap; } const std::string& CONFIG::get_cookie_hap(){ return get_confitem()->cookie_hap; } const std::string& CONFIG::get_cookie_hap_bbspink(){ return get_confitem()->cookie_hap_bbspink; } void CONFIG::set_cookie_hap( const std::string& cookie_hap ){ get_confitem()->cookie_hap = cookie_hap; } void CONFIG::set_cookie_hap_bbspink( const std::string& cookie_hap ){ get_confitem()->cookie_hap_bbspink = cookie_hap; } const std::string& CONFIG::get_command_openurl() { return get_confitem()->command_openurl; } void CONFIG::set_command_openurl( const std::string& command ){ get_confitem()->command_openurl = command; } int CONFIG::get_browsercombo_id(){ return get_confitem()->browsercombo_id; } void CONFIG::set_browsercombo_id( const int id ){ get_confitem()->browsercombo_id = id; } bool CONFIG::get_refpopup_by_mo(){ return get_confitem()->refpopup_by_mo; } bool CONFIG::get_namepopup_by_mo(){ return get_confitem()->namepopup_by_mo; } bool CONFIG::get_idpopup_by_mo(){ return get_confitem()->idpopup_by_mo; } int CONFIG::get_imgemb_interp(){ return get_confitem()->imgemb_interp; } int CONFIG::get_imgmain_interp(){ return get_confitem()->imgmain_interp; } int CONFIG::get_imgpopup_interp(){ return get_confitem()->imgpopup_interp; } int CONFIG::get_imgpopup_width(){ return get_confitem()->imgpopup_width; } int CONFIG::get_imgpopup_height(){ return get_confitem()->imgpopup_height; } bool CONFIG::get_use_image_popup(){ return get_confitem()->use_image_popup; } void CONFIG::set_use_image_popup( const bool use ){ get_confitem()->use_image_popup = use; } bool CONFIG::get_use_image_view(){ return get_confitem()->use_image_view; } void CONFIG::set_use_image_view( const bool image_view ){ get_confitem()->use_image_view = image_view; } bool CONFIG::get_use_inline_image(){ return get_confitem()->use_inline_image; } void CONFIG::set_use_inline_image( const bool inline_img ){ get_confitem()->use_inline_image = inline_img; } bool CONFIG::get_show_ssspicon(){ return get_confitem()->show_ssspicon; } void CONFIG::set_show_sssp_icon( const bool show ){ get_confitem()->show_ssspicon = show; } // インライン画像の最大幅と高さ int CONFIG::get_embimg_width(){ return get_confitem()->embimg_width; } int CONFIG::get_embimg_height(){ return get_confitem()->embimg_height; } // 埋め込み画像ビューを閉じたときにタブも閉じる bool CONFIG::get_hide_imagetab(){ return get_confitem()->hide_imagetab; } // 画像ビューでdeleteを押したときに確認ダイアログを表示する bool CONFIG::get_show_delimgdiag(){ return get_confitem()->show_delimgdiag; } void CONFIG::set_show_delimgdiag( const bool show ){ get_confitem()->show_delimgdiag = show; } bool CONFIG::get_use_mosaic(){ return get_confitem()->use_mosaic; } void CONFIG::set_use_mosaic( const bool mosaic ) { get_confitem()->use_mosaic = mosaic; } int CONFIG::get_mosaic_size(){ return get_confitem()->mosaic_size; } bool CONFIG::get_zoom_to_fit(){ return get_confitem()->zoom_to_fit; } void CONFIG::set_zoom_to_fit( const bool fit ){ get_confitem()->zoom_to_fit = fit; } int CONFIG::get_del_img_day(){ return get_confitem()->del_img_day; } void CONFIG::set_del_img_day( const int day ){ get_confitem()->del_img_day = day; } int CONFIG::get_del_imgabone_day(){ return get_confitem()->del_imgabone_day; } void CONFIG::set_del_imgabone_day( const int day ){ get_confitem()->del_imgabone_day = day; } int CONFIG::get_max_img_size(){ return get_confitem()->max_img_size; } int CONFIG::get_max_img_pixel(){ return get_confitem()->max_img_pixel; } int CONFIG::get_imgcache_size(){ return get_confitem()->imgcache_size; } int CONFIG::get_newthread_hour(){ return get_confitem()->newthread_hour; } bool CONFIG::get_inc_search_board(){ return get_confitem()->inc_search_board; } bool CONFIG::get_show_deldiag(){ return get_confitem()->show_deldiag; } void CONFIG::set_show_deldiag( const bool show ){ get_confitem()->show_deldiag = show; } // スレ一覧をロードする前にキャッシュにある一覧を表示 bool CONFIG::get_show_cached_board(){ return get_confitem()->show_cached_board; } // スレ一覧でお知らせスレ(924)のアイコンを表示する bool CONFIG::get_show_924(){ return get_confitem()->show_924; } int CONFIG::get_tree_scroll_size(){ return get_confitem()->tree_scroll_size; } int CONFIG::get_scroll_size(){ return get_confitem()->scroll_size; } int CONFIG::get_key_scroll_size(){ return get_confitem()->key_scroll_size; } int CONFIG::get_key_fastscroll_size(){ return get_confitem()->key_fastscroll_size; } bool CONFIG::get_jump_after_reload(){ return get_confitem()->jump_after_reload; } void CONFIG::set_jump_after_reload( const bool set ){ get_confitem()->jump_after_reload = set; } bool CONFIG::get_jump_new_after_reload(){ return get_confitem()->jump_new_after_reload; } void CONFIG::set_jump_new_after_reload( const bool set ){ get_confitem()->jump_new_after_reload = set; } int CONFIG::get_live_mode(){ return get_confitem()->live_mode; } void CONFIG::set_live_mode( const int mode ) { get_confitem()->live_mode = mode; } int CONFIG::get_live_speed(){ return get_confitem()->live_speed; } void CONFIG::set_live_speed( const int speed ){ get_confitem()->live_speed = speed; } int CONFIG::get_live_threshold(){ return get_confitem()->live_threshold; } void CONFIG::set_live_threshode( const int th ){ get_confitem()->live_threshold = th; } bool CONFIG::get_open_one_category(){ return get_confitem()->open_one_category; } bool CONFIG::get_open_one_favorite(){ return get_confitem()->open_one_favorite; } // デフォルトの書き込み名 std::string CONFIG::get_write_name(){ return get_confitem()->write_name; } // デフォルトのメールアドレス std::string CONFIG::get_write_mail(){ return get_confitem()->write_mail; } bool CONFIG::get_always_write_ok() { return get_confitem()->always_write_ok; } void CONFIG::set_always_write_ok( const bool write_ok ){ get_confitem()->always_write_ok = write_ok; } bool CONFIG::get_save_post_log(){ return get_confitem()->save_postlog; } void CONFIG::set_save_post_log( const bool save ){ get_confitem()->save_postlog = save; } size_t CONFIG::get_maxsize_post_log(){ return get_confitem()->maxsize_postlog; } // 書き込み履歴を保存 bool CONFIG::get_save_post_history(){ return get_confitem()->save_posthist; } void CONFIG::set_save_post_history( const bool save ){ get_confitem()->save_posthist = save; } bool CONFIG::get_hide_writing_dialog(){ return get_confitem()->hide_writing_dialog; } // 編集中のメッセージの保存確認ダイアログを表示する bool CONFIG::get_show_savemsgdiag(){ return get_confitem()->show_savemsgdiag; } void CONFIG::set_show_savemsgdiag( const bool show ){ get_confitem()->show_savemsgdiag = show; } // 書き込みビューでテキストを折り返す bool CONFIG::get_message_wrap(){ return get_confitem()->message_wrap; } void CONFIG::set_message_wrap( const bool wrap ){ get_confitem()->message_wrap = wrap; } bool CONFIG::get_fold_message(){ return get_confitem()->fold_message; } void CONFIG::set_fold_message( const bool fold ){ get_confitem()->fold_message = fold; } bool CONFIG::get_fold_image(){ return get_confitem()->fold_image; } bool CONFIG::get_keep_im_status(){ return get_confitem()->keep_im_status; } int CONFIG::get_margin_popup(){ return get_confitem()->margin_popup; } void CONFIG::set_margin_popup( const int margin ){ get_confitem()->margin_popup = margin; } // 画像ポップアップとカーソルの間のマージン int CONFIG::get_margin_imgpopup_x(){ return get_confitem()->margin_imgpopup_x; } int CONFIG::get_margin_imgpopup(){ return get_confitem()->margin_imgpopup; } // ポップアップが消えるまでの時間(ミリ秒) int CONFIG::get_hide_popup_msec(){ return get_confitem()->hide_popup_msec; } // マウスジェスチャを有効 bool CONFIG::get_enable_mg(){ return get_confitem()->enable_mg; } // マウスジェスチャの判定開始半径 int CONFIG::get_mouse_radius(){ return get_confitem()->mouse_radius; } // 数字入力ジャンプのウェイト(ミリ秒) int CONFIG::get_numberjmp_msec(){ return get_confitem()->numberjmp_msec; } int CONFIG::get_history_size(){ return get_confitem()->history_size; } int CONFIG::get_historyview_size(){ return get_confitem()->historyview_size; } int CONFIG::get_aahistory_size(){ return get_confitem()->aahistory_size; } // 0以上なら多重ポップアップの説明を表示する // 呼び出される度に--する int CONFIG::get_instruct_popup(){ if( get_confitem()->instruct_popup ) return get_confitem()->instruct_popup--; return 0; } bool CONFIG::get_instruct_tglart(){ if( get_confitem()->instruct_tglart_end ) return false; get_confitem()->instruct_tglart_end = true; // 一度表示したら表示しない return get_confitem()->instruct_tglart; } void CONFIG::set_instruct_tglart( const bool tgl ){ get_confitem()->instruct_tglart = tgl; } bool CONFIG::get_instruct_tglimg(){ if( get_confitem()->instruct_tglimg_end ) return false; get_confitem()->instruct_tglimg_end = true; // 一度表示したら表示しない return get_confitem()->instruct_tglimg; } void CONFIG::set_instruct_tglimg( bool tgl ){ get_confitem()->instruct_tglimg = tgl; } // スレビューでdeleteを押したときに確認ダイアログを表示する bool CONFIG::get_show_delartdiag(){ return get_confitem()->show_delartdiag; } void CONFIG::set_show_delartdiag( const bool show ){ get_confitem()->show_delartdiag = show; } double CONFIG::get_adjust_underline_pos(){ return get_confitem()->adjust_underline_pos; } void CONFIG::set_adjust_underline_pos( const double pos ){ get_confitem()->adjust_underline_pos = pos; } double CONFIG::get_adjust_line_space(){ return get_confitem()->adjust_line_space; } void CONFIG::set_adjust_line_space( const double space ){ get_confitem()->adjust_line_space = space; } bool CONFIG::get_draw_underline(){ return get_confitem()->draw_underline; } bool CONFIG::get_strict_char_width(){ return get_confitem()->strict_char_width; } void CONFIG::set_strict_char_width( const bool strictwidth ){ get_confitem()->strict_char_width = strictwidth; } bool CONFIG::get_check_id(){ return get_confitem()->check_id; } int CONFIG::get_num_reference_high(){ return get_confitem()->num_reference_high; } int CONFIG::get_num_reference_low(){ return get_confitem()->num_reference_low; } int CONFIG::get_num_id_high(){ return get_confitem()->num_id_high; } int CONFIG::get_num_id_low(){ return get_confitem()->num_id_low; } bool CONFIG::get_loose_url(){ return get_confitem()->loose_url; } bool CONFIG::get_hide_usrcmd(){ return get_confitem()->hide_usrcmd; } void CONFIG::set_hide_usrcmd( const bool hide ){ get_confitem()->hide_usrcmd = hide; } bool CONFIG::get_reload_allthreads(){ return get_confitem()->reload_allthreads; } int CONFIG::get_tab_min_str(){ return get_confitem()->tab_min_str; } bool CONFIG::get_show_tab_icon(){ return get_confitem()->show_tab_icon; } // タブ上でマウスホイールを回転してタブを切り替える bool CONFIG::get_switchtab_wheel(){ return get_confitem()->switchtab_wheel; } // 他のビューを開くときのタブの位置 ( 0: 一番右端 1:右隣 2:左隣 ) int CONFIG::get_newtab_pos(){ return get_confitem()->newtab_pos; } // ツリービューで選択したビューを開くときのタブの位置 ( 0: 一番右端 1:右隣 2:左隣 ) int CONFIG::get_opentab_pos(){ return get_confitem()->opentab_pos; } // 次スレ検索を開くときのタブの位置 ( 0: 次スレ検索タブ 1:新しいタブ 2:アクティブなタブを置き換え ) int CONFIG::get_boardnexttab_pos(){ return get_confitem()->boardnexttab_pos; } bool CONFIG::get_show_post_mark(){ return get_confitem()->show_post_mark; } void CONFIG::set_show_post_mark( const bool show ){ get_confitem()->show_post_mark = show; } bool CONFIG::get_flat_button(){ return get_confitem()->flat_button; } void CONFIG::set_flat_button( const bool set ){ get_confitem()->flat_button = set; } // ツールバーの背景描画 bool CONFIG::get_draw_toolbarback(){ return get_confitem()->draw_toolbarback; } void CONFIG::set_draw_toolbarback( const bool set ){ get_confitem()->draw_toolbarback = set; } std::list< std::string >& CONFIG::get_list_abone_word_thread(){ return get_confitem()->list_abone_word_thread; } std::list< std::string >& CONFIG::get_list_abone_regex_thread(){ return get_confitem()->list_abone_regex_thread; } void CONFIG::set_list_abone_word_thread( std::list< std::string >& word ) { // 前後の空白と空白行を除く get_confitem()->list_abone_word_thread = MISC::remove_space_from_list( word ); get_confitem()->list_abone_word_thread = MISC::remove_nullline_from_list( get_confitem()->list_abone_word_thread ); } void CONFIG::set_list_abone_regex_thread( std::list< std::string >& regex ) { // 前後の空白と空白行を除く get_confitem()->list_abone_regex_thread = MISC::remove_space_from_list( regex ); get_confitem()->list_abone_regex_thread = MISC::remove_nullline_from_list( get_confitem()->list_abone_regex_thread ); } int CONFIG::get_remove_old_abone_thread(){ return get_confitem()->remove_old_abone_thread; } void CONFIG::set_remove_old_abone_thread( const int remove ){ get_confitem()->remove_old_abone_thread = remove; } int CONFIG::get_abone_low_number_thread(){ return get_confitem()->abone_low_number_thread; } void CONFIG::set_abone_low_number_thread( int number ){ get_confitem()->abone_low_number_thread = number; } int CONFIG::get_abone_high_number_thread(){ return get_confitem()->abone_high_number_thread; } void CONFIG::set_abone_high_number_thread( int number ){ get_confitem()->abone_high_number_thread = number; } int CONFIG::get_abone_hour_thread(){ return get_confitem()->abone_hour_thread; } void CONFIG::set_abone_hour_thread( const int hour ){ get_confitem()->abone_hour_thread = hour; } const std::list< std::string >& CONFIG::get_list_abone_name(){ return get_confitem()->list_abone_name; } const std::list< std::string >& CONFIG::get_list_abone_word(){ return get_confitem()->list_abone_word; } const std::list< std::string >& CONFIG::get_list_abone_regex(){ return get_confitem()->list_abone_regex; } void CONFIG::set_list_abone_name( const std::list< std::string >& name ) { // 前後の空白と空白行を除く get_confitem()->list_abone_name = MISC::remove_space_from_list( name ); get_confitem()->list_abone_name = MISC::remove_nullline_from_list( get_confitem()->list_abone_name ); } void CONFIG::set_list_abone_word( const std::list< std::string >& word ) { // 前後の空白と空白行を除く get_confitem()->list_abone_word = MISC::remove_space_from_list( word ); get_confitem()->list_abone_word = MISC::remove_nullline_from_list( get_confitem()->list_abone_word ); } void CONFIG::set_list_abone_regex( const std::list< std::string >& regex ) { // 前後の空白と空白行を除く get_confitem()->list_abone_regex = MISC::remove_space_from_list( regex ); get_confitem()->list_abone_regex = MISC::remove_nullline_from_list( get_confitem()->list_abone_regex ); } // デフォルトで透明、連鎖あぼーんをするか bool CONFIG::get_abone_transparent(){ return get_confitem()->abone_transparent; } void CONFIG::set_abone_transparent( const bool set ){ get_confitem()->abone_transparent = set; } bool CONFIG::get_abone_chain(){ return get_confitem()->abone_chain; } void CONFIG::set_abone_chain( const bool set ){ get_confitem()->abone_chain = set; } // NG正規表現によるあぼーん時に大小と全半角文字の違いを無視 bool CONFIG::get_abone_icase(){ return get_confitem()->abone_icase; } void CONFIG::set_abone_icase( const bool set ){ get_confitem()->abone_icase = set; } bool CONFIG::get_abone_wchar(){ return get_confitem()->abone_wchar; } void CONFIG::set_abone_wchar( const bool set ){ get_confitem()->abone_wchar = set; } bool CONFIG::get_expand_sidebar(){ return get_confitem()->expand_sidebar; } bool CONFIG::get_expand_rpane(){ return get_confitem()->expand_rpane; } // ペーンの境界をクリックしてサイドバーを開け閉めする bool CONFIG::get_open_sidebar_by_click(){ return get_confitem()->open_sidebar_by_click; } // 次スレ検索の類似度のしきい値 int CONFIG::get_threshold_next(){ return get_confitem()->threshold_next; } // 次スレを開いたときにお気に入りのアドレスと名前を自動更新 int CONFIG::get_replace_favorite_next(){ return get_confitem()->replace_favorite_next; } void CONFIG::set_replace_favorite_next( const int mode ){ get_confitem()->replace_favorite_next = mode; } // お気に入りの自動更新をするかダイアログを出す bool CONFIG::show_diag_replace_favorite(){ return get_confitem()->show_diag_replace_favorite; } void CONFIG::set_show_diag_replace_favorite( const bool show ){ get_confitem()->show_diag_replace_favorite = show; } // スレをお気に入りに追加したときにしおりをセットする bool CONFIG::get_bookmark_drop(){ return get_confitem()->bookmark_drop; } // お気に入りの更新チェック時に板の更新もチェックする bool CONFIG::get_check_update_board(){ return get_confitem()->check_update_board; } // 起動時にお気に入りを自動でチェックする bool CONFIG::get_check_update_boot(){ return get_confitem()->check_update_boot; } // お気に入り登録時に重複項目を登録するか ( 0: 登録する 1: ダイアログ表示 2: 登録しない ) int CONFIG::get_check_favorite_dup(){ return get_confitem()->check_favorite_dup; } void CONFIG::set_check_favorite_dup( const int check ){ get_confitem()->check_favorite_dup = check; } // お気に入り登録時に挿入先ダイアログを表示する ( 0 : 表示する 1: 表示せず先頭に追加 2: 表示せず最後に追加 ) int CONFIG::get_show_favorite_select_diag(){ return get_confitem()->show_favorite_select_diag; } // Ctrl+qでウィンドウを閉じない ( 2.8.6以前と互換性を保つため残す ) bool CONFIG::get_disable_close(){ return get_confitem()->disable_close; } void CONFIG::set_disable_close( const bool disable ){ get_confitem()->disable_close = disable; } // メニューバーを非表示にした時にダイアログを表示 bool CONFIG::get_show_hide_menubar_diag(){ return get_confitem()->show_hide_menubar_diag; } void CONFIG::set_show_hide_menubar_diag( const bool set ){ get_confitem()->show_hide_menubar_diag = set; } // 状態変更時にメインステータスバーの色を変える bool CONFIG::get_change_stastatus_color(){ return get_confitem()->change_stastatus_color; } // Client-Side Decorationを使うか( 0: 使わない 1: 使う 2: デスクトップに合わせる ) int CONFIG::get_use_header_bar() { return get_confitem()->use_header_bar; } // まちBBSの取得に offlaw.cgi を使用する bool CONFIG::get_use_machi_offlaw(){ return get_confitem()->use_machi_offlaw; } void CONFIG::set_use_machi_offlaw( const bool set ){ get_confitem()->use_machi_offlaw = set; } // 書き込み履歴のあるスレを削除する時にダイアログを表示 bool CONFIG::get_show_del_written_thread_diag(){ return get_confitem()->show_del_written_thread_diag; } void CONFIG::set_del_written_thread_diag( const bool set ){ get_confitem()->show_del_written_thread_diag = set; } // スレを削除する時に画像キャッシュも削除する ( 0: ダイアログ表示 1: 削除 2: 削除しない ) int CONFIG::get_delete_img_in_thread(){ return get_confitem()->delete_img_in_thread; } void CONFIG::set_delete_img_in_thread( const int set ){ get_confitem()->delete_img_in_thread = set; } //最大表示可能レス数 int CONFIG::get_max_resnumber(){ return get_confitem()->max_resnumber; } void CONFIG::set_max_resnumber( const int set ){ get_confitem()->max_resnumber = set; } // FIFOの作成などにエラーがあったらダイアログを表示する bool CONFIG::get_show_diag_fifo_error(){ return get_confitem()->show_diag_fifo_error; } void CONFIG::set_show_diag_fifo_error( const bool set ){ get_confitem()->show_diag_fifo_error = set; } // 指定した分ごとにセッションを自動保存 (0: 保存しない) int CONFIG::get_save_session(){ return get_confitem()->save_session; } #ifdef HAVE_MIGEMO_H const std::string& CONFIG::get_migemodict_path() { return get_confitem()->migemodict_path; } #endif jdim-0.7.0/src/config/globalconf.h000066400000000000000000000523631417047150700167730ustar00rootroot00000000000000// ライセンス: GPL2 // // グローバル設定 // #ifndef _GLOBALCONF_H #define _GLOBALCONF_H #include #include namespace CONFIG { class ConfigItems; ConfigItems* get_confitem(); void delete_confitem(); // 設定読み込み、書き込み bool load_conf(); void save_conf(); // 設定の一時的なバックアップと復元 void bkup_conf(); void restore_conf(); ///////////////////////////////////////////// // 前回開いたviewを復元するか bool get_restore_board(); void set_restore_board( const bool restore ); bool get_restore_article(); void set_restore_article( const bool restore ); bool get_restore_image(); void set_restore_image( const bool restore ); // 自前でウィンドウ配置を管理する bool get_manage_winpos(); void set_manage_winpos( const bool manage ); // 色 ( # + 12桁の16進数 の形式 ) const std::string& get_color( const int id ); void set_color( const int id, const std::string& color ); void reset_colors(); // 書き込みビューでGTKテーマの設定を使用するか (GTK3版のみ) bool get_use_message_gtktheme(); void set_use_message_gtktheme( const bool use ); // ツリービューでgtkrcの設定を使用するか bool get_use_tree_gtkrc(); void set_use_tree_gtkrc( const bool use ); // スレビューの選択色でgtkrcの設定を使用するか bool get_use_select_gtkrc(); void set_use_select_gtkrc( const bool use ); // ツリービューの行間スペース int get_tree_ypad(); // ツリービューにエクスパンダを表示 bool get_tree_show_expanders(); // ツリービューのレベルインデント調整量(ピクセル) int get_tree_level_indent(); // カテゴリやディレクトリを開いたときにツリービューをスクロールする bool get_scroll_tree(); // ツリービューの選択を表示中のビューと同期する ( 0: 同期しない 1: 同期する 2: 同期する(フォルダを開く) ) int get_select_item_sync(); void set_select_item_sync( const int sync ); // 各ビューと枠との間の余白 int get_view_margin(); // スクロールバーを左に配置 bool get_left_scrbar(); // スレ一覧で古いスレも表示 bool get_show_oldarticle(); // フォント const std::string& get_fontname( const int id ); void set_fontname( const int id, const std::string& fontname ); void reset_fonts(); bool get_aafont_enabled(); // レスを参照するときに前に付ける文字 std::string get_ref_prefix(); // 参照文字( ref_prefix ) の後のスペースの数 int ref_prefix_space(); // レスにアスキーアートがあると判定する正規表現 std::string get_regex_res_aa(); void set_regex_res_aa( const std::string& regex ); // JD ホームページのアドレス const std::string& get_url_jdhp(); // JDim ホームページのアドレス const std::string& get_url_jdimhp(); // 2chの認証サーバのアドレス const std::string& get_url_login2ch(); const std::string& get_url_loginp2() = delete; // Removed in v0.3.0 (2020-05) void set_url_loginp2( const std::string& url ) = delete; // Removed in v0.3.0 (2020-05) // BEの認証サーバのアドレス const std::string& get_url_loginbe(); // bbsmenu.htmlのURL const std::string& get_url_bbsmenu(); void set_url_bbsmenu( std::string url ); // bbsmenu.html内にあるリンクは全て板とみなす bool use_link_as_board(); // 板移転時に確認ダイアログを表示する bool get_show_movediag(); void set_show_movediag( const bool show ); // スレタイ検索用メニュータイトルアドレス const std::string& get_menu_search_title(); const std::string& get_url_search_title(); // スレタイ検索用正規表現 const std::string& get_regex_search_title(); // WEB検索用メニュータイトルアドレス const std::string& get_menu_search_web(); const std::string& get_url_search_web(); const std::string& get_url_writep2() = delete; // Removed in v0.3.0 (2020-05) const std::string& get_url_resp2() = delete; // Removed in v0.3.0 (2020-05) // 2ch にアクセスするときのエージェント名 const std::string& get_agent_for2ch(); // 2ch にログインするときのX-2ch-UA const std::string& get_x_2ch_ua(); // 2ch 読み込み用プロクシとポート番号 bool get_use_proxy_for2ch(); bool get_send_cookie_to_proxy_for2ch(); const std::string& get_proxy_for2ch(); int get_proxy_port_for2ch(); const std::string& get_proxy_basicauth_for2ch(); void set_use_proxy_for2ch( const bool set ); void set_send_cookie_to_proxy_for2ch( bool set ); void set_proxy_for2ch( const std::string& proxy ); void set_proxy_port_for2ch( const int port ); // 2ch 書き込み用プロクシとポート番号 bool get_use_proxy_for2ch_w(); bool get_send_cookie_to_proxy_for2ch_w(); const std::string& get_proxy_for2ch_w(); int get_proxy_port_for2ch_w(); const std::string& get_proxy_basicauth_for2ch_w(); void set_use_proxy_for2ch_w( const bool set ); void set_send_cookie_to_proxy_for2ch_w( bool set ); void set_proxy_for2ch_w( const std::string& proxy ); void set_proxy_port_for2ch_w( const int port ); // 2ch外にアクセスするときのエージェント名 const std::string& get_agent_for_data(); // 2chの外にアクセスするときのプロクシとポート番号 bool get_use_proxy_for_data(); bool get_send_cookie_to_proxy_for_data(); const std::string& get_proxy_for_data(); int get_proxy_port_for_data(); const std::string& get_proxy_basicauth_for_data(); void set_use_proxy_for_data( const bool set ); void set_send_cookie_to_proxy_for_data( bool set ); void set_proxy_for_data( const std::string& proxy ); void set_proxy_port_for_data( const int port ); // ローダのバッファサイズ int get_loader_bufsize(); // 一般 int get_loader_bufsize_board(); // スレ一覧読み込み用 // ローダのタイムアウト値 int get_loader_timeout(); int get_loader_timeout_post(); int get_loader_timeout_data(); int get_loader_timeout_checkupdate(); // ipv6使用 bool get_use_ipv6(); void set_use_ipv6( const bool set ); // 同一ホストに対する最大コネクション数( 1 または 2 ) int get_connection_num(); // 2chのクッキー (互換性のため設定名は旧名称を使う) bool get_use_cookie_hap(); const std::string& get_cookie_hap(); const std::string& get_cookie_hap_bbspink(); void set_cookie_hap( const std::string& cookie_hap ); void set_cookie_hap_bbspink( const std::string& cookie_hap ); bool get_use_offlaw2_2ch() = delete; // Removed in v0.3.0 (2020-04) // リンクをクリックしたときに実行するコマンド const std::string& get_command_openurl(); void set_command_openurl( const std::string& command ); // ブラウザ設定ダイアログのコンボボックスの番号 int get_browsercombo_id(); void set_browsercombo_id( const int id ); // レス番号の上にマウスオーバーしたときに参照ポップアップ表示する bool get_refpopup_by_mo(); // 名前の上にマウスオーバーしたときにポップアップ表示する bool get_namepopup_by_mo(); // IDの上にマウスオーバーしたときにIDをポップアップ表示する bool get_idpopup_by_mo(); // 画像のスムージングレベル(0-2, 2が最も高画質かつ低速) int get_imgemb_interp(); int get_imgmain_interp(); int get_imgpopup_interp(); // 画像ポップアップサイズ int get_imgpopup_width(); int get_imgpopup_height(); // 画像ポップアップを使用する bool get_use_image_popup(); void set_use_image_popup( const bool use ); // 画像ビューを使用する bool get_use_image_view(); void set_use_image_view( const bool image_view ); // インライン画像表示をする bool get_use_inline_image(); void set_use_inline_image( const bool inline_img ); // ssspアイコン表示 bool get_show_ssspicon(); void set_show_sssp_icon( const bool show ); // インライン画像の最大幅と高さ int get_embimg_width(); int get_embimg_height(); // 埋め込み画像ビューを閉じたときにタブも閉じる bool get_hide_imagetab(); // 画像ビューでdeleteを押したときに確認ダイアログを表示する bool get_show_delimgdiag(); void set_show_delimgdiag( const bool show ); // 画像にモザイクかける bool get_use_mosaic(); void set_use_mosaic( const bool mosaic ); // モザイクの大きさ int get_mosaic_size(); // 画像をデフォルトでウィンドウサイズに合わせる bool get_zoom_to_fit(); void set_zoom_to_fit( const bool fit ); // 画像キャッシュ削除の日数 int get_del_img_day(); void set_del_img_day( const int day ); // 画像あぼーん削除の日数 int get_del_imgabone_day(); void set_del_imgabone_day( const int day ); // ダウンロードする画像の最大ファイルサイズ(Mbyte) int get_max_img_size(); // 画像の最大サイズ(Mピクセル) int get_max_img_pixel(); // 画像のメモリキャッシュ枚数 int get_imgcache_size(); // スレ一覧で指定した値(時間)よりも後に立てられたスレを新着とみなす int get_newthread_hour(); // スレ一覧でインクリメント検索をする bool get_inc_search_board(); // スレ一覧でdeleteを押したときに確認ダイアログを表示する bool get_show_deldiag(); void set_show_deldiag( const bool show ); // スレ一覧をロードする前にキャッシュにある一覧を表示 bool get_show_cached_board(); // スレ一覧でお知らせスレ(924)のアイコンを表示する bool get_show_924(); // ツリービューのスクロール量(マウスホイール上下・行数) int get_tree_scroll_size(); // スレビューのスクロール量(マウスホイール上下・行数) int get_scroll_size(); // スレビューのスクロール量(キー上下・行数) int get_key_scroll_size(); // スレビューの高速スクロール量(キー上下・ ページ高 - 行高 * key_fastscroll_size ) int get_key_fastscroll_size(); // スレビューでリロード後に一番下までスクロール bool get_jump_after_reload(); void set_jump_after_reload( const bool set ); // スレビューでリロード後に新着までスクロール bool get_jump_new_after_reload(); void set_jump_new_after_reload( const bool set ); // 実況モード int get_live_mode(); void set_live_mode( const int mode ); // 実況速度 int get_live_speed(); void set_live_speed( const int speed ); // 実況のスクロールモードを切り替えるしきい値 int get_live_threshold(); void set_live_threshode( const int th ); // 板一覧でカテゴリを常にひとつだけ開く bool get_open_one_category(); // お気に入りでディレクトリを常にひとつだけ開く bool get_open_one_favorite(); // デフォルトの書き込み名 std::string get_write_name(); // デフォルトのメールアドレス std::string get_write_mail(); // 書き込み時に書き込み確認ダイアログを出すかどうか bool get_always_write_ok(); void set_always_write_ok( const bool write_ok ); // 書き込みログを保存 bool get_save_post_log(); void set_save_post_log( const bool save ); // 書き込みログの最大サイズ size_t get_maxsize_post_log(); // 書き込み履歴を保存 bool get_save_post_history(); void set_save_post_history( const bool save ); // 書き込み中のダイアログを表示しない bool get_hide_writing_dialog(); // 編集中のメッセージの保存確認ダイアログを表示する bool get_show_savemsgdiag(); void set_show_savemsgdiag( const bool show ); // 書き込みビューでテキストを折り返す bool get_message_wrap(); void set_message_wrap( const bool wrap ); // 非アクティブ時に書き込みビューを折りたたむ bool get_fold_message(); void set_fold_message( const bool fold ); // 非アクティブ時に画像ビューを折りたたむ bool get_fold_image(); // 書き込み欄の日本語のON/OFF状態を保存 bool get_keep_im_status(); // ポップアップとカーソルの間のマージン int get_margin_popup(); void set_margin_popup( const int margin ); // 画像ポップアップとカーソルの間のマージン int get_margin_imgpopup_x(); // 水平方向 int get_margin_imgpopup(); // 垂直方向 // ポップアップが消えるまでの時間(ミリ秒) int get_hide_popup_msec(); // マウスジェスチャを有効 bool get_enable_mg(); // マウスジェスチャの判定開始半径 int get_mouse_radius(); // 数字入力ジャンプの待ち時間(ミリ秒) int get_numberjmp_msec(); // 履歴メニューの表示数 int get_history_size(); // 履歴ビューの表示数 int get_historyview_size(); // AA履歴の保持数 int get_aahistory_size(); // 0以上なら多重ポップアップの説明を表示する int get_instruct_popup(); // スレビューを開いたときにスレ一覧との切り替え方法を説明する bool get_instruct_tglart(); void set_instruct_tglart( const bool set ); // 画像ビューを開いたときにスレビューとの切り替え方法を説明する bool get_instruct_tglimg(); void set_instruct_tglimg( const bool set ); // スレビューでdeleteを押したときに確認ダイアログを表示する bool get_show_delartdiag(); void set_show_delartdiag( const bool show ); // 下線位置調整 double get_adjust_underline_pos(); void set_adjust_underline_pos( const double pos ); // スレ表示の行間調整 double get_adjust_line_space(); void set_adjust_line_space( const double space ); // スレ表示でリンクの下に下線を引く bool get_draw_underline(); // スレビューで文字幅の近似を厳密にする bool get_strict_char_width(); void set_strict_char_width( const bool strictwidth ); // スレビューで発言数(ID)をカウントする bool get_check_id(); // レス参照で色を変える回数 int get_num_reference_high(); int get_num_reference_low(); // 発言数で色を変える回数 int get_num_id_high(); int get_num_id_low(); // datのパース時にURL判定を甘くする(^なども含める) bool get_loose_url(); // ユーザーコマンドで選択できない項目を非表示にする bool get_hide_usrcmd(); void set_hide_usrcmd( const bool hide ); // スレビューで再読み込みボタンを押したときに全タブを更新する bool get_reload_allthreads(); // タブに表示する文字列の最小値 int get_tab_min_str(); // タブにアイコンを表示するか bool get_show_tab_icon(); // タブ上でマウスホイールを回転してタブを切り替える bool get_switchtab_wheel(); // 他のビューを開くときのタブの位置 ( 0: 一番右端 1:右隣 2:左隣 ) int get_newtab_pos(); // ツリービューで選択したビューを開くときのタブの位置 ( 0: 一番右端 1:右隣 2:左隣 ) int get_opentab_pos(); // 次スレ検索を開くときのタブの位置 ( 0: 次スレ検索タブ 1:新しいタブ 2:アクティブなタブを置き換え ) int get_boardnexttab_pos(); // スレビューに書き込みマークを表示するか bool get_show_post_mark(); void set_show_post_mark( const bool show ); // ボタンをフラットにするか bool get_flat_button(); void set_flat_button( const bool set ); // ツールバーの背景描画 bool get_draw_toolbarback(); void set_draw_toolbarback( const bool set ); // boardviewでのスレの全体あぼーん std::list< std::string >& get_list_abone_word_thread(); // ワード std::list< std::string >& get_list_abone_regex_thread(); // 正規表現 void set_list_abone_word_thread( std::list< std::string >& word ); void set_list_abone_regex_thread( std::list< std::string >& regex ); int get_remove_old_abone_thread(); // dat落ちしたスレをNGスレタイトルリストから取り除くか( 0: ダイアログ表示 1: 取り除く 2: 除かない ) void set_remove_old_abone_thread( const int remove ); int get_abone_low_number_thread(); void set_abone_low_number_thread( int number ); int get_abone_high_number_thread(); void set_abone_high_number_thread( int number ); int get_abone_hour_thread(); void set_abone_hour_thread( const int hour ); // articleviewでのレスの全体あぼーん const std::list< std::string >& get_list_abone_name(); // 名前 const std::list< std::string >& get_list_abone_word(); // ワード const std::list< std::string >& get_list_abone_regex(); // 正規表現 void set_list_abone_name( const std::list< std::string >& name ); void set_list_abone_word( const std::list< std::string >& word ); void set_list_abone_regex( const std::list< std::string >& regex ); // デフォルトで透明、連鎖あぼーんをするか bool get_abone_transparent(); void set_abone_transparent( const bool set ); bool get_abone_chain(); void set_abone_chain( const bool set ); // NG正規表現によるあぼーん時に大小と全半角文字の違いを無視 bool get_abone_icase(); void set_abone_icase( const bool set ); bool get_abone_wchar(); void set_abone_wchar( const bool set ); // 右ペーンが空の時にサイドバーを閉じるか bool get_expand_sidebar(); // 3ペーン時にスレ一覧やスレビューを最大化するか bool get_expand_rpane(); // ペーンの境界をクリックしてサイドバーを開け閉めする bool get_open_sidebar_by_click(); // 次スレ検索の類似度のしきい値 int get_threshold_next(); // 次スレを開いたときにお気に入りのアドレスと名前を自動更新 int get_replace_favorite_next(); void set_replace_favorite_next( const int mode ); // お気に入りの自動更新をするかダイアログを出す bool show_diag_replace_favorite(); void set_show_diag_replace_favorite( const bool show ); // スレをお気に入りに追加したときにしおりをセットする bool get_bookmark_drop(); // お気に入りの更新チェック時に板の更新もチェックする bool get_check_update_board(); // 起動時にお気に入りを自動でチェックする bool get_check_update_boot(); // お気に入り登録時に重複項目を登録するか ( 0: 登録する 1: ダイアログ表示 2: 登録しない ) int get_check_favorite_dup(); void set_check_favorite_dup( const int check ); // お気に入り登録時に挿入先ダイアログを表示する ( 0 : 表示する 1: 表示せず先頭に追加 2: 表示せず最後に追加 ) int get_show_favorite_select_diag(); // Ctrl+qでウィンドウを閉じない bool get_disable_close(); void set_disable_close( const bool disable ); // メニューバーを非表示にした時にダイアログを表示 bool get_show_hide_menubar_diag(); void set_show_hide_menubar_diag( const bool set ); // 状態変更時にメインステータスバーの色を変える bool get_change_stastatus_color(); // Client-Side Decorationを使うか( 0: 使わない 1: 使う 2: デスクトップに合わせる ) int get_use_header_bar(); // まちBBSの取得に offlaw.cgi を使用する bool get_use_machi_offlaw(); void set_use_machi_offlaw( const bool set ); // 書き込み履歴のあるスレを削除する時にダイアログを表示 bool get_show_del_written_thread_diag(); void set_del_written_thread_diag( const bool set ); // スレを削除する時に画像キャッシュも削除する ( 0: ダイアログ表示 1: 削除 2: 削除しない ) int get_delete_img_in_thread(); void set_delete_img_in_thread( const int set ); //最大表示可能レス数 int get_max_resnumber(); void set_max_resnumber( const int set ); // FIFOの作成などにエラーがあったらダイアログを表示する bool get_show_diag_fifo_error(); void set_show_diag_fifo_error( const bool set ); // 指定した分ごとにセッションを自動保存 (0: 保存しない) int get_save_session(); #ifdef HAVE_MIGEMO_H // migemo-dictの場所 const std::string& get_migemodict_path(); #endif } #endif jdim-0.7.0/src/config/meson.build000066400000000000000000000003661417047150700166520ustar00rootroot00000000000000sources = [ 'aboutconfig.cpp', 'aboutconfigdiag.cpp', 'configitems.cpp', 'globalconf.cpp', ] config_lib = static_library( 'config', [sources, config_h], dependencies : gtkmm_dep, include_directories : include_directories('..'), ) jdim-0.7.0/src/control/000077500000000000000000000000001417047150700147165ustar00rootroot00000000000000jdim-0.7.0/src/control/Makefile.am000066400000000000000000000010301417047150700167440ustar00rootroot00000000000000noinst_LIBRARIES = libcontrol.a libcontrol_a_SOURCES = \ control.cpp \ controlutil.cpp \ mousekeyconf.cpp keyconfig.cpp mouseconfig.cpp buttonconfig.cpp \ mousekeypref.cpp keypref.cpp mousepref.cpp buttonpref.cpp noinst_HEADERS = \ controlid.h \ controllabel.h \ keysyms.h \ control.h \ controlutil.h \ mousekeyconf.h mousekeyitem.h keyconfig.h mouseconfig.h buttonconfig.h \ mousekeypref.h keypref.h mousepref.h buttonpref.h \ defaultconf.h \ get_config.h AM_CXXFLAGS = @GTKMM_CFLAGS@ AM_CPPFLAGS = -I$(top_srcdir)/src jdim-0.7.0/src/control/buttonconfig.cpp000066400000000000000000000174171417047150700201350ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "buttonconfig.h" #include "defaultconf.h" #include "jdlib/confloader.h" #include "jdlib/jdregex.h" #include "cache.h" #ifdef _DEBUG #include "controlutil.h" #endif using namespace CONTROL; ButtonConfig::~ButtonConfig() noexcept = default; // // 設定ファイル読み込み // void ButtonConfig::load_conf() { JDLIB::ConfLoader cf( CACHE::path_buttonconf(), std::string() ); // デフォルト動作 load_motions( cf, "ClickButton", BUTTONCONF_ClickButton ); load_motions( cf, "DblClickButton", BUTTONCONF_DblClickButton ); load_motions( cf, "TrpClickButton", BUTTONCONF_TrpClickButton ); load_motions( cf, "CloseTabButton", BUTTONCONF_CloseTabButton ); load_motions( cf, "ReloadTabButton", BUTTONCONF_ReloadTabButton ); load_motions( cf, "AutoScrollButton", BUTTONCONF_AutoScrollButton ); load_motions( cf, "GestureButton", BUTTONCONF_GestureButton ); load_motions( cf, "PopupmenuButton", BUTTONCONF_PopupmenuButton ); load_motions( cf, "DragStartButton", BUTTONCONF_DragStartButton ); load_motions( cf, "TreeRowSelectionButton", BUTTONCONF_TreeRowSelectionButton ); load_motions( cf, "Reload", BUTTONCONF_Reload ); load_motions( cf, "ToggleArticle", BUTTONCONF_ToggleArticle ); load_motions( cf, "Right", BUTTONCONF_Right ); load_motions( cf, "Left", BUTTONCONF_Left ); // BBSLIST用ボタン設定 load_motions( cf, "OpenBoardButton", BUTTONCONF_OpenBoardButton ); load_motions( cf, "OpenBoardTabButton", BUTTONCONF_OpenBoardTabButton ); // BOARD用ボタン設定 load_motions( cf, "OpenArticleButton", BUTTONCONF_OpenArticleButton ); load_motions( cf, "OpenArticleTabButton", BUTTONCONF_OpenArticleTabButton ); load_motions( cf, "ScrollRightBoard", BUTTONCONF_ScrollRightBoard ); load_motions( cf, "ScrollLeftBoard", BUTTONCONF_ScrollLeftBoard ); // ARTICLE用ボタン設定 load_motions( cf, "PopupWarpButton", BUTTONCONF_PopupWarpButton ); load_motions( cf, "ReferResButton", BUTTONCONF_ReferResButton ); load_motions( cf, "BmResButton", BUTTONCONF_BmResButton ); load_motions( cf, "PostedMarkButton", BUTTONCONF_PostedMarkButton ); load_motions( cf, "PopupmenuResButton", BUTTONCONF_PopupmenuResButton ); load_motions( cf, "DrawoutAncButton", BUTTONCONF_DrawoutAncButton ); load_motions( cf, "PopupmenuAncButton", BUTTONCONF_PopupmenuAncButton ); load_motions( cf, "JumpAncButton", BUTTONCONF_JumpAncButton ); load_motions( cf, "PopupIDButton", BUTTONCONF_PopupIDButton ); load_motions( cf, "DrawoutIDButton", BUTTONCONF_DrawoutIDButton ); load_motions( cf, "PopupmenuIDButton", BUTTONCONF_PopupmenuIDButton ); load_motions( cf, "OpenImageButton", BUTTONCONF_OpenImageButton ); load_motions( cf, "OpenBackImageButton", BUTTONCONF_OpenBackImageButton ); load_motions( cf, "PopupmenuImageButton", BUTTONCONF_PopupmenuImageButton ); load_motions( cf, "OpenBeButton", BUTTONCONF_OpenBeButton ); load_motions( cf, "PopupmenuBeButton", BUTTONCONF_PopupmenuBeButton ); // IMAGE ICON用ボタン設定 load_motions( cf, "CloseImageTabButton", BUTTONCONF_CloseImageTabButton ); // IMAGE用ボタン設定 load_motions( cf, "CloseImageButton", BUTTONCONF_CloseImageButton ); load_motions( cf, "ScrollImageButton", BUTTONCONF_ScrollImageButton ); load_motions( cf, "CancelMosaicButton", BUTTONCONF_CancelMosaicButton ); load_motions( cf, "SaveImageButton", BUTTONCONF_SaveImageButton ); load_motions( cf, "ResizeImageButton", BUTTONCONF_ResizeImageButton ); } // ひとつの操作をデータベースに登録 void ButtonConfig::set_one_motion_impl( const int id, const int mode, const std::string& name, const std::string& str_motion ) { if( name.empty() ) return; #ifdef _DEBUG std::cout << "ButtonConfig::set_one_motion_impl " << name << std::endl; std::cout << "motion = " << str_motion << std::endl; #endif #ifdef _DEBUG std::cout << CONTROL::get_label( id ) << std::endl; #endif bool ctrl = false; bool shift = false; bool alt = false; bool dblclick = false; bool trpclick = false; guint motion = 0; JDLIB::Regex regex; const size_t offset = 0; const bool icase = true; // 大文字小文字区別しない const bool newline = true; const bool usemigemo = false; const bool wchar = false; if( regex.exec( "(Ctrl)?(\\+?Shift)?(\\+?Alt)?\\+?(.*)", str_motion, offset, icase, newline, usemigemo, wchar ) ){ if( regex.length( 1 ) ) ctrl = true; if( regex.length( 2 ) ) shift = true; if( regex.length( 3 ) ) alt = true; std::string str_button = regex.str( 4 ); if( str_button == "Left" ) motion = 1; if( str_button == "Mid" ) motion = 2; if( str_button == "Right" ) motion = 3; if( str_button == "Tilt_Left" ) motion = 6; if( str_button == "Tilt_Right" ) motion = 7; if( str_button == "Button4" ) motion = 8; if( str_button == "Button5" ) motion = 9; if( str_button == "DblLeft" ){ motion = 1; dblclick = true; } if( str_button == "DblMid" ) { motion = 2; dblclick = true; } if( str_button == "DblRight" ) { motion = 3; dblclick = true; } if( str_button == "TrpLeft" ){ motion = 1; trpclick = true; } if( str_button == "TrpMid" ) { motion = 2; trpclick = true; } if( str_button == "TrpRight" ) { motion = 3; trpclick = true; } } else return; #ifdef _DEBUG std::cout << "motion = " << motion << " dblclick = " << dblclick << " trpclick = " << trpclick << std::endl << std::endl; #endif // データベース登録 vec_items().push_back( MouseKeyItem( id, mode, name, str_motion, motion, ctrl, shift, alt, dblclick, trpclick ) ); } // タブで開くボタンを入れ替えているか bool ButtonConfig::is_toggled_tab_button() const { const bool ret = ( get_str_motions( CONTROL::OpenBoardButton ).find( "Mid" ) != std::string::npos && get_str_motions( CONTROL::OpenBoardTabButton ).find( "Left" ) != std::string::npos && get_str_motions( CONTROL::OpenArticleButton ).find( "Mid" ) != std::string::npos && get_str_motions( CONTROL::OpenArticleTabButton ).find( "Left" ) != std::string::npos ); #ifdef _DEBUG std::cout << "KeyConfig::is_toggled_tab_button ret = " << ret << std::endl; #endif return ret; } // タブで開くボタンを入れ替える // toggle == true なら左ボタンをタブで開くボタンにする void ButtonConfig::toggle_tab_button( const bool toggle ) { remove_motions( CONTROL::OpenBoardButton ); remove_motions( CONTROL::OpenBoardTabButton ); remove_motions( CONTROL::OpenArticleButton ); remove_motions( CONTROL::OpenArticleTabButton ); if( toggle ){ set_one_motion( "OpenBoardButton", "Mid" ); set_one_motion( "OpenBoardTabButton", "Left" ); set_one_motion( "OpenArticleButton", "Mid" ); set_one_motion( "OpenArticleTabButton", "Left" ); } else{ set_one_motion( "OpenBoardButton", "Left" ); set_one_motion( "OpenBoardTabButton", "Mid" ); set_one_motion( "OpenArticleButton", "Left" ); set_one_motion( "OpenArticleTabButton", "Mid" ); } } // ポップアップ表示の時にクリックでワープするか bool ButtonConfig::is_popup_warpmode() const { return ( get_str_motions( CONTROL::PopupWarpButton).find( "Left" ) != std::string::npos ); } // ポップアップ表示の時にクリックでワープする void ButtonConfig::toggle_popup_warpmode() { bool warp = is_popup_warpmode(); remove_motions( CONTROL::PopupWarpButton ); if( warp ) set_one_motion( "PopupWarpButton", "" ); else set_one_motion( "PopupWarpButton", "Left" ); } jdim-0.7.0/src/control/buttonconfig.h000066400000000000000000000017601417047150700175740ustar00rootroot00000000000000// ライセンス: GPL2 // // マウスボタン設定 // #ifndef _BUTTONCONFIG_H #define _BUTTONCONFIG_H #include "mousekeyconf.h" namespace CONTROL { class ButtonConfig : public MouseKeyConf { public: using MouseKeyConf::MouseKeyConf; ~ButtonConfig() noexcept; void load_conf() override; bool is_toggled_tab_button() const; // タブで開くボタンを入れ替えているか void toggle_tab_button( const bool toggle ); // タブで開くボタンを入れ替える bool is_popup_warpmode() const; // ポップアップ表示の時にクリックでワープするか void toggle_popup_warpmode(); // ポップアップ表示の時にクリックでワープする private: // ひとつの操作をデータベースに登録 void set_one_motion_impl( const int id, const int mode, const std::string& name, const std::string& str_motion ) override; }; } #endif jdim-0.7.0/src/control/buttonpref.cpp000066400000000000000000000112041417047150700176100ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "buttonpref.h" #include "controlid.h" #include "controlutil.h" using namespace CONTROL; // // ボタン入力をラベルに表示するダイアログ // ButtonInputDiag::ButtonInputDiag( Gtk::Window* parent, const std::string& url, const int id ) : CONTROL::InputDiag( parent, url, id, "マウスボタン", INPUTDIAG_MODE_BUTTON ) {} /////////////////////////////// // // 個別のボタン設定ダイアログ // ButtonDiag::ButtonDiag( Gtk::Window* parent, const std::string& url, const int id, const std::string& str_motions ) : CONTROL::MouseKeyDiag( parent, url, id, "マウスボタン", str_motions ) {} std::unique_ptr ButtonDiag::create_inputdiag() { return std::make_unique( this, "", get_id() ); } std::string ButtonDiag::get_default_motions( const int id ) { return CONTROL::get_default_buttonmotions( id ); } std::vector< int > ButtonDiag::check_conflict( const int mode, const std::string& str_motion ) { // 衝突判定をしない return {}; } /////////////////////////////////////////////// // // マウスボタン設定ダイアログ // ButtonPref::ButtonPref( Gtk::Window* parent, const std::string& url ) : MouseKeyPref( parent, url, "マウスボタン" ) { // マウスボタンのバックアップを取る // キャンセルを押したら戻す CONTROL::bkup_buttonconfig(); append_comment_row( "■ "+ CONTROL::get_mode_label( CONTROL::MODE_COMMON ) ); append_row( CONTROL::ClickButton ); append_row( CONTROL::DblClickButton ); append_row( CONTROL::TrpClickButton ); append_row( CONTROL::CloseTabButton ); append_row( CONTROL::ReloadTabButton ); append_row( CONTROL::AutoScrollButton ); append_row( CONTROL::GestureButton ); append_row( CONTROL::PopupmenuButton ); append_row( CONTROL::DragStartButton ); append_row( CONTROL::TreeRowSelectionButton ); append_row( CONTROL::Reload ); append_row( CONTROL::ToggleArticle ); append_row( CONTROL::Right ); append_row( CONTROL::Left ); append_comment_row( "" ); append_comment_row( "■ "+ CONTROL::get_mode_label( CONTROL::MODE_BBSLIST ) ); append_row( CONTROL::OpenBoardButton ); append_row( CONTROL::OpenBoardTabButton ); append_comment_row( "" ); append_comment_row( "■ "+ CONTROL::get_mode_label( CONTROL::MODE_BOARD ) ); append_row( CONTROL::OpenArticleButton ); append_row( CONTROL::OpenArticleTabButton ); append_row( CONTROL::ScrollRightBoard ); append_row( CONTROL::ScrollLeftBoard ); append_comment_row( "" ); append_comment_row( "■ "+ CONTROL::get_mode_label( CONTROL::MODE_ARTICLE ) ); append_row( CONTROL::PopupWarpButton ); append_row( CONTROL::ReferResButton ); append_row( CONTROL::BmResButton ); append_row( CONTROL::PostedMarkButton ); append_row( CONTROL::PopupmenuResButton ); append_row( CONTROL::DrawoutAncButton ); append_row( CONTROL::PopupmenuAncButton ); append_row( CONTROL::JumpAncButton ); append_row( CONTROL::PopupIDButton ); append_row( CONTROL::DrawoutIDButton ); append_row( CONTROL::PopupmenuIDButton ); append_row( CONTROL::OpenImageButton ); append_row( CONTROL::OpenBackImageButton ); append_row( CONTROL::PopupmenuImageButton ); append_row( CONTROL::OpenBeButton ); append_row( CONTROL::PopupmenuBeButton ); append_comment_row( "" ); append_comment_row( "■ "+ CONTROL::get_mode_label( CONTROL::MODE_IMAGEVIEW ) ); append_row( CONTROL::CloseImageTabButton ); append_row( CONTROL::CloseImageButton ); append_row( CONTROL::ScrollImageButton ); append_row( CONTROL::CancelMosaicButton ); append_row( CONTROL::SaveImageButton ); append_row( CONTROL::ResizeImageButton ); } std::unique_ptr ButtonPref::create_setting_diag( const int id, const std::string& str_motions ) { return std::make_unique( this, "", id, str_motions ); } std::string ButtonPref::get_str_motions( const int id ) { return CONTROL::get_str_buttonmotions( id ); } std::string ButtonPref::get_default_motions( const int id ) { return CONTROL::get_default_buttonmotions( id ); } void ButtonPref::set_motions( const int id, const std::string& str_motions ) { CONTROL::set_buttonmotions( id, str_motions ); } bool ButtonPref::remove_motions( const int id ) { return CONTROL::remove_buttonmotions( id ); } // // キャンセルボタンを押した // void ButtonPref::slot_cancel_clicked() { #ifdef _DEBUG std::cout << "ButtonPref::slot_cancel_clicked\n"; #endif // 設定を戻す CONTROL::restore_buttonconfig(); } jdim-0.7.0/src/control/buttonpref.h000066400000000000000000000036541417047150700172670ustar00rootroot00000000000000// ライセンス: GPL2 // マウスボタン設定ダイアログ // ButtonPref が本体で、ButtonPrefの各行をダブルクリックすると ButtonDiag が開いて個別に操作の設定が出来る // ButtonDiag の各行をダブルクリックすると ButtonInputDiag が開いてキー入力が出来る #ifndef _BUTTONPREFPREF_H #define _BUTTONPREFPREF_H #include "mousekeypref.h" #include "control.h" namespace CONTROL { // // ボタン入力をラベルに表示するダイアログ // class ButtonInputDiag : public CONTROL::InputDiag { public: ButtonInputDiag( Gtk::Window* parent, const std::string& url, const int id ); }; /////////////////////////////////////// // // 個別のボタン設定ダイアログ // class ButtonDiag : public CONTROL::MouseKeyDiag { public: ButtonDiag( Gtk::Window* parent, const std::string& url, const int id, const std::string& str_motions ); protected: std::unique_ptr create_inputdiag() override; std::string get_default_motions( const int id ) override; std::vector< int > check_conflict( const int mode, const std::string& str_motion ) override; }; /////////////////////////////////////// // // ボタン設定ダイアログ // class ButtonPref : public CONTROL::MouseKeyPref { public: ButtonPref( Gtk::Window* parent, const std::string& url ); protected: std::unique_ptr create_setting_diag( const int id, const std::string& str_motions ) override; std::string get_str_motions( const int id ) override; std::string get_default_motions( const int id ) override; void set_motions( const int id, const std::string& str_motions ) override; bool remove_motions( const int id ) override; private: void slot_cancel_clicked() override; }; } #endif jdim-0.7.0/src/control/control.cpp000066400000000000000000000261101417047150700171020ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "control.h" #include "controlid.h" #include "controlutil.h" #include "get_config.h" #include "keyconfig.h" #include "mouseconfig.h" #include "buttonconfig.h" #include "config/globalconf.h" #include "command.h" #include "global.h" using namespace CONTROL; // ホイールマウスジェスチャ用( 全てのビューで共通 ) bool mg_wheel_done; Control::Control() : m_send_mg_info( true ) { MG_reset(); MG_wheel_reset(); clear_mode(); } void Control::add_mode( const int mode ) { // 共通モードは最後に検索 if( mode == CONTROL::MODE_COMMON ) return; m_mode[ m_mode.size()-1 ] = mode; m_mode.push_back( CONTROL::MODE_COMMON ); #ifdef _DEBUG std::cout << "Control::add_mode size = " << m_mode.size() << std::endl; for( const int m : m_mode ) std::cout << m << std::endl; #endif } void Control::clear_mode() { m_mode.clear(); m_mode.push_back( CONTROL::MODE_COMMON ); } /////////////////////////////////////// // // キー操作 // 戻り値はコントロールID int Control::key_press( const GdkEventKey* event ) { guint key = event->keyval; const bool ctrl = ( event->state ) & GDK_CONTROL_MASK; bool shift = ( event->state ) & GDK_SHIFT_MASK; const bool alt = ( event->state ) & GDK_MOD1_MASK; const bool caps = ( event->state ) & GDK_LOCK_MASK; // caps lock const bool dblclick = false; const bool trpclick = false; // caps lockされている場合は、アスキー文字を大文字小文字入れ替えて、capsを無視する // InputDiag::on_key_press_event()も参照のこと if( caps ){ if( key >= 'A' && key <= 'Z' ){ key += 'a' - 'A'; } else if( key >= 'a' && key <= 'z' ){ key += 'A' - 'a'; } } // keyがアスキー文字の場合は shift を無視する (大文字除く) // KeyConfig::set_one_motion()も参照せよ if( CONTROL::is_ascii( key ) && ( key < 'A' || key > 'Z' ) ) shift = false; #ifdef _DEBUG std::cout << "Control::key_press key = " << std::hex << key << std::dec; if( ctrl ) std::cout << " ctrl"; if( shift ) std::cout << " shift"; if( alt ) std::cout << " alt"; if( caps ) std::cout << " caps"; std::cout << "\n"; #endif int control = CONTROL::None; for( const int mode : m_mode ) { control = CONTROL::get_keyconfig()->get_id( mode, key, ctrl, shift, alt, dblclick, trpclick ); if( control != CONTROL::None ) break; } return control; } /////////////////////////////////////// // // マウスのボタン操作 // 戻り値はコントロールID int Control::button_press( const GdkEventButton* event ) { const guint button = event->button; const bool ctrl = ( event->state ) & GDK_CONTROL_MASK; const bool shift = ( event->state ) & GDK_SHIFT_MASK; const bool alt = ( event->state ) & GDK_MOD1_MASK; const bool dblclick = ( event->type == GDK_2BUTTON_PRESS ); const bool trpclick = ( event->type == GDK_3BUTTON_PRESS ); int control = CONTROL::None; for( const int mode : m_mode ) { control = CONTROL::get_buttonconfig()->get_id( mode, button, ctrl, shift, alt, dblclick, trpclick ); if( control != CONTROL::None ) break; } return control; } // eventがidに割り当てられていたらtrue bool Control::button_alloted( const GdkEventButton* event, const int id ) { const guint button = event->button; const bool ctrl = ( event->state ) & GDK_CONTROL_MASK; const bool shift = ( event->state ) & GDK_SHIFT_MASK; const bool alt = ( event->state ) & GDK_MOD1_MASK; const bool dblclick = ( event->type == GDK_2BUTTON_PRESS ); const bool trpclick = ( event->type == GDK_3BUTTON_PRESS ); return CONTROL::get_buttonconfig()->alloted( id, button, ctrl, shift, alt, dblclick, trpclick ); } // ID からevent取得 bool Control::get_eventbutton( const int id, GdkEventButton& event ) { guint button; bool ctrl; bool shift; bool alt; bool dblclick; bool trpclick; if( CONTROL::get_buttonconfig()->get_motion( id, button, ctrl, shift, alt, dblclick, trpclick ) ){ if( dblclick ) event.type = GDK_2BUTTON_PRESS; if( trpclick ) event.type = GDK_3BUTTON_PRESS; event.button = button; event.state = ( GDK_CONTROL_MASK & ctrl ) | ( GDK_SHIFT_MASK & shift ) | ( GDK_MOD1_MASK & alt ); return true; } return false; } /////////////////////////////////////// // // マウスジェスチャ void Control::MG_reset() { m_mg = false; m_mg_x = 0; m_mg_y = 0; m_mg_value = 0; m_mg_lng = 0; m_mg_direction = std::string(); } bool Control::MG_start( const GdkEventButton* event ) { if( ! CONFIG::get_enable_mg() ) return false; MG_reset(); if( ! button_alloted( event, CONTROL::GestureButton ) ) return false; m_mg = true; m_mg_x = ( int ) event->x; m_mg_y = ( int ) event->y; #ifdef _DEBUG std::cout << "Control::MG_start\n"; #endif return true; } bool Control::MG_motion( const GdkEventMotion* event ) { if( ! m_mg ) return false; if( m_mg_lng >= MAX_MG_LNG ) return false; if( m_mg_direction.empty() ){ if( m_send_mg_info ) CORE::core_set_command( "set_mginfo", "", "■" ); } const int x = ( int ) event->x; const int y = ( int ) event->y; const int dx = x - m_mg_x; const int dy = y - m_mg_y; int direction = 0; std::string str_direction; const int radius = CONFIG::get_mouse_radius(); if( dx < 0 && -dx > radius ){ direction = 4; str_direction = "←"; } else if( dx > 0 && dx > radius ){ direction = 6; str_direction = "→"; } else if( dy < 0 && -dy > radius ){ direction = 8; str_direction = "↑"; } else if( dy > 0 && dy > radius ){ direction = 2; str_direction = "↓"; } #ifdef _DEBUG std::cout << " x = " << x << " y = " << y << " mg_x = " << m_mg_x << " mg_y = " << m_mg_y << " dx = " << dx << " dy = " << dy; #endif if( direction ){ m_mg_x = x; m_mg_y = y; // 方向更新 if( m_mg_value % 10 != direction ){ m_mg_value = m_mg_value * 10 + direction; ++m_mg_lng; m_mg_direction += str_direction; const bool ctrl = false; const bool shift = false; const bool alt = false; const bool dblclick = false; const bool trpclick = false; int control = CONTROL::None; for( const int mode : m_mode ) { control = CONTROL::get_mouseconfig()->get_id( mode, m_mg_value, ctrl, shift, alt, dblclick, trpclick ); if( control != CONTROL::None ) break; } if( m_send_mg_info ) CORE::core_set_command( "set_mginfo", "", "■" + m_mg_direction + CONTROL::get_label( control ) ); } } #ifdef _DEBUG std::cout << " dir = " << direction << " val = " << m_mg_value << std::endl; #endif return true; } // 戻り値はコントロールID int Control::MG_end( const GdkEventButton* event ) { if( ! m_mg ) return None; #ifdef _DEBUG std::cout << "Control::MG_end val = " << m_mg_value << std::endl; #endif const bool ctrl = false; const bool shift = false; const bool alt = false; const bool dblclick = false; const bool trpclick = false; int control = CONTROL::None; for( const int mode : m_mode ) { control = CONTROL::get_mouseconfig()->get_id( mode, m_mg_value, ctrl, shift, alt, dblclick, trpclick ); if( control != CONTROL::None ) break; } std::string str_command = CONTROL::get_label( control ); if( m_mg_lng ){ if( control == CONTROL::None ){ str_command = "Cancel"; control = CONTROL::CancelMG; } m_mg_direction += " " + str_command; if( m_send_mg_info ) CORE::core_set_command( "set_mginfo", "", "■" + m_mg_direction ); } MG_reset(); return control; } /////////////////////////////////////// // // ホイールマウスジェスチャ void Control::MG_wheel_reset() { mg_wheel_done = false; m_wheel_scroll_time = 0; } // ホイールマウスジェスチャ開始 bool Control::MG_wheel_start( const GdkEventButton* event ) { MG_wheel_reset(); if( ! button_alloted( event, CONTROL::GestureButton ) ) return false; #ifdef _DEBUG std::cout << "Control::MG_wheel_start\n"; #endif return true; } // ホイールマウスジェスチャ。 戻り値はコントロールID int Control::MG_wheel_scroll( const GdkEventScroll* event ) { int control = CONTROL::None; const guint direction = event->direction; // あまり速く動かした場合はキャンセル const int time_cancel = 15; // msec const int time_tmp = event->time - m_wheel_scroll_time; if( m_wheel_scroll_time && time_tmp < time_cancel ) return control; m_wheel_scroll_time = event->time; Gdk::ModifierType mask = static_cast< Gdk::ModifierType >( event->state ); int button = 0; GdkEventButton ev = GdkEventButton(); get_eventbutton( CONTROL::GestureButton, ev ); switch( ev.button ){ case 1: button = Gdk::BUTTON1_MASK; break; case 2: button = Gdk::BUTTON2_MASK; break; case 3: button = Gdk::BUTTON3_MASK; break; } const bool ctrl = false; const bool shift = false; const bool alt = false; const bool dblclick = false; const bool trpclick = false; if( direction == GDK_SCROLL_LEFT ){ button = 6; for( const int mode : m_mode ) { control = CONTROL::get_buttonconfig()->get_id( mode, button, ctrl, shift, alt, dblclick, trpclick ); if( control != CONTROL::None ) break; } } else if( direction == GDK_SCROLL_RIGHT ){ button = 7; for( const int mode : m_mode ) { control = CONTROL::get_buttonconfig()->get_id( mode, button, ctrl, shift, alt, dblclick, trpclick ); if( control != CONTROL::None ) break; } } else if( ( mask & button ) && direction == GDK_SCROLL_UP ) control = CONTROL::TabLeft; else if( ( mask & button ) && direction == GDK_SCROLL_DOWN ) control = CONTROL::TabRight; else if( ( mask & button ) && direction == GDK_SCROLL_SMOOTH ) { if( event->delta_y < 0.0 ) { control = CONTROL::TabLeft; } else if( event->delta_y > 0.0 ) { control = CONTROL::TabRight; } } #ifdef _DEBUG std::cout << "Control::MG_wheel_scroll control = " << control << std::endl; #endif if( control != CONTROL::None ){ if( m_send_mg_info ) CORE::core_set_command( "set_mginfo", "", CONTROL::get_label( control ) ); mg_wheel_done = true; } return control; } // ホイールマウスジェスチャ終了 // もしジェスチャが実行されたら true が戻る bool Control::MG_wheel_end( const GdkEventButton* event ) { #ifdef _DEBUG std::cout << "Control::MG_wheel_end\n"; #endif const bool ret = mg_wheel_done; MG_wheel_reset(); return ret; } jdim-0.7.0/src/control/control.h000066400000000000000000000041031417047150700165450ustar00rootroot00000000000000// ライセンス: GPL2 // // 入力コントロール // // キー入力やマウスジェスチャ管理 // #ifndef _CONTROL_H #define _CONTROL_H #include #include namespace CONTROL { class Control { std::vector< int > m_mode; // マウスジェスチャ用変数 bool m_mg; // true ならマウスジェスチャのモードになっている bool m_send_mg_info; // core にマウスジェスチャを送るか int m_mg_lng; int m_mg_x; int m_mg_y; int m_mg_value; std::string m_mg_direction; guint32 m_wheel_scroll_time; // 前回ホイールを回した時刻 public: Control(); // コントロールモード設定 void add_mode( const int mode ); void clear_mode(); // キー入力 // 戻り値はコントロールID int key_press( const GdkEventKey* event ); // マウスボタン int button_press( const GdkEventButton* event ); // 戻り値はコントロールID bool button_alloted( const GdkEventButton* event, const int id ); // eventがidに割り当てられていたらtrue bool get_eventbutton( const int id, GdkEventButton& event ); // ID からevent取得 // マウスジェスチャ void MG_reset(); bool is_mg_mode() const { return m_mg; } void set_send_mg_info( const bool send ){ m_send_mg_info = send; } const std::string& get_mg_direction() const{ return m_mg_direction; } bool MG_start( const GdkEventButton* event ); bool MG_motion( const GdkEventMotion* event ); int MG_end( const GdkEventButton* event ); // 戻り値はコントロールID // ホイールマウスジェスチャ void MG_wheel_reset(); bool MG_wheel_start( const GdkEventButton* event ); int MG_wheel_scroll( const GdkEventScroll* event ); // 戻り値はコントロールID bool MG_wheel_end( const GdkEventButton* event ); // ジェスチャを実行したらtrueを返す }; } #endif jdim-0.7.0/src/control/controlid.h000066400000000000000000000130111417047150700170600ustar00rootroot00000000000000// ライセンス: GPL2 // コントロールID #ifndef _CONTROLID_H #define _CONTROLID_H namespace CONTROL { // コントロールモード // // 項目を増やしたら controllabel.h も修正すること // enum { MODE_START = 0, MODE_COMMON = MODE_START, MODE_BBSLIST, MODE_BOARD, MODE_ARTICLE, MODE_IMAGEICON, MODE_IMAGEVIEW, MODE_MESSAGE, MODE_EDIT, MODE_JDGLOBALS, MODE_END = MODE_JDGLOBALS, MODE_ERROR }; // 動作 // // 項目を増やしたら controllabel.h も修正すること // enum { // 共通 COMMONMOTION = 0, Up, Down, Right, Left, TabRight, TabLeft, TabRightUpdated, TabLeftUpdated, TabNum1, TabNum2, TabNum3, TabNum4, TabNum5, TabNum6, TabNum7, TabNum8, TabNum9, CloseAllTabs, CloseOtherTabs, RestoreLastTab, CheckUpdateTabs, PreBookMark, NextBookMark, PrevView, NextView, ToggleArticle, ShowPopupMenu, ShowMenuBar, ShowToolBarMain, ShowSideBar, PageUp, PageDown, PrevDir, NextDir, Home, End, Back, Undo, Redo, Quit, Save, SaveDat, // alias: Save Delete, Reload, ReloadArticle, StopLoading, Cancel = StopLoading, OpenURL, Copy, SelectAll, AppendFavorite, Lock, PreferenceView, PreferenceBoard, // alias: PreferenceView PreferenceArticle, // alias: PreferenceView PreferenceImage, // alias: PreferenceView Search, CloseSearchBar, HiLightOff, SearchInvert, SearchNext, SearchPrev, SearchTitle, DrawOutAnd, DrawOutOr, CheckUpdateRoot, CheckUpdateOpenRoot, QuitJD, MaximizeMainWin, IconifyMainWin, FullScreen, ClickButton, // 以下、マウスボタン専用の設定 DblClickButton, TrpClickButton, CloseTabButton, ReloadTabButton, AutoScrollButton, GestureButton, PopupmenuButton, DragStartButton, TreeRowSelectionButton, COMMONMOTION_END, // BBSLIST系 BBSLISTMOTION, OpenBoard, OpenBoardTab, OpenBoardButton, // 以下、マウスボタン専用の設定 OpenBoardTabButton, BBSLISTMOTION_END, // BOARD系 BOARDMOTION, OpenArticle, OpenArticleTab, NewArticle, SearchCache, ScrollLeftBoard, ScrollRightBoard, SortColumnMark, SortColumnID, SortColumnBoard, SortColumnSubject, SortColumnRes, SortColumnStrLoad, SortColumnStrNew, SortColumnSince, SortColumnWrite, SortColumnAccess, SortColumnSpeed, SortColumnDiff, OpenArticleButton, // 以下、マウスボタン専用の設定 OpenArticleTabButton, BOARDMOTION_END, // ARTICLE系 ARTICLEMOTION, UpMid, UpFast, DownMid, DownFast, PrevRes, NextRes, PrePost, NextPost, GotoNew, OpenParentBoard, WriteMessage, LiveStartStop, SearchNextArticle, SearchWeb, SearchCacheLocal, SearchCacheAll, ShowSelectImage, DeleteSelectImage, AboneSelectImage, AboneSelectionRes, PopupWarpButton, // 以下、マウスボタン専用の設定 ReferResButton, BmResButton, PostedMarkButton, PopupmenuResButton, DrawoutAncButton, PopupmenuAncButton, JumpAncButton, PopupIDButton, DrawoutIDButton, PopupmenuIDButton, OpenImageButton, OpenBackImageButton, PopupmenuImageButton, OpenBeButton, PopupmenuBeButton, ARTICLEMOTION_END, // IMAGE ICON 系 IMAGEICONMOTION, CancelMosaic, ZoomFitImage, ZoomInImage, ZoomOutImage, OrgSizeImage, ScrollUpImage, ScrollDownImage, ScrollLeftImage, ScrollRightImage, CloseImageTabButton, // 以下、マウスボタン専用の設定 IMAGEICONMOTION_END, // IMAGE VIEW 系 IMAGEVIEWMOTION, CloseImageButton, // 以下、マウスボタン専用の設定 ScrollImageButton, CancelMosaicButton, SaveImageButton, ResizeImageButton, IMAGEVIEWMOTION_END, // MESSAGE 系 MESSAGEMOTION, CancelWrite, ExecWrite, InsertText, LockMessage, Preview, FocusWrite, ToggleSage, MESSAGEMOTION_END, // EDIT 系 EDITMOTION, HomeEdit, EndEdit, UpEdit, DownEdit, RightEdit, LeftEdit, DeleteEdit, BackspEdit, UndoEdit, EnterEdit, InputAA, EDITMOTION_END, // JD globals JDGLOBALS, JDExit, JDHelp, JDGLOBALS_END, // その他 CancelMG, None, CONTROL_END }; } #endif jdim-0.7.0/src/control/controllabel.h000066400000000000000000000247201417047150700175540ustar00rootroot00000000000000// ライセンス: GPL2 // コントロールのラベル #ifndef _CONTROLLABEL_H #define _CONTROLLABEL_H #include "controlid.h" #include "global.h" enum { MAX_CONTROL_LABEL = 64 }; namespace CONTROL { // // モード名 // char mode_label[][ MAX_CONTROL_LABEL ] ={ "共通", "板一覧/お気に入り", "スレ一覧", "スレビュー", "画像ビュー", "画像ビュー", "書き込みビュー", "編集", "特殊キー (再起動が必要)" }; // // control_label[ id ][ 0 ] を操作名 ( 例えば "Up" ) // control_label[ id ][ 1 ] をラベル ( 例えば "上移動" ) // // と呼ぶことにする // char control_label[][ 2 ][ MAX_CONTROL_LABEL ]={ // 共通 { "", "" }, { "Up", "上移動" }, { "Down", "下移動" }, { "Right", "右移動" }, { "Left", "左移動" }, { "TabRight", "右のタブに移動" }, { "TabLeft", "左のタブに移動" }, { "TabRightUpdated", "右の更新済みタブに移動" }, { "TabLeftUpdated", "左の更新済みタブに移動" }, { "TabNum1", "タブ1に移動" }, { "TabNum2", "タブ2に移動" }, { "TabNum3", "タブ3に移動" }, { "TabNum4", "タブ4に移動" }, { "TabNum5", "タブ5に移動" }, { "TabNum6", "タブ6に移動" }, { "TabNum7", "タブ7に移動" }, { "TabNum8", "タブ8に移動" }, { "TabNum9", "タブ9に移動" }, { "CloseAllTabs", "全てのタブを閉じる" }, { "CloseOtherTabs", "他のタブを閉じる" }, { "RestoreLastTab", "最後に閉じたタブを復元" }, { "CheckUpdateTabs", "全ての更新されたタブを再読み込み" }, { "PreBookMark", "前のしおりヘ移動" }, { "NextBookMark", "次のしおりヘ移動" }, { "PrevView", ITEM_NAME_BACK }, { "NextView", ITEM_NAME_FORWARD }, { "ToggleArticle", "スレ一覧とスレビュー切替" }, { "ShowPopupMenu", "メニュー表示" }, { "ShowMenuBar", "メニューバー表示" }, { "ShowToolBarMain", "メインツールバー表示" }, { "ShowSideBar", "サイドバー表示" }, { "PageUp", "上のページに移動" }, { "PageDown", "下のページに移動" }, { "PrevDir", "前のディレクトリに移動" }, { "NextDir", "次のディレクトリに移動" }, { "Home", "先頭へ移動" }, { "End", "最後へ移動" }, { "Back", "戻る" }, { "Undo", ITEM_NAME_UNDO }, { "Redo", ITEM_NAME_REDO }, { "Quit", ITEM_NAME_QUIT }, { "Save", "名前を付けて保存..." }, { "SaveDat", ITEM_NAME_SAVE_DAT "..." }, { "Delete", ITEM_NAME_DELETE }, { "Reload", ITEM_NAME_RELOAD }, { "ReloadArticle", "元のスレを開く" }, { "StopLoading", ITEM_NAME_STOPLOADING }, { "OpenURL", "URLを開く..." }, { "Copy", ITEM_NAME_COPY }, { "SelectAll", "全て選択" }, { "AppendFavorite", ITEM_NAME_APPENDFAVORITE "..." }, { "Lock", ITEM_NAME_LOCK }, { "PreferenceView", ITEM_NAME_PREFERENCEVIEW "..." }, { "PreferenceBoard", ITEM_NAME_PREF_BOARD "..." }, { "PreferenceArticle", ITEM_NAME_PREF_THREAD "..." }, { "PreferenceImage", ITEM_NAME_PREF_IMAGE "..." }, { "Search", ITEM_NAME_SEARCH }, { "CloseSearchBar", "検索バーを閉じる" }, { "HiLightOff", ITEM_NAME_CLEAR_HIGHLIGHT }, { "SearchInvert", "前方検索" }, { "SearchNext", ITEM_NAME_SEARCH_NEXT }, { "SearchPrev", ITEM_NAME_SEARCH_PREV }, { "SearchTitle", "" }, // CONTROL::get_keyconfig() で名前をセットする { "DrawOutAnd", "AND 抽出" }, { "DrawOutOr", "OR 抽出" }, { "CheckUpdateRoot", ITEM_NAME_CHECK_UPDATE_ROOT }, { "CheckUpdateOpenRoot", ITEM_NAME_CHECK_UPDATE_OPEN_ROOT }, { "QuitJD", "JDim終了" }, { "MaximizeMainWin", "最大化 / 最大化解除" }, { "IconifyMainWin", "最小化" }, { "FullScreen", "全画面表示" }, { "ClickButton", "クリック" }, { "DblClickButton", "ダブルクリック" }, { "TrpClickButton", "トリプルクリック" }, { "CloseTabButton", "タブを閉じる" }, { "ReloadTabButton", "タブを再読み込み" }, { "AutoScrollButton", "オートスクロール" }, { "GestureButton", "マウスジェスチャ" }, { "PopupmenuButton", "ポップアップメニュー表示" }, { "DragStartButton", "ドラッグ開始" }, { "TreeRowSelectionButton", "行範囲選択" }, { "", "" }, // BBSLIST { "", "" }, { "OpenBoard", "板を開く" }, { "OpenBoardTab", "タブで板を開く" }, { "OpenBoardButton", "板を開く" }, { "OpenBoardTabButton", "タブで板を開く" }, { "", "" }, // BOARD { "", "" }, { "OpenArticle", "スレを開く" }, { "OpenArticleTab", ITEM_NAME_OPENARTICLETAB }, { "NewArticle", ITEM_NAME_NEWARTICLE }, { "SearchCache", "ログ検索" }, { "ScrollLeftBoard", "左スクロール" }, { "ScrollRightBoard", "右スクロール" }, { "SortColumnMark", "ソート(対象: !)" }, { "SortColumnID", "ソート(対象: 番号)" }, { "SortColumnBoard", "ソート(対象: 板)" }, { "SortColumnSubject", "ソート(対象: タイトル)" }, { "SortColumnRes", "ソート(対象: レス)" }, { "SortColumnStrLoad", "ソート(対象: 取得)" }, { "SortColumnStrNew", "ソート(対象: 新着)" }, { "SortColumnSince", "ソート(対象: since)" }, { "SortColumnWrite", "ソート(対象: 最終書込)" }, { "SortColumnAccess", "ソート(対象: 最終取得)" }, { "SortColumnSpeed", "ソート(対象: 速度)" }, { "SortColumnDiff", "ソート(対象: 増分)" }, { "OpenArticleButton", "スレを開く" }, { "OpenArticleTabButton", "タブでスレを開く" }, { "", "" }, // ARTICLE { "", "" }, { "UpMid", "中速上移動" }, { "UpFast", "高速上移動" }, { "DownMid", "中速下移動" }, { "DownFast", "高速下移動" }, { "PrevRes", "前のレスへ移動" }, { "NextRes", "次のレスへ移動" }, { "PrePost", "前の書き込みヘ移動" }, { "NextPost", "次の書き込みヘ移動" }, { "GotoNew", "新着へ移動" }, { "OpenParentBoard", ITEM_NAME_OPENBOARD }, { "WriteMessage", ITEM_NAME_WRITEMSG }, { "LiveStartStop", ITEM_NAME_LIVE }, { "SearchNextArticle", ITEM_NAME_NEXTARTICLE }, { "SearchWeb", "" }, // CONTROL::get_keyconfig() で名前をセットする { "SearchCacheLocal", "ログ検索(対象: 板)" }, { "SearchCacheAll", "ログ検索(対象: 全ログ)" }, { "ShowSelectImage", ITEM_NAME_SELECTIMG }, { "DeleteSelectImage", "削除する" }, { "AboneSelectImage", "あぼ〜んする" }, { "AboneSelectionRes", ITEM_NAME_ABONE_SELECTION }, { "PopupWarpButton", "{X11} クリックで多重ポップアップモードに移行" }, { "ReferResButton", "参照レスポップアップ表示" }, { "BmResButton", "しおりの設定/解除" }, { "PostedMarkButton", "書き込みマークの設定/解除" }, { "PopupmenuResButton", "レス番号メニュー表示" }, { "DrawoutAncButton", "レスの周辺を抽出" }, { "PopupmenuAncButton", "アンカーをクリックでメニュー表示" }, { "JumpAncButton", "アンカーをクリックでジャンプ" }, { "PopupIDButton", "ID抽出ポップアップ表示" }, { "DrawoutIDButton", "ID抽出" }, { "PopupmenuIDButton", "IDメニュー表示" }, { "OpenImageButton", "画像を開く" }, { "OpenBackImageButton", "画像をバックで開く" }, { "PopupmenuImageButton", "画像メニュー表示" }, { "OpenBeButton", "ブラウザでBe表示" }, { "PopupmenuBeButton", "Beメニュー表示" }, { "", "" }, // IMAGE ICON { "", "" }, { "CancelMosaic", "モザイク解除" }, { "ZoomFitImage", "画面に画像サイズを合わせる" }, { "ZoomInImage", "ズームイン" }, { "ZoomOutImage", "ズームアウト" }, { "OrgSizeImage", "元の画像サイズ" }, { "ScrollUpImage", "上スクロール" }, { "ScrollDownImage", "下スクロール" }, { "ScrollLeftImage", "左スクロール" }, { "ScrollRightImage", "右スクロール" }, { "CloseImageTabButton", "タブを閉じる" }, { "", "" }, // IMAGE VIEW { "", "" }, { "CloseImageButton", "画像を閉じる" }, { "ScrollImageButton", "画像スクロール" }, { "CancelMosaicButton", "モザイク解除" }, { "SaveImageButton", "画像保存" }, { "ResizeImageButton", "画像サイズ調整" }, { "", "" }, // MESSAGE { "", "" }, { "CancelWrite", ITEM_NAME_QUIT }, { "ExecWrite", ITEM_NAME_WRITEMSG }, { "InsertText", ITEM_NAME_INSERTTEXT }, { "LockMessage", ITEM_NAME_LOCK_MESSAGE }, { "Preview", ITEM_NAME_PREVIEW }, { "FocusWrite", "書き込みボタンにフォーカスを移す" }, { "ToggleSage", "sageのON/OFF切り替え" }, { "", "" }, // EDIT { "", "" }, { "HomeEdit", "Home" }, { "EndEdit", "End" }, { "UpEdit", "カーソルを上へ移動" }, { "DownEdit", "カーソルを下へ移動" }, { "RightEdit", "カーソルを右へ移動" }, { "LeftEdit", "カーソルを左へ移動" }, { "DeleteEdit", "一文字削除" }, { "BackspEdit", "BackSpace" }, { "UndoEdit", ITEM_NAME_UNDO }, { "EnterEdit", "改行" }, { "InputAA", "アスキーアート入力" }, { "", "" }, // JD globals { "", "" }, { "JDExit", "終了" }, { "JDHelp", "オンラインマニュアル" }, { "", "" }, // その他 { "", "" }, { "", "" }, { "", "" } }; } #endif jdim-0.7.0/src/control/controlutil.cpp000066400000000000000000000616371417047150700200150ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "controlutil.h" #include "get_config.h" #include "controlid.h" #include "controllabel.h" #include "keysyms.h" #include "keyconfig.h" #include "mouseconfig.h" #include "buttonconfig.h" #include "jdlib/miscutil.h" #include "config/globalconf.h" #include "skeleton/admin.h" #include "cache.h" #include "command.h" #include namespace { CONTROL::KeyConfig* instance_keyconfig = nullptr; CONTROL::MouseConfig* instance_mouseconfig = nullptr; CONTROL::ButtonConfig* instance_buttonconfig = nullptr; } // namespace ////////////////////////////////////////////////////////// CONTROL::KeyConfig* CONTROL::get_keyconfig() { if( ! instance_keyconfig ){ instance_keyconfig = new CONTROL::KeyConfig(); // ラベルをセットしておく snprintf( control_label[ CONTROL::SearchWeb ][ 1 ], MAX_CONTROL_LABEL, "%s", CONFIG::get_menu_search_web().c_str() ); snprintf( control_label[ CONTROL::SearchTitle ][ 1 ], MAX_CONTROL_LABEL, "%s", CONFIG::get_menu_search_title().c_str() ); } return instance_keyconfig; } void CONTROL::delete_keyconfig() { delete instance_keyconfig; instance_keyconfig = nullptr; } CONTROL::MouseConfig* CONTROL::get_mouseconfig() { if( ! instance_mouseconfig ) instance_mouseconfig = new CONTROL::MouseConfig(); return instance_mouseconfig; } void CONTROL::delete_mouseconfig() { delete instance_mouseconfig; instance_mouseconfig = nullptr; } CONTROL::ButtonConfig* CONTROL::get_buttonconfig() { if( ! instance_buttonconfig ) instance_buttonconfig = new CONTROL::ButtonConfig(); return instance_buttonconfig; } void CONTROL::delete_buttonconfig() { delete instance_buttonconfig; instance_buttonconfig = nullptr; } void CONTROL::load_conf() { CONTROL::get_keyconfig()->load_conf(); CONTROL::get_mouseconfig()->load_conf(); CONTROL::get_buttonconfig()->load_conf(); } void CONTROL::save_conf() { CONTROL::get_keyconfig()->save_conf( CACHE::path_keyconf() ); CONTROL::get_mouseconfig()->save_conf( CACHE::path_mouseconf() ); CONTROL::get_buttonconfig()->save_conf( CACHE::path_buttonconf() ); } void CONTROL::delete_conf() { CONTROL::delete_keyconfig(); CONTROL::delete_mouseconfig(); CONTROL::delete_buttonconfig(); } ///////////////////////////////////////////////////////// // keysymはアスキー文字か bool CONTROL::is_ascii( const guint keysym ) { if( keysym > 32 && keysym < 127 ) return true; return false; } static void slot_set_menu_motion( Gtk::Widget& widget ) { const auto item = dynamic_cast< Gtk::MenuItem* >( &widget ); auto* const label = dynamic_cast< Gtk::AccelLabel* >( item->get_child() ); if( label ) { #ifdef _DEBUG std::cout << label->get_text() << std::endl; #endif const int id = CONTROL::get_id( label->get_text() ); if( id != CONTROL::None ) { const std::string str_label = CONTROL::get_label_with_mnemonic( id ); std::string str_motions; // CONTROL::get_str_motions()を参考に別個対応のidを処理する if( id == CONTROL::PreferenceArticle || id == CONTROL::PreferenceBoard || id == CONTROL::PreferenceImage ) { label->set_accel( GDK_KEY_p, Gdk::CONTROL_MASK | Gdk::SHIFT_MASK ); str_motions = CONTROL::get_str_mousemotions( CONTROL::PreferenceView ); } else if( id == CONTROL::SaveDat ) { label->set_accel( GDK_KEY_s, Gdk::CONTROL_MASK ); str_motions = CONTROL::get_str_mousemotions( CONTROL::Save ); } else { const auto key = CONTROL::get_accelkey( id ); if( !key.is_null() ) { label->set_accel( key.get_key(), key.get_mod() ); } str_motions = CONTROL::get_str_mousemotions( id ); } label->set_text_with_mnemonic( str_label + ( str_motions.empty() ? "" : "\t" + str_motions ) ); } } if( item->has_submenu() ) { CONTROL::set_menu_motion( item->get_submenu() ); } } // メニューにショートカットキーやマウスジェスチャを表示 void CONTROL::set_menu_motion( Gtk::Menu* menu ) { if( !menu ) return; menu->foreach( &slot_set_menu_motion ); } // IDからモードを取得 // 例えば id == CONTROL::Up の時は CONTROL::COMMONMOTION を返す int CONTROL::get_mode( const int id ) { if( id < CONTROL::COMMONMOTION_END ) return CONTROL::MODE_COMMON; if( id < CONTROL::BBSLISTMOTION_END ) return CONTROL::MODE_BBSLIST; if( id < CONTROL::BOARDMOTION_END ) return CONTROL::MODE_BOARD; if( id < CONTROL::ARTICLEMOTION_END ) return CONTROL::MODE_ARTICLE; if( id < CONTROL::IMAGEICONMOTION_END ) return CONTROL::MODE_IMAGEICON; if( id < CONTROL::IMAGEVIEWMOTION_END ) return CONTROL::MODE_IMAGEVIEW; if( id < CONTROL::MESSAGEMOTION_END ) return CONTROL::MODE_MESSAGE; if( id < CONTROL::EDITMOTION_END ) return CONTROL::MODE_EDIT; if( id < CONTROL::JDGLOBALS_END ) return CONTROL::MODE_JDGLOBALS; return CONTROL::MODE_ERROR; } // 操作モードIDからモード名取得 // 例えば mode == CONTROL::MODE_COMMON の時は "共通" を返す std::string CONTROL::get_mode_label( const int mode ) { if( mode < CONTROL::MODE_START || mode > MODE_END ) return std::string(); return CONTROL::mode_label[ mode ]; } // キー名からkeysymを取得 // 例えば keyname == "Space" の時は GDK_space を返す guint CONTROL::get_keysym( const std::string& keyname ) { #ifdef _DEBUG std::cout << "CONTROL::get_keysym name = " << keyname; #endif if( keyname.empty() ) return 0; const auto it = std::find_if( std::begin( CONTROL::keysyms ), std::end( CONTROL::keysyms ), [&keyname]( const KEYSYMS& ks ) { return ks.keyname == keyname; } ); if( it != std::end( CONTROL::keysyms ) ) { #ifdef _DEBUG std::cout << " found sym = " << it->keysym << std::endl; #endif return it->keysym; } // データベース内に見つからなかったらアスキー文字を返す #ifdef _DEBUG std::cout << " not found sym = " << ( guint )keyname[ 0 ] << std::endl; #endif return keyname[ 0 ]; } // keysymからキー名を取得 // 例えば keysym == GDK_space の時は "Space" を返す std::string CONTROL::get_keyname( const guint keysym ) { #ifdef _DEBUG std::cout << "CONTROL::get_keyname sym = " << keysym; #endif const auto it = std::find_if( std::begin( CONTROL::keysyms ), std::end( CONTROL::keysyms ), [keysym]( const KEYSYMS& ks ) { return ks.keysym == keysym; } ); if( it != std::end( CONTROL::keysyms ) ) { #ifdef _DEBUG std::cout << " found name = " << it->keyname << std::endl; #endif return it->keyname; } #ifdef _DEBUG std::cout << " not found\n"; #endif // データベース内に見つからなかったらアスキー文字を返す if( CONTROL::is_ascii( keysym ) ){ char c[ 2 ]; c[ 0 ] = keysym; c[ 1 ] = '\0'; return std::string( c ); } return std::string(); } // 操作名からID取得 // 例えば name == "Up" の時は CONTROL::Up を返す int CONTROL::get_id( const std::string& name ) { for( int id = CONTROL::COMMONMOTION; id < CONTROL::CONTROL_END; ++id ){ if( name == CONTROL::control_label[ id ][0] ) return id; } return CONTROL::None; } // IDから操作名取得 // 例えば id == CONTROL::Up の時は "Up" を返す std::string CONTROL::get_name( const int id ) { if( id < CONTROL::COMMONMOTION || id >= CONTROL::CONTROL_END ) return std::string(); return CONTROL::control_label[ id ][0]; } // IDからラベル取得 // 例えば id == CONTROL::Up の時は "上移動" を返す std::string CONTROL::get_label( const int id ) { if( id < CONTROL::COMMONMOTION || id >= CONTROL::CONTROL_END ) return std::string(); return CONTROL::control_label[ id ][ 1 ]; } // IDからショートカットを付けたラベルを取得 // 例えば id == CONTROL::Save の時は "名前を付けて保存(_S)..." を返す std::string CONTROL::get_label_with_mnemonic( const int id ) { std::string label = CONTROL::get_label ( id ); const auto pos = label.find( "...", 0); if ( pos != std::string::npos ) { switch ( id ) { case CONTROL::Save: //名前を付けて保存... case CONTROL::SaveDat: //datを保存... label.replace( pos, strlen( "..." ), "(_S)..." ); break; case CONTROL::AppendFavorite: //お気に入りに追加.. label.replace( pos, strlen( "..." ), "(_F)..." ); break; case CONTROL::OpenURL: //URLを開く label.replace( pos, strlen( "..." ), "(_U)..." ); break; case CONTROL::PreferenceView: //プロパティ... case CONTROL::PreferenceArticle: //スレのプロパティ... label.replace( pos, strlen( "..." ), "(_P)..." ); break; case CONTROL::PreferenceBoard: //板のプロパティ... label.replace( pos, strlen( "..." ), "(_O)..." ); break; case CONTROL::PreferenceImage: //画像のプロパティ... label.replace( pos, strlen( "..." ), "(_M)..." ); break; } } else { switch ( id ) { case CONTROL::PreBookMark: //前のブックマークへ移動 label += "(_R)"; break; case CONTROL::NextBookMark: //次のブックマークへ移動 label += "(_X)"; break; case CONTROL::PrevView: //前へ戻る label += "(_P)"; break; case CONTROL::NextView: //次へ進む label += "(_N)"; break; case CONTROL::ShowMenuBar: // メニューバー表示 label += "(_M)"; break; case CONTROL::ShowToolBarMain: // メインツールバー表示 label += "(_M)"; break; case CONTROL::Home: //先頭へ移動 label += "(_H)"; break; case CONTROL::End: //最後へ移動 label += "(_E)"; break; case CONTROL::Quit: //閉じる label += "(_C)"; break; case CONTROL::Delete: //削除 label += "(_D)"; break; case CONTROL::Reload: //再読み込み label += "(_R)"; break; case CONTROL::StopLoading: //読み込み中止 label += "(_T)"; break; case CONTROL::Copy: //コピー label += "(_C)"; break; case CONTROL::Search: //検索 label += "(_S)"; break; case CONTROL::SearchNext: //次検索 label += "(_N)"; break; case CONTROL::SearchPrev: //前検索 label += "(_P)"; break; case CONTROL::OpenArticleTab: // タブでスレを開く label += "(_T)"; break; case CONTROL::GotoNew: //新着へ移動 label += "(_W)"; break; case CONTROL::LiveStartStop: // 実況 label += "(_L)"; break; case CONTROL::SearchNextArticle: // 次スレ検索 label += "(_N)"; break; case CONTROL::SearchWeb: // web検索 label += "(_W)"; break; case CONTROL::SearchTitle: // スレタイ検索 label += "(_T)"; break; case CONTROL::SearchCacheLocal: // ログ検索(対象: 板) label += "(_L)"; break; case CONTROL::SearchCacheAll: // ログ検索(対象: 全て) label += "(_A)"; break; case CONTROL::ShowSelectImage: // 選択範囲の画像を開く label += "(_G)"; break; case CONTROL::DeleteSelectImage: // 選択範囲の画像を削除(サブメニュー) label += "(_D)"; break; case CONTROL::AboneSelectImage: // 選択範囲の画像をあぼ〜ん(サブメニュー) label += "(_A)"; break; case CONTROL::AboneSelectionRes: // 選択範囲のレスをあぼ〜ん label += "(_A)"; break; case CONTROL::CancelMosaic: //モザイク解除 label += "(_M)"; break; case CONTROL::ZoomFitImage: //画面に画像サイズを合わせる label += "(_A)"; break; case CONTROL::ZoomInImage: //ズームイン label += "(_I)"; break; case CONTROL::ZoomOutImage: //ズームアウト label += "(_Z)"; break; case CONTROL::OrgSizeImage: //元の画像サイズ label += "(_N)"; break; case CONTROL::FullScreen: // 全画面表示 label += "(_F)"; break; } } return label; } // IDからキーボードとマウスジェスチャの両方を取得 std::string CONTROL::get_str_motions( const int id ) { int ctl = id; // メニューのラベルとコントロールのIDが異なる、共通コントロールIDの場合は、ここで変換する switch( ctl ){ // 表示中のビューのプロパティを表示 case CONTROL::PreferenceArticle: //スレのプロパティ... case CONTROL::PreferenceBoard: //板のプロパティ... case CONTROL::PreferenceImage: //画像のプロパティ... ctl = CONTROL::PreferenceView; break; //名前を付けて保存... case CONTROL::SaveDat: //datを保存... ctl = CONTROL::Save; break; } std::string str_motion = get_str_keymotions( ctl ); std::string mouse_motion = get_str_mousemotions( ctl ); if( ! mouse_motion.empty() ){ if( !str_motion.empty() ) str_motion += " "; str_motion += "( " + mouse_motion + " )"; } return str_motion; } // IDからラベルと操作の両方を取得 std::string CONTROL::get_label_motions( const int id ) { std::string motion = CONTROL::get_str_motions( id ); return CONTROL::get_label( id ) + ( motion.empty() ? "" : " " ) + motion; } // 共通操作 bool CONTROL::operate_common( const int control, const std::string& url, SKELETON::Admin* admin ) { if( control == CONTROL::None ) return false;; switch( control ){ // 全てのタブを閉じる case CONTROL::CloseAllTabs: if( admin ) admin->set_command( "close_view", "", "closealltabs" ); break; // 他のタブを閉じる case CONTROL::CloseOtherTabs: if( admin ) admin->set_command( "close_view", url, "closeother" ); break; // 最後に閉じたタブを復元 case CONTROL::RestoreLastTab: if( admin ) admin->set_command( "restore_lasttab" ); break; // 全ての更新されたタブを再読み込み case CONTROL::CheckUpdateTabs: if( admin ) admin->set_command( "check_update_reload_all_tabs" ); break; // 右、左のタブに切り替え case CONTROL::TabLeft: if( admin ) admin->set_command( "tab_left" ); break; case CONTROL::TabRight: if( admin ) admin->set_command( "tab_right" ); break; case CONTROL::TabLeftUpdated: if( admin ) admin->set_command( "tab_left_updated" ); break; case CONTROL::TabRightUpdated: if( admin ) admin->set_command( "tab_right_updated" ); break; // タブ位置(1-9)で移動 case CONTROL::TabNum1: if( admin ) admin->set_command( "tab_num", "", "1" ); break; case CONTROL::TabNum2: if( admin ) admin->set_command( "tab_num", "", "2" ); break; case CONTROL::TabNum3: if( admin ) admin->set_command( "tab_num", "", "3" ); break; case CONTROL::TabNum4: if( admin ) admin->set_command( "tab_num", "", "4" ); break; case CONTROL::TabNum5: if( admin ) admin->set_command( "tab_num", "", "5" ); break; case CONTROL::TabNum6: if( admin ) admin->set_command( "tab_num", "", "6" ); break; case CONTROL::TabNum7: if( admin ) admin->set_command( "tab_num", "", "7" ); break; case CONTROL::TabNum8: if( admin ) admin->set_command( "tab_num", "", "8" ); break; case CONTROL::TabNum9: if( admin ) admin->set_command( "tab_num", "", "9" ); break; // サイドバー表示/非表示 case CONTROL::ShowSideBar: CORE::core_set_command( "toggle_sidebar" ); break; // メニューバー表示/非表示 case CONTROL::ShowMenuBar: CORE::core_set_command( "toggle_menubar" ); break; // メインツールバー表示/非表示 case CONTROL::ShowToolBarMain: CORE::core_set_command( "toggle_toolbarmain" ); break; // URLを開くダイアログを表示 case CONTROL::OpenURL: CORE::core_set_command( "show_openurl_diag" ); break; // JD終了 case CONTROL::QuitJD: CORE::core_set_command( "quit_jd" ); break; // 最大化 / 最大化解除 case CONTROL::MaximizeMainWin: CORE::core_set_command( "maximize_mainwin" ); break; // 最小化 case CONTROL::IconifyMainWin: CORE::core_set_command( "iconify_mainwin" ); break; // サイドバー更新チェック case CONTROL::CheckUpdateRoot: CORE::core_set_command( "check_update_root" ); break; case CONTROL::CheckUpdateOpenRoot: CORE::core_set_command( "check_update_open_root" ); break; // 全画面表示 case CONTROL::FullScreen: CORE::core_set_command( "toggle_fullscreen" ); break; // スレタイ検索 case CONTROL::SearchTitle: CORE::core_set_command( "open_article_searchtitle", "", "", "noexec" ); break; default: return false; } return true; } ///////////////////////////////////////////////////////// // キーボード設定の一時的なバックアップと復元 void CONTROL::bkup_keyconfig() { instance_keyconfig->state_backup(); } void CONTROL::restore_keyconfig() { instance_keyconfig->state_restore(); } // IDからキーボード操作を取得 std::string CONTROL::get_str_keymotions( const int id ) { return CONTROL::get_keyconfig()->get_str_motions( id ); } // IDからデフォルトキーボード操作を取得 std::string CONTROL::get_default_keymotions( const int id ) { return CONTROL::get_keyconfig()->get_default_motions( id ); } // スペースで区切られた複数のキーボード操作をデータベースに登録 void CONTROL::set_keymotions( const int id, const std::string& str_motions ) { CONTROL::get_keyconfig()->set_motions( id, str_motions ); } // 指定したIDのキーボード操作を全て削除 bool CONTROL::remove_keymotions( const int id ) { return CONTROL::get_keyconfig()->remove_motions( id ); } // キーボード操作が重複していないか std::vector< int > CONTROL::check_key_conflict( const int mode, const std::string& str_motion ) { return CONTROL::get_keyconfig()->check_conflict( mode, str_motion ); } // editviewの操作をemacs風にする bool CONTROL::is_emacs_mode() { return CONTROL::get_keyconfig()->is_emacs_mode(); } void CONTROL::toggle_emacs_mode() { CONTROL::get_keyconfig()->toggle_emacs_mode(); } // 「タブで開く」キーを入れ替える bool CONTROL::is_toggled_tab_key() { return CONTROL::get_keyconfig()->is_toggled_tab_key(); } void CONTROL::toggle_tab_key( const bool toggle ) { CONTROL::get_keyconfig()->toggle_tab_key( toggle ); } // Gtk アクセラレーションキーを取得 Gtk::AccelKey CONTROL::get_accelkey( const int id ) { return CONTROL::get_keyconfig()->get_accelkey( id ); } ///////////////////////////////////////////////////////// const std::string convert_mouse_motions( std::string motions ) { motions = MISC::replace_str( motions, "8", "↑" ); motions = MISC::replace_str( motions, "6", "→" ); motions = MISC::replace_str( motions, "4", "←" ); motions = MISC::replace_str( motions, "2", "↓" ); return motions; } const std::string convert_mouse_motions_reverse( std::string motions ) { motions = MISC::replace_str( motions, "↑", "8" ); motions = MISC::replace_str( motions, "→", "6" ); motions = MISC::replace_str( motions, "←", "4" ); motions = MISC::replace_str( motions, "↓", "2" ); return motions; } // マウスジェスチャ設定の一時的なバックアップと復元 void CONTROL::bkup_mouseconfig() { instance_mouseconfig->state_backup(); } void CONTROL::restore_mouseconfig() { instance_mouseconfig->state_restore(); } // IDからマウスジェスチャを取得 std::string CONTROL::get_str_mousemotions( const int id ) { return convert_mouse_motions( CONTROL::get_mouseconfig()->get_str_motions( id ) ); } // IDからデフォルトマウスジェスチャを取得 std::string CONTROL::get_default_mousemotions( const int id ) { return convert_mouse_motions( CONTROL::get_mouseconfig()->get_default_motions( id ) ); } // スペースで区切られた複数のマウスジェスチャをデータベースに登録 void CONTROL::set_mousemotions( const int id, const std::string& str_motions ) { const std::string motions = convert_mouse_motions_reverse( str_motions ); CONTROL::get_mouseconfig()->set_motions( id, motions ); } // 指定したIDのマウスジェスチャを全て削除 bool CONTROL::remove_mousemotions( const int id ) { return CONTROL::get_mouseconfig()->remove_motions( id ); } // マウスジェスチャが重複していないか std::vector< int > CONTROL::check_mouse_conflict( const int mode, const std::string& str_motion ) { const std::string motion = convert_mouse_motions_reverse( str_motion ); return CONTROL::get_mouseconfig()->check_conflict( mode, motion ); } ///////////////////////////////////////////////////////// // ボタン設定の一時的なバックアップと復元 void CONTROL::bkup_buttonconfig() { instance_buttonconfig->state_backup(); } void CONTROL::restore_buttonconfig() { instance_buttonconfig->state_restore(); } // IDからボタン設定を取得 std::string CONTROL::get_str_buttonmotions( const int id ) { return CONTROL::get_buttonconfig()->get_str_motions( id ); } // IDからデフォルトボタン設定を取得 std::string CONTROL::get_default_buttonmotions( const int id ) { return CONTROL::get_buttonconfig()->get_default_motions( id ); } // スペースで区切られた複数のボタン設定をデータベースに登録 void CONTROL::set_buttonmotions( const int id, const std::string& str_motions ) { CONTROL::get_buttonconfig()->set_motions( id, str_motions ); } // 指定したIDのボタン設定を全て削除 bool CONTROL::remove_buttonmotions( const int id ) { return CONTROL::get_buttonconfig()->remove_motions( id ); } // ボタンが重複していないか std::vector< int > CONTROL::check_button_conflict( const int mode, const std::string& str_motion ) { return CONTROL::get_buttonconfig()->check_conflict( mode, str_motion ); } ///////////////////////////////////////////////////////// // タブで開くボタンを入れ替える bool CONTROL::is_toggled_tab_button() { return CONTROL::get_buttonconfig()->is_toggled_tab_button(); } void CONTROL::toggle_tab_button( const bool toggle ) { CONTROL::get_buttonconfig()->toggle_tab_button( toggle ); } // ポップアップ表示の時にクリックでワープ bool CONTROL::is_popup_warpmode() { return CONTROL::get_buttonconfig()->is_popup_warpmode(); } void CONTROL::toggle_popup_warpmode() { CONTROL::get_buttonconfig()->toggle_popup_warpmode(); } jdim-0.7.0/src/control/controlutil.h000066400000000000000000000121221417047150700174430ustar00rootroot00000000000000// ライセンス: GPL2 // // コントロール系ユーティリティ関数 // #ifndef _CONTROLUTIL_H #define _CONTROLUTIL_H #include namespace SKELETON { class Admin; } namespace CONTROL { void load_conf(); void save_conf(); void delete_conf(); /////////////////////// // keysymはアスキー文字か bool is_ascii( const guint keysym ); // メニューにショートカットキーやマウスジェスチャを表示 void set_menu_motion( Gtk::Menu* menu ); // IDからモードを取得 // 例えば id == CONTROL::Up の時は CONTROL::COMMONMOTION を返す int get_mode( const int id ); // 操作モードIDからモード名取得 // 例えば mode == CONTROL::MODE_COMMON の時は "共通" を返す std::string get_mode_label( const int mode ); // キー名からkeysymを取得 // 例えば keyname == "Space" の時は GDK_space を返す guint get_keysym( const std::string& keyname ); // keysymからキー名を取得 // 例えば keysym == GDK_space の時は "Space" を返す std::string get_keyname( const guint keysym ); // 操作名からID取得 // 例えば name == "Up" の時は CONTROL::Up を返す int get_id( const std::string& name ); // IDから操作名取得 // 例えば id == CONTROL::Up の時は "Up" を返す std::string get_name( const int id ); // IDからラベル取得 // 例えば id == CONTROL::Up の時は "上移動" を返す std::string get_label( const int id ); // IDからショートカットを付けたラベルを取得 // 例えば id == CONTROL::Save の時は "名前を付けて保存(_S)..." を返す std::string get_label_with_mnemonic( const int id ); // IDからキーボードとマウスジェスチャの両方を取得 std::string get_str_motions( const int id ); // IDからラベルと操作の両方を取得 std::string get_label_motions( const int id ); // 共通操作 bool operate_common( const int control, const std::string& url, SKELETON::Admin* admin ); /////////////////////// // キーボード設定の一時的なバックアップと復元 void bkup_keyconfig(); void restore_keyconfig(); // IDからキーボード操作を取得 std::string get_str_keymotions( const int id ); // IDからデフォルトキーボード操作を取得 std::string get_default_keymotions( const int id ); // スペースで区切られた複数のキーボード操作をデータベースに登録 void set_keymotions( const int id, const std::string& str_motions ); // 指定したIDのキーボード操作を全て削除 bool remove_keymotions( const int id ); // キーボード操作が重複していないか std::vector< int > check_key_conflict( const int mode, const std::string& str_motion ); // editviewの操作をemacs風にする bool is_emacs_mode(); void toggle_emacs_mode(); // 「タブで開く」キーを入れ替える bool is_toggled_tab_key(); void toggle_tab_key( const bool toggle ); // Gtk アクセラレーションキーを取得 Gtk::AccelKey get_accelkey( const int id ); /////////////////////// // マウスジェスチャ設定の一時的なバックアップと復元 void bkup_mouseconfig(); void restore_mouseconfig(); // IDからマウスジェスチャを取得 std::string get_str_mousemotions( const int id ); // IDからデフォルトマウスジェスチャを取得 std::string get_default_mousemotions( const int id ); // スペースで区切られた複数のマウスジェスチャをデータベースに登録 void set_mousemotions( const int id, const std::string& str_motions ); // 指定したIDのマウスジェスチャを全て削除 bool remove_mousemotions( const int id ); // マウスジェスチャが重複していないか std::vector< int > check_mouse_conflict( const int mode, const std::string& str_motion ); /////////////////////// // ボタン設定の一時的なバックアップと復元 void bkup_buttonconfig(); void restore_buttonconfig(); // IDからボタン設定を取得 std::string get_str_buttonmotions( const int id ); // IDからデフォルトボタン設定を取得 std::string get_default_buttonmotions( const int id ); // スペースで区切られた複数のボタン設定をデータベースに登録 void set_buttonmotions( const int id, const std::string& str_motions ); // 指定したIDのボタン設定を全て削除 bool remove_buttonmotions( const int id ); // ボタンが重複していないか std::vector< int > check_button_conflict( const int mode, const std::string& str_motion ); /////////////////////// // タブで開くボタンを入れ替える bool is_toggled_tab_button(); void toggle_tab_button( const bool toggle ); // ポップアップ表示の時にクリックでワープ bool is_popup_warpmode(); void toggle_popup_warpmode(); } #endif jdim-0.7.0/src/control/defaultconf.h000066400000000000000000000176021417047150700173670ustar00rootroot00000000000000// ライセンス: GPL2 // // 設定のデフォルト値 // #ifndef _DEFAULTMOUSEKEYCONF_H #define _DEFAULTMOUSEKEYCONF_H #ifdef HAVE_CONFIG_H #include "config.h" #endif namespace CONTROL { // ショートカットキー #define KEYCONF_Up "k Up KP_Up" #define KEYCONF_Down "j Down KP_Down" #define KEYCONF_Right "l Right KP_Right" #define KEYCONF_Left "h Left KP_left" #define KEYCONF_TabRight "Ctrl+Page_Down Ctrl+Tab Ctrl+Left_Tab Ctrl+l Ctrl+Right" #define KEYCONF_TabLeft "Ctrl+Page_Up Ctrl+Shift+Tab Ctrl+Shift+Left_Tab Ctrl+h Ctrl+Left" #define KEYCONF_TabRightUpdated "Ctrl+Shift+Page_Down Ctrl+L Ctrl+Shift+Right ]" #define KEYCONF_TabLeftUpdated "Ctrl+Shift+Page_Up Ctrl+H Ctrl+Shift+Left [" #define KEYCONF_TabNum1 "Alt+1" #define KEYCONF_TabNum2 "Alt+2" #define KEYCONF_TabNum3 "Alt+3" #define KEYCONF_TabNum4 "Alt+4" #define KEYCONF_TabNum5 "Alt+5" #define KEYCONF_TabNum6 "Alt+6" #define KEYCONF_TabNum7 "Alt+7" #define KEYCONF_TabNum8 "Alt+8" #define KEYCONF_TabNum9 "Alt+9" #define KEYCONF_RestoreLastTab "Ctrl+T" #define KEYCONF_PreBookMark "Ctrl+F2" #define KEYCONF_NextBookMark "F2" #define KEYCONF_PrevView "Alt+Left" #define KEYCONF_NextView "Alt+Right" #define KEYCONF_ToggleArticle "Alt+x" #define KEYCONF_ShowPopupMenu "Shift+F10 Ctrl+m Menu" #define KEYCONF_ShowMenuBar "F8" #define KEYCONF_ShowToolBarMain "" #define KEYCONF_ShowSideBar "F9" #define KEYCONF_PageUp "Page_Up KP_Prior" #define KEYCONF_PageDown "Page_Down KP_Next" #define KEYCONF_PrevDir "{" #define KEYCONF_NextDir "}" #define KEYCONF_Home "Home g < KP_Home" #define KEYCONF_End "End G > KP_End" #define KEYCONF_Back "BackSpace" #define KEYCONF_Undo "Ctrl+/ Ctrl+z" #define KEYCONF_Redo "Ctrl+Z" #define KEYCONF_Quit "Ctrl+w q" #define KEYCONF_Save "Ctrl+s" #define KEYCONF_Delete "Delete KP_Delete" #define KEYCONF_Reload "F5 s" #define KEYCONF_ReloadArticle "Shift+F5 S" #define KEYCONF_StopLoading "Escape" #define KEYCONF_OpenURL "Ctrl+o" #define KEYCONF_Copy "Ctrl+c" #define KEYCONF_SelectAll "Ctrl+a" #define KEYCONF_AppendFavorite "Ctrl+d" #define KEYCONF_PreferenceView "Ctrl+P" #define KEYCONF_Search "Ctrl+f / KP_Divide" #define KEYCONF_SearchInvert "?" #define KEYCONF_SearchNext "Enter F3 Ctrl+g" #define KEYCONF_SearchPrev "Shift+Enter Ctrl+F3 Ctrl+G N" #define KEYCONF_SearchTitle "Ctrl+t" #define KEYCONF_DrawOutAnd "Ctrl+Enter" #define KEYCONF_CheckUpdateRoot "" #define KEYCONF_CheckUpdateOpenRoot "" #define KEYCONF_FullScreen "F11" // BBSLIST #define KEYCONF_OpenBoard "Space" #define KEYCONF_OpenBoardTab "Ctrl+Space" // BOARD #define KEYCONF_OpenArticle "Space" #define KEYCONF_OpenArticleTab "Ctrl+Space" #define KEYCONF_NewArticle "w" #define KEYCONF_SearchCache "Ctrl+Enter" #define KEYCONF_ScrollRightBoard "L Shift+Right" #define KEYCONF_ScrollLeftBoard "H Shift+Left" #define KEYCONF_SortColumnNoDefault "" // ARTICLE #define KEYCONF_UpMid "u" #define KEYCONF_UpFast "b Page_Up KP_Prior" #define KEYCONF_DownMid "d" #define KEYCONF_DownFast "Page_Down Space KP_Next" #define KEYCONF_PrevRes "p" #define KEYCONF_NextRes "n" #define KEYCONF_PrePost "Ctrl+Shift+F2" #define KEYCONF_NextPost "Shift+F2" #define KEYCONF_GotoNew "F4" #define KEYCONF_WriteMessage "w Alt+w" #define KEYCONF_LiveStartStop "F6" #define KEYCONF_SearchNextArticle "Ctrl+Space" #define KEYCONF_SearchWeb "Ctrl+k" #define KEYCONF_SearchCacheLocal "Ctrl+Enter" #define KEYCONF_SearchCacheAll "" #define KEYCONF_ShowSelectImage "Ctrl+I" #define KEYCONF_DeleteSelectImage "" #define KEYCONF_AboneSelectImage "" #define KEYCONF_AboneSelectionRes "" // IMAGE #define KEYCONF_CancelMosaic "c" #define KEYCONF_ZoomFitImage "x" #define KEYCONF_ZoomInImage "+ KP_Add" // (注意) ver.2.0.2 以前は + は Plus だった #define KEYCONF_ZoomOutImage "- KP_Subtract" #define KEYCONF_OrgSizeImage "z" #define KEYCONF_ScrollUpImage "K k Shift+Up Up" #define KEYCONF_ScrollDownImage "J j Shift+Down Down" #define KEYCONF_ScrollLeftImage "H Shift+Left" #define KEYCONF_ScrollRightImage "L Shift+Right" // MESSAGE #define KEYCONF_CancelWrite "Alt+q" #define KEYCONF_ExecWrite "Alt+w" #define KEYCONF_FocusWrite "Tab" #define KEYCONF_ToggleSage "Alt+s" // EDIT #define KEYCONF_HomeEdit "" #define KEYCONF_EndEdit "" #define KEYCONF_UpEdit "" #define KEYCONF_DownEdit "" #define KEYCONF_RightEdit "" #define KEYCONF_LeftEdit "" #define KEYCONF_DeleteEdit "" #define KEYCONF_BackspEdit "" #define KEYCONF_UndoEdit "Ctrl+/ Ctrl+z" #define KEYCONF_EnterEdit "" #define KEYCONF_InputAA "Alt+a" // JD globals #define KEYCONF_JDExit "Ctrl+q" #define KEYCONF_JDHelp "F1" ////////////////////////////////// // マウスジェスチャ // 8 // ↑ // 4 ← → 6 // ↓ // 2 // // ( 例 ) ↑→↓← = 8624 // 共通 #define MOUSECONF_Right "6" #define MOUSECONF_Left "4" #define MOUSECONF_TabRight "86" #define MOUSECONF_TabLeft "84" #define MOUSECONF_TabRightUpdated "" #define MOUSECONF_TabLeftUpdated "" #define MOUSECONF_CloseAllTabs "" #define MOUSECONF_CloseOtherTabs "" #define MOUSECONF_RestoreLastTab "64" #define MOUSECONF_CheckUpdateTabs "" #define MOUSECONF_ToggleArticle "2" #define MOUSECONF_ShowSideBar "" #define MOUSECONF_ShowMenuBar "" #define MOUSECONF_ShowToolBarMain "" #define MOUSECONF_Home "68" #define MOUSECONF_End "62" #define MOUSECONF_Quit "26" #define MOUSECONF_Reload "82" #define MOUSECONF_Delete "262" #define MOUSECONF_StopLoading "8" #define MOUSECONF_AppendFavorite "" #define MOUSECONF_NewArticle "24" #define MOUSECONF_WriteMessage "24" #define MOUSECONF_SearchTitle "" #define MOUSECONF_CheckUpdateRoot "48" #define MOUSECONF_CheckUpdateOpenRoot "42" #define MOUSECONF_QuitJD "" #define MOUSECONF_MaximizeMainWin "" #define MOUSECONF_IconifyMainWin "" // ARTICLE #define MOUSECONF_GotoNew "626" #define MOUSECONF_SearchNextArticle "28" #define MOUSECONF_SearchWeb "" #define MOUSECONF_LiveStartStop "" // IMAGE #define MOUSECONF_CancelMosaicButton "28" ////////////////////////////////// // ボタン割り当て // 共通 #define BUTTONCONF_ClickButton "Left" #define BUTTONCONF_DblClickButton "DblLeft" #define BUTTONCONF_TrpClickButton "TrpLeft" #define BUTTONCONF_CloseTabButton "Mid" #define BUTTONCONF_ReloadTabButton "DblLeft" #define BUTTONCONF_AutoScrollButton "Mid" #define BUTTONCONF_GestureButton "Right" #define BUTTONCONF_PopupmenuButton "Right" #define BUTTONCONF_DragStartButton "Left" #define BUTTONCONF_TreeRowSelectionButton "Mid" #define BUTTONCONF_Reload "Button4" #define BUTTONCONF_ToggleArticle "Button5" #define BUTTONCONF_Right "" #define BUTTONCONF_Left "" // BBSLIST用ボタン設定 #define BUTTONCONF_OpenBoardButton "Left" #define BUTTONCONF_OpenBoardTabButton "Mid" // BOARD用ボタン設定 #define BUTTONCONF_OpenArticleButton "Left" #define BUTTONCONF_OpenArticleTabButton "Mid" #define BUTTONCONF_ScrollRightBoard "Tilt_Right" #define BUTTONCONF_ScrollLeftBoard "Tilt_Left" // ARTICLE用ボタン設定 #define BUTTONCONF_PopupWarpButton "" #define BUTTONCONF_ReferResButton "Right" #define BUTTONCONF_BmResButton "Mid" #define BUTTONCONF_PostedMarkButton "Ctrl+Left" #define BUTTONCONF_PopupmenuResButton "Left" #define BUTTONCONF_DrawoutAncButton "Mid" #define BUTTONCONF_PopupmenuAncButton "Left Right" #define BUTTONCONF_JumpAncButton "" #define BUTTONCONF_PopupIDButton "Right" #define BUTTONCONF_DrawoutIDButton "Mid" #define BUTTONCONF_PopupmenuIDButton "Left" #define BUTTONCONF_OpenImageButton "Left" #define BUTTONCONF_OpenBackImageButton "Mid Ctrl+Left" #define BUTTONCONF_PopupmenuImageButton "Right" #define BUTTONCONF_OpenBeButton "Left Mid" #define BUTTONCONF_PopupmenuBeButton "Right" // IMAGE ICON用ボタン設定 #define BUTTONCONF_CloseImageTabButton "Mid" // IMAGE用ボタン設定 #define BUTTONCONF_CloseImageButton "Mid" #define BUTTONCONF_ScrollImageButton "Left" #define BUTTONCONF_CancelMosaicButton "" #define BUTTONCONF_SaveImageButton "" #define BUTTONCONF_ResizeImageButton "Left" } #endif jdim-0.7.0/src/control/get_config.h000066400000000000000000000006461417047150700172010ustar00rootroot00000000000000// ライセンス: GPL2 // // KeyConfig, MouseConfig, ButtonConfig 取得 // #ifndef _GET_CONFIG_H #define _GET_CONFIG_H namespace CONTROL { class KeyConfig; class MouseConfig; class ButtonConfig; KeyConfig* get_keyconfig(); MouseConfig* get_mouseconfig(); ButtonConfig* get_buttonconfig(); void delete_keyconfig(); void delete_mouseconfig(); void delete_buttonconfig(); } #endif jdim-0.7.0/src/control/keyconfig.cpp000066400000000000000000000334751417047150700174140ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG //#define _DEBUG_XML #include "jddebug.h" #include "keyconfig.h" #include "controlutil.h" #include "defaultconf.h" #include "config/globalconf.h" #include "jdlib/miscutil.h" #include "jdlib/jdregex.h" #include "jdlib/confloader.h" #include "cache.h" using namespace CONTROL; KeyConfig::~KeyConfig() noexcept = default; // // 設定ファイル読み込み // // 設定を追加したら CONFIG::KeyPref::append_row() にも追加すること // void KeyConfig::load_conf() { JDLIB::ConfLoader cf( CACHE::path_keyconf(), std::string() ); // 共通設定 load_keymotions( cf, "Up", KEYCONF_Up ); load_keymotions( cf, "Down", KEYCONF_Down ); load_keymotions( cf, "Right", KEYCONF_Right ); load_keymotions( cf, "Left", KEYCONF_Left ); load_keymotions( cf, "TabRight", KEYCONF_TabRight ); load_keymotions( cf, "TabLeft", KEYCONF_TabLeft ); load_keymotions( cf, "TabRightUpdated", KEYCONF_TabRightUpdated ); load_keymotions( cf, "TabLeftUpdated", KEYCONF_TabLeftUpdated ); load_keymotions( cf, "TabNum1", KEYCONF_TabNum1 ); load_keymotions( cf, "TabNum2", KEYCONF_TabNum2 ); load_keymotions( cf, "TabNum3", KEYCONF_TabNum3 ); load_keymotions( cf, "TabNum4", KEYCONF_TabNum4 ); load_keymotions( cf, "TabNum5", KEYCONF_TabNum5 ); load_keymotions( cf, "TabNum6", KEYCONF_TabNum6 ); load_keymotions( cf, "TabNum7", KEYCONF_TabNum7 ); load_keymotions( cf, "TabNum8", KEYCONF_TabNum8 ); load_keymotions( cf, "TabNum9", KEYCONF_TabNum9 ); load_keymotions( cf, "RestoreLastTab", KEYCONF_RestoreLastTab ); load_keymotions( cf, "PreBookMark", KEYCONF_PreBookMark ); load_keymotions( cf, "NextBookMark", KEYCONF_NextBookMark ); load_keymotions( cf, "PrevView", KEYCONF_PrevView ); load_keymotions( cf, "NextView", KEYCONF_NextView ); load_keymotions( cf, "ToggleArticle", KEYCONF_ToggleArticle ); load_keymotions( cf, "ShowPopupMenu", KEYCONF_ShowPopupMenu ); load_keymotions( cf, "ShowMenuBar", KEYCONF_ShowMenuBar ); load_keymotions( cf, "ShowToolBarMain", KEYCONF_ShowToolBarMain ); load_keymotions( cf, "ShowSideBar", KEYCONF_ShowSideBar ); load_keymotions( cf, "PageUp", KEYCONF_PageUp ); load_keymotions( cf, "PageDown", KEYCONF_PageDown ); load_keymotions( cf, "PrevDir", KEYCONF_PrevDir ); load_keymotions( cf, "NextDir", KEYCONF_NextDir ); load_keymotions( cf, "Home", KEYCONF_Home ); load_keymotions( cf, "End", KEYCONF_End ); load_keymotions( cf, "Back", KEYCONF_Back ); load_keymotions( cf, "Undo", KEYCONF_Undo ); load_keymotions( cf, "Redo", KEYCONF_Redo ); load_keymotions( cf, "Quit", KEYCONF_Quit ); load_keymotions( cf, "Save", KEYCONF_Save ); load_keymotions( cf, "Delete", KEYCONF_Delete ); load_keymotions( cf, "Reload", KEYCONF_Reload ); load_keymotions( cf, "ReloadArticle", KEYCONF_ReloadArticle ); load_keymotions( cf, "StopLoading", KEYCONF_StopLoading ); // = CONTROL::Cancel load_keymotions( cf, "OpenURL", KEYCONF_OpenURL ); load_keymotions( cf, "Copy", KEYCONF_Copy ); load_keymotions( cf, "SelectAll", KEYCONF_SelectAll ); load_keymotions( cf, "AppendFavorite", KEYCONF_AppendFavorite ); load_motions( cf, "PreferenceView", KEYCONF_PreferenceView ); load_keymotions( cf, "Search", KEYCONF_Search ); load_keymotions( cf, "SearchInvert", KEYCONF_SearchInvert ); load_keymotions( cf, "SearchNext", KEYCONF_SearchNext ); load_keymotions( cf, "SearchPrev", KEYCONF_SearchPrev ); load_keymotions( cf, "SearchTitle", KEYCONF_SearchTitle ); load_keymotions( cf, "DrawOutAnd", KEYCONF_DrawOutAnd ); load_motions( cf, "CheckUpdateRoot", KEYCONF_CheckUpdateRoot ); load_motions( cf, "CheckUpdateOpenRoot", KEYCONF_CheckUpdateOpenRoot ); load_motions( cf, "FullScreen", KEYCONF_FullScreen ); // BBSLIST load_keymotions( cf, "OpenBoard", KEYCONF_OpenBoard ); load_keymotions( cf, "OpenBoardTab", KEYCONF_OpenBoardTab ); // BOARD load_keymotions( cf, "OpenArticle", KEYCONF_OpenArticle ); load_keymotions( cf, "OpenArticleTab", KEYCONF_OpenArticleTab ); load_keymotions( cf, "NewArticle", KEYCONF_NewArticle ); load_keymotions( cf, "SearchCache", KEYCONF_SearchCache ); load_keymotions( cf, "ScrollRightBoard", KEYCONF_ScrollRightBoard ); load_keymotions( cf, "ScrollLeftBoard", KEYCONF_ScrollLeftBoard ); load_keymotions( cf, "SortColumnMark", KEYCONF_SortColumnNoDefault ); load_keymotions( cf, "SortColumnID", KEYCONF_SortColumnNoDefault ); load_keymotions( cf, "SortColumnBoard", KEYCONF_SortColumnNoDefault ); load_keymotions( cf, "SortColumnSubject", KEYCONF_SortColumnNoDefault ); load_keymotions( cf, "SortColumnRes", KEYCONF_SortColumnNoDefault ); load_keymotions( cf, "SortColumnStrLoad", KEYCONF_SortColumnNoDefault ); load_keymotions( cf, "SortColumnStrNew", KEYCONF_SortColumnNoDefault ); load_keymotions( cf, "SortColumnSince", KEYCONF_SortColumnNoDefault ); load_keymotions( cf, "SortColumnWrite", KEYCONF_SortColumnNoDefault ); load_keymotions( cf, "SortColumnAccess", KEYCONF_SortColumnNoDefault ); load_keymotions( cf, "SortColumnSpeed", KEYCONF_SortColumnNoDefault ); load_keymotions( cf, "SortColumnDiff", KEYCONF_SortColumnNoDefault ); // ARTICLE load_keymotions( cf, "UpMid", KEYCONF_UpMid ); load_keymotions( cf, "UpFast", KEYCONF_UpFast ); load_keymotions( cf, "DownMid", KEYCONF_DownMid ); load_keymotions( cf, "DownFast", KEYCONF_DownFast ); load_keymotions( cf, "PrevRes", KEYCONF_PrevRes ); load_keymotions( cf, "NextRes", KEYCONF_NextRes ); load_keymotions( cf, "PrePost", KEYCONF_PrePost ); load_keymotions( cf, "NextPost", KEYCONF_NextPost ); load_keymotions( cf, "GotoNew", KEYCONF_GotoNew ); load_keymotions( cf, "WriteMessage", KEYCONF_WriteMessage ); load_keymotions( cf, "LiveStartStop", KEYCONF_LiveStartStop ); load_keymotions( cf, "SearchNextArticle", KEYCONF_SearchNextArticle ); load_keymotions( cf, "SearchWeb", KEYCONF_SearchWeb ); load_keymotions( cf, "SearchCacheLocal", KEYCONF_SearchCacheLocal ); load_keymotions( cf, "SearchCacheAll", KEYCONF_SearchCacheAll ); load_keymotions( cf, "ShowSelectImage", KEYCONF_ShowSelectImage ); load_keymotions( cf, "DeleteSelectImage", KEYCONF_DeleteSelectImage ); load_keymotions( cf, "AboneSelectImage", KEYCONF_AboneSelectImage ); load_keymotions( cf, "AboneSelectionRes", KEYCONF_AboneSelectionRes ); // IMAGE load_keymotions( cf, "CancelMosaic", KEYCONF_CancelMosaic ); load_keymotions( cf, "ZoomFitImage", KEYCONF_ZoomFitImage ); load_keymotions( cf, "ZoomInImage", KEYCONF_ZoomInImage ); load_keymotions( cf, "ZoomOutImage", KEYCONF_ZoomOutImage ); load_keymotions( cf, "OrgSizeImage", KEYCONF_OrgSizeImage ); load_keymotions( cf, "ScrollUpImage", KEYCONF_ScrollUpImage ); load_keymotions( cf, "ScrollDownImage", KEYCONF_ScrollDownImage ); load_keymotions( cf, "ScrollLeftImage", KEYCONF_ScrollLeftImage ); load_keymotions( cf, "ScrollRightImage", KEYCONF_ScrollRightImage ); // MESSAGE load_keymotions( cf, "CancelWrite", KEYCONF_CancelWrite ); load_keymotions( cf, "ExecWrite", KEYCONF_ExecWrite ); load_keymotions( cf, "FocusWrite", KEYCONF_FocusWrite ); load_keymotions( cf, "ToggleSage", KEYCONF_ToggleSage ); // EDIT load_keymotions( cf, "HomeEdit", KEYCONF_HomeEdit ); load_keymotions( cf, "EndEdit", KEYCONF_EndEdit ); load_keymotions( cf, "UpEdit", KEYCONF_UpEdit ); load_keymotions( cf, "DownEdit", KEYCONF_DownEdit ); load_keymotions( cf, "RightEdit", KEYCONF_RightEdit ); load_keymotions( cf, "LeftEdit", KEYCONF_LeftEdit ); load_keymotions( cf, "DeleteEdit", KEYCONF_DeleteEdit ); load_keymotions( cf, "BackspEdit", KEYCONF_BackspEdit ); load_keymotions( cf, "UndoEdit", KEYCONF_UndoEdit ); load_keymotions( cf, "EnterEdit", KEYCONF_EnterEdit ); load_keymotions( cf, "InputAA", KEYCONF_InputAA ); // JD globals load_motions( cf, "JDExit", KEYCONF_JDExit ); if( CONFIG::get_disable_close() ) remove_motions( CONTROL::JDExit ); load_motions( cf, "JDHelp", KEYCONF_JDHelp ); } // ひとつの操作をデータベースに登録 void KeyConfig::set_one_motion_impl( const int id, const int mode, const std::string& name, const std::string& str_motion ) { if( name.empty() ) return; #ifdef _DEBUG std::cout << "KeyConfig::set_one_motion_impl " << name << std::endl; std::cout << "motion = " << str_motion << std::endl; #endif #ifdef _DEBUG std::cout << CONTROL::get_label( id ) << std::endl; #endif guint key = 0; bool ctrl = false; bool shift = false; bool alt = false; const bool dblclick = false; const bool trpclick = false; JDLIB::Regex regex; const size_t offset = 0; const bool icase = true; // 大文字小文字区別しない const bool newline = true; const bool usemigemo = false; const bool wchar = false; if( regex.exec( "(Ctrl\\+)?(Shift\\+)?(Alt\\+)?(.*)", str_motion, offset, icase, newline, usemigemo, wchar ) ){ if( regex.length( 1 ) ) ctrl = true; if( regex.length( 2 ) ) shift = true; if( regex.length( 3 ) ) alt = true; const std::string str_key = regex.str( 4 ); if( str_key.empty() ) return; key = CONTROL::get_keysym( str_key ); // keyがアスキー文字の場合は shift を無視する (大文字除く) // Control::key_press() も参照せよ if( CONTROL::is_ascii( key ) ){ if( regex.length( 4 ) == 1 && key >= 'A' && key <= 'Z' ) shift = true; else shift = false; } } else return; #ifdef _DEBUG std::cout << "str_motion = " << str_motion << " key = " << key; if( ctrl ) std::cout << " ctrl"; if( shift ) std::cout << " shift"; if( alt ) std::cout << " alt"; std::cout << "\n"; #endif vec_items().push_back( MouseKeyItem( id, mode, name, str_motion, key, ctrl, shift, alt, dblclick, trpclick ) ); } // editviewの操作をemacs風か bool KeyConfig::is_emacs_mode() const { return ( get_str_motions( CONTROL::UpEdit ).find( "Ctrl+p" ) != std::string::npos ); } // editviewの操作をemacs風にする void KeyConfig::toggle_emacs_mode() { bool mode = is_emacs_mode(); remove_motions( CONTROL::HomeEdit ); remove_motions( CONTROL::HomeEdit ); remove_motions( CONTROL::EndEdit ); remove_motions( CONTROL::UpEdit ); remove_motions( CONTROL::DownEdit ); remove_motions( CONTROL::RightEdit ); remove_motions( CONTROL::LeftEdit ); remove_motions( CONTROL::DeleteEdit ); remove_motions( CONTROL::EnterEdit ); if( mode ){ set_one_motion( "HomeEdit", KEYCONF_HomeEdit ); set_one_motion( "EndEdit", KEYCONF_EndEdit ); set_one_motion( "UpEdit", KEYCONF_UpEdit ); set_one_motion( "DownEdit", KEYCONF_DownEdit ); set_one_motion( "RightEdit", KEYCONF_RightEdit ); set_one_motion( "LeftEdit", KEYCONF_LeftEdit ); set_one_motion( "DeleteEdit", KEYCONF_DeleteEdit ); set_one_motion( "EnterEdit", KEYCONF_EnterEdit ); } else{ set_one_motion( "HomeEdit", "Ctrl+a" ); set_one_motion( "EndEdit", "Ctrl+e" ); set_one_motion( "UpEdit", "Ctrl+p" ); set_one_motion( "DownEdit", "Ctrl+n" ); set_one_motion( "RightEdit", "Ctrl+f" ); set_one_motion( "LeftEdit", "Ctrl+b" ); set_one_motion( "DeleteEdit", "Ctrl+d" ); set_one_motion( "EnterEdit", "Ctrl+m" ); } } // タブで開くキーを入れ替えているか bool KeyConfig::is_toggled_tab_key() const { const bool ret = ( get_str_motions( CONTROL::OpenBoard ).find( "Ctrl+Space" ) != std::string::npos && get_str_motions( CONTROL::OpenBoardTab ).find( "Space" ) != std::string::npos && get_str_motions( CONTROL::OpenArticle ).find( "Ctrl+Space" ) != std::string::npos && get_str_motions( CONTROL::OpenArticleTab ).find( "Space" ) != std::string::npos ); #ifdef _DEBUG std::cout << "KeyConfig::is_toggled_tab_key ret = " << ret << std::endl; #endif return ret; } // タブで開くキーを入れ替える // toggle == true ならスペースをタブで開くボタンにする void KeyConfig::toggle_tab_key( const bool toggle ) { remove_motions( CONTROL::OpenBoard ); remove_motions( CONTROL::OpenBoardTab ); remove_motions( CONTROL::OpenArticle ); remove_motions( CONTROL::OpenArticleTab ); if( toggle ){ set_one_motion( "OpenBoard", "Ctrl+Space" ); set_one_motion( "OpenBoardTab", "Space" ); set_one_motion( "OpenArticle", "Ctrl+Space" ); set_one_motion( "OpenArticleTab", "Space" ); } else{ set_one_motion( "OpenBoard", "Space" ); set_one_motion( "OpenBoardTab", "Ctrl+Space" ); set_one_motion( "OpenArticle", "Space" ); set_one_motion( "OpenArticleTab", "Ctrl+Space" ); } } // // Gtk アクセラレーションキーを取得 // Gtk::AccelKey KeyConfig::get_accelkey( const int id ) const { guint motion; bool ctrl, shift, alt, dblclick, trpclick; bool found = get_motion( id, motion, ctrl, shift, alt, dblclick, trpclick ); if( ! found ){ #ifdef _DEBUG std::cout << "KeyConfig::get_accelkey id = " << id << " (" << CONTROL::get_name( id ) << ") notfound " << std::endl; #endif return Gtk::AccelKey(); } Gdk::ModifierType type = static_cast(0); if( ctrl ) type |= Gdk::CONTROL_MASK; if( shift ) type |= Gdk::SHIFT_MASK; if( alt) type |= Gdk::MOD1_MASK; #ifdef _DEBUG std::cout << "KeyConfig::get_accelkey id = " << id << " (" << CONTROL::get_name( id ) << ") motion = " << motion << " ctrl = " << ctrl << " shift = " << shift << " alt = " << alt << std::endl; #endif return Gtk::AccelKey( motion, type ); } jdim-0.7.0/src/control/keyconfig.h000066400000000000000000000017771417047150700170610ustar00rootroot00000000000000// ライセンス: GPL2 // // キー設定クラス // #ifndef _KEYCONFIG_H #define _KEYCONFIG_H #include "mousekeyconf.h" #include namespace CONTROL { class KeyConfig : public MouseKeyConf { public: using MouseKeyConf::MouseKeyConf; ~KeyConfig() noexcept; void load_conf() override; // editviewの操作をemacs風にする bool is_emacs_mode() const; void toggle_emacs_mode(); bool is_toggled_tab_key() const; // タブで開くキーを入れ替えているか void toggle_tab_key( const bool toggle ); // タブで開くキーを入れ替える // Gtk アクセラレーションキーを取得 Gtk::AccelKey get_accelkey( const int id ) const; private: // ひとつの操作をデータベースに登録 void set_one_motion_impl( const int id, const int mode, const std::string& name, const std::string& str_motion ) override; }; } #endif jdim-0.7.0/src/control/keypref.cpp000066400000000000000000000204151417047150700170710ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "keypref.h" #include "controlid.h" #include "controlutil.h" #include "global.h" #include "config/globalconf.h" using namespace CONTROL; // // キーボード入力をラベルに表示するダイアログ // KeyInputDiag::KeyInputDiag( Gtk::Window* parent, const std::string& url,const int id ) : CONTROL::InputDiag( parent, url, id, "ショートカットキー", INPUTDIAG_MODE_KEY ) {} /////////////////////////////// // // 個別のショートカットキー設定ダイアログ // KeyDiag::KeyDiag( Gtk::Window* parent, const std::string& url, const int id, const std::string& str_motions ) : CONTROL::MouseKeyDiag( parent, url, id, "ショートカットキー", str_motions ) { // Gtkアクセラレーションキーは、1つだけしか設定できないようにする set_single( get_controlmode() == CONTROL::MODE_JDGLOBALS ); } std::unique_ptr KeyDiag::create_inputdiag() { return std::make_unique( this, "", get_id() ); } std::string KeyDiag::get_default_motions( const int id ) { return CONTROL::get_default_keymotions( id ); } std::vector< int > KeyDiag::check_conflict( const int mode, const std::string& str_motion ) { return CONTROL::check_key_conflict( mode, str_motion ); } /////////////////////////////////////////////// // // キーボード設定ダイアログ // KeyPref::KeyPref( Gtk::Window* parent, const std::string& url ) : MouseKeyPref( parent, url, "ショートカットキー" ) { // キー設定のバックアップを取る // キャンセルを押したら戻す CONTROL::bkup_keyconfig(); append_comment_row( "■ " + CONTROL::get_mode_label( CONTROL::MODE_COMMON ) ); append_row( CONTROL::Up ); append_row( CONTROL::Down ); append_row( CONTROL::Right ); append_row( CONTROL::Left ); append_row( CONTROL::TabRight ); append_row( CONTROL::TabLeft ); append_row( CONTROL::TabRightUpdated ); append_row( CONTROL::TabLeftUpdated ); append_row( CONTROL::TabNum1 ); append_row( CONTROL::TabNum2 ); append_row( CONTROL::TabNum3 ); append_row( CONTROL::TabNum4 ); append_row( CONTROL::TabNum5 ); append_row( CONTROL::TabNum6 ); append_row( CONTROL::TabNum7 ); append_row( CONTROL::TabNum8 ); append_row( CONTROL::TabNum9 ); append_row( CONTROL::RestoreLastTab ); append_row( CONTROL::PreBookMark ); append_row( CONTROL::NextBookMark ); append_row( CONTROL::PrevView ); append_row( CONTROL::NextView ); append_row( CONTROL::ToggleArticle ); append_row( CONTROL::ShowPopupMenu ); append_row( CONTROL::ShowMenuBar ); append_row( CONTROL::ShowToolBarMain ); append_row( CONTROL::ShowSideBar ); append_row( CONTROL::PageUp ); append_row( CONTROL::PageDown ); append_row( CONTROL::PrevDir ); append_row( CONTROL::NextDir ); append_row( CONTROL::Home ); append_row( CONTROL::End ); append_row( CONTROL::Back ); append_row( CONTROL::Undo ); append_row( CONTROL::Redo ); append_row( CONTROL::Quit ); append_row( CONTROL::Save ); append_row( CONTROL::Delete ); append_row( CONTROL::Reload ); append_row( CONTROL::ReloadArticle ); append_row( CONTROL::StopLoading ); append_row( CONTROL::OpenURL ); append_row( CONTROL::Copy ); append_row( CONTROL::SelectAll ); append_row( CONTROL::AppendFavorite ); append_row( CONTROL::PreferenceView ); append_row( CONTROL::Search ); append_row( CONTROL::SearchInvert ); append_row( CONTROL::SearchNext ); append_row( CONTROL::SearchPrev ); append_row( CONTROL::SearchTitle ); append_row( CONTROL::DrawOutAnd ); append_row( CONTROL::CheckUpdateRoot ); append_row( CONTROL::CheckUpdateOpenRoot ); append_row( CONTROL::FullScreen ); append_comment_row( "" ); append_comment_row( "■ " + CONTROL::get_mode_label( CONTROL::MODE_EDIT ) ); append_row( CONTROL::HomeEdit ); append_row( CONTROL::EndEdit ); append_row( CONTROL::UpEdit ); append_row( CONTROL::DownEdit ); append_row( CONTROL::RightEdit ); append_row( CONTROL::LeftEdit ); append_row( CONTROL::DeleteEdit ); append_row( CONTROL::BackspEdit ); append_row( CONTROL::UndoEdit ); append_row( CONTROL::EnterEdit ); append_row( CONTROL::InputAA ); append_comment_row( "" ); append_comment_row( "■ " + CONTROL::get_mode_label( CONTROL::MODE_BBSLIST ) ); append_row( CONTROL::OpenBoard ); append_row( CONTROL::OpenBoardTab ); append_comment_row( "" ); append_comment_row( "■ " + CONTROL::get_mode_label( CONTROL::MODE_BOARD ) ); append_row( CONTROL::OpenArticle ); append_row( CONTROL::OpenArticleTab ); append_row( CONTROL::NewArticle ); append_row( CONTROL::SearchCache ); append_row( CONTROL::ScrollRightBoard ); append_row( CONTROL::ScrollLeftBoard ); append_row( CONTROL::SortColumnMark ); append_row( CONTROL::SortColumnID ); append_row( CONTROL::SortColumnBoard ); append_row( CONTROL::SortColumnSubject ); append_row( CONTROL::SortColumnRes ); append_row( CONTROL::SortColumnStrLoad ); append_row( CONTROL::SortColumnStrNew ); append_row( CONTROL::SortColumnSince ); append_row( CONTROL::SortColumnWrite ); append_row( CONTROL::SortColumnAccess ); append_row( CONTROL::SortColumnSpeed ); append_row( CONTROL::SortColumnDiff ); append_comment_row( "" ); append_comment_row( "■ " + CONTROL::get_mode_label( CONTROL::MODE_ARTICLE ) ); append_row( CONTROL::UpMid ); append_row( CONTROL::UpFast ); append_row( CONTROL::DownMid ); append_row( CONTROL::DownFast ); append_row( CONTROL::PrevRes ); append_row( CONTROL::NextRes ); append_row( CONTROL::PrePost ); append_row( CONTROL::NextPost ); append_row( CONTROL::GotoNew ); append_row( CONTROL::WriteMessage ); append_row( CONTROL::SearchNextArticle ); append_row( CONTROL::SearchWeb ); append_row( CONTROL::SearchCacheLocal ); append_row( CONTROL::SearchCacheAll ); append_row( CONTROL::LiveStartStop ); append_row( CONTROL::ShowSelectImage ); append_row( CONTROL::DeleteSelectImage, ITEM_NAME_SELECTDELIMG ); append_row( CONTROL::AboneSelectImage, ITEM_NAME_SELECTABONEIMG ); append_row( CONTROL::AboneSelectionRes ); append_comment_row( "" ); append_comment_row( "■ " + CONTROL::get_mode_label( CONTROL::MODE_IMAGEVIEW ) ); append_row( CONTROL::CancelMosaic ); append_row( CONTROL::ZoomFitImage ); append_row( CONTROL::ZoomInImage ); append_row( CONTROL::ZoomOutImage ); append_row( CONTROL::OrgSizeImage ); append_row( CONTROL::ScrollUpImage ); append_row( CONTROL::ScrollDownImage ); append_row( CONTROL::ScrollLeftImage ); append_row( CONTROL::ScrollRightImage ); append_comment_row( "" ); append_comment_row( "■ " + CONTROL::get_mode_label( CONTROL::MODE_MESSAGE ) ); append_row( CONTROL::CancelWrite ); append_row( CONTROL::ExecWrite ); append_row( CONTROL::FocusWrite ); append_row( CONTROL::ToggleSage ); append_comment_row( "" ); append_comment_row( "■ " + CONTROL::get_mode_label( CONTROL::MODE_JDGLOBALS ) ); append_row( CONTROL::JDExit ); append_row( CONTROL::JDHelp ); } std::unique_ptr KeyPref::create_setting_diag( const int id, const std::string& str_motions ) { return std::make_unique( this, "", id, str_motions ); } std::string KeyPref::get_str_motions( const int id ) { return CONTROL::get_str_keymotions( id ); } std::string KeyPref::get_default_motions( const int id ) { return CONTROL::get_default_keymotions( id ); } void KeyPref::set_motions( const int id, const std::string& str_motions ) { if( id == CONTROL::JDExit && ! str_motions.empty() ){ // JDExitを設定したら、2.8.6以前との互換性オプションをオフにする CONFIG::set_disable_close( false ); } CONTROL::set_keymotions( id, str_motions ); } bool KeyPref::remove_motions( const int id ) { return CONTROL::remove_keymotions( id ); } // // キャンセルボタンを押した // void KeyPref::slot_cancel_clicked() { #ifdef _DEBUG std::cout << "KeyPref::slot_cancel_clicked\n"; #endif // キー設定を戻す CONTROL::restore_keyconfig(); } jdim-0.7.0/src/control/keypref.h000066400000000000000000000036051417047150700165400ustar00rootroot00000000000000// ライセンス: GPL2 // キーボード設定ダイアログ // KeyPref が本体で、KeyPrefの各行をダブルクリックすると KeyDiag が開いて個別に操作の設定が出来る // KeyDiag の各行をダブルクリックすると KeyInputDiag が開いてキー入力が出来る #ifndef _KEYPREFPREF_H #define _KEYPREFPREF_H #include "mousekeypref.h" namespace CONTROL { // // キーボード入力をラベルに表示するダイアログ // class KeyInputDiag : public CONTROL::InputDiag { public: KeyInputDiag( Gtk::Window* parent, const std::string& url, const int id ); }; /////////////////////////////////////// // // 個別のショートカットキー設定ダイアログ // class KeyDiag : public CONTROL::MouseKeyDiag { public: KeyDiag( Gtk::Window* parent, const std::string& url, const int id, const std::string& str_motions ); protected: std::unique_ptr create_inputdiag() override; std::string get_default_motions( const int id ) override; std::vector< int > check_conflict( const int mode, const std::string& str_motion ) override; }; /////////////////////////////////// // // キーボード設定ダイアログ // class KeyPref : public CONTROL::MouseKeyPref { public: KeyPref( Gtk::Window* parent, const std::string& url ); protected: std::unique_ptr create_setting_diag( const int id, const std::string& str_motions ) override; std::string get_str_motions( const int id ) override; std::string get_default_motions( const int id ) override; void set_motions( const int id, const std::string& str_motions ) override; bool remove_motions( const int id ) override; private: void slot_cancel_clicked() override; }; } #endif jdim-0.7.0/src/control/keysyms.h000066400000000000000000000042351417047150700165770ustar00rootroot00000000000000// ライセンス: GPL2 // キー名 <-> keysym 変換テーブル #ifndef _KEYSYMS_H #define _KEYSYMS_H enum { MAX_KEYNAME = 64 }; struct KEYSYMS { char keyname[ MAX_KEYNAME ]; size_t keysym; }; namespace CONTROL { KEYSYMS keysyms[] ={ { "Space", GDK_KEY_space }, { "Escape", GDK_KEY_Escape }, { "Delete", GDK_KEY_Delete }, { "Enter", GDK_KEY_Return }, { "Up", GDK_KEY_Up }, { "Down", GDK_KEY_Down }, { "Left", GDK_KEY_Left }, { "Right", GDK_KEY_Right }, { "Page_Up", GDK_KEY_Page_Up }, { "Page_Down", GDK_KEY_Page_Down }, { "Tab", GDK_KEY_Tab }, { "Left_Tab", GDK_KEY_ISO_Left_Tab }, { "Home", GDK_KEY_Home }, { "End", GDK_KEY_End }, { "BackSpace", GDK_KEY_BackSpace }, { "F1", GDK_KEY_F1 }, { "F2", GDK_KEY_F2 }, { "F3", GDK_KEY_F3 }, { "F4", GDK_KEY_F4 }, { "F5", GDK_KEY_F5 }, { "F6", GDK_KEY_F6 }, { "F7", GDK_KEY_F7 }, { "F8", GDK_KEY_F8 }, { "F9", GDK_KEY_F9 }, { "F10", GDK_KEY_F10 }, { "F11", GDK_KEY_F11 }, { "F12", GDK_KEY_F12 }, { "Menu", GDK_KEY_Menu }, // テンキー { "KP_Divide", GDK_KEY_KP_Divide }, // "/" { "KP_Multiply", GDK_KEY_KP_Multiply }, // "*" { "KP_Subtract", GDK_KEY_KP_Subtract }, // "-" { "KP_Home", GDK_KEY_KP_Home }, // "Home(7)" { "KP_Up", GDK_KEY_KP_Up }, // "↑(8)" { "KP_Prior", GDK_KEY_KP_Prior }, // "Pg UP(9)" { "KP_Add", GDK_KEY_KP_Add }, // "+" { "KP_Left", GDK_KEY_KP_Left }, // "←(4)" //{ "KP_Begin", GDK_KEY_KP_Begin }, // "(5)" { "KP_Right", GDK_KEY_KP_Right }, // "→(6)" { "KP_End", GDK_KEY_KP_End }, // "End(1)" { "KP_Down", GDK_KEY_KP_Down }, // "↓(2)" { "KP_Next", GDK_KEY_KP_Next }, // "Pg Dn(3)" { "KP_Enter", GDK_KEY_KP_Enter }, // "Enter" { "KP_Insert", GDK_KEY_KP_Insert }, // "Ins(0)" { "KP_Delete", GDK_KEY_KP_Delete }, // "Del(.)" }; } #endif jdim-0.7.0/src/control/meson.build000066400000000000000000000005511417047150700170610ustar00rootroot00000000000000sources = [ 'buttonconfig.cpp', 'buttonpref.cpp', 'control.cpp', 'controlutil.cpp', 'keyconfig.cpp', 'keypref.cpp', 'mouseconfig.cpp', 'mousekeyconf.cpp', 'mousekeypref.cpp', 'mousepref.cpp', ] control_lib = static_library( 'control', [sources, config_h], dependencies : gtkmm_dep, include_directories : include_directories('..'), ) jdim-0.7.0/src/control/mouseconfig.cpp000066400000000000000000000076701417047150700177520ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "mouseconfig.h" #include "defaultconf.h" #include "jdlib/miscutil.h" #include "jdlib/confloader.h" #include "cache.h" #ifdef _DEBUG #include "controlutil.h" #endif using namespace CONTROL; MouseConfig::~MouseConfig() noexcept = default; // // 設定ファイル読み込み // void MouseConfig::load_conf() { JDLIB::ConfLoader cf( CACHE::path_mouseconf(), std::string() ); // 共通 load_motions( cf, "Right", MOUSECONF_Right ); load_motions( cf, "Left", MOUSECONF_Left ); load_motions( cf, "TabRight", MOUSECONF_TabRight ); load_motions( cf, "TabLeft", MOUSECONF_TabLeft ); load_motions( cf, "TabRightUpdated", MOUSECONF_TabRightUpdated ); load_motions( cf, "TabLeftUpdated", MOUSECONF_TabLeftUpdated ); load_motions( cf, "CloseAllTabs", MOUSECONF_CloseAllTabs ); load_motions( cf, "CloseOtherTabs", MOUSECONF_CloseOtherTabs ); load_motions( cf, "RestoreLastTab", MOUSECONF_RestoreLastTab ); load_motions( cf, "CheckUpdateTabs", MOUSECONF_CheckUpdateTabs ); load_motions( cf, "ToggleArticle", MOUSECONF_ToggleArticle ); load_motions( cf, "ShowSideBar", MOUSECONF_ShowSideBar ); load_motions( cf, "ShowMenuBar", MOUSECONF_ShowMenuBar ); load_motions( cf, "ShowToolBarMain", MOUSECONF_ShowToolBarMain ); load_motions( cf, "Home", MOUSECONF_Home ); load_motions( cf, "End", MOUSECONF_End ); load_motions( cf, "Quit", MOUSECONF_Quit ); load_motions( cf, "Reload", MOUSECONF_Reload ); load_motions( cf, "Delete", MOUSECONF_Delete ); load_motions( cf, "StopLoading", MOUSECONF_StopLoading ); load_motions( cf, "AppendFavorite", MOUSECONF_AppendFavorite ); load_motions( cf, "NewArticle", MOUSECONF_NewArticle ); load_motions( cf, "WriteMessage", MOUSECONF_WriteMessage ); load_motions( cf, "SearchTitle", MOUSECONF_SearchTitle ); load_motions( cf, "CheckUpdateRoot", MOUSECONF_CheckUpdateRoot ); load_motions( cf, "CheckUpdateOpenRoot", MOUSECONF_CheckUpdateOpenRoot ); load_motions( cf, "QuitJD", MOUSECONF_QuitJD ); load_motions( cf, "MaximizeMainWin", MOUSECONF_MaximizeMainWin ); load_motions( cf, "IconifyMainWin", MOUSECONF_IconifyMainWin ); // ARTICLE load_motions( cf, "GotoNew", MOUSECONF_GotoNew ); load_motions( cf, "SearchNextArticle", MOUSECONF_SearchNextArticle ); load_motions( cf, "SearchWeb", MOUSECONF_SearchWeb ); load_motions( cf, "LiveStartStop", MOUSECONF_LiveStartStop ); // IMAGE load_motions( cf, "CancelMosaicButton", MOUSECONF_CancelMosaicButton ); } // ひとつの操作をデータベースに登録 void MouseConfig::set_one_motion_impl( const int id, const int mode, const std::string& name, const std::string& str_motion ) { if( name.empty() || str_motion.empty() ) return; #ifdef _DEBUG std::cout << "MouseConfig::set_one_motion_impl " << name << std::endl; std::cout << "motion = " << str_motion << std::endl; std::cout << CONTROL::get_label( id ) << std::endl; #endif const bool ctrl = false; const bool shift = false; const bool alt = false; const guint motion = atoi( str_motion.c_str() ); const bool dblclick = false; const bool trpclick = false; vec_items().push_back( MouseKeyItem( id, mode, name, str_motion, motion, ctrl, shift, alt, dblclick, trpclick ) ); } // 操作文字列取得 std::string MouseConfig::get_str_motions( const int id_ ) const { int id = id_; // (注) この行が無いと画像ビューのコンテキストメニューにマウスジェスチャが表示されない if( id == CONTROL::CancelMosaic ) id = CONTROL::CancelMosaicButton; return MouseKeyConf::get_str_motions( id ); } // IDからデフォルトの操作文字列取得 std::string MouseConfig::get_default_motions( const int id_ ) const { int id = id_; if( id == CONTROL::CancelMosaic ) id = CONTROL::CancelMosaicButton; return MouseKeyConf::get_default_motions( id ); } jdim-0.7.0/src/control/mouseconfig.h000066400000000000000000000014761417047150700174150ustar00rootroot00000000000000// ライセンス: GPL2 // // マウスジェスチャ設定 // #ifndef _MOUSECONFIG_H #define _MOUSECONFIG_H #include "mousekeyconf.h" namespace CONTROL { class MouseConfig : public MouseKeyConf { public: using MouseKeyConf::MouseKeyConf; ~MouseConfig() noexcept; void load_conf() override; // 操作文字列取得 std::string get_str_motions( const int id ) const override; // IDからデフォルトの操作文字列取得 std::string get_default_motions( const int id ) const override; private: // ひとつの操作をデータベースに登録 void set_one_motion_impl( const int id, const int mode, const std::string& name, const std::string& str_motion ) override; }; } #endif jdim-0.7.0/src/control/mousekeyconf.cpp000066400000000000000000000160671417047150700201430ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "mousekeyconf.h" #include "mousekeyitem.h" #include "controlutil.h" #include "jdlib/miscutil.h" #include "cache.h" #include using namespace CONTROL; MouseKeyConf::MouseKeyConf() {} MouseKeyConf::~MouseKeyConf() noexcept = default; // 設定ファイル保存 void MouseKeyConf::save_conf( const std::string& savefile ) { #ifdef _DEBUG std::cout << "MouseKeyConf::save_conf " << savefile << std::endl; #endif JDLIB::ConfLoader cf( savefile, std::string() ); for( const auto& key_val : m_map_default_motions ) { const int id = key_val.first; const std::string name = CONTROL::get_name( id ); std::string motions; for( const MouseKeyItem& item : m_vec_items ) { if( item.get_id() == id ){ if( !motions.empty() ) motions += " "; motions.append( item.get_str_motion() ); } } #ifdef _DEBUG std::cout << name << " = " << motions << std::endl; #endif cf.update( name, motions ); } cf.save(); } // 操作からID取得 int MouseKeyConf::get_id( const int mode, const guint motion, const bool ctrl, const bool shift, const bool alt, const bool dblclick, const bool trpclick ) const { int id = CONTROL::None;; for( const MouseKeyItem& item : m_vec_items ) { id = item.is_activated( mode, motion, ctrl, shift, alt, dblclick, trpclick ); if( id != CONTROL::None ) break; } #ifdef _DEBUG std::cout << "MouseKeyConf::get_id mode = " << mode << " id = " << id << " motion = " << motion << " ctrl = " << ctrl << " shift = " << shift << " alt = " << alt << " dblclick = " << dblclick << " trpclick = " << trpclick << std::endl; #endif return id; } // ID から操作を取得 // (注意) リストの一番上にあるものを出力 bool MouseKeyConf::get_motion( const int id, guint& motion, bool& ctrl, bool& shift, bool& alt, bool& dblclick, bool& trpclick ) const { auto it = std::find_if( m_vec_items.cbegin(), m_vec_items.cend(), [id]( const MouseKeyItem& i ) { return i.get_id() == id; } ); if( it != m_vec_items.cend() ) { motion = it->get_motion(); ctrl = it->get_ctrl(); shift = it->get_shift(); alt = it->get_alt(); dblclick = it->get_dblclick(); trpclick = it->get_trpclick(); return true; } return false; } // ID が割り当てられているかチェック bool MouseKeyConf::alloted( const int id, const guint motion, const bool ctrl, const bool shift, const bool alt, const bool dblclick, const bool trpclick ) const { const auto equal = [&]( const MouseKeyItem& item ) { return item.equal( motion, ctrl, shift, alt, dblclick, trpclick ) == id; }; return std::any_of( m_vec_items.cbegin(), m_vec_items.cend(), equal ); } // 同じモード内でモーションが重複していないかチェック std::vector< int > MouseKeyConf::check_conflict( const int mode, const std::string& str_motion ) const { std::vector< int > vec_ids; for( const MouseKeyItem& item : m_vec_items ) { const int id = item.is_activated( mode, str_motion ); if( id != CONTROL::None ) vec_ids.push_back( id ); } return vec_ids; } // IDから操作文字列取得 std::string MouseKeyConf::get_str_motions( const int id ) const { std::string motions; for( const MouseKeyItem& item : m_vec_items ) { if( item.get_id() == id ){ if( ! motions.empty() ) motions += " "; motions.append( item.get_str_motion() ); } } return motions; } // IDからデフォルトの操作文字列取得 std::string MouseKeyConf::get_default_motions( const int id ) const { auto it = m_map_default_motions.find( id ); if( it != m_map_default_motions.end() ) return ( *it ).second; return std::string(); } // 設定ファイルから読み込んだモーションを登録 void MouseKeyConf::load_motions( JDLIB::ConfLoader& cf, const std::string& name, const std::string& default_motions ) { const int id = CONTROL::get_id( name ); const std::string str_motions = cf.get_option_str( name, default_motions, 256 ); set_motions( id, str_motions ); set_default_motions( id, default_motions ); } // 設定ファイルから読み込んだモーションを登録 // ver.2.0.2 以前との互換性のため Plus を + に置き換える void MouseKeyConf::load_keymotions( JDLIB::ConfLoader& cf, const std::string& name, const std::string& default_motions ) { const int id = CONTROL::get_id( name ); std::string str_motions = cf.get_option_str( name, default_motions, 256 ); if( str_motions.find( "Plus" ) != std::string::npos ) str_motions = MISC::replace_str( str_motions, "Plus", "+" ); set_motions( id, str_motions ); set_default_motions( id, default_motions ); } // スペースで区切られた複数の操作をデータベースに登録 void MouseKeyConf::set_motions( const int id, const std::string& str_motions ) { if( id == CONTROL::None ) return; const std::string name = CONTROL::get_name( id ); if( name.empty() ) return; const int mode = CONTROL::get_mode( id ); if( mode == CONTROL::MODE_ERROR ) return; std::list< std::string > list_motion = MISC::StringTokenizer( str_motions, ' ' ); for( const std::string& str_motion : list_motion ) set_one_motion_impl( id, mode, name, str_motion ); } // デフォルト操作を登録 void MouseKeyConf::set_default_motions( const int id, const std::string& default_motions ) { if( id == CONTROL::None ) return; m_map_default_motions.insert( make_pair( id, default_motions ) ); } // ひとつの操作をデータベースに登録 void MouseKeyConf::set_one_motion( const std::string& name, const std::string& str_motion ) { const int id = CONTROL::get_id( name ); if( id == CONTROL::None ) return; const int mode = CONTROL::get_mode( id ); if( mode == CONTROL::MODE_ERROR ) return; set_one_motion_impl( id, mode, name, str_motion ); } // 指定したIDの操作を全て削除 // 削除したら true を返す bool MouseKeyConf::remove_motions( const int id ) { // erase-remove idiom const auto it = std::remove_if( m_vec_items.begin(), m_vec_items.end(), [id]( const MouseKeyItem& i ) { return i.get_id() == id; } ); if( it != m_vec_items.end() ) { m_vec_items.erase( it, m_vec_items.end() ); return true; } return false; } // 設定を一時的にバックアップする (Not thread safe) void MouseKeyConf::state_backup() { m_backup_vec_items = m_vec_items; m_backup_map_default_motions = m_map_default_motions; } // バックアップした設定を復元する (Not thread safe) void MouseKeyConf::state_restore() { m_vec_items = m_backup_vec_items; m_map_default_motions = m_backup_map_default_motions; } jdim-0.7.0/src/control/mousekeyconf.h000066400000000000000000000062551417047150700176060ustar00rootroot00000000000000// ライセンス: GPL2 // // マウス、キーボード設定のベースクラス // #ifndef _MOUSEKEYCONF_H #define _MOUSEKEYCONF_H #include "mousekeyitem.h" #include "controlutil.h" #include "jdlib/confloader.h" #include #include #include namespace CONTROL { class MouseKeyConf { std::vector m_vec_items; std::vector m_backup_vec_items; std::map m_map_default_motions; std::map m_backup_map_default_motions; public: MouseKeyConf(); virtual ~MouseKeyConf() noexcept; // 設定ファイル読み込み virtual void load_conf() = 0; // 設定ファイル保存 void save_conf( const std::string& savefile ); // 操作からID取得 int get_id( const int mode, const guint motion, const bool ctrl, const bool shift, const bool alt, const bool dblclick, const bool trpclick ) const; // ID から操作を取得 // (注意) リストの一番上にあるものを出力 bool get_motion( const int id, guint& motion, bool& ctrl, bool& shift, bool& alt, bool& dblclick, bool& trpclick ) const; // ID が割り当てられているかチェック bool alloted( const int id, const guint motion, const bool ctrl, const bool shift, const bool alt, const bool dblclick, const bool trpclick ) const; // IDから操作文字列取得 virtual std::string get_str_motions( const int id ) const; // IDからデフォルトの操作文字列取得 virtual std::string get_default_motions( const int id ) const; // 同じモード内でモーションが重複していないかチェック // 戻り値 : コントロールID std::vector< int > check_conflict( const int mode, const std::string& str_motion ) const; // スペースで区切られた複数の操作をデータベースに登録 void set_motions( const int id, const std::string& str_motions ); // 指定したIDの操作を全て削除 bool remove_motions( const int id ); // 設定の一時的なバックアップと復元 (Not thread safe) void state_backup(); void state_restore(); protected: std::vector< MouseKeyItem >& vec_items(){ return m_vec_items; } // 設定ファイルから読み込んだモーションを登録 void load_motions( JDLIB::ConfLoader& cf, const std::string& name, const std::string& default_motions ); void load_keymotions( JDLIB::ConfLoader& cf, const std::string& name, const std::string& default_motions ); // デフォルト操作を登録 void set_default_motions( const int id, const std::string& default_motions ); // ひとつの操作をデータベースに登録 void set_one_motion( const std::string& name, const std::string& str_motion ); virtual void set_one_motion_impl( const int id, const int mode, const std::string& name, const std::string& str_motion ) = 0; }; } #endif jdim-0.7.0/src/control/mousekeyitem.h000066400000000000000000000052371417047150700176160ustar00rootroot00000000000000// ライセンス: GPL2 // // マウスやキー設定のデータ // #ifndef _MOUSEKEYITEM_H #define _MOUSEKEYITEM_H #include "controlid.h" #include #include namespace CONTROL { class MouseKeyItem { int m_id; int m_mode; std::string m_name; std::string m_str_motion; guint m_motion; bool m_ctrl; bool m_shift; bool m_alt; bool m_dblclick; bool m_trpclick; public: MouseKeyItem( const guint id, const int mode, const std::string& name, const std::string& str_motion, const guint motion, const bool ctrl, const bool shift, const bool alt, const bool dblclick, const bool trpclick ) : m_id( id ), m_mode( mode ), m_name( name ), m_str_motion( str_motion ), m_motion( motion ), m_ctrl( ctrl ), m_shift( shift ), m_alt( alt ), m_dblclick( dblclick ), m_trpclick( trpclick ) {} int get_id() const { return m_id; } int get_mode() const { return m_mode; } const std::string& get_name() const { return m_name; } const std::string& get_str_motion() const { return m_str_motion; } gint get_motion() const { return m_motion; } bool get_ctrl() const { return m_ctrl; } bool get_shift() const { return m_shift; } bool get_alt() const { return m_alt; } bool get_dblclick() const { return m_dblclick; } bool get_trpclick() const { return m_trpclick; } // モード無視 int equal( const std::string& str_motion ) const { if( str_motion == m_str_motion ) return m_id; return CONTROL::None; } // モード無視 int equal( const guint motion, const bool ctrl, const bool shift, const bool alt, const bool dblclick, const bool trpclick ) const { if( motion == m_motion && ctrl == m_ctrl && shift == m_shift && alt == m_alt && dblclick == m_dblclick && trpclick == m_trpclick ) return m_id; return CONTROL::None; } int is_activated( const int mode, const std::string& str_motion ) const { if( mode == m_mode ) return equal( str_motion ); return CONTROL::None; } int is_activated( const int mode, const guint motion, const bool ctrl, const bool shift, const bool alt, const bool dblclick, const bool trpclick ) const { if( mode == m_mode ) return equal( motion, ctrl, shift, alt, dblclick, trpclick ); return CONTROL::None; } }; } #endif jdim-0.7.0/src/control/mousekeypref.cpp000066400000000000000000000504101417047150700201400ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "mousekeypref.h" #include "controlutil.h" #include "controlid.h" #include "config/globalconf.h" #include "skeleton/msgdiag.h" #include "jdlib/miscutil.h" #include "colorid.h" #include using namespace CONTROL; // // キーやマウス入力ダイアログの基底クラス // InputDiag::InputDiag( Gtk::Window* parent, const std::string& url, const int id, const std::string& target, const int mode ) : SKELETON::PrefDiag( parent, url ), m_id( id ), m_mode( mode ), m_controlmode( CONTROL::get_mode( m_id ) ), m_label( target + "を入力して下さい。" ) { add_events( Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK | Gdk::POINTER_MOTION_MASK ); m_control.add_mode( m_controlmode ); m_control.set_send_mg_info( false ); set_title( CONTROL::get_label( m_id ) + " ( " + CONTROL::get_mode_label( m_controlmode ) + " )" ); resize( 400, 400 ); get_content_area()->pack_start( m_label ); show_all_children(); } bool InputDiag::on_key_press_event( GdkEventKey* event ) { guint key = event->keyval; const bool ctrl = ( event->state ) & GDK_CONTROL_MASK; bool shift = ( event->state ) & GDK_SHIFT_MASK; const bool alt = ( event->state ) & GDK_MOD1_MASK; const bool caps = ( event->state ) & GDK_LOCK_MASK; // caps lock if( m_mode & INPUTDIAG_MODE_KEY ){ // caps lockされている場合は、アスキー文字を大文字小文字入れ替えて、capsを無視する // Control::key_press()も参照のこと if( caps ){ if( key >= 'A' && key <= 'Z' ){ key += 'a' - 'A'; } else if( key >= 'a' && key <= 'z' ){ key += 'A' - 'a'; } } // keyがアスキー文字の場合は shift を無視する // KeyConfig::set_one_motion()も参照せよ if( CONTROL::is_ascii( key ) ) shift = false; #ifdef _DEBUG std::cout << "InputDiag::on_key_press_event key = " << std::hex << key << std::dec; if( ctrl ) std::cout << " ctrl"; if( shift ) std::cout << " shift"; if( alt ) std::cout << " alt"; std::cout << "\n"; #endif m_str_motion = std::string(); const std::string keyname = CONTROL::get_keyname( key ); std::string control_label; if( ! keyname.empty() ){ if( ctrl ) m_str_motion += "Ctrl+"; if( shift ) m_str_motion += "Shift+"; if( alt ) m_str_motion += "Alt+"; m_str_motion += keyname; // ラベルに被っている操作名を表示 control_label = get_key_label(); } m_label.set_label( m_str_motion + "\n" + control_label ); // エンターやスペースキーを押すとキャンセルボタンが押されてしまうのでキャンセルする if( keyname == "Space" || keyname == "Enter" ) return true; } return SKELETON::PrefDiag::on_key_press_event( event ); } bool InputDiag::on_button_press_event( GdkEventButton* event ) { #ifdef _DEBUG std::cout << "InputDiag::on_button_press_event\n"; #endif const bool ret = SKELETON::PrefDiag::on_button_press_event( event ); if( m_mode & INPUTDIAG_MODE_MOUSE ){ m_control.MG_start( event ); m_str_motion = std::string(); m_label.set_label( "" ); } else if( m_mode & INPUTDIAG_MODE_BUTTON ){ const guint button = event->button; const bool ctrl = ( event->state ) & GDK_CONTROL_MASK; const bool shift = ( event->state ) & GDK_SHIFT_MASK; const bool alt = ( event->state ) & GDK_MOD1_MASK; const bool dblclick = ( event->type == GDK_2BUTTON_PRESS ); const bool trpclick = ( event->type == GDK_3BUTTON_PRESS ); m_str_motion = std::string(); std::string buttonname; std::string control_label; if( button == 1 ){ if( trpclick ) buttonname = "TrpLeft"; else if( dblclick ) buttonname = "DblLeft"; else buttonname = "Left"; } else if( button == 2 ){ if( trpclick ) buttonname = "TrpMid"; else if( dblclick ) buttonname = "DblMid"; else buttonname = "Mid"; } else if( button == 3 ){ if( trpclick ) buttonname = "TrpRight"; else if( dblclick ) buttonname = "DblRight"; else buttonname = "Right"; } else if( button == 6 ) buttonname = "Tilt_Left"; else if( button == 7 ) buttonname = "Tilt_Right"; else if( button == 8 ) buttonname = "Button4"; else if( button == 9 ) buttonname = "Button5"; if( ! buttonname.empty() ){ if( ctrl ) m_str_motion += "Ctrl+"; if( shift ) m_str_motion += "Shift+"; if( alt ) m_str_motion += "Alt+"; m_str_motion += buttonname; control_label = get_button_label(); } m_label.set_label( m_str_motion + "\n" + control_label ); } return ret; } bool InputDiag::on_button_release_event( GdkEventButton* event ) { #ifdef _DEBUG std::cout << "InputDiag::on_button_release_event\n"; #endif const bool ret = SKELETON::PrefDiag::on_button_release_event( event ); if( m_mode & INPUTDIAG_MODE_MOUSE ){ m_control.MG_end( event ); } return ret; } bool InputDiag::on_motion_notify_event( GdkEventMotion* event ) { #ifdef _DEBUG // std::cout << "InputDiag::on_motion_notify_event\n"; #endif const bool ret = SKELETON::PrefDiag::on_motion_notify_event( event ); if( ( m_mode & INPUTDIAG_MODE_MOUSE ) && m_control.is_mg_mode() ){ m_control.MG_motion( event ); if( m_str_motion != m_control.get_mg_direction() ){ m_str_motion = m_control.get_mg_direction(); m_label.set_label( m_str_motion + "\n" + get_mouse_label() ); } } return ret; } std::string InputDiag::get_key_label() const { std::string label; for( int mode = CONTROL::MODE_START; mode <= CONTROL::MODE_END; ++mode ){ const std::vector< int > vec_ids = CONTROL::check_key_conflict( mode, m_str_motion ); for( const int id : vec_ids ) { label += "\n" + CONTROL::get_label( id ) + " ( " + CONTROL::get_mode_label( mode ) + " )"; if( mode == m_controlmode && id != m_id ) label += " ×"; } } return label; } std::string InputDiag::get_mouse_label() const { std::string label; for( int mode = CONTROL::MODE_START; mode <= CONTROL::MODE_END; ++mode ){ const std::vector< int > vec_ids = CONTROL::check_mouse_conflict( mode, m_str_motion ); for( const int id : vec_ids ) { label += "\n" + CONTROL::get_label( id ) + " ( " + CONTROL::get_mode_label( mode ) + " )"; if( mode == m_controlmode && id != m_id ) label += " ×"; } } return label; } std::string InputDiag::get_button_label() const { std::string label; for( int mode = CONTROL::MODE_START; mode <= CONTROL::MODE_END; ++mode ){ const std::vector< int > vec_ids = CONTROL::check_button_conflict( mode, m_str_motion ); for( const int id : vec_ids ) { label.append( "\n" + CONTROL::get_label( id ) + " ( " + CONTROL::get_mode_label( mode ) + " )" ); } } return label; } /////////////////////////////////// // // 個別のショートカットキー設定ダイアログ // MouseKeyDiag::MouseKeyDiag( Gtk::Window* parent, const std::string& url, const int id, const std::string& target, const std::string& str_motions ) : SKELETON::PrefDiag( parent, url ) , m_id( id ) , m_controlmode( CONTROL::get_mode( m_id ) ) , m_label( "編集したい" + target + "設定をダブルクリックして下さい。" ) , m_button_delete( g_dpgettext( GTK_DOMAIN, "Stock label\x04_Delete", 12 ), true ) , m_button_add( g_dpgettext( GTK_DOMAIN, "Stock label\x04_Add", 12 ), true ) , m_button_reset( "デフォルト" ) { m_liststore = Gtk::ListStore::create( m_columns ); m_treeview.set_model( m_liststore ); m_treeview.set_size_request( 320, 200 ); m_treeview.signal_row_activated().connect( sigc::mem_fun( *this, &MouseKeyDiag::slot_row_activated ) ); Gtk::TreeViewColumn* column = Gtk::manage( new Gtk::TreeViewColumn( target, m_columns.m_col_motion ) ); column->set_resizable( true ); m_treeview.append_column( *column ); m_scrollwin.add( m_treeview ); m_scrollwin.set_policy( Gtk::POLICY_AUTOMATIC, Gtk::POLICY_ALWAYS ); m_button_delete.signal_clicked().connect( sigc::mem_fun( *this, &MouseKeyDiag::slot_delete ) ); m_button_add.signal_clicked().connect( sigc::mem_fun( *this, &MouseKeyDiag::slot_add ) ); m_button_reset.signal_clicked().connect( sigc::mem_fun( *this, &MouseKeyDiag::slot_reset ) ); m_vbuttonbox.pack_start( m_button_delete, Gtk::PACK_SHRINK ); m_vbuttonbox.pack_start( m_button_add, Gtk::PACK_SHRINK ); m_vbuttonbox.pack_start( m_button_reset, Gtk::PACK_SHRINK ); m_vbuttonbox.set_layout( Gtk::BUTTONBOX_START ); m_vbuttonbox.set_spacing( 4 ); m_hbox.pack_start( m_scrollwin, Gtk::PACK_EXPAND_WIDGET ); m_hbox.pack_start( m_vbuttonbox, Gtk::PACK_SHRINK ); get_content_area()->set_spacing( 8 ); get_content_area()->pack_start( m_label, Gtk::PACK_SHRINK ); get_content_area()->pack_start( m_hbox ); show_all_children(); set_title( CONTROL::get_label( m_id ) + " ( " + CONTROL::get_mode_label( m_controlmode ) + " )" ); // キー設定をスペース毎に区切って行を作成 std::list< std::string > list_motions = MISC::StringTokenizer( str_motions, ' ' ); if( list_motions.size() ){ for( const std::string& motion : list_motions ) append_row( MISC::remove_space( motion ) ); // 先頭にカーソルセット Gtk::TreeModel::Children children = m_liststore->children(); Gtk::TreeModel::iterator it_row = children.begin(); if( *it_row ) m_treeview.set_cursor( m_liststore->get_path( *it_row ) ); } } Gtk::TreeModel::Row MouseKeyDiag::append_row( const std::string& motion ) { Gtk::TreeModel::Row row; row = *( m_liststore->append() ); if( row ) row[ m_columns.m_col_motion ] = motion; return row; } std::string MouseKeyDiag::get_str_motions() { std::string str_motions; Gtk::TreeModel::Children children = m_liststore->children(); for( const Gtk::TreeRow& row : children ) { if( ! str_motions.empty() ) str_motions.push_back( ' ' ); str_motions.append( row[ m_columns.m_col_motion ] ); } #ifdef _DEBUG std::cout << "MouseKeyDiag::get_str_motions motions = " << str_motions << std::endl; #endif return str_motions; } // // 入力ダイアログを表示 // std::string MouseKeyDiag::show_inputdiag( bool is_append ) { std::string str_motion; if( m_single ){ // 1つだけしか設定できない場合 const int count = get_count(); if( count > 1 || ( count == 1 && is_append ) ){ SKELETON::MsgDiag mdiag( nullptr, "この項目には、1つだけ設定できます。" ); mdiag.run(); return std::string(); } } std::unique_ptr diag = create_inputdiag(); if( diag == nullptr ) return std::string(); while( diag->run() == Gtk::RESPONSE_OK ){ // 設定が重複していないかチェック bool conflict = false; const std::vector< int > vec_ids = check_conflict( m_controlmode, diag->get_str_motion() ); auto it = std::find_if( vec_ids.cbegin(), vec_ids.cend(), [this]( int id ) { return id != CONTROL::None && id != m_id; } ); if( it != vec_ids.cend() ) { const std::string label = CONTROL::get_label( *it ); SKELETON::MsgDiag mdiag( nullptr, diag->get_str_motion() + "\n\nは「" + label + "」で使用されています" ); mdiag.run(); conflict = true; } if( ! conflict ){ str_motion = diag->get_str_motion(); break; } } return str_motion; } // 行をダブルクリック void MouseKeyDiag::slot_row_activated( const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn* column ) { #ifdef _DEBUG std::cout << "MouseKeyDiag::slot_row_activated path = " << path.to_string() << std::endl; #endif Gtk::TreeModel::Row row = *( m_liststore->get_iter( path ) ); if( ! row ) return; std::string str_motion = show_inputdiag( false ); if( ! str_motion.empty() ) { row[ m_columns.m_col_motion ] = str_motion; static_cast( row ); // cppcheck: unreadVariable } } // 行削除 void MouseKeyDiag::slot_delete() { std::vector< Gtk::TreeModel::Path > rows = m_treeview.get_selection()->get_selected_rows(); if( ! rows.size() ) return; Gtk::TreeModel::Path path = *rows.begin(); Gtk::TreeModel::iterator row = *( m_liststore->get_iter( path ) ); if( ! row ) return; // 削除する行の次の行にカーソルを移動 Gtk::TreeModel::Path path_next = path; path_next.next(); Gtk::TreeModel::iterator row_next = *( m_liststore->get_iter( path_next ) ); if( row_next ) m_treeview.set_cursor( path_next ); else{ // 次が無ければ前の行にカーソル移動 Gtk::TreeModel::Path path_prev = path; path_prev.prev(); Gtk::TreeModel::iterator row_prev = *( m_liststore->get_iter( path_prev ) ); if( row_prev ) m_treeview.set_cursor( path_prev ); } m_liststore->erase( row ); } // 行追加 void MouseKeyDiag::slot_add() { std::string str_motion = show_inputdiag( true ); if( str_motion.empty() ) return; // 既に登録済みか調べる Gtk::TreeModel::Children children = m_liststore->children(); for( const Gtk::TreeRow& row : children ) { const std::string& motion_tmp = row[ m_columns.m_col_motion ]; if( str_motion == motion_tmp ) return; } Gtk::TreeModel::iterator it_new = m_liststore->append(); ( *it_new )[ m_columns.m_col_motion ] = str_motion; static_cast( *it_new ); // cppcheck: unreadVariable } // デフォルトに戻す void MouseKeyDiag::slot_reset() { const std::string default_motions = get_default_motions( m_id ); #ifdef _DEBUG std::cout << "MouseKeyDiag::slot_reset default = " << default_motions << std::endl; #endif // デフォルト設定が既に使われていないか確認 std::list< std::string > list_motions = MISC::StringTokenizer( default_motions, ' ' ); std::list< std::string > list_defaults; for( const std::string& raw_motion : list_motions ) { std::string motion = MISC::remove_space( raw_motion ); bool conflict = false; const std::vector< int > vec_ids = check_conflict( m_controlmode, motion ); auto it = std::find_if( vec_ids.cbegin(), vec_ids.cend(), [this]( int id ) { return id != CONTROL::None && id != m_id; } ); if( it != vec_ids.cend() ) { const std::string label = CONTROL::get_label( *it ); SKELETON::MsgDiag mdiag( nullptr, motion + "\n\nは「" + label + "」で使用されています" ); mdiag.run(); conflict = true; } if( ! conflict ) list_defaults.push_back( std::move( motion ) ); } // クリアして再登録 m_liststore->clear(); for( const std::string& motion : list_defaults ) append_row( motion ); } /////////////////////////////////// // // マウスジェスチャ、キーボード設定ダイアログの基底クラス // MouseKeyPref::MouseKeyPref( Gtk::Window* parent, const std::string& url, const std::string& target ) : SKELETON::PrefDiag( parent, url ), m_button_reset( "全てデフォルト設定に戻す" ), m_label( "編集したい" + target + "設定をダブルクリックして下さい。" ) { m_liststore = Gtk::ListStore::create( m_columns ); m_treeview.set_model( m_liststore ); m_treeview.set_size_request( 640, 400 ); m_treeview.signal_row_activated().connect( sigc::mem_fun( *this, &MouseKeyPref::slot_row_activated ) ); Gtk::TreeViewColumn* column = Gtk::manage( new Gtk::TreeViewColumn( "コマンド", m_columns.m_col_label ) ); column->set_fixed_width( 220 ); column->set_sizing( Gtk::TREE_VIEW_COLUMN_FIXED ); column->set_resizable( true ); m_treeview.append_column( *column ); column = Gtk::manage( new Gtk::TreeViewColumn( target, m_columns.m_col_motions ) ); column->set_resizable( true ); m_treeview.append_column( *column ); Gtk::CellRenderer *cell = column->get_first_cell(); if( cell ) column->set_cell_data_func( *cell, sigc::mem_fun( *this, &MouseKeyPref::slot_cell_data ) ); m_scrollwin.add( m_treeview ); m_scrollwin.set_policy( Gtk::POLICY_AUTOMATIC, Gtk::POLICY_ALWAYS ); m_scrollwin.set_propagate_natural_height( true ); m_scrollwin.set_propagate_natural_width( true ); m_button_reset.signal_clicked().connect( sigc::mem_fun( *this, &MouseKeyPref::slot_reset ) ); m_hbox.pack_start( m_button_reset, Gtk::PACK_SHRINK ); get_content_area()->set_spacing( 8 ); get_content_area()->pack_start( m_label, Gtk::PACK_SHRINK ); get_content_area()->pack_start( m_scrollwin ); get_content_area()->pack_start( m_hbox, Gtk::PACK_SHRINK ); show_all_children(); set_title( target + "設定" ); } // 行追加 void MouseKeyPref::append_row( const int id, const std::string& label ) { Gtk::TreeModel::Row row; row = *( get_liststore()->append() ); if( row ){ const std::string motions = get_str_motions( id ); if( label.empty() ){ row[ get_colums().m_col_label ] = CONTROL::get_label( id ); }else{ row[ get_colums().m_col_label ] = label; } row[ get_colums().m_col_motions ] = motions; row[ get_colums().m_col_id ] = id; row.set_value( get_colums().m_col_drawbg, motions != get_default_motions( id ) ); } } // // コメント行追加 // void MouseKeyPref::append_comment_row( const std::string& comment ) { Gtk::TreeModel::Row row; row = *( m_liststore->append() ); if( row ){ row[ m_columns.m_col_label ] = comment; row[ m_columns.m_col_motions ] = std::string(); row[ m_columns.m_col_id ] = CONTROL::None; row[ m_columns.m_col_drawbg ] = false; static_cast( row ); // cppcheck: unreadVariable } } // // デフォルト設定に戻す // void MouseKeyPref::slot_reset() { const Gtk::TreeModel::Children children = get_liststore()->children(); for( Gtk::TreeRow row : children ) { if( row ){ const int id = row[ get_colums().m_col_id ]; if( id != CONTROL::None ){ const std::string str_motions = get_default_motions( id ); row[ get_colums().m_col_motions ] = str_motions; row[ get_colums().m_col_drawbg ] = false; remove_motions( id ); set_motions( id, str_motions ); } } } } // // 行をダブルクリック // void MouseKeyPref::slot_row_activated( const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn* column ) { #ifdef _DEBUG std::cout << "MouseKeyPref::slot_row_activated path = " << path.to_string() << std::endl; #endif Gtk::TreeModel::Row row = *( get_liststore()->get_iter( path ) ); if( ! row ) return; const int id = row[ get_colums().m_col_id ]; if( id == CONTROL::None ) return; std::unique_ptr diag = create_setting_diag( id, row[ get_colums().m_col_motions ] ); if( diag->run() == Gtk::RESPONSE_OK ){ const std::string motions = diag->get_str_motions(); row[ get_colums().m_col_motions ] = motions; row.set_value( get_colums().m_col_drawbg, motions != get_default_motions( id ) ); remove_motions( id ); set_motions( id, motions ); } } // // 実際の描画の際に cellrendere のプロパティをセットするスロット関数 // void MouseKeyPref::slot_cell_data( Gtk::CellRenderer* cell, const Gtk::TreeModel::iterator& it ) { Gtk::TreeModel::Row row = *it; if( row[ m_columns.m_col_drawbg ] ){ cell->property_cell_background() = CONFIG::get_color( COLOR_BACK_HIGHLIGHT_TREE ); cell->property_cell_background_set() = true; } else cell->property_cell_background_set() = false; } jdim-0.7.0/src/control/mousekeypref.h000066400000000000000000000132171417047150700176110ustar00rootroot00000000000000// ライセンス: GPL2 // // マウスジェスチャ、キーボード設定ダイアログの基底クラス // #ifndef _MOUSEKEYPREFPREF_H #define _MOUSEKEYPREFPREF_H #include "skeleton/prefdiag.h" #include "skeleton/treeviewbase.h" #include "control.h" #include namespace CONTROL { enum { INPUTDIAG_MODE_BUTTON = 1, INPUTDIAG_MODE_MOUSE = 2, INPUTDIAG_MODE_KEY = 4 }; // // キーやマウス入力ダイアログの基底クラス // class InputDiag : public SKELETON::PrefDiag { int m_id; int m_mode; int m_controlmode; std::string m_str_motion; Gtk::Label m_label; Gtk::HBox m_hbox; CONTROL::Control m_control; public: InputDiag( Gtk::Window* parent, const std::string& url, const int id, const std::string& target, const int mode ); const std::string& get_str_motion() const { return m_str_motion; } void set_str_motion( const std::string& motion ){ m_str_motion = motion; } protected: int get_id() const { return m_id; } std::string get_key_label() const; std::string get_mouse_label() const; std::string get_button_label() const; private: virtual bool on_key_press_event (GdkEventKey* event); virtual bool on_button_press_event( GdkEventButton* event ); virtual bool on_button_release_event( GdkEventButton* event ); virtual bool on_motion_notify_event( GdkEventMotion* event ); }; /////////////////////////////////////// class MouseKeyDiagColumn : public Gtk::TreeModel::ColumnRecord { public: Gtk::TreeModelColumn< std::string > m_col_motion; MouseKeyDiagColumn() { add( m_col_motion ); } }; // // 個別のマウスジェスチャ、ショートカットキー設定ダイアログの基底クラス // class MouseKeyDiag : public SKELETON::PrefDiag { int m_id; int m_controlmode; bool m_single{}; SKELETON::JDTreeViewBase m_treeview; Glib::RefPtr< Gtk::ListStore > m_liststore; MouseKeyDiagColumn m_columns; Gtk::ScrolledWindow m_scrollwin; Gtk::Label m_label; Gtk::Button m_button_delete; Gtk::Button m_button_add; Gtk::Button m_button_reset; Gtk::VButtonBox m_vbuttonbox; Gtk::HBox m_hbox; public: MouseKeyDiag( Gtk::Window* parent, const std::string& url, const int id, const std::string& target, const std::string& str_motions ); std::string get_str_motions(); protected: int get_id() const { return m_id; } int get_controlmode() const { return m_controlmode; } int get_count() const { return m_liststore->children().size(); } int get_single() const { return m_single; } void set_single( bool single ){ m_single = single; } virtual std::unique_ptr create_inputdiag() = 0; virtual std::string get_default_motions( const int id ) = 0; virtual std::vector< int > check_conflict( const int mode, const std::string& str_motion ) = 0; private: Gtk::TreeModel::Row append_row( const std::string& motion ); // 入力ダイアログを表示 std::string show_inputdiag( bool is_append ); // 行をダブルクリック void slot_row_activated( const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn* column ); // 行削除 void slot_delete(); // 行追加 void slot_add(); // デフォルトに戻す void slot_reset(); }; /////////////////////////////////////// class MouseKeyTreeColumn : public Gtk::TreeModel::ColumnRecord { public: Gtk::TreeModelColumn< std::string > m_col_label; Gtk::TreeModelColumn< std::string > m_col_motions; Gtk::TreeModelColumn< int > m_col_id; Gtk::TreeModelColumn< bool > m_col_drawbg; MouseKeyTreeColumn() { add( m_col_label ); add( m_col_motions ); add( m_col_id ); add( m_col_drawbg ); } }; // // マウスジェスチャ、キーボード設定ダイアログの基底クラス // class MouseKeyPref : public SKELETON::PrefDiag { SKELETON::JDTreeViewBase m_treeview; Glib::RefPtr< Gtk::ListStore > m_liststore; MouseKeyTreeColumn m_columns; Gtk::ScrolledWindow m_scrollwin; Gtk::HBox m_hbox; Gtk::Button m_button_reset; Gtk::Label m_label; public: MouseKeyPref( Gtk::Window* parent, const std::string& url, const std::string& target ); protected: Glib::RefPtr< Gtk::ListStore >& get_liststore(){ return m_liststore; } MouseKeyTreeColumn& get_colums(){ return m_columns; } void append_row( const int id, const std::string& label = std::string() ); void append_comment_row( const std::string& comment ); virtual std::unique_ptr create_setting_diag( const int id, const std::string& str_motions ) = 0; virtual std::string get_str_motions( const int id ) = 0; virtual std::string get_default_motions( const int id ) = 0; virtual void set_motions( const int id, const std::string& str_motions ) = 0; virtual bool remove_motions( const int id ) = 0; private: void slot_reset(); void slot_row_activated( const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn* column ); void slot_cell_data( Gtk::CellRenderer* cell, const Gtk::TreeModel::iterator& it ); }; } #endif jdim-0.7.0/src/control/mousepref.cpp000066400000000000000000000101331417047150700174250ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "mousepref.h" #include "controlid.h" #include "controlutil.h" using namespace CONTROL; // // マウスジェスチャ入力をラベルに表示するダイアログ // MouseInputDiag::MouseInputDiag( Gtk::Window* parent, const std::string& url, const int id ) : CONTROL::InputDiag( parent, url, id, "マウスジェスチャ", INPUTDIAG_MODE_MOUSE ) {} /////////////////////////////// // // 個別のマウスジェスチャ設定ダイアログ // MouseDiag::MouseDiag( Gtk::Window* parent, const std::string& url, const int id, const std::string& str_motions ) : CONTROL::MouseKeyDiag( parent, url, id, "マウスジェスチャ", str_motions ) {} std::unique_ptr MouseDiag::create_inputdiag() { return std::make_unique( this, "", get_id() ); } std::string MouseDiag::get_default_motions( const int id ) { return CONTROL::get_default_mousemotions( id ); } std::vector< int > MouseDiag::check_conflict( const int mode, const std::string& str_motion ) { return CONTROL::check_mouse_conflict( mode, str_motion ); } /////////////////////////////////////////////// // // マウスジェスチャ設定ダイアログ // MousePref::MousePref( Gtk::Window* parent, const std::string& url ) : MouseKeyPref( parent, url, "マウスジェスチャ" ) { // マウスジェスチャのバックアップを取る // キャンセルを押したら戻す CONTROL::bkup_mouseconfig(); append_comment_row( "■ "+ CONTROL::get_mode_label( CONTROL::MODE_COMMON ) ); append_row( CONTROL::Right ); append_row( CONTROL::Left ); append_row( CONTROL::TabRight ); append_row( CONTROL::TabLeft ); append_row( CONTROL::TabRightUpdated ); append_row( CONTROL::TabLeftUpdated ); append_row( CONTROL::ToggleArticle ); append_row( CONTROL::ShowSideBar ); append_row( CONTROL::ShowMenuBar ); append_row( CONTROL::ShowToolBarMain ); append_row( CONTROL::Home ); append_row( CONTROL::End ); append_row( CONTROL::Quit ); append_row( CONTROL::CloseAllTabs ); append_row( CONTROL::CloseOtherTabs ); append_row( CONTROL::RestoreLastTab ); append_row( CONTROL::CheckUpdateTabs ); append_row( CONTROL::Reload ); append_row( CONTROL::Delete ); append_row( CONTROL::StopLoading ); append_row( CONTROL::AppendFavorite ); append_row( CONTROL::SearchTitle ); append_row( CONTROL::CheckUpdateRoot ); append_row( CONTROL::CheckUpdateOpenRoot ); append_row( CONTROL::QuitJD ); append_row( CONTROL::MaximizeMainWin ); append_row( CONTROL::IconifyMainWin ); append_comment_row( "" ); append_comment_row( "■ "+ CONTROL::get_mode_label( CONTROL::MODE_BOARD ) ); append_row( CONTROL::NewArticle ); append_comment_row( "" ); append_comment_row( "■ "+ CONTROL::get_mode_label( CONTROL::MODE_ARTICLE ) ); append_row( CONTROL::GotoNew ); append_row( CONTROL::WriteMessage ); append_row( CONTROL::SearchNextArticle ); append_row( CONTROL::SearchWeb ); append_row( CONTROL::LiveStartStop ); append_comment_row( "" ); append_comment_row( "■ "+ CONTROL::get_mode_label( CONTROL::MODE_IMAGEVIEW ) ); append_row( CONTROL::CancelMosaicButton ); } std::unique_ptr MousePref::create_setting_diag( const int id, const std::string& str_motions ) { return std::make_unique( this, "", id, str_motions ); } std::string MousePref::get_str_motions( const int id ) { return CONTROL::get_str_mousemotions( id ); } std::string MousePref::get_default_motions( const int id ) { return CONTROL::get_default_mousemotions( id ); } void MousePref::set_motions( const int id, const std::string& str_motions ) { CONTROL::set_mousemotions( id, str_motions ); } bool MousePref::remove_motions( const int id ) { return CONTROL::remove_mousemotions( id ); } // // キャンセルボタンを押した // void MousePref::slot_cancel_clicked() { #ifdef _DEBUG std::cout << "MousePref::slot_cancel_clicked\n"; #endif // 設定を戻す CONTROL::restore_mouseconfig(); } jdim-0.7.0/src/control/mousepref.h000066400000000000000000000037211417047150700170770ustar00rootroot00000000000000// ライセンス: GPL2 // マウスジェスチャ設定ダイアログ // MousePref が本体で、MousePrefの各行をダブルクリックすると MouseDiag が開いて個別に操作の設定が出来る // MouseDiag の各行をダブルクリックすると MouseInputDiag が開いてキー入力が出来る #ifndef _MOUSEPREFPREF_H #define _MOUSEPREFPREF_H #include "mousekeypref.h" #include "control.h" namespace CONTROL { // // マウスジェスチャ入力をラベルに表示するダイアログ // class MouseInputDiag : public CONTROL::InputDiag { public: MouseInputDiag( Gtk::Window* parent, const std::string& url, const int id ); }; /////////////////////////////////////// // // 個別のマウスジェスチャ設定ダイアログ // class MouseDiag : public CONTROL::MouseKeyDiag { public: MouseDiag( Gtk::Window* parent, const std::string& url, const int id, const std::string& str_motions ); protected: std::unique_ptr create_inputdiag() override; std::string get_default_motions( const int id ) override; std::vector< int > check_conflict( const int mode, const std::string& str_motion ) override; }; /////////////////////////////////////// // // マウスジェスチャ設定ダイアログ // class MousePref : public CONTROL::MouseKeyPref { public: MousePref( Gtk::Window* parent, const std::string& url ); protected: std::unique_ptr create_setting_diag( const int id, const std::string& str_motions ) override; std::string get_str_motions( const int id ) override; std::string get_default_motions( const int id ) override; void set_motions( const int id, const std::string& str_motions ) override; bool remove_motions( const int id ) override; private: void slot_cancel_clicked() override; }; } #endif jdim-0.7.0/src/core.cpp000066400000000000000000005143371417047150700147070ustar00rootroot00000000000000 // ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "core.h" #include "maintoolbar.h" #include "command.h" #include "winmain.h" #include "session.h" #include "global.h" #include "type.h" #include "dndmanager.h" #include "usrcmdmanager.h" #include "linkfiltermanager.h" #include "replacestrmanager.h" #include "compmanager.h" #include "searchmanager.h" #include "aamanager.h" #include "dispatchmanager.h" #include "cssmanager.h" #include "updatemanager.h" #include "login2ch.h" #include "loginbe.h" #include "environment.h" #include "setupwizard.h" #include "cache.h" #include "sharedbuffer.h" #include "control/controlutil.h" #include "control/controlid.h" #include "history/historymanager.h" #include "skeleton/msgdiag.h" #include "config/globalconf.h" #include "config/defaultconf.h" #include "jdlib/cookiemanager.h" #include "jdlib/miscutil.h" #include "jdlib/miscgtk.h" #include "jdlib/misctime.h" #include "jdlib/loader.h" #include "jdlib/timeout.h" #include "dbtree/interface.h" #include "dbimg/imginterface.h" #include "bbslist/bbslistadmin.h" #include "board/boardadmin.h" #include "article/articleadmin.h" #include "image/imageadmin.h" #include "message/messageadmin.h" #include "message/logmanager.h" #include "sound/soundmanager.h" #include using namespace CORE; Core* instance_core; Core* CORE::get_instance() { return instance_core; } // 全ビューをフォーカスアウト #define FOCUS_OUT_ALL() do{ \ ARTICLE::get_admin()->set_command_immediately( "focus_out" ); \ BOARD::get_admin()->set_command_immediately( "focus_out" ); \ BBSLIST::get_admin()->set_command_immediately( "focus_out" ); \ IMAGE::get_admin()->set_command_immediately( "focus_out" ); \ MESSAGE::get_admin()->set_command_immediately( "focus_out" ); \ }while(0) ////////////////////////////////////////////////////// Core::Core( JDWinMain& win_main ) : m_win_main( win_main ) , m_hpaned( SKELETON::PANE_FIXSIZE_PAGE1 ) , m_vpaned_r( SKELETON::PANE_FIXSIZE_PAGE1 ) , m_hpaned_r( SKELETON::PANE_FIXSIZE_PAGE1 ) , m_vpaned_message( SKELETON::PANE_FIXSIZE_PAGE2 ) , m_enable_menuslot( true ) { // ディスパッチマネージャ作成 CORE::get_dispmanager(); instance_core = this; // データベースのルート作成 DBTREE::create_root(); DBIMG::create_root(); // 2chログインマネージャ作成 CORE::get_login2ch(); // BEログインマネージャ作成 CORE::get_loginbe(); // マウス、キー設定読み込み CONTROL::load_conf(); // 各管理クラス作成 BBSLIST::get_admin(); BOARD::get_admin(); ARTICLE::get_admin(); IMAGE::get_admin(); MESSAGE::get_admin(); // D&Dマネージャ作成 CORE::get_dnd_manager(); // ユーザコマンドマネージャ作成 CORE::get_usrcmd_manager(); // リンクフィルタマネージャ作成 CORE::get_linkfilter_manager(); // 文字列置換マネージャ作成 CORE::get_replacestr_manager(); // ログ検索マネージャ作成 CORE::get_search_manager(); // HTTPクッキー管理マネージャ作成 JDLIB::get_cookie_manager(); } Core::~Core() { #ifdef _DEBUG std::cout << "Core::~Core\n"; #endif // デストラクタの中からdispatchを呼ぶと落ちるので dispatch不可にする set_dispatchable( false ); SESSION::set_quitting( true ); // ローダの起動待ちキューにあるスレッドを実行しない // アプリ終了時にこの関数を呼び出さないとキューに登録されたスレッドが起動してしまうので注意 JDLIB::disable_pop_loader_queue(); // 削除リストに登録されているスレを削除 ( 実況したスレなど ) const std::vector< std::string >& dellist = SESSION::get_delete_list(); if( dellist.size() ){ for( const std::string& url : dellist ) { // しおりが付いている場合は削除しない if( ! DBTREE::is_bookmarked_thread( url ) && ! DBTREE::get_num_bookmark( url ) ){ DBTREE::delete_article( url, false ); ARTICLE::get_admin()->set_command_immediately( "unlock_views", url ); ARTICLE::get_admin()->set_command_immediately( "close_view", url, "closeall" // command.url を含む全てのビューを閉じる ); } } } save_session(); // HTTPクッキー管理マネージャ削除 JDLIB::delete_cookie_manager(); // ログ検索マネージャ削除 CORE::delete_search_manager(); // 文字列置換マネージャ削除 CORE::delete_replacestr_manager(); // ユーザコマンドマネージャ削除 CORE::delete_usrcmd_manager(); // リンクフィルタマネージャ削除 CORE::delete_linkfilter_manager(); // 補完マネージャ削除 CORE::delete_completion_manager(); // D&Dマネージャ削除 CORE::delete_dnd_manager(); // AA マネージャ削除 CORE::delete_aamanager(); // 更新チェックマネージャ削除 CORE::delete_checkupdate_manager(); // マウス、キーコンフィグ削除 CONTROL::delete_conf(); // ビューを削除する前にswitch_pageをdisconnectしておかないとエラーが出る if( m_sigc_switch_page.connected() ) m_sigc_switch_page.disconnect(); // 各管理クラスを削除 BBSLIST::delete_admin(); BOARD::delete_admin(); ARTICLE::delete_admin(); IMAGE::delete_admin(); MESSAGE::delete_admin(); // 履歴マネージャ削除 HISTORY::delete_history_manager(); // cssマネージャ削除 CORE::delete_css_manager(); // 2chログインマネージャ削除 CORE::delete_login2ch(); // BEログインマネージャ削除 CORE::delete_loginbe(); // データベース削除 DBTREE::delete_root(); DBIMG::delete_root(); // サウンドマネージャ削除 SOUND::delete_sound_manager(); // 書き込みログマネージャ削除 MESSAGE::delete_log_manager(); // ディスパッチマネージャ削除 CORE::delete_dispatchmanager(); // ツールバー削除 m_toolbar.reset(); // 設定削除 CONFIG::delete_confitem(); } // // セッション保存 // void Core::save_session() { // 設定保存 CONFIG::save_conf(); // マウス、キーコンフィグ保存 CONTROL::save_conf(); // PANEの敷居の位置保存 SESSION::set_hpane_main_pos( m_hpaned.get_ctrl().get_position() ); SESSION::set_vpane_main_pos( m_vpaned_r.get_ctrl().get_position() ); SESSION::set_hpane_main_r_pos( m_hpaned_r.get_ctrl().get_position() ); SESSION::set_vpane_main_mes_pos( m_vpaned_message.get_ctrl().get_position() ); CORE::get_completion_manager()->save_session(); CORE::get_aamanager()->save_history(); BBSLIST::get_admin()->save_session(); BOARD::get_admin()->save_session(); ARTICLE::get_admin()->save_session(); IMAGE::get_admin()->save_session(); MESSAGE::get_admin()->save_session(); // 内部で SESSION::get_*_URLs() を使用しているので // ARTICLEやBOARDなどの管理クラスのセッションを保存してから呼び出すこと HISTORY::get_history_manager()->viewhistory2xml(); // 全スレ情報保存 // 板情報は BOARD::get_admin()->save_session() で保存される DBTREE::save_articleinfo_all(); SESSION::save_session(); } Gtk::Widget* Core::get_toplevel() { return m_win_main.get_toplevel(); } // // 実行 // // init = true なら初回起動 // skip_setupdiag = true なら初回起動時にセットアップダイアログ非表示 // void Core::run( const bool init, const bool skip_setupdiag ) { // 初回起動時の設定 if( init && ! skip_setupdiag ) first_setup(); // メインメニューの設定 m_action_group = Gtk::ActionGroup::create(); // File m_action_group->add( Gtk::Action::create( "Menu_File", "ファイル(_F)" ) ); m_action_group->add( Gtk::Action::create( "OpenURL", "OpenURL"), sigc::mem_fun( *this, &Core::slot_openurl ) ); m_action_group->add( Gtk::ToggleAction::create( "Online", "オフライン作業(_W)", std::string(), ! SESSION::is_online() ), sigc::mem_fun( *this, &Core::slot_toggle_online ) ); m_action_group->add( Gtk::ToggleAction::create( "Login2ch", "2chにログイン(_L)", std::string(), false ), sigc::mem_fun( *this, &Core::slot_toggle_login2ch ) ); m_action_group->add( Gtk::ToggleAction::create( "LoginBe", "BEにログイン(_B)", std::string(), false ), sigc::mem_fun( *this, &Core::slot_toggle_loginbe ) ); m_action_group->add( Gtk::Action::create( "ReloadList", "板一覧再読込(_R)"), sigc::mem_fun( *this, &Core::slot_reload_list ) ); m_action_group->add( Gtk::Action::create( "SaveSession", "セッション保存(_S)"), sigc::mem_fun( *this, &Core::save_session ) ); Gtk::AccelKey jdexitKey = CONTROL::get_accelkey( CONTROL::JDExit ); if( jdexitKey.is_null() ){ m_action_group->add( Gtk::Action::create( "Quit", "終了(_Q)" ), sigc::mem_fun(*this, &Core::slot_quit ) ); }else{ m_action_group->add( Gtk::Action::create( "Quit", "終了(_Q)" ), jdexitKey, sigc::mem_fun(*this, &Core::slot_quit ) ); } ////////////////////////////////////////////////////// // 表示 m_action_group->add( Gtk::Action::create( "Menu_View", "表示(_V)" ) ); m_action_group->add( Gtk::Action::create( "Show_Board", "スレ一覧(_B)" ), sigc::bind< bool >( sigc::mem_fun(*this, &Core::switch_board ), false ) ); m_action_group->add( Gtk::Action::create( "Show_Thread", "スレビュー(_T)" ), sigc::bind< bool >( sigc::mem_fun(*this, &Core::switch_article ), false ) ); m_action_group->add( Gtk::Action::create( "Show_Image", "画像ビュー(_I)" ), sigc::bind< bool >( sigc::mem_fun(*this, &Core::switch_image ), false ) ); // サイドバー m_action_group->add( Gtk::Action::create( "Sidebar_Menu", "サイドバー(_S)" ) ); m_action_group->add( Gtk::ToggleAction::create( "Show_BBS", "板一覧(_B)", std::string(), SESSION::show_sidebar() ), sigc::bind< std::string, bool >( sigc::mem_fun(*this, &Core::switch_sidebar ), URL_BBSLISTVIEW, false ) ); m_action_group->add( Gtk::ToggleAction::create( "Show_FAVORITE", std::string( ITEM_NAME_FAVORITEVIEW ) + "(_F)", std::string(), SESSION::show_sidebar() ), sigc::bind< std::string, bool >( sigc::mem_fun(*this, &Core::switch_sidebar ), URL_FAVORITEVIEW, false ) ); m_action_group->add( Gtk::ToggleAction::create( "Show_HISTTHREAD", std::string( ITEM_NAME_HISTVIEW ) + "(_T)", std::string(), SESSION::show_sidebar() ), sigc::bind< std::string, bool >( sigc::mem_fun(*this, &Core::switch_sidebar ), URL_HISTTHREADVIEW, false ) ); m_action_group->add( Gtk::ToggleAction::create( "Show_HISTBOARD", std::string( ITEM_NAME_HIST_BOARDVIEW ) + "(_B)", std::string(), SESSION::show_sidebar() ), sigc::bind< std::string, bool >( sigc::mem_fun(*this, &Core::switch_sidebar ), URL_HISTBOARDVIEW, false ) ); m_action_group->add( Gtk::ToggleAction::create( "Show_HISTCLOSE", std::string( ITEM_NAME_HIST_CLOSEVIEW ) + "(_M)", std::string(), SESSION::show_sidebar() ), sigc::bind< std::string, bool >( sigc::mem_fun(*this, &Core::switch_sidebar ), URL_HISTCLOSEVIEW, false ) ); m_action_group->add( Gtk::ToggleAction::create( "Show_HISTCLOSEBOARD", std::string( ITEM_NAME_HIST_CLOSEBOARDVIEW ) + "(_N)", std::string(), SESSION::show_sidebar() ), sigc::bind< std::string, bool >( sigc::mem_fun(*this, &Core::switch_sidebar ), URL_HISTCLOSEBOARDVIEW, false ) ); m_action_group->add( Gtk::ToggleAction::create( "Show_HISTCLOSEIMG", std::string( ITEM_NAME_HIST_CLOSEIMGVIEW ) + "(_I)", std::string(), SESSION::show_sidebar() ), sigc::bind< std::string, bool >( sigc::mem_fun(*this, &Core::switch_sidebar ), URL_HISTCLOSEIMGVIEW, false ) ); m_action_group->add( Gtk::Action::create( "View_Menu", "詳細設定(_D)" ) ); // 一般 m_action_group->add( Gtk::ToggleAction::create( "ShowMenuBar", "ShowMenuBar", std::string(), false ), sigc::mem_fun( *this, &Core::toggle_menubar ) ); m_action_group->add( Gtk::ToggleAction::create( "ShowStatBar", "ステータスバー表示(_S)", std::string(), false ), sigc::mem_fun( *this, &Core::toggle_statbar ) ); m_action_group->add( Gtk::ToggleAction::create( "ToggleFlatButton", "ボタンをフラット表示(_F)", std::string(), false ), sigc::mem_fun( *this, &Core::toggle_flat_button ) ); m_action_group->add( Gtk::ToggleAction::create( "ToggleDrawToolbarback", "ツールバーの背景を描画する(_T)", std::string(), false ), sigc::mem_fun( *this, &Core::toggle_draw_toolbarback ) ); m_action_group->add( Gtk::ToggleAction::create( "TogglePostMark", "自分が書き込んだレスにマークをつける(_W)", std::string(), CONFIG::get_show_post_mark() ), sigc::mem_fun( *this, &Core::toggle_post_mark ) ); // since Gtk::RadioButtonGroup radiogroup_since; m_action_group->add( Gtk::Action::create( "Since_Menu", "スレ一覧の since 表示(_N)" ) ); Glib::RefPtr< Gtk::RadioAction > raction_since0 = Gtk::RadioAction::create( radiogroup_since, "Since_Normal", "年/月/日 時:分" ); Glib::RefPtr< Gtk::RadioAction > raction_since1 = Gtk::RadioAction::create( radiogroup_since, "Since_NoYear", "月/日 時:分" ); Glib::RefPtr< Gtk::RadioAction > raction_since2 = Gtk::RadioAction::create( radiogroup_since, "Since_Week", "年/月/日(曜日) 時:分:秒" ); Glib::RefPtr< Gtk::RadioAction > raction_since3 = Gtk::RadioAction::create( radiogroup_since, "Since_Second", "年/月/日 時:分:秒" ); Glib::RefPtr< Gtk::RadioAction > raction_since4 = Gtk::RadioAction::create( radiogroup_since, "Since_Passed", "~前" ); switch( SESSION::get_col_since_time() ){ case MISC::TIME_NORMAL: raction_since0->set_active( true ); break; case MISC::TIME_NO_YEAR: raction_since1->set_active( true ); break; case MISC::TIME_WEEK: raction_since2->set_active( true ); break; case MISC::TIME_SECOND: raction_since3->set_active( true ); break; case MISC::TIME_PASSED: raction_since4->set_active( true ); break; } m_action_group->add( raction_since0, sigc::bind< int >( sigc::mem_fun( *this, &Core::slot_toggle_since ), MISC::TIME_NORMAL ) ); m_action_group->add( raction_since1, sigc::bind< int >( sigc::mem_fun( *this, &Core::slot_toggle_since ), MISC::TIME_NO_YEAR ) ); m_action_group->add( raction_since2, sigc::bind< int >( sigc::mem_fun( *this, &Core::slot_toggle_since ), MISC::TIME_WEEK ) ); m_action_group->add( raction_since3, sigc::bind< int >( sigc::mem_fun( *this, &Core::slot_toggle_since ), MISC::TIME_SECOND ) ); m_action_group->add( raction_since4, sigc::bind< int >( sigc::mem_fun( *this, &Core::slot_toggle_since ), MISC::TIME_PASSED ) ); // 最終書き込み Gtk::RadioButtonGroup radiogroup_write; m_action_group->add( Gtk::Action::create( "Write_Menu", "スレ一覧の最終書込表示(_N)" ) ); Glib::RefPtr< Gtk::RadioAction > raction_write0 = Gtk::RadioAction::create( radiogroup_write, "Write_Normal", "年/月/日 時:分" ); Glib::RefPtr< Gtk::RadioAction > raction_write1 = Gtk::RadioAction::create( radiogroup_write, "Write_NoYear", "月/日 時:分" ); Glib::RefPtr< Gtk::RadioAction > raction_write2 = Gtk::RadioAction::create( radiogroup_write, "Write_Week", "年/月/日(曜日) 時:分:秒" ); Glib::RefPtr< Gtk::RadioAction > raction_write3 = Gtk::RadioAction::create( radiogroup_write, "Write_Second", "年/月/日 時:分:秒" ); Glib::RefPtr< Gtk::RadioAction > raction_write4 = Gtk::RadioAction::create( radiogroup_write, "Write_Passed", "~前" ); switch( SESSION::get_col_write_time() ){ case MISC::TIME_NORMAL: raction_write0->set_active( true ); break; case MISC::TIME_NO_YEAR: raction_write1->set_active( true ); break; case MISC::TIME_WEEK: raction_write2->set_active( true ); break; case MISC::TIME_SECOND: raction_write3->set_active( true ); break; case MISC::TIME_PASSED: raction_write4->set_active( true ); break; } m_action_group->add( raction_write0, sigc::bind< int >( sigc::mem_fun( *this, &Core::slot_toggle_write ), MISC::TIME_NORMAL ) ); m_action_group->add( raction_write1, sigc::bind< int >( sigc::mem_fun( *this, &Core::slot_toggle_write ), MISC::TIME_NO_YEAR ) ); m_action_group->add( raction_write2, sigc::bind< int >( sigc::mem_fun( *this, &Core::slot_toggle_write ), MISC::TIME_WEEK ) ); m_action_group->add( raction_write3, sigc::bind< int >( sigc::mem_fun( *this, &Core::slot_toggle_write ), MISC::TIME_SECOND ) ); m_action_group->add( raction_write4, sigc::bind< int >( sigc::mem_fun( *this, &Core::slot_toggle_write ), MISC::TIME_PASSED ) ); // 最終アクセス Gtk::RadioButtonGroup radiogroup_access; m_action_group->add( Gtk::Action::create( "Access_Menu", "スレ一覧の最終取得表示(_N)" ) ); Glib::RefPtr< Gtk::RadioAction > raction_access0 = Gtk::RadioAction::create( radiogroup_access, "Access_Normal", "年/月/日 時:分" ); Glib::RefPtr< Gtk::RadioAction > raction_access1 = Gtk::RadioAction::create( radiogroup_access, "Access_NoYear", "月/日 時:分" ); Glib::RefPtr< Gtk::RadioAction > raction_access2 = Gtk::RadioAction::create( radiogroup_access, "Access_Week", "年/月/日(曜日) 時:分:秒" ); Glib::RefPtr< Gtk::RadioAction > raction_access3 = Gtk::RadioAction::create( radiogroup_access, "Access_Second", "年/月/日 時:分:秒" ); Glib::RefPtr< Gtk::RadioAction > raction_access4 = Gtk::RadioAction::create( radiogroup_access, "Access_Passed", "~前" ); switch( SESSION::get_col_access_time() ){ case MISC::TIME_NORMAL: raction_access0->set_active( true ); break; case MISC::TIME_NO_YEAR: raction_access1->set_active( true ); break; case MISC::TIME_WEEK: raction_access2->set_active( true ); break; case MISC::TIME_SECOND: raction_access3->set_active( true ); break; case MISC::TIME_PASSED: raction_access4->set_active( true ); break; } m_action_group->add( raction_access0, sigc::bind< int >( sigc::mem_fun( *this, &Core::slot_toggle_access ), MISC::TIME_NORMAL ) ); m_action_group->add( raction_access1, sigc::bind< int >( sigc::mem_fun( *this, &Core::slot_toggle_access ), MISC::TIME_NO_YEAR ) ); m_action_group->add( raction_access2, sigc::bind< int >( sigc::mem_fun( *this, &Core::slot_toggle_access ), MISC::TIME_WEEK ) ); m_action_group->add( raction_access3, sigc::bind< int >( sigc::mem_fun( *this, &Core::slot_toggle_access ), MISC::TIME_SECOND ) ); m_action_group->add( raction_access4, sigc::bind< int >( sigc::mem_fun( *this, &Core::slot_toggle_access ), MISC::TIME_PASSED ) ); // ツールバー表示 m_action_group->add( Gtk::Action::create( "Toolbar_Menu", "ツールバー表示(_T)" ) ); // メインツールバー m_action_group->add( Gtk::ToggleAction::create( "ShowToolBarMain", "ShowToolBarMain", std::string(), false ), sigc::mem_fun( *this, &Core::slot_toggle_toolbarmain ) ); Gtk::RadioButtonGroup radiogroup_toolbar; m_action_group->add( Gtk::Action::create( "Toolbar_Main_Menu", "メインツールバーの位置(_P)" ) ); Glib::RefPtr< Gtk::RadioAction > raction_toolbarpos0 = Gtk::RadioAction::create( radiogroup_toolbar, "ToolbarPos0", "メニューバーの下に表示する(_U)" ); Glib::RefPtr< Gtk::RadioAction > raction_toolbarpos1 = Gtk::RadioAction::create( radiogroup_toolbar, "ToolbarPos1", "サイドバーの右に表示する(_R)" ); switch( SESSION::get_toolbar_pos() ){ case SESSION::TOOLBAR_POS_NORMAL: raction_toolbarpos0->set_active( true ); break; case SESSION::TOOLBAR_POS_RIGHT: raction_toolbarpos1->set_active( true ); break; } m_action_group->add( raction_toolbarpos0, sigc::bind< int >( sigc::mem_fun( *this, &Core::slot_toggle_toolbarpos ), SESSION::TOOLBAR_POS_NORMAL ) ); m_action_group->add( raction_toolbarpos1, sigc::bind< int >( sigc::mem_fun( *this, &Core::slot_toggle_toolbarpos ), SESSION::TOOLBAR_POS_RIGHT ) ); // その他のツールバー m_action_group->add( Gtk::ToggleAction::create( "ShowToolBarBbslist", "サイドバーのツールバー表示(_S)", std::string(), false ), sigc::mem_fun( *this, &Core::slot_toggle_toolbarbbslist ) ); m_action_group->add( Gtk::ToggleAction::create( "ShowToolBarBoard", "スレ一覧のツールバー表示(_B)", std::string(), false ), sigc::mem_fun( *this, &Core::slot_toggle_toolbarboard ) ); m_action_group->add( Gtk::ToggleAction::create( "ShowToolBarArticle", "スレビューのツールバー表示(_A)", std::string(), false ), sigc::mem_fun( *this, &Core::slot_toggle_toolbararticle ) ); // タブ表示 m_action_group->add( Gtk::Action::create( "Tab_Menu", "タブ表示(_B)" ) ); m_action_group->add( Gtk::ToggleAction::create( "TabBoard", "スレ一覧(_B)", std::string(), false ), sigc::mem_fun( *this, &Core::slot_toggle_tabboard ) ); m_action_group->add( Gtk::ToggleAction::create( "TabArticle", "スレビュー(_A)", std::string(), false ), sigc::mem_fun( *this, &Core::slot_toggle_tabarticle ) ); // pane 設定 Gtk::RadioButtonGroup radiogroup; Glib::RefPtr< Gtk::RadioAction > raction0 = Gtk::RadioAction::create( radiogroup, "2Pane", "2ペイン表示(_2)" ); Glib::RefPtr< Gtk::RadioAction > raction1 = Gtk::RadioAction::create( radiogroup, "3Pane", "3ペイン表示(_3)" ); Glib::RefPtr< Gtk::RadioAction > raction2 = Gtk::RadioAction::create( radiogroup, "v3Pane", "縦3ペイン表示(_V)" ); switch( SESSION::get_mode_pane() ){ case SESSION::MODE_2PANE: raction0->set_active( true ); break; case SESSION::MODE_3PANE: raction1->set_active( true ); break; case SESSION::MODE_V3PANE: raction2->set_active( true ); break; } m_action_group->add( raction0, sigc::mem_fun( *this, &Core::slot_toggle_2pane ) ); m_action_group->add( raction1, sigc::mem_fun( *this, &Core::slot_toggle_3pane ) ); m_action_group->add( raction2, sigc::mem_fun( *this, &Core::slot_toggle_v3pane ) ); // フルスクリーン m_action_group->add( Gtk::ToggleAction::create( "FullScreen", "FullScreen", std::string(), false ), sigc::mem_fun( *this, &Core::slot_toggle_fullscreen ) ); // 書き込みビュー m_action_group->add( Gtk::Action::create( "MessageView_Menu", "書き込み設定(_M)" ) ); m_action_group->add( Gtk::Action::create( "ShowMsgView_Menu", "書き込みビュー(_M)" ) ); Gtk::RadioButtonGroup radiogroup_msg; Glib::RefPtr< Gtk::RadioAction > raction_msg0 = Gtk::RadioAction::create( radiogroup_msg, "UseWinMsg", "ウィンドウ表示する(_W)" ); Glib::RefPtr< Gtk::RadioAction > raction_msg1 = Gtk::RadioAction::create( radiogroup_msg, "UseEmbMsg", "埋め込み表示する(_E)" ); if( ! SESSION::get_embedded_mes() ) raction_msg0->set_active( true ); else raction_msg1->set_active( true ); m_action_group->add( raction_msg0, sigc::mem_fun( *this, &Core::slot_toggle_winmsg ) ); m_action_group->add( raction_msg1, sigc::mem_fun( *this, &Core::slot_toggle_embmsg ) ); m_action_group->add( Gtk::ToggleAction::create( "ToggleWrap", "テキストを折り返し表示する(_W)", std::string(), CONFIG::get_message_wrap() ), sigc::mem_fun( *this, &Core::slot_toggle_msg_wrap ) ); // 画像表示設定 m_action_group->add( Gtk::Action::create( "ImageView_Menu", "画像表示設定(_G)" ) ); m_action_group->add( Gtk::Action::create( "ShowImageView_Menu", "画像ビュー(_V)" ) ); Gtk::RadioButtonGroup radiogroup_img; Glib::RefPtr< Gtk::RadioAction > raction_img0 = Gtk::RadioAction::create( radiogroup_img, "UseWinImg", "ウィンドウ表示する(_W)" ); Glib::RefPtr< Gtk::RadioAction > raction_img1 = Gtk::RadioAction::create( radiogroup_img, "UseEmbImg", "埋め込み表示する(_E)" ); Glib::RefPtr< Gtk::RadioAction > raction_img2 = Gtk::RadioAction::create( radiogroup_img, "NoUseImg", "表示しない(_D)" ); if( CONFIG::get_use_image_view() ){ if( ! SESSION::get_embedded_img() ) raction_img0->set_active( true ); else raction_img1->set_active( true ); } else { raction_img2->set_active( true ); } m_action_group->add( raction_img0, sigc::bind< int >( sigc::mem_fun( *this, &Core::slot_toggle_imgview ), IMGVIEW_WINDOW ) ); m_action_group->add( raction_img1, sigc::bind< int >( sigc::mem_fun( *this, &Core::slot_toggle_imgview ), IMGVIEW_EMB ) ); m_action_group->add( raction_img2, sigc::bind< int >( sigc::mem_fun( *this, &Core::slot_toggle_imgview ), IMGVIEW_NO ) ); m_action_group->add( Gtk::ToggleAction::create( "UseMosaic", "画像にモザイクをかける(_M)", std::string(), CONFIG::get_use_mosaic() ), sigc::mem_fun( *this, &Core::slot_toggle_use_mosaic ) ); m_action_group->add( Gtk::ToggleAction::create( "UseImgPopup", "画像ポップアップを表示する(_P)", std::string(), CONFIG::get_use_image_popup() ), sigc::mem_fun( *this, &Core::slot_toggle_use_imgpopup ) ); m_action_group->add( Gtk::ToggleAction::create( "UseInlineImg", "インライン画像を表示する(_I)", std::string(), CONFIG::get_use_inline_image() ), sigc::mem_fun( *this, &Core::slot_toggle_use_inlineimg ) ); m_action_group->add( Gtk::ToggleAction::create( "ShowSsspIcon", "BEアイコン/エモティコンを表示する(_B)", std::string(), CONFIG::get_show_ssspicon() ), sigc::mem_fun( *this, &Core::slot_toggle_show_ssspicon ) ); // リスト表示項目設定 m_action_group->add( Gtk::Action::create( "ListItem_Menu", "リスト項目設定(_L)" ) ); m_action_group->add( Gtk::Action::create( "SetupBoardItemColumn", "スレ一覧(_T)..." ), sigc::mem_fun( *this, &Core::slot_setup_boarditem_column ) ); // ツールバー項目設定 m_action_group->add( Gtk::Action::create( "Item_Menu", "ツールバー項目設定(_I)" ) ); m_action_group->add( Gtk::Action::create( "SetupMainItem", "メイン(_M)..." ), sigc::mem_fun( *this, &Core::slot_setup_mainitem ) ); m_action_group->add( Gtk::Action::create( "SetupSidebarItem", "サイドバー(_S)..." ), sigc::mem_fun( *this, &Core::slot_setup_sidebaritem ) ); m_action_group->add( Gtk::Action::create( "SetupBoardItem", "スレ一覧(_B)..." ), sigc::mem_fun( *this, &Core::slot_setup_boarditem ) ); m_action_group->add( Gtk::Action::create( "SetupArticleItem", "スレビュー(_A)..." ), sigc::mem_fun( *this, &Core::slot_setup_articleitem ) ); m_action_group->add( Gtk::Action::create( "SetupSearchItem", "ログ/スレタイ検索(_L)..." ), sigc::mem_fun( *this, &Core::slot_setup_searchitem ) ); m_action_group->add( Gtk::Action::create( "SetupMsgItem", "書き込みビュー(_W)..." ), sigc::mem_fun( *this, &Core::slot_setup_msgitem ) ); // コンテキストメニュー項目設定 m_action_group->add( Gtk::Action::create( "MenuItem_Menu", "コンテキストメニュー項目設定(_C)" ) ); m_action_group->add( Gtk::Action::create( "SetupBoardItemMenu", "スレ一覧(_B)..." ), sigc::mem_fun( *this, &Core::slot_setup_boarditem_menu ) ); m_action_group->add( Gtk::Action::create( "SetupArticleItemMenu", "スレビュー(_A)..." ), sigc::mem_fun( *this, &Core::slot_setup_articleitem_menu ) ); ////////////////////////////////////////////////////// // 履歴 m_action_group->add( Gtk::Action::create( "Menu_History", "履歴(_S)" ) ); // 戻る、進む m_action_group->add( Gtk::Action::create( "PrevView", "PrevView"), sigc::mem_fun( *this, &Core::slot_prevview ) ); m_action_group->add( Gtk::Action::create( "NextView", "NextView"), sigc::mem_fun( *this, &Core::slot_nextview ) ); ////////////////////////////////////////////////////// // 設定 m_action_group->add( Gtk::Action::create( "Menu_Config", "設定(_C)" ) ); m_action_group->add( Gtk::Action::create( "Property_Menu", "プロパティ(_P)" ) ); m_action_group->add( Gtk::Action::create( "BbslistPref", "板一覧のプロパティ(_L)..." ), sigc::mem_fun( *this, &Core::slot_bbslist_pref ) ); m_action_group->add( Gtk::Action::create( "BoardPref", "表示中の板のプロパティ(_B)..." ), sigc::mem_fun( *this, &Core::slot_board_pref ) ); m_action_group->add( Gtk::Action::create( "ArticlePref", "表示中のスレのプロパティ(_T)..." ), sigc::mem_fun( *this, &Core::slot_article_pref ) ); m_action_group->add( Gtk::Action::create( "ImagePref", "表示中の画像のプロパティ(_I)..." ), sigc::mem_fun( *this, &Core::slot_image_pref ) ); // 一般 m_action_group->add( Gtk::Action::create( "General_Menu", "一般(_G)" ) ); m_action_group->add( Gtk::ToggleAction::create( "RestoreViews", "前回開いていた各ビューを起動時に復元する(_R)", std::string(), ( CONFIG::get_restore_board() & CONFIG::get_restore_article() & CONFIG::get_restore_image() ) ), sigc::mem_fun( *this, &Core::slot_toggle_restore_views ) ); m_action_group->add( Gtk::ToggleAction::create( "ToggleFoldMessage", "非アクティブ時に書き込みビューを折りたたむ(_C)", std::string(), CONFIG::get_fold_message() ), sigc::mem_fun( *this, &Core::slot_toggle_fold_message ) ); m_action_group->add( Gtk::ToggleAction::create( "SelectItemSync", "サイドバー/スレ一覧の選択を表示中のビューと同期する(_S)", std::string(), ( CONFIG::get_select_item_sync() != 0 ) ), sigc::mem_fun( *this, &Core::slot_toggle_select_item_sync ) ); m_action_group->add( Gtk::ToggleAction::create( "SavePostLog", "書き込みログを保存する(_A)", std::string(), CONFIG::get_save_post_log() ), sigc::mem_fun( *this, &Core::slot_toggle_save_post_log ) ); m_action_group->add( Gtk::ToggleAction::create( "SavePostHist", "書き込み履歴(鉛筆マーク)を保存する(_P)", std::string(), CONFIG::get_save_post_history() ), sigc::mem_fun( *this, &Core::slot_toggle_save_post_history ) ); m_action_group->add( Gtk::ToggleAction::create( "UseMachiOfflaw", "まちBBSでofflaw.cgiを使用する(_O)", std::string(), CONFIG::get_use_machi_offlaw() ), sigc::mem_fun( *this, &Core::slot_toggle_use_machi_offlaw ) ); // マウス/キーボード m_action_group->add( Gtk::Action::create( "Mouse_Menu", "マウス/キーボード(_M)" ) ); const bool toggled = CONTROL::is_toggled_tab_button() && CONTROL::is_toggled_tab_key(); m_action_group->add( Gtk::ToggleAction::create( "ToggleTab", "スレ一覧/スレビューを開く時に常に新しいタブで開く(_T)", std::string(), toggled ), sigc::mem_fun( *this, &Core::slot_toggle_tabbutton ) ); m_action_group->add( Gtk::ToggleAction::create( "TogglePopupWarp", "{X11} スレビューでアンカーをクリックして多重ポップアップモードに移行する(_W)", std::string(), CONTROL::is_popup_warpmode() ), sigc::mem_fun( *this, &Core::slot_toggle_popupwarpmode ) ); m_action_group->add( Gtk::ToggleAction::create( "ShortMarginPopup", "スレビューでカーソルを移動して多重ポップアップモードに移行する(_M)", std::string(), ( CONFIG::get_margin_popup() != CONFIG::CONF_MARGIN_POPUP ) ), sigc::mem_fun( *this, &Core::slot_shortmargin_popup ) ); m_action_group->add( Gtk::ToggleAction::create( "ToggleEmacsMode", "書き込みビューのショートカットキーをEmacs風にする(_E)", std::string(), CONTROL::is_emacs_mode() ), sigc::mem_fun( *this, &Core::slot_toggle_emacsmode ) ); m_action_group->add( Gtk::Action::create( "MousePref", "マウスジェスチャ詳細設定(_G)..." ), sigc::mem_fun( *this, &Core::slot_setup_mouse ) ); m_action_group->add( Gtk::Action::create( "KeyPref", "ショートカットキー詳細設定(_R)..." ), sigc::mem_fun( *this, &Core::slot_setup_key ) ); m_action_group->add( Gtk::Action::create( "ButtonPref", "マウスボタン詳細設定(_B)..." ), sigc::mem_fun( *this, &Core::slot_setup_button ) ); // フォントと色 m_action_group->add( Gtk::Action::create( "FontColor_Menu", "フォントと色(_F)" ) ); m_action_group->add( Gtk::Action::create( "FontMain", "スレビューフォント(_T)..." ), sigc::mem_fun( *this, &Core::slot_changefont_main ) ); m_action_group->add( Gtk::Action::create( "FontMail", "メール欄フォント(_U)..." ), sigc::mem_fun( *this, &Core::slot_changefont_mail ) ); m_action_group->add( Gtk::Action::create( "FontPopup", "ポップアップフォント(_P)..." ), sigc::mem_fun( *this, &Core::slot_changefont_popup ) ); m_action_group->add( Gtk::Action::create( "FontTree", "板/スレ一覧フォント(_B)..." ), sigc::mem_fun( *this, &Core::slot_changefont_tree ) ); m_action_group->add( Gtk::Action::create( "ColorChar", "スレビュー文字色(_C)..." ), sigc::mem_fun( *this, &Core::slot_changecolor_char ) ); m_action_group->add( Gtk::Action::create( "ColorBack", "スレビュー背景色(_A)..." ), sigc::mem_fun( *this, &Core::slot_changecolor_back ) ); m_action_group->add( Gtk::Action::create( "ColorCharTree", "板/スレ一覧文字色(_H)..." ), sigc::mem_fun( *this, &Core::slot_changecolor_char_tree ) ); m_action_group->add( Gtk::Action::create( "ColorBackTree", "板/スレ一覧背景色(_K)..." ), sigc::mem_fun( *this, &Core::slot_changecolor_back_tree ) ); m_action_group->add( Gtk::Action::create( "FontColorPref", "詳細設定(_R)..." ), sigc::mem_fun( *this, &Core::slot_setup_fontcolor ) ); // ネットワーク m_action_group->add( Gtk::Action::create( "Net_Menu", "ネットワーク(_N)" ) ); m_action_group->add( Gtk::Action::create( "SetupProxy", "プロキシ(_X)..." ), sigc::mem_fun( *this, &Core::slot_setup_proxy ) ); m_action_group->add( Gtk::Action::create( "SetupBrowser", "Webブラウザ(_W)..." ), sigc::mem_fun( *this, &Core::slot_setup_browser ) ); m_action_group->add( Gtk::Action::create( "SetupPasswd", "パスワード(_P)..." ), sigc::mem_fun( *this, &Core::slot_setup_passwd ) ); m_action_group->add( Gtk::ToggleAction::create( "ToggleIPv6", "IPv6使用(_I)", std::string(), CONFIG::get_use_ipv6() ), sigc::mem_fun( *this, &Core::slot_toggle_ipv6 ) ); // あぼーん m_action_group->add( Gtk::Action::create( "Abone_Menu", "あぼ〜ん(_A)" ) ); m_action_group->add( Gtk::Action::create( "SetupAbone", "全体あぼ〜ん設定(対象: スレビュー)(_V)..." ), sigc::mem_fun( *this, &Core::slot_setup_abone ) ); m_action_group->add( Gtk::Action::create( "SetupAboneThread", "全体スレあぼ〜ん設定(対象: スレ一覧)(_L)..." ), sigc::mem_fun( *this, &Core::slot_setup_abone_thread ) ); m_action_group->add( Gtk::ToggleAction::create( "TranspChainAbone", "スレビューで透明/連鎖あぼ〜んをデフォルト設定にする(_T)", std::string(), ( CONFIG::get_abone_transparent() && CONFIG::get_abone_chain() ) ), sigc::mem_fun( *this, &Core::slot_toggle_abone_transp_chain ) ); m_action_group->add( Gtk::ToggleAction::create( "IcaseWcharAbone", "NG正規表現で大小と全半角文字の違いを無視する(_W)", std::string(), ( CONFIG::get_abone_icase() && CONFIG::get_abone_wchar() ) ), sigc::mem_fun( *this, &Core::slot_toggle_abone_icase_wchar ) ); // その他 m_action_group->add( Gtk::Action::create( "Etc_Menu", "その他(_O)" ) ); m_action_group->add( Gtk::Action::create( "LivePref", "実況設定(_L)..." ), sigc::mem_fun( *this, &Core::slot_setup_live ) ); m_action_group->add( Gtk::Action::create( "UsrCmdPref", "ユーザコマンドの編集(_U)..." ), sigc::mem_fun( *this, &Core::slot_usrcmd_pref ) ); m_action_group->add( Gtk::Action::create( "FilterPref", "リンクフィルタの編集(_F)..." ), sigc::mem_fun( *this, &Core::slot_filter_pref ) ); m_action_group->add( Gtk::Action::create( "ReplacePref", "置換文字列の編集(_R)..." ), [this] { slot_replace_pref(); } ); m_action_group->add( Gtk::Action::create( "AboutConfig", "about:config 高度な設定(_C)..." ), sigc::mem_fun( *this, &Core::slot_aboutconfig ) ); // プライバシー m_action_group->add( Gtk::Action::create( "Privacy_Menu", "プライバシー(_R)" ) ); m_action_group->add( Gtk::Action::create( "ClearAllPrivacy", "各履歴等の消去(_I)..." ), sigc::mem_fun( *this, &Core::slot_clear_privacy ) ); m_action_group->add( Gtk::Action::create( "ClearPostLog", "書き込みログの消去(_P)" ), sigc::mem_fun( *this, &Core::slot_clear_post_log ) ); m_action_group->add( Gtk::Action::create( "ClearPostHist", "書き込み履歴(鉛筆マーク)の消去(_H)" ), sigc::mem_fun( *this, &Core::slot_clear_post_history ) ); m_action_group->add( Gtk::Action::create( "DeleteImages", "画像キャッシュの消去(_D)..." ), sigc::mem_fun( *this, &Core::slot_delete_all_images ) ); ////////////////////////////////////////////////////// // ツール m_action_group->add( Gtk::Action::create( "Menu_Tool", "ツール(_T)" ) ); m_action_group->add( Gtk::Action::create( "LiveStartStop", "LiveStartStop"), sigc::mem_fun( *this, &Core::slot_live_start_stop ) ); m_action_group->add( Gtk::Action::create( "SearchCache_Menu", "キャッシュ内ログ検索(_C)" ) ); m_action_group->add( Gtk::Action::create( "SearchCacheBoard", "表示中の板のログを検索(_B)" ), sigc::mem_fun( *this, &Core::slot_search_cache_board ) ); m_action_group->add( Gtk::Action::create( "SearchCache", "キャッシュ内の全ログを検索(_A)" ), sigc::mem_fun( *this, &Core::slot_search_cache ) ); m_action_group->add( Gtk::Action::create( "ShowCache_Menu", "キャッシュ内ログ一覧(_H)" ) ); m_action_group->add( Gtk::Action::create( "ShowCacheBoard", "表示中の板のログをスレ一覧に表示(_B)" ), sigc::mem_fun( *this, &Core::slot_show_cache_board ) ); m_action_group->add( Gtk::Action::create( "ShowCache", "キャッシュ内の全ログをスレ一覧に表示(_A)" ), sigc::mem_fun( *this, &Core::slot_show_cache ) ); m_action_group->add( Gtk::Action::create( "SearchTitle", "SearchTitle" ), sigc::mem_fun( *this, &Core::slot_search_title ) ); m_action_group->add( Gtk::Action::create( "CheckUpdate_Menu", "サイドバーの更新チェック(_U)" ) ); m_action_group->add( Gtk::Action::create( "CheckUpdateRoot", "更新チェックのみ(_R)" ), sigc::mem_fun( *this, &Core::slot_check_update_root ) ); m_action_group->add( Gtk::Action::create( "CheckUpdateOpenRoot", "更新されたスレをタブで開く(_T)" ), sigc::mem_fun( *this, &Core::slot_check_update_open_root ) ); m_action_group->add( Gtk::Action::create( "CancelCheckUpdate", "キャンセル(_C)" ), sigc::mem_fun( *this, &Core::slot_cancel_check_update ) ); m_action_group->add( Gtk::Action::create( "EditFavorite", "お気に入りの編集(_E)"), sigc::mem_fun( *this, &Core::slot_edit_favorite ) ); m_action_group->add( Gtk::Action::create( "ShowPostlog", "書き込みログの表示(_P)" ), sigc::mem_fun( *this, &Core::slot_show_postlog ) ); m_action_group->add( Gtk::Action::create( "ImportDat", "表示中の板にdatをインポート(_I)" ), sigc::mem_fun( *this, &Core::slot_import_dat ) ); m_action_group->add( Gtk::Action::create( "ShowSidebarBoard", "サイドバーをスレ一覧に表示(_B)" ), sigc::mem_fun( *this, &Core::slot_show_sidebarboard ) ); m_action_group->add( Gtk::Action::create( "CreateVBoard", "サイドバーの仮想板を作成(_V)" ), sigc::mem_fun( *this, &Core::slot_create_vboard ) ); ////////////////////////////////////////////////////// // help m_action_group->add( Gtk::Action::create( "Menu_Help", "ヘルプ(_H)" ) ); m_action_group->add( Gtk::Action::create( "Bbs", "JD サポート掲示板(_B)" ), sigc::mem_fun( *this, &Core::slot_show_bbs ) ); m_action_group->add( Gtk::Action::create( "OldLog", "2chスレ過去ログ(_L)" ), sigc::mem_fun( *this, &Core::slot_show_old2ch ) ); Gtk::AccelKey jdhelpKey = CONTROL::get_accelkey( CONTROL::JDHelp ); if( jdhelpKey.is_null() ){ m_action_group->add( Gtk::Action::create( "Manual", "オンラインマニュアル(_M)..." ), sigc::mem_fun( *this, &Core::slot_show_manual ) ); }else{ m_action_group->add( Gtk::Action::create( "Manual", "オンラインマニュアル(_M)..." ), jdhelpKey, sigc::mem_fun( *this, &Core::slot_show_manual ) ); } m_action_group->add( Gtk::Action::create( "About", "JDimについて(_A)..." ), sigc::mem_fun( *this, &Core::slot_show_about ) ); m_ui_manager = Gtk::UIManager::create(); m_ui_manager->insert_action_group( m_action_group ); // アクセラレータの追加 m_win_main.add_accel_group( m_ui_manager->get_accel_group() ); Glib::ustring menu_font = "" "" "" "" "" "" "" "" "" "" "" "" ""; Glib::ustring str_ui = "" "" // ファイル "" "" "" "" "" "" "" "" "" "" "" "" "" // 表示 "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" // 詳細設定 "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""; str_ui += menu_font; str_ui += "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" // 履歴 "" "" "" "" // ツール "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" // 設定 "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""; str_ui += menu_font; str_ui += "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" // プライバシー "" "" "" "" "" "" "" "" "" // その他 "" "" "" "" "" "" "" "" "" // ヘルプ "" "" "" "" "" "" "" "" "" ""; m_ui_manager->add_ui_from_string( str_ui ); m_menubar = dynamic_cast< Gtk::MenuBar* >( m_ui_manager->get_widget("/menu_bar") ); assert( m_menubar ); m_menubar->set_size_request( 0 ); // 履歴メニュー追加 const auto items = m_menubar->get_children(); auto item = dynamic_cast< Gtk::MenuItem* >( *std::next( items.begin(), 2 ) ); item->signal_activate().connect( sigc::mem_fun( *this, &Core::slot_activate_historymenu ) ); Gtk::Menu* submenu = item->get_submenu(); submenu->append( *Gtk::manage( new Gtk::SeparatorMenuItem() ) ); // スレ履歴 submenu->append( *HISTORY::get_history_manager()->get_menu_thread() ); // 板履歴 submenu->append( *HISTORY::get_history_manager()->get_menu_board() ); // 最近閉じたスレ履歴 submenu->append( *HISTORY::get_history_manager()->get_menu_close() ); // 最近閉じた板履歴 submenu->append( *HISTORY::get_history_manager()->get_menu_closeboard() ); // 最近閉じた画像履歴 submenu->append( *HISTORY::get_history_manager()->get_menu_closeimg() ); submenu->show_all_children(); // メニューにショートカットキーやマウスジェスチャを表示 for( auto&& widget : items ) { auto menu_item = dynamic_cast< Gtk::MenuItem* >( widget ); CONTROL::set_menu_motion( menu_item->get_submenu() ); menu_item->signal_activate().connect( sigc::mem_fun( *this, &Core::slot_activate_menubar ) ); } // ツールバー作成 create_toolbar(); assert( m_toolbar ); // サイドバー m_sidebar = BBSLIST::get_admin()->get_widget(); assert( m_sidebar ); // その他設定とwidgetのパッキング m_notebook_right.set_show_tabs( false ); m_notebook_right.set_show_border( false ); m_notebook_right.set_margin_start( 0 ); m_notebook_right.set_margin_end( 0 ); if( CONFIG::get_open_sidebar_by_click() ) m_hpaned.get_ctrl().set_click_fold( SKELETON::PANE_CLICK_FOLD_PAGE1 ); m_hpaned.get_ctrl().add_remove1( false, *m_sidebar ); pack_widget( false ); m_sigc_switch_page = m_notebook_right.signal_switch_page().connect( sigc::mem_fun( *this, &Core::slot_switch_page ) ); m_hpaned.get_ctrl().sig_pane_modechanged().connect( sigc::mem_fun( *this, &Core::slot_show_hide_leftpane ) ); m_win_main.signal_focus_out_event().connect( sigc::mem_fun(*this, &Core::slot_focus_out_event ) ); m_win_main.signal_focus_in_event().connect( sigc::mem_fun(*this, &Core::slot_focus_in_event ) ); m_win_main.show_all_children(); // 各管理クラスが開いていたURLを復元 core_set_command( "restore_views" ); } // // 3paneモードか // bool Core::is_3pane() const { int mode_pane = SESSION::get_mode_pane(); return( mode_pane == SESSION::MODE_3PANE || mode_pane == SESSION::MODE_V3PANE ); } // (bbslistを除く)全adminがemptyか bool Core::is_all_admin_empty() const { bool emp_img = ! ( SESSION::get_embedded_img() && ! IMAGE::get_admin()->empty() ); bool emp_mes = ! ( SESSION::get_embedded_mes() && ! MESSAGE::get_admin()->empty() ); return ( BOARD::get_admin()->empty() && ARTICLE::get_admin()->empty() && emp_mes && emp_img ); } // // 右側ペーン取得 // Gtk::Paned* Core::get_rpane() { Gtk::Paned* paned_r = &m_vpaned_r; if( SESSION::get_mode_pane() == SESSION::MODE_V3PANE ) paned_r = &m_hpaned_r; return paned_r; } // // 右側ペーンコントロール取得 // SKELETON::PaneControl* Core::get_rpctrl() { SKELETON::PaneControl* pctrl = &m_vpaned_r.get_ctrl(); if( SESSION::get_mode_pane() == SESSION::MODE_V3PANE ) pctrl = &m_hpaned_r.get_ctrl(); return pctrl; } // // widget のパック // void Core::pack_widget( bool unpack ) { m_enable_menuslot = false; int mode_pane = SESSION::get_mode_pane(); if( unpack ){ SESSION::set_hpane_main_pos( m_hpaned.get_ctrl().get_position() ); SESSION::set_vpane_main_pos( m_vpaned_r.get_ctrl().get_position() ); SESSION::set_hpane_main_r_pos( m_hpaned_r.get_ctrl().get_position() ); SESSION::set_vpane_main_mes_pos( m_vpaned_message.get_ctrl().get_position() ); } if( SESSION::get_embedded_mes() ){ // 埋め込みmessage // 書き込みウィンドウを閉じる MESSAGE::get_admin()->set_command_immediately( "close_window" ); m_vpaned_message.get_ctrl().add_remove1( unpack, *ARTICLE::get_admin()->get_widget() ); m_vpaned_message.get_ctrl().add_remove2( unpack, *MESSAGE::get_admin()->get_widget() ); m_notebook_right.append_remove_page( unpack, m_vpaned_message, "スレッド" ); } else{ // 書き込みウィンドウ表示 MESSAGE::get_admin()->set_command_immediately( "open_window" ); m_notebook_right.append_remove_page( unpack, *ARTICLE::get_admin()->get_widget(), "スレッド" ); } if( SESSION::get_embedded_img() ){ // 埋め込みimage // 画像ウィンドウを閉じる IMAGE::get_admin()->set_command_immediately( "close_window" ); m_notebook_right.append_remove_page( unpack, *IMAGE::get_admin()->get_widget(), "画像" ); } else{ // 画像ウィンドウ表示 IMAGE::get_admin()->set_command_immediately( "open_window" ); } // 画像インジケータ if( unpack ) hide_imagetab(); // 2ペーン if( mode_pane == SESSION::MODE_2PANE ){ m_notebook_right.append_remove_page( unpack, *BOARD::get_admin()->get_widget(), "スレ一覧" ); if( SESSION::get_show_main_toolbar() && SESSION::get_toolbar_pos() == SESSION::TOOLBAR_POS_RIGHT ) m_vbox_article.pack_remove_start( unpack, *m_toolbar, Gtk::PACK_SHRINK ); m_vbox_article.pack_remove_start( unpack, m_notebook_right ); m_hpaned.get_ctrl().add_remove2( unpack, m_vbox_article ); } // 3ペーン else if( is_3pane() ){ m_vbox_article.pack_remove_start( unpack, m_notebook_right ); get_rpctrl()->add_remove1( unpack, *BOARD::get_admin()->get_widget() ); get_rpctrl()->add_remove2( unpack, m_vbox_article ); if( SESSION::get_show_main_toolbar() && SESSION::get_toolbar_pos() == SESSION::TOOLBAR_POS_RIGHT ){ m_vbox_toolbar.pack_remove_start( unpack, *m_toolbar, Gtk::PACK_SHRINK ); m_vbox_toolbar.pack_remove_start( unpack, *get_rpane() ); m_hpaned.get_ctrl().add_remove2( unpack, m_vbox_toolbar ); } else m_hpaned.get_ctrl().add_remove2( unpack, *get_rpane() ); } // メインwindowのパッキング if( SESSION::get_show_main_statbar() ) m_win_main.pack_remove_end( unpack, m_win_main.get_statbar(), Gtk::PACK_SHRINK ); m_win_main.pack_remove_end( unpack, m_hpaned ); if( SESSION::get_show_main_toolbar() && SESSION::get_toolbar_pos() == SESSION::TOOLBAR_POS_NORMAL ) m_win_main.pack_remove_end( unpack, *m_toolbar, Gtk::PACK_SHRINK ); if( SESSION::show_menubar() ) m_win_main.pack_remove_end( unpack, *m_menubar, Gtk::PACK_SHRINK ); if( ! unpack ){ // ペーンの位置設定 m_vpaned_r.get_ctrl().set_position( SESSION::vpane_main_pos() ); m_hpaned_r.get_ctrl().set_position( SESSION::hpane_main_r_pos() ); m_vpaned_message.get_ctrl().set_position( SESSION::vpane_main_mes_pos() ); // 画像インジケータ if( ! IMAGE::get_admin()->empty() ) show_imagetab(); // サイドバーの位置設定 m_hpaned.get_ctrl().set_position( SESSION::hpane_main_pos() ); toggle_maximize_rightpane(); } m_enable_menuslot = true; } // // ツールバー作成 // void Core::create_toolbar() { if( m_toolbar ) return; m_toolbar = std::make_unique(); m_toolbar->m_button_bbslist.signal_clicked().connect( sigc::bind< std::string, bool >( sigc::mem_fun(*this, &Core::switch_sidebar ), URL_BBSLISTVIEW, false ) ); m_toolbar->m_button_favorite.signal_clicked().connect( sigc::bind< std::string, bool >( sigc::mem_fun(*this, &Core::switch_sidebar ), URL_FAVORITEVIEW, false ) ); m_toolbar->m_button_hist.signal_clicked().connect( sigc::bind< std::string, bool >( sigc::mem_fun(*this, &Core::switch_sidebar ), URL_HISTTHREADVIEW, false ) ); m_toolbar->m_button_hist_board.signal_clicked().connect( sigc::bind< std::string, bool >( sigc::mem_fun(*this, &Core::switch_sidebar ), URL_HISTBOARDVIEW, false ) ); m_toolbar->m_button_hist_close.signal_clicked().connect( sigc::bind< std::string, bool >( sigc::mem_fun(*this, &Core::switch_sidebar ), URL_HISTCLOSEVIEW, false ) ); m_toolbar->m_button_hist_closeboard.signal_clicked().connect( sigc::bind< std::string, bool >( sigc::mem_fun(*this, &Core::switch_sidebar ), URL_HISTCLOSEBOARDVIEW, false ) ); m_toolbar->m_button_hist_closeimg.signal_clicked().connect( sigc::bind< std::string, bool >( sigc::mem_fun(*this, &Core::switch_sidebar ), URL_HISTCLOSEIMGVIEW, false ) ); m_toolbar->m_button_board.signal_clicked().connect( sigc::bind< bool >( sigc::mem_fun(*this, &Core::switch_board ), false ) ); m_toolbar->m_button_thread.signal_clicked().connect( sigc::bind< bool >( sigc::mem_fun(*this, &Core::switch_article ), false ) ); m_toolbar->m_button_image.signal_clicked().connect( sigc::bind< bool >( sigc::mem_fun(*this, &Core::switch_image ), false ) ); m_toolbar->m_entry_url.signal_activate().connect( sigc::mem_fun( *this, &Core::slot_active_url ) ); m_toolbar->m_button_go.signal_clicked().connect( sigc::mem_fun( *this, &Core::slot_active_url ) ); m_toolbar->open_buttonbar(); m_toolbar->show_toolbar(); } // // 初回起動時のセットアップ // void Core::first_setup() { m_init = true; SetupWizard wizard; wizard.run(); m_init = false; } // // メインタイトルセット // void Core::set_maintitle() { if( SESSION::is_booting() ) return; std::string title; if( m_title.empty() ) title = "JDim - " + ENVIRONMENT::get_jdversion(); else title = "JDim - " + m_title; if( CORE::get_login2ch()->login_now() ) title +=" [ ● ]"; if( CORE::get_loginbe()->login_now() ) title +=" [ BE ]"; if( ! SESSION::is_online() ) title += " [ offline ]"; m_win_main.set_title( title ); } static inline void toggle_sidebar_action( Glib::RefPtr< Gtk::ActionGroup >& group, const std::string& action, const std::string& url ) { Glib::RefPtr< Gtk::Action > act; Glib::RefPtr< Gtk::ToggleAction > tact; act = group->get_action( action ); tact = Glib::RefPtr< Gtk::ToggleAction >::cast_dynamic( act ); if( tact ){ if( SESSION::show_sidebar() && SESSION::get_sidebar_current_url() == url ) tact->set_active( true ); else tact->set_active( false ); } } // // メニューバーがアクティブになったときに呼ばれるスロット // void Core::slot_activate_menubar() { // toggle アクションを activeにするとスロット関数が呼ばれるので処理しないようにする m_enable_menuslot = false; Glib::RefPtr< Gtk::Action > act; Glib::RefPtr< Gtk::ToggleAction > tact; // サイドバー toggle_sidebar_action( m_action_group, "Show_BBS", URL_BBSLISTVIEW ); toggle_sidebar_action( m_action_group, "Show_FAVORITE", URL_FAVORITEVIEW ); toggle_sidebar_action( m_action_group, "Show_HISTTHREAD", URL_HISTTHREADVIEW ); toggle_sidebar_action( m_action_group, "Show_HISTBOARD", URL_HISTBOARDVIEW ); toggle_sidebar_action( m_action_group, "Show_HISTCLOSE", URL_HISTCLOSEVIEW ); toggle_sidebar_action( m_action_group, "Show_HISTCLOSEBOARD", URL_HISTCLOSEBOARDVIEW ); toggle_sidebar_action( m_action_group, "Show_HISTCLOSEIMG", URL_HISTCLOSEIMGVIEW ); // メニューバー act = m_action_group->get_action( "ShowMenuBar" ); tact = Glib::RefPtr< Gtk::ToggleAction >::cast_dynamic( act ); if( tact ){ if( SESSION::show_menubar() ) tact->set_active( true ); else tact->set_active( false ); } // ボタンのrelief切り替え act = m_action_group->get_action( "ToggleFlatButton" ); tact = Glib::RefPtr< Gtk::ToggleAction >::cast_dynamic( act ); if( tact ){ if( CONFIG::get_flat_button() ) tact->set_active( true ); else tact->set_active( false ); } // ツールバー背景描画 act = m_action_group->get_action( "ToggleDrawToolbarback" ); tact = Glib::RefPtr< Gtk::ToggleAction >::cast_dynamic( act ); if( tact ){ if( CONFIG::get_draw_toolbarback() ) tact->set_active( true ); else tact->set_active( false ); } // ツールバー act = m_action_group->get_action( "ShowToolBarMain" ); tact = Glib::RefPtr< Gtk::ToggleAction >::cast_dynamic( act ); if( tact ){ if( SESSION::get_show_main_toolbar() ) tact->set_active( true ); else tact->set_active( false ); } act = m_action_group->get_action( "ShowToolBarBbslist" ); tact = Glib::RefPtr< Gtk::ToggleAction >::cast_dynamic( act ); if( tact ){ if( SESSION::get_show_bbslist_toolbar() ) tact->set_active( true ); else tact->set_active( false ); } act = m_action_group->get_action( "ShowToolBarBoard" ); tact = Glib::RefPtr< Gtk::ToggleAction >::cast_dynamic( act ); if( tact ){ if( SESSION::get_show_board_toolbar() ) tact->set_active( true ); else tact->set_active( false ); } act = m_action_group->get_action( "ShowToolBarArticle" ); tact = Glib::RefPtr< Gtk::ToggleAction >::cast_dynamic( act ); if( tact ){ if( SESSION::get_show_article_toolbar() ) tact->set_active( true ); else tact->set_active( false ); } // タブ act = m_action_group->get_action( "TabBoard" ); tact = Glib::RefPtr< Gtk::ToggleAction >::cast_dynamic( act ); if( tact ){ if( SESSION::get_show_board_tab() ) tact->set_active( true ); else tact->set_active( false ); } act = m_action_group->get_action( "TabArticle" ); tact = Glib::RefPtr< Gtk::ToggleAction >::cast_dynamic( act ); if( tact ){ if( SESSION::get_show_article_tab() ) tact->set_active( true ); else tact->set_active( false ); } // フルスクリーン act = m_action_group->get_action( "FullScreen" ); tact = Glib::RefPtr< Gtk::ToggleAction >::cast_dynamic( act ); if( tact ){ if( SESSION::is_full_win_main() ) tact->set_active( true ); else tact->set_active( false ); } // ステータスバー act = m_action_group->get_action( "ShowStatBar" ); tact = Glib::RefPtr< Gtk::ToggleAction >::cast_dynamic( act ); if( tact ){ if( SESSION::get_show_main_statbar() ) tact->set_active( true ); else tact->set_active( false ); } // 2chログイン act = m_action_group->get_action( "Login2ch" ); tact = Glib::RefPtr< Gtk::ToggleAction >::cast_dynamic( act ); if( tact ){ if( CORE::get_login2ch()->login_now() ) tact->set_active( true ); else tact->set_active( false ); } // BEログイン act = m_action_group->get_action( "LoginBe" ); tact = Glib::RefPtr< Gtk::ToggleAction >::cast_dynamic( act ); if( tact ){ if( CORE::get_loginbe()->login_now() ) tact->set_active( true ); else tact->set_active( false ); } // 表示->スレ一覧に切替 (アクティブ状態を切り替える) act = m_action_group->get_action( "Show_Board" ); if( BOARD::get_admin()->empty() ) act->set_sensitive( false ); else act->set_sensitive( true ); // 表示->スレビューに切替 (アクティブ状態を切り替える) act = m_action_group->get_action( "Show_Thread" ); if( ARTICLE::get_admin()->empty() ) act->set_sensitive( false ); else act->set_sensitive( true ); // 表示->画像ビューに切替 (アクティブ状態を切り替える) act = m_action_group->get_action( "Show_Image" ); if( CONFIG::get_use_image_view() && ! IMAGE::get_admin()->empty() ) act->set_sensitive( true ); else act->set_sensitive( false ); // 開いている板のログ検索 act = m_action_group->get_action( "SearchCacheBoard" ); if( BOARD::get_admin()->empty() || DBTREE::url_boardbase( BOARD::get_admin()->get_current_url() ).empty() ) act->set_sensitive( false ); else act->set_sensitive( true ); // 開いている板のログ一覧表示 act = m_action_group->get_action( "ShowCacheBoard" ); if( BOARD::get_admin()->empty() || DBTREE::url_boardbase( BOARD::get_admin()->get_current_url() ).empty() ) act->set_sensitive( false ); else act->set_sensitive( true ); // スレ一覧のプロパティ act = m_action_group->get_action( "BoardPref" ); if( BOARD::get_admin()->empty() || DBTREE::url_boardbase( BOARD::get_admin()->get_current_url() ).empty() ) act->set_sensitive( false ); else act->set_sensitive( true ); // スレのプロパティ act = m_action_group->get_action( "ArticlePref" ); if( ! ARTICLE::get_admin()->empty() ) act->set_sensitive( true ); else act->set_sensitive( false ); // 画像のプロパティ act = m_action_group->get_action( "ImagePref" ); if( ! IMAGE::get_admin()->empty() ) act->set_sensitive( true ); else act->set_sensitive( false ); // 実況 act = m_action_group->get_action( "LiveStartStop" ); if( ! ARTICLE::get_admin()->empty() ) act->set_sensitive( true ); else act->set_sensitive( false ); // emacsモード act = m_action_group->get_action( "ToggleEmacsMode" ); tact = Glib::RefPtr< Gtk::ToggleAction >::cast_dynamic( act ); if( tact ){ if( CONTROL::is_emacs_mode() ) tact->set_active( true ); else tact->set_active( false ); } // datのインポート act = m_action_group->get_action( "ImportDat" ); if( ! BOARD::get_admin()->empty() ) act->set_sensitive( true ); else act->set_sensitive( false ); // サイドバーのスレ一覧表示 act = m_action_group->get_action( "ShowSidebarBoard" ); if( SESSION::get_sidebar_current_url() != URL_BBSLISTVIEW && SESSION::get_sidebar_current_url() != URL_HISTBOARDVIEW ) act->set_sensitive( true ); else act->set_sensitive( false ); // 仮想板作成 act = m_action_group->get_action( "CreateVBoard" ); if( SESSION::get_sidebar_current_url() != URL_BBSLISTVIEW && SESSION::get_sidebar_current_url() != URL_HISTBOARDVIEW ) act->set_sensitive( true ); else act->set_sensitive( false ); m_enable_menuslot = true; } // // 履歴メニューがアクティブになった // void Core::slot_activate_historymenu() { m_enable_menuslot = false; std::string view_url; switch( SESSION::focused_admin() ){ case SESSION::FOCUS_BOARD: view_url = BOARD::get_admin()->get_current_url(); break; case SESSION::FOCUS_ARTICLE: view_url = ARTICLE::get_admin()->get_current_url(); break; } bool enable_prev = false; bool enable_next = false; if( ! view_url.empty() ){ enable_prev = HISTORY::get_history_manager()->can_back_viewhistory( view_url, 1 ); enable_next = HISTORY::get_history_manager()->can_forward_viewhistory( view_url, 1 ); } #ifdef _DEBUG std::cout << "Core::slot_activate_historymenu\n" << "view_url = " << view_url << " prev = " << enable_prev << " next = " << enable_next << std::endl; #endif Glib::RefPtr< Gtk::Action > act; act = m_action_group->get_action( "PrevView" ); if( act ) act->set_sensitive( enable_prev ); act = m_action_group->get_action( "NextView" ); if( act ) act->set_sensitive( enable_next ); m_enable_menuslot = true; } // 戻る void Core::slot_prevview() { if( SESSION::is_booting() ) return; if( ! m_enable_menuslot ) return; if( SESSION::focused_admin() == SESSION::FOCUS_ARTICLE ){ ARTICLE::get_admin()->set_command( "back_viewhistory", ARTICLE::get_admin()->get_current_url(), "1" ); } else if( SESSION::focused_admin() == SESSION::FOCUS_BOARD ){ BOARD::get_admin()->set_command( "back_viewhistory", BOARD::get_admin()->get_current_url(), "1" ); } } // 進む void Core::slot_nextview() { if( SESSION::is_booting() ) return; if( ! m_enable_menuslot ) return; if( SESSION::focused_admin() == SESSION::FOCUS_ARTICLE ){ ARTICLE::get_admin()->set_command( "forward_viewhistory", ARTICLE::get_admin()->get_current_url(), "1" ); } else if( SESSION::focused_admin() == SESSION::FOCUS_BOARD ){ BOARD::get_admin()->set_command( "forward_viewhistory", BOARD::get_admin()->get_current_url(), "1" ); } } void Core::slot_clear_board() { HISTORY::remove_allhistories( URL_HISTBOARDVIEW ); BOARD::get_admin()->set_command( "clear_viewhistory" ); } void Core::slot_clear_thread() { HISTORY::remove_allhistories( URL_HISTTHREADVIEW ); ARTICLE::get_admin()->set_command( "clear_viewhistory" ); } void Core::slot_clear_close() { HISTORY::remove_allhistories( URL_HISTCLOSEVIEW ); } void Core::slot_clear_closeboard() { HISTORY::remove_allhistories( URL_HISTCLOSEBOARDVIEW ); } void Core::slot_clear_closeimg() { HISTORY::remove_allhistories( URL_HISTCLOSEIMGVIEW ); } void Core::slot_clear_search() { CORE::get_completion_manager()->clear( CORE::COMP_SEARCH_ARTICLE ); CORE::get_completion_manager()->clear( CORE::COMP_SEARCH_BBSLIST ); CORE::get_completion_manager()->clear( CORE::COMP_SEARCH_BOARD ); } void Core::slot_clear_name() { CORE::get_completion_manager()->clear( CORE::COMP_NAME ); } void Core::slot_clear_mail() { CORE::get_completion_manager()->clear( CORE::COMP_MAIL ); } // // 色選択ダイアログを開く // フォントと色の設定 // bool Core::open_color_diag( std::string title, int id ) { Gdk::RGBA color( CONFIG::get_color( id ) ); Gtk::ColorSelectionDialog diag( title ); diag.get_color_selection()->set_current_rgba( color ); diag.set_transient_for( *CORE::get_mainwindow() ); if( diag.run() == Gtk::RESPONSE_OK ){ Gtk::ColorSelection* sel = diag.get_color_selection(); CONFIG::set_color( id, MISC::color_to_str( sel->get_current_rgba() ) ); return true; } return false; } // // メニューバー表示切替え // void Core::toggle_menubar() { if( SESSION::is_booting() ) return; if( ! m_enable_menuslot ) return; #ifdef _DEBUG std::cout << "Core::toggle_menubar\n"; #endif pack_widget( true ); SESSION::set_show_menubar( ! SESSION::show_menubar() ); pack_widget( false ); restore_focus( true, false ); if( ! SESSION::show_menubar() && CONFIG::get_show_hide_menubar_diag() ){ SKELETON::MsgCheckDiag mdiag( nullptr, "メニューバーを再表示するには\n\n" + CONTROL::get_str_motions( CONTROL::ShowMenuBar ) + "\n\nを押してください", "今後表示しない (_D)" ); mdiag.run(); if( mdiag.get_chkbutton().get_active() ) CONFIG::set_show_hide_menubar_diag( false ); } } // // ステータスバー表示切替え // void Core::toggle_statbar() { if( SESSION::is_booting() ) return; if( ! m_enable_menuslot ) return; #ifdef _DEBUG std::cout << "Core::toggle_statbar\n"; #endif pack_widget( true ); SESSION::set_show_main_statbar( ! SESSION::get_show_main_statbar() ); pack_widget( false ); restore_focus( true, false ); } // // ボタンのreliefの切り替え // void Core::toggle_flat_button() { if( SESSION::is_booting() ) return; if( ! m_enable_menuslot ) return; #ifdef _DEBUG std::cout << "Core::toggle_flat_button\n"; #endif CONFIG::set_flat_button( ! CONFIG::get_flat_button() ); ARTICLE::get_admin()->set_command( "update_toolbar_button" ); BOARD::get_admin()->set_command( "update_toolbar_button" ); BBSLIST::get_admin()->set_command( "update_toolbar_button" ); IMAGE::get_admin()->set_command( "update_toolbar_button" ); MESSAGE::get_admin()->set_command( "update_toolbar_button" ); m_toolbar->update_button(); } // // ツールバーの背景描画切り替え // void Core::toggle_draw_toolbarback() { if( SESSION::is_booting() ) return; if( ! m_enable_menuslot ) return; #ifdef _DEBUG std::cout << "Core::toggle_draw_toolbarback\n"; #endif CONFIG::set_draw_toolbarback( ! CONFIG::get_draw_toolbarback() ); SKELETON::MsgDiag mdiag( nullptr, "正しく表示させるためにはJDimを再起動してください。" ); mdiag.run(); } // // 書き込みマーク表示切り替え // void Core::toggle_post_mark() { if( SESSION::is_booting() ) return; if( ! m_enable_menuslot ) return; CONFIG::set_show_post_mark( ! CONFIG::get_show_post_mark() ); ARTICLE::get_admin()->set_command( "relayout_all" ); } // // サイドバー表示切替え // // url を開いているときは閉じる // 閉じているときは開く // // サイドバーの表示が切り替わったら slot_show_hide_leftpane()が呼び出される // void Core::toggle_sidebar() { if( SESSION::is_booting() ) return; if( ! m_enable_menuslot ) return; #ifdef _DEBUG std::cout << "Core::toggle_sidebar focus = " << SESSION::focused_admin() << " mode = " << m_hpaned.get_ctrl().get_mode() << " empty = " << is_all_admin_empty() << std::endl; #endif // 閉じていたらサイドバーを開く if( ! SESSION::show_sidebar() ){ // 右ペーンが空の時は常に最大化 if( CONFIG::get_expand_sidebar() && is_all_admin_empty() ) m_hpaned.get_ctrl().set_mode( SKELETON::PANE_MAX_PAGE1 ); // 通常 else m_hpaned.get_ctrl().set_mode( SKELETON::PANE_NORMAL ); } // 開いていたら閉じる( 右ペーンが空の時は閉じない ) else if( ! is_all_admin_empty() ) m_hpaned.get_ctrl().set_mode( SKELETON::PANE_MAX_PAGE2 ); } // // サイドバーの表示が切り替わったときに呼ばれる // void Core::slot_show_hide_leftpane( int mode ) { const bool present = false; #ifdef _DEBUG std::cout << "slot_show_hide_leftpane mode = " << mode << std::endl; #endif if( mode == SKELETON::PANE_NORMAL ) SESSION::set_show_sidebar( true ); else SESSION::set_show_sidebar( false ); // 表示されたらサイドバーをフォーカス if( SESSION::focused_admin() != SESSION::FOCUS_SIDEBAR && SESSION::show_sidebar() ) switch_sidebar( std::string(), present ); // 非表示になったときは SESSION::focused_admin_sidebar() で指定されるadminにフォーカスを移す else{ #ifdef _DEBUG std::cout << "focused_admin = " << SESSION::focused_admin_sidebar() << std::endl; #endif if( SESSION::focused_admin_sidebar() == SESSION::FOCUS_BOARD ) switch_board( present ); else if( SESSION::focused_admin_sidebar() == SESSION::FOCUS_ARTICLE ) switch_article( present ); else if( SESSION::focused_admin_sidebar() == SESSION::FOCUS_IMAGE ) switch_image( present ); else if( SESSION::focused_admin_sidebar() == SESSION::FOCUS_MESSAGE ) switch_message( present ); else if( SESSION::focused_admin_sidebar() == SESSION::FOCUS_NOT ){ if( ! BOARD::get_admin()->empty() ) switch_board( present ); else if( ! ARTICLE::get_admin()->empty() ) switch_article( present ); else if( ! IMAGE::get_admin()->empty() ) switch_image( present ); } } } // // コマンドセット // // 他のadminクラスに委譲する場合はこの関数で、coreが実行するコマンドはexec_command()で処理 // void Core::set_command( const COMMAND_ARGS& command ) { if( SESSION::is_quitting() ) return; #ifdef _DEBUG std::cout << "Core::set_command : " << command.command << " " << command.url << " " << command.arg1 << " " << command.arg2 << " " << command.arg3 << " " << command.arg4 << std::endl; #endif bool emp_mes = ! ( SESSION::get_embedded_mes() && ! MESSAGE::get_admin()->empty() ); //////////////////////////// // article系のコマンド // メインビュー if( command.command == "open_article" ) { if( ! emp_mes ) m_vpaned_message.get_ctrl().set_mode( SKELETON::PANE_NORMAL ); ARTICLE::get_admin()->set_command( "open_view", command.url, // 以下 Admin::set_command() における COMMAND_ARGS::arg1, arg2,.... // 詳しくは Admin::open_view() を参照せよ command.arg1, // 開く位置 "false", // command.url を開いてるかチェックする command.arg2, // 開き方のモード "MAIN" // メインモードでarticleを開く ); // ジャンプ // command.arg3 がジャンプ先番号( empty ならジャンプしない )、arg4 がジャンプ元番号 if( ! command.arg3.empty() ) ARTICLE::get_admin()->set_command( "goto_num", command.url, command.arg3, command.arg4 ); return; } // メインビューを複数開く // if( command.command == "open_article_list" ) { if( ! emp_mes ) m_vpaned_message.get_ctrl().set_mode( SKELETON::PANE_NORMAL ); ARTICLE::get_admin()->set_command( "open_list", std::string(), // 以下 Admin::set_command() におけるCOMMAND_ARGS::arg1, arg2,.... command.arg1, // datファイルのURLを空白で区切って指定 command.arg2 // 開き方のモード ); return; } // レス抽出 else if( command.command == "open_article_res" ) { if( ! emp_mes ) m_vpaned_message.get_ctrl().set_mode( SKELETON::PANE_NORMAL ); ARTICLE::get_admin()->set_command( "open_view", command.url, // 以下 Admin::set_command() におけるCOMMAND_ARGS::arg1, arg2,.... // 詳しくは Admin::open_view() を参照せよ "newtab", // 開く位置 "false", // command.url を開いてるかチェックする "", // 開き方のモード "RES", // レス抽出モード command.arg1, // レス番号 ( from-to ) command.arg2 // ジャンプ番号( empty ならジャンプしない ) ); // 画像ウィンドウが開いている時にメインウィンドウを前面に出す switch_article( true ); return; } // 名前 で抽出 else if( command.command == "open_article_name" ) { if( ! emp_mes ) m_vpaned_message.get_ctrl().set_mode( SKELETON::PANE_NORMAL ); ARTICLE::get_admin()->set_command( "open_view", command.url, // 以下 Admin::set_command() における COMMAND_ARGS::arg1, arg2,.... // 詳しくは Admin::open_view() を参照せよ "newtab", // 開く位置 "false", // command.url を開いてるかチェックする "", // 開き方のモード "NAME", // 名前抽出モード command.arg1 // 名前 ); return; } // ID で抽出 else if( command.command == "open_article_id" ) { if( ! emp_mes ) m_vpaned_message.get_ctrl().set_mode( SKELETON::PANE_NORMAL ); ARTICLE::get_admin()->set_command( "open_view", command.url, // 以下 Admin::set_command() における COMMAND_ARGS::arg1, arg2,.... // 詳しくは Admin::open_view() を参照せよ "newtab", // 開く位置 "false", // command.url を開いてるかチェックする "", // 開き方のモード "ID", // ID 抽出モード command.arg1 // ユーザID ); return; } // ブックマークで抽出 else if( command.command == "open_article_bm" ) { if( ! emp_mes ) m_vpaned_message.get_ctrl().set_mode( SKELETON::PANE_NORMAL ); ARTICLE::get_admin()->set_command( "open_view", command.url, // 以下 Admin::set_command() における COMMAND_ARGS::arg1, arg2,.... // 詳しくは Admin::open_view() を参照せよ "newtab", // 開く位置 "false", // command.url を開いてるかチェックする "", // 開き方のモード "BM" // ブックマーク抽出モード ); return; } // 自分の書き込みレスを抽出 else if( command.command == "open_article_post" ) { if( ! emp_mes ) m_vpaned_message.get_ctrl().set_mode( SKELETON::PANE_NORMAL ); ARTICLE::get_admin()->set_command( "open_view", command.url, // 以下 Admin::set_command() における COMMAND_ARGS::arg1, arg2,.... // 詳しくは Admin::open_view() を参照せよ "newtab", // 開く位置 "false", // command.url を開いてるかチェックする "", // 開き方のモード "POST" // 書き込み抽出モード ); return; } // 高参照レスを抽出 else if( command.command == "open_article_highly_referened_res" ) { if( ! emp_mes ) m_vpaned_message.get_ctrl().set_mode( SKELETON::PANE_NORMAL ); ARTICLE::get_admin()->set_command( "open_view", command.url, // 以下 Admin::set_command() における COMMAND_ARGS::arg1, arg2,.... // 詳しくは Admin::open_view() を参照せよ "newtab", // 開く位置 "false", // command.url を開いてるかチェックする "", // 開き方のモード "HIGHREFRES" // 高参照レス抽出モード ); return; } // URL抽出 else if( command.command == "open_article_url" ) { if( ! emp_mes ) m_vpaned_message.get_ctrl().set_mode( SKELETON::PANE_NORMAL ); ARTICLE::get_admin()->set_command( "open_view", command.url, // 以下 Admin::set_command() における COMMAND_ARGS::arg1, arg2,.... // 詳しくは Admin::open_view() を参照せよ "newtab", // 開く位置 "false", // command.url を開いてるかチェックする "", // 開き方のモード "URL" // URL抽出モード ); return; } // 参照抽出 else if( command.command == "open_article_refer" ) { if( ! emp_mes ) m_vpaned_message.get_ctrl().set_mode( SKELETON::PANE_NORMAL ); ARTICLE::get_admin()->set_command( "open_view", command.url, // 以下 Admin::set_command() における COMMAND_ARGS::arg1, arg2,.... // 詳しくは Admin::open_view() を参照せよ "newtab", // 開く位置 "false", // command.url を開いてるかチェックする "", // 開き方のモード "REF", // 参照抽出モード command.arg1 // 対象レス番号 ); return; } // キーワードで抽出( AND/OR ) else if( command.command == "open_article_keyword" ) { if( ! emp_mes ) m_vpaned_message.get_ctrl().set_mode( SKELETON::PANE_NORMAL ); std::string mode_str = "KEYWORD"; if( command.arg2 == "true" ) mode_str = "KEYWORD_OR"; // OR 抽出 // 検索履歴更新 CORE::get_completion_manager()->set_query( CORE::COMP_SEARCH_ARTICLE, command.arg1 ); ARTICLE::get_admin()->set_command( "open_view", command.url, // 以下 Admin::set_command() におけるCOMMAND_ARGS::arg1, arg2,.... // 詳しくは Admin::open_view() を参照せよ "newtab", // 開く位置 "false", // command.url を開いてるかチェックする "", // 開き方のモード mode_str, // キーワード抽出モード command.arg1 // query ); return; } // 書き込みログ表示 else if( command.command == "open_article_postlog" ) { if( ! emp_mes ) m_vpaned_message.get_ctrl().set_mode( SKELETON::PANE_NORMAL ); ARTICLE::get_admin()->set_command( "open_view", "postlog", // 以下 Admin::set_command() における COMMAND_ARGS::arg1, arg2,.... // 詳しくは Admin::open_view() を参照せよ "newtab", // 開く位置 "false", // command.url を開いてるかチェックする "", // 開き方のモード "POSTLOG", // モード command.arg1 // ログ番号 ); return; } // ログ検索 else if( command.command == "open_article_searchlog" ) { if( CORE::get_search_manager()->is_searching() ){ SKELETON::MsgDiag mdiag( nullptr, "他の検索スレッドが実行中です" ); mdiag.run(); return; } if( ! emp_mes ) m_vpaned_message.get_ctrl().set_mode( SKELETON::PANE_NORMAL ); // URL_SEARCH_ALLBOARD の時は全ログ対象 std::string mode = "SEARCHLOG"; if( command.url == URL_SEARCH_ALLBOARD ) mode = "SEARCHALLLOG"; // 検索履歴更新 CORE::get_completion_manager()->set_query( CORE::COMP_SEARCH_ARTICLE, command.arg1 ); ARTICLE::get_admin()->set_command( "open_view", command.url, // 以下 Admin::set_command() における COMMAND_ARGS::arg1, arg2,.... // 詳しくは Admin::open_view() を参照せよ "newtab", // 開く位置 "false", // command.url を開いてるかチェックする "", // 開き方のモード mode, command.arg1, // query command.arg2, // "exec" ならViewを開いた直後に検索開始 command.arg3, // OR command.arg4 // BM ); return; } // スレタイ検索 else if( command.command == "open_article_searchtitle" ) { if( CORE::get_search_manager()->is_searching() ){ SKELETON::MsgDiag mdiag( nullptr, "他の検索スレッドが実行中です" ); mdiag.run(); return; } if( ! emp_mes ) m_vpaned_message.get_ctrl().set_mode( SKELETON::PANE_NORMAL ); ARTICLE::get_admin()->set_command( "open_view", URL_SEARCH_TITLE, // 以下 Admin::set_command() における COMMAND_ARGS::arg1, arg2,.... // 詳しくは Admin::open_view() を参照せよ "newtab", // 開く位置 "false", // command.url を開いてるかチェックする "", // 開き方のモード "SEARCHTITLE", // モード command.arg1, // query command.arg2 // "exec" ならViewを開いた直後に検索開始 ); return; } // datが更新されたときにローダから呼ばれる else if( command.command == "update_article" ){ ARTICLE::get_admin()->set_command( "update_view", command.url ); return; } // datが更新が終わったときにローダから呼ばれる // "update_article" はdatロード時などの更新中でも呼び出されるが // "update_article_finish" は最後に一度だけ呼び出される else if( command.command == "update_article_finish" ){ ARTICLE::get_admin()->set_command( "update_finish", command.url ); return; } // articleの削除 // // command.arg1 == "reget" のときはキャッシュだけ再読み込み // command.arg2 再読み込み後にジャンプするレス番号 // else if( command.command == "delete_article" ){ bool locked = FALSE; int num_open = 0; std::string current_url; if( command.arg1 == "reget" ){ locked = ARTICLE::get_admin()->is_locked( command.url ); current_url = ARTICLE::get_admin()->get_current_url(); if( current_url.rfind( command.url, 0 ) == 0 ) current_url = command.url; // タブを開く位置を取得 const std::list list_urls = ARTICLE::get_admin()->get_URLs(); for( const std::string& url : list_urls ) { if( url == command.url ) break; if( url.find( command.url ) != std::string::npos ) continue; ++num_open; } } ARTICLE::get_admin()->set_command( "unlock_views", command.url ); ARTICLE::get_admin()->set_command( "close_view", command.url, "closeall" // command.url を含む全てのビューを閉じる ); BOARD::get_admin()->set_command( "unlock_views", command.url ); BOARD::get_admin()->set_command( "close_view", command.url, "closeall" // command.url を含む全てのビューを閉じる ); DBTREE::delete_article( command.url, ( command.arg1 == "reget" ) ); if( DBTREE::article_is_cached( command.url ) ) return; // ポップアップも削除して対象となるarticlebaseのロックを解除 (注) ポップアップは遅延してdeleteされる // そうしないと articlebase::unlock_impl()が呼び出されないためnotetreebaseが削除されない ARTICLE::get_admin()->set_command( "delete_all_popups" ); // もう一度開く if( command.arg1 == "reget" ){ const std::string str_num_open = "page" + std::to_string( num_open ); const std::string mode = std::string( "noswitch" ) + ( locked ? " lock" : "" ); const std::string str_num_jump = command.arg2; #ifdef _DEBUG std::cout << "reget tab = " << str_num_open << " mode = " << mode << " jump = " << str_num_jump << std::endl; #endif core_set_command( "open_article", command.url , str_num_open, mode, str_num_jump ); ARTICLE::get_admin()->set_command( "switch_view", current_url ); } return; } // 全articleviewの再レイアウト else if( command.command == "relayout_all_article" ){ ARTICLE::get_admin()->set_command( "relayout_all" ); } // 全articleviewのフォントの初期化 else if( command.command == "init_font_all_article" ){ ARTICLE::get_admin()->set_command( "init_font" ); } // スレビューのタブのアイコン表示を更新 else if( command.command == "toggle_article_icon" ){ ARTICLE::get_admin()->set_command( "toggle_icon", command.url ); return; } // ツールバー表示更新 else if( command.command == "redraw_article_toolbar" ){ ARTICLE::get_admin()->set_command( "redraw_toolbar" ); return; } // ツールバーボタン更新 else if( command.command == "update_article_toolbar_button" ){ ARTICLE::get_admin()->set_command( "update_toolbar_button" ); return; } // ポップアップメニュー再作成 else if( command.command == "reset_article_popupmenu" ){ ARTICLE::get_admin()->set_command( "reset_popupmenu" ); return; } //////////////////////////// // board系のコマンド // メインビュー else if( command.command == "open_board" ){ BOARD::get_admin()->set_command( "open_view", command.url, // 以下 Admin::set_command() における COMMAND_ARGS::arg1, arg2,.... // 詳しくは Admin::open_view() を参照せよ command.arg1, // 開く位置 "false", // command.url を開いてるかチェック command.arg2, // 開き方のモード "MAIN" // モード ); return; } // 次スレ検索 else if( command.command == "open_board_next" ){ int tabpos = CONFIG::get_boardnexttab_pos(); std::string str_tab; std::string str_mode = ""; switch (tabpos) { case -1: // 2.8.5以前の動作 { // タブだらけになってしまうので実況中の場合はタブで開かない const bool live = SESSION::is_live( command.arg1 ); str_tab = (live) ? "false" : "newtab"; break; } case 1: // 新しいタブで開く str_tab = "newtab"; break; case 2: // アクティブなタブを置き換える str_tab = "false"; break; case 0: // 次スレ検索タブで開く default: str_tab = "replace"; str_mode = "boardnext"; break; } BOARD::get_admin()->set_command( "open_view", command.url, // 以下 Admin::set_command() における COMMAND_ARGS::arg1, arg2,.... // 詳しくは Admin::open_view() を参照せよ str_tab, // 開く位置 "false", // 既にビューを開いてるかチェックする str_mode, // 開き方のモード "NEXT", // モード command.arg1 // スレのアドレス ); return; } // ログ一覧表示 else if( command.command == "open_board_showlog" || command.command == "open_board_showalllog" ){ if( CORE::get_search_manager()->is_searching() ){ SKELETON::MsgDiag mdiag( nullptr, "他の検索スレッドが実行中です" ); mdiag.run(); return; } std::string url = command.url; if( command.command == "open_board_showalllog" ){ SKELETON::MsgDiag mdiag( nullptr, "全ログの一覧表示はかなり時間がかかります。\n\n本当に表示しますか?", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO ); if( mdiag.run() != Gtk::RESPONSE_YES ) return; url = URL_ALLLOG; } BOARD::get_admin()->set_command( "open_view", url, // 以下 Admin::set_command() における COMMAND_ARGS::arg1, arg2,.... // 詳しくは Admin::open_view() を参照せよ "newtab", // 開く位置 "false", // 既にビューを開いてるかチェックする "", // 開き方のモード "LOG" // モード ); return; } // サイドバーをスレ一覧に表示 else if( command.command == "open_sidebar_board" ) { BOARD::get_admin()->set_command( "open_view", command.url, // 以下 Admin::set_command() における COMMAND_ARGS::arg1, arg2,.... // 詳しくは Admin::open_view() を参照せよ command.arg1, // 開く位置 "false", // command.url を開いてるかチェック command.arg2, // 開き方のモード "SIDEBAR", // モード command.arg3, // お気に入りのディレクトリID、emptyでも可(その場合はcommand.urlにIDを含める) command.arg4 // "set_history" の時は板の履歴に登録する ); return; } // 複数開く // else if( command.command == "open_board_list" ) { BOARD::get_admin()->set_command( "open_list", std::string(), // 以下 Admin::set_command() における COMMAND_ARGS::arg1, arg2,.... command.arg1 // datファイルのURLを空白で区切って指定 ); return; } // 板を閉じる // その板に所属するスレも閉じる else if( command.command == "close_board" ){ std::string datbase = DBTREE::url_datbase( command.url ); ARTICLE::get_admin()->set_command_immediately( "close_view", datbase, "closeall" // datbase を含む全てのビューを閉じる ); BOARD::get_admin()->set_command_immediately( "close_view", command.url, "closeall" // command.url を含む全てのビューを閉じる ); } else if( command.command == "update_board" ){ BOARD::get_admin()->set_command( "update_view", command.url ); return; } else if( command.command == "update_board_item" ){ BOARD::get_admin()->set_command( "update_item", command.url, command.arg1 // スレID ); return; } // ツールバーボタン更新 else if( command.command == "update_board_toolbar_button" ){ BOARD::get_admin()->set_command( "update_toolbar_button" ); return; } // 列項目更新 else if( command.command == "update_board_columns" ){ BOARD::get_admin()->set_command( "update_columns" ); return; } // スレ一覧のタブのアイコン表示を更新 else if( command.command == "toggle_board_icon" ){ BOARD::get_admin()->set_command( "toggle_icon", command.url ); return; } // 表示中のスレ一覧のURLを選択 else if( command.command == "select_board_item" ){ BOARD::get_admin()->set_command_immediately( "select_item", command.url ); return; } // 全boardviewの再レイアウト else if( command.command == "relayout_all_board" ){ BOARD::get_admin()->set_command( "relayout_all" ); } // datのインポート else if( command.command == "import_dat" ){ if( command.arg1 == "show_diag" ){ SKELETON::MsgDiag mdiag( nullptr, "「"+ DBTREE::board_name( command.url ) + "」\n\nにdatファイルをインポートしますか?", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO ); if( mdiag.run() != Gtk::RESPONSE_YES ) return; } std::vector< std::string > list_files; // ダイアログを開いてファイルのリストを取得 if( command.arg2.empty() ){ list_files = CACHE::open_load_diag( nullptr, SESSION::get_dir_dat(), CACHE::FILE_TYPE_DAT, true ); } // 共有バッファからファイルのリストを取得 else if( CORE::SBUF_size() ){ const CORE::DATA_INFO_LIST list_info = CORE::SBUF_list_info(); for( const CORE::DATA_INFO& info : list_info ) { if( info.type == TYPE_FILE ) list_files.push_back( info.url ); } } if( list_files.size() ){ SESSION::set_dir_dat( MISC::get_dir( *list_files.begin() ) ); import_dat( command.url, list_files ); } } //////////////////////////// // bbslist(サイドバー)系のコマンド // 板一覧の表示を更新 // 板一覧を更新したとき等に呼び出す else if( command.command == "update_bbslist" ){ CORE::core_set_command( "set_status","" ,"" ); // フォーカスされていなかったらサイドバーにフォーカス切り替え if( SESSION::focused_admin() != SESSION::FOCUS_SIDEBAR || SESSION::get_sidebar_current_url() != URL_BBSLISTVIEW ) CORE::core_set_command( "switch_sidebar", URL_BBSLISTVIEW ); BBSLIST::get_admin()->set_command( "update_view", URL_BBSLISTVIEW ); return; } // お気に入りに項目追加 // 共有バッファにコピーデータをセットしておくこと else if( command.command == "append_favorite" ){ BBSLIST::get_admin()->set_command( "append_item", URL_FAVORITEVIEW ); return; } // お気に入りから command.url で指定したスレを削除 else if( command.command == "remove_favorite" ){ BBSLIST::get_admin()->set_command( "remove_item", URL_FAVORITEVIEW, command.url ); return; } // 新スレ移行時等にお気に入りのスレの url と 名前を変更 else if( command.command == "replace_favorite_thread" ){ BBSLIST::get_admin()->set_command( "replace_thread", URL_FAVORITEVIEW, command.arg1, // 旧スレのURL command.arg2 // 新スレのURL ); return; } // サイドバーの表示中のビューの全体更新チェック else if( command.command == "check_update_root" ){ check_update( false ); return; } // サイドバーの表示中のビューの全体を更新チェックして開く else if( command.command == "check_update_open_root" ){ check_update( true ); return; } // サイドバーの全体の更新チェックのキャンセル else if( command.command == "cancel_check_update" ){ BBSLIST::get_admin()->set_command( "cancel_check_update", SESSION::get_sidebar_current_url() ); return; } // お気に入りの編集ウィンドウを開く else if( command.command == "edit_favorite" ){ BBSLIST::get_admin()->set_command( "edit_tree", URL_FAVORITEVIEW ); return; } // サイドバーのアイコン表示を更新 ( スレ ) else if( command.command == "toggle_sidebar_articleicon" ){ BBSLIST::get_admin()->set_command_immediately( "toggle_articleicon", URL_FAVORITEVIEW, command.url ); BBSLIST::get_admin()->set_command_immediately( "toggle_articleicon", URL_HISTTHREADVIEW, command.url ); BBSLIST::get_admin()->set_command_immediately( "toggle_articleicon", URL_HISTCLOSEVIEW, command.url ); // 履歴メニューを開いていたらメニューのアイコンも更新 HISTORY::get_history_manager()->set_menulabel( URL_HISTTHREADVIEW ); HISTORY::get_history_manager()->set_menulabel( URL_HISTCLOSEVIEW ); return; } // サイドバーのアイコン表示を更新 ( 板 ) else if( command.command == "toggle_sidebar_boardicon" ){ BBSLIST::get_admin()->set_command_immediately( "toggle_boardicon", URL_FAVORITEVIEW, command.url ); BBSLIST::get_admin()->set_command_immediately( "toggle_boardicon", URL_HISTBOARDVIEW, command.url ); // 履歴メニューを開いていたらメニューのアイコンも更新 HISTORY::get_history_manager()->set_menulabel( URL_HISTBOARDVIEW ); return; } // 表示中のサイドバーのURLを選択 else if( command.command == "select_sidebar_item" ){ BBSLIST::get_admin()->set_command_immediately( "select_item", SESSION::get_sidebar_current_url(), command.url ); return; } // 移転時にサイドバーに登録されているURLを新URLに更新 else if( command.command == "update_sidebar_item" ){ BBSLIST::get_admin()->set_command( "update_item", URL_BBSLISTVIEW ); BBSLIST::get_admin()->set_command( "update_item", URL_FAVORITEVIEW ); BBSLIST::get_admin()->set_command( "update_item", URL_HISTTHREADVIEW ); BBSLIST::get_admin()->set_command( "update_item", URL_HISTBOARDVIEW ); BBSLIST::get_admin()->set_command( "update_item", URL_HISTCLOSEVIEW ); BBSLIST::get_admin()->set_command( "update_item", URL_HISTCLOSEBOARDVIEW ); return; } // 各履歴の更新 // 共有バッファにデータをセットしておくこと else if( command.command == "append_history" ){ BBSLIST::get_admin()->set_command_immediately( "append_history", command.url ); return; } // 各履歴から command.arg1 で指定した項目を削除 else if( command.command == "remove_history" ){ BBSLIST::get_admin()->set_command( "remove_item", command.url, command.arg1 ); return; } // 各履歴から先頭にある項目を削除 else if( command.command == "remove_headhistory" ){ BBSLIST::get_admin()->set_command( "remove_headitem", command.url ); return; } // 各履歴から全項目を削除 else if( command.command == "remove_allhistories" ){ BBSLIST::get_admin()->set_command( "remove_allitems", command.url ); return; } // ツールバーボタン更新 else if( command.command == "update_bbslist_toolbar_button" ){ BBSLIST::get_admin()->set_command( "update_toolbar_button" ); return; } // 全bbslistviewの再レイアウト else if( command.command == "relayout_all_bbslist" ){ BBSLIST::get_admin()->set_command( "relayout_all" ); } //////////////////////////// // image系のコマンド else if( command.command == "open_image" ){ show_imagetab(); // キャッシュに無かったらロード if( ! DBIMG::is_cached( command.url ) && ! DBIMG::is_loading( command.url ) && ! DBIMG::is_wait( command.url ) ){ const bool mosaic = CONFIG::get_use_mosaic(); DBIMG::download_img( command.url, std::string(), mosaic ); } IMAGE::get_admin()->set_command( "open_view", command.url ); return; } else if( command.command == "delete_image" ){ DBIMG::delete_cache( command.url ); return; } else if( command.command == "close_image" ){ IMAGE::get_admin()->set_command( "close_view", command.url ); } // キャッシュに無い画像を閉じる else if( command.command == "close_nocached_image_views" ){ IMAGE::get_admin()->set_command( "close_nocached_views" ); return; } //////////////////////////// // message系 else if( command.command == "open_message" ){ if( ! SESSION::is_online() ){ SKELETON::MsgDiag mdiag( nullptr, "オフラインです" ); mdiag.run(); } else{ const size_t max_lng = DBTREE::board_get_max_dat_lng( command.url ); if( max_lng > 0 && DBTREE::article_lng_dat( command.url ) > max_lng * 1000 ){ SKELETON::MsgDiag mdiag( nullptr, "スレのサイズが" + std::to_string( max_lng ) + "Kバイトを越えています。\n\n本当に書き込みますか?", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO ); if( mdiag.run() != Gtk::RESPONSE_YES ) return; } if( SESSION::get_embedded_mes() ) m_vpaned_message.get_ctrl().set_mode( SKELETON::PANE_NORMAL ); MESSAGE::get_admin()->set_command( "open_view", command.url, command.arg1 ); } } else if( command.command == "close_message" ){ if( ! MESSAGE::get_admin()->empty() ) MESSAGE::get_admin()->set_command( "close_message", command.url ); } else if( command.command == "create_new_thread" ){ if( ! SESSION::is_online() ){ SKELETON::MsgDiag mdiag( nullptr, "オフラインです" ); mdiag.run(); } else if( DBTREE::url_bbscgi_new( command.url ).empty() ){ SKELETON::MsgDiag mdiag( nullptr, "この板では新スレを立てることは出来ません" ); mdiag.run(); } else{ if( SESSION::get_embedded_mes() ) m_vpaned_message.get_ctrl().set_mode( SKELETON::PANE_NORMAL ); MESSAGE::get_admin()->set_command( "open_view", command.url, command.arg1, "new" ); } } // ツールバーボタン更新 else if( command.command == "update_message_toolbar_button" ){ MESSAGE::get_admin()->set_command( "update_toolbar_button" ); return; } // messageviewの再レイアウト else if( command.command == "relayout_all_message" ){ MESSAGE::get_admin()->set_command( "relayout_all" ); } // meesageview の wrap 切り替え else if( command.command == "toggle_message_wrap" ){ CONFIG::set_message_wrap( ! CONFIG::get_message_wrap() ); MESSAGE::get_admin()->set_command( "toggle_wrap" ); } //////////////////////////// // ダイアログボックスが表示/非表示状態になった else if( command.command == "dialog_shown" ){ // 表示された // フォーカスが外れて画像ウィンドウの開け閉めをしないようにする IMAGE::get_admin()->set_command_immediately( "disable_fold_win" ); MESSAGE::get_admin()->set_command_immediately( "disable_fold_win" ); return; } else if( command.command == "dialog_hidden" ){ // 非表示になった IMAGE::get_admin()->set_command_immediately( "enable_fold_win" ); MESSAGE::get_admin()->set_command_immediately( "enable_fold_win" ); return; } //////////////////////////// // 再描画系 // command.url を含むviewを全て再描画 else if( command.command == "redraw" ){ ARTICLE::get_admin()->set_command( "redraw", command.url ); BOARD::get_admin()->set_command( "redraw", command.url ); BBSLIST::get_admin()->set_command( "redraw", command.url ); IMAGE::get_admin()->set_command( "redraw", command.url ); MESSAGE::get_admin()->set_command( "redraw", command.url ); return; } // 表示中のbbslist viewを再描画 else if( command.command == "redraw_bbslist" ) { BBSLIST::get_admin()->set_command( "redraw_current_view" ); return; } // 表示中のboard viewを再描画 else if( command.command == "redraw_board" ) { BOARD::get_admin()->set_command( "redraw_current_view" ); return; } // 表示中のarticle viewを再描画 else if( command.command == "redraw_article" ) { ARTICLE::get_admin()->set_command( "redraw_current_view" ); return; } // 表示中のmessage viewを再描画 else if( command.command == "redraw_message" ) { MESSAGE::get_admin()->set_command( "redraw_current_view" ); return; } // command.url を含むarticle viewを全て再レイアウトして再描画 else if( command.command == "relayout_article" ){ ARTICLE::get_admin()->set_command( "relayout_views", command.url ); return; } // 表示中のimage viewを再描画 else if( command.command == "redraw_image" ) { IMAGE::get_admin()->set_command( "redraw_current_view" ); return; } // 表示中のviewを全部再描画 else if( command.command == "redraw_all" ) { ARTICLE::get_admin()->set_command( "redraw_current_view" ); BOARD::get_admin()->set_command( "redraw_current_view" ); BBSLIST::get_admin()->set_command( "redraw_current_view" ); IMAGE::get_admin()->set_command( "redraw_current_view" ); MESSAGE::get_admin()->set_command( "redraw_current_view" ); return; } /////////////////////////////// // 移転があった else if( command.command == "update_url" ){ ARTICLE::get_admin()->set_command( "update_url", command.url, command.arg1 ); BOARD::get_admin()->set_command( "update_url", command.url, command.arg1 ); BBSLIST::get_admin()->set_command( "update_url", command.url, command.arg1 ); IMAGE::get_admin()->set_command( "update_url", command.url, command.arg1 ); MESSAGE::get_admin()->set_command( "update_url", command.url, command.arg1 ); return; } /////////////////////////////// // 板名変更 else if( command.command == "update_boardname" ){ ARTICLE::get_admin()->set_command( "update_boardname", command.url ); BOARD::get_admin()->set_command( "update_boardname", command.url ); BBSLIST::get_admin()->set_command( "update_boardname", command.url ); IMAGE::get_admin()->set_command( "update_boardname", command.url ); MESSAGE::get_admin()->set_command( "update_boardname", command.url ); return; } /////////////////////////////// // フォーカス回復 else if( command.command == "restore_focus" ){ restore_focus( true, ( command.arg1 == "present" ) ); return; } /////////////////////////////// // タイトル、URL、ステータスなどの表示 else if( command.command == "set_title" ){ m_title = command.arg1; set_maintitle(); } else if( command.command == "set_url" ){ m_toolbar->m_entry_url.set_text( command.url ); } else if( command.command == "set_status" ){ m_win_main.set_status( command.arg1 ); } else if( command.command == "set_status_color" ){ if( CONFIG::get_change_stastatus_color() ) m_win_main.set_status_color( command.arg1 ); } // 一時的にステータスバーの表示を変える( マウスオーバーでのURL表示用 ) else if( command.command == "set_status_temporary" ){ m_win_main.set_status_temporary( command.arg1 ); } // 一時的に変えたステータスバーの表示を戻す else if( command.command == "restore_status" ){ m_win_main.restore_status(); } // ステータスバーのマウスジェスチャ欄にに一時的な情報を表示 // ダイアログを表示するまでも無い場合に使用する else if( command.command == "set_info" ){ m_win_main.set_mginfo( command.arg1 ); } // マウスジェスチャ else if( command.command == "set_mginfo" ){ // 画像ウィンドウが表示されている場合 if( ! SESSION::get_embedded_img() && SESSION::is_shown_win_img() && SESSION::is_focus_win_img() ){ IMAGE::get_admin()->set_command( "set_mginfo", "", command.arg1 ); } else m_win_main.set_mginfo( command.arg1 ); } //////////////////////////// // ポップアップを隠す else if( command.command == "hide_popup" ){ BBSLIST::get_admin()->set_command_immediately( "hide_popup" ); ARTICLE::get_admin()->set_command_immediately( "hide_popup" ); MESSAGE::get_admin()->set_command_immediately( "hide_popup" ); return; } //////////////////////////// // その他 Coreが自前で処理するコマンド( Core::exec_command() で処理 ) m_list_command.push_back( command ); dispatch(); // 一度メインループに戻った後にcallback_dispatch() が呼び戻される } // // ディスパッチャのコールバック関数 // void Core::callback_dispatch() { while( m_list_command.size() ) exec_command(); } // coreが自前でする処理 void Core::exec_command() { const bool present = false; if( m_list_command.size() == 0 ) return; COMMAND_ARGS command = m_list_command.front(); m_list_command.pop_front(); #ifdef _DEBUG std::cout << "Core::exec_command : " << command.command << " " << command.url << " " << command.arg1 << " " << command.arg2 << " " << command.arg3 << " " << command.arg4 << std::endl; #endif // 各管理クラスが開いていたURLを復元 if( command.command == "restore_views" ){ // bbslist は無条件でリストア // 板一覧がロードされてない時はここでロードされる BBSLIST::get_admin()->set_command( "restore" ); // 残りは CONFIG::get_restore_* で全てリストアするかロックされているタブだけリストアするか決定 BOARD::get_admin()->set_command( "restore", "", ( CONFIG::get_restore_board() ? "" : "only_locked" ) ); ARTICLE::get_admin()->set_command( "restore", "", ( CONFIG::get_restore_article() ? "" : "only_locked" ) ); // ロックされている画像があるか調べる bool img_locked = false; if( ! CONFIG::get_restore_image() ){ std::list< bool > list_locked = SESSION::get_image_locked(); img_locked = std::any_of( list_locked.cbegin(), list_locked.cend(), []( bool b ) { return b; } ); } if( SESSION::image_URLs().size() && ( CONFIG::get_restore_image() || img_locked ) ){ show_imagetab(); IMAGE::get_admin()->set_command( "restore", "", ( CONFIG::get_restore_image() ? "" : "only_locked" ) ); } } // 各ビューのタブ幅調整 else if( command.command == "adjust_tabwidth" ){ BOARD::get_admin()->set_command( "adjust_tabwidth" ); ARTICLE::get_admin()->set_command( "adjust_tabwidth" ); } // メインツールバーのボタン表示更新 else if( command.command == "update_main_toolbar_button" ){ m_toolbar->update_button(); } // 履歴のクリア else if( command.command == "clear_board" ) slot_clear_board(); else if( command.command == "clear_thread" ) slot_clear_thread(); else if( command.command == "clear_closed_thread" ) slot_clear_close(); else if( command.command == "clear_search" ) slot_clear_search(); else if( command.command == "clear_name" ) slot_clear_name(); else if( command.command == "clear_mail" ) slot_clear_mail(); // ビューの切替え else if( command.command == "switch_article" ) switch_article( present ); else if( command.command == "switch_board" ) switch_board( present ); else if( command.command == "switch_sidebar" ) switch_sidebar( command.url, present ); else if( command.command == "switch_image" ) switch_image( present ); else if( command.command == "switch_message" ) switch_message( present ); else if( command.command == "toggle_article" ) toggle_article(); else if( command.command == "switch_leftview" ) switch_leftview(); else if( command.command == "switch_rightview" ) switch_rightview(); // メニューバー表示/非表示 else if( command.command == "toggle_menubar" ) toggle_menubar(); // メインツールバー表示/非表示 else if( command.command == "toggle_toolbarmain" ) slot_toggle_toolbarmain(); // サイドバー表示/非表示 else if( command.command == "toggle_sidebar" ) toggle_sidebar(); // 2chへのログイン処理が完了した else if( command.command == "login2ch_finished" ) set_maintitle(); // BEへのログイン処理が完了した else if( command.command == "loginbe_finished" ) set_maintitle(); // あるadminのnotebookが空になった else if( command.command == "empty_page" ) empty_page( command.url ); // あるadminのnotebookがswitchした else if( command.command == "page_switched" ){ set_toggle_view_button(); } // bbsmenu再読み込み else if( command.command == "reload_bbsmenu" ){ slot_reload_list(); } // グローバルあぼーん(名前) else if( command.command == "set_global_abone_name" ){ std::list< std::string >list_tmp = CONFIG::get_list_abone_name(); list_tmp.push_back( command.arg1 ); CONFIG::set_list_abone_name( list_tmp ); DBTREE::update_abone_all_article(); CORE::core_set_command( "relayout_all_article" ); } // グローバルあぼーん(ワード) else if( command.command == "set_global_abone_word" ){ std::list< std::string >list_tmp = CONFIG::get_list_abone_word(); list_tmp.push_back( command.arg1 ); CONFIG::set_list_abone_word( list_tmp ); DBTREE::update_abone_all_article(); CORE::core_set_command( "relayout_all_article" ); } // ユーザコマンド実行 else if( command.command == "exec_usr_cmd" ){ CORE::get_usrcmd_manager()->exec( atoi( command.arg1.c_str() ), // コマンド番号 command.url, // URL command.arg2, // Link command.arg3, // 選択文字 atoi( command.arg4.c_str() ) // レス番号 ); } // JD 終了 else if( command.command == "quit_jd" ) slot_quit(); // 最大化/最大化解除 else if( command.command == "maximize_mainwin" ){ if( ! SESSION::is_maximized_win_main() ) m_win_main.maximize_win(); else m_win_main.unmaximize_win(); } // 最小化 else if( command.command == "iconify_mainwin" ){ m_win_main.iconify_win(); } // 全画面表示 else if( command.command == "toggle_fullscreen" ){ slot_toggle_fullscreen(); } // URL のオープン関係 // URLを開くダイアログを表示 else if( command.command == "show_openurl_diag" ) slot_openurl(); // 常に外部ブラウザで開く場合 else if( command.command == "open_url_browser" ) open_by_browser( command.url ); // タイプによって判定する場合 else if( command.command == "open_url" ){ // プロトコルが指定されていない場合 command.url = MISC::remove_space( command.url ); if( command.url.rfind( "http://", 0 ) != 0 && command.url.rfind( "https://", 0 ) != 0 && command.url.rfind( "file://", 0 ) != 0 && command.url.rfind( "ftp://", 0 ) != 0 ){ // ローカルのファイルかチェック std::string path_real = CACHE::get_realpath( command.url ); if( ! path_real.empty() ) command.url = "file://" + path_real; // "http://"を仮定する else command.url = "http://" + command.url; } int num_from, num_to; std::string num_str; const std::string url_dat = DBTREE::url_dat( command.url, num_from, num_to, num_str ); const std::string boardbase = DBTREE::url_boardbase( command.url ); // datの場合ビューで開く if( ! url_dat.empty() ){ #ifdef _DEBUG std::cout << "exec : open_article url = " << url_dat << std::endl; #endif if( num_from ) CORE::core_set_command( "open_article" , url_dat, "newtab", "", std::to_string( num_from ) ); else CORE::core_set_command( "open_article" , url_dat, "newtab", "" ); } // 画像の場合 else if( DBIMG::get_type_ext( command.url ) != DBIMG::T_UNKNOWN ){ // 画像ビューを使用 if( CONFIG::get_use_image_view() ){ if( ! SESSION::is_online() ){ SKELETON::MsgDiag mdiag( nullptr, "オフラインです" ); mdiag.run(); } else{ // キャッシュに無かったらロード if( ! DBIMG::is_cached( command.url ) ){ const bool mosaic = CONFIG::get_use_mosaic(); DBIMG::download_img( command.url, std::string(), mosaic ); } CORE::core_set_command( "open_image", command.url ); CORE::core_set_command( "switch_image" ); } } // 外部ビュアー使用 else open_by_browser( command.url ); } // 掲示板のベースURLの場合 else if( ! boardbase.empty() ){ #ifdef _DEBUG std::cout << "exec : open_board url = " << boardbase << std::endl; #endif CORE::core_set_command( "open_board", boardbase, "true" ); } // その他 else open_by_browser( command.url ); } // ある admin クラスのコマンドが空になった else if( command.command == "empty_command" ){} // ある jdwindow クラスのブートが終わった else if( command.command == "window_boot_fin" ){} // 起動中 if( SESSION::is_booting() && ! m_init ){ // coreがコマンドを全て実行して、かつ全てのadminクラスがブートした if( m_list_command.size() == 0 && ! BBSLIST::get_admin()->is_booting() && ! BOARD::get_admin()->is_booting() && ! ARTICLE::get_admin()->is_booting() && ! IMAGE::get_admin()->is_booting() && ! MESSAGE::get_admin()->is_booting() ){ // 起動完了 SESSION::set_booting( false ); exec_command_after_boot(); } } } // // 起動処理完了後に実行する処理 // void Core::exec_command_after_boot() { #ifdef _DEBUG std::cout << "Core::exec_command_after_boot\n"; #endif // サイドバー表示状態変更 if( ! SESSION::show_sidebar() ) m_hpaned.get_ctrl().set_mode( SKELETON::PANE_MAX_PAGE2 ); // フォーカス回復 restore_focus( true, true ); // タイマーセット sigc::slot< bool > slot_timeout = sigc::bind( sigc::mem_fun(*this, &Core::slot_timeout), 0 ); m_conn_timer = JDLIB::Timeout::connect( slot_timeout, TIMER_TIMEOUT ); // 2chログイン if( SESSION::login2ch() ) slot_toggle_login2ch(); // BEログイン if( SESSION::loginbe() ) slot_toggle_loginbe(); // タイトル表示 set_maintitle(); // 画像ウィンドウが復元されると画面が表示されないので再レイアウト指定 ARTICLE::get_admin()->set_command( "relayout_current_view" ); // お気に入り更新チェック if( CONFIG::get_check_update_boot() && SESSION::is_online() ){ BBSLIST::get_admin()->set_command( "check_update_root", URL_FAVORITEVIEW ); } #ifdef _DEBUG std::cout << "\n\n----------- boot fin --------------\n\n"; #endif } // // フォーカス回復 // // force : true の時は強制的に回復(処理が重い) // present : フォーカス回復後にメインウィンドウをpresentする // void Core::restore_focus( const bool force, const bool present ) { int admin = SESSION::focused_admin(); // フォーカスするadminがemptyならリセット bool reset_focus = false; switch( admin ) { case SESSION::FOCUS_SIDEBAR: if( BBSLIST::get_admin()->empty() ) reset_focus = true; break; case SESSION::FOCUS_BOARD: if( BOARD::get_admin()->empty() ) reset_focus = true; break; case SESSION::FOCUS_ARTICLE: if( ARTICLE::get_admin()->empty() ) reset_focus = true; break; case SESSION::FOCUS_IMAGE: if( IMAGE::get_admin()->empty() ) reset_focus = true; break; case SESSION::FOCUS_MESSAGE: if( MESSAGE::get_admin()->empty() ) reset_focus = true; break; } if( reset_focus ){ SESSION::set_focused_admin( SESSION::FOCUS_NOT ); SESSION::set_focused_admin_sidebar( SESSION::FOCUS_NOT ); admin = SESSION::FOCUS_NOT; } #ifdef _DEBUG std::cout << "Core::restore_focus admin = " << admin << std::endl; #endif // ウィンドウが表示されているときはウィンドウのフォーカスを外す if( ! SESSION::get_embedded_img() ) IMAGE::get_admin()->set_command_immediately( "focus_out" ); if( ! SESSION::get_embedded_mes() ) MESSAGE::get_admin()->set_command_immediately( "focus_out" ); if( ! force ){ // 通常回復 // フォーカス状態回復 switch( admin ) { case SESSION::FOCUS_SIDEBAR: BBSLIST::get_admin()->set_command_immediately( "restore_focus" ); break; case SESSION::FOCUS_BOARD: BOARD::get_admin()->set_command_immediately( "restore_focus" ); break; case SESSION::FOCUS_ARTICLE: ARTICLE::get_admin()->set_command_immediately( "restore_focus" ); break; case SESSION::FOCUS_IMAGE: IMAGE::get_admin()->set_command_immediately( "restore_focus" ); break; case SESSION::FOCUS_MESSAGE: MESSAGE::get_admin()->set_command_immediately( "restore_focus" ); break; } } else { // 強制的に回復 int admin_sidebar = SESSION::focused_admin_sidebar(); if( admin == SESSION::FOCUS_NOT ){ if( ! ARTICLE::get_admin()->empty() ) admin = admin_sidebar = SESSION::FOCUS_ARTICLE; else if( ! BOARD::get_admin()->empty() ) admin = admin_sidebar = SESSION::FOCUS_BOARD; else if( ! BBSLIST::get_admin()->empty() ) admin = admin_sidebar = SESSION::FOCUS_SIDEBAR; } SESSION::set_focused_admin( SESSION::FOCUS_NOT ); SESSION::set_focused_admin_sidebar( SESSION::FOCUS_NOT ); // adminの表示状態回復 set_right_current_page( SESSION::notebook_main_page() ); // フォーカス状態回復 switch( admin ){ case SESSION::FOCUS_SIDEBAR: switch_sidebar( std::string(), present ); break; case SESSION::FOCUS_BOARD: switch_board( present ); break; case SESSION::FOCUS_ARTICLE: switch_article( present ); break; case SESSION::FOCUS_IMAGE: switch_image( present ); break; case SESSION::FOCUS_MESSAGE: switch_message( present ); break; } SESSION::set_focused_admin( admin ); SESSION::set_focused_admin_sidebar( admin_sidebar ); } } // // メインタイマー // // TIMER_TIMEOUT msec毎に呼び出される // bool Core::slot_timeout( int timer_number ) { // 各管理クラスにクロック入力する BBSLIST::get_admin()->clock_in(); BOARD::get_admin()->clock_in(); ARTICLE::get_admin()->clock_in(); IMAGE::get_admin()->clock_in(); MESSAGE::get_admin()->clock_in(); DBIMG::clock_in(); // Panedにクロック入力 m_hpaned.get_ctrl().clock_in(); m_vpaned_r.get_ctrl().clock_in(); m_hpaned_r.get_ctrl().clock_in(); m_vpaned_message.get_ctrl().clock_in(); // セッション保存 if( CONFIG::get_save_session() ){ ++m_count_savesession; if( m_count_savesession > ( CONFIG::get_save_session() * 60 * 1000 / TIMER_TIMEOUT )){ m_count_savesession = 0; save_session(); } } return true; } // // 右ペーンのnotebookのタブの切替え // void Core::slot_switch_page( Gtk::Widget*, guint ) { const bool present = false; const int page = get_right_current_page(); #ifdef _DEBUG std::cout << "Core::slot_switch_page " << page << std::endl; #endif switch( page ){ case SESSION::PAGE_ARTICLE: switch_article( present ); break; case SESSION::PAGE_IMAGE: switch_image( present ); break; case SESSION::PAGE_BOARD: switch_board( present ); break; } } // 右ペーンのnotebookのページ番号 int Core::get_right_current_page() const { const int mode = SESSION::get_mode_pane(); int page = m_notebook_right.get_current_page(); if( mode == SESSION::MODE_2PANE ){ // 2paneで画像ビューをウィンドウ表示している場合 // 1 ページ目はIMAGEではなくてBOARDになる if( ! SESSION::get_embedded_img() && page == 1 ) page = SESSION::PAGE_BOARD; } return page; } // 右ペーンのnotebookのページをセット void Core::set_right_current_page( int page ) { // page が empty でないか調べる if( page == SESSION::PAGE_ARTICLE && ARTICLE::get_admin()->empty() && ( SESSION::get_embedded_mes() && MESSAGE::get_admin()->empty() ) ){ if( SESSION::get_mode_pane() == SESSION::MODE_2PANE && ! BOARD::get_admin()->empty() ) page = SESSION::PAGE_BOARD; else if( ! IMAGE::get_admin()->empty() ) page = SESSION::PAGE_IMAGE; else return; } if( page == SESSION::PAGE_BOARD && BOARD::get_admin()->empty() ){ if( ! ARTICLE::get_admin()->empty() ) page = SESSION::PAGE_ARTICLE; else if( ! IMAGE::get_admin()->empty() ) page = SESSION::PAGE_IMAGE; else return; } if( page == SESSION::PAGE_IMAGE && IMAGE::get_admin()->empty() ){ if( ! ARTICLE::get_admin()->empty() ) page = SESSION::PAGE_ARTICLE; else if( SESSION::get_mode_pane() == SESSION::MODE_2PANE && ! BOARD::get_admin()->empty() ) page = SESSION::PAGE_BOARD; else return; } if( get_right_current_page() == page ) return; // 画像ビューをウィンドウ表示している場合 if( ! SESSION::get_embedded_img() && page == SESSION::PAGE_IMAGE ) return; if( SESSION::get_mode_pane() == SESSION::MODE_2PANE ){ // 2paneで画像ビューをウィンドウ表示している場合 // 1 ページ目はIMAGEではなくてBOARDになる if( ! SESSION::get_embedded_img() && page == SESSION::PAGE_BOARD ) page = 1; } else if( page == SESSION::PAGE_BOARD ) return; // 2pane以外ではboardはnotebookに含まれない m_notebook_right.set_current_page( page ); SESSION::set_notebook_main_page( get_right_current_page() ); } // // フォーカスアウトイベント // bool Core::slot_focus_out_event( GdkEventFocus* ) { #ifdef _DEBUG std::cout << "Core::slot_focus_out_event admin = " << SESSION::focused_admin() << std::endl; #endif if( SESSION::is_dialog_shown() ) return true; FOCUS_OUT_ALL(); return true; } // // フォーカスインイベント // bool Core::slot_focus_in_event( GdkEventFocus* ) { #ifdef _DEBUG std::cout << "Core::slot_focus_in_event admin = " << SESSION::focused_admin() << std::endl; #endif restore_focus( false, false ); return true; } // // URL entryでenterを押した // void Core::slot_active_url() { std::string url = m_toolbar->m_entry_url.get_text(); if( !url.empty() ){ if( url == "about:config" ) slot_aboutconfig(); else CORE::core_set_command( "open_url", url ); } } // // あるadminがemptyになったので他のadminにスイッチ // // url : empty になったadmin // void Core::empty_page( const std::string& url ) { #ifdef _DEBUG std::cout << "Core::empty_page url = " << url << std::endl; #endif const bool present = false; const bool emp_img = ! ( SESSION::get_embedded_img() && ! IMAGE::get_admin()->empty() ); const bool emp_mes = ! ( SESSION::get_embedded_mes() && ! MESSAGE::get_admin()->empty() ); int focused_admin = SESSION::FOCUS_NOT; // emptyになったadminとフォーカスされているadminが異なる場合は // フォーカスを移動しない if( SESSION::focused_admin() == SESSION::FOCUS_SIDEBAR ) focused_admin = SESSION::FOCUS_SIDEBAR; else if( SESSION::focused_admin() == SESSION::FOCUS_BOARD && ! BOARD::get_admin()->empty() ) focused_admin = SESSION::FOCUS_BOARD; else if( SESSION::focused_admin() == SESSION::FOCUS_ARTICLE && ! ARTICLE::get_admin()->empty() ) focused_admin = SESSION::FOCUS_ARTICLE; else if( SESSION::focused_admin() == SESSION::FOCUS_IMAGE && ! IMAGE::get_admin()->empty() ) focused_admin = SESSION::FOCUS_IMAGE; else if( SESSION::focused_admin() == SESSION::FOCUS_MESSAGE && ! MESSAGE::get_admin()->empty() ) focused_admin = SESSION::FOCUS_MESSAGE; // 埋め込み画像ビューが空になった if( url == URL_IMAGEADMIN && SESSION::get_embedded_img() ){ if( CONFIG::get_hide_imagetab() ) hide_imagetab(); // 空でないadminを前に出す if( SESSION::get_mode_pane() == SESSION::MODE_2PANE ){ if( get_right_current_page() == SESSION::PAGE_IMAGE ){ if( ! ARTICLE::get_admin()->empty() ) switch_article( present ); else if( ! BOARD::get_admin()->empty() ) switch_board( present ); } } else if( ! ARTICLE::get_admin()->empty() ) switch_article( present ); // フォーカス切り替え if( focused_admin == SESSION::FOCUS_NOT ){ if( ! ARTICLE::get_admin()->empty() ) focused_admin = SESSION::FOCUS_ARTICLE; else if( ! BOARD::get_admin()->empty() ) focused_admin = SESSION::FOCUS_BOARD; else{ focused_admin = SESSION::FOCUS_SIDEBAR; SESSION::set_focused_admin_sidebar( SESSION::FOCUS_NOT ); } } } // articleビューが空になった else if( url == URL_ARTICLEADMIN ){ // 空でないadminを前に出す if( SESSION::get_mode_pane() == SESSION::MODE_2PANE ){ if( get_right_current_page() == SESSION::PAGE_ARTICLE && emp_mes ) { if( BOARD::get_admin()->empty() && ! emp_img ) switch_image( present ); else if( ! BOARD::get_admin()->empty() ) switch_board( present ); } } else if( ! emp_img ) switch_image( present ); // フォーカス切り替え if( focused_admin == SESSION::FOCUS_NOT ){ if( ! emp_mes ) focused_admin = SESSION::FOCUS_MESSAGE; else if( ! BOARD::get_admin()->empty() ) focused_admin = SESSION::FOCUS_BOARD; else{ focused_admin = SESSION::FOCUS_SIDEBAR; SESSION::set_focused_admin_sidebar( SESSION::FOCUS_NOT ); } } } // boardビューが空になった else if( url == URL_BOARDADMIN ){ // 空でないadminを前に出す if( SESSION::get_mode_pane() == SESSION::MODE_2PANE ){ if( get_right_current_page() == SESSION::PAGE_BOARD ){ if( ! ARTICLE::get_admin()->empty() ) switch_article( present ); else if( ! emp_img ) switch_image( present ); } } // フォーカス切り替え if( focused_admin == SESSION::FOCUS_NOT ){ focused_admin = SESSION::FOCUS_SIDEBAR; SESSION::set_focused_admin_sidebar( SESSION::FOCUS_NOT ); } } // 埋め込みmessageビューが空になった else if( url == URL_MESSAGEADMIN && SESSION::get_embedded_mes() ){ // フォーカス切り替え if( focused_admin == SESSION::FOCUS_NOT ){ if( ! ARTICLE::get_admin()->empty() ) focused_admin = SESSION::FOCUS_ARTICLE; else if( ! BOARD::get_admin()->empty() ) focused_admin = SESSION::FOCUS_BOARD; else{ focused_admin = SESSION::FOCUS_SIDEBAR; SESSION::set_focused_admin_sidebar( SESSION::FOCUS_NOT ); } } } // 切り替え実行 switch( focused_admin ){ case SESSION::FOCUS_SIDEBAR: switch_sidebar( std::string(), present ); break; case SESSION::FOCUS_BOARD: switch_board( present ); break; case SESSION::FOCUS_ARTICLE: switch_article( present ); break; case SESSION::FOCUS_IMAGE: switch_image( present ); break; case SESSION::FOCUS_MESSAGE: switch_message( present ); break; } } // // ビューのトグルボタンを上げ下げする // void Core::set_toggle_view_button() { m_enable_menuslot = false; bool sidebar_state[ 7 ] = { false, false, false, false, false, false, false }; switch( SESSION::focused_admin() ){ case SESSION::FOCUS_SIDEBAR: sidebar_state[ SESSION::get_sidebar_current_page() ] = true; m_toolbar->m_button_bbslist.set_active( sidebar_state[ 0 ] ); m_toolbar->m_button_favorite.set_active( sidebar_state[ 1 ] ); m_toolbar->m_button_hist.set_active( sidebar_state[ 2 ] ); m_toolbar->m_button_hist_board.set_active( sidebar_state[ 3 ] ); m_toolbar->m_button_hist_close.set_active( sidebar_state[ 4 ] ); m_toolbar->m_button_hist_closeboard.set_active( sidebar_state[ 5 ] ); m_toolbar->m_button_hist_closeimg.set_active( sidebar_state[ 6 ] ); m_toolbar->m_button_board.set_active( false ); m_toolbar->m_button_thread.set_active( false ); m_toolbar->m_button_image.set_active( false ); break; case SESSION::FOCUS_BOARD: if( ! BOARD::get_admin()->empty() ){ m_toolbar->m_button_bbslist.set_active( sidebar_state[ 0 ] ); m_toolbar->m_button_favorite.set_active( sidebar_state[ 1 ] ); m_toolbar->m_button_hist.set_active( sidebar_state[ 2 ] ); m_toolbar->m_button_hist_board.set_active( sidebar_state[ 3 ] ); m_toolbar->m_button_hist_close.set_active( sidebar_state[ 4 ] ); m_toolbar->m_button_hist_closeboard.set_active( sidebar_state[ 5 ] ); m_toolbar->m_button_hist_closeimg.set_active( sidebar_state[ 6 ] ); m_toolbar->m_button_board.set_active( true ); m_toolbar->m_button_thread.set_active( false ); m_toolbar->m_button_image.set_active( false ); } else m_toolbar->m_button_board.set_active( false ); break; case SESSION::FOCUS_ARTICLE: if( ! ARTICLE::get_admin()->empty() ){ m_toolbar->m_button_bbslist.set_active( sidebar_state[ 0 ] ); m_toolbar->m_button_favorite.set_active( sidebar_state[ 1 ] ); m_toolbar->m_button_hist.set_active( sidebar_state[ 2 ] ); m_toolbar->m_button_hist_board.set_active( sidebar_state[ 3 ] ); m_toolbar->m_button_hist_close.set_active( sidebar_state[ 4 ] ); m_toolbar->m_button_hist_closeboard.set_active( sidebar_state[ 5 ] ); m_toolbar->m_button_hist_closeimg.set_active( sidebar_state[ 6 ] ); m_toolbar->m_button_board.set_active( false ); m_toolbar->m_button_thread.set_active( true ); m_toolbar->m_button_image.set_active( false ); } else m_toolbar->m_button_thread.set_active( false ); break; case SESSION::FOCUS_IMAGE: if( ! IMAGE::get_admin()->empty() ){ m_toolbar->m_button_bbslist.set_active( sidebar_state[ 0 ] ); m_toolbar->m_button_favorite.set_active( sidebar_state[ 1 ] ); m_toolbar->m_button_hist.set_active( sidebar_state[ 2 ] ); m_toolbar->m_button_hist_board.set_active( sidebar_state[ 3 ] ); m_toolbar->m_button_hist_close.set_active( sidebar_state[ 4 ] ); m_toolbar->m_button_hist_closeboard.set_active( sidebar_state[ 5 ] ); m_toolbar->m_button_hist_closeimg.set_active( sidebar_state[ 6 ] ); m_toolbar->m_button_board.set_active( false ); m_toolbar->m_button_thread.set_active( false ); m_toolbar->m_button_image.set_active( true ); } else m_toolbar->m_button_image.set_active( false ); break; case SESSION::FOCUS_MESSAGE: m_toolbar->m_button_bbslist.set_active( sidebar_state[ 0 ] ); m_toolbar->m_button_favorite.set_active( sidebar_state[ 1 ] ); m_toolbar->m_button_hist.set_active( sidebar_state[ 2 ] ); m_toolbar->m_button_hist_board.set_active( sidebar_state[ 3 ] ); m_toolbar->m_button_hist_close.set_active( sidebar_state[ 4 ] ); m_toolbar->m_button_hist_closeboard.set_active( sidebar_state[ 5 ] ); m_toolbar->m_button_hist_closeimg.set_active( sidebar_state[ 6 ] ); m_toolbar->m_button_board.set_active( false ); if( SESSION::get_embedded_mes() ) m_toolbar->m_button_thread.set_active( true ); else m_toolbar->m_button_thread.set_active( false ); m_toolbar->m_button_image.set_active( false ); break; } m_enable_menuslot = true; } // // ビューのトグルボタンのsensitive状態を切り替える // void Core::set_sensitive_view_button() { m_enable_menuslot = false; bool emp_mes = ! ( SESSION::get_embedded_mes() && ! MESSAGE::get_admin()->empty() ); // スレ一覧ボタンの切り替え if( BOARD::get_admin()->empty() ) m_toolbar->m_button_board.set_sensitive( false ); else m_toolbar->m_button_board.set_sensitive( true ); // スレビューボタンの切り替え if( ! ARTICLE::get_admin()->empty() || ! emp_mes ) m_toolbar->m_button_thread.set_sensitive( true ); else m_toolbar->m_button_thread.set_sensitive( false ); // 画像ビューボタンの切り替え if( IMAGE::get_admin()->empty() ) m_toolbar->m_button_image.set_sensitive( false ); else m_toolbar->m_button_image.set_sensitive( true ); m_enable_menuslot = true; } // // 右ペーンの最大化表示切り替え // void Core::toggle_maximize_rightpane() { bool emp_board = BOARD::get_admin()->empty(); bool emp_article = ARTICLE::get_admin()->empty(); bool emp_img = ! ( SESSION::get_embedded_img() && ! IMAGE::get_admin()->empty() ); bool emp_mes = ! ( SESSION::get_embedded_mes() && ! MESSAGE::get_admin()->empty() ); // 埋め込みmessage if( SESSION::get_embedded_mes() ){ if( ! emp_article && emp_mes ) m_vpaned_message.get_ctrl().set_mode( SKELETON::PANE_MAX_PAGE1 ); else if( emp_article && ! emp_mes ) m_vpaned_message.get_ctrl().set_mode( SKELETON::PANE_MAX_PAGE2 ); } if( is_3pane() && CONFIG::get_expand_rpane() ){ // スレ一覧を最大化 if( ! emp_board && emp_article && emp_img && emp_mes ) get_rpctrl()->set_mode( SKELETON::PANE_MAX_PAGE1 ); // スレビューを最大化 else if( emp_board && ( ! emp_article || ! emp_img || ! emp_mes ) ) get_rpctrl()->set_mode( SKELETON::PANE_MAX_PAGE2 ); // 戻す else if( ! emp_board && ( ! emp_article || ! emp_img || ! emp_mes ) ) get_rpctrl()->set_mode( SKELETON::PANE_NORMAL ); } } // // 各viewにスイッチ // void Core::switch_article( const bool present ) { if( SESSION::is_booting() ) return; if( ! m_enable_menuslot ) return; #ifdef _DEBUG std::cout << "Core::switch_article present = " << present << std::endl; #endif const bool emp_mes = ! ( SESSION::get_embedded_mes() && ! MESSAGE::get_admin()->empty() ); if( ! ARTICLE::get_admin()->empty() ){ if( m_hpaned.get_ctrl().get_mode() == SKELETON::PANE_MAX_PAGE1 ) m_hpaned.get_ctrl().set_mode( SKELETON::PANE_NORMAL ); if( SESSION::focused_admin() != SESSION::FOCUS_ARTICLE ){ FOCUS_OUT_ALL(); ARTICLE::get_admin()->set_command( "delete_popup" ); set_right_current_page( SESSION::PAGE_ARTICLE ); } ARTICLE::get_admin()->set_command_immediately( "focus_current_view" ); SESSION::set_focused_admin( SESSION::FOCUS_ARTICLE ); SESSION::set_focused_admin_sidebar( SESSION::FOCUS_ARTICLE ); if( SESSION::get_embedded_img() ) SESSION::set_shown_win_img( false ); } // articleは空だが、埋め込みmessageが表示されている場合 else if( ! emp_mes ){ switch_message( present ); return; } set_sensitive_view_button(); set_toggle_view_button(); toggle_maximize_rightpane(); if( present ) m_win_main.present(); } void Core::switch_board( const bool present ) { if( SESSION::is_booting() ) return; if( ! m_enable_menuslot ) return; #ifdef _DEBUG std::cout << "Core::switch_board\n"; #endif if( ! BOARD::get_admin()->empty() ){ if( m_hpaned.get_ctrl().get_mode() == SKELETON::PANE_MAX_PAGE1 ) m_hpaned.get_ctrl().set_mode( SKELETON::PANE_NORMAL ); if( SESSION::focused_admin() != SESSION::FOCUS_BOARD ){ FOCUS_OUT_ALL(); ARTICLE::get_admin()->set_command( "delete_popup" ); set_right_current_page( SESSION::PAGE_BOARD ); } BOARD::get_admin()->set_command_immediately( "focus_current_view" ); SESSION::set_focused_admin( SESSION::FOCUS_BOARD ); SESSION::set_focused_admin_sidebar( SESSION::FOCUS_BOARD ); // 3paneの時はboardに切り替えても(フォーカスアウトしても) // 画像は表示されたままの時があることに注意 if( SESSION::get_embedded_img() && SESSION::get_mode_pane() == SESSION::MODE_2PANE ) SESSION::set_shown_win_img( false ); } set_sensitive_view_button(); set_toggle_view_button(); toggle_maximize_rightpane(); if( present ) m_win_main.present(); } // // url は表示するページ( URL_BBSLISTVIEW or URL_FAVORITEVIEW ) // urlが空の時はフォーカスを移すだけ // present が true の時はメインウィンドウを前面に出す // void Core::switch_sidebar( const std::string& url, const bool present ) { if( SESSION::is_booting() ) return; if( ! m_enable_menuslot ) return; #ifdef _DEBUG std::cout << "Core::switch_sidebar url = " << url << " current = " << SESSION::get_sidebar_current_url() << std::endl; #endif if( ! BBSLIST::get_admin()->empty() ){ if( SESSION::focused_admin() != SESSION::FOCUS_SIDEBAR ){ FOCUS_OUT_ALL(); ARTICLE::get_admin()->set_command( "delete_popup" ); } // urlがフォーカスされていて、かつ他のadminがemptyで無いときは閉じる else if( SESSION::get_sidebar_current_url() == url && ! is_all_admin_empty() ){ toggle_sidebar(); return; } // 閉じていたら開く if( ! SESSION::show_sidebar() ) toggle_sidebar(); // 右ペーンがemptyなら最大化 else if( CONFIG::get_expand_sidebar() && is_all_admin_empty() ) m_hpaned.get_ctrl().set_mode( SKELETON::PANE_MAX_PAGE1 ); if( ! url.empty() ) BBSLIST::get_admin()->set_command( "switch_view", url ); BBSLIST::get_admin()->set_command_immediately( "focus_current_view" ); SESSION::set_focused_admin( SESSION::FOCUS_SIDEBAR ); } set_sensitive_view_button(); set_toggle_view_button(); toggle_maximize_rightpane(); if( present ) m_win_main.present(); } void Core::switch_image( const bool present ) { if( SESSION::is_booting() ) return; if( ! m_enable_menuslot ) return; #ifdef _DEBUG std::cout << "Core::switch_image\n"; #endif if( ! IMAGE::get_admin()->empty() ){ if( SESSION::get_embedded_img() ){ // 埋め込み画像ビュー if( m_hpaned.get_ctrl().get_mode() == SKELETON::PANE_MAX_PAGE1 ) m_hpaned.get_ctrl().set_mode( SKELETON::PANE_NORMAL ); if( SESSION::focused_admin() != SESSION::FOCUS_IMAGE ){ FOCUS_OUT_ALL(); ARTICLE::get_admin()->set_command( "delete_popup" ); set_right_current_page( SESSION::PAGE_IMAGE ); // 画像強制表示 IMAGE::get_admin()->set_command( "show_image" ); } SESSION::set_focused_admin( SESSION::FOCUS_IMAGE ); SESSION::set_focused_admin_sidebar( SESSION::FOCUS_IMAGE ); } IMAGE::get_admin()->set_command_immediately( "focus_current_view" ); if( SESSION::get_embedded_img() ) SESSION::set_shown_win_img( true ); } set_sensitive_view_button(); set_toggle_view_button(); toggle_maximize_rightpane(); if( present && SESSION::get_embedded_img() ) m_win_main.present(); } void Core::switch_message( const bool present ) { if( SESSION::is_booting() ) return; if( ! m_enable_menuslot ) return; #ifdef _DEBUG std::cout << "Core::switch_message\n"; #endif // 埋め込み書き込みビュー使用 const bool emb_mes = SESSION::get_embedded_mes(); if( ! MESSAGE::get_admin()->empty() ){ if( emb_mes ){ if( m_hpaned.get_ctrl().get_mode() == SKELETON::PANE_MAX_PAGE1 ) m_hpaned.get_ctrl().set_mode( SKELETON::PANE_NORMAL ); if( SESSION::focused_admin() != SESSION::FOCUS_MESSAGE ){ FOCUS_OUT_ALL(); ARTICLE::get_admin()->set_command( "delete_popup" ); set_right_current_page( SESSION::PAGE_ARTICLE ); } SESSION::set_focused_admin( SESSION::FOCUS_MESSAGE ); SESSION::set_focused_admin_sidebar( SESSION::FOCUS_MESSAGE ); } MESSAGE::get_admin()->set_command_immediately( "focus_current_view" ); } set_sensitive_view_button(); set_toggle_view_button(); toggle_maximize_rightpane(); if( present && emb_mes ) m_win_main.present(); } // 2paneの時にboard <-> article 切替え void Core::toggle_article() { const bool present = true; // 画像ウィンドウが表示されている場合 if( ! SESSION::get_embedded_img() && SESSION::is_shown_win_img() && SESSION::is_focus_win_img() ){ if( SESSION::focused_admin() == SESSION::FOCUS_ARTICLE ) switch_article( present ); else switch_board( present ); } else if( SESSION::focused_admin() == SESSION::FOCUS_ARTICLE ) switch_board( present ); else switch_article( present ); } // 左移動 void Core::switch_leftview() { const bool present = true; int next_admin = SESSION::focused_admin(); // 画像ウィンドウが表示されている場合 if( ! SESSION::get_embedded_img() && SESSION::is_shown_win_img() && SESSION::is_focus_win_img() ){ next_admin = SESSION::FOCUS_IMAGE; } for(;;){ if( next_admin == SESSION::FOCUS_IMAGE ) next_admin = SESSION::FOCUS_ARTICLE; else if( next_admin == SESSION::FOCUS_ARTICLE ) next_admin = SESSION::FOCUS_BOARD; else if( next_admin == SESSION::FOCUS_BOARD ) next_admin = SESSION::FOCUS_SIDEBAR; else break; if( next_admin == SESSION::FOCUS_SIDEBAR && ! BBSLIST::get_admin()->empty() ){ switch_sidebar( std::string(), present ); break; } else if( next_admin == SESSION::FOCUS_BOARD && ! BOARD::get_admin()->empty() ){ switch_board( present ); break; } else if( next_admin == SESSION::FOCUS_ARTICLE && ! ARTICLE::get_admin()->empty() ){ switch_article( present ); break; } } } // 右移動 void Core::switch_rightview() { const bool present = true; int next_admin = SESSION::focused_admin(); for(;;){ if( next_admin == SESSION::FOCUS_SIDEBAR ) next_admin = SESSION::FOCUS_BOARD; else if( next_admin == SESSION::FOCUS_BOARD ) next_admin = SESSION::FOCUS_ARTICLE; else if( next_admin == SESSION::FOCUS_ARTICLE ) next_admin = SESSION::FOCUS_IMAGE; else break; if( next_admin == SESSION::FOCUS_BOARD && ! BOARD::get_admin()->empty() ){ switch_board( present ); break; } else if( next_admin == SESSION::FOCUS_ARTICLE && ( ! ARTICLE::get_admin()->empty() || ( SESSION::get_embedded_mes() && ! MESSAGE::get_admin()->empty() ) ) ){ switch_article( present ); break; } else if( next_admin == SESSION::FOCUS_IMAGE && ! IMAGE::get_admin()->empty() ){ switch_image( present ); break; } } } // ブラウザで開く void Core::open_by_browser( const std::string& url ) { if( url.empty() ) return; std::string command_openurl = CONFIG::get_command_openurl(); if( !command_openurl.empty() ){ std::string tmp_url = url; size_t tmp_url_length = tmp_url.length(); // urlの先頭と最後のの " を取る while( *tmp_url.c_str() == '\"' ) tmp_url = tmp_url.substr( 1 ); while( tmp_url.c_str()[ tmp_url_length - 1 ] == '\"' ) { tmp_url = tmp_url.substr( 0, tmp_url_length - 1 ); --tmp_url_length; } command_openurl = MISC::replace_str( command_openurl, "%LINK", tmp_url ); command_openurl = MISC::replace_str( command_openurl, "%s", tmp_url ); #ifdef _DEBUG std::cout << "spawn url = " << tmp_url << " command = " << command_openurl << std::endl; #endif Glib::spawn_command_line_async( command_openurl ); } } // // 画像インジケータ表示 // void Core::show_imagetab() { if( SESSION::get_embedded_img() && ! m_imagetab_shown ){ // ツールバーの位置がサイドバーの右側の時はツールバーの下に挿入する int pos = 0; if( SESSION::get_show_main_toolbar() && SESSION::get_toolbar_pos() == SESSION::TOOLBAR_POS_RIGHT && SESSION::get_mode_pane() == SESSION::MODE_2PANE ) pos = 1; m_vbox_article.pack_start( IMAGE::get_admin()->tab(), Gtk::PACK_SHRINK ); m_vbox_article.reorder_child( IMAGE::get_admin()->tab(), pos ); m_win_main.show_all_children(); m_imagetab_shown = true; } } // // 画像インジケータを隠す // void Core::hide_imagetab() { if( m_imagetab_shown ){ m_vbox_article.remove( IMAGE::get_admin()->tab() ); m_win_main.show_all_children(); m_imagetab_shown = false; } } // // 板にdatファイルをインポートする // void Core::import_dat( const std::string& url_board, const std::vector< std::string >& list_files ) { if( ! list_files.size() ) return; const std::string boardbase = DBTREE::url_boardbase( url_board ); #ifdef _DEBUG std::cout << "Core::import_dat url = " << boardbase << std::endl; #endif CORE::DATA_INFO_LIST list_info; CORE::DATA_INFO info; info.type = TYPE_THREAD; for( const auto& filename : list_files ) { #ifdef _DEBUG std::cout << filename << std::endl; #endif std::string url = DBTREE::board_import_dat( boardbase, filename ); if( ! url.empty() ){ info.url = url; list_info.push_back( info ); } } if( list_info.size() ){ CORE::core_set_command( "open_board", boardbase, "true", "auto" ); CORE::SBUF_set_list( list_info ); BOARD::get_admin()->set_command( "draw_bg_articles", boardbase ); } } // サイドバー更新チェック // open : 更新があったらタブで開く void Core::check_update( const bool open ) { if( ! SESSION::is_online() ){ SKELETON::MsgDiag mdiag( nullptr, "オフラインです" ); mdiag.run(); return; } if( SESSION::get_sidebar_current_url() != URL_BBSLISTVIEW ){ // 閉じていたら開く if( ! SESSION::show_sidebar() ){ const int admin = SESSION::focused_admin(); toggle_sidebar(); switch( admin ){ case SESSION::FOCUS_BOARD: switch_board( false ); break; case SESSION::FOCUS_ARTICLE: switch_article( false ); break; } } if( ! open ) BBSLIST::get_admin()->set_command( "check_update_root", SESSION::get_sidebar_current_url() ); else BBSLIST::get_admin()->set_command( "check_update_open_root", SESSION::get_sidebar_current_url() ); } } jdim-0.7.0/src/core.h000066400000000000000000000225721417047150700143470ustar00rootroot00000000000000// ライセンス: GPL2 // // コアクラス // #ifndef _CORE_H #define _CORE_H #include "gtkmmversion.h" #include "skeleton/dispatchable.h" #include "skeleton/hpaned.h" #include "skeleton/vpaned.h" #include "skeleton/notebook.h" #include "skeleton/vbox.h" #include "command_args.h" #include #include #include #include class JDWinMain; namespace BOARD { class BoardAdmin; } namespace BBSLIST { class BBSListAdmin; } namespace ARTICLE { class ArticleAdmin; } namespace IMAGE { class ImageAdmin; } namespace JDLIB { class Timeout; } namespace MESSAGE { class MessageAdmin; } enum { IMGVIEW_WINDOW = 0, IMGVIEW_EMB, IMGVIEW_NO }; namespace CORE { class MainToolBar; class DND_Manager; class Core : public SKELETON::Dispatchable { std::list< COMMAND_ARGS > m_list_command; sigc::connection m_sigc_switch_page; JDWinMain& m_win_main; SKELETON::JDHPaned m_hpaned; // サイドバー Gtk::Widget* m_sidebar{}; // (縦/横) 3ペーンモード時の右側ペーン SKELETON::JDVPaned m_vpaned_r; SKELETON::JDHPaned m_hpaned_r; // 右ペーンで使用するwidget SKELETON::JDVBox m_vbox_article; SKELETON::JDVBox m_vbox_toolbar; SKELETON::JDNotebook m_notebook_right; bool m_imagetab_shown{}; SKELETON::JDVPaned m_vpaned_message; // 埋め込み書き込みビュー用 // ツールバー std::unique_ptr m_toolbar; // タイトルに表示する文字列 // set_maintitle() 参照 std::string m_title; Gtk::MenuBar* m_menubar{}; Gtk::MenuItem* m_menuitem_prevview{}; Gtk::MenuItem* m_menuitem_nextview{}; Glib::RefPtr< Gtk::ActionGroup > m_action_group; Glib::RefPtr< Gtk::UIManager > m_ui_manager; bool m_enable_menuslot; // 初期設定中 bool m_init{}; // セッション保存までのカウンタ int m_count_savesession{}; std::unique_ptr m_conn_timer; public: explicit Core( JDWinMain& win_main ); ~Core(); Gtk::Widget* get_toplevel(); // init = true なら初回起動 // skip_setupdiag = true なら初回起動時にセットアップダイアログ非表示 void run( const bool init, const bool skip_setupdiag ); void set_command( const COMMAND_ARGS& command ); // セッション保存 void save_session(); private: bool is_3pane() const; bool is_all_admin_empty() const; Gtk::Paned* get_rpane(); SKELETON::PaneControl* get_rpctrl(); void pack_widget( bool unpack ); void create_toolbar(); // 初回起動時のセットアップ void first_setup(); void set_maintitle(); void slot_activate_menubar(); void slot_activate_historymenu(); void slot_prevview(); void slot_nextview(); void slot_clear_board(); void slot_clear_thread(); void slot_clear_close(); void slot_clear_closeboard(); void slot_clear_closeimg(); void slot_clear_search(); void slot_clear_name(); void slot_clear_mail(); bool open_color_diag( std::string title, int id ); void toggle_menubar(); void toggle_statbar(); void toggle_flat_button(); void toggle_draw_toolbarback(); void toggle_post_mark(); void toggle_sidebar(); void slot_show_hide_leftpane( int mode ); void callback_dispatch() override; // coreが自前でするコマンド処理 void exec_command(); // 起動完了直後に実行する処理 void exec_command_after_boot(); // フォーカス回復 void restore_focus( const bool force, const bool present ); // メインタイマー bool slot_timeout( int timer_number ); // 右ペーンのnotebookのタブの切替え void slot_switch_page( Gtk::Widget*, guint page ); // 右ペーンのnotebookのページ番号 int get_right_current_page() const; // 右ペーンのnotebookのページをセット void set_right_current_page( int page ); bool slot_focus_out_event( GdkEventFocus* ev ); bool slot_focus_in_event( GdkEventFocus* ev ); // URL entryでenterを押した void slot_active_url(); // あるadminがemptyになったので他のadminにスイッチ void empty_page( const std::string& url ); void set_toggle_view_button(); void set_sensitive_view_button(); void toggle_maximize_rightpane(); void switch_article( const bool present ); void switch_board( const bool present ); void switch_sidebar( const std::string& url, const bool present ); void switch_image( const bool present ); void switch_message( const bool present ); void toggle_article(); void switch_leftview(); void switch_rightview(); void open_by_browser( const std::string& url ); // 画像インジケータ表示/非表示 void show_imagetab(); void hide_imagetab(); // 板にdatファイルをインポートする void import_dat( const std::string& url_board, const std::vector< std::string >& list_files ); // サイドバー更新チェック // open : 更新があったらタブで開く void check_update( const bool open ); // // メニュー関係( menuslots.cpp の中に記述) // void slot_openurl(); void slot_toggle_online(); void slot_toggle_login2ch(); void slot_toggle_loginbe(); void slot_reload_list(); void slot_quit(); /// void slot_toggle_since( const int mode ); void slot_toggle_write( const int mode ); void slot_toggle_access( const int mode ); void slot_toggle_toolbarmain(); void slot_toggle_toolbarpos( const int pos ); void slot_toggle_toolbarbbslist(); void slot_toggle_toolbarboard(); void slot_toggle_toolbararticle(); void slot_toggle_tabboard(); void slot_toggle_tabarticle(); void slot_toggle_2pane(); void slot_toggle_3pane(); void slot_toggle_v3pane(); void slot_toggle_fullscreen(); void slot_toggle_winmsg(); void slot_toggle_embmsg(); void slot_toggle_msg_wrap(); void slot_toggle_imgview( const int mode ); void slot_toggle_use_imgpopup(); void slot_toggle_use_inlineimg(); void slot_toggle_show_ssspicon(); void slot_setup_boarditem_column(); void slot_setup_mainitem(); void slot_setup_sidebaritem(); void slot_setup_boarditem(); void slot_setup_articleitem(); void slot_setup_searchitem(); void slot_setup_msgitem(); void slot_setup_boarditem_menu(); void slot_setup_articleitem_menu(); // void slot_bbslist_pref(); void slot_board_pref(); void slot_article_pref(); void slot_image_pref(); void slot_toggle_restore_views(); void slot_toggle_fold_message(); void slot_toggle_select_item_sync(); void slot_toggle_save_post_log(); void slot_toggle_save_post_history(); void slot_toggle_use_mosaic(); void slot_toggle_use_machi_offlaw(); void slot_toggle_tabbutton(); void slot_toggle_popupwarpmode(); void slot_shortmargin_popup(); void slot_toggle_emacsmode(); void slot_setup_mouse(); void slot_setup_key(); void slot_setup_button(); void slot_changefont_main(); void slot_changefont_mail(); void slot_changefont_popup(); void slot_changefont_tree(); void slot_changecolor_char(); void slot_changecolor_back(); void slot_changecolor_char_tree(); void slot_changecolor_back_tree(); void slot_setup_fontcolor(); void slot_setup_proxy(); void slot_setup_browser(); void slot_setup_passwd(); void slot_toggle_ipv6(); void slot_setup_abone(); void slot_setup_abone_thread(); void slot_toggle_abone_transp_chain(); void slot_toggle_abone_icase_wchar(); void slot_setup_live(); void slot_usrcmd_pref(); void slot_filter_pref(); void slot_replace_pref(); void slot_aboutconfig(); void slot_clear_privacy(); void slot_clear_post_log(); void slot_clear_post_history(); void slot_delete_all_images(); // void slot_live_start_stop(); void slot_search_cache_board(); void slot_search_cache(); void slot_show_cache_board(); void slot_show_cache(); void slot_search_title(); void slot_check_update_root(); void slot_check_update_open_root(); void slot_cancel_check_update(); void slot_edit_favorite(); void slot_show_postlog(); void slot_import_dat(); void slot_show_sidebarboard(); void slot_create_vboard(); // void slot_show_bbs(); void slot_show_old2ch(); void slot_show_manual(); void slot_show_about(); }; Core* get_instance(); } #endif jdim-0.7.0/src/cssmanager.cpp000066400000000000000000000523761417047150700161020ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "dbtree/node.h" #include "jdlib/miscutil.h" #include "jdlib/miscgtk.h" #include "jdlib/jdregex.h" #include "cssmanager.h" #include "colorid.h" #include "cache.h" #include enum { SIZE_OF_HEAP = 16 * 1024 }; CORE::Css_Manager* instance_css_manager = nullptr; CORE::Css_Manager* CORE::get_css_manager() { if( ! instance_css_manager ) instance_css_manager = new Css_Manager(); assert( instance_css_manager ); return instance_css_manager; } void CORE::delete_css_manager() { if( instance_css_manager ) delete instance_css_manager; instance_css_manager = nullptr; } /////////////////////////////////////////////// using namespace CORE; enum { SIZETYPE_PX = 0, SIZETYPE_EM }; Css_Manager::Css_Manager() : m_heap( SIZE_OF_HEAP ) { #ifdef _DEBUG std::cout << "Css_Manager::Css_Manager\n"; #endif set_default_css(); read_css(); if( ! read_html() ) set_default_html(); } void Css_Manager::clear_property( CSS_PROPERTY* css ) { memset( css, 0, sizeof( CSS_PROPERTY ) ); css->color = -1; css->bg_color = -1; css->border_left_color = -1; css->border_right_color = -1; css->border_top_color = -1; css->border_bottom_color = -1; css->align = ALIGN_LEFT; } // // ユーザ設定の色取得 // std::string Css_Manager::get_color( int colorid ) const { colorid -= USRCOLOR_BASE; return m_colors[ colorid ]; } // // クラス名からID取得 // int Css_Manager::get_classid( const std::string& classname ) const { int id = 0; std::list< std::string >::const_iterator it = m_css_class.begin(); for( ; it != m_css_class.end(); ++it, ++id ) if( ( *it ) == classname ) return id; return -1; } // // クラス名登録 // int Css_Manager::register_class( const std::string& classname ) { #ifdef _DEBUG std::cout << "Css_Manager::register_class name = " << classname << std::endl; #endif m_css_class.push_back( classname ); return get_classid( classname ); } // // デフォルトのcssをセット // void Css_Manager::set_default_css() { CSS_PROPERTY css; //////////////// // body clear_property( &css ); css.padding_bottom_em = 2; set_property( "body", css ); ///////////////// // res clear_property( &css ); css.mrg_left_em = 0.8; css.mrg_right_em = 0.8; css.mrg_bottom_em = 1; set_property( "res", css ); ///////////////// // separator clear_property( &css ); css.align = ALIGN_CENTER; css.color = COLOR_BACK; css.padding_top_px = 4; css.padding_bottom_px = 4; css.mrg_bottom_em = 1; set_property( "separator", css ); ///////////////// // comment clear_property( &css ); css.mrg_left_em = 0.8; css.mrg_right_em = 0.8; css.mrg_bottom_em = 1; set_property( "comment", css ); ///////////////// // title clear_property( &css ); set_property( "title", css ); ///////////////// // mes clear_property( &css ); css.padding_left_em = 1.5; set_property( "mes", css ); ///////////////// // imgpopup clear_property( &css ); css.border_left_width_px = 1; css.border_right_width_px = 1; css.border_top_width_px = 1; css.border_bottom_width_px = 1; set_property( "imgpopup", css ); } // // css 読み込み // bool Css_Manager::read_css() { const std::string css_file = CACHE::path_css(); #ifdef _DEBUG std::cout << "Css_Manager::read_css file = " << css_file << std::endl; #endif std::string css_data; if( ! CACHE::load_rawdata( css_file, css_data ) ) return false; #ifdef _DEBUG std::cout << "css : \n" << css_data << "--------------\n\n"; #endif // 改行を取り除く css_data = MISC::remove_str( css_data, "\n" ); // コメントを取り除く css_data = MISC::remove_str( css_data, "/*", "*/" ); size_t start_pos = 0, l_pos = 0, r_pos = 0; while( ( l_pos = css_data.find( '{', start_pos ) ) != std::string::npos && ( r_pos = css_data.find( '}', l_pos + 1 ) ) != std::string::npos ) { // セレクタ部分を取り出す std::string selector = MISC::remove_spaces( css_data.substr( start_pos, l_pos - start_pos ) ); start_pos = r_pos + 1; if( selector.rfind( '.', 0 ) == 0 ) selector.erase( 0, 1 ); if( selector.empty() ) break; // {中身}を取り出す std::string range = css_data.substr( l_pos + 1, r_pos - l_pos - 1 ); #ifdef _DEBUG std::cout << "selector = " << selector << std::endl; #endif // 中身を";"で分ける std::list< std::string > properties = MISC::StringTokenizer( range, ';' ); // プロパティペア(名前, 値)の作成 std::map< std::string, std::string > css_pair; std::list< std::string >::iterator it = properties.begin(); while( it != properties.end() ) { size_t colon = (*it).find( ':' ); std::string key = MISC::remove_spaces( (*it).substr( 0, colon ) ); std::string value = MISC::remove_spaces( (*it).substr( colon + 1 ) ); #ifdef _DEBUG std::cout << " key = " << key << " value = " << value << std::endl; #endif css_pair.insert( make_pair( key, value ) ); ++it; } // 各プロパティを作成 if( css_pair.size() ) set_property( selector, create_property( css_pair ) ); } return true; } // CSS_PROPERTY を作成 #define GET_PROPVAL() {\ std::string sizestr = value; \ size = atof( sizestr.c_str() ); \ type = SIZETYPE_PX; \ if( sizestr.find( "em" ) != std::string::npos ) type = SIZETYPE_EM; \ }while(0) CSS_PROPERTY Css_Manager::create_property( std::map< std::string, std::string >& css_pair ) { CSS_PROPERTY css; clear_property( &css ); JDLIB::Regex regex; const size_t offset = 0; const bool icase = true; // 大文字小文字区別しない const bool newline = true; const bool usemigemo = false; const bool wchar = false; for( const auto& pair : css_pair ) { const std::string& key = pair.first; const std::string& value = pair.second; // background-color if( key == "background-color" ) { #ifdef _DEBUG std::cout << "background-color = " << value << std::endl; #endif m_colors.push_back( MISC::htmlcolor_to_str( value ) ); css.bg_color = USRCOLOR_BASE + m_colors.size()-1; } // border-color else if( key == "border-color" ) { #ifdef _DEBUG std::cout << "border-color = " << value << std::endl; #endif m_colors.push_back( MISC::htmlcolor_to_str( value ) ); int colorid = USRCOLOR_BASE + m_colors.size()-1; css.border_left_color = colorid; css.border_right_color = colorid; css.border_top_color = colorid; css.border_bottom_color = colorid; } // border-*-color else if( regex.exec( "border-([a-z]+)-color", key, offset, icase, newline, usemigemo, wchar ) ) { std::string mode = regex.str( 1 ); #ifdef _DEBUG std::cout << "border-" << mode << "-color = " << value << std::endl; #endif m_colors.push_back( MISC::htmlcolor_to_str( value ) ); int colorid = USRCOLOR_BASE + m_colors.size()-1; if( mode == "left" ) css.border_left_color = colorid; else if( mode == "right" ) css.border_right_color = colorid; else if( mode == "top" ) css.border_top_color = colorid; else if( mode == "bottom" ) css.border_bottom_color = colorid; } // border-style else if( key == "border-style" ) { #ifdef _DEBUG std::cout << "border-style = " << value << std::endl; #endif if( value == "solid" ) css.border_style = BORDER_SOLID; } // border-width else if( key == "border-width" ) { int type; double size; GET_PROPVAL(); #ifdef _DEBUG std::cout << "border-width = " << size << std::endl; #endif if( type == SIZETYPE_EM ) { css.border_left_width_em = size; css.border_right_width_em = size; css.border_top_width_em = size; css.border_bottom_width_em = size; } else { css.border_left_width_px = (int)size; css.border_right_width_px = (int)size; css.border_top_width_px = (int)size; css.border_bottom_width_px = (int)size; } } // border-*-width else if( regex.exec( "border-([a-z]+)-width", key, offset, icase, newline, usemigemo, wchar ) ) { std::string mode = regex.str( 1 ); int type; double size; GET_PROPVAL(); #ifdef _DEBUG std::cout << "border-" << mode << "-width size = " << size << " type = " << type << std::endl; #endif if( mode == "left" ){ if( type == SIZETYPE_EM ) css.border_left_width_em = size; else css.border_left_width_px = (int)size; } else if( mode == "right" ){ if( type == SIZETYPE_EM ) css.border_right_width_em = size; else css.border_right_width_px = (int)size; } else if( mode == "top" ){ if( type == SIZETYPE_EM ) css.border_top_width_em = size; else css.border_top_width_px = (int)size; } else if( mode == "bottom" ){ if( type == SIZETYPE_EM ) css.border_bottom_width_em = size; else css.border_bottom_width_px = (int)size; } } // color else if( key == "color" ) { #ifdef _DEBUG std::cout << "color = " << value << std::endl; #endif m_colors.push_back( MISC::htmlcolor_to_str( value ) ); css.color = USRCOLOR_BASE + m_colors.size()-1; } // margin else if( key == "margin" ) { int type; double size; GET_PROPVAL(); #ifdef _DEBUG std::cout << "margin = " << size << std::endl; #endif if( type == SIZETYPE_EM ) { css.mrg_left_em = size; css.mrg_right_em = size; css.mrg_top_em = size; css.mrg_bottom_em = size; } else { css.mrg_left_px = (int)size; css.mrg_right_px = (int)size; css.mrg_top_px = (int)size; css.mrg_bottom_px = (int)size; } } // margin-* else if( regex.exec( "margin-([a-z]+)", key, offset, icase, newline, usemigemo, wchar ) ) { std::string mode = regex.str( 1 ); int type; double size; GET_PROPVAL(); #ifdef _DEBUG std::cout << "margin-" << mode << " size = " << size << " type = " << type << std::endl; #endif if( mode == "left" ){ if( type == SIZETYPE_EM ) css.mrg_left_em = size; else css.mrg_left_px = (int)size; } else if( mode == "right" ){ if( type == SIZETYPE_EM ) css.mrg_right_em = size; else css.mrg_right_px = (int)size; } else if( mode == "top" ){ if( type == SIZETYPE_EM ) css.mrg_top_em = size; else css.mrg_top_px = (int)size; } else if( mode == "bottom" ){ if( type == SIZETYPE_EM ) css.mrg_bottom_em = size; else css.mrg_bottom_px = (int)size; } } // padding else if( key == "padding" ) { int type; double size; GET_PROPVAL(); #ifdef _DEBUG std::cout << "padding = " << size << std::endl; #endif if( type == SIZETYPE_EM ) { css.padding_left_em = size; css.padding_right_em = size; css.padding_top_em = size; css.padding_bottom_em = size; } else { css.padding_left_px = (int)size; css.padding_right_px = (int)size; css.padding_top_px = (int)size; css.padding_bottom_px = (int)size; } } // padding-* else if( regex.exec( "padding-([a-z]+)", key, offset, icase, newline, usemigemo, wchar ) ) { std::string mode = regex.str( 1 ); int type; double size; GET_PROPVAL(); #ifdef _DEBUG std::cout << "padding-" << mode << " size = " << size << " type = " << type << std::endl; #endif if( mode == "left" ){ if( type == SIZETYPE_EM ) css.padding_left_em = size; else css.padding_left_px = (int)size; } else if( mode == "right" ){ if( type == SIZETYPE_EM ) css.padding_right_em = size; else css.padding_right_px = (int)size; } else if( mode == "top" ){ if( type == SIZETYPE_EM ) css.padding_top_em = size; else css.padding_top_px = (int)size; } else if( mode == "bottom" ){ if( type == SIZETYPE_EM ) css.padding_bottom_em = size; else css.padding_bottom_px = (int)size; } } // text-align else if( key == "text-align" ) { #ifdef _DEBUG std::cout << "text-align = " << value << std::endl; #endif if( value == "left" ) css.align = ALIGN_LEFT; else if( value == "center" ) css.align = ALIGN_CENTER; else if( value == "right" ) css.align = ALIGN_RIGHT; } } return css; } // // プロパティ取得 // CSS_PROPERTY Css_Manager::get_property( const int id ) { return m_css[ id ]; } // // プロパティをセット // void Css_Manager::set_property( const std::string& classname, const CSS_PROPERTY& css ) { if( classname.empty() ) return; #ifdef _DEBUG std::cout << "Css_Manager::set_property class = " << classname << std::endl; #endif int id = get_classid( classname ); if( id < 0 ) id = register_class( classname ); m_css.erase( id ); m_css.insert( std::pair< int, CSS_PROPERTY >( id, css ) ); } // // 文字の高さを与えてemをセット // void Css_Manager::set_size( CSS_PROPERTY* css, double height ) const { if( ! css ) return; if( ! height ) return; css->padding_left = 0; css->padding_right = 0; css->padding_top = 0; css->padding_bottom = 0; if( css->padding_left_px > 0 ) css->padding_left = css->padding_left_px; if( css->padding_right_px > 0 ) css->padding_right = css->padding_right_px; if( css->padding_top_px > 0 ) css->padding_top = css->padding_top_px; if( css->padding_bottom_px > 0 ) css->padding_bottom = css->padding_bottom_px; if( css->padding_left_em > 0 ) css->padding_left = (int)(height * css->padding_left_em); if( css->padding_right_em > 0 ) css->padding_right = (int)(height * css->padding_right_em); if( css->padding_top_em > 0 ) css->padding_top = (int)(height * css->padding_top_em); if( css->padding_bottom_em > 0 ) css->padding_bottom = (int)(height * css->padding_bottom_em); /////////////////////////////////////// css->mrg_left = 0; css->mrg_right = 0; css->mrg_top = 0; css->mrg_bottom = 0; if( css->mrg_left_px > 0 ) css->mrg_left = css->mrg_left_px; if( css->mrg_right_px > 0 ) css->mrg_right = css->mrg_right_px; if( css->mrg_top_px > 0 ) css->mrg_top = css->mrg_top_px; if( css->mrg_bottom_px > 0 ) css->mrg_bottom = css->mrg_bottom_px; if( css->mrg_left_em > 0 ) css->mrg_left = (int)(height * css->mrg_left_em); if( css->mrg_right_em > 0 ) css->mrg_right = (int)(height * css->mrg_right_em); if( css->mrg_top_em > 0 ) css->mrg_top = (int)(height * css->mrg_top_em); if( css->mrg_bottom_em > 0 ) css->mrg_bottom = (int)(height * css->mrg_bottom_em); /////////////////////////////////////// css->border_left_width = 0; css->border_right_width = 0; css->border_top_width = 0; css->border_bottom_width = 0; if( css->border_left_width_px > 0 ) css->border_left_width = css->border_left_width_px; if( css->border_right_width_px > 0 ) css->border_right_width = css->border_right_width_px; if( css->border_top_width_px > 0 ) css->border_top_width = css->border_top_width_px; if( css->border_bottom_width_px > 0 ) css->border_bottom_width = css->border_bottom_width_px; if( css->border_left_width_em > 0 ) css->border_left_width = (int)(height * css->border_left_width_em); if( css->border_right_width_em > 0 ) css->border_right_width = (int)(height * css->border_right_width_em); if( css->border_top_width_em > 0 ) css->border_top_width = (int)( height * css->border_top_width_em); if( css->border_bottom_width_em > 0 ) css->border_bottom_width = (int)(height * css->border_bottom_width_em); /////////////////////////////////////// css->padding_left += css->border_left_width; css->padding_right += css->border_right_width; css->padding_top += css->border_top_width; css->padding_bottom += css->border_bottom_width; } // // DOM作成 // DOM* Css_Manager::create_domnode( int type ) { DOM* tmpdom = m_heap.heap_alloc(); tmpdom->nodetype = type; tmpdom->attr = 0; if( m_last_dom ) m_last_dom->next_dom = tmpdom; else m_dom = tmpdom; m_last_dom = tmpdom; return tmpdom; } DOM* Css_Manager::create_divnode( const std::string& classname ) { int classid = get_classid( classname ); if( classid < 0 ) return nullptr; #ifdef _DEBUG std::cout << "Css_Manager::create_divnode name = " << classname << std::endl; #endif DOM* tmpdom = create_domnode( DOMNODE_DIV ); tmpdom->dat = classid; return tmpdom; } DOM* Css_Manager::create_blocknode( int blockid ) { DOM* tmpdom = create_domnode( DOMNODE_BLOCK ); tmpdom->dat = blockid; #ifdef _DEBUG std::cout << "Css_Manager::create_blocknode id = " << blockid << std::endl; #endif return tmpdom; } DOM* Css_Manager::create_textnode( const char* text ) { int lng = strlen( text ); if( ! lng ) return nullptr; #ifdef _DEBUG std::cout << "Css_Manager::create_textnode text = " << text << std::endl; #endif DOM* tmpdom = create_domnode( DOMNODE_TEXT ); tmpdom->chardat = m_heap.heap_alloc( lng + 1 ); strncpy( tmpdom->chardat, text, lng + 1 ); return tmpdom; } DOM* Css_Manager::create_imagenode() { #ifdef _DEBUG std::cout << "Css_Manager::create_imagenode\n"; #endif DOM* tmpdom = create_domnode( DOMNODE_IMAGE ); return tmpdom; } // // デフォルトのhtmlをセット // void Css_Manager::set_default_html() { create_divnode( "title" ); create_blocknode( DBTREE::BLOCK_NUMBER ); create_textnode( " " ); create_blocknode( DBTREE::BLOCK_NAMELINK ); create_textnode( ":" ); create_blocknode( DBTREE::BLOCK_NAME ); create_textnode( " " ); create_blocknode( DBTREE::BLOCK_MAIL ); create_textnode( ": " ); create_blocknode( DBTREE::BLOCK_DATE ); create_textnode( " " ); create_blocknode( DBTREE::BLOCK_ID_NAME ); create_divnode( "mes" ); create_blocknode( DBTREE::BLOCK_MES ); create_imagenode(); } // // html読み込み // bool Css_Manager::read_html() { const std::string htmlfile = CACHE::path_reshtml(); #ifdef _DEBUG std::cout << "Css_Manager::read_html file = " << htmlfile << std::endl; #endif std::string html; if( ! CACHE::load_rawdata( htmlfile, html ) ) return false; #ifdef _DEBUG std::cout << "html : \n" << html << "--------------\n\n"; #endif // 改行を消した後に '>' でhtmlを分ける std::list< std::string > blocks = MISC::StringTokenizer( MISC::remove_str( html, "\n" ), '>' ); JDLIB::Regex regex; const size_t offset = 0; const bool icase = true; // 大文字小文字区別しない const bool newline = true; const bool usemigemo = false; const bool wchar = false; for( std::string& block : blocks ) { #ifdef _DEBUG std::cout << "block = " << block << std::endl; #endif std::string::size_type pos = block.find( '<' ); if( pos == std::string::npos ){ create_textnode( block.c_str() ); continue; } else if( pos > 0 ){ create_textnode( block.substr( 0, pos ).c_str() ); block = block.substr( pos ); } block = MISC::remove_space( MISC::tolower_str( block.substr( 1 ) ) ); #ifdef _DEBUG std::cout << "tag = " << block << std::endl; #endif if( regex.exec( "div +class=\"([^\"]*)\"", block, offset, icase, newline, usemigemo, wchar ) ){ #ifdef _DEBUG std::cout << "name = " << regex.str( 1 ) << std::endl; #endif create_divnode( regex.str( 1 ) ); } else if( block.rfind( "number", 0 ) == 0 ) create_blocknode( DBTREE::BLOCK_NUMBER ); else if( block.rfind( "namelink", 0 ) == 0 ) create_blocknode( DBTREE::BLOCK_NAMELINK ); else if( block.rfind( "name", 0 ) == 0 ) create_blocknode( DBTREE::BLOCK_NAME ); else if( block.rfind( "mail", 0 ) == 0 ) create_blocknode( DBTREE::BLOCK_MAIL ); else if( block.rfind( "date", 0 ) == 0 ) create_blocknode( DBTREE::BLOCK_DATE ); else if( block.rfind( "id", 0 ) == 0 ) create_blocknode( DBTREE::BLOCK_ID_NAME ); else if( block.rfind( "message", 0 ) == 0 ){ DOM* dom = create_blocknode( DBTREE::BLOCK_MES ); if( block.find( " br=\"no\"" ) != std::string::npos ) dom->attr |= DOMATTR_NOBR; } else if( block.rfind( "image", 0 ) == 0 ) create_imagenode(); } return true; } jdim-0.7.0/src/cssmanager.h000066400000000000000000000101721417047150700155330ustar00rootroot00000000000000// ライセンス: GPL2 // // css 管理クラス // #ifndef _CSSMANAGER_H #define _CSSMANAGER_H #include #include #include #include #include "jdlib/heap.h" namespace CORE { // cssプロパティ struct CSS_PROPERTY { int align; // text-align int border_style; //////////////////// // 色 // >=0 のときは色を塗る int color; int bg_color; int border_left_color; int border_right_color; int border_top_color; int border_bottom_color; //////////////////// int padding_left; int padding_right; int padding_top; int padding_bottom; int padding_left_px; int padding_right_px; int padding_top_px; int padding_bottom_px; double padding_left_em; double padding_right_em; double padding_top_em; double padding_bottom_em; //////////////////// int mrg_left; int mrg_right; int mrg_top; int mrg_bottom; int mrg_left_px; int mrg_right_px; int mrg_top_px; int mrg_bottom_px; double mrg_left_em; double mrg_right_em; double mrg_top_em; double mrg_bottom_em; //////////////////// int border_left_width; int border_right_width; int border_top_width; int border_bottom_width; int border_left_width_px; int border_right_width_px; int border_top_width_px; int border_bottom_width_px; double border_left_width_em; double border_right_width_em; double border_top_width_em; double border_bottom_width_em; }; // text-align enum{ ALIGN_LEFT = 0, ALIGN_CENTER, ALIGN_RIGHT }; // border-style enum{ BORDER_NONE = 0, BORDER_SOLID }; ///////////////////////////////////////// // DOM構造 struct DOM { int nodetype; int dat; char* chardat; int attr; DOM* next_dom; }; // nodetype enum{ DOMNODE_DIV, DOMNODE_BLOCK, DOMNODE_TEXT, DOMNODE_IMAGE, }; // attribute flags enum{ DOMATTR_NOBR = 1, }; ///////////////////////////////////////// class Css_Manager { JDLIB::HEAP m_heap; std::map< int, CSS_PROPERTY > m_css; std::vector< std::string > m_colors; std::list< std::string > m_css_class; DOM* m_dom{}; DOM* m_last_dom{}; public: Css_Manager(); virtual ~Css_Manager() noexcept = default; // ユーザ設定の色取得 std::string get_color( int colorid ) const; // ユーザ設定の色( 先頭は黒 ) std::vector< std::string >& get_colors() { return m_colors; } // クラス名からID取得 int get_classid( const std::string& classname ) const; // プロパティ取得 CSS_PROPERTY get_property( const int id ); // 文字の高さを与えてemをセット void set_size( CSS_PROPERTY* css, double height ) const; // DOM 取得 const DOM* get_dom() const { return m_dom; } private: // クラス名登録 int register_class( const std::string& classname ); // cssプロパティ関係 CSS_PROPERTY create_property( std::map< std::string, std::string >& css_pair ); void set_property( const std::string& classname, const CSS_PROPERTY& css ); void clear_property( CSS_PROPERTY* css ); void set_default_css(); bool read_css(); // DOM関係 DOM* create_domnode( int type ); DOM* create_divnode( const std::string& classname ); DOM* create_blocknode( int blockid ); DOM* create_textnode( const char* text ); DOM* create_imagenode(); void set_default_html(); bool read_html(); }; /////////////////////////////////////// // インターフェース Css_Manager* get_css_manager(); void delete_css_manager(); } #endif jdim-0.7.0/src/data_info.h000066400000000000000000000013001417047150700153250ustar00rootroot00000000000000// データクラス // 関数の引数で使ったり、共有バッファ(sharedbuffer.h)と組み合わて使う #ifndef _DATA_INFO_H #define _DATA_INFO_H #include #include namespace Gtk { class Window; } namespace CORE { class DATA_INFO { public: int type{}; // type.h で定義されているタイプ Gtk::Window* parent{}; std::string url; std::string name; std::string path; // treeview の path std::string data; size_t dirid{}; // ディレクトリID、ディレクトリで無い場合は0 bool expanded{}; }; typedef std::vector< CORE::DATA_INFO > DATA_INFO_LIST; } #endif jdim-0.7.0/src/dbimg/000077500000000000000000000000001417047150700143205ustar00rootroot00000000000000jdim-0.7.0/src/dbimg/Makefile.am000066400000000000000000000004301417047150700163510ustar00rootroot00000000000000noinst_LIBRARIES = libdbimg.a libdbimg_a_SOURCES = \ imgroot.cpp \ img.cpp \ imginterface.cpp \ delimgcachediag.cpp noinst_HEADERS = \ imgroot.h \ img.h \ imginterface.h \ delimgdiag.h \ delimgcachediag.h AM_CXXFLAGS = @GTKMM_CFLAGS@ AM_CPPFLAGS = -I$(top_srcdir)/src jdim-0.7.0/src/dbimg/delimgcachediag.cpp000066400000000000000000000123111417047150700200740ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "delimgcachediag.h" #ifdef _DEBUG #include "jdlib/misctime.h" #endif #include "cache.h" #include "config/globalconf.h" #include "jdlib/miscutil.h" #include "jdlib/miscmsg.h" #include #include using namespace DBIMG; DelImgCacheDiag::DelImgCacheDiag() : m_label( "画像キャッシュ削除中・・・\n\nしばらくお待ち下さい" ) , m_stop{ true } { add_button( g_dgettext( GTK_DOMAIN, "_Cancel" ), Gtk::RESPONSE_CANCEL ) ->signal_clicked().connect( sigc::mem_fun(*this, &DelImgCacheDiag::slot_cancel_clicked ) ); set_title( "JDim 画像キャッシュ削除中" ); const int mrg = 8; get_content_area()->set_spacing( mrg ); set_border_width( mrg ); get_content_area()->pack_start( m_label, Gtk::PACK_SHRINK ); show_all_children(); } DelImgCacheDiag::~DelImgCacheDiag() { #ifdef _DEBUG std::cout << "DelImgCacheDiag::~DelImgCacheDiag\n"; #endif slot_cancel_clicked(); } bool DelImgCacheDiag::on_draw( const Cairo::RefPtr< Cairo::Context >& cr ) { #ifdef _DEBUG std::cout << "DelImgCacheDiag::on_draw\n"; #endif launch_thread(); return Gtk::Dialog::on_draw( cr ); } void DelImgCacheDiag::launch_thread() { // スレッドを起動してキャッシュ削除 if( !m_thread.is_running() ) { m_stop = false; if( !m_thread.create( static_cast< STARTFUNC >( launcher ), static_cast< void* >( this ), JDLIB::NODETACH ) ) { MISC::ERRMSG( "DelImgCacheDiag::launch_thread : could not start thread" ); } } } void* DelImgCacheDiag::launcher( void* dat ) { DelImgCacheDiag* tt = ( DelImgCacheDiag * ) dat; tt->main_thread(); return nullptr; } // 画像キャッシュ削除スレッド void DelImgCacheDiag::main_thread() { #ifdef _DEBUG std::cout << "DelImgCacheDiag::main_thread start\n"; #endif int days = 0; // info と 画像キャッシュ std::string path_info_root = CACHE::path_img_info_root(); std::list list_infofile = CACHE::get_filelist( path_info_root ); for( const std::string& infofile : list_infofile ) { if( m_stop ) break; const std::string path_info = path_info_root + infofile; // info ファイルの作成日時を調べて、設定した日時よりも古かったら消す if( CONFIG::get_del_img_day() > 0 ){ days = get_days( path_info ); if( days < CONFIG::get_del_img_day() ) continue; } // 画像ファイル名取得 std::string filename = MISC::get_filename( path_info ); std::string path_file = CACHE::path_img_root() + filename.substr( 0, filename.size() - 5 ); // ".info" を外す #ifdef _DEBUG std::cout << "\ninfo = " << path_info << std::endl; std::cout << "path = " << path_file << std::endl; std::cout << "days = " << days << std::endl; #endif // キャッシュ削除 if( CACHE::file_exists( path_file ) == CACHE::EXIST_FILE ) unlink( to_locale_cstr( path_file ) ); // info 削除 if( CACHE::file_exists( path_info ) == CACHE::EXIST_FILE ) unlink( to_locale_cstr( path_info ) ); } // あぼーん情報 #ifdef _DEBUG std::cout << "\ndelete abone info\n"; #endif path_info_root = CACHE::path_img_abone_root(); list_infofile = CACHE::get_filelist( path_info_root ); for( const std::string& infofile : list_infofile ) { if( m_stop ) break; const std::string path_info = path_info_root + infofile; // info ファイルの作成日時を調べて、設定した日時よりも古かったら消す if( CONFIG::get_del_imgabone_day() > 0 ){ days = get_days( path_info ); if( days < CONFIG::get_del_imgabone_day() ) continue; } #ifdef _DEBUG std::cout << "\nabone info = " << path_info << std::endl; std::cout << "days = " << days << std::endl; #endif // info 削除 if( CACHE::file_exists( path_info ) == CACHE::EXIST_FILE ) unlink( to_locale_cstr( path_info ) ); } #ifdef _DEBUG std::cout << "DelImgCacheDiag::main_thread end\n"; #endif // アクセシビリティをonにした時に別スレッドでhide()すると // フリーズするので、dispatchしてメインスレッドでhide()する dispatch(); } void DelImgCacheDiag::callback_dispatch() { #ifdef _DEBUG std::cout << "DelImgCacheDiag::callback_dispatch\n"; #endif hide(); } // // ファイルの作成日からの経過日数を計算 // // エラーの時ははマイナスの値が戻る // //static member int DelImgCacheDiag::get_days( const std::string& path ) { const std::time_t current = std::time( nullptr ); const std::time_t mtime = CACHE::get_filemtime( path ); if( ! mtime ) return -1; return static_cast( ( current - mtime ) / ( 60 * 60 * 24 ) ); } void DelImgCacheDiag::wait() { #ifdef _DEBUG std::cout << "DelImgCacheDiag::wait\n"; #endif m_thread.join(); } void DelImgCacheDiag::slot_cancel_clicked() { #ifdef _DEBUG std::cout << "DelImgCacheDiag::slot_cancel_clicked\n"; #endif m_stop = true; wait(); } jdim-0.7.0/src/dbimg/delimgcachediag.h000066400000000000000000000020211417047150700175360ustar00rootroot00000000000000// ライセンス: GPL2 // 画像キャッシュ削除ダイアログ // // キャンセルボタンを押すとキャッシュの削除を中止する #ifndef _DELIMGCACHEDIAG_H #define _DELIMGCACHEDIAG_H #include "jdlib/jdthread.h" #include "skeleton/dispatchable.h" #include #include namespace DBIMG { class DelImgCacheDiag : public Gtk::Dialog, SKELETON::Dispatchable { Gtk::Label m_label; bool m_stop; // = true にするとスレッド停止 JDLIB::Thread m_thread; public: DelImgCacheDiag(); ~DelImgCacheDiag(); // 画像キャッシュ削除スレッド void main_thread(); static void* launcher( void* dat ); protected: bool on_draw( const Cairo::RefPtr< Cairo::Context >& cr ) override; private: void callback_dispatch() override; void wait(); void slot_cancel_clicked(); static int get_days( const std::string& path ); void launch_thread(); }; } #endif jdim-0.7.0/src/dbimg/delimgdiag.h000066400000000000000000000066651417047150700165740ustar00rootroot00000000000000// ライセンス: GPL2 // 画像削除時の確認ダイアログ #ifndef _DELIMGDIAG_H #define _DELIMGDIAG_H #include "skeleton/prefdiag.h" #include "config/globalconf.h" #include "cache.h" #include namespace DBIMG { class ImgCacheFrame : public Gtk::Frame { Gtk::VBox m_vbox; Gtk::HBox m_hbox; Gtk::Label m_label; Gtk::Label m_spinlabel; Gtk::SpinButton m_spin; public: Gtk::SpinButton& get_spin(){ return m_spin; } ImgCacheFrame() { std::stringstream ss; ss << "現在の画像キャッシュサイズ : " << ( CACHE::get_dirsize( CACHE::path_img_root() ) / 1024 / 1024 ) << "M"; m_label.set_text( ss.str() ); m_spinlabel.set_text_with_mnemonic( "日より以前のキャッシュファイルを消去(_H)" ); m_spinlabel.set_mnemonic_widget( m_spin ); m_spin.set_range( 0, 360 ); m_spin.set_increments( 1, 1 ); m_spin.set_value( CONFIG::get_del_img_day() ); m_hbox.set_border_width( 8 ); m_hbox.set_spacing( 4 ); m_hbox.pack_start( m_spin, Gtk::PACK_SHRINK ); m_hbox.pack_start( m_spinlabel, Gtk::PACK_SHRINK ); m_vbox.set_spacing( 16 ); m_vbox.set_border_width( 8 ); m_vbox.pack_start( m_label, Gtk::PACK_SHRINK ); m_vbox.pack_start( m_hbox, Gtk::PACK_SHRINK ); set_border_width( 8 ); set_label( "画像キャッシュ" ); add( m_vbox ); } }; class ImgAboneFrame : public Gtk::Frame { Gtk::VBox m_vbox; Gtk::HBox m_hbox; Gtk::Label m_spinlabel; Gtk::SpinButton m_spin; public: Gtk::SpinButton& get_spin(){ return m_spin; } ImgAboneFrame() { m_spinlabel.set_text_with_mnemonic( "日より以前のあぼ〜ん情報を消去(_A)" ); m_spinlabel.set_mnemonic_widget( m_spin ); m_spin.set_range( 0, 360 ); m_spin.set_increments( 1, 1 ); m_spin.set_value( CONFIG::get_del_imgabone_day() ); m_hbox.set_border_width( 16 ); m_hbox.set_spacing( 4 ); m_hbox.pack_start( m_spin, Gtk::PACK_SHRINK ); m_hbox.pack_start( m_spinlabel, Gtk::PACK_SHRINK ); set_border_width( 8 ); set_label( "画像あぼ〜ん" ); add( m_hbox ); } }; class DelImgDiag : public SKELETON::PrefDiag { ImgCacheFrame m_frame_cache; ImgAboneFrame m_frame_abone; // OK押した void slot_ok_clicked() override { CONFIG::set_del_img_day( m_frame_cache.get_spin().get_value_as_int() ); CONFIG::set_del_imgabone_day( m_frame_abone.get_spin().get_value_as_int() ); } public: DelImgDiag( Gtk::Window* parent, const std::string& url ): SKELETON::PrefDiag( parent, url ) { get_content_area()->set_spacing( 8 ); get_content_area()->pack_start( m_frame_cache ); get_content_area()->pack_start( m_frame_abone ); set_activate_entry( m_frame_cache.get_spin() ); set_activate_entry( m_frame_abone.get_spin() ); set_title( "画像キャッシュの消去" ); show_all_children(); } ~DelImgDiag() noexcept = default; }; } #endif jdim-0.7.0/src/dbimg/img.cpp000066400000000000000000000567561417047150700156230ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "img.h" #include "imginterface.h" #include "jdlib/miscutil.h" #include "jdlib/miscmsg.h" #include "jdlib/imgloader.h" #include "jdlib/confloader.h" #include "jdlib/loaderdata.h" #include "config/globalconf.h" #include "command.h" #include "httpcode.h" #include "cache.h" #include "session.h" #include "global.h" #include "urlreplacemanager.h" #include #include #ifndef MAX #define MAX( a, b ) ( a > b ? a : b ) #endif #ifndef MIN #define MIN( a, b ) ( a < b ? a : b ) #endif enum { MAX_REDIRECT = 5 // 最大リダイレクト回数 }; static constexpr const std::size_t kSignatureSizeRequirement = 16; // 情報ファイルから値を読み込み // dbtree/articlebase.cpp からコピペ #define GET_INFOVALUE(target,targetname) \ do { \ target = std::string(); \ option = targetname; \ it2 = it; \ while( it2 != lines.end() && ( *it2 ).find( option ) != 0 ) ++it2; \ if( it2 != lines.end() ){ \ target = ( *it2 ).substr( option.length() ); \ it = ++it2; \ } } while( false ) using namespace DBIMG; Img::Img( const std::string& url ) : SKELETON::Loadable() , m_url( url ) { #ifdef _DEBUG std::cout << "Img::Img url = " << m_url << std::endl; #endif set_priority_low(); m_imgctrl = CORE::get_urlreplace_manager()->get_imgctrl( url ); reset(); } Img::~Img() { #ifdef _DEBUG std::cout << "Img::~Img url = " << m_url << std::endl; #endif if( m_fout ) fclose( m_fout ); m_fout = nullptr; } void Img::clock_in() { // ロード待ち if( m_wait ){ --m_wait_counter; if( m_wait_counter <= 0 ) download_img( m_refurl, m_mosaic, 0 ); } } // // リセット( 情報をクリアしてinfoファイル再読み込み ) // void Img::reset() { clear(); read_info(); } // 情報クリア void Img::clear() { m_protect = false; m_refurl = std::string(); clear_load_data(); // HTTPコードのクリア m_mosaic = CONFIG::get_use_mosaic(); m_zoom_to_fit = CONFIG::get_zoom_to_fit(); m_size = 100; m_type = T_UNKNOWN; m_width = 0; m_height = 0; m_width_emb = 0; m_height_emb = 0; m_width_mosaic = 0; m_height_mosaic = 0; m_abone = false; m_wait = false; m_wait_counter = 0; } // // ロード待ち状態セット/リセット // bool Img::set_wait( const std::string& refurl, const bool mosaic, const int waitsec ) { if( waitsec && ! m_wait ){ #ifdef _DEBUG std::cout << "Img::set_wait url = " << m_url << std::endl; #endif m_wait = true; m_wait_counter = (1000/TIMER_TIMEOUT) * waitsec; DBIMG::set_clock_in( this ); m_refurl = refurl; m_mosaic = mosaic; CORE::core_set_command( "redraw", m_url ); CORE::core_set_command( "redraw_article" ); CORE::core_set_command( "redraw_message" ); return true; } return false; } void Img::reset_wait() { if( m_wait ){ #ifdef _DEBUG std::cout << "Img::reset_wait url = " << m_url << std::endl; #endif m_wait = false; m_wait_counter = 0; DBIMG::reset_clock_in( this ); CORE::core_set_command( "redraw", m_url ); CORE::core_set_command( "redraw_article" ); CORE::core_set_command( "redraw_message" ); } } // スレ埋め込み画像の高さ、幅 int Img::get_width_emb() { if( ! m_width_emb ) set_embedded_size(); return m_width_emb; } int Img::get_height_emb() { if( ! m_height_emb ) set_embedded_size(); return m_height_emb; } // モザイク処理時に縮小するサイズ int Img::get_width_mosaic() { if( ! m_width_mosaic ) set_mosaic_size(); return m_width_mosaic; } int Img::get_height_mosaic() { if( ! m_height_mosaic ) set_mosaic_size(); return m_height_mosaic; } bool Img::is_cached() const { if( is_loading() ) return false; if( is_wait() ) return false; if( ! total_length() ) return false; return ( get_code() == HTTP_OK ); } // // あぼーん状態セット // // キャッシュに無くてもinfoを作るので is_cached() でチェックしない // void Img::set_abone( bool abone ) { if( m_abone == abone ) return; #ifdef _DEBUG std::cout << "Img::set_abone = " << abone << std::endl; #endif if( abone ) clear(); m_abone = abone; save_info(); } std::string Img::get_cache_path() const { if( m_protect ) return CACHE::path_img_protect( m_url ); return CACHE::path_img( m_url ); } // // ロード開始 // // receive_data() と receive_finish() がコールバックされる // // refurl : 参照元のスレのアドレス // mosaic : モザイク表示するか // waitsec: 指定した秒数経過後にロード開始 // void Img::download_img( const std::string& refurl, const bool mosaic, const int waitsec ) { // ダウンロード初回(リダイレクトでは無い) if( ! m_count_redirect ) m_url_alt = std::string(); #ifdef _DEBUG std::cout << "Img::download_img url = "; if( ! m_url_alt.empty() ) std::cout << m_url_alt << " count = " << m_count_redirect << std::endl; else std::cout << m_url << std::endl; std::cout << "refurl = " << refurl << " wait = " << m_wait << " waitsec = " << waitsec << std::endl; #endif if( is_loading() ) return; if( is_cached() ) return; if( ! CACHE::mkdir_imgroot() ) return; if( get_type() == T_LARGE ) return; if( is_wait() && waitsec ) return; // ロード待ち状態にセット if( set_wait( refurl, mosaic, waitsec ) ) return; // ロード待ち状態解除 reset_wait(); #ifdef _DEBUG std::cout << "start...\n"; #endif // ダウンロード開始 std::string path = get_cache_path(); m_fout = fopen( to_locale_cstr( path ), "wb" ); if( m_fout == nullptr ){ m_type = T_OPENFAILED; receive_finish(); return; } clear(); m_refurl = refurl; m_mosaic = mosaic; JDLIB::LOADERDATA data; data.init_for_data(); if( ! m_url_alt.empty() ) data.url = m_url_alt; else data.url = m_url; // Urlreplaceによるリファラ指定 std::string referer; if( CORE::get_urlreplace_manager()->referer( m_url, referer ) ) data.referer = referer; // 効率がよい画像形式を受け入れる(Accept)クライアントに対して // URLの拡張子と異なる画像形式でデータを送信するサイトがある // 拡張子の偽装をチェックしないURLなら画像の軽量化を利用する data.accept = "text/html,application/xhtml+xml,application/xml;q=0.9,"; if( m_imgctrl & CORE::IMGCTRL_GENUINE ) { if( DBIMG::is_avif_support() ) data.accept.append( "image/avif," ); if( DBIMG::is_webp_support() ) data.accept.append( "image/webp," ); } data.accept.append( "*/*;q=0.8" ); if( !start_load( data ) ) receive_finish(); else CORE::core_set_command( "redraw", m_url ); } // // ロード停止 // void Img::stop_load() { #ifdef _DEBUG std::cout << "Img::stop_load url = " << m_url << std::endl; #endif SKELETON::Loadable::stop_load(); if( m_wait ){ set_code( HTTP_TIMEOUT ); set_str_code( "stop loading" ); reset_wait(); } } // // キャッシュ保存 // // path_to はデフォルトのファイル名 // bool Img::save( Gtk::Window* parent, const std::string& path_to ) { if( ! is_cached() ) return false; std::string dir = MISC::get_dir( path_to ); if( dir.empty() ) dir = SESSION::dir_img_save(); std::string name = MISC::get_filename( path_to ); if( name.empty() ) name = MISC::get_filename( m_url ); std::string save_to = CACHE::copy_file( parent, get_cache_path(), dir + name, CACHE::FILE_TYPE_ALL ); if( ! save_to.empty() ){ SESSION::set_dir_img_save( MISC::get_dir( save_to ) ); return true; } return false; } // // モザイクon/off // void Img::set_mosaic( const bool mosaic ) { if( ! is_cached() ) return; m_mosaic = mosaic; save_info(); // 再描画 CORE::core_set_command( "redraw_bbslist" ); CORE::core_set_command( "redraw_article" ); CORE::core_set_command( "redraw_message" ); } // // サイズの大きいファイルを表示 // void Img::show_large_img() { if( m_type != T_LARGE ) return; const int size = 256; char data[ size ]; const std::size_t read_size = CACHE::load_rawdata( get_cache_path(), data, size ); if( read_size >= kSignatureSizeRequirement ) { m_type = DBIMG::get_image_type( reinterpret_cast( data ) ); } if( m_type != T_NOIMG ){ set_code( HTTP_OK ); set_str_code( "" ); CORE::core_set_command( "redraw", m_url ); CORE::core_set_command( "redraw_article" ); CORE::core_set_command( "redraw_message" ); } } // // 保護モード // void Img::set_protect( bool protect ) { if( ! is_cached() ) return; if( m_protect == protect ) return; if( protect ){ CACHE::jdmv( CACHE::path_img( m_url ), CACHE::path_img_protect( m_url ) ); CACHE::jdmv( CACHE::path_img_info( m_url ), CACHE::path_img_protect_info( m_url ) ); } else{ CACHE::jdmv( CACHE::path_img_protect( m_url ), CACHE::path_img( m_url ) ); CACHE::jdmv( CACHE::path_img_protect_info( m_url ), CACHE::path_img_info( m_url ) ); } m_protect = protect; } // 拡張子が偽装されているか bool Img::is_fake() const { if( ! is_cached() ) return false; if( m_imgctrl & CORE::IMGCTRL_GENUINE ) return false; bool ret = false; if( DBIMG::get_type_ext( m_url ) != m_type ) ret = true; else if( ! m_url_alt.empty() && DBIMG::get_type_ext( m_url_alt ) != m_type ) ret = true; #ifdef _DEBUG std::cout << "Img::is_fake url = " << m_url << " ret = " << ret << std::endl; #endif return ret; } // // データ受信 // void Img::receive_data( const char* data, size_t size ) { if( ! size ) return; #ifdef _DEBUG std::cout << "Img::receive_data code = " << get_code() << std::endl << "size / total = " << current_length() << " / " << total_length() << std::endl; #endif // Created は OK にしておく if( get_code() == HTTP_CREATED ) set_code( HTTP_OK ); // 先頭のシグネチャを見て画像かどうかをチェック if( m_type == T_UNKNOWN && get_code() == HTTP_OK ){ if( size >= kSignatureSizeRequirement ) { m_type = DBIMG::get_image_type( reinterpret_cast( data ) ); } if( m_type == T_NOIMG ){ // リダイレクトしたら 404 を疑う // データに "404" "not" "found" という文字列が含まれていたら not found と仮定 if( ! m_url_alt.empty() ){ // std::stringにいきなりデータを入れるのはなんとなく恐いので strncasecmp() を使用 unsigned char notfound = 0; for( unsigned int i = 0; i < size; ++i ){ if( strncasecmp( data + i, "404", 3 ) == 0 ) notfound |= 1; if( strncasecmp( data + i, "not", 3 ) == 0 ) notfound |= 2; if( strncasecmp( data + i, "found", 5 ) == 0 ) notfound |= 4; } if( notfound == 7 ) m_type = T_NOT_FOUND; } stop_load(); #ifdef _DEBUG std::cout << "no image : size = " << size << std::endl; std::cout << data << std::endl; #endif } else if( m_type == T_NOT_SUPPORT ) { // GdkPixbufが未対応で表示できない画像なら読み込みを中止する stop_load(); } } if( m_fout && m_type != T_NOIMG && m_type != T_NOT_FOUND && m_type != T_NOT_SUPPORT ) { if( fwrite( data, 1, size, m_fout ) != size ){ m_type = T_WRITEFAILED; // 書き込み失敗 stop_load(); } } #ifdef _DEBUG std::cout << "type = " << m_type << std::endl; #endif } // // ロード終了 // void Img::receive_finish() { #ifdef _DEBUG std::cout << "Img::receive_finish code = " << get_code() << std::endl << "strcode = " << get_str_code() << std::endl << "total byte = " << total_length() << std::endl << "contenttype = " << get_contenttype() << std::endl << "cookies : " << std::endl; #endif if( m_fout ) fclose( m_fout ); m_fout = nullptr; // Created は OK にしておく if( get_code() == HTTP_CREATED ) set_code( HTTP_OK ); // データが無い if( get_code() == HTTP_OK && ! current_length() ) m_type = T_NODATA; // リダイレクト if( get_code() == HTTP_REDIRECT || get_code() == HTTP_MOVED_PERM ){ #ifdef _DEBUG std::cout << "301/302 redirect url = " << location() << std::endl; #endif // アドレスに "404", ".htm" が含まれていたら not found と仮定 std::string url_tmp = MISC::tolower_str( location() ); if( url_tmp.find( "404" ) != std::string::npos && url_tmp.find( ".htm" ) != std::string::npos ) m_type = T_NOT_FOUND; else if( ! location().empty() && m_count_redirect < MAX_REDIRECT ){ #ifdef _DEBUG std::cout << "exec redirect\n"; #endif ++m_count_redirect; m_url_alt = location(); download_img( m_refurl, m_mosaic, 0 ); return; } else m_type = T_NODATA; } m_count_redirect = 0; if( get_code() != HTTP_OK ) set_current_length( 0 ); // 画像サイズ取得 if( get_code() == HTTP_OK && current_length() ){ JDLIB::ImgLoader::get_loader( get_cache_path() )->get_size( m_width, m_height ); if( ! m_width || ! m_height ) m_type = T_NOSIZE; } ////////////////////////////////////////////////// // エラーメッセージのセット if( m_type == T_NOIMG ){ set_code( HTTP_ERR ); set_str_code( "画像ファイルではありません (" + get_contenttype() + ")" ); set_current_length( 0 ); } else if( m_type == T_NOT_FOUND ){ set_code( HTTP_NOT_FOUND ); set_str_code( "404 Not Found" ); set_current_length( 0 ); } else if( m_type == T_OPENFAILED ){ set_code( HTTP_ERR ); set_str_code( "ファイルのオープンに失敗しました" ); set_current_length( 0 ); } else if( m_type == T_WRITEFAILED ){ set_code( HTTP_ERR ); set_str_code( "ハードディスクへの書き込みに失敗しました" ); set_current_length( 0 ); } else if( m_type == T_NODATA ){ set_code( HTTP_ERR ); set_str_code( "サーバ上にファイルが存在しません" ); set_current_length( 0 ); } else if( m_type == T_NOSIZE ){ set_code( HTTP_ERR ); set_str_code( "画像サイズを取得出来ません" ); set_current_length( 0 ); } else if( m_type == T_NOT_SUPPORT ) { set_code( HTTP_ERR ); set_str_code( "拡張子が偽装されています…" + get_contenttype() + "の表示にはGdkPixbufローダーのインストールが必要です" ); set_current_length( 0 ); } else if( get_code() == HTTP_OK && m_type == T_UNKNOWN ){ set_code( HTTP_ERR ); set_str_code( "未知の画像形式です" ); set_current_length( 0 ); } // 画像やファイルサイズが大きい else if( current_length() > (size_t) CONFIG::get_max_img_size() * 1024 * 1024 || m_width * m_height > CONFIG::get_max_img_pixel() * 1000 * 1000 ){ m_type = T_LARGE; set_code( HTTP_ERR ); std::stringstream ss; ss << "サイズが大きすぎます ( " << ( total_length() / 1024 / 1024 ) << " M, " << m_width << " x " << m_height << " ) ※コンテキストメニューから表示可能"; set_str_code( ss.str() ); } set_total_length( current_length() ); // 読み込み失敗の場合はファイルを消しておく if( ! total_length() ){ std::string path = get_cache_path(); if( CACHE::file_exists( path ) == CACHE::EXIST_FILE ) unlink( to_locale_cstr( path ) ); #ifdef _DEBUG std::cout << "unlink cache\n"; #endif } #ifdef _DEBUG std::cout << "type = " << m_type << std::endl << "refurl = " << m_refurl << std::endl; #endif // 画像が小さい場合はモザイクを解除 if( m_width && m_height ){ const int minsize = MAX( 1, CONFIG::get_mosaic_size() ); if( m_width >= m_height && m_width < minsize ) m_mosaic = false; else if( m_width < m_height && m_height < minsize ) m_mosaic = false; } // 拡張子が偽装されている時はモザイク表示にする if( is_fake() ) m_mosaic = true; // 読み込み失敗の場合でもエラーメッセージを残すので info は保存する save_info(); CORE::core_set_command( "redraw", m_url ); CORE::core_set_command( "redraw_article" ); CORE::core_set_command( "redraw_message" ); } // 埋め込み画像のサイズを計算 void Img::set_embedded_size() { if( ! m_width || ! m_height ) return; // 縮小比率を計算してサイズ取得 double scale; double scale_w = ( double ) CONFIG::get_embimg_width() / m_width; double scale_h = ( double ) CONFIG::get_embimg_height() / m_height; scale = MIN( scale_w, scale_h ); if( scale < 1.0 ){ m_width_emb = (int)( m_width * scale ); m_height_emb = (int)( m_height * scale ); } else{ m_width_emb = m_width; m_height_emb = m_height; } #ifdef _DEBUG std::cout << "Img::set_embedded_size w = " << m_width_emb << " h = " << m_height_emb << std::endl; #endif } // モザイク処理時に縮小するサイズを経産 void Img::set_mosaic_size() { if( ! m_width || ! m_height ) return; // 一旦元画像の横幅を mossize ピクセルまで縮めてから描画サイズに戻す const int mossize = MAX( 1, CONFIG::get_mosaic_size() ); int moswidth = m_width; int mosheight = m_height; if( moswidth >= mosheight ){ const int dev = MAX( 1, moswidth / mossize ); mosheight /= dev; moswidth = mossize; } else{ const int dev = MAX( 1, mosheight / mossize ); mosheight = mossize; moswidth /= dev; } m_width_mosaic = MAX( 1, moswidth ); m_height_mosaic = MAX( 1, mosheight ); } // // キャッシュ情報読み込み // void Img::read_info() { #ifdef _DEBUG std::cout << "Img::read_info\n"; #endif std::string filename = CACHE::filename_img_info( m_url ); std::string path_info = CACHE::path_img_info_root() + filename; bool exist = false; m_abone = false; do{ // 通常 if( CACHE::file_exists( path_info ) == CACHE::EXIST_FILE ){ m_protect = false; exist = true; break; } // 保護の場合 path_info = CACHE::path_img_protect_info_root() + filename; if( CACHE::file_exists( path_info ) == CACHE::EXIST_FILE ){ m_protect = true; exist = true; break; } // あぼーんの場合 path_info = CACHE::path_img_abone_root() + filename; if( CACHE::file_exists( path_info ) == CACHE::EXIST_FILE ){ m_abone = true; exist = false; break; } path_info = std::string(); }while( 0 ); if( exist ){ /* JDLIB::ConfLoader cf( path_info, std::string() ); m_refurl = cf.get_option( "refurl", "" ); set_code( cf.get_option( "code", HTTP_INIT ) ); set_str_code( cf.get_option( "str_code", "" ) ); set_total_length( cf.get_option( "byte", 0 ) ); m_mosaic = cf.get_option( "mosaic", CONFIG::get_use_mosaic() ); m_type = cf.get_option( "type", T_UNKNOWN ); m_width = cf.get_option( "width", 0 ); m_height = cf.get_option( "height", 0 ); */ // TODO : JDLIB::ConfLoaderFast を作る std::string str_info, str_tmp; CACHE::load_rawdata( path_info, str_info ); std::list< std::string > lines = MISC::get_lines( str_info ); std::list < std::string >::iterator it = lines.begin(), it2; std::string option; // GET_INFOVALUE で使用 GET_INFOVALUE( m_refurl, "refurl = " ); set_code( HTTP_INIT ); GET_INFOVALUE( str_tmp, "code = " ); if( ! str_tmp.empty() ) set_code( atoi( str_tmp.c_str() ) ); GET_INFOVALUE( str_tmp, "str_code = " ); set_str_code( str_tmp ); set_total_length( 0 ); GET_INFOVALUE( str_tmp, "byte = " ); if( ! str_tmp.empty() ) set_total_length( atoi( str_tmp.c_str() ) ); m_mosaic = CONFIG::get_use_mosaic(); GET_INFOVALUE( str_tmp, "mosaic = " ); if( ! str_tmp.empty() ) m_mosaic = atoi( str_tmp.c_str() ); m_type = T_UNKNOWN; GET_INFOVALUE( str_tmp, "type = " ); if( ! str_tmp.empty() ) m_type = atoi( str_tmp.c_str() ); m_width = 0; GET_INFOVALUE( str_tmp, "width = " ); if( ! str_tmp.empty() ) m_width = atoi( str_tmp.c_str() ); m_height = 0; GET_INFOVALUE( str_tmp, "height = " ); if( ! str_tmp.empty() ) m_height = atoi( str_tmp.c_str() ); if( ! total_length() ){ set_total_length( CACHE::get_filesize( get_cache_path() ) ); if( total_length() ) save_info(); } set_current_length( total_length() ); } #ifdef _DEBUG std::cout << "path_info = " << path_info << std::endl; std::cout << "exist = " << exist << std::endl; std::cout << "protect = " << m_protect << std::endl; std::cout << "refurl = " << m_refurl << std::endl; std::cout << "code = " << get_code() << std::endl; std::cout << "str_code = " << get_str_code() << std::endl; std::cout << "byte = " << total_length() << std::endl; std::cout << "mosaic = " << m_mosaic << std::endl; std::cout << "type = " << m_type << std::endl; std::cout << "width = " << m_width << std::endl; std::cout << "height = " << m_height << std::endl; std::cout << "abone = " << m_abone << std::endl; #endif } // // 情報保存 // void Img::save_info() { if( is_loading() ) return; if( ! CACHE::mkdir_imgroot() ) return; if( ! CACHE::mkdir_imgroot_favorite() ) return; std::string path_info; if( ! is_cached() && ! m_abone ){ // あぼーん情報ファイルがあったら消しておく path_info = CACHE::path_img_abone( m_url ); if( CACHE::file_exists( path_info ) == CACHE::EXIST_FILE ){ #ifdef _DEBUG std::cout << "unlink " << path_info << std::endl; #endif unlink( to_locale_cstr( path_info ) ); } if( get_code() == HTTP_INIT ) return; } if( m_protect ) path_info = CACHE::path_img_protect_info( m_url ); else if( m_abone ) path_info = CACHE::path_img_abone( m_url ); else path_info = CACHE::path_img_info( m_url ); std::ostringstream oss; oss << "url = " << m_url << std::endl << "refurl = " << m_refurl << std::endl << "code = " << get_code() << std::endl << "str_code = " << get_str_code() << std::endl << "byte = " << total_length() << std::endl << "mosaic = " << m_mosaic << std::endl << "type = " << m_type << std::endl << "width = " << m_width << std::endl << "height = " << m_height << std::endl; #ifdef _DEBUG std::cout << "Img::save_info file = " << path_info << std::endl; std::cout << "protect = " << m_protect << std::endl; std::cout << oss.str() << std::endl; #endif CACHE::save_rawdata( path_info, oss.str() ); } jdim-0.7.0/src/dbimg/img.h000066400000000000000000000077311417047150700152550ustar00rootroot00000000000000// ライセンス: GPL2 // 画像データクラス #ifndef _IMG_H #define _IMG_H #include "skeleton/loadable.h" #include namespace Gtk { class Window; } namespace DBIMG { class Img : public SKELETON::Loadable { std::string m_url; std::string m_url_alt; // リダイレクトなどで使用するアドレス int m_count_redirect{}; // リダイレクト回数 int m_imgctrl; // 画像コントロール int m_type; // 画像タイプ // 幅、高さ int m_width; int m_height; // 埋め込み画像の幅、高さ int m_width_emb; int m_height_emb; // モザイク処理時に縮小するサイズ int m_width_mosaic; int m_height_mosaic; bool m_mosaic; // モザイクかける bool m_zoom_to_fit; // windowにサイズをあわせる int m_size; // 画像の大きさ(パーセントで) bool m_protect; // true ならキャッシュを保護する( delete_cache()で削除しない ) std::string m_refurl; // 参照元のスレのURL bool m_abone; // あぼーんされている // 読み込み待ち bool m_wait; int m_wait_counter; // 保存用ファイルハンドラ FILE* m_fout{}; public: explicit Img( const std::string& url ); ~Img(); void clock_in(); void reset(); void clear(); const std::string& url() const { return m_url; } std::string get_cache_path() const; bool is_wait() const { return m_wait; } int get_imgctrl() const { return m_imgctrl; } int get_type() const { return m_type; } void set_type( const int type ){ m_type = type; } // 高さ、幅 int get_width() const { return m_width; } int get_height() const { return m_height; } // スレ埋め込み画像の高さ、幅 int get_width_emb(); int get_height_emb(); // モザイク処理時に縮小するサイズ int get_width_mosaic(); int get_height_mosaic(); bool is_cached() const; bool get_abone() const { return m_abone; } void set_abone( bool abone ); bool get_mosaic() const { return m_mosaic; } void set_mosaic( const bool mosaic ); void show_large_img(); bool is_zoom_to_fit() const { return m_zoom_to_fit; } void set_zoom_to_fit( bool fit ) { m_zoom_to_fit = fit; } // 表示倍率 // ファイルサイズ(byte)は JDLIB::Loadable::total_length() で取得 int get_size() const { return m_size; } void set_size( int size ) { m_size = size; } const std::string& get_refurl() const { return m_refurl; } bool is_protected() const { return m_protect; } void set_protect( bool protect ); // 拡張子が偽装されているか bool is_fake() const; // ロード開始 // receive_data() と receive_finish() がコールバックされる // refurl : 参照元のスレのアドレス // mosaic : モザイク表示するか // waitsec: 指定した秒数経過後にロード開始 void download_img( const std::string& refurl, const bool mosaic, const int waitsec ); // ロード停止 void stop_load() override; bool save( Gtk::Window* parent, const std::string& path_to ); private: void receive_data( const char* data, size_t size ) override; void receive_finish() override; // ロード待ち状態セット/リセット bool set_wait( const std::string& refurl, const bool mosaic, const int waitsec ); void reset_wait(); // 埋め込み画像のサイズを計算 void set_embedded_size(); // モザイク処理時に縮小するサイズを経産 void set_mosaic_size(); void read_info(); void save_info(); }; } #endif jdim-0.7.0/src/dbimg/imginterface.cpp000066400000000000000000000152651417047150700174720ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "imginterface.h" #include "imgroot.h" #include "img.h" #include "cache.h" #include "global.h" // インスタンスは Core でひとつだけ作って、Coreのデストラクタでdeleteする DBIMG::ImgRoot *instance_dbimg_root = nullptr; void DBIMG::create_root() { if( ! instance_dbimg_root ) instance_dbimg_root = new DBIMG::ImgRoot(); } void DBIMG::delete_root() { if( instance_dbimg_root ) delete instance_dbimg_root; } bool DBIMG::is_webp_support() { if( instance_dbimg_root ) return instance_dbimg_root->is_webp_support(); return false; } bool DBIMG::is_avif_support() { if( instance_dbimg_root ) return instance_dbimg_root->is_avif_support(); return false; } void DBIMG::clock_in() { if( instance_dbimg_root ) instance_dbimg_root->clock_in(); } // 読み込み待ちのためクロックを回すImgクラスをセット/リセット void DBIMG::set_clock_in( Img* img ) { if( instance_dbimg_root ) instance_dbimg_root->set_clock_in( img ); } void DBIMG::reset_clock_in( Img* img ) { if( instance_dbimg_root ) instance_dbimg_root->reset_clock_in( img ); } int DBIMG::get_type_ext( const std::string& url ) { if( instance_dbimg_root ) return instance_dbimg_root->get_type_ext( url ); return T_UNKNOWN; } int DBIMG::get_image_type( const unsigned char *sign ) { if( instance_dbimg_root ) return instance_dbimg_root->get_image_type( sign ); return T_UNKNOWN; } int DBIMG::get_type_ext( const char* url, int n ) { if( instance_dbimg_root ) return instance_dbimg_root->get_type_ext( url, n ); return T_UNKNOWN; } int DBIMG::get_type_real( const std::string& url ) { int type = T_UNKNOWN; DBIMG::Img* img = DBIMG::get_img( url ); if( img ) type = img->get_type(); return type; } DBIMG::Img* DBIMG::get_img( const std::string& url ) { if( instance_dbimg_root ) return instance_dbimg_root->get_img( url ); return nullptr; } std::string DBIMG::get_cache_path( const std::string& url ) { DBIMG::Img* img = DBIMG::get_img( url ); if( img ) return img->get_cache_path(); return std::string(); } void DBIMG::download_img( const std::string& url, const std::string& refurl, const bool mosaic ) { DBIMG::Img* img = DBIMG::get_img( url ); if( img ) img->download_img( refurl, mosaic, 0 ); } void DBIMG::download_img_wait( const std::string& url, const std::string& refurl, const bool mosaic, const int first ) { DBIMG::Img* img = DBIMG::get_img( url ); if( img && instance_dbimg_root ){ int wait = 0; if( ! first || instance_dbimg_root->get_wait_size() ) wait = ( instance_dbimg_root->get_wait_size() + 1 ) * WAITLOADIMG_SEC; img->download_img( refurl, mosaic, wait ); } } void DBIMG::stop_load( const std::string& url ) { DBIMG::Img* img = DBIMG::get_img( url ); if( img ) img->stop_load(); } bool DBIMG::save( const std::string& url, Gtk::Window* parent, const std::string& path_to ) { DBIMG::Img* img = DBIMG::get_img( url ); if( img ) return img->save( parent, path_to ); return true; } void DBIMG::delete_cache( const std::string& url ) { if( instance_dbimg_root ) instance_dbimg_root->delete_cache( url ); } void DBIMG::delete_all_files() { if( instance_dbimg_root ) instance_dbimg_root->delete_all_files(); } int DBIMG::get_width( const std::string& url ) { DBIMG::Img* img = DBIMG::get_img( url ); if( img ) return img->get_width(); return 0; } int DBIMG::get_height( const std::string& url ) { DBIMG::Img* img = DBIMG::get_img( url ); if( img ) return img->get_height(); return 0; } bool DBIMG::is_cached( const std::string& url ) { DBIMG::Img* img = DBIMG::get_img( url ); if( img ) return img->is_cached(); return false; } bool DBIMG::get_abone( const std::string& url ) { DBIMG::Img* img = DBIMG::get_img( url ); if( img ) return img->get_abone(); return false; } void DBIMG::set_abone( const std::string& url, bool abone ) { DBIMG::Img* img = DBIMG::get_img( url ); if( img ) return img->set_abone( abone ); } bool DBIMG::is_loading( const std::string& url ) { DBIMG::Img* img = DBIMG::get_img( url ); if( img ) return img->is_loading(); return false; } bool DBIMG::is_wait( const std::string& url ) { DBIMG::Img* img = DBIMG::get_img( url ); if( img ) return img->is_wait(); return false; } int DBIMG::get_code( const std::string& url ) { DBIMG::Img* img = DBIMG::get_img( url ); if( img ) return img->get_code(); return 0; } std::string DBIMG::get_str_code( const std::string& url ) { DBIMG::Img* img = DBIMG::get_img( url ); if( img ) return img->get_str_code(); return std::string(); } bool DBIMG::get_mosaic( const std::string& url ) { DBIMG::Img* img = DBIMG::get_img( url ); if( img ) return img->get_mosaic(); return true; } void DBIMG::set_mosaic( const std::string& url, bool mosaic ) { DBIMG::Img* img = DBIMG::get_img( url ); if( img ) img->set_mosaic( mosaic ); } void DBIMG::show_large_img( const std::string& url ) { DBIMG::Img* img = DBIMG::get_img( url ); if( img ) img->show_large_img(); } bool DBIMG::is_zoom_to_fit( const std::string& url ) { DBIMG::Img* img = DBIMG::get_img( url ); if( img ) return img->is_zoom_to_fit(); return true; } void DBIMG::set_zoom_to_fit( const std::string& url, bool fit ) { DBIMG::Img* img = DBIMG::get_img( url ); if( img ) img->set_zoom_to_fit( fit ); } int DBIMG::get_size( const std::string& url ) { DBIMG::Img* img = DBIMG::get_img( url ); if( img ) return img->get_size(); return 100; } void DBIMG::set_size( const std::string& url, int size ) { DBIMG::Img* img = DBIMG::get_img( url ); if( img ) img->set_size( size ); } std::string DBIMG::get_refurl( const std::string& url ) { DBIMG::Img* img = DBIMG::get_img( url ); if( img ) return img->get_refurl(); return std::string(); } size_t DBIMG::byte( const std::string& url ) { DBIMG::Img* img = DBIMG::get_img( url ); if( img ) return img->current_length(); return 0; } size_t DBIMG::get_filesize( const std::string& url ) { DBIMG::Img* img = DBIMG::get_img( url ); if( img ) return img->total_length(); return 0; } bool DBIMG::is_protected( const std::string& url ) { DBIMG::Img* img = DBIMG::get_img( url ); if( img ) return img->is_protected(); return true; } bool DBIMG::is_fake( const std::string& url ) { DBIMG::Img* img = DBIMG::get_img( url ); if( img ) return img->is_fake(); return false; } void DBIMG::set_protect( const std::string& url, bool protect ) { DBIMG::Img* img = DBIMG::get_img( url ); if( img ) img->set_protect( protect ); } jdim-0.7.0/src/dbimg/imginterface.h000066400000000000000000000067311417047150700171350ustar00rootroot00000000000000// ライセンス: GPL2 // // 画像データベースへのインターフェース関数 // #ifndef _IMGINTERFACE_H #define _IMGINTERFACE_H #include namespace Gtk { class Window; } namespace DBIMG { // DBIMG::get_type_*() で取得する画像タイプ enum{ T_NOIMG = 0, T_JPG, T_PNG, T_GIF, T_BMP, T_WEBP, T_AVIF, T_LARGE, T_NOSIZE, T_OPENFAILED, T_WRITEFAILED, T_NOT_FOUND, T_NODATA, T_NOT_SUPPORT, // 読み込みに対応していない画像 T_UNKNOWN, // 画像ではない T_FORCEIMAGE, // 拡張子がなくても画像として扱う }; class Img; void create_root(); void delete_root(); bool is_webp_support(); bool is_avif_support(); void clock_in(); // 読み込み待ちのためクロックを回すImgクラスをセット/リセット void set_clock_in( Img* img ); void reset_clock_in( Img* img ); // 画像データの先頭のシグネチャを見て画像のタイプを取得 // 画像ではない場合は T_NOIMG を返す int get_image_type( const unsigned char *sign ); // 拡張子だけをみて画像の種類を判断 // キャッシュに無くても判断可能 // 画像ではない場合は T_UNKNOWN を返す int get_type_ext( const std::string& url ); int get_type_ext( const char* url, int n ); // 実際の画像ファイルの種類を判断 // 画像がキャッシュに無いときは判断不能( T_UNKNOWN を返す ) int get_type_real( const std::string& url ); DBIMG::Img* get_img( const std::string& url ); std::string get_cache_path( const std::string& url ); // ロード開始 // refurl : 参照元のスレのアドレス // mosaic : モザイク表示するか void download_img( const std::string& url, const std::string& refurl, const bool mosaic ); // 時間差ロード // first : 一番最初の画像か void download_img_wait( const std::string& url, const std::string& refurl, const bool mosaic, const int first ); void stop_load( const std::string& url ); bool save( const std::string& url, Gtk::Window* parent, const std::string& path_to ); void delete_cache( const std::string& url ); void delete_all_files(); int get_width( const std::string& url ); int get_height( const std::string& url ); bool is_cached( const std::string& url ); int get_type( const std::string& url ); bool get_abone( const std::string& url ); void set_abone( const std::string& url, bool abone ); bool is_loading( const std::string& url ); bool is_wait( const std::string& url ); int get_code( const std::string& url ); std::string get_str_code( const std::string& url ); bool get_mosaic( const std::string& url ); void set_mosaic( const std::string& url, bool mosaic ); void show_large_img( const std::string& url ); bool is_zoom_to_fit( const std::string& url ); void set_zoom_to_fit( const std::string& url, bool fit ); int get_size( const std::string& url ); void set_size( const std::string& url, int size ); std::string get_refurl( const std::string& url ); size_t byte( const std::string& url ); size_t get_filesize( const std::string& url ); bool is_protected( const std::string& url ); bool is_fake( const std::string& url ); void set_protect( const std::string& url, bool protect ); } #endif jdim-0.7.0/src/dbimg/imgroot.cpp000066400000000000000000000223341417047150700165100ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "imgroot.h" #include "img.h" #include "delimgcachediag.h" #include "imginterface.h" #include "jdlib/confloader.h" #ifdef _DEBUG #include "jdlib/misctime.h" #endif #include "cache.h" #include "command.h" #include "prefdiagfactory.h" #include "urlreplacemanager.h" #include #include using namespace DBIMG; ImgRoot::ImgRoot() { // GdkPixbufが直接サポートしていない画像フォーマットは利用できるか確認する for( const Gdk::PixbufFormat& fmt : Gdk::Pixbuf::get_formats() ) { const Glib::ustring name = fmt.get_name(); if( name == "webp" ) { m_webp_support = true; } else if( name == "avif" ) { m_avif_support = true; } } } ImgRoot::~ImgRoot() { for( auto& key_val : m_map_img ) { key_val.second->terminate_load(); key_val.second.reset(); // call unique_ptr::reset() } } void ImgRoot::clock_in() { if( m_list_wait.size() ){ for( Img* img : m_list_wait ) img->clock_in(); remove_clock_in(); } } // 読み込み待ちのためクロックを回すImgクラスをセット/リセット void ImgRoot::set_clock_in( Img* img ) { m_list_wait.push_back( img ); } void ImgRoot::reset_clock_in( Img* img ) { // リストに登録しておいて remove_clock_in()で消す m_list_delwait.push_back( img ); } void ImgRoot::remove_clock_in() { if( m_list_delwait.size() ){ #ifdef _DEBUG std::cout << "ImgRoot::remove_clock_in\n"; #endif for( Img* img : m_list_delwait ) { #ifdef _DEBUG std::cout << img->url() << std::endl; #endif m_list_wait.remove( img ); } m_list_delwait.clear(); } } // // Imgクラス取得 // データベースに無ければImgクラスを作る // Img* ImgRoot::get_img( const std::string& url ) { auto it = m_map_img.find( url ); if( it != m_map_img.end() ) return it->second.get(); // 無ければ作る auto uniq = std::make_unique( url ); Img* img = uniq.get(); m_map_img.emplace( url, std::move( uniq ) ); return img; } // // 画像データの先頭のシグネチャを見て画像のタイプを取得 // 画像ではない場合は T_NOIMG を返す // // また、拡張子が偽装されていると未対応の画像が読み込まれる場合がある // 読み込みが未対応の場合は T_NOT_SUPPORT を返す // int ImgRoot::get_image_type( const unsigned char *sign ) const { int type = T_NOIMG; // jpeg は FF D8 if( sign[ 0 ] == 0xFF && sign[ 1 ] == 0xD8 ) type = T_JPG; // png は 0x89 0x50 0x4e 0x47 0xd 0xa 0x1a 0xa else if( sign[ 0 ] == 0x89 && sign[ 1 ] == 0x50 && sign[ 2 ] == 0x4e && sign[ 3 ] == 0x47 && sign[ 4 ] == 0x0d && sign[ 5 ] == 0x0a && sign[ 6 ] == 0x1a && sign[ 7 ] == 0x0a ) type = T_PNG; // gif else if( sign[ 0 ] == 'G' && sign[ 1 ] == 'I' && sign[ 2 ] == 'F' ) type = T_GIF; // bitmap else if( sign[ 0 ] == 'B' && sign[ 1 ] == 'M' ) type = T_BMP; // webp else if( sign[ 0 ] == 'R' && sign[ 1 ] == 'I' && sign[ 2 ] == 'F' && sign[ 3 ] == 'F' && sign[ 8 ] == 'W' && sign[ 9 ] == 'E' && sign[ 10 ] == 'B' && sign[ 11 ] == 'P' ) type = m_webp_support ? T_WEBP : T_NOT_SUPPORT; // avif else if( sign[ 4 ] == 'f' && sign[ 5 ] == 't' && sign[ 6 ] == 'y' && sign[ 7 ] == 'p' && sign[ 8 ] == 'a' && sign[ 9 ] == 'v' && sign[ 10 ] == 'i' && sign[ 11 ] == 'f' ) type = m_avif_support ? T_AVIF : T_NOT_SUPPORT; return type; } // // 拡張子からタイプを取得 // 画像ではない場合は T_UNKNOWN を返す // int ImgRoot::get_type_ext( const std::string& url ) const { return get_type_ext( url.c_str(), url.length() ); } int ImgRoot::get_type_ext( const char* url, int n ) const { // Urlreplaceによる画像コントロールを取得する int imgctrl = CORE::get_urlreplace_manager()->get_imgctrl( url ); // URLに拡張子があっても画像として扱わない if( imgctrl & CORE::IMGCTRL_FORCEBROWSER ) return T_UNKNOWN; if( is_jpg( url, n ) ) return T_JPG; if( is_png( url, n ) ) return T_PNG; if( is_gif( url, n ) ) return T_GIF; if( is_bmp( url, n ) ) return T_BMP; if( m_webp_support && is_webp( url, n ) ) return T_WEBP; if( m_avif_support && is_avif( url, n ) ) return T_AVIF; // URLに拡張子がない場合でも画像として扱うか if( imgctrl & CORE::IMGCTRL_FORCEIMAGE ) return T_FORCEIMAGE; return T_UNKNOWN; } // 今のところ拡張子だけを見る bool ImgRoot::is_jpg( const char* url, int n ) { // .jpg if( *( url + n -4 ) == '.' && *( url + n -3 ) == 'j' && *( url + n -2 ) == 'p' && *( url + n -1 ) == 'g' ) return true; if( *( url + n -4 ) == '.' && *( url + n -3 ) == 'J' && *( url + n -2 ) == 'P' && *( url + n -1 ) == 'G' ) return true; // .jpeg if( *( url + n -5 ) == '.' && *( url + n -4 ) == 'j' && *( url + n -3 ) == 'p' && *( url + n -2 ) == 'e' && *( url + n -1 ) == 'g' ) return true; if( *( url + n -5 ) == '.' && *( url + n -4 ) == 'J' && *( url + n -3 ) == 'P' && *( url + n -2 ) == 'E' && *( url + n -1 ) == 'G' ) return true; return false; } bool ImgRoot::is_png( const char* url, int n ) { // .png if( *( url + n -4 ) == '.' && *( url + n -3 ) == 'p' && *( url + n -2 ) == 'n' && *( url + n -1 ) == 'g' ) return true; if( *( url + n -4 ) == '.' && *( url + n -3 ) == 'P' && *( url + n -2 ) == 'N' && *( url + n -1 ) == 'G' ) return true; return false; } bool ImgRoot::is_gif( const char* url, int n ) { // .gif if( *( url + n -4 ) == '.' && *( url + n -3 ) == 'g' && *( url + n -2 ) == 'i' && *( url + n -1 ) == 'f' ) return true; if( *( url + n -4 ) == '.' && *( url + n -3 ) == 'G' && *( url + n -2 ) == 'I' && *( url + n -1 ) == 'F' ) return true; return false; } bool ImgRoot::is_bmp( const char* url, int n ) { // .bmp if( *( url + n -4 ) == '.' && *( url + n -3 ) == 'b' && *( url + n -2 ) == 'm' && *( url + n -1 ) == 'p' ) return true; if( *( url + n -4 ) == '.' && *( url + n -3 ) == 'B' && *( url + n -2 ) == 'M' && *( url + n -1 ) == 'P' ) return true; return false; } bool ImgRoot::is_webp( const char* url, int n ) { // .webp if( *( url + n -5 ) == '.' && *( url + n -4 ) == 'w' && *( url + n -3 ) == 'e' && *( url + n -2 ) == 'b' && *( url + n -1 ) == 'p' ) return true; if( *( url + n -5 ) == '.' && *( url + n -4 ) == 'W' && *( url + n -3 ) == 'E' && *( url + n -2 ) == 'B' && *( url + n -1 ) == 'P' ) return true; return false; } bool ImgRoot::is_avif( const char* url, int n ) { // .avif if( *( url + n -5 ) == '.' && *( url + n -4 ) == 'a' && *( url + n -3 ) == 'v' && *( url + n -2 ) == 'i' && *( url + n -1 ) == 'f' ) return true; if( *( url + n -5 ) == '.' && *( url + n -4 ) == 'A' && *( url + n -3 ) == 'V' && *( url + n -2 ) == 'I' && *( url + n -1 ) == 'F' ) return true; return false; } // // キャッシュ削除 // void ImgRoot::delete_cache( const std::string& url ) { #ifdef _DEBUG std::cout << "ImgRoot::delete_cache url = " << url << std::endl; #endif Img* img = get_img( url ); if( img && img->is_protected() ) return; // キャッシュ削除 std::string path = CACHE::path_img( url ); if( CACHE::file_exists( path ) == CACHE::EXIST_FILE ) unlink( to_locale_cstr( path ) ); // info 削除 path = CACHE::path_img_info( url ); if( CACHE::file_exists( path ) == CACHE::EXIST_FILE ) unlink( to_locale_cstr( path ) ); // 再描画 if( img ) img->reset(); // call Img::reset() CORE::core_set_command( "close_image", url ); CORE::core_set_command( "redraw_article" ); CORE::core_set_command( "redraw_message" ); } // // 全キャッシュ削除 // // image/info フォルダにあるファイルを全て取得して // 期限切れのファイルを削除 // void ImgRoot::delete_all_files() { #ifdef _DEBUG std::cout << "ImgRoot::delete_all_files\n"; #endif auto pref = CORE::PrefDiagFactory( nullptr, CORE::PREFDIAG_DELIMG, "" ); int ret = pref->run(); if( ret != Gtk::RESPONSE_OK ) return; // 画像キャッシュ削除ダイアログ表示 DelImgCacheDiag deldiag; deldiag.run(); reset_imgs(); CORE::core_set_command( "close_nocached_image_views" ); CORE::core_set_command( "redraw_article" ); CORE::core_set_command( "redraw_message" ); } // // Img クラスの情報をリセット // void ImgRoot::reset_imgs() { for( auto& key_val : m_map_img ) { key_val.second->reset(); // call Img::reset() } } jdim-0.7.0/src/dbimg/imgroot.h000066400000000000000000000045631417047150700161610ustar00rootroot00000000000000// ライセンス: GPL2 // 画像データベースのルートクラス #ifndef _IMGROOT_H #define _IMGROOT_H #include #include #include #include namespace DBIMG { class Img; class ImgRoot { std::map> m_map_img; std::list< Img* > m_list_wait; // ロード待ち状態のImgクラス std::list< Img* > m_list_delwait; // ロード待ち状態のImgクラスを削除する時の一時変数 bool m_webp_support{}; // WebP の読み込みに対応しているか bool m_avif_support{}; // AVIF の読み込みに対応しているか public: ImgRoot(); ~ImgRoot(); bool is_webp_support() const noexcept { return m_webp_support; } bool is_avif_support() const noexcept { return m_avif_support; } void clock_in(); // ロード待ちのためクロックを回すImgクラスをセット/リセット void set_clock_in( Img* img ); void reset_clock_in( Img* img ); void remove_clock_in(); int get_wait_size() const noexcept { return m_list_wait.size(); } // Imgクラス取得(無ければ作成) Img* get_img( const std::string& url ); // 検索(無ければnullptr) Img* search_img( const std::string& url ) = delete; // 画像データの先頭のシグネチャを見て画像のタイプを取得 // 画像ではない場合は T_NOIMG を返す // 読み込みが未対応の場合は T_NOT_SUPPORT を返す int get_image_type( const unsigned char *sign ) const; // 拡張子から画像タイプを取得 // 画像ではない場合は T_UNKNOWN を返す int get_type_ext( const std::string& url ) const; int get_type_ext( const char* url, int n ) const; // キャッシュ削除 void delete_cache( const std::string& url ); // 全キャッシュ削除 void delete_all_files(); private: static bool is_jpg( const char* url, int n ); static bool is_png( const char* url, int n ); static bool is_gif( const char* url, int n ); static bool is_bmp( const char* url, int n ); static bool is_webp( const char* url, int n ); static bool is_avif( const char* url, int n ); void reset_imgs(); }; } #endif jdim-0.7.0/src/dbimg/meson.build000066400000000000000000000003361417047150700164640ustar00rootroot00000000000000sources = [ 'delimgcachediag.cpp', 'img.cpp', 'imginterface.cpp', 'imgroot.cpp', ] dbimg_lib = static_library( 'dbimg', sources, dependencies : gtkmm_dep, include_directories : include_directories('..'), ) jdim-0.7.0/src/dbtree/000077500000000000000000000000001417047150700145035ustar00rootroot00000000000000jdim-0.7.0/src/dbtree/Makefile.am000066400000000000000000000022521417047150700165400ustar00rootroot00000000000000noinst_LIBRARIES = libdbtree.a libdbtree_a_SOURCES = \ interface.cpp \ spchar_decoder.cpp \ root.cpp \ \ boardbase.cpp \ board2ch.cpp \ board2chcompati.cpp \ boardjbbs.cpp \ boardmachi.cpp \ boardlocal.cpp \ \ frontloader.cpp \ settingloader.cpp \ ruleloader.cpp \ boardfactory.cpp \ articlehash.cpp \ \ articlebase.cpp \ article2ch.cpp \ article2chcompati.cpp \ articlejbbs.cpp \ articlemachi.cpp \ articlelocal.cpp \ \ nodetreebase.cpp \ nodetree2ch.cpp \ nodetree2chcompati.cpp \ nodetreejbbs.cpp \ nodetreemachi.cpp \ nodetreelocal.cpp \ nodetreedummy.cpp noinst_HEADERS = \ interface.h \ spchar_decoder.h \ spchar_tbl.h \ etcboardinfo.h \ root.h \ \ boardbase.h \ board2ch.h \ board2chcompati.h \ boardlocal.h \ boardjbbs.h \ boardmachi.h \ \ frontloader.h \ settingloader.h \ ruleloader.h \ boardfactory.h \ articlehash.h \ \ articlebase.h \ article2ch.h \ article2chcompati.h \ articlelocal.h \ articlejbbs.h \ articlemachi.h \ \ node.h \ nodetreebase.h \ nodetree2ch.h \ nodetree2chcompati.h \ nodetreejbbs.h \ nodetreemachi.h \ nodetreelocal.h \ nodetreedummy.h AM_CXXFLAGS = @GTKMM_CFLAGS@ AM_CPPFLAGS = -I$(top_srcdir)/src jdim-0.7.0/src/dbtree/article2ch.cpp000066400000000000000000000045311417047150700172320ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "article2ch.h" #include "nodetree2ch.h" #include "interface.h" #include "jdlib/miscutil.h" #include "jdlib/misctime.h" #include "config/globalconf.h" #include "login2ch.h" #include using namespace DBTREE; Article2ch::Article2ch( const std::string& datbase, const std::string& id, bool cached ) : Article2chCompati( datbase, id, cached ) {} Article2ch::~Article2ch() noexcept = default; // 書き込みメッセージ変換 std::string Article2ch::create_write_message( const std::string& name, const std::string& mail, const std::string& msg ) { if( msg.empty() ) return std::string(); const std::string charset = DBTREE::board_charset( get_url() ); std::stringstream ss_post; ss_post << "FROM=" << MISC::charset_url_encode( name, charset ) << "&mail=" << MISC::charset_url_encode( mail, charset ) << "&MESSAGE=" << MISC::charset_url_encode( msg, charset ) << "&bbs=" << DBTREE::board_id( get_url() ) << "&key=" << get_key() << "&time=" << get_time_modified() << "&submit=" << MISC::charset_url_encode( "書き込む", charset ) // XXX: ブラウザの種類に関係なく含めて問題ないか? << "&oekaki_thread1="; // キーワード( hana=mogera や suka=pontan など ) const std::string keyword = DBTREE::board_keyword_for_write( get_url() ); if( ! keyword.empty() ) ss_post << "&" << keyword; // ログイン中 if( CORE::get_login2ch()->login_now() ){ std::string sid = CORE::get_login2ch()->get_sessionid(); ss_post << "&sid=" << MISC::url_encode( sid.c_str(), sid.length() ); } #ifdef _DEBUG std::cout << "Article2ch::create_write_message " << ss_post.str() << std::endl; #endif // 書き込みメッセージを作成したらキーワードはリセットする DBTREE::board_set_keyword_for_write( get_url(), std::string{} ); return ss_post.str(); } NodeTreeBase* Article2ch::create_nodetree() { return new NodeTree2ch( get_url(), get_org_url(), get_date_modified(), get_since_time() ); } // // dat落ちしたスレをロードするか // bool Article2ch::is_load_olddat() const { // 2chにログインしている場合 return CORE::get_login2ch()->login_now(); } jdim-0.7.0/src/dbtree/article2ch.h000066400000000000000000000014051417047150700166740ustar00rootroot00000000000000// ライセンス: GPL2 // // 2ch型スレ情報クラス // #ifndef _ARTICLE2ch_H #define _ARTICLE2ch_H #include "article2chcompati.h" namespace DBTREE { class Article2ch : public Article2chCompati { public: Article2ch( const std::string& datbase, const std::string& id, bool cached ); ~Article2ch() noexcept; // 書き込みメッセージ変換 std::string create_write_message( const std::string& name, const std::string& mail, const std::string& msg ) override; protected: // dat落ちしたスレをロードするか bool is_load_olddat() const override; private: NodeTreeBase* create_nodetree() override; }; } #endif jdim-0.7.0/src/dbtree/article2chcompati.cpp000066400000000000000000000050721417047150700206100ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "article2chcompati.h" #include "nodetree2chcompati.h" #include "interface.h" #include "jdlib/miscutil.h" #include "jdlib/misctime.h" #include "config/globalconf.h" #include using namespace DBTREE; Article2chCompati::Article2chCompati( const std::string& datbase, const std::string& _id, bool cached ) : ArticleBase( datbase, _id, cached ) { assert( ! get_id().empty() ); // key (idから拡張子を除いたもの)を取得 size_t i = get_id().rfind( '.' ); // 拡張子は取り除く if( i != std::string::npos ) set_key( get_id().substr( 0, i ) ); // key から since 計算 const char* ckey = get_key().c_str(); if( i != std::string::npos ) set_since_time( atol( ckey ) ); // スレッド924か if( ckey[ 0 ] == '9' && ckey[ 1 ] == '2' && ckey[ 2 ] == '4' ) set_is_924( true ); } Article2chCompati::~Article2chCompati() noexcept = default; // 書き込みメッセージ変換 std::string Article2chCompati::create_write_message( const std::string& name, const std::string& mail, const std::string& msg ) { if( msg.empty() ) return std::string(); std::string charset = DBTREE::board_charset( get_url() ); std::stringstream ss_post; ss_post.clear(); ss_post << "bbs=" << DBTREE::board_id( get_url() ) << "&key=" << get_key() << "&time=" << get_time_modified() << "&submit=" << MISC::charset_url_encode( "書き込む", charset ) << "&FROM=" << MISC::charset_url_encode( name, charset ) << "&mail=" << MISC::charset_url_encode( mail, charset ) << "&MESSAGE=" << MISC::charset_url_encode( msg, charset ); #ifdef _DEBUG std::cout << "Article2chCompati::create_write_message " << ss_post.str() << std::endl; #endif return ss_post.str(); } // // bbscgi のURL // // (例) "http://www.hoge2ch.net/test/bbs.cgi" // // std::string Article2chCompati::url_bbscgi() const { std::string cgibase = DBTREE::url_bbscgibase( get_url() ); if( ! cgibase.empty() ) cgibase.pop_back(); // 最後の '/' を除く return cgibase; } // // subbbscgi のURL // // (例) "http://www.hoge2ch.net/test/subbbs.cgi" // std::string Article2chCompati::url_subbbscgi() const { std::string cgibase = DBTREE::url_subbbscgibase( get_url() ); if( ! cgibase.empty() ) cgibase.pop_back(); // 最後の '/' を除く return cgibase; } NodeTreeBase* Article2chCompati::create_nodetree() { return new NodeTree2chCompati( get_url(), get_date_modified() ); } jdim-0.7.0/src/dbtree/article2chcompati.h000066400000000000000000000015071417047150700202540ustar00rootroot00000000000000// ライセンス: GPL2 // // 2ch互換型スレ情報クラス // #ifndef _ARTICLE2CHCOMPATI_H #define _ARTICLE2CHCOMPATI_H #include "articlebase.h" namespace DBTREE { class Article2chCompati : public ArticleBase { public: Article2chCompati( const std::string& datbase, const std::string& id, bool cached ); ~Article2chCompati() noexcept; // 書き込みメッセージ変換 std::string create_write_message( const std::string& name, const std::string& mail, const std::string& msg ) override; // bbscgi のURL std::string url_bbscgi() const override; // subbbscgi のURL std::string url_subbbscgi() const override; private: NodeTreeBase* create_nodetree() override; }; } #endif jdim-0.7.0/src/dbtree/articlebase.cpp000066400000000000000000002030101417047150700174610ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "articlebase.h" #include "nodetreebase.h" #include "interface.h" #include "skeleton/msgdiag.h" #include "jdlib/miscutil.h" #include "jdlib/misctime.h" #include "jdlib/miscmsg.h" #include "jdlib/jdregex.h" #include "jdlib/tfidf.h" #include "dbimg/imginterface.h" #include "config/globalconf.h" #include "httpcode.h" #include "command.h" #include "cache.h" #include "global.h" #include "login2ch.h" #include "session.h" #include "updatemanager.h" #include #include #include using namespace DBTREE; // 情報ファイルのパスをセット // デストラクタの中でCACHE::path_article_ext_info()などを呼ぶとabortするので // メンバ変数としてパスを取得しておく #define SET_INFOPATH() \ do{ \ m_path_article_info = CACHE::path_article_info( m_url, m_id ); \ m_path_article_ext_info = CACHE::path_article_ext_info( m_url, m_id ); \ } while( false ) // 情報ファイルから値を読み込み // JDLIB::ConfLoader では遅いので別に作成。オプションの順番に注意すること #define GET_INFOVALUE(target,targetname) \ do { \ target = std::string(); \ const int n = strlen( targetname ); \ it2 = it; \ while( it2 != lines.end() && ( ( *it2 ).c_str()[ 0 ] != targetname[ 0 ] || strncmp( ( *it2 ).c_str(), targetname, n ) != 0 ) ) ++it2; \ if( it2 != lines.end() ){ \ target = ( *it2 ).substr( n ); \ it = ++it2; \ } } while( false ) ArticleBase::ArticleBase( const std::string& datbase, const std::string& id, bool cached ) : SKELETON::Lockable() , m_id( id ) , m_code( HTTP_INIT ) , m_status( STATUS_UNKNOWN ) , m_abone_board( true ) , m_abone_global( true ) , m_cached( cached ) { // m_url にURLセット update_datbase( datbase ); // この段階では移転前の旧ホスト名は分からないのでとりあえず現在のホスト名をセットしておく // あとで BoardBase::url_dat()でURLを変換する時に旧ホスト名を教えてもらってinfoファイルに保存しておく。 // BoardBase::url_dat()も参照せよ m_org_host = MISC::get_hostname( m_url ); } ArticleBase::~ArticleBase() { #ifdef _DEBUG // std::cout << "ArticleBase::~ArticleBase : " << m_id << std::endl; #endif // 参照ロックが外れていない assert( get_lock() == 0 ); // nodetreeのクリアと情報保存 ArticleBase::unlock_impl(); } // ID がこのスレのものかどうか bool ArticleBase::equal( const std::string& datbase, const std::string& id ) const { return ( id == m_id ); } // // 移転する前のオリジナルのURL // std::string ArticleBase::get_org_url() const { std::string newhost = MISC::get_hostname( m_url ); return m_org_host + m_url.substr( newhost.length() ); } // スレ立て時刻( string型 ) const std::string& ArticleBase::get_since_date() { if( m_since_date.empty() || SESSION::get_col_since_time() == MISC::TIME_PASSED ){ m_since_date = MISC::timettostr( get_since_time(), SESSION::get_col_since_time() ); } return m_since_date; } // スレ速度 int ArticleBase::get_speed() const { std::time_t current = std::time( nullptr ); return ( static_cast( get_number() ) * 60 * 60 * 24 ) / MAX( 1, current - get_since_time() ); } // キャッシュにあるdatファイルのサイズ size_t ArticleBase::get_lng_dat() { return get_nodetree()->get_lng_dat(); } // // nodetree の number 番のレスのヘッダノードのポインタを返す // NODE* ArticleBase::res_header( int number ) { return get_nodetree()->res_header( number ); } // // number番の名前 // std::string ArticleBase::get_name( int number ) { return get_nodetree()->get_name( number ); } // // number番の名前の重複数( = 発言数 ) // int ArticleBase::get_num_name( int number ) { return get_nodetree()->get_num_name( number ); } // // 指定した発言者の名前のレス番号をリストにして取得 // std::list< int > ArticleBase::get_res_name( const std::string& name ) { return get_nodetree()->get_res_name( name ); } // // number番のレスの時刻を文字列で取得 // 内部で regex を使っているので遅い // std::string ArticleBase::get_time_str( int number ) { return get_nodetree()->get_time_str( number ); } // // number番の発言者ID // std::string ArticleBase::get_id_name( int number ) { return get_nodetree()->get_id_name( number ); } // 指定した発言者ID の重複数( = 発言数 ) // 下のnum_id_name( int number )と違って検索するので遅い int ArticleBase::get_num_id_name( const std::string& id ) { return get_nodetree()->get_num_id_name( id ); } // number番の発言者ID の重複数( = 発言数 ) int ArticleBase::get_num_id_name( int number ) { return get_nodetree()->get_num_id_name( number ); } // 指定した発言者IDを持つレス番号をリストにして取得 std::list< int > ArticleBase::get_res_id_name( const std::string& id_name ) { return get_nodetree()->get_res_id_name( id_name ); } // str_num で指定したレス番号をリストにして取得 // str_num は "from-to" の形式 (例) 3から10をセットしたいなら "3-10" // list_jointは出力で true のスレは前のスレに連結される (例) "3+4" なら 4が3に連結 std::list< int > ArticleBase::get_res_str_num( const std::string& str_num, std::list< bool >& list_joint ) { if( empty() ){ std::list< int > tmp; return tmp; } return get_nodetree()->get_res_str_num( str_num, list_joint ); } // ブックマークをつけたレス番号をリストにして取得 std::list< int > ArticleBase::get_res_bm() { std::list< int > list_resnum; for( int i = 1; i <= m_number_load ; ++i ){ if( is_bookmarked( i ) ) list_resnum.push_back( i ); } // あぼーんしていてもリストから取り除かない return list_resnum; } // 書き込みしたレス番号をリストにして取得 std::list< int > ArticleBase::get_res_posted() { std::list< int > list_resnum; for( int i = 1; i <= m_number_load ; ++i ){ if( is_posted( i ) ) list_resnum.push_back( i ); } // あぼーんしていてもリストから取り除かない return list_resnum; } // 高参照レスをリストにして取得 std::list< int > ArticleBase::get_highly_referenced_res() { return get_nodetree()->get_highly_referened_res(); } // // number番のレスを参照しているレス番号をリストにして取得 // std::list< int > ArticleBase::get_res_reference( const int number ) { return get_nodetree()->get_res_reference( number ); } // // res_num に含まれるレスを参照しているレス番号をリストにして取得 // std::list< int > ArticleBase::get_res_reference( const std::list< int >& res_num ) { return get_nodetree()->get_res_reference( res_num ); } // // URL を含むレス番号をリストにして取得 // std::list< int > ArticleBase::get_res_with_url() { if( empty() ){ std::list< int > tmp; return tmp; } return get_nodetree()->get_res_with_url(); } // // query を含むレス番号をリストにして取得 // // mode_or == true なら OR抽出 // std::list< int > ArticleBase::get_res_query( const std::string& query, const bool mode_or ) { if( empty() ){ std::list< int > tmp; return tmp; } return get_nodetree()->get_res_query( query, mode_or ); } // // number番のレスの文字列を返す // ref == true なら先頭に ">" を付ける // std::string ArticleBase::get_res_str( int number, bool ref ) { return get_nodetree()->get_res_str( number, ref ); } // // 更新時刻 // std::time_t ArticleBase::get_time_modified() const { std::time_t time_out = MISC::datetotime( m_date_modified ); if( time_out == 0 ) time_out = std::time( nullptr ) - 600; return time_out; } // スレが立ってからの経過時間( 時間 ) int ArticleBase::get_hour() const { return ( std::time( nullptr ) - get_since_time() ) / ( 60 * 60 ); } // // url の更新 // // 移転があったときなどに上位クラスのboardbaseから呼ばれる // void ArticleBase::update_datbase( const std::string& datbase ) { if( m_id.empty() ) return; #ifdef _DEBUG std::string old_url = m_url; #endif // URL 更新 m_datbase = datbase; m_url = datbase + m_id; // info ファイルのパスも更新 if( ! m_path_article_info.empty() ) SET_INFOPATH(); #ifdef _DEBUG if( !old_url.empty() ) std::cout << "ArticleBase::update_datbase from " << old_url << " to " << m_url << std::endl; #endif if( m_nodetree ) m_nodetree->update_url( m_url ); } // // 移転前のオリジナルのホスト名をセット // void ArticleBase::set_org_host( const std::string& host ) { if( host != m_org_host ){ #ifdef _DEBUG std::cout << "ArticleBase::set_org_host : " << m_id << std::endl << "m_url = " << m_url << std::endl << "org_host = " << m_org_host << " -> " << host << std::endl; #endif m_org_host = host; m_save_info = true; } } // // access_time を 文字列に変換して返す // std::string ArticleBase::get_access_time_str() const { struct timeval buf{}; buf.tv_sec = m_access_time; return MISC::timevaltostr( buf ); } // // ユーザが最後にロードした月日( string型 ) // const std::string& ArticleBase::get_access_date() { if( m_access_time ){ if( m_access_date.empty() || SESSION::get_col_access_time() == MISC::TIME_PASSED ){ m_access_date = MISC::timettostr( m_access_time, SESSION::get_col_access_time() ); } } return m_access_date; } void ArticleBase::reset_status() { #ifdef _DEBUG std::cout << "ArticleBase::reset_status\n"; #endif m_status = STATUS_UNKNOWN; } void ArticleBase::set_subject( const std::string& subject ) { if( subject.empty() ) return; // 特殊文字の置き換え if( subject.find( '&' ) != std::string::npos ){ std::string subject_tmp = MISC::html_unescape( subject ); if( subject_tmp != m_subject ){ m_subject = subject_tmp; m_save_info = true; } } else if( subject != m_subject ){ m_subject = subject; m_save_info = true; } } void ArticleBase::set_number( const int number, const bool is_online ) { if( ! number ) return; m_number_diff = 0; m_status &= ~STATUS_BROKEN_SUBJECT; if( number > m_number ){ m_number_diff = number - m_number; m_number = number; // キャッシュがあって更新可能になった場合は // お気に入りとスレビューのタブのアイコンに更新マークを表示 if( is_cached() && !( m_status & STATUS_UPDATE ) && m_number_load < m_number ) show_updateicon( true ); } // subject.txt に示されたレス数よりも実際の取得数の方が多い else if( is_online && number < m_number ){ #ifdef _DEBUG std::cout << "ArticleBase::set_number : broken_subject " << get_subject() << " " << number << " / " << m_number << std::endl; #endif m_status |= STATUS_BROKEN_SUBJECT; } } void ArticleBase::set_number_load( const int number_load ) { if( number_load && number_load != m_number_load ) m_number_load = number_load; } void ArticleBase::set_number_seen( const int number_seen ) { if( number_seen && number_seen != m_number_seen ){ m_number_seen = number_seen; m_save_info = true; } } // 最終書き込み時間( string型 ) const std::string& ArticleBase::get_write_date() { if( m_write_time ){ if( m_write_time_date.empty() || SESSION::get_col_write_time() == MISC::TIME_PASSED ){ m_write_time_date = MISC::timettostr( m_write_time, SESSION::get_col_write_time() ); } } return m_write_time_date; } // // 書き込み時間更新 // void ArticleBase::update_writetime() { std::time_t current; if( CONFIG::get_save_post_history() && std::time( ¤t ) != std::time_t(-1) ){ m_write_time = current; m_write_time_date.clear(); #ifdef _DEBUG std::cout << "ArticleBase::update_writetime : " << m_write_time << std::endl; #endif m_save_info = true; // BoardViewの行を更新 CORE::core_set_command( "update_board_item", DBTREE::url_boardbase( m_url ), m_id ); } // 板の書き込み時間を更新 DBTREE::board_update_writetime( m_url ); } // // スレ自体のスレ一覧でのブックマーク // void ArticleBase::set_bookmarked_thread( const bool bookmarked ) { m_bookmarked_thread = bookmarked; save_info( true ); // BoardViewの行を更新 CORE::core_set_command( "update_board_item", DBTREE::url_boardbase( m_url ), m_id ); } // // キャッシュがあって、かつ新着の読み込みが可能 // bool ArticleBase::enable_load() const { return ( is_cached() && ( m_status & STATUS_UPDATE ) && ! ( m_status & STATUS_OLD ) ); } // // キャッシュはあるが規定のレス数を越えていて、かつ全てのレスが既読 // bool ArticleBase::is_finished() const { if( is_cached() && ! enable_load() && m_number_max && get_number_seen() >= m_number_max ){ #ifdef _DEBUG std::cout << "ArticleBase::is_finished : seen = " << get_number_seen() << " max = " << m_number_max << " : " << get_subject() << std::endl; #endif return true; } return false; } // // 透明あぼーん // bool ArticleBase::get_abone_transparent() const { if( CONFIG::get_abone_transparent() ) return true; return m_abone_transparent; } // // 連鎖あぼーん // bool ArticleBase::get_abone_chain() const { if( CONFIG::get_abone_chain() ) return true; return m_abone_chain; } // // あぼーんしてるか // bool ArticleBase::get_abone( int number ) { return get_nodetree()->get_abone( number ); } // // 全レスのあぼーん状態の更新 // // あぼーん情報を変更したら呼び出す // // あぼーんしたスレの発言数や参照はカウントしないので、発言数や参照数も更新する // void ArticleBase::update_abone() { // nodetreeが作られていないときは更新しない if( ! m_nodetree ) return; get_nodetree()->copy_abone_info( m_list_abone_id, m_list_abone_name, m_list_abone_word, m_list_abone_regex, m_abone_reses, m_abone_transparent, m_abone_chain, m_abone_age, m_abone_default_name, m_abone_noid, m_abone_board, m_abone_global ); get_nodetree()->update_abone_all(); } // // あぼーん状態のリセット(情報セットと状態更新) // void ArticleBase::reset_abone( const std::list< std::string >& ids, const std::list< std::string >& names, const std::list< std::string >& words, const std::list< std::string >& regexs, const std::vector< char >& vec_abone_res, const bool transparent, const bool chain, const bool age, const bool default_name, const bool noid, const bool board, const bool global ) { if( empty() ) return; #ifdef _DEBUG std::cout << "ArticleBase::reset_abone\n"; #endif // 前後の空白と空白行を除く m_list_abone_id = MISC::remove_space_from_list( ids ); m_list_abone_id = MISC::remove_nullline_from_list( m_list_abone_id ); m_list_abone_name = MISC::remove_space_from_list( names ); m_list_abone_name = MISC::remove_nullline_from_list( m_list_abone_name ); m_list_abone_word = MISC::remove_space_from_list( words ); m_list_abone_word = MISC::remove_nullline_from_list( m_list_abone_word ); m_list_abone_regex = MISC::remove_space_from_list( regexs ); m_list_abone_regex = MISC::remove_nullline_from_list( m_list_abone_regex ); if( vec_abone_res.size() ){ for( int i = 1; i <= MIN( m_number_load, (int)vec_abone_res.size() ) ; ++i ){ if( vec_abone_res[ i ] ) { m_abone_reses.insert( i ); } else { m_abone_reses.erase( i ); } } } m_abone_transparent = transparent; m_abone_chain = chain; m_abone_age = age; m_abone_default_name = default_name; m_abone_noid = noid; m_abone_board = board; m_abone_global = global; update_abone(); m_save_info = true; } // // あぼーんID追加 // void ArticleBase::add_abone_id( const std::string& id ) { if( empty() ) return; if( id.empty() ) return; #ifdef _DEBUG std::cout << "ArticleBase::add_abone_id : " << id << std::endl; #endif std::string id_tmp = id.substr( strlen( PROTO_ID ) ); m_list_abone_id.push_back( id_tmp ); update_abone(); m_save_info = true; } // // あぼーん名前追加 // void ArticleBase::add_abone_name( const std::string& name ) { if( empty() ) return; if( name.empty() ) return; #ifdef _DEBUG std::cout << "ArticleBase::add_abone_name : " << name << std::endl; #endif m_list_abone_name.push_back( name ); update_abone(); m_save_info = true; } // // あぼーん文字列追加 // void ArticleBase::add_abone_word( const std::string& word ) { if( empty() ) return; if( word.empty() ) return; #ifdef _DEBUG std::cout << "ArticleBase::add_abone_word : " << word << std::endl; #endif m_list_abone_word.push_back( word ); update_abone(); m_save_info = true; } // // レスあぼーんのセット // void ArticleBase::set_abone_res( const int num_from, const int num_to, const bool set ) { if( empty() ) return; if( num_from > num_to ) return; if( num_from <= 0 || num_to > CONFIG::get_max_resnumber() ) return; #ifdef _DEBUG std::cout << "ArticleBase::set_abone_res num_from = " << num_from << " num_to = " << num_to << " set = " << set << std::endl; #endif if( set ) { for( int i = num_from; i <= num_to; ++i ) m_abone_reses.insert( i ); } else { for( int i = num_from; i <= num_to; ++i ) m_abone_reses.erase( i ); } update_abone(); m_save_info = true; } // // 透明あぼーん更新 // void ArticleBase::set_abone_transparent( const bool set ) { if( empty() ) return; m_abone_transparent = set; update_abone(); m_save_info = true; } // // 連鎖あぼーん更新 // void ArticleBase::set_abone_chain( const bool set ) { if( empty() ) return; m_abone_chain = set; update_abone(); m_save_info = true; } // // ageあぼーん更新 // void ArticleBase::set_abone_age( const bool set ) { if( empty() ) return; m_abone_age = set; update_abone(); m_save_info = true; } // // デフォルト名無しあぼーん更新 // void ArticleBase::set_abone_default_name( const bool set ) { if( empty() ) return; m_abone_default_name = set; update_abone(); m_save_info = true; } // // ID無しあぼーん更新 // void ArticleBase::set_abone_noid( const bool set ) { if( empty() ) return; m_abone_noid = set; update_abone(); m_save_info = true; } // // 板レベルでのあぼーんを有効にする // void ArticleBase::set_abone_board( const bool set ) { if( empty() ) return; m_abone_board = set; update_abone(); m_save_info = true; } // // 全体レベルでのあぼーんを有効にする // void ArticleBase::set_abone_global( const bool set ) { if( empty() ) return; m_abone_global = set; update_abone(); m_save_info = true; } // // ブックマークされているか // bool ArticleBase::is_bookmarked( const int number ) { if( number <= 0 || number > m_number_load ) return false; // まだnodetreeが作られてなくてブックマークの情報が得られてないのでnodetreeを作って情報取得 if( m_bookmarks.empty() ) get_nodetree(); return ( m_bookmarks.find( number ) != m_bookmarks.end() ); } // // ブックマークセット // void ArticleBase::set_bookmark( const int number, const bool set ) { if( m_bookmarks.empty() ) get_nodetree(); if( number <= 0 || number > CONFIG::get_max_resnumber() ) return; m_save_info = true; if( set ) { m_bookmarks.insert( number ); } else { m_bookmarks.erase( number ); } } // // 自分が書き込んだレスか // bool ArticleBase::is_posted( const int number ) { if( number <= 0 || number > m_number_load ) return false; // まだnodetreeが作られてなくて情報が得られてないのでnodetreeを作って情報取得 if( m_posts.empty() ) get_nodetree(); return ( m_posts.find( number ) != m_posts.end() ); } // 自分の書き込みにレスしたか bool ArticleBase::is_refer_posted( const int number ) { return get_nodetree()->is_refer_posted( number ); } // 書き込みマークセット void ArticleBase::set_posted( const int number, const bool set ) { if( number <= 0 || number > m_number_load ) return; // まだnodetreeが作られてなくて情報が得られてないのでnodetreeを作って情報取得 if( m_posts.empty() ) get_nodetree(); m_save_info = true; if( set ) { m_posts.insert( number ); } else { m_posts.erase( number ); } // nodetreeに情報反映 m_nodetree->set_posted( number, set ); } // 書き込み履歴のリセット void ArticleBase::clear_post_history() { if( empty() ) return; if( ! is_cached() ) return; read_info(); if( !m_posts.empty() || m_write_time ){ #ifdef _DEBUG std::cout << "ArticleBase::clear_post_history size = " << m_posts.size() << " time = " << m_write_time_date << " subject = " << m_subject << std::endl; #endif m_posts.clear(); m_write_time = 0; m_write_time_date.clear(); // nodetreeが作られている時はnodetreeもリセット if( m_nodetree ) m_nodetree->clear_post_history(); m_write_name = std::string(); m_write_mail = std::string(); m_write_fixname = false; m_write_fixmail = false; save_info( true ); } } // // NodeTree作成 // // もしNodeTreeが作られていなかったらここでNewする // // this の参照が無くなったら ArticleBase::unlock_impl()が呼ばれて m_nodetree は自動クリアされる // NodeTreeBase* ArticleBase::get_nodetree() { assert( !empty() ); if( ! m_nodetree ){ #ifdef _DEBUG std::cout << "ArticleBase::get_nodetree create " << m_url << std::endl; #endif m_nodetree.reset( create_nodetree() ); assert( m_nodetree ); // あぼーん情報のコピー m_nodetree->copy_abone_info( m_list_abone_id, m_list_abone_name, m_list_abone_word, m_list_abone_regex, m_abone_reses, m_abone_transparent, m_abone_chain, m_abone_age, m_abone_default_name, m_abone_noid, m_abone_board, m_abone_global ); // 書き込み情報のコピー m_nodetree->copy_post_info( m_posts ); m_nodetree->sig_updated().connect( sigc::mem_fun( *this, &ArticleBase::slot_node_updated ) ); m_nodetree->sig_finished().connect( sigc::mem_fun( *this, &ArticleBase::slot_load_finished ) ); // キャッシュ読み込み m_number_load = 0; // 読み込み数リセット m_nodetree->load_cache(); } return m_nodetree.get(); } // // this の参照ロックが外れたときに呼ばれる // // m_nodetree を deleteする // void ArticleBase::unlock_impl() { if( !m_nodetree ) return; #ifdef _DEBUG std::cout << "ArticleBase::unlock_impl url = " << m_url << std::endl; #endif m_nodetree->terminate_load(); // deleteする前にスレッド停止 // スレ情報保存 save_info( false ); m_nodetree.reset(); } // // ロード中か // bool ArticleBase::is_loading() const { if( ! m_nodetree ) return false; return m_nodetree->is_loading(); } // // 更新チェック中か // bool ArticleBase::is_checking_update() const { if( ! is_loading() ) return false; return m_nodetree->is_checking_update(); } // // ロード停止 // void ArticleBase::stop_load() { if( ! m_nodetree ) return; m_nodetree->stop_load(); } // // スレッドのロード開始 // // DAT落ちの場合はロードしないので、強制的にリロードしたいときは reset_status() で // ステータスをリセットしてからロードする // // check_update == true の時はHEADによる更新チェックをおこなう // void ArticleBase::download_dat( const bool check_update ) { if( empty() ) return; #ifdef _DEBUG std::cout << "ArticleBase::download_dat " << m_url << " status = " << m_status << " checkupdate = " << check_update << std::endl << "url_pre_article = " << m_url_pre_article << std::endl; #endif std::time_t current = std::time( nullptr ); if( current == std::time_t(-1) ) current = 0; // 更新チェック可能か判定する if( check_update ){ // 一度更新チェックしたらしばらくは再チェックできないようにする time_t passed = 0; if( current ) passed = std::max( 0, current - m_check_update_time ); if( ! SESSION::is_online() || ! enable_check_update() || is_loading() || enable_load() // 既に新着あり状態の時はチェックしない || ( m_status & STATUS_OLD ) || ( passed <= CHECKUPDATE_MINSEC ) ) { #ifdef _DEBUG std::cout << "skipped : passed = " << passed << " enable_load = " << enable_load() << " loading = " << is_loading() << std::endl; #endif // スレビューのタブとサイドバーのアイコン表示を更新 // JDが異常終了すると、お気に入りに+が付いてないのにスレが更新可能状態になっていて // 更新チェックが行われてなくなるときがある。このバグは次の様にして再現出来た // (1) 端末からJDを起動する // (2) 適当な板のスレ一覧を開く // (3) お気に入りに適当なスレを登録 // (4) そのスレを読み込む。読み込んだらスレビューは閉じないままにしておく // (5) スレ一覧を更新してそのスレに + マークを付ける。お気に入りにも + が付く // (6) そのスレのスレビューを閉じる ( スレ情報を保存 ) // (7) 端末で Ctrl+c を押してJDを強制終了 // (8) 再起動してお気に入りの復元をしない // (9) お気に入りの + が消えている。以後更新チェックも行われない CORE::core_set_command( "toggle_article_icon", m_url); CORE::core_set_command( "toggle_sidebar_articleicon", m_url ); return; } } if( SESSION::is_online() && current ) m_check_update_time = current; // DAT落ちしていてロードしない場合 if( ( m_status & STATUS_OLD ) && ! is_load_olddat() ) { #ifdef _DEBUG std::cout << "old !\n"; #endif CORE::core_set_command( "toggle_sidebar_articleicon", m_url ); // update_article_finish コマンドを送らないとキャッシュが無くて // dat落ちしているスレのタブが空白になる CORE::core_set_command( "update_article_finish", m_url ); return; } #ifdef _DEBUG std::cout << "start\n"; #endif get_nodetree()->download_dat( check_update ); } // // 前スレのアドレスの指定 // // 前スレのアドレスをセットしてからdownload_dat()を呼び出すと // ロード終了時( slot_load_finished() )に次スレ移行チェックをする // void ArticleBase::set_url_pre_article( const std::string& url_pre_article ) { #ifdef _DEBUG std::cout << "ArticleBase::set_url_pre_article url = " << url_pre_article << std::endl; #endif m_url_pre_article = url_pre_article; if( ! m_url_pre_article.empty() ){ // 既読スレや板違いのスレに対しては移行チェックはしない if( m_number_load || m_datbase != DBTREE::url_datbase( m_url_pre_article ) || get_since_time() < DBTREE::article_since_time( m_url_pre_article ) ) m_url_pre_article = std::string(); } // TFIDFによる類似度判定のためオフラインに切り替えてからキャッシュにあるsubject.txtを読み込む if( ! m_url_pre_article.empty() && ! DBTREE::board_list_subject( m_url ).size() ){ #ifdef _DEBUG std::cout << "load subjects\n"; #endif const bool online = SESSION::is_online(); SESSION::set_online( false ); DBTREE::board_download_subject( m_url, std::string() ); SESSION::set_online( online ); } } // // url_src で示されるスレの情報を引き継ぐ // void ArticleBase::copy_article_info( const std::string& url_src ) { if( url_src.empty() ) return; if( ! DBTREE::article_is_cached( url_src ) ) return; // 名前、メール m_write_fixname = DBTREE::write_fixname( url_src ); m_write_name = DBTREE::write_name( url_src ); m_write_fixmail = DBTREE::write_fixmail( url_src ); m_write_mail = DBTREE::write_mail( url_src ); // あぼーん関係 std::list< std::string > ids; std::list< std::string > names = DBTREE::get_abone_list_name( url_src ); std::list< std::string > words = DBTREE::get_abone_list_word( url_src ); std::list< std::string > regexs = DBTREE::get_abone_list_regex( url_src ); std::vector< char > vec_abone_res; const bool transparent = DBTREE::get_abone_transparent( url_src ); const bool chain = DBTREE::get_abone_chain( url_src ); const bool age = DBTREE::get_abone_age( url_src ); const bool default_name = DBTREE::get_abone_default_name( url_src ); const bool noid = DBTREE::get_abone_noid( url_src ); const bool board = DBTREE::get_abone_board( url_src ); const bool global = DBTREE::get_abone_global( url_src ); reset_abone( ids, names ,words, regexs, vec_abone_res, transparent, chain, age, default_name, noid, board, global ); } // // ロード中など nodetree の構造が変わったときにnodetreeから呼ばれる slot // void ArticleBase::slot_node_updated() { assert( m_nodetree ); // 更新チェック中 if( m_nodetree->is_checking_update() ) return; #ifdef _DEBUG std::cout << "ArticleBase::slot_node_updated" << std::endl; #endif // nodetreeから情報取得 if( ! m_nodetree->get_subject().empty() ) set_subject( m_nodetree->get_subject() ); // スレが更新している場合 if( m_number_load != m_nodetree->get_res_number() ){ // スレの読み込み数更新 m_number_load = m_nodetree->get_res_number(); // 対応するarticleビューを更新 CORE::core_set_command( "update_article", m_url ); } } // // ロード終了後に nodetree から呼ばれる slot // nodetree から情報を取得する // void ArticleBase::slot_load_finished() { assert( m_nodetree ); #ifdef _DEBUG std::cout << "ArticleBase::slot_load_finished" << std::endl; #endif slot_node_updated(); // HTTPコード取得 const int old_code = m_code; m_code = m_nodetree->get_code(); m_status &= ~STATUS_UPDATED; // 状態更新 const int old_status = m_status; if( m_code != HTTP_ERR ){ // DAT落ち if( m_code == HTTP_MOVED_PERM || m_code == HTTP_REDIRECT || m_code == HTTP_NOT_FOUND || m_code == HTTP_OLD ){ m_status &= ~STATUS_NORMAL; m_status |= STATUS_OLD; CORE::core_set_command( "toggle_sidebar_articleicon", m_url ); } // 既にDAT落ち状態では無いときは通常状態にする else if( ! (m_status & STATUS_OLD ) ){ m_status |= STATUS_NORMAL; m_status &= ~STATUS_OLD; } } // 壊れている if( m_nodetree->is_broken() ) m_status |= STATUS_BROKEN; // レス数が最大表示可能数以上か if( get_number_load() >= CONFIG::get_max_resnumber() ) m_status |= STATUS_OVERFLOW; else { m_status &= ~STATUS_OVERFLOW; } // 状態が変わっていたら情報保存 if( old_status != m_status ) m_save_info = true; // 更新チェック if( m_nodetree->is_checking_update() ){ #ifdef _DEBUG std::cout << "check_update code = " << m_code << std::endl; #endif // スレタブとお気に入りとスレ一覧のアイコンに更新マークをつける if( m_code == HTTP_OK // まちBBSは206が返らない(200か304のみ) || m_code == HTTP_PARTIAL_CONTENT ){ show_updateicon( true ); // このスレが所属する板を更新可能状態にしてお気に入りやスレ一覧のタブのアイコンに更新マークを表示 DBTREE::board_show_updateicon( m_url, true ); // スレ一覧の ! 行のアイコンを更新マークにする CORE::core_set_command( "update_board_item", DBTREE::url_boardbase( m_url ), m_id ); } // code と modified を戻しておく m_code = old_code; m_nodetree->set_date_modified( m_date_modified ); #ifdef _DEBUG std::cout << "check_update done\n"; #endif // 更新チェック時間を保存 save_info( true ); // 次のスレを更新チェック CORE::get_checkupdate_manager()->pop_front(); return; } // nodetreeから情報取得 m_str_code = m_nodetree->get_str_code(); std::string m_old_modified = m_date_modified; m_date_modified = m_nodetree->get_date_modified(); if( m_number_before_load < m_number_load ) m_number_new = m_number_load - m_number_before_load; else m_number_new = 0; // 書き込み情報 const auto& node_posts = m_nodetree->get_posts(); if( m_number_new && node_posts.size() ) { const auto end = m_posts.end(); const auto node_end = node_posts.end(); (void)end; // _DEBUGが定義されていないときの警告抑制 for( int i = m_number_before_load +1; i <= m_number_load; ++i ){ if( node_posts.find( i ) != node_end ) { m_posts.insert( i ); } else { m_posts.erase( i ); } #ifdef _DEBUG if( m_posts.find( i ) != end ) std::cout << "posted no = " << i << std::endl; #endif } } // 次スレチェック bool relayout = false; if( m_number_load && ! m_url_pre_article.empty() && ! m_subject.empty() ){ const std::string pre_subject = DBTREE::article_subject( m_url_pre_article ); if( ! pre_subject.empty() ){ #ifdef _DEBUG std::cout << "check next\n"; #endif int value = 0; const std::vector< DBTREE::ArticleBase* >& list_subject = DBTREE::board_list_subject( m_url ); // subjectがキャッシュにある場合は TFIDF を使って類似度チェック if( list_subject.size() ){ #ifdef _DEBUG std::cout << "use tfidf\n"; #endif // 単語ベクトル作成 MISC::VEC_WORDS vec_words; MISC::tfidf_create_vec_words( vec_words, pre_subject ); // IDFベクトル計算 MISC::VEC_IDF vec_idf; MISC::tfidf_create_vec_idf_from_board( vec_idf, pre_subject, list_subject, vec_words ); // TFIDFベクトル計算 MISC::VEC_TFIDF vec_tfidf_src; MISC::VEC_TFIDF vec_tfidf; vec_tfidf_src.resize( vec_words.size() ); vec_tfidf.resize( vec_words.size() ); MISC::tfidf_calc_vec_tfifd( vec_tfidf_src, pre_subject, vec_idf, vec_words ); MISC::tfidf_calc_vec_tfifd( vec_tfidf, m_subject, vec_idf, vec_words ); value = ( int )( MISC::tfidf_cos_similarity( vec_tfidf_src, vec_tfidf ) * 10 + .5 ); } // subject がキャッシュに無い場合はレーベンシュタイン距離を使って類似度チェック else{ #ifdef _DEBUG std::cout << "use leven\n"; #endif const int MAXSTR = 256; std::vector< std::vector< int > > dist( MAXSTR, std::vector< int >( MAXSTR ) ); value = 10 - ( int )( MISC::leven( dist, pre_subject, m_subject ) * 10 + .5 ); } #ifdef _DEBUG std::cout << "pre_subject = " << pre_subject << std::endl << "subject = " << m_subject << std::endl << "value = " << value << std::endl; #endif // このスレは m_url_pre_article の次スレとみなして情報を引き継ぐ if( value >= CONFIG::get_threshold_next() ){ #ifdef _DEBUG std::cout << "hit!\n"; #endif copy_article_info( m_url_pre_article ); // お気に入りのアドレスと名前を自動更新 CORE::core_set_command( "replace_favorite_thread", "", m_url_pre_article, m_url ); // 前スレにしおりがセットされていたらしおりをつける if( DBTREE::is_bookmarked_thread( m_url_pre_article ) ){ set_bookmarked_thread( true ); } relayout = true; } #ifdef _DEBUG else std::cout << "not hit\n"; #endif } m_url_pre_article = std::string(); } m_number_before_load = m_number_load; m_ext_err = m_nodetree->get_ext_err(); // スレの数が0ならスレ情報はセーブしない if( ! m_number_load ) m_cached = false; else{ // スレ情報を更新 if( m_number_new // スレが更新している場合 || m_date_modified != m_old_modified // ときどき modified が誤って返るときがあるので最新の値を保存しておく || ( SESSION::is_online() && ( m_status & STATUS_UPDATE ) && ( m_code == HTTP_OK || m_code == HTTP_NOT_MODIFIED ) ) // 間違って更新可能マークが付いている場合はマークを消す ){ m_cached = true; m_read_info = true; m_save_info = true; std::time_t current; if( std::time( ¤t ) != std::time_t(-1) ) { m_access_time = current; m_access_date.clear(); } if( m_number < m_number_load ) m_number = m_number_load; m_status |= STATUS_UPDATED; show_updateicon( false ); // 情報ファイルのパスをセット if( m_path_article_info.empty() ) SET_INFOPATH(); } } #ifdef _DEBUG std::cout << "ArticleBase::slot_load_finished " << std::endl << "subject = " << m_subject << std::endl << "load = " << m_number_load << std::endl << "number = " << m_number << std::endl << "new = " << m_number_new << std::endl << "date = " << m_date_modified << std::endl << "access-time = " << get_access_time_str() << std::endl << "code = " << m_code << std::endl << "status = " << m_status << std::endl ; #endif // 対応するBoardビューの行を更新 CORE::core_set_command( "update_board_item", DBTREE::url_boardbase( m_url ), m_id ); // articleビューに終了を知らせる CORE::core_set_command( "update_article", m_url ); CORE::core_set_command( "update_article_finish", m_url ); // あぼーん情報が前スレよりコピーされたので再レイアウト指定 if( relayout ) CORE::core_set_command( "relayout_article", m_url ); } // // お気に入りのアイコンとスレビューのタブのアイコンに更新マークを表示 // // update == true の時に表示。falseなら戻す // void ArticleBase::show_updateicon( const bool update ) { #ifdef _DEBUG std::cout << "ArticleBase::show_updateicon url = " << m_url << " update = " << update << " status = " << ( m_status & STATUS_UPDATE ) << std::endl; #endif const std::time_t current = std::time( nullptr ); if( current != std::time_t(-1) ) m_check_update_time = current; if( update ){ if( ! ( m_status & STATUS_UPDATE ) ){ #ifdef _DEBUG std::cout << "toggle_icon on\n"; #endif m_save_info = true; m_status |= STATUS_UPDATE; // スレビューのタブとサイドバーのアイコン表示を更新 CORE::core_set_command( "toggle_article_icon", m_url); CORE::core_set_command( "toggle_sidebar_articleicon", m_url ); } } else{ // この if をコメントアウトしないと // // スレ一覧を開いてお気に入りにあるスレに更新マークを付ける → スレ一覧を閉じる // 更新マークを付けたスレを開かないでJD終了(※) → 再起動してお気に入りで更新マークを付けたスレをクリック // → お気に入りのアイコン表示が戻らない // // という問題が生じる( ※ の所でスレ情報が保存されていないので再起動すると STATUS_UPDATE が外れるため。 // 終了時にスレ情報を保存しようとすると終了処理が重くなる。) // if( m_status & STATUS_UPDATE ){ #ifdef _DEBUG std::cout << "toggle_icon off\n"; #endif m_save_info = true; m_status &= ~STATUS_UPDATE; // サイドバーのアイコン表示を戻す // スレビューのタブのアイコンはArticleViewがロード終了時に自動的に戻す CORE::core_set_command( "toggle_sidebar_articleicon", m_url ); // } } } // // キャッシュ削除 // // cache_only == true の時はキャッシュだけ削除してスレ情報は消さない // void ArticleBase::delete_cache( const bool cache_only ) { #ifdef _DEBUG std::cout << "ArticleBase::delete_cache url = " << m_url << std::endl; #endif if( empty() ) return; if( ! cache_only ){ if( m_bookmarked_thread ){ const std::string msg = "「" + get_subject() + "」にはしおりが付けられています。\n\nスレを削除しますか?\n\nしおりを解除するにはスレの上で右クリックしてしおり解除を選択してください。"; SKELETON::MsgDiag mdiag( nullptr, msg, false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO ); mdiag.set_default_response( Gtk::RESPONSE_YES ); if( mdiag.run() != Gtk::RESPONSE_YES ) return; } if( CONFIG::get_show_del_written_thread_diag() && m_write_time ){ const std::string msg = "「" + get_subject() + "」には書き込み履歴が残っています。\n\nスレを削除しますか?"; SKELETON::MsgCheckDiag mdiag( nullptr, msg, "今後表示しない(常に削除)(_D)", Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO ); if( mdiag.run() != Gtk::RESPONSE_YES ) return; if( mdiag.get_chkbutton().get_active() ) CONFIG::set_del_written_thread_diag( false ); } // スレ内の画像キャッシュ削除 if( CONFIG::get_delete_img_in_thread() != 2 ){ const std::list list_urls = get_nodetree()->get_imglinks(); bool delete_img_cache = std::any_of( list_urls.cbegin(), list_urls.cend(), &DBIMG::is_cached ); if( delete_img_cache ){ if( CONFIG::get_delete_img_in_thread() == 0 ){ const std::string msg = "「" + get_subject() + "」には画像が貼られています。\n\n画像のキャッシュも削除しますか?"; SKELETON::MsgCheckDiag mdiag( nullptr, msg, "今後表示しない(常に削除しない)(_D)", Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_NONE ); mdiag.add_button( "スレ削除中止(_C)", Gtk::RESPONSE_CANCEL ); mdiag.add_button( g_dgettext( GTK_DOMAIN, "_Yes" ), Gtk::RESPONSE_YES ); mdiag.add_default_button( g_dgettext( GTK_DOMAIN, "_No" ), Gtk::RESPONSE_NO ); const int ret = mdiag.run(); if( ret == Gtk::RESPONSE_CANCEL ) return; if( ret != Gtk::RESPONSE_YES ){ if( mdiag.get_chkbutton().get_active() ) CONFIG::set_delete_img_in_thread( 2 ); delete_img_cache = false; } } if( delete_img_cache ){ for( const std::string& url : list_urls ) { if( DBIMG::is_cached( url ) ){ #ifdef _DEBUG std::cout << "delete " << url << std::endl; #endif DBIMG::delete_cache( url ); } } } } } } m_number_load = m_number_seen = m_number_before_load = 0; m_cached = false; reset_status(); m_date_modified.clear(); m_access_time = 0; m_check_update_time = 0; if( ! cache_only ){ m_write_time = 0; m_write_time_date.clear(); m_code = HTTP_INIT; m_str_code = std::string(); m_write_name.clear(); m_write_mail.clear(); m_write_fixname = false; m_write_fixmail = false; m_bookmarks.clear(); m_posts.clear(); m_list_abone_id.clear(); m_list_abone_name.clear(); m_list_abone_word.clear(); m_list_abone_regex.clear(); m_abone_reses.clear(); m_abone_transparent = false; m_abone_chain = false; m_abone_age = false; m_abone_default_name = false; m_abone_noid = false; m_abone_board = true; m_abone_global = true; m_read_info = false; m_save_info = false; m_bookmarked_thread = false; // info 削除 if( CACHE::file_exists( m_path_article_info ) == CACHE::EXIST_FILE ) unlink( to_locale_cstr( m_path_article_info ) ); // 拡張info 削除 if( CACHE::file_exists( m_path_article_ext_info ) == CACHE::EXIST_FILE ) unlink( to_locale_cstr( m_path_article_ext_info ) ); // お気に入りから削除 CORE::core_set_command( "remove_favorite", m_url ); } // キャッシュ削除 std::string path_dat = CACHE::path_dat( m_url ); if( CACHE::file_exists( path_dat ) == CACHE::EXIST_FILE ) unlink( to_locale_cstr( path_dat ) ); // BoardViewの行を更新 CORE::core_set_command( "update_board_item", DBTREE::url_boardbase( m_url ), m_id ); // サイドバーのアイコン表示を戻す CORE::core_set_command( "toggle_sidebar_articleicon", m_url ); } // // キャッシュを名前を付けて保存 // // path_to はデフォルトのファイル名 // bool ArticleBase::save_dat( const std::string& path_to ) { if( is_loading() ) return false; std::string dir = MISC::get_dir( path_to ); if( dir.empty() ) dir = SESSION::get_dir_dat(); std::string name = MISC::get_filename( path_to ); if( name.empty() ) name = get_id(); std::string save_to = CACHE::copy_file( nullptr, CACHE::path_dat( m_url ), dir + name, CACHE::FILE_TYPE_DAT ); if( ! save_to.empty() ){ SESSION::set_dir_dat( MISC::get_dir( save_to ) ); return true; } return false; } // // infoファイル読み込み // // インスタンスが出来るたびに呼んでいると重くなるので、BoardBase::get_article_fromURL() // で初めて参照されたときや、Boardビューに表示するときに一回だけ読み込む // void ArticleBase::read_info() { if( m_read_info ) return; // 一度読んだら2度読みしない if( empty() ) return; if( ! is_cached() ) return; // キャッシュがないなら読まない #ifdef _DEBUG std::cout << "ArticleBase::read_info : url = " << m_url << std::endl; #endif m_read_info = true; const int status_old = m_status; bool saveinfo = false; // 情報ファイルのパスをセット if( m_path_article_info.empty() ) SET_INFOPATH(); int ret = CACHE::file_exists( m_path_article_ext_info ); if( ret == CACHE::EXIST_FILE ){ std::string str_info, str_tmp; std::list< std::string > list_tmp; std::list< std::string >::iterator it_tmp; CACHE::load_rawdata( m_path_article_ext_info, str_info ); std::list< std::string > lines = MISC::get_lines( str_info ); std::list < std::string >::iterator it = lines.begin(), it2; // subject GET_INFOVALUE( m_subject, "subject = " ); // 旧ホスト名 GET_INFOVALUE( m_org_host, "org_host = " ); if( m_org_host.empty() ) m_org_host = MISC::get_hostname( m_url ); // 取得数 m_number_load = 0; GET_INFOVALUE( str_tmp, "load = " ); if( ! str_tmp.empty() ) m_number_load = std::stoi( str_tmp ); m_number_before_load = m_number_load; // 見た場所 m_number_seen = 0; GET_INFOVALUE( str_tmp, "seen = " ); if( ! str_tmp.empty() ) m_number_seen = std::stoi( str_tmp ); // 更新時間 (time) GET_INFOVALUE( m_date_modified, "modified = " ); // access time (Emacs Lisp の Lisp timestamp形式) GET_INFOVALUE( str_tmp, "access = " ); if( ! str_tmp.empty() ){ list_tmp = MISC::split_line( str_tmp ); // Emacsでは(high low micro pico)まで拡張されているが(high low)部分のみ使う if( list_tmp.size() >= 2 ){ it_tmp = list_tmp.begin(); m_access_time = std::atoi( it_tmp->c_str() ) << 16; // high ++it_tmp; m_access_time += std::atoi( it_tmp->c_str() ); // low // 以前はmicro秒を解析していたが未使用だったため省略した (Since v0.5.0+) } } // write time (elisp の Lisp timestamp形式) GET_INFOVALUE( str_tmp, "writetime = " ); if( ! str_tmp.empty() ){ list_tmp = MISC::split_line( str_tmp ); // (high low)部分のみ使う if( list_tmp.size() >= 2 ){ it_tmp = list_tmp.begin(); m_write_time = std::atoi( it_tmp->c_str() ) << 16; // high ++it_tmp; m_write_time += std::atoi( it_tmp->c_str() ); // low // 以前はmicro秒を解析していたが未使用だったため省略した (Since v0.5.0+) } } // write name m_write_name = std::string(); GET_INFOVALUE( m_write_name, "writename = " ); // write mail m_write_mail = std::string(); GET_INFOVALUE( m_write_mail, "writemail = " ); // name 固定 m_write_fixname = false; GET_INFOVALUE( str_tmp, "writefixname = " ); if( ! str_tmp.empty() ) m_write_fixname = atoi( str_tmp.c_str() ); // mail 固定 m_write_fixmail = false; GET_INFOVALUE( str_tmp, "writefixmail = " ); if( ! str_tmp.empty() ) m_write_fixmail = atoi( str_tmp.c_str() ); // 状態 reset_status(); GET_INFOVALUE( str_tmp, "status = " ); if( ! str_tmp.empty() ) m_status = atoi( str_tmp.c_str() ); // あぼーん ID GET_INFOVALUE( str_tmp, "aboneid = " ); if( ! str_tmp.empty() ) m_list_abone_id = MISC::strtolist( str_tmp ); // あぼーん name GET_INFOVALUE( str_tmp, "abonename = " ); if( ! str_tmp.empty() ) m_list_abone_name = MISC::strtolist( str_tmp ); // レスブックマーク GET_INFOVALUE( str_tmp, "bookmark = " ); if( ! str_tmp.empty() ){ list_tmp = MISC::split_line( str_tmp ); for( const std::string& num_str : list_tmp ) { if( !num_str.empty() ) { m_bookmarks.insert( std::stoi( num_str ) ); } } } // あぼーん word GET_INFOVALUE( str_tmp, "aboneword = " ); if( ! str_tmp.empty() ) m_list_abone_word = MISC::strtolist( str_tmp ); // あぼーん regex GET_INFOVALUE( str_tmp, "aboneregex = " ); if( ! str_tmp.empty() ) m_list_abone_regex = MISC::strtolist( str_tmp ); // 透明あぼーん m_abone_transparent = false; GET_INFOVALUE( str_tmp, "abonetrp = " ); if( ! str_tmp.empty() ) m_abone_transparent = atoi( str_tmp.c_str() ); // 連鎖あぼーん m_abone_chain = false; GET_INFOVALUE( str_tmp, "abonechain = " ); if( ! str_tmp.empty() ) m_abone_chain = atoi( str_tmp.c_str() ); // レス番号あぼーん m_abone_reses.clear(); GET_INFOVALUE( str_tmp, "aboneres = " ); if( ! str_tmp.empty() ){ list_tmp = MISC::split_line( str_tmp ); for( const std::string& num_str : list_tmp ) { if( !num_str.empty() ) { m_abone_reses.insert( std::stoi( num_str ) ); } } } // スレ一覧でブックマークされているか m_bookmarked_thread = false; GET_INFOVALUE( str_tmp, "bkmark_thread = " ); if( ! str_tmp.empty() ) m_bookmarked_thread = atoi( str_tmp.c_str() ); // 書き込みしたレス番号 GET_INFOVALUE( str_tmp, "posted = " ); if( ! str_tmp.empty() ){ list_tmp = MISC::split_line( str_tmp ); for( const std::string& num_str : list_tmp ) { if( !num_str.empty() ) { m_posts.insert( std::stoi( num_str ) ); } } } // ageあぼーん m_abone_age = false; GET_INFOVALUE( str_tmp, "aboneage = " ); if( ! str_tmp.empty() ) m_abone_age = atoi( str_tmp.c_str() ); // デフォルト名無しあぼーん m_abone_default_name = false; GET_INFOVALUE( str_tmp, "abonedefaultname = " ); if( ! str_tmp.empty() ) m_abone_default_name = std::atoi( str_tmp.c_str() ); // ID無しあぼーん m_abone_noid = false; GET_INFOVALUE( str_tmp, "abonenoid = " ); if( ! str_tmp.empty() ) m_abone_noid = std::atoi( str_tmp.c_str() ); // 最終更新チェック時間 (elisp の Lisp timestamp形式) GET_INFOVALUE( str_tmp, "checktime = " ); if( ! str_tmp.empty() ){ list_tmp = MISC::split_line( str_tmp ); // (high low)部分のみ if( list_tmp.size() >= 2 ){ it_tmp = list_tmp.begin(); m_check_update_time = std::atoi( it_tmp->c_str() ) << 16; // high ++it_tmp; m_check_update_time += std::atoi( it_tmp->c_str() ); // low } } // 板レベルでのあぼーんを有効にする m_abone_board = true; GET_INFOVALUE( str_tmp, "aboneboard = " ); if( ! str_tmp.empty() ) m_abone_board = atoi( str_tmp.c_str() ); // 全体レベルでのあぼーんを有効にする m_abone_global = true; GET_INFOVALUE( str_tmp, "aboneglobal = " ); if( ! str_tmp.empty() ) m_abone_global = atoi( str_tmp.c_str() ); } // キャッシュはあるけど情報ファイルが無い場合 // 一時的にnodetreeを作って情報を取得して保存 else{ #ifdef _DEBUG std::cout << "ArticleBase::read_info : update info " << m_url << std::endl; std::cout << "load = " << m_number_load << " subject = " << m_subject << std::endl; std::cout << "ret = " << ret << std::endl; std::cout << "path = " << m_path_article_ext_info << std::endl; #endif CORE::core_set_command( "set_status","", "スレ情報更新中・・・しばらくお待ち下さい" ); MISC::MSG( "updating " + m_url ); reset_status(); set_subject( get_nodetree()->get_subject() ); m_number_load = get_nodetree()->get_res_number(); if( !m_number_load ){ m_number_load = 1; m_status |= STATUS_BROKEN; MISC::MSG( "updating failed" ); } m_number_before_load = m_number_load; saveinfo = true; unlock_impl(); #ifdef _DEBUG std::cout << "\nArticleBase::read_info : update done.\n\n"; #endif } if( m_number < m_number_load ) m_number = m_number_load; // infoファイル読み込み前に既にDAT落ち状態になっていた場合は // 状態を DAT 落ちに戻しておく if( ( status_old & STATUS_OLD ) && ( m_status & STATUS_NORMAL ) ){ m_status &= ~STATUS_NORMAL; m_status |= STATUS_OLD; saveinfo = true; } if( saveinfo ) save_info( true ); #ifdef _DEBUG std::cout << "ArticleBase::read_info file = " << m_path_article_ext_info << std::endl; std::cout << "subject = " << m_subject << std::endl << "org_host = " << m_org_host << std::endl << "load = " << m_number_load << std::endl << "seen = " << m_number_seen << std::endl << "modified = " << m_date_modified << std::endl << "writetime = " << m_write_time_date << std::endl << "writename = " << m_write_name << std::endl << "writemail = " << m_write_mail << std::endl << "writefixname = " << m_write_fixname << std::endl << "writefixmail = " << m_write_fixmail << std::endl << "status = " << m_status << std::endl << "transparent_abone = " << m_abone_transparent << std::endl << "bookmarked_thread = " << m_bookmarked_thread << std::endl ; std::cout << "abone-id\n"; for( const std::string& s : m_list_abone_id ) std::cout << s << std::endl; std::cout << "abone-name\n"; for( const std::string& s : m_list_abone_name ) std::cout << s << std::endl; std::cout << "abone-word\n"; for( const std::string& s : m_list_abone_word ) std::cout << s << std::endl; std::cout << "abone-regex\n"; for( const std::string& s : m_list_abone_regex ) std::cout << s << std::endl; if( !m_abone_reses.empty() ) { std::cout << "abone-res ="; const auto end = m_abone_reses.end(); for( int i = 1; i <= m_number_load; ++i ) { if( m_abone_reses.find( i ) != end ) std::cout << ' ' << i; } } if( !m_bookmarks.empty() ) { std::cout << "bookmark = "; const auto end = m_bookmarks.end(); for( int i = 1; i <= m_number_load; ++i ) { if( m_bookmarks.find( i ) != end ) std::cout << ' ' << i; } std::cout << std::endl; } if( !m_posts.empty() ) { std::cout << "posted ="; const auto end = m_posts.end(); for( int i = 1; i <= m_number_load; ++i ) { if( m_posts.find( i ) != end ) std::cout << ' ' << i; } std::cout << std::endl; } #endif } // // infoファイル書き込み // // キャッシュがある( is_cached() == true ) かつ // m_save_info = true かつ nodetree が作られている時に保存。 // save_info()を呼ぶ前にm_save_infoをセットすること。 // // キャッシュがあって、force = true の時は強制書き込み void ArticleBase::save_info( const bool force ) { if( empty() ) return; if( ! is_cached() ) return; if( ! force ){ if( ! m_save_info ) return; if( ! m_nodetree ) return; } m_save_info = false; if( m_path_article_ext_info.empty() ) return; if( ! CACHE::mkdir_boardroot( m_url ) ) return; #ifdef _DEBUG std::cout << "ArticleBase::save_info force = " << force << std::endl; std::cout << "path_article_info = " << m_path_article_info << std::endl; std::cout << "path_article_ext_info = " << m_path_article_ext_info << std::endl; std::cout << "subject = " << m_subject << std::endl; #endif // 書き込み時間 (elisp の Lisp timestamp形式, 互換性のためmicro秒は0で固定) std::ostringstream ss_write; if( m_write_time ) ss_write << ( m_write_time >> 16 ) << ' ' << ( m_write_time & 0xffff ) << " 0"; // 更新チェック時間 (elisp の Lisp timestamp形式, highとlowのみ) std::ostringstream ss_check; if( m_check_update_time ) ss_check << ( m_check_update_time >> 16 ) << ' ' << ( m_check_update_time & 0xffff ); // あぼーん情報 std::string str_abone_id = MISC::listtostr( m_list_abone_id ); std::string str_abone_name = MISC::listtostr( m_list_abone_name ); std::string str_abone_word = MISC::listtostr( m_list_abone_word ); std::string str_abone_regex = MISC::listtostr( m_list_abone_regex ); // レスあぼーん std::ostringstream ss_abone_res; if( !m_abone_reses.empty() ) { const auto end = m_abone_reses.end(); for( int i = 1; i <= m_number_load; ++i ) { if( m_abone_reses.find( i ) != end ) ss_abone_res << ' ' << i; } } // レスのブックマーク std::ostringstream ss_bookmark; if( !m_bookmarks.empty() ) { const auto end = m_bookmarks.end(); for( int i = 1; i <= m_number_load; ++i ) { if( m_bookmarks.find( i ) != end ) ss_bookmark << ' ' << i; } } // 書き込み std::ostringstream ss_posted; if( !m_posts.empty() ) { const auto end = m_posts.end(); for( int i = 1; i <= m_number_load; ++i ) { if( m_posts.find( i ) != end ) ss_posted << ' ' << i; } } std::ostringstream sstr; sstr << "subject = " << m_subject << std::endl << "org_host = " << m_org_host << std::endl << "load = " << m_number_load << std::endl << "seen = " << m_number_seen << std::endl << "modified = " << m_date_modified << std::endl << "access = " << get_access_time_str() << std::endl << "writetime = " << ss_write.str() << std::endl << "writename = " << m_write_name << std::endl << "writemail = " << m_write_mail << std::endl << "writefixname = " << m_write_fixname << std::endl << "writefixmail = " << m_write_fixmail << std::endl << "status = " << m_status << std::endl << "aboneid = " << str_abone_id << std::endl << "abonename = " << str_abone_name << std::endl << "bookmark = " << ss_bookmark.str() << std::endl << "aboneword = " << str_abone_word << std::endl << "aboneregex = " << str_abone_regex << std::endl << "abonetrp = " << m_abone_transparent << std::endl << "abonechain = " << m_abone_chain << std::endl << "aboneres = " << ss_abone_res.str() << std::endl << "bkmark_thread = " << m_bookmarked_thread << std::endl << "posted = " << ss_posted.str() << std::endl << "aboneage = " << m_abone_age << std::endl << "abonedefaultname = " << m_abone_default_name << std::endl << "abonenoid = " << m_abone_noid << std::endl << "checktime = " << ss_check.str() << std::endl << "aboneboard = " << m_abone_board << std::endl << "aboneglobal = " << m_abone_global << std::endl ; #ifdef _DEBUG std::cout << "ArticleBase::save_info file = " << m_path_article_ext_info << std::endl; std::cout << sstr.str() << std::endl; #endif CACHE::save_rawdata( m_path_article_ext_info, sstr.str() ); // 互換性のため save_navi2ch_info(); } // // navi2ch互換情報ファイル書き込み // // 互換性のため書き出すだけで実際にはこの中の情報は使わない // void ArticleBase::save_navi2ch_info() { if( empty() ) return; if( ! is_cached() ) return; if( m_path_article_info.empty() ) return; std::string name = "nil"; std::string hide = "nil"; std::string important = "nil"; std::string unfilter = "nil"; std::string mail = "nil"; std::string kako = "nil"; // 保存してあるinfoから扱ってない情報をコピー if( CACHE::file_exists( m_path_article_info ) == CACHE::EXIST_FILE ){ std::string str_info; CACHE::load_rawdata( m_path_article_info, str_info ); #ifdef _DEBUG std::cout << "str_info " << str_info << std::endl; #endif std::list< std::string > lists = MISC::get_elisp_lists( str_info ); std::list< std::string >::iterator it = lists.begin(); do{ ++it; if( it == lists.end() ) break; name = *( it++ ); if( it == lists.end() ) break; ++it; if( it == lists.end() ) break; hide = *( it++ ); if( it == lists.end() ) break; important = *( it++ ); if( it == lists.end() ) break; unfilter = *( it++ ); if( it == lists.end() ) break; mail = *( it++ ); if( it == lists.end() ) break; kako = *( it++ ); } while(0); } std::ostringstream sstr; sstr << "(" << "(number . " << m_number_load << ")" << " " << name << " " << "(time . \"" << m_date_modified << "\")" << " " << hide << " " << important << " " << unfilter << " " << mail << " " << kako << ")"; #ifdef _DEBUG std::cout << "ArticleBase::save_navi2ch_info file = " << m_path_article_info << std::endl; std::cout << sstr.str() << std::endl; #endif CACHE::save_rawdata( m_path_article_info, sstr.str() ); } jdim-0.7.0/src/dbtree/articlebase.h000066400000000000000000000463131417047150700171410ustar00rootroot00000000000000// ライセンス: GPL2 // // スレ情報のベースクラス // // 新スレ用の id は 0000000000(.各板別の拡張子) とする。 // #ifndef _ARTICLEBASE_H #define _ARTICLEBASE_H #include "skeleton/lockable.h" #include #include #include #include #include #include namespace DBTREE { class NodeTreeBase; struct NODE; class ArticleBase : public SKELETON::Lockable { // 情報ファイルのパス // デストラクタの中でCACHE::path_article_ext_info()を呼ぶとabortするので // ArticleBase::read_info()が呼ばれたときにパスを取得しておく std::string m_path_article_info; std::string m_path_article_ext_info; // m_nodetree は参照が外れたら自動でクリアされる std::unique_ptr m_nodetree; std::string m_url; // dat ファイルのURL std::string m_datbase; // ベースアドレス std::string m_id; // ID ( .datなどの拡張子付き (例) 1234567.dat ) std::string m_key; // ID から拡張子を取った物 std::string m_date_modified; // サーバのデータが更新された時間 std::time_t m_since_time{}; // スレが立った時刻 std::string m_since_date; // スレ立て月日( string型 ) int m_code; // HTTPコード std::string m_str_code; // HTTPコード(文字列) std::string m_ext_err; // HTTPコード以外のエラーメッセージ int m_status; // 状態 ( global.h で定義 ) // 移転する前にこのスレがあった旧ホスト名( 移転していないなら m_url に含まれているホスト名と同じ ) // 詳しくはコンストラクタの説明を参照せよ std::string m_org_host; std::string m_subject; // サブジェクト int m_number{}; // サーバ上にあるレスの数 int m_number_diff{}; // レス増分( subject.txt をロードした時の m_number の増分 ) int m_number_new{}; // 新着数( ロードした時の差分読み込み数) int m_number_load{}; // キャッシュにあるレスの数 int m_number_before_load{}; // ロード前のレスの数( m_number_new を計算するのに使う ) int m_number_seen{}; // どこまで読んだか int m_number_max{}; // 規定の最大レス数(0:未設定) std::time_t m_access_time{}; // ユーザが最後にロードした時間 std::string m_access_date; // ユーザが最後にロードした月日( string型 ) std::time_t m_check_update_time{}; // 最終更新チェック時間 std::time_t m_write_time{}; // 最終書き込み時間 std::string m_write_time_date; // 最終書き込み月日( string型 ) std::string m_write_name; // 書き込み時の名前 std::string m_write_mail; // 書き込み時のメアド bool m_write_fixname{}; // 書き込み時名前固定 bool m_write_fixmail{}; // 書き込み時メール固定 // あぼーん情報 std::list< std::string > m_list_abone_id; // あぼーんするID std::list< std::string > m_list_abone_name; // あぼーんする名前 std::list< std::string > m_list_abone_word; // あぼーんする文字列 std::list< std::string > m_list_abone_regex; // あぼーんする正規表現 std::unordered_set< int > m_abone_reses; // レスあぼーん情報 bool m_abone_transparent{}; // 透明あぼーん bool m_abone_chain{}; // 連鎖あぼーん bool m_abone_age{}; // age ているレスをあぼーん bool m_abone_default_name{}; // デフォルト名無しをあぼーん bool m_abone_noid{}; // ID無しをあぼーん bool m_abone_board; // 板レベルでのあぼーんを有効にする bool m_abone_global; // 全体レベルでのあぼーんを有効にする // 「スレ」がスレ一覧でブックマークされているか bool m_bookmarked_thread{}; // 「レス」のブックマーク std::unordered_set< int > m_bookmarks; // ブックマーク判定キャッシュ // 自分が書き込んだレスか std::unordered_set< int > m_posts; // HDDにキャッシュされているか bool m_cached; // 情報ファイルを読みこんだらtrueにして2度読みしないようにする bool m_read_info{}; // true ならunlock_impl()がコールバックされたときに情報保存 bool m_save_info{}; // 前スレのアドレス // スレが未取得で、この変数がemptyで無いとき download_dat()を呼び出すと // ロード終了時に次スレ移行チェックと前スレの情報のコピーをする std::string m_url_pre_article; // スレッド924か bool m_924{}; protected: void set_key( const std::string& key ){ m_key = key; } void set_since_time( const time_t since ){ m_since_time = since; } void set_is_924( const bool is924 ){ m_924 = is924; } // dat落ちしたスレをロードするか virtual bool is_load_olddat() const { return false; } public: ArticleBase( const std::string& datbase, const std::string& id, bool cached ); ~ArticleBase(); bool empty() const noexcept { return m_url.empty(); } const std::string& get_url() const { return m_url; } // ID がこのスレのものかどうか virtual bool equal( const std::string& datbase, const std::string& id ) const; // 移転があったときなどにdatファイルのベースアドレスを更新 void update_datbase( const std::string& datbase ); // 移転する前のオリジナルのURL std::string get_org_url() const; // 移転する前のオリジナルのホスト名 const std::string& get_org_host() const { return m_org_host; } void set_org_host( const std::string& host ); const std::string& get_id() const { return m_id; } const std::string& get_key() const { return m_key; } const std::string& get_subject() const { return m_subject; } int get_number() const noexcept { return m_number; } int get_number_diff() const noexcept { return m_number_diff; } int get_number_new() const noexcept { return m_number_new; } int get_number_load() const noexcept { return m_number_load; } int get_number_seen() const noexcept { return m_number_seen; } void set_number_max( const int number ){ m_number_max = number; } // スレ速度 int get_speed() const; // キャッシュにあるdatファイルのサイズ size_t get_lng_dat(); // nodetree の number 番のレスのヘッダノードのポインタを返す NODE* res_header( int number ); // number番のレスの発言者の名前 std::string get_name( int number ); // number番の名前の重複数( = 発言数 ) int get_num_name( int number ); // 指定した発言者の名前のレス番号をリストにして取得 std::list< int > get_res_name( const std::string& name ); // number番のレスの時刻を文字列で取得 // 内部で regex を使っているので遅い std::string get_time_str( int number ); // number番のレスの発言者ID( スレIDではなくて名前の横のID ) std::string get_id_name( int number ); // 指定した発言者ID の重複数( = 発言数 ) // (注) 下の get_num_id_name( int number )と違って検索するので遅い int get_num_id_name( const std::string& id ); // number番の発言者ID の重複数( = 発言数 ) int get_num_id_name( int number ); // 指定した発言者IDを持つレス番号をリストにして取得 std::list< int > get_res_id_name( const std::string& id_name ); // str_num で指定したレス番号をリストにして取得 // str_num は "from-to" の形式 (例) 3から10をセットしたいなら "3-10" // list_jointは出力で true のスレは前のスレに連結される (例) "3+4" なら 4が3に連結 std::list< int > get_res_str_num( const std::string& str_num, std::list< bool >& list_joint ); // ブックマークをつけたレス番号をリストにして取得 std::list< int > get_res_bm(); // 書き込みしたレス番号をリストにして取得 std::list< int > get_res_posted(); // 高参照レスをリストにして取得 std::list< int > get_highly_referenced_res(); // number番のレスを参照しているレス番号をリストにして取得 std::list< int > get_res_reference( const int number ); // res_num に含まれるレスを参照しているレス番号をリストにして取得 std::list< int > get_res_reference( const std::list< int >& res_num ); // URL を含むレス番号をリストにして取得 std::list< int > get_res_with_url(); // query を含むレス番号をリストにして取得 // mode_or == true なら OR抽出 std::list< int > get_res_query( const std::string& query, const bool mode_or ); // number番のレスの文字列を返す // ref == true なら先頭に ">" を付ける std::string get_res_str( int number, bool ref = false ); // 書き込み時の名前とメアド const std::string& get_write_name() const { return m_write_name; } void set_write_name( const std::string& str ){ m_save_info = true; m_write_name = str; } bool get_write_fixname() const noexcept { return m_write_fixname; } void set_write_fixname( bool set ){ m_save_info = true; m_write_fixname = set; } const std::string& get_write_mail() const { return m_write_mail; } void set_write_mail( const std::string& str ){ m_save_info = true; m_write_mail = str; } bool get_write_fixmail() const noexcept { return m_write_fixmail; } void set_write_fixmail( bool set ){ m_save_info = true; m_write_fixmail = set; } // 書き込みメッセージ作成 virtual std::string create_write_message( const std::string& name, const std::string& mail, const std::string& msg ) { return {}; } // bbscgi のURL virtual std::string url_bbscgi() const { return {}; } // subbbscgi のURL virtual std::string url_subbbscgi() const { return {}; } // 最終アクセス時間 std::string get_access_time_str() const; time_t get_access_time() const noexcept { return m_access_time; } // 秒 const std::string& get_access_date(); // string型 void reset_access_date(){ m_access_date = std::string(); } // 最終書き込み時間 time_t get_write_time() const noexcept { return m_write_time; } // 秒 const std::string& get_write_date(); // string型 void reset_write_date(){ m_write_time_date = std::string(); } // 書き込み数 int get_num_posted() const noexcept { return m_posts.size(); } // 自分の書き込みか bool is_posted( const int number ); // 自分の書き込みにレスしたか bool is_refer_posted( const int number ); // 書き込みマークセット void set_posted( const int number, const bool set ); // 書き込み履歴のリセット void clear_post_history(); // スレ立て時刻 time_t get_since_time() const noexcept { return m_since_time; }; const std::string& get_since_date(); void reset_since_date(){ m_since_date = std::string(); } // 更新時間 std::time_t get_time_modified() const; const std::string& get_date_modified() const { return m_date_modified; } void set_date_modified( const std::string& date ){ m_date_modified = date; } // スレが立ってからの経過時間( 時間 ) int get_hour() const; // http コード int get_code() const noexcept { return m_code; } const std::string& get_str_code() const { return m_str_code; } // エラーメッセージ const std::string& get_ext_err() const { return m_ext_err; } // DAT落ちかどうかなどの状態 ( global.hで定義 ) int get_status() const noexcept { return m_status; } void set_status( const int status ){ m_status = status; } void set_subject( const std::string& subject ); void set_number( const int number, const bool is_online ); void set_number_load( const int number_load ); void set_number_seen( const int number_seen ); void update_writetime(); // キャッシュ削除 // cache_only == true の時はキャッシュだけ削除してスレ情報は消さない virtual void delete_cache( const bool cache_only ); // キャッシュ保存 bool save_dat( const std::string& path_to ); // HDDにキャッシュされているか bool is_cached() const noexcept { return m_cached; } void set_cached( const bool set ){ m_cached = set; } // キャッシュがarticlebaseに読み込まれている(nodetree!=nullptr)か bool is_cache_read() const noexcept { return static_cast( m_nodetree ); } // キャッシュがあって、かつ新着の読み込みが可能 bool enable_load() const; // キャッシュはあるが規定のレス数を越えていて、かつ全てのレスが既読 bool is_finished() const; // あぼーん情報 const std::list& get_abone_list_id() const { return m_list_abone_id; } const std::list& get_abone_list_name() const { return m_list_abone_name; } const std::list& get_abone_list_word() const { return m_list_abone_word; } const std::list& get_abone_list_regex() const { return m_list_abone_regex; } const std::unordered_set< int >& get_abone_reses() const noexcept { return m_abone_reses; } // 透明 bool get_abone_transparent() const; // 連鎖 bool get_abone_chain() const; // ageあぼーん bool get_abone_age() const { return m_abone_age; } // デフォルト名無しあぼーん bool get_abone_default_name() const { return m_abone_default_name; } // ID無しあぼーん bool get_abone_noid() const noexcept { return m_abone_noid; } // 板レベルでのあぼーん bool get_abone_board() const noexcept { return m_abone_board; } // 全体レベルでのあぼーん bool get_abone_global() const { return m_abone_global; } // number番のレスがあぼーんされているか bool get_abone( int number ); // 全レスのあぼーん状態の更新 void update_abone(); // あぼーん状態のリセット(情報セットと状態更新を同時におこなう) void reset_abone( const std::list< std::string >& ids, const std::list< std::string >& names, const std::list< std::string >& words, const std::list< std::string >& regexs, const std::vector< char >& vec_abone_res, const bool transparent, const bool chain, const bool age, const bool default_name, const bool noid, const bool board, const bool global ); // あぼ〜ん状態更新(reset_abone()と違って各項目ごと個別におこなう) void add_abone_id( const std::string& id ); void add_abone_name( const std::string& name ); void add_abone_word( const std::string& word ); void set_abone_res( const int num_from, const int num_to, const bool set ); void set_abone_transparent( const bool set ); // 透明 void set_abone_chain( const bool set ); // 連鎖 void set_abone_age( const bool set ); // age void set_abone_default_name( const bool set ); // デフォルト名無し void set_abone_noid( const bool set ); // ID無し void set_abone_board( const bool set ); // 板レベルでのあぼーん void set_abone_global( const bool set ); // 全体レベルでのあぼーん // 「スレ」のブックマーク void set_bookmarked_thread( const bool bookmarked ); bool is_bookmarked_thread() const noexcept { return m_bookmarked_thread; } // 「レス」のブックマーク int get_num_bookmark() const noexcept { return m_bookmarks.size(); } bool is_bookmarked( const int number ); void set_bookmark( const int number, const bool set ); // 情報ファイル読み込み void read_info(); // 情報ファイル書き込み // キャッシュがあって、force = true の時は強制書き込み virtual void save_info( const bool force ); bool is_loading() const; // ロード中か bool is_checking_update() const; // 更新チェック中か // スレッドのロード停止 void stop_load(); // スレッドのロード開始 // DAT落ちの場合はロードしないので、強制的にリロードしたいときは reset_status() で // ステータスをリセットしてからロードする // check_update : true の時はHEADによる更新チェックをおこなう virtual void download_dat( const bool check_update ); // 前スレのアドレスを指定 // 前スレのアドレスをセットしてからdownload_dat()を呼び出すと // ロード終了時( slot_load_finished() )に次スレ移行チェックをする void set_url_pre_article( const std::string& url_pre_article ); // url_src で示されるスレの情報を引き継ぐ void copy_article_info( const std::string& url_src ); // スレッド924か bool is_924() const noexcept { return m_924; } private: // 更新チェック可能 virtual bool enable_check_update() const { return true; } // NodeTree作成 // もしNodeTreeが作られていなかったら作成 NodeTreeBase* get_nodetree(); virtual NodeTreeBase* create_nodetree(){ return nullptr; } void reset_status(); void slot_node_updated(); void slot_load_finished(); void unlock_impl() override; // お気に入りのアイコンとスレビューのタブのアイコンに更新マークを表示 // update == true の時に表示。falseなら戻す void show_updateicon( const bool update ); // navi2ch互換情報ファイル書き込み void save_navi2ch_info(); }; } #endif jdim-0.7.0/src/dbtree/articlehash.cpp000066400000000000000000000057201417047150700175020ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "articlehash.h" #include "articlebase.h" #include #include // atoi using namespace DBTREE; enum { HASH_TBLSIZE = 1024 }; ArticleHash::ArticleHash() : m_min_hash( HASH_TBLSIZE + 1 ) { } ArticleHash::~ArticleHash() { #ifdef _DEBUG if( size() ){ std::cout << "ArticleHash::~ArticleHash\n"; const size_t tblsize = m_table.size(); for( size_t hash = 0; hash < tblsize; ++hash ){ if( m_table[ hash ].size()) std::cout << hash << " : size = " << m_table[ hash ].size() << std::endl; } } #endif } int ArticleHash::get_hash( const std::string& id ) const { const size_t hash = atoi( id.c_str() ) & ( HASH_TBLSIZE -1 ); #ifdef _DEBUG std::cout << id << " -> " << hash << std::endl; #endif return hash; } ArticleBase* ArticleHash::insert( std::unique_ptr article ) { if( ! m_table.size() ){ m_table.resize( HASH_TBLSIZE ); } const size_t hash = get_hash( article->get_id() ); if( hash < m_min_hash ) m_min_hash = hash; ++m_size; m_table[ hash ].push_back( std::move( article ) ); return m_table[ hash ].back().get(); } ArticleBase* ArticleHash::find( const std::string& datbase, const std::string& id ) { if( ! m_table.size() ) return nullptr; const size_t hash = get_hash( id ); auto& block = m_table[ hash ]; auto it = std::find_if( block.begin(), block.end(), [&datbase, &id]( auto& a ) { return a->equal( datbase, id ); } ); if( it != block.end() ) return it->get(); return nullptr; } ArticleHashIterator ArticleHash::begin() { m_it_hash = m_min_hash; m_it_pos = 0; m_it_size = 0; return ArticleHashIterator{ this }; } ArticleBase* ArticleHash::it_get() { if( m_it_hash >= m_table.size() ) return nullptr; return m_table[ m_it_hash ][ m_it_pos ].get(); } void ArticleHash::it_inc() { #ifdef _DEBUG std::cout << "ArticleHash::it_inc hash = " << m_it_hash << " pos = " << m_it_pos; #endif ++m_it_size; if( m_it_size < size() ){ ++m_it_pos; if( m_it_pos == m_table[ m_it_hash ].size() ){ m_it_pos = 0; while( ! m_table[ ++m_it_hash ].size() ); } } #ifdef _DEBUG std::cout << " -> hash = " << m_it_hash << " tablesize = " << m_table[ m_it_hash ].size() << " pos = " << m_it_pos << " size = " << m_it_size << " / " << size() << std::endl; #endif } ///////////////////////////////////////////////////// ArticleHashIterator::ArticleHashIterator( ArticleHash* hashtable ) : m_hashtable( hashtable ) {} ArticleBase* ArticleHashIterator::operator * () { return m_hashtable->it_get(); } ArticleBase* ArticleHashIterator::operator ++ () { m_hashtable->it_inc(); return m_hashtable->it_get(); } bool ArticleHashIterator::operator != ( const size_t size ) { return ( m_hashtable->it_size() != size ); } jdim-0.7.0/src/dbtree/articlehash.h000066400000000000000000000033141417047150700171440ustar00rootroot00000000000000// ライセンス: GPL2 // // ArticleBaseのチェーン式ハッシュテーブルとイテレータ // // コンパイルが遅くなるのでテンプレートを使用しないでArticleBase専用にした #ifndef _ARTICLEHASH_H #define _ARTICLEHASH_H #include #include #include namespace DBTREE { class ArticleBase; class ArticleHashIterator; class ArticleHash { friend class ArticleHashIterator; size_t m_size{}; size_t m_min_hash; std::vector< std::vector> > m_table; // iterator 用変数 size_t m_it_hash{}; size_t m_it_pos{}; size_t m_it_size{}; public: ArticleHash(); ArticleHash( const ArticleHash& ) = delete; virtual ~ArticleHash(); ArticleHash& operator=( const ArticleHash& ) = delete; size_t size() const { return m_size; } ArticleBase* insert( std::unique_ptr article ); ArticleBase* find( const std::string& datbase, const std::string& id ); ArticleHashIterator begin(); size_t end() const { return size(); } private: int get_hash( const std::string& id ) const; // iterator 用関数 ArticleBase* it_get(); void it_inc(); size_t it_size() const { return m_it_size; } }; ///////////////////////////////////////////////////// class ArticleHashIterator { ArticleHash* m_hashtable; public: explicit ArticleHashIterator( ArticleHash* hashtable ); ArticleBase* operator * (); ArticleBase* operator ++ (); bool operator != ( const size_t size ); }; } #endif jdim-0.7.0/src/dbtree/articlejbbs.cpp000066400000000000000000000046661417047150700175070ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "articlejbbs.h" #include "nodetreejbbs.h" #include "interface.h" #include "jdlib/miscutil.h" #include "jdlib/misctime.h" #include using namespace DBTREE; ArticleJBBS::ArticleJBBS( const std::string& datbase, const std::string& _id, bool cached ) : ArticleBase( datbase, _id, cached ) { assert( ! get_id().empty() ); // JBBS の場合は拡張子が無いので key = id set_key( get_id() ); // key から since 計算 set_since_time( atol( get_key().c_str() ) ); } ArticleJBBS::~ArticleJBBS() noexcept = default; std::string ArticleJBBS::create_write_message( const std::string& name, const std::string& mail, const std::string& msg ) { if( msg.empty() ) return std::string(); std::string charset = DBTREE::board_charset( get_url() ); // DIR と BBS を分離する( ID = DIR/BBS ) std::string boardid = DBTREE::board_id( get_url() ); int i = boardid.find( '/' ); std::string dir = boardid.substr( 0, i ); std::string bbs = boardid.substr( i + 1 ); std::stringstream ss_post; ss_post.clear(); ss_post << "BBS=" << bbs << "&KEY=" << get_key() << "&DIR=" << dir << "&TIME=" << get_time_modified() << "&submit=" << MISC::charset_url_encode( "書き込む", charset ) << "&NAME=" << MISC::charset_url_encode( name, charset ) << "&MAIL=" << MISC::charset_url_encode( mail, charset ) << "&MESSAGE=" << MISC::charset_url_encode( msg, charset ); #ifdef _DEBUG std::cout << "Articlejbbs::create_write_message " << ss_post.str() << std::endl; #endif return ss_post.str(); } // // bbscgi(write.cgi) のURL // // (例) "http://jbbs.shitaraba.net/bbs/write.cgi/computer/123/1234567/" // // std::string ArticleJBBS::url_bbscgi() const { return DBTREE::url_bbscgibase( get_url() ) + DBTREE::board_id( get_url() ) + "/" + get_key() + "/"; } // // subbbscgi のURL // // (例) "http://jbbs.shitaraba.net/bbs/write.cgi/computer/123/1234567/" // std::string ArticleJBBS::url_subbbscgi() const { return DBTREE::url_subbbscgibase( get_url() ) + DBTREE::board_id( get_url() ) + "/" + get_key() + "/"; } NodeTreeBase* ArticleJBBS::create_nodetree() { #ifdef _DEBUG std::cout << "ArticleJBBS::create_nodetree " << get_url() << std::endl; #endif return new NodeTreeJBBS( get_url(), get_date_modified() ); } jdim-0.7.0/src/dbtree/articlejbbs.h000066400000000000000000000016511417047150700171430ustar00rootroot00000000000000// ライセンス: GPL2 // // JBBS型スレ情報クラス // #ifndef _ARTICLEJBBS_H #define _ARTICLEJBBS_H #include "articlebase.h" namespace DBTREE { class NodeTreeBase; class ArticleJBBS : public ArticleBase { public: ArticleJBBS( const std::string& datbase, const std::string& id, bool cached ); ~ArticleJBBS() noexcept; // 書き込みメッセージ変換 std::string create_write_message( const std::string& name, const std::string& mail, const std::string& msg ) override; // bbscgi のURL std::string url_bbscgi() const override; // subbbscgi のURL std::string url_subbbscgi() const override; private: // 更新チェック不可能 bool enable_check_update() const override { return false; } NodeTreeBase* create_nodetree() override; }; } #endif jdim-0.7.0/src/dbtree/articlelocal.cpp000066400000000000000000000015341417047150700176500ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "articlelocal.h" #include "nodetreelocal.h" using namespace DBTREE; ArticleLocal::ArticleLocal( const std::string& datbase, const std::string& id ) : Article2chCompati( datbase, id, true ) { #ifdef _DEBUG std::cout << "ArticleLocal::ArticleLocal datbase = " << datbase << ", id = " << id << ", url = " << get_url() << std::endl; #endif } ArticleLocal::~ArticleLocal() { #ifdef _DEBUG std::cout << "ArticleLocal::~ArticleLocal url = " << get_url() << std::endl; #endif } // ID がこのスレのものかどうか bool ArticleLocal::equal( const std::string& datbase, const std::string& id ) const { return ( get_url() == datbase + id ); } NodeTreeBase* ArticleLocal::create_nodetree() { return new NodeTreeLocal( get_url() ); } jdim-0.7.0/src/dbtree/articlelocal.h000066400000000000000000000016151417047150700173150ustar00rootroot00000000000000// ライセンス: GPL2 // // ローカルファイル用スレ情報クラス // #ifndef _ARTICLELOCAL_H #define _ARTICLELOCAL_H #include "article2chcompati.h" namespace DBTREE { class ArticleLocal : public Article2chCompati { public: ArticleLocal( const std::string& datbase, const std::string& id ); ~ArticleLocal(); // ID がこのスレのものかどうか bool equal( const std::string& datbase, const std::string& id ) const override; // キャッシュの削除をしない void delete_cache( const bool cache_only ) override {} // 情報ファイルを保存しない void save_info( const bool force ) override {} // ダウンロードしない void download_dat( const bool check_update ) override {} private: NodeTreeBase* create_nodetree() override; }; } #endif jdim-0.7.0/src/dbtree/articlemachi.cpp000066400000000000000000000046531417047150700176440ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "articlemachi.h" #include "nodetreemachi.h" #include "interface.h" #include "jdlib/miscutil.h" #include "jdlib/misctime.h" #include "config/globalconf.h" #include using namespace DBTREE; ArticleMachi::ArticleMachi( const std::string& datbase, const std::string& _id, bool cached ) : ArticleBase( datbase, _id, cached ) { assert( !get_id().empty() ); // Machi の場合は拡張子が無いので key = id set_key( get_id() ); // key から since 計算 set_since_time( atol( get_key().c_str() ) ); } ArticleMachi::~ArticleMachi() noexcept = default; std::string ArticleMachi::create_write_message( const std::string& name, const std::string& mail, const std::string& msg ) { if( msg.empty() ) return std::string(); std::string charset = DBTREE::board_charset( get_url() ); std::stringstream ss_post; ss_post.clear(); ss_post << "BBS=" << DBTREE::board_id( get_url() ) << "&KEY=" << get_key() << "&TIME=" << get_time_modified() << "&submit=" << MISC::charset_url_encode( "書き込む", charset ) << "&NAME=" << MISC::charset_url_encode( name, charset ) << "&MAIL=" << MISC::charset_url_encode( mail, charset ) << "&MESSAGE=" << MISC::charset_url_encode( msg, charset ); #ifdef _DEBUG std::cout << "ArticleMachi::create_write_message " << ss_post.str() << std::endl; #endif return ss_post.str(); } // // bbscgi のURL // // (例) "http://www.machi.to/bbs/write.cgi" // // std::string ArticleMachi::url_bbscgi() const { std::string cgibase = DBTREE::url_bbscgibase( get_url() ); if( ! cgibase.empty() ) cgibase.pop_back(); // 最後の '/' を除く return cgibase; } // // subbbscgi のURL // // (例) "http://www.machi.to/bbs/write.cgi" // std::string ArticleMachi::url_subbbscgi() const { std::string cgibase = DBTREE::url_subbbscgibase( get_url() ); if( ! cgibase.empty() ) cgibase.pop_back(); // 最後の '/' を除く return cgibase; } // offlawモードなら更新チェック可能 bool ArticleMachi::enable_check_update() const { return CONFIG::get_use_machi_offlaw(); } NodeTreeBase* ArticleMachi::create_nodetree() { #ifdef _DEBUG std::cout << "ArticleMachi::create_nodetree " << get_url() << std::endl; #endif return new NodeTreeMachi( get_url(), get_date_modified() ); } jdim-0.7.0/src/dbtree/articlemachi.h000066400000000000000000000016611417047150700173050ustar00rootroot00000000000000// ライセンス: GPL2 // // まち型スレ情報クラス // #ifndef _ARTICLEMACHI_H #define _ARTICLEMACHI_H #include "articlebase.h" namespace DBTREE { class NodeTreeBase; class ArticleMachi : public ArticleBase { public: ArticleMachi( const std::string& datbase, const std::string& id, bool cached ); ~ArticleMachi() noexcept; // 書き込みメッセージ変換 std::string create_write_message( const std::string& name, const std::string& mail, const std::string& msg ) override; // bbscgi のURL std::string url_bbscgi() const override; // subbbscgi のURL std::string url_subbbscgi() const override; private: // offlawモードなら更新チェック可能 bool enable_check_update() const override; NodeTreeBase* create_nodetree() override; }; } #endif jdim-0.7.0/src/dbtree/board2ch.cpp000066400000000000000000000212771417047150700167040ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "board2ch.h" #include "article2ch.h" #include "articlehash.h" #include "frontloader.h" #include "config/globalconf.h" #include "jdlib/miscutil.h" #include "jdlib/misctime.h" #include "login2ch.h" #include "loginbe.h" #include using namespace DBTREE; Board2ch::Board2ch( const std::string& root, const std::string& path_board, const std::string& name ) : Board2chCompati( root, path_board, name, std::string() ) { #ifdef _DEBUG std::cout << "Board2ch::Board2ch\n"; #endif } Board2ch::~Board2ch() noexcept { if( m_frontloader ) { m_frontloader->terminate_load(); } } // ユーザエージェント // ダウンロード用 const std::string& Board2ch::get_agent() const { if( get_board_agent().empty() ) { return CONFIG::get_agent_for2ch(); } else { return get_board_agent(); } } // 書き込み用 const std::string& Board2ch::get_agent_w() const { if( get_board_agent().empty() ) { return CONFIG::get_agent_for2ch(); } else { return get_board_agent(); } } // 読み込み用プロキシ std::string Board2ch::get_proxy_host() const { const int mode = get_mode_local_proxy(); if( mode == DBTREE::PROXY_GLOBAL ){ if( CONFIG::get_use_proxy_for2ch() ) return CONFIG::get_proxy_for2ch(); } else if( mode == DBTREE::PROXY_LOCAL ) return get_local_proxy(); return std::string(); } int Board2ch::get_proxy_port() const { const int mode = get_mode_local_proxy(); if( mode == DBTREE::PROXY_GLOBAL ) return CONFIG::get_proxy_port_for2ch(); else if( mode == DBTREE::PROXY_LOCAL ) return get_local_proxy_port(); return 0; } std::string Board2ch::get_proxy_basicauth() const { const int mode = get_mode_local_proxy(); if( mode == DBTREE::PROXY_GLOBAL ) return CONFIG::get_proxy_basicauth_for2ch(); else if( mode == DBTREE::PROXY_LOCAL ) return get_local_proxy_basicauth(); return std::string(); } // 書き込み用プロキシ std::string Board2ch::get_proxy_host_w() const { const int mode = get_mode_local_proxy_w(); if( mode == DBTREE::PROXY_GLOBAL ){ if( CONFIG::get_use_proxy_for2ch_w() ) return CONFIG::get_proxy_for2ch_w(); } else if( mode == DBTREE::PROXY_LOCAL ) return get_local_proxy_w(); return std::string(); } int Board2ch::get_proxy_port_w() const { const int mode = get_mode_local_proxy_w(); if( mode == DBTREE::PROXY_GLOBAL ) return CONFIG::get_proxy_port_for2ch_w(); else if( mode == DBTREE::PROXY_LOCAL ) return get_local_proxy_port_w(); return 0; } std::string Board2ch::get_proxy_basicauth_w() const { const int mode = get_mode_local_proxy_w(); if( mode == DBTREE::PROXY_GLOBAL ) return CONFIG::get_proxy_basicauth_for2ch_w(); else if( mode == DBTREE::PROXY_LOCAL ) return get_local_proxy_basicauth_w(); return std::string(); } // BE 用クッキー作成 void Board2ch::set_cookie_for_be( std::string& cookie ) const { // BE ログイン中 if( CORE::get_loginbe()->login_now() ) { if( ! cookie.empty() ) cookie += "; "; cookie += "DMDM=" + CORE::get_loginbe()->get_sessionid() + "; MDMD=" + CORE::get_loginbe()->get_sessiondata(); } } // // 読み込み用クッキー作成 // // プロキシの2ch読み込み用設定がoffのとき // またはプロキシにクッキーを送る設定のときは対象サイトにcookieを送信する // std::string Board2ch::cookie_for_request() const { std::string cookie; if( ! CONFIG::get_use_proxy_for2ch() || CONFIG::get_send_cookie_to_proxy_for2ch() ) { cookie = cookie_by_host(); if( cookie.empty() ) cookie = get_hap(); } set_cookie_for_be( cookie ); #ifdef _DEBUG std::cout << "Board2ch::cookie_for_request cookie = " << cookie << std::endl; #endif return cookie; } // // 書き込み用クッキー作成 // // プロキシの2ch書き込み用設定がoffのとき // またはプロキシにクッキーを送る設定のときは対象サイトにcookieを送信する // std::string Board2ch::cookie_for_post() const { std::string cookie; if( ! CONFIG::get_use_proxy_for2ch_w() || CONFIG::get_send_cookie_to_proxy_for2ch_w() ) { cookie = cookie_by_host(); if( cookie.empty() ) cookie = get_hap(); } set_cookie_for_be( cookie ); #ifdef _DEBUG std::cout << "Board2ch::cookie_for_post cookie = " << cookie << std::endl; #endif return cookie; } std::string Board2ch::get_write_referer( const std::string& url ) { return url_readcgi( url, 0, 0 ); } // フロントページのダウンロード void Board2ch::download_front() { if( ! m_frontloader ) m_frontloader = std::make_unique( url_boardbase() ); m_frontloader->reset(); m_frontloader->download_text(); } // 新スレ作成時の書き込みメッセージ作成 std::string Board2ch::create_newarticle_message( const std::string& subject, const std::string& name, const std::string& mail, const std::string& msg ) { if( subject.empty() ) return std::string(); if( msg.empty() ) return std::string(); if( ! m_frontloader || m_frontloader->get_data().empty() ) { // フロントページを読み込んでない場合はメッセージ作成を中断してダウンロードする set_keyword_for_newarticle( std::string{} ); Board2ch::download_front(); return {}; } std::stringstream ss_post; ss_post << "submit=" << MISC::charset_url_encode( "新規スレッド作成", get_charset() ) << "&subject=" << MISC::charset_url_encode( subject, get_charset() ) << "&FROM=" << MISC::charset_url_encode( name, get_charset() ) << "&mail=" << MISC::charset_url_encode( mail, get_charset() ) << "&MESSAGE=" << MISC::charset_url_encode( msg, get_charset() ) << "&bbs=" << get_id() << "&time=" << m_frontloader->get_time_modified(); // キーワード const std::string keyword = get_keyword_for_newarticle(); if( ! keyword.empty() ) ss_post << "&" << keyword; // 2chログイン中 // sidを送る if( CORE::get_login2ch()->login_now() ){ std::string sid = CORE::get_login2ch()->get_sessionid(); ss_post << "&sid=" << MISC::url_encode( sid.c_str(), sid.length() ); } #ifdef _DEBUG std::cout << "Board2ch::create_newarticle_message " << ss_post.str() << std::endl; #endif // スレ立てのメッセージを作成したらキーワードとフロントページの読み込み状況はリセットする set_keyword_for_newarticle( std::string{} ); m_frontloader->reset(); // call SKELETON::TextLoader::reset() return ss_post.str(); } // // 新スレ作成時のbbscgi のURL // // (例) "http://www.hoge2ch.net/test/bbs.cgi" // // std::string Board2ch::url_bbscgi_new() const { return Board2chCompati::url_bbscgi_new(); } // // 新スレ作成時のsubbbscgi のURL // // (例) "http://www.hoge2ch.net/test/subbbs.cgi" // std::string Board2ch::url_subbbscgi_new() const { return Board2chCompati::url_subbbscgi_new(); } // // 新しくArticleBaseクラスを追加してそのポインタを返す // // cached : HDD にキャッシュがあるならtrue // ArticleBase* Board2ch::append_article( const std::string& datbase, const std::string& id, const bool cached ) { if( empty() ) return get_article_null(); ArticleBase* article = insert( std::make_unique( datbase, id, cached ) ); if( article ){ // 最大レス数セット article->set_number_max( get_number_max_res() ); } else return get_article_null(); return article; } // 2chのクッキー std::string Board2ch::get_hap() const { if( ! CONFIG::get_use_cookie_hap() ) return std::string(); if( get_root().find( ".bbspink.com" ) != std::string::npos ) return CONFIG::get_cookie_hap_bbspink(); return CONFIG::get_cookie_hap(); } void Board2ch::set_hap( const std::string& hap ) { if( ! CONFIG::get_use_cookie_hap() ) return; if( get_root().find( ".bbspink.com" ) != std::string::npos ) CONFIG::set_cookie_hap_bbspink( hap ); else CONFIG::set_cookie_hap( hap ); } // // 2chのクッキーの更新 // void Board2ch::update_hap() { if( ! CONFIG::get_use_cookie_hap() ) return; const std::string new_cookie = Board2chCompati::cookie_for_request(); if( ! new_cookie.empty() ) { #ifdef _DEBUG const std::string old_cookie = get_hap(); #endif set_hap( new_cookie ); #ifdef _DEBUG std::cout << "Board2ch::update_hap old = " << old_cookie << std::endl; std::cout << "Board2ch::update_hap new = " << new_cookie << std::endl; #endif } } jdim-0.7.0/src/dbtree/board2ch.h000066400000000000000000000054001417047150700163370ustar00rootroot00000000000000// ライセンス: GPL2 // // 2ch // #ifndef _BOARD2CH_H #define _BOARD2CH_H #include "board2chcompati.h" #include namespace DBTREE { class FrontLoader; enum { DEFAULT_NUMBER_MAX_2CH = 1000, // デフォルト最大レス数 DEFAULT_MAX_DAT_LNG = 512 // デフォルトのdatの最大サイズ(Kバイト) }; class Board2ch : public Board2chCompati { std::unique_ptr m_frontloader; public: Board2ch( const std::string& root, const std::string& path_board,const std::string& name ); ~Board2ch() noexcept; // ユーザーエージェント const std::string& get_agent() const override; // ダウンロード用 const std::string& get_agent_w() const override; // 書き込み用 // 読み込み用プロキシ std::string get_proxy_host() const override; int get_proxy_port() const override; std::string get_proxy_basicauth() const override; // 書き込み用プロキシ std::string get_proxy_host_w() const override; int get_proxy_port_w() const override; std::string get_proxy_basicauth_w() const override; // 読み込み用クッキー std::string cookie_for_request() const override; // 書き込み用クッキー std::string cookie_for_post() const override; // 書き込み時のリファラ std::string get_write_referer( const std::string& url ) override; // フロントページのダウンロード void download_front() override; // 新スレ作成用のメッセージ変換 std::string create_newarticle_message( const std::string& subject, const std::string& name, const std::string& mail, const std::string& msg ) override; // 新スレ作成用のbbscgi のURL std::string url_bbscgi_new() const override; // 新スレ作成用のsubbbscgi のURL std::string url_subbbscgi_new() const override; // datの最大サイズ(Kバイト) int get_max_dat_lng() const override { return DEFAULT_MAX_DAT_LNG; } protected: // クッキー std::string get_hap() const override; void set_hap( const std::string& hap ) override; // クッキーの更新 (クッキーをセットした時に実行) void update_hap() override; private: // デフォルト最大レス数 int get_default_number_max_res() const override { return DEFAULT_NUMBER_MAX_2CH; } ArticleBase* append_article( const std::string& datbase, const std::string& id, const bool cached ) override; void set_cookie_for_be( std::string& cookie ) const; }; } #endif jdim-0.7.0/src/dbtree/board2chcompati.cpp000066400000000000000000000377121417047150700202620ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "board2chcompati.h" #include "article2chcompati.h" #include "articlehash.h" #include "settingloader.h" #include "ruleloader.h" #include "jdlib/miscutil.h" #include "jdlib/miscmsg.h" #include "jdlib/jdregex.h" #include "config/globalconf.h" #include "httpcode.h" #include "global.h" #include #include #include #include using namespace DBTREE; #ifndef MAX #define MAX( a, b ) ( a > b ? a : b ) #endif #ifndef MIN #define MIN( a, b ) ( a < b ? a : b ) #endif Board2chCompati::Board2chCompati( const std::string& root, const std::string& path_board, const std::string& name, const std::string& basicauth ) : BoardBase( root, path_board, name ) { set_path_dat( "/dat" ); set_path_readcgi( "/test/read.cgi" ); set_path_bbscgi( "/test/bbs.cgi" ); set_path_subbbscgi( "/test/subbbs.cgi" ); set_subjecttxt( "subject.txt" ); set_ext( ".dat" ); set_id( path_board.substr( 1 ) ); // 先頭の '/' を除く set_charset( "MS932" ); BoardBase::set_basicauth( basicauth ); } Board2chCompati::~Board2chCompati() { if( m_settingloader ){ m_settingloader->terminate_load(); } if( m_ruleloader ){ m_ruleloader->terminate_load(); } } // // キャッシュのファイル名が正しいか // bool Board2chCompati::is_valid( const std::string& filename ) const { const std::string& ext = get_ext(); if( filename.size() <= ext.size() ) return false; if( filename.compare( filename.size() - ext.size(), ext.size(), ext ) != 0 ) return false; return std::all_of( filename.cbegin(), std::prev( filename.cend(), ext.size() ), []( char c ) { return '0' <= c && c <= '9'; } ); } std::string Board2chCompati::analyze_keyword_impl( const std::string& html, bool full_parse ) { std::string keyword; // form要素から action属性(送信先URLのパス) を取得する const std::string path = MISC::parse_html_form_action( html ); // action属性が見つかったら m_path_subbbscgi に設定する if( ! path.empty() ) { #ifdef _DEBUG std::cout << "Board2chCompati::analyze_keyword_impl update subbbscgi path = " << path << std::endl; #endif set_path_subbbscgi( path ); } std::vector data = MISC::parse_html_form_data( html ); for( MISC::FormDatum& d : data ) { const std::string lowname = MISC::tolower_str( d.name ); if( ! full_parse ) { // 除外する name の判定 // 2ch の仕様が変わったら項目を追加すること if( lowname == "subject" || lowname == "from" || lowname == "mail" || lowname == "message" || lowname == "bbs" || lowname == "time" || lowname == "key" || lowname == "submit" ) continue; } if( lowname == "message" ) { // アンカー記号(>>)などがエスケープされる(>>)ため // 一度HTMLエスケープされた文字をデコードする d.value = MISC::html_unescape( d.value ); } // キーワード取得 if( ! keyword.empty() ) keyword.push_back( '&' ); keyword.append( MISC::charset_url_encode( d.name, get_charset() ) ); keyword.push_back( '=' ); keyword.append( MISC::charset_url_encode( d.value, get_charset() ) ); } #ifdef _DEBUG std::cout << "Board2chCompati::analyze_keyword_impl form data = " << keyword << std::endl; #endif return keyword; } // 書き込み時に必要なキーワード( hana=mogera や suka=pontan など )を // 確認画面のhtmlから解析する void Board2chCompati::analyze_keyword_for_write( const std::string& html ) { #ifdef _DEBUG std::cout << "Board2chCompati::analyze_keyword_for_write\n"; std::cout << html << std::endl << "--------------------\n"; #endif constexpr bool full_parse{ false }; std::string keyword = analyze_keyword_impl( html, full_parse ); set_keyword_for_write( keyword ); } // スレ立て時に必要なキーワードをフロントページのhtmlから解析する void Board2chCompati::analyze_keyword_for_newarticle( const std::string& html ) { #ifdef _DEBUG std::cout << "Board2chCompati::analyze_keyword_for_newarticle\n"; std::cout << html << std::endl << "--------------------\n"; #endif // XXX: スレ立てフォームはページの最後にあると決め打ちしている std::size_t i = html.rfind( "( datbase, id, cached ) ); if( article ){ // 最大レス数セット article->set_number_max( get_number_max_res() ); } else return get_article_null(); return article; } // // subject.txt から Aarticle のリストにアイテムを追加・更新 // void Board2chCompati::parse_subject( const char* str_subject_txt ) { #ifdef _DEBUG std::cout << "Board2chCompati::parse_subject\n"; #endif const char* pos = str_subject_txt; while( *pos != '\0' ){ const char* str_id_dat; int lng_id_dat = 0; const char* str_subject; int lng_subject = 0; while( *pos == ' ' ) ++pos; // datのID取得 str_id_dat = pos; while( *pos != ' ' && *pos != '<' && *pos != '\0' && *pos != '\n' ) { ++pos; ++lng_id_dat; } // 壊れてる if( *pos == '\0' ){ MISC::ERRMSG( "subject.txt is broken" ); break; } if( *pos == '\n' ) { ++pos; continue; } while( *pos != '\0' && *pos != '<' ) ++pos; if( *pos != '\0' ) ++pos; if( *pos != '>' ){ MISC::ERRMSG( "subject.txt is broken" ); break; } // subject取得 bool exist_amp = false; ++pos; str_subject = pos; while( *pos != '\0' && *pos != '\n' ){ if( *pos == '&' ) exist_amp = true; ++pos; } --pos; while( *pos != '(' && *pos != '\n' && pos != str_subject ) --pos; // 壊れてる if( *pos == '\n' || pos == str_subject ){ MISC::ERRMSG( "subject.txt is broken" ); break; } lng_subject = ( int )( pos - str_subject ); // レス数取得 (符号付き32bit整数より大きいと未定義) ++pos; std::string str_num; while( '0' <= *pos && *pos <= '9' ) str_num.push_back( *( pos++ ) ); // 壊れてる if( str_num.empty() ){ MISC::ERRMSG( "subject.txt is broken (res)" ); break; } if( *pos == '\0' ) break; if( *pos == '\n' ) { ++pos; continue; } ++pos; // id, subject, number 取得 ARTICLE_INFO artinfo; artinfo.id.assign( str_id_dat, lng_id_dat ); if( str_subject[ lng_subject-1 ] == ' ' ){ lng_subject--; // 2chのsubject.txtは()の前に空白が一つ入る } artinfo.subject.assign( str_subject, lng_subject ); if( exist_amp ){ artinfo.subject = MISC::replace_str( artinfo.subject, "<", "<" ); artinfo.subject = MISC::replace_str( artinfo.subject, ">", ">" ); } const auto num = std::atoi( str_num.c_str() ); artinfo.number = ( num < CONFIG::get_max_resnumber() ) ? num : CONFIG::get_max_resnumber(); get_list_artinfo().push_back( artinfo ); #ifdef _DEBUG std::cout << "pos = " << ( pos - str_subject_txt ) << " lng = " << lng_subject << " id = " << artinfo.id << " num = " << artinfo.number; std::cout << " : " << artinfo.subject << std::endl; #endif } } void Board2chCompati::regist_article( const bool is_online ) { if( ! get_list_artinfo().size() ) return; #ifdef _DEBUG std::cout << "Board2chCompati::regist_article size = " << get_list_artinfo().size() << std::endl; #endif const std::string datbase = url_datbase(); for( const ARTICLE_INFO& artinfo : get_list_artinfo() ) { // DBに登録されてるならarticle クラスの情報更新 ArticleBase* article = get_article( datbase, artinfo.id ); // DBにないなら新規に article クラスをDBに登録 // // なお BoardBase::receive_finish() のなかで append_all_article_in_cache() が既に呼び出されているため // DBに無いということはキャッシュに無いということ。よって append_article()の呼出に cached = false を指定する if( article->empty() ) article = append_article( datbase, artinfo.id, false // キャッシュ無し ); // スレ情報更新 if( article ){ // ステータスをDAT落ち状態から通常状態に変更 int status = article->get_status(); status |= STATUS_NORMAL; status &= ~STATUS_OLD; article->set_status( status ); // 情報ファイル読み込み article->read_info(); // 情報ファイルが無い場合もあるのでsubject.txtから取得したサブジェクト、レス数を指定しておく article->set_subject( artinfo.subject ); article->set_number( artinfo.number, is_online ); // 情報ファイル読み込み後にステータスが変わることがあるので、もう一度 // ステータスをDAT落ち状態から通常状態に変更 status = article->get_status(); status |= STATUS_NORMAL; status &= ~STATUS_OLD; article->set_status( status ); // boardビューに表示するリスト更新 if( ! BoardBase::is_abone_thread( article ) ) get_list_subject().push_back( article ); } } } std::string Board2chCompati::localrule() const { if( m_ruleloader ){ if( m_ruleloader->is_loading() ) return "ロード中です"; else if( m_ruleloader->get_code() == HTTP_OK || m_ruleloader->get_code() == HTTP_REDIRECT || m_ruleloader->get_code() == HTTP_MOVED_PERM ){ if( m_ruleloader->get_data().empty() ) return "ローカルルールはありません"; else return m_ruleloader->get_data(); } else return "ロードに失敗しました : " + m_ruleloader->get_str_code(); } return BoardBase::localrule(); } // // SETTING.TXTのURL // // (例) "http://hoge.2ch.net/hogeboard/SETTING.TXT" // std::string Board2chCompati::url_settingtxt() const { return url_boardbase() + DBTREE::kSettingTxt; } std::string Board2chCompati::settingtxt() const { if( m_settingloader ){ if( m_settingloader->is_loading() ) return "ロード中です"; else if( m_settingloader->get_code() == HTTP_OK || m_settingloader->get_code() == HTTP_REDIRECT || m_settingloader->get_code() == HTTP_MOVED_PERM ){ if( m_settingloader->get_data().empty() ) return "SETTING.TXTはありません"; else return m_settingloader->get_data(); } else return "ロードに失敗しました : " + m_settingloader->get_str_code(); } return BoardBase::settingtxt(); } std::string Board2chCompati::default_noname() const { if( m_settingloader && m_settingloader->get_code() == HTTP_OK ) return m_settingloader->default_noname(); return BoardBase::default_noname(); } int Board2chCompati::line_number() const { if( m_settingloader && m_settingloader->get_code() == HTTP_OK ) return m_settingloader->line_number(); return BoardBase::line_number(); } int Board2chCompati::message_count() const { if( m_settingloader && m_settingloader->get_code() == HTTP_OK ) return m_settingloader->message_count(); return BoardBase::message_count(); } std::string Board2chCompati::get_unicode() const { if( m_settingloader && m_settingloader->get_code() == HTTP_OK ) return m_settingloader->get_unicode(); return BoardBase::get_unicode(); } // // ローカルルールとSETTING.TXTをキャッシュから読み込む // // BoardBase::read_info()で呼び出す // void Board2chCompati::load_rule_setting() { #ifdef _DEBUG std::cout << "Board2chCompati::load_rule_setting\n"; #endif if( ! m_ruleloader ) m_ruleloader = std::make_unique( url_boardbase() ); m_ruleloader->load_text(); if( ! m_settingloader ) m_settingloader = std::make_unique( url_boardbase() ); m_settingloader->load_text(); } // // ローカルルールとSETTING.TXTをサーバからダウンロード // // 読み込むタイミングはsubject.txtを読み終わった直後( BoardBase::receive_finish() ) // void Board2chCompati::download_rule_setting() { #ifdef _DEBUG std::cout << "Board2chCompati::download_rule_setting\n"; #endif if( ! m_ruleloader ) m_ruleloader = std::make_unique( url_boardbase() ); m_ruleloader->download_text(); if( ! m_settingloader ) m_settingloader = std::make_unique( url_boardbase() ); m_settingloader->download_text(); } // // レス数以下であぼーん(グローバル) // int Board2chCompati::get_abone_low_number_global() const { return CONFIG::get_abone_low_number_thread(); } // // レス数以上であぼーん(グローバル) // int Board2chCompati::get_abone_high_number_global() const { return CONFIG::get_abone_high_number_thread(); } jdim-0.7.0/src/dbtree/board2chcompati.h000066400000000000000000000053761417047150700177300ustar00rootroot00000000000000// ライセンス: GPL2 // // 2ch 互換型板 // #ifndef _BOARD2CHCOMPATI_H #define _BOARD2CHCOMPATI_H #include "boardbase.h" #include namespace DBTREE { class SettingLoader; class RuleLoader; class Board2chCompati : public BoardBase { std::unique_ptr m_settingloader; std::unique_ptr m_ruleloader; public: Board2chCompati( const std::string& root, const std::string& path_board, const std::string& name, const std::string& basicauth ); ~Board2chCompati(); // 書き込み時に必要なキーワード( hana=mogera や suka=pontan など )を // 確認画面のhtmlから解析する void analyze_keyword_for_write( const std::string& html ) override; // スレ立て時に必要なキーワードをフロントページのhtmlから解析する void analyze_keyword_for_newarticle( const std::string& html ) override; // 確認画面のHTMLから書き込み、スレ立て時に使うフォームデータを取得する std::string parse_form_data( const std::string& html ) override; // 新スレ作成用のメッセージ変換 std::string create_newarticle_message( const std::string& subject, const std::string& name, const std::string& mail, const std::string& msg ) override; // 新スレ作成用のbbscgi のURL std::string url_bbscgi_new() const override; // 新スレ作成用のsubbbscgi のURL std::string url_subbbscgi_new() const override; // ローカルルール std::string localrule() const override; // SETTING.TXT のURL std::string url_settingtxt() const override; // SETTING.TXT std::string settingtxt() const override; std::string default_noname() const override; int line_number() const override; int message_count() const override; std::string get_unicode() const override; private: bool is_valid( const std::string& filename ) const override; ArticleBase* append_article( const std::string& datbase, const std::string& id, const bool cached ) override; void parse_subject( const char* str_subject_txt ) override; void regist_article( const bool is_online ) override; void load_rule_setting() override; void download_rule_setting() override; // レス数であぼーん(グローバル) int get_abone_low_number_global() const override; int get_abone_high_number_global() const override; // htmlからキーワードを解析する std::string analyze_keyword_impl( const std::string& html, bool full_parse ); }; } #endif jdim-0.7.0/src/dbtree/boardbase.cpp000066400000000000000000002164311417047150700171400ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG //#define _TEST_CACHE #include "jddebug.h" #include "boardbase.h" #include "articlebase.h" #include "articlehash.h" #include "interface.h" #include "skeleton/msgdiag.h" #include "jdlib/cookiemanager.h" #include "jdlib/jdiconv.h" #include "jdlib/jdregex.h" #include "jdlib/miscutil.h" #include "jdlib/miscmsg.h" #include "jdlib/loaderdata.h" #include "jdlib/confloader.h" #include "global.h" #include "httpcode.h" #include "command.h" #include "cache.h" #include "config/globalconf.h" #include "session.h" #include "boardcolumnsid.h" #include #include #include #include #ifdef _TEST_CACHE int cache_hit_art = 0; int cache_nohit_art = 0; #endif enum { SIZE_OF_RAWDATA = 2 * 1024 * 1024 }; using namespace DBTREE; BoardBase::BoardBase( const std::string& root, const std::string& path_board, const std::string& name ) : SKELETON::Loadable() , m_status( STATUS_UNKNOWN ) , m_view_sort_column( -1 ) , m_view_sort_mode( -1 ) , m_view_sort_pre_column( -1 ) , m_view_sort_pre_mode( -1 ) , m_root( root ) , m_path_board( path_board ) , m_name( name ) { clear_load_data(); // 板情報はクラスが作られた時点ではまだ読まない // BoardBase::read_info() の説明を見ること } // // デストラクタで子ArticleBaseクラスを全部削除 // BoardBase::~BoardBase() { #ifdef _DEBUG if( m_hash_article.size() ) std::cout << "BoardBase::~BoardBase : " << url_boardbase() << std::endl; #endif clear(); #ifdef _TEST_CACHE if( m_hash_article.size() ){ std::cout << "article cache\n" << "hit = " << cache_hit_art << std::endl << "nohit = " << cache_nohit_art << std::endl << "hit/total*100 = " << (double)(cache_hit_art)/(cache_hit_art+cache_nohit_art)*100. << std::endl; } #endif } ArticleBase* BoardBase::get_article_null() { if( ! m_article_null ) m_article_null = std::make_unique( "", "", false ); return m_article_null.get(); } bool BoardBase::empty() const { return m_root.empty(); } // // url がこの板のものかどうか // bool BoardBase::equal( const std::string& url ) const { if( url.rfind( get_root(), 0 ) == 0 && url.find( get_path_board() + "/", get_root().size() ) != std::string::npos ) return true; return false; } // // 名前を変更 // void BoardBase::update_name( const std::string& name ) { if( m_name != name ){ m_name = name; // 表示中のviewの板名表示更新 CORE::core_set_command( "update_boardname", url_boardbase() ); } } // ユーザエージェント // ダウンロード用 const std::string& BoardBase::get_agent() const { if( get_board_agent().empty() ) { return CONFIG::get_agent_for_data(); } else { return get_board_agent(); } } // 書き込み用 const std::string& BoardBase::get_agent_w() const { return get_agent(); } // 読み込み用プロキシ std::string BoardBase::get_proxy_host() const { const int mode = get_mode_local_proxy(); if( mode == DBTREE::PROXY_GLOBAL ){ if( CONFIG::get_use_proxy_for_data() ) return CONFIG::get_proxy_for_data(); } else if( mode == DBTREE::PROXY_LOCAL ) return get_local_proxy(); return std::string(); } int BoardBase::get_proxy_port() const { const int mode = get_mode_local_proxy(); if( mode == DBTREE::PROXY_GLOBAL ) return CONFIG::get_proxy_port_for_data(); else if( mode == DBTREE::PROXY_LOCAL ) return get_local_proxy_port(); return 0; } std::string BoardBase::get_proxy_basicauth() const { const int mode = get_mode_local_proxy(); if( mode == DBTREE::PROXY_GLOBAL ) return CONFIG::get_proxy_basicauth_for_data(); else if( mode == DBTREE::PROXY_LOCAL ) return get_local_proxy_basicauth(); return std::string(); } // 書き込み用プロキシ std::string BoardBase::get_proxy_host_w() const { const int mode = get_mode_local_proxy_w(); if( mode == DBTREE::PROXY_GLOBAL ){ if( CONFIG::get_use_proxy_for_data() ) return CONFIG::get_proxy_for_data(); } else if( mode == DBTREE::PROXY_LOCAL ) return get_local_proxy_w(); return std::string(); } int BoardBase::get_proxy_port_w() const { const int mode = get_mode_local_proxy_w(); if( mode == DBTREE::PROXY_GLOBAL ) return CONFIG::get_proxy_port_for_data(); else if( mode == DBTREE::PROXY_LOCAL ) return get_local_proxy_port_w(); return 0; } std::string BoardBase::get_proxy_basicauth_w() const { const int mode = get_mode_local_proxy_w(); if( mode == DBTREE::PROXY_GLOBAL ) return CONFIG::get_proxy_basicauth_for_data(); else if( mode == DBTREE::PROXY_LOCAL ) return get_local_proxy_basicauth_w(); return std::string(); } // ローカルルール std::string BoardBase::localrule() const { return "利用できません"; } // setting.txt std::string BoardBase::settingtxt() const { return "利用できません"; } // デフォルトの名無し名 std::string BoardBase::default_noname() const { return "???"; } // 最大改行数/2 int BoardBase::line_number() const { return 0; } // 最大書き込みバイト数 int BoardBase::message_count() const { return 0; } // 特殊文字書き込み可能か( pass なら可能、 change なら不可 ) std::string BoardBase::get_unicode() const { return {}; } // 板のホストを指定してクッキーを取得 std::string BoardBase::cookie_by_host() const { const JDLIB::CookieManager* cookie_manager = JDLIB::get_cookie_manager(); const std::string hostname = MISC::get_hostname( get_root(), false ); return cookie_manager->get_cookie_by_host( hostname ); } // // 読み込み用クッキー作成 // // プロキシの読み込み用設定がoffのとき // またはプロキシにクッキーを送る設定がonのときはサイトにcookieを送信する // std::string BoardBase::cookie_for_request() const { std::string cookie; if( ! CONFIG::get_use_proxy_for_data() || CONFIG::get_send_cookie_to_proxy_for_data() ) { cookie = cookie_by_host(); } return cookie; } // // 書き込み用クッキー作成 // // プロキシの書き込み用設定がoffのとき // またはプロキシにクッキーを送る設定がonのときはサイトにcookieを送信する // std::string BoardBase::cookie_for_post() const { std::string cookie; if( ! CONFIG::get_use_proxy_for_data() || CONFIG::get_send_cookie_to_proxy_for_data() ) { cookie = cookie_by_host(); } return cookie; } // 板のホストを指定してクッキーを追加 void BoardBase::set_list_cookies( const std::list< std::string >& list_cookies ) { JDLIB::CookieManager* cookie_manager = JDLIB::get_cookie_manager(); const std::string hostname = MISC::get_hostname( get_root(), false ); for( const std::string& input : list_cookies ) { cookie_manager->feed( hostname, MISC::remove_space( input ) ); } update_hap(); } // 板のホストを指定してクッキーを削除 void BoardBase::delete_cookies() { JDLIB::CookieManager* cookie_manager = JDLIB::get_cookie_manager(); const std::string hostname = MISC::get_hostname( get_root(), false ); cookie_manager->delete_cookie_by_host( hostname ); } void BoardBase::clear() { m_rawdata.clear(); m_rawdata.shrink_to_fit(); m_rawdata_left.clear(); m_rawdata_left.shrink_to_fit(); m_get_article_url = std::string(); m_iconv.reset(); m_list_artinfo.clear(); } // // m_url_update_views に登録されている view に update_board コマンドを送る // void BoardBase::send_update_board() { #ifdef _DEBUG std::cout << "BoardBase::send_update_board\n"; #endif // ダウンロードを開始したビュー以外のビューの内容を更新する // // "update_board" コマンドの後に"update_board_item"を送ると // ローディングが終了しているため行を二回更新してしまうので注意 // 詳しくは BoardViewBase::update_item() を参照 CORE::core_set_command( "update_board_item", url_boardbase(), std::string() // IDとして空文字を送る ); // ダウンロードを開始したビューの内容を更新する for( const std::string& url : m_url_update_views ) { #ifdef _DEBUG std::cout << "update : " << url << std::endl; #endif CORE::core_set_command( "update_board", url ); } m_url_update_views.clear(); } // // 新しくArticleBaseクラスを追加してそのポインタを返す // ArticleBase* BoardBase::append_article( const std::string& datbase, const std::string& id, const bool cached ) { // ベースクラスでは何もしない return get_article_null(); } // // 書き込み時間更新 // void BoardBase::update_writetime() { const std::time_t current = std::time( nullptr ); if( current != std::time_t(-1) ) { m_write_time = current; } #ifdef _DEBUG std::cout << "BoardBase::update_writetime : " << m_write_time << std::endl; #endif } // // 経過時間(秒) // time_t BoardBase::get_write_pass() const { std::time_t current; if( m_write_time && std::time( ¤t ) != std::time_t(-1) ) { return std::max( 0, current - m_write_time ); } return 0; } // // 書き込み可能までの残り秒 // time_t BoardBase::get_write_leftsec() const { const time_t mrg = 2; if( ! m_samba_sec ) return 0; if( ! get_write_pass() ) return 0; // ログイン中は書き込み規制無し if( SESSION::login2ch() ) return 0; return MAX( 0, m_samba_sec + mrg - get_write_pass() ); } // // 全書き込み履歴クリア // void BoardBase::clear_all_post_history() { // キャッシュにあるレスをデータベースに登録 append_all_article_in_cache(); if( m_hash_article.size() == 0 ) return; for( ArticleBase* a : m_hash_article ) a->clear_post_history(); } // // 全スレの書き込み時間とスレ立て時間の文字列をリセット // void BoardBase::reset_all_since_date() { for( ArticleBase* a : m_hash_article ) a->reset_since_date(); } void BoardBase::reset_all_write_date() { for( ArticleBase* a : m_hash_article ) a->reset_write_date(); } void BoardBase::reset_all_access_date() { for( ArticleBase* a : m_hash_article ) a->reset_access_date(); } // // 最大レス数をセット // void BoardBase::set_number_max_res( const int number ) { #ifdef _DEBUG std::cout << "BoardBase::set_number_max_res " << m_number_max_res << " -> " << number << std::endl; #endif m_number_max_res = MAX( 0, MIN( CONFIG::get_max_resnumber(), number ) ); for( ArticleBase* a : m_hash_article ) a->set_number_max( m_number_max_res ); } // // 板情報の取得 // // コンストラクタで呼ぶと起動時に全ての板の情報を読まなければならなくなるので、 // Root::get_board()で初めて参照されたときに一度だけ実行 // void BoardBase::read_info() { if( empty() ) return; if( ! m_read_info ){ // 一度読んだらもう処理しない #ifdef _DEBUG std::cout << "BoardBase::read_info : " << m_id << std::endl; #endif m_read_info = true; // 板の情報ファイル読み込み read_board_info(); // キャッシュからローカルルールとSETTING.TXT のロード load_rule_setting(); } } // // 移転などで板のルートやパスを変更する // void BoardBase::update_url( const std::string& root, const std::string& path_board ) { #ifdef _DEBUG std::cout << "BoardBase::update_url\n" << m_root << " -> " << root << std::endl << m_path_board << " -> " << path_board << std::endl; #endif m_root = root; m_path_board = path_board; m_query_dat.clear(); m_query_cgi.clear(); m_query_kako.clear(); // modified 時刻をリセット // 自動移転処理後に bbsmenu.html を読み込んだときに、bbsmenu.html の // アドレスが古いと二度と自動移転処理しなくなる set_date_modified( std::string() ); // 配下の ArticleBase にも知らせてあげる const std::string datbase = url_datbase(); for( ArticleBase* a : m_hash_article ) a->update_datbase( datbase ); } // // スレの urlをdat型のurlに変換 // // url がスレッドのURLで無い時はempty()が返る // もしurlが移転前の旧ホストのものだったら対応するarticlebaseクラスに旧ホスト名を知らせる // // (例) url = "http://www.hoge2ch.net/test/read.cgi/hogeboard/12345/12-15"のとき、 // // 戻り値 : "http://www.hoge2ch.net/hogeboard/dat/12345.dat", num_from = 12, num_to = 15, num_str = "12-15" // std::string BoardBase::url_dat( const std::string& url, int& num_from, int& num_to, std::string& num_str ) { if( empty() ) return std::string(); JDLIB::Regex regex; const size_t offset = 0; std::string id; // スレッドのID #ifdef _DEBUG std::cout << "BoardBase::url_dat : url = " << url << std::endl; #endif num_from = num_to = 0; if( ! m_query_dat.compiled() ){ constexpr bool icase = false; constexpr bool newline = true; constexpr bool usemigemo = false; constexpr bool wchar = false; // dat 型 const std::string datpath = MISC::replace_str( url_datpath(), "?", "\\?" ); const std::string reg_dat = "^ *(https?://.+" + datpath + ")([0-9]+" + get_ext() + ") *$"; m_query_dat.set( reg_dat, icase, newline, usemigemo, wchar ); // read.cgi型 const std::string cgipath = MISC::replace_str( url_readcgipath(), "?", "\\?" ); const std::string reg_cgi = "^ *(https?://.+" + cgipath + ")([0-9]+)/?r?(l50)?([0-9]+)?(-)?([0-9]+)?.*$"; m_query_cgi.set( reg_cgi, icase, newline, usemigemo, wchar ); // 過去ログかどうか const std::string pathboard = MISC::replace_str( m_path_board, "?", "\\?" ); const std::string reg_kako = "^ *(https?://.+)" + pathboard + "/kako(/[0-9]+)?/[0-9]+/([0-9]+).html *$"; m_query_kako.set( reg_kako, icase, newline, usemigemo, wchar ); #ifdef _DEBUG std::cout << "reg_dat = " << reg_dat << std::endl; std::cout << "reg_cgi = " << reg_cgi << std::endl; std::cout << "reg_kako = " << reg_kako << std::endl; #endif } if( regex.match( m_query_dat, url, offset ) ) id = regex.str( 2 ); else if( regex.match( m_query_cgi, url, offset ) ){ id = regex.str( 2 ) + get_ext(); if( regex.length( 3 ) ){ // l50 num_from = 1; num_to = 50; } else{ num_from = atoi( regex.str( 4 ).c_str() ); num_to = atoi( regex.str( 6 ).c_str() ); } if( num_from != 0 ){ num_from = MAX( 1, num_from ); // 12- みたいな場合はとりあえず大きい数字を入れとく if( regex.length( 5 ) && !num_to ) num_to = CONFIG::get_max_resnumber() + 1; } // -15 みたいな場合 else if( num_to != 0 ) num_from = 1; num_to = MAX( num_from, num_to ); num_str = MISC::get_filename( url ); } // どちらでもない(スレのURLでない)場合 else{ if( regex.match( m_query_kako, url, offset ) ){ std::string url_tmp = regex.str( 1 ) + url_datpath() + regex.str( 3 ) + get_ext(); #ifdef _DEBUG std::cout << "kako log -> " << url_tmp << std::endl; #endif return url_dat( url_tmp, num_from, num_to, num_str ); } // 外部板の移転の場合は path_board も変わるときがあるので // 移転した場合はurlを置換してからもう一度試す std::string old_root; std::string old_path_board; std::string new_root; std::string new_path_board; std::string new_url = DBTREE::is_board_moved( url, old_root, old_path_board, new_root, new_path_board ); #ifdef _DEBUG std::cout << "old_root = " << old_root << " new_root = " << new_root << std::endl; std::cout << "old_path_board = " << old_path_board << " new_path_board = " << new_path_board << std::endl; #endif if( ! new_url.empty() ){ std::string url_tmp = MISC::replace_str( url, old_root, new_root ); url_tmp = MISC::replace_str( url_tmp, old_path_board + "/", new_path_board + "/" ); #ifdef _DEBUG std::cout << "moved -> " << url_tmp << std::endl; #endif return url_dat( url_tmp, num_from, num_to, num_str ); } #ifdef _DEBUG std::cout << "not found\n"; #endif return std::string(); } const std::string datbase = url_datbase(); #ifdef _DEBUG std::cout << "BoardBase::url_dat result : " << url << " ->\n"; std::cout << "datbase = " << datbase << " id = " << id << " from " << num_from << " to " << num_to << " num = " << num_str << std::endl; #endif // もしurl(スレッドのURL)が移転前の旧URLのものだったら対応するarticlebaseクラスに旧ホスト名を教えてあげる // ( offlaw による dat落ちスレの読み込み時に使用する ) if( m_root.rfind( MISC::get_hostname( url ), 0 ) != 0 ){ #ifdef _DEBUG std::cout << "org_host : " << MISC::get_hostname( url ) << std::endl; #endif get_article_create( datbase, id )->set_org_host( MISC::get_hostname( url ) ); } return datbase + id; } // // スレの url を read.cgi型のurlに変換 // // url がスレッドのURLで無い時はempty()が返る // num_from と num_to が 0 で無い時はスレ番号を付ける // // (例) "http://www.hoge2ch.net/hogeboard/dat/12345.dat", num_from = 12, num_to = 15 のとき // // 戻り値 : "http://www.hoge2ch.net/test/read.cgi/hogeboard/12345/12-15" // std::string BoardBase::url_readcgi( const std::string& url, int num_from, int num_to ) { if( empty() ) return std::string(); #ifdef _DEBUG std::cout << "BoardBase::url_readcgi : " << url << " from " << num_from << " to " << num_to << std::endl; #endif ArticleBase* article = get_article_fromURL( url ); if( !article ) return std::string(); if( article->empty() ) return std::string(); std::string readcgi = url_readcgibase() + article->get_key() + "/"; #ifdef _DEBUG std::cout << "BoardBase::::url_readcgi : to " << readcgi << std::endl; #endif if( num_from > 0 || num_to > 0 ){ std::ostringstream ss; ss << readcgi; if( num_from ) ss << num_from; if( num_to > num_from ) ss << "-" << num_to; return ss.str(); } return readcgi; } // // subject.txt の URLを取得 // // (例) "http://www.hoge2ch.net/hogeboard/subject.txt" // std::string BoardBase::url_subject() const { if( empty() ) return std::string(); return url_boardbase() + m_subjecttxt; } // // ルートアドレス // // (例) "http://www.hoge2ch.net/" (最後に '/' がつく) // std::string BoardBase::url_root() const { if( empty() ) return std::string(); return m_root + "/"; } // // 板のベースアドレス // // (例) "http://www.hoge2ch.net/hogeboard/" (最後に '/' がつく) // std::string BoardBase::url_boardbase() const { if( empty() ) return std::string(); return m_root + m_path_board + "/"; } // // dat ファイルのURLのベースアドレスを返す // // (例) "http://www.hoge2ch.net/hogeboard/dat/" (最後に '/' がつく) // std::string BoardBase::url_datbase() const { if( empty() ) return std::string(); return m_root + url_datpath(); } // // dat ファイルのURLのパスを返す // // (例) "/hogeboard/dat/" (最初と最後に '/' がつく) // std::string BoardBase::url_datpath() const { if( empty() ) return std::string(); return m_path_board + m_path_dat + "/"; } // // read.cgi のURLのベースアドレスを返す // // (例) "http://www.hoge2ch.net/test/read.cgi/hogeboard/" (最後に '/' がつく) // std::string BoardBase::url_readcgibase() const { if( empty() ) return std::string(); return m_root + url_readcgipath(); } // // read.cgi のURLのパスを返す // // (例) "/test/read.cgi/hogeboard/" (最初と最後に '/' がつく) // std::string BoardBase::url_readcgipath() const { if( empty() ) return std::string(); return m_path_readcgi + m_path_board + "/"; } // // bbscgi のURLのベースアドレス // // (例) "http://www.hoge2ch.net/test/bbs.cgi/" ( 最後に '/' がつく ) // // std::string BoardBase::url_bbscgibase() const { if( empty() ) return std::string(); return m_root + m_path_bbscgi + "/"; } // // subbbscgi のURLのベースアドレス // // (例) "http://www.hoge2ch.net/test/subbbs.cgi/" ( 最後に '/' がつく ) // std::string BoardBase::url_subbbscgibase() const { if( empty() ) return std::string(); return m_root + m_path_subbbscgi + "/"; } // // article のID を渡してハッシュから article のポインタを検索して返すだけ // // 無ければNullクラスを返す // ArticleBase* BoardBase::get_article( const std::string& datbase, const std::string& id ) { if( id.empty() ) return get_article_null(); // キャッシュにあるレスをデータベースに登録 append_all_article_in_cache(); ArticleBase* art = m_hash_article.find( datbase, id ); if( art ) return art; return get_article_null(); } // // articleの IDを渡して ハッシュから article のポインタを検索して返す // // ポインタがあった場合は情報ファイルを読み込む // さらにデータベースにArticleBaseクラスが登録されてない場合はクラスを作成して登録する // ArticleBase* BoardBase::get_article_create( const std::string& datbase, const std::string& id ) { #ifdef _DEBUG std::cout << "BoardBase::get_article_create id = " << id << std::endl; #endif ArticleBase* art = get_article( datbase, id ); // データベースに無いので新規登録 // get_article() の中で append_all_article_in_cache() を // 呼び出しているので、スレがキャッシュ内にない場合 art->empty() == TRUE になる if( art->empty() ){ #ifdef _DEBUG std::cout << "BoardBase::get_article_create : append_article id = " << id << std::endl; #endif art = append_article( datbase, id, false // キャッシュ無し ); assert( art ); } if( art->is_cached() ) art->read_info(); return art; } ArticleBase* BoardBase::insert( std::unique_ptr article ) { return m_hash_article.insert( std::move( article ) ); } // // article の URL を渡してハッシュから article のポインタを検索して返す // // さらにデータベースにArticleBaseクラスが登録されてない場合はクラスを作成して登録する // ArticleBase* BoardBase::get_article_fromURL( const std::string& url ) { if( empty() ) return get_article_null(); // キャッシュ if( url == m_get_article_url ){ #ifdef _TEST_CACHE ++cache_hit_art; #endif return m_get_article; } m_get_article_url = url; m_get_article = get_article_null(); #ifdef _DEBUG std::cout << "BoardBase::get_article_fromURL url = " << url << std::endl; #endif // dat型のURLにしてdatbaseとidを取得 int num_from, num_to; std::string num_str; const std::string urldat = url_dat( url, num_from, num_to, num_str ); if( urldat.empty() ) return m_get_article; const std::string datbase = url_datbase(); const std::string id = urldat.substr( datbase.length() ); if( id.empty() ) return m_get_article; #ifdef _DEBUG std::cout << "datbase = " << datbase << std::endl << "id = " << id << std::endl; #endif #ifdef _TEST_CACHE ++cache_nohit_art; #endif // get_article_create() 経由で ArticleBase::read_info() から get_article_fromURL()が // 再帰呼び出しされることもあるので m_get_article_url を空にしておく m_get_article_url = std::string(); m_get_article = get_article_create( datbase, id ); m_get_article_url = url; return m_get_article; } // // subject.txt ダウンロード // // url_update_view : CORE::core_set_command( "update_board" ) を送信するビューのアドレス // read_from_cache : まだスレ一覧を開いていないときにキャッシュのsubject.txtを読み込む // void BoardBase::download_subject( const std::string& url_update_view, const bool read_from_cache ) { #ifdef _DEBUG std::cout << "BoardBase::download_subject " << url_boardbase() << std::endl << "url_update_view = " << url_update_view << std::endl << "read_from_cache = " << read_from_cache << std::endl << "empty = " << empty() << std::endl << "loading = " << is_loading() << std::endl << "views = " << m_url_update_views.size() << std::endl; #endif // ダウンロード中に他のビューから再びダウンロード依頼が来たら // ダウンロード終了時にまとめてビューにupdateコマンドを送る if( ! url_update_view.empty() ) m_url_update_views.push_back( url_update_view ); if( m_url_update_views.size() >= 2 ) return; if( empty() ) return; if( is_loading() ) return; if( read_from_cache && m_list_subject_created ) return; clear(); m_read_url_boardbase = false; if( read_from_cache ) m_is_online = false; else m_is_online = SESSION::is_online(); m_is_booting = SESSION::is_booting(); // オフライン if( ! m_is_online ){ // まだスレ一覧を開いていないときはディスパッチャを通さないで直接subject.txtを読み込む if( read_from_cache ) receive_finish(); // 一度でもスレ一覧を開いている場合はディスパッチャ経由で receive_finish() を呼び出す else{ // HTTP コードは Loadable::callback_dispatch() の中で HTTP_INIT にセットされる set_str_code( "" ); finish(); } return; } // オンライン // subject.txtのキャッシュが無かったら modified をリセット std::string path_subject = CACHE::path_board_root_fast( url_boardbase() ) + m_subjecttxt; if( CACHE::file_exists( path_subject ) != CACHE::EXIST_FILE ) set_date_modified( std::string() ); JDLIB::LOADERDATA data; create_loaderdata( data ); if( ! start_load( data ) ){ send_update_board(); clear(); } } // // ロード用データ作成 // void BoardBase::create_loaderdata( JDLIB::LOADERDATA& data ) { data.url = url_subject(); data.agent = get_agent(); data.host_proxy = get_proxy_host(); data.port_proxy = get_proxy_port(); data.basicauth_proxy = get_proxy_basicauth(); data.size_buf = CONFIG::get_loader_bufsize_board(); data.timeout = CONFIG::get_loader_timeout(); data.basicauth = get_basicauth(); data.modified = get_date_modified(); data.cookie_for_request = cookie_for_request(); } // // ローダよりsubject.txt受信 // void BoardBase::receive_data( const char* data, size_t size ) { if( ! size ) return; if( m_rawdata.capacity() < SIZE_OF_RAWDATA ) { m_rawdata.reserve( SIZE_OF_RAWDATA ); } m_rawdata.append( data, size ); if( m_read_url_boardbase ) return; // url_boardbase をロードして移転が起きたかチェック中 // // 改行ごとに区切ってUTF8に文字コード変換して解析 // if( m_rawdata_left.capacity() < SIZE_OF_RAWDATA ) { m_rawdata_left.reserve( SIZE_OF_RAWDATA ); } if( ! m_iconv ) m_iconv = std::make_unique( "UTF-8", m_charset ); m_rawdata_left.append( data, size ); std::size_t byte_in = m_rawdata_left.rfind( '\n' ); if( byte_in != std::string::npos ) { byte_in += 1; // 改行まで含める int byte_out; const char* rawdata_utf8 = m_iconv->convert( &*m_rawdata_left.begin(), byte_in, byte_out ); parse_subject( rawdata_utf8 ); // 残りを先頭に移動 m_rawdata_left.erase( 0, byte_in ); #ifdef _DEBUG std::cout << "BoardBase::receive_data rawdata.size = " << m_rawdata.size() << " size = " << size << " byte_in = " << byte_in << " byte_out = " << byte_out << " rawdata_left.size = " << m_rawdata_left.size() << std::endl; #endif } } // // ロード完了 // void BoardBase::receive_finish() { // 別スレッドでローカルルールとSETTING.TXT のダウンロード開始 if( m_is_online ) download_rule_setting(); m_list_subject.clear(); m_list_abone_thread_remove.clear(); m_status &= ~STATUS_UPDATED; bool read_from_cache = false; const std::string path_subject = CACHE::path_board_root_fast( url_boardbase() ) + m_subjecttxt; const std::string path_oldsubject = CACHE::path_board_root_fast( url_boardbase() ) + "old-" + m_subjecttxt; #ifdef _DEBUG std::cout << "----------------------------------\nBoardBase::receive_finish code = " << get_str_code() << std::endl; std::cout << "rawdata.size = " << m_rawdata.size() << std::endl; #endif /////////////////////////////////////////////////////// // // url_boardbase をロードして移転が起きたかチェック if( m_read_url_boardbase ){ #ifdef _DEBUG std::cout << "move check\n"; #endif set_date_modified( std::string() ); send_update_board(); // Locationヘッダーで移転先を指定された場合 if( ( get_code() == HTTP_MOVED_PERM || get_code() == HTTP_REDIRECT ) && ! location().empty() ) { // location() は url_boardbase() の移転先 (start_checkking_if_board_moved() を参照) if( DBTREE::move_board( url_boardbase(), location() ) ) { // 再読み込み const std::string str_tab = "false"; CORE::core_set_command( "open_board", url_boardbase(), str_tab ); } } // HTMLの埋め込みスクリプトで移動を指示された場合 else if( !m_rawdata.empty() && get_code() == HTTP_OK && m_rawdata.find( "window.location.href" ) != std::string::npos ) { #ifdef _DEBUG std::cout << m_rawdata << std::endl; #endif JDLIB::Regex regex; const size_t offset = 0; const bool icase = false; const bool newline = true; const bool usemigemo = false; const bool wchar = false; std::string query = ".*window.location.href=\"([^\"]*)\".*"; if( regex.exec( query, m_rawdata, offset, icase, newline, usemigemo, wchar ) ){ std::string new_url = regex.str( 1 ); if( new_url.rfind( "//", 0 ) == 0 ){ std::string tmp_url = url_boardbase(); size_t pos = tmp_url.find("://"); if( pos != std::string::npos ) new_url.insert( 0, tmp_url.substr( 0, pos + 1 ) ); } int ret = Gtk::RESPONSE_YES; if( CONFIG::get_show_movediag() ){ const std::string msg = "「" + get_name() + "」は\n\n" + new_url + " に移転しました。\n\nデータベースを更新しますか?"; SKELETON::MsgCheckDiag mdiag( nullptr, msg, "今後表示しない(常に更新)(_D)", Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO ); mdiag.set_title( "更新確認" ); ret = mdiag.run(); if( ret == Gtk::RESPONSE_YES && mdiag.get_chkbutton().get_active() ) CONFIG::set_show_movediag( false ); } if( ret == Gtk::RESPONSE_YES ){ if( DBTREE::move_board( url_boardbase(), new_url ) ){ // 再読み込み const std::string str_tab = "false"; CORE::core_set_command( "open_board", url_boardbase(), str_tab ); } } } } else{ SKELETON::MsgDiag mdiag( nullptr, "移転しました\n\n板一覧を更新しますか?", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO ); mdiag.set_default_response( Gtk::RESPONSE_YES ); if( mdiag.run() == Gtk::RESPONSE_YES ) CORE::core_set_command( "reload_bbsmenu" ); } clear(); return; } // 移転チェック終わり // /////////////////////////////////////////////////////// // サーバーからtimeoutなどのエラーが返った or 移転 if( get_code() != HTTP_ERR // HTTP_ERR はローダでの内部のエラー && get_code() != HTTP_OK && get_code() != HTTP_NOT_MODIFIED ){ // // リダイレクト(302)の場合は移転確認 // // ちなみにdatの読み込みでリダイレクト(302)が返ってきたときは、移転かdat落ちか判断出来ないので注意 // NodeTree2ch::receive_finish()も参照せよ // if( get_code() == HTTP_REDIRECT || get_code() == HTTP_MOVED_PERM ){ set_date_modified( std::string() ); if( start_checkking_if_board_moved() ) return; send_update_board(); clear(); return; } // サーバからエラーが返ったらキャッシュからデータをロード read_from_cache = true; } // ローダがエラーを返した 又は not modified 又はオフラインならキャッシュからデータをロード if( get_code() == HTTP_ERR || get_code() == HTTP_NOT_MODIFIED || ! m_is_online ) read_from_cache = true; // キャッシュから読み込み if( read_from_cache ){ #ifdef _DEBUG std::cout << "read from cache " << path_subject << std::endl; #endif m_rawdata.clear(); m_rawdata_left.clear(); std::vector rawdata( SIZE_OF_RAWDATA ); const std::size_t lng = CACHE::load_rawdata( path_subject, rawdata.data(), SIZE_OF_RAWDATA ); receive_data( rawdata.data(), lng ); } #ifdef _DEBUG std::cout << "size = " << m_list_artinfo.size() << " rawdata.size = " << m_rawdata.size() << " left = " << m_rawdata_left.size() << std::endl; #endif // データが無い if( ! m_list_artinfo.size() ){ set_date_modified( std::string() ); // 移転した可能性があるので url_boardbase をロードして解析 if( m_is_online && get_code() == HTTP_OK ){ if( start_checkking_if_board_moved() ) return; } send_update_board(); clear(); return; } ////////////////////////////// // データベース更新 // subject.txtを解析して現行スレだけsubjectリスト(m_list_subject)に加える // キャッシュにあるレスをデータベースに登録 append_all_article_in_cache(); // 一度全てのarticleをdat落ち状態にして subject.txt に // 含まれているものだけ regist_article()の中で通常状態にする for( ArticleBase* article : m_hash_article ) { if( read_from_cache && ! article->is_924() && article->get_since_time() > m_last_access_time ){ // キャッシュから読み込む場合にsubject.txtよりも新しいスレは残す article->read_info(); if( ! is_abone_thread( article ) ) m_list_subject.push_back( article ); } else{ int status = article->get_status(); status &= ~STATUS_NORMAL; status |= STATUS_OLD; article->set_status( status ); } } m_is_booting = false; regist_article( m_is_online ); // list_subject 更新 if( ! m_list_subject.empty() ){ #ifdef _DEBUG std::cout << "list_subject was updated\n"; #endif if( m_is_online ){ // 既読スレに更新があったかチェック const auto is_updated = []( const ArticleBase* a ) { return a->is_cached() && a->get_number() > a->get_number_load(); }; if( std::any_of( m_list_subject.cbegin(), m_list_subject.cend(), is_updated ) ) { m_status |= STATUS_UPDATED; } show_updateicon( false ); } // DAT落ちなどでsubject.txtに無いスレもsubjectリストに加える if( CONFIG::get_show_oldarticle() || m_show_oldlog ){ for( ArticleBase* article : m_hash_article ) { if( article->is_cached() && ( article->get_status() & STATUS_OLD ) ){ // サブジェクトやロード数などの情報が無いのでスレのinfoファイルから // 取得する必要がある // TODO : 数が多いとboardビューを開くまで時間がかかるのをなんとかする #ifdef _DEBUG std::cout << "read article_info : " << article->get_url() << std::endl; #endif article->read_info(); if( ! is_abone_thread( article ) ) m_list_subject.push_back( article ); } } } // オンライン、かつcodeが200か304なら最終アクセス時刻を更新 if( m_is_online && ( get_code() == HTTP_OK || get_code() == HTTP_NOT_MODIFIED ) ){ m_last_access_time = time( nullptr ); #ifdef _DEBUG std::cout << "access time " << m_last_access_time << std::endl; #endif } // オンライン、かつcodeが200なら情報を更新・保存して subject.txt をキャッシュに保存 if( m_is_online && get_code() == HTTP_OK ){ m_last_access_time = time( nullptr ); #ifdef _DEBUG std::cout << "save info and subject.txt\n"; std::cout << "rename " << path_subject << " " << path_oldsubject << std::endl; std::cout << "save " << path_subject << std::endl; #endif save_info(); // subject.txtをキャッシュ if( CACHE::mkdir_boardroot( url_boardbase() ) ){ // 古いファイルをrename if( CACHE::file_exists( path_subject ) == CACHE::EXIST_FILE ){ if( rename( path_subject.c_str(), path_oldsubject.c_str() ) != 0 ){ MISC::ERRMSG( "rename failed " + path_subject ); } } // subject.txt セーブ CACHE::save_rawdata( path_subject, m_rawdata ); } } m_list_subject_created = true; } // if( ! m_list_subject.empty() ) // list_subject が更新されなかった else{ #ifdef _DEBUG std::cout << "list_subject was NOT updated\n"; #endif set_date_modified( std::string() ); } // コアにデータベース更新を知らせる send_update_board(); clear(); } // // url_boardbase をロードして移転したかどうか解析開始 // // 移転するとコード200で // // // // の様なHTML本文が送られて来るので移転先が分かる // bool BoardBase::start_checkking_if_board_moved() { #ifdef _DEBUG std::cout << "BoardBase::start_checkking_if_board_moved " << url_boardbase() << std::endl; #endif m_rawdata.clear(); JDLIB::LOADERDATA data; data.init_for_data(); data.url = url_boardbase(); data.cookie_for_request = cookie_for_request(); if( start_load( data ) ){ m_read_url_boardbase = true; return true; } return false; } // // キャッシュのディレクトリ内にあるスレのファイル名のリストを取得 // std::list BoardBase::get_filelist_in_cache() const { std::list list_out; if( empty() ) return list_out; const std::string path_board_root = CACHE::path_board_root_fast( url_boardbase() ); std::list list_file = CACHE::get_filelist( path_board_root ); if( list_file.empty() ) return list_out; for( std::string& file : list_file ) { if( is_valid( file ) ) list_out.emplace_back( std::move( file ) ); } return list_out; } // // キャッシュのディレクトリ内にあるスレのファイル名を取得してDBにすべてを登録 // // 全てのスレのinfoファイルを読むと遅くなるのでこの段階では読まない(DBへの登録のみ) // // boardビューに一覧表示するためBoardBaseの派生クラスのparse_subject()を呼び出したり、 // BoardBase::get_article_fromURL()で参照されたときに初めてスレのinfoファイルを読み込む // void BoardBase::append_all_article_in_cache() { if( m_append_articles ) return; m_append_articles = true; const std::string datbase = url_datbase(); #ifdef _DEBUG std::cout << "BoardBase::append_all_article_in_cache url = " << datbase << std::endl; #endif std::list list_file = get_filelist_in_cache(); for( const std::string& id : list_file ) { #ifdef _DEBUG std::cout << "append id = " << id << std::endl; #endif // キャッシュあり( cached = true ) 指定でDBに登録 // キャッシュに無いスレはsubject.txtを読み込んだ時に // 派生クラスのparse_subject()で登録する。 append_article( datbase, id, true // キャッシュあり ); } } // // 配下の全articlebaseクラスのあぼーん状態の更新 // void BoardBase::update_abone_all_article() { for( ArticleBase* a : m_hash_article ) a->update_abone(); } // // スレあぼーん判定 // bool BoardBase::is_abone_thread( ArticleBase* article ) { if( ! article ) return false; if( article->empty() ) return false; const int load = article->get_number_load(); const auto get_check_number = [load]( int board, int global ) { return load ? 0 : ( board ? board : global ); }; const int check_low_number = get_check_number( m_abone_low_number_thread, get_abone_low_number_global() ); const int check_high_number = get_check_number( m_abone_high_number_thread, get_abone_high_number_global() ); const int check_hour = get_check_number( m_abone_hour_thread, CONFIG::get_abone_hour_thread() ); const bool check_thread = ! m_list_abone_thread.empty(); const bool check_word = ! m_list_abone_word_thread.empty(); const bool check_regex = ! m_list_abone_regex_thread.empty(); const bool check_word_global = ! CONFIG::get_list_abone_word_thread().empty(); const bool check_regex_global = ! CONFIG::get_list_abone_regex_thread().empty(); if( !check_low_number && !check_high_number && !check_hour && !check_thread && !check_word && !check_regex && !check_word_global && !check_regex_global ) return false; JDLIB::Regex regex; const size_t offset = 0; const bool icase = CONFIG::get_abone_icase(); const bool newline = true; const bool usemigemo = false; const bool wchar = CONFIG::get_abone_wchar(); // レスの数であぼーん if( check_low_number && article->get_number() <= check_low_number ) return true; if( check_high_number && article->get_number() >= check_high_number ) return true; // スレ立てからの時間であぼーん if( check_hour ) if( article->get_hour() >= check_hour ) return true; // スレあぼーん if( check_thread ){ for( const std::string& subject : m_list_abone_thread ) { if( MISC::remove_space( article->get_subject() ) == MISC::remove_space( subject ) ){ // 対象スレがDat落ちした場合はあぼーんしなかったスレ名をリストから消去する // remove_old_abone_thread() も参照 m_list_abone_thread_remove.push_back( subject ); return true; } } } // ローカル NG word if( check_word ){ for( const std::string& word : m_list_abone_word_thread ) { if( article->get_subject().find( word ) != std::string::npos ) return true; } } // ローカル NG regex if( check_regex ){ for( const std::string& pat : m_list_abone_regex_thread ) { if( regex.exec( pat, article->get_subject(), offset, icase, newline, usemigemo, wchar ) ) return true; } } // 全体 NG word if( check_word_global ){ for( const std::string& word : CONFIG::get_list_abone_word_thread() ) { if( article->get_subject().find( word ) != std::string::npos ) return true; } } // 全体 NG regex if( check_regex_global ){ for( const std::string& pat : CONFIG::get_list_abone_regex_thread() ) { if( regex.exec( pat, article->get_subject(), offset, icase, newline, usemigemo, wchar ) ) return true; } } return false; } // // subject.txtのロード後にdat落ちしたスレッドをスレあぼーんのリストから取り除く // // is_abone_thread() も参照せよ // void BoardBase::remove_old_abone_thread() { if( m_cancel_remove_abone_thread ) return; if( CONFIG::get_remove_old_abone_thread() == 2 ) return; if( m_list_abone_thread.empty() ) return; if( m_list_abone_thread.size() == m_list_abone_thread_remove.size() ) return; #ifdef _DEBUG std::cout << "BoardBase::remove_old_abone_thread\n"; for( const std::string& s : m_list_abone_thread ) std::cout << s << std::endl; std::cout << "->\n"; for( const std::string& s : m_list_abone_thread_remove ) std::cout << s << std::endl; #endif if( CONFIG::get_remove_old_abone_thread() == 0 ){ SKELETON::MsgCheckDiag mdiag( nullptr, "NGスレタイトルに登録したスレがdat落ちしました。\n\nNGスレタイトルから除外しますか?\n後で板のプロパティから削除する事も可能です。", "今後表示しない(_D)", Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_NONE ); mdiag.add_default_button( g_dgettext( GTK_DOMAIN, "_No" ), Gtk::RESPONSE_NO ); mdiag.add_button( g_dgettext( GTK_DOMAIN, "_Yes" ), Gtk::RESPONSE_YES ); const int ret = mdiag.run(); // 一度いいえを選択したらあとは再起動するまでダイアログを表示しない if( ret != Gtk::RESPONSE_YES ) m_cancel_remove_abone_thread = true; if( mdiag.get_chkbutton().get_active() ){ if( ret == Gtk::RESPONSE_YES ) CONFIG::set_remove_old_abone_thread( 1 ); else CONFIG::set_remove_old_abone_thread( 2 ); } if( ret != Gtk::RESPONSE_YES ) return; } // dat落ちしたスレタイトルをNGリストから取り除く for( auto it = m_list_abone_thread.cbegin(); it != m_list_abone_thread.cend(); ) { const auto cend = m_list_abone_thread_remove.cend(); if( std::find( m_list_abone_thread_remove.cbegin(), cend, *it ) == cend ) { it = m_list_abone_thread.erase( it ); } else { ++it; } } } // // スレあぼーん情報を更新した時に対応するスレ一覧の表示を更新する // // CONFIG::set_abone_number_thread() などでグローバル設定をした後などに呼び出す // // redraw : スレ一覧の表示更新を行う // void BoardBase::update_abone_thread( const bool redraw ) { #ifdef _DEBUG std::cout << "BoardBase::update_abone_thread\n"; #endif // まだスレ一覧にスレを表示していないBoardBaseクラスは更新しない if( ! m_list_subject_created ) return; // オフラインに切り替えてからキャッシュにあるsubject.txtを再読み込みする const bool online = SESSION::is_online(); SESSION::set_online( false ); download_subject( ( redraw ? url_boardbase() : std::string() ), false ); SESSION::set_online( online ); } // // 板レベルでのあぼーん状態のリセット(情報セットとスレビューの表示更新を同時におこなう) // void BoardBase::reset_abone_board( const std::list< std::string >& ids, const std::list< std::string >& names, const std::list< std::string >& words, const std::list< std::string >& regexs ) { if( empty() ) return; // 前後の空白と空白行を除く m_list_abone_id = MISC::remove_space_from_list( ids ); m_list_abone_id = MISC::remove_nullline_from_list( m_list_abone_id ); m_list_abone_name = MISC::remove_space_from_list( names ); m_list_abone_name = MISC::remove_nullline_from_list( m_list_abone_name ); m_list_abone_word = MISC::remove_space_from_list( words ); m_list_abone_word = MISC::remove_nullline_from_list( m_list_abone_word ); m_list_abone_regex = MISC::remove_space_from_list( regexs ); m_list_abone_regex = MISC::remove_nullline_from_list( m_list_abone_regex ); update_abone_all_article(); CORE::core_set_command( "relayout_all_article" ); } // 板レベルでのあぼ〜ん状態更新(reset_abone()と違って各項目ごと個別におこなう。スレビューの表示更新も同時におこなう) void BoardBase::add_abone_id_board( const std::string& id ) { if( empty() ) return; if( id.empty() ) return; std::string id_tmp = id.substr( strlen( PROTO_ID ) ); m_list_abone_id.push_back( id_tmp ); update_abone_all_article(); CORE::core_set_command( "relayout_all_article" ); } void BoardBase::add_abone_name_board( const std::string& name ) { if( empty() ) return; if( name.empty() ) return; m_list_abone_name.push_back( name ); update_abone_all_article(); CORE::core_set_command( "relayout_all_article" ); } void BoardBase::add_abone_word_board( const std::string& word ) { if( empty() ) return; if( word.empty() ) return; m_list_abone_word.push_back( word ); update_abone_all_article(); CORE::core_set_command( "relayout_all_article" ); } // // スレあぼーん状態のリセット // // redraw : スレ一覧の表示更新を行う // void BoardBase::reset_abone_thread( const std::list< std::string >& threads, const std::list< std::string >& words, const std::list< std::string >& regexs, const int low_number, const int high_number, const int hour, const bool redraw ) { if( empty() ) return; #ifdef _DEBUG std::cout << "BoardBase::reset_abone_thread\n"; #endif // 前後の空白と空白行を除く m_list_abone_thread = MISC::remove_space_from_list( threads ); m_list_abone_thread = MISC::remove_nullline_from_list( m_list_abone_thread ); m_list_abone_word_thread = MISC::remove_space_from_list( words ); m_list_abone_word_thread = MISC::remove_nullline_from_list( m_list_abone_word_thread ); m_list_abone_regex_thread = MISC::remove_space_from_list( regexs ); m_list_abone_regex_thread = MISC::remove_nullline_from_list( m_list_abone_regex_thread ); m_abone_low_number_thread = low_number; m_abone_high_number_thread = high_number; m_abone_hour_thread = hour; update_abone_thread( redraw ); } // // 読み込み用ローカルプロキシ設定 // void BoardBase::set_local_proxy( const std::string& proxy ) { m_local_proxy = proxy; m_local_proxy_basicauth = std::string(); if( proxy.empty() ) return; // basic認証 JDLIB::Regex regex; const size_t offset = 0; const bool icase = false; const bool newline = true; const bool usemigemo = false; const bool wchar = false; if( regex.exec( "([^/]+:[^/]+@)(.+)$" , proxy, offset, icase, newline, usemigemo, wchar ) ) { m_local_proxy_basicauth = regex.str( 1 ).substr( 0, regex.length( 1 ) - 1 ); m_local_proxy = regex.str( 2 ); } } // // 書き込み用ローカルプロキシ設定 // void BoardBase::set_local_proxy_w( const std::string& proxy ) { m_local_proxy_w = proxy; m_local_proxy_basicauth_w = std::string(); if( proxy.empty() ) return; // basic認証 JDLIB::Regex regex; const size_t offset = 0; const bool icase = false; const bool newline = true; const bool usemigemo = false; const bool wchar = false; if( regex.exec( "([^/]+:[^/]+@)(.+)$" , proxy, offset, icase, newline, usemigemo, wchar ) ) { m_local_proxy_basicauth_w = regex.str( 1 ).substr( 0, regex.length( 1 ) - 1 ); m_local_proxy_w = regex.str( 2 ); } } // // キャッシュ内のログ検索 // // ArticleBase のアドレスをリスト(list_article)にセットして返す // query が空の時はキャッシュにあるログを全てヒットさせる // bm がtrueの時、しおりが付いている(スレ一覧でしおりを付けた or レスに一つでもしおりが付いている)スレのみを対象に検索する // void BoardBase::search_cache( std::vector< DBTREE::ArticleBase* >& list_article, const std::string& query, const bool mode_or, // 今のところ無視 const bool bm, const bool stop // 呼出元のスレッドで true にセットすると検索を停止する ) { #ifdef _DEBUG std::cout << "BoardBase::search_cache " << url_boardbase() << std::endl; #endif if( empty() ) return; // キャッシュにあるレスをデータベースに登録 append_all_article_in_cache(); if( m_hash_article.size() == 0 ) return; const bool append_all = query.empty(); const std::string query_local = MISC::Iconv( query, get_charset(), "UTF-8" ); const std::list< std::string > list_query = MISC::split_line( query_local ); const std::string path_board_root = CACHE::path_board_root_fast( url_boardbase() ); for( ArticleBase* article : m_hash_article ) { if( stop ) break; if( ! article->is_cached() ) continue; // しおりがついているスレだけ追加 if( bm ){ article->read_info(); if( ! article->is_bookmarked_thread() && ! article->get_num_bookmark() ) continue; } // 全て追加 if( append_all ){ article->read_info(); #ifdef _DEBUG std::cout << "append " << article->get_subject() << " bm = " << article->get_num_bookmark() << std::endl; #endif list_article.push_back( article ); continue; } const std::string path = path_board_root + article->get_id(); std::string rawdata; if( CACHE::load_rawdata( path, rawdata ) > 0 ){ #ifdef _DEBUG std::cout << "load " << path << " size = " << rawdata.size() << " byte" << std::endl; #endif // 今の所 AND だけ対応 const auto search = [&rawdata]( const auto& q ) { return rawdata.find( q ) != std::string::npos; }; const bool apnd = std::all_of( list_query.cbegin(), list_query.cend(), search ); if( apnd ){ article->read_info(); #ifdef _DEBUG std::cout << "found word in " << url_readcgi( article->get_url(), 0, 0 ) << std::endl << article->get_subject() << std::endl; #endif list_article.push_back( article ); } } } } // datファイルのインポート // 成功したらdat型のurlを返す std::string BoardBase::import_dat( const std::string& filename ) { if( empty() ) return std::string(); if( CACHE::file_exists( filename ) != CACHE::EXIST_FILE ){ SKELETON::MsgDiag mdiag( nullptr, "datファイルが存在しません" ); mdiag.run(); return std::string(); } const std::string id = MISC::get_filename( filename ); if( id.empty() || ! is_valid( id ) ){ SKELETON::MsgDiag mdiag( nullptr, "ファイル名が正しくありません" ); mdiag.run(); return std::string(); } const std::string file_to = CACHE::path_board_root_fast( url_boardbase() ) + id; #ifdef _DEBUG std::cout << "BoardBase::import_dat cp " << filename << " " << file_to << std::endl; #endif const std::string datbase = url_datbase(); ArticleBase* art = get_article_create( datbase, id ); if( ! art->is_cached() ){ CACHE::jdcopy( filename, file_to ); if( CACHE::file_exists( file_to ) == CACHE::EXIST_FILE ){ art->set_cached( true ); art->read_info(); } else{ SKELETON::MsgDiag mdiag( nullptr, "datファイルのコピーに失敗しました" ); mdiag.run(); return std::string(); } } return art->get_url(); } // // board.info( jd用 ) を読む // BoardBase::read_info() も参照すること // void BoardBase::read_board_info() { if( empty() ) return; std::string path_info = CACHE::path_jdboard_info( url_boardbase() ); #ifdef _DEBUG std::cout << "BoardBase::read_board_info " << path_info << std::endl; #endif JDLIB::ConfLoader cf( path_info, std::string() ); std::string modified = cf.get_option_str( "modified", "" ); set_date_modified( modified ); m_modified_localrule = cf.get_option_str( "modified_localrule", std::string() ); m_modified_setting = cf.get_option_str( "modified_setting", std::string() ); m_view_sort_column = cf.get_option_int( "view_sort_column", -1, -1, COL_VISIBLE_END-1 ); m_view_sort_mode = cf.get_option_int( "view_sort_mode", SORTMODE_ASCEND, 0, SORTMODE_NUM -1 ); m_view_sort_pre_column = cf.get_option_int( "view_sort_pre_column", -1, -1, COL_VISIBLE_END-1 ); m_view_sort_pre_mode = cf.get_option_int( "view_sort_pre_mode", SORTMODE_ASCEND, 0, SORTMODE_NUM -1 ); m_check_noname = cf.get_option_bool( "check_noname", false ); m_show_oldlog = cf.get_option_bool( "show_oldlog", false ); std::string str_tmp; // あぼーん id は再起動ごとにリセット // str_tmp = cf.get_option( "aboneid", std::string() ); // if( ! str_tmp.empty() ) m_list_abone_id = MISC::strtolist( str_tmp ); // あぼーん name str_tmp = cf.get_option_str( "abonename", "" ); if( ! str_tmp.empty() ) m_list_abone_name = MISC::strtolist( str_tmp ); // あぼーん word str_tmp = cf.get_option_str( "aboneword", "" ); if( ! str_tmp.empty() ) m_list_abone_word = MISC::strtolist( str_tmp ); // あぼーん regex str_tmp = cf.get_option_str( "aboneregex", "" ); if( ! str_tmp.empty() ) m_list_abone_regex = MISC::strtolist( str_tmp ); // スレ あぼーん str_tmp = cf.get_option_str( "abonethread", "" ); if( ! str_tmp.empty() ) m_list_abone_thread = MISC::strtolist( str_tmp ); // スレ あぼーん word str_tmp = cf.get_option_str( "abonewordthread", "" ); if( ! str_tmp.empty() ) m_list_abone_word_thread = MISC::strtolist( str_tmp ); // スレ あぼーん regex str_tmp = cf.get_option_str( "aboneregexthread", "" ); if( ! str_tmp.empty() ) m_list_abone_regex_thread = MISC::strtolist( str_tmp ); // レス数であぼーん // abonenumberthread は変数や関数と名前が異なるが互換性のため維持する m_abone_low_number_thread = cf.get_option_int( "abonelownumberthread", 0, 0, CONFIG::get_max_resnumber() ); m_abone_high_number_thread = cf.get_option_int( "abonenumberthread", 0, 0, CONFIG::get_max_resnumber() ); // スレ立てからの経過時間であぼーん m_abone_hour_thread = cf.get_option_int( "abonehourthread", 0, 0, 9999 ); // ローカルプロキシ m_mode_local_proxy = cf.get_option_int( "mode_local_proxy", PROXY_GLOBAL, 0, PROXY_NUM -1 ); str_tmp = cf.get_option_str( "local_proxy", "" ); set_local_proxy( str_tmp ); m_local_proxy_port = cf.get_option_int( "local_proxy_port", 8080, 1, 65535 ); m_mode_local_proxy_w = cf.get_option_int( "mode_local_proxy_w", PROXY_GLOBAL, 0, PROXY_NUM -1 ); str_tmp = cf.get_option_str( "local_proxy_w", "" ); set_local_proxy_w( str_tmp ); m_local_proxy_port_w = cf.get_option_int( "local_proxy_port_w", 8080, 1, 65535 ); // 書き込み時のデフォルトの名前とメアド m_write_name = cf.get_option_str( "write_name", "" ); m_write_mail = cf.get_option_str( "write_mail", "" ); if( ! m_write_name.empty() && ( m_write_name == CONFIG::get_write_name() || ( m_write_name == JD_NAME_BLANK && CONFIG::get_write_name().empty() ) ) ){ #ifdef _DEBUG std::cout << "reset name = " << m_write_name << std::endl; #endif m_write_name = std::string(); } if( ! m_write_mail.empty() && ( m_write_mail == CONFIG::get_write_mail() || ( m_write_mail == JD_MAIL_BLANK && CONFIG::get_write_mail().empty() ) ) ){ #ifdef _DEBUG std::cout << "reset mail = " << m_write_mail << std::endl; #endif m_write_mail = std::string(); } // samba24 m_samba_sec = cf.get_option_int( "samba_sec", 0, 0, 65535 ); // 実況の秒数 m_live_sec = cf.get_option_int( "live_sec", 0, 0, 65535 ); // 最終アクセス時刻 m_last_access_time = cf.get_option_int( "last_access_time", 0, 0, 2147483647 ); // ステータス m_status = cf.get_option_int( "status", STATUS_UNKNOWN, 0, 8192 ); // 最大レス数 m_number_max_res = cf.get_option_int( "max_res", get_default_number_max_res(), 0, CONFIG::get_max_resnumber() ); // 板のユーザーエージェント設定 m_board_agent = cf.get_option_str( "user_agent", std::string{} ); #ifdef _DEBUG std::cout << "modified = " << get_date_modified() << std::endl; #endif } // // 情報保存 // void BoardBase::save_info() { if( empty() ) return; if( ! CACHE::mkdir_boardroot( url_boardbase() ) ) return; save_jdboard_info(); save_summary(); save_board_info(); } // // board.info( jd用 ) セーブ // void BoardBase::save_jdboard_info() { std::string path_info = CACHE::path_jdboard_info( url_boardbase() ); #ifdef _DEBUG std::cout << "BoardBase::save_jdboard_info file = " << path_info << std::endl; #endif // あぼーん情報 // std::string str_abone_id = MISC::listtostr( m_list_abone_id ); std::string str_abone_name = MISC::listtostr( m_list_abone_name ); std::string str_abone_word = MISC::listtostr( m_list_abone_word ); std::string str_abone_regex = MISC::listtostr( m_list_abone_regex ); // スレあぼーん情報 std::string str_abone_thread = MISC::listtostr( m_list_abone_thread ); std::string str_abone_word_thread = MISC::listtostr( m_list_abone_word_thread ); std::string str_abone_regex_thread = MISC::listtostr( m_list_abone_regex_thread ); // ローカルプロキシ std::string local_proxy; if( m_local_proxy_basicauth.empty() ) local_proxy = m_local_proxy; else local_proxy = m_local_proxy_basicauth + "@" + m_local_proxy; std::string local_proxy_w; if( m_local_proxy_basicauth_w.empty() ) local_proxy_w = m_local_proxy_w; else local_proxy_w = m_local_proxy_basicauth_w + "@" + m_local_proxy_w; std::ostringstream sstr; sstr << "modified = " << get_date_modified() << std::endl << "modified_localrule = " << m_modified_localrule << std::endl << "modified_setting = " << m_modified_setting << std::endl << "view_sort_column = " << m_view_sort_column << std::endl << "view_sort_mode = " << m_view_sort_mode << std::endl << "view_sort_pre_column = " << m_view_sort_pre_column << std::endl << "view_sort_pre_mode = " << m_view_sort_pre_mode << std::endl << "check_noname = " << m_check_noname << std::endl << "show_oldlog = " << m_show_oldlog << std::endl // IDは再起動ごとにリセット // << "aboneid = " << str_abone_id << std::endl << "abonename = " << str_abone_name << std::endl << "aboneword = " << str_abone_word << std::endl << "aboneregex = " << str_abone_regex << std::endl << "abonethread = " << str_abone_thread << std::endl << "abonewordthread = " << str_abone_word_thread << std::endl << "aboneregexthread = " << str_abone_regex_thread << std::endl << "abonelownumberthread = " << m_abone_low_number_thread << std::endl << "abonenumberthread = " << m_abone_high_number_thread << std::endl << "abonehourthread = " << m_abone_hour_thread << std::endl << "mode_local_proxy = " << m_mode_local_proxy << std::endl << "local_proxy = " << local_proxy << std::endl << "local_proxy_port = " << m_local_proxy_port << std::endl << "mode_local_proxy_w = " << m_mode_local_proxy_w << std::endl << "local_proxy_w = " << local_proxy_w << std::endl << "local_proxy_port_w = " << m_local_proxy_port_w << std::endl << "write_name = " << m_write_name << std::endl << "write_mail = " << m_write_mail << std::endl << "samba_sec = " << m_samba_sec << std::endl << "live_sec = " << m_live_sec << std::endl << "last_access_time = " << m_last_access_time << std::endl << "status = " << m_status << std::endl << "max_res = " << m_number_max_res << std::endl << "user_agent = " << m_board_agent << std::endl ; CACHE::save_rawdata( path_info, sstr.str() ); } // // article-summary( navi2ch互換 )保存 // // navi2chとの互換性のために保存しているだけで article-summary の情報は使わない // void BoardBase::save_summary() { std::string path_summary = CACHE::path_article_summary( url_boardbase() ); #ifdef _DEBUG std::cout << "BoardBase::save_summary file = " << path_summary << std::endl; #endif int count = 0; std::ostringstream sstr_out; sstr_out << "("; for( ArticleBase* art : m_list_subject ) { if( art->is_cached() && ( art->get_status() & STATUS_NORMAL ) ){ if( count ) sstr_out << " "; ++count; // key sstr_out << "(\"" << art->get_key() << "\""; // 読んだ位置 sstr_out << " :seen " << art->get_number_seen(); // access-time sstr_out << " :access-time (" << art->get_access_time_str() << "))"; } } sstr_out << ")"; #ifdef _DEBUG // std::cout << sstr_out.str() << std::endl; #endif if( count ) CACHE::save_rawdata( path_summary, sstr_out.str() ); } // // board.info( navi2ch 互換 ) セーブ // // navi2chとの互換性のために保存しているだけで情報は使わない // void BoardBase::save_board_info() { std::string path_info = CACHE::path_board_info( url_boardbase() ); #ifdef _DEBUG std::cout << "BoardBase::save_board_info file = " << path_info << std::endl; #endif // time だけ更新する(他の情報は使わない) std::string bookmark = "nil"; std::string hide = "nil"; std::string time = "(time . \"" + get_date_modified() + "\")"; std::string logo = "nil"; // board.info 読み込み if( CACHE::file_exists( path_info ) == CACHE::EXIST_FILE ){ std::string str_info; CACHE::load_rawdata( path_info, str_info ); #ifdef _DEBUG std::cout << "str_info " << str_info << std::endl; #endif for( const std::string& elem : MISC::get_elisp_lists( str_info ) ) { if( elem.find( "bookmark" ) != std::string::npos ) bookmark = elem; if( elem.find( "hide" ) != std::string::npos ) hide = elem; if( elem.find( "logo" ) != std::string::npos ) logo = elem; } } std::string str_out = "(" + bookmark + " " + hide + " " + time + " " + logo + ")"; #ifdef _DEBUG std::cout << "info = " << str_out << std::endl; #endif CACHE::save_rawdata( path_info, str_out ); } // // 配下の全articlebaseクラスの情報保存 // void BoardBase::save_articleinfo_all() { for( ArticleBase* a : m_hash_article ) a->save_info( false ); } // 更新可能状態にしてお気に入りやスレ一覧のタブのアイコンに更新マークを表示 // update == true の時に表示。falseなら戻す void BoardBase::show_updateicon( const bool update ) { #ifdef _DEBUG std::cout << "BoardBase::show_updateicon url = " << url_boardbase() << " update = " << update << " status = " << ( m_status & STATUS_UPDATE ) << std::endl; #endif if( update ){ if( ! ( m_status & STATUS_UPDATE ) ){ #ifdef _DEBUG std::cout << "toggle_icon on\n"; #endif m_status |= STATUS_UPDATE; // スレ一覧のタブのアイコン表示を更新 CORE::core_set_command( "toggle_board_icon", url_boardbase() ); // サイドバーのアイコン表示を更新 CORE::core_set_command( "toggle_sidebar_boardicon", url_datbase() ); save_info(); } } else{ if( m_status & STATUS_UPDATE ){ #ifdef _DEBUG std::cout << "toggle_icon off\n"; #endif m_status &= ~STATUS_UPDATE; // サイドバーのアイコン表示を戻す // スレ一覧のタブのアイコンはBoardViewがロード終了時に自動的に戻す CORE::core_set_command( "toggle_sidebar_boardicon", url_boardbase() ); save_info(); } } } // 板の更新チェック時に、更新チェックを行うスレのアドレスのリスト // キャッシュが存在し、かつdat落ちしていないで新着数が0のスレを速度の順でソートして返す std::list< std::string > BoardBase::get_check_update_articles() { std::list< std::string > list_url; if( empty() ) return list_url; if( is_loading() ) return list_url; if( m_status & STATUS_UPDATE ) return list_url; if( ! m_list_subject_created ) { download_subject( std::string(), true ); return list_url; } #ifdef _DEBUG std::cout << "BoardBase::get_check_update_articles url = " << url_boardbase() << std::endl; #endif std::list< int > list_speed; for( ArticleBase* article : m_hash_article ) { if( article->is_cached() && article->get_number() && ! ( article->get_status() & STATUS_UPDATE ) && ! ( article->get_status() & STATUS_OLD ) && article->get_number() == article->get_number_load() ){ const std::string& url = article->get_url(); const int speed = article->get_speed(); #ifdef _DEBUG std::cout << "added " << url << " number = " << article->get_number() << " load = " << article->get_number_load() << " speed = " << speed << " " << article->get_subject() << std::endl; #endif // 挿入ソート std::list< std::string >::iterator it_url = list_url.begin(); std::list< int >::iterator it_speed = list_speed.begin(); for( ; it_url != list_url.end(); ++it_url, ++it_speed ) if( ( *it_speed ) < speed ) break; list_url.insert( it_url, url ); list_speed.insert( it_speed, speed ); } } #ifdef _DEBUG std::cout << "result of insert sorting\n"; std::list< std::string >::const_iterator it_url = list_url.begin(); std::list< int >::const_iterator it_speed = list_speed.begin(); for( ; it_url != list_url.end(); ++it_url, ++it_speed ) std::cout << ( *it_speed ) << " / " << ( *it_url ) << std::endl; #endif return list_url; } jdim-0.7.0/src/dbtree/boardbase.h000066400000000000000000000676411417047150700166140ustar00rootroot00000000000000// ライセンス: GPL2 // // 板情報クラスのベース // #ifndef _BOARDBASE_H #define _BOARDBASE_H #include "articlehash.h" #include "jdlib/jdregex.h" #include "skeleton/loadable.h" #include #include #include #include #include namespace JDLIB { class LOADERDATA; class Iconv; } namespace DBTREE { // ローカルプロキシ設定 enum { PROXY_GLOBAL = 0, // 全体設定使用 PROXY_DISABLE, // 全体設定無効 PROXY_LOCAL, // ローカル設定使用 PROXY_NUM }; struct ARTICLE_INFO { std::string id; std::string subject; int number; }; typedef std::vector< ARTICLE_INFO > ARTICLE_INFO_LIST; class Root; class ArticleBase; class BoardBase : public SKELETON::Loadable { // ArticleBaseクラス のキャッシュ // ArticleBaseクラスは一度作ったら~BoardBase()以外ではdeleteしないこと ArticleHash m_hash_article; // subject.txt から作ったArticleBaseクラスのポインタのリスト // subject.txt と同じ順番で、ロードされるたびに更新される std::vector< ArticleBase* > m_list_subject; // ダウンロード中に parse_subject() でsubject.txtを解析して結果を入れておく // ダウンロード終了後に regist_article() でスレを登録する ARTICLE_INFO_LIST m_list_artinfo; // 状態 ( global.h で定義 ) int m_status; // 一度でも m_list_subject が作られた(=subject.txtを開いた)らtrue bool m_list_subject_created{}; // ローカルルールのmodified 時刻 std::string m_modified_localrule; // setting.txtの modified 時刻 std::string m_modified_setting; // subjectダウンロード指示時(BoardBase::download_subject)にオンラインだったか bool m_is_online{}; // subjectダウンロード指示時(BoardBase::download_subject)にブート中だったか bool m_is_booting{}; // ビュワーでソートをする列番号、ソード順 int m_view_sort_column; int m_view_sort_mode; int m_view_sort_pre_column; int m_view_sort_pre_mode; // 名無し書き込み不可 bool m_check_noname{}; // 過去ログも表示する bool m_show_oldlog{}; // // subjectファイルのURLが "http://www.hoge2ch.net/hogeboard/subject.txt" // datファイルのURLが "http://www.hoge2ch.net/hogeboard/dat/12345.dat" // スレのURLが "http://www.hoge2ch.net/test/read.cgi/hogeboard/12345" のとき、 // // m_root = "http://www.hoge2ch.net" // m_path_board = "/hogeboard" // m_path_dat = "/dat" // m_path_readcgi = "/test/read.cgi" // m_path_bbscgi = "/test/bbs.cgi" // m_path_subbbscgi = "/test/subbbs.cgi" // m_subjecttxt = "subject.txt" // m_ext = ".dat" // m_id = "hogeboard" // m_charset = "MS932" // // 先頭に'/'を付けて最後に '/' は付けないことにフォーマットを統一 // std::string m_root; std::string m_path_board; std::string m_path_dat; std::string m_path_readcgi; std::string m_path_bbscgi; std::string m_path_subbbscgi; std::string m_subjecttxt; std::string m_ext; std::string m_id; std::string m_charset; std::string m_name; // 板名 // dat型のurlに変換する時のquery ( url_dat()で使用する ) JDLIB::RegexPattern m_query_dat; JDLIB::RegexPattern m_query_cgi; JDLIB::RegexPattern m_query_kako; // ローカルあぼーん情報(板内の全レス対象) std::list< std::string > m_list_abone_id; // あぼーんするID std::list< std::string > m_list_abone_name; // あぼーんする名前 std::list< std::string > m_list_abone_word; // あぼーんする文字列 std::list< std::string > m_list_abone_regex; // あぼーんする正規表現 // ローカルスレあぼーん情報 std::list< std::string > m_list_abone_thread; // あぼーんするスレのタイトル std::list< std::string > m_list_abone_thread_remove; // あぼーんするスレのタイトル( dat 落ち判定用、remove_old_abone_thread()を参照せよ ) std::list< std::string > m_list_abone_word_thread; // あぼーんする文字列 std::list< std::string > m_list_abone_regex_thread; // あぼーんする正規表現 int m_abone_low_number_thread{}; // nレス以下をあぼーんする int m_abone_high_number_thread{}; // nレス以上をあぼーんする int m_abone_hour_thread{}; // スレ立てからの経過時間 // 読み込み用ローカルプロキシ設定 int m_mode_local_proxy{}; std::string m_local_proxy; int m_local_proxy_port{}; std::string m_local_proxy_basicauth; // basic 認証用の「ユーザID:パスワード」の組 // 書き込み用ローカルプロキシ設定 int m_mode_local_proxy_w{}; std::string m_local_proxy_w; int m_local_proxy_port_w{}; std::string m_local_proxy_basicauth_w; // basic 認証用の「ユーザID:パスワード」の組 // 書き込み時のデフォルトの名前とメアド std::string m_write_name; std::string m_write_mail; // 最終書き込み時間 std::time_t m_write_time{}; // samba(秒) std::time_t m_samba_sec{}; // 実況時の更新間隔(秒) std::time_t m_live_sec{}; // 最終アクセス時刻 std::time_t m_last_access_time{}; // 最大レス数 int m_number_max_res{}; // ダウンロード用変数 std::list< std::string > m_url_update_views; // CORE::core_set_command( "update_board" ) を送信するビューのアドレス std::unique_ptr m_iconv; std::string m_rawdata; std::string m_rawdata_left; // 情報ファイルを読みこんだらtrueにして2度読みしないようにする bool m_read_info{}; // キャッシュにあるこの板に属するスレをデータベースに登録したか bool m_append_articles{}; // 移転を調査するために url_boardbase を読んでいる bool m_read_url_boardbase{}; // 書き込み時に必要なキーワード( hana=mogera や suka=pontan など ) // 書き込み時のメッセージに付加する std::string m_keyword_for_write; // スレ立て時に必要なキーワード // スレ立て時のメッセージに付加する std::string m_keyword_for_newarticle; // basic 認証用の「ユーザID:パスワード」の組 std::string m_basicauth; // get_article_fromURL()のキャッシュ std::string m_get_article_url; ArticleBase* m_get_article{}; // remove_old_abone_thread() をキャンセルするか bool m_cancel_remove_abone_thread{}; // Null artice クラス std::unique_ptr m_article_null; /// 板のユーザーエージェント設定 (空のときは全体設定を使う) std::string m_board_agent; protected: ARTICLE_INFO_LIST& get_list_artinfo(){ return m_list_artinfo; } std::list< std::string >& get_url_update_views(){ return m_url_update_views; } ArticleBase* get_article_null(); ArticleBase* get_article( const std::string& datbase, const std::string& id ); ArticleBase* get_article_create( const std::string& datbase, const std::string& id ); ArticleBase* insert( std::unique_ptr article ); void set_path_dat( const std::string& str ){ m_path_dat = str; } void set_path_readcgi( const std::string& str ){ m_path_readcgi = str; } void set_path_bbscgi( const std::string& str ){ m_path_bbscgi = str; } void set_path_subbbscgi( const std::string& str ){ m_path_subbbscgi = str; } void set_subjecttxt( const std::string& str ){ m_subjecttxt = str; } void set_ext( const std::string& str ){ m_ext = str; } void set_id( const std::string& str ){ m_id = str; } void set_charset( const std::string& str ){ m_charset = str; } // articleがスレあぼーんされているか bool is_abone_thread( ArticleBase* article ); // m_url_update_views に登録されている view に update_board コマンドを送る void send_update_board(); // クッキー virtual std::string get_hap() const { return {}; } virtual void set_hap( const std::string& hap ){} // クッキーの更新 (クッキーをセットした時に実行) virtual void update_hap(){} // subject.txt の URLを取得 // (例) "http://hoge.2ch.net/hogeboard/subject.txt" std::string url_subject() const; public: BoardBase( const std::string& root, const std::string& path_board, const std::string& name ); ~BoardBase(); bool empty() const; // 状態 ( global.hで定義 ) int get_status() const { return m_status; } // boardviewに表示するスレッドのリストを取得 std::vector< ArticleBase* >& get_list_subject(){ return m_list_subject; } // ローカルルールの modified 時刻 const std::string& get_modified_localrule() const { return m_modified_localrule; } void set_modified_localrule( const std::string& modified ){ m_modified_localrule = modified; } // setting.txtの modified 時刻 const std::string& get_modified_setting() const { return m_modified_setting; } void set_modified_setting( const std::string& modified ){ m_modified_setting = modified; } // boardviewでソートする列番号とソート順 int get_view_sort_column() const { return m_view_sort_column; } void set_view_sort_column( int column ){ m_view_sort_column = column; } int get_view_sort_mode() const { return m_view_sort_mode; } void set_view_sort_mode( int mode ){ m_view_sort_mode = mode; } int get_view_sort_pre_column() const { return m_view_sort_pre_column; } void set_view_sort_pre_column( int column ) { m_view_sort_pre_column = column; } int get_view_sort_pre_mode() const { return m_view_sort_pre_mode; } void set_view_sort_pre_mode( int mode ){ m_view_sort_pre_mode = mode; } // 名無し書き込み不可 bool get_check_noname() const { return m_check_noname; } void set_check_noname( const bool check ){ m_check_noname = check; } // 過去ログも表示する bool get_show_oldlog() const { return m_show_oldlog; } void set_show_oldlog( const bool show ){ m_show_oldlog = show; } // url がこの板のものかどうか virtual bool equal( const std::string& url ) const; // 移転などで板のルートやパスを変更する void update_url( const std::string& root, const std::string& path_board ); const std::string& get_root() const{ return m_root; } const std::string& get_path_board() const { return m_path_board; } const std::string& get_ext() const { return m_ext; } const std::string& get_id() const { return m_id; } const std::string& get_charset() const { return m_charset; } const std::string& get_name() const { return m_name; } void update_name( const std::string& name ); const std::string& get_subjecttxt() const { return m_subjecttxt; } // ユーザーエージェント virtual const std::string& get_agent() const; // ダウンロード用 virtual const std::string& get_agent_w() const; // 書き込み用 // ダウンロード時のプロキシ virtual std::string get_proxy_host() const; virtual int get_proxy_port() const; virtual std::string get_proxy_basicauth() const; // 書き込み時のプロキシ virtual std::string get_proxy_host_w() const; virtual int get_proxy_port_w() const; virtual std::string get_proxy_basicauth_w() const; // ローカルルール virtual std::string localrule() const; // SETTING.TXT virtual std::string settingtxt() const; // 書き込みの時のデフォルト名 virtual std::string default_noname() const; // 最大改行数/2 virtual int line_number() const; // 最大書き込みバイト数 virtual int message_count() const; // 特殊文字書き込み可能か( pass なら可能、 change なら不可 ) virtual std::string get_unicode() const; // 板のホストを指定してクッキーのやり取り std::string cookie_by_host() const; virtual std::string cookie_for_request() const; virtual std::string cookie_for_post() const; void set_list_cookies( const std::list< std::string >& list_cookies ); void delete_cookies(); // 書き込み時に必要なキーワード( hana=mogera や suka=pontan など ) // 書き込み時のメッセージに付加する const std::string& get_keyword_for_write() const { return m_keyword_for_write; } void set_keyword_for_write( const std::string& keyword ){ m_keyword_for_write = keyword; } // スレ立て時に必要なキーワード // スレ立て時のメッセージに付加する const std::string& get_keyword_for_newarticle() const { return m_keyword_for_newarticle; } void set_keyword_for_newarticle( const std::string& keyword ){ m_keyword_for_newarticle = keyword; } // 書き込み時に必要なキーワード( hana=mogera や suka=pontan など )を // 確認画面のhtmlから解析する virtual void analyze_keyword_for_write( const std::string& html ){} // スレ立て時に必要なキーワードをフロントページのhtmlから解析する virtual void analyze_keyword_for_newarticle( const std::string& html ) {} // 確認画面のHTMLから書き込み、スレ立て時に使うフォームデータを取得する virtual std::string parse_form_data( const std::string& html ) { return {}; } // 書き込み時のリファラ virtual std::string get_write_referer( const std::string& url ){ return url_boardbase(); } // スレ立て時のリファラ virtual std::string get_newarticle_referer( const std::string& url ) { return url_boardbase(); } // basic認証 const std::string& get_basicauth() const { return m_basicauth; } void set_basicauth( const std::string& basicauth ){ m_basicauth = basicauth; } // スレの url を dat型のurlに変換して出力 // url がスレッドのURLで無い時はempty()が返る // もしurlが移転前の旧ホストのものだったら対応するarticlebaseクラスに旧ホスト名を知らせる // (例) url = "http://www.hoge2ch.net/test/read.cgi/hogeboard/12345/12-15"のとき、 // "http://www.hoge2ch.net/hogeboard/dat/12345.dat", num_from = 12, num_to = 15, num_str = "12-15" virtual std::string url_dat( const std::string& url, int& num_from, int& num_to, std::string& num_str ); // スレの url を read.cgi型のurlに変換 // url がスレッドのURLで無い時はempty()が返る // num_from と num_to が 0 で無い時はスレ番号を付ける // (例) "http://www.hoge2ch.net/hogeboard/dat/12345.dat", num_from = 12, num_to = 15 のとき // "http://www.hoge2ch.net/test/read.cgi/hogeboard/12345/12-15" virtual std::string url_readcgi( const std::string& url, int num_from, int num_to ); // SETTING.TXT の URLを取得 // (例) "http://hoge.2ch.net/hogeboard/SETTING.TXT" virtual std::string url_settingtxt() const { return {}; } // ルートアドレス // (例) "http://www.hoge2ch.net/hogeboard/" なら "http://www.hoge2ch.net/" std::string url_root() const; // 板のベースアドレス // (例) "http://www.hoge2ch.net/hogeboard/" std::string url_boardbase() const; // dat ファイルのURLのベースアドレスを返す // (例) "http://www.hoge2ch.net/hogeboard/dat/12345.dat" なら "http://www.hoge2ch.net/hogeboard/dat/" std::string url_datbase() const; // dat ファイルのURLのパスを返す // (例) "http://www.hoge2ch.net/hogeboard/dat/12345.dat" なら "/hogeboard/dat/" virtual std::string url_datpath() const; // read.cgi のURLのベースアドレスを返す // (例) "http://www.hoge2ch.net/test/read.cgi/hogeboard/12345" なら "http://www.hoge2ch.net/test/read.cgi/hogeboard/" std::string url_readcgibase() const; // read.cgi のURLのパスを返す // (例) "http://www.hoge2ch.net/test/read.cgi/hogeboard/12345" なら "/test/read.cgi/hogeboard/" std::string url_readcgipath() const; // bbscgi のURLのベースアドレス // (例) "http://www.hoge2ch.net/test/bbs.cgi/" ( 最後に '/' がつく ) std::string url_bbscgibase() const; // subbbscgi のURLのベースアドレス // (例) "http://www.hoge2ch.net/test/subbbs.cgi/" ( 最後に '/' がつく ) std::string url_subbbscgibase() const; // article クラスのポインタ取得 ArticleBase* get_article_fromURL( const std::string& url ); // フロントページのダウンロード virtual void download_front() {} // subject.txt ダウンロード // url_update_view : CORE::core_set_command( "update_board" ) を送信するビューのアドレス // read_from_cache : まだスレ一覧を開いていないときにキャッシュのsubject.txtを読み込む virtual void download_subject( const std::string& url_update_view, const bool read_from_cache ); // 新スレ作成用のメッセージ変換 virtual std::string create_newarticle_message( const std::string& subject, const std::string& name, const std::string& mail, const std::string& msg ) { return {}; } // 新スレ作成用のbbscgi のURL virtual std::string url_bbscgi_new() const { return {}; } // 新スレ作成用のsubbbscgi のURL virtual std::string url_subbbscgi_new() const { return {}; } // 配下の全articlebaseクラスのあぼーん状態の更新 void update_abone_all_article(); // 板レベルでのあぼーん情報 const std::list< std::string >& get_abone_list_id_board(){ return m_list_abone_id; } const std::list< std::string >& get_abone_list_name_board(){ return m_list_abone_name; } const std::list< std::string >& get_abone_list_word_board(){ return m_list_abone_word; } const std::list< std::string >& get_abone_list_regex_board(){ return m_list_abone_regex; } // 板レベルでのあぼーん状態のリセット(情報セットとスレビューの表示更新を同時におこなう) void reset_abone_board( const std::list< std::string >& ids, const std::list< std::string >& names, const std::list< std::string >& words, const std::list< std::string >& regexs ); // 板レベルでのあぼ〜ん状態更新(reset_abone()と違って各項目ごと個別におこなう。スレビューの表示更新も同時におこなう) void add_abone_id_board( const std::string& id ); void add_abone_name_board( const std::string& name ); void add_abone_word_board( const std::string& word ); // スレあぼーん情報 const std::list< std::string >& get_abone_list_thread(){ return m_list_abone_thread; } const std::list< std::string >& get_abone_list_thread_remove(){ return m_list_abone_thread_remove; } const std::list< std::string >& get_abone_list_word_thread(){ return m_list_abone_word_thread; } const std::list< std::string >& get_abone_list_regex_thread(){ return m_list_abone_regex_thread; } int get_abone_low_number_thread() const noexcept { return m_abone_low_number_thread; } int get_abone_high_number_thread() const noexcept { return m_abone_high_number_thread; } int get_abone_hour_thread() const noexcept { return m_abone_hour_thread; } // subject.txtのロード後にdat落ちしたスレッドをスレあぼーんのリストから取り除く void remove_old_abone_thread(); // スレあぼーん情報を更新した時に対応するスレ一覧の表示を更新する // CONFIG::set_abone_number_thread() などでグローバル設定をした後などに呼び出す // redraw : スレ一覧の表示更新を行う void update_abone_thread( const bool redraw ); // スレあぼーん状態のリセット(情報セットとスレ一覧の表示更新を同時におこなう) // redraw : スレ一覧の表示更新を行う void reset_abone_thread( const std::list< std::string >& threads, const std::list< std::string >& words, const std::list< std::string >& regexs, const int low_number, const int high_number, const int hour, const bool redraw ); // ローカルプロキシ設定 int get_mode_local_proxy() const { return m_mode_local_proxy; } const std::string& get_local_proxy() const { return m_local_proxy; } int get_local_proxy_port() const { return m_local_proxy_port; } const std::string& get_local_proxy_basicauth() const { return m_local_proxy_basicauth; } void set_mode_local_proxy( int mode ){ m_mode_local_proxy = mode; } void set_local_proxy( const std::string& proxy ); void set_local_proxy_port( int port ){ m_local_proxy_port = port; } int get_mode_local_proxy_w() const { return m_mode_local_proxy_w; } const std::string& get_local_proxy_w() const { return m_local_proxy_w; } int get_local_proxy_port_w() const { return m_local_proxy_port_w; } const std::string& get_local_proxy_basicauth_w() const { return m_local_proxy_basicauth_w; } void set_mode_local_proxy_w( int mode ){ m_mode_local_proxy_w = mode; } void set_local_proxy_w( const std::string& proxy ); void set_local_proxy_port_w( int port ){ m_local_proxy_port_w = port; } // 書き込み時のデフォルトの名前とメアド const std::string& get_write_name() const { return m_write_name; } const std::string& get_write_mail() const { return m_write_mail; } void set_write_name( const std::string& name ){ m_write_name = name; } void set_write_mail( const std::string& mail ){ m_write_mail = mail; } // 最終書き込み時間 void update_writetime(); time_t get_write_time() const noexcept { return m_write_time; } // 秒 time_t get_write_pass() const; // 経過時間(秒) time_t get_samba_sec() const { return m_samba_sec; } // samba(秒) void set_samba_sec( time_t sec ){ m_samba_sec = sec; } time_t get_write_leftsec() const; // 書き込み可能までの残り秒 // 全書き込み履歴クリア void clear_all_post_history(); // 全スレの書き込み時間とスレ立て時間の文字列をリセット void reset_all_since_date(); void reset_all_write_date(); void reset_all_access_date(); // 実況の秒数 time_t get_live_sec() const{ return m_live_sec; } void set_live_sec( time_t sec ){ m_live_sec = sec; } // 最終アクセス時刻 time_t get_last_access_time() const{ return m_last_access_time; } // 板のユーザーエージェント設定 const std::string& get_board_agent() const { return m_board_agent; } void set_board_agent( const std::string& user_agent ) { m_board_agent = user_agent; } // 最大レス数 int get_number_max_res() const { return m_number_max_res; } void set_number_max_res( const int number ); // datの最大サイズ(Kバイト) virtual int get_max_dat_lng() const { return 0; } // 板情報の取得 virtual void read_info(); // 情報保存 virtual void save_info(); // 配下の全articlebaseクラスの情報保存 void save_articleinfo_all(); // キャッシュ内のログ検索 // ArticleBase のアドレスをリスト(list_article)にセットして返す // query が空の時はキャッシュにあるログを全てヒットさせる // bm がtrueの時、しおりが付いている(スレ一覧でしおりを付けた or レスに一つでもしおりが付いている)スレのみを対象に検索する virtual void search_cache( std::vector< ArticleBase* >& list_article, const std::string& query, const bool mode_or, // 今のところ無視 const bool bm, const bool stop // 呼出元のスレッドで true にセットすると検索を停止する ); // datファイルのインポート // 成功したらdat型のurlを返す virtual std::string import_dat( const std::string& filename ); // 更新可能状態にしてお気に入りやスレ一覧のタブのアイコンに更新マークを表示 // update == true の時に表示。falseなら戻す void show_updateicon( const bool update ); // 板の更新チェック時に、更新チェックを行うスレのアドレスのリスト // キャッシュが存在し、かつdat落ちしていないで新着数が0のスレを速度の順でソートして返す std::list< std::string > get_check_update_articles(); private: void clear(); // デフォルト最大レス数( 0 : 未設定 ) virtual int get_default_number_max_res() const { return 0; } // キャッシュのファイル名が正しいかどうか virtual bool is_valid( const std::string& filename ) const { return false; } virtual void create_loaderdata( JDLIB::LOADERDATA& data ); void receive_data( const char* data, size_t size ) override; void receive_finish() override; // url_boardbase をロードして移転したかどうか解析開始 bool start_checkking_if_board_moved(); virtual ArticleBase* append_article( const std::string& datbase, const std::string& id, const bool cached ); virtual void parse_subject( const char* str_subject_txt ){} virtual void regist_article( const bool is_online ){} std::list get_filelist_in_cache() const; void read_board_info(); virtual void append_all_article_in_cache(); void save_summary(); void save_board_info(); void save_jdboard_info(); // ローカルルールとsetting.txtの読み込み及びダウンロード virtual void load_rule_setting(){} virtual void download_rule_setting(){} // レス数であぼーん(グローバル) // 2ch以外の板ではキャンセルする virtual int get_abone_low_number_global() const { return 0; } virtual int get_abone_high_number_global() const { return 0; } }; } #endif jdim-0.7.0/src/dbtree/boardfactory.cpp000066400000000000000000000020271417047150700176670ustar00rootroot00000000000000// ライセンス: GPL2 #include "boardfactory.h" #include "board2ch.h" #include "board2chcompati.h" #include "boardlocal.h" #include "boardjbbs.h" #include "boardmachi.h" #include "type.h" std::unique_ptr DBTREE::BoardFactory( int type, const std::string& root, const std::string& path_board, const std::string& name, const std::string& basicauth ) { switch( type ) { case TYPE_BOARD_2CH: return std::make_unique( root, path_board, name ); case TYPE_BOARD_2CH_COMPATI: return std::make_unique( root, path_board, name, basicauth ); case TYPE_BOARD_LOCAL: return std::make_unique( root, path_board, name ); case TYPE_BOARD_JBBS: return std::make_unique( root, path_board, name ); case TYPE_BOARD_MACHI: return std::make_unique( root, path_board, name ); default: return nullptr; } } jdim-0.7.0/src/dbtree/boardfactory.h000066400000000000000000000006631417047150700173400ustar00rootroot00000000000000// ライセンス: GPL2 // // BoardBaseのファクトリー // #ifndef _BOARDFACTORY_H #define _BOARDFACTORY_H #include #include namespace DBTREE { class BoardBase; std::unique_ptr BoardFactory( int type, const std::string& root, const std::string& path_board, const std::string& name, const std::string& basicauth ); } #endif jdim-0.7.0/src/dbtree/boardjbbs.cpp000066400000000000000000000266241417047150700171510ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "boardjbbs.h" #include "articlejbbs.h" #include "articlehash.h" #include "ruleloader.h" #include "settingloader.h" #include "jdlib/miscutil.h" #include "jdlib/miscmsg.h" #include "jdlib/loaderdata.h" #include "config/globalconf.h" #include "global.h" #include "httpcode.h" #include #include #include using namespace DBTREE; BoardJBBS::BoardJBBS( const std::string& root, const std::string& path_board, const std::string& name ) : BoardBase( root, path_board, name ) { // dat のURLは特殊なので url_datpath()をオーバライドする set_path_dat( "" ); set_path_readcgi( "/bbs/read.cgi" ); set_path_bbscgi( "/bbs/write.cgi" ); set_path_subbbscgi( "/bbs/write.cgi" ); set_subjecttxt( "subject.txt" ); set_ext( "" ); set_id( path_board.substr( 1 ) ); // 先頭の '/' を除く set_charset( "EUCJP-WIN" ); } BoardJBBS::~BoardJBBS() noexcept { if( m_settingloader ) { m_settingloader->terminate_load(); } if( m_ruleloader ) { m_ruleloader->terminate_load(); } } // // キャッシュのファイル名が正しいか // bool BoardJBBS::is_valid( const std::string& filename ) const { if( filename.length() != 10 ) return false; return std::all_of( filename.cbegin(), filename.cend(), []( char c ) { return '0' <= c && c <= '9'; } ); } // // 新しくArticleBaseクラスを追加してそのポインタを返す // // cached : HDD にキャッシュがあるならtrue // ArticleBase* BoardJBBS::append_article( const std::string& datbase, const std::string& id, const bool cached ) { if( empty() ) return get_article_null(); ArticleBase* article = insert( std::make_unique( datbase, id, cached ) ); if( article ){ // 最大レス数セット article->set_number_max( get_number_max_res() ); } else return get_article_null(); return article; } // // rawmode のURLのパスを返す // // (例) "/bbs/rawmode.cgi/board/" (最初と最後に '/' がつく) // std::string BoardJBBS::url_datpath() const { if( empty() ) return std::string(); return "/bbs/rawmode.cgi" + get_path_board() + "/"; } std::string BoardJBBS::create_newarticle_message( const std::string& subject, const std::string& name, const std::string& mail, const std::string& msg ) { if( subject.empty() ) return std::string(); if( msg.empty() ) return std::string(); // DIR と BBS を分離する( ID = DIR/BBS ) std::string boardid = get_id(); int i = boardid.find( '/' ); std::string dir = boardid.substr( 0, i ); std::string bbs = boardid.substr( i + 1 ); std::stringstream ss_post; ss_post.clear(); ss_post << "SUBJECT=" << MISC::charset_url_encode( subject, get_charset() ) << "&submit=" << MISC::charset_url_encode( "新規書き込み", get_charset() ) << "&NAME=" << MISC::charset_url_encode( name, get_charset() ) << "&MAIL=" << MISC::charset_url_encode( mail, get_charset() ) << "&MESSAGE=" << MISC::charset_url_encode( msg, get_charset() ) << "&DIR=" << dir << "&BBS=" << bbs << "&TIME=" << get_time_modified(); #ifdef _DEBUG std::cout << "BoardJBBS::create_newarticle_message " << ss_post.str() << std::endl; #endif return ss_post.str(); } // // 新スレ作成時のbbscgi(write.cgi) のURL // // (例) "http://jbbs.shitaraba.net/bbs/write.cgi/computer/123/new/" // // std::string BoardJBBS::url_bbscgi_new() const { return url_bbscgibase() + get_id() + "/new/"; } // // 新スレ作成時のsubbbscgi のURL // // (例) "http://jbbs.shitaraba.net/bbs/write.cgi/computer/123/new/" // std::string BoardJBBS::url_subbbscgi_new() const { return url_subbbscgibase() + get_id() + "/new/"; } std::string BoardJBBS::localrule() const { if( m_ruleloader ){ if( m_ruleloader->is_loading() ) return "ロード中です"; else if( m_ruleloader->get_code() == HTTP_OK || m_ruleloader->get_code() == HTTP_REDIRECT || m_ruleloader->get_code() == HTTP_MOVED_PERM ) { const std::string& data = m_ruleloader->get_data(); return data.empty() ? "ローカルルールはありません" : data; } return "ロードに失敗しました : " + m_ruleloader->get_str_code(); } return BoardBase::localrule(); } std::string BoardJBBS::settingtxt() const { if( m_settingloader ){ if( m_settingloader->is_loading() ) return "ロード中です"; else if( m_settingloader->get_code() == HTTP_OK || m_settingloader->get_code() == HTTP_REDIRECT || m_settingloader->get_code() == HTTP_MOVED_PERM ) { const std::string& data = m_settingloader->get_data(); return data.empty() ? "SETTING.TXTはありません" : data; } return "ロードに失敗しました : " + m_settingloader->get_str_code(); } return BoardBase::settingtxt(); } std::string BoardJBBS::default_noname() const { if( m_settingloader && m_settingloader->get_code() == HTTP_OK ) { return m_settingloader->default_noname(); } return BoardBase::default_noname(); } // // SETTING.TXT のURL // // (例) "http://jbbs.shitaraba.net/bbs/api/setting.cgi/computer/123/" // std::string BoardJBBS::url_settingtxt() const { return get_root() + "/bbs/api/setting.cgi/" + get_id() + "/"; } // // ローカルルールとSETTING.TXTをキャッシュから読み込む // // BoardBase::read_info()で呼び出す // void BoardJBBS::load_rule_setting() { if( ! m_ruleloader ) m_ruleloader = std::make_unique( url_boardbase(), "MS932" ); m_ruleloader->load_text(); if( ! m_settingloader ) m_settingloader = std::make_unique( url_boardbase() ); m_settingloader->load_text(); } // // SETTING.TXTをサーバからダウンロード // ローカルルールはスレ本文とエンコーディングが異なる // // 読み込むタイミングはsubject.txtを読み終わった直後( BoardBase::receive_finish() ) // void BoardJBBS::download_rule_setting() { if( ! m_ruleloader ) m_ruleloader = std::make_unique( url_boardbase(), "MS932" ); m_ruleloader->download_text(); if( ! m_settingloader ) m_settingloader = std::make_unique( url_boardbase() ); m_settingloader->download_text(); } // // subject.txt から Aarticle のリストにアイテムを追加・更新 // void BoardJBBS::parse_subject( const char* str_subject_txt ) { #ifdef _DEBUG std::cout << "BoardJBBS::parse_subject\n"; #endif const char* pos = str_subject_txt; while( *pos != '\0' ){ const char* str_id_dat; int lng_id_dat = 0; const char* str_subject; int lng_subject = 0; // datのID取得 // ".cgi" は除く str_id_dat = pos; while( *pos != '.' && *pos != '\0' && *pos != '\n' ) { ++pos; ++lng_id_dat; } // 壊れてる if( *pos == '\0' ) break; if( *pos == '\n' ) { ++pos; continue; } // subject取得 pos += 5; // ' ".cgi," str_subject = pos; while( *pos != '\0' && *pos != '\n' ) ++pos; --pos; while( *pos != '(' && *pos != '\n' && pos != str_subject_txt ) --pos; // 壊れてる if( *pos == '\n' || pos == str_subject_txt ){ MISC::ERRMSG( "subject.txt is broken" ); break; } lng_subject = ( int )( pos - str_subject ); // レス数取得 (符号付き32bit整数より大きいと未定義) ++pos; std::string str_num; while( '0' <= *pos && *pos <= '9' ) str_num.push_back( *( pos++ ) ); // 壊れてる if( str_num.empty() ){ MISC::ERRMSG( "subject.txt is broken (res)" ); break; } if( *pos == '\0' ) break; if( *pos == '\n' ) { ++pos; continue; } ++pos; // id, subject, number 取得 ARTICLE_INFO artinfo; artinfo.id.assign( str_id_dat, lng_id_dat ); artinfo.id = MISC::remove_space( artinfo.id ); artinfo.subject.assign( str_subject, lng_subject ); artinfo.subject = MISC::remove_space( artinfo.subject ); artinfo.subject = MISC::replace_str( artinfo.subject, "<", "<" ); artinfo.subject = MISC::replace_str( artinfo.subject, ">", ">" ); const auto num = std::atoi( str_num.c_str() ); artinfo.number = ( num < CONFIG::get_max_resnumber() ) ? num : CONFIG::get_max_resnumber(); get_list_artinfo().push_back( artinfo ); #ifdef _DEBUG std::cout << "pos = " << ( pos - str_subject_txt ) << " lng = " << lng_subject << " id = " << artinfo.id << " num = " << artinfo.number; std::cout << " : " << artinfo.subject << std::endl; #endif } } void BoardJBBS::regist_article( const bool is_online ) { if( ! get_list_artinfo().size() ) return; #ifdef _DEBUG std::cout << "BoardJBBS::regist_article size = " << get_list_artinfo().size() << std::endl; #endif ArticleBase* article_first = nullptr; const std::string datbase = url_datbase(); for( const ARTICLE_INFO& artinfo : get_list_artinfo() ) { // DBに登録されてるならarticle クラスの情報更新 ArticleBase* article = get_article( datbase, artinfo.id ); // DBにないなら新規に article クラスを追加 // // なお BoardBase::receive_finish() のなかで append_all_article_in_cache() が既に呼び出されているため // DBに無いということはキャッシュにも無いということ。よって append_article()で cached = false if( article->empty() ) article = append_article( datbase, artinfo.id, false ); // スレ情報更新 if( article ){ // ステータスをDAT落ち状態から通常状態に変更 int status = article->get_status(); status |= STATUS_NORMAL; status &= ~STATUS_OLD; article->set_status( status ); // 情報ファイル読み込み article->read_info(); // 情報ファイルが無い場合もあるのでsubject.txtから取得したサブジェクト、レス数を指定しておく article->set_subject( artinfo.subject ); article->set_number( artinfo.number, is_online ); // boardビューに表示するリスト更新 // JBBSは最初と最後の行が同じになる仕様があるので最後の行を除く bool pushback = true; if( ! article_first ) article_first = article; else if( article == article_first ) pushback = false; if( pushback ){ // 情報ファイル読み込み後にステータスが変わることがあるので、もう一度 // ステータスをDAT落ち状態から通常状態に変更 status = article->get_status(); status |= STATUS_NORMAL; status &= ~STATUS_OLD; article->set_status( status ); // boardビューに表示するリスト更新 if( ! BoardBase::is_abone_thread( article ) ) get_list_subject().push_back( article ); } } } } jdim-0.7.0/src/dbtree/boardjbbs.h000066400000000000000000000033421417047150700166060ustar00rootroot00000000000000// ライセンス: GPL2 // // JBBS 型板 // #ifndef _BOARDJBBS_H #define _BOARDJBBS_H #include "boardbase.h" #include namespace DBTREE { class RuleLoader; class SettingLoader; class BoardJBBS : public BoardBase { std::unique_ptr m_ruleloader; std::unique_ptr m_settingloader; public: BoardJBBS( const std::string& root, const std::string& path_board,const std::string& name ); ~BoardJBBS() noexcept; std::string url_datpath() const override; // 新スレ作成用のメッセージ変換 std::string create_newarticle_message( const std::string& subject, const std::string& name, const std::string& mail, const std::string& msg ) override; // 新スレ作成用のbbscgi のURL std::string url_bbscgi_new() const override; // 新スレ作成用のsubbbscgi のURL std::string url_subbbscgi_new() const override; // ローカルルール std::string localrule() const override; // SETTING.TXT std::string settingtxt() const override; std::string default_noname() const override; // SETTING.TXT のURL std::string url_settingtxt() const override; private: bool is_valid( const std::string& filename ) const override; ArticleBase* append_article( const std::string& datbase, const std::string& id, const bool cached ) override; void parse_subject( const char* str_subject_txt ) override; void regist_article( const bool is_online ) override; void load_rule_setting() override; void download_rule_setting() override; }; } #endif jdim-0.7.0/src/dbtree/boardlocal.cpp000066400000000000000000000063231417047150700173150ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "boardlocal.h" #include "articlelocal.h" #include "articlehash.h" #include "jdlib/miscutil.h" #include "jdlib/jdregex.h" #include "cache.h" using namespace DBTREE; BoardLocal::BoardLocal( const std::string& root, const std::string& path_board, const std::string& name ) : Board2chCompati( root, path_board, name, std::string() ) { #ifdef _DEBUG std::cout << "BoardLocal::BoardLocal\n"; #endif } BoardLocal::~BoardLocal() noexcept = default; // // url がこの板のものかどうか // bool BoardLocal::equal( const std::string& url ) const { return url.rfind( "file://", 0 ) == 0; } // そのまま出力 std::string BoardLocal::url_dat( const std::string& url, int& num_from, int& num_to, std::string& num_str ) { num_from = 0; num_to = 0; num_str = std::string(); return url_readcgi( url, num_from, num_to ); } // そのまま出力 std::string BoardLocal::url_readcgi( const std::string& url, int num_from, int num_to ) { JDLIB::Regex regex; const size_t offset = 0; const bool icase = false; const bool newline = true; const bool usemigemo = false; const bool wchar = false; const std::string query = "^ *file://.*/[1234567890]+" + get_ext() + " *$"; if( ! regex.exec( query , url, offset, icase, newline, usemigemo, wchar ) ) return std::string(); #ifdef _DEBUG std::cout << "BoardLocal::url_readcgi : url = " << url << std::endl; #endif return url; } void BoardLocal::download_subject( const std::string& url_update_view, const bool ) { // ダウンロードを実行しない get_url_update_views().push_back( url_update_view ); send_update_board(); } ArticleBase* BoardLocal::append_article( const std::string& datbase, const std::string& id, const bool cached ) { #ifdef _DEBUG std::cout << "BoardLocal::append_article datbase = " << datbase << ", id = " << id << std::endl; #endif ArticleBase* article = insert( std::make_unique( datbase, id ) ); if( article ){ // subject にも追加する get_list_subject().push_back( article ); } else return get_article_null(); return article; } // datファイルのインポート std::string BoardLocal::import_dat( const std::string& filename ) { if( empty() ) return {}; if( CACHE::file_exists( filename ) != CACHE::EXIST_FILE ) return std::string(); int num_from, num_to; std::string num_str; const std::string urldat = url_dat( filename, num_from, num_to, num_str ); if( urldat.empty() ) return std::string(); const std::string datbase = MISC::get_dir( urldat ); const std::string id = urldat.substr( datbase.length() ); if( ! id.empty() ) return std::string(); ArticleBase* art = get_article( datbase, id ); // データベースに無いのでインポート if( art->empty() ){ #ifdef _DEBUG std::cout << "BoardLocal::import_dat file = " << filename << std::endl; #endif art = append_article( datbase, id, true // キャッシュあり ); assert( art ); art->read_info(); return filename; } return std::string(); } jdim-0.7.0/src/dbtree/boardlocal.h000066400000000000000000000031021417047150700167520ustar00rootroot00000000000000// ライセンス: GPL2 // // ローカルファイル用仮想板 // #ifndef _BOARDLOCAL_H #define _BOARDLOCAL_H #include "board2chcompati.h" namespace DBTREE { class BoardLocal : public Board2chCompati { public: BoardLocal( const std::string& root, const std::string& path_board, const std::string& name ); ~BoardLocal() noexcept; // url がこの板のものかどうか bool equal( const std::string& url ) const override; std::string url_dat( const std::string& url, int& num_from, int& num_to, std::string& num_str ) override; std::string url_readcgi( const std::string& url, int num_from, int num_to ) override; std::string url_datpath() const override { return {}; } void download_subject( const std::string& url_update_view, const bool ) override; // 板情報の読み書きをキャンセル void read_info() override {} void save_info() override {} // キャッシュサーチをキャンセル void search_cache( std::vector< ArticleBase* >&, const std::string&, const bool, const bool, const bool ) override {} // datファイルのインポート std::string import_dat( const std::string& filename ) override; private: ArticleBase* append_article( const std::string& datbase, const std::string& id, const bool cached ) override; void append_all_article_in_cache() override {} void load_rule_setting() override {} void download_rule_setting() override {} }; } #endif jdim-0.7.0/src/dbtree/boardmachi.cpp000066400000000000000000000242301417047150700173010ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "boardmachi.h" #include "articlemachi.h" #include "articlehash.h" #include "jdlib/miscutil.h" #include "jdlib/miscmsg.h" #include "jdlib/loaderdata.h" #include "jdlib/jdregex.h" #include "config/globalconf.h" #include "global.h" #include #include #include using namespace DBTREE; BoardMachi::BoardMachi( const std::string& root, const std::string& path_board, const std::string& name ) : BoardBase( root, path_board, name ) { // dat のURLは特殊なので url_datpath()をオーバライドする set_path_dat( "" ); set_path_readcgi( "/bbs/read.cgi" ); set_path_bbscgi( "/bbs/write.cgi" ); set_path_subbbscgi( "/bbs/write.cgi" ); set_subjecttxt( "subject.txt" ); set_ext( "" ); set_id( path_board.substr( 1 ) ); // 先頭の '/' を除く set_charset( "MS932" ); } // // url がこの板のものかどうか // bool BoardMachi::equal( const std::string& url ) const { if( url.rfind( get_root(), 0 ) == 0 ){ if( url.find( get_path_board() + "/" ) != std::string::npos ) return true; if( url.find( "BBS=" + get_id() ) != std::string::npos ) return true; } return false; } // // キャッシュのファイル名が正しいか // bool BoardMachi::is_valid( const std::string& filename ) const { if( filename.length() != 10 ) return false; return std::all_of( filename.cbegin(), filename.cend(), []( char c ) { return '0' <= c && c <= '9'; } ); } // // 新しくArticleBaseクラスを追加してそのポインタを返す // // cached : HDD にキャッシュがあるならtrue // ArticleBase* BoardMachi::append_article( const std::string& datbase, const std::string& id, const bool cached ) { if( empty() ) return get_article_null(); ArticleBase* article = insert( std::make_unique( datbase, id, cached ) ); if( article ){ // 最大レス数セット article->set_number_max( get_number_max_res() ); } else return get_article_null(); return article; } // // スレの url を dat型のurlに変換して出力 // // (例) "http://hoge.machi.to/bbs/read.cgi?BBS=board&KEY=12345&START=12&END=15"" のとき // 戻り値 : "http://hoge.machi.to/bbs/read.cgi?BBS=board&KEY=12345", num_from = 12, num_to = 15, num_str = 12-15 // std::string BoardMachi::url_dat( const std::string& url, int& num_from, int& num_to, std::string& num_str ) { if( empty() ) return std::string(); // read.cgi 型の判定 std::string urldat = BoardBase::url_dat( url, num_from, num_to, num_str ); if( ! urldat.empty() ) return urldat; // 旧形式(read.pl)型の場合 if( url.find( "read.pl" ) != std::string::npos ){ urldat = BoardBase::url_dat( MISC::replace_str( url, "read.pl", "read.cgi" ), num_from, num_to, num_str ); if( ! urldat.empty() ) return urldat; } #ifdef _DEBUG std::cout << "BoardMachi::url_dat : url = " << url << std::endl; #endif JDLIB::Regex regex; const size_t offset = 0; const bool icase = false; const bool newline = true; const bool usemigemo = false; const bool wchar = false; std::string id; // スレッドのID num_from = num_to = 0; // dat 型 // LAST, START, END を考慮する const std::string datpath = MISC::replace_str( url_datpath(), "?", "\\?" ); const std::string query_dat = "^ *(https?://.+" + datpath + ")([1234567890]+" + get_ext() + ")(&LAST=[1234567890]+)?(&START=([1234567890])+)?(&END=([1234567890])+)? *$"; #ifdef _DEBUG std::cout << "query_dat = " << query_dat << std::endl; #endif if( regex.exec( query_dat , url, offset, icase, newline, usemigemo, wchar ) ){ id = regex.str( 2 ); if( regex.length( 5 ) ){ num_from = atoi( regex.str( 5 ).c_str() ); num_str = std::to_string( num_from ); } if( regex.length( 7 ) ){ num_to = atoi( regex.str( 7 ).c_str() ); num_str += "-" + std::to_string( num_to ); } #ifdef _DEBUG std::cout << "id = " << id << std::endl << "start = " << num_from << std::endl << "end = " << num_to << std::endl << "num = " << num_str << std::endl; #endif } else return std::string(); return url_datbase() + id; } // // read.cgi のURLのパスを返す // // (例) "/bbs/read.cgi?BBS=board&KEY=" (最初に '/' がつく) // std::string BoardMachi::url_datpath() const { if( empty() ) return std::string(); return "/bbs/read.cgi?BBS=" + get_id() + "&KEY="; } std::string BoardMachi::create_newarticle_message( const std::string& subject, const std::string& name, const std::string& mail, const std::string& msg ) { if( subject.empty() ) return std::string(); if( msg.empty() ) return std::string(); // まちではテスト出来ないので新スレを立てない return std::string(); } // // 新スレ作成時のbbscgi(write.cgi) のURL // std::string BoardMachi::url_bbscgi_new() const { return std::string(); } // // 新スレ作成時のsubbbscgi のURL // std::string BoardMachi::url_subbbscgi_new() const { return std::string(); } // // subject.txt から Aarticle のリストにアイテムを追加・更新 // void BoardMachi::parse_subject( const char* str_subject_txt ) { #ifdef _DEBUG std::cout << "BoardMachi::parse_subject" << std::endl; #endif const char* pos = str_subject_txt; while( *pos != '\0' ){ const char* str_id_dat; int lng_id_dat = 0; const char* str_subject; int lng_subject = 0; // datのID取得 // ".cgi" は除く str_id_dat = pos; while( *pos != '.' && *pos != '\0' && *pos != '\n' ) { ++pos; ++lng_id_dat; } // 壊れてる if( *pos == '\0' ) break; if( *pos == '\n' ) { ++pos; continue; } // subject取得 pos += 5; // ' ".cgi," str_subject = pos; while( *pos != '\0' && *pos != '\n' ) ++pos; --pos; while( *pos != '(' && *pos != '\n' && pos != str_subject_txt ) --pos; // 壊れてる if( *pos == '\n' || pos == str_subject_txt ){ MISC::ERRMSG( "subject.txt is broken" ); break; } lng_subject = ( int )( pos - str_subject ); // レス数取得 (符号付き32bit整数より大きいと未定義) ++pos; std::string str_num; while( '0' <= *pos && *pos <= '9' ) str_num.push_back( *( pos++ ) ); // 壊れてる if( str_num.empty() ){ MISC::ERRMSG( "subject.txt is broken (res)" ); break; } if( *pos == '\0' ) break; if( *pos == '\n' ) { ++pos; continue; } ++pos; // id, subject, number 取得 ARTICLE_INFO artinfo; artinfo.id.assign( str_id_dat, lng_id_dat ); artinfo.id = MISC::remove_space( artinfo.id ); artinfo.subject.assign( str_subject, lng_subject ); artinfo.subject = MISC::remove_space( artinfo.subject ); artinfo.subject = MISC::replace_str( artinfo.subject, "<", "<" ); artinfo.subject = MISC::replace_str( artinfo.subject, ">", ">" ); const auto num = std::atoi( str_num.c_str() ); artinfo.number = ( num < CONFIG::get_max_resnumber() ) ? num : CONFIG::get_max_resnumber(); get_list_artinfo().push_back( artinfo ); #ifdef _DEBUG std::cout << "pos = " << ( pos - str_subject_txt ) << " lng = " << lng_subject << " id = " << artinfo.id << " num = " << artinfo.number; std::cout << " : " << artinfo.subject << std::endl; #endif } } void BoardMachi::regist_article( const bool is_online ) { if( ! get_list_artinfo().size() ) return; #ifdef _DEBUG std::cout << "BoardMachii::regist_article size = " << get_list_artinfo().size() << std::endl; #endif ArticleBase* article_first = nullptr; const std::string datbase = url_datbase(); for( const ARTICLE_INFO& artinfo : get_list_artinfo() ) { // DBに登録されてるならarticle クラスの情報更新 ArticleBase* article = get_article( datbase, artinfo.id ); // DBにないなら新規に article クラスを追加 // // なお BoardBase::receive_finish() のなかで append_all_article_in_cache() が既に呼び出されているため // DBに無いということはキャッシュにも無いということ。よって append_article()で cached = false if( article->empty() ) article = append_article( datbase, artinfo.id, false ); // スレ情報更新 if( article ){ // ステータスをDAT落ち状態から通常状態に変更 int status = article->get_status(); status |= STATUS_NORMAL; status &= ~STATUS_OLD; article->set_status( status ); // 情報ファイル読み込み article->read_info(); // 情報ファイルが無い場合もあるのでsubject.txtから取得したサブジェクト、レス数を指定しておく article->set_subject( artinfo.subject ); article->set_number( artinfo.number, is_online ); // boardビューに表示するリスト更新 // Machiは最初と最後の行が同じになる仕様があるので最後の行を除く bool pushback = true; if( ! article_first ) article_first = article; else if( article == article_first ) pushback = false; if( pushback ){ // 情報ファイル読み込み後にステータスが変わることがあるので、もう一度 // ステータスをDAT落ち状態から通常状態に変更 status = article->get_status(); status |= STATUS_NORMAL; status &= ~STATUS_OLD; article->set_status( status ); // boardビューに表示するリスト更新 if( ! BoardBase::is_abone_thread( article ) ) get_list_subject().push_back( article ); } } } } jdim-0.7.0/src/dbtree/boardmachi.h000066400000000000000000000032761417047150700167550ustar00rootroot00000000000000// ライセンス: GPL2 // // まち 型板 // #ifndef _BOARDMACHI_H #define _BOARDMACHI_H #include "boardbase.h" namespace DBTREE { class BoardMachi : public BoardBase { public: BoardMachi( const std::string& root, const std::string& path_board,const std::string& name ); ~BoardMachi() noexcept = default; // url がこの板のものかどうか bool equal( const std::string& url ) const override; // スレの url を dat型のurlに変換して出力 // (例) "http://hoge.machi.to/bbs/read.cgi?BBS=board&KEY=12345&START=12&END=15"" のとき // 戻り値 : "http://hoge.machi.to/bbs/read.cgi?BBS=board&KEY=12345", num_from = 12, num_to = 15, num_str = 12-15 std::string url_dat( const std::string& url, int& num_from, int& num_to, std::string& num_str ) override; std::string url_datpath() const override; // 新スレ作成用のメッセージ変換 std::string create_newarticle_message( const std::string& subject, const std::string& name, const std::string& mail, const std::string& msg ) override; // 新スレ作成用のbbscgi のURL std::string url_bbscgi_new() const override; // 新スレ作成用のsubbbscgi のURL std::string url_subbbscgi_new() const override; private: bool is_valid( const std::string& filename ) const override; ArticleBase* append_article( const std::string& datbase, const std::string& id, const bool cached ) override; void parse_subject( const char* str_subject_txt ) override; void regist_article( const bool is_online ) override; }; } #endif jdim-0.7.0/src/dbtree/etcboardinfo.h000066400000000000000000000005541417047150700173170ustar00rootroot00000000000000// ライセンス: GPL2 // // 外部板情報 // #ifndef _ETCBOARDINFO_H #define _ETCBOARDINFO_H #include namespace DBTREE { struct ETCBOARDINFO { std::string name; std::string url; std::string basicauth; // basic認証 ID:PASS std::string boardid; // navi2chのetc形式との互換のため }; } #endif jdim-0.7.0/src/dbtree/frontloader.cpp000066400000000000000000000031551417047150700175320ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "frontloader.h" #include "interface.h" #include "jdlib/loaderdata.h" #include "config/globalconf.h" #include "cache.h" using namespace DBTREE; FrontLoader::FrontLoader( const std::string& url_boadbase ) : SKELETON::TextLoader() , m_url_boadbase( url_boadbase ) { } std::string FrontLoader::get_charset() const { return DBTREE::board_charset( m_url_boadbase ); } // ロード用データ作成 void FrontLoader::create_loaderdata( JDLIB::LOADERDATA& data ) { // 移転処理 m_url_boadbase = DBTREE::url_boardbase( m_url_boadbase ); data.url = get_url(); data.agent = DBTREE::get_agent( m_url_boadbase ); data.host_proxy = DBTREE::get_proxy_host( m_url_boadbase ); data.port_proxy = DBTREE::get_proxy_port( m_url_boadbase ); data.basicauth_proxy = DBTREE::get_proxy_basicauth( m_url_boadbase ); data.size_buf = CONFIG::get_loader_bufsize(); data.timeout = CONFIG::get_loader_timeout(); if( ! get_date_modified().empty() ) data.modified = get_date_modified(); data.basicauth = DBTREE::board_basicauth( m_url_boadbase ); data.cookie_for_request = DBTREE::board_cookie_for_request( m_url_boadbase ); } // ロード後に呼び出される void FrontLoader::parse_data() { // フロントページからキーワードを解析して登録する if( ! get_data().empty() ) { DBTREE::board_analyze_keyword_for_newarticle( m_url_boadbase, get_data() ); } } void FrontLoader::receive_cookies() { DBTREE::board_set_list_cookies( m_url_boadbase, SKELETON::Loadable::cookies() ); } jdim-0.7.0/src/dbtree/frontloader.h000066400000000000000000000017331417047150700171770ustar00rootroot00000000000000// ライセンス: GPL2 // // 板のフロントページのローダー // #ifndef JDIM_FRONTLOADER_H #define JDIM_FRONTLOADER_H #include "skeleton/textloader.h" #include namespace JDLIB { class LOADERDATA; } namespace DBTREE { class FrontLoader : public SKELETON::TextLoader { std::string m_url_boadbase; public: explicit FrontLoader( const std::string& url_boardbase ); ~FrontLoader() = default; protected: std::string get_url() const override { return m_url_boadbase; } std::string get_path() const override { return {}; } // キャッシュには保存しない std::string get_charset() const override; // ロード用データ作成 void create_loaderdata( JDLIB::LOADERDATA& data ) override; // ロード後に呼び出される void parse_data() override; private: void receive_cookies() override; }; } #endif // JDIM_FRONTLOADER_H jdim-0.7.0/src/dbtree/interface.cpp000066400000000000000000001047441417047150700171610ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "interface.h" #include "root.h" #include "boardbase.h" #include "articlebase.h" #include "jdlib/miscutil.h" #include "global.h" // インスタンスは Core でひとつだけ作って、Coreのデストラクタでdeleteする DBTREE::Root *instance_dbtree_root = nullptr; void DBTREE::create_root() { if( ! instance_dbtree_root ) instance_dbtree_root = new DBTREE::Root(); } void DBTREE::delete_root() { if( instance_dbtree_root ){ instance_dbtree_root->terminate_load(); delete instance_dbtree_root; } } ////////////////////////////////////// // // ツリーの構成要素のポインタ取得 // // root から葉っぱに向かって順に取得していく // DBTREE::Root* DBTREE::get_root() { assert( instance_dbtree_root != nullptr ); return instance_dbtree_root; } DBTREE::BoardBase* DBTREE::get_board( const std::string& url ) { DBTREE::BoardBase* board = DBTREE::get_root()->get_board( url ); assert( board != nullptr ); return board; } DBTREE::ArticleBase* DBTREE::get_article( const std::string& url ) { DBTREE::ArticleBase* article = DBTREE::get_board( url )->get_article_fromURL( url ); assert( article != nullptr ); return article; } ////////////////////////////////////// std::string DBTREE::url_root( const std::string& url ) { return DBTREE::get_board( url )->url_root(); } std::string DBTREE::url_boardbase( const std::string& url ) { return DBTREE::get_board( url )->url_boardbase(); } std::string DBTREE::url_datbase( const std::string& url ) { return DBTREE::get_board( url )->url_datbase(); } // urlをdat型のurlに変換 std::string DBTREE::url_dat( const std::string& url, int& num_from, int& num_to, std::string& num_str ) { return DBTREE::get_board( url )->url_dat( url, num_from, num_to, num_str ); } // urlをdat型のurlに変換(簡易版) std::string DBTREE::url_dat( const std::string& url ) { int num_from, num_to; std::string num_str; return url_dat( url, num_from, num_to, num_str ); } // url を read.cgi型のurlに変換 std::string DBTREE::url_readcgi( const std::string& url, int num_from, int num_to ) { return DBTREE::get_board( url )->url_readcgi( url, num_from, num_to ); } std::string DBTREE::url_settingtxt( const std::string& url ) { return DBTREE::get_board( url )->url_settingtxt(); } std::string DBTREE::url_bbscgibase( const std::string& url ) { return DBTREE::get_board( url )->url_bbscgibase(); } std::string DBTREE::url_subbbscgibase( const std::string& url ) { return DBTREE::get_board( url )->url_subbbscgibase(); } std::string DBTREE::url_bbscgi( const std::string& url ) { return DBTREE::get_article( url )->url_bbscgi(); } std::string DBTREE::url_subbbscgi( const std::string& url ) { return DBTREE::get_article( url )->url_subbbscgi(); } std::string DBTREE::url_bbscgi_new( const std::string& url ) { return DBTREE::get_board( url )->url_bbscgi_new(); } std::string DBTREE::url_subbbscgi_new( const std::string& url ) { return DBTREE::get_board( url )->url_subbbscgi_new(); } // 簡易版 std::string DBTREE::is_board_moved( const std::string& url ) { return get_root()->is_board_moved( url ); } std::string DBTREE::is_board_moved( const std::string& url, std::string& old_root, std::string& old_path_board, std::string& new_root, std::string& new_path_board ) { return get_root()->is_board_moved( url, old_root, old_path_board, new_root, new_path_board ); } bool DBTREE::move_board( const std::string& url_old, const std::string& url_new ) { return get_root()->move_board( url_old, url_new, false ); } void DBTREE::set_enable_save_movetable( const bool set ) { get_root()->set_enable_save_movetable( set ); } void DBTREE::save_movetable() { get_root()->save_movetable(); } const XML::Document& DBTREE::get_xml_document() { return get_root()->xml_document(); } const std::list< DBTREE::ETCBOARDINFO >& DBTREE::get_etcboards() { return get_root()->get_etcboards(); } bool DBTREE::add_etc( const std::string& url, const std::string& name, const std::string& basicauth, const std::string& id ) { return get_root()->add_etc( url, name, basicauth, id ); } bool DBTREE::move_etc( const std::string& url_old, const std::string& url_new, const std::string& name_old, const std::string& name_new, const std::string& basicauth, const std::string& boardid ) { return get_root()->move_etc( url_old, url_new, name_old, name_new, basicauth, boardid ); } bool DBTREE::remove_etc( const std::string& url, const std::string& name ) { return get_root()->remove_etc( url, name ); } void DBTREE::save_etc() { get_root()->save_etc(); } void DBTREE::download_bbsmenu() { get_root()->download_bbsmenu(); } // bbsmenuの更新時間( 文字列 ) std::string DBTREE::get_date_modified() { return get_root()->get_date_modified(); } // bbsmenuの更新時間( time_t ) time_t DBTREE::get_time_modified() { return get_root()->get_time_modified(); } std::string DBTREE::board_path( const std::string& url ) { return DBTREE::get_board( url )->get_path_board(); } std::string DBTREE::board_id( const std::string& url ) { return DBTREE::get_board( url )->get_id(); } // 更新時間( time_t ) time_t DBTREE::board_time_modified( const std::string& url ) { return DBTREE::get_board( url )->get_time_modified(); } // 板の更新時間( 文字列 ) std::string DBTREE::board_date_modified( const std::string& url ) { return DBTREE::get_board( url )->get_date_modified(); } // 板の更新時間( 文字列 )をセット void DBTREE::board_set_date_modified( const std::string& url, const std::string& date ) { DBTREE::get_board( url )->set_date_modified( date ); } const std::string& DBTREE::board_get_modified_localrule( const std::string& url ) { return DBTREE::get_board( url )->get_modified_localrule(); } void DBTREE::board_set_modified_localrule( const std::string& url, const std::string& modified ) { DBTREE::get_board( url )->set_modified_localrule( modified ); } const std::string& DBTREE::board_get_modified_setting( const std::string& url ) { return DBTREE::get_board( url )->get_modified_setting(); } void DBTREE::board_set_modified_setting( const std::string& url, const std::string& modified ) { DBTREE::get_board( url )->set_modified_setting( modified ); } std::string DBTREE::board_name( const std::string& url ) { return DBTREE::get_board( url )->get_name(); } std::string DBTREE::board_subjecttxt( const std::string& url ) { return DBTREE::get_board( url )->get_subjecttxt(); } std::string DBTREE::board_charset( const std::string& url ) { return DBTREE::get_board( url )->get_charset(); } std::string DBTREE::board_cookie_by_host( const std::string& url ) { return DBTREE::get_board( url )->cookie_by_host(); } std::string DBTREE::board_cookie_for_request( const std::string& url ) { return DBTREE::get_board( url )->cookie_for_request(); } std::string DBTREE::board_cookie_for_post( const std::string& url ) { return DBTREE::get_board( url )->cookie_for_post(); } void DBTREE::board_set_list_cookies( const std::string& url, const std::list< std::string>& list_cookies ) { DBTREE::get_board( url )->set_list_cookies( list_cookies ); } void DBTREE::board_delete_cookies( const std::string& url ) { DBTREE::get_board( url )->delete_cookies(); } std::string DBTREE::board_keyword_for_write( const std::string& url ) { return DBTREE::get_board( url )->get_keyword_for_write(); } void DBTREE::board_set_keyword_for_write( const std::string& url, const std::string& keyword ) { DBTREE::get_board( url )->set_keyword_for_write( keyword ); } void DBTREE::board_analyze_keyword_for_write( const std::string& url, const std::string& html ) { DBTREE::get_board( url )->analyze_keyword_for_write( html ); } std::string DBTREE::board_keyword_for_newarticle( const std::string& url ) { return DBTREE::get_board( url )->get_keyword_for_newarticle(); } void DBTREE::board_set_keyword_for_newarticle( const std::string& url, const std::string& keyword ) { DBTREE::get_board( url )->set_keyword_for_newarticle( keyword ); } void DBTREE::board_analyze_keyword_for_newarticle( const std::string& url, const std::string& html ) { DBTREE::get_board( url )->analyze_keyword_for_newarticle( html ); } std::string DBTREE::board_parse_form_data( const std::string& url, const std::string& html ) { return DBTREE::get_board( url )->parse_form_data( html ); } std::string DBTREE::board_basicauth( const std::string& url ) { return DBTREE::get_board( url )->get_basicauth(); } std::string DBTREE::board_ext( const std::string& url ) { return DBTREE::get_board( url )->get_ext(); } int DBTREE::board_status( const std::string& url ) { return DBTREE::get_board( url )->get_status(); } int DBTREE::board_code( const std::string& url ) { return DBTREE::get_board( url )->get_code(); } std::string DBTREE::board_str_code( const std::string& url ) { return DBTREE::get_board( url )->get_str_code(); } void DBTREE::board_save_info( const std::string& url ) { DBTREE::get_board( url )->save_info(); } void DBTREE::board_download_front( const std::string& url ) { DBTREE::get_board( url )->download_front(); } void DBTREE::board_download_subject( const std::string& url, const std::string& url_update_view ) { DBTREE::get_board( url )->download_subject( url_update_view, false ); } void DBTREE::board_read_subject_from_cache( const std::string& url ) { DBTREE::get_board( url )->download_subject( std::string(), true ); } bool DBTREE::board_is_loading( const std::string& url ) { return DBTREE::get_board( url )->is_loading(); } void DBTREE::board_stop_load( const std::string& url ) { DBTREE::get_board( url )->stop_load(); } std::vector< DBTREE::ArticleBase* >& DBTREE::board_list_subject( const std::string& url ) { return DBTREE::get_board( url )->get_list_subject(); } int DBTREE::board_view_sort_column( const std::string& url ) { return DBTREE::get_board( url )->get_view_sort_column(); } void DBTREE::board_set_view_sort_column( const std::string& url, int column ) { DBTREE::get_board( url )->set_view_sort_column( column ); } int DBTREE::board_view_sort_mode( const std::string& url ) { return DBTREE::get_board( url )->get_view_sort_mode(); } void DBTREE::board_set_view_sort_mode( const std::string& url, int mode ) { DBTREE::get_board( url )->set_view_sort_mode( mode ); } int DBTREE::board_view_sort_pre_column( const std::string& url ) { return DBTREE::get_board( url )->get_view_sort_pre_column(); } void DBTREE::board_set_view_sort_pre_column( const std::string& url, int column ) { DBTREE::get_board( url )->set_view_sort_pre_column( column ); } int DBTREE::board_view_sort_pre_mode( const std::string& url ) { return DBTREE::get_board( url )->get_view_sort_pre_mode(); } void DBTREE::board_set_view_sort_pre_mode( const std::string& url, int mode ) { DBTREE::get_board( url )->set_view_sort_pre_mode( mode ); } bool DBTREE::board_check_noname( const std::string& url ) { return DBTREE::get_board( url )->get_check_noname(); } void DBTREE::board_set_check_noname( const std::string& url, const bool check ) { DBTREE::get_board( url )->set_check_noname( check ); } bool DBTREE::board_show_oldlog( const std::string& url ) { return DBTREE::get_board( url )->get_show_oldlog(); } void DBTREE::board_set_show_oldlog( const std::string& url, const bool show ) { DBTREE::get_board( url )->set_show_oldlog( show ); } int DBTREE::board_get_mode_local_proxy( const std::string& url ) { return DBTREE::get_board( url )->get_mode_local_proxy(); } const std::string& DBTREE::board_get_local_proxy( const std::string& url ) { return DBTREE::get_board( url )->get_local_proxy(); } int DBTREE::board_get_local_proxy_port( const std::string& url ) { return DBTREE::get_board( url )->get_local_proxy_port(); } const std::string& DBTREE::board_get_local_proxy_basicauth( const std::string& url ) { return DBTREE::get_board( url )->get_local_proxy_basicauth(); } void DBTREE::board_set_mode_local_proxy( const std::string& url, int mode ) { DBTREE::get_board( url )->set_mode_local_proxy( mode ); } void DBTREE::board_set_local_proxy( const std::string& url, const std::string& proxy ) { DBTREE::get_board( url )->set_local_proxy( proxy ); } void DBTREE::board_set_local_proxy_port( const std::string& url, int port ) { DBTREE::get_board( url )->set_local_proxy_port( port ); } int DBTREE::board_get_mode_local_proxy_w( const std::string& url ) { return DBTREE::get_board( url )->get_mode_local_proxy_w(); } const std::string& DBTREE::board_get_local_proxy_w( const std::string& url ) { return DBTREE::get_board( url )->get_local_proxy_w(); } const std::string& DBTREE::board_get_local_proxy_basicauth_w( const std::string& url ) { return DBTREE::get_board( url )->get_local_proxy_basicauth_w(); } int DBTREE::board_get_local_proxy_port_w( const std::string& url ) { return DBTREE::get_board( url )->get_local_proxy_port_w(); } void DBTREE::board_set_mode_local_proxy_w( const std::string& url, int mode ) { DBTREE::get_board( url )->set_mode_local_proxy_w( mode ); } void DBTREE::board_set_local_proxy_w( const std::string& url, const std::string& proxy ) { DBTREE::get_board( url )->set_local_proxy_w( proxy ); } void DBTREE::board_set_local_proxy_port_w( const std::string& url, int port ) { DBTREE::get_board( url )->set_local_proxy_port_w( port ); } const std::string& DBTREE::board_get_write_name( const std::string& url ) { return DBTREE::get_board( url )->get_write_name(); } const std::string& DBTREE::board_get_write_mail( const std::string& url ) { return DBTREE::get_board( url )->get_write_mail(); } void DBTREE::board_set_write_name( const std::string& url, const std::string& name ) { DBTREE::get_board( url )->set_write_name( name ); } void DBTREE::board_set_write_mail( const std::string& url, const std::string& mail ) { DBTREE::get_board( url )->set_write_mail( mail ); } // 全スレの書き込み履歴のリセット void DBTREE::clear_all_post_history() { DBTREE::get_root()->clear_all_post_history(); } void DBTREE::read_boardinfo_all() { DBTREE::get_root()->read_boardinfo_all(); } void DBTREE::search_cache_all( std::vector< DBTREE::ArticleBase* >& list_article, const std::string& query, const bool mode_or, const bool bm, const bool stop ) { DBTREE::get_root()->search_cache( list_article, query, mode_or, bm, stop ); } void DBTREE::search_cache( const std::string& url, std::vector< DBTREE::ArticleBase* >& list_article, const std::string& query, const bool mode_or, const bool bm, const bool stop ) { DBTREE::get_board( url )->search_cache( list_article, query, mode_or, bm, stop ); } void DBTREE::board_update_writetime( const std::string& url ) { DBTREE::get_board( url )->update_writetime(); } time_t DBTREE::board_write_time( const std::string& url ) { return DBTREE::get_board( url )->get_write_time(); } time_t DBTREE::board_write_pass( const std::string& url ) { return DBTREE::get_board( url )->get_write_pass(); } time_t DBTREE::board_samba_sec( const std::string& url ) { return DBTREE::get_board( url )->get_samba_sec(); } void DBTREE::board_set_samba_sec( const std::string& url, time_t sec ) { DBTREE::get_board( url )->set_samba_sec( sec ); } time_t DBTREE::board_write_leftsec( const std::string& url ) { return DBTREE::get_board( url )->get_write_leftsec(); } void DBTREE::board_show_updateicon( const std::string& url, const bool update ) { DBTREE::get_board( url )->show_updateicon( update ); } std::list< std::string > DBTREE::board_get_check_update_articles( const std::string& url ) { return DBTREE::get_board( url )->get_check_update_articles(); } // datファイルのインポート std::string DBTREE::board_import_dat( const std::string& url, const std::string& filename ) { return DBTREE::get_board( url )->import_dat( filename ); } // 板に属する全スレの書き込み履歴のリセット void DBTREE::board_clear_all_post_history( const std::string& url ) { DBTREE::get_board( url )->clear_all_post_history(); } int DBTREE::board_get_number_max_res( const std::string& url ) { return DBTREE::get_board( url )->get_number_max_res(); } void DBTREE::board_set_number_max_res( const std::string& url, const int number ) { DBTREE::get_board( url )->set_number_max_res( number ); } // datの最大サイズ(Kバイト) int DBTREE::board_get_max_dat_lng( const std::string& url ) { return DBTREE::get_board( url )->get_max_dat_lng(); } time_t DBTREE::board_get_live_sec( const std::string& url ) { return DBTREE::get_board( url )->get_live_sec(); } void DBTREE::board_set_live_sec( const std::string& url, time_t sec ) { DBTREE::get_board( url )->set_live_sec( sec ); } time_t DBTREE::board_last_access_time( const std::string& url ) { return DBTREE::get_board( url )->get_last_access_time(); } const std::string& DBTREE::board_get_board_agent( const std::string& url ) { return DBTREE::get_board( url )->get_board_agent(); } void DBTREE::board_set_board_agent( const std::string& url, const std::string& user_agent ) { DBTREE::get_board( url )->set_board_agent( user_agent ); } ///////////////////////////////////////////////// const std::list< std::string >& DBTREE::get_abone_list_id_board( const std::string& url ) { return DBTREE::get_board( url )->get_abone_list_id_board(); } const std::list< std::string >& DBTREE::get_abone_list_name_board( const std::string& url ) { return DBTREE::get_board( url )->get_abone_list_name_board(); } const std::list< std::string >& DBTREE::get_abone_list_word_board( const std::string& url ) { return DBTREE::get_board( url )->get_abone_list_word_board(); } const std::list< std::string >& DBTREE::get_abone_list_regex_board( const std::string& url ) { return DBTREE::get_board( url )->get_abone_list_regex_board(); } void DBTREE::reset_abone_board( const std::string& url, const std::list< std::string >& ids, const std::list< std::string >& names, const std::list< std::string >& words, const std::list< std::string >& regexs ) { DBTREE::get_board( url )->reset_abone_board( ids, names, words, regexs ); } void DBTREE::add_abone_id_board( const std::string& url, const std::string& id ) { DBTREE::get_board( url )->add_abone_id_board( id ); } void DBTREE::add_abone_name_board( const std::string& url, const std::string& name ) { DBTREE::get_board( url )->add_abone_name_board( name ); } void DBTREE::add_abone_word_board( const std::string& url, const std::string& word ) { DBTREE::get_board( url )->add_abone_word_board( word ); } ///////////////////////////////////////////////// const std::list< std::string >& DBTREE::get_abone_list_thread( const std::string& url ) { return DBTREE::get_board( url )->get_abone_list_thread(); } const std::list< std::string >& DBTREE::get_abone_list_thread_remove( const std::string& url ) { return DBTREE::get_board( url )->get_abone_list_thread_remove(); } const std::list< std::string >& DBTREE::get_abone_list_word_thread( const std::string& url ) { return DBTREE::get_board( url )->get_abone_list_word_thread(); } const std::list< std::string >& DBTREE::get_abone_list_regex_thread( const std::string& url ) { return DBTREE::get_board( url )->get_abone_list_regex_thread(); } int DBTREE::get_abone_low_number_thread( const std::string& url ) { return DBTREE::get_board( url )->get_abone_low_number_thread(); } int DBTREE::get_abone_high_number_thread( const std::string& url ) { return DBTREE::get_board( url )->get_abone_high_number_thread(); } int DBTREE::get_abone_hour_thread( const std::string& url ) { return DBTREE::get_board( url )->get_abone_hour_thread(); } void DBTREE::remove_old_abone_thread( const std::string& url ) { DBTREE::get_board( url )->remove_old_abone_thread(); } void DBTREE::update_abone_thread() { DBTREE::get_root()->update_abone_thread(); } void DBTREE::reset_abone_thread( const std::string& url, const std::list< std::string >& threads, const std::list< std::string >& words, const std::list< std::string >& regexs, const int low_number, const int high_number, const int hour, const bool redraw ) { DBTREE::get_board( url )->reset_abone_thread( threads, words, regexs, low_number, high_number, hour, redraw ); } ///////////////////////////////////////////////// bool DBTREE::article_is_cached( const std::string& url ) { return DBTREE::get_article( url )->is_cached(); } // 拡張子付き std::string DBTREE::article_id( const std::string& url ) { return DBTREE::get_article( url )->get_id(); } // idから拡張子を取ったもの std::string DBTREE::article_key( const std::string& url ) { return DBTREE::get_article( url )->get_key(); } // 移転する前のオリジナルのURL std::string DBTREE::article_org_host( const std::string& url ) { return DBTREE::get_article( url )->get_org_host(); } time_t DBTREE::article_since_time( const std::string& url ) { return DBTREE::get_article( url )->get_since_time(); } std::string DBTREE::article_since_date( const std::string& url ) { return DBTREE::get_article( url )->get_since_date(); } // スレの更新時間( time_t ) time_t DBTREE::article_time_modified( const std::string& url ) { return DBTREE::get_article( url )->get_time_modified(); } // スレの更新時間( 文字列 ) std::string DBTREE::article_date_modified( const std::string& url ) { return DBTREE::get_article( url )->get_date_modified(); } // スレの更新時間( 文字列 )をセット void DBTREE::article_set_date_modified( const std::string& url, const std::string& date ) { DBTREE::get_article( url )->set_date_modified( date ); } int DBTREE::article_hour( const std::string& url ) { return DBTREE::get_article( url )->get_hour(); } // 最終書き込み時刻 time_t DBTREE::article_write_time( const std::string& url ) { return DBTREE::get_article( url )->get_write_time(); } // 最終書き込み時刻(文字列) std::string DBTREE::article_write_date( const std::string& url ) { return DBTREE::get_article( url )->get_write_date(); } int DBTREE::article_status( const std::string& url ) { return DBTREE::get_article( url )->get_status(); } int DBTREE::article_code( const std::string& url ) { return DBTREE::get_article( url )->get_code(); } std::string DBTREE::article_str_code( const std::string& url ) { return DBTREE::get_article( url )->get_str_code(); } std::string DBTREE::article_ext_err( const std::string& url ) { return DBTREE::get_article( url )->get_ext_err(); } std::string DBTREE::article_subject( const std::string& url ) { return DBTREE::get_article( url )->get_subject(); } int DBTREE::article_number( const std::string& url ) { return DBTREE::get_article( url )->get_number(); } int DBTREE::article_number_load( const std::string& url ) { return DBTREE::get_article( url )->get_number_load(); } int DBTREE::article_number_seen( const std::string& url ) { return DBTREE::get_article( url )->get_number_seen(); } void DBTREE::article_set_number_seen( const std::string& url, int seen ) { DBTREE::get_article( url )->set_number_seen( seen ); } int DBTREE::article_number_new( const std::string& url ) { return DBTREE::get_article( url )->get_number_new(); } bool DBTREE::article_is_loading( const std::string& url ) { return DBTREE::get_article( url )->is_loading(); } bool DBTREE::article_is_checking_update( const std::string& url ) { return DBTREE::get_article( url )->is_checking_update(); } void DBTREE::article_download_dat( const std::string& url, const bool check_update ) { DBTREE::get_article( url )->download_dat( check_update ); } void DBTREE::article_set_url_pre_article( const std::string& url, const std::string& url_pre_article ) { DBTREE::get_article( url )->set_url_pre_article( url_pre_article ); } void DBTREE::article_copy_article_info( const std::string& url, const std::string& url_src ) { DBTREE::get_article( url )->copy_article_info( url_src ); } void DBTREE::article_stop_load( const std::string& url ) { DBTREE::get_article( url )->stop_load(); } int DBTREE::article_get_speed( const std::string& url ) { return DBTREE::get_article( url )->get_speed(); } // 書き込み履歴のリセット void DBTREE::article_clear_post_history( const std::string& url ) { DBTREE::get_article( url )->clear_post_history(); } // ユーザーエージェント // ダウンロード用 const std::string& DBTREE::get_agent( const std::string& url ) { return DBTREE::get_board( url )->get_agent(); } // 書き込み用 const std::string& DBTREE::get_agent_w( const std::string& url ) { return DBTREE::get_board( url )->get_agent_w(); } std::string DBTREE::get_proxy_host( const std::string& url ) { return DBTREE::get_board( url )->get_proxy_host(); } int DBTREE::get_proxy_port( const std::string& url ) { return DBTREE::get_board( url )->get_proxy_port(); } std::string DBTREE::get_proxy_basicauth( const std::string& url ) { return DBTREE::get_board( url )->get_proxy_basicauth(); } std::string DBTREE::get_proxy_host_w( const std::string& url ) { return DBTREE::get_board( url )->get_proxy_host_w(); } int DBTREE::get_proxy_port_w( const std::string& url ) { return DBTREE::get_board( url )->get_proxy_port_w(); } std::string DBTREE::get_proxy_basicauth_w( const std::string& url ) { return DBTREE::get_board( url )->get_proxy_basicauth_w(); } std::string DBTREE::localrule( const std::string& url ) { return DBTREE::get_board( url )->localrule(); } std::string DBTREE::settingtxt( const std::string& url ) { return DBTREE::get_board( url )->settingtxt(); } std::string DBTREE::default_noname( const std::string& url ) { return DBTREE::get_board( url )->default_noname(); } int DBTREE::line_number( const std::string& url ) { return DBTREE::get_board( url )->line_number(); } int DBTREE::message_count( const std::string& url ) { return DBTREE::get_board( url )->message_count(); } // 特殊文字書き込み可能か( pass なら可能、 change なら不可 ) std::string DBTREE::get_unicode( const std::string& url ) { return DBTREE::get_board( url )->get_unicode(); } const std::string& DBTREE::write_name( const std::string& url ) { return DBTREE::get_article( url )->get_write_name(); } void DBTREE::set_write_name( const std::string& url, const std::string& str ) { DBTREE::get_article( url )->set_write_name( str ); } bool DBTREE::write_fixname( const std::string& url ) { return DBTREE::get_article( url )->get_write_fixname(); } void DBTREE::set_write_fixname( const std::string& url, bool set ) { DBTREE::get_article( url )->set_write_fixname( set ); } const std::string& DBTREE::write_mail( const std::string& url ) { return DBTREE::get_article( url )->get_write_mail(); } void DBTREE::set_write_mail( const std::string& url, const std::string& str ) { DBTREE::get_article( url )->set_write_mail( str ); } bool DBTREE::write_fixmail( const std::string& url ) { return DBTREE::get_article( url )->get_write_fixmail(); } void DBTREE::set_write_fixmail( const std::string& url, bool set ) { DBTREE::get_article( url )->set_write_fixmail( set ); } std::string DBTREE::create_write_message( const std::string& url, const std::string& name, const std::string& mail, const std::string& msg ) { return DBTREE::get_article( url )->create_write_message( name, mail, msg ); } std::string DBTREE::create_newarticle_message( const std::string& url, const std::string& subject, const std::string& name, const std::string& mail, const std::string& msg ) { return DBTREE::get_board( url )->create_newarticle_message( subject, name, mail, msg ); } std::string DBTREE::get_write_referer( const std::string& url ) { return DBTREE::get_board( url )->get_write_referer( url ); } std::string DBTREE::get_newarticle_referer( const std::string& url ) { return DBTREE::get_board( url )->get_newarticle_referer( url ); } // キャッシュ削除 void DBTREE::delete_article( const std::string& url, const bool cache_only ) { DBTREE::get_article( url )->delete_cache( cache_only ); } // キャッシュ保存 bool DBTREE::article_save_dat( const std::string& url, const std::string& path_to ) { return DBTREE::get_article( url )->save_dat( path_to ); } // 全スレ情報の保存 void DBTREE::save_articleinfo_all() { DBTREE::get_root()->save_articleinfo_all(); } void DBTREE::article_update_writetime( const std::string& url ) { DBTREE::get_article( url )->update_writetime(); } size_t DBTREE::article_lng_dat( const std::string& url ) { return DBTREE::get_article( url )->get_lng_dat(); } void DBTREE::update_abone_all_article() { DBTREE::get_root()->update_abone_all_article(); } // 全articlebaseクラスの書き込み時間とスレ立て時間の文字列をリセット void DBTREE::reset_all_since_date() { DBTREE::get_root()->reset_all_since_date(); } void DBTREE::reset_all_write_date() { DBTREE::get_root()->reset_all_write_date(); } void DBTREE::reset_all_access_date() { DBTREE::get_root()->reset_all_access_date(); } const std::list< std::string >& DBTREE::get_abone_list_id( const std::string& url ) { return DBTREE::get_article( url )->get_abone_list_id(); } const std::list< std::string >& DBTREE::get_abone_list_name( const std::string& url ) { return DBTREE::get_article( url )->get_abone_list_name(); } const std::list< std::string >& DBTREE::get_abone_list_word( const std::string& url ) { return DBTREE::get_article( url )->get_abone_list_word(); } const std::list< std::string >& DBTREE::get_abone_list_regex( const std::string& url ) { return DBTREE::get_article( url )->get_abone_list_regex(); } const std::unordered_set< int >& DBTREE::get_abone_reses( const std::string& url ) { return DBTREE::get_article( url )->get_abone_reses(); } void DBTREE::reset_abone( const std::string& url, const std::list< std::string >& ids, const std::list< std::string >& names, const std::list< std::string >& words, const std::list< std::string >& regexs, const std::vector< char >& vec_abone_res, const bool transparent, const bool chain, const bool age, const bool default_name, const bool noid, const bool board, const bool global ) { DBTREE::get_article( url )->reset_abone( ids, names, words, regexs, vec_abone_res, transparent, chain, age, default_name, noid, board, global ); } void DBTREE::set_abone_res( const std::string& url, const int num_from, const int num_to, const bool set ) { DBTREE::get_article( url )->set_abone_res( num_from, num_to, set ); } void DBTREE::add_abone_id( const std::string& url, const std::string& id ) { DBTREE::get_article( url )->add_abone_id( id ); } void DBTREE::add_abone_name( const std::string& url, const std::string& name ) { DBTREE::get_article( url )->add_abone_name( name ); } void DBTREE::add_abone_word( const std::string& url, const std::string& word ) { DBTREE::get_article( url )->add_abone_word( word ); } // 透明あぼーん bool DBTREE::get_abone_transparent( const std::string& url ) { return DBTREE::get_article( url )->get_abone_transparent(); } void DBTREE::set_abone_transparent( const std::string& url, const bool set ) { DBTREE::get_article( url )->set_abone_transparent( set ); } // 連鎖あぼーん bool DBTREE::get_abone_chain( const std::string& url ) { return DBTREE::get_article( url )->get_abone_chain(); } void DBTREE::set_abone_chain( const std::string& url, const bool set ) { DBTREE::get_article( url )->set_abone_chain( set ); } // ageあぼーん bool DBTREE::get_abone_age( const std::string& url ) { return DBTREE::get_article( url )->get_abone_age(); } void DBTREE::set_abone_age( const std::string& url, const bool set ) { DBTREE::get_article( url )->set_abone_age( set ); } // デフォルト名無しあぼーん bool DBTREE::get_abone_default_name( const std::string& url ) { return DBTREE::get_article( url )->get_abone_default_name(); } void DBTREE::set_abone_default_name( const std::string& url, const bool set ) { DBTREE::get_article( url )->set_abone_default_name( set ); } // ID無しあぼーん bool DBTREE::get_abone_noid( const std::string& url ) { return DBTREE::get_article( url )->get_abone_noid(); } void DBTREE::set_abone_noid( const std::string& url, const bool set ) { DBTREE::get_article( url )->set_abone_noid( set ); } // 板レベルでのあぼーん bool DBTREE::get_abone_board( const std::string& url ) { return DBTREE::get_article( url )->get_abone_board(); } void DBTREE::set_abone_board( const std::string& url, const bool set ) { DBTREE::get_article( url )->set_abone_board( set ); } // 全体レベルでのあぼーん bool DBTREE::get_abone_global( const std::string& url ) { return DBTREE::get_article( url )->get_abone_global(); } void DBTREE::set_abone_global( const std::string& url, const bool set ) { DBTREE::get_article( url )->set_abone_global( set ); } bool DBTREE::is_bookmarked_thread( const std::string& url ) { return DBTREE::get_article( url )->is_bookmarked_thread(); } void DBTREE::set_bookmarked_thread( const std::string& url, const bool bookmarked ) { DBTREE::get_article( url )->set_bookmarked_thread( bookmarked ); } int DBTREE::get_num_bookmark( const std::string& url ) { return DBTREE::get_article( url )->get_num_bookmark(); } bool DBTREE::is_bookmarked( const std::string& url, const int number ) { return DBTREE::get_article( url )->is_bookmarked( number ); } void DBTREE::set_bookmark( const std::string& url, const int number, const bool set ) { DBTREE::get_article( url )->set_bookmark( number, set ); } jdim-0.7.0/src/dbtree/interface.h000066400000000000000000000533231417047150700166220ustar00rootroot00000000000000// ライセンス: GPL2 // // データベースへのインターフェース関数 // #ifndef _INTERFACE_H #define _INTERFACE_H #include "etcboardinfo.h" #include #include #include #include #include namespace XML { class Document; } namespace DBTREE { class Root; class BoardBase; class NodeTreeBase; class ArticleBase; void create_root(); void delete_root(); // 各クラスのポインタ取得 Root* get_root(); BoardBase* get_board( const std::string& url ); ArticleBase* get_article( const std::string& url ); // urlの変換関係 std::string url_subject( const std::string& url ) = delete; // 板の subject.txt の URL std::string url_root( const std::string& url ); std::string url_boardbase( const std::string& url ); std::string url_datbase( const std::string& url ); // dat型のurlに変換 std::string url_dat( const std::string& url, int& num_from, int& num_to, std::string& num_str ); // dat型のurlに変換(簡易版) std::string url_dat( const std::string& url ); // read.cgi型のurlに変換 std::string url_readcgi( const std::string& url, int num_from, int num_to ); std::string url_settingtxt( const std::string& url ); std::string url_bbscgibase( const std::string& url ); std::string url_subbbscgibase( const std::string& url ); std::string url_bbscgi( const std::string& url ); std::string url_subbbscgi( const std::string& url ); std::string url_bbscgi_new( const std::string& url ); std::string url_subbbscgi_new( const std::string& url ); // 板が移転したかチェックする // 移転した時は移転後のURLを返す std::string is_board_moved( const std::string& url ); std::string is_board_moved( const std::string& url, std::string& old_root, std::string& old_path_board, std::string& new_root, std::string& new_path_board ); // 板移転 bool move_board( const std::string& url_old, const std::string& url_new ); // 板移転情報の保存の有効切り替え void set_enable_save_movetable( const bool set ); // 移転情報保存 void save_movetable(); // bbslist系 const XML::Document& get_xml_document(); const std::list< DBTREE::ETCBOARDINFO >& get_etcboards(); bool add_etc( const std::string& url, const std::string& name, const std::string& basicauth, const std::string& id ); bool move_etc( const std::string& url_old, const std::string& url_new, const std::string& name_old, const std::string& name_new, const std::string& basicauth, const std::string& boardid ); bool remove_etc( const std::string& url, const std::string& name ); void save_etc(); void download_bbsmenu(); std::string get_date_modified(); // bbsmenuの更新時間( 文字列 ) time_t get_time_modified(); // bbsmenuの更新時間( time_t ) // board 系 std::string board_path( const std::string& url ); std::string board_id( const std::string& url ); time_t board_time_modified( const std::string& url ); // 板の更新時間( time_t ) std::string board_date_modified( const std::string& url ); // 板の更新時間( 文字列 ) void board_set_date_modified( const std::string& url, const std::string& date ); // 板の更新時間( 文字列 )をセット const std::string& board_get_modified_localrule( const std::string& url ); void board_set_modified_localrule( const std::string& url, const std::string& modified ); const std::string& board_get_modified_setting( const std::string& url ); void board_set_modified_setting( const std::string& url, const std::string& modified ); std::string board_name( const std::string& url ); std::string board_subjecttxt( const std::string& url ); std::string board_charset( const std::string& url ); std::string board_cookie_by_host( const std::string& url ); std::string board_cookie_for_request( const std::string& url ); std::string board_cookie_for_post( const std::string& url ); void board_set_list_cookies( const std::string& url, const std::list< std::string>& list_cookies ); void board_delete_cookies( const std::string& url ); std::string board_keyword_for_write( const std::string& url ); void board_set_keyword_for_write( const std::string& url, const std::string& keyword ); void board_analyze_keyword_for_write( const std::string& url, const std::string& html ); std::string board_keyword_for_newarticle( const std::string& url ); void board_set_keyword_for_newarticle( const std::string& url, const std::string& keyword ); void board_analyze_keyword_for_newarticle( const std::string& url, const std::string& html ); std::string board_parse_form_data( const std::string& url, const std::string& html ); std::string board_basicauth( const std::string& url ); std::string board_ext( const std::string& url ); int board_status( const std::string& url ); int board_code( const std::string& url ); std::string board_str_code( const std::string& url ); void board_save_info( const std::string& url ); void board_download_front( const std::string& url ); void board_download_subject( const std::string& url, const std::string& url_update_view ); void board_read_subject_from_cache( const std::string& url ); bool board_is_loading( const std::string& url ); void board_stop_load( const std::string& url ); std::vector< DBTREE::ArticleBase* >& board_list_subject( const std::string& url ); int board_view_sort_column( const std::string& url ); void board_set_view_sort_column( const std::string& url, int column ); int board_view_sort_mode( const std::string& url ); void board_set_view_sort_mode( const std::string& url, int mode ); int board_view_sort_pre_column( const std::string& url ); void board_set_view_sort_pre_column( const std::string& url, int column ); int board_view_sort_pre_mode( const std::string& url ); void board_set_view_sort_pre_mode( const std::string& url, int mode ); bool board_check_noname( const std::string& url ); void board_set_check_noname( const std::string& url, const bool check ); bool board_show_oldlog( const std::string& url ); void board_set_show_oldlog( const std::string& url, const bool show ); int board_get_mode_local_proxy( const std::string& url ); const std::string& board_get_local_proxy( const std::string& url ); int board_get_local_proxy_port( const std::string& url ); const std::string& board_get_local_proxy_basicauth( const std::string& url ); void board_set_mode_local_proxy( const std::string& url, int mode ); void board_set_local_proxy( const std::string& url, const std::string& proxy ); void board_set_local_proxy_port( const std::string& url, int port ); int board_get_mode_local_proxy_w( const std::string& url ); const std::string& board_get_local_proxy_w( const std::string& url ); int board_get_local_proxy_port_w( const std::string& url ); const std::string& board_get_local_proxy_basicauth_w( const std::string& url ); void board_set_mode_local_proxy_w( const std::string& url, int mode ); void board_set_local_proxy_w( const std::string& url, const std::string& proxy ); void board_set_local_proxy_port_w( const std::string& url, int port ); const std::string& board_get_write_name( const std::string& url ); const std::string& board_get_write_mail( const std::string& url ); void board_set_write_name( const std::string& url, const std::string& name ); void board_set_write_mail( const std::string& url, const std::string& mail ); void board_update_writetime( const std::string& url ); time_t board_write_time( const std::string& url ); time_t board_write_pass( const std::string& url ); time_t board_samba_sec( const std::string& url ); void board_set_samba_sec( const std::string& url, time_t sec ); time_t board_write_leftsec( const std::string& url ); // 更新可能状態にしてお気に入りやスレ一覧のタブのアイコンに更新マークを表示 // update == true の時に表示。falseなら戻す void board_show_updateicon( const std::string& url, const bool update ); // 板の更新チェック時に、更新チェックを行うスレのアドレスのリスト // キャッシュが存在し、かつdat落ちしていないで新着数が0のスレを速度の順でソートして返す std::list< std::string > board_get_check_update_articles( const std::string& url ); // datファイルのインポート // 成功したらdat型のurlを返す std::string board_import_dat( const std::string& url, const std::string& filename ); // 各板に属する全スレの書き込み履歴のリセット void board_clear_all_post_history( const std::string& url ); int board_get_number_max_res( const std::string& url ); void board_set_number_max_res( const std::string& url, const int number ); // datの最大サイズ(Kバイト) int board_get_max_dat_lng( const std::string& url ); time_t board_get_live_sec( const std::string& url ); void board_set_live_sec( const std::string& url, time_t sec ); time_t board_last_access_time( const std::string& url ); // 板のユーザーエージェント設定 const std::string& board_get_board_agent( const std::string& url ); void board_set_board_agent( const std::string& url, const std::string& user_agent ); // 全スレの書き込み履歴のリセット void clear_all_post_history(); // 全板の情報ファイル読み込み void read_boardinfo_all(); // キャッシュ内のログ検索 // ArticleBase のアドレスをリスト(list_article)にセットして返す // query が空の時はキャッシュにあるログを全てヒットさせる // bm がtrueの時、しおりが付いている(スレ一覧でしおりを付けた or レスに一つでもしおりが付いている)スレのみを対象に検索する void search_cache_all( std::vector< DBTREE::ArticleBase* >& list_article, const std::string& query, const bool mode_or, const bool bm, const bool stop ); void search_cache( const std::string& url, std::vector< DBTREE::ArticleBase* >& list_article, const std::string& query, const bool mode_or, const bool bm, const bool stop ); // article 系 bool article_is_cached( const std::string& url ); // キャッシュにあるかどうか std::string article_id( const std::string& url ); // 拡張子込み "12345.dat" みたいに std::string article_key( const std::string& url ); // idから拡張子を取ったもの。書き込み用 std::string article_org_host( const std::string& url ); // 移転する前のオリジナルのURL time_t article_since_time( const std::string& url ); std::string article_since_date( const std::string& url ); time_t article_time_modified( const std::string& url ); // スレの更新時間( time_t ) std::string article_date_modified( const std::string& url ); // スレの更新時間( 文字列 ) void article_set_date_modified( const std::string& url, const std::string& date ); // スレの更新時間( 文字列 )をセット int article_hour( const std::string& url ); time_t article_write_time( const std::string& url ); std::string article_write_date( const std::string& url ); int article_status( const std::string& url ); int article_code( const std::string& url ); std::string article_str_code( const std::string& url ); std::string article_ext_err( const std::string& url ); std::string article_subject( const std::string& url ); int article_number( const std::string& url ); int article_number_load( const std::string& url ); int article_number_seen( const std::string& url ); void article_set_number_seen( const std::string& url, int seen ); int article_number_new( const std::string& url ); bool article_is_loading( const std::string& url ); bool article_is_checking_update( const std::string& url ); void article_download_dat( const std::string& url, const bool check_update ); void article_set_url_pre_article( const std::string& url, const std::string& url_pre_article ); void article_copy_article_info( const std::string& url, const std::string& url_src ); void article_stop_load( const std::string& url ); int article_get_speed( const std::string& url ); // 書き込み履歴のリセット void article_clear_post_history( const std::string& url ); // キャッシュ削除 // cache_only == true の時はキャッシュだけ削除してスレ情報は消さない void delete_article( const std::string& url, const bool cache_only ); // キャッシュ保存 bool article_save_dat( const std::string& url, const std::string& path_to ); // 全スレ情報の保存 void save_articleinfo_all(); void article_update_writetime( const std::string& url ); size_t article_lng_dat( const std::string& url ); // ユーザーエージェント const std::string& get_agent( const std::string& url ); // ダウンロード用 const std::string& get_agent_w( const std::string& url ); // 書き込み用 // 読み込み用プロキシ std::string get_proxy_host( const std::string& url ); int get_proxy_port( const std::string& url ); std::string get_proxy_basicauth( const std::string& url ); // 書き込み用プロキシ std::string get_proxy_host_w( const std::string& url ); int get_proxy_port_w( const std::string& url ); std::string get_proxy_basicauth_w( const std::string& url ); // ローカルルール std::string localrule( const std::string& url ); // setting.txt std::string settingtxt( const std::string& url ); // 書き込み関係 // デフォルトの名無し名 std::string default_noname( const std::string& url ); // 最大改行数/2 int line_number( const std::string& url ); // 最大書き込みバイト数 int message_count( const std::string& url ); // 特殊文字書き込み可能か( pass なら可能、 change なら不可 ) std::string get_unicode( const std::string& url ); // 書き込み時の名前とメール const std::string& write_name( const std::string& url ); void set_write_name( const std::string& url, const std::string& str ); bool write_fixname( const std::string& url ); void set_write_fixname( const std::string& url, bool set ); const std::string& write_mail( const std::string& url ); void set_write_mail( const std::string& url, const std::string& str ); bool write_fixmail( const std::string& url ); void set_write_fixmail( const std::string& url, bool set ); // ポストするメッセージの作成 std::string create_write_message( const std::string& url, const std::string& name, const std::string& mail, const std::string& msg ); std::string create_newarticle_message( const std::string& url, const std::string& subject, const std::string& name, const std::string& mail, const std::string& msg ); // 書き込み時のリファラ std::string get_write_referer( const std::string& url ); // スレ立て時のリファラ std::string get_newarticle_referer( const std::string& url ); // あぼーん関係 // 板レベルでのあぼーん情報 // グローバルなあぼーん情報は globalconf が管理 const std::list< std::string >& get_abone_list_id_board( const std::string& url ); const std::list< std::string >& get_abone_list_name_board( const std::string& url ); const std::list< std::string >& get_abone_list_word_board( const std::string& url ); const std::list< std::string >& get_abone_list_regex_board( const std::string& url ); // 板レベルでのあぼーん状態のリセット(情報セットとスレビューの表示更新を同時におこなう) void reset_abone_board( const std::string& url, const std::list< std::string >& ids, const std::list< std::string >& names, const std::list< std::string >& words, const std::list< std::string >& regexs ); // 板レベルでのあぼ〜ん状態更新(reset_abone()と違って各項目ごと個別におこなう。スレビューの表示更新も同時におこなう) void add_abone_id_board( const std::string& url, const std::string& id ); void add_abone_name_board( const std::string& url, const std::string& name ); void add_abone_word_board( const std::string& url, const std::string& word ); // スレあぼーん情報 // グローバルなあぼーん情報は globalconf が管理 const std::list< std::string >& get_abone_list_thread( const std::string& url ); const std::list< std::string >& get_abone_list_thread_remove( const std::string& url ); const std::list< std::string >& get_abone_list_word_thread( const std::string& url ); const std::list< std::string >& get_abone_list_regex_thread( const std::string& url ); const std::unordered_set< int >& get_abone_reses( const std::string& url ); int get_abone_low_number_thread( const std::string& url ); int get_abone_high_number_thread( const std::string& url ); int get_abone_hour_thread( const std::string& url ); // subject.txtのロード後にdat落ちしたスレッドをスレあぼーんのリストから取り除く void remove_old_abone_thread( const std::string& url ); // スレあぼーん情報を更新した時、全boardbaseクラスに対応するスレ一覧の表示を更新させる // CONFIG::set_abone_number_thread() などでグローバル設定をした後などに呼び出す void update_abone_thread(); // スレあぼーん状態のリセット // redraw : スレ一覧の表示更新を行う void reset_abone_thread( const std::string& url, const std::list< std::string >& threads, const std::list< std::string >& words, const std::list< std::string >& regexs, const int low_number, const int high_number, const int hour, const bool redraw ); // // 各articlebase別のあぼーん情報 // // 全articlebaseクラスのあぼーん状態の更新 void update_abone_all_article(); // 全articlebaseクラスの書き込み時間とスレ立て時間の文字列をリセット void reset_all_since_date(); void reset_all_write_date(); void reset_all_access_date(); // レスあぼーん // グローバルなあぼーん情報は globalconf が管理 const std::list< std::string >& get_abone_list_id( const std::string& url ); const std::list< std::string >& get_abone_list_name( const std::string& url ); const std::list< std::string >& get_abone_list_word( const std::string& url ); const std::list< std::string >& get_abone_list_regex( const std::string& url ); // 全あぼーん情報の同時セットと更新 void reset_abone( const std::string& url, const std::list< std::string >& ids, const std::list< std::string >& names, const std::list< std::string >& words, const std::list< std::string >& regexs, const std::vector< char >& vec_abone_res, const bool transparent, const bool chain, const bool age, const bool default_name, const bool noid, const bool board, const bool global ); // 個別のあぼーん情報のセットと更新 void set_abone_res( const std::string& url, const int num_from, const int num_to, const bool set ); void add_abone_id( const std::string& url, const std::string& id ); void add_abone_name( const std::string& url, const std::string& name ); void add_abone_word( const std::string& url, const std::string& word ); // 透明あぼーん bool get_abone_transparent( const std::string& url ); void set_abone_transparent( const std::string& url, const bool set ); // 連鎖あぼーん bool get_abone_chain( const std::string& url ); void set_abone_chain( const std::string& url, const bool set ); // ageあぼーん bool get_abone_age( const std::string& url ); void set_abone_age( const std::string& url, const bool set ); // デフォルト名無しあぼーん bool get_abone_default_name( const std::string& url ); void set_abone_default_name( const std::string& url, const bool set ); // ID無しあぼーん bool get_abone_noid( const std::string& url ); void set_abone_noid( const std::string& url, const bool set ); // 板レベルでのあぼーん bool get_abone_board( const std::string& url ); void set_abone_board( const std::string& url, const bool set ); // 全体レベルでのあぼーん bool get_abone_global( const std::string& url ); void set_abone_global( const std::string& url, const bool set ); // ブックマーク関係 // スレのブックマーク bool is_bookmarked_thread( const std::string& url ); void set_bookmarked_thread( const std::string& url, const bool bookmarked ); // レスのブックマーク int get_num_bookmark( const std::string& url ); bool is_bookmarked( const std::string& url, const int number ); void set_bookmark( const std::string& url, const int number, const bool set ); } #endif jdim-0.7.0/src/dbtree/meson.build000066400000000000000000000013271417047150700166500ustar00rootroot00000000000000sources = [ 'article2ch.cpp', 'article2chcompati.cpp', 'articlebase.cpp', 'articlehash.cpp', 'articlejbbs.cpp', 'articlelocal.cpp', 'articlemachi.cpp', 'board2ch.cpp', 'board2chcompati.cpp', 'boardbase.cpp', 'boardfactory.cpp', 'boardjbbs.cpp', 'boardlocal.cpp', 'boardmachi.cpp', 'frontloader.cpp', 'interface.cpp', 'nodetree2ch.cpp', 'nodetree2chcompati.cpp', 'nodetreebase.cpp', 'nodetreedummy.cpp', 'nodetreejbbs.cpp', 'nodetreelocal.cpp', 'nodetreemachi.cpp', 'root.cpp', 'ruleloader.cpp', 'settingloader.cpp', 'spchar_decoder.cpp', ] dbtree_lib = static_library( 'dbtree', sources, dependencies : gtkmm_dep, include_directories : include_directories('..'), ) jdim-0.7.0/src/dbtree/node.h000066400000000000000000000062221417047150700156030ustar00rootroot00000000000000// ライセンス: GPL2 // // ノードツリーのノード // #ifndef _NODE_H #define _NODE_H namespace DBIMG { class Img; } namespace DBTREE { // NODE::type enum { NODE_HEADER = 0, // ヘッダ NODE_BLOCK, // ブロック先頭 NODE_TEXT, // テキスト NODE_LINK, // リンク NODE_IDNUM, // 発言回数(IDの出現数) NODE_BR, // 改行 NODE_HR, // 水平線 NODE_DIV, // div NODE_IMG, // img // スペース(幅0) NODE_ZWSP, // 連続半角スペース NODE_MULTISP, // 水平タブ(0x09) NODE_HTAB, // sssp アイコン NODE_SSSP, NODE_NONE }; // HEADERINFO::block enum { BLOCK_NUMBER = 0, // レス番号 BLOCK_NAMELINK, // 「名前」の文字列(デフォルト名前で無いときはリンク) BLOCK_NAME, // 名前 BLOCK_MAIL, // メール BLOCK_DATE, // 日付 BLOCK_ID_NAME, // ID BLOCK_MES, // 本文 BLOCK_NUM }; struct NODE; // アンカー情報 // anc_from番 から anc_to番までのアンカー struct ANCINFO { int anc_from; int anc_to; }; // ヘッダ拡張情報 struct HEADERINFO { NODE* next_header; // 次のヘッダノードのアドレス bool abone; // あぼーんされているか int num_reference; // 他のレスから参照されている数 char* name; // 名前 bool sage; // メール欄がsageか int num_id_name; // 同じIDのレスの個数( = 発言数 ) NODE* block[ BLOCK_NUM ]; }; // リンク情報 struct LINKINFO { char* link; // リンクURL // アンカー情報のベクトル // nullptr なら一般のリンク // ancinfo->anc_from == ancinfo->anc_to == 0 が終端 ANCINFO* ancinfo; // 画像関係の情報 // // 画像リンクの場合、実際にリンクが画面に表示される段階でノードに DBIMG::Img // のポインタと色をセットする。 // image == true かつ img == nullptr ならまだ img は未取得 // 実際にノードが画面に表示された際に img のポインタを取得して画像の状態を取得する // 詳しくは DrawAreaBase::draw_one_node() を参照 bool image; // 画像かどうか char* imglink; // 画像のURL DBIMG::Img* img; // 画像データクラスへのポインタ(危険だが高速化のため直接アクセス、deleteしないこと) }; // ノード構造体 struct NODE { unsigned char type; int id_header; // ヘッダID ( つまりレス番号、ルートヘッダは0 ) NODE* next_node; // 最終ノードはnullptr char* text; unsigned char color_text; // 色 bool bold; char fontid; // fontid.h // ヘッダ拡張情報 HEADERINFO* headinfo; // リンク情報 LINKINFO* linkinfo; }; } #endif jdim-0.7.0/src/dbtree/nodetree2ch.cpp000066400000000000000000000211351417047150700174130ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "nodetree2ch.h" #include "interface.h" #include "jdlib/jdregex.h" #include "jdlib/loaderdata.h" #include "jdlib/miscutil.h" #include "config/globalconf.h" #include "httpcode.h" #include "session.h" #include "login2ch.h" #include using namespace DBTREE; enum { MODE_NORMAL = 0, MODE_OFFLAW, MODE_KAKO_GZ, MODE_KAKO, MODE_OLDURL }; NodeTree2ch::NodeTree2ch( const std::string& url, const std::string& org_url, const std::string& date_modified, time_t since_time ) : NodeTree2chCompati( url, date_modified ) , m_org_url( org_url ) , m_since_time( since_time ) , m_mode( MODE_NORMAL ) { #ifdef _DEBUG std::cout << "NodeTree2ch::NodeTree2ch url = " << url << std::endl << "org_url = " << m_org_url << " modified = " << date_modified << " since = " << m_since_time << std::endl; #endif } NodeTree2ch::~NodeTree2ch() { #ifdef _DEBUG std::cout << "NodeTree2ch::~NodeTree2ch : " << get_url() << std::endl; #endif } // // キャッシュに保存する前の前処理 // // 先頭にrawモードのステータスが入っていたら取り除く // char* NodeTree2ch::process_raw_lines( char* rawlines ) { char* pos = rawlines; if( m_mode == MODE_OFFLAW ){ // rokka独自のステータスが入っている int status = 0; if( strncmp( pos, "Success", 7 ) == 0 ) status = 1; if( strncmp( pos, "Error", 5 ) == 0 ) status = 2; #ifdef _DEBUG std::cout << "NodeTree2ch::process_raw_lines : raw mode status = " << status << std::endl; #endif if( status != 0 ){ pos = skip_status_line( pos, status ); } } else { pos = NodeTree2chCompati::process_raw_lines( rawlines ); } return pos; } // // ロード用データ作成 // void NodeTree2ch::create_loaderdata( JDLIB::LOADERDATA& data ) { #ifdef _DEBUG std::cout << "NodeTree2ch::create_loaderdata : mode = " << m_mode << " url = " << get_url() << std::endl; #endif data.url = std::string(); data.byte_readfrom = 0; //rokka使用 (旧offlaw, offlaw2は廃止) if( m_mode == MODE_OFFLAW ){ JDLIB::Regex regex; const size_t offset = 0; const bool icase = false; const bool newline = true; const bool usemigemo = false; const bool wchar = false; if( ! regex.exec( "(https?://)([^/\\.]+)(\\.[^/]+)(/.*)/dat(/.*)\\.dat$", m_org_url, offset, icase, newline, usemigemo, wchar ) ) return; // http://rokka(.2ch.net|.bbspink.com)////?sid= std::ostringstream ss; ss << regex.str( 1 ) << "rokka" << regex.str( 3 ) << "/" << regex.str( 2 ) << regex.str( 4 ) << regex.str( 5 ); std::string sid = CORE::get_login2ch()->get_sessionid(); ss << "/?sid=" << MISC::url_encode( sid.c_str(), sid.length() ); // レジューム設定 // レジュームを有りにして、サーバが range を無視して送ってきた場合と同じ処理をする if( get_lng_dat() ) { set_resume( true ); } else set_resume( false ); data.url = ss.str(); } // 過去ログ倉庫使用 else if( m_mode == MODE_KAKO_GZ || m_mode == MODE_KAKO ){ JDLIB::Regex regex; const size_t offset = 0; const bool icase = false; const bool newline = true; const bool usemigemo = false; const bool wchar = false; if( ! regex.exec( "(https?://[^/]*)(/.*)/dat(/.*)\\.dat$", m_org_url, offset, icase, newline, usemigemo, wchar ) ) return; const int id = atoi( regex.str( 3 ).c_str() + 1 ); std::ostringstream ss; // スレIDが10桁の場合 → http://サーバ/板ID/kako/IDの上位4桁/IDの上位5桁/ID.dat.gz if( id / 1000000000 ) ss << regex.str( 1 ) << regex.str( 2 ) << "/kako/" << ( id / 1000000 ) << "/" << ( id / 100000 ) << regex.str( 3 ); // スレIDが9桁の場合 → http://サーバ/板ID/kako/IDの上位3桁/ID.dat.gz else ss << regex.str( 1 ) << regex.str( 2 ) << "/kako/" << ( id / 1000000 ) << regex.str( 3 ); if( m_mode == MODE_KAKO_GZ ) ss << ".dat.gz"; else ss << ".dat"; // レジュームは無し set_resume( false ); data.url = ss.str(); } // 普通もしくは旧URLからの読み込み else{ // レジューム設定 // 1byte前からレジュームして '\n' が返ってこなかったらあぼーんがあったってこと if( get_lng_dat() ) { data.byte_readfrom = get_lng_dat() -1; set_resume( true ); } else set_resume( false ); data.url = ( m_mode == MODE_OLDURL ) ? m_org_url : get_url(); } #ifdef _DEBUG std::cout << "load from " << data.url << std::endl; #endif data.agent = DBTREE::get_agent( get_url() ); #ifdef _DEBUG std::cout << "agent = " << data.agent << std::endl; #endif data.host_proxy = DBTREE::get_proxy_host( get_url() ); data.port_proxy = DBTREE::get_proxy_port( get_url() ); data.basicauth_proxy = DBTREE::get_proxy_basicauth( get_url() ); data.cookie_for_request = DBTREE::board_cookie_for_request( get_url() ); data.size_buf = CONFIG::get_loader_bufsize(); data.timeout = CONFIG::get_loader_timeout(); if( ! get_date_modified().empty() ) data.modified = get_date_modified(); } // // ロード完了 // void NodeTree2ch::receive_finish() { #ifdef _DEBUG std::cout << "NodeTree2ch::receive_finish : " << get_url() << std::endl << "mode = " << m_mode << " code = " << get_code() << std::endl; #endif // 更新チェックではない、オンラインの場合は offlaw や 過去ログ倉庫から取得出来るか試みる if( ! is_checking_update() && SESSION::is_online() && ( get_code() == HTTP_REDIRECT || get_code() == HTTP_MOVED_PERM || get_code() == HTTP_NOT_FOUND || ( m_mode == MODE_OFFLAW && ! get_ext_err().empty() ) // rokka 読み込み失敗 ) ){ /* ・スレIDが10桁の場合 → http://サーバ/板ID/kako/IDの上位4桁/IDの上位5桁/ID.dat.gz (例) http://HOGE.2ch.net/test/read.cgi/hoge/1234567890/ を取得 (1) http://HOGE.2ch.net/hoge/dat/1234567890.dat から dat を取得。302で●がある場合(2-1)、旧URLがある場合(2-2)、無い場合は(2-3)へ(※) (2-1) offlaw.cgiを使って取得 (2-2) 旧URLから取得 (2-3) http://HOGE.2ch.net/hoge/kako/1234/12345/1234567890.dat.gz から取得。302なら(3)へ (3) http://HOGE.2ch.net/hoge/kako/1234/12345/1234567890.dat から取得 ・スレIDが9桁の場合 → http://サーバ/板ID/kako/IDの上位3桁/ID.dat.gz (例) http://HOGE.2ch.net/test/read.cgi/hoge/123456789/ を取得 (1) http://HOGE.2ch.net/hoge/dat/1234567890.dat から dat を取得。302で●がある場合(2-1)、旧URLがある場合(2-2)、無い場合は(2-3)へ(※) (2-1) offlaw.cgiを使って取得 (2-2) 旧URLから取得 (2-3) http://HOGE.2ch.net/hoge/kako/123/123456789.dat.gz から取得。302なら(3)へ (3) http://HOGE.2ch.net/hoge/kako/123/123456789.dat から取得 (※)ただし 2008年1月1日以降に立てられたスレは除く (注) 古すぎる(2000年頃)のdatは形式が違う(<>ではなくて,で区切られている)ので読み込みに失敗する */ // ログインしている場合は rokka 経由で旧URLで再取得 if( m_mode == MODE_NORMAL && CORE::get_login2ch()->login_now() ) m_mode = MODE_OFFLAW; // 旧URLがある場合、そのURLで再取得 else if( ( m_mode == MODE_NORMAL || m_mode == MODE_OFFLAW ) && get_url() != m_org_url ) m_mode = MODE_OLDURL; // 過去ログ倉庫(gz圧縮) // ただし 2008年1月1日以降に立てられたスレは除く else if( ( m_mode == MODE_NORMAL || m_mode == MODE_OFFLAW || m_mode == MODE_OLDURL ) && m_since_time < 1199113200 ) m_mode = MODE_KAKO_GZ; // 過去ログ倉庫 else if( m_mode == MODE_KAKO_GZ ) m_mode = MODE_KAKO; // 失敗 else m_mode = MODE_NORMAL; #ifdef _DEBUG std::cout << "switch mode to " << m_mode << std::endl; #endif if( m_mode != MODE_NORMAL ){ download_dat( is_checking_update() ); return; } } // offlaw や 過去ログから読み込んだ場合は DAT 落ちにする if( m_mode != MODE_NORMAL ){ m_mode = MODE_NORMAL; set_code( HTTP_OLD ); } NodeTreeBase::receive_finish(); } jdim-0.7.0/src/dbtree/nodetree2ch.h000066400000000000000000000014641417047150700170630ustar00rootroot00000000000000// ライセンス: GPL2 // // 2ch型ノードツリー // #ifndef _NODETREE2ch_H #define _NODETREE2ch_H #include "nodetree2chcompati.h" #include namespace DBTREE { class NodeTree2ch : public NodeTree2chCompati { std::string m_org_url; // 移転前のオリジナルURL time_t m_since_time; // スレが立った時刻 int m_mode; // 読み込みモード public: NodeTree2ch( const std::string& url, const std::string& org_url, const std::string& date_modified, time_t since_time ); ~NodeTree2ch(); protected: char* process_raw_lines( char* rawlines ) override; void create_loaderdata( JDLIB::LOADERDATA& data ) override; private: void receive_finish() override; }; } #endif jdim-0.7.0/src/dbtree/nodetree2chcompati.cpp000066400000000000000000000102201417047150700207610ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "nodetree2chcompati.h" #include "interface.h" #include "jdlib/jdiconv.h" #include "jdlib/loaderdata.h" #include "jdlib/miscmsg.h" #include "config/globalconf.h" using namespace DBTREE; NodeTree2chCompati::NodeTree2chCompati( const std::string& url, const std::string& date_modified ) : NodeTreeBase( url, date_modified ) { #ifdef _DEBUG std::cout << "NodeTree2chCompati::NodeTree2chCompati url = " << url << " modified = " << date_modified << std::endl; #endif } NodeTree2chCompati::~NodeTree2chCompati() { #ifdef _DEBUG std::cout << "NodeTree2chCompati::~NodeTree2chCompati : " << get_url() << std::endl; #endif NodeTree2chCompati::clear(); } // // バッファなどのクリア // void NodeTree2chCompati::clear() { #ifdef _DEBUG std::cout << "NodeTree2chCompati::clear : " << get_url() << std::endl; #endif NodeTreeBase::clear(); // iconv 削除 m_iconv.reset(); } // // ロード実行前に呼ぶ初期化関数 // void NodeTree2chCompati::init_loading() { #ifdef _DEBUG std::cout << "NodeTree2chCompati::init_loading : " << get_url() << std::endl; #endif NodeTreeBase::init_loading(); // iconv 初期化 std::string charset = DBTREE::board_charset( get_url() ); if( ! m_iconv ) m_iconv = std::make_unique( "UTF-8", charset ); } // // キャッシュに保存する前の前処理 // // 先頭にrawモードのステータスが入っていたら取り除く // char* NodeTree2chCompati::process_raw_lines( char* rawlines ) { char* pos = rawlines; if( *pos == '+' || *pos == '-' || *pos == 'E' ){ int status = 0; if( pos[ 1 ] == 'O' && pos[ 2 ] == 'K' ) status = 1; if( pos[ 1 ] == 'E' && pos[ 2 ] == 'R' && pos[ 3 ] == 'R' ) status = 2; if( pos[ 1 ] == 'I' && pos[ 2 ] == 'N' && pos[ 3 ] == 'C' && pos[ 4 ] == 'R' ) status = 3; if( pos[ 0 ] == 'E' && pos[ 1 ] == 'R' && pos[ 2 ] == 'R' && pos[ 3 ] == 'O' && pos[ 4 ] == 'R' ) status = 4; #ifdef _DEBUG std::cout << "NodeTree2chCompati::process_raw_lines : raw mode status = " << status << std::endl; #endif if( status != 0 ){ pos = skip_status_line( pos, status ); } } return pos; } // // ステータス行のスキップ処理 // status == 1 : 正常ステータス // status != 1 : 異常ステータス // char* NodeTree2chCompati::skip_status_line( char* pos, int status ) { // この行を飛ばす char* pos_msg = pos; while( *pos != '\n' && *pos != '\0' ) ++pos; // エラー if( status != 1 ){ int byte; std::string ext_err = std::string( m_iconv->convert( pos_msg, pos - pos_msg, byte ) ); set_ext_err( ext_err ); MISC::ERRMSG( ext_err ); } if( *pos == '\n' ) ++pos; return pos; } // // raw データを dat 形式に変換 // // 2ch型サーバの場合は文字コードを変換するだけ // const char* NodeTree2chCompati::raw2dat( char* rawlines, int& byte ) { assert( m_iconv != nullptr ); // バッファ自体はiconvクラスの中で持っているのでポインタだけもらう return m_iconv->convert( rawlines, strlen( rawlines ), byte ); } // // ロード用データ作成 // void NodeTree2chCompati::create_loaderdata( JDLIB::LOADERDATA& data ) { data.url = get_url(); data.agent = DBTREE::get_agent( get_url() ); // 1byte前からレジュームして '\n' が返ってこなかったらあぼーんがあったってこと if( get_lng_dat() ) { data.byte_readfrom = get_lng_dat() -1; set_resume( true ); } else set_resume( false ); data.host_proxy = DBTREE::get_proxy_host( get_url() ); data.port_proxy = DBTREE::get_proxy_port( get_url() ); data.basicauth_proxy = DBTREE::get_proxy_basicauth( get_url() ); data.size_buf = CONFIG::get_loader_bufsize(); data.timeout = CONFIG::get_loader_timeout(); data.basicauth = DBTREE::board_basicauth( get_url() ); data.cookie_for_request = DBTREE::board_cookie_for_request( get_url() ); if( ! get_date_modified().empty() ) data.modified = get_date_modified(); } jdim-0.7.0/src/dbtree/nodetree2chcompati.h000066400000000000000000000014761417047150700204430ustar00rootroot00000000000000// ライセンス: GPL2 // // 2ch互換型ノードツリー // #ifndef _NODETREE2CHCOMPATI_H #define _NODETREE2CHCOMPATI_H #include "nodetreebase.h" #include namespace JDLIB { class Iconv; } namespace DBTREE { class NodeTree2chCompati : public NodeTreeBase { std::unique_ptr m_iconv; public: NodeTree2chCompati( const std::string& url, const std::string& date_modified ); ~NodeTree2chCompati(); protected: void clear() override; void init_loading() override; char* process_raw_lines( char* rawlines ) override; const char* raw2dat( char* rawlines, int& byte ) override; char* skip_status_line( char* pos, int status ); void create_loaderdata( JDLIB::LOADERDATA& data ) override; }; } #endif jdim-0.7.0/src/dbtree/nodetreebase.cpp000066400000000000000000003416121417047150700176560ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "nodetreebase.h" #include "spchar_decoder.h" #include "interface.h" #include "jdlib/miscutil.h" #include "jdlib/miscmsg.h" #include "jdlib/loaderdata.h" #include "dbimg/imginterface.h" #include "message/logmanager.h" #include "config/globalconf.h" #include "global.h" #include "httpcode.h" #include "colorid.h" #include "fontid.h" #include "command.h" #include "cache.h" #include "session.h" #include "replacestrmanager.h" #include "urlreplacemanager.h" #include #include #include #include #ifndef MAX #define MAX( a, b ) ( a > b ? a : b ) #endif #ifndef MIN #define MIN( a, b ) ( a < b ? a : b ) #endif constexpr size_t SECTION_NUM = 5; constexpr int LNG_ID = 256; constexpr size_t LNG_LINK = 256; constexpr size_t MAX_ANCINFO = 64; constexpr int RANGE_REF = 20; constexpr size_t MAX_RES_DIGIT = std::numeric_limits< int >::digits10 + 1; // レスアンカーや発言数で使われるレスの桁数 constexpr size_t MAXSISE_OF_LINES = 512 * 1024; // ロード時に1回の呼び出しで読み込まれる最大データサイズ constexpr size_t SIZE_OF_HEAP = MAXSISE_OF_LINES + 64; constexpr size_t INITIAL_RES_BUFSIZE = 128; // レスの文字列を返すときの初期バッファサイズ constexpr std::size_t kMaxBytesOfUTF8Char = 4 + 1; // UTF-8文字の最大バイト数 + ヌル文字 // レジュームのモード enum { RESUME_NO = 0, // レジューム無し RESUME_MODE1, // 通常のレジューム RESUME_MODE2, // サーバが range を無視してデータを送ってきた RESUME_MODE3, // サーバが range を無視してデータを送ってきた時にデータをスキップ中 RESUME_FAILED // レジューム失敗 }; #define IS_URL(node) \ ( node->type == NODE_LINK && node->linkinfo->link \ && ( memcmp( node->linkinfo->link, "http", 4 ) == 0 \ || memcmp( node->linkinfo->link, "ftp", 3 ) == 0 ) ) using namespace DBTREE; NodeTreeBase::NodeTreeBase( const std::string& url, const std::string& modified ) : SKELETON::Loadable() , m_url( url ) , m_resume( RESUME_NO ) , m_heap( SIZE_OF_HEAP ) { set_date_modified( modified ); // ルートヘッダ作成。中は空。 m_id_header = -1; // ルートヘッダIDが 0 になるように -1 NODE* tmpnode = create_node_header(); assert( tmpnode ); assert( m_vec_header.size() == static_cast< decltype( m_vec_header.size() ) >( m_id_header ) ); m_vec_header.push_back( tmpnode ); m_default_noname = DBTREE::default_noname( m_url ); // 参照で色を変える回数 m_num_reference[ LINK_HIGH ] = CONFIG::get_num_reference_high(); m_num_reference[ LINK_LOW ] = CONFIG::get_num_reference_low(); // 発言数で色を変える回数 m_num_id[ LINK_HIGH ] = CONFIG::get_num_id_high(); m_num_id[ LINK_LOW ] = CONFIG::get_num_id_low(); // レスにアスキーアートがあると判定する正規表現 if( CONFIG::get_aafont_enabled() ){ constexpr bool icase = false; constexpr bool newline = true; m_aa_regex.set( CONFIG::get_regex_res_aa(), icase, newline ); } #ifdef _DEBUG std::cout << "NodeTreeBase::NodeTreeBase url = " << m_url << " modified = " << get_date_modified() << " noname = " << m_default_noname << std::endl; #endif } NodeTreeBase::~NodeTreeBase() { #ifdef _DEBUG std::cout << "NodeTreeBase::~NodeTreeBase : " << m_url << std::endl; #endif NodeTreeBase::clear(); } // // url の更新 // // 移転があったときなどにarticlebaseから呼ばれる // void NodeTreeBase::update_url( const std::string& url ) { if( empty() ) return; #ifdef _DEBUG std::string old_url = m_url; #endif m_url = url; #ifdef _DEBUG if( ! old_url.empty() ) std::cout << "NodeTreeBase::update_url from " << old_url << " to " << m_url << std::endl; #endif } // // バッファなどのクリア // void NodeTreeBase::clear() { #ifdef _DEBUG std::cout << "NodeTreeBase::clear : " << m_url << std::endl; #endif m_buffer_lines.clear(); m_buffer_lines.shrink_to_fit(); m_parsed_text.clear(); m_parsed_text.shrink_to_fit(); m_buffer_write.clear(); m_buffer_write.shrink_to_fit(); if( m_fout ) fclose( m_fout ); m_fout = nullptr; m_ext_err = std::string(); } // // 総レス数 // // ロード中は m_id_header 番のレスはまだ処理中なので m_id_header -1 を返す // int NodeTreeBase::get_res_number() const { if( is_loading() ) return m_id_header -1; return m_id_header; } // // number 番のレスのヘッダのポインタを返す // const NODE* NodeTreeBase::res_header( int number ) const { if( number > m_id_header || number <= 0 ) return nullptr; return m_vec_header[ number ]; } // // 指定したID の重複数( = 発言数 ) // // 下の get_num_id_name( int number ) と違って検索するので遅い // int NodeTreeBase::get_num_id_name( const std::string& id ) const { if( id.empty() ) return 0; if( ! CONFIG::get_check_id() ){ // IDの数を数えていない場合は全て数える int count = 0; for( int i = 1; i <= m_id_header; ++i ){ if( get_id_name( i ) == id ) count++; } return count; } // IDの数を数えている場合 for( int i = 1; i <= m_id_header; ++i ){ if( get_id_name( i ) == id ) return get_num_id_name( i ); } return 0; } // // number番の ID の重複数 // int NodeTreeBase::get_num_id_name( const int number ) const { const NODE* head = res_header( number ); if( ! head ) return 0; // IDの数を数えていない場合 if( ! CONFIG::get_check_id() ) return get_num_id_name( get_id_name( number ) ); return head->headinfo->num_id_name; } // // 指定した発言者IDを持つレス番号をリストにして取得 // std::list< int > NodeTreeBase::get_res_id_name( const std::string& id_name ) const { std::list< int > list_resnum; for( int i = 1; i <= m_id_header ; ++i ){ if( id_name == get_id_name( i ) && ( ! m_abone_transparent || ! get_abone( i ) ) // 透明あぼーんしていない or あぼーんしていない ) list_resnum.push_back( i ); } return list_resnum; } // // str_num で指定したレス番号をリストにして取得 // str_num は "from-to" の形式 (例) 3から10をセットしたいなら "3-10" // list_jointは出力で true のスレは前のスレに連結される (例) "3+4" なら 4が3に連結 // std::list< int > NodeTreeBase::get_res_str_num( const std::string& str_num, std::list< bool >& list_joint ) const { #ifdef _DEBUG std::cout << "NodeTreeBase::get_res_str_num " << str_num << std::endl; #endif std::list< int > list_resnum; // "," ごとにブロック分けする (例) "1-2,3-4+5" -> "1-2","3-4+5" std::list< std::string > list_str_num = MISC::StringTokenizer( str_num, ',' ); for( const std::string& comma_block : list_str_num ) { // "=" ごとにブロック分けする (例) "1-2=3-4+5" -> "1-2","3-4+5" std::list list_str_num_eq = MISC::StringTokenizer( comma_block, '=' ); for( const std::string& eq_block : list_str_num_eq ) { // true なら前のスレと結合 bool joint = false; // "+"ごとにブロックを分ける (例) "1+2-3+4" -> "1","2-3","4" std::list list_str_num_pl = MISC::StringTokenizer( eq_block, '+' ); for( const std::string& plus_block : list_str_num_pl ) { // num_from から num_to まで表示 int num_from = MAX( 1, atol( plus_block.c_str() ) ); if( num_from <= m_id_header ){ int num_to = 0; size_t i; if( ( i = plus_block.find( '-' ) ) != std::string::npos ) num_to = atol( plus_block.substr( i +1 ).c_str() ); num_to = MIN( MAX( num_to, num_from ), m_id_header ); for( int i2 = num_from; i2 <= num_to ; ++i2 ) { // 透明あぼーんしていない or あぼーんしていないなら追加 if( ! m_abone_transparent || ! get_abone( i2 ) ){ #ifdef _DEBUG std::cout << plus_block << " " << num_from << " - " << num_to << " i2 = " << i2 << " joint = " << joint << std::endl; #endif list_resnum.push_back( i2 ); list_joint.push_back( joint ); // "+"が付いていたら2つ目のブロックから連結指定 if( list_str_num_pl.size() >= 2 ) joint = true; } } } } } } return list_resnum; } // // URL を含むレス番号をリストにして取得 // std::list< int > NodeTreeBase::get_res_with_url() const { std::list< int > list_resnum; for( int i = 1; i <= m_id_header; ++i ){ const NODE* head = res_header( i ); if( head ){ for( int block = 0; block < BLOCK_NUM; ++block ){ const NODE* node = head->headinfo->block[ block ]; while( node ){ if( IS_URL( node ) && ( ! m_abone_transparent || ! get_abone( i ) ) // 透明あぼーんしていない or あぼーんしていない ){ list_resnum.push_back( i ); block = BLOCK_NUM; break; } node = node->next_node; } } } } return list_resnum; } // // ツリーに含まれてる 画像URL をリストにして取得 // std::list NodeTreeBase::get_imglinks() const { std::list list_urls; for( int i = 1; i <= m_id_header; ++i ){ const NODE* head = res_header( i ); if( head ){ for( int block = 0; block < BLOCK_NUM; ++block ){ const NODE* node = head->headinfo->block[ block ]; while( node ){ if( IS_URL( node ) && node->linkinfo->imglink ) { list_urls.emplace_back( node->linkinfo->imglink ); } node = node->next_node; } } } } return list_urls; } // // number番のレスを参照しているレス番号をリストにして取得 // std::list< int > NodeTreeBase::get_res_reference( const int number ) const { std::list< int > res_num; res_num.push_back( number ); return get_res_reference( res_num ); } // // res_num に含まれるレスを参照しているレス番号をリストにして取得 // std::list< int > NodeTreeBase::get_res_reference( const std::list< int >& res_num ) const { std::list< int > list_resnum; if( ! res_num.size() ) return list_resnum; for( int i = 1; i <= m_id_header; ++i ){ // 透明あぼーんは除外 if( m_abone_transparent && get_abone( i ) ) continue; const NODE* head = res_header( i ); if( head ){ for( int block = 0; block < BLOCK_NUM; ++block ){ const NODE* node = head->headinfo->block[ block ]; while( node ){ if( node->type == NODE_LINK ){ // アンカーノードの時は node->linkinfo->ancinfo != nullptr; if( node->linkinfo->ancinfo ){ int anc = 0; int anc_from; int anc_to; for(;;){ anc_from = node->linkinfo->ancinfo[ anc ].anc_from; anc_to = node->linkinfo->ancinfo[ anc ].anc_to; if( anc_from == 0 ) break; ++anc; for( const int number : res_num ) { if( i != number && anc_to - anc_from < RANGE_REF // >>1-1000 みたいなアンカーは弾く && anc_from <= number && number <= anc_to ) { list_resnum.push_back( i ); goto EXIT_LOOP; } } } } } node = node->next_node; } // while( node ) } // for( block ) } // if( head ) EXIT_LOOP:; } // for( int i ) #ifdef _DEBUG std::cout << "NodeTreeBase::get_reference\n"; for( const int resnum : list_resnum ) std::cout << resnum << std::endl; #endif return list_resnum; } // // 高参照レスの番号をリストにして取得 // std::list< int > NodeTreeBase::get_highly_referened_res() const { std::list< int > list_resnum; for( int i = 1; i <= m_id_header; ++i ){ const NODE* head = res_header( i ); if ( ! head ) continue; if ( get_abone( i ) ) continue; // あぼーんしているものは飛ばす if ( head->headinfo->num_reference < m_num_reference[ LINK_HIGH ] ) continue; // リストに追加 list_resnum.push_back( i ); } return list_resnum; } // // number番のレスに含まれるレスアンカーをリストにして取得 // std::list< ANCINFO* > NodeTreeBase::get_res_anchors( const int number ) { std::list< ANCINFO* > list_resnum; NODE* head = res_header( number ); if( head && head->headinfo && ! head->headinfo->abone ){ for( int block = 0; block < BLOCK_NUM; ++block ){ NODE* node = head->headinfo->block[ block ]; while( node ){ // アンカーノードの時は node->linkinfo->ancinfo != nullptr; if( node->type == NODE_LINK && node->linkinfo->ancinfo ){ for(int anc = 0; ; ++anc){ ANCINFO* anchor = &( node->linkinfo->ancinfo[ anc ] ); if( anchor->anc_from == 0 ) break; // >>1-1000 みたいなアンカーは弾く if( anchor->anc_to - anchor->anc_from < RANGE_REF ){ list_resnum.push_back( anchor ); } } } node = node->next_node; } // while( node ) } // for( block ) } // if( head ) return list_resnum; } // // query を含むレス番号をリストにして取得 // // mode_or == true なら OR抽出 // std::list< int > NodeTreeBase::get_res_query( const std::string& query, const bool mode_or ) const { std::list< int > list_resnum; if( query.empty() ) return list_resnum; std::list list_regex; JDLIB::Regex regex; const auto make_pattern = []( const std::string& query ) { constexpr bool icase = true; // 大文字小文字区別しない constexpr bool newline = true; // . に改行をマッチさせない constexpr bool usemigemo = true; // migemo使用 constexpr bool wchar = true; // 全角半角の区別をしない return JDLIB::RegexPattern( query, icase, newline, usemigemo, wchar ); }; const std::list list_query = MISC::split_line( query ); std::transform( list_query.cbegin(), list_query.cend(), std::back_inserter( list_regex ), make_pattern ); for( int i = 1; i <= m_id_header ; ++i ){ const std::string res_str = get_res_str( i ); bool apnd = true; if( mode_or ) apnd = false; for( const JDLIB::RegexPattern& pattern : list_regex ) { constexpr std::size_t offset = 0; const bool ret = regex.match( pattern, res_str, offset ); // OR if( mode_or ){ if( ret ){ apnd = true; break; } } // AND else{ if( ! ret ){ apnd = false; break; } } } if( apnd && ( ! m_abone_transparent || ! get_abone( i ) ) // 透明あぼーんしていない or あぼーんしていない ) list_resnum.push_back( i ); } return list_resnum; } // // number 番のレスの文字列を返す // // ref == true なら先頭に参照文字( "> "など)を付ける // #define GETNODESTR( id ) do{ \ node = head->headinfo->block[ id ]; \ while( node ){ \ if( node->type == DBTREE::NODE_BR ) str_res += "\n" + ref_prefix; \ else if( node->type == DBTREE::NODE_HTAB ) str_res += "\t"; \ else if( node->text ) str_res += node->text; \ node = node->next_node; \ } }while(0) \ std::string NodeTreeBase::get_res_str( int number, bool ref ) const { std::string str_res; #ifdef _DEBUG std::cout << "NodeTreeBase::get_res_str : num = " << number << std::endl; #endif const NODE* head = res_header( number ); if( ! head ) return std::string(); std::string ref_prefix; if( ref ) ref_prefix = CONFIG::get_ref_prefix(); str_res.reserve( INITIAL_RES_BUFSIZE ); str_res += ref_prefix; NODE* node; GETNODESTR( BLOCK_NUMBER ); str_res += " "; GETNODESTR( BLOCK_NAMELINK ); str_res += ":"; GETNODESTR( BLOCK_NAME ); str_res += " "; GETNODESTR( BLOCK_MAIL ); str_res += ": "; GETNODESTR( BLOCK_DATE ); str_res += " "; GETNODESTR( BLOCK_ID_NAME ); str_res += " "; str_res += "\n" + ref_prefix; GETNODESTR( BLOCK_MES ); str_res += "\n"; #ifdef _DEBUG std::cout << str_res << std::endl; #endif return str_res; } // // number番を書いた人の名前を取得 // std::string NodeTreeBase::get_name( int number ) const { const NODE* head = res_header( number ); if( ! head ) return std::string(); if( ! head->headinfo->name ) return std::string(); return head->headinfo->name; } // // number番の名前の重複数( = 発言数 ) // int NodeTreeBase::get_num_name( int number ) const { int num = 0; std::string name = get_name( number ); for( int i = 1; i <= m_id_header; ++i ){ if( get_name( i ) == name ) ++num; } return num; } // // 指定した発言者の名前のレス番号をリストにして取得 // std::list< int > NodeTreeBase::get_res_name( const std::string& name ) const { std::list< int > list_resnum; for( int i = 1; i <= m_id_header ; ++i ){ if( name == get_name( i ) && ( ! m_abone_transparent || ! get_abone( i ) ) // 透明あぼーんしていない or あぼーんしていない ) list_resnum.push_back( i ); } return list_resnum; } // // number番のレスの時刻を文字列で取得 // 内部で regex を使っているので遅い // std::string NodeTreeBase::get_time_str( int number ) const { std::string res_str = get_res_str( number ); if( res_str.empty() ) return std::string(); std::string time_str; JDLIB::Regex regex; const size_t offset = 0; const bool icase = false; const bool newline = true; const bool usemigemo = false; const bool wchar = false; if( regex.exec( " 名前:.+]: +([0-9]*/[0-9]*/[0-9]*[^ ]* [0-9]*:[0-9]*[^ ]*).*$", res_str, offset, icase, newline, usemigemo, wchar ) ){ time_str = regex.str( 1 ); } return time_str; } // // number番の ID 取得 // std::string NodeTreeBase::get_id_name( int number ) const { const NODE* head = res_header( number ); if( ! head ) return std::string(); if( ! head->headinfo->block[ BLOCK_ID_NAME ] ) return std::string(); return head->headinfo->block[ BLOCK_ID_NAME ]->next_node->linkinfo->link; } // // 基本ノード作成 // NODE* NodeTreeBase::create_node() { NODE* tmpnode = m_heap.heap_alloc(); tmpnode->id_header = m_id_header; tmpnode->fontid = FONT_EMPTY; // フォントID未設定 if( m_node_previous ) m_node_previous->next_node = tmpnode; m_node_previous = tmpnode; return tmpnode; } // // ヘッダノード作成 // // 要素数が(CONFIG::get_max_resnumber())より多くなる場合nullptrを返す NODE* NodeTreeBase::create_node_header() { if( m_id_header >= CONFIG::get_max_resnumber() ) { return nullptr; } ++m_id_header; m_node_previous = nullptr; NODE* tmpnode = create_node(); tmpnode->type = NODE_HEADER; // ヘッダ情報 tmpnode->headinfo = m_heap.heap_alloc(); if( m_id_header >= 2 ) m_vec_header[ m_id_header -1 ]->headinfo->next_header = tmpnode; return tmpnode; } // // block ノード作成 // // 名前や本文などのブロックの先頭に置く // NODE* NodeTreeBase::create_node_block() { m_node_previous = nullptr; NODE* tmpnode = create_node(); tmpnode->type = NODE_BLOCK; return tmpnode; } // // 発言回数(IDの出現数)ノード // NODE* NodeTreeBase::create_node_idnum() { const char* dummy = " (10000)"; NODE* tmpnode = create_node_text( dummy, COLOR_CHAR ); tmpnode->type = NODE_IDNUM; tmpnode->text[ 0 ] = '\0'; // メモリだけ確保して文字を消す return tmpnode; } // // 改行ノード作成 // NODE* NodeTreeBase::create_node_br() { NODE* tmpnode = create_node(); tmpnode->type = NODE_BR; return tmpnode; } // // 水平線ノード作成 // NODE* NodeTreeBase::create_node_hr() { NODE* tmpnode = create_node(); tmpnode->type = NODE_HR; return tmpnode; } // // スペースノード // NODE* NodeTreeBase::create_node_space( const int type ) { NODE* tmpnode = create_node(); tmpnode->type = type; return tmpnode; } // // 連続半角スペース // NODE* NodeTreeBase::create_node_multispace( const char* text, const int n, const char fontid ) { NODE* tmpnode = create_node_ntext( text, n, COLOR_CHAR, false, fontid ); tmpnode->type = NODE_MULTISP; return tmpnode; } // // 水平タブノード // NODE* NodeTreeBase::create_node_htab() { NODE* tmpnode = create_node(); tmpnode->type = NODE_HTAB; return tmpnode; } // // リンクノード作成 // // bold : 太字か // NODE* NodeTreeBase::create_node_link( const char* text, const int n, const char* link, const int n_link, const int color_text, const bool bold, const char fontid ) { NODE* tmpnode = create_node_ntext( text, n, color_text, bold, fontid ); if( tmpnode ){ tmpnode->type = NODE_LINK; // リンク情報作成 char *tmplink = m_heap.heap_alloc( n_link + 1 ); memcpy( tmplink, link, n_link ); tmplink[ n_link ] = '\0'; // リンク情報セット tmpnode->linkinfo = m_heap.heap_alloc(); tmpnode->linkinfo->link = tmplink; } return tmpnode; } // // アンカーノード作成 // NODE* NodeTreeBase::create_node_anc( const char* text, const int n, const char* link, const int n_link, const int color_text, const bool bold, const ANCINFO* ancinfo, const int lng_ancinfo, const char fontid ) { NODE* tmpnode = create_node_link( text, n, link, n_link, color_text, bold, fontid ); if( tmpnode ){ tmpnode->linkinfo->ancinfo = m_heap.heap_alloc( lng_ancinfo + 1 ); memcpy( tmpnode->linkinfo->ancinfo, ancinfo, sizeof( ANCINFO ) * lng_ancinfo ); } return tmpnode; } // // SSSPノード // NODE* NodeTreeBase::create_node_sssp( const char* link, const int n_link ) { NODE* tmpnode = create_node(); tmpnode->type = NODE_SSSP; // リンク情報作成 char *tmplink = m_heap.heap_alloc( n_link + 1 ); memcpy( tmplink, link, n_link ); tmplink[ n_link ] = '\0'; // リンク情報セット tmpnode->linkinfo = m_heap.heap_alloc(); tmpnode->linkinfo->link = tmplink; tmpnode->linkinfo->image = true; tmpnode->linkinfo->imglink = tmpnode->linkinfo->link; return tmpnode; } // // 画像ノード作成 // NODE* NodeTreeBase::create_node_img( const char* text, const int n, const char* link, const int n_link, const int color_text, const bool bold, const char fontid ) { NODE* tmpnode = create_node_link( text, n, link, n_link, color_text, bold, fontid ); if( tmpnode ){ tmpnode->linkinfo->image = true; tmpnode->linkinfo->imglink = tmpnode->linkinfo->link; } return tmpnode; } // // サムネイル画像ノード ( youtubeなどのサムネイル表示用 ) // NODE* NodeTreeBase::create_node_thumbnail( const char* text, const int n, const char* link, const int n_link, const char* thumb, const int n_thumb, const int color_text, const bool bold, const char fontid ) { NODE* tmpnode = create_node_link( text, n, link, n_link, color_text, bold, fontid ); if( tmpnode ){ // サムネイル画像のURLをセット char *tmpthumb = m_heap.heap_alloc( n_thumb + 1 ); memcpy( tmpthumb, thumb, n_thumb ); tmpthumb[ n_thumb ] = '\0'; tmpnode->linkinfo->imglink = tmpthumb; } return tmpnode; } // // テキストノード作成 // NODE* NodeTreeBase::create_node_text( const char* text, const int color_text, const bool bold, const char fontid ) { return create_node_ntext( text, strlen( text ), color_text, bold, fontid ); } // // テキストノード作成( サイズ指定 ) // NODE* NodeTreeBase::create_node_ntext( const char* text, const int n, const int color_text, const bool bold, const char fontid ) { if( n <= 0 ) return nullptr; NODE* tmpnode = create_node(); if( tmpnode ){ tmpnode->type = NODE_TEXT; tmpnode->text = m_heap.heap_alloc( n + MAX_RES_DIGIT + 4 ); memcpy( tmpnode->text, text, n ); tmpnode->text[ n ] = '\0'; tmpnode->color_text = color_text; tmpnode->bold = bold; if ( fontid != FONT_MAIN ) tmpnode->fontid = fontid; } return tmpnode; } // // html をコメントとして追加 // // パースして追加したノードのポインタを返す // NODE* NodeTreeBase::append_html( const std::string& html ) { if( is_loading() ) return nullptr; if( html.empty() ) return nullptr; #ifdef _DEBUG std::cout << "NodeTreeBase::append_html url = " << m_url << " html = " << html << std::endl; #endif NODE* header = create_node_header(); if( !header ) { return nullptr; } assert( m_vec_header.size() == static_cast< decltype( m_vec_header.size() ) >( m_id_header ) ); m_vec_header.push_back( header ); init_loading(); header->headinfo->block[ BLOCK_MES ] = create_node_block(); const bool digitlink = false; const bool bold = false; const bool ahref = true; parse_html( html.c_str(), html.length(), COLOR_CHAR, digitlink, bold, ahref ); clear(); return header; } // // dat を追加 // // パースして追加したノードのポインタを返す // NODE* NodeTreeBase::append_dat( const std::string& dat ) { if( is_loading() ) return nullptr; if( dat.empty() ) return nullptr; init_loading(); receive_data( dat.c_str(), dat.length() ); receive_finish(); return res_header( m_id_header ); } // // キャッシュからスレッドをロード // void NodeTreeBase::load_cache() { std::string path_cache = CACHE::path_dat( m_url ); if( CACHE::file_exists( path_cache ) == CACHE::EXIST_FILE ){ #ifdef _DEBUG std::cout << "NodeTreeBase::load_cache from " << path_cache << std::endl; #endif std::string str; if( CACHE::load_rawdata( path_cache, str ) ){ const char* data = str.data(); size_t size = 0; m_check_update = false; m_check_write = false; m_loading_newthread = false; set_resume( false ); init_loading(); const size_t str_length = str.length(); while( size < str_length ){ size_t size_tmp = MIN( MAXSISE_OF_LINES - m_buffer_lines.size(), str_length - size ); receive_data( data + size, size_tmp ); size += size_tmp; } receive_finish(); // レジューム時のチェックデータをキャッシュ set_resume_data( data, str_length ); } } } // // レジューム時のチェックデータをキャッシュ // void NodeTreeBase::set_resume_data( const char* data, size_t length ) { // キャッシュされていない場合だけキャッシュする if( ! m_resume_cached ){ m_resume_cached = true; // レジューム時のチェック用に生データの先頭から RESUME_CHKSIZE バイト分をコピーしておく // 詳しくは NodeTreeBase::receive_data() を参照せよ const size_t length_chk = MIN( (RESUME_CHKSIZE - 1), length ); memcpy( m_resume_head, data, length_chk ); m_resume_head[ length_chk ] = '\0'; } } // // ロード実行前に呼ぶ初期化関数 // void NodeTreeBase::init_loading() { clear(); // 一時バッファ作成 if( m_buffer_lines.capacity() < MAXSISE_OF_LINES ) { m_buffer_lines.reserve( MAXSISE_OF_LINES ); } if( m_parsed_text.capacity() < MAXSISE_OF_LINES ) { m_parsed_text.reserve( MAXSISE_OF_LINES ); } } // // レジュームのモードをセットする // void NodeTreeBase::set_resume( const bool resume ) { #ifdef _DEBUG std::cout << "NodeTreeBase::set_resume resume = " << resume << std::endl; #endif if( resume ) m_resume = RESUME_MODE1; else m_resume = RESUME_NO; } // // ロード開始 // // check_update : HEADによる更新チェックのみ // void NodeTreeBase::download_dat( const bool check_update ) { if( is_loading() ) return; m_check_update = check_update; m_check_write = ! m_check_update; m_loading_newthread = ( ! get_res_number() ); #ifdef _DEBUG std::cout << "NodeTreeBase::download_dat : " << m_url << " lng = " << m_lng_dat << std::endl << "modified = " << get_date_modified() << " check_update = " << check_update << " newthread = " << m_loading_newthread << std::endl; #endif // オフライン if( ! SESSION::is_online() ){ set_str_code( "" ); // ディスパッチャ経由でreceive_finish()を呼ぶ finish(); return; } // // オンライン // init_loading(); if( ! m_check_update ){ // 保存ディレクトリ作成(無ければ) if( CACHE::mkdir_boardroot( m_url ) ){ // 保存ファイルオープン std::string path_cache = CACHE::path_dat( m_url ); #ifdef _DEBUG std::cout << "open " << path_cache.c_str() << std::endl; #endif m_fout = fopen( to_locale_cstr( path_cache ), "ab" ); if( m_fout == nullptr ){ MISC::ERRMSG( "fopen failed : " + path_cache ); } } else{ MISC::ERRMSG( "could not create " + DBTREE::url_boardbase( m_url ) ); } } // ロード開始 // ロード完了したら receive_finish() が呼ばれる JDLIB::LOADERDATA data; create_loaderdata( data ); // 更新チェックの時はHEADを使う if( m_check_update ){ data.head = true; data.timeout = CONFIG::get_loader_timeout_checkupdate(); } if( data.url.empty() || ! start_load( data ) ){ m_sig_finished.emit(); clear(); } } // // ローダからデータ受け取り // void NodeTreeBase::receive_data( const char* data, size_t size ) { // BOF防止 size = MIN( MAXSISE_OF_LINES, size ); if( is_loading() && ( get_code() != HTTP_OK && get_code() != HTTP_PARTIAL_CONTENT ) ){ #ifdef _DEBUG std::cout << "NodeTreeBase::receive_data : code = " << get_code() << std::endl; std::cout << data << std::endl; #endif return; } if( m_check_update ) return; // 通常のレジューム処理 if( m_resume == RESUME_MODE1 ){ #ifdef _DEBUG std::cout << "resume mode = " << m_resume << " -> "; #endif // レジュームした時に先頭が '\n' ならレジューム成功 if( data[ 0 ] == '\n' ){ ++data; --size; m_resume = RESUME_NO; } // サーバが range を無視してデータを送ってきた // 続きは add_raw_lines() を参照 else if( get_code() == HTTP_OK ) m_resume = RESUME_MODE2; // あぼーんが起きた? else{ m_broken = true; MISC::ERRMSG( "failed to resume" ); m_resume = RESUME_NO; } #ifdef _DEBUG std::cout << m_resume << std::endl; #endif } if( m_resume == RESUME_FAILED ) return; if( !size ) return; // バッファが '\n' で終わるように調整 const char* pos = data + size; if( *pos == '\n' && pos != data ) --pos; if( *pos == '\0' ) --pos; // '\0' を除く while( *pos != '\n' && pos != data ) --pos; // 前回の残りのデータに新しいデータを付け足して add_raw_lines()にデータを送る size_t size_in = ( int )( pos - data ); if( size_in > 0 ){ size_in ++; // '\n'を加える m_buffer_lines.append( data, size_in ); add_raw_lines( &*m_buffer_lines.begin(), m_buffer_lines.size() ); // 送ったバッファをクリア m_buffer_lines.clear(); } // add_raw_lines() でレジュームに失敗したと判断したら、バッファをクリアする if( m_resume == RESUME_FAILED ){ m_buffer_lines.clear(); return; } // 残りのデータをバッファにコピーしておく m_buffer_lines.assign( data + size_in, size - size_in ); } // // ロード完了 // void NodeTreeBase::receive_finish() { bool is_error = false; if( get_code() != HTTP_INIT && get_code() != HTTP_OK && get_code() != HTTP_PARTIAL_CONTENT && get_code() != HTTP_NOT_MODIFIED && get_code() != HTTP_RANGE_ERR && get_code() != HTTP_OLD ){ is_error = true; std::ostringstream err; err << m_url << std::endl << "load failed. : " << get_str_code(); if( get_code() == HTTP_MOVED_PERM || get_code() == HTTP_REDIRECT ) err << " location = " << location(); MISC::ERRMSG( err.str() ); } if( ! m_check_update ){ if( ! is_error ){ // 特殊スレのdatには、最後の行に'\n'がない場合がある if( !m_buffer_lines.empty() ) { // 正常に読込完了した場合で、バッファが残っていれば add_raw_lines()にデータを送る add_raw_lines( &*m_buffer_lines.begin(), m_buffer_lines.size() ); // バッファをクリア m_buffer_lines.clear(); } } // Requested Range Not Satisfiable if( get_code() == HTTP_RANGE_ERR ){ m_broken = true; MISC::ERRMSG( "Requested Range Not Satisfiable" ); } // データがロードされなかったらキャッシュを消す if( get_res_number() == 0 ){ std::string path = CACHE::path_dat( m_url ); if( CACHE::file_exists( path ) == CACHE::EXIST_FILE ) unlink( to_locale_cstr( path ) ); set_date_modified( std::string() ); } // その他、何かエラーがあったらmodifiedをクリアしておく if( !get_ext_err().empty() ) set_date_modified( std::string() ); // 書き込みチェック終了 if( m_check_write && MESSAGE::get_log_manager()->size() && ( get_code() == HTTP_OK || get_code() == HTTP_PARTIAL_CONTENT || get_code() == HTTP_NOT_MODIFIED ) ) MESSAGE::get_log_manager()->remove_items( m_url ); } #ifdef _DEBUG std::cout << "NodeTreeBase::receive_finish lng = " << m_lng_dat << " raw lng = " << current_length() << " code = " << get_code() << " " << get_str_code() << " modified = " << get_date_modified() << std::endl; #endif // 親 article クラスにシグナルを打ってツリー構造が変わったことを教える m_sig_finished.emit(); clear(); if( ! m_check_update && ( get_code() == HTTP_OK || get_code() == HTTP_PARTIAL_CONTENT ) && ! get_date_modified().empty() ) { CACHE::set_filemtime( CACHE::path_dat( m_url ), get_time_modified() ); // クッキーのセット DBTREE::board_set_list_cookies( m_url, SKELETON::Loadable::cookies() ); } m_check_update = false; m_check_write = false; m_loading_newthread = false; } // // 鯖から生の(複数)行のデータを受け取ってdat形式に変換して add_one_dat_line() に出力 // void NodeTreeBase::add_raw_lines( char* rawlines, size_t size ) { // 時々サーバ側のdatファイルが壊れていてデータ中に \0 が // 入っている時があるので取り除く for( size_t i = 0; i < size; ++i ){ if( rawlines[ i ] == '\0' ){ const size_t beg = i; while( i < size && rawlines[ i ] == '\0' ) ++i; MISC::ERRMSG( std::to_string( i - beg ) + " EOF was inserted in the middle of the raw data" ); memset( rawlines + beg, ' ', i - beg ); } } // 保存前にrawデータを加工 rawlines = process_raw_lines( rawlines ); size_t lng = strlen( rawlines ); if( ! lng ) return; // サーバが range を無視してデータを送ってきたときのレジューム処理 if( m_resume == RESUME_MODE2 ){ #ifdef _DEBUG std::cout << "NodeTreeBase::add_raw_lines : resume\n"; #endif // 先頭からdatを送ってきたかチェック const size_t length_chk = MIN( lng, MIN( (RESUME_CHKSIZE - 1), strlen( m_resume_head ) ) ); if( strncmp( rawlines, m_resume_head, length_chk ) == 0 ){ m_resume = RESUME_MODE3; m_resume_lng = 0; } // 全く違うデータを送ってきた else{ m_broken = true; MISC::ERRMSG( "failed to resume" ); m_resume = RESUME_FAILED; return; } } // レジューム処理でデータをスキップ中 if( m_resume == RESUME_MODE3 ){ #ifdef _DEBUG std::cout << "NodeTreeBase::add_raw_lines : resume skip resume_lng = " << m_resume_lng << " lng = " << lng << " / lng_dat = " << m_lng_dat << std::endl; #endif m_resume_lng += lng; if( m_resume_lng <= m_lng_dat ) return; // 越えた分をカットしてレジューム処理終了 rawlines += ( lng - ( m_resume_lng - m_lng_dat ) ); lng = ( m_resume_lng - m_lng_dat ); m_resume = RESUME_NO; #ifdef _DEBUG std::cout << "resume finished : lng = " << lng << std::endl; #endif } if( ! lng ) return; m_lng_dat += lng; // キャッシュに保存 if( m_fout ){ #ifdef _DEBUG std::cout << "NodeTreeBase::add_raw_lines save " << lng << " bytes\n"; #endif if( fwrite( rawlines, 1, lng, m_fout ) < lng ){ MISC::ERRMSG( "write failed in NodeTreeBase::add_raw_lines\n" ); } // レジューム時のチェックデータをキャッシュ set_resume_data( rawlines, lng ); } // dat形式に変換 int byte; const char* datlines = raw2dat( rawlines, byte ); if( !byte ) return; // '\n' 単位で区切って add_one_dat_line() に渡す int num_before = m_id_header; const char* pos = datlines; while( ( pos = add_one_dat_line( pos ) ) && *pos != '\0' ) ++pos; if( num_before != m_id_header ){ // あぼーん判定 update_abone( num_before +1, m_id_header ); // 発言数更新 update_id_name( num_before +1, m_id_header ); // 参照数更新 update_reference( num_before +1, m_id_header ); // フォント判定 update_fontid( num_before +1, m_id_header ); // articlebase クラスに状態が変わったことを知らせる m_sig_updated.emit(); } } // // dat 解析関数 // // datの1行を解析してノード木を作成する // // 戻り値: バッファの最後の位置 // const char* NodeTreeBase::add_one_dat_line( const char* datline ) { const char* pos = datline; if( *pos == '\0' || *pos == '\n' ) return datline; size_t i; NODE* header = create_node_header(); NODE *node; if( !header ) { return nullptr; } assert( m_vec_header.size() == static_cast< decltype( m_vec_header.size() ) >( m_id_header ) ); m_vec_header.push_back( header ); // レス番号 const std::string tmpstr = std::to_string( header->id_header ); const std::string tmplink = PROTO_RES + tmpstr; node = header->headinfo->block[ BLOCK_NUMBER ] = create_node_block(); node->fontid = FONT_MAIL; create_node_link( tmpstr.c_str(), tmpstr.size(), tmplink.c_str(), tmplink.size(), COLOR_CHAR_LINK_RES, true, FONT_MAIL ); const char* section[ SECTION_NUM ]{}; int section_lng[ SECTION_NUM ]{}; // セクション分けしながら壊れてないかチェック for( i = 0; i < SECTION_NUM; ++i ) { section[i] = pos; while( *pos != '\0' && *pos != '\n' && ! ( pos[0] == '<' && pos[1] == '>' ) ) ++pos; section_lng[i] = pos - section[i]; if( *pos == '\0' || *pos == '\n' ) { ++i; break; } pos += 2; // "<>"の分 } if( i < ( SECTION_NUM - 1 ) ) { // 本文途中まで解析出来てなければ旧dat形式でチェックしてみる pos = datline; for( i = 0; i < SECTION_NUM; ++i ) { section[i] = pos; while( *pos != ',' && *pos != '\0' && *pos != '\n' ) ++pos; section_lng[i] = pos - section[i]; if( *pos == '\0' || *pos == '\n' ) { ++i; break; } pos += 1; // ","の分 } } // 行末まで読み飛ばす while( *pos != '\0' && *pos != '\n' ) ++pos; // 名前 const int color_name = section_lng[1] ? COLOR_CHAR_NAME : COLOR_CHAR_NAME_NOMAIL; parse_name( header, section[0], section_lng[0], color_name ); // メール if( i > 1 ) parse_mail( header, section[1], section_lng[1] ); // 日付とID if( i > 2 ) parse_date_id( header, section[2], section_lng[2] ); // 本文 if( i > 3 ) { header->headinfo->block[ BLOCK_MES ] = create_node_block(); const char* str = section[3]; int lng_msg = section_lng[3]; std::string str_msg; // 文字列置換 const CORE::ReplaceStr_Manager* const mgr = CORE::get_replacestr_manager(); if( mgr->list_get_active( CORE::REPLACETARGET_MESSAGE ) ) { str_msg = mgr->replace( str, lng_msg, CORE::REPLACETARGET_MESSAGE ); str = str_msg.c_str(); lng_msg = str_msg.size(); } constexpr bool digitlink = false; constexpr bool bold = false; constexpr bool ahref = false; parse_html( str, lng_msg, COLOR_CHAR, digitlink, bold, ahref ); } // 壊れている if( i != SECTION_NUM ){ #ifdef _DEBUG std::cout << header->id_header << " is broken section = " << i << std::endl; std::cout << datline << std::endl; #endif m_broken = true; if( i <= 2 ) { node = header->headinfo->block[ BLOCK_MES ] = create_node_block(); node->fontid = FONT_MAIL; } constexpr bool digitlink = false; constexpr bool bold = true; constexpr bool ahref = false; constexpr const char message[] = "

壊れています
"; parse_html( message, std::strlen( message ), COLOR_CHAR, digitlink, bold, ahref, FONT_MAIL ); const char str_broken[] = "ここ"; create_node_link( str_broken, strlen( str_broken ) , PROTO_BROKEN, strlen( PROTO_BROKEN ), COLOR_CHAR_LINK, false ); create_node_text( "をクリックしてスレを再取得して下さい。", COLOR_CHAR ); return pos; } // サブジェクト if( header->id_header == 1 ) { m_subject.assign( section[4], section_lng[4] ); #ifdef _DEBUG std::cout << "subject = " << m_subject << std::endl; #endif } // 自分の書き込みかチェック if( m_check_write && MESSAGE::get_log_manager()->has_items( m_url, m_loading_newthread ) ){ if( m_buffer_write.capacity() < MAXSISE_OF_LINES ) { m_buffer_write.reserve( MAXSISE_OF_LINES ); } // 簡易チェック // 最初の lng_check 文字だけ見る const bool newthread = ( header->id_header == 1 ); const std::size_t lng_check = MIN( section_lng[ 3 ], 32 ); parse_write( section[ 3 ], section_lng[ 3 ], lng_check ); if( MESSAGE::get_log_manager()->check_write( m_url, newthread, m_buffer_write.c_str(), lng_check ) ){ // 全ての文字列で一致しているかチェック parse_write( section[ 3 ], section_lng[ 3 ], 0 ); const bool hit = MESSAGE::get_log_manager()->check_write( m_url, newthread, m_buffer_write.c_str(), 0 ); if( hit ){ m_posts.insert( header->id_header ); } #ifdef _DEBUG std::cout << "check_write id = " << header->id_header << " hit = " << hit << std::endl; #endif } } return pos; } // // 名前 // void NodeTreeBase::parse_name( NODE* header, const char* str, const int lng, const int color_name ) { const bool bold = true; const bool ahref = false; int lng_name = lng; NODE *node; const bool defaultname{ m_default_noname.compare( 0, lng, str, lng ) == 0 }; // 後ろの空白を除く while( lng_name > 0 && str[ lng_name - 1 ] == ' ' ) --lng_name; // 文字列置換 std::string str_name; const CORE::ReplaceStr_Manager* const mgr = CORE::get_replacestr_manager(); if( mgr->list_get_active( CORE::REPLACETARGET_NAME ) ) { str_name = mgr->replace( str, lng_name, CORE::REPLACETARGET_NAME ); str = str_name.c_str(); lng_name = str_name.size(); } node = header->headinfo->block[ BLOCK_NAMELINK ] = create_node_block(); node->fontid = FONT_MAIL; // デフォルトの名前で無いときはリンクにする if( defaultname ) create_node_text( "名前", COLOR_CHAR, false, FONT_MAIL ); else{ const char namestr[] = "名前"; create_node_link( namestr, strlen( namestr ) , PROTO_NAME, strlen( PROTO_NAME ), COLOR_CHAR, false, FONT_MAIL ); } node = header->headinfo->block[ BLOCK_NAME ] = create_node_block(); node->fontid = FONT_MAIL; // デフォルト名無しと同じときはアンカーを作らない if( defaultname ){ constexpr bool digitlink = false; parse_html( str, lng_name, color_name, digitlink, bold, ahref, FONT_MAIL ); } else{ int pos = 0; int i; while( pos < lng_name ) { // トリップなど
の中の文字列は色を変えて数字をリンクにしない for( i = pos; i < lng_name; ++i ) { if( str[ i ] == '<' && str[ i+1 ] == '/' && ( str[ i+2 ] == 'b' || str[ i+2 ] == 'B' ) && str[ i+3 ] == '>' ) break; } // の前までパース if( i != pos ){ // デフォルト名無しと同じときはアンカーを作らない const bool digitlink = ( strncmp( m_default_noname.data(), str + pos, i - pos ) != 0 ); parse_html( str + pos, i - pos, color_name, digitlink, bold, ahref, FONT_MAIL ); } if( i >= lng_name ) break; pos = i + 4; // 4 = strlen( "
" ); // の位置を探す int pos_end = lng_name; for( i = pos; i < lng_name; ++i ) { if( str[ i ] == '<' && ( str[ i+1 ] == 'b' || str[ i+1 ] == 'B' ) && str[ i+2 ] == '>' ){ pos_end = i; break; } } #ifdef _DEBUG char tmp_str[256]; memset( tmp_str, 0, 256); memcpy( tmp_str, str + pos, pos_end - pos ); std::cout << "NodeTreeBase::parseName trip = " << tmp_str << " begin = " << pos << " end = " << pos_end << std::endl; #endif // の中をパース constexpr bool digitlink = false; // 数字が入ってもリンクしない parse_html( str + pos, pos_end - pos, COLOR_CHAR_NAME_B, digitlink, bold, ahref, FONT_MAIL ); pos = pos_end + 3; // 3 = strlen( "" ); } } // plainな名前取得 // 名前あぼーんや名前抽出などで使用する if( defaultname ){ header->headinfo->name = m_heap.heap_alloc( lng_name +2 ); std::memcpy( header->headinfo->name, str, lng_name ); } else{ std::string str_tmp; node = node->next_node; while( node ){ if( node->text ) str_tmp += node->text; node = node->next_node; } header->headinfo->name = m_heap.heap_alloc( str_tmp.length() +2 ); memcpy( header->headinfo->name, str_tmp.c_str(), str_tmp.length() ); } } // // メール // void NodeTreeBase::parse_mail( NODE* header, const char* str, const int lng ) { // sage 以外の時は色を変える int color = COLOR_CHAR; int i = 0; NODE *node; while( i < lng && str[ i ] != 's' ) ++i; if( str[ i ] != 's' || str[ i+1 ] != 'a' || str[ i+2 ] != 'g' || str[ i+3 ] != 'e' ){ color = COLOR_CHAR_AGE; header->headinfo->sage = FALSE; } else header->headinfo->sage = TRUE; node = header->headinfo->block[ BLOCK_MAIL ] = create_node_block(); node->fontid = FONT_MAIL; // 文字列置換 std::string str_mail; int lng_mail = lng; const CORE::ReplaceStr_Manager* const mgr = CORE::get_replacestr_manager(); if( mgr->list_get_active( CORE::REPLACETARGET_MAIL ) ) { str_mail = mgr->replace( str, lng, CORE::REPLACETARGET_MAIL ); str = str_mail.c_str(); lng_mail = str_mail.size(); } if( lng_mail == 0 ) { create_node_text( "[]", color, false, FONT_MAIL ); } else{ const bool digitlink = true; const bool bold = false; const bool ahref = false; create_node_text( "[", color, false, FONT_MAIL ); parse_html( str, lng_mail, color, digitlink, bold, ahref, FONT_MAIL ); create_node_text( "]", color, false, FONT_MAIL ); } } // // 日付とID、及びBE、株、その他 // void NodeTreeBase::parse_date_id( NODE* header, const char* str, const int lng ) { std::string str_date; int lng_date = lng; // 文字列置換 const CORE::ReplaceStr_Manager* const mgr = CORE::get_replacestr_manager(); if( mgr->list_get_active( CORE::REPLACETARGET_DATE ) ) { str_date = mgr->replace( str, lng, CORE::REPLACETARGET_DATE ); str = str_date.c_str(); lng_date = str_date.size(); } int start = 0; int lng_text = 0; int lng_link_tmp; char tmplink[ LNG_LINK ]; NODE *node; int lng_id_tmp; char tmpid[ LNG_ID ]; node = header->headinfo->block[ BLOCK_DATE ] = create_node_block(); node->fontid = FONT_MAIL; for(;;){ // 先頭の空白を飛ばす while( start + lng_text < lng_date && str[ start + lng_text ] == ' ' ) ++lng_text; // 空白ごとにブロック分けしてパースする int start_block = start + lng_text; int lng_block = 0; // ブロックの長さ while( start_block + lng_block < lng_date && str[ start_block + lng_block ] != ' ' ) ++lng_block; if( !lng_block ) break; if( // ID ( ??? の時は除く ) ( str[ start_block ] == 'I' && str[ start_block + 1 ] == 'D' && str[ start_block + 3 ] != '?' ) // HOST || ( str[ start_block + 0 ] == 'H' && str[ start_block + 1 ] == 'O' && str[ start_block + 2 ] == 'S' && str[ start_block + 3 ] == 'T' ) // 発言元 || ( str[ start_block + 0 ] == (char)0xe7 && str[ start_block + 1 ] == (char)0x99 && str[ start_block + 2 ] == (char)0xba // 発 && str[ start_block + 3 ] == (char)0xe4 && str[ start_block + 4 ] == (char)0xbf && str[ start_block + 5 ] == (char)0xa1 // 言 && str[ start_block + 6 ] == (char)0xe5 && str[ start_block + 7 ] == (char)0x85 && str[ start_block + 8 ] == (char)0x83 // 元 ) ){ // フラッシュ if( lng_text ){ if( *( str + start + lng_text - 1 ) == ' ' ) --lng_text; create_node_ntext( str + start, lng_text, COLOR_CHAR, false, FONT_MAIL ); } int offset = 0; if( str[ start_block ] == 'I' ) offset = 3; else if( str[ start_block ] == 'H' ){ offset = 5; // HOST: の場合は途中で空白が入るときがあるので最後までブロックを伸ばす lng_block = lng_date - start_block; } else if( str[ start_block ] == (char)0xe7 ) offset = 10; // id 取得 lng_id_tmp = MIN( lng_block, LNG_ID - 16 ); memcpy( tmpid, str + start_block, lng_id_tmp ); tmpid[ lng_id_tmp ] = '\0'; // リンク文字作成 memcpy( tmplink, PROTO_ID, sizeof( PROTO_ID ) ); memcpy( tmplink + sizeof( PROTO_ID ) - 1, tmpid, lng_id_tmp + 1 ); lng_link_tmp = strlen( tmplink ); // 後ろに●が付いていたら取り除く if( tmplink[ lng_link_tmp - 3 ] == (char)0xe2 && tmplink[ lng_link_tmp - 2 ] == (char)0x97 && tmplink[ lng_link_tmp - 1 ] == (char)0x8f ){ lng_link_tmp -= 3; tmplink[ lng_link_tmp ] = '\0'; } // リンク作成 node = header->headinfo->block[ BLOCK_ID_NAME ] = create_node_block(); node->fontid = FONT_MAIL; create_node_link( tmpid, offset, tmplink, lng_link_tmp, COLOR_CHAR, false, FONT_MAIL ); create_node_ntext( tmpid +offset, lng_id_tmp -offset, COLOR_CHAR, false, FONT_MAIL); // 発言回数ノード作成 node = create_node_idnum(); node->fontid = FONT_MAIL; // 次のブロックへ移動 start = start_block + lng_block; lng_text = 0; } // BE: else if( str[ start_block ] == 'B' && str[ start_block + 1 ] == 'E' ){ const int strlen_of_BE = 3; // = strlen( "BE:" ); // フラッシュ if( lng_text ) create_node_ntext( str + start, lng_text, COLOR_CHAR, false, FONT_MAIL ); // id 取得 int lng_header = 0; while( str[ start_block + lng_header ] != '-' && lng_header < lng_block ) ++lng_header; lng_id_tmp = lng_header - strlen_of_BE; if( str[ start_block + lng_header ] == '-' ) ++lng_header; memcpy( tmpid, str + start_block + strlen_of_BE, lng_id_tmp ); tmpid[ lng_id_tmp ] = '\0'; // リンク文字作成 memcpy( tmplink, PROTO_BE, sizeof( PROTO_BE ) ); memcpy( tmplink + sizeof( PROTO_BE ) -1, tmpid, lng_id_tmp + 1 ); // リンク作成 create_node_link( "?", 1, tmplink, strlen( tmplink ), COLOR_CHAR, false, FONT_MAIL ); create_node_ntext( str + start_block + lng_header, lng_block - lng_header, COLOR_CHAR, false, FONT_MAIL ); // 次のブロックへ移動 start = start_block + lng_block; lng_text = 0; } // 株などの else if( str[ start_block ] == '<' && ( str[ start_block + 1 ] == 'a' || str[ start_block + 1 ] == 'A' ) && str[ start_block + 2 ] == ' ' ){ // フラッシュ if( lng_text ) create_node_ntext( str + start, lng_text, COLOR_CHAR, false, FONT_MAIL ); // までブロックの長さを伸ばす while( start_block + lng_block < lng_date && ! ( ( str[ start_block + lng_block -1 ] == 'a' || str[ start_block + lng_block -1 ] == 'A' ) && str[ start_block + lng_block ] == '>' ) ) ++lng_block; ++lng_block; const bool digitlink = false; const bool bold = false; const bool ahref = true; parse_html( str + start_block, lng_block, COLOR_CHAR, digitlink, bold, ahref, FONT_MAIL ); // 次のブロックへ移動 start = start_block + lng_block; lng_text = 0; } // テキスト(日付含む) else lng_text += lng_block; } // フラッシュ if( lng_text ) create_node_ntext( str + start, lng_text, COLOR_CHAR, false, FONT_MAIL ); } // // HTMLパーサ // // digitlink : true の時は先頭に数字が現れたらアンカーにする( parse_name() などで使う ) // false なら数字の前に >> がついてるときだけアンカーにする // // bold : ボールド表示 // // ahref : からリンクノードを作成する // (例) parse_html( "hoge", 27, COLOR_CHAR, false, false ); // // (パッチ) // // 行頭の空白は全て除くパッチ // Thanks to 「パッチ投稿スレ」の28氏 // http://jd4linux.sourceforge.jp/cgi-bin/bbs/test/read.cgi/support/1151836078/28 // void NodeTreeBase::parse_html( const char* str, const int lng, const int color_text, bool digitlink, const bool bold, const bool ahref, char fontid ) { const char* pos = str; const char* pos_end = str + lng; NODE *node; m_parsed_text.clear(); if( *pos == ' ' ){ pos++; // 一文字だけなら取り除く // 連続半角空白 if( *pos == ' ' ){ while( *pos == ' ' ) { m_parsed_text.push_back( *(pos++) ); } create_node_multispace( m_parsed_text.c_str(), m_parsed_text.size(), fontid ); m_parsed_text.clear(); } } for( ; pos < pos_end; ++pos, digitlink = false ){ /////////////////////// // HTMLタグ if( *pos == '<' ){ bool br = false; // 改行
if( ( *( pos + 1 ) == 'b' || *( pos + 1 ) == 'B' ) && ( *( pos + 2 ) == 'r' || *( pos + 2 ) == 'R' ) ) br = true; // ahref == true かつ else if( ahref && ( *( pos + 1 ) == 'a' || *( pos + 1 ) == 'A' ) && *( pos + 2 ) == ' ' ){ // フラッシュ create_node_ntext( m_parsed_text.c_str(), m_parsed_text.size(), color_text, bold, fontid ); m_parsed_text.clear(); while( pos < pos_end && *pos != '=' ) ++pos; ++pos; if( *pos == ' ' ) ++pos; if( pos >= pos_end ) continue; bool dbq = false; if( *pos == '"' ){ dbq = true; ++pos; } const char* pos_link_start = pos; int lng_link = 0; while( pos < pos_end && ( ( ( dbq && *pos != '"' ) || ( !dbq && *pos != ' ' ) ) && *pos != '>' ) ){ ++pos; ++lng_link; } if( pos >= pos_end ) continue; while( pos < pos_end && *pos != '>' ) ++pos; if( pos >= pos_end ) continue; ++pos; const char* pos_str_start = pos; int lng_str = 0; bool exec_decode = false; while( pos < pos_end && *pos != '<' ){ if( *pos == '&' ) exec_decode = true; ++pos; ++lng_str; } if( pos >= pos_end ) continue; while( pos < pos_end && *pos != '>' ) ++pos; if( pos >= pos_end ) continue; ++pos; if( lng_link && lng_str ){ // 特殊文字デコード if( exec_decode ){ for( int pos_tmp = 0; pos_tmp < lng_str; ++ pos_tmp ){ int n_in = 0; int n_out = 0; char out_char[kMaxBytesOfUTF8Char]{}; // FIXME: std::stringを受け付けるdecode_char()を作る const int ret_decode = DBTREE::decode_char( pos_str_start + pos_tmp, n_in, out_char, n_out, false ); if( ret_decode != NODE_NONE ){ m_parsed_text.append( out_char, n_out ); pos_tmp += n_in; pos_tmp--; } else { m_parsed_text.push_back( *( pos_str_start + pos_tmp ) ); } } #ifdef _DEBUG std::cout << m_parsed_text << std::endl; #endif pos_str_start = m_parsed_text.c_str(); lng_str = m_parsed_text.size(); } create_node_link( pos_str_start, lng_str , pos_link_start, lng_link, COLOR_CHAR_LINK, false, fontid ); m_parsed_text.clear(); } } // else if( *( pos + 1 ) == '/' && ( *( pos + 2 ) == 'a' || *( pos + 2 ) == 'A' ) && *( pos + 3 ) == '>' ) pos += 4; // 改行にするタグ else if( //

( ( *( pos + 1 ) == 'p' || *( pos + 1 ) == 'P' ) && *( pos + 2 ) == '>' ) //

|| ( ( *( pos + 2 ) == 'p' || *( pos + 2 ) == 'P' ) && *( pos + 3 ) == '>' && *( pos + 1 ) == '/' ) //
|| ( ( *( pos + 1 ) == 'd' || *( pos + 1 ) == 'D' ) && ( *( pos + 2 ) == 'd' || *( pos + 2 ) == 'D' ) ) // || ( ( *( pos + 2 ) == 'd' || *( pos + 2 ) == 'D' ) && ( *( pos + 3 ) == 'l' || *( pos + 3 ) == 'L' ) && *( pos + 1 ) == '/' ) // || ( ( *( pos + 2 ) == 'u' || *( pos + 2 ) == 'U' ) && ( *( pos + 3 ) == 'l' || *( pos + 3 ) == 'L' ) && *( pos + 1 ) == '/' ) // || ( ( *( pos + 2 ) == 'l' || *( pos + 2 ) == 'L' ) && ( *( pos + 3 ) == 'i' || *( pos + 3 ) == 'I' ) && *( pos + 1 ) == '/' ) // || ( ( *( pos + 2 ) == 't' || *( pos + 2 ) == 'T' ) && ( *( pos + 3 ) == 'i' || *( pos + 3 ) == 'I' ) && ( *( pos + 4 ) == 't' || *( pos + 4 ) == 'T' ) && ( *( pos + 5 ) == 'l' || *( pos + 5 ) == 'L' ) && ( *( pos + 6 ) == 'e' || *( pos + 6 ) == 'E' ) && *( pos + 1 ) == '/' ) ) br = true; //
  • は・にする else if( ( *( pos + 1 ) == 'l' || *( pos + 2 ) == 'L' ) && ( *( pos + 2 ) == 'i' || *( pos + 3 ) == 'I' ) ){ pos += 4; m_parsed_text.append( u8"\u30FB" ); // KATAKANA MIDDLE DOT } // 水平線
    else if( ( *( pos + 1 ) == 'h' || *( pos + 1 ) == 'H' ) && ( *( pos + 2 ) == 'r' || *( pos + 2 ) == 'R' ) ){ // フラッシュ create_node_ntext( m_parsed_text.c_str(), m_parsed_text.size(), color_text, bold, fontid ); m_parsed_text.clear(); // 水平線ノード作成 node = create_node_hr(); if (fontid != FONT_MAIN) node->fontid = fontid; pos += 4; } // その他のタグは無視。タグを取り除いて中身だけを見る else { // フラッシュ create_node_ntext( m_parsed_text.c_str(), m_parsed_text.size(), color_text, bold, fontid ); m_parsed_text.clear(); while( pos < pos_end && *pos != '>' ) ++pos; ++pos; } // 改行実行 if( br ){ // フラッシュ if( (pos > str) && *( pos - 1 ) == ' ' && ! m_parsed_text.empty() ) { m_parsed_text.pop_back(); // 改行前の空白を取り除く } create_node_ntext( m_parsed_text.c_str(), m_parsed_text.size(), color_text, bold, fontid ); m_parsed_text.clear(); // 改行ノード作成 node = create_node_br(); if (fontid != FONT_MAIN) node->fontid = fontid; while( *pos != '>' ) { ++pos; } ++pos; if( *pos == ' ' ){ pos++; // 一文字だけなら取り除く // 連続半角空白 if( *pos == ' ' ){ while( *pos == ' ' ) { m_parsed_text.push_back( *(pos++) ); } create_node_multispace( m_parsed_text.c_str(), m_parsed_text.size(), fontid ); m_parsed_text.clear(); } } } // forのところで++されるので--しておく --pos; continue; } /////////////////////// // アンカーのチェック int n_in = 0; char tmpstr[ LNG_LINK +16 ]; // 画面に表示する文字列 char tmplink[ LNG_LINK +16 ]; // 編集したリンク文字列 int lng_str = 0, lng_link = strlen( PROTO_ANCHORE ); ANCINFO ancinfo[ MAX_ANCINFO ]; int lng_anc = 0; int mode = 0; if( digitlink ) mode = 2; if( check_anchor( mode , pos, n_in, tmpstr + lng_str, tmplink + lng_link, LNG_LINK - lng_link, ancinfo + lng_anc ) ){ // フラッシュしてからアンカーノードをつくる create_node_ntext( m_parsed_text.c_str(), m_parsed_text.size(), color_text, bold, fontid ); m_parsed_text.clear(); memcpy( tmplink, PROTO_ANCHORE, strlen( PROTO_ANCHORE ) ); lng_str += strlen( tmpstr ) - lng_str; lng_link += strlen( tmplink ) - lng_link; ++lng_anc; pos += n_in; // , や = や +が続くとき // MAX_ANCINFOを超えた部分はリンクに含めない mode = 1; while( lng_anc < static_cast( MAX_ANCINFO ) && check_anchor( mode, pos, n_in, tmpstr + lng_str, tmplink + lng_link , LNG_LINK - lng_link, ancinfo + lng_anc ) ){ lng_str += strlen( tmpstr ) - lng_str; lng_link += strlen( tmplink ) - lng_link; ++lng_anc; pos += n_in; } create_node_anc( tmpstr, lng_str, tmplink, lng_link, COLOR_CHAR_LINK, bold, ancinfo, lng_anc, fontid ); // forのところで++されるので--しておく --pos; continue; } // digitlink = true の時は数字が長すぎるときは飛ばす( 例えば 名前: 12345678 みたいなとき ) if( digitlink ){ --n_in; while( n_in-- > 0 ) { m_parsed_text.push_back( *(pos++) ); } } /////////////////////// // リンク(http)のチェック char tmpreplace[ LNG_LINK +16 ]; // Urlreplaceで変換した後のリンク文字列 int lng_replace = 0; int linktype = check_link( pos, (int)( pos_end - pos ), n_in, tmplink, LNG_LINK ); if( linktype != MISC::SCHEME_NONE ){ // リンクノードで実際にアクセスするURLの変換 while( remove_imenu( tmplink ) ); // ime.nuなどの除去 lng_link = convert_amp( tmplink, strlen( tmplink ) ); // & → & // Urlreplaceによる正規表現変換 std::string tmpurl( tmplink, lng_link ); if( CORE::get_urlreplace_manager()->exec( tmpurl ) == false ){ // 変換されてない lng_replace = lng_link; memcpy( tmpreplace, tmplink, lng_replace +1 ); } else { if( tmpurl.size() > LNG_LINK ){ MISC::ERRMSG( std::string( "too long replaced url : " ) + tmplink ); // 変換後のURLが長すぎるので、元のURLのままにする lng_replace = lng_link; memcpy( tmpreplace, tmplink, lng_replace +1 ); } else { // 正常に変換された lng_replace = tmpurl.size(); memcpy( tmpreplace, tmpurl.c_str(), lng_replace +1 ); // 正規表現変換の結果、スキームだけの簡易チェックをする int delim_pos = 0; if( MISC::SCHEME_NONE == MISC::is_url_scheme( tmpreplace, &delim_pos ) ){ // スキーム http:// が消えていた linktype = MISC::SCHEME_NONE; } } } } // リンクノードか再チェック if( linktype != MISC::SCHEME_NONE ){ // フラッシュしてからリンクノードつくる create_node_ntext( m_parsed_text.c_str(), m_parsed_text.size(), color_text, bold, fontid ); m_parsed_text.clear(); // リンクノードの表示テキスト memcpy( tmpstr, pos, n_in ); tmpstr[ n_in ] = '\0'; lng_str = convert_amp( tmpstr, n_in ); // & → & // ssspアイコン if( linktype == MISC::SCHEME_SSSP ){ node = create_node_sssp( tmpreplace, lng_replace ); if (fontid != FONT_MAIN) node->fontid = fontid; } else { // Urlreplaceによる画像コントロールを取得する int imgctrl = CORE::get_urlreplace_manager()->get_imgctrl( std::string( tmpreplace, lng_replace ) ); // youtubeなどのサムネイル画像リンク if( imgctrl & CORE::IMGCTRL_THUMBNAIL ){ create_node_thumbnail( tmpstr, lng_str, tmplink , lng_link, tmpreplace, lng_replace, COLOR_CHAR_LINK, bold, fontid ); } // 画像リンク else if( DBIMG::get_type_ext( tmpreplace, lng_replace ) != DBIMG::T_UNKNOWN ){ node = create_node_img( tmpstr, lng_str, tmpreplace , lng_replace, COLOR_IMG_NOCACHE, bold ); if ( fontid != FONT_MAIN ) node->fontid = fontid; } // 一般リンク else create_node_link( tmpstr, lng_str, tmpreplace , lng_replace, COLOR_CHAR_LINK, bold, fontid ); } pos += n_in; // forのところで++されるので--しておく --pos; continue; } /////////////////////// // 特殊文字デコード if( *pos == '&' ){ int n_out = 0; char out_char[kMaxBytesOfUTF8Char]{}; const int ret_decode = DBTREE::decode_char( pos, n_in, out_char, n_out, false ); if( ret_decode != NODE_NONE ){ // 文字以外の空白ノードならフラッシュして空白ノード追加 if( ret_decode != NODE_TEXT ){ create_node_ntext( m_parsed_text.c_str(), m_parsed_text.size(), color_text, bold, fontid ); m_parsed_text.clear(); node = create_node_space( ret_decode ); if ( fontid != FONT_MAIN ) node->fontid = fontid; } else { m_parsed_text.append( out_char, n_out ); } pos += n_in; // forのところで++されるので--しておく --pos; continue; } } /////////////////////// // 水平タブ(0x09) if( *pos == 0x09 ){ // フラッシュしてからタブノードをつくる create_node_ntext( m_parsed_text.c_str(), m_parsed_text.size(), color_text, bold, fontid ); m_parsed_text.clear(); node = create_node_htab(); if ( fontid != FONT_MAIN ) node->fontid = fontid; continue; } /////////////////////// // LF(0x0A), CR(0x0D) if( *pos == 0x0A || *pos == 0x0D ){ // 無視する continue; } /////////////////////// // 連続半角空白 if( *pos == ' ' && *( pos + 1 ) == ' ' ){ m_parsed_text.push_back( *(pos++) ); // フラッシュしてから連続半角ノードを作る create_node_ntext( m_parsed_text.c_str(), m_parsed_text.size(), color_text, bold, fontid ); m_parsed_text.clear(); while( *pos == ' ' ) { m_parsed_text.push_back( *(pos++) ); } create_node_multispace( m_parsed_text.c_str(), m_parsed_text.size(), fontid ); m_parsed_text.clear(); // forのところで++されるので--しておく --pos; continue; } m_parsed_text.push_back( *pos ); } create_node_ntext( m_parsed_text.c_str(), m_parsed_text.size(), color_text, bold, fontid ); m_parsed_text.clear(); } // // 書き込みログ比較用文字列作成 // // m_buffer_write に作成した文字列をセットする // // max_lng_write > 0 のときは m_buffer_write の文字数が max_lng_write 以上になったら停止 // void NodeTreeBase::parse_write( const char* str, const int lng, const std::size_t max_lng_write ) { #ifdef _DEBUG std::cout << "NodeTreeBase::parse_write lng = " << lng << " max = " << max_lng_write << std::endl; #endif bool head = true; const char* pos = str; const char* pos_end = str + lng; int offset_num; int lng_num; m_buffer_write.clear(); // 行頭の空白は全て除く while( *pos == ' ' ) ++pos; for( ; pos < pos_end && ( max_lng_write == 0 || m_buffer_write.size() < max_lng_write ) ; ++pos ){ // タグ if( *pos == '<' ){ //
    if( ( *( pos + 1 ) == 'b' || *( pos + 1 ) == 'B' ) && ( *( pos + 2 ) == 'r' || *( pos + 2 ) == 'R' ) ){ m_buffer_write.push_back( '\n' ); pos += 3; } // その他のタグは無視 else while( pos < pos_end && *pos != '>' ) ++pos; continue; } // gt else if( *pos == '&' && ( *( pos + 1 ) == 'g' && *( pos + 2 ) == 't' && *( pos + 3 ) == ';' ) ){ m_buffer_write.push_back( '>' ); pos += 3; continue; } // lt else if( *pos == '&' && ( *( pos + 1 ) == 'l' && *( pos + 2 ) == 't' && *( pos + 3 ) == ';' ) ){ m_buffer_write.push_back( '<' ); pos += 3; continue; } // amp else if( *pos == '&' && ( *( pos + 1 ) == 'a' && *( pos + 2 ) == 'm' && *( pos + 3 ) == 'p' && *( pos + 4 ) == ';' ) ){ m_buffer_write.push_back( '&' ); pos += 4; continue; } // quot else if( *pos == '&' && ( *( pos + 1 ) == 'q' && *( pos + 2 ) == 'u' && *( pos + 3 ) == 'o' && *( pos + 4 ) == 't' && *( pos + 5 ) == ';' ) ){ m_buffer_write.push_back( '"' ); pos += 5; continue; } // 先頭のsssp else if( head && *pos == 's' && ( *( pos + 1 ) == 's' && *( pos + 2 ) == 's' && *( pos + 3 ) == 'p' ) ){ // 次のタグ(改行)が来るまで進める while( pos < pos_end && *pos != '>' ) ++pos; continue; } // 水平タブ else if( *pos == '\t' ){ // 空白に置き換える m_buffer_write.push_back( ' ' ); continue; } // 数字参照 else if( *pos == '&' && *( pos + 1 ) == '#' && ( lng_num = MISC::spchar_number_ln( pos, offset_num ) ) != -1 ){ const int num = MISC::decode_spchar_number( pos, offset_num, lng_num ); char utf8[kMaxBytesOfUTF8Char]{}; const int n_out = MISC::ucs2toutf8( num, utf8 ); m_buffer_write.append( utf8, n_out ); pos += offset_num + lng_num; continue; } head = false; m_buffer_write.push_back( *pos ); } } // // アンカーが現れたかチェックして文字列を取得する関数 // // 入力 // mode : 0 なら >> が先頭に無ければアンカーにしない、1 なら,か+か=があればアンカーにする、2 なら数字が先頭に来たらアンカーにする // str_in : 入力文字列の先頭アドレス // lng_link : str_linkのバッファサイズ // // 出力 // n_in : str_in から何バイト読み取ったか // str_out : (画面に表示される)文字列 // str_link : リンクの文字列 // ancinfo : ancinfo->anc_from 番から ancinfo->anc_to 番までのアンカーが現れた // // 戻り値 : アンカーが現れれば true // bool NodeTreeBase::check_anchor( const int mode, const char* str_in, int& n_in, char* str_out, char* str_link, int lng_link, ANCINFO* ancinfo ) const { char tmp_out[ 64 ]; int lng_out = 0; const char* pos = str_in; n_in = 0; // ">" を最大2回チェック if( mode == 0 ){ for( int i = 0; i < 2; ++i ){ // '>' if( *pos == '&' && *( pos + 1 ) == 'g' && *( pos + 2 ) == 't' ){ tmp_out[ lng_out++ ] = '>'; pos += 4; } // utf-8で">" else if( ( unsigned char )( *pos ) == 0xef && ( unsigned char ) ( *( pos + 1 ) ) == 0xbc && ( unsigned char ) ( *( pos + 2 ) ) == 0x9e ){ tmp_out[ lng_out++ ] = static_cast< char >( 0xef ); tmp_out[ lng_out++ ] = static_cast< char >( 0xbc ); tmp_out[ lng_out++ ] = static_cast< char >( 0x9e ); pos += 3; } else if( i == 0 ) return false; } } // カンマかイコールかプラスをチェック else if( mode == 1 ){ if( *( pos ) == '=' || *( pos ) == ',' || *( pos ) == '+' ){ tmp_out[ lng_out++ ] = *( pos ); str_link[ 0 ] = *( pos ); ++str_link; --lng_link; ++pos; } // utf-8で"、" else if( ( unsigned char )( *pos ) == 0xe3 && ( unsigned char ) ( *( pos + 1 ) ) == 0x80 && ( unsigned char ) ( *( pos + 2 ) ) == 0x81 ){ tmp_out[ lng_out++ ] = static_cast< char >( 0xe3 ); tmp_out[ lng_out++ ] = static_cast< char >( 0x80 ); tmp_out[ lng_out++ ] = static_cast< char >( 0x81 ); str_link[ 0 ] = ','; ++str_link; --lng_link; pos += 3; } else return false; } // 数字かチェック size_t n, dig; int num = MISC::str_to_uint( pos, dig, n ); if( dig == 0 || dig > MAX_RES_DIGIT || num == 0 ){ // モード2で数字が長すぎるときは飛ばす if( mode == 2 && dig > MAX_RES_DIGIT ) n_in = ( int )( pos - str_in ) + n; return false; } // アンカーが現れたのでとりあえず作成する // 画面に表示する文字 memcpy( str_out, tmp_out, lng_out ); memcpy( str_out + lng_out, pos, n ); str_out[ lng_out + n ] = '\0'; pos += n; lng_out += n; // をキャンセル if( *( pos ) == '<' && *( pos + 1 ) == '/' && ( *( pos + 2 ) == 'a' || *( pos + 2 ) == 'A' ) && *( pos + 3 ) == '>' ){ pos += 4; // もう一度数字チェック // >>11 を書き込むと >>11 となるため size_t n2, dig2; const int num2 = MISC::str_to_uint( pos, dig2, n2 ); if( dig2 > 0 && dig2 <= MAX_RES_DIGIT ){ for( size_t i = 0; i < dig2; ++i ) num *= 10; num += num2; memcpy( str_out + lng_out, pos, n2 ); str_out[ lng_out + n2 ] = '\0'; pos += n2; lng_out += n2; } } ancinfo->anc_from = ancinfo->anc_to = num; // アンカー文字 snprintf( str_link, lng_link, "%d", ancinfo->anc_from ); // "-" でつながってる場合同じことをもう一回 int offset = 0; if( *( pos ) == '-' ) offset = 1; // utf-8で"−" else if( ( unsigned char )( * pos ) == 0xef && ( unsigned char ) ( *( pos + 1 ) ) == 0xbc && ( unsigned char ) ( *( pos + 2 ) ) == 0x8d ) offset = 3; // 半角"-" else if( ( unsigned char )( * pos ) == 0xef && ( unsigned char ) ( *( pos + 1 ) ) == 0xbd && ( unsigned char ) ( *( pos + 2 ) ) == 0xb0 ) offset = 3; if( offset ){ ancinfo->anc_to = MAX( ancinfo->anc_from, MISC::str_to_uint( pos + offset, dig, n ) ); if( dig && dig <= MAX_RES_DIGIT && ancinfo->anc_to ){ // 画面に表示する文字 memcpy( str_out + lng_out, pos, offset + n ); str_out[ lng_out + offset + n ] = '\0'; // アンカー文字をもう一度作成 snprintf( str_link, lng_link, "%d-%d", ancinfo->anc_from, ancinfo->anc_to ); pos += offset + n; } } //">>数字-数字"のパターンの時にをのぞく if( *( pos ) == '<' && *( pos + 1 ) == '/' && ( *( pos + 2 ) == 'a' || *( pos + 2 ) == 'A' ) && *( pos + 3 ) == '>' ) pos += 4; n_in = ( int )( pos - str_in ); return true; } // // リンクが現れたかチェックして文字列を取得する関数 // // 入力 // str_in : 入力文字列の先頭アドレス // lng_str : str_inのバッファサイズ // lng_link : str_linkのバッファサイズ // linktype : is_url_scheme()のリタンコード // delim_pos : is_url_scheme()で得たスキーム文字列の長さ // // 出力 // n_in : str_in から何バイト読み取ったか // str_link : リンクの文字列 // // 戻り値 : リンクのタイプ(例えばSCHEME_HTTPなど) // // 注意 : MISC::is_url_scheme() と MISC::is_url_char() の仕様に合わせる事 // int NodeTreeBase::check_link_impl( const char* str_in, const int lng_in, int& n_in, char* str_link, const int lng_link, const int linktype, const int delim_pos ) const { // CONFIG::get_loose_url() == true の時はRFCで規定されていない文字も含める const bool loose_url = CONFIG::get_loose_url(); // リンクの長さを取得 n_in = delim_pos; int n_in_tmp, n_out_tmp; char buf[16]; while( n_in < lng_in ){ // URLとして扱う文字かどうか if ( MISC::is_url_char( str_in + n_in, loose_url ) == false ) break; // HTML特殊文字( &〜; ) if ( *( str_in + n_in ) == '&' && DBTREE::decode_char( str_in + n_in, n_in_tmp, buf, n_out_tmp, false ) != DBTREE::NODE_NONE ){ // デコード結果が"&(&)"でないもの if( n_out_tmp != 1 || buf[0] != '&' ) break; } n_in++; } // URLとして短かすぎる場合は除外する( 最短ドメイン名の例 "1.cc" ) if( n_in - delim_pos < 4 ) return MISC::SCHEME_NONE; // URL出力バッファより長いときも除外する( 一般に256バイトを超えるとキャッシュをファイル名として扱えなくなる ) if( lng_link <= n_in ) return MISC::SCHEME_NONE; char *pos = str_link; // URLスキームを修正 int str_pos = 0; int n_out = n_in; // 実際に書き込む長さ switch( linktype ){ // ttp -> http case MISC::SCHEME_TTP: n_out += 1; if( n_out >= lng_link ) return MISC::SCHEME_NONE; *pos = 'h'; pos++; break; // tp -> http case MISC::SCHEME_TP: n_out += 2; if( n_out >= lng_link ) return MISC::SCHEME_NONE; *pos = 'h'; *(++pos) = 't'; pos++; break; // sssp -> http case MISC::SCHEME_SSSP: *pos = 'h'; *(++pos) = 't'; *(++pos) = 't'; pos++; str_pos = 3; break; } // srr_inの文字列をstr_linkにコピー int i = str_pos; for( ; i < n_in; i++, pos++ ){ *pos = str_in[ i ]; // loose_urlで含める"^"と"|"をエンコードする // "[]"はダウンローダに渡す用途のためにエンコードしないでおく if( loose_url == true ){ if( str_in[ i ] == '^' ){ // '^' → "%5E"(+2Byte) n_out += 2; if( n_out >= lng_link ) return MISC::SCHEME_NONE; *pos = '%'; *(++pos) = '5'; *(++pos) = 'E'; } else if( str_in[ i ] == '|' ){ // '|' → "%7C"(+2Byte) n_out += 2; if( n_out >= lng_link ) return MISC::SCHEME_NONE; *pos = '%'; *(++pos) = '7'; *(++pos) = 'C'; } } } // str_linkの終端 *pos = '\0'; #ifdef _DEBUG std::cout << str_link << std::endl << "len = " << strlen( str_link ) << " lng_link = " << lng_link << " n_in = " << n_in << std::endl; #endif return linktype; } // あぼーんしているか bool NodeTreeBase::get_abone( int number ) const { const NODE* head = res_header( number ); if( ! head ) return false; if( ! head->headinfo ) return false; return head->headinfo->abone; } // あぼーんのクリア void NodeTreeBase::clear_abone() { for( int i = 1; i <= m_id_header; ++i ){ NODE* tmphead = m_vec_header[ i ]; if( tmphead && tmphead->headinfo ) tmphead->headinfo->abone = false; } } // あぼーん情報を親クラスのarticlebaseからコピーする void NodeTreeBase::copy_abone_info( const std::list< std::string >& list_abone_id, const std::list< std::string >& list_abone_name, const std::list< std::string >& list_abone_word, const std::list< std::string >& list_abone_regex, const std::unordered_set< int >& abone_reses, const bool abone_transparent, const bool abone_chain, const bool abone_age, const bool abone_default_name, const bool abone_noid, const bool abone_board, const bool abone_global ) { m_list_abone_id = list_abone_id; m_list_abone_name = list_abone_name; m_list_abone_id_board = DBTREE::get_abone_list_id_board( m_url ); m_list_abone_name_board = DBTREE::get_abone_list_name_board( m_url ); std::list list_str; const auto make_pattern = []( const std::string& query ) { const bool icase = CONFIG::get_abone_icase(); constexpr bool newline = true; const bool wchar = CONFIG::get_abone_wchar(); return JDLIB::RegexPattern( query, icase, newline, false, wchar ); }; // 設定ファイルには改行は"\\n"で保存されているので "\n" に変換する m_list_abone_word = MISC::replace_str_list( list_abone_word, "\\n", "\n" ); list_str = MISC::replace_str_list( list_abone_regex, "\\n", "\n" ); m_list_abone_regex.clear(); std::transform( list_str.cbegin(), list_str.cend(), std::back_inserter( m_list_abone_regex ), make_pattern ); m_list_abone_word_board = DBTREE::get_abone_list_word_board( m_url ); m_list_abone_word_board = MISC::replace_str_list( m_list_abone_word_board, "\\n", "\n" ); list_str = DBTREE::get_abone_list_regex_board( m_url ); list_str = MISC::replace_str_list( list_str, "\\n", "\n" ); m_list_abone_regex_board.clear(); std::transform( list_str.cbegin(), list_str.cend(), std::back_inserter( m_list_abone_regex_board ), make_pattern ); m_list_abone_word_global = MISC::replace_str_list( CONFIG::get_list_abone_word(), "\\n", "\n" ); list_str = MISC::replace_str_list( CONFIG::get_list_abone_regex(), "\\n", "\n" ); m_list_abone_regex_global.clear(); std::transform( list_str.cbegin(), list_str.cend(), std::back_inserter( m_list_abone_regex_global ), make_pattern ); m_abone_reses = abone_reses; if( CONFIG::get_abone_transparent() ) m_abone_transparent = true; else m_abone_transparent = abone_transparent; if( CONFIG::get_abone_chain() ) m_abone_chain = true; else m_abone_chain = abone_chain; m_abone_age = abone_age; m_abone_default_name = abone_default_name; m_abone_noid = abone_noid; m_abone_board = abone_board; m_abone_global = abone_global; } // // 全レスのあぼーん状態の更新 // // 発言数や参照数も更新する // void NodeTreeBase::update_abone_all() { // あぼーん更新 clear_abone(); update_abone( 1, m_id_header ); // 発言数更新 clear_id_name(); update_id_name( 1, m_id_header ); // 参照状態更新 clear_reference(); update_reference( 1, m_id_header ); // フォント判定更新 update_fontid( 1, m_id_header ); } // // from_number番から to_number 番までのレスのあぼーん状態を更新 // void NodeTreeBase::update_abone( const int from_number, const int to_number ) { if( empty() ) return; if( to_number < from_number ) return; for( int i = from_number ; i <= to_number; ++i ){ if( check_abone_res( i ) ) continue; if( check_abone_id( i ) ) continue; if( check_abone_name( i ) ) continue; if( check_abone_mail( i ) ) continue; if( check_abone_word( i ) ) continue; if( check_abone_chain( i ) ) continue; } } // // number番のあぼーん判定(レスあぼーん) // // あぼーんの時はtrueを返す // bool NodeTreeBase::check_abone_res( const int number ) { if( m_abone_reses.find( number ) == m_abone_reses.end() ) return false; NODE* head = res_header( number ); if( ! head ) return false; if( ! head->headinfo ) return false; head->headinfo->abone = true; return true; } // // number番のあぼーん判定( id / board id ) // // あぼーんの時はtrueを返す // bool NodeTreeBase::check_abone_id( const int number ) { const bool check_id = ! m_list_abone_id.empty(); const bool check_id_board = ! m_list_abone_id_board.empty(); if( !m_abone_noid && !check_id && !check_id_board ) return false; NODE* head = res_header( number ); if( ! head ) return false; if( ! head->headinfo ) return false; if( head->headinfo->abone ) return true; if( ! head->headinfo->block[ BLOCK_ID_NAME ] ) { // ID無し if( m_abone_noid ) { head->headinfo->abone = true; return true; } return false; } const int ln_protoid = strlen( PROTO_ID ); const char* const link_id = head->headinfo->block[ BLOCK_ID_NAME ]->next_node->linkinfo->link + ln_protoid; const auto equal_id = [link_id]( const std::string& id ) { return id == link_id; }; // ローカルID if( check_id ){ if( std::any_of( m_list_abone_id.cbegin(), m_list_abone_id.cend(), equal_id ) ) { head->headinfo->abone = true; return true; } } // 板レベル ID if( check_id_board ){ if( std::any_of( m_list_abone_id_board.cbegin(), m_list_abone_id_board.cend(), equal_id ) ) { head->headinfo->abone = true; return true; } } return false; } // // number番のあぼーん判定(name / board name / global name ) // // あぼーんの時はtrueを返す // bool NodeTreeBase::check_abone_name( const int number ) { const bool check_name = ! m_list_abone_name.empty(); const bool check_name_board = ! m_list_abone_name_board.empty(); const bool check_name_global = ! CONFIG::get_list_abone_name().empty(); if( !m_abone_default_name && !check_name && !check_name_board && !check_name_global ) return false; NODE* head = res_header( number ); if( ! head ) return false; if( ! head->headinfo ) return false; if( head->headinfo->abone ) return true; if( ! head->headinfo->name ) return false; // デフォルト名無し if( m_abone_default_name && head->headinfo->block[ BLOCK_NAMELINK ] ){ const NODE* idnode = head->headinfo->block[ BLOCK_NAMELINK ]->next_node; // デフォルトのときはリンクがない if( idnode && ! idnode->linkinfo ){ head->headinfo->abone = true; return true; } } const std::string name_str( head->headinfo->name ); // ローカル name if( check_name ){ for( const std::string& name : m_list_abone_name ) { if( name_str.find( name ) != std::string::npos ) { head->headinfo->abone = true; return true; } } } // 板レベル name if( check_name_board ){ for( const std::string& name : m_list_abone_name_board ) { if( name_str.find( name ) != std::string::npos ) { head->headinfo->abone = true; return true; } } } // 全体 name if( check_name_global ){ for( const std::string& name : CONFIG::get_list_abone_name() ) { if( name_str.find( name ) != std::string::npos ) { head->headinfo->abone = true; return true; } } } return false; } // // number番のあぼーん判定( mail ) // // あぼーんの時はtrueを返す // bool NodeTreeBase::check_abone_mail( const int number ) { if( ! m_abone_age ) return false; NODE* head = res_header( number ); if( ! head ) return false; if( ! head->headinfo ) return false; if( head->headinfo->abone ) return true; if( ! head->headinfo->sage ){ head->headinfo->abone = true; return true; } return false; } // // number番のあぼーん判定( word, regex / board word, board regex / global word, global regex ) // // あぼーんの時はtrueを返す // bool NodeTreeBase::check_abone_word( const int number ) { const bool check_word = ! m_list_abone_word.empty(); const bool check_regex = ! m_list_abone_regex.empty(); const bool check_word_board = ( m_abone_board && ! m_list_abone_word_board.empty() ); const bool check_regex_board = ( m_abone_board && ! m_list_abone_regex_board.empty() ); const bool check_word_global = ( m_abone_global && ! m_list_abone_word_global.empty() ); const bool check_regex_global = ( m_abone_global && ! m_list_abone_regex_global.empty() ); if( !check_word && !check_regex && !check_word_board && !check_regex_board && !check_word_global && !check_regex_global ) return false; NODE* head = res_header( number ); if( ! head ) return false; if( ! head->headinfo ) return false; if( head->headinfo->abone ) return true; const std::string res_str = get_res_str( number ); JDLIB::Regex regex; const size_t offset = 0; // ローカル NG word if( check_word ){ for( const std::string& word : m_list_abone_word ) { if( res_str.find( word ) != std::string::npos ) { head->headinfo->abone = true; return true; } } } // ローカル NG regex if( check_regex ){ for( const JDLIB::RegexPattern& pattern : m_list_abone_regex ) { if( regex.match( pattern, res_str, offset ) ){ head->headinfo->abone = true; return true; } } } // 板レベル NG word if( check_word_board ){ for( const std::string& word : m_list_abone_word_board ) { if( res_str.find( word ) != std::string::npos ) { head->headinfo->abone = true; return true; } } } // 板レベル NG regex if( check_regex_board ){ for( const JDLIB::RegexPattern& pattern : m_list_abone_regex_board ) { if( regex.match( pattern, res_str, offset ) ){ head->headinfo->abone = true; return true; } } } // 全体 NG word if( check_word_global ){ for( const std::string& word : m_list_abone_word_global ) { if( res_str.find( word ) != std::string::npos ) { head->headinfo->abone = true; return true; } } } // 全体 NG regex if( check_regex_global ){ for( const JDLIB::RegexPattern& pattern : m_list_abone_regex_global ) { if( regex.match( pattern, res_str, offset ) ){ head->headinfo->abone = true; return true; } } } return false; } // // number番のあぼーん判定(連鎖) // // あぼーんしているレスにアンカーを張っているときはtrueを返す // bool NodeTreeBase::check_abone_chain( const int number ) { if( !m_abone_chain ) return false; NODE* head = res_header( number ); if( ! head ) return false; if( ! head->headinfo ) return false; if( head->headinfo->abone ) return true; bool abone = false; for( int block = 0; block < BLOCK_NUM; ++block ){ NODE* node = head->headinfo->block[ block ]; while( node ){ // アンカーノードの時は node->linkinfo->ancinfo != nullptr; if( node->type == NODE_LINK && node->linkinfo->ancinfo ){ int anc = 0; for(;;){ int anc_from = node->linkinfo->ancinfo[ anc ].anc_from; int anc_to = node->linkinfo->ancinfo[ anc ].anc_to; if( anc_from == 0 ) break; ++anc; // number-1 番以下のレスだけを見る if( anc_from >= number ) continue; anc_to = MIN( anc_to, number -1 ); // anc_from から anc_to まで全てあぼーんされているかチェック // ひとつでもあぼーんされていないレスが見付かったらあぼーんしない while( anc_from <= anc_to ){ NODE* tmphead = res_header( anc_from++ ); if( tmphead && ! tmphead->headinfo->abone ) return false; } abone = true; } } node = node->next_node; } } head->headinfo->abone = abone; return abone; } // 参照数(num_reference)と色のクリア void NodeTreeBase::clear_reference() { for( int i = 1; i <= m_id_header; ++i ){ NODE* tmphead = m_vec_header[ i ]; if( tmphead && tmphead->headinfo && tmphead->headinfo->block[ BLOCK_NUMBER ]->next_node ){ tmphead->headinfo->num_reference = 0; tmphead->headinfo->block[ BLOCK_NUMBER ]->next_node->color_text = COLOR_CHAR_LINK_RES; } } } // // from_number番から to_number 番までのレスが参照しているレスの参照数を更新 // void NodeTreeBase::update_reference( int from_number, int to_number ) { if( empty() ) return; if( to_number < from_number ) return; for( int i = from_number ; i <= to_number; ++i ) check_reference( i ); } // // number番のレスが参照しているレスのレス番号の参照数(num_reference)と色をチェック // // TBD {update,check}_id_nameとの一貫性からこの関数もupdate_referenceと統合したほうが良いかも? void NodeTreeBase::check_reference( const int number ) { NODE* head = res_header( number ); if( ! head ) return; // 既にあぼーんしているならチェックしない if( head->headinfo->abone ) return; // 2重チェック防止用 std::unordered_set< int > checked; checked.reserve( m_id_header +1 ); const bool posted = !m_posts.empty(); // 過去のレスから number 番へのアンカーがあった場合 if( m_map_future_refer.size() ){ std::map< int, std::vector< int > >::iterator it_map = m_map_future_refer.find( number ); if( it_map != m_map_future_refer.end() ){ const auto& refs = it_map->second; inc_reference( head, refs.size() ); #ifdef _DEBUG std::cout << "found number = " << number << " size = " << refs.size() << std::endl; #endif // 過去のレスへ自分の書き込みへの参照マークを付ける if( posted && m_posts.find( number ) != m_posts.end() ) { for( const int from : refs ) { #ifdef _DEBUG std::cout << "from " << from << std::endl; #endif NODE* tmphead = res_header( from ); if( tmphead && ! tmphead->headinfo->abone ){ m_refer_posts.insert( from ); } } } m_map_future_refer.erase( it_map ); #ifdef _DEBUG std::cout << "map_future_refer size = " << m_map_future_refer.size() << std::endl; #endif } } for( int block = 0; block < BLOCK_NUM; ++block ){ NODE* node = head->headinfo->block[ block ]; while( node ){ if( node->type == NODE_LINK ){ // アンカーノードの時は node->linkinfo->ancinfo != nullptr; if( node->linkinfo->ancinfo ){ int anc = 0; for(;;){ int anc_from = node->linkinfo->ancinfo[ anc ].anc_from; int anc_to = node->linkinfo->ancinfo[ anc ].anc_to; if( anc_from == 0 ) break; ++anc; anc_to = MIN( anc_to, CONFIG::get_max_resnumber() ); // >>1-1000 みたいなアンカーは弾く if( anc_to - anc_from >= RANGE_REF ) continue; for( int i = anc_from; i <= anc_to ; ++i ){ // 既にチェックしている if( checked.find( i ) != checked.end() ) continue; // 自分自身 if( i == number ) continue; // 未来へのレス if( i > number ){ #ifdef _DEBUG std::cout << "future ref " << i << " from " << number << std::endl; #endif std::map< int, std::vector< int > >::iterator it_map = m_map_future_refer.find( i ); if( it_map != m_map_future_refer.end() ){ ( (*it_map).second ).push_back( number ); #ifdef _DEBUG std::cout << "found size = " << ( (*it_map).second ).size() << std::endl; #endif } else{ #ifdef _DEBUG std::cout << "not found\n"; #endif std::pair< int, std::vector< int > > tmp_pair; tmp_pair.first = i; tmp_pair.second.push_back( number ); m_map_future_refer.insert( tmp_pair ); } continue; } // 過去へのレス NODE* tmphead = res_header( i ); if( tmphead && ! tmphead->headinfo->abone // 対象スレがあぼーんしていたらカウントしない && tmphead->headinfo->block[ BLOCK_NUMBER ] ){ checked.insert( i ); // 自分の書き込みに対するレス if( posted && m_posts.find( i ) != m_posts.end() ) { m_refer_posts.insert( number ); #ifdef _DEBUG std::cout << "ref " << i << " from " << number << std::endl; #endif } inc_reference( tmphead, 1 ); } } } } } node = node->next_node; } // while( node ) } // for( block ) } // // 参照数を count だけ増やしてして色を変更 // void NodeTreeBase::inc_reference( NODE* head, const int count ) { head->headinfo->num_reference += count; // 参照回数大 if( head->headinfo->num_reference >= m_num_reference[ LINK_HIGH ] ) head->headinfo->block[ BLOCK_NUMBER ]->next_node->color_text = COLOR_CHAR_LINK_HIGH; // 参照回数中 else if( head->headinfo->num_reference >= m_num_reference[ LINK_LOW ] ) head->headinfo->block[ BLOCK_NUMBER ]->next_node->color_text = COLOR_CHAR_LINK_LOW; // 参照無し else head->headinfo->block[ BLOCK_NUMBER ]->next_node->color_text = COLOR_CHAR_LINK_RES; } // 発言数(( num_id_name ))とIDの色のクリア void NodeTreeBase::clear_id_name() { for( int i = 1; i <= m_id_header; ++i ){ NODE* tmphead = m_vec_header[ i ]; if( tmphead && tmphead->headinfo && tmphead->headinfo->block[ BLOCK_ID_NAME ] ){ tmphead->headinfo->num_id_name = 0; tmphead->headinfo->block[ BLOCK_ID_NAME ]->next_node->color_text = COLOR_CHAR; } } } // // from_number番から to_number 番までの発言数の更新 // void NodeTreeBase::update_id_name( const int from_number, const int to_number ) { if( ! CONFIG::get_check_id() ) return; if( empty() ) return; if( to_number < from_number ) return; //まずIDをキーにしたレス番号の一覧を集計 for( int i = from_number ; i <= to_number; ++i ) { NODE* header = res_header( i ); if( ! header ) continue; if( ! header->headinfo->block[ BLOCK_ID_NAME ] ) continue; std::string str_id = header->headinfo->block[ BLOCK_ID_NAME ]->next_node->linkinfo->link; m_map_id_name_resnumber[ str_id ].insert( i ); } //集計したものを元に各ノードの情報を更新 for( const auto &a: m_map_id_name_resnumber ){ // ID = a.first, レス番号の一覧 = a.second for( const auto &num: a.second ) { NODE* header = res_header( num ); if( ! header ) continue; if( ! header->headinfo->block[ BLOCK_ID_NAME ] ) continue; set_num_id_name( header, a.second.size() ); } } } // // 発言数( num_id_name )の更新 // // IDノードの色も変更する // void NodeTreeBase::set_num_id_name( NODE* header, const int num_id_name ) { if( ! header->headinfo->block[ BLOCK_ID_NAME ] ) return; header->headinfo->num_id_name = num_id_name; if( num_id_name >= m_num_id[ LINK_HIGH ] ) header->headinfo->block[ BLOCK_ID_NAME ]->next_node->color_text = COLOR_CHAR_LINK_ID_HIGH; else if( num_id_name >= m_num_id[ LINK_LOW ] ) header->headinfo->block[ BLOCK_ID_NAME ]->next_node->color_text = COLOR_CHAR_LINK_ID_LOW; else header->headinfo->block[ BLOCK_ID_NAME ]->next_node->color_text = COLOR_CHAR; } // // from_number番から to_number 番までのレスのフォント判定を更新 // void NodeTreeBase::update_fontid( const int from_number, const int to_number ) { if( empty() ) return; if( to_number < from_number ) return; for( int i = from_number ; i <= to_number; ++i ) check_fontid( i ); } // // number番のレスのフォント判定を更新 // // TBD {update,check}_id_nameとの一貫性からこの関数もupdate_fontidと統合したほうが良いかも? void NodeTreeBase::check_fontid( const int number ) { NODE* head = res_header( number ); if( ! head ) return; if( ! head->headinfo ) return; if( head->fontid != FONT_EMPTY ) return; // ヘッダノードには、フォント判定済みの意味を兼ねて、デフォルトフォントを設定しておく head->fontid = FONT_DEFAULT; if( ! m_aa_regex.compiled() ) return; char fontid_mes = FONT_DEFAULT; // 本文のフォント(fontid.h) // AAフォント判定 const std::string res_str = get_res_str( number ); JDLIB::Regex regex; const size_t offset = 0; if( regex.match( m_aa_regex, res_str, offset ) ){ fontid_mes = FONT_AA; #ifdef _DEBUG std::cout << "NodeTreeBase::check_fontid() fontid = " << FONT_AA << " res = " << number << std::endl; #endif } // 本文のフォントを設定 if( fontid_mes != FONT_DEFAULT ){ NODE *node = head->headinfo->block[ BLOCK_MES ]; while (node) { node->fontid = fontid_mes; node = node->next_node; } } } // // http://ime.nu/ などをリンクから削除 // // 取り除いたらtrueを返す // // Thanks to 「パッチ投稿」スレの24氏 // // http://jd4linux.sourceforge.jp/cgi-bin/bbs/test/read.cgi/support/1151836078/24 // //static member bool NodeTreeBase::remove_imenu( char* str_link ) { char *p = str_link; if ( memcmp( p, "http", strlen( "http" ) ) != 0 ) return false; p += strlen( "http" ); if ( *p == 's' ) p++; if ( memcmp( p, "://", strlen( "://" ) ) != 0 ) return false; p += strlen( "://" ); const char *cut_sites[] = { "ime.nu/", "ime.st/", "nun.nu/", "pinktower.com/", nullptr }; const char **q = cut_sites; while ( *q ) { size_t cs_len = strlen( *q ); if ( memcmp( p, *q, cs_len ) == 0 ) { // "http://ime.nu/"等、URLがそれだけだった場合は削除しない if ( p[cs_len] == '\0' ) return false; memmove( p, p + cs_len, strlen( p + cs_len ) + 1 ); return true; } q ++; } return false; } // 文字列中の"&"を"&"に変換する //static member int NodeTreeBase::convert_amp( char* text, const int n ) { int m = n; int i; for( i = 0; i < m; i++ ){ if( text[ i ] == '&' && m > (i + 4) && text[i + 1] == 'a' && text[i + 2] == 'm' && text[i + 3] == 'p' && text[i + 4] == ';' ){ // &の次, &の次, &の次からの長さ memmove( text + i + 1, text + i + 5, n - i - 5 ); // "amp;"の分減らす m -= 4; } } text[m] = '\0'; return m; } // 自分の書き込みにレスしたか bool NodeTreeBase::is_refer_posted( const int number ) const { return m_refer_posts.find( number ) != m_refer_posts.end(); } // 書き込みマークセット void NodeTreeBase::set_posted( const int number, const bool set ) { if( set ) { m_posts.insert( number ); } else { m_posts.erase( number ); } // 自分の書き込みに対するレス const std::list< int > res_num = get_res_reference( number ); // レスされたマークを設定する if( set ){ for( const int n : res_num ) { m_refer_posts.insert( n ); } } // レスされてなくなったので、マークを解除する else{ for( const int n : res_num ) { // レスアンカーのリストを取得 std::list< ANCINFO* > anchors = get_res_anchors( n ); for( const ANCINFO* anchor : anchors ) { // 他の自分の書き込みに対するレスになっていないか? const auto end = m_posts.end(); for( int i = anchor->anc_from; i <= anchor->anc_to; i++ ){ // 他の自分の書き込みに対するレス if( m_posts.find( i ) != end ) goto KEEP_POSTMARK; } } // マークを解除する m_refer_posts.erase( n ); KEEP_POSTMARK:; } } } // 書き込み履歴のリセット void NodeTreeBase::clear_post_history() { m_posts.clear(); m_refer_posts.clear(); } jdim-0.7.0/src/dbtree/nodetreebase.h000066400000000000000000000432451417047150700173240ustar00rootroot00000000000000// ライセンス: GPL2 // // ノードツリー( DOMみたいな木構造 )のベースクラス および DAT & HTMLパーサ // #ifndef _NODETREEBASE_H #define _NODETREEBASE_H #include "node.h" #include "fontid.h" #include "skeleton/loadable.h" #include "jdlib/heap.h" #include "jdlib/miscutil.h" #include "jdlib/jdregex.h" #include #include #include #include namespace JDLIB { class LOADERDATA; } namespace DBTREE { enum { LINK_LOW = 0, LINK_HIGH, LINK_NUM }; constexpr size_t RESUME_CHKSIZE = 64; //ノードツリーのベースクラス class NodeTreeBase : public SKELETON::Loadable { typedef sigc::signal< void > SIG_UPDATED; typedef sigc::signal< void > SIG_FINISHED; SIG_UPDATED m_sig_updated; SIG_UPDATED m_sig_finished; std::string m_url; std::string m_default_noname; // コード変換前の生データのサイズ ( byte ) std::size_t m_lng_dat{}; // レジュームのモード int m_resume; // レジューム時のチェック用 bool m_resume_cached{}; // 生データの先頭から RESUME_CHKSIZE バイト分を入れる char m_resume_head[ RESUME_CHKSIZE ]; // レジューム中にスキップした生データサイズ std::size_t m_resume_lng{}; // 現在処理中のヘッダ番号( つまりロード中でないなら総レス数になる ) int m_id_header; // サーバー側であぼーんがあったりしてスレが壊れている bool m_broken{}; JDLIB::HEAP m_heap; std::vector< NODE* > m_vec_header; // レスのヘッダのポインタの配列 std::string m_subject; // 参照で色を変える回数 int m_num_reference[ LINK_NUM ]; // 発言数で色を変える回数 int m_num_id[ LINK_NUM ]; // あぼーん情報 // 実体は親のarticlebaseクラスが持っていてcopy_abone_info()でコピーする std::list< std::string > m_list_abone_id; // あぼーんするID std::list< std::string > m_list_abone_name; // あぼーんする名前 std::list< std::string > m_list_abone_word; // あぼーんする文字列 std::list< JDLIB::RegexPattern > m_list_abone_regex; // あぼーんする正規表現 std::list< std::string > m_list_abone_id_board; // あぼーんするID(板レベル) std::list< std::string > m_list_abone_name_board; // あぼーんする名前(板レベル) std::list< std::string > m_list_abone_word_board; // あぼーんする文字列(板レベル) std::list< JDLIB::RegexPattern > m_list_abone_regex_board; // あぼーんする正規表現(板レベル) std::list< std::string > m_list_abone_word_global; // あぼーんする文字列(全体) std::list< JDLIB::RegexPattern > m_list_abone_regex_global; // あぼーんする正規表現(全体) std::unordered_set< int > m_abone_reses; // レスあぼーん情報 bool m_abone_transparent{}; // 透明あぼーん bool m_abone_chain{}; // 連鎖あぼーん bool m_abone_age{}; // age ているレスはあぼーん bool m_abone_default_name{}; // デフォルト名無しのレスはあぼーん bool m_abone_noid{}; // ID無しのレスはあぼーん bool m_abone_board{}; // 板レベルでのあぼーんを有効にする bool m_abone_global{}; // 全体レベルでのあぼーんを有効にする // 自分が書き込んだレスか std::unordered_set< int > m_posts; // 自分の書き込みにレスしているか std::unordered_set< int > m_refer_posts; // 未来のレスに対するアンカーがある時に使用する // check_reference() を参照 std::map< int, std::vector< int > > m_map_future_refer; // ロード用変数 std::string m_buffer_lines; std::string m_parsed_text; // HTMLパーサに使うバッファ std::string m_buffer_write; // 書き込みチェック用バッファ bool m_check_update{}; // HEADによる更新チェックのみ bool m_check_write{}; // 自分の書き込みかチェックする bool m_loading_newthread{}; // 新スレ読み込み中 // キャッシュ保存用ファイルハンドラ FILE* m_fout{}; // パース用雑用変数 NODE* m_node_previous{}; // AA判定用 JDLIB::RegexPattern m_aa_regex; // その他のエラーメッセージ std::string m_ext_err; // 各IDと発言数、レス番号のマッピング std::unordered_map< std::string, std::unordered_set< int > > m_map_id_name_resnumber; protected: void set_resume( const bool resume ); void set_broken( const bool broken ) { m_broken = broken; } int id_header() const noexcept { return m_id_header; } void set_ext_err( const std::string& ext_err ){ m_ext_err = ext_err; } public: NodeTreeBase( const std::string& url, const std::string& date_modified ); ~NodeTreeBase(); bool empty() const noexcept { return m_url.empty(); } void update_url( const std::string& url ); SIG_UPDATED& sig_updated() { return m_sig_updated; } SIG_FINISHED& sig_finished() { return m_sig_finished; } // キャッシュかららロード void load_cache(); const std::string& get_url() const { return m_url; } const std::string& get_subject() const { return m_subject; } int get_res_number() const; size_t get_lng_dat() const { return m_lng_dat; } bool is_broken() const{ return m_broken; } const std::string& get_ext_err() const { return m_ext_err; } bool is_checking_update() const { return m_check_update; } // number番のレスのヘッダノードのポインタを返す const NODE* res_header( int number ) const; NODE* res_header( int number ) { return const_cast( static_cast( *this ).res_header( number ) ); } // number番の名前 std::string get_name( int number ) const; // number番の名前の重複数( = 発言数 ) int get_num_name( int number ) const; // 指定した発言者の名前のレス番号をリストにして取得 std::list< int > get_res_name( const std::string& name ) const; // number番のレスの時刻を文字列で取得 // 内部で regex を使っているので遅い std::string get_time_str( int number ) const; // number番のID std::string get_id_name( int number ) const; // 指定したID の重複数( = 発言数 ) // 下のget_num_id_name( int number )と違って検索するので遅い int get_num_id_name( const std::string& id ) const; // number番のID の重複数( = 発言数 ) int get_num_id_name( const int number ) const; // 指定した発言者IDを持つレス番号をリストにして取得 std::list< int > get_res_id_name( const std::string& id_name ) const; // str_num で指定したレス番号をリストにして取得 // str_num は "from-to" の形式 (例) 3から10をセットしたいなら "3-10" // list_jointは出力で true のスレは前のスレに連結される (例) "3+4" なら 4が3に連結 std::list< int > get_res_str_num( const std::string& str_num, std::list< bool >& list_joint ) const; // URL を含むレス番号をリストにして取得 std::list< int > get_res_with_url() const; // ツリーに含まれてる 画像URL をリストにして取得 std::list get_imglinks() const; // number番のレスを参照しているレス番号をリストにして取得 std::list< int > get_res_reference( const int number ) const; // res_num に含まれるレスを参照しているレス番号をリストにして取得 std::list< int > get_res_reference( const std::list< int >& res_num ) const; // 高参照レスの番号をリストにして取得 std::list< int > get_highly_referened_res() const; // query を含むレス番号をリストにして取得 // mode_or == true なら OR抽出 std::list< int > get_res_query( const std::string& query, const bool mode_or ) const; // number番のレスの文字列を返す // ref == true なら先頭に ">" を付ける std::string get_res_str( int number, bool ref = false ) const; // 明示的にhtml を加える // パースして追加したノードのポインタを返す // html は UTF-8 であること NODE* append_html( const std::string& html ); // 明示的にdat を加える // パースして追加したノードのポインタを返す // dat は UTF-8 であること NODE* append_dat( const std::string& dat ); // ロード開始 // check_update : HEADによる更新チェックのみ virtual void download_dat( const bool check_update ); // あぼーんしているか bool get_abone( int number ) const; // あぼーん情報を親クラスのarticlebaseからコピーする void copy_abone_info( const std::list< std::string >& list_abone_id, const std::list< std::string >& list_abone_name, const std::list< std::string >& list_abone_word, const std::list< std::string >& list_abone_regex, const std::unordered_set< int >& abone_reses, const bool abone_transparent, const bool abone_chain, const bool abone_age, const bool abone_default_name, const bool abone_noid, const bool abone_board, const bool abone_global ); // 全レスのあぼーん状態の更新 // 発言数や参照数も更新する void update_abone_all(); // 自分が書き込んだレスか void copy_post_info( const std::unordered_set< int >& posts ){ m_posts = posts; } const std::unordered_set< int >& get_posts() const noexcept { return m_posts; } // 自分の書き込みにレスしたか bool is_refer_posted( const int number ) const; // 書き込みマークセット void set_posted( const int number, const bool set ); // 書き込み履歴のリセット void clear_post_history(); protected: virtual void clear(); virtual void init_loading(); // ロード用データ作成 virtual void create_loaderdata( JDLIB::LOADERDATA& data ){} // 保存前にrawデータを加工 // デフォルトでは何もしない virtual char* process_raw_lines( char* rawlines ){ return rawlines; } // raw データを dat に変換 // デフォルトでは何もしない virtual const char* raw2dat( char* rawlines, int& byte ){ byte = strlen( rawlines ); return rawlines; } void receive_data( const char* data, size_t size ) override; void receive_finish() override; private: NODE* create_node(); NODE* create_node_header(); NODE* create_node_block(); NODE* create_node_idnum(); NODE* create_node_br(); NODE* create_node_hr(); NODE* create_node_space( const int type ); NODE* create_node_multispace( const char* text, const int n, const char fontid = FONT_MAIN ); NODE* create_node_htab(); NODE* create_node_link( const char* text, const int n, const char* link, const int n_link, const int color_text, const bool bold, const char fontid = FONT_MAIN ); NODE* create_node_anc( const char* text, const int n, const char* link, const int n_link, const int color_text, const bool bold, const ANCINFO* ancinfo, const int lng_ancinfo, const char fontid = FONT_MAIN ); NODE* create_node_sssp( const char* link, const int n_link ); NODE* create_node_img( const char* text, const int n, const char* link, const int n_link, const int color_text, const bool bold, const char fontid = FONT_MAIN ); NODE* create_node_text( const char* text, const int color_text, const bool bold = false, const char fontid = FONT_MAIN ); NODE* create_node_ntext( const char* text, const int n, const int color_text, const bool bold = false, const char fontid = FONT_MAIN ); NODE* create_node_thumbnail( const char* text, const int n, const char* link, const int n_link, const char* thumb, const int n_thumb, const int color_text, const bool bold, const char fontid = FONT_MAIN ); // 以下、構文解析用関数 void add_raw_lines( char* rawines, size_t size ); const char* add_one_dat_line( const char* datline ); void parse_name( NODE* header, const char* str, const int lng, const int color_name ); void parse_mail( NODE* header, const char* str, const int lng ); void parse_date_id( NODE* header, const char* str, const int lng ); // HTMLパーサ // digitlink : true の時は先頭に数字が現れたらアンカーにする( parse_name() などで使う ) // false なら数字の前に >> がついてるときだけアンカーにする // bold : ボールド表示 // ahref : からリンクノードを作成する void parse_html( const char* str, const int lng, const int color_text, bool digitlink, const bool bold, const bool ahref, const char fontid = FONT_MAIN ); // 書き込みログ比較用文字列作成 // m_buffer_write に作成した文字列をセットする void parse_write( const char* str, const int lng, const std::size_t max_lng_write ); bool check_anchor( const int mode, const char* str_in, int& n, char* str_out, char* str_link, int lng_link, ANCINFO* ancinfo ) const; int check_link( const char* str_in, const int lng_in, int& n_in, char* str_link, const int lng_link ) const; int check_link_impl( const char* str_in, const int lng_in, int& n_in, char* str_link, const int lng_link, const int linktype, const int delim_pos ) const; // レジューム時のチェックデータをキャッシュ void set_resume_data( const char* data, size_t length ); // あぼーんのクリア void clear_abone(); // from_number番から to_number 番までのレスのあぼーん状態を更新 void update_abone( const int from_number, const int to_number ); // あぼーんチェック bool check_abone_res( const int number ); bool check_abone_id( const int number ); bool check_abone_name( const int number ); bool check_abone_mail( const int number ); bool check_abone_word( const int number ); bool check_abone_chain( const int number ); // number番のレスに含まれるレスアンカーをリストにして取得 std::list< ANCINFO* > get_res_anchors( const int number ); // 参照数(num_reference)と色のクリア void clear_reference(); // from_number番から to_number 番までのレスが参照しているレスの参照数を更新 void update_reference( int from_number, int to_number ); // number番のレスが参照しているレスのレス番号の参照数(num_reference)と色をチェック void check_reference( const int number ); // 参照数を count だけ増やしてして色を変更 void inc_reference( NODE* head, const int count ); // 発言数とIDの色のクリア void clear_id_name(); // from_number番から to_number 番までの発言数の更新 void update_id_name( const int from_number, const int to_number ); // number番のレスの発言数をチェック void check_id_name( const int number ) = delete; // 発言数( num_id_name )の更新 // IDノードの色も変更する void set_num_id_name( NODE* header, const int num_id_name ); // from_number番から to_number 番までのレスのフォント判定を更新 void update_fontid( const int from_number, const int to_number ); // number番のレスのフォント判定を更新 void check_fontid( const int number ); // http://ime.nu/ などをリンクから削除 static bool remove_imenu( char* str_link ); // 文字列中の"&"を"&"に変換する static int convert_amp( char* text, const int n ); }; // // リンクが現れたかチェックして文字列を取得する関数 // (引数の値は、check_link_impl()を見ること) // inline int NodeTreeBase::check_link( const char* str_in, const int lng_in, int& n_in, char* str_link, const int lng_link ) const { // http://, https://, ftp://, ttp(s)://, tp(s):// のチェック int delim_pos = 0; const int linktype = MISC::is_url_scheme( str_in, &delim_pos ); if( linktype == MISC::SCHEME_NONE ) return linktype; return check_link_impl( str_in, lng_in, n_in, str_link, lng_link, linktype, delim_pos ); } } #endif jdim-0.7.0/src/dbtree/nodetreedummy.cpp000066400000000000000000000007211417047150700200700ustar00rootroot00000000000000// ライセンス: GPL2 // #define _DEBUG #include "jddebug.h" #include "nodetreedummy.h" using namespace DBTREE; NodeTreeDummy::NodeTreeDummy( const std::string& url ) : NodeTreeBase( url, std::string() ) { #ifdef _DEBUG std::cout << "NodeTreeDummy::NodeTreeDummy : " << get_url() << std::endl; #endif } NodeTreeDummy::~NodeTreeDummy() { #ifdef _DEBUG std::cout << "NodeTreeDummy::~NodeTreeDummy : " << get_url() << std::endl; #endif } jdim-0.7.0/src/dbtree/nodetreedummy.h000066400000000000000000000007441417047150700175420ustar00rootroot00000000000000// ライセンス: GPL2 // // ARTICLE::LayoutTree::append_html() で使用するダミーノードツリー // #ifndef _NODETREEDUMMY_H #define _NODETREEDUMMY_H #include "nodetreebase.h" namespace DBTREE { class NodeTreeDummy : public NodeTreeBase { public: explicit NodeTreeDummy( const std::string& url ); ~NodeTreeDummy(); // ダウンロードしない void download_dat( const bool check_update ) override {} }; } #endif jdim-0.7.0/src/dbtree/nodetreejbbs.cpp000066400000000000000000000144441417047150700176640ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "nodetreejbbs.h" #include "interface.h" #include "jdlib/jdiconv.h" #include "jdlib/loaderdata.h" #include "config/globalconf.h" #include "global.h" #include #define APPEND_SECTION( num ) do {\ if( lng_sec[ num ] ){ \ assert( m_decoded_lines.size() + lng_sec[ num ] < BUF_SIZE_ICONV_OUT ); \ m_decoded_lines.append( lines + pos_sec[ num ], lng_sec[ num ] ); \ } } while( 0 ) using namespace DBTREE; NodeTreeJBBS::NodeTreeJBBS( const std::string& url, const std::string& date_modified ) : NodeTreeBase( url, date_modified ) { #ifdef _DEBUG std::cout << "NodeTreeJBBS::NodeTreeJBBS url = " << get_url() << " modified = " << date_modified << std::endl; #endif } NodeTreeJBBS::~NodeTreeJBBS() { #ifdef _DEBUG std::cout << "NodeTreeJBBS::~NodeTreeJBBS : " << get_url() << std::endl; #endif NodeTreeJBBS::clear(); } // // バッファなどのクリア // void NodeTreeJBBS::clear() { #ifdef _DEBUG std::cout << "NodeTreeJBBS::clear : " << get_url() << std::endl; #endif NodeTreeBase::clear(); // iconv 削除 m_iconv.reset(); m_decoded_lines.clear(); m_decoded_lines.shrink_to_fit(); } // // ロード実行前に呼ぶ初期化関数 // void NodeTreeJBBS::init_loading() { #ifdef _DEBUG std::cout << "NodeTreeJBBS::init_loading : " << get_url() << std::endl; #endif NodeTreeBase::init_loading(); // iconv 初期化 std::string charset = DBTREE::board_charset( get_url() ); if( ! m_iconv ) m_iconv = std::make_unique( "UTF-8", charset ); if( m_decoded_lines.capacity() < BUF_SIZE_ICONV_OUT ) { m_decoded_lines.reserve( BUF_SIZE_ICONV_OUT ); } } // // ロード用データ作成 // void NodeTreeJBBS::create_loaderdata( JDLIB::LOADERDATA& data ) { std::stringstream ss; ss << get_url() << "/"; // レジュームはしない代わりにスレを直接指定 set_resume( false ); if( id_header() ) ss << id_header() + 1 << "-"; data.url = ss.str(); data.agent = DBTREE::get_agent( get_url() ); data.host_proxy = DBTREE::get_proxy_host( get_url() ); data.port_proxy = DBTREE::get_proxy_port( get_url() ); data.basicauth_proxy = DBTREE::get_proxy_basicauth( get_url() ); data.size_buf = CONFIG::get_loader_bufsize(); data.timeout = CONFIG::get_loader_timeout(); data.cookie_for_request = DBTREE::board_cookie_for_request( get_url() ); if( ! get_date_modified().empty() ) data.modified = get_date_modified(); #ifdef _DEBUG std::cout << "NodeTreeJBBS::create_loader : " << data.url << std::endl; #endif } // // raw データを dat 形式に変換 // enum { MIN_SECTION = 5, MAX_SECTION = 8 }; const char* NodeTreeJBBS::raw2dat( char* rawlines, int& byte ) { assert( m_iconv != nullptr ); int byte_lines; const char* lines = m_iconv->convert( rawlines, strlen( rawlines ), byte_lines ); int number = id_header() + 1; #ifdef _DEBUG std::cout << "NodeTreeJBBS::raw2dat : byte_lines = " << byte_lines << std::endl; #endif // セクション分けして再合成する m_decoded_lines.clear(); byte = 0; int pos = 0; int section = 0; int pos_sec[ MAX_SECTION ]; int lng_sec[ MAX_SECTION ]; memset( lng_sec, 0, sizeof( int ) * MAX_SECTION ); while( pos < byte_lines ){ // セクション分け pos_sec[ section ] = pos; while( !( lines[ pos ] == '<' && lines[ pos +1 ] == '>' ) && lines[ pos ] != '\n' && pos < byte_lines ) ++pos; lng_sec[ section ] = pos - pos_sec[ section ]; // 最後の行で、かつ壊れている場合 if( pos >= byte_lines ){ set_broken( true ); break; } // スレを2ch型に再構築して改行 if( lines[ pos ] == '\n' ){ // セクション数が MIN_SECTION より小さい時はスレが壊れている if( section >= MIN_SECTION ){ // 透明あぼーんの判定 char number_str[ 64 ]; memset( number_str, 0, 64 ); memcpy( number_str, lines + pos_sec[ 0 ], MIN( lng_sec[ 0 ], 64 -1 ) ); int number_in = atoi( number_str ); while( number_in > number ){ #ifdef _DEBUG std::cout << "abone : number = "<< number << " : " << number_in << std::endl; #endif constexpr char broken_str[] = "あぼ〜ん<><>あぼ〜ん<> あぼ〜ん <>\n"; m_decoded_lines.append( broken_str ); ++number; } // 名前 APPEND_SECTION( 1 ); m_decoded_lines.append( "<>" ); // メアド APPEND_SECTION( 2 ); m_decoded_lines.append( "<>" ); // 日付 APPEND_SECTION( 3 ); // ID constexpr int i = 6; if( lng_sec[ i ] ){ m_decoded_lines.append( " ID:" ); m_decoded_lines.append( lines + pos_sec[ i ], lng_sec[ i ] ); } m_decoded_lines.append( "<>" ); // 本文 APPEND_SECTION( 4 ); m_decoded_lines.append( "<>" ); // タイトル APPEND_SECTION( 5 ); m_decoded_lines.push_back( '\n' ); ++number; } // 新しい行へ移動 ++pos; section = 0; memset( lng_sec, 0, sizeof( int ) * MAX_SECTION ); } // 次のセクションへ移動 else{ pos += 2; ++section; // 壊れている if( section >= MAX_SECTION ){ #ifdef _DEBUG std::cout << "NodeTreeJBBS::raw2dat : broken section = " << section-1 << std::endl; #endif set_broken( true ); // その行は飛ばす while( pos < byte_lines && lines[ pos ] != '\n' ) ++pos; ++pos; section = 0; memset( lng_sec, 0, sizeof( int ) * MAX_SECTION ); } } } byte = m_decoded_lines.size(); #ifdef _DEBUG std::cout << "byte = " << byte << std::endl; #endif return m_decoded_lines.c_str(); } jdim-0.7.0/src/dbtree/nodetreejbbs.h000066400000000000000000000013341417047150700173230ustar00rootroot00000000000000// ライセンス: GPL2 // // JBBS型ノードツリー // #ifndef _NODETREEJBBS_H #define _NODETREEJBBS_H #include "nodetreebase.h" #include #include namespace JDLIB { class Iconv; } namespace DBTREE { class NodeTreeJBBS : public NodeTreeBase { std::unique_ptr m_iconv; std::string m_decoded_lines; public: NodeTreeJBBS( const std::string& url, const std::string& date_modified ); ~NodeTreeJBBS(); protected: void clear() override; void init_loading() override; void create_loaderdata( JDLIB::LOADERDATA& data ) override; const char* raw2dat( char* rawlines, int& byte ) override; }; } #endif jdim-0.7.0/src/dbtree/nodetreelocal.cpp000066400000000000000000000007261417047150700200340ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "nodetreelocal.h" using namespace DBTREE; NodeTreeLocal::NodeTreeLocal( const std::string& url ) : NodeTree2chCompati( url, std::string() ) { #ifdef _DEBUG std::cout << "NodeTreeLocal::NodeTreeLocal url = " << get_url() << std::endl; #endif } NodeTreeLocal::~NodeTreeLocal() { #ifdef _DEBUG std::cout << "NodeTreeLocal::~NodeTreeLocal : " << get_url() << std::endl; #endif } jdim-0.7.0/src/dbtree/nodetreelocal.h000066400000000000000000000007201417047150700174730ustar00rootroot00000000000000// ライセンス: GPL2 // // ローカルファイル用ノードツリー // #ifndef _NODETREELOCAL_H #define _NODETREELOCAL_H #include "nodetree2chcompati.h" namespace DBTREE { class NodeTreeLocal : public NodeTree2chCompati { public: explicit NodeTreeLocal( const std::string& url ); ~NodeTreeLocal(); // ダウンロードしない void download_dat( const bool check_update ) override {} }; } #endif jdim-0.7.0/src/dbtree/nodetreemachi.cpp000066400000000000000000000251331417047150700200220ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "nodetreemachi.h" #include "interface.h" #include "jdlib/jdiconv.h" #include "jdlib/jdregex.h" #include "jdlib/loaderdata.h" #include "jdlib/miscutil.h" #include "jdlib/miscmsg.h" #include "config/globalconf.h" #include "global.h" #include "httpcode.h" #include using namespace DBTREE; constexpr size_t BUF_SIZE_200 = 256; NodeTreeMachi::NodeTreeMachi( const std::string& url, const std::string& date_modified ) : NodeTreeBase( url, date_modified ) { #ifdef _DEBUG std::cout << "NodeTreeMachi::NodeTreeMachi url = " << get_url() << " modified = " << date_modified << std::endl; #endif } NodeTreeMachi::~NodeTreeMachi() { #ifdef _DEBUG std::cout << "NodeTreeMachi::~NodeTreeMachi : " << get_url() << std::endl; #endif NodeTreeMachi::clear(); } // // バッファなどのクリア // void NodeTreeMachi::clear() { #ifdef _DEBUG std::cout << "NodeTreeMachi::clear : " << get_url() << std::endl; #endif NodeTreeBase::clear(); m_regex.reset(); // regex 削除 m_iconv.reset(); // iconv 削除 m_decoded_lines.clear(); m_decoded_lines.shrink_to_fit(); m_buffer.clear(); m_buffer.shrink_to_fit(); m_buffer_for_200.clear(); m_buffer_for_200.shrink_to_fit(); } // // ロード実行前に呼ぶ初期化関数 // void NodeTreeMachi::init_loading() { #ifdef _DEBUG std::cout << "NodeTreeMachi::init_loading : " << get_url() << std::endl; #endif NodeTreeBase::init_loading(); // regex 初期化 if( ! m_regex ) m_regex = std::make_unique(); // iconv 初期化 std::string charset = DBTREE::board_charset( get_url() ); if( ! m_iconv ) m_iconv = std::make_unique( "UTF-8", charset ); m_buffer_for_200.clear(); m_tmp_buffer = std::string(); } // // ロード用データ作成 // void NodeTreeMachi::create_loaderdata( JDLIB::LOADERDATA& data ) { // レジュームはしない代わりにスレを直接指定 set_resume( false ); // offlaw 形式 if( CONFIG::get_use_machi_offlaw() ){ JDLIB::Regex regex; const size_t offset = 0; const bool icase = false; const bool newline = true; const bool usemigemo = false; const bool wchar = false; if( regex.exec( "(https?://[^/]*)/bbs/read.cgi\\?BBS=([^&]*)&KEY=([0-9]*)", get_url(), offset, icase, newline, usemigemo, wchar ) ){ data.url = regex.str( 1 ) + std::string( "/bbs/offlaw.cgi/" ) + regex.str( 2 ) + std::string( "/" ) + regex.str( 3 ); if( id_header() >= 1 ) data.url += "/" + std::to_string( id_header() +1 ) + "-"; } } // read.cgi 形式 else{ data.url = get_url(); if( id_header() ) data.url += "&START=" + std::to_string( id_header() + 1 ); } data.agent = DBTREE::get_agent( get_url() ); data.host_proxy = DBTREE::get_proxy_host( get_url() ); data.port_proxy = DBTREE::get_proxy_port( get_url() ); data.basicauth_proxy = DBTREE::get_proxy_basicauth( get_url() ); data.size_buf = CONFIG::get_loader_bufsize(); data.timeout = CONFIG::get_loader_timeout(); data.cookie_for_request = DBTREE::board_cookie_for_request( get_url() ); if( ! get_date_modified().empty() ) data.modified = get_date_modified(); #ifdef _DEBUG std::cout << "NodeTreeMachi::create_loader : " << data.url << std::endl; #endif } // // キャッシュに保存する前の前処理 // char* NodeTreeMachi::process_raw_lines( char* rawlines ) { // オフラインか offlaw 形式を使用する場合はそのまま返す if( ! is_loading() || CONFIG::get_use_machi_offlaw() ) return rawlines; const size_t offset = 0; const bool icase = false; const bool newline = true; const bool usemigemo = false; const bool wchar = false; std::string buffer; // オンラインでかつ read.cgi 形式の場合は // 入力データを行ごとに分割して余計なタグを取り除いて本文だけ取り出す std::list< std::string > lines = MISC::get_lines( rawlines ); for( std::string& line : lines ) { line = MISC::remove_space( line ); if( m_tmp_buffer.empty() ){ if( line.rfind( "
    ", 0 ) == 0 ){ // 既に読み込んでいる場合は飛ばす char num_tmp[ 8 ]; memcpy( num_tmp, line.c_str() + strlen( "
    " ), 5 ); num_tmp[ 5 ] = '0'; if( atoi( num_tmp ) <= id_header() ) continue; // 行の途中で改行が入ったときは一時バッファに貯めておく if( line.find( "
    " ) != std::string::npos ){ buffer += line; buffer += "\n"; } else m_tmp_buffer = line; } // タイトル取得 else if( ! id_header() && m_subject_machi.empty() ){ std::string reg_subject( "([^<]*)" ); if( m_regex->exec( reg_subject, line, offset, icase, newline, usemigemo, wchar ) ){ const std::string charset = DBTREE::board_charset( get_url() ); m_subject_machi = MISC::Iconv( m_regex->str( 1 ), "UTF-8", charset ); #ifdef _DEBUG std::cout << "NodeTreeMachi::process_raw_lines\n"; std::cout << "subject = " << m_subject_machi << std::endl; #endif } } } else{ if( line.find( "
    " ) != std::string::npos ){ buffer += m_tmp_buffer; buffer += line; buffer += "\n"; m_tmp_buffer.clear(); } } } if( buffer.length() > BUF_SIZE_ICONV_OUT ){ MISC::ERRMSG( "buffer over flow in NodeTreeMachi::process_raw_lines" ); buffer = std::string(); } m_buffer = std::move( buffer ); return &*m_buffer.begin(); } // // raw データを dat 形式に変換 // const char* NodeTreeMachi::raw2dat( char* rawlines, int& byte ) { #ifdef _DEBUG std::cout << "NodeTreeMachi::raw2dat\n"; #endif const size_t offset = 0; const bool icase = false; const bool newline = true; const bool usemigemo = false; const bool wchar = false; int next = id_header() + 1; std::string buffer; // 文字コード変換 int byte_lines; const char* str_lines = m_iconv->convert( rawlines, strlen( rawlines ), byte_lines ); std::list< std::string > lines = MISC::get_lines( str_lines ); for( std::string& line : lines ) { line = MISC::remove_space( line ); if( line.empty() ) continue; int num = 0; std::string name; std::string mail; std::string date; std::string body; // offlaw 形式 if( line.c_str()[ 0 ] != '<' ){ std::string reg( "(.*?)<>(.*?)<>(.*?)<>(.*?)<>(.*?)<>(.*?)$"); if( ! m_regex->exec( reg, line, offset, icase, newline, usemigemo, wchar ) ){ #ifdef _DEBUG std::cout << "失敗\n"; std::cout << line << std::endl; #endif continue; } num = atoi( m_regex->str( 1 ).c_str() ); name = m_regex->str( 2 ); mail = m_regex->str( 3 ); date = m_regex->str( 4 ); body = m_regex->str( 5 ); if( num == 1 ) m_subject_machi = m_regex->str( 6 ); } // read.cgi 形式 else{ std::string reg( "
    ([1-9][0-9]*) ?名前:(|]*>) ?(]*>)?([^<]*)()? ?.+ ?投稿日: ?([^<]*)( ]*>\\[ ?(.*) ?\\])?
    ?(.*) ?

    $" ); if( ! m_regex->exec( reg, line, offset, icase, newline, usemigemo, wchar ) ){ #ifdef _DEBUG std::cout << "失敗\n"; std::cout << line << std::endl; #endif continue; } num = atoi( m_regex->str( 1 ).c_str() ); name = m_regex->str( 5 ); mail = m_regex->str( 3 ); date = m_regex->str( 7 ); if( !m_regex->str( 9 ).empty() ) date += " HOST:" + m_regex->str( 9 ); body = m_regex->str( 10 ); } while( next < num ){ #ifdef _DEBUG std::cout << "abone = " << num << std::endl; #endif buffer += "あぼ〜ん<><>あぼ〜ん<> あぼ〜ん <><>\n"; next++; } if( num == 1 ){ #ifdef _DEBUG std::cout << "subject = " << m_subject_machi << std::endl; #endif buffer = name + "<>" + mail + "<>" + date + "<> " + body + " <>" + m_subject_machi + "<>\n"; } else buffer += name + "<>" + mail + "<>" + date + "<> " + body + " <><>\n"; ++next; } if( buffer.length() > BUF_SIZE_ICONV_OUT ){ MISC::ERRMSG( "buffer over flow in NodeTreeMachi::process_raw_lines" ); buffer = std::string(); } m_decoded_lines = std::move( buffer ); byte = m_decoded_lines.size(); return m_decoded_lines.c_str(); } // // ローダからデータ受け取り // void NodeTreeMachi::receive_data( const char* data, size_t size ) { // dat落ち判定用処理。 receive_finish() も参照 if( ! is_checking_update() && get_code() == HTTP_OK && m_buffer_for_200.empty() ) { #ifdef _DEBUG std::cout << "NodeTreeMachi::receive_data : save some bytes\n"; #endif const int lng = MIN( size, BUF_SIZE_200 ); m_buffer_for_200.append( data, lng ); } NodeTreeBase::receive_data( data, size ); } // // ロード完了 // void NodeTreeMachi::receive_finish() { #ifdef _DEBUG std::cout << "NodeTreeMachi::receive_finish : " << get_url() << std::endl << " code = " << get_code() << std::endl; #endif // dat落ち判定 if( m_buffer_for_200.size() >= 2 && m_buffer_for_200[ 0 ] == '<' && ( m_buffer_for_200[ 1 ] == 'E' || m_buffer_for_200[ 1 ] == 'h' ) ) { int byte_lines; std::string str_lines( m_iconv->convert( &*m_buffer_for_200.begin(), m_buffer_for_200.size(), byte_lines ) ); #ifdef _DEBUG std::cout << str_lines << std::endl; #endif if( str_lines.find( "" ) != std::string::npos || str_lines.find( "\nなんらかの原因により、まちBBSサーバ内にログを見つけることができませんでした。" ) != std::string::npos ){ #ifdef _DEBUG std::cout << "not found\n"; #endif set_code( HTTP_NOT_FOUND ); set_str_code( "Not Found" ); } } NodeTreeBase::receive_finish(); } jdim-0.7.0/src/dbtree/nodetreemachi.h000066400000000000000000000021641417047150700174660ustar00rootroot00000000000000// ライセンス: GPL2 // // Machi型ノードツリー // #ifndef _NODETREEMACHI_H #define _NODETREEMACHI_H #include "nodetreebase.h" #include namespace JDLIB { class Iconv; class Regex; } namespace DBTREE { class NodeTreeMachi : public NodeTreeBase { std::unique_ptr m_regex; std::unique_ptr m_iconv; std::string m_decoded_lines; std::string m_buffer; std::string m_buffer_for_200; // HTTP200が来た時のdat落ち判定用 std::string m_tmp_buffer; std::string m_subject_machi; public: NodeTreeMachi( const std::string& url, const std::string& date_modified ); ~NodeTreeMachi(); protected: void clear() override; void init_loading() override; void create_loaderdata( JDLIB::LOADERDATA& data ) override; char* process_raw_lines( char* rawlines ) override; const char* raw2dat( char* rawlines, int& byte ) override; void receive_data( const char* data, size_t size ) override; void receive_finish() override; }; } #endif jdim-0.7.0/src/dbtree/root.cpp000066400000000000000000001411561417047150700162020ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG //#define _SHOW_GETBOARD //#define _SHOW_BOARD //#define _TEST_CACHE #include "jddebug.h" #include "root.h" #include "boardfactory.h" #include "boardbase.h" #include "articlebase.h" #include "jdlib/cookiemanager.h" #include "jdlib/jdiconv.h" #include "jdlib/jdregex.h" #include "jdlib/miscutil.h" #include "jdlib/miscmsg.h" #include "jdlib/loaderdata.h" #include "skeleton/editviewdialog.h" #include "skeleton/msgdiag.h" #include "type.h" #include "command.h" #include "config/globalconf.h" #include "cache.h" #include "httpcode.h" #include "environment.h" #include "global.h" #include // chmod #include #include #include #include #ifdef _TEST_CACHE int cache_hit1 = 0; int cache_hit2 = 0; int cache_nohit = 0; #endif enum { SIZE_OF_RAWDATA = 2 * 1024 * 1024 // bbsmenu.html の最大サイズ }; // ルート要素名( boards.xml ) #define ROOT_NODE_NAME "boardlist" using namespace DBTREE; // is_moved() の戻り値 enum { BOARD_MOVED = 0, BOARD_NEW, BOARD_EXISTS }; Root::Root() : SKELETON::Loadable() , m_board_null{ std::make_unique( "", "", "" ) } , m_enable_save_movetable( true ) { m_xml_document.clear(); clear(); clear_load_data(); load_movetable(); load_cache(); load_etc(); // JDのサポートBBS登録 set_board( ENVIRONMENT::get_jdbbs(), "JDサポートBBS" ); // 2chのスレの過去ログ set_board( ENVIRONMENT::get_jd2chlog(), "2chスレ過去ログ" ); // ローカルファイル set_board( URL_BOARD_LOCAL, "ローカルファイル" ); } // // デストラクタで子Boardクラスをすべて削除 // Root::~Root() { #ifdef _DEBUG std::cout << "Root::~Root\n"; #endif clear(); for( auto& b : m_list_board ) b->terminate_load(); #ifdef _TEST_CACHE std::cout << "board cache\n" << "hit1 = " << cache_hit1 << std::endl << "hit2 = " << cache_hit2 << std::endl << "nohit = " << cache_nohit << std::endl << "(hit1+hit2)/total*100 = " << (double)(cache_hit1+cache_hit2)/(cache_hit1+cache_hit2+cache_nohit)*100. << std::endl; #endif } void Root::clear() { m_rawdata.clear(); m_rawdata.shrink_to_fit(); } // // URLから BoardBase を取得する関数 // // count は無限再帰呼び出し禁止用 // BoardBase* Root::get_board( const std::string& url, const int count ) { #ifdef _SHOW_GETBOARD std::cout << "Root::get_board : count = " << count << " url = " << url << std::endl; #endif const int max_count = 50; // キャッシュ if( m_get_board ){ if( url == m_get_board_url ){ #ifdef _TEST_CACHE ++cache_hit1; #endif return m_get_board; } else if( m_get_board->equal( url ) ){ m_get_board_url = url; #ifdef _TEST_CACHE ++cache_hit2; #endif return m_get_board; } } #ifdef _TEST_CACHE ++cache_nohit; #endif m_get_board_url = url; m_get_board = nullptr; if( count == 0 ){ const std::size_t pos = url.rfind( "http://" ); const std::size_t pos2 = url.rfind( "https://" ); // ユーザープロフィールアドレス( http://be.2ch.net/test/p.php?u=d:http://〜 )の様に // 先頭以外に http:// が入っている場合は失敗 if( ( pos != std::string::npos && pos != 0 ) || ( pos2 != std::string::npos && pos2 != 0 ) ) { return m_board_null.get(); } // http:// が含まれていなかったら先頭に追加して再帰呼び出し else if( pos == std::string::npos && pos2 == std::string::npos && ! is_local( url ) ){ const char* scheme{ url.rfind( "//", 0 ) == 0 ? "http:" : "http://" }; BoardBase* board = get_board( scheme + url, count + 1 ); m_get_board_url = url; return board; } } // サーチ auto it_board = std::find_if( m_list_board.begin(), m_list_board.end(), [&url]( const auto& b ) { return b->equal( url ); } ); if( it_board != m_list_board.end() ) { m_get_board = it_board->get(); m_get_board->read_info(); // 板情報の取得( 詳しくはBoardBase::read_info()をみること ) #ifdef _SHOW_GETBOARD std::cout << "found\n"; #endif return m_get_board;; } // 見つからなかった if( count < max_count ){ // 移転した時はrootを付け変えて再帰呼び出し std::string new_url = is_board_moved( url ); if( ! new_url.empty() ){ BoardBase* board = get_board( new_url, count + 1 ); m_get_board_url = url; return board; } // 2ch型の場合、板パスを見てもし一致したら新ホストに移転したと判断して移転テーブルを更新する if( is_2ch( url ) ){ // 板パスを見て一致したら移転したと見なす // TODO : 板パスが同じ板が2つ以上あるときどうするか? const auto match_path = [&url]( const auto& b ) { return is_2ch( b->get_root() ) && url.find( b->get_path_board() + "/" ) != std::string::npos; }; // 全ての板をサーチして移転先の板を探す auto it = std::find_if( m_list_board.begin(), m_list_board.end(), match_path ); if( it != m_list_board.end() ) { BoardBase* board = it->get(); const std::string hostname = MISC::get_hostname( url ); const std::string& path_board = board->get_path_board(); // 板移転テーブルを更新 push_movetable( hostname, path_board, board->get_root(), path_board ); std::ostringstream ss; ss << board->get_name() << '\n' << "旧 URL = " << hostname + path_board << '/' << '\n' << "新 URL = " << board->url_boardbase() << std::endl; MISC::MSG( ss.str() ); if( m_enable_save_movetable ){ //移転テーブル保存 save_movetable(); // サイドバーに登録されているURL更新 CORE::core_set_command( "update_sidebar_item" ); } board = get_board( url, count + 1 ); m_get_board_url = url; return board; } } // 最後が "/" で終わってなかったら足して再帰呼び出し if( url[ url.length() -1 ] != '/' ){ BoardBase* board = get_board( url + "/" , count + 1 ); m_get_board_url = url; return board; } } #ifdef _DEBUG std::cout << "Root::get_board: not found url = " << url << std::endl;; #endif // それでも見つからなかったらNullクラスを返す return m_board_null.get(); } // ローカルキャッシュから板一覧XML読み込み // // (注) 板一覧 XML の保存は BBSLIST::BBSListViewMain が行う // void Root::load_cache() { clear(); std::string file_in = CACHE::path_xml_listmain(); #ifdef _DEBUG std::cout << "Root::load_cache xml = " << file_in << std::endl; #endif std::string xml_bbsmenu; if( CACHE::load_rawdata( file_in, xml_bbsmenu ) ) { // Domノードを初期化 m_xml_document.init( xml_bbsmenu ); // Domノードの内容からDBに板を登録 analyze_board_xml(); } } // // サーバから bbsmenu.html を読み込んで xml に変換開始 // // 読み終わったらreceive_finish()でXMLに変換して"update_bbslist"コマンド発行 // void Root::download_bbsmenu() { if( is_loading() ) return; clear(); m_xml_document.clear(); m_rawdata.reserve( SIZE_OF_RAWDATA ); JDLIB::LOADERDATA data; data.init_for_data(); data.url = CONFIG::get_url_bbsmenu(); data.modified = get_date_modified(); bool send_cookie = true; constexpr bool protocol = false; const std::string host = MISC::get_hostname( data.url, protocol ); if( host.find( ".5ch.net" ) != std::string::npos || host.find( ".2ch.net" ) != std::string::npos ) { data.agent = CONFIG::get_agent_for2ch(); if( CONFIG::get_use_proxy_for2ch() ) { data.host_proxy = CONFIG::get_proxy_for2ch(); data.port_proxy = CONFIG::get_proxy_port_for2ch(); data.basicauth_proxy = CONFIG::get_proxy_basicauth_for2ch(); data.basicauth = CONFIG::get_proxy_basicauth_for2ch(); send_cookie = CONFIG::get_send_cookie_to_proxy_for2ch(); } } else { data.agent = CONFIG::get_agent_for_data(); if( CONFIG::get_use_proxy_for_data() ) { data.host_proxy = CONFIG::get_proxy_for_data(); data.port_proxy = CONFIG::get_proxy_port_for_data(); data.basicauth_proxy = CONFIG::get_proxy_basicauth_for_data(); data.basicauth = CONFIG::get_proxy_basicauth_for_data(); send_cookie = CONFIG::get_send_cookie_to_proxy_for_data(); } } if( send_cookie ) { const JDLIB::CookieManager* cookie_manager = JDLIB::get_cookie_manager(); data.cookie_for_request = cookie_manager->get_cookie_by_host( data.url ); } start_load( data ); } // // bbsmenu 受信中 // // virtual void Root::receive_data( const char* data, size_t size ) { m_rawdata.append( data, size ); } // // bbsmenu 受信完了 // // virtual void Root::receive_finish() { #ifdef _DEBUG std::cout << "Root::receive_finish code = " << get_code() << std::endl; #endif if( get_code() == HTTP_NOT_MODIFIED ){ std::string msg = get_str_code() + "\n\nサーバー上の板一覧は更新されていません。強制的に再読み込みをしますか?"; SKELETON::MsgDiag mdiag( nullptr, msg, false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO ); mdiag.set_default_response( Gtk::RESPONSE_YES ); if( mdiag.run() == Gtk::RESPONSE_YES ){ set_date_modified( std::string() ); download_bbsmenu(); } return; } if( ( get_code() == HTTP_MOVED_PERM || get_code() == HTTP_REDIRECT ) && ! location().empty() ){ const std::string msg = get_str_code() + "\n\n板一覧が " + location() + " に移転しました。更新しますか?"; SKELETON::MsgDiag mdiag( nullptr, msg, false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO ); mdiag.set_default_response( Gtk::RESPONSE_YES ); if( mdiag.run() == Gtk::RESPONSE_YES ){ set_date_modified( std::string() ); CONFIG::set_url_bbsmenu( location() ); download_bbsmenu(); } return; } if( get_code() != HTTP_OK ){ std::string msg = get_str_code() + "\n\n板一覧の読み込みに失敗したため板一覧は更新されませんでした。\n\nプロキシ設定や板一覧を取得するサーバのアドレスを確認して、ファイルメニューから板一覧の再読み込みをして下さい。\n板一覧取得サーバのアドレスはabout:configで確認出来ます。"; SKELETON::MsgDiag mdiag( nullptr, msg, false, Gtk::MESSAGE_ERROR ); mdiag.run(); MISC::ERRMSG( "bbsmenu load failed : " + get_str_code() ); CORE::core_set_command( "update_bbslist" ); return; } // 文字コードを変換してXML作成 JDLIB::Iconv libiconv{ "UTF-8", "MS932" }; int byte_out; const std::string rawdata_utf8 = libiconv.convert( &*m_rawdata.begin(), m_rawdata.size(), byte_out ); bbsmenu2xml( rawdata_utf8 ); if( m_xml_document.hasChildNodes() ) { // データベース更新 analyze_board_xml(); // bbslistview更新 CORE::core_set_command( "update_bbslist" ); } clear(); } // // bbsmenu.html -> xml 変換 // void Root::bbsmenu2xml( const std::string& menu ) { if( menu.empty() ) return; #ifdef _DEBUG std::cout << "Root::bbsmenu2xml\n"; #endif JDLIB::Regex regex; const size_t offset = 0; const bool icase = false; const bool newline = true; const bool usemigemo = false; const bool wchar = false; // menu のノードツリーを取得( menu がHTMLなので第二引数は true ) const XML::Document html( menu, true ); // XML用のノードツリーにルートノードを追加 m_xml_document.clear(); XML::Dom* root = m_xml_document.appendChild( XML::NODE_TYPE_ELEMENT, std::string( ROOT_NODE_NAME ) ); // カテゴリの要素 XML::Dom* subdir = nullptr; // カテゴリの有効/無効 bool enabled = true; // 現在の仕様では HTML > BODY > font[size="2"] の子要素が対象 // 特定のサイト(2ch.sc、next2ch.net)のbbsmenu.htmlにはfontタグがないため別のタグを使う std::list targets = html.getElementsByTagName( "font" ); if( targets.empty() ) targets = html.getElementsByTagName( "small" ); if( targets.empty() ) targets = html.getElementsByTagName( "body" ); if( targets.empty() ) { MISC::ERRMSG( "parse error for bbsmenu" ); return; } for( const XML::Dom* child : *targets.front() ) { // 要素b( カテゴリ名 ) if( child->nodeName() == "b" ) { const std::string category = child->firstChild()->nodeValue(); // 追加しないカテゴリ if( category == "チャット" || category == "ツール類" || category == "他のサイト" ) { enabled = false; continue; } else enabled = true; // subdir = root->appendChild( XML::NODE_TYPE_ELEMENT, "subdir" ); subdir->setAttribute( "name", category ); } // 要素bに続く要素a( 板URL ) else if( subdir && enabled && child->nodeName() == "a" ) { const std::string board_name = child->firstChild()->nodeValue(); const std::string url = child->getAttribute( "href" ); // 板として扱うURLかどうかで要素名を変える std::string element_name; if( CONFIG::use_link_as_board() ) element_name = "board"; else if( ( regex.exec( "^(https?:)?//.*/.*/$", url, offset, icase, newline, usemigemo, wchar ) && ( is_2ch( url ) || is_machi( url ) ) ) || is_JBBS( url ) || is_vip2ch( url ) ) element_name = "board"; else element_name = "link"; XML::Dom* board = subdir->appendChild( XML::NODE_TYPE_ELEMENT, element_name ); board->setAttribute( "name", board_name ); board->setAttribute( "url", url ); } } root->setAttribute( "date_modified", get_date_modified() ); #ifdef _DEBUG std::cout << "modified = " << get_date_modified() << std::endl; #endif } // // XML に含まれる板情報を取り出してデータベースを更新 // void Root::analyze_board_xml() { m_move_info = std::string(); m_analyzing_board_xml = true; m_analyzed_path_board.clear(); const std::list boards = m_xml_document.getElementsByTagName( "board" ); for( const XML::Dom* child : boards ) { const std::string name = child->getAttribute( "name" ); const std::string url = child->getAttribute( "url" ); //板情報セット set_board( url, name ); } // 移転があった if( ! m_move_info.empty() ) { SKELETON::EditViewDialog diag( m_move_info, "移転板一覧", false ); diag.resize( 600, 400 ); diag.run(); if( m_enable_save_movetable ){ //移転テーブル保存 save_movetable(); // サイドバーに登録されているURL更新 CORE::core_set_command( "update_sidebar_item" ); } } const XML::Dom* root = m_xml_document.get_root_element( std::string( ROOT_NODE_NAME ) ); if( root ) set_date_modified( root->getAttribute( "date_modified" ) ); m_analyzing_board_xml = false; m_analyzed_path_board.clear(); #ifdef _DEBUG std::cout << "Root::analyze_board_xml\n"; std::cout << "date_modified = " << get_date_modified() << std::endl; #endif } // // 板のタイプを判定 // int Root::get_board_type( const std::string& url, std::string& root, std::string& path_board, const bool etc ) const { JDLIB::Regex regex; const size_t offset = 0; const bool icase = false; const bool newline = true; const bool usemigemo = false; const bool wchar = false; int type = TYPE_BOARD_UNKNOWN; // 2ch if( ! etc && is_2ch( url ) ){ if( regex.exec( "(https?://[^/]*)(/[^/]*)/$" , url, offset, icase, newline, usemigemo, wchar ) ){ root = regex.str( 1 ); path_board = regex.str( 2 ); type = TYPE_BOARD_2CH; } } // JBBS else if( is_JBBS( url ) ){ if( regex.exec( "(https?://[^/]*)(/.*)/(index2?\\.html?)?$" , url, offset, icase, newline, usemigemo, wchar ) ){ root = regex.str( 1 ); path_board = regex.str( 2 ); type = TYPE_BOARD_JBBS; } } // まち else if( is_machi( url ) ){ if( regex.exec( "(https?://[^/]*)(/[^/]*)/(index2?\\.html?)?$" , url, offset, icase, newline, usemigemo, wchar ) ){ root = regex.str( 1 ); path_board = regex.str( 2 ); type = TYPE_BOARD_MACHI; } } // vipサービス else if( is_vip2ch( url ) ){ if( regex.exec( "(https?://[^/]*)(/[^/]*)/$" , url, offset, icase, newline, usemigemo, wchar ) ){ root = regex.str( 1 ); path_board = regex.str( 2 ); type = TYPE_BOARD_2CH_COMPATI; } } // ローカルファイル else if( is_local( url ) ){ root = "file://"; path_board = "/local"; type = TYPE_BOARD_LOCAL; } // その他は互換型 else{ if( regex.exec( "(https?://.*)(/[^/]*)/([^\\.]+\\.html?)?$" , url, offset, icase, newline, usemigemo, wchar ) ){ root = regex.str( 1 ); path_board = regex.str( 2 ); type = TYPE_BOARD_2CH_COMPATI; } } return type; } // // 板のタイプを簡易判定 // // 通常の get_board_type() で得た root を再判定するときに使用する ( 板移転処理用 ) // int Root::get_board_type( const std::string& root, const bool etc ) const { int type = TYPE_BOARD_UNKNOWN; // 2ch if( ! etc && is_2ch( root ) ) type = TYPE_BOARD_2CH; // JBBS else if( is_JBBS( root ) ) type = TYPE_BOARD_JBBS; // まち else if( is_machi( root ) ) type = TYPE_BOARD_MACHI; // ローカルファイル else if( is_local( root ) ) type = TYPE_BOARD_LOCAL; // その他は互換型 else type = TYPE_BOARD_2CH_COMPATI; return type; } // // 板のタイプに合わせて板情報をセット // ついでに移転の自動判定と移転処理もおこなう // // etc == true なら etc.txtに登録された外部板を意味する // bool Root::set_board( const std::string& url, const std::string& name, const std::string& basicauth, bool etc ) { #ifdef _SHOW_BOARD std::cout << "Root::set_board " << url << " " << name << std::endl; #endif std::string real_url; std::string root; std::string path_board; // scheme省略の場合はbbsmenuのURLから補う if( url.rfind( "//", 0 ) == 0 ) { const std::string menu_url = CONFIG::get_url_bbsmenu(); const std::size_t pos = menu_url.find( "://" ); if( pos != std::string::npos ) real_url = menu_url.substr( 0, pos + 1 ); } real_url.append( url ); // タイプ判定 const int type = get_board_type( real_url, root, path_board, etc ); if( type == TYPE_BOARD_UNKNOWN ) return false; // 移転チェック BoardBase* board = nullptr; const int state = is_moved( root, path_board, name, &board, etc ); #ifdef _SHOW_BOARD std::cout << "root = " << root << " path_board = " << path_board << " basicauth = " << basicauth <<" type = " << type << " state = " << state << std::endl; #endif // 新板登録 if( state == BOARD_NEW ){ auto uniq = DBTREE::BoardFactory( type, root, path_board, name, basicauth ); if( uniq ){ board = uniq.get(); m_list_board.push_back( std::move( uniq ) ); if( m_analyzing_board_xml ) m_analyzed_path_board.insert( path_board ); } } // 移転処理 else if( state == BOARD_MOVED ){ // XML解析中に、 // // // のように同じ板で異なるアドレスが現れた場合は、移転処理をせずにキャッシュが存在する方のアドレスを残す if( m_analyzing_board_xml && m_analyzed_path_board.find( path_board ) != m_analyzed_path_board.end() ){ std::string tmp_msg = "Root::set_board : The XML file is broken !\n"; tmp_msg += url + " has already been registered as " + board->url_boardbase(); MISC::ERRMSG( tmp_msg ); const std::string path1 = CACHE::path_board_root_fast( board->url_boardbase() ); const std::string path2 = CACHE::path_board_root_fast( real_url ); #ifdef _DEBUG std::cout << "path1 = " << path1 << std::endl << "path2 = " << path2 << std::endl; #endif // キャッシュが存在する方を正しいアドレスとする if( CACHE::file_exists( path1 ) == CACHE::EXIST_DIR ){ #ifdef _DEBUG std::cout << "path1 exists\n"; #endif // 既に登録済みなので何もしない } else if( CACHE::file_exists( path2 ) == CACHE::EXIST_DIR ){ #ifdef _DEBUG std::cout << "path2 exists\n"; #endif // url の方を登録する board->update_url( root, path_board ); } } // 移転処理実行 else{ if( ! exec_move_board( board, board->get_root(), board->get_path_board(), root, path_board ) ) return false; if( m_analyzing_board_xml ) m_analyzed_path_board.insert( path_board ); } } return true; } // // (明示的に)板移転 // bool Root::move_board( const std::string& url_old, const std::string& url_new, const bool etc ) { if( url_old == url_new ) return false; #ifdef _DEBUG std::cout << "Root::move_board " << url_old << " -> " << url_new << std::endl; #endif m_move_info = std::string(); std::string root; std::string path_board; BoardBase * board = get_board( url_old ); if( ! board ) return false; // タイプ判定 int type = get_board_type( url_new, root, path_board, etc ); if( type == TYPE_BOARD_UNKNOWN ) return false; if( ! exec_move_board( board, board->get_root(), board->get_path_board(), std::move( root ), std::move( path_board ) ) ) return false; // キャッシュを移動した if( ! m_move_info.empty() ){ if( m_enable_save_movetable ){ save_movetable(); // サイドバーに登録されているURL更新 CORE::core_set_command( "update_sidebar_item" ); } } return true; } // // 板移転処理実行 // // 引数を参照渡し(const std::string&)するとDB更新で値が変わるため値渡しする // bool Root::exec_move_board( BoardBase* board, std::string old_root, std::string old_path_board, std::string new_root, std::string new_path_board ) { if( ! board ) return false; #ifdef _SHOW_BOARD std::cout << "Root::exec_move_board\n"; std::cout << old_root << old_path_board << " -> " << new_root << new_path_board << std::endl; #endif if( old_root == new_root && old_path_board == new_path_board ){ std::string errmsg = "移転元のアドレスと移転先のアドレスが同じです\n\n" + old_root + old_path_board + " → " + new_root + new_path_board; SKELETON::MsgDiag mdiag( nullptr, errmsg, false, Gtk::MESSAGE_ERROR ); mdiag.run(); return false; } const std::string old_url = board->url_boardbase(); std::string old_path = CACHE::path_board_root( old_url ); // DB更新 board->update_url( new_root, new_path_board ); std::string new_url = board->url_boardbase(); std::string new_path = CACHE::path_board_root( new_url ); std::ostringstream ss; ss << board->get_name() << std::endl << " 旧 URL = " << old_url << std::endl << " 新 URL = " << new_url << std::endl; MISC::MSG( ss.str() ); m_get_board_url = std::string(); m_get_board = nullptr; // もしキャッシュが存在したら移動して移転テーブル更新 if( CACHE::file_exists( old_path ) == CACHE::EXIST_DIR ){ // キャッシュがある場合はダイアログに表示 m_move_info += ss.str() + "\n"; // 移動先に同名のファイルかフォルダ何かあったらリネームしてバックアップをとっておく if( CACHE::file_exists( new_path ) != CACHE::EXIST_ERROR ){ std::string path_tmp = new_path.substr( 0, new_path.length() - 1 ) + "_bk/"; if( rename( new_path.c_str(), path_tmp.c_str() ) == 0 ) MISC::MSG( "rename : " + new_path + " -> " + path_tmp ); else MISC::ERRMSG( "can't rename " + new_path + " to " + path_tmp ); } // キャッシュ移動 if( CACHE::mkdir_parent_of_board( new_url ) ){ if( rename( old_path.c_str(), new_path.c_str() ) == 0 ) MISC::MSG( "cache was moved : " + old_path + " -> " + new_path ); else MISC::ERRMSG( "can't move cache from " + old_path + " to " + new_path ); } #ifdef _DEBUG std::cout << "movetable was updated.\n" << "old_root = " << old_root << std::endl << "new_root = " << new_root << std::endl << "old_path_board = " << old_path_board << std::endl << "new_path_board = " << new_path_board << std::endl; #endif push_movetable( std::move( old_root ), std::move( old_path_board ), std::move( new_root ), std::move( new_path_board ) ); // この板に関連する表示中のviewのURLを更新 CORE::core_set_command( "update_url", old_url, new_url ); } return true; } // // 板移転テーブルを更新 // void Root::push_movetable( std::string old_root, std::string old_path_board, std::string new_root, std::string new_path_board ) { #ifdef _DEBUG std::cout << "Root::push_movetable : " << old_root << old_path_board << " -> " << new_root << new_path_board << std::endl; #endif if( old_root == new_root && old_path_board == new_path_board ){ std::string errmsg = "移転元のアドレスと移転先のアドレスが同じです (Root::push_movetable)\n\n" + old_root + old_path_board + " → " + new_root + new_path_board; SKELETON::MsgDiag mdiag( nullptr, errmsg, false, Gtk::MESSAGE_ERROR ); mdiag.run(); return; } std::string str; // new_root, new_path_board, old_root, old_path が過去に登録済みなら // 消す、または修正する(パフォーマンス向上、循環防止) std::list< MOVETABLE >::iterator it_move = m_movetable.begin(); for( ; it_move != m_movetable.end(); ){ #ifdef _DEBUG std::cout << "size = " << m_movetable.size() << " " << it_move->old_root << it_move->old_path_board << "/ -> " << it_move->new_root << std::endl; #endif if( ( it_move->old_root == new_root && it_move->old_path_board == new_path_board ) || ( it_move->old_root == old_root && it_move->old_path_board == old_path_board ) ){ const std::string str_tmp = "削除: " + it_move->old_root + it_move->old_path_board + "/ -> " + it_move->new_root + "\n"; #ifdef _DEBUG std::cout << str_tmp << std::endl; #endif str += str_tmp; it_move = m_movetable.erase( it_move ); continue; } // 移転先を最新にする // // (注意) old_root == new_root かつ old_path_board == new_path_board のとき // erase した内容と push_back した内容が同じになるので無限ループに落ちる else if( it_move->new_root == old_root && it_move->new_path_board == old_path_board ){ MOVETABLE movetable = *it_move; movetable.new_root = new_root; movetable.new_path_board = new_path_board; const std::string str_tmp = "更新: " + it_move->old_root + it_move->old_path_board + "/ -> " + it_move->new_root + " => " + movetable.old_root + movetable.old_path_board + "/ -> " + movetable.new_root + "\n"; #ifdef _DEBUG std::cout << str_tmp << std::endl; #endif str += str_tmp; it_move = m_movetable.erase( it_move ); m_movetable.push_back( std::move( movetable ) ); continue; } ++it_move; } if( ! str.empty() ) MISC::MSG( "\n" + str ); MOVETABLE movetable; movetable.old_root = std::move( old_root ); movetable.old_path_board = std::move( old_path_board ); movetable.new_root = std::move( new_root ); movetable.new_path_board = std::move( new_path_board ); m_movetable.push_back( std::move( movetable ) ); } // // 板をデータベースから削除 // bool Root::remove_board( const std::string& url ) { #ifdef _SHOW_BOARD std::cout << "Root::remove_board " << url << std::endl; #endif BoardBase * board = get_board( url ); if( ! board ) return false; #ifdef _SHOW_BOARD std::cout << "found\n" << "root = " << board->get_root() << std::endl << "path = " << board->get_path_board() << std::endl << "name = " << board->get_name() << std::endl; #endif // この板に関連するビューを全て閉じる // delete board する前に全て閉じないとセグフォの原因となるので注意 CORE::core_set_command( "close_board", url ); // 削除対象はアドレスで判定する m_list_board.remove_if( [board]( const auto& b ) { return board == b.get(); } ); m_get_board_url = std::string(); m_get_board = nullptr; return true; } // // 板が移転したどうかチェックする関数 // // 戻り値 : // // BOARD_EXISTS : DBに登録されていて移転していない // BOARD_MOVED : DBに登録されていて移転した // BOARD_NEW : DBに登録されていない // // 移転したなら board_old に古いデータが入って戻る // int Root::is_moved( const std::string& root, const std::string& path_board, const std::string& name, BoardBase** board_old, bool etc ) { for( auto& board : m_list_board ) { if( board->get_path_board() == path_board ){ // 既にリストに登録されてる if( board->get_root() == root ) return BOARD_EXISTS; // 名前が同じで、サイトが同じなら移転 if( board->get_name() == name && get_board_type( board->get_root() ) == get_board_type( root, etc ) ){ *board_old = board.get(); return BOARD_MOVED; } } } return BOARD_NEW; } // // etc.txtから外部板情報を読み込み // // etc.txt(Navi2ch互換) を読み込んで外部板情報( etcboardinfo.h )作成およびデータベース登録 // void Root::load_etc() { m_etcboards.clear(); JDLIB::Regex regex; const size_t offset = 0; const bool icase = false; const bool newline = true; const bool usemigemo = false; const bool wchar = false; std::string file_etctxt = CACHE::path_etcboard(); std::string etcboard; if( CACHE::load_rawdata( file_etctxt, etcboard ) ) { std::list< std::string > list_etc = MISC::get_lines( etcboard ); list_etc = MISC::remove_commentline_from_list( list_etc ); list_etc = MISC::remove_space_from_list( list_etc ); list_etc = MISC::remove_nullline_from_list( list_etc ); std::list< std::string >::iterator it; for( it = list_etc.begin(); it != list_etc.end(); ++it ){ DBTREE::ETCBOARDINFO info; // 名前 info.name = *( it++ ); if( it == list_etc.end() ) break; // url info.url = *( it++ ); if( it == list_etc.end() ) break; // basic認証 if( regex.exec( "(https?://)([^/]+:[^/]+@)(.+)$" , info.url, offset, icase, newline, usemigemo, wchar ) ) { info.basicauth = regex.str( 2 ); info.basicauth.pop_back(); info.url = regex.str( 1 ) + regex.str( 3 ); } // board id info.boardid = *( it ); #ifdef _DEBUG std::cout << "etc board : name = " << info.name << std::endl << "url = " << info.url << std::endl << "id:passwd = " < " << url_new << std::endl << name_old << " -> " << name_new << std::endl << board->get_basicauth() << " -> " << basicauth << std::endl; #endif // 移転処理 if( url_old != url_new ){ (*it).url = url_new; move_board( url_old, url_new, true ); } // 名前変更 (*it).name = name_new; board->update_name( name_new ); // BASIC認証変更 (*it).basicauth = basicauth; board->set_basicauth( basicauth ); // ID更新 (*it).boardid = boardid; save_etc(); return true; } // // 外部板削除 // bool Root::remove_etc( const std::string& url, const std::string& name ) { #ifdef _DEBUG std::cout << "Root::remove_etc url = " << url << " name = " << name << std::endl; #endif if( m_etcboards.empty() ) return false; const auto find_info = [&]( const DBTREE::ETCBOARDINFO& i ) { return i.url == url && i.name == name; }; const auto it = std::find_if( m_etcboards.cbegin(), m_etcboards.cend(), find_info ); if( it != m_etcboards.cend() ) { #ifdef _DEBUG std::cout << "found\n"; #endif remove_board( url ); m_etcboards.erase( it ); return true; } return false; } // // 外部板保存 // // 外部板情報から etc.txt(Navi2ch互換)作成 // void Root::save_etc() { // m_etcboards が空でも実行する #ifdef _DEBUG std::cout << "Root::save_etc\n"; #endif std::string etcboard; for( const DBTREE::ETCBOARDINFO& info : m_etcboards ) { etcboard += info.name + "\n"; if( info.basicauth.empty() ) etcboard += info.url + "\n"; else{ const std::size_t i = info.url.find( "://" ); if( i != std::string::npos ){ etcboard += info.url.substr( 0, i+3 ) + info.basicauth + "@" + info.url.substr( i+3 ) + "\n"; } else etcboard += info.url + "\n"; } etcboard += info.boardid + "\n"; } std::string file_etctxt = CACHE::path_etcboard(); if( ! CACHE::save_rawdata( file_etctxt, etcboard ) ){ MISC::ERRMSG( "failed to save " + file_etctxt ); } // BASIC認証のパスワード対策 else chmod( file_etctxt.c_str(), S_IWUSR | S_IRUSR ); #ifdef _DEBUG std::cout << etcboard << std::endl; #endif } // // 移転テーブル読み込み // void Root::load_movetable() { #ifdef _DEBUG std::cout << "Root::load_movetable\n"; #endif std::string file_move = CACHE::path_movetable(); std::string movetable_rawdata; if( CACHE::load_rawdata( file_move, movetable_rawdata ) ){ std::list< std::string > list_table = MISC::get_lines( movetable_rawdata ); for( const std::string& line : list_table ) { std::list tokens = MISC::split_line( line ); if( tokens.size() == 3 // 旧形式 || tokens.size() == 4 ){ auto move_it = std::make_move_iterator( tokens.begin() ); MOVETABLE movetable; movetable.old_root = *(move_it++); movetable.new_root = *(move_it++); movetable.old_path_board = *(move_it++); if( tokens.size() == 4 ) movetable.new_path_board = *(move_it++); else movetable.new_path_board = movetable.old_path_board; m_movetable.push_back( movetable ); } } } #ifdef _DEBUG std::cout << "MOVETABLE : \n"; for( const MOVETABLE& t : m_movetable ) { std::cout << t.old_root << t.old_path_board << " -> " << t.new_root << t.new_path_board << std::endl; } #endif } // // 板が移転したかチェックする // // 移転した時は移転後のURLを返す // std::string Root::is_board_moved( const std::string& url ) // 簡易版 { std::string old_root; std::string old_path_board; std::string new_root; std::string new_path_board; return is_board_moved( url, old_root, old_path_board, new_root, new_path_board ); } std::string Root::is_board_moved( const std::string& url, std::string& old_root, std::string& old_path_board, std::string& new_root, std::string& new_path_board, const int count ) { const int max_count = 50; #ifdef _DEBUG std::cout << "Root::is_board_moved count = " << count << " url = " << url << std::endl; #endif // 移転テーブルが循環している場合 2ch 型ならテーブルを修復する if( count > max_count ){ if( is_2ch( url ) ){ // 板パスを見て一致したら移転したと見なす // TODO : 板パスが同じ板が2つ以上あるときどうするか? const auto match_path = [&url]( const auto& b ) { return is_2ch( b->get_root() ) && url.find( b->get_path_board() + "/" ) != std::string::npos; }; // 板の最新のrootとpathを取得する auto it_board = std::find_if( m_list_board.cbegin(), m_list_board.cend(), match_path ); if( it_board != m_list_board.cend() ) { const BoardBase* board = it_board->get(); std::string msg = "移転テーブルが破損していたので修復しました\n"; for( auto it = m_movetable.begin(); it != m_movetable.end(); ) { if( is_2ch( it->old_root ) && url.find( it->old_path_board + "/" ) != std::string::npos ) { // 最新のrootとpathに変更する it->new_root = board->get_root(); it->new_path_board = board->get_path_board(); const std::string from_to = it->old_root + it->old_path_board + "/ -> " + board->url_boardbase() + "\n"; // url -> url の形となった場合は消す if( it->old_root == it->new_root && it->old_path_board == it->new_path_board ) { msg.append( "削除: " ); msg.append( from_to ); it = m_movetable.erase( it ); continue; } msg.append( "更新: " ); msg.append( from_to ); } ++it; } MISC::MSG( msg ); if( m_enable_save_movetable ){ //移転テーブル保存 save_movetable(); // サイドバーに登録されているURL更新 CORE::core_set_command( "update_sidebar_item" ); } // 改めてもう一度実行 return is_board_moved( url, old_root, old_path_board, new_root, new_path_board, 0 ); } } return std::string(); } // 移転テーブルを検索 for( const MOVETABLE& table : m_movetable ) { if( url.rfind( table.old_root, 0 ) == 0 && url.find( table.old_path_board + "/" ) != std::string::npos ){ const std::string new_url = table.new_root + table.new_path_board + "/"; old_root = table.old_root; old_path_board = table.old_path_board; new_root = table.new_root; new_path_board = table.new_path_board; std::string old_root_bkup = old_root; std::string old_path_board_bkup = old_path_board; #ifdef _DEBUG std::cout << "count = " << count << " : " << url << " is moved to " << new_url << std::endl; #endif // 連鎖的に検索 std::string ret_url = is_board_moved( new_url, old_root, old_path_board, new_root, new_path_board, count +1 ); // old_root と old_path_board が書き換わっているので戻しておく // // (注意) もし再帰呼び出ししたis_board_moved() の中で m_movetable.erase を実行すると // table は無効になるので、以前の様に // old_root = table.old_root; // old_path_board = table.old_path_board; // と table を使ってold_rootなどに代入するとメモリを破壊してセグフォになる時がある // (イテレータのメモリが残っていればセグフォにならないので完全に運まかせ) // 次にJDを起動したときは再帰呼び出ししたis_board_moved() の中の save_movetable()で // 移転テーブルは更新済みで落ちないのでタチが悪い old_root = std::move( old_root_bkup ); old_path_board = std::move( old_path_board_bkup ); return ret_url; } } if( count ) return url; // 再起呼び出しの場合 return std::string(); } // 全板の情報ファイル読み込み void Root::read_boardinfo_all() { for( auto& b : m_list_board ) b->read_info(); } // 全スレ情報の保存 void Root::save_articleinfo_all() { #ifdef _DEBUG std::cout << "Root::save_articleinfo_all\n"; #endif for( auto& b : m_list_board ) b->save_articleinfo_all(); #ifdef _DEBUG std::cout << "end\n"; #endif } // 全ログ検索 void Root::search_cache( std::vector< ArticleBase* >& list_article, const std::string& query, const bool mode_or, const bool bm, const bool stop ) { for( auto& b : m_list_board ) { b->search_cache( list_article, query, mode_or, bm, stop ); if( stop ) break; } } // 全てのスレの書き込み履歴削除 void Root::clear_all_post_history() { for( auto& b : m_list_board ) b->clear_all_post_history(); } // // 移転テーブル保存 // void Root::save_movetable() { #ifdef _DEBUG std::cout << "Root::save_movetable\n"; #endif std::string file_move = CACHE::path_movetable(); std::ostringstream movetable; for( const MOVETABLE& t : m_movetable ) { movetable << t.old_root << " " << t.new_root << " " << t.old_path_board; // 新形式 if( ! t.new_path_board.empty() && t.old_path_board != t.new_path_board ) movetable << " " << t.new_path_board; movetable << std::endl; } #ifdef _DEBUG std::cout << movetable.str(); #endif CACHE::save_rawdata( file_move, movetable.str() ); } // // 2ch型のURLかどうか // bool Root::is_2ch( const std::string& url ) { const std::string hostname = MISC::get_hostname( url ); if( ( hostname.find( ".2ch.net" ) != std::string::npos && hostname.find( "info.2ch.net" ) == std::string::npos ) || ( hostname.find( ".5ch.net" ) != std::string::npos && hostname.find( "info.5ch.net" ) == std::string::npos ) || hostname.find( ".bbspink.com" ) != std::string::npos ) return true; return false; } // // JBBS型のURLかどうか // bool Root::is_JBBS( const std::string& url ) { const std::string hostname = MISC::get_hostname( url ); if( hostname.find( "jbbs.livedoor.jp" ) != std::string::npos || hostname.find( "jbbs.shitaraba.com" ) != std::string::npos || hostname.find( "jbbs.shitaraba.net" ) != std::string::npos ) return true; return false; } // // まち型のURLかどうか // bool Root::is_machi( const std::string& url ) { const std::string hostname = MISC::get_hostname( url ); if( hostname.find( ".machi.to" ) != std::string::npos ) return true; return false; } // // vipサービスのURLか // bool Root::is_vip2ch( const std::string& url ) { const std::string hostname = MISC::get_hostname( url ); if( hostname.find( ".vip2ch.com" ) != std::string::npos ) return true; return false; } // // ローカルファイルか // bool Root::is_local( const std::string& url ) { if( url.find( "file://" ) != std::string::npos ) return true; return false; } // // スレあぼーん情報を更新した時、全boardbaseクラスに対応するスレ一覧の表示を更新させる // // CONFIG::set_abone_number_thread() などでグローバル設定をした後などに呼び出す // void Root::update_abone_thread() { for( auto& b : m_list_board ) b->update_abone_thread( true ); } // // 全boardbaseクラスに、それに属する全articlebaseクラスのあぼーん状態の更新をさせる // void Root::update_abone_all_article() { for( auto& b : m_list_board ) b->update_abone_all_article(); } // // 全boardbaseクラスに、それに属する全articlebaseクラスの書き込み時間とスレ立て時間の文字列をリセットさせる // void Root::reset_all_since_date() { for( auto& b : m_list_board ) b->reset_all_since_date(); } void Root::reset_all_write_date() { for( auto& b : m_list_board ) b->reset_all_write_date(); } void Root::reset_all_access_date() { for( auto& b : m_list_board ) b->reset_all_access_date(); } jdim-0.7.0/src/dbtree/root.h000066400000000000000000000166051417047150700156470ustar00rootroot00000000000000// ライセンス: GPL2 // データベースのルートクラス // // クラス図 [ Root ] ---> [ BoardBase ] ---> [ ArticleBase ] ---> [ NodeTreeBase ] // #ifndef _ROOT_H #define _ROOT_H #include "etcboardinfo.h" #include "skeleton/loadable.h" #include "xml/document.h" #include #include #include #include namespace DBTREE { class BoardBase; class ArticleBase; // サーバ移転テーブル // // (1) bbsmenuを読み込んで移転していた場合( Root::set_board() ) // // 現在のホストを新ホストに移動する。キャッシュも移動する // // (2) 参照しようとした板が無かった時( Root::get_board() ) // // 参照した古いホストの移動先を現在のホストに設定する。キャッシュは移動しない // // (3) ある板のsubject.txtを読み込んだときにHTTP_REDIRECTが戻ってきて // BoardBase::start_checkking_if_board_moved()により移転が確認されたとき // // 現在のホストを新ホストに移動する。キャッシュも移動する // struct MOVETABLE { std::string old_root; std::string new_root; std::string old_path_board; std::string new_path_board; }; class Root : public SKELETON::Loadable { // Boardクラス のキャッシュ // Boardクラスは一度作ったら~Root()以外ではdeleteしないこと std::list> m_list_board; // 鯖移転テーブル std::list< MOVETABLE > m_movetable; XML::Document m_xml_document; std::string m_rawdata; std::list< DBTREE::ETCBOARDINFO > m_etcboards; // 外部板情報 // 移転処理用変数 bool m_analyzing_board_xml{}; // XML 解析中 std::set< std::string > m_analyzed_path_board; // XML 解析中に処理済みの板のpath std::string m_move_info; // 移転したときにダイアログに表示する移転済み板の一覧 // Null board クラス std::unique_ptr m_board_null; // get_board()のキャッシュ // get_article_fromURL()のキャッシュ std::string m_get_board_url; BoardBase* m_get_board{}; bool m_enable_save_movetable; public: Root(); ~Root(); // 板一覧のxml const XML::Document& xml_document() const { return m_xml_document; } // 板移転 bool move_board( const std::string& url_old, const std::string& url_new, const bool etc ); // 外部板情報取得 const std::list< DBTREE::ETCBOARDINFO >& get_etcboards() const { return m_etcboards; } // 外部板追加 bool add_etc( const std::string& url, const std::string& name, const std::string& basicauth, const std::string& id ); // 外部板更新 bool move_etc( const std::string& url_old, const std::string& url_new, const std::string& name_old, const std::string& name_new, const std::string& basicauth, const std::string& boardid ); // 外部板削除 bool remove_etc( const std::string& url, const std::string& name ); // 外部板情報保存 void save_etc(); // Board クラスのポインタ取得 BoardBase* get_board( const std::string& url, const int count = 0 ); // bbsmenuのダウンロード void download_bbsmenu(); // スレあぼーん情報を更新した時、全boardbaseクラスに対応するスレ一覧の表示を更新させる // CONFIG::set_abone_number_thread() などでグローバル設定をした後などに呼び出す void update_abone_thread(); // 全boardbaseクラスに、それに属する全articlebaseクラスのあぼーん状態の更新をさせる void update_abone_all_article(); // 全boardbaseクラスに、それに属する全articlebaseクラスの書き込み時間とスレ立て時間の文字列をリセットさせる void reset_all_since_date(); void reset_all_write_date(); void reset_all_access_date(); // 板が移転したかチェックする // 移転した時は移転後のURLを返す std::string is_board_moved( const std::string& url ); // 簡易版 std::string is_board_moved( const std::string& url, std::string& old_root, std::string& old_path_board, std::string& new_root, std::string& new_path_board, const int count = 0 ); // 移転情報保存の有効切り替え void set_enable_save_movetable( const bool set ){ m_enable_save_movetable = set; } // 移転情報保存 void save_movetable(); // 全板の情報ファイル読み込み void read_boardinfo_all(); // 全スレ情報の保存 void save_articleinfo_all(); // 全ログ検索 void search_cache( std::vector< ArticleBase* >& list_article, const std::string& query, const bool mode_or, const bool bm, const bool stop ); // 全てのスレの書き込み履歴削除 void clear_all_post_history(); private: // bbsmenuのダウンロード用関数 void clear(); void receive_data( const char* data, size_t size ) override; void receive_finish() override; void bbsmenu2xml( const std::string& menu ); // XML に含まれる板情報を取り出してデータベースを更新 void analyze_board_xml(); // 板のタイプを判定 int get_board_type( const std::string& url, std::string& root, std::string& path_board, const bool etc ) const; int get_board_type( const std::string& root, const bool etc = false ) const; // 板のタイプに合わせて板情報をセット bool set_board( const std::string& url, const std::string& name, const std::string& basicauth = std::string(), bool etc = false ); // 板移転処理 bool exec_move_board( BoardBase* board, std::string old_root, std::string old_path_board, std::string new_root, std::string new_path_board ); // 板移転テーブルに追加 void push_movetable( std::string old_root, std::string old_path_board, std::string new_root, std::string new_path_board ); // 板をデータベースから削除 bool remove_board( const std::string& url ); int is_moved( const std::string& root, const std::string& path_board, const std::string& name, BoardBase** board_old, bool etc ); void load_cache(); void load_etc(); void load_movetable(); // urlのタイプ判定 static bool is_2ch( const std::string& url ); static bool is_JBBS( const std::string& url ); static bool is_machi( const std::string& url ); static bool is_vip2ch( const std::string& url ); static bool is_local( const std::string& url ); }; } #endif jdim-0.7.0/src/dbtree/ruleloader.cpp000066400000000000000000000044641417047150700173550ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "ruleloader.h" #include "interface.h" #include "jdlib/confloader.h" #include "jdlib/loaderdata.h" #include "config/globalconf.h" #include "cache.h" #define HEAD_TXT "head.txt" using namespace DBTREE; RuleLoader::RuleLoader( const std::string& url_boadbase, const char* override_charset ) : SKELETON::TextLoader() , m_url_boadbase( url_boadbase ) , m_override_charset{ override_charset } { #ifdef _DEBUG std::cout << "RuleLoader::RuleLoader : " << RuleLoader::get_url() << std::endl; #endif set_date_modified( DBTREE::board_get_modified_localrule( m_url_boadbase ) ); } RuleLoader::~RuleLoader() { #ifdef _DEBUG std::cout << "RuleLoader::~RuleLoader : " << RuleLoader::get_url() << std::endl; #endif } std::string RuleLoader::get_url() const { return m_url_boadbase + HEAD_TXT; } std::string RuleLoader::get_path() const { return CACHE::path_board_root( m_url_boadbase ) + HEAD_TXT; } std::string RuleLoader::get_charset() const { return m_override_charset ? m_override_charset : DBTREE::board_charset( m_url_boadbase ); } // ロード用データ作成 void RuleLoader::create_loaderdata( JDLIB::LOADERDATA& data ) { if( !CACHE::mkdir_boardroot( m_url_boadbase ) ) data.url = std::string(); else{ // 移転処理 m_url_boadbase = DBTREE::url_boardbase( m_url_boadbase ); data.url = get_url(); data.agent = DBTREE::get_agent( m_url_boadbase ); data.host_proxy = DBTREE::get_proxy_host( m_url_boadbase ); data.port_proxy = DBTREE::get_proxy_port( m_url_boadbase ); data.basicauth_proxy = DBTREE::get_proxy_basicauth( m_url_boadbase ); data.size_buf = CONFIG::get_loader_bufsize(); data.timeout = CONFIG::get_loader_timeout(); if( ! get_date_modified().empty() ) data.modified = get_date_modified(); data.basicauth = DBTREE::board_basicauth( m_url_boadbase ); data.cookie_for_request = DBTREE::board_cookie_for_request( m_url_boadbase ); } } // ロード後に呼び出される void RuleLoader::parse_data() { DBTREE::board_set_modified_localrule( m_url_boadbase, get_date_modified() ); } void RuleLoader::receive_cookies() { DBTREE::board_set_list_cookies( m_url_boadbase, SKELETON::Loadable::cookies() ); } jdim-0.7.0/src/dbtree/ruleloader.h000066400000000000000000000021311417047150700170070ustar00rootroot00000000000000// ライセンス: GPL2 // // 2chのローカルルールのローダ // #ifndef _RULELOADER_H #define _RULELOADER_H #include "skeleton/textloader.h" #include namespace JDLIB { class LOADERDATA; } namespace DBTREE { class RuleLoader : public SKELETON::TextLoader { std::string m_url_boadbase; // スレ本文とエンコーディングが異なる板があるため指定可能にする // 文字列の寿命は呼び出し元が責任を持つこと const char* m_override_charset; public: explicit RuleLoader( const std::string& url_boardbase, const char* override_charset = nullptr ); ~RuleLoader(); protected: std::string get_url() const override; std::string get_path() const override; std::string get_charset() const override; // ロード用データ作成 void create_loaderdata( JDLIB::LOADERDATA& data ) override; // ロード後に呼び出される void parse_data() override; private: void receive_cookies() override; }; } #endif jdim-0.7.0/src/dbtree/settingloader.cpp000066400000000000000000000065211417047150700200570ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "settingloader.h" #include "interface.h" #include "jdlib/confloader.h" #include "jdlib/loaderdata.h" #include "config/globalconf.h" #include "cache.h" using namespace DBTREE; SettingLoader::SettingLoader( const std::string& url_boadbase ) : SKELETON::TextLoader() , m_url_boadbase( url_boadbase ) { #ifdef _DEBUG std::cout << "SettingLoader::SettingLoader : " << m_url_boadbase << std::endl; #endif set_date_modified( DBTREE::board_get_modified_setting( m_url_boadbase ) ); } SettingLoader::~SettingLoader() { #ifdef _DEBUG std::cout << "SettingLoader::~SettingLoader : " << m_url_boadbase << std::endl; #endif } std::string SettingLoader::get_url() const { return DBTREE::url_settingtxt( m_url_boadbase ); } std::string SettingLoader::get_path() const { return CACHE::path_board_root( m_url_boadbase ) + DBTREE::kSettingTxt; } std::string SettingLoader::get_charset() const { return DBTREE::board_charset( m_url_boadbase ); } // ロード用データ作成 void SettingLoader::create_loaderdata( JDLIB::LOADERDATA& data ) { if( !CACHE::mkdir_boardroot( m_url_boadbase ) ) data.url = std::string(); else{ // 移転処理 m_url_boadbase = DBTREE::url_boardbase( m_url_boadbase ); data.url = get_url(); data.agent = DBTREE::get_agent( m_url_boadbase ); data.host_proxy = DBTREE::get_proxy_host( m_url_boadbase ); data.port_proxy = DBTREE::get_proxy_port( m_url_boadbase ); data.basicauth_proxy = DBTREE::get_proxy_basicauth( m_url_boadbase ); data.size_buf = CONFIG::get_loader_bufsize(); data.timeout = CONFIG::get_loader_timeout(); if( ! get_date_modified().empty() ) data.modified = get_date_modified(); data.basicauth = DBTREE::board_basicauth( m_url_boadbase ); data.cookie_for_request = DBTREE::board_cookie_for_request( m_url_boadbase ); } } // ロード後に呼び出される void SettingLoader::parse_data() { JDLIB::ConfLoader cf( "", get_data() ); m_default_noname = cf.get_option_str( "BBS_NONAME_NAME", "No Name" ); m_line_number = cf.get_option_int( "BBS_LINE_NUMBER", 0, 0, 8192 ); m_message_count = cf.get_option_int( "BBS_MESSAGE_COUNT", 0, 0, 81920 ); m_unicode = cf.get_option_str( "BBS_UNICODE", "" ); const int num_stop = cf.get_option_int( "BBS_THREAD_STOP", 0, 0, CONFIG::get_max_resnumber() ); if( num_stop ) { // 板設定の最大レス数がデフォルト(0:未設定)でないときは BBS_THREAD_STOP の値に上書きする const int max_res = DBTREE::board_get_number_max_res( m_url_boadbase ); if( !max_res && max_res != num_stop ) { DBTREE::board_set_number_max_res( m_url_boadbase, num_stop ); } } DBTREE::board_set_modified_setting( m_url_boadbase, get_date_modified() ); #ifdef _DEBUG std::cout << "SettingLoader::parse url = " << get_url() << std::endl << "default_noname = " << m_default_noname << std::endl << "line_number = " << m_line_number << std::endl << "message_count = " << m_message_count << std::endl << "unicode = " << m_unicode << std::endl; #endif } void SettingLoader::receive_cookies() { DBTREE::board_set_list_cookies( m_url_boadbase, SKELETON::Loadable::cookies() ); } jdim-0.7.0/src/dbtree/settingloader.h000066400000000000000000000027571417047150700175330ustar00rootroot00000000000000// ライセンス: GPL2 // // 2chのSETTING.TXTのローダ // #ifndef _SETTINGLOADER_H #define _SETTINGLOADER_H #include "skeleton/textloader.h" #include namespace JDLIB { class LOADERDATA; } namespace DBTREE { // 板設定のファイル名 constexpr const char kSettingTxt[] = "SETTING.TXT"; class SettingLoader : public SKELETON::TextLoader { std::string m_url_boadbase; // デフォルト名無し std::string m_default_noname; // 最大改行数/2 int m_line_number{}; // 最大書き込みバイト数 int m_message_count{}; // 特殊文字書き込み std::string m_unicode; public: explicit SettingLoader( const std::string& url_boardbase ); ~SettingLoader(); const std::string& default_noname() const { return m_default_noname; } int line_number() const noexcept { return m_line_number; } int message_count() const noexcept { return m_message_count; } const std::string& get_unicode() const { return m_unicode; } protected: std::string get_url() const override; std::string get_path() const override; std::string get_charset() const override; // ロード用データ作成 void create_loaderdata( JDLIB::LOADERDATA& data ) override; // ロード後に呼び出される void parse_data() override; private: void receive_cookies() override; }; } #endif jdim-0.7.0/src/dbtree/spchar_decoder.cpp000066400000000000000000000067671417047150700201740ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "spchar_decoder.h" #include "spchar_tbl.h" #include "node.h" #include "jdlib/miscutil.h" #include #include bool check_spchar( const char* n_in, const char* spchar ) { int i = 0; while( spchar[ i ] != '\0' ){ if( n_in[ i ] != spchar[ i ] ) return false; ++i; } return true; } // // ユニコード文字参照 &#数字; // // in_char: 入力文字列、in_char[1] == "#" であること // n_in : 入力で使用した文字数が返る // out_char : 出力文字列 // n_out : 出力した文字数が返る // only_check : チェックのみ実施 ( out_char は nullptr でも可 ) // // 戻り値 : node.h で定義したノード番号 // int decode_char_number( const char* in_char, int& n_in, char* out_char, int& n_out, const bool only_check ) { int ret = DBTREE::NODE_TEXT; n_in = n_out = 0; int offset; const int lng = MISC::spchar_number_ln( in_char, offset ); if( lng == -1 ) return DBTREE::NODE_NONE; if( only_check ) return ret; const int num = MISC::decode_spchar_number( in_char, offset, lng ); switch( num ){ //zwnj,zwj,lrm,rlm は今のところ無視(zwspにする) case UCS_ZWSP: case UCS_ZWNJ: case UCS_ZWJ: case UCS_LRM: case UCS_RLM: ret = DBTREE::NODE_ZWSP; break; // U+2028 LINE SEPARATOR を描画処理に渡すと改行が乱れるため空白に置き換える (webブラウザと同じ挙動) case CP_LINE_SEPARATOR: out_char[0] = ' '; n_out = 1; break; default: n_out = MISC::ucs2toutf8( num, out_char ); if( ! n_out ) return DBTREE::NODE_NONE; } n_in = offset + lng; if( in_char[n_in] == ';' ) n_in++; // 数値文字参照の終端「;」の場合は1文字削除 if( out_char ) out_char[ n_out ] = '\0'; return ret; } // // 文字参照のデコード // // in_char : 入力文字列, in_char[ 0 ] = '&' となっていること // n_in : 入力で使用した文字数が返る // out_char : 出力文字列 // n_out : 出力した文字数が返る // only_check : チェックのみ実施 ( out_char は nullptr でも可 ) // // 戻り値 : node.h で定義したノード番号 // int DBTREE::decode_char( const char* in_char, int& n_in, char* out_char, int& n_out, const bool only_check ) { // 1文字目が&以外の場合は出力しない if( in_char[ 0 ] != '&' ){ n_in = n_out = 0; if( out_char ) out_char[ n_out ] = '\0'; return DBTREE::NODE_NONE; } // 数字参照 &#数字; if( in_char[ 1 ] == '#' ) return decode_char_number( in_char, n_in, out_char, n_out, only_check ); // 文字参照 -> ユニコード変換 int ret = DBTREE::NODE_TEXT; n_in = n_out = 0; for( const UCSTBL& t : ucstbl ) { const int ucs = t.ucs; if( ! ucs ) break; if( in_char[1] == t.str[0] && check_spchar( in_char + 1, t.str ) ) { if( only_check ) return ret; n_in = std::strlen( t.str ) + 1; // zwnj, zwj, lrm, rlm は今のところ無視する(zwspにする) if( ucs >= UCS_ZWSP && ucs <= UCS_RLM ) ret = DBTREE::NODE_ZWSP; else n_out = MISC::ucs2toutf8( ucs, out_char ); break; } } if( !n_in ) ret = DBTREE::NODE_NONE; if( out_char ) out_char[ n_out ] = '\0'; return ret; } jdim-0.7.0/src/dbtree/spchar_decoder.h000066400000000000000000000012251417047150700176210ustar00rootroot00000000000000// ライセンス: GPL2 // // 特殊HTML文字のデコード関数 // #ifndef _SPCHAR_DECODER_H #define _SPCHAR_DECODER_H namespace DBTREE { // 文字参照のデコード // in_char : 入力文字列, in_char[ 0 ] = '&' となっていること // n_in : 入力で使用した文字数が返る // out_char : 出力文字列 // n_out : 出力した文字数が返る // only_check : チェックのみ実施 ( out_char は nullptr でも可 ) // // 戻り値 : node.h で定義したノード番号 // int decode_char( const char* in_char, int& n_in, char* out_char, int& n_out, const bool only_check ); } #endif jdim-0.7.0/src/dbtree/spchar_tbl.h000066400000000000000000000142011417047150700167730ustar00rootroot00000000000000// ライセンス: GPL2 // // ユニコード(ucs2) <-> 文字参照変換テーブル // #ifndef _SPCHAR_TBL_H #define _SPCHAR_TBL_H struct UCSTBL { int ucs; char str[ 256 ]; }; UCSTBL ucstbl[] = { { 34, "quot;" }, { 38, "amp;" }, { 60, "lt;" }, { 62, "gt;" }, { 32, "nbsp;" }, // { 160, "nbsp;" }, // 正しくはこちら { 161, "iexcl;" }, { 162, "cent;" }, { 163, "pound;" }, { 164, "curren;" }, { 165, "yen;" }, { 166, "brvbar;" }, { 167, "sect;" }, { 168, "uml;" }, { 169, "copy;" }, { 170, "ordf;" }, { 171, "laquo;" }, { 172, "not;" }, { 173, "shy;" }, { 174, "reg;" }, { 175, "macr;" }, { 176, "deg;" }, { 177, "plusmn;" }, { 178, "sup2;" }, { 179, "sup3;" }, { 180, "acute;" }, { 181, "micro;" }, { 182, "para;" }, { 183, "middot;" }, { 184, "cedil;" }, { 185, "sup1;" }, { 186, "ordm;" }, { 187, "raquo;" }, { 188, "frac14;" }, { 189, "frac12;" }, { 190, "frac34;" }, { 191, "iquest;" }, { 192, "Agrave;" }, { 193, "Aacute;" }, { 194, "Acirc;" }, { 195, "Atilde;" }, { 196, "Auml;" }, { 197, "Aring;" }, { 198, "AElig;" }, { 199, "Ccedil;" }, { 200, "Egrave;" }, { 201, "Eacute;" }, { 202, "Ecirc;" }, { 203, "Euml;" }, { 204, "Igrave;" }, { 205, "Iacute;" }, { 206, "Icirc;" }, { 207, "Iuml;" }, { 208, "ETH;" }, { 209, "Ntilde;" }, { 210, "Ograve;" }, { 211, "Oacute;" }, { 212, "Ocirc;" }, { 213, "Otilde;" }, { 214, "Ouml;" }, { 215, "times;" }, { 216, "Oslash;" }, { 217, "Ugrave;" }, { 218, "Uacute;" }, { 219, "Ucirc;" }, { 220, "Uuml;" }, { 221, "Yacute;" }, { 222, "THORN;" }, { 223, "szlig;" }, { 224, "agrave;" }, { 225, "aacute;" }, { 226, "acirc;" }, { 227, "atilde;" }, { 228, "auml;" }, { 229, "aring;" }, { 230, "aelig;" }, { 231, "ccedil;" }, { 232, "egrave;" }, { 233, "eacute;" }, { 234, "ecirc;" }, { 235, "euml;" }, { 236, "igrave;" }, { 237, "iacute;" }, { 238, "icirc;" }, { 239, "iuml;" }, { 240, "eth;" }, { 241, "ntilde;" }, { 242, "ograve;" }, { 243, "oacute;" }, { 244, "ocirc;" }, { 245, "otilde;" }, { 246, "ouml;" }, { 247, "divide;" }, { 248, "oslash;" }, { 249, "ugrave;" }, { 250, "uacute;" }, { 251, "ucirc;" }, { 252, "uuml;" }, { 253, "yacute;" }, { 254, "thorn;" }, { 255, "yuml;" }, { 338, "OElig;" }, { 339, "oelig;" }, { 352, "Scaron;" }, { 353, "scaron;" }, { 376, "Yuml;" }, { 402, "fnof;" }, { 710, "circ;" }, { 732, "tilde;" }, { 913, "Alpha;" }, { 914, "Beta;" }, { 915, "Gamma;" }, { 916, "Delta;" }, { 917, "Epsilon;" }, { 918, "Zeta;" }, { 919, "Eta;" }, { 920, "Theta;" }, { 921, "Iota;" }, { 922, "Kappa;" }, { 923, "Lambda;" }, { 924, "Mu;" }, { 925, "Nu;" }, { 926, "Xi;" }, { 927, "Omicron;" }, { 928, "Pi;" }, { 929, "Rho;" }, { 931, "Sigma;" }, { 932, "Tau;" }, { 933, "Upsilon;" }, { 934, "Phi;" }, { 935, "Chi;" }, { 936, "Psi;" }, { 937, "Omega;" }, { 945, "alpha;" }, { 946, "beta;" }, { 947, "gamma;" }, { 948, "delta;" }, { 949, "epsilon;" }, { 950, "zeta;" }, { 951, "eta;" }, { 952, "theta;" }, { 953, "iota;" }, { 954, "kappa;" }, { 955, "lambda;" }, { 956, "mu;" }, { 957, "nu;" }, { 958, "xi;" }, { 959, "omicron;" }, { 960, "pi;" }, { 961, "rho;" }, { 962, "sigmaf;" }, { 963, "sigma;" }, { 964, "tau;" }, { 965, "upsilon;" }, { 966, "phi;" }, { 967, "chi;" }, { 968, "psi;" }, { 969, "omega;" }, { 977, "thetasym;" }, { 978, "upsih;" }, { 982, "piv;" }, { 8194, "ensp;" }, { 8195, "emsp;" }, { 8201, "thinsp;" }, { 8203, "zwsp;" }, { 8204, "zwnj;" }, { 8205, "zwj;" }, { 8206, "lrm;" }, { 8207, "rlm;" }, { 8211, "ndash;" }, { 8212, "mdash;" }, { 8216, "lsquo;" }, { 8217, "rsquo;" }, { 8218, "sbquo;" }, { 8220, "ldquo;" }, { 8221, "rdquo;" }, { 8222, "bdquo;" }, { 8224, "dagger;" }, { 8225, "Dagger;" }, { 8226, "bull;" }, { 8230, "hellip;" }, { 8240, "permil;" }, { 8242, "prime;" }, { 8243, "Prime;" }, { 8249, "lsaquo;" }, { 8250, "rsaquo;" }, { 8254, "oline;" }, { 8260, "frasl;" }, { 8364, "euro;" }, { 8465, "image;" }, { 8472, "weierp;" }, { 8476, "real;" }, { 8482, "trade;" }, { 8501, "alefsym;" }, { 8592, "larr;" }, { 8593, "uarr;" }, { 8594, "rarr;" }, { 8595, "darr;" }, { 8596, "harr;" }, { 8629, "crarr;" }, { 8656, "lArr;" }, { 8657, "uArr;" }, { 8658, "rArr;" }, { 8659, "dArr;" }, { 8660, "hArr;" }, { 8704, "forall;" }, { 8706, "part;" }, { 8707, "exist;" }, { 8709, "empty;" }, { 8711, "nabla;" }, { 8712, "isin;" }, { 8713, "notin;" }, { 8715, "ni;" }, { 8719, "prod;" }, { 8721, "sum;" }, { 8722, "minus;" }, { 8727, "lowast;" }, { 8730, "radic;" }, { 8733, "prop;" }, { 8734, "infin;" }, { 8736, "ang;" }, { 8743, "and;" }, { 8744, "or;" }, { 8745, "cap;" }, { 8746, "cup;" }, { 8747, "int;" }, { 8756, "there4;" }, { 8764, "sim;" }, { 8773, "cong;" }, { 8776, "asymp;" }, { 8800, "ne;" }, { 8801, "equiv;" }, { 8804, "le;" }, { 8805, "ge;" }, { 8834, "sub;" }, { 8835, "sup;" }, { 8836, "nsub;" }, { 8838, "sube;" }, { 8839, "supe;" }, { 8853, "oplus;" }, { 8855, "otimes;" }, { 8869, "perp;" }, { 8901, "sdot;" }, { 8968, "lceil;" }, { 8969, "rceil;" }, { 8970, "lfloor;" }, { 8971, "rfloor;" }, { 9001, "lang;" }, { 9002, "rang;" }, { 9674, "loz;" }, { 9824, "spades;" }, { 9827, "clubs;" }, { 9829, "hearts;" }, { 9830, "diams;" }, { 0, "" } // 終端 }; enum { UCS_ZWSP = 8203, UCS_ZWNJ = 8204, UCS_ZWJ = 8205, UCS_LRM = 8206, UCS_RLM = 8207, CP_LINE_SEPARATOR = 8232, }; #endif jdim-0.7.0/src/dispatchmanager.cpp000066400000000000000000000050731417047150700171010ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "dispatchmanager.h" #include "skeleton/dispatchable.h" #include #include static std::mutex dispatch_mutex; CORE::DispatchManager* instance_dispmanager = nullptr; CORE::DispatchManager* CORE::get_dispmanager() { if( ! instance_dispmanager ) instance_dispmanager = new CORE::DispatchManager(); return instance_dispmanager; } void CORE::delete_dispatchmanager() { if( instance_dispmanager ) delete instance_dispmanager; instance_dispmanager = nullptr; } ////////////////////// using namespace CORE; DispatchManager::DispatchManager() { #ifdef _DEBUG std::cout << "DispatchManager::DispatchManager\n"; #endif m_dispatch.connect( sigc::mem_fun( *this, &DispatchManager::slot_dispatch ) ); } DispatchManager::~DispatchManager() { #ifdef _DEBUG std::cout << "DispatchManager::~DispatchManager size = " << m_children.size() << std::endl; #endif } void DispatchManager::add( SKELETON::Dispatchable* child ) { std::lock_guard< std::mutex > lock( dispatch_mutex ); // 既にlistに登録されていたらキャンセルする if( std::find( m_children.cbegin(), m_children.cend(), child ) != m_children.cend() ) { #ifdef _DEBUG std::cout << "DispatchManager::add canceled\n"; #endif return; } m_children.push_back( child ); m_dispatch.emit(); #ifdef _DEBUG std::cout << "DispatchManager::add size = " << m_children.size() << std::endl; #endif } void DispatchManager::remove( SKELETON::Dispatchable* child ) { std::lock_guard< std::mutex > lock( dispatch_mutex ); size_t size = m_children.size(); if( ! size ) return; m_children.remove( child ); #ifdef _DEBUG if( size != m_children.size() ) std::cout << "!!!!!!!\nDispatchManager::remove size " << size << " -> " << m_children.size() << "\n!!!!!!!\n"; #endif } void DispatchManager::slot_dispatch() { std::unique_lock< std::mutex > lock( dispatch_mutex ); const size_t size = m_children.size(); if( ! size ) return; SKELETON::Dispatchable* child = *( m_children.begin() ); // child->callback_dispatch()の中で再び Dispatchable::add()が呼び出されると // キャンセルされてしまうので callback_dispatch() を呼び出す前にremoveする m_children.remove( child ); lock.unlock(); if( child ) child->callback_dispatch(); #ifdef _DEBUG std::cout << "DispatchManager::slot_dispatch size = " << size << " -> " << m_children.size() << std::endl; #endif } jdim-0.7.0/src/dispatchmanager.h000066400000000000000000000014671417047150700165510ustar00rootroot00000000000000// ライセンス: GPL2 // // Dispatchの管理クラス // // Glib::Dispatcher::emit()後に呼出先がdeleteされると segmentation fault で落ちるので // Dispatchを一元管理して安全にDispatchする // #ifndef _DISPATCHMANAGER_H #define _DISPATCHMANAGER_H #include #include namespace SKELETON { class Dispatchable; } namespace CORE { class DispatchManager { Glib::Dispatcher m_dispatch; std::list< SKELETON::Dispatchable* > m_children; public: DispatchManager(); virtual ~DispatchManager(); void add( SKELETON::Dispatchable* child ); void remove( SKELETON::Dispatchable* child ); void slot_dispatch(); }; CORE::DispatchManager* get_dispmanager(); void delete_dispatchmanager(); } #endif jdim-0.7.0/src/dndmanager.cpp000066400000000000000000000015401417047150700160420ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "dndmanager.h" CORE::DND_Manager* instance_dnd_manager = nullptr; CORE::DND_Manager* CORE::get_dnd_manager() { if( ! instance_dnd_manager ) instance_dnd_manager = new DND_Manager(); assert( instance_dnd_manager ); return instance_dnd_manager; } void CORE::delete_dnd_manager() { if( instance_dnd_manager ) delete instance_dnd_manager; instance_dnd_manager = nullptr; } void CORE::DND_Begin() { CORE::DND_Manager* manager = CORE::get_dnd_manager(); if( manager ) manager->begin(); } void CORE::DND_End() { CORE::DND_Manager* manager = CORE::get_dnd_manager(); if( manager ) manager->end(); } bool CORE::DND_Now_dnd() { CORE::DND_Manager* manager = CORE::get_dnd_manager(); if( manager ) return manager->now_dnd(); return false; } jdim-0.7.0/src/dndmanager.h000066400000000000000000000016551417047150700155160ustar00rootroot00000000000000// ライセンス: GPL2 // // ドラッグ&ドロップの管理クラス // #ifndef _DNDMANAGER_H #define _DNDMANAGER_H #include #include // ターゲット #define DNDTARGET_FAVORITE "dnd/favorite" #define DNDTARGET_TAB "dnd/tab" #define DNDTARGET_IMAGETAB "dnd/imagetab" #define DNDTARGET_USRCMD "dnd/usrcmd" namespace CORE { class DND_Manager { // true ならd&d中 bool m_dnd{}; public: DND_Manager() noexcept = default; virtual ~DND_Manager() noexcept = default; bool now_dnd() const { return m_dnd; } // DnD 開始 void begin(){ m_dnd = true; } // DnD終了 void end(){ m_dnd = false; } }; /////////////////////////////////////// // インターフェース DND_Manager* get_dnd_manager(); void delete_dnd_manager(); void DND_Begin(); void DND_End(); bool DND_Now_dnd(); } #endif jdim-0.7.0/src/environment.cpp000066400000000000000000000360371417047150700163170ustar00rootroot00000000000000// License GPL2 //#define _DEBUG #include "jddebug.h" #include "environment.h" #include "cache.h" #include "config/globalconf.h" #include "jdversion.h" #include "jdlib/miscutil.h" #include #if __has_include() #define HAVE_SYS_UTSNAME_H #include // uname() #endif #if defined(USE_GNUTLS) # include #elif defined(USE_OPENSSL) # include #endif #include #include #include #include std::string ENVIRONMENT::get_progname() { return "JDim"; } std::string ENVIRONMENT::get_jdcomments(){ return std::string( JDCOMMENT ); } std::string ENVIRONMENT::get_jdcopyright(){ return std::string( JDCOPYRIGHT ); } std::string ENVIRONMENT::get_jdbbs(){ return std::string( JDBBS ); } std::string ENVIRONMENT::get_jd2chlog(){ return std::string( JD2CHLOG ); } std::string ENVIRONMENT::get_jdhelp(){ return std::string( JDHELP ); } std::string ENVIRONMENT::get_jdhelpcmd(){ return std::string( JDHELPCMD ); } std::string ENVIRONMENT::get_jdhelpreplstr() { return JDHELPREPLSTR; } std::string ENVIRONMENT::get_jdlicense(){ return std::string( JDLICENSE ); } static ENVIRONMENT::DesktopType window_manager = ENVIRONMENT::DesktopType::unknown; // // CONFIGURE_ARGSを返す // // mode: 整形モード(デフォルト = CONFIGURE_OMITTED 省略したものを一行で) // std::string ENVIRONMENT::get_configure_args( const int mode ) { std::string configure_args; #ifdef CONFIGURE_ARGS const std::string args = CONFIGURE_ARGS; // FULLはそのまま返す if( mode == CONFIGURE_FULL ) return args; size_t search_pos = 0, found_pos = 0; const size_t end_quote_pos = args.rfind( '\'' ); // 複数の項目 bool multi = false; // 省略形として"--with-"や"--enable-"などを取り出す while( ( found_pos = args.find( "'--", search_pos ) ) != std::string::npos ) { const size_t quote_pos = args.find( '\'', found_pos + 1 ); if( args.compare( found_pos + 3, 4, "with" ) != 0 && args.compare( found_pos + 3, 6, "enable" ) != 0 && args.compare( found_pos + 3, 7, "disable" ) != 0 ) { search_pos = quote_pos + 1; } else if( quote_pos != std::string::npos && quote_pos != end_quote_pos ) { // 項目が複数の場合は改行かスペースを付与 if( multi ) { if( mode == CONFIGURE_OMITTED_MULTILINE ) configure_args.append( "\n" ); else configure_args.append( " " ); } multi = true; configure_args.append( args.substr( found_pos, ( quote_pos - found_pos ) + 1 ) ); search_pos = quote_pos + 1; } else { configure_args.append( args.substr( found_pos ) ); break; } } #endif return configure_args; } // // GITリビジョンとして表示する文字列を返す // リビジョンが得られなかった場合(tarballのソース等)は、fallbackの日付を返す // git_dirtyは、まだcommitされてない変更があるかどうか // std::string get_git_revision (const char *git_date, const char *git_hash, const int git_dirty, const char *fallback_date) { bool date_valid = false; if( git_date ) { const std::size_t length = std::strlen( git_date ); if( length >= 8 && git_date[0] >= '1' ) { // isdigit() requires a value within the range of unsigned char. date_valid = std::all_of( git_date, git_date + length, []( unsigned char c ) { return std::isdigit( c ); } ); } } bool hash_valid = false; if( git_hash ) { // ハッシュ省略表記はgit 2.11から長さを自動で調整するようになった // ビルドメタデータが表記揺れするのは紛らわしいので固定長にする constexpr const size_t fixed_hash_length = 10; if( std::strlen( git_hash ) == fixed_hash_length ) { // isxdigit() requires a value within the range of unsigned char. hash_valid = std::all_of( git_hash, git_hash + fixed_hash_length, []( unsigned char c ) { return std::isxdigit( c ); } ); } } std::string git_revision; if( date_valid && hash_valid ) { git_revision.append( git_date ); git_revision.append( "(git:" ); git_revision.append( git_hash ); if( git_dirty ) { git_revision.append( ":M" ); } git_revision.push_back( ')' ); } else { git_revision.append( fallback_date ); } return git_revision; } // // JDのバージョンを取得 // std::string ENVIRONMENT::get_jdversion() { std::stringstream jd_version; jd_version << MAJORVERSION << "." << MINORVERSION << "." << MICROVERSION << "-" << JDTAG << get_git_revision(GIT_DATE, GIT_HASH, GIT_DIRTY, JDDATE_FALLBACK); return jd_version.str(); } // // ファイル等からディストリ名を取得 // std::string ENVIRONMENT::get_distname() { #ifdef _DEBUG std::cout << "SESSION::get_dist_name\n"; #endif std::string tmp; std::string text_data; // 各ディストリビューション共通の形式として定められた // http://www.freedesktop.org/software/systemd/man/os-release.html // 旧形式のコードは頃合いを見て削除する予定 if( CACHE::load_rawdata( "/etc/os-release", text_data ) ) { std::list< std::string > lines = MISC::get_lines( text_data ); std::list< std::string >::reverse_iterator it = lines.rbegin(); while( it != lines.rend() ) { std::string name, value; size_t e; if( ( e = (*it).find( '=' ) ) != std::string::npos ) { name = MISC::remove_spaces( (*it).substr( 0, e ) ); value = MISC::remove_spaces( (*it).substr( e + 1 ) ); } if( name == "PRETTY_NAME" && ! value.empty() ) { tmp = MISC::cut_str( value, "\"", "\"" ); break; } ++it; } } // LSB系 ( Ubuntu ..etc ) else if( CACHE::load_rawdata( "/etc/lsb-release", text_data ) ) { std::list< std::string > lines = MISC::get_lines( text_data ); std::list< std::string >::reverse_iterator it = lines.rbegin(); while( it != lines.rend() ) { std::string lsb_name, lsb_data; size_t e; if( ( e = (*it).find( '=' ) ) != std::string::npos ) { lsb_name = MISC::remove_spaces( (*it).substr( 0, e ) ); lsb_data = MISC::remove_spaces( (*it).substr( e + 1 ) ); } // 「DISTRIB_DESCRIPTION="Ubuntu 7.10"」などから「Ubuntu 7.10」を取得 if( lsb_name == "DISTRIB_DESCRIPTION" && ! lsb_data.empty() ) { tmp = MISC::cut_str( lsb_data, "\"", "\"" ); break; } ++it; } } // KNOPPIX (LSB?) else if( CACHE::load_rawdata( "/etc/knoppix-version", text_data ) ) { tmp = "KNOPPIX "; tmp.append( text_data ); } // SUSE else if( CACHE::load_rawdata( "/etc/SuSE-release", text_data ) ) { std::list< std::string > lines = MISC::get_lines( text_data ); tmp = lines.front(); // 1行目のみ } // Debian else if( CACHE::load_rawdata( "/etc/debian_version", text_data ) ) { tmp = "Debian GNU/Linux "; tmp.append( text_data ); } // Solaris系 else if( CACHE::load_rawdata( "/etc/release", text_data ) ) { std::list< std::string > lines = MISC::get_lines( text_data ); std::list< std::string >::iterator it = lines.begin(); while( it != lines.end() ) { // 名前が含まれている行を取得 if( (*it).find( "BeleniX" ) != std::string::npos || (*it).find( "Nexenta" ) != std::string::npos || (*it).find( "SchilliX" ) != std::string::npos || (*it).find( "Solaris" ) != std::string::npos ) { tmp = *it; break; } ++it; } } // ファイルの中身がそのままディストリ名として扱える物 else { // ディストリ名が書かれているファイル std::string dist_files[] = { "/etc/fedora-release", "/etc/gentoo-release", "/etc/lfs-release", "/etc/mandriva-release", "/etc/momonga-release", "/usr/lib/setup/plamo-version", "/etc/puppyversion", "/etc/redhat-release", // Redhat, CentOS, WhiteBox, PCLinuxOS "/etc/sabayon-release", "/etc/slackware-version", "/etc/turbolinux-release", "/etc/vine-release", "/etc/zenwalk-version" }; unsigned int i; for( i = 0; i < sizeof( dist_files ) / sizeof( std::string ); ++i ) { if( CACHE::load_rawdata( dist_files[i], text_data ) ) { tmp = text_data; break; } } } // 文字列両端のスペースなどを削除する std::string dist_name = MISC::remove_spaces( tmp ); // 取得した文字が異常に長い場合は空にする if( dist_name.length() > 50 ) dist_name.clear(); #ifdef HAVE_SYS_UTSNAME_H char *sysname = nullptr, *release = nullptr, *machine = nullptr; // システムコール uname() 準拠:SVr4, POSIX.1-2001. struct utsname uts; if( uname( &uts ) == 0 ) { sysname = uts.sysname; release = uts.release; machine = uts.machine; } // FreeBSD等やディストリ名が取得できなかった場合は"$ uname -rs"と同じ様式 if( dist_name.empty() && sysname && release ) { dist_name.append( sysname ); dist_name.push_back( ' ' ); dist_name.append( release ); } // アーキテクチャがx86でない場合 if( machine && ( strlen( machine ) != 4 || ! ( machine[0] == 'i' && machine[1] >= '3' && machine[1] <= '6' && machine[2] == '8' && machine[3] == '6' ) ) ) { const std::string arch = "(" + std::string( machine ) + ")"; if ( dist_name.find( arch ) == std::string::npos ) dist_name.append( " " + arch ); } #endif return dist_name; } static constexpr const char* tbl_desktop[] = { "GNOME", "XFCE", "KDE", "LXDE", "UNITY", "CINNAMON", "MATE", "BUDGIE", "PANTHEON", "ENLIGHTENMENT", "LXQT", "(unknown)" }; // // WM 判定 // TODO: 環境変数で判定できない場合の判定方法を考える // ENVIRONMENT::DesktopType ENVIRONMENT::get_wm() { if( window_manager != DesktopType::unknown ) return window_manager; constexpr const char* envvar[] = { "DESKTOP_SESSION", "XDG_CURRENT_DESKTOP" }; for( const char* v : envvar ) { const std::string str_wm = MISC::toupper_str( MISC::getenv_limited( v, 20 ) ); if( !str_wm.empty() ){ constexpr auto table_size = static_cast< std::size_t >( DesktopType::unknown ); for( std::size_t i = 0; i < table_size; ++i ){ if( str_wm.find( tbl_desktop[ i ] ) != std::string::npos ){ window_manager = static_cast< DesktopType >( i ); } } } } if( window_manager == DesktopType::unknown ) { if( ! MISC::getenv_limited( "GNOME_DESKTOP_SESSION_ID" ).empty() ) { window_manager = DesktopType::gnome; } else { const std::string str_wm = MISC::getenv_limited( "KDE_FULL_SESSION", 4 ); if( str_wm == "true" ) window_manager = DesktopType::kde; } } return window_manager; } // // WM名を文字列で返す // std::string ENVIRONMENT::get_wm_str() { return tbl_desktop[ static_cast< int >( get_wm() ) ]; } // // gtkmmのバージョンを取得 // std::string ENVIRONMENT::get_gtkmm_version() { std::stringstream gtkmm_ver; gtkmm_ver << GTKMM_MAJOR_VERSION << "." << GTKMM_MINOR_VERSION << "." << GTKMM_MICRO_VERSION; return gtkmm_ver.str(); } // // glibmmのバージョンを取得 // std::string ENVIRONMENT::get_glibmm_version() { std::stringstream glibmm_ver; glibmm_ver << GLIBMM_MAJOR_VERSION << "." << GLIBMM_MINOR_VERSION << "." << GLIBMM_MICRO_VERSION; return glibmm_ver.str(); } // // TLSライブラリのバージョンを取得 // std::string ENVIRONMENT::get_tlslib_version() { std::string version; #if defined(USE_GNUTLS) version = "GnuTLS "; version += gnutls_check_version(nullptr); #elif defined(USE_OPENSSL) version = SSLeay_version(SSLEAY_VERSION); #endif return version; } // // 動作環境を取得 // std::string ENVIRONMENT::get_jdinfo() { std::stringstream jd_info; const std::string progname = get_progname(); // バージョンを取得(jdversion.h) const std::string version = get_jdversion(); // ディストリビューション名を取得 const std::string distribution = get_distname(); // デスクトップ環境を取得( 環境変数から判別可能の場合 ) std::string desktop = get_wm_str(); // その他 std::string other; // $LANG が ja_JP.UTF-8 でない場合は"その他"に追加する。 const std::string lang = MISC::getenv_limited( "LANG", 11 ); if( lang.empty() ) other.append( "LANG 未定義" ); else if( lang != "ja_JP.utf8" && lang != "ja_JP.UTF-8" ) other.append( "LANG = " + lang ); jd_info << "[バージョン] " << progname << " " << version << "\n" << "[ディストリ ] " << distribution << "\n" << "[パッケージ] " << "バイナリ/ソース( <配布元> )" << "\n" << "[ DE/WM ] " << desktop << "\n" << "[ gtkmm  ] " << get_gtkmm_version() << "\n" << "[ glibmm  ] " << get_glibmm_version() << "\n" << "[ TLS lib ] " << get_tlslib_version() << "\n" << #ifdef CONFIGURE_ARGS "[オプション ] " << get_configure_args( CONFIGURE_OMITTED_MULTILINE ) << "\n" << #endif "[ そ の 他 ] " << other << "\n"; return jd_info.str(); } // Client-Side Decorationを使うか static bool should_use_header_bar() { const int flag = CONFIG::get_use_header_bar(); // 0: 使わない 1: 使う if( flag == 0 || flag == 1 ) return static_cast( flag ); // 2: デスクトップに合わせる のときはデスクトップ環境を調べる return ENVIRONMENT::get_wm() == ENVIRONMENT::DesktopType::gnome; } // ダイアログでClient-Side Decorationを使うか // JDimの設定とGtkSettingsから使うか判断する bool ENVIRONMENT::get_dialog_use_header_bar() { const bool dialog_use_header = Gtk::Settings::get_default()->property_gtk_dialogs_use_header(); return dialog_use_header && should_use_header_bar(); } jdim-0.7.0/src/environment.h000066400000000000000000000023741417047150700157610ustar00rootroot00000000000000// License GPL2 #ifndef _ENVIRONMENT_H #define _ENVIRONMENT_H #include namespace ENVIRONMENT { // WM enum class DesktopType { gnome = 0, xfce, kde, lxde, unity, cinnamon, mate, budgie, pantheon, enlightenment, lxqt, unknown }; // configure_argsのモード enum { CONFIGURE_OMITTED = 0, CONFIGURE_OMITTED_MULTILINE, CONFIGURE_FULL }; std::string get_progname(); std::string get_jdcomments(); std::string get_jdcopyright(); std::string get_jdbbs(); std::string get_jd2chlog(); std::string get_jdhelp(); std::string get_jdhelpcmd(); std::string get_jdhelpreplstr(); std::string get_jdlicense(); std::string get_configure_args( const int mode = CONFIGURE_OMITTED ); std::string get_jdversion(); std::string get_distname(); DesktopType get_wm(); std::string get_wm_str(); std::string get_gtkmm_version(); std::string get_glibmm_version(); std::string get_tlslib_version(); std::string get_jdinfo(); // ダイアログでClient-Side Decorationを使うか // JDimの設定とGtkSettingsから使うか判断する bool get_dialog_use_header_bar(); } #endif jdim-0.7.0/src/fontcolorpref.cpp000066400000000000000000000516271417047150700166370ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "skeleton/msgdiag.h" #include "config/globalconf.h" #include "config/defaultconf.h" #include "jdlib/miscgtk.h" #include "control/controlid.h" #include "control/controlutil.h" #include "fontcolorpref.h" #include "colorid.h" #include "fontid.h" #include "command.h" #define WARNING_STRICTCHAR "スレビューのフォント幅の近似計算を厳密に行います\n\nレイアウトが崩れにくくなるかわりにパフォーマンスが著しく低下します。通常は設定しないでください" #define WARNING_GTKRC_TREE "gtkrc 関係の設定はJDimの再起動後に有効になります" using namespace CORE; FontColorPref::FontColorPref( Gtk::Window* parent, const std::string& url ) : SKELETON::PrefDiag( parent, url, true, true ), m_label_aafont( true, "AAレスと判定する正規表現(_R): " ), m_bt_reset_font( "フォントの設定を全てデフォルトに戻す(_F)", true ), m_bt_change_color( "選択行の色を設定する(_S)", true ), m_bt_reset_color( "選択行の色をデフォルトに戻す(_R)", true ), m_bt_reset_all_colors( "色の設定を全てデフォルトに戻す(_C)", true ) { CONFIG::bkup_conf(); pack_widget(); // フォント設定をセット set_font_settings( "スレビュー", FONT_MAIN, "スレビューのフォント" ); set_font_settings( "メール欄とか", FONT_MAIL, "メール欄とかのフォント" ); set_font_settings( "ポップアップ", FONT_POPUP, "ポップアップのフォント" ); set_font_settings( "アスキーアート", FONT_AA, "AA(スレビュー)のフォント" ); set_font_settings( "板一覧/お気に入り", FONT_BBS, "板一覧/お気に入りのフォント" ); set_font_settings( "スレ一覧", FONT_BOARD, "スレ一覧のフォント" ); set_font_settings( "書き込みビュー", FONT_MESSAGE, "書き込みビューのフォント" ); // 色設定をセット set_color_settings( COLOR_NONE, "■ " + CONTROL::get_mode_label( CONTROL::MODE_COMMON ), "" ); set_color_settings( COLOR_BACK_HIGHLIGHT_TREE, "板、スレ一覧での検索結果などのハイライトの背景色", CONF_COLOR_BACK_HIGHLIGHT_TREE ); set_color_settings( COLOR_NONE, "", "" ); set_color_settings( COLOR_NONE, "■ "+ CONTROL::get_mode_label( CONTROL::MODE_BBSLIST ), "" ); set_color_settings( COLOR_CHAR_BBS, "文字色", CONF_COLOR_CHAR_BBS ); set_color_settings( COLOR_CHAR_BBS_COMMENT, "コメントの文字色", CONF_COLOR_CHAR_BBS_COMMENT ); set_color_settings( COLOR_BACK_BBS, "奇数行の背景色", CONF_COLOR_BACK_BBS ); set_color_settings( COLOR_BACK_BBS_EVEN, "偶数行の背景色", CONF_COLOR_BACK_BBS_EVEN ); set_color_settings( COLOR_NONE, "", "" ); set_color_settings( COLOR_NONE, "■ "+ CONTROL::get_mode_label( CONTROL::MODE_BOARD ), "" ); set_color_settings( COLOR_CHAR_BOARD, "文字色", CONF_COLOR_CHAR_BOARD ); set_color_settings( COLOR_BACK_BOARD, "奇数行の背景色", CONF_COLOR_BACK_BOARD ); set_color_settings( COLOR_BACK_BOARD_EVEN, "偶数行の背景色", CONF_COLOR_BACK_BOARD_EVEN ); set_color_settings( COLOR_NONE, "", "" ); set_color_settings( COLOR_NONE, "■ "+ CONTROL::get_mode_label( CONTROL::MODE_ARTICLE ), "" ); set_color_settings( COLOR_CHAR, "基本の文字色", CONF_COLOR_CHAR ); set_color_settings( COLOR_CHAR_NAME, "名前欄の通常の文字色", CONF_COLOR_CHAR_NAME ); set_color_settings( COLOR_CHAR_NAME_B, "名前欄のトリップ等の文字色", CONF_COLOR_CHAR_NAME_B ); set_color_settings( COLOR_CHAR_NAME_NOMAIL, "メール無しの名前欄の文字色", CONF_COLOR_CHAR_NAME_NOMAIL ); set_color_settings( COLOR_CHAR_AGE, "sage でないメール欄の文字色", CONF_COLOR_CHAR_AGE ); set_color_settings( COLOR_CHAR_SELECTION, "選択範囲の文字色", CONF_COLOR_CHAR_SELECTION ); set_color_settings( COLOR_CHAR_HIGHLIGHT, "検索結果などのハイライトの文字色", CONF_COLOR_CHAR_HIGHLIGHT ); set_color_settings( COLOR_CHAR_LINK, "通常のリンクの文字色", CONF_COLOR_CHAR_LINK ); set_color_settings( COLOR_CHAR_LINK_ID_LOW, "複数発言したIDの文字色", CONF_COLOR_CHAR_LINK_ID_LOW ); set_color_settings( COLOR_CHAR_LINK_ID_HIGH, "多く発言したIDの文字色", CONF_COLOR_CHAR_LINK_ID_HIGH ); set_color_settings( COLOR_CHAR_LINK_RES, "参照されていないレス番号の文字色", CONF_COLOR_CHAR_LINK_RES ); set_color_settings( COLOR_CHAR_LINK_LOW, "他のレスから参照されたレス番号の文字色", CONF_COLOR_CHAR_LINK_LOW ); set_color_settings( COLOR_CHAR_LINK_HIGH, "参照された数が多いレス番号の文字色", CONF_COLOR_CHAR_LINK_HIGH ); set_color_settings( COLOR_IMG_NOCACHE, "画像として扱うリンクのうち、キャッシュされていない物の文字色", CONF_COLOR_IMG_NOCACHE ); set_color_settings( COLOR_IMG_CACHED, "画像として扱うリンクのうち、キャッシュされている物の文字色", CONF_COLOR_IMG_CACHED ); set_color_settings( COLOR_IMG_LOADING, "画像として扱うリンクのうち、ロード中の物の文字色", CONF_COLOR_IMG_LOADING ); set_color_settings( COLOR_IMG_ERR, "画像として扱うリンクのうち、エラーになっている物の文字色", CONF_COLOR_IMG_ERR ); set_color_settings( COLOR_BACK, "スレビューの背景色", CONF_COLOR_BACK ); set_color_settings( COLOR_BACK_POPUP, "ポップアップの背景色", CONF_COLOR_BACK_POPUP ); set_color_settings( COLOR_BACK_SELECTION, "選択範囲の背景色", CONF_COLOR_BACK_SELECTION ); set_color_settings( COLOR_BACK_HIGHLIGHT, "検索結果などのハイライトの背景色", CONF_COLOR_BACK_HIGHLIGHT ); set_color_settings( COLOR_SEPARATOR_NEW, "新着しおりの色", CONF_COLOR_SEPARATOR_NEW ); set_color_settings( COLOR_FRAME, "ポップアップのフレーム色", CONF_COLOR_FRAME ); set_color_settings( COLOR_MARKER, "オートスクロールのマーカ色", CONF_COLOR_MARKER ); set_color_settings( COLOR_NONE, "", "" ); set_color_settings( COLOR_NONE, "■ " + CONTROL::get_mode_label( CONTROL::MODE_MESSAGE ), "" ); set_color_settings( COLOR_CHAR_MESSAGE, "文字色", CONF_COLOR_CHAR_MESSAGE ); set_color_settings( COLOR_CHAR_MESSAGE_SELECTION, "選択範囲の文字色", CONF_COLOR_CHAR_MESSAGE_SELECTION ); set_color_settings( COLOR_BACK_MESSAGE, "背景色", CONF_COLOR_BACK_MESSAGE ); set_color_settings( COLOR_BACK_MESSAGE_SELECTION, "選択範囲の背景色", CONF_COLOR_BACK_MESSAGE_SELECTION ); m_combo_font.set_active( 0 ); m_fontbutton.set_font_name( CONFIG::get_fontname( m_font_tbl[ 0 ] ) ); m_event_font.set_tooltip_text( m_tooltips_font[ 0 ] ); m_fontbutton.set_tooltip_text( m_tooltips_font[ 0 ] ); set_title( "フォントと色の詳細設定" ); show_all_children(); } FontColorPref::~FontColorPref() noexcept = default; // // 各ウィジェットを追加 // void FontColorPref::pack_widget() { const int mrg = 8; // フォント m_event_font.add( m_combo_font ); m_hbox_font.pack_start( m_event_font, Gtk::PACK_SHRINK ); m_hbox_font.pack_start( m_fontbutton, Gtk::PACK_EXPAND_WIDGET, mrg ); m_vbox_font.set_border_width( mrg ); m_vbox_font.pack_start( m_hbox_font, Gtk::PACK_SHRINK, mrg/2 ); m_checkbutton_font.add_label( "スレビューでフォント幅の近似計算を厳密に行う(_S)", true ), m_checkbutton_font.set_active( CONFIG::get_strict_char_width() ); m_checkbutton_font.set_tooltip_text( WARNING_STRICTCHAR ); m_hbox_checkbutton.pack_start( m_checkbutton_font, Gtk::PACK_SHRINK ); m_vbox_font.pack_start( m_hbox_checkbutton, Gtk::PACK_SHRINK, mrg/2 ); // 行高さ m_spin_space.set_digits( 1 ); m_spin_space.set_range( 0.1, 10.0 ); m_spin_space.set_increments( 0.1, 0.1 ); m_spin_space.set_value( CONFIG::get_adjust_line_space() ); m_label_space.set_text_with_mnemonic( "スレビューの文字列の行の高さ(_H): " ); m_label_space.set_mnemonic_widget( m_spin_space ); m_hbox_space.set_spacing ( mrg ); m_hbox_space.pack_start( m_label_space, Gtk::PACK_SHRINK ); m_hbox_space.pack_start( m_spin_space, Gtk::PACK_SHRINK ); m_spin_space.set_tooltip_text( "スレビューにおいて行の高さを調節します( 標準は 1 )" ); m_vbox_font.pack_start( m_hbox_space, Gtk::PACK_SHRINK, mrg/2 ); set_activate_entry( m_spin_space ); // 下線位置 m_spin_ubar.set_digits( 1 ); m_spin_ubar.set_range( 0.1, 10.0 ); m_spin_ubar.set_increments( 0.1, 0.1 ); m_spin_ubar.set_value( CONFIG::get_adjust_underline_pos() ); m_label_ubar.set_text_with_mnemonic( "スレビューの文字列の下線位置(_U): " ); m_label_ubar.set_mnemonic_widget( m_spin_ubar ); m_hbox_ubar.pack_start( m_label_ubar, Gtk::PACK_SHRINK ); m_hbox_ubar.pack_start( m_spin_ubar, Gtk::PACK_SHRINK ); m_vbox_font.pack_start( m_hbox_ubar, Gtk::PACK_SHRINK, mrg/2 ); m_spin_ubar.set_tooltip_text( "スレビューにおいてアンカーなどの下線の位置を調節します( 標準は 1 )" ); set_activate_entry( m_spin_ubar ); // AAレスと判定する正規表現 m_label_aafont.set_text( CONFIG::get_regex_res_aa() ); m_vbox_font.pack_start( m_label_aafont, Gtk::PACK_SHRINK, mrg/2 ); m_label_aafont.set_tooltip_text( "この正規表現に一致したレスは、アスキーアートフォントで表示します( 次に開いたスレから有効 )" ); set_activate_entry( m_label_aafont ); // フォントのリセット m_bt_reset_font.signal_clicked().connect( sigc::mem_fun( *this, &FontColorPref::slot_reset_font ) ); m_vbox_font.pack_end( m_bt_reset_font, Gtk::PACK_SHRINK ); m_notebook.append_page( m_vbox_font, "フォントの設定" ); m_combo_font.signal_changed().connect( sigc::mem_fun( *this, &FontColorPref::slot_combo_font_changed ) ); m_fontbutton.signal_font_set().connect( sigc::mem_fun( *this, &FontColorPref::slot_fontbutton_on_set ) ); m_checkbutton_font.signal_toggled().connect( sigc::mem_fun( *this, &FontColorPref::slot_checkbutton_font_toggled ) ); // 色 m_vbox_color.set_border_width( mrg ); m_vbox_color.set_spacing( mrg ); m_label_warning_color.set_text( "Ctrl+クリック又はShift+クリックで複数行選択可能\nテーマによってはツリービュー(板一覧、スレ一覧)の背景色が正しく設定されない場合があります。" ); m_vbox_color.pack_start( m_label_warning_color, Gtk::PACK_SHRINK ); m_liststore_color = Gtk::ListStore::create( m_columns_color ); m_treeview_color.set_model( m_liststore_color ); m_treeview_color.set_size_request( 480, 280 ); m_treeview_color.get_selection()->set_mode( Gtk::SELECTION_MULTIPLE ); m_treeview_color.signal_row_activated().connect( sigc::mem_fun( *this, &FontColorPref::slot_row_activated ) ); m_scrollwin_color.add( m_treeview_color ); m_scrollwin_color.set_min_content_height( 180 ); m_scrollwin_color.set_policy( Gtk::POLICY_AUTOMATIC, Gtk::POLICY_ALWAYS ); m_vbox_color.pack_start( m_scrollwin_color, Gtk::PACK_EXPAND_WIDGET ); Gtk::TreeViewColumn* column = Gtk::manage( new Gtk::TreeViewColumn( "設定名", m_columns_color.m_col_name ) ); column->set_fixed_width( 430 ); column->set_sizing( Gtk::TREE_VIEW_COLUMN_FIXED ); column->set_resizable( true ); m_treeview_color.append_column( *column ); Gtk::CellRenderer *cell = column->get_first_cell(); if( cell ) column->set_cell_data_func( *cell, sigc::mem_fun( *this, &FontColorPref::slot_cell_data_name ) ); column = Gtk::manage( new Gtk::TreeViewColumn( "色", m_columns_color.m_col_color ) ); m_treeview_color.append_column( *column ); cell = column->get_first_cell(); if( cell ) column->set_cell_data_func( *cell, sigc::mem_fun( *this, &FontColorPref::slot_cell_data_color ) ); m_bt_change_color.signal_clicked().connect( sigc::mem_fun( *this, &FontColorPref::slot_change_color ) ); m_bt_reset_color.signal_clicked().connect( sigc::mem_fun( *this, &FontColorPref::slot_reset_color ) ); m_hbox_change_color.set_spacing( mrg ); m_hbox_change_color.pack_end( m_bt_reset_color, Gtk::PACK_SHRINK ); m_hbox_change_color.pack_end( m_bt_change_color , Gtk::PACK_SHRINK ); m_vbox_color.pack_start( m_hbox_change_color, Gtk::PACK_SHRINK ); m_chk_use_gtktheme_message.add_label( "書き込みビューの配色設定に GTKテーマ を用いる(_W)", true ); m_chk_use_gtktheme_message.set_active( CONFIG::get_use_message_gtktheme() ); m_vbox_color.pack_start( m_chk_use_gtktheme_message, Gtk::PACK_SHRINK ); m_chk_use_gtkrc_tree.add_label( "ツリービューの背景色設定に gtkrc を用いる(_T)", true ), m_chk_use_gtkrc_tree.set_active( CONFIG::get_use_tree_gtkrc() ); m_vbox_color.pack_start( m_chk_use_gtkrc_tree, Gtk::PACK_SHRINK ); m_chk_use_gtkrc_selection.add_label( "スレビューの選択範囲の色設定に gtkrc を用いる(_S)", true ), m_chk_use_gtkrc_selection.set_active( CONFIG::get_use_select_gtkrc() ); m_vbox_color.pack_start( m_chk_use_gtkrc_selection, Gtk::PACK_SHRINK ); m_bt_reset_all_colors.signal_clicked().connect( sigc::mem_fun( *this, &FontColorPref::slot_reset_all_colors ) ); m_vbox_color.pack_end( m_bt_reset_all_colors, Gtk::PACK_SHRINK ); m_notebook.append_page( m_vbox_color, "色の設定" ); // 全体 get_content_area()->pack_start( m_notebook ); get_content_area()->set_spacing( mrg ); set_border_width( mrg ); } // // 設定ダイアログで OK が押された // void FontColorPref::slot_ok_clicked() { #ifdef _DEBUG std::cout << "FontColorPref::slot_ok_clicked\n"; #endif CONFIG::set_adjust_line_space( m_spin_space.get_value() ); CONFIG::set_adjust_underline_pos( m_spin_ubar.get_value() ); CONFIG::set_regex_res_aa( m_label_aafont.get_text() ); CONFIG::set_strict_char_width( m_checkbutton_font.property_active() ); CONFIG::set_use_message_gtktheme( m_chk_use_gtktheme_message.property_active() ); CONFIG::set_use_tree_gtkrc( m_chk_use_gtkrc_tree.property_active() ); CONFIG::set_use_select_gtkrc( m_chk_use_gtkrc_selection.property_active() ); CORE::core_set_command( "relayout_all_bbslist" ); CORE::core_set_command( "relayout_all_board" ); CORE::core_set_command( "init_font_all_article" ); CORE::core_set_command( "relayout_all_article" ); CORE::core_set_command( "relayout_all_message" ); } void FontColorPref::slot_apply_clicked() { #ifdef _DEBUG std::cout << "FontColorPref::slot_apply_clicked\n"; #endif slot_ok_clicked(); CONFIG::bkup_conf(); } // // 設定ダイアログでキャンセルが押された // void FontColorPref::slot_cancel_clicked() { #ifdef _DEBUG std::cout << "FontColorPref::slot_cancel_clicked\n"; #endif CONFIG::restore_conf(); } // // フォント設定の名前と設定値をセット // void FontColorPref::set_font_settings( const std::string& name, const int fontid, const std::string& tooltip ) { if( ! name.empty() && fontid < FONT_NUM ) { m_combo_font.append( name ); m_font_tbl.push_back( fontid ); m_tooltips_font.push_back( tooltip ); } } // // フォント設定のコンボボックスの状態が変わった // void FontColorPref::slot_combo_font_changed() { const int num = m_combo_font.get_active_row_number(); m_fontbutton.set_font_name( CONFIG::get_fontname( m_font_tbl[ num ] ) ); m_event_font.set_tooltip_text( m_tooltips_font[ num ] ); m_fontbutton.set_tooltip_text( m_tooltips_font[ num ] ); } // // チェックボックスの状態が変わった // void FontColorPref::slot_checkbutton_font_toggled() { if( m_checkbutton_font.property_active() ) { SKELETON::MsgDiag mdiag( nullptr, WARNING_STRICTCHAR ); mdiag.run(); } } // // フォント選択ダイアログで OK が押された // void FontColorPref::slot_fontbutton_on_set() { const int num = m_combo_font.get_active_row_number(); const std::string result = m_fontbutton.get_font_name(); CONFIG::set_fontname( m_font_tbl[ num ], result ); } // // フォント設定のリセット // void FontColorPref::slot_reset_font() { CONFIG::reset_fonts(); const int num = m_combo_font.get_active_row_number(); m_fontbutton.set_font_name( CONFIG::get_fontname( m_font_tbl[ num ] ) ); m_checkbutton_font.set_active( CONFIG::CONF_STRICT_CHAR_WIDTH ); m_spin_space.set_value( CONFIG::CONF_ADJUST_LINE_SPACE ); m_spin_ubar.set_value( CONFIG::CONF_ADJUST_UNDERLINE_POS ); m_label_aafont.set_text( CONF_REGEX_RES_AA_DEFAULT ); } // // 色設定の名前と設定値をセット // void FontColorPref::set_color_settings( const int colorid, const std::string& name, const std::string& defaultval ) { #ifdef _DEBUG std::cout << " FontColorPref::set_color_settings name = " << name << " id = " << colorid << std::endl; #endif Gtk::TreeModel::Row row; row = *( m_liststore_color->append() ); row[ m_columns_color.m_col_name ] = name; row[ m_columns_color.m_col_color ] = std::string(); row[ m_columns_color.m_col_colorid ] = colorid; row[ m_columns_color.m_col_default ] = defaultval; static_cast( row ); // cppcheck: unreadVariable } // // 行選択 // void FontColorPref::slot_row_activated( const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn* column ) { #ifdef _DEBUG std::cout << "FontColorPref::slot_row_activated path = " << path.to_string() << std::endl; #endif slot_change_color(); } // // 実際の描画の際に cellrendere のプロパティをセットするスロット関数 // void FontColorPref::slot_cell_data_name( Gtk::CellRenderer* cell, const Gtk::TreeModel::iterator& it ) { Gtk::TreeModel::Row row = *it; const int colorid = row[ m_columns_color.m_col_colorid ]; const std::string defaultcolor = row[ m_columns_color.m_col_default ]; if( colorid != COLOR_NONE && CONFIG::get_color( colorid ) != defaultcolor ){ cell->property_cell_background() = CONFIG::get_color( COLOR_BACK_HIGHLIGHT_TREE ); cell->property_cell_background_set() = true; } else cell->property_cell_background_set() = false; } void FontColorPref::slot_cell_data_color( Gtk::CellRenderer* cell, const Gtk::TreeModel::iterator& it ) { Gtk::TreeModel::Row row = *it; const int colorid = row[ m_columns_color.m_col_colorid ]; if( colorid != COLOR_NONE ){ cell->property_cell_background() = CONFIG::get_color( colorid ); cell->property_cell_background_set() = true; } else cell->property_cell_background_set() = false; } // // 選択行の色の変更 // void FontColorPref::slot_change_color() { Gtk::TreeRow row; std::vector< Gtk::TreePath > selection_path = m_treeview_color.get_selection()->get_selected_rows(); if( selection_path.empty() ) return; int colorid = COLOR_NONE; if( selection_path.size() == 1 ){ row = *m_liststore_color->get_iter( selection_path.front() ); if( ! row ) return; colorid = row[ m_columns_color.m_col_colorid ]; if( colorid == COLOR_NONE ) return; } Gtk::ColorSelectionDialog colordiag; if( colorid != COLOR_NONE ) { Gtk::ColorSelection* sel = colordiag.get_color_selection(); sel->set_current_rgba( Gdk::RGBA( CONFIG::get_color( colorid ) ) ); } colordiag.set_transient_for( *CORE::get_mainwindow() ); const int ret = colordiag.run(); if( ret == Gtk::RESPONSE_OK ){ for( const Gtk::TreePath& path : selection_path ) { row = *m_liststore_color->get_iter( path ); if( ! row ) continue; colorid = row[ m_columns_color.m_col_colorid ]; if( colorid != COLOR_NONE ) { Gtk::ColorSelection* sel = colordiag.get_color_selection(); CONFIG::set_color( colorid, MISC::color_to_str( sel->get_current_rgba() ) ); } } } } // // 選択行の色のリセット // void FontColorPref::slot_reset_color() { std::vector< Gtk::TreePath > selection_path = m_treeview_color.get_selection()->get_selected_rows(); if( selection_path.empty() ) return; for( const Gtk::TreePath& path : selection_path ) { Gtk::TreeRow row = *m_liststore_color->get_iter( path ); if( ! row ) continue; const int colorid = row[ m_columns_color.m_col_colorid ]; if( colorid != COLOR_NONE ){ const std::string defaultcolor = row[ m_columns_color.m_col_default ]; CONFIG::set_color( colorid , defaultcolor ); m_treeview_color.queue_draw(); } } } // // 全ての色のリセット // void FontColorPref::slot_reset_all_colors() { m_chk_use_gtktheme_message.set_active( CONFIG::CONF_USE_MESSAGE_GTKTHEME ); m_chk_use_gtkrc_tree.set_active( CONFIG::CONF_USE_TREE_GTKRC ); m_chk_use_gtkrc_selection.set_active( CONFIG::CONF_USE_SELECT_GTKRC ); CONFIG::reset_colors(); m_treeview_color.queue_draw(); } jdim-0.7.0/src/fontcolorpref.h000066400000000000000000000063601417047150700162760ustar00rootroot00000000000000// ライセンス: GPL2 #ifndef _FONTCOLORPREF_H #define _FONTCOLORPREF_H #include "skeleton/prefdiag.h" #include "skeleton/label_entry.h" #include namespace CORE { class ColorTreeColumn : public Gtk::TreeModel::ColumnRecord { public: Gtk::TreeModelColumn< Glib::ustring > m_col_name; Gtk::TreeModelColumn< std::string > m_col_color; Gtk::TreeModelColumn< int > m_col_colorid; Gtk::TreeModelColumn< std::string > m_col_default; ColorTreeColumn() { add( m_col_name ); add( m_col_color ); add( m_col_colorid ); add( m_col_default ); } }; //////////////////////////////// class FontColorPref : public SKELETON::PrefDiag { Gtk::Notebook m_notebook; // フォントの設定 std::vector< int > m_font_tbl; std::vector< std::string > m_tooltips_font; Gtk::HBox m_hbox_font; Gtk::VBox m_vbox_font; Gtk::EventBox m_event_font; Gtk::ComboBoxText m_combo_font; Gtk::FontButton m_fontbutton; Gtk::HBox m_hbox_checkbutton; Gtk::CheckButton m_checkbutton_font; Gtk::HBox m_hbox_space; Gtk::HBox m_hbox_ubar; Gtk::Label m_label_space; Gtk::SpinButton m_spin_space; Gtk::Label m_label_ubar; Gtk::SpinButton m_spin_ubar; SKELETON::LabelEntry m_label_aafont; Gtk::Button m_bt_reset_font; // 色の設定 Gtk::Label m_label_warning_color; Gtk::VBox m_vbox_color; Gtk::CheckButton m_chk_use_gtktheme_message; Gtk::CheckButton m_chk_use_gtkrc_tree; Gtk::CheckButton m_chk_use_gtkrc_selection; Gtk::TreeView m_treeview_color; Glib::RefPtr< Gtk::ListStore > m_liststore_color; CORE::ColorTreeColumn m_columns_color; Gtk::ScrolledWindow m_scrollwin_color; Gtk::HBox m_hbox_change_color; Gtk::Button m_bt_change_color; Gtk::Button m_bt_reset_color; Gtk::Button m_bt_reset_all_colors; public: FontColorPref( Gtk::Window* parent, const std::string& url ); ~FontColorPref() noexcept; private: // ウィジェットを追加 void pack_widget(); // フォントの設定 void set_font_settings( const std::string& name, const int fontid, const std::string& tooltip ); void slot_combo_font_changed(); void slot_fontbutton_on_set(); void slot_checkbutton_font_toggled(); void slot_reset_font(); // 色の設定 void set_color_settings( const int colorid, const std::string& name, const std::string& defaultval ); void slot_row_activated( const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn* column ); void slot_cell_data_name( Gtk::CellRenderer* cell, const Gtk::TreeModel::iterator& it ); void slot_cell_data_color( Gtk::CellRenderer* cell, const Gtk::TreeModel::iterator& it ); void slot_change_color(); void slot_reset_color(); void slot_reset_all_colors(); // OK,cancel,apply が押された void slot_ok_clicked() override; void slot_apply_clicked() override; void slot_cancel_clicked() override; }; } #endif jdim-0.7.0/src/fontid.h000066400000000000000000000011621417047150700146720ustar00rootroot00000000000000// フォントID #ifndef _FONT_ID_H #define _FONT_ID_H enum { FONT_MAIN = 0, // スレッドビューなどの基本の物 FONT_MAIL, // メールとか日付とかの部分 FONT_POPUP, // ポップアップ FONT_AA, // AA(スレビュー) FONT_BBS, // スレ一覧 FONT_BOARD, // 板一覧 FONT_MESSAGE, // 書き込みビューのエディタ FONT_ENTRY_DEFAULT, // Gtk::Entryのデフォルトフォント FONT_NUM, FONT_EMPTY, // フォントID未設定 FONT_DEFAULT, // ビューの標準フォントを使用 }; #endif jdim-0.7.0/src/global.h000066400000000000000000000213101417047150700146440ustar00rootroot00000000000000// グローバルな定数などの定義 #ifndef _GLOBAL_H #define _GLOBAL_H #include enum{ TIMER_TIMEOUT = 50, // msec 内部クロックの周期 TIMER_TIMEOUT_SMOOTH_SCROLL = 33, // msec スレビューのスムーススクロール描画用クロック周期 MAX_MG_LNG = 5, // マウスジェスチャの最大ストローク ICON_SIZE = 32 // 画像アイコンの大きさ }; // 書き込みビューの名前欄の空白 #define JD_NAME_BLANK "jd_name_blank" // 書き込みビューのメール欄の空白 #define JD_MAIL_BLANK "jd_mail_blank" // SKELETON::SelectItemPrefの項目名 #define ITEM_NAME_BBSLISTVIEW "板一覧" #define ITEM_NAME_FAVORITEVIEW "お気に入り" #define ITEM_NAME_HISTVIEW "スレ履歴" #define ITEM_NAME_HIST_BOARDVIEW "板履歴" #define ITEM_NAME_HIST_CLOSEVIEW "最近閉じたスレ" #define ITEM_NAME_HIST_CLOSEBOARDVIEW "最近閉じた板" #define ITEM_NAME_HIST_CLOSEIMGVIEW "最近閉じた画像" #define ITEM_NAME_BOARDVIEW "スレ一覧" #define ITEM_NAME_ARTICLEVIEW "スレビュー" #define ITEM_NAME_IMAGEVIEW "画像ビュー" #define ITEM_NAME_URL "URL" #define ITEM_NAME_GO "移動" #define ITEM_NAME_SEPARATOR "区切り" #define ITEM_NAME_MARK "!" #define ITEM_NAME_ID "番号" #define ITEM_NAME_BOARD "板" #define ITEM_NAME_NAME "タイトル" #define ITEM_NAME_RES "レス" #define ITEM_NAME_LOAD "取得" #define ITEM_NAME_NEW "新着" #define ITEM_NAME_SINCE "since" #define ITEM_NAME_LASTWRITE "最終書込" #define ITEM_NAME_ACCESS "最終取得" #define ITEM_NAME_SPEED "速度" #define ITEM_NAME_DIFF "増分" #define ITEM_NAME_WRITEMSG "書き込み" #define ITEM_NAME_OPENBOARD "板を開く" #define ITEM_NAME_OPENARTICLETAB "タブでスレを開く" #define ITEM_NAME_REGETARTICLE "スレ情報を消さずに再取得" #define ITEM_NAME_BOOKMARK "しおりを設定/解除" #define ITEM_NAME_SEARCH "検索" #define ITEM_NAME_DRAWOUT "抽出" #define ITEM_NAME_RELOAD "再読み込み" #define ITEM_NAME_STOPLOADING "読み込み中止" #define ITEM_NAME_APPENDFAVORITE "お気に入りに追加" #define ITEM_NAME_FAVORITE_ARTICLE "スレをお気に入りに追加" #define ITEM_NAME_CHECK_UPDATE_ROOT "サイドバー更新チェック" #define ITEM_NAME_CHECK_UPDATE_OPEN_ROOT "サイドバー更新チェックして開く" #define ITEM_NAME_COPY "コピー" #define ITEM_NAME_COPY_URL "URLをコピー" #define ITEM_NAME_COPY_TITLE_URL "タイトルとURLをコピー" #define ITEM_NAME_COPY_TITLE_URL_THREAD "スレのタイトルとURLをコピー" #define ITEM_NAME_COPY_THREAD_INFO "スレ情報を引き継ぐ" #define ITEM_NAME_DELETE "削除" #define ITEM_NAME_QUIT "閉じる" #define ITEM_NAME_BACK "前へ戻る" #define ITEM_NAME_FORWARD "次へ進む" #define ITEM_NAME_LOCK "タブをロックする" #define ITEM_NAME_LIVE "実況開始/停止" #define ITEM_NAME_NEWARTICLE "新スレ作成" #define ITEM_NAME_SEARCHBOX "検索ボックス" #define ITEM_NAME_SEARCH_NEXT "次検索" #define ITEM_NAME_SEARCH_PREV "前検索" #define ITEM_NAME_NEXTARTICLE "次スレ検索" #define ITEM_NAME_CLEAR_HIGHLIGHT "ハイライト解除" #define ITEM_NAME_INSERTTEXT "テキストファイル挿入" #define ITEM_NAME_LOCK_MESSAGE "書き込み後に閉じない" #define ITEM_NAME_PREVIEW "プレビュー表示" #define ITEM_NAME_UNDO "元に戻す(Undo)" #define ITEM_NAME_REDO "やり直し(Redo)" #define ITEM_NAME_NGWORD "NGワード" #define ITEM_NAME_ABONE_SELECTION "選択範囲のレスをあぼ〜ん" #define ITEM_NAME_ABONE_ARTICLE "スレをあぼ〜んする" #define ITEM_NAME_QUOTE_SELECTION "引用してレスする" #define ITEM_NAME_OPEN_BROWSER "ブラウザで開く" #define ITEM_NAME_OPEN_CACHE_BROWSER "キャッシュをブラウザで開く" #define ITEM_NAME_USER_COMMAND "ユーザコマンド" #define ITEM_NAME_ETC "その他" #define ITEM_NAME_SAVE_DAT "datを保存" #define ITEM_NAME_SELECTIMG "選択範囲の画像を開く" #define ITEM_NAME_SELECTDELIMG "選択範囲の画像を削除" #define ITEM_NAME_SELECTABONEIMG "選択範囲の画像をあぼ〜ん" #define ITEM_NAME_PREFERENCEVIEW "プロパティ" #define ITEM_NAME_PREF_BOARD "板のプロパティ" #define ITEM_NAME_PREF_THREAD "スレのプロパティ" #define ITEM_NAME_PREF_IMAGE "画像のプロパティ" // SESSION::get_item_*() の戻り値 enum { ITEM_BBSLISTVIEW = 0, ITEM_FAVORITEVIEW, ITEM_BOARDVIEW, ITEM_HISTVIEW, ITEM_HIST_BOARDVIEW, ITEM_HIST_CLOSEVIEW, ITEM_HIST_CLOSEBOARDVIEW, ITEM_HIST_CLOSEIMGVIEW, ITEM_ARTICLEVIEW, ITEM_IMAGEVIEW, ITEM_URL, ITEM_GO, ITEM_SEPARATOR, ITEM_MARK, ITEM_ID, ITEM_BOARD, ITEM_NAME, ITEM_RES, ITEM_LOAD, ITEM_NEW, ITEM_SINCE, ITEM_LASTWRITE, ITEM_ACCESS, ITEM_SPEED, ITEM_DIFF, ITEM_WRITEMSG, ITEM_OPENBOARD, ITEM_OPENARTICLETAB, ITEM_REGETARTICLE, ITEM_BOOKMARK, ITEM_SEARCH, ITEM_DRAWOUT, ITEM_RELOAD, ITEM_STOPLOADING, ITEM_APPENDFAVORITE, ITEM_FAVORITE_ARTICLE, ITEM_CHECK_UPDATE_ROOT, ITEM_CHECK_UPDATE_OPEN_ROOT, ITEM_COPY, ITEM_COPY_URL, ITEM_COPY_TITLE_URL, ITEM_COPY_TITLE_URL_THREAD, ITEM_COPY_THREAD_INFO, ITEM_DELETE, ITEM_QUIT, ITEM_BACK, ITEM_FORWARD, ITEM_LOCK, ITEM_LIVE, ITEM_NEWARTICLE, ITEM_SEARCHBOX, ITEM_SEARCH_NEXT, ITEM_SEARCH_PREV, ITEM_NEXTARTICLE, ITEM_CLEAR_HIGHLIGHT, ITEM_INSERTTEXT, ITEM_LOCK_MESSAGE, ITEM_PREVIEW, ITEM_UNDO, ITEM_REDO, ITEM_NGWORD, ITEM_ABONE_SELECTION, ITEM_ABONE_ARTICLE, ITEM_QUOTE_SELECTION, ITEM_OPEN_BROWSER, ITEM_OPEN_CACHE_BROWSER, ITEM_USER_COMMAND, ITEM_ETC, ITEM_SAVE_DAT, ITEM_SELECTIMG, ITEM_SELECTDELIMG, ITEM_SELECTABONEIMG, ITEM_PREFERENCEVIEW, ITEM_PREF_BOARD, ITEM_PREF_THREAD, ITEM_PREF_IMAGE, ITEM_END }; // 板やスレッドの状態 enum { STATUS_UNKNOWN = 0, // 不明 STATUS_NORMAL = 1 << 0, // 通常 STATUS_OLD = 1 << 1, // DAT落ち or 板が移転した STATUS_BROKEN = 1 << 2, // あぼーんなどで壊れている STATUS_UPDATE = 1 << 3, // 更新可能 STATUS_UPDATED = 1 << 4, // 更新済み STATUS_BROKEN_SUBJECT = 1 << 5, // subject.txt が壊れている( subject.txt に示されたレス数よりも実際の取得数の方が多い ) STATUS_OVERFLOW = 1 << 6 // レス数が最大表示可能数以上 }; // オートリロードのモード enum { AUTORELOAD_NOT = 0, AUTORELOAD_ONCE, // 1 回だけリロードして終わり AUTORELOAD_ON // オートリロード実行中 }; enum { AUTORELOAD_MINSEC = 2, // オートリロードの最小秒数 MIN_LIVE_RELOAD_SEC = 10, // 実況時の最小リロード間隔 WAITLOADIMG_SEC = 2, // 画像のロード待ち間隔 CHECKUPDATE_MINSEC = 60 // 更新チェックの最小秒数 }; // 実況スクロールモード enum { LIVE_SCRMODE_VARIABLE = 0, // 速度可変、速度がしきい値を越えると行単位でスクロール LIVE_SCRMODE_STEADY, // 速度一定、遅れがしきい値を越えると行単位でスクロール LIVE_SCRMODE_NUM }; // プロトコル #define PROTO_ANCHORE "anc://" #define PROTO_RES "res://" #define PROTO_NAME "name://" #define PROTO_ID "ID://" #define PROTO_BE "BE://" #define PROTO_ABONE "abone://" #define PROTO_OR "or://" #define PROTO_BM "bm://" #define PROTO_BROKEN "bloken://" #define PROTO_POSTLOG "postlog://" #define PROTO_SSSP "sssp://" // 仮想 URL #define URL_LOGIN2CH "jdlogin://login2ch" #define URL_LOGINBE "jdlogin://loginbe" #define URL_BBSLISTADMIN "jdadmin://bbslist" #define URL_BOARDADMIN "jdadmin://board" #define URL_ARTICLEADMIN "jdadmin://article" #define URL_IMAGEADMIN "jdadmin://image" #define URL_MESSAGEADMIN "jdadmin://message" #define URL_BBSLISTVIEW "jdview://bbslist" #define URL_FAVORITEVIEW "jdview://favorite" #define URL_HISTTHREADVIEW "jdview://histthread" #define URL_HISTBOARDVIEW "jdview://histboard" #define URL_HISTCLOSEVIEW "jdview://histclose" #define URL_HISTCLOSEBOARDVIEW "jdview://histcloseboard" #define URL_HISTCLOSEIMGVIEW "jdview://histcloseimg" #define URL_ALLLOG "jdview://alllog" #define URL_USRCMD "jdpref://usrcmd" #define URL_LINKFILTER "jdpref://linkfilter" #define URL_REPLACESTR "jdpref://replacestr" #define URL_BROWSER "jdpref://browser" #define URL_ABOUTCONFIG "jdpref://aboutconfig" #define URL_PRIVACY "jdpref://privacy" #define URL_BOARD_LOCAL "file:///local" #define URL_SEARCH_ALLBOARD "allboard" #define URL_SEARCH_TITLE "title" #endif jdim-0.7.0/src/globalabonepref.h000066400000000000000000000052211417047150700165310ustar00rootroot00000000000000// ライセンス: GPL2 // 全体あぼーん設定ダイアログ #ifndef _GLOBALABONPREF_H #define _GLOBALABONPREF_H #include "skeleton/prefdiag.h" #include "skeleton/editview.h" #include "config/globalconf.h" #include "dbtree/interface.h" #include "jdlib/miscutil.h" #include "command.h" namespace CORE { class GlobalAbonePref : public SKELETON::PrefDiag { Gtk::Notebook m_notebook; SKELETON::EditView m_edit_name, m_edit_word, m_edit_regex; Gtk::Label m_label_warning; // OK押した void slot_ok_clicked() override { // 全体あぼーん再設定 std::list< std::string > list_name = MISC::get_lines( m_edit_name.get_text() ); std::list< std::string > list_word = MISC::get_lines( m_edit_word.get_text() ); std::list< std::string > list_regex = MISC::get_lines( m_edit_regex.get_text() ); CONFIG::set_list_abone_name( list_name ); CONFIG::set_list_abone_word( list_word ); CONFIG::set_list_abone_regex( list_regex ); // あぼーん情報更新 DBTREE::update_abone_all_article(); CORE::core_set_command( "relayout_all_article" ); } public: GlobalAbonePref( Gtk::Window* parent, const std::string& url ) : SKELETON::PrefDiag( parent, url ) { // name std::list< std::string > list_name = CONFIG::get_list_abone_name(); m_edit_name.set_text( MISC::concat_with_suffix( list_name, '\n' ) ); // word std::list< std::string > list_word = CONFIG::get_list_abone_word(); m_edit_word.set_text( MISC::concat_with_suffix( list_word, '\n' ) ); // regex std::list< std::string > list_regex = CONFIG::get_list_abone_regex(); m_edit_regex.set_text( MISC::concat_with_suffix( list_regex, '\n' ) ); m_label_warning.set_text( "ここでのあぼーん設定は全板の全スレに適用されます。\n\n設定のし過ぎは全板の全スレの表示速度を低下させます。\n\n指定のし過ぎに気を付けてください。" ); m_notebook.append_page( m_label_warning, "注意" ); m_notebook.append_page( m_edit_name, "NG 名前" ); m_notebook.append_page( m_edit_word, "NG ワード" ); m_notebook.append_page( m_edit_regex, "NG 正規表現" ); get_content_area()->pack_start( m_notebook ); set_title( "全体あぼ〜ん設定" ); resize( 600, 400 ); show_all_children(); } ~GlobalAbonePref() noexcept = default; }; } #endif jdim-0.7.0/src/globalabonethreadpref.h000066400000000000000000000125471417047150700177320ustar00rootroot00000000000000// ライセンス: GPL2 // 全体スレあぼーん設定ダイアログ #ifndef _GLOBALABONTHREADPREF_H #define _GLOBALABONTHREADPREF_H #include "skeleton/prefdiag.h" #include "skeleton/editview.h" #include "config/globalconf.h" #include "dbtree/interface.h" #include "jdlib/miscutil.h" #include "command.h" namespace CORE { class GlobalAboneThreadPref : public SKELETON::PrefDiag { Gtk::Notebook m_notebook; SKELETON::EditView m_edit_word, m_edit_regex; Gtk::Label m_label_warning; Gtk::VBox m_vbox_abone_thread; Gtk::Label m_label_abone_thread; Gtk::Box m_hbox_low_number; Gtk::Label m_label_low_number; Gtk::SpinButton m_spin_low_number; Gtk::Box m_hbox_high_number; Gtk::Label m_label_high_number; Gtk::SpinButton m_spin_high_number; Gtk::HBox m_hbox_hour; Gtk::Label m_label_hour; Gtk::SpinButton m_spin_hour; // OK押した void slot_ok_clicked() override { // 全体あぼーん再設定 // スレ数、時間 CONFIG::set_abone_low_number_thread( m_spin_low_number.get_value_as_int() ); CONFIG::set_abone_high_number_thread( m_spin_high_number.get_value_as_int() ); CONFIG::set_abone_hour_thread( m_spin_hour.get_value_as_int() ); // word std::list< std::string > list_word = MISC::get_lines( m_edit_word.get_text() ); // regex std::list< std::string > list_regex = MISC::get_lines( m_edit_regex.get_text() ); CONFIG::set_list_abone_word_thread( list_word ); CONFIG::set_list_abone_regex_thread( list_regex ); // スレ一覧再描画 DBTREE::update_abone_thread(); } public: GlobalAboneThreadPref( Gtk::Window* parent, const std::string& url ) : SKELETON::PrefDiag( parent, url ) , m_hbox_low_number{ Gtk::ORIENTATION_HORIZONTAL, 4 } , m_hbox_high_number{ Gtk::ORIENTATION_HORIZONTAL, 4 } { // スレ数、時間 m_label_abone_thread.set_text( "以下の数字が0の時は未設定になります。\nまたキャッシュにログがあるスレはあぼ〜んされません。\n\n" ); m_label_low_number.set_text( "レス以下のスレをあぼ〜ん" ); m_spin_low_number.set_range( 0, CONFIG::get_max_resnumber() ); m_spin_low_number.set_increments( 1, 1 ); m_spin_low_number.set_value( CONFIG::get_abone_low_number_thread() ); m_hbox_low_number.pack_start( m_spin_low_number, Gtk::PACK_SHRINK ); m_hbox_low_number.pack_start( m_label_low_number, Gtk::PACK_SHRINK ); set_activate_entry( m_spin_low_number ); m_label_high_number.set_text( "レス以上のスレをあぼ〜ん" ); m_spin_high_number.set_range( 0, CONFIG::get_max_resnumber() ); m_spin_high_number.set_increments( 1, 1 ); m_spin_high_number.set_value( CONFIG::get_abone_high_number_thread() ); m_hbox_high_number.pack_start( m_spin_high_number, Gtk::PACK_SHRINK ); m_hbox_high_number.pack_start( m_label_high_number, Gtk::PACK_SHRINK ); set_activate_entry( m_spin_high_number ); m_label_hour.set_text( "時間以上スレ立てから経過したスレをあぼ〜ん" ); m_spin_hour.set_range( 0, 9999 ); m_spin_hour.set_increments( 1, 1 ); m_spin_hour.set_value( CONFIG::get_abone_hour_thread() ); m_hbox_hour.set_spacing( 4 ); m_hbox_hour.pack_start( m_spin_hour, Gtk::PACK_SHRINK ); m_hbox_hour.pack_start( m_label_hour, Gtk::PACK_SHRINK ); set_activate_entry( m_spin_hour ); m_vbox_abone_thread.set_border_width( 16 ); m_vbox_abone_thread.set_spacing( 8 ); m_vbox_abone_thread.pack_start( m_label_abone_thread, Gtk::PACK_SHRINK ); m_vbox_abone_thread.pack_start( m_hbox_low_number, Gtk::PACK_SHRINK ); m_vbox_abone_thread.pack_start( m_hbox_high_number, Gtk::PACK_SHRINK ); m_vbox_abone_thread.pack_start( m_hbox_hour, Gtk::PACK_SHRINK ); // word std::list< std::string > list_word = CONFIG::get_list_abone_word_thread(); m_edit_word.set_text( MISC::concat_with_suffix( list_word, '\n' ) ); // regex std::list< std::string > list_regex = CONFIG::get_list_abone_regex_thread(); m_edit_regex.set_text( MISC::concat_with_suffix( list_regex, '\n' ) ); m_label_warning.set_text( "ここでのあぼーん設定は全板のスレ一覧に適用されます。\n\n設定のし過ぎは全板の全スレ一覧表示速度を低下させます。\n\n指定のし過ぎに気を付けてください。" ); m_notebook.append_page( m_label_warning, "注意" ); m_notebook.append_page( m_vbox_abone_thread, "一般" ); m_notebook.append_page( m_edit_word, "NG ワード" ); m_notebook.append_page( m_edit_regex, "NG 正規表現" ); get_content_area()->pack_start( m_notebook ); set_title( "全体スレあぼ〜ん設定" ); resize( 600, 400 ); show_all_children(); } ~GlobalAboneThreadPref() noexcept = default; }; } #endif jdim-0.7.0/src/gtkmmversion.h000066400000000000000000000007211417047150700161340ustar00rootroot00000000000000// ライセンス: GPL2 #ifndef GTKMM_VERSION_H #define GTKMM_VERSION_H // GTK_CHECK_VERSION in gtk/gtkversion.h (GPL2) #ifndef GTKMM_CHECK_VERSION #define GTKMM_CHECK_VERSION(major,minor,micro) \ (GTKMM_MAJOR_VERSION > (major) || \ (GTKMM_MAJOR_VERSION == (major) && GTKMM_MINOR_VERSION > (minor)) || \ (GTKMM_MAJOR_VERSION == (major) && GTKMM_MINOR_VERSION == (minor) && \ GTKMM_MICRO_VERSION >= (micro))) #endif #endif // GTKMM_VERSION_H jdim-0.7.0/src/history/000077500000000000000000000000001417047150700147375ustar00rootroot00000000000000jdim-0.7.0/src/history/Makefile.am000066400000000000000000000004741417047150700170000ustar00rootroot00000000000000noinst_LIBRARIES = libhistory.a libhistory_a_SOURCES = \ historymanager.cpp \ historymenu.cpp \ historysubmenu.cpp \ viewhistory.cpp noinst_HEADERS = \ historymanager.h \ historymenu.h \ historysubmenu.h \ viewhistory.h \ viewhistoryitem.h AM_CXXFLAGS = @GTKMM_CFLAGS@ AM_CPPFLAGS = -I$(top_srcdir)/src jdim-0.7.0/src/history/historymanager.cpp000066400000000000000000000372331417047150700205070ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "historymanager.h" #include "historymenu.h" #include "viewhistory.h" #include "viewhistoryitem.h" #include "dbtree/interface.h" #include "xml/document.h" #include "xml/tools.h" #include "cache.h" #include "session.h" #include "sharedbuffer.h" #include "command.h" #include "global.h" HISTORY::History_Manager* instance_history_manager = nullptr; HISTORY::History_Manager* HISTORY::get_history_manager() { if( ! instance_history_manager ) instance_history_manager = new History_Manager(); assert( instance_history_manager ); return instance_history_manager; } void HISTORY::delete_history_manager() { if( instance_history_manager ) delete instance_history_manager; instance_history_manager = nullptr; } // url_history で指定した履歴に追加 void HISTORY::append_history( const std::string& url_history, const std::string& url, const std::string& name, const int type ) { get_history_manager()->append_history( url_history, url, name, type ); } // url_history で指定した履歴の先頭を復元 void HISTORY::restore_history( const std::string& url_history ) { get_history_manager()->restore_history( url_history ); } // url_history で指定した履歴を全クリア void HISTORY::remove_allhistories( const std::string& url_history ) { get_history_manager()->remove_allhistories( url_history ); } /////////////////////////////////////////////// using namespace HISTORY; // XML ルート要素名 #define ROOT_NODE_NAME "viewhistory" History_Manager::History_Manager() { #ifdef _DEBUG std::cout << "History_Manager::History_Manager\n"; #endif xml2viewhistory(); } // 履歴メニュー取得 Gtk::MenuItem* History_Manager::get_menu_thread() { if( ! m_menu_thread ) { m_menu_thread = std::make_unique( URL_HISTTHREADVIEW, std::string( ITEM_NAME_HISTVIEW ) + "(_T)" ); } return m_menu_thread.get(); } Gtk::MenuItem* History_Manager::get_menu_board() { if( ! m_menu_board ) { m_menu_board = std::make_unique( URL_HISTBOARDVIEW, std::string( ITEM_NAME_HIST_BOARDVIEW ) + "(_B)" ); } return m_menu_board.get(); } Gtk::MenuItem* History_Manager::get_menu_close() { if( ! m_menu_close ) { m_menu_close = std::make_unique( URL_HISTCLOSEVIEW, std::string( ITEM_NAME_HIST_CLOSEVIEW ) + "(_M)" ); } return m_menu_close.get(); } Gtk::MenuItem* History_Manager::get_menu_closeboard() { if( ! m_menu_closeboard ) { m_menu_closeboard = std::make_unique( URL_HISTCLOSEBOARDVIEW, std::string( ITEM_NAME_HIST_CLOSEBOARDVIEW ) + "(_N)" ); } return m_menu_closeboard.get(); } Gtk::MenuItem* History_Manager::get_menu_closeimg() { if( ! m_menu_closeimg ) { m_menu_closeimg = std::make_unique( URL_HISTCLOSEIMGVIEW, std::string( ITEM_NAME_HIST_CLOSEIMGVIEW ) + "(_I)" ); } return m_menu_closeimg.get(); } // url_history で指定した履歴に追加 void History_Manager::append_history( const std::string& url_history, const std::string& url, const std::string& name, const int type ) { if( SESSION::is_booting() ) return; CORE::DATA_INFO info; info.type = type; if( url_history == URL_HISTTHREADVIEW || url_history == URL_HISTCLOSEVIEW ){ info.url = DBTREE::url_dat( url ); info.name = DBTREE::article_subject( info.url ); } if( url_history == URL_HISTBOARDVIEW || url_history == URL_HISTCLOSEBOARDVIEW ){ if( type == TYPE_BOARD ){ info.url = DBTREE::url_boardbase( url ); info.name = DBTREE::board_name( info.url ); } else{ info.url = url; info.name = name; } } if( url_history == URL_HISTCLOSEIMGVIEW ){ info.url = url; info.name = name; } CORE::DATA_INFO_LIST list_info; list_info.push_back( info ); CORE::SBUF_set_list( list_info ); CORE::core_set_command( "append_history", url_history ); set_menulabel( url_history ); } // url_history で指定した履歴の先頭を復元 void History_Manager::restore_history( const std::string& url_history ) { if( url_history == URL_HISTCLOSEVIEW && m_menu_close ) m_menu_close->restore_history(); if( url_history == URL_HISTCLOSEBOARDVIEW && m_menu_closeboard ) m_menu_closeboard->restore_history(); if( url_history == URL_HISTCLOSEIMGVIEW && m_menu_closeimg ) m_menu_closeimg->restore_history(); } // url_history で指定した履歴を全クリア void History_Manager::remove_allhistories( const std::string& url_history ) { CORE::core_set_command( "remove_allhistories", url_history ); } // url_history で指定した履歴メニューのラベルを更新 void History_Manager::set_menulabel( const std::string& url_history ) { if( url_history == URL_HISTTHREADVIEW && m_menu_thread ) m_menu_thread->set_menulabel(); if( url_history == URL_HISTBOARDVIEW && m_menu_board ) m_menu_board->set_menulabel(); if( url_history == URL_HISTCLOSEVIEW && m_menu_close ) m_menu_close->set_menulabel(); if( url_history == URL_HISTCLOSEBOARDVIEW && m_menu_closeboard ) m_menu_closeboard->set_menulabel(); if( url_history == URL_HISTCLOSEIMGVIEW && m_menu_closeimg ) m_menu_closeimg->set_menulabel(); } /////////////////////////////////////////////////////////////////////////////// // // XMLを読み込んで View履歴に変換 // void History_Manager::xml2viewhistory() { std::string xml; CACHE::load_rawdata( CACHE::path_xml_history_view(), xml ); #ifdef _DEBUG std::cout << "History_Manager::xml2viewhistory\n"; std::cout << "xml:\n" << xml << std::endl; #endif if( xml.empty() ) return; const XML::Document document( xml ); const XML::Dom* root = document.get_root_element( std::string( ROOT_NODE_NAME ) ); for( const XML::Dom* subdir : *root ){ if( subdir->nodeType() != XML::NODE_TYPE_ELEMENT ) continue; // viewhistory 作成 const int type = XML::get_type( subdir->nodeName() ); if( type != TYPE_DIR ) continue; const int top = atoi( subdir->getAttribute( "top" ).c_str() ); const int cur = atoi( subdir->getAttribute( "cur" ).c_str() ); const int end = atoi( subdir->getAttribute( "end" ).c_str() ); #ifdef _DEBUG std::cout << "\n---------------\nnew viewhistory\n" << "top = " << top << std::endl << "cur = " << cur << std::endl << "end = " << end << std::endl; #endif m_view_histories.emplace_back(); ViewHistory& history = m_view_histories.back(); // viewhistory に item を append for( const XML::Dom* histitem : *subdir ) { if( histitem->nodeType() != XML::NODE_TYPE_ELEMENT ) continue; const int item_type = XML::get_type( histitem->nodeName() ); if( item_type != TYPE_HISTITEM ) continue; const std::string name = histitem->getAttribute( "name" ); const std::string url = histitem->getAttribute( "url" ); #ifdef _DEBUG std::cout << "type = " << item_type << std::endl << "name = " << name << std::endl << "url = " << url << std::endl; #endif history.append( url ); history.replace_current_title( name ); } history.set_top( top ); history.set_cur( cur ); history.set_end( end ); } } // // View履歴をXMLに変換して保存 // void History_Manager::viewhistory2xml() { #ifdef _DEBUG std::cout << "History_Manager::viewhistory2xml\n"; #endif XML::Document document; // タブに表示されているViewのアドレスを取得 std::set< std::string > taburls; const std::list< std::string >& article_urls = SESSION::get_article_URLs(); const std::list< std::string >& board_urls = SESSION::get_board_URLs(); for( const std::string& url : article_urls ) { #ifdef _DEBUG std::cout << "insert " << url << std::endl; #endif taburls.insert( url ); } for( const std::string& url : board_urls ) { #ifdef _DEBUG std::cout << "insert " << url << std::endl; #endif taburls.insert( url ); } // root XML::Dom* root = document.appendChild( XML::NODE_TYPE_ELEMENT, std::string( ROOT_NODE_NAME ) ); for( const ViewHistory& history : m_view_histories ) { const int size = history.get_size(); const int top = history.get_top(); const int cur = history.get_cur(); const int end = history.get_end(); #ifdef _DEBUG std::cout << "\n---------------\nviewhistory\n" << "size = "<< size << std::endl << "top = " << top << std::endl << "cur = " << cur << std::endl << "end = " << end << std::endl << "url = " << history.get_item( cur )->url << std::endl << "title = " << history.get_item( cur )->title << std::endl; #endif // タブに表示されていない履歴はXMLにしない if( taburls.find( history.get_item( cur )->url ) == taburls.end() ) continue; #ifdef _DEBUG std::cout << "make xml\n"; #endif std::string node_name = XML::get_name( TYPE_DIR ); XML::Dom* node = root->appendChild( XML::NODE_TYPE_ELEMENT, node_name ); node->setAttribute( "top", top ); node->setAttribute( "cur", cur ); node->setAttribute( "end", end ); if( ! size ) continue; node_name = XML::get_name( TYPE_HISTITEM ); for( int i = 0; i < size; ++i ){ XML::Dom* node_hist = node->appendChild( XML::NODE_TYPE_ELEMENT, node_name ); node_hist->setAttribute( "name", history.get_item( i )->title ); node_hist->setAttribute( "url", history.get_item( i )->url ); } } std::string xml; if( root->hasChildNodes() ) xml = document.get_xml(); if( ! xml.empty() ){ CACHE::save_rawdata( CACHE::path_xml_history_view(), xml ); #ifdef _DEBUG std::cout << xml << std::endl; #endif } } // // View履歴取得 // ViewHistory* History_Manager::get_viewhistory( const std::string& url ) { if( url.empty() ) return nullptr; // キャッシュ if( m_last_viewhistory && m_last_viewhistory->get_current_url() == url ) return m_last_viewhistory; #ifdef _DEBUG std::cout << "History_Manager::get_view_history : " << url << std::endl << "size = " << m_view_histories.size() << std::endl; #endif auto it = std::find_if( m_view_histories.begin(), m_view_histories.end(), [&url]( auto& h ) { return h.get_current_url() == url; } ); if( it != m_view_histories.end() ) { #ifdef _DEBUG std::cout << "found\n"; #endif // NOTE: 挿入削除で参照が無効にならないコンテナが条件 m_last_viewhistory = std::addressof( *it ); return m_last_viewhistory; } #ifdef _DEBUG std::cout << "not found\n"; #endif return nullptr; } // // View履歴作成 // void History_Manager::create_viewhistory( const std::string& url ) { ViewHistory* history = get_viewhistory( url ); if( history ) return; #ifdef _DEBUG std::cout << "History_Manager::create_viewhistory : " << url << std::endl; #endif m_view_histories.emplace_back(); m_view_histories.back().append( url ); } // // View履歴削除 // void History_Manager::delete_viewhistory( const std::string& url ) { #ifdef _DEBUG std::cout << "History_Manager::delete_view_history : " << url << std::endl << "size = " << m_view_histories.size() << std::endl; #endif auto it = std::find_if( m_view_histories.begin(), m_view_histories.end(), [&url]( auto& h ) { return h.get_current_url() == url; } ); if( it != m_view_histories.end() ) { m_view_histories.erase( it ); m_last_viewhistory = nullptr; } } // // 履歴全体で url_old を url_new に変更 // void History_Manager::replace_url_viewhistory( const std::string& url_old, const std::string& url_new ) { #ifdef _DEBUG std::cout << "History_Manager::replace_url_viewhistory\n" << "old = " << url_old << std::endl << "new = " << url_new << std::endl; #endif for( auto& h : m_view_histories ) { h.replace_url( url_old, url_new ); } } // // View履歴タイトル更新 // bool History_Manager::replace_current_title_viewhistory( const std::string& url, const std::string& title ) { #ifdef _DEBUG std::cout << "History_Manager::replace_current_title_viewhistory\n" << "url = " << url << std::endl << "new = " << title << std::endl; #endif ViewHistory* history = get_viewhistory( url ); if( !history ) return false; history->replace_current_title( title ); return true; } // item の取得 std::vector< ViewHistoryItem* >& History_Manager::get_items_back_viewhistory( const std::string& url, const int count ) { static std::vector< ViewHistoryItem* > nullitems; ViewHistory* history = get_viewhistory( url ); if( !history ) return nullitems; return history->get_items_back( count ); } std::vector< ViewHistoryItem* >& History_Manager::get_items_forward_viewhistory( const std::string& url, const int count ) { static std::vector< ViewHistoryItem* > nullitems; ViewHistory* history = get_viewhistory( url ); if( !history ) return nullitems; return history->get_items_forward( count ); } // // View履歴追加 // bool History_Manager::append_viewhistory( const std::string& url_current, const std::string& url_append ) { #ifdef _DEBUG std::cout << "History_Manager::append_viewhistory" << std::endl << "currnet = " << url_current << std::endl << "append = " << url_append << std::endl; #endif ViewHistory* history = get_viewhistory( url_current ); if( !history ) return false; history->append( url_append ); return true; } // // View履歴「戻る」可能 // bool History_Manager::can_back_viewhistory( const std::string& url, const int count ) { ViewHistory* history = get_viewhistory( url ); if( ! history ) return false; return history->can_back( count ); } // // View履歴「進む」可能 // bool History_Manager::can_forward_viewhistory( const std::string& url, const int count ) { ViewHistory* history = get_viewhistory( url ); if( ! history ) return false; return history->can_forward( count ); } // // View履歴戻る // // exec = true のときは履歴の位置を変更する // false の時はURLの取得のみ // const ViewHistoryItem* History_Manager::back_viewhistory( const std::string& url, const int count, const bool exec ) { #ifdef _DEBUG std::cout << "History_Manager::back_viewhistory count = " << count << " exec = " << exec << " url = " << url << std::endl; #endif ViewHistory* history = get_viewhistory( url ); if( ! history ) return nullptr; return history->back( count, exec ); } // // View履歴進む // // exec = true のときは履歴の位置を変更する // false の時はURLの取得のみ // const ViewHistoryItem* History_Manager::forward_viewhistory( const std::string& url, const int count, const bool exec ) { #ifdef _DEBUG std::cout << "History_Manager::forward_viewhistory count = " << count << " exec = " << exec << " url = " << url << std::endl; #endif ViewHistory* history = get_viewhistory( url ); if( ! history ) return nullptr; return history->forward( count, exec ); } jdim-0.7.0/src/history/historymanager.h000066400000000000000000000077011417047150700201510ustar00rootroot00000000000000// ライセンス: GPL2 // // 履歴管理クラス // #ifndef _HISTORYMANAGER_H #define _HISTORYMANAGER_H #include #include #include #include namespace Gtk { class Menu; class MenuItem; } namespace HISTORY { class HistoryMenu; class ViewHistory; struct ViewHistoryItem; class History_Manager { // 履歴メニュー std::unique_ptr m_menu_thread; std::unique_ptr m_menu_board; std::unique_ptr m_menu_close; std::unique_ptr m_menu_closeboard; std::unique_ptr m_menu_closeimg; // View履歴 std::list m_view_histories; ViewHistory* m_last_viewhistory{}; public: History_Manager(); virtual ~History_Manager() noexcept = default; // 履歴メニュー取得 Gtk::MenuItem* get_menu_thread(); Gtk::MenuItem* get_menu_board(); Gtk::MenuItem* get_menu_close(); Gtk::MenuItem* get_menu_closeboard(); Gtk::MenuItem* get_menu_closeimg(); // url_history で指定した履歴に追加 void append_history( const std::string& url_history, const std::string& url, const std::string& name, const int type ); // url_history で指定した履歴の先頭を復元 void restore_history( const std::string& url_history ); // url_history で指定した履歴を全クリア void remove_allhistories( const std::string& url_history ); // url_history で指定した履歴メニューのラベルを更新 void set_menulabel( const std::string& url_history ); ////////////////////////////////////////////////////////////////////////////// // // View履歴 public: // View履歴をXMLに変換して保存 void viewhistory2xml(); // 作成 / 削除 void create_viewhistory( const std::string& url ); void delete_viewhistory( const std::string& url ); // 履歴全体で url_old を url_new に変更 void replace_url_viewhistory( const std::string& url_old, const std::string& url_new ); // タイトル更新 bool replace_current_title_viewhistory( const std::string& url, const std::string& title ); // item の取得 std::vector< ViewHistoryItem* >& get_items_back_viewhistory( const std::string& url, const int count ); std::vector< ViewHistoryItem* >& get_items_forward_viewhistory( const std::string& url, const int count ); // 追加 bool append_viewhistory( const std::string& url_current, const std::string& url_append ); // 戻る / 進む // exec = true のときは履歴の位置を変更する // false の時はURLの取得のみ bool can_back_viewhistory( const std::string& url, const int count ); bool can_forward_viewhistory( const std::string& url, const int count ); const ViewHistoryItem* back_viewhistory( const std::string& url, const int count, const bool exec ); const ViewHistoryItem* forward_viewhistory( const std::string& url, const int count, const bool exec ); private: // XMLを読み込んで View履歴に変換 void xml2viewhistory(); // View履歴取得 ViewHistory* get_viewhistory( const std::string& url ); }; /////////////////////////////////////// // インターフェース History_Manager* get_history_manager(); void delete_history_manager(); // url_history で指定した履歴に追加 void append_history( const std::string& url_history, const std::string& url, const std::string& name, const int type ); // url_history で指定した履歴の先頭を復元 void restore_history( const std::string& url_history ); // url_history で指定した履歴を全クリア void remove_allhistories( const std::string& url_history ); } #endif jdim-0.7.0/src/history/historymenu.cpp000066400000000000000000000024131417047150700200310ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "historymenu.h" #include "historysubmenu.h" using namespace HISTORY; HistoryMenu::HistoryMenu( const std::string& url_history, const std::string& label ) : Gtk::MenuItem( label, true ) { m_submenu = Gtk::manage( new HistorySubMenu( url_history ) ); set_submenu( *m_submenu ); signal_activate().connect( sigc::mem_fun( *this, &HistoryMenu::slot_activate_menu ) ); signal_deselect().connect( sigc::mem_fun( *this, &HistoryMenu::slot_deactivate_menu ) ); } HistoryMenu::~HistoryMenu() noexcept = default; void HistoryMenu::restore_history() { m_submenu->restore_history(); } // ラベルをセットする void HistoryMenu::set_menulabel() { if( ! m_activate ) return; if( ! m_submenu ) return; #ifdef _DEBUG std::cout << "HistoryMenu::set_menulabel\n"; #endif m_submenu->set_menulabel(); } // activeになった void HistoryMenu::slot_activate_menu() { #ifdef _DEBUG std::cout << "HistoryMenu::slot_activate_menu\n"; #endif m_activate = true; set_menulabel(); } // メニューが deactive になった void HistoryMenu::slot_deactivate_menu() { #ifdef _DEBUG std::cout << "HistoryMenu::slot_deactivate_menu\n"; #endif m_activate = false; } jdim-0.7.0/src/history/historymenu.h000066400000000000000000000012711417047150700174770ustar00rootroot00000000000000// ライセンス: GPL2 // // 履歴メニュー // #ifndef _HISTORYMENU_H #define _HISTORYMENU_H #include namespace HISTORY { class HistorySubMenu; class HistoryMenu : public Gtk::MenuItem { HistorySubMenu* m_submenu; bool m_activate{}; public: HistoryMenu( const std::string& url_history, const std::string& label ); ~HistoryMenu() noexcept; // 履歴の先頭を復元 void restore_history(); void set_menulabel(); private: // メニューがactiveになった時にラベルをセットする void slot_activate_menu(); void slot_deactivate_menu(); }; } #endif jdim-0.7.0/src/history/historysubmenu.cpp000066400000000000000000000226421417047150700205510ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "historysubmenu.h" #include "command.h" #include "prefdiagfactory.h" #include "session.h" #include "jdlib/miscutil.h" #include "skeleton/msgdiag.h" #include "dbtree/interface.h" #include "dbimg/imginterface.h" #include "config/globalconf.h" #include "control/controlid.h" #include "control/controlutil.h" #include "xml/tools.h" using namespace HISTORY; #define HIST_NONAME "--------------" enum { SPACING_MENU = 3, // アイコンと項目名の間のスペース HIST_MAX_LNG = 50 // 履歴に表示する文字数(半角) }; HistorySubMenu::HistorySubMenu( const std::string& url_history ) : Gtk::Menu(), m_url_history( url_history ) { Gtk::MenuItem* item; // メニュー項目作成 // 履歴クリア Gtk::Menu* menu = Gtk::manage( new Gtk::Menu() ); item = Gtk::manage( new Gtk::MenuItem( "クリアする(_C)", true ) ); menu->append( *item ); item->signal_activate().connect( sigc::mem_fun( *this, &HistorySubMenu::slot_clear ) ); item = Gtk::manage( new Gtk::MenuItem( "履歴クリア(_C)", true ) ); item->set_submenu( *menu ); append( *item ); item = Gtk::manage( new Gtk::MenuItem( "サイドバーに全て表示(_S)", true ) ); append( *item ); item->signal_activate().connect( sigc::mem_fun( *this, &HistorySubMenu::slot_switch_sideber ) ); // セパレータ item = Gtk::manage( new Gtk::SeparatorMenuItem() ); append( *item ); // 履歴項目 for( int i = 0; i < CONFIG::get_history_size(); ++i ){ Gtk::Image* image = Gtk::manage( new Gtk::Image() ); m_vec_images.push_back( image ); Gtk::Label* label = Gtk::manage( new Gtk::Label( HIST_NONAME ) ); m_vec_label.push_back( label ); Gtk::Label *label_motion = Gtk::manage( new Gtk::Label() ); if( i == 0 ) label_motion->set_text( CONTROL::get_str_motions( CONTROL::RestoreLastTab ) ); Gtk::HBox* hbox = Gtk::manage( new Gtk::HBox() ); hbox->set_spacing( SPACING_MENU ); hbox->pack_start( *image, Gtk::PACK_SHRINK ); hbox->pack_start( *label, Gtk::PACK_SHRINK ); hbox->pack_end( *label_motion, Gtk::PACK_SHRINK ); item = Gtk::manage( new Gtk::MenuItem( *hbox ) ); append( *item ); item->signal_activate().connect( sigc::bind< int >( sigc::mem_fun( *this, &HistorySubMenu::slot_active ), i ) ); item->signal_button_press_event().connect( sigc::bind< int >( sigc::mem_fun( *this, &HistorySubMenu::slot_button_press ), i ) ); } // ポップアップメニュー作成 m_popupmenu.signal_deactivate().connect( sigc::mem_fun( *this, &HistorySubMenu::deactivate ) ); item = Gtk::manage( new Gtk::MenuItem( "タブで開く" ) ); item->signal_activate().connect( sigc::mem_fun( *this, &HistorySubMenu::slot_open_history ) ); m_popupmenu.append( *item ); item = Gtk::manage( new Gtk::SeparatorMenuItem() ); m_popupmenu.append( *item ); item = Gtk::manage( new Gtk::MenuItem( "履歴から削除" ) ); item->signal_activate().connect( sigc::mem_fun( *this, &HistorySubMenu::slot_remove_history ) ); m_popupmenu.append( *item ); item = Gtk::manage( new Gtk::SeparatorMenuItem() ); m_popupmenu.append( *item ); item = Gtk::manage( new Gtk::MenuItem( "プロパティ" ) ); item->signal_activate().connect( sigc::mem_fun( *this, &HistorySubMenu::slot_show_property ) ); m_popupmenu.append( *item ); m_popupmenu.show_all_children(); } HistorySubMenu::~HistorySubMenu() { #ifdef _DEBUG std::cout << "HistorySubMenu::~HistorySubMenu\n"; #endif } // 履歴の先頭を復元 void HistorySubMenu::restore_history() { #ifdef _DEBUG std::cout << "HistorySubMenu::restore_history " << m_url_history << std::endl; #endif if( open_history( 0 ) ) CORE::core_set_command( "remove_headhistory", m_url_history ); } // 履歴を開く bool HistorySubMenu::open_history( const int i ) { bool ret = false; CORE::DATA_INFO_LIST info_list; SESSION::get_history( m_url_history, info_list ); if( (int)info_list.size() <= i ) return ret; if( ! info_list[ i ].url.empty() ){ #ifdef _DEBUG std::cout << "open " << info_list[ i ].url << std::endl; #endif const std::string tab = "newtab"; const std::string mode = ""; switch( info_list[ i ].type ){ case TYPE_THREAD: case TYPE_THREAD_UPDATE: case TYPE_THREAD_OLD: CORE::core_set_command( "open_article" , DBTREE::url_dat( info_list[ i ].url ), tab, mode ); ret = true; break; case TYPE_BOARD: CORE::core_set_command( "open_board", DBTREE::url_boardbase( info_list[ i ].url ), tab, mode ); ret = true; break; case TYPE_VBOARD: CORE::core_set_command( "open_sidebar_board", info_list[ i ].url, tab, mode, "", "set_history" ); ret = true; break; case TYPE_IMAGE: if( DBIMG::get_abone( info_list[ i ].url )){ SKELETON::MsgDiag mdiag( nullptr, "あぼ〜んされています" ); mdiag.run(); } else{ CORE::core_set_command( "open_image", info_list[ i ].url ); CORE::core_set_command( "switch_image" ); ret = true; } break; } } return ret; } // メニューアイテムがactiveになった void HistorySubMenu::slot_active( const int i ) { #ifdef _DEBUG std::cout << "HistorySubMenu::slot_key_press key = " << "no = " << i << std::endl; #endif m_number_menuitem = i; open_history( i ); } // マウスボタンをクリックした bool HistorySubMenu::slot_button_press( GdkEventButton* event, int i ) { #ifdef _DEBUG std::cout << "HistorySubMenu::slot_button_press button = " << event->button << " no = " << i << std::endl; #endif m_number_menuitem = i; // ポップアップメニュー表示 if( event->button == 3 ) { m_popupmenu.popup_at_pointer( reinterpret_cast( event ) ); } return true; } // アクティブ時にラベルをセットする void HistorySubMenu::set_menulabel() { #ifdef _DEBUG std::cout << "HistorySubMenu::set_menulabel\n"; #endif CORE::DATA_INFO_LIST info_list; SESSION::get_history( m_url_history, info_list ); for( size_t i = 0; i < m_vec_label.size(); ++i ){ std::string name; int type = TYPE_UNKNOWN; if( i < info_list.size() ){ name = info_list[ i ].name; type = info_list[ i ].type; } if( name.empty() ) name = HIST_NONAME; m_vec_images[ i ]->set( XML::get_icon( type ) ); m_vec_label[ i ]->set_text( MISC::cut_str( name, HIST_MAX_LNG ) ); } } // 履歴クリア void HistorySubMenu::slot_clear() { #ifdef _DEBUG std::cout << "HistorySubMenu::slot_clear " << m_url_history << std::endl; #endif CORE::core_set_command( "remove_allhistories", m_url_history ); } // サイドバー切り替え void HistorySubMenu::slot_switch_sideber() { #ifdef _DEBUG std::cout << "HistorySubMenu::slot_switch_sideber " << m_url_history << std::endl; #endif CORE::core_set_command( "switch_sidebar", m_url_history ); } // 指定した履歴を開く // これを呼ぶ前に m_number_menuitem に番号をセットしておく void HistorySubMenu::slot_open_history() { #ifdef _DEBUG std::cout << "HistorySubMenu::slot_open_history no = " << m_number_menuitem << std::endl; #endif open_history( m_number_menuitem ); } // 指定した履歴を削除 // これを呼ぶ前に m_number_menuitem に番号をセットしておく void HistorySubMenu::slot_remove_history() { #ifdef _DEBUG std::cout << "HistorySubMenu::slot_remove_history no = " << m_number_menuitem << std::endl; #endif const int i = m_number_menuitem; CORE::DATA_INFO_LIST info_list; SESSION::get_history( m_url_history, info_list ); if( (int)info_list.size() <= i ) return; CORE::core_set_command( "remove_history", m_url_history, info_list[ i ].url ); } // プロパティ表示 // これを呼ぶ前に m_number_menuitem に番号をセットしておく void HistorySubMenu::slot_show_property() { #ifdef _DEBUG std::cout << "HistorySubMenu::slot_show_property no = " << m_number_menuitem << std::endl; #endif const int i = m_number_menuitem; CORE::DATA_INFO_LIST info_list; SESSION::get_history( m_url_history, info_list ); if( (int)info_list.size() <= i ) return; if( ! info_list[ i ].url.empty() ){ #ifdef _DEBUG std::cout << "url " << info_list[ i ].url << std::endl; #endif std::unique_ptr pref; switch( info_list[ i ].type ){ case TYPE_THREAD: case TYPE_THREAD_UPDATE: case TYPE_THREAD_OLD: pref= CORE::PrefDiagFactory( nullptr, CORE::PREFDIAG_ARTICLE, info_list[ i ].url ); break; case TYPE_BOARD: case TYPE_VBOARD: pref= CORE::PrefDiagFactory( nullptr, CORE::PREFDIAG_BOARD, info_list[ i ].url ); break; case TYPE_IMAGE: pref= CORE::PrefDiagFactory( nullptr, CORE::PREFDIAG_IMAGE, info_list[ i ].url ); break; } if( pref ){ pref->run(); } } } jdim-0.7.0/src/history/historysubmenu.h000066400000000000000000000023131417047150700202070ustar00rootroot00000000000000// ライセンス: GPL2 // // 履歴サブメニュー // #ifndef _HISTORYSUBMENU_H #define _HISTORYSUBMENU_H #include #include namespace HISTORY { class HistorySubMenu : public Gtk::Menu { std::string m_url_history; std::vector< Gtk::Image* > m_vec_images; std::vector< Gtk::Label* > m_vec_label; // ポップアップメニュー Gtk::Menu m_popupmenu; int m_number_menuitem{}; public: explicit HistorySubMenu( const std::string& url_history ); ~HistorySubMenu(); // 履歴の先頭を復元 void restore_history(); // アクティブ時にラベルをセットする void set_menulabel(); private: bool open_history( const int i ); // メニューのslot関数 void slot_clear(); void slot_switch_sideber(); // メニューアイテムがactiveになった void slot_active( const int i ); bool slot_button_press( GdkEventButton* event, int i ); // ポップアップメニューのslot void slot_open_history(); void slot_remove_history(); void slot_show_property(); }; } #endif jdim-0.7.0/src/history/meson.build000066400000000000000000000003571417047150700171060ustar00rootroot00000000000000sources = [ 'historymanager.cpp', 'historymenu.cpp', 'historysubmenu.cpp', 'viewhistory.cpp', ] history_lib = static_library( 'history', sources, dependencies : gtkmm_dep, include_directories : include_directories('..'), ) jdim-0.7.0/src/history/viewhistory.cpp000066400000000000000000000237531417047150700200510ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "viewhistory.h" using namespace HISTORY; enum { MAX_LOCAL_HISTORY = 20 // 履歴保持数 }; void ViewHistory::set_top( const int top ) { if( m_items.size() < MAX_LOCAL_HISTORY && top > m_history_top ) return; m_history_top = top; #ifdef _DEBUG std::cout << "ViewHistory::set_top" << std::endl << "size = " << m_items.size() << std::endl << "top = " << m_history_top << std::endl << "cur = " << m_history_current << std::endl << "end = " << m_history_end << std::endl; #endif } void ViewHistory::set_cur( const int cur ) { if( m_items.size() < MAX_LOCAL_HISTORY && cur > m_history_top ) m_history_current = m_history_top; else m_history_current = cur; #ifdef _DEBUG std::cout << "ViewHistory::set_cur" << std::endl << "size = " << m_items.size() << std::endl << "top = " << m_history_top << std::endl << "cur = " << m_history_current << std::endl << "end = " << m_history_end << std::endl; #endif } void ViewHistory::set_end( const int end ) { m_history_end = end; #ifdef _DEBUG std::cout << "ViewHistory::set_end" << std::endl << "size = " << m_items.size() << std::endl << "top = " << m_history_top << std::endl << "cur = " << m_history_current << std::endl << "end = " << m_history_end << std::endl; #endif } const std::string& ViewHistory::get_current_url() const { #ifdef _DEBUG std::cout << "ViewHistory::get_current_url" << std::endl << "size = " << m_items.size() << std::endl << "top = " << m_history_top << std::endl << "cur = " << m_history_current << std::endl << "end = " << m_history_end << std::endl; assert( m_items[ m_history_current ] != nullptr ); #endif return m_items[ m_history_current ]->url; } const std::string& ViewHistory::get_current_title() const { return m_items[ m_history_current ]->title; } // // URL更新 // void ViewHistory::replace_current_url( const std::string& url ) { #ifdef _DEBUG std::cout << "ViewHistory::replace_current_url\n" << "old = " << m_items[ m_history_current ]->url << std::endl << "new = " << url << std::endl << "size = " << m_items.size() << std::endl << "top = " << m_history_top << std::endl << "cur = " << m_history_current << std::endl << "end = " << m_history_end << std::endl; #endif m_items[ m_history_current ]->url = url; } void ViewHistory::replace_url( const std::string& url_old, const std::string& url_new ) { #ifdef _DEBUG std::cout << "ViewHistory::replace_url\n" << "old = " << url_old << std::endl << "new = " << url_new << std::endl << "size = " << m_items.size() << std::endl << "top = " << m_history_top << std::endl << "cur = " << m_history_current << std::endl << "end = " << m_history_end << std::endl; #endif for( auto& item : m_items ) { if( item->url == url_old ) { item->url = url_new; #ifdef _DEBUG std::cout << "replaced\n"; #endif } } } // // タイトル更新 // void ViewHistory::replace_current_title( const std::string& title ) { #ifdef _DEBUG std::cout << "ViewHistory::replace_current_title\n" << "old = " << m_items[ m_history_current ]->title << std::endl << "new = " << title << std::endl << "size = " << m_items.size() << std::endl << "top = " << m_history_top << std::endl << "cur = " << m_history_current << std::endl << "end = " << m_history_end << std::endl; #endif m_items[ m_history_current ]->title = title; } // item の取得 std::vector< ViewHistoryItem* >& ViewHistory::get_items_back( const int count ) const { static std::vector< ViewHistoryItem* > items; items.clear(); if( count <= 0 ) return items; int tmp_current = m_history_current; for( int i = 0; i < count; ++i ){ if( tmp_current == m_history_end ) break; tmp_current = ( tmp_current + MAX_LOCAL_HISTORY - 1 ) % MAX_LOCAL_HISTORY; items.push_back( m_items[ tmp_current ].get() ); } return items; } std::vector< ViewHistoryItem* >& ViewHistory::get_items_forward( const int count ) const { static std::vector< ViewHistoryItem* > items; items.clear(); if( count <= 0 ) return items; int tmp_current = m_history_current; for( int i = 0; i < count; ++i ){ if( tmp_current == m_history_top ) break; tmp_current = ( tmp_current + 1 ) % MAX_LOCAL_HISTORY; items.push_back( m_items[ tmp_current ].get() ); } return items; } // // 「戻る」可能 // bool ViewHistory::can_back( const int count ) const noexcept { if( count <= 0 ) return false; int tmp_current = m_history_current; for( int i = 0; i < count; ++i ){ if( tmp_current == m_history_end ) return false; tmp_current = ( tmp_current + MAX_LOCAL_HISTORY - 1 ) % MAX_LOCAL_HISTORY; } return true; } // // 「進む」可能 // bool ViewHistory::can_forward( const int count ) const noexcept { if( count <= 0 ) return false; int tmp_current = m_history_current; for( int i = 0; i < count; ++i ){ if( tmp_current == m_history_top ) return false; tmp_current = ( tmp_current + 1 ) % MAX_LOCAL_HISTORY; } return true; } // ローカル履歴追加 void ViewHistory::append( const std::string& url ) { #ifdef _DEBUG std::cout << "-------------\nViewhHstory::append : " << url << std::endl; #endif // 一番最初の呼び出し if( m_items.empty() ) { auto item = std::make_unique(); item->url = url; m_items.push_back( std::move( item ) ); } else{ if( get_current_url() == url ) return; // 既に登録されていないか確認 bool exist = false; int tmp_current = m_history_current; for( int i = 0; i < MAX_LOCAL_HISTORY; ++i ){ if( m_items[ tmp_current ]->url == url ){ exist = true; break; } if( tmp_current == m_history_end ) break; tmp_current = ( tmp_current + MAX_LOCAL_HISTORY - 1 ) % MAX_LOCAL_HISTORY; } // 登録されている場合は前に詰める if( exist ){ #ifdef _DEBUG std::cout << "exist\n"; #endif std::unique_ptr item = std::move( m_items[ tmp_current ] ); for( int i = 0; i < MAX_LOCAL_HISTORY; ++i ){ if( tmp_current == m_history_current ) break; int next = ( tmp_current + 1 ) % MAX_LOCAL_HISTORY; #ifdef _DEBUG std::cout << "current = " << tmp_current << " next = " << next << std::endl; #endif m_items[ tmp_current ] = std::move( m_items[ next ] ); tmp_current = next; } m_items[ tmp_current ] = std::move( item ); m_history_top = m_history_current; } // 新しく追加 else{ #ifdef _DEBUG std::cout << "append\n"; #endif bool pback = ( m_items.size() < MAX_LOCAL_HISTORY && m_history_top == ( int )m_items.size() -1 && m_history_top == m_history_current ); m_history_current = ( m_history_current + 1) % MAX_LOCAL_HISTORY; if( pback ){ #ifdef _DEBUG std::cout << "push_back\n"; #endif auto item = std::make_unique(); item->url = url; m_items.push_back( std::move( item ) ); } else m_items[ m_history_current ]->url = url; m_history_top = m_history_current; if( m_history_top == m_history_end ) m_history_end = ( m_history_end + 1 ) % MAX_LOCAL_HISTORY; } } #ifdef _DEBUG std::cout << "size = " << m_items.size() << std::endl << "top = " << m_history_top << std::endl << "cur = " << m_history_current << std::endl << "end = " << m_history_end << std::endl << "cur = " << get_current_url() << std::endl << "title = " << get_current_title() << std::endl; assert( get_current_url() == url ); #endif } // // 戻る // // exec = true のときは履歴の位置を変更する // false の時はURLの取得のみ // const ViewHistoryItem* ViewHistory::back( const int count, const bool exec ) { return back_forward( true, count, exec ); } // // 進む // // exec = true のときは履歴の位置を変更する // false の時はURLの取得のみ // const ViewHistoryItem* ViewHistory::forward( const int count, const bool exec ) { return back_forward( false, count, exec ); } // // 戻る / 進む // const ViewHistoryItem* ViewHistory::back_forward( const bool back, const int count, const bool exec ) { #ifdef _DEBUG std::cout << "ViewHistory::back_forward count = " << count << " back = " << back << " exec = " << exec << std::endl; #endif int tmp_current = m_history_current; if( back ){ if( ! can_back( count ) ) return nullptr; tmp_current = ( tmp_current + MAX_LOCAL_HISTORY - count ) % MAX_LOCAL_HISTORY; } else{ if( ! can_forward( count ) ) return nullptr; tmp_current = ( tmp_current + count ) % MAX_LOCAL_HISTORY; } // 更新 if( exec ) m_history_current = tmp_current; #ifdef _DEBUG std::cout << "size = " << m_items.size() << std::endl << "top = " << m_history_top << std::endl << "cur = " << m_history_current << std::endl << "end = " << m_history_end << std::endl << "ret.url = " << m_items[ tmp_current ]->url << std::endl << "ret.title = " << m_items[ tmp_current ]->title << std::endl; #endif return m_items[ tmp_current ].get(); } jdim-0.7.0/src/history/viewhistory.h000066400000000000000000000045341417047150700175120ustar00rootroot00000000000000// ライセンス: GPL2 // View履歴クラス // #ifndef _VIEW_HISTORY_H #define _VIEW_HISTORY_H #include "viewhistoryitem.h" #include #include #include namespace HISTORY { class ViewHistory { friend class History_Manager; // History_Manager 以外からは直接操作禁止 std::vector> m_items; int m_history_top{}; int m_history_current{}; int m_history_end{}; public: // privateだと std::list で要素の構築・削除ができない ViewHistory() = default; virtual ~ViewHistory() noexcept = default; private: int get_size() const noexcept { return m_items.size(); } const ViewHistoryItem* get_item( const int pos ) const { return m_items[ pos ].get(); } int get_top() const noexcept { return m_history_top; } int get_cur() const noexcept { return m_history_current; } int get_end() const noexcept { return m_history_end; } void set_top( const int top ); void set_cur( const int cur ); void set_end( const int end ); const std::string& get_current_url() const; const std::string& get_current_title() const; // URL更新 void replace_current_url( const std::string& url ); // 現在のアドレス void replace_url( const std::string& url_old, const std::string& url_new ); // 全体 // タイトル更新 void replace_current_title( const std::string& title ); // item の取得 std::vector< ViewHistoryItem* >& get_items_back( const int count ) const; std::vector< ViewHistoryItem* >& get_items_forward( const int count ) const; // 戻る / 進む 可能かの判定 bool can_back( const int count ) const noexcept; bool can_forward( const int count ) const noexcept; // 追加 void append( const std::string& url ); // 戻る / 進む // exec = true のときは履歴の位置を変更する // false の時はitemの取得のみ const ViewHistoryItem* back( const int count, const bool exec ); const ViewHistoryItem* forward( const int count, const bool exec ); const ViewHistoryItem* back_forward( const bool back, const int count, const bool exec ); }; } #endif jdim-0.7.0/src/history/viewhistoryitem.h000066400000000000000000000004041417047150700203610ustar00rootroot00000000000000// ライセンス: GPL2 // View履歴で使う構造体 // #ifndef _VIEW_HISTORYITEM_H #define _VIEW_HISTORYITEM_H #include namespace HISTORY { struct ViewHistoryItem { std::string url; std::string title; }; } #endif jdim-0.7.0/src/httpcode.h000066400000000000000000000007601417047150700152240ustar00rootroot00000000000000/* HTTP コード */ #ifndef _HTTPCODE_H #define _HTTPCODE_H enum { HTTP_ERR = -1, HTTP_INIT = 0, HTTP_CANCEL = 1, HTTP_OLD = 2, // offlaw や 過去ログ倉庫からの読み込み HTTP_OK = 200, HTTP_CREATED = 201, HTTP_PARTIAL_CONTENT = 206, HTTP_MOVED_PERM = 301, HTTP_REDIRECT = 302, HTTP_NOT_MODIFIED = 304, HTTP_FORBIDDEN = 403, HTTP_NOT_FOUND = 404, HTTP_TIMEOUT = 408, HTTP_RANGE_ERR = 416, HTTP_TEMP_UNAV = 503 }; #endif jdim-0.7.0/src/icons/000077500000000000000000000000001417047150700143515ustar00rootroot00000000000000jdim-0.7.0/src/icons/Makefile.am000066400000000000000000000015031417047150700164040ustar00rootroot00000000000000noinst_LIBRARIES = libicon.a libicon_a_SOURCES = \ iconmanager.cpp icon_headers = jd16.h jd32.h jd48.h jd96.h \ dir.h board.h board_update.h board_updated.h thread.h thread_update.h thread_updated.h thread_old.h \ image.h link.h loading.h loading_stop.h check.h down.h \ update.h newthread.h newthread_hour.h broken_subject.h bkmark.h bkmark_broken_subject.h bkmark_update.h bkmark_thread.h favorite.h write.h post.h post_refer.h \ hist.h hist_board.h hist_close.h hist_closeboard.h hist_closeimg.h info.h noinst_HEADERS = \ iconmanager.h iconid.h iconfiles.h $(icon_headers) AM_CXXFLAGS = @GTKMM_CFLAGS@ AM_CPPFLAGS = -I$(top_srcdir)/src DISTCLEANFILES = $(icon_headers) clean_icons : rm -rf $(icon_headers) iconmanager.cpp : $(icon_headers) SUFFIXES = .png .h .png.h: gdk-pixbuf-csource --raw --name=icon_$* $< > $@ jdim-0.7.0/src/icons/bkmark.png000066400000000000000000000002641417047150700163300ustar00rootroot00000000000000PNG  IHDRa{IDAT8 C鲏ڑOWu#d\TT@RmDH4?{9n)́q$ @o29B2HW%6^AT2 2bg8)dicncW(IENDB`jdim-0.7.0/src/icons/bkmark_broken_subject.png000066400000000000000000000002671417047150700214120ustar00rootroot00000000000000PNG  IHDRa~IDAT8 C0 ONB( QC-P~!_.-A CL(m!1kV&y3 bD5O[X5O8^c GG^h~IENDB`jdim-0.7.0/src/icons/bkmark_thread.png000066400000000000000000000003011417047150700176470ustar00rootroot00000000000000PNG  IHDR "aIDAT(K0\n=sA?~RIHI-$f%&c`G>(H*jJ(CWhҩ $'# WYCk[&Ŏ?px nq+8tmZURx3>c/f(8IENDB`jdim-0.7.0/src/icons/bkmark_update.png000066400000000000000000000002531417047150700176700ustar00rootroot00000000000000PNG  IHDRarIDAT8Q0 BxzrZ\KoMxa$L$Z@ rTŔ p @܁, \HFRWҦz]s p 5 3PBfh=m]ݎ"X6qIENDB`jdim-0.7.0/src/icons/board.png000066400000000000000000000003231417047150700161440ustar00rootroot00000000000000PNG  IHDRaIDAT8c?%HдЃ\&3z&7l a` !7o 9sv 12bP k0dg,/ p;xe w\p `DFe(ܡGVU$IENDB`jdim-0.7.0/src/icons/board_update.png000066400000000000000000000004151417047150700175100ustar00rootroot00000000000000PNG  IHDRaIDAT8 0EL,$ұ 3`@C"IDG(+\c y}ޝ- ISѻ42'@DRoc樺"&_` !8벜(Wge|;0mj$d&NvEɡ*DP^?<ݓ3z(GP 4^N6#LGۋݕfڦ^; K'Djx#SmIENDB`jdim-0.7.0/src/icons/board_updated.png000066400000000000000000000004521417047150700176550ustar00rootroot00000000000000PNG  IHDRaIDAT8S!@%hj*%4~@rcGR45\u%EЖ.4-vv'@d:՘5Db8U[dDmQ~Fd#wTm[TB /gD ̚YȊ;a5@.Q. (rEr^/U"6٤)4JIENDB`jdim-0.7.0/src/icons/check.png000066400000000000000000000003271417047150700161360ustar00rootroot00000000000000PNG  IHDRaIDAT8 C_Sw̧sd_ k`TUZHıMINdf Xg- HH :⫱^q69K W}B.k\$af^%F [s C.>ܯ q XW7=+2,\7D^DIENDB`jdim-0.7.0/src/icons/dir.png000066400000000000000000000002531417047150700156350ustar00rootroot00000000000000PNG  IHDRarIDAT8 9"-r(R]yH+RHj y!X&^5웥 X[>ni.G?E&0KpWu$@'(0 IENDB`jdim-0.7.0/src/icons/down.png000066400000000000000000000002631417047150700160270ustar00rootroot00000000000000PNG  IHDRazIDAT8 L 7dDl Ue7q  81ffc&gwVBZrv= ڦHZ!ObIENDB`jdim-0.7.0/src/icons/iconfiles.h000066400000000000000000000056451417047150700165070ustar00rootroot00000000000000// ライセンス: GPL2 // アイコンテーマのファイル #ifndef _ICONFILES_H #define _ICONFILES_H enum { MAX_ICON_FILES = 32 }; namespace ICON { char iconfiles[][ MAX_ICON_FILES ]={ "jd16", "jd32", "jd48", "jd96", // サイドバーで使用するアイコン "dir", "image", "link", // サイドバーやタブで使用するアイコン "board", "board_update", "thread", "thread_update", "thread_old", // タブで使用するアイコン "board_updated", "thread_updated", "loading", "loading_stop", // スレ一覧で使用するアイコン "bkmark_update", "bkmark_broken_subject", "bkmark", "update", "newthread", "newthread_hour", "broken_subject", "check", "old", "info", // スレビューで使用するアイコン "bkmark_thread", "post", "post_refer", // その他 "down", "transparent", //////////////////////// // ツールバーのアイコン // 共通 "search_prev", // 前検索 "search_next", // 次検索 "stoploading", // 読み込み中止 "write", // 書き込み / 新スレ作成 "reload", // 再読み込み "appendfavorite", // お気に入りに追加 "delete", // 削除 "quit", // 閉じる "back", // 前へ戻る "forward", // 次へ進む "lock", // タブをロックする "undo", // 元に戻す(Undo) "redo", // やり直し(Redo) // メイン "bbslistview", // 板一覧 "favoriteview", // お気に入り "histview", // スレ履歴 "hist_boardview", // 板履歴 "hist_closeview", // 最近閉じたスレ "hist_closeboardview", // 最近閉じた板 "hist_closeimgview", // 最近閉じた画像 "boardview", // スレ一覧 "articleview", // スレビュー "imageview", // 画像ビュー "go", // 移動 // サイドバー "check_update_root", // サイドバー更新チェック "check_update_open_root", // サイドバー更新チェックして開く // スレビュー "search", // 検索 "live", // 実況開始/停止 // 検索バー "close_search", // 検索バーを閉じる "clear_search", // ハイライト解除 "search_and", // AND 抽出 "search_or", // OR 抽出 // 書き込みビュー "preview", // プレビュー表示 "inserttext", // テキストファイル挿入 "" }; } #endif jdim-0.7.0/src/icons/iconid.h000066400000000000000000000055371417047150700160010ustar00rootroot00000000000000// ライセンス: GPL2 // アイコンのID // // !!注意!! 項目を増やしたら iconfiles.h も修正すること // #ifndef _ICONID_H #define _ICONID_H namespace ICON { enum { NONE = -1, JD16 = 0, JD32, JD48, JD96, // サイドバーで使用するアイコン DIR, IMAGE, LINK, // サイドバーやタブで使用するアイコン BOARD, BOARD_UPDATE, THREAD, THREAD_UPDATE, THREAD_OLD, // タブで使用するアイコン BOARD_UPDATED, THREAD_UPDATED, LOADING, LOADING_STOP, // スレ一覧で使用するアイコン BKMARK_UPDATE, BKMARK_BROKEN_SUBJECT, BKMARK, UPDATE, NEWTHREAD, NEWTHREAD_HOUR , BROKEN_SUBJECT, CHECK, OLD, INFO, // スレビューで使用するアイコン BKMARK_THREAD, POST, POST_REFER, // その他 DOWN, TRANSPARENT, //////////////////////// // ツールバーのアイコン // 共通 SEARCH_PREV, // 前検索 SEARCH_NEXT, // 次検索 STOPLOADING, // 読み込み中止 WRITE, // 書き込み / 新スレ作成 RELOAD, // 再読み込み APPENDFAVORITE, // お気に入りに追加 DELETE, // 削除 QUIT, // 閉じる BACK, // 前へ戻る FORWARD, // 次へ進む LOCK, // タブをロックする UNDO, // 元に戻す(Undo) REDO, // やり直し(Redo) // メイン BBSLISTVIEW, // 板一覧 FAVORITEVIEW, // お気に入り HISTVIEW, // スレ履歴 HIST_BOARDVIEW, // 板履歴 HIST_CLOSEVIEW, // 最近閉じたスレ HIST_CLOSEBOARDVIEW, // 最近閉じた板 HIST_CLOSEIMGVIEW, // 最近閉じた画像 BOARDVIEW, // スレ一覧 ARTICLEVIEW, // スレビュー IMAGEVIEW, // 画像ビュー GO, // 移動 // サイドバー CHECK_UPDATE_ROOT, // サイドバー更新チェック CHECK_UPDATE_OPEN_ROOT, // サイドバー更新チェックして開く // スレビュー SEARCH, // 検索 LIVE, // 実況開始/停止 // 検索バー CLOSE_SEARCH, // 検索バーを閉じる CLEAR_SEARCH, // ハイライト解除 SEARCH_AND, // AND 抽出 SEARCH_OR, // OR 抽出 // 書き込みビュー PREVIEW, // プレビュー表示 INSERTTEXT, // テキストファイル挿入 //////////////////////// NUM_ICONS }; } #endif jdim-0.7.0/src/icons/iconmanager.cpp000066400000000000000000000245601417047150700173470ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "gtkmmversion.h" #include "iconmanager.h" #include "iconfiles.h" #include "cache.h" #include "jd16.h" #include "jd32.h" #include "jd48.h" #include "jd96.h" #include "bkmark_update.h" #include "bkmark.h" #include "bkmark_broken_subject.h" #include "bkmark_thread.h" #include "update.h" #include "newthread.h" #include "newthread_hour.h" #include "broken_subject.h" #include "check.h" #include "down.h" #include "write.h" #include "post.h" #include "post_refer.h" #include "loading.h" #include "loading_stop.h" #include "dir.h" #include "favorite.h" #include "hist.h" #include "hist_board.h" #include "hist_close.h" #include "hist_closeboard.h" #include "hist_closeimg.h" #include "board.h" #include "board_update.h" #include "board_updated.h" #include "thread.h" #include "thread_update.h" #include "thread_updated.h" #include "thread_old.h" #include "image.h" #include "link.h" #include "info.h" #include ICON::ICON_Manager* instance_icon_manager = nullptr; ICON::ICON_Manager* ICON::get_icon_manager() { if( ! instance_icon_manager ) instance_icon_manager = new ICON::ICON_Manager(); assert( instance_icon_manager ); return instance_icon_manager; } void ICON::delete_icon_manager() { if( instance_icon_manager ) delete instance_icon_manager; instance_icon_manager = nullptr; } Glib::RefPtr< Gdk::Pixbuf > ICON::get_icon( int id ) { return get_icon_manager()->get_icon( id ); } /////////////////////////////////////////////// using namespace ICON; ICON_Manager::ICON_Manager() { m_list_icons.resize( NUM_ICONS ); m_list_icons[ ICON::JD16 ] = Gdk::Pixbuf::create_from_inline( sizeof( icon_jd16 ), icon_jd16 ); m_list_icons[ ICON::JD32 ] = Gdk::Pixbuf::create_from_inline( sizeof( icon_jd32 ), icon_jd32 ); m_list_icons[ ICON::JD48 ] = Gdk::Pixbuf::create_from_inline( sizeof( icon_jd48 ), icon_jd48 ); m_list_icons[ ICON::JD96 ] = Gdk::Pixbuf::create_from_inline( sizeof( icon_jd96 ), icon_jd96 ); // サイドバーで使用するアイコン m_list_icons[ ICON::DIR ] = Gdk::Pixbuf::create_from_inline( sizeof( icon_dir ), icon_dir ); m_list_icons[ ICON::IMAGE ] = Gdk::Pixbuf::create_from_inline( sizeof( icon_image ), icon_image ); m_list_icons[ ICON::LINK ] = Gdk::Pixbuf::create_from_inline( sizeof( icon_link ), icon_link ); // サイドバーやタブで使用するアイコン m_list_icons[ ICON::BOARD ] = Gdk::Pixbuf::create_from_inline( sizeof( icon_board ), icon_board ); m_list_icons[ ICON::BOARD_UPDATE ] = Gdk::Pixbuf::create_from_inline( sizeof( icon_board_update ), icon_board_update ); m_list_icons[ ICON::THREAD ] = Gdk::Pixbuf::create_from_inline( sizeof( icon_thread ), icon_thread ); m_list_icons[ ICON::THREAD_UPDATE ] = Gdk::Pixbuf::create_from_inline( sizeof( icon_thread_update ), icon_thread_update ); m_list_icons[ ICON::THREAD_OLD ] = Gdk::Pixbuf::create_from_inline( sizeof( icon_thread_old ), icon_thread_old ); // タブで使用するアイコン m_list_icons[ ICON::BOARD_UPDATED ] = Gdk::Pixbuf::create_from_inline( sizeof( icon_board_updated ), icon_board_updated ); m_list_icons[ ICON::THREAD_UPDATED ] = Gdk::Pixbuf::create_from_inline( sizeof( icon_thread_updated ), icon_thread_updated ); m_list_icons[ ICON::LOADING ] = Gdk::Pixbuf::create_from_inline( sizeof( icon_loading ), icon_loading ); m_list_icons[ ICON::LOADING_STOP ] = Gdk::Pixbuf::create_from_inline( sizeof( icon_loading_stop ), icon_loading_stop ); // スレ一覧で使用するアイコン m_list_icons[ ICON::BKMARK_UPDATE ] = Gdk::Pixbuf::create_from_inline( sizeof( icon_bkmark_update ), icon_bkmark_update ); m_list_icons[ ICON::BKMARK_BROKEN_SUBJECT ] = Gdk::Pixbuf::create_from_inline( sizeof( icon_bkmark_broken_subject ), icon_bkmark_broken_subject ); m_list_icons[ ICON::BKMARK ] = Gdk::Pixbuf::create_from_inline( sizeof( icon_bkmark ), icon_bkmark ); m_list_icons[ ICON::UPDATE ] = Gdk::Pixbuf::create_from_inline( sizeof( icon_update ), icon_update ); m_list_icons[ ICON::NEWTHREAD ] = Gdk::Pixbuf::create_from_inline( sizeof( icon_newthread ), icon_newthread ); m_list_icons[ ICON::NEWTHREAD_HOUR ] = Gdk::Pixbuf::create_from_inline( sizeof( icon_newthread_hour ), icon_newthread_hour ); m_list_icons[ ICON::BROKEN_SUBJECT ] = Gdk::Pixbuf::create_from_inline( sizeof( icon_broken_subject ), icon_broken_subject ); m_list_icons[ ICON::CHECK ] = Gdk::Pixbuf::create_from_inline( sizeof( icon_check ), icon_check ); m_list_icons[ ICON::OLD ] = Gdk::Pixbuf::create_from_inline( sizeof( icon_down ), icon_down ); m_list_icons[ ICON::INFO ] = Gdk::Pixbuf::create_from_inline( sizeof( icon_info ), icon_info ); // スレビューで使用するアイコン m_list_icons[ ICON::BKMARK_THREAD ] = Gdk::Pixbuf::create_from_inline( sizeof( icon_bkmark_thread ), icon_bkmark_thread ); m_list_icons[ ICON::POST ] = Gdk::Pixbuf::create_from_inline( sizeof( icon_post ), icon_post ); m_list_icons[ ICON::POST_REFER ] = Gdk::Pixbuf::create_from_inline( sizeof( icon_post_refer ), icon_post_refer ); // その他 m_list_icons[ ICON::DOWN ] = m_list_icons[ ICON::OLD ]; m_list_icons[ ICON::TRANSPARENT ] = Gdk::Pixbuf::create( Gdk::COLORSPACE_RGB, true, 8, 1, 1 ); m_list_icons[ ICON::TRANSPARENT ]->fill( 0 ); ////////////////////////////// // ツールバーのアイコン // アイコン名はfreedesktop.orgの規格とGTK3デフォルトテーマのAdwaitaを参照する // https://specifications.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html // https://gitlab.gnome.org/GNOME/adwaita-icon-theme const auto icon_theme = Gtk::IconTheme::get_default(); constexpr int size_menu = 16; // Gtk::ICON_SIZE_MENU // 共通 m_list_icons[ ICON::SEARCH_PREV ] = icon_theme->load_icon( "go-up", size_menu ); m_list_icons[ ICON::SEARCH_NEXT ] = icon_theme->load_icon( "go-down", size_menu ); m_list_icons[ ICON::STOPLOADING ] = icon_theme->load_icon( "process-stop", size_menu ); m_list_icons[ ICON::WRITE ] = Gdk::Pixbuf::create_from_inline( sizeof( icon_write ), icon_write ); m_list_icons[ ICON::RELOAD ] = icon_theme->load_icon( "view-refresh", size_menu ); m_list_icons[ ICON::APPENDFAVORITE ] = icon_theme->load_icon( "edit-copy", size_menu ); m_list_icons[ ICON::DELETE ] = icon_theme->load_icon( "edit-delete", size_menu ); m_list_icons[ ICON::QUIT ] = icon_theme->load_icon( "window-close", size_menu ); m_list_icons[ ICON::BACK ] = icon_theme->load_icon( "go-previous", size_menu ); m_list_icons[ ICON::FORWARD ] = icon_theme->load_icon( "go-next", size_menu ); try { // changes-prevent is not a standard icon name. m_list_icons[ ICON::LOCK ] = icon_theme->load_icon( "changes-prevent-symbolic", size_menu ); } catch( Gtk::IconThemeError& ) { m_list_icons[ ICON::LOCK ] = icon_theme->load_icon( "window-close", size_menu ); } // メイン m_list_icons[ ICON::BBSLISTVIEW ] = m_list_icons[ ICON::DIR ]; m_list_icons[ ICON::FAVORITEVIEW ] = Gdk::Pixbuf::create_from_inline( sizeof( icon_favorite ), icon_favorite ); m_list_icons[ ICON::HISTVIEW ] = Gdk::Pixbuf::create_from_inline( sizeof( icon_hist ), icon_hist ); m_list_icons[ ICON::HIST_BOARDVIEW ] = Gdk::Pixbuf::create_from_inline( sizeof( icon_hist_board ), icon_hist_board ); m_list_icons[ ICON::HIST_CLOSEVIEW ] = Gdk::Pixbuf::create_from_inline( sizeof( icon_hist_close ), icon_hist_close ); m_list_icons[ ICON::HIST_CLOSEBOARDVIEW ] = Gdk::Pixbuf::create_from_inline( sizeof( icon_hist_closeboard ), icon_hist_closeboard ); m_list_icons[ ICON::HIST_CLOSEIMGVIEW ] = Gdk::Pixbuf::create_from_inline( sizeof( icon_hist_closeimg ), icon_hist_closeimg ); m_list_icons[ ICON::BOARDVIEW ] = m_list_icons[ ICON::BOARD ]; m_list_icons[ ICON::ARTICLEVIEW ] = m_list_icons[ ICON::THREAD ]; m_list_icons[ ICON::IMAGEVIEW ] = m_list_icons[ ICON::IMAGE ]; m_list_icons[ ICON::GO ] = icon_theme->load_icon( "go-jump", size_menu ); m_list_icons[ ICON::UNDO ] = icon_theme->load_icon( "edit-undo", size_menu ); m_list_icons[ ICON::REDO ] = icon_theme->load_icon( "edit-redo", size_menu ); // サイドバー m_list_icons[ ICON::CHECK_UPDATE_ROOT ] = icon_theme->load_icon( "view-refresh", size_menu ); m_list_icons[ ICON::CHECK_UPDATE_OPEN_ROOT ] = Gdk::Pixbuf::create_from_inline( sizeof( icon_thread ), icon_thread ); // スレビュー m_list_icons[ ICON::SEARCH ] = icon_theme->load_icon( "edit-find", size_menu ); m_list_icons[ ICON::LIVE ] = icon_theme->load_icon( "media-playback-start", size_menu ); // 検索バー m_list_icons[ ICON::CLOSE_SEARCH ] = icon_theme->load_icon( "edit-undo", size_menu ); m_list_icons[ ICON::CLEAR_SEARCH ] = icon_theme->load_icon( "edit-clear", size_menu ); m_list_icons[ ICON::SEARCH_AND ] = icon_theme->load_icon( "edit-cut", size_menu ); m_list_icons[ ICON::SEARCH_OR ] = icon_theme->load_icon( "list-add", size_menu ); // 書き込みビュー m_list_icons[ ICON::PREVIEW ] = m_list_icons[ ICON::THREAD ]; m_list_icons[ ICON::INSERTTEXT ] = icon_theme->load_icon( "document-open", size_menu ); load_theme(); } ICON_Manager::~ICON_Manager() { #ifdef _DEBUG std::cout << "ICON::~ICON_Manager\n"; #endif } Glib::RefPtr< Gdk::Pixbuf > ICON_Manager::get_icon( const int id ) { return m_list_icons[ id ]; } // // アイコンテーマ読み込み // void ICON_Manager::load_theme() { if( CACHE::file_exists( CACHE::path_theme_icon_root() ) != CACHE::EXIST_DIR ) return; const std::list< std::string > files = CACHE::get_filelist( CACHE::path_theme_icon_root() ); if( ! files.size() ) return; #ifdef _DEBUG std::cout << "ICON::load_theme\n"; #endif for( const std::string& filename : files ) { #ifdef _DEBUG std::cout << filename << std::endl; #endif int id = 0; // 拡張子を探す const std::size_t i = filename.rfind( '.' ); while( iconfiles[ id ][ 0 ] != '\0' ){ // 拡張子を除いたファイル名を比較 if( filename.compare( 0, i, iconfiles[ id ] ) == 0 ) { #ifdef _DEBUG std::cout << "hit : " << iconfiles[ id ] << " id = " << id << std::endl; #endif m_list_icons[ id ] = Gdk::Pixbuf::create_from_file( CACHE::path_theme_icon_root() + filename ); break; } ++id; } } } jdim-0.7.0/src/icons/iconmanager.h000066400000000000000000000012541417047150700170070ustar00rootroot00000000000000// ライセンス: GPL2 // // アイコンの管理クラス // #ifndef _ICONNAGER_H #define _ICONNAGER_H #include "iconid.h" #include #include namespace ICON { class ICON_Manager { std::vector< Glib::RefPtr< Gdk::Pixbuf > > m_list_icons; public: ICON_Manager(); virtual ~ICON_Manager(); Glib::RefPtr< Gdk::Pixbuf > get_icon( const int id ); private: void load_theme(); }; /////////////////////////////////////// // インターフェース ICON_Manager* get_icon_manager(); void delete_icon_manager(); Glib::RefPtr< Gdk::Pixbuf > get_icon( const int id ); } #endif jdim-0.7.0/src/icons/image.png000066400000000000000000000004411417047150700161400ustar00rootroot00000000000000PNG  IHDRaIDAT8Mj@ ֻ^ ͢ФwU&GaRhr.yƮc R5鋪I#U,8DUBydC 3 *sBRHJr"R$ 'B"HJ s.@6ϫI p =+7wyY'Xn_jY=kœb69qV\&"%afVi:[/ːoElIENDB`jdim-0.7.0/src/icons/info.png000066400000000000000000000002111417047150700160040ustar00rootroot00000000000000PNG  IHDRaPIDAT8; b7rr\L(+ 8Z (X7[€C/1H@>`E )oIENDB`jdim-0.7.0/src/icons/jd16.png000066400000000000000000000005521417047150700156250ustar00rootroot00000000000000PNG  IHDRa1IDAT8ݓ1@H, uB,7!u,ҥPlvZ IJRUY\"uovffaM]|~pm.ZX\yky^.iP% x| B90 5jRާ{!i2`۽%I,p?|>W'8L&ۭ% xUE\W8&I8~{mf)%s~hTU۶.IMNearP{F`65;p8T{:IENDB`jdim-0.7.0/src/icons/jd32.png000066400000000000000000000017051417047150700156240ustar00rootroot00000000000000PNG  IHDR szzsBIT|d pHYsy(qtEXtSoftwarewww.inkscape.org<XtEXtCopyrightCC0 Public Domain Dedication http://creativecommons.org/publicdomain/zero/1.0/IDATX;H#A;C &>5bjD .D6ډ}P.6ڊ &\ٻ$w{ffxS__߷D" z{{9@,]]]_2<]+!麞0 IR[[[nxU z+INOO9!ޚ64@ ^&, `ww7oucrnZZZhhh`zz:}%zdY puu|YN(byy9gсid@%=)1eHB͙$I!j,/r?W`0PQMMMA// 4faahB DZXD"LMMpqqQ***RlIgttEFFFlYYZZ"g'UU+B[[VƼ7,ǔ>UU g޲I( 08;;PWWUn3hfv0[>v%x<s<+t] ˅P( sJ #Sٕkkk!~W#;dVVV>TCybgg_d^Qu] ?ZHAee%Ȳh4 X EQ̸4(IENDB`jdim-0.7.0/src/icons/jd48.png000066400000000000000000000022711417047150700156320ustar00rootroot00000000000000PNG  IHDR00WsBIT|d pHYsbb_'StEXtSoftwarewww.inkscape.org<XtEXtCopyrightCC0 Public Domain Dedication http://creativecommons.org/publicdomain/zero/1.0/IDAThKrYT"M FvrٶMHԢZӈ E6"E@fDQ4*1@͢bo~A{﹞{(@oKB>DA+`0p||#^l6*!0~LmIPB-I7`)WBm0Ĕ$=inL&ǔPqc tuu5&͖*Zߣt8NƘz@?QNcppX!D^*xxxV),,P(Tn!ʱVtb888vʀ$I lnnb144nggg'+HdjjUVpļ'rJKt:zzzH̼jS'HBF~i ZUUb`qq1[7!) rz^ɳ>6_K | d/l%mFwwwSL% ȲL +2www}Ը+++Φ\!^&&&7'''%̛L`X 0??j,󯮮Ae Bfsɤl|>jkkLz^" e&BKSSSRƤf COp--- connAnoo?>>rqqjK26WBB+{{{ 3::Jii)۸\.n&@gg'V5fϛ8J|>v;VU~D%%FAѤ/dH@%y!$I6-moohTsl%V+*++3I19K0VӧO4|| m^ةxAqZ%P J?jrD*y@2"!u P7޽{cH`٪' HP `dbb#YbΐΐTVVh4=#O͆҄6I%`~~?mrrMtx^ttt$swGoooSxpA^|gYqpp86$)wwwU,>}FW, ,+ρP(PH}R ⌡hݮطQ^^nH޽bߥڊ6ž@ ;wdHHfzihrѣGI/L.@Etuuw*q\I`ZOU>~FN'QTTo381jkkqg ̓ ,..*mnn㗛NSq1xsZz@ oPWW8nzz 1U S\+;?P^^gϞi c޽۷o+y_444p([ZZǏْDQLz!E`6mMMMiW¼2$3$3$3G΢Z ޿od.9j$app$ : &j5.F~,J)#rIDYD.#B_;\E  \]`!Ps~@Iu[xE&cW\AYYb6|)wss3~sUU ǭ9p\+@j%`f>r*s$-?g IENDB`jdim-0.7.0/src/icons/jd_windres.ico000066400000000000000000011152761417047150700172120ustar00rootroot00000000000000  P  (R  (]`` Fe@@ (B00 %<(( ha  &| Ό hVPNG  IHDR{` IDATxKG~dz;@ADpHJpHpq%'l)$ㄸ O!)XK8XrAXABI6QC;~Y/3U]UxwW= ,)2ĊsBX'*70l)z)_zxP`+n)b^?ׄF%t! `ш0i@xW` xV` $xUP?"<*P +!#)^ᏘYgދmrH?rfq϶69`qGN?*&ßXO g ß$dq@'4?Qj+w3af1}mNb;6/n2l 6ѕs,XU h8ٌ8M=#b@e4^E ^> Tȁ}܅Cx+~7ڜE-@w[$~VY |N}7F-9eWAc]pa]^+0 K)`)fJ+4 |x|R]s~,`=`}|Gli(@t4;XDIsҌ bPD(@VD_(@T$?(@"R`SȃrXX&=n*> }8;/?uӘ)s;lC iQez Mq ŷ]ƯxAJx~Txë GCA=m+dF 'HU@4NXl~W (@ 1(@^  PU <d^`rV fˇ(@$Q@nu)@4 ")`q?iP }A(@tFDP >Ya5𥳐(@( zDLk)p(p(p(p(p(p(p(p(p(p .xwϫPP)*ĕțP)*B;=?>}45|$%lR()}!R@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8@8Q B@}M6۴.ٯQI4>#&eτIL ~TBxIrIe8^ӨX3%FbT:0/ch\/H8g)GG98딿ƟS 5RѤ&@8@8T@poPhXHѸs 0ok`(7Ef9 `=z{X y!01pK 4 ; P(`L4(⫀0}Ń80Z+!>+R4i|(;)S fpS@ 3?̤,:q+2"h $ 96-/۰w7cJ%0Bߞ!yz$>l24l̢ٝ0I *VO_P3떚d`zxowo8h:)UW3\YGc+qs>OY bS6]@Wqm&F6JZ_0UjQcW$8w7YA5iodx <4&| qe'R“;tl;nLSFx:n?xz:\t-9l^sI'TB1@~ >a Ck?*           kZ@:SV`%@x@ +Xį&HKTR6P2 Zk (<3;: b\Il }$Q^`.Fp +,`YB/>Oso ' $A<޺1&K2K,H`2 H&dVT&LRl ^UKT@CNrĎ@cRILP $ k(crO KΣl<̐4B6[-\DQ8,}a]cqoutYliu~g#L`npcc,n[ÆkΠ\ğNAgϦTSeKtjT픎j@1R6cr]:Tm _ % tIENDB`( @>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>===<<<;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<>>>>>>>>>>>>>>>===;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<>>>>>>>>>>>>>>><<<;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<>>>>>>>>>>>>>>><<<;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;===>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>===KKK~~~{{{III===>>>>>>===WWWqqqBBB>>>>>>>>>>>>cccfff???>>>>>>>>>AAAoooZZZ===>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<___XXX<<<>>>>>><<<{{{HHH===>>>>>>>>>???>>>>>>>>>DDD;;;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<^^^XXX<<<>>>>>><<>>>>>>>>???>>>>>>>>>DDD;;;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<^^^XXX<<<>>>>>><<>>>>>>>>???>>>>>>>>>DDD;;;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<^^^XXX<<<>>>>>><<>>>>>>>>???>>>>>>>>>DDD;;;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<^^^XXX<<<>>>>>><<>>>>>>>>???>>>>>>>>>DDD;;;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<^^^XXX<<<>>>>>><<>>>>>>>>???>>>>>>>>>DDD;;;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<^^^XXX<<<>>>>>><<>>>>>>>>???>>>>>>>>>DDD;;;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<^^^XXX<<<>>>>>><<>>>>>>>>???>>>>>>>>>DDD;;;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<^^^XXX<<<>>>>>><<>>>>>>>>???>>>>>>>>>DDD;;;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<^^^XXX<<<>>>>>><<>>>>>>>>???>>>>>>>>>DDD;;;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<^^^XXX<<<>>>>>><<>>>>>>>>???>>>>>>>>>DDD;;;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<^^^XXX<<<>>>>>><<>>>>>>>>???>>>>>>>>>DDD;;;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<^^^XXX<<<>>>>>><<>>>>>>>>???>>>>>>>>>DDD;;;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>===<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<===>>>>>>>>><<<^^^XXX<<<>>>>>><<>>>>>>>>???>>>>>>>>>DDD;;;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>UUU`````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````___HHH===>>>>>><<<^^^XXX<<<>>>>>><<>>>>>>>>???>>>>>>>>>DDD;;;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>===rrrppp;;;>>>>>><<<^^^XXX<<<>>>>>><<>>>>>>>>???>>>>>>>>>DDD;;;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>===qqqttt;;;>>>>>><<<^^^XXX<<<>>>>>><<>>>>>>>>???>>>>>>>>>DDD;;;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>===qqqttt;;;>>>>>><<<^^^XXX<<<>>>>>><<>>>>>>>>???>>>>>>>>>DDD;;;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>===qqqttt;;;>>>>>><<<^^^XXX<<<>>>>>><<>>>>>>>>???>>>>>>>>>DDD;;;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>rrrttt;;;>>>>>><<<^^^XXX<<<>>>>>><<>>>>>>>>???>>>>>>>>>DDD;;;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>===VVVttt;;;>>>>>><<<^^^XXX<<<>>>>>><<>>>>>>>>???>>>>>>>>>DDD;;;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>><<<^^^XXX<<<>>>>>><<>>>>>>>>???>>>>>>>>>DDD;;;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>><<<^^^XXX<<<>>>>>><<>>>>>>>>???>>>>>>>>>DDD;;;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>><<<^^^XXX<<<>>>>>><<>>>>>>>>???>>>>>>>>>DDD;;;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>><<<^^^XXX<<<>>>>>><<>>>>>>>>???>>>>>>>>>DDD;;;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>><<<^^^XXX<<<>>>>>><<>>>>>>>>???>>>>>>>>>DDD;;;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>><<<^^^XXX<<<>>>>>><<>>>>>>>>???>>>>>>>>>DDD;;;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>><<<^^^XXX<<<>>>>>><<>>>>>>>>???>>>>>>>>>DDD;;;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>><<<^^^XXX<<<>>>>>><<>>>>>>>>???>>>>>>>>>DDD;;;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>><<<^^^XXX<<<>>>>>><<>>>>>>>>???>>>>>>>>>DDD;;;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>><<<^^^XXX<<<>>>>>><<>>>>>>>>???>>>>>>>>>DDD;;;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>><<<___XXX<<<>>>>>><<>>>>>>>>???>>>>>>>>>DDD;;;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>><<>>>>><<>>>>>>>>???>>>>>>>>>DDD;;;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>@@@HHHIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHH@@@>>>>>>>>><<>>>>>>>>???>>>>>>>>>DDD;;;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>==========================================>>>>>>>>>>>><<>>>>>>>>???>>>>>>>>>DDD;;;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>???>>>>>>>>>DDD;;;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>???>>>>>>>>>DDD;;;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>???>>>>>>===DDD;;;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>===PPPLLL===>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>><<<___XXX<<<>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>><<<^^^XXX<<<>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>><<<^^^XXX<<<>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>><<<^^^XXX<<<>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>><<<^^^XXX<<<>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>><<<^^^XXX<<<>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>><<<^^^XXX<<<>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>><<<^^^XXX<<<>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>><<<^^^XXX<<<>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>><<<^^^XXX<<<>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>><<<^^^XXX<<<>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>><<<^^^XXX<<<>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>><<<___XXX<<<>>>>>><<<{{{ttt>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>===RRRNNN===>>>>>>===bbbooo>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>???@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???>>>>>>>>>>>>>>>>>>???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????===>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>CCCEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDD???>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>}>>> >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>~>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>~>>> >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>~>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>~>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>~>>> >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>~>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>~>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>~>>> >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>~======>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>~>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>~>>> >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>===NNN\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>~======>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>===ZZZ\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>~>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>~>>> >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>~>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>}>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>~>>> >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>===NNN````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````OOO===>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>~======>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>===<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<===>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>~>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>~>>> >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>~======>>>>>>w>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>~>>>>>>>>>>>>>>>x>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>~>>> >>>>>>>>>>>>x>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>~>>>>>>>>>>>>>>>x>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>~>>>>>>>>>>>>>>>x>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>~>>> >>>>>>>>>>>>x>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>~>>>>>>>>>>>>>>>x>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>}>>>>>>>>>>>>>>>x>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>~>>> >>>>>>>>>>>>x>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>~>>>>>>>>>>>>>>>x>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>~>>>>>>>>>>>>>>>x>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>~>>> >>>>>>>>>>>>x>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>~>>>>>>>>>>>>>>>x>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>~>>>>>>>>>>>>>>>x>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>~>>> >>>>>>>>>>>>x>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>~>>>>>>>>>>>>>>>w>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>}>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> >>>?????( >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<;;;;;;;;;;;;;;;;;;;;;;;;<<<>>>>>>>>>===;;;;;;;;;;;;;;;;;;;;;;;;<<<>>>>>>>>>===;;;;;;;;;;;;;;;;;;;;;;;;<<<>>>>>>>>>===;;;;;;;;;;;;;;;;;;;;;;;;;;;===>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>???aaapppooooooooooooooooooppp___???>>>>>>YYYooonnnnnnnnnnnnnnnnnnoooeeeBBB>>>===QQQnnnnnnnnnnnnnnnnnnnnnooojjjFFF======JJJlllnnnnnnnnnnnnnnnnnnnnnmmmMMM===>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>BBB@@@>>>===JJJ===;;;}}}ZZZ<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>BBB@@@>>>===JJJ===;;;~~~ZZZ<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>BBB@@@>>>===JJJ===;;;~~~ZZZ<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>BBB@@@>>>===JJJ===;;;~~~ZZZ<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>BBB@@@>>>===JJJ===;;;~~~ZZZ<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>BBB@@@>>>===JJJ===;;;~~~ZZZ<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>BBB@@@>>>===JJJ===;;;~~~ZZZ<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>BBB@@@>>>===JJJ===;;;~~~ZZZ<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>===<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<===>>>>>>BBB@@@>>>===JJJ===;;;~~~ZZZ<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>===MMM\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[\\\MMM>>>>>>BBB@@@>>>===JJJ===;;;~~~ZZZ<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\<<<>>>BBB@@@>>>===JJJ===;;;~~~ZZZ<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\<<<>>>BBB@@@>>>===JJJ===;;;~~~ZZZ<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>===\\\<<<>>>BBB@@@>>>===JJJ===;;;~~~ZZZ<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>===NNN<<<>>>BBB@@@>>>===JJJ===;;;~~~ZZZ<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\<<<>>>BBB@@@>>>===JJJ===;;;~~~ZZZ<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\<<<>>>BBB@@@>>>===JJJ===;;;~~~ZZZ<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\<<<>>>BBB@@@>>>===JJJ===;;;~~~ZZZ<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\<<<>>>BBB@@@>>>===JJJ===;;;~~~ZZZ<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\<<<>>>BBB@@@>>>===JJJ===;;;~~~ZZZ<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\<<<>>>BBB@@@>>>===JJJ===;;;~~~ZZZ<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\<<<>>>BBB@@@>>>===JJJ===;;;~~~ZZZ<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\<<<>>>AAA@@@>>>===JJJ===;;;~~~ZZZ<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\hhh===>>>>>>GGGKKKKKKKKKKKKKKKKKKKKKKKKGGG>>>>>>===JJJ===;;;~~~ZZZ<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\333555555555555555555555555555555555555555555555555555555555555555^^^KKK444555555555555555555555:::>>>>>>>>>==============================>>>>>>===JJJ===;;;~~~ZZZ<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\666888888888888888888888888888888888888888888888888888888888888888666^^^NNN777888888888888888888888;;;>>>>>>>>><<<;;;;;;;;;;;;;;;;;;;;;;;;<<<>>>>>>===HHH;;;999|||XXX:::999dddooo;;;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\666888888888888888888888888888888888888888888888888888888888888888888666^^^NNN777888888888888888888888;;;>>>>>>???lll~~~~~~~~~~~~~~~~~~iii???>>>===~~~}}}}}}}}}ooo;;;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\666888888888888888888888888888888888888888888888888888888888888888888888666^^^NNN777888888888888888888888;;;>>>>>>BBB@@@>>>===ooo;;;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\666888888888888888888888888888888888888888888888888888888888888888888888888666^^^NNN777888888888888888888888;;;>>>>>>BBB@@@>>>===ooo;;;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\666888888888888888888888888888888888888888888888888888888888888888888888888888666^^^NNN777888888888888888888888;;;>>>>>>BBB@@@>>>===ppp;;;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\666888888888888888888888888888888888888888888888888888888888888888888888888888888666^^^NNN777888888888888888888888;;;>>>>>>BBB@@@>>>===bbb<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\666888888888888888888888888888888888888888888888888888888888888888888888888888888888666^^^NNN777888888888888888888888;;;>>>>>>BBB@@@>>>===|||???>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\666888888888888888888888888888888888888888888888888888888888888888888888888888888888888666^^^NNN777888888888888888888888;;;>>>>>>BBB@@@>>>===|||>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\666888888888888888888888888888888888888888888888888888888888888888888888888888888888888888666___NNN777888888888888888888888;;;>>>>>>BBB@@@>>>===|||>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\666888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888999NNN777888888888888888888888;;;>>>>>>BBB@@@>>>===|||>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\666888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888:::NNN777888888888888888888888;;;>>>>>>AAA@@@>>>===|||>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\666888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888:::NNN777888888888888888888888;;;>>>>>>???SSS[[[[[[[[[[[[[[[[[[[[[[[[QQQ>>>>>>>>>NNNZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZUUU???>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\666888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888:::NNN777888888888888888888888;;;>>>>>>>>>===<<<<<<<<<<<<<<<<<<<<<<<<===>>>>>>>>>===<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<===>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\666888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888:::NNN777888888888888888888888;;;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>===;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\333555555555555555555555555555555555555555666888888888888888888888888888888888888888888888888:::NNN777888888888888888888888:::=====================;;;::::::::::::::::::<<<=============================================<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\eeegggggggggggggggggggggggggggggggggggggggaaa>>>888888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888999WWWhhhggggggggggggdddBBB888888888888888888888888888888888888888888888777[[[<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\NNN777888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888:::]]]666888888888888888888888888888888888888888888666\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\OOO777888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888:::^^^666888888888888888888888888888888888888888888666\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\OOO777888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888:::^^^666888888888888888888888888888888888888888888666\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\OOO777888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888:::^^^666888888888888888888888888888888888888888888666\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\OOO777888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888:::^^^666888888888888888888888888888888888888888888666\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\OOO777888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888:::^^^666888888888888888888888888888888888888888888666\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\OOO777888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888:::^^^666888888888888888888888888888888888888888888666\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\OOO777888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888:::^^^666888888888888888888888888888888888888888888666\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\OOO777888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888:::^^^666888888888888888888888888888888888888888888666\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\OOO777888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888:::^^^666888888888888888888888888888888888888888888666\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\OOO777888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888:::^^^666888888888888888888888888888888888888888888666\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\OOO777888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888:::^^^666888888888888888888888888888888888888888888666\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\OOO777888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888:::^^^666888888888888888888888888888888888888888888666\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\OOO777888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888:::^^^666888888888888888888888888888888888888888888666\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\OOO777888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888:::^^^666888888888888888888888888888888888888888888666\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\OOO777888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888:::^^^666888888888888888888888888888888888888888888666\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\OOO777888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888:::^^^666888888888888888888888888888888888888888888666\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\OOO777888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888:::^^^666888888888888888888888888888888888888888888666\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\OOO777888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888:::^^^666888888888888888888888888888888888888888888666\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\OOO777888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888:::^^^666888888888888888888888888888888888888888888666\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\OOO777888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888:::^^^666888888888888888888888888888888888888888888666\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\OOO777888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888:::^^^666888888888888888888888888888888888888888888666\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\OOO777888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888:::^^^666888888888888888888888888888888888888888888666\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\OOO777888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888:::^^^666888888888888888888888888888888888888888888666\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\OOO777888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888:::^^^666888888888888888888888888888888888888888888666\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\OOO777888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888:::^^^666888888888888888888888888888888888888888888666\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\OOO777888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888:::^^^666888888888888888888888888888888888888888888666\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\OOO777888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888:::^^^666888888888888888888888888888888888888888888666\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\OOO777888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888:::^^^666888888888888888888888888888888888888888888666\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\OOO777888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888:::^^^666888888888888888888888888888888888888888888666\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\OOO777888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888:::^^^666888888888888888888888888888888888888888888666\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\OOO777888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888:::^^^666888888888888888888888888888888888888888888666\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\OOO777888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888:::^^^666888888888888888888888888888888888888888888666\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\OOO777888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888:::^^^666888888888888888888888888888888888888888888666\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\OOO777888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888:::^^^666888888888888888888888888888888888888888888666\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\OOO777888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888:::^^^666888888888888888888888888888888888888888888666\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\OOO777888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888:::^^^666888888888888888888888888888888888888888888666\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\OOO777888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888:::^^^666888888888888888888888888888888888888888888666\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\OOO777888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888:::^^^666888888888888888888888888888888888888888888666\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\NNN777888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888:::]]]666888888888888888888888888888888888888888888666\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\eeeggggggggggggaaa>>>888888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888888WWWhhhggggggggggggdddBBB777888888888888888888888888888888888888888888666\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\ddd222555555555555666888888888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888888666555555555555555555777888888888888888888888888888888888888888888888666\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\fff555888888888888888888888888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888666\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\fff555888888888888888888888888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888666\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\fff555888888888888888888888888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888666\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\fff555888888888888888888888888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888666\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\fff555888888888888888888888888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888555\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\fff555888888888888888888888888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888666SSS\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\fff555888888888888888888888888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888666RRR\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\fff555888888888888888888888888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888666RRR\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\fff555888888888888888888888888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888666RRR\\\<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\fff555888888888888888888888888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888666RRRRRR===>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\fff555888888888888888888888888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888666RRRmmm>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\fff555888888888888888888888888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888666RRRmmm<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\fff555888888888888888888888888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888666RRRmmm<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\fff555888888888888888888888888888888888888888888888888888888888888888:::NNN777888888888888888888888888888888888888888888888888888888888888888888888888888888888888888666RRRmmm<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\ddd222555555555555555555555555555555555555555555555555555555555555555777KKK444555555555555555555555555555555555555555555555555555555555555555555555555555555555555444RRRmmm<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\mmm<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\mmm<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\mmm<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\mmm<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>???>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\mmm<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>???>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\mmm<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>???>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\mmm<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>???>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\mmm<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>???>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<\\\mmm<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>???>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>===IIImmm<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>???>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>===PPPmmm<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>???>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>???>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>???>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>===III[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[\\\RRR>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>???>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>===<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<===>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>???>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>;;;>>>>>>y>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>???>>>>>>>>>z>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>???>>>>>>>>>z>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>???>>>>>>>>>z>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>???>>>>>>>>>z>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>???>>>>>>>>>z>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>???>>>>>>>>>z>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>???>>>>>>>>>z>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>???>>>>>>>>>z>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>???>>>>>>>>>y>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>???>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>$>>>??????(` >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>===<<<<<<<<<<<<<<<<<<===>>>>>><<<<<<<<<<<<<<<<<<<<<===>>>>>><<<<<<<<<<<<<<<<<<<<<===>>>>>><<<<<<<<<<<<<<<<<<<<<===>>>>>>>>>>>> >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>UUUggggggggggggggggggSSS>>>>>>XXXgggfffffffffffffffOOO===???\\\gggfffffffffffffffLLL===AAA___gggffffffffffffeeeHHH===>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>===<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>===<<>>>>>>>> >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>===<<>>>>>>>> >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>===<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>===<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>===<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>===<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<===>>>===<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>===<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>===<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>===TTT>>>===<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<>>>===<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<>>>===<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<>>>===<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<>>>===<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<>>>===<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<>>><<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<>>>===eeeccc===???;;;DDDuuu;;;JJJggg<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>><<<;;;;;;;;;;;;;;;;;;<<<>>>???;;;CCCuuu:::IIIggg<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>AAADDDDDDDDDDDDDDDDDDAAA>>>???AAAIIIyyy@@@OOOggg<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>===}}}===???ggg<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>===<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>===<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>===<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>===<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>===<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>===<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>]]]vvvuuuuuuuuuuuuvvv[[[===>>>bbbuuutttttttttttttttttttttttttttttttttttttttttttttttttttttttttuuusssPPP<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>===;;;;;;;;;;;;;;;;;;===>>>>>><<<;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;===>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>><<<;;;;;;;;;;;;<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>===CCCKKKKKKKKKKKKKKKKKKKKKEEE>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<666888888888888888888888888888888666555888888888888888888888888888888666777888888888888888888888888888888555<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<555888888888888888888888888888888666555888888888888888888888888888888666777888888888888888888888888888888555<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<555888888888888888888888888888888666555888888888888888888888888888888666777888888888888888888888888888888555<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<555888888888888888888888888888888666555888888888888888888888888888888666777888888888888888888888888888888555<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<555888888888888888888888888888888666555888888888888888888888888888888666777888888888888888888888888888888555<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<555888888888888888888888888888888666555888888888888888888888888888888666777888888888888888888888888888888555<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<555888888888888888888888888888888666555888888888888888888888888888888666777888888888888888888888888888888555<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<555888888888888888888888888888888666555888888888888888888888888888888666777888888888888888888888888888888555<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<555888888888888888888888888888888666555888888888888888888888888888888666777888888888888888888888888888888555<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<555888888888888888888888888888888666555888888888888888888888888888888666777888888888888888888888888888888555<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<555888888888888888888888888888888666555888888888888888888888888888888666777888888888888888888888888888888555<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<555888888888888888888888888888888666555888888888888888888888888888888666777888888888888888888888888888888555<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<555888888888888888888888888888888666555888888888888888888888888888888666777888888888888888888888888888888555<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<555888888888888888888888888888888666555888888888888888888888888888888666777888888888888888888888888888888555<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<555888888888888888888888888888888666555888888888888888888888888888888666777888888888888888888888888888888555<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<555888888888888888888888888888888666555888888888888888888888888888888666777888888888888888888888888888888555<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<555888888888888888888888888888888666555888888888888888888888888888888666777888888888888888888888888888888555<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<555888888888888888888888888888888666555888888888888888888888888888888666777888888888888888888888888888888555<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<555888888888888888888888888888888666555888888888888888888888888888888666777888888888888888888888888888888555<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<555888888888888888888888888888888666555888888888888888888888888888888666777888888888888888888888888888888555<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<555888888888888888888888888888888666555888888888888888888888888888888666777888888888888888888888888888888555<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<555888888888888888888888888888888666555888888888888888888888888888888666777888888888888888888888888888888555<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<555888888888888888888888888888888666555888888888888888888888888888888666777888888888888888888888888888888555<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<555888888888888888888888888888888666555888888888888888888888888888888666777888888888888888888888888888888555<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<555888888888888888888888888888888666555888888888888888888888888888888666777888888888888888888888888888888555<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<555888888888888888888888888888888666555888888888888888888888888888888666777888888888888888888888888888888555<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<555888888888888888888888888888888666555888888888888888888888888888888666777888888888888888888888888888888555<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<555888888888888888888888888888888666555888888888888888888888888888888666777888888888888888888888888888888555<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<555888888888888888888888888888888666555888888888888888888888888888888666777888888888888888888888888888888555<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<666888888888888888888888888888888666555888888888888888888888888888888666777888888888888888888888888888888555<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<]]]``````aaaLLL777888888888888888888888888888888666555888888888888888888888888888888888LLL`````````aaaOOO888888888888888888888888888888888555<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<222666666666777888888888888888888888888888888888666555888888888888888888888888888888888777666666666666777888888888888888888888888888888888555<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<555888888888888888888888888888888888888888888888666555888888888888888888888888888888888888888888888888888888888888888888888888888888888888555<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<555888888888888888888888888888888888888888888888666555888888888888888888888888888888888888888888888888888888888888888888888888888888888888555<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<555888888888888888888888888888888888888888888888666555888888888888888888888888888888888888888888888888888888888888888888888888888888888888444<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<555888888888888888888888888888888888888888888888666555888888888888888888888888888888888888888888888888888888888888888888888888888888888777>>><<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<555888888888888888888888888888888888888888888888666555888888888888888888888888888888888888888888888888888888888888888888888888888888777>>><<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<555888888888888888888888888888888888888888888888666555888888888888888888888888888888888888888888888888888888888888888888888888888777>>><<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<555888888888888888888888888888888888888888888888666555888888888888888888888888888888888888888888888888888888888888888888888888777>>><<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<555888888888888888888888888888888888888888888888666555888888888888888888888888888888888888888888888888888888888888888888888777>>>MMM===>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<555888888888888888888888888888888888888888888888666555888888888888888888888888888888888888888888888888888888888888888888777>>>LLL<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<444777777777777777777777777777777777777777777777555555777777777777777777777777777777777777777777777777777777777777777777>>>LLL<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<@@@CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC@@@@@@CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBGGGLLL<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>~>>> >>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>===LLLLLL<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>===<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<===>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>{>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>|>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>|>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>|>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>|>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>|>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>{>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>~>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> >>>??(@ @>>>>>> >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>===<<<<<<<<<<<<===>>><<<<<<<<<<<<<<<>>>===<<<<<<<<<<<<===>>>===<<<<<<<<<<<<>>>>>>>>>>>> >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>===KKK____________JJJ>>>VVV___^^^___YYYAAAFFF]]]^^^^^^___OOO===QQQ___^^^^^^\\\EEE>>>>>>>>> >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>;;;zzzvvv@@@JJJbbb<<<]]]<<<>>>>>> >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>;;;~~~zzzAAAKKKddd;;;___<<<>>> >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>;;;}}}zzz@@@KKKddd;;;___<<<>>>>>>>>>>>>>>>>>>>>>>>>===<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<===;;;}}}zzz@@@KKKddd;;;___<<<>>>>>>>>>>>>>>>>>>>>>===FFFWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWXXXPPP<<<}}}zzz@@@KKKddd;;;___<<<>>>>>>>>>>>>>>>>>>===LLL???}}}zzz@@@KKKddd;;;___<<<>>>>>>>>>>>>>>>===FFF@@@}}}zzz@@@KKKddd;;;___<<<>>>>>>>>>>>>>>><<>>>>>>>>>>>>>><<>>>>>>>>>>>>>><<>>>>>>>>>>>>>><<>>>>>>>>>>>>>><<>>>>>>>>>>>>>><<>>>>>>>>>>>>>><<>>>>>>>>>>>>>><<>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>><<>>===<<<<<<::::::<<<>>>===<<<<<<<<<<<<<<<<<<<<<;;;;;;;;;;;;;;;<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>R>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>H>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>H>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>H>>>>>>>>>>>>>>>>>>>>>>>>EEE>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>H>>>>>>>>>>>>>>>>>>>>>>>>===HHH>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>H>>>>>>>>>>>>>>>>>>>>>>>>>>>===DDDWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWSSS???>>>>>>>>>>>>>>>>>>>>>>>>>>>H>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<===>>>>>>>>>>>>>>>>>>>>>>>>>>>H???>>>>>>>>>|>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>H>>>>>>>>>>>>>>>}>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>H>>>>>>>>>>>>>>>}>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>H>>>>>>>>>>>>>>>|>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>H>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>R>>>>>>?(0` $>>>>>>>>>A>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>============>>>============>>>============>>>============>>>>>>>>>>>>8>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>AAAEEE~~~FFFyyyGGGuuu===>>>>>>8>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>CCCKKKLLLNNN===>>>@>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>===CCCJJJLLLNNN===>>>>>>>>>>>>>>>===???CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBCCCJJJLLLNNN===>>>>>>>>>>>>===JJJIIIJJJLLLNNN===>>>>>>>>>>>>???KKKJJJLLLNNN===>>>>>>>>>>>>DDDKKKJJJLLLNNN===>>>>>>>>>>>>CCCKKKJJJLLLNNN===>>>>>>>>>>>>CCCHHHIIIKKKMMM===>>>>>>>>>>>>CCCEEEHHHHHHHHHHHHHHHHHHKKKyyyEEEHHHFFF???HHHLLLLLLGGGFFFVVVXXX===>>>>>>>>>>>>CCC444777777777777777777666???mmm444777888BBBJJJ===>>>>>>>>>>>>CCC555888888888888888888888777???mmm555888999CCCJJJ<<<>>>>>>>>>>>>CCC555888888888888888888888888777???mmm555888999CCCKKKIII===>>>>>>>>>>>>CCC555888888888888888888888888888444mmmmmm555888999AAAFFFIII===>>>>>>>>>>>>>>>CCC444777777777777888888888888888555nnnmmm555888999===>>>>>>===<<<===>>>>>>>>>>>>>>>BBBOOOOOOOOOKKK???======>>>>>>>>>>>>>>>>>>CCCVVV666888888888555nnnmmm555888888999777WWW\\\777999999999777aaaCCC>>>>>>>>>>>>>>>>>>>>>>>>CCCooo555888888888555nnnmmm555888888888555ooovvv555888888888555gggCCC>>>>>>>>>>>>>>>>>>>>>>>>CCCnnn555888888888555nnnmmm555888888888555nnnuuu555888888888555gggCCC>>>>>>>>>>>>>>>>>>>>>>>>CCCnnn555888888888555nnnmmm555888888888555nnnuuu555888888888555gggCCC>>>>>>>>>>>>>>>>>>>>>>>>CCCnnn555888888888555nnnmmm555888888888555nnnuuu555888888888555gggCCC>>>>>>>>>>>>>>>>>>>>>>>>CCCnnn555888888888555nnnmmm555888888888555nnnuuu555888888888555gggCCC>>>>>>>>>>>>>>>>>>>>>>>>CCCnnn555888888888555nnnmmm555888888888555nnnuuu555888888888555gggCCC>>>>>>>>>>>>>>>>>>>>>>>>CCCnnn555888888888555nnnmmm555888888888555nnnuuu555888888888555gggCCC>>>>>>>>>>>>>>>>>>>>>>>>CCCnnn555888888888555nnnmmm555888888888555nnnuuu555888888888555gggCCC>>>>>>>>>>>>>>>>>>>>>>>>CCCnnn555888888888555nnnmmm555888888888555nnnuuu555888888888555gggCCC>>>>>>>>>>>>>>>>>>>>>>>>CCCnnn555888888888555nnnmmm555888888888555nnnuuu555888888888555gggCCC>>>>>>>>>>>>>>>>>>>>>>>>CCCnnn555888888888555nnnmmm555888888888555nnnuuu555888888888555gggCCC>>>>>>>>>>>>>>>>>>>>>>>>CCCnnn555888888888555nnnmmm555888888888555nnnuuu555888888888555gggCCC>>>>>>>>>>>>>>>>>>>>>>>>CCCnnn555888888888555nnnmmm555888888888555nnnuuu555888888888555gggCCC>>>>>>>>>>>>>>>>>>>>>>>>CCCooo555888888888555nnnmmm555888888888555ooovvv555888888888555gggCCC>>>>>>>>>>>>>>>>>>>>>>>>CCCVVV666888888888555nnnmmm555888888888666WWWZZZ666888888888555gggCCC>>>>>>>>>>>>>>>>>>>>>>>>CCC999666888888888888888555nnnmmm555888888888888888777777888888888888888555gggCCC>>>>>>>>>>>>>>>>>>>>>>>>CCC:::888888888888888888555nnnmmm555888888888888888888888888888888888888555fffCCC>>>>>>>>>>>>>>>>>>>>>>>>CCC:::888888888888888888555nnnmmm555888888888888888888888888888888888777===DDD>>>>>>>>>>>>>>>>>>>>>>>>CCC:::888888888888888888555nnnmmm555888888888888888888888888888888777===BBB>>>>>>>>>>>>>>>>>>>>>>>>CCC999777777777777777777444mmmmmm444777777777777777777777777777666===kkk===>>>>>>>>>>>>>>>>>>>>>>>>CCCJJJHHHHHHHHHHHHHHHHHHEEEyyyyyyEEEHHHHHHHHHHHHHHHHHHHHHHHHHHHJJJkkk<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>CCCkkk<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>CCCkkk<<<>>>>>>>>>>>>>>>>>>~>>> >>>>>>>>>>>>CCCkkk<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>???kkk<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>===GGGkkk<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>===???CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBB===>>>>>>>>>>>>>>>>>>>>>>>>>>>7>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>0>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>0>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>~>>>>>>>>>>>>>>>7>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> >>>?((P >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>BBBGGGGGGBBBAAAGGGGGGCCC@@@FFFGGGDDD@@@FFFGGGEEE>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<{{{wwwkkk```XXXEEE>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>>>>======>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>===IIIeeeyyykkk```GGG>>>>>>>>>===yyykkk```GGG>>>>>>>>>>>>~~~yyykkk```GGG>>>>>>>>>>>>}}}yyyjjj___GGG>>>>>>>>>>>>pppqqqqqqqqqqqqrrroooqqqPPPMMM^^^^^^JJJ{{{mmmbbbGGG>>>>>>>>>>>>PPP333555555555555444FFF222555999uuupppzzzGGG>>>>>>>>>>>>SSS666888888888888888666GGG555888999yyyBBB>>>>>>>>>>>>SSS666888888888888888888666III555888999wwwPPP===>>>>>>>>>>>>PPP333555555555888888888888:::555888;;;SSShhheeeOOOMMMhhhhhhhhhhhhgggggggggggggggLLL===>>>>>>>>>>>>>>>vvvwwwxxxkkk<<<888888888:::555888:::888HHHuuunnn???888888888999<<<===>>>>>>>>>>>>>>>>>>DDD777888888:::555888888555gggGGG777888888999>>>>>>>>>>>>>>>>>>>>>>>>DDD777888888:::555888888555hhhGGG777888888999>>>>>>>>>>>>>>>>>>>>>>>>DDD777888888:::555888888555hhhGGG777888888999>>>>>>>>>>>>>>>>>>>>>>>>DDD777888888:::555888888555hhhGGG777888888999>>>>>>>>>>>>>>>>>>>>>>>>DDD777888888:::555888888555hhhGGG777888888999>>>>>>>>>>>>>>>>>>>>>>>>DDD777888888:::555888888555hhhGGG777888888999>>>>>>>>>>>>>>>>>>>>>>>>DDD777888888:::555888888555hhhGGG777888888999>>>>>>>>>>>>>>>>>>>>>>>>DDD777888888:::555888888555hhhGGG777888888999>>>>>>>>>>>>>>>>>>>>>>>>DDD777888888:::555888888555hhhGGG777888888999>>>>>>>>>>>>>>>>>>>>>>>>DDD777888888:::555888888555hhhGGG777888888999>>>>>>>>>>>>>>>>>>>>>>>>DDD777888888:::555888888555hhhGGG777888888999>>>>>>>>>>>>>>>>>>>>>>>>DDD777888888:::555888888555hhhGGG777888888999>>>>>>>>>>>>>>>>>>>>>>>>zzzjjj<<<888888888:::555888888777HHHuuulll===888888888999>>>>>>>>>>>>>>>>>>>>>>>>999555888888888888:::555888888888777555555888888888888888>>>>>>>>>>>>>>>>>>>>>>>><<<888888888888888:::555888888888888888888888888888666FFF>>>>>>>>>>>>>>>>>>>>>>>><<<888888888888888:::555888888888888888888888888666DDD>>>>>>>>>>>>>>>>>>>>>>>>999555555555555555777222555555555555555555555444DDDaaa===>>>>>>>>>>>>>>>>>>>>>tttqqqqqqqqqqqqqqqsssoooqqqqqqqqqqqqqqqqqqqqqrrraaa<<<>>>>>>>>>>>>>>>>>>>>>>>>aaa<<<>>>>>>>>>>>>>>>R>>>>>>>>>>>>aaa<<<>>>>>>>>>>>>>>>H>>>>>>>>>>>>===aaa<<<>>>>>>>>>>>>>>>H>>>>>>>>>>>>>>>===GGGaaa<<<>>>>>>>>>>>>>>>H>>>>>>>>>>>>>>>>>>======>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>===>>>>>>>>>>>>>>>H>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>H>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>H>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>R>>>>>>?( @ >>>>>> >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>EEEZZZZZZGGGUUU\\\LLLOOO\\\SSSHHH[[[XXXCCC>>> >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<>>>>>>>>===<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<:::eeeooowww[[[>>>>>>===IIIkkkooowww[[[>>>>>><<>>>>><<>>>>><<>>>>><<<777777777777666RRR555777TTT___\\\>>>>>><<<888888888888888666QQQ555777fffpppNNN>>>>>><<<666666666777888888444jjj555777ZZZaaaZZZ===>>>>>><<>>>>>>>><<<666888555bbb555888666^^^666888555]]]<<<>>>>>>>>>>>><<<555888555bbb555888555bbb666888666^^^<<<>>>>>>>>>>>><<<555888555bbb555888555bbb666888666^^^<<<>>>>>>>>>>>><<<555888555bbb555888555bbb666888666^^^<<<>>>>>>>>>>>><<<555888555bbb555888555bbb666888666^^^<<<>>>>>>>>>>>><<<555888555bbb555888555bbb666888666^^^<<<>>>>>>>>>>>><<<555888555bbb555888555bbb666888666^^^<<<>>>>>>>>>>>><<<555888555bbb555888555bbb666888666^^^<<<>>>>>>>>>>>><<<555888555bbb555888555bbb666888666^^^<<<>>>>>>>>>>>><<<666888555bbb555888666^^^666888666^^^<<<>>>>>>>>>>>><<>>SSSGGG888888666]]]<<<>>>>>>>>>>>><<<===777888888555bbb555888888888666777888888555fff<<<>>>>>>>>>>>><<>>>>>>>>>>><<<>>>777777777555bbb555777777777777777666OOOXXX===>>>>>>>>>>>><<>>>>>>>>>>>>>><<>>>>>>>>>>>#>>>>>><<>>>>>>>>>>>>>>>>>>>>===GGGXXX<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>===<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<===>>>>>>>>>>>>>>>>>>>>>>>>|>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>#>>>>>>(0 >>>>>>e>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>BBBpppnnn\\\zzzZZZqqqlll]]]yyyTTT>>>e>>>===;;;;;;;;;;;;;;;;;;;;;;;;;;;:::HHHyyy>>>===GGG{{{gggzzz>>>;;;{{{zzz>>>;;;zzz>>>;;;xxxIIIKKKJJJfffKKKIII~~~{{{>>>;;;iii333666777555___666FFF^^^>>>;;;nnn;;;>>>999888777777???nnnppp[[[vvvvvv[[[===>>>;;;SSS666888888777RRRUUU444555zzz:::>>>>>>;;;]]]666888888666^^^aaa555777;;;>>>>>>;;;]]]666888888666]]]```666777;;;>>>>>>;;;]]]666888888666]]]```666777;;;>>>>>>;;;]]]666888888666]]]```666777;;;>>>>>>;;;]]]666888888666]]]```666777;;;>>>>>>;;;]]]666888888666^^^aaa555777;;;>>>>>>;;;SSS666888888666TTTVVV666777;;;>>>>>>;;;GGG999888888888888999===999888666;;;>>>>>>;;;@@@666777666666777777666777555]]];;;>>>>>>;;;TTTKKKKKKKKKKKKKKKKKKKKKJJJdddPPP===>>>>>>;;;QQQ<<<>>>>>>>>>;;;zzzQQQ<<<>>>>>>~>>> >>>>>>FFFzzzPPP<<<>>>>>>>>>>>>>>>_>>>>>>;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;===>>>>>>~>>>>>>>>>>>>_>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> >>>(  >>>C===;;;;;;;;;;;;;;;;;;;;;wwwzzzzzz===FFFlllsssssssssrrrsssiii;;;lll;;;uuu|||uuuppp}}};;;uuu;;;333555{{{:::qqq;;;ttt}}}888YYY<<>>R===EEElllssssssssssssrrrssssssssspppJJJ<<<>>>G>>>>>>@===;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;===>>>S>>>>>>jdim-0.7.0/src/icons/link.png000066400000000000000000000004201417047150700160100ustar00rootroot00000000000000PNG  IHDRaIDAT8SA @Kԛ7T=3U vv fLe) `oH.IbRtk IH*YbYOc0NR1+z~@eoวO K*wsf;ASY@G)wpuՈЧi7 YZ,]ad$ǑHEXBK5F _ ) IENDB`jdim-0.7.0/src/icons/loading.png000066400000000000000000000003431417047150700164740ustar00rootroot00000000000000PNG  IHDRaIDAT8S |r<m4GY-P25)./$žC ^f2puoAZ{`* INVHDɏ;{}tn"[grM;8Ȍ10S6aA.P`LENԽNɐ8zHa'dNoaM)IENDB`jdim-0.7.0/src/icons/loading_stop.png000066400000000000000000000003521417047150700175410ustar00rootroot00000000000000PNG  IHDRaIDAT8 0 DOAZrg@ Y";d%Bfa@%6 VurKջxo,][`2}2Đbml8j"qY^!:c}fۂgdTB֘,C$P6X2X:;pHe`YCZ< _IENDB`jdim-0.7.0/src/icons/meson.build000066400000000000000000000023021417047150700165100ustar00rootroot00000000000000gdk_pixbuf_csource = find_program('gdk-pixbuf-csource') gen = generator(gdk_pixbuf_csource, output : '@BASENAME@.h', capture : true, arguments : ['--raw', '--build-list', 'icon_@BASENAME@', '@INPUT@']) icon_files = [ 'bkmark.png', 'bkmark_broken_subject.png', 'bkmark_thread.png', 'bkmark_update.png', 'board.png', 'board_update.png', 'board_updated.png', 'broken_subject.png', 'check.png', 'dir.png', 'down.png', 'favorite.png', 'hist.png', 'hist_board.png', 'hist_close.png', 'hist_closeboard.png', 'hist_closeimg.png', 'image.png', 'info.png', 'jd16.png', 'jd32.png', 'jd48.png', 'jd96.png', 'link.png', 'loading.png', 'loading_stop.png', 'newthread.png', 'newthread_hour.png', 'post.png', 'post_refer.png', 'thread.png', 'thread_old.png', 'thread_update.png', 'thread_updated.png', 'update.png', 'write.png', ] generated_headers = [] foreach f : icon_files generated_headers += gen.process(f) endforeach sources = [ 'iconmanager.cpp', ] icon_lib = static_library( 'icon', [sources, generated_headers], dependencies : gtkmm_dep, include_directories : include_directories('..'), ) jdim-0.7.0/src/icons/newthread.png000066400000000000000000000002421417047150700170360ustar00rootroot00000000000000PNG  IHDRaiIDAT8 0 ψ[HmR?<7e;l2{ʱѴ 7C3_x pttRvŤ$yZ"(;!:'pE+ IENDB`jdim-0.7.0/src/icons/newthread_hour.png000066400000000000000000000002351417047150700200750ustar00rootroot00000000000000PNG  IHDRadIDAT8͒A 0Ǽ|z,m4!`630p`X,%}pG {fk/s)GXx? V)W ܖf_vIENDB`jdim-0.7.0/src/icons/post.png000066400000000000000000000003451417047150700160460ustar00rootroot00000000000000PNG  IHDR "aIDAT(͒0 D @ + jO0Eek8Eq 6`oy/"NJIB)ƨ}ܽguH),`B:DD;:0^Vr}ې^PD<9), rz6g^kz{ xs=0IENDB`jdim-0.7.0/src/icons/post_refer.png000066400000000000000000000002561417047150700172320ustar00rootroot00000000000000PNG  IHDR "auIDAT(Q ClMPPB{&Ai?I{MKO w SY,e޷;a\ &5(-y%ѿqTqk|8.Z0L}3it!IENDB`jdim-0.7.0/src/icons/thread.png000066400000000000000000000003411417047150700163240ustar00rootroot00000000000000PNG  IHDRaIDAT8͓A E<G16޴;[nM<߅BHJZčxJ/i M$feZ7$af  C/j9@-RHTݟ8 ཏuܒ& Jcfz_UP9ͨ`mqV]"S *y& Sߘ^CWK` RjIENDB`jdim-0.7.0/src/icons/thread_old.png000066400000000000000000000004351417047150700171660ustar00rootroot00000000000000PNG  IHDRaIDAT80Dgܨ%RK\ DjID `c~DJkIv3{%3y 1-/L!|gۙ3z IOL9)2N`FıGpIENDB`jdim-0.7.0/src/icons/write.png000066400000000000000000000004061417047150700162110ustar00rootroot00000000000000PNG  IHDRaIDAT8ݓ 0 D;IG A nR`_,EN}$Jefd7'$IOt 6 @tI  y'l1" ); m_left.set_focus_on_click( false ); m_right.set_focus_on_click( false ); m_gesture_press_left = Gtk::GestureMultiPress::create( m_left ); m_gesture_press_right = Gtk::GestureMultiPress::create( m_right ); m_gesture_press_left->signal_pressed().connect( sigc::mem_fun( *this, &ImageAdmin::slot_press_left ) ); m_gesture_press_right->signal_pressed().connect( sigc::mem_fun( *this, &ImageAdmin::slot_press_right ) ); m_gesture_press_left->signal_released().connect( sigc::mem_fun( *this, &ImageAdmin::slot_release_left ) ); m_gesture_press_right->signal_released().connect( sigc::mem_fun( *this, &ImageAdmin::slot_release_right ) ); // マウスホイールによる画像ビューのタブ切り替えを設定する m_tab.add_events( Gdk::SCROLL_MASK ); m_tab.add_events( Gdk::SMOOTH_SCROLL_MASK ); m_tab.signal_scroll_event().connect( sigc::mem_fun( *this, &ImageAdmin::slot_scroll_event ) ); m_tab.pack_start( m_scrwin ); m_tab.pack_end( m_right, Gtk::PACK_SHRINK ); m_tab.pack_end( m_left, Gtk::PACK_SHRINK ); m_tab.show_all_children(); } ImageAdmin::~ImageAdmin() { #ifdef _DEBUG std::cout << "ImageAdmin::~ImageAdmin\n"; #endif ImageAdmin::close_window(); } void ImageAdmin::save_session() { Admin::save_session(); // 開いているURLを保存 SESSION::set_image_URLs( get_URLs() ); SESSION::set_image_locked( get_locked() ); SESSION::set_image_page( get_current_page() ); } // 前回開いていたURLを復元 void ImageAdmin::restore( const bool only_locked ) { #ifdef _DEBUG std::cout << "ImageAdmin::restore\n"; #endif int set_page_num = 0; std::list< std::string > list_url = SESSION::image_URLs(); std::list< std::string >::iterator it_url= list_url.begin(); std::list< bool > list_locked = SESSION::get_image_locked(); std::list< bool >::iterator it_locked = list_locked.begin(); for( int page = 0; it_url != list_url.end(); ++it_url, ++page ){ // タブのロック状態 bool lock = false; if( it_locked != list_locked.end() ){ if( (*it_locked ) ) lock = true; ++it_locked; } // ロックされているものだけ表示 if( only_locked && ! lock ) continue; if( page == SESSION::image_page() ) set_page_num = get_tab_nums(); COMMAND_ARGS command_arg = url_to_openarg( *it_url, true, lock ); if( ! command_arg.url.empty() ) open_view( command_arg ); } SKELETON::View* view = get_nth_icon( set_page_num ); if( view ){ switch_img( view->get_url() ); switch_admin(); } } COMMAND_ARGS ImageAdmin::url_to_openarg( const std::string& url, const bool tab, const bool lock ) { COMMAND_ARGS command_arg; command_arg.command = "open_view"; command_arg.url = url; if( lock ) command_arg.arg3 = "lock"; return command_arg; } void ImageAdmin::switch_admin() { if( ! has_focus() ) CORE::core_set_command( "switch_image" ); } // // ページが含まれていないか // bool ImageAdmin::empty() const { return m_list_view.empty(); } // // 含まれているタブの数 // int ImageAdmin::get_tab_nums() { return static_cast< int >( m_iconbox.get_children().size() ); } // // 含まれているページのURLのリスト取得 // std::list< std::string > ImageAdmin::get_URLs() { std::list< std::string > urls; m_iconbox.foreach( [&urls]( Gtk::Widget& w ) { auto view = dynamic_cast< SKELETON::View* >( &w ); if( view ) { urls.push_back( view->get_url() ); } } ); return urls; } // // コアからのクロック入力 // // 各viewにクロックを渡すだけ // void ImageAdmin::clock_in() { // アイコンにクロックを送る m_iconbox.foreach( []( Gtk::Widget& w ) { auto view = dynamic_cast< SKELETON::View* >( &w ); if( view ) { view->clock_in(); } } ); // 画像が表示されている場合viewにクロックを回す if( SESSION::is_shown_win_img() ){ // アクティブなviewにだけクロックを送る SKELETON::View* view = get_current_view(); if( view ) view->clock_in(); } // タブのスクロール if( m_scroll != SCROLL_NO ){ const int timing = 100; // msec ++m_counter_scroll; if( timing / TIMER_TIMEOUT <= m_counter_scroll ){ scroll_tab( m_scroll ); m_counter_scroll = 0; } } if( get_jdwin() ) get_jdwin()->clock_in(); } // // 現在表示されているページ番号 // int ImageAdmin::get_current_page() { int pos; SKELETON::View* view = get_current_view(); if( !view ) return -1; get_icon( view->get_url(), pos ); return pos; } // // ローカルなコマンド // void ImageAdmin::command_local( const COMMAND_ARGS& command ) { // 切り替え if( command.command == "switch_image" ) switch_img( command.url ); // 画像強制表示 if( command.command == "show_image" ){ clock_in(); clock_in(); clock_in(); } // すべて保存 else if( command.command == "save_all" ) save_all(); // 並び替え else if( command.command == "reorder" ) reorder( command.arg1, command.arg2 ); else if( command.command == "close_other_views" ) close_other_views( command.url ); else if( command.command == "close_left_views" ) close_left_views( command.url ); else if( command.command == "close_right_views" ) close_right_views( command.url ); else if( command.command == "close_error_views" ) close_error_views( command.arg1 ); else if( command.command == "close_noerror_views" ) close_noerror_views(); else if( command.command == "close_all_views" ) close_other_views( std::string() ); // キャッシュに無いviewを削除 else if( command.command == "close_nocached_views" ) close_error_views( "nocached" ); // 画面のスクロール else if( command.command == "scroll_up" ){ SKELETON::View* view = get_current_view(); if( view ) view->scroll_up(); } else if( command.command == "scroll_down" ){ SKELETON::View* view = get_current_view(); if( view ) view->scroll_down(); } else if( command.command == "scroll_left" ){ SKELETON::View* view = get_current_view(); if( view ) view->scroll_left(); } else if( command.command == "scroll_right" ){ SKELETON::View* view = get_current_view(); if( view ) view->scroll_right(); } else if( command.command == "set_imgtab_operating" ){ if( command.arg1 == "true" ) SESSION::set_tab_operating( get_url(), true ); else{ SESSION::set_tab_operating( get_url(), false ); if( ! empty() ){ update_status_of_all_views(); } } } } // // 画像を開く // void ImageAdmin::open_view( const COMMAND_ARGS& command ) { #ifdef _DEBUG std::cout << "ImageAdmin::open_view url = " << command.url << std::endl; #endif // まだ表示されていない if( ! get_view( command.url ) ){ // アイコン作成 & 表示 SKELETON::View* icon = Gtk::manage( CORE::ViewFactory( CORE::VIEW_IMAGEICON, command.url ) ); if( icon ){ if( command.arg3 == "lock" ) icon->lock(); icon->set_size_request( ICON_SIZE , ICON_SIZE ); icon->show_view(); m_iconbox.pack_start( *icon, Gtk::PACK_SHRINK ); m_iconbox.show_all_children(); } // view作成 auto view = std::unique_ptr( CORE::ViewFactory( CORE::VIEW_IMAGEVIEW, command.url ) ); if( view ){ view->show_view(); m_list_view.push_back( std::move( view ) ); } } open_window(); update_status_of_all_views(); switch_img( command.url ); } // // タブの切替え // void ImageAdmin::tab_left( const bool updated ) { std::string url_to; // gtk2のGlib::ListHandleのイテレーターはデクリメント不可なので型変換する const std::vector< Gtk::Widget* > widgets = m_iconbox.get_children(); if( widgets.size() <= 1 ) return; const SKELETON::View* const icon = get_current_icon(); for( auto&& widget : widgets ) { auto view = dynamic_cast< SKELETON::View* >( widget ); if( view ) { if( view == icon ) break; url_to = view->get_url(); } } // 一番最後へ戻る if( url_to.empty() ) { auto view = dynamic_cast< SKELETON::View* >( widgets.back() ); if( view ) { url_to = view->get_url(); } } if( !url_to.empty() ) switch_img( url_to ); focus_current_view(); } void ImageAdmin::tab_right( const bool updated ) { std::string url_to; const auto widgets = m_iconbox.get_children(); if( widgets.size() <= 1 ) return; auto it = std::find( widgets.begin(), widgets.end(), static_cast< Gtk::Widget* >( get_current_icon() ) ); if( ++it == widgets.end() ) { it = widgets.begin(); } auto view = dynamic_cast< SKELETON::View* >( *it ); if( view ) { url_to = view->get_url(); } if( !url_to.empty() ) switch_img( url_to ); focus_current_view(); } // // タブの最初に移動 // void ImageAdmin::tab_head() { const auto widgets = m_iconbox.get_children(); if( widgets.size() <= 1 ) return; auto view = dynamic_cast< SKELETON::View* >( *widgets.begin() ); std::string url_to; if( view ) url_to = view->get_url(); if( !url_to.empty() ) switch_img( url_to ); switch_admin(); } // // タブの最後に移動 // void ImageAdmin::tab_tail() { const std::vector< Gtk::Widget* > widgets = m_iconbox.get_children(); if( widgets.size() <= 1 ) return; auto view = dynamic_cast< SKELETON::View* >( widgets.back() ); std::string url_to; if( view ) url_to = view->get_url(); if( !url_to.empty() ) switch_img( url_to ); switch_admin(); } // // タブアイコンの並び替え // void ImageAdmin::reorder( const std::string& url_from, const std::string& url_to ) { SKELETON::View* view_from = get_icon( url_from ); int pos; get_icon( url_to, pos ); if( view_from && pos != -1 ){ #ifdef _DEBUG std::cout << "ImageAdmin::reorder " << url_from << "\n-> " << url_to << " pos = " << pos << std::endl; #endif m_iconbox.reorder_child( *view_from, pos ); update_status_of_all_views(); } } // // 全てのビューのステータス表示内容更新の予約 // void ImageAdmin::update_status_of_all_views() { if( SESSION::is_tab_operating( get_url() ) ) return; #ifdef _DEBUG std::cout << "ImageAdmin::update_status_of_all_views\n"; #endif for( auto& v : m_list_view ) { v->set_command( "update_status" ); } } // // 指定したビューを再描画 // void ImageAdmin::redraw_view( const std::string& url ) { #ifdef _DEBUG std::cout << "ImageAdmin::redraw_view url = " << url << std::endl; #endif SKELETON::View* view = get_view( url ); if( view ) view->redraw_view(); view = get_icon( url ); if( view ) view->redraw_view(); } // // 現在のビューを再描画 // void ImageAdmin::redraw_current_view() { #ifdef _DEBUG std::cout << "ImageAdmin::redraw_current_view\n"; #endif SKELETON::View* view = get_current_view(); if( view ) view->redraw_view(); view = get_current_icon(); if( view ) view->redraw_view(); } // // 閉じる // void ImageAdmin::close_view( const std::string& url ) { #ifdef _DEBUG std::cout << "ImageAdmin::close_view : url = " << url << std::endl; #endif // 次に表示するviewのURL std::string url_next = std::string(); SKELETON::View* icon = get_icon( url ); SKELETON::View* view = get_view( url ); if( ! icon && ! view ) return; if( DBIMG::is_cached( url ) && icon && icon->is_locked() ) return; // 現在表示中のviewを閉じた場合は次か前の画像に切り替える if( view && view == get_current_view() ){ m_view.remove(); SKELETON::View* view_prev = nullptr; SKELETON::View* view_next = nullptr; const auto widgets = m_iconbox.get_children(); for( auto it = widgets.begin(); it != widgets.end(); ++it ) { auto view_tmp = dynamic_cast< SKELETON::View* >( *it ); if( view_tmp->get_url() == url ) { if( ++it != widgets.end() ) { view_next = dynamic_cast< SKELETON::View* >( *it ); } break; } view_prev = view_tmp; } if( view_next ) url_next = view_next->get_url(); else if( view_prev ) url_next = view_prev->get_url(); } if( icon ){ m_iconbox.remove( *icon ); delete icon; } if( view ){ // 削除対象はアドレスで判定する m_list_view.remove_if( [view]( auto& p ) { return view == p.get(); } ); } if( empty() ){ close_window(); CORE::core_set_command( "empty_page", get_url() ); } else if( ! url_next.empty() ){ update_status_of_all_views(); switch_img( url_next ); } } // // ウィンドウ開く // void ImageAdmin::open_window() { ImageWin* win = dynamic_cast< ImageWin* >( get_jdwin() ); if( ! SESSION::get_embedded_img() && ! win && ! empty() ){ set_jdwin( std::make_unique() ); win = dynamic_cast( get_jdwin() ); win->pack_remove_tab( false, m_tab ); win->pack_remove_end( false, m_view ); win->show_all(); } else if( win && win->is_hide() ){ win->show(); win->focus_in(); } } // // ウィンドウ閉じる // void ImageAdmin::close_window() { ImageWin* win = dynamic_cast< ImageWin* >( get_jdwin() ); if( win ){ win->pack_remove_tab( true, m_tab ); win->pack_remove_end( true, m_view ); delete_jdwin(); } } // // url 以外の画像を閉じる // void ImageAdmin::close_other_views( const std::string& url ) { set_command( "set_imgtab_operating", "", "true" ); m_iconbox.foreach( [this, &url]( Gtk::Widget& w ) { auto view = dynamic_cast< SKELETON::View* >( &w ); if( view && view->get_url() != url ) { set_command( "close_view", view->get_url() ); } } ); set_command( "set_imgtab_operating", "", "false" ); } // // url の左側の画像を閉じる // void ImageAdmin::close_left_views( const std::string& url ) { set_command( "set_imgtab_operating", "", "true" ); for( auto&& widget : m_iconbox.get_children() ) { if( auto view{ dynamic_cast( widget ) } ) { if( view->get_url() == url ) break; set_command( "close_view", view->get_url() ); } } set_command( "set_imgtab_operating", "", "false" ); } // // url の右側の画像を閉じる // void ImageAdmin::close_right_views( const std::string& url ) { set_command( "set_imgtab_operating", "", "true" ); const auto widgets = m_iconbox.get_children(); auto it = widgets.begin(); for( ; it != widgets.end(); ++it ) { auto view = dynamic_cast< SKELETON::View* >( *it ); if( view->get_url() == url ) break; } for( ++it; it != widgets.end(); ++it ) { auto view = dynamic_cast< SKELETON::View* >( *it ); if( view ) { set_command( "close_view", view->get_url() ); } } set_command( "set_imgtab_operating", "", "false" ); } // // エラーになっている画像を閉じる // void ImageAdmin::close_error_views( const std::string& mode ) { #ifdef _DEBUG std::cout << "ImageAdmin::close_error_views\n"; #endif set_command( "set_imgtab_operating", "", "true" ); for( auto&& widget : m_iconbox.get_children() ) { auto view = dynamic_cast< SKELETON::View* >( widget ); if( view ){ const std::string url = view->get_url(); if( DBIMG::is_cached ( url ) ) continue; if( mode != "all" && DBIMG::is_loading ( url ) ) continue; if( DBIMG::is_wait ( url ) ) continue; const int code = DBIMG::get_code( url ); if( ( code == HTTP_FORBIDDEN || code == HTTP_NOT_FOUND ) // 404,403 は無条件で閉じる || ( mode == "notimeout" && code != HTTP_TIMEOUT && code != HTTP_TEMP_UNAV ) // timeout,503以外 || ( mode == "nocached" && code == HTTP_INIT ) // キャッシュに無い(削除済み)の画像 || mode == "all" // 読み込み中も含めて閉じる ){ set_command( "close_view", url ); if( mode == "all" ) DBIMG::stop_load( url ); } } } set_command( "set_imgtab_operating", "", "false" ); } // // エラーでない画像を閉じる // void ImageAdmin::close_noerror_views() { #ifdef _DEBUG std::cout << "ImageAdmin::close_noerror_views\n"; #endif set_command( "set_imgtab_operating", "", "true" ); for( auto&& widget : m_iconbox.get_children() ) { auto view = dynamic_cast< SKELETON::View* >( widget ); if( view ){ const std::string url = view->get_url(); if( ! DBIMG::is_cached ( url ) ) continue; const int code = DBIMG::get_code( url ); if( code == HTTP_OK ) set_command( "close_view", url ); } } set_command( "set_imgtab_operating", "", "false" ); } void ImageAdmin::restore_lasttab() { HISTORY::restore_history( URL_HISTCLOSEIMGVIEW ); } // // 現在のviewをフォーカスする // // 他のクラスからは直接呼ばないで、set_command()経由で呼ぶこと // void ImageAdmin::focus_view( int) { #ifdef _DEBUG std::cout << "ImageAdmin::focus_view\n"; #endif SKELETON::View* view_icon = get_current_icon(); if( view_icon ) { if( get_jdwin() ) get_jdwin()->focus_in(); focus_out_all(); view_icon->focus_view(); SKELETON::View* view = get_current_view(); if( view ) update_status( view, false ); } } void ImageAdmin::focus_current_view() { focus_view( 0 ); } // // 全アイコンのフォーカスをはずす // void ImageAdmin::focus_out_all() { m_iconbox.foreach( []( Gtk::Widget& w ) { auto view = dynamic_cast< SKELETON::View* >( &w ); if( view ) { view->focus_out(); } } ); } // // 画像切り替え // void ImageAdmin::switch_img( const std::string& url ) { #ifdef _DEBUG std::cout << "ImageAdmin::switch_img url = " << url << std::endl; #endif // 画像切り替え for( auto& view : m_list_view ) { if( view->get_url() == url ) { if( view.get() != get_current_view() ) { #ifdef _DEBUG std::cout << "view was toggled.\n"; #endif m_view.remove(); m_view.add( *view ); m_view.show_all_children(); } break; } } focus_out_all(); // アイコン切り替え int page = 0; SKELETON::View* view_icon = get_icon( url, page ); if( view_icon ) view_icon->set_command( "switch_icon" ); // タブをスクロール auto adjust = m_scrwin.get_hadjustment(); if( page != -1 && adjust ){ double pos = adjust->get_value(); double upper = m_list_view.size() * ICON_SIZE; double width = adjust->get_page_size(); double pos_to = page * ICON_SIZE; #ifdef _DEBUG std::cout << "pos = " << pos << std::endl; std::cout << "page = " << page << std::endl; std::cout << "pos_to = " << pos_to << std::endl; std::cout << "upper = " << upper << std::endl; std::cout << "width = " << width << std::endl; #endif if( pos_to <= pos || pos_to >= pos + width ){ if( pos_to + width >= upper ) pos_to = upper - width; adjust->set_value( pos_to ); } } if( has_focus() ) focus_current_view(); } // // アイコン取得 // // pos にアイコンの位置が入る(見付からないときは-1) // SKELETON::View* ImageAdmin::get_icon( const std::string& url, int& pos ) { pos = 0; for( auto&& widget : m_iconbox.get_children() ) { auto view = dynamic_cast< SKELETON::View* >( widget ); if( view && view->get_url() == url ) return view; ++pos; } pos = -1; return nullptr; } // 簡易版 SKELETON::View* ImageAdmin::get_icon( const std::string& url) { int pos; return get_icon( url, pos ); } // // アイコン取得(番号で) // SKELETON::View* ImageAdmin::get_nth_icon( const unsigned int n ) { const std::vector< Gtk::Widget* > widgets = m_iconbox.get_children(); if( n >= widgets.size() ) return nullptr; return dynamic_cast< SKELETON::View* >( widgets[ n ] ); } // // カレントアイコン取得 // SKELETON::View* ImageAdmin::get_current_icon() { SKELETON::View* view = get_current_view(); if( !view ) return nullptr; return get_icon( view->get_url() ); } // // view 取得 // SKELETON::View* ImageAdmin::get_view( const std::string& url ) { SKELETON::View* view = get_current_view(); if( view && view->get_url() == url ) return view; for( auto& v : m_list_view ) { if( v->get_url() == url ) return v.get(); } return nullptr; } // // カレントview 取得 // SKELETON::View* ImageAdmin::get_current_view() { return dynamic_cast< SKELETON::View* >( m_view.get_child() ); } // // スクロール // void ImageAdmin::scroll_tab( int scroll ) { if( scroll == SCROLL_NO ) return; #ifdef _DEBUG std::cout << "ImageAdmin::scroll_tab " << scroll << std::endl; #endif auto adjust = m_scrwin.get_hadjustment(); if( adjust ){ double pos = adjust->get_value(); double upper = adjust->get_upper(); double width = adjust->get_page_size(); #ifdef _DEBUG std::cout << "pos = " << pos << std::endl; std::cout << "upper = " << upper << std::endl; std::cout << "width = " << width << std::endl; #endif if( upper == width ) return; if( scroll == SCROLL_LEFT ) pos -= ICON_SIZE; else pos += ICON_SIZE; if( pos <= 0 ) pos = 0; else if( pos + width >= upper ) pos = upper - width; // ICON_SIZEの倍数にする else pos = ICON_SIZE * ( ( (int)pos ) / ICON_SIZE ); adjust->set_value( pos ); } } //左押した void ImageAdmin::slot_press_left( int, double, double ) { m_scroll = SCROLL_LEFT; m_counter_scroll = 0; scroll_tab( m_scroll ); } //右押した void ImageAdmin::slot_press_right( int, double, double ) { m_scroll = SCROLL_RIGHT; m_counter_scroll = 0; scroll_tab( m_scroll ); } //左離した void ImageAdmin::slot_release_left( int, double, double ) { m_scroll = SCROLL_NO; } // 右離した void ImageAdmin::slot_release_right( int, double, double ) { m_scroll = SCROLL_NO; } // // タブのマウスホイールイベント // bool ImageAdmin::slot_scroll_event( GdkEventScroll* event ) { // 回転したらタブ切り替え const char* command_name = nullptr; if( event->direction == GDK_SCROLL_UP ) command_name = "tab_left"; else if( event->direction == GDK_SCROLL_DOWN ) command_name = "tab_right"; else if( event->direction == GDK_SCROLL_SMOOTH ) { constexpr double smooth_scroll_factor{ 2.0 }; m_smooth_dy += smooth_scroll_factor * event->delta_y; if( m_smooth_dy < -1.0 ) { command_name = "tab_left"; m_smooth_dy = 0.0; } else if( m_smooth_dy > 1.0 ) { command_name = "tab_right"; m_smooth_dy = 0.0; } } if( command_name ) set_command( command_name ); set_command( "switch_admin" ); return true; } // // 画像ファイルのコピー // bool ImageAdmin::copy_file( const std::string& url, const std::string& path_from, const std::string& path_to ) { #ifdef _DEBUG std::cout << "ImageAdmin::copy_file url = " << url << std::endl << "from = " << path_from << std::endl << "to = " << path_to << std::endl; #endif if( ! CACHE::jdcopy( path_from, path_to ) ){ // ビューを切り替えてURLやステータス更新 switch_img( url ); SKELETON::View* view = get_current_view(); if( view ) update_status( view, true ); SKELETON::MsgDiag mdiag( get_win(), path_to + "\n\nの保存に失敗しました。\nハードディスクの容量やパーミッションなどを確認してください。\n\n画像保存をキャンセルしました。原因を解決してからもう一度保存を行ってください。" ); mdiag.run(); return false; } return true; } // // すべて保存 // void ImageAdmin::save_all() { #ifdef _DEBUG std::cout << "ImageAdmin::save_all\n"; #endif // ディレクトリ選択 SKELETON::FileDiag diag( get_win(), "保存先選択", Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER ); diag.set_current_folder( SESSION::dir_img_save() ); if( diag.run() == Gtk::RESPONSE_ACCEPT ){ diag.hide(); std::string path_dir = MISC::recover_path( diag.get_filename() ); if( path_dir.empty() ) return; if( path_dir.c_str()[ path_dir.length()-1 ] != '/' ) path_dir += "/"; #ifdef _DEBUG std::cout << "dir = " << path_dir << std::endl; #endif if( CACHE::jdmkdir( path_dir ) ){ SESSION::set_dir_img_save( path_dir ); int overwrite = Gtk::RESPONSE_NO; bool use_name_in_cache = false; const std::list list_urls = get_URLs(); for( const std::string& url : list_urls ) { if( ! DBIMG::is_cached( url ) || DBIMG::get_mosaic( url ) ) continue; std::string path_from = DBIMG::get_cache_path( url ); std::string path_to = path_dir + MISC::get_filename( url ); #ifdef _DEBUG std::cout << "from = " << path_from << std::endl; std::cout << "to = " << path_to << std::endl; #endif // 既にファイルがある場合 if( CACHE::file_exists( path_to ) == CACHE::EXIST_FILE ){ // すべて上書き if( overwrite == SKELETON::OVERWRITE_YES_ALL ){ if( ! copy_file( url, path_from, path_to ) ) return; } // ファイル名として画像キャッシュでのファイル名を使用 else if( use_name_in_cache ){ if( ! copy_file( url, path_from, path_dir + CACHE::filename_img( url ) ) ) return; } // すべていいえ else if( overwrite == SKELETON::OVERWRITE_NO_ALL ){} else{ // ビューを切り替えてURLやステータス更新 switch_img( url ); SKELETON::View* view = get_current_view(); if( view ) update_status( view, true ); for(;;){ SKELETON::MsgOverwriteDiag mdiag( get_win() ); overwrite = mdiag.run(); mdiag.hide(); switch( overwrite ){ // すべて上書き case SKELETON::OVERWRITE_YES_ALL: // 上書き case SKELETON::OVERWRITE_YES: if( ! copy_file( url, path_from, path_to ) ) return; break; // 名前変更 case Gtk::RESPONSE_YES: { SKELETON::MsgDiag mdiag_name( get_win(), "ファイル名として画像キャッシュでのファイル名を使用しますか?\n\nいいえを選ぶとファイル選択ダイアログを開きます。", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO ); mdiag_name.add_button( "すべてはい", SKELETON::OVERWRITE_YES_ALL ); mdiag_name.set_default_response( Gtk::RESPONSE_YES ); const int ret = mdiag_name.run(); if( ret == Gtk::RESPONSE_YES || ret == SKELETON::OVERWRITE_YES_ALL ){ if( ret == SKELETON::OVERWRITE_YES_ALL ) use_name_in_cache = true; if( ! copy_file( url, path_from, path_dir + CACHE::filename_img( url ) ) ) return; } else{ if( ! DBIMG::save( url, get_win(), path_to ) ) continue; } break; } default: break; } break; } } } else if( ! copy_file( url, path_from, path_to ) ) return; } } else{ SKELETON::MsgDiag mdiag( get_win(), path_dir + "\n\nの作成に失敗しました。\nハードディスクの容量やパーミッションなどを確認してください。\n\n画像保存をキャンセルしました。原因を解決してからもう一度保存を行ってください。" ); mdiag.run(); } } } // ページがロックされているかリストで取得 std::list< bool > ImageAdmin::get_locked() { std::list< bool > locked; m_iconbox.foreach( [&locked]( Gtk::Widget& w ) { const auto view = dynamic_cast< SKELETON::View* >( &w ); if( view ) { locked.push_back( view->is_locked() ); } } ); return locked; } // タブのロック/アンロック bool ImageAdmin::is_lockable( const int page ) { SKELETON::View* view = get_nth_icon( page ); if( view ) return view->is_lockable(); return false; } bool ImageAdmin::is_locked( const int page ) { SKELETON::View* view = get_nth_icon( page ); if( view ) return view->is_locked(); return false; } void ImageAdmin::lock( const int page ) { SKELETON::View* view = get_nth_icon( page ); if( view ) return view->lock(); } void ImageAdmin::unlock( const int page ) { SKELETON::View* view = get_nth_icon( page ); if( view ) return view->unlock(); } jdim-0.7.0/src/image/imageadmin.h000066400000000000000000000104461417047150700165710ustar00rootroot00000000000000// ライセンス: GPL2 // // 板の管理クラス // #ifndef _IMAGEADMIN_H #define _IMAGEADMIN_H #include "skeleton/admin.h" #include #include namespace IMAGE { // スクロール方向 enum{ SCROLL_NO, SCROLL_LEFT, SCROLL_RIGHT }; class ImageAdmin : public SKELETON::Admin { Gtk::HBox m_tab; Gtk::HBox m_iconbox; Gtk::ScrolledWindow m_scrwin; Gtk::Button m_left, m_right; Gtk::EventBox m_view; std::list> m_list_view; int m_scroll; int m_counter_scroll{}; double m_smooth_dy{}; // GDK_SCROLL_SMOOTH のスクロール変化量 Glib::RefPtr m_gesture_press_left; Glib::RefPtr m_gesture_press_right; public: explicit ImageAdmin( const std::string& url ); ~ImageAdmin(); void save_session() override; Gtk::HBox& tab() { return m_tab; } Gtk::Widget* get_widget() override { return &m_view; } bool empty() const override; void clock_in() override; // タブの数 int get_tab_nums() override; // 含まれているページのURLのリスト取得 std::list< std::string > get_URLs() override; // 現在表示してるページ番号 int get_current_page() override; protected: void command_local( const COMMAND_ARGS& command ) override; void restore( const bool only_locked ) override; COMMAND_ARGS url_to_openarg( const std::string& url, const bool tab, const bool lock ) override; void switch_admin() override; void open_view( const COMMAND_ARGS& command ) override; void tab_left( const bool updated ) override; void tab_right( const bool updatd ) override; void tab_head() override; void tab_tail() override; void redraw_view( const std::string& url ) override; void redraw_current_view() override; void close_view( const std::string& url ) override; void close_other_views( const std::string& url ) override; void restore_lasttab() override; void focus_view( int page ) override; void focus_current_view() override; void open_window() override; void close_window() override; SKELETON::View* get_view( const std::string& url ) override; SKELETON::View* get_current_view() override; // ページがロックされているかリストで取得 std::list< bool > get_locked() override; // タブのロック/アンロック bool is_lockable( const int page ) override; bool is_locked( const int page ) override; void lock( const int page ) override; void unlock( const int page ) override; // タブの D&D 処理は SKELETON::Admin とは違うロジックでおこなう void slot_drag_data_get( Gtk::SelectionData& selection_data, const int page ) override {} private: void close_left_views( const std::string& url ); void close_right_views( const std::string& url ); void close_error_views( const std::string& mode ); void close_noerror_views(); void reorder( const std::string& url_from, const std::string& url_to ); void update_status_of_all_views(); void focus_out_all(); void switch_img( const std::string& url ); SKELETON::View* get_icon( const std::string& url, int& pos ); SKELETON::View* get_icon( const std::string& url ); SKELETON::View* get_nth_icon( const unsigned int n ); SKELETON::View* get_current_icon(); // スクロール void scroll_tab( int scroll ); // スクロールボタン void slot_press_left( int, double, double ); void slot_press_right( int, double, double ); void slot_release_left( int, double, double ); void slot_release_right( int, double, double ); // マウスホイールによるタブ切り替え bool slot_scroll_event( GdkEventScroll* event ); bool copy_file( const std::string& url, const std::string& path_from, const std::string& path_to ); void save_all(); }; IMAGE::ImageAdmin* get_admin(); void delete_admin(); } #endif jdim-0.7.0/src/image/imagearea.cpp000066400000000000000000000050671417047150700167470ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "imagearea.h" #include "dbimg/img.h" #include "config/globalconf.h" #include "session.h" #include using namespace IMAGE; ImageAreaMain::ImageAreaMain( const std::string& url ) : ImageAreaBase( url, CONFIG::get_imgmain_interp() ) { #ifdef _DEBUG std::cout << "ImageAreaMain::ImageAreaMain url = " << url << std::endl; #endif } ImageAreaMain::~ImageAreaMain() { #ifdef _DEBUG std::cout << "ImageAreaMain::~ImageAreaMain url = " << get_url() << std::endl; #endif } // // 表示 // void ImageAreaMain::show_image() { #ifdef _DEBUG std::cout << "ImageAreaMain::show_image url = " << get_url() << std::endl; #endif if( is_loading() ) return; set_errmsg( std::string() ); int width_max = 0; int height_max = 0; if( get_parent() && get_parent()->get_parent() ){ // 親(EventBox)の親(ScrolledWindow)がいるときはそのサイズ const int mrg = 32; width_max = get_parent()->get_parent()->get_width() - mrg; height_max = get_parent()->get_parent()->get_height() - mrg; } else{ width_max = Gtk::Image::get_width(); height_max = Gtk::Image::get_height(); } // まだrealizeしてなくてウィンドウサイズが取得できていないのでImageViewMain::clock_in()経由で後でもう一度呼ぶ if( ! is_ready() && ( width_max <= 1 || height_max <= 1 ) ) return; bool zoom_to_fit = get_img()->is_zoom_to_fit(); int size = get_img()->get_size(); // スケール調整 int w_org = get_img()->get_width(); int h_org = get_img()->get_height(); set_width( w_org ); set_height( h_org ); // 画面サイズに合わせる if( zoom_to_fit && w_org && h_org ){ double scale_w = ( double ) width_max / w_org; double scale_h = ( double ) height_max / h_org; const double scale = ( SESSION::get_img_fit_mode() == SESSION::IMG_FIT_NORMAL ) ? std::fmin( scale_w, scale_h ) : scale_w; if( scale < 1 ){ set_width( (int)( w_org * scale ) ); set_height( (int)( h_org * scale ) ); } } // サイズ変更 else if( size != 100 ){ const double scale = size / 100.0; set_width( (int)( w_org * scale ) ); set_height( (int)( h_org * scale ) ); } //データベースのサイズ情報更新 get_img()->set_size( get_width() * 100 / get_img()->get_width() ); load_image(); } jdim-0.7.0/src/image/imagearea.h000066400000000000000000000005441417047150700164070ustar00rootroot00000000000000// ライセンス: GPL2 // // メイン画像クラス // #ifndef _IMAGEAREA_H #define _IMAGEAREA_H #include "imageareabase.h" namespace IMAGE { class ImageAreaMain : public ImageAreaBase { public: explicit ImageAreaMain( const std::string& url ); ~ImageAreaMain(); void show_image() override; }; } #endif jdim-0.7.0/src/image/imageareabase.cpp000066400000000000000000000131541417047150700175760ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "imageareabase.h" #include "jdlib/miscmsg.h" #include "dbimg/imginterface.h" #include "dbimg/img.h" #include "config/globalconf.h" #include // // スレッドのランチャ // static std::mutex imgarea_launcher_mutex; void* imgarea_launcher( void* dat ) { std::lock_guard< std::mutex > lock( imgarea_launcher_mutex ); #ifdef _DEBUG std::cout << "start imgarea_launcher" << std::endl; #endif IMAGE::ImageAreaBase* area = ( IMAGE::ImageAreaBase* )( dat ); area->load_image_thread(); #ifdef _DEBUG std::cout << "end" << std::endl; #endif return nullptr; } ////////////////////////////////// using namespace IMAGE; // interptype : // 0 -> INTERP_NEAREST // 1 -> INTERP_BILINEAR // 3 -> INTERP_HYPER ImageAreaBase::ImageAreaBase( const std::string& url, const int interptype ) : m_url( url ) , m_img( DBIMG::get_img( m_url ) ) { #ifdef _DEBUG std::cout << "ImageAreaBase::ImageAreaBase url = " << m_url << " type = " << interptype << std::endl; #endif assert( m_img ); m_interptype = Gdk::INTERP_NEAREST; if( interptype == 1 ) m_interptype = Gdk::INTERP_BILINEAR; else if( interptype >= 2 ) m_interptype = Gdk::INTERP_HYPER; set_halign( Gtk::ALIGN_CENTER ); set_valign( Gtk::ALIGN_CENTER ); } ImageAreaBase::~ImageAreaBase() { #ifdef _DEBUG std::cout << "ImageAreaBase::~ImageAreaBase url = " << m_url << std::endl; #endif // デストラクタの中からdispatchを呼ぶと落ちるので dispatch不可にする set_dispatchable( false ); stop(); wait(); } void ImageAreaBase::stop() { #ifdef _DEBUG std::cout << "ImageAreaBase::stop" << std::endl; #endif if( m_imgloader ) m_imgloader->request_stop(); } void ImageAreaBase::wait() { #ifdef _DEBUG std::cout << "ImageAreaBase::wait" << std::endl; #endif m_thread.join(); #ifdef _DEBUG std::cout << "ImageAreaBase::wait ok" << std::endl; #endif } // // 幅、高さセット // // 0にするとGdk::Pixbuf::create()で落ちるので注意 // void ImageAreaBase::set_width( const int width ) { m_width = MAX( 1, width ); } void ImageAreaBase::set_height( const int height ) { m_height = MAX( 1, height ); } // // 画像読み込み // void ImageAreaBase::load_image() { #ifdef _DEBUG std::cout << "ImageAreaBase::load_image" << std::endl; #endif // 大きい画像を表示しようとするとJDが固まるときがあるのでスレッドを使用する // ランチャ経由で load_image_thread() を動かす if( ! m_thread.create( imgarea_launcher, ( void* ) this, JDLIB::NODETACH ) ) { MISC::ERRMSG( "ImageAreaBase::load_image : could not start thread" ); } } // // 画像読み込みスレッド // void ImageAreaBase::load_image_thread() { #ifdef _DEBUG std::cout << "ImageAreaBase::load_image_thread url = " << get_url() << std::endl; #endif const int w_org = get_img()->get_width(); const int h_org = get_img()->get_height(); // アニメーションoff bool pixbufonly = ( w_org != get_width() || h_org != get_height() ); // pixbufonly = trueにすると プログレッシブJPGではモザイクがかかったようになる const int minsize = w_org/4; if( pixbufonly && get_width() > minsize && m_img->get_type() == DBIMG::T_JPG ) pixbufonly = false; // BMP の場合 pixbufonly = true にすると真っ黒になる if( pixbufonly && m_img->get_type() == DBIMG::T_BMP ) pixbufonly = false; std::string errmsg; if( ! create_imgloader( pixbufonly, errmsg ) ){ set_errmsg( errmsg ); MISC::ERRMSG( get_errmsg() ); } // 表示 // スレッドの中でset_image()すると固まるときがあるのでディスパッチャ経由で // callback_dispatch() -> set_image() の順に呼び出す dispatch(); #ifdef _DEBUG std::cout << "ImageAreaBase::load_image_thread finished" << std::endl; #endif } bool ImageAreaBase::create_imgloader( const bool pixbufonly, std::string& errmsg ) { m_imgloader = JDLIB::ImgLoader::get_loader( m_img->get_cache_path() ); bool ret = m_imgloader->load( pixbufonly ); if( ! ret ) { errmsg = m_imgloader->get_errmsg(); } return ret; } // // ディスパッチャのコールバック関数 // void ImageAreaBase::callback_dispatch() { set_image(); wait(); } // // Gtk::Image::set()を使用して画像表示 // void ImageAreaBase::set_image() { #ifdef _DEBUG std::cout << "ImageAreaBase::set_image" << std::endl; #endif clear(); const int w_org = get_img()->get_width(); const int h_org = get_img()->get_height(); Glib::RefPtr< Gdk::Pixbuf > pixbuf = m_imgloader->get_pixbuf(); if( pixbuf ){ if( m_img->get_mosaic() ){ // モザイク set_mosaic( pixbuf ); }else if( w_org != get_width() || h_org != get_height() ){ // 拡大縮小 set( pixbuf->scale_simple( get_width(), get_height(), m_interptype ) ); }else{ // 通常 set( m_imgloader->get_animation() ); } } m_imgloader.reset(); set_ready( true ); } // // モザイク表示 // void ImageAreaBase::set_mosaic( Glib::RefPtr< Gdk::Pixbuf > pixbuf ) { const int moswidth = get_img()->get_width_mosaic(); const int mosheight = get_img()->get_height_mosaic(); if( moswidth && mosheight ){ Glib::RefPtr< Gdk::Pixbuf > pixbuf2; pixbuf2 = pixbuf->scale_simple( moswidth, mosheight, Gdk::INTERP_NEAREST ); set( pixbuf2->scale_simple( get_width(), get_height(), Gdk::INTERP_NEAREST ) ); } } jdim-0.7.0/src/image/imageareabase.h000066400000000000000000000040271417047150700172420ustar00rootroot00000000000000// ライセンス: GPL2 // // 画像クラスのベースクラス // #ifndef _IMAGEAREABASE_H #define _IMAGEAREABASE_H #include #include "skeleton/dispatchable.h" #include "jdlib/jdthread.h" #include "jdlib/imgloader.h" namespace DBIMG { class Img; } namespace IMAGE { class ImageAreaBase : public Gtk::Image, public SKELETON::Dispatchable { std::string m_url; DBIMG::Img* m_img; Gdk::InterpType m_interptype; std::string m_errmsg; // エラーメッセージ bool m_ready{}; // 画像がsetされた int m_width{}; int m_height{}; // スレッド用変数 JDLIB::Thread m_thread; protected: Glib::RefPtr< JDLIB::ImgLoader > m_imgloader; public: // interptype : // 0 -> INTERP_NEAREST // 1 -> INTERP_BILINEAR // 3 -> INTERP_HYPER ImageAreaBase( const std::string& url, const int interptype ); ~ImageAreaBase(); void stop(); void wait(); const std::string& get_url() const{ return m_url;} const std::string& get_errmsg() const{ return m_errmsg;} bool is_ready() const { return m_ready; } bool is_loading() const { return m_thread.is_running(); } int get_width() const { return m_width; } int get_height() const { return m_height; } virtual void show_image() = 0; void set_fit_in_win( bool fit ); virtual void load_image_thread(); protected: DBIMG::Img* get_img() { return m_img; } void set_errmsg( const std::string& errmsg ){ m_errmsg = errmsg; } void set_ready( bool ready ){ m_ready = ready; } void set_width( const int width ); void set_height( const int height ); void load_image(); bool create_imgloader( const bool pixbufonly, std::string& errmsg ); private: void callback_dispatch() override; virtual void set_image(); void set_mosaic( Glib::RefPtr< Gdk::Pixbuf > pixbuf ); }; } #endif jdim-0.7.0/src/image/imageareaicon.cpp000066400000000000000000000122171417047150700176130ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "imageareaicon.h" #include "dbimg/img.h" #include "dbimg/imginterface.h" #include "jdlib/miscmsg.h" #include "jdlib/miscgtk.h" #include "config/globalconf.h" #include "global.h" #include "httpcode.h" #include "colorid.h" using namespace IMAGE; ImageAreaIcon::ImageAreaIcon( const std::string& url ) : ImageAreaBase( url, 0 ) // アイコンは常に Gdk::INTERP_NEAREST { #ifdef _DEBUG std::cout << "ImageAreaIcon::ImageAreaIcon url = " << url << std::endl; #endif set_width( ICON_SIZE ); set_height( ICON_SIZE ); } ImageAreaIcon::~ImageAreaIcon() { #ifdef _DEBUG std::cout << "ImageAreaIcon::~ImageAreaIcon url = " << get_url() << std::endl; #endif } // // 表示 // // メインスレッドで大きい画像を開くと反応が無くなるので別の // スレッドを起動して開く。 // void ImageAreaIcon::show_image() { #ifdef _DEBUG std::cout << "ImageAreaIcon::show_image url = " << get_url() << std::endl; #endif if( is_loading() ) return; // 既に画像が表示されている if( m_shown && get_img()->is_cached() ) return; if( m_pixbuf ){ m_pixbuf.reset(); m_pixbuf_loading.reset(); m_pixbuf_err.reset(); } if( m_pixbuf_icon ) m_pixbuf_icon.reset(); m_shown = false; set_ready( false ); // キャッシュされてない時は読み込みorエラーマークを表示 if( ! get_img()->is_cached() ){ show_indicator( ( get_img()->is_loading() || get_img()->is_wait() || get_img()->get_code() == HTTP_INIT ) ); set_image(); } // スレッドを起動してバックグラウンドでアイコン表示 else{ // 縮小比率を計算 double scale; int w_org = get_img()->get_width(); int h_org = get_img()->get_height(); double scale_w = ( double ) ICON_SIZE / w_org; double scale_h = ( double ) ICON_SIZE / h_org; scale = MIN( scale_w, scale_h ); set_width( (int)( w_org * scale ) ); set_height( (int)( h_org * scale ) ); load_image(); } } // // 表示スレッド // void ImageAreaIcon::load_image_thread() { #ifdef _DEBUG std::cout << "ImageAreaIcon::load_image_thread url = " << get_url() << std::endl; #endif bool pixbufonly = true; if( get_img()->get_type() == DBIMG::T_BMP ) pixbufonly = false; // BMP の場合 pixbufonly = true にすると真っ黒になる std::string errmsg; if( create_imgloader( pixbufonly, errmsg ) ){ Glib::RefPtr< Gdk::Pixbuf > pixbuf = m_imgloader->get_pixbuf(); if( pixbuf ) m_pixbuf_icon = pixbuf->scale_simple( get_width(), get_height(), Gdk::INTERP_NEAREST ); } m_imgloader.reset(); if( m_pixbuf_icon ){ m_imagetype = IMAGE_SHOW_ICON; m_shown = true; } else{ set_errmsg( errmsg ); MISC::ERRMSG( get_errmsg() ); show_indicator( false ); } // 表示 // スレッドの中でset_image()すると固まるときがあるのでディスパッチャ経由で // callback_dispatch() -> set_image() の順に呼び出す dispatch(); #ifdef _DEBUG std::cout << "ImageAreaIcon::load_image_thread finished" << std::endl; #endif } // // インジケータ幅、高さ int ImageAreaIcon::width_indicator() const { return MAX( 1, get_width()/4 ); } int ImageAreaIcon::height_indicator() const { return MAX( 1, get_height()/4 ); } // // インジゲータ画像表示 // void ImageAreaIcon::show_indicator( bool loading ) { #ifdef _DEBUG std::cout << "ImageAreaIcon::show_indicator load = " << loading << std::endl; #endif if( ! m_pixbuf ){ m_pixbuf = Gdk::Pixbuf::create( Gdk::COLORSPACE_RGB, false, 8, get_width(), get_height() ); m_pixbuf_err = Gdk::Pixbuf::create( Gdk::COLORSPACE_RGB, false, 8, width_indicator(), height_indicator() ); m_pixbuf_loading = Gdk::Pixbuf::create( Gdk::COLORSPACE_RGB, false, 8, width_indicator(), height_indicator() ); assert( m_pixbuf ); assert( m_pixbuf_err ); assert( m_pixbuf_loading ); m_pixbuf->fill( 0xffffff00 ); m_pixbuf_loading->fill( MISC::color_to_int( Gdk::RGBA( CONFIG::get_color( COLOR_IMG_LOADING ) ) ) ); m_pixbuf_err->fill( MISC::color_to_int( Gdk::RGBA( CONFIG::get_color( COLOR_IMG_ERR ) ) ) ); } // 読み込み中 if( loading ) m_pixbuf_loading->copy_area( 0, 0, width_indicator(), height_indicator(), m_pixbuf, 4, 4 ); // エラー else m_pixbuf_err->copy_area( 0, 0, width_indicator(), height_indicator(), m_pixbuf, 4, 4 ); m_imagetype = IMAGE_SHOW_INDICATOR; } // // 表示 // void ImageAreaIcon::set_image() { #ifdef _DEBUG std::cout << "ImageAreaIcon::set_image type = " << m_imagetype << std::endl; #endif clear(); if( m_imagetype == IMAGE_SHOW_ICON ){ #ifdef _DEBUG std::cout << "show icon" << std::endl; #endif set( m_pixbuf_icon ); } else if( m_imagetype == IMAGE_SHOW_INDICATOR ){ #ifdef _DEBUG std::cout << "show indicator" << std::endl; #endif set( m_pixbuf ); } set_ready( true ); } jdim-0.7.0/src/image/imageareaicon.h000066400000000000000000000017641417047150700172650ustar00rootroot00000000000000// ライセンス: GPL2 // // アイコン画像クラス // #ifndef _IMAGEAREAICON_H #define _IMAGEAREAICON_H #include "imageareabase.h" namespace IMAGE { // m_imagetype にセットする値 enum { IMAGE_SHOW_ICON = 0, IMAGE_SHOW_INDICATOR }; class ImageAreaIcon : public ImageAreaBase { bool m_shown{}; int m_imagetype{}; // dispatch()前に表示する画像を入れる Glib::RefPtr< Gdk::Pixbuf > m_pixbuf; Glib::RefPtr< Gdk::Pixbuf > m_pixbuf_loading; Glib::RefPtr< Gdk::Pixbuf > m_pixbuf_err; Glib::RefPtr< Gdk::Pixbuf > m_pixbuf_icon; public: explicit ImageAreaIcon( const std::string& url ); ~ImageAreaIcon(); void show_image() override; void load_image_thread() override; private: int width_indicator() const; int height_indicator() const; void show_indicator( bool loading ); void set_image() override; }; } #endif jdim-0.7.0/src/image/imageareapopup.cpp000066400000000000000000000027221417047150700200260ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "imageareapopup.h" #include "dbimg/img.h" #include "config/globalconf.h" #ifndef MIN #define MIN( a, b ) ( a < b ? a : b ) #endif using namespace IMAGE; ImageAreaPopup::ImageAreaPopup( const std::string& url ) : ImageAreaBase( url, CONFIG::get_imgpopup_interp() ) { #ifdef _DEBUG std::cout << "ImageAreaPopup::ImageAreaPopup url = " << url << std::endl; #endif } ImageAreaPopup::~ImageAreaPopup() { #ifdef _DEBUG std::cout << "ImageAreaPopup::~ImageAreaPopup url = " << get_url() << std::endl; #endif } // // 表示 // void ImageAreaPopup::show_image() { if( is_loading() ) return; if( ! get_img()->is_cached() ) return; #ifdef _DEBUG std::cout << "ImageAreaPopup::show_image url = " << get_url() << std::endl; #endif set_errmsg( std::string() ); const int width_max = CONFIG::get_imgpopup_width(); const int height_max = CONFIG::get_imgpopup_height(); // 縮小比率を計算 const int w_org = get_img()->get_width(); const int h_org = get_img()->get_height(); const double scale_w = ( double ) width_max / w_org; const double scale_h = ( double ) height_max / h_org; const double scale = MIN( scale_w, scale_h ); if( scale < 1 ){ set_width( (int)( w_org * scale ) ); set_height( (int)( h_org * scale ) ); } else{ set_width( w_org ); set_height( h_org ); } load_image(); } jdim-0.7.0/src/image/imageareapopup.h000066400000000000000000000006021417047150700174660ustar00rootroot00000000000000// ライセンス: GPL2 // // ポップアップ画像クラス // #ifndef _IMAGEAREAPOPUP_H #define _IMAGEAREAPOPUP_H #include "imageareabase.h" namespace IMAGE { class ImageAreaPopup : public ImageAreaBase { public: explicit ImageAreaPopup( const std::string& url ); ~ImageAreaPopup(); void show_image() override; }; } #endif jdim-0.7.0/src/image/imageview.cpp000066400000000000000000000354201417047150700170050ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "imageadmin.h" #include "imageview.h" #include "imagearea.h" #include "skeleton/msgdiag.h" #include "dbimg/img.h" #include "jdlib/miscutil.h" #include "config/globalconf.h" #include "control/controlid.h" #include "history/historymanager.h" #include "command.h" #include "httpcode.h" #include "session.h" #include "global.h" #include #ifndef MAX #define MAX( a, b ) ( a > b ? a : b ) #endif #ifndef MIN #define MIN( a, b ) ( a < b ? a : b ) #endif enum { IMGWIN_REDRAWTIME = 250 // msec }; using namespace IMAGE; ImageViewMain::ImageViewMain( const std::string& url ) : ImageViewBase( url ) { #ifdef _DEBUG std::cout << "ImageViewMain::ImageViewMain : " << get_url() << std::endl; #endif // コントロールモード設定 get_control().add_mode( CONTROL::MODE_IMAGEVIEW ); // スクロールウィンドウを作ってEventBoxを貼る m_scrwin = Gtk::manage( new Gtk::ScrolledWindow() ); assert( m_scrwin ); m_scrwin->property_vscrollbar_policy() = Gtk::POLICY_AUTOMATIC; m_scrwin->property_hscrollbar_policy() = Gtk::POLICY_AUTOMATIC; m_scrwin->add( get_event() ); pack_start( *m_scrwin ); setup_common(); // タイトルセット set_title( "[ 画像 ]" ); IMAGE::get_admin()->set_command( "set_title", get_url(), get_title() ); } ImageViewMain::~ImageViewMain() { // 閉じた画像履歴更新 HISTORY::append_history( URL_HISTCLOSEIMGVIEW, get_url(), MISC::get_filename( get_url() ), TYPE_IMAGE ); } // // クロック入力 // // アクティブ(表示されている)view以外にはクロックは来ないのに注意 // void ImageViewMain::clock_in() { View::clock_in(); const int width_view = get_width(); const int height_view = get_height(); // ステータスのタブ番号などの表示内容更新 if( m_update_status ){ m_update_status = false; add_tab_number(); m_show_status = true; } // viewがアクティブになった(クロック入力が来た)ときにステータス表示 if( m_show_status ){ m_show_status = false; IMAGE::get_admin()->set_command( "set_status", get_url(), get_status() ); } // ロード待ち if( is_wait() ){ // ロード待ち状態解除 if( ! get_img()->is_wait() ){ set_wait( false ); if( get_img()->is_loading() ){ set_loading( true ); set_status_local( "読み込み中" ); add_tab_number(); if( m_show_label ) m_label.set_text( get_status_local() ); } else{ set_loading( false ); show_view(); show_status(); } } } // ロード中 else if( is_loading() ){ // 読み込みサイズの表示更新 if( get_img()->is_loading() ) show_status(); // ロード完了 else{ set_loading( false ); show_view(); show_status(); } } // ロード中でなく、viewが表示されている else if( get_imagearea() && ! get_imagearea()->is_loading() && width_view > 1 && height_view > 1 ){ // バックグラウンドで開いた時やロード直後に画像を表示すると重くなるので // ビューがアクティブになった(クロック入力が来た) 時点で画面を表示する if( ! get_imagearea()->is_ready() ){ m_pre_width = width_view; m_pre_height = height_view; m_redraw_count = 10000; get_imagearea()->show_image(); // 読み込みエラーが起きたらimageareaを除いてラベルを貼る if( ! get_imagearea()->get_errmsg().empty() ){ set_status_local( get_imagearea()->get_errmsg() ); add_tab_number(); remove_imagearea(); set_label(); } show_status(); if( CONFIG::get_instruct_tglimg() ) show_instruct_diag(); } // サイズが変わって、かつ zoom to fit モードの場合再描画 else if( get_imagearea()->is_ready() && get_img()->is_cached() && get_img()->is_zoom_to_fit() && ( m_pre_width != width_view || m_pre_height != height_view ) ){ // 毎回再描画していると遅いのでカウンタを付ける ++m_redraw_count; if( m_redraw_count >= ( IMGWIN_REDRAWTIME / TIMER_TIMEOUT ) ) { m_pre_width = width_view; m_pre_height = height_view; m_redraw_count = 0; #ifdef _DEBUG std::cout << "ImageViewMain::clock_in resize\n" << get_url() << " " << m_pre_width << " - " << m_pre_height << std::endl; #endif get_imagearea()->show_image(); show_status(); } } else m_redraw_count = 0; } } // // スレビューとの切り替え方法説明ダイアログ表示 // void ImageViewMain::show_instruct_diag() { SKELETON::MsgCheckDiag mdiag( get_parent_win(), "画像ビューからスレビューに戻る方法として\n\n(1) マウスジェスチャを使う\n(マウス右ボタンを押しながら左または下にドラッグして右ボタンを離す)\n\n(2) マウスの5ボタンを押す\n\n(3) Alt+x か h か ← を押す\n\n(4) ツールバーのスレビューアイコンを押す\n\n(5) 表示メニューからスレビューを選ぶ\n\nなどがあります。詳しくはオンラインマニュアルを参照してください。", "今後表示しない(_D)" ); mdiag.set_title( "ヒント" ); mdiag.run(); if( mdiag.get_chkbutton().get_active() ) CONFIG::set_instruct_tglimg( false ); } // // ラベルを貼る // void ImageViewMain::set_label() { if( !m_show_label ){ m_label.set_text( get_status_local() ); get_event().add( m_label ); m_label.show(); m_show_label = true; } } // // ラベルをremove // void ImageViewMain::remove_label() { if( m_show_label ){ get_event().remove(); m_show_label = false; } } // // 表示 // void ImageViewMain::show_view() { if( is_loading() ) return; if( is_wait() ) return; #ifdef _DEBUG std::cout << "ImageViewMain::show_view url = " << get_url() << std::endl; #endif // 画像を既に表示している if( get_imagearea() ){ // キャッシュされてるなら再描画 if( get_img()->is_cached() ){ #ifdef _DEBUG std::cout << "redraw\n"; #endif get_imagearea()->show_image(); show_status(); return; } remove_imagearea(); } remove_label(); // ロード待ち、又は読み込み中 if( get_img()->is_wait() || get_img()->is_loading() ){ if( get_img()->is_wait() ){ #ifdef _DEBUG std::cout << "wait\n"; #endif set_wait( true ); set_loading( false ); set_status_local( "待機中" ); } else{ #ifdef _DEBUG std::cout << "loading\n"; #endif set_wait( false ); set_loading( true ); set_status_local( "読み込み中" ); } add_tab_number(); m_length_prev = 0; m_show_status = true; // viewがアクティブになった時点でステータス表示 set_label(); } // キャッシュがあるなら画像を表示 else if( get_img()->is_cached() ){ #ifdef _DEBUG std::cout << "set image\n"; #endif // 表示はビューがアクティブになった時に clock_in()の中で行う set_imagearea( Gtk::manage( new ImageAreaMain( get_url() ) ) ); } // エラー else{ if( ! get_img()->get_str_code().empty() ) set_status_local( get_img()->get_str_code() ); else set_status_local( "画像情報が存在しません。再読み込みして下さい" ); add_tab_number(); m_show_status = true; // viewがアクティブになった時点でステータス表示 set_label(); } show_all_children(); } // // ステータス表示 // void ImageViewMain::show_status() { if( ! is_loading() && ! is_wait() ){ // 画像が表示されていたら画像情報 if( get_imagearea() ){ std::stringstream ss; ss << get_img()->get_width() << " x " << get_img()->get_height(); if( get_img()->get_width() ) ss << " (" << get_img()->get_size() << " %)"; ss << " " << get_img()->total_length()/1024 << " K "; if( get_img()->is_protected() ) ss << " 保護中"; set_status_local( ss.str() ); } // エラー(ネットワーク系) else if( get_img()->get_code() != HTTP_OK ) set_status_local( get_img()->get_str_code() ); add_tab_number(); m_show_status = true; // viewがアクティブになった時点でステータス表示 if( m_show_label ) m_label.set_text( get_status_local() ); } // ロード中 else if( is_loading() ){ // 読み込みサイズが更新した場合 if( m_length_prev != get_img()->current_length() ){ m_length_prev = get_img()->current_length(); std::string tmpstr = std::to_string( m_length_prev / 1024 ); tmpstr.append( " / " ); tmpstr.append( std::to_string( get_img()->total_length() / 1024 ) ); tmpstr.append( " KiB" ); set_status_local( tmpstr ); add_tab_number(); m_show_status = true; // viewがアクティブになった時点でステータス表示 if( m_show_label ) m_label.set_text( get_status_local() ); } } } void ImageViewMain::update_status() { m_update_status = true; // viewがアクティブになった時点でステータス表示更新 } // // ステータスのタブ番号などの表示内容更新 // void ImageViewMain::add_tab_number() { set_status( " [" + std::to_string( IMAGE::get_admin()->get_current_page() + 1 ) + "/" + std::to_string( IMAGE::get_admin()->get_tab_nums() ) + "] " + get_status_local() + " [" + MISC::get_filename( get_url() ) + " (" + MISC::get_hostname( get_url(), false ) + ")]" ); #ifdef _DEBUG std::cout << "ImageViewMain::add_tab_number : " << get_status() << std::endl;; #endif } // // ポップアップメニュー取得 // // SKELETON::View::show_popupmenu() を参照すること // Gtk::Menu* ImageViewMain::get_popupmenu( const std::string& url ) { Gtk::Menu* popupmenu = dynamic_cast< Gtk::Menu* >( ui_manager()->get_widget( "/popup_menu" ) ); return popupmenu; } // // ボタンクリック // bool ImageViewMain::slot_button_press( GdkEventButton* event ) { ImageViewBase::slot_button_press( event ); // ドラッグして画像移動するときの起点 m_x_motion = event->x_root; m_y_motion = event->y_root; m_scrolled = false; // ボタンをリリースした時に大きさを変更 m_do_resizing = false; if( ! is_loading() && ! is_wait() && get_control().button_alloted( event, CONTROL::ResizeImageButton ) ) m_do_resizing = true; return true; } // // マウスモーション // bool ImageViewMain::slot_motion_notify( GdkEventMotion* event ) { ImageViewBase::slot_motion_notify( event ); // スクロールバー移動 if( m_scrwin ){ GdkEventButton event_button; get_control().get_eventbutton( CONTROL::ScrollImageButton, event_button ); #ifdef _DEBUG // std::cout << "state = " << event->state << " / " << GDK_BUTTON1_MASK << " button = " << event_button.button << std::endl; #endif if( ( ( event->state & GDK_BUTTON1_MASK ) && event_button.button == 1 ) || ( ( event->state & GDK_BUTTON2_MASK ) && event_button.button == 2 ) || ( ( event->state & GDK_BUTTON3_MASK ) && event_button.button == 3 ) ){ auto hadj = m_scrwin->get_hadjustment(); auto vadj = m_scrwin->get_vadjustment(); gdouble dx = event->x_root - m_x_motion; gdouble dy = event->y_root - m_y_motion; #ifdef _DEBUG // std::cout << "dx = " << dx << " dy = " << dy << std::endl; #endif m_x_motion = event->x_root; m_y_motion = event->y_root; if( hadj ) hadj->set_value( MAX( hadj->get_lower(), MIN( hadj->get_upper() - hadj->get_page_size(), hadj->get_value() - dx ) ) ); if( vadj ) vadj->set_value( MAX( vadj->get_lower(), MIN( vadj->get_upper() - vadj->get_page_size(), vadj->get_value() - dy ) ) ); m_scrolled = true; } } return true; } // // 上スクロール // void ImageViewMain::scroll_up() { #ifdef _DEBUG std::cout << "ImageViewMain::scroll_up\n"; #endif auto vadjust = m_scrwin->get_vadjustment(); if( !vadjust ) return; vadjust->set_value( MAX( 0, vadjust->get_value() - vadjust->get_step_increment() ) ); } // // 下スクロール // void ImageViewMain::scroll_down() { #ifdef _DEBUG std::cout << "ImageViewMain::scroll_down\n"; #endif auto vadjust = m_scrwin->get_vadjustment(); if( !vadjust ) return; vadjust->set_value( MIN( vadjust->get_upper() - vadjust->get_page_size(), vadjust->get_value() + vadjust->get_step_increment() ) ); } // // 左スクロール // void ImageViewMain::scroll_left() { #ifdef _DEBUG std::cout << "ImageViewMain::scroll_left\n"; #endif auto hadjust = m_scrwin->get_hadjustment(); if( !hadjust ) return; hadjust->set_value( MAX( 0, hadjust->get_value() - hadjust->get_step_increment() ) ); } // // 右スクロール // void ImageViewMain::scroll_right() { #ifdef _DEBUG std::cout << "ImageViewMain::scroll_right\n"; #endif auto hadjust = m_scrwin->get_hadjustment(); if( !hadjust ) return; hadjust->set_value( MIN( hadjust->get_upper() - hadjust->get_page_size(), hadjust->get_value() + hadjust->get_step_increment() ) ); } // // viewの操作 // bool ImageViewMain::operate_view( const int control ) { // スクロールしたときはキャンセル if( m_scrolled ){ m_scrolled = false; return false; } int cntl = control; if( m_do_resizing ){ m_do_resizing = false; if( get_img()->get_size() == 100 ) cntl = CONTROL::ZoomFitImage; else cntl = CONTROL::OrgSizeImage; } return ImageViewBase::operate_view( cntl ); } jdim-0.7.0/src/image/imageview.h000066400000000000000000000031371417047150700164520ustar00rootroot00000000000000// ライセンス: GPL2 // // 画像ビュークラス // #ifndef _IMAGEVIEW_H #define _IMAGEVIEW_H #include "imageviewbase.h" namespace IMAGE { class ImageViewMain : public ImageViewBase { Gtk::ScrolledWindow* m_scrwin{}; Gtk::Label m_label; gdouble m_x_motion{}; gdouble m_y_motion{}; size_t m_length_prev{}; bool m_show_status{}; bool m_update_status{}; bool m_show_label{}; std::string m_status_local; int m_pre_width{}; int m_pre_height{}; int m_redraw_count{}; bool m_do_resizing{}; bool m_scrolled{}; public: explicit ImageViewMain( const std::string& url ); ~ImageViewMain(); void clock_in() override; void show_view() override; void scroll_up() override; void scroll_down() override; void scroll_left() override; void scroll_right() override; bool operate_view( const int control ) override; protected: Gtk::Menu* get_popupmenu( const std::string& url ) override; bool slot_button_press( GdkEventButton* event ) override; bool slot_motion_notify( GdkEventMotion* event ) override; private: void set_status_local( const std::string& status ){ m_status_local = status; } const std::string& get_status_local() const { return m_status_local; } void show_status() override; void update_status() override; void add_tab_number(); void show_instruct_diag(); void set_label(); void remove_label(); }; } #endif jdim-0.7.0/src/image/imageviewbase.cpp000066400000000000000000001041171417047150700176400ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "imageadmin.h" #include "imageviewbase.h" #include "imageareabase.h" #include "skeleton/msgdiag.h" #include "dbtree/interface.h" #include "dbimg/imginterface.h" #include "dbimg/img.h" #include "jdlib/miscgtk.h" #include "control/controlutil.h" #include "control/controlid.h" #include "config/globalconf.h" #include "cache.h" #include "command.h" #include "sharedbuffer.h" #include "global.h" #include "type.h" #include "prefdiagfactory.h" #include "usrcmdmanager.h" #include "httpcode.h" #include "session.h" #include #define SIZE_MENU { 25, 50, 75, 100, 150, 200, 400 } using namespace IMAGE; ImageViewBase::ImageViewBase( const std::string& url, const std::string& arg1, const std::string& arg2 ) : SKELETON::View( url ) , m_img{ DBIMG::get_img( get_url() ) } // 高速化のためデータベースに直接アクセス , m_enable_menuslot( true ) { assert( m_img ); // マウスジェスチャ可能 set_enable_mg( true ); } ImageViewBase::~ImageViewBase() { #ifdef _DEBUG std::cout << "ImageViewBase::~ImageViewBase : " << get_url() << std::endl; #endif } SKELETON::Admin* ImageViewBase::get_admin() { return IMAGE::get_admin(); } // // 親ウィンドウを取得 // Gtk::Window* ImageViewBase::get_parent_win() { return IMAGE::get_admin()->get_win(); } // // 共通セットアップ // void ImageViewBase::setup_common() { const int default_width = 200; const int default_height = 50; set_width_client( default_width ); set_height_client( default_height ); // focus 可、モーションキャプチャ可 m_event.set_can_focus( true ); m_event.add_events( Gdk::POINTER_MOTION_MASK ); m_event.add_events( Gdk::SMOOTH_SCROLL_MASK ); m_event.signal_button_press_event().connect( sigc::mem_fun( *this, &ImageViewBase::slot_button_press ) ); m_event.signal_button_release_event().connect( sigc::mem_fun( *this, &ImageViewBase::slot_button_release ) ); m_event.signal_motion_notify_event().connect( sigc::mem_fun( *this, &ImageViewBase::slot_motion_notify ) ); m_event.signal_key_press_event().connect( sigc::mem_fun(*this, &ImageViewBase::slot_key_press ) ); m_event.signal_scroll_event().connect( sigc::mem_fun(*this, &ImageViewBase::slot_scroll_event ) ); m_event.signal_enter_notify_event().connect( sigc::mem_fun( *this, &ImageViewBase::slot_enter_notify_event ) ); m_event.signal_leave_notify_event().connect( sigc::mem_fun( *this, &ImageViewBase::slot_leave_notify_event ) ); m_event.grab_focus(); // ポップアップメニューの設定 // アクショングループを作ってUIマネージャに登録 action_group() = Gtk::ActionGroup::create(); action_group()->add( Gtk::Action::create( "CancelMosaic", "CancelMosaic"), sigc::mem_fun( *this, &ImageViewBase::slot_cancel_mosaic ) ); action_group()->add( Gtk::Action::create( "ShowLargeImg", "サイズが大きい画像を表示(_G)"), sigc::mem_fun( *this, &ImageViewBase::slot_show_large_img ) ); action_group()->add( Gtk::Action::create( "LoadStop", "StopLoading" ), sigc::mem_fun( *this, &ImageViewBase::stop ) ); action_group()->add( Gtk::Action::create( "Reload", "強制再読み込み(_E)"), sigc::mem_fun( *this, &ImageViewBase::slot_reload_force ) ); action_group()->add( Gtk::Action::create( "AppendFavorite", "AppendFavorite"), sigc::mem_fun( *this, &ImageViewBase::slot_favorite ) ); action_group()->add( Gtk::Action::create( "ZoomFitImage", "ZoomFitImage" ), sigc::mem_fun( *this, &ImageViewBase::slot_fit_win ) ); action_group()->add( Gtk::Action::create( "ZoomInImage", "ZoomInImage" ), sigc::mem_fun( *this, &ImageViewBase::slot_zoom_in ) ); action_group()->add( Gtk::Action::create( "ZoomOutImage", "ZoomOutImage" ), sigc::mem_fun( *this, &ImageViewBase::slot_zoom_out ) ); action_group()->add( Gtk::Action::create( "OrgSizeImage", "OrgSizeImage" ), sigc::bind< int >( sigc::mem_fun( *this, &ImageViewBase::slot_resize_image ), 100 ) ); action_group()->add( Gtk::Action::create( "Size_Menu", "サイズ変更(_R)" ) ); // サイズ unsigned int size[] = SIZE_MENU; for( unsigned int i = 0; i < sizeof( size )/sizeof( unsigned int ) ; ++i ){ int tmp_size = size[ i ]; std::string str_size = std::to_string( tmp_size ); //ショートカットは、1から始まる std::string str_shortcut = "(_" + std::to_string( i+1 ) + ")"; Glib::RefPtr< Gtk::Action > action = Gtk::Action::create( "Size" + str_size, str_size + "%" + str_shortcut ); action_group()->add( action, sigc::bind< int >( sigc::mem_fun( *this, &ImageViewBase::slot_resize_image ), tmp_size ) ); } action_group()->add( Gtk::Action::create( "Move_Menu", ITEM_NAME_GO "(_M)" ) ); action_group()->add( Gtk::Action::create( "MoveHead", "先頭に移動(_H)" ), sigc::mem_fun( *this, &ImageViewBase::slot_move_head ) ); action_group()->add( Gtk::Action::create( "MoveTail", "最後に移動(_T)" ), sigc::mem_fun( *this, &ImageViewBase::slot_move_tail ) ); action_group()->add( Gtk::Action::create( "Quit", "Quit" ), sigc::mem_fun( *this, &ImageViewBase::close_view ) ); action_group()->add( Gtk::Action::create( "Close_Menu", "複数の画像を閉じる(_L)" ) ); action_group()->add( Gtk::Action::create( "CloseOther", "他の画像(_O)" ), sigc::mem_fun( *this, &ImageViewBase::slot_close_other_views ) ); action_group()->add( Gtk::Action::create( "CloseLeft", "左←の画像(_L)" ), sigc::mem_fun( *this, &ImageViewBase::slot_close_left_views ) ); action_group()->add( Gtk::Action::create( "CloseRight", "右→の画像(_R)" ), sigc::mem_fun( *this, &ImageViewBase::slot_close_right_views ) ); action_group()->add( Gtk::Action::create( "CloseError404", "エラー画像(404,403のみ)(_E)" ), sigc::mem_fun( *this, &ImageViewBase::slot_close_error_views ) ); action_group()->add( Gtk::Action::create( "CloseError503", "エラー画像(timeout,503以外)(_T)" ), sigc::mem_fun( *this, &ImageViewBase::slot_close_notimeout_error_views ) ); action_group()->add( Gtk::Action::create( "CloseErrorAll", "エラー画像(読込み中含め全て)(_W)" ), sigc::mem_fun( *this, &ImageViewBase::slot_close_all_error_views ) ); action_group()->add( Gtk::Action::create( "CloseNoError", "エラー以外の画像(_N)" ), sigc::mem_fun( *this, &ImageViewBase::slot_close_noerror_views ) ); action_group()->add( Gtk::Action::create( "CloseAll", "全ての画像(_A)" ), sigc::mem_fun( *this, &ImageViewBase::slot_close_all_views ) ); action_group()->add( Gtk::ToggleAction::create( "LockTab", "タブをロックする(_K)", std::string(), false ), sigc::mem_fun( *this, &ImageViewBase::slot_lock ) ); action_group()->add( Gtk::Action::create( "OpenBrowser", ITEM_NAME_OPEN_BROWSER "(_W)" ), sigc::mem_fun( *this, &ImageViewBase::slot_open_browser ) ); action_group()->add( Gtk::Action::create( "OpenCacheBrowser", ITEM_NAME_OPEN_CACHE_BROWSER "(_X)" ), sigc::mem_fun( *this, &ImageViewBase::slot_open_cache_browser ) ); action_group()->add( Gtk::Action::create( "OpenRef", "参照元のレスを開く(_O)"), sigc::mem_fun( *this, &ImageViewBase::slot_open_ref ) ); action_group()->add( Gtk::Action::create( "CopyURL", ITEM_NAME_COPY_URL "(_U)" ), sigc::mem_fun( *this, &ImageViewBase::slot_copy_url ) ); action_group()->add( Gtk::Action::create( "Save", "Save"), sigc::mem_fun( *this, &ImageViewBase::slot_save ) ); action_group()->add( Gtk::Action::create( "SaveAll", "全ての画像を保存(_A)..."), sigc::mem_fun( *this, &ImageViewBase::slot_save_all ) ); action_group()->add( Gtk::Action::create( "DeleteMenu", "Delete" ) ); action_group()->add( Gtk::Action::create( "DeleteImage", "削除する(_D)"), sigc::mem_fun( *this, &ImageViewBase::delete_view ) ); action_group()->add( Gtk::ToggleAction::create( "ProtectImage", "キャッシュを保護する(_H)", std::string(), false ), sigc::mem_fun( *this, &ImageViewBase::slot_toggle_protectimage ) ); action_group()->add( Gtk::Action::create( "AboneImage", "画像をあぼ〜んする(_B)"), sigc::mem_fun( *this, &ImageViewBase::slot_abone_img ) ); action_group()->add( Gtk::Action::create( "PreferenceImage", "PreferenceImage"), sigc::mem_fun( *this, &ImageViewBase::show_preference ) ); action_group()->add( Gtk::Action::create( "Preference", "プロパティ(_P)..."), sigc::mem_fun( *this, &ImageViewBase::show_preference ) ); const std::string usrcmd = CORE::get_usrcmd_manager()->create_usrcmd_menu( action_group() ); const int usrcmd_size = CORE::get_usrcmd_manager()->get_size(); for( int i = 0; i < usrcmd_size; ++i ){ Glib::RefPtr< Gtk::Action > act = CORE::get_usrcmd_manager()->get_action( action_group(), i ); if( act ) act->signal_activate().connect( sigc::bind< int >( sigc::mem_fun( *this, &ImageViewBase::slot_usrcmd ), i ) ); } ui_manager() = Gtk::UIManager::create(); ui_manager()->insert_action_group( action_group() ); // 画像ビューのメニュー const std::string menu = "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" + usrcmd + std::string( "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ); // アイコンのメニュー const std::string menu_icon = "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""; // 画像ポップアップのメニュー const std::string menu_popup = "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ; ui_manager()->add_ui_from_string( "" + menu + menu_icon + menu_popup + "" ); // ポップアップメニューにキーアクセレータやマウスジェスチャを表示 Gtk::Menu* popupmenu = dynamic_cast< Gtk::Menu* >( ui_manager()->get_widget( "/popup_menu" ) ); CONTROL::set_menu_motion( popupmenu ); popupmenu = dynamic_cast< Gtk::Menu* >( ui_manager()->get_widget( "/popup_menu_icon" ) ); CONTROL::set_menu_motion( popupmenu ); popupmenu = dynamic_cast< Gtk::Menu* >( ui_manager()->get_widget( "/popup_menu_popup" ) ); CONTROL::set_menu_motion( popupmenu ); } // // ImageAreaBaseのセット // void ImageViewBase::set_imagearea( ImageAreaBase* imagearea ) { assert( imagearea ); assert( ! m_imagearea ); m_imagearea = imagearea; set_width_client( imagearea->get_width() ); set_height_client( imagearea->get_height() ); m_event.add( *m_imagearea ); } // // ImageAreaBaseのクリア // void ImageViewBase::remove_imagearea() { if( m_imagearea ){ m_event.remove(); delete m_imagearea; m_imagearea = nullptr; } } // // コマンド // bool ImageViewBase::set_command( const std::string& command, const std::string& arg1, const std::string& arg2 ) { if( command == "switch_icon" ) switch_icon(); else if( command == "update_status" ) update_status(); return true; } // // 再読込 // void ImageViewBase::reload() { #ifdef _DEBUG std::cout << "ImageViewBase::reload url = " << get_url() << std::endl; #endif std::string refurl = m_img->get_refurl(); const bool mosaic = m_img->get_mosaic(); m_img->download_img( refurl, mosaic, 0 ); if( m_img->is_loading() ){ CORE::core_set_command( "redraw", get_url() ); CORE::core_set_command( "redraw_article" ); } } // // ロード停止 // void ImageViewBase::stop() { #ifdef _DEBUG std::cout << "ImageViewBase::stop url = " << get_url() << std::endl; #endif m_img->stop_load(); } // // 再描画 // void ImageViewBase::redraw_view() { #ifdef _DEBUG std::cout << "ImageViewBase::redraw_view url = " << get_url() << std::endl; #endif show_view(); } // // 先頭に移動 // void ImageViewBase::slot_move_head() { IMAGE::get_admin()->set_command( "tab_head", "" ); } // // 最後に移動 // void ImageViewBase::slot_move_tail() { IMAGE::get_admin()->set_command( "tab_tail", "" ); } // // 閉じる // void ImageViewBase::close_view() { IMAGE::get_admin()->set_command( "close_view", get_url() ); } // // 他の画像を閉じる // void ImageViewBase::slot_close_other_views() { IMAGE::get_admin()->set_command( "close_other_views", get_url() ); } // // 左の画像を閉じる // void ImageViewBase::slot_close_left_views() { IMAGE::get_admin()->set_command( "close_left_views", get_url() ); } // // 右の画像を閉じる // void ImageViewBase::slot_close_right_views() { IMAGE::get_admin()->set_command( "close_right_views", get_url() ); } // // エラーの画像を閉じる( HTTP404 のみ ) // void ImageViewBase::slot_close_error_views() { IMAGE::get_admin()->set_command( "close_error_views", "", "" ); } // // エラーの画像を閉じる( timeout, 503 以外 ) // void ImageViewBase::slot_close_notimeout_error_views() { IMAGE::get_admin()->set_command( "close_error_views", "", "notimeout" ); } // // エラーの画像を閉じる( 全て ) // void ImageViewBase::slot_close_all_error_views() { IMAGE::get_admin()->set_command( "close_error_views", "", "all" ); } // // エラー以外の画像を閉じる // void ImageViewBase::slot_close_noerror_views() { IMAGE::get_admin()->set_command( "close_noerror_views" ); } // // 全ての画像を閉じる // void ImageViewBase::slot_close_all_views() { IMAGE::get_admin()->set_command( "close_all_views" ); } // // プロパティ // void ImageViewBase::show_preference() { CORE::core_set_command( "hide_popup" ); auto pref = CORE::PrefDiagFactory( get_parent_win(), CORE::PREFDIAG_IMAGE, get_url() ); IMAGE::get_admin()->set_command_immediately( "disable_fold_win" ); // run 直前に呼ぶこと pref->run(); IMAGE::get_admin()->set_command_immediately( "enable_fold_win" ); // run 直後に呼ぶこと } // // 削除 // void ImageViewBase::delete_view() { delete_view_impl( false ); } void ImageViewBase::delete_view_impl( const bool show_diag ) { CORE::core_set_command( "hide_popup" ); if( show_diag ){ if( m_img->is_protected() ){ SKELETON::MsgDiag mdiag( get_parent_win(), "キャッシュ保護されています" ); mdiag.run(); return; } else if( CONFIG::get_show_delimgdiag() ){ SKELETON::MsgCheckDiag mdiag( get_parent_win(), "画像を削除しますか?", "今後表示しない(常に削除) (_D)", Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO, Gtk::RESPONSE_NO ); if( mdiag.run() != Gtk::RESPONSE_YES ) return; if( mdiag.get_chkbutton().get_active() ) CONFIG::set_show_delimgdiag( false ); } } CORE::core_set_command( "delete_image", get_url() ); } // // クリックした時の処理 // void ImageViewBase::clicked() { IMAGE::get_admin()->set_command( "switch_image", get_url() ); IMAGE::get_admin()->set_command( "switch_admin" ); } // // viewの操作 // bool ImageViewBase::operate_view( const int control ) { if( CONTROL::operate_common( control, get_url(), IMAGE::get_admin() ) ) return true; #ifdef _DEBUG std::cout << "ImageViewBase::operate_view control = " << control << std::endl; #endif switch( control ){ case CONTROL::CancelMosaic: case CONTROL::CancelMosaicButton: slot_cancel_mosaic(); break; case CONTROL::ZoomInImage: slot_zoom_in(); break; case CONTROL::ZoomOutImage: slot_zoom_out(); break; case CONTROL::ZoomFitImage: slot_fit_win(); break; case CONTROL::OrgSizeImage: slot_resize_image( 100 ); break; case CONTROL::ReloadTabButton: case CONTROL::Reload: reload(); break; case CONTROL::StopLoading: stop(); break; case CONTROL::CloseImageButton: case CONTROL::CloseImageTabButton: case CONTROL::Quit: close_view(); break; // スクロール case CONTROL::ScrollUpImage: IMAGE::get_admin()->set_command( "scroll_up" ); break; case CONTROL::ScrollDownImage: IMAGE::get_admin()->set_command( "scroll_down" ); break; case CONTROL::ScrollLeftImage: IMAGE::get_admin()->set_command( "scroll_left" ); break; case CONTROL::ScrollRightImage: IMAGE::get_admin()->set_command( "scroll_right" ); break; // article に切り替え case CONTROL::Left: CORE::core_set_command( "switch_leftview" ); break; case CONTROL::ToggleArticle: CORE::core_set_command( "toggle_article" ); break; case CONTROL::Save: case CONTROL::SaveImageButton: slot_save(); break; case CONTROL::Delete: delete_view_impl( true ); break; // お気に入りに追加 case CONTROL::AppendFavorite: slot_favorite(); break; // 画像のプロパティ case CONTROL::PreferenceView: show_preference(); break; default: return false; } return true; } // // キープレスイベント // bool ImageViewBase::slot_key_press( GdkEventKey* event ) { #ifdef _DEBUG std::cout << "ImageViewBase::slot_key_press url = " << get_url() << std::endl; #endif return operate_view( get_control().key_press( event ) ); } // // ボタンクリック // bool ImageViewBase::slot_button_press( GdkEventButton* event ) { #ifdef _DEBUG std::cout << "ImageViewBase::slot_button_press url = " << get_url() << std::endl; #endif // マウスジェスチャ get_control().MG_start( event ); // ホイールマウスジェスチャ get_control().MG_wheel_start( event ); // ダブルクリック // button_release_eventでは event->type に必ず GDK_BUTTON_RELEASE が入る m_dblclick = false; if( event->type == GDK_2BUTTON_PRESS ) m_dblclick = true; // クリック // 反応を良くするため slot_button_release() ではなくてボタンを押した時点で処理する if( get_control().button_alloted( event, CONTROL::ClickButton ) ) clicked(); return true; } // // マウスボタンのリリースイベント // bool ImageViewBase::slot_button_release( GdkEventButton* event ) { /// マウスジェスチャ int mg = get_control().MG_end( event ); // ホイールマウスジェスチャ // 実行された場合は何もしない if( get_control().MG_wheel_end( event ) ) return true; if( mg != CONTROL::None && enable_mg() ){ operate_view( mg ); return true; } // ダブルクリックの処理のため一時的にtypeを切替える GdkEventType type_copy = event->type; if( m_dblclick ) event->type = GDK_2BUTTON_PRESS; // ポップアップメニュー if( get_control().button_alloted( event, CONTROL::PopupmenuButton ) ){ show_popupmenu( "", false ); } else if( is_under_mouse() ) operate_view( get_control().button_press( event ) ); event->type = type_copy; return true; } // // マウスモーション // bool ImageViewBase::slot_motion_notify( GdkEventMotion* event ) { /// マウスジェスチャ get_control().MG_motion( event ); return true; } // // マウスホイールイベント // bool ImageViewBase::slot_scroll_event( GdkEventScroll* event ) { // ホイールマウスジェスチャ int control = get_control().MG_wheel_scroll( event ); if( enable_mg() && control != CONTROL::None ){ operate_view( control ); return true; } return false; } // // マウスが入った // bool ImageViewBase::slot_enter_notify_event( GdkEventCrossing* event ) { #ifdef _DEBUG std::cout << "ImageViewBase::slot_enter_notify_event\n"; #endif m_under_mouse = true; return true; } // // マウスが出た // bool ImageViewBase::slot_leave_notify_event( GdkEventCrossing* event ) { #ifdef _DEBUG std::cout << "ImageViewBase::slot_leave_notify_event\n"; #endif m_under_mouse = false; return true; } // // 強制再読み込み // void ImageViewBase::slot_reload_force() { if( ! m_enable_menuslot ) return; if( ! SESSION::is_online() ){ CORE::core_set_command( "hide_popup" ); SKELETON::MsgDiag mdiag( get_parent_win(), "オフラインです" ); mdiag.run(); return; } // コードをリセットしてから再読み込みをかける m_img->reset(); m_img->set_code( HTTP_INIT ); m_img->set_type( DBIMG::T_UNKNOWN ); reload(); } // // モザイク解除 // void ImageViewBase::slot_cancel_mosaic() { if( ! m_enable_menuslot ) return; if( ! m_img->is_cached() ) return; if( ! m_img->get_mosaic() ) return; if( m_img->is_fake() ){ CORE::core_set_command( "hide_popup" ); std::string type = "本当の画像タイプは"; switch( DBIMG::get_type_real( get_url() ) ){ case DBIMG::T_JPG: type += "JPEG"; break; case DBIMG::T_PNG: type += "PNG"; break; case DBIMG::T_GIF: type += "GIF"; break; case DBIMG::T_BMP: type += "BMP"; break; case DBIMG::T_WEBP: type += "WebP"; break; case DBIMG::T_AVIF: type += "AVIF"; break; } type += "です。"; SKELETON::MsgDiag mdiag( get_parent_win(), "拡張子が偽装されています。" + type + "\n\nモザイクを解除しますか?", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO ); mdiag.set_default_response( Gtk::RESPONSE_NO ); if( mdiag.run() != Gtk::RESPONSE_YES ) return; } m_img->set_mosaic( false ); CORE::core_set_command( "redraw", get_url() ); } // // 大きい画像を表示 // void ImageViewBase::slot_show_large_img() { m_img->show_large_img(); } // // ウィンドウにサイズを合わせる // void ImageViewBase::slot_fit_win() { if( m_img->is_zoom_to_fit() ) SESSION::toggle_img_fit_mode(); m_img->set_zoom_to_fit( true ); CORE::core_set_command( "redraw", get_url() ); } // // ズームイン // void ImageViewBase::slot_zoom_in() { zoom_in_out( true ); } // // ズームアウト // void ImageViewBase::slot_zoom_out() { zoom_in_out( false ); } // // ズームイン、アウトの実行部 void ImageViewBase::zoom_in_out( bool zoomin ) { unsigned int size[] = SIZE_MENU; unsigned int size_current = m_img->get_size(); int size_zoomin = 0, size_zoomout = 0; // 現在のサイズから次のサイズを決定 if( size_current ){ size_zoomin = size_current + 100; size_zoomout = size_current - 100; unsigned int maxsize = sizeof( size )/sizeof( unsigned int ); for( unsigned int i = 1; i < maxsize ; ++i ){ if( zoomin && size[ i ] > size_current + 5 ){ size_zoomin = size[ i ]; break; } if( !zoomin && size[ i ] > size_current -5 ){ size_zoomout = size[ i -1 ]; break; } } } #ifdef _DEBUG std::cout << "ImageViewBase::zoom_in_out\n" << "size_current = " << size_current << std::endl << "zoomin = " << size_zoomin << std::endl << "zoomout = " << size_zoomout << std::endl; #endif if( zoomin ) slot_resize_image( size_zoomin ); else slot_resize_image( size_zoomout ); } // // 画像サイズ // void ImageViewBase::slot_resize_image( int size ) { unsigned int sizemenu[] = SIZE_MENU; int maxsize = sizemenu[ sizeof( sizemenu )/sizeof( unsigned int ) -1 ]; if( size <= 0 ) return; if( size > maxsize ) return; if( !m_img->is_zoom_to_fit() && m_img->get_size() == size ) return; m_img->set_zoom_to_fit( false ); m_img->set_size( size ); CORE::core_set_command( "redraw", get_url() ); } // // ロックする // void ImageViewBase::slot_lock() { if( ! m_enable_menuslot ) return; if( is_locked() ) unlock(); else lock(); } // // ブラウザで開く // void ImageViewBase::slot_open_browser() { if( ! m_enable_menuslot ) return; CORE::core_set_command( "open_url_browser", get_url() ); } // // キャッシュをブラウザで開く // void ImageViewBase::slot_open_cache_browser() { if( ! m_enable_menuslot ) return; if( ! m_img->is_cached() ) return; const std::string url = "file://" + m_img->get_cache_path(); CORE::core_set_command( "open_url_browser", url ); } // // 参照元を開く // void ImageViewBase::slot_open_ref() { if( ! m_enable_menuslot ) return; const std::string refurl = m_img->get_refurl(); int center, from, to; std::string num_str; const std::string url = DBTREE::url_dat( refurl, center, to, num_str ); if( url.empty() ) return; const int range = 10; from = MAX( 0, center - range ); to = center + range; std::stringstream ss; ss << from << "-" << to; CORE::core_set_command( "open_article_res" ,url, ss.str(), std::to_string( center ) ); } // // URLをクリップボードにコピー // void ImageViewBase::slot_copy_url() { if( ! m_enable_menuslot ) return; MISC::CopyClipboard( get_url() ); } // // 保存 // void ImageViewBase::slot_save() { if( ! m_enable_menuslot ) return; CORE::core_set_command( "hide_popup" ); m_img->save( IMAGE::get_admin()->get_win(), std::string() ); } // // すべて保存 // void ImageViewBase::slot_save_all() { if( ! m_enable_menuslot ) return; IMAGE::get_admin()->set_command( "save_all" ); } // // お気に入り // void ImageViewBase::slot_favorite() { if( ! m_enable_menuslot ) return; set_image_to_buffer(); CORE::core_set_command( "append_favorite", URL_FAVORITEVIEW ); } // // 画像キャッシュ保護 // void ImageViewBase::slot_toggle_protectimage() { if( ! m_enable_menuslot ) return; m_img->set_protect( ! m_img->is_protected( ) ); CORE::core_set_command( "redraw", get_url() ); // ステータス再描画 } // // 画像あぼーん // void ImageViewBase::slot_abone_img() { m_img->set_abone( true ); delete_view(); } // // 共有バッファセット // void ImageViewBase::set_image_to_buffer() { CORE::DATA_INFO info; info.type = TYPE_IMAGE; info.parent = IMAGE::get_admin()->get_win(); info.url = get_url(); info.name = get_url(); info.path = Gtk::TreePath( "0" ).to_string(); CORE::DATA_INFO_LIST list_info; list_info.push_back( info ); CORE::SBUF_set_list( list_info ); } // // ポップアップメニューを表示する前にメニューのアクティブ状態を切り替える // // SKELETON::View::show_popupmenu() を参照すること // void ImageViewBase::activate_act_before_popupmenu( const std::string& url ) { if( !m_img ) return; // toggle アクションを activeにするとスロット関数が呼ばれるので処理しないようにする m_enable_menuslot = false; Glib::RefPtr< Gtk::Action > act; bool current_protect = m_img->is_protected(); // ロック act = action_group()->get_action( "LockTab" ); if( act ){ auto tact = Glib::RefPtr< Gtk::ToggleAction >::cast_dynamic( act ); if( is_locked() ) tact->set_active( true ); else tact->set_active( false ); } // 閉じる act = action_group()->get_action( "Quit" ); if( act ){ if( is_locked() ) act->set_sensitive( false ); else act->set_sensitive( true ); } // モザイク act = action_group()->get_action( "CancelMosaic" ); if( act ){ if( m_img->is_cached() && m_img->get_mosaic() ) act->set_sensitive( true ); else act->set_sensitive( false ); } // サイズの大きい画像を表示 act = action_group()->get_action( "ShowLargeImg" ); if( act ){ if( m_img->get_type() == DBIMG::T_LARGE ) act->set_sensitive( true ); else act->set_sensitive( false ); } // サイズ系メニュー、お気に入り、保存 std::string sizemenus[] = { "Size_Menu", "OrgSizeImage", "ZoomFitImage", "ZoomInImage", "ZoomOutImage", "AppendFavorite", "Save" }; for( const std::string& menu : sizemenus ) { act = action_group()->get_action( menu ); if( act ){ if( m_img->is_cached() ) act->set_sensitive( true ); else act->set_sensitive( false ); } } // キャッシュをブラウザで開く act = action_group()->get_action( "OpenCacheBrowser" ); if( act ){ if( m_img->is_cached() ) act->set_sensitive( true ); else act->set_sensitive( false ); } // 参照元スレ act = action_group()->get_action( "OpenRef" ); if( act ){ if( ! m_img->get_refurl().empty() ) act->set_sensitive( true ); else act->set_sensitive( false ); } // 保護 act = action_group()->get_action( "ProtectImage" ); if( act ){ if( m_img->is_cached() ){ act->set_sensitive( true ); auto tact = Glib::RefPtr< Gtk::ToggleAction >::cast_dynamic( act ); if( tact ){ if( current_protect ) tact->set_active( true ); else tact->set_active( false ); } } else act->set_sensitive( false ); } // 削除 act = action_group()->get_action( "DeleteMenu" ); if( act ){ if( m_img->get_code() != HTTP_INIT && ! m_img->is_protected() ) act->set_sensitive( true ); else act->set_sensitive( false ); } // ロード停止 act = action_group()->get_action( "LoadStop" ); if( act ){ if( m_img->is_loading() ) act->set_sensitive( true ); else act->set_sensitive( false ); } // あぼーん act = action_group()->get_action( "AboneImage" ); if( act ){ if( ! m_img->is_protected() ) act->set_sensitive( true ); else act->set_sensitive( false ); } // ユーザコマンド // 選択不可かどうか判断して visible か sensitive にする const std::string url_article = DBTREE::url_dat( m_img->get_refurl() ); CORE::get_usrcmd_manager()->toggle_sensitive( action_group(), url_article, get_url(), "" ); m_enable_menuslot = true; } // // ユーザコマンド実行 // void ImageViewBase::slot_usrcmd( int num ) { int from, to; std::string num_str; const std::string url_article = DBTREE::url_dat( m_img->get_refurl(), from, to, num_str ); CORE::core_set_command( "exec_usr_cmd" , url_article, std::to_string( num ), get_url(), "", std::to_string( from ) ); } jdim-0.7.0/src/image/imageviewbase.h000066400000000000000000000102141417047150700172770ustar00rootroot00000000000000// ライセンス: GPL2 // // 画像ビューのベースクラス // #ifndef _IMAGEVIEWBASE_H #define _IMAGEVIEWBASE_H #include "skeleton/view.h" #include "skeleton/admin.h" #include namespace SKELETON { class Admin; } namespace DBIMG { class Img; } namespace IMAGE { class ImageAreaBase; // // ビューのベースクラス // class ImageViewBase : public SKELETON::View { DBIMG::Img* m_img; // Gtk::manage で作っているのでdeleteしなくても良い ImageAreaBase* m_imagearea{}; bool m_wait{}; bool m_loading{}; Gtk::EventBox m_event; bool m_dblclick{}; bool m_under_mouse{}; bool m_enable_menuslot; protected: // Viewが所属するAdminクラス SKELETON::Admin* get_admin() override; DBIMG::Img* get_img() { return m_img; } ImageAreaBase* get_imagearea() { return m_imagearea; } void set_imagearea( ImageAreaBase* imagearea ); void remove_imagearea(); bool is_wait() const { return m_wait; } void set_wait( const bool wait ){ m_wait = wait; } bool is_loading() const override { return m_loading; } void set_loading( const bool loading ){ m_loading = loading; } Gtk::EventBox& get_event(){ return m_event; } public: explicit ImageViewBase( const std::string& url, const std::string& arg1 = {}, const std::string& arg2 = {} ); ~ImageViewBase(); bool is_under_mouse() const { return m_under_mouse; } // // SKELETON::View の関数のオーバロード // void save_session() override {} // 親ウィンドウを取得 Gtk::Window* get_parent_win() override; // キーを押した bool slot_key_press( GdkEventKey* event ) override; // コマンド bool set_command( const std::string& command, const std::string& arg1 = {}, const std::string& arg2 = {} ) override; void reload() override; void stop() override; void redraw_view() override; void close_view() override; void delete_view() override; bool operate_view( const int control ) override; void show_preference() override; protected: void setup_common(); void set_image_to_buffer(); void activate_act_before_popupmenu( const std::string& url ) override; void delete_view_impl( const bool show_diag ); void slot_cancel_mosaic(); void slot_save(); virtual bool slot_button_press( GdkEventButton* event ); bool slot_button_release( GdkEventButton* event ); virtual bool slot_motion_notify( GdkEventMotion* event ); virtual bool slot_scroll_event( GdkEventScroll* event ); bool slot_enter_notify_event( GdkEventCrossing* event ); bool slot_leave_notify_event( GdkEventCrossing* event ); private: virtual void show_status(){} virtual void update_status(){} virtual void add_image(){} virtual void switch_icon(){} // クリックした時の処理 virtual void clicked(); void zoom_in_out( bool zoomin ); void slot_move_head(); void slot_move_tail(); void slot_reload_force(); void slot_show_large_img(); void slot_fit_win(); void slot_zoom_in(); void slot_zoom_out(); void slot_resize_image( int size ); void slot_lock(); void slot_open_browser(); void slot_open_cache_browser(); void slot_open_ref(); void slot_copy_url(); void slot_save_all(); void slot_favorite(); void slot_toggle_protectimage(); void slot_abone_img(); void slot_close_other_views(); void slot_close_left_views(); void slot_close_right_views(); void slot_close_error_views(); void slot_close_notimeout_error_views(); void slot_close_all_error_views(); void slot_close_noerror_views(); void slot_close_all_views(); void slot_usrcmd( int num ); }; } #endif jdim-0.7.0/src/image/imageviewicon.cpp000066400000000000000000000140661417047150700176610ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "imageadmin.h" #include "imageviewicon.h" #include "imageareaicon.h" #include "dbimg/img.h" #include "jdlib/miscutil.h" #include "control/controlid.h" #include "type.h" #include "dndmanager.h" #include "sharedbuffer.h" #include "cache.h" #include "command.h" #include "global.h" // 枠を描く // TODO: GTKのcssで設定する #define DRAW_FRAME( color ) m_event_frame->override_background_color( Gdk::RGBA( color ), Gtk::STATE_FLAG_NORMAL ) using namespace IMAGE; ImageViewIcon::ImageViewIcon( const std::string& url ) : ImageViewBase( url ) { #ifdef _DEBUG std::cout << "ImageViewIcon::ImageViewIcon : " << get_url() << std::endl; #endif // コントロールモード設定 get_control().add_mode( CONTROL::MODE_IMAGEICON ); //枠を描くためにm_eventの外にもう一つEventBoxを作る ( Gtk::HBox は modify_fg() 無効なので ) m_event_frame = Gtk::manage( new Gtk::EventBox() ); pack_start( *m_event_frame ); m_event_frame->add( get_event() ); get_event().set_border_width( 1 ); DRAW_FRAME( "white" ); setup_common(); // D&D可能にする std::vector< Gtk::TargetEntry > targets; targets.push_back( Gtk::TargetEntry( DNDTARGET_IMAGETAB, Gtk::TARGET_SAME_APP, 0 ) ); get_event().drag_dest_set( targets ); targets.push_back( Gtk::TargetEntry( DNDTARGET_FAVORITE, Gtk::TARGET_SAME_APP, 0 ) ); get_event().drag_source_set( targets, Gdk::BUTTON1_MASK ); get_event().signal_drag_begin().connect( sigc::mem_fun( *this, &ImageViewIcon::slot_drag_begin ) ); get_event().signal_drag_data_get().connect( sigc::mem_fun( *this, &ImageViewIcon::slot_drag_data_get ) ); get_event().signal_drag_data_received().connect( sigc::mem_fun( *this, &ImageViewIcon::slot_drag_data_received ) ); get_event().signal_drag_end().connect( sigc::mem_fun( *this, &ImageViewIcon::slot_drag_end ) ); } ImageViewIcon::~ImageViewIcon() { #ifdef _DEBUG std::cout << "ImageViewIcon::~ImageViewIcon : " << get_url() << std::endl; #endif // スレッドを止めるために明示的にクリアする remove_imagearea(); } // // クロック入力 // void ImageViewIcon::clock_in() { View::clock_in(); // 待ち状態 if( is_wait() && ! get_img()->is_wait() ){ set_wait( false ); set_loading( get_img()->is_loading() ); show_view(); } // ロード終了 else if( get_imagearea() && is_loading() && ! get_img()->is_loading() ){ set_loading( false ); show_view(); } } // // フォーカスイン // void ImageViewIcon::focus_view() { DRAW_FRAME( "red" ); get_event().grab_focus(); } // // フォーカスアウト // void ImageViewIcon::focus_out() { SKELETON::View::focus_out(); DRAW_FRAME( "white" ); } // // 表示 // void ImageViewIcon::show_view() { if( is_loading() ) return; if( is_wait() ) return; #ifdef _DEBUG std::cout << "ImageViewIcon::show_view url = " << get_url() << std::endl; #endif // 待ち状態 if( get_img()->is_wait() ) set_wait( true ); // 読み込み中 else if( get_img()->is_loading() ) set_loading( true ); // 画像が既に表示しているなら再描画 if( get_imagearea() ) get_imagearea()->show_image(); // 画像貼り付け // ロード中の時はclock_in()経由でもう一度 show_view()が呼び出される else{ set_imagearea( Gtk::manage( new ImageAreaIcon( get_url() ) ) ); get_imagearea()->show_image(); show_all_children(); } } // // アイコンが切り替わった // void ImageViewIcon::switch_icon() { DRAW_FRAME( "red" ); } // // ポップアップメニュー取得 // // SKELETON::View::show_popupmenu() を参照すること // Gtk::Menu* ImageViewIcon::get_popupmenu( const std::string& url ) { Gtk::Menu* menu = dynamic_cast< Gtk::Menu* >( ui_manager()->get_widget( "/popup_menu_icon" ) ); // タブ情報セット if( menu ){ // 一番上のitemのラベルを書き換える auto item = dynamic_cast< Gtk::MenuItem* >( *menu->get_children().begin() ); auto label = dynamic_cast< Gtk::Label* >( item->get_child() ); if( label ) label->set_text_with_mnemonic( ITEM_NAME_GO + std::string( " [ タブ数 " ) + std::to_string( IMAGE::get_admin()->get_tab_nums() ) + " ](_M)" ); } return menu; } // // D&D開始 // void ImageViewIcon::slot_drag_begin( const Glib::RefPtr& context ) { #ifdef _DEBUG std::cout << "ImageViewIcon::slot_drag_begin url = " << get_url() << std::endl; #endif CORE::DND_Begin(); } // // D&Dで受信側がデータ送信を要求してきた // void ImageViewIcon::slot_drag_data_get( const Glib::RefPtr& context, Gtk::SelectionData& selection_data, guint info, guint time ) { #ifdef _DEBUG std::cout << "ImageViewIcon::on_drag_data_get target = " << selection_data.get_target() << " url = " << get_url() << std::endl;; #endif set_image_to_buffer(); selection_data.set( selection_data.get_target(), get_url() ); } // // 他の画像アイコンからドロップされた // void ImageViewIcon::slot_drag_data_received( const Glib::RefPtr& context, int x, int y, const Gtk::SelectionData& selection_data, guint info, guint time ) { const std::string url_from = selection_data.get_data_as_string(); #ifdef _DEBUG std::cout << "ImageViewIcon::slot_drag_data_received target = " << selection_data.get_target() << " url = " << get_url() << " url_from = " << url_from << std::endl; #endif IMAGE::get_admin()->set_command( "reorder", get_url(), url_from, get_url() ); } // // D&D終了 // void ImageViewIcon::slot_drag_end( const Glib::RefPtr< Gdk::DragContext >& context ) { #ifdef _DEBUG std::cout << "ImageViewIcon::slot_drag_end url = " << get_url() << std::endl; #endif CORE::DND_End(); } jdim-0.7.0/src/image/imageviewicon.h000066400000000000000000000025451417047150700173250ustar00rootroot00000000000000// ライセンス: GPL2 // // 画像アイコンクラス // #ifndef _IMAGEVIEWICON_H #define _IMAGEVIEWICON_H #include "imageviewbase.h" namespace IMAGE { class ImageViewIcon : public ImageViewBase { Gtk::EventBox* m_event_frame; public: explicit ImageViewIcon( const std::string& url ); ~ImageViewIcon(); void clock_in() override; void focus_view() override; void focus_out() override; void show_view() override; protected: Gtk::Menu* get_popupmenu( const std::string& url ) override; // マウスホイールのイベント(タブ切り替え)は親ウィジェットで処理する bool slot_scroll_event( GdkEventScroll* event ) override { return false; } private: void switch_icon() override; void slot_drag_begin( const Glib::RefPtr& context ); void slot_drag_data_get( const Glib::RefPtr& context, Gtk::SelectionData& selection_data, guint info, guint time ); void slot_drag_data_received( const Glib::RefPtr& context, int x, int y, const Gtk::SelectionData& selection_data, guint info, guint time ); void slot_drag_end( const Glib::RefPtr< Gdk::DragContext >& context ); }; } #endif jdim-0.7.0/src/image/imageviewpopup.cpp000066400000000000000000000215001417047150700200630ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "imageviewpopup.h" #include "imageareapopup.h" #include "dbimg/img.h" #include "config/globalconf.h" #include "control/controlid.h" #include "colorid.h" #include "cssmanager.h" using namespace IMAGE; ImageViewPopup::ImageViewPopup( const std::string& url ) : ImageViewBase( url ) { #ifdef _DEBUG std::cout << "ImageViewPopup::ImageViewPopup url = " << get_url() << std::endl; #endif // コントロールモード設定 get_control().add_mode( CONTROL::MODE_IMAGEVIEW ); get_control().add_mode( CONTROL::MODE_IMAGEICON ); // TODO: JDimのスタイルシート機能からGTKのcssに切り替えるか? int border_width = 1; int margin = 0; std::string border_color = "black"; std::string bg_color = CONFIG::get_color( COLOR_BACK ); const int classid = CORE::get_css_manager()->get_classid( "imgpopup" ); if( classid >= 0 ){ CORE::CSS_PROPERTY css = CORE::get_css_manager()->get_property( classid ); border_width = css.border_left_width_px; margin = css.mrg_left_px; if( css.border_left_color >= 0 ) border_color = CORE::get_css_manager()->get_color( css.border_left_color ); if( css.bg_color > 0 ) bg_color = CORE::get_css_manager()->get_color( css.bg_color ); } //枠を描くためにm_eventの外にもう一つEventBoxを作る ( Gtk::HBox は modify_fg() 無効なので ) pack_start( m_event_frame ); m_event_frame.add( m_event_margin ); m_event_margin.add( get_event() ); // 枠の幅 m_event_margin.set_border_width( border_width ); // マージン get_event().set_border_width( margin ); // 枠色 m_event_frame.override_background_color( Gdk::RGBA( border_color ), Gtk::STATE_FLAG_NORMAL ); // 背景色 const Gdk::RGBA color_bg( bg_color ); m_event_margin.override_background_color( color_bg, Gtk::STATE_FLAG_NORMAL ); get_event().override_background_color( color_bg, Gtk::STATE_FLAG_NORMAL ); // 全ての領域を表示できないならカーソルの上に表示 set_popup_upside( true ); setup_common(); } // // クロック入力 // void ImageViewPopup::clock_in() { View::clock_in(); // ロード中 if( is_loading() ){ // 読み込みサイズの表示更新 if( get_img()->is_loading() ) update_label(); } } // // 画像表示停止 // void ImageViewPopup::stop() { #ifdef _DEBUG std::cout << "ImageViewPopup::stop url = " << get_url() << std::endl; #endif if( get_imagearea() ){ get_imagearea()->stop(); get_imagearea()->wait(); } } // // ラベルを貼る // void ImageViewPopup::set_label( const std::string& status ) { if( !m_label ){ m_label = std::make_unique( status ); get_event().add( *m_label ); std::string text_color = CONFIG::get_color( COLOR_CHAR ); const int classid = CORE::get_css_manager()->get_classid( "imgpopup" ); if( classid >= 0 ){ CORE::CSS_PROPERTY css = CORE::get_css_manager()->get_property( classid ); if( css.color >= 0 ) text_color = CORE::get_css_manager()->get_color( css.color ); } const Gdk::RGBA color_text( text_color ); m_label->override_color( color_text, Gtk::STATE_FLAG_NORMAL ); m_label->show(); } } // // ラベル削除 // void ImageViewPopup::remove_label() { if( m_label ){ get_event().remove(); m_label.reset(); } } // // 表示 // void ImageViewPopup::show_view() { #ifdef _DEBUG std::cout << "ImageViewPopup::show_view url = " << get_url() << std::endl; #endif // 待ち状態 if( is_wait() && ! get_img()->is_wait() ){ set_wait( false ); if( get_img()->is_loading() ){ set_loading( true ); set_label( "読み込み中" ); } else{ set_loading( false ); show_view_impl(); } } // ロード完了 else if( is_loading() && ! get_img()->is_loading() ){ set_loading( false ); // 画像表示 if( CONFIG::get_use_image_popup() ){ show_view_impl(); // リサイズ依頼 sig_resize_popup().emit(); } // ポップアップを閉じる else sig_hide_popup().emit(); } else show_view_impl(); } void ImageViewPopup::show_view_impl() { if( is_loading() ) return; if( is_wait() ) return; #ifdef _DEBUG std::cout << "ImageViewPopup::show_view_impl url = " << get_url() << std::endl; #endif // 画像を既に表示している if( get_imagearea() ){ // キャッシュされてるなら再描画 if( get_img()->is_cached() ){ #ifdef _DEBUG std::cout << "redraw\n"; #endif get_imagearea()->show_image(); } return; } // サーバから読み込み中 // 読み込みが終わったら show_view() が呼び出されて画像が表示される if( get_img()->is_loading() || get_img()->is_wait() ){ if( get_img()->is_wait() ){ #ifdef _DEBUG std::cout << "wait\n"; #endif set_wait( true ); set_loading( false ); set_label( "待機中" ); } else{ #ifdef _DEBUG std::cout << "loading\n"; #endif set_wait( false ); set_loading( true ); set_label( "読み込み中" ); } m_length_prev = 0; } // 画像張り付け else{ #ifdef _DEBUG std::cout << "not loading\n"; #endif // キャッシュがあったら画像貼り付け if( get_img()->is_cached() ){ ImageAreaBase* imagearea = Gtk::manage( new ImageAreaPopup( get_url() ) ); imagearea->show_image(); if( imagearea->get_errmsg().empty() ){ remove_label(); set_imagearea( imagearea ); } // エラー表示 else{ set_label( "" ); m_label->set_text( imagearea->get_errmsg() ); } } // キャッシュが無い else{ set_label( "" ); if( get_img()->get_str_code( ).empty() ) m_label->set_text( "キャッシュが存在しません" ); else m_label->set_text( get_img()->get_str_code( ) ); } } // マージンやボーダーの分を幅と高さに加える const int classid = CORE::get_css_manager()->get_classid( "imgpopup" ); if( classid >= 0 ){ CORE::CSS_PROPERTY css = CORE::get_css_manager()->get_property( classid ); const int border_width = css.border_left_width_px; const int margin = css.mrg_left_px; set_width_client( width_client() + margin*2 + border_width*2 ); set_height_client( height_client() + margin*2 + border_width*2 ); } show_all_children(); } // // ラベル表示更新 // void ImageViewPopup::update_label() { if( ! m_label ) return; if( m_length_prev != get_img()->current_length() ){ m_length_prev = get_img()->current_length(); std::string tmpstr = std::to_string( m_length_prev / 1024 ); tmpstr.append( " / " ); tmpstr.append( std::to_string( get_img()->total_length() / 1024 ) ); tmpstr.append( " KiB" ); m_label->set_text( tmpstr ); } } // クリックした時の処理 void ImageViewPopup::clicked() { #ifdef _DEBUG std::cout << "ImageViewPopup::clicked\n"; #endif // クリックしたらマウスボタンのリリース時に閉じる m_clicked = true; } // // ポップアップメニュー取得 // // SKELETON::View::show_popupmenu() を参照すること // Gtk::Menu* ImageViewPopup::get_popupmenu( const std::string& url ) { Gtk::Menu* popupmenu = dynamic_cast< Gtk::Menu* >( ui_manager()->get_widget( "/popup_menu_popup" ) ); return popupmenu; } // // 閉じる // void ImageViewPopup::close_view() { #ifdef _DEBUG std::cout << "ImageViewPopup::close_view\n"; #endif sig_hide_popup().emit(); } // // viewの操作 // bool ImageViewPopup::operate_view( const int control ) { #ifdef _DEBUG std::cout << "ImageViewPopup::operate_view control = " << control << std::endl; #endif switch( control ){ case CONTROL::CancelMosaic: case CONTROL::CancelMosaicButton: slot_cancel_mosaic(); break; case CONTROL::Cancel: case CONTROL::CloseImageButton: case CONTROL::CloseImageTabButton: case CONTROL::Quit: close_view(); break; case CONTROL::Save: slot_save(); break; case CONTROL::Delete: delete_view_impl( true ); break; default: if( m_clicked ) close_view(); break; } m_clicked = false; return true; } jdim-0.7.0/src/image/imageviewpopup.h000066400000000000000000000022431417047150700175330ustar00rootroot00000000000000// ライセンス: GPL2 // // 画像ポップアップクラス // #ifndef _IMAGEVIEWPOPUP_H #define _IMAGEVIEWPOPUP_H #include "imageviewbase.h" #include namespace IMAGE { class ImageViewPopup : public ImageViewBase { Gtk::EventBox m_event_frame; Gtk::EventBox m_event_margin; std::unique_ptr m_label; size_t m_length_prev{}; bool m_clicked{}; public: explicit ImageViewPopup( const std::string& url ); ~ImageViewPopup() noexcept = default; void clock_in() override; // 親ウィンドウは無し Gtk::Window* get_parent_win() override { return nullptr; } void stop() override; void show_view() override; void close_view() override; bool operate_view( const int control ) override; protected: Gtk::Menu* get_popupmenu( const std::string& url ) override; private: void show_view_impl(); // クリックした時の処理 void clicked() override; void update_label(); void set_label( const std::string& status ); void remove_label(); }; } #endif jdim-0.7.0/src/image/imagewin.cpp000066400000000000000000000053521417047150700166310ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "imageadmin.h" #include "imagewin.h" #include "jdlib/miscgtk.h" #include "config/globalconf.h" #include "session.h" #include "command.h" using namespace IMAGE; ImageWin::ImageWin() : SKELETON::JDWindow( CONFIG::get_fold_image() ) { #ifdef _DEBUG std::cout << "ImageWin::ImageWin x y w h = " << ImageWin::get_x_win() << " " << ImageWin::get_y_win() << " " << ImageWin::get_width_win() << " " << ImageWin::get_height_win() << std::endl; #endif init_win(); pack_remove_end( false, get_statbar(), Gtk::PACK_SHRINK ); if( ! CONFIG::get_fold_image() ) set_transient_for( *CORE::get_mainwindow() ); show_all_children(); } ImageWin::~ImageWin() { #ifdef _DEBUG std::cout << "ImageWin::~ImageWin window size : x = " << ImageWin::get_x_win() << " y = " << ImageWin::get_y_win() << " w = " << ImageWin::get_width_win() << " h = " << ImageWin::get_height_win() << " max = " << ImageWin::is_maximized_win() << std::endl; #endif ImageWin::set_shown_win( false ); CORE::core_set_command( "restore_focus" ); } int ImageWin::get_x_win() const { return SESSION::get_x_win_img(); } int ImageWin::get_y_win() const { return SESSION::get_y_win_img(); } void ImageWin::set_x_win( const int x ) { SESSION::set_x_win_img( x ); } void ImageWin::set_y_win( const int y ) { SESSION::set_y_win_img( y ); } int ImageWin::get_width_win() const { return SESSION::get_width_win_img(); } int ImageWin::get_height_win() const { return SESSION::get_height_win_img(); } void ImageWin::set_width_win( const int width ) { SESSION::set_width_win_img( width ); } void ImageWin::set_height_win( const int height ) { SESSION::set_height_win_img( height ); } bool ImageWin::is_focus_win() const { return SESSION::is_focus_win_img(); } void ImageWin::set_focus_win( const bool set ) { SESSION::set_focus_win_img( set ); } bool ImageWin::is_maximized_win() const { return SESSION::is_maximized_win_img(); } void ImageWin::set_maximized_win( const bool set ) { SESSION::set_maximized_win_img( set ); } bool ImageWin::is_iconified_win() const { return SESSION::is_iconified_win_img(); } void ImageWin::set_iconified_win( const bool set ) { SESSION::set_iconified_win_img( set ); } bool ImageWin::is_shown_win() const { return SESSION::is_shown_win_img(); } void ImageWin::set_shown_win( const bool set ) { SESSION::set_shown_win_img( set ); } void ImageWin::switch_admin() { CORE::core_set_command( "switch_image" ); } void ImageWin::pack_remove_tab( bool unpack, Widget& tab ) { m_tab = &tab; get_vbox().pack_remove_start( unpack, tab, Gtk::PACK_SHRINK ); } jdim-0.7.0/src/image/imagewin.h000066400000000000000000000025131417047150700162720ustar00rootroot00000000000000// ライセンス: GPL2 // // 画像ウィンドウ // #ifndef _IMAGEWIN_H #define _IMAGEWIN_H #include #include "skeleton/window.h" namespace IMAGE { class ImageWin : public SKELETON::JDWindow { Gtk::Widget* m_tab{}; public: ImageWin(); ~ImageWin(); void pack_remove_tab( bool unpack, Widget& tab ); protected: void switch_admin() override; int get_x_win() const override; int get_y_win() const override; void set_x_win( const int x ) override; void set_y_win( const int y ) override; int get_width_win() const override; int get_height_win() const override; void set_width_win( const int width ) override; void set_height_win( const int height ) override; bool is_focus_win() const override; void set_focus_win( const bool set ) override; bool is_maximized_win() const override; void set_maximized_win( const bool set ) override; bool is_iconified_win() const override; void set_iconified_win( const bool set ) override; bool is_full_win() const override { return false; } void set_full_win( const bool set ) override {} bool is_shown_win() const override; void set_shown_win( const bool set ) override; }; } #endif jdim-0.7.0/src/image/meson.build000066400000000000000000000005751417047150700164710ustar00rootroot00000000000000sources = [ 'imageadmin.cpp', 'imagearea.cpp', 'imageareabase.cpp', 'imageareaicon.cpp', 'imageareapopup.cpp', 'imageview.cpp', 'imageviewbase.cpp', 'imageviewicon.cpp', 'imageviewpopup.cpp', 'imagewin.cpp', 'preference.cpp', ] image_lib = static_library( 'image', sources, dependencies : gtkmm_dep, include_directories : include_directories('..'), ) jdim-0.7.0/src/image/preference.cpp000066400000000000000000000076231417047150700171520ustar00rootroot00000000000000// ライセンス: GPL2 #include "preference.h" #include "dbtree/interface.h" #include "dbimg/imginterface.h" #include "jdlib/miscutil.h" #include "command.h" #include using namespace IMAGE; Preferences::Preferences( Gtk::Window* parent, const std::string& url ) : SKELETON::PrefDiag( parent, url, true ) ,m_label_url( false, "URL:", get_url() ) ,m_label_cache( false, "ローカルキャッシュパス:", DBIMG::get_cache_path( url ) ) ,m_label_ref( false, "参照元スレ名:" ) ,m_label_url_ref( false, "参照元レス:" ) ,m_open_ref( "開く" ) ,m_label_wh( false, "大きさ : ", std::string() ) ,m_label_size( false, "サイズ( byte / Kbyte ) : ", std::string() ) ,m_label_type( false, "種類 : ", std::string() ) ,m_check_protect( "キャッシュを保護する" ) { // 一般 int num_from, num_to; std::string num_str; const std::string refurl = DBIMG::get_refurl( get_url() ); const std::string daturl = DBTREE::url_dat( refurl, num_from, num_to, num_str ); const std::string readcgi = DBTREE::url_readcgi( daturl, num_from, 0 ); m_label_ref.set_text( DBTREE::article_subject( daturl ) ); m_label_url_ref.set_text( readcgi ); m_open_ref.signal_clicked().connect( sigc::mem_fun(*this, &Preferences::slot_open_ref ) ); m_hbox_ref.pack_start( m_label_url_ref ); m_hbox_ref.pack_start( m_open_ref, Gtk::PACK_SHRINK ); m_label_wh.set_text( std::to_string( DBIMG::get_width( get_url() ) ) + " x " + std::to_string( DBIMG::get_height( get_url() ) ) ); int size = DBIMG::get_filesize( get_url() ); m_label_size.set_text( std::to_string( size ) + " / " + std::to_string( size/1024 ) ); std::string type; switch( DBIMG::get_type_real( get_url() ) ){ case DBIMG::T_JPG: type = "JPEG"; break; case DBIMG::T_PNG: type = "PNG"; break; case DBIMG::T_GIF: type = "GIF"; break; case DBIMG::T_BMP: type = "BMP"; break; case DBIMG::T_WEBP: type = "WebP"; break; case DBIMG::T_AVIF: type = "AVIF"; break; } if( DBIMG::is_fake( get_url() ) ) type += " ※拡張子が偽装されています※"; m_label_type.set_text( type ); m_check_protect.set_active( DBIMG::is_protected( get_url() ) ); m_vbox_info.set_border_width( 16 ); m_vbox_info.set_spacing( 8 ); m_vbox_info.pack_start( m_label_url, Gtk::PACK_SHRINK ); m_vbox_info.pack_start( m_label_cache, Gtk::PACK_SHRINK ); m_vbox_info.pack_start( m_label_ref, Gtk::PACK_SHRINK ); m_vbox_info.pack_start( m_hbox_ref, Gtk::PACK_SHRINK ); m_vbox_info.pack_start( m_label_wh, Gtk::PACK_SHRINK ); m_vbox_info.pack_start( m_label_size, Gtk::PACK_SHRINK ); m_vbox_info.pack_start( m_label_type, Gtk::PACK_SHRINK ); m_vbox_info.pack_end( m_check_protect, Gtk::PACK_SHRINK ); set_title( "画像のプロパティ" ); get_content_area()->pack_start( m_vbox_info ); resize( 600, 400 ); show_all_children(); } // // OK 押した // void Preferences::slot_ok_clicked() { if( m_check_protect.get_active() ) DBIMG::set_protect( get_url(), true ); else DBIMG::set_protect( get_url(), false ); // viewの再レイアウト CORE::core_set_command( "relayout_article", get_url() ); } // // 参照元を開く // // ImageViewBase::slot_open_ref() からのコピペ // void Preferences::slot_open_ref() { std::string refurl = DBIMG::get_refurl( get_url() ); int center, from, to; std::string num_str; const std::string url = DBTREE::url_dat( refurl, center, to, num_str ); if( url.empty() ) return; const int range = 10; from = MAX( 0, center - range ); to = center + range; std::stringstream ss; ss << from << "-" << to; CORE::core_set_command( "open_article_res", url, ss.str(), std::to_string( center ) ); response( Gtk::RESPONSE_CANCEL ); } jdim-0.7.0/src/image/preference.h000066400000000000000000000016061417047150700166120ustar00rootroot00000000000000// ライセンス: GPL2 #ifndef _IMAGE_PREFERENCES_H #define _IMAGE_PREFERENCES_H #include "skeleton/prefdiag.h" #include "skeleton/editview.h" #include "skeleton/label_entry.h" namespace IMAGE { class Preferences : public SKELETON::PrefDiag { // 情報 Gtk::VBox m_vbox_info; SKELETON::LabelEntry m_label_url; SKELETON::LabelEntry m_label_cache; SKELETON::LabelEntry m_label_ref; Gtk::HBox m_hbox_ref; SKELETON::LabelEntry m_label_url_ref; Gtk::Button m_open_ref; SKELETON::LabelEntry m_label_wh; SKELETON::LabelEntry m_label_size; SKELETON::LabelEntry m_label_type; Gtk::CheckButton m_check_protect; public: Preferences( Gtk::Window* parent, const std::string& url ); private: void slot_ok_clicked() override; void slot_open_ref(); }; } #endif jdim-0.7.0/src/iomonitor.cpp000066400000000000000000000134721417047150700157700ustar00rootroot00000000000000// License: GPL2 // // FIFOを使ったプロセス間通信を行うクラス // //#define _DEBUG #include "jddebug.h" #include "iomonitor.h" #include "command.h" #include "cache.h" #include "jdlib/miscmsg.h" #include #include #include #include using namespace CORE; #define COMMAND_MAX_LENGTH 1024 /*-------------------------------------------------------------------*/ // コンストラクタ /*-------------------------------------------------------------------*/ IOMonitor::IOMonitor() : m_fifo_fd( -1 ) , m_fifo_file( CACHE::path_lock() ) , m_fifo_stat( FIFO_OK ) { init(); } /*-------------------------------------------------------------------*/ // デストラクタ /*-------------------------------------------------------------------*/ IOMonitor::~IOMonitor() { if( m_iochannel ) m_iochannel->close(); // close( m_fifo_fd ); // メインプロセスの終了時にFIFOを消去する if( m_main_process ) delete_fifo(); } /*-------------------------------------------------------------------*/ // このクラスの初期化( FIFO作成、FIFOオープン、Glib::IOChannelの作成 ) /*-------------------------------------------------------------------*/ void IOMonitor::init() { // 既にFIFOと同名のファイルが存在するか確認 const int status = CACHE::file_exists( m_fifo_file ); // 同名のファイルがFIFOでなければ削除する if( status != CACHE::EXIST_ERROR && status != CACHE::EXIST_FIFO ) { delete_fifo(); } // FIFOを作成 int mkfifo_status = -1; do_makefifo: mkfifo_status = mkfifo( m_fifo_file.c_str(), O_RDWR | S_IRUSR | S_IWUSR ); // FIFO作成でエラーになった( 基本的に既にメインプロセスがある ) if( mkfifo_status != 0 ) { // FIFOが存在する if( errno == EEXIST ) { // FIFOを書き込み専用モードでオープン( ノンブロック ) if( ( m_fifo_fd = open( m_fifo_file.c_str(), O_WRONLY | O_NONBLOCK ) ) == -1 ) { // 反対側が既にオープンされていない( 異常終了などでメインプロセスがない ) if( ( errno & ENXIO ) != 0 ) { // 残っているFIFOを消す delete_fifo(); // 最初からやり直す goto do_makefifo; } // その他のエラー #ifdef _DEBUG std::cerr << "IOMonitor::init(): " << strerror( errno ) << std::endl; #endif m_fifo_stat = FIFO_OPEN_ERROR; } } // 何らかの問題で作成出来なかった else { MISC::ERRMSG( "IOMonitor::init(): fifo create failed." ); m_fifo_stat = FIFO_CREATE_ERROR; } } // メインプロセス else { // FIFOを読み込み専用モードでオープン( ノンブロック ) if( ( m_fifo_fd = open( m_fifo_file.c_str(), O_RDWR | O_NONBLOCK ) ) == -1 ) { // エラーなのでFIFOを消す delete_fifo(); #ifdef _DEBUG std::cerr << "IOMonitor::init(): " << strerror( errno ); #endif // _DEBUG m_fifo_stat = FIFO_OPEN_ERROR; return; } // メインプロセスである m_main_process = true; // Glib::IOChannel Glib::signal_io().connect( sigc::mem_fun( this, &IOMonitor::slot_ioin ), m_fifo_fd, Glib::IO_IN ); m_iochannel = Glib::IOChannel::create_from_fd( m_fifo_fd ); } } /*-------------------------------------------------------------------*/ // FIFOを削除する /*-------------------------------------------------------------------*/ void IOMonitor::delete_fifo() { int del_stat = 0; if( ( del_stat = unlink( m_fifo_file.c_str() ) ) < 0 ) { MISC::ERRMSG( "IOMonitor::init(): fifo unlink failed." ); } g_assert( del_stat >= 0 ); } /*-------------------------------------------------------------------*/ // FIFOに書き込む // // 引数 1: 書き込む文字列 // // 戻り値: 全て書き込まれたか否か /*-------------------------------------------------------------------*/ bool IOMonitor::send_command( const char* command ) { if( ! command ) return false; const size_t command_length = strlen( command ); // 異常に長かったら書き込まない if( command_length > COMMAND_MAX_LENGTH ) return false; g_assert( m_fifo_fd >= 0 ); int status = -1; status = write( m_fifo_fd, command, command_length ); return ( (size_t)status == command_length ); } /*-------------------------------------------------------------------*/ // FIFOに書き込まれたら呼び出される( Glib::signal_io() ) // // 引数 1: Glib::IOCondition // // 戻り値: true /*-------------------------------------------------------------------*/ bool IOMonitor::slot_ioin( Glib::IOCondition io_condition ) { if( ( io_condition & ( Glib::IO_IN | Glib::IO_PRI ) ) == 0 ) { MISC::ERRMSG( "IOMonitor::slot_ioin(): Invalid fifo response." ); return false; } Glib::ustring buffer; // 最大で COMMAND_MAX_LENGTH まで読み出す Glib::IOStatus io_status = m_iochannel->read( buffer, COMMAND_MAX_LENGTH ); if( io_status == Glib::IO_STATUS_ERROR ) { MISC::ERRMSG( "IOMonitor::slot_ioin(): read error." ); } #ifdef _DEBUG std::cout << "入力文字: " << buffer << std::endl; if( buffer == "Q" ) Gtk::Main::quit(); #endif // _DEBUG // FIFOに書き込まれたURLを開く // "現在のタブ/新しいタブ"など、開き方を選ぶ必要があるかも知れない //core_set_command( "open_article", buffer, "left", "auto" ); core_set_command( "open_url", buffer ); return true; } jdim-0.7.0/src/iomonitor.h000066400000000000000000000023421417047150700154270ustar00rootroot00000000000000// License: GPL2 // // FIFOを使ったプロセス間通信を行うクラス // #ifndef _IOMONITOR_H #define _IOMONITOR_H #include namespace CORE { enum { FIFO_OK = 0, FIFO_OPEN_ERROR, FIFO_CREATE_ERROR }; class IOMonitor { // FIFOのファイルディスクリプタ int m_fifo_fd; // I/Oの架け橋 Glib::RefPtr< Glib::IOChannel > m_iochannel; // FIFOファイル名 std::string m_fifo_file; // FIFOの状態 int m_fifo_stat; // メインプロセスか否か bool m_main_process{}; private: // 初期化 void init(); // FIFOを削除する void delete_fifo(); // FIFOに書き込まれたら呼び出される bool slot_ioin( Glib::IOCondition io_condition ); public: IOMonitor(); ~IOMonitor(); // FIFOの状態を取得 int get_fifo_stat() const noexcept { return m_fifo_stat; } // メインプロセスか否かを取得 bool is_main_process() const noexcept { return m_main_process; } // FIFOに書き込み bool send_command( const char* command ); }; } #endif jdim-0.7.0/src/jddebug.h000066400000000000000000000017271417047150700150220ustar00rootroot00000000000000// ライセンス: GPL2 #ifndef _JDDEBUG_H #define _JDDEBUG_H #ifndef _DEBUG #define NDEBUG #else #include #endif #ifdef _DEBUG_CARETMOVE #include #endif #ifdef _DEBUG_RESIZE_TAB #include #endif #include #ifdef _DEBUG_SHOW_VMSIZE #include #include // プロセスのvmsizeを表示 #define show_vmsize( msg ) { \ std::stringstream com; \ com << "grep VmSize /proc/" << getpid() << "/status | sed -e \"s/V[^0-9]*//\""; \ std::cerr << msg << ": "; \ system( com.str().c_str() ); \ } while(0) #else #define show_vmsize( msg ) while(0) #endif // from GTK_CHECK_VERSION in gtk/gtkversion.h #ifndef GTKMM_CHECK_VERSION #define GTKMM_CHECK_VERSION(major,minor,micro) \ (GTKMM_MAJOR_VERSION > (major) || \ (GTKMM_MAJOR_VERSION == (major) && GTKMM_MINOR_VERSION > (minor)) || \ (GTKMM_MAJOR_VERSION == (major) && GTKMM_MINOR_VERSION == (minor) && \ GTKMM_MICRO_VERSION >= (micro))) #endif #endif jdim-0.7.0/src/jdlib/000077500000000000000000000000001417047150700143225ustar00rootroot00000000000000jdim-0.7.0/src/jdlib/Makefile.am000066400000000000000000000014301417047150700163540ustar00rootroot00000000000000noinst_LIBRARIES = libjdlib.a libjdlib_a_SOURCES = \ miscutil.cpp \ miscmsg.cpp \ misctime.cpp \ misctrip.cpp \ miscx.cpp \ miscgtk.cpp \ jdthread.cpp \ misccharcode.cpp \ heap.cpp \ jdiconv.cpp \ loader.cpp \ imgloader.cpp \ ssl.cpp \ loaderdata.cpp \ cookiemanager.cpp \ confloader.cpp \ jdregex.cpp \ jdmigemo.cpp \ tfidf.cpp \ timeout.cpp noinst_HEADERS = \ miscutil.h \ miscmsg.h \ misctime.h \ misctrip.h \ miscx.h \ miscgtk.h \ jdthread.h \ misccharcode.h \ refptr_lock.h \ heap.h \ jdiconv.h \ loader.h \ imgloader.h \ ssl.h \ loaderdata.h \ cookiemanager.h \ confloader.h \ jdregex.h \ jdmigemo.h \ tfidf.h \ timeout.h \ hkana.h AM_CXXFLAGS = @GTKMM_CFLAGS@ @GNUTLS_CFLAGS@ @OPENSSL_CFLAGS@ @X11_CFLAGS@ AM_CPPFLAGS = -I$(top_srcdir)/src jdim-0.7.0/src/jdlib/confloader.cpp000066400000000000000000000117111417047150700171430ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "confloader.h" #include "miscutil.h" #include "cache.h" #include #include using namespace JDLIB; // // file : 設定ファイル // str_conf : 設定文字列 // // もしstr_confがemptyの時はfileから読み込む // ConfLoader::ConfLoader( const std::string& file, std::string str_conf ) : m_file( file ) { if( str_conf.empty() ) CACHE::load_rawdata( m_file, str_conf ); #ifdef _DEBUG std::cout << "ConfLoader::ConfLoader " << m_file << std::endl; std::cout << str_conf << std::endl; #endif // 行ごとに分割してConfDataに登録 if( ! str_conf.empty() ){ const std::list< std::string > lines = MISC::get_lines( str_conf ); if( lines.empty() ) return; for( const std::string& line : lines ) { const size_t i = line.find( '=' ); if( i != std::string::npos ){ m_data.push_back({ MISC::remove_space( line.substr( 0, i ) ), MISC::remove_space( line.substr( i + 1 ) ) }); #ifdef _DEBUG const ConfData& data = m_data.back(); std::cout << data.name << " = " << data.value << std::endl; #endif } } } } // 保存 void ConfLoader::save() { if( m_file.empty() ) return; std::string str_conf; for( const ConfData& conf : m_data ) { str_conf.append( conf.name + " = " + conf.value + "\n" ); } #ifdef _DEBUG std::cout << "ConfLoader::save " << m_file << std::endl; std::cout << str_conf << std::endl; #endif if( !str_conf.empty() ) CACHE::save_rawdata( m_file, str_conf ); } // 値を変更 (string型) // name が無い場合は綱目を追加 void ConfLoader::update( const std::string& name, const std::string& value ) { if( name.empty() ) return; auto it = std::find_if( m_data.begin(), m_data.end(), [&name]( const ConfData& c ) { return c.name == name; } ); if( it != m_data.end() ) { it->value = value; return; } // 追加 m_data.push_back({ name, value }); } // 値を変更 (bool型) void ConfLoader::update( const std::string& name, const bool value ) { std::string str_value = value ? "1" : "0"; update( name, str_value ); } // 値を変更 (int型) void ConfLoader::update( const std::string& name, const int value ) { update( name, std::to_string( value ) ); } // 値を変更 (double型) void ConfLoader::update( const std::string& name, const double value ) { update( name, std::to_string( value ) ); } // // string 型 // // dflt はデフォルト値, デフォルト引数 maxlength = 0 std::string ConfLoader::get_option_str( const std::string& name, const std::string& dflt, const size_t maxlength ) { if( name.empty() ) return std::string(); for( const ConfData& conf : m_data ) { if( conf.name == name ) { // maxlengthが設定されている場合は文字数制限をする if( maxlength > 0 && conf.value.length() > maxlength ) { m_broken = true; #ifdef _DEBUG std::cout << "ConfLoader::get_option_str: " << name << "=" << conf.value << std::endl; #endif break; } return conf.value; } } return dflt; } // // bool型 // bool ConfLoader::get_option_bool( const std::string& name, const bool dflt ) { std::string val_str = get_option_str( name, std::string() ); if( val_str.empty() ) return dflt; if( val_str == "1" ) return true; if( val_str == "0" ) return false; val_str = MISC::toupper_str( val_str ); if( val_str == "TRUE" || val_str == "T" ) return true; if( val_str == "FALSE" || val_str == "F" ) return false; m_broken = true; #ifdef _DEBUG std::cout << "ConfLoader::get_option_bool: " << name << "=" << val_str << std::endl; #endif return dflt; } // // int 型 // int ConfLoader::get_option_int( const std::string& name, const int dflt, const int min, const int max ) { std::string val_str = get_option_str( name, std::string() ); if( val_str.empty() ) return dflt; int val_int = atoi( val_str.c_str() ); if( val_int < min || val_int > max ) { val_int = dflt; m_broken = true; #ifdef _DEBUG std::cout << "ConfLoader::get_option_int: " << name << "=" << val_int << std::endl; #endif } return val_int; } // // double 型 // double ConfLoader::get_option_double( const std::string& name, const double dflt, const double min, const double max ) { std::string val_str = get_option_str( name, std::string() ); if( val_str.empty() ) return dflt; double val_double = atof( val_str.c_str() ); if( val_double < min || val_double > max ) { val_double = dflt; m_broken = true; #ifdef _DEBUG std::cout << "ConfLoader::get_option_double: " << name << "=" << val_double << std::endl; #endif } return val_double; } jdim-0.7.0/src/jdlib/confloader.h000066400000000000000000000030741417047150700166130ustar00rootroot00000000000000// ライセンス: GPL2 // // コンフィグファイルのローダ // #ifndef _CONFLOADER_H #define _CONFLOADER_H #include #include #include "config/defaultconf.h" namespace JDLIB { struct ConfData { std::string name; std::string value; }; class ConfLoader { std::string m_file; std::list< ConfData > m_data; bool m_broken{}; public: // file : 設定ファイル // str_conf : 設定文字列 // もしstr_confがemptyの時はfileから読み込む ConfLoader( const std::string& file, std::string str_conf ); bool empty() const noexcept { return m_data.empty(); } bool is_broken() const noexcept { return m_broken; } // 保存 void save(); // 値を変更 // name が無い場合は綱目を追加 void update( const std::string& name, const std::string& value ); void update( const std::string& name, const bool value ); void update( const std::string& name, const int value ); void update( const std::string& name, const double value ); // 値取得 std::string get_option_str( const std::string& name, const std::string& dflt, const size_t maxlength = 0 ); bool get_option_bool( const std::string& name, const bool dflt ); int get_option_int( const std::string& name, const int dflt, const int min, const int max ); double get_option_double( const std::string& name, const double dflt, const double min , const double max ); }; } #endif jdim-0.7.0/src/jdlib/cookiemanager.cpp000066400000000000000000000131001417047150700176250ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "cookiemanager.h" #include "jdregex.h" #include "miscutil.h" #include #include namespace JDLIB { namespace detail { // // シンプルなクッキー // struct SimpleCookie { std::string name; std::string value; std::string domain; }; // // シンプルなクッキー解析 // 最低限の要素以外は無視する // struct SimpleCookieParser { public: // 解析に成功したらresultに結果を代入してtrueを返す bool parse( const std::string& input, SimpleCookie& result ); }; // // シンプルなクッキー管理マネージャ (シングルトン用) // ミューテックスを利用しているのでメンバー関数内で再帰的に呼び出さないように注意 // class SimpleCookieManager : public JDLIB::CookieManager { public: SimpleCookieManager() = default; ~SimpleCookieManager() = default; // シングルトン オブジェクトが前提になっているためコピーとムーブ禁止 SimpleCookieManager( const SimpleCookieManager& ) = delete; SimpleCookieManager( SimpleCookieManager&& ) = delete; SimpleCookieManager& operator=( const SimpleCookieManager& ) = delete; SimpleCookieManager& operator=( SimpleCookieManager&& ) = delete; void feed( const std::string& hostname, const std::string& input ) override; std::string get_cookie_by_host( const std::string& hostname ) const override; void delete_cookie_by_host( const std::string& hostname ) override; private: SimpleCookieParser m_parser; std::map> m_storage; }; std::mutex s_simple_cookie_manager_mutex; } // namespace detail } // namespace JDLIB namespace { // シングルトン オブジェクト JDLIB::CookieManager* singleton_manager{}; std::mutex s_singleton_manager_mutex; } // namespace JDLIB::CookieManager* JDLIB::get_cookie_manager() { std::lock_guard lock( s_singleton_manager_mutex ); if( ! singleton_manager ) singleton_manager = new JDLIB::detail::SimpleCookieManager(); return singleton_manager; } void JDLIB::delete_cookie_manager() { std::lock_guard lock( s_singleton_manager_mutex ); if( singleton_manager ) delete singleton_manager; singleton_manager = nullptr; } using namespace JDLIB::detail; bool SimpleCookieParser::parse( const std::string& input, SimpleCookie& result ) { // HTTPヘッダー名を考慮しないためinputに渡す前にヘッダー名と空白は取り除くこと // 実装をシンプルにするため規格より簡略化された解析になっている constexpr bool icase = true; constexpr bool newline = false; constexpr bool usemigemo = false; constexpr bool wchar = false; JDLIB::Regex regex; std::size_t offset = 0; // クオート(")やエスケープ(\)が含まれているクッキーは解析失敗にする if( regex.exec( R"-(([^"\=;]+)=([^"\=;]*))-", input, offset, icase, newline, usemigemo, wchar ) ) { result.name = regex.str( 1 ); result.value = regex.str( 0 ); offset = regex.length( 0 ); } else { return false; } if( regex.exec( R"-(path=/([^;]+))-", input, offset, icase, newline, usemigemo, wchar ) ) { // ルート(/)以外のpathは解析失敗にする if( regex.length( 1 ) ) return false; } if( regex.exec( R"-(domain=([^;]+))-", input, offset, icase, newline, usemigemo, wchar ) ) { result.domain = regex.str( 1 ); } return true; } void SimpleCookieManager::feed( const std::string& hostname, const std::string& input ) { #ifdef _DEBUG std::cout << "SimpleCookieManager::feed hostname = " << hostname << std::endl; std::cout << "SimpleCookieManager::feed input = " << input << std::endl; #endif SimpleCookie result; if( ! m_parser.parse( input, result ) ) return; // ドメインの解析結果が空、またはホスト名と一致する場合はホスト名を使う if( result.domain.empty() || result.domain == "." + hostname ) { result.domain = hostname; } #ifdef _DEBUG std::cout << "SimpleCookieManager::feed name = " << result.name << std::endl; std::cout << "SimpleCookieManager::feed value = " << result.value << std::endl; std::cout << "SimpleCookieManager::feed domain = " << result.domain << std::endl; #endif std::lock_guard lock( s_simple_cookie_manager_mutex ); m_storage[result.domain][result.name] = result.value; } std::string SimpleCookieManager::get_cookie_by_host( const std::string& hostname ) const { std::string output; std::lock_guard lock( s_simple_cookie_manager_mutex ); std::size_t i = 0; const char* sep = ""; while( i != std::string::npos ) { auto it = m_storage.find( hostname.substr( i ) ); if( it != m_storage.end() ) { output.append( sep ); sep = ""; for( auto& pair : it->second ) { output.append( sep ); output.append( pair.second ); sep = "; "; } } i = hostname.find( '.', i + 1 ); } return output; } void SimpleCookieManager::delete_cookie_by_host( const std::string& hostname ) { std::lock_guard lock( s_simple_cookie_manager_mutex ); std::size_t i = 0; while( i != std::string::npos ) { auto it = m_storage.find( hostname.substr( i ) ); if( it != m_storage.end() ) { m_storage.erase( it ); } i = hostname.find( '.', i + 1 ); } } jdim-0.7.0/src/jdlib/cookiemanager.h000066400000000000000000000025331417047150700173020ustar00rootroot00000000000000// ライセンス: GPL2 // // HTTPクッキー管理マネージャ // #ifndef JDIM_COOKIEMANAGER_H #define JDIM_COOKIEMANAGER_H #include namespace JDLIB { // // HTTPクッキー管理マネージャの抽象クラス // class CookieManager { public: virtual ~CookieManager() = default; // HTTPヘッダー Set-Cookie: の値からクッキーを登録する // hostname : inputにドメインがない場合はhostnameに登録 // input : inputに渡す前にヘッダー名と空白は取り除いておく virtual void feed( const std::string& hostname, const std::string& input ) = 0; // ホスト名からクッキーを取得する // 返ってくる文字列は "name1=value1; name2=value2" の形式 virtual std::string get_cookie_by_host( const std::string& hostname ) const = 0; // ホスト名からクッキーを削除する // ホスト名に含まれる上位レベルのドメインもまとめて削除する virtual void delete_cookie_by_host( const std::string& hostname ) = 0; }; // クッキー管理マネージャのシングルトン オブジェクトを取得する CookieManager* get_cookie_manager(); // シングルトン オブジェクトのクッキー管理マネージャを削除する void delete_cookie_manager(); } // namespace JDLIB #endif // JDIM_COOKIEMANAGER_H jdim-0.7.0/src/jdlib/heap.cpp000066400000000000000000000034261417047150700157500ustar00rootroot00000000000000// ライセンス: GPL2 // NOTE: unsigned charでメモリを確保する根拠 (C++規格ドラフト) // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf#section.3.10 (3.10.10) // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf#section.3.11 (3.11.6) //#define _DEBUG #include "jddebug.h" #include "heap.h" using namespace JDLIB; HEAP::HEAP( std::size_t blocksize ) noexcept : m_blocksize{ blocksize } {} HEAP::~HEAP() { clear(); } void HEAP::clear() { m_heap_list.clear(); m_space_avail = 0; m_ptr_head = nullptr; } void* HEAP::heap_alloc( std::size_t size_bytes, std::size_t alignment ) { assert( m_blocksize > size_bytes && size_bytes > 0 ); assert( size_bytes >= alignment ); while(1) { if( !m_ptr_head || m_space_avail < size_bytes || m_space_avail >= m_blocksize ) { // 確保したメモリブロックはゼロ初期化する m_heap_list.emplace_back( new unsigned char[m_blocksize]{} ); m_ptr_head = &m_heap_list.back()[0]; m_space_avail = m_blocksize - 1; } // アライメント調整された指定サイズのバッファを探す if(std::align( alignment, size_bytes, m_ptr_head, m_space_avail )) { // 見つかったアドレスを保存してポインターと空きスペースを次の検索開始位置に合わせる void* found = m_ptr_head; m_ptr_head = std::next( reinterpret_cast(m_ptr_head), size_bytes ); m_space_avail -= size_bytes; return found; } else { // 見つからなかった場合は新しいメモリブロックを追加する m_ptr_head = nullptr; m_space_avail = 0; } } } jdim-0.7.0/src/jdlib/heap.h000066400000000000000000000020041417047150700154040ustar00rootroot00000000000000// ライセンス: GPL2 // ヒープクラス #ifndef HEAP_H #define HEAP_H #include #include namespace JDLIB { class HEAP { std::list< std::unique_ptr > m_heap_list; std::size_t m_blocksize; // ブロックサイズ std::size_t m_space_avail{}; // ブロックの未使用サイズ void* m_ptr_head{}; // 検索開始位置 public: explicit HEAP( std::size_t blocksize ) noexcept; ~HEAP(); HEAP( const HEAP& ) = delete; HEAP& operator=( const HEAP& ) = delete; HEAP( HEAP&& ) noexcept = default; HEAP& operator=( HEAP&& ) = default; void clear(); // 戻り値はunsigned char*のエイリアス void* heap_alloc( std::size_t size_bytes, std::size_t alignment ); template T* heap_alloc( std::size_t length = 1 ) { return reinterpret_cast( heap_alloc( sizeof(T) * length, alignof(T) ) ); } }; } #endif jdim-0.7.0/src/jdlib/hkana.h000066400000000000000000000035131417047150700155570ustar00rootroot00000000000000// ライセンス: GPL2 // 半角カナ -> 全角カナ変換テーブル #ifndef _HKANA_H_ #define _HKANA_H_ unsigned char hkana_table1[][2][8] = { { "。", "。" }, { "「", "「" }, { "」", "」" }, { "、", "、" }, { "・", "・" }, { "ヲ", "ヲ" }, { "ァ", "ァ" }, { "ィ", "ィ" }, { "ゥ", "ゥ" }, { "ェ", "ェ" }, { "ォ", "ォ" }, { "ャ", "ャ" }, { "ュ", "ュ" }, { "ョ", "ョ" }, { "ッ", "ッ" }, { "ー", "ー" }, { "ア", "ア" }, { "イ", "イ" }, { "ウ", "ウ" }, { "エ", "エ" }, { "オ", "オ" }, { "カ", "カ" }, { "キ", "キ" }, { "ク", "ク" }, { "ケ", "ケ" }, { "コ", "コ" }, { "サ", "サ" }, { "シ", "シ" }, { "ス", "ス" }, { "セ", "セ" }, { "ソ", "ソ" }, { "タ", "タ" }, { "チ", "チ" }, { "ツ", "ツ" }, { "テ", "テ" }, { "ト", "ト" }, { "ナ", "ナ" }, { "ニ", "ニ" }, { "ヌ", "ヌ" }, { "ネ", "ネ" }, { "ノ", "ノ" }, { "ハ", "ハ" }, { "ヒ", "ヒ" }, { "フ", "フ" }, { "ヘ", "ヘ" }, { "ホ", "ホ" }, { "マ", "マ" }, { "ミ", "ミ" }, { "ム", "ム" }, { "メ", "メ" }, { "モ", "モ" }, { "ヤ", "ヤ" }, { "ユ", "ユ" }, { "ヨ", "ヨ" }, { "ラ", "ラ" }, { "リ", "リ" }, { "ル", "ル" }, { "レ", "レ" }, { "ロ", "ロ" }, { "ワ", "ワ" }, { "ン", "ン" }, // 濁点 { "ウ", "ヴ" }, { "カ", "ガ" }, { "キ", "ギ" }, { "ク", "グ" }, { "ケ", "ゲ" }, { "コ", "ゴ" }, { "サ", "ザ" }, { "シ", "ジ" }, { "ス", "ズ" }, { "セ", "ゼ" }, { "ソ", "ゾ" }, { "タ", "ダ" }, { "チ", "ヂ" }, { "ツ", "ヅ" }, { "テ", "デ" }, { "ト", "ド" }, { "ハ", "バ" }, { "ヒ", "ビ" }, { "フ", "ブ" }, { "ヘ", "ベ" }, { "ホ", "ボ" }, // 半濁点 { "ハ", "パ" }, { "ヒ", "ピ" }, { "フ", "プ" }, { "ヘ", "ペ" }, { "ホ", "ポ" }, { "", "" } }; #endif jdim-0.7.0/src/jdlib/imgloader.cpp000066400000000000000000000175621417047150700170040ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG //#define _VERBOSE #include "jddebug.h" #include "gtkmmversion.h" #include "imgloader.h" #include "cache.h" #include "config/globalconf.h" using namespace JDLIB; /**********************************************************/ /* constructions ******************************************/ ImgLoader::ImgLoader( const std::string& file ) : m_file( file ) , m_loadedlevel( LOADLEVEL_INIT ) { #ifdef _DEBUG std::cout << "ImgLoader::ImgLoader file = " << m_file << std::endl; #endif } ImgLoader::~ImgLoader() { #ifdef _DEBUG std::cout << "ImgLoader::~ImgLoader file = " << m_file << std::endl; #endif } // static // いつまでもImgLoaderを保持していると、キャッシュアウトしても解放されないため、 // ImgLoaderは不要になった時点で、ただちに解放(.clear()または他の値代入)すること Glib::RefPtr< ImgLoader > ImgLoader::get_loader( const std::string& file ) { ImgProvider& provider = ImgProvider::get_provider(); std::lock_guard< std::mutex > lock( provider.m_provider_lock ); Glib::RefPtr< ImgLoader > loader = provider.get_loader( file ); if( ! loader ) { loader = Glib::RefPtr< ImgLoader >( new ImgLoader( file ) ); provider.set_loader( loader ); } return loader; } /**********************************************************/ /* external interface to PixbufLoader *********************/ // 画像サイズ取得 bool ImgLoader::get_size( int& width, int& height ) { std::lock_guard< std::mutex > lock( m_loader_lock ); bool ret = load_imgfile( LOADLEVEL_SIZEONLY ); width = m_width; height = m_height; return ret; } Glib::RefPtr< Gdk::Pixbuf > ImgLoader::get_pixbuf( const bool pixbufonly ) { Glib::RefPtr< Gdk::Pixbuf > ret; std::lock_guard< std::mutex > lock( m_loader_lock ); if( load_imgfile( pixbufonly ? LOADLEVEL_PIXBUFONLY : LOADLEVEL_NORMAL ) ) { ret = m_loader->get_pixbuf(); } return ret; } Glib::RefPtr< Gdk::PixbufAnimation > ImgLoader::get_animation() { Glib::RefPtr< Gdk::PixbufAnimation > ret; std::lock_guard< std::mutex > lock( m_loader_lock ); if( load_imgfile( LOADLEVEL_NORMAL ) ) { ret = m_loader->get_animation(); } return ret; } // 画像読み込み // 動画でpixbufonly = true の時はアニメーションさせない bool ImgLoader::load( const bool pixbufonly ) { std::lock_guard< std::mutex > lock( m_loader_lock ); return load_imgfile( pixbufonly ? LOADLEVEL_PIXBUFONLY : LOADLEVEL_NORMAL ); } /**********************************************************/ /* create PixbufLoader ************************************/ // private, NOT thread safe bool ImgLoader::load_imgfile( const int loadlevel ) { if( m_loader ) { // キャッシュに読み込んだデータが十分かどうか if( m_loadedlevel <= loadlevel ) { #ifdef _DEBUG std::cout << "ImgLoader use cache loadlevel / loadedlevel = " << loadlevel << " / " << m_loadedlevel << " file = " << m_file << std::endl; #endif return true; } // リロード m_width = 0; m_height = 0; m_stop = false; m_y = 0; } m_loadlevel = loadlevel; #ifdef _DEBUG std::cout << "ImgLoader::load_imgfile start loadlevel = " << loadlevel << " loadedlevel = " << m_loadedlevel << " file = " << m_file << std::endl; size_t total = 0; #endif bool ret = true; FILE* f = nullptr; f = fopen( to_locale_cstr( m_file ), "rb" ); if( ! f ){ m_errmsg = "cannot file open"; return false; } try { m_loader = Gdk::PixbufLoader::create(); m_loader->signal_size_prepared().connect( sigc::mem_fun( *this, &ImgLoader::slot_size_prepared ) ); m_loader->signal_area_updated().connect( sigc::mem_fun( *this, &ImgLoader::slot_area_updated ) ); constexpr std::size_t bufsize = 8192; guint8 data[ bufsize ]; while( ! m_stop ){ const std::size_t readsize = fread( data, 1, bufsize, f ); if( readsize ) m_loader->write( data, readsize ); #ifdef _DEBUG total += readsize; // std::cout << readsize << " / " << total << std::endl; #endif if( feof( f ) ){ // 画像データ全体を読み込み完了 m_loadedlevel = LOADLEVEL_NORMAL; break; } } m_loader->close(); } catch( Glib::Error& err ) { #ifdef _DEBUG std::string stop_s = m_stop ? "true" : "false"; std::cout << "ImgLoader stop = (" << stop_s << ") : " << m_file << std::endl; #endif if( ! m_stop ){ m_errmsg = err.what(); m_loader.reset(); ret = false; } } fclose( f ); #ifdef _DEBUG std::cout << "ImgLoader::load_imgfile fisished read = " << total << " w = " << m_width << " h = " << m_height << " loadedlevel = " << m_loadedlevel << std::endl; #endif return ret; } // PixbufLoaderが生成する画像の大きさを計算するのに必要な量のデータを受け取った時のシグナルハンドラ void ImgLoader::slot_size_prepared( int w, int h ) { #ifdef _DEBUG std::cout << "ImgLoader::slot_size_prepared w = " << w << " h = " << h << std::endl; #endif m_width = w; m_height = h; if( m_loadedlevel > LOADLEVEL_SIZEONLY ) m_loadedlevel = LOADLEVEL_SIZEONLY; if( m_loadlevel >= LOADLEVEL_SIZEONLY ) request_stop(); } // 画像の一部分が更新された時のシグナルハンドラ void ImgLoader::slot_area_updated(int x, int y, int w, int h ) { if( m_loadlevel >= LOADLEVEL_PIXBUFONLY ){ #if defined( _DEBUG ) && defined( _VERBOSE ) std::cout << "ImgLoader::slot_area_updated x = " << x << " y = " << y << " w = " << w << " h = " << h << std::endl; #endif // アニメーション画像を表示する際、幅や高さが元の値と異なる時に、全ての画像データを // 読み込まなくても pixbuf だけ取り出せれば良いので、pixbufを取り出せるようになった時点で // 画像データの読み込みを途中で止めて表示待ち時間を短縮する if( y < m_y ){ m_loadedlevel = LOADLEVEL_PIXBUFONLY; request_stop(); } m_y = y; } } /**********************************************************/ /* interface inner behavior *******************************/ // 読み込み中断のリクエスト void ImgLoader::request_stop() { // 中断をリクエストされても実際には読み込みが完了していることがある m_stop = true; } bool ImgLoader::equals( const std::string& file ) const { return m_file == file; } /**********************************************************/ /* ImgProvider has relational from ImgLoader ********/ ImgProvider::ImgProvider() { } // static ImgProvider& ImgProvider::get_provider() { // singleton provider static ImgProvider instance; return instance; } // NOT thread safe Glib::RefPtr< ImgLoader > ImgProvider::get_loader( const std::string& file ) { // ImgLoaderキャッシュをサーチ auto it = std::find_if( m_cache.begin(), m_cache.end(), [&file]( const Glib::RefPtr& loader ) { return loader->equals( file ); } ); if( it != m_cache.begin() && it != m_cache.end() ) { Glib::RefPtr ret = *it; // LRU: キャッシュをlistの先頭に移動 m_cache.erase( it ); m_cache.push_front( ret ); return ret; } return Glib::RefPtr< ImgLoader >(); //null } // NOT thread safe void ImgProvider::set_loader( Glib::RefPtr< ImgLoader > loader ) { int size = CONFIG::get_imgcache_size(); if( size ) { if( m_cache.size() >= (size_t)size ) { m_cache.pop_back(); } m_cache.push_front( loader ); } } jdim-0.7.0/src/jdlib/imgloader.h000066400000000000000000000042241417047150700164400ustar00rootroot00000000000000// ライセンス: GPL2 // // 画像ローダ // #ifndef _IMGLOADER_H #define _IMGLOADER_H #include #include namespace JDLIB { // 画像ロードレベル、必要なデータ量順に定義 enum { LOADLEVEL_NORMAL = 0, // 画像データ全体を読み込む LOADLEVEL_PIXBUFONLY, // pixbufを作るのに十分なデータを読み込む LOADLEVEL_SIZEONLY, // サイズを計算するのに十分なデータを読み込む LOADLEVEL_INIT }; class ImgLoader : public Glib::Object { Glib::RefPtr< Gdk::PixbufLoader > m_loader; std::mutex m_loader_lock; std::string m_file; std::string m_errmsg; int m_width{}; int m_height{}; bool m_stop{}; int m_y{}; int m_loadlevel{}; int m_loadedlevel; public: virtual ~ImgLoader(); static Glib::RefPtr< ImgLoader > get_loader( const std::string& file ); const std::string& get_errmsg() const { return m_errmsg; } bool get_size( int& width, int& height ); void request_stop(); bool load( const bool pixbufonly = false ); Glib::RefPtr get_pixbuf( const bool pixbufonly = false ); Glib::RefPtr get_animation(); bool equals( const std::string& file ) const; private: explicit ImgLoader( const std::string& file ); bool load_imgfile( const int loadlevel ); void slot_size_prepared( int w, int h ); void slot_area_updated(int x, int y, int w, int h ); }; class ImgProvider { std::list< Glib::RefPtr< ImgLoader > > m_cache; public: std::mutex m_provider_lock; // ImgProvider操作時の必須ロック public: virtual ~ImgProvider() noexcept = default; static ImgProvider& get_provider(); Glib::RefPtr< ImgLoader > get_loader( const std::string& file ); void set_loader( Glib::RefPtr< ImgLoader > loader ); private: ImgProvider(); }; } #endif jdim-0.7.0/src/jdlib/jdiconv.cpp000066400000000000000000000207261417047150700164710ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG //#define _DEBUG_ICONV #include "jddebug.h" #include "jdiconv.h" #include "miscmsg.h" #include "miscutil.h" #include #include #include #include using namespace JDLIB; Iconv::Iconv( const std::string& coding_to, const std::string& coding_from ) : m_coding_from( coding_from ) { #ifdef _DEBUG std::cout << "Iconv::Iconv coding = " << m_coding_from << " to " << coding_to << std::endl; #endif m_buf_in = ( char* )malloc( BUF_SIZE_ICONV_IN ); m_buf_out = ( char* )malloc( BUF_SIZE_ICONV_OUT ); m_cd = g_iconv_open( coding_to.c_str(), m_coding_from.c_str() ); // MS932で失敗したらCP932で試してみる if( m_cd == ( GIConv ) -1 ){ if( coding_to == "MS932" ) m_cd = g_iconv_open( "CP932", m_coding_from.c_str() ); else if( coding_from == "MS932" ) m_cd = g_iconv_open( coding_to.c_str(), "CP932" ); } // "EUCJP-*"で失敗したら"EUCJP"で試してみる if( m_cd == ( GIConv ) - 1 && ( errno & EINVAL ) != 0 ) { if( coding_to.rfind( "EUCJP-", 0 ) == 0 ) { m_cd = g_iconv_open( "EUCJP//TRANSLIT", coding_from.c_str() ); } else if( coding_from.rfind( "EUCJP-", 0 ) == 0 ) { const std::string coding_to_translit = coding_to + "//TRANSLIT"; m_cd = g_iconv_open( coding_to_translit.c_str(), "EUCJP" ); } } if( m_cd == ( GIConv ) -1 ){ MISC::ERRMSG( "can't open iconv coding = " + m_coding_from + " to " + coding_to ); } } Iconv::~Iconv() { #ifdef _DEBUG std::cout << "Iconv::~Iconv\n"; #endif if( m_buf_in ) free( m_buf_in ); if( m_buf_out ) free( m_buf_out ); if( m_cd != ( GIConv ) -1 ) g_iconv_close( m_cd ); } const char* Iconv::convert( char* str_in, int size_in, int& size_out ) { #ifdef _DEBUG std::cout << "Iconv::convert size_in = " << size_in <<" left = " << m_byte_left_in << std::endl; #endif assert( m_byte_left_in + size_in < BUF_SIZE_ICONV_IN ); if( m_cd == ( GIConv ) -1 ) return nullptr; size_t byte_left_out = BUF_SIZE_ICONV_OUT; char* buf_out = m_buf_out; // 前回の残りをコピー if( m_byte_left_in ){ memcpy( m_buf_in, m_buf_in_tmp, m_byte_left_in ); m_buf_in_tmp = m_buf_in; memcpy( m_buf_in + m_byte_left_in , str_in, size_in ); } else m_buf_in_tmp = str_in; m_byte_left_in += size_in; // iconv 実行 do{ #ifdef _DEBUG std::cout << "m_byte_left_in = " << m_byte_left_in << std::endl; std::cout << "byte_left_out = " << byte_left_out << std::endl; #endif const int ret = g_iconv( m_cd, &m_buf_in_tmp, &m_byte_left_in, &buf_out, &byte_left_out ); #ifdef _DEBUG std::cout << "--> ret = " << ret << std::endl; std::cout << "m_byte_left_in = " << m_byte_left_in << std::endl; std::cout << "byte_left_out = " << byte_left_out << std::endl; #endif // エラー if( ret == -1 ){ if( errno == EILSEQ ){ #ifdef _DEBUG_ICONV char str_tmp[256]; #endif const unsigned char code0 = *m_buf_in_tmp; const unsigned char code1 = *(m_buf_in_tmp+1); const unsigned char code2 = *(m_buf_in_tmp+2); if( m_coding_from == "MS932" ) { // 空白(0xa0) if( code0 == 0xa0 ){ *m_buf_in_tmp = 0x20; continue; } // <>の誤判別 ( 開発スレ 489 を参照 ) if( code1 == 0x3c && code2 == 0x3e ){ *m_buf_in_tmp = '?'; #ifdef _DEBUG_ICONV snprintf( str_tmp, 256, "iconv 0x%x%x> -> ?<>", code0, code1 ); MISC::MSG( str_tmp ); #endif continue; } // マッピング失敗 // □(0x81a0)を表示する if( ( code0 >= 0x81 && code0 <=0x9F ) || ( code0 >= 0xe0 && code0 <=0xef ) ){ *m_buf_in_tmp = static_cast< char >( 0x81 ); *(m_buf_in_tmp+1) = static_cast< char >( 0xa0 ); #ifdef _DEBUG_ICONV snprintf( str_tmp, 256, "iconv 0x%x%x -> □ (0x81a0) ", code0, code1 ); MISC::MSG( str_tmp ); #endif continue; } } // unicode 文字からの変換失敗 // 数値文字参照(&#????;)形式にする if( m_coding_from == "UTF-8" ){ // https://github.com/JDimproved/JDim/issues/214 (emoji subdivision flagの処理)について // // TAG LATIN SMALL LETTER と CANCEL TAG については、libcのiconv()関数にかけると、 // エラーを返さず、なおかつこれらの文字を無視してしまうので、 // U+1F3F4 WAVING BLACK FLAGが現れた時は、上の範囲のUTF-8文字が続いてないか、 // このblockの範囲で確認してから、脱出する。 bool is_converted_to_ucs2 = false; // 数値文字参照に変換されたかどうか bool is_handling_emoji_subdivision_flag = false; // emoji subdivision flags の処理の途中か for ( ; ; ) { int byte; const int ucs2 = MISC::utf8toucs2( m_buf_in_tmp, byte ); if( byte <= 1 ) break; // emoji subdivision flags の処理 if ( is_handling_emoji_subdivision_flag ) { // Tag Latin Small Letterの範囲か、Cancel Tagでなければ、処理中断 if ( byte != 4 ) break; if ( ucs2 < 917601 ) break; // U+E0061 TAG LATIN SMALL LETTER A if ( ucs2 > 917631 ) break; // U+E007F CANCEL TAG } const std::string ucs2_str = std::to_string( ucs2 ); #ifdef _DEBUG std::cout << "ucs2 = " << ucs2_str << " byte = " << byte << std::endl; #endif m_buf_in_tmp += byte; m_byte_left_in -= byte; *(buf_out++) = '&'; *(buf_out++) = '#'; memcpy( buf_out, ucs2_str.c_str(), ucs2_str.size() ); buf_out += ucs2_str.size(); *(buf_out++) = ';'; byte_left_out -= ucs2_str.size() + 3; is_converted_to_ucs2 = true; // 一度変換されたのでマーク if ( ! is_handling_emoji_subdivision_flag ) { if ( ( byte == 4 ) && ( ucs2 == 127988 ) ){ // U+1F3F4 WAVING BLACK FLAG // emoji subdivision flags の処理開始 is_handling_emoji_subdivision_flag = true; continue; // 連続処理 } } else { // まだ emoji subdivision flags の処理中 continue; } break; } // 数値文字参照に変換された場合は、continueする if ( is_converted_to_ucs2 ) continue; } // 時々空白(0x20)で EILSEQ が出るときがあるのでもう一度トライする if( code0 == 0x20 ) continue; //その他、1文字を空白にして続行 #ifdef _DEBUG_ICONV snprintf( str_tmp, 256, "iconv EILSEQ left = %zu code = %x %x %x", m_byte_left_in, code0, code1, code2 ); MISC::ERRMSG( str_tmp ); #endif *m_buf_in_tmp = 0x20; } else if( errno == EINVAL ){ #ifdef _DEBUG_ICONV MISC::ERRMSG( "iconv EINVAL\n" ); #endif break; } else if( errno == E2BIG ){ #ifdef _DEBUG_ICONV MISC::ERRMSG( "iconv E2BIG\n" ); #endif break; } } } while( m_byte_left_in > 0 ); size_out = BUF_SIZE_ICONV_OUT - byte_left_out; m_buf_out[ size_out ] = '\0'; return m_buf_out; } jdim-0.7.0/src/jdlib/jdiconv.h000066400000000000000000000015331417047150700161310ustar00rootroot00000000000000// ライセンス: GPL2 #ifndef _JDICONV_H #define _JDICONV_H #include #include // GIConv // iconv の内部で確保するバッファサイズ(バイト) // BUF_SIZE_ICONV_IN を超える入力は扱えないので注意 enum { BUF_SIZE_ICONV_IN = 1024 * 1024, BUF_SIZE_ICONV_OUT = BUF_SIZE_ICONV_IN /2 * 3 }; namespace JDLIB { class Iconv { GIConv m_cd; // iconv実装は環境で違いがあるためGlibのラッパーAPIを利用する size_t m_byte_left_in{}; char* m_buf_in{}; char* m_buf_in_tmp{}; char* m_buf_out{}; std::string m_coding_from; public: Iconv( const std::string& coding_to, const std::string& coding_from ); ~Iconv(); const char* convert( char* str_in, int size_in, int& size_out ); }; } #endif jdim-0.7.0/src/jdlib/jdmigemo.cpp000066400000000000000000000026711417047150700166270ustar00rootroot00000000000000// ライセンス: GPL2 // // Thanks to 「テスト運用中」スレの18氏 // // http://jd4linux.sourceforge.jp/cgi-bin/bbs/test/read.cgi/support/1149945056/18 // #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef HAVE_MIGEMO_H //#define _DEBUG #include "jddebug.h" #include "jdmigemo.h" #include static migemo* migemo_object{}; std::string jdmigemo::convert(const char* input) { if(! migemo_is_enable(migemo_object)) return {}; std::basic_string query; while( *input != '\n' && *input != '\0' ) { query.push_back( static_cast( *input ) ); ++input; } unsigned char* result = migemo_query(migemo_object, query.c_str()); std::string output = reinterpret_cast( result ); migemo_release(migemo_object, result); #ifdef _DEBUG std::cout << "migemo converted: " << output << std::endl; #endif return output; } bool jdmigemo::init(const std::string& filename) { if(migemo_is_enable(migemo_object)) return true; #ifdef _DEBUG std::cout << "migemo-dict: " << filename << std::endl; #endif migemo_object = migemo_open(filename.c_str()); if(migemo_is_enable(migemo_object)) { return true; } else { migemo_close(migemo_object); migemo_object = nullptr; return false; } } void jdmigemo::close() { migemo_close(migemo_object); migemo_object = nullptr; } #endif // HAVE_MIGEMO_H jdim-0.7.0/src/jdlib/jdmigemo.h000066400000000000000000000012751417047150700162730ustar00rootroot00000000000000// ライセンス: GPL2 // // ローマ字入力を日本語検索のための正規表現に変換する // Thanks to 「テスト運用中」スレの18氏 // // http://jd4linux.sourceforge.jp/cgi-bin/bbs/test/read.cgi/support/1149945056/18 // #ifndef JD_MIGEMO_H #define JD_MIGEMO_H #ifdef HAVE_MIGEMO_H #include namespace jdmigemo { // inputのヌル文字'\0'または改行'\n'までを変換して返す std::string convert(const char* input); // 辞書を読み込めたらtrueを返す // 既に読み込んでる場合は何もせずtrueを返す bool init(const std::string& filename); void close(); } // namespace jdmigemo #endif // HAVE_MIGEMO_H #endif // JD_MIGEMO_H jdim-0.7.0/src/jdlib/jdregex.cpp000066400000000000000000000235541417047150700164670ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "jdregex.h" #include "miscutil.h" #ifdef HAVE_MIGEMO_H #include "jdmigemo.h" #endif constexpr std::size_t MAX_TARGET_SIZE = 64 * 1024; // 全角半角変換のバッファサイズ #ifdef POSIX_STYLE_REGEX_API constexpr std::size_t REGEX_MAX_NMATCH = 32; #endif using namespace JDLIB; RegexPattern::RegexPattern( const std::string& reg, const bool icase, const bool newline, const bool usemigemo, const bool wchar ) { set( reg, icase, newline, usemigemo, wchar ); } RegexPattern::~RegexPattern() noexcept { clear(); } RegexPattern::RegexPattern( RegexPattern&& other ) noexcept : m_regex{ other.m_regex } , m_compiled{ other.m_compiled } , m_newline{ other.m_newline } , m_wchar{ other.m_wchar } , m_error{ other.m_error } { other.m_compiled = false; other.m_error = decltype( m_error ){}; } RegexPattern& RegexPattern::operator=( RegexPattern&& other ) noexcept { if( this != &other ) { clear(); m_regex = other.m_regex; m_compiled = other.m_compiled; m_newline = other.m_newline; m_wchar = other.m_wchar; m_error = other.m_error; other.m_compiled = false; other.m_error = decltype( m_error ){}; } return *this; } void RegexPattern::clear() { if ( m_compiled ) { #ifdef POSIX_STYLE_REGEX_API regfree( &m_regex ); #else g_regex_unref( m_regex ); m_regex = nullptr; #endif } m_compiled = false; #ifdef POSIX_STYLE_REGEX_API m_error = 0; #else g_clear_error( &m_error ); #endif } // icase : 大文字小文字区別しない // newline : . に改行をマッチさせない // usemigemo : migemo使用 (コンパイルオプションで指定する必要あり) // wchar : 全角半角の区別をしない bool RegexPattern::set( const std::string& reg, const bool icase, const bool newline, const bool usemigemo, const bool wchar ) { #ifdef _DEBUG if( wchar ){ std::cout << "RegexPattern::set " << reg << std::endl; } #endif clear(); if( reg.empty() ) return false; #ifdef POSIX_STYLE_REGEX_API int cflags = REG_EXTENDED; if( newline ) cflags |= REG_NEWLINE; if( icase ) cflags |= REG_ICASE; #else int cflags = G_REGEX_OPTIMIZE; if( newline ) cflags |= G_REGEX_MULTILINE; else cflags |= G_REGEX_DOTALL; // . を改行にマッチさせる if( icase ) cflags |= G_REGEX_CASELESS; #endif // POSIX_STYLE_REGEX_API m_newline = newline; m_wchar = wchar; const char* asc_reg = reg.c_str(); std::string target_asc; // 全角英数字 → 半角英数字、半角カナ → 全角カナ if( m_wchar && MISC::has_widechar( asc_reg ) ){ target_asc.reserve( MAX_TARGET_SIZE ); std::vector temp; MISC::asc( asc_reg, target_asc, temp ); asc_reg = target_asc.c_str(); #ifdef _DEBUG std::cout << target_asc << std::endl; #endif } #ifdef HAVE_MIGEMO_H std::string migemo_regex; if( usemigemo ) { migemo_regex = jdmigemo::convert( asc_reg ); if( ! migemo_regex.empty() ) { asc_reg = migemo_regex.c_str(); } } #endif #ifdef POSIX_STYLE_REGEX_API m_error = regcomp( &m_regex, asc_reg, cflags ); if( m_error != 0 ) { regfree( &m_regex ); return false; } #else m_regex = g_regex_new( asc_reg, GRegexCompileFlags( cflags ), GRegexMatchFlags( 0 ), &m_error ); if( ! m_regex ) { return false; } #endif m_compiled = true; return true; } std::string RegexPattern::errstr() const { std::string errmsg; #ifdef POSIX_STYLE_REGEX_API switch( m_error ) { case 0: errmsg = "エラーはありません。"; break; case REG_BADBR: errmsg = "無効な後方参照オペレータを使用しています。"; break; case REG_BADPAT: errmsg = "無効なグループやリストなどを使用しています。"; break; case REG_BADRPT: errmsg = "'*' が最初の文字としてくるような、無効な繰り返しオペレータを使用しています。"; break; case REG_EBRACE: errmsg = "インターバルオペレータ {} が閉じていません。"; break; case REG_EBRACK: errmsg = "リストオペレータ [] が閉じていません。"; break; case REG_ECOLLATE: errmsg = "照合順序の要素として有効ではありません。"; break; case REG_ECTYPE: errmsg = "未知のキャラクタークラス名です。"; break; case REG_EESCAPE: errmsg = "正規表現がバックスラッシュで終っています。"; break; case REG_EPAREN: errmsg = "グループオペレータ () が閉じていません。"; break; case REG_ERANGE: errmsg = "無効な範囲オペレータを使用しています。"; break; #ifndef HAVE_ONIGPOSIX_H case REG_ESIZE: errmsg = "複雑過ぎる正規表現です。POSIX.2 には定義されていません。"; break; #endif case REG_ESPACE: errmsg = "regex ルーチンがメモリーを使い果たしました。"; break; case REG_ESUBREG: errmsg = "サブエクスプレッション (...) への無効な後方参照です。"; break; default: errmsg = "未知のエラーが発生しました。"; break; } #else if( m_error ) { errmsg = m_error->message; } #endif return errmsg; } /////////////////////////////////////////////// bool Regex::match( const RegexPattern& creg, const std::string& target, const std::size_t offset, const bool notbol, const bool noteol ) { m_pos.clear(); m_results.clear(); m_target_asc.clear(); m_table_pos.clear(); if ( ! creg.m_compiled ) return false; if( target.empty() ) return false; if( target.size() <= offset ) return false; const char* asc_target = target.c_str() + offset; // 全角英数字 → 半角英数字、半角カナ → 全角カナ if( creg.m_wchar && MISC::has_widechar( asc_target ) ) { #ifdef _DEBUG std::cout << "Regex::match offset = " << offset << std::endl; std::cout << target << std::endl; #endif if( m_target_asc.capacity() < MAX_TARGET_SIZE ) { m_target_asc.reserve( MAX_TARGET_SIZE ); m_table_pos.reserve( MAX_TARGET_SIZE ); } MISC::asc( asc_target, m_target_asc, m_table_pos ); asc_target = m_target_asc.c_str(); #ifdef _DEBUG std::cout << m_target_asc << std::endl; #endif } #ifdef HAVE_ONIGPOSIX_H std::string target_copy; // 鬼車はnewlineを無視するようなので、文字列のコピーを取って // 改行をスペースにしてから実行する if( ! creg.m_newline ) { target_copy = asc_target; std::replace( target_copy.begin(), target_copy.end(), '\n', ' ' ); asc_target = target_copy.c_str(); } #endif #ifdef POSIX_STYLE_REGEX_API regmatch_t pmatch[ REGEX_MAX_NMATCH ]{}; int eflags = 0; if( notbol ) eflags |= REG_NOTBOL; if( noteol ) eflags |= REG_NOTEOL; #else GMatchInfo* pmatch{}; int eflags = 0; if( notbol ) eflags |= G_REGEX_MATCH_NOTBOL; if( noteol ) eflags |= G_REGEX_MATCH_NOTEOL; #endif #ifdef POSIX_STYLE_REGEX_API if( regexec( &creg.m_regex, asc_target, REGEX_MAX_NMATCH, pmatch, eflags ) != 0 ) { return false; } constexpr int match_count = REGEX_MAX_NMATCH; #else if( ! g_regex_match( creg.m_regex, asc_target, GRegexMatchFlags( eflags ), &pmatch ) ) { g_match_info_free( pmatch ); return false; } const int match_count = g_match_info_get_match_count( pmatch ) + 1; #endif for( int i = 0; i < match_count; ++i ){ #ifdef POSIX_STYLE_REGEX_API int so = pmatch[ i ].rm_so; int eo = pmatch[ i ].rm_eo; #else int so; int eo; if( ! g_match_info_fetch_pos( pmatch, i, &so, &eo ) ) so = eo = -1; #endif if( so < 0 || eo < 0 ) { m_pos.push_back( so ); m_results.push_back( std::string() ); } else { if( ! m_table_pos.empty() ) { #ifdef _DEBUG std::cout << "so=" << so << " eo=" << eo; #endif while( so > 0 && m_table_pos[so] < 0 ) so--; so = m_table_pos[so]; auto it = std::find_if( m_table_pos.cbegin() + eo, m_table_pos.cend(), []( int p ) { return p >= 0; } ); eo = ( it != m_table_pos.cend() ) ? *it : m_table_pos.size(); #ifdef _DEBUG std::cout << " -> so=" << so << " eo=" << eo << std::endl; #endif } so += offset; eo += offset; m_pos.push_back( so ); m_results.push_back( target.substr( so, eo - so ) ); } } #ifndef POSIX_STYLE_REGEX_API g_match_info_free( pmatch ); #endif return true; } // // マッチした文字列と \0〜\9 を置換する // std::string Regex::replace( const std::string& repstr ) const { if( repstr.empty() ) return repstr; const char* p0 = repstr.c_str(); const char* p1; std::string str_out; while( ( p1 = strchr( p0, '\\' ) ) != nullptr ) { int n = p1[1] - '0'; str_out.append( p0, p1 - p0 ); p0 = p1 + 2; if( n < 0 || n > 9 ) { str_out.push_back( p1[1] ); } else if( m_results.size() > static_cast( n ) && m_pos[n] != -1 ){ str_out.append( m_results[n] ); } } str_out.append( repstr, p0 - repstr.c_str(), std::string::npos ); return str_out; } int Regex::length( std::size_t num ) const noexcept { if( m_results.size() > num ) return m_results[num].size(); return 0; } int Regex::pos( std::size_t num ) const noexcept { if( m_results.size() > num ) return m_pos[num]; return -1; } std::string Regex::str( std::size_t num ) const { if( m_results.size() > num ) return m_results[num]; return {}; } jdim-0.7.0/src/jdlib/jdregex.h000066400000000000000000000064671417047150700161400ustar00rootroot00000000000000// ライセンス: GPL2 #ifndef _JDREGEX_H #define _JDREGEX_H #include #include #ifdef HAVE_CONFIG_H #include "config.h" #endif #if defined(HAVE_ONIGPOSIX_H) #include #else #include #endif #if defined(HAVE_ONIGPOSIX_H) #define POSIX_STYLE_REGEX_API 1 #endif namespace JDLIB { class Regex; class RegexPattern { friend class Regex; #ifdef POSIX_STYLE_REGEX_API // onigurumaではregexec()の引数regex_t*がconst修飾されていない // そのためRegex::match()のコンパイルエラーを回避するためmutable修飾が必要 mutable regex_t m_regex; #else GRegex* m_regex{}; #endif bool m_compiled{}; bool m_newline{}; bool m_wchar{}; #ifdef POSIX_STYLE_REGEX_API int m_error{}; #else GError *m_error{}; #endif public: RegexPattern() noexcept = default; RegexPattern( const std::string& reg, const bool icase, const bool newline, const bool usemigemo = false, const bool wchar = false ); ~RegexPattern() noexcept; // regex_tを複製する方法がないためcopy禁止にする RegexPattern( const RegexPattern& ) = delete; RegexPattern& operator=( const RegexPattern& ) = delete; // moveは許可 RegexPattern( RegexPattern&& ) noexcept; RegexPattern& operator=( RegexPattern&& ) noexcept; bool set( const std::string& reg, const bool icase, const bool newline, const bool usemigemo = false, const bool wchar = false ); void clear(); bool compiled() const noexcept { return m_compiled; } std::string errstr() const; }; class Regex { std::vector m_pos; std::vector m_results; // 全角半角を区別しないときに使う変換用バッファ // 処理可能なバッファ長は regoff_t (= int) のサイズに制限される std::string m_target_asc; std::vector m_table_pos; public: Regex() noexcept = default; ~Regex() noexcept = default; // notbol : 行頭マッチは必ず失敗する // noteol : 行末マッチは必ず失敗する bool match( const RegexPattern& creg, const std::string& target, const std::size_t offset, const bool notbol = false, const bool noteol = false ); // icase : 大文字小文字区別しない // newline : . に改行をマッチさせない // usemigemo : migemo使用 (コンパイルオプションで指定する必要あり) // wchar : 全角半角の区別をしない bool exec( const std::string& reg, const std::string& target, const std::size_t offset, const bool icase, const bool newline, const bool usemigemo = false, const bool wchar = false ) { RegexPattern pattern( reg, icase, newline, usemigemo, wchar ); return match( pattern, target, offset ); } // マッチした文字列と \0〜\9 を置換する std::string replace( const std::string& repstr ) const; int length( std::size_t num ) const noexcept; int pos( std::size_t num ) const noexcept; std::string str( std::size_t num ) const; }; } #endif jdim-0.7.0/src/jdlib/jdthread.cpp000066400000000000000000000021041417047150700166100ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "jdthread.h" #include "miscmsg.h" #include using namespace JDLIB; Thread::~Thread() { join(); } // スレッド作成 bool Thread::create( STARTFUNC func , void* arg, const bool detach, const int ) { if( m_thread.joinable() ){ MISC::ERRMSG( "Thread::create : thread is already running" ); return false; } try { m_thread = std::thread( func, arg ); } catch( std::system_error& err ) { MISC::ERRMSG( err.what() ); return false; } if( detach ) { #ifdef _DEBUG std::cout << "Thread::create detach" << std::endl; #endif m_thread.detach(); assert( ! m_thread.joinable() ); } #ifdef _DEBUG std::cout << "Thread::create thread = " << m_thread.get_id() << std::endl; #endif return true; } bool Thread::join() { #ifdef _DEBUG std::cout << "Thread::join thread = " << m_thread.get_id() << std::endl; #endif if( m_thread.joinable() ) { m_thread.join(); } return true; } jdim-0.7.0/src/jdlib/jdthread.h000066400000000000000000000015451417047150700162650ustar00rootroot00000000000000// ライセンス: GPL2 // スレッドクラス #ifndef _JDTHREAD_H #define _JDTHREAD_H #include typedef void* ( *STARTFUNC )( void * ); enum { DEFAULT_STACKSIZE = 64 }; namespace JDLIB { enum { DETACH = true, NODETACH = false }; class Thread { std::thread m_thread; public: Thread() noexcept = default; Thread( Thread&& ) noexcept = delete; Thread( const Thread& ) = delete; virtual ~Thread(); Thread& operator=( Thread&& ) noexcept = delete; Thread& operator=( const Thread& ) = delete; bool is_running() const noexcept { return m_thread.joinable(); } // スレッド作成 bool create( STARTFUNC func , void * arg, const bool detach, const int stack_kbyte = DEFAULT_STACKSIZE ); bool join(); }; } #endif jdim-0.7.0/src/jdlib/loader.cpp000066400000000000000000001303231417047150700162760ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG //#define _DEBUG_CHUNKED //#define _DEBUG_TIME #include "jddebug.h" #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "loader.h" #include "miscmsg.h" #include "miscutil.h" #include "ssl.h" #ifdef _DEBUG_TIME #include "misctime.h" #include #endif #include "config/globalconf.h" #include "skeleton/loadable.h" #include "httpcode.h" #include #include #include #include #include #include #include #include #include #include #include // _soc : int #define SOC_ISVALID(_soc) ( (_soc) >= 0 ) constexpr int MAX_LOADER = 10; // 最大スレッド数 constexpr int MAX_LOADER_SAMEHOST = 2; // 同一ホストに対して実行できる最大スレッド数 constexpr size_t LNG_BUF_MIN = 1 * 1024; // 読み込みバッファの最小値 (byte) constexpr long TIMEOUT_MIN = 1; // タイムアウトの最小値 (秒) namespace { std::string get_error_message( int err_code ) { std::string result = " [Errno " + std::to_string( err_code ) + "] "; result.append( std::strerror( err_code ) ); return result; } } // namespace // // トークンとスレッド起動待ちキュー // // 起動しているローダが MAX_LOADER 個を越えたらローダをスレッド待ちキューに入れる // namespace JDLIB { bool get_token( JDLIB::Loader* loader ); void return_token( JDLIB::Loader* loader ); void push_loader_queue( JDLIB::Loader* loader ); bool remove_loader_queue( JDLIB::Loader* loader ); void pop_loader_queue(); } static std::mutex mutex_token; static std::mutex mutex_queue; std::list< JDLIB::Loader* > queue_loader; // スレッド起動待ちの Loader のキュー int token_loader = 0; std::vector< JDLIB::Loader* > vec_loader( MAX_LOADER ); bool disable_pop = false; // トークン取得 bool JDLIB::get_token( JDLIB::Loader* loader ) { std::lock_guard< std::mutex > lock( mutex_token ); #ifdef _DEBUG std::cout << "JDLIB::get_token : url = " << loader->data().url << " token = " << token_loader << std::endl; #endif if( token_loader >= MAX_LOADER ) return false; const std::string& host = loader->data().host; const auto compare_host = [&host]( const auto* ldr ) { return ldr && ldr->data().host == host; }; const int count = std::count_if( vec_loader.cbegin(), vec_loader.cend(), compare_host ); #ifdef _DEBUG std::cout << "count = " << count << std::endl; #endif const int max_loader = MIN( MAX_LOADER_SAMEHOST, MAX( 1, CONFIG::get_connection_num() ) ); if( count >= max_loader ) return false; #ifdef _DEBUG std::cout << "got token\n"; #endif ++token_loader; auto it = std::find( vec_loader.begin(), vec_loader.end(), nullptr ); if( it != vec_loader.end() ) *it = loader; return true; } // トークン返す void JDLIB::return_token( JDLIB::Loader* loader ) { std::lock_guard< std::mutex > lock( mutex_token ); --token_loader; assert( token_loader >= 0 ); constexpr JDLIB::Loader* null_loader = nullptr; std::replace( vec_loader.begin(), vec_loader.end(), loader, null_loader ); #ifdef _DEBUG std::cout << "JDLIB::return_token : url = " << loader->data().url << " token = " << token_loader << std::endl; #endif } // スレッド起動待ちキューに Loader を登録 void JDLIB::push_loader_queue( JDLIB::Loader* loader ) { std::lock_guard< std::mutex > lock( mutex_queue ); if( ! loader ) return; if( loader->get_low_priority() ) queue_loader.push_back( loader ); else{ auto pos = std::find_if( queue_loader.cbegin(), queue_loader.cend(), []( const auto* ql ) { return ql->get_low_priority(); } ); queue_loader.insert( pos, loader ); } #ifdef _DEBUG std::cout << "JDLIB::push_loader_queue url = " << loader->data().url << " size = " << queue_loader.size() << std::endl; #endif } // キューから Loader を取り除いたらtrueを返す bool JDLIB::remove_loader_queue( JDLIB::Loader* loader ) { std::lock_guard< std::mutex > lock( mutex_queue ); if( ! queue_loader.size() ) return false; if( std::find( queue_loader.begin(), queue_loader.end(), loader ) == queue_loader.end() ) return false; queue_loader.remove( loader ); #ifdef _DEBUG std::cout << "JDLIB::remove_loader_queue url = " << loader->data().url << " size = " << queue_loader.size() << std::endl; #endif return true; } // キューに登録されたスレッドを起動する void JDLIB::pop_loader_queue() { std::lock_guard< std::mutex > lock( mutex_queue ); if( disable_pop ) return; if( ! queue_loader.size() ) return; #ifdef _DEBUG std::cout << "JDLIB::pop_loader_queue size = " << queue_loader.size() << std::endl; #endif auto it = std::find_if( queue_loader.begin(), queue_loader.end(), &JDLIB::get_token ); if( it == queue_loader.end() ) return; JDLIB::Loader* loader = *it; queue_loader.remove( loader ); #ifdef _DEBUG std::cout << "pop " << loader->data().url << std::endl; #endif loader->create_thread(); } // // ローダの起動待ちキューにあるスレッドを実行しない // // アプリ終了時にこの関数を呼び出さないとキューに登録されたスレッドが起動してしまうので注意 // void JDLIB::disable_pop_loader_queue() { std::lock_guard< std::mutex > lock( mutex_queue ); #ifdef _DEBUG std::cout << "JDLIB::disable_pop_loader_queue\n"; #endif disable_pop = true; } // // mainの最後でローダが動いていないかチェックする関数 // void JDLIB::check_loader_alive() { #ifdef _DEBUG std::cout << "JDLIB::check_loader_alive loader = " << token_loader << " queue = " << queue_loader.size() << std::endl; #endif if( token_loader ){ MISC::ERRMSG( "loaders are still moving." ); assert( false ); } if( queue_loader.size() ){ MISC::ERRMSG( "queue of loaders are not empty." ); assert( false ); } } /////////////////////////////////////////////////// using namespace JDLIB; // // low_priority = true の時はスレッド起動待ち状態になった時に、起動順のプライオリティを下げる // Loader::Loader( const bool low_priority ) : m_addrinfo( nullptr ), m_stop( false ), m_loading( false ), m_low_priority( low_priority ), m_buf( nullptr ), m_buf_zlib_in ( nullptr ), m_buf_zlib_out ( nullptr ), m_use_zlib ( 0 ) { #ifdef _DEBUG std::cout << "Loader::Loader : loader was created.\n"; #endif clear(); } Loader::~Loader() { #ifdef _DEBUG std::cout << "Loader::~Loader : url = " << m_data.url << std::endl; #endif clear(); // assert( ! m_loading ); } void Loader::clear() { #ifdef _DEBUG std::cout << "Loader::clear\n"; #endif stop(); wait(); m_loadable = nullptr; m_use_chunk = false; if( m_buf ) free( m_buf ); m_buf = nullptr; if( m_buf_zlib_in ) free( m_buf_zlib_in ); m_buf_zlib_in = nullptr; if( m_buf_zlib_out ) free( m_buf_zlib_out ); m_buf_zlib_out = nullptr; if( m_use_zlib ) inflateEnd( &m_zstream ); m_use_zlib = false; } void Loader::wait() { m_thread.join(); } void Loader::stop() { if( ! m_loading ) return; #ifdef _DEBUG std::cout << "Loader::stop : url = " << m_data.url << std::endl; #endif m_stop = true; // スレッド起動待ち状態の時は SKELETON::Loadable にメッセージを送る if( JDLIB::remove_loader_queue( this ) ){ m_data.code = HTTP_TIMEOUT; m_data.modified = std::string(); m_data.str_code = "stop loading"; finish_loading(); } } // // ダウンロード開始 // // data_in でセットする必要がある項目 ( url は必須 ) // // url // head ( true なら HEAD 送信 ) // port ( == 0 ならプロトコルを見て自動認識 ) // str_post( != empty なら POST する。UTF-8であること ) // modified // byte_readfrom ( != 0 ならその位置からレジューム) // agent // referer // cookie_for_request // host_proxy ( != empty ならproxy使用 ) // port_proxy ( == 0 なら 8080 ) // basicauth_proxy // size_buf ( バッファサイズ, k 単位で指定。 == 0 ならデフォルトサイズ(LNG_BUF_MIN)使用) // timeout ( タイムアウト秒。==0 ならデフォルト( TIMEOUT )使用 ) // basicauth // bool Loader::run( SKELETON::Loadable* cb, const LOADERDATA& data_in ) { assert( ! data_in.url.empty() ); #ifdef _DEBUG std::cout << "Loader::run : url = " << data_in.url << std::endl; #endif if( m_loading ){ MISC::ERRMSG( "now loading : " + data_in.url ); return false; } clear(); m_loadable = cb; m_data.init(); m_stop = false; // バッファサイズ設定 m_data.size_buf = data_in.size_buf; m_lng_buf = MAX( LNG_BUF_MIN, m_data.size_buf * 1024 ); m_lng_buf_zlib_in = m_lng_buf * 2; m_lng_buf_zlib_out = m_lng_buf * 10; // 小さいとパフォーマンスが落ちるので少し多めに10倍位 // protocol と host と path 取得 m_data.url = data_in.url; size_t i = m_data.url.find( "://", 0 ); // "http://" とつけるのは呼び出し側の責任で if( i == std::string::npos ){ m_data.code = HTTP_ERR; m_data.str_code = "could not get protocol : " + m_data.url; MISC::ERRMSG( m_data.str_code ); return false; } i += 3; m_data.protocol = data_in.url.substr( 0, i ); size_t i2 = m_data.url.find( '/', i ); if( i2 == std::string::npos ){ m_data.code = HTTP_ERR; m_data.str_code = "could not get hostname and path : " + m_data.url; MISC::ERRMSG( m_data.str_code ); return false; } m_data.host = m_data.url.substr( i, i2 - i ); m_data.path = m_data.url.substr( i2 ); // ポートセット // ホスト名の後に指定されている if( ( i = m_data.host.find( ':' ) ) != std::string::npos ){ m_data.port = atoi( m_data.host.substr( i+1 ).c_str() ); m_data.host = m_data.host.substr( 0, i ); } // 明示的に指定 else if( data_in.port != 0 ) m_data.port = data_in.port; // プロトコルを見て自動決定 else{ // http if( m_data.protocol.find( "http://" ) != std::string::npos ) m_data.port = data_in.use_ssl ? 443 : 80; // https else if( m_data.protocol.find( "https://" ) != std::string::npos ){ m_data.port = 443; } // その他 else{ m_data.code = HTTP_ERR; m_data.str_code = "unknown protocol : " + m_data.url; MISC::ERRMSG( m_data.str_code ); return false; } } // ssl使用指定 // HACK: httpsから始まるURLで プロキシを使わない or 2ch系サイトでない 場合はhttpsで送信する constexpr std::array domains{ ".5ch.net", ".2ch.net", ".bbspink.com" }; const std::string& hostname = m_data.host; const auto has_domain = [&hostname]( const char* d ) { return hostname.find( d ) != std::string::npos; }; if( data_in.use_ssl || ( m_data.protocol.find( "https://" ) != std::string::npos && ( data_in.host_proxy.empty() || std::none_of( domains.cbegin(), domains.cend(), has_domain ) ) ) ){ m_data.use_ssl = true; m_data.async = false; } // プロキシ m_data.host_proxy = data_in.host_proxy; // 先頭に *tp:// が付いていたら取り除く if( ! m_data.host_proxy.empty() && m_data.host_proxy.find( "tp://" ) != std::string::npos ){ const bool protocol = false; m_data.host_proxy = MISC::get_hostname( m_data.host_proxy , protocol ); } if( ! m_data.host_proxy.empty() ){ m_data.port_proxy = data_in.port_proxy; if( m_data.port_proxy == 0 ) m_data.port_proxy = 8080; m_data.basicauth_proxy = data_in.basicauth_proxy; } // その他 m_data.head = data_in.head; m_data.str_post = data_in.str_post; m_data.modified = data_in.modified; m_data.byte_readfrom = data_in.byte_readfrom; m_data.contenttype = data_in.contenttype; m_data.agent = data_in.agent; m_data.origin = data_in.origin; m_data.referer = data_in.referer; m_data.accept = data_in.accept; m_data.cookie_for_request = data_in.cookie_for_request; m_data.timeout = MAX( TIMEOUT_MIN, data_in.timeout ); m_data.ex_field = data_in.ex_field; m_data.basicauth = data_in.basicauth; m_data.use_ipv6 = data_in.use_ipv6; #ifdef _DEBUG std::cout << "host: " << m_data.host << std::endl; std::cout << "protocol: " << m_data.protocol << std::endl; std::cout << "path: " << m_data.path << std::endl; std::cout << "port: " << m_data.port << std::endl; std::cout << "modified: " << m_data.modified << std::endl; std::cout << "byte_readfrom: " << m_data.byte_readfrom << std::endl; std::cout << "contenttype: " << m_data.contenttype << std::endl; std::cout << "agent: " << m_data.agent << std::endl; std::cout << "referer: " << m_data.referer << std::endl; std::cout << "cookie: " << m_data.cookie_for_request << std::endl; std::cout << "proxy: " << m_data.host_proxy << std::endl; std::cout << "port of proxy: " << m_data.port_proxy << std::endl; std::cout << "proxy basicauth : " << m_data.basicauth_proxy << std::endl; std::cout << "buffer size: " << m_lng_buf / 1024 << " Kb" << std::endl; std::cout << "timeout : " << m_data.timeout << " sec" << std::endl; std::cout << "ex_field : " << m_data.ex_field << std::endl; std::cout << "basicauth : " << m_data.basicauth << std::endl; std::cout << "\n"; #endif m_loading = true; // トークンを取得出来なかったら、他のスレッドが終了した時に // 改めて create_thread() を呼び出す if( get_token( this ) ) create_thread(); else JDLIB::push_loader_queue( this ); return true; } // // スレッド起動 // void Loader::create_thread() { #ifdef _DEBUG std::cout << "Loader::create_thread : url = " << m_data.url << std::endl; #endif if( ! m_thread.create( ( STARTFUNC ) launcher, ( void * ) this, JDLIB::NODETACH ) ){ m_data.code = HTTP_ERR; m_data.str_code = "Loader::run : could not start thread"; MISC::ERRMSG( m_data.str_code ); finish_loading(); return; } } // // スレッドのランチャ (static) // void* Loader::launcher( void* dat ) { Loader* tt = ( Loader * ) dat; tt->run_main(); return nullptr; } bool Loader::send_connect( const int soc, std::string& errmsg ) { std::string authority; std::string msg_send; authority = m_data.host + ":" + std::to_string( m_data.port ); msg_send = "CONNECT " + authority + " HTTP/1.1\r\nHost: " + authority + "\r\n\r\n"; size_t send_size = strlen( msg_send.data() ); while( send_size > 0 && !m_stop ){ if( ! wait_recv_send( soc, false ) ){ m_data.code = HTTP_TIMEOUT; errmsg = "send timeout"; return false; } #ifdef MSG_NOSIGNAL ssize_t tmpsize = send( soc, msg_send.data(), send_size, MSG_NOSIGNAL ); #else // SolarisにはMSG_NOSIGNALが無いのでSIGPIPEをIGNOREする (FreeBSD4.11Rにもなかった) signal( SIGPIPE , SIG_IGN ); /* シグナルを無視する */ ssize_t tmpsize = send( soc, msg_send.data(), send_size,0); signal(SIGPIPE,SIG_DFL); /* 念のため戻す */ #endif // MSG_NOSIGNAL if( tmpsize == 0 || ( tmpsize < 0 && !( errno == EWOULDBLOCK || errno == EINTR ) ) ){ m_data.code = HTTP_ERR; errmsg = "send failed : " + m_data.url; errmsg.append( get_error_message( errno ) ); return false; } if( tmpsize > 0 ) send_size -= tmpsize; } char rbuf[256]; size_t read_size = 0; while( read_size < sizeof(rbuf) && !m_stop ){ ssize_t tmpsize; if( !wait_recv_send( soc, true ) ){ m_data.code = HTTP_TIMEOUT; errmsg = "CONNECT: read timeout in"; return false; } tmpsize = recv( soc, rbuf + read_size, sizeof(rbuf) - read_size, 0 ); if( tmpsize < 0 && errno != EINTR ){ m_data.code = HTTP_ERR; errmsg = "CONNECT: recv() failed"; errmsg.append( get_error_message( errno ) ); return false; } if( tmpsize == 0 ) break; if( tmpsize > 0 ){ read_size += tmpsize; const int ret = receive_header( rbuf, read_size ); if( ret == HTTP_ERR ){ m_data.code = HTTP_ERR; errmsg = "CONNECT: invalid header : " + m_data.url; return false; } else if( ret == HTTP_OK ) return true; } } return false; } // // 実際の処理部 // void Loader::run_main() { // エラーメッセージ std::string errmsg; int soc = -1; // ソケットID bool use_proxy = ( ! m_data.host_proxy.empty() ); JDLIB::JDSSL* ssl = nullptr; // 送信メッセージ作成 const std::string msg_send = create_msg_send(); #ifdef _DEBUG std::cout << "Loader::run_main : start loading thread : " << m_data.url << std::endl;; if( use_proxy ) std::cout << "use_proxy : " << m_data.host_proxy << std::endl; std::cout <<"send :----------\n" << msg_send << "\n---------------\n"; #endif // addrinfo 取得 if( m_data.host_proxy.empty() ){ m_addrinfo = get_addrinfo( m_data.host, m_data.port ); if( ! m_addrinfo ){ m_data.code = HTTP_ERR; errmsg = "getaddrinfo failed : " + m_data.url; goto EXIT_LOADING; } } else{ m_addrinfo = get_addrinfo( m_data.host_proxy, m_data.port_proxy ); if( ! m_addrinfo ){ m_data.code = HTTP_ERR; errmsg = "getaddrinfo failed : " + m_data.host_proxy; goto EXIT_LOADING; } } // ソケット作成 soc = socket( m_addrinfo ->ai_family, m_addrinfo ->ai_socktype, m_addrinfo ->ai_protocol ); if( ! SOC_ISVALID( soc ) ){ m_data.code = HTTP_ERR; errmsg = "socket failed : " + m_data.url; goto EXIT_LOADING; } // ソケットを非同期に設定 if( m_data.async ){ int flags; flags = fcntl( soc, F_GETFL, 0); if( flags == -1 || fcntl( soc, F_SETFL, flags | O_NONBLOCK ) < 0 ){ m_data.code = HTTP_ERR; errmsg = "fcntl failed"; goto EXIT_LOADING; } } // サーバにconnect int ret; ret = connect( soc, m_addrinfo ->ai_addr, m_addrinfo ->ai_addrlen ); if( ret != 0 ){ // ノンブロックでまだ接続中 if ( !( m_data.async && errno == EINPROGRESS ) ){ m_data.code = HTTP_ERR; if( ! use_proxy ) errmsg = "connect failed : " + m_data.host; else errmsg = "connect failed : " + m_data.host_proxy; errmsg.append( get_error_message( errno ) ); goto EXIT_LOADING; } } // connect待ち if( m_data.async ){ if( ! wait_recv_send( soc, false ) ){ // タイムアウト m_data.code = HTTP_TIMEOUT; errmsg = "connect timeout"; goto EXIT_LOADING; } // connectが成功したかチェック int optval; socklen_t optlen = sizeof( int ); if( getsockopt( soc, SOL_SOCKET, SO_ERROR, (void *)&optval, &optlen ) < 0 ){ m_data.code = HTTP_ERR; errmsg = "getsockopt failed"; goto EXIT_LOADING; } if( optval != 0 ){ m_data.code = HTTP_ERR; errmsg = "connect(getsockopt) failed"; goto EXIT_LOADING; } #ifdef _DEBUG std::cout << "connect ok\n"; #endif } // ssl 初期化とコネクト if( m_data.use_ssl ){ if ( use_proxy ) { if ( ! send_connect( soc, errmsg ) ) goto EXIT_LOADING; } ssl = new JDLIB::JDSSL(); if( ! ssl->connect( soc, m_data.host.c_str() ) ){ m_data.code = HTTP_ERR; errmsg = ssl->get_errmsg() + " : " + m_data.url; goto EXIT_LOADING; } } // SEND 又は POST // 通常 if( !ssl ){ size_t send_size = strlen( msg_send.data() ); while( send_size > 0 && !m_stop ){ // writefds 待ち if( ! wait_recv_send( soc, false ) ){ // タイムアウト m_data.code = HTTP_TIMEOUT; errmsg = "send timeout"; goto EXIT_LOADING; } // SEND 又は POST #ifdef MSG_NOSIGNAL ssize_t tmpsize = send( soc, msg_send.data(), send_size, MSG_NOSIGNAL ); #else // SolarisにはMSG_NOSIGNALが無いのでSIGPIPEをIGNOREする (FreeBSD4.11Rにもなかった) signal( SIGPIPE , SIG_IGN ); /* シグナルを無視する */ ssize_t tmpsize = send( soc, msg_send.data(), send_size,0); signal(SIGPIPE,SIG_DFL); /* 念のため戻す */ #endif // MSG_NOSIGNAL if( tmpsize == 0 || ( tmpsize < 0 && !( errno == EWOULDBLOCK || errno == EINTR ) ) ){ m_data.code = HTTP_ERR; errmsg = "send failed : " + m_data.url; errmsg.append( get_error_message( errno ) ); goto EXIT_LOADING; } if( tmpsize > 0 ) send_size -= tmpsize; } if( m_stop ) goto EXIT_LOADING; #ifdef _DEBUG std::cout << "send ok\n"; #endif } // SSL使用 else{ if( ssl->write( msg_send.data(), strlen( msg_send.data() ) ) < 0 ){ m_data.code = HTTP_ERR; errmsg = ssl->get_errmsg() + " : " + m_data.url; goto EXIT_LOADING; } } // 受信用バッファを作ってメッセージ受信 size_t mrg; mrg = 64; // 一応オーバーフロー避けのおまじない assert( m_buf == nullptr ); m_buf = ( char* )malloc( m_lng_buf + mrg ); bool receiving_header; #ifdef _DEBUG_TIME MISC::start_measurement( 1 ); #endif // 受信開始 receiving_header = true; m_data.length_current = 0; m_data.size_data = 0; do{ // 読み込み size_t read_size = 0; while( read_size < m_lng_buf - mrg && !m_stop ){ ssize_t tmpsize; // 通常 if( !ssl ){ #ifdef _DEBUG_TIME MISC::start_measurement( 0 ); #endif // readfds 待ち if( !wait_recv_send( soc, true ) ){ // タイムアウト m_data.code = HTTP_TIMEOUT; errmsg = "read timeout"; goto EXIT_LOADING; } tmpsize = recv( soc, m_buf + read_size, m_lng_buf - read_size - mrg, 0 ); if( tmpsize < 0 && errno != EINTR ){ m_data.code = HTTP_ERR; errmsg = "recv() failed"; errmsg.append( get_error_message( errno ) ); goto EXIT_LOADING; } #ifdef _DEBUG_TIME std::cout << "size = " << tmpsize << " time(ns) = " << MISC::measurement( 0 ) << std::endl; #endif } // SSL else{ tmpsize = ssl->read( m_buf + read_size, m_lng_buf - read_size - mrg ); if( tmpsize < 0 ){ m_data.code = HTTP_ERR; errmsg = ssl->get_errmsg() + " : " + m_data.url; goto EXIT_LOADING; } } if( tmpsize == 0 ) break; if( tmpsize > 0 ){ read_size += tmpsize; // ヘッダ取得 if( receiving_header ){ const int http_code = receive_header( m_buf, read_size ); if( http_code == HTTP_ERR ){ m_data.code = HTTP_ERR; errmsg = "invalid header : " + m_data.url; goto EXIT_LOADING; } else if( http_code == HTTP_OK ) receiving_header = false; } if( m_data.length && m_data.length <= m_data.length_current + read_size ) break; } } m_buf[ read_size ] = '\0'; // 停止指定 if( m_stop ) break; // サーバ側がcloseした if( read_size == 0 ){ // ヘッダを取得する前にcloseしたらエラー if( receiving_header && m_data.size_data == 0 ){ m_data.code = HTTP_ERR; errmsg = "no data"; goto EXIT_LOADING; } // コード304等の場合は終了 break; } // ヘッダ取得失敗 if( receiving_header ){ m_data.code = HTTP_ERR; errmsg = "no http header"; goto EXIT_LOADING; } m_data.length_current += read_size; // chunkedな場合 if( m_use_chunk ){ if( !skip_chunk( m_buf, read_size ) ){ m_data.code = HTTP_ERR; errmsg = "skip_chunk() failed : " + m_data.url; goto EXIT_LOADING; } if( ! read_size ) break; } // 圧縮されていない時はそのままコールバック呼び出し if( !m_use_zlib ) { m_data.size_data += read_size; // コールバック呼び出し if( m_loadable ) m_loadable->receive( m_buf, read_size ); } // 圧縮されているときは unzip してからコールバック呼び出し else if( !unzip( m_buf, read_size ) ){ m_data.code = HTTP_ERR; errmsg = "unzip() failed : " + m_data.url; goto EXIT_LOADING; } if( m_data.length && m_data.length <= m_data.length_current ) break; } while( !m_stop ); #ifdef _DEBUG_TIME std::cout << "Loader::run_main loading time(ns) = " << MISC::measurement( 1 ) << std::endl; #endif // 終了処理 EXIT_LOADING: // ssl クローズ if( ssl ){ ssl->close(); delete ssl; ssl = nullptr; } if( SOC_ISVALID( soc ) ){ // writefds待ち // 待たないとclose()したときにfinパケットが消える? if( ! wait_recv_send( soc, false ) ){ // タイムアウト m_data.code = HTTP_TIMEOUT; errmsg = "send timeout"; } // 送信禁止 shutdown( soc, SHUT_WR ); } // 強制停止した場合 if( m_stop ){ #ifdef _DEBUG std::cout << "Loader::run_main : stop loading\n"; #endif m_data.code = HTTP_TIMEOUT; m_data.modified = std::string(); m_data.str_code = "stop loading"; } // エラーあり else if( ! errmsg.empty() ){ m_data.modified = std::string(); MISC::ERRMSG( errmsg ); m_data.str_code = errmsg; } // ソケットクローズ if( SOC_ISVALID( soc ) ){ close( soc ); } // addrinfo開放 if( m_addrinfo ) freeaddrinfo( m_addrinfo ); m_addrinfo = nullptr; // トークン返す return_token( this ); finish_loading(); #ifdef _DEBUG std::cout << "Loader::run_main : finish loading : " << m_data.url << std::endl;; std::cout << "read size : " << m_data.length_current << " / " << m_data.length << std::endl;; std::cout << "data size : " << m_data.size_data << std::endl;; std::cout << "code : " << m_data.code << std::endl << std::endl; #endif } // // addrinfo 取得 // struct addrinfo* Loader::get_addrinfo( const std::string& hostname, const int port ) { if( port < 0 || port > 65535 ) return nullptr; if( hostname.empty() ) return nullptr; int ret; struct addrinfo hints, *res; memset( &hints, 0, sizeof( addrinfo ) ); if( m_data.use_ipv6 ) hints.ai_family = AF_UNSPEC; else hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; const std::string port_str = std::to_string( port ); ret = getaddrinfo( hostname.c_str(), port_str.c_str(), &hints, &res ); if( ret ) { MISC::ERRMSG( m_data.str_code ); return nullptr; } #ifdef _DEBUG const auto family = res->ai_addr->sa_family; const void* src; if( family == AF_INET ) { src = &reinterpret_cast( res->ai_addr )->sin_addr; } else { src = &reinterpret_cast( res->ai_addr )->sin6_addr; } char buf[ INET6_ADDRSTRLEN ]{}; if( inet_ntop( family, src, buf, sizeof(buf) ) ) { std::cout << "host = " << hostname << ", ipv6 = " << m_data.use_ipv6 << ", ip = " << buf << std::endl; } #endif return res; } // // 送信メッセージ作成 // std::string Loader::create_msg_send() const { bool post_msg = ( !m_data.str_post.empty() && !m_data.head ); bool use_proxy = ( ! m_data.host_proxy.empty() ); std::ostringstream msg; msg.clear(); if( m_data.head ) msg << "HEAD "; else if( ! post_msg ) msg << "GET "; else msg << "POST "; if( ! use_proxy ) msg << m_data.path << " HTTP/1.1\r\n"; else { msg << m_data.protocol << m_data.host << ":" << m_data.port << m_data.path << " HTTP/1.1\r\n"; } msg << "Host: " << m_data.host << "\r\n"; if( ! m_data.contenttype.empty() ) msg << "Content-Type: " << m_data.contenttype << "\r\n"; if( ! m_data.agent.empty() ) msg << "User-Agent: " << m_data.agent << "\r\n"; if( ! m_data.origin.empty() ) msg << "Origin: " << m_data.origin << "\r\n"; if( ! m_data.referer.empty() ) msg << "Referer: " << m_data.referer << "\r\n"; // basic認証 if( ! m_data.basicauth.empty() ) msg << "Authorization: Basic " << MISC::base64( m_data.basicauth ) << "\r\n"; // proxy basic認証 if( use_proxy && ! m_data.basicauth_proxy.empty() ) msg << "Proxy-Authorization: Basic " << MISC::base64( m_data.basicauth_proxy ) << "\r\n"; if( ! m_data.cookie_for_request.empty() ) msg << "Cookie: " << m_data.cookie_for_request << "\r\n"; if( ! m_data.modified.empty() ) msg << "If-Modified-Since: " << m_data.modified << "\r\n"; if( ! m_data.accept.empty() ) msg << "Accept: " << m_data.accept << "\r\n"; else msg << "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\r\n"; msg << "Accept-Language: ja,en-US;q=0.7,en;q=0.3\r\n"; // レジュームするときは gzip は受け取らない if( m_data.byte_readfrom ){ msg << "Range: bytes=" << m_data.byte_readfrom << "-\r\n"; // プロキシ使う場合は no-cache 指定 if( use_proxy ) msg << "Cache-Control: no-cache\r\n"; } else msg << "Accept-Encoding: gzip\r\n"; // レジュームしないなら gzip 受け取り可能 // その他のフィールド if( ! m_data.ex_field.empty() ) msg << m_data.ex_field; msg << "Connection: close\r\n"; msg << "Upgrade-Insecure-Requests: 1\r\n"; // POST する文字列 if( post_msg ){ msg << "Content-Length: " << m_data.str_post.length() << "\r\n"; msg << "\r\n"; msg << m_data.str_post; } msg << "\r\n"; return msg.str(); } // // サーバから送られてきた生データからヘッダ取得 // // 戻り値 : 成功 HTTP_OK、失敗 HTTP_ERR、未処理 HTTP_INIT // // 入力 // buf : 生データ // readsize: 生データサイズ // // 出力 // buf : ヘッダが取り除かれたデータ // readsize: 出力データサイズ // int Loader::receive_header( char* buf, size_t& read_size ) { #ifdef _DEBUG std::cout << "Loader::receive_header : read_size = " << read_size << std::endl; #endif buf[ read_size ] = '\0'; m_data.str_header = buf; size_t lng_header = m_data.str_header.find( "\r\n\r\n" ); if( lng_header != std::string::npos ) lng_header += 4; else{ lng_header = m_data.str_header.find( "\n\n" ); if( lng_header != std::string::npos ) lng_header +=2; else return HTTP_INIT; } m_data.str_header.resize( lng_header ); #ifdef _DEBUG std::cout << "header : size = " << lng_header << " byte\n"; std::cout << m_data.str_header << std::endl; #endif if( ! analyze_header() ) return HTTP_ERR; // 残りのデータを前に移動 read_size -= lng_header; if( read_size ){ memmove( buf, buf+ lng_header, read_size ); buf[ read_size ] = '\0'; } return HTTP_OK; } // // ヘッダ解析 // bool Loader::analyze_header() { // コード std::string str_tmp = analyze_header_option( "HTTP/1.1 " ); if( ! str_tmp.empty() ){ m_data.str_code = "HTTP/1.1 " + str_tmp; } else{ str_tmp = analyze_header_option( "HTTP/1.0 " ); if( ! str_tmp.empty() ) m_data.str_code = "HTTP/1.0 " + str_tmp; } if( str_tmp.empty() ){ MISC::ERRMSG( "could not find HTTP/1.1" ); return false; } size_t i = str_tmp.find( ' ' ); if( i == std::string::npos ) m_data.code = atoi( str_tmp.c_str() ); else m_data.code = atoi( str_tmp.substr( 0, i ).c_str() ); if( m_data.code == 0 ){ MISC::ERRMSG( "could not get http status code" ); return false; } // サイズ m_data.length = 0; str_tmp = analyze_header_option( "Content-Length: " ); if( ! str_tmp.empty() ) m_data.length = atoi( str_tmp.c_str() ); // date m_data.date = analyze_header_option( "Date: " ); // modified m_data.modified = analyze_header_option( "Last-Modified: " ); // cookie m_data.list_cookies = analyze_header_option_list( "Set-Cookie: " ); // Location if( m_data.code == HTTP_REDIRECT || m_data.code == HTTP_MOVED_PERM ) m_data.location = analyze_header_option( "Location: " ); else m_data.location = std::string(); // Content-Type m_data.contenttype = analyze_header_option( "Content-Type: " ); // chunked か m_use_chunk = false; str_tmp = analyze_header_option( "Transfer-Encoding: " ); if( str_tmp.find( "chunked" ) != std::string::npos ){ m_use_chunk = true; m_status_chunk = 0; m_pos_sizepart = m_str_sizepart; } // gzip か m_use_zlib = false; str_tmp = analyze_header_option( "Content-Encoding: " ); if( str_tmp.find( "gzip" ) != std::string::npos ){ if( !init_unzip() ) return false; } #ifdef _DEBUG std::cout << "code = " << m_data.code << std::endl; std::cout << "length = " << m_data.length << std::endl; std::cout << "date = " << m_data.date << std::endl; std::cout << "modified = " << m_data.modified << std::endl; for( const std::string& s : m_data.list_cookies ) std::cout << "cookie " << s << std::endl; std::cout << "location = " << m_data.location << std::endl; std::cout << "contenttype = " << m_data.contenttype<< std::endl; if( m_use_chunk ) std::cout << "m_use_chunk = true\n"; if( m_use_zlib ) std::cout << "m_use_zlib = true\n"; std::cout << "authenticate = " << analyze_header_option( "WWW-Authenticate: " ) << std::endl; std::cout << "\n"; #endif return true; } // // analyze_header() から呼ばれるオプションの値を取り出す関数 // std::string Loader::analyze_header_option( const std::string& option ) const { const std::size_t i = MISC::ascii_ignore_case_find( m_data.str_header, option ); if( i != std::string::npos ){ const std::size_t option_length = option.length(); std::size_t i2 = m_data.str_header.find( "\r\n", i ); if( i2 == std::string::npos ) i2 = m_data.str_header.find( "\n", i ); if( i2 != std::string::npos ) return m_data.str_header.substr( i + option_length, i2 - ( i + option_length ) ); } return std::string(); } // // analyze_header() から呼ばれるオプションの値を取り出す関数(リスト版) // std::list< std::string > Loader::analyze_header_option_list( const std::string& option ) const { std::list< std::string > str_list; const std::size_t option_length = option.length(); std::size_t i2 = 0; for(;;){ const std::size_t i = MISC::ascii_ignore_case_find( m_data.str_header, option, i2 ); if( i == std::string::npos ) break; i2 = m_data.str_header.find( "\r\n", i ); if( i2 == std::string::npos ) i2 = m_data.str_header.find( "\n", i ); if( i2 == std::string::npos ) break; str_list.push_back( m_data.str_header.substr( i + option_length, i2 - ( i + option_length ) ) ); } return str_list; } // // chunked なデータを切りつめる関数 // // 入力 // buf : 生データ // readsize: 生データサイズ // // 出力 // buf : 切りつめられたデータ // readsize: 出力データサイズ // bool Loader::skip_chunk( char* buf, size_t& read_size ) { #ifdef _DEBUG_CHUNKED std::cout << "\n[[ skip_chunk : start read_size = " << read_size << " ]]\n"; #endif size_t pos_chunk = 0; size_t pos_data_chunk_start = 0; size_t buf_size = 0; for(;;){ // サイズ部 if( m_status_chunk == 0 ){ // \nが来るまで m_str_sizepart[] に文字をコピーしていく for( ; pos_chunk < read_size; ++pos_chunk, ++m_pos_sizepart ){ // バッファオーバーフローのチェック if( ( long )( m_pos_sizepart - m_str_sizepart ) >= 64 ){ MISC::ERRMSG( "buffer over flow at skip_chunk" ); return false; } *( m_pos_sizepart ) = buf[ pos_chunk ]; // \n が来たらデータ部のサイズを取得 if( buf[ pos_chunk ] == '\n' ){ ++pos_chunk; *( m_pos_sizepart ) = '\0'; if( *( m_pos_sizepart -1 ) == '\r' ) *( m_pos_sizepart -1 ) = '\0'; // "\r\n"の場合 m_lng_leftdata = strtol( m_str_sizepart, nullptr, 16 ); m_pos_sizepart = m_str_sizepart; #ifdef _DEBUG_CHUNKED std::cout << "[[ skip_chunk : size chunk finished : str = 0x" << m_str_sizepart << " ]]\n"; std::cout << "[[ skip_chunk : enter the data chunk, data size = " << m_lng_leftdata << " ]]\n"; #endif pos_data_chunk_start = pos_chunk; m_status_chunk = 1; break; } } } // データ部 if( m_status_chunk == 1 ){ // データを前に詰める if( m_lng_leftdata ){ for( ; m_lng_leftdata > 0 && pos_chunk < read_size; --m_lng_leftdata, ++pos_chunk ); size_t buf_size_tmp = pos_chunk - pos_data_chunk_start; if( buf_size != pos_data_chunk_start && buf_size_tmp ) memmove( buf + buf_size , buf + pos_data_chunk_start, buf_size_tmp ); buf_size += buf_size_tmp; } // データを全部読み込んだらデータ部終わり if( m_lng_leftdata == 0 ) m_status_chunk = 2; } // データ部→サイズ部切り替え中("\r"の前) if( m_status_chunk == 2 && pos_chunk != read_size ){ if( buf[ pos_chunk++ ] != '\r' ){ MISC::ERRMSG( "broken chunked data." ); return false; } m_status_chunk = 3; } // データ部→サイズ部切り替え中("\n"の前: "\r" と "\n" の間でサーバからの入力が分かれる時がある) if( m_status_chunk == 3 && pos_chunk != read_size ){ if( buf[ pos_chunk++ ] != '\n' ){ MISC::ERRMSG( "broken chunked data." ); return false; } #ifdef _DEBUG_CHUNKED std::cout << "[[ skip_chunk : data chunk finished. ]]\n"; #endif m_status_chunk = 0; } // バッファ終わり if( pos_chunk == read_size ){ read_size = buf_size; buf[ read_size ] = '\0'; #ifdef _DEBUG_CHUNKED std::cout << "[[ skip_chunk : output = " << read_size << " ]]\n\n"; #endif return true; } } return true; } // // zlib 初期化 // bool Loader::init_unzip() { #ifdef _DEBUG std::cout << "Loader::init_unzip\n"; #endif m_use_zlib = true; // zlib 初期化 m_zstream.zalloc = Z_NULL; m_zstream.zfree = Z_NULL; m_zstream.opaque = Z_NULL; m_zstream.next_in = Z_NULL; m_zstream.avail_in = 0; if ( inflateInit2( &m_zstream, 15 + 32 ) != Z_OK ) // デフォルトの15に+32する( windowBits = 47 )と自動でヘッダ認識 { MISC::ERRMSG( "inflateInit2 failed." ); return false; } assert( m_buf_zlib_in == nullptr ); assert( m_buf_zlib_out == nullptr ); m_buf_zlib_in = ( Bytef* )malloc( sizeof( Bytef ) * m_lng_buf_zlib_in + 64 ); m_buf_zlib_out = ( Bytef* )malloc( sizeof( Bytef ) * m_lng_buf_zlib_out + 64 ); return true; } // // unzipしてコールバック呼び出し // bool Loader::unzip( char* buf, std::size_t read_size ) { // zlibの入力バッファに値セット if( m_zstream.avail_in + read_size > m_lng_buf_zlib_in ){ // オーバーフローのチェック MISC::ERRMSG( "buffer over flow at zstream_in : " + m_data.url ); return false; } memcpy( m_buf_zlib_in + m_zstream.avail_in , buf, read_size ); m_zstream.avail_in += read_size; m_zstream.next_in = m_buf_zlib_in; size_t byte_out = 0; do{ // 出力バッファセット m_zstream.next_out = m_buf_zlib_out; m_zstream.avail_out = m_lng_buf_zlib_out; // 解凍 int ret = inflate( &m_zstream, Z_NO_FLUSH ); if( ret == Z_OK || ret == Z_STREAM_END ){ byte_out = m_lng_buf_zlib_out - m_zstream.avail_out; m_buf_zlib_out[ byte_out ] = '\0'; m_data.size_data += byte_out; #ifdef _DEBUG std::cout << "inflate ok byte = " << byte_out << std::endl; #endif // コールバック呼び出し if( byte_out && m_loadable ) m_loadable->receive( ( char* )m_buf_zlib_out, byte_out ); } else return true; } while ( byte_out ); // 入力バッファに使ってないデータが残っていたら前に移動 if( m_zstream.avail_in ) memmove( m_buf_zlib_in, m_buf_zlib_in + ( read_size - m_zstream.avail_in ), m_zstream.avail_in ); return true; } // // sent, recv待ち // bool Loader::wait_recv_send( const int fd, const bool recv ) { if( !fd ) return true; // 同期している場合は何もしない if( !m_data.async ) return true; int count = 0; for(;;){ errno = 0; int ret; fd_set fdset; FD_ZERO( &fdset ); FD_SET( fd , &fdset ); timeval timeout; memset( &timeout, 0, sizeof( timeval ) ); timeout.tv_sec = 1; if( recv ) ret = select( fd+1 , &fdset , nullptr , nullptr , &timeout ); else ret = select( fd+1 , nullptr, &fdset , nullptr , &timeout ); #ifdef _DEBUG if( errno == EINTR && ret < 0 ) std::cout << "Loader::wait_recv_send : errno = EINTR " << errno << std::endl; #endif if( errno != EINTR && ret < 0 ){ #ifdef _DEBUG std::cout << "Loader::wait_recv_send : errno = " << errno << std::endl; #endif MISC::ERRMSG( "select failed" ); break; } if( errno != EINTR && FD_ISSET( fd, &fdset ) ) return true; if( m_stop ) break; if( ++count >= m_data.timeout ) break; #ifdef _DEBUG std::cout << "Loader::wait_recv_send ret = " << ret << " errno = " << errno << " timeout = " << count << std::endl; #endif } return false; } // // ローディング終了処理 // void Loader::finish_loading() { #ifdef _DEBUG std::cout << "Loader::finish_loading : url = " << m_data.url << std::endl; #endif // SKELETON::Loadable に終了を知らせる if( m_loadable ) m_loadable->finish(); m_loading = false; JDLIB::pop_loader_queue(); } jdim-0.7.0/src/jdlib/loader.h000066400000000000000000000062021417047150700157410ustar00rootroot00000000000000// ライセンス: GPL2 // // ファイルローダ // // SKELETON::Loadable と組み合わせて使用する // #ifndef _LOADER_H #define _LOADER_H #include "loaderdata.h" #include "jdthread.h" #include #include #include #include namespace SKELETON { class Loadable; } namespace JDLIB { class Loader { LOADERDATA m_data; struct addrinfo* m_addrinfo; bool m_stop; // = true にするとスレッド停止 bool m_loading; JDLIB::Thread m_thread; SKELETON::Loadable* m_loadable; // スレッド起動待ち状態になった時に、起動順のプライオリティを下げる bool m_low_priority; // 読み込みバッファ unsigned long m_lng_buf; char* m_buf; // zlib 用のバッファ unsigned long m_lng_buf_zlib_in; unsigned long m_lng_buf_zlib_out; Bytef* m_buf_zlib_in; Bytef* m_buf_zlib_out; // chunk 用変数 bool m_use_chunk; long m_status_chunk; char m_str_sizepart[ 64 ]; // サイズ部のバッファ。64byte以下と仮定(超えるとエラー) char* m_pos_sizepart; size_t m_lng_leftdata; // zlib 用変数 bool m_use_zlib; z_stream m_zstream; public: // low_priority = true の時はスレッド起動待ち状態になった時に、起動順のプライオリティを下げる explicit Loader( const bool low_priority ); ~Loader(); bool is_loading() const { return m_loading; } const LOADERDATA& data() const { return m_data; } bool run( SKELETON::Loadable* cb, const LOADERDATA& data_in ); void wait(); void stop(); bool get_low_priority() const { return m_low_priority; } void create_thread(); private: static void* launcher( void* ); void clear(); void run_main(); struct addrinfo* get_addrinfo( const std::string& hostname, const int port ); std::string create_msg_send() const; bool wait_recv_send( const int fd, const bool recv ); bool send_connect( const int soc, std::string& errmsg ); // ローディング終了処理 void finish_loading(); // ヘッダ用 int receive_header( char* buf, size_t& read_size ); bool analyze_header(); std::string analyze_header_option( const std::string& option ) const; std::list< std::string > analyze_header_option_list( const std::string& option ) const; // chunk用 bool skip_chunk( char* buf, size_t& read_size ); // unzip 用 bool init_unzip(); bool unzip( char* buf, std::size_t read_size ); }; // ローダの起動待ちキューにあるスレッドを実行しない // アプリ終了時にこの関数を呼び出さないとキューに登録されたスレッドが起動してしまうので注意 void disable_pop_loader_queue(); // mainの最後でローダが動いていないかチェックする関数 void check_loader_alive(); } #endif jdim-0.7.0/src/jdlib/loaderdata.cpp000066400000000000000000000026341417047150700171330ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "loader.h" #include "config/globalconf.h" #include "httpcode.h" using namespace JDLIB; LOADERDATA::LOADERDATA() { init(); } void LOADERDATA::init() { head = false; length = 0; length_current = 0; size_data = 0; url.clear(); protocol.clear(); host.clear(); path.clear(); port = 0; use_ssl = false; async = true; use_ipv6 = CONFIG::get_use_ipv6(); str_post.clear(); host_proxy.clear(); port_proxy = 0; basicauth_proxy.clear(); agent.clear(); origin.clear(); referer.clear(); ex_field.clear(); str_header.clear(); code = HTTP_INIT; str_code.clear(); date.clear(); modified.clear(); byte_readfrom = 0; cookie_for_request.clear(); list_cookies.clear(); contenttype.clear(); basicauth.clear(); size_buf = 0; timeout = 0; } // 一般データのダウンロード用初期化 void LOADERDATA::init_for_data() { agent = CONFIG::get_agent_for_data(); if( CONFIG::get_use_proxy_for_data() ) host_proxy = CONFIG::get_proxy_for_data(); else host_proxy = std::string(); port_proxy = CONFIG::get_proxy_port_for_data(); basicauth_proxy = CONFIG::get_proxy_basicauth_for_data(); size_buf = CONFIG::get_loader_bufsize(); timeout = CONFIG::get_loader_timeout_data(); } jdim-0.7.0/src/jdlib/loaderdata.h000066400000000000000000000032711417047150700165760ustar00rootroot00000000000000// ライセンス: GPL2 // // ファイルローダに渡すデータクラス // #ifndef _LOADERDATA_H #define _LOADERDATA_H #include #include namespace JDLIB { class LOADERDATA { public: bool head; size_t length; // Content-Length の値 size_t length_current; // 解凍前の現在までに読み込んでいるサイズ。よって length_current <= length size_t size_data; // 解凍したあとの純粋なデータサイズ。よって length < size_data となる場合もある std::string url; std::string protocol; std::string host; std::string path; long port; bool use_ssl; // https bool async; // 非同期ソケット使用 bool use_ipv6; // ipv6使用 std::string str_post; std::string host_proxy; long port_proxy; std::string basicauth_proxy; // proxy 認証 std::string agent; std::string origin; std::string referer; std::string accept; std::string ex_field; // 送信時にヘッダに追加するフィールド std::string str_header; long code; std::string str_code; std::string date; std::string modified; size_t byte_readfrom; std::string cookie_for_request; std::list< std::string > list_cookies; std::string location; std::string contenttype; std::string basicauth; size_t size_buf; long timeout; LOADERDATA(); void init(); // 一般データのダウンロード用初期化 void init_for_data(); }; } #endif jdim-0.7.0/src/jdlib/meson.build000066400000000000000000000010651417047150700164660ustar00rootroot00000000000000sources = [ 'confloader.cpp', 'cookiemanager.cpp', 'heap.cpp', 'imgloader.cpp', 'jdiconv.cpp', 'jdmigemo.cpp', 'jdregex.cpp', 'jdthread.cpp', 'loader.cpp', 'loaderdata.cpp', 'misccharcode.cpp', 'miscgtk.cpp', 'miscmsg.cpp', 'misctime.cpp', 'misctrip.cpp', 'miscutil.cpp', 'miscx.cpp', 'ssl.cpp', 'tfidf.cpp', 'timeout.cpp', ] deps = [ gtkmm_dep, socket_dep, tls_dep, x11_dep, ] jdlib_lib = static_library( 'jdlib', [sources, config_h], dependencies : deps, include_directories : include_directories('..'), ) jdim-0.7.0/src/jdlib/misccharcode.cpp000066400000000000000000000165701417047150700174630ustar00rootroot00000000000000// License: GPL2 //#define _DEBUG #include "jddebug.h" #include "misccharcode.h" #include // チェックする最大バイト数 #define CHECK_LIMIT 1024 /*--- 制御文字とASCII -----------------------------------------------*/ // [ 制御文字 ] 0x00〜0x1F 0x7F #define CTRL_RNAGE( target ) ( target < 0x20 || target == 0x7F ) // [ ASCII ] 0x20〜0x7E #define ASCII_RANGE( target ) ( (unsigned char)( target - 0x20 ) < 0x5F ) // [ 制御文字とASCII ] 0x00〜0x7F #define CTRL_AND_ASCII_RANGE( target ) ( (unsigned char)target < 0x80 ) /*---- EUC-JP -------------------------------------------------------*/ // // [ カナ ] // 1バイト目 0x8E #define EUC_CODE_KANA( target ) ( (unsigned char)target == 0x8E ) // 2バイト目 0xA1〜0xDF #define EUC_RANGE_KANA( target ) ( (unsigned char)( target - 0xA1 ) < 0x3F ) // // [ 補助漢字 ] // 1バイト目 0x8F #define EUC_CODE_SUB_KANJI( target ) ( (unsigned char)target == 0x8F ) // // [ 漢字 ] // 1バイト目 0xA1〜0xFE // 2バイト目 0xA1〜0xFE #define EUC_RANGE_MULTI( target ) ( (unsigned char)( target - 0xA1 ) < 0x5E ) // bool MISC::is_euc( const char* input, size_t read_byte ) { if( ! input ) return false; size_t byte = read_byte; const size_t input_length = strlen( input ); while( byte < input_length && byte < CHECK_LIMIT ) { // 制御文字かアスキー if( CTRL_AND_ASCII_RANGE( input[ byte ] ) ) { ++byte; } // カナ else if( EUC_CODE_KANA( input[ byte ] ) && EUC_RANGE_KANA( input[ byte + 1 ] ) ) { byte += 2; } // 補助漢字 else if( EUC_CODE_SUB_KANJI( input[ byte ] ) && EUC_RANGE_MULTI( input[ byte + 1 ] ) && EUC_RANGE_MULTI( input[ byte + 2 ] ) ) { byte += 3; } // 漢字 else if( EUC_RANGE_MULTI( input[ byte ] ) && EUC_RANGE_MULTI( input[ byte + 1 ] ) ) { byte += 2; } // その他 else { return false; } } return true; } /*---- ISO-2022-JP --------------------------------------------------*/ // // エスケープシーケンスの開始文字 ESC(0x1B) #define JIS_ESC_SEQ_START( target ) ( target == 0x1B ) // bool MISC::is_jis( const char* input, size_t& byte ) { if( ! input ) return false; const size_t input_length = strlen( input ); while( byte < input_length && byte < CHECK_LIMIT ) { // ESCが出現したか否かだけで判断 if( JIS_ESC_SEQ_START( input[ byte ] ) ) return true; // JISに該当しないコード 0x80〜 else if( ! CTRL_AND_ASCII_RANGE( input[ byte ] ) ) return false; ++byte; } // ループが終了していたら制御文字かアスキー return false; } /*---- Shift_JIS ----------------------------------------------------*/ // // [ カナ ] 0xA1〜0xDF #define SJIS_RANGE_KANA( target ) ( (unsigned char)( target - 0xA1 ) < 0x3F ) // // [ 漢字 ] // 1バイト目 0x81〜0x9F 0xE0〜0xFC( 0xEF ) //#define SJIS_RANGE_1( target ) ( (unsigned char)( target ^ 0x20 ) - 0xA1 < 0x2F ) #define SJIS_RANGE_1( target ) ( ( (unsigned char)target ^ 0x20 ) - 0xA1 < 0x3C ) // 0x81〜0x9F #define SJIS_RANGE_1_H( target ) ( (unsigned char)( target - 0x81 ) < 0x1F ) // 0xE0〜0xFC #define SJIS_RANGE_1_T( target ) ( (unsigned char)( target - 0xE0 ) < 0x1D ) // // 2バイト目 0x40〜0x7E 0x80〜0xFC #define SJIS_RANGE_2( target ) ( (unsigned char)( target - 0x40 ) < 0xBD && target != 0x7F ) // 0x40〜0x7E #define SJIS_RANGE_2_H( target ) ( (unsigned char)( target - 0x40 ) < 0x3F ) // 0x80〜0xFC #define SJIS_RANGE_2_T( target ) ( (unsigned char)( target - 0x80 ) < 0x7D ) // bool MISC::is_sjis( const char* input, size_t read_byte ) { if( ! input ) return false; size_t byte = read_byte; const size_t input_length = strlen( input ); while( byte < input_length && byte < CHECK_LIMIT ) { // 制御文字かアスキー if( CTRL_AND_ASCII_RANGE( input[ byte ] ) ) { ++byte; } // カナ else if( SJIS_RANGE_KANA( input[ byte ] ) ) { ++byte; } // 漢字(MS932) else if( SJIS_RANGE_1( input[ byte ] ) && SJIS_RANGE_2( input[ byte + 1 ] ) ) { byte += 2; } // その他 else { return false; } } return true; } /*---- UTF ---------------------------------------------------------*/ // // 0xC0・0xC1はセキュリティ上の問題で使用が禁止されている // // [ 1バイト目の範囲 ] 0xC2〜0xFD [ RFC2279(破棄) ] // [ 1バイト目の範囲 ] 0xC2〜0xF4 [ RFC6329 ] #define UTF_RANGE_1( target ) ( (unsigned char)( target - 0xC2 ) < 0x33 ) // // [ 1バイト目 (2バイト文字) ] 先頭2ビットが1 #define UTF_FLAG_2( target ) ( ( target & 0xC0 ) == 0xC0 ) // // [ 1バイト目 (3バイト文字) ] 先頭3ビットが1 #define UTF_FLAG_3( target ) ( ( target & 0xE0 ) == 0xE0 ) // // [ 1バイト目 (4バイト文字) ] 先頭4ビットが1 #define UTF_FLAG_4( target ) ( ( target & 0xF0 ) == 0xF0 ) // // [ 2バイト目以降 ] 0x80〜0xBF #define UTF_RANGE_MULTI_BYTE( target ) ( (unsigned char)( target - 0x80 ) < 0x40 ) // bool MISC::is_utf( const char* input, size_t read_byte ) { if( ! input ) return false; bool valid = true; size_t byte = read_byte; const size_t input_length = strlen( input ); while( byte < input_length && byte < CHECK_LIMIT ) { // 制御文字かアスキー if( CTRL_AND_ASCII_RANGE( input[ byte ] ) ) { ++byte; continue; } // UTF-8の1バイト目の範囲ではない else if( ! UTF_RANGE_1( input[ byte ] ) ) { return false; } int byte_count = 1; // 4,3,2バイト if( UTF_FLAG_4( input[ byte ] ) ) byte_count = 4; else if( UTF_FLAG_3( input[ byte ] ) ) byte_count = 3; else if( UTF_FLAG_2( input[ byte ] ) ) byte_count = 2; ++byte; // 2バイト目以降 while( byte_count > 1 ) { if( UTF_RANGE_MULTI_BYTE( input[ byte ] ) ) { ++byte; } else { valid = false; break; } --byte_count; } } return valid; } // // 日本語文字コードの判定 // // 各コードの判定でtrueの間は文字数分繰り返されるので // 速度の求められる繰り返し処理などで使わない事 // int MISC::judge_char_code( const std::string& str ) { int code = CHARCODE_UNKNOWN; if( str.empty() ) return code; size_t read_byte = 0; // JISの判定 if( is_jis( str.c_str(), read_byte ) ) code = CHARCODE_JIS; // JISの判定で最後まで進んでいたら制御文字かアスキー else if( read_byte == str.length() ) code = CHARCODE_ASCII; // is_jis()でASCII範囲外のバイトが現れた箇所から判定する // UTF-8の範囲 else if( is_utf( str.c_str(), read_byte ) ) code = CHARCODE_UTF; // EUC-JPの範囲 else if( is_euc( str.c_str(), read_byte ) ) code = CHARCODE_EUC_JP; // Shift_JISの範囲 else if( is_sjis( str.c_str(), read_byte ) ) code = CHARCODE_SJIS; return code; } jdim-0.7.0/src/jdlib/misccharcode.h000066400000000000000000000011411417047150700171140ustar00rootroot00000000000000// License: GPL2 // 日本語文字コードの判定 #ifndef _MISCCHARCODE_H #define _MISCCHARCODE_H #include namespace MISC { enum CodeSet { CHARCODE_UNKNOWN = -1, CHARCODE_ASCII = 0, CHARCODE_EUC_JP, CHARCODE_JIS, CHARCODE_SJIS, CHARCODE_UTF }; bool is_euc( const char* input, size_t read_byte ); bool is_jis( const char* input, size_t& read_byte ); bool is_sjis( const char* input, size_t read_byte ); bool is_utf( const char* input, size_t read_byte ); int judge_char_code( const std::string& str ); } #endif jdim-0.7.0/src/jdlib/miscgtk.cpp000066400000000000000000000311041417047150700164660ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "miscgtk.h" #include "miscutil.h" #include "imgloader.h" #include #include #include #include enum { CHAR_BUF = 256 // char の初期化用 }; // 色キーワードの検索で使う色名と16進表記を対応付ける配列 // https://developer.mozilla.org/en-US/docs/Web/CSS/color_value // 二分探索するため配列の要素は予めnameで辞書順にソートしておく static struct color_map { const char *name; const char *rgb; } constexpr const color_names[] = { { "aliceblue", "#F0F8FF" }, // since CSS3 { "antiquewhite", "#FAEBD7" }, // since CSS3 { "aqua", "#00ffff" }, // since CSS1 { "aquamarine", "#7FFFD4" }, // since CSS3 { "azure", "#F0FFFF" }, // since CSS3 { "beige", "#F5F5DC" }, // since CSS3 { "bisque", "#FFE4C4" }, // since CSS3 { "black", "#000000" }, // since CSS1 { "blanchedalmond", "#FFEBCD" }, // since CSS3 { "blue", "#0000ff" }, // since CSS1 { "blueviolet", "#8A2BE2" }, // since CSS3 { "brown", "#A52A2A" }, // since CSS3 { "burlywood", "#DEB887" }, // since CSS3 { "cadetblue", "#5F9EA0" }, // since CSS3 { "chartreuse", "#7FFF00" }, // since CSS3 { "chocolate", "#D2691E" }, // since CSS3 { "coral", "#FF7F50" }, // since CSS3 { "cornflowerblue", "#6495ED" }, // since CSS3 { "cornsilk", "#FFF8DC" }, // since CSS3 { "crimson", "#DC143C" }, // since CSS3 { "cyan", "#00FFFF" }, // since CSS3 { "darkblue", "#00008B" }, // since CSS3 { "darkcyan", "#008B8B" }, // since CSS3 { "darkgoldenrod", "#B8860B" }, // since CSS3 { "darkgray", "#A9A9A9" }, // since CSS3 { "darkgreen", "#006400" }, // since CSS3 { "darkgrey", "#A9A9A9" }, // since CSS3 { "darkkhaki", "#BDB76B" }, // since CSS3 { "darkmagenta", "#8B008B" }, // since CSS3 { "darkolivegreen", "#556B2F" }, // since CSS3 { "darkorange", "#FF8C00" }, // since CSS3 { "darkorchid", "#9932CC" }, // since CSS3 { "darkred", "#8B0000" }, // since CSS3 { "darksalmon", "#E9967A" }, // since CSS3 { "darkseagreen", "#8FBC8F" }, // since CSS3 { "darkslateblue", "#483D8B" }, // since CSS3 { "darkslategray", "#2F4F4F" }, // since CSS3 { "darkslategrey", "#2F4F4F" }, // since CSS3 { "darkturquoise", "#00CED1" }, // since CSS3 { "darkviolet", "#9400D3" }, // since CSS3 { "deeppink", "#FF1493" }, // since CSS3 { "deepskyblue", "#00BFFF" }, // since CSS3 { "dimgray", "#696969" }, // since CSS3 { "dimgrey", "#696969" }, // since CSS3 { "dodgerblue", "#1E90FF" }, // since CSS3 { "firebrick", "#B22222" }, // since CSS3 { "floralwhite", "#FFFAF0" }, // since CSS3 { "forestgreen", "#228B22" }, // since CSS3 { "fuchsia", "#ff00ff" }, // since CSS1 { "gainsboro", "#DCDCDC" }, // since CSS3 { "ghostwhite", "#F8F8FF" }, // since CSS3 { "gold", "#FFD700" }, // since CSS3 { "goldenrod", "#DAA520" }, // since CSS3 { "gray", "#808080" }, // since CSS1 { "green", "#008000" }, // since CSS1 { "greenyellow", "#ADFF2F" }, // since CSS3 { "grey", "#808080" }, // since CSS3 { "honeydew", "#F0FFF0" }, // since CSS3 { "hotpink", "#FF69B4" }, // since CSS3 { "indianred", "#CD5C5C" }, // since CSS3 { "indigo", "#4B0082" }, // since CSS3 { "ivory", "#FFFFF0" }, // since CSS3 { "khaki", "#F0E68C" }, // since CSS3 { "lavender", "#E6E6FA" }, // since CSS3 { "lavenderblush", "#FFF0F5" }, // since CSS3 { "lawngreen", "#7CFC00" }, // since CSS3 { "lemonchiffon", "#FFFACD" }, // since CSS3 { "lightblue", "#ADD8E6" }, // since CSS3 { "lightcoral", "#F08080" }, // since CSS3 { "lightcyan", "#E0FFFF" }, // since CSS3 { "lightgoldenrodyellow", "#FAFAD2" }, // since CSS3 { "lightgray", "#D3D3D3" }, // since CSS3 { "lightgreen", "#90EE90" }, // since CSS3 { "lightgrey", "#D3D3D3" }, // since CSS3 { "lightpink", "#FFB6C1" }, // since CSS3 { "lightsalmon", "#FFA07A" }, // since CSS3 { "lightseagreen", "#20B2AA" }, // since CSS3 { "lightskyblue", "#87CEFA" }, // since CSS3 { "lightslategray", "#778899" }, // since CSS3 { "lightslategrey", "#778899" }, // since CSS3 { "lightsteelblue", "#B0C4DE" }, // since CSS3 { "lightyellow", "#FFFFE0" }, // since CSS3 { "lime", "#00ff00" }, // since CSS1 { "limegreen", "#32CD32" }, // since CSS3 { "linen", "#FAF0E6" }, // since CSS3 { "magenta", "#FF00FF" }, // since CSS3 { "maroon", "#800000" }, // since CSS1 { "mediumaquamarine", "#66CDAA" }, // since CSS3 { "mediumblue", "#0000CD" }, // since CSS3 { "mediumorchid", "#BA55D3" }, // since CSS3 { "mediumpurple", "#9370DB" }, // since CSS3 { "mediumseagreen", "#3CB371" }, // since CSS3 { "mediumslateblue","#7B68EE" }, // since CSS3 { "mediumspringgreen", "#00FA9A" }, // since CSS3 { "mediumturquoise","#48D1CC" }, // since CSS3 { "mediumvioletred","#C71585" }, // since CSS3 { "midnightblue", "#191970" }, // since CSS3 { "mintcream", "#F5FFFA" }, // since CSS3 { "mistyrose", "#FFE4E1" }, // since CSS3 { "moccasin", "#FFE4B5" }, // since CSS3 { "navajowhite", "#FFDEAD" }, // since CSS3 { "navy", "#000080" }, // since CSS1 { "oldlace", "#FDF5E6" }, // since CSS3 { "olive", "#808000" }, // since CSS1 { "olivedrab", "#6B8E23" }, // since CSS3 { "orange", "#ffa500" }, // since CSS2.1 { "orangered", "#FF4500" }, // since CSS3 { "orchid", "#DA70D6" }, // since CSS3 { "palegoldenrod", "#EEE8AA" }, // since CSS3 { "palegreen", "#98FB98" }, // since CSS3 { "paleturquoise", "#AFEEEE" }, // since CSS3 { "palevioletred", "#DB7093" }, // since CSS3 { "papayawhip", "#FFEFD5" }, // since CSS3 { "peachpuff", "#FFDAB9" }, // since CSS3 { "peru", "#CD853F" }, // since CSS3 { "pink", "#FFC0CB" }, // since CSS3 { "plum", "#DDA0DD" }, // since CSS3 { "powderblue", "#B0E0E6" }, // since CSS3 { "purple", "#800080" }, // since CSS1 { "rebeccapurple", "#663399" }, // since CSS4 { "red", "#ff0000" }, // since CSS1 { "rosybrown", "#BC8F8F" }, // since CSS3 { "royalblue", "#4169E1" }, // since CSS3 { "saddlebrown", "#8B4513" }, // since CSS3 { "salmon", "#FA8072" }, // since CSS3 { "sandybrown", "#F4A460" }, // since CSS3 { "seagreen", "#2E8B57" }, // since CSS3 { "seashell", "#FFF5EE" }, // since CSS3 { "sienna", "#A0522D" }, // since CSS3 { "silver", "#c0c0c0" }, // since CSS1 { "skyblue", "#87CEEB" }, // since CSS3 { "slateblue", "#6A5ACD" }, // since CSS3 { "slategray", "#708090" }, // since CSS3 { "slategrey", "#708090" }, // since CSS3 { "snow", "#FFFAFA" }, // since CSS3 { "springgreen", "#00FF7F" }, // since CSS3 { "steelblue", "#4682B4" }, // since CSS3 { "tan", "#D2B48C" }, // since CSS3 { "teal", "#008080" }, // since CSS1 { "thistle", "#D8BFD8" }, // since CSS3 { "tomato", "#FF6347" }, // since CSS3 { "turquoise", "#40E0D0" }, // since CSS3 { "violet", "#EE82EE" }, // since CSS3 { "wheat", "#F5DEB3" }, // since CSS3 { "white", "#ffffff" }, // since CSS1 { "whitesmoke", "#F5F5F5" }, // since CSS3 { "yellow", "#ffff00" }, // since CSS1 { "yellowgreen", "#9ACD32" }, // since CSS3 }; static bool color_map_less( const color_map& a, const color_map& b ) { return std::strcmp( a.name, b.name ) < 0; } // int[3] -> 16進数表記の文字列 std::string MISC::color_to_str( const int* l_rgb ) { // 16進数表記で各色の文字列を連結して格納 char str_value[ CHAR_BUF ]; snprintf( str_value, CHAR_BUF, "#%04x%04x%04x", l_rgb[ 0 ], l_rgb[ 1 ], l_rgb[ 2 ] ); #ifdef _DEBUG std::cout << "MISC::color_to_str" << std::endl; std::cout << l_rgb[0] << ", " << l_rgb[1] << ", " << l_rgb[2] << "->" << str_value << std::endl; #endif return str_value; } // Gdk::RGBA -> 16進数表記の文字列 std::string MISC::color_to_str( const Gdk::RGBA& rgba ) { int l_rgb[ 3 ]; l_rgb[ 0 ] = rgba.get_red_u(); l_rgb[ 1 ] = rgba.get_green_u(); l_rgb[ 2 ] = rgba.get_blue_u(); return color_to_str( l_rgb ); } // htmlカラー (#ffffffなど) -> 16進数表記の文字列 // // 入力文字列は大文字小文字を区別しない // 未知のキーワードや不正な値は変換して返す std::string MISC::htmlcolor_to_str( const std::string& _htmlcolor ) { std::string htmlcolor = MISC::tolower_str( _htmlcolor ); if( htmlcolor[0] != '#' ) { const color_map key{ htmlcolor.c_str(), nullptr }; auto it = std::lower_bound( std::begin( color_names ), std::end( color_names ), key, color_map_less ); // キーワードに一致しないときは空文字列を返す if( it == std::end( color_names ) ) return {}; htmlcolor = it->rgb; } const int digits = ( htmlcolor.size() == 4 ) ? 1 : 2; constexpr int len = 3; int rgb[len]; for( int i = 0; i < len; ++i ) { constexpr int offset = 1; std::string tmpstr = htmlcolor.substr( offset + ( i * digits ), digits ); for( int j = 0; j < ( len - digits ); ++j ) tmpstr.append( tmpstr ); // 不正な値はstrtol()の挙動に従って変換される rgb[i] = std::strtol( tmpstr.c_str(), nullptr, 16 ); } #ifdef _DEBUG std::cout << "MISC::htmlcolor_to_gdkcolor color = " << htmlcolor << " r = " << rgb[ 0 ] << " g = " << rgb[ 1 ] << " b = " << rgb[ 2 ] << std::endl; #endif return color_to_str( rgb ); } // Gdk::RGBA -> int 変換 guint32 MISC::color_to_int( const Gdk::RGBA& color ) { guint32 red = color.get_red_u() >> 8; guint32 green = color.get_green_u() >> 8; guint32 blue = color.get_blue_u() >> 8; return ( red << 24 ) + ( green << 16 ) + ( blue << 8 ); } // 使用可能なフォントの一覧を取得 std::set< std::string > MISC::get_font_families() { std::set< std::string > set_out; Gtk::DrawingArea dummy; const std::vector> list_families = dummy.get_pango_context()->list_families(); for( const auto& family : list_families ) { #ifdef _DEBUG std::cout << family->get_name() << std::endl; #endif set_out.insert( family->get_name() ); } return set_out; } // gtk::entryのフォント名を取得 std::string MISC::get_entry_font() { Gtk::Entry entry; return entry.get_style_context()->get_font().to_string(); } // gtk::entryの文字色を16進数表記の文字列で取得 std::string MISC::get_entry_color_text() { Gtk::Entry entry; auto rgba = entry.get_style_context()->get_color( Gtk::STATE_FLAG_NORMAL ); return color_to_str( rgba ); } // gtk::entryの背景色を16進数表記の文字列で取得 std::string MISC::get_entry_color_base() { Gtk::Entry entry; // REVIEW: get_background_color()が期待通りに背景色を返さない環境があった auto context = entry.get_style_context(); Gdk::RGBA rgba; if( !context->lookup_color( "theme_base_color", rgba ) ) { #ifdef _DEBUG std::cout << "ERROR:MISC::get_entry_color_base() " << "lookup theme_base_color failed." << std::endl; #endif } return color_to_str( rgba ); } // str をクリップボードにコピー void MISC::CopyClipboard( const std::string& str ) { Glib::RefPtr< Gtk::Clipboard > clip = Gtk::Clipboard::get(); clip->set_text( str ); clip = Gtk::Clipboard::get( GDK_SELECTION_PRIMARY ); clip->set_text( str ); } // ウインドウの左上隅を基準としたマウスポインターの座標を取得 Glib::RefPtr MISC::get_pointer_at_window( const Glib::RefPtr& window, int& x, int& y ) { assert( window ); Gdk::ModifierType unused; const auto device = Gdk::Display::get_default()->get_default_seat()->get_pointer(); return window->get_device_position( device, x, y, unused ); } jdim-0.7.0/src/jdlib/miscgtk.h000066400000000000000000000024411417047150700161350ustar00rootroot00000000000000// ライセンス: GPL2 // gtk, gdk 関係の関数 #ifndef _MISCGTK_H #define _MISCGTK_H #include "gtkmmversion.h" #include #include namespace MISC { // int[3] -> 16進数表記の文字列 std::string color_to_str( const int* l_rgb ); // Gdk::RGBA -> 16進数表記の文字列 std::string color_to_str( const Gdk::RGBA& rgba ); // htmlカラー (#ffffffなど) -> 16進数表記の文字列 std::string htmlcolor_to_str( const std::string& htmlcolor ); // Gdk::RGBA -> int 変換 guint32 color_to_int( const Gdk::RGBA& color ); // 使用可能なフォントの一覧を取得 std::set< std::string > get_font_families(); // gtk::entryのフォント名を取得 std::string get_entry_font(); // gtk::entryの文字色を16進数表記の文字列で取得 std::string get_entry_color_text(); // gtk::entryの背景色を16進数表記の文字列で取得 std::string get_entry_color_base(); // str をクリップボードにコピー void CopyClipboard( const std::string& str ); // ウィジェット左上隅を基準としたマウスポインターの座標を取得 Glib::RefPtr get_pointer_at_window( const Glib::RefPtr& window, int& x, int& y ); } #endif jdim-0.7.0/src/jdlib/miscmsg.cpp000066400000000000000000000012231417047150700164660ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "miscmsg.h" #include #include namespace MISC { int id_err = 0; int id_msg = 0; } // // エラー出力 // void MISC::ERRMSG( const std::string& err ) { time_t current; time( ¤t ); char buf[ 256 ]; std::string str_date = ctime_r( ¤t, buf ); str_date.resize( str_date.length() -1 ); std::cerr << str_date << " (ER " << id_err << ") : " << err << std::endl; ++id_err; } // // メッセージ // void MISC::MSG( const std::string& msg ) { std::cout << "(MSG " << id_msg << "): " << msg << std::endl; ++id_msg; } jdim-0.7.0/src/jdlib/miscmsg.h000066400000000000000000000004211417047150700161320ustar00rootroot00000000000000// ライセンス: GPL2 // メッセージ表示関数 #ifndef _MISCMSG_H #define _MISCMSG_H #include namespace MISC { // エラー出力 void ERRMSG( const std::string& err ); // メッセージ void MSG( const std::string& msg ); } #endif jdim-0.7.0/src/jdlib/misctime.cpp000066400000000000000000000076431417047150700166520ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "misctime.h" #include "miscmsg.h" #include #include #include #include #include #include #include // // timeval を str に変換 // std::string MISC::timevaltostr( const struct timeval& tv ) { std::ostringstream sstr; sstr << ( tv.tv_sec >> 16 ) << " " << ( tv.tv_sec & 0xffff ) << " " << tv.tv_usec; return sstr.str(); } // // 時刻を紀元からの経過秒に直す // 日時のフォーマットはHTTPリクエストの形式(RFC 7232 IMF-fixdate) // time_t MISC::datetotime( const std::string& date ) { if( date.empty() ) return 0; std::tm buf{}; std::istringstream iss( date ); iss.imbue( std::locale::classic() ); // Cロケール iss >> std::get_time( &buf, "%a, %d %b %Y %T GMT" ); if( iss.fail() ) return 0; const std::time_t utc = timegm( &buf ); #ifdef _DEBUG std::cout << "MISC::datetotime " << date << " -> " << utc << std::endl; #endif return utc; } // // time_t を月日の文字列に変換 // std::string MISC::timettostr( const time_t time_from, const int mode ) { std::tm tm_tmp; std::ostringstream ss; if( mode == MISC::TIME_NORMAL ){ if( localtime_r( &time_from, &tm_tmp ) ) { ss << std::put_time( &tm_tmp, "%Y/%m/%d %H:%M" ); } } else if( mode == MISC::TIME_NO_YEAR ){ if( localtime_r( &time_from, &tm_tmp ) ) { ss << std::put_time( &tm_tmp, "%m/%d %H:%M" ); } } else if( mode == MISC::TIME_WEEK ){ constexpr char week[][32] = { "日","月","火","水","木","金","土" }; if( localtime_r( &time_from, &tm_tmp ) ) { // ロケール依存の書式(%a)は使わない ss << std::put_time( &tm_tmp, "%Y/%m/%d(" ) << week[ tm_tmp.tm_wday ] << std::put_time( &tm_tmp, ") %H:%M:%S" ); } } else if( mode == MISC::TIME_SECOND ){ if( localtime_r( &time_from, &tm_tmp ) ) { ss << std::put_time( &tm_tmp, "%Y/%m/%d %H:%M:%S" ); } } else if( mode == MISC::TIME_PASSED ){ const std::time_t duration = std::time( nullptr ) - time_from; if( duration < 0 ) ss << "未来"; else if( duration < 60 ) ss << duration << " 秒前"; else if( duration < 60 * 60 ) ss << ( duration / 60 ) << " 分前"; else if( duration < 60 * 60 * 24 ) ss << ( duration / ( 60 * 60 ) ) << " 時間前"; else if( duration < 60 * 60 * 24 * 365 ) ss << ( duration / ( 60 * 60 * 24 ) ) << " 日前"; else ss << ( duration / ( 60 * 60 * 24 * 365 ) ) << " 年前"; } std::string str_ret = ss.str(); #ifdef _DEBUG std::cout << "MISC::timettostr " << time_from << " -> " << str_ret << std::endl; #endif return str_ret; } #ifdef NO_TIMEGM // // timegm // // Solarisの場合はtimegmが存在しないため、代替コードを宣言する(by kohju) // 原典:linux の man timegm // time_t timegm (struct tm *tm) { time_t ret; char *tz; tz = getenv("TZ"); setenv("TZ", "", 1); tzset(); ret = mktime(tm); if (tz) setenv("TZ", tz, 1); else unsetenv("TZ"); tzset(); return ret; } #endif // 実行時間測定用 static std::vector tv_measurement; void MISC::start_measurement( const unsigned int id ) { if( tv_measurement.size() <= id ) { tv_measurement.resize( id + 1 ); } tv_measurement[id] = std::chrono::steady_clock::now(); } long long MISC::measurement( const unsigned int id ) { if( id >= tv_measurement.size() ) return 0; const auto current = std::chrono::steady_clock::now(); const auto duration = current - tv_measurement[id]; tv_measurement[id] = current; return std::chrono::duration_cast( duration ).count(); } jdim-0.7.0/src/jdlib/misctime.h000066400000000000000000000021721417047150700163070ustar00rootroot00000000000000// ライセンス: GPL2 // 時間関係の関数 #ifndef _MISCTIME_H #define _MISCTIME_H #include #include #include namespace MISC { // timetostr のモード enum { TIME_NORMAL = 0, // 年/月/日 時:分 TIME_NO_YEAR, // 月/日 時:分 TIME_WEEK, // 年/月/日(曜日) 時:分:秒 TIME_PASSED, // ~前 TIME_SECOND, // 年/月/日 時:分:秒 // この値が設定ファイルに保存されているので、最後に追加 TIME_NUM }; // timeval を str に変換 std::string timevaltostr( const struct timeval& tv ); // 時刻の文字列を紀元からの経過秒に直す // (例) Tue, 27 Dec 2005 14:28:10 GMT -> 1135693690 time_t datetotime( const std::string& date ); // time_t を月日の文字列に変換 // (例) mode == TIME_NORMAL なら 1135785252 -> 2005/12/29 0:54 std::string timettostr( const time_t time_from, const int mode ); // 実行時間測定用 void start_measurement( const unsigned int id ); long long measurement( const unsigned int id ); } #endif jdim-0.7.0/src/jdlib/misctrip.cpp000066400000000000000000000174011417047150700166630ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #ifdef _DEBUG #include #endif #include "misctrip.h" #include "miscutil.h" #include #include #include #include // crypt #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef USE_OPENSSL #include #include #else // defined USE_GNUTLS #include #include #endif #if __has_include() #include #endif /*--------------------------------------------------------------------*/ // ローカル関数の宣言 // SHA1を計算 std::string create_sha1( const std::string& key ); // トリップを計算(新方式) std::string create_trip_newtype( const std::string& key ); // トリップを計算(従来方式) std::string create_trip_conventional( const std::string& key ); /*--------------------------------------------------------------------*/ /*--------------------------------------------------------------------*/ // SHA1を計算 // // param1: 元となる文字列 // return: SHA1文字列 /*--------------------------------------------------------------------*/ std::string create_sha1( const std::string& key ) { if( key.empty() ) return std::string(); #ifdef USE_OPENSSL constexpr const unsigned int digest_length = SHA_DIGEST_LENGTH; std::array< unsigned char, digest_length > digest; // unsigned char *SHA1( const unsigned char *, size_t, unsigned char * ); SHA1( (const unsigned char *)key.c_str(), key.length(), digest.data() ); #else // defined USE_GNUTLS const unsigned int digest_length = ::gnutls_hash_get_len( GNUTLS_DIG_SHA1 ); std::vector< unsigned char > digest( digest_length ); if( ::gnutls_hash_fast( GNUTLS_DIG_SHA1, key.c_str(), key.size(), digest.data() ) < 0 ) { return std::string{}; } #endif #ifdef _DEBUG std::cout << "create_sha1 : SHA1 = " << std::hex << std::setfill( '0' ); for( unsigned char u : digest ) { std::cout << std::setw( 2 ) << static_cast( u ); } std::cout << std::setfill( ' ' ) << std::dec << std::endl; #endif return std::string( digest.begin(), digest.end() ); } /*--------------------------------------------------------------------*/ // トリップを計算(新方式) 2009/06の仕様 // param1: 元となる文字列 // return: トリップ文字列 /*--------------------------------------------------------------------*/ std::string create_trip_newtype( const std::string& key ) { if( key.empty() ) return std::string(); const size_t key_length = key.length(); // キーは1024文字以内に限定される if( key_length > 1024 ) return std::string(); std::string trip = "???"; // "#"で始まる if( key[0] == '#' ) { // 全体が17〜19文字 ^#[0-9A-Fa-f]{16}[./0-9A-Za-z]{0,2}$ if( key_length > 16 && key_length < 20 ) { // 16進数文字列部分 const std::string hex_part = key.substr( 1, 16 ); char key_binary[17] = { 0 }; // 16進数文字列を全てバイナリに変換出来た [0-9A-Za-z]{16} if( MISC::chrtobin( hex_part.c_str(), key_binary ) == 16 ) { std::string salt; bool is_salt_suitable = true; size_t n; // salt 候補の17〜18文字目を検証 for( n = 17; n < 19 && key[n]; ++n ) { // [./0-9A-Za-z] if( isalnum( key[n] ) != 0 || (unsigned char)( key[n] - 0x2e ) < 2 ) { salt.push_back( key[n] ); } else { is_salt_suitable = false; break; } } // salt が適切(空も含む) if( is_salt_suitable == true ) { // salt に".."を足す salt.append( ".." ); // crypt (key は先頭8文字しか使われない) #ifdef HAVE_CRYPT_R struct crypt_data data; data.initialized = 0; const char* crypted = crypt_r( key_binary, salt.c_str(), &data ); #else const char* crypted = crypt( key_binary, salt.c_str() ); #endif // 末尾から10文字(cryptの戻り値はnullptrでなければ必ず13文字) if( crypted ) trip = std::string( crypted + 3 ); else trip.clear(); } } } } // "$"で始まる else if( key[0] == '$' ) { // 現在は"???"を返す } // SHA1パターン else { const std::string sha1 = create_sha1( key ); if( ! sha1.empty() ) { // BASE64エンコード const std::string encoded = MISC::base64( sha1 ); // 先頭から12文字 trip = encoded.substr( 0, 12 ); std::replace( trip.begin(), trip.end(), '+', '.' ); } } return trip; } /*--------------------------------------------------------------------*/ // トリップを計算(従来方式) 2003/11/15の仕様らしい // 新方式導入に伴い、特殊文字の置換が不要になったようだ // // param1: 元となる文字列 // return: トリップ文字列 /*--------------------------------------------------------------------*/ std::string create_trip_conventional( const std::string& key ) { if( key.empty() ) return std::string(); // key の2,3バイト目を salt として取り出す std::string salt = key.substr( 1, 2 ); // 仕様に合わせて salt を変換 const size_t salt_length = salt.length(); size_t n; for( n = 0; n < salt_length; n++ ) { // 0x2e〜0x7aの範囲にないものは '.'(0x2e) if( (unsigned char)( salt[n] - 0x2E ) > 0x4C ) { salt[n] = 0x2e; } // :;<=>?@ (0x3a〜0x40) は A〜G (0x41〜0x47) else if( (unsigned char)( salt[n] - 0x3A ) < 0x07 ) { salt[n] += 7; } // [\]^_` (0x5b〜0x60) は a〜f (0x61〜0x66) else if( (unsigned char)( salt[n] - 0x5B ) < 0x06 ) { salt[n] += 6; } } // salt の末尾に"H."を足す salt.append( "H." ); // crypt (key は先頭8文字しか使われない) #ifdef HAVE_CRYPT_R struct crypt_data data; data.initialized = 0; const char* crypted = crypt_r( key.c_str(), salt.c_str(), &data ); #else const char* crypted = crypt( key.c_str(), salt.c_str() ); #endif std::string trip; // 末尾10(cryptの戻り値はnullptrでなければ必ず13文字) if( crypted ) trip = std::string( crypted + 3 ); return trip; } /*--------------------------------------------------------------------*/ // トリップを取得 // // param1: 元となる文字列(最初の"#"を含まない。UTF-8 であること) // param2: 書き込む掲示板の文字コード // return: トリップ文字列 /*--------------------------------------------------------------------*/ std::string MISC::get_trip( const std::string& str, const std::string& charset ) { if( str.empty() ) return std::string(); // str の文字コードを UTF-8 から charset に変更して key に代入する std::string key = MISC::Iconv( str, charset, "UTF-8" ); std::string trip; // key が12文字以上の場合は新方式 if( key.length() > 11 ) { trip = create_trip_newtype( key ); } // 従来方式 else { trip = create_trip_conventional( key ); } #ifdef _DEBUG std::cout << "MISC::get_trip : " << str << " -> " << trip << std::endl; #endif return trip; } jdim-0.7.0/src/jdlib/misctrip.h000066400000000000000000000004321417047150700163240ustar00rootroot00000000000000// ライセンス: GPL2 // トリップ関係の関数 #ifndef _MISCTRIP_H #define _MISCTRIP_H #include namespace MISC { // トリップを取得 (SHA1等の新方式対応) std::string get_trip( const std::string& str, const std::string& charset ); } #endif jdim-0.7.0/src/jdlib/miscutil.cpp000066400000000000000000001576331417047150700166760ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "miscutil.h" #include "miscmsg.h" #include "jdiconv.h" #include "jdregex.h" #include "hkana.h" #include "dbtree/spchar_decoder.h" #include "dbtree/node.h" #include #include #include #include #include // // str を "\n" ごとに区切ってlistにして出力 // std::list< std::string > MISC::get_lines( const std::string& str ){ std::list< std::string > lines; size_t i = 0, i2 = 0; while ( ( i2 = str.find( '\n', i ) ) != std::string::npos ){ std::size_t r = 0; if( (i2 >= 1) && (str[ i2 - 1 ] == '\r') ) r = 1; if( i2 - i > 0 ){ lines.push_back( str.substr( i, i2 - i - r ) ); } i = i2 + 1; } // 最後の行 if( i != str.length() +1 ) lines.push_back( str.substr( i ) ); return lines; } // // emacs lisp のリスト型を要素ごとにlistにして出力 // std::list< std::string > MISC::get_elisp_lists( const std::string& str ) { #ifdef _DEBUG std::cout << "MISC::get_elisp_lists\n"; #endif std::list< std::string > lists; std::string str2 = remove_space( str ); const char* data = str2.c_str(); if( data[ 0 ] != '(' ) return lists; std::size_t pos = 1; while( data[ pos ] != '\0' ){ // 空白削除 while( data[ pos ] == ' ' ) ++pos; if( data[ pos ] == '\0' ) break; std::size_t length = 1; // (が現れた if( data[ pos ] == '(' ){ int count = 1; while( data[ pos + length ] != '\0' ){ if( data[ pos + length ] == ')' ) --count; else if( data[ pos + length ] == '(' ) ++count; ++length; if( ! count ) break; } } // 改行 or データが壊れてる else if( data[ pos ] == '\n' || data[ pos ] == ')' ){ ++pos; continue; } // 通常データ else{ //空白 or ) を探す while( data[ pos + length ] != ' ' && data[ pos + length ] != ')' && data[ pos + length ] != '\0' ) ++length; } #ifdef _DEBUG std::cout << "pos = " << pos << " length = " << length << std::endl; #endif lists.push_back( str2.substr( pos, length ) ); pos += length; } #ifdef _DEBUG for( const std::string& s : lists ) std::cout << "[" << s << "]" << std::endl; #endif return lists; } // // strを空白または "" 単位で区切って list で出力 // std::list< std::string > MISC::split_line( const std::string& str ) { constexpr const char* str_space = u8"\u3000"; // "\xE3\x80\x80" 全角スペース constexpr size_t lng_space = 3; std::list< std::string > list_str; size_t i = 0, lng = str.length(); for(;;){ // 空白を取る while( 1 ){ // 半角 if( str[ i ] == ' ' ) ++i; // 全角 else if( str[ i ] == str_space[ 0 ] && str[ i +1 ] == str_space[ 1 ] && str[ i +2 ] == str_space[ 2 ] ) i += lng_space; else break; } // " から始まる ( \"は除く ) bool dquote = false; if( (i < 1 || str[ i -1 ] != '\\') && str[ i ] == '\"' ){ dquote = true; ++i; } // 空白か " を探す std::size_t i2 = i; size_t lng_tmp = 1; while( i2 < lng ){ // " 発見( \"は除く ) if( dquote ){ if( str[ i2 ] == '\"' && str[ i2-1 ] != '\\' ) break; } else{ // 半角 if( str[ i2 ] == ' ' ) break; // 全角 else if( str[ i2 ] == str_space[ 0 ] && str[ i2 +1 ] == str_space[ 1 ] && str[ i2 +2 ] == str_space[ 2 ] ){ lng_tmp = lng_space; break; } } ++i2; } if( i2 - i ) list_str.push_back( str.substr( i, i2 - i ) ); if( i2 >= lng ) break; i = i2 + lng_tmp; } return list_str; } // strを delimで区切って list で出力 std::list< std::string > MISC::StringTokenizer( const std::string& str, const char delim ) { std::list< std::string > list_str; size_t i = 0, i2 = 0, lng = str.length(); for(;;){ while( i2 < lng && str[ i2++ ] != delim ); int tmp = ( i2 >= 1 && ( str[ i2-1 ] == delim || str[ i2 -1 ] == '\n' ) ) ? 1 : 0; if( i2 - i ) list_str.push_back( str.substr( i, i2 - i - tmp ) ); if( i2 >= lng ) break; i = i2; } return list_str; } // // list_inから空白行を除いてリストを返す // std::list< std::string > MISC::remove_nullline_from_list( const std::list< std::string >& list_in ) { std::list< std::string > list_ret; for( const std::string& s : list_in ) { std::string tmp_str = MISC::remove_space( s ); if( ! tmp_str.empty() ) list_ret.push_back( s ); } return list_ret; } // // list_inの各行から前後の空白を除いてリストを返す // std::list< std::string > MISC::remove_space_from_list( const std::list< std::string >& list_in ) { std::list< std::string > list_ret; for( const std::string& s : list_in ) { std::string tmp_str = MISC::remove_space( s ); list_ret.push_back( std::move( tmp_str ) ); } return list_ret; } // // list_inからコメント行(#)を除いてリストを返す // std::list< std::string > MISC::remove_commentline_from_list( const std::list< std::string >& list_in ) { const char commentchr = '#'; std::list< std::string > list_ret; for( const std::string& s : list_in ) { std::string tmp_str = MISC::remove_space( s ); if( tmp_str[0] != commentchr ) list_ret.push_back( s ); } return list_ret; } // // 空白と""で区切られた str_in の文字列をリストにして出力 // // \"は " に置換される // // (例) "aaa" "bbb" "\"ccc\"" → aaa と bbb と "ccc" // std::list< std::string > MISC::strtolist( const std::string& str_in ) { std::list< std::string > list_ret; std::list list_tmp = MISC::split_line( str_in ); for( const std::string& s : list_tmp ) { if( ! s.empty() ) list_ret.push_back( MISC::recover_quot( s ) ); } return list_ret; } // // list_in の文字列リストを空白と""で区切ってストリングにして出力 // // "は \" に置換される // // (例) "aaa" "bbb" "\"ccc\"" // std::string MISC::listtostr( const std::list< std::string >& list_in ) { std::string str_out; for( const std::string& s : list_in ) { if( ! s.empty() ) str_out.append( " \"" + MISC::replace_quot( s ) + "\"" ); } return str_out; } // // list_in から空文字列を除き suffix でつなげて返す // 他のプログラミング言語にあるjoin()と動作が異なり返り値の末尾にもsuffixが付く // // (例) {"aa", "", "bb", "cc"}, '!' -> "aa!bb!cc!" // std::string MISC::concat_with_suffix( const std::list& list_in, char suffix ) { std::string str_out; for( const std::string& s : list_in ) { if( s.empty() ) continue; str_out.append( s ); str_out.push_back( suffix ); } return str_out; } // // strの前後の空白削除 // std::string MISC::remove_space( const std::string& str ) { constexpr const char* str_space = u8"\u3000"; // "\xE3\x80\x80" 全角スペース constexpr size_t lng_space = 3; size_t lng = str.length(); if( lng == 0 ) return str; if( str.find( ' ' ) == std::string::npos ) return str; // 前 size_t i = 0; while( i < lng ){ // 半角 if( str[ i ] == ' ' ) ++i; // 全角 else if( str[ i ] == str_space[ 0 ] && str[ i +1 ] == str_space[ 1 ] && str[ i +2 ] == str_space[ 2 ] ) i += lng_space; else break; } if (i >= lng) return ""; // 後 size_t i2 = lng -1; while( 1 ){ // 半角 if( str[ i2 ] == ' ' ) --i2; // 全角 else if( i2 +1 >= lng_space && str[ i2 - lng_space +1 ] == str_space[ 0 ] && str[ i2 - lng_space +2 ] == str_space[ 1 ] && str[ i2 - lng_space +3 ] == str_space[ 2 ] ) i2 -= lng_space; else break; } return str.substr( i, i2 - i + 1 ); } // // str前後の改行、タブ、スペースを削除 // std::string MISC::remove_spaces( const std::string& str ) { if( str.empty() ) return std::string(); size_t l = 0, r = str.length(); while( l < r && ( str[l] == '\n' || str[l] == '\r' || str[l] == '\t' || str[l] == ' ' ) ) ++l; // 最後の文字の位置は文字数より1少ない size_t p = r - 1; while( p > 0 && ( str[p] == '\n' || str[p] == '\r' || str[p] == '\t' || str[p] == ' ' ) ){ --p; --r; } return str.substr( l, r - l ); } // // str1からstr2で示された文字列を除く // std::string MISC::remove_str( const std::string& str1, const std::string& str2 ) { return MISC::replace_str( str1, str2, "" ); } // // start 〜 end の範囲をstrから取り除く ( /* コメント */ など ) // std::string MISC::remove_str( const std::string& str, const std::string& start, const std::string& end ) { std::string str_out = str; size_t l_pos = 0, r_pos = 0; const size_t start_length = start.length(); const size_t end_length = end.length(); while( ( l_pos = str_out.find( start, l_pos ) ) != std::string::npos && ( r_pos = str_out.find( end, l_pos + start_length ) ) != std::string::npos ) { str_out.erase( l_pos, r_pos - l_pos + end_length ); } return str_out; } // // 正規表現を使ってstr1からqueryで示された文字列を除く // std::string MISC::remove_str_regex( const std::string& str1, const std::string& query ) { JDLIB::Regex regex; const size_t offset = 0; const bool icase = false; const bool newline = true; const bool usemigemo = false; const bool wchar = false; if( ! regex.exec( query, str1, offset, icase, newline, usemigemo, wchar ) ) return std::string(); return MISC::remove_str( str1, regex.str( 0 ) ); } // // str1, str2 に囲まれた文字列を切り出す // std::string MISC::cut_str( const std::string& str, const std::string& str1, const std::string& str2 ) { size_t i = str.find( str1 ); if( i == std::string::npos ) return std::string(); i += str1.length(); size_t i2 = str.find( str2, i ); if( i2 == std::string::npos ) return std::string(); return str.substr( i, i2 - i ); } // // str1 を str2 に置き換え // std::string MISC::replace_str( const std::string& str, const std::string& str1, const std::string& str2 ) { size_t i, pos = 0; if( ( i = str.find( str1 , pos ) ) == std::string::npos ) return str; std::string str_out; str_out.reserve( str.length() ); do { str_out.append( str, pos, ( i - pos ) ); str_out.append( str2 ); pos = i + str1.length(); } while( ( i = str.find( str1 , pos ) ) != std::string::npos ); str_out.append( str, pos, str.length() ); return str_out; } // // list_inから str1 を str2 に置き換えてリストを返す // std::list< std::string > MISC::replace_str_list( const std::list< std::string >& list_in, const std::string& str1, const std::string& str2 ) { std::list< std::string > list_out; std::transform( list_in.cbegin(), list_in.cend(), std::back_inserter( list_out ), [&]( const std::string& s ) { return replace_str( s, str1, str2 ); } ); return list_out; } // // str_in に含まれる改行文字を replace に置き換え // std::string MISC::replace_newlines_to_str( const std::string& str_in, const std::string& replace ) { if( str_in.empty() || replace.empty() ) return str_in; std::string str_out; str_out.reserve( str_in.length() ); size_t pos = 0, found = 0; while( ( found = str_in.find_first_of( "\r\n", pos ) ) != std::string::npos ) { str_out.append( str_in, pos, ( found - pos ) ); str_out.append( replace ); pos = found + 1; if( str_in[ found ] == '\r' && str_in[ found + 1 ] == '\n' ) ++pos; } str_out.append( str_in, pos, str_in.length() ); return str_out; } // // " を \" に置き換え // std::string MISC::replace_quot( const std::string& str ) { return MISC::replace_str( str, "\"", "\\\"" ); } // // \" を " に置き換え // std::string MISC::recover_quot( const std::string& str ) { return MISC::replace_str( str, "\\\"", "\"" ); } // // str 中に含まれている str2 の 数を返す // int MISC::count_str( const std::string& str, const std::string& str2 ) { int count = 0; size_t found, pos = 0; while( ( found = str.find( str2, pos ) ) != std::string::npos ) { ++count; pos = found + 1; } return count; } // // 文字列(utf-8も) -> 整数変換 // // (例) "123" -> 123 // // 入力: // str // // 出力: // dig: 桁数、0なら失敗 // n : str から何バイト読み取ったか // // 戻り値: 数値 // int MISC::str_to_uint( const char* str, size_t& dig, size_t& n ) { int out = 0; dig = 0; n = 0; while( *str != '\0' ){ const unsigned char in = (*str); if( '0' <= in && in <= '9' ){ out = out*10 + ( in - '0' ); ++dig; ++str; ++n; } else{ const auto in2 = static_cast( in == 0xEF ? *( str + 1 ) : 0 ); const auto in3 = static_cast( in2 == 0xBC ? *( str + 2 ) : 0 ); // utf-8 if( 0x90 <= in3 && in3 <= 0x99 ){ out = out*10 + ( in3 - 0x90 ); ++dig; str += 3; n += 3; } else break; } } return out; } // // listで指定した数字を文字に変換 // std::string MISC::intlisttostr( const std::list< int >& list_num ) { assert( ! list_num.empty() ); std::ostringstream comment; std::list < int >::const_iterator it = list_num.begin(); bool comma = false; int num_from = *it; int num_to = -1; // -1 は番兵 int i = 0; // 連番判定に使う for(;;){ ++i; ++it; const bool loop_end{ it == list_num.end() }; const int num{ loop_end ? -1 : *it }; if( num_from + i != num || loop_end ) { if( comma ) comment << ","; comment << num_from; if( num_to != -1 ) comment << "-" << num_to; num_from = num; num_to = -1; i = 0; comma = true; if( loop_end ) break; } // 数字が連番のときは記録しておく else num_to = num; } return comment.str(); } // // 16進数表記文字をバイナリに変換する( 例 "E38182" -> 0xE38182 ) // // 出力 : char_out // 戻り値: 変換に成功した chr_in のバイト数 // size_t MISC::chrtobin( const char* chr_in, char* chr_out ) { if( ! chr_in ) return 0; const size_t chr_in_length = strlen( chr_in ); size_t a, b; for( a = 0, b = a; a < chr_in_length; ++a ) { unsigned char chr = chr_in[a]; chr_out[b] <<= 4; // 0(0x30)〜9(0x39) if( (unsigned char)( chr - 0x30 ) < 10 ) chr_out[b] |= chr - 0x30; // A(0x41)〜F(0x46) else if( (unsigned char)( chr - 0x41 ) < 6 ) chr_out[b] |= chr - 0x37; // a(0x61)〜f(0x66) else if( (unsigned char)( chr - 0x61 ) < 6 ) chr_out[b] |= chr - 0x57; // その他 else break; if( a % 2 != 0 ) ++b; } return a; } // // strが半角でmaxsize文字を超えたらカットして後ろに...を付ける // std::string MISC::cut_str( const std::string& str, const unsigned int maxsize ) { std::string outstr = str; unsigned int pos, lng_str; int byte = 0; const size_t outstr_length = outstr.length(); for( pos = 0, lng_str = 0; pos < outstr_length; pos += byte ){ MISC::utf8toucs2( outstr.c_str()+pos, byte ); if( byte > 1 ) lng_str += 2; else ++lng_str; if( lng_str >= maxsize ) break; } // カットしたら"..."をつける if( pos != outstr_length ) outstr = outstr.substr( 0, pos ) + "..."; return outstr; } // // 正規表現のメタ文字が含まれているか // // escape == true ならエスケープを考慮 (例) escape == true なら \+ → \+ のまま、falseなら \+ → \\\+ // #define REGEX_METACHARS ".+*?^$|{}[]()\\" bool MISC::has_regex_metachar( const std::string& str, const bool escape ) { const char metachars[] = REGEX_METACHARS; const size_t str_length = str.length(); for( size_t pos = 0; pos < str_length; ++pos ){ if( escape && str[ pos ] == '\\' ){ int i = 0; while( metachars[ i ] != '\0' ){ if( str[ pos + 1 ] == metachars[ i ] ) break; ++i; } if( metachars[ i ] == '\0' ) return true; ++pos; } else{ int i = 0; while( metachars[ i ] != '\0' ){ if( str[ pos ] == metachars[ i ] ) return true; ++i; } } } return false; } // // 正規表現のメタ文字をエスケープ // // escape == true ならエスケープを考慮 (例) escape == true なら \+ → \+ のまま、falseなら \+ → \\\+ // std::string MISC::regex_escape( const std::string& str, const bool escape ) { if( ! has_regex_metachar( str, escape ) ) return str; #ifdef _DEBUG std::cout << "MISC::regex_escape" << std::endl; #endif std::string str_out; const char metachars[] = REGEX_METACHARS; const size_t str_length = str.length(); for( size_t pos = 0; pos < str_length; ++pos ){ if( escape && str[ pos ] == '\\' ){ int i = 0; while( metachars[ i ] != '\0' ){ if( str[ pos + 1 ] == metachars[ i ] ) break; ++i; } if( metachars[ i ] == '\0' ) str_out += '\\'; else{ str_out += str[ pos ]; ++pos; } } else{ int i = 0; while( metachars[ i ] != '\0' ){ if( str[ pos ] == metachars[ i ] ){ str_out += '\\'; break; } ++i; } } str_out += str[ pos ]; } #ifdef _DEBUG std::cout << str << " -> " << str_out << std::endl; #endif return str_out; } // // 正規表現のメタ文字をアンエスケープ // std::string MISC::regex_unescape( const std::string& str ) { #ifdef _DEBUG std::cout << "MISC::regex_unescape" << std::endl; #endif std::string str_out; const char metachars[] = REGEX_METACHARS; const size_t str_length = str.length(); for( size_t pos = 0; pos < str_length; ++pos ){ if( str[ pos ] == '\\' ){ int i = 0; while( metachars[ i ] != '\0' ){ if( str[ pos + 1 ] == metachars[ i ] ){ ++pos; break; } ++i; } } str_out += str[ pos ]; } #ifdef _DEBUG std::cout << str << " -> " << str_out << std::endl; #endif return str_out; } // // HTMLエスケープ // // include_url : URL中でもエスケープする( デフォルト = true ) // std::string MISC::html_escape( const std::string& str, const bool include_url ) { if( str.empty() ) return str; bool is_url = false; int scheme = SCHEME_NONE; std::string str_out; const size_t str_length = str.length(); for( size_t pos = 0; pos < str_length; ++pos ) { char tmpchar = str.c_str()[ pos ]; // URL中はエスケープしない場合 if( ! include_url ) { // URLとして扱うかどうか // エスケープには影響がないので loose_url としておく if( scheme != SCHEME_NONE ) is_url = is_url_char( str.c_str() + pos, true ); // URLスキームが含まれているか判別 int len = 0; if( ! is_url ) scheme = is_url_scheme( str.c_str() + pos, &len ); // URLスキームが含まれていた場合は文字数分進めてループに戻る if( len > 0 ) { str_out += str.substr( pos, len ); pos += len - 1; // あとで ++pos される分を引く continue; } } // include_url = false でURL中ならエスケープしない if( is_url ) str_out += tmpchar; else if( tmpchar == '&' ) { const int bufsize = 64; char out_char[ bufsize ]; int n_in, n_out; const int type = DBTREE::decode_char( str.c_str() + pos, n_in, out_char, n_out, false ); if( type == DBTREE::NODE_NONE ) str_out += "&"; else str_out += tmpchar; } else if( tmpchar == '\"' ) str_out += """; else if( tmpchar == '<' ) str_out += "<"; else if( tmpchar == '>' ) str_out += ">"; else str_out += tmpchar; } #ifdef _DEBUG if( str != str_out ){ std::cout << "MISC::html_escape\nstr = " << str << std::endl << "out = " << str_out << std::endl; } #endif return str_out; } // // HTMLアンエスケープ // std::string MISC::html_unescape( const std::string& str ) { if( str.empty() ) return str; if( str.find( '&' ) == std::string::npos ) return str; std::string str_out; const size_t str_length = str.length(); for( size_t pos = 0; pos < str_length; ++pos ){ const int bufsize = 64; char out_char[ bufsize ]; int n_in, n_out; DBTREE::decode_char( str.c_str() + pos, n_in, out_char, n_out, false ); if( n_out ){ str_out += out_char; pos += n_in -1; } else str_out += str.c_str()[ pos ]; } #ifdef _DEBUG if( str != str_out ){ std::cout << "MISC::html_unescape\nstr = " << str << std::endl << "out = " << str_out << std::endl; } #endif return str_out; } // // 文字参照のデコード内部処理 // // strは'&'で始まる文字列を指定すること // completely = true の時は'"' '&' '<' '>'も含めて変換する // static std::string chref_decode_one( const char* str, int& n_in, const bool completely ) { std::string out_char( 15u, '\0' ); int n_out; const int type = DBTREE::decode_char( str, n_in, &*out_char.begin(), n_out, false ); out_char.resize( n_out ); // 改行、タブ、スペースの処理 if( type != DBTREE::NODE_NONE && ( out_char[0] == ' ' || out_char[0] == '\n' ) ) { out_char.assign( 1u, ' ' ); } // 変換できない文字 else if( type == DBTREE::NODE_NONE ) { out_char.assign( 1u, *str ); n_in = 1; } // エスケープする文字の場合は元に戻す else if( ! completely && n_out == 1 ) { switch( out_char[0] ) { case '"': out_char.assign( """ ); break; case '&': out_char.assign( "&" ); break; case '<': out_char.assign( "<" ); break; case '>': out_char.assign( ">" ); break; default: break; } } return out_char; } // // HTMLの文字参照をデコード // // completely = true の時は'"' '&' '<' '>'もデコードする // std::string MISC::chref_decode( const char* str, const int lng, const bool completely ) { std::string str_out; if( lng <= 0 ) return str_out; if( std::memchr( str, '&', lng ) == nullptr ) { str_out.assign( str, lng ); return str_out; } const char* pos = str; const char* pos_end = str + lng; while( pos < pos_end ) { // '&' までコピーする while( *pos != '&' && pos < pos_end ) str_out.push_back( *pos++ ); if( pos >= pos_end ) break; // 文字参照のデコード int n_in; str_out.append( chref_decode_one( pos, n_in, completely ) ); pos += n_in; } return str_out; } // // URL中のスキームを判別する // // 戻り値 : スキームタイプ // length : "http://"等の文字数 // int MISC::is_url_scheme_impl( const char* str_in, int* length ) { int scheme = SCHEME_NONE; int len = 0; // http https if( MISC::starts_with( str_in, "http" ) ) { scheme = SCHEME_HTTP; len = 4; if( *( str_in + len ) == 's' ) ++len; } // ftp else if( MISC::starts_with( str_in, "ftp" ) ) { scheme = SCHEME_FTP; len = 3; } // ttp ttps else if( MISC::starts_with( str_in, "ttp" ) ) { scheme = SCHEME_TTP; len = 3; if( *( str_in + len ) == 's' ) ++len; } // tp tps else if( MISC::starts_with( str_in, "tp" ) ) { scheme = SCHEME_TP; len = 2; if( *( str_in + len ) == 's' ) ++len; } // sssp else if( MISC::starts_with( str_in, "sssp" ) ) { if( MISC::starts_with( str_in + 7, "img.5ch" ) || MISC::starts_with( str_in + 7, "img.2ch" ) ) { scheme = SCHEME_SSSP; } else{ // XXX img.[25]ch以外のアドレスはHTTPスキームにする scheme = SCHEME_HTTP; } len = 4; } // 各スキーム後に続く共通の"://" if( MISC::starts_with( str_in + len, "://" ) ) { len += 3; if( length ) *length = len; } else scheme = SCHEME_NONE; return scheme; } // // URLとして扱う文字かどうか判別する // // 基本 : 「!#$%&'()*+,-./0-9:;=?@A-Z_a-z~」 // 拡張 : 「[]^|」 // // "RFC 3986" : http://www.ietf.org/rfc/rfc3986.txt // "RFC 2396" : http://www.ietf.org/rfc/rfc2396.txt // static const char s_url_char[ 128 ] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // ! " # $ % & ' ( ) * + , - . / 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0 1 2 3 4 5 6 7 8 9 : ; < = > ? 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, // @ A B C D E F G H I J K L M N O 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // P Q R S T U V W X Y Z [ \ ] ^ _ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0, 2, 2, 1, // ` a b c d e f g h i j k l m n o 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // p q r s t u v w x y z { | } ~ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 2, 0, 1, 0, }; bool MISC::is_url_char( const char* str_in, const bool loose_url ) { unsigned char c = (unsigned char)(*str_in); // 128以上のテーブルはないので先に判定 if( c & 0x80 ) return false; // 基本 if( s_url_char[ c ] == 1 ) return true; // 拡張 // RFC 3986(2.2.)では"[]"が予約文字として定義されているが // RFC 2396(2.4.3.)では除外されていて、普通にURLとして扱う // と問題がありそうなので"loose_url"の扱いにしておく。 if( loose_url && s_url_char[ c ] == 2 ) return true; return false; } // // URLデコード // std::string MISC::url_decode( const std::string& url ) { if( url.empty() ) return std::string(); const size_t url_length = url.length(); std::vector< char > decoded( url_length + 1, '\0' ); unsigned int a, b; for( a = 0, b = a; a < url_length; ++a, ++b ) { if( url[a] == '%' && ( a + 2 < url_length ) ) { char src[3] = { url[ a + 1 ], url[ a + 2 ], '\0' }; char tmp[3] = { '\0', '\0', '\0' }; if( chrtobin( src, tmp ) == 2 ) { // '%4A' など、2文字が変換できていること decoded[b] = *tmp; a += 2; } else { // 変換失敗は、単なる '%' 文字として扱う decoded[b] = url[a]; } } else if( url[a] == '+' ) { decoded[b] = ' '; } else { decoded[b] = url[a]; } } return decoded.data(); } // // url エンコード // std::string MISC::url_encode( const char* str, const size_t n ) { if( str[ n ] != '\0' ){ ERRMSG( "url_encode : invalid input." ); return std::string(); } std::string str_encoded; for( size_t i = 0; i < n; i++ ){ unsigned char c = str[ i ]; const int tmplng = 16; char str_tmp[ tmplng ]; if( ! ( 'a' <= c && c <= 'z' ) && ! ( 'A' <= c && c <= 'Z' ) && ! ( '0' <= c && c <= '9' ) && ( c != '*' ) && ( c != '-' ) && ( c != '.' ) && ( c != '@' ) && ( c != '_' )){ std::snprintf( str_tmp, tmplng, "%%%02X", c ); } else { str_tmp[ 0 ] = c; str_tmp[ 1 ] = '\0'; } str_encoded += str_tmp; } return str_encoded; } std::string MISC::url_encode( const std::string& str ) { return url_encode( str.c_str(), str.length() ); } // // 文字コード変換して url エンコード // // str は UTF-8 であること // std::string MISC::charset_url_encode( const std::string& str, const std::string& charset ) { if( charset.empty() || charset == "UTF-8" ) return MISC::url_encode( str.c_str(), str.length() ); const std::string str_enc = MISC::Iconv( str, charset, "UTF-8" ); return MISC::url_encode( str_enc.c_str(), str_enc.length() ); } // // 文字コード変換して url エンコード // // ただし半角スペースのところを+に置き換えて区切る // std::string MISC::charset_url_encode_split( const std::string& str, const std::string& charset ) { std::list< std::string > list_str = MISC::split_line( str ); std::string str_out; for( const std::string& s : list_str ) { if( ! str_out.empty() ) str_out.push_back( '+' ); str_out.append( MISC::charset_url_encode( s, charset ) ); } return str_out; } // // BASE64 // std::string MISC::base64( const std::string& str ) { constexpr const char table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; int lng = str.length(); std::string out; out.reserve( lng * 2 ); std::string data = str + "\0\0\0\0"; for( int i = 0; i < lng; i += 3 ){ unsigned char* cstr = (unsigned char*)( data.c_str() + i ); unsigned char key[ 4 ]; key[ 0 ] = (*cstr) >> 2; key[ 1 ] = ( ( (*cstr) << 4 ) + ( (*(cstr+1)) >> 4 ) ); key[ 2 ] = ( ( (*(cstr+1)) << 2 ) + ( (*(cstr+2)) >> 6 ) ); key[ 3 ] = *(cstr+2); for( int j = 0; j < 4; ++j ){ key[ j ] &= 0x3f; out += table[ key[ j ] ]; } } if( lng % 3 == 1 ){ out[ out.length()-2 ] = '='; out[ out.length()-1 ] = '='; } else if( lng % 3 == 2 ){ out[ out.length()-1 ] = '='; } #ifdef _DEBUG std::cout << "MISC::base64 " << str << " -> " << out << std::endl; #endif return out; } // // 文字コードを coding_from から coding_to に変換 // // 遅いので連続的な処理が必要な時は使わないこと // std::string MISC::Iconv( const std::string& str, const std::string& coding_to, const std::string& coding_from ) { if( coding_from == coding_to ) return str; std::string str_bk = str; JDLIB::Iconv libiconv( coding_to, coding_from ); int byte_out; std::string str_enc = libiconv.convert( &*str_bk.begin(), str_bk.size(), byte_out ); return str_enc; } // // 「&#数字;」形式の数字参照文字列の中の「数字」部分の文字列長 // // in_char: 入力文字列、in_char[0] == "&" && in_char[1] == "#" であること // offset : 開始位置が返る // // 戻り値 : 「&#数字;」の中の数字の文字列の長さ、変換出来ないときは -1 // // 例 : ✏ なら 戻り値 = 4、 offset = 2 // int MISC::spchar_number_ln( const char* in_char, int& offset ) { int lng = 0; offset = 2; // offset == 2 なら 10 進数、3 なら16進数 if( in_char[ offset ] == 'x' || in_char[ offset ] == 'X' ) ++offset; // UCS2の2バイトの範囲でデコードするので最大65535 // デコードするとき「;」で終端されていなくてもよい // デコード可能かチェック // 10 進数 if( offset == 2 ){ // 最大7桁 (􏿿) for( lng = 0; lng <= 7; lng++ ){ if( in_char[ offset + lng ] < '0' || in_char[ offset + lng ] > '9' ) break; } // 桁数チェック if( lng == 0 || lng == 8 ) return -1; } // 16 進数 else{ // 最大6桁 (􏿿) for( lng = 0; lng <= 6; lng++ ){ if( ! ( ( in_char[ offset + lng ] >= '0' && in_char[ offset + lng ] <= '9' ) || ( in_char[ offset + lng ] >= 'a' && in_char[ offset + lng ] <= 'f' ) || ( in_char[ offset + lng ] >= 'A' && in_char[ offset + lng ] <= 'F' ) ) ) break; } // 桁数チェック if( lng == 0 || lng == 7 ) return -1; } return lng; } // 特定の変換が必要なコードポイントをチェックする static int transform_7f_9f( int raw_point ) { switch( raw_point ) { case 0x80: return 0x20AC; // EURO SIGN (€) case 0x82: return 0x201A; // SINGLE LOW-9 QUOTATION MARK (‚) case 0x83: return 0x0192; // LATIN SMALL LETTER F WITH HOOK (ƒ) case 0x84: return 0x201E; // DOUBLE LOW-9 QUOTATION MARK („) case 0x85: return 0x2026; // HORIZONTAL ELLIPSIS (…) case 0x86: return 0x2020; // DAGGER (†) case 0x87: return 0x2021; // DOUBLE DAGGER (‡) case 0x88: return 0x02C6; // MODIFIER LETTER CIRCUMFLEX ACCENT (ˆ) case 0x89: return 0x2030; // PER MILLE SIGN (‰) case 0x8A: return 0x0160; // LATIN CAPITAL LETTER S WITH CARON (Š) case 0x8B: return 0x2039; // SINGLE LEFT-POINTING ANGLE QUOTATION MARK (‹) case 0x8C: return 0x0152; // LATIN CAPITAL LIGATURE OE (Œ) case 0x8E: return 0x017D; // LATIN CAPITAL LETTER Z WITH CARON (Ž) case 0x91: return 0x2018; // LEFT SINGLE QUOTATION MARK (‘) case 0x92: return 0x2019; // RIGHT SINGLE QUOTATION MARK (’) case 0x93: return 0x201C; // LEFT DOUBLE QUOTATION MARK (“) case 0x94: return 0x201D; // RIGHT DOUBLE QUOTATION MARK (”) case 0x95: return 0x2022; // BULLET (•) case 0x96: return 0x2013; // EN DASH (–) case 0x97: return 0x2014; // EM DASH (—) case 0x98: return 0x02DC; // SMALL TILDE (˜) case 0x99: return 0x2122; // TRADE MARK SIGN (™) case 0x9A: return 0x0161; // LATIN SMALL LETTER S WITH CARON (š) case 0x9B: return 0x203A; // SINGLE RIGHT-POINTING ANGLE QUOTATION MARK (›) case 0x9C: return 0x0153; // LATIN SMALL LIGATURE OE (œ) case 0x9E: return 0x017E; // LATIN SMALL LETTER Z WITH CARON (ž) case 0x9F: return 0x0178; // LATIN CAPITAL LETTER Y WITH DIAERESIS (Ÿ) default: return 0xFFFD; // REPLACEMENT CHARACTER } } // // コードポイントが数値文字参照としてはエラーなら規定の値へ変換する // 例えばサロゲートペアは'REPLACEMENT CHARACTER' (U+FFFD)を返す // // 参考文献 : Numeric character reference end state (HTML 5.3) // https://www.w3.org/TR/html53/syntax.html#numeric-character-reference-end-state // static int sanitize_numeric_character_reference( int raw_point ) { // NOTE: 記号や絵文字を速やかに処理できるよう順番が組まれている bool parse_error = false; // 基本多言語面(BMP)をおおまかにチェック if( 0x009F < raw_point && raw_point < 0xD800 ) return raw_point; // 特定のbitパターンの非文字と符号空間の範囲をチェック else if( ( raw_point & 0xFFFE ) == 0xFFFE || raw_point > 0x10FFFF ) parse_error = true; // bitパターンを除いたらBMPの一部と追加多言語面(SMP)以降をチェック else if( 0xFDEF < raw_point ) return raw_point; // サロゲートペアはエラー else if( 0xD800 <= raw_point && raw_point <= 0xDFFF ) parse_error = true; // 基本ラテン文字をチェック else if( 0x001F < raw_point && raw_point < 0x007F ) return raw_point; // 特定の変換が必要なコードポイントをチェック else if( 0x007F <= raw_point && raw_point <= 0x009F ) return transform_7f_9f( raw_point ); // 最後に制御文字と非文字をチェック // サロゲートペアは他の値より入力される可能性が高いので処理を優先している else if( raw_point <= 0x0008 // Control character || raw_point == 0x000B // Control character (Vertical tab) || ( 0x000D <= raw_point && raw_point <= 0x001F ) // Control character || ( 0xFDD0 <= raw_point // && raw_point <= 0xFDEF の境界は上でチェックしているので不要 ) // Noncharacters ) { parse_error = true; } if( parse_error ) { #ifdef _DEBUG std::cout << "Parse error for numeric character reference... " << raw_point << std::endl; #endif return 0xFFFD; // REPLACEMENT CHARACTER } return raw_point; } // // 「&#数字;」形式の数字参照文字列を数字(int)に変換する // // 最初に MISC::spchar_number_ln() を呼び出して offset と lng を取得すること // // in_char: 入力文字列、in_char[0] == "&" && in_char[1] == "#" であること // offset : spchar_number_ln() の戻り値 // lng : spchar_number_ln() の戻り値 // // 戻り値 : 「&#数字;」の中の数字(int型) // int MISC::decode_spchar_number( const char* in_char, const int offset, const int lng ) { char str_num[ 16 ]; memcpy( str_num, in_char + offset, lng ); str_num[ lng ] = '\0'; #ifdef _DEBUG std::cout << "MISC::decode_spchar_number offset = " << offset << " lng = " << lng << " str = " << str_num << std::endl; #endif int num = 0; if( offset == 2 ) num = atoi( str_num ); else num = strtol( str_num, nullptr, 16 ); return sanitize_numeric_character_reference( num ); } // // str に含まれる「&#数字;」形式の数字参照文字列を全てユニーコード文字に変換する // std::string MISC::decode_spchar_number( const std::string& str ) { std::string str_out; const size_t str_length = str.length(); for( size_t i = 0; i < str_length ; ++i ){ if( str[ i ] == '&' && str[ i + 1 ] == '#' ){ int offset; const int lng = MISC::spchar_number_ln( str.c_str()+i, offset ); if( lng == -1 ){ str_out += str[ i ]; continue; } const int num = MISC::decode_spchar_number( str.c_str()+i, offset, lng ); char out_char[ 64 ]; const int n_out = MISC::ucs2toutf8( num, out_char ); if( ! n_out ){ str_out += str[ i ]; continue; } for( int j = 0; j < n_out; ++j ) str_out += out_char[ j ]; i += offset + lng; } else str_out += str[ i ]; } return str_out; } // // utf-8 -> ucs2 変換 // // 入力 : utfstr 入力文字 (UTF-8) // // 出力 : byte 長さ(バイト) utfstr が ascii なら 1, UTF-8 なら 2 or 3 or 4 を入れて返す // // 戻り値 : ucs2 // int MISC::utf8toucs2( const char* utfstr, int& byte ) { int ucs2 = 0; byte = 0; if( utfstr[ 0 ] == '\0' ) return '\0'; else if( ( ( unsigned char ) utfstr[ 0 ] & 0xf0 ) == 0xe0 ){ byte = 3; ucs2 = utfstr[ 0 ] & 0x0f; ucs2 = ( ucs2 << 6 ) + ( utfstr[ 1 ] & 0x3f ); ucs2 = ( ucs2 << 6 ) + ( utfstr[ 2 ] & 0x3f ); } else if( ( ( unsigned char ) utfstr[ 0 ] & 0x80 ) == 0 ){ // ascii byte = 1; ucs2 = utfstr[ 0 ]; } else if( ( ( unsigned char ) utfstr[ 0 ] & 0xe0 ) == 0xc0 ){ byte = 2; ucs2 = utfstr[ 0 ] & 0x1f; ucs2 = ( ucs2 << 6 ) + ( utfstr[ 1 ] & 0x3f ); } else if( ( ( unsigned char ) utfstr[ 0 ] & 0xf8 ) == 0xf0 ){ byte = 4; ucs2 = utfstr[ 0 ] & 0x07; ucs2 = ( ucs2 << 6 ) + ( utfstr[ 1 ] & 0x3f ); ucs2 = ( ucs2 << 6 ) + ( utfstr[ 2 ] & 0x3f ); ucs2 = ( ucs2 << 6 ) + ( utfstr[ 3 ] & 0x3f ); } // 不正なUTF8 else { byte = 1; ucs2 = utfstr[ 0 ]; ERRMSG( "MISC::utf8toucs2 : invalid code = " + std::to_string( ucs2 ) ); } return ucs2; } // // ucs2 -> utf8 変換 // // 出力 : utfstr 変換後の文字 // // 戻り値 : バイト数 // int MISC::ucs2toutf8( const int ucs2, char* utfstr ) { int byte = 0; if( ucs2 <= 0x7f ){ // ascii byte = 1; utfstr[ 0 ] = ucs2; } else if( ucs2 <= 0x07ff ){ byte = 2; utfstr[ 0 ] = ( 0xc0 ) + ( ucs2 >> 6 ); utfstr[ 1 ] = ( 0x80 ) + ( ucs2 & 0x3f ); } else if( ucs2 <= 0xffff){ byte = 3; utfstr[ 0 ] = ( 0xe0 ) + ( ucs2 >> 12 ); utfstr[ 1 ] = ( 0x80 ) + ( ( ucs2 >>6 ) & 0x3f ); utfstr[ 2 ] = ( 0x80 ) + ( ucs2 & 0x3f ); } else{ byte = 4; utfstr[ 0 ] = ( 0xf0 ) + ( ucs2 >> 18 ); utfstr[ 1 ] = ( 0x80 ) + ( ( ucs2 >>12 ) & 0x3f ); utfstr[ 2 ] = ( 0x80 ) + ( ( ucs2 >>6 ) & 0x3f ); utfstr[ 3 ] = ( 0x80 ) + ( ucs2 & 0x3f ); } utfstr[ byte ] = 0; return byte; } // // ucs2 の種類 // int MISC::get_ucs2mode( const int ucs2 ) { if( ucs2 >= 0x0000 && ucs2 <= 0x007f ) return UCS2MODE_BASIC_LATIN; if( ucs2 >= 0x3040 && ucs2 <= 0x309f ) return UCS2MODE_HIRA; if( ucs2 >= 0x30a0 && ucs2 <= 0x30ff ) return UCS2MODE_KATA; return UCS2MODE_OTHER; } // // WAVEDASHなどのWindows系UTF-8文字をUnix系文字と相互変換 // std::string MISC::utf8_fix_wavedash( const std::string& str, const int mode ) { // WAVE DASH 問題 const size_t size = 4; const unsigned char Win[size][4] = { { 0xef, 0xbd, 0x9e, '\0' }, // FULLWIDTH TILDE (U+FF5E) { 0xe2, 0x80, 0x95, '\0' }, // HORIZONTAL BAR (U+2015) { 0xe2, 0x88, 0xa5, '\0' }, // PARALLEL TO (U+2225) { 0xef, 0xbc, 0x8d, '\0' } // FULLWIDTH HYPHEN-MINUS (U+FF0D) }; const unsigned char Unix[size][4] = { { 0xe3, 0x80, 0x9c, '\0' }, // WAVE DASH (U+301C) { 0xe2, 0x80, 0x94, '\0' }, // EM DASH(U+2014) { 0xe2, 0x80, 0x96, '\0' }, // DOUBLE VERTICAL LINE (U+2016) { 0xe2, 0x88, 0x92, '\0' } // MINUS SIGN (U+2212) }; std::string ret(str); if( mode == WINtoUNIX ){ for( size_t i = 0; i < ret.length(); i++ ) { for( size_t s = 0; s < size; s++ ) { if( ret[ i ] != (char)Win[ s ][ 0 ] || ret[ i+1 ] != (char)Win[ s ][ 1 ] || ret[ i+2 ] != (char)Win[ s ][ 2 ] ) continue; for( size_t t = 0; t < 3; t++ ) ret[ i+t ] = (char)Unix[ s ][ t ]; i += 2; break; } } }else{ for( size_t i = 0; i < ret.length(); i++ ) { for( size_t s = 0; s < size; s++ ) { if( ret[ i ] != (char)Unix[ s ][ 0 ] || ret[ i+1 ] != (char)Unix[ s ][ 1 ] || ret[ i+2 ] != (char)Unix[ s ][ 2 ] ) continue; for( size_t t = 0; t < 3; t++ ) ret[ i+t ] = (char)Win[ s ][ t ]; i += 2; break; } } } return ret; } // // str を大文字化 // std::string MISC::toupper_str( const std::string& str ) { std::string str_out; std::transform( str.cbegin(), str.cend(), std::back_inserter( str_out ), []( unsigned char c ) { return std::toupper( c ); } ); return str_out; } // // list 内のアイテムを全部大文字化 // std::list< std::string > MISC::toupper_list( const std::list< std::string >& list_str ) { std::list< std::string > list_out; std::transform( list_str.cbegin(), list_str.cend(), std::back_inserter( list_out ), []( const std::string& s ) { return MISC::toupper_str( s ); } ); return list_out; } // // str を小文字化 // std::string MISC::tolower_str( const std::string& str ) { std::string str_out; std::transform( str.cbegin(), str.cend(), std::back_inserter( str_out ), []( unsigned char c ) { return std::tolower( c ); } ); return str_out; } // // path からホスト名だけ取り出す // // protocol = false のときはプロトコルを除く // std::string MISC::get_hostname( const std::string& path, bool protocol ) { int lng = 0; if( path.rfind( "http://", 0 ) == 0 ) lng = strlen( "http://" ); else if( path.rfind( "https://", 0 ) == 0 ) lng = strlen( "https://" ); else if( path.rfind( "ftp://", 0 ) == 0 ) lng = strlen( "ftp://" ); if( !lng ) return std::string(); int pos = 0; if( ! protocol ) pos = lng; size_t i = path.find( '/', lng ); if( i == std::string::npos ) return path.substr( pos ); return path.substr( pos, i - pos ); } // // path からファイル名だけ取り出す // std::string MISC::get_filename( const std::string& path ) { if( path.empty() ) return std::string(); size_t i = path.rfind( '/' ); if( i == std::string::npos ) return path; return path.substr( i+1 ); } // // path からファイル名を除いてディレクトリだけ取り出す // std::string MISC::get_dir( const std::string& path ) { if( path.empty() ) return std::string(); size_t i = path.rfind( '/' ); if( i == std::string::npos ) return std::string(); return path.substr( 0, i+1 ); } // // 文字数を限定して環境変数の値を返す // std::string MISC::getenv_limited( const char *name, const size_t size ) { if( ! name || ! getenv( name ) ) return std::string(); std::string env = getenv( name ); if( env.size() > size ) env.resize( size ); return env; } // // pathセパレータを / に置き換える // std::string MISC::recover_path( const std::string& str ) { return str; } std::vector< std::string > MISC::recover_path( std::vector< std::string > list_str ) { return list_str; } // // 文字列(utf-8)に全角英数字が含まれるか判定する // bool MISC::has_widechar( const char* str ) { while( *str != '\0' ){ const unsigned char in = *str; if( ( in & 0xf0 ) == 0xe0 ){ if( in == 0xef ){ const auto in2 = static_cast( *( str + 1 ) ); const auto in3 = static_cast( in2 != '\0' ? *( str + 2 ) : 0 ); if( in2 == 0xbc ){ // 全角数字 if( 0x90 <= in3 && in3 <= 0x99 ) return true; // 全角大文字 else if( 0xa1 <= in3 && in3 <= 0xba ) return true; } // 全角小文字 else if( in2 == 0xbd && ( 0x81 <= in3 && in3 <= 0x9a ) ) return true; // 半角かな else if( ( in2 == 0xbd && ( 0xa1 <= in3 && in3 <= 0xbf ) ) || ( in2 == 0xbe && ( 0x80 <= in3 && in3 <= 0x9f ) ) ) return true; } str += 3; } else if( ( in & 0xe0 ) == 0xc0 ) str += 2; else if( ( in & 0xf8 ) == 0xf0 ) str += 4; else ++str; } return false; } // // 全角英数字(str1) -> 半角英数字(str2) // // table_pos : 置き換えた文字列の位置 // void MISC::asc( const char* str1, std::string& str2, std::vector< int >& table_pos ) { int pos = 0; while( str1[ pos ] != '\0' ) { assert( pos >= 0 ); assert( table_pos.max_size() > table_pos.size() ); const auto in1 = static_cast< unsigned char >( str1[ pos ] ); if( in1 == 0xef ) { const auto in2 = static_cast< unsigned char >( str1[ pos + 1 ] ); unsigned char in3 = 0; if ( in2 ) in3 = static_cast< unsigned char >( str1[ pos + 2 ] ); if( in2 == 0xbc ){ // 全角数字 (U+FF10 - U+FF19) if( 0x90 <= in3 && in3 <= 0x99 ){ str2.push_back( '0' + in3 - 0x90 ); table_pos.push_back( pos ); pos += 3; continue; } // 全角大文字 (U+FF21 - U+FF3A) else if( 0xa1 <= in3 && in3 <= 0xba ){ str2.push_back( 'A' + in3 - 0xa1 ); table_pos.push_back( pos ); pos += 3; continue; } } // 全角小文字 (U+FF41 - U+FF5A) else if( in2 == 0xbd && ( 0x81 <= in3 && in3 <= 0x9a ) ){ str2.push_back( 'a' + in3 - 0x81 ); table_pos.push_back( pos ); pos += 3; continue; } // 半角かな (U+FF61 - U+FF9F) else if( ( in2 == 0xbd && ( 0xa1 <= in3 && in3 <= 0xbf ) ) || ( in2 == 0xbe && ( 0x80 <= in3 && in3 <= 0x9f ) ) ){ bool flag_hkana = false; bool dakuten = false; size_t i = 0; // 濁点、半濁点 unsigned char in4 = 0; unsigned char in5 = 0; if ( in3 ) in4 = static_cast< unsigned char >( str1[ pos + 3 ] ); if ( in4 ) in5 = static_cast< unsigned char >( str1[ pos + 4 ] ); if( in4 == 0xef && in5 == 0xbe ){ const auto in6 = static_cast< unsigned char >( str1[ pos + 5 ] ); // 濁点 if( in6 == 0x9e ){ dakuten = true; i = 61; } // 半濁点 else if( in6 == 0x9f ){ dakuten = true; i = 61 + 21; } } while( hkana_table1[ i ][ 0 ][ 0 ] != '\0' ){ if( in1 == hkana_table1[ i ][ 0 ][ 0 ] && in2 == hkana_table1[ i ][ 0 ][ 1 ] && in3 == hkana_table1[ i ][ 0 ][ 2 ] ) { std::copy_n( hkana_table1[ i ][ 1 ], 3, std::back_inserter( str2 ) ); std::generate_n( std::back_inserter( table_pos ), 3, [&pos]{ return pos++; } ); if( dakuten ) pos += 3; flag_hkana = true; break; } ++i; } if( flag_hkana ) continue; } } str2.push_back( str1[ pos ] ); table_pos.push_back( pos ); ++pos; } // 文字列の終端(ヌル文字)の位置を追加する。 // ヌル文字の位置がないと検索対象の末尾にマッチングしたとき範囲外アクセスが発生する。 table_pos.push_back( pos ); } // // selfの先頭部分がstartsと等しいか(ヌル終端文字列バージョン) // Unicode正規化は行わなずバイト列として比較する // // self : 対象の文字列 // starts : 先頭部分 // bool MISC::starts_with( const char* self, const char* starts ) { for( std::size_t i = 0; starts[i] != '\0'; ++i ) { if( self[i] == '\0' || self[i] != starts[i] ) return false; } return true; } // // HTMLからform要素を解析してinput,textarea要素の名前と値を返す // std::vector MISC::parse_html_form_data( const std::string& html ) { JDLIB::Regex regex; JDLIB::RegexPattern pat; constexpr bool icase = true; // 大文字小文字区別しない constexpr bool newline = false; // . に改行をマッチさせる constexpr bool usemigemo = false; constexpr bool wchar = false; // or ))"; pat.set( pattern, icase, newline, usemigemo, wchar ); std::vector data; for( std::size_t offset = 0; ; ++offset){ std::string name; std::string value; if( regex.match( pat, html, offset ) ) { const std::string name_value = MISC::tolower_str( regex.str( 3 ) ); if( name_value.rfind( "name=", 0 ) == 0 ) { name = MISC::remove_space( regex.str( 4 ) ); value = MISC::remove_space( regex.str( 5 ) ); } else if( name_value.rfind( "value=", 0 ) == 0 ) { name = MISC::remove_space( regex.str( 7 ) ); value = MISC::remove_space( regex.str( 6 ) ); } else { name = MISC::remove_space( regex.str( 8 ) ); value = MISC::remove_space( regex.str( 9 ) ); } } if( name.empty() ) break; offset = regex.pos( 0 ); if( name[ 0 ] == '\"' ) name = MISC::cut_str( name, "\"", "\"" ); if( value[ 0 ] == '\"' ) value = MISC::cut_str( value, "\"", "\"" ); #ifdef _DEBUG std::cout << "offset = " << offset << " " << regex.str( 0 ) << std::endl << "name = " << name << " value = " << value << std::endl; #endif data.push_back( MISC::FormDatum{ std::move( name ), std::move( value ) } ); } return data; } // HTMLのform要素から action属性(送信先URLのパス) を取得する // 2ch互換板に特化して実装しているため他の掲示板で期待した結果を返す保証はない // 詳細は実装やテストコードを参照 // std::string MISC::parse_html_form_action( const std::string& html ) { const char pattern[] = R"(
    ]* action="(\.\.)?(/test/(sub)?bbs\.cgi(\?[^"]*)?))"; const char pattern_same_hierarchy[] = R"(]* action="\.(/(sub)?bbs\.cgi(\?[^"]*)?))"; JDLIB::Regex regex; constexpr std::size_t offset = 0; constexpr bool icase = true; // 大文字小文字区別しない constexpr bool newline = false; // . に改行をマッチさせる constexpr bool usemigemo = false; constexpr bool wchar = false; std::string path; if( regex.exec( pattern, html, offset, icase, newline, usemigemo, wchar ) ) { path = regex.str( 3 ); } else if( regex.exec( pattern_same_hierarchy, html, offset, icase, newline, usemigemo, wchar ) ) { path = "/test" + regex.str( 2 ); } return path; } // haystack の pos 以降から最初に needle と一致する位置を返す (ASCIIだけignore case) // 見つからない場合は std::string::npos を返す、needle が空文字列なら pos を返す std::size_t MISC::ascii_ignore_case_find( const std::string& haystack, const std::string& needle, std::size_t pos ) { if( haystack.size() < pos ) return std::string::npos; if( needle.empty() ) return pos; const char head[3] = { g_ascii_toupper( needle[0] ), g_ascii_tolower( needle[0] ) }; std::size_t i = pos; while( true ) { i = haystack.find_first_of( head, i ); if( i == std::string::npos ) break; // ヌル終端文字列が要件なので注意 if( g_ascii_strncasecmp( haystack.c_str() + i, needle.c_str(), needle.size() ) == 0 ) break; ++i; } return i; } jdim-0.7.0/src/jdlib/miscutil.h000066400000000000000000000307341417047150700163330ustar00rootroot00000000000000// ライセンス: GPL2 // 文字列関係の関数 #ifndef _MISCUTIL_H #define _MISCUTIL_H #include #include #include #include #include namespace MISC { // URLスキームタイプ enum { SCHEME_NONE, SCHEME_HTTP, SCHEME_FTP, SCHEME_TTP, SCHEME_TP, SCHEME_SSSP }; // get_ucs2mode()の戻り値 enum { UCS2MODE_BASIC_LATIN = 0, UCS2MODE_HIRA, UCS2MODE_KATA, UCS2MODE_OTHER }; // utf8_fix_wavedash のモード enum { UNIXtoWIN = 0, WINtoUNIX }; // parse_html_form_data() の戻り値 struct FormDatum { std::string name; std::string value; bool operator==( const FormDatum& rhs ) const { return name == rhs.name && value == rhs.value; } }; // str を "\n" ごとに区切ってlistにして出力 std::list< std::string > get_lines( const std::string& str ); // strを空白または "" 単位で区切って list で出力 std::list< std::string > split_line( const std::string& str ); // strを delimで区切って list で出力 std::list< std::string > StringTokenizer( const std::string& str, const char delim ); // emacs lisp のリスト型を要素ごとにlistにして出力 std::list< std::string > get_elisp_lists( const std::string& str ); // list_inから空白行を除いてリストを返す std::list< std::string > remove_nullline_from_list( const std::list< std::string >& list_in ); // list_inの各行から前後の空白を除いてリストを返す std::list< std::string > remove_space_from_list( const std::list< std::string >& list_in ); // list_inからコメント行(#)を除いてリストを返す std::list< std::string > remove_commentline_from_list( const std::list< std::string >& list_in ); // 空白と""で区切られた str_in の文字列をリストにして出力 // \"は " に置換される // (例) "aaa" "bbb" "\"ccc\"" → aaa と bbb と "ccc" std::list< std::string > strtolist( const std::string& str_in ); // list_in の文字列リストを空白と""で区切ってストリングにして出力 // "は \" に置換される // (例) "aaa" "bbb" "\"ccc\"" std::string listtostr( const std::list< std::string >& list_in ); // list_in から空文字列を除き suffix でつなげて返す // 他のプログラミング言語にあるjoin()と動作が異なり返り値の末尾にもsuffixが付く // (例) {"aa", "", "bb", "cc"}, '!' -> "aa!bb!cc!" std::string concat_with_suffix( const std::list& list_in, char suffix ); // strの前後の空白削除 std::string remove_space( const std::string& str ); // str前後の改行、タブ、スペースを削除 std::string remove_spaces( const std::string& str ); // str1からstr2で示された文字列を除く std::string remove_str( const std::string& str1, const std::string& str2 ); // start 〜 end の範囲をstrから取り除く ( /* コメント */ など ) std::string remove_str( const std::string& str, const std::string& start, const std::string& end ); // 正規表現を使ってstr1からqueryで示された文字列を除く std::string remove_str_regex( const std::string& str1, const std::string& query ); // str1, str2 に囲まれた文字列を切り出す std::string cut_str( const std::string& str, const std::string& str1, const std::string& str2 ); // str1 を str2 に置き換え std::string replace_str( const std::string& str, const std::string& str1, const std::string& str2 ); // list_inから str1 を str2 に置き換えてリストを返す std::list< std::string > replace_str_list( const std::list< std::string >& list_in, const std::string& str1, const std::string& str2 ); // str_in に含まれる改行文字を replace に置き換え std::string replace_newlines_to_str( const std::string& str_in, const std::string& replace ); // " を \" に置き換え std::string replace_quot( const std::string& str ); // \" を " に置き換え std::string recover_quot( const std::string& str ); // str 中に含まれている str2 の 数を返す int count_str( const std::string& str, const std::string& str2 ); // 文字列(utf-8も) -> 整数変換 // (例) "123" -> 123 // 入力: str // 出力: // dig: 桁数、0なら失敗 // n : str から何バイト読み取ったか // 戻り値: 数値 int str_to_uint( const char* str, size_t& dig, size_t& n ); // listで指定した数字を文字に変換 std::string intlisttostr( const std::list< int >& list_num ); // 16進数表記文字をバイナリに変換する( 例 "E38182" -> 0xE38182 ) // 出力 : char_out // 戻り値: 変換に成功した chr_in のバイト数 size_t chrtobin( const char* chr_in, char* chr_out ); // strが半角でmaxsize文字を超えたらカットして後ろに...を付ける std::string cut_str( const std::string& str, const unsigned int maxsize ); // 正規表現のメタ文字が含まれているか // escape == true ならエスケープを考慮 (例) escape == true なら \+ → \+ のまま、falseなら \+ → \\\+ bool has_regex_metachar( const std::string& str, const bool escape ); // 正規表現のメタ文字をエスケープ // escape == true ならエスケープを考慮 (例) escape == true なら \+ → \+ のまま、falseなら \+ → \\\+ std::string regex_escape( const std::string& str, const bool escape ); // 正規表現のメタ文字をアンエスケープ std::string regex_unescape( const std::string& str ); // HTMLエスケープ // include_url : URL中でもエスケープする( デフォルト = true ) std::string html_escape( const std::string& str, const bool include_url = true ); // HTMLアンエスケープ std::string html_unescape( const std::string& str ); // HTML文字参照をデコード( completely=trueの場合は '&' '<' '>' '"' を含める ) std::string chref_decode( const char* str, const int lng, const bool completely = true ); inline std::string chref_decode( const std::string& str, const bool completely = true ) { return MISC::chref_decode( str.c_str(), str.size(), completely ); } // URL中のスキームを判別する // 戻り値 : スキームタイプ // length : "http://"等の文字数 int is_url_scheme( const char* str_in, int* length = nullptr ); int is_url_scheme_impl( const char* str_in, int* length ); // URLとして扱う文字かどうか判別する // 基本 : 「!#$%&'()*+,-./0-9:;=?@A-Z_a-z~」 // 拡張 : 「[]^|」 // // "RFC 3986" : http://www.ietf.org/rfc/rfc3986.txt // "RFC 2396" : http://www.ietf.org/rfc/rfc2396.txt bool is_url_char( const char* str_in, const bool loose_url ); // URLデコード std::string url_decode( const std::string& url ); // urlエンコード std::string url_encode( const char* str, const size_t n ); std::string url_encode( const std::string& str ); // 文字コードを変換して url エンコード // str は UTF-8 であること std::string charset_url_encode( const std::string& str, const std::string& charset ); // 文字コード変換して url エンコード // ただし半角スペースのところを+に置き換えて区切る std::string charset_url_encode_split( const std::string& str, const std::string& charset ); // BASE64 std::string base64( const std::string& str ); // 文字コードを coding_from から coding_to に変換 // 遅いので連続的な処理が必要な時は使わないこと std::string Iconv( const std::string& str, const std::string& coding_to, const std::string& coding_from ); // 「&#数字;」形式の数字参照文字列の中の「数字」部分の文字列長 // // in_char: 入力文字列、in_char[0] == "&" && in_char[1] == "#" であること // offset : 開始位置が返る // // 戻り値 : 「&#数字;」の中の数字の文字列の長さ、変換出来ないときは -1 // // 例 : ✏ なら 戻り値 = 4、 offset = 2 // int spchar_number_ln( const char* in_char, int& offset ); // 「&#数字;」形式の数字参照文字列を数字(int)に変換する // // 最初に MISC::spchar_number_ln() を呼び出して offset と lng を取得すること // // in_char: 入力文字列、in_char[0] == "&" && in_char[1] == "#" であること // offset : spchar_number_ln() の戻り値 // lng : spchar_number_ln() の戻り値 // // 戻り値 : 「&#数字;」の中の数字(int型) // int decode_spchar_number( const char* in_char, const int offset, const int lng ); // str に含まれる「&#数字;」形式の数字参照文字列を全てユニーコード文字に変換する std::string decode_spchar_number( const std::string& str ); // utf-8 -> ucs2 変換 // 入力 : utfstr 入力文字 (UTF-8) // 出力 : byte 長さ(バイト) utfstr が ascii なら 1, UTF-8 なら 2 or 3 or 4 を入れて返す // 戻り値 : ucs2 int utf8toucs2( const char* utfstr, int& byte ); // ucs2 の種類 int get_ucs2mode( const int ucs2 ); // ucs2 -> utf8 変換 // 出力 : utfstr 変換後の文字 // 戻り値 : バイト数 int ucs2toutf8( const int ucs2, char* utfstr ); // WAVEDASHなどのWindows系UTF-8文字をUnix系文字と相互変換 std::string utf8_fix_wavedash( const std::string& str, const int mode ); // str を大文字化 std::string toupper_str( const std::string& str ); // list 内のアイテムを全部大文字化 std::list< std::string > toupper_list( const std::list< std::string >& list_str ); //str を小文字化 std::string tolower_str( const std::string& str ); // path からホスト名だけ取り出す // protocol = false のときはプロトコルを除く std::string get_hostname( const std::string& path, const bool protocol = true ); // path からファイル名だけ取り出す std::string get_filename( const std::string& path ); // path からファイル名を除いてディレクトリだけ取り出す std::string get_dir( const std::string& path ); // 文字数を限定して環境変数の値を返す std::string getenv_limited( const char *name, const size_t size = 1 ); // pathセパレータを / に置き換える std::string recover_path( const std::string& str ); std::vector< std::string > recover_path( std::vector< std::string > list_str ); // 文字列(utf-8)に全角英数字が含まれるか判定する bool has_widechar( const char* str ); // 全角英数字(str1) -> 半角英数字(str2) // table_pos : 置き換えた文字列の位置 void asc( const char* str1, std::string& str2, std::vector< int >& table_pos ); // URL中のスキームを判別する inline int is_url_scheme( const char* str_in, int* length ) { // 候補になり得ない場合は以降の処理はしない if( *str_in != 'h' && *str_in != 'f' && *str_in != 't' && *str_in != 's' ) return SCHEME_NONE; return is_url_scheme_impl( str_in, length ); } // selfの先頭部分がstartsと等しいか(ヌル終端文字列バージョン) // Unicode正規化は行わなずバイト列として比較する bool starts_with( const char* self, const char* starts ); // HTMLからform要素を解析してinput,textarea要素の名前と値を返す std::vector parse_html_form_data( const std::string& html ); // HTMLのform要素から action属性(送信先URLのパス) を取得する // 2ch互換板に特化して実装しているため他の掲示板で期待した結果を返す保証はない // 詳細は実装やテストコードを参照 std::string parse_html_form_action( const std::string& html ); // haystack の pos 以降から最初に needle と一致する位置を返す (ASCIIだけignore case) // 見つからない場合は std::string::npos を返す、needle が空文字列なら pos を返す std::size_t ascii_ignore_case_find( const std::string& haystack, const std::string& needle, std::size_t pos = 0 ); } #endif jdim-0.7.0/src/jdlib/miscx.cpp000066400000000000000000000016621417047150700161560ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "gtkmmversion.h" #include "jddebug.h" #include "miscx.h" #include #ifdef GDK_WINDOWING_X11 #include #endif // GDK_WINDOWING_X11 // // dest ウインドウ上の、クライアント座標 x,y に移動する // void MISC::WarpPointer( Glib::RefPtr< Gdk::Window > src, Glib::RefPtr< Gdk::Window > dest, int x, int y ){ #ifdef GDK_WINDOWING_X11 GdkDisplay* display = gdk_window_get_display( Glib::unwrap( src ) ); // X11環境でない場合は何もしない if( ! GDK_IS_X11_DISPLAY( display ) ) return; // XXX: X11やXwaylandがインストールされていない環境ではコンパイルエラーになるかもしれない XWarpPointer( gdk_x11_display_get_xdisplay( display ), None, gdk_x11_window_get_xid( Glib::unwrap( dest ) ) , 0, 0, 0, 0, x, y ); #endif // GDK_WINDOWING_X11 } jdim-0.7.0/src/jdlib/miscx.h000066400000000000000000000003531417047150700156170ustar00rootroot00000000000000// ライセンス: GPL2 // X 関係の関数 #ifndef _MISCX_H #define _MISCX_H #include namespace MISC { void WarpPointer( Glib::RefPtr< Gdk::Window > src, Glib::RefPtr< Gdk::Window > dest, int x, int y ); } #endif jdim-0.7.0/src/jdlib/refptr_lock.h000066400000000000000000000025741417047150700170150ustar00rootroot00000000000000// ライセンス: GPL2 // // ロック付きリファレンスクラスのテンプレート // // SKELETON::Lockable を継承したクラスを RefPtr_Lock 経由で呼ぶことによってロックを掛ける // #ifndef _REFPTR_LOCK_H #define _REFPTR_LOCK_H namespace JDLIB { template < typename T > class RefPtr_Lock { T* m_p{}; public: void clear(){ if( m_p ){ m_p->unlock(); m_p = nullptr; } } void set( T* p ){ clear(); m_p = p; if( m_p ) m_p->lock(); } T* operator -> () noexcept { return m_p; } const T* operator -> () const noexcept { return m_p; } bool operator == ( const T* p ) const noexcept { return ( m_p == p ); } bool operator != ( const T* p ) const noexcept { return ( m_p != p ); } bool operator ! () const noexcept { return ( m_p == nullptr ); } operator bool () const noexcept { return ( m_p != nullptr ); } RefPtr_Lock& operator = ( const RefPtr_Lock& a ) { set( a.m_p ); return *this; } RefPtr_Lock& operator = ( T* p ) { set( p ); return *this; } RefPtr_Lock() noexcept = default; RefPtr_Lock( const RefPtr_Lock& a ) { set( a.m_p ); } RefPtr_Lock( T* p ) { set( p ); } virtual ~RefPtr_Lock(){ clear();} }; } #endif jdim-0.7.0/src/jdlib/ssl.cpp000066400000000000000000000145531417047150700156370ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "ssl.h" using namespace JDLIB; #ifdef USE_GNUTLS #include // gnutls 使用 void JDLIB::init_ssl() { #ifdef _DEBUG std::cout << "init_ssl(gnutls)\n"; #endif gnutls_global_init(); } void JDLIB::deinit_ssl() { #ifdef _DEBUG std::cout << "deinit_ssl(gnutls)\n"; #endif gnutls_global_deinit(); } JDSSL::JDSSL() { #ifdef _DEBUG std::cout << "JDSSL::JDSSL(gnutls)\n"; #endif } JDSSL::~JDSSL() { #ifdef _DEBUG std::cout << "JDSSL::~JDSSL(gnutls)\n"; #endif close(); } bool JDSSL::connect( const int soc, const char *host ) { #ifdef _DEBUG std::cout << "JDSSL::connect(gnutls)\n"; #endif if( soc < 0 ) return false; if( m_session ) return false; if( m_cred ) return false; int ret; ret = gnutls_init( &m_session, GNUTLS_CLIENT ); if( ret != 0 ){ m_errmsg = "gnutls_init failed"; return false; } auto failure = [this]( int err ) { m_errmsg = gnutls_strerror( err ); return false; }; ret = gnutls_certificate_allocate_credentials( &m_cred ); if( ret != GNUTLS_E_SUCCESS ) return failure( ret ); ret = gnutls_certificate_set_x509_system_trust( m_cred ); if( ret < 0 ) return failure( ret ); ret = gnutls_server_name_set( m_session, GNUTLS_NAME_DNS, host, strlen( host ) ); if( ret != GNUTLS_E_SUCCESS ) return failure( ret ); ret = gnutls_set_default_priority( m_session ); if( ret != GNUTLS_E_SUCCESS ) return failure( ret ); ret = gnutls_credentials_set( m_session, GNUTLS_CRD_CERTIFICATE, m_cred ); if( ret != GNUTLS_E_SUCCESS ) return failure( ret ); gnutls_session_set_verify_cert( m_session, host, 0 ); gnutls_transport_set_int( m_session, soc ); gnutls_handshake_set_timeout( m_session, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT ); do { ret = gnutls_handshake( m_session ); } while( ret < 0 && gnutls_error_is_fatal( ret ) == 0 ); if( ret < 0 ) { m_errmsg = "JDSSL::connect(gnutls) *** Handshake failed: "; m_errmsg += gnutls_strerror( ret ); if( ret == GNUTLS_E_CERTIFICATE_VERIFICATION_ERROR ) { gnutls_certificate_type_t type = gnutls_certificate_type_get( m_session ); const unsigned status = gnutls_session_get_verify_cert_status( m_session ); gnutls_datum_t out; ret = gnutls_certificate_verification_status_print( status, type, &out, 0 ); if( ret == GNUTLS_E_SUCCESS ) { m_errmsg += "\nJDSSL::connect(gnutls) - cert verify output: "; m_errmsg += reinterpret_cast< const char* >( out.data ); gnutls_free( out.data ); } } return false; } #ifdef _DEBUG char* desc = gnutls_session_get_desc( m_session ); assert( desc ); std::cout << "JDSSL::connect(gnutls) - Session info: " << desc << std::endl; gnutls_free( desc ); #endif // _DEBUG return true; } bool JDSSL::close() { #ifdef _DEBUG std::cout << "JDSSL::close(gnutls)\n"; #endif if( m_session ){ gnutls_bye( m_session, GNUTLS_SHUT_RDWR ); gnutls_deinit( m_session ); m_session = nullptr; } if( m_cred ){ gnutls_certificate_free_credentials( m_cred ); m_cred = nullptr; } return true; } int JDSSL::write( const char* buf, const size_t bufsize ) { int tmpsize; do { tmpsize = gnutls_record_send( m_session, buf, bufsize ); } while( tmpsize == GNUTLS_E_AGAIN || tmpsize == GNUTLS_E_INTERRUPTED ); #ifdef _DEBUG std::cout << "JDSSL::write(gnutls) tmpsize = " << tmpsize << "; bufsize = " << bufsize << std::endl; #endif if( tmpsize < 0 ) m_errmsg = "gnutls_record_send failed"; return tmpsize; } int JDSSL::read( char* buf, const size_t bufsize ) { int tmpsize; do { tmpsize = gnutls_record_recv( m_session, buf, bufsize ); } while( tmpsize == GNUTLS_E_AGAIN || tmpsize == GNUTLS_E_INTERRUPTED ); #ifdef _DEBUG std::cout << "JDSSL::read(gnutls) tmpsize = " << tmpsize << "; bufsize = " << bufsize << std::endl; #endif if( tmpsize == GNUTLS_E_PREMATURE_TERMINATION || tmpsize == GNUTLS_E_INVALID_SESSION ) { // Transfer-Encoding: chuncked のときはデータの長さが分からない // そのため受信エラーをデータ無しに置き換えて受信終了を判断する return 0; } if( tmpsize < 0 ) m_errmsg = "gnutls_record_recv failed"; return tmpsize; } #else //////////////////////////////////////////////////////////////////// // OpenSSL 使用 void JDLIB::init_ssl() { #ifdef _DEBUG std::cout << "init_ssl(openssl)\n"; #endif } void JDLIB::deinit_ssl() { #ifdef _DEBUG std::cout << "deinit_ssl(openssl)\n"; #endif } JDSSL::JDSSL() { #ifdef _DEBUG std::cout << "JDSSL::JDSSL(openssl)\n"; #endif } JDSSL::~JDSSL() { #ifdef _DEBUG std::cout << "JDSSL::~JDSSL(openssl)\n"; #endif close(); } bool JDSSL::connect( const int soc, const char *host ) { #ifdef _DEBUG std::cout << "JDSSL::connect(openssl)\n"; #endif if( soc < 0 ) return false; if( m_ctx ) return false; if( m_ssl ) return false; SSL_library_init(); m_ctx = SSL_CTX_new( SSLv23_client_method() ); if( ! m_ctx ){ m_errmsg = "SSL_CTX_new failed"; return false; } m_ssl = SSL_new( m_ctx ); if( ! m_ssl ){ m_errmsg = "SSL_new failed"; return false; } if( SSL_set_fd( m_ssl, soc ) == 0 ){ m_errmsg = "SSL_set_fd failed"; return false; } SSL_set_tlsext_host_name( m_ssl, host ) ; if( SSL_connect( m_ssl ) != 1 ){ m_errmsg = "SSL_connect failed"; return false; } #ifdef _DEBUG std::cout << "connect ok\n"; #endif return true; } bool JDSSL::close() { #ifdef _DEBUG std::cout << "JDSSL::close(openssl)\n"; #endif if( m_ssl ){ SSL_shutdown( m_ssl ); SSL_free( m_ssl ); m_ssl = nullptr; } if( m_ctx ){ SSL_CTX_free( m_ctx ); m_ctx = nullptr; } return true; } int JDSSL::write( const char* buf, const size_t bufsize ) { int tmpsize = SSL_write( m_ssl, buf, bufsize ); if( tmpsize < 0 ) m_errmsg = "SSL_write failed"; return tmpsize; } int JDSSL::read( char* buf, const size_t bufsize ) { int tmpsize = SSL_read( m_ssl, buf, bufsize ); if( tmpsize < 0 ) m_errmsg = "SSL_read failed"; return tmpsize; } #endif jdim-0.7.0/src/jdlib/ssl.h000066400000000000000000000016721417047150700153020ustar00rootroot00000000000000// ライセンス: GPL2 // // SSL ローダ // #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef USE_GNUTLS #include #else #include // gdkmm/device.h で定義される set_key マクロと衝突する #ifdef set_key #undef set_key #endif #endif #include namespace JDLIB { class JDSSL { std::string m_errmsg; #ifdef USE_GNUTLS gnutls_session_t m_session{}; gnutls_certificate_credentials_t m_cred{}; #else // USE_GNUTLS SSL_CTX* m_ctx{}; SSL* m_ssl{}; #endif // USE_GNUTLS public: JDSSL(); virtual ~JDSSL(); const std::string& get_errmsg() const { return m_errmsg; } bool connect( const int soc, const char* host ); bool close(); int write( const char* buf, const size_t bufsize ); int read( char* buf, const size_t bufsize ); }; void init_ssl(); void deinit_ssl(); } jdim-0.7.0/src/jdlib/tfidf.cpp000077500000000000000000000150011417047150700161220ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "tfidf.h" #include "dbtree/articlebase.h" #include "global.h" #include #include // // 単語ベクトル作成 // void MISC::tfidf_create_vec_words( VEC_WORDS& vec_words, const Glib::ustring& document ) { const int n = document.length() - 1; #ifdef _DEBUG std::cout << "tfidf_create_vec_words\n"; std::cout << "doc = " << document.raw() << std::endl; std::cout << "n = " << n << std::endl; #endif if( n <= 0 ) return; std::set< Glib::ustring > set_words; for( int i = 0; i < n; ++i ){ Glib::ustring word = document.substr( i, 2 ); const auto result = set_words.insert( word ); if( result.second ) { // 重複しないように単語をvec_wordsへ追加する vec_words.push_back( word ); } } } // // IDF計算 (実際には頻度計算) // // vec_idf はあらかじめ resize しておくこと // void MISC::tfidf_create_vec_idf( VEC_IDF& vec_idf, const Glib::ustring& document, const VEC_WORDS& vec_words ) { const int n = vec_words.size(); if( ! n || n != (int)vec_idf.size() ) return; for( int i = 0; i < n; ++i ){ if( document.find( vec_words[ i ] ) != Glib::ustring::npos ) vec_idf[ i ] += 1; } } // // documnet に対する TFIDF ベクトル計算 // // vec_tfidf はあらかじめ resize しておくこと // void MISC::tfidf_calc_vec_tfifd( VEC_TFIDF& vec_tfidf, const Glib::ustring& document, const VEC_IDF& vec_idf, const VEC_WORDS& vec_words ) { const int n = vec_words.size(); const int n_doc = document.size() - 1; #ifdef _DEBUG std::cout << "tfidf_calc_vec_tfidf\n"; std::cout << "doc = " << document.raw() << std::endl; std::cout << "n = " << n << " n_doc = " << n_doc << std::endl; #endif if( ! n || n_doc <= 0 || n != (int)vec_tfidf.size() ) return; double total = 0; for( int i = 0; i < n; ++i ){ if( document.find( vec_words[ i ] ) == Glib::ustring::npos ){ vec_tfidf[ i ] = 0; continue; } int hit = 0; for( int j = 0; j < n_doc; ++j ) if( document.substr( j, 2 ) == vec_words[ i ] ) ++hit; vec_tfidf[ i ] = hit; total += hit; } for( int i = 0; i < n; ++i ){ if( total ){ vec_tfidf[ i ] /= total; vec_tfidf[ i ] *= vec_idf[ i ]; } else vec_tfidf[ i ] = 0; } } // // 相関計算 // double MISC::tfidf_cos_similarity( const VEC_TFIDF& vec_tfidf1, const VEC_TFIDF& vec_tfidf2 ) { const int n = vec_tfidf1.size(); #ifdef _DEBUG std::cout << "MISC::tfidf_cos_similarity n = " << n << std::endl; #endif if( ! n || n != (int)vec_tfidf1.size() ) return 0; double product = 0; double lng1 = 0; double lng2 = 0; for( int i = 0; i < n; ++i ){ product += vec_tfidf1[ i ] * vec_tfidf2[ i ]; lng1 += vec_tfidf1[ i ] * vec_tfidf1[ i ]; lng2 += vec_tfidf2[ i ] * vec_tfidf2[ i ]; } if( lng1 == 0 ) return 0; if( lng2 == 0 ) return 0; const double ret = product / sqrt( lng1 * lng2 ); #ifdef _DEBUG std::cout << "similarity = " << ret << std::endl; #endif return ret; } // // スレ一覧からIDF 計算 // void MISC::tfidf_create_vec_idf_from_board( VEC_IDF& vec_idf, const Glib::ustring& subject_src, const std::vector< DBTREE::ArticleBase* >& list_subject, const VEC_WORDS& vec_words ) { #ifdef _DEBUG std::cout << "MISC::tfidf_create_vec_idf_from_board\n"; #endif if( subject_src.empty() || ! list_subject.size() || ! vec_words.size() ) return; vec_idf.resize( vec_words.size() ); MISC::tfidf_create_vec_idf( vec_idf, subject_src, vec_words ); int D = 1; for( const DBTREE::ArticleBase* art : list_subject ) { // DAT落ちのスレは除く if( art->get_status() & STATUS_OLD ) continue; const Glib::ustring subject = art->get_subject(); if( subject != subject_src ){ MISC::tfidf_create_vec_idf( vec_idf, subject, vec_words ); ++D; } } for( int i = 0; i < (int)vec_words.size(); ++i ){ #ifdef _DEBUG std::cout << vec_words[ i ].raw() << " hit = " << (int)vec_idf[ i ] << " / " << D; #endif vec_idf[ i ] = log( D / vec_idf[ i ] ); #ifdef _DEBUG std::cout << " idf = " << vec_idf[ i ] << std::endl; #endif } } // str1 と str2 間のレーベンシュタイン距離 double MISC::leven( std::vector< std::vector< int > >& dist, const Glib::ustring& str1, const Glib::ustring& str2 ) { const size_t maxlng = dist.size() -1; const size_t lng1 = MIN( maxlng, str1.length() ); const size_t lng2 = MIN( maxlng, str2.length() ); const gunichar sp_wide = Glib::ustring( " " )[0]; #ifdef _DEBUG std::cout << "MISC::leven< str1 = " << str1.raw() << " lng1 = " << lng1 << std::endl << "str2 = " << str2.raw() << " lng2 = " << lng2 << std::endl << "maxlng = " << maxlng << std::endl; #endif dist[ 0 ][ 0 ] = 0; for( size_t i = 1, cost = 0; i <= lng1; ++i ){ const gunichar c = str1[ i-1 ]; // 半角、全角空白の挿入コストは0 if( c != ' ' && c != sp_wide ) ++cost; dist[ i ][ 0 ] = cost; } for( size_t i = 1, cost = 0; i <= lng2; ++i ){ const gunichar c = str2[ i-1 ]; // 半角、空白の削除コストは0 if( c != ' ' && c != sp_wide ) ++cost; dist[ 0 ][ i ] = cost; } for( size_t i = 1; i <= lng1; ++i ){ for( size_t j = 1; j <= lng2; ++j ){ const gunichar c1 = str1[ i-1 ]; const gunichar c2 = str2[ j-1 ]; int cost_replace = dist[ i-1 ][ j-1 ]; if( c1 != c2 ) cost_replace += 1; int cost_insert = dist[ i-1 ][ j ]; // 半角、空白の挿入コストは0 if( c1 != ' ' && c1 != sp_wide ) cost_insert += 1; int cost_delete = dist[ i ][ j-1 ]; // 半角、空白の削除コストは0 if( c2 != ' ' && c2 != sp_wide ) cost_delete += 1; dist[ i ][ j ] = MIN( cost_replace, MIN( cost_insert, cost_delete ) ); } } #ifdef _DEBUG for( size_t i = 0; i <= lng1; ++i ){ for( size_t j = 0; j <= lng2; ++j ) std::cout << dist[ i ][ j ] << " "; std::cout << std::endl; } #endif // 0 - 1 の範囲に正規化 return ( double )dist[ lng1 ][ lng2 ] / MAX( dist[ lng1 ][ 0 ], dist[ 0 ][ lng2 ] ); } jdim-0.7.0/src/jdlib/tfidf.h000066400000000000000000000030751417047150700155740ustar00rootroot00000000000000// ライセンス: GPL2 // // TF-IDF // #ifndef _TFIDF_H #define _TFIDF_H #include #include namespace DBTREE { class ArticleBase; } namespace MISC { typedef std::vector< double > VEC_TFIDF; typedef std::vector< Glib::ustring > VEC_WORDS; typedef std::vector< double > VEC_IDF; // 単語ベクトル作成 void tfidf_create_vec_words( VEC_WORDS& vec_words, const Glib::ustring& document ); // IDF計算 (実際には頻度計算) // vec_idf はあらかじめ resize しておくこと void tfidf_create_vec_idf( VEC_IDF& vec_idf, const Glib::ustring& document, const VEC_WORDS& vec_words ); // documnet に対する TFIDF ベクトル計算 // vec_tfidf はあらかじめ resize しておくこと void tfidf_calc_vec_tfifd( VEC_TFIDF& vec_tfidf, const Glib::ustring& document, const VEC_IDF& vec_idf, const VEC_WORDS& vec_words ); // tfidf1 と tfidf2 の相関計算 double tfidf_cos_similarity( const VEC_TFIDF& vec_tfidf1, const VEC_TFIDF& vec_tfidf2 ); // スレ一覧からIDF 計算 void tfidf_create_vec_idf_from_board( VEC_IDF& vec_idf, const Glib::ustring& subject_src, const std::vector< DBTREE::ArticleBase* >& list_subject, const VEC_WORDS& vec_words ); // str1 と str2 間のレーベンシュタイン距離 // スレ一覧を使用できない場合に使う double leven( std::vector< std::vector< int > >& dist, const Glib::ustring& str1, const Glib::ustring& str2 ); } #endif jdim-0.7.0/src/jdlib/timeout.cpp000066400000000000000000000015011417047150700165110ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "timeout.h" #include "miscmsg.h" using namespace JDLIB; /* * Glib::signal_timeout()はタイムアウトのタイミングを得るために、インターバルに応じたsleepで * g_get_current_time()を呼んで時刻取得することで、タイミングを得ている */ // private Timeout::Timeout( const sigc::slot< bool > slot_timeout ) : m_slot_timeout( slot_timeout ) { } Timeout::~Timeout() { m_connection.disconnect(); } // static std::unique_ptr Timeout::connect( const sigc::slot< bool > slot_timeout, unsigned int interval ) { Timeout* timeout = new Timeout( slot_timeout ); timeout->m_connection = Glib::signal_timeout().connect( slot_timeout, interval ); return std::unique_ptr( timeout ); } jdim-0.7.0/src/jdlib/timeout.h000066400000000000000000000007601417047150700161640ustar00rootroot00000000000000// ライセンス: GPL2 #ifndef _TIMEOUT_H #define _TIMEOUT_H #include #include namespace JDLIB { class Timeout { sigc::slot< bool > m_slot_timeout; sigc::connection m_connection; public: ~Timeout(); static std::unique_ptr connect( const sigc::slot< bool > slot_timeout, unsigned int interval ); private: explicit Timeout( const sigc::slot< bool > slot_timeout ); }; } #endif // _TIMEOUT_H jdim-0.7.0/src/jdversion.h000066400000000000000000000045731417047150700154230ustar00rootroot00000000000000// バージョン情報 #ifndef _JDVER_H #define _JDVER_H #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef HAVE_BUILDINFO_H #include "buildinfo.h" #endif // gitのリポジトリを使ってビルドしているときはリビジョンから日付を取得する // リビジョンが参照できない場合はJDDATE_FALLBACKを使う // SEE ALSO: ENVIRONMENT::get_jdversion() #define MAJORVERSION 0 #define MINORVERSION 7 #define MICROVERSION 0 #define JDDATE_FALLBACK "20220115" #define JDTAG "" //--------------------------------- #define JDCOMMENT "JDim (JD improved) は gtkmm/GTK+ を用いた2chブラウザです。" #define JDCOPYRIGHT "(c) 2006-2015 JD project" "\n" \ "(c) 2017-2019 yama-natuki" "\n" \ "(c) 2019-2022 JDimproved project" #define JDBBS CONFIG::get_url_jdhp()+"cgi-bin/bbs/support/" #define JD2CHLOG CONFIG::get_url_jdhp()+"old2ch/" #define JDHELP "https://jdimproved.github.io/JDim/" #define JDHELPCMD JDHELP "usrcmd/" #define JDHELPREPLSTR JDHELP "replacestr/" // [ ライセンス表記 ] // // 以下の文章は和訳を元にバージョン及び住所を訂正した物です。 // http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt // http://www.opensource.jp/gpl/gpl.ja.html#SEC4 (和訳) #define JDLICENSE JDCOMMENT "\n" \ "\n" \ JDCOPYRIGHT "\n" \ "\n" \ "このプログラムはフリーソフトウェアです。あなたはこれを、フリーソフトウェ" \ "ア財団によって発行された GNU 一般公衆利用許諾契約書(バージョン2)の定める" \ "条件の下で再頒布または改変することができます。\n" \ "\n" \ "このプログラムは有用であることを願って頒布されますが、*全くの無保証* " \ "です。商業可能性の保証や特定の目的への適合性は、言外に示されたものも含" \ "め全く存在しません。詳しくはGNU 一般公衆利用許諾契約書をご覧ください。\n" \ "\n" \ "あなたはこのプログラムと共に、GNU 一般公衆利用許諾契約書の複製物を一部" \ "受け取ったはずです。もし受け取っていなければ、フリーソフトウェア財団ま" \ "で請求してください(宛先は the Free Software Foundation, Inc., 51 " \ "Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA)。\n" #endif jdim-0.7.0/src/linkfiltermanager.cpp000066400000000000000000000077271417047150700174550ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "linkfiltermanager.h" #include "usrcmdmanager.h" #include "cache.h" #include "type.h" #include "command.h" #include "jdlib/jdregex.h" #include "jdlib/miscutil.h" #include "xml/document.h" #include "xml/tools.h" #define ROOT_NODE_NAME_LINKFILTER "linkfilterlist" CORE::Linkfilter_Manager* instance_linkfilter_manager = nullptr; CORE::Linkfilter_Manager* CORE::get_linkfilter_manager() { if( ! instance_linkfilter_manager ) instance_linkfilter_manager = new Linkfilter_Manager(); assert( instance_linkfilter_manager ); return instance_linkfilter_manager; } void CORE::delete_linkfilter_manager() { if( instance_linkfilter_manager ) delete instance_linkfilter_manager; instance_linkfilter_manager = nullptr; } /////////////////////////////////////////////// using namespace CORE; Linkfilter_Manager::Linkfilter_Manager() { std::string xml; if( CACHE::load_rawdata( CACHE::path_linkfilter(), xml ) ) xml2list( xml ); } // // xml -> リスト // void Linkfilter_Manager::xml2list( const std::string& xml ) { m_list_cmd.clear(); if( xml.empty() ) return; const XML::Document document( xml ); const XML::Dom* root = document.get_root_element( std::string( ROOT_NODE_NAME_LINKFILTER ) ); if( ! root ) return; #ifdef _DEBUG std::cout << "Linkfilter_Manager::xml2list"; std::cout << " children =" << document.size() << std::endl; #endif for( const XML::Dom* child : *root ) { if( child->nodeType() == XML::NODE_TYPE_ELEMENT ){ LinkFilterItem item; item.url = child->getAttribute( "url" ); item.cmd = child->getAttribute( "data" ); #ifdef _DEBUG std::cout << "url = " << item.url << " cmd =" << item.cmd << std::endl; #endif if( ! item.url.empty() && ! item.cmd.empty() ) m_list_cmd.push_back( item ); } } } // // XML 保存 // void Linkfilter_Manager::save_xml() { XML::Document document; XML::Dom* root = document.appendChild( XML::NODE_TYPE_ELEMENT, std::string( ROOT_NODE_NAME_LINKFILTER ) ); if( ! root ) return; for( const LinkFilterItem& item : m_list_cmd ) { const std::string& url = item.url; const std::string& cmd = item.cmd; if( ! url.empty() && ! cmd.empty() ){ XML::Dom* node = root->appendChild( XML::NODE_TYPE_ELEMENT, XML::get_name( TYPE_LINKFILTER ) ); node->setAttribute( "url", url ); node->setAttribute( "data", cmd ); } } #ifdef _DEBUG std::cout << "Linkfilter_Manager::save_xml\n"; std::cout << document.get_xml() << std::endl; #endif CACHE::save_rawdata( CACHE::path_linkfilter(), document.get_xml() ); } // // 実行 // // 実行したら true を返す // bool Linkfilter_Manager::exec( const std::string& url, const std::string& link, const std::string& selection ) { if( ! m_list_cmd.size() ) return false; #ifdef _DEBUG std::cout << "Linkfilter_Manager::exec\n" << "url = " << url << std::endl << "link = " << link << std::endl << "selection = " << selection << std::endl; #endif JDLIB::Regex regex; const size_t offset = 0; const bool icase = false; const bool newline = true; const bool usemigemo = false; const bool wchar = false; for( const LinkFilterItem& item : m_list_cmd ) { const std::string& query = item.url; const std::string& cmd = item.cmd; #ifdef _DEBUG std::cout << "query = " << query << std::endl << "cmd = " << cmd << std::endl; #endif if( ! regex.exec( query, link, offset, icase, newline, usemigemo, wchar ) ) continue; // \0 ... \9 までのcmd文字列を置換 const std::string cmd_out = regex.replace( cmd ); // queryと一致したら実行 CORE::get_usrcmd_manager()->exec( cmd_out, url, link, selection, 0 ); return true; } return false; } jdim-0.7.0/src/linkfiltermanager.h000066400000000000000000000017211417047150700171060ustar00rootroot00000000000000// ライセンス: GPL2 // // リンクフィルタの管理クラス // #ifndef _LINKFILTERMANAGER_H #define _LINKFILTERMANAGER_H #include #include namespace CORE { struct LinkFilterItem { std::string url; std::string cmd; }; class Linkfilter_Manager { std::vector< LinkFilterItem > m_list_cmd; public: Linkfilter_Manager(); virtual ~Linkfilter_Manager() noexcept = default; std::vector< LinkFilterItem >& get_list(){ return m_list_cmd; } void save_xml(); // 実行 // 実行したら true を返す bool exec( const std::string& url, const std::string& link, const std::string& selection ); private: void xml2list( const std::string& xml ); }; /////////////////////////////////////// // インターフェース Linkfilter_Manager* get_linkfilter_manager(); void delete_linkfilter_manager(); } #endif jdim-0.7.0/src/linkfilterpref.cpp000066400000000000000000000246501417047150700167710ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "linkfilterpref.h" #include "linkfiltermanager.h" #include "skeleton/msgdiag.h" #include "config/globalconf.h" #include "jdlib/miscutil.h" #include "control/controlid.h" #include "command.h" #include "environment.h" #include using namespace CORE; LinkFilterDiag::LinkFilterDiag( Gtk::Window* parent, const std::string& url, const std::string& cmd ) : SKELETON::PrefDiag( parent, "" ), m_label_url( "アドレス", Gtk::ALIGN_START ), m_label_cmd( "実行するコマンド", Gtk::ALIGN_START ), m_button_manual( "オンラインマニュアルの置換文字一覧を表示" ) { resize( 640, 1 ); m_entry_url.set_text( url ); m_entry_cmd.set_text( cmd ); m_button_manual.signal_clicked().connect( sigc::mem_fun( *this, &LinkFilterDiag::slot_show_manual ) ); m_vbox.set_spacing( 8 ); m_vbox.pack_start( m_label_url, Gtk::PACK_SHRINK ); m_vbox.pack_start( m_entry_url, Gtk::PACK_SHRINK ); m_hbox_cmd.pack_start( m_label_cmd, Gtk::PACK_SHRINK ); m_hbox_cmd.pack_end( m_button_manual, Gtk::PACK_SHRINK ); m_vbox.pack_start( m_hbox_cmd, Gtk::PACK_SHRINK ); m_vbox.pack_start( m_entry_cmd, Gtk::PACK_SHRINK ); set_activate_entry( m_entry_url ); set_activate_entry( m_entry_cmd ); get_content_area()->set_spacing( 8 ); get_content_area()->pack_start( m_vbox ); set_title( "フィルタ設定" ); show_all_children(); } void LinkFilterDiag::slot_show_manual() { CORE::core_set_command( "open_url_browser", ENVIRONMENT::get_jdhelpcmd() ); } /////////////////////////////////////////////// LinkFilterPref::LinkFilterPref( Gtk::Window* parent, const std::string& url ) : SKELETON::PrefDiag( parent, url ) , m_label( "追加ボタンを押すとフィルタ設定を追加出来ます。編集するにはダブルクリックします。" ) , m_button_top( g_dpgettext( GTK_DOMAIN, "Stock label, navigation\x04_Top", 24 ), true ) , m_button_up( g_dpgettext( GTK_DOMAIN, "Stock label, navigation\x04_Up", 24 ), true ) , m_button_down( g_dpgettext( GTK_DOMAIN, "Stock label, navigation\x04_Down", 24 ), true ) , m_button_bottom( g_dpgettext( GTK_DOMAIN, "Stock label, navigation\x04_Bottom", 24 ), true ) , m_button_delete( g_dpgettext( GTK_DOMAIN, "Stock label\x04_Delete", 12 ), true ) , m_button_add( g_dpgettext( GTK_DOMAIN, "Stock label\x04_Add", 12 ), true ) { m_button_top.set_image_from_icon_name( "go-top" ); m_button_up.set_image_from_icon_name( "go-up" ); m_button_down.set_image_from_icon_name( "go-down" ); m_button_bottom.set_image_from_icon_name( "go-bottom" ); m_button_top.signal_clicked().connect( sigc::mem_fun( *this, &LinkFilterPref::slot_top ) ); m_button_up.signal_clicked().connect( sigc::mem_fun( *this, &LinkFilterPref::slot_up ) ); m_button_down.signal_clicked().connect( sigc::mem_fun( *this, &LinkFilterPref::slot_down ) ); m_button_bottom.signal_clicked().connect( sigc::mem_fun( *this, &LinkFilterPref::slot_bottom ) ); m_button_delete.signal_clicked().connect( sigc::mem_fun( *this, &LinkFilterPref::slot_delete ) ); m_button_add.signal_clicked().connect( sigc::mem_fun( *this, &LinkFilterPref::slot_add ) ); m_liststore = Gtk::ListStore::create( m_columns ); m_treeview.set_model( m_liststore ); m_treeview.set_size_request( 640, 400 ); m_treeview.signal_row_activated().connect( sigc::mem_fun( *this, &LinkFilterPref::slot_row_activated ) ); m_treeview.sig_key_release().connect( sigc::mem_fun(*this, &LinkFilterPref::slot_key_release ) ); Gtk::TreeViewColumn* column = Gtk::manage( new Gtk::TreeViewColumn( "アドレス", m_columns.m_col_url ) ); column->set_fixed_width( 240 ); column->set_sizing( Gtk::TREE_VIEW_COLUMN_FIXED ); column->set_resizable( true ); m_treeview.append_column( *column ); column = Gtk::manage( new Gtk::TreeViewColumn( "コマンド", m_columns.m_col_cmd ) ); column->set_fixed_width( 240 ); column->set_sizing( Gtk::TREE_VIEW_COLUMN_FIXED ); column->set_resizable( true ); m_treeview.append_column( *column ); m_scrollwin.add( m_treeview ); m_scrollwin.set_policy( Gtk::POLICY_AUTOMATIC, Gtk::POLICY_ALWAYS ); m_vbuttonbox.pack_start( m_button_top, Gtk::PACK_SHRINK ); m_vbuttonbox.pack_start( m_button_up, Gtk::PACK_SHRINK ); m_vbuttonbox.pack_start( m_button_down, Gtk::PACK_SHRINK ); m_vbuttonbox.pack_start( m_button_bottom, Gtk::PACK_SHRINK ); m_vbuttonbox.pack_start( m_button_delete, Gtk::PACK_SHRINK ); m_vbuttonbox.pack_start( m_button_add, Gtk::PACK_SHRINK ); m_vbuttonbox.set_layout( Gtk::BUTTONBOX_START ); m_vbuttonbox.set_spacing( 4 ); m_hbox.pack_start( m_scrollwin, Gtk::PACK_EXPAND_WIDGET ); m_hbox.pack_start( m_vbuttonbox, Gtk::PACK_SHRINK ); get_content_area()->set_spacing( 8 ); get_content_area()->pack_start( m_label, Gtk::PACK_SHRINK ); get_content_area()->pack_start( m_hbox ); show_all_children(); set_title( "リンクフィルタ設定" ); append_rows(); } void LinkFilterPref::append_rows() { const std::vector& list_item = CORE::get_linkfilter_manager()->get_list(); if( list_item.empty() ) return; for( const LinkFilterItem& item : list_item ) append_row( item.url, item.cmd ); select_row( get_top_row() ); } void LinkFilterPref::append_row( const std::string& url, const std::string& cmd ) { Gtk::TreeModel::Row row; row = *( m_liststore->append() ); if( row ){ row[ m_columns.m_col_url ] = url; row[ m_columns.m_col_cmd ] = cmd; select_row( row ); } } Gtk::TreeModel::iterator LinkFilterPref::get_selected_row() { Gtk::TreeModel::iterator row; std::vector< Gtk::TreeModel::Path > paths = m_treeview.get_selection()->get_selected_rows(); if( ! paths.size() ) return row; row = *( m_liststore->get_iter( *paths.begin() ) ); return row; } Gtk::TreeModel::iterator LinkFilterPref::get_top_row() { Gtk::TreeModel::iterator row; Gtk::TreeModel::Children children = m_liststore->children(); if( children.empty() ) return row; row = children.begin(); return row; } Gtk::TreeModel::iterator LinkFilterPref::get_bottom_row() { Gtk::TreeModel::iterator row; Gtk::TreeModel::Children children = m_liststore->children(); if( children.empty() ) return row; row = --children.end(); return row; } void LinkFilterPref::select_row( const Gtk::TreeModel::iterator& row ) { const Gtk::TreePath path( row ); m_treeview.get_selection()->select( path ); } // // OK ボタンを押した // void LinkFilterPref::slot_ok_clicked() { #ifdef _DEBUG std::cout << "LinkFilterPref::slot_ok_clicked\n"; #endif std::vector< LinkFilterItem >& list_item = CORE::get_linkfilter_manager()->get_list(); list_item.clear(); const Gtk::TreeModel::Children children = m_liststore->children(); Gtk::TreeModel::iterator it = children.begin(); while( it != children.end() ){ Gtk::TreeModel::Row row = ( *it ); if( row ){ LinkFilterItem item; item.url = row[ m_columns.m_col_url ]; item.cmd = row[ m_columns.m_col_cmd ]; list_item.push_back( item ); #ifdef _DEBUG std::cout << item.url << " " << item.cmd << std::endl; #endif } ++it; } CORE::get_linkfilter_manager()->save_xml(); } void LinkFilterPref::slot_row_activated( const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn* column ) { #ifdef _DEBUG std::cout << "LinkFilterPref::slot_row_activated path = " << path.to_string() << std::endl; #endif Gtk::TreeModel::Row row = *( m_liststore->get_iter( path ) ); if( ! row ) return; LinkFilterDiag diag( this, row[ m_columns.m_col_url ], row[ m_columns.m_col_cmd ] ); if( diag.run() == Gtk::RESPONSE_OK ){ row[ m_columns.m_col_url ] = diag.get_url(); row[ m_columns.m_col_cmd ] = diag.get_cmd(); static_cast( row ); // cppcheck: unreadVariable } } bool LinkFilterPref::slot_key_release( GdkEventKey* event ) { const int id = m_control.key_press( event ); #ifdef _DEBUG std::cout << "LinkFilterPref::slot_key_release id = " << id << std::endl; #endif if( id == CONTROL::Delete ) slot_delete(); return true; } // // 一番上へ移動 // void LinkFilterPref::slot_top() { Gtk::TreeModel::iterator row = get_selected_row(); Gtk::TreeModel::iterator row_top = get_top_row(); if( row && row != row_top ) m_liststore->move( row, row_top ); } // // 上へ移動 // void LinkFilterPref::slot_up() { Gtk::TreeModel::iterator row = get_selected_row(); Gtk::TreeModel::iterator row_top = get_top_row(); if( row && row != row_top ){ Gtk::TreePath path_dst( row ); if( path_dst.prev() ){ Gtk::TreeModel::iterator row_dst = m_liststore->get_iter( path_dst ); m_liststore->iter_swap( row, row_dst ); } } } // // 下へ移動 // void LinkFilterPref::slot_down() { Gtk::TreeModel::iterator row = get_selected_row(); Gtk::TreeModel::iterator row_bottom = get_bottom_row(); if( row && row != row_bottom ){ Gtk::TreePath path_dst( row ); path_dst.next(); Gtk::TreeModel::iterator row_dst = m_liststore->get_iter( path_dst ); if( row_dst ) m_liststore->iter_swap( row, row_dst ); } } // // 一番下へ移動 // void LinkFilterPref::slot_bottom() { Gtk::TreeModel::iterator row = get_selected_row(); Gtk::TreeModel::iterator row_bottom = get_bottom_row(); if( row && row != row_bottom ){ m_liststore->move( row, m_liststore->children().end() ); } } // // 削除ボタン // void LinkFilterPref::slot_delete() { Gtk::TreeModel::iterator row = get_selected_row(); if( ! row ) return; SKELETON::MsgDiag mdiag( nullptr, "削除しますか?", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO ); if( mdiag.run() != Gtk::RESPONSE_YES ) return; Gtk::TreePath path_next( row ); path_next.next(); Gtk::TreeModel::iterator row_next = m_liststore->get_iter( path_next ); m_liststore->erase( row ); if( row_next ) select_row( row_next ); else{ Gtk::TreeModel::iterator row_bottom = get_bottom_row(); if( row_bottom ) select_row( row_bottom ); } } // // 追加ボタン // void LinkFilterPref::slot_add() { LinkFilterDiag diag( this, "", "" ); if( diag.run() == Gtk::RESPONSE_OK ) append_row( diag.get_url(), diag.get_cmd() ); } jdim-0.7.0/src/linkfilterpref.h000066400000000000000000000047551417047150700164420ustar00rootroot00000000000000// ライセンス: GPL2 // リンククリック時のフィルタ設定ダイアログ #ifndef _LINKFILTERPREF_H #define _LINKFILTERPREF_H #include "skeleton/prefdiag.h" #include "skeleton/treeviewbase.h" #include "control/control.h" namespace CORE { class LinkFilterDiag : public SKELETON::PrefDiag { Gtk::VBox m_vbox; Gtk::Entry m_entry_url; Gtk::Entry m_entry_cmd; Gtk::Label m_label_url; Gtk::HBox m_hbox_cmd; Gtk::Label m_label_cmd; Gtk::Button m_button_manual; public: LinkFilterDiag( Gtk::Window* parent, const std::string& url, const std::string& cmd ); Glib::ustring get_url() const { return m_entry_url.get_text(); } Glib::ustring get_cmd() const { return m_entry_cmd.get_text(); } private: void slot_show_manual(); }; class TreeColumn : public Gtk::TreeModel::ColumnRecord { public: Gtk::TreeModelColumn< std::string > m_col_url; Gtk::TreeModelColumn< std::string > m_col_cmd; TreeColumn() { add( m_col_url ); add( m_col_cmd ); } }; class LinkFilterPref : public SKELETON::PrefDiag { SKELETON::JDTreeViewBase m_treeview; CONTROL::Control m_control; Glib::RefPtr< Gtk::ListStore > m_liststore; TreeColumn m_columns; Gtk::ScrolledWindow m_scrollwin; Gtk::Label m_label; Gtk::Button m_button_top; Gtk::Button m_button_up; Gtk::Button m_button_down; Gtk::Button m_button_bottom; Gtk::Button m_button_delete; Gtk::Button m_button_add; Gtk::VButtonBox m_vbuttonbox; Gtk::HBox m_hbox; public: LinkFilterPref( Gtk::Window* parent, const std::string& url ); private: void append_rows(); void append_row( const std::string& url, const std::string& cmd ); Gtk::TreeModel::iterator get_selected_row(); Gtk::TreeModel::iterator get_top_row(); Gtk::TreeModel::iterator get_bottom_row(); void select_row( const Gtk::TreeModel::iterator& row ); // OK押した void slot_ok_clicked() override; void slot_row_activated( const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn* column ); bool slot_key_release( GdkEventKey* event ); void slot_top(); void slot_up(); void slot_down(); void slot_bottom(); void slot_delete(); void slot_add(); }; } #endif jdim-0.7.0/src/livepref.cpp000066400000000000000000000063401417047150700155610ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "livepref.h" #include "config/globalconf.h" #include "config/defaultconf.h" #include "global.h" using namespace CORE; LivePref::LivePref( Gtk::Window* parent, const std::string& url ) : SKELETON::PrefDiag( parent, url ), m_label_inst( "実況を行うには始めに板のプロパティで更新間隔を設定して下さい。\n速度を0にするとスクロールしません。" ), m_mode1( m_radiogroup, "速度可変、速度がしきい値を越えると行単位でスクロール(_1)", true ), m_mode2( m_radiogroup, "速度一定、遅れがしきい値を越えると行単位でスクロール(_2)", true ), m_bt_reset( "設定を全てデフォルトに戻す(_F)", true ) { const int mrg = 8; // スクロールモード m_vbox_mode.set_spacing( mrg ); m_vbox_mode.set_border_width( mrg ); m_vbox_mode.pack_start( m_mode1 ); m_vbox_mode.pack_start( m_mode2 ); m_frame_mode.set_label( "オートスクロールモード" ); m_frame_mode.add( m_vbox_mode ); if( CONFIG::get_live_mode() == LIVE_SCRMODE_VARIABLE ) m_mode1.set_active( true ); else m_mode2.set_active( true ); // 速度 m_spin_speed.set_range( 0, 50 ); m_spin_speed.set_increments( 1, 1 ); m_spin_speed.set_value( CONFIG::get_live_speed() ); m_label_speed.set_text_with_mnemonic( "可変モードでの最低速度/一定モードでの速度(_S):" ); m_label_speed.set_mnemonic_widget( m_spin_speed ); m_hbox_speed.set_spacing( mrg ); m_hbox_speed.pack_start( m_label_speed, Gtk::PACK_SHRINK ); m_hbox_speed.pack_start( m_spin_speed, Gtk::PACK_SHRINK ); set_activate_entry( m_spin_speed ); // しきい値 m_spin_th.set_range( 1, 50 ); m_spin_th.set_increments( 1, 1 ); m_spin_th.set_value( CONFIG::get_live_threshold() ); m_label_th.set_text_with_mnemonic( "しきい値(_T):" ); m_label_th.set_mnemonic_widget( m_spin_th ); m_hbox_th.set_spacing( mrg ); m_hbox_th.pack_start( m_label_th, Gtk::PACK_SHRINK ); m_hbox_th.pack_start( m_spin_th, Gtk::PACK_SHRINK ); set_activate_entry( m_spin_th ); m_bt_reset.signal_clicked().connect( sigc::mem_fun( *this, &LivePref::slot_reset ) ); m_vbox.pack_start( m_frame_mode, Gtk::PACK_SHRINK ); m_vbox.pack_start( m_hbox_speed, Gtk::PACK_SHRINK ); m_vbox.pack_start( m_hbox_th, Gtk::PACK_SHRINK ); m_vbox.pack_start( m_bt_reset, Gtk::PACK_SHRINK ); m_vbox.set_border_width( mrg ); get_content_area()->set_spacing( mrg ); get_content_area()->pack_start( m_label_inst, Gtk::PACK_SHRINK ); get_content_area()->pack_start( m_vbox, Gtk::PACK_SHRINK ); set_title( "実況設定" ); show_all_children(); } // OK押した void LivePref::slot_ok_clicked() { if( m_mode1.get_active() ) CONFIG::set_live_mode( LIVE_SCRMODE_VARIABLE ); else CONFIG::set_live_mode( LIVE_SCRMODE_STEADY ); CONFIG::set_live_speed( m_spin_speed.get_value_as_int() ); CONFIG::set_live_threshode( m_spin_th.get_value_as_int() ); } void LivePref::slot_reset() { m_mode1.set_active( true ); m_spin_speed.set_value( CONFIG::CONF_LIVE_SPEED ); m_spin_th.set_value( CONFIG::CONF_LIVE_THRESHOLD ); } jdim-0.7.0/src/livepref.h000066400000000000000000000016311417047150700152240ustar00rootroot00000000000000// ライセンス: GPL2 // 実況設定ダイアログ #ifndef _LIVEPREF_H #define _LIVEPREF_H #include "skeleton/prefdiag.h" namespace CORE { class LivePref : public SKELETON::PrefDiag { Gtk::VBox m_vbox; Gtk::Label m_label_inst; Gtk::Frame m_frame_mode; Gtk::VBox m_vbox_mode; Gtk::RadioButtonGroup m_radiogroup; Gtk::RadioButton m_mode1; Gtk::RadioButton m_mode2; Gtk::HBox m_hbox_speed; Gtk::SpinButton m_spin_speed; Gtk::Label m_label_speed; Gtk::HBox m_hbox_th; Gtk::SpinButton m_spin_th; Gtk::Label m_label_th; Gtk::Button m_bt_reset; public: LivePref( Gtk::Window* parent, const std::string& url ); ~LivePref() noexcept = default; private: // OK押した void slot_ok_clicked() override; void slot_reset(); }; } #endif jdim-0.7.0/src/login2ch.cpp000066400000000000000000000111151417047150700154460ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "login2ch.h" #include "global.h" #include "httpcode.h" #include "session.h" #include "command.h" #include "skeleton/msgdiag.h" #include "config/globalconf.h" #include "jdlib/loaderdata.h" #include "jdlib/miscmsg.h" #include enum { SIZE_OF_RAWDATA = 64 * 1024 }; CORE::Login2ch* instance_login2ch = nullptr; CORE::Login2ch* CORE::get_login2ch() { if( ! instance_login2ch ) instance_login2ch = new CORE::Login2ch(); assert( instance_login2ch ); return instance_login2ch; } void CORE::delete_login2ch() { if( instance_login2ch ){ instance_login2ch->terminate_load(); delete instance_login2ch; } instance_login2ch = nullptr; } using namespace CORE; Login2ch::Login2ch() : SKELETON::Login( URL_LOGIN2CH ) { #ifdef _DEBUG std::cout << "Login2ch::Login2ch\n"; #endif } // // ログアウト // void Login2ch::logout() { #ifdef _DEBUG std::cout << "Login2ch::logout\n"; #endif if( is_loading() ) return; SKELETON::Login::set_login_now( false ); SKELETON::Login::set_sessionid( std::string() ); SESSION::set_login2ch( false ); } // // ログイン開始 // void Login2ch::start_login() { if( is_loading() ) return; #ifdef _DEBUG std::cout << "Login2ch::start_login url = " << CONFIG::get_url_login2ch() << std::endl; #endif set_str_code( "" ); if( ! SESSION::is_online() ){ // ディスパッチャ経由でreceive_finish()を呼ぶ finish(); return; } if( CONFIG::get_url_login2ch().empty() || get_username().empty() || get_passwd().empty() ){ finish(); return; } JDLIB::LOADERDATA data; data.init_for_data(); data.url = CONFIG::get_url_login2ch(); data.agent = "DOLIB/1.00"; data.ex_field = "X-2ch-UA: " + CONFIG::get_x_2ch_ua() + "\r\n"; data.str_post = "ID="; data.str_post += get_username(); data.str_post += "&PW="; data.str_post += get_passwd(); logout(); if( m_rawdata.capacity() < SIZE_OF_RAWDATA ) m_rawdata.reserve( SIZE_OF_RAWDATA ); m_rawdata.clear(); start_load( data ); } // // データ受信 // void Login2ch::receive_data( const char* data, size_t size ) { #ifdef _DEBUG std::cout << "Login2ch::receive_data\n"; #endif m_rawdata.append( data, size ); } // // データ受信完了 // void Login2ch::receive_finish() { #ifdef _DEBUG std::cout << "Login2ch::receive_finish code = " << get_code() << " rawdata size = " << m_rawdata.size() << std::endl; #endif bool show_err = true; if( ! m_rawdata.empty() && get_code() == HTTP_OK ){ // 末尾のLFを除去 const std::size_t pos_lf = m_rawdata.find( '\n' ); if( pos_lf != std::string::npos ) { m_rawdata.erase( pos_lf ); #ifdef _DEBUG std::cout << "removed LF\n"; #endif } // SID 取得 std::string sid = m_rawdata; if( sid.rfind( "SESSION-ID=", 0 ) == 0 ){ sid = sid.substr( strlen( "SESSION-ID=" ) ); #ifdef _DEBUG // std::cout << "sid = " << sid << std::endl; #endif if( sid.rfind( "ERROR", 0 ) != 0 ){ SKELETON::Login::set_login_now( true ); SKELETON::Login::set_sessionid( sid ); show_err = false; SESSION::set_login2ch( true ); } else{ MISC::ERRMSG( "2chログイン失敗 : sid = " + sid ); set_str_code( get_str_code() + "\nIDとパスワードを確認して下さい" ); } } else set_str_code( get_str_code() + "\n認証サーバーのURLを確認して下さい" ); } // エラー表示 if( ! SESSION::is_online() ){ SKELETON::MsgDiag mdiag( nullptr, "オフラインです" ); mdiag.run(); } else if( get_username().empty() || get_passwd().empty() ){ SKELETON::MsgDiag mdiag( nullptr, "IDまたはパスワードが設定されていません\n\n設定→ネットワーク→パスワードで設定してください" ); mdiag.run(); } else if( CONFIG::get_url_login2ch().empty() ){ SKELETON::MsgDiag mdiag( nullptr, "2chの認証サーバのURLが指定されていません。" ); mdiag.run(); } else if( show_err ){ std::string str_err = "ログインに失敗しました。\n"; str_err += get_str_code(); SKELETON::MsgDiag mdiag( nullptr, str_err ); mdiag.run(); } // コアに受信完了を知らせる CORE::core_set_command( "login2ch_finished", "" ); } jdim-0.7.0/src/login2ch.h000066400000000000000000000012461417047150700151170ustar00rootroot00000000000000// ライセンス: GPL2 // // 2chへのログイン管理クラス // // セッション管理やログイン、パスワードの保存などを行う // #ifndef _LOGIN2CH_H #define _LOGIN2CH_H #include "skeleton/login.h" #include namespace CORE { class Login2ch : public SKELETON::Login { std::string m_rawdata; public: Login2ch(); ~Login2ch() = default; void start_login() override; void logout() override; private: void receive_data( const char* , size_t ) override; void receive_finish() override; }; Login2ch* get_login2ch(); void delete_login2ch(); } #endif jdim-0.7.0/src/loginbe.cpp000066400000000000000000000107341417047150700153660ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "loginbe.h" #include "global.h" #include "httpcode.h" #include "session.h" #include "command.h" #include "config/globalconf.h" #include "skeleton/msgdiag.h" #include "jdlib/loaderdata.h" #include "jdlib/miscutil.h" #include "jdlib/jdregex.h" enum { SIZE_OF_RAWDATA = 64 * 1024 }; CORE::LoginBe* instance_loginbe = nullptr; CORE::LoginBe* CORE::get_loginbe() { if( ! instance_loginbe ) instance_loginbe = new CORE::LoginBe(); assert( instance_loginbe ); return instance_loginbe; } void CORE::delete_loginbe() { if( instance_loginbe ){ instance_loginbe->terminate_load(); delete instance_loginbe; } instance_loginbe = nullptr; } using namespace CORE; LoginBe::LoginBe() : SKELETON::Login( URL_LOGINBE ) { #ifdef _DEBUG std::cout << "LoginBe::LoginBe\n"; #endif } // // ログアウト // void LoginBe::logout() { #ifdef _DEBUG std::cout << "LoginBe::logout\n"; #endif if( is_loading() ) return; SKELETON::Login::set_login_now( false ); SKELETON::Login::set_sessionid( std::string() ); SKELETON::Login::set_sessiondata( std::string() ); SESSION::set_loginbe( false ); } // // ログイン開始 // void LoginBe::start_login() { if( is_loading() ) return; #ifdef _DEBUG std::cout << "LoginBe::start_login url = " << CONFIG::get_url_loginbe() << std::endl; #endif set_str_code( "" ); if( ! SESSION::is_online() ){ SKELETON::MsgDiag mdiag( nullptr, "オフラインです" ); mdiag.run(); return; } if( get_username().empty() || get_passwd().empty() ){ SKELETON::MsgDiag mdiag( nullptr, "メールアドレスまたはパスワードが設定されていません\n\n設定→ネットワーク→パスワードで設定してください" ); mdiag.run(); return; } if( CONFIG::get_url_loginbe().empty() ){ SKELETON::MsgDiag mdiag( nullptr, "BEの認証サーバのアドレスが指定されていません。" ); mdiag.run(); return; } JDLIB::LOADERDATA data; data.init_for_data(); data.url = CONFIG::get_url_loginbe(); data.contenttype = "application/x-www-form-urlencoded"; data.str_post = "m=" + MISC::url_encode( get_username() ); data.str_post += "&p=" + MISC::url_encode( get_passwd() ); data.str_post += "&submit=" + MISC::charset_url_encode( "登録", "EUC-JP" ); logout(); if( m_rawdata.capacity() < SIZE_OF_RAWDATA ) m_rawdata.reserve( SIZE_OF_RAWDATA ); m_rawdata.clear(); start_load( data ); } // // データ受信 // void LoginBe::receive_data( const char* data, size_t size ) { #ifdef _DEBUG std::cout << "LoginBe::receive_data\n"; #endif if( m_rawdata.size() + size < SIZE_OF_RAWDATA ){ m_rawdata.append( data, size ); } } // // データ受信完了 // void LoginBe::receive_finish() { #ifdef _DEBUG std::cout << "LoginBe::receive_finish code = " << get_code() << std::endl; std::cout << "rawdata size = " << m_rawdata.size() << std::endl; std::cout << m_rawdata << std::endl; #endif std::string dmdm; std::string mdmd; if( get_code() == HTTP_OK ){ for( const std::string& cookie : cookies() ) { #ifdef _DEBUG std::cout << cookie << std::endl; #endif JDLIB::Regex regex; const size_t offset = 0; const bool icase = false; const bool newline = true; const bool usemigemo = false; const bool wchar = false; std::string query = "DMDM=([^;]*)"; if( regex.exec( query, cookie, offset, icase, newline, usemigemo, wchar ) ) dmdm = regex.str( 1 ); query = "MDMD=([^;]*)"; if( regex.exec( query, cookie, offset, icase, newline, usemigemo, wchar ) ) mdmd = regex.str( 1 ); } } #ifdef _DEBUG std::cout << "dmdm = " << dmdm << " mdmd = " << mdmd << std::endl; #endif if( ! dmdm.empty() && ! mdmd.empty() ){ set_login_now( true ); set_sessionid( dmdm ); set_sessiondata( mdmd ); SESSION::set_loginbe( true ); CORE::core_set_command( "loginbe_finished", "" ); } else{ std::string str_err = "ログインに失敗しました。\n\nBEの認証サーバのアドレスやメールアドレス、パスワード等を確認して下さい。\n\n"; str_err += get_str_code(); SKELETON::MsgDiag mdiag( nullptr, str_err ); mdiag.run(); } } jdim-0.7.0/src/loginbe.h000066400000000000000000000012271417047150700150300ustar00rootroot00000000000000// ライセンス: GPL2 // // BEログイン管理クラス // // セッション管理やログイン、パスワードの保存などを行う // #ifndef _LOGINBE_H #define _LOGINBE_H #include "skeleton/login.h" #include namespace CORE { class LoginBe : public SKELETON::Login { std::string m_rawdata; public: LoginBe(); ~LoginBe() = default; void start_login() override; void logout() override; private: void receive_data( const char* , size_t ) override; void receive_finish() override; }; LoginBe* get_loginbe(); void delete_loginbe(); } #endif jdim-0.7.0/src/main.cpp000066400000000000000000000353141417047150700146740ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG //#define _DEBUG_MEM_PROFILE #include "jddebug.h" #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "config/globalconf.h" #include "winmain.h" #include "cache.h" #include "environment.h" #include "iomonitor.h" #include "jdlib/miscmsg.h" #include "jdlib/miscutil.h" #include "jdlib/ssl.h" #include "jdlib/jdregex.h" #include #include #include #include #include #include #include #include #ifdef USE_XSMP #include #include struct XSMPDATA { SmcConn smc_connect; IceConn ice_connect; guint id_process_message; }; #endif enum { MAX_SAFE_ARGC = 4, // 引数の数の制限値 MAX_SAFE_ARGV = 1024 // 各引数の文字列の制限値 }; JDWinMain* Win_Main = nullptr; // SIGINTのハンドラ void sig_handler( int sig ) { if( sig == SIGHUP || sig == SIGINT || sig == SIGQUIT ){ #ifdef _DEBUG std::cout << "sig_handler sig = " << sig << std::endl; #endif if( Win_Main ) Win_Main->save_session(); } exit(0); } // XSMPによるセッション管理 #ifdef USE_XSMP // セッションが終了したので情報を保存するコールバック関数 void xsmp_session_save_yourself( SmcConn smc_connect, SmPointer client_data, int save_type, Bool shutdown, int interact_style, Bool fast ) { if( shutdown && !fast ){ #ifdef _DEBUG std::cout << "session_save_yourself\n"; #endif if( Win_Main ) Win_Main->save_session(); } SmcSaveYourselfDone( smc_connect, TRUE ) ; } // ダミー void xsmp_session_die( SmcConn conn, SmPointer data ){} void xsmp_session_save_complete( SmcConn conn, SmPointer data ){} void xsmp_session_shutdown_cancelled( SmcConn conn, SmPointer data ){} gboolean ice_process_message( GIOChannel *channel, GIOCondition condition, XSMPDATA *xsmpdata ) { if( ! xsmpdata->ice_connect ) return FALSE; IceProcessMessagesStatus status = IceProcessMessages( xsmpdata->ice_connect, nullptr, nullptr ); #ifdef _DEBUG std::cout << "ice_process_message status = " << status << std::endl; #endif if( status == IceProcessMessagesIOError ){ #ifdef _DEBUG std::cout << "ice_process_message IOError\n"; #endif IceCloseConnection( xsmpdata->ice_connect ); xsmpdata->ice_connect = nullptr; return FALSE; } return TRUE; } void ice_watch_proc( IceConn ice_connect, IcePointer client_data, Bool opening, IcePointer *watch_data ) { XSMPDATA *xsmpdata = reinterpret_cast( client_data ); xsmpdata->ice_connect = ice_connect; if( xsmpdata->id_process_message ){ #ifdef _DEBUG std::cout << "ice_watch_proc remove\n"; #endif g_source_remove( xsmpdata->id_process_message ); xsmpdata->id_process_message = 0; } else if( opening && xsmpdata->ice_connect ){ int fd = IceConnectionNumber( xsmpdata->ice_connect ); if( fd >= 0 ){ #ifdef _DEBUG std::cout << "ice_watch_proc opening fd = " << fd << std::endl; #endif GIOChannel *channel = g_io_channel_unix_new( fd ); *watch_data = xsmpdata; xsmpdata->id_process_message = g_io_add_watch_full( channel, G_PRIORITY_DEFAULT, ( GIOCondition )( G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP ), ( GIOFunc ) ice_process_message, xsmpdata, nullptr ); g_io_channel_unref( channel ); } } } // XSMP初期化関数 void xsmp_session_init( XSMPDATA* xsmpdata ) { if( xsmpdata->smc_connect ) return; if( g_getenv("SESSION_MANAGER") == nullptr ) return; #ifdef _DEBUG std::cout << "SESSION_MANAGER = " << g_getenv("SESSION_MANAGER") << std::endl; #endif IceAddConnectionWatch( ice_watch_proc, xsmpdata ); SmcCallbacks smc_callbacks; memset( &smc_callbacks, 0, sizeof( SmcCallbacks ) ); smc_callbacks.save_yourself.callback = xsmp_session_save_yourself; smc_callbacks.die.callback = xsmp_session_die; smc_callbacks.save_complete.callback = xsmp_session_save_complete; smc_callbacks.shutdown_cancelled.callback = xsmp_session_shutdown_cancelled; gchar *id = nullptr; char errstr[1024] = "[XSMP] Failed connecting to the session manager. "; const int header_msg_size = std::strlen( errstr ); xsmpdata->smc_connect = SmcOpenConnection( nullptr, nullptr, SmProtoMajor, SmProtoMinor, SmcSaveYourselfProcMask | SmcDieProcMask | SmcSaveCompleteProcMask | SmcShutdownCancelledProcMask, &smc_callbacks, nullptr, &id, 1023 - header_msg_size, errstr + header_msg_size ); if( ! xsmpdata->smc_connect ){ MISC::ERRMSG( errstr ); return; } } // XSMP終了関数 void xsmp_session_end( XSMPDATA* xsmpdata ) { if( xsmpdata->smc_connect ) SmcCloseConnection( xsmpdata->smc_connect, 0, nullptr ); xsmpdata->smc_connect = nullptr; } #endif //////////////////////////////////////////////////////////// void usage( const int status ) { // -h, --help で表示するメッセージ std::stringstream help_message; help_message << "Usage: jdim [OPTION] [URL,FILE]\n" "\n" "-h, --help\n" " Display this information\n" //"-t , --tab=\n" //" URL open of BBS etc by Tab\n" "-m, --multi\n" " Do not quit even if multiple sub-process\n" "-s, --skip-setup\n" " Skip the setup dialog\n" "-l, --logfile\n" " Write message to msglog file\n" "-g, --geometry WxH-X+Y\n" " The initial size and location\n" "-V, --version\n" " Display version of this program\n"; std::cout << help_message.str() << std::endl; exit( status ); } int main( int argc, char **argv ) { /*--- 引数処理 --------------------------------------------------*/ // 引数の数を制限 if( argc > MAX_SAFE_ARGC ) { MISC::ERRMSG( "main(): Too many arguments." ); exit( EXIT_FAILURE ); } // 各引数の文字数を制限 int i; for( i = 0; i < argc; ++i ) { if( strlen( argv[ i ] ) > MAX_SAFE_ARGV ) { MISC::ERRMSG( "main(): Too many the strings in argument." ); exit( EXIT_FAILURE ); } } // "現在のタブ/新規タブ"など引数によって開き方を変えたい場合は、--tab= // など新しいオプションを追加する // --help, --tab=, --multi, --logfile --version const struct option options[] = { { "help", 0, nullptr, 'h' }, //{ "tab", 1, nullptr, 't' }, { "multi", 0, nullptr, 'm' }, { "skip-setup", 0, nullptr, 's' }, { "logfile", 0, nullptr, 'l' }, { "geometry", required_argument, nullptr, 'g' }, { "version", 0, nullptr, 'V' }, { nullptr, 0, nullptr, 0 } }; std::string url; bool multi_mode = false; bool skip_setupdiag = false; bool logfile_mode = false; int init_w = -1; int init_h = -1; int init_x = -1; int init_y = -1; JDLIB::Regex regex; const size_t offset = 0; const bool icase = false; const bool newline = true; const bool usemigemo = false; const bool wchar = false; std::string query; // -h, -t , -m, -s, -l, -g WxH+X+Y, -V int opt = 0; while( ( opt = getopt_long( argc, argv, "ht:mslg:V", options, nullptr ) ) != -1 ) { switch( opt ) { case 'h': usage( EXIT_SUCCESS ); break; //case 't': //url = optarg; //break; case 'm': multi_mode = true; break; case 's': skip_setupdiag = true; break; case 'l': // メッセージをログファイルに出力 logfile_mode = true; break; case 'g': if( ! optarg ) usage( EXIT_FAILURE ); query = "(([0-9]*)x([0-9]*))?\\-([0-9]*)\\+([0-9]*)"; if( regex.exec( query, optarg, offset, icase, newline, usemigemo, wchar ) ){ if( regex.length( 2 ) ) init_w = atoi( regex.str( 2 ).c_str() ); if( regex.length( 3 ) ) init_h = atoi( regex.str( 3 ).c_str() ); if( regex.length( 4 ) ) init_x = atoi( regex.str( 4 ).c_str() ); if( regex.length( 5 ) ) init_y = atoi( regex.str( 5 ).c_str() ); } else usage( EXIT_FAILURE ); break; case 'V': // バージョンと完全なconfigureオプションを表示 std::cout << ENVIRONMENT::get_progname() << " " << ENVIRONMENT::get_jdversion() << "\n" << ENVIRONMENT::get_jdcopyright() << "\n" "configure: " << ENVIRONMENT::get_configure_args( ENVIRONMENT::CONFIGURE_FULL ) << std::endl; exit( 0 ); break; default: usage( EXIT_FAILURE ); } } if( url.empty() ) { // 引数がURLのみの場合 if( argc > optind ) { url = argv[ optind ]; } // -m 、-s でなく、URLを含まない引数だけの場合は終了 else if( optind > 1 && ! ( multi_mode || skip_setupdiag || logfile_mode ) ){ usage( EXIT_FAILURE ); } } /*---------------------------------------------------------------*/ // SIGINT、SIGQUITのハンドラ設定 struct sigaction sigact; sigset_t blockset; sigemptyset( &blockset ); sigaddset( &blockset, SIGHUP ); sigaddset( &blockset, SIGINT ); sigaddset( &blockset, SIGQUIT ); sigaddset( &blockset, SIGTERM ); memset( &sigact, 0, sizeof(struct sigaction) ); sigact.sa_handler = sig_handler; sigact.sa_mask = blockset; sigact.sa_flags = SA_RESETHAND; // シグナルハンドラ設定 if( sigaction( SIGHUP, &sigact, nullptr ) != 0 || sigaction( SIGINT, &sigact, nullptr ) != 0 || sigaction( SIGQUIT, &sigact, nullptr ) != 0 ){ fprintf( stderr, "sigaction failed\n" ); exit( 1 ); } Gtk::Main m( &argc, &argv ); // XSMPによるセッション管理 #ifdef USE_XSMP #ifdef _DEBUG std::cout << "USE_XSMP\n"; #endif XSMPDATA xsmpdata; memset( &xsmpdata, 0, sizeof( XSMPDATA ) ); xsmp_session_init( &xsmpdata ); #endif JDLIB::init_ssl(); // 全体設定ロード bool init = !( CONFIG::load_conf() ); if( init ){ // 設定ファイルが読み込めないときに存在するか確認 int exists = CACHE::file_exists( CACHE::path_conf() ); if( exists == CACHE::EXIST_FILE || exists == CACHE::EXIST_DIR ){ std::string msg = "JDimの設定ファイル(" + CACHE::path_conf() + ")は存在しますが読み込むことが出来ませんでした。\n\n起動しますか?"; Gtk::MessageDialog mdiag( msg, false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO ); const int ret = mdiag.run(); if( ret != Gtk::RESPONSE_YES ) return 0; } // 初回起動時にルートを作る CACHE::mkdir_root(); } // メッセージをログファイルに出力 if( logfile_mode && CACHE::mkdir_logroot() ){ FILE *tmp; // warning 消し const std::string logfile = Glib::locale_from_utf8( CACHE::path_msglog() ); tmp = freopen( logfile.c_str(), "ab", stdout ); setbuf( tmp, nullptr ); tmp = freopen( logfile.c_str(), "ab", stderr ); setbuf( tmp, nullptr ); } /*--- IOMonitor -------------------------------------------------*/ CORE::IOMonitor iomonitor; // FIFOの状態をチェックする if( iomonitor.get_fifo_stat() == CORE::FIFO_OK ) { // 引数にURLがある if( ! url.empty() ) { // ローカルファイルかどうかチェック const std::string url_real = CACHE::get_realpath( url ); if( ! url_real.empty() ) url = url_real; // FIFOに書き込む iomonitor.send_command( url.c_str() ); // マルチモードでなく、メインプロセスでもない場合は終了 if( ! multi_mode && ! iomonitor.is_main_process() ) return 0; } // マルチモードでなく、メインプロセスでもない場合は問い合わせる else if( ! multi_mode && ! iomonitor.is_main_process() ) { Gtk::MessageDialog mdiag( "JDimは既に起動しています。起動しますか?\n\n起動中のJDimが存在しない場合\n" + CACHE::path_lock() + " を削除してください。", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO ); mdiag.set_title( "ロックファイルが見つかりました" ); int ret = mdiag.run(); if( ret != Gtk::RESPONSE_YES ) return 0; } } // FIFOに問題がある(FATで作成出来ないなど) else { if( CONFIG::get_show_diag_fifo_error() ) { Gtk::MessageDialog mdiag( CACHE::path_lock() + "の作成またはオープンに問題があります。このまま起動しますか?", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO ); Gtk::CheckButton chk_button( "今後表示しない" ); mdiag.get_content_area()->pack_start( chk_button, Gtk::PACK_SHRINK ); chk_button.show(); const int ret = mdiag.run(); CONFIG::set_show_diag_fifo_error( ! chk_button.get_active() ); if( ret != Gtk::RESPONSE_YES ) { CONFIG::save_conf(); return 1; } } } Win_Main = new JDWinMain( init, skip_setupdiag, init_w, init_h, init_x, init_y ); if( Win_Main ){ m.run( *Win_Main ); delete Win_Main; Win_Main = nullptr; } #ifdef USE_XSMP xsmp_session_end( &xsmpdata ); #endif JDLIB::deinit_ssl(); return 0; } jdim-0.7.0/src/mainitempref.cpp000066400000000000000000000042071417047150700164250ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "mainitempref.h" #include "config/globalconf.h" #include "icons/iconmanager.h" #include "jdlib/miscutil.h" #include "global.h" #include "session.h" #include "command.h" using namespace CORE; MainItemPref::MainItemPref( Gtk::Window* parent, const std::string& url ) : SKELETON::SelectItemPref( parent, url ) { // デフォルトの項目を設定 append_default_pair( ITEM_NAME_BBSLISTVIEW, ICON::get_icon( ICON::BBSLISTVIEW ) ); append_default_pair( ITEM_NAME_FAVORITEVIEW, ICON::get_icon( ICON::FAVORITEVIEW ) ); append_default_pair( ITEM_NAME_HISTVIEW, ICON::get_icon( ICON::HISTVIEW ) ); append_default_pair( ITEM_NAME_HIST_BOARDVIEW, ICON::get_icon( ICON::HIST_BOARDVIEW ) ); append_default_pair( ITEM_NAME_HIST_CLOSEVIEW, ICON::get_icon( ICON::HIST_CLOSEVIEW ) ); append_default_pair( ITEM_NAME_HIST_CLOSEBOARDVIEW, ICON::get_icon( ICON::HIST_CLOSEBOARDVIEW ) ); append_default_pair( ITEM_NAME_HIST_CLOSEIMGVIEW, ICON::get_icon( ICON::HIST_CLOSEIMGVIEW ) ); append_default_pair( ITEM_NAME_BOARDVIEW, ICON::get_icon( ICON::BOARDVIEW ) ); append_default_pair( ITEM_NAME_ARTICLEVIEW, ICON::get_icon( ICON::ARTICLEVIEW ) ); if( CONFIG::get_use_image_view() ) { append_default_pair( ITEM_NAME_IMAGEVIEW, ICON::get_icon( ICON::IMAGEVIEW ) ); } append_default_pair( ITEM_NAME_SEPARATOR, ICON::get_icon( ICON::TRANSPARENT ) ); append_default_pair( ITEM_NAME_URL, ICON::get_icon( ICON::TRANSPARENT ) ); append_default_pair( ITEM_NAME_GO, ICON::get_icon( ICON::GO ) ); append_default_pair( ITEM_NAME_SEPARATOR, ICON::get_icon( ICON::TRANSPARENT ) ); // 文字列を元に列を追加 append_rows( SESSION::get_items_main_toolbar_str() ); set_title( "ツールバー項目設定(メイン)" ); } // OKを押した void MainItemPref::slot_ok_clicked() { SESSION::set_items_main_toolbar_str( get_items() ); CORE::core_set_command( "update_main_toolbar_button" ); } // // デフォルトボタン // void MainItemPref::slot_default() { append_rows( SESSION::get_items_main_toolbar_default_str() ); } jdim-0.7.0/src/mainitempref.h000066400000000000000000000010401417047150700160620ustar00rootroot00000000000000// ライセンス: GPL2 // メインツールバーの表示項目設定 #ifndef _MAINITEMPREF_H #define _MAINITEMPREF_H #include "skeleton/selectitempref.h" namespace CORE { class MainItemPref : public SKELETON::SelectItemPref { public: MainItemPref( Gtk::Window* parent, const std::string& url ); ~MainItemPref() noexcept = default; private: // OK押した void slot_ok_clicked() override; // デフォルトボタン void slot_default() override; }; } #endif jdim-0.7.0/src/maintoolbar.cpp000066400000000000000000000106571417047150700162620ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "maintoolbar.h" #include "config/globalconf.h" #include "icons/iconmanager.h" #include "control/controlutil.h" #include "control/controlid.h" #include "session.h" #include "global.h" using namespace CORE; MainToolBar::MainToolBar() : SKELETON::ToolBar( nullptr ) , m_button_go( ICON::GO, ITEM_NAME_GO ) , m_button_bbslist( ICON::BBSLISTVIEW, ITEM_NAME_BBSLISTVIEW ) , m_button_favorite( ICON::FAVORITEVIEW, ITEM_NAME_FAVORITEVIEW ) , m_button_hist( ICON::HISTVIEW, ITEM_NAME_HISTVIEW ) , m_button_hist_board( ICON::HIST_BOARDVIEW, ITEM_NAME_HIST_BOARDVIEW ) , m_button_hist_close( ICON::HIST_CLOSEVIEW, ITEM_NAME_HIST_CLOSEVIEW ) , m_button_hist_closeboard( ICON::HIST_CLOSEBOARDVIEW, ITEM_NAME_HIST_CLOSEBOARDVIEW ) , m_button_hist_closeimg( ICON::HIST_CLOSEIMGVIEW, ITEM_NAME_HIST_CLOSEIMGVIEW ) , m_button_board( ICON::BOARDVIEW, ITEM_NAME_BOARDVIEW ) , m_button_thread( ICON::ARTICLEVIEW, ITEM_NAME_ARTICLEVIEW ) , m_button_image( ICON::IMAGEVIEW, ITEM_NAME_IMAGEVIEW ) { m_entry_url.set_size_request( 0 ); m_tool_url.add( m_entry_url ); m_tool_url.set_expand( true ); set_tooltip( m_button_go, ITEM_NAME_GO ); set_tooltip( m_button_bbslist, std::string( ITEM_NAME_BBSLISTVIEW ) + "\n\nお気に入りに切替え " + CONTROL::get_str_motions( CONTROL::TabRight ) ); set_tooltip( m_button_favorite, std::string( ITEM_NAME_FAVORITEVIEW ) + "\n\n板一覧に切替え " + CONTROL::get_str_motions( CONTROL::TabLeft ) ); set_tooltip( m_button_hist, std::string( ITEM_NAME_HISTVIEW ) ); set_tooltip( m_button_hist_board, std::string( ITEM_NAME_HIST_BOARDVIEW ) ); set_tooltip( m_button_hist_close, std::string( ITEM_NAME_HIST_CLOSEVIEW ) ); set_tooltip( m_button_hist_closeboard, std::string( ITEM_NAME_HIST_CLOSEBOARDVIEW ) ); set_tooltip( m_button_hist_closeimg, std::string( ITEM_NAME_HIST_CLOSEIMGVIEW ) ); set_tooltip( m_button_board, std::string( ITEM_NAME_BOARDVIEW ) + "\n\n" + CONTROL::get_label_motions( CONTROL::ToggleArticle ) ); set_tooltip( m_button_thread, std::string( ITEM_NAME_ARTICLEVIEW ) + "\n\n" + CONTROL::get_label_motions( CONTROL::ToggleArticle ) ); set_tooltip( m_button_image, std::string( ITEM_NAME_IMAGEVIEW ) + "\n\nスレビューに切替 " + CONTROL::get_str_motions( CONTROL::ToggleArticle ) + " , " + CONTROL::get_str_motions( CONTROL::Left ) ); MainToolBar::pack_buttons(); } MainToolBar::~MainToolBar() noexcept = default; // ボタンのパッキング // virtual void MainToolBar::pack_buttons() { int num = 0; for(;;){ int item = SESSION::get_item_main_toolbar( num ); if( item == ITEM_END ) break; switch( item ){ case ITEM_BBSLISTVIEW: get_buttonbar().append( m_button_bbslist ); break; case ITEM_FAVORITEVIEW: get_buttonbar().append( m_button_favorite ); break; case ITEM_HISTVIEW: get_buttonbar().append( m_button_hist ); break; case ITEM_HIST_BOARDVIEW: get_buttonbar().append( m_button_hist_board ); break; case ITEM_HIST_CLOSEVIEW: get_buttonbar().append( m_button_hist_close ); break; case ITEM_HIST_CLOSEBOARDVIEW: get_buttonbar().append( m_button_hist_closeboard ); break; case ITEM_HIST_CLOSEIMGVIEW: get_buttonbar().append( m_button_hist_closeimg ); break; case ITEM_BOARDVIEW: get_buttonbar().append( m_button_board ); break; case ITEM_ARTICLEVIEW: get_buttonbar().append( m_button_thread ); break; case ITEM_IMAGEVIEW: if( CONFIG::get_use_image_view() ) get_buttonbar().append( m_button_image ); break; case ITEM_URL: get_buttonbar().append( m_tool_url ); break; case ITEM_GO: get_buttonbar().append( m_button_go ); break; case ITEM_SEPARATOR: pack_separator(); break; } ++num; } set_relief(); show_all_children(); } jdim-0.7.0/src/maintoolbar.h000066400000000000000000000022361417047150700157210ustar00rootroot00000000000000// ライセンス: GPL2 // メインツールバークラス // // CORE::Core 以外では使わない // #ifndef _MAIN_TOOLBAR_H #define _MAIN_TOOLBAR_H #include #include "skeleton/toolbar.h" #include "skeleton/imgtoolbutton.h" namespace CORE { class MainToolBar : public SKELETON::ToolBar { friend class Core; Gtk::ToolItem m_tool_url; Gtk::Entry m_entry_url; SKELETON::ImgToolButton m_button_go; SKELETON::ImgToggleToolButton m_button_bbslist; SKELETON::ImgToggleToolButton m_button_favorite; SKELETON::ImgToggleToolButton m_button_hist; SKELETON::ImgToggleToolButton m_button_hist_board; SKELETON::ImgToggleToolButton m_button_hist_close; SKELETON::ImgToggleToolButton m_button_hist_closeboard; SKELETON::ImgToggleToolButton m_button_hist_closeimg; SKELETON::ImgToggleToolButton m_button_board; SKELETON::ImgToggleToolButton m_button_thread; SKELETON::ImgToggleToolButton m_button_image; public: MainToolBar(); ~MainToolBar() noexcept; protected: void pack_buttons() override; }; } #endif jdim-0.7.0/src/menuslots.cpp000066400000000000000000000667231417047150700160110ustar00rootroot00000000000000// ライセンス: GPL2 // メインウィンドウのメニューのslot関数 //#define _DEBUG #include "jddebug.h" #include "core.h" #include "command.h" #include "cache.h" #include "session.h" #include "login2ch.h" #include "loginbe.h" #include "winmain.h" #include "fontid.h" #include "colorid.h" #include "global.h" #include "sharedbuffer.h" #include "linkfiltermanager.h" #include "prefdiagfactory.h" #include "environment.h" #include "config/globalconf.h" #include "config/defaultconf.h" #include "control/controlutil.h" #include "skeleton/msgdiag.h" #include "skeleton/aboutdiag.h" #include "dbtree/interface.h" #include "dbimg/imginterface.h" #include "jdlib/miscutil.h" #include "message/logmanager.h" #include "bbslist/bbslistadmin.h" #include "board/boardadmin.h" #include "article/articleadmin.h" #include "image/imageadmin.h" #include "message/messageadmin.h" using namespace CORE; // // URLを開く // void Core::slot_openurl() { auto pref = CORE::PrefDiagFactory( nullptr, CORE::PREFDIAG_OPENURL, "" ); pref->run(); } // // オンライン、オフライン切替え // void Core::slot_toggle_online() { SESSION::set_online( !SESSION::is_online() ); set_maintitle(); // オートリロードキャンセル if( ! SESSION::is_online() ){ BOARD::get_admin()->set_command( "cancel_reload" ); ARTICLE::get_admin()->set_command( "cancel_reload" ); } } // // 2chにログイン // void Core::slot_toggle_login2ch() { if( ! m_enable_menuslot ) return; #ifdef _DEBUG std::cout << "Core::slot_toggle_login2ch\n"; #endif // ログイン中ならログアウト if( CORE::get_login2ch()->login_now() ){ CORE::get_login2ch()->logout(); set_maintitle(); } // ログオフ中ならログイン開始 else CORE::get_login2ch()->start_login(); } // // BEにログイン // void Core::slot_toggle_loginbe() { if( ! m_enable_menuslot ) return; #ifdef _DEBUG std::cout << "Core::slot_toggle_loginbe\n"; #endif // ログイン中ならログアウト if( CORE::get_loginbe()->login_now() ) CORE::get_loginbe()->logout(); // ログオフ中ならログイン開始 else CORE::get_loginbe()->start_login(); set_maintitle(); } // // 板リスト再読込 // void Core::slot_reload_list() { if( ! SESSION::is_online() ){ SKELETON::MsgDiag mdiag( nullptr, "オフラインです" ); mdiag.run(); return; } DBTREE::download_bbsmenu(); CORE::core_set_command( "set_status","", "板一覧再読み込み中...." ); } // // 終了 // void Core::slot_quit() { m_win_main.hide(); } // // スレ一覧のsinceの表示モード void Core::slot_toggle_since( const int mode ) { if( ! m_enable_menuslot ) return; if( SESSION::get_col_since_time() == mode ) return; #ifdef _DEBUG std::cout << "Core::slot_toggle_since mode = " << mode << std::endl; #endif SESSION::set_col_since_time( mode ); DBTREE::reset_all_since_date(); BOARD::get_admin()->set_command( "relayout_all" ); } // // スレ一覧の最終書込の表示モード void Core::slot_toggle_write( const int mode ) { if( ! m_enable_menuslot ) return; if( SESSION::get_col_write_time() == mode ) return; #ifdef _DEBUG std::cout << "Core::slot_toggle_write mode = " << mode << std::endl; #endif SESSION::set_col_write_time( mode ); DBTREE::reset_all_write_date(); BOARD::get_admin()->set_command( "relayout_all" ); } // // スレ一覧の最終取得の表示モード void Core::slot_toggle_access( const int mode ) { if( ! m_enable_menuslot ) return; if( SESSION::get_col_access_time() == mode ) return; #ifdef _DEBUG std::cout << "Core::slot_toggle_access mode = " << mode << std::endl; #endif SESSION::set_col_access_time( mode ); DBTREE::reset_all_access_date(); BOARD::get_admin()->set_command( "relayout_all" ); } // // メインツールバー表示切り替え // void Core::slot_toggle_toolbarmain() { if( SESSION::is_booting() ) return; if( ! m_enable_menuslot ) return; pack_widget( true ); SESSION::set_show_main_toolbar( ! SESSION::get_show_main_toolbar() ); pack_widget( false ); restore_focus( true, false ); } // // メインツールバーの表示位置 // void Core::slot_toggle_toolbarpos( const int pos ) { if( SESSION::is_booting() ) return; if( ! m_enable_menuslot ) return; #ifdef _DEBUG std::cout << "Core::slot_toggle_toolbarpos pos = " << pos << " / " << SESSION::get_toolbar_pos() << std::endl; #endif if( ! SESSION::get_show_main_toolbar() ) SESSION::set_toolbar_pos( pos ); else{ pack_widget( true ); SESSION::set_toolbar_pos( pos ); pack_widget( false ); restore_focus( true, false ); } } // // 板一覧のツールバー表示切り替え // void Core::slot_toggle_toolbarbbslist() { if( SESSION::is_booting() ) return; if( ! m_enable_menuslot ) return; SESSION::set_show_bbslist_toolbar( ! SESSION::get_show_bbslist_toolbar() ); BBSLIST::get_admin()->set_command_immediately( "toggle_toolbar" ); } // // スレ一覧のツールバー表示切り替え // void Core::slot_toggle_toolbarboard() { if( SESSION::is_booting() ) return; if( ! m_enable_menuslot ) return; SESSION::set_show_board_toolbar( ! SESSION::get_show_board_toolbar() ); BOARD::get_admin()->set_command_immediately( "toggle_toolbar" ); } // // スレビューのツールバー表示切り替え // void Core::slot_toggle_toolbararticle() { if( SESSION::is_booting() ) return; if( ! m_enable_menuslot ) return; SESSION::set_show_article_toolbar( ! SESSION::get_show_article_toolbar() ); ARTICLE::get_admin()->set_command_immediately( "toggle_toolbar" ); } // // スレ一覧のタブ表示切り替え // void Core::slot_toggle_tabboard() { if( SESSION::is_booting() ) return; if( ! m_enable_menuslot ) return; SESSION::set_show_board_tab( ! SESSION::get_show_board_tab() ); BOARD::get_admin()->set_command_immediately( "toggle_tab" ); } // // スレビューのタブ表示切り替え // void Core::slot_toggle_tabarticle() { if( SESSION::is_booting() ) return; if( ! m_enable_menuslot ) return; SESSION::set_show_article_tab( ! SESSION::get_show_article_tab() ); ARTICLE::get_admin()->set_command_immediately( "toggle_tab" ); } // // 2paneモード // void Core::slot_toggle_2pane() { if( SESSION::get_mode_pane() == SESSION::MODE_2PANE ) return; pack_widget( true ); SESSION::set_mode_pane( SESSION::MODE_2PANE ); pack_widget( false ); restore_focus( true, false ); } // // 3paneモード // void Core::slot_toggle_3pane() { if( SESSION::get_mode_pane() == SESSION::MODE_3PANE ) return; pack_widget( true ); SESSION::set_mode_pane( SESSION::MODE_3PANE ); pack_widget( false ); restore_focus( true, false ); } // // 縦3paneモード // void Core::slot_toggle_v3pane() { if( SESSION::get_mode_pane() == SESSION::MODE_V3PANE ) return; pack_widget( true ); SESSION::set_mode_pane( SESSION::MODE_V3PANE ); pack_widget( false ); restore_focus( true, false ); } // // フルスクリーン // void Core::slot_toggle_fullscreen() { if( ! m_enable_menuslot ) return; if( SESSION::is_full_win_main() ) m_win_main.unfullscreen(); else m_win_main.fullscreen(); } // // messageビューをウィンドウ表示 // void Core::slot_toggle_winmsg() { pack_widget( true ); SESSION::set_embedded_mes( false ); pack_widget( false ); restore_focus( true, false ); } // // messageビューを埋め込み表示 // void Core::slot_toggle_embmsg() { pack_widget( true ); SESSION::set_embedded_mes( true ); pack_widget( false ); restore_focus( true, false ); } // // messgeビューのwrap切り替え // void Core::slot_toggle_msg_wrap() { CORE::core_set_command( "toggle_message_wrap", "" ); } // // imageビュー表示設定 // void Core::slot_toggle_imgview( const int mode ) { if( SESSION::is_booting() ) return; if( ! m_enable_menuslot ) return; // imageビューの状態 int current_mode = IMGVIEW_NO; if( CONFIG::get_use_image_view() ){ if( SESSION::get_embedded_img() ) current_mode = IMGVIEW_EMB; else current_mode = IMGVIEW_WINDOW; } if( current_mode == mode ) return; // imageビュー使用切り替え if( mode == IMGVIEW_NO ){ CONFIG::set_use_image_view( false ); IMAGE::get_admin()->set_command( "close_all_views" ); } else { CONFIG::set_use_image_view( true ); } // ウィンドウ、埋め込みモード切り替え pack_widget( true ); if( mode == IMGVIEW_EMB ) SESSION::set_embedded_img( true ); else SESSION::set_embedded_img( false ); pack_widget( false ); SESSION::set_focused_admin( SESSION::FOCUS_NOT ); SESSION::set_focused_admin_sidebar( SESSION::FOCUS_NOT ); restore_focus( true, false ); } // // 画像ポップアップon/off // void Core::slot_toggle_use_imgpopup() { CONFIG::set_use_image_popup( ! CONFIG::get_use_image_popup() ); } // // インライン画像on/off // void Core::slot_toggle_use_inlineimg() { CONFIG::set_use_inline_image( ! CONFIG::get_use_inline_image() ); ARTICLE::get_admin()->set_command( "relayout_all" ); } // // ssspアイコン on/off // void Core::slot_toggle_show_ssspicon() { CONFIG::set_show_sssp_icon( ! CONFIG::get_show_ssspicon() ); ARTICLE::get_admin()->set_command( "relayout_all" ); } // // リスト項目(スレ一覧)の設定 // void Core::slot_setup_boarditem_column() { auto pref = CORE::PrefDiagFactory( nullptr, CORE::PREFDIAG_BOARDITEM_COLUM, "" ); pref->run(); } // // ツールバーのアイコン(メインツールバー)の表示項目 // void Core::slot_setup_mainitem() { auto pref = CORE::PrefDiagFactory( nullptr, CORE::PREFDIAG_MAINITEM, "" ); pref->run(); } // // ツールバーのアイコン(サイドバー)の表示項目 // void Core::slot_setup_sidebaritem() { auto pref = CORE::PrefDiagFactory( nullptr, CORE::PREFDIAG_SIDEBARITEM, "" ); pref->run(); } // // ツールバーのアイコン(スレ一覧)の表示項目 // void Core::slot_setup_boarditem() { auto pref = CORE::PrefDiagFactory( nullptr, CORE::PREFDIAG_BOARDITEM, "" ); pref->run(); } // // ツールバーのアイコン(スレビュー)の表示項目 // void Core::slot_setup_articleitem() { auto pref = CORE::PrefDiagFactory( nullptr, CORE::PREFDIAG_ARTICLEITEM, "" ); pref->run(); } // // ツールバーのアイコン(ログ/スレタイ検索)の表示項目 // void Core::slot_setup_searchitem() { auto pref = CORE::PrefDiagFactory( nullptr, CORE::PREFDIAG_SEARCHITEM, "" ); pref->run(); } // // ツールバーのアイコン(書き込みビュー)の表示項目 // void Core::slot_setup_msgitem() { auto pref = CORE::PrefDiagFactory( nullptr, CORE::PREFDIAG_MSGITEM, "" ); pref->run(); } // // コンテキストメニュー(スレ一覧)の表示項目 // void Core::slot_setup_boarditem_menu() { auto pref = CORE::PrefDiagFactory( nullptr, CORE::PREFDIAG_BOARDITEM_MENU, "" ); pref->run(); } // // コンテキストメニュー(スレビュー)の表示項目 // void Core::slot_setup_articleitem_menu() { auto pref = CORE::PrefDiagFactory( nullptr, CORE::PREFDIAG_ARTICLEITEM_MENU, "" ); pref->run(); } // // 板一覧のプロパティ // void Core::slot_bbslist_pref() { BBSLIST::get_admin()->set_command( "show_preferences", URL_BBSLISTVIEW ); } // // スレ一覧のプロパティ // void Core::slot_board_pref() { BOARD::get_admin()->set_command( "show_current_preferences" ); } // // スレのプロパティ // void Core::slot_article_pref() { ARTICLE::get_admin()->set_command( "show_current_preferences" ); } // // 画像のプロパティ // void Core::slot_image_pref() { IMAGE::get_admin()->set_command( "show_current_preferences" ); } // // 起動時にviewを復元 // void Core::slot_toggle_restore_views() { bool status = CONFIG::get_restore_board() & CONFIG::get_restore_article() & CONFIG::get_restore_image(); CONFIG::set_restore_board( ! status ); CONFIG::set_restore_article( ! status ); CONFIG::set_restore_image( ! status ); } // // 非アクティブ時に書き込みビューを折りたたむ // void Core::slot_toggle_fold_message() { CONFIG::set_fold_message( ! CONFIG::get_fold_message() ); SKELETON::MsgDiag mdiag( nullptr, "次に書き込みビューを開いた時から有効になります" ); mdiag.run(); } // // ツリービューの選択を表示中のビューと同期する // void Core::slot_toggle_select_item_sync() { if( CONFIG::get_select_item_sync() != 0 ) CONFIG::set_select_item_sync( 0 ); // 同期しない else CONFIG::set_select_item_sync( 1 ); // 同期する } // // 書き込みログを保存 // void Core::slot_toggle_save_post_log() { CONFIG::set_save_post_log( ! CONFIG::get_save_post_log() ); } // // 書き込み履歴を保存 // void Core::slot_toggle_save_post_history() { CONFIG::set_save_post_history( ! CONFIG::get_save_post_history() ); } // // 画像モザイクon/off // void Core::slot_toggle_use_mosaic() { CONFIG::set_use_mosaic( ! CONFIG::get_use_mosaic() ); SKELETON::MsgDiag mdiag( nullptr, "次に開いた画像から有効になります" ); mdiag.run(); } // // まちBBSのofflawモードの切り替え // void Core::slot_toggle_use_machi_offlaw() { CONFIG::set_use_machi_offlaw( ! CONFIG::get_use_machi_offlaw() ); if( CONFIG::get_use_machi_offlaw() ){ SKELETON::MsgDiag mdiag( nullptr, "offlaw.cgiを使用するとリモートホストが表示されない問題が生じるので注意して下さい。" ); mdiag.run(); } } // // タブで開くボタンを入れ替える // void Core::slot_toggle_tabbutton() { bool toggled = CONTROL::is_toggled_tab_button() && CONTROL::is_toggled_tab_key(); CONTROL::toggle_tab_button( !toggled ); CONTROL::toggle_tab_key( !toggled ); } // // クリックで多重ポップアップモードに移行 // void Core::slot_toggle_popupwarpmode() { CONTROL::toggle_popup_warpmode(); } // // マウス移動で多重ポップアップモードに移行 // void Core::slot_shortmargin_popup() { int margin = 2; if( CONFIG::get_margin_popup() != CONFIG::CONF_MARGIN_POPUP ) margin = CONFIG::CONF_MARGIN_POPUP; CONFIG::set_margin_popup( margin ); } // // editview を emacs風のキーバインドにする void Core::slot_toggle_emacsmode() { if( ! m_enable_menuslot ) return; CONTROL::toggle_emacs_mode(); } // // マウスジェスチャ詳細設定 // void Core::slot_setup_mouse() { auto pref = CORE::PrefDiagFactory( nullptr, CORE::PREFDIAG_MOUSE, "" ); pref->run(); } // // キーボード詳細設定 // void Core::slot_setup_key() { auto pref = CORE::PrefDiagFactory( nullptr, CORE::PREFDIAG_KEY, "" ); pref->run(); } // // マウスボタン詳細設定 // void Core::slot_setup_button() { auto pref = CORE::PrefDiagFactory( nullptr, CORE::PREFDIAG_BUTTON, "" ); pref->run(); } // // メインフォント変更 // void Core::slot_changefont_main() { Gtk::FontChooserDialog diag; diag.set_font( CONFIG::get_fontname( FONT_MAIN ) ); diag.set_title( "スレビューフォント" ); diag.set_transient_for( *CORE::get_mainwindow() ); if( diag.run() == Gtk::RESPONSE_OK ){ CONFIG::set_fontname( FONT_MAIN, diag.get_font() ); ARTICLE::get_admin()->set_command( "init_font" ); ARTICLE::get_admin()->set_command( "relayout_all" ); CONFIG::set_fontname( FONT_MESSAGE, diag.get_font() ); MESSAGE::get_admin()->set_command( "relayout_all" ); } } // // メールのフォント変更 // void Core::slot_changefont_mail() { Gtk::FontChooserDialog diag; diag.set_font( CONFIG::get_fontname( FONT_MAIL ) ); diag.set_title( "メール欄のフォント" ); diag.set_transient_for( *CORE::get_mainwindow() ); if( diag.run() == Gtk::RESPONSE_OK ){ CONFIG::set_fontname( FONT_MAIL, diag.get_font() ); ARTICLE::get_admin()->set_command( "init_font" ); ARTICLE::get_admin()->set_command( "relayout_all" ); CONFIG::set_fontname( FONT_MESSAGE, diag.get_font() ); MESSAGE::get_admin()->set_command( "relayout_all" ); } } // // ポップアップフォント変更 // void Core::slot_changefont_popup() { Gtk::FontChooserDialog diag; diag.set_font( CONFIG::get_fontname( FONT_POPUP ) ); diag.set_title( "ポップアップフォント" ); diag.set_transient_for( *CORE::get_mainwindow() ); if( diag.run() == Gtk::RESPONSE_OK ){ CONFIG::set_fontname( FONT_POPUP, diag.get_font() ); ARTICLE::get_admin()->set_command( "init_font" ); } } // // 板/スレ一覧のフォント変更 // void Core::slot_changefont_tree() { Gtk::FontChooserDialog diag; diag.set_font( CONFIG::get_fontname( FONT_BBS ) ); diag.set_title( "板/スレ一覧フォント" ); diag.set_transient_for( *CORE::get_mainwindow() ); if( diag.run() == Gtk::RESPONSE_OK ){ CONFIG::set_fontname( FONT_BBS, diag.get_font() ); BBSLIST::get_admin()->set_command( "relayout_all" ); CONFIG::set_fontname( FONT_BOARD, diag.get_font() ); BOARD::get_admin()->set_command( "relayout_all" ); } } // // スレ文字色変更 // void Core::slot_changecolor_char() { if( open_color_diag( "スレビュー文字色", COLOR_CHAR ) ){ ARTICLE::get_admin()->set_command( "relayout_all" ); CONFIG::set_color( COLOR_CHAR_MESSAGE, CONFIG::get_color( COLOR_CHAR ) ); MESSAGE::get_admin()->set_command( "relayout_all" ); } } // // スレ、ポップアップ背景色変更 // void Core::slot_changecolor_back() { if( open_color_diag( "スレビュー背景色", COLOR_BACK ) ){ CONFIG::set_color( COLOR_BACK_POPUP, CONFIG::get_color( COLOR_BACK) ); CONFIG::set_color( COLOR_BACK_MESSAGE, CONFIG::get_color( COLOR_BACK) ); MESSAGE::get_admin()->set_command( "relayout_all" ); ARTICLE::get_admin()->set_command( "relayout_all" ); } } // // 板/スレ一覧文字色変更 // void Core::slot_changecolor_char_tree() { if( open_color_diag( "板/スレ一覧文字色", COLOR_CHAR_BBS ) ){ CONFIG::set_color( COLOR_CHAR_BBS_COMMENT, CONFIG::get_color( COLOR_CHAR_BBS ) ); CONFIG::set_color( COLOR_CHAR_BOARD, CONFIG::get_color( COLOR_CHAR_BBS ) ); BBSLIST::get_admin()->set_command( "relayout_all" ); BOARD::get_admin()->set_command( "relayout_all" ); } } // // 板/スレ一覧背景色変更 // void Core::slot_changecolor_back_tree() { if( open_color_diag( "板/スレ一覧背景色", COLOR_BACK_BBS ) ){ CONFIG::set_color( COLOR_BACK_BBS_EVEN, CONFIG::get_color( COLOR_BACK_BBS ) ); CONFIG::set_color( COLOR_BACK_BOARD, CONFIG::get_color( COLOR_BACK_BBS ) ); CONFIG::set_color( COLOR_BACK_BOARD_EVEN, CONFIG::get_color( COLOR_BACK_BBS ) ); BBSLIST::get_admin()->set_command( "relayout_all" ); BOARD::get_admin()->set_command( "relayout_all" ); } } // // フォントと色の詳細設定 // void Core::slot_setup_fontcolor() { auto pref = CORE::PrefDiagFactory( nullptr, CORE::PREFDIAG_FONTCOLOR, "" ); pref->run(); } // // プロキシ設定 // void Core::slot_setup_proxy() { auto pref = CORE::PrefDiagFactory( nullptr, CORE::PREFDIAG_PROXY, "" ); pref->run(); } // // ブラウザ設定 // void Core::slot_setup_browser() { auto pref = CORE::PrefDiagFactory( nullptr, CORE::PREFDIAG_BROWSER, URL_BROWSER ); pref->run(); } // // パスワード設定 // void Core::slot_setup_passwd() { auto pref = CORE::PrefDiagFactory( nullptr, CORE::PREFDIAG_PASSWD, "" ); pref->run(); } // // IPv6使用 // void Core::slot_toggle_ipv6() { CONFIG::set_use_ipv6( ! CONFIG::get_use_ipv6() ); } // // あぼーん設定 // void Core::slot_setup_abone() { auto pref = CORE::PrefDiagFactory( nullptr, CORE::PREFDIAG_GLOBALABONE, "" ); pref->run(); } // // スレあぼーん設定 // void Core::slot_setup_abone_thread() { auto pref = CORE::PrefDiagFactory( nullptr, CORE::PREFDIAG_GLOBALABONETHREAD, "" ); pref->run(); } // // 透明/連鎖あぼーん切り替え // void Core::slot_toggle_abone_transp_chain() { const bool status = CONFIG::get_abone_chain() & CONFIG::get_abone_transparent(); CONFIG::set_abone_transparent( ! status ); CONFIG::set_abone_chain( ! status ); // あぼーん情報更新 DBTREE::update_abone_all_article(); CORE::core_set_command( "relayout_all_article" ); } // // NG正規表現によるあぼーん時に大小と全半角文字の違いを無視する // void Core::slot_toggle_abone_icase_wchar() { const bool status = CONFIG::get_abone_icase() & CONFIG::get_abone_wchar(); CONFIG::set_abone_icase( ! status ); CONFIG::set_abone_wchar( ! status ); // あぼーん情報更新 DBTREE::update_abone_thread(); DBTREE::update_abone_all_article(); CORE::core_set_command( "relayout_all_article" ); } // 実況設定 void Core::slot_setup_live() { auto pref = CORE::PrefDiagFactory( nullptr, CORE::PREFDIAG_LIVE, "" ); pref->run(); } // // ユーザコマンドの編集 // void Core::slot_usrcmd_pref() { auto pref = CORE::PrefDiagFactory( nullptr, CORE::PREFDIAG_USRCMD, URL_USRCMD ); pref->run(); } // // リンクフィルタの編集 // void Core::slot_filter_pref() { auto pref = CORE::PrefDiagFactory( nullptr, CORE::PREFDIAG_LINKFILTER, URL_LINKFILTER ); pref->run(); } // // 置換文字列の編集 // void Core::slot_replace_pref() { auto pref = CORE::PrefDiagFactory( nullptr, CORE::PREFDIAG_REPLACESTR, URL_REPLACESTR ); pref->run(); } // // about:config // void Core::slot_aboutconfig() { auto pref = CORE::PrefDiagFactory( nullptr, CORE::PREFDIAG_ABOUTCONFIG, URL_ABOUTCONFIG ); pref->run(); } // プライバシー情報のクリア void Core::slot_clear_privacy() { auto pref = CORE::PrefDiagFactory( nullptr, CORE::PREFDIAG_PRIVACY, URL_PRIVACY ); pref->run(); } // // 書き込みログのクリア // void Core::slot_clear_post_log() { SKELETON::MsgDiag mdiag( nullptr, "書き込みログを削除しますか?", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO ); if( mdiag.run() != Gtk::RESPONSE_YES ) return; MESSAGE::get_log_manager()->clear_post_log(); // ログ表示を閉じる ARTICLE::get_admin()->set_command( "close_view", "postlog" + std::string( POSTLOG_SIGN ), "closeall" ); } // // 全スレの書き込み履歴(鉛筆マーク)のクリア // void Core::slot_clear_post_history() { SKELETON::MsgDiag mdiag( nullptr, "全スレの書き込み履歴を削除します。\n\nある板、または特定のスレの履歴を削除するには板またはスレのプロパティから行って下さい。\n\nまた全スレの書き込み履歴の削除には時間がかかります。\n\n全スレの書き込み履歴の削除を実行しますか?", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO ); if( mdiag.run() != Gtk::RESPONSE_YES ) return; DBTREE::clear_all_post_history(); // ビューの表示更新 CORE::core_set_command( "redraw_article" ); std::list< std::string > list_urls = BOARD::get_admin()->get_URLs(); for( const std::string& url : list_urls ) CORE::core_set_command( "update_board", url ); } // // 画像キャッシュクリア // void Core::slot_delete_all_images() { DBIMG::delete_all_files(); IMAGE::get_admin()->set_command( "close_uncached_views" ); } // // 実況 // void Core::slot_live_start_stop() { std::string url = ARTICLE::get_admin()->get_current_url(); ARTICLE::get_admin()->set_command( "live_start_stop", url ); } // // 現在開いている板のキャッシュ内のログ検索 // void Core::slot_search_cache_board() { const std::string url = BOARD::get_admin()->get_current_url(); if( ! url.empty() ) CORE::core_set_command( "open_article_searchlog", url, "", "noexec" ); } // // キャッシュ内のログ検索 // void Core::slot_search_cache() { CORE::core_set_command( "open_article_searchlog", URL_SEARCH_ALLBOARD, "", "noexec" ); } // // 現在開いている板のキャッシュ内のログ一覧表示 // void Core::slot_show_cache_board() { const std::string url = DBTREE::url_boardbase( BOARD::get_admin()->get_current_url() ); if( ! url.empty() ) CORE::core_set_command( "open_board_showlog", url ); } // // キャッシュ内の全ログ一覧表示 // void Core::slot_show_cache() { CORE::core_set_command( "open_board_showalllog" ); } // // スレタイ検索 // void Core::slot_search_title() { CORE::core_set_command( "open_article_searchtitle", "", "", "noexec" ); } // // サイドバーの全更新チェック // void Core::slot_check_update_root() { CORE::core_set_command( "check_update_root", "" ); } // // サイドバーを全更新チェックしてタブで開く // void Core::slot_check_update_open_root() { CORE::core_set_command( "check_update_open_root", "" ); } // // 更新チェックをキャンセル // void Core::slot_cancel_check_update() { CORE::core_set_command( "cancel_check_update", "" ); } // // お気に入りの編集 // void Core::slot_edit_favorite() { CORE::core_set_command( "edit_favorite","" ); } // // 書き込みログ // void Core::slot_show_postlog() { CORE::core_set_command( "open_article_postlog" ); } // // 開いている板にdatをインポート // void Core::slot_import_dat() { std::string url_board = BOARD::get_admin()->get_current_url(); #ifdef _DEBUG std::cout << "Core::slot_import_dat url = " << url_board << std::endl; #endif if( ! url_board.empty() ) CORE::core_set_command( "import_dat", url_board, "show_diag" ); } // // サイドバーをスレ一覧に表示 // void Core::slot_show_sidebarboard() { if( SESSION::get_sidebar_current_url() != URL_BBSLISTVIEW && SESSION::get_sidebar_current_url() != URL_HISTBOARDVIEW ){ const std::string tab = "newtab"; const std::string mode = ""; CORE::core_set_command( "open_sidebar_board", SESSION::get_sidebar_current_url(), tab, mode, "0" ); } } // // サイドバーの仮想板を作成 // void Core::slot_create_vboard() { if( SESSION::get_sidebar_current_url() != URL_BBSLISTVIEW && SESSION::get_sidebar_current_url() != URL_HISTBOARDVIEW ){ CORE::DATA_INFO_LIST list_info; CORE::DATA_INFO info; info.type = TYPE_VBOARD; info.parent = CORE::get_mainwindow(); info.url = SESSION::get_sidebar_current_url() + SIDEBAR_SIGN + "0"; info.name = SESSION::get_sidebar_dirname( SESSION::get_sidebar_current_url(), 0 ); info.path = Gtk::TreePath( "0" ).to_string(); list_info.push_back( info ); CORE::SBUF_set_list( list_info ); CORE::core_set_command( "append_favorite", URL_FAVORITEVIEW ); } } // // サポートBBS // void Core::slot_show_bbs() { CORE::core_set_command( "open_board", DBTREE::url_boardbase( ENVIRONMENT::get_jdbbs() ), "newtab" ); } // // 過去ログ // void Core::slot_show_old2ch() { CORE::core_set_command( "open_board", DBTREE::url_boardbase( ENVIRONMENT::get_jd2chlog() ), "newtab" ); } // // マニュアル // void Core::slot_show_manual() { open_by_browser( ENVIRONMENT::get_jdhelp() ); } // // about // void Core::slot_show_about() { SKELETON::AboutDiag about( "JDimについて" ); about.run(); } jdim-0.7.0/src/meson.build000066400000000000000000000045671417047150700154140ustar00rootroot00000000000000config_h = configure_file(output : 'config.h', configuration : conf) # 作業ディレクトリの状態を反映するためにビルド毎に更新する buildinfo_h = custom_target('buildinfo.h', build_always_stale : true, capture : true, command : ['sh', '@INPUT@', '@SOURCE_ROOT@'], input : 'buildinfo.h.sh', output : 'buildinfo.h') subdir('article') subdir('bbslist') subdir('board') subdir('config') subdir('control') subdir('dbimg') subdir('dbtree') subdir('history') subdir('icons') subdir('image') subdir('jdlib') subdir('message') subdir('skeleton') subdir('sound') subdir('xml') # テストプログラムのビルドで利用するため files() でまとめる core_sources = files( 'aamanager.cpp', 'articleitemmenupref.cpp', 'articleitempref.cpp', 'boarditemmenupref.cpp', 'boarditempref.cpp', 'browsers.cpp', 'cache.cpp', 'command.cpp', 'compmanager.cpp', 'core.cpp', 'cssmanager.cpp', 'dispatchmanager.cpp', 'dndmanager.cpp', 'environment.cpp', 'fontcolorpref.cpp', 'iomonitor.cpp', 'linkfiltermanager.cpp', 'linkfilterpref.cpp', 'livepref.cpp', 'login2ch.cpp', 'loginbe.cpp', 'mainitempref.cpp', 'maintoolbar.cpp', 'menuslots.cpp', 'msgitempref.cpp', 'openurldiag.cpp', 'prefdiagfactory.cpp', 'replacestrmanager.cpp', 'replacestrpref.cpp', 'searchitempref.cpp', 'searchloader.cpp', 'searchmanager.cpp', 'session.cpp', 'setupwizard.cpp', 'sharedbuffer.cpp', 'sidebaritempref.cpp', 'updatemanager.cpp', 'urlreplacemanager.cpp', 'usrcmdmanager.cpp', 'usrcmdpref.cpp', 'viewfactory.cpp', 'winmain.cpp', ) sources = [ 'main.cpp', ] jdim_deps = [ alsa_dep, crypt_dep, gtkmm_dep, ice_dep, migemo_dep, regex_dep, sm_dep, socket_dep, threads_dep, tls_dep, x11_dep, zlib_dep, ] jdim_incs = include_directories('.') jdim_libs = [ article_lib, bbslist_lib, board_lib, config_lib, control_lib, dbimg_lib, dbtree_lib, history_lib, icon_lib, image_lib, jdlib_lib, message_lib, skeleton_lib, sound_lib, xml_lib, ] jdim_exe = executable( 'jdim', [core_sources, sources, buildinfo_h, config_h], dependencies : jdim_deps, include_directories : jdim_incs, link_with : jdim_libs, install : true, ) jdim-0.7.0/src/message/000077500000000000000000000000001417047150700146625ustar00rootroot00000000000000jdim-0.7.0/src/message/Makefile.am000066400000000000000000000006641417047150700167240ustar00rootroot00000000000000noinst_LIBRARIES = libmessage.a libmessage_a_SOURCES = \ messageadmin.cpp \ messagewin.cpp \ messageviewbase.cpp \ messageview.cpp \ post.cpp \ toolbar.cpp \ logmanager.cpp \ confirmdiag.cpp noinst_HEADERS = \ messageadmin.h \ messagewin.h \ messageviewbase.h \ messageview.h \ post.h \ toolbar.h \ logitem.h \ logmanager.h \ confirmdiag.h \ logitem.h AM_CXXFLAGS = @GTKMM_CFLAGS@ AM_CPPFLAGS = -I$(top_srcdir)/src jdim-0.7.0/src/message/confirmdiag.cpp000066400000000000000000000022301417047150700176450ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "messageadmin.h" #include "confirmdiag.h" #include "dbtree/interface.h" #include "skeleton/view.h" using namespace MESSAGE; ConfirmDiag::ConfirmDiag( const std::string& url, const std::string& message ) : SKELETON::DetailDiag( MESSAGE::get_admin()->get_win(), url, true, message, "投稿確認", "", "ローカルルール" ), m_chkbutton( "今後表示しない(常にOK)(_D)", true ) { const int mrg = 16; Gtk::HBox* hbox = Gtk::manage( new Gtk::HBox ); hbox->pack_start( m_chkbutton, Gtk::PACK_EXPAND_WIDGET, mrg ); get_content_area()->pack_start( *hbox, Gtk::PACK_SHRINK ); set_title( "投稿確認" ); show_all_children(); grab_ok(); } ConfirmDiag::~ConfirmDiag() noexcept = default; void ConfirmDiag::slot_switch_page( Gtk::Widget*, guint page ) { if( get_notebook().get_nth_page( page ) == get_detail() ){ get_detail()->set_command( "clear_screen" ); get_detail()->set_command( "append_html", DBTREE::localrule( get_url() ) ); } } jdim-0.7.0/src/message/confirmdiag.h000066400000000000000000000010571417047150700173200ustar00rootroot00000000000000// ライセンス: GPL2 // // 記事投稿確認ダイアログ // #ifndef _CONFIRMDIAG_H #define _CONFIRMDIAG_H #include "skeleton/detaildiag.h" namespace MESSAGE { class ConfirmDiag : public SKELETON::DetailDiag { Gtk::CheckButton m_chkbutton; public: ConfirmDiag( const std::string& url, const std::string& message ); ~ConfirmDiag() noexcept; Gtk::CheckButton& get_chkbutton(){ return m_chkbutton; } private: void slot_switch_page( Gtk::Widget*, guint page ) override; }; } #endif jdim-0.7.0/src/message/logitem.h000066400000000000000000000034721417047150700165010ustar00rootroot00000000000000// ライセンス: GPL2 // // 自分の書き込みを判定するために書き込み内容を一時的に保存しておくクラス // #ifndef _LOGITEM_H #define _LOGITEM_H #include "messageadmin.h" #include "jdlib/miscutil.h" #include #include #include enum { LOGITEM_SIZE_HEAD = 64 }; namespace MESSAGE { class LogItem { public: std::string url; const bool newthread; std::string msg; std::time_t time_write; std::list< std::string > msg_lines; char head[ LOGITEM_SIZE_HEAD ]{}; bool remove{}; LogItem( const std::string& _url, const bool _newthread, const std::string& _msg ) : url( _url ) , newthread( _newthread ) , msg( _msg ) , time_write{ std::time( nullptr ) } { if( newthread && url.find( ID_OF_NEWTHREAD ) != std::string::npos ) url = url.substr( 0, url.find( ID_OF_NEWTHREAD ) ); // WAVE DASH 問題 msg = MISC::utf8_fix_wavedash( msg, MISC::UNIXtoWIN ); // 水平タブを空白に置き換える msg = MISC::replace_str( msg, "\t", " " ); // 数字参照を変換 msg = MISC::decode_spchar_number( msg ); // MISC::replace_str( ..., "\n", " \n" ) しているのは MISC::get_lines 実行時に // 改行のみの行を削除しないようにするため msg_lines = MISC::get_lines( MISC::replace_str( MISC::remove_spaces( msg ), "\n", " \n" ) ); // 簡易チェック用に先頭の文字列をコピー(空白は除く) for( size_t i = 0, i2 = 0; i < LOGITEM_SIZE_HEAD -1 && i2 < msg.length(); ++i2 ){ if( msg[ i2 ] != ' ' ) head[ i++ ] = msg[ i2 ]; } } }; } #endif jdim-0.7.0/src/message/logmanager.cpp000066400000000000000000000246211417047150700175070ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "logmanager.h" #include "logitem.h" #include "messageadmin.h" #include "config/globalconf.h" #include "dbtree/interface.h" #include "jdlib/miscutil.h" #include "jdlib/misctime.h" #include "cache.h" #include "session.h" #include #include #include #include // chmod enum { WRITE_TIMEOUT = 180 // 書き込んでからこの秒数を過ぎても書き込んだレスを判定できなかったらロストとする }; MESSAGE::Log_Manager* instance_log_manager = nullptr; MESSAGE::Log_Manager* MESSAGE::get_log_manager() { if( ! instance_log_manager ) instance_log_manager = new MESSAGE::Log_Manager(); assert( instance_log_manager ); return instance_log_manager; } void MESSAGE::delete_log_manager() { if( instance_log_manager ) delete instance_log_manager; instance_log_manager = nullptr; } /////////////////////////////////////////////// using namespace MESSAGE; Log_Manager::Log_Manager() { #ifdef _DEBUG std::cout << "MESSAGE::Log_Manager\n"; #endif } Log_Manager::~Log_Manager() { #ifdef _DEBUG std::cout << "MESSAGE::~Log_Manager\n"; for( const LogItem& item : m_logitems ) { std::cout << "url = " << item.url << std::endl; std::cout << "newthread = " << item.newthread << std::endl; std::cout << "msg = " << item.msg << std::endl; } #endif } bool Log_Manager::has_items( const std::string& url, const bool newthread ) const { if( m_logitems.empty() ) return false; #ifdef _DEBUG std::cout << "Log_Manager::has_items url = " << url << " newthread " << newthread << std::endl; #endif for( const LogItem& item : m_logitems ) { #ifdef _DEBUG std::cout << "checking : " << item.url << std::endl; #endif if( newthread != item.newthread ) continue; if( item.url == url ){ #ifdef _DEBUG std::cout << "found\n"; #endif return true; } if( item.newthread && url.rfind( item.url, 0 ) == 0 ){ #ifdef _DEBUG std::cout << "found\n"; #endif return true; } } #ifdef _DEBUG std::cout << "not found" << std::endl; #endif return false; } void Log_Manager::remove_items( const std::string& url ) { const std::time_t current = std::time( nullptr ); if( m_logitems.empty() ) return; #ifdef _DEBUG std::cout << "Log_Manager::remove_items url = " << url << std::endl << "size = " << m_logitems.size() << std::endl; #endif for( auto it = m_logitems.begin(); it != m_logitems.end(); ) { if( it->url == url || ( it->newthread && url.rfind( it->url, 0 ) == 0 ) ){ const std::time_t elapsed = current - it->time_write; #ifdef _DEBUG std::cout << "elapsed = " << elapsed << std::endl; #endif // removeフラグが立っているか、時間切れの場合は削除 if( it->remove || elapsed > WRITE_TIMEOUT ){ #ifdef _DEBUG std::cout << "removed url = " << it->url << std::endl; #endif it = m_logitems.erase( it ); continue; } } ++it; } #ifdef _DEBUG std::cout << "-> size = " << m_logitems.size() << std::endl; #endif } // // messageが自分の書き込んだものかチェックする // // newthread == true の時は新スレの>>1のチェック // // headsize > 0 の時は先頭の headsize 文字だけを比較 // bool Log_Manager::check_write( const std::string& url, const bool newthread, const char* msg_in, const size_t headsize ) { if( m_logitems.empty() ) return false; #ifdef _DEBUG std::cout << "Log_Manager::check_write url = " << url << " newthread = " << newthread << " headsize = " << headsize << std::endl; #endif const char* msg = msg_in; for( LogItem& item : m_logitems ) { if( item.newthread != newthread ) continue; if( ! item.newthread && item.url != url ) continue; if( item.newthread && url.rfind( item.url, 0 ) != 0 ) continue; // 先頭のheadsize文字だけ簡易チェックする // ヒットしてもitem.remove を true にしない if( headsize ){ bool flag = true; size_t i = 0, i2 = 0; while( item.head[ i ] != '\0' && i2 < headsize ){ // 空白は除く while( item.head[ i ] == ' ' ) ++i; while( msg[ i2 ] == ' ' ) ++i2; #ifdef _DEBUG std::cout << (int)( item.head[ i ] ) << " - " << (int)( msg[ i2 ] ) << std::endl; #endif // もしバッファの最後が空白で終わっていたら成功と見なす if( i && i2 && ( item.head[ i ] == '\0' || msg[ i2 ] == '\0' ) ) break; if( item.head[ i ] != msg[ i2 ] ){ flag = false; #ifdef _DEBUG std::cout << "!! failed (head) !!\n"; #endif break; } ++i; ++i2; } if( ! flag ) continue; #ifdef _DEBUG std::cout << "!! hit (head) !!\n"; #endif return true; } // 全文でチェック // MISC::replace_str( ..., "\n", " \n" ) しているのは MISC::get_lines 実行時に // 改行のみの行を削除しないようにするため std::list< std::string > msg_lines = MISC::get_lines( MISC::replace_str( MISC::remove_spaces( msg ), "\n", " \n" ) ); #ifdef _DEBUG std::cout << "lines = " << msg_lines.size() << " : " << item.msg_lines.size() << std::endl; std::cout << "newthread = " << newthread << " : " << item.newthread << std::endl; #endif if( msg_lines.size() != item.msg_lines.size() ) continue; std::list< std::string >::iterator it_msg = msg_lines.begin(); std::list< std::string >::iterator it_item = item.msg_lines.begin(); for( ; it_msg != msg_lines.end() ; ++it_msg, ++it_item ){ #ifdef _DEBUG std::cout << (*it_msg) << " | " << (*it_item) << std::endl; #endif if( MISC::remove_spaces( (*it_msg) ) != MISC::remove_spaces( (*it_item ) ) ) break; } if( it_msg != msg_lines.end() ) continue; #ifdef _DEBUG std::cout << "!! hit !!\n"; #endif item.remove = true; return true; } return false; } // // 自分の書き込みの判定用データの保存 // void Log_Manager::push_logitem( const std::string& url, const bool newthread, const std::string& msg ) { if( ! CONFIG::get_save_post_history() ) return; m_logitems.emplace_back( url, newthread, msg ); #ifdef _DEBUG const LogItem& item = m_logitems.back(); std::cout << "Log_Manager::push_logitem\n"; std::cout << "url = " << item.url << std::endl; std::cout << "newthread = " << item.newthread << std::endl; std::cout << "msg = " << item.msg << std::endl; #endif } // // ログの保存 // void Log_Manager::save( const std::string& url, const std::string& subject, const std::string& msg, const std::string& name, const std::string& mail ) { #ifdef _DEBUG std::cout << "Log_Manager::save\n"; std::cout << "url = " << url << std::endl; std::cout << "subject = " << subject << std::endl; std::cout << "msg = " << msg << std::endl; #endif if( ! CONFIG::get_save_post_log() ) return; // 実況中の時は保存しない if( SESSION::is_live( url ) ) return; if( ! CACHE::mkdir_logroot() ) return; const std::time_t current = std::time( nullptr ); // 保存メッセージ作成 const std::string date = MISC::timettostr( current, MISC::TIME_WEEK ); const bool include_url = false; // URLを除外して msg をエスケープ std::string html = "

    " + DBTREE::url_readcgi( url, 0, 0 ) + "
    " + "[ " + MISC::html_escape( DBTREE::board_name( url ) ) + " ] " + MISC::html_escape( subject ) + "
    " + "名前:" + MISC::html_escape( name ) + " [" + MISC::html_escape( mail ) + "]:" + date + "
    " + MISC::html_escape( msg, include_url ) + "

    "; html = MISC::replace_str( html, "\n", "
    " ); html += "\n"; // 保存先決定 // もし保存先ファイルの容量が大きい場合は mv する const std::string path = CACHE::path_postlog(); const size_t filesize = CACHE::get_filesize( path ); #ifdef _DEBUG std::cout << path << std::endl; std::cout << "size = " << filesize << std::endl; #endif if( filesize > CONFIG::get_maxsize_post_log() ){ const int maxno = get_max_num_of_log() + 1; const std::string newpath = path + "-" + std::to_string( maxno ); CACHE::jdmv( path, newpath ); chmod( newpath.c_str(), S_IWUSR | S_IRUSR ); #ifdef _DEBUG std::cout << "mv to " << newpath << std::endl; #endif } // 保存 #ifdef _DEBUG std::cout << html << std::endl; #endif const bool append = true; CACHE::save_rawdata( path, html, append ); chmod( path.c_str(), S_IWUSR | S_IRUSR ); } // // 書き込みログ取得 // std::string Log_Manager::get_post_log( const int num ) { std::string path = CACHE::path_postlog(); if( num ) path += "-" + std::to_string( num ); std::string html; CACHE::load_rawdata( path, html ); return html; } // // ログファイル( log/postlog-* ) の最大数 // int Log_Manager::get_max_num_of_log() { #ifdef _DEBUG std::cout << "Log_Manager::get_max_num_of_log\n"; #endif std::list< std::string > filelist = CACHE::get_filelist( CACHE::path_logroot() ); if( ! filelist.size() ) return 0; const std::string path = CACHE::path_postlog() + "-"; int maxno = 0; for( const std::string& filename : filelist ) { const std::string target = CACHE::path_logroot() + filename; #ifdef _DEBUG std::cout << target << std::endl; #endif if( target.rfind( path, 0 ) == 0 ){ const int tmpno = atoi( target.substr( path.length() ).c_str() ); if( tmpno > maxno ) maxno = tmpno; #ifdef _DEBUG std::cout << "no = " << tmpno << " max = " << maxno << std::endl; #endif } } return maxno; } // // ログ削除 // void Log_Manager::clear_post_log() { const int maxno = get_max_num_of_log(); for( int num = 0; num <= maxno; ++num ){ std::string path = CACHE::path_postlog(); if( num ) path += "-" + std::to_string( num ); unlink( to_locale_cstr( path ) ); } } jdim-0.7.0/src/message/logmanager.h000066400000000000000000000031151417047150700171470ustar00rootroot00000000000000// ライセンス: GPL2 // // 書き込みログの管理クラス // #ifndef _LOGMANAGER_H #define _LOGMANAGER_H #include #include namespace MESSAGE { class LogItem; class Log_Manager { std::list m_logitems; public: Log_Manager(); virtual ~Log_Manager(); int size() const { return m_logitems.size(); } bool has_items( const std::string& url, const bool newthread ) const; void remove_items( const std::string& url ); // messageが自分の書き込んだものかチェックする // newthread == true の時は新スレの>>1のチェック // headsize > 0 の時は先頭の headsize 文字だけを比較 bool check_write( const std::string& url, const bool newthread, const char* msg_in, const size_t headsize ); // 自分の書き込みの判定用データの保存 void push_logitem( const std::string& url, const bool newthread, const std::string& msg ); // ログの保存 void save( const std::string& url, const std::string& subject, const std::string& msg, const std::string& name, const std::string& mail ); // 書き込みログ取得 std::string get_post_log( const int num ); // ログファイル( log/postlog-* ) の最大数 int get_max_num_of_log(); // ログ削除 void clear_post_log(); }; /////////////////////////////////////// // インターフェース Log_Manager* get_log_manager(); void delete_log_manager(); } #endif jdim-0.7.0/src/message/meson.build000066400000000000000000000004651417047150700170310ustar00rootroot00000000000000sources = [ 'confirmdiag.cpp', 'logmanager.cpp', 'messageadmin.cpp', 'messageview.cpp', 'messageviewbase.cpp', 'messagewin.cpp', 'post.cpp', 'toolbar.cpp', ] message_lib = static_library( 'message', sources, dependencies : gtkmm_dep, include_directories : include_directories('..'), ) jdim-0.7.0/src/message/messageadmin.cpp000066400000000000000000000233111417047150700200230ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "messageadmin.h" #include "messagewin.h" #include "toolbar.h" #include "skeleton/view.h" #include "skeleton/msgdiag.h" #include "skeleton/dragnote.h" #include "skeleton/editview.h" #include "dbtree/interface.h" #include "config/globalconf.h" #include "viewfactory.h" #include "command.h" #include "global.h" #include "session.h" MESSAGE::MessageAdmin* instance_messageadmin = nullptr; MESSAGE::MessageAdmin* MESSAGE::get_admin() { if( ! instance_messageadmin ) instance_messageadmin = new MESSAGE::MessageAdmin( URL_MESSAGEADMIN ); return instance_messageadmin; } void MESSAGE::delete_admin() { if( instance_messageadmin ) delete instance_messageadmin; instance_messageadmin = nullptr; } using namespace MESSAGE; MessageAdmin::MessageAdmin( const std::string& url ) : SKELETON::Admin( url ) { get_notebook()->set_show_tabs( false ); } void MessageAdmin::show_entry_new_subject( bool show ) { if( m_toolbar ) m_toolbar->show_entry_new_subject( show ); } std::string MessageAdmin::get_new_subject() const { if( m_toolbar ) return m_toolbar->get_new_subject(); return std::string(); } SKELETON::EditView* MessageAdmin::get_text_message() { if( ! m_text_message ) m_text_message = std::make_unique(); return m_text_message.get(); } // // ローカルコマンド実行 // // virtual void MessageAdmin::command_local( const COMMAND_ARGS& command ) { #ifdef _DEBUG std::cout << "MessageAdmin::command_local command = " << command.command << std::endl; #endif SKELETON::View *view = get_current_view(); // 書き込みボタンにフォーカスを移す if( command.command == "focus_button_write" ){ if( get_notebook()->get_current_toolbar() == TOOLBAR_MESSAGE ) m_toolbar->focus_button_write(); else m_toolbar_preview->focus_button_write(); } // プレビュー切り替え else if( command.command == "toggle_preview" ){ if( view ) view->set_command( command.command ); } // undo else if( command.command == "undo_text" ){ if( view ) view->set_command( command.command ); } // 下書き挿入 else if( command.command == "insert_draft" ){ if( view ) view->set_command( command.command ); } // 通常のツールバーに表示切り替え else if( command.command == "switch_toolbar_message" ){ if( view ){ get_notebook()->set_current_toolbar( TOOLBAR_MESSAGE, view ); m_toolbar->set_active_previewbutton( false ); } } // プレビューツールバーに表示切り替え else if( command.command == "switch_toolbar_preview" ){ if( view ){ get_notebook()->set_current_toolbar( TOOLBAR_PREVIEW, view ); m_toolbar_preview->set_active_previewbutton( true ); } } // 指定したスレに対応する書き込みビューを開いていて // かつ書き込みビューが空なら閉じる else if( command.command == "close_message" ){ if( view && view->set_command( "empty" ) && view->get_url().find( command.url ) != std::string::npos ){ close_current_view(); } } // ビューの wrap 切り替え else if( command.command == "toggle_wrap" ){ if( view ) view->set_command( command.command ); } } // // 閉じる // // virtual void MessageAdmin::close_view( const std::string& url ) { #ifdef _DEBUG std::cout << "MessageAdmin::close_view url = " << url << std::endl; #endif SKELETON::View *view = get_current_view(); if( ! view ) return; if( view->is_loading() ){ SKELETON::MsgDiag mdiag( get_win(), "書き込み中です" ); mdiag.run(); return; } if( ! view->set_command( "empty" ) ){ if( ! delete_message( view ) ) return; } if( m_toolbar ) m_toolbar->clear_new_subject(); if( view->is_locked() ) view->set_command( "clear_message" ); else{ Admin::close_view( url ); if( empty() ) close_window(); } } // // ウィンドウ開く // // virtual void MessageAdmin::open_window() { SKELETON::JDWindow* win = get_jdwin(); if( ! SESSION::get_embedded_mes() && ! win && ! empty() ){ #ifdef _DEBUG std::cout << "MessageAdmin::open_window\n"; #endif set_jdwin( std::make_unique() ); win = get_jdwin(); win->pack_remove_end( false, *get_widget() ); win->show_all(); } else if( win && win->is_hide() ){ win->show(); win->focus_in(); } } // // ウィンドウ閉じる // // virtual void MessageAdmin::close_window() { if( get_jdwin() ){ #ifdef _DEBUG std::cout << "MessageAdmin::close_window\n"; #endif get_jdwin()->pack_remove_end( true, *get_widget() ); delete_jdwin(); } } // virtual void MessageAdmin::switch_admin() { if( ! has_focus() ) CORE::core_set_command( "switch_message" ); } // virtual void MessageAdmin::tab_left( const bool updated ) { SKELETON::View *view = get_current_view(); if( view ) view->set_command( "tab_left" ); } // virtual void MessageAdmin::tab_right( const bool updated ) { SKELETON::View *view = get_current_view(); if( view ) view->set_command( "tab_right" ); } // // 開く // // command.arg2 == "new" なら新スレ // // virtual void MessageAdmin::open_view( const COMMAND_ARGS& command ) { const std::string url = command.url; const std::string msg = command.arg1; const bool new_thread = ( command.arg2 == "new" ); #ifdef _DEBUG std::cout << "MessageAdmin::open_view " << url << std::endl; #endif SKELETON::View *current_view = get_current_view(); if( current_view ){ if( current_view->is_loading() ){ SKELETON::MsgDiag mdiag( get_win(), "書き込み中です" ); mdiag.run(); return; } // 既存のビューにメッセージを追加してフォーカスを移す if( url == current_view->get_url() ) { if( ! msg.empty() ) current_view->set_command( "add_message", msg ); switch_admin(); return; } // URLが異なればビューを破棄 if( ! current_view->set_command( "empty" ) ){ if( ! delete_message( current_view ) ) return; } // 古いビューを破棄 int page = get_notebook()->get_current_page(); get_notebook()->remove_page( page, true ); delete current_view; if( m_toolbar ) m_toolbar->clear_new_subject(); } std::string url_msg; int type; CORE::VIEWFACTORY_ARGS args; if( ! new_thread ){ type = CORE::VIEW_MESSAGE; args.arg1 = msg; url_msg = url; } // 新スレ // スレッドの id は ID_OF_NEWTHREAD.(各板別の拡張子) とする。 else{ type = CORE::VIEW_NEWTHREAD; args.arg1 = msg; url_msg = DBTREE::url_datbase( url ) + ID_OF_NEWTHREAD + DBTREE::board_ext( url ); } // ツールバー表示 show_toolbar(); SKELETON::View *view = CORE::ViewFactory( type, url_msg, args ); view->set_margin_start( 10 ); view->set_margin_end( 10 ); get_notebook()->append_page( url_msg, *view ); // ウィンドウ表示 open_window(); get_notebook()->show_all(); switch_admin(); view->show(); view->show_view(); get_notebook()->set_current_toolbar( view->get_id_toolbar(), view ); set_current_page( get_notebook()->page_num( *view ) ); focus_current_view(); } // // ステータス表示 // // virtual void MessageAdmin::set_status( const std::string& url, const std::string& stat, const bool force ) { // 埋め込みビューで、実況中の場合はステータス表示しない if( SESSION::get_embedded_mes() && SESSION::is_live( url ) ) return; SKELETON::Admin::set_status( url, stat, force ); } // // ツールバー表示 // // virtual void MessageAdmin::show_toolbar() { if( ! m_toolbar ){ // 通常のツールバー m_toolbar = std::make_unique(); get_notebook()->append_toolbar( *m_toolbar ); m_toolbar->open_buttonbar(); // プレビュー用のツールバー m_toolbar_preview = std::make_unique(); get_notebook()->append_toolbar( *m_toolbar_preview ); m_toolbar_preview->open_buttonbar(); } get_notebook()->show_toolbar(); } // // メッセージを破棄するか尋ねる // // 破棄する場合はtrueが戻る // bool MessageAdmin::delete_message( SKELETON::View * view ) { if( ! CONFIG::get_show_savemsgdiag() ) return true; SKELETON::MsgCheckDiag mdiag( get_win(), "編集中のメッセージを閉じる前に内容を保存しますか?\n\n保存ボタンを押すとメッセージを保存できます。", "今後表示しない(常に保存せずに閉じる) (_D)", Gtk::MESSAGE_WARNING, Gtk::BUTTONS_NONE ); mdiag.add_button( "保存せずに閉じる(_Q)", Gtk::RESPONSE_NO ); mdiag.add_button( g_dgettext( GTK_DOMAIN, "_Cancel" ), Gtk::RESPONSE_CANCEL ); mdiag.add_default_button( g_dpgettext( GTK_DOMAIN, "Stock label\x04_Save", 12 ), Gtk::RESPONSE_YES ); int ret = mdiag.run(); mdiag.hide(); bool result = false; switch( ret ) { case Gtk::RESPONSE_NO: result = true; if( mdiag.get_chkbutton().get_active() ) CONFIG::set_show_savemsgdiag( false ); break; case Gtk::RESPONSE_YES: if( ! view->set_command( "save_message" ) ) return delete_message( view ); result = true; break; } return result; } jdim-0.7.0/src/message/messageadmin.h000066400000000000000000000044461417047150700175000ustar00rootroot00000000000000// ライセンス: GPL2 // // 書き込みビューの管理クラス // #ifndef _MESSAGEADMIN_H #define _MESSAGEADMIN_H #include "skeleton/admin.h" #include namespace SKELETON { class EditView; } namespace MESSAGE { #define ID_OF_NEWTHREAD "0000000000" enum { TOOLBAR_MESSAGE = 0, TOOLBAR_PREVIEW = 1 }; class MessageToolBar; class MessageToolBarPreview; class MessageAdmin : public SKELETON::Admin { std::unique_ptr m_toolbar; std::unique_ptr m_toolbar_preview; // 書き込み用のメッセージ欄 // インスタンスを破棄しないで、前回書き込みビューを閉じた時の // 日本語のON/OFF状態を次回開いたときに継続させる std::unique_ptr m_text_message; public: explicit MessageAdmin( const std::string& url ); ~MessageAdmin() = default; void save_session() override {} void show_entry_new_subject( bool show ); std::string get_new_subject() const; SKELETON::EditView* get_text_message(); protected: void set_status( const std::string& url, const std::string& stat, const bool force ) override; // ツールバー void show_toolbar() override; void command_local( const COMMAND_ARGS& command ) override; private: bool delete_message( SKELETON::View * view ); // 復元をしない void restore( const bool only_locked ) override {} COMMAND_ARGS url_to_openarg( const std::string& url, const bool tab, const bool lock ) override { COMMAND_ARGS ret; return ret; } void open_view( const COMMAND_ARGS& command ) override; void switch_admin() override; void tab_left( const bool updated ) override; void tab_right( const bool updated ) override; void close_view( const std::string& url ) override; void open_window() override; void close_window() override; // タブの D&D 処理をしない void slot_drag_data_get( Gtk::SelectionData& selection_data, const int page ) override {} }; MESSAGE::MessageAdmin* get_admin(); void delete_admin(); } #endif jdim-0.7.0/src/message/messageview.cpp000066400000000000000000000145731417047150700177170ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "messageadmin.h" #include "messageview.h" #include "post.h" #include "skeleton/msgdiag.h" #include "skeleton/label_entry.h" #include "skeleton/editview.h" #include "jdlib/miscutil.h" #include "dbtree/interface.h" #include "command.h" #include "session.h" #include #include using namespace MESSAGE; MessageViewMain::MessageViewMain( const std::string& url, const std::string& msg ) : MessageViewBase( url ) { setup_view(); set_message( msg ); // ツールバーのスレタイトルを編集不可にする MESSAGE::get_admin()->show_entry_new_subject( false ); // メインウィンドウのタイトルに表示する文字 set_title( "[ 書き込み ] " + DBTREE::article_subject( get_url() ) ); // ツールバーにスレ名を表示 set_label( DBTREE::article_subject( get_url() ) ); } MessageViewMain::~MessageViewMain() { save_name(); } // // ポストするメッセージ作成 // std::string MessageViewMain::create_message() { if( ! get_text_message() ) return std::string(); const Glib::ustring msg = get_text_message()->get_text(); const std::string name = get_entry_name().get_text(); const std::string mail = get_entry_mail().get_text(); if( msg.empty() ){ SKELETON::MsgDiag mdiag( get_parent_win(), "書き込みがキャンセルされました。" ); mdiag.property_message_type() = Gtk::MESSAGE_WARNING; mdiag.set_secondary_text( "本文が空欄です。" ); mdiag.run(); return std::string(); } /* else { // 終端スペース/改行チェック const size_t end_pos = msg.find_last_not_of( "  \n" ); const size_t msg_length = msg.length(); // 最後がスペース/改行である if( end_pos != msg_length - 1 ) { SKELETON::MsgDiag mdiag( get_parent_win(), "メッセージがスペースまたは改行で終わっています。\n\n" "このまま書き込みますか? (または、改行等を削除)", false, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_NONE ); mdiag.set_title( "確認" ); mdiag.add_button( g_dgettext( GTK_DOMAIN, "_Cancel" ), Gtk::RESPONSE_CANCEL ); mdiag.add_button( g_dgettext( GTK_DOMAIN, "_Remove" ), Gtk::RESPONSE_DELETE_EVENT ); mdiag.add_button( g_dgettext( GTK_DOMAIN, "_Yes" ), Gtk::RESPONSE_YES ); switch( mdiag.run() ) { // スペース/改行を取り除いた物に置き換える(書き込まない) case Gtk::RESPONSE_DELETE_EVENT: get_text_message().set_text( msg.substr( 0, end_pos + 1 ) ); return std::string(); // キャンセル case Gtk::RESPONSE_CANCEL: return std::string(); } } } */ // 誤爆を警告 if( SESSION::get_article_current_url().find( get_url() ) == std::string::npos ){ SKELETON::MsgDiag mdiag( get_parent_win(), "スレビューで開いているスレと異なるスレに書き込もうとしています\n\n誤爆する可能性がありますが書き込みますか?", false, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_NONE ); mdiag.set_title( "!!!誤爆注意!!!" ); mdiag.add_button( g_dgettext( GTK_DOMAIN, "_No" ), Gtk::RESPONSE_NO ); mdiag.add_button( g_dgettext( GTK_DOMAIN, "_Yes" ), Gtk::RESPONSE_YES ); mdiag.add_button( "スレを開く", Gtk::RESPONSE_YES + 100 ); int ret = mdiag.run(); if( ret != Gtk::RESPONSE_YES ){ if( ret == Gtk::RESPONSE_YES + 100 ) CORE::core_set_command( "open_article", get_url(), "true", "" ); return std::string(); } } return DBTREE::create_write_message( get_url(), name, mail, msg ); } // // 書き込み // // 書き込みが終わったら MessageViewBase::post_fin()が呼ばれる // void MessageViewMain::write_impl( const std::string& msg ) { post_msg( msg, false ); } void MessageViewMain::reload() { CORE::core_set_command( "open_article", get_url(), "true", "" ); } /////////////////////////// MessageViewNew::MessageViewNew( const std::string& url, const std::string& msg ) : MessageViewBase( url ) { setup_view(); set_message( msg ); // ツールバーのスレタイトルを編集可能にする MESSAGE::get_admin()->show_entry_new_subject( true ); // メインウィンドウのタイトルに表示する文字 set_title( "[ 新スレ作成 ] " + DBTREE::article_subject( get_url() ) ); // 板のフロントページをダウンロードしてスレ立てに使うキーワードを更新する DBTREE::board_download_front( get_url() ); } // // ポストするメッセージ作成 // std::string MessageViewNew::create_message() { if( ! get_text_message() ) return std::string(); std::string subject = MESSAGE::get_admin()->get_new_subject(); std::string msg = get_text_message()->get_text(); std::string name = get_entry_name().get_text(); std::string mail = get_entry_mail().get_text(); const char* reason = nullptr; if( subject.empty() ) reason = "スレタイトルが空欄です。"; else if( msg.empty() ) reason = "本文が空欄です。"; if( reason ) { SKELETON::MsgDiag mdiag( get_parent_win(), "スレ立てがキャンセルされました。" ); mdiag.property_message_type() = Gtk::MESSAGE_WARNING; mdiag.set_secondary_text( reason ); mdiag.run(); return std::string{}; } SKELETON::MsgDiag mdiag( get_parent_win(), "新スレを作成しますか?", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO ); if( mdiag.run() == Gtk::RESPONSE_YES ) return DBTREE::create_newarticle_message( get_url(), subject, name, mail, msg ); return std::string(); } // // 書き込み // // 書き込みが終わったら MessageViewBase::post_fin()が呼ばれる // void MessageViewNew::write_impl( const std::string& msg ) { post_msg( msg, true ); } void MessageViewNew::reload() { CORE::core_set_command( "open_board", DBTREE::url_boardbase( get_url() ), "true" ); } jdim-0.7.0/src/message/messageview.h000066400000000000000000000016041417047150700173530ustar00rootroot00000000000000// ライセンス: GPL2 #ifndef _MESSAGEVIEW_H #define _MESSAGEVIEW_H #include "messageviewbase.h" namespace MESSAGE { // 通常の書き込みビュー class MessageViewMain : public MessageViewBase { public: MessageViewMain( const std::string& url, const std::string& msg ); ~MessageViewMain(); void reload() override; private: void write_impl( const std::string& msg ) override; std::string create_message() override; }; // 新スレ立て用ビュー class MessageViewNew : public MessageViewBase { public: MessageViewNew( const std::string& url, const std::string& msg ); ~MessageViewNew() noexcept = default; void reload() override; private: void write_impl( const std::string& msg ) override; std::string create_message() override; }; } #endif jdim-0.7.0/src/message/messageviewbase.cpp000066400000000000000000000723421417047150700205500ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG //#define _DEBUG_KEY #include "jddebug.h" #include "gtkmmversion.h" #include "messageadmin.h" #include "messageviewbase.h" #include "post.h" #include "logmanager.h" #include "skeleton/msgdiag.h" #include "skeleton/label_entry.h" #include "skeleton/editview.h" #include "skeleton/detaildiag.h" #include "jdlib/miscutil.h" #include "jdlib/misctime.h" #include "jdlib/misctrip.h" #include "jdlib/jdiconv.h" #include "jdlib/jdregex.h" #include "dbtree/interface.h" #include "config/globalconf.h" #include "control/controlutil.h" #include "control/controlid.h" #include "httpcode.h" #include "viewfactory.h" #include "fontid.h" #include "cache.h" #include "session.h" #include "colorid.h" #include "global.h" #include "compmanager.h" #include #include #include using namespace MESSAGE; enum { MAX_STR_ICONV = 128*1024, PASS_TIMEOUT = 500, PASS_MAXTIME = 120, }; // ページ番号 enum { PAGE_MESSAGE = 0, PAGE_PREVIEW }; MessageViewBase::MessageViewBase( const std::string& url ) : SKELETON::View( url ) , m_entry_name( CORE::COMP_NAME ) , m_entry_mail( CORE::COMP_MAIL ) , m_enable_focus( true ) { #ifdef _DEBUG std::cout << "MessageViewBase::MessageViewBase " << get_url() << std::endl; #endif // コントロールモード設定 get_control().add_mode( CONTROL::MODE_MESSAGE ); m_max_line = DBTREE::line_number( get_url() ) * 2; m_max_str = DBTREE::message_count( get_url() ); m_iconv = std::make_unique( DBTREE::board_charset( get_url() ), "UTF-8" );; m_lng_iconv = m_max_str * 3; if( ! m_lng_iconv ) m_lng_iconv = MAX_STR_ICONV; if( SESSION::get_close_mes() ) unlock(); else lock(); } MessageViewBase::~MessageViewBase() { #ifdef _DEBUG std::cout << "MessageViewBase::~MessageViewBase " << get_url() << std::endl << "lock = " << is_locked() << std::endl; #endif // 名前、メール履歴保存 std::string name = m_entry_name.get_text(); std::string mail = m_entry_mail.get_text(); CORE::get_completion_manager()->set_query( CORE::COMP_NAME, name ); CORE::get_completion_manager()->set_query( CORE::COMP_MAIL, mail ); if( m_post ){ m_post->terminate_load(); m_post.reset(); } SESSION::set_close_mes( ! is_locked() ); } SKELETON::Admin* MessageViewBase::get_admin() { return MESSAGE::get_admin(); } // // 親ウィンドウを取得 // Gtk::Window* MessageViewBase::get_parent_win() { return MESSAGE::get_admin()->get_win(); } // // コピー用URL( readcgi型 ) // // メインウィンドウのURLバーなどに表示する) // std::string MessageViewBase::url_for_copy() const { return DBTREE::url_readcgi( get_url(), 0, 0 ); } void MessageViewBase::clock_in() { if( m_preview ) m_preview->clock_in(); ++m_counter; if( m_counter % ( PASS_TIMEOUT / TIMER_TIMEOUT ) == 0 ){ m_counter = 0; // 書き込みから時間があまり経っていなければステータス表示を更新 if( time( nullptr ) - DBTREE::article_write_time( get_url() ) < 60 * 60 ) show_status(); // 書き込み規制経過時刻表示 time_t left = DBTREE::board_write_leftsec( get_url() ); if( left ){ bool set_color = false; if( m_str_pass.empty() ) set_color = true; m_str_pass = "規制中 " + std::to_string( left ) + " 秒 "; std::string force; if( SESSION::focused_admin() == SESSION::FOCUS_MESSAGE ) force = "force"; MESSAGE::get_admin()->set_command( "set_title", get_url(), m_str_pass + get_title(), force ); MESSAGE::get_admin()->set_command( "set_status", get_url(), m_str_pass + get_status(), force ); if( set_color ){ MESSAGE::get_admin()->set_command( "redraw_toolbar" ); MESSAGE::get_admin()->set_command( "set_status_color", get_url(), get_color(), force ); } } else if( ! m_str_pass.empty() ){ m_str_pass = std::string(); std::string force; if( SESSION::focused_admin() == SESSION::FOCUS_MESSAGE ) force = "force"; MESSAGE::get_admin()->set_command( "set_title", get_url(), get_title(), force ); MESSAGE::get_admin()->set_command( "set_status", get_url(), get_status(), force ); MESSAGE::get_admin()->set_command( "redraw_toolbar" ); MESSAGE::get_admin()->set_command( "set_status_color", get_url(), get_color(), force ); } } } // // セットアップ // void MessageViewBase::setup_view() { pack_widget(); } // // フォント初期化 // void MessageViewBase::init_font( const std::string& fontname ) { Pango::FontDescription pfd( fontname ); pfd.set_weight( Pango::WEIGHT_NORMAL ); m_entry_name.modify_font( pfd ); m_entry_mail.modify_font( pfd ); if( m_text_message ) m_text_message->modify_font( pfd ); } // // 色初期化 // void MessageViewBase::init_color() { if( m_text_message ){ if( CONFIG::get_use_message_gtktheme() ) { m_text_message->update_style( u8"" ); } else { const char* const classname = m_text_message->get_css_classname(); const auto fg = Gdk::RGBA( CONFIG::get_color( COLOR_CHAR_MESSAGE ) ).to_string(); const auto bg = Gdk::RGBA( CONFIG::get_color( COLOR_BACK_MESSAGE ) ).to_string(); const auto sel_fg = Gdk::RGBA( CONFIG::get_color( COLOR_CHAR_MESSAGE_SELECTION ) ).to_string(); const auto sel_bg = Gdk::RGBA( CONFIG::get_color( COLOR_BACK_MESSAGE_SELECTION ) ).to_string(); m_text_message->update_style( Glib::ustring::compose( u8R"( .%1, .%1 text { color: %2; background-color: %3; caret-color: %2; } .%1:selected, .%1:selected:focus, .%1 text:selected, .%1 text:selected:focus, .%1 text selection, .%1 text selection:focus { color: %4; background-color: %5; } )", classname, fg, bg, sel_fg, sel_bg ) ); } } } void MessageViewBase::set_message( const std::string& msg ) { if( m_text_message ) m_text_message->set_text( msg ); } Glib::ustring MessageViewBase::get_message() const { if( m_text_message ) return m_text_message->get_text(); return Glib::ustring(); } // // ロード中 // // virtual bool MessageViewBase::is_loading() const { if( ! m_post ) return false; return m_post->is_loading(); } // // コマンド // bool MessageViewBase::set_command( const std::string& command, const std::string& arg1, const std::string& arg2 ) { if( command == "empty" ) return get_message().empty(); else if( command == "toggle_preview" ) toggle_preview(); else if( command == "undo_text" && m_text_message ) m_text_message->undo(); else if( command == "insert_draft" ) insert_draft(); else if( command == "tab_left" ) tab_left(); else if( command == "tab_right" ) tab_right(); else if( command == "hide_popup" && m_preview ) m_preview->set_command( "hide_popup" ); // メッセージをクリア else if( command == "clear_message" ){ if( m_text_message ){ m_text_message->set_text( std::string() ); m_text_message->clear_undo(); } if( m_notebook.get_current_page() != PAGE_MESSAGE ){ m_enable_focus = false; m_notebook.set_current_page( PAGE_MESSAGE ); m_enable_focus = true; } } // メッセージを追加 else if( command == "add_message" ) { if( ! arg1.empty() && m_text_message ) m_text_message->insert_str( arg1, true ); } // メッセージ保存 else if( command == "save_message" ) { if( ! get_message().empty() ){ std::string filename = "draft-" + MISC::get_filename( get_url() ); if( filename.find( ".dat" ) != std::string::npos ) filename = MISC::replace_str( filename, ".dat", ".txt" ); else filename += ".txt"; std::string save_to = CACHE::open_save_diag( MESSAGE::get_admin()->get_win(), SESSION::get_dir_draft(), filename, CACHE::FILE_TYPE_TEXT ); if( ! save_to.empty() ){ SESSION::set_dir_draft( MISC::get_dir( save_to ) ); if( CACHE::save_rawdata( save_to, get_message() ) != get_message().raw().length() ){ SKELETON::MsgDiag mdiag( get_parent_win(), "保存に失敗しました。\nハードディスクの容量やパーミッションなどを確認してください。" ); mdiag.run(); } else return true; } } } // ビューの wrap 切り替え else if( command == "toggle_wrap" ) set_wrap(); return false; } // // 名前やメールを保存 // void MessageViewBase::save_name() { bool check_fixname = m_check_fixname.get_active(); bool check_fixmail = m_check_fixmail.get_active(); if( check_fixname != DBTREE::write_fixname( get_url() ) ) DBTREE::set_write_fixname( get_url(), check_fixname ); if( check_fixname ){ std::string name = m_entry_name.get_text(); if( name != DBTREE::write_name( get_url() ) ) DBTREE::set_write_name( get_url(), name ); } if( check_fixmail != DBTREE::write_fixmail( get_url() ) ) DBTREE::set_write_fixmail( get_url(), check_fixmail ); if( check_fixmail ){ std::string mail = m_entry_mail.get_text(); if( mail != DBTREE::write_mail( get_url() ) ) DBTREE::set_write_mail( get_url(), mail ); } } // // 名前欄に名前をセット // void MessageViewBase::set_name() { // スレ別の名前 if( DBTREE::write_fixname( get_url() ) ){ m_check_fixname.set_active(); m_entry_name.set_text( DBTREE::write_name( get_url() ) ); } // スレ別の名前が設定されていなかったら板別の名前 else if( ! DBTREE::board_get_write_name( get_url() ).empty() ){ std::string tmpname = DBTREE::board_get_write_name( get_url() ); // 空白セット if( tmpname == JD_NAME_BLANK ) m_entry_name.set_text( std::string() ); else m_entry_name.set_text( tmpname ); } // デフォルトをセット else m_entry_name.set_text( CONFIG::get_write_name() ); } // // メール欄にアドレスをセット // void MessageViewBase::set_mail() { // スレ別のメール if( DBTREE::write_fixmail( get_url() ) ){ m_check_fixmail.set_active(); m_entry_mail.set_text( DBTREE::write_mail( get_url() ) ); } // スレ別の名前が設定されていなかったら板別のメール else if( ! DBTREE::board_get_write_mail( get_url() ).empty() ){ std::string tmpmail = DBTREE::board_get_write_mail( get_url() ); // 空白セット if( tmpmail == JD_MAIL_BLANK ) m_entry_mail.set_text( std::string() ); else m_entry_mail.set_text( tmpmail ); } // デフォルトをセット else m_entry_mail.set_text( CONFIG::get_write_mail() ); } // // ツールバーなどのパック // void MessageViewBase::pack_widget() { // 書き込みビュー m_label_name.set_xalign( 0 ); m_label_mail.set_xalign( 0 ); m_label_name.set_text( " 名前 " ); m_label_mail.set_text( " メール " ); m_check_fixname.set_label( "固定" ); m_check_fixmail.set_label( "固定" ); m_check_fixname.set_tooltip_text( "チェックすると名前欄を保存して固定にする" ); m_check_fixmail.set_tooltip_text( "チェックするとメール欄を保存して固定にする" ); set_name(); set_mail(); m_tool_name.add( m_label_name ); m_tool_mail.add( m_label_mail ); m_tool_fixname.add( m_check_fixname ); m_tool_fixmail.add( m_check_fixmail ); m_tool_entry_name.add( m_entry_name ); m_tool_entry_mail.add( m_entry_mail ); m_tool_entry_name.set_expand( true ); m_tool_entry_mail.set_expand( true ); m_toolbar_name_mail.set_icon_size( Gtk::ICON_SIZE_MENU ); m_toolbar_name_mail.set_toolbar_style( Gtk::TOOLBAR_ICONS ); m_toolbar_name_mail.append( m_tool_name ); m_toolbar_name_mail.append( m_tool_fixname ); m_toolbar_name_mail.append( m_tool_entry_name ); m_toolbar_name_mail.append( m_tool_mail ); m_toolbar_name_mail.append( m_tool_fixmail ); m_toolbar_name_mail.append( m_tool_entry_mail ); m_msgview.pack_start( m_toolbar_name_mail, Gtk::PACK_SHRINK ); if( ! m_text_message ){ // 日本語のON/OFF状態を保存 // Admin から EditView のインスタンスをもらう if( CONFIG::get_keep_im_status() ){ m_text_message = MESSAGE::get_admin()->get_text_message(); m_text_message->set_text( std::string() ); m_text_message->clear_undo(); } else m_text_message = Gtk::manage( new SKELETON::EditView() ); } set_wrap(); if( m_text_message->get_parent() ) m_text_message->reparent( m_msgview ); else m_msgview.pack_start( *m_text_message ); m_text_message->set_accepts_tab( false ); m_text_message->sig_key_press().connect( sigc::mem_fun(*this, &MessageViewBase::slot_key_press ) ); m_text_message->sig_button_press().connect( sigc::mem_fun(*this, &MessageViewBase::slot_button_press ) ); m_text_message->get_buffer()->signal_changed().connect( sigc::mem_fun(*this, &MessageViewBase::slot_text_changed ) ); // プレビュー m_preview.reset( CORE::ViewFactory( CORE::VIEW_ARTICLEPREVIEW, get_url() ) ); m_notebook.set_show_tabs( false ); m_notebook.set_show_border( false ); m_notebook.append_page( m_msgview, "メッセージ" ); m_notebook.append_page( *m_preview, "プレビュー" ); m_notebook.signal_switch_page().connect( sigc::mem_fun( *this, &MessageViewBase::slot_switch_page ) ); m_notebook.set_current_page( PAGE_MESSAGE ); pack_start( m_notebook ); set_size_request( 1, 1 ); // フォントセット init_font( CONFIG::get_fontname( FONT_MESSAGE ) ); // 色セット init_color(); show_status(); } // // テキストの折り返し // void MessageViewBase::set_wrap() { if( ! m_text_message ) return; if( CONFIG::get_message_wrap() ) m_text_message->set_wrap_mode( Gtk::WRAP_CHAR ); else m_text_message->set_wrap_mode( Gtk::WRAP_NONE ); } // // 再描画 // void MessageViewBase::redraw_view() { if( m_preview ) m_preview->redraw_view(); } void MessageViewBase::focus_view() { #ifdef _DEBUG // std::cout << "MessageViewBase::focus_view page = " << m_notebook.get_current_page() << std::endl; #endif if( m_notebook.get_current_page() == PAGE_MESSAGE && m_text_message ) m_text_message->focus_view(); else if( m_preview && m_notebook.get_current_page() == PAGE_PREVIEW ) m_preview->focus_view(); } // // viewの操作 // bool MessageViewBase::operate_view( const int control ) { if( control == CONTROL::None ) return false; switch( control ){ // 書き込まずに閉じる case CONTROL::CancelWrite: close_view(); break; // 書き込み実行 case CONTROL::ExecWrite: MESSAGE::get_admin()->set_command( "toolbar_write", get_url() ); break; case CONTROL::TabLeft: case CONTROL::TabLeftUpdated: MESSAGE::get_admin()->set_command( "tab_left" ); break; case CONTROL::TabRight: case CONTROL::TabRightUpdated: MESSAGE::get_admin()->set_command( "tab_right" ); break; case CONTROL::ToggleSage: if( m_entry_mail.get_text() == "sage" ) m_entry_mail.set_text( "" ); else set_mail(); break; // 書き込みボタンにフォーカスを移す case CONTROL::FocusWrite: MESSAGE::get_admin()->set_command( "focus_button_write" ); break; default: return false; } return true; } // // 書き込み実行 // void MessageViewBase::write() { #ifdef _DEBUG std::cout << "MessageViewBase::write\n"; #endif time_t left = DBTREE::board_write_leftsec( get_url() ); if( left && ! SESSION::loginbe() // BEログイン中はダイアログを表示しない ){ constexpr const char* message = " 秒 )\n\nもう暫くお待ち下さい。規制秒数が短くなった場合は板のプロパティからリセットできます。"; SKELETON::MsgDiag mdiag( get_parent_win(), "書き込み規制中です ( 残り " + std::to_string( left ) + message ); mdiag.run(); return; } if( m_post && m_post->is_loading() ){ m_post->show_writingdiag( true ); return; } // fusianasan チェック if( DBTREE::default_noname( get_url() ) == "fusianasan" ) DBTREE::board_set_check_noname( get_url(), true ); const char* reason = nullptr; // 名無し書き込みチェック if( DBTREE::board_check_noname( get_url() ) ){ std::string name = get_entry_name().get_text(); if( name.empty() ){ reason = "名前欄が空欄です。fusianasan 書き込みになる可能性があります。"; } } // 行数チェック if( ! reason && m_max_line ){ if( m_text_message && m_text_message->get_buffer()->get_line_count() > m_max_line ){ reason = "行数が多すぎます。"; } } // バイト数チェック if( ! reason && m_max_str ){ if( m_lng_str_enc > m_max_str ){ reason = "文字数が多すぎます。"; } } if( reason ) { SKELETON::MsgDiag mdiag( get_parent_win(), "投稿がキャンセルされました。" ); mdiag.property_message_type() = Gtk::MESSAGE_WARNING; mdiag.set_secondary_text( reason ); mdiag.run(); return; } const std::string msg = create_message(); if( msg.empty() ) return; // 数値文字参照(&#????;)書き込み可能か if( DBTREE::get_unicode( get_url() ) == "change" ){ JDLIB::Regex regex; const size_t offset = 0; const bool icase = false; const bool newline = true; const bool usemigemo = false; const bool wchar = false; if( regex.exec( "%26%23[0-9]+%3b", msg, offset, icase, newline, usemigemo, wchar ) ){ SKELETON::MsgDiag mdiag( get_parent_win(), "ユニコード文字が含まれていますが、この板ではユニコード文字は文字化けします(BBS_UNICODE=change)。\n\n書き込みますか?", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO ); if( mdiag.run() != Gtk::RESPONSE_YES ) return; } } write_impl( msg ); } // // 下書きファイル挿入 // void MessageViewBase::insert_draft() { const auto list_files = CACHE::open_load_diag( MESSAGE::get_admin()->get_win(), SESSION::get_dir_draft(), CACHE::FILE_TYPE_TEXT, false ); if( list_files.size() ) { const std::string& open_path = list_files.front(); std::string draft; SESSION::set_dir_draft( MISC::get_dir( open_path ) ); CACHE::load_rawdata( open_path, draft ); if( ! draft.empty() ) set_command( "add_message", draft ); } } // // プレビュー切り替え // void MessageViewBase::toggle_preview() { #ifdef _DEBUG std::cout << "MessageViewBase::toggle_preview page = " << m_notebook.get_current_page() << std::endl; #endif if( m_notebook.get_current_page() == PAGE_MESSAGE ) tab_right(); else tab_left(); } // // テキストビューでキーを押した // bool MessageViewBase::slot_key_press( GdkEventKey* event ) { #ifdef _DEBUG_KEY guint key = event->keyval; bool ctrl = ( event->state ) & GDK_CONTROL_MASK; bool shift = ( event->state ) & GDK_SHIFT_MASK; bool alt = ( event->state ) & GDK_MOD1_MASK; std::cout << "MessageViewBase::slot_key_press" << " key = " << key << " ctrl = " << ctrl << " shift = " << shift << " alt = " << alt << std::endl; #endif return operate_view( SKELETON::View::get_control().key_press( event ) ); } // // テキストビューでマウスボタン押した // bool MessageViewBase::slot_button_press( GdkEventButton* event ) { #ifdef _DEBUG std::cout << "MessageViewBase::slot_button_press\n"; #endif MESSAGE::get_admin()->set_command( "switch_admin" ); return true; } // // フォントの更新 // void MessageViewBase::relayout() { init_font( CONFIG::get_fontname( FONT_MESSAGE ) ); init_color(); } // // 閉じる // void MessageViewBase::close_view() { #ifdef _DEBUG std::cout << "MessageViewBase::close_view\n"; #endif MESSAGE::get_admin()->set_command( "close_currentview" ); } // // タブ左移動 // void MessageViewBase::tab_left() { #ifdef _DEBUG std::cout << "MessageViewBase::tab_left\n"; #endif int page = m_notebook.get_current_page(); if( page == PAGE_MESSAGE ) m_notebook.set_current_page( PAGE_PREVIEW ); else m_notebook.set_current_page( PAGE_MESSAGE ); focus_view(); } // // タブ右移動 // void MessageViewBase::tab_right() { #ifdef _DEBUG std::cout << "MessageViewBase::tab_right\n"; #endif int page = m_notebook.get_current_page(); if( page == PAGE_PREVIEW ) m_notebook.set_current_page( PAGE_MESSAGE ); else m_notebook.set_current_page( PAGE_PREVIEW ); m_preview->focus_view(); } // // 書き込み // void MessageViewBase::post_msg( const std::string& msg, bool new_article ) { push_logitem(); if( m_post ){ m_post->terminate_load(); } m_post = std::make_unique( this, get_url(), msg, new_article ); m_post->sig_fin().connect( sigc::mem_fun( *this, &MessageViewBase::post_fin ) ); m_post->post_msg(); // 名前、メール履歴保存 std::string name = m_entry_name.get_text(); std::string mail = m_entry_mail.get_text(); CORE::get_completion_manager()->set_query( CORE::COMP_NAME, name ); CORE::get_completion_manager()->set_query( CORE::COMP_MAIL, mail ); } // // 書き込みが終わったら呼ばれる // void MessageViewBase::post_fin() { int code = m_post->get_code(); std::string location = m_post->location(); #ifdef _DEBUG std::cout << "MessageViewBase::post_fin" << std::endl << "code = " << code << std::endl << "location = " << location << std::endl; #endif // 成功 if( code == HTTP_OK || ( ( code == HTTP_MOVED_PERM || code == HTTP_REDIRECT ) && ! location.empty() ) // (まちBBSなどで)リダイレクトした場合 ){ save_postlog(); if( m_text_message ){ m_text_message->set_text( std::string() ); m_text_message->clear_undo(); } close_view(); if( ! SESSION::is_live( get_url() ) ) reload(); } // タイムアウト else if( code == HTTP_TIMEOUT ){ SKELETON::MsgDiag mdiag( get_parent_win(), "タイムアウトしました\n\n書き込み自体は成功している可能性があります。\nメッセージのバックアップをとってからスレを再読み込みして下さい。" ); mdiag.run(); } // 失敗 else if( code != HTTP_CANCEL ){ SKELETON::DetailDiag ddiag( get_parent_win(), get_url(), false, "書き込みに失敗しました\n\n" + m_post->get_errmsg(), "概要", m_post->get_return_html(), "詳細" ); ddiag.set_title( "書き込みエラー" ); ddiag.run(); } } // // タブのページが切り替わったら呼ばれるslot // void MessageViewBase::slot_switch_page( Gtk::Widget*, guint page ) { #ifdef _DEBUG std::cout << "MessageViewBase::slot_switch_page : " << get_url() << " page = " << page << std::endl; #endif // プレビュー表示 if( m_preview && page == PAGE_PREVIEW ){ // ツールバー切り替え MESSAGE::get_admin()->set_command( "switch_toolbar_preview" ); std::string new_subject = MESSAGE::get_admin()->get_new_subject(); if( ! new_subject.empty() ) set_label( new_subject ); // URLを除外してエスケープ const bool include_url = false; std::string msg; if( m_text_message ) msg = MISC::html_escape( m_text_message->get_text(), include_url ); msg = MISC::replace_str( msg, "\n", "
    " ); std::stringstream ss; // 名前 + トリップ if( ! m_entry_name.get_text().empty() ){ const std::string name_field = m_entry_name.get_text(); const size_t trip_pos = name_field.find( '#', 0 ); const std::string name = MISC::html_escape( name_field.substr( 0, trip_pos ) ); std::string trip; if( trip_pos != std::string::npos ) { trip = MISC::get_trip( name_field.substr( trip_pos + 1 ), DBTREE::board_charset( get_url() ) ); } ss << name; if( ! trip.empty() ) ss << " ◆" << trip; } else ss << DBTREE::default_noname( get_url() ); std::string mail = MISC::html_escape( m_entry_mail.get_text() ); ss << "<>" << mail << "<>"; const std::time_t current = std::time( nullptr ); ss << MISC::timettostr( current, MISC::TIME_WEEK ); ss << " ID:???" << "<>" << msg << "<>\n"; #ifdef _DEBUG std::cout << ss.str() << std::endl; #endif m_preview->set_command( "clear_screen" ); m_preview->set_command( "append_dat", ss.str() ); } // メッセージビュー else if( page == PAGE_MESSAGE ){ // ツールバー切り替え MESSAGE::get_admin()->set_command( "switch_toolbar_message" ); } if( m_enable_focus ){ MESSAGE::get_admin()->set_command( "switch_admin" ); MESSAGE::get_admin()->set_command( "focus_current_view" ); } } // // 書き込み欄のテキストが更新された // void MessageViewBase::slot_text_changed() { m_text_changed = true; show_status(); m_text_changed = false; } // // ステータス表示 // void MessageViewBase::show_status() { if( ! m_text_message ) return; const bool broken = is_broken(); std::stringstream ss; const int line_count = m_text_message->get_buffer()->get_line_count(); ss << " [ 行数 " << line_count; if( m_max_line ){ ss << "/ " << m_max_line; if( m_max_line < line_count ) m_over_lines = true; else m_over_lines = false; } const std::string message = m_text_message->get_text(); ss << " / 文字数 "; if( ( int ) message.size() > m_lng_iconv ) { ss << "過多"; } else if( m_text_changed ) { int byte_out; const char* msgc = message.c_str(); std::string str_enc = m_iconv->convert( (char*)msgc, strlen( msgc ), byte_out ); m_lng_str_enc = str_enc.length(); // 特殊文字の文字数を計算 for( const char c : str_enc ) { if( c == '\n' || c == '"' ) { // "
    " = 6バイト, " = 6バイト m_lng_str_enc += 5; } else if( c == '<' || c == '>' ) { // < = 4バイト, > = 4バイト m_lng_str_enc += 3; } } ss << m_lng_str_enc; } else ss << m_lng_str_enc; if( m_max_str ){ ss << "/ " << m_max_str; if( m_max_str < m_lng_str_enc ) m_over_lng = true; else m_over_lng = false; } if( DBTREE::get_unicode( get_url() ) == "pass" ) ss << " / unicode ○"; else if( DBTREE::get_unicode( get_url() ) == "change" ) ss << " / unicode ×"; const time_t wtime = DBTREE::article_write_time( get_url() ); if( wtime ) ss << " / 最終書込 " << ( MISC::timettostr( wtime, MISC::TIME_WEEK ) + " ( " + MISC::timettostr( wtime, MISC::TIME_PASSED ) + " )" ); ss << " ]"; set_status( ss.str() ); MESSAGE::get_admin()->set_command( "set_status", get_url(), m_str_pass + get_status() ); if( broken != is_broken() ){ MESSAGE::get_admin()->set_command( "redraw_toolbar" ); MESSAGE::get_admin()->set_command( "set_status_color", get_url(), get_color() ); } } // // 自分の書き込みの判定用データの保存 // // 実況中など post_fin() がコールされる前に自分のレスが表示されてしまう時があるので // m_post->post_msg() する前に情報を保存しておく // void MessageViewBase::push_logitem() { if( ! m_text_message ) return; const bool newthread = ! ( MESSAGE::get_admin()->get_new_subject().empty() ); const std::string msg = m_text_message->get_text(); MESSAGE::get_log_manager()->push_logitem( get_url(), newthread, msg ); } // // 書き込みログ保存 // void MessageViewBase::save_postlog() { if( ! m_text_message ) return; std::string subject = MESSAGE::get_admin()->get_new_subject(); if( subject.empty() ) subject = DBTREE::article_subject( get_url() ); const std::string msg = m_text_message->get_text(); const std::string name = get_entry_name().get_text(); const std::string mail = get_entry_mail().get_text(); MESSAGE::get_log_manager()->save( get_url(), subject, msg, name, mail ); } jdim-0.7.0/src/message/messageviewbase.h000066400000000000000000000107061417047150700202110ustar00rootroot00000000000000// ライセンス: GPL2 #ifndef _MESSAGEVIEWBASE_H #define _MESSAGEVIEWBASE_H #include "gtkmmversion.h" #include "skeleton/view.h" #include "skeleton/compentry.h" #include "skeleton/jdtoolbar.h" #include namespace JDLIB { class Iconv; } namespace SKELETON { class Admin; class LabelEntry; class EditView; } namespace MESSAGE { class Post; class MessageViewBase : public SKELETON::View { std::unique_ptr m_post; Gtk::Notebook m_notebook; std::unique_ptr m_preview; Gtk::VBox m_msgview; SKELETON::JDToolbar m_toolbar_name_mail; Gtk::ToolItem m_tool_name; Gtk::ToolItem m_tool_mail; Gtk::ToolItem m_tool_fixname; Gtk::ToolItem m_tool_fixmail; Gtk::ToolItem m_tool_entry_name; Gtk::ToolItem m_tool_entry_mail; Gtk::Label m_label_name; Gtk::Label m_label_mail; Gtk::CheckButton m_check_fixname; Gtk::CheckButton m_check_fixmail; SKELETON::CompletionEntry m_entry_name; SKELETON::CompletionEntry m_entry_mail; SKELETON::EditView* m_text_message{}; bool m_enable_focus; // 文字数計算用 std::unique_ptr m_iconv; int m_max_line; int m_max_str; int m_lng_str_enc{}; int m_lng_iconv; // 経過時間表示用 int m_counter{}; std::string m_str_pass; bool m_text_changed{}; bool m_over_lines{}; bool m_over_lng{}; public: explicit MessageViewBase( const std::string& url ); ~MessageViewBase(); // // SKELETON::View の関数のオーバロード // void save_session() override {} // 親ウィンドウを取得 Gtk::Window* get_parent_win() override; // コピー用のURL std::string url_for_copy() const override; // コマンド bool set_command( const std::string& command, const std::string& arg1 = {}, const std::string& arg2 = {} ) override; // ロード中 bool is_loading() const override; // 規制中や行数や文字列がオーバーして書き込めない bool is_broken() const override { return ( ! m_str_pass.empty() || m_over_lines || m_over_lng ); } // キーを押した bool slot_key_press( GdkEventKey* event ) override; void clock_in() override; void write() override; void reload() override {} void relayout() override; void close_view() override; void redraw_view() override; void focus_view() override; bool operate_view( const int control ) override; private: // フォント初期化 void init_font( const std::string& fontname ); // 色初期化 void init_color(); // 名前欄に名前をセット void set_name(); // メール欄にアドレスをセット void set_mail(); // 自分の書き込みの判定用データの保存 void push_logitem(); // 書き込みログ保存 void save_postlog(); // 実際の書き込み処理を行う関数(子クラス別に実装) virtual void write_impl( const std::string& msg ) = 0; // プレビュー切り替え void toggle_preview(); void tab_left(); void tab_right(); // 下書きファイル挿入 void insert_draft(); bool slot_button_press( GdkEventButton* event ); void slot_switch_page( Gtk::Widget*, guint page ); void slot_text_changed(); virtual std::string create_message() = 0; void show_status(); protected: // Viewが所属するAdminクラス SKELETON::Admin* get_admin() override; void set_message( const std::string& msg ); Glib::ustring get_message() const; const SKELETON::CompletionEntry& get_entry_name() const { return m_entry_name; } const SKELETON::CompletionEntry& get_entry_mail() const { return m_entry_mail; } SKELETON::EditView* get_text_message() { return m_text_message; } void post_msg( const std::string& msg, bool new_article ); void post_fin(); void save_name(); void setup_view(); void pack_widget(); // テキストの折り返し void set_wrap(); }; } #endif jdim-0.7.0/src/message/messagewin.cpp000066400000000000000000000060751417047150700175400ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "messageadmin.h" #include "messagewin.h" #include "jdlib/miscgtk.h" #include "config/globalconf.h" #include "session.h" #include "command.h" using namespace MESSAGE; // メッセージウィンドウにはステータスバー内のマウスジェスチャ表示欄が // 不要なので SKELETON::JDWindow() の第2引数を flase にする MessageWin::MessageWin() : SKELETON::JDWindow( CONFIG::get_fold_message(), false ) { #ifdef _DEBUG std::cout << "MessageWin::MessageWin x y w h = " << MessageWin::get_x_win() << " " << MessageWin::get_y_win() << " " << MessageWin::get_width_win() << " " << MessageWin::get_height_win() << std::endl; #endif get_vbox().pack_remove_end( false, get_statbar(), Gtk::PACK_SHRINK ); init_win(); if( ! CONFIG::get_fold_message() ) set_transient_for( *CORE::get_mainwindow() ); show_all_children(); } MessageWin::~MessageWin() { #ifdef _DEBUG std::cout << "MessageWin::~MessageWin window size : x = " << MessageWin::get_x_win() << " y = " << MessageWin::get_y_win() << " w = " << MessageWin::get_width_win() << " h = " << MessageWin::get_height_win() << " max = " << MessageWin::is_maximized_win() << std::endl; #endif MessageWin::set_shown_win( false ); CORE::core_set_command( "restore_focus" ); } bool MessageWin::on_delete_event( GdkEventAny* event ) { #ifdef _DEBUG std::cout << "MessageWin::on_delete_event\n"; #endif MESSAGE::get_admin()->set_command( "close_currentview" ); return true; } int MessageWin::get_x_win() const { return SESSION::get_x_win_mes(); } int MessageWin::get_y_win() const { return SESSION::get_y_win_mes(); } void MessageWin::set_x_win( const int x ) { SESSION::set_x_win_mes( x ); } void MessageWin::set_y_win( const int y ) { SESSION::set_y_win_mes( y ); } int MessageWin::get_width_win() const { return SESSION::get_width_win_mes(); } int MessageWin::get_height_win() const { return SESSION::get_height_win_mes(); } void MessageWin::set_width_win( const int width ) { SESSION::set_width_win_mes( width ); } void MessageWin::set_height_win( const int height ) { SESSION::set_height_win_mes( height ); } bool MessageWin::is_focus_win() const { return SESSION::is_focus_win_mes(); } void MessageWin::set_focus_win( const bool set ) { SESSION::set_focus_win_mes( set ); } bool MessageWin::is_maximized_win() const { return SESSION::is_maximized_win_mes(); } void MessageWin::set_maximized_win( const bool set ) { SESSION::set_maximized_win_mes( set ); } bool MessageWin::is_iconified_win() const { return SESSION::is_iconified_win_mes(); } void MessageWin::set_iconified_win( const bool set ) { SESSION::set_iconified_win_mes( set ); } bool MessageWin::is_shown_win() const { return SESSION::is_shown_win_mes(); } void MessageWin::set_shown_win( const bool set ) { SESSION::set_shown_win_mes( set ); } void MessageWin::switch_admin() { CORE::core_set_command( "switch_message" ); } jdim-0.7.0/src/message/messagewin.h000066400000000000000000000024271417047150700172020ustar00rootroot00000000000000// ライセンス: GPL2 #ifndef _MESSAGEWIN_H #define _MESSAGEWIN_H #include #include "skeleton/window.h" namespace MESSAGE { class MessageWin : public SKELETON::JDWindow { public: MessageWin(); ~MessageWin(); protected: void switch_admin() override; int get_x_win() const override; int get_y_win() const override; void set_x_win( const int x ) override; void set_y_win( const int y ) override; int get_width_win() const override; int get_height_win() const override; void set_width_win( const int width ) override; void set_height_win( const int height ) override; bool is_focus_win() const override; void set_focus_win( const bool set ) override; bool is_maximized_win() const override; void set_maximized_win( const bool set ) override; bool is_iconified_win() const override; void set_iconified_win( const bool set ) override; bool is_full_win() const override { return false; } void set_full_win( const bool ) override {} bool is_shown_win() const override; void set_shown_win( const bool set ) override; bool on_delete_event( GdkEventAny* event ) override; }; } #endif jdim-0.7.0/src/message/post.cpp000066400000000000000000000346521417047150700163650ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "messageadmin.h" #include "post.h" #include "confirmdiag.h" #include "skeleton/msgdiag.h" #include "jdlib/loaderdata.h" #include "jdlib/jdiconv.h" #include "jdlib/miscmsg.h" #include "jdlib/miscutil.h" #include "jdlib/jdregex.h" #include "dbtree/interface.h" #include "config/globalconf.h" #include "httpcode.h" #include namespace { // PostStrategyの実装 // newするのは大げさなので静的変数を定義した // 書き込み用のインターフェース struct WriteStrategy : public MESSAGE::PostStrategy { WriteStrategy() noexcept = default; ~WriteStrategy() noexcept = default; std::string url_bbscgi( const std::string& url ) override { return DBTREE::url_bbscgi( url ); } std::string url_subbbscgi( const std::string& url ) override { return DBTREE::url_subbbscgi( url ); } std::string get_referer( const std::string& url ) const override { return DBTREE::get_write_referer( url ); } } s_write_strategy; // スレ立て用のインターフェース struct NewArticleStrategy : public MESSAGE::PostStrategy { NewArticleStrategy() noexcept = default; ~NewArticleStrategy() noexcept = default; std::string url_bbscgi( const std::string& url ) override { return DBTREE::url_bbscgi_new( url ); } std::string url_subbbscgi( const std::string& url ) override { return DBTREE::url_subbbscgi_new( url ); } std::string get_referer( const std::string& url ) const override { return DBTREE::get_newarticle_referer( url ); } } s_new_article_strategy; } // namespace using namespace MESSAGE; enum { SIZE_OF_RAWDATA = 2 * 1024 * 1024 }; Post::Post( Gtk::Widget* parent, const std::string& url, const std::string& msg, bool new_article ) : SKELETON::Loadable() , m_parent( parent ) , m_url( url ) , m_msg( msg ) , m_new_article( new_article ) { #ifdef _DEBUG std::cout << "Post::Post " << m_url << std::endl; #endif if( new_article ) m_post_strategy = &s_new_article_strategy; else m_post_strategy = &s_write_strategy; clear(); } Post::~Post() { #ifdef _DEBUG std::cout << "Post::~Post " << m_url << std::endl; #endif clear(); } void Post::clear() { #ifdef _DEBUG std::cout << "Post::clear\n"; #endif m_rawdata.clear(); m_rawdata.shrink_to_fit(); if( m_writingdiag ) m_writingdiag->hide(); } void Post::emit_sigfin() { #ifdef _DEBUG std::cout << "Post::emit_sigfin\n"; #endif m_sig_fin.emit(); clear(); m_writingdiag.reset(); } // // 書き込み中ダイアログ表示 // void Post::show_writingdiag( const bool show_buttons ) { Gtk::Window* toplevel = dynamic_cast< Gtk::Window* >( m_parent->get_toplevel() ); if( ! toplevel ) return; Gtk::ButtonsType buttons = Gtk::BUTTONS_NONE; if( show_buttons ) buttons = Gtk::BUTTONS_OK; if( ! m_writingdiag ){ m_writingdiag = std::make_unique( *toplevel, "書き込み中・・・", false, Gtk::MESSAGE_INFO, buttons, false ); m_writingdiag->signal_response().connect( sigc::mem_fun( *this, &Post::slot_response ) ); } m_writingdiag->show(); // gtkのバージョンによってはラベルが選択状態になっている場合があるので // 選択状態を解除する Gtk::Label *label = dynamic_cast< Gtk::Label* >( m_writingdiag->get_focus() ); if( label ) label->set_selectable( false ); } // // 書き込み中ダイアログのボタンを押した // void Post::slot_response( int id ) { #ifdef _DEBUG std::cout << "Post::slot_response id = " << id << std::endl; #endif if( m_writingdiag ) m_writingdiag->hide(); } // // ポスト実行 // void Post::post_msg() { if( is_loading() ) return; clear(); m_rawdata.reserve( SIZE_OF_RAWDATA ); // 書き込み中ダイアログ表示 if( ! CONFIG::get_hide_writing_dialog() ) show_writingdiag( false ); JDLIB::LOADERDATA data; if( ! m_subbbs ) data.url = m_post_strategy->url_bbscgi( m_url ); // 1回目の投稿先 else data.url = m_post_strategy->url_subbbscgi( m_url ); // 2回目の投稿先 // Content-Type (2009/02/18に報告された"したらば"に書き込めない問題で追加) // http://www.asahi-net.or.jp/~sd5a-ucd/rec-html401j/interact/forms.html#h-17.13.4.1 // http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1 data.contenttype = "application/x-www-form-urlencoded"; data.agent = DBTREE::get_agent_w( m_url ); data.referer = m_count < 1 ? m_post_strategy->get_referer( m_url ) : m_post_strategy->url_bbscgi( m_url ); // WebブラウザのUAならOriginを含める if( data.agent.rfind( "Mozilla/5.0", 0 ) == 0 ) { data.origin = MISC::get_hostname( m_url ); } data.str_post = m_msg; data.host_proxy = DBTREE::get_proxy_host_w( m_url ); data.port_proxy = DBTREE::get_proxy_port_w( m_url ); data.basicauth_proxy = DBTREE::get_proxy_basicauth_w( m_url ); data.size_buf = CONFIG::get_loader_bufsize(); data.timeout = CONFIG::get_loader_timeout_post(); data.cookie_for_request = DBTREE::board_cookie_for_post( m_url ); data.basicauth = DBTREE::board_basicauth( m_url ); #ifdef _DEBUG std::cout << "Post::post_msg : " << std::endl << "url = " << data.url << std::endl << "contenttype = " << data.contenttype << std::endl << "agent = " << data.agent << std::endl << "origin = " << data.origin << std::endl << "referer = " << data.referer << std::endl << "cookie = " << data.cookie_for_request << std::endl << "proxy = " << data.host_proxy << ":" << data.port_proxy << std::endl << m_msg << std::endl; #endif if( data.url.empty() ) return; start_load( data ); } // // ローダからデータを受け取る // void Post::receive_data( const char* data, size_t size ) { if( get_code() != HTTP_OK ) return; m_rawdata.append( data, size ); } // // ローダがsendを終了したので戻り値解析 // void Post::receive_finish() { #ifdef _DEBUG std::cout << "Post::receive_finish\n"; #endif { const std::string charset = DBTREE::board_charset( m_url ); JDLIB::Iconv libiconv( "UTF-8", charset ); int byte_out; m_return_html = libiconv.convert( &*m_rawdata.begin(), m_rawdata.size(), byte_out ); } #ifdef _DEBUG std::cout << "code = " << get_code() << std::endl; std::cout << m_return_html << std::endl; #endif clear(); /////////////////// // ポスト失敗 if( get_code() != HTTP_OK && ! ( ( get_code() == HTTP_MOVED_PERM || get_code() == HTTP_REDIRECT ) && ! location().empty() ) // リダイレクトは成功(かもしれない) ){ m_errmsg = get_str_code(); emit_sigfin(); return; } // 以下、code == 200、 又は 302 かつ locationがセットされている(リダイレクト) の場合 JDLIB::Regex regex; const size_t offset = 0; bool icase = false; bool newline = true; const bool usemigemo = false; const bool wchar = false; std::string title; std::string tag_2ch; std::string msg; std::string conf; // タイトル icase = true; newline = false; // . に改行をマッチさせる regex.exec( ".*([^<]*).*", m_return_html, offset, icase, newline, usemigemo, wchar ); title = MISC::remove_space( regex.str( 1 ) ); // 2chタグ icase = false; newline = false; // . に改行をマッチさせる regex.exec( ".*2ch_X:([^\\-]*)\\-\\->.*", m_return_html, offset, icase, newline, usemigemo, wchar ); tag_2ch = MISC::remove_space( regex.str( 1 ) ); // エラー内容を取得 // 一番内側のを探して取得 icase = true; newline = false; // . に改行をマッチさせる m_errmsg = std::string(); if( regex.exec( "([^>]|[^b]>)*(([^>]|[^b]>)*).*", m_return_html, offset, icase, newline, usemigemo, wchar ) ){ m_errmsg = regex.str( 2 ); } // 2ch タグで error が返った場合 else if( tag_2ch.find( "error" ) != std::string::npos ){ icase = true; newline = false; // . に改行をマッチさせる if( regex.exec( "error +-->(.*)", m_return_html, offset, icase, newline, usemigemo, wchar ) ) m_errmsg = regex.str( 1 ); } // p2 型 // XXX: p2ログイン機能を削除したのでもう不要か? else if( title.find( "error" ) != std::string::npos ){ icase = true; newline = false; // . に改行をマッチさせる if( regex.exec( "

    (.*)

    ", m_return_html, offset, icase, newline, usemigemo, wchar ) ) m_errmsg = regex.str( 1 ); } if( ! m_errmsg.empty() ){ m_errmsg = MISC::replace_str( m_errmsg, "\n", "" ); //
    (.*)(.*)", m_errmsg, offset, icase, newline, usemigemo, wchar ) ){ m_errmsg = regex.str( 1 ) + " " + regex.str( 2 ) + " " + regex.str( 3 ) + regex.str( 4 ); } // 改行その他 m_errmsg= MISC::replace_str( m_errmsg, "
    ", "\n" ); m_errmsg= MISC::replace_str( m_errmsg, "
    ", "\n-------------------\n" ); // samba秒取得 icase = false; newline = false; // . に改行をマッチさせる // Smaba24規制の場合 // ERROR - 593 60 sec たたないと書けません。(1回目、8 sec しかたってない) // 忍法帖規制の場合 ( samba秒だけ取得する。 ) // ERROR:修行が足りません(Lv=2)。しばらくたってから投稿してください。(48 sec) // この板のsambaは samba=30 sec if( regex.exec( "ERROR( +- +593 +|:.+samba=)([0-9]+) +sec", m_errmsg, offset, icase, newline, usemigemo, wchar ) ){ time_t sec = atoi( regex.str( 2 ).c_str() ); #ifdef _DEBUG std::cout << "samba = " << sec << std::endl; #endif DBTREE::board_set_samba_sec( m_url, sec ); DBTREE::board_update_writetime( m_url ); } } // 書き込み確認 icase = false; newline = true; regex.exec( ".*([^<]*).*", m_return_html, offset, icase, newline, usemigemo, wchar ); conf = MISC::remove_space( regex.str( 1 ) ); // メッセージ本文 // 2ch 型 icase = false; newline = false; // . に改行をマッチさせる if( ! regex.exec( ".*.*(.*).*.*(.*).* list_cookies = SKELETON::Loadable::cookies(); #ifdef _DEBUG std::cout << "TITLE: [" << title << "]\n"; std::cout << "2ch_X: [" << tag_2ch << "]\n"; std::cout << "CONF: [" << conf << "]\n"; std::cout << "MSG: [" << msg << "]\n"; std::cout << "ERR: [" << m_errmsg << "]\n"; for( const std::string& cookie : list_cookies ) std::cout << "cookie : [" << cookie << "]\n"; std::cout << "location: [" << location() << "]\n"; #endif // クッキーのセット const bool empty_cookies = DBTREE::board_cookie_for_post( m_url ).empty(); if( list_cookies.size() ) DBTREE::board_set_list_cookies( m_url, list_cookies ); // 成功 if( title.find( "書きこみました" ) != std::string::npos || tag_2ch.find( "true" ) != std::string::npos || ( ( get_code() == HTTP_MOVED_PERM || get_code() == HTTP_REDIRECT ) && ! location().empty() ) // リダイレクトされた場合 ){ #ifdef _DEBUG std::cout << "write ok" << std::endl; #endif DBTREE::article_update_writetime( m_url ); if( m_new_article ) { // 板のフロントページをダウンロードしてスレ立てに使うキーワードを更新する DBTREE::board_download_front( m_url ); } emit_sigfin(); return; } // クッキー確認 else if( m_count < 1 && // 永久ループ防止 ( title.find( "書き込み確認" ) != std::string::npos || tag_2ch.find( "cookie" ) != std::string::npos || ( empty_cookies && list_cookies.size() ) ) ){ clear(); // 書き込み確認表示 if( ! CONFIG::get_always_write_ok() ){ std::string diagmsg = MISC::replace_str( msg, "
    ", "\n" ); if( diagmsg.empty() ){ if( m_errmsg.empty() ) diagmsg = "クッキーを有功にして書き込みますか?"; else diagmsg = m_errmsg; } ConfirmDiag mdiag( m_url, diagmsg ); const int response = mdiag.run(); mdiag.hide(); if( response != Gtk::RESPONSE_OK ){ set_code( HTTP_CANCEL ); emit_sigfin(); return; } if( mdiag.get_chkbutton().get_active() ) CONFIG::set_always_write_ok( true ); } // HTMLからフォームデータを取得できたらメッセージボディを更新する std::string msg_body = DBTREE::board_parse_form_data( m_url, m_return_html ); if( ! msg_body.empty() ) m_msg = std::move( msg_body ); // subbbs.cgi にポスト先を変更してもう一回ポスト m_subbbs = true; ++m_count; // 永久ループ防止 post_msg(); return; } // スレ立て時の書き込み確認 else if( m_count < 1 // 永久ループ防止 && ! m_subbbs && conf.find( "書き込み確認" ) != std::string::npos ){ // HTMLからフォームデータを取得できたらメッセージボディを更新する std::string msg_body = DBTREE::board_parse_form_data( m_url, m_return_html ); if( ! msg_body.empty() ) m_msg = std::move( msg_body ); // subbbs.cgi にポスト先を変更してもう一回ポスト m_subbbs = true; ++m_count; // 永久ループ防止 post_msg(); return; } // その他のエラー #ifdef _DEBUG std::cout << "Error" << std::endl; std::cout << m_return_html << std::endl; #endif MISC::ERRMSG( m_errmsg ); set_code( HTTP_ERR ); emit_sigfin(); } jdim-0.7.0/src/message/post.h000066400000000000000000000042761417047150700160310ustar00rootroot00000000000000// ライセンス: GPL2 // // 記事投稿クラス // #ifndef _POST_H #define _POST_H #include "skeleton/loadable.h" #include #include #include namespace SKELETON { class MsgDiag; } namespace MESSAGE { // 投稿の種類で異なるインターフェース部分を抽象クラスにしてStrategyパターンを使う // 実行時にコンストラクタでインターフェースを選択する struct PostStrategy { virtual ~PostStrategy() noexcept = default; virtual std::string url_bbscgi( const std::string& url ) = 0; // 1回目の投稿先 virtual std::string url_subbbscgi( const std::string& url ) = 0; // 2回目の投稿先 virtual std::string get_referer( const std::string& url ) const = 0; // リファラを取得 }; class Post : public SKELETON::Loadable { // ポスト終了シグナル typedef sigc::signal< void > SIG_FIN; SIG_FIN m_sig_fin; // 親widget Gtk::Widget* m_parent; std::string m_url; std::string m_msg; std::string m_return_html; std::string m_errmsg; std::string m_rawdata; int m_count{}; // 書き込み確認時の永久ループ防止用 bool m_subbbs{}; // true なら subbbs.cgiにpostする bool m_new_article; // 新スレ作成 // 書き込んでいますのダイアログ std::unique_ptr m_writingdiag; PostStrategy* m_post_strategy; public: Post( Gtk::Widget* parent, const std::string& url, const std::string& msg, bool new_article ); ~Post(); SIG_FIN sig_fin() const { return m_sig_fin; } const std::string& get_return_html() const { return m_return_html;} const std::string& get_errmsg() const { return m_errmsg; } void post_msg(); // 書き込み中ダイアログ表示 void show_writingdiag( const bool show_buttons ); private: void clear(); void emit_sigfin(); void slot_response( int id ); void receive_data( const char* data, size_t size ) override; void receive_finish() override; }; } #endif jdim-0.7.0/src/message/toolbar.cpp000066400000000000000000000202621417047150700170320ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "toolbar.h" #include "messageadmin.h" #include "skeleton/imgtoolbutton.h" #include "icons/iconmanager.h" #include "control/controlutil.h" #include "control/controlid.h" #include "session.h" #include "global.h" using namespace MESSAGE; MessageToolBarBase::MessageToolBarBase() : SKELETON::ToolBar( MESSAGE::get_admin() ) , m_enable_slot( true ) {} Gtk::ToggleToolButton* MessageToolBarBase::get_button_preview() { #ifdef _DEBUG std::cout << "MessageToolBarBase::get_button_preview\n"; #endif if( ! m_button_preview ){ m_button_preview = Gtk::manage( new SKELETON::ImgToggleToolButton( ICON::PREVIEW ) ); m_button_preview->signal_clicked().connect( sigc::mem_fun( *this, &MessageToolBarBase::slot_toggle_preview ) ); } return m_button_preview; } // プレビュー切り替え void MessageToolBarBase::slot_toggle_preview() { if( ! m_enable_slot ) return; #ifdef _DEBUG std::cout << "MessageToolBarBase::slot_toggle_preview\n"; #endif MESSAGE::get_admin()->set_command( "toggle_preview" ); } // previewボタンのトグル void MessageToolBarBase::set_active_previewbutton( const bool active ) { m_enable_slot = false; m_button_preview->set_active( active ); m_enable_slot = true; } ////////////////////////////////// // 通常のツールバー MessageToolBar::MessageToolBar() : MessageToolBarBase() { MessageToolBar::pack_buttons(); } MessageToolBar::~MessageToolBar() noexcept = default; // 新規スレ名entry表示切り替え void MessageToolBar::show_entry_new_subject( bool show ) { if( m_show_entry_new_subject == show ) return; m_show_entry_new_subject = show; update_button(); } std::string MessageToolBar::get_new_subject() const { if( m_show_entry_new_subject && m_entry_new_subject ) return m_entry_new_subject->get_text(); return std::string(); } void MessageToolBar::clear_new_subject() { if( m_entry_new_subject ) m_entry_new_subject->set_text( "" ); } // ボタンのパッキング // virtual void MessageToolBar::pack_buttons() { #ifdef _DEBUG std::cout << "MessageToolBar::pack_buttons\n"; #endif int num = 0; for(;;){ int item = SESSION::get_item_msg_toolbar( num ); if( item == ITEM_END ) break; switch( item ){ case ITEM_PREVIEW: if( auto button = get_button_preview() ) { get_buttonbar().append( *button ); button->set_label( CONTROL::get_label( CONTROL::Preview ) ); set_tooltip( *button, CONTROL::get_label_motions( CONTROL::Preview ) + "\n\nタブ移動のショートカットでも表示の切り替えが可能\n\n" + CONTROL::get_label_motions( CONTROL::TabRight ) + "\n\n" + CONTROL::get_label_motions( CONTROL::TabLeft ) ); } break; case ITEM_WRITEMSG: get_buttonbar().append( *get_button_write() ); set_tooltip( *get_button_write(), CONTROL::get_label_motions( CONTROL::ExecWrite ) + "\n\nTabキーで書き込みボタンにフォーカスを移すことも可能" ); break; case ITEM_OPENBOARD: get_buttonbar().append( *get_button_board() ); break; case ITEM_NAME: pack_transparent_separator(); // スレ名ラベルを表示 if( ! m_show_entry_new_subject ) get_buttonbar().append( *get_label() ); // 新規スレ名入力entry表示 else{ if( ! m_tool_new_subject ){ m_tool_new_subject = Gtk::manage( new Gtk::ToolItem ); m_entry_new_subject = Gtk::manage( new Gtk::Entry ); m_entry_new_subject->set_size_request( 0 ); m_tool_new_subject->add( *m_entry_new_subject ); m_tool_new_subject->set_expand( true ); } get_buttonbar().append( *m_tool_new_subject ); } pack_transparent_separator(); break; case ITEM_UNDO: if( ! m_button_undo ){ m_button_undo = Gtk::manage( new SKELETON::ImgToolButton( ICON::UNDO, CONTROL::UndoEdit ) ); m_button_undo->signal_clicked().connect( sigc::mem_fun( *this, &MessageToolBar::slot_undo_clicked ) ); } get_buttonbar().append( *m_button_undo ); set_tooltip( *m_button_undo, CONTROL::get_label_motions( CONTROL::UndoEdit ) ); break; case ITEM_INSERTTEXT: if( ! m_button_insert_draft ){ m_button_insert_draft = Gtk::manage( new SKELETON::ImgToolButton( ICON::INSERTTEXT, CONTROL::InsertText ) ); m_button_insert_draft->signal_clicked().connect( sigc::mem_fun( *this, &MessageToolBar::slot_insert_draft_clicked ) ); } get_buttonbar().append( *m_button_insert_draft ); set_tooltip( *m_button_insert_draft, CONTROL::get_label_motions( CONTROL::InsertText ) ); break; case ITEM_LOCK_MESSAGE: if( auto button = get_button_lock() ) { get_buttonbar().append( *button ); button->set_label( CONTROL::get_label( CONTROL::LockMessage ) ); set_tooltip( *button, CONTROL::get_label_motions( CONTROL::LockMessage ) ); } break; case ITEM_QUIT: get_buttonbar().append( *get_button_close() ); set_tooltip( *get_button_close(), CONTROL::get_label_motions( CONTROL::CancelWrite ) ); break; case ITEM_SEPARATOR: pack_separator(); break; } ++num; } set_relief(); show_all_children(); } // undo ボタン void MessageToolBar::slot_undo_clicked() { MESSAGE::get_admin()->set_command( "undo_text" ); } // 下書き挿入ボタン void MessageToolBar::slot_insert_draft_clicked() { MESSAGE::get_admin()->set_command( "insert_draft" ); } /////////////////////// // プレビュー用のツールバー MessageToolBarPreview::MessageToolBarPreview() : MessageToolBarBase() { MessageToolBarPreview::pack_buttons(); } MessageToolBarPreview::~MessageToolBarPreview() noexcept = default; // ボタンのパッキング // virtual void MessageToolBarPreview::pack_buttons() { #ifdef _DEBUG std::cout << "MessageToolBarPreview::pack_buttons\n"; #endif int num = 0; for(;;){ int item = SESSION::get_item_msg_toolbar( num ); if( item == ITEM_END ) break; switch( item ){ case ITEM_PREVIEW: if( auto button = get_button_preview() ) { get_buttonbar().append( *button ); button->set_label( "プレビューを閉じる" ); set_tooltip( *button, "プレビューを閉じる" ); } break; case ITEM_OPENBOARD: get_buttonbar().append( *get_button_board() ); break; case ITEM_NAME: pack_transparent_separator(); get_buttonbar().append( *get_label() ); pack_transparent_separator(); break; case ITEM_WRITEMSG: get_buttonbar().append( *get_button_write() ); set_tooltip( *get_button_write(), CONTROL::get_label_motions( CONTROL::ExecWrite ) + "\n\nTabキーで書き込みボタンにフォーカスを移すことも可能" ); break; case ITEM_QUIT: get_buttonbar().append( *get_button_close() ); set_tooltip( *get_button_close(), CONTROL::get_label_motions( CONTROL::CancelWrite ) ); break; } ++num; } set_relief(); show_all_children(); } jdim-0.7.0/src/message/toolbar.h000066400000000000000000000033141417047150700164760ustar00rootroot00000000000000// ライセンス: GPL2 // ツールバーのクラス #ifndef _MESSAGE_TOOLBAR_H #define _MESSAGE_TOOLBAR_H #include #include "skeleton/toolbar.h" namespace MESSAGE { class MessageToolBarBase : public SKELETON::ToolBar { bool m_enable_slot; Gtk::ToggleToolButton* m_button_preview{}; public: MessageToolBarBase(); ~MessageToolBarBase() noexcept = default; // previewボタンのトグル void set_active_previewbutton( const bool active ); protected: Gtk::ToggleToolButton* get_button_preview(); private: void slot_toggle_preview(); }; /////////////////////////////////// // 通常 class MessageToolBar : public MessageToolBarBase { Gtk::ToolButton* m_button_insert_draft{}; Gtk::ToolButton* m_button_undo{}; // false ならスレ名ラベル、trueなら新規レス名entry表示 bool m_show_entry_new_subject{}; Gtk::ToolItem* m_tool_new_subject{}; Gtk::Entry* m_entry_new_subject{}; public: MessageToolBar(); ~MessageToolBar() noexcept; void show_entry_new_subject( bool show ); std::string get_new_subject() const; void clear_new_subject(); protected: void pack_buttons() override; private: void slot_undo_clicked(); void slot_insert_draft_clicked(); }; /////////////////////////////////// // プレビュー用 class MessageToolBarPreview : public MessageToolBarBase { public: MessageToolBarPreview(); ~MessageToolBarPreview() noexcept; protected: void pack_buttons() override; }; } #endif jdim-0.7.0/src/msgitempref.cpp000066400000000000000000000031021417047150700162600ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "msgitempref.h" #include "icons/iconmanager.h" #include "jdlib/miscutil.h" #include "global.h" #include "session.h" #include "command.h" using namespace CORE; MsgItemPref::MsgItemPref( Gtk::Window* parent, const std::string& url ) : SKELETON::SelectItemPref( parent, url ) { // デフォルトの項目を設定 append_default_pair( ITEM_NAME_PREVIEW, ICON::get_icon( ICON::PREVIEW ) ); append_default_pair( ITEM_NAME_WRITEMSG, ICON::get_icon( ICON::WRITE ) ); append_default_pair( ITEM_NAME_OPENBOARD, ICON::get_icon( ICON::TRANSPARENT ) ); append_default_pair( ITEM_NAME_NAME, ICON::get_icon( ICON::TRANSPARENT ) ); append_default_pair( ITEM_NAME_UNDO, ICON::get_icon( ICON::UNDO ) ); append_default_pair( ITEM_NAME_INSERTTEXT, ICON::get_icon( ICON::INSERTTEXT ) ); append_default_pair( ITEM_NAME_LOCK_MESSAGE, ICON::get_icon( ICON::LOCK) ); append_default_pair( ITEM_NAME_QUIT, ICON::get_icon( ICON::QUIT ) ); append_default_pair( ITEM_NAME_SEPARATOR, ICON::get_icon( ICON::TRANSPARENT ) ); // 文字列を元に行を追加 append_rows( SESSION::get_items_msg_toolbar_str() ); set_title( "ツールバー項目設定(書き込みビュー)" ); } // OKを押した void MsgItemPref::slot_ok_clicked() { SESSION::set_items_msg_toolbar_str( get_items() ); CORE::core_set_command( "update_message_toolbar_button" ); } // // デフォルトボタン // void MsgItemPref::slot_default() { append_rows( SESSION::get_items_msg_toolbar_default_str() ); } jdim-0.7.0/src/msgitempref.h000066400000000000000000000010521417047150700157270ustar00rootroot00000000000000// ライセンス: GPL2 // 書き込みビューのツールバーの表示項目設定 #ifndef _MSGITEMPREF_H #define _MSGITEMPREF_H #include "skeleton/selectitempref.h" namespace CORE { class MsgItemPref : public SKELETON::SelectItemPref { public: MsgItemPref( Gtk::Window* parent, const std::string& url ); ~MsgItemPref() noexcept = default; private: // OK押した void slot_ok_clicked() override; // デフォルトボタン void slot_default() override; }; } #endif jdim-0.7.0/src/openurldiag.cpp000066400000000000000000000012771417047150700162620ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "openurldiag.h" #include "command.h" using namespace CORE; OpenURLDialog::OpenURLDialog( const std::string& url ) : SKELETON::PrefDiag( nullptr, url, true, false, true ), m_label_url( true, "URL :" ) { m_label_url.set_text( url ); set_activate_entry( m_label_url ); get_content_area()->pack_start( m_label_url, Gtk::PACK_SHRINK ); set_title( "URLを開く" ); resize( 600, 1 ); set_default_response( Gtk::RESPONSE_OK ); show_all_children(); m_label_url.grab_focus(); } void OpenURLDialog::slot_ok_clicked() { CORE::core_set_command( "open_url", m_label_url.get_text() ); } jdim-0.7.0/src/openurldiag.h000066400000000000000000000007251417047150700157240ustar00rootroot00000000000000// ライセンス: GPL2 // // URL を開く // #ifndef _OPENURL_H #define _OPENURL_H #include "skeleton/prefdiag.h" #include "skeleton/label_entry.h" namespace CORE { class OpenURLDialog : public SKELETON::PrefDiag { SKELETON::LabelEntry m_label_url; public: explicit OpenURLDialog( const std::string& url ); ~OpenURLDialog() noexcept = default; protected: void slot_ok_clicked() override; }; } #endif jdim-0.7.0/src/passwdpref.h000066400000000000000000000077611417047150700156000ustar00rootroot00000000000000// ライセンス: GPL2 // パスワード設定ダイアログ #ifndef _PASSWDPREF_H #define _PASSWDPREF_H #include "skeleton/prefdiag.h" #include "skeleton/label_entry.h" #include "login2ch.h" #include "loginbe.h" #include "jdlib/miscutil.h" enum { BOXSPACING = 8 }; namespace CORE { // 2chログイン用 class PasswdFrame2ch : public Gtk::VBox { SKELETON::LabelEntry m_label_sid_2ch; public: SKELETON::LabelEntry entry_id; SKELETON::LabelEntry entry_passwd; PasswdFrame2ch() : m_label_sid_2ch( false, "SID: ", CORE::get_login2ch()->get_sessionid() ), entry_id( true, "ユーザID(_I): " ), entry_passwd( true, "パスワード(_P): " ) { const int mrg = 8; set_border_width( BOXSPACING ); entry_id.set_border_width( BOXSPACING ); pack_start( entry_id ); entry_passwd.set_border_width( BOXSPACING ); entry_passwd.set_visibility( false ); pack_start( entry_passwd, Gtk::PACK_SHRINK ); m_label_sid_2ch.set_border_width( BOXSPACING ); pack_start( m_label_sid_2ch ); set_border_width( mrg ); } }; // BEログイン用 class PasswdFrameBe : public Gtk::VBox { SKELETON::LabelEntry m_label_dmdm; SKELETON::LabelEntry m_label_mdmd; public: SKELETON::LabelEntry entry_id; SKELETON::LabelEntry entry_passwd; PasswdFrameBe() : m_label_dmdm( false, "DMDM: ", CORE::get_loginbe()->get_sessionid() ), m_label_mdmd( false, "MDMD: ", CORE::get_loginbe()->get_sessiondata() ), entry_id( true, "メールアドレス(_I): " ), entry_passwd( true, "パスワード(_P): " ) { set_border_width( BOXSPACING ); entry_id.set_border_width( BOXSPACING ); pack_start( entry_id ); entry_passwd.set_border_width( BOXSPACING ); entry_passwd.set_visibility( false ); pack_start( entry_passwd, Gtk::PACK_SHRINK ); pack_start( m_label_dmdm ); pack_start( m_label_mdmd ); set_border_width( BOXSPACING ); } }; class PasswdPref : public SKELETON::PrefDiag { Gtk::Notebook m_notebook; PasswdFrame2ch m_frame_2ch; PasswdFrameBe m_frame_be; // OK押した void slot_ok_clicked() override { // 2ch CORE::get_login2ch()->set_username( MISC::remove_space( m_frame_2ch.entry_id.get_text() ) ); CORE::get_login2ch()->set_passwd( MISC::remove_space( m_frame_2ch.entry_passwd.get_text() ) ); // BE CORE::get_loginbe()->set_username( MISC::remove_space( m_frame_be.entry_id.get_text() ) ); CORE::get_loginbe()->set_passwd( MISC::remove_space( m_frame_be.entry_passwd.get_text() ) ); } public: PasswdPref( Gtk::Window* parent, const std::string& url ) : SKELETON::PrefDiag( parent, url ) , m_frame_2ch(), m_frame_be() { // 2chログイン用 m_frame_2ch.entry_id.set_text( CORE::get_login2ch()->get_username() ); m_frame_2ch.entry_passwd.set_text( CORE::get_login2ch()->get_passwd() ); set_activate_entry( m_frame_2ch.entry_id ); set_activate_entry( m_frame_2ch.entry_passwd ); // beログイン用 m_frame_be.entry_id.set_text( CORE::get_loginbe()->get_username() ); m_frame_be.entry_passwd.set_text( CORE::get_loginbe()->get_passwd() ); set_activate_entry( m_frame_be.entry_id ); set_activate_entry( m_frame_be.entry_passwd ); m_notebook.append_page( m_frame_2ch, "2ch" ); m_notebook.append_page( m_frame_be, "BE" ); get_content_area()->pack_start( m_notebook ); set_title( "パスワード設定" ); show_all_children(); } ~PasswdPref() noexcept = default; }; } #endif jdim-0.7.0/src/prefdiagfactory.cpp000066400000000000000000000102161417047150700171130ustar00rootroot00000000000000// ライセンス: GPL2 #include "prefdiagfactory.h" #include "passwdpref.h" #include "privacypref.h" #include "browserpref.h" #include "linkfilterpref.h" #include "replacestrpref.h" #include "usrcmdpref.h" #include "proxypref.h" #include "globalabonepref.h" #include "globalabonethreadpref.h" #include "fontcolorpref.h" #include "livepref.h" #include "openurldiag.h" #include "mainitempref.h" #include "sidebaritempref.h" #include "boarditempref.h" #include "boarditemmenupref.h" #include "articleitempref.h" #include "articleitemmenupref.h" #include "searchitempref.h" #include "msgitempref.h" #include "dbimg/delimgdiag.h" #include "board/preference.h" #include "article/preference.h" #include "image/preference.h" #include "control/keypref.h" #include "control/mousepref.h" #include "control/buttonpref.h" #include "config/aboutconfig.h" std::unique_ptr CORE::PrefDiagFactory( Gtk::Window* parent, const int type, const std::string& url, const std::string& command ) { switch( type ) { case PREFDIAG_PASSWD: return std::make_unique( parent, url ); case PREFDIAG_PRIVACY: return std::make_unique( parent, url ); case PREFDIAG_BROWSER: return std::make_unique( parent, url ); case PREFDIAG_LINKFILTER: return std::make_unique( parent, url ); case PREFDIAG_REPLACESTR: return std::make_unique( parent, url ); case PREFDIAG_USRCMD: return std::make_unique( parent, url ); case PREFDIAG_PROXY: return std::make_unique( parent, url ); case PREFDIAG_GLOBALABONE: return std::make_unique( parent, url ); case PREFDIAG_GLOBALABONETHREAD: return std::make_unique( parent, url ); case PREFDIAG_FONTCOLOR: return std::make_unique( parent, url ); case PREFDIAG_LIVE: return std::make_unique( parent, url ); case PREFDIAG_MAINITEM: return std::make_unique( parent, url ); case PREFDIAG_SIDEBARITEM: return std::make_unique( parent, url ); case PREFDIAG_BOARDITEM_COLUM: return std::make_unique( parent, url ); case PREFDIAG_BOARDITEM: return std::make_unique( parent, url ); case PREFDIAG_BOARDITEM_MENU: return std::make_unique( parent, url ); case PREFDIAG_ARTICLEITEM: return std::make_unique( parent, url ); case PREFDIAG_ARTICLEITEM_MENU: return std::make_unique( parent, url ); case PREFDIAG_SEARCHITEM: return std::make_unique( parent, url ); case PREFDIAG_MSGITEM: return std::make_unique( parent, url ); case PREFDIAG_DELIMG: return std::make_unique( parent, url ); case PREFDIAG_BOARD: return std::make_unique( parent, url, command ); case PREFDIAG_ARTICLE: return std::make_unique( parent, url, command ); case PREFDIAG_IMAGE: return std::make_unique( parent, url ); case PREFDIAG_KEY: return std::make_unique( parent, url ); case PREFDIAG_MOUSE: return std::make_unique( parent, url ); case PREFDIAG_BUTTON: return std::make_unique( parent, url ); case PREFDIAG_ABOUTCONFIG: return std::make_unique( parent ); case PREFDIAG_OPENURL: return std::make_unique( url ); default: return nullptr; } } jdim-0.7.0/src/prefdiagfactory.h000066400000000000000000000024351417047150700165640ustar00rootroot00000000000000// ライセンス: GPL2 // // SKELETON::PrefDiagのファクトリー // #ifndef _PREFDIAGFACTORY_H #define _PREFDIAGFACTORY_H #include "skeleton/prefdiag.h" #include #include #include namespace CORE { enum { PREFDIAG_PASSWD, PREFDIAG_PRIVACY, PREFDIAG_BROWSER, PREFDIAG_LINKFILTER, PREFDIAG_REPLACESTR, PREFDIAG_USRCMD, PREFDIAG_PROXY, PREFDIAG_GLOBALABONETHREAD, PREFDIAG_GLOBALABONE, PREFDIAG_FONTCOLOR, PREFDIAG_LIVE, PREFDIAG_MAINITEM, PREFDIAG_SIDEBARITEM, PREFDIAG_BOARDITEM, PREFDIAG_BOARDITEM_MENU, PREFDIAG_BOARDITEM_COLUM, PREFDIAG_ARTICLEITEM, PREFDIAG_ARTICLEITEM_MENU, PREFDIAG_SEARCHITEM, PREFDIAG_MSGITEM, PREFDIAG_DELIMG, PREFDIAG_BOARD, PREFDIAG_ARTICLE, PREFDIAG_IMAGE, PREFDIAG_KEY, PREFDIAG_MOUSE, PREFDIAG_BUTTON, PREFDIAG_ABOUTCONFIG, PREFDIAG_OPENURL, PREFDIAG_NONE }; std::unique_ptr PrefDiagFactory( Gtk::Window* parent, const int type, const std::string& url, const std::string& command = {} ); } #endif jdim-0.7.0/src/privacypref.h000066400000000000000000000055651417047150700157540ustar00rootroot00000000000000// ライセンス: GPL2 // プライバシー設定ダイアログ #ifndef _PRIVACYPREF_H #define _PRIVACYPREF_H #include "skeleton/prefdiag.h" #include "skeleton/msgdiag.h" #include "command.h" namespace CORE { class PrivacyPref : public SKELETON::PrefDiag { Gtk::VBox m_vbox; Gtk::CheckButton m_bt_board; Gtk::CheckButton m_bt_thread; Gtk::CheckButton m_bt_close; Gtk::CheckButton m_bt_search; Gtk::CheckButton m_bt_name; Gtk::CheckButton m_bt_mail; Gtk::HBox m_hbox_selectall; Gtk::Button m_bt_selectall; void slot_selectall() { m_bt_board.set_active( true ); m_bt_thread.set_active( true ); m_bt_close.set_active( true ); m_bt_search.set_active( true ); m_bt_name.set_active( true ); m_bt_mail.set_active( true ); } // OK押した void slot_ok_clicked() override { if( m_bt_board.get_active() ) CORE::core_set_command( "clear_board" ); if( m_bt_thread.get_active() ) CORE::core_set_command( "clear_thread" ); if( m_bt_close.get_active() ) CORE::core_set_command( "clear_closed_thread" ); if( m_bt_search.get_active() ) CORE::core_set_command( "clear_search" ); if( m_bt_name.get_active() ) CORE::core_set_command( "clear_name" ); if( m_bt_mail.get_active() ) CORE::core_set_command( "clear_mail" ); } public: PrivacyPref( Gtk::Window* parent, const std::string& url ) : SKELETON::PrefDiag( parent, url ), m_bt_board( "板履歴(_B)", true ), m_bt_thread( "スレ履歴(_T)", true ), m_bt_close( "最近閉じたスレの履歴(_R)", true ), m_bt_search( "検索履歴(_F)", true ), m_bt_name( "書き込みビューの名前履歴(_N)", true ), m_bt_mail( "書き込みビューのメール履歴(_E)", true ), m_bt_selectall( "全て選択(_A)", true ) { m_vbox.set_spacing( 8 ); m_vbox.set_border_width( 8 ); m_vbox.pack_start( m_bt_thread ); m_vbox.pack_start( m_bt_board ); m_vbox.pack_start( m_bt_close ); m_vbox.pack_start( m_bt_search ); m_vbox.pack_start( m_bt_name ); m_vbox.pack_start( m_bt_mail ); m_bt_selectall.signal_clicked().connect( sigc::mem_fun( *this, &PrivacyPref::slot_selectall ) ); m_hbox_selectall.pack_start( m_bt_selectall, Gtk::PACK_SHRINK ); m_vbox.pack_start( m_hbox_selectall, Gtk::PACK_SHRINK ); get_content_area()->set_spacing( 8 ); get_content_area()->pack_start( m_vbox, Gtk::PACK_SHRINK ); set_title( "プライバシー情報の消去" ); show_all_children(); } ~PrivacyPref() noexcept = default; }; } #endif jdim-0.7.0/src/proxypref.h000066400000000000000000000150271417047150700154520ustar00rootroot00000000000000// ライセンス: GPL2 // プロキシ設定ダイアログ #ifndef _PROXYPREF_H #define _PROXYPREF_H #include "skeleton/prefdiag.h" #include "skeleton/label_entry.h" #include "config/globalconf.h" #include "jdlib/miscutil.h" namespace CORE { constexpr const char kSendCookieTooltip[] = "JDimからプロキシへサイトのクッキーを送信します。\n" "プロキシのクッキー処理にあわせて設定してください。"; class ProxyFrame : public Gtk::Frame { Gtk::VBox m_vbox; Gtk::HBox m_hbox; public: Gtk::CheckButton ckbt; Gtk::CheckButton send_cookie_check; SKELETON::LabelEntry entry_host; SKELETON::LabelEntry entry_port; ProxyFrame( const std::string& title, const Glib::ustring& ckbt_label, const Glib::ustring& send_label, const Glib::ustring& host_label, const Glib::ustring& port_label ) : ckbt( ckbt_label, true ) , send_cookie_check( send_label, true ) , entry_host( true, host_label) , entry_port( true, port_label ) { send_cookie_check.set_tooltip_text( kSendCookieTooltip ); m_hbox.set_spacing( 8 ); m_hbox.pack_start( ckbt, Gtk::PACK_SHRINK ); m_hbox.pack_start( send_cookie_check, Gtk::PACK_SHRINK ); m_hbox.pack_start( entry_host ); m_hbox.pack_start( entry_port, Gtk::PACK_SHRINK ); m_hbox.set_border_width( 8 ); m_vbox.set_spacing( 8 ); m_vbox.pack_start( m_hbox, Gtk::PACK_SHRINK ); set_label( title ); set_border_width( 8 ); add( m_vbox ); } }; class ProxyPref : public SKELETON::PrefDiag { Gtk::Label m_label; // 2ch読み込み用 ProxyFrame m_frame_2ch; // 2ch書き込み用 ProxyFrame m_frame_2ch_w; // 一般用 ProxyFrame m_frame_data; // OK押した void slot_ok_clicked() override { // 2ch CONFIG::set_use_proxy_for2ch( m_frame_2ch.ckbt.get_active() ); CONFIG::set_send_cookie_to_proxy_for2ch( m_frame_2ch.send_cookie_check.get_active() ); CONFIG::set_proxy_for2ch( MISC::remove_space( m_frame_2ch.entry_host.get_text() ) ); CONFIG::set_proxy_port_for2ch( atoi( m_frame_2ch.entry_port.get_text().c_str() ) ); // 2ch書き込み用 CONFIG::set_use_proxy_for2ch_w( m_frame_2ch_w.ckbt.get_active() ); CONFIG::set_send_cookie_to_proxy_for2ch_w( m_frame_2ch_w.send_cookie_check.get_active() ); CONFIG::set_proxy_for2ch_w( MISC::remove_space( m_frame_2ch_w.entry_host.get_text() ) ); CONFIG::set_proxy_port_for2ch_w( atoi( m_frame_2ch_w.entry_port.get_text().c_str() ) ); // 一般 CONFIG::set_use_proxy_for_data( m_frame_data.ckbt.get_active() ); CONFIG::set_send_cookie_to_proxy_for_data( m_frame_data.send_cookie_check.get_active() ); CONFIG::set_proxy_for_data( MISC::remove_space( m_frame_data.entry_host.get_text() ) ); CONFIG::set_proxy_port_for_data( atoi( m_frame_data.entry_port.get_text().c_str() ) ); } public: ProxyPref( Gtk::Window* parent, const std::string& url ) : SKELETON::PrefDiag( parent, url ) , m_label( "認証を行う場合はホスト名を「ユーザID:パスワード@ホスト名」としてください。" ) , m_frame_2ch( "2ch読み込み用", "使用する(_U)", "クッキーを送る(_I)", "ホスト名(_H): ", "ポート番号(_P): " ) , m_frame_2ch_w( "2ch書き込み用", "使用する(_S)", "クッキーを送る(_J)", "ホスト名(_N): ", "ポート番号(_R): " ) , m_frame_data( "その他のサーバ用(板一覧、外部板、画像など)", "使用する(_E)", "クッキーを送る(_K)", "ホスト名(_A): ", "ポート番号(_T): " ) { std::string host; // 2ch用 m_frame_2ch.ckbt.set_active( CONFIG::get_use_proxy_for2ch() ); m_frame_2ch.send_cookie_check.set_active( CONFIG::get_send_cookie_to_proxy_for2ch() ); if( CONFIG::get_proxy_basicauth_for2ch().empty() ) host = CONFIG::get_proxy_for2ch(); else host = CONFIG::get_proxy_basicauth_for2ch() + "@" + CONFIG::get_proxy_for2ch(); m_frame_2ch.entry_host.set_text( host ); m_frame_2ch.entry_port.set_text( std::to_string( CONFIG::get_proxy_port_for2ch() ) ); set_activate_entry( m_frame_2ch.entry_host ); set_activate_entry( m_frame_2ch.entry_port ); // 2ch書き込み用 m_frame_2ch_w.ckbt.set_active( CONFIG::get_use_proxy_for2ch_w() ); m_frame_2ch_w.send_cookie_check.set_active( CONFIG::get_send_cookie_to_proxy_for2ch_w() ); if( CONFIG::get_proxy_basicauth_for2ch_w().empty() ) host = CONFIG::get_proxy_for2ch_w(); else host = CONFIG::get_proxy_basicauth_for2ch_w() + "@" + CONFIG::get_proxy_for2ch_w(); m_frame_2ch_w.entry_host.set_text( host ); m_frame_2ch_w.entry_port.set_text( std::to_string( CONFIG::get_proxy_port_for2ch_w() ) ); set_activate_entry( m_frame_2ch_w.entry_host ); set_activate_entry( m_frame_2ch_w.entry_port ); // 一般用 m_frame_data.ckbt.set_active( CONFIG::get_use_proxy_for_data() ); m_frame_data.send_cookie_check.set_active( CONFIG::get_send_cookie_to_proxy_for_data() ); if( CONFIG::get_proxy_basicauth_for_data().empty() ) host = CONFIG::get_proxy_for_data(); else host = CONFIG::get_proxy_basicauth_for_data() + "@" + CONFIG::get_proxy_for_data(); m_frame_data.entry_host.set_text( host ); m_frame_data.entry_port.set_text( std::to_string( CONFIG::get_proxy_port_for_data() ) ); set_activate_entry( m_frame_data.entry_host ); set_activate_entry( m_frame_data.entry_port ); get_content_area()->set_spacing( 4 ); get_content_area()->pack_start( m_label ); get_content_area()->pack_start( m_frame_2ch ); get_content_area()->pack_start( m_frame_2ch_w ); get_content_area()->pack_start( m_frame_data ); set_title( "プロキシ設定" ); show_all_children(); } ~ProxyPref() noexcept = default; }; } #endif jdim-0.7.0/src/replacestrmanager.cpp000066400000000000000000000226741417047150700174540ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "replacestrmanager.h" #include "cache.h" #include "type.h" #include "jdlib/miscmsg.h" #include "jdlib/miscutil.h" #include "xml/document.h" #include "xml/tools.h" #include // std::any_of(), std::find_if() #include #include #include // std::distance() constexpr const char* kRootNodeNameReplaceStr = "replacestrlist"; static CORE::ReplaceStr_Manager* instance_replacestr_manager = nullptr; CORE::ReplaceStr_Manager* CORE::get_replacestr_manager() { if( ! instance_replacestr_manager ) instance_replacestr_manager = new ReplaceStr_Manager(); assert( instance_replacestr_manager ); return instance_replacestr_manager; } void CORE::delete_replacestr_manager() { if( instance_replacestr_manager ) delete instance_replacestr_manager; instance_replacestr_manager = nullptr; } /////////////////////////////////////////////// using namespace CORE; unsigned long ReplaceStrCondition::to_ulong() const { // エンディアンの問題を回避するため std::bitset を使って変換する // データ互換性のため削除したフラグのビット位置を再利用しないこと std::bitset<31> out; out[0] = active; out[1] = icase; out[2] = regex; out[3] = wchar; out[4] = norm; return out.to_ulong(); } ReplaceStrCondition ReplaceStrCondition::from_ulong( unsigned long condition ) noexcept { const std::bitset<31> input( condition ); ReplaceStrCondition out{}; out.active = input[0]; out.icase = input[1]; out.regex = input[2]; out.wchar = input[3]; out.norm = input[4]; return out; } ReplaceStr_Manager::ReplaceStr_Manager() { std::string xml; if( CACHE::load_rawdata( CACHE::path_replacestr(), xml ) ) xml2list( xml ); else { // デフォルトの設定を行う ReplaceStrCondition condition{}; list_append( REPLACETARGET_MESSAGE, condition, "ダブルクリックすると編集出来ます", "(この項目は削除して構いません)" ); condition.regex = true; list_append( REPLACETARGET_MESSAGE, condition, "(?= REPLACETARGET_MAX ) return; JDLIB::RegexPattern creg; if( condition.regex ) { constexpr bool newline = true; constexpr bool migemo = false; if( ! creg.set( pattern, condition.icase, newline, migemo ) ) { std::string msg = "invlid replacestr pattern: "; msg.append( creg.errstr() + ": " + pattern ); MISC::ERRMSG( msg ); } } #ifdef _DEBUG std::cout << "ReplaceStr_Manager::list_append id=" << id << " conditnon=" << condition.to_ulong() << " pattern=" << pattern << " replace=" << replace << std::endl; #endif m_list[id].push_back( ReplaceStrItem{ condition, pattern, replace, std::move( creg ) } ); } // // xml -> リスト // void ReplaceStr_Manager::xml2list( const std::string& xml ) { for( auto& item : m_list ) { item.clear(); } if( xml.empty() ) return; const XML::Document document( xml ); const XML::Dom* const root = document.get_root_element( kRootNodeNameReplaceStr ); if( ! root ) return; for( const XML::Dom* dom : *root ) { if( dom->nodeType() != XML::NODE_TYPE_ELEMENT ) continue; const int type = XML::get_type( dom->nodeName() ); if( type != TYPE_DIR ) continue; const std::size_t id = target_id( dom->getAttribute( "name" ) ); if( id >= REPLACETARGET_MAX ) continue; m_chref[id] = dom->getAttribute( "chref" ) == "true"; for( const XML::Dom* query : *dom ) { if( query->nodeType() != XML::NODE_TYPE_ELEMENT ) continue; const int element_type = XML::get_type( query->nodeName() ); if( element_type != TYPE_REPLACESTR ) continue; const std::string cond_str = query->getAttribute( "condition" ); // 解析に失敗した時は初期値(0)にする const unsigned long cond_num = std::strtoul( cond_str.c_str(), nullptr, 10 ); list_append( id, ReplaceStrCondition::from_ulong( cond_num ), query->getAttribute( "pattern" ), query->getAttribute( "replace" ) ); } } } // // XML 保存 // void ReplaceStr_Manager::save_xml() { XML::Document document; XML::Dom* root = document.appendChild( XML::NODE_TYPE_ELEMENT, kRootNodeNameReplaceStr ); if( ! root ) return; for( int i = 0; i < REPLACETARGET_MAX; ++i ) { XML::Dom* node = dom_append( root, i, m_chref[i] ); for( const auto& item : m_list[i] ) { dom_append( node, item.condition, item.pattern, item.replace ); } } #ifdef _DEBUG std::cout << "ReplaceStr_Manager::save_xml" << std::endl; std::cout << document.get_xml() << std::endl; #endif CACHE::save_rawdata( CACHE::path_replacestr(), document.get_xml() ); } // // 置換対象の要素名からid取得 // int ReplaceStr_Manager::target_id( const std::string& name ) { auto it = std::find_if( kReplStrTargetNames.begin(), kReplStrTargetNames.end(), [&name] ( const char* id ) { return name == id; } ); return std::distance( kReplStrTargetNames.begin(), it ); } // // 置換対象idから要素名取得 // std::string ReplaceStr_Manager::target_name( const int id ) { if( id >= REPLACETARGET_MAX ) return {}; return kReplStrTargetNames[id]; } // // 実行 // std::string ReplaceStr_Manager::replace( const char* str, const int lng, const int id ) const { if( id >= REPLACETARGET_MAX || ( m_list[id].empty() && ! m_chref[id] ) ) return std::string( str, lng ); std::string buffer; if( m_chref[id] ) buffer = MISC::chref_decode( str, lng, false ); else buffer.assign( str, lng ); #ifdef _DEBUG std::cout << "ReplaceStr_Manager::replace str=" << buffer << std::endl; #endif // bufferに対してリストに登録された変換処理を実行していく for( const auto& item : m_list[id] ) { const ReplaceStrCondition condition = item.condition; if( ! condition.active || item.pattern.empty() ) continue; if( condition.regex ) { const int lng_buf = buffer.size(); // 文字列が空の時は"^$"などにマッチするように改行を追加 if( lng_buf == 0 ) buffer.push_back( '\n' ); JDLIB::Regex regex; bool match = false; int offset = 0; std::string replaced; while( regex.match( item.creg, buffer, offset, match ) ) { match = true; const int p0 = regex.pos( 0 ); if( p0 != offset ) replaced.append( buffer.substr( offset, p0 - offset ) ); // \0 ... \9 の文字列を置換 replaced.append( regex.replace( item.replace ) ); offset = p0 + regex.length( 0 ); if( offset >= lng_buf ) break; // 0文字にマッチするパターンは繰り返さない if( regex.length( 0 ) == 0 ) break; } if( match ) { if( lng_buf > offset ) { replaced.append( buffer.substr( offset, lng_buf - offset ) ); } buffer = std::move( replaced ); } } // 正規表現を使わない置換処理 else buffer = MISC::replace_str( buffer, item.pattern, item.replace ); } #ifdef _DEBUG std::cout << "ReplaceStr_Manager::replace replaced str=" << buffer << std::endl; #endif return buffer; } // // 置換対象のXMLノード作成 // XML::Dom* ReplaceStr_Manager::dom_append( XML::Dom* node, const int id, const bool chref ) { XML::Dom* const dir = node->appendChild( XML::NODE_TYPE_ELEMENT, XML::get_name( TYPE_DIR ) ); dir->setAttribute( "name", target_name( id ) ); dir->setAttribute( "chref", chref ? "true" : "false" ); return dir; } // // 置換条件のXMLノード作成 // XML::Dom* ReplaceStr_Manager::dom_append( XML::Dom* node, ReplaceStrCondition condition, const std::string& pattern, const std::string& replace ) { XML::Dom* const query = node->appendChild( XML::NODE_TYPE_ELEMENT, XML::get_name( TYPE_REPLACESTR ) ); query->setAttribute( "condition", std::to_string( condition.to_ulong() ) ); query->setAttribute( "pattern", pattern ); query->setAttribute( "replace", replace ); return query; } jdim-0.7.0/src/replacestrmanager.h000066400000000000000000000057211417047150700171130ustar00rootroot00000000000000// ライセンス: GPL2 // // 文字列置換の管理クラス // #ifndef REPLACESTRMANAGER_H #define REPLACESTRMANAGER_H #include "jdlib/jdregex.h" #include #include #include namespace XML { class Dom; } namespace CORE { // NOTE: スレタイトル(subject)は未実装の項目(予約済) constexpr int kReplStrTargetReserved_0 = 1; enum { REPLACETARGET_SUBJECT = 0, REPLACETARGET_NAME, REPLACETARGET_MAIL, REPLACETARGET_DATE, REPLACETARGET_MESSAGE, REPLACETARGET_MAX }; constexpr std::array kReplStrTargetNames = { "subject", "name", "mail", "date", "msg" }; constexpr std::array kReplStrTargetLabels = { "スレタイトル", "名前", "メール", "日付/ID", "本文" }; struct ReplaceStrCondition { // データ互換性のためフラグの並び(ビット位置)は変更しない unsigned char active : 1; // 0 unsigned char icase : 1; // 1 unsigned char regex : 1; // 2 unsigned char wchar : 1; // 3 reserved unsigned char norm : 1; // 4 reserved static ReplaceStrCondition from_ulong( unsigned long condition ) noexcept; unsigned long to_ulong() const; }; struct ReplaceStrItem { ReplaceStrCondition condition; std::string pattern; std::string replace; JDLIB::RegexPattern creg; }; class ReplaceStr_Manager { std::list m_list[ REPLACETARGET_MAX ]{}; bool m_chref[ REPLACETARGET_MAX ]{}; public: ReplaceStr_Manager(); ~ReplaceStr_Manager() noexcept = default; const std::list& get_list( int id ) const noexcept { return m_list[id]; } bool list_get_active( const int id ) const; void list_clear( const int id ); void list_append( const int id, ReplaceStrCondition condition, const std::string& pattern, const std::string& replace ); bool get_chref( int id ) const noexcept { return m_chref[id]; } void set_chref( int id, bool chref ) { m_chref[id] = chref; } void save_xml(); // 文字列を置換 std::string replace( const char* str, const int lng, const int id ) const; static XML::Dom* dom_append( XML::Dom* node, const int id, const bool chref ); static XML::Dom* dom_append( XML::Dom* node, ReplaceStrCondition condition, const std::string& pattern, const std::string& replace ); private: void xml2list( const std::string& xml ); static int target_id( const std::string& name ); static std::string target_name( const int id ); }; /////////////////////////////////////// // インターフェース ReplaceStr_Manager* get_replacestr_manager(); void delete_replacestr_manager(); } #endif jdim-0.7.0/src/replacestrpref.cpp000066400000000000000000000423731417047150700167740ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "replacestrpref.h" #include "replacestrmanager.h" #include "control/controlid.h" #include "jdlib/miscgtk.h" #include "skeleton/msgdiag.h" #include "xml/document.h" #include "command.h" #include "environment.h" #include #include using namespace CORE; ReplaceStrDiag::ReplaceStrDiag( Gtk::Window* parent, ReplaceStrCondition condition, const Glib::ustring& pattern, const Glib::ustring& replace ) : SKELETON::PrefDiag( parent, "" ) , m_button_copy( "この設定をクリップボードにコピー(_L)", true ) , m_check_active( "有効(_E)", true ) , m_check_icase( "大文字小文字(_I)", true ) , m_check_regex( "正規表現(_R)", true ) , m_label_pattern( "置換パターン(_P):", true ) , m_label_replace( "置換文字列(_S):", true ) { resize( 600, 1 ); m_button_copy.signal_clicked().connect( [this] { slot_copy(); } ); m_check_regex.signal_clicked().connect( [this] { slot_sens(); } ); m_check_active.set_active( condition.active ); m_check_icase.set_active( condition.icase ); m_check_icase.set_sensitive( condition.regex ); m_check_regex.set_active( condition.regex ); m_check_active.set_tooltip_text( "この条件の置換を有効にする" ); m_check_icase.set_tooltip_text( "大文字小文字を区別しない" ); m_check_regex.set_tooltip_text( "正規表現を使用する" ); m_hbox_regex.pack_start( m_check_regex, Gtk::PACK_SHRINK ); m_hbox_regex.pack_start( m_check_icase, Gtk::PACK_SHRINK ); m_entry_pattern.set_text( pattern ); m_entry_replace.set_text( replace ); m_entry_pattern.set_hexpand( true ); m_entry_replace.set_hexpand( true ); m_label_pattern.set_mnemonic_widget( m_entry_pattern ); m_label_replace.set_mnemonic_widget( m_entry_replace ); m_label_pattern.set_xalign( 0 ); m_label_replace.set_xalign( 0 ); m_grid_entry.attach( m_label_pattern, 0, 0, 1, 1 ); m_grid_entry.attach( m_entry_pattern, 1, 0, 1, 1 ); m_grid_entry.attach( m_label_replace, 0, 1, 1, 1 ); m_grid_entry.attach( m_entry_replace, 1, 1, 1, 1 ); m_grid_entry.set_row_spacing( 8 ); m_hbox_active.pack_start( m_check_active ); m_hbox_active.pack_start( m_button_copy, Gtk::PACK_SHRINK ); get_content_area()->set_spacing( 8 ); get_content_area()->pack_start( m_hbox_active, Gtk::PACK_SHRINK ); get_content_area()->pack_start( m_hbox_regex, Gtk::PACK_SHRINK ); get_content_area()->pack_start( m_grid_entry, Gtk::PACK_SHRINK ); set_title( "置換条件設定" ); show_all_children(); } // // 条件フラグ取得 // ReplaceStrCondition ReplaceStrDiag::get_condition() const { ReplaceStrCondition condition{}; condition.active = get_active(); condition.icase = get_icase(); condition.regex = get_regex(); return condition; } // // クリップボードにコピー // void ReplaceStrDiag::slot_copy() { XML::Document document; const XML::Dom* node = ReplaceStr_Manager::dom_append( &document, get_condition(), get_pattern(), get_replace() ); MISC::CopyClipboard( node->get_xml() ); } // // チェックボックスのsensitive切り替え // void ReplaceStrDiag::slot_sens() { m_check_icase.set_sensitive( get_regex() ); } /////////////////////////////////////////////// ReplaceStrPref::ReplaceStrPref( Gtk::Window* parent, const std::string& url ) : SKELETON::PrefDiag( parent, url ) , m_id_target( REPLACETARGET_MAX - 1 ) // 初期設定は本文 , m_label_target( "置換対象(_R):", true ) , m_link_manual( ENVIRONMENT::get_jdhelpreplstr(), "オンラインマニュアル(_M)" ) , m_check_chref( "対象の置換前に文字参照をデコード(_E)", true ) , m_chref( REPLACETARGET_MAX ) , m_store( REPLACETARGET_MAX ) , m_button_top( g_dpgettext( GTK_DOMAIN, "Stock label, navigation\x04_Top", 24 ), true ) , m_button_up( g_dpgettext( GTK_DOMAIN, "Stock label, navigation\x04_Up", 24 ), true ) , m_button_down( g_dpgettext( GTK_DOMAIN, "Stock label, navigation\x04_Down", 24 ), true ) , m_button_bottom( g_dpgettext( GTK_DOMAIN, "Stock label, navigation\x04_Bottom", 24 ), true ) , m_button_delete( g_dpgettext( GTK_DOMAIN, "Stock label\x04_Delete", 12 ), true ) , m_button_add( g_dpgettext( GTK_DOMAIN, "Stock label\x04_Add", 12 ), true ) , m_vbuttonbox{ Gtk::ORIENTATION_VERTICAL } { m_button_top.set_image_from_icon_name( "go-top" ); m_button_up.set_image_from_icon_name( "go-up" ); m_button_down.set_image_from_icon_name( "go-down" ); m_button_bottom.set_image_from_icon_name( "go-bottom" ); m_button_top.signal_clicked().connect( [this] { slot_top(); } ); m_button_up.signal_clicked().connect( [this] { slot_up(); } ); m_button_down.signal_clicked().connect( [this] { slot_down(); } ); m_button_bottom.signal_clicked().connect( [this] { slot_bottom(); } ); m_button_delete.signal_clicked().connect( [this] { slot_delete(); } ); m_button_add.signal_clicked().connect( [this] { slot_add(); } ); std::generate( m_store.begin(), m_store.end(), [this] { return Gtk::ListStore::create( m_columns ); } ); m_current_store = m_store.back(); m_treeview.set_model( m_current_store ); m_treeview.set_size_request( 640, 400 ); m_treeview.signal_row_activated().connect( sigc::mem_fun( *this, &ReplaceStrPref::slot_row_activated ) ); m_treeview.sig_key_press().connect( sigc::mem_fun( *this, &ReplaceStrPref::slot_key_press ) ); m_treeview.sig_key_release().connect( sigc::mem_fun( *this, &ReplaceStrPref::slot_key_release ) ); std::vector columns( m_columns.size() ); columns[0] = Gtk::manage( new Gtk::TreeViewColumn( "有効", m_columns.m_col_active ) ); columns[0]->set_fixed_width( 35 ); columns[0]->set_alignment( Gtk::ALIGN_CENTER ); // 正規と大小は設定の関係性から元になったパッチから変更して列を入れ替えた columns[1] = Gtk::manage( new Gtk::TreeViewColumn( "正規", m_columns.m_col_regex ) ); columns[1]->set_fixed_width( 35 ); columns[1]->set_alignment( Gtk::ALIGN_CENTER ); columns[2] = Gtk::manage( new Gtk::TreeViewColumn( "大小", m_columns.m_col_icase ) ); columns[2]->set_fixed_width( 35 ); columns[2]->set_alignment( Gtk::ALIGN_CENTER ); columns[3] = Gtk::manage( new Gtk::TreeViewColumn( "置換パターン", m_columns.m_col_pattern ) ); columns[3]->set_fixed_width( 220 ); columns[4] = Gtk::manage( new Gtk::TreeViewColumn( "置換文字列", m_columns.m_col_replace ) ); columns[4]->set_fixed_width( 200 ); for( Gtk::TreeViewColumn* col : columns ) { col->set_sizing( Gtk::TREE_VIEW_COLUMN_FIXED ); col->set_resizable( true ); m_treeview.append_column( *col ); } m_scrollwin.add( m_treeview ); m_scrollwin.set_policy( Gtk::POLICY_AUTOMATIC, Gtk::POLICY_ALWAYS ); m_scrollwin.set_size_request( 640, 400 ); m_vbuttonbox.pack_start( m_button_top, Gtk::PACK_SHRINK ); m_vbuttonbox.pack_start( m_button_up, Gtk::PACK_SHRINK ); m_vbuttonbox.pack_start( m_button_down, Gtk::PACK_SHRINK ); m_vbuttonbox.pack_start( m_button_bottom, Gtk::PACK_SHRINK ); m_vbuttonbox.pack_start( m_button_delete, Gtk::PACK_SHRINK ); m_vbuttonbox.pack_start( m_button_add, Gtk::PACK_SHRINK ); m_vbuttonbox.set_spacing( 4 ); m_hbox.pack_start( m_scrollwin, Gtk::PACK_EXPAND_WIDGET ); m_hbox.pack_start( m_vbuttonbox, Gtk::PACK_SHRINK ); // スレタイトルは未実装のため省略する std::for_each( kReplStrTargetLabels.begin() + kReplStrTargetReserved_0, kReplStrTargetLabels.end(), [this]( const char* target ) { m_menu_target.append( target ); } ); m_menu_target.signal_changed().connect( [this] { slot_target_changed(); } ); m_menu_target.set_active_text( kReplStrTargetLabels.back() ); m_label_target.set_mnemonic_widget( m_menu_target ); m_grid_head.attach( m_label_target, 0, 0, 1, 1 ); m_grid_head.attach( m_menu_target, 1, 0, 1, 1 ); m_grid_head.attach( m_link_manual, 2, 0, 1, 1 ); m_grid_head.attach( m_check_chref, 0, 1, 2, 1 ); m_grid_head.set_hexpand( true ); m_link_manual.set_halign( Gtk::ALIGN_END ); m_link_manual.set_hexpand( true ); m_link_manual.set_use_underline( true ); m_check_chref.set_tooltip_markup( "ダブルクォーテション\", アンパサント" "&, 少なり記号<, 大なり記号>除く" "文字参照をデコードしてから置換を行います。" ); get_content_area()->set_spacing( 8 ); get_content_area()->pack_start( m_grid_head, Gtk::PACK_SHRINK ); get_content_area()->pack_start( m_hbox ); show_all_children(); m_treeview.grab_focus(); set_title( "文字列置換設定" ); append_rows(); } void ReplaceStrPref::append_rows() { const ReplaceStr_Manager* const mgr = CORE::get_replacestr_manager(); for( int i = 0; i < REPLACETARGET_MAX; ++i ) { m_chref[i] = mgr->get_chref( i ); for( const auto& item : mgr->get_list( i ) ) { append_row( m_store[i], item.condition, item.pattern, item.replace ); } } // 省略したスレタイトルの分インデックスをずらしてデリファレンスする const int id = m_menu_target.get_active_row_number() + kReplStrTargetReserved_0; m_check_chref.set_active( m_chref[id] ); select_row( get_top_row() ); } void ReplaceStrPref::append_row( const Glib::RefPtr& store, ReplaceStrCondition condition, const std::string& pattern, const std::string& replace ) { Gtk::TreeModel::Row row = *store->append(); if( row ) { row[ m_columns.m_col_active ] = condition.active; row[ m_columns.m_col_icase ] = condition.icase; row[ m_columns.m_col_regex ] = condition.regex; row[ m_columns.m_col_pattern ] = pattern; row[ m_columns.m_col_replace ] = replace; select_row( row ); } } Gtk::TreeModel::const_iterator ReplaceStrPref::get_selected_row() const { std::vector paths = m_treeview.get_selection()->get_selected_rows(); if( paths.empty() ) return Gtk::TreeModel::const_iterator(); return *( m_current_store->get_iter( paths.front() ) ); } Gtk::TreeModel::const_iterator ReplaceStrPref::get_top_row() const { Gtk::TreeModel::Children children = m_current_store->children(); if( children.empty() ) return Gtk::TreeModel::const_iterator(); return children.begin(); } Gtk::TreeModel::const_iterator ReplaceStrPref::get_bottom_row() const { Gtk::TreeModel::Children children = m_current_store->children(); if( children.empty() ) return Gtk::TreeModel::const_iterator(); return std::prev( children.end() ); } void ReplaceStrPref::select_row( const Gtk::TreeModel::const_iterator& it ) { if( ! it ) return; const Gtk::TreePath path( it ); m_treeview.get_selection()->select( path ); } // // OK ボタンを押した // void ReplaceStrPref::slot_ok_clicked() { ReplaceStr_Manager* const mgr = CORE::get_replacestr_manager(); m_chref[ m_id_target ] = m_check_chref.get_active(); for( int i = 0; i < REPLACETARGET_MAX; ++i ) { mgr->list_clear( i ); mgr->set_chref( i, m_chref[i] ); for( const Gtk::TreeModel::Row& row : m_store[i]->children() ) { const bool active = row[ m_columns.m_col_active ]; const bool icase = row[ m_columns.m_col_icase ]; const bool regex = row[ m_columns.m_col_regex ]; constexpr bool reserved = false; // wchar, norm const ReplaceStrCondition condition{ active, icase, regex, reserved, reserved }; const Glib::ustring& pattern = row[ m_columns.m_col_pattern ]; const Glib::ustring& replace = row[ m_columns.m_col_replace ]; mgr->list_append( i, condition, pattern.raw(), replace.raw() ); } } mgr->save_xml(); //DBTREE::update_abone_thread(); CORE::core_set_command( "relayout_all_board" ); CORE::core_set_command( "relayout_all_article" ); } void ReplaceStrPref::slot_row_activated( const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn* ) { #ifdef _DEBUG std::cout << "ReplaceStrPref::slot_row_activated path = " << path.to_string() << std::endl; #endif Gtk::TreeModel::Row row = *( m_current_store->get_iter( path ) ); if( ! row ) return; const bool active = row[ m_columns.m_col_active ]; const bool icase = row[ m_columns.m_col_icase ]; const bool regex = row[ m_columns.m_col_regex ]; constexpr bool reserved = false; // wchar, norm const ReplaceStrCondition condition{ active, icase, regex, reserved, reserved }; ReplaceStrDiag dlg( this, condition, row[ m_columns.m_col_pattern ], row[ m_columns.m_col_replace ] ); if( dlg.run() == Gtk::RESPONSE_OK ) { row.set_value( m_columns.m_col_active, dlg.get_active() ); row.set_value( m_columns.m_col_icase, dlg.get_icase() ); row.set_value( m_columns.m_col_regex, dlg.get_regex() ); row.set_value( m_columns.m_col_pattern, dlg.get_pattern() ); row.set_value( m_columns.m_col_replace, dlg.get_replace() ); if( dlg.get_regex() ) { JDLIB::RegexPattern ptn; constexpr bool newline = true; constexpr bool migemo = false; if( ! ptn.set( dlg.get_pattern(), dlg.get_icase(), newline, migemo ) ) { const std::string msg = ptn.errstr() + "\n\n" + dlg.get_pattern(); SKELETON::MsgDiag mdlg( *this, msg, false, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_OK ); mdlg.run(); } } } } bool ReplaceStrPref::slot_key_press( GdkEventKey* event ) { const int id = m_control.key_press( event ); #ifdef _DEBUG std::cout << "ReplaceStrPref::slot_key_press id = " << id << std::endl; #endif if( id == CONTROL::Up ) { if( auto it = get_selected_row() ) { if( --it ) { m_treeview.get_selection()->select( it ); return true; } } } else if( id == CONTROL::Down ) { if( auto it = get_selected_row() ) { if( ++it ) { m_treeview.get_selection()->select( it ); return true; } } } else if( event->keyval == GDK_KEY_Return || event->keyval == GDK_KEY_space ) { if( auto it = get_selected_row() ) { slot_row_activated( Gtk::TreePath( it ), nullptr ); } } return false; } bool ReplaceStrPref::slot_key_release( GdkEventKey* event ) { const int id = m_control.key_press( event ); #ifdef _DEBUG std::cout << "ReplaceStrPref::slot_key_release id = " << id << std::endl; #endif if( id == CONTROL::Delete ) slot_delete(); return true; } // // 一番上へ移動 // void ReplaceStrPref::slot_top() { Gtk::TreeModel::iterator it = get_selected_row(); Gtk::TreeModel::iterator it_top = get_top_row(); if( it && it != it_top ) m_current_store->move( it, it_top ); } // // 上へ移動 // void ReplaceStrPref::slot_up() { if( auto it = get_selected_row() ) { if( auto it_dest = std::prev( it ) ) { m_current_store->iter_swap( it, it_dest ); } } } // // 下へ移動 // void ReplaceStrPref::slot_down() { if( auto it = get_selected_row() ) { if( auto it_dest = std::next( it ) ) { m_current_store->iter_swap( it, it_dest ); } } } // // 一番下へ移動 // void ReplaceStrPref::slot_bottom() { Gtk::TreeModel::iterator it = get_selected_row(); Gtk::TreeModel::iterator it_bottom = get_bottom_row(); if( it && it != it_bottom ) { m_current_store->move( it, m_current_store->children().end() ); } } // // 削除ボタン // void ReplaceStrPref::slot_delete() { Gtk::TreeModel::iterator it = get_selected_row(); if( ! it ) return; SKELETON::MsgDiag mdiag( *this, "削除しますか?", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO ); if( mdiag.run() != Gtk::RESPONSE_YES ) return; auto it_next = std::next( it ); m_current_store->erase( it ); if( it_next ) select_row( it_next ); else { Gtk::TreeModel::iterator it_bottom = get_bottom_row(); if( it_bottom ) select_row( it_bottom ); } } // // 追加ボタン // void ReplaceStrPref::slot_add() { constexpr ReplaceStrCondition condition{ true, false, false, false, false }; ReplaceStrDiag dlg( this, condition, "", "" ); if( dlg.run() == Gtk::RESPONSE_OK ) { append_row( m_current_store, dlg.get_condition(), dlg.get_pattern(), dlg.get_replace() ); } } // // 置換対象変更 // void ReplaceStrPref::slot_target_changed() { // 省略したスレタイトルの分インデックスをずらしてデリファレンスする const int id = m_menu_target.get_active_row_number() + kReplStrTargetReserved_0; #ifdef _DEBUG std::cout << "ReplaceStrPref::slot_target_changed target=" << id << std::endl; #endif m_chref[ m_id_target ] = m_check_chref.get_active(); m_check_chref.set_active( m_chref[ id ] ); m_current_store = m_store[ id ]; m_treeview.set_model( m_current_store ); select_row( get_top_row() ); m_id_target = id; } jdim-0.7.0/src/replacestrpref.h000066400000000000000000000076561417047150700164460ustar00rootroot00000000000000// ライセンス: GPL2 // // 文字列置換設定ダイアログ // #ifndef REPLACESTRPREF_H #define REPLACESTRPREF_H #include "skeleton/prefdiag.h" #include "skeleton/treeviewbase.h" #include "control/control.h" #include namespace CORE { struct ReplaceStrCondition; class ReplaceStrDiag : public SKELETON::PrefDiag { Gtk::Box m_hbox_active; Gtk::Box m_hbox_regex; Gtk::Button m_button_copy; Gtk::CheckButton m_check_active; Gtk::CheckButton m_check_icase; Gtk::CheckButton m_check_regex; Gtk::Grid m_grid_entry; Gtk::Label m_label_pattern; Gtk::Entry m_entry_pattern; Gtk::Label m_label_replace; Gtk::Entry m_entry_replace; public: ReplaceStrDiag( Gtk::Window* parent, ReplaceStrCondition condition, const Glib::ustring& pattern, const Glib::ustring& replace ); ~ReplaceStrDiag() noexcept = default; bool get_active() const { return m_check_active.get_active(); } bool get_icase() const { return m_check_icase.get_active(); } bool get_regex() const { return m_check_regex.get_active(); } ReplaceStrCondition get_condition() const; Glib::ustring get_pattern() const { return m_entry_pattern.get_text(); } Glib::ustring get_replace() const { return m_entry_replace.get_text(); } private: void slot_copy(); void slot_sens(); }; class ReplaceRecord : public Gtk::TreeModel::ColumnRecord { public: Gtk::TreeModelColumn m_col_active; Gtk::TreeModelColumn m_col_icase; Gtk::TreeModelColumn m_col_regex; Gtk::TreeModelColumn m_col_pattern; Gtk::TreeModelColumn m_col_replace; ReplaceRecord() { add( m_col_active ); add( m_col_icase ); add( m_col_regex ); add( m_col_pattern ); add( m_col_replace ); } ~ReplaceRecord() noexcept = default; }; class ReplaceStrPref : public SKELETON::PrefDiag { int m_id_target; Gtk::Grid m_grid_head; Gtk::Label m_label_target; Gtk::ComboBoxText m_menu_target; Gtk::LinkButton m_link_manual; Gtk::CheckButton m_check_chref; std::vector m_chref; SKELETON::JDTreeViewBase m_treeview; CONTROL::Control m_control; std::vector> m_store; Glib::RefPtr m_current_store; ReplaceRecord m_columns; Gtk::ScrolledWindow m_scrollwin; Gtk::Button m_button_top; Gtk::Button m_button_up; Gtk::Button m_button_down; Gtk::Button m_button_bottom; Gtk::Button m_button_delete; Gtk::Button m_button_add; Gtk::Box m_vbuttonbox; Gtk::Box m_hbox; public: ReplaceStrPref( Gtk::Window* parent, const std::string& url ); ~ReplaceStrPref() noexcept = default; private: void append_rows(); void append_row( const Glib::RefPtr& store, ReplaceStrCondition condition, const std::string& pattern, const std::string& replace ); Gtk::TreeModel::const_iterator get_selected_row() const; Gtk::TreeModel::const_iterator get_top_row() const; Gtk::TreeModel::const_iterator get_bottom_row() const; void select_row( const Gtk::TreeModel::const_iterator& it ); // OK押した void slot_ok_clicked() override; void slot_row_activated( const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn* ); bool slot_key_press( GdkEventKey* event ); bool slot_key_release( GdkEventKey* event ); void slot_top(); void slot_up(); void slot_down(); void slot_bottom(); void slot_delete(); void slot_add(); void slot_target_changed(); }; } #endif jdim-0.7.0/src/searchitempref.cpp000066400000000000000000000025321417047150700167450ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "searchitempref.h" #include "icons/iconmanager.h" #include "global.h" #include "session.h" #include "command.h" using namespace CORE; SearchItemPref::SearchItemPref( Gtk::Window* parent, const std::string& url ) : SKELETON::SelectItemPref( parent, url ) { // デフォルトの項目を設定 append_default_pair( ITEM_NAME_NAME, ICON::get_icon( ICON::TRANSPARENT ) ); append_default_pair( ITEM_NAME_SEARCH, ICON::get_icon( ICON::SEARCH ) ); append_default_pair( ITEM_NAME_RELOAD, ICON::get_icon( ICON::RELOAD ) ); append_default_pair( ITEM_NAME_STOPLOADING, ICON::get_icon( ICON::STOPLOADING ) ); append_default_pair( ITEM_NAME_QUIT, ICON::get_icon( ICON::QUIT ) ); append_default_pair( ITEM_NAME_SEPARATOR, ICON::get_icon( ICON::TRANSPARENT ) ); // 文字列を元に行を追加 append_rows( SESSION::get_items_search_toolbar_str() ); set_title( "ツールバー項目設定(ログ/スレタイ検索)" ); } // // OKを押した // void SearchItemPref::slot_ok_clicked() { SESSION::set_items_search_toolbar_str( get_items() ); CORE::core_set_command( "update_article_toolbar_button" ); } // // デフォルトボタン // void SearchItemPref::slot_default() { append_rows( SESSION::get_items_search_toolbar_default_str() ); } jdim-0.7.0/src/searchitempref.h000066400000000000000000000010751417047150700164130ustar00rootroot00000000000000// ライセンス: GPL2 // ログ/スレタイ検索のツールバーの表示項目設定 #ifndef _SEARCHITEMPREF_H #define _SEARCHITEMPREF_H #include "skeleton/selectitempref.h" namespace CORE { class SearchItemPref : public SKELETON::SelectItemPref { public: SearchItemPref( Gtk::Window* parent, const std::string& url ); ~SearchItemPref() noexcept = default; private: // OKボタン void slot_ok_clicked() override; // デフォルトボタン void slot_default() override; }; } #endif jdim-0.7.0/src/searchloader.cpp000066400000000000000000000040431417047150700163770ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "searchloader.h" #include "usrcmdmanager.h" #include "jdlib/loaderdata.h" #include "jdlib/miscutil.h" #include "config/globalconf.h" using namespace CORE; SearchLoader::SearchLoader() : SKELETON::TextLoader() { std::string url = CONFIG::get_url_search_title(); m_charset = "UTF-8"; // 結果のエンコード指定を、検索結果のエンコードに設定する if( url.find( "$OUTU" ) != std::string::npos ) m_charset = "UTF-8"; else if( url.find( "$OUTX" ) != std::string::npos ) m_charset = "EUC-JP"; else if( url.find( "$OUTE" ) != std::string::npos ) m_charset = "MS932"; // 結果のエンコード指定がない場合は、検索クエリのエンコードを検索結果のエンコードに設定する else if( url.find( "$TEXTX" ) != std::string::npos ) m_charset = "EUC-JP"; else if( url.find( "$TEXTE" ) != std::string::npos ) m_charset = "MS932"; #ifdef _DEBUG std::cout << "SearchLoader::SearchLoader charset = " << m_charset << std::endl;; #endif } SearchLoader::~SearchLoader() { #ifdef _DEBUG std::cout << "SearchLoader::~SearchLoader\n"; #endif } std::string SearchLoader::get_url() const { std::string url = get_usrcmd_manager()->replace_cmd( CONFIG::get_url_search_title(), "", "", m_query, 0 ); #ifdef _DEBUG std::cout << "SearchLoader::get_url url = " << url << std::endl; #endif return url; } void SearchLoader::search( const std::string& query ) { #ifdef _DEBUG std::cout << "SearchLoader::search query = " << query << std::endl; #endif m_query = query; reset(); download_text(); } // ロード用データ作成 void SearchLoader::create_loaderdata( JDLIB::LOADERDATA& data ) { #ifdef _DEBUG std::cout << "SearchLoader::create_loaderdata\n"; #endif data.init_for_data(); data.url = get_url(); } // ロード後に呼び出される void SearchLoader::parse_data() { #ifdef _DEBUG std::cout << "SearchLoader::parse_data\n"; #endif m_sig_search_fin.emit(); } jdim-0.7.0/src/searchloader.h000066400000000000000000000020121417047150700160360ustar00rootroot00000000000000// ライセンス: GPL2 // // スレタイ検索ローダ // #ifndef _SEARCHLOADER_H #define _SEARCHLOADER_H #include "skeleton/textloader.h" #include namespace CORE { class SearchLoader : public SKELETON::TextLoader { // 検索が終了するとemitされる typedef sigc::signal< void > SIG_SEARCH_FIN; SIG_SEARCH_FIN m_sig_search_fin; std::string m_charset; std::string m_query; public: SearchLoader(); ~SearchLoader(); SIG_SEARCH_FIN sig_search_fin(){ return m_sig_search_fin; } void search( const std::string& query ); protected: std::string get_url() const override; std::string get_path() const override { return {}; } std::string get_charset() const override { return m_charset; } // ロード用データ作成 void create_loaderdata( JDLIB::LOADERDATA& data ) override; // ロード後に呼び出される void parse_data() override; }; } #endif jdim-0.7.0/src/searchmanager.cpp000066400000000000000000000171071417047150700165500ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "searchmanager.h" #include "searchloader.h" #include "jdlib/miscutil.h" #include "jdlib/miscmsg.h" #include "jdlib/jdregex.h" #include "dbtree/interface.h" #include "dbtree/articlebase.h" #include "config/globalconf.h" CORE::Search_Manager* instance_search_manager = nullptr; CORE::Search_Manager* CORE::get_search_manager() { if( ! instance_search_manager ) instance_search_manager = new Search_Manager(); assert( instance_search_manager ); return instance_search_manager; } void CORE::delete_search_manager() { if( instance_search_manager ) delete instance_search_manager; instance_search_manager = nullptr; } /////////////////////////////////////////////// using namespace CORE; Search_Manager::Search_Manager() = default; Search_Manager::~Search_Manager() { // デストラクタの中からdispatchを呼ぶと落ちるので dispatch不可にする set_dispatchable( false ); stop( std::string() ); wait(); } // // ログ検索 // // 結果は m_list_article と m_list_data に入る。 // // id : 呼び出し元の ID。 検索終了時に SIG_SEARCH_FIN シグナルで送る // searchmode : 検索モード // url: ログ検索先の板のアドレス // query : 検索文字列、空文字ならキャッシュにあるスレを全て選択 // mode_or : false なら AND、true なら OR で検索する // bm : trueの時、しおりが付いている(スレ一覧でしおりを付けた or レスに一つでもしおりが付いている)スレのみを対象に検索する // calc_data : 検索終了時に m_list_data を求める // bool Search_Manager::search( const std::string& id, const int searchmode, const std::string& url, const std::string& query, const bool mode_or, const bool bm, const bool calc_data ) { #ifdef _DEBUG std::cout << "Search_Manager::search url = " << url << " query = " << query << std::endl; #endif if( m_searching ) return false; if( m_thread.is_running() ) return false; m_searchmode = searchmode; m_id = id; m_url = url; m_query = query; m_mode_or = mode_or; m_bm = bm; m_calc_data = calc_data; m_list_article.clear(); m_list_data.clear(); // 全ログ検索のとき、スレッドを起動する前にメインスレッドで板情報ファイルを // 読み込んでおかないと大量の warning が出る if( m_searchmode == SEARCHMODE_ALLLOG ) DBTREE::read_boardinfo_all(); if( ! m_thread.create( ( STARTFUNC ) launcher, ( void * ) this, JDLIB::NODETACH ) ){ MISC::ERRMSG( "Search_Manager::search : could not start thread" ); return FALSE; } m_searching = true; return true; } // // スレッドのランチャ (static) // void* Search_Manager::launcher( void* dat ) { Search_Manager* sm = ( Search_Manager * ) dat; sm->thread_search(); return nullptr; } // // ログ検索実行スレッド // void Search_Manager::thread_search() { #ifdef _DEBUG std::cout << "Search_Manager::thread_search\n"; #endif m_stop = false; if( m_searchmode == SEARCHMODE_LOG ) DBTREE::search_cache( m_url, m_list_article, m_query, m_mode_or, m_bm, m_stop ); else if( m_searchmode == SEARCHMODE_ALLLOG ) DBTREE::search_cache_all( m_list_article, m_query, m_mode_or, m_bm, m_stop ); if( m_calc_data ){ for( const DBTREE::ArticleBase* article : m_list_article ) { SEARCHDATA data; data.url_readcgi = DBTREE::url_readcgi( article->get_url(), 0, 0 ); data.subject = article->get_subject(); data.num = article->get_number_load(); data.bookmarked = article->is_bookmarked_thread(); data.num_bookmarked = article->get_num_bookmark(); data.boardname = DBTREE::board_name( data.url_readcgi ); #ifdef _DEBUG std::cout << "url = " << data.url_readcgi << std::endl << "board = " << data.boardname << std::endl << "subject = " << data.subject << std::endl << "num = " << data.num << std::endl << std::endl; #endif m_list_data.push_back( data ); } } dispatch(); } // // ディスパッチャのコールバック関数 // void Search_Manager::callback_dispatch() { wait(); search_fin(); } // // 検索終了 // void Search_Manager::search_fin() { #ifdef _DEBUG std::cout << "Search_Manager::search_fin\n"; #endif m_sig_search_fin.emit( m_id ); m_searching = false; } // // 検索中止 // void Search_Manager::stop( const std::string& id ) { if( ! m_searching ) return; if( ! id.empty() && id != m_id ) return; #ifdef _DEBUG std::cout << "Search_Manager::stop\n"; #endif m_stop = true; // スレタイ検索停止 if( m_searchmode == SEARCHMODE_TITLE && m_searchloader ) m_searchloader->stop_load(); } void Search_Manager::wait() { m_thread.join(); } ///////////////////////////////////////////////////////////////////////// // // スレタイ検索 // bool Search_Manager::search_title( const std::string& id, const std::string& query ) { if( m_searching ) return false; if( m_searchloader && m_searchloader->is_loading() ) return false; #ifdef _DEBUG std::cout << "Search_Manager::search_title query = " << query << std::endl; #endif m_searchmode = SEARCHMODE_TITLE; m_id = id; m_query = query; m_list_data.clear(); // スレタイの検索が終わったら search_fin_title が呼び出される if( ! m_searchloader ){ m_searchloader = std::make_unique(); m_searchloader->sig_search_fin().connect( sigc::mem_fun( *this, &Search_Manager::search_fin_title ) ); } m_searching = true; m_searchloader->search( m_query ); return true; } // // スレタイ検索ロード終了 // void Search_Manager::search_fin_title() { #ifdef _DEBUG std::cout << "Search_Manager::search_fin_title\n"; #endif if( ! m_searchloader ) return; // 正規表現を使ってURLを抜き出していく const std::string& source = m_searchloader->get_data(); if( ! source.empty() ){ const std::string pattern = CONFIG::get_regex_search_title(); JDLIB::Regex regex; const bool icase = false; const bool newline = true; const bool usemigemo = false; const bool wchar = false; const JDLIB::RegexPattern regexptn( pattern, icase, newline, usemigemo, wchar ); std::size_t offset = 0; while( regex.match( regexptn, source, offset ) ){ SEARCHDATA data; data.url_readcgi = DBTREE::url_readcgi( regex.str( 1 ), 0, 0 ); data.subject = MISC::html_unescape( regex.str( 2 ) ); data.num = std::atoi( regex.str( 3 ).c_str() ); // マッチしていなければ 0 になる data.bookmarked = false; data.num_bookmarked = 0; if( ! data.url_readcgi.empty() ){ data.boardname = DBTREE::board_name( data.url_readcgi ); #ifdef _DEBUG std::cout << "url = " << data.url_readcgi << std::endl << "board = " << data.boardname << std::endl << "subject = " << data.subject << std::endl << "num = " << data.num << std::endl << std::endl; #endif m_list_data.push_back( data ); } // オフセットを設定して再検索する offset = regex.pos( 0 ) + regex.length( 0 ); } } m_searchloader->reset(); // call SKELETON::TextLoader::reset() search_fin(); } jdim-0.7.0/src/searchmanager.h000066400000000000000000000066471417047150700162240ustar00rootroot00000000000000// ライセンス: GPL2 // // ログ、スレタイ検索クラス // #ifndef _SEARCHMANAGER_H #define _SEARCHMANAGER_H #include "skeleton/dispatchable.h" #include "jdlib/jdthread.h" #include #include #include #include namespace DBTREE { class ArticleBase; } namespace CORE { class SearchLoader; // 検索モード enum { SEARCHMODE_LOG = 0, // ログ検索 SEARCHMODE_ALLLOG, // 全ログ検索 SEARCHMODE_TITLE // スレタイ検索 }; struct SEARCHDATA { std::string url_readcgi; std::string boardname; std::string subject; int num; // 読み込み数 bool bookmarked; // スレ全体にしおりがついているか int num_bookmarked; // レスにつけられたしおりの数 }; class Search_Manager : public SKELETON::Dispatchable { typedef sigc::signal< void, const std::string& > SIG_SEARCH_FIN; SIG_SEARCH_FIN m_sig_search_fin; JDLIB::Thread m_thread; int m_searchmode{}; std::string m_id; std::string m_url; std::string m_query; bool m_mode_or{}; bool m_bm{}; bool m_calc_data{}; std::vector< DBTREE::ArticleBase* > m_list_article; std::list< SEARCHDATA > m_list_data; // 検索実行中 bool m_searching{}; bool m_stop{}; // スレタイ検索ローダ std::unique_ptr m_searchloader; public: Search_Manager(); ~Search_Manager(); SIG_SEARCH_FIN sig_search_fin(){ return m_sig_search_fin; } const std::vector< DBTREE::ArticleBase* >& get_list_article() const{ return m_list_article; } const std::list< SEARCHDATA >& get_list_data() const { return m_list_data; } bool is_searching() const { return m_searching; } bool is_searching( const std::string& id ) const { if( id == m_id ) return m_searching; else return false; } void stop( const std::string& id ); // ログ検索 // // 結果は m_list_article と m_list_data に入る。 // // id : 呼び出し元の ID。 検索終了時に SIG_SEARCH_FIN シグナルで送る // searchmode : 検索モード // url: ログ検索先の板のアドレス // query : 検索文字列、空文字ならキャッシュにあるスレを全て選択 // mode_or : false なら AND、true なら OR で検索する // bm : trueの時、しおりが付いている(スレ一覧でしおりを付けた or レスに一つでもしおりが付いている)スレのみを対象に検索する // calc_data : 検索終了時に m_list_data を求める bool search( const std::string& id, const int searchmode, const std::string& url, const std::string& query, const bool mode_or, const bool bm, const bool calc_data ); // スレタイ検索 bool search_title( const std::string& id, const std::string& query ); private: static void* launcher( void* ); void wait(); void thread_search(); void callback_dispatch() override; void search_fin(); void search_fin_title(); }; /////////////////////////////////////// // インターフェース Search_Manager* get_search_manager(); void delete_search_manager(); } #endif jdim-0.7.0/src/session.cpp000066400000000000000000001467651417047150700154500ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "session.h" #include "cache.h" #include "global.h" #include "jdlib/confloader.h" #include "jdlib/miscutil.h" #include "jdlib/misctime.h" #include "bbslist/bbslistadmin.h" #include "article/articleadmin.h" #include "article/articleviewbase.h" #include #include bool booting = true; bool quitting = false; bool tab_operating_article = false; bool tab_operating_image = false; int mode_pane; bool mode_online; bool mode_login2ch; bool mode_loginbe; enum { mode_loginp2 }; // Removed in v0.3.0 (2020-05) int win_hpane_main_pos; int win_vpane_main_pos; int win_hpane_main_r_pos; int win_vpane_main_mes_pos; int win_notebook_main_page; int win_bbslist_page; int win_board_page; int win_article_page; int win_image_page; std::list< std::string > board_urls; std::list< bool > board_locked; std::list< std::string > board_switchhistory; std::list< std::string > article_urls; std::list< bool > article_locked; std::list< std::string > article_switchhistory; std::list< std::string > image_urls; std::list< bool > image_locked; std::string items_sidebar_toolbar_str; std::vector< int > items_sidebar_toolbar; std::string items_main_toolbar_str; std::vector< int > items_main_toolbar; std::string items_article_toolbar_str; std::vector< int > items_article_toolbar; std::string items_search_toolbar_str; std::vector< int > items_search_toolbar; std::string items_board_toolbar_str; std::vector< int > items_board_toolbar; std::string items_board_col_str; std::vector< int > items_board_col; std::string items_msg_toolbar_str; std::vector< int > items_msg_toolbar; std::string items_board_menu_str; std::vector< int > items_board_menu; std::string items_article_menu_str; std::vector< int > items_article_menu; int board_col_mark; int board_col_id; int board_col_board; int board_col_subject; int board_col_number; int board_col_load; int board_col_new; int board_col_since; int board_col_write; int board_col_access; int board_col_speed; int board_col_diff; int board_col_since_time; int board_col_write_time; int board_col_access_time; bool win_show_sidebar; bool win_show_menubar; bool show_main_toolbar; int win_toolbar_pos; bool show_bbslist_toolbar; bool show_board_toolbar; bool show_article_toolbar; bool show_board_tab; bool show_article_tab; bool show_main_statbar; int win_focused_admin; int win_focused_admin_sidebar; int x_win_main; int y_win_main; int x_win_img; int y_win_img; int x_win_mes; int y_win_mes; int width_win_main; int height_win_main; int width_win_img; int height_win_img; int width_win_mes; int height_win_mes; bool focus_win_main = false; bool focus_win_img = false; bool focus_win_mes = false; bool maximized_win_main = false; bool maximized_win_img = false; bool maximized_win_mes = false; bool iconified_win_main = false; bool iconified_win_img = false; bool iconified_win_mes = false; bool shown_win_main = false; bool shown_win_img = false; bool shown_win_mes = false; bool full_win_main = false; bool dialog_shown = false; bool embedded_img; bool embedded_mes; bool close_mes; std::string dir_dat; std::string img_dir_img_save; std::string dir_draft; bool popupmenu_shown; std::vector< std::string > delete_list; std::vector< std::string > live_urls; int img_fit_mode; std::string dir_select_favorite; ///////////////////////////////////// // ツールバー等の項目名 -> ID 変換 int SESSION::parse_item( const std::string& item_name ) { int item = ITEM_END; if( item_name == ITEM_NAME_BBSLISTVIEW ) item = ITEM_BBSLISTVIEW; else if( item_name == ITEM_NAME_FAVORITEVIEW ) item = ITEM_FAVORITEVIEW; else if( item_name == ITEM_NAME_BOARDVIEW ) item = ITEM_BOARDVIEW; else if( item_name == ITEM_NAME_HISTVIEW ) item = ITEM_HISTVIEW; else if( item_name == ITEM_NAME_HIST_BOARDVIEW ) item = ITEM_HIST_BOARDVIEW; else if( item_name == ITEM_NAME_HIST_CLOSEVIEW ) item = ITEM_HIST_CLOSEVIEW; else if( item_name == ITEM_NAME_HIST_CLOSEBOARDVIEW ) item = ITEM_HIST_CLOSEBOARDVIEW; else if( item_name == ITEM_NAME_HIST_CLOSEIMGVIEW ) item = ITEM_HIST_CLOSEIMGVIEW; else if( item_name == ITEM_NAME_ARTICLEVIEW ) item = ITEM_ARTICLEVIEW; else if( item_name == ITEM_NAME_IMAGEVIEW ) item = ITEM_IMAGEVIEW; else if( item_name == ITEM_NAME_URL ) item = ITEM_URL; else if( item_name == ITEM_NAME_GO ) item = ITEM_GO; else if( item_name == ITEM_NAME_SEPARATOR ) item = ITEM_SEPARATOR; else if( item_name == ITEM_NAME_MARK ) item = ITEM_MARK; else if( item_name == ITEM_NAME_ID ) item = ITEM_ID; else if( item_name == ITEM_NAME_BOARD ) item = ITEM_BOARD; else if( item_name == ITEM_NAME_NAME ) item = ITEM_NAME; else if( item_name == ITEM_NAME_RES ) item = ITEM_RES; else if( item_name == ITEM_NAME_LOAD ) item = ITEM_LOAD; else if( item_name == ITEM_NAME_NEW ) item = ITEM_NEW; else if( item_name == ITEM_NAME_SINCE ) item = ITEM_SINCE; else if( item_name == ITEM_NAME_LASTWRITE ) item = ITEM_LASTWRITE; else if( item_name == ITEM_NAME_ACCESS ) item = ITEM_ACCESS; else if( item_name == ITEM_NAME_SPEED ) item = ITEM_SPEED; else if( item_name == ITEM_NAME_DIFF ) item = ITEM_DIFF; else if( item_name == ITEM_NAME_WRITEMSG ) item = ITEM_WRITEMSG; else if( item_name == ITEM_NAME_OPENBOARD ) item = ITEM_OPENBOARD; else if( item_name == ITEM_NAME_OPENARTICLETAB ) item = ITEM_OPENARTICLETAB; else if( item_name == ITEM_NAME_REGETARTICLE ) item = ITEM_REGETARTICLE; else if( item_name == ITEM_NAME_BOOKMARK ) item = ITEM_BOOKMARK; else if( item_name == ITEM_NAME_SEARCH ) item = ITEM_SEARCH; else if( item_name == ITEM_NAME_DRAWOUT ) item = ITEM_DRAWOUT; else if( item_name == ITEM_NAME_RELOAD ) item = ITEM_RELOAD; else if( item_name == ITEM_NAME_STOPLOADING ) item = ITEM_STOPLOADING; else if( item_name == ITEM_NAME_APPENDFAVORITE ) item = ITEM_APPENDFAVORITE; else if( item_name == ITEM_NAME_FAVORITE_ARTICLE ) item = ITEM_FAVORITE_ARTICLE; else if( item_name == ITEM_NAME_CHECK_UPDATE_ROOT ) item = ITEM_CHECK_UPDATE_ROOT; else if( item_name == ITEM_NAME_CHECK_UPDATE_OPEN_ROOT ) item = ITEM_CHECK_UPDATE_OPEN_ROOT; else if( item_name == ITEM_NAME_COPY ) item = ITEM_COPY; else if( item_name == ITEM_NAME_COPY_URL ) item = ITEM_COPY_URL; else if( item_name == ITEM_NAME_COPY_TITLE_URL ) item = ITEM_COPY_TITLE_URL; else if( item_name == ITEM_NAME_COPY_TITLE_URL_THREAD ) item = ITEM_COPY_TITLE_URL_THREAD; else if( item_name == ITEM_NAME_COPY_THREAD_INFO ) item = ITEM_COPY_THREAD_INFO; else if( item_name == ITEM_NAME_DELETE ) item = ITEM_DELETE; else if( item_name == ITEM_NAME_QUIT ) item = ITEM_QUIT; else if( item_name == ITEM_NAME_BACK ) item = ITEM_BACK; else if( item_name == ITEM_NAME_FORWARD ) item = ITEM_FORWARD; else if( item_name == ITEM_NAME_LOCK ) item = ITEM_LOCK; else if( item_name == ITEM_NAME_LIVE ) item = ITEM_LIVE; else if( item_name == ITEM_NAME_NEWARTICLE ) item = ITEM_NEWARTICLE; else if( item_name == ITEM_NAME_SEARCHBOX ) item = ITEM_SEARCHBOX; else if( item_name == ITEM_NAME_SEARCH_NEXT ) item = ITEM_SEARCH_NEXT; else if( item_name == ITEM_NAME_SEARCH_PREV ) item = ITEM_SEARCH_PREV; else if( item_name == ITEM_NAME_NEXTARTICLE ) item = ITEM_NEXTARTICLE; else if( item_name == ITEM_NAME_CLEAR_HIGHLIGHT ) item = ITEM_CLEAR_HIGHLIGHT; else if( item_name == ITEM_NAME_INSERTTEXT ) item = ITEM_INSERTTEXT; else if( item_name == ITEM_NAME_LOCK_MESSAGE ) item = ITEM_LOCK_MESSAGE; else if( item_name == ITEM_NAME_PREVIEW ) item = ITEM_PREVIEW; else if( item_name == ITEM_NAME_UNDO ) item = ITEM_UNDO; else if( item_name == ITEM_NAME_REDO ) item = ITEM_REDO; else if( item_name == ITEM_NAME_NGWORD ) item = ITEM_NGWORD; else if( item_name == ITEM_NAME_ABONE_SELECTION ) item = ITEM_ABONE_SELECTION; else if( item_name == ITEM_NAME_ABONE_ARTICLE ) item = ITEM_ABONE_ARTICLE; else if( item_name == ITEM_NAME_QUOTE_SELECTION ) item = ITEM_QUOTE_SELECTION; else if( item_name == ITEM_NAME_OPEN_BROWSER ) item = ITEM_OPEN_BROWSER; else if( item_name == ITEM_NAME_OPEN_CACHE_BROWSER ) item = ITEM_OPEN_CACHE_BROWSER; else if( item_name == ITEM_NAME_USER_COMMAND ) item = ITEM_USER_COMMAND; else if( item_name == ITEM_NAME_ETC ) item = ITEM_ETC; else if( item_name == ITEM_NAME_SAVE_DAT ) item = ITEM_SAVE_DAT; else if( item_name == ITEM_NAME_SELECTIMG ) item = ITEM_SELECTIMG; else if( item_name == ITEM_NAME_SELECTDELIMG ) item = ITEM_SELECTDELIMG; else if( item_name == ITEM_NAME_SELECTABONEIMG ) item = ITEM_SELECTABONEIMG; else if( item_name == ITEM_NAME_PREF_BOARD ) item = ITEM_PREF_BOARD; else if( item_name == ITEM_NAME_PREF_THREAD ) item = ITEM_PREF_THREAD; return item; } std::vector< int > parse_items( const std::string& items_str ) { std::vector< int > items; const std::list< std::string > list_order = MISC::split_line( items_str ); for( const std::string& item_str : list_order ) { const int item = SESSION::parse_item( item_str ); if( item != ITEM_END ) items.push_back( item ); } items.push_back( ITEM_END ); return items; } void read_list_urls( JDLIB::ConfLoader& cf, const std::string& id_urls, std::list< std::string >& list_urls ) { list_urls.clear(); const std::string str_tmp = cf.get_option_str( id_urls, "" ); if( ! str_tmp.empty() ){ std::list list_tmp = MISC::split_line( str_tmp ); for( std::string& url : list_tmp ) if( ! url.empty() ) list_urls.push_back( std::move( url ) ); } } void read_list_locked( JDLIB::ConfLoader& cf, const std::string& id_locked, std::list< bool >& list_locked ) { list_locked.clear(); const std::string str_tmp = cf.get_option_str( id_locked, "" ); if( ! str_tmp.empty() ){ const std::list list_tmp = MISC::split_line( str_tmp ); std::transform( list_tmp.cbegin(), list_tmp.cend(), std::back_inserter( list_locked ), []( const auto& lock_str ) { return lock_str == "1"; } ); } } // セッション情報読み込み void SESSION::init_session() { #ifdef _DEBUG std::cout << "SESSION::init_session\n"; #endif JDLIB::ConfLoader cf( CACHE::path_session(), std::string() ); // オンライン mode_online = cf.get_option_bool( "mode_online", true ); // 2chログイン mode_login2ch = cf.get_option_bool( "mode_login2ch", false ); // beログイン mode_loginbe = cf.get_option_bool( "mode_loginbe", false ); // paneのモード mode_pane = cf.get_option_int( "mode_pane", MODE_2PANE, 0, MODE_PANE_NUM -1 ); x_win_main = cf.get_option_int( "x", 0, 0, 8192 ); y_win_main = cf.get_option_int( "y", 0, 0, 8192 ); width_win_main = cf.get_option_int( "width", 800, 80, 8192 ); height_win_main = cf.get_option_int( "height", 600, 60, 8192 ); maximized_win_main = cf.get_option_bool( "maximized", false ); full_win_main = cf.get_option_bool( "full_win_main", false ); win_show_sidebar = cf.get_option_bool( "show_sidebar", true ); win_show_menubar = cf.get_option_bool( "show_menubar", true ); show_main_toolbar = cf.get_option_bool( "show_main_toolbar", true ); win_toolbar_pos = cf.get_option_int( "toolbar_pos", TOOLBAR_POS_NORMAL, 0, TOOLBAR_POS_NUM -1 ); show_bbslist_toolbar = cf.get_option_bool( "show_bbslist_toolbar", true ); show_board_toolbar = cf.get_option_bool( "show_board_toolbar", true ); show_article_toolbar = cf.get_option_bool( "show_article_toolbar", true ); show_board_tab = cf.get_option_bool( "show_board_tab", true ); show_article_tab = cf.get_option_bool( "show_article_tab", true ); show_main_statbar = cf.get_option_bool( "show_main_statbar", true ); win_focused_admin = cf.get_option_int( "focused_admin", FOCUS_NOT, 0, FOCUS_NUM -1 ); win_focused_admin_sidebar = cf.get_option_int( "focused_admin_sidebar", FOCUS_NOT, 0, FOCUS_NUM -1 ); win_hpane_main_pos = cf.get_option_int( "hpane_main_pos", 190, 0, 8192 ); win_vpane_main_pos = cf.get_option_int( "vpane_main_pos", 200, 0, 8192 ); win_hpane_main_r_pos = cf.get_option_int( "hpane_main_r_pos", 300, 0, 8192 ); win_vpane_main_mes_pos = cf.get_option_int( "vpane_main_mes_pos", 400, 0, 8192 ); // メインnotebookのページ番号 win_notebook_main_page = cf.get_option_int( "notebook_main_page", PAGE_ARTICLE, 0, PAGE_NUM -1 ); // 各ビューの開いてるページ番号 win_bbslist_page = cf.get_option_int( "bbslist_page", 0, 0, 1024 ); win_board_page = cf.get_option_int( "board_page", 0, 0, 1024 ); win_article_page = cf.get_option_int( "article_page", 0, 0, 1024 ); win_image_page = cf.get_option_int( "image_page", 0, 0, 1024 ); // スレ一覧のURLとロック状態と切り替え履歴 read_list_urls( cf, "board_urls", board_urls ); read_list_locked( cf, "board_locked", board_locked ); read_list_urls( cf, "board_switchhistory", board_switchhistory ); // スレタブのURLとロック状態と切り替え履歴 read_list_urls( cf, "article_urls", article_urls ); read_list_locked( cf, "article_locked", article_locked ); read_list_urls( cf, "article_switchhistory", article_switchhistory ); // 画像タブのURLとロック状態 read_list_urls( cf, "image_urls", image_urls ); read_list_locked( cf, "image_locked", image_locked ); // サイドバーのツールバー項目 items_sidebar_toolbar_str = cf.get_option_str( "items_sidebar", get_items_sidebar_toolbar_default_str() ); items_sidebar_toolbar = parse_items( items_sidebar_toolbar_str ); // メインツールバーの項目 items_main_toolbar_str = cf.get_option_str( "items_main_toolbar", get_items_main_toolbar_default_str() ); items_main_toolbar = parse_items( items_main_toolbar_str ); // スレビューのツールバーの項目 items_article_toolbar_str = cf.get_option_str( "items_article_toolbar", get_items_article_toolbar_default_str() ); items_article_toolbar = parse_items( items_article_toolbar_str ); // 検索ビューのツールバーの項目 items_search_toolbar_str = cf.get_option_str( "items_search_toolbar", get_items_search_toolbar_default_str() ); items_search_toolbar = parse_items( items_search_toolbar_str ); // スレ一覧のツールバー項目 items_board_toolbar_str = cf.get_option_str( "items_board_toolbar", get_items_board_toolbar_default_str() ); items_board_toolbar = parse_items( items_board_toolbar_str ); // 書き込みビューのツールバー項目 items_msg_toolbar_str = cf.get_option_str( "items_msg_toolbar", get_items_msg_toolbar_default_str() ); items_msg_toolbar = parse_items( items_msg_toolbar_str ); // スレ一覧の列項目 items_board_col_str = cf.get_option_str( "items_board", get_items_board_col_default_str() ); items_board_col = parse_items( items_board_col_str ); // スレ一覧のコンテキストメニュー項目 items_board_menu_str = cf.get_option_str( "items_board_menu", get_items_board_menu_default_str() ); items_board_menu = parse_items( items_board_menu_str ); // スレビューのコンテキストメニュー項目 items_article_menu_str = cf.get_option_str( "items_article_menu", get_items_article_menu_default_str() ); items_article_menu = parse_items( items_article_menu_str ); // board ビューの列幅 board_col_mark = cf.get_option_int( "col_mark", 30, 4, 8192 ); board_col_id = cf.get_option_int( "col_id", 45, 4, 8192 ); board_col_board = cf.get_option_int( "col_board", 70, 4, 8192 ); board_col_subject = cf.get_option_int( "col_subject", 190, 4, 8192 ); board_col_number = cf.get_option_int( "col_number", 45, 4, 8192 ); board_col_load = cf.get_option_int( "col_load", 45, 4, 8192 ); board_col_new = cf.get_option_int( "col_new", 45, 4, 8192 ); board_col_since = cf.get_option_int( "col_since", 70, 4, 8192 ); board_col_write = cf.get_option_int( "col_write", 70, 4, 8192 ); board_col_access = cf.get_option_int( "col_access", 70, 4, 8192 ); board_col_speed = cf.get_option_int( "col_speed", 45, 4, 8192 ); board_col_diff = cf.get_option_int( "col_diff", 45, 4, 8192 ); // スレ一覧の since の表示モード board_col_since_time = cf.get_option_int( "col_since_time", MISC::TIME_NORMAL, 0, MISC::TIME_NUM-1 ); // スレ一覧の 最終書込 の表示モード board_col_write_time = cf.get_option_int( "col_write_time", MISC::TIME_NORMAL, 0, MISC::TIME_NUM-1 ); // スレ一覧の 最終取得 の表示モード board_col_access_time = cf.get_option_int( "col_access_time", MISC::TIME_NORMAL, 0, MISC::TIME_NUM-1 ); embedded_img = cf.get_option_bool( "embedded_img", true ); x_win_img = cf.get_option_int( "x_win_img", 0, 0, 8192 ); y_win_img = cf.get_option_int( "y_win_img", 0, 0, 8192 ); width_win_img = cf.get_option_int( "width_win_img", 600, 60, 8192 ); height_win_img = cf.get_option_int( "height_win_img", 400, 40, 8192 ); embedded_mes = cf.get_option_bool( "embedded_mes", false ); // 書き込み後にmessageを閉じる close_mes = cf.get_option_bool( "close_mes", true ); x_win_mes = cf.get_option_int( "x_win_mes", 0, 0, 8192 ); y_win_mes = cf.get_option_int( "y_win_mes", 0, 0, 8192 ); width_win_mes = cf.get_option_int( "width_win_mes", 600, 60, 8192 ); height_win_mes = cf.get_option_int( "height_win_mes", 400, 40, 8192 ); dir_dat = cf.get_option_str( "dir_dat", "" ); img_dir_img_save = cf.get_option_str( "img_dir_img_save", "" ); dir_draft = cf.get_option_str( "dir_draft", "" ); popupmenu_shown = false; // 画像のfitモード img_fit_mode = cf.get_option_int( "img_fit_mode", IMG_FIT_NORMAL, 0, IMG_FIT_NUM -1 ); dir_select_favorite = cf.get_option_str( "dir_select_favorite", "" ); #ifdef _DEBUG std::cout << "x=" << x_win_main << std::endl << "y=" << y_win_main << std::endl << "w=" << width_win_main << std::endl << "h=" << height_win_main << std::endl << "max=" << maximized_win_main << std::endl << "toolbar_pos=" << win_toolbar_pos << std::endl << "sidebar=" << win_show_sidebar << std::endl << "menubar=" << win_show_menubar << std::endl << "focused_admin=" << win_focused_admin << std::endl << "focused_admin_sidebar=" << win_focused_admin_sidebar << std::endl << "hpane=" << win_hpane_main_pos << std::endl << "vpane=" << win_vpane_main_pos << std::endl << "hpane_r=" << win_hpane_main_r_pos << std::endl << "vpane_mes=" << win_vpane_main_mes_pos << std::endl << "notebook_main_page=" << win_notebook_main_page << std::endl << "bbslist_page=" << win_bbslist_page << std::endl << "board_page=" << win_board_page << std::endl << "article_page=" << win_article_page << std::endl << "image_page=" << win_image_page << std::endl; std::cout << "board_urls\n"; for( const std::string& url : board_urls ) if( ! url.empty() ) std::cout << url; std::cout << "article_urls\n"; for( const std::string& url : article_urls ) if( ! url.empty() ) std::cout << url; std::cout << "image_urls\n"; for( const std::string& url : image_urls ) if( ! url.empty() ) std::cout << url; std::cout << "columns\n" << board_col_mark << std::endl << board_col_id << std::endl << board_col_board << std::endl << board_col_subject << std::endl << board_col_number << std::endl << board_col_load << std::endl << board_col_new << std::endl << board_col_since << std::endl << board_col_write << std::endl << board_col_speed << std::endl << board_col_diff << std::endl << "embedded_mes = " << embedded_mes << std::endl << "close_mes = " << close_mes << std::endl << "wx=" << x_win_mes << std::endl << "wy=" << y_win_mes << std::endl << "ww=" << width_win_mes << std::endl << "wh=" << height_win_mes << std::endl; #endif } // セッション情報保存 void SESSION::save_session() { // 開いているタブのURL std::string str_board_urls; std::string str_article_urls; std::string str_image_urls; for( const std::string& url : board_urls ) { if( ! url.empty() ) str_board_urls.append( " \"" + url + "\"" ); } for( const std::string& url : article_urls ) { if( ! url.empty() ) str_article_urls.append( " \"" + url + "\"" ); } for( const std::string& url : image_urls ) { if( ! url.empty() ) str_image_urls.append( " \"" + url + "\"" ); } // 開いているタブのロック状態 std::string str_board_locked; std::string str_article_locked; std::string str_image_locked; for( const bool lock : board_locked ) { str_board_locked.append( lock ? " 1" : " 0" ); } for( const bool lock : article_locked ) { str_article_locked.append( lock ? " 1" : " 0" ); } for( const bool lock : image_locked ) { str_image_locked.append( lock ? " 1" : " 0" ); } // タブの切り替え履歴 std::string str_board_switchhistory; std::string str_article_switchhistory; for( const std::string& url : board_switchhistory ) { if( ! url.empty() ) str_board_switchhistory.append( " \"" + url + "\"" ); } for( const std::string& url : article_switchhistory ) { if( ! url.empty() ) str_article_switchhistory.append( " \"" + url + "\"" ); } // 保存内容作成 std::ostringstream oss; oss << "mode_pane = " << mode_pane << std::endl << "mode_online = " << mode_online << std::endl << "mode_login2ch = " << mode_login2ch << std::endl << "mode_loginbe = " << mode_loginbe << std::endl << "x = " << x_win_main << std::endl << "y = " << y_win_main << std::endl << "width = " << width_win_main << std::endl << "height = " << height_win_main << std::endl << "maximized = " << maximized_win_main << std::endl << "full_win_main = " << full_win_main << std::endl << "show_main_toolbar = " << show_main_toolbar << std::endl << "toolbar_pos = " << win_toolbar_pos << std::endl << "show_bbslist_toolbar = " << show_bbslist_toolbar << std::endl << "show_board_toolbar = " << show_board_toolbar << std::endl << "show_article_toolbar = " << show_article_toolbar << std::endl << "show_board_tab = " << show_board_tab << std::endl << "show_article_tab = " << show_article_tab << std::endl << "show_sidebar = " << win_show_sidebar << std::endl << "show_menubar = " << win_show_menubar << std::endl << "show_main_statbar = " << show_main_statbar << std::endl << "focused_admin = " << win_focused_admin << std::endl << "focused_admin_sidebar = " << win_focused_admin_sidebar << std::endl << "hpane_main_pos = " << win_hpane_main_pos << std::endl << "vpane_main_pos = " << win_vpane_main_pos << std::endl << "hpane_main_r_pos = " << win_hpane_main_r_pos << std::endl << "vpane_main_mes_pos = " << win_vpane_main_mes_pos << std::endl << "notebook_main_page = " << win_notebook_main_page << std::endl << "bbslist_page = " << win_bbslist_page << std::endl << "board_page = " << win_board_page << std::endl << "board_urls = " << str_board_urls << std::endl << "board_locked = " << str_board_locked << std::endl << "board_switchhistory = " << str_board_switchhistory << std::endl << "article_page = " << win_article_page << std::endl << "article_urls = " << str_article_urls << std::endl << "article_locked = " << str_article_locked << std::endl << "article_switchhistory = " << str_article_switchhistory << std::endl << "image_page = " << win_image_page << std::endl << "image_urls = " << str_image_urls << std::endl << "image_locked = " << str_image_locked << std::endl << "items_sidebar = " << items_sidebar_toolbar_str << std::endl << "items_main_toolbar = " << items_main_toolbar_str << std::endl << "items_article_toolbar = " << items_article_toolbar_str << std::endl << "items_search_toolbar = " << items_search_toolbar_str << std::endl << "items_board_toolbar = " << items_board_toolbar_str << std::endl << "items_msg_toolbar = " << items_msg_toolbar_str << std::endl << "items_board = " << items_board_col_str << std::endl << "items_board_menu = " << items_board_menu_str << std::endl << "items_article_menu = " << items_article_menu_str << std::endl << "col_mark = " << board_col_mark << std::endl << "col_id = " << board_col_id << std::endl << "col_board = " << board_col_board << std::endl << "col_subject = " << board_col_subject << std::endl << "col_number = " << board_col_number << std::endl << "col_load = " << board_col_load << std::endl << "col_new = " << board_col_new << std::endl << "col_since = " << board_col_since << std::endl << "col_write = " << board_col_write << std::endl << "col_access = " << board_col_access << std::endl << "col_speed = " << board_col_speed << std::endl << "col_diff = " << board_col_diff << std::endl << "col_since_time = " << board_col_since_time << std::endl << "col_write_time = " << board_col_write_time << std::endl << "col_access_time = " << board_col_access_time << std::endl << "embedded_img = " << embedded_img << std::endl << "x_win_img = " << x_win_img << std::endl << "y_win_img = " << y_win_img << std::endl << "width_win_img = " << width_win_img << std::endl << "height_win_img = " << height_win_img << std::endl << "embedded_mes = " << embedded_mes << std::endl << "close_mes = " << close_mes << std::endl << "x_win_mes = " << x_win_mes << std::endl << "y_win_mes = " << y_win_mes << std::endl << "width_win_mes = " << width_win_mes << std::endl << "height_win_mes = " << height_win_mes << std::endl << "dir_dat = " << dir_dat << std::endl << "img_dir_img_save = " << img_dir_img_save << std::endl << "dir_draft = " << dir_draft << std::endl << "img_fit_mode = " << img_fit_mode << std::endl << "dir_select_favorite = " << dir_select_favorite << std::endl; CACHE::save_rawdata( CACHE::path_session(), oss.str() ); #ifdef _DEBUG std::cout << "SESSION::save_session\n" << oss.str() << std::endl; #endif } // ブート中 bool SESSION::is_booting(){ return booting; } void SESSION::set_booting( const bool boot ){ booting = boot; } // 終了中 bool SESSION::is_quitting(){ return quitting; } void SESSION::set_quitting( const bool quit ){ quitting = quit; } // 入れ替えなどのタブ操作中 // ビューの再描画などを禁止する bool SESSION::is_tab_operating( const std::string& url_admin ) { if( url_admin == URL_ARTICLEADMIN ) return tab_operating_article; if( url_admin == URL_IMAGEADMIN ) return tab_operating_image; return false; } void SESSION::set_tab_operating( const std::string& url_admin, const bool operating ) { if( url_admin == URL_ARTICLEADMIN ) tab_operating_article = operating; if( url_admin == URL_IMAGEADMIN ) tab_operating_image = operating; } int SESSION::get_mode_pane() { return mode_pane; } void SESSION::set_mode_pane( const int mode ){ mode_pane = mode; } bool SESSION::is_online(){ return mode_online; } void SESSION::set_online( const bool mode ){ mode_online = mode; } bool SESSION::login2ch(){ return mode_login2ch; } void SESSION::set_login2ch( const bool login ){ mode_login2ch = login; } bool SESSION::loginbe(){ return mode_loginbe; } void SESSION::set_loginbe( const bool login ){ mode_loginbe = login; } bool SESSION::show_sidebar(){ return win_show_sidebar; } bool SESSION::show_menubar(){ return win_show_menubar; } void SESSION::set_show_menubar( const bool show ){ win_show_menubar = show; } bool SESSION::get_show_main_toolbar(){ return show_main_toolbar; } void SESSION::set_show_main_toolbar( const bool show ){ show_main_toolbar = show; } int SESSION::get_toolbar_pos(){ return win_toolbar_pos; } void SESSION::set_toolbar_pos( const int pos ){ win_toolbar_pos = pos; } bool SESSION::get_show_bbslist_toolbar(){ return show_bbslist_toolbar; } void SESSION::set_show_bbslist_toolbar( const bool show ){ show_bbslist_toolbar = show; } bool SESSION::get_show_board_toolbar(){ return show_board_toolbar; } void SESSION::set_show_board_toolbar( const bool show ){ show_board_toolbar = show; } bool SESSION::get_show_article_toolbar(){ return show_article_toolbar; } void SESSION::set_show_article_toolbar( const bool show ){ show_article_toolbar = show; } bool SESSION::get_show_board_tab(){ return show_board_tab; } void SESSION::set_show_board_tab( const bool show ){ show_board_tab = show; } bool SESSION::get_show_article_tab(){ return show_article_tab; } void SESSION::set_show_article_tab( const bool show ){ show_article_tab = show; } bool SESSION::get_show_main_statbar(){ return show_main_statbar; } void SESSION::set_show_main_statbar( const bool show ){ show_main_statbar = show; } int SESSION::focused_admin(){ return win_focused_admin; } void SESSION::set_focused_admin( const int admin ){ win_focused_admin = admin; } int SESSION::focused_admin_sidebar(){ return win_focused_admin_sidebar; } void SESSION::set_focused_admin_sidebar( const int admin ){ win_focused_admin_sidebar = admin; } // 各windowの座標 int SESSION::get_x_win_main(){ return x_win_main; } int SESSION::get_y_win_main(){ return y_win_main; } void SESSION::set_x_win_main( const int x ){ x_win_main = x; } void SESSION::set_y_win_main( const int y ){ y_win_main = y; } int SESSION::get_x_win_img(){ return x_win_img; } int SESSION::get_y_win_img(){ return y_win_img; } void SESSION::set_x_win_img( const int x ){ x_win_img = x; } void SESSION::set_y_win_img( const int y ){ y_win_img = y; } int SESSION::get_x_win_mes(){ return x_win_mes; } int SESSION::get_y_win_mes(){ return y_win_mes; } void SESSION::set_x_win_mes( const int x ){ x_win_mes = x; } void SESSION::set_y_win_mes( const int y ){ y_win_mes = y; } // 各windowのサイズ int SESSION::get_width_win_main(){ return width_win_main; } int SESSION::get_height_win_main(){ return height_win_main; } void SESSION::set_width_win_main( const int width ){ width_win_main = width; } void SESSION::set_height_win_main( const int height ){ height_win_main = height; } int SESSION::get_width_win_img(){ return width_win_img; } int SESSION::get_height_win_img(){ return height_win_img; } void SESSION::set_width_win_img( const int width ){ width_win_img = width; } void SESSION::set_height_win_img( const int height ){ height_win_img = height; } int SESSION::get_width_win_mes(){ return width_win_mes; } int SESSION::get_height_win_mes(){ return height_win_mes; } void SESSION::set_width_win_mes( const int width ){ width_win_mes = width; } void SESSION::set_height_win_mes( const int height ){ height_win_mes = height; } // 各window がフォーカスされているか bool SESSION::is_focus_win_main(){ return focus_win_main; } void SESSION::set_focus_win_main( const bool set ){ focus_win_main = set; } bool SESSION::is_focus_win_img(){ return focus_win_img; } void SESSION::set_focus_win_img( const bool set ){ focus_win_img = set; } bool SESSION::is_focus_win_mes(){ return focus_win_mes; } void SESSION::set_focus_win_mes( const bool set ){ focus_win_mes = set; } // 各window が最大化されているか bool SESSION::is_maximized_win_main(){ return maximized_win_main; } void SESSION::set_maximized_win_main( const bool set ){ maximized_win_main = set; } bool SESSION::is_maximized_win_img(){ return maximized_win_img; } void SESSION::set_maximized_win_img( const bool set ){ maximized_win_img = set; } bool SESSION::is_maximized_win_mes(){ return maximized_win_mes; } void SESSION::set_maximized_win_mes( const bool set ){ maximized_win_mes = set; } // 各window が最小化されているか bool SESSION::is_iconified_win_main(){ return iconified_win_main; } void SESSION::set_iconified_win_main( const bool set ){ iconified_win_main = set; } bool SESSION::is_iconified_win_img(){ return iconified_win_img; } void SESSION::set_iconified_win_img( const bool set ){ iconified_win_img = set; } bool SESSION::is_iconified_win_mes(){ return iconified_win_mes; } void SESSION::set_iconified_win_mes( const bool set ){ iconified_win_mes = set; } // 各window が画面に表示されているか bool SESSION::is_shown_win_main(){ return shown_win_main; } void SESSION::set_shown_win_main( const bool set ){ shown_win_main = set; } bool SESSION::is_shown_win_img(){ return shown_win_img; } void SESSION::set_shown_win_img( const bool set ){ shown_win_img = set; } bool SESSION::is_shown_win_mes(){ return shown_win_mes; } void SESSION::set_shown_win_mes( const bool set ){ shown_win_mes = set; } // windowがフルスクリーンか bool SESSION::is_full_win_main(){ return full_win_main; } void SESSION::set_full_win_main( const bool set ){ full_win_main = set; } // ダイアログ表示中 bool SESSION::is_dialog_shown(){ return dialog_shown; } void SESSION::set_dialog_shown( const bool set ){ dialog_shown = set; } void SESSION::set_show_sidebar( const bool showurl ){ win_show_sidebar = showurl; } // メインウィンドウのペインの敷居の位置 int SESSION::hpane_main_pos(){ return win_hpane_main_pos; } int SESSION::vpane_main_pos(){ return win_vpane_main_pos; } int SESSION::hpane_main_r_pos(){ return win_hpane_main_r_pos; } int SESSION::vpane_main_mes_pos(){ return win_vpane_main_mes_pos; } void SESSION::set_hpane_main_pos( const int pos ){ win_hpane_main_pos = pos; } void SESSION::set_vpane_main_pos( const int pos ){ win_vpane_main_pos = pos; } void SESSION::set_hpane_main_r_pos( const int pos ){ win_hpane_main_r_pos = pos; } void SESSION::set_vpane_main_mes_pos( const int pos ){ win_vpane_main_mes_pos = pos; } // メインnotebookのページ番号 int SESSION::notebook_main_page(){ return win_notebook_main_page; } void SESSION::set_notebook_main_page( const int page ){ win_notebook_main_page = page; } // bbslistの開いてるページ番号 int SESSION::bbslist_page(){ return win_bbslist_page; } void SESSION::set_bbslist_page( const int page ){ win_bbslist_page = page; } // 前回閉じたときに開いていたboardのページ番号とURL int SESSION::board_page(){ return win_board_page; } void SESSION::set_board_page( const int page ){ win_board_page = page; } const std::list< std::string >& SESSION::get_board_URLs(){ return board_urls; } void SESSION::set_board_URLs( const std::list< std::string >& urls ){ board_urls = urls; } // スレ一覧のロック状態 const std::list< bool >& SESSION::get_board_locked(){ return board_locked; } void SESSION::set_board_locked( const std::list< bool >& locked ){ board_locked = locked; } // スレ一覧の切り替え履歴 const std::list< std::string >& SESSION::get_board_switchhistory(){ return board_switchhistory; } void SESSION::set_board_switchhistory( const std::list< std::string >& hist ){ board_switchhistory = hist; } // 前回閉じたときに開いていたスレタブのページ番号とURL int SESSION::article_page(){ return win_article_page; } void SESSION::set_article_page( const int page ){ win_article_page = page; } const std::list< std::string >& SESSION::get_article_URLs(){ return article_urls; } void SESSION::set_article_URLs( const std::list< std::string >& urls ){ article_urls = urls; } // スレタブのロック状態 const std::list< bool >& SESSION::get_article_locked(){ return article_locked; } void SESSION::set_article_locked( const std::list< bool >& locked ){ article_locked = locked; } // スレタブの切り替え履歴 const std::list< std::string >& SESSION::get_article_switchhistory(){ return article_switchhistory; } void SESSION::set_article_switchhistory( const std::list< std::string >& hist ){ article_switchhistory = hist; } // 前回閉じたときに開いていたimageのページ番号とURL int SESSION::image_page(){ return win_image_page; } void SESSION::set_image_page( const int page ){ win_image_page = page; } const std::list< std::string >& SESSION::image_URLs(){ return image_urls; } void SESSION::set_image_URLs( const std::list< std::string >& urls ){ image_urls = urls; } // 画像タブのロック状態 const std::list< bool >& SESSION::get_image_locked(){ return image_locked; } void SESSION::set_image_locked( const std::list< bool >& locked ){ image_locked = locked; } // サイドバーのツールバーの項目 const std::string& SESSION::get_items_sidebar_toolbar_str(){ return items_sidebar_toolbar_str; } std::string SESSION::get_items_sidebar_toolbar_default_str() { return ITEM_NAME_SEARCHBOX + std::string ( " " ) + ITEM_NAME_SEARCH_NEXT + std::string ( " " ) + ITEM_NAME_SEARCH_PREV; } void SESSION::set_items_sidebar_toolbar_str( const std::string& items_str ) { items_sidebar_toolbar_str = items_str; items_sidebar_toolbar = parse_items( items_sidebar_toolbar_str ); } int SESSION::get_item_sidebar_toolbar( const int num ){ return items_sidebar_toolbar[ num ]; } // メインツールバーの項目 const std::string& SESSION::get_items_main_toolbar_str(){ return items_main_toolbar_str; } std::string SESSION::get_items_main_toolbar_default_str() { return ITEM_NAME_BBSLISTVIEW + std::string ( " " ) + ITEM_NAME_FAVORITEVIEW + std::string ( " " ) + ITEM_NAME_BOARDVIEW + std::string ( " " ) + ITEM_NAME_ARTICLEVIEW + std::string ( " " ) + ITEM_NAME_IMAGEVIEW + std::string ( " " ) + ITEM_NAME_SEPARATOR + std::string ( " " ) + ITEM_NAME_URL + std::string ( " " ) + ITEM_NAME_GO; } void SESSION::set_items_main_toolbar_str( const std::string& items_str ) { items_main_toolbar_str = items_str; items_main_toolbar = parse_items( items_main_toolbar_str ); } int SESSION::get_item_main_toolbar( const int num ){ return items_main_toolbar[ num ]; } // スレビューのツールバーの項目 const std::string& SESSION::get_items_article_toolbar_str(){ return items_article_toolbar_str; } std::string SESSION::get_items_article_toolbar_default_str() { return ITEM_NAME_WRITEMSG + std::string ( " " ) + ITEM_NAME_OPENBOARD + std::string ( " " ) + ITEM_NAME_NAME + std::string ( " " ) + ITEM_NAME_SEARCH + std::string ( " " ) + ITEM_NAME_RELOAD + std::string ( " " ) + ITEM_NAME_STOPLOADING + std::string ( " " ) + ITEM_NAME_APPENDFAVORITE + std::string ( " " ) + ITEM_NAME_DELETE + std::string ( " " ) + ITEM_NAME_QUIT; } void SESSION::set_items_article_toolbar_str( const std::string& items_str ) { items_article_toolbar_str = items_str; items_article_toolbar = parse_items( items_article_toolbar_str ); } int SESSION::get_item_article_toolbar( const int num ){ return items_article_toolbar[ num ]; } // 検索ビューのツールバーの項目 const std::string& SESSION::get_items_search_toolbar_str(){ return items_search_toolbar_str; } std::string SESSION::get_items_search_toolbar_default_str() { return ITEM_NAME_NAME + std::string ( " " ) + ITEM_NAME_SEARCH + std::string ( " " ) + ITEM_NAME_RELOAD + std::string ( " " ) + ITEM_NAME_STOPLOADING + std::string ( " " ) + ITEM_NAME_QUIT; } void SESSION::set_items_search_toolbar_str( const std::string& items_str ) { items_search_toolbar_str = items_str; items_search_toolbar = parse_items( items_search_toolbar_str ); } int SESSION::get_item_search_toolbar( const int num ){ return items_search_toolbar[ num ]; } // スレ一覧のツールバー項目 const std::string& SESSION::get_items_board_toolbar_str(){ return items_board_toolbar_str; } std::string SESSION::get_items_board_toolbar_default_str() { return ITEM_NAME_NEWARTICLE + std::string ( " " ) + ITEM_NAME_SEARCHBOX + std::string ( " " ) + ITEM_NAME_SEARCH_NEXT + std::string ( " " ) + ITEM_NAME_SEARCH_PREV + std::string ( " " ) + ITEM_NAME_RELOAD + std::string ( " " ) + ITEM_NAME_STOPLOADING + std::string ( " " ) + ITEM_NAME_APPENDFAVORITE + std::string ( " " ) + ITEM_NAME_DELETE + std::string ( " " ) + ITEM_NAME_QUIT; } void SESSION::set_items_board_toolbar_str( const std::string& items_str ) { items_board_toolbar_str = items_str; items_board_toolbar = parse_items( items_board_toolbar_str ); } int SESSION::get_item_board_toolbar( const int num ){ return items_board_toolbar[ num ]; } // 書き込みビューのツールバー項目 const std::string& SESSION::get_items_msg_toolbar_str(){ return items_msg_toolbar_str; } std::string SESSION::get_items_msg_toolbar_default_str() { return ITEM_NAME_PREVIEW + std::string ( " " ) + ITEM_NAME_WRITEMSG+ std::string ( " " ) + ITEM_NAME_NAME + std::string ( " " ) + ITEM_NAME_UNDO + std::string ( " " ) + ITEM_NAME_INSERTTEXT + std::string ( " " ) + ITEM_NAME_LOCK_MESSAGE + std::string ( " " ) + ITEM_NAME_QUIT; } void SESSION::set_items_msg_toolbar_str( const std::string& items_str ) { items_msg_toolbar_str = items_str; items_msg_toolbar = parse_items( items_msg_toolbar_str ); } int SESSION::get_item_msg_toolbar( const int num ){ return items_msg_toolbar[ num ]; } // スレ一覧の列項目 const std::string& SESSION::get_items_board_col_str(){ return items_board_col_str; } std::string SESSION::get_items_board_col_default_str() { return ITEM_NAME_MARK + std::string ( " " ) + ITEM_NAME_ID + std::string ( " " ) + ITEM_NAME_NAME + std::string ( " " ) + ITEM_NAME_RES + std::string ( " " ) + ITEM_NAME_LOAD + std::string ( " " ) + ITEM_NAME_NEW + std::string ( " " ) + ITEM_NAME_SINCE + std::string ( " " ) + ITEM_NAME_LASTWRITE + std::string ( " " ) + ITEM_NAME_SPEED; } void SESSION::set_items_board_col_str( const std::string& items_str ) { items_board_col_str = items_str; items_board_col = parse_items( items_board_col_str ); } int SESSION::get_item_board_col( const int num ){ return items_board_col[ num ]; } // スレ一覧のコンテキストメニュー項目 const std::string& SESSION::get_items_board_menu_str(){ return items_board_menu_str; } std::string SESSION::get_items_board_menu_default_str() { return ITEM_NAME_BOOKMARK + std::string ( " " ) + ITEM_NAME_SEPARATOR + std::string ( " " ) + ITEM_NAME_OPENARTICLETAB + std::string ( " " ) + ITEM_NAME_OPEN_BROWSER + std::string ( " " ) + ITEM_NAME_SEPARATOR + std::string ( " " ) + ITEM_NAME_COPY_URL + std::string ( " " ) + ITEM_NAME_COPY_TITLE_URL_THREAD + std::string ( " " ) + ITEM_NAME_SEPARATOR + std::string ( " " ) + ITEM_NAME_ABONE_ARTICLE + std::string ( " " ) + ITEM_NAME_SEPARATOR + std::string ( " " ) + ITEM_NAME_DELETE + std::string ( " " ) + ITEM_NAME_SEPARATOR + std::string ( " " ) + ITEM_NAME_ETC + std::string ( " " ) + ITEM_NAME_SEPARATOR + std::string ( " " ) + ITEM_NAME_PREF_THREAD + std::string ( " " ) + ITEM_NAME_PREF_BOARD; } void SESSION::set_items_board_menu_str( const std::string& items_str ) { items_board_menu_str = items_str; items_board_menu = parse_items( items_board_menu_str ); } int SESSION::get_item_board_menu( const int num ){ return items_board_menu[ num ]; } // スレビューのコンテキストメニュー項目 const std::string& SESSION::get_items_article_menu_str(){ return items_article_menu_str; } std::string SESSION::get_items_article_menu_default_str() { return ITEM_NAME_DRAWOUT + std::string ( " " ) + ITEM_NAME_GO + std::string ( " " ) + ITEM_NAME_SEARCH + std::string ( " " ) + ITEM_NAME_NGWORD + std::string ( " " ) + ITEM_NAME_SEPARATOR + std::string ( " " ) + ITEM_NAME_QUOTE_SELECTION + std::string ( " " ) + ITEM_NAME_SEPARATOR + std::string ( " " ) + ITEM_NAME_OPEN_BROWSER + std::string ( " " ) + ITEM_NAME_USER_COMMAND + std::string ( " " ) + ITEM_NAME_SEPARATOR + std::string ( " " ) + ITEM_NAME_COPY_URL + std::string ( " " ) + ITEM_NAME_COPY + std::string ( " " ) + ITEM_NAME_SEPARATOR + std::string ( " " ) + ITEM_NAME_ETC + std::string ( " " ) + ITEM_NAME_SEPARATOR + std::string ( " " ) + ITEM_NAME_PREF_THREAD; } void SESSION::set_items_article_menu_str( const std::string& items_str ) { items_article_menu_str = items_str; items_article_menu = parse_items( items_article_menu_str ); } int SESSION::get_item_article_menu( const int num ){ return items_article_menu[ num ]; } // board ビューの列幅 int SESSION::col_mark(){ return board_col_mark; } int SESSION::col_id(){ return board_col_id; } int SESSION::col_board(){ return board_col_board; } int SESSION::col_subject(){ return board_col_subject; } int SESSION::col_number(){ return board_col_number; } int SESSION::col_load(){ return board_col_load; } int SESSION::col_new(){ return board_col_new; } int SESSION::col_since(){ return board_col_since; } int SESSION::col_write(){ return board_col_write; } int SESSION::col_access(){ return board_col_access; } int SESSION::col_speed(){ return board_col_speed; } int SESSION::col_diff(){ return board_col_diff; } // スレ一覧の since の表示モード int SESSION::get_col_since_time() { return board_col_since_time; } void SESSION::set_col_since_time( const int mode ){ board_col_since_time = mode; } // スレ一覧の 最終書込 の表示モード int SESSION::get_col_write_time() { return board_col_write_time; } void SESSION::set_col_write_time( const int mode ){ board_col_write_time = mode; } // スレ一覧の 最終取得 の表示モード int SESSION::get_col_access_time() { return board_col_access_time; } void SESSION::set_col_access_time( const int mode ){ board_col_access_time = mode; } // 現在開いているサイドバーのページ int SESSION::get_sidebar_current_page() { return BBSLIST::get_admin()->get_current_page(); } // 現在開いているサイドバーのurl std::string SESSION::get_sidebar_current_url() { return BBSLIST::get_admin()->get_current_url(); } void SESSION::set_col_mark( const int width ){ board_col_mark = width; } void SESSION::set_col_id( const int width ){ board_col_id = width; } void SESSION::set_col_board( const int width ){ board_col_board = width; } void SESSION::set_col_subject( const int width ){ board_col_subject = width; } void SESSION::set_col_number( const int width ){ board_col_number = width; } void SESSION::set_col_load( const int width ){ board_col_load = width; } void SESSION::set_col_new( const int width ){ board_col_new = width; } void SESSION::set_col_since( const int width ){ board_col_since = width; } void SESSION::set_col_write( const int width ){ board_col_write = width; } void SESSION::set_col_access( const int width ){ board_col_access = width; } void SESSION::set_col_speed( const int width ){ board_col_speed = width; } void SESSION::set_col_diff( const int width ){ board_col_diff = width; } // 現在開いているarticle の ARTICLE::DrawAreaBase ARTICLE::DrawAreaBase* SESSION::get_base_drawarea() { ARTICLE::ArticleViewBase* base_view = nullptr; base_view = dynamic_cast< ARTICLE::ArticleViewBase* >( ARTICLE::get_admin()->get_current_view() ); if( base_view == nullptr ) return nullptr; ARTICLE::DrawAreaBase* base_drawarea = nullptr; base_drawarea = base_view->drawarea(); return base_drawarea; } // 現在開いているarticle のurl std::string SESSION::get_article_current_url() { return ARTICLE::get_admin()->get_current_url(); } // 埋め込みimage使用 bool SESSION::get_embedded_img(){ return embedded_img; } void SESSION::set_embedded_img( const bool set ){ embedded_img = set; } // 埋め込みmessageを使用 bool SESSION::get_embedded_mes(){ return embedded_mes; } void SESSION::set_embedded_mes( const bool set ){ embedded_mes = set; } // 書き込み後にmessageを閉じる bool SESSION::get_close_mes(){ return close_mes; } void SESSION::set_close_mes( const bool set ){ close_mes = set; } // 最後にdatを読み書きしたディレクトリ const std::string& SESSION::get_dir_dat(){ return dir_dat; } void SESSION::set_dir_dat( const std::string& dir ){ dir_dat = dir; } // 最後に画像を保存したディレクトリ const std::string& SESSION::dir_img_save(){ return img_dir_img_save; } void SESSION::set_dir_img_save( const std::string& dir ){ img_dir_img_save = dir; } // 下書きファイルのディレクトリ const std::string& SESSION::get_dir_draft(){ return dir_draft; } void SESSION::set_dir_draft( const std::string& dir ){ dir_draft = dir; } // ポップアップメニュー表示中 bool SESSION::is_popupmenu_shown(){ return popupmenu_shown; } void SESSION::set_popupmenu_shown( const bool shown ){ popupmenu_shown = shown; } // JD終了時に削除するスレのリスト // 実況などしたスレは削除する。 Core::~Core()を参照 const std::vector< std::string >& SESSION::get_delete_list() { return delete_list; } void SESSION::append_delete_list( const std::string& url ) { if( std::find( delete_list.cbegin(), delete_list.cend(), url ) != delete_list.cend() ) { return; } delete_list.push_back( url ); #ifdef _DEBUG std::cout << "SESSION::append_delete_list urls == " << delete_list.size() << " url = " << url << std::endl; #endif } void SESSION::remove_delete_list( const std::string& url ) { if( delete_list.empty() ) return; const auto it = std::find( delete_list.cbegin(), delete_list.cend(), url ); if( it != delete_list.cend() ) delete_list.erase( it ); #ifdef _DEBUG std::cout << "SESSION::remove_delete_list urls == " << delete_list.size() << " url = " << url << std::endl; #endif } // 実況実行中のスレか bool SESSION::is_live( const std::string& url ) { #ifdef _DEBUG std::cout << "SESSION::is_live live_urls == " << live_urls.size() << " url = " << url << std::endl; #endif if( live_urls.empty() ) return false; const auto it = std::find( live_urls.cbegin(), live_urls.cend(), url ); return it != live_urls.cend(); } void SESSION::append_live( const std::string& url ) { if( ! is_live( url ) ) live_urls.push_back( url ); #ifdef _DEBUG std::cout << "SESSION::append_live live_urls == " << live_urls.size() << " url = " << url << std::endl; #endif } void SESSION::remove_live( const std::string& url ) { if( live_urls.empty() ) return; const auto it = std::find( live_urls.cbegin(), live_urls.cend(), url ); if( it != live_urls.cend() ) live_urls.erase( it ); #ifdef _DEBUG std::cout << "SESSION::remove_live live_urls == " << live_urls.size() << " url = " << url << std::endl; #endif } // 画像のfitモード int SESSION::get_img_fit_mode() { return img_fit_mode; } void SESSION::toggle_img_fit_mode() { if( img_fit_mode == IMG_FIT_NORMAL ) img_fit_mode = IMG_FIT_WIDTH; else img_fit_mode = IMG_FIT_NORMAL; } // お気に入り挿入ダイアログで最後に保存したディレクトリ名 const std::string& SESSION::get_dir_select_favorite() { return dir_select_favorite; } void SESSION::set_dir_select_favorite( const std::string& dir ) { dir_select_favorite = dir; } // 各履歴を取得 void SESSION::get_history( const std::string& url, CORE::DATA_INFO_LIST& info_list ) { return BBSLIST::get_admin()->get_history( url, info_list ); } // サイドバーの指定したidのディレクトリに含まれるスレのアドレスを取得 void SESSION::get_sidebar_threads( const std::string& url, const int dirid, std::vector< std::string >& list_url ) { BBSLIST::get_admin()->get_threads( url, dirid, list_url ); } // サイドバーの指定したidのディレクトリの名前を取得 std::string SESSION::get_sidebar_dirname( const std::string& url, const int dirid ) { return BBSLIST::get_admin()->get_dirname( url, dirid ); } jdim-0.7.0/src/session.h000066400000000000000000000355651417047150700151100ustar00rootroot00000000000000// ライセンス: GPL2 // // 座標などのウィンドウ情報とかのセッション情報 // #ifndef _SESSION_H #define _SESSION_H #include "type.h" #include "data_info.h" #include #include #include namespace ARTICLE { class DrawAreaBase; } namespace SESSION { // focused_admin の値。どこにフォーカスしているか // Core::slot_focus_in_event, Core::slot_focus_out_event などを参照 enum { FOCUS_SIDEBAR = 0, FOCUS_BOARD, FOCUS_ARTICLE, FOCUS_IMAGE, FOCUS_MESSAGE, FOCUS_NOT, // どこもフォーカスされていない FOCUS_NUM }; // ペーンモード enum { MODE_2PANE = 0, MODE_3PANE, MODE_V3PANE, MODE_PANE_NUM }; // メインウィンドウの右ペーンに表示中のnotebook enum { PAGE_ARTICLE = 0, PAGE_IMAGE, PAGE_BOARD, PAGE_NUM }; // メインツールバーの位置 enum { TOOLBAR_POS_NORMAL = 0, // メニューバーの下に表示 TOOLBAR_POS_RIGHT, // サイドバーの右に表示 TOOLBAR_POS_NUM }; // 画像のfitモード enum { IMG_FIT_NORMAL = 0, // 縦と横で小さい方をウィンドウに合わせる IMG_FIT_WIDTH, // 常に横をウィンドウに合わせる IMG_FIT_NUM }; void init_session(); void save_session(); // ブート中 bool is_booting(); void set_booting( const bool boot ); // 終了中 bool is_quitting(); void set_quitting( const bool quit ); // 入れ替えなどのタブ操作中 // ビューの再描画などを禁止する bool is_tab_operating( const std::string& url_admin ); void set_tab_operating( const std::string& url_admin, const bool operating ); int get_mode_pane(); void set_mode_pane( const int mode ); bool is_online(); void set_online( const bool mode ); // 2chログイン中 bool login2ch(); void set_login2ch( const bool login ); // BEログイン中 bool loginbe(); void set_loginbe( const bool login ); bool loginp2() = delete; // Removed in v0.3.0 (2020-05) void set_loginp2( const bool login ) = delete; // Removed in v0.3.0 (2020-05) // サイドバー表示中 bool show_sidebar(); void set_show_sidebar( const bool showbar ); // メニューバー bool show_menubar(); void set_show_menubar( const bool show ); // メインツールバー表示 bool get_show_main_toolbar(); void set_show_main_toolbar( const bool show ); // メインツールバー位置 int get_toolbar_pos(); void set_toolbar_pos( const int pos ); // 板一覧のツールバー表示 bool get_show_bbslist_toolbar(); void set_show_bbslist_toolbar( const bool show ); // スレ一覧のツールバー表示 bool get_show_board_toolbar(); void set_show_board_toolbar( const bool show ); // スレビューのツールバー表示 bool get_show_article_toolbar(); void set_show_article_toolbar( const bool show ); // スレ一覧のタブ表示 bool get_show_board_tab(); void set_show_board_tab( const bool show ); // スレビューのタブ bool get_show_article_tab(); void set_show_article_tab( const bool show ); // メインステータスバー表示 bool get_show_main_statbar(); void set_show_main_statbar( const bool show ); // フォーカスされているadmin int focused_admin(); void set_focused_admin( const int admin ); // 各windowの座標 int get_x_win_main(); // メインウィンドウ int get_y_win_main(); void set_x_win_main( const int x ); void set_y_win_main( const int y ); int get_x_win_img(); // 画像ウィンドウ int get_y_win_img(); void set_x_win_img( const int x ); void set_y_win_img( const int y ); int get_x_win_mes(); // 書き込みウィンドウ int get_y_win_mes(); void set_x_win_mes( const int x ); void set_y_win_mes( const int y ); // 各windowのサイズ int get_width_win_main(); // メインウィンドウ int get_height_win_main(); void set_width_win_main( const int width ); void set_height_win_main( const int height ); int get_width_win_img(); // 画像ウィンドウ int get_height_win_img(); void set_width_win_img( const int width ); void set_height_win_img( const int height ); int get_width_win_mes(); // 書き込みウィンドウ int get_height_win_mes(); void set_width_win_mes( const int width ); void set_height_win_mes( const int height ); // 各window がフォーカスされているか bool is_focus_win_main(); // メインウィンドウ void set_focus_win_main( const bool set ); bool is_focus_win_img(); // 画像ウィンドウ void set_focus_win_img( const bool set ); bool is_focus_win_mes(); // 書き込みウィンドウ void set_focus_win_mes( const bool set ); // 各window が最大化されているか bool is_maximized_win_main(); // メインウィンドウ void set_maximized_win_main( const bool maximized ); bool is_maximized_win_img(); // 画像ウィンドウ void set_maximized_win_img( const bool set ); bool is_maximized_win_mes(); // 書き込みウィンドウ void set_maximized_win_mes( const bool maximized ); // 各window が最小化されているか bool is_iconified_win_main(); // メインウィンドウ void set_iconified_win_main( const bool set ); bool is_iconified_win_img(); // 画像ウィンドウ void set_iconified_win_img( const bool set ); bool is_iconified_win_mes(); // 書き込みウィンドウ void set_iconified_win_mes( const bool set ); // 各window が画面に表示されているか bool is_shown_win_main(); // メインウィンドウ void set_shown_win_main( const bool set ); bool is_shown_win_img(); // 画像ウィンドウ void set_shown_win_img( const bool set ); bool is_shown_win_mes(); // 書き込みウィンドウ void set_shown_win_mes( const bool set ); // windowがフルスクリーンか bool is_full_win_main(); // メインウィンドウ void set_full_win_main( const bool set ); // ダイアログ表示中 bool is_dialog_shown(); void set_dialog_shown( const bool set ); // サイドバーを閉じる前にフォーカスされていたadmin int focused_admin_sidebar(); void set_focused_admin_sidebar( const int admin ); /// メインウィンドウのペインの敷居の位置 int hpane_main_pos(); int vpane_main_pos(); int hpane_main_r_pos(); int vpane_main_mes_pos(); void set_hpane_main_pos( const int pos ); void set_vpane_main_pos( const int pos ); void set_hpane_main_r_pos( const int pos ); void set_vpane_main_mes_pos( const int pos ); // 前回閉じたときに開いていたメインnotebookのページ番号 int notebook_main_page(); void set_notebook_main_page( const int page ); // 前回閉じたときに開いていたbbslistの開いてるページ番号 int bbslist_page(); void set_bbslist_page( const int page ); // 前回閉じたときに開いていたスレ一覧のページ番号とURL int board_page(); void set_board_page( const int page ); const std::list< std::string >& get_board_URLs(); void set_board_URLs( const std::list< std::string >& urls ); // スレ一覧のロック状態 const std::list< bool >& get_board_locked(); void set_board_locked( const std::list< bool >& locked ); // スレ一覧の切り替え履歴 const std::list< std::string >& get_board_switchhistory(); void set_board_switchhistory( const std::list< std::string >& hist ); // 前回閉じたときに開いていたスレタブのページ番号とURL int article_page(); void set_article_page( const int page ); const std::list< std::string >& get_article_URLs(); void set_article_URLs( const std::list< std::string >& urls ); // スレタブのロック状態 const std::list< bool >& get_article_locked(); void set_article_locked( const std::list< bool >& locked ); // スレタブの切り替え履歴 const std::list< std::string >& get_article_switchhistory(); void set_article_switchhistory( const std::list< std::string >& hist ); // 前回閉じたときに開いていたimageのページ番号とURL int image_page(); void set_image_page( const int page ); const std::list< std::string >& image_URLs(); void set_image_URLs( const std::list< std::string >& urls ); // 画像タブのロック状態 const std::list< bool >& get_image_locked(); void set_image_locked( const std::list< bool >& locked ); // 現在開いているサイドバーのページ int get_sidebar_current_page(); // 現在開いているサイドバーのurl std::string get_sidebar_current_url(); // ツールバー等の項目名 -> ID 変換 int parse_item( const std::string& item_name ); // サイドバーのツールバー項目 const std::string& get_items_sidebar_toolbar_str(); std::string get_items_sidebar_toolbar_default_str(); void set_items_sidebar_toolbar_str( const std::string& items ); int get_item_sidebar_toolbar( const int num ); // メインツールバーの項目 const std::string& get_items_main_toolbar_str(); std::string get_items_main_toolbar_default_str(); void set_items_main_toolbar_str( const std::string& items_str ); int get_item_main_toolbar( const int num ); // スレビューのツールバーの項目 const std::string& get_items_article_toolbar_str(); std::string get_items_article_toolbar_default_str(); void set_items_article_toolbar_str( const std::string& items_str ); int get_item_article_toolbar( const int num ); // 検索ビューのツールバーの項目 const std::string& get_items_search_toolbar_str(); std::string get_items_search_toolbar_default_str(); void set_items_search_toolbar_str( const std::string& items_str ); int get_item_search_toolbar( const int num ); // スレ一覧のツールバー項目 const std::string& get_items_board_toolbar_str(); std::string get_items_board_toolbar_default_str(); void set_items_board_toolbar_str( const std::string& items ); int get_item_board_toolbar( const int num ); // 書き込みビューのツールバー項目 const std::string& get_items_msg_toolbar_str(); std::string get_items_msg_toolbar_default_str(); void set_items_msg_toolbar_str( const std::string& items ); int get_item_msg_toolbar( const int num ); // スレ一覧の列項目 const std::string& get_items_board_col_str(); std::string get_items_board_col_default_str(); void set_items_board_col_str( const std::string& items ); int get_item_board_col( const int num ); // スレ一覧のコンテキストメニュー項目 const std::string& get_items_board_menu_str(); std::string get_items_board_menu_default_str(); void set_items_board_menu_str( const std::string& items_str ); int get_item_board_menu( const int num ); // スレビューのコンテキストメニュー項目 const std::string& get_items_article_menu_str(); std::string get_items_article_menu_default_str(); void set_items_article_menu_str( const std::string& items_str ); int get_item_article_menu( const int num ); // スレ一覧の列幅 int col_mark(); int col_id(); int col_board(); int col_subject(); int col_number(); int col_load(); int col_new(); int col_since(); int col_write(); int col_access(); int col_speed(); int col_diff(); void set_col_mark( const int width ); void set_col_id( const int width ); void set_col_board( const int width ); void set_col_subject( const int width ); void set_col_number( const int width ); void set_col_load( const int width ); void set_col_new( const int width ); void set_col_since( const int width ); void set_col_write( const int width ); void set_col_access( const int width ); void set_col_speed( const int width ); void set_col_diff( const int width ); // スレ一覧の since の表示モード int get_col_since_time(); void set_col_since_time( const int mode ); // スレ一覧の 最終書込 の表示モード int get_col_write_time(); void set_col_write_time( const int mode ); // スレ一覧の 最終取得 の表示モード int get_col_access_time(); void set_col_access_time( const int mode ); // 現在開いているarticle の ARTICLE::DrawAreaBase ARTICLE::DrawAreaBase* get_base_drawarea(); // 現在開いているarticle のurl std::string get_article_current_url(); // 埋め込みimage使用 bool get_embedded_img(); void set_embedded_img( const bool set ); // 埋め込みmessageを使用 bool get_embedded_mes(); void set_embedded_mes( const bool set ); // 書き込み後にmessageを閉じる bool get_close_mes(); void set_close_mes( const bool set ); // 最後にdatを読み書きしたディレクトリ const std::string& get_dir_dat(); void set_dir_dat( const std::string& dir ); // 最後に画像を保存したディレクトリ const std::string& dir_img_save(); void set_dir_img_save( const std::string& dir ); // 下書きファイルのディレクトリ const std::string& get_dir_draft(); void set_dir_draft( const std::string& dir ); // ポップアップメニュー表示中 bool is_popupmenu_shown(); void set_popupmenu_shown( const bool shown ); // JD終了時に削除するスレのリスト const std::vector< std::string >& get_delete_list(); void append_delete_list( const std::string& url ); void remove_delete_list( const std::string& url ); // 実況実行中のスレ bool is_live( const std::string& url ); void append_live( const std::string& url ); void remove_live( const std::string& url ); // 画像のfitモード int get_img_fit_mode(); void toggle_img_fit_mode(); // お気に入り挿入ダイアログで最後に保存したディレクトリ名 const std::string& get_dir_select_favorite(); void set_dir_select_favorite( const std::string& dir ); // 各履歴を取得 void get_history( const std::string& url, CORE::DATA_INFO_LIST& info_list ); // サイドバーの指定したidのディレクトリに含まれるスレのアドレスを取得 void get_sidebar_threads( const std::string& url, const int dirid, std::vector< std::string >& list_url ); // サイドバーの指定したidのディレクトリの名前を取得 std::string get_sidebar_dirname( const std::string& url, const int dirid ); } #endif jdim-0.7.0/src/setupwizard.cpp000066400000000000000000000262651417047150700163360ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "setupwizard.h" #include "prefdiagfactory.h" #include "fontid.h" #include "session.h" #include "config/globalconf.h" #include "icons/iconmanager.h" using namespace CORE; enum { SPACING_SIZE = 8 }; PageStart::PageStart() : Gtk::Grid() , m_icon( ICON::get_icon_manager()->get_icon( ICON::JD48 ) ) , m_label( "1/5.JDim セットアップ開始", Gtk::ALIGN_START ) , m_label2( "JDimセットアップウィザードへようこそ\n\n" "このウィザードでネットワークとフォント等の設定をおこないます\n\n" "設定を始めるには[次へ]を押してください", Gtk::ALIGN_START ) { set_column_spacing( SPACING_SIZE ); set_row_spacing( SPACING_SIZE ); // Gtk::Grid::attach( child, column, row, width, height ) attach( m_icon, 0, 0, 1, 1 ); attach( m_label, 1, 0, 1, 1 ); attach( m_label2, 0, 1, 2, 1 ); m_label.set_hexpand( true ); } ///////////////////////////////////////////// PageNet::PageNet( Gtk::Window* parent ) : Gtk::Grid() , m_parent{ parent } , m_icon( ICON::get_icon_manager()->get_icon( ICON::JD48 ) ) , m_label( "2/5.ネットワークの設定をします", Gtk::ALIGN_START ) , m_proxy( "プロキシ設定(_P)", true ) , m_browser( "ブラウザ設定(_W)", true ) , m_frame( "ブラウザ起動コマンド" ) // フレーム , m_label_browser( CONFIG::get_command_openurl(), Gtk::ALIGN_START ) { m_label_browser.property_margin() = SPACING_SIZE; m_frame.add( m_label_browser ); m_proxy.signal_clicked().connect( sigc::mem_fun( *this, &PageNet::slot_setup_proxy ) ); m_browser.signal_clicked().connect( sigc::mem_fun( *this, &PageNet::slot_setup_browser ) ); set_column_spacing( SPACING_SIZE ); set_row_spacing( SPACING_SIZE ); // Gtk::Grid::attach( child, column, row, width, height ) attach( m_icon, 0, 0, 1, 1 ); attach( m_label, 1, 0, 1, 1 ); attach( m_proxy, 0, 1, 2, 1 ); attach( m_browser, 0, 2, 2, 1 ); attach( m_frame, 0, 3, 2, 1 ); // フレームの追加 m_label.set_hexpand( true ); } // // プロキシ設定 // void PageNet::slot_setup_proxy() { auto pref = CORE::PrefDiagFactory( m_parent, CORE::PREFDIAG_PROXY, "" ); pref->run(); } // // ブラウザ設定 // void PageNet::slot_setup_browser() { auto pref = CORE::PrefDiagFactory( m_parent, CORE::PREFDIAG_BROWSER, "" ); pref->run(); m_label_browser.set_text( CONFIG::get_command_openurl() ); } ///////////////////////////////////////////// PageFont::PageFont() : Gtk::Grid() , m_icon( ICON::get_icon_manager()->get_icon( ICON::JD48 ) ) , m_label( "3/5.フォントの設定をします", Gtk::ALIGN_START ) , m_label_res( "スレ(_T)", Gtk::ALIGN_START, Gtk::ALIGN_CENTER, true ) , m_label_mail( "メール(_U)", Gtk::ALIGN_START, Gtk::ALIGN_CENTER, true ) , m_label_popup( "ポップアップ(_P)", Gtk::ALIGN_START, Gtk::ALIGN_CENTER, true ) , m_label_tree( "板/スレ一覧(_O)", Gtk::ALIGN_START, Gtk::ALIGN_CENTER, true ) , m_font_res( "スレフォント" ) , m_font_mail( "メール欄フォント" ) , m_font_popup( "ポップアップフォント" ) , m_font_tree( "板/スレ一覧フォント" ) { m_label_res.set_mnemonic_widget( m_font_res ); m_label_mail.set_mnemonic_widget( m_font_mail ); m_label_popup.set_mnemonic_widget( m_font_popup ); m_label_tree.set_mnemonic_widget( m_font_tree ); m_font_res.set_font_name( CONFIG::get_fontname( FONT_MAIN ) ); m_font_mail.set_font_name( CONFIG::get_fontname( FONT_MAIL ) ); m_font_popup.set_font_name( CONFIG::get_fontname( FONT_POPUP ) ); m_font_tree.set_font_name( CONFIG::get_fontname( FONT_BBS ) ); m_font_res.signal_font_set().connect( sigc::mem_fun( *this, &PageFont::slot_font_res ) ); m_font_mail.signal_font_set().connect( sigc::mem_fun( *this, &PageFont::slot_font_mail ) ); m_font_popup.signal_font_set().connect( sigc::mem_fun( *this, &PageFont::slot_font_popup ) ); m_font_tree.signal_font_set().connect( sigc::mem_fun( *this, &PageFont::slot_font_tree ) ); m_font_res.set_hexpand( true ); m_font_mail.set_hexpand( true ); m_font_popup.set_hexpand( true ); m_font_tree.set_hexpand( true ); constexpr int width = 1; constexpr int height = 1; m_table.attach( m_label_res, 0, 0, width, height ); m_table.attach( m_label_mail, 0, 1, width, height ); m_table.attach( m_label_popup, 0, 2, width, height ); m_table.attach( m_label_tree, 0, 3, width, height ); m_table.attach( m_font_res, 1, 0, width, height ); m_table.attach( m_font_mail, 1, 1, width, height ); m_table.attach( m_font_popup, 1, 2, width, height ); m_table.attach( m_font_tree, 1, 3, width, height ); set_column_spacing( SPACING_SIZE ); set_row_spacing( SPACING_SIZE ); // Gtk::Grid::attach( child, column, row, width, height ) attach( m_icon, 0, 0, 1, 1 ); attach( m_label, 1, 0, 1, 1 ); attach( m_table, 0, 1, 2, 1 ); m_label.set_hexpand( true ); m_table.set_hexpand( true ); m_table.set_row_spacing( 4 ); m_table.set_column_spacing( 4 ); } void PageFont::slot_font_res() { CONFIG::set_fontname( FONT_MAIN, m_font_res.get_font_name() ); CONFIG::set_fontname( FONT_MESSAGE, m_font_res.get_font_name() ); } void PageFont::slot_font_mail() { CONFIG::set_fontname( FONT_MAIL, m_font_mail.get_font_name() ); } void PageFont::slot_font_popup() { CONFIG::set_fontname( FONT_POPUP, m_font_popup.get_font_name() ); } void PageFont::slot_font_tree() { CONFIG::set_fontname( FONT_BBS, m_font_tree.get_font_name() ); CONFIG::set_fontname( FONT_BOARD, m_font_tree.get_font_name() ); } ///////////////////////////////////////////// PagePane::PagePane() : Gtk::Grid() , m_icon( ICON::get_icon_manager()->get_icon( ICON::JD48 ) ) , m_label( "4/5.ペイン表示設定をします", Gtk::ALIGN_START ) , m_2pane( m_radiogroup, "2ペイン表示(_2)", true ) , m_3pane( m_radiogroup, "3ペイン表示(_3)", true ) , m_v3pane( m_radiogroup, "縦3ペイン表示(_V)", true ) , m_label_inst( "", Gtk::ALIGN_START ) { switch( SESSION::get_mode_pane() ){ case SESSION::MODE_2PANE: m_2pane.set_active( true ); slot_2pane(); break; case SESSION::MODE_3PANE: m_3pane.set_active( true ); slot_3pane(); break; case SESSION::MODE_V3PANE: m_v3pane.set_active( true ); slot_v3pane(); break; } m_2pane.signal_toggled().connect( sigc::mem_fun( *this, &PagePane::slot_2pane ) ); m_3pane.signal_toggled().connect( sigc::mem_fun( *this, &PagePane::slot_3pane ) ); m_v3pane.signal_toggled().connect( sigc::mem_fun( *this, &PagePane::slot_v3pane ) ); set_column_spacing( SPACING_SIZE ); set_row_spacing( SPACING_SIZE ); // Gtk::Grid::attach( child, column, row, width, height ) attach( m_icon, 0, 0, 1, 1 ); attach( m_label, 1, 0, 1, 1 ); attach( m_2pane, 0, 1, 2, 1 ); attach( m_3pane, 0, 2, 2, 1 ); attach( m_v3pane, 0, 3, 2, 1 ); attach( m_label_inst, 0, 4, 2, 1 ); m_label.set_hexpand( true ); } void PagePane::slot_2pane() { SESSION::set_mode_pane( SESSION::MODE_2PANE ); m_label_inst.set_text( "ウィンドウの左に板一覧、右にスレ一覧とスレビューを切り替え表示" ); } void PagePane::slot_3pane() { SESSION::set_mode_pane( SESSION::MODE_3PANE ); m_label_inst.set_text( "ウィンドウの左に板一覧、右上にスレ一覧、右下にスレビューを表示" ); } void PagePane::slot_v3pane() { SESSION::set_mode_pane( SESSION::MODE_V3PANE ); // ウインドウの幅を固定するため空白で長さを揃える m_label_inst.set_text( "ウィンドウの左に板一覧、中央にスレ一覧、右にスレビューを表示 " ); } ///////////////////////////////////////////// PageEnd::PageEnd() : Gtk::Grid() , m_icon( ICON::get_icon_manager()->get_icon( ICON::JD48 ) ) , m_label( "5/5.JDim セットアップ完了", Gtk::ALIGN_START ) , m_label2( "その他の設定は起動後に設定及び表示メニューからおこなって下さい\n\n" "完了を押すとJDimを起動して板一覧のリストをロードします\n" "板一覧が表示されるまでしばらくお待ち下さい", Gtk::ALIGN_START ) { set_column_spacing( SPACING_SIZE ); set_row_spacing( SPACING_SIZE ); // Gtk::Grid::attach( child, column, row, width, height ) attach( m_icon, 0, 0, 1, 1 ); attach( m_label, 1, 0, 1, 1 ); attach( m_label2, 0, 1, 2, 1 ); m_label.set_hexpand( true ); } ///////////////////////////////////////////// SetupWizard::SetupWizard() : Gtk::Dialog() , m_page_network{ this } , m_back( "<< 戻る(_B)", true ) , m_next( "次へ(_N) >>", true ) { set_title( "JDim セットアップウィザード" ); set_keep_above( true ); set_resizable( false ); set_position( Gtk::WIN_POS_CENTER ); // 配置はデスクトップ環境次第 // ボタン get_action_area()->set_spacing( SPACING_SIZE / 2 ); get_action_area()->pack_start( m_back, Gtk::PACK_SHRINK ); get_action_area()->pack_start( m_next, Gtk::PACK_SHRINK ); m_fin = add_button( "完了(_C)", Gtk::RESPONSE_OK ); m_back.set_sensitive( false ); m_next.set_sensitive( true ); m_fin->set_sensitive( false ); m_back.signal_clicked().connect( sigc::mem_fun( *this, &SetupWizard::slot_back ) ); m_next.signal_clicked().connect( sigc::mem_fun( *this, &SetupWizard::slot_next ) ); // ページ m_notebook.append_page( m_page_start ); m_notebook.append_page( m_page_network ); m_notebook.append_page( m_page_font ); m_notebook.append_page( m_page_pane ); m_notebook.append_page( m_page_end ); m_notebook.set_border_width( SPACING_SIZE ); m_notebook.set_show_border( false ); m_notebook.set_show_tabs( false ); m_sigc_switch_page = m_notebook.signal_switch_page().connect( sigc::mem_fun( *this, &SetupWizard::slot_switch_page ) ); get_content_area()->pack_start( m_notebook, Gtk::PACK_EXPAND_PADDING, SPACING_SIZE ); show_all_children(); } SetupWizard::~SetupWizard() { if( m_sigc_switch_page.connected() ) m_sigc_switch_page.disconnect(); } void SetupWizard::slot_switch_page( Gtk::Widget* notebookpage, guint page ) { switch( page ){ case 0: m_back.set_sensitive( false ); m_next.set_sensitive( true ); m_fin->set_sensitive( false ); break; case 1: case 2: case 3: m_back.set_sensitive( true ); m_next.set_sensitive( true ); m_fin->set_sensitive( false ); break; case 4: m_back.set_sensitive( true ); m_next.set_sensitive( false ); m_fin->set_sensitive( true ); m_fin->grab_focus(); break; } } void SetupWizard::slot_back() { int page = m_notebook.get_current_page(); m_notebook.set_current_page( page - 1 ); } void SetupWizard::slot_next() { int page = m_notebook.get_current_page(); m_notebook.set_current_page( page + 1 ); } jdim-0.7.0/src/setupwizard.h000066400000000000000000000054521417047150700157760ustar00rootroot00000000000000// ライセンス: GPL2 // // セットアップウィザード // #ifndef _SETUPWIZARD_H #define _SETUPWIZARD_H #include "gtkmmversion.h" #include namespace CORE { class PageStart : public Gtk::Grid { Gtk::Image m_icon; Gtk::Label m_label; Gtk::Label m_label2; public: PageStart(); }; //////////////////////////////////////////// class PageNet : public Gtk::Grid { Gtk::Window* m_parent{}; Gtk::Image m_icon; Gtk::Label m_label; Gtk::Button m_proxy; Gtk::Button m_browser; // フレームの追加 Gtk::Frame m_frame; Gtk::Label m_label_browser; public: explicit PageNet( Gtk::Window* parent ); private: void slot_setup_proxy(); void slot_setup_browser(); }; ///////////////////////////////////////////// class PageFont : public Gtk::Grid { Gtk::Image m_icon; Gtk::Label m_label; Gtk::Grid m_table; Gtk::Label m_label_res; Gtk::Label m_label_mail; Gtk::Label m_label_popup; Gtk::Label m_label_tree; Gtk::FontButton m_font_res; Gtk::FontButton m_font_mail; Gtk::FontButton m_font_popup; Gtk::FontButton m_font_tree; public: PageFont(); private: void slot_font_res(); void slot_font_mail(); void slot_font_popup(); void slot_font_tree(); }; ///////////////////////////////////////////// class PagePane : public Gtk::Grid { Gtk::Image m_icon; Gtk::Label m_label; Gtk::RadioButtonGroup m_radiogroup; Gtk::RadioButton m_2pane; Gtk::RadioButton m_3pane; Gtk::RadioButton m_v3pane; Gtk::Label m_label_inst; public: PagePane(); private: void slot_2pane(); void slot_3pane(); void slot_v3pane(); }; ///////////////////////////////////////////// class PageEnd : public Gtk::Grid { Gtk::Image m_icon; Gtk::Label m_label; Gtk::Label m_label2; public: PageEnd(); }; ///////////////////////////////////////////// class SetupWizard : public Gtk::Dialog { sigc::connection m_sigc_switch_page; Gtk::Notebook m_notebook; PageStart m_page_start; PageNet m_page_network; PageFont m_page_font; PagePane m_page_pane; PageEnd m_page_end; Gtk::Button* m_fin; Gtk::Button m_back; Gtk::Button m_next; public: SetupWizard(); ~SetupWizard(); private: void slot_switch_page( Gtk::Widget* notebookpage, guint page ); void slot_back(); void slot_next(); void slot_fin(); }; } #endif jdim-0.7.0/src/sharedbuffer.cpp000066400000000000000000000006461417047150700164100ustar00rootroot00000000000000// ライセンス: GPL2 #include "sharedbuffer.h" using namespace CORE; CORE::DATA_INFO_LIST shared_infolist; int CORE::SBUF_size() { return shared_infolist.size(); } void CORE:: SBUF_clear_info() { shared_infolist.clear(); } void CORE::SBUF_set_list( const CORE::DATA_INFO_LIST& list_info ) { shared_infolist = list_info; } CORE::DATA_INFO_LIST& CORE::SBUF_list_info() { return shared_infolist; } jdim-0.7.0/src/sharedbuffer.h000066400000000000000000000006131417047150700160470ustar00rootroot00000000000000// ライセンス: GPL2 // // 共有バッファ // // クラス間で情報をやり取りするときに使う // #ifndef _SHAREDBUFFER_H #define _SHAREDBUFFER_H #include "type.h" #include "data_info.h" namespace CORE { int SBUF_size(); void SBUF_clear_info(); void SBUF_set_list( const CORE::DATA_INFO_LIST& list_info ); CORE::DATA_INFO_LIST& SBUF_list_info(); } #endif jdim-0.7.0/src/sidebaritempref.cpp000066400000000000000000000030161417047150700171070ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "sidebaritempref.h" #include "icons/iconmanager.h" #include "jdlib/miscutil.h" #include "global.h" #include "session.h" #include "command.h" using namespace CORE; SidebarItemPref::SidebarItemPref( Gtk::Window* parent, const std::string& url ) : SKELETON::SelectItemPref( parent, url ) { // デフォルトの項目を設定 append_default_pair( ITEM_NAME_SEARCHBOX, ICON::get_icon( ICON::TRANSPARENT ) ); append_default_pair( ITEM_NAME_SEARCH_NEXT, ICON::get_icon( ICON::SEARCH_NEXT ) ); append_default_pair( ITEM_NAME_SEARCH_PREV, ICON::get_icon( ICON::SEARCH_PREV ) ); append_default_pair( ITEM_NAME_CHECK_UPDATE_ROOT, ICON::get_icon( ICON::CHECK_UPDATE_ROOT ) ); append_default_pair( ITEM_NAME_CHECK_UPDATE_OPEN_ROOT, ICON::get_icon( ICON::CHECK_UPDATE_OPEN_ROOT ) ); append_default_pair( ITEM_NAME_STOPLOADING, ICON::get_icon( ICON::STOPLOADING ) ); append_default_pair( ITEM_NAME_SEPARATOR, ICON::get_icon( ICON::TRANSPARENT ) ); // 文字列を元に列を追加 append_rows( SESSION::get_items_sidebar_toolbar_str() ); set_title( "ツールバー項目設定(サイドバー)" ); } // OKを押した void SidebarItemPref::slot_ok_clicked() { SESSION::set_items_sidebar_toolbar_str( get_items() ); CORE::core_set_command( "update_bbslist_toolbar_button" ); } // // デフォルトボタン // void SidebarItemPref::slot_default() { append_rows( SESSION::get_items_sidebar_toolbar_default_str() ); } jdim-0.7.0/src/sidebaritempref.h000066400000000000000000000010711417047150700165530ustar00rootroot00000000000000// ライセンス: GPL2 // サイドバーのツールバーの表示項目設定 #ifndef _SIDEBARITEMPREF_H #define _SIDEBARITEMPREF_H #include "skeleton/selectitempref.h" namespace CORE { class SidebarItemPref : public SKELETON::SelectItemPref { public: SidebarItemPref( Gtk::Window* parent, const std::string& url ); ~SidebarItemPref() noexcept = default; private: // OK押した void slot_ok_clicked() override; // デフォルトボタン void slot_default() override; }; } #endif jdim-0.7.0/src/sign.h000066400000000000000000000012671417047150700143550ustar00rootroot00000000000000// ビューのアドレスで使用するサイン #ifndef _SIGN_H #define _SIGN_H #define ARTICLE_SIGN "_ARTICLE_" #define NEXT_SIGN "_NEXT_" #define LOG_SIGN "_LOG_" #define SIDEBAR_SIGN "_SIDEBAR_" #define BOARD_SIGN "_BOARD_" #define RES_SIGN "_RES_" #define NAME_SIGN "_NAME_" #define ID_SIGN "_ID_" #define BOOKMK_SIGN "_BM_" #define POST_SIGN "_POST_" #define HIGHREFRES_SIGN \ "_HIGH_REFRES_" #define URL_SIGN "_URL_" #define REFER_SIGN "_REF_" #define KEYWORD_SIGN "_KW_" #define POSTLOG_SIGN "_POST_" #define ORMODE_SIGN "_OR_" #define CENTER_SIGN "_CENTER_" #define TIME_SIGN "_TIME_" #define TITLE_SIGN "_TITLE_" #endif jdim-0.7.0/src/skeleton/000077500000000000000000000000001417047150700150625ustar00rootroot00000000000000jdim-0.7.0/src/skeleton/Makefile.am000066400000000000000000000030401417047150700171130ustar00rootroot00000000000000noinst_LIBRARIES = libskeleton.a libskeleton_a_SOURCES = \ window.cpp \ aamenu.cpp \ admin.cpp \ dispatchable.cpp \ loadable.cpp \ treeviewbase.cpp \ dragtreeview.cpp \ edittreeview.cpp \ editcolumns.cpp \ view.cpp \ editview.cpp \ dragnote.cpp \ tabnote.cpp \ toolbarnote.cpp \ viewnote.cpp \ tablabel.cpp \ tabswitchbutton.cpp \ tabswitchmenu.cpp \ menubutton.cpp \ toolmenubutton.cpp \ backforwardbutton.cpp \ popupwinbase.cpp \ popupwin.cpp \ entry.cpp \ compentry.cpp \ label_entry.cpp \ login.cpp \ prefdiag.cpp \ selectitempref.cpp \ msgdiag.cpp \ aboutdiag.cpp \ detaildiag.cpp \ panecontrol.cpp \ hpaned.cpp \ vpaned.cpp \ notebook.cpp \ vbox.cpp \ jdtoolbar.cpp \ toolbar.cpp \ textloader.cpp \ undobuffer.cpp noinst_HEADERS = \ window.h \ aamenu.h \ admin.h \ dispatchable.h \ loadable.h \ lockable.h \ treeviewbase.h \ dragtreeview.h \ edittreeview.h \ editcolumns.h \ view.h \ editview.h \ editviewdialog.h \ dragnote.h \ tabnote.h \ toolbarnote.h \ viewnote.h \ tablabel.h \ tabswitchbutton.h \ tabswitchmenu.h \ imgtoolbutton.h \ menubutton.h \ toolmenubutton.h \ backforwardbutton.h \ popupwinbase.h \ popupwin.h \ iconpopup.h \ entry.h \ compentry.h \ label_entry.h \ prefdiag.h \ selectitempref.h \ msgdiag.h \ aboutdiag.h \ detaildiag.h \ editviewdialog.h \ filediag.h \ login.h \ panecontrol.h \ hpaned.h \ vpaned.h \ notebook.h \ vbox.h \ jdtoolbar.h \ toolbar.h \ textloader.h \ undobuffer.h AM_CXXFLAGS = @GTKMM_CFLAGS@ AM_CPPFLAGS = -I$(top_srcdir)/src jdim-0.7.0/src/skeleton/aamenu.cpp000066400000000000000000000170331417047150700170400ustar00rootroot00000000000000// AA 選択ポップアップメニュークラス //#define _DEBUG #include "gtkmmversion.h" #include "jddebug.h" #include "aamenu.h" #include "config/globalconf.h" #include "fontid.h" #include "colorid.h" #include "aamanager.h" #include "cache.h" using namespace SKELETON; AAMenu::AAMenu( Gtk::Window& parent ) : Gtk::Menu() , m_parent( parent ), m_popup( SKELETON::POPUPWIN_NOFRAME ) { #ifdef _DEBUG std::cout << "AAMenu::AAMenu\n"; #endif Pango::FontDescription pfd( CONFIG::get_fontname( FONT_MESSAGE ) ); pfd.set_weight( Pango::WEIGHT_NORMAL ); m_textview.override_font( pfd ); m_textview.override_color( Gdk::RGBA( CONFIG::get_color( COLOR_CHAR_SELECTION ) ), Gtk::STATE_FLAG_NORMAL ); m_textview.override_background_color( Gdk::RGBA( CONFIG::get_color( COLOR_BACK_SELECTION ) ), Gtk::STATE_FLAG_NORMAL ); m_popup.set_transient_for( m_parent ); m_popup.sig_configured().connect( sigc::mem_fun( *this, &AAMenu::slot_configured_popup ) ); m_popup.add( m_textview ); m_popup.show_all_children(); create_popupmenu(); } AAMenu::~AAMenu() { #ifdef _DEBUG std::cout << "AAMenu::~AAMenu\n"; #endif } int AAMenu::get_size() const { return static_cast< int >( get_children().size() ); } void AAMenu::set_text( const std::string& text ) { m_popup.show(); m_popup.resize( 1, 1 ); m_textview.get_buffer()->set_text( text ); } // メニュー項目作成 void AAMenu::create_menuitem( Glib::RefPtr< Gtk::ActionGroup > actiongroup, Gtk::Menu* menu, const int id ) { const int maxchar = 20; Glib::ustring aa_label = CORE::get_aamanager()->get_label( id ); std::string shortcut = CORE::get_aamanager()->id2shortcut( id ); if( ! shortcut.empty() ) aa_label = "[" + shortcut + "] " + aa_label; std::string actname = "aa" + std::to_string( id ); if( actiongroup->get_action( actname ) ) return; // 登録済み #ifdef _DEBUG std::cout << actname << " label = " << aa_label << std::endl; #endif Glib::RefPtr< Gtk::Action > action = Gtk::Action::create( actname, aa_label.substr( 0, maxchar ) ); action->set_accel_group( m_parent.get_accel_group() ); Gtk::MenuItem* item = Gtk::manage( action->create_menu_item() ); actiongroup->add( action, sigc::bind< Gtk::MenuItem* >( sigc::mem_fun( *this, &AAMenu::slot_aainput_menu_clicked ), item ) ); item->signal_select().connect( sigc::bind< Gtk::MenuItem* >( sigc::mem_fun( *this, &AAMenu::slot_select_item ), item ) ); menu->append( *item ); m_map_items.insert( std::make_pair( item, id ) ); } // メニュー作成 void AAMenu::create_popupmenu() { std::string aa_lines; if( ! CACHE::load_rawdata( CACHE::path_aalist(), aa_lines ) ) return; Glib::RefPtr< Gtk::ActionGroup > actiongroup = Gtk::ActionGroup::create(); // 履歴 for( int i = 0 ; i < CORE::get_aamanager()->get_historysize() ; ++i ){ int org_id = CORE::get_aamanager()->history2id( i ); create_menuitem( actiongroup, this, org_id ); } if( CORE::get_aamanager()->get_historysize() ){ Gtk::MenuItem* item = Gtk::manage( new Gtk::SeparatorMenuItem() ); append( *item ); m_map_items.insert( std::make_pair( item, -1 ) ); } for( int i = 0 ; i < CORE::get_aamanager()->get_size() ; ++i ) create_menuitem( actiongroup, this, i ); show_all_children(); } void AAMenu::on_map() { #ifdef _DEBUG std::cout << "AAMenu::on_realize\n"; #endif Gtk::Menu::on_map(); select_item( *dynamic_cast< Gtk::MenuItem* >( *get_children().begin() ) ); } void AAMenu::on_hide() { #ifdef _DEBUG std::cout << "AAMenu::on_hide\n"; #endif m_popup.hide(); Gtk::Menu::on_hide(); } // 下移動 bool AAMenu::move_down() { #ifdef _DEBUG std::cout << "AAMenu::move_down\n"; #endif const auto items = get_children(); auto it = std::find( items.begin(), items.end(), static_cast< Gtk::Widget* >( m_activeitem ) ); ++it; if( m_map_items[ dynamic_cast< Gtk::MenuItem* >( *it ) ] == -1 ) { ++it; // セパレータをスキップする } if( it == items.end() ) { it = items.begin(); // 一番下まで行ったら上に戻る } select_item( *dynamic_cast< Gtk::MenuItem* >( *it ) ); return true; } // 上移動 bool AAMenu::move_up() { #ifdef _DEBUG std::cout << "AAMenu::move_up\n"; #endif // gtk2のGlib::ListHandleのイテレーターはデクリメント不可なので型変換する const std::vector< Gtk::Widget* > items = get_children(); auto it = std::find( items.begin(), items.end(), static_cast< Gtk::Widget* >( m_activeitem ) ); if( it != items.begin() ) { --it; if( m_map_items[ dynamic_cast< Gtk::MenuItem* >( *it ) ] == -1 && it != items.begin() ) { --it; // セパレータをスキップする } select_item( *dynamic_cast< Gtk::MenuItem* >( *it ) ); } else { // 一番上に行ったら下に戻る select_item( *dynamic_cast< Gtk::MenuItem* >( items.back() ) ); } return true; } // キー入力のフック bool AAMenu::on_key_press_event( GdkEventKey* event ) { #ifdef _DEBUG std::cout << "AAMenu::on_key_press_event key = " << event->keyval << std::endl; #endif // 下移動 if( event->keyval == GDK_KEY_j || ( ( event->state & GDK_CONTROL_MASK ) && event->keyval == GDK_KEY_n ) || event->keyval == GDK_KEY_space ){ move_down(); return true; } // 上移動 else if( event->keyval == GDK_KEY_k || ( ( event->state & GDK_CONTROL_MASK ) && event->keyval == GDK_KEY_p ) ){ move_up(); return true; } // ショートカット else{ int id = CORE::get_aamanager()->shortcut2id( event->string[ 0 ] ); #ifdef _DEBUG std::cout << "id = " << id << " key = " << event->string[ 0 ] << std::endl; #endif if( id >= 0 ){ std::string aa = CORE::get_aamanager()->get_aa( id ); if( ! aa.empty() ){ m_sig_selected.emit( aa ); CORE::get_aamanager()->append_history( id ); hide(); } } } return Gtk::Menu::on_key_press_event( event ); } // // ポップアップウィンドウのサイズが変わった // void AAMenu::slot_configured_popup( int width, int height ) { int sw = get_screen()->get_width(); int x, y; get_window()->get_root_coords( 0, 0, x, y ); #ifdef _DEBUG std::cout << " AAMenu::slot_configured_popup w = " << width << " h = " << height << " x = " << x << " screen width = " << sw << std::endl; #endif y -= height; if( x + width > sw ) x = sw - width; m_popup.move( x, y ); } // // メニューの行を選択 // void AAMenu::slot_select_item( Gtk::MenuItem* item ) { if( ! get_active() ) return; int id = m_map_items[ item ]; m_activeitem = item; #ifdef _DEBUG std::cout << "AAMenu::slot_select_item id = " << id << std::endl; #endif set_text( CORE::get_aamanager()->get_aa( id ) ); } // // アスキーアート入力 // void AAMenu::slot_aainput_menu_clicked( Gtk::MenuItem* item ) { if( ! get_active() ) return; int id = m_map_items[ item ]; m_activeitem = item; #ifdef _DEBUG std::cout << "AAMenu::slot_aainput_menu_clicked id = " << id << std::endl; std::cout << CORE::get_aamanager()->get_aa( id ) << std::endl; #endif m_sig_selected.emit( CORE::get_aamanager()->get_aa( id ) ); CORE::get_aamanager()->append_history( id ); } jdim-0.7.0/src/skeleton/aamenu.h000066400000000000000000000025651417047150700165110ustar00rootroot00000000000000// ライセンス: GPL2 // // AA 選択ポップアップメニュークラス // #ifndef _AAMENU_H #define _AAMENU_H #include #include #include "popupwinbase.h" namespace SKELETON { typedef sigc::signal< void, const std::string& > SIG_AAMENU_SELECTED; class AAMenu : public Gtk::Menu { SIG_AAMENU_SELECTED m_sig_selected; Gtk::Window& m_parent; SKELETON::PopupWinBase m_popup; Gtk::TextView m_textview; std::map< Gtk::MenuItem*, int > m_map_items; Gtk::MenuItem* m_activeitem{}; public: explicit AAMenu( Gtk::Window& parent ); ~AAMenu(); // 選択されたらemitされる SIG_AAMENU_SELECTED sig_selected() { return m_sig_selected; } protected: void on_map() override; void on_hide() override; bool on_key_press_event (GdkEventKey* event) override; private: int get_size() const; void set_text( const std::string& text ); void create_menuitem( Glib::RefPtr< Gtk::ActionGroup > actiongroup, Gtk::Menu* menu, const int id ); void create_popupmenu(); bool move_down(); bool move_up(); void slot_select_item( Gtk::MenuItem* item ); void slot_configured_popup( int width, int height ); void slot_aainput_menu_clicked( Gtk::MenuItem* item ); }; } #endif jdim-0.7.0/src/skeleton/aboutdiag.cpp000066400000000000000000000214161417047150700175310ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "aboutdiag.h" #include "command.h" #include "environment.h" #include "session.h" #include "config/globalconf.h" #include "icons/iconmanager.h" #include "jdlib/miscgtk.h" #include using namespace SKELETON; enum { MARGIN = 8 }; AboutDiag::AboutDiag( const Glib::ustring& title ) : Gtk::Dialog( title, ENVIRONMENT::get_dialog_use_header_bar() ? Gtk::DIALOG_USE_HEADER_BAR : Gtk::DialogFlags{} ) , m_button_copy_environment( "クリップボードへコピー" ) { set_transient_for( *CORE::get_mainwindow() ); set_resizable( false ); add_button( g_dgettext( GTK_DOMAIN, "_Close" ), Gtk::RESPONSE_CLOSE ); set_default_response( Gtk::RESPONSE_CLOSE ); set_logo( ICON::get_icon( ICON::JD96 ) ); set_version( ENVIRONMENT::get_jdversion() ); set_comments( ENVIRONMENT::get_jdcomments() ); set_website( CONFIG::get_url_jdimhp() ); set_copyright( ENVIRONMENT::get_jdcopyright() ); set_license( ENVIRONMENT::get_jdlicense() ); set_environment_list(); } AboutDiag::~AboutDiag() noexcept = default; // // run() // int AboutDiag::run() { #ifdef _DEBUG std::cout << "AboutDiag::run start\n"; #endif init(); SESSION::set_dialog_shown( true ); CORE::core_set_command( "dialog_shown" ); int ret = Gtk::Dialog::run(); SESSION::set_dialog_shown( false ); CORE::core_set_command( "dialog_hidden" ); #ifdef _DEBUG std::cout << "AboutDiag::run fin\n"; #endif return ret; } // // 各ウィジェットを配置して初期化 // void AboutDiag::init() { // 情報タブの追加 m_label_tab_info.set_label( "情報" ); m_vbox_info.set_spacing( MARGIN ); m_vbox_info.set_border_width( MARGIN ); // ロゴ if( get_logo() ) { m_vbox_info.pack_start( m_image_logo, Gtk::PACK_SHRINK ); } // バージョン if( ! get_version().empty() ) { m_vbox_info.pack_start( m_label_version, Gtk::PACK_EXPAND_WIDGET, MARGIN ); } // コメント if( ! get_comments().empty() ) { m_vbox_info.pack_start( m_label_comments, Gtk::PACK_SHRINK ); } // コピーライト if( ! get_copyright().empty() ) { m_label_copyright.set_justify( Gtk::JUSTIFY_CENTER ); m_vbox_info.pack_start( m_label_copyright, Gtk::PACK_SHRINK ); } // Webサイト if( ! get_website_label().empty() ) { m_hbox_url.pack_start( m_button_website, Gtk::PACK_EXPAND_PADDING ); m_vbox_info.pack_start( m_hbox_url, Gtk::PACK_SHRINK ); } m_notebook.append_page( m_vbox_info, m_label_tab_info ); // ライセンスタブの追加 if( ! get_license().empty() ) { m_label_tab_license.set_label( "ライセンス" ); m_notebook.append_page( m_scrollwindow_license, m_label_tab_license ); } // 動作環境タブの追加 m_label_tab_environment.set_label( "動作環境" ); m_notebook.append_page( m_vbox_environment, m_label_tab_environment ); // 動作環境一覧 m_vbox_environment.pack_start( m_scrollwindow_environment, Gtk::PACK_EXPAND_WIDGET ); // クリップボードへコピーのボタン m_button_copy_environment.signal_clicked().connect( sigc::mem_fun( *this, &AboutDiag::slot_copy_environment ) ); m_hbuttonbox_environment.set_layout( Gtk::BUTTONBOX_END ); m_hbuttonbox_environment.pack_start( m_button_copy_environment, Gtk::PACK_SHRINK ); m_vbox_environment.pack_start( m_hbuttonbox_environment, Gtk::PACK_SHRINK ); get_content_area()->pack_start( m_notebook, Gtk::PACK_EXPAND_WIDGET, MARGIN ); show_all_children(); // バージョンの文字列の長さが短い( x.x.x-YYMMDD )と // ウィンドウの幅が狭すぎるので"幅/高さ"いずれかの大 // きい方に合わせて4:3を保持するようにする。 int win_w, win_h; get_size( win_w, win_h ); if( win_w * 3 / 4 < win_h ) set_size_request( win_h * 4 / 3, -1 ); else set_size_request( -1, win_w * 3 / 4 ); } // // ロゴ // void AboutDiag::set_logo( const Glib::RefPtr< Gdk::Pixbuf >& logo ) { m_image_logo.set( logo ); } Glib::RefPtr< Gdk::Pixbuf > AboutDiag::get_logo() { return m_image_logo.get_pixbuf(); } // // バージョン表示 // void AboutDiag::set_version( const Glib::ustring& version ) { m_label_version.set_label( version ); // TODO: GTKのcssで設定するか? Pango::FontDescription font_discription_version = m_label_version.get_style_context()->get_font(); const int label_version_font_size = font_discription_version.get_size(); font_discription_version.set_size( label_version_font_size * 5 / 3 ); font_discription_version.set_weight( Pango::WEIGHT_BOLD ); m_label_version.override_font( font_discription_version ); } Glib::ustring AboutDiag::get_version() const { return m_label_version.get_label(); } // // コメント // void AboutDiag::set_comments( const Glib::ustring& comments ) { m_label_comments.set_label( comments ); } Glib::ustring AboutDiag::get_comments() const { return m_label_comments.get_label(); } // // コピーライト // void AboutDiag::set_copyright( const Glib::ustring& copyright ) { m_label_copyright.set_label( copyright ); } Glib::ustring AboutDiag::get_copyright() const { return m_label_copyright.get_label(); } // // Webサイト // void AboutDiag::set_website( const Glib::ustring& website ) { if( m_button_website.get_label().empty() ) m_button_website.set_label( website ); m_button_website.set_relief( Gtk::RELIEF_NONE ); m_button_website.signal_clicked().connect( sigc::mem_fun( this, &AboutDiag::slot_button_website_clicked ) ); m_website_url = website; } Glib::ustring AboutDiag::get_website() const { return m_website_url; } // // Webサイトラベル // void AboutDiag::set_website_label( const Glib::ustring& website_label ) { m_button_website.set_label( website_label ); } Glib::ustring AboutDiag::get_website_label() const { return m_button_website.get_label(); } // // Webサイトのボタンがクリックされた // void AboutDiag::slot_button_website_clicked() { CORE::core_set_command( "open_url_browser", m_website_url ); } // // ライセンス // void AboutDiag::set_license( const Glib::ustring& license ) { m_textview_license.get_buffer()->set_text( license ); m_textview_license.set_editable( false ); m_textview_license.set_cursor_visible( false ); m_textview_license.set_accepts_tab( false ); m_textview_license.set_wrap_mode( Gtk::WRAP_WORD_CHAR ); m_scrollwindow_license.set_policy( Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC ); m_scrollwindow_license.add( m_textview_license ); } Glib::ustring AboutDiag::get_license() const { return m_textview_license.get_buffer()->get_text(); } // // 動作環境一覧 // void AboutDiag::set_environment_list() { Gtk::TreeModelColumn< Glib::ustring > column_name; Gtk::TreeModelColumn< Glib::ustring > column_value; Gtk::TreeModel::ColumnRecord record; Glib::RefPtr< Gtk::ListStore > liststore; record.add( column_name ); record.add( column_value ); liststore = Gtk::ListStore::create( record ); m_treeview_environment.set_model( liststore ); Gtk::TreeModel::Row row; m_treeview_environment.append_column( "項目", column_name ); m_treeview_environment.append_column( "内容", column_value ); row = *( liststore->append() ); row[ column_name ] = "JDimのバージョン"; row[ column_value ] = ENVIRONMENT::get_jdversion(); row = *( liststore->append() ); row[ column_name ] = "ディストリビューション"; row[ column_value ] = ENVIRONMENT::get_distname(); row = *( liststore->append() ); row[ column_name ] = "デスクトップ環境等"; row[ column_value ] = ENVIRONMENT::get_wm_str(); row = *( liststore->append() ); row[ column_name ] = "gtkmmのバージョン"; row[ column_value ] = ENVIRONMENT::get_gtkmm_version(); row = *( liststore->append() ); row[ column_name ] = "glibmmのバージョン"; row[ column_value ] = ENVIRONMENT::get_glibmm_version(); row = *( liststore->append() ); row[ column_name ] = "TLSライブラリ"; row[ column_value ] = ENVIRONMENT::get_tlslib_version(); row = *( liststore->append() ); row[ column_name ] = "主なオプション"; row[ column_value ] = ENVIRONMENT::get_configure_args( ENVIRONMENT::CONFIGURE_OMITTED ); static_cast( row ); // cppcheck: unreadVariable m_scrollwindow_environment.add( m_treeview_environment ); } // // 動作環境をクリップボードにコピー // void AboutDiag::slot_copy_environment() { std::string jdinfo = ENVIRONMENT::get_jdinfo(); if( ! jdinfo.empty() ) MISC::CopyClipboard( jdinfo ); } jdim-0.7.0/src/skeleton/aboutdiag.h000066400000000000000000000041341417047150700171740ustar00rootroot00000000000000// ライセンス: GPL2 // Gtk::AboutDialog( gtkmm >= 2.6 )の代わりのクラス #ifndef _ABOUTDIAG_H #define _ABOUTDIAG_H #include namespace SKELETON { class AboutDiag : public Gtk::Dialog { Gtk::Notebook m_notebook; // 情報タブ Gtk::Label m_label_tab_info; Gtk::Image m_image_logo; Gtk::Label m_label_version; Gtk::VBox m_vbox_info; Gtk::Label m_label_info; Gtk::Label m_label_comments; Gtk::HBox m_hbox_url; Gtk::Button m_button_website; Gtk::Label m_label_copyright; // ライセンスタブ Gtk::Label m_label_tab_license; Gtk::ScrolledWindow m_scrollwindow_license; Gtk::TextView m_textview_license; // 動作環境タブ Gtk::Label m_label_tab_environment; Gtk::VBox m_vbox_environment; Gtk::HButtonBox m_hbuttonbox_environment; Gtk::Button m_button_copy_environment; Gtk::ScrolledWindow m_scrollwindow_environment; Gtk::TreeView m_treeview_environment; void set_environment_list(); Glib::ustring m_website_url; void init(); void slot_button_website_clicked(); void slot_copy_environment(); public: explicit AboutDiag( const Glib::ustring& title ); ~AboutDiag() noexcept; int run(); void set_logo( const Glib::RefPtr< Gdk::Pixbuf >& logo ); Glib::RefPtr< Gdk::Pixbuf > get_logo(); void set_version( const Glib::ustring& version ); Glib::ustring get_version() const; void set_comments( const Glib::ustring& comments ); Glib::ustring get_comments() const; void set_website( const Glib::ustring& website ); Glib::ustring get_website() const; void set_website_label( const Glib::ustring& website_label ); Glib::ustring get_website_label() const; void set_copyright( const Glib::ustring& copyright ); Glib::ustring get_copyright() const; void set_license( const Glib::ustring& license ); Glib::ustring get_license() const; }; } #endif jdim-0.7.0/src/skeleton/admin.cpp000066400000000000000000002330251417047150700166630ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "admin.h" #include "window.h" #include "view.h" #include "dragnote.h" #include "msgdiag.h" #include "tabswitchmenu.h" #include "dbtree/interface.h" #include "jdlib/miscutil.h" #include "jdlib/miscgtk.h" #include "history/historymanager.h" #include "history/viewhistoryitem.h" #include "control/controlutil.h" #include "control/controlid.h" #include "config/globalconf.h" #include "command.h" #include "session.h" #include "global.h" #include "updatemanager.h" #include enum { MAX_TABS = 50 }; // open_view で使うモード enum { OPEN_MODE_AUTO = 1, OPEN_MODE_NOSWITCH = 2, OPEN_MODE_LOCK = 4, OPEN_MODE_OFFLINE = 8, OPEN_MODE_REGET = 16 }; using namespace SKELETON; Admin::Admin( const std::string& url ) : m_url( url ) , m_notebook{ std::make_unique() } { m_notebook->signal_switch_page().connect( sigc::mem_fun( *this, &Admin::slot_switch_page ) ); m_notebook->set_scrollable( true ); m_notebook->sig_tab_clicked().connect( sigc::mem_fun( *this, &Admin::slot_tab_clicked ) ); m_notebook->sig_tab_scrolled().connect( sigc::mem_fun( *this, &Admin::slot_tab_scrolled ) ); m_notebook->sig_tab_close().connect( sigc::mem_fun( *this, &Admin::slot_tab_close ) ); m_notebook->sig_tab_reload().connect( sigc::mem_fun( *this, &Admin::slot_tab_reload ) ); m_notebook->sig_tab_menu().connect( sigc::mem_fun( *this, &Admin::slot_tab_menu ) ); m_notebook->get_tabswitch_button().signal_clicked().connect( sigc::mem_fun(*this, &Admin::slot_show_tabswitchmenu ) ); // D&D m_notebook->sig_drag_data_get().connect( sigc::mem_fun(*this, &Admin::slot_drag_data_get ) ); m_list_command.clear(); } Admin::~Admin() { #ifdef _DEBUG std::cout << "Admin::~Admin " << m_url << std::endl; #endif // デストラクタの中からdispatchを呼ぶと落ちるので dispatch不可にする set_dispatchable( false ); const int pages = m_notebook->get_n_pages(); #ifdef _DEBUG std::cout << "pages = " << pages << std::endl; #endif if( pages ){ for( int i = 0; i < pages; ++i ){ SKELETON::View* view = dynamic_cast< View* >( m_notebook->get_nth_page( 0 ) ); m_notebook->remove_page( 0, false ); if( view ) delete view; } } m_list_command.clear(); Admin::close_window(); } void Admin::save_session() { std::list< SKELETON::View* > list_view = get_list_view(); for( SKELETON::View* view : list_view ) { if( view ) view->save_session(); } } // // メニューのセットアップ // void Admin::setup_menu() { // 右クリックメニュー m_action_group = Gtk::ActionGroup::create(); m_action_group->add( Gtk::Action::create( "Quit", "Quit" ), sigc::mem_fun( *this, &Admin::slot_close_tab ) ); m_action_group->add( Gtk::ToggleAction::create( "LockTab", "タブをロックする(_K)", std::string(), false ), sigc::mem_fun( *this, &Admin::slot_lock ) ); m_action_group->add( Gtk::Action::create( "Close_Tab_Menu", "複数のタブを閉じる(_T)" ) ); m_action_group->add( Gtk::Action::create( "CloseOther", "他のタブ(_O)" ), sigc::mem_fun( *this, &Admin::slot_close_other_tabs ) ); m_action_group->add( Gtk::Action::create( "CloseLeft", "左←のタブ(_L)" ), sigc::mem_fun( *this, &Admin::slot_close_left_tabs ) ); m_action_group->add( Gtk::Action::create( "CloseRight", "右→のタブ(_R)" ), sigc::mem_fun( *this, &Admin::slot_close_right_tabs ) ); m_action_group->add( Gtk::Action::create( "CloseAll", "全てのタブ(_A)" ), sigc::mem_fun( *this, &Admin::slot_close_all_tabs ) ); m_action_group->add( Gtk::Action::create( "CloseSameIcon", "同じアイコンのタブ(_I)" ), sigc::mem_fun( *this, &Admin::slot_close_same_icon_tabs ) ); m_action_group->add( Gtk::Action::create( "Reload_Tab_Menu", "全てのタブの再読み込み(_A)" ) ); m_action_group->add( Gtk::Action::create( "CheckUpdateAll", "更新チェックのみ(_U)" ), sigc::mem_fun( *this, &Admin::slot_check_update_all_tabs ) ); m_action_group->add( Gtk::Action::create( "CheckUpdateReloadAll", "更新されたタブを再読み込み(_A)" ), sigc::mem_fun( *this, &Admin::slot_check_update_reload_all_tabs ) ); m_action_group->add( Gtk::Action::create( "ReloadAll", "再読み込み(_R)" ), sigc::mem_fun( *this, &Admin::slot_reload_all_tabs ) ); m_action_group->add( Gtk::Action::create( "CancelReloadAll", "キャンセル(_C)" ), sigc::mem_fun( *this, &Admin::slot_cancel_reload_all_tabs ) ); m_action_group->add( Gtk::Action::create( "OpenBrowser", ITEM_NAME_OPEN_BROWSER "(_W)" ), sigc::mem_fun( *this, &Admin::slot_open_by_browser ) ); m_action_group->add( Gtk::Action::create( "CopyURL", ITEM_NAME_COPY_URL "(_U)" ), sigc::mem_fun( *this, &Admin::slot_copy_url ) ); m_action_group->add( Gtk::Action::create( "CopyTitleURL", ITEM_NAME_COPY_TITLE_URL "(_L)" ), sigc::mem_fun( *this, &Admin::slot_copy_title_url ) ); m_action_group->add( Gtk::Action::create( "Preference", "プロパティ(_P)..."), sigc::mem_fun( *this, &Admin::show_preference ) ); // 戻る、進む m_action_group->add( Gtk::Action::create( "PrevView", "PrevView"), sigc::bind< int >( sigc::mem_fun( *this, &Admin::back_clicked_viewhistory ), 1 ) ); m_action_group->add( Gtk::Action::create( "NextView", "NextView"), sigc::bind< int >( sigc::mem_fun( *this, &Admin::forward_clicked_viewhistory ), 1 ) ); m_ui_manager = Gtk::UIManager::create(); m_ui_manager->insert_action_group( m_action_group ); // ポップアップメニューのレイアウト Glib::ustring str_ui = "" // 通常 "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""; m_ui_manager->add_ui_from_string( str_ui ); Gtk::Menu* popupmenu = dynamic_cast< Gtk::Menu* >( m_ui_manager->get_widget( "/popup_menu" ) ); Gtk::MenuItem* item; // 移動サブメニュー m_move_menu = std::make_unique( m_notebook.get(), this ); // 進む、戻る Glib::RefPtr< Gtk::Action > act; act = m_action_group->get_action( "PrevView" ); act->set_accel_group( m_ui_manager->get_accel_group() ); item = Gtk::manage( act->create_menu_item() ); m_move_menu->append( *item ); act = m_action_group->get_action( "NextView" ); act->set_accel_group( m_ui_manager->get_accel_group() ); item = Gtk::manage( act->create_menu_item() ); m_move_menu->append( *item ); m_move_menu->append( *Gtk::manage( new Gtk::SeparatorMenuItem() ) ); // 先頭、最後に移動 item = Gtk::manage( new Gtk::MenuItem( "先頭のタブに移動(_H)", true ) ); m_move_menu->append( *item ); item->signal_activate().connect( sigc::mem_fun( *this, &Admin::tab_head_focus ) ); item = Gtk::manage( new Gtk::MenuItem( "最後のタブに移動(_T)", true ) ); m_move_menu->append( *item ); item->signal_activate().connect( sigc::mem_fun( *this, &Admin::tab_tail_focus ) ); m_move_menu->append( *Gtk::manage( new Gtk::SeparatorMenuItem() ) ); m_move_menuitem = Gtk::manage( new Gtk::MenuItem( "移動" ) ); m_move_menuitem->set_submenu( *m_move_menu ); popupmenu->insert( *m_move_menuitem, 0 ); m_move_menuitem->show_all(); item = Gtk::manage( new Gtk::SeparatorMenuItem() ); popupmenu->insert( *item, 1 ); item->show_all(); // ポップアップメニューにアクセレータを表示 CONTROL::set_menu_motion( popupmenu ); popupmenu->signal_deactivate().connect( sigc::mem_fun( *this, &Admin::slot_popupmenu_deactivate ) ); } void Admin::slot_popupmenu_deactivate() { if( m_move_menu ) m_move_menu->deactivate(); } Gtk::Widget* Admin::get_widget() { return static_cast( m_notebook.get() ); } Gtk::Window* Admin::get_win() { return static_cast( m_win.get() ); } void Admin::set_jdwin( std::unique_ptr win ) { m_win = std::move( win ); } void Admin::delete_jdwin() { m_win.reset(); } bool Admin::is_booting() { if( get_jdwin() && get_jdwin()->is_booting() ) return true; return ( has_commands() ); } // // ページが含まれていないか // bool Admin::empty() const { return ( m_notebook->get_n_pages() == 0 ); } // タブの数 int Admin::get_tab_nums() { return m_notebook->get_n_pages(); } // // 含まれているページのURLのリスト取得 // std::list Admin::get_URLs() { std::list urls; const int pages = m_notebook->get_n_pages(); if( pages ){ for( int i = 0; i < pages; ++i ){ SKELETON::View* view = dynamic_cast< SKELETON::View* >( m_notebook->get_nth_page( i ) ); if( view ) urls.push_back( view->get_url() ); } } return urls; } // // クロック入力 // void Admin::clock_in() { // アクティブなビューにクロックを送る SKELETON::View* view = get_current_view(); if( view ) view->clock_in(); // 全てのビューにクロックを送る // clock_in_always()には軽い処理だけを含めること const int pages = m_notebook->get_n_pages(); if( pages ){ for( int i = 0; i < pages; ++i ){ view = dynamic_cast< SKELETON::View* >( m_notebook->get_nth_page( i ) ); if( view ) view->clock_in_always(); } } m_notebook->clock_in(); if( m_win ) m_win->clock_in(); } // // コマンド受付(通常) // void Admin::set_command( const std::string& command, const std::string& url, const std::string& arg1, const std::string& arg2, const std::string& arg3, const std::string& arg4, const std::string& arg5, const std::string& arg6, const std::string& arg7, const std::string& arg8 ) { COMMAND_ARGS command_arg; command_arg.command = command; command_arg.url = url; command_arg.arg1 = arg1; command_arg.arg2 = arg2; command_arg.arg3 = arg3; command_arg.arg4 = arg4; command_arg.arg5 = arg5; command_arg.arg6 = arg6; command_arg.arg7 = arg7; command_arg.arg8 = arg8; set_command_impl( false, command_arg ); } // // コマンド受付(即実行) // void Admin::set_command_immediately( const std::string& command, const std::string& url, const std::string& arg1, const std::string& arg2, const std::string& arg3, const std::string& arg4, const std::string& arg5, const std::string& arg6, const std::string& arg7, const std::string& arg8 ) { COMMAND_ARGS command_arg; command_arg.command = command; command_arg.url = url; command_arg.arg1 = arg1; command_arg.arg2 = arg2; command_arg.arg3 = arg3; command_arg.arg4 = arg4; command_arg.arg5 = arg5; command_arg.arg6 = arg6; command_arg.arg7 = arg7; command_arg.arg8 = arg8; set_command_impl( true, command_arg ); } // // コマンド受付 // // immediately = false の場合はすぐにコマンドを実行しないで一旦Dispatcherで // メインスレッドにコマンドを渡してからメインスレッドで実行する。通常は // immediately = false で呼び出して、緊急にコマンドを実行させたい場合は // immediately = true とすること。 // void Admin::set_command_impl( const bool immediately, const COMMAND_ARGS& command_arg ) { #ifdef _DEBUG std::cout << "Admin::set_command : immediately = " << immediately << " command = " << command_arg.command << " url = " << command_arg.url << std::endl << command_arg.arg1 << " " << command_arg.arg2 << std::endl << command_arg.arg3 << " " << command_arg.arg4 << std::endl << command_arg.arg5 << " " << command_arg.arg6 << std::endl << command_arg.arg7 << " " << command_arg.arg8 << std::endl; #endif if( immediately ){ m_list_command.push_front( command_arg ); exec_command(); } else{ m_list_command.push_back( command_arg ); dispatch(); // 一度メインループに戻った後にcallback_dispatch() が呼び戻される } } // // ディスパッチャのコールバック関数 // void Admin::callback_dispatch() { while( m_list_command.size() ) exec_command(); } // // コマンド実行 // void Admin::exec_command() { if( m_list_command.size() == 0 ) return; COMMAND_ARGS command = m_list_command.front(); m_list_command.pop_front(); // コマンドリストが空になったことをcoreに知らせる if( m_list_command.size() == 0 ) CORE::core_set_command( "empty_command", m_url ); #ifdef _DEBUG std::cout << "Admin::exec_command " << m_url << " : " << command.command << " " << command.url << " " << std::endl << command.arg1 << " " << command.arg2 << std::endl << command.arg3 << " " << command.arg4 << std::endl << command.arg5 << " " << command.arg6 << std::endl; #endif // 前回終了時の状態を回復 if( command.command == "restore" ){ restore( ( command.arg1 == "only_locked" ) ); } // 移転などでホストの更新 else if( command.command == "update_url" ){ update_url( command.url, command.arg1 ); } // 板名更新 else if( command.command == "update_boardname" ){ update_boardname( command.url ); } // viewを開く else if( command.command == "open_view" ){ open_view( command ); } // リストで開く // arg1 にはdatファイルを空白で区切って指定する // else if( command.command == "open_list" ){ open_list( command ); } else if( command.command == "switch_view" ){ switch_view( command.url ); } else if( command.command == "reload_view" ){ reload_view( command.url ); } else if( command.command == "tab_left" ){ tab_left( false ); } else if( command.command == "tab_right" ){ tab_right( false ); } else if( command.command == "tab_left_updated" ){ tab_left( true ); } else if( command.command == "tab_right_updated" ){ tab_right( true ); } else if( command.command == "tab_num" ){ tab_num( command.arg1 ); } else if( command.command == "tab_head" ){ tab_head(); } else if( command.command == "tab_tail" ){ tab_tail(); } else if( command.command == "set_tab_operating" ){ #ifdef _DEBUG std::cout << command.command << " " << m_url << " " << command.arg1 << std::endl; #endif if( command.arg1 == "true" ) SESSION::set_tab_operating( m_url, true ); else{ SESSION::set_tab_operating( m_url, false ); if( ! empty() ){ // 現在開いているタブへの switch 処理 const bool focus_tmp = m_focus; m_focus = true; slot_switch_page( nullptr, m_notebook->get_current_page() ); m_focus = focus_tmp; } } } else if( command.command == "redraw" ){ redraw_view( command.url ); } else if( command.command == "redraw_current_view" ){ redraw_current_view(); } else if( command.command == "relayout_current_view" ){ relayout_current_view(); } // command.url を含むview全てを検索して表示中なら再描画 else if( command.command == "redraw_views" ){ redraw_views( command.url ); } else if( command.command == "update_view" ){ // ビュー全体を更新 update_view( command.url ); } else if( command.command == "update_item" ){ // ビューの一部を更新 update_item( command.url, command.arg1 ); } else if( command.command == "update_finish" ){ update_finish( command.url ); } // 全ロック解除 else if( command.command == "unlock_views" ){ unlock_all_view( command.url ); } // ビューを閉じる else if( command.command == "close_view" ){ if( command.arg1 == "closeall" ) close_all_view( command.url ); // command.url を含むタブを全て閉じる else if( command.arg1 == "closealltabs" ) slot_close_all_tabs(); // 全て閉じる else if( command.arg1 == "closeother" ) close_other_views( command.url ); else close_view( command.url ); } else if( command.command == "close_currentview" ){ close_current_view(); } // 最後に閉じたタブを復元する else if( command.command == "restore_lasttab" ){ restore_lasttab(); } else if( command.command == "set_page" ){ set_current_page( atoi( command.arg1.c_str() ) ); } // フォーカスイン、アウト else if( command.command == "focus_current_view" ){ m_focus = true; focus_current_view(); } else if( command.command == "focus_out" ){ m_focus = false; focus_out(); } else if( command.command == "restore_focus" ){ m_focus = true; restore_focus(); } // adminクラスを前面に出す else if( command.command == "switch_admin" ){ switch_admin(); } // タブに文字をセット、タブ幅調整 else if( command.command == "set_tablabel" ){ set_tablabel( command.url, command.arg1 ); } else if( command.command == "adjust_tabwidth" ){ m_notebook->adjust_tabwidth(); } // 全てのビューを再描画 else if( command.command == "relayout_all" ) relayout_all(); // タイトル表示 // アクティブなviewから依頼が来たらコアに渡す else if( command.command == "set_title" ){ const bool force = ( command.arg2 == "force" ); set_title( command.url, command.arg1, force ); } // ステータス表示 // アクティブなviewから依頼が来たらコアに渡す else if( command.command == "set_status" ){ const bool force = ( command.arg2 == "force" ); set_status( command.url, command.arg1, force ); } // ステータスの色を変える else if( command.command == "set_status_color" ){ const bool force = ( command.arg2 == "force" ); set_status_color( command.url, command.arg1, force ); } // マウスジェスチャ else if( command.command == "set_mginfo" ){ if( m_win ) m_win->set_mginfo( command.arg1 ); } // 全タブオートリロード else if( command.command == "reload_all_tabs" ){ reload_all_tabs(); } // 全タブ更新チェックして開く else if( command.command == "check_update_reload_all_tabs" ){ check_update_all_tabs( true ); } // オートリロードのキャンセル else if( command.command == "cancel_reload" ){ slot_cancel_reload_all_tabs(); } // タブを隠す else if( command.command == "hide_tabs" ) m_notebook->set_show_tabs( false ); // window 開け閉じ else if( command.command == "open_window" ){ open_window(); return; } else if( command.command == "close_window" ){ close_window(); return; } // ツールバーの検索ボックスをフォーカス else if( command.command == "focus_toolbar_search" ){ focus_toolbar_search(); } // ツールバー表示/非表示切り替え else if( command.command == "toggle_toolbar" ){ toggle_toolbar(); } // ツールバーのアドレスを更新 else if( command.command == "update_toolbar_url" ){ m_notebook->update_toolbar_url( command.url, command.arg1 ); } // ツールバーのラベルを更新 else if( command.command == "redraw_toolbar" ){ redraw_toolbar(); } // ツールバーボタン表示更新 else if( command.command == "update_toolbar_button" ){ update_toolbar_button(); } // 検索バー表示 else if( command.command == "open_searchbar" ){ open_searchbar(); } // 検索バー非表示 else if( command.command == "close_searchbar" ){ close_searchbar(); } // タブ表示切り替え else if( command.command == "toggle_tab" ){ toggle_tab(); } // アイコン表示切り替え else if( command.command == "toggle_icon" ){ toggle_icon( command.url ); } // window 開け閉じ可能/不可 else if( command.command == "enable_fold_win" ){ if( get_jdwin() ) get_jdwin()->set_enable_fold( true ); } else if( command.command == "disable_fold_win" ){ if( get_jdwin() ) get_jdwin()->set_enable_fold( false ); } // プロパティ表示 else if( command.command == "show_preferences" ){ SKELETON::View* view = get_view( command.url ); if( view ) view->show_preference(); } else if( command.command == "show_current_preferences" ){ SKELETON::View* view = get_current_view(); if( view ) view->show_preference(); } // View履歴:戻る else if( command.command == "back_viewhistory" ){ back_viewhistory( command.url, atoi( command.arg1.c_str() ) ); } // View履歴:進む else if( command.command == "forward_viewhistory" ){ forward_viewhistory( command.url, atoi( command.arg1.c_str() ) ); } // View履歴削除 else if( command.command == "clear_viewhistory" ){ clear_viewhistory(); } // オートリロード開始 else if( command.command == "start_autoreload" ){ int mode = AUTORELOAD_ON; if( command.arg1 == "once" ) mode = AUTORELOAD_ONCE; int sec = atoi( command.arg2.c_str() ); set_autoreload_mode( command.url, mode, sec ); } // オートリロード停止 else if( command.command == "stop_autoreload" ){ set_autoreload_mode( command.url, AUTORELOAD_NOT, 0 ); } // ポップアップを隠す(インスタンスは削除しない) else if( command.command == "hide_popup" ) hide_popup(); else{ // ツールバー関係 SKELETON::View* view = get_view( command.url ); if( view && command.command == "toolbar_exec_search" ) view->exec_search(); else if( view && command.command == "toolbar_operate_search" ) view->operate_search( command.arg1 ); else if( view && command.command == "toolbar_set_search_query" ) view->set_search_query( command.arg1 ); if( view && command.command == "toolbar_up_search" ) view->up_search(); if( view && command.command == "toolbar_down_search" ) view->down_search(); else if( view && command.command == "toolbar_write" ) view->write(); else if( view && command.command == "toolbar_reload" ) view->reload(); else if( view && command.command == "toolbar_stop" ) view->stop(); else if( view && command.command == "toolbar_close_view" ) view->close_view(); else if( view && command.command == "toolbar_delete_view" ) view->delete_view(); else if( view && command.command == "toolbar_set_favorite" ) view->set_favorite(); // ロック/アンロック else if( view && command.command == "toolbar_lock_view" ){ if( view->is_locked() ) unlock( get_current_page() ); else lock( get_current_page() ); } // 子クラス別のコマンド処理 else command_local( command ); } } // リストで与えられたページをタブで連続して開く // // 連続してリロードかけるとサーバに負担をかけるので、オフラインで開いて // タイミングをずらしながらリロードする // void Admin::open_list( const COMMAND_ARGS& command_list ) { #ifdef _DEBUG std::cout << "Admin::open_list " << m_url << std::endl; #endif const std::string& str_list = command_list.arg1; std::list< std::string > list_url = MISC::split_line( str_list ); if( list_url.empty() ) return; int waittime = 0; const bool online = SESSION::is_online(); SESSION::set_tab_operating( m_url, true ); std::list< std::string >::iterator it = list_url.begin(); for( ; it != list_url.end(); ++it, waittime += AUTORELOAD_MINSEC ){ // 各admin別の引数をセット COMMAND_ARGS command_arg = get_open_list_args( ( *it ), command_list ); // 共通の引数をセット command_arg.command = "open_view"; command_arg.url = (*it); command_arg.arg1 = "true"; // タブで開く command_arg.arg2 = "false"; // 既に開いているかチェック command_arg.arg3 = "noswitch"; // タブを切り替えない if( ! command_list.arg2.empty() ) command_arg.arg3 += " " + command_list.arg2; #ifdef _DEBUG std::cout << "url = " << command_arg.url << std::endl; #endif open_view( command_arg ); // 一番最初のページは普通にオンラインで開く // 二番目からは ウェイトを入れてリロード if( online ){ if( !waittime ) SESSION::set_online( false ); else set_autoreload_mode( command_arg.url, AUTORELOAD_ONCE, waittime ); } } SESSION::set_online( online ); set_command( "set_tab_operating", "", "false" ); switch_admin(); switch_view( *( list_url.begin() ) ); } // // 移転などでviewのurlを更新 // void Admin::update_url( const std::string& url_old, const std::string& url_new ) { #ifdef _DEBUG std::cout << "Admin::update_url " << url_old << " -> " << url_new << std::endl; #endif const int pages = m_notebook->get_n_pages(); if( pages ){ for( int i = 0; i < pages; ++i ){ SKELETON::View* view = dynamic_cast< View* >( m_notebook->get_nth_page( i ) ); if( view && view->get_url().rfind( url_old, 0 ) == 0 ){ std::list< std::string >::iterator it = std::find( m_list_switchhistory.begin(), m_list_switchhistory.end(), view->get_url() ); view->update_url( url_old, url_new ); if( it != m_list_switchhistory.end() ) (*it) = view->get_url(); } } } } // // urlを含むviewの板名を更新 // void Admin::update_boardname( const std::string& url ) { #ifdef _DEBUG std::cout << "Admin::update_boardname url = " << url << std::endl; #endif const int pages = m_notebook->get_n_pages(); if( pages ){ for( int i = 0; i < pages; ++i ){ SKELETON::View* view = dynamic_cast< View* >( m_notebook->get_nth_page( i ) ); if( view && view->get_url().rfind( url, 0 ) == 0 ) view->update_boardname(); } redraw_toolbar(); } } // // URLやステータスを更新 // void Admin::update_status( View* view, const bool force ) { if( view ){ set_title( view->get_url(), view->get_title(), force ); set_url( view->get_url(), view->url_for_copy(), force ); set_status( view->get_url(), view->get_status(), force ); set_status_color( view->get_url(), view->get_color(), force ); } } // // ビューを開く // // command.arg1: 開く位置 // // "false" ならアクティブなタブを置き換える ( デフォルト ) // "true" なら一番右側に新しいタブを開く // "right" ならアクティブなタブの右に開く // "newtab" "opentab" なら設定によってアクティブなタブの右に開くか一番右側に開くか切り替える // "left" ならアクティブなタブの左に開く // "page数字" なら指定した位置にタブで開く // "replace" なら arg3 に指定したタブがあれば置き換え、なければ "newtab" で動作する // // command.arg2: "true" なら既に command.url を開いているかチェックしない // // command.arg3: モード ( 複数指定する場合は空白で空ける ) // // "auto"なら表示されていればリロードせずに切替える // されていなければarg1で指定した場所に新しいタブで開いてロード // スレ番号ジャンプなどで使用する // "noswitch"ならタブを切り替えない(連続して開くときに使用) // "lock" なら開いてからロックする // "offline" なら オフラインで開く // "reget" なら読み込み時にキャッシュ等を消してから再読み込みする // "boardnext" など、 "replace" で置き換えるタブの種類を指定する // // その他のargは各ビュー別の設定 // void Admin::open_view( const COMMAND_ARGS& command ) { #ifdef _DEBUG std::cout << "Admin::open_view : " << command.url << std::endl << "arg1 = " << command.arg1 << std::endl << "arg2 = " << command.arg2 << std::endl << "arg3 = " << command.arg3 << std::endl; #endif SKELETON::View* view; SKELETON::View* current_view = get_current_view(); const bool online = SESSION::is_online(); const bool nocheck_opened = ( command.arg2 == "true" ); int mode = 0; if( ! command.arg3.empty() ){ if( command.arg3.find( "auto" ) != std::string::npos ) mode |= OPEN_MODE_AUTO; if( command.arg3.find( "noswitch" ) != std::string::npos ) mode |= OPEN_MODE_NOSWITCH; if( command.arg3.find( "lock" ) != std::string::npos ) mode |= OPEN_MODE_LOCK; if( command.arg3.find( "offline" ) != std::string::npos ) mode |= OPEN_MODE_OFFLINE; if( command.arg3.find( "reget" ) != std::string::npos ) mode |= OPEN_MODE_REGET; } if( current_view ) current_view->focus_out(); // urlを既に開いていたら表示してリロード if( ! nocheck_opened ){ view = get_view( command_to_url( command ) ); if( view ){ // タブの切り替え if( ! ( mode & OPEN_MODE_NOSWITCH ) ){ int page = m_notebook->page_num( *view ); #ifdef _DEBUG std::cout << "page = " << page << std::endl; #endif set_current_page( page ); switch_admin(); } // オートモードは切り替えのみ if( mode & OPEN_MODE_AUTO ) return; // ロック if( mode & OPEN_MODE_LOCK ) lock( m_notebook->page_num( *view ) ); // オフラインで開く if( mode & OPEN_MODE_OFFLINE ) SESSION::set_online( false ); // ロード時にキャッシュを削除してからスレを再読み込みする if( mode & OPEN_MODE_REGET ) view->set_reget( true ); view->show_view(); SESSION::set_online( online ); return; } } // 開く位置の基準を、アクティブなタブに仮定 int page = m_notebook->get_current_page(); std::string open_method = command.arg1; if( open_method.empty() ){ open_method = "false"; } // 置き替えるページを探す if( open_method == "replace" ){ // 該当するタブが見つからない場合のために、新しいタブで開くモードに仮設定 open_method = "newtab"; // タブの種類 (command.arg3) に該当するタブを探す int find_page = find_view( command.arg3 ); #ifdef _DEBUG std::cout << "replace mode = " << command.arg3 << " page = " << page << " find = " << find_page << std::endl; #endif if( find_page >= 0 ){ SKELETON::View* found_view = dynamic_cast< View* >( m_notebook->get_nth_page( page ) ); if( found_view ){ // 指定したタブを置き換えるモードに設定 open_method = "false"; // 開く位置の基準を、見つかったタブに設定 page = find_page; current_view = found_view; } } } view = create_view( command ); if( !view ) return; const bool open_tab = ( page == -1 || open_method != "false" || ( mode & OPEN_MODE_AUTO ) // オートモードの時もタブで開く || is_locked( page ) ); // ツールバー表示 if( page == -1 ) show_toolbar(); // タブで表示 if( open_tab ){ // 一番右側に新しいタブを開くに仮定 int openpage = -1; // 現在のページの右に表示 if( open_method == "right" ) openpage = page +1; // 設定によって切り替える else if( open_method == "newtab" ){ switch( CONFIG::get_newtab_pos() ){ case 1: openpage = page +1; break; case 2: openpage = page; break; } } else if( open_method == "opentab" ){ switch( CONFIG::get_opentab_pos() ){ case 1: openpage = page +1; break; case 2: openpage = page; break; } } // 現在のページの左に表示 else if( open_method == "left" ) openpage = page; // 指定した位置に表示 else if( open_method.rfind( "page", 0 ) == 0 ) openpage = atoi( open_method.c_str() + 4 ); // ロックされていたら右に表示 else if( open_method == "false" && page != -1 && is_locked( page ) ) openpage = page +1; #ifdef _DEBUG std::cout << "append openpage = " << openpage << " / page = " << page << std::endl; #endif if( page != -1 ) m_notebook->insert_page( command.url, *view, openpage ); // 最後に表示 else m_notebook->append_page( command.url, *view ); if( m_use_viewhistory ){ if( m_last_closed_url.empty() ) HISTORY::get_history_manager()->create_viewhistory( view->get_url() ); // 直前に閉じたViewの履歴を次に開いたViewに引き継ぐ else{ HISTORY::get_history_manager()->append_viewhistory( m_last_closed_url, view->get_url() ); m_last_closed_url = std::string(); } } } // 開いてるviewを消してその場所に表示 else{ #ifdef _DEBUG std::cout << "replace page\n"; #endif // タブを入れ替えたときに隣のタブの再描画を防ぐ set_command_immediately( "set_tab_operating", "", "true" ); // タブ入れ替え m_notebook->insert_page( command.url, *view, page ); m_notebook->remove_page( page + 1, false ); if( current_view ){ const std::string url_current = current_view->get_url(); delete current_view; remove_switchhistory( url_current ); if( m_use_viewhistory ){ // Aを開く -> B をタブで開く -> A を閉じる -> A を開いて B と置き換える(*) -> B をタブで開く // とすると、History_Manager::get_viewhistory()のキャッシュが誤作動して // ビューの戻る進むが変になるので、(*)の所で進む戻るの履歴を引き継ぐのをキャンセルする if( ! m_last_closed_url.empty() && view->get_url() == m_last_closed_url ){ HISTORY::get_history_manager()->delete_viewhistory( m_last_closed_url ); m_last_closed_url = std::string(); } HISTORY::get_history_manager()->append_viewhistory( url_current, view->get_url() ); } } #ifdef _DEBUG std::cout << "replace done\n"; #endif set_command( "set_tab_operating", "", "false" ); } m_notebook->show_all(); view->show(); // オフラインで開く if( mode & OPEN_MODE_OFFLINE ) SESSION::set_online( false ); // ロード時にキャッシュを削除してからスレを再読み込みする if( mode & OPEN_MODE_REGET ) view->set_reget( true ); view->show_view(); SESSION::set_online( online ); // ツールバーの情報更新 m_notebook->set_current_toolbar( view->get_id_toolbar(), view ); // タブの切り替え if( ! ( mode & OPEN_MODE_NOSWITCH ) ){ switch_admin(); set_current_page( m_notebook->page_num( *view ) ); } // ロック if( mode & OPEN_MODE_LOCK ) lock( m_notebook->page_num( *view ) ); } // // ツールバーの検索ボックスをフォーカス // void Admin::focus_toolbar_search() { m_notebook->focus_toolbar_search(); } // // ツールバー表示更新 // void Admin::redraw_toolbar() { SKELETON::View* view = get_current_view(); if( view ) m_notebook->set_current_toolbar( view->get_id_toolbar(), view ); } // // ツールバーのボタン表示更新 // void Admin::update_toolbar_button() { m_notebook->update_toolbar_button(); redraw_toolbar(); } // // ビュー切り替え // // 指定したURLのビューに切り替える // void Admin::switch_view( const std::string& url ) { SKELETON::View* view = get_view( url ); if( view ){ int page = m_notebook->page_num( *view ); set_current_page( page ); } } // // 指定したURLのビューを再読み込み // void Admin::reload_view( const std::string& url ) { SKELETON::View* view = get_view( url ); if( view ) view->reload(); } // // タブ左移動 // // updated == true の時は更新されたタブに移動 // void Admin::tab_left( const bool updated ) { const int pages = m_notebook->get_n_pages(); if( pages == 1 ) return; int page = m_notebook->get_current_page(); if( page == -1 ) return; #ifdef _DEBUG std::cout << "Admin::tab_left updated << " << updated << " page = " << page; #endif for( int i = 0; i < pages ; ++i ){ --page; if( page < 0 ) page = pages -1; if( ! updated ) break; SKELETON::View* view = dynamic_cast< View* >( m_notebook->get_nth_page( page ) ); if( ! view ) return; // updated アイコンが表示されているタブを見つける if( get_notebook()->get_tabicon( page ) == view->get_icon( "updated" ) ) break; } #ifdef _DEBUG std::cout << " -> " << page << std::endl; #endif if( page != m_notebook->get_current_page() ) set_current_page( page ); } // // タブ右移動 // // updated == true の時は更新されたタブに移動 // void Admin::tab_right( const bool updated ) { const int pages = m_notebook->get_n_pages(); if( pages == 1 ) return; int page = m_notebook->get_current_page(); if( page == -1 ) return; #ifdef _DEBUG std::cout << "Admin::tab_right updated << " << updated << " page = " << page; #endif for( int i = 0; i < pages; ++i ){ ++page; if( page >= pages ) page = 0; if( ! updated ) break; SKELETON::View* view = dynamic_cast< View* >( m_notebook->get_nth_page( page ) ); if( ! view ) return; // updated アイコンが表示されているタブを見つける if( get_notebook()->get_tabicon( page ) == view->get_icon( "updated" ) ) break; } #ifdef _DEBUG std::cout << " -> " << page << std::endl; #endif if( page != m_notebook->get_current_page() ) set_current_page( page ); } // // タブ位置(1-9)で移動 // void Admin::tab_num( const std::string& str_num ) { if( str_num.empty() ) return; const int num = strtol( str_num.c_str(), nullptr, 10 ); // Firefoxの動作に合わせた // 0 → 無視 // 8以下で存在する数より多い → 無視 // 9(存在しない) → 最後のタブ if ( num < 1 || num > 9 || ( num < 9 && num > get_tab_nums() ) ) return; // 9は存在しなくてもそのまま渡してしまう set_current_page( num - 1 ); } // // タブ先頭移動 // void Admin::tab_head() { const int pages = m_notebook->get_n_pages(); if( pages == 1 ) return; set_current_page( 0 ); } // // フォーカスしてタブ先頭移動 // void Admin::tab_head_focus() { if( ! m_focus ) switch_admin(); tab_head(); } // // タブ最後に移動 // void Admin::tab_tail() { const int pages = m_notebook->get_n_pages(); if( pages == 1 ) return; set_current_page( pages-1 ); } // // タブ最後に移動 // void Admin::tab_tail_focus() { if( ! m_focus ) switch_admin(); tab_tail(); } // // ビューを再描画 // void Admin::redraw_view( const std::string& url ) { #ifdef _DEBUG std::cout << "Admin::redraw_view : " << m_url << " : url = " << url << std::endl; #endif SKELETON::View* view = get_view( url ); if( view ) view->redraw_view(); } // // 現在のビューを再描画 // void Admin::redraw_current_view() { #ifdef _DEBUG std::cout << "Admin::redraw_current_view : " << m_url << std::endl; #endif SKELETON::View* view = get_current_view(); if( view ) view->redraw_view(); } // // 現在のビューを再レイアウト // void Admin::relayout_current_view() { #ifdef _DEBUG std::cout << "Admin::relayout_current_view : " << m_url << std::endl; #endif SKELETON::View* view = get_current_view(); if( view ) view->relayout(); } // // urlを含むビューを検索してそれがカレントならば再描画 // void Admin::redraw_views( const std::string& url ) { #ifdef _DEBUG std::cout << "Admin::redraw_view : " << m_url << " : url = " << url << std::endl; #endif SKELETON::View* current_view = get_current_view(); std::list< SKELETON::View* > list_view = get_list_view( url ); for( SKELETON::View* view : list_view ) { if( view == current_view ) view->redraw_view(); } } // // ビューを閉じる // void Admin::close_view( const std::string& url ) { #ifdef _DEBUG std::cout << "Admin::close_view : " << url << std::endl; #endif SKELETON::View* view = get_view( url ); close_view( view ); } void Admin::close_view( SKELETON::View* view ) { if( !view ) return; int page = m_notebook->page_num( *view ); int current_page = m_notebook->get_current_page(); if( is_locked( page ) ) return; remove_switchhistory( view->get_url() ); if( page == current_page ){ // その前に開いていたビューに切り替える const std::string hist_url = get_valid_switchhistory(); if( ! hist_url.empty() ) switch_view( hist_url ); // もし現在表示中のビューを消すときは予めひとつ右のビューにスイッチしておく // そうしないと左のビューを一度表示してしまうので遅くなる else{ SKELETON::View* newview = dynamic_cast< View* >( m_notebook->get_nth_page( page + 1 ) ); if( newview ) switch_view( newview->get_url() ); } } m_notebook->remove_page( page, true ); if( m_use_viewhistory ){ // 直前に閉じたViewの履歴を次に開いたViewに引き継ぐ if( ! m_last_closed_url.empty() ) HISTORY::get_history_manager()->delete_viewhistory( m_last_closed_url ); m_last_closed_url = view->get_url(); } delete view; #ifdef _DEBUG std::cout << "Admin::close_view : delete page = " << page << std::endl; #endif // 全てのビューが無くなったらコアに知らせる if( empty() ){ #ifdef _DEBUG std::cout << "empty\n"; #endif m_notebook->hide_toolbar(); CORE::core_set_command( "empty_page", m_url ); } } // // url を含むビューを全てアンロック // void Admin::unlock_all_view( const std::string& url ) { #ifdef _DEBUG std::cout << "Admin::unlock_all_view : " << url << std::endl; #endif std::list< View* > list_view = get_list_view( url ); for( SKELETON::View* view : list_view ) { if( view && view->is_locked() ){ view->unlock(); redraw_toolbar(); } } } // // url を含むビューを全て閉じる // void Admin::close_all_view( const std::string& url ) { #ifdef _DEBUG std::cout << "Admin::close_all_view : " << url << std::endl; #endif std::list< View* > list_view = get_list_view( url ); for( SKELETON::View* view : list_view ) { close_view( view ); } } // // url 以外のビューを全て閉じる // void Admin::close_other_views( const std::string& url ) { const int pages = m_notebook->get_n_pages(); for( int i = 0; i < pages; ++i ){ SKELETON::View* view = dynamic_cast< View* >( m_notebook->get_nth_page( i ) ); if( view && view->get_url() != url ) set_command( "close_view", view->get_url() ); } } // // 現在のビューを閉じる // void Admin::close_current_view() { #ifdef _DEBUG std::cout << "Admin::close_current_view : " << m_url << std::endl; #endif SKELETON::View* view = get_current_view(); if( view ) close_view( view->get_url() ); } // // ビューを更新する // void Admin::update_view( const std::string& url ) { #ifdef _DEBUG std::cout << "Admin::update_view : " << url << std::endl; #endif SKELETON::View* view = get_view( url ); if( view ) view->update_view(); } // // ビューの一部(例えばBoardViewなら行など)を更新 // void Admin::update_item( const std::string& url, const std::string& id ) { #ifdef _DEBUG std::cout << "Admin::update_item : " << url << " " << id << std::endl; #endif std::list< SKELETON::View* > list_view = get_list_view(); for( SKELETON::View* view : list_view ) { if( view ) view->update_item( url, id ); } } // // ビューに更新終了を知らせる // // update_view はデータロード時などの更新途中でも呼び出されるが // update_finish は最後に一度だけ呼び出される // void Admin::update_finish( const std::string& url ) { #ifdef _DEBUG std::cout << "Admin::update_finish : " << url << std::endl; #endif SKELETON::View* view = get_view( url ); if( view ) view->update_finish(); } // // タイトル表示 // void Admin::set_title( const std::string& url, const std::string& title, const bool force ) { if( m_win ) m_win->set_title( "JDim - " + title ); else{ SKELETON::View* view = get_current_view(); if( view ){ // アクティブなviewからコマンドが来たら表示する if( force || ( m_focus && view->get_url() == url ) ) CORE::core_set_command( "set_title", url, title ); } } } // // URLバーにアドレス表示 // void Admin::set_url( const std::string& url, const std::string& url_show, const bool force ) { if( m_win ){} else{ SKELETON::View* view = get_current_view(); if( view ){ // アクティブなviewからコマンドが来たら表示する if( force || ( m_focus && view->get_url() == url ) ){ CORE::core_set_command( "set_url", url_show ); // ツリービューのURLを選択 if( CONFIG::get_select_item_sync() != 0 ){ CORE::core_set_command( "select_sidebar_item", url ); CORE::core_set_command( "select_board_item", url ); } } } } } // // ステータス表示 // // virtual void Admin::set_status( const std::string& url, const std::string& stat, const bool force ) { if( m_win ) m_win->set_status( stat ); else{ SKELETON::View* view = get_current_view(); if( view ){ // アクティブなviewからコマンドが来たら表示する if( force || ( m_focus && view->get_url() == url ) ){ CORE::core_set_command( "set_status", url, stat ); CORE::core_set_command( "set_mginfo", "", "" ); } } } } // // ステータスの色を変える // void Admin::set_status_color( const std::string& url, const std::string& color, const bool force ) { if( m_win ) m_win->set_status_color( color ); else{ SKELETON::View* view = get_current_view(); if( view ){ // アクティブなviewからコマンドが来たら色を変える if( force || ( m_focus && view->get_url() == url ) ){ CORE::core_set_command( "set_status_color", url, color ); } } } } // // フォーカスする // // タイトルやURLバーやステータス表示も更新する // void Admin::focus_view( int page ) { #ifdef _DEBUG std::cout << "Admin::focus_view : " << m_url << " page = " << page << std::endl; #endif if( m_win ) m_win->focus_in(); SKELETON::View* view = dynamic_cast< View* >( m_notebook->get_nth_page( page ) ); if( view ) { view->focus_view(); update_status( view, false ); } } // // 現在のviewをフォーカスする // void Admin::focus_current_view() { if( ! m_focus ) return; #ifdef _DEBUG std::cout << "Admin::focus_current_view : " << m_url << std::endl; #endif int page = m_notebook->get_current_page(); focus_view( page ); } // // フォーカスアウトしたあとにフォーカス状態を回復する // // focus_current_view()と違ってURLバーやステータスを再描画しない // void Admin::restore_focus() { #ifdef _DEBUG std::cout << "Admin::restore_focus : " << m_url << std::endl; #endif int page = m_notebook->get_current_page(); SKELETON::View* view = dynamic_cast< View* >( m_notebook->get_nth_page( page ) ); if( view ) view->focus_view(); } // // 現在のviewをフォーカスアウトする // メインウィンドウがフォーカスアウトしたときなどに呼ばれる // void Admin::focus_out() { #ifdef _DEBUG std::cout << "Admin::focus_out : " << m_url << std::endl; #endif // ウィンドウ表示の時、ビューが隠れないようにフォーカスアウトする前に transient 指定をしておく if( get_jdwin() ) get_jdwin()->set_transient( true ); SKELETON::View* view = get_current_view(); if( view ) view->focus_out(); } // // タブラベル更新 // void Admin::set_tablabel( const std::string& url, const std::string& str_label ) { #ifdef _DEBUG std::cout << "Admin::set_tablabel : " << url << std::endl; #endif SKELETON::View* view = get_view( url ); if( view ){ m_notebook->set_tab_fulltext( str_label, m_notebook->page_num( *view ) ); // View履歴のタイトルも更新 if( m_use_viewhistory ){ HISTORY::get_history_manager()->replace_current_title_viewhistory( view->get_url(), str_label ); } } } // // 再レイアウト実行 // void Admin::relayout_all() { std::list< SKELETON::View* > list_view = get_list_view(); for( SKELETON::View* view : list_view ) { if( view ) view->relayout(); } } // // タブ表示切り替え // void Admin::toggle_tab() { if( m_notebook->get_show_tabs() ) m_notebook->set_show_tabs( false ); else m_notebook->set_show_tabs( true ); } // // アイコン表示切り替え // void Admin::toggle_icon( const std::string& url ) { #ifdef _DEBUG std::cout << "Admin::toggle_icon url = " << url << std::endl; #endif SKELETON::View* view = get_view( url ); if( view ){ std::string iconname = "default"; // まだロード中 if( view->is_loading() ) iconname = "loading"; // オートリロードモードでロード待ち else if( view->get_autoreload_mode() != AUTORELOAD_NOT ) iconname = "loading_stop"; // 古い else if( view->is_old() ) iconname = "old"; // 更新チェックして更新可能 ( 更新はしていない ) else if( view->is_check_update() ) iconname = "update"; // 更新済み ( HTTP_OK 又は HTTP_PARTIAL_CONTENT ) else if( view->is_updated() ){ // タブがアクティブの時は通常アイコンを表示 if( get_notebook()->page_num( *view ) == get_notebook()->get_current_page() ) iconname = "default"; else iconname = "updated"; } #ifdef _DEBUG std::cout << "name = " << iconname << std::endl; #endif const int id = view->get_icon( iconname ); get_notebook()->set_tabicon( iconname, get_notebook()->page_num( *view ), id ); if( m_move_menu ) m_move_menu->update_icons(); if( m_tabswitchmenu ) m_tabswitchmenu->update_icons(); } } // // オートリロードのモード設定 // // 成功したらtrueを返す // // mode : モード (global.h 参照) // sec : リロードまでの秒数 // bool Admin::set_autoreload_mode( const std::string& url, int mode, int sec ) { SKELETON::View* view = get_view( url ); if( view ){ if( mode == AUTORELOAD_NOT && view->get_autoreload_mode() == AUTORELOAD_NOT ) return false; view->set_autoreload_mode( mode, sec ); // モード設定が成功したらアイコン変更 if( view->get_autoreload_mode() == mode ){ toggle_icon( view->get_url() ); return true; } } return false; } // ポップアップを隠す(インスタンスは削除しない) void Admin::hide_popup() { SKELETON::View* view = get_current_view(); if( view ) view->set_command( "hide_popup" ); } // // ビュークラス取得 // View* Admin::get_view( const std::string& url ) { SKELETON::View* view = get_current_view(); if( view && view->get_url() == url ) return view; const int pages = m_notebook->get_n_pages(); if( pages ){ for( int i = 0; i < pages; ++i ){ view = dynamic_cast< View* >( m_notebook->get_nth_page( i ) ); if( view && view->get_url() == url ) return view; } } return nullptr; } // // ビュークラスのリスト取得 // // url を含むビューのリストを返す // std::list< View* > Admin::get_list_view( const std::string& url ) { std::list< View* > list_view; const int pages = m_notebook->get_n_pages(); if( pages ){ for( int i = 0; i < pages; ++i ){ SKELETON::View* view = dynamic_cast< View* >( m_notebook->get_nth_page( i ) ); if( view && view->get_url().find ( url ) != std::string::npos ) list_view.push_back( view ); } } return list_view; } // // 全てのビュークラスのリスト取得 // // std::list< View* > Admin::get_list_view() { std::list< View* > list_view; const int pages = m_notebook->get_n_pages(); if( pages ){ for( int i = 0; i < pages; ++i ){ SKELETON::View* view = dynamic_cast< View* >( m_notebook->get_nth_page( i ) ); if( view ) list_view.push_back( view ); } } return list_view; } // // 現在表示されているビュークラス取得 // View* Admin::get_current_view() { int page = m_notebook->get_current_page(); if( page == -1 ) return nullptr; SKELETON::View* view = dynamic_cast< View* >( m_notebook->get_nth_page( page ) ); return view; } // // 指定したページに表示切替え // void Admin::set_current_page( const int page ) { m_notebook->set_current_page( page ); } // // フォーカスしてから指定したページに表示切替え // void Admin::set_current_page_focus( const int page ) { if( ! m_focus ) switch_admin(); m_notebook->set_current_page( page ); } // // 現在表示されているページ番号 // int Admin::get_current_page() { return m_notebook->get_current_page(); } // // 現在表示されているページのURL // std::string Admin::get_current_url() { SKELETON::View* view = get_current_view(); if( ! view ) return std::string(); return view->get_url(); } // // notebookのタブのページが切り替わったら呼ばれるslot // void Admin::slot_switch_page( Gtk::Widget*, guint page ) { // 起動中とシャットダウン中は処理しない if( SESSION::is_booting() ) return; if( SESSION::is_quitting() ) return; // タブ操作中 if( SESSION::is_tab_operating( m_url ) ) return; #ifdef _DEBUG std::cout << "Admin::slot_switch_page : " << m_url << " page = " << page << " focus = " << m_focus << std::endl; #endif SKELETON::View* view = dynamic_cast< View* >( m_notebook->get_nth_page( page ) ); if( view ){ #ifdef _DEBUG std::cout << "url = " << view->get_url() << std::endl; #endif // ツールバー表示切り替え m_notebook->set_current_toolbar( view->get_id_toolbar(), view ); // タブのアイコンを通常に戻して再描画 toggle_icon( view->get_url() ); view->redraw_view(); if( m_focus ){ update_status( view, false ); focus_current_view(); } append_switchhistory( view->get_url() ); CORE::core_set_command( "page_switched", m_url, view->get_url() ); } } // // タブをクリックした // void Admin::slot_tab_clicked( const int page ) { #ifdef _DEBUG std::cout << "Admin::slot_tab_clicked " << page << std::endl; #endif if( ! m_focus ) switch_admin(); } // タブの上でホイールを回した void Admin::slot_tab_scrolled( GdkEventScroll* event ) { #ifdef _DEBUG std::cout << "Admin::slot_tab_scrolled\n"; #endif if( ! m_focus ) switch_admin(); } // // タブを閉じる // void Admin::slot_tab_close( const int page ) { #ifdef _DEBUG std::cout << "Admin::slot_tab_close " << page << std::endl; #endif // 閉じる SKELETON::View* view = dynamic_cast< View* >( m_notebook->get_nth_page( page ) ); if( view ) close_view( view->get_url() ); } // // タブ再読み込み // void Admin::slot_tab_reload( const int page ) { #ifdef _DEBUG std::cout << "Admin::slot_tab_reload " << page << std::endl; #endif SKELETON::View* view = dynamic_cast< View* >( m_notebook->get_nth_page( page ) ); if( view ) view->reload(); } // // タブメニュー表示 // void Admin::slot_tab_menu( int page, int x, int y ) { if( ! m_ui_manager ) return; #ifdef _DEBUG std::cout << "Admin::slot_tab_menu " << page << std::endl; #endif Glib::RefPtr< Gtk::Action > act; m_clicked_page = -1; // メニューのactive状態を変えたときにslot関数が呼び出されるのをキャンセル SKELETON::View* view = dynamic_cast< View* >( m_notebook->get_nth_page( page ) ); // ロック act = m_action_group->get_action( "LockTab" ); if( page >= 0 && act ){ if( ! is_lockable( page ) ) act->set_sensitive( false ); else{ act->set_sensitive( true ); Glib::RefPtr< Gtk::ToggleAction > tact = Glib::RefPtr< Gtk::ToggleAction >::cast_dynamic( act ); if( is_locked( page ) ) tact->set_active( true ); else tact->set_active( false ); } } // 閉じる act = m_action_group->get_action( "Quit" ); if( act ){ if( is_locked( page ) ) act->set_sensitive( false ); else act->set_sensitive( true ); } // 進む、戻る if( view ){ act = m_action_group->get_action( "PrevView" ); if( act ){ if( HISTORY::get_history_manager()->can_back_viewhistory( view->get_url(), 1 ) ) act->set_sensitive( true ); else act->set_sensitive( false ); } act = m_action_group->get_action( "NextView" ); if( act ){ if( HISTORY::get_history_manager()->can_forward_viewhistory( view->get_url(), 1 ) ) act->set_sensitive( true ); else act->set_sensitive( false ); } } m_clicked_page = page; Gtk::Menu* popupmenu = dynamic_cast< Gtk::Menu* >( m_ui_manager->get_widget( "/popup_menu" ) ); if( popupmenu && m_move_menu ){ m_move_menu->remove_items(); m_move_menu->append_items(); m_move_menu->update_labels(); m_move_menu->update_icons(); Gtk::Label* label = dynamic_cast< Gtk::Label* >( m_move_menuitem->get_child() ); if( label ) label->set_text_with_mnemonic( ITEM_NAME_GO + std::string( " [ タブ数 " ) + std::to_string( m_notebook->get_n_pages() ) +" ](_M)" ); popupmenu->popup_at_pointer( nullptr ); // current event } } // タブ切り替えメニュー表示 void Admin::slot_show_tabswitchmenu() { #ifdef _DEBUG std::cout << "Admin::slot_show_tabswitchmenu\n"; #endif if( ! m_tabswitchmenu ) m_tabswitchmenu = std::make_unique( m_notebook.get(), this ); m_tabswitchmenu->remove_items(); m_tabswitchmenu->append_items(); m_tabswitchmenu->update_labels(); m_tabswitchmenu->update_icons(); // Specify the current event by nullptr. m_tabswitchmenu->popup_at_widget( &( m_notebook->get_tabswitch_button() ), Gdk::GRAVITY_SOUTH_EAST, Gdk::GRAVITY_NORTH_EAST, nullptr ); } // // 右クリックメニューの閉じる // void Admin::slot_close_tab() { if( m_clicked_page < 0 ) return; #ifdef _DEBUG std::cout << "Admin::slot_close_tab " << m_clicked_page << std::endl; #endif SKELETON::View* view = dynamic_cast< View* >( m_notebook->get_nth_page( m_clicked_page ) ); if( view ) close_view( view->get_url() ); } // // ロック // void Admin::slot_lock() { if( m_clicked_page < 0 ) return; if( is_locked( m_clicked_page ) ) unlock( m_clicked_page ); else lock( m_clicked_page ); } // // 右クリックメニューの他を閉じる // void Admin::slot_close_other_tabs() { #ifdef _DEBUG std::cout << "Admin::slot_close_other_tabs " << m_clicked_page << std::endl; #endif set_command( "set_tab_operating", "", "true" ); std::string url; SKELETON::View* view = dynamic_cast< View* >( m_notebook->get_nth_page( m_clicked_page ) ); if( view ) url = view->get_url(); close_other_views( url ); set_command( "set_tab_operating", "", "false" ); } // // 右クリックメニューの左を閉じる // void Admin::slot_close_left_tabs() { #ifdef _DEBUG std::cout << "Admin::slot_close_left_tabs " << m_clicked_page << std::endl; #endif set_command( "set_tab_operating", "", "true" ); for( int i = 0; i < m_clicked_page; ++i ){ SKELETON::View* view = dynamic_cast< View* >( m_notebook->get_nth_page( i ) ); if( view ) set_command( "close_view", view->get_url() ); } set_command( "set_tab_operating", "", "false" ); } // // 右クリックメニューの右を閉じる // void Admin::slot_close_right_tabs() { #ifdef _DEBUG std::cout << "Admin::slot_close_right_tabs " << m_clicked_page << std::endl; #endif set_command( "set_tab_operating", "", "true" ); const int pages = m_notebook->get_n_pages(); for( int i = m_clicked_page +1; i < pages; ++i ){ SKELETON::View* view = dynamic_cast< View* >( m_notebook->get_nth_page( i ) ); if( view ) set_command( "close_view", view->get_url() ); } set_command( "set_tab_operating", "", "false" ); } // // 右クリックメニューの全てを閉じる // void Admin::slot_close_all_tabs() { #ifdef _DEBUG std::cout << "Admin::slot_close_all_tabs " << m_clicked_page << std::endl; #endif set_command( "set_tab_operating", "", "true" ); const int pages = m_notebook->get_n_pages(); for( int i = 0; i < pages; ++i ){ SKELETON::View* view = dynamic_cast< View* >( m_notebook->get_nth_page( i ) ); if( view ) set_command( "close_view", view->get_url() ); } set_command( "set_tab_operating", "", "false" ); } // // 右クリックメニューの同じアイコンのタブを閉じる // void Admin::slot_close_same_icon_tabs() { const int id = get_notebook()->get_tabicon( m_clicked_page ); #ifdef _DEBUG std::cout << "Admin::slot_close_same_icon_tabs page = " << m_clicked_page << " id = " << id << std::endl; #endif set_command( "set_tab_operating", "", "true" ); const int pages = m_notebook->get_n_pages(); for( int i = 0; i < pages; ++i ){ if( get_notebook()->get_tabicon( i ) == id ){ SKELETON::View* view = dynamic_cast< View* >( m_notebook->get_nth_page( i ) ); if( view ) set_command( "close_view", view->get_url() ); } } set_command( "set_tab_operating", "", "false" ); } // // 右クリックメニューの全てのタブの更新チェック // void Admin::slot_check_update_all_tabs() { check_update_all_tabs( m_clicked_page, false ); } // // 右クリックメニューの全てのタブの更新チェックと再読み込み // void Admin::slot_check_update_reload_all_tabs() { check_update_all_tabs( m_clicked_page, true ); } // // 開いているタブから全てのタブの更新チェックと再読み込み // void Admin::check_update_all_tabs( const bool open ) { check_update_all_tabs( m_notebook->get_current_page(), open ); } // // from_page から全てのタブを更新チェック // void Admin::check_update_all_tabs( const int from_page, const bool open ) { #ifdef _DEBUG std::cout << "Admin::check_update_all_tabs from = " << from_page << std::endl; #endif if( ! SESSION::is_online() ) return; const int pages = m_notebook->get_n_pages(); // 開始タブから右側 for( int i = from_page ; i < pages; ++i ){ SKELETON::View* view = dynamic_cast< View* >( m_notebook->get_nth_page( i ) ); if( view && view->get_enable_autoreload() ) CORE::get_checkupdate_manager()->push_back( view->get_url(), open ); } // 開始タブから左側 for( int i = 0 ; i < from_page; ++i ){ SKELETON::View* view = dynamic_cast< View* >( m_notebook->get_nth_page( i ) ); if( view && view->get_enable_autoreload() ) CORE::get_checkupdate_manager()->push_back( view->get_url(), open ); } CORE::get_checkupdate_manager()->run(); } // // 右クリックメニューの全ての再読み込み // void Admin::slot_reload_all_tabs() { #ifdef _DEBUG std::cout << "Admin::slot_reload_all_tabs " << m_clicked_page << std::endl; #endif reload_all_tabs( m_clicked_page ); } // // 開いているタブから全てのタブを再読み込み // void Admin::reload_all_tabs() { reload_all_tabs( m_notebook->get_current_page() ); } // // from_page から全てのタブを再読み込み // void Admin::reload_all_tabs( const int from_page ) { #ifdef _DEBUG std::cout << "Admin::reload_all_tabs from = " << from_page << std::endl; #endif if( ! SESSION::is_online() ) return; int waittime = 0; const int pages = m_notebook->get_n_pages(); // クリックしたタブから右側 for( int i = from_page ; i < pages; ++i ){ SKELETON::View* view = dynamic_cast< View* >( m_notebook->get_nth_page( i ) ); if( view ){ if( set_autoreload_mode( view->get_url(), AUTORELOAD_ONCE, waittime ) ) waittime += AUTORELOAD_MINSEC; } } // クリックしたタブから左側 for( int i = 0 ; i < from_page; ++i ){ SKELETON::View* view = dynamic_cast< View* >( m_notebook->get_nth_page( i ) ); if( view ){ if( set_autoreload_mode( view->get_url(), AUTORELOAD_ONCE, waittime ) ) waittime += AUTORELOAD_MINSEC; } } } // // 右クリックメニューの更新キャンセル // void Admin::slot_cancel_reload_all_tabs() { for( int i = 0 ; i < m_notebook->get_n_pages(); ++i ){ SKELETON::View* view = dynamic_cast< View* >( m_notebook->get_nth_page( i ) ); if( view ) set_autoreload_mode( view->get_url(), AUTORELOAD_NOT, 0 ); } CORE::get_checkupdate_manager()->stop(); } // // 右クリックメニューのブラウザで開く // void Admin::slot_open_by_browser() { #ifdef _DEBUG std::cout << "Admin::slot_open_by_browser " << m_clicked_page << std::endl; #endif SKELETON::View* view = dynamic_cast< View* >( m_notebook->get_nth_page( m_clicked_page ) ); if( view ) CORE::core_set_command( "open_url_browser", view->url_for_copy() ); } // // 右クリックメニューのURLコピー // void Admin::slot_copy_url() { SKELETON::View* view = dynamic_cast< View* >( m_notebook->get_nth_page( m_clicked_page ) ); if( view ) MISC::CopyClipboard( view->url_for_copy() ); } // // 右クリックメニューのタイトルとURLコピー // void Admin::slot_copy_title_url() { std::string str = m_notebook->get_tab_fulltext( m_clicked_page ); SKELETON::View* view = dynamic_cast< View* >( m_notebook->get_nth_page( m_clicked_page ) ); if( view ) str += "\n" + view->url_for_copy(); MISC::CopyClipboard( str ); } // ページがロックされているかリストで取得 std::list< bool > Admin::get_locked() { std::list< bool > locked; const int pages = m_notebook->get_n_pages(); if( pages ){ for( int i = 0; i < pages; ++i ) locked.push_back( is_locked( i ) ); } return locked; } // タブのロック/アンロック bool Admin::is_lockable( const int page ) { SKELETON::View* view = dynamic_cast< View* >( m_notebook->get_nth_page( page ) ); if( view ) return view->is_lockable(); return false; } bool Admin::is_locked( const int page ) { SKELETON::View* view = dynamic_cast< View* >( m_notebook->get_nth_page( page ) ); if( view ) return view->is_locked(); return false; } bool Admin::is_locked( const std::string& url ) { SKELETON::View* view = get_view( url ); if( view ) return view->is_locked(); return false; } void Admin::lock( const int page ) { SKELETON::View* view = dynamic_cast< View* >( m_notebook->get_nth_page( page ) ); if( view ){ view->lock(); redraw_toolbar(); } } void Admin::unlock( const int page ) { SKELETON::View* view = dynamic_cast< View* >( m_notebook->get_nth_page( page ) ); if( view ){ view->unlock(); redraw_toolbar(); } } // urlで指定されるタブが存在するか bool Admin::exist_tab( const std::string& url ) { SKELETON::View* view = get_view( url ); if( view ) return true; return false; } // プロパティ表示 void Admin::show_preference() { SKELETON::View* view = dynamic_cast< View* >( m_notebook->get_nth_page( m_clicked_page ) ); if( view ) view->show_preference(); } // // View履歴:戻る // bool Admin::back_viewhistory( const std::string& url, const int count ) { return back_forward_viewhistory( url, true, count ); } void Admin::back_clicked_viewhistory( const int count ) { if( m_clicked_page < 0 ) return; SKELETON::View* view = dynamic_cast< View* >( m_notebook->get_nth_page( m_clicked_page ) ); if( ! view ) return; set_current_page( m_clicked_page ); back_forward_viewhistory( view->get_url(), true, count ); } // // View履歴進む // bool Admin::forward_viewhistory( const std::string& url, const int count ) { return back_forward_viewhistory( url, false, count ); } void Admin::forward_clicked_viewhistory( const int count ) { if( m_clicked_page < 0 ) return; SKELETON::View* view = dynamic_cast< View* >( m_notebook->get_nth_page( m_clicked_page ) ); if( ! view ) return; set_current_page( m_clicked_page ); back_forward_viewhistory( view->get_url(), false, count ); } // // 戻る、進む // bool Admin::back_forward_viewhistory( const std::string& url, const bool back, const int count ) { if( ! m_use_viewhistory ) return false; #ifdef _DEBUG std::cout << "Admin::back_forward_viewhistory back = " << back << "count = " << count << " tab = " << url << std::endl; #endif SKELETON::View* view = get_view( url ); if( view ){ if( view->is_locked() ) return false; const HISTORY::ViewHistoryItem* historyitem; if( back ) historyitem = HISTORY::get_history_manager()->back_viewhistory( url, count, false ); else historyitem = HISTORY::get_history_manager()->forward_viewhistory( url, count, false ); if( historyitem && ! historyitem->url.empty() ){ #ifdef _DEBUG std::cout << "open : " << historyitem->url << std::endl; #endif // 既にタブで開いている場合 if( get_view( historyitem->url) ){ // 次のviewを開けるかチェック bool enable_next = false; if( back ) enable_next = HISTORY::get_history_manager()->can_back_viewhistory( url, count +1 ); else enable_next = HISTORY::get_history_manager()->can_forward_viewhistory( url, count +1 ); SKELETON::MsgDiag mdiag( get_win(), historyitem->title + "\n\nは既にタブで開いています", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_NONE ); mdiag.add_button( "タブを開く(_T)", Gtk::RESPONSE_YES ); if( enable_next ) mdiag.add_button( "次を開く(_N)", Gtk::RESPONSE_NO ); mdiag.add_button( g_dgettext( GTK_DOMAIN, "_Cancel" ), Gtk::RESPONSE_CANCEL ); int ret = mdiag.run(); mdiag.hide(); switch( ret ){ case Gtk::RESPONSE_YES: switch_view( historyitem->url ); return false; case Gtk::RESPONSE_NO: return back_forward_viewhistory( url, back, count + 1 ); default: break; } return false; } // Admin::open_view() 中の append_viewhistory() を実行しないでここで View履歴の現在位置を変更する // Admin::Open_view()も参照すること const bool use_history = get_use_viewhistory(); set_use_viewhistory( false ); if( use_history ){ if( back ) HISTORY::get_history_manager()->back_viewhistory( url, count, true ); else HISTORY::get_history_manager()->forward_viewhistory( url, count, true ); } const COMMAND_ARGS command_arg = url_to_openarg( historyitem->url, false, false ); open_view( command_arg ); set_use_viewhistory( use_history ); return true; } } return false; } // // View履歴削除 // void Admin::clear_viewhistory() { if( ! m_use_viewhistory ) return; std::list< SKELETON::View* > list_view = get_list_view(); for( const SKELETON::View* view : list_view ) { if( view ){ HISTORY::get_history_manager()->delete_viewhistory( view->get_url() ); HISTORY::get_history_manager()->create_viewhistory( view->get_url() ); const int page = m_notebook->page_num( *view ); const std::string& str_label = m_notebook->get_tab_fulltext( page ); HISTORY::get_history_manager()->replace_current_title_viewhistory( view->get_url(), str_label ); } } if( ! m_last_closed_url.empty() ) HISTORY::get_history_manager()->delete_viewhistory( m_last_closed_url ); m_last_closed_url = std::string(); update_toolbar_button(); } // // タブの切り替え履歴を更新 // void Admin::append_switchhistory( const std::string& url ) { if( ! m_use_switchhistory ) return; // 起動中とシャットダウン中は処理しない if( SESSION::is_booting() ) return; if( SESSION::is_quitting() ) return; #ifdef _DEBUG std::cout << "Admin::append_switchhistory url = " << url << std::endl; #endif m_list_switchhistory.remove( url ); m_list_switchhistory.push_back( url ); #ifdef _DEBUG for( const std::string& hist_url : m_list_switchhistory ) { std::cout << hist_url << std::endl; } #endif } void Admin::remove_switchhistory( const std::string& url ) { if( ! m_use_switchhistory ) return; // 起動中とシャットダウン中は処理しない if( SESSION::is_booting() ) return; if( SESSION::is_quitting() ) return; #ifdef _DEBUG std::cout << "Admin::remove_switchhistory url = " << url << std::endl; #endif m_list_switchhistory.remove( url ); #ifdef _DEBUG for( const std::string& hist_url : m_list_switchhistory ) { std::cout << hist_url << std::endl; } #endif } // // タブの切り替え履歴から、有効な最後のURLを返す // // 表示されているビューに存在しないURLは履歴から削除して、表示されているビューのURLを返す。 // 見つからないときは空文字列を返す。 // // NOTE: // タブの切り替え履歴にあるビューは、表示されているビューのうちのどれかであるはずだが、 // 検索ビューなどでURLが変更になる場合があり、 ( View::set_url()で変更できてしまう。 ) // タブの切り替え履歴にあるURLと、表示されているビューのURLが不一致となることがある。 // また、その不一致なタブの切り替え履歴を、セッション情報 ( article_switchhistoryなど ) に // 保存してしまっていたため、ここで不一致な履歴の削除を行うことで、履歴を修復する。 // std::string Admin::get_valid_switchhistory() { if( ! m_use_switchhistory ) return std::string(); while( m_list_switchhistory.size() > 0 ){ // タブの切り替え履歴から、最後のURLを取り出す const std::string url = m_list_switchhistory.back(); // 表示されているビューであれば、そのURLを返す SKELETON::View* view = get_view( url ); if( view ) return url; // 表示されていないので、最後のURLを削除する m_list_switchhistory.pop_back(); #ifdef _DEBUG std::cout << "Admin::get_valid_switchhistory remove = " << url << std::endl; #endif } return std::string(); } jdim-0.7.0/src/skeleton/admin.h000066400000000000000000000321501417047150700163240ustar00rootroot00000000000000// ライセンス: GPL2 // Viewを管理するクラス // 派生クラスはcreate_view()をオーバロードすること // Adminへの命令はすべてset_command()を通じておこなうこと #ifndef _ADMIN_H #define _ADMIN_H #include "dispatchable.h" #include "command_args.h" #include #include #include #include namespace SKELETON { class JDWindow; class View; class DragableNoteBook; class TabSwitchMenu; class Admin : public Dispatchable { std::string m_url; std::unique_ptr m_win; protected: std::unique_ptr m_notebook; private: bool m_focus{}; std::list< COMMAND_ARGS > m_list_command; // 右クリックメニュー用 Glib::RefPtr< Gtk::ActionGroup > m_action_group; Glib::RefPtr< Gtk::UIManager > m_ui_manager; int m_clicked_page; // 移動サブメニュー Gtk::MenuItem* m_move_menuitem; std::unique_ptr m_move_menu; // タブ切り替えメニュー std::unique_ptr m_tabswitchmenu; // view履歴使用 bool m_use_viewhistory{}; std::string m_last_closed_url; // タブの切り替え履歴 bool m_use_switchhistory{}; std::list< std::string > m_list_switchhistory; public: explicit Admin( const std::string& url ); ~Admin(); // コピー禁止 Admin( const Admin& ) = delete; Admin& operator=( const Admin& ) = delete; virtual void save_session(); void setup_menu(); virtual bool empty() const; const std::string& get_url() const{ return m_url; } virtual Gtk::Widget* get_widget(); virtual Gtk::Window* get_win(); // 起動中 bool is_booting(); // フォーカスされているか virtual bool has_focus() const { return m_focus; } // タブの数 virtual int get_tab_nums(); // 含まれているページのURLのリスト取得 virtual std::list< std::string > get_URLs(); // Core からのクロック入力。 // Coreでタイマーをひとつ動かして全体の同期を取るようにしているので // 一定時間毎に clock_in() が Core から呼び出される virtual void clock_in(); // コマンド入力(通常) void set_command( const std::string& command, const std::string& url = std::string(), const std::string& arg1 = std::string(), const std::string& arg2 = std::string(), const std::string& arg3 = std::string(), const std::string& arg4 = std::string(), const std::string& arg5 = std::string(), const std::string& arg6 = std::string(), const std::string& arg7 = std::string(), const std::string& arg8 = std::string() ); // コマンド入力(即時実行) void set_command_immediately( const std::string& command, const std::string& url = std::string(), const std::string& arg1 = std::string(), const std::string& arg2 = std::string(), const std::string& arg3 = std::string(), const std::string& arg4 = std::string(), const std::string& arg5 = std::string(), const std::string& arg6 = std::string(), const std::string& arg7 = std::string(), const std::string& arg8 = std::string() ); // コマンドがセットされているか bool has_commands() const { return m_list_command.size(); } // 現在表示してるページ番号およびURL // 表示ページを指定したいときは "set_page" コマンドを使う virtual int get_current_page(); std::string get_current_url(); // urlで指定されるタブがロックされているか bool is_locked( const std::string& url ); // urlで指定されるタブが存在するか bool exist_tab( const std::string& url ); // 指定したページに表示切替え void set_current_page( const int page ); // フォーカスしてから指定したページに表示切替え void set_current_page_focus( const int page ); virtual View* get_current_view(); protected: void set_use_viewhistory( const bool use ){ m_use_viewhistory = use; } bool get_use_viewhistory() const { return m_use_viewhistory; } void set_use_switchhistory( const bool use ){ m_use_switchhistory = use; } JDWindow* get_jdwin(){ return m_win.get(); } void set_jdwin( std::unique_ptr win ); void delete_jdwin(); // URLやステータスを更新 void update_status( View* view, const bool force ); DragableNoteBook* get_notebook(){ return m_notebook.get(); } // コマンド入力 // immediately = true のときディスパッチャを呼ばずにすぐさま実行 void set_command_impl( const bool immediately, const COMMAND_ARGS& command_arg ); void callback_dispatch() override; // admin共通コマンド実行 virtual void exec_command(); // 派生クラス固有のコマンドを実行 virtual void command_local( const COMMAND_ARGS& command ) = 0; // 起動時に前回の状態を回復 virtual void restore( const bool only_locked ) = 0; // url から Viewを開くための COMMAND_ARGS を取得する // restore() などで使用する virtual COMMAND_ARGS url_to_openarg( const std::string& url, const bool tab, const bool lock ) = 0; // COMMAND_ARGS からビューの URL を取得する virtual std::string command_to_url( const COMMAND_ARGS& command ){ return command.url; } // view_modeに該当するページを探す virtual int find_view( const std::string& view_mode ){ return -1; }; virtual void open_view( const COMMAND_ARGS& command ); virtual void switch_admin() = 0; // CORE::core_set_command( "switch_*" ) を送る virtual void switch_view( const std::string& url ); void reload_view( const std::string& url ); // タブ左右移動 // updated == true の時は更新されたタブに移動 virtual void tab_left( const bool updated ); virtual void tab_right( const bool updated ); virtual void tab_num( const std::string& str_num ); virtual void tab_head(); void tab_head_focus(); virtual void tab_tail(); void tab_tail_focus(); virtual void redraw_view( const std::string& url ); virtual void redraw_current_view(); virtual void relayout_current_view(); virtual void redraw_views( const std::string& url ); virtual void update_view( const std::string& url ); virtual void update_item( const std::string& url, const std::string& id ); virtual void update_finish( const std::string& url ); virtual void close_view( const std::string& url ); virtual void unlock_all_view( const std::string& url ); virtual void close_all_view( const std::string& url ); virtual void close_other_views( const std::string& url ); virtual void close_current_view(); virtual void restore_lasttab(){} virtual void set_title( const std::string& url, const std::string& title, const bool force ); virtual void set_url( const std::string& url, const std::string& url_show, const bool force ); virtual void set_status( const std::string& url, const std::string& stat, const bool force ); void set_status_color( const std::string& url, const std::string& color, const bool force ); virtual void focus_view( int page ); virtual void focus_current_view(); virtual void restore_focus(); virtual void focus_out(); virtual void set_tablabel( const std::string& url, const std::string& str_label ); virtual void relayout_all(); virtual void open_window(){} virtual void close_window(){} virtual void toggle_tab(); virtual void toggle_icon( const std::string& url ); // オートリロードのモード設定 virtual bool set_autoreload_mode( const std::string& url, int mode, int sec ); // ポップアップを隠す(インスタンスは削除しない) void hide_popup(); // タブをお気に入りにドロップした時にお気に入りがデータ送信を要求してきた virtual void slot_drag_data_get( Gtk::SelectionData& selection_data, const int page ) = 0; void open_list( const COMMAND_ARGS& command_list ); virtual COMMAND_ARGS get_open_list_args( const std::string& url, const COMMAND_ARGS& command_list ){ return COMMAND_ARGS(); } virtual View* create_view( const COMMAND_ARGS& command ){ return nullptr; }; virtual View* get_view( const std::string& url ); // url を含むビュークラスをリストで取得 ( url と一致するビューでは無い ) std::list< View* > get_list_view( const std::string& url ); // 全てのビュークラスをリストで取得 std::list< View* > get_list_view(); // ツールバー virtual void show_toolbar(){} virtual void toggle_toolbar(){} void focus_toolbar_search(); void redraw_toolbar(); void update_toolbar_button(); virtual void open_searchbar(){} virtual void close_searchbar(){} // タブの更新チェック void check_update_all_tabs( const bool open ); void check_update_all_tabs( const int from_page, const bool open ); // タブの再読み込み void reload_all_tabs(); void reload_all_tabs( const int from_page ); // notebookのタブが切り替わったときに呼ばれるslot void slot_switch_page( Gtk::Widget*, guint page ); // タブをクリックした void slot_tab_clicked( const int page ); // タブの上でホイールを回した void slot_tab_scrolled( GdkEventScroll* event ); // タブを閉じる void slot_tab_close( const int page ); // タブ再読み込み void slot_tab_reload( const int page ); // タブメニュー表示 virtual void slot_tab_menu( int page, int x, int y ); // タブ切り替えメニュー表示 void slot_show_tabswitchmenu(); // 右クリックメニュー virtual void slot_close_tab(); virtual void slot_lock(); virtual void slot_close_other_tabs(); virtual void slot_close_left_tabs(); virtual void slot_close_right_tabs(); virtual void slot_close_all_tabs(); virtual void slot_close_same_icon_tabs(); virtual void slot_check_update_all_tabs(); virtual void slot_check_update_reload_all_tabs(); virtual void slot_reload_all_tabs(); virtual void slot_cancel_reload_all_tabs(); virtual void slot_open_by_browser(); virtual void slot_copy_url(); virtual void slot_copy_title_url(); // ページがロックされているかリストで取得 virtual std::list< bool > get_locked(); // タブのロック/アンロック virtual bool is_lockable( const int page ); virtual bool is_locked( const int page ); virtual void lock( const int page ); virtual void unlock( const int page ); // プロパティ表示 void show_preference(); // View履歴戻る / 進む bool back_viewhistory( const std::string& url, const int count ); void back_clicked_viewhistory( const int count ); bool forward_viewhistory( const std::string& url, const int count ); void forward_clicked_viewhistory( const int count ); // View履歴削除 void clear_viewhistory(); // タブの切り替え履歴 const std::list< std::string >& get_switchhistory() const { return m_list_switchhistory; } void set_switchhistory( const std::list< std::string >& hist ){ m_list_switchhistory = hist; } private: void slot_popupmenu_deactivate(); bool back_forward_viewhistory( const std::string& url, const bool back, const int count ); // 移転などでviewのurlを更新 void update_url( const std::string& url_old, const std::string& url_new ); // urlを含むviewの板名を更新 void update_boardname( const std::string& url ); // タブの切り替え履歴を更新 void append_switchhistory( const std::string& url ); void remove_switchhistory( const std::string& url ); std::string get_valid_switchhistory(); void close_view( View* view ); }; } #endif jdim-0.7.0/src/skeleton/backforwardbutton.cpp000066400000000000000000000037241417047150700213150ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "backforwardbutton.h" #include "history/historymanager.h" #include "history/viewhistoryitem.h" #include "icons/iconmanager.h" #include using namespace SKELETON; BackForwardButton::BackForwardButton( const std::string& url, const bool back ) : SKELETON::MenuButton( true, ( back ? ICON::BACK : ICON::FORWARD ) ), m_url( url ), m_back( back ) {} void BackForwardButton::set_url( const std::string& url ) { m_url = url; #ifdef _DEBUG std::cout << "BackForwardButton::set_url back = " << m_back << " url = " << m_url << std::endl; #endif if( m_back ){ if( HISTORY::get_history_manager()->can_back_viewhistory( m_url, 1 ) ) set_sensitive( true ); else set_sensitive( false ); } else{ if( HISTORY::get_history_manager()->can_forward_viewhistory( m_url, 1 ) ) set_sensitive( true ); else set_sensitive( false ); } } // // ポップアップメニュー表示 // // virtual void BackForwardButton::show_popupmenu() { #ifdef _DEBUG std::cout << "BackForwardButton::show_popupmenu back = " << m_back << " url = " << m_url << std::endl; #endif std::vector< std::string > items; auto inserter = std::back_inserter( items ); const auto get_title = []( const HISTORY::ViewHistoryItem* item ) { return item->title; }; // 戻る更新 if( m_back ){ std::vector< HISTORY::ViewHistoryItem* >& histitems = HISTORY::get_history_manager()->get_items_back_viewhistory( m_url, MAX_MENU_SIZE ); std::transform( histitems.cbegin(), histitems.cend(), inserter, get_title ); } else{ std::vector< HISTORY::ViewHistoryItem* >& histitems = HISTORY::get_history_manager()->get_items_forward_viewhistory( m_url, MAX_MENU_SIZE ); std::transform( histitems.cbegin(), histitems.cend(), inserter, get_title ); } append_menu( items ); SKELETON::MenuButton::show_popupmenu(); } jdim-0.7.0/src/skeleton/backforwardbutton.h000066400000000000000000000010441417047150700207530ustar00rootroot00000000000000// ライセンス: GPL2 // 「戻る」「進む」履歴付きボタン #ifndef _BACKFORWARDBUTTON_H #define _BACKFORWARDBUTTON_H #include "menubutton.h" namespace SKELETON { class BackForwardButton : public SKELETON::MenuButton { std::string m_url; bool m_back; public: BackForwardButton( const std::string& url, const bool back ); void set_url( const std::string& url ); protected: // ポップアップメニュー表示 void show_popupmenu() override; }; } #endif jdim-0.7.0/src/skeleton/compentry.cpp000066400000000000000000000160671417047150700176200ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "compentry.h" #include "control/controlid.h" #include "compmanager.h" using namespace SKELETON; enum { POPUP_SIZE = 5 }; CompletionEntry::CompletionEntry( const int mode ) : m_mode( mode ) , m_enable_changed( true ) , m_popup_win( SKELETON::POPUPWIN_DRAWFRAME ) { m_entry.signal_key_press().connect( sigc::mem_fun( *this, &CompletionEntry::slot_entry_key_press ) ); m_entry.signal_button_press().connect( sigc::mem_fun(*this, &CompletionEntry::slot_entry_button_press ) ); m_entry.signal_operate().connect( sigc::mem_fun( *this, &CompletionEntry::slot_entry_operate ) ); m_entry.signal_activate().connect( sigc::mem_fun( *this, &CompletionEntry::slot_entry_acivate ) ); m_entry.signal_changed().connect( sigc::mem_fun( *this, &CompletionEntry::slot_entry_changed ) ); m_entry.signal_focus_out_event().connect( sigc::mem_fun(*this, &CompletionEntry::slot_entry_focus_out ) ); m_entry.set_max_width_chars( 1 ); m_entry.set_width_chars( 1 ); m_entry.set_hexpand( true ); pack_start( m_entry ); // ポップアップ m_column_record.add( m_column ); m_liststore = Gtk::ListStore::create( m_column_record ); m_treeview.set_model( m_liststore ); m_treeview.append_column( "", m_column ); m_treeview.set_headers_visible( false ); m_treeview.sig_motion_notify().connect( sigc::mem_fun(*this, &CompletionEntry::slot_treeview_motion ) ); m_treeview.sig_button_release().connect( sigc::mem_fun(*this, &CompletionEntry::slot_treeview_button_release ) ); m_scr_win.add( m_treeview ); m_scr_win.set_policy( Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC ); m_scr_win.set_size_request( 1, 1 ); m_popup_win.add( m_scr_win ); set_size_request( 0 ); } CompletionEntry::~CompletionEntry() noexcept = default; // 補完実行 bool CompletionEntry::completion() { bool ret = false; if( m_show_popup ){ Gtk::TreeModel::Row row = m_treeview.get_current_row(); if( row ){ set_text( row[ m_column ] ); ret = true; } } hide_popup(); return ret; } void CompletionEntry::set_text( const Glib::ustring& text ) { m_enable_changed = false; m_entry.set_text( text ); m_entry.set_position( text.length() ); m_enable_changed = true; } void CompletionEntry::grab_focus() { #ifdef _DEBUG std::cout << "CompletionEntry::grab_focus\n"; #endif m_focused = true; m_entry.grab_focus(); } // // ポップアップ表示 // // show_all == true なら候補を全て表示する // void CompletionEntry::show_popup( const bool show_all ) { const int mrg = 2; std::string query = m_entry.get_text(); if( ! show_all && query.empty() ){ hide_popup(); return; } if( show_all ) query = std::string(); CORE::COMPLIST complist = CORE::get_completion_manager()->get_list( m_mode, query ); if( ! complist.size() ) hide_popup(); m_liststore->clear(); Gtk::TreeModel::Row row; for( const std::string& comp : complist ) { if( comp != m_entry.get_text() ) { row = *( m_liststore->append() ); row[ m_column ] = comp; } } const int size = m_liststore->children().size(); if( ! size ){ hide_popup(); return; } // 座標と大きさを計算してポップアップ表示 const int cell_h = m_treeview.get_row_height() + mrg; int x, y; get_window()->get_origin( x, y ); Gdk::Rectangle rect = get_allocation(); m_popup_win.move( x + rect.get_x(), y + rect.get_y() + rect.get_height() ); m_popup_win.resize( get_width(), cell_h * MIN( POPUP_SIZE, size ) + mrg ); m_popup_win.show_all(); m_show_popup = true; m_treeview.unset_cursor(); m_treeview.scroll_to_point( -1, 0 ); Gdk::RGBA rgba; if( !get_style_context()->lookup_color( "theme_bg_color", rgba ) ) { #ifdef _DEBUG std::cout << "ERROR:CompletionEntry::show_popup() " << "lookup theme_bg_color faild." << std::endl; #endif } m_treeview.get_column_cell_renderer( 0 )->property_cell_background_rgba() = rgba; } // ポップアップ閉じる void CompletionEntry::hide_popup() { if( m_show_popup ){ m_popup_win.hide(); m_show_popup = false; } } // entryでキーが押された void CompletionEntry::slot_entry_key_press( int keyval ) { #ifdef _DEBUG std::cout << "CompletionEntry::slot_entry_key_press keyval = " << keyval << std::endl; #endif } // entryでボタンを押した void CompletionEntry::slot_entry_button_press( GdkEventButton* event ) { if( event->type != GDK_BUTTON_PRESS ) return; #ifdef _DEBUG std::cout << "CompletionEntry::slot_entry_button_press button = " << event->button << " focused = " << m_focused << std::endl; #endif if( m_show_popup ) hide_popup(); // 右クリックならコンテキストメニューを優先して補完候補は表示しない else if( m_focused && event->button != 3 ) { show_popup( m_entry.get_text().empty() ); } m_focused = true; } // entry操作 void CompletionEntry::slot_entry_operate( int controlid ) { #ifdef _DEBUG std::cout << "CompletionEntry::slot_entry_operate id = " << controlid << std::endl; #endif switch( controlid ){ case CONTROL::Up: if( ! m_treeview.row_up() ) m_treeview.goto_bottom(); break; case CONTROL::Down: if( ! m_treeview.row_down() ) m_treeview.goto_top(); break; case CONTROL::Cancel: if( m_show_popup ) hide_popup(); else m_sig_operate.emit( controlid ); break; default: m_sig_operate.emit( controlid ); } } // entry からsignal_activateを受け取った void CompletionEntry::slot_entry_acivate() { #ifdef _DEBUG std::cout << "CompletionEntry::slot_entry_acivate\n"; #endif if( ! completion() ) m_sig_activate.emit(); } // entry からsignal_changedを受け取った void CompletionEntry::slot_entry_changed() { if( m_enable_changed ){ if( m_entry.get_text().empty() ) hide_popup(); else show_popup( false ); } m_sig_changed.emit(); } // entryのフォーカスが外れた bool CompletionEntry::slot_entry_focus_out( GdkEventFocus* ) { #ifdef _DEBUG std::cout << "CompletionEntry::slot_entry_focus_out\n"; #endif hide_popup(); m_focused = false; return true; } // ポップアップ内をマウスを動かした bool CompletionEntry::slot_treeview_motion( GdkEventMotion* ) { Gtk::TreeModel::Path path = m_treeview.get_path_under_mouse(); if( path.size() > 0 ) m_treeview.set_cursor( path ); return true; } // ポップアップクリック bool CompletionEntry::slot_treeview_button_release( GdkEventButton* ) { #ifdef _DEBUG std::cout << "CompletionEntry::slot_treeview_button_release\n"; #endif hide_popup(); Gtk::TreeModel::Row row = m_treeview.get_current_row(); if( row ){ set_text( row[ m_column ] ); hide_popup(); } return true; } jdim-0.7.0/src/skeleton/compentry.h000066400000000000000000000054471417047150700172650ustar00rootroot00000000000000// ライセンス: GPL2 // // 補完 Entryクラス // #ifndef _COMPENTRY_H #define _COMPENTRY_H #include "entry.h" #include "popupwinbase.h" #include "treeviewbase.h" #include namespace SKELETON { class CompletionEntry : public Gtk::HBox { typedef sigc::signal< void, int > SIG_OPERATE; typedef sigc::signal< void > SIG_ACTIVATE; typedef sigc::signal< void > SIG_CHANGED; SIG_OPERATE m_sig_operate; SIG_ACTIVATE m_sig_activate; SIG_CHANGED m_sig_changed; int m_mode; JDEntry m_entry; bool m_enable_changed; bool m_focused{}; // ポップアップ bool m_show_popup{}; PopupWinBase m_popup_win; Gtk::ScrolledWindow m_scr_win; JDTreeViewBase m_treeview; Gtk::TreeModelColumn< Glib::ustring > m_column; Gtk::TreeModel::ColumnRecord m_column_record; Glib::RefPtr< Gtk::ListStore > m_liststore; public: // mode は補完モード ( compmanager.h 参照 ) explicit CompletionEntry( const int mode ); ~CompletionEntry() noexcept; SIG_OPERATE signal_operate(){ return m_sig_operate; } SIG_ACTIVATE signal_activate(){ return m_sig_activate; } SIG_CHANGED signal_changed(){ return m_sig_changed; } // m_entry の入力コントローラのモード設定 // 補完モード(m_mode)とは異なる void add_control_mode( int mode ){ m_entry.add_mode( mode ); } // 補完実行 bool completion(); Glib::ustring get_text() const { return m_entry.get_text(); } void set_text( const Glib::ustring& text ); void grab_focus(); void modify_font( const Pango::FontDescription& pfd ) { m_entry.override_font( pfd ); } private: // ポップアップ表示 // show_all == true なら候補を全て表示する void show_popup( const bool show_all ); // ポップアップ閉じる void hide_popup(); // entryでキーが押された void slot_entry_key_press( int keyval ); // entryでボタンを押した void slot_entry_button_press( GdkEventButton* event ); // entry操作 void slot_entry_operate( int controlid ); // entry からsignal_activateを受け取った void slot_entry_acivate(); // entry からsignal_changedを受け取った void slot_entry_changed(); // entryのフォーカスが外れた bool slot_entry_focus_out( GdkEventFocus* ); // ポップアップ内をマウスを動かした bool slot_treeview_motion( GdkEventMotion* ); // ポップアップクリック bool slot_treeview_button_release( GdkEventButton* ); }; } #endif jdim-0.7.0/src/skeleton/detaildiag.cpp000066400000000000000000000031161417047150700176560ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "gtkmmversion.h" #include "detaildiag.h" #include "view.h" #include "viewfactory.h" using namespace SKELETON; DetailDiag::DetailDiag( Gtk::Window* parent, const std::string& url, const bool add_cancel, const std::string& message, const std::string& tab_message, const std::string& detail_html, const std::string& tab_detail ) : SKELETON::PrefDiag( parent, url, add_cancel ) , m_message( message ) { m_message.set_width_chars( 60 ); m_message.set_line_wrap( true ); m_message.property_margin() = 8; m_message.set_selectable( true ); m_message.property_can_focus() = false; m_detail.reset( CORE::ViewFactory( CORE::VIEW_ARTICLEINFO, get_url() ) ); if( ! detail_html.empty() ) m_detail->set_command( "append_html", detail_html ); m_notebook.append_page( m_message, tab_message ); m_notebook.append_page( *m_detail, tab_detail ); m_notebook.signal_switch_page().connect( sigc::mem_fun( *this, &DetailDiag::slot_switch_page ) ); get_content_area()->pack_start( m_notebook ); show_all_children(); grab_ok(); } // メンバーに不完全型のスマートポインターがあるためデストラクタはinlineにできない DetailDiag::~DetailDiag() noexcept = default; void DetailDiag::timeout() { if( m_detail ) m_detail->clock_in(); } void DetailDiag::slot_switch_page( Gtk::Widget*, guint page ) { if( get_notebook().get_nth_page( page ) == m_detail.get() ) m_detail->redraw_view(); } jdim-0.7.0/src/skeleton/detaildiag.h000066400000000000000000000017031417047150700173230ustar00rootroot00000000000000// ライセンス: GPL2 // 詳細表示ダイアログ #ifndef _DETAILDIAG_H #define _DETAILDIAG_H #include "prefdiag.h" #include namespace SKELETON { class View; class DetailDiag : public SKELETON::PrefDiag { Gtk::Notebook m_notebook; Gtk::Label m_message; std::unique_ptr m_detail; public: DetailDiag( Gtk::Window* parent, const std::string& url, const bool add_cancel, const std::string& message, const std::string& tab_message, const std::string& detail_html, const std::string& tab_detail ); ~DetailDiag() noexcept; protected: Gtk::Notebook& get_notebook(){ return m_notebook; } SKELETON::View* get_detail(){ return m_detail.get(); } private: virtual void slot_switch_page( Gtk::Widget*, guint page ); void timeout() override; }; } #endif jdim-0.7.0/src/skeleton/dispatchable.cpp000066400000000000000000000011551417047150700202130ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "dispatchable.h" #include "dispatchmanager.h" using namespace SKELETON; Dispatchable::Dispatchable() : m_dispatchable( true ) {} Dispatchable::~Dispatchable() { #ifdef _DEBUG std::cout << "Dispatchable::~Dispatchable\n"; #endif set_dispatchable( false ); } void Dispatchable::set_dispatchable( const bool dispatchable ) { m_dispatchable = dispatchable; if( ! m_dispatchable ) CORE::get_dispmanager()->remove( this ); } void Dispatchable::dispatch() { if( m_dispatchable ) CORE::get_dispmanager()->add( this ); } jdim-0.7.0/src/skeleton/dispatchable.h000066400000000000000000000020121417047150700176510ustar00rootroot00000000000000// ライセンス: GPL2 // // Dispatchクラス // // Core::DispatchManagerと組み合わせて使う。詳しくはCore::DispatchManagerの説明を見ること // // なお派生クラスのデストラクタの中からdispatchを呼ぶと落ちるので、特にスレッドを使用している // 派生クラスのデストラクタの先頭に set_dispatchable( false ) を入れてdispatch不可にすること // #ifndef _DISPATCHABLE_H #define _DISPATCHABLE_H #include namespace CORE { class DispatchManager; } namespace SKELETON { class Dispatchable { friend class CORE::DispatchManager; bool m_dispatchable; public: Dispatchable(); virtual ~Dispatchable(); protected: void dispatch(); void set_dispatchable( const bool dispatchable ); // dispacth() で DispatchManager に登録されて // DispatchManager が callback_dispatch() を呼び戻す virtual void callback_dispatch() = 0; }; } #endif jdim-0.7.0/src/skeleton/dragnote.cpp000066400000000000000000000410651417047150700173770ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "dragnote.h" #include "tablabel.h" #include "toolbar.h" #include "view.h" #include "iconpopup.h" #include "icons/iconmanager.h" #include "control/controlid.h" #include "command.h" #include "dndmanager.h" #include "session.h" #include using namespace SKELETON; DragableNoteBook::DragableNoteBook() : Gtk::VBox() , m_notebook_tab( this ) , m_notebook_toolbar( this ) , m_notebook_view( this ) , m_bt_tabswitch( this ) , m_show_tabs{ true } , m_show_toolbar{ true } , m_page( -1 ) { set_spacing( 0 ); m_notebook_tab.signal_switch_page().connect( sigc::mem_fun( *this, &DragableNoteBook::slot_switch_page_tab ) ); m_notebook_tab.sig_button_press().connect( sigc::mem_fun( *this, &DragableNoteBook::slot_button_press_event ) ); m_notebook_tab.sig_button_release().connect( sigc::mem_fun( *this, &DragableNoteBook::slot_button_release_event ) ); m_notebook_tab.sig_tab_drag_motion().connect( sigc::mem_fun(*this, &DragableNoteBook::slot_drag_motion ) ); m_notebook_tab.sig_scroll_event().connect( sigc::mem_fun(*this, &DragableNoteBook::slot_scroll_event ) ); m_hbox_tab.pack_start( m_notebook_tab ); m_hbox_tab.pack_start( m_bt_tabswitch, Gtk::PACK_SHRINK ); m_bt_tabswitch.set_tooltip_text( "タブの一覧表示" ); pack_start( m_hbox_tab, Gtk::PACK_SHRINK ); pack_start( m_notebook_toolbar, Gtk::PACK_SHRINK ); pack_start( m_notebook_view ); show_all_children(); } // メンバーに不完全型のスマートポインターがあるためデストラクタはinlineにできない DragableNoteBook::~DragableNoteBook() noexcept = default; // // クロック入力 // void DragableNoteBook::clock_in() { m_notebook_tab.clock_in(); } void DragableNoteBook::set_show_tabs( bool show_tabs ) { #ifdef _DEBUG std::cout << "DragableNoteBook::set_show_tabs show_tabs = " << show_tabs << std::endl; #endif if( ! show_tabs && m_show_tabs ){ remove( m_hbox_tab ); m_show_tabs = false; } else if( show_tabs && ! m_show_tabs ){ if( m_show_toolbar && m_notebook_toolbar.get_n_pages() ) remove( m_notebook_toolbar ); remove( m_notebook_view ); pack_start( m_hbox_tab, Gtk::PACK_SHRINK ); if( m_show_toolbar && m_notebook_toolbar.get_n_pages() ) pack_start( m_notebook_toolbar, Gtk::PACK_SHRINK ); pack_start( m_notebook_view ); m_show_tabs = true; } } void DragableNoteBook::set_scrollable( bool scrollable ) { m_notebook_tab.set_scrollable( scrollable ); } int DragableNoteBook::get_n_pages() const { return m_notebook_view.get_n_pages(); } Gtk::Widget* DragableNoteBook::get_nth_page( int page_num ) { return m_notebook_view.get_nth_page( page_num ); } int DragableNoteBook::page_num( const Gtk::Widget& child ) const { return m_notebook_view.page_num( child ); } int DragableNoteBook::get_current_page() const { return m_notebook_view.get_current_page(); } void DragableNoteBook::set_current_page( int page_num ) { if( get_current_page() == page_num ) return; if( page_num >= get_n_pages() ) page_num = get_n_pages()-1; m_notebook_tab.set_current_page( page_num ); m_notebook_view.set_current_page( page_num ); SKELETON::View* view = dynamic_cast< View* >( get_nth_page( page_num ) ); if( view ) set_current_toolbar( view->get_id_toolbar(), view ); } // // ページのappendとinsert // int DragableNoteBook::append_page( const std::string& url, Gtk::Widget& child ) { m_notebook_view.append_page( child ); m_bt_tabswitch.show_button(); SKELETON::TabLabel* tablabel = Gtk::manage( create_tablabel( url ) ); return m_notebook_tab.append_tab( *tablabel ); } int DragableNoteBook::insert_page( const std::string& url, Gtk::Widget& child, int page ) { m_notebook_view.insert_page( child, page ); m_bt_tabswitch.show_button(); SKELETON::TabLabel* tablabel = Gtk::manage( create_tablabel( url ) ); return m_notebook_tab.insert_tab( *tablabel, page ); } // // タブの文字列取得 // const std::string& DragableNoteBook::get_tab_fulltext( const int page ) { return m_notebook_tab.get_tab_fulltext( page ); } // // タブに文字列をセットとタブ幅調整 // void DragableNoteBook::set_tab_fulltext( const std::string& str, const int page ) { m_notebook_tab.set_tab_fulltext( str, page ); } // // ページ取り除き // void DragableNoteBook::remove_page( const int page, const bool adjust_tab ) { // タブはGtk::manage()の効果でdeleteされる m_notebook_tab.remove_tab( page, adjust_tab ); m_notebook_view.remove_page( page ); if( ! get_n_pages() ) m_bt_tabswitch.hide_button(); } // // ツールバー取得 // SKELETON::ToolBar* DragableNoteBook::get_toolbar( int page ) { return dynamic_cast< SKELETON::ToolBar* >( m_notebook_toolbar.get_nth_page( page ) ); } // // ツールバー全体を表示 // // タブにビューが表示されたら admin から呼び出される // void DragableNoteBook::show_toolbar() { #ifdef _DEBUG std::cout << "DragableNoteBook::show_toolbar\n"; #endif if( ! m_show_toolbar && m_notebook_toolbar.get_n_pages() ){ remove( m_notebook_view ); pack_start( m_notebook_toolbar, Gtk::PACK_SHRINK ); pack_start( m_notebook_view ); m_show_toolbar = true; } } // // ツールバー全体を非表示 // // タブに全てのビューが無くなったら admin から呼び出される // void DragableNoteBook::hide_toolbar() { #ifdef _DEBUG std::cout << "DragableNoteBook::hide_toolbar\n"; #endif if( m_show_toolbar && m_notebook_toolbar.get_n_pages() ){ remove( m_notebook_toolbar ); m_show_toolbar = false; } } // // ツールバーセット // // 各Adminクラスの virtual void show_toolbar() でセットされる // void DragableNoteBook::append_toolbar( Gtk::Widget& toolbar ) { #ifdef _DEBUG std::cout << "DragableNoteBook::append_toolbar\n"; #endif m_notebook_toolbar.append_page( toolbar ); } // // ツールバー切り替え // void DragableNoteBook::set_current_toolbar( const int id_toolbar, SKELETON::View* view ) { // タブ操作中 if( SESSION::is_tab_operating( view->get_url_admin() ) ) return; #ifdef _DEBUG std::cout << "DragableNoteBook::set_current_toolbar id = " << id_toolbar << " / " << m_notebook_toolbar.get_n_pages() << std::endl << "url = " << view->get_url() << std::endl; #endif if( m_notebook_toolbar.get_n_pages() <= id_toolbar ) return; m_notebook_toolbar.set_current_page( id_toolbar ); // ツールバーのラベルなどの情報を更新 SKELETON::ToolBar* toolbar = get_toolbar( id_toolbar ); if( toolbar ){ toolbar->set_view( view ); toolbar->show_toolbar(); } } int DragableNoteBook::get_current_toolbar() const { return m_notebook_toolbar.get_current_page(); } // // ツールバー内の検索ボックスにフォーカスを移す // void DragableNoteBook::focus_toolbar_search() { SKELETON::ToolBar* toolbar = get_toolbar( m_notebook_toolbar.get_current_page() ); if( toolbar ) toolbar->focus_entry_search(); } // // ツールバーURL更新 // void DragableNoteBook::update_toolbar_url( const std::string& url_old, const std::string& url_new ) { for( int i = 0; i < m_notebook_toolbar.get_n_pages(); ++i ){ SKELETON::ToolBar* toolbar = get_toolbar( i ); if( toolbar && toolbar->get_url() == url_old ) toolbar->set_url( url_new ); } } // // ツールバーボタン表示更新 // void DragableNoteBook::update_toolbar_button() { for( int i = 0; i < m_notebook_toolbar.get_n_pages(); ++i ){ SKELETON::ToolBar* toolbar = get_toolbar( i ); if( toolbar ) toolbar->update_button(); } } // // タブのアイコンを取得する // int DragableNoteBook::get_tabicon( const int page ) { SKELETON::TabLabel* tablabel = m_notebook_tab.get_tablabel( page ); if( tablabel ) return tablabel->get_id_icon(); return ICON::NONE; } // // タブにアイコンをセットする // void DragableNoteBook::set_tabicon( const std::string& iconname, const int page, const int id ) { SKELETON::TabLabel* tablabel = m_notebook_tab.get_tablabel( page ); if( tablabel && id != ICON::NONE ) tablabel->set_id_icon( id ); } // // タブ作成 // SKELETON::TabLabel* DragableNoteBook::create_tablabel( const std::string& url ) { SKELETON::TabLabel *tablabel = new SKELETON::TabLabel( url ); // ドラッグ設定 GdkEventButton event; m_control.get_eventbutton( CONTROL::DragStartButton, event ); tablabel->set_dragable( m_dragable, event.button ); tablabel->sig_tab_drag_begin().connect( sigc::mem_fun(*this, &DragableNoteBook::slot_drag_begin ) ); tablabel->sig_tab_drag_data_get().connect( sigc::mem_fun(*this, &DragableNoteBook::slot_drag_data_get ) ); tablabel->sig_tab_drag_end().connect( sigc::mem_fun(*this, &DragableNoteBook::slot_drag_end ) ); return tablabel; } // // タブの幅を固定するか // void DragableNoteBook::set_fixtab( bool fix ) { m_notebook_tab.set_fixtab( fix ); } // // タブ幅調整 // bool DragableNoteBook::adjust_tabwidth() { return m_notebook_tab.adjust_tabwidth(); } // // notebook_tabのタブが切り替わったときに呼ばれるslot // void DragableNoteBook::slot_switch_page_tab( Gtk::Widget* bookpage, guint page ) { // view も切り替える m_notebook_view.set_current_page( page ); // ツールバー(枠) 再描画 m_notebook_toolbar.queue_draw(); m_sig_switch_page.emit( bookpage, page ); } // // タブの上でボタンを押した // bool DragableNoteBook::slot_button_press_event( GdkEventButton* event ) { // ボタンを押した時点でのページ番号を記録しておく m_page = m_notebook_tab.get_page_under_mouse(); m_dragging_tab = false; // ダブルクリック // button_release_eventでは event->type に必ず GDK_BUTTON_RELEASE が入る m_dblclick = false; if( event->type == GDK_2BUTTON_PRESS ) m_dblclick = true; #ifdef _DEBUG std::cout << "DragableNoteBook::on_button_press_event page = " << m_page << std::endl; std::cout << "x = " << (int)event->x_root << " y = " << (int)event->y_root << " dblclick = " << m_dblclick << std::endl; #endif if( m_page >= 0 && m_page < get_n_pages() ){ // ページ切替え if( m_control.button_alloted( event, CONTROL::ClickButton ) ){ set_current_page( m_page ); m_sig_tab_clicked.emit( m_page ); } return true; } else m_page = -1; return false; } // // タブの上でボタンを離した // bool DragableNoteBook::slot_button_release_event( GdkEventButton* event ) { const int page = m_notebook_tab.get_page_under_mouse(); const int x = (int)event->x_root; const int y = (int)event->y_root; #ifdef _DEBUG std::cout << "DragableNoteBook::on_button_release_event\n"; std::cout << "x = " << (int)event->x_root << " y = " << (int)event->y_root << std::endl; #endif if( ! m_dragging_tab && m_page >= 0 && m_page < get_n_pages() ){ // ダブルクリックの処理のため一時的にtypeを切替える GdkEventType type_copy = event->type; if( m_dblclick ) event->type = GDK_2BUTTON_PRESS; // タブを閉じる if( page == m_page && m_control.button_alloted( event, CONTROL::CloseTabButton ) ){ m_sig_tab_close.emit( m_page ); // タブにページが残ってなかったらtrueをreturnしないと落ちる // TabNotebook::on_button_release_event() も参照せよ // // (注意) なぜか m_notebook_tab.get_n_pages() < m_notebook_view.get_n_pages() の時があって // 以前の様に // if( get_n_pages() == 0 ) // という条件では m_notebook_tab.get_n_pages() = 0 でも get_n_pages() != 0 になって落ちることがある if( m_notebook_tab.get_n_pages() == 0 ){ m_page = -1; return true; } } // タブを再読み込み else if( m_control.button_alloted( event, CONTROL::ReloadTabButton ) ) m_sig_tab_reload.emit( m_page ); // ポップアップメニュー else if( m_control.button_alloted( event, CONTROL::PopupmenuButton ) ) m_sig_tab_menu.emit( m_page, x, y ); m_page = -1; event->type = type_copy; } return false; } // notebook_tab の上でホイールを回した bool DragableNoteBook::slot_scroll_event( GdkEventScroll* event ) { #ifdef _DEBUG std::cout << "DragableNoteBook::slot_scroll_event direction = " << event->direction << " page = " << get_current_page() << std::endl; #endif bool ret = false; int next_page; if( event->direction == GDK_SCROLL_UP ) { next_page = get_current_page() - 1; // If negative, the last page will be used. ret = true; } else if( event->direction == GDK_SCROLL_DOWN ) { next_page = ( get_current_page() + 1 ) % get_n_pages(); ret = true; } else if( event->direction == GDK_SCROLL_SMOOTH ) { constexpr double smooth_scroll_factor{ 4.0 }; m_smooth_dy += smooth_scroll_factor * event->delta_y; if( m_smooth_dy < -1.0 ) { next_page = get_current_page() - 1; // If negative, the last page will be used. ret = true; m_smooth_dy = 0.0; } else if( m_smooth_dy > 1.0 ) { next_page = ( get_current_page() + 1 ) % get_n_pages(); ret = true; m_smooth_dy = 0.0; } } if( ret ) { set_current_page( next_page ); } m_sig_tab_scrolled.emit( event ); return ret; } // // タブのドラッグを開始 // void DragableNoteBook::slot_drag_begin() { #ifdef _DEBUG std::cout << "DragableNoteBook::slot_drag_begin page = " << m_page << std::endl; #endif CORE::DND_Begin(); m_dragging_tab = true; } // // タブをドラッグ中 // // 矢印アイコンをタブの上に表示する // void DragableNoteBook::slot_drag_motion( const int page, const int tab_x, const int tab_y, const int tab_width ) { #ifdef _DEBUG std::cout << "DragableNoteBook::slot_drag_motion page = " << page << " tab_x = " << tab_x << " tab_y = " << tab_y << " tab_w " << tab_width << std::endl; #endif if( page < 0 || page == m_page ){ if( m_down_arrow ) m_down_arrow->hide(); } else if( m_dragging_tab ){ if( ! m_down_arrow ) m_down_arrow = std::make_unique( ICON::DOWN ); // HACK: サブウインドウにタブ機能がないためメインウインドウに決め打ちしている m_down_arrow->set_transient_for( *CORE::get_mainwindow() ); m_down_arrow->show(); const int space = 4; int x, y; m_notebook_tab.get_window()->get_origin( x, y ); x += tab_x - m_down_arrow->get_img_width()/2; y += tab_y - m_down_arrow->get_img_height() - space; if( page > m_page ) x += tab_width; m_down_arrow->move( x , y ); } } // // D&Dで受信側がデータ送信を要求してきた // void DragableNoteBook::slot_drag_data_get( Gtk::SelectionData& selection_data ) { #ifdef _DEBUG std::cout << "DragableNoteBook::slot_drag_data_get target = " << selection_data.get_target() << " page = " << m_page << std::endl; #endif // お気に入りがデータ送信を要求してきた if( selection_data.get_target() == DNDTARGET_FAVORITE ) m_sig_drag_data_get.emit( selection_data, m_page ); // タブの入れ替え処理 else if( selection_data.get_target() == DNDTARGET_TAB ){ int page = m_notebook_tab.get_page_under_mouse(); if( page >= m_notebook_tab.get_n_pages() ) page = m_notebook_tab.get_n_pages()-1; // ドラッグ前とページが変わっていたら入れ替え if( m_page != -1 && page != -1 && m_page != page ){ #ifdef _DEBUG std::cout << "reorder_chiled " << m_page << " -> " << page << std::endl; #endif m_notebook_tab.reorder_child( m_page, page ); m_notebook_tab.queue_draw(); m_notebook_view.reorder_child( *m_notebook_view.get_nth_page( m_page ), page ); m_page = -1; } if( m_down_arrow ) m_down_arrow->hide(); } } // // タブのドラッグを終了 // void DragableNoteBook::slot_drag_end() { #ifdef _DEBUG std::cout << "DragableNoteBook::slot_drag_end\n"; #endif m_dragging_tab = false; if( m_down_arrow ) m_down_arrow->hide(); CORE::DND_End(); } jdim-0.7.0/src/skeleton/dragnote.h000066400000000000000000000141461417047150700170440ustar00rootroot00000000000000// ライセンス: GPL2 // // タブをドラッグしてページを入れ替え可能なNoteBook // // DragableNoteBook は 下の3つの notebook から構成されている // // タブ : TabNotebook // ツールバー : ToolBarNotebook // ビュー : ViewNotebook #ifndef _DRAGNOTE_H #define _DRAGNOTE_H #include "tabnote.h" #include "toolbarnote.h" #include "viewnote.h" #include "tabswitchbutton.h" #include "control/control.h" #include #include namespace SKELETON { class View; class ToolBar; class TabLabel; class IconPopup; typedef sigc::signal< void, Gtk::Widget*, int > SIG_SWITCH_PAGE; typedef sigc::signal< void, int > SIG_TAB_CLICKED; typedef sigc::signal< void, int > SIG_TAB_CLOSE; typedef sigc::signal< void, int > SIG_TAB_RELOAD; typedef sigc::signal< void, int, int , int > SIG_TAB_MENU; typedef sigc::signal< void, GdkEventScroll* > SIG_TAB_SCROLLED; typedef sigc::signal< void, Gtk::SelectionData&, const int > SIG_DRAG_DATA_GET; // DragableNoteBook を構成している各Notebookの高さ // 及びタブの高さや位置の情報 struct Alloc_NoteBook { int y_tab; int x_tab; int height_tab; int width_tab; int y_tabbar; int height_tabbar; int y_toolbar; int height_toolbar; int x_box; int y_box; int width_box; int height_box; }; class DragableNoteBook : public Gtk::VBox { SIG_SWITCH_PAGE m_sig_switch_page; SIG_TAB_CLICKED m_sig_tab_clicked; SIG_TAB_CLOSE m_sig_tab_close; SIG_TAB_RELOAD m_sig_tab_reload; SIG_TAB_MENU m_sig_tab_menu; SIG_TAB_SCROLLED m_sig_tab_scrolled; SIG_DRAG_DATA_GET m_sig_drag_data_get; // DragableNoteBook は 下の3つのノートブックから出来ている TabNotebook m_notebook_tab; // タブ ToolBarNotebook m_notebook_toolbar; // ツールバー ViewNotebook m_notebook_view; // ビュー Gtk::HBox m_hbox_tab; TabSwitchButton m_bt_tabswitch; // タブの切り替えボタン bool m_show_tabs; bool m_show_toolbar; int m_page; // タブをドラッグ中 bool m_dragging_tab{}; bool m_dblclick{}; // 入力コントローラ CONTROL::Control m_control; bool m_dragable{}; std::unique_ptr m_down_arrow; double m_smooth_dy{}; // GDK_SCROLL_SMOOTH のスクロール変化量 public: SIG_SWITCH_PAGE signal_switch_page(){ return m_sig_switch_page; } SIG_TAB_CLICKED sig_tab_clicked() { return m_sig_tab_clicked; } SIG_TAB_CLOSE sig_tab_close() { return m_sig_tab_close; } SIG_TAB_RELOAD sig_tab_reload(){ return m_sig_tab_reload; } SIG_TAB_MENU sig_tab_menu() { return m_sig_tab_menu; } SIG_TAB_SCROLLED sig_tab_scrolled(){ return m_sig_tab_scrolled; } SIG_DRAG_DATA_GET sig_drag_data_get() { return m_sig_drag_data_get; } DragableNoteBook(); ~DragableNoteBook() noexcept; void clock_in(); bool get_show_tabs() const { return m_show_tabs; } void set_show_tabs( bool show_tabs ); void set_scrollable( bool scrollable ); int get_n_pages() const; Gtk::Widget* get_nth_page( int page_num ); int page_num( const Gtk::Widget& child ) const; int get_current_page() const; void set_current_page( int page_num ); int append_page( const std::string& url, Gtk::Widget& child ); int insert_page( const std::string& url, Gtk::Widget& child, int page ); void remove_page( const int page, const bool adust_tab ); // ツールバー関係 // 各Adminクラスの virtual void show_toolbar()でツールバーを作成してappend_toolbar()で登録する void show_toolbar(); void hide_toolbar(); void append_toolbar( Gtk::Widget& toolbar ); void set_current_toolbar( const int id_toolbar, SKELETON::View* view ); int get_current_toolbar() const; void focus_toolbar_search(); // ツールバー内の検索entryにフォーカスを移す void update_toolbar_url( const std::string& url_old, const std::string& url_new ); void update_toolbar_button(); // タブの文字列取得/セット const std::string& get_tab_fulltext( const int page ); void set_tab_fulltext( const std::string& str, const int page ); // タブのアイコン取得/セット int get_tabicon( const int page ); void set_tabicon( const std::string& iconname, const int page, const int icon ); // ドラッグ可/不可切り替え(デフォルト false ); void set_dragable( bool dragable ){ m_dragable = dragable; } // タブの幅を固定するか void set_fixtab( bool fix ); // タブ幅調整 bool adjust_tabwidth(); // タブ切り替えボタン Gtk::Button& get_tabswitch_button(){ return m_bt_tabswitch.get_button(); } private: // ツールバー取得 SKELETON::ToolBar* get_toolbar( int page ); // タブ作成 SKELETON::TabLabel* create_tablabel( const std::string& url ); // notebook_tabのタブが切り替わったときに呼び出されるslot void slot_switch_page_tab( Gtk::Widget*, guint page ); // notebook_tab の上でボタンを押した/離した bool slot_button_press_event( GdkEventButton* event ); bool slot_button_release_event( GdkEventButton* event ); // notebook_tab の上でホイールを回した bool slot_scroll_event( GdkEventScroll* event ); protected: // コントローラ CONTROL::Control& get_control(){ return m_control; } // タブのドラッグ・アンド・ドロップ処理 void slot_drag_begin(); void slot_drag_motion( const int page, const int tab_x, const int tab_y, const int tab_width ); void slot_drag_data_get( Gtk::SelectionData& selection_data ); void slot_drag_end(); }; } #endif jdim-0.7.0/src/skeleton/dragtreeview.cpp000066400000000000000000000407131417047150700202630ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "gtkmmversion.h" #include "dragtreeview.h" #include "view.h" #include "popupwin.h" #include "config/globalconf.h" #include "jdlib/miscutil.h" #include "control/controlid.h" #include "command.h" #include "colorid.h" #include "dndmanager.h" #include "session.h" #ifndef MAX #define MAX( a, b ) ( a > b ? a : b ) #endif #ifndef MIN #define MIN( a, b ) ( a < b ? a : b ) #endif using namespace SKELETON; constexpr const char* DragTreeView::s_css_classname; DragTreeView::DragTreeView( const std::string& url, const std::string& dndtarget, const bool use_usr_fontcolor, const std::string& fontname, const int colorid_text, const int colorid_bg, const int colorid_bg_even ) : JDTreeViewBase() , m_url( url ) , m_dndtarget( dndtarget ) { #ifdef _DEBUG std::cout << "DragTreeView::DragTreeView\n"; #endif set_enable_search( false ); add_events( Gdk::LEAVE_NOTIFY_MASK ); auto context = get_style_context(); context->add_class( s_css_classname ); context->add_provider( m_provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION ); if( use_usr_fontcolor ){ init_color( colorid_text, colorid_bg, colorid_bg_even ); init_font( fontname ); } get_selection()->set_mode( Gtk::SELECTION_MULTIPLE ); get_selection()->signal_changed().connect( sigc::mem_fun( *this, &DragTreeView::slot_selection_changed ) ); // D&D 設定 std::vector< Gtk::TargetEntry > targets; targets.push_back( Gtk::TargetEntry( get_dndtarget(), Gtk::TARGET_SAME_APP, 0 ) ); // ドラッグ開始ボタン設定 Gdk::ModifierType type = Gdk::BUTTON1_MASK; GdkEventButton event; m_control.get_eventbutton( CONTROL::DragStartButton, event ); switch( event.button ){ case 1: type = Gdk::BUTTON1_MASK; break; case 2: type = Gdk::BUTTON2_MASK; break; case 3: type = Gdk::BUTTON3_MASK; break; case 4: type = Gdk::BUTTON4_MASK; break; case 5: type = Gdk::BUTTON5_MASK; break; } drag_source_set( targets, type ); } // メンバーに不完全型のスマートポインターがあるためデストラクタはinlineにできない DragTreeView::~DragTreeView() noexcept = default; // // 他のアプリからの text/url-list のドロップを有効にする // ドロップされるとSIG_DROPPED_URI_LIST を発行する // void DragTreeView::set_enable_drop_uri_list() { std::vector< Gtk::TargetEntry > targets_drop; targets_drop.push_back( Gtk::TargetEntry( "text/uri-list" ) ); // ドロップされると on_drag_data_received() が呼び出される drag_dest_set( targets_drop, Gtk::DEST_DEFAULT_ALL, Gdk::ACTION_COPY ); } // // 再描画 // void DragTreeView::redraw_view() { // 今のところ特に何もしない // ポップアップが表示されていたらポップアップを再描画 if( is_popup_shown() && m_popup_win->view() ) m_popup_win->view()->redraw_view(); } // // 色初期化 // void DragTreeView::init_color( const int colorid_text, const int colorid_bg, const int colorid_bg_even ) { if( CONFIG::get_use_tree_gtkrc() ) { m_use_bg_even = false; m_provider->load_from_data( u8"" ); return; } // 文字色 m_color_text.set( CONFIG::get_color( colorid_text ) ); // 背景色 m_color_bg.set( CONFIG::get_color( colorid_bg ) ); m_use_bg_even = ! ( CONFIG::get_color( colorid_bg ) == CONFIG::get_color( colorid_bg_even ) ); m_color_bg_even.set( CONFIG::get_color( colorid_bg_even ) ); // TreeViewの偶数/奇数行を指定するCSSセレクタは動作しないバージョンが // 存在するため偶数行の背景色設定は既存のコードをそのまま使う // https://gitlab.gnome.org/GNOME/gtk/issues/581 try { m_provider->load_from_data( Glib::ustring::compose( u8".%1.view:not(:selected) { color: %2; background-color: %3; }", s_css_classname, m_color_text.to_string(), m_color_bg.to_string() ) ); } catch( Gtk::CssProviderError& err ) { #ifdef _DEBUG std::cout << "ERROR:DragTreeView::init_color load from data failed: " << err.what() << std::endl; #endif } } // // フォント初期化 // void DragTreeView::init_font( const std::string& fontname ) { Pango::FontDescription pfd( fontname ); pfd.set_weight( Pango::WEIGHT_NORMAL ); override_font( pfd ); } // // クロック入力 // void DragTreeView::clock_in() { // ポップアップ表示中はそちらにクロックを回す if( is_popup_shown() && m_popup_win->view() ){ m_popup_win->view()->clock_in(); return; } } // // ポップアップが表示されていてかつマウスがその上にあるか // bool DragTreeView::is_mouse_on_popup() { if( ! is_popup_shown() ) return false; if( ! m_popup_win->view() ) return false; return m_popup_win->view()->is_mouse_on_view(); } // // ポップアップウィンドウ表示 // void DragTreeView::show_popup( const std::string& url, View* view ) { const int mrg_x = 10; const int mrg_y = 10; #ifdef _DEBUG std::cout << "DragTreeView::show_popup url = " << url << std::endl; #endif delete_popup(); m_popup_win = std::make_unique( this, view, mrg_x, mrg_y ); m_popup_win->signal_leave_notify_event().connect( sigc::mem_fun( *this, &DragTreeView::slot_popup_leave_notify_event ) ); m_popup_win->sig_hide_popup().connect( sigc::mem_fun( *this, &DragTreeView::hide_popup ) ); m_pre_popup_url = url; m_popup_shown = true; } // // popup windowの外にポインタが出た // bool DragTreeView::slot_popup_leave_notify_event( GdkEventCrossing* event ) { #ifdef _DEBUG std::cout << "DragTreeView::slot_popup_leave_notify_event\n"; #endif // クリックしたときやホイールを回すと event->mode に GDK_CROSSING_GRAB // か GDK_CROSSING_UNGRAB がセットされてイベントが発生する場合がある if( event->mode == GDK_CROSSING_GRAB ) return false; if( event->mode == GDK_CROSSING_UNGRAB ) return false; // ポインタがポップアップ上にある場合はポップアップは消さない // 時々ポインタがポップアップの上にあってもイベントが発生する場合がある if( is_mouse_on_popup() ) return false; hide_popup(); return true; } // // ポップアップを隠す // void DragTreeView::hide_popup() { if( ! is_popup_shown() ) return; // ポップアップメニューが表示されてたらhideしない if( SESSION::is_popupmenu_shown() ) return; #ifdef _DEBUG std::cout << "DragTreeView::hide_popup\n"; #endif m_popup_win->hide(); if( m_popup_win->view() ) m_popup_win->view()->stop(); if( get_path_under_mouse().empty() ) m_pre_popup_url = std::string(); m_popup_shown = false; } // // ポップアップウィンドウ削除 // void DragTreeView::delete_popup() { if( m_popup_win ){ #ifdef _DEBUG std::cout << "DragTreeView::delete_popup\n"; #endif m_popup_win.reset(); m_pre_popup_url.clear(); m_popup_shown = false; } } // // マウスボタンを押した // bool DragTreeView::on_button_press_event( GdkEventButton* event ) { Gtk::TreeModel::Path path = get_path_under_xy( (int)event->x, (int)event->y ); m_dragging = false; m_selection_canceled = false; sig_button_press().emit( event ); // ドラッグして範囲選択 // m_path_dragstart が empty でない時に範囲選択を行う // on_motion_notify_event()も参照せよ if( m_control.button_alloted( event, CONTROL::TreeRowSelectionButton ) ) m_path_dragstart = m_path_dragpre = path; else m_path_dragstart = m_path_dragpre = Gtk::TreeModel::Path(); // 複数行選択時の動作 if( get_selection()->get_selected_rows().size() >= 2 ){ // D&Dのため、ctrlやshiftなしで普通にクリックしたときも選択解除しない if( !( event->state & GDK_CONTROL_MASK ) && !( event->state & GDK_SHIFT_MASK ) ){ // ただし範囲選択外をクリックしたとき、または範囲選択開始ボタンを押したときは選択解除 if( ! get_selection()->is_selected( path ) || ! m_path_dragstart.empty() ){ get_selection()->unselect_all(); if( get_row( path ) ) set_cursor( path ); m_selection_canceled = true; // ボタンを離したときにシグナルをemitしないようにする } else return true; } } return Gtk::TreeView::on_button_press_event( event ); } // // マウスボタンを離した // bool DragTreeView::on_button_release_event( GdkEventButton* event ) { bool emit_sig = false; // true なら m_sig_button_release をemitする Gtk::TreeModel::Path path = get_path_under_xy( (int)event->x, (int)event->y ); if( ! m_dragging // ドラッグ中ではない && !m_selection_canceled // on_button_press_event()で選択状態を解除していない && !( event->state & GDK_CONTROL_MASK ) && !( event->state & GDK_SHIFT_MASK ) // ctrl/shift + クリックで複数行選択操作をしてない場合 && !( !m_path_dragstart.empty() && ! path.empty() && path != m_path_dragstart ) // 範囲選択操作をしていない場合 ){ // 以上の場合はシグナルをemitする emit_sig = true; // 範囲選択状態でポップアップメニュー以外のボタンを離したら選択解除 if( get_selection()->get_selected_rows().size() >= 2 && ! m_control.button_alloted( event, CONTROL::PopupmenuButton ) ){ get_selection()->unselect_all(); if( get_row( path ) ) set_cursor( path ); emit_sig = false; } } // ポップアップメニューボタンを押したら必ずシグナルをemit if( m_control.button_alloted( event, CONTROL::PopupmenuButton ) ) emit_sig = true; m_path_dragstart = m_path_dragpre = Gtk::TreeModel::Path(); const bool expanded = row_expanded( path ); const bool ret = Gtk::TreeView::on_button_release_event( event ); // 左の△ボタンを押してディレクトリを開け閉めした場合は信号のemitをキャンセル if( expanded != row_expanded( path ) ) emit_sig = false; if( emit_sig ) sig_button_release().emit( event ); return ret; } // // D&D開始 // // このtreeがソースで無い時は呼ばれないのに注意 // void DragTreeView::on_drag_begin( const Glib::RefPtr< Gdk::DragContext >& context ) { #ifdef _DEBUG Gtk::TreeModel::Path path = get_path_under_mouse(); std::cout << "DragTreeView::on_drag_begin path = " << path.to_string() << std::endl; #endif m_dragging = true; CORE::DND_Begin(); Gtk::TreeView::on_drag_begin( context ); } // // D&D 終了 // // このtreeがソースでない時は呼び出されない // void DragTreeView::on_drag_end( const Glib::RefPtr< Gdk::DragContext >& context ) { #ifdef _DEBUG std::cout << "DragTreeView::on_drag_end\n"; #endif m_dragging = false; CORE::DND_End(); Gtk::TreeView::on_drag_end( context ); } // // ドロップされた void DragTreeView::on_drag_data_received( const Glib::RefPtr< Gdk::DragContext >& context, int x, int y, const Gtk::SelectionData& selection, guint info, guint time ) { #ifdef _DEBUG std::cout << "DragTreeView::on_drag_data_received type = " << selection.get_data_type() << std::endl; #endif if( selection.get_data_type() == "text/uri-list" ){ #ifdef _DEBUG std::cout << selection.get_data_as_string() << std::endl; #endif std::list< std::string > uri_list = MISC::get_lines( MISC::url_decode( selection.get_data_as_string() ) ); m_sig_dropped_url_list.emit( uri_list ); } context->drag_finish( true, false, time ); } // // キーボードのキーを押した // bool DragTreeView::on_key_press_event( GdkEventKey* event ) { #ifdef _DEBUG std::cout << "DragTreeView::on_key_press_event\n"; #endif // ポップアップ表示中はそちらにクロックを回す if( is_popup_shown() && m_popup_win->view() ) return m_popup_win->view()->slot_key_press( event ); return JDTreeViewBase::on_key_press_event( event ); } // // マウスを動かした // bool DragTreeView::on_motion_notify_event( GdkEventMotion* event ) { #ifdef _DEBUG // std::cout << "DragTreeView::on_motion_notify_event x = " << event->x << " y = " << event->y << std::endl; #endif // drag_source_set() でセットしたボタン以外でドラッグして範囲選択 // m_path_dragstart が empty で無いときに実行 // DragTreeView::on_button_press_event() も参照せよ Gtk::TreeModel::Path path = get_path_under_xy( (int)event->x, (int)event->y ); if( ! m_path_dragstart.empty() && !path.empty() && path != m_path_dragpre ){ get_selection()->unselect_all(); get_selection()->select( path, m_path_dragstart ); m_path_dragpre = path; } sig_motion_notify().emit( event ); return Gtk::TreeView::on_motion_notify_event( event ); } // マウスのwheelを回した bool DragTreeView::on_scroll_event( GdkEventScroll* event ) { sig_scroll_event().emit( event ); return true; } // // マウスホイールの処理 // void DragTreeView::wheelscroll( GdkEventScroll* event ) { auto adj = get_vadjustment(); double val = adj->get_value(); int scr_inc = get_row_height() * CONFIG::get_tree_scroll_size(); if( event->direction == GDK_SCROLL_UP ) val = MAX( 0, val - scr_inc ); else if( event->direction == GDK_SCROLL_DOWN ) val = MIN( adj->get_upper() - adj->get_page_size(), val + scr_inc ); else if( event->direction == GDK_SCROLL_SMOOTH ) { constexpr double smooth_scroll_factor = 4.0; m_smooth_dy += smooth_scroll_factor * event->delta_y; if( std::abs( m_smooth_dy ) > 1.0 ) { val += std::copysign( scr_inc, m_smooth_dy ); m_smooth_dy = 0.0; } // チルトホイール(横スクロール)対応 constexpr double smooth_scroll_factor_x = 0.5; auto hadj = get_hadjustment(); hadj->set_value( hadj->get_value() + scr_inc * smooth_scroll_factor_x * event->delta_x ); } adj->set_value( val ); #ifdef _DEBUG std::cout << "DragTreeView::on_scroll_event\n"; std::cout << "scr_inc = " << scr_inc << std::endl; std::cout << "lower = " << adj->get_lower() << std::endl; std::cout << "upper = " << adj->get_upper() << std::endl; std::cout << "value = " << val << std::endl; std::cout << "step = " << adj->get_step_increment() << std::endl; std::cout << "page = " << adj->get_page_increment() << std::endl; std::cout << "page_size = " << adj->get_page_size() << std::endl; #endif } bool DragTreeView::on_leave_notify_event( GdkEventCrossing* event ) { if( ! is_mouse_on_popup() ){ hide_popup(); m_pre_popup_url = std::string(); } return Gtk::TreeView::on_leave_notify_event( event ); } // // 範囲選択更新 // void DragTreeView::slot_selection_changed() { int size = get_selection()->get_selected_rows().size(); std::string str; if( size >= 2 ) str = "選択数 " + std::to_string( size ); CORE::core_set_command( "set_info" ,"", str ); } // // 実際の描画の際に cellrenderer のプロパティをセットするスロット関数 // void DragTreeView::slot_cell_data( Gtk::CellRenderer* cell, const Gtk::TreeModel::iterator& it ) { if( ! m_use_bg_even ){ cell->property_cell_background_set() = false; return; } Gtk::TreeModel::Row row = *it; Gtk::TreePath path = get_model()->get_path( row ); std::string path_str = path.to_string(); #ifdef _DEBUG std::cout << "DragTreeView::slot_cell_data path = " << path_str << std::endl; #endif bool even = false; int rownum = atoi( path_str.c_str() ); if( rownum %2 ) even = true; size_t pos = path_str.find( ':' ); while( pos != std::string::npos ){ path_str = path_str.substr( pos+1 ); rownum = atoi( path_str.c_str() ); if( ( even && ( rownum %2 ) ) || ( ! even && !( rownum %2 ) ) ) even = true; else even = false; pos = path_str.find( ':' ); } // 偶数行に色を塗る if( even ){ cell->property_cell_background_rgba() = m_color_bg_even; cell->property_cell_background_set() = true; } else cell->property_cell_background_set() = false; } jdim-0.7.0/src/skeleton/dragtreeview.h000066400000000000000000000124111417047150700177220ustar00rootroot00000000000000// ライセンス: GPL2 // // ドラッグ開始可能なtreeviewクラス // // set_enable_drop_uri_list() で他のアプリから text/uri-list のドロップを受け付ける // // フォント変更、偶数、奇数行別に色分けも可能。コンストラクタの use_usr_fontcolor を trueにしてフォントや色を指定する // 複数行選択、ツールチップ、ポップアップの表示も可能 // #ifndef _DRAGTREEVIEW_H #define _DRAGTREEVIEW_H #include "gtkmmversion.h" #include "treeviewbase.h" #include "control/control.h" #include #include namespace SKELETON { class View; class PopupWin; typedef sigc::signal< void, const std::list< std::string >& > SIG_DROPPED_URI_LIST; class DragTreeView : public JDTreeViewBase { std::string m_url; std::string m_dndtarget; bool m_dragging{}; // ドラッグ中 // 範囲選択に使用 bool m_selection_canceled{}; // 範囲選択を解除したときにsig_button_release()を発行しないようにする Gtk::TreeModel::Path m_path_dragstart; Gtk::TreeModel::Path m_path_dragpre; static constexpr const char* s_css_classname = u8"jd-dragtreeview"; Glib::RefPtr< Gtk::CssProvider > m_provider = Gtk::CssProvider::create(); // 色 bool m_use_bg_even{}; Gdk::RGBA m_color_text; Gdk::RGBA m_color_bg; Gdk::RGBA m_color_bg_even; // ポップアップウィンドウ用 std::unique_ptr m_popup_win; std::string m_pre_popup_url; bool m_popup_shown{}; // 入力コントローラ CONTROL::Control m_control; // text/uri-list をドロップされた SIG_DROPPED_URI_LIST m_sig_dropped_url_list; double m_smooth_dy{ 0.0 }; // GDK_SCROLL_SMOOTH のスクロール変化量 public: // use_usr_fontcolor が true の時はフォントや色を指定する DragTreeView( const std::string& url, const std::string& dndtarget, const bool use_usr_fontcolor, const std::string& fontname, const int colorid_text, const int colorid_bg, const int colorid_bg_even ); ~DragTreeView() noexcept; virtual void clock_in(); SIG_DROPPED_URI_LIST sig_dropped_uri_list(){ return m_sig_dropped_url_list; } const std::string& get_dndtarget() const { return m_dndtarget; } // 他のアプリからの text/url-list のドロップを有効にする // ドロップされるとSIG_DROPPED_URI_LIST を発行する void set_enable_drop_uri_list(); void redraw_view(); // 色初期化 void init_color( const int colorid_text, const int colorid_bg, const int colorid_bg_even ); // フォント初期化 void init_font( const std::string& fontname ); // ポップアップウィンドウ表示 const std::string& pre_popup_url() const { return m_pre_popup_url; } void reset_pre_popupurl( const std::string& url ){ if( ! m_pre_popup_url.empty() && m_pre_popup_url != url ) m_pre_popup_url = std::string(); } void show_popup( const std::string& url, View* view ); void hide_popup(); // マウスホイールの処理 void wheelscroll( GdkEventScroll* event ); // 実際の描画の際に cellrenderer のプロパティをセットするスロット関数 void slot_cell_data( Gtk::CellRenderer* cell, const Gtk::TreeModel::iterator& it ); protected: // drag_source_set() でセットしたボタンでドラッグした時に呼び出される順番は // // (1) on_button_press_event // (2) on_drag_begin // (3) on_drag_motion // (4) on_button_release_event // (5) on_drag_drop // (6) on_drag_end // // drag_source_set() でセットしたボタン以外でドラッグしたときは on_drag_motion() // ではなくて普通に on_motion_notify_event() が呼ばれるのに注意 // bool on_button_press_event( GdkEventButton* event ) override; bool on_button_release_event( GdkEventButton* event ) override; void on_drag_begin( const Glib::RefPtr< Gdk::DragContext>& context ) override; void on_drag_end( const Glib::RefPtr< Gdk::DragContext>& context ) override; void on_drag_data_received( const Glib::RefPtr< Gdk::DragContext >& context, int x, int y, const Gtk::SelectionData& selection, guint info, guint time ) override; bool on_key_press_event( GdkEventKey* event ) override; bool on_motion_notify_event( GdkEventMotion* event ) override; bool on_scroll_event( GdkEventScroll* event ) override; bool on_leave_notify_event( GdkEventCrossing* event ) override; private: // ポップアップが表示されているか bool is_popup_shown() const { return ( m_popup_win && m_popup_shown ); } // ポップアップ削除 void delete_popup(); // ポップアップが表示されていてかつマウスがその上にあるか bool is_mouse_on_popup(); bool slot_popup_leave_notify_event( GdkEventCrossing* event ); void slot_selection_changed(); }; } #endif jdim-0.7.0/src/skeleton/editcolumns.cpp000066400000000000000000000015431417047150700201170ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "editcolumns.h" #include "xml/tools.h" #include "type.h" using namespace SKELETON; EditColumns::EditColumns() { add( m_name ); add( m_image ); add( m_type ); add( m_url ); add( m_data ); add( m_underline ); add( m_expand ); add( m_fgcolor ); add( m_dirid ); } EditColumns::~EditColumns() noexcept = default; void EditColumns::setup_row( Gtk::TreeModel::Row& row, const Glib::ustring url, const Glib::ustring name, const Glib::ustring data, const int type, const size_t dirid ) { row[ m_name ] = name; row[ m_image ] = XML::get_icon( type ); row[ m_type ] = type; row[ m_url ] = url; row[ m_data ] = data; row[ m_expand ] = false; row[ m_underline ] = false; row[ m_dirid ] = dirid; } jdim-0.7.0/src/skeleton/editcolumns.h000066400000000000000000000030221417047150700175560ustar00rootroot00000000000000// ライセンス: GPL2 // // 編集可能な ColumnRecord クラス // // SKELETON::EditTreeView と組み合わせて使う // #ifndef _EDITCOLUMNS_H #define _EDITCOLUMNS_H #include namespace SKELETON { // 列ID enum { EDITCOL_NAME = 0, EDITCOL_IMAGE, // 以下不可視 EDITCOL_TYPE, EDITCOL_URL, EDITCOL_DATA, EDITCOL_UNDERLINE, EDITCOL_EXPAND, EDITCOL_FGCOLOR, EDITCOL_DIRID, EDITCOL_NUM_COL }; class EditColumns : public Gtk::TreeModel::ColumnRecord { public: Gtk::TreeModelColumn< Glib::ustring > m_name; // サブジェクト Gtk::TreeModelColumn< Glib::RefPtr< Gdk::Pixbuf > > m_image; // アイコン画像 Gtk::TreeModelColumn< int > m_type; // 行のタイプ Gtk::TreeModelColumn< Glib::ustring > m_url; // アドレス Gtk::TreeModelColumn< Glib::ustring > m_data; // ユーザデータ Gtk::TreeModelColumn< bool > m_underline; // 行に下線を引く Gtk::TreeModelColumn< bool > m_expand; // Dom::parse() で使用 Gtk::TreeModelColumn< Gdk::RGBA > m_fgcolor; // 文字色 Gtk::TreeModelColumn< size_t > m_dirid; // ディレクトリID EditColumns(); ~EditColumns() noexcept; virtual void setup_row( Gtk::TreeModel::Row& row, const Glib::ustring url, const Glib::ustring name, const Glib::ustring data, const int type, const size_t dirid ); }; } #endif jdim-0.7.0/src/skeleton/edittreeview.cpp000066400000000000000000001501511417047150700202710ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "gtkmmversion.h" #include "edittreeview.h" #include "editcolumns.h" #include "msgdiag.h" #include "undobuffer.h" #include "config/globalconf.h" #include "dbtree/interface.h" #include "jdlib/miscgtk.h" #include "xml/document.h" #include "sharedbuffer.h" #include "global.h" #ifndef MAX #define MAX( a, b ) ( a > b ? a : b ) #endif #ifndef MIN #define MIN( a, b ) ( a < b ? a : b ) #endif enum { DNDSCROLLSPEED = 250 // D&D 時のスクロール速度 }; // // 行の最新アドレスを取得 // const std::string get_uptodate_url( const std::string& url_org, const int type ) { std::string url = url_org; if( type == TYPE_BOARD || type == TYPE_BOARD_UPDATE ){ url = DBTREE::url_boardbase( url ); } else if( type == TYPE_THREAD || type == TYPE_THREAD_UPDATE || type == TYPE_THREAD_OLD ){ url = DBTREE::url_dat( url ); } return url; } // // 行の最新状態を取得 // int get_uptodate_type( const std::string& url, const int type_org ) { int type = type_org; if( type == TYPE_BOARD || type == TYPE_BOARD_UPDATE ){ if( DBTREE::board_status( url ) & STATUS_UPDATE ) type = TYPE_BOARD_UPDATE; else type = TYPE_BOARD; } else if( type == TYPE_THREAD || type == TYPE_THREAD_UPDATE || type == TYPE_THREAD_OLD ){ if( DBTREE::article_status( url ) & STATUS_OLD ) type = TYPE_THREAD_OLD; else if( DBTREE::article_status( url ) & STATUS_UPDATE ) type = TYPE_THREAD_UPDATE; else type = TYPE_THREAD; } return type; } // // ソート用比較クラス // // EditTreeView::sort()で使用 // class compare_path { SKELETON::JDTreeViewBase& m_treeview; SKELETON::EditColumns& m_columns; int m_mode; int type_to_order( const int type ) const { constexpr const int order[] = { TYPE_DIR, TYPE_VBOARD, TYPE_BOARD_UPDATE, TYPE_BOARD, TYPE_THREAD_UPDATE, TYPE_THREAD, TYPE_IMAGE, TYPE_COMMENT, TYPE_THREAD_OLD, TYPE_UNKNOWN }; int i = 0; for( ; order[ i ] != TYPE_UNKNOWN; ++i ){ if( type == order[ i ] ) return i; } return i; } public: compare_path( SKELETON::JDTreeViewBase& treeview, SKELETON::EditColumns& columns, const int mode ) : m_treeview( treeview ), m_columns( columns ), m_mode( mode ){} // path_a が上ならtrue path_b が上ならfalse bool operator () ( const Gtk::TreePath& path_a, const Gtk::TreePath& path_b ) { const Gtk::TreeRow row_a = m_treeview.get_row( path_a ); const Gtk::TreeRow row_b = m_treeview.get_row( path_b ); if( ! row_a || ! row_b ) return false; // 名前でソート if( m_mode == SKELETON::SORT_BY_NAME ){ const Glib::ustring name_a = row_a[ m_columns.m_name ]; const Glib::ustring name_b = row_b[ m_columns.m_name ]; if( name_a > name_b ) return false; else if( name_a < name_b ) return true; } // タイプでソート const Glib::ustring url_a = row_a[ m_columns.m_url ]; const Glib::ustring url_b = row_b[ m_columns.m_url ]; const int type_a = get_uptodate_type( url_a.raw(), row_a[ m_columns.m_type ] ); const int type_b = get_uptodate_type( url_b.raw(), row_b[ m_columns.m_type ] ); const int order_a = type_to_order( type_a ); const int order_b = type_to_order( type_b ); if( order_a == order_b ) return false; return ( order_a < order_b ); } }; ///////////////////////////////////////////////// using namespace SKELETON; EditTreeView::EditTreeView( const std::string& url, const std::string& dndtarget, EditColumns& columns, const bool use_usr_fontcolor, const std::string& fontname, const int colorid_text, const int colorid_bg, const int colorid_bg_even ) : DragTreeView( url, dndtarget, use_usr_fontcolor, fontname, colorid_text, colorid_bg, colorid_bg_even ), m_url( url ), m_columns( columns ) {} EditTreeView::EditTreeView( const std::string& url, const std::string& dndtarget, EditColumns& columns ) : DragTreeView( url, dndtarget, false, "", 0, 0, 0 ), m_url( url ), m_columns( columns ) {} EditTreeView::~EditTreeView() { #ifdef _DEBUG std::cout << "EditTreeView::~EditTreeView url = " << m_url << std::endl; #endif } // // 編集可能にする // void EditTreeView::set_editable_view( const bool editable ) { m_editable = editable; if( m_editable ){ // D&D のドロップを可能にする std::vector< Gtk::TargetEntry > targets; targets.push_back( Gtk::TargetEntry( get_dndtarget(), Gtk::TARGET_SAME_APP, 0 ) ); drag_dest_set( targets ); } } // // クロック入力 // void EditTreeView::clock_in() { DragTreeView::clock_in(); // D&D 中にカーソルが画面の上か下の方にある場合はスクロールさせる if( m_editable && m_dragging_on_tree ){ ++m_dnd_counter; if( m_dnd_counter >= DNDSCROLLSPEED / TIMER_TIMEOUT ){ m_dnd_counter = 0; Gtk::TreePath path = get_path_under_mouse(); auto adjust = get_vadjustment(); if( get_row( path ) && adjust ){ const int height = get_height(); const int step = (int)adjust->get_step_increment() / 2; int val = -1; int x,y; MISC::get_pointer_at_window( get_window(), x, y ); if( y < step * 2 ){ val = MAX( 0, (int)adjust->get_value() - step ); } else if( y > height - step * 2 ){ val = MIN( (int)adjust->get_value() + step, (int)( adjust->get_upper() - adjust->get_page_size() ) ); } if( val != -1 ){ adjust->set_value( val ); path = get_path_under_mouse(); draw_underline_while_dragging( path ); } } } } // 行を追加した直後に scroll_to_row() を呼んでもまだツリーが更新されてなくて // 正しくスクロールしないので更新されてからスクロールする else if( m_pre_adjust_upper && ! m_jump_path.empty() ){ const int upper = ( int )( get_vadjustment()->get_upper() ); #ifdef _DEBUG std::cout << "adjust_upper : " << m_pre_adjust_upper << " -> " << upper << " count = " << m_jump_count << std::endl; #endif // 時間切れ const int max_jump_count = 200 / TIMER_TIMEOUT; ++m_jump_count; if( m_pre_adjust_upper != upper || m_jump_count >= max_jump_count ){ #ifdef _DEBUG std::cout << "scroll to " << m_jump_path.to_string() << std::endl; #endif Gtk::TreeRow row = get_row( Gtk::TreePath( m_jump_path ) ); if( row ) scroll_to_row( m_jump_path, 0.5 ); m_pre_adjust_upper = 0; m_jump_path = Gtk::TreePath(); m_jump_count = 0; } } } // // path にスクロール // // 遅延させてツリー構造が変わってからスクロールする // clock_in()を参照 // void EditTreeView::set_scroll( const Gtk::TreePath& path ) { m_pre_adjust_upper = ( int )( get_vadjustment()->get_upper() ); m_jump_path = path; m_jump_count = 0; } // // treestoreのセット // void EditTreeView::set_treestore( const Glib::RefPtr< Gtk::TreeStore >& treestore ) { set_model( treestore ); set_headers_visible( false ); } // // xml -> tree 展開して treestore をセットする // void EditTreeView::xml2tree( const XML::Document& document, Glib::RefPtr< Gtk::TreeStore >& treestore, const std::string& root_name ) { #ifdef _DEBUG std::cout << "EditTreeView::xml2tree "; std::cout << " root = " << root_name; std::cout << " size = " << document.size() << std::endl; #endif treestore->clear(); unset_model(); // 開いてるツリーの格納用 std::list< Gtk::TreePath > list_path_expand; // Domノードから Gtk::TreeStore をセット document.set_treestore( treestore, m_columns, root_name, list_path_expand ); set_treestore( treestore ); // ディレクトリIDのセット(まだセットされていない場合) get_max_dirid(); set_dirid(); // ディレクトリオープン std::list< Gtk::TreePath >::iterator it_path = list_path_expand.begin(); while( it_path != list_path_expand.end() ) { expand_parents( *it_path ); expand_row( *it_path, false ); ++it_path; } } // // tree -> XML 変換 // void EditTreeView::tree2xml( XML::Document& document, const std::string& root_name ) { Glib::RefPtr< Gtk::TreeStore > treestore = Glib::RefPtr< Gtk::TreeStore >::cast_dynamic( get_model() ); if( ! treestore || treestore->children().empty() ){ document.clear(); return; } // 全てのツリーに row[ m_columns.expand ] の値をセットする set_expanded_row( treestore, treestore->children() ); // m_treestore からノードツリーを作成 document.init( treestore, m_columns, root_name ); #ifdef _DEBUG std::cout << "EditTreeView::tree2xml "; std::cout << " root = " << root_name; std::cout << " size = " << document.size() << std::endl; #endif } // ディレクトリIDの最大値を取得 void EditTreeView::get_max_dirid() { m_max_dirid = 0; SKELETON::EditTreeViewIterator it( *this, m_columns, Gtk::TreePath() ); for( ; ! it.end(); ++it ){ Gtk::TreeModel::Row row = *it; if( row[ m_columns.m_type ] == TYPE_DIR ){ const size_t dirid = row[ m_columns.m_dirid ]; if( dirid > m_max_dirid ) m_max_dirid = dirid; } } ++m_max_dirid; #ifdef _DEBUG std::cout << "EditTreeView::get_max_dirid id = " << m_max_dirid << std::endl; #endif } // IDがついていないディレクトリにIDをセットする void EditTreeView::set_dirid() { #ifdef _DEBUG std::cout << "EditTreeView::set_dirid\n"; #endif SKELETON::EditTreeViewIterator it( *this, m_columns, Gtk::TreePath() ); for( ; ! it.end(); ++it ){ Gtk::TreeModel::Row row = *it; if( row[ m_columns.m_type ] == TYPE_DIR ){ const size_t dirid = row[ m_columns.m_dirid ]; if( ! dirid ){ row[ m_columns.m_dirid ] = m_max_dirid++; #ifdef _DEBUG std::cout << row[ m_columns.m_name ] << " id = " << row[ m_columns.m_dirid ] << std::endl; #endif } } } } // // 全てのツリーに m_columns.m_expand の値をセットする( tree2xml()で使用 ) // void EditTreeView::set_expanded_row( Glib::RefPtr< Gtk::TreeStore >& treestore, const Gtk::TreeModel::Children& children ) { Gtk::TreeModel::iterator it = children.begin(); while( it != children.end() ) { const Gtk::TreePath path = treestore->get_path( *it ); // ツリーが開いているか if( row_expanded( path ) ) (*it)[ m_columns.m_expand ] = true; else (*it)[ m_columns.m_expand ] = false; // 再帰 if( ! (*it)->children().empty() ) set_expanded_row( treestore, (*it)->children() ); ++it; } } // // 列の作成 // // ypad : 行間スペース // Gtk::TreeViewColumn* EditTreeView::create_column( const int ypad ) { // Gtk::mange してるのでdeleteしなくてもよい Gtk::TreeViewColumn* col = Gtk::manage( new Gtk::TreeViewColumn( "name" ) ); col->pack_start( m_columns.m_image, Gtk::PACK_SHRINK ); m_ren_text = Gtk::manage( new Gtk::CellRendererText() ); m_ren_text->signal_edited().connect( sigc::mem_fun( *this, &EditTreeView::slot_ren_text_on_edited ) ); m_ren_text->signal_editing_canceled().connect( sigc::mem_fun( *this, &EditTreeView::slot_ren_text_on_canceled ) ); m_ren_text->property_underline() = Pango::UNDERLINE_SINGLE; // 行間スペース if( ypad >= 0 ) m_ren_text->property_ypad() = ypad; col->pack_start( *m_ren_text, true ); col->add_attribute( *m_ren_text, "text", EDITCOL_NAME ); col->add_attribute( *m_ren_text, "underline", EDITCOL_UNDERLINE ); col->add_attribute( *m_ren_text, "foreground_rgba", EDITCOL_FGCOLOR ); col->set_sizing( Gtk::TREE_VIEW_COLUMN_FIXED ); // 実際の描画時に偶数行に色を塗る col->set_cell_data_func( *col->get_first_cell(), sigc::mem_fun( *this, &DragTreeView::slot_cell_data ) ); col->set_cell_data_func( *m_ren_text, sigc::mem_fun( *this, &DragTreeView::slot_cell_data ) ); append_column( *col ); return col; } // // 新規ディレクトリ作成 // Gtk::TreePath EditTreeView::create_newdir( const Gtk::TreePath& path ) { CORE::DATA_INFO_LIST list_info; CORE::DATA_INFO info; info.type = TYPE_DIR; info.name = "新規ディレクトリ"; info.path = path.to_string(); while( ! dirid_to_path( m_max_dirid ).empty() ) ++m_max_dirid; info.dirid = m_max_dirid; list_info.push_back( info ); const bool before = false; const bool scroll = false; const bool force = false; // append_info 内でundoのコミットをしないで名前を変更してからslot_ren_text_on_canceled()でコミットする const bool cancel_undo_commit = true; const int check_dup = 0; // 項目の重複チェックをしない append_info( list_info, path, before, scroll, force, cancel_undo_commit, check_dup ); Gtk::TreePath path_new = get_current_path(); set_cursor( path_new ); rename_row( path_new ); return path_new; } // ディレクトリIDとパスを相互変換 Gtk::TreePath EditTreeView::dirid_to_path( const size_t dirid ) { if( ! dirid ) return Gtk::TreePath(); SKELETON::EditTreeViewIterator it( *this, m_columns, Gtk::TreePath() ); for( ; ! it.end(); ++it ){ Gtk::TreeModel::Row row = *it; if( row[ m_columns.m_type ] == TYPE_DIR ){ if( row[ m_columns.m_dirid ] == dirid ) return get_model()->get_path( row ); } } return Gtk::TreePath(); } size_t EditTreeView::path_to_dirid( const Gtk::TreePath path ) { Gtk::TreeModel::Row row = get_row( Gtk::TreePath( path ) ); if( row ) return row[ m_columns.m_dirid ]; return 0; } // // コメント挿入 // Gtk::TreePath EditTreeView::create_newcomment( const Gtk::TreePath& path ) { CORE::DATA_INFO_LIST list_info; CORE::DATA_INFO info; info.type = TYPE_COMMENT; info.name = "コメント"; info.path = path.to_string(); list_info.push_back( info ); const bool before = false; const bool scroll = false; const bool force = false; // append_info 内でundoのコミットをしないで名前を変更してからslot_ren_text_on_canceled()でコミットする const bool cancel_undo_commit = true; const int check_dup = 0; // 項目の重複チェックをしない append_info( list_info, path, before, scroll, force, cancel_undo_commit, check_dup ); Gtk::TreePath path_new = get_current_path(); set_cursor( path_new ); rename_row( path_new ); return path_new; } // // pathで指定した行の名前変更 // void EditTreeView::rename_row( const Gtk::TreePath& path ) { if( path.empty() ) return; // edit可 slot_ren_text_on_edited() と slot_ren_text_on_canceled で false にする m_ren_text->property_editable() = true; set_cursor( path, *get_column( 0 ), true ); } // // 行の名前を変更したときにCellRendererTextから呼び出される // void EditTreeView::slot_ren_text_on_edited( const Glib::ustring& path, const Glib::ustring& text ) { #ifdef _DEBUG std::cout << "EditTreeView::slot_ren_text_on_edited\n" << "path = " << path << std::endl << "text = " << text << std::endl; #endif Gtk::TreeRow row = get_row( Gtk::TreePath( path ) ); if( row ){ const Glib::ustring text_before = row[ m_columns.m_name ]; row.set_value( m_columns.m_name, text ); if( m_editable && m_undo_buffer ) m_undo_buffer->set_name( Gtk::TreePath( path ), text, text_before ); } slot_ren_text_on_canceled(); } // // 行の名前変更をキャンセルしたときにCellRendererTextから呼び出される // void EditTreeView::slot_ren_text_on_canceled() { #ifdef _DEBUG std::cout << "EditTreeView::slot_ren_text_on_canceld\n"; #endif m_ren_text->property_editable() = false; if( m_editable && m_undo_buffer ) m_undo_buffer->commit(); } // // D&D中の受信側上でマウスを動かした // // 編集可能の時は下線を引く // 他のwidgetがソースの時も呼び出される。ドラッグ中は on_motion_notify_event() は呼び出されない // bool EditTreeView::on_drag_motion( const Glib::RefPtr& context, int x, int y, guint time ) { const Gtk::TreePath path = get_path_under_mouse(); #ifdef _DEBUG if( ! m_dragging_on_tree ) std::cout << "EditTreeView::on_drag_enter"; else std::cout << "EditTreeView::on_drag_motion"; std::cout << " x = " << x << " y = " << y; if( ! path.empty() ) std::cout << " path = " << path.to_string(); std::cout << std::endl; #endif const bool ret = DragTreeView::on_drag_motion( context, x, y, time ); m_dragging_on_tree = true; draw_underline_while_dragging( path ); return ret; } // // D&D中に受信側からマウスが出た // void EditTreeView::on_drag_leave( const Glib::RefPtr& context, guint time ) { #ifdef _DEBUG std::cout << "EditTreeView::on_drag_leave\n"; #endif DragTreeView::on_drag_leave( context, time ); m_dragging_on_tree = false; draw_underline( m_drag_path_uline, false ); } // // D&Dで受信側がデータ送信を要求してきた // void EditTreeView::on_drag_data_get( const Glib::RefPtr& context, Gtk::SelectionData& selection_data, guint info, guint time ) { #ifdef _DEBUG std::cout << "EditTreeView::on_drag_data_get target = " << selection_data.get_target() << std::endl; #endif DragTreeView::on_drag_data_get( context, selection_data, info, time ); // 範囲選択行を共有バッファに入れる if( selection_data.get_target() == get_dndtarget() ){ CORE::DATA_INFO_LIST list_info; const bool dir = true; get_info_in_selection( list_info, dir ); if( list_info.size() ){ CORE::SBUF_set_list( list_info ); // 受信側の on_drag_data_received() を呼び出す selection_data.set( get_dndtarget(), m_url ); } } } // // D&Dの受信側がデータを取得 // void EditTreeView::on_drag_data_received( const Glib::RefPtr& context, int x, int y, const Gtk::SelectionData& selection_data, guint info, guint time ) { const std::string url_from = selection_data.get_data_as_string(); #ifdef _DEBUG std::cout << "EditTreeView::on_drag_data_received target = " << selection_data.get_target() << " url = " << m_url << " url_from = " << url_from << std::endl; #endif DragTreeView::on_drag_data_received( context, x, y, selection_data, info, time ); draw_underline( m_drag_path_uline, false ); m_exec_drop = false; m_row_dest = Gtk::TreeRow(); m_row_dest_before = false; m_dropped_from_other = false; // 挿入先のrowを保存 if( m_editable && selection_data.get_target() == get_dndtarget() ){ CORE::DATA_INFO_LIST list_info = CORE::SBUF_list_info(); if( ! list_info.size() ) return; const Gtk::TreePath path_dest = get_path_under_mouse(); m_row_dest = get_row( path_dest ); // セル内の座標を見て真ん中より上だったら上に挿入 if( m_row_dest ){ int cell_x, cell_y, cell_w, cell_h; get_cell_xy_wh( cell_x, cell_y, cell_w, cell_h ); if( cell_y < cell_h / 2 ) m_row_dest_before = true; } if( url_from != m_url ) m_dropped_from_other = true; #ifdef _DEBUG std::cout << "x = " << x << " y = " << y << " path_dest = " << path_dest.to_string() << " before = " << m_row_dest_before << " other = " << m_dropped_from_other << std::endl; #endif // 同じ widget からドロップされた場合は上書きになっていないかチェックする if( m_row_dest && ! m_dropped_from_other ){ for( const CORE::DATA_INFO& data : list_info ) { #ifdef _DEBUG std::cout << data.name << " path = " << data.path << std::endl; #endif if( data.path.empty() ) continue; // 移動先と送り側が同じならキャンセル if( data.path == path_dest.to_string() ) return; // 移動先がサブディレクトリに含まれないかチェック if( Gtk::TreePath( data.path ).is_ancestor( path_dest ) ){ SKELETON::MsgDiag mdiag( get_parent_win(), "移動先は送り側のディレクトリのサブディレクトリです", false, Gtk::MESSAGE_ERROR ); mdiag.run(); return; } } } m_exec_drop = true; // 送信側の on_drag_data_delete()を呼び出す context->drag_finish( true, true, time ); } } // // D&Dの送信側がデータを削除 // void EditTreeView::on_drag_data_delete( const Glib::RefPtr& context ) { #ifdef _DEBUG std::cout << "EditTreeView::on_drag_data_delete\n"; #endif DragTreeView::on_drag_data_delete( context ); // 選択行の削除 if( m_editable ){ CORE::DATA_INFO_LIST list_info = CORE::SBUF_list_info(); delete_rows( list_info, Gtk::TreePath() ); if( m_editable && m_undo_buffer ){ m_undo_buffer->set_list_info_selected( list_info ); // undo したときに選択する列 m_undo_buffer->set_list_info( CORE::DATA_INFO_LIST(), list_info ); } } } // // D&Dで受信側にデータがドロップされた // // 他のwidgetがソースの時も呼ばれるのに注意 // bool EditTreeView::on_drag_drop( const Glib::RefPtr& context, int x, int y, guint time ) { #ifdef _DEBUG std::cout << "EditTreeView::on_drag_drop\n"; #endif const bool ret = DragTreeView::on_drag_drop( context, x, y, time ); if( ! get_model() ) return ret; if( ! m_editable ) return ret; if( ! m_exec_drop ) return ret; Gtk::TreePath path_dest = Gtk::TreePath(); bool before = false; if( m_row_dest ){ path_dest = get_model()->get_path( m_row_dest ); before = m_row_dest_before; } #ifdef _DEBUG std::cout << "path_dest = " << path_dest.to_string() << " before = " << before << std::endl; #endif // 共有バッファ内の行を追加 const bool scroll = false; const bool force = false; const bool cancel_undo_commit = false; const int check_dup = m_dropped_from_other ? CONFIG::get_check_favorite_dup() : 0; const CORE::DATA_INFO_LIST list_info = append_info( CORE::SBUF_list_info(), path_dest, before, scroll, force, cancel_undo_commit, check_dup ); CORE::SBUF_clear_info(); if( m_dropped_from_other ) m_sig_dropped_from_other.emit( list_info ); return ret; } // // D&Dで受信側に終了を知らせる // // この widget がソースでない時は呼び出されない // void EditTreeView::on_drag_end( const Glib::RefPtr< Gdk::DragContext >& context ) { #ifdef _DEBUG std::cout << "EditTreeView::on_drag_end\n"; #endif draw_underline( m_drag_path_uline, false ); DragTreeView::on_drag_end( context ); } // // ドラッグ中にマウスカーソルの下に下線を引く // void EditTreeView::draw_underline_while_dragging( Gtk::TreePath path ) { if( ! m_editable ) return; if( path.empty() ) return; bool draw = true; int cell_x, cell_y, cell_w, cell_h; get_cell_xy_wh( cell_x, cell_y, cell_w, cell_h ); // 真ん中より上の場合 if( cell_y < cell_h / 2 ){ path.prev(); if( is_dir( path ) || path == Gtk::TreePath( "0" ) // 先頭行 ) draw = false; } draw_underline( m_drag_path_uline, false ); if( draw ) draw_underline( path, true ); m_drag_path_uline = path; } // // draw == true なら pathに下線を引く // void EditTreeView::draw_underline( const Gtk::TreePath& path, const bool draw ) { if( ! m_editable ) return; Gtk::TreeRow row = get_row( path ); if( ! row ) return; row[ m_columns.m_underline ] = draw; static_cast( row ); // cppcheck: unreadVariable } // // path は ディレクトリか // bool EditTreeView::is_dir( const Gtk::TreeModel::iterator& it ) const { const Gtk::TreeRow row = ( *it ); if( ! row ) return false; if( row[ m_columns.m_type ] == TYPE_DIR ) return true; return false; } bool EditTreeView::is_dir( const Gtk::TreePath& path ) { if( path.size() <= 0 ) return false; Gtk::TreeModel::iterator it = get_model()->get_iter( path ); return is_dir( it ); } // 前のディレクトリに移動 void EditTreeView::prev_dir() { Gtk::TreePath path = get_current_path(); for(;;){ Gtk::TreePath new_path = prev_path( path ); if( path == new_path ){ goto_top(); return; } path = new_path; if( is_dir( path ) ) break; } set_cursor( path ); } // 次のディレクトリに移動 void EditTreeView::next_dir() { Gtk::TreePath path = get_current_path(); for(;;){ path = next_path( path ); if( ! path.size() || ! get_row( path ) ){ goto_bottom(); return; } if( is_dir( path ) ) break; } set_cursor( path ); } // // 指定したアドレスの行が含まれているか // bool EditTreeView::exist_row( const std::string& url, const int type ) { if( url.empty() ) return false; const std::string url_target = get_uptodate_url( url, type ); SKELETON::EditTreeViewIterator it( *this, m_columns, Gtk::TreePath() ); for( ; ! it.end(); ++it ){ Gtk::TreeModel::Row row = *it; const Glib::ustring url_row = row[ m_columns.m_url ]; if( ! url_row.empty() ){ if( url_target == get_uptodate_url( url_row.raw(), row[ m_columns.m_type ] ) ) return true; } } return false; } // // ディレクトリ内を全選択 // void EditTreeView::select_all_dir( Gtk::TreePath path_dir ) { if( ! is_dir( path_dir ) ) return; get_selection()->select( path_dir ); path_dir.down(); while( get_row( path_dir ) ){ get_selection()->select( path_dir ); select_all_dir( path_dir ); path_dir.next(); } } // // list_info を path_dest 以下に追加 // // list_info の各path にあらかじめ値をセットしておくこと // // scroll = true なら追加した行にスクロールする // // force = true なら m_editable が false でも追加 // // cancel_undo_commit = true なら undo バッファをコミットしない // // check_dup == 0 ならチェックせず追加 1 なら重複チェックをして重複してたらダイアログ表示、2なら重複チェックして重複してたら追加しない // // (1) path_dest が empty なら一番最後 // // (2) before = true なら path_dest の前 // // (3) path_destがディレクトリなら path_dest の下 // // (4) そうでなければ path_dest の後 // CORE::DATA_INFO_LIST EditTreeView::append_info( const CORE::DATA_INFO_LIST& list_info, const Gtk::TreePath& path_dest, const bool before, const bool scroll, const bool force, const bool cancel_undo_commit, int check_dup ) { CORE::DATA_INFO_LIST list_info_src; if( ! force && ! m_editable ) return list_info_src; if( ! list_info.size() ) return list_info_src; #ifdef _DEBUG std::cout << "EditTreeView::append_info" << " path_dest = " << path_dest.to_string() << " before = " << before << " check_dup = " << check_dup << std::endl; #endif if( ! check_dup ) list_info_src = list_info; // 重複がないかチェック else{ #ifdef _DEBUG std::cout << "checking duplicatiion\n"; #endif for( const CORE::DATA_INFO& info : list_info ) { if( exist_row( info.url, info.type ) ){ if( check_dup == 2 ) continue; SKELETON::MsgCheckDiag mdiag( get_parent_win(), info.name + "\n\nは既に含まれています。追加しますか?", "今後表示しない(常に追加しない) (_D)", Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO, Gtk::RESPONSE_NO ); mdiag.set_title( "お気に入り追加確認" ); const int ret = mdiag.run(); if( mdiag.get_chkbutton().get_active() ){ CONFIG::set_check_favorite_dup( 2 ); check_dup = 2; } if( ret != Gtk::RESPONSE_YES ) continue; } list_info_src.push_back( info ); } } if( ! list_info_src.size() ) return list_info_src; if( m_editable && m_undo_buffer ){ CORE::DATA_INFO_LIST list_info_selected; const bool dir = false; get_info_in_selection( list_info_selected, dir ); m_undo_buffer->set_list_info_selected( list_info_selected ); // undo したときに選択する列 } replace_infopath( list_info_src, path_dest, before ); expand_parents( path_dest ); append_rows( list_info_src ); select_info( list_info_src ); if( m_editable && m_undo_buffer ){ m_undo_buffer->set_list_info( list_info_src, CORE::DATA_INFO_LIST() ); m_undo_buffer->set_list_info_selected( list_info_src ); // redo したときに選択する列 if( ! cancel_undo_commit ) m_undo_buffer->commit(); } // 遅延させてツリー構造が変わってからスクロールする // clock_in()を参照 if( scroll ) set_scroll( Gtk::TreePath( list_info_src.front().path ) ); return list_info_src; } // // pathをまとめて削除 // // force = true なら m_editable が false でも削除 // void EditTreeView::delete_path( const std::list& list_path, const bool force ) { if( ! list_path.size() ) return; if( ! force && ! m_editable ) return; #ifdef _DEBUG std::cout << "EditTreeView::delete_path\n"; #endif // 削除範囲に現在のカーソルがある時はカーソルの位置を変更する bool selected = false; const Gtk::TreePath path_selected = get_current_path(); CORE::DATA_INFO_LIST list_info; for( const Gtk::TreePath& path : list_path ) { if( path_selected == path ) selected = true; #ifdef _DEBUG std::cout << "path = " << path.to_string() << std::endl; #endif CORE::DATA_INFO info; path2info( info, path ); list_info.push_back( info ); } // カーソルを最後の行の次の行に移動するため、あらかじめ削除範囲の最後の行に移動しておく Gtk::TreePath next = path_selected; if( selected ) next = next_path( Gtk::TreePath( ( list_info.back() ).path ), true ); delete_rows( list_info, next ); if( m_editable && m_undo_buffer ){ m_undo_buffer->set_list_info_selected( list_info ); // undo したときに選択する列 m_undo_buffer->set_list_info( CORE::DATA_INFO_LIST(), list_info ); CORE::DATA_INFO_LIST list_info_selected; const bool dir = false; get_info_in_selection( list_info_selected, dir ); m_undo_buffer->set_list_info_selected( list_info_selected ); // redo したときに選択する列 m_undo_buffer->commit(); } } // // 選択した行をまとめて削除 // // force = true なら m_editable が false でも削除 // void EditTreeView::delete_selected_rows( const bool force ) { if( ! force && ! m_editable ) return; std::list< Gtk::TreeModel::iterator > list_selected = get_selected_iterators(); if( ! list_selected.size() ) return; // ディレクトリが含まれていないか無いか確認 for( const Gtk::TreeModel::iterator& iter : list_selected ) { if( is_dir( iter ) ){ SKELETON::MsgDiag mdiag( get_parent_win(), "ディレクトリを削除するとディレクトリ内の行も全て削除されます。削除しますか?", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO ); if( mdiag.run() != Gtk::RESPONSE_YES ) return; break; } } #ifdef _DEBUG std::cout << "EditTreeView::delete_selected_rows" << " path = " << get_model()->get_path( *list_selected.begin() ).to_string() << std::endl; #endif // 削除する行を取得 CORE::DATA_INFO_LIST list_info; constexpr bool scan_in_dir = true; get_info_in_selection( list_info, scan_in_dir ); // カーソルを最後の行の次の行に移動するため、あらかじめ削除範囲の最後の行に移動しておく const Gtk::TreePath next = next_path( Gtk::TreePath( ( list_info.back() ).path ), true ); delete_rows( list_info, next ); if( m_editable && m_undo_buffer ){ m_undo_buffer->set_list_info_selected( list_info ); // undo したときに選択する列 m_undo_buffer->set_list_info( CORE::DATA_INFO_LIST(), list_info ); CORE::DATA_INFO_LIST list_info_selected; constexpr bool not_scan_in_dir = false; get_info_in_selection( list_info_selected, not_scan_in_dir ); m_undo_buffer->set_list_info_selected( list_info_selected ); // redo したときに選択する列 m_undo_buffer->commit(); } } // // UNDO // void EditTreeView::undo() { if( ! m_undo_buffer ) return; if( ! m_undo_buffer->get_enable_undo() ) return; m_undo_buffer->undo(); const UNDO_DATA& data = m_undo_buffer->get_undo_data(); const int size = data.size(); #ifdef _DEBUG std::cout << "EditTreeView::undo size = " << size << std::endl; #endif for( int i = size-1; i >=0; --i ){ if( ! data[ i ].list_info_selected.empty() ){ set_scroll( Gtk::TreePath( data[ i ].list_info_selected.front().path ) ); select_info( data[ i ].list_info_selected ); } // 追加キャンセル if( ! data[ i ].list_info_append.empty() ) delete_rows( data[ i ].list_info_append, Gtk::TreePath() ); // 削除キャンセル if( ! data[ i ].list_info_delete.empty() ){ expand_rows( data[ i ].list_info_delete ); append_rows( data[ i ].list_info_delete ); } // 名前変更キャンセル Gtk::TreeRow row = get_row( data[ i ].path_renamed ); if( row ){ scroll_to_row( data[ i ].path_renamed, 0.5 ); // ツリー構造に変化は無いのですぐにスクロールする set_cursor( data[ i ].path_renamed ); if( ! data[ i ].name_before.empty() ) row[ m_columns.m_name ] = data[ i ].name_before; } } } // // REDO // void EditTreeView::redo() { #ifdef _DEBUG std::cout << "EditTreeView::redo\n"; #endif if( ! m_undo_buffer ) return; if( ! m_undo_buffer->get_enable_redo() ) return; const UNDO_DATA& data = m_undo_buffer->get_undo_data(); #ifdef _DEBUG std::cout << "EditTreeView::redo size = " << data.size() << std::endl; #endif for( const UNDO_ITEM& item : data ) { if( ! item.list_info_selected.empty() ){ set_scroll( Gtk::TreePath( item.list_info_selected.front().path ) ); select_info( item.list_info_selected ); } // 削除 if( ! item.list_info_delete.empty() ) delete_rows( item.list_info_delete, Gtk::TreePath() ); // 追加 if( ! item.list_info_append.empty() ){ expand_rows( item.list_info_append ); append_rows( item.list_info_append ); } // 名前変更 Gtk::TreeRow row = get_row( item.path_renamed ); if( row ){ scroll_to_row( item.path_renamed, 0.5 ); // ツリー構造に変化は無いのですぐにスクロールする set_cursor( item.path_renamed ); if( ! item.name_new.empty() ) row[ m_columns.m_name ] = item.name_new; } } m_undo_buffer->redo(); } // // list_info の各要素の path を path_dest 以下に変更 // // list_info の各要素の path にあらかじめ値をセットしておくこと // // (1) path_dest が empty なら一番最後 // // (2) before = true なら path_dest の前 // // (3) path_destがディレクトリなら path_dest の下 // // (4) そうでなければ path_dest の後 // void EditTreeView::replace_infopath( CORE::DATA_INFO_LIST& list_info, const Gtk::TreePath& path_dest, const bool before ) { if( ! list_info.size() ) return; if( ! get_model() ) return; Gtk::TreePath path = path_dest; // path の初期値を求める if( path.empty() ){ Gtk::TreeModel::Children children = get_model()->children(); if( children.empty() ) path = Gtk::TreePath( "0" ); else{ path = get_model()->get_path( *( std::prev( children.end() ) ) ); path.next(); } } else if( before ); else if( is_dir( path ) ) path.down(); else path.next(); // list_infoの先頭に path をセット CORE::DATA_INFO_LIST::iterator it = list_info.begin(); Gtk::TreePath path_prev( ( *it ).path ); ( *it ).path = path.to_string(); const size_t max_pathsize_org = path_prev.size(); const size_t max_pathsize = path.size(); #ifdef _DEBUG std::cout << "EditTreeView::replace_infopath\n" << "max_pathsize_org = " << max_pathsize_org << " max_pathsize = " << max_pathsize << std::endl << "path_prev = " << path_prev.to_string() << " -> " << ( *it ).path << " type = " << ( *it ).type << " name = " << ( *it ).name << std::endl; #endif for( ++it; it != list_info.end() ; ++it ){ CORE::DATA_INFO& info = ( *it ); // list_infoの構造と合わせて path を更新していく do{ // 次 Gtk::TreePath path_tmp = path_prev; path_tmp.next(); if( path_tmp.to_string() == info.path ){ path.next(); break; } // ディレクトリ下がる path_tmp = path_prev; path_tmp.down(); if( path_tmp.to_string() == info.path ){ path.down(); break; } // ディレクトリ上がる bool reset = true; int count_up = 0; path_tmp = path_prev; while( path_tmp.size() != max_pathsize_org ){ ++count_up; path_tmp.up(); path_tmp.next(); if( path_tmp.to_string() == info.path ){ while( count_up-- ) path.up(); path.next(); reset = false; break; } } // どれにも該当しない場合には一番上のレベルまで戻る if( reset ){ while( path.size() != max_pathsize ) path.up(); path.next(); } } while(0); path_prev = Gtk::TreePath( info.path ); info.path = path.to_string(); #ifdef _DEBUG std::cout << "path_prev = " << path_prev.to_string() << " -> " << info.path << " type = " << info.type << " name = " << info.name << " data = " << info.data << std::endl; #endif } } // // 選択行をlist_infoにセット // // dir : trueの時はディレクトリが選択されているときはディレクトリ内の行もlist_infoに再帰的にセットする // void EditTreeView::get_info_in_selection( CORE::DATA_INFO_LIST& list_info, const bool dir ) { list_info.clear(); if( ! get_model() ) return; std::list< Gtk::TreeModel::iterator > list_selected = get_selected_iterators(); if( ! list_selected.size() ) return; for( const Gtk::TreeModel::iterator& iter : list_selected ) { Gtk::TreePath path = get_model()->get_path( iter ); CORE::DATA_INFO info; path2info( info, path ); // 既に path が list_info に登録されていたら登録しない const auto equal_to = [&info]( const CORE::DATA_INFO& i ) { return i.path == info.path; }; if( std::any_of( list_info.cbegin(), list_info.cend(), equal_to ) ) continue; list_info.push_back( info ); if( dir ) get_info_in_dir( list_info, path ); } } // // ディレクトリ(path_dir)内の行を全てlist_infoにセットする // // path_dir が empty() ならルートの行を全てセット // void EditTreeView::get_info_in_dir( CORE::DATA_INFO_LIST& list_info, const Gtk::TreePath& path_dir ) { if( ! path_dir.empty() && ! is_dir( path_dir ) ) return; CORE::DATA_INFO info; Gtk::TreePath path = path_dir; if( path_dir.empty() ) path = Gtk::TreePath( "0" ); else path.down(); while( get_row( path ) ){ path2info( info, path ); list_info.push_back( info ); get_info_in_dir( list_info, path ); path.next(); } } // // path から info を取得 // void EditTreeView::path2info( CORE::DATA_INFO& info, const Gtk::TreePath& path ) { Glib::ustring tmp_str; Gtk::TreeRow row = get_row( path ); info.type = row[ m_columns.m_type ]; info.parent = nullptr; tmp_str = row[ m_columns.m_url ]; info.url = tmp_str.raw(); tmp_str = row[ m_columns.m_name ]; info.name = tmp_str.raw(); tmp_str = row[ m_columns.m_data ]; info.data = tmp_str.raw(); info.path = path.to_string(); info.dirid = row[ m_columns.m_dirid ]; info.expanded = row_expanded( path ); } // // 一行追加 // // 戻り値は追加した行のpath // // (1) path_dest が empty なら一番最後に作る // // (2) before = true なら前に作る // // (3) path_dest がディレクトリかつ sudir == true なら path_dest の下に追加。 // // (4) そうでなければ path_dest の後に追加 // Gtk::TreePath EditTreeView::append_one_row( const std::string& url, const std::string& name, const int type, const size_t dirid, const std::string& data, const Gtk::TreePath& path_dest, const bool before, const bool subdir ) { Glib::RefPtr< Gtk::TreeStore > treestore = Glib::RefPtr< Gtk::TreeStore >::cast_dynamic( get_model() ); if( ! treestore ) return Gtk::TreePath(); #ifdef _DEBUG std::cout << "EditTreeView::append_one_row : " << name << " path = " << path_dest.to_string() << " before = " << before << " subdir = " << subdir << std::endl; #endif Gtk::TreeRow row_dest = get_row( path_dest ); Gtk::TreeRow row_new; // 一番下に追加 if( ! row_dest ) row_new = *( treestore->append() ); // 前に追加 else if( before ) row_new = *( treestore->insert( row_dest ) ); // ディレクトリの下に追加 else if( subdir && row_dest[ m_columns.m_type ] == TYPE_DIR ){ row_new = *( treestore->prepend( row_dest.children() ) ); } // 後ろに追加 else row_new = *( treestore->insert_after( row_dest ) ); // 行のアドレスや状態を最新にする const std::string url_new = get_uptodate_url( url, type ); const int type_new = get_uptodate_type( url_new, type ); m_columns.setup_row( row_new, url_new, name, data, type_new, dirid ); return treestore->get_path( row_new ); } // // list_info に示した行の親を再起的にexpandする // list_info の各要素の path にあらかじめ値をセットしておくこと // void EditTreeView::expand_rows( const CORE::DATA_INFO_LIST& list_info ) { #ifdef _DEBUG std::cout << "EditTreeView::expand_rows\n"; #endif if( ! list_info.size() ) return; for( const CORE::DATA_INFO& info : list_info ) { #ifdef _DEBUG std::cout << "path = " << info.path << std::endl; #endif expand_parents( Gtk::TreePath( info.path ) ); } } // // list_info に示した行を追加 // // list_info の各要素の path にあらかじめ値をセットしておくこと // void EditTreeView::append_rows( const CORE::DATA_INFO_LIST& list_info ) { #ifdef _DEBUG std::cout << "EditTreeView::append_rows\n"; #endif if( ! list_info.size() ) return; Gtk::TreePath path_parent; bool expand_parent = false; bool head_info = true; for( const CORE::DATA_INFO& info : list_info ) { Gtk::TreePath path( info.path ); #ifdef _DEBUG std::cout << "path = " << path.to_string() << " type = " << info.type << " name = " << info.name << " data = " << info.data << std::endl; #endif bool before = false; bool subdir = false; const bool prev = path.prev(); if( ! prev ){ // 先頭行 if( path == Gtk::TreePath( "0" ) ) before = true; // ディレクトリの先頭 else { path.up(); subdir = true; // 最初の info なら親のディレクトリを開く if( head_info ){ #ifdef _DEBUG std::cout << "expand parent dir\n"; #endif path_parent = path; expand_parent = true; } } } append_one_row( info.url, info.name, info.type, info.dirid, info.data, path, before, subdir ); head_info = false; } // ディレクトリを開く if( expand_parent ) expand_row( path_parent, false ); for( const CORE::DATA_INFO& info : list_info ) { if( info.expanded ){ const Gtk::TreePath path( info.path ); expand_row( path, false ); } } m_updated = true; } // // list_info に示した行を削除 // // list_info の各要素の path にあらかじめ値をセットしておくこと // // 削除した後、path_select にカーソルを移動する(emptyの場合は移動しない) // void EditTreeView::delete_rows( const CORE::DATA_INFO_LIST& list_info, const Gtk::TreePath& path_select ) { #ifdef _DEBUG std::cout << "EditTreeView::delete_rows"; if( ! path_select.empty() ) std::cout << " path_select = " << path_select.to_string(); std::cout << std::endl; #endif if( ! list_info.size() ) return; Glib::RefPtr< Gtk::TreeStore > treestore = Glib::RefPtr< Gtk::TreeStore >::cast_dynamic( get_model() ); if( ! treestore ) return; // あらかじめ path_select にカーソルを移動しておく // もし path_select が存在しなかったら削除してから一番下に移動 bool gotobottom = false; if( ! path_select.empty() ){ gotobottom = ( ! get_row( path_select ) ); if( ! gotobottom ) set_cursor( path_select ); } CORE::DATA_INFO_LIST::const_iterator it = list_info.end(); do{ --it; const CORE::DATA_INFO& info = ( *it ); Gtk::TreePath path( info.path ); #ifdef _DEBUG std::cout << path.to_string() << " type = " << info.type << " name = " << info.name << " url = " << info.url << " data = " << info.data << std::endl; #endif Gtk::TreeRow row = get_row( path ); treestore->erase( row ); } while( it != list_info.begin() ); m_updated = true; if( gotobottom ) goto_bottom(); } // // list_infoに示した行を選択 // void EditTreeView::select_info( const CORE::DATA_INFO_LIST& list_info ) { get_selection()->unselect_all(); for( const CORE::DATA_INFO& info : list_info ) { Gtk::TreePath path( info.path ); get_selection()->select( path ); } } // // ソート実行 // // path : ディレクトリなら中をソート、そうでなければそのレベルでソート // mode : ソートのモード。詳しくは class compare_path を参照 // void EditTreeView::sort( const Gtk::TreePath& path, const int mode ) { if( ! m_editable ) return; if( ! get_row( path ) ) return; CORE::DATA_INFO_LIST list_info; Gtk::TreePath path_head = path; if( is_dir( path_head ) ) path_head.down(); else while( path_head.prev() ); if( ! get_row( path_head ) ) return; Gtk::TreePath path_parent = path_head; if( path_parent.size() >= 2 ) path_parent.up(); else path_parent = Gtk::TreePath(); #ifdef _DEBUG std::cout << "EditTreeView::sort head = " << path_head.to_string() << " parent = " << path_parent.to_string() << std::endl; #endif // ソート std::list< Gtk::TreePath > list_path; Gtk::TreePath path_tmp = path_head; while( get_row( path_tmp ) ){ list_path.push_back( path_tmp ); path_tmp.next(); } list_path.sort( compare_path( *this, m_columns, mode ) ); for( const Gtk::TreePath& p : list_path ) { const Gtk::TreeRow row = get_row( p ); if( row ){ #ifdef _DEBUG std::cout << p.to_string() << " type = " << row[ m_columns.m_type ] << " name = " << row[ m_columns.m_name ] << std::endl; #endif CORE::DATA_INFO info; path2info( info, p ); list_info.push_back( info ); get_info_in_dir( list_info, p ); } } // path_parent の中を全て削除 if( m_undo_buffer ){ CORE::DATA_INFO_LIST list_info_selected; const bool dir = false; get_info_in_selection( list_info_selected, dir ); m_undo_buffer->set_list_info_selected( list_info_selected ); // undo したときに選択する列 } CORE::DATA_INFO_LIST list_info_delete; get_info_in_dir( list_info_delete, path_parent ); delete_rows( list_info_delete, Gtk::TreePath() ); if( m_undo_buffer ){ m_undo_buffer->set_list_info( CORE::DATA_INFO_LIST(), list_info_delete ); } // list_info を path_parent の中に追加 const bool before = false; const bool scroll = false; const bool force = false; const bool cancel_undo_commit = false; const int check_dup = 0; // 項目の重複チェックをしない append_info( list_info, path_parent, before, scroll, force, cancel_undo_commit, check_dup ); set_scroll( path_parent ); } //////////////////////////////// // // EditTreeViewの項目の反復子 // // path から反復開始 // path が empty の時はルートから反復する // EditTreeViewIterator::EditTreeViewIterator( EditTreeView& treeview, EditColumns& columns, const Gtk::TreePath path ) : m_treeview( treeview ) , m_columns( columns ) , m_path( path ) { const bool root = ( m_path.empty() ); if( root ){ Gtk::TreeModel::Children children = m_treeview.get_model()->children(); if( ! children.empty() ) m_path = m_treeview.get_model()->get_path( children.begin() ); else{ m_end = true; return; } } Gtk::TreeModel::Row row = m_treeview.get_row( m_path ); if( ! row ) m_end = true; else{ m_depth = m_path.size(); if( ! root ) ++m_depth; } } Gtk::TreeModel::Row EditTreeViewIterator::operator * () { return m_treeview.get_row( m_path ); } void EditTreeViewIterator::operator ++ () { if( m_end ) return; Gtk::TreeModel::Row row = m_treeview.get_row( m_path ); while( 1 ){ if( row ){ switch( row[ m_columns.m_type ] ){ case TYPE_DIR: m_path.down(); break; default: m_path.next(); break; } } else{ if( m_path.size() > m_depth ){ m_path.up(); m_path.next(); } else{ m_end = true; break; } } row = m_treeview.get_row( m_path ); if( row ) break; } } jdim-0.7.0/src/skeleton/edittreeview.h000066400000000000000000000266371417047150700177510ustar00rootroot00000000000000// ライセンス: GPL2 // // D&Dによって行の編集が可能なtreeviewクラス // // set_editable_view() で true を指定すると編集可能になる // #ifndef _EDITTREEVIEW_H #define _EDITTREEVIEW_H #include "dragtreeview.h" #include "type.h" #include "data_info.h" #include #include namespace XML { class Document; } namespace SKELETON { // ソートのモード enum { SORT_BY_TYPE = 0, SORT_BY_NAME }; // 他のwidgetからドロップされた typedef sigc::signal< void, const CORE::DATA_INFO_LIST& > SIG_DROPPED_FROM_OTHER; class EditColumns; class UNDO_BUFFER; class EditTreeView : public DragTreeView { SIG_DROPPED_FROM_OTHER m_sig_dropped_from_other; std::string m_url; Gtk::Window* m_parent_win{}; EditColumns& m_columns; Gtk::CellRendererText* m_ren_text{}; // Gtk managed // 編集可能 bool m_editable{}; // UNDO 用のバッファ UNDO_BUFFER* m_undo_buffer{}; // D&D用変数 Gtk::TreePath m_drag_path_uline; // D&D時に下線を引いている行 int m_dnd_counter{}; // D&D時のスクロールのタイミング用 bool m_exec_drop{}; // D&D時のドロップ処理で挿入するか Gtk::TreeRow m_row_dest{}; // ドロップ先 bool m_row_dest_before{}; // m_row_dest の前に挿入するか bool m_dropped_from_other{}; // 他のwidgetからドロップされた // スクロール用変数 // 詳しくは EditTreeView::clock_in() を参照 int m_pre_adjust_upper{}; Gtk::TreePath m_jump_path; int m_jump_count{}; // 更新された bool m_updated{}; // ドラッグがこのツリー上で行われている bool m_dragging_on_tree{}; // ディレクトリIDの最大値 size_t m_max_dirid{}; public: // ColumnRecord として SKELETON::EditColumns を派生したものを使用すること EditTreeView( const std::string& url, const std::string& dndtarget, EditColumns& columns, const bool use_usr_fontcolor, const std::string& fontname, const int colorid_text, const int colorid_bg, const int colorid_bg_even ); EditTreeView( const std::string& url, const std::string& dndtarget, EditColumns& columns ); ~EditTreeView(); void clock_in() override; SIG_DROPPED_FROM_OTHER sig_dropped_from_other(){ return m_sig_dropped_from_other; } void set_parent_win( Gtk::Window* parent_win ){ m_parent_win = parent_win; } Gtk::Window* get_parent_win(){ return m_parent_win; } void set_undo_buffer( UNDO_BUFFER* undo_buffer ){ m_undo_buffer = undo_buffer; } bool is_updated() const { return m_updated; }; void set_updated( const bool set ){ m_updated = set; } // treestoreのセット void set_treestore( const Glib::RefPtr< Gtk::TreeStore >& treestore ); // xml -> tree 展開して treestore をセットする void xml2tree( const XML::Document& document, Glib::RefPtr< Gtk::TreeStore >& treestore, const std::string& root_name ); // tree -> XML 変換 void tree2xml( XML::Document& document, const std::string& root_name ); // 列の作成 // ypad : 行間スペース Gtk::TreeViewColumn* create_column( const int ypad ); // 編集可能にする void set_editable_view( const bool editable ); // 指定した path のタイプは ディレクトリか bool is_dir( const Gtk::TreeModel::iterator& it ) const; bool is_dir( const Gtk::TreePath& path ); // 次のディレクトリに移動 void prev_dir(); void next_dir(); // 指定したアドレスの行が含まれているか bool exist_row( const std::string& url, const int type ); // ディレクトリ内を全選択 void select_all_dir( Gtk::TreePath path_dir ); // 新規ディレクトリ作成 Gtk::TreePath create_newdir( const Gtk::TreePath& path ); // ディレクトリIDとパスを相互変換 Gtk::TreePath dirid_to_path( const size_t dirid ); size_t path_to_dirid( const Gtk::TreePath path ); // コメント挿入 Gtk::TreePath create_newcomment( const Gtk::TreePath& path ); // pathで指定した行の名前の変更 void rename_row( const Gtk::TreePath& path ); bool is_renaming_row() const { return m_ren_text->property_editable(); } // list_info を path_dest 以下に追加 // // list_info の各path にあらかじめ値をセットしておくこと // scroll = true なら追加した行にスクロールする // force = true なら m_editable が false でも追加 // cancel_undo_commit = true なら undo バッファをコミットしない // check_dup == 0 ならチェックせず追加 1 なら重複チェックをして重複してたらダイアログ表示、2なら重複チェックして重複してたら追加しない // // (1) path_dest が empty なら一番最後 // (2) before = true なら path_dest の前 // (3) path_destがディレクトリなら path_dest の下 // (4) そうでなければ path_dest の後 CORE::DATA_INFO_LIST append_info( const CORE::DATA_INFO_LIST& list_info, const Gtk::TreePath& path_dest, const bool before, const bool scroll, const bool force, const bool cancel_undo_commit, int check_dup ); // pathをまとめて削除 // force = true なら m_editable が false でも削除 void delete_path( const std::list& list_path, const bool force ); // 選択した行をまとめて削除 // force = true なら m_editable が false でも削除 void delete_selected_rows( const bool force ) override; void undo(); void redo(); // 選択行をlist_infoにセットする // dir : true の時はディレクトリが選択されているときはディレクトリ内の行もlist_infoに再帰的にセットする void get_info_in_selection( CORE::DATA_INFO_LIST& list_info, const bool dir ); // 一行追加 // 戻り値は追加した行のpath // (1) path_dest が empty なら一番最後に作る // (2) before = true なら前に作る // (3) path_dest がディレクトリかつ sudir == true なら path_dest の下に追加。 // (4) そうでなければ path_dest の後に追加 Gtk::TreePath append_one_row( const std::string& url, const std::string& name, const int type, const size_t dirid, const std::string& data, const Gtk::TreePath& path_dest, const bool before, const bool subdir ); // ソート実行 void sort( const Gtk::TreePath& path, const int mode ); protected: bool on_drag_motion( const Glib::RefPtr& context, int x, int y, guint time ) override; void on_drag_leave( const Glib::RefPtr< Gdk::DragContext >& context, guint time ) override; bool on_drag_drop( const Glib::RefPtr& context, int x, int y, guint time ) override; void on_drag_data_get( const Glib::RefPtr< Gdk::DragContext >& context, Gtk::SelectionData& selection_data, guint info, guint time ) override; void on_drag_data_received( const Glib::RefPtr< Gdk::DragContext >& context, int x, int y, const Gtk::SelectionData& selection_data, guint info, guint time ) override; void on_drag_data_delete( const Glib::RefPtr& context ) override; void on_drag_end( const Glib::RefPtr< Gdk::DragContext>& context ) override; private: // path にスクロール void set_scroll( const Gtk::TreePath& path ); // set_model()をprivate化して使用不可にする。代わりにset_treestore()を使用すること void set_model( const Glib::RefPtr< Gtk::TreeModel >& model ){ Gtk::TreeView::set_model( model ); } // ディレクトリIDの最大値を取得 void get_max_dirid(); // ディレクトリにIDをセットする void set_dirid(); // 全てのツリーに m_columns.m_expand の値をセットする( tree2xml()で使用 ) void set_expanded_row( Glib::RefPtr< Gtk::TreeStore >& treestore, const Gtk::TreeModel::Children& children ); void slot_ren_text_on_edited( const Glib::ustring& path, const Glib::ustring& text ); void slot_ren_text_on_canceled(); // ドラッグ中にマウスカーソルの下に下線を引く void draw_underline_while_dragging( Gtk::TreePath path ); // draw == true なら pathに下線を引く void draw_underline( const Gtk::TreePath& path, bool const draw ); // list_info の各要素の path を path_dest 以下に変更 // list_info の各様の path にあらかじめ値をセットしておくこと // (1) path_dest が empty なら一番最後 // (2) before = true なら path_dest の前 // (3) path_destがディレクトリなら path_dest の下 // (4) そうでなければ path_dest の後 void replace_infopath( CORE::DATA_INFO_LIST& list_info, const Gtk::TreePath& path_dest, const bool before ); // ディレクトリ(path_dir)内の行を全てlist_infoにセットする void get_info_in_dir( CORE::DATA_INFO_LIST& list_info, const Gtk::TreePath& path_dir ); // path から info を取得 void path2info( CORE::DATA_INFO& info, const Gtk::TreePath& path ); // list_info に示した行の親を再起的にexpandする // list_info の各要素の path にあらかじめ値をセットしておくこと void expand_rows( const CORE::DATA_INFO_LIST& list_info ); // list_info に示した行を追加 // list_info の各要素の path にあらかじめ値をセットしておくこと void append_rows( const CORE::DATA_INFO_LIST& list_info ); // list_info に示した行を削除 // list_info の各要素の path にあらかじめ値をセットしておくこと // 削除した後、path_select にカーソルを移動する(emptyの場合は移動しない) void delete_rows( const CORE::DATA_INFO_LIST& list_info, const Gtk::TreePath& path_select ); // list_infoに示した行を選択 void select_info( const CORE::DATA_INFO_LIST& list_info ); }; //////////////////////////////// // EditTreeViewの項目の反復子 class EditTreeViewIterator { EditTreeView& m_treeview; EditColumns& m_columns; Gtk::TreePath::size_type m_depth; bool m_end{}; Gtk::TreePath m_path; public: // path から反復開始 // path が empty の時はルートから反復する EditTreeViewIterator( EditTreeView& treeview, EditColumns& columns, const Gtk::TreePath path ); Gtk::TreeModel::Row operator * (); Gtk::TreePath get_path() const { return m_path; } void operator ++ (); bool end() const { return m_end; } }; } #endif jdim-0.7.0/src/skeleton/editview.cpp000066400000000000000000000524321417047150700174140ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "gtkmmversion.h" #include "jddebug.h" #include "editview.h" #include "aamenu.h" #include "control/controlid.h" #include "control/controlutil.h" #include "aamanager.h" #include "environment.h" #include "session.h" #include "jdlib/miscutil.h" #include "config/globalconf.h" using namespace SKELETON; enum { MIN_AAMENU_LINES = 12 }; // extend-selectionのハンドラーはC APIを利用するためヘッダーには公開しない static gboolean EditTextView_slot_extend_selection( GtkTextView*, GtkTextExtendSelection granularity, GtkTextIter* location, GtkTextIter* start, GtkTextIter* end, gpointer ); // 区切り文字の判定 // FIXME: src/article/drawareabase.cpp にある同名の関数と統合する static inline bool is_separate_char( char32_t c ) { return g_unichar_isspace( c ) || g_unichar_ispunct( c ); } EditTextView::EditTextView() : Gtk::TextView() , m_pre_offset{ -1 } , m_pre_line{ -1 } , m_line_offset{ -1 } { // コントロールモード設定 m_control.add_mode( CONTROL::MODE_EDIT ); m_control.add_mode( CONTROL::MODE_MESSAGE ); get_buffer()->signal_changed().connect( sigc::mem_fun( *this, &EditTextView::slot_buffer_changed ) ); // NOTE: gtkmmでextend-selectionシグナルが公開されていなかった g_signal_connect( G_OBJECT( gobj() ), "extend-selection", G_CALLBACK( &EditTextView_slot_extend_selection ), nullptr ); } // メンバーに不完全型のスマートポインターがあるためデストラクタはinlineにできない EditTextView::~EditTextView() noexcept = default; // // カーソルの位置に挿入 // // use_br == true なら改行を入れる // void EditTextView::insert_str( const std::string& str, bool use_br ) { std::string br; if( use_br ){ Gtk::TextIter it = get_buffer()->get_insert()->get_iter(); if( it.get_chars_in_line() > 1 ) br = "\n\n"; else if( it.backward_char() && it.get_chars_in_line() > 1 ) br = "\n"; } get_buffer()->insert_at_cursor( br + str ); scroll_to( get_buffer()->get_insert(), 0.0 ); } void EditTextView::cursor_up_down( bool up ) { Gtk::TextIter it = get_buffer()->get_insert()->get_iter(); int line = it.get_line(); int offset = it.get_line_offset(); it.set_line_offset( 0 ); // 上 if( up ){ if( ! it.backward_line() ) return; } // 下 else{ if( ! it.forward_line() ){ // 最終行に文字が無い場合 if( get_buffer()->get_line_count() -1 == it.get_line() ) it.forward_to_end(); else return; } } if( m_line_offset >= 0 && offset == m_pre_offset && line == m_pre_line ) offset = m_line_offset; if( offset < it.get_chars_in_line() ){ it.set_line_offset( offset ); m_line_offset = -1; } // 文字数オーバー else{ m_line_offset = offset; if( it.get_chars_in_line() > 1 && ! it.forward_to_line_end() ) it.forward_to_end(); } get_buffer()->place_cursor( it ); scroll_to( get_buffer()->get_insert(), 0.0 ); m_pre_line = get_buffer()->get_insert()->get_iter().get_line(); m_pre_offset = get_buffer()->get_insert()->get_iter().get_line_offset(); } void EditTextView::cursor_up() { cursor_up_down( true ); } void EditTextView::cursor_down() { cursor_up_down( false ); } void EditTextView::cursor_left() { Gtk::TextIter it = get_buffer()->get_insert()->get_iter(); if( it.backward_char() ) get_buffer()->place_cursor( it ); } void EditTextView::cursor_right() { Gtk::TextIter it = get_buffer()->get_insert()->get_iter(); if( ! it.forward_char() ) it.forward_to_end(); get_buffer()->place_cursor( it ); } void EditTextView::cursor_home() { Gtk::TextIter it = get_buffer()->get_insert()->get_iter(); it.set_line_offset( 0 ); get_buffer()->place_cursor( it ); } void EditTextView::cursor_end() { Gtk::TextIter it = get_buffer()->get_insert()->get_iter(); if( ! it.forward_to_line_end() ) it.forward_to_end(); get_buffer()->place_cursor( it ); } void EditTextView::delete_char() { // 範囲選択消去 if( get_buffer()->erase_selection() ) return; Gtk::TextIter it = get_buffer()->get_insert()->get_iter(); Gtk::TextIter it2 = it; if( ! it2.forward_char() ) it2.forward_to_end(); m_delete_pushed = true; get_buffer()->erase( it, it2 ); } void EditTextView::backsp_char() { // 範囲選択消去 if( get_buffer()->erase_selection() ) return; Gtk::TextIter it = get_buffer()->get_insert()->get_iter(); Gtk::TextIter it2 = it; if( it2.backward_char() ) get_buffer()->erase( it2, it ); } // // バッファの文字列が変化したときに UNDO バッファを変更する // void EditTextView::slot_buffer_changed() { if( m_cancel_change ) return; #ifdef _DEBUG std::cout << "EditTextView::slot_buffer_changed\n"; #endif Glib::ustring text = get_buffer()->get_text(); unsigned int lng = text.length(); unsigned int pre_lng = m_pre_text.length(); unsigned int size = lng > pre_lng ? lng - pre_lng : pre_lng - lng; if( size ){ UNDO_DATA udata; udata.pos = 0; udata.append = false; // diff を取る for( ; udata.pos < MIN( lng, pre_lng ) && text.at( udata.pos ) == m_pre_text.at( udata.pos ); ++udata.pos ); // 追加 if( lng > pre_lng ){ udata.append = true; udata.str_diff = text.substr( udata.pos, size ); } // 削除 else udata.str_diff = m_pre_text.substr( udata.pos, size ); // カーソルの位置を取得 udata.pos_cursor = get_buffer()->get_insert()->get_iter().get_offset(); if( udata.append ) udata.pos_cursor -= size; else { if( size > 1 // 範囲選択で消した場合 || ! m_delete_pushed // backspaceで消した場合 ) udata.pos_cursor += size; } #ifdef _DEBUG std::cout << "size = " << size << " offset = " << udata.pos << " cursor = " << udata.pos_cursor; if( udata.append ) std::cout << " + "; else std::cout << " - "; std::cout << "pre = " << m_pre_text << " text = " << text << " diff = " << udata.str_diff << std::endl; #endif m_undo_tree.push_back( udata ); m_undo_pos = m_undo_tree.size() -1; } m_pre_text = text; } // // undo // void EditTextView::undo() { if( ! m_undo_tree.size() ) return; if( m_undo_pos < 0 ){ // 根元まで来たらツリーの先頭に戻る m_undo_pos = m_undo_tree.size() -1; return; } UNDO_DATA udata = m_undo_tree[ m_undo_pos-- ]; #ifdef _DEBUG std::cout << "EditTextView::undo pos = " << m_undo_pos << " size = " << m_undo_tree.size() << std::endl; std::cout << "offset = " << udata.pos << " cursor = " << udata.pos_cursor; if( udata.append ) std::cout << " - "; else std::cout << " + "; std::cout << udata.str_diff << std::endl; #endif m_cancel_change = true; // slot_buffer_changed() の呼出をキャンセル // 追加と削除を逆転 udata.append = ! udata.append; // 追加 if( udata.append ) get_buffer()->insert( get_buffer()->get_iter_at_offset( udata.pos ), udata.str_diff ); // 削除 else get_buffer()->erase( get_buffer()->get_iter_at_offset( udata.pos ), get_buffer()->get_iter_at_offset( udata.pos + udata.str_diff.length() ) ); // カーソル移動 Gtk::TextIter it = get_buffer()->get_iter_at_offset( udata.pos_cursor ); get_buffer()->place_cursor( it ); m_cancel_change = false; m_pre_text = get_buffer()->get_text(); // 逆方向にツリーを延ばす if( udata.append ) udata.pos_cursor += udata.str_diff.length(); m_undo_tree.push_back( udata ); } void EditTextView::clear_undo() { m_undo_tree.clear(); } // // マウスボタン入力のフック // bool EditTextView::on_button_press_event( GdkEventButton* event ) { m_sig_button_press.emit( event ); return Gtk::TextView::on_button_press_event( event ); } // // キー入力のフック // bool EditTextView::on_key_press_event( GdkEventKey* event ) { #ifdef _DEBUG std::cout << "EditTextView::on_key_press_event key = " << event->keyval << std::endl; #endif bool cancel_event = false; m_delete_pushed = false; if( event->keyval == GDK_KEY_Delete ) m_delete_pushed = true; const int controlid = m_control.key_press( event ); switch( controlid ){ case CONTROL::ExecWrite: case CONTROL::CancelWrite: case CONTROL::FocusWrite: case CONTROL::TabLeft: case CONTROL::TabRight: case CONTROL::TabLeftUpdated: case CONTROL::TabRightUpdated: case CONTROL::ToggleSage: case CONTROL::HomeEdit: case CONTROL::EndEdit: case CONTROL::UpEdit: case CONTROL::DownEdit: case CONTROL::RightEdit: case CONTROL::LeftEdit: case CONTROL::DeleteEdit: case CONTROL::BackspEdit: case CONTROL::UndoEdit: case CONTROL::InputAA: { if( im_context_filter_keypress( event ) ) { #ifdef _DEBUG std::cout << "gtk_im_context_filter_keypress\n"; #endif reset_im_context(); return true; } } } switch( controlid ){ // MessageViewでショートカットで書き込むと文字が挿入されてしまうので // キャンセルする case CONTROL::ExecWrite: case CONTROL::CancelWrite: case CONTROL::FocusWrite: case CONTROL::TabLeft: case CONTROL::TabRight: case CONTROL::ToggleSage: cancel_event = true; break; case CONTROL::HomeEdit: cursor_home(); return true; case CONTROL::EndEdit: cursor_end(); return true; case CONTROL::UpEdit: cursor_up(); return true; case CONTROL::DownEdit: cursor_down(); return true; case CONTROL::RightEdit: cursor_right(); return true; case CONTROL::LeftEdit: cursor_left(); return true; case CONTROL::DeleteEdit: delete_char(); return true; case CONTROL::BackspEdit: backsp_char(); return true; case CONTROL::UndoEdit: undo(); return true; case CONTROL::EnterEdit: event->keyval = GDK_KEY_Return; event->state &= ~GDK_CONTROL_MASK; event->state &= ~GDK_SHIFT_MASK; event->state &= ~GDK_MOD1_MASK; break; case CONTROL::InputAA: show_aalist_popup(); return true; } m_sig_key_press.emit( event ); if( cancel_event ) return true; return Gtk::TextView::on_key_press_event( event ); } bool EditTextView::on_key_release_event( GdkEventKey* event ) { #ifdef _DEBUG std::cout << "EditTextView::on_key_release_event key = " << event->keyval << std::endl; #endif bool cancel_event = false; switch( m_control.key_press( event ) ){ // MessageViewでショートカットで書き込むと文字が挿入されてしまうので // キャンセルする case CONTROL::ExecWrite: case CONTROL::CancelWrite: case CONTROL::FocusWrite: case CONTROL::TabLeft: case CONTROL::TabRight: case CONTROL::ToggleSage: cancel_event = true; break; case CONTROL::HomeEdit: case CONTROL::EndEdit: case CONTROL::UpEdit: case CONTROL::DownEdit: case CONTROL::RightEdit: case CONTROL::LeftEdit: case CONTROL::DeleteEdit: case CONTROL::BackspEdit: case CONTROL::UndoEdit: case CONTROL::InputAA: return true; } m_sig_key_release.emit( event ); if( cancel_event ) return true; return Gtk::TextView::on_key_release_event( event ); } // // コンテキストメニュー表示 // void EditTextView::on_populate_popup( Gtk::Menu* menu ) { #ifdef _DEBUG std::cout << "EditTextView::on_populate_popup\n"; #endif m_context_menu = menu; menu->signal_map().connect( sigc::mem_fun( *this, &EditTextView::slot_map_popupmenu ) ); menu->signal_hide().connect( sigc::mem_fun( *this, &EditTextView::slot_hide_popupmenu ) ); // セパレータ Gtk::MenuItem* menuitem = Gtk::manage( new Gtk::SeparatorMenuItem() ); menu->prepend( *menuitem ); // JDimの動作環境を記入 menuitem = Gtk::manage( new Gtk::MenuItem( "JDimの動作環境を記入" ) ); menuitem->signal_activate().connect( sigc::mem_fun( *this, &EditTextView::slot_write_jdinfo ) ); menu->prepend( *menuitem ); // 変換(スペース⇔ ) menuitem = Gtk::manage( new Gtk::MenuItem( "変換(スペース⇔ )" ) ); menuitem->signal_activate().connect( sigc::mem_fun( *this, &EditTextView::slot_convert_space ) ); menu->prepend( *menuitem ); // クリップボードから引用 menuitem = Gtk::manage( new Gtk::MenuItem( "クリップボードから引用" ) ); menuitem->signal_activate().connect( sigc::mem_fun( *this, &EditTextView::slot_quote_clipboard ) ); Glib::RefPtr< Gtk::Clipboard > clip = Gtk::Clipboard::get(); if( clip->wait_is_text_available() ) menuitem->set_sensitive( true ); else menuitem->set_sensitive( false ); menu->prepend( *menuitem ); // AA入力メニュー追加 if( CORE::get_aamanager()->get_size() ){ menuitem = Gtk::manage( new Gtk::MenuItem( CONTROL::get_label_motions( CONTROL::InputAA ) ) ); menuitem->signal_activate().connect( sigc::mem_fun( *this, &EditTextView::slot_select_aamenu ) ); menu->prepend( *menuitem ); } menu->show_all_children(); Gtk::TextView::on_populate_popup( menu ); } // // AA追加メニュー // void EditTextView::slot_select_aamenu() { if( m_context_menu ) m_context_menu->hide(); show_aalist_popup(); } // // クリップボードから引用して貼り付け // void EditTextView::slot_quote_clipboard() { if( m_context_menu ) m_context_menu->hide(); Glib::RefPtr< Gtk::Clipboard > clip = Gtk::Clipboard::get(); Glib::ustring text = clip->wait_for_text(); std::string str_res; str_res = CONFIG::get_ref_prefix(); text = MISC::replace_str( text, "\n", "\n" + str_res ); insert_str( str_res + text, false ); } // // 変換(スペース⇔ ) // void EditTextView::slot_convert_space() { if( m_context_menu ) m_context_menu->hide(); Glib::RefPtr< Gtk::TextBuffer > buffer = get_buffer(); std::string text = buffer->get_text(); std::string converted; //  が含まれていたらスペースに変換する if( text.find( " " ) != std::string::npos ) { converted = MISC::replace_str( text, " ", " " ); } else { std::list lines = MISC::get_lines( text ); for( std::string& line : lines ) { if( ! line.empty() ) { // 行末のスペースを取り除く line.erase( line.find_last_not_of( ' ' ) + 1 ); // 行頭のスペースを に変換する if( line.front() == ' ' ) { line.replace( 0, 1, " " ); } // 連続スペースを に変換する converted.append( MISC::replace_str( line, " ", "  " ) ); } converted.push_back( '\n' ); } // 最後の改行を取り除く converted.pop_back(); } buffer->set_text( converted ); } // // JDの動作環境を記入 // void EditTextView::slot_write_jdinfo() { if( m_context_menu ) m_context_menu->hide(); std::string jdinfo = ENVIRONMENT::get_jdinfo(); insert_str( jdinfo, false ); } // // ポップアップメニューがmapしたときに呼ばれるslot // void EditTextView::slot_map_popupmenu() { #ifdef _DEBUG std::cout << "EditTextView::slot_map_popupmenu\n"; #endif SESSION::set_popupmenu_shown( true ); } // // コンテキストメニューが閉じた // void EditTextView::slot_hide_popupmenu() { #ifdef _DEBUG std::cout << "EditTextView::slot_hide_popupmenu\n"; #endif m_context_menu = nullptr; SESSION::set_popupmenu_shown( false ); } // // カーソルの画面上の座標 // Gdk::Rectangle EditTextView::get_cursor_root_origin() { Gdk::Rectangle rect; int wx, wy; int x, y; get_iter_location( get_buffer()->get_insert()->get_iter(), rect ); buffer_to_window_coords( Gtk::TEXT_WINDOW_TEXT, rect.get_x(), rect.get_y(), wx, wy ); get_window( Gtk::TEXT_WINDOW_TEXT )->get_origin( x, y ); rect.set_x( x + wx ); rect.set_y( y + wy ); return rect; } // // AA ポップアップメニュー表示 // void EditTextView::show_aalist_popup() { if( CORE::get_aamanager()->get_size() ) { m_aapopupmenu = std::make_unique( *dynamic_cast( get_toplevel() ) ); m_aapopupmenu->sig_selected().connect( sigc::mem_fun( *this, &EditTextView::slot_aamenu_selected ) ); m_aapopupmenu->signal_map().connect( sigc::mem_fun( *this, &EditTextView::slot_map_aamenu ) ); m_aapopupmenu->signal_hide().connect( sigc::mem_fun( *this, &EditTextView::slot_hide_aamenu ) ); m_aapopupmenu->popup( Gtk::Menu::SlotPositionCalc( sigc::mem_fun( *this, &EditTextView::slot_popup_aamenu_pos ) ), 0, gtk_get_current_event_time() ); } } // // AA ポップアップの表示位置を決定 // void EditTextView::slot_popup_aamenu_pos( int& x, int& y, bool& push_in ) { push_in = false; const Gdk::Rectangle rect = get_cursor_root_origin(); const int line_height = rect.get_height(); const int sh = get_screen()->get_height(); const int min_height = MIN( CORE::get_aamanager()->get_size(), MIN_AAMENU_LINES ) * line_height; x = rect.get_x(); y = rect.get_y() + line_height; // 最低でも MIN_AAMENU_LINES 行よりも表示領域が高くなるようにする if( y + min_height > sh ) y = sh - min_height; } // // AAポップアップで選択された // void EditTextView::slot_aamenu_selected( const std::string& aa ) { insert_str( aa, false ); } // // AAポップアップメニューがmapしたときに呼ばれるslot // void EditTextView::slot_map_aamenu() { #ifdef _DEBUG std::cout << "EditTextView::slot_map_aamenu\n"; #endif SESSION::set_popupmenu_shown( true ); } // // AAポップアップが閉じた void EditTextView::slot_hide_aamenu() { #ifdef _DEBUG std::cout << "EditTextView::slot_hide_aamenu\n"; #endif SESSION::set_popupmenu_shown( false ); } // // 範囲選択を実行する // static gboolean EditTextView_slot_extend_selection( GtkTextView*, GtkTextExtendSelection granularity, GtkTextIter* location, GtkTextIter* start, GtkTextIter* end, gpointer ) { const char32_t loc_char = gtk_text_iter_get_char( location ); if( loc_char == 0 ) return GDK_EVENT_PROPAGATE; gtk_text_iter_assign( start, location ); gtk_text_iter_assign( end, location ); Gtk::TextIter& start_iter = Glib::wrap( start ); Gtk::TextIter& end_iter = Glib::wrap( end ); if( granularity == GTK_TEXT_EXTEND_SELECTION_WORD ) { const auto mode = MISC::get_ucs2mode( loc_char ); const bool sep = is_separate_char( loc_char ); const auto find_char = [mode, sep]( char32_t c ) { return mode != MISC::get_ucs2mode( c ) || sep != is_separate_char( c ); }; if( start_iter.backward_find_char( find_char ) ) { start_iter.forward_char(); } end_iter.forward_find_char( find_char ); return GDK_EVENT_STOP; } else if( granularity == GTK_TEXT_EXTEND_SELECTION_LINE ) { while( start_iter.backward_char() ) { if( start_iter.ends_line() ) { start_iter.forward_char(); break; } } if( !end_iter.ends_line() ) { end_iter.forward_to_line_end(); } return GDK_EVENT_STOP; } return GDK_EVENT_PROPAGATE; } ////////////////////////////////////////////// EditView::EditView() : Gtk::ScrolledWindow() { set_policy( Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC ); add( m_textview ); auto context = m_textview.get_style_context(); context->add_class( s_css_classname ); context->add_provider( m_provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION ); show_all_children(); } EditView::~EditView() noexcept = default; constexpr const char* EditView::s_css_classname; // // EditTextViewのスタイルを更新する // void EditView::update_style( const Glib::ustring& custom_css ) { #ifdef _DEBUG std::cout << "EditView::update_style custom css: " << custom_css << std::endl; #endif try { m_provider->load_from_data( custom_css ); } catch( Gtk::CssProviderError& err ) { #ifdef _DEBUG std::cout << "ERROR:EditView::update_style fail " << err.what() << std::endl; #endif } } jdim-0.7.0/src/skeleton/editview.h000066400000000000000000000114221417047150700170530ustar00rootroot00000000000000// ライセンス: GPL2 #ifndef _EDITVIEW_H #define _EDITVIEW_H #include "control/control.h" #include #include namespace SKELETON { class AAMenu; typedef sigc::signal< bool, GdkEventKey* > SIG_KEY_PRESS; typedef sigc::signal< bool, GdkEventKey* > SIG_KEY_RELEASE; typedef sigc::signal< bool, GdkEventButton* > SIG_BUTTON_PRESS; // undo 用のバッファ struct UNDO_DATA { Glib::ustring str_diff; unsigned int pos; unsigned int pos_cursor; bool append; }; // キーのプレスとリリースをフックする class EditTextView : public Gtk::TextView { SIG_KEY_PRESS m_sig_key_press; SIG_KEY_RELEASE m_sig_key_release; SIG_BUTTON_PRESS m_sig_button_press; // 入力コントローラ CONTROL::Control m_control; // undo 用 std::vector< UNDO_DATA > m_undo_tree; int m_undo_pos{}; Glib::ustring m_pre_text; bool m_cancel_change{}; bool m_delete_pushed{}; // カーソル移動用 int m_pre_offset; int m_pre_line; int m_line_offset; // コンテキストメニュー Gtk::Menu* m_context_menu{}; // AAポップアップ std::unique_ptr m_aapopupmenu; public: SIG_KEY_PRESS sig_key_press(){ return m_sig_key_press; } SIG_KEY_RELEASE sig_key_release(){ return m_sig_key_release; } SIG_BUTTON_PRESS sig_button_press() { return m_sig_button_press; } EditTextView(); ~EditTextView() noexcept; void insert_str( const std::string& str, bool use_br ); void cursor_up(); void cursor_down(); void cursor_left(); void cursor_right(); void cursor_home(); void cursor_end(); void delete_char(); void backsp_char(); void undo(); void clear_undo(); // カーソルの画面上の座標 Gdk::Rectangle get_cursor_root_origin(); protected: bool on_button_press_event( GdkEventButton* event ) override; bool on_key_press_event( GdkEventKey* event ) override; bool on_key_release_event( GdkEventKey* event ) override; void on_populate_popup( Gtk::Menu* menu ) override; void slot_buffer_changed(); private: void cursor_up_down( bool up ); void slot_select_aamenu(); void slot_map_popupmenu(); void slot_hide_popupmenu(); // クリップボードから引用 void slot_quote_clipboard(); // 変換(スペース⇔ ) void slot_convert_space(); // JDの動作環境を記入 void slot_write_jdinfo(); // AA ポップアップ void slot_popup_aamenu_pos( int& x, int& y, bool& push_in ); void show_aalist_popup(); void slot_aamenu_selected( const std::string& aa ); void slot_map_aamenu(); void slot_hide_aamenu(); }; class EditView : public Gtk::ScrolledWindow { EditTextView m_textview; static constexpr const char* s_css_classname = u8"jd-editview"; Glib::RefPtr< Gtk::CssProvider > m_provider = Gtk::CssProvider::create(); public: EditView(); ~EditView() noexcept; SIG_BUTTON_PRESS sig_button_press(){ return m_textview.sig_button_press(); } SIG_KEY_PRESS sig_key_press(){ return m_textview.sig_key_press(); } SIG_KEY_RELEASE sig_key_release(){ return m_textview.sig_key_release(); } Glib::RefPtr< Gtk::TextBuffer > get_buffer(){ return m_textview.get_buffer(); } void set_text( const Glib::ustring& text ){ m_textview.get_buffer()->set_text( text ); } Glib::ustring get_text() const { return m_textview.get_buffer()->get_text(); } void set_wrap_mode( Gtk::WrapMode wrap_mode ){ m_textview.set_wrap_mode( wrap_mode ); } const char* get_css_classname() const noexcept { return s_css_classname; } // EditTextViewのスタイルを更新する void update_style( const Glib::ustring& custom_css ); void insert_str( const std::string& str, bool use_br ){ m_textview.insert_str( str, use_br ); } void set_editable( bool editable ){ m_textview.set_editable( editable ); } void set_accepts_tab( bool accept ){ m_textview.set_accepts_tab( accept ); } void modify_font( const Pango::FontDescription& font_desc ) { m_textview.override_font( font_desc ); } void focus_view(){ m_textview.grab_focus(); } void undo(){ m_textview.undo(); } void clear_undo(){ m_textview.clear_undo(); } // カーソルの画面上の座標 Gdk::Rectangle get_cursor_root_origin(){ return m_textview.get_cursor_root_origin(); } }; } #endif jdim-0.7.0/src/skeleton/editviewdialog.h000066400000000000000000000013011417047150700202260ustar00rootroot00000000000000// ライセンス: GPL2 #ifndef _EDITVIEWDIALOG_H #define _EDITVIEWDIALOG_H #include #include #include "editview.h" namespace SKELETON { class EditViewDialog : public Gtk::Dialog { SKELETON::EditView m_edit; public: EditViewDialog( const std::string& str, const std::string& title, bool editable ){ m_edit.set_text( str ); m_edit.set_editable( editable ); add_button( g_dgettext( GTK_DOMAIN, "_OK" ), Gtk::RESPONSE_OK ); get_content_area()->pack_start( m_edit ); set_title( title ); show_all_children(); } }; } #endif jdim-0.7.0/src/skeleton/entry.cpp000066400000000000000000000042101417047150700167240ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "entry.h" #include "control/controlid.h" using namespace SKELETON; JDEntry::~JDEntry() noexcept = default; // ボタン入力のフック bool JDEntry::on_button_press_event( GdkEventButton* event ) { #ifdef _DEBUG std::cout << "JDEntry::on_button_press_event\n"; #endif m_sig_button_press.emit( event ); return Gtk::Entry::on_button_press_event( event ); } // キー入力のフック bool JDEntry::on_key_press_event( GdkEventKey* event ) { #ifdef _DEBUG std::cout << "JDEntry::on_key_press_event key = " << event->keyval << std::endl; #endif m_controlid = CONTROL::None; const guint key = event->keyval; const bool ctrl = ( event->state ) & GDK_CONTROL_MASK; const bool up = ( key == GDK_KEY_Up || ( ctrl && key == 'p' ) ); const bool down = ( key == GDK_KEY_Down || ( ctrl && key == 'n' ) ); const bool esc = ( key == GDK_KEY_Escape ); // 上下をキャンセル // gtkentry.cpp からのハック。環境やバージョンによっては問題が出るかもしれないので注意 if( up || down || esc ){ if( im_context_filter_keypress( event ) ) { #ifdef _DEBUG std::cout << "gtk_im_context_filter_keypress\n"; #endif reset_im_context(); return TRUE; } else if( up || down ){ if( up ) m_sig_operate.emit( CONTROL::Up ); else m_sig_operate.emit( CONTROL::Down ); return TRUE; } } m_sig_key_press.emit( event->keyval ); m_controlid = m_control.key_press( event ); return Gtk::Entry::on_key_press_event( event ); } bool JDEntry::on_key_release_event( GdkEventKey* event ) { const bool ret = Gtk::Entry::on_key_release_event( event ); #ifdef _DEBUG std::cout << "JDEntry::on_key_release_event id = " << m_controlid << std::endl; #endif switch( m_controlid ){ case CONTROL::DrawOutAnd: case CONTROL::SearchCache: case CONTROL::Cancel: m_sig_operate.emit( m_controlid ); break; } m_controlid = CONTROL::None; return ret; } jdim-0.7.0/src/skeleton/entry.h000066400000000000000000000027131417047150700163770ustar00rootroot00000000000000// ライセンス: GPL2 // // キーボードフックしたentryクラス // #ifndef _ENTRY_H #define _ENTRY_H #include "control/control.h" #include namespace SKELETON { class JDEntry : public Gtk::Entry { typedef sigc::signal< void, GdkEventButton* > SIG_BUTTON_PRESS; typedef sigc::signal< void, int > SIG_KEY_PRESS; typedef sigc::signal< void, int > SIG_OPERATE; SIG_BUTTON_PRESS m_sig_button_press; SIG_KEY_PRESS m_sig_key_press; SIG_OPERATE m_sig_operate; // 入力コントローラ CONTROL::Control m_control; int m_controlid{}; public: SIG_BUTTON_PRESS signal_button_press(){ return m_sig_button_press; } SIG_KEY_PRESS signal_key_press(){ return m_sig_key_press; } SIG_OPERATE signal_operate(){ return m_sig_operate; } using Gtk::Entry::Entry; ~JDEntry() noexcept; // CONTROL::Control のモード設定( controlid.h 参照 ) // キー入力をフックして JDEntry::on_key_release_event() で SIG_OPERATE をemitする void add_mode( const int mode ){ m_control.add_mode( mode ); } protected: // マウスクリックのフック bool on_button_press_event( GdkEventButton* event ) override; // キー入力のフック bool on_key_press_event( GdkEventKey* event ) override; bool on_key_release_event( GdkEventKey* event ) override; }; } #endif jdim-0.7.0/src/skeleton/filediag.h000066400000000000000000000037051417047150700170040ustar00rootroot00000000000000// ライセンス: GPL2 // ファイル選択ダイアログの基底クラス #ifndef _FILEDIAG_H #define _FILEDIAG_H #include #include #include "command.h" #include "session.h" namespace SKELETON { class FileDiag : public Gtk::FileChooserDialog { Gtk::FileChooserAction m_action; public: // ボタン追加 + saveボタンをデフォルトボタンにセット void add_buttons(){ add_button( g_dgettext( GTK_DOMAIN, "_Cancel" ), Gtk::RESPONSE_CANCEL ); if( m_action == Gtk::FILE_CHOOSER_ACTION_OPEN ) { add_button( g_dpgettext( GTK_DOMAIN, "Stock label\x04_Open", 12 ), Gtk::RESPONSE_ACCEPT ); } else add_button( g_dpgettext( GTK_DOMAIN, "Stock label\x04_Save", 12 ), Gtk::RESPONSE_ACCEPT ); set_default_response( Gtk::RESPONSE_ACCEPT ); } FileDiag( Gtk::Window& parent, const Glib::ustring& title, Gtk::FileChooserAction action = Gtk::FILE_CHOOSER_ACTION_OPEN ) : Gtk::FileChooserDialog( parent, title, action ), m_action( action ) { add_buttons(); } // parent がポインタの時は nullptr かどうかで場合分け FileDiag( Gtk::Window* parent, const Glib::ustring& title, Gtk::FileChooserAction action = Gtk::FILE_CHOOSER_ACTION_OPEN ) : Gtk::FileChooserDialog( title, action ), m_action( action ) { add_buttons(); if( parent ) set_transient_for( *parent ); else set_transient_for( *CORE::get_mainwindow() ); } ~FileDiag() noexcept = default; virtual int run(){ SESSION::set_dialog_shown( true ); CORE::core_set_command( "dialog_shown" ); int ret = Gtk::FileChooserDialog::run(); SESSION::set_dialog_shown( false ); CORE::core_set_command( "dialog_hidden" ); return ret; } }; } #endif jdim-0.7.0/src/skeleton/hpaned.cpp000066400000000000000000000022731417047150700170310ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "hpaned.h" using namespace SKELETON; JDHPaned::JDHPaned( const int fixmode ) : Gtk::Paned( Gtk::ORIENTATION_HORIZONTAL ) , m_pctrl( *this, fixmode ) {} JDHPaned::~JDHPaned() noexcept = default; void JDHPaned::on_realize() { Gtk::Paned::on_realize(); m_pctrl.update_position(); } bool JDHPaned::on_button_press_event( GdkEventButton* event ) { m_pctrl.button_press_event( event ); return Gtk::Paned::on_button_press_event( event ); } bool JDHPaned::on_button_release_event( GdkEventButton* event ) { m_pctrl.button_release_event( event ); return Gtk::Paned::on_button_release_event( event ); } bool JDHPaned::on_motion_notify_event( GdkEventMotion* event ) { m_pctrl.motion_notify_event( event ); return Gtk::Paned::on_motion_notify_event( event ); } bool JDHPaned::on_enter_notify_event( GdkEventCrossing* event ) { m_pctrl.enter_notify_event( event ); return Gtk::Paned::on_enter_notify_event( event ); } bool JDHPaned::on_leave_notify_event( GdkEventCrossing* event ) { m_pctrl.leave_notify_event( event ); return Gtk::Paned::on_leave_notify_event( event ); } jdim-0.7.0/src/skeleton/hpaned.h000066400000000000000000000014461417047150700164770ustar00rootroot00000000000000// ライセンス: GPL2 // // HPaneeクラス // #ifndef _HPANED_H #define _HPANED_H #include #include "panecontrol.h" namespace SKELETON { class JDHPaned : public Gtk::Paned { HPaneControl m_pctrl; public: explicit JDHPaned( const int fixmode ); ~JDHPaned() noexcept; HPaneControl& get_ctrl(){ return m_pctrl; } protected: void on_realize() override; bool on_button_press_event( GdkEventButton* event ) override; bool on_button_release_event( GdkEventButton* event ) override; bool on_motion_notify_event( GdkEventMotion* event ) override; bool on_enter_notify_event( GdkEventCrossing* event ) override; bool on_leave_notify_event( GdkEventCrossing* event ) override; }; } #endif jdim-0.7.0/src/skeleton/iconpopup.h000066400000000000000000000021411417047150700172450ustar00rootroot00000000000000// ライセンス: GPL2 #ifndef _ICONPOPUP_H #define _ICONPOPUP_H #include #include "icons/iconmanager.h" namespace SKELETON { class IconPopup : public Gtk::Window { Glib::RefPtr< Gdk::Pixbuf > m_pixbuf; Gtk::Image m_img; public: explicit IconPopup( const int icon_id ) : Gtk::Window( Gtk::WINDOW_POPUP ) , m_pixbuf{ ICON::get_icon( icon_id ) } { m_img.set( m_pixbuf ); // NOTE: アルファチャンネルが利用できない環境では背景を透過できない set_decorated( false ); set_app_paintable( true ); auto screen = get_screen(); auto visual = screen->get_rgba_visual(); if( visual && screen->is_composited() ) { gtk_widget_set_visual( GTK_WIDGET( gobj() ), visual->gobj() ); } add( m_img ); show_all_children(); } int get_img_width() const { return m_pixbuf->get_width(); } int get_img_height() const { return m_pixbuf->get_height(); } }; } #endif jdim-0.7.0/src/skeleton/imgtoolbutton.h000066400000000000000000000022671417047150700201500ustar00rootroot00000000000000// ライセンス: GPL2 // 画像つきツールボタン #ifndef _IMGTOOLBUTTON_H #define _IMGTOOLBUTTON_H #include #include #include #include "control/controlutil.h" #include "icons/iconmanager.h" namespace SKELETON { template class ToolButtonExtension : public Base { static_assert( std::is_base_of::value, "Base must inherit Gtk::ToolButton" ); public: explicit ToolButtonExtension( const int iconid ) : Base( *Gtk::manage( new Gtk::Image( ICON::get_icon( iconid ) ) ) ) {} explicit ToolButtonExtension( const int iconid, const int controlid ) : Base( *Gtk::manage( new Gtk::Image( ICON::get_icon( iconid ) ) ), CONTROL::get_label( controlid ) ) {} explicit ToolButtonExtension( const int iconid, const Glib::ustring& label ) : Base( *Gtk::manage( new Gtk::Image( ICON::get_icon( iconid ) ) ), label ) {} ~ToolButtonExtension() noexcept = default; }; using ImgToolButton = ToolButtonExtension; using ImgToggleToolButton = ToolButtonExtension; } #endif jdim-0.7.0/src/skeleton/jdtoolbar.cpp000066400000000000000000000006601417047150700175500ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "jdtoolbar.h" using namespace SKELETON; JDToolbar::JDToolbar() : Gtk::Toolbar() { // 子ウィジェットの配色がGTKテーマと違うことがある。 // ツールバーのcssクラスを削除し配色を修正する。 get_style_context()->remove_class( GTK_STYLE_CLASS_TOOLBAR ); } JDToolbar::~JDToolbar() noexcept = default; jdim-0.7.0/src/skeleton/jdtoolbar.h000066400000000000000000000007751417047150700172240ustar00rootroot00000000000000// ライセンス: GPL2 // // カスタマイズした Gtk::Toolbar ( 背景を描画しない ) // // (注) DragableNoteBookにappendするのは SKELETON::ToolBar // // TODO: クライアント側で Gtk::Toolbar に置き換える // TODO: CONFIG::(set|get)_draw_toolbarback() を削除する #ifndef JDTOOLBAR_H #define JDTOOLBAR_H #include namespace SKELETON { class JDToolbar : public Gtk::Toolbar { public: JDToolbar(); ~JDToolbar() noexcept; }; } #endif jdim-0.7.0/src/skeleton/label_entry.cpp000066400000000000000000000037311417047150700200720ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "label_entry.h" #include "view.h" using namespace SKELETON; LabelEntry::LabelEntry( const bool editable, const std::string& label, const std::string& text ) : m_editable( editable ) { set_label( label ); m_label.set_mnemonic_widget ( m_entry ); m_entry.signal_activate().connect( sigc::mem_fun( *this, &LabelEntry::slot_entry_acivate ) ); pack_start( m_label, Gtk::PACK_SHRINK ); m_info.set_size_request( 0, 0 ); m_info.set_xalign( 0 ); m_info.set_selectable( true ); m_info.set_ellipsize( Pango::ELLIPSIZE_END ); setup(); set_text( text ); } LabelEntry::~LabelEntry() noexcept = default; void LabelEntry::setup() { if( m_editable ) pack_start( m_entry ); else pack_start( m_info ); show_all_children(); } void LabelEntry::set_editable( const bool editable ) { if( m_editable == editable ) return; #ifdef _DEBUG std::cout << "LabelEntry::set_editable editable = " << editable << std::endl; #endif if( m_editable ) remove( m_entry ); else remove( m_info ); m_editable = editable; setup(); } void LabelEntry::set_visibility( bool visibility ) { if( m_editable ) m_entry.set_visibility( visibility ); } void LabelEntry::set_label( const std::string& label ) { m_label.set_text_with_mnemonic( label ); } void LabelEntry::set_text( const std::string& text ) { m_entry.set_text( text ); m_info.set_text( text ); } Glib::ustring LabelEntry::get_text() const { if( m_editable ) return m_entry.get_text(); return m_info.get_text(); } void LabelEntry::grab_focus() { if( m_editable ) m_entry.grab_focus(); } bool LabelEntry::has_grab() const { if( m_editable ) return m_entry.has_grab(); return false; } // entry からsignal_activateを受け取った void LabelEntry::slot_entry_acivate() { #ifdef _DEBUG std::cout << "LabelEntry::slot_entry_acivate\n"; #endif m_sig_activate.emit(); } jdim-0.7.0/src/skeleton/label_entry.h000066400000000000000000000021431417047150700175330ustar00rootroot00000000000000// ライセンス: GPL2 // // ラベル + ラベル / エントリー // // プロパティの表示用 // #ifndef _LABEL_ENTRY_H #define _LABEL_ENTRY_H #include namespace SKELETON { class LabelEntry : public Gtk::HBox { typedef sigc::signal< void > SIG_ACTIVATE; SIG_ACTIVATE m_sig_activate; bool m_editable; Gtk::Label m_label; Gtk::Label m_info; Gtk::Entry m_entry; public: LabelEntry( const bool editable, const std::string& label, const std::string& text = std::string() ); ~LabelEntry() noexcept; SIG_ACTIVATE signal_activate(){ return m_sig_activate; } void set_editable( const bool editable ); void set_visibility( const bool visibility ); void set_label( const std::string& label ); void set_text( const std::string& text ); Glib::ustring get_text() const; void grab_focus(); bool has_grab() const; private: void setup(); // entry からsignal_activateを受け取った void slot_entry_acivate(); }; } #endif jdim-0.7.0/src/skeleton/loadable.cpp000066400000000000000000000124341417047150700173350ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "loadable.h" #include "jdlib/loader.h" #include "jdlib/misctime.h" #include "httpcode.h" using namespace SKELETON; Loadable::Loadable() { clear_load_data(); } Loadable::~Loadable() { terminate_load(); // 一応デストラクタ内部でも実行しておく } // ロード強制停止 void Loadable::terminate_load() { #ifdef _DEBUG std::cout << "Loadable::terminate_load\n"; #endif // loadableを delete する前に terminate_load() を呼び出さないと // スレッド実行中にメモリなどが初期化されてしまうため落ちる時がある // デストラクタ内から terminate_load() しても落ちる時があるので // デストラクタの外から呼び出すこと set_dispatchable( false ); m_loader.reset(); } // 完全クリア void Loadable::clear_load_data() { m_code = HTTP_INIT; m_str_code = std::string(); m_date_modified = std::string(); m_cookies.clear(); m_location = std::string(); m_total_length = 0; m_current_length = 0; } bool Loadable::is_loading() const { // m_loader != nullptr ならローダ起動中ってこと return static_cast( m_loader ); } // // 更新時刻 // time_t Loadable::get_time_modified() { time_t time_out; time_out = MISC::datetotime( m_date_modified ); if( time_out == 0 ) time_out = time( nullptr ) - 600; #ifdef _DEBUG std::cout << "Loadable::get_time_modified " << m_date_modified << std::endl << " -> " << time_out << std::endl; #endif return time_out; } // // ロード開始 // bool Loadable::start_load( const JDLIB::LOADERDATA& data ) { if( is_loading() ) return false; #ifdef _DEBUG std::cout << "Loadable::start_load url = " << data.url << std::endl; #endif assert( m_loader == nullptr ); m_loader = std::make_unique( m_low_priority ); // 情報初期化 // m_date_modified, m_cookie は初期化しない m_code = HTTP_INIT; m_str_code = std::string(); m_location = std::string(); m_total_length = 0; m_current_length = 0; if( !m_loader->run( this, data ) ){ m_code = get_loader_code(); m_str_code = get_loader_str_code(); m_loader.reset(); return false; } return true; } // // ロード中止 // void Loadable::stop_load() { if( m_loader ) m_loader->stop(); } // ローダーからコールバックされてコードなどを取得してから // receive_data() を呼び出す void Loadable::receive( const char* data, size_t size ) { m_code = get_loader_code(); if( ! m_total_length && m_code != HTTP_INIT ) m_total_length = get_loader_length(); m_current_length += size; receive_data( data, size ); } // 別スレッドで動いているローダからfinish()を呼ばれたらディスパッチして // メインスレッドに制御を戻してから callback_dispatch()を呼び出す。 // そうしないと色々不具合が生じる void Loadable::finish() { #ifdef _DEBUG std::cout << "Loadable::finish\n"; #endif dispatch(); } // // ローダを削除してreceive_finish()をコール // void Loadable::callback_dispatch() { #ifdef _DEBUG std::cout << "Loadable::callback_dispatch\n"; #endif // ローダを削除する前に情報保存 m_code = get_loader_code(); if( ! get_loader_str_code().empty() ) m_str_code = get_loader_str_code(); if( ! get_loader_contenttype().empty() ) m_contenttype = get_loader_contenttype(); if( ! get_loader_modified().empty() ) m_date_modified = get_loader_modified(); if( ! get_loader_cookies().empty() ) m_cookies = get_loader_cookies(); if( ! get_loader_location().empty() ) m_location = get_loader_location(); #ifdef _DEBUG std::cout << "delete loader\n"; #endif m_loader.reset(); #ifdef _DEBUG std::cout << "code = " << m_code << std::endl; std::cout << "str_code = " << m_str_code << std::endl; std::cout << "contenttype = " << m_contenttype << std::endl; std::cout << "modified = " << m_date_modified << std::endl; std::cout << "location = " << m_location << std::endl; std::cout << "total_length = " << m_total_length << std::endl; std::cout << "current length = " << m_current_length << std::endl; #endif receive_finish(); } // ローダから各種情報の取得 int Loadable::get_loader_code() const { if( ! m_loader ) return HTTP_INIT; return m_loader->data().code; } std::string Loadable::get_loader_str_code() const { if( ! m_loader ) return std::string(); return m_loader->data().str_code; } std::string Loadable::get_loader_contenttype() const { if( ! m_loader ) return std::string(); return m_loader->data().contenttype; } std::string Loadable::get_loader_modified() const { if( ! m_loader ) return std::string(); return m_loader->data().modified; } std::list< std::string > Loadable::get_loader_cookies() const { if( ! m_loader ) return std::list< std::string >(); return m_loader->data().list_cookies; } std::string Loadable::get_loader_location() const { if( ! m_loader ) return std::string(); return m_loader->data().location; } size_t Loadable::get_loader_length() const { if( ! m_loader ) return 0; return m_loader->data().length; } jdim-0.7.0/src/skeleton/loadable.h000066400000000000000000000121741417047150700170030ustar00rootroot00000000000000// ライセンス: GPL2 // // ロード可能クラスの基底クラス // JDLIB::Loaderを使ってデータを受信する // // ・start_load() でロード開始。JDLIB::LOADERDATA data を引数に渡す // ・stop_load() で停止 // // ロード中はreceive_data()がコールバックされてデータが送られる // ロードが終わると receive_finish() がコールバックされる // // ・ delete する前に terminate_load() を明示的に呼び出すこと(terminate_load()のコメントを参照) // // 詳しい流れは次の通り /* (1) start_load() の JDLIB::create_loader() でローダが作成される (2) m_loader->run() でロード開始 --------ここから別スレッド内で実行 (3) ローダがデータを受け取るとreceive()がコールバックされてhttpコードやサイズを取得する (4) receive()からreceive_data()が呼び出される (5) ロードが終了したらfinish()が呼ばれて Dispatchable::dispatch()により ディスパッチャを使ってメインスレッドに制御を戻す --------ここからメインスレッドに戻る (6) ディスパッチャ経由で Dispatchable::callback_dispatch() が呼び出される (7) クッキー、更新時刻などを取得する (8) m_loader.reset()でローダの停止を待って削除する (9) receive_finish()を呼び出す 注意点 ・receive_data()は別スレッド内で実行される ・receive_finish() はメインスレッド内で実行される ・receive_finish()が呼ばれた時点で既にローダは削除されているので明示的に削除する必要はない */ #ifndef _LOADABLE_H #define _LOADABLE_H #include "dispatchable.h" #include #include #include #include namespace JDLIB { class Loader; class LOADERDATA; } namespace SKELETON { class Loadable : public Dispatchable { std::unique_ptr m_loader; bool m_low_priority{}; // ローダからコピーしたデータ int m_code; std::string m_str_code; std::string m_contenttype; std::string m_date_modified; std::list< std::string > m_cookies; std::string m_location; size_t m_total_length; size_t m_current_length; public: Loadable(); ~Loadable(); // HTTPコードなどの完全クリア void clear_load_data(); // ロード中かどうか bool is_loading() const; int get_code() const { return m_code; } void set_code( int code ) { m_code = code; } const std::string& get_str_code() const { return m_str_code; } void set_str_code( const std::string& str_code ){ m_str_code = str_code; } const std::string& get_contenttype() const { return m_contenttype; } const std::list< std::string >& cookies() { return m_cookies; } const std::string& location() const { return m_location; } size_t total_length() const { return m_total_length; } void set_total_length( int length ){ m_total_length = length; } size_t current_length() const { return m_current_length; } void set_current_length( int length ){ m_current_length = length; } // 更新時刻関係 time_t get_time_modified(); const std::string& get_date_modified() const { return m_date_modified; } void set_date_modified( const std::string& date ){ m_date_modified = date; } // ローダーからコールバックされてコードなどを取得してから // receive_data() を呼び出す void receive( const char* data, size_t size ); // ディスパッチャ経由で Dispatchable::callback_dispatch()、 receive_finish()を呼ぶ // ロード直後に直接 receive_finish() は呼ばないこと void finish(); // ロード開始 // パラメータは Loader::run()の説明をみること bool start_load( const JDLIB::LOADERDATA& data ); // ロード停止 virtual void stop_load(); // ロード強制停止 // loadableを delete する前に terminate_load() を呼び出さないと // スレッド実行中にメモリなどが初期化されてしまうため落ちる時がある // デストラクタ内から terminate_load() しても落ちる時があるので // デストラクタの外から呼び出すこと void terminate_load(); // ローダがスレッド起動待ち状態になった時に、起動順のプライオリティを下げる void set_priority_low(){ m_low_priority = true; } private: virtual void receive_data( const char* , size_t ){}; virtual void receive_finish(){}; void callback_dispatch() override; int get_loader_code() const; std::string get_loader_str_code() const; std::string get_loader_contenttype() const; std::string get_loader_modified() const; std::list< std::string > get_loader_cookies() const; std::string get_loader_location() const; size_t get_loader_length() const; }; } #endif jdim-0.7.0/src/skeleton/lockable.h000066400000000000000000000013331417047150700170070ustar00rootroot00000000000000// ライセンス: GPL2 // // ロック可能クラス ( RefPtr と組み合わせて使う ) // // Lockable を継承したクラスを JDLIB::RefPtr_Lock 経由で呼ぶことによってロックを掛ける // ロックが外れたら unlook_impl が呼ばれる // #ifndef _LOCKABLE_H #define _LOCKABLE_H namespace SKELETON { class Lockable { int m_lock{}; public: Lockable() = default; virtual ~Lockable() noexcept = default; int get_lock() const { return m_lock; } void lock(){ ++m_lock; } void unlock(){ --m_lock; if( m_lock == 0 ) unlock_impl(); } private: virtual void unlock_impl(){} }; } #endif jdim-0.7.0/src/skeleton/login.cpp000066400000000000000000000035301417047150700166770ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "login.h" #include "cache.h" #include "jdlib/confloader.h" #include #include #include // chmod #include using namespace SKELETON; Login::Login( const std::string& url ) : m_url( url ) { #ifdef _DEBUG std::cout << "Login::Login " << get_url() << std::endl; #endif read_info(); } Login::~Login() { #ifdef _DEBUG std::cout << "Login::~Login " << get_url() << std::endl; #endif if( m_save_info ) save_info(); } void Login::set_username( const std::string& username ) { if( username != m_username ){ m_username = username; m_save_info = true; } } void Login::set_passwd( const std::string& passwd ) { if( passwd != m_passwd ){ m_passwd = passwd; m_save_info = true; } } // // パスワードやユーザー名読み込み // void Login::read_info() { std::string path = CACHE::path_passwd( get_url().substr( strlen( "jdlogin://" ) ) ); #ifdef _DEBUG std::cout << "Login::read_info path = " << path << std::endl; #endif JDLIB::ConfLoader cf( path, std::string() ); m_username = cf.get_option_str( "username", "" ); m_passwd = cf.get_option_str( "passwd", "" ); #ifdef _DEBUG std::cout << "user = " << m_username << " ,passwd = " << m_passwd << std::endl; #endif } // // パスワードやユーザー名書き込み // void Login::save_info() { std::string path = CACHE::path_passwd( get_url().substr( strlen( "jdlogin://" ) ) ); #ifdef _DEBUG std::cout << "Login::save_info path = " << path << std::endl; #endif std::ostringstream oss; oss << "username = " << m_username<< std::endl << "passwd = " << m_passwd << std::endl; CACHE::save_rawdata( path, oss.str() ); chmod( path.c_str(), S_IWUSR | S_IRUSR ); } jdim-0.7.0/src/skeleton/login.h000066400000000000000000000030171417047150700163440ustar00rootroot00000000000000// ライセンス: GPL2 // // 2chなどへのログイン管理クラス // // セッション管理やログイン、パスワードの保存などを行う // #ifndef _LOGIN_H #define _LOGIN_H #include "loadable.h" namespace SKELETON { class Login : public SKELETON::Loadable { std::string m_url; bool m_login_now{}; bool m_save_info{}; std::string m_username; std::string m_passwd; std::string m_sessionid; // セッションID std::string m_sessiondata; // セッションデータ public: explicit Login( const std::string& url ); ~Login(); bool login_now() const { return m_login_now; } void set_login_now( bool login_now ){ m_login_now = login_now; } const std::string& get_url() const { return m_url; } const std::string& get_username() const { return m_username; } void set_username( const std::string& username ); const std::string& get_passwd() const { return m_passwd; } void set_passwd( const std::string& passwd ); const std::string& get_sessionid() const { return m_sessionid; } void set_sessionid( const std::string& id ){ m_sessionid = id; } const std::string& get_sessiondata() const { return m_sessiondata; } void set_sessiondata( const std::string& data ){ m_sessiondata = data; } virtual void start_login()=0; virtual void logout()=0; private: void read_info(); void save_info(); }; } #endif jdim-0.7.0/src/skeleton/menubutton.cpp000066400000000000000000000124141417047150700177700ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "gtkmmversion.h" #include "menubutton.h" #include "jdlib/miscutil.h" #include "icons/iconmanager.h" #include "command.h" using namespace SKELETON; enum { MENU_MAX_LNG = 50 // メニューに表示する文字数(半角) }; MenuButton::MenuButton( const bool show_arrow, Gtk::Widget* label, Gtk::PackOptions options ) : m_label{ label } , m_enable_sig_clicked{ true } { Gtk::Box* hbox = Gtk::manage( new Gtk::Box( Gtk::ORIENTATION_HORIZONTAL ) ); hbox->set_spacing( 4 ); if( m_label ) hbox->pack_start( *m_label, options ); if( show_arrow ){ m_arrow = Gtk::manage( new Gtk::Image() ); m_arrow->set_from_icon_name( "pan-down-symbolic", Gtk::ICON_SIZE_SMALL_TOOLBAR ); hbox->pack_start( *m_arrow, Gtk::PACK_SHRINK ); } else m_enable_sig_clicked = false; add_events( Gdk::ENTER_NOTIFY_MASK ); add_events( Gdk::POINTER_MOTION_MASK ); add_events( Gdk::LEAVE_NOTIFY_MASK ); add_events( Gdk::SCROLL_MASK ); signal_enter_notify_event().connect( sigc::mem_fun( *this, &MenuButton::slot_enter ) ); signal_leave_notify_event().connect( sigc::mem_fun( *this, &MenuButton::slot_leave ) ); signal_motion_notify_event().connect( sigc::mem_fun( *this, &MenuButton::slot_motion ) ); add( *hbox ); show_all_children(); // メニュー項目作成 Glib::RefPtr< Gtk::ActionGroup > actiongroup = Gtk::ActionGroup::create(); Glib::RefPtr< Gtk::AccelGroup > agroup = CORE::get_mainwindow()->get_accel_group(); for( size_t i = 0 ; i < MAX_MENU_SIZE; ++i ){ Glib::RefPtr< Gtk::Action > action = Gtk::Action::create( "menu" + std::to_string( i ), "dummy" ); action->set_accel_group( agroup ); Gtk::MenuItem* item = Gtk::manage( action->create_menu_item() ); actiongroup->add( action, [this, i] { slot_menu_selected( i ); } ); m_menuitems.push_back( item ); } set_focus_on_click( false ); } MenuButton::MenuButton( const bool show_arrow, Gtk::Widget& label ) : MenuButton( show_arrow, &label ) { } MenuButton::MenuButton( const bool show_arrow, const int id ) : MenuButton( show_arrow, Gtk::manage( new Gtk::Image( ICON::get_icon( id ) ) ), Gtk::PACK_SHRINK ) { } void MenuButton::set_tooltip_arrow( const std::string& tooltip ) { if( m_arrow ) { m_arrow->set_tooltip_text( tooltip ); } } // // メニュー項目追加 // void MenuButton::append_menu( std::vector< std::string >& items ) { // 古いメニューからメニュー項目を取り除いてdelete if( m_popupmenu ){ const int menusize = m_popupmenu->get_children().size(); for( int i = 0; i < menusize; ++i ) m_popupmenu->remove( *m_menuitems[ i ] ); m_popupmenu.reset(); } if( ! items.size() ) return; // 新しくメニューを作成して項目追加 m_popupmenu = std::make_unique(); const size_t size = MIN( items.size(), MAX_MENU_SIZE ); for( size_t i = 0 ; i < size; ++i ){ Gtk::MenuItem* item = nullptr; if( items[ i ] == "separator" ){ item = Gtk::manage( new Gtk::SeparatorMenuItem() ); } else{ item = m_menuitems[ i ]; if( auto label = dynamic_cast( item->get_child() ) ) { label->set_text( MISC::cut_str( items[i], MENU_MAX_LNG ) ); } } if( item ) m_popupmenu->append( *item ); } m_popupmenu->show_all(); } // // ポップアップメニュー表示 // // virtual void MenuButton::show_popupmenu() { if( ! m_popupmenu ) return; // Specify the current event by nullptr. m_popupmenu->popup_at_widget( this, Gdk::GRAVITY_SOUTH_WEST, Gdk::GRAVITY_NORTH_WEST, nullptr ); } // // メニューが選ばれた // void MenuButton::slot_menu_selected( int i ) { #ifdef _DEBUG std::cout << "MenuButton::slot_menu_selected i = " << i << std::endl; #endif m_sig_selected.emit( i ); } // // ボタンがクリックされたらクリックした位置で処理を変える // void MenuButton::on_clicked() { #ifdef _DEBUG std::cout << "MenuButton::on_clicked\n"; #endif if( ! m_enable_sig_clicked || m_on_arrow ) show_popupmenu(); else m_sig_clicked.emit(); } bool MenuButton::slot_enter( GdkEventCrossing* event ) { #ifdef _DEBUG std::cout << "MenuButton::slot_enter\n"; #endif check_on_arrow( (int)event->x ); return true; } bool MenuButton::slot_leave( GdkEventCrossing* event ) { #ifdef _DEBUG std::cout << "MenuButton::slot_leave\n"; #endif m_on_arrow = false; return true; } bool MenuButton::slot_motion( GdkEventMotion* event ) { check_on_arrow( (int)event->x ); return true; } // // ポインタが矢印の下にあるかチェック // void MenuButton::check_on_arrow( int ex ) { if( ! m_arrow ){ m_on_arrow = false; return; } if( ! m_label ){ m_on_arrow = true; return; } Gdk::Rectangle rect = m_arrow->get_allocation(); int x = rect.get_x(); rect = get_allocation(); x -= rect.get_x(); if( ex >= x ) m_on_arrow = true; else m_on_arrow = false; #ifdef _DEBUG std::cout << "MenuButton::check_on_arrow x = " << ex << " / " << x << " on = " << m_on_arrow << std::endl; #endif } jdim-0.7.0/src/skeleton/menubutton.h000066400000000000000000000040411417047150700174320ustar00rootroot00000000000000// ライセンス: GPL2 // メニュー付きボタン #ifndef _MENUBUTTON_H #define _MENUBUTTON_H #include #include #include #include constexpr size_t MAX_MENU_SIZE = 20; namespace SKELETON { class MenuButton : public Gtk::Button { typedef sigc::signal< void > SIG_BUTTON_CLICKED; typedef sigc::signal< void, const int > SIG_SELECTED; SIG_BUTTON_CLICKED m_sig_clicked; SIG_SELECTED m_sig_selected; std::unique_ptr m_popupmenu; std::vector< Gtk::MenuItem* > m_menuitems; Gtk::Widget* m_label{}; Gtk::Image* m_arrow{}; bool m_on_arrow{}; bool m_enable_sig_clicked; MenuButton( const bool show_arrow, Gtk::Widget* label, Gtk::PackOptions options = Gtk::PACK_EXPAND_WIDGET ); public: MenuButton( const bool show_arrow, Gtk::Widget& label ); MenuButton( const bool show_arrow , const int id ); ~MenuButton() noexcept = default; Gtk::Widget* get_label_widget(){ return m_label; } void set_tooltip_arrow( const std::string& tooltip ); SIG_BUTTON_CLICKED signal_button_clicked(){ return m_sig_clicked; } // メニューが選択されたらemitされる SIG_SELECTED signal_selected(){ return m_sig_selected; } // メニュー項目追加 void append_menu( std::vector< std::string >& items ); // 矢印ボタン以外をクリックしたときにSIG_BUTTON_CLICKEDをemitする // false の時はボタンのどこを押してもメニューを表示する void set_enable_sig_clicked( const bool enable ){ m_enable_sig_clicked = enable; } void on_clicked(); protected: // ポップアップメニュー表示 virtual void show_popupmenu(); private: void slot_menu_selected( int i ); bool slot_enter( GdkEventCrossing* event ); bool slot_leave( GdkEventCrossing* event ); bool slot_motion( GdkEventMotion* event ); void check_on_arrow( int ex ); }; } #endif jdim-0.7.0/src/skeleton/meson.build000066400000000000000000000016511417047150700172270ustar00rootroot00000000000000sources = [ 'aamenu.cpp', 'aboutdiag.cpp', 'admin.cpp', 'backforwardbutton.cpp', 'compentry.cpp', 'detaildiag.cpp', 'dispatchable.cpp', 'dragnote.cpp', 'dragtreeview.cpp', 'editcolumns.cpp', 'edittreeview.cpp', 'editview.cpp', 'entry.cpp', 'hpaned.cpp', 'jdtoolbar.cpp', 'label_entry.cpp', 'loadable.cpp', 'login.cpp', 'menubutton.cpp', 'msgdiag.cpp', 'notebook.cpp', 'panecontrol.cpp', 'popupwin.cpp', 'popupwinbase.cpp', 'prefdiag.cpp', 'selectitempref.cpp', 'tablabel.cpp', 'tabnote.cpp', 'tabswitchbutton.cpp', 'tabswitchmenu.cpp', 'textloader.cpp', 'toolbar.cpp', 'toolbarnote.cpp', 'toolmenubutton.cpp', 'treeviewbase.cpp', 'undobuffer.cpp', 'vbox.cpp', 'view.cpp', 'viewnote.cpp', 'vpaned.cpp', 'window.cpp', ] skeleton_lib = static_library( 'skeleton', sources, dependencies : gtkmm_dep, include_directories : include_directories('..'), ) jdim-0.7.0/src/skeleton/msgdiag.cpp000066400000000000000000000123431417047150700172040ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "msgdiag.h" #include "command.h" #include "session.h" #include "dispatchmanager.h" #include "global.h" #include using namespace SKELETON; MsgDiag::MsgDiag( Gtk::Window& parent, const Glib::ustring& message, bool use_markup, Gtk::MessageType type, Gtk::ButtonsType buttons, bool modal ) : Gtk::MessageDialog( parent, message, use_markup, type, buttons, modal ) {} // parent がポインタの時は nullptr かどうかで場合分け MsgDiag::MsgDiag( Gtk::Window* parent, const Glib::ustring& message, bool use_markup, Gtk::MessageType type, Gtk::ButtonsType buttons, bool modal ) : Gtk::MessageDialog( message, use_markup, type, buttons, modal ) { if( parent ) set_transient_for( *parent ); else set_transient_for( *CORE::get_mainwindow() ); // tab でラベルにフォーカスが移らないようにする const std::vector< Gtk::Widget* > area = get_message_area()->get_children(); Gtk::Label* const primary_label = dynamic_cast< Gtk::Label* >( area.front() ); if( primary_label ) { primary_label->set_can_focus( false ); } } MsgDiag::~MsgDiag() noexcept = default; void MsgDiag::add_default_button( const Glib::ustring& label, const int id ) { Gtk::Button* button = Gtk::manage( new Gtk::Button( label, true ) ); add_default_button( button, id ); } void MsgDiag::add_default_button( Gtk::Widget* button, const int id ) { if( ! button ) return; add_action_widget( *button, id ); button->show(); button->set_can_default( true ); button->grab_default(); button->grab_focus(); } int MsgDiag::run() { #ifdef _DEBUG std::cout << "MsgDiag::run start\n"; #endif SESSION::set_dialog_shown( true ); CORE::core_set_command( "dialog_shown" ); // タイマーセット sigc::slot< bool > slot_timeout = sigc::bind( sigc::mem_fun(*this, &MsgDiag::slot_timeout), 0 ); m_conn_timer = JDLIB::Timeout::connect( slot_timeout, TIMER_TIMEOUT ); int ret = Gtk::MessageDialog::run(); SESSION::set_dialog_shown( false ); CORE::core_set_command( "dialog_hidden" ); #ifdef _DEBUG std::cout << "MsgDiag::run fin\n"; #endif return ret; } void MsgDiag::show() { #ifdef _DEBUG std::cout << "MsgDiag::show\n"; #endif SESSION::set_dialog_shown( true ); CORE::core_set_command( "dialog_shown" ); Gtk::MessageDialog::show(); } void MsgDiag::hide() { #ifdef _DEBUG std::cout << "MsgDiag::hide\n"; #endif SESSION::set_dialog_shown( false ); CORE::core_set_command( "dialog_hidden" ); Gtk::MessageDialog::hide(); } // タイマーのslot関数 bool MsgDiag::slot_timeout( int timer_number ) { // メインループが停止していて dispatcher が働かないため // タイマーから強制的に実行させる CORE::get_dispmanager()->slot_dispatch(); return true; } ///////////////////////////////////// MsgCheckDiag::MsgCheckDiag( Gtk::Window* parent, const Glib::ustring& message, const Glib::ustring& message_check, Gtk::MessageType type, Gtk::ButtonsType buttons, const int default_response ) : SKELETON::MsgDiag( parent, message, false, type, Gtk::BUTTONS_NONE, false ) , m_chkbutton( message_check, true ) { const int mrg = 16; Gtk::HBox* hbox = Gtk::manage( new Gtk::HBox ); hbox->pack_start( m_chkbutton, Gtk::PACK_EXPAND_WIDGET, mrg ); get_content_area()->pack_start( *hbox, Gtk::PACK_SHRINK ); if( buttons == Gtk::BUTTONS_OK ){ add_default_button( g_dgettext( GTK_DOMAIN, "_OK" ), Gtk::RESPONSE_OK ); } else if( buttons == Gtk::BUTTONS_OK_CANCEL ){ add_button( g_dgettext( GTK_DOMAIN, "_No" ), Gtk::RESPONSE_CANCEL ); add_default_button( g_dgettext( GTK_DOMAIN, "_OK" ), Gtk::RESPONSE_OK ); } else if( buttons == Gtk::BUTTONS_YES_NO ){ if( default_response == Gtk::RESPONSE_NO ){ add_default_button( g_dgettext( GTK_DOMAIN, "_No" ), Gtk::RESPONSE_NO ); add_button( g_dgettext( GTK_DOMAIN, "_Yes" ), Gtk::RESPONSE_YES ); } else{ add_button( g_dgettext( GTK_DOMAIN, "_No" ), Gtk::RESPONSE_NO ); add_default_button( g_dgettext( GTK_DOMAIN, "_Yes" ), Gtk::RESPONSE_YES ); } } show_all_children(); } MsgCheckDiag::~MsgCheckDiag() noexcept = default; ///////////////////////////////////// MsgOverwriteDiag::MsgOverwriteDiag( Gtk::Window* parent ) : SKELETON::MsgDiag( parent, "ファイルが存在します。ファイル名を変更して保存しますか?", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_NONE ) { add_button( g_dgettext( GTK_DOMAIN, "_No" ), Gtk::RESPONSE_NO ); add_button( g_dgettext( GTK_DOMAIN, "_Yes" ), Gtk::RESPONSE_YES ); add_button( "上書き", OVERWRITE_YES ); add_button( "すべていいえ", OVERWRITE_NO_ALL ); add_button( "すべて上書き", OVERWRITE_YES_ALL ); } MsgOverwriteDiag::~MsgOverwriteDiag() noexcept = default; jdim-0.7.0/src/skeleton/msgdiag.h000066400000000000000000000045651417047150700166600ustar00rootroot00000000000000// ライセンス: GPL2 // メッセージダイアログの基底クラス #ifndef _MSGDIAG_H #define _MSGDIAG_H #include #include "jdlib/timeout.h" namespace SKELETON { class MsgDiag : public Gtk::MessageDialog { std::unique_ptr m_conn_timer; public: MsgDiag( Gtk::Window& parent, const Glib::ustring& message, bool use_markup = false, Gtk::MessageType type = Gtk::MESSAGE_INFO, Gtk::ButtonsType buttons = Gtk::BUTTONS_OK, bool modal = false); // parent がポインタの時は nullptr かどうかで場合分け MsgDiag( Gtk::Window* parent, const Glib::ustring& message, bool use_markup = false, Gtk::MessageType type = Gtk::MESSAGE_INFO, Gtk::ButtonsType buttons = Gtk::BUTTONS_OK, bool modal = false); ~MsgDiag() noexcept; void add_default_button( const Glib::ustring& label, const int id ); void add_default_button( Gtk::Widget* button, const int id ); virtual int run(); void show(); void hide(); private: // タイマーのslot関数 bool slot_timeout( int timer_number ); }; ///////////////////////////////////// // チェックボタン付き class MsgCheckDiag : public SKELETON::MsgDiag { Gtk::CheckButton m_chkbutton; public: MsgCheckDiag( Gtk::Window* parent, const Glib::ustring& message, const Glib::ustring& message_check, Gtk::MessageType type = Gtk::MESSAGE_INFO, Gtk::ButtonsType buttons = Gtk::BUTTONS_OK, const int default_response = -1 ); ~MsgCheckDiag() noexcept; Gtk::CheckButton& get_chkbutton(){ return m_chkbutton; } }; ///////////////////////////////////// // 上書きチェックダイアログ enum { OVERWRITE_YES = Gtk::RESPONSE_YES + 100, OVERWRITE_YES_ALL = Gtk::RESPONSE_YES + 200, OVERWRITE_NO_ALL = Gtk::RESPONSE_NO + 200 }; class MsgOverwriteDiag : public SKELETON::MsgDiag { public: explicit MsgOverwriteDiag( Gtk::Window* parent ); ~MsgOverwriteDiag() noexcept; }; } #endif jdim-0.7.0/src/skeleton/notebook.cpp000066400000000000000000000006761417047150700174170ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "notebook.h" using namespace SKELETON; JDNotebook::~JDNotebook() noexcept = default; // unpack = true の時取り除く int JDNotebook::append_remove_page( bool unpack, Widget& child, const Glib::ustring& tab_label, bool use_mnemonic ) { if( unpack ){ remove( child ); return 0; } return append_page( child, tab_label, use_mnemonic ); } jdim-0.7.0/src/skeleton/notebook.h000066400000000000000000000007071417047150700170570ustar00rootroot00000000000000// ライセンス: GPL2 // // Notebook クラス // #ifndef _NOTEBOOK_H #define _NOTEBOOK_H #include namespace SKELETON { class JDNotebook : public Gtk::Notebook { public: using Gtk::Notebook::Notebook; ~JDNotebook() noexcept; // unpack = true の時取り除く int append_remove_page( bool unpack, Widget& child, const Glib::ustring& tab_label, bool use_mnemonic = false ); }; } #endif jdim-0.7.0/src/skeleton/panecontrol.cpp000066400000000000000000000141631417047150700201170ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "panecontrol.h" using namespace SKELETON; enum { PANE_PAGE_MINSIZE = 32 }; PaneControl::PaneControl( Gtk::Paned& paned, int fixmode ) : m_paned( paned ) , m_click_fold( PANE_CLICK_NORMAL ) , m_fixmode( fixmode ) , m_mode( PANE_NORMAL ) , m_pre_size{ -1 } {} PaneControl::~PaneControl() noexcept = default; // // クロック入力 // void PaneControl::clock_in() { // Gtk::Paned は configure_event()をキャッチ出来ないので // 応急処置としてタイマーの中でサイズが変更したか調べて // 変わっていたら仕切りの位置を調整する if( m_pre_size != get_size() ){ #ifdef _DEBUG int pos = m_paned.get_position(); std::cout << "PaneControl::clock_in resize pos = " << pos << " presize = " << m_pre_size << " size = " << get_size() << std::endl; #endif update_position(); m_pre_size = get_size(); } } // // モードにしたがってセパレータの位置を更新 // void PaneControl::update_position() { #ifdef _DEBUG std::cout << "PaneControl::update_position fixmode = " << m_fixmode << " mode = " << m_mode << " pos = " << m_pos << " size = " << get_size() << std::endl; #endif int pos = m_paned.get_position(); // PAGE1を最大化 if( m_mode == PANE_MAX_PAGE1 ) m_paned.set_position( get_size() ); // PAGE2を最大化 else if( m_mode == PANE_MAX_PAGE2 && pos > 0 ) m_paned.set_position( 0 ); // 通常状態 else if( m_mode == PANE_NORMAL ){ // ウィンドウをリサイズしたときの処理 // m_fixmode の値によりPAGE1とPAGE2のどちらのサイズを固定するか判断する int newpos = 0; if( m_fixmode == PANE_FIXSIZE_PAGE1 && pos != m_pos ){ newpos = MIN( m_pos, get_size() - PANE_PAGE_MINSIZE ); } else if( m_fixmode == PANE_FIXSIZE_PAGE2 && pos != get_size() - m_pos ){ newpos = MAX( get_size() - m_pos, PANE_PAGE_MINSIZE ); } if( newpos ) m_paned.set_position( newpos ); #ifdef _DEBUG std::cout << "newpos = " << newpos << " pos = " << m_paned.get_position() << std::endl; #endif } } int PaneControl::get_position() { if( ! m_pos ){ if( m_fixmode == PANE_FIXSIZE_PAGE1 ) m_pos = m_paned.get_position(); else if( m_fixmode == PANE_FIXSIZE_PAGE2 ) m_pos = get_size() - m_paned.get_position(); } #ifdef _DEBUG std::cout << "PaneControl::get_position " << m_pos << std::endl; #endif return m_pos; } void PaneControl::set_position( int position ) { if( position == m_pos ) return; #ifdef _DEBUG std::cout << "PaneControl::set_position position = " << position << " size = " << get_size() << std::endl; #endif m_pos = position; update_position(); } // // ページ最大化切り替え // void PaneControl::set_mode( int mode ) { if( mode == m_mode ) return; #ifdef _DEBUG int pos = m_paned.get_position(); int size = get_size(); std::cout << "PaneControl::set_mode = " << mode << " size = " << size << " current_pos = " << pos << " pos = " << get_position() << std::endl; #endif m_mode = mode; update_position(); m_sig_pane_modechanged.emit( m_mode ); } // unpack = true の時取り除く void PaneControl::add_remove1( bool unpack, Gtk::Widget& child ) { if( unpack ) m_paned.remove( child ); // paned.ccg Revision 2 , Tue Jan 21 13:41:59 2003 UTC をハック // pack1 を SHRINK、pack2 を EXPAND にしないとリサイズしたときに // 仕切りがバタつく else m_paned.pack1( child, Gtk::SHRINK ); } // unpack = true の時取り除く void PaneControl::add_remove2( bool unpack, Gtk::Widget& child ) { if( unpack ) m_paned.remove( child ); else m_paned.pack2( child, Gtk::EXPAND ); } void PaneControl::button_press_event( GdkEventButton* event ) { m_clicked = is_separater_clicked( event ); // 仕切りをクリックしたかチェック m_drag = false; #ifdef _DEBUG std::cout << "PaneControl::button_press_event" << " x = " << event->x << " y = " << event->y << " pos = " << m_pos << " current_pos = " << m_paned.get_position() << " click = " << m_clicked << std::endl; #endif } void PaneControl::button_release_event( GdkEventButton* event ) { #ifdef _DEBUG std::cout << "PaneControl::butoon_release_event clicked = " << m_clicked << " drag = " << m_drag << std::endl; #endif // 仕切りをクリックしたら折りたたむ if( m_click_fold != PANE_CLICK_NORMAL && m_clicked && ! m_drag ){ if( m_click_fold == PANE_CLICK_FOLD_PAGE1 ){ if( m_mode == PANE_NORMAL ) set_mode( PANE_MAX_PAGE2 ); else set_mode( PANE_NORMAL ); } else if( m_click_fold == PANE_CLICK_FOLD_PAGE2 ){ if( m_mode == PANE_NORMAL ) set_mode( PANE_MAX_PAGE1 ); else set_mode( PANE_NORMAL ); } } // 仕切りをドラッグした場合 else if( m_clicked && m_drag ){ if( m_mode == PANE_NORMAL ){ if( m_fixmode == PANE_FIXSIZE_PAGE1 ) m_pos = m_paned.get_position(); else if( m_fixmode == PANE_FIXSIZE_PAGE2 ) m_pos = get_size() - m_paned.get_position(); #ifdef _DEBUG std::cout << "new pos = " << m_pos << std::endl; #endif set_mode( PANE_NORMAL ); } else if( m_mode == PANE_MAX_PAGE1 ) m_paned.set_position( get_size() ); else if( m_mode == PANE_MAX_PAGE2 ) m_paned.set_position( 0 ); } m_clicked = false; m_drag = false; } void PaneControl::motion_notify_event( GdkEventMotion* event ) { #ifdef _DEBUG // std::cout << "PaneControl::motion_notify_event\n"; #endif m_drag = true; } void PaneControl::enter_notify_event( GdkEventCrossing* event ) { #ifdef _DEBUG std::cout << "PaneControl::enter_notify_event\n"; #endif m_on_paned = true; } void PaneControl::leave_notify_event( GdkEventCrossing* event ) { #ifdef _DEBUG std::cout << "PaneControl::leave_notify_event\n"; #endif m_on_paned = false; } jdim-0.7.0/src/skeleton/panecontrol.h000066400000000000000000000076211417047150700175650ustar00rootroot00000000000000// ライセンス: GPL2 // // Paned widget の制御クラス // #ifndef _PANECONTROL_H #define _PANECONTROL_H #include namespace SKELETON { // コンストラクタで指定する m_fixmode の値 enum { PANE_FIXSIZE_PAGE1, // ウィンドウリサイズ時にPAGE1のサイズを固定する PANE_FIXSIZE_PAGE2 // ウィンドウリサイズ時にPAGE2のサイズを固定する }; // set_mode()で指定する m_mode の値 enum { PANE_NORMAL = 0, // どちらのページも折り畳んでいない通常状態 PANE_MAX_PAGE1, // PAGE1 最大化中 PANE_MAX_PAGE2 // PAGE2 最大化中 }; // set_click_fold()で指定する m_click_fold の値 enum { PANE_CLICK_NORMAL, // セパレータをクリックしても折り畳まない PANE_CLICK_FOLD_PAGE1, // セパレータをクリックするとPAGE1を折り畳む PANE_CLICK_FOLD_PAGE2 // セパレータをクリックするとPAGE2を折り畳む }; // ペーン表示が切り替えられた typedef sigc::signal< void, int > SIG_PANE_MODECHANGED; class PaneControl { SIG_PANE_MODECHANGED m_sig_pane_modechanged; Gtk::Paned& m_paned; int m_click_fold; bool m_clicked{}; bool m_drag{}; bool m_on_paned{}; int m_fixmode; int m_mode; int m_pos{}; int m_pre_size; public: PaneControl( Gtk::Paned& paned, int fixmode ); virtual ~PaneControl() noexcept; SIG_PANE_MODECHANGED& sig_pane_modechanged() { return m_sig_pane_modechanged; } void clock_in(); // セパレータの位置の取得やセット void update_position(); int get_position(); void set_position( int position ); // PANE_MAX_PAGE1 などを指定 void set_mode( int mode ); int get_mode() const { return m_mode; } // PANE_CLICK_FOLD_PAGE1 などを指定 void set_click_fold( int mode ){ m_click_fold = mode; } int get_click_fold() const { return m_click_fold; } void add_remove1( bool unpack, Gtk::Widget& child ); void add_remove2( bool unpack, Gtk::Widget& child ); void button_press_event( GdkEventButton* event ); void button_release_event( GdkEventButton* event ); void motion_notify_event( GdkEventMotion* event ); void enter_notify_event( GdkEventCrossing* event ); void leave_notify_event( GdkEventCrossing* event ); protected: bool is_on_paned() const { return m_on_paned; } const Gtk::Paned& get_paned() const { return m_paned; } virtual int get_size() const = 0; virtual bool is_separater_clicked( GdkEventButton* event ) const = 0; }; ///////////////////////////////////// class HPaneControl : public PaneControl { public: using PaneControl::PaneControl; ~HPaneControl() noexcept = default; protected: int get_size() const override { return get_paned().get_width(); } bool is_separater_clicked( GdkEventButton* event ) const override { if( is_on_paned() && event->type == GDK_BUTTON_PRESS && event->button == 1 && event->x >= 0 && event->x <= 8 ) return true; return false; } }; ///////////////////////////////////// class VPaneControl : public PaneControl { public: using PaneControl::PaneControl; ~VPaneControl() noexcept = default; protected: int get_size() const override { return get_paned().get_height(); } bool is_separater_clicked( GdkEventButton* event ) const override { if( is_on_paned() && event->type == GDK_BUTTON_PRESS && event->button == 1 && event->y >= 0 && event->y <= 8 ) return true; return false; } }; } #endif jdim-0.7.0/src/skeleton/popupwin.cpp000066400000000000000000000053121417047150700174500ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "popupwin.h" using namespace SKELETON; PopupWin::PopupWin( Gtk::Widget* parent, SKELETON::View* view, const int mrg_x, const int mrg_y ) : PopupWinBase( POPUPWIN_NOFRAME ) , m_parent{ parent } , m_view{ view } , m_mrg_x{ mrg_x } , m_mrg_y{ mrg_y } { #ifdef _DEBUG std::cout << "PopupWin::PopupWin\n"; #endif m_view->sig_resize_popup().connect( sigc::mem_fun( *this, &PopupWin::slot_resize_popup ) ); add( *m_view ); m_view->sig_hide_popup().connect( sigc::mem_fun( *this, &PopupWin::slot_hide_popup ) ); m_view->show_view(); Gtk::Widget* toplevel = m_parent->get_toplevel(); if( toplevel->get_is_toplevel() ) { set_transient_for( *dynamic_cast< Gtk::Window* >( toplevel ) ); } slot_resize_popup(); } // // ポップアップウィンドウの座標と幅と高さを計算して移動とリサイズ // void PopupWin::slot_resize_popup() { if( ! m_view ) return; // マウス座標 int x_mouse, y_mouse; Gdk::Display::get_default()->get_default_seat()->get_pointer()->get_position( x_mouse, y_mouse ); // クライアントのサイズを取得 const int width_client = m_view->width_client(); const int height_client = m_view->height_client(); const int width_desktop = m_parent->get_screen()->get_width(); const int height_desktop = m_parent->get_screen()->get_height(); // x 座標と幅 const int width_popup = width_client; int x_popup; if( x_mouse + m_mrg_x + width_popup <= width_desktop ) x_popup = x_mouse + m_mrg_x; else x_popup = MAX( 0, width_desktop - width_popup ); // y 座標と高さ int y_popup; int height_popup; if( y_mouse - ( height_client + m_mrg_y ) >= 0 ){ // 上にスペースがある y_popup = y_mouse - height_client - m_mrg_y; height_popup = height_client; } else if( y_mouse + m_mrg_y + height_client <= height_desktop ){ // 下にスペースがある y_popup = y_mouse + m_mrg_y; height_popup = height_client; } else if( m_view->get_popup_upside() || y_mouse > height_desktop/2 ){ // スペースは無いが上に表示 y_popup = MAX( 0, y_mouse - ( height_client + m_mrg_y ) ); height_popup = y_mouse - ( y_popup + m_mrg_y ); } else{ // 下 y_popup = y_mouse + m_mrg_y; height_popup = height_desktop - y_popup; } #ifdef _DEBUG std::cout << "PopupWin::slot_resize_popup : x = " << x_popup << " y = " << y_popup << " w = " << width_popup << " h = " << height_popup << std::endl; #endif move( x_popup, y_popup ); resize( width_popup, height_popup ); show_all(); } jdim-0.7.0/src/skeleton/popupwin.h000066400000000000000000000024271417047150700171210ustar00rootroot00000000000000// ライセンス: GPL2 // // ポップアップウィンドウ // // SKELETON::Viewをポップアップ表示する。 // 表示するSKELETON::Viewはデストラクタでdeleteするので呼出元でdeleteしなくても良い // #ifndef _POPUPWIN_H #define _POPUPWIN_H #include "popupwinbase.h" #include "view.h" #include namespace SKELETON { class PopupWin : public PopupWinBase { SIG_HIDE_POPUP m_sig_hide_popup; Gtk::Widget* m_parent; std::unique_ptr m_view; int m_mrg_x; // ポップアップとマウスカーソルの間のマージン(水平方向) int m_mrg_y; // ポップアップとマウスカーソルの間のマージン(垂直方向) public: PopupWin( Gtk::Widget* parent, SKELETON::View* view, const int mrg_x, const int mrg_y ); ~PopupWin() noexcept = default; // m_view からの hide シグナルをブリッジする SIG_HIDE_POPUP& sig_hide_popup() { return m_sig_hide_popup; } void slot_hide_popup(){ m_sig_hide_popup.emit(); } SKELETON::View* view(){ return m_view.get(); } // ポップアップウィンドウの座標と幅と高さを計算して移動とリサイズ void slot_resize_popup(); }; } #endif jdim-0.7.0/src/skeleton/popupwinbase.cpp000066400000000000000000000015401417047150700203020ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "popupwinbase.h" #include "command.h" using namespace SKELETON; PopupWinBase::PopupWinBase( bool draw_frame ) : Gtk::Window( Gtk::WINDOW_POPUP ) , m_draw_frame( draw_frame ) { if( m_draw_frame ) { set_border_width( 1 ); auto provider = Gtk::CssProvider::create(); provider->load_from_data( "window { border: 1px solid black; }" ); get_style_context()->add_provider( provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION ); } if ( auto main_window = CORE::get_mainwindow() ) { set_transient_for( *main_window ); } } bool PopupWinBase::on_configure_event( GdkEventConfigure* event ) { const bool ret = Gtk::Window::on_configure_event( event ); m_sig_configured.emit( get_width(), get_height() ); return ret; } jdim-0.7.0/src/skeleton/popupwinbase.h000066400000000000000000000017361417047150700177560ustar00rootroot00000000000000// ライセンス: GPL2 // // ポップアップウィンドウの基底クラス // #ifndef POPUPWINBASE_H #define POPUPWINBASE_H #include namespace SKELETON { constexpr bool POPUPWIN_DRAWFRAME = true; constexpr bool POPUPWIN_NOFRAME = false; // // Gtk::Windows は signal_configure_event()を発行しないようなので // 自前でconfigureイベントをフックしてシグナルを発行する // using SIG_CONFIGURED_POPUP = sigc::signal< void, int, int >; class PopupWinBase : public Gtk::Window { SIG_CONFIGURED_POPUP m_sig_configured; const bool m_draw_frame; public: // draw_frame == true なら枠を描画する explicit PopupWinBase( bool draw_frame ); ~PopupWinBase() noexcept = default; SIG_CONFIGURED_POPUP sig_configured(){ return m_sig_configured; } protected: bool on_configure_event( GdkEventConfigure* event ) override; }; } #endif jdim-0.7.0/src/skeleton/prefdiag.cpp000066400000000000000000000061541417047150700173550ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "prefdiag.h" #include "label_entry.h" #include "command.h" #include "dispatchmanager.h" #include "environment.h" #include "global.h" #include "session.h" #include using namespace SKELETON; PrefDiag::PrefDiag( Gtk::Window* parent, const std::string& url, const bool add_cancel, const bool add_apply, const bool add_open ) : Gtk::Dialog( "", ENVIRONMENT::get_dialog_use_header_bar() ? Gtk::DIALOG_USE_HEADER_BAR : Gtk::DialogFlags{} ) , m_url( url ) , m_bt_apply( g_dgettext( GTK_DOMAIN, "_Apply" ), true ) { if( add_apply ){ m_bt_apply.signal_clicked().connect( sigc::mem_fun(*this, &PrefDiag::slot_apply_clicked ) ); if( property_use_header_bar() ) { get_header_bar()->pack_start( m_bt_apply ); } else { get_action_area()->pack_start( m_bt_apply ); } } if( add_cancel ){ add_button( g_dgettext( GTK_DOMAIN, "_Cancel" ), Gtk::RESPONSE_CANCEL ) ->signal_clicked().connect( sigc::mem_fun(*this, &PrefDiag::slot_cancel_clicked ) ); } if( add_open ) m_bt_ok = add_button( g_dpgettext( GTK_DOMAIN, "Stock label\x04_Open", 12 ), Gtk::RESPONSE_OK ); else m_bt_ok = add_button( g_dgettext( GTK_DOMAIN, "_OK" ), Gtk::RESPONSE_OK ); m_bt_ok->signal_clicked().connect( sigc::mem_fun(*this, &PrefDiag::slot_ok_clicked ) ); if( parent ) set_transient_for( *parent ); else set_transient_for( *CORE::get_mainwindow() ); } PrefDiag::~PrefDiag() = default; // // okボタンをフォーカス // void PrefDiag::grab_ok() { if( ! m_bt_ok ) return; m_bt_ok->set_can_default( true ); m_bt_ok->grab_default(); m_bt_ok->grab_focus(); } // // Entry、LabelEntryがactiveになったときにOKでダイアログを終了させる // void PrefDiag::set_activate_entry( Gtk::Entry& entry ) { entry.signal_activate().connect( sigc::mem_fun( *this, &PrefDiag::slot_activate_entry ) ); } void PrefDiag::set_activate_entry( LabelEntry& entry ) { entry.signal_activate().connect( sigc::mem_fun( *this, &PrefDiag::slot_activate_entry ) ); } void PrefDiag::slot_activate_entry() { #ifdef _DEBUG std::cout << "PrefDiag::slot_activate_entry\n"; #endif slot_ok_clicked(); response( Gtk::RESPONSE_OK ); } int PrefDiag::run(){ SESSION::set_dialog_shown( true ); CORE::core_set_command( "dialog_shown" ); // タイマーセット sigc::slot< bool > slot_timeout = sigc::bind( sigc::mem_fun(*this, &PrefDiag::slot_timeout), 0 ); m_conn_timer = JDLIB::Timeout::connect( slot_timeout, TIMER_TIMEOUT ); int ret = Gtk::Dialog::run(); SESSION::set_dialog_shown( false ); CORE::core_set_command( "dialog_hidden" ); return ret; } // タイマーのslot関数 bool PrefDiag::slot_timeout( int timer_number ) { // メインループが停止していて dispatcher が働かないため // タイマーから強制的に実行させる CORE::get_dispmanager()->slot_dispatch(); timeout(); return true; } jdim-0.7.0/src/skeleton/prefdiag.h000066400000000000000000000027121417047150700170160ustar00rootroot00000000000000// ライセンス: GPL2 // 設定ダイアログの基底クラス #ifndef _PREFDIAG_H #define _PREFDIAG_H #include #include "jdlib/timeout.h" namespace SKELETON { class LabelEntry; class PrefDiag : public Gtk::Dialog { std::string m_url; Gtk::Button* m_bt_ok{}; Gtk::Button m_bt_apply; std::unique_ptr m_conn_timer; public: // parent == nullptr のときはメインウィンドウをparentにする PrefDiag( Gtk::Window* parent, const std::string& url, const bool add_cancel = true, const bool add_apply = false, const bool add_open = false ); ~PrefDiag(); const std::string& get_url() const { return m_url; } // okボタンをフォーカス void grab_ok(); // Entry、LabelEntryがactiveになったときにOKでダイアログを終了させる void set_activate_entry( Gtk::Entry& entry ); void set_activate_entry( LabelEntry& entry ); virtual int run(); protected: virtual void slot_ok_clicked(){} virtual void slot_cancel_clicked(){} virtual void slot_apply_clicked(){} private: // タイマーのslot関数 bool slot_timeout( int timer_number ); // 各設定ダイアログ別のタイムアウト処理 ( slot_timeout()から呼び出される ) virtual void timeout(){} void slot_activate_entry(); }; } #endif jdim-0.7.0/src/skeleton/selectitempref.cpp000066400000000000000000000444521417047150700206120ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "selectitempref.h" #include "config/globalconf.h" #include "jdlib/miscutil.h" #include "global.h" #include "session.h" #include using namespace SKELETON; #define ROW_COLOR "WhiteSmoke" SelectItemPref::SelectItemPref( Gtk::Window* parent, const std::string& url ) : SKELETON::PrefDiag( parent, url, true, true ) , m_button_top( g_dpgettext( GTK_DOMAIN, "Stock label, navigation\x04_Top", 24 ), true ) , m_button_up( g_dpgettext( GTK_DOMAIN, "Stock label, navigation\x04_Up", 24 ), true ) , m_button_down( g_dpgettext( GTK_DOMAIN, "Stock label, navigation\x04_Down", 24 ), true ) , m_button_bottom( g_dpgettext( GTK_DOMAIN, "Stock label, navigation\x04_Bottom", 24 ), true ) , m_button_default( g_dpgettext( GTK_DOMAIN, "Stock label\x04_Revert", 12 ), true ) { m_list_default_data.clear(); m_button_top.set_image_from_icon_name( "go-top" ); m_button_up.set_image_from_icon_name( "go-up" ); m_button_down.set_image_from_icon_name( "go-down" ); m_button_bottom.set_image_from_icon_name( "go-bottom" ); m_button_delete.set_image_from_icon_name( "go-next" ); m_button_add.set_image_from_icon_name( "go-previous" ); pack_widgets(); } // // widgetのパック // void SelectItemPref::pack_widgets() { // 同一カラムにアイコンと項目名の両方を表示するので、手動で列を作成する // 表示項目リスト m_tree_shown.get_selection()->set_mode( Gtk::SELECTION_MULTIPLE ); m_tree_shown.signal_focus_in_event().connect( sigc::mem_fun( *this, &SelectItemPref::slot_focus_in_shown ) ); m_store_shown = Gtk::ListStore::create( m_columns_shown ); m_tree_shown.set_model( m_store_shown ); // "表示"の列を作成 Gtk::TreeViewColumn* view_column_shown = Gtk::manage( new Gtk::TreeViewColumn( "表示" ) ); m_tree_shown.append_column( *view_column_shown ); // アイコン Gtk::CellRendererPixbuf* render_pixbuf_shown = Gtk::manage( new Gtk::CellRendererPixbuf() ); view_column_shown->pack_start( *render_pixbuf_shown, false ); view_column_shown->add_attribute( *render_pixbuf_shown, "pixbuf", 0 ); // 項目名 Gtk::CellRendererText* render_text_shown = Gtk::manage( new Gtk::CellRendererText() ); view_column_shown->pack_start( *render_text_shown, true ); view_column_shown->add_attribute( *render_text_shown, "text", 1 ); // ボタン(縦移動) m_vbuttonbox_v.pack_start( m_button_top, Gtk::PACK_SHRINK ); m_vbuttonbox_v.pack_start( m_button_up, Gtk::PACK_SHRINK ); m_vbuttonbox_v.pack_start( m_button_down, Gtk::PACK_SHRINK ); m_vbuttonbox_v.pack_start( m_button_bottom, Gtk::PACK_SHRINK ); // ボタン(横移動) m_vbuttonbox_h.pack_start( m_button_delete, Gtk::PACK_SHRINK ); m_vbuttonbox_h.pack_start( m_button_add, Gtk::PACK_SHRINK ); // ボタン(アクション) m_vbuttonbox_action.pack_start( m_button_default, Gtk::PACK_SHRINK ); // ボタン(スロット関数) m_button_top.signal_clicked().connect( sigc::mem_fun( *this, &SelectItemPref::slot_top ) ); m_button_up.signal_clicked().connect( sigc::mem_fun( *this, &SelectItemPref::slot_up ) ); m_button_down.signal_clicked().connect( sigc::mem_fun( *this, &SelectItemPref::slot_down ) ); m_button_bottom.signal_clicked().connect( sigc::mem_fun( *this, &SelectItemPref::slot_bottom ) ); m_button_delete.signal_clicked().connect( sigc::mem_fun( *this, &SelectItemPref::slot_delete ) ); m_button_add.signal_clicked().connect( sigc::mem_fun( *this, &SelectItemPref::slot_add ) ); m_button_default.signal_clicked().connect( sigc::mem_fun( *this, &SelectItemPref::slot_default ) ); // 非表示項目リスト m_tree_hidden.get_selection()->set_mode( Gtk::SELECTION_MULTIPLE ); m_tree_hidden.signal_focus_in_event().connect( sigc::mem_fun( *this, &SelectItemPref::slot_focus_in_hidden ) ); m_store_hidden = Gtk::ListStore::create( m_columns_hidden ); m_tree_hidden.set_model( m_store_hidden ); // "非表示"の列を作成 Gtk::TreeViewColumn* view_column_hidden = Gtk::manage( new Gtk::TreeViewColumn( "非表示" ) ); m_tree_hidden.append_column( *view_column_hidden ); // アイコン Gtk::CellRendererPixbuf* render_pixbuf_hidden = Gtk::manage( new Gtk::CellRendererPixbuf() ); view_column_hidden->pack_start( *render_pixbuf_hidden, false ); view_column_hidden->add_attribute( *render_pixbuf_hidden, "pixbuf", 0 ); // 項目名 Gtk::CellRendererText* render_text_hidden = Gtk::manage( new Gtk::CellRendererText() ); view_column_hidden->pack_start( *render_text_hidden, true ); view_column_hidden->add_attribute( *render_text_hidden, "text", 1 ); // 全体のパッキング m_scroll_shown.add( m_tree_shown ); m_scroll_shown.set_size_request( 250, 300 ); m_scroll_shown.set_policy( Gtk::POLICY_NEVER, Gtk::POLICY_ALWAYS ); m_hbox.pack_start( m_scroll_shown, Gtk::PACK_EXPAND_WIDGET ); m_vbuttonbox_v.set_layout( Gtk::BUTTONBOX_START ); m_vbuttonbox_v.set_spacing( 4 ); m_vbox.pack_start( m_vbuttonbox_v, Gtk::PACK_EXPAND_WIDGET ); m_vbuttonbox_h.set_layout( Gtk::BUTTONBOX_EDGE ); m_vbuttonbox_h.set_spacing( 4 ); m_vbox.pack_start( m_vbuttonbox_h, Gtk::PACK_SHRINK ); m_vbuttonbox_action.set_layout( Gtk::BUTTONBOX_END ); m_vbuttonbox_action.set_spacing( 4 ); m_vbox.pack_start( m_vbuttonbox_action, Gtk::PACK_EXPAND_WIDGET ); m_hbox.pack_start( m_vbox, Gtk::PACK_SHRINK, 4 ); m_scroll_hidden.add( m_tree_hidden ); m_scroll_hidden.set_size_request( 250, 300 ); m_scroll_hidden.set_policy( Gtk::POLICY_NEVER, Gtk::POLICY_ALWAYS ); m_hbox.pack_start( m_scroll_hidden, Gtk::PACK_EXPAND_WIDGET ); get_content_area()->set_spacing( 8 ); get_content_area()->pack_start( m_hbox ); show_all_children(); } // // フォーカスが外れたTreeViewから項目の選択をなくす // bool SelectItemPref::slot_focus_in_shown( GdkEventFocus* event ) { m_tree_hidden.get_selection()->unselect_all(); return true; } bool SelectItemPref::slot_focus_in_hidden( GdkEventFocus* event ) { m_tree_shown.get_selection()->unselect_all(); return true; } // // 項目名でデフォルトデータからアイコンを取得 // Glib::RefPtr< Gdk::Pixbuf > SelectItemPref::get_icon( const Glib::ustring& name ) { Glib::RefPtr< Gdk::Pixbuf > icon; std::list< DEFAULT_DATA >::iterator it = m_list_default_data.begin(); while( it != m_list_default_data.end() ) { if( (*it).name == name ) { icon = (*it).icon; break; } ++it; } return icon; } // // 表示項目のクリア // void SelectItemPref::clear() { m_store_shown->clear(); m_store_hidden->clear(); } // // デフォルトデータを追加( 項目名、アイコンID ) // void SelectItemPref::append_default_pair( const Glib::ustring& name, const Glib::RefPtr< Gdk::Pixbuf > icon ) { if( name.empty() ) return; DEFAULT_DATA default_data; default_data.name = name; default_data.icon = icon; m_list_default_data.push_back( default_data ); } // // 文字列を元に行を作成 // void SelectItemPref::append_rows( const std::string& str ) { clear(); bool found_separator = false; // "非表示"にデフォルトデータを全て追加 std::list< DEFAULT_DATA >::iterator def_it = m_list_default_data.begin(); while( def_it != m_list_default_data.end() ) { // 2つ以上セパレータを追加しない if( (*def_it).name == ITEM_NAME_SEPARATOR ) { if( ! found_separator ) append_hidden( (*def_it).name, false ); found_separator = true; } else append_hidden( (*def_it).name, false ); ++def_it; } if( str.empty() ) return; // "表示"に追加と不要な"非表示"を削除 std::list< std::string > items = MISC::split_line( str ); std::list< std::string >::iterator it = items.begin(); while( it != items.end() ) { if( append_shown( *it, false ) ) erase_hidden( *it ); ++it; } } // // 全ての有効な項目を文字列で取得 // std::string SelectItemPref::get_items() const { std::string items; const Gtk::TreeModel::Children children = m_store_shown->children(); Gtk::TreeModel::iterator it = children.begin(); while( it != children.end() ) { Gtk::TreeModel::Row row = *it; items.append( row[ m_columns_shown.m_column_text ] + " " ); ++it; } return items; } // // 表示項目に指定した項目を追加 // Gtk::TreeRow SelectItemPref::append_shown( const std::string& name, const bool set_cursor ) { Gtk::TreeModel::Row row; if( SESSION::parse_item( name ) == ITEM_END ) return row; row = *( m_store_shown->append() ); Glib::RefPtr< Gdk::Pixbuf > icon = get_icon( name ); if( icon ) row[ m_columns_shown.m_column_icon ] = icon; row[ m_columns_shown.m_column_text ] = name; if( set_cursor ){ m_tree_shown.get_selection()->select( row ); m_tree_shown.set_cursor( m_store_shown->get_path( row ) ); } return row; } // // 非表示項目に指定した項目を追加 // Gtk::TreeRow SelectItemPref::append_hidden( const std::string& name, const bool set_cursor ) { Gtk::TreeModel::Row row = *( m_store_hidden->append() ); Glib::RefPtr< Gdk::Pixbuf > icon = get_icon( name ); if( icon ) row[ m_columns_hidden.m_column_icon ] = icon; row[ m_columns_hidden.m_column_text ] = name; if( set_cursor ){ m_tree_hidden.get_selection()->select( row ); m_tree_hidden.set_cursor( m_store_hidden->get_path( row ) ); } return row; } // // 非表示項目から指定した項目を削除 // void SelectItemPref::erase_hidden( const std::string& name ) { if( name == ITEM_NAME_SEPARATOR ) return; const Gtk::TreeModel::Children children = m_store_hidden->children(); Gtk::TreeModel::iterator it = children.begin(); while( it != children.end() ) { Gtk::TreeModel::Row row = *it; if( row[ m_columns_hidden.m_column_text ] == name ) { m_store_hidden->erase( *it ); break; } ++it; } } // // KeyPressのフック // bool SelectItemPref::on_key_press_event( GdkEventKey* event ) { bool hook = false; // "Gtk::Dialog"は"Esc"を押すと閉じられてしまうので // "GDK_KEY_Escape"をキャンセルする switch( event->keyval ) { case GDK_KEY_Escape : hook = true; break; } m_sig_key_press.emit( event ); if( hook ) return true; return Gtk::Dialog::on_key_press_event( event ); } // // KeyReleaseのフック // bool SelectItemPref::on_key_release_event( GdkEventKey* event ) { bool hook = false; switch( event->keyval ) { case GDK_KEY_Escape : hook = true; m_tree_shown.get_selection()->unselect_all(); m_tree_hidden.get_selection()->unselect_all(); break; case GDK_KEY_Right : slot_delete(); break; case GDK_KEY_Left : slot_add(); break; } m_sig_key_release.emit( event ); if( hook ) return true; return Gtk::Dialog::on_key_release_event( event ); } // // 上端へ移動 // void SelectItemPref::slot_top() { Gtk::TreeModel::Children children = m_store_shown->children(); if( children.empty() ) return; // 移動先のイテレータ Gtk::TreeIter upper_it = children.begin(); std::vector< Gtk::TreePath > selection_path = m_tree_shown.get_selection()->get_selected_rows(); std::vector< Gtk::TreePath >::iterator it = selection_path.begin(); while( it != selection_path.end() ) { Gtk::TreeIter src_it = m_store_shown->get_iter( *it ); Gtk::TreeIter dst_it = upper_it; // 元と先が同じでない if( src_it != dst_it ) { // 参照渡しなので書き換えられてしまう m_store_shown->move( src_it, dst_it ); upper_it = src_it; } // 移動先の位置を下げる if( upper_it != children.end() ) ++upper_it; ++it; } // フォーカスを移す set_focus( m_tree_shown ); } // // 上へ移動 // void SelectItemPref::slot_up() { Gtk::TreeModel::Children children = m_store_shown->children(); if( children.empty() ) return; // 上限のイテレータ Gtk::TreeIter upper_it = children.begin(); std::vector< Gtk::TreePath > selection_path = m_tree_shown.get_selection()->get_selected_rows(); std::vector< Gtk::TreePath >::iterator it = selection_path.begin(); while( it != selection_path.end() ) { Gtk::TreePath src = *it; Gtk::TreeIter src_it = m_store_shown->get_iter( src ); // 限度位置に達していなければ入れ替え if( src_it != upper_it ) { Gtk::TreePath dst( src ); if( dst.prev() ) { Gtk::TreeIter dst_it = m_store_shown->get_iter( dst ); m_store_shown->iter_swap( src_it, dst_it ); } } // 上限に達していたら次の限度位置を下げる else if( upper_it != children.end() ) ++upper_it; ++it; } // フォーカスを移す set_focus( m_tree_shown ); } // // 下へ移動 // void SelectItemPref::slot_down() { Gtk::TreeModel::Children children = m_store_shown->children(); if( children.empty() ) return; // 下限のイテレータ Gtk::TreeIter bottom_it = --children.end(); std::vector< Gtk::TreePath > selection_path = m_tree_shown.get_selection()->get_selected_rows(); std::vector< Gtk::TreePath >::reverse_iterator it = selection_path.rbegin(); while( it != selection_path.rend() ) { Gtk::TreePath src = *it; Gtk::TreeIter src_it = m_store_shown->get_iter( src ); // 限度位置に達していなければ入れ替え if( src_it != bottom_it ) { Gtk::TreePath dst( src ); dst.next(); Gtk::TreeIter dst_it = m_store_shown->get_iter( dst ); if( dst_it ) { m_store_shown->iter_swap( src_it, dst_it ); } } // 下限に達していたら次の限度位置を上げる else if( bottom_it != children.begin() ) --bottom_it; ++it; } // フォーカスを移す set_focus( m_tree_shown ); } // // 下端へ移動 // void SelectItemPref::slot_bottom() { Gtk::TreeModel::Children children = m_store_shown->children(); if( children.empty() ) return; // 移動先のイテレータ Gtk::TreeIter bottom_it = children.end(); std::vector< Gtk::TreePath > selection_path = m_tree_shown.get_selection()->get_selected_rows(); std::vector< Gtk::TreePath >::reverse_iterator it = selection_path.rbegin(); while( it != selection_path.rend() ) { Gtk::TreeIter src_it = m_store_shown->get_iter( *it ); Gtk::TreeIter dst_it = bottom_it; // 元と先が同じでない if( src_it != dst_it ) { // 参照渡しなので書き換えられてしまう m_store_shown->move( src_it, dst_it ); bottom_it = dst_it; } // 移動先の位置を上げる if( bottom_it != children.begin() ) --bottom_it; ++it; } // フォーカスを移す set_focus( m_tree_shown ); } // // 削除ボタン // void SelectItemPref::slot_delete() { std::vector< Gtk::TreePath > selection_path = m_tree_shown.get_selection()->get_selected_rows(); // 選択したアイテムが無い場合はフォーカスだけ移して出る if( selection_path.empty() ) { set_focus( m_tree_hidden ); return; } std::list< Gtk::TreeRow > erase_rows; bool set_cursor = true; // 一番上の選択項目にカーソルをセット std::vector< Gtk::TreePath >::iterator it = selection_path.begin(); while( it != selection_path.end() ) { Gtk::TreePath path = *it; Gtk::TreeRow row = *m_store_shown->get_iter( path ); if( row ) { Glib::ustring name = row[ m_columns_shown.m_column_text ]; // 非表示項目に追加 if( name != ITEM_NAME_SEPARATOR ) { append_hidden( name, set_cursor ); set_cursor = false; } // 表示項目から削除するリストに追加 erase_rows.push_back( row ); } ++it; } // 追加と削除を同時にすると滅茶苦茶になるので、分けて削除する std::list< Gtk::TreeRow >::iterator erase_it = erase_rows.begin(); while( erase_it != erase_rows.end() ) { m_store_shown->erase( *erase_it ); ++erase_it; } // フォーカスを移す set_focus( m_tree_hidden ); } // // 追加ボタン // void SelectItemPref::slot_add() { std::vector< Gtk::TreePath > selection_path = m_tree_hidden.get_selection()->get_selected_rows(); // 選択したアイテムが無い場合はフォーカスだけ移して出る if( selection_path.empty() ) { set_focus( m_tree_shown ); return; } std::list< Gtk::TreeRow > erase_rows; bool set_cursor = true; // 一番上の選択項目にカーソルをセット // 選択したアイテムを追加 std::vector< Gtk::TreePath >::iterator it = selection_path.begin(); while( it != selection_path.end() ) { Gtk::TreeRow row = *m_store_hidden->get_iter( *it ); if( row ) { Glib::ustring name = row[ m_columns_hidden.m_column_text ]; // 表示項目に追加 append_shown( name, set_cursor ); set_cursor = false; // 非表示項目から削除するリストに追加 if( name != ITEM_NAME_SEPARATOR ) erase_rows.push_back( row ); } ++it; } // 追加と削除を同時にすると滅茶苦茶になるので、分けて削除する std::list< Gtk::TreeRow >::iterator erase_it = erase_rows.begin(); while( erase_it != erase_rows.end() ) { m_store_hidden->erase( *erase_it ); ++erase_it; } // フォーカスを移す set_focus( m_tree_shown ); } jdim-0.7.0/src/skeleton/selectitempref.h000066400000000000000000000103731417047150700202520ustar00rootroot00000000000000// ライセンス: GPL2 // 表示項目設定 #ifndef _SELECTITEMPREF_H #define _SELECTITEMPREF_H #include "prefdiag.h" #include #include namespace SKELETON { typedef struct { std::string name; Glib::RefPtr< Gdk::Pixbuf > icon; } DEFAULT_DATA; class SelectItemPref : public SKELETON::PrefDiag { // デフォルトの値を格納( 項目名, アイコンID, 有効/無効 ) std::list< DEFAULT_DATA > m_list_default_data; // ColumnRecord class TreeModelColumns : public Gtk::TreeModel::ColumnRecord { public: TreeModelColumns() { add( m_column_icon ); add( m_column_text ); } Gtk::TreeModelColumn< Glib::RefPtr< Gdk::Pixbuf > > m_column_icon; Gtk::TreeModelColumn< Glib::ustring > m_column_text; }; // 表示項目 Gtk::TreeView m_tree_shown; Glib::RefPtr< Gtk::ListStore > m_store_shown; TreeModelColumns m_columns_shown; Gtk::ScrolledWindow m_scroll_shown; // ボタン(縦移動) Gtk::Button m_button_top; Gtk::Button m_button_up; Gtk::Button m_button_down; Gtk::Button m_button_bottom; Gtk::VButtonBox m_vbuttonbox_v; // ボタン(横移動) Gtk::Button m_button_delete; Gtk::Button m_button_add; Gtk::VButtonBox m_vbuttonbox_h; // ボタン(アクション) Gtk::Button m_button_default; Gtk::VButtonBox m_vbuttonbox_action; // まとめ( m_vbuttonbox_* ) Gtk::VBox m_vbox; // 非表示項目 Gtk::TreeView m_tree_hidden; Glib::RefPtr< Gtk::ListStore > m_store_hidden; TreeModelColumns m_columns_hidden; Gtk::ScrolledWindow m_scroll_hidden; // まとめ( m_tree.shown, m_vbox, m_tree_hidden ) Gtk::HBox m_hbox; // キーフック用 typedef sigc::signal< bool, GdkEventKey* > SIG_KEY_PRESS; typedef sigc::signal< bool, GdkEventKey* > SIG_KEY_RELEASE; SIG_KEY_PRESS m_sig_key_press; SIG_KEY_RELEASE m_sig_key_release; public: SelectItemPref( Gtk::Window* parent, const std::string& url ); ~SelectItemPref() noexcept = default; private: // widgetのパック void pack_widgets(); // フォーカスが外れたTreeViewから項目の選択をなくす bool slot_focus_in_shown( GdkEventFocus* event ); bool slot_focus_in_hidden( GdkEventFocus* event ); // 項目名でデフォルトデータからアイコンを取得 Glib::RefPtr< Gdk::Pixbuf > get_icon( const Glib::ustring& name ); protected: // 表示項目のクリア void clear(); // デフォルトデータを追加 void append_default_pair( const Glib::ustring& name, const Glib::RefPtr< Gdk::Pixbuf > icon = Glib::RefPtr< Gdk::Pixbuf >() ); // 文字列を元に行を作成 void append_rows( const std::string& str ); // 全ての有効な項目を文字列で取得 std::string get_items() const; // 表示項目に指定した項目を追加 Gtk::TreeRow append_shown( const std::string& name, const bool set_cursor ); // 非表示項目に指定した項目を追加 Gtk::TreeRow append_hidden( const std::string& name, const bool set_cursor ); // 非表示項目から指定した項目を削除 void erase_hidden( const std::string& name ); // KeyPressのフック bool on_key_press_event( GdkEventKey* event ) override; // KeyReleaseのフック bool on_key_release_event( GdkEventKey* event ) override; // 最上位へ移動 void slot_top(); // 上へ移動 void slot_up(); // 下へ移動 void slot_down(); // 最下位へ移動 void slot_bottom(); // 削除ボタン void slot_delete(); // 追加ボタン void slot_add(); // デフォルトボタン virtual void slot_default() = 0; // 適用ボタン void slot_apply_clicked() override { slot_ok_clicked(); } }; } #endif jdim-0.7.0/src/skeleton/tablabel.cpp000066400000000000000000000114601417047150700173360ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "tablabel.h" #include "icons/iconmanager.h" #include "config/globalconf.h" #include "dndmanager.h" using namespace SKELETON; enum { SPACING_LABEL = 3 // アイコンとラベルの間のスペース }; TabLabel::TabLabel( const std::string& url ) : m_url( url ) , m_id_icon( ICON::NUM_ICONS ) { #ifdef _DEBUG std::cout << "TabLabel::TabLabel " << m_url << std::endl; #endif // 背景透過 set_visible_window( false ); add_events( Gdk::POINTER_MOTION_MASK ); add_events( Gdk::LEAVE_NOTIFY_MASK ); add_events( Gdk::SMOOTH_SCROLL_MASK ); // マウスホイールによるタブの切り替え add( m_hbox ); m_hbox.pack_start( m_label, Gtk::PACK_SHRINK ); show_all_children(); } // アイコンセット void TabLabel::set_id_icon( const int id ) { if( ! CONFIG::get_show_tab_icon() ) return; if( m_id_icon == id ) return; if( id == ICON::NONE ) return; if( !m_image ){ m_image = std::make_unique(); m_hbox.remove( m_label ); m_hbox.set_spacing( SPACING_LABEL ); m_hbox.pack_start( *m_image, Gtk::PACK_SHRINK ); m_hbox.pack_start( m_label, Gtk::PACK_SHRINK ); show_all_children(); } #ifdef _DEBUG std::cout << "TabLabel::set_icon " << m_fulltext << " id = " << id << std::endl; #endif m_id_icon = id; m_image->set( ICON::get_icon( id ) ); } // タブの文字列の文字数がlngになるようにリサイズする void TabLabel::resize_tab( const unsigned int lng ) { Glib::ustring ulabel( m_fulltext ); const unsigned int lng_org = ulabel.length(); ulabel.resize( lng ); if( lng < lng_org ) ulabel += "..."; m_label.set_text( ulabel ); } // // D&D設定 // void TabLabel::set_dragable( bool dragable, int button ) { if( dragable ){ std::vector< Gtk::TargetEntry > targets; targets.push_back( Gtk::TargetEntry( DNDTARGET_FAVORITE, Gtk::TARGET_SAME_APP, 0 ) ); targets.push_back( Gtk::TargetEntry( DNDTARGET_TAB, Gtk::TARGET_SAME_APP, 0 ) ); // ドラッグ開始側にする switch( button ){ case 1: drag_source_set( targets, Gdk::BUTTON1_MASK ); break; case 2: drag_source_set( targets, Gdk::BUTTON2_MASK ); break; case 3: drag_source_set( targets, Gdk::BUTTON3_MASK ); break; default: return; } } else{ drag_source_unset(); drag_dest_unset(); } } // // 本体の横幅 - ラベルの横幅 // int TabLabel::get_label_margin() const { int label_margin; int x_pad, y_pad; m_label.get_padding( x_pad, y_pad ); label_margin = x_pad*2 + m_hbox.get_spacing() + m_hbox.get_border_width()*2 + get_border_width()*2; if( CONFIG::get_show_tab_icon() && m_id_icon < ICON::NUM_ICONS ) label_margin += ICON::get_icon( m_id_icon )->get_width(); #ifdef _DEBUG std::cout << "image_w = " << ( m_image ? m_image->get_allocation().get_width() : 0 ) << " label_w = " << m_label.get_allocation().get_width() << " alloc_w = " << get_allocation().get_width() << " label_margine = " << label_margin << std::endl; #endif return label_margin; } // // マウスが動いた // bool TabLabel::on_motion_notify_event( GdkEventMotion* event ) { const bool ret = Gtk::EventBox::on_motion_notify_event( event ); m_sig_tab_motion_event.emit(); return ret; } // // マウスが出た // bool TabLabel::on_leave_notify_event( GdkEventCrossing* event ) { #ifdef _DEBUG std::cout << "TabLabel::leave\n"; #endif const bool ret = Gtk::EventBox::on_leave_notify_event( event ); m_sig_tab_leave_event.emit(); return ret; } // // ドラッグ開始 // void TabLabel::on_drag_begin( const Glib::RefPtr< Gdk::DragContext >& context ) { #ifdef _DEBUG std::cout << "TabLabel::on_drag_begin " << m_fulltext << std::endl; #endif m_sig_tab_drag_begin.emit(); Gtk::EventBox::on_drag_begin( context ); } // // D&Dで受信側がデータ送信を要求してきた // void TabLabel::on_drag_data_get( const Glib::RefPtr& context, Gtk::SelectionData& selection_data, guint info, guint time ) { #ifdef _DEBUG std::cout << "TabLabel::on_drag_data_get target = " << selection_data.get_target() << " " << m_fulltext << std::endl;; #endif Gtk::EventBox::on_drag_data_get( context, selection_data, info, time ); m_sig_tab_drag_data_get( selection_data ); } // // ドラッグ終了 // void TabLabel::on_drag_end( const Glib::RefPtr< Gdk::DragContext >& context ) { #ifdef _DEBUG std::cout << "TabLabel::on_drag_end " << m_fulltext << std::endl;; #endif Gtk::EventBox::on_drag_end( context ); m_sig_tab_drag_end.emit(); } jdim-0.7.0/src/skeleton/tablabel.h000066400000000000000000000065231417047150700170070ustar00rootroot00000000000000// ライセンス: GPL2 #ifndef _TABLABEL_H #define _TABLABEL_H #include #include #include namespace SKELETON { // マウス typedef sigc::signal< void > SIG_TAB_MOTION_EVENT; typedef sigc::signal< void > SIG_TAB_LEAVE_EVENT; // D&D typedef sigc::signal< void > SIG_TAB_DRAG_BEGIN; typedef sigc::signal< void, Gtk::SelectionData& > SIG_TAB_DRAG_DATA_GET; typedef sigc::signal< void > SIG_TAB_DRAG_END; class TabLabel : public Gtk::EventBox { SIG_TAB_MOTION_EVENT m_sig_tab_motion_event; SIG_TAB_LEAVE_EVENT m_sig_tab_leave_event; SIG_TAB_DRAG_BEGIN m_sig_tab_drag_begin; SIG_TAB_DRAG_DATA_GET m_sig_tab_drag_data_get; SIG_TAB_DRAG_END m_sig_tab_drag_end; int m_x{}; int m_y{}; int m_width{}; int m_height{}; std::string m_url; Gtk::HBox m_hbox; int m_id_icon; Gtk::Label m_label; // アイコン画像 std::unique_ptr m_image; // ラベルに表示する文字列の全体 std::string m_fulltext; public: explicit TabLabel( const std::string& url ); ~TabLabel() noexcept = default; SIG_TAB_MOTION_EVENT sig_tab_motion_event(){ return m_sig_tab_motion_event; } SIG_TAB_LEAVE_EVENT sig_tab_leave_event(){ return m_sig_tab_leave_event; } SIG_TAB_DRAG_BEGIN sig_tab_drag_begin() { return m_sig_tab_drag_begin; } SIG_TAB_DRAG_DATA_GET sig_tab_drag_data_get() { return m_sig_tab_drag_data_get; } SIG_TAB_DRAG_END sig_tab_drag_end() { return m_sig_tab_drag_end; } int get_tab_x() const { return m_x; } int get_tab_y() const { return m_y; } int get_tab_width() const { return m_width; } int get_tab_height() const { return m_height; } void set_tab_x( const int x ){ m_x = x; } void set_tab_y( const int y ){ m_y = y; } void set_tab_width( const int width ){ m_width = width; } void set_tab_height( const int height ){ m_height = height; } Pango::FontDescription get_label_font_description(){ return m_label.get_pango_context()->get_font_description(); } const std::string& get_url() const { return m_url; } void set_dragable( bool dragable, int button ); // 本体の横幅 - ラベルの横幅 int get_label_margin() const; // カットしていない全体の文字列 const std::string& get_fulltext() const { return m_fulltext; } void set_fulltext( const std::string& label ){ m_fulltext = label; } // アイコンセット void set_id_icon( const int id ); int get_id_icon() const { return m_id_icon; } // タブの文字列の文字数をlngにセット void resize_tab( const unsigned int lng ); private: bool on_motion_notify_event( GdkEventMotion* event ) override; bool on_leave_notify_event( GdkEventCrossing* event ) override; void on_drag_begin( const Glib::RefPtr< Gdk::DragContext >& context ) override; void on_drag_data_get( const Glib::RefPtr< Gdk::DragContext >& context, Gtk::SelectionData& selection_data, guint info, guint time ) override; void on_drag_end( const Glib::RefPtr< Gdk::DragContext>& context ) override; }; } #endif jdim-0.7.0/src/skeleton/tabnote.cpp000066400000000000000000000372001417047150700172240ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG //#define _DEBUG_RESIZE_TAB #include "jddebug.h" #include "gtkmmversion.h" #include "dragnote.h" #include "tabnote.h" #include "tablabel.h" #include "config/globalconf.h" #include "jdlib/miscgtk.h" #include "session.h" #include "dndmanager.h" using namespace SKELETON; // ダミーWidgetを作成してtabにappend ( 表示はされない ) class DummyWidget : public Gtk::Widget { public: DummyWidget() : Gtk::Widget() { set_has_window( false ); } ~DummyWidget() noexcept = default; }; ////////////////////////////////////////////// TabNotebook::TabNotebook( DragableNoteBook* parent ) : Gtk::Notebook() , m_parent( parent ) , m_layout_tab{ create_pango_layout( "" ) } { set_border_width( 0 ); set_size_request( 1, -1 ); // これが無いと最大化を解除したときにウィンドウが勝手にリサイズする add_events( Gdk::POINTER_MOTION_MASK ); add_events( Gdk::LEAVE_NOTIFY_MASK ); add_events( Gdk::SCROLL_MASK ); // DnD設定 // ドロップ側に設定する drag_source_unset(); drag_dest_unset(); std::vector< Gtk::TargetEntry > targets; targets.push_back( Gtk::TargetEntry( DNDTARGET_TAB, Gtk::TARGET_SAME_APP, 0 ) ); drag_dest_set( targets, Gtk::DEST_DEFAULT_MOTION | Gtk::DEST_DEFAULT_DROP ); m_tab_mrg = get_margin_start(); if( m_tab_mrg <= 0 ) { m_tab_mrg = get_style_context()->get_margin().get_left(); } m_pre_width = get_width(); } TabNotebook::~TabNotebook() noexcept = default; // // クロック入力 // void TabNotebook::clock_in() { // Gtk::NoteBook は on_configure_event() と on_window_state_event() をキャッチ出来ない // かつ、 on_size_allocate() は最大化、最小化の時に無反応なので // 応急処置としてタイマーの中でサイズが変更したか調べて // 変わっていたらタブ幅を調整する if( ! m_fixtab && get_n_pages() && m_pre_width != get_width() && ! SESSION::is_booting() && ! SESSION::is_quitting() ){ calc_tabsize(); adjust_tabwidth(); } } // // サイズ変更 // void TabNotebook::on_size_allocate( Gtk::Allocation& allocation ) { clock_in(); // タブ再描画の反応を良くする Gtk::Notebook::on_size_allocate( allocation ); } int TabNotebook::append_tab( Widget& tab ) { #ifdef _DEBUG std::cout << "TabNotebook::append_tab\n"; #endif // ダミーWidgetを作成してtabにappend (表示はされない ) DummyWidget* dummypage = Gtk::manage( new DummyWidget ); return append_page( *dummypage , tab ); } int TabNotebook::insert_tab( Widget& tab, int page ) { #ifdef _DEBUG std::cout << "TabNotebook::insert_tab position = " << page << std::endl; #endif // ダミーWidgetを作成してtabにappend (表示はされない ) DummyWidget* dummypage = Gtk::manage( new DummyWidget ); return insert_page( *dummypage, tab, page ); } void TabNotebook::remove_tab( const int page, const bool adjust_tab ) { #ifdef _DEBUG std::cout << "TabNotebook::remove_tab page = " << page << std::endl; #endif // ダミーWidgetはGtk::manage()の効果でdeleteされる remove_page( page ); if( adjust_tab ) adjust_tabwidth(); } void TabNotebook::reorder_child( int page1, int page2 ) { Gtk::Notebook::reorder_child( *get_nth_page( page1 ), page2 ); } // タブ取得 SKELETON::TabLabel* TabNotebook::get_tablabel( int page ) { return dynamic_cast< SKELETON::TabLabel* >( get_tab_label( *get_nth_page( page ) ) ); } // // マウスの下にあるタブの番号を取得 // // タブ上では無いときは-1を返す // マウスがタブの右側にある場合はページ数( get_n_pages() )を返す // int TabNotebook::get_page_under_mouse() { int x, y; Gdk::Rectangle rect = get_allocation(); MISC::get_pointer_at_window( get_window(), x, y ); #ifdef _DEBUG std::cout << "TabNotebook::get_page_under_mouse x = " << x << " y = " << y << std::endl; #endif if( y < rect.get_y() || y > rect.get_y() + rect.get_height() ) return -1; calc_tabsize(); const int pages = get_n_pages(); int ret = pages; for( int i = 0; i < pages; ++i ){ SKELETON::TabLabel* tab = get_tablabel( i ); if( tab ){ int tab_x = tab->get_tab_x(); int tab_w = tab->get_tab_width(); if( tab_x < 0 ) continue; #ifdef _DEBUG std::cout << "TabNotebook::get_page_under_mouse page = " << i << " x = " << tab_x << " w = " << tab_w << std::endl; #endif if( x < tab_x ){ ret = -1; break; } if( x >= tab_x && x <= tab_x + tab_w ){ ret = i; break; } } } #ifdef _DEBUG std::cout << "TabNotebook::get_page_under_mouse ret = " << ret << std::endl; #endif return ret; } // // タブの文字列取得 // const std::string& TabNotebook::get_tab_fulltext( int page ) { SKELETON::TabLabel* tablabel = get_tablabel( page ); if( ! tablabel ) return m_str_empty; return tablabel->get_fulltext(); } // // タブに文字列をセットとタブ幅調整 // void TabNotebook::set_tab_fulltext( const std::string& str, int page ) { #ifdef _DEBUG std::cout << "TabNotebook::set_tab_fulltext page = " << page << " " << str << std::endl; #endif SKELETON::TabLabel* tablabel = get_tablabel( page ); if( tablabel && str != tablabel->get_fulltext() ){ tablabel->set_fulltext( str ); tablabel->set_tooltip_text( str ); if( m_fixtab ) tablabel->resize_tab( str.length() ); else adjust_tabwidth(); } } // // 各タブのサイズと座標を取得 // void TabNotebook::calc_tabsize() { #ifdef _DEBUG std::cout << "TabNotebook::calc_tabsize\n"; #endif // gtk3は実装の詳細がバージョンによって異なるためタブの代わりにラベルの領域を取得する const int n_pages = get_n_pages(); // ラベルの領域とタブの領域のオフセットを概算する // GTKテーマが変更されるとオフセットが変わる可能性があるので毎回計算する // XXX: この修正はラベルの左右の余白の大きさが同じであることを前提とする int offset = 0; if( n_pages > 1 ) { const auto* tab1 = get_tablabel( 0 ); for( int i = 1; i < n_pages; ++i ) { const auto* const tab2 = get_tablabel( i ); if( tab1 && tab2 && tab1->get_mapped() && tab2->get_mapped() ) { const auto alloc1 = tab1->get_allocation(); const auto alloc2 = tab2->get_allocation(); offset = alloc2.get_x() - ( alloc1.get_x() + alloc1.get_width() ); #ifdef _DEBUG std::cout << "computed offset = " << offset << std::endl; #endif break; } tab1 = tab2; } } for( int i = 0; i < n_pages; ++i ) { auto* const tab_label = get_tablabel( i ); if( tab_label ) { int tab_x = -1; int tab_y = -1; int tab_w = -1; int tab_h = -1; if( tab_label->get_mapped() ) { Gdk::Rectangle rect = tab_label->get_allocation(); tab_x = rect.get_x() - ( offset / 2 ); tab_y = rect.get_y(); tab_w = rect.get_width() + offset; tab_h = rect.get_height(); m_tab_mrg = 0; } #ifdef _DEBUG std::cout << "page = " << i << " x = " << tab_x << " w = " << tab_w << " mrg = " << m_tab_mrg << std::endl; #endif tab_label->set_tab_x( tab_x ); tab_label->set_tab_y( tab_y ); tab_label->set_tab_width( tab_w ); tab_label->set_tab_height( tab_h ); } } } // // タブ幅調整 // bool TabNotebook::adjust_tabwidth() { // 起動中とシャットダウン中は処理しない if( SESSION::is_booting() ) return false; if( SESSION::is_quitting() ) return false; const int mrg_notebook = 30; const int pages = get_n_pages(); if( ! pages ) return false; // layoutにラベルのフォントをセットする SKELETON::TabLabel* tab = get_tablabel( 0 ); if( ! tab ) return false; m_layout_tab->set_font_description( tab->get_label_font_description() ); const int label_margin = tab->get_label_margin() + m_tab_mrg; m_pre_width = get_width(); const int width_notebook = m_pre_width - mrg_notebook; int avg_width_tab = (int)( (double)width_notebook / MAX( 3, pages ) ); // タブ幅の平均値 if( avg_width_tab < label_margin ){ avg_width_tab = label_margin; } #ifdef _DEBUG_RESIZE_TAB std::cout << "TabNotebook::adjust_tabwidth\n" << "width_notebook = " << width_notebook << " page = " << pages << std::endl << "avg_width_tab = " << avg_width_tab << " tab_mrg = " << m_tab_mrg << " label_margin " << label_margin << std::endl; #endif int label_width_org; // 変更前のタブの文字数 int label_width; // 変更後のタブの文字数 int width_total = 0; int tab_width, max_width; for( int i = 0; i < pages; ++i ){ tab = get_tablabel( i ); if( tab ){ Glib::ustring ulabel( tab->get_fulltext() ); Glib::ustring ulabel_org( ulabel ); bool cut_suffix = false; label_width_org = label_width = ulabel.length(); tab_width = -1; // タブ幅を未計算状態に初期化 max_width = avg_width_tab * ( i + 1 ) - width_total; // 最大値 ( 収まらないときマイナスになる ) // 長すぎるタブの文字列は表示しないよう、あらかじめ最大値を256に制限する if( label_width > 256 ){ label_width = 256; ulabel.resize( label_width ); } // タブの幅を最大値以下に縮める while( label_width > CONFIG::get_tab_min_str() ){ if( ! cut_suffix && label_width < label_width_org ){ cut_suffix = true; // 文字列を切り詰めたら "..." を付与する ulabel.append( "..." ); } m_layout_tab->set_text( ulabel ); int width = m_layout_tab->get_pixel_logical_extents().get_width() + label_margin; #ifdef _DEBUG_RESIZE_TAB std::cout << "s " << i << " " << width << " / " << max_width << " + " << width_total << " lng = " << label_width << " : " << ulabel << std::endl; #endif if( width < max_width ){ // 最大値以下 tab_width = width; // タブ幅を保存 break; } // 最大値を越えていたら、概算で収まるように縮める int n = label_width - (int)( (double)label_width * max_width / width ); if( n < 1 ) n = 1; if( label_width - n < CONFIG::get_tab_min_str() ){ n = label_width - CONFIG::get_tab_min_str(); } label_width -= n; ulabel.erase( label_width, n ); // 末尾のn文字を消す } // 横をはみださないようにタブ幅を延ばしていく for(;;){ if( label_width >= label_width_org ) break; // 全て収まった if( max_width < 0 ) break; // 確実に収まらない ulabel.insert( label_width, 1, ulabel_org[ label_width ] ); // 末尾の1文字を戻す ++label_width; m_layout_tab->set_text( ulabel ); int width = m_layout_tab->get_pixel_logical_extents().get_width() + label_margin; #ifdef _DEBUG_RESIZE_TAB std::cout << "w " << i << " " << width << " / " << max_width << " + " << width_total << " lng = " << label_width << " : " << ulabel << std::endl; #endif // 最大値を越えたらひとつ戻してbreak; if( width > max_width ){ --label_width; ulabel.erase( label_width, 1 ); // 末尾の1文字を消す break; } tab_width = width; // 最大値を超えていないタブ幅を保存 } // タブ幅を確定する if( tab_width < 0 ){ m_layout_tab->set_text( ulabel ); tab_width = m_layout_tab->get_pixel_logical_extents().get_width() + label_margin; #ifdef _DEBUG_RESIZE_TAB std::cout << "f " << i << " " << tab_width << " / " << max_width << " + " << width_total << " lng = " << label_width << " : " << ulabel << std::endl; #endif } width_total += tab_width; tab->resize_tab( label_width ); } } // 枠の再描画 m_parent->queue_draw(); return true; } // signal_button_press_event と signal_button_release_event は emit されない // ときがあるので自前でemitする bool TabNotebook::on_button_press_event( GdkEventButton* event ) { #ifdef _DEBUG std::cout << "TabNotebook::on_button_press_event\n"; #endif if( m_sig_button_press.emit( event ) ) return true; #ifdef _DEBUG std::cout << "Gtk::Notebook::on_button_press_event\n"; #endif return Gtk::Notebook::on_button_press_event( event ); } bool TabNotebook::on_button_release_event( GdkEventButton* event ) { #ifdef _DEBUG std::cout << "TabNotebook::on_button_release_event\n"; #endif if( m_sig_button_release.emit( event ) ) return true; #ifdef _DEBUG std::cout << "Gtk::Notebook::on_button_release_event\n"; #endif return Gtk::Notebook::on_button_release_event( event ); } // // マウスが動いた // bool TabNotebook::on_motion_notify_event( GdkEventMotion* event ) { #ifdef _DEBUG std::cout << "TabNotebook::on_motion_notify_event\n"; #endif m_sig_tab_motion_event.emit(); return Gtk::Notebook::on_motion_notify_event( event ); } // // マウスが出た // bool TabNotebook::on_leave_notify_event( GdkEventCrossing* event ) { #ifdef _DEBUG std::cout << "TabNotebook::leave\n"; #endif m_sig_tab_leave_event.emit(); return Gtk::Notebook::on_leave_notify_event( event ); } // // マウスホイールを回した // bool TabNotebook::on_scroll_event( GdkEventScroll* event ) { if( ! CONFIG::get_switchtab_wheel() ) return true; #ifdef _DEBUG std::cout << "TabNotebook::scroll\n"; #endif if( m_sig_scroll_event.emit( event ) ) return true; return Gtk::Notebook::on_scroll_event( event ); } // // ドラッグ中にマウスを動かした // bool TabNotebook::on_drag_motion( const Glib::RefPtr& context, int x, int y, guint time) { #ifdef _DEBUG std::cout << "Gtk::Notebook::on_drag_motion x = " << x << " y = " << y << std::endl; #endif int tab_x = -1; int tab_y = -1; int tab_w = -1; int page = get_page_under_mouse(); if( page >= 0 ){ if( page >= get_n_pages() ) page = get_n_pages() -1; SKELETON::TabLabel* tab = get_tablabel( page ); if( tab ){ tab_x = tab->get_tab_x(); tab_y = tab->get_tab_y(); tab_w = tab->get_tab_width(); #ifdef _DEBUG std::cout << "page = " << page << " tab_x = " << tab_x << " tab_y = " << tab_y << " tab_w " << tab_w << std::endl; #endif } } m_sig_tab_drag_motion( page, tab_x, tab_y, tab_w ); // on_drag_motion をキャンセルしないとDnD中にタブが勝手に切り替わる( gtknotebook.c をハック ) return true; } jdim-0.7.0/src/skeleton/tabnote.h000066400000000000000000000071341417047150700166740ustar00rootroot00000000000000// ライセンス: GPL2 // // DragableNoteBookを構成するタブ表示用の Notebook // #ifndef _TABNOTE_H #define _TABNOTE_H #include namespace SKELETON { class DragableNoteBook; class TabLabel; struct Alloc_NoteBook; // タブのクリック typedef sigc::signal< bool, GdkEventButton* > SIG_BUTTON_PRESS; typedef sigc::signal< bool, GdkEventButton* > SIG_BUTTON_RELEASE; // マウス移動 typedef sigc::signal< void > SIG_TAB_MOTION_EVENT; typedef sigc::signal< void > SIG_TAB_LEAVE_EVENT; // ホイール回転 typedef sigc::signal< bool, GdkEventScroll* > SIG_SCROLL_EVENT; // D&D typedef sigc::signal< void, const int, const int, const int, const int > SIG_TAB_DRAG_MOTION; // タブ用の Notebook class TabNotebook : public Gtk::Notebook { SIG_BUTTON_PRESS m_sig_button_press; SIG_BUTTON_RELEASE m_sig_button_release; SIG_TAB_MOTION_EVENT m_sig_tab_motion_event; SIG_TAB_LEAVE_EVENT m_sig_tab_leave_event; SIG_TAB_DRAG_MOTION m_sig_tab_drag_motion; SIG_SCROLL_EVENT m_sig_scroll_event; DragableNoteBook* m_parent; // タブ幅計算用layout Glib::RefPtr< Pango::Layout > m_layout_tab; int m_pre_width; bool m_fixtab{}; int m_tab_mrg; const std::string m_str_empty; public: SIG_BUTTON_PRESS sig_button_press(){ return m_sig_button_press; } SIG_BUTTON_RELEASE sig_button_release(){ return m_sig_button_release; } SIG_TAB_MOTION_EVENT sig_tab_motion_event(){ return m_sig_tab_motion_event; } SIG_TAB_LEAVE_EVENT sig_tab_leave_event(){ return m_sig_tab_leave_event; } SIG_TAB_DRAG_MOTION sig_tab_drag_motion(){ return m_sig_tab_drag_motion; } SIG_SCROLL_EVENT sig_scroll_event(){ return m_sig_scroll_event; } explicit TabNotebook( DragableNoteBook* parent ); ~TabNotebook() noexcept; void clock_in(); // タブの幅を固定するか(デフォルト false ); void set_fixtab( bool fix ){ m_fixtab = fix; } int append_tab( Widget& tab ); int insert_tab( Widget& tab, int page ); void remove_tab( const int page, const bool adjust_tab ); void reorder_child( int page1, int page2 ); // タブ取得 SKELETON::TabLabel* get_tablabel( int page ); // タブの文字列取得/セット const std::string& get_tab_fulltext( int page ); void set_tab_fulltext( const std::string& str, int page ); // タブ幅調整 bool adjust_tabwidth(); // マウスの下にあるタブの番号を取得 // タブ上では無いときは-1を返す // マウスがタブの右側にある場合はページ数の値を返す int get_page_under_mouse(); private: // 各タブのサイズと座標を取得 void calc_tabsize(); protected: void on_size_allocate( Gtk::Allocation& allocation ) override; // signal_button_press_event と signal_button_release_event は emit されない // ときがあるので自前でemitする bool on_button_press_event( GdkEventButton* event ) override; bool on_button_release_event( GdkEventButton* event ) override; bool on_motion_notify_event( GdkEventMotion* event ) override; bool on_leave_notify_event( GdkEventCrossing* event ) override; bool on_scroll_event( GdkEventScroll* event ) override; bool on_drag_motion( const Glib::RefPtr& context, int x, int y, guint time) override; }; } #endif jdim-0.7.0/src/skeleton/tabswitchbutton.cpp000066400000000000000000000017721417047150700210210ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "tabswitchbutton.h" using namespace SKELETON; TabSwitchButton::TabSwitchButton( DragableNoteBook* ) : Gtk::Notebook() { set_border_width( 0 ); m_arrow.set_from_icon_name( "pan-down-symbolic", Gtk::ICON_SIZE_SMALL_TOOLBAR ); m_button.add( m_arrow ); m_button.show_all_children(); m_button.set_relief( Gtk::RELIEF_NONE ); m_button.set_focus_on_click( false ); // フォーカス時にボタンの枠がはみ出さないようにする m_button.set_margin_top( 0 ); m_button.set_margin_bottom( 0 ); m_vbox.pack_start( m_button, Gtk::PACK_SHRINK ); set_show_tabs( false ); } TabSwitchButton::~TabSwitchButton() noexcept = default; void TabSwitchButton::show_button() { if( m_shown ) return; append_page( m_vbox ); show_all_children(); m_shown = true; } void TabSwitchButton::hide_button() { if( ! m_shown ) return; remove_page( m_vbox ); m_shown = false; } jdim-0.7.0/src/skeleton/tabswitchbutton.h000066400000000000000000000012371417047150700204620ustar00rootroot00000000000000// ライセンス: GPL2 // // タブの切り替えボタン // // TODO: 使われなくなったコンストラクタの引数を整理する #ifndef TABSWITCHBUTON_H #define TABSWITCHBUTON_H #include namespace SKELETON { class DragableNoteBook; class TabSwitchButton: public Gtk::Notebook { Gtk::VBox m_vbox; Gtk::Button m_button; Gtk::Image m_arrow; bool m_shown = false; public: explicit TabSwitchButton( DragableNoteBook* ); ~TabSwitchButton() noexcept; Gtk::Button& get_button(){ return m_button; } void show_button(); void hide_button(); }; } #endif jdim-0.7.0/src/skeleton/tabswitchmenu.cpp000066400000000000000000000057561417047150700204600ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "tabswitchmenu.h" #include "dragnote.h" #include "admin.h" #include "jdlib/miscutil.h" #include "icons/iconmanager.h" enum { SPACING_MENU = 3, // アイコンと項目名の間のスペース }; using namespace SKELETON; TabSwitchMenu::TabSwitchMenu( DragableNoteBook* notebook, Admin* admin ) : Gtk::Menu() , m_parentadmin( admin ) , m_parentnote( notebook ) , m_deactivated{ true } { #ifdef _DEBUG std::cout << "TabSwitchMenu::TabSwitchMenu\n"; #endif } TabSwitchMenu::~TabSwitchMenu() { #ifdef _DEBUG std::cout << "TabSwitchMenu::~TabSwitchMenu\n"; #endif } void TabSwitchMenu::remove_items() { #ifdef _DEBUG std::cout << "TabSwitchMenu::remove_items\n"; #endif for( int i = 0; i < m_size; ++i ) remove( *m_vec_items[ i ] ); } void TabSwitchMenu::append_items() { #ifdef _DEBUG std::cout << "TabSwitchMenu::append_items\n"; #endif m_size = m_parentnote->get_n_pages(); if( (int) m_vec_items.size() < m_size ){ for( int i = m_vec_items.size(); i < m_size ; ++ i ){ Gtk::Image* image = Gtk::manage( new Gtk::Image() ); m_vec_images.push_back( image ); Gtk::Label* label = Gtk::manage( new Gtk::Label() ); m_vec_labels.push_back( label ); Gtk::HBox* hbox = Gtk::manage( new Gtk::HBox() ); hbox->set_spacing( SPACING_MENU ); hbox->pack_start( *image, Gtk::PACK_SHRINK ); hbox->pack_start( *label, Gtk::PACK_SHRINK ); Gtk::MenuItem* item = Gtk::manage( new Gtk::MenuItem( *hbox ) ); item->signal_activate().connect( sigc::bind< int >( sigc::mem_fun( *m_parentadmin, &Admin::set_current_page_focus ), i ) ); m_vec_items.push_back( item ); } } for( int i = 0; i < m_size; ++i ) append( *m_vec_items[ i ] ); show_all_children(); m_deactivated = false; } void TabSwitchMenu::update_labels() { if( m_deactivated ) return; if( ! m_parentnote ) return; #ifdef _DEBUG std::cout << "TabSwitchMenu::update_labels\n"; #endif for( int i = 0; i < m_size; ++ i ){ std::string name = m_parentnote->get_tab_fulltext( i ); if( name.empty() ) name = "???"; const unsigned int maxsize = 50; m_vec_labels[ i ]->set_label( MISC::cut_str( name, maxsize ) ); } } void TabSwitchMenu::update_icons() { if( m_deactivated ) return; if( ! m_parentnote ) return; #ifdef _DEBUG std::cout << "TabSwitchMenu::update_icons\n"; #endif for( int i = 0; i < m_size; ++ i ){ const int icon = m_parentnote->get_tabicon( i ); if( icon != ICON::NONE && icon != ICON::NUM_ICONS ){ m_vec_images[ i ]->set( ICON::get_icon( icon ) ); } } } void TabSwitchMenu::deactivate() { #ifdef _DEBUG std::cout << "TabSwitchMenu::deactivate\n"; #endif m_deactivated = true; } void TabSwitchMenu::on_deactivate() { deactivate(); Gtk::Menu::on_deactivate(); } jdim-0.7.0/src/skeleton/tabswitchmenu.h000066400000000000000000000014621417047150700201130ustar00rootroot00000000000000// ライセンス: GPL2 // // タブの切り替えメニュー // #include #include namespace SKELETON { class DragableNoteBook; class Admin; class TabSwitchMenu : public Gtk::Menu { Admin* m_parentadmin; DragableNoteBook* m_parentnote; bool m_deactivated; int m_size{}; std::vector< Gtk::MenuItem* > m_vec_items; std::vector< Gtk::Label* > m_vec_labels; std::vector< Gtk::Image* > m_vec_images; public: TabSwitchMenu( DragableNoteBook* notebook, Admin* admin ); ~TabSwitchMenu(); void remove_items(); void append_items(); void update_labels(); void update_icons(); void deactivate(); protected: void on_deactivate() override; }; } jdim-0.7.0/src/skeleton/textloader.cpp000066400000000000000000000072201417047150700177420ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "textloader.h" #include "jdlib/jdiconv.h" #include "jdlib/loaderdata.h" #include "jdlib/miscmsg.h" #include "httpcode.h" #include "session.h" #include "cache.h" #include enum { SIZE_OF_RAWDATA = 1024 * 1024 }; using namespace SKELETON; TextLoader::TextLoader() : SKELETON::Loadable() { #ifdef _DEBUG std::cout << "TextLoader::TextLoader\n"; #endif } TextLoader::~TextLoader() { #ifdef _DEBUG std::cout << "TextLoader::~TextLoader\n"; #endif clear(); } void TextLoader::init() { clear(); if( m_rawdata.capacity() < SIZE_OF_RAWDATA ) { m_rawdata.reserve( SIZE_OF_RAWDATA ); } } void TextLoader::clear() { m_rawdata.clear(); m_rawdata.shrink_to_fit(); } void TextLoader::reset() { m_loaded = false; m_data = std::string(); clear(); } // // キャッシュからロード // void TextLoader::load_text() { if( get_path().empty() ) return; init(); set_code( HTTP_INIT ); receive_finish(); } // // ダウンロード開始 // void TextLoader::download_text() { #ifdef _DEBUG std::cout << "TextLoader::download_text url = " << get_url() << std::endl; #endif if( get_url().empty() ) return; if( is_loading() ) return; if( m_loaded ) return; // 読み込み済み if( ! SESSION::is_online() ){ load_text(); return; } #ifdef _DEBUG std::cout << "start loading...\n"; #endif JDLIB::LOADERDATA data; init(); create_loaderdata( data ); if( data.url.empty() ) return; if( ! start_load( data ) ) clear(); } // // ローダよりデータ受信 // void TextLoader::receive_data( const char* data, size_t size ) { if( m_rawdata.size() + size < SIZE_OF_RAWDATA ){ m_rawdata.append( data, size ); } else{ MISC::ERRMSG( "TextLoader : received failed ( BOF )\n" ); } } // // ロード完了 // void TextLoader::receive_finish() { #ifdef _DEBUG std::cout << "TextLoader::receive_finish code = " << get_str_code() << std::endl << "lng = " << m_rawdata.size() << " modified = " << get_date_modified() << std::endl; #endif // 初期化時やnot modifiedの時はキャッシュから読み込み if( ! get_path().empty() && ( get_code() == HTTP_INIT || get_code() == HTTP_NOT_MODIFIED ) ){ m_rawdata.resize( SIZE_OF_RAWDATA ); const std::size_t read_size = CACHE::load_rawdata( get_path(), &*m_rawdata.begin(), SIZE_OF_RAWDATA ); m_rawdata.resize( read_size ); #ifdef _DEBUG std::cout << "read from " << get_path() << std::endl; if( ! read_size ) std::cout << "no data in cache!\n"; #endif if( ! read_size && get_code() == HTTP_INIT ) set_date_modified( std::string() ); } // 読み込みエラー if( get_code() != HTTP_OK && get_code() != HTTP_INIT && get_code() != HTTP_NOT_MODIFIED ){ if( get_code() == HTTP_NOT_FOUND ) m_loaded = true; clear(); set_date_modified( std::string() ); parse_data(); return; } // キャッシュに保存 if( ! get_path().empty() && get_code() == HTTP_OK && ! m_rawdata.empty() ){ CACHE::save_rawdata( get_path(), m_rawdata ); #ifdef _DEBUG std::cout << "save to " << get_path() << std::endl; #endif } if( get_code() != HTTP_INIT ) m_loaded = true; set_code( HTTP_OK ); set_str_code( std::string() ); // UTF-8に変換しておく JDLIB::Iconv libiconv( "UTF-8", get_charset() ); int byte_out; m_data = libiconv.convert( &*m_rawdata.begin(), m_rawdata.size(), byte_out ); clear(); receive_cookies(); parse_data(); } jdim-0.7.0/src/skeleton/textloader.h000066400000000000000000000032021417047150700174030ustar00rootroot00000000000000// ライセンス: GPL2 // // テキストファイルの簡易ローダ // // ロードしたファイルはget_path()で示されたパスに保存される // get_path() が empty() ならば保存しない // #ifndef _TEXTLODER_H #define _TEXTLODER_H #include "loadable.h" #include namespace JDLIB { class LOADERDATA; } namespace SKELETON { class TextLoader : public SKELETON::Loadable { bool m_loaded{}; // 読み込み済みか std::string m_rawdata; std::string m_data; public: TextLoader(); ~TextLoader(); const std::string& get_data() const { return m_data; } // 一度ロードしたらreset()を呼ばない限りリロードしない void reset(); // キャッシュからロード void load_text(); // ダウンロード開始 // not modifiedの時はキャッシュから読み込む void download_text(); protected: virtual std::string get_url() const = 0; virtual std::string get_path() const = 0; virtual std::string get_charset() const = 0; // ロード用データ作成 virtual void create_loaderdata( JDLIB::LOADERDATA& data ) = 0; // ロード後に呼び出される virtual void parse_data() = 0; private: void init(); void clear(); void receive_data( const char* data, size_t size ) override; void receive_finish() override; // HTTP応答ヘッダーのクッキーを取り扱う場合は派生クラスでoverrideする virtual void receive_cookies() {} }; } #endif jdim-0.7.0/src/skeleton/toolbar.cpp000066400000000000000000000631041417047150700172340ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "toolbar.h" #include "admin.h" #include "view.h" #include "imgtoolbutton.h" #include "menubutton.h" #include "toolmenubutton.h" #include "backforwardbutton.h" #include "compentry.h" #include "dbtree/interface.h" #include "jdlib/miscutil.h" #include "icons/iconmanager.h" #include "config/globalconf.h" #include "control/controlutil.h" #include "control/controlid.h" #include "command.h" #include "prefdiagfactory.h" using namespace SKELETON; constexpr const char* ToolBar::s_css_label; ToolBar::ToolBar( Admin* admin ) : m_admin( admin ) , m_enable_slot{ true } { m_buttonbar.set_border_width( 0 ); m_buttonbar.set_icon_size( Gtk::ICON_SIZE_MENU ); m_buttonbar.set_toolbar_style( Gtk::TOOLBAR_ICONS ); // ツールバーの子ウィジェットのコンテキストメニューの配色がGTKテーマと違うことがある。 // ツールバーのcssクラスを削除しコンテキストメニューの配色を修正する。 m_buttonbar.get_style_context()->remove_class( GTK_STYLE_CLASS_TOOLBAR ); } ToolBar::~ToolBar() noexcept = default; void ToolBar::set_url( const std::string& url ) { m_url = url; if( m_button_back ) m_button_back->get_backforward_button()->set_url( m_url ); if( m_button_forward ) m_button_forward->get_backforward_button()->set_url( m_url ); } // // タブが切り替わった時にDragableNoteBook::set_current_toolbar()から呼び出される( Viewの情報を取得する ) // // virtual void ToolBar::set_view( SKELETON::View* view ) { if( ! view ) return; // slot関数を実行しない m_enable_slot = false; set_url( view->get_url() ); // ラベル表示更新 set_label( view->get_label() ); if( view->is_broken() || view->is_old() || view->is_overflow() ) set_color( view->get_color() ); // 閉じるボタンの表示更新 if( m_button_close ){ if( view->is_locked() ) m_button_close->set_sensitive( false ); else m_button_close->set_sensitive( true ); if( m_button_lock ) m_button_lock->set_active( view->is_locked() ); } if( m_button_write ) m_button_write->set_sensitive( view->is_writeable() ); if( m_entry_search ) m_entry_search->set_text( view->get_search_query() ); if( m_label_board ) m_label_board->set_text( DBTREE::board_name( get_url() ) ); m_enable_slot = true; } // // タブが切り替わった時にDragableNoteBookから呼び出される( ツールバーを表示する ) // // gtk+-2.4 辺りの古い gtk では、スレビューなどツールバーの複数が複数ある場合に // 最初からボタンバーを pack するとボタンが押せなくなる症状があったので、実際に // ツールバーが表示されるまでpackしないようにした // void ToolBar::show_toolbar() { // ボタンバーのpack if( m_buttonbar_shown && ! m_buttonbar_packed ){ if( m_searchbar_packed ) remove( *m_searchbar ); pack_start( m_buttonbar, Gtk::PACK_SHRINK ); if( m_searchbar_packed ) pack_start( *m_searchbar, Gtk::PACK_SHRINK ); show_all_children(); set_relief(); m_buttonbar_packed = true; } // 検索バーのpack if( m_searchbar_shown && ! m_searchbar_packed ){ pack_start( *m_searchbar, Gtk::PACK_SHRINK ); show_all_children(); set_relief(); m_searchbar_packed = true; } } // ボタンバー表示 void ToolBar::open_buttonbar() { // フラグだけ立てて実際に pack するのは show_toolbar() の中 // 何故そんな面倒な事をしているかは show_toolbar() の説明を参照 m_buttonbar_shown = true; } // ボタンバーを隠す void ToolBar::close_buttonbar() { m_buttonbar_shown = false; if( m_buttonbar_packed ){ remove( m_buttonbar ); show_all_children(); m_buttonbar_packed = false; } } // ボタン表示更新 void ToolBar::update_button() { unpack_buttons(); pack_buttons(); if( ! m_buttonbar_shown ) close_buttonbar(); // 進む、戻るボタンのsensitive状態を更新する set_url( m_url ); } // ボタンのアンパック void ToolBar::unpack_buttons() { std::vector< Gtk::Widget* > lists = m_buttonbar.get_children(); for( Gtk::Widget* widget : lists ) { m_buttonbar.remove( *widget ); if( dynamic_cast( widget ) ) delete widget; } } // 検索ツールバー上のボタンのアンパック void ToolBar::unpack_search_buttons() { if( ! m_searchbar ) return; std::vector< Gtk::Widget* > lists = m_searchbar->get_children(); for( Gtk::Widget* widget : lists ) { m_searchbar->remove( *widget ); if( dynamic_cast( widget ) ) delete widget; } } // ボタンのrelief指定 void ToolBar::set_relief() { std::vector< Gtk::Widget* > lists_toolbar = get_children(); for( Gtk::Widget* bar_widget : lists_toolbar ) { Gtk::Toolbar* toolbar = dynamic_cast( bar_widget ); if( ! toolbar ) continue; std::vector< Gtk::Widget* > lists = toolbar->get_children(); for( Gtk::Widget* btn_widget : lists ) { Gtk::Button* button = nullptr; Gtk::ToolButton* toolbutton = dynamic_cast( btn_widget ); if( toolbutton ) button = dynamic_cast< Gtk::Button* >( toolbutton->get_child() ); if( ! button ){ Gtk::ToolItem* toolitem = dynamic_cast( btn_widget ); if( toolitem ) button = dynamic_cast< Gtk::Button* >( toolitem->get_child() ); } if( button ){ if( CONFIG::get_flat_button() ) button->set_relief( Gtk:: RELIEF_NONE ); else button->set_relief( Gtk:: RELIEF_NORMAL ); } } } } // 区切り追加 void ToolBar::pack_separator() { Gtk::SeparatorToolItem *sep = Gtk::manage( new Gtk::SeparatorToolItem() ); // delete は unpack_buttons() で行う m_buttonbar.append( *sep ); } // 透明区切り追加 void ToolBar::pack_transparent_separator() { Gtk::SeparatorToolItem *sep = Gtk::manage( new Gtk::SeparatorToolItem() ); // delete は unpack_buttons() で行う sep->set_draw( false ); m_buttonbar.append( *sep ); } // // ツールチップ // void ToolBar::set_tooltip( Gtk::ToolItem& toolitem, const std::string& tip ) { toolitem.set_tooltip_text( tip ); } // // ラベル // Gtk::ToolItem* ToolBar::get_label() { if( ! m_tool_label ){ m_tool_label = Gtk::manage( new Gtk::ToolItem ); m_ebox_label = Gtk::manage( new Gtk::EventBox ); m_label = Gtk::manage( new Gtk::Label ); m_label->set_ellipsize( Pango::ELLIPSIZE_END ); m_label->set_xalign( 0 ); m_label->set_selectable( true ); m_ebox_label->add( *m_label ); m_ebox_label->set_visible_window( false ); m_tool_label->add( *m_ebox_label ); m_tool_label->set_expand( true ); auto context = m_label->get_style_context(); context->add_class( s_css_label ); context->add_provider( m_label_provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION ); try { m_label_provider->load_from_data( ".red:not(:selected), .red:active:not(:selected) { color: white; background-color: red; }" ".green:not(:selected), .green:active:not(:selected) { color: white; background-color: green; }" ".blue:not(:selected), .blue:active:not(:selected) { color: white; background-color: blue; }" ); } catch( Gtk::CssProviderError& err ) { #ifdef _DEBUG std::cout << "ERROR:ToolBar::get_label css fail " << err.what() << std::endl; #endif } } return m_tool_label; } void ToolBar::set_label( const std::string& label ) { if( ! m_ebox_label ) return; m_label->set_text( label ); if( m_tool_label ) set_tooltip( *m_tool_label, label ); set_color( "" ); } void ToolBar::set_color( const std::string& color ) { if( ! m_ebox_label ) return; auto context = m_label->get_style_context(); context->remove_class( "red" ); context->remove_class( "green" ); context->remove_class( "blue" ); if( ! color.empty() ) context->add_class( color ); } // 検索バー Gtk::Toolbar* ToolBar::get_searchbar() { if( ! m_searchbar ){ m_searchbar = Gtk::manage( new SKELETON::JDToolbar() ); m_searchbar->set_icon_size( Gtk::ICON_SIZE_MENU ); m_searchbar->set_toolbar_style( Gtk::TOOLBAR_ICONS ); } return m_searchbar; } // 検索バー表示 void ToolBar::open_searchbar() { if( ! m_searchbar ) return; #ifdef _DEBUG std::cout << "ToolBar::open_searchbar\n"; #endif // フラグだけ立てて実際に pack するのは show_toolbar() の中 // 何故そんな面倒な事をしているかは show_toolbar() の説明を参照 m_searchbar_shown = true; } // 検索バー非表示 void ToolBar::close_searchbar() { if( ! m_searchbar ) return; #ifdef _DEBUG std::cout << "ToolBar::close_searchbar\n"; #endif m_searchbar_shown = false; if( m_searchbar_packed ){ remove( *m_searchbar ); show_all_children(); m_searchbar_packed = false; m_admin->set_command( "focus_current_view" ); } } // // 検索バーを開く/閉じるボタン // Gtk::ToolItem* ToolBar::get_button_open_searchbar() { if( ! m_button_open_searchbar ){ m_button_open_searchbar = Gtk::manage( new ImgToolButton( ICON::SEARCH, CONTROL::Search ) ); std::string tooltip = "検索バーを開く " + CONTROL::get_str_motions( CONTROL::Search ); set_tooltip( *m_button_open_searchbar, tooltip ); m_button_open_searchbar->signal_clicked().connect( sigc::mem_fun(*this, &ToolBar::slot_toggle_searchbar ) ); } return m_button_open_searchbar; } Gtk::ToolItem* ToolBar::get_button_close_searchbar() { if( ! m_button_close_searchbar ){ m_button_close_searchbar = Gtk::manage( new ImgToolButton( ICON::CLOSE_SEARCH, CONTROL::CloseSearchBar ) ); set_tooltip( *m_button_close_searchbar, CONTROL::get_label_motions( CONTROL::CloseSearchBar ) ); m_button_close_searchbar->signal_clicked().connect( sigc::mem_fun(*this, &ToolBar::slot_toggle_searchbar ) ); } return m_button_close_searchbar; } // 検索バー表示/非表示切り替え void ToolBar::slot_toggle_searchbar() { if( ! m_enable_slot ) return; if( ! m_searchbar ) return; #ifdef _DEBUG std::cout << "ToolBar::slot_toggle_searchbar shown = " << m_searchbar_shown << std::endl; #endif if( ! m_searchbar_shown ) m_admin->set_command( "open_searchbar", m_url ); else m_admin->set_command( "close_searchbar", m_url ); } // // 検索 entry // Gtk::ToolItem* ToolBar::get_tool_search( const int mode ) { if( ! m_tool_search ){ m_tool_search = Gtk::manage( new Gtk::ToolItem ); m_entry_search = Gtk::manage( new CompletionEntry( mode ) ); m_entry_search->signal_changed().connect( sigc::mem_fun( *this, &ToolBar::slot_changed_search ) ); m_entry_search->signal_activate().connect( sigc::mem_fun( *this, &ToolBar::slot_active_search ) ); m_entry_search->signal_operate().connect( sigc::mem_fun( *this, &ToolBar::slot_operate_search ) ); m_tool_search->add( *m_entry_search ); m_tool_search->set_expand( true ); } return m_tool_search; } SKELETON::CompletionEntry* ToolBar::get_entry_search() { return m_entry_search; } void ToolBar::add_search_control_mode( const int mode ) { if( m_entry_search ) m_entry_search->add_control_mode( mode ); } std::string ToolBar::get_search_text() const { if( ! m_entry_search ) return std::string(); return m_entry_search->get_text(); } void ToolBar::slot_changed_search() { if( ! m_enable_slot ) return; if( m_url.empty() || ! m_admin ) return; std::string query = m_entry_search->get_text(); #ifdef _DEBUG std::cout << "ToolBar::slot_changed_search query = " << query << std::endl; #endif m_admin->set_command( "toolbar_set_search_query", m_url, query ); } void ToolBar::slot_active_search() { if( ! m_enable_slot ) return; if( m_url.empty() || ! m_admin ) return; #ifdef _DEBUG std::cout << "ToolBar::slot_active_search\n"; #endif m_admin->set_command( "toolbar_exec_search", m_url ); } void ToolBar::slot_operate_search( const int controlid ) { if( ! m_enable_slot ) return; if( m_url.empty() || ! m_admin ) return; #ifdef _DEBUG std::cout << "ToolBar::slot_operate_search id = " << controlid << std::endl; #endif m_admin->set_command( "toolbar_operate_search", m_url, std::to_string( controlid ) ); } // 検索 entry をフォーカス void ToolBar::focus_entry_search() { if( m_entry_search ) m_entry_search->grab_focus(); } // // 上検索 // Gtk::ToolButton* ToolBar::get_button_up_search() { if( ! m_button_up_search ){ m_button_up_search = Gtk::manage( new ImgToolButton( ICON::SEARCH_PREV, CONTROL::SearchPrev ) ); set_tooltip( *m_button_up_search, CONTROL::get_label_motions( CONTROL::SearchPrev ) ); m_button_up_search->signal_clicked().connect( sigc::mem_fun(*this, &ToolBar::slot_clicked_up_search ) ); } return m_button_up_search; } void ToolBar::slot_clicked_up_search() { if( ! m_enable_slot ) return; if( m_url.empty() || ! m_admin ) return; #ifdef _DEBUG std::cout << "ToolBar::slot_clicked_up_search\n"; #endif m_admin->set_command( "toolbar_up_search", m_url ); } // // 下検索 // Gtk::ToolButton* ToolBar::get_button_down_search() { if( ! m_button_down_search ){ m_button_down_search = Gtk::manage( new ImgToolButton( ICON::SEARCH_NEXT, CONTROL::SearchNext ) ); set_tooltip( *m_button_down_search, CONTROL::get_label_motions( CONTROL::SearchNext ) ); m_button_down_search->signal_clicked().connect( sigc::mem_fun(*this, &ToolBar::slot_clicked_down_search ) ); } return m_button_down_search; } void ToolBar::slot_clicked_down_search() { if( ! m_enable_slot ) return; if( m_url.empty() || ! m_admin ) return; #ifdef _DEBUG std::cout << "ToolBar::slot_clicked_down_search\n"; #endif m_admin->set_command( "toolbar_down_search", m_url ); } // // ハイライト解除 // Gtk::ToolButton* ToolBar::get_button_clear_highlight() { if( ! m_button_clear_highlight ){ m_button_clear_highlight = Gtk::manage( new ImgToolButton( ICON::CLEAR_SEARCH, CONTROL::HiLightOff ) ); set_tooltip( *m_button_clear_highlight, CONTROL::get_label_motions( CONTROL::HiLightOff ) ); m_button_clear_highlight->signal_clicked().connect( sigc::mem_fun(*this, &ToolBar::slot_clear_highlight ) ); } return m_button_clear_highlight; } void ToolBar::slot_clear_highlight() { if( ! m_enable_slot ) return; if( m_url.empty() || ! m_admin ) return; m_admin->set_command( "clear_highlight", m_url ); } // 板を開くボタン Gtk::ToolItem* ToolBar::get_button_board() { if( ! m_button_board ){ m_label_board = Gtk::manage( new Gtk::Label ); m_label_board->set_xalign( 0 ); m_button_board = Gtk::manage( new SKELETON::ToolMenuButton( CONTROL::get_label( CONTROL::OpenParentBoard ), false, true, *m_label_board ) ); std::vector< std::string > menu; menu.push_back( "開く" ); menu.push_back( "再読み込みして開く" ); menu.push_back( "separator" ); menu.push_back( "ローカルルールを表示" ); menu.push_back( "板のプロパティを表示" ); m_button_board->get_button()->append_menu( menu ); m_button_board->get_button()->signal_selected().connect( sigc::mem_fun(*this, &ToolBar::slot_menu_board ) ); m_button_board->get_button()->signal_button_clicked().connect( sigc::mem_fun(*this, &ToolBar::slot_open_board ) ); set_tooltip( *m_button_board, CONTROL::get_label_motions( CONTROL::OpenParentBoard ) ); } return m_button_board; } void ToolBar::slot_open_board() { if( ! m_enable_slot ) return; CORE::core_set_command( "open_board", DBTREE::url_boardbase( get_url() ), "true", "auto" // オートモードで開く ); } void ToolBar::slot_menu_board( int i ) { if( ! m_enable_slot ) return; std::unique_ptr pref; // ToolBar::get_button_board()で作成したメニューの順番に内容を合わせる if( i == 0 ) slot_open_board(); else if( i == 1 ) CORE::core_set_command( "open_board", DBTREE::url_boardbase( get_url() ), "true" ); else if( i == 3 ) pref = CORE::PrefDiagFactory( CORE::get_mainwindow(), CORE::PREFDIAG_BOARD, DBTREE::url_boardbase( get_url() ), "show_localrule" ); else if( i == 4 ) pref = CORE::PrefDiagFactory( CORE::get_mainwindow(), CORE::PREFDIAG_BOARD, DBTREE::url_boardbase( get_url() ) ); if( pref ){ pref->run(); } } // // 書き込みボタン // Gtk::ToolButton* ToolBar::get_button_write() { if( ! m_button_write ){ m_button_write = Gtk::manage( new ImgToolButton( ICON::WRITE, CONTROL::WriteMessage ) ); set_tooltip( *m_button_write, CONTROL::get_label_motions( CONTROL::WriteMessage ) ); m_button_write->signal_clicked().connect( sigc::mem_fun(*this, &ToolBar::slot_clicked_write ) ); m_button_write->get_child()->signal_focus_out_event().connect( sigc::mem_fun(*this, &ToolBar::slot_focusout_write_button ) ); } return m_button_write; } void ToolBar::slot_clicked_write() { if( ! m_enable_slot ) return; if( m_url.empty() || ! m_admin ) return; #ifdef _DEBUG std::cout << "ToolBar::slot_clicked_write\n"; #endif m_admin->set_command( "toolbar_write", m_url ); } // 書き込みボタンをフォーカス void ToolBar::focus_button_write() { get_button_write()->get_child()->grab_focus(); } bool ToolBar::slot_focusout_write_button( GdkEventFocus* ) { #ifdef _DEBUG std::cout << "ToolBar::slot_focusout_write_button\n"; #endif return true; } // // 再読み込みボタン // Gtk::ToolButton* ToolBar::get_button_reload() { if( ! m_button_reload ){ m_button_reload = Gtk::manage( new ImgToolButton( ICON::RELOAD, CONTROL::Reload ) ); set_tooltip( *m_button_reload, CONTROL::get_label_motions( CONTROL::Reload ) ); m_button_reload->signal_clicked().connect( sigc::mem_fun(*this, &ToolBar::slot_clicked_reload ) ); } return m_button_reload; } void ToolBar::slot_clicked_reload() { if( ! m_enable_slot ) return; if( m_url.empty() || ! m_admin ) return; #ifdef _DEBUG std::cout << "ToolBar::slot_clicked_reload\n"; #endif m_admin->set_command( "toolbar_reload", m_url ); } // // 読み込み停止ボタン // Gtk::ToolButton* ToolBar::get_button_stop() { if( ! m_button_stop ){ m_button_stop = Gtk::manage( new ImgToolButton( ICON::STOPLOADING, CONTROL::StopLoading ) ); set_tooltip( *m_button_stop, CONTROL::get_label_motions( CONTROL::StopLoading ) ); m_button_stop->signal_clicked().connect( sigc::mem_fun(*this, &ToolBar::slot_clicked_stop ) ); } return m_button_stop; } void ToolBar::slot_clicked_stop() { if( ! m_enable_slot ) return; if( m_url.empty() || ! m_admin ) return; #ifdef _DEBUG std::cout << "ToolBar::slot_clicked_stop\n"; #endif m_admin->set_command( "toolbar_stop", m_url ); } // // 閉じるボタン // Gtk::ToolButton* ToolBar::get_button_close() { if( ! m_button_close ){ m_button_close = Gtk::manage( new ImgToolButton( ICON::QUIT, CONTROL::Quit ) ); set_tooltip( *m_button_close, CONTROL::get_label_motions( CONTROL::Quit ) ); m_button_close->signal_clicked().connect( sigc::mem_fun(*this, &ToolBar::slot_clicked_close ) ); } return m_button_close; } void ToolBar::slot_clicked_close() { if( ! m_enable_slot ) return; if( m_url.empty() || ! m_admin ) return; #ifdef _DEBUG std::cout << "ToolBar::slot_clicked_close\n"; #endif // relief が Gtk:: RELIEF_NONE のときにタブの最後のビューを閉じると、 // ボタンに leave_notify イベントが送られないため、次にビューを開いたときに // 枠が残ったままになる if( m_admin->get_tab_nums() == 1 ){ Gtk::Button* button = dynamic_cast< Gtk::Button* >( m_button_close->get_child() ); // ボタンを一旦非表示にして描画状態をリセットする button->unmap(); button->map(); } m_admin->set_command( "toolbar_close_view", m_url ); } // // 削除ボタン // Gtk::ToolItem* ToolBar::get_button_delete() { if( ! m_button_delete ){ m_button_delete = Gtk::manage( new ImgToolButton( ICON::DELETE, CONTROL::Delete ) ); set_tooltip( *m_button_delete, CONTROL::get_label_motions( CONTROL::Delete ) ); m_button_delete->signal_clicked().connect( sigc::mem_fun(*this, &ToolBar::slot_clicked_delete ) ); } return m_button_delete; } void ToolBar::slot_clicked_delete() { if( ! m_enable_slot ) return; if( m_url.empty() || ! m_admin ) return; #ifdef _DEBUG std::cout << "ToolBar::slot_clicked_delete\n"; #endif m_admin->set_command( "toolbar_delete_view", m_url ); } // // お気に入りボタン // Gtk::ToolItem* ToolBar::get_button_favorite() { if( ! m_button_favorite ){ m_button_favorite = Gtk::manage( new ImgToolButton( ICON::APPENDFAVORITE, CONTROL::AppendFavorite ) ); set_tooltip( *m_button_favorite, CONTROL::get_label_motions( CONTROL::AppendFavorite ) ); m_button_favorite->signal_clicked().connect( sigc::mem_fun(*this, &ToolBar::slot_clicked_favorite ) ); } return m_button_favorite; } void ToolBar::slot_clicked_favorite() { if( ! m_enable_slot ) return; if( m_url.empty() || ! m_admin ) return; #ifdef _DEBUG std::cout << "ToolBar::slot_clicked_favorite\n"; #endif m_admin->set_command( "toolbar_set_favorite", m_url ); } // // UNDO ボタン // Gtk::ToolButton* ToolBar::get_button_undo() { if( ! m_button_undo ){ m_button_undo = Gtk::manage( new ImgToolButton( ICON::UNDO, CONTROL::Undo ) ); m_button_undo->set_sensitive( false ); set_tooltip( *m_button_undo, CONTROL::get_label_motions( CONTROL::Undo ) ); } return m_button_undo; } // // REDO ボタン // Gtk::ToolButton* ToolBar::get_button_redo() { if( ! m_button_redo ){ m_button_redo = Gtk::manage( new ImgToolButton( ICON::REDO, CONTROL::Redo ) ); m_button_redo->set_sensitive( false ); set_tooltip( *m_button_redo, CONTROL::get_label_motions( CONTROL::Redo ) ); } return m_button_redo; } // // 戻るボタン // Gtk::ToolItem* ToolBar::get_button_back() { if( ! m_button_back ){ const std::string label = CONTROL::get_label( CONTROL::PrevView ); m_button_back = Gtk::manage( new SKELETON::ToolBackForwardButton( label, false, m_url, true ) ); m_button_back->get_button()->signal_button_clicked().connect( sigc::mem_fun(*this, &ToolBar::slot_clicked_back ) ); m_button_back->get_button()->signal_selected().connect( sigc::mem_fun(*this, &ToolBar::slot_selected_back ) ); set_tooltip( *m_button_back, CONTROL::get_label_motions( CONTROL::PrevView ) ); } return m_button_back; } void ToolBar::slot_clicked_back() { if( ! m_enable_slot ) return; if( m_url.empty() || ! m_admin ) return; #ifdef _DEBUG std::cout << "ToolBar::slot_clicked_back : " << m_url << std::endl; #endif m_admin->set_command( "back_viewhistory", m_url, "1" ); } void ToolBar::slot_selected_back( const int i ) { if( ! m_enable_slot ) return; if( m_url.empty() || ! m_admin ) return; #ifdef _DEBUG std::cout << "ToolBar::slot_selected_back : " << i << " url = " << m_url << std::endl; #endif m_admin->set_command( "back_viewhistory", m_url, std::to_string( i+1 ) ); } // // 進むボタン // Gtk::ToolItem* ToolBar::get_button_forward() { if( ! m_button_forward ){ const std::string label = CONTROL::get_label( CONTROL::NextView ); m_button_forward = Gtk::manage( new SKELETON::ToolBackForwardButton( label, false, m_url, false ) ); m_button_forward->get_button()->signal_button_clicked().connect( sigc::mem_fun(*this, &ToolBar::slot_clicked_forward ) ); m_button_forward->get_button()->signal_selected().connect( sigc::mem_fun(*this, &ToolBar::slot_selected_forward ) ); set_tooltip( *m_button_forward, CONTROL::get_label_motions( CONTROL::NextView ) ); } return m_button_forward; } void ToolBar::slot_clicked_forward() { if( ! m_enable_slot ) return; if( m_url.empty() || ! m_admin ) return; #ifdef _DEBUG std::cout << "ToolBar::slot_clicked_forward : " << m_url << std::endl; #endif m_admin->set_command( "forward_viewhistory", m_url, "1" ); } void ToolBar::slot_selected_forward( const int i ) { if( ! m_enable_slot ) return; if( m_url.empty() || ! m_admin ) return; #ifdef _DEBUG std::cout << "ToolBar::slot_selected_forward : " << i << " url = " << m_url << std::endl; #endif m_admin->set_command( "forward_viewhistory", m_url, std::to_string( i+1 ) ); } // // ロックボタン // Gtk::ToolButton* ToolBar::get_button_lock() { if( ! m_button_lock ){ m_button_lock = Gtk::manage( new ImgToggleToolButton( ICON::LOCK, CONTROL::Lock ) ); set_tooltip( *m_button_lock, CONTROL::get_label_motions( CONTROL::Lock ) ); m_button_lock->signal_clicked().connect( sigc::mem_fun( *this, &ToolBar::slot_lock_clicked ) ); } return m_button_lock; } void ToolBar::slot_lock_clicked() { if( ! m_enable_slot ) return; m_admin->set_command( "toolbar_lock_view", get_url() ); } jdim-0.7.0/src/skeleton/toolbar.h000066400000000000000000000136611417047150700167040ustar00rootroot00000000000000// ライセンス: GPL2 // // ツールバーの基底クラス // #ifndef _TOOLBAR_H #define _TOOLBAR_H #include "jdtoolbar.h" #include namespace SKELETON { class Admin; class View; class ToolMenuButton; class ToolBackForwardButton; class BackForwardButton; class CompletionEntry; class ToolBar : public Gtk::VBox { SKELETON::Admin* m_admin; std::string m_url; bool m_enable_slot; // ボタンバー SKELETON::JDToolbar m_buttonbar; bool m_buttonbar_shown{}; bool m_buttonbar_packed{}; // ラベル Gtk::ToolItem* m_tool_label{}; Gtk::EventBox* m_ebox_label{}; Gtk::Label* m_label{}; // 検索関係 Gtk::Toolbar* m_searchbar{}; // 検索バー bool m_searchbar_shown{}; bool m_searchbar_packed{}; Gtk::ToolButton* m_button_open_searchbar{}; Gtk::ToolButton* m_button_close_searchbar{}; Gtk::ToolButton* m_button_up_search{}; Gtk::ToolButton* m_button_down_search{}; Gtk::ToolButton* m_button_clear_highlight{}; Gtk::ToolItem* m_tool_search{}; SKELETON::CompletionEntry* m_entry_search{}; // 板を開く Gtk::Label* m_label_board{}; SKELETON::ToolMenuButton* m_button_board{}; // その他ボタン Gtk::ToolButton* m_button_write{}; Gtk::ToolButton* m_button_reload{}; Gtk::ToolButton* m_button_stop{}; Gtk::ToolButton* m_button_close{}; Gtk::ToolButton* m_button_delete{}; Gtk::ToolButton* m_button_favorite{}; Gtk::ToolButton* m_button_undo{}; Gtk::ToolButton* m_button_redo{}; Gtk::ToggleToolButton* m_button_lock{}; // 進む、戻るボタン SKELETON::ToolBackForwardButton* m_button_back{}; SKELETON::ToolBackForwardButton* m_button_forward{}; static constexpr const char* s_css_label = u8"jd-toolbar-label"; Glib::RefPtr< Gtk::CssProvider > m_label_provider = Gtk::CssProvider::create(); public: explicit ToolBar( Admin* admin ); ~ToolBar() noexcept; void set_url( const std::string& url ); const std::string& get_url() const { return m_url; } // タブが切り替わった時にDragableNoteBookから呼び出される( Viewの情報を取得する ) virtual void set_view( SKELETON::View * view ); // タブが切り替わった時にDragableNoteBookから呼び出される( ツールバーを表示する ) void show_toolbar(); // ボタンバー表示/非表示 void open_buttonbar(); void close_buttonbar(); // 検索バー表示/非表示 void open_searchbar(); void close_searchbar(); // 検索entryをフォーカス void focus_entry_search(); // 書き込みボタンをフォーカス void focus_button_write(); // ボタン表示更新 void update_button(); protected: SKELETON::Admin* get_admin(){ return m_admin; } // ボタンのパッキング virtual void pack_buttons() = 0; void unpack_buttons(); void unpack_search_buttons(); // ボタンのrelief指定 void set_relief(); Gtk::Toolbar& get_buttonbar(){ return m_buttonbar; } // ラベル Gtk::ToolItem* get_label(); // 検索関係 Gtk::Toolbar* get_searchbar(); Gtk::ToolItem* get_button_open_searchbar(); Gtk::ToolItem* get_button_close_searchbar(); // mode は補完モード ( compmanager.h 参照 ) Gtk::ToolItem* get_tool_search( const int mode ); SKELETON::CompletionEntry* get_entry_search(); // CompletionEntry の入力コントローラのモード設定 void add_search_control_mode( const int mode ); std::string get_search_text() const; // 上検索 Gtk::ToolButton* get_button_up_search(); // 下検索 Gtk::ToolButton* get_button_down_search(); // ハイライト解除 Gtk::ToolButton* get_button_clear_highlight(); // 板を開く Gtk::ToolItem* get_button_board(); // その他ボタン Gtk::ToolButton* get_button_write(); Gtk::ToolButton* get_button_reload(); Gtk::ToolButton* get_button_stop(); Gtk::ToolButton* get_button_close(); Gtk::ToolItem* get_button_delete(); Gtk::ToolItem* get_button_favorite(); Gtk::ToolButton* get_button_undo(); Gtk::ToolButton* get_button_redo(); Gtk::ToolButton* get_button_lock(); Gtk::ToolItem* get_button_back(); Gtk::ToolItem* get_button_forward(); void pack_separator(); void pack_transparent_separator(); void set_tooltip( Gtk::ToolItem& toolitem, const std::string& tip ); private: // ラベル関係 void set_label( const std::string& label ); void set_color( const std::string& color ); // 書き込みボタン関係 bool slot_focusout_write_button( GdkEventFocus* event ); // 検索関係 void slot_toggle_searchbar(); void slot_changed_search(); void slot_active_search(); void slot_operate_search( const int controlid ); void slot_clicked_up_search(); void slot_clicked_down_search(); void slot_clear_highlight(); // 板を開く void slot_open_board(); void slot_menu_board( int i ); // その他ボタン void slot_clicked_write(); void slot_clicked_reload(); void slot_clicked_stop(); void slot_clicked_close(); void slot_clicked_delete(); void slot_clicked_favorite(); void slot_lock_clicked(); void slot_clicked_back(); void slot_selected_back( const int i ); void slot_clicked_forward(); void slot_selected_forward( const int i ); }; } #endif jdim-0.7.0/src/skeleton/toolbarnote.cpp000066400000000000000000000006141417047150700201170ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "toolbarnote.h" using namespace SKELETON; ToolBarNotebook::ToolBarNotebook( DragableNoteBook* ) : Gtk::Notebook() { set_show_border( true ); set_show_tabs( false ); set_border_width( 0 ); set_margin_top( 1 ); set_margin_bottom( 1 ); } ToolBarNotebook::~ToolBarNotebook() noexcept = default; jdim-0.7.0/src/skeleton/toolbarnote.h000066400000000000000000000007361417047150700175710ustar00rootroot00000000000000// ライセンス: GPL2 // // DragableNoteBookを構成するツールバー表示用の Notebook // // TODO: 使われなくなったコンストラクタの引数を整理する #ifndef _TOOLBARNOTE_H #define _TOOLBARNOTE_H #include namespace SKELETON { class DragableNoteBook; class ToolBarNotebook : public Gtk::Notebook { public: explicit ToolBarNotebook( DragableNoteBook* ); ~ToolBarNotebook() noexcept; }; } #endif jdim-0.7.0/src/skeleton/toolmenubutton.cpp000066400000000000000000000051141417047150700206650ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "toolmenubutton.h" #include "menubutton.h" #include "backforwardbutton.h" using namespace SKELETON; ToolMenuButton::ToolMenuButton() = default; ToolMenuButton::ToolMenuButton( const std::string& label, const bool expand, const bool show_arrow, Gtk::Widget& widget ) : ToolMenuButton( Gtk::manage( new SKELETON::MenuButton( show_arrow, widget ) ), label, expand ) { } ToolMenuButton::ToolMenuButton( const std::string& label, const bool expand, const bool show_arrow, const int id ) : ToolMenuButton( Gtk::manage( new SKELETON::MenuButton( show_arrow, id ) ), label, expand ) { } ToolMenuButton::~ToolMenuButton() noexcept = default; ToolMenuButton::ToolMenuButton( SKELETON::MenuButton* button, const std::string& label, const bool expand ) : m_button{ button } { assert( m_button != nullptr ); Gtk::Widget* label_widget = m_button->get_label_widget(); assert( label_widget ); Gtk::MenuItem* item = nullptr; // アイコンの場合はアイコン表示 Gtk::Image* image = dynamic_cast< Gtk::Image* >( label_widget ); if( image ){ const Gtk::ImageType type = image->get_storage_type(); if( type == Gtk::IMAGE_STOCK ) { Gtk::StockID id; Gtk::IconSize size; image->get_stock( id, size ); item = Gtk::manage( new Gtk::ImageMenuItem( *Gtk::manage( new Gtk::Image( id, size ) ), label ) ); } else if( type == Gtk::IMAGE_PIXBUF ) { auto pixbuf = image->get_pixbuf(); item = Gtk::manage( new Gtk::ImageMenuItem( *Gtk::manage( new Gtk::Image( pixbuf ) ), label ) ); } } if( !item ) { item = Gtk::manage( new Gtk::MenuItem( label ) ); } if( item ){ item->signal_activate().connect( sigc::mem_fun( *m_button, &MenuButton::on_clicked ) ); set_proxy_menu_item( label, *item ); } set_expand( expand ); add( *m_button ); } /////////////////////////// ToolBackForwardButton::ToolBackForwardButton( const std::string& label, const bool expand, const std::string& url, const bool back ) : ToolMenuButton( Gtk::manage( new SKELETON::BackForwardButton( url, back ) ), label, expand ) { } SKELETON::BackForwardButton* ToolBackForwardButton::get_backforward_button() { return dynamic_cast< SKELETON::BackForwardButton* >( get_button() ); } jdim-0.7.0/src/skeleton/toolmenubutton.h000066400000000000000000000026751417047150700203430ustar00rootroot00000000000000// ライセンス: GPL2 // ツールバーに表示する SKELETON::MenuButton、および SKELETON::BackForwardButton // メニューがオーバーフローしたときにアイコン、または label を表示して // 選択したら MenuButton::on_clicked() を呼び出す #ifndef _TOOLMENUBUTTON_H #define _TOOLMENUBUTTON_H #include namespace SKELETON { class MenuButton; class BackForwardButton; class ToolMenuButton : public Gtk::ToolItem { SKELETON::MenuButton* m_button{}; public: ToolMenuButton(); ToolMenuButton( const std::string& label, const bool expand, const bool show_arrow, Gtk::Widget& widget ); ToolMenuButton( const std::string& label, const bool expand, const bool show_arrow , const int id ); ~ToolMenuButton() noexcept; SKELETON::MenuButton* get_button(){ return m_button; } protected: ToolMenuButton( SKELETON::MenuButton* button, const std::string& label, const bool expand ); }; ////////////////////////// class ToolBackForwardButton : public SKELETON::ToolMenuButton { public: ToolBackForwardButton( const std::string& label, const bool expand, const std::string& url, const bool back ); SKELETON::BackForwardButton* get_backforward_button(); }; } #endif jdim-0.7.0/src/skeleton/treeviewbase.cpp000066400000000000000000000242521417047150700202600ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "treeviewbase.h" #include "jdlib/miscgtk.h" #include using namespace SKELETON; JDTreeViewBase::JDTreeViewBase() { add_events( Gdk::KEY_PRESS_MASK ); add_events( Gdk::KEY_RELEASE_MASK ); add_events( Gdk::SCROLL_MASK ); add_events( Gdk::BUTTON_PRESS_MASK ); add_events( Gdk::POINTER_MOTION_MASK ); } JDTreeViewBase::~JDTreeViewBase() noexcept = default; // // 行数 // int JDTreeViewBase::get_row_size() const { if( ! get_model() ) return 0; return get_model()->children().size(); } // // 現在フォーカスしてる行の最初のパスを取得 // Gtk::TreeModel::Path JDTreeViewBase::get_current_path() const { Gtk::TreeModel::Path path; std::vector< Gtk::TreeModel::Path > paths = get_selection()->get_selected_rows(); if( paths.size() ){ std::vector< Gtk::TreeModel::Path >::iterator it = paths.begin(); path = ( *it ); } return path; } // 現在フォーカスしてる行の最初のrowを取得 Gtk::TreeModel::Row JDTreeViewBase::get_current_row() { Gtk::TreePath path = get_current_path(); return get_row( path ); } // // x, y 座標の下のパスを取得 // Gtk::TreeModel::Path JDTreeViewBase::get_path_under_xy( int x, int y ) const { Gtk::TreeModel::Path path; Gtk::TreeViewColumn* column; int cell_x, cell_y; if( !get_path_at_pos( x, y, path, column, cell_x, cell_y ) ) return Gtk::TreeModel::Path(); return path; } // // 現在のマウスポインタの下のパスを取得 // Gtk::TreeModel::Path JDTreeViewBase::get_path_under_mouse() const { int x, y; MISC::get_pointer_at_window( get_window(), x, y ); return get_path_under_xy( x, y ); } // // 現在のマウスポインタの下のセルの幅高さとセル内での座標を取得 // void JDTreeViewBase::get_cell_xy_wh( int& cell_x, int& cell_y, int& cell_w, int& cell_h ) const { cell_x = cell_y = cell_w = cell_h = -1; Gtk::TreeModel::Path path; Gtk::TreeViewColumn* column; int x, y; Gdk::Rectangle rect; MISC::get_pointer_at_window( get_window(), x, y ); get_path_at_pos( x, y, path, column, cell_x, cell_y ); if( column ) { int o_x, o_y; column->cell_get_size( rect, o_x, o_y, cell_w, cell_h ); } } // // 選択中の Gtk::TreeModel::iterator のリストを取得 // // 削除などを実行してから get_model()->get_iter() するとパスが変わってエラーが出るので // 先に iterator だけ取得しておく // std::list< Gtk::TreeModel::iterator > JDTreeViewBase::get_selected_iterators() { std::list< Gtk::TreeModel::iterator > list_it; if( get_model() ){ std::vector< Gtk::TreeModel::Path > paths = get_selection()->get_selected_rows(); std::transform( paths.begin(), paths.end(), std::back_inserter( list_it ), [this]( Gtk::TreeModel::Path& p ) { return get_model()->get_iter( p ); } ); } return list_it; } // // 選択行の削除 // void JDTreeViewBase::delete_selected_rows( const bool force ) { std::vector< Gtk::TreeModel::Path > list_path = get_selection()->get_selected_rows(); if( ! list_path.size() ) return; Glib::RefPtr< Gtk::ListStore > liststore; Glib::RefPtr< Gtk::TreeStore > treestore = Glib::RefPtr< Gtk::TreeStore >::cast_dynamic( get_model() ); if( ! treestore ){ liststore = Glib::RefPtr< Gtk::ListStore >::cast_dynamic( get_model() ); if( ! liststore ) return; } // カーソルを選択範囲の最後の行の次の行に移動 const Gtk::TreePath next = next_path( list_path.back(), true ); const bool gotobottom = ( ! get_row( next ) ); if( ! gotobottom ) set_cursor( next ); for( const Gtk::TreePath& path : list_path ) { Gtk::TreeRow row = get_row( path ); if( treestore ) treestore->erase( row ); else liststore->erase( row ); } if( gotobottom ) goto_bottom(); } // // 先頭へ // void JDTreeViewBase::goto_top() { if( ! get_row_size() ) return; Gtk::TreePath path = get_model()->get_path( *( get_model()->children().begin() ) ); scroll_to_row( path, 0 ); set_cursor( path ); } // // 一番最後へ // void JDTreeViewBase::goto_bottom() { if( ! get_row_size() ) return; Gtk::TreePath path = get_model()->get_path( *std::prev( get_model()->children().end() ) ); scroll_to_row( path, 0 ); set_cursor( path ); } // // 選択行を上へ移動 // bool JDTreeViewBase::row_up() { Gtk::TreePath path = get_current_path(); if( !get_row( path ) ) return false; Gtk::TreePath new_path = prev_path( path ); if( path != new_path ) set_cursor( new_path ); else return false; return true; } // // 選択行を下へ移動 // bool JDTreeViewBase::row_down() { Gtk::TreePath path = get_current_path(); if( !get_row( path ) ) return false; Gtk::TreePath new_path = next_path( path ); if( new_path.size() && get_row( new_path ) ) set_cursor( new_path ); else return false; return true; } // // page up // void JDTreeViewBase::page_up() { bool set_top = false; // スクロール auto adj = get_vadjustment(); double val = adj->get_value(); if( val > adj->get_page_size()/2 ) set_top = true; val = MAX( 0, val - adj->get_page_size() ); adj->set_value( val ); // 選択行移動 Gtk::TreePath path; if( set_top ) path = get_path_under_xy( 0, (int)adj->get_page_size() - 4 ); else path = get_path_under_xy( 0, 0 ); if( path.size() && get_row( path ) )set_cursor( path ); } // // page down // void JDTreeViewBase::page_down() { bool set_bottom = false; // スクロール auto adj = get_vadjustment(); double val = adj->get_value(); if( val < adj->get_upper() - adj->get_page_size() - adj->get_page_size()/2 ) set_bottom = true; val = MIN( adj->get_upper() - adj->get_page_size(), val + adj->get_page_size() ); adj->set_value( val ); // 選択行移動 Gtk::TreePath path; if( set_bottom ) path = get_path_under_xy( 0, 0 ); else path = get_path_under_xy( 0, (int)adj->get_page_size() - 4 ); if( path.size() && get_row( path ) ) set_cursor( path ); } // // path の前の path を取得 // // check_expand = true なら行が開いてるかチェックして開いて無い時はdown()しない // Gtk::TreePath JDTreeViewBase::prev_path( const Gtk::TreePath& path, bool check_expand ) { Gtk::TreePath path_out( path ); // 前に移動 if( path_out.prev() && ( row_expanded( path_out ) || ! check_expand ) ){ Gtk::TreePath path_tmp = path_out; while( get_row( path_out ) && ( path_out = next_path( path_out, check_expand ) ) != path ) path_tmp = path_out; if( get_row( path_tmp ) ) return path_tmp; } // 一番上まで到達したらup path_out = path; if( ! path_out.prev() && path_out.size() >= 2 ) path_out.up(); return path_out;; } // // path の次の path を取得 // // check_expand = true なら行が開いてるかチェックして開いて無い時はdown()しない // Gtk::TreePath JDTreeViewBase::next_path( const Gtk::TreePath& path, bool check_expand ) { if( !get_row( path ) ) return path; Gtk::TreePath path_out( path ); if( row_expanded( path_out ) || ! check_expand ){ path_out.down(); if( get_row( path_out ) ) return path_out; } // next()してレベルの一番下まで到達したら上のレベルに移動 path_out = path; while( path_out.next(), ( ! get_row( path_out ) && path_out.size() >=2 ) ) path_out.up(); return path_out; } // // path->row 変換 // Gtk::TreeModel::Row JDTreeViewBase::get_row( const Gtk::TreePath& path ) { if( path.empty() || ! get_model() ) return Gtk::TreeModel::Row(); Gtk::TreeModel::Row row = *( get_model()->get_iter( path ) ); if( !row ) return Gtk::TreeModel::Row(); if( path != get_model()->get_path( row ) ) return Gtk::TreeModel::Row(); return row; } // // pathの親を再起的に開く // void JDTreeViewBase::expand_parents( const Gtk::TreePath& path ) { if( ! get_model() ) return; for( Gtk::TreePath::size_type level = 1; level < path.size(); ++level ){ Gtk::TreeModel::Row row_tmp = get_row( path ); if( ! row_tmp ) return; for( Gtk::TreePath::size_type i = 0; i < path.size() - level; ++i ){ if( row_tmp.parent() ) row_tmp = *( row_tmp.parent() ); } Gtk::TreePath path_tmp = get_model()->get_path( row_tmp ); expand_row( path_tmp, false ); } } // // pathが開かれているか // bool JDTreeViewBase::is_expand( const Gtk::TreePath& path ) { Gtk::TreePath parent( path ); if( path.size() < 2 ) return true; if( parent.up() && row_expanded( parent ) ) return true; return false; } // // 行のセルの高さ // int JDTreeViewBase::get_row_height() const { const Gtk::TreeViewColumn* column = get_column( m_column_for_height ); if( !column ) return 0; int x,y,w,h; Gdk::Rectangle rect; column->cell_get_size( rect, x, y, w, h ); return h; } // // キーボードのキーを押した // bool JDTreeViewBase::on_key_press_event( GdkEventKey* event ) { return m_sig_key_press.emit( event ); } // // キーボードのキーを離した // bool JDTreeViewBase::on_key_release_event( GdkEventKey* event ) { m_sig_key_release.emit( event ); return true; } // マウスのwheelを回した bool JDTreeViewBase::on_scroll_event( GdkEventScroll* event ) { m_sig_scroll_event.emit( event ); return Gtk::TreeView::on_scroll_event( event ); } // // マウスボタンを押した // bool JDTreeViewBase::on_button_press_event( GdkEventButton* event ) { m_sig_button_press.emit( event ); return Gtk::TreeView::on_button_press_event( event ); } // // マウスボタンを離した // bool JDTreeViewBase::on_button_release_event( GdkEventButton* event ) { m_sig_button_release.emit( event ); return Gtk::TreeView::on_button_release_event( event ); } // // マウスを動かした // bool JDTreeViewBase::on_motion_notify_event( GdkEventMotion* event ) { m_sig_motion_notify.emit( event ); return Gtk::TreeView::on_motion_notify_event( event ); } jdim-0.7.0/src/skeleton/treeviewbase.h000066400000000000000000000075341417047150700177310ustar00rootroot00000000000000// ライセンス: GPL2 // // treeviewクラスの基底クラス // #ifndef _TREEVIEWBASE_H #define _TREEVIEWBASE_H #include #include namespace SKELETON { class JDTreeViewBase : public Gtk::TreeView { typedef sigc::signal< bool, GdkEventKey* > SIG_KEY_PRESS; typedef sigc::signal< bool, GdkEventKey* > SIG_KEY_RELEASE; typedef sigc::signal< bool, GdkEventScroll* > SIG_SCROLL_EVENT; typedef sigc::signal< bool, GdkEventButton* > SIG_BUTTON_PRESS; typedef sigc::signal< bool, GdkEventButton* > SIG_BUTTON_RELEASE; typedef sigc::signal< bool, GdkEventMotion* > SIG_MOTION_NOTIFY; SIG_KEY_PRESS m_sig_key_press; SIG_KEY_RELEASE m_sig_key_release; SIG_SCROLL_EVENT m_sig_scroll_event; SIG_BUTTON_PRESS m_sig_button_press; SIG_BUTTON_RELEASE m_sig_button_release; SIG_MOTION_NOTIFY m_sig_motion_notify; // get_row_height() で高さを取得するためのcolumn番号 int m_column_for_height{}; public: SIG_KEY_PRESS& sig_key_press() { return m_sig_key_press; } SIG_KEY_RELEASE& sig_key_release() { return m_sig_key_release; } SIG_SCROLL_EVENT& sig_scroll_event(){ return m_sig_scroll_event; } SIG_BUTTON_PRESS& sig_button_press() { return m_sig_button_press; } SIG_BUTTON_RELEASE& sig_button_release() { return m_sig_button_release; } SIG_MOTION_NOTIFY& sig_motion_notify() { return m_sig_motion_notify; } JDTreeViewBase(); ~JDTreeViewBase() noexcept; // 行数 int get_row_size() const; // カーソル解除 void unset_cursor(){ get_selection()->unselect_all(); } // 現在フォーカスしてる行の最初のパスを取得 Gtk::TreeModel::Path get_current_path() const; // 現在フォーカスしてる行の最初のrowを取得 Gtk::TreeModel::Row get_current_row(); //x, y 座標の下のパスを取得 Gtk::TreeModel::Path get_path_under_xy( int x, int y ) const; // 現在のマウスポインタの下のパスを取得 Gtk::TreeModel::Path get_path_under_mouse() const; // 現在のマウスポインタの下のセルの幅高さとセル内での座標を取得 void get_cell_xy_wh( int& cell_x, int& cell_y, int& cell_w, int& cell_h ) const; // 選択中の Gtk::TreeModel::iterator のリストを取得 std::list< Gtk::TreeModel::iterator > get_selected_iterators(); // 選択行の削除 virtual void delete_selected_rows( const bool force ); // 選択行の移動 void goto_top(); void goto_bottom(); bool row_up(); bool row_down(); void page_up(); void page_down(); // path の前後のpathを取得 Gtk::TreePath prev_path( const Gtk::TreePath& path, bool check_expand = true ); Gtk::TreePath next_path( const Gtk::TreePath& path, bool check_expand = true ); // path -> row 変換 Gtk::TreeModel::Row get_row( const Gtk::TreePath& path ); // pathの親を再起的にexpandする void expand_parents( const Gtk::TreePath& path ); // pathが開かれているか bool is_expand( const Gtk::TreePath& path ); // 行のセルの高さ int get_row_height() const; void set_column_for_height( int column ){ m_column_for_height = column; } protected: bool on_key_press_event( GdkEventKey* event ) override; bool on_key_release_event( GdkEventKey* event ) override; bool on_scroll_event( GdkEventScroll* event ) override; bool on_button_press_event( GdkEventButton* event ) override; bool on_button_release_event( GdkEventButton* event ) override; bool on_motion_notify_event( GdkEventMotion* event ) override; }; } #endif jdim-0.7.0/src/skeleton/undobuffer.cpp000066400000000000000000000054611417047150700177330ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "undobuffer.h" using namespace SKELETON; UNDO_BUFFER::UNDO_BUFFER() : m_first{ true } { m_vec_undo.push_back( UNDO_DATA() ); } void UNDO_BUFFER::set_item( UNDO_ITEM& item ) { #ifdef _DEBUG std::cout << "UNDO_BUFFER::set_item\n"; #endif if( m_first ){ #ifdef _DEBUG std::cout << "reset_data\n"; #endif get_undo_data().clear(); m_first = false; } get_undo_data().push_back( item ); #ifdef _DEBUG std::cout << "size = " << get_undo_data().size() << std::endl; #endif } void UNDO_BUFFER::undo() { if( get_enable_undo() ){ #ifdef _DEBUG std::cout << "UNDO_BUFFER::undo pos = " << m_pos << " max = " << m_max << std::endl; #endif --m_pos; m_first = true; m_sig_undo.emit(); } } void UNDO_BUFFER::redo() { if( get_enable_redo() ){ #ifdef _DEBUG std::cout << "SKELETON::UNDO_BUFFER::redo pos = " << m_pos << " max = " << m_max << std::endl; #endif ++m_pos; m_first = true; m_sig_redo.emit(); } } // // 選択中の行 // void UNDO_BUFFER::set_list_info_selected( const CORE::DATA_INFO_LIST& list_info_selected ) { #ifdef _DEBUG std::cout << "UNDO_BUFFER::set_list_info_selected\n"; #endif UNDO_ITEM item; item.list_info_selected = list_info_selected; set_item( item ); } // // 削除や追加した行 // void UNDO_BUFFER::set_list_info( const CORE::DATA_INFO_LIST& list_info_append, const CORE::DATA_INFO_LIST& list_info_delete ) { #ifdef _DEBUG std::cout << "UNDO_BUFFER::set_list_info pos = " << m_pos << " max = " << m_max << std::endl; #endif UNDO_ITEM item; item.list_info_append = list_info_append; item.list_info_delete = list_info_delete; set_item( item ); } // // 名前を変更した行 // void UNDO_BUFFER::set_name( const Gtk::TreePath& path_renamed, const Glib::ustring& name_new, const Glib::ustring& name_before ) { #ifdef _DEBUG std::cout << "UNDO_BUFFER::set_name pos = " << m_pos << " max = " << m_max << std::endl << "path = " << path_renamed.to_string() << " new = " << name_new << " before = " << name_before << std::endl; #endif UNDO_ITEM item; item.path_renamed = path_renamed; item.name_new = name_new; item.name_before = name_before; set_item( item ); } // // set_* でデータをセットしたら最後にcommitする // void UNDO_BUFFER::commit() { if( m_first ) return; #ifdef _DEBUG std::cout << "UNDO_BUFFER::commit pos = " << m_pos << " max = " << m_pos << std::endl; #endif ++m_pos; m_max = m_pos; if( (int)m_vec_undo.size() == m_pos ) m_vec_undo.push_back( UNDO_DATA() ); m_first = true; m_sig_commit.emit(); #ifdef _DEBUG std::cout << "-> pos = max = " << m_pos << std::endl; #endif } jdim-0.7.0/src/skeleton/undobuffer.h000066400000000000000000000042201417047150700173700ustar00rootroot00000000000000// ライセンス: GPL2 // UNDO用バッファ #include "sharedbuffer.h" #include namespace SKELETON { class UNDO_ITEM { public: CORE::DATA_INFO_LIST list_info_selected; CORE::DATA_INFO_LIST list_info_append; CORE::DATA_INFO_LIST list_info_delete; Gtk::TreePath path_renamed; Glib::ustring name_new; Glib::ustring name_before; UNDO_ITEM() { list_info_selected.clear(); list_info_append.clear(); list_info_delete.clear(); path_renamed = Gtk::TreePath(); name_new = std::string(); name_before = std::string(); } }; typedef std::vector< UNDO_ITEM > UNDO_DATA; typedef sigc::signal< void > SIG_UNDO; typedef sigc::signal< void > SIG_REDO; typedef sigc::signal< void > SIG_COMMIT; class UNDO_BUFFER { std::vector< UNDO_DATA > m_vec_undo; int m_pos{}; int m_max{}; bool m_first; SIG_UNDO m_sig_undo; SIG_REDO m_sig_redo; SIG_COMMIT m_sig_commit; public: UNDO_BUFFER(); virtual ~UNDO_BUFFER() noexcept = default; SIG_UNDO sig_undo(){ return m_sig_undo; } SIG_REDO sig_redo(){ return m_sig_redo; } SIG_COMMIT sig_commit(){ return m_sig_commit; } UNDO_DATA& get_undo_data(){ return m_vec_undo[ m_pos ]; } bool get_enable_undo() const { return ( m_pos ); } bool get_enable_redo() const { return ( m_pos < m_max ); } void undo(); void redo(); // 選択中の行 void set_list_info_selected( const CORE::DATA_INFO_LIST& list_info_selected ); // 削除や追加した行 void set_list_info( const CORE::DATA_INFO_LIST& list_info_append, const CORE::DATA_INFO_LIST& list_info_delete ); // 名前を変更した行 void set_name( const Gtk::TreePath& path_renamed, const Glib::ustring& name_new, const Glib::ustring& name_before ); // set_* でデータをセットしたら最後にcommitする void commit(); private: void set_item( UNDO_ITEM& item ); }; } jdim-0.7.0/src/skeleton/vbox.cpp000066400000000000000000000011241417047150700165420ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "vbox.h" using namespace SKELETON; JDVBox::~JDVBox() noexcept = default; // unpack = true の時取り除く void JDVBox::pack_remove_start( bool unpack, Widget& child, Gtk::PackOptions options, guint padding ) { if( unpack ) remove( child ); else pack_start( child, options, padding ); } // unpack = true の時取り除く void JDVBox::pack_remove_end( bool unpack, Widget& child, Gtk::PackOptions options, guint padding ) { if( unpack ) remove( child ); else pack_end( child, options, padding ); } jdim-0.7.0/src/skeleton/vbox.h000066400000000000000000000010651417047150700162130ustar00rootroot00000000000000// ライセンス: GPL2 // // VBoxクラス // #ifndef _VBOX_H #define _VBOX_H #include namespace SKELETON { class JDVBox : public Gtk::VBox { public: using Gtk::VBox::VBox; ~JDVBox() noexcept; // unpack = true の時取り除く void pack_remove_start( bool unpack, Widget& child, Gtk::PackOptions options = Gtk::PACK_EXPAND_WIDGET, guint padding = 0 ); void pack_remove_end( bool unpack, Widget& child, Gtk::PackOptions options = Gtk::PACK_EXPAND_WIDGET, guint padding = 0 ); }; } #endif jdim-0.7.0/src/skeleton/view.cpp000066400000000000000000000150311417047150700165400ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "admin.h" #include "view.h" #include "config/globalconf.h" #include "history/historymanager.h" #include "jdlib/miscgtk.h" #include "global.h" #include "session.h" #include "command.h" using namespace SKELETON; View::View( const std::string& url, const std::string& arg1 ,const std::string& arg2 ) : m_url( url ) , m_autoreload_mode( AUTORELOAD_NOT ) , m_lockable( true ) , m_writeable( true ) , m_id_toolbar( 0 ) // 派生クラスでコンテキストが異なる {} const std::string& View::get_url_admin() { return get_admin()->get_url(); } // // url_new に URL を変更 // void View::set_url( const std::string& url_new ) { if( ! m_url.empty() && ! url_new.empty() && m_url != url_new ){ // View履歴のURLを更新 HISTORY::get_history_manager()->replace_url_viewhistory( m_url, url_new ); // ツールバーのURLを更新 get_admin()->set_command( "update_toolbar_url", m_url, url_new ); m_url = url_new; } } // // url 更新 // // 移転があったときなどにadminから呼び出される // void View::update_url( const std::string& url_old, const std::string& url_new ) { if( m_url.rfind( url_old, 0 ) != 0 ) return; std::string url = url_new + m_url.substr( url_old.length() ); #ifdef _DEBUG std::cout << "View::update_url\n"; std::cout << m_url << " -> " << url << std::endl; #endif set_url( url ); } // クロック入力 // clock_in_always()はviewの種類に依らず常に呼び出されるので重い処理を含めてはいけない void View::clock_in_always() { // タブ単位でのオートリロードモード if( m_autoreload_mode == AUTORELOAD_ONCE && inc_autoreload_counter() ) reload(); // キーボード数字入力ジャンプ if( inc_keyjump_counter() ){ goto_num( m_keyjump_num, 0 ); reset_keyjump_counter(); } } // オートリロードのカウンタをインクリメント // 指定秒数を越えたら true を返す bool View::inc_autoreload_counter() { if( m_autoreload_mode == AUTORELOAD_NOT ) return false; ++m_autoreload_counter; if( m_autoreload_counter > m_autoreload_sec * 1000/TIMER_TIMEOUT ){ reset_autoreload_counter(); return true; } return false; } // オートリロードのモードを設定 void View::set_autoreload_mode( int mode, int sec ) { if( ! m_enable_autoreload ) return; if( m_autoreload_mode != AUTORELOAD_NOT && mode != AUTORELOAD_NOT ) return; m_autoreload_mode = mode; m_autoreload_sec = sec; m_autoreload_counter = 0; } // オートリロードのカウンタをリセット void View::reset_autoreload_counter() { m_autoreload_counter = 0; // オートリロードのモードがAUTORELOAD_ONCEの時はオートリロード停止 if( m_autoreload_mode == AUTORELOAD_ONCE ) m_autoreload_mode = AUTORELOAD_NOT; } // 数字入力ジャンプカウンタのインクリメント // 指定秒数を越えたら true を返す bool View::inc_keyjump_counter() { if( ! m_keyjump_counter ) return false; ++m_keyjump_counter; if( m_keyjump_counter > CONFIG::get_numberjmp_msec() / TIMER_TIMEOUT ) return true; return false; } // 数字入力ジャンプカウンタのリセット void View::reset_keyjump_counter() { m_keyjump_counter = 0; m_keyjump_num = 0; } // 数字入力ジャンプ用に sig_key_press() から呼び出す bool View::release_keyjump_key( int key ) { // キーパッド対応 if( key >= GDK_KEY_KP_0 && key <= GDK_KEY_KP_9 ) key = key - GDK_KEY_KP_0 + GDK_KEY_0; if( key >= GDK_KEY_0 && key <= GDK_KEY_9 ){ m_keyjump_counter = 1; m_keyjump_num *= 10; m_keyjump_num += key - '0'; CORE::core_set_command( "set_info", "", std::to_string( m_keyjump_num ) ); return true; } return false; } // view 上にマウスポインタがあれば true bool View::is_mouse_on_view() const { bool ret = false; int x,y; MISC::get_pointer_at_window( get_window(), x, y ); if( x < get_width() && x >= 0 && y < get_height() && y >= 0 ) ret = true; #ifdef _DEBUG std::cout << "View::is_mouse_on_view ret = " << ret << " x= " << x << " y= " << y << " w= " << get_width() << " h= " << get_height() << std::endl; #endif return ret; } // ポップアップメニュー表示 void View::show_popupmenu( const std::string& url, bool use_slot ) { // ポップアップメニューを表示する前にメニューのアクティブ状態を切り替える activate_act_before_popupmenu( url ); Gtk::Menu* popupmenu = get_popupmenu( url ); if( popupmenu ){ #ifdef _DEBUG std::cout << "View::show_popupmenu\n"; #endif const auto result = m_url_popup.insert( url ); if( result.second ) { popupmenu->signal_map().connect( sigc::mem_fun( *this, &View::slot_map_popupmenu ) ); popupmenu->signal_hide().connect( sigc::mem_fun( *this, &View::slot_hide_popupmenu ) ); } if( use_slot ) popupmenu->popup( sigc::mem_fun( *this, &View::slot_popup_menu_position ), 0, gtk_get_current_event_time() ); else popupmenu->popup( 0, gtk_get_current_event_time() ); } } // ポップアップメニュー表示時に表示位置を決めるスロット void View::slot_popup_menu_position( int& x, int& y, bool& push_in) { // viewの左上の座標をセットする int x2, y2; get_window()->get_position( x, y ); translate_coordinates( *dynamic_cast< Gtk::Widget* >( get_toplevel() ), 0, 0, x2, y2 ); x += x2; y += y2; push_in = false; } // // ポップアップメニューがmapしたときに呼ばれるslot // void View::slot_map_popupmenu() { #ifdef _DEBUG std::cout << "View::slot_map_popupmenu\n"; #endif SESSION::set_popupmenu_shown( true ); } // // ポップアップメニューがhideしたときに呼ばれるslot // void View::slot_hide_popupmenu() { #ifdef _DEBUG std::cout << "View::slot_hide_popupmenu\n"; #endif SESSION::set_popupmenu_shown( false ); // もしviewがポップアップウィンドウ上にあって、かつ // メニューを消したときにマウスポインタが領域外にあれば自分自身をhide if( ! is_mouse_on_view() ) sig_hide_popup().emit(); } // // ラベルやステータスバーの色 // std::string View::get_color() const { if( is_broken() ) return "red"; else if( is_old() ) return "blue"; else if( is_overflow() ) return "green"; return ""; } jdim-0.7.0/src/skeleton/view.h000066400000000000000000000300301417047150700162010ustar00rootroot00000000000000// ライセンス: GPL2 #ifndef _VIEW_H #define _VIEW_H #include #include #include #include "control/control.h" namespace SKELETON { class Admin; // 自分がポップアップviewの時に(ポップアップウィンドウ( SKELETON::PopupWin ) 経由で) // 親widgetにhideを依頼するシグナル。PopupWin::PopupWin()でPopupWin::slot_hide_popup()にコネクトされる。 typedef sigc::signal< void > SIG_HIDE_POPUP; // 自分がポップアップviewでリサイズしたときに、明示的にポップアップウィンドウ( SKELETON::PopupWin ) // にリサイズを依頼するシグナル。PopupWin::PopupWin()でPopupWin::slot_resize_popup()にコネクトされる。 typedef sigc::signal< void > SIG_RESIZE_POPUP; class View : public Gtk::VBox { SIG_HIDE_POPUP m_sig_hide_popup; SIG_RESIZE_POPUP m_sig_resize_popup; std::string m_url; Gtk::Window* m_parent_win{}; // クライアント領域の幅、高さ int m_width_client{}; int m_height_client{}; // 入力コントローラ CONTROL::Control m_control; // ポップアップメニュー Glib::RefPtr< Gtk::ActionGroup > m_action_group; Glib::RefPtr< Gtk::UIManager > m_ui_manager; std::set< std::string > m_url_popup; // ツールバーに表示する文字列 std::string m_label; // メインウィンドウのタイトルに表示する文字 std::string m_title; // メインウィンドウのステータスバーに表示する文字 std::string m_status; // true ならマウスジェスチャ使用 bool m_enable_mg{}; // オートリロード bool m_enable_autoreload{}; // true ならオートリロード可能(デフォルト:off) int m_autoreload_mode; // モード int m_autoreload_sec{}; // 何秒おきにリロードするか int m_autoreload_counter{}; // オートリロード用のカウンタ // キーボード数字入力ジャンプ用 int m_keyjump_counter{}; int m_keyjump_num{}; // ロック可能か bool m_lockable; // ロック状態 bool m_locked{}; // 書き込み可能か bool m_writeable; // ツールバーのID (派生クラスでコンテキストが異なる) int m_id_toolbar; // 検索文字列 std::string m_search_query; // ポップアップ時に全ての領域を表示できないならカーソルの上に表示 bool m_popup_upside{}; // ロード時にキャッシュを削除してからviewを再読み込みする bool m_reget{}; protected: // url_new に URL を変更 void set_url( const std::string& url_new ); // Viewが所属するAdminクラス virtual Admin* get_admin() = 0; // UI Glib::RefPtr< Gtk::ActionGroup >& action_group(){ return m_action_group; } Glib::RefPtr< Gtk::UIManager >& ui_manager(){ return m_ui_manager; } // コントローラ CONTROL::Control& get_control(){ return m_control; } // ツールバーに表示するラベル void set_label( const std::string& label ){ m_label = label; } // メインウィンドウのタイトルに表示する文字列 void set_title( const std::string& title ){ m_title = title; } // メインウィンドウのステータスバーに表示する文字 void set_status( const std::string& status ){ m_status = status; } // マウスジェスチャ void set_enable_mg( bool mg ){ m_enable_mg = mg; } bool enable_mg() const { return m_enable_mg; } // オートリロードのカウンタをインクリメント // 指定秒数を越えたら true を返す bool inc_autoreload_counter(); // オートリロード可能/不可能切替え void set_enable_autoreload( bool autoreload ){ m_enable_autoreload = autoreload; } // オートリロードのカウンタをリセット void reset_autoreload_counter(); // オートリロード間隔設定 int get_autoreload_sec() const { return m_autoreload_sec; } void set_autoreload_sec( const int sec ){ m_autoreload_sec = sec; } // オートリロード用のカウンタ int get_autoreload_counter() const { return m_autoreload_counter; } void set_autoreload_counter( const int counter ) { m_autoreload_counter = counter; } // 数字入力ジャンプカウンタのインクリメント // 指定秒数を越えたら true を返す bool inc_keyjump_counter(); // 数字入力ジャンプカウンタのリセット void reset_keyjump_counter(); // 数字入力ジャンプ用に sig_key_press() から呼び出す bool release_keyjump_key( int key ); // ポップアップメニュー表示 void show_popupmenu( const std::string& url, bool use_slot = false ); // ポップアップメニュー表示時に表示位置を決めるスロット void slot_popup_menu_position( int& x, int& y, bool& push_in ); // ポップアップメニューがmapした時に呼び出されるスロット void slot_map_popupmenu(); // ポップアップメニューがhideした時に呼び出されるスロット void slot_hide_popupmenu(); // ポップアップメニューを表示する前にメニューのアクティブ状態を切り替える virtual void activate_act_before_popupmenu( const std::string& url ){} // ポップアップメニュー取得 virtual Gtk::Menu* get_popupmenu( const std::string& url ){ return nullptr; } public: SIG_HIDE_POPUP sig_hide_popup(){ return m_sig_hide_popup; } SIG_RESIZE_POPUP sig_resize_popup(){ return m_sig_resize_popup; } explicit View( const std::string& url, const std::string& arg1 = {}, const std::string& arg2 = {} ); ~View() noexcept = default; virtual void save_session() = 0; virtual const std::string& get_url() const { return m_url; } const std::string& get_url_admin(); virtual void set_parent_win( Gtk::Window* parent_win ){ m_parent_win = parent_win; } virtual Gtk::Window* get_parent_win(){ return m_parent_win; } // 移転があったときなどにadminから呼び出される virtual void update_url( const std::string& url_old, const std::string& url_new ); // 検索文字列 const std::string& get_search_query() const { return m_search_query; } // ツールバーのID // タブの切り替えのときに Admin から参照される // コンストラクタであらかじめ指定しておくこと void set_id_toolbar( const int id ) { m_id_toolbar = id; } int get_id_toolbar() const { return m_id_toolbar; } // ロック/アンロック bool is_lockable() const { return m_lockable; } void set_lockable( const bool lockable ){ m_lockable = lockable; } bool is_locked() const { return m_locked; } virtual void lock(){ m_locked = true; } virtual void unlock(){ m_locked = false; } // 書き込み可能/不可能 bool is_writeable() const { return m_writeable; } void set_writeable( const bool writeable ){ m_writeable = writeable; } // ポップアップ時に全ての領域を表示できないならカーソルの上に表示 bool get_popup_upside() const { return m_popup_upside; } void set_popup_upside( const bool upside ){ m_popup_upside = upside; } // view 上にマウスポインタがあれば true bool is_mouse_on_view() const; // 各view個別のコマンド virtual bool set_command( const std::string& command, const std::string& arg1 = {}, const std::string& arg2 = {} ) { return true; } // コピー用のURL virtual std::string url_for_copy() const { return m_url; } // ツールバーのラベルに表示する文字列 const std::string& get_label() const { return m_label; } // メインウィンドウのタイトルバーに表示する文字列 virtual const std::string& get_title() const { return m_title; } // メインウィンドウのステータスバーに表示する文字列 virtual const std::string& get_status() const { return m_status; } // クライアント領域の幅、高さ virtual int width_client() const { return m_width_client; } virtual int height_client() const { return m_height_client; } void set_width_client( int val ){ m_width_client = val; } void set_height_client( int val ){ m_height_client = val; } // オートリロード可能か bool get_enable_autoreload() const { return m_enable_autoreload; } // オートリロードのモード設定 void set_autoreload_mode( int mode, int sec ); // 現在のオートリロードのモード取得 int get_autoreload_mode() const { return m_autoreload_mode; } // ロード時にキャッシュを削除してからviewを再読み込みする bool get_reget() const{ return m_reget; } void set_reget( const bool reget ){ m_reget = reget; } // アイコンのID取得 virtual int get_icon( const std::string& iconname ) const { return -1; } // ロード中 virtual bool is_loading() const { return false; } // 更新した virtual bool is_updated() const { return false; } // 更新チェックして更新可能か virtual bool is_check_update() const { return false; } // 古いデータか virtual bool is_old() const { return false; } // 壊れているか virtual bool is_broken() const { return false; } // レス数が最大表示可能数以上か virtual bool is_overflow() const noexcept { return false; } // ラベルやステータスバーの色 std::string get_color() const; // キーを押した virtual bool slot_key_press( GdkEventKey* event ){ return false; } // クロック入力 // clock_in()はビューがアクティブのときに呼び出される // clock_in_always()はviewの種類に依らず常に呼び出されるので重い処理を含めてはいけない virtual void clock_in(){}; virtual void clock_in_always(); virtual void write(){} virtual void reload(){} virtual void stop(){} virtual void show_view(){} virtual void redraw_view(){} virtual void relayout(){} virtual void update_view(){} virtual void update_finish(){} virtual void focus_view(){} virtual void focus_out(){} virtual void close_view(){} virtual void delete_view(){} virtual void set_favorite(){} virtual void update_item( const std::string& url, const std::string& id ){} virtual bool operate_view( const int ) = 0; virtual void goto_top(){} virtual void goto_bottom(){} virtual void goto_num( const int num_to, const int num_from ){} virtual void scroll_up(){} virtual void scroll_down(){} virtual void scroll_left(){} virtual void scroll_right(){} virtual void show_preference(){} virtual void update_boardname(){} // 進む、戻る virtual void back_viewhistory( const int count ){} virtual void forward_viewhistory( const int count ){} // 検索 virtual void exec_search(){} virtual void up_search(){} virtual void down_search(){} virtual void operate_search( const std::string& controlid ){} virtual void set_search_query( const std::string& query ){ m_search_query = query; } }; } #endif jdim-0.7.0/src/skeleton/viewnote.cpp000066400000000000000000000005771417047150700174370ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "viewnote.h" #include "config/globalconf.h" using namespace SKELETON; ViewNotebook::ViewNotebook( DragableNoteBook* ) : Gtk::Notebook() { set_show_border( true ); set_show_tabs( false ); set_border_width( CONFIG::get_view_margin() ); } ViewNotebook::~ViewNotebook() noexcept = default; jdim-0.7.0/src/skeleton/viewnote.h000066400000000000000000000007041417047150700170740ustar00rootroot00000000000000// ライセンス: GPL2 // // DragableNoteBookを構成するview表示用の Notebook // // TODO: 使われなくなったコンストラクタの引数を整理する #ifndef _VIEWNOTE_H #define _VIEWNOTE_H #include namespace SKELETON { class DragableNoteBook; class ViewNotebook : public Gtk::Notebook { public: explicit ViewNotebook( DragableNoteBook* ); ~ViewNotebook() noexcept; }; } #endif jdim-0.7.0/src/skeleton/vpaned.cpp000066400000000000000000000022711417047150700170450ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "vpaned.h" using namespace SKELETON; JDVPaned::JDVPaned( const int fixmode ) : Gtk::Paned( Gtk::ORIENTATION_VERTICAL ) , m_pctrl( *this, fixmode ) {} JDVPaned::~JDVPaned() noexcept = default; void JDVPaned::on_realize() { Gtk::Paned::on_realize(); m_pctrl.update_position(); } bool JDVPaned::on_button_press_event( GdkEventButton* event ) { m_pctrl.button_press_event( event ); return Gtk::Paned::on_button_press_event( event ); } bool JDVPaned::on_button_release_event( GdkEventButton* event ) { m_pctrl.button_release_event( event ); return Gtk::Paned::on_button_release_event( event ); } bool JDVPaned::on_motion_notify_event( GdkEventMotion* event ) { m_pctrl.motion_notify_event( event ); return Gtk::Paned::on_motion_notify_event( event ); } bool JDVPaned::on_enter_notify_event( GdkEventCrossing* event ) { m_pctrl.enter_notify_event( event ); return Gtk::Paned::on_enter_notify_event( event ); } bool JDVPaned::on_leave_notify_event( GdkEventCrossing* event ) { m_pctrl.leave_notify_event( event ); return Gtk::Paned::on_leave_notify_event( event ); } jdim-0.7.0/src/skeleton/vpaned.h000066400000000000000000000014461417047150700165150ustar00rootroot00000000000000// ライセンス: GPL2 // // VPanedクラス // #ifndef _VPANED_H #define _VPANED_H #include #include "panecontrol.h" namespace SKELETON { class JDVPaned : public Gtk::Paned { VPaneControl m_pctrl; public: explicit JDVPaned( const int fixmode ); ~JDVPaned() noexcept; VPaneControl& get_ctrl(){ return m_pctrl; } protected: void on_realize() override; bool on_button_press_event( GdkEventButton* event ) override; bool on_button_release_event( GdkEventButton* event ) override; bool on_motion_notify_event( GdkEventMotion* event ) override; bool on_enter_notify_event( GdkEventCrossing* event ) override; bool on_leave_notify_event( GdkEventCrossing* event ) override; }; } #endif jdim-0.7.0/src/skeleton/window.cpp000066400000000000000000000530701417047150700171020ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "window.h" #include "config/globalconf.h" #include "environment.h" #include "global.h" #include "session.h" #include "dndmanager.h" #include "command.h" #include enum { MGINFO_CHARS = MAX_MG_LNG * 2 + 16, // マウスジェスチャ表示欄の文字数 FOCUSOUT_TIMEOUT = 250, // GNOME環境でフォーカスを外すまでの時間 ( msec ) FOCUS_TIME = 100, // JDWindowにフォーカスを移してウィンドウサイズを復元するまでの時間 ( msec ) UNGRAB_TIME = 100, // ブート直後にフォーカスをメインウィンドウに戻すまでの時間 ( msec ) JDWIN_FOLDSIZE = 10 // 折りたたみ時に指定するウィンドウ高さ }; // ウィンドウ状態 enum { JDWIN_INIT = 0, JDWIN_NORMAL, // 開いている JDWIN_FOLD, // 折り畳んでいる JDWIN_EXPANDING, // 展開中( clock_in() 参照 ) JDWIN_UNMAX, // 最大化 -> 通常 JDWIN_UNMAX_FOLD, // 最大化 -> 折り畳み JDWIN_HIDE, // hide 中 JDWIN_UNGRAB // ブート直後にungrabする( clock_in() 参照 ) }; ////////////////////////////////////////////// using namespace SKELETON; constexpr const char* JDWindow::s_css_stat_label; // メッセージウィンドウでは m_mginfo が不要なので need_mginfo = false になる JDWindow::JDWindow( const bool fold_when_focusout, const bool need_mginfo ) : Gtk::Window( Gtk::WINDOW_TOPLEVEL ) , m_fold_when_focusout( fold_when_focusout ) , m_boot( true ) , m_enable_fold( m_fold_when_focusout ) , m_mode( JDWIN_INIT ) { // ステータスバー m_label_stat.set_size_request( 0, -1 ); m_label_stat.set_xalign( 0 ); m_label_stat.set_selectable( true ); m_label_stat.set_single_line_mode( true ); m_label_stat.set_ellipsize( Pango::ELLIPSIZE_END ); m_label_stat_ebox.add( m_label_stat ); m_label_stat_ebox.set_visible_window( false ); m_statbar.pack_start( m_label_stat_ebox ); if( need_mginfo ){ m_mginfo_ebox.add( m_mginfo ); m_mginfo_ebox.set_visible_window( false ); m_statbar.pack_start( m_mginfo_ebox, Gtk::PACK_SHRINK ); } m_mginfo.set_width_chars( MGINFO_CHARS ); m_mginfo.set_xalign( 0 ); m_statbar.show_all_children(); add( m_vbox ); m_gtkwidget = GTK_WIDGET( gobj() ); m_gtkwindow = GTK_WINDOW( gobj() ); gpointer parent_class = g_type_class_peek_parent( G_OBJECT_GET_CLASS( gobj() ) ); m_grand_parent_class = g_type_class_peek_parent( parent_class ); auto context = m_label_stat.get_style_context(); context->add_class( s_css_stat_label ); context->add_provider( m_stat_provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION ); if( need_mginfo ) { context = m_mginfo.get_style_context(); context->add_class( s_css_stat_label ); context->add_provider( m_stat_provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION ); } try { m_stat_provider->load_from_data( ".red:not(:selected), .red:active:not(:selected) { color: white; background-color: red; }" ".green:not(:selected), .green:active:not(:selected) { color: white; background-color: green; }" ".blue:not(:selected), .blue:active:not(:selected) { color: white; background-color: blue; }" ); } catch( Gtk::CssProviderError& err ) { #ifdef _DEBUG std::cout << "ERROR:JDWindow::JDWindow css fail " << err.what() << std::endl; #endif } } // windowの初期設定(サイズ変更や移動など) void JDWindow::init_win() { // フォーカスアウトで折り畳む場合 if( m_fold_when_focusout ){ m_scrwin = std::make_unique(); m_vbox_view = std::make_unique(); m_scrwin->set_size_request( 0, 0 ); m_scrwin->set_policy( Gtk::POLICY_EXTERNAL, Gtk::POLICY_EXTERNAL ); m_scrwin->add( *m_vbox_view ); m_vbox.pack_remove_end( false, *m_scrwin, Gtk::PACK_EXPAND_WIDGET ); set_skip_taskbar_hint( true ); resize( get_width_win(), 1 ); move_win( get_x_win(), get_y_win() ); focus_out(); property_window_position().set_value( Gtk::WIN_POS_NONE ); set_transient( true ); Glib::signal_idle().connect( sigc::mem_fun( *this, &JDWindow::slot_idle ) ); } // 通常のウィンドウ else{ resize( get_width_win(), get_height_win() ); move_win( get_x_win(), get_y_win() ); if( is_maximized_win() ) maximize_win(); property_window_position().set_value( Gtk::WIN_POS_NONE ); set_shown_win( true ); Glib::signal_idle().connect( sigc::mem_fun( *this, &JDWindow::slot_idle ) ); } } bool JDWindow::slot_idle() { // ブート完了 if( m_boot ){ #ifdef _DEBUG std::cout << "----------------\nJDWinow::slot_idle boot end mode = " << m_mode << std::endl; #endif m_boot = false; move_win( get_x_win(), get_y_win() ); if( m_fold_when_focusout ){ // 遅延させて clock_in()の中でフォーカスをメインウィンドウに戻す if( m_mode == JDWIN_FOLD ){ m_mode = JDWIN_UNGRAB; m_counter = 0; } } CORE::core_set_command( "window_boot_fin" ); } return false; } // クロック入力 void JDWindow::clock_in() { // 折りたたみ処理 if( m_fold_when_focusout ){ // 遅延リサイズ( focus_in()にある説明を参照 ) if( m_mode == JDWIN_EXPANDING ){ constexpr int waitcount = FOCUS_TIME / TIMER_TIMEOUT; ++m_counter; if( m_counter > waitcount && ! ( m_counter % waitcount ) ){ if( get_height() < get_height_win() ) resize( get_width_win(), get_height_win() ); else{ #ifdef _DEBUG std::cout << "JDWindow::clock_in resize\n"; #endif m_mode = JDWIN_NORMAL; set_shown_win( true ); present(); } } } // ブート直後にフォーカスをメインウィンドウに戻す else if( m_mode == JDWIN_UNGRAB ){ ++m_counter; if( m_counter > UNGRAB_TIME / TIMER_TIMEOUT ){ #ifdef _DEBUG std::cout << "JDWindow::clock_in ungrab\n"; #endif // WMによってはフォーカスが外れない時があるのでlower()して // 無理矢理フォーカスを外す set_transient( false ); get_window()->lower(); set_transient( true ); CORE::core_set_command( "restore_focus", "", "present" ); m_mode = JDWIN_FOLD; } } // GNOME環境ではタスクトレイなどで切り替えたときに画像windowがフォーカスされてしまうので // メインウィンドウと画像ウィンドウが同時にフォーカスアウトしたら // 一時的に transient 指定を外す。メインウィンドウがフォーカスインしたときに // Admin::focus_out() で transient 指定を戻す using ENVIRONMENT::DesktopType; const DesktopType wm = ENVIRONMENT::get_wm(); if( ( wm == DesktopType::gnome || wm == DesktopType::mate || wm == DesktopType::cinnamon ) && ! SESSION::is_iconified_win_main() // メインウィンドウが最小化しているときに transient を外すとウィンドウが表示されなくなる && ! SESSION::is_focus_win_main() && ! is_focus_win() ){ constexpr int waitcount = FOCUSOUT_TIMEOUT / TIMER_TIMEOUT; if( m_count_focusout < waitcount ) ++m_count_focusout; if( m_count_focusout == waitcount ){ #ifdef _DEBUG std::cout << "JDWindow::clock_in focus timeout\n"; #endif set_transient( false ); ++m_count_focusout; } } else m_count_focusout = 0; } } void JDWindow::set_spacing( int space ) { m_vbox.set_spacing( space ); } void JDWindow::maximize_win() { set_maximized_win( true ); maximize(); } void JDWindow::unmaximize_win() { set_maximized_win( false ); unmaximize(); } void JDWindow::iconify_win() { set_iconified_win( true ); iconify(); } // // ウィンドウ移動 // void JDWindow::move_win( const int x, const int y ) { if( ! CONFIG::get_manage_winpos() ) return; #ifdef _DEBUG std::cout << "JDWindow::move_win " << "x = " << x << " y = " << y << std::endl; #endif move( x, y ); set_x_win( x ); set_y_win( y ); // compiz 環境などでは move() で指定した座標がズレるので補正する m_win_moved = true; } // // ウィンドウ座標取得 // void JDWindow::set_win_pos() { if( ! get_window() ) return; int x,y; get_window()->get_root_origin( x, y ); #ifdef _DEBUG std::cout << "JDWindow::set_win_pos " << "x = " << x << " / " << get_x_win() << ", y = " << y << " / " << get_y_win() << std::endl; #endif // compiz 環境などでは move() で指定した座標がズレるので補正する if( m_win_moved ){ if( x != get_x_win() || y != get_y_win() ){ // 補正量がmrgを越えたら補正を諦める const int mrg = 64; const int delta_x = x - get_x_win(); const int delta_y = y - get_y_win(); x = get_x_win(); y = get_y_win(); m_win_moved = false; if( abs( delta_x ) <= mrg && abs( delta_y ) <= mrg ){ move( get_x_win() - delta_x, get_y_win() - delta_y ); #ifdef _DEBUG std::cout << "!!! moved x = " << get_x_win() << " y = " << get_y_win() << " dx = " << delta_x << " dy = " << delta_y << std::endl; #endif } } } set_x_win( x ); set_y_win( y ); } // hide 中 bool JDWindow::is_hide() const { return ( m_mode == JDWIN_HIDE ); } void JDWindow::pack_remove_start( bool unpack, Widget& child, Gtk::PackOptions options, guint padding ) { if( m_fold_when_focusout ){ m_vbox_view->pack_remove_start( unpack, child, options, padding ); if( ! unpack ) m_vbox_view->show_all_children(); } else{ m_vbox.pack_remove_start( unpack, child, options, padding ); if( ! unpack ) m_vbox.show_all_children(); } } void JDWindow::pack_remove_end( bool unpack, Widget& child, Gtk::PackOptions options, guint padding ) { if( m_fold_when_focusout ){ m_vbox_view->pack_remove_end( unpack, child, options, padding ); if( ! unpack ) m_vbox_view->show_all_children(); } else{ m_vbox.pack_remove_end( unpack, child, options, padding ); if( ! unpack ) m_vbox.show_all_children(); } } // ステータスバー表示 void JDWindow::set_status( const std::string& stat ) { if( stat == m_status ) return; m_status = stat; m_label_stat.set_text( stat ); m_label_stat_ebox.set_tooltip_text( stat ); } // 一時的にステータスバーの表示を変える( マウスオーバーでのURL表示用 ) // // 恒久的に変えてしまうと、マウススオ-バー中に ArticleViewBase では // ないクラスから表示を変更された場合に本来の表示に戻せなくなる。 void JDWindow::set_status_temporary( const std::string& stat ) { if( stat == m_status ) return; m_label_stat.set_text( stat ); } // 一時的に変えたステータスバーの表示を戻す void JDWindow::restore_status() { m_label_stat.set_text( m_status ); } // マウスジェスチャ表示 void JDWindow::set_mginfo( const std::string& mginfo ) { if( m_mginfo.get_realized() ) { m_mginfo.set_text( mginfo ); } } // ステータスの色を変える void JDWindow::set_status_color( const std::string& color ) { #ifdef _DEBUG std::cout << "JDWindow::set_status_color " << color << std::endl; #endif auto context = m_label_stat.get_style_context(); context->remove_class( "red" ); context->remove_class( "green" ); context->remove_class( "blue" ); if( ! color.empty() ) context->add_class( color ); if( m_mginfo.get_realized() ) { context = m_mginfo.get_style_context(); context->remove_class( "red" ); context->remove_class( "green" ); context->remove_class( "blue" ); if( ! color.empty() ) context->add_class( color ); } } // メインウィンドウに対して transient 設定 void JDWindow::set_transient( bool set ) { if( m_fold_when_focusout ){ #ifdef _DEBUG std::cout << "JDWindow::set_transient set = " << set << " " << m_transient << std::endl; #endif if( set && ! m_transient && CORE::get_mainwindow() ){ set_transient_for( *CORE::get_mainwindow() ); m_transient = true; } // transientを外す else if( ! set && m_transient ){ unset_transient_for(); m_transient = false; } } } // // ダイアログ表示などでフォーカスが外れてもウインドウを畳まないようにする // void JDWindow::set_enable_fold( bool enable ) { if( m_fold_when_focusout && m_enable_fold != enable ){ #ifdef _DEBUG std::cout << "JDWindow::set_enable_fold " << enable << std::endl; #endif m_enable_fold = enable; // XFCE 環境の場合はここでpresent()しておかないとフォーカスが外れる if( m_mode == JDWIN_NORMAL && m_enable_fold ){ if( ENVIRONMENT::get_wm() == ENVIRONMENT::DesktopType::kde ) switch_admin(); else present(); } } } // フォーカスイン void JDWindow::focus_in() { // 折りたたみ処理 if( m_fold_when_focusout ){ // メインウィンドウが最小化しているときはメインウィンドウを開く if( SESSION::is_iconified_win_main() ){ m_mode = JDWIN_UNGRAB; m_counter = 0; return; } if( ! m_enable_fold ) return; show(); if( is_iconified_win() ) deiconify(); if( ! is_maximized_win() && get_window() ){ int x, y; get_window()->get_root_origin( x, y ); if( x != get_x_win() || y != get_y_win() ) move_win( get_x_win(), get_y_win() ); } // 開く // // GNOME環境では focus in 動作中に resize() が失敗する時が // あるので、遅延させて clock_in() の中でリサイズとpresentする if( ! is_maximized_win() && m_mode != JDWIN_EXPANDING ){ m_mode = JDWIN_EXPANDING; m_counter = 0; } #ifdef _DEBUG std::cout << "JDWindow::focus_in mode = " << m_mode << " maximized = " << is_maximized_win() << " iconified = " << is_iconified_win() << std::endl; #endif } else{ show(); if( is_iconified_win() ) deiconify(); present(); } } // フォーカスアウト void JDWindow::focus_out() { // 折りたたみ処理 if( m_fold_when_focusout && m_enable_fold ){ // ポップアップメニューを表示しているかD&D中はfocus_outしない if( SESSION::is_popupmenu_shown() ) return; if( CORE::DND_Now_dnd() ) return; // 最大化している時は通常状態に戻しておかないと表示されなくなる if( is_maximized_win() ) unmaximize(); // 折り畳み if( m_mode != JDWIN_FOLD ){ resize( get_width_win(), JDWIN_FOLDSIZE ); m_mode = JDWIN_FOLD; set_shown_win( false ); CORE::core_set_command( "restore_focus" ); } #ifdef _DEBUG std::cout << "JDWindow::focus_out mode = " << m_mode << " maximized = " << is_maximized_win() << " iconified = " << is_iconified_win() << std::endl; #endif } } // フォーカスインイベント bool JDWindow::on_focus_in_event( GdkEventFocus* event ) { set_focus_win( true ); if( ! m_boot ){ // 折りたたみ処理 if( m_fold_when_focusout ){ #ifdef _DEBUG std::cout << "JDWindow::on_focus_in_event\n"; #endif if( m_mode != JDWIN_UNGRAB ) switch_admin(); } } return Gtk::Window::on_focus_in_event( event ); } // フォーカスアウトイベント bool JDWindow::on_focus_out_event( GdkEventFocus* event ) { set_focus_win( false ); if( ! m_boot ){ // 折りたたみ処理 if( m_fold_when_focusout ){ #ifdef _DEBUG std::cout << "JDWindow::on_focus_out_event\n"; #endif focus_out(); } } return Gtk::Window::on_focus_out_event( event ); } // Xボタンを押した bool JDWindow::on_delete_event( GdkEventAny* event ) { #ifdef _DEBUG std::cout << "JDWindow::on_delete_event\n"; #endif // 折りたたみ処理 if( m_fold_when_focusout ){ if( is_maximized_win() ) unmaximize(); else{ // hideする前に座標保存 if( ! is_maximized_win() && ! is_iconified_win() && get_window() ) set_win_pos(); hide(); m_mode = JDWIN_HIDE; set_shown_win( false ); } return true; } return Gtk::Window::on_delete_event( event ); } // 最大、最小化 bool JDWindow::on_window_state_event( GdkEventWindowState* event ) { const bool maximized = event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED; const bool iconified = event->new_window_state & GDK_WINDOW_STATE_ICONIFIED; const bool fullscreen = event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN; #ifdef _DEBUG std::cout << "JDWindow::on_window_state_event : " << "maximized = " << is_maximized_win() << " -> " << maximized << " / iconified = " << is_iconified_win() << " -> " << iconified << " / full = " << is_full_win() << " -> " << fullscreen << std::endl; #endif if( ! m_boot ){ // 通常 -> アイコン化 if( ! is_iconified_win() && iconified ) set_shown_win( false ); // アイコン -> 通常 else if( is_iconified_win() && ! iconified && m_mode != JDWIN_FOLD ) set_shown_win( true ); // 通常 -> 最大化 if( ! is_maximized_win() && maximized ){ m_mode = JDWIN_NORMAL; set_shown_win( true ); } // 最大 -> 折り畳み or 通常 else if( is_maximized_win() && ! maximized ){ // 最大 -> 折り畳み if( m_fold_when_focusout && m_mode == JDWIN_FOLD ) m_mode = JDWIN_UNMAX_FOLD; // 最大 -> 通常 else m_mode = JDWIN_UNMAX; } // 通常 -> 全画面 else if( ! is_full_win() && fullscreen ) set_full_win( true ); // 全画面 -> 通常 else if( is_full_win() && ! fullscreen ) set_full_win( false ); #ifdef _DEBUG std::cout << " mode = " << m_mode << std::endl; #endif } set_maximized_win( maximized ); set_iconified_win( iconified ); return Gtk::Window::on_window_state_event( event ); } // 移動、サイズ変更イベント bool JDWindow::on_configure_event( GdkEventConfigure* event ) { const int mrg = 16; const int width_new = event->width; const int height_new = event->height; int min_height = 0; if( m_scrwin ) min_height = m_vbox.get_height() - m_scrwin->get_height() + mrg; if( ! m_boot ){ #ifdef _DEBUG std::cout << "JDWindow::on_configure_event" << " boot = " << m_boot << " mode = " << m_mode << " x = " << event->x << " y = " << event->y << " w = " << width_new << " h = " << height_new << " min_height = " << min_height << std::endl; #endif // 最大 -> 通常に戻る時はリサイズをキャンセル if( m_mode == JDWIN_UNMAX ) m_mode = JDWIN_NORMAL; else if( m_mode == JDWIN_UNMAX_FOLD ) m_mode = JDWIN_FOLD; // 最大、最小化しているときは除く else if( ! is_maximized_win() && ! is_iconified_win() && ! is_full_win() ){ set_win_pos(); // サイズ変更 if( ( ! m_fold_when_focusout || m_mode == JDWIN_NORMAL || m_mode == JDWIN_FOLD ) && height_new > min_height ) { set_width_win( width_new ); set_height_win( height_new ); } } #ifdef _DEBUG std::cout << "configure fin --> mode = " << m_mode << " show = " << is_shown_win() << " maximized = " << is_maximized_win() << " iconified = " << is_iconified_win() << " x = " << get_x_win() << " y = " << get_y_win() << " w = " << get_width_win() << " height = " << get_height_win() << std::endl; #endif } return Gtk::Window::on_configure_event( event ); } // uimなど、漢字変換モードの途中でctrl+qを押すとキーアクセレータが // 優先されてJDが終了する問題があった。 // // gedit-window.c の gedit_window_key_press_event を見ると // gtk_window_propagate_key_event() を実行した後でキーアクセレータ // の処理をするようにしていたのでJDもそうした。 bool JDWindow::on_key_press_event( GdkEventKey* event ) { if( gtk_window_propagate_key_event( m_gtkwindow, event ) ) return true; if( gtk_window_activate_key( m_gtkwindow, event ) ) return true; #ifdef _DEBUG std::cout << "JDWindow::on_key_press_event key = " << event->keyval << std::endl; std::cout << m_grand_parent_class << " - " << m_gtkwidget << std::endl; #endif return GTK_WIDGET_CLASS( m_grand_parent_class )->key_press_event( m_gtkwidget, event ); } jdim-0.7.0/src/skeleton/window.h000066400000000000000000000107301417047150700165430ustar00rootroot00000000000000// ライセンス: GPL2 // // Window クラス // #ifndef _JDWINDOW_H #define _JDWINDOW_H #include "gtkmmversion.h" #include "vbox.h" #include #include namespace SKELETON { class JDWindow : public Gtk::Window { GtkWidget* m_gtkwidget{}; GtkWindow* m_gtkwindow{}; gpointer m_grand_parent_class{}; bool m_win_moved{}; // フォーカスアウト時の折りたたみ処理で用いるメンバ変数 bool m_fold_when_focusout; // フォーカスアウトしたときにウィンドウを畳むか bool m_boot; bool m_enable_fold; // 「一時的に」折りたたみ可能かどうか切り替える bool m_transient{}; int m_mode; int m_counter{}; int m_count_focusout{}; // フォーカス制御用カウンタ SKELETON::JDVBox m_vbox; std::unique_ptr m_scrwin; std::unique_ptr m_vbox_view; // ステータスバー std::string m_status; Gtk::HBox m_statbar; Gtk::Label m_label_stat; Gtk::EventBox m_label_stat_ebox; Gtk::EventBox m_mginfo_ebox; Gtk::Label m_mginfo; static constexpr const char* s_css_stat_label = u8"jd-stat-label"; Glib::RefPtr< Gtk::CssProvider > m_stat_provider = Gtk::CssProvider::create(); public: explicit JDWindow( const bool fold_when_focusout, const bool need_mginfo = true ); ~JDWindow() noexcept = default; Gtk::HBox& get_statbar(){ return m_statbar; } virtual void clock_in(); // 最大、最小化 void maximize_win(); void unmaximize_win(); void iconify_win(); void set_spacing( int space ); // hide 中 bool is_hide() const; // 起動中 bool is_booting() const { return m_boot; } void pack_remove_start( bool unpack, Widget& child, Gtk::PackOptions options = Gtk::PACK_EXPAND_WIDGET, guint padding = 0 ); void pack_remove_end( bool unpack, Widget& child, Gtk::PackOptions options = Gtk::PACK_EXPAND_WIDGET, guint padding = 0 ); void set_status( const std::string& stat ); void set_status_temporary( const std::string& stat ); void restore_status(); std::string get_status() const { return m_status; } void set_mginfo( const std::string& mginfo ); // ステータスの色を変える void set_status_color( const std::string& color ); // メインウィンドウに対して transient 設定 void set_transient( bool set ); // ダイアログ表示などでフォーカスが外れてもウインドウを畳まないようにする void set_enable_fold( bool enable ); virtual void focus_in(); virtual void focus_out(); protected: SKELETON::JDVBox& get_vbox(){ return m_vbox;} // windowの初期設定(サイズ変更や移動など) void init_win(); virtual void switch_admin(){} virtual int get_x_win() const = 0; virtual int get_y_win() const = 0; virtual void set_x_win( const int x ) = 0; virtual void set_y_win( const int y ) = 0; virtual int get_width_win() const = 0; virtual int get_height_win() const = 0; virtual void set_width_win( const int width ) = 0; virtual void set_height_win( const int height ) = 0; virtual bool is_focus_win() const = 0; virtual void set_focus_win( const bool set ) = 0; virtual bool is_maximized_win() const = 0; virtual void set_maximized_win( const bool set ) = 0; virtual bool is_iconified_win() const = 0; virtual void set_iconified_win( const bool set ) = 0; virtual bool is_full_win() const = 0; virtual void set_full_win( const bool set ) = 0; virtual bool is_shown_win() const = 0; virtual void set_shown_win( const bool set ) = 0; bool on_focus_in_event( GdkEventFocus* event ) override; bool on_focus_out_event( GdkEventFocus* event ) override; bool on_delete_event( GdkEventAny* event ) override; bool on_window_state_event( GdkEventWindowState* event ) override; bool on_configure_event( GdkEventConfigure* event ) override; bool on_key_press_event( GdkEventKey* event ) override; private: bool slot_idle(); void move_win( const int x, const int y ); void set_win_pos(); }; } #endif jdim-0.7.0/src/sound/000077500000000000000000000000001417047150700143665ustar00rootroot00000000000000jdim-0.7.0/src/sound/Makefile.am000066400000000000000000000003321417047150700164200ustar00rootroot00000000000000noinst_LIBRARIES = libsound.a libsound_a_SOURCES = \ playsound.cpp \ soundmanager.cpp noinst_HEADERS = \ playsound.h \ soundmanager.h AM_CXXFLAGS = @GTKMM_CFLAGS@ @ALSA_CFLAGS@ AM_CPPFLAGS = -I$(top_srcdir)/src jdim-0.7.0/src/sound/meson.build000066400000000000000000000003461417047150700165330ustar00rootroot00000000000000sources = [ 'playsound.cpp', 'soundmanager.cpp', ] deps = [ alsa_dep, gtkmm_dep, ] sound_lib = static_library( 'sound', [sources, config_h], dependencies : deps, include_directories : include_directories('..'), ) jdim-0.7.0/src/sound/playsound.cpp000066400000000000000000000145371417047150700171220ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "playsound.h" #ifdef USE_ALSA #include "jdlib/miscmsg.h" #include #include #include #include #include using namespace SOUND; #ifdef WORDS_BIGENDIAN namespace { std::uint16_t byteswap16( std::uint16_t us ) { return ((us & 0xFF00) >> 8) | ((us & 0x00FF) << 8); } std::uint32_t byteswap32( std::uint32_t ui ) { return ((ui & 0xFF00'0000) >> 24) | ((ui & 0x00FF'0000) >> 8) | ((ui & 0x0000'FF00) << 8) | ((ui & 0x0000'00FF) << 24); } } // namespace #endif // WORDS_BIGENDIAN Play_Sound::Play_Sound() { #ifdef _DEBUG std::cout << "Play_Sound::Play_Sound\n"; #endif } Play_Sound::~Play_Sound() { #ifdef _DEBUG std::cout << "Play_Sound::~Play_Sound\n"; #endif // デストラクタの中からdispatchを呼ぶと落ちるので dispatch不可にする set_dispatchable( false ); stop(); wait(); } void Play_Sound::stop() { if( ! is_playing() ) return; #ifdef _DEBUG std::cout << "Play_Sound::stop\n"; #endif m_stop = true; wait(); } void Play_Sound::wait() { m_thread.join(); } // // wav 再生スレッド起動 // void Play_Sound::play( const std::string& wavfile ) { if( wavfile.empty() ) return; if( m_thread.is_running() ){ MISC::ERRMSG( "Play_Sound::play : thread has been running" ); return; } m_wavfile = wavfile; m_stop = false; if( ! m_thread.create( ( STARTFUNC ) launcher, ( void * ) this, JDLIB::NODETACH ) ){ MISC::ERRMSG( "Play_Sound::play : could not start thread" ); } else{ m_playing = true; } } // // スレッドのランチャ (static) // void* Play_Sound::launcher( void* dat ) { Play_Sound* ps = ( Play_Sound * ) dat; ps->play_wavfile(); return nullptr; } // // wav 再生 スレッド // void Play_Sound::play_wavfile() { if( m_wavfile.empty() ) return; #ifdef _DEBUG std::cout << "Play_Sound::play_wavfile file = " << m_wavfile << std::endl; #endif FILE* fin = nullptr; snd_pcm_t *handle = nullptr; try{ fin = fopen( m_wavfile.c_str(), "rb" ); if( ! fin ) throw "cannot open " + m_wavfile; // フォーマット取得 size_t filepos = 0; RIFFCHK riffchk; size_t chksize = 8; filepos += fread( &riffchk, 1, chksize, fin ); #ifdef WORDS_BIGENDIAN // RIFFはリトルエンディアンで保存されるためビッグエンディアンの環境ではバイトオーダーを逆にする riffchk.size = byteswap32( riffchk.size ); #endif if( strncmp( riffchk.id, "RIFF", 4 ) != 0 ) throw m_wavfile + " is not a wave file"; WAVEFMTCHK wavefmt; chksize = 28; filepos += fread( &wavefmt, 1, chksize, fin ); #ifdef WORDS_BIGENDIAN wavefmt.size = byteswap32( wavefmt.size ); wavefmt.fmt = byteswap16( wavefmt.fmt ); wavefmt.chn = byteswap16( wavefmt.chn ); wavefmt.rate = byteswap32( wavefmt.rate ); wavefmt.average = byteswap32( wavefmt.average ); wavefmt.block = byteswap16( wavefmt.block ); wavefmt.bit = byteswap16( wavefmt.bit ); #endif if( strncmp( wavefmt.id, "WAVEfmt ", 8 ) != 0 ) throw m_wavfile + " is broken"; if( wavefmt.fmt != 1 ) throw m_wavfile + " is not a PCM format"; // データチャンクまでseek DATACHK datachk; chksize = 8; for(;;){ fseek( fin, filepos, SEEK_SET ); if( ! fread( &datachk, 1, chksize, fin ) ) throw m_wavfile + " is broken"; if( strncmp( datachk.id, "data", 4 ) == 0 ) break; ++filepos; } #ifdef WORDS_BIGENDIAN datachk.size = byteswap32( datachk.size ); #endif #ifdef _DEBUG std::cout << "rate = " << wavefmt.rate << std::endl << "chn = " << wavefmt.chn << std::endl << "bit = " << wavefmt.bit << std::endl << "sec = " << (double)datachk.size/wavefmt.average << std::endl; #endif // デバイスオープン const char *device = "default"; int err = snd_pcm_open( &handle, device, SND_PCM_STREAM_PLAYBACK, 0 ); if( err < 0 || ! handle ) throw std::string( "cannot open sound device : " ) + snd_strerror( err ); // パラメータ設定 const int msec = 100; snd_pcm_format_t pcmfm = ( wavefmt.bit == 8 ? SND_PCM_FORMAT_U8 : SND_PCM_FORMAT_S16_LE ); err = snd_pcm_set_params( handle, pcmfm, SND_PCM_ACCESS_RW_INTERLEAVED, wavefmt.chn, wavefmt.rate, 1, // リサンプリングする msec * 1000 // period の秒数( msec * 1000 ) ); if( err < 0 ) throw std::string( "failed to set parameter : " ) + snd_strerror( err ); // バッファのメモリ確保 snd_pcm_uframes_t buffer_size; snd_pcm_uframes_t period_size; snd_pcm_get_params( handle, &buffer_size, &period_size ); size_t bufsize = period_size * wavefmt.block; std::vector buffer( bufsize + 64 ); #ifdef _DEBUG std::cout << "period = " << period_size << ", bufsize = " << bufsize << std::endl; size_t totalsize = 0; #endif while( ! m_stop ){ std::fill( buffer.begin(), buffer.end(), 0 ); const std::size_t readsize = fread( buffer.data(), 1, bufsize , fin ); if( ! readsize ) break; #ifdef _DEBUG totalsize += readsize; std::cout << totalsize << " / " << datachk.size << std::endl; #endif snd_pcm_sframes_t frames = snd_pcm_writei( handle, buffer.data(), readsize / wavefmt.block ); if( frames < 0 ) frames = snd_pcm_recover( handle, frames, 0 ); // レジューム if( frames < 0 || frames < ( snd_pcm_sframes_t ) ( readsize / wavefmt.block ) ) throw std::string( "failed to snd_pcm_write : " ) + snd_strerror( err ); } } catch( const std::string& err ){ MISC::ERRMSG( err ); } if( handle ) snd_pcm_close( handle ); if( fin ) fclose( fin ); #ifdef _DEBUG std::cout << "fin\n"; #endif dispatch(); } // // ディスパッチャのコールバック関数 // void Play_Sound::callback_dispatch() { #ifdef _DEBUG std::cout << "Play_Sound::callback_dispatch\n"; #endif wait(); m_playing = false; } #endif jdim-0.7.0/src/sound/playsound.h000066400000000000000000000030011417047150700165470ustar00rootroot00000000000000// ライセンス: GPL2 // サウンド再生クラス #ifndef _PLAYSOUND_H #define _PLAYSOUND_H #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef USE_ALSA #include "skeleton/dispatchable.h" #include "jdlib/jdthread.h" #include namespace SOUND { // RIFF ( 8 byte ) struct RIFFCHK { char id[ 4 ]; // = "RIFF" std::uint32_t size; // 全体サイズ }; // WAVEfmt ( 28 byte ) struct WAVEFMTCHK { char id[ 8 ]; // "WAVEfmt " std::uint32_t size; // チャンクサイズ std::uint16_t fmt; // 種類( PCMは1 ) std::uint16_t chn; std::uint32_t rate; std::uint32_t average; // = rate * block ( byte ) std::uint16_t block; // = chn * bit / 8 ( byte ) std::uint16_t bit; }; // data ( 8 byte ) struct DATACHK { char id[ 4 ]; // = "data" std::uint32_t size; // チャンクサイズ = PCMデータサイズ }; class Play_Sound : public SKELETON::Dispatchable { JDLIB::Thread m_thread; std::string m_wavfile; bool m_stop{}; bool m_playing{}; public: Play_Sound(); ~Play_Sound(); bool is_playing() const { return m_playing; } void play( const std::string& wavfile ); void stop(); private: void wait(); static void* launcher( void* ); void play_wavfile(); void callback_dispatch() override; }; } #endif #endif jdim-0.7.0/src/sound/soundmanager.cpp000066400000000000000000000040631417047150700175600ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "soundmanager.h" #include "cache.h" SOUND::SOUND_Manager* instance_sound_manager = nullptr; SOUND::SOUND_Manager* SOUND::get_sound_manager() { #ifdef USE_ALSA if( ! instance_sound_manager ) instance_sound_manager = new SOUND::SOUND_Manager(); assert( instance_sound_manager ); #endif return instance_sound_manager; } void SOUND::delete_sound_manager() { #ifdef USE_ALSA if( instance_sound_manager ) delete instance_sound_manager; instance_sound_manager = nullptr; #endif } void SOUND::play( const int sound ) { #ifdef USE_ALSA SOUND::get_sound_manager()->play( sound ); #endif } /////////////////////////////////////////////// #ifdef USE_ALSA using namespace SOUND; SOUND_Manager::SOUND_Manager() { #ifdef _DEBUG std::cout << "SOUND_Manager::SOUND_Manager\n"; #endif for( int i = 0; i < NUM_SOUNDS; ++i ){ std::string path_sound = get_file( i ); bool exist = false; if( CACHE::file_exists( path_sound ) == CACHE::EXIST_FILE ) exist = true; #ifdef _DEBUG std::cout << path_sound << " " << exist << std::endl; #endif m_playable.push_back( exist ); } } SOUND_Manager::~SOUND_Manager() { #ifdef _DEBUG std::cout << "SOUND_Manager::~SOUND_Manager\n"; #endif m_playsound.stop(); } void SOUND_Manager::play( const int sound ) { if( ! m_playable[ sound ] ) return; #ifdef _DEBUG std::cout << "SOUND_Manager::play sound = " << sound << std::endl; #endif std::string path_sound = get_file( sound ); if( ! path_sound.empty() ) m_playsound.play( path_sound ); } std::string SOUND_Manager::get_file( const int sound ) { std::string path_sound = CACHE::path_sound_root(); switch( sound ){ case SOUND_RES: path_sound += "res.wav"; break; case SOUND_NO: path_sound += "no.wav"; break; case SOUND_NEW: path_sound += "new.wav"; break; case SOUND_ERR: path_sound += "err.wav"; break; default: return std::string(); } return path_sound; } #endif jdim-0.7.0/src/sound/soundmanager.h000066400000000000000000000017071417047150700172270ustar00rootroot00000000000000// ライセンス: GPL2 // サウンド管理クラス #ifndef _SOUNDMANAGER_H #define _SOUNDMANAGER_H #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "playsound.h" #include namespace SOUND { enum{ SOUND_RES = 0, SOUND_NO, SOUND_NEW, SOUND_ERR, NUM_SOUNDS }; #ifdef USE_ALSA class SOUND_Manager { std::vector< bool > m_playable; Play_Sound m_playsound; public: SOUND_Manager(); virtual ~SOUND_Manager(); void play( const int sound ); private: std::string get_file( const int sound ); }; #else class SOUND_Manager { public: SOUND_Manager(){} virtual ~SOUND_Manager(){} }; #endif /////////////////////////////////////// // インターフェース SOUND_Manager* get_sound_manager(); void delete_sound_manager(); void play( const int sound ); } #endif jdim-0.7.0/src/type.h000066400000000000000000000014771417047150700144010ustar00rootroot00000000000000// タイプ #ifndef _TYPE_H #define _TYPE_H enum { // 板のタイプ TYPE_BOARD_2CH = 0, TYPE_BOARD_2CH_COMPATI, // 2ch 互換 TYPE_BOARD_LOCAL, // ローカルファイル TYPE_BOARD_JBBS, // したらば TYPE_BOARD_MACHI, // まち TYPE_BOARD_UNKNOWN, // その他一般的なデータタイプ TYPE_BOARD, TYPE_BOARD_UPDATE, TYPE_THREAD, TYPE_THREAD_UPDATE, TYPE_THREAD_OLD, TYPE_IMAGE, TYPE_DIR, TYPE_DIR_END, // お気に入りの追加の時にサブディレクトリの終了の意味で使う TYPE_COMMENT, TYPE_LINK, TYPE_VBOARD, // お気に入りの仮想板 TYPE_AA, TYPE_HISTITEM, TYPE_USRCMD, TYPE_LINKFILTER, TYPE_REPLACESTR, TYPE_SEPARATOR, TYPE_FILE, TYPE_UNKNOWN }; #endif jdim-0.7.0/src/updatemanager.cpp000066400000000000000000000154121417047150700165620ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "updatemanager.h" #include "dbtree/interface.h" #include "command.h" #include "global.h" #include CORE::CheckUpdate_Manager* instance_checkupdate_manager = nullptr; CORE::CheckUpdate_Manager* CORE::get_checkupdate_manager() { if( ! instance_checkupdate_manager ) instance_checkupdate_manager = new CheckUpdate_Manager(); assert( instance_checkupdate_manager ); return instance_checkupdate_manager; } void CORE::delete_checkupdate_manager() { if( instance_checkupdate_manager ) delete instance_checkupdate_manager; instance_checkupdate_manager = nullptr; } /////////////////////////////////////////////// using namespace CORE; CheckUpdate_Manager::CheckUpdate_Manager() = default; CheckUpdate_Manager::~CheckUpdate_Manager() noexcept { assert( ! m_list_item.size() ); } void CheckUpdate_Manager::run() { if( m_running ){ #ifdef _DEBUG std::cout << "CheckUpdate_Manager::run failed items = " << m_list_item.size() << std::endl; #endif return; } m_total = m_list_item.size(); if( ! m_total ) return; #ifdef _DEBUG std::cout << "CheckUpdate_Manager::run total = " << m_total << std::endl; #endif m_running = true; m_url_checking = std::string(); pop_front(); } void CheckUpdate_Manager::stop() { if( ! m_running ) return; m_list_item.clear(); #ifdef _DEBUG std::cout << "CheckUpdate_Manager::stop running = " << m_url_checking << std::endl; #endif // ArticleBase::slot_load_finished()経由でpop_front()が呼び出されて m_running が false になる DBTREE::article_stop_load( m_url_checking ); } // // 更新チェックする板やスレをセットする // // open == true なら更新チェック終了時に url を開く( url が更新可能状態なら ) // void CheckUpdate_Manager::push_back( const std::string& url, const bool open ) { if( m_running ) return; #ifdef _DEBUG std::cout << "CheckUpdate_Manager::push_back " << " open = " << open << " url = " << url << std::endl; #endif std::list< std::string > urllist; // urlが板のアドレスかスレのアドレスか判断する int num_from, num_to; std::string num_str; const std::string url_dat = DBTREE::url_dat( url, num_from, num_to, num_str ); const std::string boardbase = DBTREE::url_boardbase( url ); // スレ if( ! url_dat.empty() ){ #ifdef _DEBUG std::cout << "type = dat\n" << DBTREE::article_subject( url ) << std::endl; #endif urllist.push_back( url ); } // 板 else if( ! boardbase.empty() ){ #ifdef _DEBUG std::cout << "type = board\n" << boardbase << std::endl; #endif urllist = DBTREE::board_get_check_update_articles( url ); } else return; if( open ) m_list_open.push_back( url ); if( ! urllist.size() ) return; // リストの先頭にあるスレから更新チェックをしていき、もし更新されていたらグループに属する // 残りのスレの更新チェックをキャンセルする CheckItem item; item.urllist = urllist; m_list_item.push_back( item ); #ifdef _DEBUG std::cout << "size = " << m_list_item.size() << std::endl; for( const std::string& u : urllist ) std::cout << u << std::endl; #endif } // 次のスレをチェック void CheckUpdate_Manager::pop_front() { if( ! m_running ) return; // チェック完了 if( ! m_list_item.size() ){ m_running = false; #ifdef _DEBUG std::cout << "CheckUpdate_Manager::pop_front end size = " << m_list_item.size() << std::endl; #endif // 更新したスレを開く if( m_list_open.size() ){ std::string urls_article = std::string(); std::string urls_board = std::string(); for( const std::string& url : m_list_open ) { // urlが板のアドレスかスレのアドレスか判断する int num_from, num_to; std::string num_str; const std::string url_dat = DBTREE::url_dat( url, num_from, num_to, num_str ); const std::string boardbase = DBTREE::url_boardbase( url ); // スレ if( ! url_dat.empty() ){ if( DBTREE::article_status( url ) & STATUS_UPDATE ) urls_article += url + " "; } // 板 else if( ! boardbase.empty() ){ if( DBTREE::board_status( url ) & STATUS_UPDATE ) urls_board += url + " "; } } #ifdef _DEBUG std::cout << "open urls_article = " << urls_article << std::endl; std::cout << "open urls_board = " << urls_board << std::endl; #endif if( ! urls_article.empty() ) CORE::core_set_command( "open_article_list", std::string(), urls_article ); if( ! urls_board.empty() ) CORE::core_set_command( "open_board_list", std::string(), urls_board ); m_list_open.clear(); } CORE::core_set_command( "set_info", "", "更新チェック完了"); } // チェック実行 else{ CORE::core_set_command( "set_info", "", "更新チェック中 (" + std::to_string( m_total - m_list_item.size() ) + "/" + std::to_string( m_total ) +")" ); // チェックした結果、更新されていたら // グループの残りのスレのチェックをキャンセルして次のグループへ if( ! m_url_checking.empty() && ( DBTREE::article_status( m_url_checking ) & STATUS_UPDATE ) ){ m_list_item.pop_front(); m_url_checking = std::string(); pop_front(); return; } CheckItem& item = m_list_item.front(); if( ! item.urllist.size() ){ // 次のグループへ m_list_item.pop_front(); m_url_checking = std::string(); pop_front(); return; } m_url_checking = item.urllist.front(); item.urllist.pop_front(); #ifdef _DEBUG std::cout << "CheckUpdate_Manager::pop_front download url = " << m_url_checking << " size = " << m_list_item.size() << std::endl; #endif // 更新チェックが終わったらローダからpop_front()がコールバックされる const bool check_update = true; DBTREE::article_download_dat( m_url_checking, check_update ); if( ! DBTREE::article_is_checking_update( m_url_checking ) ){ #ifdef _DEBUG std::cout << "skipped\n"; #endif m_url_checking = std::string(); pop_front(); return; } #ifdef _DEBUG std::cout << "started\n"; #endif } } jdim-0.7.0/src/updatemanager.h000066400000000000000000000025431417047150700162300ustar00rootroot00000000000000// ライセンス: GPL2 // // 更新チェッククラス // // push_back() 更新チェックするスレのグループを指定してからrun()を呼び出すと更新チェックを開始する。 // stop()で停止する。 // #ifndef _UPDATEMANAGER_H #define _UPDATEMANAGER_H #include #include namespace CORE { class CheckItem { public: std::list< std::string > urllist; // 更新チェックするスレのアドレスのリスト CheckItem(){ urllist.clear(); } }; class CheckUpdate_Manager { std::list< CheckItem > m_list_item; std::list< std::string > m_list_open; bool m_running{}; int m_total{}; std::string m_url_checking; public: CheckUpdate_Manager(); virtual ~CheckUpdate_Manager() noexcept; void run(); void stop(); // 更新チェックする板やスレをセットする // open == true なら更新チェック終了時に url を開く( url が更新可能状態なら ) void push_back( const std::string& url, const bool open ); // 次のスレをチェック void pop_front(); }; /////////////////////////////////////// // インターフェース CheckUpdate_Manager* get_checkupdate_manager(); void delete_checkupdate_manager(); } #endif jdim-0.7.0/src/urlreplacemanager.cpp000066400000000000000000000155021417047150700174360ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "urlreplacemanager.h" #include "cache.h" #include "type.h" #include "jdlib/miscutil.h" #include "jdlib/miscmsg.h" #include CORE::Urlreplace_Manager* instance_urlreplace_manager = nullptr; CORE::Urlreplace_Manager* CORE::get_urlreplace_manager() { if( ! instance_urlreplace_manager ) instance_urlreplace_manager = new Urlreplace_Manager(); assert( instance_urlreplace_manager ); return instance_urlreplace_manager; } void CORE::delete_urlreplace_manager() { if( instance_urlreplace_manager ) delete instance_urlreplace_manager; instance_urlreplace_manager = nullptr; } /////////////////////////////////////////////// using namespace CORE; #define DEFALUT_CONFIG \ "#\n" \ "# UrlReplace設定ファイル (urlreplace.conf)\n" \ "#\n" \ "# 設定ファイルの書式:\n" \ "# 正規表現<タブ>変換後URL<タブ>リファラURL<タブ>制御文字\n" \ "#\n" \ "# 設定例:\n" \ "# http://www\\.foobar\\.com/(view/|img\\.php\\?id=)([0-9]+) http://www.foobar.com/view/$2 $0 $IMAGE\n" \ "#\n" \ "# 詳細な書式はマニュアルを参照してください。\n" \ "# この機能を無効にする場合は、このファイルの内容を空にして保存してください。\n" \ "#\n" \ "https?://www\\.youtube\\.com/watch\\?(|[^#]+&)v=([^&#/]+) http://img.youtube.com/vi/$2/0.jpg\n" \ "https?://youtu\\.be/([^#&=/]+) http://img.youtube.com/vi/$1/0.jpg\n" \ "https?://img\\.youtube\\.com/vi/[^/]+/0.jpg $0 $THUMBNAIL\n" \ "\n" Urlreplace_Manager::Urlreplace_Manager() { std::string conf; const std::string path = CACHE::path_urlreplace(); if( CACHE::load_rawdata( path, conf ) ){ conf2list( conf ); } else { // 読み込みエラー、または空ファイル if( CACHE::file_exists( path ) == CACHE::EXIST_ERROR ){ // ファイルが存在しないとき、デフォルト設定ファイルを作成する conf = DEFALUT_CONFIG; if( CACHE::save_rawdata( path, conf ) ){ conf2list( conf ); } } } } Urlreplace_Manager::~Urlreplace_Manager() noexcept = default; // // conf -> リスト // void Urlreplace_Manager::conf2list( const std::string& conf ) { m_list_cmd.clear(); if( conf.empty() ) return; std::list< std::string > lines = MISC::get_lines( conf ); if( lines.size() == 0 ) return; for( const std::string& conf_str : lines ) { if( conf_str.empty() ) continue; if( conf_str[0] == '#' ) continue; // コメント行 std::list tokens = MISC::StringTokenizer( conf_str, '\t' ); if( tokens.size() < 2 ) continue; UrlreplaceItem item; std::list::iterator str = tokens.begin(); // 1: 検索URL constexpr bool icase = false; constexpr bool newline = true; constexpr bool migemo = false; constexpr bool wchar = false; if( (*str).empty() || ! item.creg.set( *str, icase, newline, migemo, wchar ) ) { continue; } // 2: 置換URL if( (*++str).empty() ) { continue; } item.replace = std::move( *str ); std::string tgt_text = "$0"; std::string rep_text = "\\0"; for( int n = '0'; n <= '9'; ++n ) { tgt_text[1] = rep_text[1] = n; item.replace = MISC::replace_str( item.replace, tgt_text, rep_text ); } // 3: リファラURL if( (++str) != tokens.end() ) item.referer = std::move( *str ); // 4: コントロール item.imgctrl = IMGCTRL_NONE; item.match_break = false; if( (++str) != tokens.end() ) { const std::string& ctrl = *str; int imgctrl = IMGCTRL_INIT; // 拡張子がない場合でも画像として扱う if( ctrl.find( "$IMAGE" ) != std::string::npos ){ imgctrl += IMGCTRL_FORCEIMAGE; } // 拡張子があっても画像として扱わない else if( ctrl.find( "$BROWSER" ) != std::string::npos ){ imgctrl += IMGCTRL_FORCEBROWSER; // $IMAGEを優先 } // 拡張子の偽装をチェックしない if( ctrl.find( "$GENUINE" ) != std::string::npos ){ imgctrl += IMGCTRL_GENUINE; } // サムネイル画像 if( ctrl.find( "$THUMBNAIL" ) != std::string::npos ){ imgctrl += IMGCTRL_THUMBNAIL; } if( imgctrl != IMGCTRL_INIT ) item.imgctrl = imgctrl; // 正規表現に一致したら以降の判定を行わない item.match_break = ( ctrl.find( "$BREAK" ) != std::string::npos ); } m_list_cmd.push_back( std::move( item ) ); } } // // URLを任意の正規表現で変換する // bool Urlreplace_Manager::exec( std::string &url ) const { if( m_list_cmd.empty() ) return false; JDLIB::Regex regex; constexpr std::size_t offset = 0; // いずれかの正規表現に一致するか bool matched = false; for( const auto& cmd : m_list_cmd ) { if( regex.match( cmd.creg, url, offset ) ) { matched = true; // 置換URLの変換 url = regex.replace( cmd.replace ); // URLが空になったか、以降の判定を行わない if( url.empty() || cmd.match_break ) break; } } return matched; } // // URLからリファラを求める // bool Urlreplace_Manager::referer( const std::string &url, std::string &refstr ) const { if( m_list_cmd.empty() ) return false; refstr = url; JDLIB::Regex regex; constexpr std::size_t offset = 0; // いずれかの正規表現に一致するか bool matched = false; for( const auto& cmd : m_list_cmd ) { if( regex.match( cmd.creg, refstr, offset ) ) { matched = true; // リファラURLの変換 refstr = regex.replace( cmd.referer ); // URLが空になったか、以降の判定を行わない if( refstr.empty() || cmd.match_break ) break; } } return matched; } // // URLの画像コントロールを取得する // int Urlreplace_Manager::get_imgctrl( const std::string &url ) const { if( m_list_cmd.empty() ) return IMGCTRL_NONE; int imgctrl = IMGCTRL_NONE; JDLIB::Regex regex; constexpr std::size_t offset = 0; // いずれかの正規表現に一致するか for( const auto& cmd : m_list_cmd ) { if( regex.match( cmd.creg, url, offset ) ) { // 画像コントロールを取得 imgctrl = cmd.imgctrl; // 以降の判定を行わない if( cmd.match_break ) break; } } return imgctrl; } jdim-0.7.0/src/urlreplacemanager.h000066400000000000000000000024431417047150700171030ustar00rootroot00000000000000// ライセンス: GPL2 #ifndef _URLREPLACEMANAGER_H #define _URLREPLACEMANAGER_H #include "jdlib/jdregex.h" #include #include namespace CORE { enum { IMGCTRL_INIT = 0, IMGCTRL_NONE = 1, IMGCTRL_FORCEIMAGE = 2, IMGCTRL_FORCEBROWSER = 4, IMGCTRL_GENUINE = 8, IMGCTRL_THUMBNAIL = 16, }; struct UrlreplaceItem { JDLIB::RegexPattern creg; std::string replace; std::string referer; int imgctrl; bool match_break; }; class Urlreplace_Manager { std::vector m_list_cmd; public: Urlreplace_Manager(); virtual ~Urlreplace_Manager() noexcept; // URLを任意の正規表現で変換する bool exec( std::string& url ) const; // URLからリファラを求める bool referer( const std::string& url, std::string& refstr ) const; // URLの画像コントロールを取得する int get_imgctrl( const std::string& url ) const; private: void conf2list( const std::string& conf ); }; /////////////////////////////////////// // インターフェース Urlreplace_Manager* get_urlreplace_manager(); void delete_urlreplace_manager(); } #endif jdim-0.7.0/src/usrcmdmanager.cpp000066400000000000000000000417711417047150700166040ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "gtkmmversion.h" #include "usrcmdmanager.h" #include "command.h" #include "cache.h" #include "type.h" #include "skeleton/msgdiag.h" #include "skeleton/prefdiag.h" #include "xml/tools.h" #include "jdlib/miscutil.h" #include "jdlib/jdregex.h" #include "dbtree/interface.h" #include "dbimg/imginterface.h" #include "config/globalconf.h" CORE::Usrcmd_Manager* instance_usrcmd_manager = nullptr; CORE::Usrcmd_Manager* CORE::get_usrcmd_manager() { if( ! instance_usrcmd_manager ) instance_usrcmd_manager = new Usrcmd_Manager(); assert( instance_usrcmd_manager ); return instance_usrcmd_manager; } void CORE::delete_usrcmd_manager() { if( instance_usrcmd_manager ) delete instance_usrcmd_manager; instance_usrcmd_manager = nullptr; } /////////////////////////////////////////////// class ReplaceTextDiag : public SKELETON::PrefDiag { Gtk::VBox m_vbox; Gtk::Entry m_entry; Gtk::Label m_label; public: ReplaceTextDiag( Gtk::Window* parent, const std::string& title ) : SKELETON::PrefDiag( parent, "" ), m_label( title + "を置き換えるテキストを入力してください。" ) { resize( 640, 1 ); m_vbox.pack_start( m_label, Gtk::PACK_SHRINK ); m_vbox.pack_start( m_entry, Gtk::PACK_SHRINK ); get_content_area()->set_spacing( 8 ); get_content_area()->pack_start( m_vbox ); set_title( "テキスト入力" ); show_all_children(); } Glib::ustring get_text() const { return m_entry.get_text(); } }; /////////////////////////////////////////////// using namespace CORE; Usrcmd_Manager::Usrcmd_Manager() { std::string xml; if( CACHE::load_rawdata( CACHE::path_usrcmd(), xml ) ) m_document.init( xml ); analyze_xml(); } // // XML に含まれるコマンド情報を取り出してデータベースを更新 // void Usrcmd_Manager::analyze_xml() { #ifdef _DEBUG std::cout << "Usrcmd_Manager::analyze_xml\n"; #endif m_list_cmd.clear(); const std::list usrcmds = m_document.getElementsByTagName( XML::get_name( TYPE_USRCMD ) ); for( const XML::Dom* usrcmd : usrcmds ) { const std::string cmd = usrcmd->getAttribute( "data" ); #ifdef _DEBUG const std::string name = usrcmd->getAttribute( "name" ); std::cout << usrcmd->nodeName() << " " << name << " " << cmd << std::endl; #endif set_cmd( cmd ); } m_size = m_list_cmd.size(); } void Usrcmd_Manager::set_cmd( const std::string& cmd ) { std::string cmd2 = MISC::remove_space( cmd ); m_list_cmd.push_back( cmd2 ); #ifdef _DEBUG std::cout << "Usrcmd_Manager::set_cmd "; std::cout << "[" << m_list_cmd.size()-1 << "] " << cmd2 << std::endl; #endif } // // XML 保存 // void Usrcmd_Manager::save_xml() { #ifdef _DEBUG std::cout << "Usrcmd_Manager::save_xml\n"; std::cout << m_document.get_xml() << std::endl; #endif CACHE::save_rawdata( CACHE::path_usrcmd(), m_document.get_xml() ); } // // 実行 // void Usrcmd_Manager::exec( const int comnum, // コマンド番号 const std::string& url, const std::string& link, const std::string& selection, // 選択文字 const int number // レス番号 ) { if( comnum >= m_size ) return; exec( m_list_cmd[ comnum ], url, link, selection, number ); } void Usrcmd_Manager::exec( const std::string& command, // コマンド const std::string& url, const std::string& link, const std::string& selection, // 選択文字 const int number // レス番号 ) { std::string cmd = command; if( cmd.find( "$ONLY" ) != std::string::npos ){ std::list< std::string > lines = MISC::split_line( cmd ); if( lines.size() >= 2 ){ std::list< std::string >::iterator it = lines.begin(); ++it; ++it; cmd = *it; ++it; for( ; it != lines.end(); ++it ) cmd += " " + ( *it ); } } #ifdef _DEBUG std::cout << "Usrcmd_Manager::exec\n" << "command = " << cmd << std::endl << "url = " << url << std::endl << "link = " << link << std::endl << "selection = " << selection << std::endl << "number = " << number << std::endl; #endif bool use_browser = false; if( cmd.rfind( "$VIEW", 0 ) == 0 ){ use_browser = true; cmd = cmd.substr( 5 ); cmd = MISC::remove_space( cmd ); } bool show_dialog = false; if( cmd.rfind( "$DIALOG", 0 ) == 0 ){ show_dialog = true; cmd = cmd.substr( 7 ); cmd = MISC::remove_space( cmd ); } cmd = replace_cmd( cmd, url, link, selection, number ); #ifdef _DEBUG std::cout << "exec " << cmd << std::endl; #endif if( cmd.empty() ) return; if( use_browser ) CORE::core_set_command( "open_url_browser", cmd ); else if( show_dialog ){ SKELETON::MsgDiag mdiag( nullptr, cmd ); mdiag.run(); } else Glib::spawn_command_line_async( cmd ); } // // 文字列置換用テキストダイアログ表示 // bool Usrcmd_Manager::show_replacetextdiag( std::string& texti, const std::string& title ) const { if( ! texti.empty() ) return true; ReplaceTextDiag textdiag( nullptr, title ); if( textdiag.run() != Gtk::RESPONSE_OK ) return false; texti = textdiag.get_text(); return true; } // コマンド置換 // cmdの$URLをurl, $LINKをlink, $TEXT*をtext, $NUMBERをnumberで置き換えて出力 // text は UTF-8 であること std::string Usrcmd_Manager::replace_cmd( const std::string& cmd, const std::string& url, const std::string& link, const std::string& text, const int number ) const { std::string cmd_out = cmd; const std::string oldhostl = DBTREE::article_org_host( link ); const std::string oldhost = DBTREE::article_org_host( url ); cmd_out = MISC::replace_str( cmd_out, "$URL", DBTREE::url_readcgi( url, 0, 0 ) ); cmd_out = MISC::replace_str( cmd_out, "$DATURL", DBTREE::url_dat( url ) ); cmd_out = MISC::replace_str( cmd_out, "$LOGPATH", CACHE::path_root() ); cmd_out = MISC::replace_str( cmd_out, "$LOCALDATL", CACHE::path_dat( link ) ); cmd_out = MISC::replace_str( cmd_out, "$LOCALDAT", CACHE::path_dat( url ) ); cmd_out = MISC::replace_str( cmd_out, "$LINK", link ); cmd_out = MISC::replace_str( cmd_out, "$NUMBER", std::to_string( number ) ); if( cmd_out.find( "$CACHEDIMG" ) != std::string::npos ){ cmd_out = MISC::replace_str( cmd_out, "$CACHEDIMG", DBIMG::get_cache_path( link ) ); } // ホスト名(http://含む) cmd_out = MISC::replace_str( cmd_out, "$SERVERL", MISC::get_hostname( link ) ); cmd_out = MISC::replace_str( cmd_out, "$SERVER", MISC::get_hostname( url ) ); // ホスト名(http://含まない) cmd_out = MISC::replace_str( cmd_out, "$OLDHOSTNAMEL", MISC::get_hostname( oldhostl, false ) ); cmd_out = MISC::replace_str( cmd_out, "$OLDHOSTNAME", MISC::get_hostname( oldhost, false ) ); cmd_out = MISC::replace_str( cmd_out, "$OLDHOSTL", MISC::get_hostname( oldhostl, false ) ); cmd_out = MISC::replace_str( cmd_out, "$OLDHOST", MISC::get_hostname( oldhost, false ) ); cmd_out = MISC::replace_str( cmd_out, "$HOSTNAMEL", MISC::get_hostname( link, false ) ); cmd_out = MISC::replace_str( cmd_out, "$HOSTNAME", MISC::get_hostname( url, false ) ); cmd_out = MISC::replace_str( cmd_out, "$HOSTL", MISC::get_hostname( link, false ) ); cmd_out = MISC::replace_str( cmd_out, "$HOST", MISC::get_hostname( url, false ) ); // スレが属する板のID cmd_out = MISC::replace_str( cmd_out, "$BBSNAMEL", DBTREE::board_id( link ) ); cmd_out = MISC::replace_str( cmd_out, "$BBSNAME", DBTREE::board_id( url ) ); // スレのID cmd_out = MISC::replace_str( cmd_out, "$DATNAMEL", DBTREE::article_key( link ) ); cmd_out = MISC::replace_str( cmd_out, "$DATNAME", DBTREE::article_key( url ) ); cmd_out = MISC::replace_str( cmd_out, "$TITLE", DBTREE::article_subject( url ) ); cmd_out = MISC::replace_str( cmd_out, "$BOARDNAME", DBTREE::board_name( url ) ); // 範囲選択した文字列 std::string texti = text; if( cmd_out.find( "$TEXTIU" ) != std::string::npos ){ if( ! show_replacetextdiag( texti, "$TEXTIU" ) ) return std::string(); cmd_out = MISC::replace_str( cmd_out, "$TEXTIU", MISC::charset_url_encode_split( texti, "UTF-8" ) ); } if( cmd_out.find( "$TEXTIX" ) != std::string::npos ){ if( ! show_replacetextdiag( texti, "$TEXTIX" ) ) return std::string(); cmd_out = MISC::replace_str( cmd_out, "$TEXTIX", MISC::charset_url_encode_split( texti, "EUC-JP" ) ); } if( cmd_out.find( "$TEXTIE" ) != std::string::npos ){ if( ! show_replacetextdiag( texti, "$TEXTIE" ) ) return std::string(); cmd_out = MISC::replace_str( cmd_out, "$TEXTIE", MISC::charset_url_encode_split( texti, "MS932" ) ); } if( cmd_out.find( "$TEXTI" ) != std::string::npos ){ if( ! show_replacetextdiag( texti, "$TEXTI" ) ) return std::string(); cmd_out = MISC::replace_str( cmd_out, "$TEXTI", texti ); } if( cmd_out.find( "$TEXTU" ) != std::string::npos ){ cmd_out = MISC::replace_str( cmd_out, "$TEXTU", MISC::charset_url_encode_split( texti, "UTF-8" ) ); } if( cmd_out.find( "$TEXTX" ) != std::string::npos ){ cmd_out = MISC::replace_str( cmd_out, "$TEXTX", MISC::charset_url_encode_split( texti, "EUC-JP" ) ); } if( cmd_out.find( "$TEXTE" ) != std::string::npos ){ cmd_out = MISC::replace_str( cmd_out, "$TEXTE", MISC::charset_url_encode_split( texti, "MS932" ) ); } if( cmd_out.find( "$TEXT" ) != std::string::npos ){ cmd_out = MISC::replace_str( cmd_out, "$TEXT", texti ); } // 入力ダイアログを表示 std::string input; if( cmd_out.find( "$INPUTU" ) != std::string::npos ){ if( ! show_replacetextdiag( input, "$INPUTU" ) ) return std::string(); cmd_out = MISC::replace_str( cmd_out, "$INPUTU", MISC::charset_url_encode_split( input, "UTF-8" ) ); } if( cmd_out.find( "$INPUTX" ) != std::string::npos ){ if( ! show_replacetextdiag( input, "$INPUTX" ) ) return std::string(); cmd_out = MISC::replace_str( cmd_out, "$INPUTX", MISC::charset_url_encode_split( input, "EUC-JP" ) ); } if( cmd_out.find( "$INPUTE" ) != std::string::npos ){ if( ! show_replacetextdiag( input, "$INPUTE" ) ) return std::string(); cmd_out = MISC::replace_str( cmd_out, "$INPUTE", MISC::charset_url_encode_split( input, "MS932" ) ); } if( cmd_out.find( "$INPUT" ) != std::string::npos ){ if( ! show_replacetextdiag( input, "$INPUT" ) ) return std::string(); cmd_out = MISC::replace_str( cmd_out, "$INPUT", input ); } // 結果のエンコード cmd_out = MISC::replace_str( cmd_out, "$OUTU", "" ); cmd_out = MISC::replace_str( cmd_out, "$OUTX", "" ); cmd_out = MISC::replace_str( cmd_out, "$OUTE", "" ); #ifdef _DEBUG std::cout << "Usrcmd_Manager::replace_cmd\n"; std::cout << "cmd = " << cmd << std::endl; std::cout << "out = " << cmd_out << std::endl; #endif return cmd_out; } // // メニューをアクティブにするか // bool Usrcmd_Manager::is_sensitive( int num, const std::string& link, const std::string& selection ) const { const unsigned int max_selection_str = 1024; if( num >= m_size ) return false; const std::string cmd = m_list_cmd[ num ]; if( cmd.find( "$LINK" ) != std::string::npos || cmd.find( "$SERVERL" ) != std::string::npos || cmd.find( "$HOSTNAMEL" ) != std::string::npos || cmd.find( "$HOSTL" ) != std::string::npos || cmd.find( "$OLDHOSTNAMEL" ) != std::string::npos || cmd.find( "$OLDHOSTL" ) != std::string::npos || cmd.find( "$BBSNAMEL" ) != std::string::npos || cmd.find( "$DATNAMEL" ) != std::string::npos || cmd.find( "$LOCALDATL" ) != std::string::npos ){ if( link.empty() ) return false; } if( cmd.find( "$TEXT" ) != std::string::npos && cmd.find( "$TEXTI" ) == std::string::npos ){ if( selection.empty() || selection.length() > max_selection_str ) return false; } if( cmd.find( "$CACHEDIMG" ) != std::string::npos ){ if( link.empty() ) return false; if( DBIMG::get_type_ext( link ) == DBIMG::T_UNKNOWN ) return false; if( ! DBIMG::is_cached( link ) ) return false; } return true; } // // メニューを隠すか // bool Usrcmd_Manager::is_hide( int num, const std::string& url ) const { if( num >= m_size ) return false; const std::string cmd = m_list_cmd[ num ]; #ifdef _DEBUG std::cout << "Usrcmd_manager::is_hide cmd = " << cmd << std::endl << "url = " << url << std::endl; #endif if( cmd.find( "$ONLY" ) != std::string::npos ){ std::list< std::string > lines = MISC::split_line( cmd ); if( lines.size() >= 2 ){ JDLIB::Regex regex; const size_t offset = 0; const bool icase = false; const bool newline = true; const bool usemigemo = false; const bool wchar = false; std::list< std::string >::iterator it = lines.begin(); ++it; const std::string query = *it; #ifdef _DEBUG std::cout << "query = " << query << std::endl; #endif if( ! regex.exec( query, url, offset, icase, newline, usemigemo, wchar ) ) return true; } } return false; } // // ユーザコマンドの登録とメニュー作成 // std::string Usrcmd_Manager::create_usrcmd_menu( Glib::RefPtr< Gtk::ActionGroup >& action_group ) { int dirno = 0; int cmdno = 0; return create_usrcmd_menu( action_group, &m_document, dirno, cmdno ); } // ユーザコマンドの登録とメニュー作成(再帰用) std::string Usrcmd_Manager::create_usrcmd_menu( Glib::RefPtr< Gtk::ActionGroup >& action_group, const XML::Dom* dom, int& dirno, int& cmdno ) { std::string menu; if( ! dom ) return menu; for( const XML::Dom* child : *dom ) { if( child->nodeType() == XML::NODE_TYPE_ELEMENT ) { #ifdef _DEBUG std::cout << "name = " << child->nodeName() << std::endl; #endif const int type = XML::get_type( child->nodeName() ); if( type == TYPE_DIR ){ const std::string name = child->getAttribute( "name" ); #ifdef _DEBUG std::cout << "[" << dirno << "] " << name << std::endl; #endif const std::string dirname = "usrcmd_dir" + std::to_string( dirno ); action_group->add( Gtk::Action::create( dirname, name ) ); ++dirno; menu += ""; menu += create_usrcmd_menu( action_group, child, dirno, cmdno ); menu += ""; } else if( type == TYPE_SEPARATOR ){ menu += ""; } else if( type == TYPE_USRCMD ){ const std::string name = child->getAttribute( "name" ); #ifdef _DEBUG std::cout << "[" << cmdno << "] " << name << std::endl; #endif const std::string cmdname = "usrcmd" + std::to_string( cmdno ); action_group->add( Gtk::Action::create( cmdname, name ) ); ++cmdno; menu += ""; } else if( child->hasChildNodes() ) menu += create_usrcmd_menu( action_group, child, dirno, cmdno ); } } return menu; } Glib::RefPtr< Gtk::Action > Usrcmd_Manager::get_action( Glib::RefPtr< Gtk::ActionGroup >& action_group, const int num ) { const std::string str_cmd = "usrcmd" + std::to_string( num ); return action_group->get_action( str_cmd ); } // // 選択不可かどうか判断して visible や sensitive を切り替える // void Usrcmd_Manager::toggle_sensitive( Glib::RefPtr< Gtk::ActionGroup >& action_group, const std::string& url_article, const std::string& url_link, const std::string& str_select ) { for( int i = 0; i < m_size; ++i ){ Glib::RefPtr< Gtk::Action > act = get_action( action_group, i ); if( act ){ if( is_hide( i, url_article ) ) act->set_visible( false ); else{ act->set_visible( true ); if( is_sensitive( i, url_link, str_select ) ) act->set_sensitive( true ); else{ act->set_sensitive( false ); if( CONFIG::get_hide_usrcmd() ) act->set_visible( false ); } } } } } jdim-0.7.0/src/usrcmdmanager.h000066400000000000000000000056271417047150700162510ustar00rootroot00000000000000// ライセンス: GPL2 // // ユーザーコマンドの管理クラス // #ifndef _USRCMDMANAGER_H #define _USRCMDMANAGER_H #include "xml/document.h" #include #include #define ROOT_NODE_NAME_USRCMD "usrcmdlist" namespace CORE { class Usrcmd_Manager { XML::Document m_document; int m_size; std::vector< std::string > m_list_cmd; public: Usrcmd_Manager(); virtual ~Usrcmd_Manager() noexcept = default; XML::Document& xml_document() { return m_document; } void analyze_xml(); void save_xml(); int get_size() const { return m_size; } // 実行 void exec( const int comnum, // コマンド番号 const std::string& url, const std::string& link, const std::string& selection, // 選択文字 const int number // レス番号 ); void exec( const std::string& command, // コマンド const std::string& url, const std::string& link, const std::string& selection, // 選択文字 const int number // レス番号 ); // コマンド置換 // cmdの$URLをurl, $LINKをlink, $TEXT*をtext, $NUMBERをnumberで置き換えて出力 // text は UTF-8 であること std::string replace_cmd( const std::string& cmd, const std::string& url, const std::string& link, const std::string& text, const int number ) const; // ユーザコマンドメニューの作成 std::string create_usrcmd_menu( Glib::RefPtr< Gtk::ActionGroup >& action_group ); std::string create_usrcmd_menu( Glib::RefPtr< Gtk::ActionGroup >& action_group, const XML::Dom* dom, int& dirno, int& cmdno ); Glib::RefPtr< Gtk::Action > get_action( Glib::RefPtr< Gtk::ActionGroup >& action_group, const int num ); // 選択不可かどうか判断して visible や sensitive を切り替える void toggle_sensitive( Glib::RefPtr< Gtk::ActionGroup >& action_group, const std::string& url_article, const std::string& url_link, const std::string& str_select ); private: bool show_replacetextdiag( std::string& texti, const std::string& title ) const; void set_cmd( const std::string& cmd ); bool is_sensitive( int num, const std::string& link, const std::string& selection ) const; bool is_hide( int num, const std::string& url ) const; }; /////////////////////////////////////// // インターフェース Usrcmd_Manager* get_usrcmd_manager(); void delete_usrcmd_manager(); } #endif jdim-0.7.0/src/usrcmdpref.cpp000066400000000000000000000270541417047150700161240ustar00rootroot00000000000000// ライセンス: GPL2 //#define _DEBUG #include "jddebug.h" #include "gtkmmversion.h" #include "usrcmdpref.h" #include "usrcmdmanager.h" #include "command.h" #include "dndmanager.h" #include "environment.h" #include "type.h" #include "control/controlid.h" #include "control/controlutil.h" #include "skeleton/msgdiag.h" #include "config/globalconf.h" #include "jdlib/miscutil.h" using namespace CORE; UsrCmdDiag::UsrCmdDiag( Gtk::Window* parent, const Glib::ustring& name, const Glib::ustring& cmd ) : SKELETON::PrefDiag( parent, "" ), m_label_name( "コマンド名", Gtk::ALIGN_START ), m_label_cmd( "実行するコマンド", Gtk::ALIGN_START ), m_button_manual( "オンラインマニュアルの置換文字一覧を表示" ) { resize( 640, 1 ); m_entry_name.set_text( name ); m_entry_cmd.set_text( cmd ); m_button_manual.signal_clicked().connect( sigc::mem_fun( *this, &UsrCmdDiag::slot_show_manual ) ); m_vbox.set_spacing( 8 ); m_vbox.pack_start( m_label_name, Gtk::PACK_SHRINK ); m_vbox.pack_start( m_entry_name, Gtk::PACK_SHRINK ); m_hbox_cmd.pack_start( m_label_cmd, Gtk::PACK_SHRINK ); m_hbox_cmd.pack_end( m_button_manual, Gtk::PACK_SHRINK ); m_vbox.pack_start( m_hbox_cmd, Gtk::PACK_SHRINK ); m_vbox.pack_start( m_entry_cmd, Gtk::PACK_SHRINK ); get_content_area()->set_spacing( 8 ); get_content_area()->pack_start( m_vbox ); set_activate_entry( m_entry_name ); set_activate_entry( m_entry_cmd ); set_title( "ユーザコマンド設定" ); show_all_children(); } void UsrCmdDiag::slot_show_manual() { CORE::core_set_command( "open_url_browser", ENVIRONMENT::get_jdhelpcmd() ); } /////////////////////////////////////////// UsrCmdPref::UsrCmdPref( Gtk::Window* parent, const std::string& url ) : SKELETON::PrefDiag( parent, url ), m_label( "右クリックしてコンテキストメニューからコマンドの追加と削除が出来ます。編集するにはダブルクリックします。" ), m_treeview( url, DNDTARGET_USRCMD, m_columns ), m_ckbt_hide_usrcmd( "選択不可のユーザコマンドを非表示にする", true ) { m_treestore = Gtk::TreeStore::create( m_columns ); m_treeview.create_column( 0 ); m_treeview.set_editable_view( true ); m_treeview.set_size_request( 640, 400 ); m_treeview.signal_row_activated().connect( sigc::mem_fun( *this, &UsrCmdPref::slot_row_activated ) ); m_treeview.sig_button_press().connect( sigc::mem_fun(*this, &UsrCmdPref::slot_button_press ) ); m_treeview.sig_button_release().connect( sigc::mem_fun(*this, &UsrCmdPref::slot_button_release ) ); m_treeview.sig_key_press().connect( sigc::mem_fun(*this, &UsrCmdPref::slot_key_press ) ); m_treeview.sig_key_release().connect( sigc::mem_fun(*this, &UsrCmdPref::slot_key_release ) ); m_scrollwin.add( m_treeview ); m_scrollwin.set_policy( Gtk::POLICY_AUTOMATIC, Gtk::POLICY_ALWAYS ); m_scrollwin.set_size_request( 640, 400 ); get_content_area()->set_spacing( 8 ); get_content_area()->pack_start( m_label, Gtk::PACK_SHRINK ); get_content_area()->pack_start( m_scrollwin ); m_ckbt_hide_usrcmd.set_active( CONFIG::get_hide_usrcmd() ); get_content_area()->pack_start( m_ckbt_hide_usrcmd, Gtk::PACK_SHRINK ); // ポップアップメニュー m_action_group = Gtk::ActionGroup::create(); m_action_group->add( Gtk::Action::create( "NewCmd", "新規コマンド(_C)"), sigc::mem_fun( *this, &UsrCmdPref::slot_newcmd ) ); m_action_group->add( Gtk::Action::create( "NewDir", "新規ディレクトリ(_N)"), sigc::mem_fun( *this, &UsrCmdPref::slot_newdir ) ); m_action_group->add( Gtk::Action::create( "NewSepa", "区切り(_S)"), sigc::mem_fun( *this, &UsrCmdPref::slot_newsepa ) ); m_action_group->add( Gtk::Action::create( "Rename", "名前変更(_R)"), sigc::mem_fun( *this, &UsrCmdPref::slot_rename ) ); m_action_group->add( Gtk::Action::create( "Delete_Menu", "Delete" ) ); m_action_group->add( Gtk::Action::create( "Delete", "削除する(_D)"), sigc::mem_fun( *this, &UsrCmdPref::slot_delete ) ); Glib::ustring str_ui = "" "" "" "" "" "" "" "" "" "" "" "" // 複数選択 "" "" "" "" "" ""; m_ui_manager = Gtk::UIManager::create(); m_ui_manager->insert_action_group( m_action_group ); m_ui_manager->add_ui_from_string( str_ui ); // ポップアップメニューにキーアクセレータを表示 Gtk::Menu* menu = dynamic_cast< Gtk::Menu* >( m_ui_manager->get_widget( "/popup_menu" ) ); CONTROL::set_menu_motion( menu ); menu = dynamic_cast< Gtk::Menu* >( m_ui_manager->get_widget( "/popup_menu_mul" ) ); CONTROL::set_menu_motion( menu ); m_treeview.xml2tree( CORE::get_usrcmd_manager()->xml_document(), m_treestore, ROOT_NODE_NAME_USRCMD ); show_all_children(); set_title( "ユーザコマンド設定" ); } void UsrCmdPref::timeout() { m_treeview.clock_in(); } void UsrCmdPref::slot_ok_clicked() { CONFIG::set_hide_usrcmd( m_ckbt_hide_usrcmd.get_active() ); m_treeview.tree2xml( CORE::get_usrcmd_manager()->xml_document(), ROOT_NODE_NAME_USRCMD ); CORE::get_usrcmd_manager()->analyze_xml(); CORE::get_usrcmd_manager()->save_xml(); CORE::core_set_command( "reset_article_popupmenu" ); } // // マウスボタン押した // bool UsrCmdPref::slot_button_press( GdkEventButton* event ) { #ifdef _DEBUG std::cout << "UsrCmdPref::slot_button_press\n"; #endif return true; } // // マウスボタン離した // bool UsrCmdPref::slot_button_release( GdkEventButton* event ) { m_path_selected = m_treeview.get_path_under_xy( (int)event->x, (int)event->y ); #ifdef _DEBUG std::cout << "UsrCmdPref::slot_button_release path = " << m_path_selected.to_string() << std::endl;; #endif if( m_control.button_alloted( event, CONTROL::PopupmenuButton ) ) show_popupmenu(); // ディレクトリの開閉 else if( ! m_path_selected.empty() && m_control.button_alloted( event, CONTROL::ClickButton ) ){ Gtk::TreeModel::Row row = *( m_treestore->get_iter( m_path_selected ) ); if( row && row[ m_columns.m_type ] == TYPE_DIR ){ if( ! m_treeview.row_expanded( m_path_selected ) ) m_treeview.expand_row( m_path_selected, false ); else m_treeview.collapse_row( m_path_selected ); } } return true; } // // キーを押した // bool UsrCmdPref::slot_key_press( GdkEventKey* event ) { // 行の名前を編集中なら何もしない if( m_treeview.is_renaming_row() ) return false; const int control = m_control.key_press( event ); #ifdef _DEBUG std::cout << "UsrCmdPref::slot_key_press key = " << control << std::endl; #endif if( control == CONTROL::Delete ) delete_row(); return true; } // // キー上げた // bool UsrCmdPref::slot_key_release( GdkEventKey* event ) { // 行の名前を編集中なら何もしない if( m_treeview.is_renaming_row() ) return false; #ifdef _DEBUG std::cout << "UsrCmdPref::slot_key_release\n"; #endif return true; } // ポップアップメニュー表示 void UsrCmdPref::show_popupmenu() { Gtk::Menu* menu = nullptr; std::list< Gtk::TreeModel::iterator > list_it = m_treeview.get_selected_iterators(); if( list_it.size() <= 1 ) menu = dynamic_cast< Gtk::Menu* >( m_ui_manager->get_widget( "/popup_menu" ) ); else menu = dynamic_cast< Gtk::Menu* >( m_ui_manager->get_widget( "/popup_menu_mul" ) ); if( ! menu ) return; Glib::RefPtr< Gtk::Action > act_del, act_rename; act_rename = m_action_group->get_action( "Rename" ); act_del = m_action_group->get_action( "Delete_Menu" ); if( m_path_selected.empty() ){ if( act_rename ) act_rename->set_sensitive( false ); if( act_del ) act_del->set_sensitive( false ); } else{ Gtk::TreeModel::Row row = *( m_treestore->get_iter( m_path_selected ) ); int type = row[ m_columns.m_type ]; if( act_rename ){ if( type != TYPE_SEPARATOR ) act_rename->set_sensitive( true ); else act_rename->set_sensitive( false ); } if( act_del ) act_del->set_sensitive( true ); } // Specify the current event by nullptr. menu->popup_at_pointer( nullptr ); } // コマンド作成 void UsrCmdPref::slot_newcmd() { UsrCmdDiag diag( this, "", "" ); if( diag.run() == Gtk::RESPONSE_OK ){ if( diag.get_name().empty() || diag.get_cmd().empty() ) return; CORE::DATA_INFO_LIST list_info; CORE::DATA_INFO info; info.type = TYPE_USRCMD; info.parent = nullptr; info.url = std::string(); info.name = diag.get_name(); info.data = diag.get_cmd(); if( m_path_selected.empty() ) info.path = Gtk::TreePath( "0" ).to_string(); else info.path = m_path_selected.to_string(); list_info.push_back( info ); const bool subdir = true; const bool scroll = false; const bool force = false; const bool cancel_undo_commit = false; const int check_dup = 0; // 項目の重複チェックをしない m_treeview.append_info( list_info, m_path_selected, subdir, scroll, force, cancel_undo_commit, check_dup ); m_path_selected = m_treeview.get_current_path(); } } // ディレクトリ作成 void UsrCmdPref::slot_newdir() { m_path_selected = m_treeview.create_newdir( m_path_selected ); } // 区切り作成 void UsrCmdPref::slot_newsepa() { CORE::DATA_INFO_LIST list_info; CORE::DATA_INFO info; info.type = TYPE_SEPARATOR; info.parent = nullptr; info.url = std::string(); info.name = "--- 区切り ---"; info.data = std::string(); if( m_path_selected.empty() ) info.path = Gtk::TreePath( "0" ).to_string(); else info.path = m_path_selected.to_string(); list_info.push_back( info ); const bool subdir = true; const bool scroll = false; const bool force = false; const bool cancel_undo_commit = false; const int check_dup = 0; // 項目の重複チェックをしない m_treeview.append_info( list_info, m_path_selected, subdir, scroll, force, cancel_undo_commit, check_dup ); m_path_selected = m_treeview.get_current_path(); } // // 削除 // void UsrCmdPref::delete_row() { SKELETON::MsgDiag mdiag( nullptr, "削除しますか?", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO ); if( mdiag.run() != Gtk::RESPONSE_YES ) return; slot_delete(); } void UsrCmdPref::slot_delete() { const bool force = false; m_treeview.delete_selected_rows( force ); } // 名前変更 void UsrCmdPref::slot_rename() { m_treeview.rename_row( m_path_selected ); } void UsrCmdPref::slot_row_activated( const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn* column ) { #ifdef _DEBUG std::cout << "UsrCmdPref::slot_row_activated path = " << path.to_string() << std::endl; #endif Gtk::TreeModel::Row row = *( m_treestore->get_iter( path ) ); if( ! row ) return; if( row[ m_columns.m_type ] != TYPE_USRCMD ) return; UsrCmdDiag diag( this, row[ m_columns.m_name ], row[ m_columns.m_data ] ); if( diag.run() == Gtk::RESPONSE_OK ){ row[ m_columns.m_name ] = diag.get_name(); row[ m_columns.m_data ] = diag.get_cmd(); static_cast( row ); // cppcheck: unreadVariable } } jdim-0.7.0/src/usrcmdpref.h000066400000000000000000000041371417047150700155660ustar00rootroot00000000000000// ライセンス: GPL2 // ユーザコマンド設定ダイアログ #ifndef _USRCMDPREF_H #define _USRCMDPREF_H #include "skeleton/prefdiag.h" #include "skeleton/edittreeview.h" #include "skeleton/editcolumns.h" #include "control/control.h" namespace CORE { class UsrCmdDiag : public SKELETON::PrefDiag { Gtk::VBox m_vbox; Gtk::Entry m_entry_name; Gtk::Entry m_entry_cmd; Gtk::Label m_label_name; Gtk::HBox m_hbox_cmd; Gtk::Label m_label_cmd; Gtk::Button m_button_manual; public: UsrCmdDiag( Gtk::Window* parent, const Glib::ustring& name, const Glib::ustring& cmd ); Glib::ustring get_name() const { return m_entry_name.get_text(); } Glib::ustring get_cmd() const { return m_entry_cmd.get_text(); } private: void slot_show_manual(); }; class UsrCmdPref : public SKELETON::PrefDiag { Gtk::Label m_label; SKELETON::EditTreeView m_treeview; SKELETON::EditColumns m_columns; Glib::RefPtr< Gtk::TreeStore > m_treestore; Gtk::ScrolledWindow m_scrollwin; Gtk::CheckButton m_ckbt_hide_usrcmd; Gtk::TreeModel::Path m_path_selected; // ポップアップメニュー Glib::RefPtr< Gtk::ActionGroup > m_action_group; Glib::RefPtr< Gtk::UIManager > m_ui_manager; CONTROL::Control m_control; public: UsrCmdPref( Gtk::Window* parent, const std::string& url ); private: void timeout() override; // OK押した void slot_ok_clicked() override; bool slot_button_press( GdkEventButton* event ); bool slot_button_release( GdkEventButton* event ); bool slot_key_press( GdkEventKey* event ); bool slot_key_release( GdkEventKey* event ); void show_popupmenu(); void delete_row(); void slot_newcmd(); void slot_newdir(); void slot_newsepa(); void slot_delete(); void slot_rename(); void slot_row_activated( const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn* column ); }; } #endif jdim-0.7.0/src/viewfactory.cpp000066400000000000000000000122041417047150700163030ustar00rootroot00000000000000// ライセンス: GPL2 #include "viewfactory.h" #include "searchmanager.h" #include "bbslist/bbslistview.h" #include "bbslist/favoriteview.h" #include "bbslist/selectlistview.h" #include "bbslist/historyview.h" #include "board/boardview.h" #include "board/boardviewnext.h" #include "board/boardviewlog.h" #include "board/boardviewsidebar.h" #include "article/articleview.h" #include "article/articleviewsearch.h" #include "article/articleviewpreview.h" #include "article/articleviewinfo.h" #include "article/articleviewpopup.h" #include "article/articleviewetc.h" #include "image/imageview.h" #include "image/imageviewicon.h" #include "image/imageviewpopup.h" #include "message/messageview.h" SKELETON::View* CORE::ViewFactory( int type, const std::string& url, VIEWFACTORY_ARGS view_args ) { switch( type ) { case VIEW_BBSLISTVIEW: return new BBSLIST::BBSListViewMain( url, view_args.arg1, view_args.arg2 ); case VIEW_FAVORITELIST: return new BBSLIST::FavoriteListView( url, view_args.arg1, view_args.arg2 ); case VIEW_SELECTLIST: return new BBSLIST::SelectListView( url, view_args.arg1, view_args.arg2 ); case VIEW_HISTTHREAD: return new BBSLIST::HistoryThreadView( url, view_args.arg1, view_args.arg2 ); case VIEW_HISTCLOSE: return new BBSLIST::HistoryCloseView( url, view_args.arg1, view_args.arg2 ); case VIEW_HISTBOARD: return new BBSLIST::HistoryBoardView( url, view_args.arg1, view_args.arg2 ); case VIEW_HISTCLOSEBOARD: return new BBSLIST::HistoryCloseBoardView( url, view_args.arg1, view_args.arg2 ); case VIEW_HISTCLOSEIMG: return new BBSLIST::HistoryCloseImgView( url, view_args.arg1, view_args.arg2 ); ////////////////// case VIEW_BOARDVIEW: return new BOARD::BoardView( url ); case VIEW_BOARDNEXT: return new BOARD::BoardViewNext( url, view_args.arg1 ); case VIEW_BOARDLOG: return new BOARD::BoardViewLog( url ); case VIEW_BOARDSIDEBAR: return new BOARD::BoardViewSidebar( url, ( view_args.arg1 == "set_history" ) ); ///////////////// case VIEW_ARTICLEVIEW: return new ARTICLE::ArticleViewMain( url ); case VIEW_ARTICLERES: return new ARTICLE::ArticleViewRes( url ); case VIEW_ARTICLENAME: return new ARTICLE::ArticleViewName( url ); case VIEW_ARTICLEID: return new ARTICLE::ArticleViewID( url ); case VIEW_ARTICLEBM: return new ARTICLE::ArticleViewBM( url ); case VIEW_ARTICLEPOST: return new ARTICLE::ArticleViewPost( url ); case VIEW_ARTICLEHIGHREFRES: return new ARTICLE::ArticleViewHighRefRes( url ); case VIEW_ARTICLEURL: return new ARTICLE::ArticleViewURL( url ); case VIEW_ARTICLEREFER: return new ARTICLE::ArticleViewRefer( url ); case VIEW_ARTICLEDRAWOUT: return new ARTICLE::ArticleViewDrawout( url ); case VIEW_ARTICLEPOSTLOG: return new ARTICLE::ArticleViewPostlog( url ); case VIEW_ARTICLESEARCHLOG: return new ARTICLE::ArticleViewSearch( url, ( view_args.arg1 == "exec" ) ); case VIEW_ARTICLESEARCHALLLOG: return new ARTICLE::ArticleViewSearch( url, ( view_args.arg1 == "exec" ) ); case VIEW_ARTICLESEARCHTITLE: return new ARTICLE::ArticleViewSearch( url, ( view_args.arg1 == "exec" ) ); case VIEW_ARTICLEPREVIEW: return new ARTICLE::ArticleViewPreview( url ); case VIEW_ARTICLEINFO: return new ARTICLE::ArticleViewInfo( url ); ///////////////// case VIEW_ARTICLEPOPUPHTML: return new ARTICLE::ArticleViewPopupHTML( url, view_args.arg1 ); case VIEW_ARTICLEPOPUPRES: return new ARTICLE::ArticleViewPopupRes( url, view_args.arg1, ( view_args.arg2 == "true" ), ( view_args.arg3 == "true" ) ); case VIEW_ARTICLEPOPUPNAME: return new ARTICLE::ArticleViewPopupName( url, view_args.arg1 ); case VIEW_ARTICLEPOPUPID: return new ARTICLE::ArticleViewPopupID( url, view_args.arg1 ); case VIEW_ARTICLEPOPUPREFER: return new ARTICLE::ArticleViewPopupRefer( url, view_args.arg1 ); case VIEW_ARTICLEPOPUPDRAWOUT: return new ARTICLE::ArticleViewPopupDrawout( url, view_args.arg1, ( view_args.arg2 == "OR" ) ); case VIEW_ARTICLEPOPUPBM: return new ARTICLE::ArticleViewPopupBM( url ); ///////////////// case VIEW_IMAGEVIEW: return new IMAGE::ImageViewMain( url ); case VIEW_IMAGEICON: return new IMAGE::ImageViewIcon( url ); case VIEW_IMAGEPOPUP: return new IMAGE::ImageViewPopup( url ); ///////////////// case VIEW_MESSAGE: return new MESSAGE::MessageViewMain( url, view_args.arg1 ); case VIEW_NEWTHREAD: return new MESSAGE::MessageViewNew( url, view_args.arg1 ); default: return nullptr; } } jdim-0.7.0/src/viewfactory.h000066400000000000000000000031311417047150700157470ustar00rootroot00000000000000// ライセンス: GPL2 // // SKELETON::VIEWのファクトリー // #ifndef _VIEWFACTORY_H #define _VIEWFACTORY_H #include #include namespace SKELETON { class View; } namespace CORE { enum { VIEW_BBSLISTVIEW, VIEW_FAVORITELIST, VIEW_SELECTLIST, VIEW_HISTTHREAD, VIEW_HISTCLOSE, VIEW_HISTBOARD, VIEW_HISTCLOSEBOARD, VIEW_HISTCLOSEIMG, VIEW_BOARDVIEW, VIEW_BOARDNEXT, VIEW_BOARDLOG, VIEW_BOARDSIDEBAR, VIEW_ARTICLEVIEW, VIEW_ARTICLERES, VIEW_ARTICLENAME, VIEW_ARTICLEID, VIEW_ARTICLEBM, VIEW_ARTICLEPOST, VIEW_ARTICLEHIGHREFRES, VIEW_ARTICLEURL, VIEW_ARTICLEREFER, VIEW_ARTICLEDRAWOUT, VIEW_ARTICLEPREVIEW, VIEW_ARTICLEINFO, VIEW_ARTICLESEARCHLOG, VIEW_ARTICLESEARCHALLLOG, VIEW_ARTICLESEARCHTITLE, VIEW_ARTICLEPOSTLOG, VIEW_ARTICLEPOPUPHTML, VIEW_ARTICLEPOPUPRES, VIEW_ARTICLEPOPUPNAME, VIEW_ARTICLEPOPUPID, VIEW_ARTICLEPOPUPREFER, VIEW_ARTICLEPOPUPDRAWOUT, VIEW_ARTICLEPOPUPBM, VIEW_IMAGEVIEW, VIEW_IMAGEICON, VIEW_IMAGEPOPUP, VIEW_MESSAGE, VIEW_NEWTHREAD, VIEW_NONE }; struct VIEWFACTORY_ARGS { std::string arg1; std::string arg2; std::string arg3; std::string arg4; }; SKELETON::View* ViewFactory( int type, const std::string& url, VIEWFACTORY_ARGS view_args = VIEWFACTORY_ARGS() ); } #endif jdim-0.7.0/src/winmain.cpp000066400000000000000000000165171417047150700154160ustar00rootroot00000000000000// ライセンス: GPL2 #ifdef HAVE_CONFIG_H #include "config.h" #endif //#define _DEBUG #include "jddebug.h" #include "winmain.h" #include "command.h" #include "core.h" #include "session.h" #include "icons/iconmanager.h" #include "jdlib/loader.h" #include "control/controlutil.h" #include "control/controlid.h" #ifdef HAVE_MIGEMO_H #include "jdlib/jdmigemo.h" #include "config/globalconf.h" #endif JDWinMain::JDWinMain( const bool init, const bool skip_setupdiag, const int init_w, const int init_h, const int init_x, const int init_y ) : SKELETON::JDWindow( false ) { #ifdef _DEBUG std::cout << "JDWinMain::JDWinMain init = " << init << std::endl << "x y w h = " << JDWinMain::get_x_win() << " " << JDWinMain::get_y_win() << " " << JDWinMain::get_width_win() << " " << JDWinMain::get_height_win() << std::endl; #endif setlocale( LC_ALL, "ja_JP.UTF-8" ); // アイコンをセット std::vector< Glib::RefPtr< Gdk::Pixbuf > > list_icons; list_icons.push_back( ICON::get_icon( ICON::JD16 ) ); list_icons.push_back( ICON::get_icon( ICON::JD32 ) ); list_icons.push_back( ICON::get_icon( ICON::JD48 ) ); list_icons.push_back( ICON::get_icon( ICON::JD96 ) ); Gtk::Window::set_default_icon_list( list_icons ); // セッション回復 SESSION::init_session(); bool cancel_maximize = false; if( init_w >= 0 ){ cancel_maximize = true; JDWinMain::set_width_win( init_w ); } if( init_h >= 0 ){ cancel_maximize = true; JDWinMain::set_height_win( init_h ); } if( init_x >= 0 ){ cancel_maximize = true; JDWinMain::set_x_win( init_x ); } if( init_y >= 0 ){ cancel_maximize = true; JDWinMain::set_y_win( init_y ); } if( cancel_maximize ){ JDWinMain::set_maximized_win( false ); JDWinMain::set_full_win( false ); } // サイズ変更 init_win(); if( SESSION::is_full_win_main() ) fullscreen(); set_spacing( 4 ); set_modal( false ); property_window_position().set_value( Gtk::WIN_POS_NONE ); set_resizable( true ); property_destroy_with_parent().set_value( false ); add_events( Gdk::BUTTON_PRESS_MASK ); add_events( Gdk::BUTTON_RELEASE_MASK ); add_events( Gdk::POINTER_MOTION_MASK ); // migemo 初期化 #ifdef HAVE_MIGEMO_H jdmigemo::init( CONFIG::get_migemodict_path() ); #endif // 後はcoreを作って任せる m_core = std::make_unique( *this ); m_core->run( init, skip_setupdiag ); } JDWinMain::~JDWinMain() { #ifdef _DEBUG std::cout << "JDWinMain::~JDWinMain window size : x = " << JDWinMain::get_x_win() << " y = " << JDWinMain::get_y_win() << " w = " << JDWinMain::get_width_win() << " h = " << JDWinMain::get_height_win() << " max = " << JDWinMain::is_maximized_win() << std::endl; #endif m_core.reset(); JDLIB::check_loader_alive(); // migemo のクローズ #ifdef HAVE_MIGEMO_H jdmigemo::close(); #endif // アイコンマネージャ削除 ICON::delete_icon_manager(); } // 通常のセッション保存 void JDWinMain::save_session() { #ifdef _DEBUG std::cout << "JDWinMain::save_session\n"; #endif m_core->save_session(); } void JDWinMain::hide() { #ifdef _DEBUG std::cout << "JDWinMain::hide\n"; #endif // GNOME環境の時に閉じるとウィンドウが最小化する時があるので // state_eventをキャンセルする m_cancel_state_event = true; Gtk::Widget::hide(); } int JDWinMain::get_x_win() const { return SESSION::get_x_win_main(); } int JDWinMain::get_y_win() const { return SESSION::get_y_win_main(); } void JDWinMain::set_x_win( const int x ) { SESSION::set_x_win_main( x ); } void JDWinMain::set_y_win( const int y ) { SESSION::set_y_win_main( y ); } int JDWinMain::get_width_win() const { return SESSION::get_width_win_main(); } int JDWinMain::get_height_win() const { return SESSION::get_height_win_main(); } void JDWinMain::set_width_win( const int width ) { SESSION::set_width_win_main( width ); } void JDWinMain::set_height_win( const int height ) { SESSION::set_height_win_main( height ); } bool JDWinMain::is_focus_win() const { return SESSION::is_focus_win_main(); } void JDWinMain::set_focus_win( const bool set ) { SESSION::set_focus_win_main( set ); } bool JDWinMain::is_maximized_win() const { return SESSION::is_maximized_win_main(); } void JDWinMain::set_maximized_win( const bool set ) { SESSION::set_maximized_win_main( set ); } bool JDWinMain::is_iconified_win() const { return SESSION::is_iconified_win_main(); } void JDWinMain::set_iconified_win( const bool set ) { SESSION::set_iconified_win_main( set ); } bool JDWinMain::is_full_win() const { return SESSION::is_full_win_main(); } void JDWinMain::set_full_win( const bool set ) { SESSION::set_full_win_main( set ); } bool JDWinMain::is_shown_win() const { return SESSION::is_shown_win_main(); } void JDWinMain::set_shown_win( const bool set ) { SESSION::set_shown_win_main( set ); } bool JDWinMain::on_delete_event( GdkEventAny* event ) { #ifdef _DEBUG std::cout << "JDWinMain::on_delete_event\n"; #endif // GNOME環境の時に閉じるとウィンドウが最小化する時があるので // state_eventをキャンセルする m_cancel_state_event = true; return SKELETON::JDWindow::on_delete_event( event ); } // 最大、最小化 bool JDWinMain::on_window_state_event( GdkEventWindowState* event ) { // キャンセル ( JDWinMain::on_delete_even() の説明を参照せよ ) if( m_cancel_state_event ) { #ifdef _DEBUG std::cout << "JDWinMain::on_window_state_event cancel" << std::endl; #endif return Gtk::Window::on_window_state_event( event ); } return SKELETON::JDWindow::on_window_state_event( event ); } bool JDWinMain::on_configure_event( GdkEventConfigure* event ) { // キャンセル ( JDWinMain::on_delete_event() の説明を参照せよ ) if( m_cancel_state_event ) { #ifdef _DEBUG std::cout << "JDWinMain::on_configure_event cancel" << std::endl; #endif return Gtk::Window::on_configure_event( event ); } return SKELETON::JDWindow::on_configure_event( event ); } bool JDWinMain::on_button_press_event( GdkEventButton* event ) { #ifdef _DEBUG std::cout << "JDWinMain::on_button_press_event\n"; #endif // マウスジェスチャ m_control.MG_start( event ); return SKELETON::JDWindow::on_button_press_event( event ); } bool JDWinMain::on_button_release_event( GdkEventButton* event ) { #ifdef _DEBUG std::cout << "JDWinMain::on_button_release_event\n"; #endif const bool ret = SKELETON::JDWindow::on_button_release_event( event ); /// マウスジェスチャ const int mg = m_control.MG_end( event ); // マウスジェスチャ if( mg != CONTROL::None ) operate_win( mg ); return ret; } // // マウスが動いた // bool JDWinMain::on_motion_notify_event( GdkEventMotion* event ) { #ifdef _DEBUG std::cout << "JDWinMain::on_motion_notify_event\n"; #endif /// マウスジェスチャ m_control.MG_motion( event ); return SKELETON::JDWindow::on_motion_notify_event( event ); } bool JDWinMain::operate_win( const int control ) { return CONTROL::operate_common( control, std::string(), nullptr ); } jdim-0.7.0/src/winmain.h000066400000000000000000000036451417047150700150610ustar00rootroot00000000000000// ライセンス: GPL2 // // メインウィンドウとmain関数 // #ifndef _MAINWIN_H #define _MAINWIN_H #include "skeleton/window.h" #include "control/control.h" #include namespace CORE { class Core; } class JDWinMain : public SKELETON::JDWindow { std::unique_ptr m_core; bool m_cancel_state_event{}; // 入力コントローラ CONTROL::Control m_control; public: JDWinMain( const bool init, const bool skip_setupdiag, const int init_w, const int init_h, const int init_x, const int init_y ); ~JDWinMain(); // 通常のセッション保存 void save_session(); void hide(); protected: int get_x_win() const override; int get_y_win() const override; void set_x_win( const int x ) override; void set_y_win( const int y ) override; int get_width_win() const override; int get_height_win() const override; void set_width_win( const int width ) override; void set_height_win( const int height ) override; bool is_focus_win() const override; void set_focus_win( const bool set ) override; bool is_maximized_win() const override; void set_maximized_win( const bool set ) override; bool is_iconified_win() const override; void set_iconified_win( const bool set ) override; bool is_full_win() const override; void set_full_win( const bool set ) override; bool is_shown_win() const override; void set_shown_win( const bool set ) override; bool on_delete_event( GdkEventAny* event ) override; bool on_window_state_event( GdkEventWindowState* event ) override; bool on_configure_event( GdkEventConfigure* event ) override; bool on_button_press_event( GdkEventButton* event ) override; bool on_button_release_event( GdkEventButton* event ) override; bool on_motion_notify_event( GdkEventMotion* event ) override; private: bool operate_win( const int control ); }; #endif jdim-0.7.0/src/xml/000077500000000000000000000000001417047150700140365ustar00rootroot00000000000000jdim-0.7.0/src/xml/Makefile.am000066400000000000000000000003141417047150700160700ustar00rootroot00000000000000noinst_LIBRARIES = libxml.a libxml_a_SOURCES = \ document.cpp \ dom.cpp \ tools.cpp noinst_HEADERS = \ document.h \ dom.h \ tools.h AM_CXXFLAGS = @GTKMM_CFLAGS@ AM_CPPFLAGS = -I$(top_srcdir)/src jdim-0.7.0/src/xml/document.cpp000066400000000000000000000072401417047150700163630ustar00rootroot00000000000000// License GPL2 //#define _DEBUG #include "jddebug.h" #include "document.h" #include "jdlib/miscutil.h" enum { SIZE_OF_RAWDATA = 2 * 1024 * 1024 }; using namespace XML; // 文字列を元にノードツリーを作る場合( htmlはHTMLモード。デフォルト = false ) Document::Document( const std::string& str, const bool html ) : Dom( NODE_TYPE_DOCUMENT, "#document", html ) { init( str ); } // Gtk::TreeStore を元にノードツリーを作る場合 Document::Document( Glib::RefPtr< Gtk::TreeStore > treestore, SKELETON::EditColumns& columns, const std::string& root_name ) : Dom( NODE_TYPE_DOCUMENT, "#document" ) { init( treestore, columns, root_name ); } // 何も無い状態からノードツリーを作る場合 Document::Document() : Dom( NODE_TYPE_DOCUMENT, "#document" ) {} // // このクラスは代入可能 // Document& Document::operator=( const Document& document ) { if( this == &document ) return *this; copy_childNodes( document ); return *this; } // // 初期化 // void Document::init( const std::string& str ) { clear(); if( ! str.empty() ) parse( remove_comments( str ) ); } void Document::init( Glib::RefPtr< Gtk::TreeStore > treestore, SKELETON::EditColumns& columns, const std::string& root_name ) { clear(); // XMLとして必要なのでルート要素を作成 Dom* root = appendChild( NODE_TYPE_ELEMENT, root_name ); // ルート以下に追加 root->parse( treestore->children(), columns ); } // // 予めコメントやXML宣言などを取り除いておく( 順番に注意 ) // std::string Document::remove_comments( const std::string& str ) { if( str.size() > SIZE_OF_RAWDATA ) return std::string(); std::string out_str = str; out_str = MISC::remove_str( out_str, "" ); // out_str = MISC::remove_str( out_str, "" ); // out_str = MISC::remove_str( out_str, "" ); // * ]]> out_str = MISC::remove_str( out_str, "" ); // return out_str; } // // Gtk::TreeStore をセット // // list_path_expand = 後で Gtk::TreeView::expand_row() をするためのリスト // void Document::set_treestore( Glib::RefPtr< Gtk::TreeStore >& treestore, SKELETON::EditColumns& columns, const std::string& root_name, std::list< Gtk::TreePath >& list_path_expand ) const { treestore->clear(); if( size() ) { // ルートの子要素以下が対象 const Dom* root = get_root_element( root_name ); // ルート要素の有無で処理を分ける( 旧様式=無, 新様式=有 ) if( root ) root->append_treestore( treestore, columns, list_path_expand ); else append_treestore( treestore, columns, list_path_expand ); } } // // ルート要素を取得 // // node_name : 要素名で限定、空の時は要素名問わず取得 // static Dom* get_root_element_impl( const std::list& children, const std::string& node_name ) { auto it = std::find_if( children.cbegin(), children.cend(), []( const Dom* c ) { return c->nodeType() == NODE_TYPE_ELEMENT; } ); if( it != children.cend() && ( node_name.empty() || (*it)->nodeName() == node_name ) ) return *it; return nullptr; } Dom* Document::get_root_element( const std::string& node_name ) { // Domのfriend classなのでアクセス可能 return get_root_element_impl( m_childNodes, node_name ); } const Dom* Document::get_root_element( const std::string& node_name ) const { return get_root_element_impl( m_childNodes, node_name ); } jdim-0.7.0/src/xml/document.h000066400000000000000000000035001417047150700160230ustar00rootroot00000000000000// License GPL2 // Document( Dom派生 )クラス #ifndef _DOCUMENT_H #define _DOCUMENT_H #include "dom.h" namespace XML { class Document : public Dom { // コメントなどを取り除く std::string remove_comments( const std::string& str ); // コピーコンストラクタは使わない Document( const Document& ) = delete; public: // 文字列を元にノードツリーを作る場合( html = HTMLモード ) explicit Document( const std::string& str, const bool html = false ); // Gtk::TreeStore を元にノードツリーを作る場合 // ただし列は SKELETON::EditColumns を継承したものであること Document( Glib::RefPtr< Gtk::TreeStore > treestore, SKELETON::EditColumns& columns, const std::string& root_name ); // 何も無い状態からノードツリーを作る場合 Document(); ~Document() noexcept = default; // このクラスは代入可能 Document& operator=( const Document& document ); // 初期化 void init( const std::string& str ); void init( Glib::RefPtr< Gtk::TreeStore > treestore, SKELETON::EditColumns& columns, const std::string& root_name ); // Gtk::TreeStore をセットする // ただし列は SKELETON::EditColumns を継承したものであること void set_treestore( Glib::RefPtr< Gtk::TreeStore >& treestore, SKELETON::EditColumns& columns, const std::string& root_name, std::list< Gtk::TreePath >& list_path_expand ) const; // ルート要素を取得する Dom* get_root_element( const std::string& node_name = {} ); const Dom* get_root_element( const std::string& node_name = {} ) const; }; } #endif jdim-0.7.0/src/xml/dom.cpp000066400000000000000000000462771417047150700153410ustar00rootroot00000000000000// License: GPL2 //#define _DEBUG #include "jddebug.h" #include "dom.h" #include "jdlib/miscutil.h" #include "type.h" #include "tools.h" #include "skeleton/editcolumns.h" #include enum { SIZE_OF_RAWDATA = 2 * 1024 * 1024 }; using namespace XML; // staticなHTMLの要素名リストを再定義 std::set< std::string > Dom::m_static_html_elements; // コンストラクタ Dom::Dom( const int type, const std::string& name, const bool html ) : m_html( html ) , m_nodeType( type ) , m_nodeName( name ) { // HTMLの場合に空要素として扱う物の内で、使われていそうな物( 小文字で統一 ) if( html && m_static_html_elements.empty() ) { m_static_html_elements.insert( "base" ); m_static_html_elements.insert( "br" ); m_static_html_elements.insert( "hr" ); m_static_html_elements.insert( "img" ); m_static_html_elements.insert( "input" ); m_static_html_elements.insert( "link" ); m_static_html_elements.insert( "meta" ); } } // デストラクタ Dom::~Dom() noexcept { #ifdef _DEBUG std::cout << "~Dom() : " << m_nodeName << ", " << m_childNodes.size() << std::endl; #endif clear(); } // コピーコンストラクタ Dom::Dom( const Dom& dom ) : m_html( dom.m_html ) , m_nodeType( dom.m_nodeType ) , m_nodeName( dom.m_nodeName ) , m_nodeValue( dom.m_nodeValue ) , m_attributes( dom.m_attributes ) { copy_childNodes( dom ); } // // 子ノードのクリア // void Dom::clear() noexcept { for( Dom* child : m_childNodes ) { if( child ) delete child; } m_childNodes.clear(); } // // XMLの文字列をパースして子ノードを追加する // void Dom::parse( const std::string& str ) { if( str.empty() || str.size() > SIZE_OF_RAWDATA ) return; size_t current_pos = 0; const size_t str_length = str.length(); while( current_pos < str_length ) { //プロパティなどに使う変数 int type = NODE_TYPE_UNKNOWN; std::string name; std::string value; std::map< std::string, std::string > attributes_pair; std::string next_source; // "<"を探す const std::size_t tag_lt_pos = str.find( '<', current_pos ); // タグの前のテキストノード if( current_pos < tag_lt_pos ) { type = NODE_TYPE_TEXT; name = "#text"; value = str.substr( current_pos, tag_lt_pos - current_pos ); current_pos = tag_lt_pos; } // 全てがテキストノード else if( tag_lt_pos == std::string::npos ) { type = NODE_TYPE_TEXT; name = "#text"; value = str; current_pos = str.length(); } // 要素ノード else if( current_pos == tag_lt_pos ) { const std::size_t tag_gt_pos = str.find( '>', tag_lt_pos + 1 ); current_pos = tag_gt_pos + 1; // 開始タグの中身( )を取り出す std::string open_tag = str.substr( tag_lt_pos + 1, tag_gt_pos - tag_lt_pos - 1 ); if( open_tag.empty() ) continue; // タグの中身がアルファベットで始まっているかチェック if( !( open_tag[0] >= 'A' && open_tag[0] <= 'Z' ) && !( open_tag[0] >= 'a' && open_tag[0] <= 'z' ) ) { continue; } // タグ構造が壊れてる場合 size_t broken_pos = 0; if( ( broken_pos = open_tag.find( '<' ) ) != std::string::npos ) { current_pos += broken_pos; continue; } // XMLの場合に空要素として扱う要素( )かどうか bool empty_element = false; if( ! m_html && open_tag.compare( open_tag.length() - 1, 1, "/" ) == 0 ) { empty_element = true; open_tag.erase( open_tag.length() - 1 ); } // 要素名を取り出す std::string element_name; size_t i = 0; const size_t open_tag_length = open_tag.length(); while( i < open_tag_length && open_tag[i] != '\n' && open_tag[i] != '\t' && open_tag[i] != ' ' ) { element_name += open_tag[i]; ++i; } if( element_name.empty() ) continue; // 要素ノードのタイプを設定 type = NODE_TYPE_ELEMENT; // 要素名は小文字に統一 name = MISC::tolower_str( element_name ); // 属性ペアのリストを作成 attributes_pair = create_attribute( open_tag.substr( i ) ); // HTMLの場合に空要素として扱う要素かどうか if( m_html && m_static_html_elements.find( name ) != m_static_html_elements.end() ) empty_element = true; // 終了タグがある要素からの中身の取り出し( 中身 ) if( ! empty_element ) { // count は見つける必要がある終了タグの数 size_t close_tag_lt_pos = 0, close_tag_gt_pos = 0, count = 1; while( ( close_tag_lt_pos = str.find( '<', current_pos ) ) != std::string::npos && ( close_tag_gt_pos = str.find( '>', close_tag_lt_pos + 1 ) ) != std::string::npos ) { current_pos = close_tag_gt_pos + 1; // タグの中身を取り出す const std::string close_tag = MISC::tolower_str( str.substr( close_tag_lt_pos + 1, close_tag_gt_pos - close_tag_lt_pos - 1 ) ); // タグ構造が壊れてる場合 if( close_tag.empty() ) continue; else if( ( broken_pos = close_tag.find( '<' ) ) != std::string::npos ) { current_pos += broken_pos; continue; } // 空要素でない同名の開始タグを見つけたらカウントを増やす if( close_tag.rfind( name, 0 ) == 0 && close_tag.compare( close_tag.size() - 1, 1, "/" ) != 0 ) ++count; // 終了タグを見つけたらカウントを減らす else if( close_tag.compare( 0, name.length() + 1, "/" + name ) == 0 ) --count; // 終了タグを見つける必要数が 0 になったらループを抜ける if( count == 0 ) break; } // 必要な終了タグが見付からないまま上記のループを抜けた場合は、全体のループを抜ける if( count > 0 ) break; // 次の再帰で使う"子ノード"の素材になる文字列 next_source = str.substr( tag_gt_pos + 1, close_tag_lt_pos - tag_gt_pos - 1 ); } } #ifdef _DEBUG std::cout << "Dom:parse():---------------------------------------\n"; std::cout << "nodeName : " << name << "\n"; std::cout << "nodeType : " << type << "\n"; for( const auto& attr : attributes_pair ) { std::cout << "Attribute: " << "name=" << attr.first << ", value=" << attr.second << std::endl; } std::cout << "nodeValue: " << value << std::endl; #endif // ノードを追加 Dom* node = appendChild( type, name ); node->m_attributes = std::move( attributes_pair ); node->m_nodeValue = std::move( value ); // 再帰 if( ! next_source.empty() ) node->parse( next_source ); } } // // 属性ペアのリストを作成 // std::map< std::string, std::string > Dom::create_attribute( const std::string& str ) const { std::map< std::string, std::string > attributes_pair; if( str.empty() ) return attributes_pair; size_t i = 0, str_length = str.length(); while( i < str_length ) { std::string attr_key; std::string attr_value; // 余分なスペース等を詰める while( i < str_length && ( str[i] == '\n' || str[i] == '\t' || str[i] == ' ' ) ) ++i; // "="かスペース等でなければ属性名とする while( i < str_length && str[i] != '\n' && str[i] != '\t' && str[i] != ' ' && str[i] != '=' ) { attr_key += str[i]; ++i; } // 余分なスペース等を詰める while( i < str_length && ( str[i] == '\n' || str[i] == '\t' || str[i] == ' ' ) ) ++i; // "="の区切りがある場合のみ属性の値をセットする if( str[i] == '=' ) { // "="の分進める ++i; // 余分なスペース等を詰める while( i < str_length && ( str[i] == '\n' || str[i] == '\t' || str[i] == ' ' ) ) ++i; // 属性の値 "〜" if( str[i] == '"' ) { ++i; while( i < str_length && str[i] != '"' ) { attr_value += str[i]; ++i; } if( str[i] == '"' ) ++i; } // 属性の値 '〜' else if( str[i] == '\'' ) { ++i; while( i < str_length && str[i] != '\'' ) { attr_value += str[i]; ++i; } if( str[i] == '\'' ) ++i; } // 属性の値 その他( 本来ならXMLとしてエラー ) else { while( i < str_length && str[i] != '\n' && str[i] != '\t' && str[i] != ' ' ) { attr_value += str[i]; ++i; } } } // 属性を追加 if( ! attr_key.empty() && ! attr_value.empty() ) { // 属性名は小文字に統一する attributes_pair.insert( make_pair( MISC::tolower_str( attr_key ), attr_value ) ); } } return attributes_pair; } // // XMLタグ構造の文字列を生成 // std::string Dom::get_xml( const int n ) const { std::stringstream xml; // インデント std::string indent; // テキストノードの文字列 std::string text; // ノードの種類別に処理 switch( m_nodeType ) { // 要素 case NODE_TYPE_ELEMENT: // インデントを追加 for( int i = 0; i < n; ++i ) indent += " "; // タグの始まり xml << indent << "<" << m_nodeName; // 属性を追加 for( const auto& attr : m_attributes ) { xml << " " << attr.first << "=\"" << attr.second << "\""; } // 子要素がある場合 if( ! m_childNodes.empty() ) { xml << ">\n"; for( const Dom* child : m_childNodes ) { // 子要素をたどる xml << child->get_xml( n + 2 ); } xml << indent << "\n"; } // 空要素の場合 else xml << " />\n"; break; // テキストノード case NODE_TYPE_TEXT: text = MISC::remove_spaces( m_nodeValue ); if( ! text.empty() ) { // インデントを追加 for( int i = 0; i < n; ++i ) indent += " "; xml << indent << text << "\n"; } break; // ドキュメントノード case NODE_TYPE_DOCUMENT: if( ! m_childNodes.empty() ) xml << "\n"; for( const Dom* child : m_childNodes ) { // 子要素をたどる xml << child->get_xml(); } break; } return xml.str(); } // // Gtk::TreeModel::Children からノードツリーを生成 // // ただし列は SKELETON::EditColumns を継承したものであること // void Dom::parse( const Gtk::TreeModel::Children& children, SKELETON::EditColumns& columns ) { if( children.empty() ) return; // Gtk::TreeModel::Children を走査 for( const Gtk::TreeModel::Row& row : children ) { // 各値を取得( skeleton/editcolumns.h を参照 ) const int type = row[ columns.m_type ]; const Glib::ustring& url = row[ columns.m_url ]; const Glib::ustring& data = row[ columns.m_data ]; const Glib::ustring& name = row[ columns.m_name ]; const size_t dirid = row[ columns.m_dirid ]; const bool expand = row[ columns.m_expand ]; if( type != TYPE_UNKNOWN ) { // タイプにより要素名を決定( board や link など) const std::string node_name = XML::get_name( type ); if( ! node_name.empty() ) { Dom* node = appendChild( NODE_TYPE_ELEMENT, node_name ); if( type == TYPE_DIR && expand ) node->setAttribute( "open", "y" ); if( ! name.empty() ) node->setAttribute( "name", name ); if( ! url.empty() ) node->setAttribute( "url", url ); if( ! data.empty() ) node->setAttribute( "data", data ); if( dirid ) node->setAttribute( "dirid", dirid ); // 再帰 const Gtk::TreeModel::Children sub_children = row.children(); if( ! sub_children.empty() ) node->parse( sub_children, columns ); } } } } // // ノードを分解して Gtk::TreeStore へ Gtk::TreeModel::Row を追加 // // ただし列は SKELETON::EditColumns を継承したものであること // // list_path_expand は開いてるツリーを格納するための参照渡し // void Dom::append_treestore( Glib::RefPtr< Gtk::TreeStore >& treestore, SKELETON::EditColumns& columns, std::list< Gtk::TreePath >& list_path_expand, const Gtk::TreeModel::Row& parent ) const { // ノードの子要素を走査 for( const Dom* child : m_childNodes ) { const int node_type = child->nodeType(); if( node_type == NODE_TYPE_ELEMENT ) { const int type = XML::get_type( child->nodeName() ); if( type != TYPE_UNKNOWN ) { Gtk::TreeModel::Row row; // Gtk::TreeStore::append() の追加ポイント if( parent ) row = *( treestore->append( parent.children() ) ); else row = *( treestore->append() ); // 各値をセット columns.setup_row( row, child->getAttribute( "url" ), child->getAttribute( "name" ), child->getAttribute( "data" ), type, 0 ); if( type == TYPE_DIR ){ row[ columns.m_dirid ] = atoi( child->getAttribute( "dirid" ).c_str() ); // 開いているツリーを追加 if( child->getAttribute( "open" ) == "y" ) list_path_expand.push_back( treestore->get_path( row ) ); } // 再帰 if( child->hasChildNodes() ) child->append_treestore( treestore, columns, list_path_expand, row ); } } } } // // getElementById() // Dom* Dom::getElementById( const std::string& id ) const { Dom* node = nullptr; for( Dom* child : m_childNodes ) { if( child->nodeType() == NODE_TYPE_ELEMENT ) { if( child->getAttribute( "id" ) == id ) node = child; // 再帰 else if( child->hasChildNodes() ) node = child->getElementById( id ); if( node ) break; } } return node; } // // getElementsByTagName() // std::list Dom::getElementsByTagName( const std::string& name ) const { std::list domlist; for( Dom* child : m_childNodes ) { if( child->nodeType() == NODE_TYPE_ELEMENT ) { if( child->nodeName() == name ) domlist.push_back( child ); // 再帰 std::list sub_nodes = child->getElementsByTagName( name ); domlist.splice( domlist.end(), std::move( sub_nodes ) ); } } return domlist; } // // ノード:hasChildNodes // bool Dom::hasChildNodes() const noexcept { return ! m_childNodes.empty(); } // // dom の子ノードをコピーする // void Dom::copy_childNodes( const Dom& dom ) { clear(); for( const Dom* child : dom.m_childNodes ) { Dom* node = new Dom( child->m_nodeType, child->m_nodeName ); node->m_nodeValue = child->m_nodeValue; node->m_parentNode = this; node->m_attributes = child->m_attributes; node->copy_childNodes( *child ); m_childNodes.push_back( node ); } } // // ノード:firstChild // Dom* Dom::firstChild() const { if( m_childNodes.empty() ) return nullptr; return m_childNodes.front(); } // // ノード:appendChild() // Dom* Dom::appendChild( const int node_type, const std::string& node_name ) { Dom* node = new Dom( node_type, node_name, m_html ); node->m_parentNode = this; m_childNodes.push_back( node ); return node; } // // ノード:removeChild() // bool Dom::removeChild( Dom* node ) { if( ! node ) return false; m_childNodes.remove( node ); delete node; return true; } // // insertBefore() の機能をフルに使っていなかったためシンプルにした関数を導入する // Dom* Dom::emplace_front( const int node_type, const std::string& node_name ) { Dom* node = new Dom( node_type, node_name ); node->m_parentNode = this; m_childNodes.push_front( node ); return node; } // // 属性:getAttribute() // std::string Dom::getAttribute( const std::string& name ) const { std::string value; if( name.empty() ) return value; std::map< std::string, std::string >::const_iterator it = m_attributes.find( name ); if( it != m_attributes.cend() ) value = MISC::html_unescape( (*it).second ); return value; } // // 属性:setAttribute( std::string ) // bool Dom::setAttribute( const std::string& name, const std::string& value ) { if( name.empty() || value.empty() || m_nodeType != NODE_TYPE_ELEMENT ) return false; // 属性名は小文字に統一 const std::string attr_name = MISC::tolower_str( name ); m_attributes.insert( make_pair( attr_name, MISC::html_escape( value ) ) ); return true; } // // 属性:setAttribute( int ) // bool Dom::setAttribute( const std::string& name, const int value ) { if( name.empty() || m_nodeType != NODE_TYPE_ELEMENT ) return false; // 属性名は小文字に統一 const std::string attr_name = MISC::tolower_str( name ); m_attributes.insert( make_pair( attr_name, std::to_string( value ) ) ); return true; } jdim-0.7.0/src/xml/dom.h000066400000000000000000000103631417047150700147710ustar00rootroot00000000000000// License: GPL2 // DOM基本クラス #ifndef _DOM_H #define _DOM_H #include #include #include #include #include namespace SKELETON { class EditColumns; } namespace XML { // ノードタイプ( m_nodeType ) enum { NODE_TYPE_UNKNOWN = 0, NODE_TYPE_ELEMENT, NODE_TYPE_TEXT, NODE_TYPE_DOCUMENT }; class Dom { // HTMLの時に空要素として扱う要素を格納する static std::set< std::string > m_static_html_elements; // HTMLモード有/無 bool m_html; // プロパティ int m_nodeType; std::string m_nodeName; std::string m_nodeValue; Dom* m_parentNode{}; // 属性ペアのリスト std::map< std::string, std::string > m_attributes; // 子要素リスト std::list< Dom* > m_childNodes; private: // このクラスの代入演算子は使わない Dom& operator=( const Dom& ) = delete; // 属性ペアのリストを作成 std::map< std::string, std::string > create_attribute( const std::string& str ) const; protected: // Document をフレンドクラスとする( append_treestore() を public にしたくない ) friend class Document; // コピーコンストラクタ Dom( const Dom& dom ); // パースして子ノードを追加 void parse( const std::string& str ); void parse( const Gtk::TreeModel::Children& children, SKELETON::EditColumns& columns ); // プロパティをセットするアクセッサ void copy_childNodes( const Dom& dom ); // dom の子ノードをコピーする // ノードを分解して Gtk::TreeStore へ Gtk::TreeModel::Row を追加 // ただし列は SKELETON::EditColumns を継承したものであること void append_treestore( Glib::RefPtr< Gtk::TreeStore >& treestore, SKELETON::EditColumns& columns, std::list< Gtk::TreePath >& list_path_expand, const Gtk::TreeModel::Row& parnet = Gtk::TreeModel::Row() ) const; public: // コンストラクタ、デストラクタ Dom( const int type, const std::string& name, const bool html = false ); virtual ~Dom() noexcept; // クリア void clear() noexcept; // XMLタグ構造の文字列を生成 std::string get_xml( const int n = 0 ) const; // getElement(s)By*() Dom* getElementById( const std::string& id ) const; std::list getElementsByTagName( const std::string& name ) const; // プロパティを扱うアクセッサ int nodeType() const noexcept { return m_nodeType; } std::string nodeName() const { return m_nodeName; } std::string nodeValue() const { return m_nodeValue; } void nodeValue( const std::string& value ) { m_nodeValue = value; } // ノード // 注意:appendChild() は // 戻り値と引数がJavascriptなどのDOMとは異なります。 // // delete忘れを防ぐために外部でnewしない方が良いだろうという // 事で、メンバ関数の内部でnewして追加されたノードのポインタ // を返すようにしてあります。 // // クラス外で使用していないメンバ関数は削除してあります。 bool hasChildNodes() const noexcept; std::list childNodes() const = delete; Dom* firstChild() const; Dom* appendChild( const int node_type, const std::string& node_name ); bool removeChild( Dom* node ); Dom* emplace_front( int node_type, const std::string& node_name ); // 属性 std::string getAttribute( const std::string& name ) const; bool setAttribute( const std::string& name, const std::string& value ); bool setAttribute( const std::string& name, const int value ); std::size_t size() const noexcept { return m_childNodes.size(); } auto begin() const noexcept { return m_childNodes.cbegin(); } auto end() const noexcept { return m_childNodes.cend(); } }; } #endif jdim-0.7.0/src/xml/meson.build000066400000000000000000000002731417047150700162020ustar00rootroot00000000000000sources = [ 'document.cpp', 'dom.cpp', 'tools.cpp', ] xml_lib = static_library( 'xml', sources, dependencies : gtkmm_dep, include_directories : include_directories('..'), ) jdim-0.7.0/src/xml/tools.cpp000066400000000000000000000105171417047150700157060ustar00rootroot00000000000000// License: GPL2 #include "tools.h" #include "icons/iconmanager.h" #include "type.h" // // TYPE_ID から要素名を取得( TYPE_ID は global.h を参照 ) // std::string XML::get_name( const int type_id ) { std::string name; switch( type_id ) { case TYPE_DIR: // サブディレクトリ name = "subdir"; break; case TYPE_BOARD: // 板 name = "board"; break; case TYPE_BOARD_UPDATE: // 更新可能板 name = "board_update"; break; case TYPE_THREAD: // スレ name = "thread"; break; case TYPE_THREAD_UPDATE: // 更新可能スレ name = "thread_update"; break; case TYPE_THREAD_OLD: // dat落ちスレ name = "thread_old"; break; case TYPE_IMAGE: // 画像 name = "image"; break; case TYPE_COMMENT: // コメント name = "comment"; break; case TYPE_LINK: // リンク name = "link"; break; case TYPE_VBOARD: // 仮想板 name = "vboard"; break; case TYPE_AA: // AA name = "aa"; break; case TYPE_HISTITEM: // HISTORY::ViewHistoryItem name = "histitem"; break; case TYPE_USRCMD: name = "usrcmd"; break; case TYPE_LINKFILTER: name = "linkfilter"; break; case TYPE_REPLACESTR: name = "replacestr"; break; case TYPE_SEPARATOR: name = "separator"; break; } return name; } // // 要素名から TYPE_ID を取得 // int XML::get_type( const std::string& node_name ) { int type = TYPE_UNKNOWN; if( node_name == "board" ) { type = TYPE_BOARD; } else if( node_name == "board_update" ) { type = TYPE_BOARD_UPDATE; } else if( node_name == "comment" ) { type = TYPE_COMMENT; } else if( node_name == "image" ) { type = TYPE_IMAGE; } else if( node_name == "link" ) { type = TYPE_LINK; } else if( node_name == "vboard" ) { type = TYPE_VBOARD; } else if( node_name == "subdir" ) { type = TYPE_DIR; } else if( node_name == "thread" ) { type = TYPE_THREAD; } else if( node_name == "thread_update" ) { type = TYPE_THREAD_UPDATE; } else if( node_name == "thread_old" ) { type = TYPE_THREAD_OLD; } else if( node_name == "aa" ) { type = TYPE_AA; } else if( node_name == "histitem" ) { type = TYPE_HISTITEM; } else if( node_name == "usrcmd" ) { type = TYPE_USRCMD; } else if( node_name == "linkfilter" ) { type = TYPE_LINKFILTER; } else if( node_name == "replacestr" ) { type = TYPE_REPLACESTR; } else if( node_name == "separator" ) { type = TYPE_SEPARATOR; } return type; } // // TYPE_ID からアイコンを取得 ( アイコンは icons/iconmanager.h を参照 ) // Glib::RefPtr< Gdk::Pixbuf > XML::get_icon( const int type_id ) { Glib::RefPtr< Gdk::Pixbuf > icon = ICON::get_icon( ICON::TRANSPARENT ); switch( type_id ) { case TYPE_DIR: icon = ICON::get_icon( ICON::DIR ); break; case TYPE_BOARD: icon = ICON::get_icon( ICON::BOARD ); break; case TYPE_BOARD_UPDATE: icon = ICON::get_icon( ICON::BOARD_UPDATE ); break; case TYPE_THREAD: icon = ICON::get_icon( ICON::THREAD ); break; case TYPE_THREAD_UPDATE: icon = ICON::get_icon( ICON::THREAD_UPDATE ); break; case TYPE_THREAD_OLD: icon = ICON::get_icon( ICON::THREAD_OLD ); break; case TYPE_IMAGE: icon = ICON::get_icon( ICON::IMAGE ); break; case TYPE_LINK: icon = ICON::get_icon( ICON::LINK ); break; case TYPE_VBOARD: icon = ICON::get_icon( ICON::BOARD ); break; case TYPE_USRCMD: icon = ICON::get_icon( ICON::THREAD ); break; } return icon; } jdim-0.7.0/src/xml/tools.h000066400000000000000000000007451417047150700153550ustar00rootroot00000000000000// License: GPL2 #ifndef _TOOLS_H #define _TOOLS_H #include #include namespace XML { // TYPE_ID から要素名を取得( TYPE_ID は global.h を参照 ) std::string get_name( const int type_id ); // 要素名から TYPE_ID を取得 int get_type( const std::string& node_name ); // TYPE_ID からアイコンを取得 ( アイコンは icons/iconmanager.h を参照 ) Glib::RefPtr< Gdk::Pixbuf > get_icon( const int type_id ); } #endif jdim-0.7.0/test/000077500000000000000000000000001417047150700134265ustar00rootroot00000000000000jdim-0.7.0/test/Makefile.am000066400000000000000000000071161417047150700154670ustar00rootroot00000000000000AUTOMAKE_OPTIONS = subdir-objects GTEST_SRCDIR = @GTEST_SRCDIR@ .PHONY: test # no-op all: ; test: check if USE_GTEST_SOURCES GTEST_DIR = $(GTEST_SRCDIR)/googletest GTEST_INCS = -isystem $(GTEST_DIR)/include GTEST_MAIN_LIB = gtest_main.a # For simplicity and to avoid depending on Google Test's # implementation details, the dependencies specified below are # conservative and not optimized. This is fine as Google Test # compiles fast and for ordinary users its source rarely changes. gtest-all.o: $(GTEST_DIR)/src/gtest-all.cc $(CXX) $(CPPFLAGS) $(GTEST_INCS) -I$(GTEST_DIR) $(CXXFLAGS) -c -o $@ $< gtest_main.o: $(GTEST_DIR)/src/gtest_main.cc $(CXX) $(CPPFLAGS) $(GTEST_INCS) -I$(GTEST_DIR) $(CXXFLAGS) -c -o $@ $< $(GTEST_MAIN_LIB): gtest-all.o gtest_main.o $(AR) $(ARFLAGS) $@ $^ endif check_PROGRAMS = gtest_jdim TESTS = gtest_jdim AM_CXXFLAGS = @GTKMM_CFLAGS@ @GNUTLS_CFLAGS@ @OPENSSL_CFLAGS@ @LIBSM_CFLAGS@ @GTEST_CFLAGS@ \ $(GTEST_INCS) -I$(top_srcdir)/src gtest_jdim_LDADD = \ $(top_builddir)/src/article/libarticle.a \ $(top_builddir)/src/bbslist/libbbslist.a \ $(top_builddir)/src/board/libboard.a \ $(top_builddir)/src/config/libconfig.a \ $(top_builddir)/src/control/libcontrol.a \ $(top_builddir)/src/dbimg/libdbimg.a \ $(top_builddir)/src/dbtree/libdbtree.a \ $(top_builddir)/src/history/libhistory.a \ $(top_builddir)/src/icons/libicon.a \ $(top_builddir)/src/image/libimage.a \ $(top_builddir)/src/jdlib/libjdlib.a \ $(top_builddir)/src/message/libmessage.a \ $(top_builddir)/src/skeleton/libskeleton.a \ $(top_builddir)/src/sound/libsound.a \ $(top_builddir)/src/xml/libxml.a \ \ $(top_builddir)/src/aamanager.o \ $(top_builddir)/src/articleitemmenupref.o \ $(top_builddir)/src/articleitempref.o \ $(top_builddir)/src/boarditemmenupref.o \ $(top_builddir)/src/boarditempref.o \ $(top_builddir)/src/browsers.o \ $(top_builddir)/src/cache.o \ $(top_builddir)/src/command.o \ $(top_builddir)/src/compmanager.o \ $(top_builddir)/src/core.o \ $(top_builddir)/src/cssmanager.o \ $(top_builddir)/src/dispatchmanager.o \ $(top_builddir)/src/dndmanager.o \ $(top_builddir)/src/environment.o \ $(top_builddir)/src/fontcolorpref.o \ $(top_builddir)/src/iomonitor.o \ $(top_builddir)/src/linkfiltermanager.o \ $(top_builddir)/src/linkfilterpref.o \ $(top_builddir)/src/livepref.o \ $(top_builddir)/src/login2ch.o \ $(top_builddir)/src/loginbe.o \ $(top_builddir)/src/mainitempref.o \ $(top_builddir)/src/maintoolbar.o \ $(top_builddir)/src/menuslots.o \ $(top_builddir)/src/msgitempref.o \ $(top_builddir)/src/openurldiag.o \ $(top_builddir)/src/prefdiagfactory.o \ $(top_builddir)/src/replacestrmanager.o \ $(top_builddir)/src/replacestrpref.o \ $(top_builddir)/src/searchitempref.o \ $(top_builddir)/src/searchloader.o \ $(top_builddir)/src/searchmanager.o \ $(top_builddir)/src/session.o \ $(top_builddir)/src/setupwizard.o \ $(top_builddir)/src/sharedbuffer.o \ $(top_builddir)/src/sidebaritempref.o \ $(top_builddir)/src/updatemanager.o \ $(top_builddir)/src/urlreplacemanager.o \ $(top_builddir)/src/usrcmdmanager.o \ $(top_builddir)/src/usrcmdpref.o \ $(top_builddir)/src/viewfactory.o \ $(top_builddir)/src/winmain.o \ \ $(GTEST_MAIN_LIB) \ @LIBS@ @GTKMM_LIBS@ @GNUTLS_LIBS@ @OPENSSL_LIBS@ @LIBSM_LIBS@ @ALSA_LIBS@ @ONIG_LIBS@ @X11_LIBS@ @GTEST_LIBS@ # テストコードのファイルを追加したらここにリストします # 行末のバックスラッシュに注意 gtest_jdim_SOURCES = \ gtest_jdlib_cookiemanager.cpp \ gtest_jdlib_jdiconv.cpp \ gtest_jdlib_misctime.cpp \ gtest_jdlib_misctrip.cpp \ gtest_jdlib_miscutil.cpp \ gtest_xml_dom.cpp jdim-0.7.0/test/README.md000066400000000000000000000116521417047150700147120ustar00rootroot00000000000000# JDim テストガイド JDimは [Google Test][google_test] を使って機能をテストします。 テストはmakeコマンドでビルド、実行が行えます。 makeのかわりに [Meson][mesonbuild] を利用することもできます。([下記参照](#meson-test)) ## テストプログラムのビルドに必要なもの - JDimソースコードの各namespaceで生成されるオブジェクトファイル(\*.o)や静的ライブラリ(\*.a) - googletestのライブラリ、またはソースコード (セットアップを参照) ## セットアップ (a)ディストリビューションのパッケージをインストールするか、 (b)ソースコード(googletestリポジトリ)を利用してください。 #### (a) ディストリビューションのパッケージを利用する場合 ディストロのパッケージ管理ツールを使ってgoogletestのパッケージをインストールします。 ##### Debian系 1. `libgtest-dev`をインストールします。 ```sh sudo apt install libgtest-dev ``` 2. ./configureを実行してmakeコマンドでJDimをビルドします。 ```sh autoreconf -i ./configure make ``` #### (b) ソースコード(googletestリポジトリ)を利用する場合 1. GitHubの"[google/googletest][google_test]"リポジトリからmasterブランチをクローンします。 他のgoogletestリポジトリでも可能なはずです。 ```sh git clone -b master --depth 1 https://github.com/google/googletest.git /path/to/googletest ``` 2. configureスクリプトの引数 **GTEST_SRCDIR** にリポジトリのフルパスを指定してください。 makeコマンドでJDimをビルドします。 ```sh autoreconf -i ./configure GTEST_SRCDIR=/path/to/googletest make ``` #### セットアップの注意 互換性のためディストロのパッケージよりソースコードが優先されます。 パッケージを利用するときは **GTEST_SRCDIR** を設定しないでください。 ## テストのビルドと実行 makeの **test** サブコマンドでテストコードのビルドと実行を行います。 結果表示など詳細はGoogle Testを解説しているwebページを参照してください。 ```sh make test ``` ライブラリとソースコードどちらも見つからないときは `make test` (及び `make check`)は失敗します。 ## テストを追加する テストコードのファイル名は gtest\_\*.cpp の形式にする必要があります。 ファイル名は小文字で `gtest_サブディレクトリ名_ソースファイル名.cpp` を推奨します。 テストの記述方法はテストコードを見るかwebを参照してください。 例 * `src/jdlib/miscutil.cpp` → `test/gtest_jdlib_miscutil.cpp` * `src/core.cpp` → `test/gtest_core.cpp` 追加したテストコードのファイルを **test/Makefile.am** の `gtest_jdim_SOURCES` にリストします。 (行末のバックスラッシュ`\`に注意) ## 制限 以下は今のところサポートしておりません。 * JDim全体ではなくテスト対象のソースコードだけをビルドする * `src/main.cpp` をテストする (ファイル名やエントリーポイントが衝突する) [google_test]: https://github.com/google/googletest --- ## Mesonを利用してテストする Mesonのインストール方法は[GitHub][#556]を参照してください。 ### セットアップ [事前準備][readme-prepare]に加えて`googletest`をインストールします。 (a)ディストリビューションのパッケージをインストールするか、 (b)[meson wraptool][meson-wraptool]を利用してください。 #### (a) ディストリビューションのパッケージを利用する場合 ディストロのパッケージ管理ツールを使ってgoogletestのパッケージをインストールします。 ##### Debian系 ```sh sudo apt install libgtest-dev ``` #### (b) Meson wraptool を利用する場合 [Wrap DB][wrapdb]を利用してローカルの`subprojects`ディレクトリにインストールします。 googletestのソースコードは構成時にダウンロードされます。 ```sh mkdir subprojects meson wrap install gtest ``` ### テストのビルドと実行 mesonの **test** サブコマンドでテストプログラムのビルドと実行を行います。 結果表示など詳細はMesonの[リファレンス][meson-reference]を参照してください。 ```sh meson builddir meson test -C builddir ``` [mesonbuild]: https://mesonbuild.com/ [#556]: https://github.com/JDimproved/JDim/discussions/556 [readme-prepare]: https://github.com/JDimproved/JDim/blob/master/README.md#%E4%BA%8B%E5%89%8D%E6%BA%96%E5%82%99 [meson-wraptool]: https://mesonbuild.com/Using-wraptool.html [wrapdb]: https://wrapdb.mesonbuild.com/gtest [meson-reference]: https://mesonbuild.com/Unit-tests.html#other-test-options jdim-0.7.0/test/gtest_jdlib_cookiemanager.cpp000066400000000000000000000054641417047150700213210ustar00rootroot00000000000000// License: GPL2 #include "jdlib/cookiemanager.h" #include "gtest/gtest.h" namespace { class CookieManager_TestBase : public ::testing::Test { public: JDLIB::CookieManager* the_cookie_manager{}; void SetUp() override { the_cookie_manager = JDLIB::get_cookie_manager(); } void TearDown() override { JDLIB::delete_cookie_manager(); the_cookie_manager = nullptr; } }; class CookieManager_GetCookieByHost : public CookieManager_TestBase {}; TEST_F(CookieManager_GetCookieByHost, no_data) { const std::string expect = ""; EXPECT_EQ( expect, the_cookie_manager->get_cookie_by_host( "example.com" ) ); } TEST_F(CookieManager_GetCookieByHost, single_value) { the_cookie_manager->feed( "example.com", "foo=bar" ); const std::string expect = "foo=bar"; EXPECT_EQ( expect, the_cookie_manager->get_cookie_by_host( "example.com" ) ); } TEST_F(CookieManager_GetCookieByHost, multiple_values) { the_cookie_manager->feed( "example.com", "foo=bar" ); the_cookie_manager->feed( "example.com", "baz=qux" ); // 現在の実装ではクッキー名を辞書順でソートする const std::string expect = "baz=qux; foo=bar"; EXPECT_EQ( expect, the_cookie_manager->get_cookie_by_host( "example.com" ) ); } TEST_F(CookieManager_GetCookieByHost, empty_values) { the_cookie_manager->feed( "example.com", "foo=" ); the_cookie_manager->feed( "example.com", "bar=" ); const std::string expect = "bar=; foo="; EXPECT_EQ( expect, the_cookie_manager->get_cookie_by_host( "example.com" ) ); } TEST_F(CookieManager_GetCookieByHost, parsing_domain) { the_cookie_manager->feed( "first.hello.world.test", "foo=bar; domain=.world.test" ); the_cookie_manager->feed( "second.hello.world.test", "baz=qux" ); const std::string expect = "baz=qux; foo=bar"; EXPECT_EQ( expect, the_cookie_manager->get_cookie_by_host( "second.hello.world.test" ) ); } TEST_F(CookieManager_GetCookieByHost, parsing_path) { // ルート(/)以外のpathは無視する the_cookie_manager->feed( "example.com", "foo=bar; path=/" ); the_cookie_manager->feed( "example.com", "baz=qux; path=/user" ); const std::string expect = "foo=bar"; EXPECT_EQ( expect, the_cookie_manager->get_cookie_by_host( "example.com" ) ); } class CookieManager_DeleteCookieByHost : public CookieManager_TestBase {}; TEST_F(CookieManager_DeleteCookieByHost, delete_toplevel) { the_cookie_manager->feed( "hello.world.test", "foo=bar; domain=.world.test" ); the_cookie_manager->feed( "hello.world.test", "baz=qux;" ); // トップレベルのドメインまで削除される the_cookie_manager->delete_cookie_by_host( "hello.world.test" ); const std::string expect = ""; EXPECT_EQ( expect, the_cookie_manager->get_cookie_by_host( "hello.world.test" ) ); } } // namespace jdim-0.7.0/test/gtest_jdlib_jdiconv.cpp000066400000000000000000000110611417047150700201370ustar00rootroot00000000000000// License: GPL2 #include "jdlib/jdiconv.h" #include "gtest/gtest.h" #include namespace { // エンコーディング変換は無数の組み合わせがあるためテストケースを網羅できない // JDim側で特別な処理をするパターンについてテストする class Iconv_ToAsciiFromUtf8 : public ::testing::Test {}; TEST_F(Iconv_ToAsciiFromUtf8, empty) { char input[] = ""; int size_out; JDLIB::Iconv icv( "ASCII", "UTF-8" ); const char* result = icv.convert( input, std::strlen(input), size_out ); EXPECT_STREQ( "", result ); EXPECT_EQ( 0, size_out ); } TEST_F(Iconv_ToAsciiFromUtf8, helloworld) { char input[] = "hello world!\n"; int size_out; JDLIB::Iconv icv( "ASCII", "UTF-8" ); const char* result = icv.convert( input, std::strlen(input), size_out ); EXPECT_STREQ( "hello world!\n", result ); EXPECT_EQ( 13, size_out ); } TEST_F(Iconv_ToAsciiFromUtf8, hiragana) { char input[] = "あいうえお"; int size_out; JDLIB::Iconv icv( "ASCII", "UTF-8" ); const char* result = icv.convert( input, std::strlen(input), size_out ); EXPECT_STREQ( "あいうえお", result ); EXPECT_EQ( 40, size_out ); } TEST_F(Iconv_ToAsciiFromUtf8, subdivision_flag) { // :england: JDLIB::Iconv::convert()のコメントを参照 char input[] = "\U0001F3F4\U000E0067\U000E0062\U000E0065\U000E006E\U000E0067\U000E007F"; int size_out; JDLIB::Iconv icv( "ASCII", "UTF-8" ); const char* result = icv.convert( input, std::strlen(input), size_out ); EXPECT_STREQ( "🏴󠁧󠁢󠁥󠁮󠁧󠁿", result ); EXPECT_EQ( 63, size_out ); } class Iconv_ToUtf8FromMs932 : public ::testing::Test {}; TEST_F(Iconv_ToUtf8FromMs932, empty) { char input[] = ""; int size_out; JDLIB::Iconv icv( "UTF-8", "MS932" ); const char* result = icv.convert( input, std::strlen(input), size_out ); EXPECT_STREQ( "", result ); EXPECT_EQ( 0, size_out ); } TEST_F(Iconv_ToUtf8FromMs932, helloworld) { char input[] = "hello world!\n"; int size_out; JDLIB::Iconv icv( "UTF-8", "MS932" ); const char* result = icv.convert( input, std::strlen(input), size_out ); EXPECT_STREQ( "hello world!\n", result ); EXPECT_EQ( 13, size_out ); } TEST_F(Iconv_ToUtf8FromMs932, hiragana) { char input[] = "\x82\xA0\x82\xA2\x82\xA4\x82\xA6\x82\xA8"; int size_out; JDLIB::Iconv icv( "UTF-8", "MS932" ); const char* result = icv.convert( input, std::strlen(input), size_out ); EXPECT_STREQ( "あいうえお", result ); EXPECT_EQ( 15, size_out ); } TEST_F(Iconv_ToUtf8FromMs932, hex_a0) { char input[] = "hello\xa0world!\n"; int size_out; JDLIB::Iconv icv( "UTF-8", "MS932" ); const char* result = icv.convert( input, std::strlen(input), size_out ); EXPECT_STREQ( "hello world!\n", result ); EXPECT_EQ( 13, size_out ); } TEST_F(Iconv_ToUtf8FromMs932, mojibake_fix_inequality_sign_pattern1) { // DATのデータ区切り <> が文字化けするとスレが壊れるため変換を修正する // エンコーディングがMS932のスレにUTF-8で書き込み文字化けした場合をテスト char input[] = "<>test テスト<>"; int size_out; JDLIB::Iconv icv( "UTF-8", "MS932" ); const char* result = icv.convert( input, std::strlen(input), size_out ); EXPECT_STREQ( "<>test \xE7\xB9\x9D\xE2\x96\xA1\xE3\x81\x9B\xE7\xB9\x9D?<>", result ); EXPECT_EQ( 22, size_out ); } TEST_F(Iconv_ToUtf8FromMs932, mojibake_fix_inequality_sign_pattern2) { // DATのデータ区切り <> が文字化けするとスレが壊れるため変換を修正する // エンコーディングがMS932のスレにUTF-8で書き込み文字化けした場合をテスト char input[] = "<> test テスト <>"; int size_out; JDLIB::Iconv icv( "UTF-8", "MS932" ); const char* result = icv.convert( input, std::strlen(input), size_out ); EXPECT_STREQ( "<> test \xE7\xB9\x9D\xE2\x96\xA1\xE3\x81\x9B\xE7\xB9\x9D\xE2\x96\xA1<>", result ); EXPECT_EQ( 25, size_out ); } TEST_F(Iconv_ToUtf8FromMs932, mapping_error) { // MS932の符号として不正な2バイトコードは白四角(\x81\A0 == U+25A1)として処理する // テストは網羅してない char input[] = "\x81\xAD\x82\x40\x88\x90\x98\x90"; int size_out; JDLIB::Iconv icv( "UTF-8", "MS932" ); const char* result = icv.convert( input, std::strlen(input), size_out ); EXPECT_STREQ( "\u25A1\u25A1\u25A1\u25A1", result ); EXPECT_EQ( 12, size_out ); } } // namespace jdim-0.7.0/test/gtest_jdlib_misctime.cpp000066400000000000000000000114021417047150700203140ustar00rootroot00000000000000// License: GPL-2.0 #include "jdlib/misctime.h" #include "gtest/gtest.h" #include #include // tzset namespace { // NOTE: time_tのエポックはUnix epoch (1970-01-01T00:00:00 UTC)を想定している class MISC_DateToTimeTest : public ::testing::Test {}; TEST_F(MISC_DateToTimeTest, empty_input) { const std::time_t result = MISC::datetotime( "" ); EXPECT_EQ( result, 0 ); } TEST_F(MISC_DateToTimeTest, invalid_format) { const std::time_t result = MISC::datetotime( "hello world" ); EXPECT_EQ( result, 0 ); } TEST_F(MISC_DateToTimeTest, unix_epoch) { const std::time_t result = MISC::datetotime( "Thu, 01 Jan 1970 00:00:00 GMT" ); EXPECT_EQ( result, 0 ); } TEST_F(MISC_DateToTimeTest, non_gmt_wont_be_parsed) { const std::time_t result = MISC::datetotime( "Wed, 01 Jan 2020 12:34:56 JST" ); EXPECT_EQ( result, 0 ); } TEST_F(MISC_DateToTimeTest, iso8601_wont_be_parsed) { const std::time_t result = MISC::datetotime( "2006-01-27T03:48:59Z" ); EXPECT_EQ( result, 0 ); } TEST_F(MISC_DateToTimeTest, one_hundred_million) { const std::time_t result = MISC::datetotime( "Sat, 03 Mar 1973 09:46:40 GMT" ); EXPECT_EQ( result, 100'000'000 ); } TEST_F(MISC_DateToTimeTest, wrong_day_will_be_parsed) { // Although correct day of 2001-09-09 is Sunday, parsed time is right. const std::time_t result = MISC::datetotime( "Mon, 09 Sep 2001 01:46:40 GMT" ); EXPECT_EQ( result, 1'000'000'000 ); } } // namespace #ifdef _POSIX_C_SOURCE namespace { static_assert( sizeof(time_t) >= 4, "MISC_TimetToStrTest requires sizeof(time_t) >= 4." ); // NOTE: 関数の戻り値はUTF-8文字列であるのが前提 class MISC_TimetToStrTest : public ::testing::Test { std::string m_save_tz; void SetUp() override { m_save_tz = Glib::getenv( "TZ" ); Glib::setenv( "TZ", "GMT" ); ::tzset(); } void TearDown() override { Glib::setenv( "TZ", m_save_tz ); ::tzset(); } }; TEST_F(MISC_TimetToStrTest, time_normal) { EXPECT_EQ( "1970/01/01 00:00", MISC::timettostr( 0, MISC::TIME_NORMAL ) ); EXPECT_EQ( "2000/10/02 15:20", MISC::timettostr( 970500000, MISC::TIME_NORMAL ) ); EXPECT_EQ( "2009/02/13 23:31", MISC::timettostr( 1234567890, MISC::TIME_NORMAL ) ); EXPECT_EQ( "2038/01/19 03:14", MISC::timettostr( 2147483647, MISC::TIME_NORMAL ) ); } TEST_F(MISC_TimetToStrTest, time_no_year) { EXPECT_EQ( "01/01 00:00", MISC::timettostr( 0, MISC::TIME_NO_YEAR ) ); EXPECT_EQ( "10/02 15:20", MISC::timettostr( 970500000, MISC::TIME_NO_YEAR ) ); EXPECT_EQ( "02/13 23:31", MISC::timettostr( 1234567890, MISC::TIME_NO_YEAR ) ); EXPECT_EQ( "01/19 03:14", MISC::timettostr( 2147483647, MISC::TIME_NO_YEAR ) ); } TEST_F(MISC_TimetToStrTest, time_week) { EXPECT_EQ( "1970/01/01(木) 00:00:00", MISC::timettostr( 0, MISC::TIME_WEEK ) ); EXPECT_EQ( "2000/10/02(月) 15:20:00", MISC::timettostr( 970500000, MISC::TIME_WEEK ) ); EXPECT_EQ( "2000/10/04(水) 22:53:20", MISC::timettostr( 970700000, MISC::TIME_WEEK ) ); EXPECT_EQ( "2000/10/07(土) 06:26:40", MISC::timettostr( 970900000, MISC::TIME_WEEK ) ); EXPECT_EQ( "2000/10/08(日) 10:13:20", MISC::timettostr( 971000000, MISC::TIME_WEEK ) ); EXPECT_EQ( "2009/02/13(金) 23:31:30", MISC::timettostr( 1234567890, MISC::TIME_WEEK ) ); EXPECT_EQ( "2038/01/19(火) 03:14:07", MISC::timettostr( 2147483647, MISC::TIME_WEEK ) ); } TEST_F(MISC_TimetToStrTest, time_passed) { // NOTE: テストケースは60秒以内に関数の実行が完了すること&算術オーバフローしないことが前提 // 〜秒後はタイミングがシビアなので数値はテストしない const std::string sec = MISC::timettostr( time( nullptr ), MISC::TIME_PASSED ); // 現時刻 EXPECT_EQ( " 秒前", sec.substr( sec.size() - 7, std::string::npos ) ); EXPECT_EQ( "1 分前", MISC::timettostr( time( nullptr ) - 60, MISC::TIME_PASSED ) ); // 60秒前 EXPECT_EQ( "1 時間前", MISC::timettostr( time( nullptr ) - 3600, MISC::TIME_PASSED ) ); // 60分前 EXPECT_EQ( "1 日前", MISC::timettostr( time( nullptr ) - 86400, MISC::TIME_PASSED ) ); // 24時間前 EXPECT_EQ( "1 年前", MISC::timettostr( time( nullptr ) - 31622400, MISC::TIME_PASSED ) ); // 366日前 EXPECT_EQ( "未来", MISC::timettostr( time( nullptr ) + 60, MISC::TIME_PASSED ) ); // 60秒後 } TEST_F(MISC_TimetToStrTest, time_second) { EXPECT_EQ( "1970/01/01 00:00:00", MISC::timettostr( 0, MISC::TIME_SECOND ) ); EXPECT_EQ( "2000/10/02 15:20:00", MISC::timettostr( 970500000, MISC::TIME_SECOND ) ); EXPECT_EQ( "2009/02/13 23:31:30", MISC::timettostr( 1234567890, MISC::TIME_SECOND ) ); EXPECT_EQ( "2038/01/19 03:14:07", MISC::timettostr( 2147483647, MISC::TIME_SECOND ) ); } } // namespace #endif // _POSIX_C_SOURCE jdim-0.7.0/test/gtest_jdlib_misctrip.cpp000066400000000000000000000046361417047150700203470ustar00rootroot00000000000000// License: GPL-2.0 // Copyright (C) 2019, JDimproved project #include "jdlib/misctrip.h" #include "gtest/gtest.h" namespace { class GetTripTest : public ::testing::Test {}; // 実際に運用されているShift_JISエンコーディングでテスト // ASCII以外の文字はShift_JISに変換されるのでバイト数に注意すること // 戻り値のテストデータはWikipediaのperlスクリプトを使って計算した // ヘルパー関数 inline static std::string get_trip_sjis( std::string u8key ) { return MISC::get_trip( u8key, "Shift_JIS" ); } TEST_F(GetTripTest, trip8_sjis_empty) { EXPECT_EQ( "", get_trip_sjis( u8"" ) ); } TEST_F(GetTripTest, trip8_sjis_A) { EXPECT_EQ( "hRJ9Ya./t.", get_trip_sjis( u8"A" ) ); } TEST_F(GetTripTest, trip8_sjis_hello7) { EXPECT_EQ( "/wfpxFEFeQ", get_trip_sjis( u8"hellowo" ) ); } TEST_F(GetTripTest, trip8_sjis_hello8) { EXPECT_EQ( "d75etXAowg", get_trip_sjis( u8"hellowor" ) ); } TEST_F(GetTripTest, trip8_sjis_hello11) { // 11バイトまでは従来の方式(9〜11バイト目は無視される) EXPECT_EQ( "d75etXAowg", get_trip_sjis( u8"helloworld!" ) ); } TEST_F(GetTripTest, trip8_sjis_yojijukugo) { EXPECT_EQ( "sX.SlNvMe.", get_trip_sjis( u8"四字熟語" ) ); } TEST_F(GetTripTest, trip8_sjis_hex_less_12) { // シャープで始まる16進数キーでも12バイト未満なら従来の方式 EXPECT_EQ( "RTDIJZhD3g", get_trip_sjis( u8"#0123456789" ) ); } TEST_F(GetTripTest, trip8_sjis_dollar_less_12) { // ドル記号で始まるキーでも12バイト未満なら従来の方式 EXPECT_EQ( "46g6cHndYk", get_trip_sjis( u8"$0123456789" ) ); } TEST_F(GetTripTest, trip12_sjis_hello12) { // 12バイト以上は新方式 EXPECT_EQ( "xwumaTFfu1OK", get_trip_sjis( u8"helloworld!?" ) ); } TEST_F(GetTripTest, trip12_sjis_jdimprovedproject) { // 新方式の+は.に変換される EXPECT_EQ( "Y7G9gYfXrr6.", get_trip_sjis( u8"jdimprovedproject" ) ); } TEST_F(GetTripTest, trip12_sjis_hex) { // 新方式 16進数のキー EXPECT_EQ( "ClNHFHdYIw", get_trip_sjis( u8"#0123456789abcdef" ) ); } TEST_F(GetTripTest, trip12_sjis_non_hex) { // 念の為トライグラフ対策としてエスケープ EXPECT_EQ( "\?\?\?", get_trip_sjis( u8"#あいうえおか" ) ); } TEST_F(GetTripTest, trip12_sjis_dollar) { EXPECT_EQ( "\?\?\?", get_trip_sjis( u8"$将来の拡張用" ) ); } } // namespace jdim-0.7.0/test/gtest_jdlib_miscutil.cpp000066400000000000000000000770351417047150700203510ustar00rootroot00000000000000// License: GPL2 #include "jdlib/miscutil.h" #include "gtest/gtest.h" namespace { class SplitLineTest : public ::testing::Test {}; TEST_F(SplitLineTest, split_empty) { std::list< std::string > expect = {}; EXPECT_EQ( expect, MISC::split_line( u8"" ) ); } TEST_F(SplitLineTest, split_U_0020) { std::list< std::string > expect = {}; EXPECT_EQ( expect, MISC::split_line( u8" " ) ); expect.assign( { u8"the", u8"quick", u8"brown", u8"fox" } ); EXPECT_EQ( expect, MISC::split_line( u8" the quick brown fox " ) ); } TEST_F(SplitLineTest, split_U_3000) { std::list< std::string > expect = {}; EXPECT_EQ( expect, MISC::split_line( u8"\u3000 \u3000 " ) ); expect.assign( { u8"the", u8"quick", u8"brown", u8"fox" } ); EXPECT_EQ( expect, MISC::split_line( u8"\u3000the\u3000quick \u3000brown\u3000 \u3000fox\u3000 " ) ); } TEST_F(SplitLineTest, split_doublequote_U_0020) { std::list< std::string > expect = {}; EXPECT_EQ( expect, MISC::split_line( u8" \"\" " ) ); expect.assign( { u8"the quick", u8" ", u8" brown fox " } ); EXPECT_EQ( expect, MISC::split_line( u8" \"the quick\" \" \" \" brown fox \" " ) ); } TEST_F(SplitLineTest, split_doublequote_U_3000) { std::list< std::string > expect = { u8"the\u3000quick", u8"\u3000", u8"\u3000brown \u3000fox\u3000" }; EXPECT_EQ( expect, MISC::split_line( u8"\u3000\"the\u3000quick\" \"\u3000\" \"\u3000brown \u3000fox\u3000\"" ) ); } class ConcatWithSuffixTest : public ::testing::Test {}; TEST_F(ConcatWithSuffixTest, empty_list) { std::list list_in; EXPECT_EQ( "", MISC::concat_with_suffix( list_in, '!' ) ); } TEST_F(ConcatWithSuffixTest, one_element) { std::list list_in = { "hello" }; EXPECT_EQ( "hello!", MISC::concat_with_suffix( list_in, '!' ) ); } TEST_F(ConcatWithSuffixTest, hello_world) { std::list list_in = { "hello", "world" }; EXPECT_EQ( "hello!world!", MISC::concat_with_suffix( list_in, '!' ) ); } TEST_F(ConcatWithSuffixTest, ignore_empty_string) { std::list list_in = { "", "hello", "", "", "world", "" }; EXPECT_EQ( "hello!world!", MISC::concat_with_suffix( list_in, '!' ) ); } class RemoveSpaceTest : public ::testing::Test {}; TEST_F(RemoveSpaceTest, remove_empty) { std::string expect = {}; EXPECT_EQ( expect, MISC::remove_space( u8"" ) ); } TEST_F(RemoveSpaceTest, remove_U_0020) { std::string expect = {}; EXPECT_EQ( expect, MISC::remove_space( u8" " ) ); expect.assign( u8"the quick brown fox" ); EXPECT_EQ( expect, MISC::remove_space( u8" the quick brown fox " ) ); } TEST_F(RemoveSpaceTest, remove_U_3000) { std::string expect = {}; EXPECT_EQ( expect, MISC::remove_space( u8"\u3000 \u3000 " ) ); expect.assign( u8"the quick\u3000brown\u3000 fox" ); EXPECT_EQ( expect, MISC::remove_space( u8"\u3000the quick\u3000brown\u3000 fox\u3000 " ) ); } TEST_F(RemoveSpaceTest, remove_doublequote) { std::string expect = u8"\"\""; EXPECT_EQ( expect, MISC::remove_space( u8"\u3000 \"\"\u3000 " ) ); } class IsUrlSchemeTest : public ::testing::Test {}; TEST_F(IsUrlSchemeTest, url_none) { EXPECT_EQ( MISC::SCHEME_NONE, MISC::is_url_scheme( "foo" ) ); EXPECT_EQ( MISC::SCHEME_NONE, MISC::is_url_scheme( "http:/" ) ); EXPECT_EQ( MISC::SCHEME_NONE, MISC::is_url_scheme( "ttp:/" ) ); EXPECT_EQ( MISC::SCHEME_NONE, MISC::is_url_scheme( "tp:/" ) ); EXPECT_EQ( MISC::SCHEME_NONE, MISC::is_url_scheme( "ftp:/" ) ); // "sssp:/" はバッファオーバーランを起こす } TEST_F(IsUrlSchemeTest, url_http) { int length; EXPECT_EQ( MISC::SCHEME_HTTP, MISC::is_url_scheme( "http://", &length ) ); EXPECT_EQ( 7, length ); EXPECT_EQ( MISC::SCHEME_HTTP, MISC::is_url_scheme( "http://foobar", &length ) ); EXPECT_EQ( 7, length ); } TEST_F(IsUrlSchemeTest, url_ttp) { int length; EXPECT_EQ( MISC::SCHEME_TTP, MISC::is_url_scheme( "ttp://", &length ) ); EXPECT_EQ( 6, length ); EXPECT_EQ( MISC::SCHEME_TTP, MISC::is_url_scheme( "ttp://foobar", &length ) ); EXPECT_EQ( 6, length ); } TEST_F(IsUrlSchemeTest, url_tp) { int length; EXPECT_EQ( MISC::SCHEME_TP, MISC::is_url_scheme( "tp://", &length ) ); EXPECT_EQ( 5, length ); EXPECT_EQ( MISC::SCHEME_TP, MISC::is_url_scheme( "tp://foobar", &length ) ); EXPECT_EQ( 5, length ); } TEST_F(IsUrlSchemeTest, url_ftp) { int length; EXPECT_EQ( MISC::SCHEME_FTP, MISC::is_url_scheme( "ftp://", &length ) ); EXPECT_EQ( 6, length ); EXPECT_EQ( MISC::SCHEME_FTP, MISC::is_url_scheme( "ftp://foobar", &length ) ); EXPECT_EQ( 6, length ); } TEST_F(IsUrlSchemeTest, url_sssp) { int length; EXPECT_EQ( MISC::SCHEME_HTTP, MISC::is_url_scheme( "sssp://", &length ) ); EXPECT_EQ( 7, length ); EXPECT_EQ( MISC::SCHEME_HTTP, MISC::is_url_scheme( "sssp://foobar", &length ) ); EXPECT_EQ( 7, length ); EXPECT_EQ( MISC::SCHEME_SSSP, MISC::is_url_scheme( "sssp://img.2ch", &length ) ); EXPECT_EQ( 7, length ); EXPECT_EQ( MISC::SCHEME_SSSP, MISC::is_url_scheme( "sssp://img.5ch", &length ) ); EXPECT_EQ( 7, length ); } class MISC_AscTest : public ::testing::Test {}; TEST_F(MISC_AscTest, empty_input) { std::string output; std::vector table; // 入力はヌル終端文字列 MISC::asc( u8"", output, table ); EXPECT_EQ( u8"", output ); EXPECT_EQ( 0, output.size() ); // 文字列の終端(ヌル文字)の位置が追加されるためtableのサイズが+1大きくなる EXPECT_EQ( 1, table.size() ); EXPECT_EQ( 0, table.at( 0 ) ); } TEST_F(MISC_AscTest, halfwidth_latin_capital_letter) { std::string output; std::vector table; MISC::asc( u8"THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG.", output, table ); EXPECT_EQ( u8"THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG.", output ); EXPECT_EQ( output.size(), table.size() - 1 ); for( int i = 0, size = table.size(); i < size; ++i ) { EXPECT_EQ( i, table.at( i ) ); } } TEST_F(MISC_AscTest, halfwidth_latin_small_letter) { std::string output; std::vector table; MISC::asc( u8"the quick brown fox jumps over the lazy dog.", output, table ); EXPECT_EQ( u8"the quick brown fox jumps over the lazy dog.", output ); EXPECT_EQ( output.size(), table.size() - 1 ); for( int i = 0, size = table.size(); i < size; ++i ) { EXPECT_EQ( i, table.at( i ) ); } } TEST_F(MISC_AscTest, halfwidth_digit_sign) { std::string output; std::vector table; MISC::asc( u8"1234567890+-*/", output, table ); EXPECT_EQ( u8"1234567890+-*/", output ); EXPECT_EQ( output.size(), table.size() - 1 ); for( int i = 0, size = table.size(); i < size; ++i ) { EXPECT_EQ( i, table.at( i ) ); } } TEST_F(MISC_AscTest, halfwidth_append_data) { std::string output = "123"; std::vector table = { 0, 1, 2 }; // アウトプット引数は初期化せずデータを追加する MISC::asc( u8"hello", output, table ); EXPECT_EQ( u8"123hello", output ); EXPECT_EQ( output.size(), table.size() - 1 ); const std::vector expected_table = { 0, 1, 2, 0, 1, 2, 3, 4, 5 }; EXPECT_EQ( expected_table, table ); } std::vector expected_table_fullwidth_quick_brown_fox() { // 全角英数字から半角英数字に変換したときの文字列の位置を保存しておくテーブルのテストデータ return { // TH E U+3000 Q U I C K U+3000 B R O W N U+3000 F O X U+3000 0, 3, 6, 9, 10, 11, 12, 15, 18, 21, 24, 27, 28, 29, 30, 33, 36, 39, 42, 45, 46, 47, 48, 51, 54, 57, 58, 59, // JU M P S U+3000 O V E R U+3000 T H E U+3000 L A Z Y 60, 63, 66, 69, 72, 75, 76, 77, 78, 81, 84, 87, 90, 91, 92, 93, 96, 99, 102, 103, 104, 105, 108, 111, 114, // U+3000 D O G U+FF0E U+0000 117, 118, 119, 120, 123, 126, 129, 130, 131, 132, }; } TEST_F(MISC_AscTest, fullwidth_latin_capital_letter) { std::string output; std::vector table; MISC::asc( u8"THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG.", output, table ); // 和字間隔(U+3000)は半角スペースに変換されない EXPECT_EQ( u8"THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG.", output ); EXPECT_EQ( output.size(), table.size() - 1 ); EXPECT_EQ( expected_table_fullwidth_quick_brown_fox(), table ); } TEST_F(MISC_AscTest, fullwidth_latin_small_letter) { std::string output; std::vector table; MISC::asc( u8"the quick brown fox jumps over the lazy dog.", output, table ); EXPECT_EQ( u8"the quick brown fox jumps over the lazy dog.", output ); EXPECT_EQ( output.size(), table.size() - 1 ); EXPECT_EQ( expected_table_fullwidth_quick_brown_fox(), table ); } TEST_F(MISC_AscTest, fullwidth_digit_sign) { std::string output; std::vector table; MISC::asc( u8"1234567890+−*/", output, table ); // 全角数字は半角に変換されるが、全角記号は半角に変換されない EXPECT_EQ( u8"1234567890+−*/", output ); EXPECT_EQ( output.size(), table.size() - 1 ); const std::vector expected_table = { 0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42 }; EXPECT_EQ( expected_table, table ); } TEST_F(MISC_AscTest, halfwidth_katakana_without_voiced_sound_mark) { std::string output; std::vector table; constexpr const char halfwidth[] { u8"\uFF61\uFF62\uFF63\uFF64\uFF65" u8"\uFF66" u8"\uFF67\uFF68\uFF69\uFF6A\uFF6B" u8"\uFF6C\uFF6D\uFF6E\uFF6F\uFF70" u8"\uFF71\uFF72\uFF73\uFF74\uFF75" u8"\uFF76\uFF77\uFF78\uFF79\uFF7A" u8"\uFF7B\uFF7C\uFF7D\uFF7E\uFF7F" u8"\uFF80\uFF81\uFF82\uFF83\uFF84" u8"\uFF85\uFF86\uFF87\uFF88\uFF89" u8"\uFF8A\uFF8B\uFF8C\uFF8D\uFF8E" u8"\uFF8F\uFF90\uFF91\uFF92\uFF93" u8"\uFF94\uFF95\uFF96" u8"\uFF97\uFF98\uFF99\uFF9A\uFF9B" u8"\uFF9C\uFF9D" }; constexpr const char fullwidth[] { u8"。「」、・" u8"ヲ" u8"ァィゥェォ" u8"ャュョッー" u8"アイウエオ" u8"カキクケコ" u8"サシスセソ" u8"タチツテト" u8"ナニヌネノ" u8"ハヒフヘホ" u8"マミムメモ" u8"ヤユヨ" u8"ラリルレロ" u8"ワン" }; // 半角片仮名から全角片仮名へ一対一の変換 MISC::asc( halfwidth, output, table ); EXPECT_EQ( fullwidth, output ); EXPECT_EQ( output.size(), table.size() - 1 ); for( int i = 0, size = table.size(); i < size; ++i ) { EXPECT_EQ( i, table.at( i ) ); } } TEST_F(MISC_AscTest, halfwidth_katakana_only_voiced_sound_mark) { std::string output; std::vector table; // 半角の濁点と半濁点は単独では全角に変換されない MISC::asc( u8"\uFF9E\uFF9F", output, table ); EXPECT_EQ( u8"\uFF9E\uFF9F", output ); EXPECT_EQ( output.size(), table.size() - 1 ); for( int i = 0, size = table.size(); i < size; ++i ) { EXPECT_EQ( i, table.at( i ) ); } } TEST_F(MISC_AscTest, halfwidth_katakana_combining_voiced_sound_mark_to_precomposed) { std::string output; std::vector table; constexpr const char halfwidth[] = { u8"\uFF76\uFF9E\uFF77\uFF9E\uFF78\uFF9E\uFF79\uFF9E\uFF7A\uFF9E" u8"\uFF7B\uFF9E\uFF7C\uFF9E\uFF7D\uFF9E\uFF7E\uFF9E\uFF7F\uFF9E" u8"\uFF80\uFF9E\uFF81\uFF9E\uFF82\uFF9E\uFF83\uFF9E\uFF84\uFF9E" u8"\uFF8A\uFF9E\uFF8B\uFF9E\uFF8C\uFF9E\uFF8D\uFF9E\uFF8E\uFF9E" u8"\uFF8A\uFF9F\uFF8B\uFF9F\uFF8C\uFF9F\uFF8D\uFF9F\uFF8E\uFF9F" }; constexpr const char fullwidth[] { u8"\u30AC\u30AE\u30B0\u30B2\u30B4" // ガギグゲゴ u8"\u30B6\u30B8\u30BA\u30BC\u30BE" // ザジズゼゾ u8"\u30C0\u30C2\u30C5\u30C7\u30C9" // ダヂヅデド u8"\u30D0\u30D3\u30D6\u30D9\u30DC" // バビブベボ u8"\u30D1\u30D4\u30D7\u30DA\u30DD" // パピプペポ }; // 合成済み文字が存在する(半)濁点付き半角片仮名は全角へ変換される MISC::asc( halfwidth, output, table ); EXPECT_EQ( fullwidth, output ); EXPECT_EQ( output.size(), table.size() - 1 ); const std::vector expected_table = { 0, 1, 2, 6, 7, 8, 12, 13, 14, 18, 19, 20, 24, 25, 26, 30, 31, 32, 36, 37, 38, 42, 43, 44, 48, 49, 50, 54, 55, 56, 60, 61, 62, 66, 67, 68, 72, 73, 74, 78, 79, 80, 84, 85, 86, 90, 91, 92, 96, 97, 98, 102, 103, 104, 108, 109, 110, 114, 115, 116, 120, 121, 122, 126, 127, 128, 132, 133, 134, 138, 139, 140, 144, 145, 146, 150, }; EXPECT_EQ( expected_table, table ); } TEST_F(MISC_AscTest, halfwidth_katakana_combining_voiced_sound_mark_wagyo) { std::string output; std::vector table; // 濁点付き半角片仮名のウは全角の合成済み文字に変換される:ヴ // ワ行の濁点付き半角片仮名は全角に変換されない(変換表が未実装) : ヷヺ MISC::asc( u8"\uFF73\uFF9E\uFF9C\uFF9E\uFF66\uFF9E", output, table ); EXPECT_EQ( u8"\u30F4\uFF9C\uFF9E\uFF66\uFF9E", output ); EXPECT_EQ( output.size(), table.size() - 1 ); const std::vector expected_table = { 0, 1, 2, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 }; EXPECT_EQ( expected_table, table ); } TEST_F(MISC_AscTest, halfwidth_katakana_combining_voiced_sound_mark_through) { std::string output; std::vector table; // 合成済み文字が存在しない(半)濁点付き半角片仮名は全角に変換されない : カ゚キ゚ク゚ケ゚コ゚ // NOTE: 組み合わせが多いのでテストは網羅していない MISC::asc( u8"\uFF76\uFF9F\uFF77\uFF9F\uFF78\uFF9F\uFF79\uFF9F\uFF7A\uFF9F", output, table ); EXPECT_EQ( u8"\uFF76\uFF9F\uFF77\uFF9F\uFF78\uFF9F\uFF79\uFF9F\uFF7A\uFF9F", output ); EXPECT_EQ( output.size(), table.size() - 1 ); for( int i = 0, size = table.size(); i < size; ++i ) { EXPECT_EQ( i, table.at( i ) ); } } TEST_F(MISC_AscTest, fullwidth_katakana_without_voiced_sound_mark) { std::string output; std::vector table; constexpr const char fullwidth[] { u8"\u30A1\u30A2\u30A3\u30A4\u30A5\u30A6\u30A7\u30A8\u30A9\u30AA" // ァ - オ u8"\u30AB\u30AD\u30AF\u30B1\u30B3\u30B5\u30B7\u30B9\u30BB\u30BD" // カ - ソ u8"\u30BF\u30C1\u30C3\u30C4\u30C6\u30C8\u30CA\u30CB\u30CC\u30CD\u30CE" // タ - ノ u8"\u30CF\u30D2\u30D5\u30D8\u30DB\u30DE\u30DF\u30E0\u30E1\u30E2" // ハ - モ u8"\u30E3\u30E4\u30E5\u30E6\u30E7\u30E8\u30E9\u30EA\u30EB\u30EC\u30ED" // ャ - ロ u8"\u30EE\u30EF\u30F0\u30F1\u30F2\u30F3\u30F5\u30F6" // ヮ - ヶ }; // (半)濁点の付いていない全角片仮名はそのまま MISC::asc( fullwidth, output, table ); EXPECT_EQ( fullwidth, output ); EXPECT_EQ( output.size(), table.size() - 1 ); for( int i = 0, size = table.size(); i < size; ++i ) { EXPECT_EQ( i, table.at( i ) ); } } TEST_F(MISC_AscTest, fullwidth_katakana_precomposed_voiced_sound_mark) { std::string output; std::vector table; constexpr const char fullwidth[] { u8"\u30AC\u30AE\u30B0\u30B2\u30B4\u30B6\u30B8\u30BA\u30BC\u30BE" // ガ - ゾ u8"\u30C0\u30C2\u30C5\u30C7\u30C9\u30D0\u30D3\u30D6\u30D9\u30DC" // ダ - ボ u8"\u30F4\u30F7\u30F8\u30F9\u30FA" // ヴ - ヺ u8"\u30D1\u30D4\u30D7\u30DA0x30DD" // パ - ポ }; // 合成済み文字の(半)濁点付き全角片仮名はそのまま MISC::asc( fullwidth, output, table ); EXPECT_EQ( fullwidth, output ); EXPECT_EQ( output.size(), table.size() - 1 ); for( int i = 0, size = table.size(); i < size; ++i ) { EXPECT_EQ( i, table.at( i ) ); } } TEST_F(MISC_AscTest, fullwidth_katakana_combining_voiced_sound_mark) { std::string output; std::vector table; constexpr const char fullwidth[] { u8"\u30AB\u3099\u30AD\u3099\u30AF\u3099\u30B1\u3099\u30B3\u3099" // ガギグゲゴ u8"\u30AB\u309A\u30AD\u309A\u30AF\u309A\u30B1\u309A\u30B3\u309A" // カ゚キ゚ク゚ケ゚コ゚ }; // 合成済み文字の有無に関係なく(半)濁点の結合文字が付いている全角片仮名は変換されない // NOTE: 組み合わせが多いのでテストは網羅していない MISC::asc( fullwidth, output, table ); EXPECT_EQ( fullwidth, output ); EXPECT_EQ( output.size(), table.size() - 1 ); for( int i = 0, size = table.size(); i < size; ++i ) { EXPECT_EQ( i, table.at( i ) ); } } TEST_F(MISC_AscTest, fullwidth_hiragana_without_voiced_sound_mark) { std::string output; std::vector table; constexpr const char fullwidth[] { u8"\u3041\u3042\u3043\u3044\u3045\u3046\u3047\u3048\u3049\u304A" // ぁ - お u8"\u304B\u304D\u304F\u3051\u3053\u3055\u3057\u3059\u305B\u305D" // か - そ u8"\u305F\u3061\u3063\u3064\u3066\u3068\u306A\u306B\u306C\u306D\u306E" // た - の u8"\u306F\u3072\u3075\u3078\u307B\u307E\u307F\u3080\u3081\u3082" // は - も u8"\u3083\u3084\u3085\u3086\u3087\u3088\u3089\u308A\u308B\u308C\u308D" // ゃ - ろ u8"\u308E\u308F\u3090\u3091\u3092\u3093\u3095\u3096" // ゎ - ゖ }; // (半)濁点の付いていない全角平仮名はそのまま MISC::asc( fullwidth, output, table ); EXPECT_EQ( fullwidth, output ); EXPECT_EQ( output.size(), table.size() - 1 ); for( int i = 0, size = table.size(); i < size; ++i ) { EXPECT_EQ( i, table.at( i ) ); } } TEST_F(MISC_AscTest, fullwidth_hiragana_precomposed_voiced_sound_mark) { std::string output; std::vector table; constexpr const char fullwidth[] { u8"\u304C\u304E\u3050\u3052\u3054\u3056\u3058\u305A\u305C\u305E" // が - ぞ u8"\u3060\u3062\u3065\u3067\u3069\u3070\u3073\u3076\u3079\u307C" // だ - ぼ u8"\u3094" // ゔ u8"\u3071\u3074\u3077\u307A0x307D" // ぱ - ぽ }; // 合成済み文字の(半)濁点付き全角平仮名はそのまま MISC::asc( fullwidth, output, table ); EXPECT_EQ( fullwidth, output ); EXPECT_EQ( output.size(), table.size() - 1 ); for( int i = 0, size = table.size(); i < size; ++i ) { EXPECT_EQ( i, table.at( i ) ); } } TEST_F(MISC_AscTest, fullwidth_hiragana_combining_voiced_sound_mark) { std::string output; std::vector table; constexpr const char fullwidth[] { u8"\u304B\u3099\u304D\u3099\u304F\u3099\u3051\u3099\u3053\u3099" // がぎぐげご u8"\u304B\u309A\u304D\u309A\u304F\u309A\u3051\u309A\u3053\u309A" // か゚き゚く゚け゚こ゚ }; // 合成済み文字の有無に関係なく(半)濁点の結合文字が付いている全角平仮名は変換されない // NOTE: 組み合わせが多いのでテストは網羅していない MISC::asc( fullwidth, output, table ); EXPECT_EQ( fullwidth, output ); EXPECT_EQ( output.size(), table.size() - 1 ); for( int i = 0, size = table.size(); i < size; ++i ) { EXPECT_EQ( i, table.at( i ) ); } } class MISC_StartsWith : public ::testing::Test {}; TEST_F(MISC_StartsWith, null_terminated_string_with_zero_length) { EXPECT_TRUE( MISC::starts_with( "", "" ) ); EXPECT_TRUE( MISC::starts_with( "helloworld", "" ) ); EXPECT_FALSE( MISC::starts_with( "", "helloworld" ) ); } TEST_F(MISC_StartsWith, null_terminated_string) { EXPECT_TRUE( MISC::starts_with( "hello", "hello" ) ); EXPECT_TRUE( MISC::starts_with( "helloworld", "hello" ) ); EXPECT_FALSE( MISC::starts_with( "hello", "helloworld" ) ); } class MISC_ParseHtmlFormData : public ::testing::Test {}; TEST_F(MISC_ParseHtmlFormData, empty_html) { auto result = MISC::parse_html_form_data( std::string{} ); EXPECT_TRUE( result.empty() ); } TEST_F(MISC_ParseHtmlFormData, hidden_datum) { auto result = MISC::parse_html_form_data( "" ); decltype(result) expect{ { "FOO", "100" } }; EXPECT_EQ( result, expect ); } TEST_F(MISC_ParseHtmlFormData, hidden_datum_uppercase) { auto result = MISC::parse_html_form_data( "" ); decltype(result) expect{ { "FOO", "100" } }; EXPECT_EQ( result, expect ); } TEST_F(MISC_ParseHtmlFormData, hidden_datum_with_double_quotes) { auto result = MISC::parse_html_form_data( R"()" ); decltype(result) expect{ { "FOO", "100" } }; EXPECT_EQ( result, expect ); } TEST_F(MISC_ParseHtmlFormData, submit_datum) { auto result = MISC::parse_html_form_data( "" ); decltype(result) expect{ { "submit", "書き込む" } }; EXPECT_EQ( result, expect ); } TEST_F(MISC_ParseHtmlFormData, submit_datum_with_double_quotes) { auto result = MISC::parse_html_form_data( R"()" ); decltype(result) expect{ { "submit", "書き込む" } }; EXPECT_EQ( result, expect ); } TEST_F(MISC_ParseHtmlFormData, textarea_datum) { auto result = MISC::parse_html_form_data( "" ); decltype(result) expect{ { "MESSAGE", "あかさたな" } }; EXPECT_EQ( result, expect ); } TEST_F(MISC_ParseHtmlFormData, textarea_have_tag) { auto result = MISC::parse_html_form_data( "" ); decltype(result) expect{ { "MESSAGE", "あか
    さたな" } }; EXPECT_EQ( result, expect ); } TEST_F(MISC_ParseHtmlFormData, textarea_datum_uppercase) { auto result = MISC::parse_html_form_data( "" ); decltype(result) expect{ { "MESSAGE", "あかさたな" } }; EXPECT_EQ( result, expect ); } TEST_F(MISC_ParseHtmlFormData, textarea_datum_with_double_quotes) { auto result = MISC::parse_html_form_data( R"()" ); decltype(result) expect{ { "MESSAGE", "あかさたな" } }; EXPECT_EQ( result, expect ); } TEST_F(MISC_ParseHtmlFormData, multi_data_1) { auto result = MISC::parse_html_form_data( R"()" "" R"()" ); decltype(result) expect{ { "MESSAGE", "あかさたな" }, { "submit", "書き込む" }, { "FOO", "100" } }; EXPECT_EQ( result, expect ); } TEST_F(MISC_ParseHtmlFormData, multi_data_2) { auto result = MISC::parse_html_form_data( "" R"()" R"()" ); decltype(result) expect{ { "submit", "書き込む" }, { "FOO", "100" }, { "MESSAGE", "あかさたな" } }; EXPECT_EQ( result, expect ); } TEST_F(MISC_ParseHtmlFormData, multi_data_3) { auto result = MISC::parse_html_form_data( R"()" R"()" "" ); decltype(result) expect{ { "FOO", "100" }, { "MESSAGE", "あかさたな" }, { "submit", "書き込む" } }; EXPECT_EQ( result, expect ); } TEST_F(MISC_ParseHtmlFormData, hidden_empty_name_by_double_quotes) { auto result = MISC::parse_html_form_data( R"()" ); decltype(result) expect{ { std::string{}, "100" } }; EXPECT_EQ( result, expect ); } TEST_F(MISC_ParseHtmlFormData, hidden_empty_value_by_double_quotes) { auto result = MISC::parse_html_form_data( R"()" ); decltype(result) expect{ { "FOO", std::string{} } }; EXPECT_EQ( result, expect ); } TEST_F(MISC_ParseHtmlFormData, submit_empty_name_by_double_quotes) { auto result = MISC::parse_html_form_data( R"()" ); decltype(result) expect{ { std::string{}, "書き込む" } }; EXPECT_EQ( result, expect ); } TEST_F(MISC_ParseHtmlFormData, submit_empty_value_by_double_quotes) { auto result = MISC::parse_html_form_data( R"()" ); decltype(result) expect{ { "submit", std::string{} } }; EXPECT_EQ( result, expect ); } TEST_F(MISC_ParseHtmlFormData, textarea_empty_name_by_double_quotes) { auto result = MISC::parse_html_form_data( R"()" ); decltype(result) expect{ { std::string{}, "あかさたな" } }; EXPECT_EQ( result, expect ); } TEST_F(MISC_ParseHtmlFormData, textarea_empty_value_by_double_quotes) { auto result = MISC::parse_html_form_data( R"()" ); decltype(result) expect{ { "MESSAGE", std::string{} } }; EXPECT_EQ( result, expect ); } class MISC_ParseHtmlFormActionTest : public ::testing::Test {}; TEST_F(MISC_ParseHtmlFormActionTest, empty_html) { std::string result = MISC::parse_html_form_action( std::string{} ); EXPECT_EQ( result, std::string{} ); } TEST_F(MISC_ParseHtmlFormActionTest, unquote_post) { const std::string html = R"(")"; std::string result = MISC::parse_html_form_action( html ); EXPECT_EQ( result, "/test/bbs.cgi" ); } TEST_F(MISC_ParseHtmlFormActionTest, quote_post) { const std::string html = R"(")"; std::string result = MISC::parse_html_form_action( html ); EXPECT_EQ( result, "/test/bbs.cgi" ); } TEST_F(MISC_ParseHtmlFormActionTest, subbbscgi) { const std::string html = R"(")"; std::string result = MISC::parse_html_form_action( html ); EXPECT_EQ( result, "/test/subbbs.cgi" ); } TEST_F(MISC_ParseHtmlFormActionTest, with_parameter) { const std::string html = R"(")"; std::string result = MISC::parse_html_form_action( html ); EXPECT_EQ( result, "/test/bbs.cgi?foo=bar" ); } TEST_F(MISC_ParseHtmlFormActionTest, ignore_attributes) { const std::string html = R"(")"; std::string result = MISC::parse_html_form_action( html ); EXPECT_EQ( result, "/test/bbs.cgi" ); } TEST_F(MISC_ParseHtmlFormActionTest, relative_path_an_upper_order) { const std::string html = R"(")"; std::string result = MISC::parse_html_form_action( html ); EXPECT_EQ( result, "/test/bbs.cgi" ); } TEST_F(MISC_ParseHtmlFormActionTest, relative_path_same_hierarchy) { std::string html = R"(")"; std::string result = MISC::parse_html_form_action( html ); EXPECT_EQ( result, "/test/bbs.cgi" ); html = R"(")"; result = MISC::parse_html_form_action( html ); EXPECT_EQ( result, "/test/subbbs.cgi" ); } TEST_F(MISC_ParseHtmlFormActionTest, relative_path_double_upper_orders) { const std::string html = R"(")"; std::string result = MISC::parse_html_form_action( html ); EXPECT_EQ( result, std::string{} ); } TEST_F(MISC_ParseHtmlFormActionTest, relative_path_middle_upper_order) { const std::string html = R"(")"; std::string result = MISC::parse_html_form_action( html ); EXPECT_EQ( result, std::string{} ); } TEST_F(MISC_ParseHtmlFormActionTest, absolute_path_without_scheme) { const std::string html = R"(")"; std::string result = MISC::parse_html_form_action( html ); EXPECT_EQ( result, std::string{} ); } TEST_F(MISC_ParseHtmlFormActionTest, http_url) { const std::string html = R"(")"; std::string result = MISC::parse_html_form_action( html ); EXPECT_EQ( result, std::string{} ); } TEST_F(MISC_ParseHtmlFormActionTest, https_url) { const std::string html = R"(")"; std::string result = MISC::parse_html_form_action( html ); EXPECT_EQ( result, std::string{} ); } class ChrefDecodeTest : public ::testing::Test {}; TEST_F(ChrefDecodeTest, empty) { const std::string result = MISC::chref_decode( std::string{}, true ); EXPECT_EQ( result, std::string{} ); } TEST_F(ChrefDecodeTest, decimal_hello) { const std::string result = MISC::chref_decode( std::string{ "hello" }, true ); EXPECT_EQ( result, "hello" ); } TEST_F(ChrefDecodeTest, hexadecimal_hello) { const std::string result = MISC::chref_decode( std::string{ "hello" }, true ); EXPECT_EQ( result, "hello" ); } TEST_F(ChrefDecodeTest, escape_html_char_completely) { const std::string input = "<>&" <>&" <>&""; const std::string result = MISC::chref_decode( input, true ); EXPECT_EQ( result, R"(<>&" <>&" <>&")" ); } TEST_F(ChrefDecodeTest, escape_html_char_keeping) { const std::string input = "<>&" <>&" <>&""; const std::string result = MISC::chref_decode( input, false ); EXPECT_EQ( result, "<>&" <>&" <>&"" ); } class AsciiIgnoreCaseFindTest : public ::testing::Test {}; TEST_F(AsciiIgnoreCaseFindTest, empty) { EXPECT_EQ( 0, MISC::ascii_ignore_case_find( std::string{}, std::string{} ) ); EXPECT_EQ( std::string::npos, MISC::ascii_ignore_case_find( std::string{}, std::string{}, 10 ) ); } TEST_F(AsciiIgnoreCaseFindTest, not_match) { const std::string haystack = "spam ham eggs"; EXPECT_EQ( std::string::npos, MISC::ascii_ignore_case_find( haystack, "foo bar" ) ); } TEST_F(AsciiIgnoreCaseFindTest, out_of_bounds) { const std::string haystack = "out of bounds"; EXPECT_EQ( std::string::npos, MISC::ascii_ignore_case_find( haystack, "needle", haystack.size() + 1 ) ); } TEST_F(AsciiIgnoreCaseFindTest, match_same_case) { const std::string haystack = "helloworld"; EXPECT_EQ( 0, MISC::ascii_ignore_case_find( haystack, "hello" ) ); EXPECT_EQ( 5, MISC::ascii_ignore_case_find( haystack, "world" ) ); } TEST_F(AsciiIgnoreCaseFindTest, match_lowercase) { const std::string haystack_lower = "helloworld"; EXPECT_EQ( 0, MISC::ascii_ignore_case_find( haystack_lower, "HELLO" ) ); EXPECT_EQ( 5, MISC::ascii_ignore_case_find( haystack_lower, "WORLD" ) ); } TEST_F(AsciiIgnoreCaseFindTest, match_uppercase) { const std::string haystack_upper = "HELLOWORLD"; EXPECT_EQ( 0, MISC::ascii_ignore_case_find( haystack_upper, "hello" ) ); EXPECT_EQ( 5, MISC::ascii_ignore_case_find( haystack_upper, "world" ) ); } TEST_F(AsciiIgnoreCaseFindTest, match_mix_case) { const std::string haystack_mix = "HeLlOwOrLd"; EXPECT_EQ( 0, MISC::ascii_ignore_case_find( haystack_mix, "hElLo" ) ); EXPECT_EQ( 5, MISC::ascii_ignore_case_find( haystack_mix, "WoRlD" ) ); } TEST_F(AsciiIgnoreCaseFindTest, match_lead_word) { const std::string haystack = "foo bar baz bar"; EXPECT_EQ( 4, MISC::ascii_ignore_case_find( haystack, "bar" ) ); EXPECT_EQ( 12, MISC::ascii_ignore_case_find( haystack, "BAR", 5 ) ); } } // namespace jdim-0.7.0/test/gtest_xml_dom.cpp000066400000000000000000000260651417047150700170100ustar00rootroot00000000000000// License: GPL-2.0 #include "xml/dom.h" #include "gtest/gtest.h" namespace { class XML_DomGetXml : public ::testing::Test {}; TEST_F(XML_DomGetXml, xml_empty) { XML::Dom dom{ XML::NODE_TYPE_UNKNOWN, "test" }; std::string xml = dom.get_xml(); EXPECT_EQ( "", xml ); } TEST_F(XML_DomGetXml, xml_empty_document) { XML::Dom dom{ XML::NODE_TYPE_DOCUMENT, "document" }; std::string xml = dom.get_xml(); EXPECT_EQ( "", xml ); } TEST_F(XML_DomGetXml, xml_single_element) { XML::Dom dom{ XML::NODE_TYPE_DOCUMENT, "document" }; XML::Dom* root = dom.appendChild( XML::NODE_TYPE_ELEMENT, "root" ); root->setAttribute( "name", "value" ); std::string xml = dom.get_xml(); EXPECT_EQ( "\n\n", xml ); } TEST_F(XML_DomGetXml, xml_single_child_element) { XML::Dom dom{ XML::NODE_TYPE_DOCUMENT, "document" }; XML::Dom* root = dom.appendChild( XML::NODE_TYPE_ELEMENT, "root" ); root->appendChild( XML::NODE_TYPE_ELEMENT, "child" ); std::string xml = dom.get_xml(); EXPECT_EQ( "\n\n \n\n", xml ); } TEST_F(XML_DomGetXml, xml_single_child_text) { XML::Dom dom{ XML::NODE_TYPE_DOCUMENT, "document" }; XML::Dom* root = dom.appendChild( XML::NODE_TYPE_ELEMENT, "root" ); XML::Dom* child = root->appendChild( XML::NODE_TYPE_TEXT, "child" ); child->nodeValue( "hello world" ); std::string xml = dom.get_xml(); EXPECT_EQ( "\n\n hello world\n\n", xml ); } TEST_F(XML_DomGetXml, xml_nesting_child_element) { XML::Dom dom{ XML::NODE_TYPE_DOCUMENT, "document" }; XML::Dom* root = dom.appendChild( XML::NODE_TYPE_ELEMENT, "root" ); XML::Dom* child = root->appendChild( XML::NODE_TYPE_ELEMENT, "first" ); child->appendChild( XML::NODE_TYPE_ELEMENT, "second" ); std::string xml = dom.get_xml(); std::string expect = R"=( )="; EXPECT_EQ( expect, xml ); } class XML_DomProperty : public ::testing::Test {}; TEST_F(XML_DomProperty, node_type) { const auto types = { XML::NODE_TYPE_UNKNOWN, XML::NODE_TYPE_ELEMENT, XML::NODE_TYPE_TEXT, XML::NODE_TYPE_DOCUMENT, }; for( const auto t : types ) { XML::Dom dom{ t, "test" }; int result = dom.nodeType(); EXPECT_EQ( t, result ); } } TEST_F(XML_DomProperty, node_name) { XML::Dom dom{ XML::NODE_TYPE_UNKNOWN, "test" }; std::string name = dom.nodeName(); EXPECT_EQ( "test", name ); } TEST_F(XML_DomProperty, node_value) { XML::Dom dom{ XML::NODE_TYPE_UNKNOWN, "test" }; std::string value = dom.nodeValue(); EXPECT_EQ( "", value ); } class XML_DomGetElement : public ::testing::Test {}; TEST_F(XML_DomGetElement, empty_id) { XML::Dom dom{ XML::NODE_TYPE_UNKNOWN, "test" }; XML::Dom* result = dom.getElementById( "" ); EXPECT_EQ( nullptr, result ); } TEST_F(XML_DomGetElement, not_found_id) { XML::Dom dom{ XML::NODE_TYPE_UNKNOWN, "test" }; XML::Dom* child = dom.appendChild( XML::NODE_TYPE_UNKNOWN, "unknown" ); child->setAttribute( "id", "the-id" ); child = dom.appendChild( XML::NODE_TYPE_TEXT, "text" ); child->setAttribute( "id", "the-id" ); child = dom.appendChild( XML::NODE_TYPE_DOCUMENT, "document" ); child->setAttribute( "id", "the-id" ); XML::Dom* result = dom.getElementById( "the-id" ); EXPECT_EQ( nullptr, result ); } TEST_F(XML_DomGetElement, found_first_id) { XML::Dom dom{ XML::NODE_TYPE_UNKNOWN, "test" }; XML::Dom* child = dom.appendChild( XML::NODE_TYPE_ELEMENT, "first" ); child->setAttribute( "id", "the-id" ); XML::Dom* expect = child; child = dom.appendChild( XML::NODE_TYPE_ELEMENT, "second" ); child->setAttribute( "id", "the-id" ); XML::Dom* result = dom.getElementById( "the-id" ); EXPECT_EQ( expect, result ); } TEST_F(XML_DomGetElement, empty_tag_name) { XML::Dom dom{ XML::NODE_TYPE_UNKNOWN, "test" }; const std::list result = dom.getElementsByTagName( "" ); EXPECT_TRUE( result.empty() ); } TEST_F(XML_DomGetElement, not_found_tag_name) { XML::Dom dom{ XML::NODE_TYPE_UNKNOWN, "test" }; dom.appendChild( XML::NODE_TYPE_UNKNOWN, "the-tag" ); dom.appendChild( XML::NODE_TYPE_TEXT, "the-tag" ); dom.appendChild( XML::NODE_TYPE_DOCUMENT, "the-tag" ); const std::list result = dom.getElementsByTagName( "" ); EXPECT_TRUE( result.empty() ); } TEST_F(XML_DomGetElement, found_tag_name) { XML::Dom dom{ XML::NODE_TYPE_UNKNOWN, "test" }; XML::Dom* first = dom.appendChild( XML::NODE_TYPE_ELEMENT, "the-tag" ); dom.appendChild( XML::NODE_TYPE_ELEMENT, "another-tag" ); XML::Dom* second = dom.appendChild( XML::NODE_TYPE_ELEMENT, "the-tag" ); const std::list expect = { first, second }; const std::list result = dom.getElementsByTagName( "the-tag" ); EXPECT_EQ( expect, result ); } class XML_DomChildren : public ::testing::Test {}; TEST_F(XML_DomChildren, has_child_nodes) { XML::Dom dom{ XML::NODE_TYPE_UNKNOWN, "test" }; bool result = dom.hasChildNodes(); EXPECT_FALSE( result ); dom.appendChild( XML::NODE_TYPE_ELEMENT, "child" ); result = dom.hasChildNodes(); EXPECT_TRUE( result ); } TEST_F(XML_DomChildren, child_nodes) { XML::Dom dom{ XML::NODE_TYPE_UNKNOWN, "unknown" }; XML::Dom* first = dom.appendChild( XML::NODE_TYPE_ELEMENT, "element" ); XML::Dom* second = dom.appendChild( XML::NODE_TYPE_TEXT, "text" ); XML::Dom* third = dom.appendChild( XML::NODE_TYPE_DOCUMENT, "document" ); const std::list expect = { first, second, third }; const std::list result{ dom.begin(), dom.end() }; EXPECT_EQ( expect, result ); } TEST_F(XML_DomChildren, append_child) { XML::Dom dom{ XML::NODE_TYPE_UNKNOWN, "test" }; XML::Dom* child = dom.appendChild( XML::NODE_TYPE_ELEMENT, "child" ); EXPECT_NE( nullptr, child ); EXPECT_EQ( XML::NODE_TYPE_ELEMENT, child->nodeType() ); EXPECT_EQ( "child", child->nodeName() ); } TEST_F(XML_DomChildren, first_child) { XML::Dom dom{ XML::NODE_TYPE_UNKNOWN, "test" }; XML::Dom* child = dom.firstChild(); EXPECT_EQ( nullptr, child ); dom.appendChild( XML::NODE_TYPE_ELEMENT, "child" ); child = dom.firstChild(); EXPECT_NE( nullptr, child ); EXPECT_EQ( XML::NODE_TYPE_ELEMENT, child->nodeType() ); EXPECT_EQ( "child", child->nodeName() ); } TEST_F(XML_DomChildren, remove_child) { XML::Dom dom{ XML::NODE_TYPE_UNKNOWN, "test" }; bool result = dom.removeChild( nullptr ); EXPECT_FALSE( result ); XML::Dom* child = dom.appendChild( XML::NODE_TYPE_ELEMENT, "child" ); result = dom.removeChild( child ); EXPECT_TRUE( result ); EXPECT_FALSE( dom.hasChildNodes() ); } TEST_F(XML_DomChildren, emplace_front) { XML::Dom dom{ XML::NODE_TYPE_UNKNOWN, "test" }; dom.appendChild( XML::NODE_TYPE_ELEMENT, "child1" ); XML::Dom* second_child = dom.emplace_front( XML::NODE_TYPE_ELEMENT, "child2" ); EXPECT_EQ( dom.firstChild(), second_child ); XML::Dom* third_child = dom.emplace_front( XML::NODE_TYPE_ELEMENT, "child2" ); EXPECT_EQ( dom.firstChild(), third_child ); } TEST_F(XML_DomChildren, children_clear) { XML::Dom dom{ XML::NODE_TYPE_UNKNOWN, "test" }; dom.appendChild( XML::NODE_TYPE_ELEMENT, "child1" ); dom.appendChild( XML::NODE_TYPE_ELEMENT, "child2" ); dom.clear(); EXPECT_FALSE( dom.hasChildNodes() ); } class XML_DomAttribute : public ::testing::Test {}; TEST_F(XML_DomAttribute, set_not_element_type) { XML::Dom dom_unknown{ XML::NODE_TYPE_UNKNOWN, "unknown" }; bool result = dom_unknown.setAttribute( "name", "value" ); EXPECT_FALSE( result ); XML::Dom dom_text{ XML::NODE_TYPE_TEXT, "text" }; result = dom_text.setAttribute( "name", "value" ); EXPECT_FALSE( result ); XML::Dom dom_doc{ XML::NODE_TYPE_DOCUMENT, "document" }; result = dom_doc.setAttribute( "name", "value" ); EXPECT_FALSE( result ); } TEST_F(XML_DomAttribute, set_empty_name_with_string_value) { XML::Dom dom{ XML::NODE_TYPE_ELEMENT, "test" }; bool result = dom.setAttribute( "", "value" ); EXPECT_FALSE( result ); } TEST_F(XML_DomAttribute, set_empty_name_with_integer_value) { XML::Dom dom{ XML::NODE_TYPE_ELEMENT, "test" }; bool result = dom.setAttribute( "", 123 ); EXPECT_FALSE( result ); } TEST_F(XML_DomAttribute, set_empty_string_value) { XML::Dom dom{ XML::NODE_TYPE_ELEMENT, "test" }; bool result = dom.setAttribute( "name", "" ); EXPECT_FALSE( result ); } TEST_F(XML_DomAttribute, get_empty) { XML::Dom dom{ XML::NODE_TYPE_ELEMENT, "test" }; std::string value = dom.getAttribute( "" ); EXPECT_EQ( "", value ); } TEST_F(XML_DomAttribute, get_not_found) { XML::Dom dom{ XML::NODE_TYPE_ELEMENT, "test" }; std::string value = dom.getAttribute( "not-found" ); EXPECT_EQ( "", value ); } TEST_F(XML_DomAttribute, set_string_value) { XML::Dom dom{ XML::NODE_TYPE_ELEMENT, "test" }; bool result = dom.setAttribute( "hello", "world" ); std::string value = dom.getAttribute( "hello" ); EXPECT_TRUE( result ); EXPECT_EQ( "world", value ); } TEST_F(XML_DomAttribute, set_string_value_twice) { XML::Dom dom{ XML::NODE_TYPE_ELEMENT, "test" }; dom.setAttribute( "hello", "world" ); bool result = dom.setAttribute( "hello", "jdim" ); std::string value = dom.getAttribute( "hello" ); EXPECT_TRUE( result ); EXPECT_EQ( "world", value ); } TEST_F(XML_DomAttribute, set_positive_integer_value) { XML::Dom dom{ XML::NODE_TYPE_ELEMENT, "test" }; bool result = dom.setAttribute( "width", 32767 ); std::string value = dom.getAttribute( "width" ); EXPECT_TRUE( result ); EXPECT_EQ( "32767", value ); } TEST_F(XML_DomAttribute, set_negative_integer_value) { XML::Dom dom{ XML::NODE_TYPE_ELEMENT, "test" }; bool result = dom.setAttribute( "width", -32768 ); std::string value = dom.getAttribute( "width" ); EXPECT_TRUE( result ); EXPECT_EQ( "-32768", value ); } TEST_F(XML_DomAttribute, set_integer_value_twice) { XML::Dom dom{ XML::NODE_TYPE_ELEMENT, "test" }; dom.setAttribute( "width", 12321 ); bool result = dom.setAttribute( "width", 0 ); std::string value = dom.getAttribute( "width" ); EXPECT_TRUE( result ); EXPECT_EQ( "12321", value ); } TEST_F(XML_DomAttribute, set_uppercase_name_with_string_value) { XML::Dom dom{ XML::NODE_TYPE_ELEMENT, "test" }; bool result = dom.setAttribute( "UPPER", "hello world" ); EXPECT_TRUE( result ); std::string value = dom.getAttribute( "UPPER" ); EXPECT_EQ( "", value ); value = dom.getAttribute( "upper" ); EXPECT_EQ( "hello world", value ); } TEST_F(XML_DomAttribute, set_uppercase_name_with_integer_value) { XML::Dom dom{ XML::NODE_TYPE_ELEMENT, "test" }; bool result = dom.setAttribute( "UPPER", 256 ); EXPECT_TRUE( result ); std::string value = dom.getAttribute( "UPPER" ); EXPECT_EQ( "", value ); value = dom.getAttribute( "upper" ); EXPECT_EQ( "256", value ); } } // namespace jdim-0.7.0/test/meson.build000066400000000000000000000007341417047150700155740ustar00rootroot00000000000000# Add test code source files to following list. sources = [ 'gtest_jdlib_cookiemanager.cpp', 'gtest_jdlib_jdiconv.cpp', 'gtest_jdlib_misctime.cpp', 'gtest_jdlib_misctrip.cpp', 'gtest_jdlib_miscutil.cpp', 'gtest_xml_dom.cpp', ] deps = [ gtest_main_dep, jdim_deps, ] test_exe = executable( 'gtest_jdim', [buildinfo_h, core_sources, sources], dependencies : deps, include_directories : jdim_incs, link_with : jdim_libs, ) test('gtest tests', test_exe)