diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 000000000..e69de29bb diff --git a/COPYING b/COPYING new file mode 100644 index 000000000..d60c31a97 --- /dev/null +++ b/COPYING @@ -0,0 +1,340 @@ + 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. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 000000000..54b52fddc --- /dev/null +++ b/ChangeLog @@ -0,0 +1 @@ +README参照 diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 000000000..10e7f0cbc --- /dev/null +++ b/Makefile.am @@ -0,0 +1,2 @@ +SUBDIRS = src +EXTRA_DIST = AUTHORS TODO README configure diff --git a/NEWS b/NEWS new file mode 100644 index 000000000..e69de29bb diff --git a/README b/README new file mode 100644 index 000000000..95bb91993 --- /dev/null +++ b/README @@ -0,0 +1,564 @@ +名前 + jd - 2ch browser for Linux + +書式 + jd + +URL + + http://www.geocities.jp/jd4linux/ + + + +////////////////////////////////////////////////////////////// +// +// ライセンス、コピーライト +// + +最新のGPL(今のところGPL2) + +(c)2006 JD project + + +パッチやファイルを取り込んだ場合、それらのコピーライトは「JD project」に統一します。 +またGPL3以降へのライセンス変更に関しては作者に一任させて頂きます。 + + + +////////////////////////////////////////////////////////////// +// +// make、実行方法 +// + +・動作環境 + +gtkmm24-2.4.8 以上必須 +zlib-1.2 以上必須 +openssl-0.9 以上必須 + +kernel2.6以上推奨 +gtkmm2.6以上推奨 ( 2.4の場合レイアウトが一部崩れる ) +UTF-8環境推奨 ( EUC環境は動作保証外 ) + + +・makeに必要なツール、ライブラリ + +autoconf +automake +libtool +gtkmm24-devel +zlib-devel +openssl-devel( 又は libssl-devel ) + + +・make 方法( rpmbuild の場合 ) + +rpmbuild -tb 〜.tgz でrpmファイルが出来るのであとは rpm -Uvh 〜.rpm +ライブラリが足りないといわれたら yum install 〜-devel +起動はメニューから起動するか、端末で jd と打ち込んでエンターを押す。 + + + +・make 方法( configure + make の場合 ) + +./autogen.sh +./configure +make +(お好みで) strip src/jd + +実行するには直接 src/jd を起動するか手動で /usr/bin あたりに src/jd を cp する。 + + + +以上の操作でmakeが通らなかったり動作が変な時はconfigure.in の user settingsの項を修正する。 + +use_xsmp : XSMPを使ってセッション管理をする +use_gnomeui : GNOMEUIを使ってセッション管理をする。libgnomeui-develが必要。use_xsmp=yesの時は無効 +use_ms932 : MS932ではなくCP932で文字コード変換をする + + + +////////////////////////////////////////////////////////////// +// +// datファイルのインポート +// + +JDのキャッシュ構造はnavi2chの上位互換仕様なので、navi2chからインポートする場合は +起動する前に cp -r .navi2ch .jd というようにディレクトリごとコピーするだけで良い。 + +また、設定ファイル(~/.jdrc)の path_cacheroot の行を + +path_cacheroot = ~/.navi2ch/ + +とすればnavi2chとキャッシュを共有できるがあくまで自己責任でおこなうこと。 + + +個別にdatファイルをインポートする場合は、対象の板を一度開いてからjdを終了し +キャッシュディレクトリにdatファイルを全てコピーしてからもう一度起動して +その板を開くと自動でインポートされる。 + +なおインポート後に初めてその板を開いたときはdatファイルを解析するため少し時間がかかる。 + + + +////////////////////////////////////////////////////////////// +// +// バックアップ、アンインストール +// + +バックアップは設定ファイル( ~/.jdrc )、及びキャッシュディレクトリ(デフォルトでは ~/.jd )を保存する。 + + +・アンインストール方法( rpm の場合 ) + +rpm -e jd + + +・アンインストール方法( 手動の場合 ) + +rm (インストールパス)/jd +rm -rf キャッシュディレクトリ +rm ~/.jdrc + + + +////////////////////////////////////////////////////////////// +// +// お気に入りの編集、登録、削除方法 +// + +板、スレ、画像に関してお気に入りの登録が可能。左のサイドバー内のfavorite +タブをクリックするとお気に入り(Favoriteビュー)が表示される。 + +お気に入りはドラッグ&ドロップで直接編集可能。また、お気に入りの設定ファイル +(キャッシュディレクトリ内にあるfavorite.xml)はXML形式なので手動で編集しても可。 + + +登録方法 + +(1) スレ一覧(board)、スレ(article)などの各ビューのタブをFavoriteビューにドラッグ&ドロップ +(2) 各ビューのお気に入りボタンを押す +(3) boardビューでctrl+クリックで複数のスレを選択 -> Favoriteにドラッグ&ドロップ +(4) boardビューでctrl+クリックで複数のスレを選択 -> 右クリック -> お気に入りに登録 +(5) 画像アイコンをFavoriteにドラッグ&ドロップ +(6) 画像(image)ビューで右クリック -> お気に入りに登録 + + +削除方法 + +(1) Favoriteビューでctrl+クリックで複数のスレを選択 -> 右クリック -> 削除 +(2) Favoriteビューでctrl+クリックで複数のスレを選択 -> delete キー + + + +////////////////////////////////////////////////////////////// +// +// 板移転について +// + +Fileメニューから板リストを再読込したときに移転した板があるときは自動で移転処理する。 + + + + +////////////////////////////////////////////////////////////// +// +// 外部板登録 +// + +現在の対応板 + +2ch互換板 +livedoorしたらば +まちBBS + + +まちBBSはListビューに表示される(表示されてい無い時は板一覧を更新する)。 + +その他の板を登録するにはキャッシュディレクトリにある etc.txt にアドレスを書く。 +登録した板はEtcビューに表示される。 + + +etc.txtの形式はnavi2ch互換で + +板の名前 +板のURL +板のID + +の順。なお板のIDはJDでは使用しないので適当で可。またURLの最後は必ず'/'で終わらせること。 + +(例) + +Linux板 +http://pc8.2ch.net/linux/ +linux +JBBSの板(適当) +http://jbbs.livedoor.jp/computer/123456/ +123456 + + + +////////////////////////////////////////////////////////////// +// +// 操作方法 +// + + +・共通操作 + +h, 左: 前のビューに戻る(例えばArticleビューならBoardビューに戻る) +l, 右: 次のビューに進む +Alt + x : Boardview <-> Article 切替え (2ペーンモード時) +q, Ctrl+w : 閉じる +s, F5 : 再読込 +ESC : ロード停止 +ctrl + h, ctrl + 左, ctrl + Pageup, Ctrl + Shift + Tab : 左のタブへ移動 +ctrl + l, ctrl + 右, ctrl + PageDown, Ctrl + Tab : 右のタブへ移動 +タブをダブルクリック : 再読み込み +タブを中クリック : 閉じる +タブをドラッグ -> タブにドロップ : ページの入れ替え +タブをドラッグ -> Favoriteにドロップ : お気に入り登録 + + +・board ←→ article の切り替え方法 + +デフォルトで2pane表示になっているので、boardビューとarticleビューを切り替えたいときは + +(1) マウスジェスチャ(マウス右ボタンを押しながら ↓ に動かしてボタンを放す)を使う +(2) ←→キー、又は h,l キー又は alt+x で切り替える +(3) ctrl+w でarticleを閉じる +(4) タブで切り替える +(5) メニューのView -> 2pane のチェックを外して3paneモードにする + + +・検索 + +Ctrl+f, / : 検索ボックスにフォーカスを移して後方検索開始 +? : 検索ボックスにフォーカスを移して前方検索開始 +検索ボックスに検索文字列を入れてEnter: 検索開始 +検索ボックスに検索文字列入れてCtrl+Enter: ( articleビューのみ ) レス抽出 +Esc : 検索中止 +Enter, F3, n : 後方検索 +Shift+Enter, Ctrl+F3, N : 前方検索 + +( articleビューのみ )単語をスペースで分けて検索すると全ての単語を検索する + + + +・List、Etc、Favoriteビューの操作 + +j , 下 : 1行下 +k, 上 : 1行上 +h, 左 : ディレクトリ閉じる +l, 右 : (ディレクトリが開いてないなら)ディレクトリを開く +space : 板を開く +del : 選択行削除( Favorite のみ ) + +行を中ボタンクリック : 板をタブで開く +Ctrl+クリック : 複数行選択 + + + +・Boardビューの操作 + +基本操作はListビューと同じ + +del : 選択スレのキャッシュ削除 + +選択スレをドラッグ -> Favoriteにドロップ : 選択スレをお気に入りに登録 +列名クリック : ソート状態変更 + +列幅の変更は、最後に閉じたBoardビューの幅が記憶されるので +他のBoardビューを全て閉じてからおこなうこと + + +・ Articleビューの操作 + +多段ポップアップの方法 + +ポップアップ表示されている時にマウスの右ボタンをクリックし、そのままボタンを離さずに +カーソルを動かすと、ポップアップが固定されて閉じなくなるのでカーソルを他のホップアップ +の上に移動できる。ポップアップの外にカーソルが出ると閉じる + +本文中の数字を範囲選択してマウスポインタを上に移動 : 対応したレスをポップアップ表示 +本文中の"ID:〜"を範囲選択してマウスポインタを上に移動 : ID抽出してポップアップ表示 + +何もないところで中クリック : オートスクロール開始 +何もないところで右クリック : 通常メニュー表示 + +レスアンカーの上でクリック : メニュー表示 +レスアンカーの上で中クリック : 対象の周辺のレスを抽出して別のタブに表示 +レスアンカーの上で右クリック : 通常メニュー表示 + +レス番号の上でクリック : メニュー表示 +レス番号の上で中クリック : ブックマーク設定/解除 +レス番号の上で右クリック : 現在のレスを参照しているレスを抽出してポップアップ表示 + +IDの上でクリック : メニュー表示 +IDの上で中クリック : ID抽出して別のタブに表示 +IDの上で右クリック : ID抽出してポップアップ表示 + +リンクの上でクリック : 開く +リンクの上で右クリック : 通常メニュー表示 + +画像リンクの上でクリック : 開く +画像リンクの上で中クリック : バックグラウンドで開く +画像リンクの上で右クリック : 通常メニュー表示 + +ESC: ロード停止、ポップアップ表示を消す + +Space, PageDown : 1ページ下 +d : 半ページ下 +j , 下 : 1行下 + +b, PageUp : 1ページ上 +u : 半ページ上 +k, 上 : 1行上 + +F2 : 次のブックマークに移動 +Ctrl+F2 : 前のブックマークに移動 + +Home, g, < : Home +End, G, > : End +F4 : 新着へ移動 + +w : 書き込みウィンドウ表示 + + +・ Messageビューの操作 + +alt + c : 書き込みをキャンセル +alt + w : 書き込み実行 + + +・ Imageビューの操作 + +マウスでドラッグ : 画面スクロール +ダブルクリック : 画像再読み込み +中クリック : 閉じる +右クリック : メニュー表示 + +z: 元の画像サイズ +x: 画像を画面にフィットさせる ++: 画像の拡大 +-: 画像の縮小 +c: 画像のモザイク解除 +Ctrl+s : 保存 +Delete : 削除 + + +////////////////////////////////////////////////////////////// +// +// マウスジェスチャ +// + +右ボタンを押しながら下のジェスチャを入力しボタンを放すとコマンドが実行される + +← : 前のビューに戻る +→ : 次のビューに進む +↓→: 閉じる +↑↓ : 再読込 +↑ : ロード停止(Escape) +↓ : Boardview <-> Article 切替 (2ペーンモード時) +↑← : 左のタブへ移動 +↑→ : 右のタブへ移動 +→↑ : Home +→↓ : End +→↓→ : 新着へ移動 +↓←: 書き込み +↓↑ : 画像のモザイク解除 + + + + +////////////////////////////////////////////////////////////// +// +// 履歴 +// + + +- 1.50.060601 + +誤った正規表現を入力すると落ちるバグを修正 +正式版リリース + + +- 1.5rc.060528 + +hana仕様変更対応 + + +- 1.5rc.060527 + +タブを中クリックで全て閉じると落ちるバグを修正 +hanaに暫定対応 +テスト項目消化 + + +- 1.5b.060522 + +フォーカスアウト周りの処理をKDEなどの環境に合わせて変えるのを止めた +→GdkEventCrossingのmodeをみてフォーカスアウトするかどうか決定 + + +- 1.5b.060518 + +KDE環境でリンクをクリックしても反応が無い問題を修正 +KDE環境で多重ポップアップ出来ない問題を修正 +まちBBSでレスの引用書き込みが出来なかったバグを修正 +gtk2-2.9のツリービューのパス取得の仕様が変わったせいで固まるバグを修正 + + +- 1.5b.060514 フィーチャーフリーズ + +キーワードやIDなどを抽出するときは抽出元のスレの隣にタブを開くようにした +マウスジェスチャやショートカットキーボタンの設定を細かく出来るようにした +名前がデフォルトの名無しの時は数字をアンカーにしないようにした +sprintfになっていた部分をsnprintfに直した +FreeBSDでhistoryがセグフォ出していた問題を修正 +書き込み確認のダイアログを表示するようにした( always_write_ok ) +書き込み中に「書き込み中」と表示するようにした +書き込みがタイムアウトしたら再読みしろというダイアログを出すようにした +FreeBSDでもautogen.shが通るようにした +履歴を板とスレで分けた +履歴の保持数を設定できるようにした( history_size ) +板一覧でカテゴリ全体をまとめてお気に入りに登録できるようにした + +(追加変更した操作) +タブをダブルクリック : 再読み込み +ダブルクリック : 画像再読み込み +alt + q : 書き込みをキャンセル ( alt + c から変更 ) + + +- 1.5b.060415 + +configure.inを修正 +libgnomeuiでは無くてXSMPをデフォルトで使用するようにした +ポップアップ中にマウスホイールを回したらポップアップの方をスクロールさせるようにした +ポップアップとカーソルの間のマージンを設定出来るようにした( margin_popup ) +板一覧でカテゴリをひとつだけしか開かないように設定可能にした( open_one_category ) +メニューにショートカットキーを表示するようにした +設定ファイル(keyconf.xml)でキーバインディングを設定出来るようにした + +(追加変更したショートカットキー) +alt + c : 書き込みをキャンセル +alt + w : 書き込み実行 +F3 : 新着へ移動が次検索と被っていたのでF4に変更 + + +- 1.5b.060401 + +gnomeセッションを終了時にセッション情報を保存するようにした(要libgnomeui-devel) +configure.in.vineを消してconfigure.inひとつに統一した +コンパイルオプションに-Wallを付けて出てきた警告を全部消した + + +- 1.5b.060319 + +自前のツールチップの表示部を作りなおした(背景色をテーマに合わせるようにした) +タブの色がテーマによっては変になるバグを修正 +"123-124"みたいなアンカーがあるとき、半角の"-"も認識するようにした +いつの間にかポップアップでスクロールバーが出なくなっていたので修正 +SIGINTやSIGHUPを受け取ったときにお気に入りをバックアップするようにした(テスト不十分) +ローダがget_addrinfoに失敗すると落ちるバグを修正 + + +- 1.5b.060311 + +他のスレをポップアップした直後にそのスレを消して再ポップアップすると落ちるバグを修正 +ロード開始直後にすぐスレを閉じると落ちるバグを修正 +キャッシュのルートディレクトリがうまく設定できないバグを修正 +正規表現クラス実装 +rawモード読み込み対応(?) +まちBBSの読み書きに対応(スレ立てはテスト出来ないので未実装) +bbslist,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 + +ファイルロード時にconnectに失敗していてもsendしていた問題を修正 + + +- 1.5b.060214 + +アイコンバーのアイコンを並び替えるとスクロールが変になるバグを修正 +画像のロード中に画像保存ダイアログを開くとロードが停止するバグを修正 +名前欄のトリップの中の数字をリンクしないようにした +Fileメニューにお気に入り保存を追加 + +(追加、変更したショートカットキー、マウスジェスチャ) +F3 : 新着へ移動 +→↓→ : 新着へ移動 +z: 元の画像サイズ +x: 画像を画面にフィットさせる ++: 画像の拡大 +-: 画像の縮小 +c: 画像のモザイク解除 ( x から変更、gthumbに合わせるため ) + + + +- 1.5b.060207 + +ファイルローダで一度タイムアウトすると再読み込みしなくなるバグを修正 +縦3paneモード実装 + + +- 1.5b.060205 + +subject.txtが壊れていると落ちるバグを修正 +ファイルローダでsend時にタイムアウトしているのにエラーチェックを素通りしていたバグを修正 +ファイルローダをhttps対応にした +ファイルロード中にロード停止したときの反応を良くした +Gtk::Stock::MEDIA_RECORDを使うのを止めた + + +- 1.5b.060204 + +コードを大幅整理(特にスレ標示部) +ユニコード文字参照に対応 +書き込み中にarticleビューの書き込みボタンを押してもmessageを閉じないようにした + + +- 1.5b.060115 + +履歴をクリアした後板を開くと落ちるバグを修正 +ツリービューのスクロール処理を自前でするようにした( .jdrcのtree_scroll_sizeで調整 ) +ダブルクリックで文字列を範囲選択出来るようにした + + +- 1.5b.060113 + +GLIBのスレッド初期化忘れを修正 +コンパイルオプションに -DNOUSE_MS932 を与えるととCP932で文字コード変換するようにした +iconvでMS932からUTF8にマッピング出来なかった文字を□に変換するようにした +起動時のimageの復元機能実装 +SETTING.TXTのローダ作成 +操作性向上(主にタブ操作、画像操作まわり) + + +- 1.5b.060106 + +favoriteで名前が変更出来ないバグを修正 +画像を保存した後に画像をダウンロードすると固まるバグを修正 +起動時のboard,articleの復元機能実装 +history機能実装 +文字参照の一部を実装(thinsp, ensp, emsp, etc.) + + +- 1.0.060101 + +リリース diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 000000000..ba5cdd5e1 --- /dev/null +++ b/autogen.sh @@ -0,0 +1,57 @@ +#!/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 autoconf >/dev/null 2>&1 +then AC_POSTFIX="" +else + echo 'you need autoconfig (2.58+ recommended) to generate the Makefile' + exit 1 +fi +echo `autoconf$AC_POSTFIX --version | head -1` found + +if which automake-1.9 >/dev/null 2>&1 +then AM_POSTFIX=-1.9 +elif which automake-1.8 >/dev/null 2>&1 +then AM_POSTFIX=-1.8 +elif which automake-1.7 >/dev/null 2>&1 +then AM_POSTFIX=-1.7 +elif which automake-1.6 >/dev/null 2>&1 +then AM_POSTFIX=-1.6 +elif which automake >/dev/null 2>&1 +then AM_POSTFIX="" +else + echo 'you need automake (1.8.3+ recommended) to generate the Makefile' + exit 1 +fi + +if test -d /usr/local/share/aclocal ; then + ACLOCAL_INCLUDE="-I /usr/local/share/aclocal" +fi + +echo `automake$AM_POSTFIX --version | head -1` found + +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 --force --copy +autoheader$AC_POSTFIX +automake$AM_POSTFIX --add-missing --copy --gnu +autoconf$AC_POSTFIX +#./configure $* && $MAKE diff --git a/configure.in b/configure.in new file mode 100644 index 000000000..024a3586d --- /dev/null +++ b/configure.in @@ -0,0 +1,132 @@ +dnl +dnl JD用 configure.in +dnl +AC_PREREQ(2.50) +AC_INIT(jd, 1.0) +AM_INIT_AUTOMAKE +AC_CONFIG_HEADERS(config.h) + +AC_ISC_POSIX +AC_PROG_CC +AM_PROG_CC_STDC +AC_HEADER_STDC +AC_PROG_CPP +AC_PROG_CXX +AC_PROG_CXXCPP +AC_PROG_LIBTOOL +AC_LANG_CPLUSPLUS + +dnl --------------------------------------------------- +dnl --------------------------------------------------- +dnl +dnl ユーザー設定 +dnl + +dnl XSMPを使ってセッション管理をする。libSMとlibICEが必要。無ければXSMPは無効になる +use_xsmp=yes + +dnl GNOMEUIを使ってセッション管理をする。libgnomeui-develが必要。use_xsmp=yesの時は無効 +use_gnomeui=no + +dnl MS932ではなくCP932で文字コード変換をする +use_cp932=no + +dnl 追加コンパイルオプション +CXXFLAGS="$CXXFLAGS -ggdb -Wall" + +dnl --------------------------------------------------- +dnl --------------------------------------------------- + +LIBSM_CFLAGS="" +LIBSM_LIBS="" +GNOMEUI_CFLAGS="" +GNOMEUI_LIBS="" + +dnl +dnl パッケージのチェック +dnl +PKG_CHECK_MODULES(GTKMM,[gtkmm-2.4 >= 2.4.8] ) +PKG_CHECK_MODULES(GTHREAD, [gthread-2.0 >= 2.0] ) +PKG_CHECK_MODULES(OPENSSL, [openssl >= 0.9] ) + +AC_SUBST(GTKMM_CFLAGS) +AC_SUBST(GTKMM_LIBS) +AC_SUBST(GTHREAD_CFLAGS) +AC_SUBST(GTHREAD_LIBS) +AC_SUBST(OPENSSL_CFLAGS) +AC_SUBST(OPENSSL_LIBS) + + +dnl +dnl CP932使用 +dnl +if test x"$use_cp932" = "xyes" ; then + echo "use CP932" + CXXFLAGS="$CXXFLAGS -DNOUSE_MS932" +else + echo "use MS932" +fi + + +dnl +dnl XSMP を使用してセッション管理 +dnl +dnl dirsの並びは Tk の configure に書いてあったもの + Fedora 向けにディレクトリを追加 +dnl +if test x"$use_xsmp" = "xyes" ; then + + AC_MSG_CHECKING(for SMlib.h and ICElib.h) + LIBSM_CFLAGS="" + dirs="/usr/unsupported/include /usr/local/include /usr/X386/include /usr/X11R6/include /usr/X11R5/include /usr/include/X11R5 /usr/include/X11R4 /usr/openwin/include /usr/X11/include /usr/sww/include /usr/include" + for i in $dirs ; do + if test -r $i/X11/ICE/ICElib.h -a -r $i/X11/SM/SMlib.h ; then + AC_MSG_RESULT($i) + LIBSM_CFLAGS="-I$i" + break + fi + done + if test -z "$LIBSM_CFLAGS" ; then + AC_MSG_RESULT(could not find SMlib.h and ICElib.h) + fi + + AC_MSG_CHECKING(for libSM and libICE) + LIBSM_LIBS="" + dirs="/usr/unsupported/lib /usr/local/lib /usr/X386/lib /usr/X11R6/lib /usr/X11R5/lib /usr/lib/X11R5 /usr/lib/X11R4 /usr/openwin/lib /usr/X11/lib /usr/sww/X11/lib /usr/lib" + for i in $dirs ; do + if test -r $i/libICE.so -a -r $i/libSM.so ; then + AC_MSG_RESULT($i) + LIBSM_LIBS="-L$i -lICE -lSM" + break + fi + done + if test -z "$LIBSM_LIBS" ; then + AC_MSG_RESULT(could not find libSM and libICE.) + fi + + if test -n "$LIBSM_CFLAGS" -a -n "$LIBSM_LIBS" ; then + echo "use XSMP" + AC_SUBST(LIBSM_CFLAGS) + AC_SUBST(LIBSM_LIBS) + CXXFLAGS="$CXXFLAGS -DUSE_XSMP" + fi + +else + + +dnl +dnl GNOMEUI を使用してセッション管理 +dnl +if test x"$use_gnomeui" = "xyes" ; then + + PKG_CHECK_MODULES(GNOMEUI, [libgnomeui-2.0 >= 2.0] ) + + echo "use GNOMEUI" + AC_SUBST(GNOMEUI_CFLAGS) + AC_SUBST(GNOMEUI_LIBS) + CXXFLAGS="$CXXFLAGS -DUSE_GNOMEUI" +fi + +fi + + +AC_OUTPUT(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/config/Makefile ) diff --git a/jd.desktop b/jd.desktop new file mode 100644 index 000000000..28277c4d5 --- /dev/null +++ b/jd.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Name=JD 2ch browser +Comment=JD is the browser for 2ch.It's similar to OpenJaneDoe. +Exec=jd +Icon=jd.png +Terminal=0 +Type=Application +Encoding=UTF-8 +Categories=Application;Network;X-Red-Hat-Base;X-Red-Hat-Base-Only; + diff --git a/jd.spec b/jd.spec new file mode 100644 index 000000000..19c9f00b6 --- /dev/null +++ b/jd.spec @@ -0,0 +1,52 @@ +Summary: jd 2ch browser +Name: jd +Version: 150_060601 +Release: 1 +Group: Applications/Internet +URL: http://www.geocities.jp/jd4linux/ +Source0: %{name}-%{version}.tgz +BuildRoot: %{_tmppath}/%{name}-%{version}-root +Requires: gtkmm24 glibmm24 pango openssl zlib +BuildRequires: libtool automake autoconf gtkmm24-devel glibmm24-devel pango-devel openssl-devel zlib-devel +License: GPL + +%description +jd is the browser for 2ch.It's similar to OpenJaneDoe. + +%prep +%setup -q + +%build +sh autogen.sh +%configure +make + +%install +%makeinstall +%{__install} -D -m 644 %{name}.desktop %{buildroot}%{_datadir}/applications/%{name}.desktop +%{__install} -D -m 644 %{name}.png %{buildroot}%{_datadir}/pixmaps/%{name}.png + +rm -rf $RPM_BUILD_ROOT/usr/lib +rm -rf $RPM_BUILD_ROOT/usr/src + +%clean +rm -rf $RPM_BUILD_ROOT +rm -rf $RPM_BUILD_DIR/%{name}-%{version}/ + +%post + +%files +%defattr(-,root,root) +%doc README COPYING ChangeLog +%{_bindir}/jd +%{_datadir}/applications/%{name}.desktop +%{_datadir}/pixmaps/%{name}.png + +%changelog +* Sun Mar 9 2006 Houritsuchu +- Version up. +- add icon + +* Sat Feb 25 2006 Houritsuchu +- first + diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 000000000..050dece15 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,62 @@ +SUBDIRS = dbtree dbimg bbslist board article image message jdlib skeleton config + +bin_PROGRAMS = jd + +jd_LDADD = @LIBS@ @GTKMM_LIBS@ @GTHREAD_LIBS@ @OPENSSL_LIBS@ @GNOMEUI_LIBS@ @LIBSM_LIBS@\ + ./dbtree/libdbtree.a \ + ./dbimg/libdbimg.a \ + ./bbslist/libbbslist.a \ + ./board/libboard.a \ + ./article/libarticle.a \ + ./image/libimage.a \ + ./message/libmessage.a \ + ./skeleton/libskeleton.a \ + ./jdlib/libjdlib.a \ + ./config/libconfig.a + +jd_SOURCES = \ + main.cpp \ + winmain.cpp \ + core.cpp \ + cache.cpp \ + xml.cpp \ + command.cpp \ + viewfactory.cpp \ + sharedbuffer.cpp \ + dndmanager.cpp \ + historymenu.cpp \ + historysubmenu.cpp \ + control.cpp \ + controlutil.cpp \ +\ + session.cpp \ + login2ch.cpp + +noinst_HEADERS = \ + winmain.h \ + httpcode.h \ + core.h \ + cache.h \ + xml.h \ + jddebug.h \ + global.h \ + jdversion.h \ + command.h \ + viewfactory.h \ + sharedbuffer.h \ + dndmanager.h \ + proxypref.h \ + browserpref.h \ + passwdpref.h \ + historymenu.h \ + historysubmenu.h \ + colorid.h \ + controlid.h \ + controllabel.h \ + control.h \ + controlutil.h \ +\ + session.h \ + login2ch.h + +AM_CXXFLAGS = @GTKMM_CFLAGS@ @GTHREAD_CFLAGS@ @OPENSSL_CFLAGS@ @GNOMEUI_CFLAGS@ @LIBSM_CFLAGS@ diff --git a/src/article/Makefile.am b/src/article/Makefile.am new file mode 100644 index 000000000..41744d396 --- /dev/null +++ b/src/article/Makefile.am @@ -0,0 +1,39 @@ +noinst_LIBRARIES = libarticle.a + +libarticle_a_SOURCES = \ + articleadmin.cpp \ +\ + drawareabase.cpp \ + drawareamain.cpp \ + drawareapopup.cpp \ +\ + articleviewbase.cpp \ + articleview.cpp \ + articleviewpreview.cpp \ + articleviewpopup.cpp \ +\ + layouttree.cpp \ + font.cpp \ + preference.cpp + +noinst_HEADERS = \ + articleadmin.h \ +\ + drawareabase.h \ + drawareamain.h \ + drawareapopup.h \ +\ + articleviewbase.h \ + articleview.h \ + articleviewpreview.h \ + articleviewpopup.h \ +\ + layouttree.h \ + font.h \ + preference.h \ + caret.h \ + scrollinfo.h \ + toolbar.h + +AM_CXXFLAGS = @GTKMM_CFLAGS@ +INCLUDES = -I$(top_srcdir)/src diff --git a/src/article/articleadmin.cpp b/src/article/articleadmin.cpp new file mode 100644 index 000000000..f704d038f --- /dev/null +++ b/src/article/articleadmin.cpp @@ -0,0 +1,340 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "articleadmin.h" +#include "font.h" + +#include "dbtree/interface.h" + +#include "jdlib/miscutil.h" +#include "jdlib/jdregex.h" + +#include "skeleton/view.h" + +#include "global.h" +#include "viewfactory.h" +#include "dndmanager.h" +#include "sharedbuffer.h" +#include "session.h" + +ARTICLE::ArticleAdmin *instance_articleadmin = NULL; + +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 = NULL; +} + + +using namespace ARTICLE; + +ArticleAdmin::ArticleAdmin( const std::string& url ) + : SKELETON::Admin( url ) +{ + ARTICLE::init_font(); + + //D&D可 + get_notebook().set_dragable( true ); +} + + +ArticleAdmin::~ArticleAdmin() +{ +#ifdef _DEBUG + std::cout << "ArticleAdmin::~ArticleAdmin\n"; +#endif + + SESSION::set_article_URLs( get_URLs() ); + SESSION::set_article_page( get_current_page() ); + ARTICLE::init_font(); +} + + +// 前回開いていたURLを復元 +void ArticleAdmin::restore() +{ +#ifdef _DEBUG + std::cout << "ArticleAdmin::restore\n"; +#endif + + JDLIB::Regex regex; + + bool online = SESSION::is_online(); + SESSION::set_online( false ); + + std::list< std::string > list_tmp; + std::list< std::string >::iterator it_tmp; + + list_tmp = SESSION::article_URLs(); + it_tmp = list_tmp.begin(); + for( ; it_tmp != list_tmp.end(); ++it_tmp ){ + + std::string url = (*it_tmp); + COMMAND_ARGS command_arg; + command_arg.command = "open_view"; + command_arg.url = std::string(); + + // レス抽出 + if( regex.exec( std::string( "(.*)" ) + ARTICLE_SIGN + RES_SIGN + "(.*)" + + CENTER_SIGN + "(.*)" + TIME_SIGN, url )){ + + command_arg.url = regex.str( 1 ); + command_arg.arg1 = "true"; // タブで開く + command_arg.arg2 = "true"; // 既に開いているかチェック無し + command_arg.arg3 = "RES"; + command_arg.arg4 = regex.str( 2 ); + if( regex.str( 3 ) != "0" ) command_arg.arg5 = regex.str( 3 ); + } + + // ID抽出 + else if( regex.exec( std::string( "(.*)" ) + ARTICLE_SIGN + ID_SIGN + "(.*)" + TIME_SIGN, url )){ + + command_arg.url = regex.str( 1 ); + command_arg.arg1 = "true"; // タブで開く + command_arg.arg2 = "true"; // 既に開いているかチェック無し + command_arg.arg3 = "ID"; + command_arg.arg4 = regex.str( 2 ); + } + + // ブックマーク抽出 + else if( regex.exec( std::string( "(.*)" ) + ARTICLE_SIGN + BOOKMK_SIGN, url )){ + + command_arg.url = regex.str( 1 ); + command_arg.arg1 = "true"; // タブで開く + command_arg.arg2 = "true"; // 既に開いているかチェック無し + command_arg.arg3 = "BM"; + } + + // URL抽出 + else if( regex.exec( std::string( "(.*)" ) + ARTICLE_SIGN + URL_SIGN, url )){ + + command_arg.url = regex.str( 1 ); + command_arg.arg1 = "true"; // タブで開く + command_arg.arg2 = "true"; // 既に開いているかチェック無し + command_arg.arg3 = "URL"; + } + + // 参照 + else if( regex.exec( std::string( "(.*)" ) + ARTICLE_SIGN + REFER_SIGN + "(.*)" + TIME_SIGN, url )){ + + command_arg.url = regex.str( 1 ); + command_arg.arg1 = "true"; // タブで開く + command_arg.arg2 = "true"; // 既に開いているかチェック無し + command_arg.arg3 = "REF"; + command_arg.arg4 = regex.str( 2 ); + } + + // キーワード + else if( regex.exec( std::string( "(.*)" ) + ARTICLE_SIGN + KEYWORD_SIGN + "(.*)" + + ORMODE_SIGN + "(.*)" + TIME_SIGN, url )){ + + command_arg.url = regex.str( 1 ); + command_arg.arg1 = "true"; // タブで開く + command_arg.arg2 = "true"; // 既に開いているかチェック無し + + if( regex.str( 3 ) == "1" ) command_arg.arg3 = "KEYWORD_OR"; + else command_arg.arg3 = "KEYWORD"; + command_arg.arg4 = regex.str( 2 ); + } + + // MAIN + else if( !url.empty() ){ + command_arg.url = url; + command_arg.arg1 = "true"; // タブで開く + command_arg.arg2 = "false"; // 既に開いているかチェック + command_arg.arg3 = "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; +#endif + + if( ! command_arg.url.empty() ) open_view( command_arg ); + } + + + + SESSION::set_online( online ); + set_command( "set_page", std::string(), MISC::itostr( SESSION::article_page() ) ); +} + + +// +// カレントビューでポップアップ表示していたら消す +// +void ArticleAdmin::delete_popup() +{ + SKELETON::View* view = get_current_view(); + if( view ) view->set_command( "delete_popup" ); +} + + +// +// arg1 は "ID" や "MAIN" などのビューの種類を指定 +// +// arg2 は各ビュー別のパラメータ +// +SKELETON::View* ArticleAdmin::create_view( const COMMAND_ARGS& command ) +{ +#ifdef _DEBUG + std::cout << "ArticleAdmin::create_view : " << command.arg3 << std::endl; +#endif + + delete_popup(); + + int type = CORE::VIEW_NONE; + CORE::VIEWFACTORY_ARGS view_args; + std::string str_jump; + + // メインビュー + if( command.arg3 == "MAIN" ){ + type = CORE::VIEW_ARTICLEVIEW; + str_jump = command.arg4; // ジャンプ + } + + // キーワード抽出ビュー(AND) + else if( command.arg3 == "KEYWORD" ){ + type = CORE::VIEW_ARTICLEDRAWOUT; + view_args.arg1 = command.arg4; // query + view_args.arg2 = "AND"; + } + + // キーワード抽出ビュー(OR) + else if( command.arg3 == "KEYWORD_OR" ){ + type = CORE::VIEW_ARTICLEDRAWOUT; + view_args.arg1 = command.arg4; // query + view_args.arg2 = "OR"; + } + + // レス抽出ビュー + if( command.arg3 == "RES" ){ + type = CORE::VIEW_ARTICLERES; + view_args.arg1 = command.arg4; // レス番号 ( from-to ) + view_args.arg2 = "false"; + view_args.arg3 = command.arg5; // ジャンプ + str_jump = command.arg5; + } + + // ID 抽出ビュー + if( command.arg3 == "ID" ){ + type = CORE::VIEW_ARTICLEID; + view_args.arg1 = command.arg4; // ID + } + + // ブックマーク抽出ビュー + if( command.arg3 == "BM" ){ + type = CORE::VIEW_ARTICLEBM; + } + + // URL抽出ビュー + if( command.arg3 == "URL" ){ + type = CORE::VIEW_ARTICLEURL; + } + + // 参照抽出ビュー + if( command.arg3 == "REF" ){ + type = CORE::VIEW_ARTICLEREFER; + view_args.arg1 = command.arg4; // 対象レス番号 + } + + SKELETON::View* view = CORE::ViewFactory( type, command.url, view_args ); + assert( view != NULL ); + + // ジャンプ + if( ! str_jump.empty() ){ +#ifdef _DEBUG + std::cout << "goto " << str_jump << std::endl; +#endif + set_command( "goto_num", view->get_url(), str_jump ); + } + + return view; +} + + +// +// ローカルなコマンド +// +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 ); + } + + // ポップアップ消去 + else if( command.command == "delete_popup" ) delete_popup(); + + // command.url を含むビューを全て再レイアウト + else if( command.command == "relayout_views" ){ + + std::list< SKELETON::View* > list_view = get_list_view( command.url ); + std::list< SKELETON::View* >::iterator it = list_view.begin(); + + for( ; it != list_view.end(); ++it ){ + SKELETON::View* view = ( *it ); + if( view ) view->relayout(); + } + } + + // フォント初期化 + else if( command.command == "init_font" ) ARTICLE::init_font(); +} + + + + + +// +// タブのD&Dを開始 +// +void ArticleAdmin::slot_drag_begin( int page ) +{ + SKELETON::View* view = ( SKELETON::View* )get_notebook().get_nth_page( page ); + if( !view ) return; + + std::string url = view->get_url(); + + CORE::DND_Begin( 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 ); + +#ifdef _DEBUG + std::cout << "ArticleAdmin::slot_drag_begin " << info.name << std::endl; +#endif + + CORE::SBUF_clear_info(); + CORE::SBUF_append( info ); +} + + + +// +// タブのD&D終了 +// +void ArticleAdmin::slot_drag_end() +{ +#ifdef _DEBUG + std::cout << "ArticleAdmin::slot_drag_end\n"; +#endif + + CORE::DND_End(); +} diff --git a/src/article/articleadmin.h b/src/article/articleadmin.h new file mode 100644 index 000000000..9d57eac90 --- /dev/null +++ b/src/article/articleadmin.h @@ -0,0 +1,49 @@ +// ライセンス: 最新のGPL + +// +// 記事の管理クラス +// +#ifndef _ARTICLEADMIN_H +#define _ARTICLEADMIN_H + +#include "skeleton/admin.h" + +#include + +#define ARTICLE_SIGN "_ARTICLE_" +#define RES_SIGN "_RES_" +#define ID_SIGN "_ID_" +#define BOOKMK_SIGN "_BM_" +#define URL_SIGN "_URL_" +#define REFER_SIGN "_REF_" +#define KEYWORD_SIGN "_KW_" +#define ORMODE_SIGN "_OR_" +#define CENTER_SIGN "_CENTER_" +#define TIME_SIGN "_TIME_" + +namespace ARTICLE +{ + class ArticleAdmin : public SKELETON::Admin + { + public: + ArticleAdmin( const std::string& url ); + ~ArticleAdmin(); + + protected: + SKELETON::View* create_view( const COMMAND_ARGS& command ); + virtual void command_local( const COMMAND_ARGS& command ); + + virtual void restore(); + + private: + void delete_popup(); + + virtual void slot_drag_begin( int page ); + virtual void slot_drag_end(); + }; + + ARTICLE::ArticleAdmin* get_admin(); + void delete_admin(); +} + +#endif diff --git a/src/article/articleview.cpp b/src/article/articleview.cpp new file mode 100644 index 000000000..29bfe016f --- /dev/null +++ b/src/article/articleview.cpp @@ -0,0 +1,651 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "articleadmin.h" +#include "articleview.h" +#include "drawareamain.h" +#include "toolbar.h" + +#include "dbtree/interface.h" +#include "dbtree/articlebase.h" + +#include "jdlib/misctime.h" + +#include "command.h" +#include "global.h" + +#include +#include + + +using namespace ARTICLE; + + +// メインビュー + +ArticleViewMain::ArticleViewMain( const std::string& url ) + : ArticleViewBase( url ), m_gotonum_reserve( 0 ) +{ +#ifdef _DEBUG + std::cout << "ArticleViewMain::ArticleViewMain " << get_url() << " url_article = " << url_article() << std::endl; +#endif + + setup_view(); +} + + + +ArticleViewMain::~ArticleViewMain() +{ +#ifdef _DEBUG + std::cout << "ArticleViewMain::~ArticleViewMain : " << get_url() << " url_article = " << url_article() << std::endl; +#endif + int seen = drawarea()->seen_current(); + +#ifdef _DEBUG + std::cout << "set seen to " << seen << std::endl; +#endif + if( seen >= 1 ) get_article()->set_number_seen( seen ); +} + + +// +// num 番にジャンプ +// +// ローディング中ならジャンプ予約をしてロード後に update_finish() の中で改めて goto_num() を呼び出す +// +void ArticleViewMain::goto_num( int num ) +{ + if( get_article()->get_number_load() < num && get_article()->is_loading() ){ + + m_gotonum_reserve = num; + return; + } + + m_gotonum_reserve = 0; + ArticleViewBase::goto_num( num ); +} + + + +// +// 再読み込み +// +void ArticleViewMain::reload() +{ + // DAT落ちしてるとロードしないので状態をリセットしておく + get_article()->reset_status(); + show_view(); + CORE::core_set_command( "set_history_article", url_article() ); +} + + + +// +// キャッシュ表示 & 差分ロード開始 +// +void ArticleViewMain::show_view() +{ + m_gotonum_reserve = 0; + +#ifdef _DEBUG + std::cout << "ArticleViewBase::show_view\n"; +#endif + + CORE::core_set_command( "switch_article" ); + + if( get_url().empty() ){ + set_status( "invalid URL" ); + CORE::core_set_command( "set_status","", get_status() ); + return; + } + + // キャッシュに含まれているレスを表示 + int from_num = drawarea()->max_number() + 1; + int to_num = get_article()->get_number_load(); + if( from_num <= to_num ){ + + drawarea()->append_res( from_num, to_num ); + + // 以前見ていたところにジャンプ + drawarea()->goto_num( get_article()->get_number_seen() ); + } + + // セパレータを最後に移動 + drawarea()->set_separator_new( to_num + 1 ); + + update_finish(); + + // 差分 download 開始 + get_article()->download_dat(); + if( get_article()->is_loading() ){ + set_status( "loading..." ); + CORE::core_set_command( "set_status","", get_status() ); + } +} + + + +// +// ロード中にノード構造が変わったら呼ばれる +// +void ArticleViewMain::update_view() +{ + int num_from = drawarea()->max_number() + 1; + int num_to = get_article()->get_number_load(); + +#ifdef _DEBUG + std::cout << "ArticleViewMain::update_view : from " << num_from << " to " << num_to << std::endl; +#endif + + if( num_from > num_to ) return; + + drawarea()->append_res( num_from, num_to ); + drawarea()->redraw_view(); +} + + + +// +// ロードが終わったときに呼ばれる +// +void ArticleViewMain::update_finish() +{ +#ifdef _DEBUG + std::cout << "ArticleViewMain::update_finish\n"; +#endif + + int status = DBTREE::article_status( url_article() ); + std::string str_stat; + if( status & STATUS_OLD ) str_stat = "[ DAT落ち or 移転しました ]"; + if( status & STATUS_BROKEN ) str_stat = "[ 壊れています ]"; + + if( ! DBTREE::article_ext_err( url_article() ).empty() ) str_stat += " [ " + DBTREE::article_ext_err( url_article() ) + " ]"; + + // ラベルセット + toolbar()->m_button_board.set_label( "[ " + DBTREE::board_name( url_article() ) + " ]" ); + toolbar()->set_label( str_stat + DBTREE::article_subject( url_article() ) ); + + // タブのラベルセット + std::string str_label = str_stat + DBTREE::article_subject( url_article() ); + ARTICLE::get_admin()->set_command( "set_tablabel", get_url(), str_label ); + + std::ostringstream ss_tmp; + ss_tmp << DBTREE::article_str_code( url_article() ) + << " [ Total " << DBTREE::article_number_load( url_article() ) + << " / New " << DBTREE::article_number_new( url_article() ) << " ] " + << str_stat + << " " << DBTREE::article_lng_dat( url_article() )/1024 << " k"; + + set_status( ss_tmp.str() ); + CORE::core_set_command( "set_status", "", get_status() ); + + // 全体再描画 + drawarea()->redraw_view(); + + if( m_gotonum_reserve ) goto_num( m_gotonum_reserve ); + m_gotonum_reserve = 0; +} + + + +// +// 画面を消してレイアウトやりなおし & 再描画 +// +void ArticleViewMain::relayout() +{ +#ifdef _DEBUG + std::cout << "ArticleViewMain::relayout\n"; +#endif + + int seen = drawarea()->seen_current(); + + drawarea()->clear_screen(); + drawarea()->append_res( 1, get_article()->get_number_load() ); + drawarea()->goto_num( seen ); + drawarea()->redraw_view(); +} + + + + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// レス抽出ビュー + + +ArticleViewRes::ArticleViewRes( const std::string& url, + const std::string& num, bool show_title, const std::string& center ) + : ArticleViewBase( url ), + m_str_num( num ), + m_str_center( center ), + m_show_title( show_title ) +{ + struct timeval tv; + struct timezone tz; + gettimeofday( &tv, &tz ); + + // viewのURL更新 + std::string url_tmp = url_article() + ARTICLE_SIGN + RES_SIGN + m_str_num + CENTER_SIGN; + if( !m_str_center.empty() ) url_tmp += m_str_center; + else url_tmp += "0"; + url_tmp += TIME_SIGN + MISC::timevaltostr( tv ); + set_url( url_tmp ); + +#ifdef _DEBUG + std::cout << "ArticleViewRes::ArticleViewRes " << get_url() << std::endl; +#endif + + setup_view(); +} + + + +ArticleViewRes::~ArticleViewRes() +{ + +#ifdef _DEBUG + std::cout << "ArticleViewRes::~ArticleViewRes : " << get_url() << std::endl; +#endif +} + + +// +// 抽出表示 +// +void ArticleViewRes::show_view() +{ + CORE::core_set_command( "switch_article" ); + + show_res( m_str_num, m_show_title ); + + // ラベルとタブ + if( toolbar() ){ + + toolbar()->m_button_board.set_label( "[ " + DBTREE::board_name( url_article() ) + " ]" ); + toolbar()->set_label( " [ RES:" + m_str_num + " ] - " + DBTREE::article_subject( url_article() ) ); + std::string str_label = "[RES] " + DBTREE::article_subject( url_article() ); + ARTICLE::get_admin()->set_command( "set_tablabel", get_url(), str_label ); + } +} + + + +// +// 画面を消してレイアウトやりなおし & 再描画 +// +void ArticleViewRes::relayout() +{ +#ifdef _DEBUG + std::cout << "ArticleViewRes::relayout\n"; +#endif + + drawarea()->clear_screen(); + show_res( m_str_num, m_show_title ); + drawarea()->redraw_view(); +} + + + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// ID抽出ビュー + + +ArticleViewID::ArticleViewID( const std::string& url, const std::string& id ) + : ArticleViewBase( url ), + m_str_id( id ) +{ + struct timeval tv; + struct timezone tz; + gettimeofday( &tv, &tz ); + + // viewのURL更新 + set_url( url_article() + ARTICLE_SIGN + ID_SIGN + m_str_id + TIME_SIGN + MISC::timevaltostr( tv ) ); + +#ifdef _DEBUG + std::cout << "ArticleViewID::ArticleViewID " << get_url() << std::endl; +#endif + + setup_view(); +} + + + +ArticleViewID::~ArticleViewID() +{ + +#ifdef _DEBUG + std::cout << "ArticleViewID::~ArticleViewID : " << get_url() << std::endl; +#endif +} + + +// +// 抽出表示 +// +void ArticleViewID::show_view() +{ + CORE::core_set_command( "switch_article" ); + + show_id( m_str_id ); + + // ラベルとタブ + if( toolbar() ){ + + toolbar()->m_button_board.set_label( "[ " + DBTREE::board_name( url_article() ) + " ]" ); + toolbar()->set_label( " [ ID:" + m_str_id.substr( strlen( PROTO_ID ) ) + " ] - " + + DBTREE::article_subject( url_article() )); + std::string str_label = "[ID] " + DBTREE::article_subject( url_article() ); + ARTICLE::get_admin()->set_command( "set_tablabel", get_url(), str_label ); + } +} + + + +// +// 画面を消してレイアウトやりなおし & 再描画 +// +void ArticleViewID::relayout() +{ +#ifdef _DEBUG + std::cout << "ArticleViewID::relayout\n"; +#endif + + drawarea()->clear_screen(); + show_id( m_str_id ); + drawarea()->redraw_view(); +} + + + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// ブックマーク抽出ビュー + + +ArticleViewBM::ArticleViewBM( const std::string& url ) + : ArticleViewBase( url ) +{ + struct timeval tv; + struct timezone tz; + gettimeofday( &tv, &tz ); + + set_url( url_article() + ARTICLE_SIGN + BOOKMK_SIGN + TIME_SIGN + MISC::timevaltostr( tv ) ); + +#ifdef _DEBUG + std::cout << "ArticleViewBM::ArticleViewBM " << get_url() << std::endl; +#endif + + setup_view(); +} + + + +ArticleViewBM::~ArticleViewBM() +{ + +#ifdef _DEBUG + std::cout << "ArticleViewBM::~ArticleViewBM : " << get_url() << std::endl; +#endif +} + + + +// +// 抽出表示 +// +void ArticleViewBM::show_view() +{ + CORE::core_set_command( "switch_article" ); + + show_bm(); + + // ラベルとタブ + if( toolbar() ){ + + toolbar()->m_button_board.set_label( "[ " + DBTREE::board_name( url_article() ) + " ]" ); + toolbar()->set_label( " [ BM ] - " + DBTREE::article_subject( url_article() )); + std::string str_label = "[BM] " + DBTREE::article_subject( url_article() ); + ARTICLE::get_admin()->set_command( "set_tablabel", get_url(), str_label ); + } +} + + + +// +// 画面を消してレイアウトやりなおし & 再描画 +// +void ArticleViewBM::relayout() +{ +#ifdef _DEBUG + std::cout << "ArticleViewBM::relayout\n"; +#endif + + drawarea()->clear_screen(); + show_bm(); + drawarea()->redraw_view(); +} + + + + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// URL抽出ビュー + +ArticleViewURL::ArticleViewURL( const std::string& url ) + : ArticleViewBase( url ) +{ + struct timeval tv; + struct timezone tz; + gettimeofday( &tv, &tz ); + + // viewのURL更新 + set_url( url_article() + ARTICLE_SIGN + URL_SIGN + TIME_SIGN + MISC::timevaltostr( tv ) ); + +#ifdef _DEBUG + std::cout << "ArticleViewURL::ArticleViewURL " << get_url() << std::endl; +#endif + + setup_view(); +} + + + +ArticleViewURL::~ArticleViewURL() +{ + +#ifdef _DEBUG + std::cout << "ArticleViewURL::~ArticleViewURL : " << get_url() << std::endl; +#endif +} + + +// +// 抽出表示 +// +void ArticleViewURL::show_view() +{ + CORE::core_set_command( "switch_article" ); + + show_res_with_url(); + + // ラベルとタブ + if( toolbar() ){ + + toolbar()->m_button_board.set_label( "[ " + DBTREE::board_name( url_article() ) + " ]" ); + toolbar()->set_label( " [ URL ] - " + DBTREE::article_subject( url_article() )); + std::string str_label = "[URL] " + DBTREE::article_subject( url_article() ); + ARTICLE::get_admin()->set_command( "set_tablabel", get_url(), str_label ); + } +} + + +// +// 画面を消してレイアウトやりなおし & 再描画 +// +void ArticleViewURL::relayout() +{ +#ifdef _DEBUG + std::cout << "ArticleViewURL::relayout\n"; +#endif + + drawarea()->clear_screen(); + show_res_with_url(); + drawarea()->redraw_view(); +} + + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// 参照抽出ビュー + + +ArticleViewRefer::ArticleViewRefer( const std::string& url, const std::string& num ) + : ArticleViewBase( url ), + m_str_num( num ) +{ + struct timeval tv; + struct timezone tz; + gettimeofday( &tv, &tz ); + + set_url( url_article() + ARTICLE_SIGN + REFER_SIGN + m_str_num + TIME_SIGN + MISC::timevaltostr( tv ) ); + +#ifdef _DEBUG + std::cout << "ArticleViewRefer::ArticleViewRefer " << get_url() << std::endl; +#endif + + setup_view(); +} + + + +ArticleViewRefer::~ArticleViewRefer() +{ + +#ifdef _DEBUG + std::cout << "ArticleViewRefer::~ArticleViewRefer : " << get_url() << std::endl; +#endif +} + + +// +// 抽出表示 +// +void ArticleViewRefer::show_view() +{ + CORE::core_set_command( "switch_article" ); + + show_refer( atol( m_str_num.c_str() ) ); + + // ラベルとタブ + if( toolbar() ){ + + toolbar()->m_button_board.set_label( "[ " + DBTREE::board_name( url_article() ) + " ]" ); + toolbar()->set_label( " [ Re:" + m_str_num + " ] - " + DBTREE::article_subject( url_article() )); + std::string str_label = "[Re] " + DBTREE::article_subject( url_article() ); + ARTICLE::get_admin()->set_command( "set_tablabel", get_url(), str_label ); + } +} + + +// +// 画面を消してレイアウトやりなおし & 再描画 +// +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(); +} + + + + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// キーワード抽出ビュー + + +ArticleViewDrawout::ArticleViewDrawout( const std::string& url, const std::string& query, bool mode_or ) + : ArticleViewBase( url ), m_query( query ), m_mode_or( mode_or ) +{ + struct timeval tv; + struct timezone tz; + gettimeofday( &tv, &tz ); + + //viewのURL更新 + std::string url_tmp = url_article() + ARTICLE_SIGN + KEYWORD_SIGN + m_query + ORMODE_SIGN; + if( mode_or ) url_tmp += "1"; + else url_tmp += "0"; + url_tmp += TIME_SIGN + MISC::timevaltostr( tv ); + set_url( url_tmp ); + +#ifdef _DEBUG + std::cout << "ArticleViewDrawout::ArticleViewDrawout " << get_url() << std::endl; +#endif + + setup_view(); +} + + + +ArticleViewDrawout::~ArticleViewDrawout() +{ + +#ifdef _DEBUG + std::cout << "ArticleViewDrawout::~ArticleViewDrawout : " << get_url() << std::endl; +#endif +} + + +// +// 抽出表示 +// +void ArticleViewDrawout::show_view() +{ + CORE::core_set_command( "switch_article" ); + + drawout_keywords( m_query, m_mode_or ); + + // ラベルとタブ + if( toolbar() ){ + + toolbar()->m_button_board.set_label( "[ " + DBTREE::board_name( url_article() ) + " ]" ); + toolbar()->set_label( DBTREE::article_subject( url_article() ) ); + + std::string str_label; + if( m_mode_or ) str_label = "[OR] " + DBTREE::article_subject( url_article() ); + else str_label = "[AND] " + DBTREE::article_subject( url_article() ); + ARTICLE::get_admin()->set_command( "set_tablabel", get_url(), str_label ); + + slot_push_open_search(); + toolbar()->m_entry_search.set_text( m_query ); + } +} + + + +// +// 画面を消してレイアウトやりなおし & 再描画 +// +void ArticleViewDrawout::relayout() +{ +#ifdef _DEBUG + std::cout << "ArticleViewDrawout::relayout\n"; +#endif + + drawarea()->clear_screen(); + drawout_keywords( m_query, m_mode_or ); + drawarea()->redraw_view(); +} + + + diff --git a/src/article/articleview.h b/src/article/articleview.h new file mode 100644 index 000000000..1f9d6db59 --- /dev/null +++ b/src/article/articleview.h @@ -0,0 +1,149 @@ +// ライセンス: 最新のGPL + +// +// メインビューなどのタブに張り付けるビュー +// + +#ifndef _ARTICLEVIEW_H +#define _ARTICLEVIEW_H + +#include "articleviewbase.h" + +namespace ARTICLE +{ + // メインビュー + class ArticleViewMain : public ArticleViewBase + { + // ジャンプ予約, goto_num() のコメント参照 + int m_gotonum_reserve; + + public: + ArticleViewMain( const std::string& url ); + ~ArticleViewMain(); + + virtual void goto_num( int num ); + + // SKELETON::View の関数のオーバロード + virtual void reload(); + virtual void show_view(); + virtual void update_view(); + virtual void update_finish(); + virtual void relayout(); + }; + + + ///////////////////////////////////////////////////////////////////////// + + // レス抽出ビュー + class ArticleViewRes : public ArticleViewBase + { + std::string m_str_num; + std::string m_str_center; + bool m_show_title; + + public: + ArticleViewRes( const std::string& url, const std::string& num, bool show_title, const std::string& center ); + ~ArticleViewRes(); + + // SKELETON::View の関数のオーバロード + virtual void show_view(); + virtual void relayout(); + }; + + + + ///////////////////////////////////////////////////////////////////////// + + + // ID 抽出ビュー + class ArticleViewID : public ArticleViewBase + { + std::string m_str_id; + + public: + ArticleViewID( const std::string& url, const std::string& id ); + ~ArticleViewID(); + + // SKELETON::View の関数のオーバロード + virtual void show_view(); + virtual void relayout(); + }; + + + ///////////////////////////////////////////////////////////////////////// + + + // ブックマーク抽出ビュー + class ArticleViewBM : public ArticleViewBase + { + std::string m_str_id; + + public: + ArticleViewBM( const std::string& url ); + ~ArticleViewBM(); + + // SKELETON::View の関数のオーバロード + virtual void show_view(); + virtual void relayout(); + }; + + + + ///////////////////////////////////////////////////////////////////////// + + + // URL抽出ビュー + class ArticleViewURL : public ArticleViewBase + { + public: + ArticleViewURL( const std::string& url ); + ~ArticleViewURL(); + + // SKELETON::View の関数のオーバロード + virtual void show_view(); + virtual void relayout(); + }; + + + ///////////////////////////////////////////////////////////////////////// + + + // 参照抽出ビュー + class ArticleViewRefer : public ArticleViewBase + { + std::string m_str_num; + + public: + ArticleViewRefer( const std::string& url, const std::string& num ); + ~ArticleViewRefer(); + + // SKELETON::View の関数のオーバロード + virtual void show_view(); + virtual void relayout(); + }; + + + + ///////////////////////////////////////////////////////////////////////// + + + // キーワード抽出ビュー + class ArticleViewDrawout : public ArticleViewBase + { + std::string m_query; + bool m_mode_or; + + public: + ArticleViewDrawout( const std::string& url, const std::string& query, bool mode_or ); + ~ArticleViewDrawout(); + + // SKELETON::View の関数のオーバロード + virtual void show_view(); + virtual void relayout(); + }; + +} + + + +#endif diff --git a/src/article/articleviewbase.cpp b/src/article/articleviewbase.cpp new file mode 100644 index 000000000..000d5db6e --- /dev/null +++ b/src/article/articleviewbase.cpp @@ -0,0 +1,2178 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "articleadmin.h" +#include "articleviewbase.h" +#include "articleview.h" +#include "drawareamain.h" +#include "toolbar.h" +#include "preference.h" + +#include "jdlib/miscutil.h" +#include "jdlib/jdregex.h" + +#include "dbtree/articlebase.h" +#include "dbtree/interface.h" + +#include "dbimg/imginterface.h" + +#include "skeleton/popupwin.h" + +#include "config/globalconf.h" + +#include "global.h" +#include "httpcode.h" +#include "command.h" +#include "session.h" +#include "viewfactory.h" +#include "sharedbuffer.h" +#include "cache.h" + +#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; + + + +ArticleViewBase::ArticleViewBase( const std::string& url ) + : SKELETON::View( url ), + m_url_article( url ), + m_toolbar( 0 ), + m_popup_win( 0 ), + m_popup_shown( 0 ), + m_number_popup_shown( 0 ), + m_current_bm( 0 ) +{ + clear(); + + // マウスジェスチャ可能 + SKELETON::View::set_enable_mg( true ); + + // コントロールモード設定 + SKELETON::View::get_control().set_mode( CONTROL::MODE_ARTICLE ); +} + + + +ArticleViewBase::~ArticleViewBase() +{ +#ifdef _DEBUG + std::cout << "ArticleViewBase::~ArticleViewBase : " << get_url() << std::endl; +#endif + + clear(); +} + + + +// +// コピー用URL( readcgi型 ) +// +// メインウィンドウのURLバーなどに表示する) +// +const std::string ArticleViewBase::url_for_copy() +{ + return DBTREE::url_readcgi( m_url_article, 0, 0 ); +} + + +JDLIB::RefPtr_Lock< DBTREE::ArticleBase >& ArticleViewBase::get_article() +{ + assert( m_article ); + return m_article; +} + + +DrawAreaBase* ArticleViewBase::drawarea() +{ + assert( m_drawarea ); + return m_drawarea; +} + + +DrawAreaBase* ArticleViewBase::create_drawarea() +{ + return Gtk::manage( new ARTICLE::DrawAreaMain( m_url_article ) ); +} + + +// +// メンバ変数初期化 +// +void ArticleViewBase::clear() +{ +#ifdef _DEBUG + std::cout << "ArticleViewBase::clear " << get_url() << std::endl; +#endif + + hide_popup( true ); + delete_popup(); + + m_popupmenu_shown = false; +} + + + +// +// セットアップ +// +// 各派生ビューで初期設定が済んだ後に呼ばれる +// +void ArticleViewBase::setup_view() +{ +#ifdef _DEBUG + std::cout << "ArticleViewBase::setup_view " << get_url() << " url_article = " << m_url_article << std::endl; +#endif + + clear(); + + m_article = DBTREE::get_article( m_url_article ); + m_drawarea = create_drawarea(); + assert( m_article ); + assert( m_drawarea ); + + m_drawarea->sig_leave_notify().connect( sigc::mem_fun( *this, &ArticleViewBase::slot_leave_drawarea ) ); + m_drawarea->sig_button_press().connect( sigc::mem_fun( *this, &ArticleViewBase::slot_button_press_drawarea )); + m_drawarea->sig_button_release().connect( sigc::mem_fun( *this, &ArticleViewBase::slot_button_release_drawarea )); + m_drawarea->sig_scroll_event().connect( sigc::mem_fun( *this, &ArticleViewBase::slot_scroll_drawarea )); + m_drawarea->sig_motion_notify().connect( sigc::mem_fun( *this, &ArticleViewBase::slot_motion_notify_drawarea ) ); + m_drawarea->sig_key_press().connect( sigc::mem_fun(*this, &ArticleViewBase::slot_key_press_drawarea ) ); + m_drawarea->sig_key_release().connect( sigc::mem_fun(*this, &ArticleViewBase::slot_key_release_drawarea ) ); + 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_widget(); + setup_action(); + + show_all_children(); +} + + +// +// ツールバーやスクロールバーのパッキング +// +void ArticleViewBase::pack_widget() +{ + // ツールバーの設定 + m_toolbar = Gtk::manage( new ArticleToolBar() ); + m_toolbar->m_button_close.signal_clicked().connect( sigc::mem_fun(*this, &ArticleViewBase::close_view ) ); + m_toolbar->m_button_reload.signal_clicked().connect( sigc::mem_fun(*this, &ArticleViewBase::reload ) ); + m_toolbar->m_button_write.signal_clicked().connect( sigc::mem_fun(*this, &ArticleViewBase::slot_push_write ) ); + m_toolbar->m_button_delete.signal_clicked().connect( sigc::mem_fun(*this, &ArticleViewBase::slot_push_delete ) ); + m_toolbar->m_button_board.signal_clicked().connect( sigc::mem_fun(*this, &ArticleViewBase::slot_push_open_board ) ); + m_toolbar->m_button_favorite.signal_clicked().connect( sigc::mem_fun(*this, &ArticleViewBase::slot_favorite ) ); + m_toolbar->m_button_stop.signal_clicked().connect( sigc::mem_fun(*this, &ArticleViewBase::stop ) ); + m_toolbar->m_button_preferences.signal_clicked().connect( sigc::mem_fun(*this, &ArticleViewBase::slot_push_preferences ) ); + m_toolbar->m_button_open_search.signal_clicked().connect( sigc::mem_fun(*this, &ArticleViewBase::slot_push_open_search ) ); + + // 検索バー + m_toolbar->m_entry_search.signal_activate().connect( sigc::mem_fun( *this, &ArticleViewBase::slot_active_search ) ); + m_toolbar->m_entry_search.signal_operate().connect( sigc::mem_fun( *this, &ArticleViewBase::slot_entry_operate ) ); + m_toolbar->m_button_close_search.signal_clicked().connect( sigc::mem_fun(*this, &ArticleViewBase::slot_push_close_search ) ); + m_toolbar->m_button_up_search.signal_clicked().connect( sigc::mem_fun(*this, &ArticleViewBase::slot_push_up_search ) ); + m_toolbar->m_button_down_search.signal_clicked().connect( sigc::mem_fun(*this, &ArticleViewBase::slot_push_down_search ) ); + m_toolbar->m_button_drawout_and.signal_clicked().connect( sigc::mem_fun(*this, &ArticleViewBase::slot_push_drawout_and ) ); + m_toolbar->m_button_drawout_or.signal_clicked().connect( sigc::mem_fun(*this, &ArticleViewBase::slot_push_drawout_or ) ); + m_toolbar->m_button_clear_hl.signal_clicked().connect( sigc::mem_fun(*this, &ArticleViewBase::slot_push_claar_hl ) ); + + pack_start( *m_toolbar, Gtk::PACK_SHRINK, 2 ); + pack_start( *m_drawarea, Gtk::PACK_EXPAND_WIDGET, 2 ); +} + + + + +// +// アクション初期化 +// +void ArticleViewBase::setup_action() +{ + // アクショングループを作ってUIマネージャに登録 + action_group() = Gtk::ActionGroup::create(); + action_group()->add( Gtk::Action::create( "BookMark", "ブックマーク設定/解除"), sigc::mem_fun( *this, &ArticleViewBase::slot_bookmark ) ); + action_group()->add( Gtk::Action::create( "OpenBrowser", "ブラウザで開く"), sigc::mem_fun( *this, &ArticleViewBase::slot_open_browser ) ); + action_group()->add( Gtk::Action::create( "Google", "googleで検索"), sigc::mem_fun( *this, &ArticleViewBase::slot_search_google ) ); + action_group()->add( Gtk::Action::create( "CopyURL", "URLをコピー"), sigc::mem_fun( *this, &ArticleViewBase::slot_copy_current_url ) ); + action_group()->add( Gtk::Action::create( "CopyID", "IDコピー"), 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", "レスする" ),sigc::mem_fun( *this, &ArticleViewBase::slot_write_res ) ); + action_group()->add( Gtk::Action::create( "QuoteRes", "参照レスする"),sigc::mem_fun( *this, &ArticleViewBase::slot_quote_res ) ); + action_group()->add( Gtk::Action::create( "CopyRes", "レスをコピー"), + sigc::bind< bool >( sigc::mem_fun( *this, &ArticleViewBase::slot_copy_res ), false ) ); + action_group()->add( Gtk::Action::create( "CopyResRef", "参照コピー"), + sigc::bind< bool >( sigc::mem_fun( *this, &ArticleViewBase::slot_copy_res ), true ) ); + action_group()->add( Gtk::Action::create( "Delete", "削除する"), sigc::mem_fun( *this, &ArticleViewBase::delete_view ) ); + action_group()->add( Gtk::Action::create( "Favorite", "お気に入りに登録する"), sigc::mem_fun( *this, &ArticleViewBase::slot_favorite ) ); + + // 抽出系 + action_group()->add( Gtk::Action::create( "Drawout_Menu", "抽出" ) ); + action_group()->add( Gtk::Action::create( "DrawoutWord", "キーワード抽出"), sigc::mem_fun( *this, &ArticleViewBase::slot_drawout_selection_str ) ); + action_group()->add( Gtk::Action::create( "DrawoutRes", "レス抽出"), sigc::mem_fun( *this, &ArticleViewBase::slot_drawout_res ) ); + action_group()->add( Gtk::Action::create( "DrawoutID", "ID抽出"), sigc::mem_fun( *this, &ArticleViewBase::slot_drawout_id ) ); + action_group()->add( Gtk::Action::create( "DrawoutBM", "ブックマーク抽出"), sigc::mem_fun( *this, &ArticleViewBase::slot_drawout_bm ) ); + action_group()->add( Gtk::Action::create( "DrawoutURL", "URL抽出"), sigc::mem_fun( *this, &ArticleViewBase::slot_drawout_url ) ); + action_group()->add( Gtk::Action::create( "DrawoutRefer", "参照抽出"), sigc::mem_fun( *this, &ArticleViewBase::slot_drawout_refer ) ); + action_group()->add( Gtk::Action::create( "DrawoutAround", "周辺抽出"), sigc::mem_fun( *this, &ArticleViewBase::slot_drawout_around ) ); + action_group()->add( Gtk::Action::create( "DrawoutTmp", "テンプレート抽出"), sigc::mem_fun( *this, &ArticleViewBase::slot_drawout_tmp ) ); + + // あぼーん系 + action_group()->add( Gtk::Action::create( "AboneID", "IDあぼ〜ん"), sigc::mem_fun( *this, &ArticleViewBase::slot_abone_id ) ); + action_group()->add( Gtk::Action::create( "AboneName", "名前あぼ〜ん"), sigc::mem_fun( *this, &ArticleViewBase::slot_abone_name ) ); + + // 移動系 + action_group()->add( Gtk::Action::create( "Move_Menu", "移動" ) ); + 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( "Jump", "ジャンプ"), sigc::mem_fun( *this, &ArticleViewBase::slot_jump ) ); + + // 画像系 + action_group()->add( Gtk::Action::create( "Cancel_Mosaic", "モザイク解除"), sigc::mem_fun( *this, &ArticleViewBase::slot_cancel_mosaic ) ); + action_group()->add( Gtk::ToggleAction::create( "ProtectImage", "保護する", std::string(), false ), + sigc::mem_fun( *this, &ArticleViewBase::slot_toggle_protectimage ) ); + action_group()->add( Gtk::Action::create( "Delete_Menu", "削除" ) ); + action_group()->add( Gtk::Action::create( "DeleteImage", "削除する"), sigc::mem_fun( *this, &ArticleViewBase::slot_deleteimage ) ); + action_group()->add( Gtk::Action::create( "SaveImage", "保存"), sigc::mem_fun( *this, &ArticleViewBase::slot_saveimage ) ); + + + ui_manager() = Gtk::UIManager::create(); + ui_manager()->insert_action_group( action_group() ); + + // レイアウト + Glib::ustring str_ui = + "" + + // 削除ボタン押したときのポップアップ + "" + "" + "" + + // レス番号をクリックしたときのメニュー + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + + // レスアンカーをクリックしたときのメニュー + "" + "" + "" + "" + "" + + // IDをクリックしたときのメニュー + "" + "" + "" + "" + "" + "" + + + // 通常の右クリックメニュー + "" + + "" + "" + "" + "" + "" + "" + + "" + "" + "" + "" + "" + "" + "" + + "" + "" + "" + + "" + "" + "" + + "" + + // 画像メニュー + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + + ""; + + ui_manager()->add_ui_from_string( str_ui ); + + // ポップアップメニューにショートカットキーやマウスジェスチャを表示 + Gtk::Menu* popupmenu = dynamic_cast< Gtk::Menu* >( ui_manager()->get_widget( "/popup_menu" ) ); + CONTROL::set_menu_motion( popupmenu ); +} + + + + +// +// drawarea 上にマウスポインタがあったら true +// +bool ArticleViewBase::is_mouse_on_drawarea() +{ + return m_drawarea->is_mouse_on_drawarea(); +} + + +// +// クライアント領域幅 +// +const int ArticleViewBase::width_client() +{ +#ifdef _DEBUG + if( m_drawarea ) std::cout << "ArticleViewBase::width_client : " << m_drawarea->width_client() << std::endl; +#endif + + if( m_drawarea ) return m_drawarea->width_client(); + + return SKELETON::View::width_client(); +} + + +// +// クライアント領高さ +// +const int ArticleViewBase::height_client() +{ +#ifdef _DEBUG + if( m_drawarea ) std::cout << "ArticleViewBase::height_client : " << m_drawarea->height_client() << std::endl; +#endif + + if( m_drawarea ) return m_drawarea->height_client(); + + return SKELETON::View::height_client(); +} + + +// +// コマンド +// +bool ArticleViewBase::set_command( const std::string& command, const std::string& arg ) +{ +#ifdef _DEBUG + std::cout << "ArticleViewBase::set_command " << get_url() << std::endl + << "command = " << command << std::endl; +#endif + + if( command == "append_dat" ) append_dat( arg ); + else if( command == "goto_num" ) goto_num( atoi( arg.c_str() ) ); + else if( command == "delete_popup" ) delete_popup(); + + return true; +} + + +// +// クロック入力 +// +// クロックタイマーの本体はコアが持っていて、定期的にadminがアクティブなviewにクロック入力を渡す +// +void ArticleViewBase::clock_in() +{ + assert( m_drawarea ); + + // ポップアップが出てたらそっちにクロックを回す + if( is_popup_shown() && m_popup_win->view() ){ + m_popup_win->view()->clock_in(); + return; + } + + m_drawarea->clock_in(); + + return; +} + + + +// +// 再読み込み +// +void ArticleViewBase::reload() +{ + // DAT落ちしてるとロードしないので状態をリセットしておく + DBTREE::article_reset_status( m_url_article ); + CORE::core_set_command( "open_article", m_url_article , "true" ); +} + + +// +// ロード停止 +// +void ArticleViewBase::stop() +{ + assert( m_article ); + m_article->stop_load(); +} + + +// +// 再描画 +// +void ArticleViewBase::redraw_view() +{ + assert( m_drawarea ); + m_drawarea->redraw_view(); +} + +// +// フォーカスイン +// +void ArticleViewBase::focus_view() +{ + assert( m_drawarea ); + +#ifdef _DEBUG + std::cout << "ArticleViewBase::focus_view\n"; +#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_mouse_on_popup() ) return; + + hide_popup(); +} + + +// +// 閉じる +// +void ArticleViewBase::close_view() +{ + if( m_article->is_loading() ){ + Gtk::MessageDialog mdiag( "読み込み中です" ); + mdiag.run(); + return; + } + + ARTICLE::get_admin()->set_command( "close_currentview" ); +} + + +// +// 記事削除 +// +void ArticleViewBase::delete_view() +{ + CORE::core_set_command( "delete_article", m_url_article ); +} + + + +// +// viewの操作 +// +void ArticleViewBase::operate_view( const int& control ) +{ + assert( m_drawarea ); + + if( control == CONTROL::None ) return; + + // スクロール系操作 + if( m_drawarea->set_scroll( control ) ) return; + +#ifdef _DEBUG + std::cout << "ArticleViewBase::operate_view control = " << control << std::endl; +#endif + + // その他の処理 + switch( control ){ + + // リロード + case CONTROL::Reload: + reload(); + break; + + // コピー + case CONTROL::Copy: + slot_copy_selection_str(); + break; + + // 検索 + case CONTROL::Search: + open_searchbar( false ); + break; + + case CONTROL::SearchInvert: + open_searchbar( true ); + break; + + case CONTROL::SearchNext: + slot_push_down_search(); + break; + + case CONTROL::SearchPrev: + slot_push_up_search(); + break; + + // 閉じる + case CONTROL::Quit: + ARTICLE::get_admin()->set_command( "close_currentview" ); + break; + + // 書き込み + case CONTROL::WriteMessage: + slot_push_write(); + break; + + // 削除 + case CONTROL::Delete: + { + Gtk::MessageDialog mdiag( "ログを削除しますか?", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_OK_CANCEL ); + if( mdiag.run() != Gtk::RESPONSE_OK ) return; + delete_view(); + break; + } + + // Board に切り替え + case CONTROL::Left: + CORE::core_set_command( "switch_board" ); + break; + + case CONTROL::ToggleArticle: + CORE::core_set_command( "toggle_article" ); + break; + + // image に切り替え + case CONTROL::Right: + CORE::core_set_command( "switch_image" ); + break; + + case CONTROL::TabLeft: + ARTICLE::get_admin()->set_command( "tab_left" ); + break; + + case CONTROL::TabRight: + ARTICLE::get_admin()->set_command( "tab_right" ); + break; + + case CONTROL::PreBookMark: + slot_pre_bm(); + break; + + case CONTROL::NextBookMark: + slot_next_bm(); + break; + + // 親にhideを依頼する and ローディング停止 + case CONTROL::StopLoading: + stop(); + sig_hide_popup().emit(); + break; + } +} + + +// +// 一番上へ +// +void ArticleViewBase::goto_top() +{ + assert( m_drawarea ); + m_drawarea->goto_top(); +} + + + +// +// 一番下へ +// +void ArticleViewBase::goto_bottom() +{ + assert( m_drawarea ); + m_drawarea->goto_bottom(); +} + + + +// +// num番へジャンプ +// +void ArticleViewBase::goto_num( int num ) +{ + assert( m_drawarea ); + m_drawarea->goto_num( num ); +} + + + +// +// 新着に移動 +// +void ArticleViewBase::goto_new() +{ + assert( m_drawarea ); + m_drawarea->goto_new(); +} + + + +// +// 検索バーを開く +// +void ArticleViewBase::open_searchbar( bool invert ) +{ + if( m_toolbar ){ + m_toolbar->show_searchbar(); + m_search_invert = invert; + m_toolbar->m_entry_search.grab_focus(); + } +} + + + +// +// 検索バーを開くボタンを押した +// +void ArticleViewBase::slot_push_open_search() +{ + if( ! m_toolbar->m_searchbar_shown ) open_searchbar( false ); + else slot_push_close_search(); +} + + + +// +// 検索バーを隠すボタンを押した +// +void ArticleViewBase::slot_push_close_search() +{ + if( m_toolbar ){ + m_toolbar->hide_searchbar(); + m_drawarea->focus_view(); + } +} + + +// +// 前を検索 +// +void ArticleViewBase::slot_push_up_search() +{ + m_search_invert = true; + slot_active_search(); + m_drawarea->redraw_view(); +} + + + +// +// 次を検索 +// +void ArticleViewBase::slot_push_down_search() +{ + m_search_invert = false; + slot_active_search(); + m_drawarea->redraw_view(); +} + + + +// +// 別のタブを開いてキーワード抽出 (AND) +// +void ArticleViewBase::slot_push_drawout_and() +{ + std::string query = m_toolbar->m_entry_search.get_text(); + if( query.empty() ) return; + + CORE::core_set_command( "open_article_keyword" ,m_url_article, query, "false" ); +} + + +// +// 別のタブを開いてキーワード抽出 (OR) +// +void ArticleViewBase::slot_push_drawout_or() +{ + std::string query = m_toolbar->m_entry_search.get_text(); + if( query.empty() ) return; + + CORE::core_set_command( "open_article_keyword" ,m_url_article, query, "true" ); +} + + +// +// ハイライト解除 +// +void ArticleViewBase::slot_push_claar_hl() +{ + assert( m_drawarea ); + + m_query = std::string(); + m_drawarea->clear_highlight(); +} + + + +// +// メッセージ書き込みボタン +// +void ArticleViewBase::slot_push_write() +{ + CORE::core_set_command( "open_message" ,m_url_article, std::string() ); +} + + + +// +// 削除ボタン +// +void ArticleViewBase::slot_push_delete() +{ + Gtk::Menu* popupmenu = dynamic_cast< Gtk::Menu* >( ui_manager()->get_widget( "/popup_menu_delete" ) ); + if( popupmenu ) { + popupmenu->signal_hide().connect( sigc::mem_fun( *this, &ArticleViewBase::slot_hide_popupmenu ) ); + popupmenu->popup( 0, gtk_get_current_event_time() ); + m_popupmenu_shown = true; + } +} + + +// +// 板を開くボタン +// +void ArticleViewBase::slot_push_open_board() +{ + CORE::core_set_command( "open_board", DBTREE::url_subject( m_url_article ), "true" ); +} + + + + + +// +// 設定ボタン +// +void ArticleViewBase::slot_push_preferences() +{ +#ifdef _DEBUG + std::cout << "ArticleViewBase::slot_push_preference\n"; +#endif + + Preferences pref( m_url_article ); + if( pref.run() == Gtk::RESPONSE_OK ){ + + // 再レイアウト + ARTICLE::get_admin()->set_command( "relayout_views", m_url_article ); + } +} + + + + +// +// 前のブックマークに移動 +// +void ArticleViewBase::slot_pre_bm() +{ + assert( m_article ); + + if( m_current_bm == 0 ) m_current_bm = m_drawarea->seen_current(); + + for( int i = m_current_bm -1 ; i >= 1 ; --i ){ + if( m_article->is_bookmarked( i ) ){ + goto_num( i ); + 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 ); + m_current_bm = i; + return; + } + } +} + + + +// +// 後ろのブックマークに移動 +// +void ArticleViewBase::slot_next_bm() +{ + assert( m_article ); + + if( m_current_bm == 0 ) m_current_bm = m_drawarea->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 ); + m_current_bm = i; + return; + } + } + + for( int i = 1; i < m_current_bm ; ++i ){ + if( m_article->is_bookmarked( i ) ){ + goto_num( i ); + m_current_bm = i; + return; + } + } +} + + + +// +// ジャンプ +// +// 呼び出す前に m_str_num に対象のレス番号を入れておくこと +// +void ArticleViewBase::slot_jump() +{ + goto_num( atoi( m_str_num.c_str() ) ); +} + + + +// +// レスを抽出して表示 +// +// num は "from-to" の形式 (例) 3から10を抽出したいなら "3-10" +// +void ArticleViewBase::show_res( const std::string& num, bool show_title ) +{ + assert( m_article ); + + // 板名、レス名 + 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 ); + } + + int num_from = atol( num.c_str() ); + int num_to = 0; + + if( num_from <= m_article->get_number_load() ){ + + unsigned int i; + if( ( i = num.find( "-" ) ) != std::string::npos ) num_to = atol( num.substr( i +1 ).c_str() ); + num_to = MAX( num_to, num_from ); + + std::list< int > list_resnum; + for( int i2 = num_from; i2 <= num_to ; ++i2 ) list_resnum.push_back( i2 ); + + if( list_resnum.size() ) append_res( list_resnum ); + } + else if( !show_title ) append_html( "未取得レス" ); +} + + + +// +// ID で抽出して表示 +// +void ArticleViewBase::show_id( const std::string& id_name ) +{ + assert( m_article ); + +#ifdef _DEBUG + std::cout << "ArticleViewBase::show_id " << id_name << std::endl; +#endif + + std::list< int > list_resnum; + for( int i = 1; i <= m_article->get_number_load() ; ++i ){ + if( id_name == m_article->get_id_name( i ) ) list_resnum.push_back( i ); + } + + std::ostringstream comment; + comment << "ID:" << id_name.substr( strlen( PROTO_ID ) ) << " " << list_resnum.size() << " 件"; + + append_html( comment.str() ); + 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; + for( int i = 1; i <= m_article->get_number_load() ; ++i ){ + + if( m_article->is_bookmarked( i ) ) list_resnum.push_back( i ); + } + append_res( list_resnum ); +} + + + + +// +// 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(); + append_res( list_resnum ); +} + + + +// +// 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_reference( num ); + list_resnum.push_front( num ); + append_res( list_resnum ); +} + + + + +// +// キーワードで抽出して表示 +// +void ArticleViewBase::drawout_keywords( const std::string& query, bool mode_or ) +{ + assert( m_article ); + + JDLIB::Regex regex; + +#ifdef _DEBUG + std::cout << "ArticleViewBase::drawout_keywords " << query << std::endl; +#endif + if( query.empty() ) return; + + std::list< std::string > list_query; + list_query = MISC::split_line( query ); + + std::list< int > list_resnum; + for( int i = 1; i <= m_article->get_number_load() ; ++i ){ + + bool apnd = true; + if( mode_or ) apnd = false; + std::list< std::string >::iterator it = list_query.begin(); + for( ; it != list_query.end(); ++it ){ + + bool ret = regex.exec( ( *it ), m_article->get_res_str( i ), 0, true ); + +#ifdef _DEBUG + if( ret ) std::cout << i << " : " << ( *it ) << std::endl << regex.str( 0 ) << std::endl; +#endif + + // OR + if( mode_or ){ + + if( ret ){ + apnd = true; + break; + } + } + + // AND + else{ + + if( ! ret ){ + apnd = false; + break; + } + } + } + + if( apnd ) list_resnum.push_back( i ); + } + + std::ostringstream comment; + comment << query << " " << list_resnum.size() << " 件"; + + append_html( comment.str() ); + append_res( list_resnum ); + + // ハイライト + 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 ); + m_drawarea->redraw_view(); +} + + + +// +// dat をappend +// +void ArticleViewBase::append_dat( const std::string& dat, int num ) +{ + assert( m_drawarea ); + m_drawarea->append_dat( dat, num ); + m_drawarea->redraw_view(); +} + + + +// +// リストで指定したレスの表示 +// +void ArticleViewBase::append_res( std::list< int >& list_resnum ) +{ + assert( m_drawarea ); + m_drawarea->append_res( list_resnum ); + m_drawarea->redraw_view(); +} + + + +// +// drawareaから出た +// +bool ArticleViewBase::slot_leave_drawarea( GdkEventCrossing* ev ) +{ + // クリックしたときやホイールを回すと ev->mode に GDK_CROSSING_GRAB + // がセットされてイベントが発生する場合がある + if( ev->mode == GDK_CROSSING_GRAB ) return false; + +#ifdef _DEBUG + std::cout << "ArticleViewBase::slot_leave_drawarea\n"; +#endif + + focus_out(); + + return false; +} + + + +// +// drawarea のクリックイベント +// +bool ArticleViewBase::slot_button_press_drawarea( GdkEventButton* event ) +{ +#ifdef _DEBUG + std::cout << "ArticleViewBase::slot_button_press_drawarea url = " << get_url() << std::endl; +#endif + + // マウスジェスチャ + SKELETON::View::get_control().MG_start( event ); + + return true; +} + + + +// +// drawarea でのマウスボタンのリリースイベント +// +bool ArticleViewBase::slot_button_release_drawarea( std::string url, int res_number, GdkEventButton* event ) +{ + /// マウスジェスチャ + int mg = SKELETON::View::get_control().MG_end( event ); + +#ifdef _DEBUG + std::cout << "ArticleViewBase::slot_button_release_drawarea mg = " << mg << " url = " << get_url() << std::endl; +#endif + + if( event->type == GDK_BUTTON_RELEASE ){ + + if( ! is_mouse_on_popup() ){ + + // マウスジェスチャ + if( mg != CONTROL::None && enable_mg() ) operate_view( mg ); + + else if( ! click_url( url, res_number, event ) ){ + + if( SKELETON::View::get_control().button_alloted( event, CONTROL::PopupmenuButton ) ) show_menu( url ); + } + } + } + + return true; +} + + + +// +// drawarea でマウスが動いた +// +bool ArticleViewBase::slot_motion_notify_drawarea( GdkEventMotion* event ) +{ + /// マウスジェスチャ + SKELETON::View::get_control().MG_motion( event ); + + return true; +} + + + +// +// drawareaのキープレスイベント +// +bool ArticleViewBase::slot_key_press_drawarea( GdkEventKey* event ) +{ +#ifdef _DEBUG + std::cout << "ArticleViewBase::slot_key_press_drawarea\n"; +#endif + + // ポップアップはキーフォーカスを取れないので親からキー入力を送ってやる + ArticleViewBase* popup_article = NULL; + if( is_popup_shown() ) popup_article = dynamic_cast< ArticleViewBase* >( m_popup_win->view() ); + if( popup_article ) return popup_article->slot_key_press_drawarea( event ); + + operate_view( SKELETON::View::get_control().key_press( event ) ); + + return true; +} + + + +// +// drawareaのキーリリースイベント +// +bool ArticleViewBase::slot_key_release_drawarea( GdkEventKey* event ) +{ +#ifdef _DEBUG + std::cout << "ArticleViewBase::slot_key_release_drawarea\n"; +#endif + + // ポップアップはキーフォーカスを取れないのでキー入力を送ってやる + ArticleViewBase* popup_article = NULL; + if( is_popup_shown() ) popup_article = dynamic_cast< ArticleViewBase* >( m_popup_win->view() ); + if( popup_article ) return popup_article->slot_key_release_drawarea( event ); + + return true; +} + + + +// +// drawareaのマウスホイールイベント +// +bool ArticleViewBase::slot_scroll_drawarea( GdkEventScroll* event ) +{ + // ポップアップしているときはそちらにイベントを送ってやる + ArticleViewBase* popup_article = NULL; + if( is_popup_shown() ) popup_article = dynamic_cast< ArticleViewBase* >( m_popup_win->view() ); + if( popup_article ) return popup_article->slot_scroll_drawarea( event ); + + m_drawarea->wheelscroll( event ); + return true; +} + + + +// +// リンクの上にポインタが来た +// +// drawareaのsig_on_url()シグナルとつなぐ +// +void ArticleViewBase::slot_on_url( std::string url, int res_number ) +{ + +#ifdef _DEBUG + std::cout << "ArticleViewBase::slot_on_url " << url << std::endl; +#endif + + CORE::VIEWFACTORY_ARGS args; + SKELETON::View* view_popup = NULL; + + // 画像ポップアップ + if( DBIMG::is_loadable( url ) && ( DBIMG::is_loading( url ) || DBIMG::get_code( url ) != HTTP_ERR ) ){ + +#ifdef _DEBUG + std::cout << "image\n"; +#endif + + view_popup = CORE::ViewFactory( CORE::VIEW_IMAGEPOPUP, url ); + } + + // レスポップアップ + else if( url.find( PROTO_ANCHORE ) == 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.find( PROTO_RES ) == 0 ){ + + args.arg1 = url.substr( strlen( PROTO_RES ) ); + args.arg2 = "false"; // 板名、スレ名非表示 + args.arg3 = "true"; // あぼーんレス表示 + +#ifdef _DEBUG + std::cout << "res = " << args.arg1 << std::endl; +#endif + + if( m_article->abone( atoi( args.arg1.c_str() ) ) ){ + view_popup = CORE::ViewFactory( CORE::VIEW_ARTICLEPOPUPRES, m_url_article, args ); + } + } + + // ID:〜の範囲選択の上にポインタがあるときIDポップアップ + else if( url.find( PROTO_ID ) == std::string::npos ){ + + args.arg1 = PROTO_ID + url.substr( 3 ); + 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 ); + } + } + + // その他のリンク + if( !view_popup ){ + + // dat 又は板の場合 + int num_from, num_to; + std::string url_dat = DBTREE::url_dat( url, num_from, num_to ); + std::string url_subject = DBTREE::url_subject( url ); + +#ifdef _DEBUG + std::cout << "url_dat = " << url_dat << std::endl; + std::cout << "url_subject = " << url_subject << std::endl; +#endif + + // 他スレ + if( ! url_dat.empty() ){ + + num_from = MAX( 1, num_from ); // 最低でも1レス目は表示 + num_to = MAX( num_from, num_to ); + std::stringstream ss_tmp; + ss_tmp << num_from << "-" << num_to; + + args.arg1 = ss_tmp.str(); + args.arg2 = "true"; // 板名、スレ名表示 + args.arg3 = "false"; // あぼーんレス非表示 + + view_popup = CORE::ViewFactory( CORE::VIEW_ARTICLEPOPUPRES, url_dat, args ); + } + + // 板 + else if( ! url_subject.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 ); +} + + + +// +// リンクからマウスが出た +// +void ArticleViewBase::slot_leave_url() +{ +#ifdef _DEBUG + std::cout << "ArticleViewBase::slot_leave_url\n"; +#endif + + hide_popup(); +} + + + +// +// リンクをクリック +// +bool ArticleViewBase::click_url( std::string url, int res_number, GdkEventButton* event ) +{ + assert( m_article ); + if( url.empty() ) return false; + if( !res_number ) return false; + + CONTROL::Control& control = SKELETON::View::get_control(); + +#ifdef _DEBUG + std::cout << "ArticleViewBase::click_url " << url << std::endl; +#endif + + hide_popup(); + + // ID クリック + if( url.find( PROTO_ID ) == 0 ){ + + 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 ); + show_popup( view_popup ); + } + else if( control.button_alloted( event, CONTROL::DrawoutIDButton ) ) slot_drawout_id(); + else if( control.button_alloted( event, CONTROL::PopupmenuIDButton ) ) show_menu( url ); + } + + // BE クリック + else if( url.find( PROTO_BE ) == 0 ){ + + 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_menu( ssurl.str() ); + } + + // アンカーをクリック + else if( url.find( PROTO_ANCHORE ) == 0 ){ + + // ジャンプ先セット + m_str_num = url.substr( strlen( PROTO_ANCHORE ) ); + +#ifdef _DEBUG + std::cout << "anchor num = " << m_str_num << std::endl; +#endif + if( control.button_alloted( event, CONTROL::PopupmenuAncButton ) ) show_menu( url ); + else if( control.button_alloted( event, CONTROL::DrawoutAncButton ) ) slot_drawout_around(); + } + + // レス番号クリック + else if( url.find( PROTO_RES ) == 0 ){ + + m_str_num = url.substr( strlen( PROTO_RES ) ); + m_name = m_article->get_name( atoi( m_str_num.c_str() ) ); + m_url_tmp = DBTREE::url_readcgi( m_url_article, atoi( m_str_num.c_str() ), 0 ); + + if( control.button_alloted( event, CONTROL::PopupmenuResButton ) ) show_menu( url ); + + // ブックマークセット + else if( control.button_alloted( event, CONTROL::BmResButton ) ) slot_bookmark(); + + // 参照ポップアップ表示 + 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 ); + show_popup( view_popup ); + } + } + + // 画像クリック + else if( DBIMG::is_loadable( url ) ){ + + if( control.button_alloted( event, CONTROL::PopupmenuImageButton ) ) show_menu( url ); + + else if( ! DBIMG::is_cached( url ) && ! SESSION::is_online() ){ + Gtk::MessageDialog mdiag( "オフラインです" ); + mdiag.run(); + } + + else{ + + bool top = true; + + // バックで開く + if( control.button_alloted( event, CONTROL::OpenBackImageButton ) ) top = false; + + // キャッシュに無かったらロード + if( ! DBIMG::is_cached( url ) ){ + std::stringstream refurl; + refurl << DBTREE::url_readcgi( m_url_article, res_number, 0 ); + DBIMG::set_refurl( url, refurl.str() ); + + DBIMG::download_img( url ); + hide_popup(); + show_popup( CORE::ViewFactory( CORE::VIEW_IMAGEPOPUP, url ) ); + top = false; + } + + CORE::core_set_command( "open_image", url ); + if( top ) CORE::core_set_command( "switch_image" ); + + m_drawarea->redraw_view(); + } + } + + // ブラウザで開く + else if( control.button_alloted( event, CONTROL::ClickButton ) ) CORE::core_set_command( "open_url", url ); + + else return false; + + return true; +} + + + +// +// ポップアップが表示されているか +// +const bool ArticleViewBase::is_popup_shown() const +{ + return ( m_popup_win && m_popup_shown ); +} + + +// +// ポップアップが表示されていてかつマウスがその上にあるか +// +const bool ArticleViewBase::is_mouse_on_popup() +{ + if( ! is_popup_shown() ) return false; + + ArticleViewBase* popup_article = dynamic_cast< ArticleViewBase* >( m_popup_win->view() ); + if( ! popup_article ) return false; + + return popup_article->is_mouse_on_drawarea(); +} + + + +// +// ポップアップ表示 +// +// view にあらかじめ内容をセットしてから呼ぶこと +// viewは SKELETON::PopupWin のデストラクタで削除される +// +void ArticleViewBase::show_popup( SKELETON::View* view ) +{ + hide_popup(); + if( !view ) return; + + delete_popup(); + + const int mrg = CONFIG::get_margin_popup();; + + m_popup_win = new SKELETON::PopupWin( this, view, mrg ); + 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 ) +{ + slot_hide_popup(); + return true; +} + + +// +// 子 popup windowからhide依頼が来た +// +void ArticleViewBase::slot_hide_popup() +{ + hide_popup(); + + // ポインタがwidgetの外にあったら親に知らせて自分も閉じてもらう + if( ! is_mouse_on_drawarea() ) sig_hide_popup().emit(); +} + + + +// +// popup のhide +// +// force = true ならチェック無しで強制 hide +// +void ArticleViewBase::hide_popup( bool force ) +{ + if( ! is_popup_shown() ) return; + +#ifdef _DEBUG + std::cout << "ArticleViewBase::hide_popup force = " << force << " " << get_url() << std::endl; +#endif + + if( ! force ){ + + // ArticleView をポップアップ表示している場合 + ArticleViewBase* popup_article = NULL; + popup_article = dynamic_cast< ArticleViewBase* >( m_popup_win->view() ); + + if( popup_article ){ + + // 孫のpopupが表示されてたらhideしない + if( popup_article->is_popup_shown() ) return; + + // ポップアップメニューが表示されてたらhideしない + // ( ポップアップメニューがhideしたときにhideする ) + if( popup_article->is_popupmenu_shown() ) return; + +#ifdef _DEBUG + std::cout << "target = " << popup_article->get_url() << std::endl; +#endif + } + } + + m_popup_win->hide(); + m_popup_shown = false; + m_number_popup_shown = false; +} + + + +// +// ポップアップの削除 +// +void ArticleViewBase::delete_popup() +{ +#ifdef _DEBUG + std::cout << "ArticleViewBase::delete_popup " << get_url() << std::endl; +#endif + + if( m_popup_win ) delete m_popup_win; + m_popup_win = NULL; + m_popup_shown = false; + m_number_popup_shown = false; +} + + + + +// +// ポップアップメニュー表示 +// +void ArticleViewBase::show_menu( const std::string& url ) +{ +#ifdef _DEBUG + std::cout << "ArticleViewBase::show_menu " << get_url() << " url = " << url << std::endl; +#endif + + // 子ポップアップが表示されていて、かつポインタがその上だったら表示しない + ArticleViewBase* popup_article = NULL; + if( is_popup_shown() ) popup_article = dynamic_cast< ArticleViewBase* >( m_popup_win->view() ); + if( popup_article && popup_article->is_mouse_on_drawarea() ) 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.find( PROTO_RES ) == 0 ){ + m_url_tmp = DBTREE::url_readcgi( m_url_article, atoi( url.substr( strlen( PROTO_RES ) ).c_str() ), 0 ); + } + + // アンカークリックの場合 + else if( url.find( PROTO_ANCHORE ) == 0 ){ + m_url_tmp = DBTREE::url_readcgi( m_url_article, atoi( url.substr( strlen( PROTO_ANCHORE ) ).c_str() ), 0 ); + } + + else m_url_tmp = url; + } + + + // 範囲選択されてない + std::string str_select = m_drawarea->str_selection(); + 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() ) act->set_sensitive( false ); + else act->set_sensitive( true ); + } + + act = action_group()->get_action( "Google" ); + if( act ){ + if( str_select.empty() ) act->set_sensitive( false ); + else act->set_sensitive( true ); + } + + // ブックマークがセットされていない + act = action_group()->get_action( "DrawoutBM" ); + if( act ){ + if( m_article->get_num_bookmark() ) act->set_sensitive( true ); + else act->set_sensitive( false ); + } + + // 表示 + Gtk::Menu* popupmenu; + + // レス番号ポップアップメニュー + if( url.find( PROTO_RES ) == 0 ){ + popupmenu = dynamic_cast< Gtk::Menu* >( ui_manager()->get_widget( "/popup_menu_res" ) ); + } + + // アンカーポップアップメニュー + else if( url.find( PROTO_ANCHORE ) == 0 ){ + popupmenu = dynamic_cast< Gtk::Menu* >( ui_manager()->get_widget( "/popup_menu_anc" ) ); + } + + // IDポップアップメニュー + else if( url.find( PROTO_ID ) == 0 ){ + popupmenu = dynamic_cast< Gtk::Menu* >( ui_manager()->get_widget( "/popup_menu_id" ) ); + } + + // 画像ポップアップメニュー + else if( DBIMG::is_loadable( url ) ){ + + // モザイク + 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( "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 ); + + // slot_toggle_protectimage() が呼ばれてしまうので trueに戻しておく + DBIMG::set_protect( url, true ); + } + else{ + tact->set_active( false ); + DBIMG::set_protect( url, false ); + } + } + else act->set_sensitive( false ); + } + + // 削除 + act = action_group()->get_action( "DeleteImage" ); + if( act ){ + + if( DBIMG::is_cached( url ) && ! DBIMG::is_protected( url ) ) act->set_sensitive( true ); + else act->set_sensitive( false ); + } + + popupmenu = dynamic_cast< Gtk::Menu* >( ui_manager()->get_widget( "/popup_menu_img" ) ); + } + + // 通常メニュー + else popupmenu = dynamic_cast< Gtk::Menu* >( ui_manager()->get_widget( "/popup_menu" ) ); + + if( popupmenu ) { + popupmenu->signal_hide().connect( sigc::mem_fun( *this, &ArticleViewBase::slot_hide_popupmenu ) ); + popupmenu->popup( 0, gtk_get_current_event_time() ); + m_popupmenu_shown = true; + } + +} + + +// +// ポップアップメニューがhideしたときに呼ばれるslot +// +void ArticleViewBase::slot_hide_popupmenu() +{ + if( ! m_popupmenu_shown ) return; + +#ifdef _DEBUG + std::cout << "ArticleViewBase::slot_hide_popupmenu " << get_url() << std::endl; +#endif + + m_popupmenu_shown = false; + + // もしメニューを消したときにマウスポインタが領域外に + // あったら自分自身をhide + if( ! is_mouse_on_drawarea() ) sig_hide_popup().emit(); +} + + + + +// +// クリップボードに選択文字コピーのメニュー +// +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; + COPYCLIP( m_drawarea->str_selection() ); +} + + + +// +// 選択して抽出 +// +void ArticleViewBase::slot_drawout_selection_str() +{ + if( m_drawarea->str_selection().empty() ) return; + CORE::core_set_command( "open_article_keyword" ,m_url_article, m_drawarea->str_selection(), "false" ); +} + + +// +// google検索 +// +void ArticleViewBase::slot_search_google() +{ + if( m_drawarea->str_selection().empty() ) return; + CORE::core_set_command( "search_google" ,"", m_drawarea->str_selection() ); +} + + + +// +// ブックマーク設定、解除 +// +// 呼び出す前に 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; + m_drawarea->redraw_view(); + ARTICLE::get_admin()->set_command( "redraw_views", m_url_article ); +} + + + +// +// ポップアップメニューでブラウザで開くを選択 +// +void ArticleViewBase::slot_open_browser() +{ + if( m_url_tmp.empty() ) return; + std::string url = m_url_tmp; + + // 画像、かつキャッシュにある場合 + if( DBIMG::is_loadable( url ) && DBIMG::is_cached( url ) ) url = "file://" + CACHE::path_img( url ); + + CORE::core_set_command( "open_url_browser", url ); +} + + + +// +// レスをする +// +// 呼び出す前に m_str_num に対象のレス番号を入れておくこと +// +void ArticleViewBase::slot_write_res() +{ + 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 ) + "\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 + + COPYCLIP( m_url_tmp ); +} + + +// +// IDをコピー +// +// 呼び出す前に m_id_name にIDをセットしておくこと +// +void ArticleViewBase::slot_copy_id() +{ + std::string id = "ID:" + m_id_name.substr( strlen( PROTO_ID ) ); + COPYCLIP( 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 + + COPYCLIP( m_article->get_res_str( atoi( m_str_num.c_str() ), ref ) ); +} + + + + +// +// お気に入り登録 +// +void ArticleViewBase::slot_favorite() +{ + CORE::DATA_INFO info; + info.type = TYPE_THREAD; + info.url = m_url_article;; + info.name = DBTREE::article_subject( m_url_article ); + + CORE::SBUF_clear_info(); + CORE::SBUF_append( 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() ); +} + + + + +// +// 別のタブを開いて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 ); +} + + + +// +// 別のタブを開いて参照抽出 +// +// 呼び出す前に 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 ); +} + + + +// +// IDであぼ〜ん +// +// 呼び出す前に m_id_name にIDをセットしておくこと +// +void ArticleViewBase::slot_abone_id() +{ + DBTREE::add_abone_id( m_url_article, m_id_name ); + + // 再レイアウト + ARTICLE::get_admin()->set_command( "relayout_views", m_url_article ); +} + + + +// +// 名前であぼ〜ん +// +// 呼び出す前に 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_cancel_mosaic() +{ + if( ! DBIMG::is_cached( m_url_tmp ) ) return; + DBIMG::set_mosaic( m_url_tmp, false ); + CORE::core_set_command( "redraw", 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, std::string() ); +} + + +// +// 画像保護 +// +void ArticleViewBase::slot_toggle_protectimage() +{ + DBIMG::set_protect( m_url_tmp , ! DBIMG::is_protected( m_url_tmp ) ); +} + + + +// +// 検索entryでenterを押した +// +void ArticleViewBase::slot_active_search() +{ + focus_view(); + std::string query = m_toolbar->m_entry_search.get_text(); + if( query.empty() ) return; + + std::list< std::string > list_query; + list_query = MISC::split_line( query ); + + if( m_query == query ) m_drawarea->search_move( m_search_invert ); + + else{ + m_query = query; + + if( m_drawarea->search( list_query, m_search_invert ) ) m_drawarea->search_move( m_search_invert ); + } +} + + + +// +// 検索entryの操作 +// +void ArticleViewBase::slot_entry_operate( int controlid ) +{ + if( controlid == CONTROL::Cancel ) focus_view(); + else if( controlid == CONTROL::DrawOutAnd ) slot_push_drawout_and(); +} + diff --git a/src/article/articleviewbase.h b/src/article/articleviewbase.h new file mode 100644 index 000000000..efd4fddad --- /dev/null +++ b/src/article/articleviewbase.h @@ -0,0 +1,227 @@ +// ライセンス: 最新のGPL + +// スレビュークラスの基本クラス + +#ifndef _ARTICLEVIEWBASE_H +#define _ARTICLEVIEWBASE_H + +#include "skeleton/view.h" + +#include "jdlib/refptr_lock.h" + +#include +#include + + +namespace SKELETON +{ + class PopupWin; +} + +namespace DBTREE +{ + class ArticleBase; +} + +namespace ARTICLE +{ + class DrawAreaBase; + class ArticleToolBar; + + class ArticleViewBase : public SKELETON::View + { + // viewに表示するdatファイルのURL ( SKELETON::View::m_url はview自身のURLなのに注意すること ) + std::string m_url_article; + + // 高速化のため直接アクセス + JDLIB::RefPtr_Lock< DBTREE::ArticleBase > m_article; + + // widget + DrawAreaBase* m_drawarea; + ArticleToolBar *m_toolbar; + + // slot呼び出し時にURLのやりとりに使う一時変数 + std::string m_url_tmp; // url + std::string m_str_num; // レス番号 + std::string m_id_name; // ID + std::string m_name; // 名前 + + // ポップアップ + SKELETON::PopupWin* m_popup_win; + bool m_popup_shown; // 表示されているならtrue, falseでもdeleteしない限りは m_popup_win != NULLに注意 + bool m_number_popup_shown; // 数字範囲選択をクリックしたときのポップアップが表示中 + + // 検索用 + bool m_search_invert; // 逆方向検索モード + std::string m_query; // 前回の検索で使ったクエリー + + // ポップアップメニューが表示されている + bool m_popupmenu_shown; + + // ブックマーク移動時の現在の位置(レス番号) + int m_current_bm; + + public: + + ArticleViewBase( const std::string& url ); + virtual ~ArticleViewBase(); + + const std::string& url_article() const { return m_url_article; } + virtual const std::string url_for_copy(); + + // SKELETON::View の関数のオーバロード + virtual const int width_client(); + virtual const int height_client(); + virtual bool set_command( const std::string& command, const std::string& arg = std::string() ); + virtual void clock_in(); + virtual void reload(); + virtual void stop(); + virtual void redraw_view(); + virtual void focus_view(); + virtual void focus_out(); + virtual void close_view(); + virtual void delete_view(); + virtual void operate_view( const int& control ); + virtual void goto_top(); + virtual void goto_bottom(); + + protected: + + DrawAreaBase* drawarea(); + ArticleToolBar* toolbar() { return m_toolbar; } + JDLIB::RefPtr_Lock< DBTREE::ArticleBase >& get_article(); + + // 初期設定 + void setup_view(); + + // ジャンプ + void goto_num( int num ); + + // 新着に移動 + void goto_new(); + + // レスを抽出して表示 + // num は "from-to" の形式 (例) 3から10を抽出したいなら "3-10" + void show_res( const std::string& num, bool show_title ); + + // ID で抽出して表示 + void show_id( const std::string& id_name ); + + // ブックマークを抽出して表示 + void show_bm(); + + // URLを含むレスを抽出して表示 + void show_res_with_url(); + + // num 番のレスを参照してるレスを抽出して表示 + void show_refer( int num ); + + // キーワードで抽出して表示 + // mode_or = true の時は or 検索 + void drawout_keywords( const std::string& query, bool mode_or ); + + // HTML追加 + void append_html( const std::string& html ); + + // dat追加 + virtual void append_dat( const std::string& dat, int num = 0 ); + + // リストで指定したレスを表示 + virtual void append_res( std::list< int >& list_resnum ); + + // ツールバーのボタンを押したときのスロット + void slot_push_close_search(); + void slot_push_write(); + void slot_push_delete(); + void slot_push_open_board(); + void slot_push_preferences(); + void slot_push_open_search(); + void slot_push_up_search(); + void slot_push_down_search(); + void slot_push_drawout_and(); + void slot_push_drawout_or(); + void slot_push_claar_hl(); + + private: + + virtual DrawAreaBase* create_drawarea(); + + void clear(); + virtual void pack_widget(); + void setup_action(); + bool is_mouse_on_drawarea(); + + // drawarea の signal を受け取る slots + bool slot_button_press_drawarea( GdkEventButton* event ); + bool slot_button_release_drawarea( std::string url, int res_number, GdkEventButton* event ); + bool slot_motion_notify_drawarea( GdkEventMotion* event ); + bool slot_key_press_drawarea( GdkEventKey* event ); + bool slot_key_release_drawarea( GdkEventKey* event ); + bool slot_scroll_drawarea( GdkEventScroll* event ); + bool slot_expose_drawarea( GdkEventExpose *event ); + + bool slot_leave_drawarea( GdkEventCrossing* ev ); + + // レスポップアップ関係 + + // ポップアップが表示されているか + const bool is_popup_shown() const; + + // ポップアップが表示されていてかつマウスがその上にあるか + const bool is_mouse_on_popup(); + + void show_popup( SKELETON::View* view ); + bool slot_popup_leave_notify_event( GdkEventCrossing* event ); + void slot_hide_popup(); + void hide_popup( bool force = false ); + void delete_popup(); // ポップアップ強制削除 + + // ポップアップメニュー関係 + const bool is_popupmenu_shown() const { return m_popupmenu_shown; } + virtual void show_menu( const std::string& url ); + void slot_hide_popupmenu(); + void slot_bookmark(); + void slot_open_browser(); + void slot_write_res(); + void slot_quote_res(); + void slot_copy_current_url(); + void slot_copy_id(); + void slot_copy_selection_str(); + void slot_drawout_selection_str(); + void slot_search_google(); + void slot_copy_res( bool ref ); + void slot_favorite(); + virtual void slot_drawout_res(); + virtual void slot_drawout_around(); + virtual void slot_drawout_tmp(); + virtual void slot_drawout_id(); + virtual void slot_drawout_bm(); + virtual void slot_drawout_refer(); + virtual void slot_drawout_url(); + void slot_abone_id(); + void slot_abone_name(); + void slot_pre_bm(); + void slot_next_bm(); + void slot_jump(); + + // リンクの処理 + void slot_on_url( std::string url, int res_number ); + void slot_leave_url(); + bool click_url( std::string url, int res_number, GdkEventButton* event ); + + // 画像ポップアップメニュー用 + void slot_cancel_mosaic(); + void slot_deleteimage(); + void slot_toggle_protectimage(); + void slot_saveimage(); + + // 検索 + void open_searchbar( bool invert ); + void slot_active_search(); + void slot_entry_operate( int controlid ); + }; + +} + + +#endif diff --git a/src/article/articleviewpopup.cpp b/src/article/articleviewpopup.cpp new file mode 100644 index 000000000..f347c2968 --- /dev/null +++ b/src/article/articleviewpopup.cpp @@ -0,0 +1,79 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "articleviewpopup.h" +#include "drawareapopup.h" + +#include "jdlib/misctime.h" + +#include "global.h" + +#include "config/globalconf.h" + +#include + +using namespace ARTICLE; + + +// show_abone == true ならあぼーんされたスレも表示 +ArticleViewPopup::ArticleViewPopup( const std::string& url, bool show_abone ) + : ArticleViewBase( url ), m_show_abone( show_abone ) +{ + struct timeval tv; + struct timezone tz; + gettimeofday( &tv, &tz ); + set_url( url_article() + MISC::timevaltostr( tv ) + "_POPUP_" ); + +#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( "ヒント:マウスの右ボタンを押しながらポインタを移動すると多重ポップアップが可能" ); +} + + +// +// drawareaの作成 +// +DrawAreaBase* ArticleViewPopup::create_drawarea() +{ + if( m_show_abone ) return Gtk::manage( new ARTICLE::DrawAreaPopupShowAbone( url_article() ) ); + + return Gtk::manage( new ARTICLE::DrawAreaPopup( url_article() ) ); +} + + + +// +// ウィジットのパッキング +// +// ArticleViewBase::pack_widget()をオーパロードしてツールバーをパックしない +// +void ArticleViewPopup::pack_widget() +{ + pack_start( *drawarea() ); + show_all_children(); +} diff --git a/src/article/articleviewpopup.h b/src/article/articleviewpopup.h new file mode 100644 index 000000000..a616e30e9 --- /dev/null +++ b/src/article/articleviewpopup.h @@ -0,0 +1,108 @@ +// ライセンス: 最新のGPL + +// +// ポップアップ系ビュー +// + +#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(); + + protected: + void show_instruct_popup(); + + private: + virtual void pack_widget(); + virtual DrawAreaBase* create_drawarea(); + }; + + ///////////////////////////////////////////////////////////////////////// + + + // 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(){} + + virtual void show_view(){ 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(){} + + virtual void show_view(){ + show_instruct_popup(); + show_res( m_str_num, m_show_title ); + } + }; + + + ///////////////////////////////////////////////////////////////////////// + + + // 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(){} + + virtual void show_view(){ + show_instruct_popup(); + show_id( m_str_id ); + } + }; + + + ///////////////////////////////////////////////////////////////////////// + + + // 参照抽出ポップアップ + 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(){} + + virtual void show_view(){ + show_instruct_popup(); + show_refer( atol( m_str_num.c_str() ) ); + } + }; + +} + +#endif diff --git a/src/article/articleviewpreview.cpp b/src/article/articleviewpreview.cpp new file mode 100644 index 000000000..c09a0a192 --- /dev/null +++ b/src/article/articleviewpreview.cpp @@ -0,0 +1,109 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "articleadmin.h" +#include "articleviewpreview.h" +#include "drawareamain.h" + +#include "message/messageadmin.h" + +#include "dbtree/articlebase.h" + +#include "jdlib/misctime.h" + +#include "controlid.h" + +#include + +using namespace ARTICLE; + + +ArticleViewPreview::ArticleViewPreview( const std::string& url ) + : ArticleViewBase( url ) +{ + struct timeval tv; + struct timezone tz; + gettimeofday( &tv, &tz ); + set_url( url_article() + MISC::timevaltostr( tv ) + ARTICLE_SIGN + "_PREV_" ); + +#ifdef _DEBUG + std::cout << "ArticleViewPreview::ArticleViewPreview " << get_url() << std::endl; +#endif + + setup_view(); + + // コントロールモード設定 + get_control().set_mode( CONTROL::MODE_MESSAGE ); +} + + + + +ArticleViewPreview::~ArticleViewPreview() +{ + +#ifdef _DEBUG + std::cout << "ArticleViewPreview::~ArticleViewPreview : " << get_url() << std::endl; +#endif +} + + + +// +// ウィジットのパッキング +// +// ArticleViewBase::pack_widget()をオーパロードしてツールバーをパックしない +// +void ArticleViewPreview::pack_widget() +{ + pack_start( *drawarea() ); +} + + + +// +// viewの操作 +// +void ArticleViewPreview::operate_view( const int& control ) +{ + switch( control ){ + + // 閉じる + case CONTROL::Quit: + case CONTROL::CancelWrite: + MESSAGE::get_admin()->set_command( "close_currentview" ); + break; + + // 書き込み実行 + case CONTROL::ExecWrite: + MESSAGE::get_admin()->set_command( "exec_Write" ); + break; + + case CONTROL::TabLeft: + MESSAGE::get_admin()->set_command( "tab_left" ); + break; + + case CONTROL::TabRight: + MESSAGE::get_admin()->set_command( "tab_right" ); + break; + + default: + ArticleViewBase::operate_view( control ); + break; + } +} + + + +// +// dat をappend +// +// ArticleViewBase::append_dat()をオーパロードしてappendする前に表示を消す +// +void ArticleViewPreview::append_dat( const std::string& dat, int num ) +{ + drawarea()->clear_screen(); + ArticleViewBase::append_dat( dat, get_article()->get_number_load() +1 ); +} diff --git a/src/article/articleviewpreview.h b/src/article/articleviewpreview.h new file mode 100644 index 000000000..3aae88fde --- /dev/null +++ b/src/article/articleviewpreview.h @@ -0,0 +1,33 @@ +// ライセンス: 最新のGPL +// +// 書き込みなどのプレビュー +// + +#ifndef _ARTICLEVIEWPREVIEW_H +#define _ARTICLEVIEWPREVIEW_H + +#include "articleviewbase.h" + +namespace ARTICLE +{ + class ArticleViewPreview : public ArticleViewBase + { + public: + ArticleViewPreview( const std::string& url ); + ~ArticleViewPreview(); + + virtual void operate_view( const int& control ); + virtual void append_dat( const std::string& dat, int num = 0 ); + + private: + virtual void pack_widget(); + + // IDなどをクリックしてもメニューは出さない + virtual void show_menu( const std::string& url ){} + virtual void goto_num( int num ){} + virtual void slot_drawout_id(){} + virtual void slot_drawout_ref(){} + }; +} + +#endif diff --git a/src/article/caret.h b/src/article/caret.h new file mode 100644 index 000000000..06cbcae26 --- /dev/null +++ b/src/article/caret.h @@ -0,0 +1,92 @@ +// ライセンス: 最新のGPL + +// キャレットの座標とかを計算するクラス + +#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() : x( 0 ), y( 0 ), layout( 0 ), byte( 0 ){} + ~CARET_POSITION(){} + + // キャレット座標計算関数 + 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 ){ + + 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 ) { return ! ( *this != caret_pos ); } + + bool operator > ( const CARET_POSITION& caret_pos ){ + + 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 ){ + return ! ( *this > caret_pos ); + } + }; + +} + +#endif diff --git a/src/article/drawareabase.cpp b/src/article/drawareabase.cpp new file mode 100644 index 000000000..c32ed53da --- /dev/null +++ b/src/article/drawareabase.cpp @@ -0,0 +1,2492 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +//#define _DEBUG_CARETMOVE +#include "jddebug.h" + +#include "drawareabase.h" +#include "layouttree.h" +#include "font.h" + +#include "jdlib/miscutil.h" +#include "jdlib/miscmsg.h" +#include "jdlib/jdregex.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 "global.h" +#include "httpcode.h" +#include "controlid.h" + +#include +#include + +using namespace ARTICLE; + +#define AUTOSCR_CIRCLE 24 // オートスクロールの時のサークルの大きさ +#define BIG_WIDTH 100000 +#define BIG_HEIGHT 100000 + +#define SCROLLSPEED_FAST ( m_vscrbar ? m_vscrbar->get_adjustment()->get_page_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()*2 : 0 ) + +#define SEPARATOR_HEIGHT 6 // 新着セパレータの高さ + +////////////////////////////////////////////////////////// + + + +DrawAreaBase::DrawAreaBase( const std::string& url ) + : m_url( url ) + , m_vscrbar( 0 ) + , m_layout_tree( 0 ) + , m_window( 0 ) + , m_gc( 0 ) + , m_backscreen( 0 ) + , m_pango_layout( 0 ) +{ +#ifdef _DEBUG + std::cout << "DrawAreaBase::DrawAreaBase " << m_url << std::endl;; +#endif +} + + + +DrawAreaBase::~DrawAreaBase() +{ +#ifdef _DEBUG + std::cout << "DrawAreaBase::~DrawAreaBase " << m_url << std::endl;; +#endif + + clear(); +} + + +// 背景色( virtual ) +const int* DrawAreaBase::rgb_color_back() +{ + return CONFIG::get_color_back(); +} + + +// フォント +const std::string& DrawAreaBase::fontname() +{ + return CONFIG::get_fontname_main(); + +} + +// フォントモード +const int DrawAreaBase::fontmode() +{ + return FONT_MAIN; +} + + +// +// セットアップ +// +// show_abone : あぼーんされたスレも表示 +// show_scrbar : スクロールバーを最初から表示 +// +void DrawAreaBase::setup( bool show_abone, bool show_scrbar ) +{ + clear(); + + m_article = DBTREE::get_article( m_url ); + m_layout_tree = new LayoutTree( m_url, show_abone ); + + m_view.set_double_buffered( false ); + + // デフォルトでは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::POINTER_MOTION_MASK ); + m_view.add_events( Gdk::LEAVE_NOTIFY_MASK ); + + // focus 可にセット + m_view.set_flags( m_view.get_flags() | Gtk::CAN_FOCUS ); + 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_expose_event().connect( sigc::mem_fun( *this, &DrawAreaBase::slot_expose_event )); + m_view.signal_scroll_event().connect( sigc::mem_fun( *this, &DrawAreaBase::slot_scroll_event )); + m_view.signal_button_press_event().connect( sigc::mem_fun( *this, &DrawAreaBase::slot_button_press_event )); + m_view.signal_button_release_event().connect( sigc::mem_fun( *this, &DrawAreaBase::slot_button_release_event )); + 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 + + // 色、フォント初期化 + init_color(); + init_font(); + + // スクロールバー作成 + if( show_scrbar ) create_scrbar(); + + show_all_children(); +} + + +// +// 変数初期化 +// +void DrawAreaBase::clear() +{ + if( m_layout_tree ) delete m_layout_tree; + m_layout_tree = NULL; + + m_scrollinfo.reset(); + + m_selection.select = false; + m_separator_new = 0; + m_multi_selection.clear(); + m_layout_current = NULL; + m_width_client = 0; + m_height_client = 0; + m_drugging = false; + m_r_drugging = false; + m_pre_pos_y = 0; + m_key_press = false; + m_goto_num_reserve = 0; + m_wheel_scroll_time = 0; +} + + + +// +// スクロールバー作成とパック +// +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::VScrollbar() ); + m_event = Gtk::manage( new Gtk::EventBox() ); + assert( m_vscrbar ); + assert( m_event ); + + m_event->add( *m_vscrbar ); + pack_start( *m_event, Gtk::PACK_SHRINK ); + m_vscrbar->get_adjustment()->signal_value_changed().connect( sigc::mem_fun( *this, &DrawAreaBase::slot_change_adjust ) ); + + show_all_children(); +} + + + + +// +// 色初期化 +// +void DrawAreaBase::init_color() +{ + Glib::RefPtr< Gdk::Colormap > colormap = get_default_colormap(); + const int *rgb; + + // 文字色 + rgb = CONFIG::get_color_char(); + m_color[ COLOR_CHAR ].set_rgb( rgb[ 0 ], rgb[ 1 ], rgb[ 2 ] ); + m_color[ COLOR_CHAR_NAME ] = Gdk::Color( "darkgreen" ); + m_color[ COLOR_CHAR_SELECTION ] = Gdk::Color( "white" ); + m_color[ COLOR_CHAR_LINK ] = Gdk::Color( "blue" ); + m_color[ COLOR_CHAR_LINK_PUR ] = Gdk::Color( "magenta" ); + m_color[ COLOR_CHAR_LINK_RED ] = Gdk::Color( "red" ); + m_color[ COLOR_CHAR_HIGHLIGHT ] = Gdk::Color( "black" ); + m_color[ COLOR_CHAR_BOOKMARK ] = Gdk::Color( "red" ); + + // 背景色 + rgb = rgb_color_back(); + m_color[ COLOR_BACK ].set_rgb( rgb[ 0 ], rgb[ 1 ], rgb[ 2 ] ); + m_color[ COLOR_BACK_SELECTION ] = Gdk::Color( "blue" ); + m_color[ COLOR_BACK_HIGHLIGHT ] = Gdk::Color( "yellow" ); + + // 新着セパレータ + rgb = CONFIG::get_color_separator(); + m_color[ COLOR_SEPARATOR_NEW ].set_rgb( rgb[ 0 ], rgb[ 1 ], rgb[ 2 ] ); + + // 画像 + m_color[ COLOR_IMG_NOCACHE ] = Gdk::Color( "brown" ); + m_color[ COLOR_IMG_LOADING ] = Gdk::Color( "darkorange" ); + m_color[ COLOR_IMG_CACHED ] = Gdk::Color( "darkcyan" ); + m_color[ COLOR_IMG_ERR ] = Gdk::Color( "red" ); + + for( int i = 0; i < COLOR_NUM; ++i ) colormap->alloc_color( m_color[ i ] ); +} + + + +// +// フォント初期化 +// +void DrawAreaBase::init_font() +{ + if( fontname().empty() ) return; + + Pango::FontDescription pfd( fontname() ); + pfd.set_weight( Pango::WEIGHT_NORMAL ); + m_pango_layout->set_font_description( pfd ); + + // 改行高さと右マージン幅取得 + // 真面目にやると大変そうなので文字列 wstr の平均を取る + const char* wstr = "あいうえお"; + m_pango_layout->set_text( wstr ); + m_height_text = m_pango_layout->get_pixel_ink_extents().get_height() + 1; + m_br_size = m_height_text + 4; + m_mrg_right = ( m_pango_layout->get_pixel_ink_extents().get_width() / 5 ) /2 * 3; + m_mrg_left = ( m_pango_layout->get_pixel_ink_extents().get_width() / 5 ); + m_down_size = m_mrg_left; +} + + +// +// クロック入力 +// +void DrawAreaBase::clock_in() +{ + exec_scroll( false ); +} + + +// +// フォーカス +// +void DrawAreaBase::focus_view() +{ + m_view.grab_focus(); +} + + +// +// フォーカス解除 +// +void DrawAreaBase::focus_out() +{ + if( !m_gc ) return; + + get_window()->set_cursor(); + + m_key_press = false; + if( m_scrollinfo.mode != SCROLL_AUTO ) m_scrollinfo.reset(); + +} + + +// 範囲選択中の文字列 +const std::string DrawAreaBase::str_selection() +{ + if( ! m_selection.select ) return std::string(); + + return m_selection.str; +} + + +// +// drawarea 上にマウスポインタがあったら true +// +bool DrawAreaBase::is_mouse_on_drawarea() +{ + bool ret = false; + + int x,y; + m_view.get_pointer( x, y ); + if( x <= m_view.get_width() && x >= 0 && y <= m_view.get_height() && y >= 0 ) ret = true; + +#ifdef _DEBUG + std::cout << "DrawAreaBase::is_mouse_on_drawarea " << m_url << " ret = " << ret << std::endl; +#endif + + return ret; +} + + +// +// 現在見ているレスの番号( >= 1 ) +// +int DrawAreaBase::seen_current() +{ + assert( m_layout_tree ); + + if( !m_vscrbar ) return 0; + + int seen = DBTREE::article_number_load( m_url ); + Gtk::Adjustment* adjust = m_vscrbar->get_adjustment(); + + if( adjust->get_value() < adjust->get_upper() - adjust->get_page_size() - m_br_size ){ + int y = ( int ) adjust->get_value(); + const LAYOUT* layout = m_layout_tree->get_header_at_y( y ); + if( layout ) seen = layout->res_number; + } + + return seen; +} + + +// +// 表示されている最後のレスの番号 +// +int DrawAreaBase::max_number() +{ + assert( m_layout_tree ); + + return m_layout_tree->max_res_number(); +} + + +// +// from_num から to_num までレスをappendして再レイアウト +// +void DrawAreaBase::append_res( int from_num, 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 + + for( int num = from_num; num <= to_num; ++num ) m_layout_tree->append_node( m_article->res_header( num ) ); + + // クライアント領域のサイズをリセットして再レイアウト + m_width_client = 0; + m_height_client = 0; + layout(); +} + + + +// +// リストで指定したレスをappendして再レイアウト +// +void DrawAreaBase::append_res( std::list< int >& list_resnum ) +{ + assert( m_article ); + assert( m_layout_tree ); + + if( list_resnum.size() == 0 ) return; + +#ifdef _DEBUG + std::cout << "DrawAreaBase::append_res" << std::endl; +#endif + + std::list< int >::iterator it; + for( it = list_resnum.begin(); it != list_resnum.end(); ++it ){ + + int num = (* it ); + +#ifdef _DEBUG + std::cout << "append no. " << num << std::endl; +#endif + + m_layout_tree->append_node( m_article->res_header( num ) ); + } + + // クライアント領域のサイズをリセットして再レイアウト + m_width_client = 0; + m_height_client = 0; + 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; + 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; + layout(); +} + + +// +// 画面初期化 +// +// 色、フォントを初期化して画面を消す +// +void DrawAreaBase::clear_screen() +{ + if( ! m_layout_tree ) return; + + m_layout_tree->clear(); + m_selection.select = false; + init_color(); + init_font(); + m_width_client = 0; + m_height_client = 0; + layout(); + redraw_view(); +} + + + +// +// バックスクリーンを描き直して再描画予約(queue_draw())する。再レイアウトはしない +// +void DrawAreaBase::redraw_view() +{ +#ifdef _DEBUG + std::cout << "DrawAreaBase::redraw_view()\n"; +#endif + + draw_backscreen( true ); + m_view.queue_draw(); +} + + + + + +// +// レイアウト実行 +// +void DrawAreaBase::layout() +{ + layout_impl( false, 0, 0 ); +} + + + + +// +// 先頭ノードから順に全ノードの座標を計算する(描画はしない) +// +// nowrap = true なら wrap なしで計算 +// offset_y は y 座標の上オフセット行数 +// right_mrg は右マージン量(ピクセル) +// +void DrawAreaBase::layout_impl( bool nowrap, int offset_y, int right_mrg ) +{ + if( ! m_layout_tree ) return; + + // レイアウトがセットされていない + if( ! m_layout_tree->top_header() ) return; + + // drawareaのウィンドウサイズ + + // nowrap = true の時は十分大きい横幅で計算して wrap させない + const int width = nowrap ? BIG_WIDTH : m_view.get_width(); + const int height = m_view.get_height(); + const int min_height = 2; + +#ifdef _DEBUG + std::cout << "DrawAreaBase::layout_impl : nowrap = " << nowrap << " width = " << width << " height = " << height<< std::endl + << "m_width_client = " << m_width_client << " m_height_client = " << m_height_client << std::endl; +#endif + + //表示はされてるがまだリサイズしてない状況 + if( !nowrap && height < min_height ){ +#ifdef _DEBUG + std::cout << "drawarea is not resized yet.\n"; +#endif + return; + } + + m_width_client = 0; + m_height_client = 0; + int mrg_level; // 現在の左字下げレベル + int x, y = 0; + LAYOUT* tmpheader = m_layout_tree->top_header(); + + y += offset_y * m_br_size; + + while( tmpheader ){ + + // ヘッダの座標等をセット + mrg_level = 0; + tmpheader->mrg_level = mrg_level; + x = m_mrg_left + m_down_size * tmpheader->mrg_level; + tmpheader->x = x; + tmpheader->y = y; + tmpheader->width = 0; + + // 先頭の子ノードから順にレイアウトしていく + LAYOUT* tmplayout = tmpheader->next_layout; + while ( tmplayout ){ + + layout_one_node( tmplayout, x, y, width, mrg_level ); + + // ブロック幅更新 + int width_tmp = tmplayout->width + m_mrg_left + m_down_size * tmplayout->mrg_level; + if( width_tmp> tmpheader->width ) tmpheader->width = width_tmp; + + tmplayout = tmplayout->next_layout; + } + y += m_br_size; + + // ブロック高さ確定 + tmpheader->height = y - tmpheader->y; + + // クライアント領域全体幅更新 + if( tmpheader->width > m_width_client ) m_width_client = tmpheader->width; + + tmpheader = tmpheader->next_header; + + // ブロック間はスペースを入れる + y += m_br_size; + } + + // クライアント領域の幅、高さ確定 + m_width_client += right_mrg; + 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( !m_window ) return; + + // 表示はされてるがまだリサイズしてない状況 + if( height < min_height ) return; + + // スクロールバーが表示されていないならここで作成 + if( ! m_vscrbar && m_height_client > height ) create_scrbar(); + + // adjustment 範囲変更 + Gtk::Adjustment* adjust = m_vscrbar ? m_vscrbar->get_adjustment(): NULL; + if( adjust ){ + + double current = adjust->get_value(); + adjust->set_lower( 0 ); + adjust->set_upper( y + m_br_size ); + adjust->set_page_size( height ); + adjust->set_step_increment( m_br_size ); + adjust->set_page_increment( height / 2 ); + adjust->set_value( MAX( 0, MIN( adjust->get_upper() - adjust->get_page_size() , current ) ) ); + } + + // 裏描画画面作成と初期描画 +#ifdef _DEBUG + std::cout << "create backscreen : width = " << m_view.get_width() << " height = " << m_view.get_height() << std::endl; +#endif + + m_backscreen.clear(); + m_backscreen = Gdk::Pixmap::create( m_window, m_view.get_width(), m_view.get_height() ); + draw_backscreen( true ); + + // 予約されているならジャンプ予約を実行 + if( m_goto_num_reserve ) goto_num( m_goto_num_reserve ); +} + + + +// +// ノードひとつのレイアウト関数 +// +// draw_one_node()と違い実際に描画はしない。ノードの座標計算に使う +// +// x,y : ノードの初期座標(左上)、次のノードの左上座標が入って返る +// mrg_level : 字下げ下げレベル +// +void DrawAreaBase::layout_one_node( LAYOUT* node, int& x, int& y, int width_view, int& mrg_level ) +{ + switch( node->type ){ + + case DBTREE::NODE_TEXT: // テキスト + case DBTREE::NODE_LINK: // リンク + + node->mrg_level = mrg_level; + node->x = x; + node->y = y; + + // 次のノードの左上座標を計算(x,yが参照なので更新された値が戻る) + layout_draw_one_node( node, x, y, width_view, false ); + + break; + + case DBTREE::NODE_BR: // 改行 + + node->x = x; + node->y = y; + + x = m_mrg_left + m_down_size * mrg_level; + y += m_br_size; + + break; + + // スペース + case DBTREE::NODE_ZWSP: break; + case DBTREE::NODE_HAIRSP: x += 1; break; + case DBTREE::NODE_THINSP: x += 2; break; + case DBTREE::NODE_ENSP: x += 8; break; + case DBTREE::NODE_EMSP: x += 16; break; + + case DBTREE::NODE_DOWN_LEFT: // 字下げ + ++mrg_level; + x = m_mrg_left + m_down_size * mrg_level; + break; + } +} + + + +// +// バックスクリーン描画 +// +// ここで書かれた画面がexposeイベントで表画面にコピーされる +// +// redraw_all = true なら全画面を描画、falseならスクロールした分だけ +// +bool DrawAreaBase::draw_backscreen( bool redraw_all ) +{ + if( ! m_gc ) return false; + if( ! m_backscreen ) return false; + if( ! m_layout_tree ) return false; + if( ! m_layout_tree->top_header() ) return false; + + int width_view = m_view.get_width(); + int height_view = m_view.get_height(); + int pos_y = get_vscr_val(); + + // 移動量、再描画範囲の上限、下限 + int dy = 0; + int upper = pos_y; + int lower = upper + height_view; + + m_gc->set_foreground( m_color[ COLOR_BACK ] ); + + // 画面全体を再描画 + if( redraw_all ){ + m_backscreen->draw_rectangle( m_gc, true, 0, 0, width_view, height_view ); + } + else{ + + dy = pos_y - m_pre_pos_y; + + // 上にスクロールした + if( dy > 0 ){ + + // キーを押しっぱなしの時は描画を省略する + if( m_key_press ) dy = MIN( dy, (int)SCROLLSPEED_SLOW ); + + if( dy < height_view ){ + upper += ( height_view - dy ); + m_backscreen->draw_drawable( m_gc, m_backscreen, 0, dy, 0, 0, width_view , height_view - dy ); + } + + m_backscreen->draw_rectangle( m_gc, true, 0, MAX( 0, height_view - dy ), width_view, MIN( dy, height_view ) ); + } + + // 下にスクロールした + else if( dy < 0 ){ + + // キーを押しっぱなしの時は描画を省略する + if( m_key_press ) dy = MAX( dy, (int)-SCROLLSPEED_SLOW ); + + if( -dy < height_view ){ + lower = upper - dy; + lower += SEPARATOR_HEIGHT * 2; // 新着セパレータの分を広げる + m_backscreen->draw_drawable( m_gc, m_backscreen, 0, 0, 0, -dy, width_view , height_view + dy ); + } + + m_backscreen->draw_rectangle( m_gc, true, 0, 0, width_view, MIN( -dy, height_view ) ); + } + } + + m_pre_pos_y = pos_y; + + if( !redraw_all && ! dy ) return false; + +#ifdef _DEBUG +// std::cout << "DrawAreaBase::draw_backscreen all = " << redraw_all << " y = " << pos_y <<" dy = " << dy << std::endl; +#endif + + // ノード描画 + LAYOUT* tmpheader = m_layout_tree->top_header(); + while( tmpheader ){ + + // ヘッダが範囲に含まれてるなら描画 + if( tmpheader->y + tmpheader->height > upper + && tmpheader->y < lower ){ + + // ノードが範囲に含まれてるなら描画 + LAYOUT* tmplayout = tmpheader; + while ( tmplayout ){ + if( tmplayout->y + tmplayout->height > upper && tmplayout->y < lower ) draw_one_node( tmplayout, width_view, pos_y ); + tmplayout = tmplayout->next_layout; + } + } + + tmpheader = tmpheader->next_header; + } + + return true; +} + + +// +// 枠の描画 +// +void DrawAreaBase::draw_frame() +{ + int width_win = m_view.get_width(); + int height_win = m_view.get_height(); + m_gc->set_foreground( m_color[ COLOR_CHAR ] ); + m_window->draw_rectangle( m_gc, false, 0, 0, width_win-1, height_win-1 ); +} + + + +// +// バックスクリーンを表画面に描画 +// +// draw_backscreen()でバックスクリーンを描画してから呼ぶこと +// +bool DrawAreaBase::draw_drawarea() +{ + if( ! m_layout_tree ) return false; + + // まだ realize してない + if( !m_gc ) return false; + + // レイアウトがセットされていない or まだリサイズしていない( m_backscreen == NULL ) + // なら画面消去して終わり + if( ! m_layout_tree->top_header() + || ! m_backscreen + ){ + m_window->set_background( m_color[ COLOR_BACK ] ); + m_window->clear(); + return false; + } + + int width_view = m_view.get_width(); + int height_view = m_view.get_height(); + + // バックスクリーンをコピー + m_window->draw_drawable( m_gc, m_backscreen, 0, 0, 0, 0, width_view , height_view ); + +#if 0 + // キャレット描画 + int xx = m_caret_pos.x; + int yy = m_caret_pos.y - pos_y; + if( yy >= 0 ){ + m_gc->set_foreground( m_color[ COLOR_CHAR ] ); + m_window->draw_line( m_gc, xx, yy, xx, yy + m_height_text ); + } +#endif + + // オートスクロールのマーカ + if( m_scrollinfo.mode != SCROLL_NOT && m_scrollinfo.show_marker ){ + m_gc->set_foreground( m_color[ COLOR_CHAR ] ); + m_window->draw_arc( m_gc, false, m_scrollinfo.x - AUTOSCR_CIRCLE/2, m_scrollinfo.y - AUTOSCR_CIRCLE/2 , + AUTOSCR_CIRCLE, AUTOSCR_CIRCLE, 0, 360 * 64 ); + } + + return true; +} + + +// +// 一文字の幅を計算 +// +int DrawAreaBase::get_width_of_one_char( const char* str, int& byte ) +{ + char tmpchar[ 64 ]; + int width, height; + int i=0; + + for( i = 0; i < byte ; ++i ) tmpchar[ i ] = str[ i ]; + tmpchar[ i ] = '\0'; + + m_pango_layout->set_text( tmpchar ); + m_pango_layout->get_pixel_size( width, height ); + + // フォントが無い + if( width <=0 ){ + + int byte_tmp; + unsigned int code = MISC::utf8toucs2( tmpchar, byte_tmp ); + std::stringstream ss_err; + ss_err << "unknown font byte = " << byte_tmp << " code = " << code; + +#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() ); + width = 0; + } + + return width; +} + + +// +// ノードひとつを描画する関数 +// +// width_view : 描画領域の幅 +// pos_y : 描画領域の開始座標 +// +void DrawAreaBase::draw_one_node( LAYOUT* layout, const int& width_view, const int& pos_y ) +{ + if( ! m_article ) return; + + // ノード種類別の処理 + switch( layout->type ){ + + // リンクノード + 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->get_code() == HTTP_OK ) node->color_text = COLOR_IMG_CACHED; + else if( img->get_code() != HTTP_ERR ) node->color_text = COLOR_IMG_ERR; + else node->color_text = COLOR_IMG_NOCACHE; + } + } + + // テキストノード + case DBTREE::NODE_TEXT: + + draw_one_text_node( layout, width_view, pos_y ); + break; + + + + // ヘッダ + case DBTREE::NODE_HEADER: + + // ブックマークのマーク描画 + if( layout->res_number && m_article->is_bookmarked( layout->res_number ) ){ + + int y = layout->y - pos_y; + + m_pango_layout->set_text( ">" ); + m_backscreen->draw_layout( m_gc, 0, y, m_pango_layout, m_color[ COLOR_CHAR_BOOKMARK ], m_color[ COLOR_BACK ] ); + } + + // 新着セパレータ + if( m_separator_new && m_separator_new == layout->res_number ){ + + int y = layout->y - pos_y - SEPARATOR_HEIGHT *2; + m_gc->set_foreground( m_color[ COLOR_SEPARATOR_NEW ] ); + m_backscreen->draw_rectangle( m_gc, true, 0, y, width_view, SEPARATOR_HEIGHT ); + } + + break; + + + // ノードが増えたらここに追加していくこと + + default: + break; + } +} + + + +// +// テキストの含まれているノードひとつを描画する関数 +// +// width_view : 描画領域の幅 +// pos_y : 描画領域の開始座標 +// +void DrawAreaBase::draw_one_text_node( LAYOUT* layout, const int& width_view, const int& pos_y ) +{ + int x = layout->x; + int y = layout->y - pos_y; + + int x_selection; + int y_selection; + + int id_header = layout->id_header; + int id = layout->id ; + + int id_header_from = 0; + int id_from = 0; + + int id_header_to = 0; + int id_to = 0; + + unsigned int byte_from = 0; + unsigned int byte_to = 0; + + bool bold = layout->bold; + + // 範囲選択の描画をする必要があるかどうかの判定 + // 二度書きすると重いので、ちょっと式は複雑になるけど同じ所を二度書きしないようにする + bool draw_selection = false; + if( m_selection.select && m_selection.caret_from.layout && m_selection.caret_to.layout ){ + + draw_selection = true; + + id_header_from = m_selection.caret_from.layout->id_header; + id_from = m_selection.caret_from.layout->id; + + id_header_to = m_selection.caret_to.layout->id_header; + id_to = m_selection.caret_to.layout->id; + + byte_from = m_selection.caret_from.byte * ( id_header == id_header_from && id == id_from ); + byte_to = m_selection.caret_to.byte * ( id_header == id_header_to && id == id_to ); + if( byte_to == 0 ) byte_to = strlen( layout->text ); + + if( // このノードは範囲選択外なら範囲選択の描画をしない + ( 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 && m_selection.caret_to.byte == 0 ) + ){ + + draw_selection = false; + } + + } + + int color_text = COLOR_CHAR; + if( layout->color_text ) color_text = *layout->color_text; + + // 通常描画 + if( ! draw_selection ){ + layout_draw_one_node( layout, x, y, width_view, true, bold, color_text, COLOR_BACK ); + + } else { // 範囲選択の前後描画 + + // 前 + if( byte_from ) layout_draw_one_node( layout, x, y, width_view, true, bold, color_text, COLOR_BACK, 0, byte_from ); + + // ここでは選択部分の座標計算のみ( do_draw = false )してハイライト後に選択部分を描画 + x_selection = x; + y_selection = y; + layout_draw_one_node( layout, x, y, width_view, false, bold, color_text, COLOR_BACK, byte_from, byte_to ); + + // 後 + if( byte_to != strlen( layout->text ) ) layout_draw_one_node( layout, x, y, width_view, true, bold, color_text, COLOR_BACK, byte_to ); + } + + // 検索結果のハイライト + if( m_multi_selection.size() > 0 ){ + + std::list< SELECTION >::iterator it; + for( it = m_multi_selection.begin(); it != m_multi_selection.end(); ++it ){ + if( layout->id_header == ( *it ).caret_from.layout->id_header && layout->id == ( *it ).caret_from.layout->id ){ + + // 描画開始位置の座標を計算( do_draw = false )、 + x = layout->x; + y = layout->y - pos_y; + int byte_from2 = ( *it ).caret_from.byte; + int byte_to2 = ( *it ).caret_to.byte; + if( byte_from2 ) layout_draw_one_node( layout, x, y, width_view, false, bold, COLOR_CHAR_HIGHLIGHT , COLOR_BACK_HIGHLIGHT, 0, byte_from2 ); + + // ハイライト描画 + layout_draw_one_node( layout, x, y, width_view, true, bold, COLOR_CHAR_HIGHLIGHT , COLOR_BACK_HIGHLIGHT, byte_from2, byte_to2 ); + } + } + } + + // 範囲選択部分を描画 + if( draw_selection && byte_from != byte_to ){ + layout_draw_one_node( layout, x_selection, y_selection, width_view, true, bold, + COLOR_CHAR_SELECTION , COLOR_BACK_SELECTION, byte_from, byte_to ); + } +} + + + +// +// 実際にレイアウトノード内の文字の座標を計算したり描画する関数 +// +// x,y : ノードの初期座標(左上)、次のノードの左上座標が入って返る +// do_draw : true なら描画, false なら座標計算のみ +// byte_from バイト目の文字から byte_to バイト目の「ひとつ前」の文字まで描画 +// byte_to が 0 なら最後まで描画 +// bold : 太字 +// +// たとえば node->text = "abcdefg" で byte_from = 1, byte_to = 3 なら "bc" を描画 +// +void DrawAreaBase::layout_draw_one_node( LAYOUT* node, int& x, int& y, int width_view, bool do_draw, bool bold, + int color , int color_back, int byte_from, int byte_to ) +{ + assert( node->text != NULL ); + assert( byte_to <= strlen( node->text ) ); + + if( *node->text == '\0' ) return; + if( byte_to == 0 ) byte_to = strlen( node->text ); + + node->width = 0; + node->height = m_br_size; + + int pos_start = byte_from; + for(;;){ + + // 横に何文字並べるか計算 + + int byte_char; + int pos_to = pos_start; + int n_ustr = 0; + int width_line = 0; + + // 右端がはみ出るまで文字を足していく + + bool draw_head = true; // 最低1文字は描画 + while( ( x + width_line < width_view - m_mrg_right // > ならwrap が起こったということ + && pos_to < byte_to ) || draw_head ) { + + int width_tmp = ARTICLE::get_width_of_char( node->text + pos_to, byte_char, fontmode() ); + if( ! width_tmp ){ + width_tmp = get_width_of_one_char( node->text + pos_to, byte_char ); + ARTICLE::set_width_of_char( node->text + pos_to, byte_char, fontmode(), width_tmp ); + } + width_line += width_tmp; + pos_to += byte_char; + ++n_ustr; + draw_head = false; + } + + // pos_start から pos_to の前まで描画 + if( do_draw ){ + + m_pango_layout->set_text( Glib::ustring( node->text + pos_start, n_ustr ) ); + m_backscreen->draw_layout( m_gc, x, y, m_pango_layout, m_color[ color ], m_color[ color_back ] ); + + if( bold ){ + m_gc->set_foreground( m_color[ color ] ); + m_backscreen->draw_layout( m_gc, x+1, y, m_pango_layout ); + } + + // リンクの時は下線を引く + if( node->link ){ + + m_gc->set_foreground( m_color[ color ] ); + m_backscreen->draw_line( m_gc, x, y + m_height_text, x + width_line, y + m_height_text ); + } + } + + // ノードの幅更新 + x += width_line; + int width_tmp = x - ( m_mrg_left + m_down_size * node->mrg_level ); + if( width_tmp > node->width ) node->width = width_tmp; + + // wrap 処理 + if( x >= width_view - m_mrg_right ) { + + // 改行 + x = ( m_mrg_left + m_down_size * node->mrg_level ); + y += m_br_size; + + // ノード高さ更新 + node->height += m_br_size; + } + + if( pos_to >= byte_to ) break; + pos_start = pos_to; + } +} + + + +// +// スクロール方向指定 +// +// 実際にスクロールして描画を実行するのは exec_scroll() +// +bool DrawAreaBase::set_scroll( const int& control ) +{ + if( !m_vscrbar ) return false; + double dy = 0; + + if( m_scrollinfo.mode == SCROLL_NOT ){ + + switch( control ){ + + // 下 + case CONTROL::DownFast: + dy = SCROLLSPEED_FAST; + break; + + case CONTROL::DownMid: + dy = SCROLLSPEED_MID; + break; + + case CONTROL::Down: + dy = SCROLLSPEED_SLOW; + break; + + // 上 + case CONTROL::UpFast: + dy = - SCROLLSPEED_FAST; + break; + + case CONTROL::UpMid: + dy = - SCROLLSPEED_MID; + break; + + case CONTROL::Up: + dy = - SCROLLSPEED_SLOW; + break; + + // Home, End, New + case CONTROL::Home: + goto_top(); + break; + + case CONTROL::End: + goto_bottom(); + break; + + case CONTROL::GotoNew: + goto_new(); + break; + } + + if( dy ){ + + m_scrollinfo.reset(); + m_scrollinfo.dy = ( int ) dy; + + // キーを押しっぱなしにしてる場合スクロールロックする + if( m_key_press ) m_scrollinfo.mode = SCROLL_LOCKED; + + // レスポンスを上げるため押した直後はすぐ描画 + else{ + + m_scrollinfo.mode = SCROLL_NORMAL; + exec_scroll( false ); + } + + return true; + } + } + + return false; +} + + + + +// +// マウスホイールの処理 +// +void DrawAreaBase::wheelscroll( GdkEventScroll* event ) +{ + const int time_cancel = 15; // msec + if( !m_vscrbar ) return; + + // あまり速く動かしたならキャンセル + 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 ) ){ + + Gtk::Adjustment* adjust = m_vscrbar->get_adjustment(); + + m_scrollinfo.reset(); + m_scrollinfo.mode = SCROLL_NORMAL; + + if( event->direction == GDK_SCROLL_UP ) m_scrollinfo.dy = -( int ) adjust->get_step_increment() *3; + else if( event->direction == GDK_SCROLL_DOWN ) m_scrollinfo.dy = ( int ) adjust->get_step_increment() *3; + + exec_scroll( false ); + } + } +} + + + +// +// スクロールやジャンプを実行して再描画 +// +// clock_in()からクロック入力される度にスクロールする +// +// redraw_all : true なら前画面再描画 +// +void DrawAreaBase::exec_scroll( bool redraw_all ) +{ + if( ! m_layout_tree ) return; + if( ! m_vscrbar ) return; + if( m_scrollinfo.mode == SCROLL_NOT ) return; + + // 移動後のスクロール位置を計算 + int y = 0; + Gtk::Adjustment* adjust = m_vscrbar->get_adjustment(); + 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( m_scrollinfo.res ); + if( layout ) y = layout->y; + m_scrollinfo.reset(); + } + break; + + // 先頭、最後に移動 + case SCROLL_TO_TOP: + y = 0; + m_scrollinfo.reset(); + break; + + case SCROLL_TO_BOTTOM: + y = (int) adjust->get_upper(); + m_scrollinfo.reset(); + break; + + case SCROLL_NORMAL: // 1 回だけスクロール + + y = ( int ) adjust->get_value() + m_scrollinfo.dy; + m_scrollinfo.reset(); + break; + + case SCROLL_LOCKED: // ロックが外れるまでスクロールを続ける + + y = ( int ) adjust->get_value() + m_scrollinfo.dy; + + break; + + case SCROLL_AUTO: // オートスクロールモード + { + // 現在のポインタの位置取得 + int x_point, y_point; + m_view.get_pointer( 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 = ( dy / fabs( dy ) ) * MIN( ( exp( ( fabs( dy ) - AUTOSCR_CIRCLE/4 ) /50 ) -1 ) * 5, + adjust->get_page_size() * 3 ); + if( m_drugging ) dy *= 4; // 範囲選択中ならスピード上げる + } + + y = ( int ) adjust->get_value() -( int ) dy; + + // 範囲選択中ならキャレット移動して選択範囲更新 + if( m_drugging ){ + + CARET_POSITION caret_pos; + int y_tmp = MIN( MAX( 0, y_point ), m_view.get_height() ); + set_caret( caret_pos, x_point , y + y_tmp ); + set_selection( caret_pos ); + } + } + break; + + } + + y = (int)MAX( 0, MIN( adjust->get_upper() - adjust->get_page_size() , y ) ); + adjust->set_value( y ); + + // キーを押しっぱなしの時に一番上か下に着いたらスクロール停止 + if( m_key_press && ( y <= 0 || y >= adjust->get_upper() - adjust->get_page_size() ) ){ + m_scrollinfo.reset(); + redraw_all = true; + } + + // 再描画 + if( draw_backscreen( redraw_all ) ) draw_drawarea(); +} + + + +// +// スクロールバーの現在値 +// +int DrawAreaBase::get_vscr_val() +{ + if( m_vscrbar ) return ( int ) m_vscrbar->get_adjustment()->get_value(); + 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_backscreen ){ + m_goto_num_reserve = num; + +#ifdef _DEBUG + std::cout << "reserve goto_num(1)\n"; +#endif + return; + } + + // 範囲を越えていたら再レイアウトしたときにもう一度呼び出し + else if( num > max_number() ){ + m_goto_num_reserve = num; + +#ifdef _DEBUG + std::cout << "reserve goto_num(2)\n"; +#endif + return; + } + + else m_goto_num_reserve = 0; + + if( DBTREE::article_number_load( m_url ) < num ){ + + num = DBTREE::article_number_load( m_url ); + } + +#ifdef _DEBUG + std::cout << "exec goto_num\n"; +#endif + + m_scrollinfo.reset(); + m_scrollinfo.mode = SCROLL_TO_NUM; + m_scrollinfo.res = num; + exec_scroll( true ); +} + + +// +// 先頭、新着、最後に移動 +// +void DrawAreaBase::goto_top() +{ + m_scrollinfo.reset(); + m_scrollinfo.mode = SCROLL_TO_TOP; + exec_scroll( true ); +} + +void DrawAreaBase::goto_new() +{ + int new_num = DBTREE::article_number_load( m_url ) - DBTREE::article_number_new( m_url ); + goto_num( new_num ); +} + +void DrawAreaBase::goto_bottom() +{ + m_scrollinfo.reset(); + m_scrollinfo.mode = SCROLL_TO_BOTTOM; + exec_scroll( true ); +} + + +// +// 検索実行 +// +bool DrawAreaBase::search( std::list< std::string >& list_query, bool reverse ) +{ + assert( m_layout_tree ); + + JDLIB::Regex regex; + + if( list_query.size() == 0 ) return false; + +#ifdef _DEBUG + std::cout << "ArticleViewBase::search size = " << list_query.size() << std::endl; +#endif + + m_multi_selection.clear(); + + // 先頭ノードから順にサーチして m_multi_selection に選択箇所をセットしていく + LAYOUT* tmpheader = m_layout_tree->top_header(); + while( tmpheader ){ + + LAYOUT* tmplayout = tmpheader->next_layout; + while( tmplayout ){ + + // (注意) 今のところレイアウトノードをまたがった検索は出来ない + if( ( tmplayout->type == DBTREE::NODE_TEXT || tmplayout->type == DBTREE::NODE_LINK ) && tmplayout->text ){ + + std::string text = tmplayout->text; + int offset = 0; + for(;;){ + + int lng = 0; + std::list< std::string >::iterator it_query; + for( it_query = list_query.begin(); it_query != list_query.end() ; ++it_query ){ + + std::string query = ( *it_query ); + if( regex.exec( query, tmplayout->text, offset, true ) ){ + + offset = regex.pos( 0 ); + lng = regex.str( 0 ).length(); + +#ifdef _DEBUG + std::cout << "id = " << tmplayout->id << " offset = " << offset << " lng = " << lng + << " " << text.substr( offset, lng ) << std::endl; +#endif + + break; + } + } + + if( lng == 0 ) break; + + // 選択設定 + SELECTION selection; + selection.select = false; + selection.caret_from.set( tmplayout, offset ); + selection.caret_to.set( tmplayout, offset + lng ); + m_multi_selection.push_back( selection ); + offset += lng; + } + } + + 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 false; + + // 初期位置をセット + // selection.select = true のアイテムが現在選択中 + std::list< SELECTION >::iterator it; + for( it = m_multi_selection.begin(); it != m_multi_selection.end(); ++it ){ + + if( ( *it ).caret_from < m_caret_pos ) continue; + ( *it ).select = true; + break; + } + + if( it == m_multi_selection.end() ) m_multi_selection.back().select = true; + + // search_move でひとつ進めるのでひとつ前に戻しておく + if( ! reverse ){ + ( *it ).select = false; + if( it == m_multi_selection.begin() ) m_multi_selection.back().select = true; + else ( *( --it ) ).select = true; + } + + return true; +} + + + +// +// 次の検索結果に移動 +// +void DrawAreaBase::search_move( bool reverse ) +{ +#ifdef _DEBUG + std::cout << "ArticleViewBase::search_move " << m_multi_selection.size() << std::endl; +#endif + + if( ! m_vscrbar ) return; + if( m_multi_selection.size() == 0 ) return; + + 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, false ); + + int y = MAX( 0, ( *it ).caret_from.layout->y - 10 ); + +#ifdef _DEBUG + std::cout << "move to y = " << y << std::endl; +#endif + + Gtk::Adjustment* adjust = m_vscrbar->get_adjustment(); + if( ( int ) adjust->get_value() > y || ( int ) adjust->get_value() + ( int ) adjust->get_page_size() - m_br_size < y ) + adjust->set_value( y ); + + return; + } + } +} + + +// +// ハイライト解除 +// +void DrawAreaBase::clear_highlight() +{ + m_multi_selection.clear(); + redraw_view(); +} + + + + +// +// 座標(x,y)を与えてキャレットの位置を計算してCARET_POSITIONに値をセット +//、ついでに(x,y)の下にあるレイアウトノードも調べる +// +// CARET_POSITION& caret_pos : キャレットの位置が計算されて入る +// +// 戻り値: 座標(x,y)の下のレイアウトノード。ノード外にある場合はNULL +// +LAYOUT* DrawAreaBase::set_caret( CARET_POSITION& caret_pos, int x, int y ) +{ + if( ! m_layout_tree ) return NULL; + +#ifdef _DEBUG_CARETMOVE + std::cout << "DrawAreaBase::set_caret\n"; +#endif + + int width_view = m_view.get_width(); + + // 先頭のレイアウトブロックから順に調べる + LAYOUT* tmpheader = m_layout_tree->top_header(); + while( tmpheader ){ + + // y が含まれているブロックだけチェックする + int height_block = tmpheader->next_header ? ( tmpheader->next_header->y - tmpheader->y ) : BIG_HEIGHT; + if( tmpheader->y <= y && tmpheader->y + height_block >= y ){ + +#ifdef _DEBUG_CARETMOVE + std::cout << "header id = " << tmpheader->id_header << std::endl; +#endif + // ブロック内のノードを順に調べていく + LAYOUT* tmplayout = tmpheader->next_layout; + while( tmplayout ){ + +#ifdef _DEBUG_CARETMOVE + std::cout << "node id = " << tmplayout->id << std::endl; +#endif + + if( ! tmplayout->text ){ + tmplayout = tmplayout->next_layout; + continue; + } + + int tmp_x = tmplayout->x; + int tmp_y = tmplayout->y; + const char* pos = tmplayout->text; + int byte_char = 0; + + // 左のマージンの上にポインタがある場合 + if( ( tmp_y <= y && tmp_y + m_br_size >= y ) && x <= tmp_x ){ + +#ifdef _DEBUG_CARETMOVE + std::cout << "found: left\n"; +#endif + // 左端にキャレットをセットして終了 + caret_pos.set( tmplayout, 0, x, tmp_x, tmp_y, 0, byte_char ); + return NULL; + } + + // ノードの中のテキストを調べていく + while( *pos != '\0' ){ + + int char_width = ARTICLE::get_width_of_char( pos, byte_char, fontmode() ); + + // マウスポインタの下にノードがある場合 + if( ( tmp_x <= x && tmp_x + char_width >= x ) + && ( tmp_y <= y && tmp_y + m_br_size >= y ) ){ + +#ifdef _DEBUG_CARETMOVE + std::cout << "found: on node\n"; + if( tmplayout->link != NULL ) std::cout << "link = " << tmplayout->link << std::endl; +#endif + // キャレットをセットして終了 + caret_pos.set( tmplayout, ( int )( pos - tmplayout->text ), x, tmp_x, tmp_y, char_width, byte_char ); + return tmplayout; + } + + // 次の文字へ + pos += byte_char; + tmp_x += char_width; + + if( tmp_x >= width_view - m_mrg_right ){ // wrap + + // wrapが起きたときに右のマージンの上にポインタがある場合 + if( ( tmp_y <= y && tmp_y + m_br_size >= y ) && x >= tmp_x ){ +#ifdef _DEBUG_CARETMOVE + std::cout << "found: right (wrap)\n"; +#endif + // 右端にキャレットをセットして終わり + caret_pos.set( tmplayout, ( int )( pos - tmplayout->text ) - byte_char, x, tmp_x, tmp_y, 0, 0 ); + return NULL; + } + + // 改行 + tmp_x = m_mrg_left + m_down_size * tmplayout->mrg_level; + tmp_y += m_br_size; + } + } + + // とりあえずノードの中にはポインタは無かったので + // 今のノードと次のノード or ブロックの間にyが無いか調べる + + int next_y = -1; // 次のノード or ブロックのy座標 + LAYOUT* tmplayout2 = tmplayout->next_layout; + + // 次のノードを取得する(文字列の含まれないノードは飛ばす) + while( tmplayout2 && !tmplayout2->text ) tmplayout2 = tmplayout2->next_layout; + + // 次のノードのy座標を取得 + // 最終ノードなら次のブロックの y 座標を出す + if( tmplayout2 ) next_y = tmplayout2->y; + else{ + + if( tmpheader->next_header ) next_y = tmpheader->next_header->y; + else next_y = y + BIG_HEIGHT; // 最終行ってこと + } + + if( + // 次のノード or ブロックに行くとyを飛び越える場合 + next_y > y + + // 右のマージンの上にポインタがある場合 + || ( + ( tmplayout->type == DBTREE::NODE_BR // 改行ノード + || tmplayout->next_layout == NULL // 最終ノード + || tmplayout->next_layout->type == DBTREE::NODE_BR // 一番右端のノード + ) + + && ( tmp_y <= y && tmp_y + m_br_size >= y ) && x >= tmp_x ) + + ){ +#ifdef _DEBUG_CARETMOVE + std::cout << "found: right\n"; +#endif + // 現在のノードの右端にキャレットをセット + caret_pos.set( tmplayout, ( int )( pos - tmplayout->text ), x, tmp_x, tmp_y, 0, 0 ); + return NULL; + } + + // 次のノードへ + tmplayout = tmplayout->next_layout; + } + } + + // 次のブロックへ + tmpheader = tmpheader->next_header; + } + + return NULL; +} + + + +// +// (x,y)地点をダブルクリック時の範囲選択のためのキャレット位置を取得 +// +// caret_left : 左側のキャレット位置 +// caret_left : 右側のキャレット位置 +// +// 戻り値 : 成功すると true +// +bool DrawAreaBase::set_carets_dclick( CARET_POSITION& caret_left, CARET_POSITION& caret_right, int x, int y ) +{ + if( ! m_layout_tree ) return false; + +#ifdef _DEBUG + std::cout << "DrawAreaBase::set_carets_dclick\n"; +#endif + + int width_view = m_view.get_width(); + + // 先頭のレイアウトブロックから順に調べる + LAYOUT* tmpheader = m_layout_tree->top_header(); + while( tmpheader ){ + + // y が含まれているブロックだけチェックする + int height_block = tmpheader->next_header ? tmpheader->next_header->y - tmpheader->y : BIG_HEIGHT; + if( tmpheader->y <= y && tmpheader->y + height_block >= y ){ + + // ブロック内のノードを順に調べていく + LAYOUT* tmplayout = tmpheader->next_layout; + while( tmplayout ){ + + if( ! tmplayout->text ){ + tmplayout = tmplayout->next_layout; + continue; + } + + int tmp_x = tmplayout->x; + int tmp_y = tmplayout->y; + int byte_char = 0; + const char* pos = tmplayout->text; + + int x_left = 0, y_left = 0; + const char* pos_left = NULL; + const char* pos_right = NULL; + + const char* pos_tmp = pos; + bool mode_ascii = ( *( ( unsigned char* )( pos ) ) < 128 ); + + // ノードの中のテキストを調べていく + while( *pos != '\0' ){ + + int char_width = ARTICLE::get_width_of_char( pos, byte_char, fontmode() ); + + // x,yの下にノードがある場合 + // 左側の位置を確定 + if( ! pos_left && tmp_x <= x && tmp_x + char_width >= x && tmp_y <= y && tmp_y + m_br_size >= y ){ + pos_left = pos_tmp; + } + + unsigned char code = *( ( unsigned char* )( pos ) ); + unsigned char code_next = *( ( unsigned char* )( pos + byte_char ) ); + + if( code_next == '\0' + || code == ' ' + || code == ',' + || ( mode_ascii && code_next >= 128 ) + || ( !mode_ascii && code_next < 128 ) ){ + + // 左側の仮位置を移動 + if( ! pos_left ){ + + x_left = tmp_x; + y_left = tmp_y; + + pos_tmp = pos + byte_char; + mode_ascii = ( *( ( unsigned char* )( pos_tmp ) ) < 128 ); + } + + // 確定 + else{ + + pos_right = pos; + int x_right = tmp_x; + + if( code != ' ' && code != ',' ){ + pos_right += byte_char; + tmp_x += char_width; + } + + // キャレット設定 + caret_left.set( tmplayout, ( int )( pos_left - tmplayout->text ), x_left, x_left, y_left ); + caret_right.set( tmplayout, ( int )( pos_right - tmplayout->text ), x_right , x_right, tmp_y ); + +#ifdef _DEBUG + std::cout << "from : " << caret_left.byte << std::endl; + std::cout << "to : " << caret_right.byte << std::endl; + std::cout << "string : " << std::string( caret_left.layout->text ) + .substr( caret_left.byte, caret_right.byte - caret_left.byte ) << std::endl; +#endif + + return true; + } + } + + pos += byte_char; + tmp_x += char_width; + + if( tmp_x >= width_view - m_mrg_right ){ // wrap + + // 改行 + tmp_x = m_mrg_left + m_down_size * tmplayout->mrg_level; + tmp_y += m_br_size; + } + } + + // 次のノードへ + tmplayout = tmplayout->next_layout; + } + + return false; + } + + // 次のブロックへ + tmpheader = tmpheader->next_header; + } + + return false; +} + + + +// +// 範囲選択の範囲を計算してm_selectionにセット & 範囲選択箇所の再描画 +// +// +// caret_left から caret_right まで範囲選択状態にする +// redraw : true なら再描画, false なら範囲の計算のみ +// + +bool DrawAreaBase::set_selection( CARET_POSITION& caret_left, 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 ); +} + + + +// +// 範囲選択の範囲を計算してm_selectionにセット & 範囲選択箇所のバックスクリーンの再描画 +// +// caret_pos : 移動後のキャレット位置 +// redraw : true なら再描画, false なら範囲の計算のみ +// +// m_caret_pos_pre から caret_pos まで範囲選択状態にする +// +bool DrawAreaBase::set_selection( CARET_POSITION& caret_pos, bool redraw ) +{ + if( ! caret_pos.layout ) return false; + if( ! m_caret_pos_dragstart.layout ) return false; + + // 前回の呼び出しからキャレット位置が変わってない + if( m_caret_pos == caret_pos ) return false; + + int pos_y = get_vscr_val(); + int width_view = m_view.get_width(); + + 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( !redraw ) return true; + + + ///////////////////////////////////////////////// + // + // 前回呼び出した位置(m_caret_pos_pre.layout) から現在の位置( m_caret_pos.layout) までバックスクリーンを再描画 + // + + 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; + } + +#ifdef _DEBUG + std::cout << "redraw layout from : " << layout->id_header << ":" << layout->id + << " to " << layout_to->id_header << ":" << layout_to->id << std::endl; +#endif + + while ( layout ){ + + draw_one_node( layout, width_view, pos_y ); + if( layout == layout_to ) break; + + if( layout->next_layout ) layout = layout->next_layout; + else if( layout->header->next_header ) layout = layout->header->next_header; + else break; + } + + return true; +} + + + +// +// 範囲選択の文字列取得 +// +// set_selection()の中で毎回やると重いので、ボタンのリリース時に一回だけ呼び出すこと +// +bool DrawAreaBase::set_selection_str() +{ + assert( m_layout_tree ); + + m_selection.str.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 + + 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->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 == m_selection.caret_to.layout ) 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が含まれていて、かつ条件(IDや数字など)を満たしていたらURLとして範囲選択文字を返す +// +// TODO: ノード間をまたがっていると取得できない +// +std::string DrawAreaBase::get_selection_as_url( const CARET_POSITION& caret_pos ) +{ + std::string url; + LAYOUT* layout = caret_pos.layout; + + if( !layout || ! m_selection.select || m_selection.str.empty() ) return url; + + if( layout->id_header == m_selection.caret_from.layout->id_header + && layout->id == m_selection.caret_from.layout->id + && caret_pos.byte >= m_selection.caret_from.byte + && caret_pos.byte <= m_selection.caret_to.byte + ){ + + unsigned int n,dig; + std::string select_str = MISC::remove_space( m_selection.str ); + int num = MISC::str_to_uint( select_str.c_str(), dig, n ); + + // 数字 + if( n == strlen( select_str.c_str() ) && dig && num ) url = PROTO_ANCHORE + MISC::itostr( num ); + + // ID + else if( select_str.find( "ID:" ) == 0 ) url = select_str; + } + +#ifdef _DEBUG +// if( !url.empty() ) std::cout << "DrawAreaBase::get_selection_as_url : " << url << std::endl; +#endif + + return url; +} + + +// +// VScrollbar が動いた +// +void DrawAreaBase::slot_change_adjust() +{ + if( m_scrollinfo.mode != SCROLL_NOT ) return; // スクロール中 + + m_scrollinfo.reset(); + m_scrollinfo.mode = SCROLL_NORMAL; + exec_scroll( false ); +} + + + +// +// drawarea がリサイズした +// +bool DrawAreaBase::slot_configure_event( GdkEventConfigure* event ) +{ +#ifdef _DEBUG + std::cout << "DrawAreaBase::slot_configure_event x = " << event->x << " y = " << event->y + << " width = " << m_view.get_width() << " heith = " << m_view.get_height() << std::endl; +#endif + + layout(); + redraw_view(); + + return true; +} + + + +// +// drawarea の再描画イベント +// +bool DrawAreaBase::slot_expose_event( GdkEventExpose* event ) +{ +#ifdef _DEBUG + std::cout << "DrawAreaBase::slot_expose_event\n"; +#endif + + draw_drawarea(); + + 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 + + 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 ); + + m_gc = Gdk::GC::create( m_window ); + assert( m_gc ); + + layout(); + m_view.grab_focus(); +} + + + +// +// マウスボタンを押した +// +bool DrawAreaBase::slot_button_press_event( GdkEventButton* event ) +{ + int x, y; + int pos = get_vscr_val(); + x = ( int ) event->x; + 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(); + m_scrollinfo.just_finished = true; + m_window->set_cursor(); + } + else { + + // キャレット移動 + if( m_control.button_alloted( event, CONTROL::ClickButton ) ){ + + // ctrl+クリックで範囲選択 + if( event->state & GDK_CONTROL_MASK ){ + + set_selection( caret_pos ); + } + else{ + // ドラッグ開始 + m_drugging = true; + m_selection.select = false; + m_selection.str.clear(); + m_caret_pos_pre = m_caret_pos; + m_caret_pos = caret_pos; + m_caret_pos_dragstart = caret_pos; + } + } + + // マウスジェスチャ(右ドラッグ)開始 + 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 ) ){ // リンク上で無いなら + + 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; + m_window->set_cursor( Gdk::Cursor( Gdk::DOUBLE_ARROW )); + } + } + + // ダブルクリックしたら範囲選択 + else if( m_control.button_alloted( event, CONTROL::DblClickButton ) ){ + + CARET_POSITION caret_left, caret_right; + if( set_carets_dclick( caret_left, caret_right, x, y ) ) set_selection( caret_left, caret_right ); + } + } + + // 再描画 + redraw_view(); + + m_sig_button_press.emit( event ); + + return true; +} + + +// +// マウスボタンを離した +// +bool DrawAreaBase::slot_button_release_event( GdkEventButton* event ) +{ + std::string url; + int res_num = 0; + + if( m_layout_current && m_layout_current->link ) url = m_layout_current->link; + 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.just_finished ){ + m_scrollinfo.just_finished = false; + 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 ) +{ + m_x_pointer = ( int ) event->x; + m_y_pointer = ( int ) event->y; + + motion_mouse(); + + m_sig_motion_notify.emit( event ); + return true; +} + + + + +// +// マウスが動いた時の処理 +// +bool DrawAreaBase::motion_mouse() +{ + const int pos = get_vscr_val(); + + // 現在のマウスポインタの下にあるレイアウトノードとキャレットの更新 + m_layout_current = set_caret( m_caret_pos_current, m_x_pointer , m_y_pointer + pos ); + + int res_num = 0; + if( m_layout_current ) res_num = m_layout_current->res_number; + + // 現在のマウスポインタの下にあるリンクの文字列を更新 + // IDや数字だったらポップアップ表示する + bool link_changed = false; + std::string link_current; + if( m_layout_current ){ + + // リンクの上にポインタがある + if( m_layout_current->link ) link_current = m_layout_current->link; + + // IDや数字などの範囲選択の上にポインタがある + else link_current = get_selection_as_url( m_caret_pos_current ); + } + + if( link_current != m_link_current ) link_changed = true; // 前回とリンクの文字列が変わった + m_link_current = link_current; + + // ドラッグ中なら範囲選択 + if( m_drugging ){ + + if( set_selection( m_caret_pos_current ) ){ + + if( m_scrollinfo.mode == SCROLL_NOT ) draw_drawarea(); + } + + // ポインタが画面外に近かったらオートスクロールを開始する + const int mrg = m_br_size * 3; + Gtk::Adjustment* adjust = ( m_vscrbar ? m_vscrbar->get_adjustment() : 0 ); + if ( ( pos == 0 && m_y_pointer < mrg ) + || ( adjust && pos >= adjust->get_upper() - adjust->get_page_size() && m_y_pointer > m_view.get_height() - 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; + } + else{ + m_scrollinfo.enable_up = false; + m_scrollinfo.enable_down = true; + m_scrollinfo.y = m_view.get_height() - mrg; + } + } + } + + // 右ドラッグしていないとき + 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シグナルを出さない + ){ +#ifdef _DEBUG + std::cout << "slot_motion_notify_drawarea : enter link = " << m_link_current << std::endl;; +#endif + m_sig_on_url.emit( m_link_current, res_num ); + get_window()->set_cursor( Gdk::Cursor( Gdk::HAND2 ) ); + } + + // リンクノードからでた + else{ +#ifdef _DEBUG + std::cout << "slot_motion_notify_drawarea : leave\n"; +#endif + get_window()->set_cursor(); + } + } + } + + return true; +} + + +// +// キーを押した +// +bool DrawAreaBase::slot_key_press_event( GdkEventKey* event ) +{ +#ifdef _DEBUG + std::cout << "DrawAreaBase::slot_key_press_event\n"; +#endif + + //オートスクロール中なら無視 + if( m_scrollinfo.mode == SCROLL_AUTO ) return true; + + m_sig_key_press.emit( event ); + + m_key_press = true; + return true; +} + + + +// +// キーを離した +// +bool DrawAreaBase::slot_key_release_event( GdkEventKey* event ) +{ +#ifdef _DEBUG + std::cout << "DrawAreaBase::slot_key_release_event\n"; +#endif + + if( !m_key_press ) return true; + m_key_press = false; + + m_scrollinfo.reset(); + redraw_view(); + + m_sig_key_release.emit( event ); + return true; +} + + diff --git a/src/article/drawareabase.h b/src/article/drawareabase.h new file mode 100644 index 000000000..f09a388c6 --- /dev/null +++ b/src/article/drawareabase.h @@ -0,0 +1,262 @@ +// ライセンス: 最新のGPL + +// スレ表示部のベースクラス + +#ifndef _DRAWAREABASE_H +#define _DRAWAREABASE_H + +#include "caret.h" +#include "scrollinfo.h" + +#include "jdlib/refptr_lock.h" + +#include "colorid.h" +#include "control.h" + +#include +#include + +namespace ARTICLE +{ + struct LAYOUT; + class LayoutTree; + + // マウスボタンプレスとリリースのシグナル。リリース時にマウスがリンクの上にある時そのURLを渡す + typedef sigc::signal< bool, 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, int > SIG_ON_URL; + typedef sigc::signal< void > SIG_LEAVE_URL; + + // 範囲選択用 + struct SELECTION + { + bool select; + CARET_POSITION caret_from; + CARET_POSITION caret_to; + std::string str; + }; + + + /////////////////////////////////// + + + 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::VScrollbar* m_vscrbar; + + // レイアウトツリー + LayoutTree* m_layout_tree; + + // 描画領域の幅、高さ( != ウィンドウサイズ ) + int m_width_client; + int m_height_client; + + // 描画用 + Glib::RefPtr< Gdk::Window > m_window; + Glib::RefPtr< Gdk::GC > m_gc; + Glib::RefPtr< Gdk::Pixmap > m_backscreen; + Glib::RefPtr< Pango::Layout > m_pango_layout; + + // キャレット情報 + CARET_POSITION m_caret_pos; // 現在のキャレットの位置(クリックやドラッグすると移動) + CARET_POSITION m_caret_pos_pre; // 移動前のキャレットの位置(選択範囲の描画などで使用する) + CARET_POSITION m_caret_pos_current; // 現在のマウスポインタの下のキャレットの位置(マウスを動かすと移動) + CARET_POSITION m_caret_pos_dragstart; // ドラッグを開始したキャレット位置 + + // 色 + Gdk::Color m_color[ COLOR_NUM ]; + + // 新着セパレータの位置(レス番号), 0 なら表示しない + int m_separator_new; + + // 範囲選択 + SELECTION m_selection; + + // 検索結果のハイライト範囲 + std::list< SELECTION > m_multi_selection; + + // レイアウト用 + int m_height_text; // テキストの平均高さ + int m_br_size; // 改行量 (ピクセル値) + int m_mrg_left; // 左マージン幅 (ピクセル値) + int m_mrg_right; // 右マージン幅 (ピクセル値) + int m_down_size; // 字下げ幅(ピクセル) + + // スクロール情報 + SCROLLINFO m_scrollinfo; + guint32 m_wheel_scroll_time; // 前回ホイールを回した時刻 + int m_goto_num_reserve; // 初期化時のジャンプ予約(レス番号) + int m_pre_pos_y; // スクロールした時の差分量(dy) + + // 状態 + int m_x_pointer, m_y_pointer; // 現在のマウスポインタの位置 + bool m_key_press; // キーを押している + bool m_drugging; // ドラッグ中 + bool m_r_drugging; // 右ドラッグ中 + std::string m_link_current; // 現在マウスポインタの下にあるリンクの文字列 + LAYOUT* m_layout_current; // 現在マウスポインタの下にあるlayoutノード(下が空白ならNULL) + + // 入力コントローラ + CONTROL::Control m_control; + + 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; } + + DrawAreaBase( const std::string& url ); + ~DrawAreaBase(); + + const std::string& get_url() const { return m_url; } + const int& width_client() const { return m_width_client; } + const int& height_client() const { return m_height_client; } + int get_separator_new(){ return m_separator_new; } + void set_separator_new( int val ){ m_separator_new = val; } + + void clock_in(); + void focus_view(); + void focus_out(); + + const std::string str_selection(); // 範囲選択中の文字列 + bool is_mouse_on_drawarea(); // マウスが描画領域の上にあるかどうか + int seen_current(); // 現在見ているレスの番号 + int max_number(); // 表示されている最後のレスの番号 + + // 表示するレスやコメントの追加 + void append_res( int from_num, int to_num ); + void append_res( std::list< int >& list_resnum ); + void append_html( const std::string& html ); + void append_dat( const std::string& dat, int num ); + + // 全画面消去 + void clear_screen(); + + // バックスクリーンを描き直して再描画予約(queue_draw())する。再レイアウトはしない + void redraw_view(); + + // スクロール方向指定 + bool set_scroll( const int& control ); + + // マウスホイールの処理 + void wheelscroll( GdkEventScroll* event ); + + // ジャンプ + void goto_num( int num ); + void goto_new(); + void goto_top(); + void goto_bottom(); + + // 検索 + bool search( std::list< std::string >& list_query, bool reverse ); + void search_move( bool reverse ); + void clear_highlight(); + + protected: + + // 共通セットアップ + void setup( bool show_abone, bool show_scrbar ); + + // レイアウト処理 + virtual void layout(); + void layout_impl( bool nowrap, int offset_y, int right_mrg ); + + // バックスクリーンをDrawAreaにコピー + virtual bool draw_drawarea(); + + // DrawAreaに枠を描画 + void draw_frame(); + + private: + + // 背景色 + virtual const int* rgb_color_back(); + + // フォント + virtual const std::string& fontname(); + + // フォントモード + virtual const int fontmode(); + + // 初期化関係 + void clear(); + void create_scrbar(); + void init_color(); + void init_font(); + + // 描画、レイアウト関係 + void layout_one_node( LAYOUT* node, int& x, int& y, int width_win, int& mrg_level ); + bool draw_backscreen( bool redraw_all = false ); // バックスクリーン描画 + int get_width_of_one_char( const char* str, int& byte ); + void draw_one_node( LAYOUT* layout, const int& width_win, const int& pos_y ); + void draw_one_text_node( LAYOUT* layout, const int& width_win, const int& pos_y ); + void layout_draw_one_node( LAYOUT* node, int& x, int& y, int width_win, bool do_draw, bool bold = false, + int color = COLOR_CHAR, int color_bac = COLOR_BACK, int byte_from = 0, int byte_to = 0 ); + + // スクロール関係 + void exec_scroll( bool redraw_all ); // スクロールやジャンプを実行して再描画 + int get_vscr_val(); + + // キャレット関係 + LAYOUT* set_caret( CARET_POSITION& caret_pos, int x, int y ); + bool set_carets_dclick( CARET_POSITION& caret_left, CARET_POSITION& caret_right, int x, int y ); + + // 範囲選択関係 + bool set_selection( CARET_POSITION& caret_left, CARET_POSITION& caret_right ); + bool set_selection( CARET_POSITION& caret_pos, bool redraw = true ); + bool set_selection_str(); + std::string get_selection_as_url( const CARET_POSITION& caret_pos ); + + // マウスが動いた時の処理 + bool motion_mouse(); + + // スロット + void slot_change_adjust(); + bool slot_configure_event( GdkEventConfigure* event ); + bool slot_expose_event( GdkEventExpose* event ); + 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 ); + }; +} + +#endif + diff --git a/src/article/drawareamain.cpp b/src/article/drawareamain.cpp new file mode 100644 index 000000000..99827459b --- /dev/null +++ b/src/article/drawareamain.cpp @@ -0,0 +1,21 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "drawareamain.h" + +#include "dbtree/articlebase.h" + +using namespace ARTICLE; + + +DrawAreaMain::DrawAreaMain( const std::string& url ) + : DrawAreaBase( url ) +{ +#ifdef _DEBUG + std::cout << "DrawAreaMain::DrawAreaMain url = " << url << std::endl; +#endif + + setup( false, true ); +} diff --git a/src/article/drawareamain.h b/src/article/drawareamain.h new file mode 100644 index 000000000..e859ddc2b --- /dev/null +++ b/src/article/drawareamain.h @@ -0,0 +1,21 @@ +// ライセンス: 最新のGPL + +// メイン表示部 + +#ifndef _DRAWAREAMAIN_H +#define _DRAWAREAMAIN_H + +#include "drawareabase.h" + +namespace ARTICLE +{ + class DrawAreaMain : public ARTICLE::DrawAreaBase + { + public: + DrawAreaMain( const std::string& url ); + }; +} + + +#endif + diff --git a/src/article/drawareapopup.cpp b/src/article/drawareapopup.cpp new file mode 100644 index 000000000..51b0cb333 --- /dev/null +++ b/src/article/drawareapopup.cpp @@ -0,0 +1,100 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "drawareapopup.h" + +#include "config/globalconf.h" + +#include "global.h" + +using namespace ARTICLE; + +// スクロールバーが付くとレイアウトがずれるのでクライアント領域の横幅をその分広げる +#define POPUP_RIGHT_MRG 40 +#define POPUP_OFFSET_Y 1 + + +DrawAreaPopup::DrawAreaPopup( const std::string& url ) + : DrawAreaBase( url ) +{ +#ifdef _DEBUG + std::cout << "DrawAreaPopup::DrawAreaPopup url = " << url << std::endl; +#endif + + setup( false, false ); +} + + +// 背景色 +const int* DrawAreaPopup::rgb_color_back() +{ + return CONFIG::get_color_back_popup(); +} + + +// フォント +const std::string& DrawAreaPopup::fontname() +{ + return CONFIG::get_fontname_popup(); + +} + +// フォントモード +const int DrawAreaPopup::fontmode() +{ + return FONT_POPUP; +} + + + +// +// レイアウト実行 +// +void DrawAreaPopup::layout() +{ + // まだクライアント領域のサイズが未取得のときはwrapなしで強制的にレイアウトする + // (親ウィンドウにクライアントのサイズを知らせるため) + bool nowrap = ( width_client() == 0 || height_client() == 0 ); + +#ifdef _DEBUG + std::cout << "DrawAreaPopup::layout() " << get_url() << " nowrap = " << nowrap << std::endl; +#endif + + layout_impl( nowrap, POPUP_OFFSET_Y, POPUP_RIGHT_MRG ); +} + + + +// +// バックスクリーンをDrawAreaにコピー +// +// ポップアップの場合は枠も描く +// +bool DrawAreaPopup::draw_drawarea() +{ + // 描画 + if( ! DrawAreaBase::draw_drawarea() ) return false; + + // フレーム描画 + draw_frame(); + + return true; +} + + + +////////////////////////////////////////////// + + +// あぼーんされたレスも表示する +DrawAreaPopupShowAbone::DrawAreaPopupShowAbone( const std::string& url ) + : DrawAreaPopup( url ) +{ +#ifdef _DEBUG + std::cout << "DrawAreaPopupShowAbone::DrawAreaPopupShowAbone url = " << url << std::endl; +#endif + + setup( true, false ); +} diff --git a/src/article/drawareapopup.h b/src/article/drawareapopup.h new file mode 100644 index 000000000..e07af6172 --- /dev/null +++ b/src/article/drawareapopup.h @@ -0,0 +1,53 @@ +// ライセンス: 最新のGPL + +// ポップアップの表示部 + +#ifndef _DRAWAREAPOPUP_H +#define _DRAWAREAPOPUP_H + +#include "drawareabase.h" + +namespace ARTICLE +{ + class DrawAreaPopup : public ARTICLE::DrawAreaBase + { + + public: + + DrawAreaPopup( const std::string& url ); + + protected: + + // レイアウト実行 + virtual void layout(); + + // バックスクリーンをDrawAreaにコピー + virtual bool draw_drawarea(); + + private: + + // 背景色 + virtual const int* rgb_color_back(); + + // フォント + virtual const std::string& fontname(); + + // フォントモード + virtual const int fontmode(); + }; + + + ///////////////////////////// + + // あぼーんされたレスも表示する + class DrawAreaPopupShowAbone : public ARTICLE::DrawAreaPopup + { + + public: + DrawAreaPopupShowAbone( const std::string& url ); + }; + +} + +#endif + diff --git a/src/article/font.cpp b/src/article/font.cpp new file mode 100644 index 000000000..16bb94936 --- /dev/null +++ b/src/article/font.cpp @@ -0,0 +1,62 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "font.h" + +#include "jdlib/miscutil.h" + +#include "global.h" + +unsigned char* width_of_char[ FONT_NUM ]; + + +// +// 初期化 +// +void ARTICLE::init_font() +{ + for( int i = 0; i< FONT_NUM ; i++ ){ + if( width_of_char[ i ] ) delete width_of_char[ i ]; + width_of_char[ i ] = NULL; + } +} + + + +// +// 文字の幅を返す関数 +// +// utfstr : 入力文字 (UTF-8) +// byte : 長さ(バイト) utfstr が ascii なら 1, UTF-8 なら 2 or 3 を入れて返す +// mode : global.h で定義されてるフォントのID +// +// 戻り値 : 文字幅(ピクセル値) +// +int ARTICLE::get_width_of_char( const char* utfstr, int& byte, int mode ) +{ + if( width_of_char[ mode ] == NULL ){ + width_of_char[ mode ] = ( unsigned char* ) malloc( 65536 + 16 ); + memset( width_of_char[ mode ], 0, 65536 ); + } + + int ucs2 = MISC::utf8toucs2( utfstr, byte ); + if( ! byte ) return 0; + + return width_of_char[ mode ][ ucs2 ]; +} + + + + +// +// 文字幅をセットする関数 +// +void ARTICLE::set_width_of_char( const char* utfstr, int& byte, int mode, int width ) +{ + int ucs2 = MISC::utf8toucs2( utfstr, byte ); + if( ! byte ) return; + + width_of_char[ mode ][ ucs2 ] = width; +} diff --git a/src/article/font.h b/src/article/font.h new file mode 100644 index 000000000..85a4875f8 --- /dev/null +++ b/src/article/font.h @@ -0,0 +1,16 @@ +// ライセンス: 最新のGPL + +// 文字の幅とかを記録しておくデータベース + +#ifndef _FONT_H +#define _FONT_H + +namespace ARTICLE +{ + void init_font(); + int utf8toucs2( const char* utfstr, int& byte ); + int get_width_of_char( const char* utfstr, int& byte, int mode ); + void set_width_of_char( const char* utfstr, int& byte, int mode, int width ); +} + +#endif diff --git a/src/article/layouttree.cpp b/src/article/layouttree.cpp new file mode 100644 index 000000000..02b92e234 --- /dev/null +++ b/src/article/layouttree.cpp @@ -0,0 +1,359 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "layouttree.h" + +#include "dbtree/interface.h" +#include "dbtree/articlebase.h" +#include "dbtree/nodetreebase.h" + +#include "jdlib/miscutil.h" + +using namespace ARTICLE; + +#define SIZE_OF_HEAP 256 * 1024 + + +// m_show_abone = true ならあぼーんしたレスも表示する +LayoutTree::LayoutTree( const std::string& url, bool show_abone ) + : m_heap( SIZE_OF_HEAP ), + m_url( url ), + m_local_nodetree( 0 ), + m_show_abone( show_abone ) +{ +#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_last_header = 0; + m_max_res_number = 0; + + m_id_header = -1; // ルートヘッダのIDが 0 になるように -1 を入れておく + m_root_header = create_layout_header(); + + if( m_local_nodetree ) delete m_local_nodetree; + m_local_nodetree = NULL; +} + + + +// +// 基本レイアウトノード作成 +// +LAYOUT* LayoutTree::create_layout( const int& type ) +{ + LAYOUT* tmplayout = ( LAYOUT* ) m_heap.heap_alloc( sizeof( LAYOUT ) ); + tmplayout->type = type; + tmplayout->id_header = m_id_header; + tmplayout->id = m_id_layout++; + tmplayout->header = m_last_header; + tmplayout->x = -1; + tmplayout->y = -1; + + return tmplayout; +} + + +// +// ヘッダノード作成 +// +LAYOUT* LayoutTree::create_layout_header() +{ + m_id_layout = 0; + ++m_id_header; + + LAYOUT* tmplayout = create_layout( DBTREE::NODE_HEADER ); + m_last_layout = tmplayout; + + if( m_last_header ) m_last_header->next_header = tmplayout; + m_last_header = tmplayout; + tmplayout->header = tmplayout; + + return tmplayout; +} + + +// +// テキストノード作成 +// +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; +} + + + +// +// 改行ノード作成 +// +LAYOUT* LayoutTree::create_layout_br( const char* text ) +{ + LAYOUT* tmplayout = create_layout( DBTREE::NODE_BR ); + m_last_layout->next_layout = tmplayout; + m_last_layout = tmplayout; + + tmplayout->text = text; + + return tmplayout; +} + + +// +// スペースノード作成 +// +LAYOUT* LayoutTree::create_layout_sp( const int& type ) +{ + LAYOUT* tmplayout = create_layout( type ); + m_last_layout->next_layout = tmplayout; + m_last_layout = tmplayout; + return tmplayout; +} + + +// +// マージンレベル下げノード作成 +// +LAYOUT* LayoutTree::create_layout_downleft() +{ + LAYOUT* tmplayout = create_layout( DBTREE::NODE_DOWN_LEFT ); + m_last_layout->next_layout = tmplayout; + m_last_layout = tmplayout; + return tmplayout; +} + + +// +// nodetreeのノード構造をコピーし、レイアウトツリーの一番最後に加える +// +void LayoutTree::append_node( DBTREE::NODE* node_header ) +{ + if( ! node_header ) return; + int res_number = node_header->id_header; + if( m_article->empty() ) return; + +#ifdef _DEBUG + std::cout << "LayoutTree::append_node num = " << res_number << " show_abone = " << m_show_abone << std::endl; +#endif + + // あぼーん + if( ! m_show_abone && m_article->abone( res_number ) ){ + append_abone_node( node_header ); + return; + } + + DBTREE::NODE* tmpnode = node_header; + + while( tmpnode ){ + + LAYOUT* tmplayout = NULL; + + switch( tmpnode->type ){ + + case DBTREE::NODE_HEADER: + tmplayout = create_layout_header(); + if( res_number > m_max_res_number ) m_max_res_number = res_number; + break; + + 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 ); + break; + + case DBTREE::NODE_BR: + tmplayout = create_layout_br( tmpnode->text ); + break; + + case DBTREE::NODE_ZWSP: + case DBTREE::NODE_HAIRSP: + case DBTREE::NODE_THINSP: + case DBTREE::NODE_ENSP: + case DBTREE::NODE_EMSP: + tmplayout = create_layout_sp( tmpnode->type ); + break; + + case DBTREE::NODE_DOWN_LEFT: + tmplayout = create_layout_downleft(); + break; + } + + if( tmplayout ){ + tmplayout->res_number = res_number; + tmplayout->node = tmpnode; + } + + tmpnode = tmpnode->next_node; + } +} + + + +// +// レイアウトツリーの一番最後にあぼーんノード追加 +// +void LayoutTree::append_abone_node( DBTREE::NODE* node_header ) +{ + LAYOUT* tmplayout; + DBTREE::NODE* tmpnode; + int res_number = node_header->id_header; + +#ifdef _DEBUG + std::cout << "LayoutTree::append_abone_node num = " << res_number << std::endl; +#endif + + tmplayout = create_layout_header(); + tmplayout->res_number = res_number; + if( res_number > m_max_res_number ) m_max_res_number = res_number; + + // node_header->next_node == レス番号のリンクヘッダ + tmpnode = node_header->next_node; + tmplayout = create_layout_link( tmpnode->text, tmpnode->linkinfo->link, &tmpnode->color_text, tmpnode->bold ); + + tmplayout = create_layout_text( " あぼ〜ん", NULL, false ); + + tmplayout = create_layout_br( "" ); + + tmplayout = create_layout_downleft(); + tmplayout = create_layout_downleft(); + + tmplayout = create_layout_text( "あぼ〜ん", NULL, false ); +} + + + +// +// HTML追加 +// +// 一時的にローカルなノードツリーを作ってHTMLをパースして append_node() で作ったノードをコピー +// +void LayoutTree::append_html( const std::string& html ) +{ +#ifdef _DEBUG + std::cout << "LayoutTree::append_html " << html << std::endl; +#endif + + if( ! m_local_nodetree ) m_local_nodetree = new DBTREE::NodeTreeBase( m_url, std::string() ); + DBTREE::NODE* node = m_local_nodetree->append_html( html ); + node->id_header = 0; + append_node( node ); +} + + +// +// dat追加 +// +// 一時的にローカルなノードツリーを作ってdatをパースして append_node() で作ったノードをコピー +// num: レス番号、0なら通し番号で +// +void LayoutTree::append_dat( const std::string& dat, int num ) +{ + if( ! m_local_nodetree ) m_local_nodetree = new DBTREE::NodeTreeBase( 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" ); + } + + DBTREE::NODE* node = m_local_nodetree->append_dat( dat ); + append_node( node ); +} + + + +// +// 座標 y の下にあるヘッダを返す +// +const LAYOUT* LayoutTree::get_header_at_y( int y ) +{ +#ifdef _DEBUG + std::cout << "LayoutTree::get_header_at_y : y = " << y << std::endl; +#endif + + LAYOUT* tmpheader = top_header(); + while( tmpheader ){ + + int height_block = tmpheader->next_header ? tmpheader->next_header->y - tmpheader->y : 10000; + if( tmpheader->y <= y && tmpheader->y + height_block >= y ) return tmpheader; + + tmpheader = tmpheader->next_header; + } + + return NULL; +} + + + +// +// レス番号 number のヘッダを返す +// +const LAYOUT* LayoutTree::get_header_of_res( int number ) +{ +#ifdef _DEBUG + std::cout << "LayoutTree::get_header_of_res num = " << number << std::endl; +#endif + + LAYOUT* tmpheader = top_header(); + while( tmpheader ){ + + if( tmpheader->res_number == number ) return tmpheader; + + tmpheader = tmpheader->next_header; + } + +#ifdef _DEBUG + std::cout << "not found.\n"; +#endif + + return NULL; +} diff --git a/src/article/layouttree.h b/src/article/layouttree.h new file mode 100644 index 000000000..c039c0c0c --- /dev/null +++ b/src/article/layouttree.h @@ -0,0 +1,121 @@ +// ライセンス: 最新のGPL + +// 実際の描画レイアウト時に使うノードのツリー構造クラス + + +#ifndef _LAYOUTTREE_H +#define _LAYOUTTREE_H + +#include + +#include "jdlib/refptr_lock.h" +#include "jdlib/heap.h" + + +namespace DBTREE +{ + class ArticleBase; + class NodeTreeBase; + struct NODE; +} + + +namespace ARTICLE +{ + // 描画レイアウト用ノード + 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* next_layout; // 次のノード、最終ノードではNULL + LAYOUT* next_header; // 次のヘッダノード、ヘッダノード以外ではNULL + + // レイアウト用変数 + unsigned mrg_level; // 字下げレベル + int x; + int y; + int width; + int height; + + // 文字情報( 実際にはノード情報(DBTREE::NODE)のtext情報へのポインタ) + const char* text; + const char* link; + const unsigned char* color_text; + bool bold; + + // ノード情報へのポインタ + DBTREE::NODE* node; + }; + + + // レイアウトツリー + class LayoutTree + { + // 高速化のため直接アクセス + JDLIB::RefPtr_Lock< DBTREE::ArticleBase > m_article; + + JDLIB::HEAP m_heap; + std::string m_url; + + // コメントノードやプレビュー表示時に使うローカルなノードツリー + DBTREE::NodeTreeBase* m_local_nodetree; + + LAYOUT* m_root_header; + LAYOUT* m_last_header; + LAYOUT* m_last_layout; + + // 表示中の最大のレス番号 + int m_max_res_number; + + int m_id_header; + int m_id_layout; + + // true ならあぼーんしたレスも表示 + bool m_show_abone; + + public: + + // m_show_abone = true ならあぼーんしたレスも表示する + LayoutTree( const std::string& url, bool show_abone ); + ~LayoutTree(); + + void clear(); + + LAYOUT* top_header() const { return m_root_header->next_header; } + const LAYOUT* last_header() const { return m_last_header; } + const int max_res_number() const { return m_max_res_number; } + + // nodetreeのノード構造をコピーし、ツリーの一番最後に加える + void append_node( DBTREE::NODE* node_header ); + + // html をパースして追加 + void append_html( const std::string& html ); + + // dat をパースして追加 + void append_dat( const std::string& dat, int num ); + + + // 座標 y の下にあるヘッダ + const LAYOUT* get_header_at_y( int y ); + + const LAYOUT* get_header_of_res( int number ); + + 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_br( const char* text ); + LAYOUT* create_layout_sp( const int& type ); + LAYOUT* create_layout_downleft(); + + void append_abone_node( DBTREE::NODE* node_header ); + }; +} + +#endif diff --git a/src/article/preference.cpp b/src/article/preference.cpp new file mode 100644 index 000000000..d0cbcc650 --- /dev/null +++ b/src/article/preference.cpp @@ -0,0 +1,70 @@ +// ライセンス: 最新のGPL + +#include "preference.h" + +#include "dbtree/interface.h" + +#include "jdlib/miscutil.h" + +#include "cache.h" + +#include +#include + +using namespace ARTICLE; + +Preferences::Preferences( const std::string& url ) + : SKELETON::PrefDiag( url ) + ,m_label_name( DBTREE::article_subject( get_url() ), Gtk::ALIGN_LEFT ) + ,m_label_url( "URL : ", DBTREE:: url_readcgi( get_url(),0,0 ) ) + ,m_label_url_dat( "DAT : ", DBTREE:: url_dat( get_url() ) ) + ,m_label_cache( "ローカルキャッシュパス : ", CACHE::path_dat( get_url() ) ) + + ,m_label_since( "スレ立て日時 : ", DBTREE::article_since_date( get_url() ) ) +{ + // 一般 + 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_since, Gtk::PACK_SHRINK ); + + std::string str_id, str_name; + std::list< std::string >::iterator it; + + // id + std::list< std::string > list_id = DBTREE::get_abone_list_id( get_url() ); + for( it = list_id.begin(); it != list_id.end(); ++it ) if( ! ( *it ).empty() ) str_id += ( *it ) + "\n"; + m_edit_id.set_text( str_id ); + + // name + std::list< std::string > list_name = DBTREE::get_abone_list_name( get_url() ); + for( it = list_name.begin(); it != list_name.end(); ++it ) if( ! ( *it ).empty() ) str_name += ( *it ) + "\n"; + m_edit_name.set_text( str_name ); + + m_notebook.append_page( m_vbox_info, "一般" ); + m_notebook.append_page( m_edit_id, "あぼ〜んID" ); + m_notebook.append_page( m_edit_name, "あぼ〜んName" ); + + get_vbox()->pack_start( m_notebook ); + set_title( "スレのプロパティ" ); + resize( 600, 400 ); + show_all_children(); +} + +Preferences::~Preferences() +{} + + +// +// OK 押した +// +void Preferences::slot_ok_clicked() +{ + // あぼーん再設定 + std::list< std::string > list_id = MISC::get_lines( m_edit_id.get_text(), true ); + std::list< std::string > list_name = MISC::get_lines( m_edit_name.get_text(), true ); + DBTREE::reset_abone( get_url(), list_id, list_name ); +} diff --git a/src/article/preference.h b/src/article/preference.h new file mode 100644 index 000000000..845bc2a90 --- /dev/null +++ b/src/article/preference.h @@ -0,0 +1,36 @@ +// ライセンス: 最新のGPL + +#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; + SKELETON::EditView m_edit_id, m_edit_name; + + // 情報 + Gtk::VBox m_vbox_info; + Gtk::Label m_label_name; + SKELETON::LabelEntry m_label_url; + SKELETON::LabelEntry m_label_url_dat; + SKELETON::LabelEntry m_label_cache; + + SKELETON::LabelEntry m_label_since; + + public: + + Preferences( const std::string& url ); + virtual ~Preferences(); + + private: + virtual void slot_ok_clicked(); + }; +} + +#endif diff --git a/src/article/scrollinfo.h b/src/article/scrollinfo.h new file mode 100644 index 000000000..711323c20 --- /dev/null +++ b/src/article/scrollinfo.h @@ -0,0 +1,58 @@ +// ライセンス: 最新のGPL + +// スクロール情報 + +#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_AUTO // マーカを中心にしてオートスクロール + }; + + class SCROLLINFO + { + public: + + int mode; + int dy; // スクロール量 + int res; // レス番号 + + // 以下はオートスクロールモード用の変数 + + int x; // 中心のx座標 + int y; // 中心のy座標 + bool show_marker; // true ならマーカを出す + bool enable_up; // true なら上方向にスクロール可 + bool enable_down; // true なら下方向にスクロール可 + + bool just_finished; // true ならオートスクロールが丁度終わったところ( slot_button_release_drawarea() で使う ) + + SCROLLINFO(){ reset(); } + + void reset() + { + mode = SCROLL_NOT; + dy = 0; + res = 0; + + x = 0; + y = 0; + show_marker = false; + enable_up = false; + enable_down = false; + just_finished = false; + } + }; +} + +#endif diff --git a/src/article/toolbar.h b/src/article/toolbar.h new file mode 100644 index 000000000..7c4ef89ef --- /dev/null +++ b/src/article/toolbar.h @@ -0,0 +1,176 @@ +// ライセンス: 最新のGPL + +// ツールバーのクラス +// +// ARTICLE::ArticleView* 以外では使わない +// + +#ifndef _ARTICLE_TOOLBAR_H +#define _ARTICLE_TOOLBAR_H + +#include + +#include "skeleton/imgbutton.h" +#include "skeleton/entry.h" + +#include "controlutil.h" +#include "controlid.h" + +namespace ARTICLE +{ + class ArticleToolBar : public Gtk::VBox + { + friend class ArticleViewBase; + friend class ArticleViewMain; + friend class ArticleViewRes; + friend class ArticleViewID; + friend class ArticleViewBM; + friend class ArticleViewRefer; + friend class ArticleViewURL; + friend class ArticleViewDrawout; + + Gtk::Tooltips m_tooltip; + + // ラベル、ボタンバー + Gtk::HBox m_buttonbar; + Gtk::Entry m_label; + + Gtk::Button m_button_board; + SKELETON::ImgButton m_button_favorite; + SKELETON::ImgButton m_button_write; + SKELETON::ImgButton m_button_close; + SKELETON::ImgButton m_button_delete; + SKELETON::ImgButton m_button_reload; + SKELETON::ImgButton m_button_stop; + SKELETON::ImgButton m_button_open_search; + SKELETON::ImgButton m_button_preferences; + + // 検索バー + Gtk::HBox m_searchbar; + bool m_searchbar_shown; + SKELETON::JDEntry m_entry_search; + SKELETON::ImgButton m_button_close_search; + SKELETON::ImgButton m_button_up_search; + SKELETON::ImgButton m_button_down_search; + SKELETON::ImgButton m_button_drawout_and; + SKELETON::ImgButton m_button_drawout_or; + SKELETON::ImgButton m_button_clear_hl; + + + // 検索バー表示 + void show_searchbar() + { + if( ! m_searchbar_shown ){ + pack_start( m_searchbar, Gtk::PACK_SHRINK ); + show_all_children(); + m_searchbar_shown = true; + } + } + + // 検索バーを消す + void hide_searchbar() + { + if( m_searchbar_shown ){ + remove( m_searchbar ); + show_all_children(); + m_searchbar_shown = false; + } + } + + + void set_label( const std::string& label ) + { + m_label.set_text( label ); + m_tooltip.set_tip( m_label, label ); + } + + ArticleToolBar() : + + m_button_favorite( Gtk::Stock::COPY ), + m_button_write( Gtk::Stock::NEW ), + m_button_close( Gtk::Stock::CLOSE ), + m_button_delete( Gtk::Stock::DELETE ), + m_button_reload( Gtk::Stock::REFRESH ), + m_button_stop( Gtk::Stock::STOP ), + m_button_open_search( Gtk::Stock::FIND ), + m_button_preferences( Gtk::Stock::PREFERENCES ), + + m_searchbar_shown( 0 ), + m_button_close_search( Gtk::Stock::UNDO ), + m_button_up_search( Gtk::Stock::GO_UP ), + m_button_down_search( Gtk::Stock::GO_DOWN ), + m_button_drawout_and( Gtk::Stock::CUT ), + m_button_drawout_or( Gtk::Stock::ADD ), + m_button_clear_hl( Gtk::Stock::CLEAR ) + { + // スレ名ラベル + // Gtk::Label を使うと勝手にリサイズするときがあるので + // 面倒でも Gtk::Entry を使う。背景色は on_realize() で指定する。 + m_label.set_editable( false ); + m_label.set_activates_default( false ); + m_label.set_has_frame( false ); + + m_button_board.set_focus_on_click( false ); + m_button_board.set_relief( Gtk::RELIEF_NONE ); + + m_tooltip.set_tip( m_button_board, CONTROL::get_label_motion( CONTROL::OpenParentBoard ) ); + m_tooltip.set_tip( m_button_write, CONTROL::get_label_motion( CONTROL::WriteMessage ) ); + m_tooltip.set_tip( m_button_close, CONTROL::get_label_motion( CONTROL::Quit ) ); + m_tooltip.set_tip( m_button_reload, CONTROL::get_label_motion( CONTROL::Reload ) ); + m_tooltip.set_tip( m_button_delete, CONTROL::get_label_motion( CONTROL::Delete ) ); + m_tooltip.set_tip( m_button_favorite, CONTROL::get_label_motion( CONTROL::AppendFavorite ) ); + m_tooltip.set_tip( m_button_stop, CONTROL::get_label_motion( CONTROL::StopLoading ) ); + m_tooltip.set_tip( m_button_preferences, CONTROL::get_label_motion( CONTROL::Property ) ); + m_tooltip.set_tip( m_button_open_search, CONTROL::get_label_motion( CONTROL::Search ) ); + + // ボタンとかラベルのバー + m_buttonbar.pack_start( m_button_board, Gtk::PACK_SHRINK ); + m_buttonbar.pack_start( m_label, Gtk::PACK_EXPAND_WIDGET, 2 ); + m_buttonbar.pack_end( m_button_close, Gtk::PACK_SHRINK ); + m_buttonbar.pack_end( m_button_delete, Gtk::PACK_SHRINK ); + m_buttonbar.pack_end( m_button_preferences, Gtk::PACK_SHRINK ); + m_buttonbar.pack_end( m_button_favorite, Gtk::PACK_SHRINK ); + m_buttonbar.pack_end( m_button_write, Gtk::PACK_SHRINK ); + m_buttonbar.pack_end( m_button_stop, Gtk::PACK_SHRINK ); + m_buttonbar.pack_end( m_button_reload, Gtk::PACK_SHRINK ); + m_buttonbar.pack_end( m_button_open_search, Gtk::PACK_SHRINK ); + + set_border_width( 1 ); + pack_start( m_buttonbar, Gtk::PACK_SHRINK ); + + // 検索バー + m_tooltip.set_tip( m_button_close_search, CONTROL::get_label_motion( CONTROL::CloseSearchBar ) ); + m_tooltip.set_tip( m_button_up_search, CONTROL::get_label_motion( CONTROL::SearchPrev ) ); + m_tooltip.set_tip( m_button_down_search, CONTROL::get_label_motion( CONTROL::SearchNext ) ); + m_tooltip.set_tip( m_button_drawout_and, CONTROL::get_label_motion( CONTROL::DrawOutAnd ) ); + m_tooltip.set_tip( m_button_drawout_or, CONTROL::get_label_motion( CONTROL::DrawOutOr ) ); + m_tooltip.set_tip( m_button_clear_hl, CONTROL::get_label_motion( CONTROL::HiLightOff ) ); + + m_searchbar.pack_start( m_entry_search, Gtk::PACK_EXPAND_WIDGET ); + m_searchbar.pack_end( m_button_close_search, Gtk::PACK_SHRINK ); + m_searchbar.pack_end( m_button_clear_hl, Gtk::PACK_SHRINK ); + m_searchbar.pack_end( m_button_drawout_or, Gtk::PACK_SHRINK ); + m_searchbar.pack_end( m_button_drawout_and, Gtk::PACK_SHRINK ); + m_searchbar.pack_end( m_button_up_search, Gtk::PACK_SHRINK ); + m_searchbar.pack_end( m_button_down_search, Gtk::PACK_SHRINK ); + } + + virtual ~ArticleToolBar(){} + + virtual void on_realize() + { + Gtk::VBox::on_realize(); + + // ラベル(Gtk::Entry)の背景色を変える + Gdk::Color color_bg = get_style()->get_bg( Gtk::STATE_NORMAL ); + m_label.modify_base( Gtk::STATE_NORMAL, color_bg ); + + color_bg = get_style()->get_bg( Gtk::STATE_ACTIVE ); + m_label.modify_base( Gtk::STATE_ACTIVE, color_bg ); + } + + }; +} + + +#endif diff --git a/src/bbslist/Makefile.am b/src/bbslist/Makefile.am new file mode 100644 index 000000000..4e9e96159 --- /dev/null +++ b/src/bbslist/Makefile.am @@ -0,0 +1,24 @@ +noinst_LIBRARIES = libbbslist.a + +libbbslist_a_SOURCES = \ + bbslistadmin.cpp \ + bbslistviewbase.cpp \ + bbslistview.cpp \ + etclistview.cpp \ + favoriteview.cpp \ + selectlistview.cpp \ + selectdialog.cpp + +noinst_HEADERS = \ + bbslistadmin.h \ + bbslistviewbase.h + bbslistview.h \ + etclistview.h \ + favoriteview.h \ + selectlistview.h \ + selectdialog.h \ + columns.h \ + toolbar.h + +AM_CXXFLAGS = @GTKMM_CFLAGS@ +INCLUDES = -I$(top_srcdir)/src diff --git a/src/bbslist/bbslistadmin.cpp b/src/bbslist/bbslistadmin.cpp new file mode 100644 index 000000000..c6596af88 --- /dev/null +++ b/src/bbslist/bbslistadmin.cpp @@ -0,0 +1,97 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "bbslistadmin.h" + +#include "skeleton/view.h" + +#include "jdlib/miscutil.h" + +#include "global.h" +#include "viewfactory.h" +#include "session.h" + +BBSLIST::BBSListAdmin *instance_bbslistadmin = NULL; + +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 = NULL; +} + + +using namespace BBSLIST; + +BBSListAdmin::BBSListAdmin( const std::string& url ) + : SKELETON::Admin( url ) +{} + + +BBSListAdmin::~BBSListAdmin() +{ +#ifdef _DEBUG + std::cout << "BBSListAdmin::~BBSListAdmin\n"; +#endif + // bbslistのページの位置保存 + SESSION::set_bbslist_page( get_current_page() ); +} + + + +// 前回開いていたURLを復元 +void BBSListAdmin::restore() +{ + set_command( "open_view", URL_BBSLISTVIEW ); + set_command( "open_view", URL_ETCVIEW, "true" ); + set_command( "open_view", URL_FAVORITEVIEW, "true" ); + set_command( "set_page", std::string(), MISC::itostr( SESSION::bbslist_page() ) ); +} + + +SKELETON::View* BBSListAdmin::create_view( const COMMAND_ARGS& command ) +{ + int type = CORE::VIEW_BBSLISTVIEW; + if( command.url == URL_FAVORITEVIEW ) type = CORE::VIEW_FAVORITELIST; + else if( command.url == URL_ETCVIEW ) type = CORE::VIEW_ETCLIST; + + CORE::VIEWFACTORY_ARGS view_args; + view_args.arg1 = command.arg3; + view_args.arg2 = command.arg4; + + 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 ){ + + // お気に入り追加 + // append_favorite を呼ぶ前に共有バッファにコピーデータをセットしておくこと + if( command.command == "append_favorite" ) view->set_command( "append_favorite" ); + } + + // お気に入り保存 + else if( command.command == "save_favorite" ){ + + view = get_view( URL_FAVORITEVIEW ); + if( view ) view->set_command( "save_favorite" ); + } +} diff --git a/src/bbslist/bbslistadmin.h b/src/bbslist/bbslistadmin.h new file mode 100644 index 000000000..c64d50022 --- /dev/null +++ b/src/bbslist/bbslistadmin.h @@ -0,0 +1,39 @@ +// ライセンス: 最新のGPL + +// +// 板の管理クラス +// +#ifndef _BBSLISTADMIN_H +#define _BBSLISTADMIN_H + +#include "skeleton/admin.h" + +#include + +namespace BBSLIST +{ + class BBSListAdmin : public SKELETON::Admin + { + public: + BBSListAdmin( const std::string& url ); + ~BBSListAdmin(); + + protected: + SKELETON::View* create_view( const COMMAND_ARGS& command ); + virtual void command_local( const COMMAND_ARGS& command ); + + virtual void restore(); + + // bbslistはクローズしない + virtual void close_view( const std::string& url ){} + virtual void close_all_view( const std::string& url ){} + + // タブメニュー表示キャンセル + virtual void slot_tab_menu( int page, int x, int y ){} + }; + + BBSLIST::BBSListAdmin* get_admin(); + void delete_admin(); +} + +#endif diff --git a/src/bbslist/bbslistview.cpp b/src/bbslist/bbslistview.cpp new file mode 100644 index 000000000..b29b51181 --- /dev/null +++ b/src/bbslist/bbslistview.cpp @@ -0,0 +1,112 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "bbslistview.h" +#include "bbslistadmin.h" + +#include "dbtree/interface.h" + +#include "cache.h" +#include "command.h" + +using namespace BBSLIST; + + +// メインビュー + +BBSListViewMain::BBSListViewMain( const std::string& url, + const std::string& arg1, const std::string& arg2 ) + : BBSListViewBase( url, arg1, arg2 ) +{ + BBSListViewBase::set_expand_collapse( true ); +} + + + +BBSListViewMain::~BBSListViewMain() +{ +#ifdef _DEBUG + std::cout << "BBSListViewMain::~BBSListViewMain : " << get_url() << std::endl; +#endif + + save_xml( CACHE::path_xml_listmain() ); +} + + +void BBSListViewMain::shutdown() +{ +#ifdef _DEBUG + std::cout << "BBSListViewMain::shutdown\n"; +#endif + save_xml( CACHE::path_xml_listmain_bkup() ); +} + + +// +// リロード +// +// 更新が終わったらBBSListViewMain::update_view()が呼ばれる +// +void BBSListViewMain::reload() +{ + DBTREE::download_bbsmenu(); + set_status( "loading..." ); + CORE::core_set_command( "set_status","", get_status() ); +} + + + +// +// 表示 +// +void BBSListViewMain::show_view() +{ +#ifdef _DEBUG + std::cout << "BBSListViewMain::show_view : " << get_url() << std::endl; +#endif + + // 板一覧のxmlが空ならサーバから取得 + if( DBTREE::get_xml_bbsmenu().empty() ) reload(); + + else update_view(); + + BBSLIST::get_admin()->set_command( "set_tablabel", get_url(), "List" ); +} + + +// +// アップデート +// +void BBSListViewMain::update_view() +{ + xml2tree( DBTREE::get_xml_bbsmenu() ); + set_status( std::string() ); + CORE::core_set_command( "set_status","", get_status() ); +} + + +// +// ポップアップメニュー +// +void BBSListViewMain::show_popupmenu( const Gtk::TreePath& path ) +{ + if( path.empty() ) return; + + Gtk::Menu* popupmenu; + std::list< Gtk::TreeModel::iterator > list_it = get_treeview().get_selected_iterators(); + if( list_it.size() == 1 ) popupmenu = dynamic_cast< Gtk::Menu* >( ui_manager()->get_widget( "/popup_menu" ) ); + else popupmenu = dynamic_cast< Gtk::Menu* >( ui_manager()->get_widget( "/popup_menu_mul" ) ); + + if( popupmenu ) popupmenu->popup( 0, gtk_get_current_event_time() ); +} + + +// +// 板リスト保存 +// +void BBSListViewMain::save_xml( const std::string& file ) +{ + if( get_ready_tree() ) CACHE::save_rawdata( file , tree2xml() ); +} diff --git a/src/bbslist/bbslistview.h b/src/bbslist/bbslistview.h new file mode 100644 index 000000000..492488120 --- /dev/null +++ b/src/bbslist/bbslistview.h @@ -0,0 +1,35 @@ +// ライセンス: 最新のGPL + +// +// メインビュー +// + +#ifndef _BBSLISTVIEW_H +#define _BBSLISTVIEW_H + +#include "bbslistviewbase.h" + +namespace BBSLIST +{ + // メインビュー + class BBSListViewMain : public BBSListViewBase + { + public: + BBSListViewMain( const std::string& url, const std::string& arg1 = std::string() , const std::string& arg2 = std::string() ); + virtual ~BBSListViewMain(); + + virtual void shutdown(); + + virtual void reload(); + virtual void show_view(); + virtual void update_view(); + + protected: + virtual void show_popupmenu( const Gtk::TreePath& path ); + + void save_xml( const std::string& file ); + }; +}; + + +#endif diff --git a/src/bbslist/bbslistviewbase.cpp b/src/bbslist/bbslistviewbase.cpp new file mode 100644 index 000000000..3404c41fa --- /dev/null +++ b/src/bbslist/bbslistviewbase.cpp @@ -0,0 +1,1694 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +//#define _DEBUG_XML +#include "jddebug.h" + +#include "bbslistviewbase.h" +#include "bbslistadmin.h" + +#include "jdlib/miscutil.h" +#include "jdlib/jdregex.h" + +#include "dbtree/interface.h" + +#include "dbimg/imginterface.h" + +#include "config/globalconf.h" + +#include "command.h" +#include "global.h" +#include "httpcode.h" +#include "controlid.h" +#include "dndmanager.h" +#include "sharedbuffer.h" +#include "viewfactory.h" +#include "xml.h" + +#include +//#include // GTK_CHECK_VERSION + + +#ifndef MAX +#define MAX( a, b ) ( a > b ? a : b ) +#endif + + +#ifndef MIN +#define MIN( a, b ) ( a < b ? a : b ) +#endif + + +// row -> path +#define GET_PATH( row ) m_treestore->get_path( row ) + + +using namespace BBSLIST; + +BBSListViewBase::BBSListViewBase( const std::string& url,const std::string& arg1, const std::string& arg2 ) + : SKELETON::View( url ), + m_ready_tree( false ), + m_jump_y( -1 ), + m_dnd_counter( 0 ), + m_search_invert( 0 ), + m_cancel_focus( 0 ), + m_expand_collapse( 0 ), + m_expanding( 0 ) +{ + m_scrwin.add( m_treeview ); + m_scrwin.set_policy( Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC ); + + m_toolbar.m_entry_search.signal_activate().connect( sigc::mem_fun( *this, &BBSListViewBase::search ) ); + m_toolbar.m_button_up_search.signal_clicked().connect( sigc::mem_fun( *this, &BBSListViewBase::slot_push_up_search ) ); + m_toolbar.m_button_down_search.signal_clicked().connect( sigc::mem_fun( *this, &BBSListViewBase::slot_push_down_search ) ); + m_toolbar.m_entry_search.signal_operate().connect( sigc::mem_fun( *this, &BBSListViewBase::slot_entry_operate ) ); + + pack_start( m_toolbar, Gtk::PACK_SHRINK ); + pack_start( m_scrwin ); + show_all_children(); + + m_treestore = Gtk::TreeStore::create( m_columns ); + m_treeview.set_model( m_treestore ); + m_treeview.set_headers_visible( false ); + + // 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 +*/ + // 列の登録 + m_treeview.append_column( *create_column() ); + m_treeview.set_column_for_height( 0 ); + + // 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.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().connect( sigc::mem_fun(*this, &BBSListViewBase::slot_motion ) ); + 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_drag_begin().connect( sigc::mem_fun(*this, &BBSListViewBase::slot_drag_begin ) ); + m_treeview.sig_drag_motion().connect( sigc::mem_fun(*this, &BBSListViewBase::slot_drag_motion ) ); + m_treeview.sig_drag_drop().connect( sigc::mem_fun(*this, &BBSListViewBase::slot_drag_drop ) ); + m_treeview.sig_drag_end().connect( sigc::mem_fun(*this, &BBSListViewBase::slot_drag_end ) ); + + // D&Dマネージャのシグナルをコネクト + CORE::get_dnd_manager()->sig_dnd_begin().connect( sigc::mem_fun(*this, &BBSListViewBase::slot_receive_dnd_begin ) ); + CORE::get_dnd_manager()->sig_dnd_end().connect( sigc::mem_fun(*this, &BBSListViewBase::slot_receive_dnd_end ) ); + + /////////////////// + + // ポップアップメニューの設定 + // アクショングループを作ってUIマネージャに登録 + action_group() = Gtk::ActionGroup::create(); + action_group()->add( Gtk::Action::create( "OpenTab", "タブで開く"), sigc::mem_fun( *this, &BBSListViewBase::slot_open_tab ) ); + action_group()->add( Gtk::Action::create( "OpenBrowser", "ブラウザで開く"), sigc::mem_fun( *this, &BBSListViewBase::slot_open_browser ) ); + action_group()->add( Gtk::Action::create( "AppendFavorite", "AppendFavorite"), sigc::mem_fun( *this, &BBSListViewBase::slot_append_favorite ) ); + action_group()->add( Gtk::Action::create( "NewDir", "新規ディレクトリ"), sigc::mem_fun( *this, &BBSListViewBase::slot_newdir ) ); + action_group()->add( Gtk::Action::create( "NewCom", "コメント挿入"), sigc::mem_fun( *this, &BBSListViewBase::slot_newcomment ) ); + action_group()->add( Gtk::Action::create( "Rename", "名前変更"), sigc::mem_fun( *this, &BBSListViewBase::slot_rename ) ); + action_group()->add( Gtk::Action::create( "Delete_Menu", "Delete" ) ); + action_group()->add( Gtk::Action::create( "Delete", "削除する"), sigc::mem_fun( *this, &BBSListViewBase::delete_view ) ); + action_group()->add( Gtk::Action::create( "CopyURL", "URLをコピー"), sigc::mem_fun( *this, &BBSListViewBase::slot_copy_url ) ); + action_group()->add( Gtk::Action::create( "CopyTitleURL", "タイトルとURLをコピー"), sigc::mem_fun( *this, &BBSListViewBase::slot_copy_title_url ) ); + action_group()->add( Gtk::Action::create( "Unselect", "選択解除"), sigc::mem_fun( *this, &BBSListViewBase::slot_unselect_all ) ); + + ui_manager() = Gtk::UIManager::create(); + ui_manager()->insert_action_group( action_group() ); + + // ポップアップメニューのレイアウト + Glib::ustring str_ui = + + "" + + // 通常 + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + + + // 通常 + 複数 + "" + "" + "" + "" + "" + + + // お気に入り + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + + // お気に入り+複数選択 + "" + "" + "" + "" + "" + "" + "" + + + // お気に入り+何もないところをクリック + "" + "" + "" + + + // 選択 + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + + ""; + + ui_manager()->add_ui_from_string( str_ui ); + + // ポップアップメニューにキーアクセレータを表示 + 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 ); + + popupmenu = dynamic_cast< Gtk::Menu* >( ui_manager()->get_widget( "/popup_menu_favorite" ) ); + CONTROL::set_menu_motion( popupmenu ); + + popupmenu = dynamic_cast< Gtk::Menu* >( ui_manager()->get_widget( "/popup_menu_favorite_mul" ) ); + CONTROL::set_menu_motion( popupmenu ); + + popupmenu = dynamic_cast< Gtk::Menu* >( ui_manager()->get_widget( "/popup_menu_favorite_space" ) ); + CONTROL::set_menu_motion( popupmenu ); + + popupmenu = dynamic_cast< Gtk::Menu* >( ui_manager()->get_widget( "/popup_menu_select" ) ); + CONTROL::set_menu_motion( popupmenu ); + + // マウスジェスチャ可 + SKELETON::View::set_enable_mg( true ); + + // コントロールモード設定 + SKELETON::View::get_control().set_mode( CONTROL::MODE_BBSLIST ); +} + + + +BBSListViewBase::~BBSListViewBase() +{ +#ifdef _DEBUG + std::cout << "BBSListViewBase::~BBSListViewBase : " << get_url() << std::endl; +#endif +} + + + +// +// クロック入力 +// +void BBSListViewBase::clock_in() +{ + m_treeview.clock_in(); + + // スクロールバー移動 + // 初期化直後など、まだスクロールバーが表示されてない時があるので表示されるまでジャンプしない + if( m_jump_y != -1 ){ + + Gtk::Adjustment* 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 + adjust->set_value( m_jump_y ); + m_jump_y = -1; + } + } + + // D&D 中に画面の上か下の方にある場合はスクロールさせる + if( m_treeview.reorderable() ){ + + ++m_dnd_counter; + if( m_dnd_counter >= 250 / TIMER_TIMEOUT ){ + + m_dnd_counter = 0; + + if( CORE::DND_Now_dnd() ){ + + Gtk::TreeModel::Path path = m_treeview.get_path_under_mouse(); + Gtk::Adjustment* adjust = m_treeview.get_vadjustment(); + + if( m_treeview.get_row( path ) && adjust ){ + + int height = m_treeview.get_height(); + int step = (int)adjust->get_step_increment() / 2; + int val = -1; + int x,y; + m_treeview.get_pointer( 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 = m_treeview.get_path_under_mouse(); + slot_drag_motion( path ); + } + } + } + } + } +} + + + + +// +// 再描画 +// +void BBSListViewBase::relayout() +{ + m_treeview.init_color(); + m_treeview.init_font(); +} + + +// +// フォーカス +// +void BBSListViewBase::focus_view() +{ + // 一回キャンセル + if( m_cancel_focus ){ + m_cancel_focus = false; + return; + } + + m_treeview.grab_focus(); +} + + +// +// フォーカスアウト +// +void BBSListViewBase::focus_out() +{ + SKELETON::View::focus_out(); + + m_treeview.hide_tooltip(); + m_treeview.delete_popup(); +} + + + +// +// viewの操作 +// +void BBSListViewBase::operate_view( const int& control ) +{ + 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::Home: + goto_top(); + break; + + case CONTROL::End: + goto_bottom(); + break; + + // 展開 or 選択 + case CONTROL::OpenBoardTab: + open_tab = true; + case CONTROL::OpenBoard: + + if( m_treeview.get_row( path ) ){ + + if( ! m_treeview.row_expanded( path ) ){ + if( ! open_row( path, open_tab ) ){ + m_treeview.expand_row( path, false ); + } + } + else m_treeview.collapse_row( path ); + } + break; + + case CONTROL::Right: + + if( m_treeview.get_row( path ) ){ + if( ! m_treeview.expand_row( path, false ) ) CORE::core_set_command( "switch_board" ); + } + break; + + case CONTROL::Left: + + if( m_treeview.get_row( path ) ){ + m_treeview.collapse_row( path ); + } + break; + + case CONTROL::ToggleArticle: + CORE::core_set_command( "toggle_article" ); + break; + + + case CONTROL::TabLeft: + BBSLIST::get_admin()->set_command( "tab_left" ); + break; + + case CONTROL::TabRight: + BBSLIST::get_admin()->set_command( "tab_right" ); + break; + + case CONTROL::Reload: + reload(); + break; + + case CONTROL::Delete: + { + Gtk::MessageDialog mdiag( "削除しますか?", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_OK_CANCEL ); + if( mdiag.run() != Gtk::RESPONSE_OK ) return; + delete_view(); + break; + } + + // 検索 + case CONTROL::Search: + m_search_invert = false; + m_toolbar.m_entry_search.grab_focus(); + break; + + case CONTROL::SearchInvert: + m_search_invert = true; + m_toolbar.m_entry_search.grab_focus(); + break; + + case CONTROL::SearchNext: + slot_push_down_search(); + break; + + case CONTROL::SearchPrev: + slot_push_up_search(); + break; + } +} + + + +// +// 先頭に戻る +// +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(); +} + + + +// +// 他のviewのtreestoreをcopyして表示 +// +void BBSListViewBase::copy_treestore( Glib::RefPtr< Gtk::TreeStore >& store ) +{ +#ifdef _DEBUG + std::cout << "BBSListViewBase::copy_treestore\n"; +#endif + m_treestore = store; + m_treeview.set_model( 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(); + } + } +} + + + + +// +// マウスボタン押した +// +bool BBSListViewBase::slot_button_press( GdkEventButton* event ) +{ + // マウスジェスチャ + SKELETON::View::get_control().MG_start( event ); + + CORE::core_set_command( "switch_bbslist" ); + + return true; +} + + +// +// マウスボタン離した +// +bool BBSListViewBase::slot_button_release( GdkEventButton* event ) +{ + /// マウスジェスチャ + int mg = SKELETON::View::get_control().MG_end( event ); + 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 ); + + // 板を開く + bool openboard = SKELETON::View::get_control().button_alloted( event, CONTROL::OpenBoardButton ); + bool openboardtab = SKELETON::View::get_control().button_alloted( event, CONTROL::OpenBoardTabButton ); + if( openboard || openboardtab ){ + if( m_treeview.get_row( m_path_selected ) ) open_row( m_path_selected, openboardtab ); + } + // ポップアップメニューボタン + else if( SKELETON::View::get_control().button_alloted( event, CONTROL::PopupmenuButton ) ){ + show_popupmenu( m_path_selected ); + } + + return true; +} + + + + +// +// マウス動かした +// +bool BBSListViewBase::slot_motion( GdkEventMotion* event ) +{ + /// マウスジェスチャ + SKELETON::View::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 ) ){ + + const int mrg = 16; // アイコンの横幅。計算するのが面倒だったのでとりあえず + + Gtk::TreeModel::Row row = m_treeview.get_row( path ); + Glib::ustring subject = row[ m_columns.m_col_name ]; + Glib::ustring url = row[ m_columns.m_col_url ]; + int type = row[ m_columns.m_type ]; + + // 画像ポップアップ + if( type == TYPE_IMAGE ){ + + m_treeview.hide_tooltip(); + + if( DBIMG::is_loadable( url ) && DBIMG::get_code( url ) != HTTP_ERR ){ + + if( m_treeview.pre_popup_url() != url ){ + + m_treeview.delete_popup(); + SKELETON::View* view = CORE::ViewFactory( CORE::VIEW_IMAGEPOPUP, url ); + m_treeview.show_popup( url, view ); + } + } + else m_treeview.delete_popup(); + } + + // ツールチップ + else{ + + m_treeview.delete_popup(); + + Gdk::Rectangle rect; + m_treeview.get_cell_area( path, *column, rect ); + m_treeview.set_tooltip_min_width( rect.get_width() - mrg ); + m_treeview.set_str_tooltip( subject ); + } + } + else{ + m_treeview.hide_tooltip(); + m_treeview.delete_popup(); + } + + return true; +} + + + +// +// キーを押した +// +bool BBSListViewBase::slot_key_press( GdkEventKey* event ) +{ + operate_view( SKELETON::View::get_control().key_press( event ) ); + return true; +} + + +// +// キー上げた +// +bool BBSListViewBase::slot_key_release( GdkEventKey* event ) +{ +#ifdef _DEBUG + guint key = event->keyval; + bool ctrl = ( event->state ) & GDK_CONTROL_MASK; + bool shift = ( event->state ) & GDK_SHIFT_MASK; + + std::cout << "BBSListViewBase::slot_key_release key = " << key << " ctrl = " << ctrl << " shift = " << shift << std::endl; +#endif + + return true; +} + + + +// +// popupmenu でタブで開くを選択 +// +void BBSListViewBase::slot_open_tab() +{ + open_row( m_path_selected, true ); +} + + +// +// ブラウザで開く +// +void BBSListViewBase::slot_open_browser() +{ + std::string url = path2url( m_path_selected ); + CORE::core_set_command( "open_url_browser", url ); +} + + + +// 共通バッファに path をセット +void BBSListViewBase::set_info_to_sharedbuffer( Gtk::TreePath& path ) +{ + CORE::DATA_INFO info; + info.type = path2type( path ); + info.url = path2url( path ); + info.name = path2name( path ); + CORE::SBUF_append( info ); +#ifdef _DEBUG + std::cout << "append " << info.name << std::endl; +#endif +} + + + +// +// 列をお気に入りに追加 +// +void BBSListViewBase::slot_append_favorite() +{ + // 共有バッファにデータ登録 + std::list< Gtk::TreeModel::iterator > list_it = m_treeview.get_selected_iterators(); + if( list_it.size() ){ + + CORE::SBUF_clear_info(); + + std::list< Gtk::TreeModel::iterator >::iterator it = list_it.begin(); + for( ; it != list_it.end(); ++it ){ + + Gtk::TreeModel::Row row = *( *it ); + Gtk::TreePath path = GET_PATH( row ); + + // サブディレクトリの場合は中身もコピー + // とりあえず再帰なしで一階層のみ + if( is_dir( path ) ){ + + set_info_to_sharedbuffer( path ); + path.down(); + + while( m_treeview.get_row( path ) ){ + + set_info_to_sharedbuffer( path ); + path.next(); + } + + CORE::DATA_INFO info; + info.type = TYPE_DIR_END; + CORE::SBUF_append( info ); + } + else set_info_to_sharedbuffer( path ); + } + + CORE::core_set_command( "append_favorite", URL_FAVORITEVIEW ); + } +} + + +// +// メニューでディレクトリを作るを選択 +// +void BBSListViewBase::slot_newdir() +{ + Gtk::TreeModel::Path path = append_row( std::string(), "New Directory", TYPE_DIR, m_path_selected, true ); + m_treeview.set_cursor( path ); + show_status(); + m_path_selected = path; + slot_rename(); +} + + + +// +// メニューでコメント挿入を選択 +// +void BBSListViewBase::slot_newcomment() +{ + Gtk::TreeModel::Path path = append_row( std::string(), "Comment", TYPE_COMMENT, m_path_selected, true ); + m_treeview.set_cursor( path ); + m_path_selected = path; + slot_rename(); +} + + + +// +// 名前変更 +// +void BBSListViewBase::slot_rename() +{ + if( m_path_selected.empty() ) return; + +#ifdef _DEBUG + std::cout << "BBSListViewBase::slot_rename\n"; +#endif + + m_ren_text->property_editable() = true; // edit可 + m_treeview.set_cursor( m_path_selected, *m_treeview.get_column( 0 ), true ); + // メニューが消えるとfocus_viewが呼ばれて名前変更モードが終了するのでfocus_viewをキャンセルする + m_cancel_focus = true; + m_ren_text->property_editable() = false; +} + + +// +// URLをコピー +// +void BBSListViewBase::slot_copy_url() +{ + if( m_path_selected.empty() ) return; + + std::string url = path2url( m_path_selected ); + COPYCLIP( url ); +} + + +// 名前とURLをコピー +// +void BBSListViewBase::slot_copy_title_url() +{ + if( m_path_selected.empty() ) return; + + std::string url = path2url( m_path_selected ); + std::string name = path2name( m_path_selected ); + std::stringstream ss; + ss << name << std::endl + << url << std::endl; + + COPYCLIP( ss.str() ); +} + + +// +// 選択解除 +// +void BBSListViewBase::slot_unselect_all() +{ + m_treeview.get_selection()->unselect_all(); +} + + + +// +// フォルダを開いた時に呼ばれる +// +void BBSListViewBase::slot_row_exp( const Gtk::TreeModel::iterator&, const Gtk::TreeModel::Path& path ) +{ + // 他のフォルダを全て閉じる + if( CONFIG::get_open_one_category() && m_expand_collapse ){ + + if( m_expanding ) return; + m_expanding = true; + m_treeview.collapse_all(); + m_treeview.expand_row( path, false ); + m_expanding = false; + } + + m_treeview.set_cursor( path ); + 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(); +} + + + +// +// 名前を変更したときにCellRendererTextから呼ばれるslot +// +void BBSListViewBase::slot_ren_text_on_edited( const Glib::ustring& path, const Glib::ustring& text ) +{ + Gtk::TreeModel::Row row = m_treeview.get_row( Gtk::TreePath( path ) ); + if( row ) row[ m_columns.m_col_name ] = text; +} + + + + + + +// +// このビューからD&Dを開始したときにtreeviewから呼ばれる +// +void BBSListViewBase::slot_drag_begin() +{ +#ifdef _DEBUG + std::cout << "BBSListViewBase::slot_drag_begin\n"; +#endif + + CORE::DND_Begin( get_url() ); +} + + +// +// D&Dマネージャから D&D 開始シグナルを受けた時に呼ばれる +// +void BBSListViewBase::slot_receive_dnd_begin() +{} + + + +// +// D&D中にtreeviewから呼ばれる +// +void BBSListViewBase::slot_drag_motion( Gtk::TreeModel::Path path ) +{ + if( !m_treeview.get_row( path ) ) return; + + // 移動先に下線を引く + int cell_x, cell_y, cell_w, cell_h; + m_treeview.get_cell_xy_wh( cell_x, cell_y, cell_w, cell_h ); + if( cell_y < cell_h / 2 ){ + Gtk::TreeModel::Path path_tmp = m_treeview.prev_path( path ); + if( m_treeview.get_row( path_tmp ) ) path = path_tmp; + } + draw_underline( m_drag_path_uline, false ); + draw_underline( path, true ); + m_drag_path_uline = path; + +#ifdef _DEBUG + std::cout << "BBSListViewBase::slot_drag_motion = " << path.to_string() << std::endl; +#endif +} + + + +// +// D&Dでドロップされたときにtreeviewから呼ばれる +// +void BBSListViewBase::slot_drag_drop( Gtk::TreeModel::Path path ) +{ +#ifdef _DEBUG + std::cout << "BBSListViewBase::slot_drag_drop\n"; +#endif + + bool after = true; + draw_underline( m_drag_path_uline, false ); + + // セル内の座標を見て真ん中より上だったら上に挿入 + if( m_treeview.get_row( path ) ){ + + int cell_x, cell_y, cell_w, cell_h; + m_treeview.get_cell_xy_wh( cell_x, cell_y, cell_w, cell_h ); + if( cell_y < cell_h / 2 ) after = false; + +#ifdef _DEBUG + std::cout << "cell height = " << cell_h << " cell_y = " << cell_y << std::endl; +#endif + } + + std::string url_from = CORE::DND_Url_from(); + +#ifdef _DEBUG + std::cout << "path = " << path.to_string() << " after = " << after << " from " << url_from << std::endl; +#endif + + // 同じビュー内でディレクトリの移動 + if( url_from == get_url() ) move_selected_row( path, after ); + + // 他のビューからD&Dされた + else append_from_buffer( path, after ); +} + + + +// +// このビューからD&Dを開始した後にD&Dを終了するとtreeviewから呼ばれる +// +void BBSListViewBase::slot_drag_end() +{ +#ifdef _DEBUG + std::cout << "BBSListViewBase::slot_drag_end\n"; +#endif + CORE::DND_End(); + + draw_underline( m_drag_path_uline, false ); +} + + + +// +// D&Dマネージャから D&D 終了シグナルを受けたときに呼ばれる +// +void BBSListViewBase::slot_receive_dnd_end() +{ + if( !m_treeview.reorderable() ) return; + + draw_underline( m_drag_path_uline, false ); +} + + + + +// +// 列の作成 +// +// Gtk::mange してるのでdeleteしなくてもよい +// +Gtk::TreeViewColumn* BBSListViewBase::create_column() +{ + Gtk::TreeViewColumn* col = Gtk::manage( new Gtk::TreeViewColumn( "name" ) ); + col->pack_start( m_columns.m_col_image, Gtk::PACK_SHRINK ); + + m_ren_text = Gtk::manage( new Gtk::CellRendererText() ); + m_ren_text->signal_edited().connect( sigc::mem_fun( *this, &BBSListViewBase::slot_ren_text_on_edited ) ); + m_ren_text->property_underline().set_value( Pango::UNDERLINE_SINGLE ); + + col->pack_start( *m_ren_text, true ); + col->add_attribute( *m_ren_text, "text", 0 ); + col->add_attribute( *m_ren_text, "underline", 1 ); + col->set_sizing( Gtk::TREE_VIEW_COLUMN_FIXED ); + col->add_attribute( *m_ren_text, "underline", 1 ); + + return col; +} + + + + +// +// 選択した行を開く +// +bool BBSListViewBase::open_row( Gtk::TreePath& path, bool tab ) +{ + if( !m_treeview.get_row( path ) ) return false; + + std::string str_tab = "false"; + if( tab ) str_tab = "true"; + + Glib::ustring url = path2url( path ); + if( url.empty() ) return false; + + int type = path2type( path ); + + switch( type ){ + + case TYPE_BOARD: + CORE::core_set_command( "open_board", DBTREE::url_subject( url ), str_tab ); + break; + + case TYPE_THREAD: + CORE::core_set_command( "open_article", DBTREE::url_dat( url ), str_tab ); + break; + + case TYPE_IMAGE: + 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; + } + +#ifdef _DEBUG + std::cout << "BBSListViewBase::open_row : path = " << path.to_string() << std::endl; +#endif + + return true; +} + + + + +// +// path -> url 変換 +// +Glib::ustring BBSListViewBase::path2url( const Gtk::TreePath& path ) +{ + Gtk::TreeModel::Row row = m_treeview.get_row( path ); + if( !row ) return Glib::ustring(); + + Glib::ustring url = row[ m_columns.m_col_url ]; + if( url.empty() ) return url; + + // 移転があったら url を最新のものに変換しておく + int type = path2type( path ); + switch( type ){ + + case TYPE_BOARD: + url = DBTREE::url_boardbase( url ); + break; + + case TYPE_THREAD: + url = DBTREE::url_readcgi( url, 0, 0 ); + break; + } + + return url; +} + + + +// +// path -> name 変換 +// +Glib::ustring BBSListViewBase::path2name( const Gtk::TreePath& path ) +{ + Gtk::TreeModel::Row row = m_treeview.get_row( path ); + if( !row ) return Glib::ustring(); + return row[ m_columns.m_col_name ]; +} + + + +// +// path -> type 変換 +// +int BBSListViewBase::path2type( const Gtk::TreePath& path ) +{ + Gtk::TreeModel::Row row = m_treeview.get_row( path ); + if( !row ) return TYPE_UNKNOWN; + return row[ m_columns.m_type ]; +} + + + + +// +// ディレクトリかどうかの判定 +// +bool BBSListViewBase::is_dir( Gtk::TreeModel::iterator& it ) +{ + Gtk::TreeModel::Row row = ( *it ); + if( row[ m_columns.m_type ] == TYPE_DIR ) return true; + return false; +} + +bool BBSListViewBase::is_dir( const Gtk::TreePath& path ) +{ + Gtk::TreeModel::iterator it = m_treestore->get_iter( path ); + return is_dir( it ); +} + + + + +// +// 行追加 +// +// after = false ならpath_dest の前に追加する( デフォルト after = true ) +// path_dest がNULLなら一番最後に作る +// path_dest がディレクトリであり、かつ subdir = true なら path_dest の下に追加。 +// path_dest がディレクトリでない、または subdir = falseなら path_dest の後に追加 +// 戻り値は追加した行のpath +// +Gtk::TreeModel::Path BBSListViewBase::append_row( const std::string& url, const std::string& name, int type, + Gtk::TreeModel::Path path_dest, bool subdir, bool after ) +{ +#ifdef _DEBUG + std::cout << "BBSListViewBase::append_row " << url << " " << name << std::endl; +#endif + Gtk::TreeModel::Row row_new; + + // 一番下に追加 + if( ! m_treeview.get_row( path_dest ) ) row_new = *( m_treestore->append() ); + else{ + + Gtk::TreeModel::Row row_dest = m_treeview.get_row( path_dest ); + if( row_dest ) + { + // path_destがディレクトリなら下に追加してディレクトリを開く + if( subdir && after && row_dest[ m_columns.m_type ] == TYPE_DIR ){ + row_new = *( m_treestore->prepend( row_dest.children() ) ); + m_treeview.expand_row( path_dest, false ); + } + + // destの下に追加 + else if( after ) row_new = *( m_treestore->insert_after( row_dest ) ); + + // destの前に追加 + else row_new = *( m_treestore->insert( row_dest ) ); + } + } + setup_row( row_new, url, name, type ); + return GET_PATH( row_new ); +} + + + +// +// 行に値をセット +// +void BBSListViewBase::setup_row( Gtk::TreeModel::Row& row, Glib::ustring url, Glib::ustring name, int type ) +{ + row[ m_columns.m_col_url ] = url; + row[ m_columns.m_col_name ] = name; + row[ m_columns.m_type ] = type; + row[ m_columns.m_underline ] = false; + + switch( type ){ + + case TYPE_DIR: + row[ m_columns.m_col_image ] = render_icon( Gtk::Stock::OPEN, Gtk::ICON_SIZE_MENU ); + break; + + case TYPE_BOARD: + row[ m_columns.m_col_image ] = render_icon( Gtk::Stock::NEW, Gtk::ICON_SIZE_MENU ); + break; + + case TYPE_THREAD: + row[ m_columns.m_col_image ] = render_icon( Gtk::Stock::COPY, Gtk::ICON_SIZE_MENU ); + break; + + case TYPE_IMAGE: + row[ m_columns.m_col_image ] = render_icon( Gtk::Stock::NO, Gtk::ICON_SIZE_MENU ); + break; + + case TYPE_LINK: + row[ m_columns.m_col_image ] = render_icon( Gtk::Stock::JUMP_TO, Gtk::ICON_SIZE_MENU ); + break; + } +} + + + +// +// 行の再帰コピー +// +// subdir = true かつdestがディレクトリならサブディレクトリをその下に作ってそこにコピーする。false ならdestの後にコピー +// after = false の場合はdestの前に挿入する +// dest が NULL なら一番下にappend +// +// 成功したら dest にコピーした行のiteratorが入る +// +bool BBSListViewBase::copy_row( Gtk::TreeModel::iterator& src, Gtk::TreeModel::iterator& dest, bool subdir, bool after ) +{ + if( !src ) return false; + if( dest && src == dest ) return false; + + Gtk::TreeModel::iterator it_new; + bool src_is_dir = false, dest_is_dir = false; + + Gtk::TreeModel::Row row_src = ( *src ); + Gtk::TreeModel::Row row_dest = ( *dest ); + + Glib::ustring url = row_src[ m_columns.m_col_url ]; + Glib::ustring name = row_src[ m_columns.m_col_name ]; + int type = row_src[ m_columns.m_type ]; + + if( type == TYPE_DIR ) src_is_dir = true; + if( row_dest && row_dest[ m_columns.m_type ] == TYPE_DIR ) dest_is_dir = true; + +#ifdef _DEBUG + std::cout << "BBSListViewBase::copy_row " << name << std::endl; + if( src_is_dir ) std::cout << "src is directory\n"; + if( dest_is_dir ) std::cout << "dest is directory\n"; +#endif + + // destがNULLなら一番下に追加 + if( ! dest ) it_new = m_treestore->append(); + + // destの下にサブディレクトリ作成 + else if( subdir && after && dest_is_dir ){ + it_new = m_treestore->prepend( row_dest.children() ); + m_treeview.expand_row( GET_PATH( *dest ), false ); + } + + // destの後に追加 + else if( after ) it_new = m_treestore->insert_after( dest ); + + // destの前に追加 + else it_new = m_treestore->insert( dest ); + + Gtk::TreeModel::Row row_tmp = *( it_new ); + setup_row( row_tmp, url, name, type ); + + // srcがdirならサブディレクトリ内の行も再帰的にコピー + if( src_is_dir ){ + Gtk::TreeModel::iterator it_tmp = it_new; + Gtk::TreeModel::iterator it_child = row_src.children().begin(); + bool subdir_tmp = true; + for( ; it_child != row_src.children().end(); ++it_child ){ + copy_row( it_child, it_tmp, subdir_tmp ); + subdir_tmp = false; + } + } + + dest = it_new; + return true; +} + + + +// +// 選択した行をpathの所にまとめて移動 +// +// after = true なら path の後に移動。falseなら前 +// +void BBSListViewBase::move_selected_row( const Gtk::TreePath& path, bool after ) +{ + // 移動できるかチェック + std::list< Gtk::TreeModel::iterator > list_it = m_treeview.get_selected_iterators(); + std::list< Gtk::TreeModel::iterator >::iterator it_src; + for( it_src = list_it.begin() ; it_src != list_it.end(); ++it_src ){ + + Gtk::TreeModel::Path path_src = GET_PATH( ( *it_src ) ); + + // 移動先と送り側が同じならキャンセル + if( path_src.to_string() == path.to_string() ) return; + + // dest がサブディレクトリに含まれないかチェック + if( is_dir( ( *it_src ) ) ){ + if( path.to_string().find( path_src.to_string() ) != Glib::ustring::npos ){ + Gtk::MessageDialog mdiag( "移動先は送り側のディレクトリのサブディレクトリです", false, Gtk::MESSAGE_ERROR ); + mdiag.run(); + return; + } + } + + // path_srcのサブディレクトリ内の行も含まれていたらその行はremove + std::list< Gtk::TreeModel::iterator >::iterator it_tmp = it_src; + for( ++it_tmp ; it_tmp != list_it.end(); ++it_tmp ){ + + Gtk::TreeModel::Path path_tmp = GET_PATH( ( *it_tmp ) ); + if( path_tmp.to_string().find( path_src.to_string() ) != Glib::ustring::npos ){ + list_it.remove( ( *it_tmp ) ); + } + } + } + + // 移動 + Gtk::TreeModel::iterator it_dest = m_treestore->get_iter( path ); + bool subdir = after; + for( it_src = list_it.begin() ; it_src != list_it.end(); ++it_src ){ + + if( copy_row( ( *it_src ), it_dest, subdir, after ) ) m_treestore->erase( ( *it_src ) ); + subdir = false; + after = true;; + } +} + + + +// +// 下線を引く +// +void BBSListViewBase::draw_underline( const Gtk::TreePath& path, bool draw ) +{ + Gtk::TreeModel::Row row = m_treeview.get_row( path ); + if( !row ) return; + + row[ m_columns.m_underline ] = draw; +} + + +// +// ステータス表示 +// +void BBSListViewBase::show_status() +{ + set_status( path2url( m_treeview.get_current_path() ) ); + CORE::core_set_command( "set_status", "", path2url( m_treeview.get_current_path() ) ); +} + + + +// +// 共有バッファからデータを取り出して pathの所に追加 +// +// after = false ならpathの前に追加( デフォルト true ) +// +void BBSListViewBase::append_from_buffer( Gtk::TreeModel::Path path, bool after ) +{ + Gtk::TreeModel::Path path_bkup = path; + +#ifdef _DEBUG + std::cout << "BBSListViewBase::append_from_buffer\n"; +#endif + + bool subdir = true; + std::list< CORE::DATA_INFO > infolist = CORE::SBUF_infolist(); + std::list< CORE::DATA_INFO >::iterator it = infolist.begin(); + for( ; it != infolist.end() ; ++it ){ + + CORE::DATA_INFO& info = ( *it ); +#ifdef _DEBUG + std::cout << "append " << info.name << std::endl; +#endif + + if( info.type != TYPE_DIR_END ){ + path = append_row( info.url, info.name, info.type, path, subdir, after ); + after = true; + } + + if( info.type == TYPE_DIR ) subdir = true; + else{ + + subdir = false; + if( info.type == TYPE_DIR_END ) path.up(); + else if( info.type == TYPE_IMAGE ) DBIMG::set_protect( info.url, true ); + } + } + + path = m_treeview.next_path( path_bkup ); + if( m_treeview.get_row( path ) ){ + m_treeview.scroll_to_row( path, 0 ); + m_treeview.get_selection()->unselect_all(); + } +} + + + +// +// 選択した行をまとめて削除 +// +void BBSListViewBase::delete_selected_rows() +{ + // iterator 取得 + std::list< Gtk::TreeModel::iterator > list_it = m_treeview.get_selected_iterators(); + + if( ! list_it.size() ) return; + + // ディレクトリが無いか確認 + std::list< Gtk::TreeModel::iterator >::iterator it = list_it.begin(); + for( ; it != list_it.end(); ++it ){ + + if( is_dir( (*it ) ) ){ + Gtk::MessageDialog mdiag( "ディレクトリを削除すると中のファイルも削除されます。削除しますか?", + false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_OK_CANCEL ); + if( mdiag.run() != Gtk::RESPONSE_OK ) return; + + break; + } + } + + // まとめて削除 + // ディレクトリ内の列を同時に選択している場合があるので後から消す + it = list_it.end(); + while( it != list_it.begin() ) m_treestore->erase( ( *(--it) ) ); +} + + + +// +// tree -> XML 変換 +// +// +std::string BBSListViewBase::tree2xml() +{ + if( ! m_ready_tree ) return std::string(); + +#ifdef _DEBUG + std::cout << "BBSListViewBase::tree2xml\n"; +#endif + + std::stringstream xml; + + if( m_treestore->children().empty() ) return std::string(); + + Gtk::TreePath path = GET_PATH( m_treestore->children().begin() ); + Gtk::TreeModel::Row row; + + // 座標と選択中のパス + xml << "get_value() << "\""; + Gtk::TreeModel::Path focused_path = m_treeview.get_current_path(); + if( !focused_path.empty() ) xml << " path=\"" << focused_path.to_string() << "\""; + xml << "/>\n"; + + while( 1 ){ + + if( ( row = m_treeview.get_row( path ) ) ){ + + Glib::ustring url = row[ m_columns.m_col_url ]; + Glib::ustring name = row[ m_columns.m_col_name ]; + int type = row[ m_columns.m_type ]; + + switch( type ){ + + case TYPE_DIR: // サブディレクトリ + XML_MAKE_DIR(name); + path.down(); + break; + + case TYPE_BOARD: // 板 + XML_MAKE_BOARD(url,name); + path.next(); + break; + + case TYPE_THREAD: // スレ + XML_MAKE_THREAD(url,name); + path.next(); + break; + + case TYPE_IMAGE: // 画像 + XML_MAKE_IMAGE(url,name); + path.next(); + break; + + case TYPE_COMMENT: // コメント + XML_MAKE_COMMENT(name); + path.next(); + break; + + case TYPE_LINK: // リンク + XML_MAKE_LINK(url,name); + path.next(); + break; + } + } + + // サブディレクトリ内ならupする + else{ + + if( path.get_depth() >= 2 ){ + + path.up(); + + // サブディレクトリが開いてるか開いてないか + xml << "\n"; + else xml << "0\" >\n"; + + path.next(); + } + else break; + } + } + +#ifdef _DEBUG_XML + std::cout << xml.str() << std::endl; +#endif + + return xml.str(); +} + + +// +// XML -> tree 変換 +// +void BBSListViewBase::xml2tree( const std::string& xml ) +{ + JDLIB::Regex regex; + + m_ready_tree = false; + m_treestore->clear(); + + std::list< std::string > lines = MISC::get_lines( xml ); + if( lines.empty() ) return; + + // XML を解析してツリーを作る + Gtk::TreeModel::Row row; + int y = -1; + std::string focused_path; + int level = 0, type; + std::string url, name; + std::list< std::string >::iterator it; + for( it = lines.begin(); it != lines.end(); ++it ){ + + std::string& line = *( it ); + + // 座標 + if( regex.exec( "< *pos +y=\"(.*)\" +path=\"(.*)\" */>", line, 0, true ) ){ + + y = atoi( regex.str( 1 ).c_str() ); + focused_path = regex.str( 2 ); + } + + // タイプ別 + else if( ( type = XML::get_type( line, url, name ) ) != TYPE_UNKNOWN ){ + + // 板やスレ + if( type != TYPE_DIR ){ + Gtk::TreeModel::Row row_tmp = *( m_treestore->append( row.children() ) ); + setup_row( row_tmp, url, name, type ); + } + + // サブディレクトリ + else{ + + // レベル0ならルートに追加 + if( level == 0 ) row = *( m_treestore->append() ); + + // サブディレクトリの中にサブディレクトリを作る + else row = *( m_treestore->append( row.children() ) ); + + setup_row( row, std::string(), name, type ); + + ++level; + } + } + + // サブディレクトリ終わり + else if( regex.exec( "", line, 0, true ) ){ + + // expand + if( regex.str( 1 ) == "1" ){ + + Gtk::TreePath path = GET_PATH( row ); + m_treeview.expand_parents( path ); + m_treeview.expand_row( path, false ); + } + + // 終わり + if( level == 0 ) break; + + row = *( row.parent() ); + --level; + } + } + + // 前回閉じた位置まで移動 + if( !focused_path.empty() ){ + Gtk::TreePath path = Gtk::TreePath( focused_path ); + if( m_treeview.get_row( path ) ){ + m_treeview.set_cursor( path ); + } + } + + if( y != -1 ){ + // この段階ではまだスクロールバーが表示されてない時があるのでclock_in()で移動する + m_jump_y = y; + } + + m_ready_tree = true; +} + + + +// +// 検索 +// +// m_search_invert = true なら前方検索 +// +void BBSListViewBase::search() +{ + JDLIB::Regex regex; + + focus_view(); + std::string query = m_toolbar.m_entry_search.get_text(); + if( query.empty() ) 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; + } + + Gtk::TreePath path_start = path; + +#ifdef _DEBUG + std::cout << "BBSListViewBase::search() path = " << path.to_string() << " query = " << query << std::endl; +#endif + + 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( *( m_treestore->children().rbegin() ) ); + else path = m_treeview.prev_path( path, false ); + } + + if( path == path_start ) break; + + Glib::ustring name = path2name( path ); + Glib::ustring url = path2url( path ); + + if( regex.exec( query, name, 0, true ) || regex.exec( query, url, 0, true ) ){ + m_treeview.expand_parents( path ); + m_treeview.scroll_to_row( path, 0 ); + m_treeview.set_cursor( path ); + show_status(); + return; + } + } +} + + +// 前検索 +void BBSListViewBase::slot_push_up_search() +{ + m_search_invert = true; + search(); +} + + + +// 後検索 +void BBSListViewBase::slot_push_down_search() +{ + m_search_invert = false; + search(); +} + + +// +// 検索entryの操作 +// +void BBSListViewBase::slot_entry_operate( int controlid ) +{ + if( controlid == CONTROL::Cancel ) focus_view(); +} diff --git a/src/bbslist/bbslistviewbase.h b/src/bbslist/bbslistviewbase.h new file mode 100644 index 000000000..591993f4e --- /dev/null +++ b/src/bbslist/bbslistviewbase.h @@ -0,0 +1,163 @@ +// ライセンス: 最新のGPL + +// +// 板ビュークラスのベースクラス +// + +#ifndef _BBSLISTVIEWBASE_H +#define _BBSLISTVIEWBASE_H + +#include "skeleton/view.h" +#include "skeleton/treeview.h" + +#include "jdlib/constptr.h" + +#include "columns.h" +#include "toolbar.h" + +#include + + +namespace BBSLIST +{ + class BBSListViewBase : public SKELETON::View + { + private: + + Glib::RefPtr< Gtk::TreeStore > m_treestore; + SKELETON::JDTreeView m_treeview; + BBSListtToolBar m_toolbar; + + bool m_ready_tree; // ツリーがセットされているならtrue + + BBSLIST::TreeColumns m_columns; + + Gtk::ScrolledWindow m_scrwin; + JDLIB::ConstPtr< Gtk::CellRendererText > m_ren_text; + + // ポップアップメニュー用 + Gtk::TreeModel::Path m_path_selected; + + // クロック入力されたときにtreeview のスクロールバーを指定した位置に移動する + // clock_in()の説明を参照 + int m_jump_y; + + // D&D 用 + int m_dnd_counter; + Gtk::TreePath m_drag_path_uline;; + + // サーチで使う変数 + bool m_search_invert; + std::string m_pre_query; + + // fucus_viewを一回キャンセルする + // ポップアップメニューが消えたときにfocus_viewが呼び出されるが + // 名前の変更など都合の悪いときはキャンセルする + bool m_cancel_focus; + + // あるフォルダを開いたときに他のフォルダを閉じる + bool m_expand_collapse; + bool m_expanding; // 行を開いている最中ならtrueにしてsignal_row_collapsed()を無視する + + protected: + + Glib::RefPtr< Gtk::TreeStore >& get_treestore() { return m_treestore; } + SKELETON::JDTreeView& get_treeview() { return m_treeview; } + const BBSListtToolBar& get_toolbar() const{ return m_toolbar; } + const bool& get_ready_tree() const{ return m_ready_tree; } + void set_expand_collapse( bool set ){ m_expand_collapse = set; } + + public: + + BBSListViewBase( const std::string& url, const std::string& arg1 = std::string() , const std::string& arg2 = std::string() ); + virtual ~BBSListViewBase(); + + // SKELETON::View の関数のオーバロード + virtual const std::string url_for_copy(){ return std::string(); } + + virtual void clock_in(); + virtual void relayout(); + virtual void focus_view(); + virtual void focus_out(); + virtual void operate_view( const int& control ); + virtual void goto_top(); + virtual void goto_bottom(); + + // selectdialogで使う + Gtk::TreePath get_current_path() { return m_treeview.get_current_path(); } + void copy_treestore( Glib::RefPtr< Gtk::TreeStore >& store ); + + private: + + void row_up(); + void row_down(); + + bool slot_button_press( GdkEventButton* event ); + bool slot_button_release( GdkEventButton* event ); + bool slot_motion( GdkEventMotion* event ); + bool slot_key_press( GdkEventKey* event ); + bool slot_key_release( GdkEventKey* event ); + void slot_open_tab(); + void slot_open_browser(); + void slot_append_favorite(); + void slot_newdir(); + void slot_newcomment(); + void slot_rename(); + void slot_copy_url(); + void slot_copy_title_url(); + void slot_unselect_all(); + 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_ren_text_on_edited( const Glib::ustring& path, const Glib::ustring& text ); + + // D&D 関係 + void slot_drag_begin(); + void slot_drag_motion( Gtk::TreeModel::Path path ); + void slot_drag_drop( Gtk::TreeModel::Path path ); + void slot_drag_end(); + void slot_receive_dnd_begin(); + void slot_receive_dnd_end(); + + Gtk::TreeViewColumn* create_column(); + virtual bool open_row( Gtk::TreePath& path, bool tab ); + Glib::ustring path2url( const Gtk::TreePath& path ); + Glib::ustring path2name( const Gtk::TreePath& path ); + int path2type( const Gtk::TreePath& path ); + bool is_dir( Gtk::TreeModel::iterator& it ); + bool is_dir( const Gtk::TreePath& path ); + + Gtk::TreeModel::Path append_row( const std::string& url, const std::string& name, int type, + Gtk::TreeModel::Path path_dest = Gtk::TreeModel::Path() + , bool subdir = true, bool after = true ); + + void setup_row( Gtk::TreeModel::Row& row, Glib::ustring url, Glib::ustring name, int type ); + bool copy_row( Gtk::TreeModel::iterator& src, Gtk::TreeModel::iterator& dest, bool subdir, bool after = true ); + void move_selected_row( const Gtk::TreePath& path, bool after ); + + void draw_underline( const Gtk::TreePath& path, bool draw ); + void show_status(); + + // 検索 + void search(); + void slot_push_down_search(); + void slot_push_up_search(); + void slot_entry_operate( int controlid ); + + protected: + + virtual void show_popupmenu( const Gtk::TreePath& path ){} // popupメニューは派生ビューで指定する + + void append_from_buffer( Gtk::TreeModel::Path path, bool after = true ); + void delete_selected_rows(); + + // tree <-> XML 変換 + std::string tree2xml(); + void xml2tree( const std::string& xml ); + + private: + void set_info_to_sharedbuffer( Gtk::TreePath& path ); + }; +}; + + +#endif diff --git a/src/bbslist/columns.h b/src/bbslist/columns.h new file mode 100644 index 000000000..4b663a83d --- /dev/null +++ b/src/bbslist/columns.h @@ -0,0 +1,37 @@ +// ライセンス: 最新のGPL + +// +// コラム +// + +#ifndef _BBSLISTCOLUMNS_H +#define _BBSLISTCOLUMNS_H + +#include + +namespace BBSLIST +{ + class TreeColumns : public Gtk::TreeModel::ColumnRecord + { + + public: + + Gtk::TreeModelColumn< Glib::ustring > m_col_name; + Gtk::TreeModelColumn< bool > m_underline; + Gtk::TreeModelColumn< Glib::ustring > m_col_url; + Gtk::TreeModelColumn< Glib::RefPtr< Gdk::Pixbuf > > m_col_image; + Gtk::TreeModelColumn< int > m_type; + + TreeColumns(){ + add( m_col_name ); + add( m_underline ); + add( m_col_url ); + add( m_col_image ); + add( m_type ); + } + + ~TreeColumns(){} + }; +} + +#endif diff --git a/src/bbslist/etclistview.cpp b/src/bbslist/etclistview.cpp new file mode 100644 index 000000000..8a215695c --- /dev/null +++ b/src/bbslist/etclistview.cpp @@ -0,0 +1,53 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "etclistview.h" +#include "bbslistadmin.h" + +#include "dbtree/interface.h" + +using namespace BBSLIST; + + +EtcListView::EtcListView( const std::string& url, + const std::string& arg1, const std::string& arg2 ) + : BBSListViewBase( url, arg1, arg2 ) +{} + + +EtcListView::~EtcListView() +{ +#ifdef _DEBUG + std::cout << "EtcListView::~EtcListView : " << get_url() << std::endl; +#endif +} + + + +// +// 表示 +// +void EtcListView::show_view() +{ + xml2tree( DBTREE::get_xml_etc() ); + BBSLIST::get_admin()->set_command( "set_tablabel", get_url(), "Etc" ); +} + + + +// +// ポップアップメニュー +// +void EtcListView::show_popupmenu( const Gtk::TreePath& path ) +{ + if( path.empty() ) return; + + Gtk::Menu* popupmenu; + std::list< Gtk::TreeModel::iterator > list_it = get_treeview().get_selected_iterators(); + if( list_it.size() == 1 ) popupmenu = dynamic_cast< Gtk::Menu* >( ui_manager()->get_widget( "/popup_menu" ) ); + else popupmenu = dynamic_cast< Gtk::Menu* >( ui_manager()->get_widget( "/popup_menu_mul" ) ); + + if( popupmenu ) popupmenu->popup( 0, gtk_get_current_event_time() ); +} diff --git a/src/bbslist/etclistview.h b/src/bbslist/etclistview.h new file mode 100644 index 000000000..485888c07 --- /dev/null +++ b/src/bbslist/etclistview.h @@ -0,0 +1,28 @@ +// ライセンス: 最新のGPL + +// +// 外部板ビュー +// + +#ifndef _ETCLISTVIEW_H +#define _ETCLISTVIEW_H + +#include "bbslistviewbase.h" + +namespace BBSLIST +{ + class EtcListView : public BBSListViewBase + { + public: + EtcListView( const std::string& url, const std::string& arg1 = std::string() , const std::string& arg2 = std::string() ); + virtual ~EtcListView(); + + virtual void show_view(); + + protected: + virtual void show_popupmenu( const Gtk::TreePath& path ); + }; +}; + + +#endif diff --git a/src/bbslist/favoriteview.cpp b/src/bbslist/favoriteview.cpp new file mode 100644 index 000000000..b86477279 --- /dev/null +++ b/src/bbslist/favoriteview.cpp @@ -0,0 +1,110 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "favoriteview.h" +#include "bbslistadmin.h" +#include "selectdialog.h" + +#include "cache.h" +#include "sharedbuffer.h" + +using namespace BBSLIST; + + +FavoriteListView::FavoriteListView( const std::string& url, + const std::string& arg1, const std::string& arg2 ) + : BBSListViewBase( url, arg1, arg2 ) +{ + // D&D可 + get_treeview().set_reorderable_view( true ); +} + + +FavoriteListView::~FavoriteListView() +{ +#ifdef _DEBUG + std::cout << "FavoriteList::~FavoriteList : " << get_url() << std::endl; +#endif + + save_xml( CACHE::path_xml_favorite() ); +} + + +void FavoriteListView::shutdown() +{ +#ifdef _DEBUG + std::cout << "FavoriteList::shutdown\n"; +#endif + save_xml( CACHE::path_xml_favorite_bkup() ); +} + + +// +// コマンド +// +bool FavoriteListView::set_command( const std::string& command, const std::string& arg ) +{ + if( command == "append_favorite" ) append_favorite(); + if( command == "save_favorite" ) save_xml( CACHE::path_xml_favorite() ); + + return true; +} + + + +// +// 表示 +// +void FavoriteListView::show_view() +{ + BBSLIST::get_admin()->set_command( "set_tablabel", get_url(), "Favorite" ); + + std::string xml; + CACHE::load_rawdata( CACHE::path_xml_favorite() , xml ); + xml2tree( xml ); +} + + + +// +// ポップアップメニュー +// +void FavoriteListView::show_popupmenu( const Gtk::TreePath& path ) +{ + Gtk::Menu* popupmenu; + + if( path.empty() ) popupmenu = dynamic_cast< Gtk::Menu* >( ui_manager()->get_widget( "/popup_menu_favorite_space" ) ); + else{ + std::list< Gtk::TreeModel::iterator > list_it = get_treeview().get_selected_iterators(); + if( list_it.size() == 1 ) popupmenu = dynamic_cast< Gtk::Menu* >( ui_manager()->get_widget( "/popup_menu_favorite" ) ); + else popupmenu = dynamic_cast< Gtk::Menu* >( ui_manager()->get_widget( "/popup_menu_favorite_mul" ) ); + } + + if( popupmenu ) popupmenu->popup( 0, gtk_get_current_event_time() ); +} + + +// +// お気に入りにアイテム追加 +// +// あらかじめ共有バッファにデータを入れておくこと +// +void FavoriteListView::append_favorite() +{ + if( CORE::SBUF_size() == 0 ) return; + + SelectListDialog diag( get_url(), get_treestore() ); + if( diag.run() != Gtk::RESPONSE_OK ) return; + append_from_buffer( diag.get_path() ); +} + + +// +// お気に入り保存 +// +void FavoriteListView::save_xml( const std::string& file ) +{ + if( get_ready_tree() ) CACHE::save_rawdata( file , tree2xml() ); +} diff --git a/src/bbslist/favoriteview.h b/src/bbslist/favoriteview.h new file mode 100644 index 000000000..a5bb71885 --- /dev/null +++ b/src/bbslist/favoriteview.h @@ -0,0 +1,41 @@ +// ライセンス: 最新のGPL + +// お気に入りビュー + +#ifndef _FAVORITEVIEW_H +#define _FAVORITEVIEW_H + +#include "bbslistviewbase.h" + +namespace BBSLIST +{ + // お気に入りビュー + class FavoriteListView : public BBSListViewBase + { + public: + + FavoriteListView( const std::string& url, const std::string& arg1 = std::string() , const std::string& arg2 = std::string() ); + virtual ~FavoriteListView(); + + virtual bool set_command( const std::string& command, const std::string& arg = std::string() ); + + virtual void shutdown(); + + virtual void show_view(); + virtual void delete_view(){ delete_selected_rows(); } + + protected: + + virtual void show_popupmenu( const Gtk::TreePath& path ); + + private: + + // お気に入りにアイテム追加 + // あらかじめ共有バッファに追加するデータをセットしておくこと + void append_favorite(); + + void save_xml( const std::string& file ); + }; +} + +#endif diff --git a/src/bbslist/selectdialog.cpp b/src/bbslist/selectdialog.cpp new file mode 100644 index 000000000..7ef7283e0 --- /dev/null +++ b/src/bbslist/selectdialog.cpp @@ -0,0 +1,40 @@ +// ライセンス: 最新のGPL + +#include "selectdialog.h" +#include "bbslistviewbase.h" + +#include "viewfactory.h" + +using namespace BBSLIST; + + +SelectListDialog::SelectListDialog( const std::string& url, Glib::RefPtr< Gtk::TreeStore >& store ) +{ + m_selectview = dynamic_cast< BBSListViewBase* > ( CORE::ViewFactory( CORE::VIEW_SELECTLIST, url ) ); + if( m_selectview ){ + m_selectview->copy_treestore( store ); + get_vbox()->pack_start( *m_selectview ); + m_selectview->focus_view(); + } + + add_button( Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL ); + add_button( Gtk::Stock::OK, Gtk::RESPONSE_OK ); + + set_title( "挿入先選択" ); + resize( 600, 400 ); + show_all_children(); +} + + +SelectListDialog::~SelectListDialog() +{ + if( m_selectview ) delete m_selectview; +} + + +Gtk::TreePath SelectListDialog::get_path() +{ + if( m_selectview ) return m_selectview->get_current_path(); + + return Gtk::TreePath(); +} diff --git a/src/bbslist/selectdialog.h b/src/bbslist/selectdialog.h new file mode 100644 index 000000000..3db7dd0e8 --- /dev/null +++ b/src/bbslist/selectdialog.h @@ -0,0 +1,31 @@ +// ライセンス: 最新のGPL + +// +// お気に入り追加の時の選択ダイアログ +// + +#ifndef _SELECTDIALOG_H +#define _SELECTDIALOG_H + +#include + +namespace BBSLIST +{ + class BBSListViewBase; + + // ダイアログ + class SelectListDialog : public Gtk::Dialog + { + BBSListViewBase* m_selectview; + + public: + + SelectListDialog( const std::string& url, Glib::RefPtr< Gtk::TreeStore >& store ); + virtual ~SelectListDialog(); + + Gtk::TreePath get_path(); + }; +}; + + +#endif diff --git a/src/bbslist/selectlistview.cpp b/src/bbslist/selectlistview.cpp new file mode 100644 index 000000000..6de12a76f --- /dev/null +++ b/src/bbslist/selectlistview.cpp @@ -0,0 +1,33 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "selectlistview.h" + +using namespace BBSLIST; + +SelectListView::SelectListView( const std::string& url, const std::string& arg1, const std::string& arg2) + : BBSListViewBase( url, arg1, arg2 ) +{ + // D&D可 + get_treeview().set_reorderable_view( true ); +} + + +// +// ポップアップメニュー +// +void SelectListView::show_popupmenu( const Gtk::TreePath& path ) +{ + Gtk::Menu* popupmenu; + + if( path.empty() ) popupmenu = dynamic_cast< Gtk::Menu* >( ui_manager()->get_widget( "/popup_menu_favorite_space" ) ); + else{ + std::list< Gtk::TreeModel::iterator > list_it = get_treeview().get_selected_iterators(); + if( list_it.size() == 1 ) popupmenu = dynamic_cast< Gtk::Menu* >( ui_manager()->get_widget( "/popup_menu_select" ) ); + else popupmenu = dynamic_cast< Gtk::Menu* >( ui_manager()->get_widget( "/popup_menu_favorite_mul" ) ); + } + + if( popupmenu ) popupmenu->popup( 0, gtk_get_current_event_time() ); +} diff --git a/src/bbslist/selectlistview.h b/src/bbslist/selectlistview.h new file mode 100644 index 000000000..90b84dc34 --- /dev/null +++ b/src/bbslist/selectlistview.h @@ -0,0 +1,31 @@ +// ライセンス: 最新のGPL + +// +// お気に入り追加の時の選択ビュー +// + +#ifndef _SELECTLISTVIEW_H +#define _SELECTLISTVIEW_H + +#include "bbslistviewbase.h" + +namespace BBSLIST +{ + class SelectListView : public BBSListViewBase + { + public: + + SelectListView( const std::string& url, const std::string& arg1 = std::string() , const std::string& arg2 = std::string() ); + virtual ~SelectListView(){} + + virtual void delete_view(){ delete_selected_rows(); } + + private: + virtual bool open_row( Gtk::TreePath& path, bool tab ){ return true; } // 開かないようにキャンセル + virtual void show_popupmenu( const Gtk::TreePath& path ); + }; + +}; + + +#endif diff --git a/src/bbslist/toolbar.h b/src/bbslist/toolbar.h new file mode 100644 index 000000000..9101a1d1b --- /dev/null +++ b/src/bbslist/toolbar.h @@ -0,0 +1,43 @@ +// ライセンス: 最新のGPL + +// ツールバーのクラス + +#ifndef _BBSLIST_TOOLBAR_H +#define _BBSLIST_TOOLBAR_H + +#include + +#include "skeleton/imgbutton.h" +#include "skeleton/entry.h" + +#include "controlutil.h" +#include "controlid.h" + +namespace BBSLIST +{ + class BBSListtToolBar : public Gtk::HBox + { + friend class BBSListViewBase; + + SKELETON::JDEntry m_entry_search; + SKELETON::ImgButton m_button_up_search; + SKELETON::ImgButton m_button_down_search; + + Gtk::Tooltips m_tooltip; + + BBSListtToolBar() : + m_button_up_search( Gtk::Stock::GO_UP ), + m_button_down_search( Gtk::Stock::GO_DOWN ) + { + m_tooltip.set_tip( m_button_up_search, CONTROL::get_label_motion( CONTROL::SearchPrev ) ); + m_tooltip.set_tip( m_button_down_search, CONTROL::get_label_motion( CONTROL::SearchNext ) ); + pack_start( m_entry_search ); + pack_end( m_button_up_search, Gtk::PACK_SHRINK ); + pack_end( m_button_down_search, Gtk::PACK_SHRINK ); + } + + }; +} + + +#endif diff --git a/src/board/Makefile.am b/src/board/Makefile.am new file mode 100644 index 000000000..aed445ab9 --- /dev/null +++ b/src/board/Makefile.am @@ -0,0 +1,16 @@ +noinst_LIBRARIES = libboard.a + +libboard_a_SOURCES = \ + boardadmin.cpp \ + boardview.cpp \ + preference.cpp + +noinst_HEADERS = \ + boardadmin.h \ + boardview.h \ + toolbar.h \ + columns.h \ + preference.h + +AM_CXXFLAGS = @GTKMM_CFLAGS@ +INCLUDES = -I$(top_srcdir)/src diff --git a/src/board/boardadmin.cpp b/src/board/boardadmin.cpp new file mode 100644 index 000000000..36ac268ca --- /dev/null +++ b/src/board/boardadmin.cpp @@ -0,0 +1,137 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "boardadmin.h" + +#include "dbtree/interface.h" + +#include "skeleton/view.h" + +#include "jdlib/miscutil.h" + +#include "global.h" +#include "viewfactory.h" +#include "dndmanager.h" +#include "sharedbuffer.h" +#include "session.h" + +BOARD::BoardAdmin *instance_boardadmin = NULL; + +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 = NULL; +} + + +using namespace BOARD; + +BoardAdmin::BoardAdmin( const std::string& url ) + : SKELETON::Admin( url ) +{ + //D&D可 + get_notebook().set_dragable( true ); +} + + +BoardAdmin::~BoardAdmin() +{ +#ifdef _DEBUG + std::cout << "BoardAdmin::~BoardAdmin\n"; +#endif + + // 開いているURLを保存 + SESSION::set_board_URLs( get_URLs() ); + SESSION::set_board_page( get_current_page() ); +} + + + +// 前回開いていたURLを復元 +void BoardAdmin::restore() +{ + bool online = SESSION::is_online(); + SESSION::set_online( false ); + + std::list< std::string > list_tmp; + std::list< std::string >::iterator it_tmp; + + list_tmp = SESSION::board_URLs(); + it_tmp = list_tmp.begin(); + for( ; it_tmp != list_tmp.end(); ++it_tmp ){ + + if( !(*it_tmp).empty() ){ + COMMAND_ARGS command_arg; + command_arg.command = "open_view"; + command_arg.url = (*it_tmp); + command_arg.arg1 = "true"; + + open_view( command_arg ); + } + } + + SESSION::set_online( online ); + set_command( "set_page", std::string(), MISC::itostr( SESSION::board_page() ) ); +} + + +SKELETON::View* BoardAdmin::create_view( const COMMAND_ARGS& command ) +{ + CORE::VIEWFACTORY_ARGS view_args; + view_args.arg1 = command.arg3; + view_args.arg2 = command.arg4; + + SKELETON::View* view = CORE::ViewFactory( CORE::VIEW_BOARDVIEW, command.url, view_args ); + return view; +} + + +// +// タブのD&Dを開始 +// +void BoardAdmin::slot_drag_begin( int page ) +{ + SKELETON::View* view = ( SKELETON::View* )get_notebook().get_nth_page( page ); + if( !view ) return; + + std::string url = view->get_url(); + + CORE::DND_Begin( get_url() ); + + CORE::DATA_INFO info; + info.type = TYPE_BOARD; + info.url = DBTREE::url_boardbase( url ); + info.name = DBTREE::board_name( info.url ); + +#ifdef _DEBUG + std::cout << "BoardAdmin::slot_drag_begin " << info.name << std::endl; +#endif + + CORE::SBUF_clear_info(); + CORE::SBUF_append( info ); +} + + + +// +// タブのD&D終了 +// +void BoardAdmin::slot_drag_end() +{ +#ifdef _DEBUG + std::cout << "BoardAdmin::slot_drag_end\n"; +#endif + + CORE::DND_End(); +} diff --git a/src/board/boardadmin.h b/src/board/boardadmin.h new file mode 100644 index 000000000..b6d96e821 --- /dev/null +++ b/src/board/boardadmin.h @@ -0,0 +1,35 @@ +// ライセンス: 最新のGPL + +// +// 個別の板の管理クラス +// +#ifndef _BOARDADMIN_H +#define _BOARDADMIN_H + +#include "skeleton/admin.h" + +#include + +namespace BOARD +{ + class BoardAdmin : public SKELETON::Admin + { + public: + BoardAdmin( const std::string& url ); + ~BoardAdmin(); + + protected: + SKELETON::View* create_view( const COMMAND_ARGS& command ); + + virtual void restore(); + + private: + virtual void slot_drag_begin( int page ); + virtual void slot_drag_end(); + }; + + BoardAdmin* get_admin(); + void delete_admin(); +} + +#endif diff --git a/src/board/boardview.cpp b/src/board/boardview.cpp new file mode 100644 index 000000000..a417973ee --- /dev/null +++ b/src/board/boardview.cpp @@ -0,0 +1,1566 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "boardadmin.h" +#include "boardview.h" +#include "preference.h" + +#include "jdlib/miscutil.h" +#include "jdlib/jdregex.h" + +#include "dbtree/interface.h" +#include "dbtree/articlebase.h" + +#include "command.h" +#include "session.h" +#include "dndmanager.h" +#include "sharedbuffer.h" +#include "global.h" +#include "controlid.h" + +#include // m_liststore->gobj()->sort_column_id = -2 +#include // GTK_CHECK_VERSION +#include + +using namespace BOARD; + + +// row -> path +#define GET_PATH( row ) m_liststore->get_path( row ) + + + +// 自動ソート抑制 +// -2 = DEFAULT_UNSORTED_COLUMN_ID +// +// 列に値をセットする前にUNSORTED_COLUMN()しておかないと +// いちいちソートをかけるので極端に遅くなる +#define UNSORTED_COLUMN() do{ m_liststore->gobj()->sort_column_id = -2; } while(0) + + +#define DEFAULT_COLMUN_WIDTH 50 + +enum{ + COL_MARKVAL_UPDATED = 0, + COL_MARKVAL_CACHED, + COL_MARKVAL_NORMAL, + COL_MARKVAL_OLD +}; + + + +// set_sizing( Gtk::TREE_VIEW_COLUMN_FIXED ) を指定して append_columnする +#define APPEND_COLUMN(title,model) do{ \ +Gtk::TreeView::Column* col = Gtk::manage( new Gtk::TreeViewColumn( title, model ) ); \ +col->set_sizing( Gtk::TREE_VIEW_COLUMN_FIXED ); \ +m_treeview.append_column( *col ); \ +}while(0) + + + +BoardView::BoardView( const std::string& url,const std::string& arg1, const std::string& arg2 ) + : SKELETON::View( url ) +{ + m_scrwin.add( m_treeview ); + m_scrwin.set_policy( Gtk::POLICY_AUTOMATIC, Gtk::POLICY_ALWAYS ); + + m_toolbar.m_entry_search.signal_activate().connect( sigc::mem_fun( *this, &BoardView::search ) ); + m_toolbar.m_button_close.signal_clicked().connect( sigc::mem_fun( *this, &BoardView::close_view ) ); + m_toolbar.m_button_reload.signal_clicked().connect( sigc::mem_fun( *this, &BoardView::reload ) ); + m_toolbar.m_button_stop.signal_clicked().connect( sigc::mem_fun( *this, &BoardView::stop ) ); + m_toolbar.m_button_new_article.signal_clicked().connect( sigc::mem_fun( *this, &BoardView::slot_new_article ) ); + m_toolbar.m_button_delete.signal_clicked().connect( sigc::mem_fun( *this, &BoardView::slot_push_delete ) ); + m_toolbar.m_button_favorite.signal_clicked().connect( sigc::mem_fun( *this, &BoardView::slot_push_favorite ) ); + m_toolbar.m_button_up_search.signal_clicked().connect( sigc::mem_fun( *this, &BoardView::slot_push_up_search ) ); + m_toolbar.m_button_down_search.signal_clicked().connect( sigc::mem_fun( *this, &BoardView::slot_push_down_search ) ); + m_toolbar.m_button_preferences.signal_clicked().connect( sigc::mem_fun(*this, &BoardView::slot_push_preferences ) ); + m_toolbar.m_entry_search.signal_operate().connect( sigc::mem_fun( *this, &BoardView::slot_entry_operate ) ); + + pack_start( m_toolbar, Gtk::PACK_SHRINK ); + pack_start( m_scrwin ); + show_all_children(); + + // ツリービュー設定 + m_liststore = Gtk::ListStore::create( m_columns ); + m_treeview.set_model( m_liststore ); + +#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 << "BoardView::BoardView : m_treeview.set_fixed_height_mode\n"; +#endif + +#endif + + // columnのappend + APPEND_COLUMN( " ", m_columns.m_col_mark ); + APPEND_COLUMN( "ID", m_columns.m_col_id ); + APPEND_COLUMN( "name", m_columns.m_col_subject ); + APPEND_COLUMN( "res", m_columns.m_col_res ); + APPEND_COLUMN( "load", m_columns.m_col_str_load ); + APPEND_COLUMN( "new", m_columns.m_col_str_new ); + APPEND_COLUMN( "since", m_columns.m_col_since ); + APPEND_COLUMN( "write", m_columns.m_col_write ); + APPEND_COLUMN( "speed", m_columns.m_col_speed ); + m_treeview.set_column_for_height( 2 ); + + // サイズを調整しつつソートの設定 + for( guint i = 0; i < COL_MARK_VAL; i++ ){ + + Gtk::TreeView::Column* column = m_treeview.get_column( i ); + + int width = 0; + + switch( i ){ + + case COL_MARK: + width = SESSION::col_mark(); + break; + + case COL_ID: + width = SESSION::col_id(); + break; + + case COL_SUBJECT: + width = SESSION::col_subject(); + 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_SPEED: + width = SESSION::col_speed(); + break; + } + + if( ! width ) width = DEFAULT_COLMUN_WIDTH; + + column->set_fixed_width( width ); + column->set_resizable( true ); + column->set_clickable( true ); + + // ヘッダをクリックしたときに呼ぶslotの設定 + if( i == COL_MARK ) column->signal_clicked().connect( sigc::mem_fun(*this, &BoardView::slot_mark_clicked ) ); + else if( i == COL_ID ) column->signal_clicked().connect( sigc::mem_fun(*this, &BoardView::slot_id_clicked ) ); + else column->signal_clicked().connect( sigc::bind< int >( sigc::mem_fun( *this, &BoardView::slot_col_clicked ), i ) ); + + // subjectの背景色設定 + if( i == COL_SUBJECT ){ + Gtk::CellRendererText* rentext = dynamic_cast< Gtk::CellRendererText* >( column->get_first_cell_renderer() ); + if( rentext ){ + rentext->property_cell_background().set_value( "yellow" ); + column->add_attribute( *rentext, "cell_background_set", COL_DRAWBG ); + } + } + } + + // ソート関数セット + m_liststore->set_sort_func( COL_MARK_VAL, sigc::mem_fun( *this, &BoardView::slot_compare_mark_val ) ); + m_liststore->set_sort_func( COL_ID, sigc::mem_fun( *this, &BoardView::slot_compare_num_id ) ); + m_liststore->set_sort_func( COL_SUBJECT, sigc::mem_fun( *this, &BoardView::slot_compare_subject ) ); + m_liststore->set_sort_func( COL_RES, sigc::mem_fun( *this, &BoardView::slot_compare_num_res ) ); + m_liststore->set_sort_func( COL_LOAD, sigc::mem_fun( *this, &BoardView::slot_compare_num_load ) ); + m_liststore->set_sort_func( COL_NEW, sigc::mem_fun( *this, &BoardView::slot_compare_new ) ); + m_liststore->set_sort_func( COL_SINCE_T, sigc::mem_fun( *this, &BoardView::slot_compare_since_t ) ); + m_liststore->set_sort_func( COL_WRITE_T, sigc::mem_fun( *this, &BoardView::slot_compare_write_t) ); + m_liststore->set_sort_func( COL_SPEED, sigc::mem_fun( *this, &BoardView::slot_compare_speed ) ); + + m_treeview.sig_button_press().connect( sigc::mem_fun(*this, &BoardView::slot_button_press ) ); + m_treeview.sig_button_release().connect( sigc::mem_fun(*this, &BoardView::slot_button_release ) ); + m_treeview.sig_motion().connect( sigc::mem_fun(*this, &BoardView::slot_motion ) ); + m_treeview.sig_key_press().connect( sigc::mem_fun(*this, &BoardView::slot_key_press ) ); + + // D&D設定 + m_treeview.set_reorderable_view( true ); + m_treeview.sig_drag_begin().connect( sigc::mem_fun(*this, &BoardView::slot_drag_begin ) ); + m_treeview.sig_drag_end().connect( sigc::mem_fun(*this, &BoardView::slot_drag_end ) ); + + + // ポップアップメニューの設定 + // アクショングループを作ってUIマネージャに登録 + action_group() = Gtk::ActionGroup::create(); + action_group()->add( Gtk::Action::create( "OpenTab", "タブで開く"), sigc::mem_fun( *this, &BoardView::slot_open_tab ) ); + action_group()->add( Gtk::Action::create( "Favorite_Article", "スレをお気に入りに追加"), sigc::mem_fun( *this, &BoardView::slot_favorite_thread ) ); + action_group()->add( Gtk::Action::create( "Favorite_Board", "板をお気に入りに登録"), sigc::mem_fun( *this, &BoardView::slot_favorite_board ) ); + action_group()->add( Gtk::Action::create( "GotoTop", "一番上に移動"), sigc::mem_fun( *this, &BoardView::goto_top ) ); + action_group()->add( Gtk::Action::create( "GotoBottom", "一番下に移動"), sigc::mem_fun( *this, &BoardView::goto_bottom ) ); + action_group()->add( Gtk::Action::create( "Delete_Menu", "Delete" ) ); + action_group()->add( Gtk::Action::create( "Delete", "選択した行のログを削除する"), sigc::mem_fun( *this, &BoardView::delete_view ) ); + action_group()->add( Gtk::Action::create( "Unselect", "選択解除"), sigc::mem_fun( *this, &BoardView::slot_unselect_all ) ); + action_group()->add( Gtk::Action::create( "CopyURL", "URLをコピー"), sigc::mem_fun( *this, &BoardView::slot_copy_url ) ); + action_group()->add( Gtk::Action::create( "CopyTitleURL", "タイトルとURLをコピー"), sigc::mem_fun( *this, &BoardView::slot_copy_title_url ) ); + action_group()->add( Gtk::Action::create( "OpenBrowser", "ブラウザで開く"), sigc::mem_fun( *this, &BoardView::slot_open_browser ) ); + + + ui_manager() = Gtk::UIManager::create(); + ui_manager()->insert_action_group( action_group() ); + + Glib::ustring str_ui = + "" + + // 通常 + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + + + // 通常 + 複数 + "" + "" + "" + "" + "" + "" + "" + "" + "" + + + // お気に入りボタン押した時のメニュー + "" + "" + "" + "" + + + // 削除ボタン押した時のメニュー + "" + "" + "" + + + ""; + + ui_manager()->add_ui_from_string( str_ui ); + + // ポップアップメニューにキーアクセレータを表示 + 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 ); + + // マウスジェスチォ可能 + SKELETON::View::set_enable_mg( true ); + + // コントロールモード設定 + SKELETON::View::get_control().set_mode( CONTROL::MODE_BOARD ); +} + + +BoardView::~BoardView() +{ +#ifdef _DEBUG + std::cout << "BoardView::~BoardView : " << get_url() << std::endl; +#endif + DBTREE::board_save_info( get_url() ); + save_column_width(); +} + + + +// +// コピー用URL(メインウィンドウのURLバーなどに表示する) +// +const std::string BoardView::url_for_copy() +{ + return DBTREE::url_boardbase( get_url() ); +} + + + +// +// 列の幅の大きさを取得してセッションデータベース更新 +// +void BoardView::save_column_width() +{ +#ifdef _DEBUG + std::cout << "save_column_width " << get_url() << std::endl; +#endif + + for( guint i = 0; i < COL_MARK_VAL; i++ ){ + + Gtk::TreeView::Column* column = m_treeview.get_column( i ); + + int width = 0; + if( column ) width = column->get_width(); + if( !width ) continue; + + switch( i ){ + + case COL_MARK: + SESSION::set_col_mark( width ); + break; + + case COL_ID: + SESSION::set_col_id( 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_SPEED: + SESSION::set_col_speed( width ); + break; + } + } +} + + + +// +// ヘッダをクリックしたときのslots +// + +// mark をクリック +void BoardView::slot_mark_clicked() +{ + // 前回と違う列をソートした後にクリックしたら ASCENDING + if( m_previous_col != COL_MARK_VAL ){ + m_previous_col = COL_MARK_VAL; + m_ascend = false; + } + + slot_col_clicked( COL_MARK_VAL ); +} + +// id をクリック +void BoardView::slot_id_clicked() +{ + // 前回違う列をソートした後にクリックしたら ASCENDING + if( m_previous_col != COL_ID ){ + m_previous_col = COL_ID; + m_ascend = false; + } + + slot_col_clicked( COL_ID ); +} + +// その他のヘッダをクリック +void BoardView::slot_col_clicked( int col ) +{ + if( col == COL_STR_LOAD ) col = COL_LOAD; + else if( col == COL_STR_NEW ) col = COL_NEW; + else if( col == COL_SINCE ) col = COL_SINCE_T; + else if( col == COL_WRITE ) col = COL_WRITE_T; + + DBTREE::board_set_view_sort_column( get_url(), col ); + + if( m_previous_col == col && !m_ascend ){ + m_ascend = true; + m_liststore->set_sort_column( col, Gtk::SORT_ASCENDING ); + DBTREE::board_set_view_sort_ascend( get_url(), true ); + } + else{ + m_ascend = false; + m_liststore->set_sort_column( col, Gtk::SORT_DESCENDING ); + DBTREE::board_set_view_sort_ascend( get_url(), false ); + } + + m_previous_col = col; +} + + +// +// mark 列のソート関数 +// +int BoardView::slot_compare_mark_val( const Gtk::TreeModel::iterator& a, const Gtk::TreeModel::iterator& b ) +{ + Gtk::TreeModel::Row row_a = *( a ); + Gtk::TreeModel::Row row_b = *( b ); + + int num_a = row_a[ m_columns.m_col_mark_val ]; + int num_b = row_b[ m_columns.m_col_mark_val ]; + + // 抽出状態優先 + int ret = compare_drawbg( row_a, row_b ); + if( ret ) return ret; + + // 両方ともマーク有り + if( num_a < COL_MARKVAL_NORMAL && num_b < COL_MARKVAL_NORMAL ){ + + if( num_a > num_b ) return 1; + else if( num_a < num_b )return -1; + + // 同じマークならIDで決める + return compare_id( row_a, row_b ); + } + + // マークが付いている方を優先 + if( num_a < COL_MARKVAL_NORMAL ) return ( m_ascend ? -1 : 1 ); + if( num_b < COL_MARKVAL_NORMAL ) return ( m_ascend ? 1 : -1 ); + + // どちらもマークが付いていないならIDの小さい方を優先 + return compare_id( row_a, row_b ); +} + + +// name 列のソート関数 +int BoardView::slot_compare_subject( const Gtk::TreeModel::iterator& a, const Gtk::TreeModel::iterator& b ) +{ + Gtk::TreeModel::Row row_a = *( a ); + Gtk::TreeModel::Row row_b = *( b ); + + Glib::ustring name_a = row_a[ m_columns.m_col_subject ]; + Glib::ustring name_b = row_b[ m_columns.m_col_subject ]; + + // 抽出状態優先 + int ret = compare_drawbg( row_a, row_b ); + if( ret ) return ret; + + if( name_a > name_b ) return 1; + else if( name_a < name_b )return -1; + + // 同じ値ならIDで決める + return compare_id( row_a, row_b ); +} + + +#define SLOT_COMPARE_ROW( target ) do{ \ +Gtk::TreeModel::Row row_a = *( a ); \ +Gtk::TreeModel::Row row_b = *( b ); \ +int num_a = row_a[ target ]; \ +int num_b = row_b[ target ]; \ +return compare_row( num_a, num_b, row_a, row_b ); \ +}while(0) + +// ID 列のソート関数 +int BoardView::slot_compare_num_id( const Gtk::TreeModel::iterator& a, const Gtk::TreeModel::iterator& b ) +{ SLOT_COMPARE_ROW( m_columns.m_col_id ); } + +// res 列のソート関数 +int BoardView::slot_compare_num_res( const Gtk::TreeModel::iterator& a, const Gtk::TreeModel::iterator& b ) +{ SLOT_COMPARE_ROW( m_columns.m_col_res ); } + +// load 列のソート関数 +int BoardView::slot_compare_num_load( const Gtk::TreeModel::iterator& a, const Gtk::TreeModel::iterator& b ) +{ SLOT_COMPARE_ROW( m_columns.m_col_load ); } + +// new 列のソート関数 +int BoardView::slot_compare_new( const Gtk::TreeModel::iterator& a, const Gtk::TreeModel::iterator& b ) +{ SLOT_COMPARE_ROW( m_columns.m_col_new ); } + +// since 列のソート関数 +int BoardView::slot_compare_since_t( const Gtk::TreeModel::iterator& a, const Gtk::TreeModel::iterator& b ) +{ SLOT_COMPARE_ROW( m_columns.m_col_since_t ); } + +// write 列のソート関数 +int BoardView::slot_compare_write_t( const Gtk::TreeModel::iterator& a, const Gtk::TreeModel::iterator& b ) +{ SLOT_COMPARE_ROW( m_columns.m_col_write_t ); } + +#undef COMPARE_ROW + +// speed 列のソート関数 +int BoardView::slot_compare_speed( const Gtk::TreeModel::iterator& a, const Gtk::TreeModel::iterator& b ) +{ + Gtk::TreeModel::Row row_a = *( a ); + Gtk::TreeModel::Row row_b = *( b ); + int num_a = row_a[ m_columns.m_col_speed ]; + int num_b = row_b[ m_columns.m_col_speed ]; + + // 抽出状態優先 + int ret = compare_drawbg( row_a, row_b ); + if( ret ) return ret; + + if( num_a > num_b ) return 1; + else if( num_a < num_b )return -1; + + // 同じ値ならIDで決める + return compare_id( row_a, row_b ); +} + + + +// +// 抽出状態で比較 +// +// 抽出状態にあるものを上にする。同じなら0 +// +int BoardView::compare_drawbg( Gtk::TreeModel::Row& row_a, Gtk::TreeModel::Row& row_b ) +{ + bool draw_a = row_a[ m_columns.m_col_drawbg ]; + bool draw_b = row_b[ m_columns.m_col_drawbg ]; + if( draw_a && !draw_b ) return ( m_ascend ? -1 : 1 ); + if( draw_b && !draw_a ) return ( m_ascend ? 1 : -1 ); + return 0; +} + + + +// +// markで比較 +// +// マークが付いているか、値の小さい方を上にする +// +int BoardView::compare_mark( Gtk::TreeModel::Row& row_a, Gtk::TreeModel::Row& row_b ) +{ + int num_a = row_a[ m_columns.m_col_mark_val ]; + int num_b = row_b[ m_columns.m_col_mark_val ]; + + if( num_a < COL_MARKVAL_NORMAL && num_b < COL_MARKVAL_NORMAL ){ + + if( num_a > num_b ) return ( m_ascend ? 1 : -1 ); + else if( num_a < num_b ) return ( m_ascend ? -1 : 1 ); + + // 同じマークならIDで決める + return compare_id( row_a, row_b ); + } + + // マークが付いている方を優先 + if( num_a < COL_MARKVAL_NORMAL ) return ( m_ascend ? -1 : 1 ); + if( num_b < COL_MARKVAL_NORMAL ) return ( m_ascend ? 1 : -1 ); + + return 0; +} + + +// +// IDで比較 +// +// IDの小さい方を上にする。両方同じなら 0 +// +int BoardView::compare_id( Gtk::TreeModel::Row& row_a, Gtk::TreeModel::Row& row_b ) +{ + int num_a = row_a[ m_columns.m_col_id ]; + int num_b = row_b[ m_columns.m_col_id ]; + if( num_a > num_b ) return ( m_ascend ? 1 : -1 ); + else if( num_a < num_b ) return ( m_ascend ? -1 : 1 ); + return 0; +} + + +// +// ソート関数の本体 +// +int BoardView::compare_row( int& num_a, int& num_b, Gtk::TreeModel::Row& row_a, Gtk::TreeModel::Row& row_b ) +{ + // 抽出状態優先 + int ret = compare_drawbg( row_a, row_b ); + if( ret ) return ret; + + // 両方とも 0 より大きい + if( num_a > 0 && num_b > 0 ){ + + // 普通にcompare + if( num_a > num_b ) return 1; + else if( num_a < num_b )return -1; + + // 同じ値ならIDで決める + return compare_id( row_a, row_b ); + } + + // 0より大きい方を優先 + if( num_a > 0 ) return ( m_ascend ? -1 : 1 ); + if( num_b > 0 ) return ( m_ascend ? 1 : -1 ); + + // マークの付いている方を優先 + ret = compare_mark( row_a, row_b ); + if( ret ) return ret; + + // 両方0以下ならIDで決める + return compare_id( row_a, row_b ); +} + + +// +// クロック入力 +// +void BoardView::clock_in() +{ + m_treeview.clock_in(); +} + + + +// +// リロード +// +void BoardView::reload() +{ + show_view(); + CORE::core_set_command( "set_history_board", get_url() ); +} + + +// +// ロード停止 +// +void BoardView::stop() +{ + DBTREE::board_stop_load( get_url() ); +} + + + +// +// ビュー表示 +// +void BoardView::show_view() +{ +#ifdef _DEBUG + std::cout << "BoardView::show_view " << get_url() << std::endl; +#endif + + CORE::core_set_command( "switch_board" ); + + // DBに登録されてない + if( get_url().empty() ){ + set_status( "invalid URL" ); + CORE::core_set_command( "set_status","", get_status() ); + return; + } + + // タブに名前をセット + BOARD::get_admin()->set_command( "set_tablabel", get_url(), DBTREE::board_name( get_url() ) ); + + m_liststore->clear(); + m_pre_query = std::string(); + + // download 開始 + // 終わったら update_view() が呼ばれる + DBTREE::board_download_subject( get_url() ); + set_status( "loading..." ); + CORE::core_set_command( "set_status","", get_status() ); +} + + + +// +// 再描画(画面初期化) +// +void BoardView::redraw_view() +{ +#ifdef _DEBUG + std::cout << "BoardView::redraw_view" << get_url() << std::endl; +#endif + + m_search_invert = false; + + // ソート状態回復 + int col = DBTREE::board_view_sort_column( get_url() ); + m_previous_col = col; + m_ascend = DBTREE::board_view_sort_ascend( get_url() ); + if( col <= 0 ){ + m_previous_col = COL_NUM_COL; + m_ascend = false; + slot_mark_clicked(); + } + else if( m_ascend ) m_liststore->set_sort_column( col, Gtk::SORT_ASCENDING ); + else m_liststore->set_sort_column( col, Gtk::SORT_DESCENDING ); + + goto_top(); +} + + + +// +// 再描画 +// +void BoardView::relayout() +{ + m_treeview.init_color(); + m_treeview.init_font(); +} + + + +// +// view更新 +// +// subject.txtのロードが終わったら呼ばれる +// +void BoardView::update_view() +{ +#ifdef _DEBUG + std::cout << "BoardView::update_view " << get_url() << std::endl; +#endif + + // 高速化のためデータベースに直接アクセス + std::list< DBTREE::ArticleBase* >& list_subject = DBTREE::board_list_subject( get_url() ); + + time_t current_t = time( NULL ); + + // 自動ソート抑制 + UNSORTED_COLUMN(); + + int id = 1; + if( list_subject.size() ){ + + std::list< DBTREE::ArticleBase* >::iterator it; + for( it = list_subject.begin(); it != list_subject.end(); ++it, ++id ){ + + DBTREE::ArticleBase* art = *( it ); + + // 行を作って内容をセット + Gtk::TreeModel::Row row = *( m_liststore->prepend() ); // append より prepend の方が速いらしい + + row[ m_columns.m_col_id ] = id; + row[ m_columns.m_col_since ] = art->get_since_date(); + + if( art->is_current() ) + row[ m_columns.m_col_speed ] = art->get_number() / MAX( 1, ( current_t - art->get_since_time()) / ( 60 * 60 * 24 ) + 1 ); + + row[ m_columns.m_col_since_t ] = art->get_since_time(); + row[ m_columns.m_col_id_dat ] = art->get_id(); + + update_row_common( art, row, id ); + } + + redraw_view(); + } + + // ステータスバー更新 + std::ostringstream ss_tmp; + ss_tmp << DBTREE::board_str_code( get_url() ) << " [ Total " << ( id -1 ) << " ] "; + set_status( ss_tmp.str() ); + CORE::core_set_command( "set_status","", get_status() ); + + focus_view(); +} + +void BoardView::focus_view() +{ + m_treeview.grab_focus(); +} + + +void BoardView::focus_out() +{ + SKELETON::View::focus_out(); + + save_column_width(); + m_treeview.hide_tooltip(); +} + + +void BoardView::close_view() +{ + BOARD::get_admin()->set_command( "close_currentview" ); +} + + +// +// 選択した行のログをまとめて削除 +// +void BoardView::delete_view() +{ + std::list< Gtk::TreeModel::iterator > list_it = m_treeview.get_selected_iterators(); + std::list< Gtk::TreeModel::iterator >::iterator it = list_it.begin(); + for( ; it != list_it.end(); ++it ){ + Gtk::TreeModel::Row row = *( *it ); + std::string url = DBTREE::url_datbase( get_url() ) + row[ m_columns.m_col_id_dat ]; +#ifdef _DEBUG + std::cout << url << std::endl; +#endif + CORE::core_set_command( "delete_article", url ); + } +} + + + + +// +// viewの操作 +// +void BoardView::operate_view( const int& control ) +{ + bool open_tab = false; + + Gtk::TreePath path = m_treeview.get_current_path();; + if( path.empty() ) return; + + switch( control ){ + + case CONTROL::Down: + row_down(); + break; + + case CONTROL::Up: + row_up(); + break; + + case CONTROL::Home: + goto_top(); + break; + + case CONTROL::End: + goto_bottom(); + break; + + // 選択 + case CONTROL::OpenArticleTab: + open_tab = true; + case CONTROL::OpenArticle: + open_row( path, open_tab ); + break; + + // Listに戻る + case CONTROL::Left: + CORE::core_set_command( "switch_bbslist" ); + break; + + // 現在の記事を表示 + case CONTROL::Right: + CORE::core_set_command( "switch_article" ); + break; + + case CONTROL::ToggleArticle: + CORE::core_set_command( "toggle_article" ); + break; + + case CONTROL::TabLeft: + BOARD::get_admin()->set_command( "tab_left" ); + break; + + case CONTROL::TabRight: + BOARD::get_admin()->set_command( "tab_right" ); + break; + + case CONTROL::Quit: + close_view(); + break; + + case CONTROL::Reload: + reload(); + break; + + case CONTROL::StopLoading: + stop(); + break; + + case CONTROL::NewArticle: + slot_new_article(); + break; + + case CONTROL::Delete: + { + Gtk::MessageDialog mdiag( "選択した行のログを削除しますか?", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_OK_CANCEL ); + if( mdiag.run() != Gtk::RESPONSE_OK ) return; + delete_view(); + break; + } + + // 検索 + case CONTROL::Search: + m_search_invert = false; + m_toolbar.m_entry_search.grab_focus(); + break; + + case CONTROL::SearchInvert: + m_search_invert = true; + m_toolbar.m_entry_search.grab_focus(); + break; + + case CONTROL::SearchNext: + slot_push_down_search(); + break; + + case CONTROL::SearchPrev: + slot_push_up_search(); + break; + } +} + + + + +// +// 先頭に戻る +// +void BoardView::goto_top() +{ + m_treeview.goto_top(); +} + + +// +// 一番最後へ +// +void BoardView::goto_bottom() +{ + m_treeview.goto_bottom(); +} + + + +// +// 上へ移動 +// +void BoardView::row_up() +{ + m_treeview.row_up(); +} + + +// +// 下へ移動 +// +void BoardView::row_down() +{ + m_treeview.row_down(); +} + + + +// +// 特定の行だけの更新 +// +void BoardView::update_item( const std::string& id_dat ) +{ +#ifdef _DEBUG + std::cout << "BoardView::update_item " << id_dat << std::endl; +#endif + + Gtk::TreeModel::Children child = m_liststore->children(); + Gtk::TreeModel::Children::iterator it; + + // 自動ソート抑制 + UNSORTED_COLUMN(); + + for( it = child.begin() ; it != child.end() ; ++it ){ + Gtk::TreeModel::Row row = *( it ); + + // 対象の行なら内容更新 + if( row[ m_columns.m_col_id_dat ] == id_dat ){ + + std::string url = DBTREE::url_datbase( get_url() ) + row[ m_columns.m_col_id_dat ]; + DBTREE::ArticleBase* art = DBTREE::get_article( url ); + int id = row[ m_columns.m_col_id ]; + update_row_common( art, row, id ); + } + } +} + + + +// +// update_view() と update_item() で共通に更新する列 +// +void BoardView::update_row_common( DBTREE::ArticleBase* art, Gtk::TreeModel::Row& row, int& id ) +{ + if( art->empty() ) return; + + // タイトル、レス数、抽出 + row[ m_columns.m_col_subject ] = art->get_subject(); + row[ m_columns.m_col_res ] = art->get_number(); + row[ m_columns.m_col_drawbg ] = false; + + // 読み込み数 + + if( art->get_number_load() ){ + const int tmpsize = 32; + char tmp[ tmpsize ]; + snprintf( tmp, tmpsize, "%d", art->get_number_load() ); + row[ m_columns.m_col_str_load ] = tmp; + snprintf( tmp, tmpsize, "%d", art->get_number() - art->get_number_load() ); + row[ m_columns.m_col_str_new ] = tmp; + + row[ m_columns.m_col_load ] = art->get_number_load(); + row[ m_columns.m_col_new ] = art->get_number() - art->get_number_load(); + } + else{ + row[ m_columns.m_col_str_load ] = ""; + row[ m_columns.m_col_str_new ] = ""; + + row[ m_columns.m_col_load ] = -1; + row[ m_columns.m_col_new ] = -1; + } + + + // + // マーク + + // dat落ち + int mark_val; + if( ! art->is_current() ){ + mark_val = COL_MARKVAL_OLD; + row[ m_columns.m_col_mark ] = render_icon( Gtk::Stock::OK, Gtk::ICON_SIZE_MENU ); + } + // キャッシュあり、新着あり + else if( art->get_number_load() && art->get_number() > art->get_number_load() ){ + mark_val = COL_MARKVAL_UPDATED; + row[ m_columns.m_col_mark ] = render_icon( Gtk::Stock::ADD, Gtk::ICON_SIZE_MENU ); + } + // キャッシュあり、新着無し + else if( art->get_number_load() ){ + mark_val = COL_MARKVAL_CACHED; + row[ m_columns.m_col_mark ] = render_icon( Gtk::Stock::CANCEL, Gtk::ICON_SIZE_MENU ); + } + //キャッシュ無し + else{ + mark_val = COL_MARKVAL_NORMAL; + row[ m_columns.m_col_mark ] = Gdk::Pixbuf::create( Gdk::COLORSPACE_RGB, true, 8, 1, 1 ); + } + row[ m_columns.m_col_mark_val ] = mark_val; + + + // 書き込み時間 + 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(); + if( mark_val < COL_MARKVAL_NORMAL ) row[ m_columns.m_col_write_t ] = 0; + else row[ m_columns.m_col_write_t ] = -1; + } +} + + +// +// マウスボタン押した +// +bool BoardView::slot_button_press( GdkEventButton* event ) +{ + // マウスジェスチャ + SKELETON::View::get_control().MG_start( event ); + + return true; +} + + + +// +// マウスボタン離した +// +bool BoardView::slot_button_release( GdkEventButton* event ) +{ + /// マウスジェスチャ + int mg = SKELETON::View::get_control().MG_end( event ); + if( mg != CONTROL::None && enable_mg() ){ + operate_view( mg ); + return true; + } + + int x = (int)event->x; + 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; + + // リサイズするときにラベルをクリックすると一番上のアイテムが開く問題の対処 + // かなりその場しのぎな方法なのでGTKのバージョンが上がったら誤動作するかも + if( path.to_string() == "0" && x == cell_x && y == cell_y ) return true; + +#ifdef _DEBUG + std::cout << "BoardView::slot_button_press : " << path.to_string() << " " + << x << " " << y << " " << cell_x << " " << cell_y << std::endl; +#endif + + // 板を開く + bool openarticle = SKELETON::View::get_control().button_alloted( event, CONTROL::OpenArticleButton ); + bool openarticletab = SKELETON::View::get_control().button_alloted( event, CONTROL::OpenArticleTabButton ); + if( openarticle || openarticletab ) open_row( path, openarticletab ); + + // ポップアップメニューボタン + else if( SKELETON::View::get_control().button_alloted( event, CONTROL::PopupmenuButton ) ){ + + Gtk::Menu* popupmenu; + if( m_treeview.get_selection()->get_selected_rows().size() == 1 ){ + 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" ) ); + } + if( popupmenu ) popupmenu->popup( 0, gtk_get_current_event_time() ); + } + } + + return true; +} + + + +// +// マウス動かした +// +bool BoardView::slot_motion( GdkEventMotion* event ) +{ + /// マウスジェスチャ + SKELETON::View::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.set_tooltip_min_width( column->get_width() ); + if( column->get_title() == "name" ) m_treeview.set_str_tooltip( get_name_of_cell( path, m_columns.m_col_subject ) ); + else if( column->get_title() == "since" ) m_treeview.set_str_tooltip( get_name_of_cell( path, m_columns.m_col_since ) ); + else if( column->get_title() == "write" ) m_treeview.set_str_tooltip( get_name_of_cell( path, m_columns.m_col_write ) ); + else m_treeview.set_str_tooltip( std::string() ); + } + + return true; +} + + + + +// +// キー入力 +// +bool BoardView::slot_key_press( GdkEventKey* event ) +{ + operate_view( SKELETON::View::get_control().key_press( event ) ); + + return true; +} + + + +// +// このビューからD&Dを開始したときにtreeviewから呼ばれる +// +void BoardView::slot_drag_begin() +{ +#ifdef _DEBUG + std::cout << "BoardView::slot_drag_begin\n"; +#endif + + CORE::DND_Begin( get_url() ); + set_article_to_buffer(); +} + + + +// +// このビューからD&Dを開始した後にD&Dを終了するとtreeviewから呼ばれる +// +void BoardView::slot_drag_end() +{ +#ifdef _DEBUG + std::cout << "BoardView::slot_drag_end\n"; +#endif + + CORE::DND_End(); +} + + + + + +// +// popupmenu でタブで開くを選択 +// +void BoardView::slot_open_tab() +{ + if( ! m_path_selected.empty() ) open_row( m_path_selected, true ); +} + + +// +// スレをお気に入りに登録 +// +// ポップアップメニューのslot +// +void BoardView::slot_favorite_thread() +{ + // 共有バッファにデータをセットしてから append_favorite コマンド実行 + set_article_to_buffer(); + CORE::core_set_command( "append_favorite", URL_FAVORITEVIEW ); +} + + + + +// +// 板をお気に入りに追加 +// +void BoardView::slot_favorite_board() +{ + // 共有バッファにデータをセットしてから append_favorite コマンド実行 + set_board_to_buffer(); + CORE::core_set_command( "append_favorite", URL_FAVORITEVIEW ); +} + + +// +// 新スレをたてる +// +void BoardView::slot_new_article() +{ + CORE::core_set_command( "create_new_thread", get_url() ); +} + + +// +// ツールバーの削除ボタン +// +void BoardView::slot_push_delete() +{ + Gtk::Menu* popupmenu = dynamic_cast< Gtk::Menu* >( ui_manager()->get_widget( "/popup_menu_delete" ) ); + if( popupmenu ) popupmenu->popup( 0, gtk_get_current_event_time() ); +} + + +// +// ツールバーのお気に入りボタン +// +void BoardView::slot_push_favorite() +{ + Gtk::Menu* popupmenu = dynamic_cast< Gtk::Menu* >( ui_manager()->get_widget( "/popup_menu_favorite" ) ); + if( popupmenu ) popupmenu->popup( 0, gtk_get_current_event_time() ); +} + + +// +// 選択解除 +// +void BoardView::slot_unselect_all() +{ + m_treeview.get_selection()->unselect_all(); +} + + +// +// スレのURLをコピー +// +void BoardView::slot_copy_url() +{ + if( m_path_selected.empty() ) return; + + std::string url = DBTREE::url_readcgi( path2daturl( m_path_selected ), 0, 0 ); + COPYCLIP( url ); +} + + +// スレの名前とURLをコピー +// +void BoardView::slot_copy_title_url() +{ + if( m_path_selected.empty() ) return; + + std::string url = DBTREE::url_readcgi( path2daturl( m_path_selected ), 0, 0 ); + std::string name = DBTREE::article_subject( url ); + std::stringstream ss; + ss << name << std::endl + << url << std::endl; + + COPYCLIP( ss.str() ); +} + + +// +// ポップアップメニューでブラウザで開くを選択 +// +void BoardView::slot_open_browser() +{ + std::string url = DBTREE::url_readcgi( path2daturl( m_path_selected ), 0, 0 ); + CORE::core_set_command( "open_url_browser", url ); +} + + + +// +// 記事を開く +// +bool BoardView::open_row( Gtk::TreePath& path, bool tab ) +{ + std::string str_tab = "false"; + if( tab ) str_tab = "true"; + + std::string url_target = path2daturl( path ); + +#ifdef _DEBUG + std::cout << "BoardView::open_row " << url_target << std::endl; +#endif + + if( url_target.empty() ) return false; + + CORE::core_set_command( "open_article", url_target , str_tab ); + return true; +} + + +// +// path -> スレッドの(dat型)URL変換 +// +std::string BoardView::path2daturl( const Gtk::TreePath& path ) +{ + Gtk::TreeModel::Row row = m_treeview.get_row( path ); + if( !row ) return std::string(); + + std::string url = DBTREE::url_datbase( get_url() ) + row[ m_columns.m_col_id_dat ]; + return url; +} + + + + +// +// 抽出 +// +bool BoardView::drawout() +{ + bool find = false; + bool reset = false; + + focus_view(); + std::string query = m_toolbar.m_entry_search.get_text(); + + // 空の時はリセット + if( query.empty() ){ + find = true; + reset = true; + } + + if( m_pre_query == query ) return false; + m_pre_query = query; + +#ifdef _DEBUG + std::cout << "BoardView::drawout query = " << query << std::endl; +#endif + + // 自動ソート抑制 + UNSORTED_COLUMN(); + + JDLIB::Regex regex; + Gtk::TreeModel::Children child = m_liststore->children(); + Gtk::TreeModel::Children::iterator it = child.begin(); + for( ; it != child.end() ; ++it ){ + + Gtk::TreeModel::Row row = *( it ); + Glib::ustring subject = row[ m_columns.m_col_subject ]; + + + if( reset ) row[ m_columns.m_col_drawbg ] = false; + else if( regex.exec( query, subject, 0, true ) ){ + row[ m_columns.m_col_drawbg ] = true; + find = true; + +#ifdef _DEBUG + std::cout << subject << " " << row[ m_columns.m_col_mark_val ] << std::endl; +#endif + + } + else row[ m_columns.m_col_drawbg ] = false; + } + + if( find ) redraw_view(); + + return true; +} + + + +// +// 検索移動 +// +void BoardView::search() +{ + if( drawout() ) return; + + focus_view(); + std::string query = m_toolbar.m_entry_search.get_text(); + 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( *( m_liststore->children().rbegin() ) ); + } + + Gtk::TreePath path_start = path; + JDLIB::Regex regex; + +#ifdef _DEBUG + std::cout << "BoardView::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( *( m_liststore->children().rbegin() ) ); + } + } + + + if( path == path_start ) break; + + Glib::ustring subject = get_name_of_cell( path, m_columns.m_col_subject ); + if( regex.exec( query, subject, 0, true ) ){ + m_treeview.scroll_to_row( path, 0 ); + m_treeview.set_cursor( path ); + return; + } + } +} + + + + +// 前検索 +void BoardView::slot_push_up_search() +{ + m_search_invert = true; + search(); +} + + + +// 後検索 +void BoardView::slot_push_down_search() +{ + m_search_invert = false; + search(); +} + + +// +// 検索entryの操作 +// +void BoardView::slot_entry_operate( int controlid ) +{ + if( controlid == CONTROL::Cancel ) focus_view(); +} + + +// +// 設定ボタン +// +void BoardView::slot_push_preferences() +{ + Preferences pref( get_url() ); + pref.run(); +} + + + + + +// +// path と column からそのセルの内容を取得 +// +template < typename ColumnType > +std::string BoardView::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(); + + Glib::ustring name = row[ column ]; + return name; +} + + + +// +// 共有バッファに選択中の行を登録する +// +void BoardView::set_article_to_buffer() +{ + CORE::SBUF_clear_info(); + + std::list< Gtk::TreeModel::iterator > list_it = m_treeview.get_selected_iterators(); + if( list_it.size() ){ + std::list< Gtk::TreeModel::iterator >::iterator it = list_it.begin(); + for( ; it != list_it.end(); ++it ){ + + Gtk::TreeModel::Row row = *( *it ); + Glib::ustring name = row[ m_columns.m_col_subject ]; + + CORE::DATA_INFO info; + info.type = TYPE_THREAD; + info.url = DBTREE::url_datbase( get_url() ) + row[ m_columns.m_col_id_dat ]; + info.name = name.raw(); + + CORE::SBUF_append( info ); +#ifdef _DEBUG + std::cout << "append " << info.name << std::endl; +#endif + } + } +} + + + +// +// 共有バッファに板を登録する +// +void BoardView::set_board_to_buffer() +{ + CORE::DATA_INFO info; + info.type = TYPE_BOARD; + info.url = get_url(); + info.name = DBTREE::board_name( get_url() ); + + CORE::SBUF_clear_info(); + CORE::SBUF_append( info ); +} diff --git a/src/board/boardview.h b/src/board/boardview.h new file mode 100644 index 000000000..7a500bdc5 --- /dev/null +++ b/src/board/boardview.h @@ -0,0 +1,135 @@ +// ライセンス: 最新のGPL + +// スレ一覧ビュー + +#ifndef _BOARDVIEW_H +#define _BOARDVIEW_H + +#include "skeleton/view.h" +#include "skeleton/treeview.h" + +#include "toolbar.h" +#include "columns.h" + +#include + +namespace DBTREE +{ + class ArticleBase; +} + +namespace BOARD +{ + class BoardView : public SKELETON::View + { + SKELETON::JDTreeView m_treeview; + BOARD::TreeColumns m_columns; + Glib::RefPtr< Gtk::ListStore > m_liststore; + Gtk::ScrolledWindow m_scrwin; + + // ソートで使う変数 + int m_previous_col; + bool m_ascend; + + // サーチで使う変数 + bool m_search_invert; + std::string m_pre_query; + + // ツールバー + BoardToolBar m_toolbar; + + // ポップアップメニュー用 + Gtk::TreeModel::Path m_path_selected; + + public: + BoardView( const std::string& url, const std::string& arg1 = std::string() , const std::string& arg2 = std::string() ); + ~BoardView(); + + virtual const std::string url_for_copy(); + + // SKELETON::View の関数のオーバロード + virtual void clock_in(); + virtual void reload(); + virtual void stop(); + virtual void show_view(); + virtual void redraw_view(); + virtual void relayout(); + virtual void update_view(); + virtual void focus_view(); + virtual void focus_out(); + virtual void close_view(); + virtual void delete_view(); + virtual void update_item( const std::string& id_dat ); + virtual void operate_view( const int& control ); + virtual void goto_top(); + virtual void goto_bottom(); + + void row_up(); + void row_down(); + + private: + + void save_column_width(); + + // ソート用 + void slot_mark_clicked(); + void slot_id_clicked(); + void slot_col_clicked( int col ); + int slot_compare_mark_val( const Gtk::TreeModel::iterator& a, const Gtk::TreeModel::iterator& b ); + int slot_compare_num_id( const Gtk::TreeModel::iterator& a, const Gtk::TreeModel::iterator& b ); + int slot_compare_subject( const Gtk::TreeModel::iterator& a, const Gtk::TreeModel::iterator& b ); + int slot_compare_num_res( const Gtk::TreeModel::iterator& a, const Gtk::TreeModel::iterator& b ); + int slot_compare_num_load( const Gtk::TreeModel::iterator& a, const Gtk::TreeModel::iterator& b ); + int slot_compare_new( const Gtk::TreeModel::iterator& a, const Gtk::TreeModel::iterator& b ); + int slot_compare_since_t( const Gtk::TreeModel::iterator& a, const Gtk::TreeModel::iterator& b ); + int slot_compare_write_t( const Gtk::TreeModel::iterator& a, const Gtk::TreeModel::iterator& b ); + int slot_compare_speed( const Gtk::TreeModel::iterator& a, const Gtk::TreeModel::iterator& b ); + + int compare_drawbg( Gtk::TreeModel::Row& row_a, Gtk::TreeModel::Row& row_b ); + int compare_mark( Gtk::TreeModel::Row& row_a, Gtk::TreeModel::Row& row_b ); + int compare_id( Gtk::TreeModel::Row& row_a, Gtk::TreeModel::Row& row_b ); + int compare_row( int& num_a, int& num_b, Gtk::TreeModel::Row& row_a, Gtk::TreeModel::Row& row_b ); + + // UI + bool slot_button_press( GdkEventButton* event ); + bool slot_button_release( GdkEventButton* event ); + bool slot_motion( GdkEventMotion* event ); + bool slot_key_press( GdkEventKey* event ); + void slot_open_tab(); + void slot_favorite_thread(); + void slot_favorite_board(); + void slot_new_article(); + void slot_push_delete(); + void slot_push_favorite(); + void slot_unselect_all(); + void slot_copy_url(); + void slot_copy_title_url(); + void slot_open_browser(); + void slot_push_preferences(); + + bool open_row( Gtk::TreePath& path, bool tab ); + std::string path2daturl( const Gtk::TreePath& path ); + + // 検索 + bool drawout(); + void search(); + void slot_push_down_search(); + void slot_push_up_search(); + void slot_entry_operate( int controlid ); + + // d&d + void slot_drag_begin(); + void slot_drag_end(); + + void update_row_common( DBTREE::ArticleBase* art, Gtk::TreeModel::Row& row, int& id ); + std::string get_subject_from_path( Gtk::TreePath& path ); + 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(); + }; +}; + + +#endif diff --git a/src/board/columns.h b/src/board/columns.h new file mode 100644 index 000000000..ea4111895 --- /dev/null +++ b/src/board/columns.h @@ -0,0 +1,86 @@ +// ライセンス: 最新のGPL + +// コラム + +#ifndef _BOARDCOLUMNS_H +#define _BOARDCOLUMNS_H + +#include + +namespace BOARD +{ + // 列ID + enum + { + COL_MARK = 0, + COL_ID, + COL_SUBJECT, + COL_RES, + COL_STR_LOAD, + COL_STR_NEW, + COL_SINCE, + COL_WRITE, + COL_SPEED, + + // 以下は不可視 + COL_MARK_VAL, + COL_DRAWBG, + COL_LOAD, + COL_NEW, + COL_SINCE_T, + COL_WRITE_T, + COL_ID_DAT, + + COL_NUM_COL + }; + + + // 列 + 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< int > m_col_speed; + + Gtk::TreeModelColumn< int > m_col_mark_val; // マークの優先順(新着を一番上にする) + Gtk::TreeModelColumn< bool > m_col_drawbg; // true なら背景を塗る + Gtk::TreeModelColumn< int > m_col_load; + Gtk::TreeModelColumn< int > m_col_new; + Gtk::TreeModelColumn< time_t > m_col_since_t; + Gtk::TreeModelColumn< time_t > m_col_write_t; + Gtk::TreeModelColumn< Glib::ustring > m_col_id_dat; + + 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_speed ); + + add( m_col_mark_val ); + add( m_col_drawbg ); + add( m_col_load ); + add( m_col_new ); + add( m_col_since_t ); + add( m_col_write_t ); + add( m_col_id_dat ); + } + + ~TreeColumns(){} + }; +} + +#endif diff --git a/src/board/preference.cpp b/src/board/preference.cpp new file mode 100644 index 000000000..51cac09d6 --- /dev/null +++ b/src/board/preference.cpp @@ -0,0 +1,70 @@ +// ライセンス: 最新のGPL + +#include "preference.h" + +#include "dbtree/interface.h" + +#include "jdlib/miscutil.h" + +#include "cache.h" + +using namespace BOARD; + +Preferences::Preferences( const std::string& url ) + : SKELETON::PrefDiag( url, false ), + m_frame_cookie( "クッキー" ), + m_label_cookie( "未取得" ), + m_button_cookie( "削除" ) , + m_label_name( DBTREE::board_name( get_url() ), Gtk::ALIGN_LEFT ), + m_label_url( "URL : ", DBTREE::url_boardbase( get_url() ) ), + m_label_cache( "ローカルキャッシュパス", CACHE::path_board_root( DBTREE::url_boardbase( get_url() ) ) ), + + m_label_noname( "デフォルト名無し : ", DBTREE::default_noname( get_url() ) ), + m_label_line( "1レスの最大改行数 : " ), + m_label_byte( "1レスの最大バイト数 : " ) +{ + m_label_cookie.set_alignment( Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER ); + std::string str_tmp = DBTREE::board_cookie_for_write( get_url() ); + if( !str_tmp.empty() ) m_label_cookie.set_label( str_tmp ); + m_hbox_cookie.set_border_width( 8 ); + m_hbox_cookie.set_spacing( 8 ); + m_hbox_cookie.pack_start( m_label_cookie ); + m_hbox_cookie.pack_start( m_button_cookie, Gtk::PACK_SHRINK ); + m_button_cookie.signal_clicked().connect( sigc::mem_fun(*this, &Preferences::slot_delete_cookie ) ); + + m_frame_cookie.add( m_hbox_cookie ); + + m_label_line.set_text( MISC::itostr( DBTREE::line_number( get_url() ) * 2 ) ); + m_label_byte.set_text( MISC::itostr( DBTREE::message_count( 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_label_line, Gtk::PACK_SHRINK ); + m_vbox.pack_start( m_label_byte, Gtk::PACK_SHRINK ); + m_vbox.pack_end( m_frame_cookie, Gtk::PACK_SHRINK ); + + // SETTING.TXT + m_edit_settingtxt.textview().set_editable( false ); + m_edit_settingtxt.set_text( DBTREE::settingtxt( get_url() ) ); + + m_notebook.append_page( m_vbox, "一般" ); + m_notebook.append_page( m_edit_settingtxt, "SETTING.TXT" ); + + get_vbox()->pack_start( m_notebook ); + set_title( "板のプロパティ" ); + resize( 600, 400 ); + show_all_children(); +} + + +void Preferences::slot_delete_cookie() +{ + DBTREE::board_set_cookie_for_write( get_url(), std::string() ); + m_label_cookie.set_label( "未取得" ); +} + diff --git a/src/board/preference.h b/src/board/preference.h new file mode 100644 index 000000000..a7adb6dab --- /dev/null +++ b/src/board/preference.h @@ -0,0 +1,43 @@ +// ライセンス: 最新のGPL + +#ifndef _BOARD_PREFERENCES_H +#define _BOARD_PREFERENCES_H + +#include "skeleton/prefdiag.h" +#include "skeleton/editview.h" +#include "skeleton/label_entry.h" + +namespace BOARD +{ + class Preferences : public SKELETON::PrefDiag + { + Gtk::Notebook m_notebook; + Gtk::VBox m_vbox; + SKELETON::EditView m_edit_settingtxt; + + // クッキー + Gtk::Frame m_frame_cookie; + Gtk::HBox m_hbox_cookie; + Gtk::Label m_label_cookie; + Gtk::Button m_button_cookie; + + // 情報 + Gtk::VBox m_vbox_info; + Gtk::Label m_label_name; + SKELETON::LabelEntry m_label_url; + SKELETON::LabelEntry m_label_cache; + + SKELETON::LabelEntry m_label_noname; + SKELETON::LabelEntry m_label_line; + SKELETON::LabelEntry m_label_byte; + + public: + Preferences( const std::string& url ); + + private: + void slot_delete_cookie(); + }; + +} + +#endif diff --git a/src/board/toolbar.h b/src/board/toolbar.h new file mode 100644 index 000000000..af7241855 --- /dev/null +++ b/src/board/toolbar.h @@ -0,0 +1,78 @@ +// ライセンス: 最新のGPL + +// ツールバーのクラス + +#ifndef _BOARD_TOOLBAR_H +#define _BOARD_TOOLBAR_H + +#include + +#include "skeleton/imgbutton.h" +#include "skeleton/entry.h" + +#include "controlutil.h" +#include "controlid.h" + +namespace BOARD +{ + class BoardToolBar : public Gtk::VBox + { + + friend class BoardView; + + Gtk::HBox m_buttonbar; + + SKELETON::JDEntry m_entry_search; + SKELETON::ImgButton m_button_close; + SKELETON::ImgButton m_button_reload; + SKELETON::ImgButton m_button_delete; + SKELETON::ImgButton m_button_stop; + SKELETON::ImgButton m_button_favorite; + SKELETON::ImgButton m_button_up_search; + SKELETON::ImgButton m_button_down_search; + SKELETON::ImgButton m_button_new_article; + SKELETON::ImgButton m_button_preferences; + + Gtk::Tooltips m_tooltip; + + BoardToolBar() : + m_button_close( Gtk::Stock::CLOSE ), + m_button_reload( Gtk::Stock::REFRESH ), + m_button_delete( Gtk::Stock::DELETE ), + m_button_stop( Gtk::Stock::STOP ), + m_button_favorite( Gtk::Stock::COPY ), + m_button_up_search( Gtk::Stock::GO_UP ), + m_button_down_search( Gtk::Stock::GO_DOWN ), + m_button_new_article( Gtk::Stock::NEW ), + m_button_preferences( Gtk::Stock::PREFERENCES ) + { + m_tooltip.set_tip( m_button_close, CONTROL::get_label_motion( CONTROL::Quit ) ); + m_tooltip.set_tip( m_button_reload, CONTROL::get_label_motion( CONTROL::Reload ) ); + m_tooltip.set_tip( m_button_delete, CONTROL::get_label_motion( CONTROL::Delete ) ); + m_tooltip.set_tip( m_button_stop, CONTROL::get_label_motion( CONTROL::StopLoading ) ); + m_tooltip.set_tip( m_button_favorite, CONTROL::get_label_motion( CONTROL::AppendFavorite ) ); + m_tooltip.set_tip( m_button_up_search, CONTROL::get_label_motion( CONTROL::SearchPrev ) ); + m_tooltip.set_tip( m_button_down_search, CONTROL::get_label_motion( CONTROL::SearchNext ) ); + m_tooltip.set_tip( m_button_new_article, CONTROL::get_label_motion( CONTROL::NewArticle ) ); + m_tooltip.set_tip( m_button_preferences, CONTROL::get_label_motion( CONTROL::Property ) ); + + m_buttonbar.pack_start( m_entry_search ); + m_buttonbar.pack_end( m_button_close, Gtk::PACK_SHRINK ); + m_buttonbar.pack_end( m_button_delete, Gtk::PACK_SHRINK ); + m_buttonbar.pack_end( m_button_preferences, Gtk::PACK_SHRINK ); + m_buttonbar.pack_end( m_button_favorite, Gtk::PACK_SHRINK ); + m_buttonbar.pack_end( m_button_new_article, Gtk::PACK_SHRINK ); + m_buttonbar.pack_end( m_button_stop, Gtk::PACK_SHRINK ); + m_buttonbar.pack_end( m_button_reload, Gtk::PACK_SHRINK ); + m_buttonbar.pack_end( m_button_up_search, Gtk::PACK_SHRINK ); + m_buttonbar.pack_end( m_button_down_search, Gtk::PACK_SHRINK ); + + set_border_width( 1 ); + pack_start( m_buttonbar, Gtk::PACK_SHRINK ); + } + + }; +} + + +#endif diff --git a/src/browserpref.h b/src/browserpref.h new file mode 100644 index 000000000..d4236e53a --- /dev/null +++ b/src/browserpref.h @@ -0,0 +1,46 @@ +// ライセンス: 最新のGPL + +// ブラウザ設定ダイアログ + +#ifndef _BROWSER_H +#define _BROWSER_H + +#include "skeleton/prefdiag.h" + +#include "config/globalconf.h" + +#include "jdlib/miscutil.h" + +namespace CORE +{ + class BrowserPref : public SKELETON::PrefDiag + { + Gtk::Label m_label_notice; + Gtk::Entry m_entry_browser; + + // OK押した + virtual void slot_ok_clicked(){ + CONFIG::set_command_openurl( MISC::remove_space( m_entry_browser.get_text() ) ); + } + + public: + + BrowserPref( const std::string& url ) + : SKELETON::PrefDiag( url ) + ,m_label_notice( "%s をURLに変換します" ) + { + m_entry_browser.set_text( CONFIG::get_command_openurl() ); + + get_vbox()->set_spacing( 8 ); + get_vbox()->pack_start( m_label_notice ); + get_vbox()->pack_start( m_entry_browser, Gtk::PACK_EXPAND_WIDGET ); + + set_title( "ブラウザ設定" ); + show_all_children(); + resize( 400, 100 ); + } + }; + +} + +#endif diff --git a/src/cache.cpp b/src/cache.cpp new file mode 100644 index 000000000..9219300a8 --- /dev/null +++ b/src/cache.cpp @@ -0,0 +1,606 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "cache.h" + +#include "config/globalconf.h" + +#include "jdlib/miscutil.h" +#include "jdlib/miscmsg.h" + +#include "dbtree/interface.h" + +#include + +#include + +#include +#include +#include +#include + +#include +#include + + +// 設定ファイル +std::string CACHE::path_conf() +{ + std::string home = getenv( "HOME" ); + + return home + "/.jdrc"; +} + + +// セッション情報ファイル +std::string CACHE::path_session() +{ + return CACHE::path_root() + "session.info"; +} + + +// ロックファイル +std::string CACHE::path_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() +{ + + std::string root = MISC::remove_space( CONFIG::get_path_cacheroot() ); + + if( root[ root.length() -1 ] != '/' ) root = root + "/"; + + if( root[ 0 ] == '~' ){ + std::string home = getenv( "HOME" ); + root.replace( 0, 1, home ); + } + + return root; +} + + +// 板リスト +std::string CACHE::path_xml_listmain() +{ + return CACHE::path_root() + "list_main.xml"; +} + +std::string CACHE::path_xml_listmain_bkup() +{ + return CACHE::path_xml_listmain() + ".bkup"; +} + + +// お気に入り +std::string CACHE::path_xml_favorite() +{ + return CACHE::path_root() + "favorite.xml"; +} + +std::string CACHE::path_xml_favorite_bkup() +{ + return CACHE::path_xml_favorite() + ".bkup"; +} + + +// 外部板設定ファイル( navi2ch 互換 ) +std::string CACHE::path_etcboard() +{ + return CACHE::path_root() + "etc.txt"; +} + + +// 履歴 +std::string CACHE::path_xml_history() +{ + return CACHE::path_root() + "history.xml"; +} + + +// 板履歴 +std::string CACHE::path_xml_history_board() +{ + return CACHE::path_root() + "history_board.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 ) +{ + // http:// を取り除く + std::string url_board = DBTREE::url_boardbase( url ); + +#ifdef _DEBUG + std::cout << "CACHE::path_board_root " << url << " -> " << url_board << std::endl; +#endif + + unsigned int i = url_board.find( "://" ); + if( i == std::string::npos ) return std::string(); + + return CACHE::path_root() + url_board.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に拡張子が付いてたら取る + unsigned int 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 ) +{ + 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::filename_img( const std::string& url ) +{ + std::string file = MISC::tolower_str( url ); + file = MISC::replace_str( file, "http://", "" ); + file = MISC::replace_str( file, "/", "-" ); + file = MISC::url_encode( file.c_str(), file.length() ); + file = MISC::replace_str( file, "%", "" ); + + return file; +} + + +// +// 画像キャッシュファイルのパス +// +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( url ) + ".info"; +} + + + +// +// キャッシュのルートディレクトリを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; + } + + 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; +} + + + + +size_t CACHE::load_rawdata( const std::string& path, std::string& str ) +{ + str.clear(); + + std::ifstream fin; + fin.open( path.c_str() ); + 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, size_t n ) +{ + size_t count = 0; + std::ifstream fin; + fin.open( path.c_str() ); + if( !fin.is_open() ) return 0; + fin.read( data, n ); + count = fin.gcount(); + fin.close(); + + return count; +} + + +bool CACHE::save_rawdata( const std::string& path, const std::string& str ) +{ + return save_rawdata( path, str.c_str(), str.length() ); +} + + + +bool CACHE::save_rawdata( const std::string& path, const char* data, size_t n ) +{ + std::ofstream fout; + fout.open( path.c_str() ); + if( !fout.is_open() ){ + MISC::ERRMSG( "can't open " + path ); + return false; + } + fout.write( data, n ); + fout.close(); + + return true; +} + + +long CACHE::is_file_exists( const std::string& path ) +{ + struct stat buf_stat; + + if( stat( path.c_str(), &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; + + return EXIST; +} + + +size_t CACHE::get_filesize( const std::string& path ) +{ + struct stat buf_stat; + + if( stat( path.c_str(), &buf_stat ) != 0 ) return 0; + if( S_ISREG( buf_stat.st_mode ) ) return buf_stat.st_size; + return 0; +} + + + +// +// mkdir +// +bool CACHE::jdmkdir( const std::string& path ) +{ +#ifdef _DEBUG + std::cout << "CACHE::jdmkdir : path = " + path << std::endl; +#endif + + if( CACHE::is_file_exists( path ) == EXIST_DIR ) return true; + + std::string target = path; + + if( path.find( "~/" ) == 0 ){ + + std::string homedir = getenv( "HOME" ); + 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 + + // ルートからディレクトリがあるかチェックしていく。無ければ作る + unsigned int 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::is_file_exists( currentdir ) == EXIST_DIR ) continue; + + if( mkdir( currentdir.c_str(), 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( file_from.c_str(), &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 << "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; + } + + char* data = (char*)malloc( sizeof( char ) * buf_stat.st_size ); + + load_rawdata( file_from, data, buf_stat.st_size ); + save_rawdata( file_to, data, buf_stat.st_size ); + + free( data ); + + return true; +} + + +// +// 保存ダイアログを表示 +// +// 戻り値は保存先(失敗したらempty()) +// +std::string CACHE::open_save_diag( const std::string& file_from, const std::string& file_to ) +{ + if( file_from.empty() ) return std::string(); + if( file_to.empty() ) return std::string(); + + Gtk::FileChooserDialog diag( "save", Gtk::FILE_CHOOSER_ACTION_SAVE ); + diag.add_button( Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL ); + diag.add_button( Gtk::Stock::SAVE, Gtk::RESPONSE_ACCEPT ); + + 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 + + diag.set_current_folder( dir ); + diag.set_current_name( name ); + + if( diag.run() == Gtk::RESPONSE_ACCEPT ){ + + diag.hide(); + + std::string path_to = diag.get_filename(); + +#ifdef _DEBUG + std::cout << "to = " << path_to << std::endl; +#endif + + // 既にファイルがある場合は問い合わせる + if( CACHE::is_file_exists( path_to ) == CACHE::EXIST_FILE ){ + + Gtk::MessageDialog mdiag( "ファイルが存在します。ファイル名を変更しますか?", + false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_OK_CANCEL ); + + if( mdiag.run() == Gtk::RESPONSE_OK ) return CACHE::open_save_diag( file_from, path_to ); + + return std::string(); + } + + if( CACHE::jdcopy( file_from, path_to ) ) return path_to; + } + + return std::string(); +} + + +// +// 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( dir.c_str() ); + if( !dirp ) return list_files; + + struct dirent *direntry; + while( ( direntry = readdir( dirp ) ) ){ + + std::string filename = dir + direntry->d_name; + + if( is_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; +} diff --git a/src/cache.h b/src/cache.h new file mode 100644 index 000000000..10849f160 --- /dev/null +++ b/src/cache.h @@ -0,0 +1,105 @@ +// ライセンス: 最新のGPL + +// キャッシュ、ファイル操作まわり + +#ifndef _CACHE_H +#define _CACHE_H + +#include +#include + +namespace CACHE +{ + // is_file_exists の戻り値 + enum + { + EXIST_FILE = 0, // ファイル + EXIST_DIR, // ディレクトリ + EXIST, // 存在しない or 何か存在してる + EXIST_ERROR // エラー + }; + + // 設定ファイル + std::string path_conf(); + + // セッション情報ファイル + 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(); + + // お気に入り + std::string path_xml_favorite(); + std::string path_xml_favorite_bkup(); + + // 外部板( navi2ch 互換 ) + std::string path_etcboard(); + + // 履歴 + std::string path_xml_history(); + + // 板履歴 + std::string path_xml_history_board(); + + // 板移転情報 + 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_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 filename_img( const std::string& url ); + std::string path_img( const std::string& url ); + std::string path_img_info( const std::string& url ); + + // キャッシュの mkdir 関係 + bool mkdir_root(); + bool mkdir_imgroot(); + bool mkdir_parent_of_board( const std::string& url ); + bool mkdir_boardroot( const std::string& url ); + + // 生データ読み書き + size_t load_rawdata( const std::string& path, std::string& str ); + size_t load_rawdata( const std::string& path, char* data, size_t n ); + bool save_rawdata( const std::string& path, const std::string& str ); + bool save_rawdata( const std::string& path, const char* data, size_t n ); + + // ファイル操作 + long is_file_exists( const std::string& path ); + size_t get_filesize( const std::string& path ); + bool jdmkdir( const std::string& path ); + bool jdcopy( const std::string& file_from, const std::string& file_to ); + std::string open_save_diag( const std::string& file_from, const std::string& file_to ); + + // dir ディレクトリ内のレギュラーファイルのリストを取得 + std::list< std::string > get_filelist( const std::string& dir ); +} + +#endif diff --git a/src/colorid.h b/src/colorid.h new file mode 100644 index 000000000..c143e3b44 --- /dev/null +++ b/src/colorid.h @@ -0,0 +1,32 @@ +// カラーID + +#ifndef _COLOR_ID_H +#define _COLOR_ID_H + +enum +{ + COLOR_DEFAULT = 0, + COLOR_CHAR, + COLOR_CHAR_NAME, + COLOR_CHAR_SELECTION, + COLOR_CHAR_LINK, + COLOR_CHAR_LINK_PUR, // パープル + COLOR_CHAR_LINK_RED, + COLOR_CHAR_HIGHLIGHT, + COLOR_CHAR_BOOKMARK, + + COLOR_BACK, + COLOR_BACK_SELECTION, + COLOR_BACK_HIGHLIGHT, + + COLOR_SEPARATOR_NEW, // 新着セパレータ + + COLOR_IMG_NOCACHE, + COLOR_IMG_LOADING, + COLOR_IMG_CACHED, + COLOR_IMG_ERR, + + COLOR_NUM +}; + +#endif diff --git a/src/command.cpp b/src/command.cpp new file mode 100644 index 000000000..55ff86799 --- /dev/null +++ b/src/command.cpp @@ -0,0 +1,30 @@ +// ライセンス: 最新のGPL + +//#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::Widget* CORE::get_toplevel(){ return CORE::get_instance()->get_toplevel(); } diff --git a/src/command.h b/src/command.h new file mode 100644 index 000000000..37ee6e20d --- /dev/null +++ b/src/command.h @@ -0,0 +1,34 @@ +// ライセンス: 最新のGPL + +// +// コアのインターフェース +// + + +#ifndef _COMMAND_H +#define _COMMAND_H + +#include + +namespace Gtk +{ + class Widget; +} + +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::Widget* get_toplevel(); +} + +#endif diff --git a/src/config/Makefile.am b/src/config/Makefile.am new file mode 100644 index 000000000..4ec3e48cb --- /dev/null +++ b/src/config/Makefile.am @@ -0,0 +1,10 @@ +noinst_LIBRARIES = libconfig.a + +libconfig_a_SOURCES = \ + globalconf.cpp mousekeyconf.cpp keyconfig.cpp mouseconfig.cpp buttonconfig.cpp + +noinst_HEADERS = \ + globalconf.h mousekeyconf.h mousekeyitem.h keyconfig.h mouseconfig.h buttonconfig.h + +AM_CXXFLAGS = @GTKMM_CFLAGS@ +INCLUDES = -I$(top_srcdir)/src diff --git a/src/config/buttonconfig.cpp b/src/config/buttonconfig.cpp new file mode 100644 index 000000000..37082d1ee --- /dev/null +++ b/src/config/buttonconfig.cpp @@ -0,0 +1,137 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "buttonconfig.h" +#include "mousekeyitem.h" + +#include "cache.h" +#include "controlutil.h" + +#include "jdlib/confloader.h" + +CONFIG::ButtonConfig* instance_buttonconfig = NULL; + + +CONFIG::ButtonConfig* CONFIG::get_buttonconfig() +{ + if( ! instance_buttonconfig ) instance_buttonconfig = new CONFIG::ButtonConfig(); + + return instance_buttonconfig; +} + + +void CONFIG::delete_buttonconfig() +{ + if( instance_buttonconfig ) delete instance_buttonconfig; + instance_buttonconfig = NULL; +} + +////////////////////////////////////////////////////////// + +using namespace CONFIG; + + +ButtonConfig::ButtonConfig() + : MouseKeyConf() +{ + load_conf(); +} + + +ButtonConfig::~ButtonConfig() +{ + MouseKeyConf::save_conf( CACHE::path_buttonconf() ); +} + + +// +// 設定ファイル読み込み +// +void ButtonConfig::load_conf() +{ + std::string str_motion; + JDLIB::ConfLoader cf( CACHE::path_buttonconf(), std::string() ); + + // デフォルト動作 + SETMOTION( "ClickButton", "Left" ); + SETMOTION( "DblClickButton", "DblLeft" ); + SETMOTION( "CloseTabButton", "Mid" ); + SETMOTION( "ReloadTabButton", "DblLeft" ); + SETMOTION( "AutoScrollButton", "Mid" ); + SETMOTION( "GestureButton", "Right" ); + SETMOTION( "PopupmenuButton", "Right" ); + + // BBSLIST用ボタン設定 + SETMOTION( "OpenBoardButton", "Left" ); + SETMOTION( "OpenBoardTabButton", "Mid" ); + + // BOARD用ボタン設定 + SETMOTION( "OpenArticleButton", "Left" ); + SETMOTION( "OpenArticleTabButton", "Mid" ); + + // ARTICLE用ボタン設定 + SETMOTION( "ReferResButton", "Right" ); + SETMOTION( "BmResButton", "Mid" ); + SETMOTION( "PopupmenuResButton", "Left" ); + + SETMOTION( "DrawoutAncButton", "Mid" ); + SETMOTION( "PopupmenuAncButton", "Left Right" ); + + SETMOTION( "PopupIDButton", "Right" ); + SETMOTION( "DrawoutIDButton", "Mid" ); + SETMOTION( "PopupmenuIDButton", "Left" ); + + SETMOTION( "OpenImageButton", "Left" ); + SETMOTION( "OpenBackImageButton", "Mid" ); + SETMOTION( "PopupmenuImageButton", "Right" ); + + SETMOTION( "OpenBeButton", "Left Mid" ); + SETMOTION( "PopupmenuBeButton", "Right" ); +} + + +// ひとつの操作をデータベースに登録 +void ButtonConfig::set_one_motion( const std::string& name, const std::string& str_motion ) +{ + if( name.empty() || str_motion.empty() ) return; + +#ifdef _DEBUG + std::cout << "ButtonConfig::set_motion " << name << std::endl; +#endif + + int id = CONTROL::get_id( name ); + if( id == CONTROL::None ) return; + +#ifdef _DEBUG + std::cout << "id = " << id << std::endl; +#endif + + int mode = MouseKeyConf::get_mode( id ); + if( mode == CONTROL::MODE_ERROR ) return; + + bool ctrl = false; + bool shift = false; + bool alt = false; + bool dblclick = false; + guint motion = 0; + if( str_motion == "Left" ) motion = 1; + if( str_motion == "Mid" ) motion = 2; + if( str_motion == "Right" ) motion = 3; + if( str_motion == "DblLeft" ){ motion = 1; dblclick = true; } + if( str_motion == "DblMid" ) { motion = 2; dblclick = true; } + if( str_motion == "DblRight" ) { motion = 3; dblclick = true; } + +#ifdef _DEBUG + std::cout << "motion = " << motion << " dblclick = " << dblclick << std::endl; +#endif + + if( !motion ) return; + + // ひとつのボタンに複数の機能が割り当てられているので重複チェックはしない + + // データベース登録 + MouseKeyItem* item = new MouseKeyItem( id, mode, name, str_motion, motion, ctrl, shift, alt, dblclick ); + MouseKeyConf::vec_items().push_back( item ); +} diff --git a/src/config/buttonconfig.h b/src/config/buttonconfig.h new file mode 100644 index 000000000..b4696a03d --- /dev/null +++ b/src/config/buttonconfig.h @@ -0,0 +1,30 @@ +// ライセンス: 最新のGPL +// +// マウスボタン設定 +// + +#ifndef _BUTTONCONFIG_H +#define _BUTTONCONFIG_H + +#include "mousekeyconf.h" + +namespace CONFIG +{ + class ButtonConfig : public MouseKeyConf + { + public: + + ButtonConfig(); + virtual ~ButtonConfig(); + + private: + + void load_conf(); + virtual void set_one_motion( const std::string& name, const std::string& str_motion ); + }; + + ButtonConfig* get_buttonconfig(); + void delete_buttonconfig(); +} + +#endif diff --git a/src/config/globalconf.cpp b/src/config/globalconf.cpp new file mode 100644 index 000000000..f48788fd4 --- /dev/null +++ b/src/config/globalconf.cpp @@ -0,0 +1,373 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "globalconf.h" +#include "cache.h" + +#include "jdlib/confloader.h" + +#define COLOR_SIZE 3 + +bool restore_board; +bool restore_article; +bool restore_image; + +int color_char[ COLOR_SIZE ]; +int color_sepa[ COLOR_SIZE ]; +int color_back[ COLOR_SIZE ]; +int color_back_popup[ COLOR_SIZE ]; +int color_back_tree[ COLOR_SIZE ]; +std::string fontname_main; +std::string fontname_popup; +std::string fontname_tree;; +std::string url_login2ch; +std::string url_bbsmenu; +std::string path_cacheroot; + +std::string agent_for2ch; + +bool use_proxy_for2ch; +std::string proxy_for2ch; +int proxy_port_for2ch; + +bool use_proxy_for2ch_w; +std::string proxy_for2ch_w; +int proxy_port_for2ch_w; + +std::string agent_for_data; + +bool use_proxy_for_data; +std::string proxy_for_data; +int proxy_port_for_data; + +std::string x_2ch_ua; + +int loader_bufsize; +int loader_timeout; +int loader_timeout_post; +int loader_timeout_img; + +std::string command_openurl; + +int imgpopup_width; +int imgpopup_height; +bool use_mosaic; + +bool show_oldarticle; + +int tree_scroll_size; +bool open_one_category; +bool always_write_ok; +int margin_popup; +int mouse_radius; +int history_size; +int instruct_popup; + +// +// 初期設定 +// +const bool CONFIG::init_config() +{ + JDLIB::ConfLoader cf( CACHE::path_conf(), std::string() ); + +#ifdef _DEBUG + std::cout << "CONFIG::init_config empty = " << cf.empty() << std::endl; +#endif + + // 前回開いたviewを復元するか + restore_board = cf.get_option( "restore_board", false ); + restore_article = cf.get_option( "restore_article", false ); + restore_image = cf.get_option( "restore_image", false ); + + // フォント + fontname_main = cf.get_option( "fontname_main", "Kochi Gothic 12" ); + fontname_popup = cf.get_option( "fontname_popup","Kochi Gothic 9" ); + fontname_tree = cf.get_option( "fontname_tree","Kochi Gothic 10" ); + + // キャッシュのルートディレクトリ + // キャッシュ構造は navi2ch の上位互換なので path_cacheroot = "~/.navi2ch/" とすればnavi2chとキャッシュを共有できる + path_cacheroot = cf.get_option( "path_cacheroot", "~/.jd/" ); + + // 読み込み用プロクシとポート番号 + use_proxy_for2ch = cf.get_option( "use_proxy_for2ch", 0 ); + proxy_for2ch = cf.get_option( "proxy_for2ch", "" ); + proxy_port_for2ch = cf.get_option( "proxy_port_for2ch", 8080 ); + + // 書き込み用プロクシとポート番号 + use_proxy_for2ch_w = cf.get_option( "use_proxy_for2ch_w", 0 ); + proxy_for2ch_w = cf.get_option( "proxy_for2ch_w", "" ); + proxy_port_for2ch_w = cf.get_option( "proxy_port_for2ch_w", 8080 ); + + // 2chの外にアクセスするときのプロクシとポート番号 + use_proxy_for_data = cf.get_option( "use_proxy_for_data", 0 ); + proxy_for_data = cf.get_option( "proxy_for_data", "" ); + proxy_port_for_data = cf.get_option( "proxy_port_for_data", 8080 ); + + // 2ch にアクセスするときのエージェント名 + agent_for2ch = cf.get_option( "agent_for2ch", "Monazilla/1.00 Navi2ch" ); + + // 2ch外にアクセスするときのエージェント名 + agent_for_data = cf.get_option( "agent_for_data", "Mozilla/5.0 (Windows; U; Windows NT 5.0; ja; rv:1.8) Gecko/20051111 Firefox/1.5" ); + + // 2ch にログインするときのX-2ch-UA + x_2ch_ua = cf.get_option( "x_2ch_ua", "Navigator for 2ch 1.7.5" ); + + // ローダのバッファサイズ + loader_bufsize = cf.get_option( "loader_bufsize", 32 ); + + // ローダのタイムアウト値 + loader_timeout = cf.get_option( "loader_timeout", 10 ); + loader_timeout_post = cf.get_option( "loader_timeout_post", 30 ); // ポスト + loader_timeout_img = cf.get_option( "loader_timeout_img", 30 ); // 画像 + + // リンクをクリックしたときに実行するコマンド + command_openurl = cf.get_option( "command_openurl", "firefox -remote \"openURL(%s,new-tab)\"" ); + + // 画像ポップアップサイズ + imgpopup_width = cf.get_option( "imgpopup_width", 320 ); + imgpopup_height = cf.get_option( "imgpopup_height", 240 ); + + // 画像にモザイクかける + use_mosaic = cf.get_option( "use_mosaic", 1 ); + + // 2chの認証サーバ + url_login2ch = cf.get_option( "url_login2ch", "https://2chv.tora3.net/futen.cgi" ); + + // bbsmenu.htmlのURL + url_bbsmenu = cf.get_option( "url_bbsmenu", "http://menu.2ch.net/bbsmenu.html" ); + + // 色 ( RGB の順 ) 範囲は 0 - 65535 + + // 文字色 + color_char[ 0 ] = cf.get_option( "color_char_R", 0 ); + color_char[ 1 ] = cf.get_option( "color_char_G", 0 ); + color_char[ 2 ] = cf.get_option( "color_char_B", 0 ); + + // 新着セパレータ + color_sepa[ 0 ] = cf.get_option( "color_sepa_R", 32000 ); + color_sepa[ 1 ] = cf.get_option( "color_sepa_G", 32000 ); + color_sepa[ 2 ] = cf.get_option( "color_sepa_B", 32000 ); + + // 背景色 + color_back[ 0 ] = cf.get_option( "color_back_R", 65000 ); + color_back[ 1 ] = cf.get_option( "color_back_G", 65000 ); + color_back[ 2 ] = cf.get_option( "color_back_B", 63000 ); + + // ツリービューの背景色 + color_back_tree[ 0 ] = cf.get_option( "color_tree_R", 65000 ); + color_back_tree[ 1 ] = cf.get_option( "color_tree_G", 65000 ); + color_back_tree[ 2 ] = cf.get_option( "color_tree_B", 63000 ); + + // ポップアップの背景色 + color_back_popup[ 0 ] = cf.get_option( "color_popup_R", 65000 ); + color_back_popup[ 1 ] = cf.get_option( "color_popup_G", 65000 ); + color_back_popup[ 2 ] = cf.get_option( "color_popup_B", 63000 ); + + // boardビューで古いスレも表示 + show_oldarticle = cf.get_option( "show_oldarticle", false ); + + + ///////////////////////// + // UI 周りの設定 + + // ツリービューのスクロール量(行数) + tree_scroll_size = cf.get_option( "tree_scroll_size", 4 ); + + // 板一覧でカテゴリを常にひとつだけ開く + open_one_category = cf.get_option( "open_one_category", false ); + + // 書き込み時に書き込み確認ダイアログを出すかどうか + always_write_ok = cf.get_option( "always_write_ok", false ); + + // ポップアップとカーソルの間のマージン + margin_popup = cf.get_option( "margin_popup", 30 ); + + // マウスジェスチャの判定開始半径 + mouse_radius = cf.get_option( "mouse_radius", 25 ); + + // 履歴の保持数 + history_size = cf.get_option( "history_size", 20 ); + + // 0以上なら多重ポップアップの説明を表示する + instruct_popup = cf.get_option( "instruct_popup", 100 ); + + return ! cf.empty(); +} + + +// +// コンフィグファイル保存 +// +void CONFIG::save_conf() +{ + JDLIB::ConfLoader cf( CACHE::path_conf(), std::string() ); + + cf.update( "restore_board", restore_board ); + cf.update( "restore_article", restore_article ); + cf.update( "restore_image", restore_image ); + cf.update( "url_login2ch", url_login2ch ); + cf.update( "url_bbsmenu", url_bbsmenu ); + + cf.update( "fontname_main", fontname_main ); + cf.update( "fontname_popup", fontname_popup ); + cf.update( "fontname_tree", fontname_tree ); + + cf.update( "path_cacheroot", path_cacheroot ); + + cf.update( "agent_for2ch", agent_for2ch ); + + cf.update( "use_proxy_for2ch", use_proxy_for2ch ); + cf.update( "proxy_for2ch", proxy_for2ch ); + cf.update( "proxy_port_for2ch", proxy_port_for2ch ); + + cf.update( "use_proxy_for2ch_w", use_proxy_for2ch_w ); + cf.update( "proxy_for2ch_w", proxy_for2ch_w ); + cf.update( "proxy_port_for2ch_w", proxy_port_for2ch_w ); + + cf.update( "agent_for_data", agent_for_data ); + + cf.update( "use_proxy_for_data", use_proxy_for_data ); + cf.update( "proxy_for_data", proxy_for_data ); + 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_timeout", loader_timeout ); + cf.update( "loader_timeout_post", loader_timeout_post ); + cf.update( "loader_timeout_img", loader_timeout_img ); + + cf.update( "command_openurl", command_openurl ); + + cf.update( "imgpopup_width", imgpopup_width ); + cf.update( "imgpopup_height", imgpopup_height ); + cf.update( "use_mosaic", use_mosaic ); + + cf.update( "color_char_R", color_char[ 0 ] ); + cf.update( "color_char_G", color_char[ 1 ] ); + cf.update( "color_char_B", color_char[ 2 ] ); + + cf.update( "color_sepa_R", color_sepa[ 0 ] ); + cf.update( "color_sepa_G", color_sepa[ 1 ] ); + cf.update( "color_sepa_B", color_sepa[ 2 ] ); + + cf.update( "color_back_R", color_back[ 0 ] ); + cf.update( "color_back_G", color_back[ 1 ] ); + cf.update( "color_back_B", color_back[ 2 ] ); + + cf.update( "color_tree_R", color_back_tree[ 0 ] ); + cf.update( "color_tree_G", color_back_tree[ 1 ] ); + cf.update( "color_tree_B", color_back_tree[ 2 ] ); + + cf.update( "color_popup_R", color_back_popup[ 0 ] ); + cf.update( "color_popup_G", color_back_popup[ 1 ] ); + cf.update( "color_popup_B", color_back_popup[ 2 ] ); + + cf.update( "show_oldarticle", show_oldarticle ); + + cf.update( "tree_scroll_size", tree_scroll_size ); + cf.update( "open_one_category", open_one_category ); + cf.update( "always_write_ok", always_write_ok ); + cf.update( "margin_popup", margin_popup ); + cf.update( "mouse_radius", mouse_radius ); + cf.update( "history_size", history_size ); + cf.update( "instruct_popup", instruct_popup ); + + cf.save(); +} + + +const bool CONFIG::get_restore_board(){ return restore_board; } +void CONFIG::set_restore_board( bool restore ){ restore_board = restore; } +const bool CONFIG::get_restore_article(){ return restore_article; } +void CONFIG::set_restore_article( bool restore ){ restore_article = restore; } +const bool CONFIG::get_restore_image(){ return restore_image; } +void CONFIG::set_restore_image( bool restore ){ restore_image = restore; } + +const int* CONFIG::get_color_char() { return color_char; } +const int* CONFIG::get_color_separator() { return color_sepa; } +const int* CONFIG::get_color_back() { return color_back; } +const int* CONFIG::get_color_back_popup() { return color_back_popup; } +const int* CONFIG::get_color_back_tree() { return color_back_tree; } + +void CONFIG::set_color_char( int* color ) { memcpy( color_char, color, sizeof( int )*COLOR_SIZE ); } +void CONFIG::set_color_separator( int* color ) { memcpy( color_sepa, color, sizeof( int )*COLOR_SIZE ); } +void CONFIG::set_color_back( int* color ) { memcpy( color_back, color, sizeof( int )*COLOR_SIZE ); } +void CONFIG::set_color_back_popup( int* color ) { memcpy( color_back_popup, color, sizeof( int )*COLOR_SIZE ); } +void CONFIG::set_color_back_tree( int* color ) { memcpy( color_back_tree, color, sizeof( int )*COLOR_SIZE ); } + + +const std::string& CONFIG::get_fontname_main() { return fontname_main; } +const std::string& CONFIG::get_fontname_popup() { return fontname_popup; } +const std::string& CONFIG::get_fontname_tree() { return fontname_tree; } + +void CONFIG::set_fontname_main( const std::string& name) { fontname_main = name; } +void CONFIG::set_fontname_popup( const std::string& name) { fontname_popup = name; } +void CONFIG::set_fontname_tree( const std::string& name) { fontname_tree = name; } + +const std::string& CONFIG::get_url_login2ch() { return url_login2ch; } +const std::string& CONFIG::get_url_bbsmenu() { return url_bbsmenu; } +const std::string& CONFIG::get_path_cacheroot() { return path_cacheroot; } + +const std::string& CONFIG::get_agent_for2ch() { return agent_for2ch; } + +const bool CONFIG::get_use_proxy_for2ch() { return use_proxy_for2ch; } +const std::string& CONFIG::get_proxy_for2ch() { return proxy_for2ch; } +const int CONFIG::get_proxy_port_for2ch() { return proxy_port_for2ch; } + +void CONFIG::set_use_proxy_for2ch( bool set ){ use_proxy_for2ch = set; } +void CONFIG::set_proxy_for2ch( const std::string& proxy ){ proxy_for2ch = proxy; } +void CONFIG::set_proxy_port_for2ch( int port ){ proxy_port_for2ch = port; } + +const bool CONFIG::get_use_proxy_for2ch_w() { return use_proxy_for2ch_w; } +const std::string& CONFIG::get_proxy_for2ch_w() { return proxy_for2ch_w; } +const int CONFIG::get_proxy_port_for2ch_w() { return proxy_port_for2ch_w; } + +void CONFIG::set_use_proxy_for2ch_w( bool set ){ use_proxy_for2ch_w = set; } +void CONFIG::set_proxy_for2ch_w( const std::string& proxy ){ proxy_for2ch_w = proxy; } +void CONFIG::set_proxy_port_for2ch_w( int port ){ proxy_port_for2ch_w = port; } + +const std::string& CONFIG::get_agent_for_data() { return agent_for_data; } + +const bool CONFIG::get_use_proxy_for_data() { return use_proxy_for_data; } +const std::string& CONFIG::get_proxy_for_data() { return proxy_for_data; } +const int CONFIG::get_proxy_port_for_data() { return proxy_port_for_data; } + +const std::string& CONFIG::get_x_2ch_ua() { return x_2ch_ua; } + +void CONFIG::set_use_proxy_for_data( bool set ){ use_proxy_for_data = set; } +void CONFIG::set_proxy_for_data( const std::string& proxy ){ proxy_for_data = proxy; } +void CONFIG::set_proxy_port_for_data( int port ){ proxy_port_for_data = port; } + +const int CONFIG::get_loader_bufsize(){ return loader_bufsize; } +const int CONFIG::get_loader_timeout(){ return loader_timeout; } +const int CONFIG::get_loader_timeout_post(){ return loader_timeout_post; } +const int CONFIG::get_loader_timeout_img(){ return loader_timeout_img; } + +const std::string& CONFIG::get_command_openurl() { return command_openurl; } +void CONFIG::set_command_openurl( const std::string& command ){ command_openurl = command; } + +const int CONFIG::get_imgpopup_width(){ return imgpopup_width; } +const int CONFIG::get_imgpopup_height(){ return imgpopup_height; } +const bool CONFIG::get_use_mosaic(){ return use_mosaic; } +void CONFIG::set_use_mosaic( bool mosaic ) { use_mosaic = mosaic; } + +const bool CONFIG::get_show_oldarticle(){ return show_oldarticle; } +void CONFIG::set_show_oldarticle( bool showarticle ){ show_oldarticle = showarticle; } + +const int CONFIG::get_tree_scroll_size(){ return tree_scroll_size; } +const bool CONFIG::get_open_one_category(){ return open_one_category; } +const bool CONFIG::get_always_write_ok() { return always_write_ok; } +void CONFIG::set_always_write_ok( bool write_ok ){ always_write_ok = write_ok; } +const int CONFIG::get_margin_popup(){ return margin_popup; } +const int CONFIG::get_mouse_radius(){ return mouse_radius; } +const int CONFIG::get_history_size(){ return history_size; } + +// 0以上なら多重ポップアップの説明を表示する +// 呼び出される度に--する +const int CONFIG::get_instruct_popup(){ + if( instruct_popup ) return instruct_popup--; + return 0; +} diff --git a/src/config/globalconf.h b/src/config/globalconf.h new file mode 100644 index 000000000..efdc6318c --- /dev/null +++ b/src/config/globalconf.h @@ -0,0 +1,141 @@ +// ライセンス: 最新のGPL +// +// グローバル設定 +// + +#ifndef _GLOBALCONF_H +#define _GLOBALCONF_H + +#include + +namespace CONFIG +{ + // 設定読み込み + const bool init_config(); + + void save_conf(); + + // 前回開いたviewを復元するか + const bool get_restore_board(); + void set_restore_board( bool restore ); + const bool get_restore_article(); + void set_restore_article( bool restore ); + const bool get_restore_image(); + void set_restore_image( bool restore ); + + // 色 ( RGB の順 ) 範囲は 0 - 65535 + const int* get_color_char(); + const int* get_color_separator(); + const int* get_color_back(); + const int* get_color_back_popup(); + const int* get_color_back_tree(); + + void set_color_char( int* color ); + void set_color_separator( int* color ); + void set_color_back( int* color ); + void set_color_back_popup( int* color ); + void set_color_back_tree( int* color ); + + // フォント + const std::string& get_fontname_main(); + const std::string& get_fontname_popup(); + const std::string& get_fontname_tree(); + + void set_fontname_main( const std::string& name ); + void set_fontname_popup( const std::string& name ); + void set_fontname_tree( const std::string& name ); + + // 2chの認証サーバ + const std::string& get_url_login2ch(); + + // bbsmenu.htmlのURL + const std::string& get_url_bbsmenu(); + + // キャッシュのルートディレクトリ + // キャッシュ構造は navi2ch の上位互換なので path_cacheroot = "~/.navi2ch/" とすればnavi2chとキャッシュを共有できる + const std::string& get_path_cacheroot(); + + // 2ch にアクセスするときのエージェント名 + const std::string& get_agent_for2ch(); + + // 2ch にログインするときのX-2ch-UA + const std::string& get_x_2ch_ua(); + + // 読み込み用プロクシとポート番号 + const bool get_use_proxy_for2ch(); + const std::string& get_proxy_for2ch(); + const int get_proxy_port_for2ch(); + + void set_use_proxy_for2ch( bool set ); + void set_proxy_for2ch( const std::string& proxy ); + void set_proxy_port_for2ch( int port ); + + // 書き込み用プロクシとポート番号 + const bool get_use_proxy_for2ch_w(); + const std::string& get_proxy_for2ch_w(); + const int get_proxy_port_for2ch_w(); + + void set_use_proxy_for2ch_w( bool set ); + void set_proxy_for2ch_w( const std::string& proxy ); + void set_proxy_port_for2ch_w( int port ); + + // 2ch外にアクセスするときのエージェント名 + const std::string& get_agent_for_data(); + + // 2chの外にアクセスするときのプロクシとポート番号 + const bool get_use_proxy_for_data(); + const std::string& get_proxy_for_data(); + const int get_proxy_port_for_data(); + + void set_use_proxy_for_data( bool set ); + void set_proxy_for_data( const std::string& proxy ); + void set_proxy_port_for_data( int port ); + + // ローダのバッファサイズ + const int get_loader_bufsize(); + + // ローダのタイムアウト値 + const int get_loader_timeout(); + const int get_loader_timeout_post(); + const int get_loader_timeout_img(); + + // リンクをクリックしたときに実行するコマンド + const std::string& get_command_openurl(); + void set_command_openurl( const std::string& command ); + + // 画像ポップアップサイズ + const int get_imgpopup_width(); + const int get_imgpopup_height(); + + // 画像にモザイクかける + const bool get_use_mosaic(); + void set_use_mosaic( bool mosaic ); + + const bool get_show_oldarticle(); + void set_show_oldarticle( bool showarticle ); + + // ツリービューのスクロール量(行数) + const int get_tree_scroll_size(); + + // 板一覧でカテゴリを常にひとつだけ開く + const bool get_open_one_category(); + + // 書き込み時に書き込み確認ダイアログを出すかどうか + const bool get_always_write_ok(); + void set_always_write_ok( bool write_ok ); + + // ポップアップとカーソルの間のマージン + const int get_margin_popup(); + + // マウスジェスチャの判定開始半径 + const int get_mouse_radius(); + + // 履歴の保持数 + const int get_history_size(); + + // 0以上なら多重ポップアップの説明を表示する + const int get_instruct_popup(); +} + + +#endif diff --git a/src/config/keyconfig.cpp b/src/config/keyconfig.cpp new file mode 100644 index 000000000..b1cb19631 --- /dev/null +++ b/src/config/keyconfig.cpp @@ -0,0 +1,203 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +//#define _DEBUG_XML +#include "jddebug.h" + +#include "keyconfig.h" +#include "mousekeyitem.h" + +#include "cache.h" +#include "controlutil.h" + +#include "jdlib/miscutil.h" +#include "jdlib/miscmsg.h" +#include "jdlib/jdregex.h" +#include "jdlib/confloader.h" + + +CONFIG::KeyConfig* instance_keyconfig = NULL; + +CONFIG::KeyConfig* CONFIG::get_keyconfig() +{ + if( ! instance_keyconfig ) instance_keyconfig = new CONFIG::KeyConfig(); + + return instance_keyconfig; +} + + +void CONFIG::delete_keyconfig() +{ + if( instance_keyconfig ) delete instance_keyconfig; + instance_keyconfig = NULL; +} + +////////////////////////////////////////////////////////// + +using namespace CONFIG; + + +KeyConfig::KeyConfig() +{ + load_conf(); +} + + +KeyConfig::~KeyConfig() +{ + MouseKeyConf::save_conf( CACHE::path_keyconf() ); +} + + + +// +// 設定ファイル読み込み +// +void KeyConfig::load_conf() +{ + std::string str_motion; + JDLIB::ConfLoader cf( CACHE::path_keyconf(), std::string() ); + + // 共通設定 + SETMOTION( "Up", "k Up" ); + SETMOTION( "UpMid", "u" ); + SETMOTION( "UpFast", "b Page_Up" ); + + SETMOTION( "Down", "j Down" ); + SETMOTION( "DownMid", "d" ); + SETMOTION( "DownFast", "Page_Down Space" ); + + SETMOTION( "Right", "l Right" ); + SETMOTION( "Left", "h Left" ); + + SETMOTION( "TabRight", "Ctrl+Page_Down Ctrl+Tab Ctrl+Left_Tab Ctrl+l Ctrl+Right" ); + SETMOTION( "TabLeft", "Ctrl+Page_Up Ctrl+Shift+Tab Ctrl+Shift+Left_Tab Ctrl+h Ctrl+Left" ); + + SETMOTION( "PreBookMark", "Ctrl+F2" ); + SETMOTION( "NextBookMark", "F2" ); + SETMOTION( "ToggleArticle", "Alt+x" ); + + SETMOTION( "Home", "Home g <" ); + SETMOTION( "End", "End G >" ); + + SETMOTION( "Quit", "Ctrl+w q" ); + SETMOTION( "Save", "Ctrl+s" ); + SETMOTION( "Delete", "Delete" ); + SETMOTION( "Reload", "F5 s" ); + SETMOTION( "StopLoading", "Escape" ); // = CONTROL::Cancel + SETMOTION( "Copy", "Ctrl+c" ); + + SETMOTION( "Search", "Ctrl+f /" ); + SETMOTION( "SearchInvert", "?" ); + SETMOTION( "SearchNext", "Enter F3 Ctrl+g n" ); + SETMOTION( "SearchPrev", "Shift+Enter Ctrl+F3 Ctrl+G N" ); + SETMOTION( "DrawOutAnd", "Ctrl+Enter" ); + + // BBSLIST + SETMOTION( "OpenBoard", "Space" ); + + // BOARD + SETMOTION( "OpenArticle", "Space" ); + SETMOTION( "NewArticle", "w" ); + + // ARTICLE + SETMOTION( "GotoNew", "F4" ); + SETMOTION( "WriteMessage", "w Alt+w" ); + + // IMAGE + SETMOTION( "CancelMosaic", "c" ); + SETMOTION( "ZoomFitImage", "x" ); + SETMOTION( "ZoomInImage", "Plus" ); + SETMOTION( "ZoomOutImage", "-" ); + SETMOTION( "OrgSizeImage", "z" ); + + // MESSAGE + SETMOTION( "CancelWrite", "Alt+q" ); + SETMOTION( "ExecWrite", "Alt+w" ); +} + + + +// ひとつの操作をデータベースに登録 +void KeyConfig::set_one_motion( const std::string& name, const std::string& str_motion ) +{ + if( name.empty() || str_motion.empty() ) return; + + int id = CONTROL::get_id( name ); + if( id == CONTROL::None ) return; + + int mode = MouseKeyConf::get_mode( id ); + if( mode == CONTROL::MODE_ERROR ) return; + + JDLIB::Regex regex; + if( regex.exec( "(Ctrl)?(\\+?Shift)?(\\+?Alt)?\\+?(.*)", str_motion, 0, true ) ){ + + guint motion; + bool ctrl = false; + bool shift = false; + bool alt = false; + + if( ! regex.str( 1 ).empty() ) ctrl = true; + if( ! regex.str( 2 ).empty() ) shift = true; + if( ! regex.str( 3 ).empty() ) alt = true; + + std::string str_key = regex.str( 4 ); + if( str_key.empty() ) return; + + if( str_key == "Space" ) motion = ' '; + else if( str_key == "Escape" ) motion = GDK_Escape; + else if( str_key == "Delete" ) motion = GDK_Delete; + else if( str_key == "Enter" ) motion = GDK_Return; + + else if( str_key == "Up" ) motion = GDK_Up; + else if( str_key == "Down" ) motion = GDK_Down; + else if( str_key == "Left" ) motion = GDK_Left; + else if( str_key == "Right" ) motion = GDK_Right; + else if( str_key == "Page_Up" ) motion = GDK_Page_Up; + else if( str_key == "Page_Down" ) motion = GDK_Page_Down; + else if( str_key == "Tab" ) motion = GDK_Tab; + else if( str_key == "Left_Tab" ) motion = GDK_ISO_Left_Tab; + else if( str_key == "Home" ) motion = GDK_Home; + else if( str_key == "End" ) motion = GDK_End; + + else if( str_key == "F1" ) motion = GDK_F1; + else if( str_key == "F2" ) motion = GDK_F2; + else if( str_key == "F3" ) motion = GDK_F3; + else if( str_key == "F4" ) motion = GDK_F4; + else if( str_key == "F5" ) motion = GDK_F5; + else if( str_key == "F6" ) motion = GDK_F6; + else if( str_key == "F7" ) motion = GDK_F7; + else if( str_key == "F8" ) motion = GDK_F8; + else if( str_key == "F9" ) motion = GDK_F9; + else if( str_key == "F10" ) motion = GDK_F10; + else if( str_key == "F11" ) motion = GDK_F11; + else if( str_key == "F12" ) motion = GDK_F12; + + else if( str_key == "Plus" ) motion = '+'; + else motion = str_key[ 0 ]; + + // 大文字やshiftが必要な文字の時はshiftも有効にする + if( motion >= 'A' && motion <= 'Z' ) shift = true; + if( motion == '?' || motion == '<' || motion == '>' || motion == '+' ) shift = true; + + int id_check = MouseKeyConf::check_conflict( mode, motion, ctrl, shift, alt, false ); + if( id_check != CONTROL::None ){ + MISC::ERRMSG( "key config : " + str_motion + " is already used." ); + return; + } + + MouseKeyItem* item = new MouseKeyItem( id, mode, name, str_motion, motion, ctrl, shift, alt, false ); + MouseKeyConf::vec_items().push_back( item ); + } +} + + + +// 操作文字列取得 +const std::string KeyConfig::get_str_motion( int id ) +{ + std::string str_motion = MouseKeyConf::get_str_motion( id ); + str_motion = MISC::replace_str( str_motion, "Plus", "+" ); + + return str_motion; +} diff --git a/src/config/keyconfig.h b/src/config/keyconfig.h new file mode 100644 index 000000000..955227dc9 --- /dev/null +++ b/src/config/keyconfig.h @@ -0,0 +1,34 @@ +// ライセンス: 最新のGPL +// +// キー設定クラス +// + +#ifndef _KEYCONFIG_H +#define _KEYCONFIG_H + +#include "mousekeyconf.h" + +namespace CONFIG +{ + class KeyConfig : public MouseKeyConf + { + public: + + KeyConfig(); + virtual ~KeyConfig(); + + // 操作文字列取得 + virtual const std::string get_str_motion( int id ); + + private: + + void load_conf(); + virtual void set_one_motion( const std::string& name, const std::string& str_motion ); + }; + + KeyConfig* get_keyconfig(); + void delete_keyconfig(); +} + + +#endif diff --git a/src/config/mouseconfig.cpp b/src/config/mouseconfig.cpp new file mode 100644 index 000000000..17cec4a5a --- /dev/null +++ b/src/config/mouseconfig.cpp @@ -0,0 +1,123 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "mouseconfig.h" +#include "mousekeyitem.h" + +#include "cache.h" +#include "controlutil.h" + +#include "jdlib/miscutil.h" +#include "jdlib/miscmsg.h" +#include "jdlib/confloader.h" + + +CONFIG::MouseConfig* instance_mouseconfig = NULL; + + +CONFIG::MouseConfig* CONFIG::get_mouseconfig() +{ + if( ! instance_mouseconfig ) instance_mouseconfig = new CONFIG::MouseConfig(); + + return instance_mouseconfig; +} + + +void CONFIG::delete_mouseconfig() +{ + if( instance_mouseconfig ) delete instance_mouseconfig; + instance_mouseconfig = NULL; +} + + +////////////////////////////////////////////////////////// + +using namespace CONFIG; + + +MouseConfig::MouseConfig() + : MouseKeyConf() +{ + load_conf(); +} + + + +MouseConfig::~MouseConfig() +{ + MouseKeyConf::save_conf( CACHE::path_mouseconf() ); +} + + +// +// 設定ファイル読み込み +// +void MouseConfig::load_conf() +{ + std::string str_motion; + JDLIB::ConfLoader cf( CACHE::path_mouseconf(), std::string() ); + + // 共通 + SETMOTION( "Right", "6" ); + SETMOTION( "Left", "4" ); + SETMOTION( "TabRight", "86" ); + SETMOTION( "TabLeft", "84" ); + SETMOTION( "ToggleArticle", "2" ); + SETMOTION( "Home", "68" ); + SETMOTION( "End", "62" ); + SETMOTION( "Quit", "26" ); + SETMOTION( "Reload", "82" ); + SETMOTION( "Escape", "8" ); + SETMOTION( "NewArticle", "24" ); + SETMOTION( "WriteMessage", "24" ); + + // ARTICLE + SETMOTION( "GotoNew", "626" ); + + // IMAGE + SETMOTION( "CancelMosaic", "28" ); +} + + + +// ひとつの操作をデータベースに登録 +void MouseConfig::set_one_motion( const std::string& name, const std::string& str_motion ) +{ + if( name.empty() || str_motion.empty() ) return; + + int id = CONTROL::get_id( name ); + if( id == CONTROL::None ) return; + + int mode = MouseKeyConf::get_mode( id ); + if( mode == CONTROL::MODE_ERROR ) return; + + bool ctrl = false; + bool shift = false; + bool alt = false; + guint motion = atoi( str_motion.c_str() ); + if( !motion ) return; + + int id_check = MouseKeyConf::check_conflict( mode, motion, ctrl, shift, alt, false ); + if( id_check != CONTROL::None ){ + MISC::ERRMSG( "mouse config : " + str_motion + " is already used." ); + return; + } + + MouseKeyItem* item = new MouseKeyItem( id, mode, name, str_motion, motion, ctrl, shift, alt, false ); + MouseKeyConf::vec_items().push_back( item ); +} + + +// 操作文字列取得 +const std::string MouseConfig::get_str_motion( int id ) +{ + std::string str_motion = MouseKeyConf::get_str_motion( id ); + str_motion = MISC::replace_str( str_motion, "8", "↑" ); + str_motion = MISC::replace_str( str_motion, "6", "→" ); + str_motion = MISC::replace_str( str_motion, "4", "←" ); + str_motion = MISC::replace_str( str_motion, "2", "↓" ); + + return str_motion; +} diff --git a/src/config/mouseconfig.h b/src/config/mouseconfig.h new file mode 100644 index 000000000..37d012217 --- /dev/null +++ b/src/config/mouseconfig.h @@ -0,0 +1,44 @@ +// ライセンス: 最新のGPL +// +// マウス設定 +// + +// マウスジェスチャ設定 +// +// 8 +// ↑ +// 4 ← → 6 +// ↓ +// 2 +// +// ( 例 ) ↑→↓← = 8624 + + +#ifndef _MOUSECONFIG_H +#define _MOUSECONFIG_H + +#include "mousekeyconf.h" + +namespace CONFIG +{ + class MouseConfig : public MouseKeyConf + { + public: + + MouseConfig(); + virtual ~MouseConfig(); + + // 操作文字列取得 + virtual const std::string get_str_motion( int id ); + + private: + + void load_conf(); + virtual void set_one_motion( const std::string& name, const std::string& str_motion ); + }; + + MouseConfig* get_mouseconfig(); + void delete_mouseconfig(); +} + +#endif diff --git a/src/config/mousekeyconf.cpp b/src/config/mousekeyconf.cpp new file mode 100644 index 000000000..f235ca958 --- /dev/null +++ b/src/config/mousekeyconf.cpp @@ -0,0 +1,179 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "mousekeyconf.h" +#include "mousekeyitem.h" + +#include "jdlib/confloader.h" +#include "jdlib/miscutil.h" + +#include "cache.h" +#include "controlutil.h" + + +using namespace CONFIG; + + +MouseKeyConf::MouseKeyConf() +{} + + + +MouseKeyConf::~MouseKeyConf() +{ + std::vector< MouseKeyItem* >::iterator it = m_vec_items.begin(); + for( ; it != m_vec_items.end(); ++it ) delete (*it); +} + + + +// 設定ファイル保存 +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() ); + + int id = CONTROL::COMMONMOTION; + for( ; id < CONTROL::CONTROL_END; ++id ){ + + std::string name; + std::string motion; + std::vector< MouseKeyItem* >::iterator it = m_vec_items.begin(); + for( ; it != m_vec_items.end(); ++it ){ + if( (*it)->get_id() == id ){ + name = (*it)->get_name(); + if( !motion.empty() ) motion += " "; + motion += (*it)->get_str_motion(); + } + } + + if( !name.empty() && !motion.empty() ) cf.update( name, motion ); + } + + 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 ) +{ + int id = CONTROL::None;; + std::vector< MouseKeyItem* >::iterator it = m_vec_items.begin(); + for( ; it != m_vec_items.end(); ++it ){ + + id = (*it)->is_activated( mode, motion, ctrl, shift, alt, dblclick ); + if( id != CONTROL::None ) break; + } + + // 共通モードにして再帰呼び出し + if( id == CONTROL::None && mode != CONTROL::MODE_COMMON ) return get_id( CONTROL::MODE_COMMON, motion, ctrl, shift, alt, dblclick ); + +#ifdef _DEBUG + std::cout << "MouseKeyConf::get_id mode = " << mode << " id = " << id << " motion = " << motion << std::endl; +#endif + + return id; +} + + +// ID から操作を取得 +// (注意) リストの一番上にあるものを出力 +bool MouseKeyConf::get_motion( const int& id, guint& motion, bool& ctrl, bool& shift, bool& alt, bool& dblclick ) +{ + std::vector< MouseKeyItem* >::iterator it = m_vec_items.begin(); + for( ; it != m_vec_items.end(); ++it ){ + + if( (*it)->get_id() == id ){ + motion = (*it)->get_motion(); + ctrl = (*it)->get_ctrl(); + shift = (*it)->get_shift(); + alt = (*it)->get_alt(); + dblclick = (*it)->get_dblclick(); + 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 ) +{ + std::vector< MouseKeyItem* >::iterator it = m_vec_items.begin(); + for( ; it != m_vec_items.end(); ++it ){ + if( (*it)->equal( motion, ctrl, shift, alt, dblclick ) == id ) return true; + } + + return false; +} + + + +// モーションが重複していないかチェック +int MouseKeyConf::check_conflict( const int& mode, + const guint& motion, const bool& ctrl, const bool& shift, const bool& alt, const bool& dblclick ) +{ + int id = CONTROL::None;; + std::vector< MouseKeyItem* >::iterator it = m_vec_items.begin(); + for( ; it != m_vec_items.end(); ++it ){ + + id = (*it)->is_activated( mode, motion, ctrl, shift, alt, dblclick ); + if( id != CONTROL::None ) break; + } + + return id; +} + + + + +// IDからモードを取得 +int MouseKeyConf::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::IMAGEMOTION_END ) return CONTROL::MODE_IMAGE; + if( id < CONTROL::MESSAGEMOTION_END ) return CONTROL::MODE_MESSAGE; + + return CONTROL::MODE_ERROR; +} + + +const std::string MouseKeyConf::get_str_motion( int id ) +{ + std::string str_motion; + + std::vector< MouseKeyItem* >::iterator it = m_vec_items.begin(); + for( ; it != m_vec_items.end(); ++it ){ + + if( (*it)->get_id() == id ){ + if( ! str_motion.empty() ) str_motion += " , "; + str_motion += (*it)->get_str_motion(); + } + } + + return str_motion; +} + + + +// スペースで区切られた複数の操作をデータベースに登録 +void MouseKeyConf::set_motion( const std::string& name, const std::string& str_motion ) +{ + std::list< std::string > list_motion = MISC::StringTokenizer( str_motion, ' ' ); + std::list< std::string >::iterator it = list_motion.begin(); + for( ; it != list_motion.end(); ++it ) set_one_motion( name, (*it) ); +} diff --git a/src/config/mousekeyconf.h b/src/config/mousekeyconf.h new file mode 100644 index 000000000..e38623043 --- /dev/null +++ b/src/config/mousekeyconf.h @@ -0,0 +1,66 @@ +// ライセンス: 最新のGPL +// +// マウス、キーボード設定のベースクラス +// + +#ifndef _MOUSEKEYCONF_H +#define _MOUSEKEYCONF_H + +#include +#include + +#define SETMOTION(name,default_motion) do{\ +str_motion = cf.get_option( name, default_motion ); \ +set_motion( name, str_motion ); \ +}while(0) + +namespace CONFIG +{ + class MouseKeyItem; + + class MouseKeyConf + { + std::vector< MouseKeyItem* > m_vec_items; + + public: + + MouseKeyConf(); + virtual ~MouseKeyConf(); + + // 操作からID取得 + int get_id( const int& mode, + const guint& motion, const bool& ctrl, const bool& shift, const bool& alt, const bool& dblclick ); + + // ID から操作を取得 + // (注意) リストの一番上にあるものを出力 + bool get_motion( const int& id, + guint& motion, bool& ctrl, bool& shift, bool& alt, bool& dblclick ); + + // ID が割り当てられているかチェック + bool alloted( const int& id, + const guint& motion, const bool& ctrl, const bool& shift, const bool& alt, const bool& dblclick ); + + // 操作文字列取得 + virtual const std::string get_str_motion( int id ); + + protected: + + std::vector< MouseKeyItem* >& vec_items(){ return m_vec_items; } + + // 設定ファイル保存 + void save_conf( const std::string& savefile ); + + // モーションが重複していないかチェック + int check_conflict( const int& mode, + const guint& motion, const bool& ctrl, const bool& shift, const bool& alt, const bool& dblclick ); + + // IDからモードを取得 + int get_mode( const int& id ); + + // モーションセット + void set_motion( const std::string& name, const std::string& str_motion ); + virtual void set_one_motion( const std::string& name, const std::string& str_motion ){} + }; +} + +#endif diff --git a/src/config/mousekeyitem.h b/src/config/mousekeyitem.h new file mode 100644 index 000000000..12f5f0027 --- /dev/null +++ b/src/config/mousekeyitem.h @@ -0,0 +1,67 @@ +// ライセンス: 最新のGPL +// +// マウスやキー設定のデータ +// + +#ifndef _MOUSEKEYITEM_H +#define _MOUSEKEYITEM_H + +#include "controlid.h" + +namespace CONFIG +{ + 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; + + public: + + MouseKeyItem( guint id, int mode, const std::string& name, const std::string& str_motion, + guint motion, bool ctrl, bool shift, bool alt, bool dblclick ) + : 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 ) + {} + + const int get_id() const { return m_id; } + const 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; } + const gint get_motion() const{ return m_motion; } + const bool get_ctrl() const { return m_ctrl; } + const bool get_shift() const { return m_shift; } + const bool get_alt() const { return m_alt; } + const bool get_dblclick() const { return m_dblclick; } + + // モード無視 + int equal( const guint& motion, const bool& ctrl, const bool& shift, const bool& alt, const bool& dblclick ) + { + if( motion == m_motion && ctrl == m_ctrl && shift == m_shift && alt == m_alt && dblclick == m_dblclick ) return m_id; + return CONTROL::None; + } + + int is_activated( const int& mode, + const guint& motion, const bool& ctrl, const bool& shift, const bool& alt, const bool& dblclick ) + { + if( mode == m_mode ) return equal( motion, ctrl, shift, alt, dblclick ); + return CONTROL::None; + } + }; +} + +#endif diff --git a/src/control.cpp b/src/control.cpp new file mode 100644 index 000000000..2cf2b376a --- /dev/null +++ b/src/control.cpp @@ -0,0 +1,223 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "control.h" +#include "controlid.h" +#include "controlutil.h" +#include "command.h" +#include "global.h" + +#include "config/globalconf.h" +#include "config/keyconfig.h" +#include "config/mouseconfig.h" +#include "config/buttonconfig.h" + + +using namespace CONTROL; + + +Control::Control() + : m_mode( CONTROL::MODE_COMMON ) +{ + MG_reset(); +} + + + +/////////////////////////////////////// +// +// キー操作 + + +// 戻り値はコントロールID +int Control::key_press( GdkEventKey* event ) +{ + 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; + +#ifdef _DEBUG + std::cout << "Control::key_press key = " << std::hex << key; + if( ctrl ) std::cout << " ctrl"; + if( shift ) std::cout << " shift"; + if( alt ) std::cout << " alt"; + std::cout << "\n"; +#endif + + return CONFIG::get_keyconfig()->get_id( m_mode, key, ctrl, shift, alt, false ); +} + + + +/////////////////////////////////////// +// +// マウスのボタン操作 + + +// eventがidに割り当てられていたらtrue +bool Control::button_alloted( GdkEventButton* event, int id ) +{ + guint button = event->button; + bool ctrl = ( event->state ) & GDK_CONTROL_MASK; + bool shift = ( event->state ) & GDK_SHIFT_MASK; + bool alt = ( event->state ) & GDK_MOD1_MASK; + bool dblclick = ( event->type == GDK_2BUTTON_PRESS ); + + return CONFIG::get_buttonconfig()->alloted( id, button, ctrl, shift, alt, dblclick ); +} + + +// ID からevent取得 +bool Control::get_eventbutton( int id, GdkEventButton& event ) +{ + guint button; + bool ctrl; + bool shift; + bool alt; + bool dblclick; + + if( CONFIG::get_buttonconfig()->get_motion( id, button, ctrl, shift, alt, dblclick ) ){ + if( dblclick ) event.type = GDK_2BUTTON_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( GdkEventButton* event ) +{ + if( ! button_alloted( event, CONTROL::GestureButton ) ) return false; + + MG_reset(); + m_mg = true; + m_mg_x = ( int ) event->x; + m_mg_y = ( int ) event->y; + CORE::core_set_command( "set_mginfo", "", "" ); + +#ifdef _DEBUG + std::cout << "Control::MG_start\n"; +#endif + + return true; +} + + + +bool Control::MG_motion( GdkEventMotion* event ) +{ + if( ! m_mg ) return false; + if( m_mg_lng >= MAX_MG_LNG ) return false; + + if( m_mg_direction.empty() ) m_mg_direction = "■"; + + int x = ( int ) event->x; + int y = ( int ) event->y; + + int dx = x - m_mg_x; + int dy = y - m_mg_y; + + int direction = 0; + std::string str_direction; + 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; + + int control = CONFIG::get_mouseconfig()->get_id( m_mode, m_mg_value, false, false, false, false ); + 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( GdkEventButton* event ) +{ + if( ! m_mg ) return None; + +#ifdef _DEBUG + std::cout << "Control::MG_end val = " << m_mg_value << std::endl; +#endif + + int control = CONFIG::get_mouseconfig()->get_id( m_mode, m_mg_value, false, false, false, false ); + 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; + CORE::core_set_command( "set_mginfo", "", m_mg_direction ); + } + + MG_reset(); + + return control; +} diff --git a/src/control.h b/src/control.h new file mode 100644 index 000000000..f999e20bd --- /dev/null +++ b/src/control.h @@ -0,0 +1,53 @@ +// ライセンス: 最新のGPL + +// +// 入力コントロール +// +// キー入力やマウスジェスチャ管理 +// + +#ifndef _CONTROL_H +#define _CONTROL_H + +#include + +namespace CONTROL +{ + class Control + { + int m_mode; + + public: + + Control(); + + // コントロールモード設定 + void set_mode( int mode ){ m_mode = mode; } + + // キー入力 + // 戻り値はコントロールID + int key_press( GdkEventKey* event ); // 戻り値はコントロールID + + // マウスボタン + bool button_alloted( GdkEventButton* event, int id ); // eventがidに割り当てられていたらtrue + bool get_eventbutton( int id, GdkEventButton& event ); // ID からevent取得 + + // マウスジェスチャ + void MG_reset(); + bool MG_start( GdkEventButton* event ); + bool MG_motion( GdkEventMotion* event ); + int MG_end( GdkEventButton* event ); // 戻り値はコントロールID + + private: + + bool m_mg; + int m_mg_lng; + int m_mg_x; + int m_mg_y; + int m_mg_value; + std::string m_mg_direction; + }; +} + + +#endif diff --git a/src/controlid.h b/src/controlid.h new file mode 100644 index 000000000..a3a9e6662 --- /dev/null +++ b/src/controlid.h @@ -0,0 +1,160 @@ +// ライセンス: 最新のGPL + +// コントロールID + +#ifndef _CONTROLID_H +#define _CONTROLID_H + +namespace CONTROL +{ + // コントロールモード + enum + { + MODE_COMMON, + MODE_BBSLIST, + MODE_BOARD, + MODE_ARTICLE, + MODE_IMAGE, + MODE_MESSAGE, + + MODE_ERROR + }; + + // 動作 + // + // 綱目を増やしたら controllabel.h も修正すること + // + enum + { + // 共通 + COMMONMOTION = 0, + + Up, + UpMid, + UpFast, + + Down, + DownMid, + DownFast, + + Right, + Left, + + TabRight, + TabLeft, + + PreBookMark, + NextBookMark, + + ToggleArticle, + + Home, + End, + + Quit, + Save, + Delete, + Reload, + StopLoading, + Cancel = StopLoading, + Copy, + AppendFavorite, + Property, + + Search, + CloseSearchBar, + HiLightOff, + SearchInvert, + SearchNext, + SearchPrev, + DrawOutAnd, + DrawOutOr, + + ClickButton, // 以下、マウスボタン専用の設定 + DblClickButton, + CloseTabButton, + ReloadTabButton, + AutoScrollButton, + GestureButton, + PopupmenuButton, + + COMMONMOTION_END, + + // BBSLIST系 + BBSLISTMOTION, + + OpenBoard, + OpenBoardTab, + + OpenBoardButton, // 以下、マウスボタン専用の設定 + OpenBoardTabButton, + + BBSLISTMOTION_END, + + // BOARD系 + BOARDMOTION, + + OpenArticle, + OpenArticleTab, + NewArticle, + + OpenArticleButton, // 以下、マウスボタン専用の設定 + OpenArticleTabButton, + + BOARDMOTION_END, + + // ARTICLE系 + ARTICLEMOTION, + + GotoNew, + OpenParentBoard, + WriteMessage, + + ReferResButton, // 以下、マウスボタン専用の設定 + BmResButton, + PopupmenuResButton, + + DrawoutAncButton, + PopupmenuAncButton, + + PopupIDButton, + DrawoutIDButton, + PopupmenuIDButton, + + OpenImageButton, + OpenBackImageButton, + PopupmenuImageButton, + + OpenBeButton, + PopupmenuBeButton, + + ARTICLEMOTION_END, + + // IMAGE 系 + IMAGEMOTION, + + CancelMosaic, + ZoomFitImage, + ZoomInImage, + ZoomOutImage, + OrgSizeImage, + + IMAGEMOTION_END, + + // MESSAGE 系 + MESSAGEMOTION, + + CancelWrite, + ExecWrite, + + MESSAGEMOTION_END, + + // その他 + CancelMG, + None, + + CONTROL_END + }; +} + +#endif diff --git a/src/controllabel.h b/src/controllabel.h new file mode 100644 index 000000000..fb4aaab86 --- /dev/null +++ b/src/controllabel.h @@ -0,0 +1,154 @@ +// ライセンス: 最新のGPL + +// コントロールのラベル + +#ifndef _CONTROLLABEL_H +#define _CONTROLLABEL_H + +#include "controlid.h" + +#define MAX_CONTROL_LABEL 64 + +namespace CONTROL +{ + char control_label[][ 2 ][ MAX_CONTROL_LABEL ]={ + + // 共通 + + { "", "" }, + + { "Up", "上移動" }, + { "UpMid", "中速上移動" }, + { "UpFast", "高速上移動" }, + + { "Down", "下移動" }, + { "DownMid", "中速下移動" }, + { "DownFast", "高速下移動" }, + + { "Right", "右移動" }, + { "Left", "左移動" }, + + { "TabRight", "右のタブに移動" }, + { "TabLeft", "左のタブに移動" }, + + { "PreBookMark", "前のプックマークヘ移動" }, + { "NextBookMark", "次のプックマークヘ移動" }, + + { "ToggleArticle", "板一覧とスレ表示切替" }, + + { "Home", "先頭へ移動" }, + { "End", "最後へ移動" }, + + { "Quit", "閉じる" }, + { "Save", "保存" }, + { "Delete", "削除" }, + { "Reload", "再読み込み" }, + { "StopLoading", "読み込み中止" }, + { "Copy", "コピー" }, + { "AppendFavorite", "お気に入りに追加" }, + { "Property", "プロパティ" }, + + { "Search", "検索" }, + { "CloseSearchBar", "検索バーを閉じる" }, + { "HiLightOff", "ハイライト解除" }, + { "SearchInvert", "前方検索" }, + { "SearchNext", "次検索" }, + { "SearchPrev", "前検索" }, + { "DrawOutAnd", "AND 抽出" }, + { "DrawOutOr", "OR 抽出" }, + + { "ClickButton", "クリック" }, + { "DblClickButton", "ダブルクリック" }, + { "CloseTabButton", "タブを閉じる" }, + { "ReloadTabButton", "タブを再読み込み" }, + { "AutoScrollButton", "オートスクロール" }, + { "GestureButton", "マウスジェスチャ" }, + { "PopupmenuButton", "ポップアップメニュー表示" }, + + { "", "" }, + + // BBSLIST + + { "", "" }, + + { "OpenBoard", "板を開く" }, + { "OpenBoardTab", "タブで板を開く" }, + + { "OpenBoardButton", "板を開く" }, + { "OpenBoardTabButton", "タブで板を開く" }, + + { "", "" }, + + // BOARD + + { "", "" }, + + { "OpenArticle", "スレを開く" }, + { "OpenArticleTab", "タブでスレを開く" }, + { "NewArticle", "新スレ作成" }, + + { "OpenArticleButton", "スレを開く" }, + { "OpenArticleTabButton", "タブでスレを開く" }, + + { "", "" }, + + // ARTICLE + + { "", "" }, + + { "GotoNew", "新着へ移動" }, + { "OpenParentBoard", "板を開く" }, + { "WriteMessage", "書き込み" }, + + { "ReferResButton", "参照レスポップアップ表示" }, + { "BmResButton", "ブックマーク" }, + { "PopupmenuResButton", "レス番号メニュー表示" }, + + { "DrawoutAncButton", "レスの周辺を抽出" }, + { "PopupmenuAncButton", "アンカーメニュー表示" }, + + { "PopupIDButton", "ID抽出ポップアップ表示" }, + { "DrawoutIDButton", "ID抽出" }, + { "PopupmenuIDButton", "IDメニュー表示" }, + + { "OpenImageButton", "画像を開く" }, + { "OpenBackImageButton", "画像をバックで開く" }, + { "PopupmenuImageButton", "画像メニュー表示" }, + + { "OpenBeButton", "ブラウザでBe表示" }, + { "PopupmenuBeButton", "Beメニュー表示" }, + + { "", "" }, + + // IMAGE + + { "", "" }, + + { "CancelMosaic", "モザイク解除" }, + { "ZoomFitImage", "画面に画像サイズを会わせる" }, + { "ZoomInImage", "ズームイン" }, + { "ZoomOutImage", "ズームアウト" }, + { "OrgSizeImage", "元の画像サイズ" }, + + { "", "" }, + + // MESSAGE + + { "", "" }, + + { "CancelWrite", "書き込みキャンセル" }, + { "ExecWrite", "書き込み実行" }, + + { "", "" }, + + // その他 + + { "", "" }, + { "", "" }, + + { "", "" } + }; +} + + +#endif diff --git a/src/controlutil.cpp b/src/controlutil.cpp new file mode 100644 index 000000000..e8b0af1de --- /dev/null +++ b/src/controlutil.cpp @@ -0,0 +1,93 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "controlutil.h" +#include "controlid.h" +#include "controllabel.h" + +#include "config/keyconfig.h" +#include "config/mouseconfig.h" + + +// メニューにショートカットキーやマウスジェスチャを表示 +void CONTROL::set_menu_motion( Gtk::Menu* menu ) +{ + if( !menu ) return; + + Gtk::Menu_Helpers::MenuList& items = menu->items(); + Gtk::Menu_Helpers::MenuList::iterator it_item = items.begin(); + for( ; it_item != items.end(); ++it_item ){ + + // menuitemの中の名前を読み込んで ID を取得し、CONTROL::Noneでなかったら + // ラベルを置き換える + Gtk::Label* label = dynamic_cast< Gtk::Label* >( (*it_item).get_child() ); + if( label ){ +#ifdef _DEBUG + std::cout << label->get_text() << std::endl; +#endif + int id = CONTROL::get_id( label->get_text() ); + if( id != CONTROL::None ){ + + std::string str_label = CONTROL::get_label( id ); + std::string str_motion = CONTROL::get_motion( id ); + + ( *it_item ).remove(); + Gtk::Label *label = Gtk::manage( new Gtk::Label( str_label + ( str_motion.empty() ? "" : " " ) ) ); + Gtk::Label *label_motion = Gtk::manage( new Gtk::Label( str_motion ) ); + Gtk::HBox *box = Gtk::manage( new Gtk::HBox() ); + + box->pack_start( *label, Gtk::PACK_SHRINK ); + box->pack_end( *label_motion, Gtk::PACK_SHRINK ); + (*it_item).add( *box ); + box->show_all(); + } + } + + if( (*it_item).has_submenu() ) CONTROL::set_menu_motion( (*it_item).get_submenu() ); + } +} + + +// ラベルからID取得 +int CONTROL::get_id( const std::string& label ) +{ + for( int id = CONTROL::COMMONMOTION; id < CONTROL::CONTROL_END; ++id ){ + if( label == CONTROL::control_label[ id ][0] ) return id; + } + + return CONTROL::None; +} + + + +// IDからラベル取得 +const std::string CONTROL::get_label( int id ) +{ + return CONTROL::control_label[ id ][ 1 ]; +} + + + +// IDから操作取得 +const std::string CONTROL::get_motion( int id ) +{ + std::string str_motion = CONFIG::get_keyconfig()->get_str_motion( id ); + std::string mouse_motion = CONFIG::get_mouseconfig()->get_str_motion( id ); + if( ! mouse_motion.empty() ){ + if( !str_motion.empty() ) str_motion += " , "; + str_motion += "( " + mouse_motion + " )"; + } + + return str_motion; +} + + + +// IDからラベルと操作の両方を取得 +const std::string CONTROL::get_label_motion( int id ) +{ + std::string motion = CONTROL::get_motion( id ); + return CONTROL::get_label( id ) + ( motion.empty() ? "" : " " ) + motion; +} diff --git a/src/controlutil.h b/src/controlutil.h new file mode 100644 index 000000000..09e3f1899 --- /dev/null +++ b/src/controlutil.h @@ -0,0 +1,29 @@ +// ライセンス: 最新のGPL +// +// コントロール系ユーティリティ関数 +// + +#ifndef _CONTROLUTIL_H +#define _CONTROLUTIL_H + +#include + +namespace CONTROL +{ + // メニューにショートカットキーやマウスジェスチャを表示 + void set_menu_motion( Gtk::Menu* menu ); + + // ラベルからID取得 + int get_id( const std::string& label ); + + // IDからラベル取得 + const std::string get_label( int id ); + + // IDから操作取得 + const std::string get_motion( int id ); + + // IDからラベルと操作の両方を取得 + const std::string get_label_motion( int id ); +} + +#endif diff --git a/src/core.cpp b/src/core.cpp new file mode 100644 index 000000000..964b0893a --- /dev/null +++ b/src/core.cpp @@ -0,0 +1,1547 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "core.h" +#include "command.h" +#include "winmain.h" +#include "session.h" +#include "global.h" +#include "dndmanager.h" +#include "proxypref.h" +#include "passwdpref.h" +#include "browserpref.h" +#include "historymenu.h" +#include "login2ch.h" +#include "jdversion.h" + +#include "config/globalconf.h" +#include "config/keyconfig.h" +#include "config/mouseconfig.h" +#include "config/buttonconfig.h" + +#include "jdlib/miscutil.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 +#include // GTK_CHECK_VERSION + +using namespace CORE; + + +enum +{ + MODE_2PANE = 0, + MODE_3PANE, + MODE_V3PANE +}; + +Core* instance_core; + + +Core* CORE::get_instance() +{ + return instance_core; +} + + +// 全ビューをフォーカスアウト +#define FOCUS_OUT_ALL() do{ \ +ARTICLE::get_admin()->set_command( "focus_out" ); \ +BOARD::get_admin()->set_command( "focus_out" ); \ +BBSLIST::get_admin()->set_command( "focus_out" ); \ +IMAGE::get_admin()->set_command( "focus_out" ); \ +}while(0) + + + +Core::Core( WinMain& win_main ) + : m_win_main( win_main ), + m_imagetab_shown( 0 ), + m_button_go( Gtk::Stock::JUMP_TO, "Go" ), + m_focused_admin( FOCUS_NO ) +{ + instance_core = this; + m_disp.connect( sigc::mem_fun( *this, &Core::exec_command ) ); + + // データベースのルート作成 + DBTREE::create_root(); + DBIMG::create_root(); + + // ログインマネージャ作成 + LOGIN::get_login2ch(); + + // 各管理クラス作成 + BBSLIST::get_admin(); + BOARD::get_admin(); + ARTICLE::get_admin(); + IMAGE::get_admin(); + MESSAGE::get_admin(); + + // D&Dマネージャ作成 + CORE::get_dnd_manager(); +} + + +Core::~Core() +{ +#ifdef _DEBUG + std::cout << "Core::~Core\n"; +#endif + + // 設定保存 + CONFIG::save_conf(); + + // PANEの敷居の位置保存 + SESSION::set_hpane_main_pos( m_hpaned.get_position() ); + SESSION::set_vpane_main_pos( m_vpaned.get_position() ); + SESSION::set_hpane_main_r_pos( m_hpaned_r.get_position() ); + + // メインnotebookのページ番号 + SESSION::set_notebook_main_page( m_notebook.get_current_page() ); + + // D&Dマネージャ削除 + CORE::delete_dnd_manager(); + + // マウス、キーコンフィグ削除 + CONFIG::delete_keyconfig(); + CONFIG::delete_mouseconfig(); + CONFIG::delete_buttonconfig(); + + // ビューを削除する前に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(); + + // ログインマネージャ削除 + LOGIN::delete_login2ch(); + + // データベース削除 + DBTREE::delete_root(); + DBIMG::delete_root(); +} + + + +Gtk::Widget* Core::get_toplevel() +{ + return m_win_main.get_toplevel(); +} + + +// +// 実行 +// +// init = true なら初回起動 +// +void Core::run( bool init ) +{ + // メインメニューの設定 + m_action_group = Gtk::ActionGroup::create(); + + // File + m_action_group->add( Gtk::Action::create( "Menu_File", "_File" ) ); + m_action_group->add( Gtk::ToggleAction::create( "Online", "オンライン", std::string(), SESSION::is_online() ), + sigc::mem_fun( *this, &Core::slot_toggle_online ) ); + m_action_group->add( Gtk::Action::create( "ReloadList", "板リスト再読込"), sigc::mem_fun( *this, &Core::slot_reload_list ) ); + m_action_group->add( Gtk::Action::create( "SaveFavorite", "お気に入り保存"), sigc::mem_fun( *this, &Core::slot_save_favorite ) ); + m_action_group->add( Gtk::Action::create( "Quit", Gtk::Stock::QUIT ), sigc::mem_fun(*this, &Core::slot_quit ) ); + + + // ログイン + m_action_group->add( Gtk::Action::create( "Menu_Login", "_Login" ) ); + m_action_group->add( Gtk::ToggleAction::create( "Login2ch", "2chにログイン", std::string(), false ), + sigc::mem_fun( *this, &Core::slot_toggle_login2ch ) ); + m_action_group->add( Gtk::Action::create( "SetupPasswd", "設定" ), sigc::mem_fun( *this, &Core::slot_setup_passwd ) ); + + + // 表示 + m_action_group->add( Gtk::Action::create( "Menu_View", "_View" ) ); + m_action_group->add( Gtk::ToggleAction::create( "Urlbar", "アドレスバー", std::string(), SESSION::show_urlbar() ), + sigc::mem_fun( *this, &Core::slot_toggle_urlbar ) ); + + // pane 設定 + Gtk::RadioButtonGroup radiogroup; + Glib::RefPtr< Gtk::RadioAction > raction0 = Gtk::RadioAction::create( radiogroup, "2Pane", "2pane" ); + Glib::RefPtr< Gtk::RadioAction > raction1 = Gtk::RadioAction::create( radiogroup, "3Pane", "3pane" ); + Glib::RefPtr< Gtk::RadioAction > raction2 = Gtk::RadioAction::create( radiogroup, "v3Pane", "縦3pane" ); + + switch( SESSION::get_mode_pane() ){ + case MODE_2PANE: raction0->set_active( true ); break; + case MODE_3PANE: raction1->set_active( true ); break; + case 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::Action::create( "Menu_Config", "_Config" ) ); + m_action_group->add( Gtk::ToggleAction::create( "OldArticle", "Boardに過去ログも表示", std::string(), CONFIG::get_show_oldarticle() ), + sigc::mem_fun( *this, &Core::slot_toggle_oldarticle ) ); + + m_action_group->add( Gtk::ToggleAction::create( "RestoreBoard", "起動時にBoardを復元", std::string(), CONFIG::get_restore_board() ), + sigc::mem_fun( *this, &Core::slot_toggle_restore_board ) ); + m_action_group->add( Gtk::ToggleAction::create( "RestoreArticle", "起動時にarticleを復元", std::string(), CONFIG::get_restore_article() ), + sigc::mem_fun( *this, &Core::slot_toggle_restore_article ) ); + m_action_group->add( Gtk::ToggleAction::create( "RestoreImage", "起動時にImageを復元", std::string(), CONFIG::get_restore_image() ), + sigc::mem_fun( *this, &Core::slot_toggle_restore_image ) ); + + m_action_group->add( Gtk::Action::create( "ColorChar", "スレ文字色" ), sigc::mem_fun( *this, &Core::slot_changecolor_char ) ); + m_action_group->add( Gtk::Action::create( "ColorSepa", "新着セパレータ色" ), sigc::mem_fun( *this, &Core::slot_changecolor_separator ) ); + m_action_group->add( Gtk::Action::create( "ColorBack", "スレ背景色" ), sigc::mem_fun( *this, &Core::slot_changecolor_back ) ); + m_action_group->add( Gtk::Action::create( "ColorBackPopup", "ポップアップ背景色" ), sigc::mem_fun( *this, &Core::slot_changecolor_back_popup ) ); + m_action_group->add( Gtk::Action::create( "ColorBackTree", "ツリー背景色" ), sigc::mem_fun( *this, &Core::slot_changecolor_back_tree ) ); + + m_action_group->add( Gtk::Action::create( "FontTree", "ツリーフォント" ), sigc::mem_fun( *this, &Core::slot_changefont_tree ) ); + m_action_group->add( Gtk::Action::create( "FontMenu", "スレフォント" ), sigc::mem_fun( *this, &Core::slot_changefont_main ) ); + m_action_group->add( Gtk::Action::create( "FontPopup", "ポップアップフォント" ), sigc::mem_fun( *this, &Core::slot_changefont_popup ) ); + m_action_group->add( Gtk::Action::create( "SetupProxy", "プロキシ" ), sigc::mem_fun( *this, &Core::slot_setup_proxy ) ); + m_action_group->add( Gtk::Action::create( "SetupBrowser", "ブラウザ" ), sigc::mem_fun( *this, &Core::slot_setup_browser ) ); + + m_action_group->add( Gtk::ToggleAction::create( "UseMosaic", "画像にモザイクをかける", std::string(), CONFIG::get_use_mosaic() ), + sigc::mem_fun( *this, &Core::slot_toggle_use_mosaic ) ); + m_action_group->add( Gtk::Action::create( "DeleteImages", "画像キャッシュクリア" ), sigc::mem_fun( *this, &Core::slot_delete_all_images ) ); + + // help + m_action_group->add( Gtk::Action::create( "Menu_Help", "_Help" ) ); + m_action_group->add( Gtk::Action::create( "Manual", "オンラインマニュアル" ), sigc::mem_fun( *this, &Core::slot_show_manual ) ); + m_action_group->add( Gtk::Action::create( "About", "JDについて" ), sigc::mem_fun( *this, &Core::slot_show_about ) ); + + + 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::MenuBar* menubar = dynamic_cast< Gtk::MenuBar* >( m_ui_manager->get_widget("/menu_bar") ); + assert( menubar ); + + // 履歴メニュー追加 + m_histmenu = Gtk::manage( new HistoryMenu() ); + menubar->items().insert( --(--( menubar->items().end() )), *m_histmenu ); + + + // 初回起動時の設定 + if( init ){ + + Gtk::MessageDialog* mdiag = new Gtk::MessageDialog( "JDセットアップへようこそ\n\nはじめにネットワークの設定をおこなって下さい。" ); + mdiag->run(); + delete mdiag; + + slot_setup_proxy(); + slot_setup_browser(); + + mdiag = new Gtk::MessageDialog( "JDセットアップ\n\nスレ、ポップアップ、ツリービューの順にフォントの設定をおこなって下さい。" ); + mdiag->run(); + delete mdiag; + + slot_changefont_main(); + slot_changefont_popup(); + slot_changefont_tree(); + + mdiag = new Gtk::MessageDialog( "JDセットアップ\n\nその他の設定は起動後に設定メニューからおこなって下さい" ); + mdiag->run(); + delete mdiag; + + mdiag = new Gtk::MessageDialog( "JDセットアップ完了\n\nOKを押すとJDを起動して板のリストをロードします。\nリストが表示されるまでしばらくお待ち下さい。" ); + mdiag->run(); + delete mdiag; + } + + // 各 widget 作成 + + int mode_pane = SESSION::get_mode_pane(); + + // 2pane + if( mode_pane == MODE_2PANE ){ + + m_notebook.append_page( BOARD::get_admin()->get_gtknotebook(), "Board" ); + m_notebook.append_page( ARTICLE::get_admin()->get_gtknotebook(), "Article" ); + m_notebook.append_page( IMAGE::get_admin()->view(), "Image" ); + m_sigc_switch_page = m_notebook.signal_switch_page().connect( sigc::mem_fun( *this, &Core::slot_switch_page ) ); + + m_vbox.pack_start( m_notebook ); + + m_hpaned.add1( BBSLIST::get_admin()->get_gtknotebook() ); + m_hpaned.add2( m_vbox ); + } + + // 3ペーン + else if( mode_pane == MODE_3PANE ){ + + m_notebook.append_page( ARTICLE::get_admin()->get_gtknotebook(), "Article" ); + m_notebook.append_page( IMAGE::get_admin()->view(), "Image" ); + m_sigc_switch_page = m_notebook.signal_switch_page().connect( sigc::mem_fun( *this, &Core::slot_switch_page ) ); + + m_vbox.pack_start( m_notebook ); + + m_vpaned.add1( BOARD::get_admin()->get_gtknotebook() ); + m_vpaned.add2( m_vbox ); + + m_hpaned.add1( BBSLIST::get_admin()->get_gtknotebook() ); + m_hpaned.add2( m_vpaned ); + } + + // 縦3ペーン + else{ + + m_notebook.append_page( ARTICLE::get_admin()->get_gtknotebook(), "Article" ); + m_notebook.append_page( IMAGE::get_admin()->view(), "Image" ); + m_sigc_switch_page = m_notebook.signal_switch_page().connect( sigc::mem_fun( *this, &Core::slot_switch_page ) ); + + m_vbox.pack_start( m_notebook ); + + m_hpaned_r.add1( BOARD::get_admin()->get_gtknotebook() ); + m_hpaned_r.add2( m_vbox ); + + m_hpaned.add1( BBSLIST::get_admin()->get_gtknotebook() ); + m_hpaned.add2( m_hpaned_r ); + } + + m_hpaned.set_position( SESSION::hpane_main_pos() ); + m_vpaned.set_position( SESSION::vpane_main_pos() ); + m_hpaned_r.set_position( SESSION::hpane_main_r_pos() ); + + m_entry_url.signal_activate().connect( sigc::mem_fun( *this, &Core::slot_active_url ) ); + m_button_go.signal_clicked().connect( sigc::mem_fun( *this, &Core::slot_active_url ) ); + + m_urlbar.pack_start( m_entry_url ); + m_urlbar.pack_start( m_button_go, Gtk::PACK_SHRINK ); + + m_tooltip.set_tip( m_button_go, "移動" ); + + std::string str_tmp; +#if GTK_CHECK_VERSION(2,6,0) + m_statbar.pack_start( m_mginfo, Gtk::PACK_SHRINK ); + m_mginfo.set_width_chars( MAX_MG_LNG * 2 + 16 ); + m_mginfo.set_justify( Gtk::JUSTIFY_LEFT ); +#else + m_statbar.pack_start( m_mginfo ); +#endif + m_statbar.show_all_children(); + + // メインwindowのパッキング + m_vbox_main.set_spacing( 4 ); + m_vbox_main.pack_end( m_statbar, Gtk::PACK_SHRINK ); + m_vbox_main.pack_end( m_hpaned ); + if( SESSION::show_urlbar() ) m_vbox_main.pack_end( m_urlbar, Gtk::PACK_SHRINK ); + m_vbox_main.pack_end( *menubar, Gtk::PACK_SHRINK ); + + m_win_main.add( m_vbox_main ); + 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(); + m_statbar.push( "" ); + + // slot 作って Glib::signal_timeout() にコネクト + sigc::slot< bool > slot_timeout = sigc::bind( sigc::mem_fun(*this, &Core::slot_timeout), 0 ); + sigc::connection conn = Glib::signal_timeout().connect( slot_timeout, TIMER_TIMEOUT ); + + // 各管理クラスが開いていたURLを復元 + core_set_command( "restore_views" ); + + // タイトル表示 + SESSION::set_online( !SESSION::is_online() ); // slot_toggle_online()で反転する + slot_toggle_online(); +} + + +// +// SIGHUPを受け取った +// +// 時間のかかる処理は行わないこと +// +void Core::shutdown() +{ + // 設定保存 + CONFIG::save_conf(); + + ARTICLE::get_admin()->shutdown(); + BOARD::get_admin()->shutdown(); + BBSLIST::get_admin()->shutdown(); + IMAGE::get_admin()->shutdown(); +} + + + +// +// メインタイトルセット +// +void Core::set_maintitle() +{ + std::string title = std::string( "JD - " ) + std::string( JDVERSIONSTR ); + if( LOGIN::get_login2ch()->login_now() ) title +=" [ ログイン中 ]"; + if( ! SESSION::is_online() ) title += " [ オフライン ]"; + m_win_main.set_title( title ); +} + + + +// +// 画像モザイクon/off +// +void Core::slot_toggle_use_mosaic() +{ + CONFIG::set_use_mosaic( ! CONFIG::get_use_mosaic() ); + + Gtk::MessageDialog mdiag( "次に開いた画像から有効になります" ); + mdiag.run(); +} + + +// +// 画像キャッシュクリア +// +void Core::slot_delete_all_images() +{ + DBIMG::delete_all_files(); + IMAGE::get_admin()->set_command( "close_uncached_views" ); +} + + +// +// ツリーフォント変更 +// +void Core::slot_changefont_tree() +{ + Gtk::FontSelectionDialog diag; + diag.set_font_name( CONFIG::get_fontname_tree() ); + diag.set_title( "ツリーフォント" ); + if( diag.run() == Gtk::RESPONSE_OK ){ + CONFIG::set_fontname_tree( diag.get_font_name() ); + BBSLIST::get_admin()->set_command( "relayout_all" ); + BOARD::get_admin()->set_command( "relayout_all" ); + } +} + + + +// +// メインフォント変更 +// +void Core::slot_changefont_main() +{ + Gtk::FontSelectionDialog diag; + diag.set_font_name( CONFIG::get_fontname_main() ); + diag.set_title( "スレフォント" ); + if( diag.run() == Gtk::RESPONSE_OK ){ + CONFIG::set_fontname_main( diag.get_font_name() ); + ARTICLE::get_admin()->set_command( "init_font" ); + ARTICLE::get_admin()->set_command( "relayout_all" ); + } +} + + +// +// ポップアップフォント変更 +// +void Core::slot_changefont_popup() +{ + Gtk::FontSelectionDialog diag; + diag.set_font_name( CONFIG::get_fontname_popup() ); + diag.set_title( "ポップアップフォント" ); + if( diag.run() == Gtk::RESPONSE_OK ){ + CONFIG::set_fontname_popup( diag.get_font_name() ); + ARTICLE::get_admin()->set_command( "init_font" ); + } +} + + +// +// ツリー背景色変更 +// +void Core::slot_changecolor_back_tree() +{ + int rgb[ 3 ]; + if( open_color_diag( "ツリー背景", CONFIG::get_color_back_tree(), rgb ) ){ + CONFIG::set_color_back_tree( rgb ); + BBSLIST::get_admin()->set_command( "relayout_all" ); + BOARD::get_admin()->set_command( "relayout_all" ); + } +} + + +// +// 背景色変更 +// +void Core::slot_changecolor_back() +{ + int rgb[ 3 ]; + if( open_color_diag( "スレ背景", CONFIG::get_color_back(), rgb ) ){ + CONFIG::set_color_back( rgb ); + ARTICLE::get_admin()->set_command( "relayout_all" ); + } +} + + +// +// 文字色変更 +// +void Core::slot_changecolor_char() +{ + int rgb[ 3 ]; + if( open_color_diag( "文字色", CONFIG::get_color_char(), rgb ) ){ + CONFIG::set_color_char( rgb ); + ARTICLE::get_admin()->set_command( "relayout_all" ); + } +} + + +// +// 新着セパレータ色変更 +// +void Core::slot_changecolor_separator() +{ + int rgb[ 3 ]; + if( open_color_diag( "新着セパレータ", CONFIG::get_color_separator(), rgb ) ){ + CONFIG::set_color_separator( rgb ); + ARTICLE::get_admin()->set_command( "relayout_all" ); + } +} + + + +// +// ポップアップ背景色変更 +// +void Core::slot_changecolor_back_popup() +{ + int rgb[ 3 ]; + if( open_color_diag( "ポップアップ背景", CONFIG::get_color_back_popup(), rgb ) ){ + CONFIG::set_color_back_popup( rgb ); + ARTICLE::get_admin()->set_command( "relayout_all" ); + } +} + + + +// +// 色選択ダイアログを開く +// +bool Core::open_color_diag( std::string title, const int* rgb, int* rgb_out ) +{ + Gdk::Color color; + color.set_rgb( rgb[ 0 ], rgb[ 1 ], rgb[ 2 ] ); + + Gtk::ColorSelectionDialog diag( title ); + diag.get_colorsel()->set_current_color( color ); + if( diag.run() == Gtk::RESPONSE_OK ){ + + color = diag.get_colorsel()->get_current_color(); + rgb_out[ 0 ] = color.get_red(); + rgb_out[ 1 ] = color.get_green(); + rgb_out[ 2 ] = color.get_blue(); + + return true; + } + + return false; +} + + +// +// プロキシ設定 +// +void Core::slot_setup_proxy() +{ + ProxyPref pref( "" ); + pref.run(); +} + + +// +// パスワード設定 +// +void Core::slot_setup_passwd() +{ + PasswdPref pref( "" ); + pref.run(); +} + + + +// +// ブラウザ設定 +// +void Core::slot_setup_browser() +{ + BrowserPref pref( "" ); + pref.run(); +} + + + +// +// マニュアル +// +void Core::slot_show_manual() +{ + open_by_browser( std::string( JDURL ) + "README.txt" ); +} + + + +// +// about +// +void Core::slot_show_about() +{ + std::stringstream ss; + ss << "バージョン " << JDVERSIONSTR << std::endl << std::endl << JDCOPYRIGHT; + Gtk::MessageDialog mdiag( ss.str() ); + mdiag.run(); +} + + + +// +// 終了 +// +void Core::slot_quit() +{ + m_win_main.hide(); +} + + + +// +// 板リスト再読込 +// +void Core::slot_reload_list() +{ + DBTREE::download_bbsmenu(); + CORE::core_set_command( "set_status","", "loading...." ); +} + + + +// +// お気に入り保存 +// +void Core::slot_save_favorite() +{ + CORE::core_set_command( "save_favorite","" ); +} + + + +// +// オンライン、オフライン切替え +// +void Core::slot_toggle_online() +{ + SESSION::set_online( !SESSION::is_online() ); + set_maintitle(); +} + + +// +// 2chにログイン +// +void Core::slot_toggle_login2ch() +{ +#ifdef _DEBUG + std::cout << "Core::slot_toggle_login2ch\n"; +#endif + + if( LOGIN::get_login2ch()->login_now() ){ + LOGIN::get_login2ch()->logout(); + set_maintitle(); + } + else LOGIN::get_login2ch()->start_login(); +} + + + +// +// URLバー表示切替え +// +void Core::slot_toggle_urlbar() +{ + SESSION::set_show_urlbar( !SESSION::show_urlbar() ); + + if( SESSION::show_urlbar() ){ + + Gtk::Widget* menubar = m_ui_manager->get_widget("/menu_bar"); + assert( menubar ); + + m_vbox_main.remove( *menubar ); + m_vbox_main.pack_end( m_urlbar, Gtk::PACK_SHRINK ); + m_vbox_main.pack_end( *menubar, Gtk::PACK_SHRINK ); + } + else m_vbox_main.remove( m_urlbar ); + + m_win_main.show_all_children(); +} + + +// +// 2paneモード +// +void Core::slot_toggle_2pane() +{ + if( SESSION::get_mode_pane() == MODE_2PANE ) return; + SESSION::set_mode_pane( 0 ); + + Gtk::MessageDialog mdiag( "次回起動時から2paneになります" ); + mdiag.run(); +} + + + +// +// 3paneモード +// +void Core::slot_toggle_3pane() +{ + if( SESSION::get_mode_pane() == MODE_3PANE ) return; + SESSION::set_mode_pane( MODE_3PANE ); + + Gtk::MessageDialog mdiag( "次回起動時から3paneにになります" ); + mdiag.run(); +} + + +// +// 縦3paneモード +// +void Core::slot_toggle_v3pane() +{ + if( SESSION::get_mode_pane() == MODE_V3PANE ) return; + SESSION::set_mode_pane( MODE_V3PANE ); + + Gtk::MessageDialog mdiag( "次回起動時から縦3paneになります" ); + mdiag.run(); +} + + + + +// +// 過去ログ表示切替え +// +void Core::slot_toggle_oldarticle() +{ + CONFIG::set_show_oldarticle( ! CONFIG::get_show_oldarticle() ); + + Gtk::MessageDialog mdiag( "次に開いたBoardから有効になります" ); + mdiag.run(); +} + + +// +// 起動時にviewを復元 +// +void Core::slot_toggle_restore_board() +{ + CONFIG::set_restore_board( ! CONFIG::get_restore_board() ); +} + +void Core::slot_toggle_restore_article() +{ + CONFIG::set_restore_article( ! CONFIG::get_restore_article() ); +} + +void Core::slot_toggle_restore_image() +{ + CONFIG::set_restore_image( ! CONFIG::get_restore_image() ); +} + + +// +// コマンドセット +// +// 他のadminクラスに委譲する場合はこの関数で、coreが実行するコマンドはexec_command()で処理 +// +void Core::set_command( const COMMAND_ARGS& command ) +{ +#ifdef _DEBUG + std::cout << "Core::set_command : " << command.command << " " << command.url + << " " << command.arg1 << " " << command.arg2 << " " << command.arg3 << " " << command.arg4 << std::endl; +#endif + + //////////////////////////// + // article系のコマンド + + // メインビュー + if( command.command == "open_article" ) { + + ARTICLE::get_admin()->set_command( "open_view", + command.url, + + // 以下 COMMAND_ARGS::arg1, arg2,.... + command.arg1, // "true"ならtabで開く + "false", // url 開いてるかチェック + + "MAIN", // メインモード + + command.arg2 // ジャンプ番号( empty ならジャンプしない ) + ); + + set_history_article( command.url ); + return; + } + + + // キーワードで抽出( AND/OR ) + else if( command.command == "open_article_keyword" ) { + + std::string mode_str = "KEYWORD"; + if( command.arg2 == "true" ) mode_str = "KEYWORD_OR"; // OR 抽出 + + ARTICLE::get_admin()->set_command( "open_view", + command.url, + + // 以下 COMMAND_ARGS::arg1, arg2,.... + "left", // タブで開く + "true", // url 開いてるかチェックしない + + mode_str, // キーワード抽出モード + + command.arg1 // query + ); + return; + } + + // レス抽出 + else if( command.command == "open_article_res" ) { + + ARTICLE::get_admin()->set_command( "open_view", + command.url, + + // 以下 COMMAND_ARGS::arg1, arg2,.... + "left", // タブで開く + "true", // url 開いてるかチェックしない + + "RES", // レス抽出モード + + command.arg1, // レス番号 ( from-to ) + command.arg2 // ジャンプ番号( empty ならジャンプしない ) + ); + + return; + } + + + // ID で抽出 + else if( command.command == "open_article_id" ) { + + ARTICLE::get_admin()->set_command( "open_view", + command.url, + + // 以下 COMMAND_ARGS::arg1, arg2,.... + "left", // タブで開く + "true", // url 開いてるかチェックしない + + "ID", // ID 抽出モード + + command.arg1 // ユーザID + ); + return; + } + + // ブックマークで抽出 + else if( command.command == "open_article_bm" ) { + + ARTICLE::get_admin()->set_command( "open_view", + command.url, + + // 以下 COMMAND_ARGS::arg1, arg2,.... + "left", // タブで開く + "true", // url 開いてるかチェックしない + + "BM" // ブックマーク抽出モード + ); + return; + } + + // URL抽出 + else if( command.command == "open_article_url" ) { + + ARTICLE::get_admin()->set_command( "open_view", + command.url, + + // 以下 COMMAND_ARGS::arg1, arg2,.... + "left", // タブで開く + "true", // url 開いてるかチェックしない + + "URL" // URL抽出モード + ); + return; + } + + // 参照抽出 + else if( command.command == "open_article_refer" ) { + + ARTICLE::get_admin()->set_command( "open_view", + command.url, + + // 以下 COMMAND_ARGS::arg1, arg2,.... + "left", // タブで開く + "true", // url 開いてるかチェックしない + + "REF", // 参照抽出モード + + command.arg1 // 対象レス番号 + ); + return; + } + + // datが更新されたときにローダから呼ばれる + else if( command.command == "update_article" ){ + + ARTICLE::get_admin()->set_command( "update_view", command.url ); + return; + } + + // datが更新が終わったときにローダから呼ばれる + else if( command.command == "update_article_finish" ){ + + ARTICLE::get_admin()->set_command( "update_finish", command.url ); + return; + } + + else if( command.command == "delete_article" ){ + + ARTICLE::get_admin()->set_command( "close_view", command.url, + "true" // command.url を含む全てのビューを閉じる + ); + + DBTREE::delete_article( command.url ); + return; + } + + + //////////////////////////// + // board系のコマンド + + else if( command.command == "open_board" ){ + + BOARD::get_admin()->set_command( "open_view", + command.url, + + command.arg1 // "true" ならtabで開く + ); + + set_history_board( command.url ); + return; + } + + 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; + } + + + //////////////////////////// + // bbslist系のコマンド + + else if( command.command == "update_bbslist" ){ + + BBSLIST::get_admin()->set_command( "update_view", URL_BBSLISTVIEW ); + return; + } + else if( command.command == "append_favorite" ){ + + BBSLIST::get_admin()->set_command( "append_favorite", command.url, command.arg1, command.arg2 ); + return; + } + + else if( command.command == "save_favorite" ){ + + BBSLIST::get_admin()->set_command( "save_favorite" ); + return; + } + + + //////////////////////////// + // image系のコマンド + + else if( command.command == "open_image" ){ + + // 画像インジケータ表示 + if( !m_imagetab_shown ){ + m_vbox.pack_start( IMAGE::get_admin()->tab(), Gtk::PACK_SHRINK ); + m_vbox.reorder_child( IMAGE::get_admin()->tab(), 0 ); + m_win_main.show_all_children(); + m_imagetab_shown = true; + } + + // キャッシュに無かったらロード + if( ! DBIMG::is_cached( command.url ) ) DBIMG::download_img( command.url ); + + IMAGE::get_admin()->set_command( "open_view", command.url ); + return; + } + else if( command.command == "delete_image" ){ + + IMAGE::get_admin()->set_command( "close_view", command.url ); + DBIMG::delete_cache( command.url ); + return; + } + + + //////////////////////////// + // message系 + + else if( command.command == "open_message" ){ + + if( ! SESSION::is_online() ){ + Gtk::MessageDialog mdiag( "オフラインです" ); + mdiag.run(); + } + else MESSAGE::get_admin()->set_command( "open_view", command.url, command.arg1 ); + } + + else if( command.command == "create_new_thread" ){ + + if( ! SESSION::is_online() ){ + Gtk::MessageDialog mdiag( "オフラインです" ); + mdiag.run(); + } + else if( DBTREE::url_bbscgi_new( command.url ).empty() ){ + Gtk::MessageDialog mdiag( "この板では新スレを立てることは出来ません" ); + mdiag.run(); + } + else MESSAGE::get_admin()->set_command( "create_new_thread", command.url, command.arg1 ); + } + + //////////////////////////// + // 再描画系 + + // 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 ); + 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; + } + + // 表示中の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" ); + + return; + } + + //////////////////////////// + // Coreが自前で処理するコマンド( Core::exec_command() で処理 ) + + m_list_command.push_back( command ); + m_disp.emit(); +} + + +// coreが自前でする処理 +void Core::exec_command() +{ + 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 + + bool is2pane = ( SESSION::get_mode_pane() == MODE_2PANE ); + + // 各管理クラスが開いていたURLを復元 + if( command.command == "restore_views" ){ + + // bbslist は無条件でリストア + // 板一覧がロードされてない時はここでロードされる + BBSLIST::get_admin()->set_command( "restore" ); + + // その他は設定されていたらリストア + if( CONFIG::get_restore_board() ) BOARD::get_admin()->set_command( "restore" ); + if( CONFIG::get_restore_article() ) ARTICLE::get_admin()->set_command( "restore" ); + if( CONFIG::get_restore_image() && SESSION::image_URLs().size() ){ + + // 画像インジケータ表示 + m_vbox.pack_start( IMAGE::get_admin()->tab(), Gtk::PACK_SHRINK ); + m_vbox.reorder_child( IMAGE::get_admin()->tab(), 0 ); + m_win_main.show_all_children(); + m_imagetab_shown = true; + + IMAGE::get_admin()->set_command( "restore" ); + } + + int notebook_page = SESSION::notebook_main_page(); + if( is2pane ){ + if( notebook_page == 0 ) core_set_command( "switch_board" ); + else if( notebook_page == 2 ) core_set_command( "switch_image" ); + } + else if( notebook_page == 1 ) core_set_command( "switch_image" ); + } + + // 各ビューのタブ幅調整 + else if( command.command == "adjust_tabwidth" ){ + + BOARD::get_admin()->set_command( "adjust_tabwidth" ); + ARTICLE::get_admin()->set_command( "adjust_tabwidth" ); + } + + // history 登録 + else if( command.command == "set_history_article" ) set_history_article( command.url ); + + else if( command.command == "set_history_board" ) set_history_board( command.url ); + + else if( command.command == "switch_article" ) switch_article(); + + else if( command.command == "switch_board" ) switch_board(); + + else if( command.command == "switch_bbslist" ) switch_bbslist(); + + else if( command.command == "switch_image" ) switch_image(); + + else if( command.command == "toggle_article" ) toggle_article(); + + // 2chへのログイン処理が完了した + else if( command.command == "login2ch_finished" ){ + + // ログインに失敗したらメニューのチェックを外しておく + if( ! LOGIN::get_login2ch()->login_now() ){ + + Glib::RefPtr< Gtk::ToggleAction > tact + = Glib::RefPtr< Gtk::ToggleAction >::cast_dynamic( m_action_group->get_action( "Login2ch" ) ); + if( tact && tact->get_active() ){ + + LOGIN::get_login2ch()->set_login_now( true ); + tact->set_active( false ); + } + } + + set_maintitle(); + } + + // あるnotebookが空になった + else if( command.command == "empty_page" ) { + + FOCUS_OUT_ALL(); + + // 画像ビューの場合 + if( command.url == URL_IMAGEADMIN ){ + + // 画像インジケータを隠す + if( m_imagetab_shown ){ + m_vbox.remove( IMAGE::get_admin()->tab() ); + m_win_main.show_all_children(); + m_imagetab_shown = false; + } + + if( is2pane ) m_notebook.set_current_page( 1 ); + else m_notebook.set_current_page( 0 ); + ARTICLE::get_admin()->set_command( "focus_current_view" ); + } + + // articleビューの場合 + else if( command.url == URL_ARTICLEADMIN ){ + if( is2pane ) m_notebook.set_current_page( 0 ); + BOARD::get_admin()->set_command( "focus_current_view" ); + } + + // boardビューの場合 + else if( command.url == URL_BOARDADMIN ) BBSLIST::get_admin()->set_command( "focus_current_view" ); + } + + // URL、ステータスなどの表示 + else if( command.command == "set_url" ){ + m_entry_url.set_text( command.url ); + } + + else if( command.command == "set_status" ){ + m_statbar.push( command.arg1 ); + } + + // マウスジェスチャ + else if( command.command == "set_mginfo" ){ + m_mginfo.set_text( command.arg1 ); + } + + // bbsmenu再読み込み + else if( command.command == "reload_bbsmenu" ){ + slot_reload_list(); + } + + // URL のオープン関係 + + // 常に外部ブラウザで開く場合 + else if( command.command == "open_url_browser" ) open_by_browser( command.url ); + + // google 検索 + else if( command.command == "search_google" ){ + + std::string query = "http://www.google.co.jp/search?hl=ja&q="; + query += MISC::url_encode( command.arg1.c_str(), strlen( command.arg1.c_str() ) ); + query += "&btnG=Google+%E6%A4%9C%E7%B4%A2&lr="; + +#ifdef _DEBUG + std::cout << "exec : search_google = " << query << std::endl; +#endif + + open_by_browser( query ); + } + + // タイプによって判定する場合 + else if( command.command == "open_url" ){ + + command.url = MISC::remove_space( command.url ); + if( command.url.find( "http://" ) != 0 ) command.url = "http://" + command.url; + + int num_from, num_to; + std::string url_dat = DBTREE::url_dat( command.url, num_from, num_to ); + std::string url_subject = DBTREE::url_subject( 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, "true", MISC::itostr( num_from ) ); + else CORE::core_set_command( "open_article" , url_dat, "true" ); + } + + // 掲示板のベースURLの場合 + else if( ! url_subject.empty() ){ + +#ifdef _DEBUG + std::cout << "exec : open_board url = " << url_subject << std::endl; +#endif + + CORE::core_set_command( "open_board" , url_subject, "true" ); + } + + // 画像の場合 + else if( DBIMG::is_loadable( command.url ) ){ + + if( ! SESSION::is_online() ){ + Gtk::MessageDialog mdiag( "オフラインです" ); + mdiag.run(); + } + else{ + // キャッシュに無かったらロード + if( ! DBIMG::is_cached( command.url ) ) DBIMG::download_img( command.url ); + + CORE::core_set_command( "open_image", command.url ); + CORE::core_set_command( "switch_image" ); + } + } + + // その他 + else open_by_browser( command.url ); + } +} + + + +// +// メインタイマー +// +// 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(); + + return true; +} + + +// +// タブの切替え +// +void Core::slot_switch_page( GtkNotebookPage*, guint page ) +{ +#ifdef _DEBUG + std::cout << "Core::slot_switch_page " << page << std::endl; +#endif + + FOCUS_OUT_ALL(); + + if( SESSION::get_mode_pane() == MODE_2PANE ){ + if( page == 0 ) BOARD::get_admin()->set_command( "focus_current_view" ); + else if( page == 1 ) ARTICLE::get_admin()->set_command( "focus_current_view" ); + else if( page == 2 ) IMAGE::get_admin()->set_command( "focus_current_view" ); + } + else{ + if( page == 0 ) ARTICLE::get_admin()->set_command( "focus_current_view" ); + else if( page == 1 ) IMAGE::get_admin()->set_command( "focus_current_view" ); + } +} + + +// +// フォーカスアウトイベント +// + +bool Core::slot_focus_out_event( GdkEventFocus* ) +{ + // 現在フォーカスされているビュー番号を保存 + if( BBSLIST::get_admin()->has_focus() ) m_focused_admin = FOCUS_BBSLIST; + else if( BOARD::get_admin()->has_focus() ) m_focused_admin = FOCUS_BOARD; + else if( ARTICLE::get_admin()->has_focus() ) m_focused_admin = FOCUS_ARTICLE; + else if( IMAGE::get_admin()->has_focus() ) m_focused_admin = FOCUS_IMAGE; + else m_focused_admin = FOCUS_NO; + + FOCUS_OUT_ALL(); + +#ifdef _DEBUG + std::cout << "Core::slot_focus_out_event admin = " << m_focused_admin << std::endl; +#endif + + return true; +} + + +// +// フォーカスインイベント +// +bool Core::slot_focus_in_event( GdkEventFocus* ) +{ +#ifdef _DEBUG + std::cout << "Core::slot_focus_in_event admin = " << m_focused_admin << std::endl; +#endif + + // フォーカス状態回復 + switch( m_focused_admin ) + { + case FOCUS_BBSLIST: BBSLIST::get_admin()->set_command( "restore_focus" ); break; + case FOCUS_BOARD: BOARD::get_admin()->set_command( "restore_focus" ); break; + case FOCUS_ARTICLE: ARTICLE::get_admin()->set_command( "restore_focus" ); break; + case FOCUS_IMAGE: IMAGE::get_admin()->set_command( "restore_focus" ); break; + } + m_focused_admin = FOCUS_NO; + + return true; +} + + + +// +// URL entryでenterを押した +// +void Core::slot_active_url() +{ + std::string url = m_entry_url.get_text(); + if( !url.empty() ) CORE::core_set_command( "open_url", url ); +} + + +// +// 各viewにスイッチ +// +void Core::switch_article() +{ + if( ARTICLE::get_admin()->empty() ) return; + + FOCUS_OUT_ALL(); + ARTICLE::get_admin()->set_command( "delete_popup" ); + + m_focused_admin = FOCUS_ARTICLE; + if( SESSION::get_mode_pane() == MODE_2PANE ) m_notebook.set_current_page( 1 ); + else m_notebook.set_current_page( 0 ); + ARTICLE::get_admin()->set_command( "focus_current_view" ); +} + + +void Core::switch_board() +{ + if( BOARD::get_admin()->empty() ) return; + + FOCUS_OUT_ALL(); + ARTICLE::get_admin()->set_command( "delete_popup" ); + + m_focused_admin = FOCUS_BOARD; + if( SESSION::get_mode_pane() == MODE_2PANE ) m_notebook.set_current_page( 0 ); + BOARD::get_admin()->set_command( "focus_current_view" ); +} + + +void Core::switch_bbslist() +{ + if( BBSLIST::get_admin()->empty() ) return; + + FOCUS_OUT_ALL(); + ARTICLE::get_admin()->set_command( "delete_popup" ); + + m_focused_admin = FOCUS_BBSLIST; + BBSLIST::get_admin()->set_command( "focus_current_view" ); +} + + +void Core::switch_image() +{ + if( IMAGE::get_admin()->empty() ) return; + + FOCUS_OUT_ALL(); + ARTICLE::get_admin()->set_command( "delete_popup" ); + + m_focused_admin = FOCUS_IMAGE; + if( SESSION::get_mode_pane() == MODE_2PANE ) m_notebook.set_current_page( 2 ); + else m_notebook.set_current_page( 1 ); + IMAGE::get_admin()->set_command( "focus_current_view" ); +} + + +// 2paneの時にboard <-> article 切替え +void Core::toggle_article() +{ + if( SESSION::get_mode_pane() == MODE_2PANE ){ + + if( m_notebook.get_current_page() == 1 ) switch_board(); + else switch_article(); + } +} + + +// ブラウザで開く +void Core::open_by_browser( const std::string& url ) +{ + std::string command_openurl = CONFIG::get_command_openurl(); + if( !command_openurl.empty() ){ + command_openurl = MISC::replace_str( command_openurl, "%s", url ); +#ifdef _DEBUG + std::cout << "spawn command = " << command_openurl << std::endl; +#endif + Glib::spawn_command_line_async( command_openurl ); + } +} + + + +// history セット +void Core::set_history_article( const std::string& url ) +{ + m_histmenu->append( url, DBTREE::article_subject( url ), TYPE_THREAD ); +} + + +void Core::set_history_board( const std::string& url ) +{ + m_histmenu->append( url, DBTREE::board_name( url ), TYPE_BOARD ); +} diff --git a/src/core.h b/src/core.h new file mode 100644 index 000000000..5ef52059d --- /dev/null +++ b/src/core.h @@ -0,0 +1,177 @@ +// ライセンス: 最新のGPL + +// +// コアクラス +// + +#ifndef _CORE_H +#define _CORE_H + +#include +#include +#include + +#include "skeleton/imgbutton.h" + +struct COMMAND_ARGS; + +class WinMain; + + +namespace BOARD +{ + class BoardAdmin; +} + + +namespace BBSLIST +{ + class BBSListAdmin; +} + + +namespace ARTICLE +{ + class ArticleAdmin; +} + +namespace IMAGE +{ + class ImageAdmin; +} + + +namespace MESSAGE +{ + class MessageAdmin; +} + + +// m_focused_admin の値。どこににフォーカスしているか +// Core::slot_focus_in_event, Core::slot_focus_out_event 参照 +enum +{ + FOCUS_BBSLIST, + FOCUS_BOARD, + FOCUS_ARTICLE, + FOCUS_IMAGE, + FOCUS_NO +}; + + + +namespace CORE +{ + class DND_Manager; + class HistoryMenu; + + class Core + { + Glib::Dispatcher m_disp; + std::list< COMMAND_ARGS > m_list_command; + sigc::connection m_sigc_switch_page; + + WinMain& m_win_main; + + Gtk::VBox m_vbox_main; + Gtk::HPaned m_hpaned; + Gtk::VPaned m_vpaned; // 3ペーンモード時の右側ペーン + Gtk::HPaned m_hpaned_r; // 縦3ペーンモードの時の右側ペーン + Gtk::VBox m_vbox; + Gtk::Notebook m_notebook; + bool m_imagetab_shown; + + Gtk::Tooltips m_tooltip; + Gtk::HBox m_urlbar; + Gtk::Entry m_entry_url; + SKELETON::ImgButton m_button_go; + + // ステータスバー + Gtk::Statusbar m_statbar; + Gtk::Label m_mginfo; + + Glib::RefPtr< Gtk::ActionGroup > m_action_group; + Glib::RefPtr< Gtk::UIManager > m_ui_manager; + HistoryMenu* m_histmenu; + + // フォーカスイン、アウトイベントの時に使う変数 + /// Core::slot_focus_in_event, Core::slot_focus_out_event 参照 + int m_focused_admin; + + public: + + Core( WinMain& win_main ); + virtual ~Core(); + + Gtk::Widget* get_toplevel(); + + // init = true なら初回起動 + void run( bool init ); + + void set_command( const COMMAND_ARGS& command ); + + // SIGHUPを受け取った時の処理 + void shutdown(); + + private: + + void set_maintitle(); + + void slot_toggle_use_mosaic(); + void slot_delete_all_images(); + + void slot_changefont_tree(); + void slot_changefont_main(); + void slot_changefont_popup(); + + void slot_changecolor_char(); + void slot_changecolor_separator(); + void slot_changecolor_back(); + void slot_changecolor_back_popup(); + void slot_changecolor_back_tree(); + + bool open_color_diag( std::string title, const int* rgb, int* rgb_out ); + + void slot_setup_proxy(); + void slot_setup_passwd(); + void slot_setup_browser(); + + void slot_show_manual(); + void slot_show_about(); + void slot_quit(); + void slot_reload_list(); + void slot_save_favorite(); + void slot_toggle_online(); + void slot_toggle_login2ch(); + void slot_toggle_urlbar(); + void slot_toggle_2pane(); + void slot_toggle_3pane(); + void slot_toggle_v3pane(); + void slot_toggle_oldarticle(); + void slot_toggle_restore_board(); + void slot_toggle_restore_article(); + void slot_toggle_restore_image(); + + void exec_command(); + bool slot_timeout( int timer_number ); + void slot_switch_page( GtkNotebookPage*, guint page ); + bool slot_focus_out_event( GdkEventFocus* ev ); + bool slot_focus_in_event( GdkEventFocus* ev ); + void slot_active_url(); + + void switch_article(); + void switch_board(); + void switch_bbslist(); + void switch_image(); + void toggle_article(); + void open_by_browser( const std::string& url ); + + void set_history_article( const std::string& url ); + void set_history_board( const std::string& url ); + }; + + Core* get_instance(); +} + + +#endif diff --git a/src/dbimg/Makefile.am b/src/dbimg/Makefile.am new file mode 100644 index 000000000..679416a65 --- /dev/null +++ b/src/dbimg/Makefile.am @@ -0,0 +1,14 @@ +noinst_LIBRARIES = libdbimg.a + +libdbimg_a_SOURCES = \ + imgroot.cpp \ + img.cpp \ + imginterface.cpp + +noinst_HEADERS = \ + imgroot.h \ + img.h \ + imginterface.h + +AM_CXXFLAGS = @GTKMM_CFLAGS@ +INCLUDES = -I$(top_srcdir)/src diff --git a/src/dbimg/img.cpp b/src/dbimg/img.cpp new file mode 100644 index 000000000..1c1db31a9 --- /dev/null +++ b/src/dbimg/img.cpp @@ -0,0 +1,309 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "img.h" + +#include "jdlib/miscutil.h" +#include "jdlib/miscmsg.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 +#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 DBIMG; + + +Img::Img( const std::string& url ) + : SKELETON::Loadable() + ,m_url( url ) + ,m_fout( 0 ) +{ +#ifdef _DEBUG + std::cout << "Img::Img url = " << m_url << std::endl; +#endif + clear(); + read_info(); +} + + + + +Img::~Img() +{ +#ifdef _DEBUG + std::cout << "Img::~Img url = " << m_url << std::endl; +#endif + + if( m_fout ) fclose( m_fout ); + m_fout = NULL; +} + + +void Img::clear() +{ + m_mosaic = CONFIG::get_use_mosaic(); + m_zoom_to_fit = true; + m_size = 100; + m_protect = false; + memset( m_sign, 0, sizeof( m_sign ) ); + m_type = T_UNKNOWN; +} + + + +const bool Img::is_cached() +{ + if( is_loading() ) return false; + + return ( get_code() == HTTP_OK ); +} + + + +// +// ロード開始 +// +// receive_data() と receive_finish() がコールバックされる +// +void Img::download_img() +{ +#ifdef _DEBUG + std::cout << "Img::download_img url = " << m_url << std::endl; +#endif + + if( is_loading() ) return; + if( is_cached() ) return; + if( ! CACHE::mkdir_imgroot() ) return; + + // ダウンロード開始 + std::string path = CACHE::path_img( m_url ); + m_fout = fopen( path.c_str(), "wb" ); + if( m_fout == NULL ){ + MISC::ERRMSG( "fopen failed : " + path ); + return; + } + + clear(); + + JDLIB::LOADERDATA data; + data.url = m_url; + data.agent = CONFIG::get_agent_for_data(); + if( CONFIG::get_use_proxy_for_data() ) data.host_proxy = CONFIG::get_proxy_for_data(); + else data.host_proxy = std::string(); + data.port_proxy = CONFIG::get_proxy_port_for_data(); + data.size_buf = CONFIG::get_loader_bufsize(); + data.timeout = CONFIG::get_loader_timeout_img(); + + if( !start_load( data ) ) receive_finish(); + else CORE::core_set_command( "redraw", m_url ); +} + + + + + +// +// キャッシュ保存 +// +// path_to はデフォルトのファイル名 +// +bool Img::save( const std::string& path_to ) +{ + 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::open_save_diag( CACHE::path_img( m_url ), dir + name ); + + if( ! save_to.empty() ){ + SESSION::set_dir_img_save( MISC::get_dir( save_to ) ); + return true; + } + + return false; +} + + + +// +// モザイクon/off +// +void Img::set_mosaic( bool mosaic ) +{ + m_mosaic = mosaic; + save_info(); +} + + + +// +// 保護モード +// +void Img::set_protect( bool protect ) +{ + m_protect = protect; + save_info(); +} + + + +// +// データ受信 +// +void Img::receive_data( const char* data, size_t size ) +{ + if( ! size ) return; + + if( m_fout ) fwrite( data, 1, size, m_fout ); + if( m_sign[ 0 ] == 0 ) memcpy( m_sign, data, MIN( sizeof( m_sign ) -1, size ) ); + +#ifdef _DEBUG + std::cout << "Img::receive_data code = " << code() << " " + << current_length() << " / " << total_length() << std::endl; +#endif +} + + + +// +// ロード終了 +// +void Img::receive_finish() +{ + if( m_fout ) fclose( m_fout ); + m_fout = NULL; + + m_type = T_NOIMG; + + // シグネチャを見て画像かどうかをチェック + if( get_code() == HTTP_OK ){ + + // jpeg は FF D8 + if( m_sign[ 0 ] == 0xFF + && m_sign[ 1 ] == 0xD8 ) m_type = T_JPG; + + // png は 0x89 0x50 0x4e 0x47 0xd 0xa 0x1a 0xa + else if( m_sign[ 0 ] == 0x89 + && m_sign[ 1 ] == 0x50 + && m_sign[ 2 ] == 0x4e + && m_sign[ 3 ] == 0x47 + && m_sign[ 4 ] == 0x0d + && m_sign[ 5 ] == 0x0a + && m_sign[ 6 ] == 0x1a + && m_sign[ 7 ] == 0x0a ) m_type = T_PNG; + + // gif + else if( m_sign[ 0 ] == 'G' + && m_sign[ 1 ] == 'I' + && m_sign[ 2 ] == 'F' ) m_type = T_GIF; + + if( m_type == T_NOIMG ){ + set_code( HTTP_CANCEL ); + set_str_code( "画像ファイルではありません" ); + } + } + + if( m_type == T_NOIMG ){ + + // キャッシュを消しておく + std::string path = CACHE::path_img( m_url ); + if( CACHE::is_file_exists( path ) == CACHE::EXIST_FILE ) unlink( path.c_str() ); + } + + save_info(); + + CORE::core_set_command( "redraw", m_url ); + CORE::core_set_command( "redraw_article" ); + +#ifdef _DEBUG + std::cout << "Img::receive_finish code = " << code() << std::endl + << "total byte = " << total_length() << std::endl + << "type = " << m_type << std::endl; +#endif +} + + + + +// +// キャッシュ情報読み込み +// +void Img::read_info() +{ +#ifdef _DEBUG + std::cout << "Img::read_info\n"; +#endif + + JDLIB::ConfLoader cf( CACHE::path_img_info( m_url ), std::string() ); + + m_refurl = cf.get_option( "refurl", "" ); + set_code( cf.get_option( "code", HTTP_ERR ) ); + 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_protect = cf.get_option( "protect", 0 ); + m_type = cf.get_option( "type", T_UNKNOWN ); + + if( ! total_length() ) set_total_length( CACHE::get_filesize( CACHE::path_img( m_url ) ) ); + set_current_length( total_length() ); + +#ifdef _DEBUG + std::cout << "refurl = " << m_refurl << std::endl; + std::cout << "code = " << code() << std::endl; + std::cout << "str_code = " << str_code() << std::endl; + std::cout << "byte = " << current_length() << std::endl; + std::cout << "mosaic = " << m_mosaic << std::endl; + std::cout << "protect = " << m_protect << std::endl; + std::cout << "type = " << m_type << std::endl; +#endif +} + + +// +// 情報保存 +// +void Img::save_info() +{ + if( is_loading() ) return; + if( ! CACHE::mkdir_imgroot() ) return; + + std::string 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 = " << current_length() << std::endl + << "mosaic = " << m_mosaic << std::endl + << "protect = " << m_protect << std::endl + << "type = " << m_type << std::endl; + +#ifdef _DEBUG + std::cout << "Img::save_info file = " << path_info << std::endl; + std::cout << oss.str() << std::endl; +#endif + + CACHE::save_rawdata( path_info, oss.str() ); +} diff --git a/src/dbimg/img.h b/src/dbimg/img.h new file mode 100644 index 000000000..003723230 --- /dev/null +++ b/src/dbimg/img.h @@ -0,0 +1,79 @@ +// ライセンス: 最新のGPL + +// 画像データクラス + +#ifndef _IMG_H +#define _IMG_H + +#include "skeleton/loadable.h" + +#include + +namespace DBIMG +{ + // 画像タイプ + enum{ + T_NOIMG = 0, + T_JPG, + T_PNG, + T_GIF, + T_UNKNOWN + }; + + class Img : public SKELETON::Loadable + { + std::string m_url; + + int m_type; // 画像タイプ + bool m_mosaic; // モザイクかける + bool m_zoom_to_fit; // windowにサイズをあわせる + int m_size; // 画像の大きさ(パーセントで) + bool m_protect; // true ならキャッシュを保護する( delete_cache()で削除しない ) + std::string m_refurl; // 参照元URL + + // 保存用ファイルハンドラ + FILE* m_fout; + + // ファイル判定用のシグネチャ + unsigned char m_sign[ 16 ]; + + public: + Img( const std::string& url ); + ~Img(); + + void clear(); + + const std::string& url() const { return m_url; } + const bool is_cached(); + + const bool get_mosaic() const { return m_mosaic; } + void set_mosaic( bool mosaic ); + + const 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() で取得 + const int get_size() const { return m_size; } + void set_size( int size ) { m_size = size; } + + const bool is_protected() const { return m_protect; } + const std::string& refurl() const { return m_refurl; } + void set_refurl( const std::string& refurl ){ m_refurl = refurl; } + + void set_protect( bool protect ); + + void download_img(); + bool save( const std::string& path_to ); + + private: + + virtual void receive_data( const char* data, size_t size ); + virtual void receive_finish(); + + void read_info(); + void save_info(); + }; +} + +#endif diff --git a/src/dbimg/imginterface.cpp b/src/dbimg/imginterface.cpp new file mode 100644 index 000000000..135d1d219 --- /dev/null +++ b/src/dbimg/imginterface.cpp @@ -0,0 +1,202 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "imginterface.h" +#include "imgroot.h" +#include "img.h" + +#include "cache.h" + +// インスタンスは Core でひとつだけ作って、Coreのデストラクタでdeleteする +DBIMG::ImgRoot *instance_dbimg_root = NULL; + + +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_loadable( const std::string& url ) +{ + if( instance_dbimg_root ) return instance_dbimg_root->is_loadable( url ); + + return false; +} + + +bool DBIMG::is_loadable( const char* url, int n ) +{ + if( instance_dbimg_root ) return instance_dbimg_root->is_loadable( url, n ); + + return false; +} + + + +DBIMG::Img* DBIMG::get_img( const std::string& url ) +{ + if( instance_dbimg_root ) return instance_dbimg_root->get_img( url ); + return NULL; +} + + +void DBIMG::download_img( const std::string& url ) +{ + DBIMG::Img* img = DBIMG::get_img( url ); + if( img ) img->download_img(); +} + + +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, const std::string& path_to ) +{ + DBIMG::Img* img = DBIMG::get_img( url ); + if( img ) return img->save( path_to ); + + return true; +} + + +void DBIMG::delete_cache( const std::string& url ) +{ + if( instance_dbimg_root ) instance_dbimg_root->delete_cache( url, true ); +} + + +void DBIMG::delete_all_files() +{ + if( instance_dbimg_root ) instance_dbimg_root->delete_all_files(); +} + + +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::is_loading( const std::string& url ) +{ + DBIMG::Img* img = DBIMG::get_img( url ); + if( img ) return img->is_loading(); + + 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 ); +} + + +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::refurl( const std::string& url ) +{ + DBIMG::Img* img = DBIMG::get_img( url ); + if( img ) return img->refurl(); + return std::string(); +} + + +void DBIMG::set_refurl( const std::string& url, const std::string& refurl ) +{ + DBIMG::Img* img = DBIMG::get_img( url ); + if( img ) img->set_refurl( refurl ); +} + + + +size_t DBIMG::byte( const std::string& url ) +{ + DBIMG::Img* img = DBIMG::get_img( url ); + if( img ) return img->current_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; +} + + +void DBIMG::set_protect( const std::string& url, bool protect ) +{ + DBIMG::Img* img = DBIMG::get_img( url ); + if( img ) img->set_protect( protect ); +} diff --git a/src/dbimg/imginterface.h b/src/dbimg/imginterface.h new file mode 100644 index 000000000..bd08e2c8a --- /dev/null +++ b/src/dbimg/imginterface.h @@ -0,0 +1,47 @@ +// ライセンス: 最新のGPL + +// +// 画像データベースへのインターフェース関数 +// + +#ifndef _IMGINTERFACE_H +#define _IMGINTERFACE_H + +#include + +namespace DBIMG +{ + class Img; + + void create_root(); + void delete_root(); + + // ロード可能な画像ファイルかチェック + bool is_loadable( const std::string& url ); + bool is_loadable( const char* url, int n ); + + DBIMG::Img* get_img( const std::string& url ); + void download_img( const std::string& url ); + void stop_load( const std::string& url ); + bool save( const std::string& url, const std::string& path_to ); + void delete_cache( const std::string& url ); + void delete_all_files(); + bool is_cached( const std::string& url ); + bool is_loading( 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 ); + 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 refurl( const std::string& url ); + void set_refurl( const std::string& url, const std::string& refurl ); + + size_t byte( const std::string& url ); + bool is_protected( const std::string& url ); + void set_protect( const std::string& url, bool protect ); +} + +#endif diff --git a/src/dbimg/imgroot.cpp b/src/dbimg/imgroot.cpp new file mode 100644 index 000000000..71670f686 --- /dev/null +++ b/src/dbimg/imgroot.cpp @@ -0,0 +1,203 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "imgroot.h" +#include "img.h" + +#include "jdlib/confloader.h" + +#include "cache.h" +#include "command.h" + +#include + +using namespace DBIMG; + +ImgRoot::ImgRoot() +{ +} + + +ImgRoot::~ImgRoot() +{ + std::list< Img* >::iterator it; + for( it = m_list_img.begin(); it != m_list_img.end(); ++it ) delete( *it ); +} + + +// +// Imgクラス取得 +// データベースに無ければImgクラスを作る +// +Img* ImgRoot::get_img( const std::string& url ) +{ + Img* img = search_img( url ); + + // 無ければ作る + if( img == NULL ){ + img = new Img( url ); + m_list_img.push_back( img ); + } + + return img; +} + + +// +// 検索 +// +// DBになくてもImgクラスは作らない +// +Img* ImgRoot::search_img( const std::string& url ) +{ + Img* img; + + // 線形リストなので遅い + std::list< Img* >::iterator it; + for( it = m_list_img.begin(); it != m_list_img.end(); ++it ){ + + img = *( it ); + if( img->url() == url ) return img; + } + + return NULL; +} + + + +bool ImgRoot::is_loadable( const std::string& url ) +{ + return is_loadable( url.c_str(), url.length() ); +} + + +bool ImgRoot::is_loadable( 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; + + // .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; + + // .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; +} + + + + +// +// キャッシュ削除 +// +void ImgRoot::delete_cache( const std::string& url, bool redraw ) +{ +#ifdef _DEBUG + std::cout << "ImgRoot::delete_cache url = " << url << std::endl; +#endif + + Img* img = search_img( url ); + if( img && ! img->is_protected() ){ + img->clear(); + img->clear_load_data(); + } + + // キャッシュ削除 + std::string path = CACHE::path_img( url ); + if( CACHE::is_file_exists( path ) == CACHE::EXIST_FILE ) unlink( path.c_str() ); + + // info 削除 + path = CACHE::path_img_info( url ); + if( CACHE::is_file_exists( path ) == CACHE::EXIST_FILE ) unlink( path.c_str() ); + + // 再描画 + if( redraw ) CORE::core_set_command( "redraw_article" ); +} + + + + +// +// 全キャッシュ削除 +// +// image/info フォルダにあるファイルを全て取得してinfoファイルに +// 含まれているURLを取得して delete_cache() を呼ぶ +// +void ImgRoot::delete_all_files() +{ +#ifdef _DEBUG + std::cout << "ImgRoot::delete_all_files\n"; +#endif + + Gtk::MessageDialog mdiag( "保護されていない画像を全て削除しますか?", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_OK_CANCEL ); + if( mdiag.run() != Gtk::RESPONSE_OK ) return; + + std::list< std::string >list_file; + std::string path_info_root = CACHE::path_img_info_root(); + list_file = CACHE::get_filelist( path_info_root ); + + std::list< std::string >::iterator it = list_file.begin(); + for(; it != list_file.end(); ++it ){ + + std::string file = CACHE::path_img_info_root() + ( *it ); + + JDLIB::ConfLoader cf( file, std::string() ); + std::string url = cf.get_option( "url", "" ); + bool protect = cf.get_option( "protect", 0 ); + +#ifdef _DEBUG + std::cout << "file = " << file << std::endl; + std::cout << "url = " << url << std::endl; + std::cout << "protect = " << protect << std::endl; +#endif + + if( !protect ) delete_cache( url, + false // ビューの再描画はしない + ); + } + + // ビュー再描画 + CORE::core_set_command( "redraw_article" ); +} diff --git a/src/dbimg/imgroot.h b/src/dbimg/imgroot.h new file mode 100644 index 000000000..9f3b6b183 --- /dev/null +++ b/src/dbimg/imgroot.h @@ -0,0 +1,34 @@ +// ライセンス: 最新のGPL + +// 画像データベースのルートクラス + +#ifndef _IMGROOT_H +#define _IMGROOT_H + +#include +#include + +namespace DBIMG +{ + class Img; + + class ImgRoot + { + std::list< Img* > m_list_img; + + public: + ImgRoot(); + ~ImgRoot(); + + Img* get_img( const std::string& url ); + Img* search_img( const std::string& url ); + + bool is_loadable( const std::string& url ); + bool is_loadable( const char* url, int n ); + + void delete_cache( const std::string& url, bool redraw ); + void delete_all_files(); + }; +} + +#endif diff --git a/src/dbtree/Makefile.am b/src/dbtree/Makefile.am new file mode 100644 index 000000000..c765c13db --- /dev/null +++ b/src/dbtree/Makefile.am @@ -0,0 +1,57 @@ +noinst_LIBRARIES = libdbtree.a + +libdbtree_a_SOURCES = \ + interface.cpp \ + spchar_decoder.cpp \ + root.cpp \ +\ + boardbase.cpp \ + board2ch.cpp \ + board2chcompati.cpp \ + boardjbbs.cpp \ + boardmachi.cpp \ +\ + settingloader.cpp \ + boardfactory.cpp \ +\ + articlebase.cpp \ + article2ch.cpp \ + article2chcompati.cpp \ + articlejbbs.cpp \ + articlemachi.cpp \ +\ + nodetreebase.cpp \ + nodetree2ch.cpp \ + nodetree2chcompati.cpp \ + nodetreejbbs.cpp \ + nodetreemachi.cpp + +noinst_HEADERS = \ + interface.h \ + spchar_decoder.h \ + root.h \ +\ + boardbase.h \ + board2ch.h \ + board2chcompati.h \ + boardjbbs.h \ + boardmachi.h \ +\ + settingloader.h \ + boardfactory.h \ +\ + articlebase.h \ + article2ch.h \ + article2chcompati.h \ + articlejbbs.h \ + articlemachi.h \ +\ + node.h \ + nodetreebase.h \ + nodetree2ch.h \ + nodetree2chcompati.h \ + nodetreejbbs.h \ + nodetreemachi.h + +AM_CXXFLAGS = @GTKMM_CFLAGS@ +INCLUDES = -I$(top_srcdir)/src diff --git a/src/dbtree/article2ch.cpp b/src/dbtree/article2ch.cpp new file mode 100644 index 000000000..97244765f --- /dev/null +++ b/src/dbtree/article2ch.cpp @@ -0,0 +1,70 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "article2ch.h" +#include "nodetree2ch.h" +#include "interface.h" + +#include "jdlib/miscutil.h" +#include "jdlib/misctime.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() +{} + + + +// 書き込みメッセージ変換 +const std::string Article2ch::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(); + + // hana + std::string hana = DBTREE::board_hana_for_write( get_url() ); + if( ! hana.empty() ) ss_post << "&hana=" << hana; + + // ログイン中 + if( LOGIN::get_login2ch()->login_now() ){ + std::string sid = LOGIN::get_login2ch()->get_sessionid(); + ss_post << "&sid=" << MISC::url_encode( sid.c_str(), sid.length() ); + } + + ss_post << "&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(); +} + + + +NodeTreeBase* Article2ch::create_nodetree() +{ + return new NodeTree2ch( get_url(), get_org_url(), get_date_modified() ); +} diff --git a/src/dbtree/article2ch.h b/src/dbtree/article2ch.h new file mode 100644 index 000000000..bec64995b --- /dev/null +++ b/src/dbtree/article2ch.h @@ -0,0 +1,30 @@ +// ライセンス: 最新のGPL + +// +// 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(); + + // 書き込みメッセージ変換 + virtual const std::string create_write_message( const std::string& name, const std::string& mail, const std::string& msg ); + + private: + + virtual NodeTreeBase* create_nodetree(); + }; +} + +#endif diff --git a/src/dbtree/article2chcompati.cpp b/src/dbtree/article2chcompati.cpp new file mode 100644 index 000000000..96b3e1407 --- /dev/null +++ b/src/dbtree/article2chcompati.cpp @@ -0,0 +1,96 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "article2chcompati.h" +#include "nodetree2chcompati.h" +#include "interface.h" + +#include "jdlib/miscutil.h" +#include "jdlib/misctime.h" + +#include + +using namespace DBTREE; + +Article2chCompati::Article2chCompati( const std::string& datbase, const std::string& _id, bool cached ) + : ArticleBase( datbase, _id, cached ) +{ + assert( ! id().empty() ); + + // key (idから拡張子を除いたもの)を取得 + unsigned int i = get_id().rfind( "." ); // 拡張子は取り除く + if( i != std::string::npos ) set_key( get_id().substr( 0, i ) ); + + // key から since 計算 + if( i != std::string::npos ) set_since_time( atol( get_key().c_str() ) ); + set_since_date( MISC::timettostr( get_since_time() ) ); +} + + + +Article2chCompati::~Article2chCompati() +{} + + + +// 書き込みメッセージ変換 +const 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" +// +// +const std::string Article2chCompati::url_bbscgi() +{ + std::string cgibase = DBTREE::url_bbscgibase( get_url() ); + return cgibase.substr( 0, cgibase.length() -1 ); // 最後の '/' を除く +} + + + +// +// subbbscgi のURL +// +// (例) "http://www.hoge2ch.net/test/subbbs.cgi" +// +const std::string Article2chCompati::url_subbbscgi() +{ + std::string cgibase = DBTREE::url_subbbscgibase( get_url() ); + return cgibase.substr( 0, cgibase.length() -1 ); // 最後の '/' を除く +} + + + + +NodeTreeBase* Article2chCompati::create_nodetree() +{ + return new NodeTree2chCompati( get_url(), get_date_modified() ); +} diff --git a/src/dbtree/article2chcompati.h b/src/dbtree/article2chcompati.h new file mode 100644 index 000000000..1467ee586 --- /dev/null +++ b/src/dbtree/article2chcompati.h @@ -0,0 +1,36 @@ +// ライセンス: 最新のGPL + +// +// 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(); + + // 書き込みメッセージ変換 + virtual const std::string create_write_message( const std::string& name, const std::string& mail, const std::string& msg ); + + // bbscgi のURL + virtual const std::string url_bbscgi(); + + // subbbscgi のURL + virtual const std::string url_subbbscgi(); + + private: + + virtual NodeTreeBase* create_nodetree(); + }; +} + +#endif diff --git a/src/dbtree/articlebase.cpp b/src/dbtree/articlebase.cpp new file mode 100644 index 000000000..54ef579cc --- /dev/null +++ b/src/dbtree/articlebase.cpp @@ -0,0 +1,1110 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "articlebase.h" +#include "nodetreebase.h" +#include "interface.h" +#include "global.h" + +#include "jdlib/miscutil.h" +#include "jdlib/misctime.h" +#include "jdlib/miscmsg.h" + +#include "httpcode.h" +#include "command.h" +#include "cache.h" +#include "global.h" +#include "login2ch.h" + +#include + + +using namespace DBTREE; + + +// 情報ファイルから値を読み込み +// JDLIB::ConfLoader では遅いので別に作成。オプションの順番に注意すること +#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 ) + + + + +ArticleBase::ArticleBase( const std::string& datbase, const std::string& id, bool cached ) + : SKELETON::Lockable(), + m_heap( MAX_RESNUMBER ), + m_id ( id ), + m_key( std::string() ), + m_since_time( 0 ), + m_since_date( std::string() ), + m_str_code( std::string() ), + m_lng_dat( 0 ), + m_status( STATUS_UNKNOWN ), + m_subject( std::string() ), + m_number( 0 ), + m_number_new( 0 ), + m_number_load( 0 ), + m_number_before_load( 0 ), + m_number_seen( 0 ), + m_write_fixname( 0 ), + m_write_fixmail( 0 ), + m_abone_id( 1 ), + m_abone_name( 1 ), + m_cached( cached ), + m_read_info( 0 ), + m_current( 0 ), + m_save_info( 0 ) +{ +#ifdef _DEBUG + std::cout << "ArticleBase::ArticleBase : " << m_id << std::endl; +#endif + + memset( &m_access_time, 0, sizeof( struct timeval ) ); + memset( &m_write_time, 0, sizeof( struct timeval ) ); + + // m_url にURLセット + update_url( datbase ); + + // この段階では移転前の旧ホスト名は分からないのでとりあえず現在のホスト名をセットしておく + // あとで Root::url_dat()でURLを変換する時に旧ホスト名を教えてもらってinfoファイルに保存しておく。 + // Root::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 ); + assert( !m_nodetree ); +} + + +const bool ArticleBase::empty() +{ + return m_url.empty(); +} + + +// +// 移転する前のオリジナルのURL +// +const std::string ArticleBase::get_org_url() +{ + std::string newhost = MISC::get_hostname( m_url ); + return m_org_host + m_url.substr( newhost.length() ); +} + + + +// +// nodetree の number 番のレスのヘッダノードのポインタを返す +// +NODE* ArticleBase::res_header( int number ) +{ + return get_nodetree()->res_header( number ); +} + + +// +// number番の名前 +// +const std::string ArticleBase::get_name( int number ) +{ + return get_nodetree()->get_name( number ); +} + + +// +// number番のID +// +const 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 ); +} + + + +// +// number番のレスを参照しているレス番号をリストにして取得 +// +std::list< int > ArticleBase::get_reference( int number ) +{ + return get_nodetree()->get_reference( number ); +} + + +// +// URL を含むレス番号をリストにして取得 +// +std::list< int > ArticleBase::get_res_with_url() +{ + return get_nodetree()->get_res_with_url(); +} + + +// +// number番のレスの文字列を返す +// ref == true なら先頭に ">" を付ける +// +const std::string ArticleBase::get_res_str( int number, bool ref ) +{ + return get_nodetree()->get_res_str( number, ref ); +} + + +// +// 更新時刻 +// +time_t ArticleBase::get_time_modified() +{ + time_t time_out; + time_out = MISC::datetotime( m_date_modified ); + if( time_out == 0 ) time_out = time( NULL ) - 600; + return time_out; +} + + +// +// url の更新 +// +// 移転があったときなどに上位クラスから呼ばれる +// +void ArticleBase::update_url( const std::string& datbase ) +{ + if( m_id.empty() ) return; + +#ifdef _DEBUG + std::string old_url = m_url; +#endif + + m_url = datbase + m_id; + +#ifdef _DEBUG + if( !old_url.empty() ) std::cout << "ArticleBase::update_url from " << old_url + << " to " << m_url << std::endl; +#endif +} + + + +// +// 移転前のオリジナルのホスト名をセット +// +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 + << "host = " << host << std::endl + << "org_host = " << m_org_host << std::endl; +#endif + + m_org_host = host; + m_save_info = true; + } +} + + + + +// +// access_time を 文字列に変換して返す +// +const std::string ArticleBase::get_access_time_str() +{ + return MISC::timevaltostr( m_access_time ); +} + + +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() && subject != m_subject ){ + m_subject = subject; + m_save_info = true; + } +} + + +void ArticleBase::set_number( int number ) +{ + if( number && number != m_number ) m_number = number; +} + + +void ArticleBase::set_number_load( int number_load ) +{ + if( number_load && number_load != m_number_load ) m_number_load = number_load; +} + + +void ArticleBase::set_number_seen( int number_seen ) +{ + if( number_seen && number_seen != m_number_seen ){ + m_number_seen = number_seen; + m_save_info = true; + } +} + +// +// 書き込み時間更新 +// +void ArticleBase::update_writetime() +{ + struct timeval tv; + struct timezone tz; + if( gettimeofday( &tv, &tz ) == 0 ){ + + m_write_time = tv; + m_write_time_date = MISC::timettostr( m_write_time.tv_sec ); + +#ifdef _DEBUG + std::cout << "ArticleBase::update_writetime : " << m_write_time.tv_sec << " " << m_write_time_date << std::endl; +#endif + m_save_info = true; + + // BoardViewの行を更新 + CORE::core_set_command( "update_board_item", DBTREE::url_subject( m_url ), m_id ); + } +} + + + +// +// あぼーんしてるか +// +const bool ArticleBase::abone( int number ) +{ + if( number <= 0 || number > m_number_load ) return false; + if( empty() ) return false; + + // まだnodetreeが作られてなくてあぼーんの情報が得られてないのでnodetreeを作って情報取得 + if( !m_abone ) get_nodetree(); + assert( m_abone ); + + return m_abone[ number ]; +} + + +// +// あぼーんのリセット +// +void ArticleBase::reset_abone( std::list< std::string >& ids, std::list< std::string >& names ) +{ + if( empty() ) return; + +#ifdef _DEBUG + std::cout << "ArticleBase::reset_abone\n"; + + std::list< std::string >::iterator it; + std::cout << "IDs:\n"; + for( it = ids.begin(); it != ids.end(); ++it ) std::cout << (*it) << std::endl; + std::cout << "NAMEs:\n"; + for( it = names.begin(); it != names.end(); ++it ) std::cout << (*it) << std::endl; +#endif + + m_list_abone_id.clear(); + m_list_abone_name.clear(); + + m_list_abone_id = ids; + m_list_abone_name = names; + + check_abone( 1, m_number_load ); + + m_save_info = true; +} + + + +// +// あぼーんID追加 +// +void ArticleBase::add_abone_id( const std::string& id ) +{ + if( 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 ); + + check_abone( 1, m_number_load ); + + m_save_info = true; +} + + +// +// あぼーん名前追加 +// +void ArticleBase::add_abone_name( const std::string& name ) +{ + if( empty() ) return; + +#ifdef _DEBUG + std::cout << "ArticleBase::add_abone_name : " << name << std::endl; +#endif + + m_list_abone_name.push_back( name ); + + check_abone( 1, m_number_load ); + + m_save_info = true; +} + + + +// +// あぼーん判定を更新 +// +void ArticleBase::check_abone( int from_number, int to_number ) +{ + if( empty() ) return; + assert( m_abone ); + +#ifdef _DEBUG + std::cout << "ArticleBase::check_abone from " << from_number << " to " << to_number << std::endl; +#endif + if( to_number < from_number ) return; + + bool abone_id = ( m_abone_id && ( m_list_abone_id.size() > 0 ) ); + bool abone_name = ( m_abone_name && ( m_list_abone_name.size() > 0 ) ); + + for( int i = from_number ; i <= to_number; ++i ){ + + m_abone[ i ] = false; + std::list< std::string >::iterator it; + + // ID + if( abone_id ){ + + int ln_protoid = strlen( PROTO_ID ); + + for( it = m_list_abone_id.begin(); it != m_list_abone_id.end(); ++it ){ + + // std::string の find は遅いのでstrcmp使う + if( strcmp( get_nodetree()->get_id_name( i ).c_str() + ln_protoid, ( *it ).c_str() ) == 0 ){ + +#ifdef _DEBUG + std::cout << "abone = " << i << std::endl; +#endif + + m_abone[ i ] = true; + break; + } + } + } + if( m_abone[ i ] ) continue; + + // 名前 + if( abone_name ){ + + for( it = m_list_abone_name.begin(); it != m_list_abone_name.end(); ++it ){ + + // std::string の find は遅いのでstrcmp使う + const char* s1 = get_nodetree()->get_name( i ).c_str(); + const char* s2 = ( *it ).c_str(); + while( *s1 != '\0' && *s1 != *s2 ) s1++; + if( *s1 != '\0' ){ + + if( strncmp( s1, s2, strlen( s2 ) ) == 0 ){ + m_abone[ i ] = true; + break; + } + } + } + } + if( m_abone[ i ] ) continue; + } + +#ifdef _DEBUG + for( int i = from_number ; i <= to_number; ++i ) if( m_abone[ i ] ){ + std::cout << i << " " << get_nodetree()->get_id_name( i ) << " " << get_nodetree()->get_name( i ) << std::endl; + } +#endif + +} + + +// +// ブックマークの数 +// +int ArticleBase::get_num_bookmark() +{ + if( !m_bookmark ) return 0; + + int ret = 0; + for( int i = 1; i < MAX_RESNUMBER; ++i ) if( is_bookmarked( i ) ) ++ret; + return ret; +} + + +// +// ブックマークされているか +// +bool ArticleBase::is_bookmarked( int number ) +{ + if( number <= 0 || number > m_number_load ) return false; + + // まだnodetreeが作られてなくてブックマークとあぼーんの情報が得られてないのでnodetreeを作って情報取得 + if( !m_bookmark || !m_abone ) get_nodetree(); + assert( m_abone ); + assert( m_bookmark ); + + return ( !m_abone[ number ] && m_bookmark[ number ] ); +} + + +// +// ブックマークセット +// +void ArticleBase::set_bookmark( int number, bool set ) +{ + if( ! m_bookmark ) get_nodetree(); + if( number <= 0 || number > MAX_RESNUMBER ) return; + + m_save_info = true; + m_bookmark[ number ] = set; +} + + + + +// +// NodeTree作成 +// +// もしNodeTreeが作られていなかったらここでNewする +// +// this の参照が無くなったら ArticleBase::unlock_impl()が呼ばれて m_nodetree は自動クリアされる +// +JDLIB::ConstPtr< NodeTreeBase >& ArticleBase::get_nodetree() +{ + assert( !empty() ); + + if( ! m_nodetree ){ + +#ifdef _DEBUG + std::cout << "ArticleBase::get_nodetree create " << m_url << std::endl; +#endif + + m_nodetree = create_nodetree(); + assert( m_nodetree ); + + if( ! m_abone ) m_abone = ( char* ) m_heap.heap_alloc( MAX_RESNUMBER ); + if( ! m_bookmark ) m_bookmark = ( char* ) m_heap.heap_alloc( MAX_RESNUMBER ); + + 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; +} + + + +// +// 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.clear(); + + // スレ情報保存 + save_info(); +} + + + + + +// +// ロード中か +// +const bool ArticleBase::is_loading() +{ + if( ! m_nodetree ) return false; + return m_nodetree->is_loading(); +} + + + +// +// ロード停止 +// +void ArticleBase::stop_load() +{ + if( ! m_nodetree ) return; + m_nodetree->stop_load(); +} + + + +// +// スレッドのロード開始 +// +// DAT落ちの場合はロードしないので、強制的にリロードしたいときは reset_status() で +// ステータスをリセットしてからロードする +// +void ArticleBase::download_dat() +{ + if( empty() ) return; + +#ifdef _DEBUG + std::cout << "ArticleBase::download_dat " << m_url << std::endl;; +#endif + + // DAT落ちしていてログイン中で無い時はロードしない + if( ! ( ( m_status & STATUS_OLD ) && ! LOGIN::get_login2ch()->login_now() ) ){ + +#ifdef _DEBUG + std::cout << "start\n"; +#endif + get_nodetree()->download_dat(); + } +} + + + + +// +// ロード中など nodetree の構造が変わったときにnodetreeから呼ばれる slot +// +void ArticleBase::slot_node_updated() +{ + assert( m_nodetree ); + +#ifdef _DEBUG + std::cout << "ArticleBase::slot_node_updated" << std::endl; +#endif + + // nodetreeから情報取得 + if( ! m_nodetree->get_subject().empty() ) m_subject = m_nodetree->get_subject(); + m_lng_dat = m_nodetree->lng_dat(); + + // スレが更新している場合 + if( m_number_load != m_nodetree->get_res_number() ){ + + // あぼーん判定更新 + check_abone( m_number_load + 1, m_nodetree->get_res_number() ); + + m_number_load = m_nodetree->get_res_number(); + + // ビュー更新 + 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(); + + // nodetreeから情報取得 + m_code = m_nodetree->get_code(); + m_str_code = m_nodetree->get_str_code(); + m_date_modified = m_nodetree->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; + m_number_before_load = m_number_load; + m_ext_err = m_nodetree->get_ext_err(); + + // 状態更新 + int old_status = m_status; + if( m_code != HTTP_ERR ){ + + m_status = STATUS_NORMAL; + + // DAT落ち + if( m_code == HTTP_REDIRECT || m_code == HTTP_NOT_FOUND ) m_status = STATUS_OLD; + } + + // 壊れている + if( m_nodetree->is_broken() ) m_status |= STATUS_BROKEN; + else m_status &= ~STATUS_BROKEN; + + // 状態が変わっていたら情報保存 + if( old_status != m_status ) m_save_info = true; + + // スレの数が0ならスレ情報はセーブしない + if( ! m_number_load ) m_cached = false; + + // スレが更新している場合はスレ情報を更新 + else if( m_number_new ){ + + struct timeval tv; + struct timezone tz; + if( gettimeofday( &tv, &tz ) == 0 ) m_access_time = tv; + + if( m_number < m_number_load ) m_number = m_number_load; + m_number_seen = m_number_load; + + m_cached = true; + m_read_info = true; + m_save_info = true; + } + +#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 = " << access_time_str() << std::endl + << "lng = " << m_lng_dat << std::endl + << "code = " << m_code << std::endl + << "status = " << m_status << std::endl + + ; +#endif + + // 対応するBoardビューの行を更新 + CORE::core_set_command( "update_board_item", DBTREE::url_subject( m_url ), m_id ); + + // articleビューに終了を知らせる + CORE::core_set_command( "update_article", m_url ); + CORE::core_set_command( "update_article_finish", m_url ); +} + + + + +// +// キャッシュ削除 +// +void ArticleBase::delete_cache() +{ +#ifdef _DEBUG + std::cout << "ArticleBase::delete_cache url = " << m_url << std::endl; +#endif + + if( empty() ) return; + + m_number_load = m_number_seen = m_number_before_load = 0; + m_status = STATUS_UNKNOWN; + m_date_modified.clear(); + memset( &m_access_time, 0, sizeof( struct timeval ) ); + memset( &m_write_time, 0, sizeof( struct timeval ) ); + m_write_time_date.clear(); + + m_heap.clear(); + m_abone.reset(); + m_bookmark.reset(); + m_list_abone_id.clear(); + m_list_abone_name.clear(); + m_cached = false; + m_read_info = false; + m_save_info = false; + + // キャッシュ + std::string path = CACHE::path_dat( m_url ); + if( CACHE::is_file_exists( path ) == CACHE::EXIST_FILE ) unlink( path.c_str() ); + + // info + path = CACHE::path_article_info( m_url, m_id ); + if( CACHE::is_file_exists( path ) == CACHE::EXIST_FILE ) unlink( path.c_str() ); + + // 拡張info + path = CACHE::path_article_ext_info( m_url, m_id ); + if( CACHE::is_file_exists( path ) == CACHE::EXIST_FILE ) unlink( path.c_str() ); + + // BoardViewの行を更新 + CORE::core_set_command( "update_board_item", DBTREE::url_subject( m_url ), m_id ); +} + + + +// +// infoファイル読み込み +// +// インスタンスが出来るたびに呼んでいると重くなるので、BoardBase::get_article_fromURL() +// で初めて参照されたときや、Boardビューに表示するときに一回だけ読み込む +// +void ArticleBase::read_info() +{ + if( empty() ) return; + if( ! m_cached ) return; // キャッシュがないなら読まない + if( m_read_info ) return; // 一度読んだら2度読みしない + m_read_info = true; + +#ifdef _DEBUG + std::cout << "ArticleBase::read_info : url = " << m_url << std::endl; +#endif + + std::string path_info = CACHE::path_article_ext_info( m_url, m_id ); + if( CACHE::is_file_exists( path_info ) == 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( 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 で使用 + + // 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 = atoi( str_tmp.c_str() ); + m_number_before_load = m_number_load; + + // 見た場所 + m_number_seen = 0; + GET_INFOVALUE( str_tmp, "seen = " ); + if( ! str_tmp.empty() ) m_number_seen = atoi( str_tmp.c_str() ); + + // 更新時間 (time) + GET_INFOVALUE( m_date_modified, "modified = " ); + + // access time + GET_INFOVALUE( str_tmp, "access = " ); + if( ! str_tmp.empty() ){ + list_tmp = MISC::split_line( str_tmp ); + if( list_tmp.size() == 3 ){ + it_tmp = list_tmp.begin(); + m_access_time.tv_sec = ( atoi( ( *(it_tmp++) ).c_str() ) << 16 ) + atoi( ( *(it_tmp++) ).c_str() ); + m_access_time.tv_usec = atoi( ( *(it_tmp++) ).c_str() ); + } + } + + // write time + GET_INFOVALUE( str_tmp, "writetime = " ); + if( ! str_tmp.empty() ){ + list_tmp = MISC::split_line( str_tmp ); + if( list_tmp.size() == 3 ){ + it_tmp = list_tmp.begin(); + m_write_time.tv_sec = ( atoi( ( *(it_tmp++) ).c_str() ) << 16 ) + atoi( ( *(it_tmp++) ).c_str() ); + m_write_time.tv_usec = atoi( ( *(it_tmp++) ).c_str() ); + } + + m_write_time_date = MISC::timettostr( m_write_time.tv_sec ); + } + + // 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() ); + + // 状態 + m_status = STATUS_UNKNOWN; + 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() ){ + list_tmp = MISC::split_line( str_tmp ); + it_tmp = list_tmp.begin(); + for( ; it_tmp != list_tmp.end(); ++it_tmp ){ + if( !(*it_tmp).empty() ) m_list_abone_id.push_back( MISC::recover_quot( ( *it_tmp ) ) ); + } + } + + // あぼーん name + GET_INFOVALUE( str_tmp, "abonename = " ); + if( ! str_tmp.empty() ){ + list_tmp = MISC::split_line( str_tmp ); + it_tmp = list_tmp.begin(); + for( ; it_tmp != list_tmp.end(); ++it_tmp ){ + if( !(*it_tmp).empty() ) m_list_abone_name.push_back( MISC::recover_quot( ( *it_tmp ) ) ); + } + } + + // ブックマーク + GET_INFOVALUE( str_tmp, "bookmark = " ); + if( ! str_tmp.empty() ){ + + // ブックマーク領域作成 + if( ! m_bookmark ) m_bookmark = ( char* ) m_heap.heap_alloc( MAX_RESNUMBER ); + + list_tmp = MISC::split_line( str_tmp ); + it_tmp = list_tmp.begin(); + for( ; it_tmp != list_tmp.end(); ++it_tmp ) if( !(*it_tmp).empty() ) m_bookmark[ atoi( (*it_tmp).c_str() ) ] = true; + } + } + + // キャッシュはあるけど情報ファイルが無い場合 + // 一時的にnodetreeを作って情報を取得して保存 + if( ! m_number_load || m_subject.empty() ){ + +#ifdef _DEBUG + std::cout << "ArticleBase::read_info : update info " << m_url << std::endl; + std::cout << "load = " << m_number_load << " subject = " << m_subject << std::endl; +#endif + + CORE::core_set_command( "set_status","", "スレ情報更新中・・・しばらくお待ち下さい" ); + MISC::MSG( "updating " + m_url ); + + m_subject = get_nodetree()->get_subject(); + m_number_load = get_nodetree()->get_res_number(); + + if( m_subject.empty() ) { + m_subject = "壊れています"; + m_status |= STATUS_BROKEN; + } + if( !m_number_load ){ + m_number_load = 1; + m_status |= STATUS_BROKEN; + } + + m_number_before_load = m_number_load; + m_save_info = 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; + +#ifdef _DEBUG + std::cout << "ArticleBase::read_info file = " << path_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 + ; + + std::cout << "abone-id\n"; std::list < std::string >::iterator it = m_list_abone_id.begin(); + for( ; it != m_list_abone_id.end(); ++it ) std::cout << (*it) << std::endl; + std::cout << "abone-name\n"; it = m_list_abone_name.begin(); + for( ; it != m_list_abone_name.end(); ++it ) std::cout << (*it) << std::endl; + + if( m_bookmark ){ + std::cout << "bookmark ="; + for( int i = 1; i <= m_number_load; ++i ) if( m_bookmark[ i ] ) std::cout << " " << i; + std::cout << std::endl; + } +#endif + +} + + +// +// infoファイル書き込み +// +// キャッシュがある( m_cached = true ) かつ +// m_save_info = true の時に保存。save_info()を呼ぶ前にm_save_infoをセットすること。 +// +void ArticleBase::save_info() +{ + if( empty() ) return; + if( ! m_cached ) return; + if( ! m_save_info ) return; + m_save_info = false; + + if( ! CACHE::mkdir_boardroot( m_url ) ) return; + + std::string path_info = CACHE::path_article_ext_info( m_url, m_id ); + if( path_info.empty() ) return; + + // 書き込み時間 + std::ostringstream ss_write; + ss_write << ( m_write_time.tv_sec >> 16 ) << " " << ( m_write_time.tv_sec & 0xffff ) << " " << m_write_time.tv_usec; + + // あぼーん情報 + std::string str_abone_id, str_abone_name; + std::list< std::string >::iterator it = m_list_abone_id.begin(); + for( ; it != m_list_abone_id.end(); ++it ){ + if( ! ( *it ).empty() ) str_abone_id += " \"" + MISC::replace_quot( ( *it ) ) + "\""; + } + it = m_list_abone_name.begin(); + for( ; it != m_list_abone_name.end(); ++it ){ + if( ! ( *it ).empty() ) str_abone_name += " \"" + MISC::replace_quot( ( *it ) ) + "\""; + } + + // スレのブックマーク + std::ostringstream ss_bookmark; + if( m_bookmark ){ + for( int i = 1; i <= m_number_load; ++i ) if( m_bookmark[ i ] ) ss_bookmark << " " << 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 + ; + +#ifdef _DEBUG + std::cout << "ArticleBase::save_info file = " << path_info << std::endl; + std::cout << sstr.str() << std::endl; +#endif + + CACHE::save_rawdata( path_info, sstr.str() ); + + // 互換性のため + save_navi2ch_info(); +} + + + +// +// navi2ch互換情報ファイル書き込み +// +// 互換性のため書き出すだけで実際にはこの中の情報は使わない +// +void ArticleBase::save_navi2ch_info() +{ + if( empty() ) return; + if( ! m_cached ) return; + + if( ! CACHE::mkdir_boardroot( m_url ) ) return; + + std::string path_info = CACHE::path_article_info( m_url, m_id ); + + 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::is_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 + + std::list< std::string > lists = MISC::get_elisp_lists( str_info ); + std::list< std::string >::iterator it = lists.begin(); + ++it; + name = *( it++ ); + ++it; + hide = *( it++ ); + important = *( it++ ); + unfilter = *( it++ ); + mail = *( it++ ); + kako = *( it++ ); + } + + 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 = " << path_info << std::endl; + std::cout << sstr.str() << std::endl; +#endif + + CACHE::save_rawdata( path_info, sstr.str() ); +} diff --git a/src/dbtree/articlebase.h b/src/dbtree/articlebase.h new file mode 100644 index 000000000..94cea29dd --- /dev/null +++ b/src/dbtree/articlebase.h @@ -0,0 +1,246 @@ +// ライセンス: 最新のGPL + +// +// スレ情報のベースクラス +// +// 新スレ用の id は 0000000000(.各板別の拡張子) とする。 +// + +#ifndef _ARTICLEBASE_H +#define _ARTICLEBASE_H + +#include +#include +#include + +#include "skeleton/lockable.h" + +#include "jdlib/constptr.h" +#include "jdlib/heap.h" + +namespace DBTREE +{ + class NodeTreeBase; + class NODE; + + class ArticleBase : public SKELETON::Lockable + { + JDLIB::HEAP m_heap; + + // m_nodetree は参照が外れたら自動でクリアされる + JDLIB::ConstPtr< NodeTreeBase > m_nodetree; + + std::string m_url; // dat ファイルのURL + std::string m_id; // ID ( .datなどの拡張子付き (例) 1234567.dat ) + std::string m_key; // ID から拡張子を取った物 + std::string m_date_modified; // サーバのデータが更新された時間 + 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コード以外のエラーメッセージ + size_t m_lng_dat; // dat ファイルのサイズ + int m_status; // 状態 ( global.h で定義 ) + + // 移転する前にこのスレがあった旧ホスト名( 移転していないなら m_url に含まれているホスト名と同じ ) + // 詳しくはコンストラクタの説明を参照せよ + std::string m_org_host; + + std::string m_subject; // サブジェクト + int m_number; // サーバ上にあるレスの数 + int m_number_new; // 新着数( ロードした時の差分読み込み数) + int m_number_load; // キャッシュにあるレスの数 + int m_number_before_load; // ロード前のレスの数( m_number_new を計算するのに使う ) + int m_number_seen; // どこまで読んだか + struct timeval m_access_time; // ユーザが最後にロードした時間 + struct timeval 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; // あぼーんする名前 + + // あぼーん + JDLIB::ConstPtr< char > m_abone; // あぼーん判定のキャッシュ + + bool m_abone_id; // id であぼーん判定をする + bool m_abone_name; // 名前であぼーん判定をする + + // ブックマーク + JDLIB::ConstPtr< char > m_bookmark; // ブックマーク判定キャッシュ + + // HDDにキャッシュされているか + bool m_cached; + + // 情報ファイルを読みこんだらtrueにして2度読みしないようにする + bool m_read_info; + + // subject.txtに含まれているなら true + // 実際にDAT落ちしたかどうかはサーバにアクセスして302か404が帰ってきたらDAT落ちと判定 + bool m_current; + + // true ならunlock_impl()がコールバックされたときに情報保存 + bool m_save_info; + + protected: + void set_key( const std::string& key ){ m_key = key; } + void set_since_time( time_t since ){ m_since_time = since; } + void set_since_date( std::string since ){ m_since_date = since; } + + public: + + ArticleBase( const std::string& datbase, const std::string& id, bool cached ); + virtual ~ArticleBase(); + + const bool empty(); + + const std::string& get_url() const { return m_url; } + + // 移転があったときなどにURLを更新 + void update_url( const std::string& datbase ); + + // 移転する前のオリジナルのURL + const std::string get_org_url(); + + // 移転する前のオリジナルのホスト名 + 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; } + const int get_number() const { return m_number; } + const int get_number_new() const { return m_number_new; } + const int get_number_load() const { return m_number_load; } + const int get_number_seen() const{ return m_number_seen; } + const size_t get_lng_dat() const { return m_lng_dat; } + + // nodetree の number 番のレスのヘッダノードのポインタを返す + NODE* res_header( int number ); + + // number番のレスの発言者の名前 + const std::string get_name( int number ); + + // number番のレスの発言者のID( スレIDではなくて名前の横のID) + const 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 ); + + // number番のレスを参照しているレス番号をリストにして取得 + std::list< int > get_reference( int number ); + + // URL を含むレス番号をリストにして取得 + std::list< int > get_res_with_url(); + + // number番のレスの文字列を返す + // ref == true なら先頭に ">" を付ける + const 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; } + const bool get_write_fixname() const { 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; } + const bool get_write_fixmail() const { return m_write_fixmail; } + void set_write_fixmail( bool set ){ m_save_info = true; m_write_fixmail = set; } + + // 書き込みメッセージ作成 + virtual const std::string create_write_message( const std::string& name, const std::string& mail, const std::string& msg ) + { return std::string(); } + + // bbscgi のURL + virtual const std::string url_bbscgi() { return std::string(); } + + // subbbscgi のURL + virtual const std::string url_subbbscgi() { return std::string(); } + + // 最終アクセス時間 + const std::string get_access_time_str(); + + // 最終書き込み時間 + const time_t& get_write_time() const { return m_write_time.tv_sec; } + const std::string& get_write_date() const { return m_write_time_date; } + + // スレ立て時刻 + const time_t& get_since_time() const { return m_since_time; }; + const std::string& get_since_date() const { return m_since_date; } + + // 更新時間 + time_t get_time_modified(); + const std::string& get_date_modified() { return m_date_modified; } + + // http コード + const int get_code() const { 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; } + + // 状態 ( global.hで定義 ) + const int get_status() const{ return m_status; } + void reset_status(); + + void set_subject( const std::string& subject ); + void set_number( int number ); + void set_number_load( int number_load ); + void set_number_seen( int number_seen ); + void update_writetime(); + + void delete_cache(); + + // HDDにキャッシュされているか + const bool is_cached() const { return m_cached; } + + // subject.txtに含まれているなら true + const bool is_current() const { return m_current; }; + void set_current( bool current ){ m_current = current; } + + // あぼーん + std::list< std::string > get_abone_list_id(){ return m_list_abone_id; } + std::list< std::string > get_abone_list_name(){ return m_list_abone_name; } + const bool abone( int number ); + void reset_abone( std::list< std::string >& ids, std::list< std::string >& names ); + void add_abone_id( const std::string& id ); + void add_abone_name( const std::string& id ); + + // レスのブックマーク + int get_num_bookmark(); + bool is_bookmarked( int number ); + void set_bookmark( int number, bool set ); + + // 情報ファイル読み込み + void read_info(); + + // スレッドのロード開始 + const bool is_loading(); + void stop_load(); + void download_dat(); + + private: + + JDLIB::ConstPtr< NodeTreeBase >& get_nodetree(); + virtual NodeTreeBase* create_nodetree(){ return NULL; } + + void slot_node_updated(); + void slot_load_finished(); + virtual void unlock_impl(); + + void check_abone( int from_number, int to_number ); + + // 情報ファイル書き込み + void save_info(); + void save_navi2ch_info(); + }; +} + +#endif diff --git a/src/dbtree/articlejbbs.cpp b/src/dbtree/articlejbbs.cpp new file mode 100644 index 000000000..0a72f0dc3 --- /dev/null +++ b/src/dbtree/articlejbbs.cpp @@ -0,0 +1,99 @@ +// ライセンス: 最新のGPL + +//#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() ) ); + set_since_date( MISC::timettostr( get_since_time() ) ); +} + + +ArticleJBBS::~ArticleJBBS() +{} + + +const 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.livedoor.jp/bbs/write.cgi/computer/123/1234567/" +// +// +const std::string ArticleJBBS::url_bbscgi() +{ + return DBTREE::url_bbscgibase( get_url() ) + DBTREE::board_id( get_url() ) + "/" + get_key() + "/"; +} + + +// +// subbbscgi のURL +// +// (例) "http://jbbs.livedoor.jp/bbs/write.cgi/computer/123/1234567/" +// +const std::string ArticleJBBS::url_subbbscgi() +{ + return DBTREE::url_subbbscgibase( get_url() ) + DBTREE::board_id( get_url() ) + "/" + get_key() + "/"; +} + + + +NodeTreeBase* ArticleJBBS::create_nodetree() +{ +#ifdef _DEBUG + std::cout << "ArticleJBBS::create_nodetree " << url() << std::endl; +#endif + + return new NodeTreeJBBS( get_url(), get_date_modified() ); +} diff --git a/src/dbtree/articlejbbs.h b/src/dbtree/articlejbbs.h new file mode 100644 index 000000000..70b13795b --- /dev/null +++ b/src/dbtree/articlejbbs.h @@ -0,0 +1,38 @@ +// ライセンス: 最新のGPL + +// +// 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(); + + // 書き込みメッセージ変換 + virtual const std::string create_write_message( const std::string& name, const std::string& mail, const std::string& msg ); + + // bbscgi のURL + virtual const std::string url_bbscgi(); + + // subbbscgi のURL + virtual const std::string url_subbbscgi(); + + private: + + virtual NodeTreeBase* create_nodetree(); + }; +} + +#endif diff --git a/src/dbtree/articlemachi.cpp b/src/dbtree/articlemachi.cpp new file mode 100644 index 000000000..6afb4558e --- /dev/null +++ b/src/dbtree/articlemachi.cpp @@ -0,0 +1,94 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "articlemachi.h" +#include "nodetreemachi.h" +#include "interface.h" + +#include "jdlib/miscutil.h" +#include "jdlib/misctime.h" + +#include + +using namespace DBTREE; + +ArticleMachi::ArticleMachi( const std::string& datbase, const std::string& _id, bool cached ) + : ArticleBase( datbase, _id, cached ) +{ + assert( !id().empty() ); + + // Machi の場合は拡張子が無いので key = id + set_key( get_id() ); + + // key から since 計算 + set_since_time( atol( get_key().c_str() ) ); + set_since_date( MISC::timettostr( get_since_time() ) ); +} + + +ArticleMachi::~ArticleMachi() +{} + + +const 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" +// +// +const std::string ArticleMachi::url_bbscgi() +{ + std::string cgibase = DBTREE::url_bbscgibase( get_url() ); + return cgibase.substr( 0, cgibase.length() -1 ); // 最後の '/' を除く +} + + +// +// subbbscgi のURL +// +// (例) "http://www.machi.to/bbs/write.cgi" +// +const std::string ArticleMachi::url_subbbscgi() +{ + std::string cgibase = DBTREE::url_subbbscgibase( get_url() ); + return cgibase.substr( 0, cgibase.length() -1 ); // 最後の '/' を除く +} + + + +NodeTreeBase* ArticleMachi::create_nodetree() +{ +#ifdef _DEBUG + std::cout << "ArticleMachi::create_nodetree " << url() << std::endl; +#endif + + return new NodeTreeMachi( get_url(), get_date_modified() ); +} diff --git a/src/dbtree/articlemachi.h b/src/dbtree/articlemachi.h new file mode 100644 index 000000000..be600e7ef --- /dev/null +++ b/src/dbtree/articlemachi.h @@ -0,0 +1,38 @@ +// ライセンス: 最新のGPL + +// +// まち型スレ情報クラス +// + +#ifndef _ARTICLEMACHI_H +#define _ARTICLEMACHI_H_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(); + + // 書き込みメッセージ変換 + virtual const std::string create_write_message( const std::string& name, const std::string& mail, const std::string& msg ); + + // bbscgi のURL + virtual const std::string url_bbscgi(); + + // subbbscgi のURL + virtual const std::string url_subbbscgi(); + + private: + + virtual NodeTreeBase* create_nodetree(); + }; +} + +#endif diff --git a/src/dbtree/board2ch.cpp b/src/dbtree/board2ch.cpp new file mode 100644 index 000000000..511355ef5 --- /dev/null +++ b/src/dbtree/board2ch.cpp @@ -0,0 +1,154 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "board2ch.h" +#include "article2ch.h" +#include "settingloader.h" + +#include "config/globalconf.h" + +#include "httpcode.h" + +using namespace DBTREE; + + +Board2ch::Board2ch( const std::string& root, const std::string& path_board, const std::string& name ) + : Board2chCompati( root, path_board, name ) + , m_settingloader( 0 ) +{ +#ifdef _DEBUG + std::cout << "Board2ch::Board2ch\n"; +#endif +} + + +Board2ch::~Board2ch() +{ + if( m_settingloader ) delete m_settingloader; + m_settingloader = NULL; +} + + +const std::string& Board2ch::get_agent() +{ + return CONFIG::get_agent_for2ch(); +} + + +// +// ホスト別プロクシ +// +const std::string Board2ch::get_proxy_host() +{ + if( ! CONFIG::get_use_proxy_for2ch() ) return std::string(); + return CONFIG::get_proxy_for2ch(); +} + +const int Board2ch::get_proxy_port() +{ + return CONFIG::get_proxy_port_for2ch(); +} + + +const std::string Board2ch::get_proxy_host_w() +{ + if( ! CONFIG::get_use_proxy_for2ch_w() ) return std::string(); + return CONFIG::get_proxy_for2ch_w(); +} + +const int Board2ch::get_proxy_port_w() +{ + return CONFIG::get_proxy_port_for2ch_w(); +} + + +const std::string Board2ch::settingtxt() +{ + if( m_settingloader ){ + if( m_settingloader->is_loading() ) return "ロード中です"; + else if( m_settingloader->get_code() == HTTP_OK ) return m_settingloader->settingtxt(); + else return "ロードに失敗しました : " + m_settingloader->get_str_code(); + } + + return BoardBase::settingtxt(); +} + + +const std::string Board2ch::default_noname() +{ + if( m_settingloader + && m_settingloader->get_code() == HTTP_OK ) return m_settingloader->default_noname(); + + return BoardBase::default_noname(); +} + + +const int Board2ch::line_number() +{ + if( m_settingloader + && m_settingloader->get_code() == HTTP_OK ) return m_settingloader->line_number(); + + return BoardBase::line_number(); +} + + +const int Board2ch::message_count() +{ + if( m_settingloader + && m_settingloader->get_code() == HTTP_OK ) return m_settingloader->message_count(); + + return BoardBase::message_count(); +} + + + +// +// 新しくArticleBaseクラスを追加してそのポインタを返す +// +// cached : HDD にキャッシュがあるならtrue +// +ArticleBase* Board2ch::append_article( const std::string& id, bool cached ) +{ + if( empty() ) return get_article_null(); + + ArticleBase* article = new DBTREE::Article2ch( url_datbase(), id, cached ); + if( article ) get_list_article().push_back( article ); + else return get_article_null(); + + return article; +} + + + +// +// SETTING.TXTをキャッシュから読み込む +// +// BoardBase::read_info()で呼び出す +// +void Board2ch::load_setting() +{ +#ifdef _DEBUG + std::cout << "Board2ch::load_setting\n"; +#endif + + if( ! m_settingloader ) m_settingloader = new SettingLoader( url_boardbase() ); + m_settingloader->load_setting(); +} + + +// +// SETTING.TXTのサーバからダウンロード +// +// 読み込むタイミングはsubject.txtを読み終わった直後( BoardBase::receive_finish() ) +// +void Board2ch::download_setting() +{ +#ifdef _DEBUG + std::cout << "Board2ch::download_setting\n"; +#endif + + if( ! m_settingloader ) m_settingloader = new SettingLoader( url_boardbase() ); + m_settingloader->download_setting(); +} diff --git a/src/dbtree/board2ch.h b/src/dbtree/board2ch.h new file mode 100644 index 000000000..2be11f4b5 --- /dev/null +++ b/src/dbtree/board2ch.h @@ -0,0 +1,47 @@ +// ライセンス: 最新のGPL + +// +// 2ch +// + +#ifndef _BOARD2CH_H +#define _BOARD2CH_H + +#include "board2chcompati.h" + +namespace DBTREE +{ + class SettingLoader; + + class Board2ch : public Board2chCompati + { + SettingLoader* m_settingloader; + + public: + + Board2ch( const std::string& root, const std::string& path_board,const std::string& name ); + virtual ~Board2ch(); + + virtual const std::string& get_agent(); + + virtual const std::string get_proxy_host(); + virtual const int get_proxy_port(); + + virtual const std::string get_proxy_host_w(); + virtual const int get_proxy_port_w(); + + virtual const std::string settingtxt(); + virtual const std::string default_noname(); + virtual const int line_number(); + virtual const int message_count(); + + private: + + virtual ArticleBase* append_article( const std::string& id, bool cached ); + + virtual void load_setting(); + virtual void download_setting(); + }; +} + +#endif diff --git a/src/dbtree/board2chcompati.cpp b/src/dbtree/board2chcompati.cpp new file mode 100644 index 000000000..702e26a05 --- /dev/null +++ b/src/dbtree/board2chcompati.cpp @@ -0,0 +1,239 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "board2chcompati.h" +#include "article2chcompati.h" + +#include "jdlib/miscutil.h" +#include "jdlib/miscmsg.h" + +#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 ) + : 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 ) ); // 先頭の '/' を除く +#ifdef NOUSE_MS932 + set_charset( "CP932" ); +#else + set_charset( "MS932" ); +#endif +} + + + +// +// キャッシュのファイル名が正しいか +// +bool Board2chCompati::is_valid( const std::string& filename ) +{ + if( filename.length() != 10 + get_ext().length() ) return false; + if( filename.find( get_ext() ) == std::string::npos ) return false; + if( filename.length() - filename.rfind( get_ext() ) != get_ext().length() ) return false; + + unsigned int dig, n; + MISC::str_to_uint( filename.c_str(), dig, n ); + if( dig != n ) return false; + if( dig != 10 ) return false; + + return true; +} + + +// 新スレ作成時の書き込みメッセージ作成 +const std::string Board2chCompati::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(); + + std::stringstream ss_post; + ss_post.clear(); + ss_post << "bbs=" << get_id() + << "&subject=" << MISC::charset_url_encode( subject, get_charset() ); + + // 2chのhana値 + std::string hana = hana_for_write(); + if( ! hana.empty() ) ss_post << "&hana=" << hana; + + ss_post << "&time=" << time_modified() + << "&submit=" << MISC::charset_url_encode( "新規スレッド作成", 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() ); + +#ifdef _DEBUG + std::cout << "Board2chCompati::create_newarticle_message " << ss_post.str() << std::endl; +#endif + + return ss_post.str(); +} + + + +// +// 新スレ作成時のbbscgi のURL +// +// (例) "http://www.hoge2ch.net/test/bbs.cgi" +// +// +const std::string Board2chCompati::url_bbscgi_new() +{ + std::string cgibase = url_bbscgibase(); + return cgibase.substr( 0, cgibase.length() -1 ); // 最後の '/' を除く +} + + +// +// 新スレ作成時のsubbbscgi のURL +// +// (例) "http://www.hoge2ch.net/test/subbbs.cgi" +// +const std::string Board2chCompati::url_subbbscgi_new() +{ + std::string cgibase = url_subbbscgibase(); + return cgibase.substr( 0, cgibase.length() -1 ); // 最後の '/' を除く +} + + + +// +// 新しくArticleBaseクラスを追加してそのポインタを返す +// +// cached : HDD にキャッシュがあるならtrue +// +ArticleBase* Board2chCompati::append_article( const std::string& id, bool cached ) +{ + if( empty() ) return get_article_null(); + + ArticleBase* article = new DBTREE::Article2chCompati( url_datbase(), id, cached ); + if( article ) get_list_article().push_back( article ); + 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 int max_subject = 512; + const char* pos = str_subject_txt; + char str_tmp[ max_subject ]; + + while( *pos != '\0' ){ + + const char* str_id_dat; + int lng_id_dat = 0; + const char* str_subject; + int lng_subject = 0; + char str_num[ 16 ]; + + // datのID取得 + 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 += 2; + 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 = MIN( ( int )( pos - str_subject ), max_subject ); + + // レス数取得 + ++pos; + int i = 0; + while( *pos != ')' && *pos != '\0' && *pos != '\n' && i < 16 ) str_num[ i++ ] = *( pos++ ); + + // 壊れてる + if( *pos == '\0' ) break; + if( *pos == '\n' ) { ++pos; continue; } + + str_num[ i ] = '\0'; + ++pos; + + // id, subject, number 取得 + memcpy( str_tmp, str_id_dat, lng_id_dat ); + str_tmp[ lng_id_dat ] = '\0'; + std::string id = MISC::remove_space( str_tmp ); + + memcpy( str_tmp, str_subject, lng_subject ); + str_tmp[ lng_subject ] = '\0'; + std::string subject = MISC::remove_space( str_tmp ); + subject = MISC::replace_str( subject, "<", "<" ); + subject = MISC::replace_str( subject, ">", ">" ); + + int number = atol( str_num ); + +#ifdef _DEBUG + std::cout << pos - str_subject_txt << " " << lng_subject << " id = " << id << " num = " << number; + std::cout << " : " << subject << std::endl; +#endif + + // DBに登録されてるならarticle クラスの情報更新 + ArticleBase* article = get_article( id ); + + // DBにないなら新規に article クラスを追加 + // + // なおRoot::get_board()、BoardBase::read_info()経由で BoardBase::append_all_article() が既に呼ばれているので + // DBに無いということはキャッシュにも無いということ。よって append_article()で cached = false + + if( article->empty() ) article = append_article( id, + false // キャッシュ無し + ); + + // スレ情報更新 + if( article ){ + + // 情報ファイル読み込み + article->read_info(); + + // infoファイルが無いバアイモるのでsubject.txtから取得したサブジェクト、レス数を指定しておく + article->set_subject( subject ); + article->set_number( number ); + + // boardビューに表示するリスト更新 + article->set_current( true ); + get_list_subject().push_back( article ); + } + } +} diff --git a/src/dbtree/board2chcompati.h b/src/dbtree/board2chcompati.h new file mode 100644 index 000000000..cff1b590e --- /dev/null +++ b/src/dbtree/board2chcompati.h @@ -0,0 +1,39 @@ + +// ライセンス: 最新のGPL + +// +// 2ch 互換型板 +// + +#ifndef _BOARD2CHCOMPATI_H +#define _BOARD2CHCOMPATI_H + +#include "boardbase.h" + +namespace DBTREE +{ + class Board2chCompati : public BoardBase + { + public: + + Board2chCompati( const std::string& root, const std::string& path_board, const std::string& name ); + + // 新スレ作成用のメッセージ変換 + virtual const std::string create_newarticle_message( const std::string& subject, + const std::string& name, const std::string& mail, const std::string& msg ); + // 新スレ作成用のbbscgi のURL + virtual const std::string url_bbscgi_new(); + + // 新スレ作成用のsubbbscgi のURL + virtual const std::string url_subbbscgi_new(); + + private: + + virtual bool is_valid( const std::string& filename ); + + virtual ArticleBase* append_article( const std::string& id, bool cached ); + virtual void parse_subject( const char* str_subject_txt ); + }; +} + +#endif diff --git a/src/dbtree/boardbase.cpp b/src/dbtree/boardbase.cpp new file mode 100644 index 000000000..c23ee1e9b --- /dev/null +++ b/src/dbtree/boardbase.cpp @@ -0,0 +1,959 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "root.h" +#include "boardbase.h" +#include "articlebase.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 + +#define 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_root( root ) + , m_path_board( path_board ) + , m_name( name ) + , m_rawdata( 0 ) + , m_read_info( 0 ) + , m_article_null( 0 ) +{ + clear(); + clear_load_data(); +} + + +// +// デストラクタで子ArticleBaseクラスを全部削除 +// +BoardBase::~BoardBase() +{ +#ifdef _DEBUG + if( m_list_article.size() ) std::cout << "BoardBase::~BoardBase : " << url_boardbase() << std::endl; +#endif + + clear(); + + std::list< ArticleBase* >::iterator it; + for( it = m_list_article.begin(); it != m_list_article.end(); ++it ) delete ( *it ); + + if( m_article_null ) delete m_article_null; +} + + +ArticleBase* BoardBase::get_article_null() +{ + if( ! m_article_null ) m_article_null = new DBTREE::ArticleBase( "", "", false ); + return m_article_null; +} + + +bool BoardBase::empty() +{ + return m_root.empty(); +} + + +// +// url がこの板のものかどうか +// +bool BoardBase::equal( const std::string& url ) +{ + if( url.find( get_root() ) == 0 + && url.find( get_path_board() + "/" ) != std::string::npos ) return true; + + return false; +} + + +// user agent +const std::string& BoardBase::get_agent() +{ + return CONFIG::get_agent_for_data(); +} + + +// プロキシ +const std::string BoardBase::get_proxy_host() +{ + if( ! CONFIG::get_use_proxy_for_data() ) return std::string(); + return CONFIG::get_proxy_for_data(); +} + +const int BoardBase::get_proxy_port() +{ + return CONFIG::get_proxy_port_for_data(); +} + +// 書き込み用プロキシ +const std::string BoardBase::get_proxy_host_w() +{ + return get_proxy_host(); +} + +const int BoardBase::get_proxy_port_w() +{ + return get_proxy_port(); +} + + +// setting.txt +const std::string BoardBase::settingtxt() +{ + return "利用できません"; +} + +// デフォルトの名無し名 +const std::string BoardBase::default_noname() +{ + return "???"; +} + + +// 最大改行数/2 +const int BoardBase::line_number() +{ + return 0; +} + + +// 最大書き込みバイト数 +const int BoardBase::message_count() +{ + return 0; +} + + +void BoardBase::clear() +{ + if( m_rawdata ) free( m_rawdata ); + m_rawdata = NULL; + m_lng_rawdata = 0; + + m_get_article_url = std::string(); +} + + + +// +// 新しくArticleBaseクラスを追加してそのポインタを返す +// +ArticleBase* BoardBase::append_article( const std::string& id, bool cached ) +{ + // ベースクラスでは何もしない + return get_article_null(); +} + + + +// +// 板情報の取得 +// +// コンストラクタで呼ぶと起動時に全ての板の情報を読まなければならなくなるので、 +// 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(); + + // キャッシュにあるレスをデータベースに登録 + append_all_article(); + + // キャッシュからSETTING.TXT のロード + load_setting(); + } +} + + + +// +// 移転などで板のルートを変更する +// +void BoardBase::update_root( const std::string& root ) +{ + m_root = root; + + // 配下の ArticleBase にも知らせてあげる + std::list< ArticleBase* >::iterator it; + for( it = m_list_article.begin(); it != m_list_article.end(); ++it ) ( *it )->update_url( url_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 +// +const std::string BoardBase::url_dat( const std::string& url, int& num_from, int& num_to ) +{ + if( empty() ) return std::string(); + + JDLIB::Regex regex; + std::string id; // スレッドのID + +#ifdef _DEBUG + std::cout << "BoardBase::::url_dat : url = " << url << std::endl; +#endif + + num_from = num_to = 0; + + // dat 型 + std::string datpath = MISC::replace_str( url_datpath(), "?", "\\?" ); + std::string query_dat = "^ *(http://.+" + datpath + ")([1234567890]+" + get_ext() + ") *$"; + + // read.cgi型 + std::string cgipath = MISC::replace_str( url_readcgipath(), "?", "\\?" ); + std::string query_cgi = "^ *(http://.+" + cgipath + ")([1234567890]+)/?r?(l50)?([1234567890]+)?(-)?([1234567890]+)? *$"; + +#ifdef _DEBUG + std::cout << "query_dat = " << query_dat << std::endl; + std::cout << "query_cgi = " << query_cgi << std::endl; +#endif + + if( regex.exec( query_dat , url ) ) id = regex.str( 2 ); + + else if( regex.exec( query_cgi , url ) ){ + + id = regex.str( 2 ) + get_ext(); + + if( !regex.str( 3 ).empty() ){ // 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.str( 5 ).empty() && !num_to ) num_to = MAX_RESNUMBER + 1; + } + + // -15 みたいな場合 + else if( num_to != 0 ) num_from = 1; + + num_to = MAX( num_from, num_to ); + } + + // どちらでもない(スレのURLでない)場合 + else return std::string(); + +#ifdef _DEBUG + std::cout << "BoardBase::url_dat result : " << url << " ->\n"; + std::cout << "datbase = " << url_datbase() << " id = " << id << " from " << num_from << " to " << num_to << std::endl; +#endif + + // もしurl(スレッドのURL)が移転前の旧URLのものだったら対応するarticlebaseクラスに旧ホスト名を教えてあげる + if( m_root.find( MISC::get_hostname( url ) ) != 0 ){ +#ifdef _DEBUG + std::cout << "org_host : " << MISC::get_hostname( url ) << std::endl; +#endif + get_article_create( id )->set_org_host( MISC::get_hostname( url ) ); + } + + return url_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" +// +const 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(); + + 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" +// +const std::string BoardBase::url_subject() +{ + if( empty() ) return std::string(); + + return url_boardbase() + m_subjecttxt; +} + + + +// +// ルートアドレス +// +// (例) "http://www.hoge2ch.net/" (最後に '/' がつく) +// +const std::string BoardBase::url_root() +{ + if( empty() ) return std::string(); + + return m_root + "/"; +} + + +// +// 板のベースアドレス +// +// (例) "http://www.hoge2ch.net/hogeboard/" (最後に '/' がつく) +// +const std::string BoardBase::url_boardbase() +{ + if( empty() ) return std::string(); + + return m_root + m_path_board + "/"; +} + + +// +// dat ファイルのURLのベースアドレスを返す +// +// (例) "http://www.hoge2ch.net/hogeboard/dat/" (最後に '/' がつく) +// +const std::string BoardBase::url_datbase() +{ + if( empty() ) return std::string(); + + return m_root + url_datpath(); +} + + +// +// dat ファイルのURLのパスを返す +// +// (例) "/hogeboreadcgipath(ard/dat/" (最初と最後に '/' がつく) +// +const std::string BoardBase::url_datpath() +{ + if( empty() ) return std::string(); + + return m_path_board + m_path_dat + "/"; +} + + + +// +// read.cgi のURLのベースアドレスを返す +// +// (例) "http://www.hoge2ch.net/test/read.cgi/hogeboard/" (最後に '/' がつく) +// +const std::string BoardBase::url_readcgibase() +{ + if( empty() ) return std::string(); + + return m_root + url_readcgipath(); +} + + +// +// read.cgi のURLのパスを返す +// +// (例) "/test/read.cgi/hogeboard/" (最初と最後に '/' がつく) +// +const std::string BoardBase::url_readcgipath() +{ + if( empty() ) return std::string(); + + return m_path_readcgi + m_path_board + "/"; +} + + +// +// bbscgi のURLのベースアドレス +// +// (例) "http://www.hoge2ch.net/test/bbs.cgi/" ( 最後に '/' がつく ) +// +// +const std::string BoardBase::url_bbscgibase() +{ + if( empty() ) return std::string(); + + return m_root + m_path_bbscgi + "/"; +} + + +// +// subbbscgi のURLのベースアドレス +// +// (例) "http://www.hoge2ch.net/test/subbbs.cgi/" ( 最後に '/' がつく ) +// +const std::string BoardBase::url_subbbscgibase() +{ + if( empty() ) return std::string(); + + return m_root + m_path_subbbscgi + "/"; +} + + + +// +// article のIDから m_list_article から article のポインタを検索して返すだけ +// +// 無ければNULLクラスを返す +// +ArticleBase* BoardBase::get_article( const std::string id ) +{ + if( id.empty() ) return get_article_null(); + + // 線形リストなので遅い + // TODO : ハッシュにする + std::list< ArticleBase* >::iterator it; + for( it = m_list_article.begin(); it != m_list_article.end(); ++it ){ + + ArticleBase* art = *( it ); + if( art->get_id() == id ) return art; + } + + return get_article_null(); +} + + + +// +// m_list_article から article のポインタを検索して返す +// +// ポインタがあった場合は情報ファイルを読み込む +// さらにデータベースにArticleBaseクラスが登録されてない場合はクラスを作成して登録する +// +ArticleBase* BoardBase::get_article_create( const std::string id ) +{ +#ifdef _DEBUG + std::cout << "BoardBase::get_article_create id = " << id << std::endl; +#endif + + ArticleBase* art = get_article( id ); + + if( ! art->empty() ){ + +#ifdef _DEBUG + std::cout << "found id = " << art->id() << std::endl; +#endif + // ポインタを返す前にスレッドの情報ファイルを読み込み + art->read_info(); + } + else{ + + // データベースに無いので新規登録 + // + // なおRoot::get_board() 経由で呼ばれる BoardBase::read_info() でキャッシュにある + // スレは全てDBに登録されているので DBに無いということはキャッシュにも無いということ。 + // よって append_article()に cached = false をパラメータとして渡す + +#ifdef _DEBUG + std::cout << "BoardBase::get_article_create : append_article id = " << id << std::endl; +#endif + art = append_article( id, + false // キャッシュ無し + ); + } + + return art; +} + + + + +// +// article の URL を渡して m_list_article から article のポインタを検索して返す +// +// さらにデータベースにArticleBaseクラスが登録されてない場合はクラスを作成して登録する +// +ArticleBase* BoardBase::get_article_fromURL( const std::string url ) +{ + if( empty() ) return get_article_null(); + + // キャッシュ + if( url == m_get_article_url ) 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 + + // urlをdat型に変換してからID取得 + // 変換できないなら板がDBに登録されてないってことなので NULL クラスを返す + int num_from, num_to; + std::string urldat = url_dat( url, num_from, num_to ); + if( urldat.empty() ){ +#ifdef _DEBUG + std::cout << "could not convert url to daturl\nreturn Article_Null\n"; +#endif + return m_get_article; + } + + // id を抜き出して article クラスのポインタ取得 + std::string id = urldat.substr( url_datbase().length() ); + +#ifdef _DEBUG + std::cout << "urldat = " << urldat << std::endl + << "url_datbase = " << url_datbase() << std::endl + << "id = " << id << std::endl; + if( id.empty() ) std::cout << "return Article_Null\n"; +#endif + + if( id.empty() ) return m_get_article; + + m_get_article = get_article_create( id ); + return m_get_article; +} + + + +// +// subject.txt ダウンロード +// +void BoardBase::download_subject() +{ +#ifdef _DEBUG + std::cout << "BoardBase::download_subject() " << url_boardbase() << std::endl; +#endif + + if( empty() ) return; + if( is_loading() ) return; + + clear(); + m_rawdata = ( char* )malloc( SIZE_OF_RAWDATA ); + + // オフライン + if( ! SESSION::is_online() ){ + + set_str_code( "" ); + + // ディスパッチャ経由でreceive_finish()を呼ぶ + finish(); + return; + } + + // オンライン + + // subject.txtのキャッシュが無かったら modified をリセット + std::string path_subject = CACHE::path_board_root( url_boardbase() ) + m_subjecttxt; + if( CACHE::is_file_exists( path_subject ) != CACHE::EXIST_FILE ) set_date_modified( std::string() ); + + JDLIB::LOADERDATA data; + create_loaderdata( data ); + if( ! start_load( data ) ){ + CORE::core_set_command( "update_board", url_subject() ); + 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.size_buf = CONFIG::get_loader_bufsize(); + data.timeout = CONFIG::get_loader_timeout(); + if( ! date_modified().empty() ) data.modified = date_modified(); +} + + +// +// ローダよりsubject.txt受信 +// +void BoardBase::receive_data( const char* data, size_t size ) +{ + memcpy( m_rawdata + m_lng_rawdata , data, size ); + m_lng_rawdata += size; +} + + +// +// ロード完了 +// +void BoardBase::receive_finish() +{ + // 別スレッドでSETTING.TXT のダウンロード開始 + download_setting(); + + bool read_from_cache = false; + std::string path_subject = CACHE::path_board_root( url_boardbase() ) + m_subjecttxt; + std::string path_oldsubject = CACHE::path_board_root( url_boardbase() ) + "old-" + m_subjecttxt; + +#ifdef _DEBUG + std::cout << "BoardBase::receive_finish code = " << str_code() << std::endl; +#endif + + // エラー or 移転 + if( get_code() != HTTP_ERR + && get_code() != HTTP_OK + && get_code() != HTTP_NOT_MODIFIED ){ + + m_lng_rawdata = 0; + + // 移転した場合 + if( get_code() == HTTP_REDIRECT ){ + + Gtk::MessageDialog mdiag( "移転しました\n\n板リストを更新しますか?", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_OK_CANCEL ); + if( mdiag.run() == Gtk::RESPONSE_OK ) CORE::core_set_command( "reload_bbsmenu" ); + } + + // エラー + else read_from_cache = true; + } + + // not modified か offline ならキャッシュからデータをロード + if( get_code() == HTTP_ERR + || get_code() == HTTP_NOT_MODIFIED + || ! SESSION::is_online() ) read_from_cache = true; + + // キャッシュから読み込み + if( read_from_cache ){ + + m_lng_rawdata = CACHE::load_rawdata( path_subject, m_rawdata, SIZE_OF_RAWDATA ); + +#ifdef _DEBUG + std::cout << "BoardBase::receive_finish read cache " << path_subject << std::endl; + std::cout << "size = " << m_lng_rawdata << std::endl; +#endif + } + + if( m_lng_rawdata == 0 ){ + + CORE::core_set_command( "update_board", url_subject() ); + clear(); + return; + } + + // subject.txt をキャッシュに保存 + if( SESSION::is_online() && get_code() == HTTP_OK ){ + +#ifdef _DEBUG + std::cout << "rename " << path_subject << " " << path_oldsubject << std::endl; + std::cout << "save " << path_subject << std::endl; +#endif + + // subject.txtをキャッシュ + if( CACHE::mkdir_boardroot( url_boardbase() ) ){ + + // 古いファイルをrename + if( CACHE::is_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_lng_rawdata ); + } + } + + // UTF-8に変換しておく + JDLIB::Iconv* libiconv = new JDLIB::Iconv( m_charset ); + int byte_out; + const char* rawdata_utf8 = libiconv->convert( m_rawdata , m_lng_rawdata, byte_out ); + + + ////////////////////////////// + // データベース更新 + + m_list_subject.clear(); + + // subject.txtを解析して現行スレだけリストに加える + std::list< ArticleBase* >::iterator it; + for( it = m_list_article.begin(); it != m_list_article.end(); ++it ) ( *it )->set_current( false ); + + // subject.txtをパースしながらデータベース更新 + parse_subject( rawdata_utf8 ); + + // DAT落ちなどでsubject.txtに無いスレもリストに加える + // サブジェクトやロード数などの情報が無いのでスレのinfoファイルから + // 取得する必要がある + if( CONFIG::get_show_oldarticle() ){ + for( it = m_list_article.begin(); it != m_list_article.end(); ++it ){ + if( ( *it )->is_cached() && ! ( *it )->is_current() ){ + + // info 読み込み + // TODO : 数が多いとboardビューを開くまで時間がかかるのをなんとかする +#ifdef _DEBUG + std::cout << "read article_info << " << ( *it )->url() << std::endl; +#endif + ( *it )->read_info(); + m_list_subject.push_back( *it ); + } + } + } + // コアにデータベース更新を知らせる + CORE::core_set_command( "update_board", url_subject() ); + + delete libiconv; + clear(); +} + + + +// +// キャッシュのディレクトリ内にあるスレのファイル名を取得してDBにすべてを登録 +// +// 全てのスレのinfoファイルを読むと遅くなるのでこの段階では読まない(登録のみ) +// +// boardビューに一覧表示したり、BoardBase::get_article_fromURL()で参照された段階で初めて +// スレのinfoファイルを読み込む +// +void BoardBase::append_all_article() +{ +#ifdef _DEBUG + std::cout << "BoardBase::append_all_article\n"; +#endif + + std::list< std::string >list_file; + std::string path_board_root = CACHE::path_board_root( url_boardbase() ); + list_file = CACHE::get_filelist( path_board_root ); + + std::list< std::string >::iterator it = list_file.begin(); + for(; it != list_file.end(); ++it ){ + + std::string& file = ( *it ); + if( is_valid( file ) ){ + +#ifdef _DEBUG + std::cout << "append id = " << file << std::endl; +#endif + append_article( file, + true // キャッシュあり + ); + } + } +} + + + +// +// board.info( jd用 ) を読む +// BoardBase::read_info() も参照すること +// +void BoardBase::read_board_info() +{ +#ifdef _DEBUG + std::cout << "BoardBase::read_board_info " << m_id << std::endl; +#endif + + if( empty() ) return; + + std::string path_info = CACHE::path_jdboard_info( url_boardbase() ); + + JDLIB::ConfLoader cf( path_info, std::string() ); + + std::string modified = cf.get_option( "modified", "" ); + set_date_modified( modified ); + + m_view_sort_column = cf.get_option( "view_sort_column", -1 ); + m_view_sort_ascend = cf.get_option( "view_sort_ascend", false ); + +#ifdef _DEBUG + std::cout << "modified = " << 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::ostringstream sstr; + sstr << "modified = " << date_modified() << std::endl + << "view_sort_column = " << m_view_sort_column << std::endl + << "view_sort_ascend = " << m_view_sort_ascend << 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 << "("; + + std::list< ArticleBase* >::iterator it; + for( it = m_list_subject.begin(); it != m_list_subject.end(); ++it ){ + + ArticleBase* art = *( it ); + if( art->is_cached() && art->is_current() ){ + 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 . \"" + date_modified() + "\")"; + std::string logo = "nil"; + + // board.info 読み込み + if( CACHE::is_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 + + std::list< std::string > lists = MISC::get_elisp_lists( str_info ); + std::list< std::string >::iterator it = lists.begin(); + bookmark = *( it++ ); + hide = *( it++ ); + ++it; + logo = *( it++ ); + } + + 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 ); +} diff --git a/src/dbtree/boardbase.h b/src/dbtree/boardbase.h new file mode 100644 index 000000000..7170ed272 --- /dev/null +++ b/src/dbtree/boardbase.h @@ -0,0 +1,254 @@ +// ライセンス: 最新のGPL +// +// 板情報クラスのベース +// + +#ifndef _BOARDBASE_H +#define _BOARDBASE_H + +#include "skeleton/loadable.h" + +#include +#include + +namespace JDLIB +{ + class LOADERDATA; +} + + +namespace DBTREE +{ + class Root; + class ArticleBase; + + class BoardBase : public SKELETON::Loadable + { + // ArticleBaseクラス のキャッシュ + // ArticleBaseクラスは一度作ったら~BoardBase()以外ではdeleteしないこと + std::list< ArticleBase* > m_list_article; + + // subject.txt から作ったArticleBaseクラスのポインタのリスト + // subject.txt と同じ順番で、ロードされるたびに更新される + std::list< ArticleBase* > m_list_subject; + + // ビュワーでソートをする列番号、ソード順 + int m_view_sort_column; + bool m_view_sort_ascend; + + // + // 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; // 板名 + + char* m_rawdata; + int m_lng_rawdata; + bool m_read_info; + + std::string m_cookie_for_write; // クッキー、書き込みの時に必要 + std::string m_hana_for_write; // hana, 2ch書き込み時に必要 + + // get_article_fromURL()のキャッシュ + std::string m_get_article_url; + ArticleBase* m_get_article; + + // NULL artice クラス + ArticleBase* m_article_null; + + protected: + + std::list< ArticleBase* >& get_list_article(){ return m_list_article; } + + ArticleBase* get_article_null(); + + 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; } + + public: + + BoardBase( const std::string& root, const std::string& path_board, const std::string& name ); + virtual ~BoardBase(); + bool empty(); + + // boardviewに表示するスレッドのリストを取得 + std::list< ArticleBase* >& get_list_subject(){ return m_list_subject; } + + // boardviewでソートする列番号とソート順 + const int get_view_sort_column() const { return m_view_sort_column; } + void set_view_sort_column( int column ){ m_view_sort_column = column; } + const bool get_view_sort_ascend() const { return m_view_sort_ascend; } + void set_view_sort_ascend( bool ascend ){ m_view_sort_ascend = ascend; } + + + // url がこの板のものかどうか + virtual bool equal( const std::string& url ); + + // 移転などで板のルートを変更する + void update_root( const std::string& root ); + + 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; } + const std::string& get_subjecttxt() const { return m_subjecttxt; } + + // ダウンロード、書き込み時のエージェント名やプロキシ + virtual const std::string& get_agent(); + virtual const std::string get_proxy_host(); + virtual const int get_proxy_port(); + virtual const std::string get_proxy_host_w(); + virtual const int get_proxy_port_w(); + + // SETTING.TXT + virtual const std::string settingtxt(); + + // 書き込みの時のデフォルト名 + virtual const std::string default_noname(); + + // 最大改行数/2 + virtual const int line_number(); + + // 最大書き込みバイト数 + virtual const int message_count(); + + // 書き込み用クッキー + const std::string& cookie_for_write() { return m_cookie_for_write; } + void set_cookie_for_write( const std::string& cookie ){ m_cookie_for_write = cookie; } + + // hana + const std::string& hana_for_write() { return m_hana_for_write; } + void set_hana_for_write( const std::string& hana ){ m_hana_for_write = hana; } + + // スレの 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 + virtual const std::string url_dat( const std::string& url, int& num_from, int& num_to ); + + // スレの 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 const std::string url_readcgi( const std::string& url, int num_from, int num_to ); + + // subject.txt の URLを取得 + // (例) "http://www.hoge2ch.net/hogeboard/subject.txt" + const std::string url_subject(); + + // ルートアドレス + // (例) "http://www.hoge2ch.net/hogeboard/" なら "http://www.hoge2ch.net/" + const std::string url_root(); + + // 板のベースアドレス + // (例) "http://www.hoge2ch.net/hogeboard/" + const std::string url_boardbase(); + + // dat ファイルのURLのベースアドレスを返す + // (例) "http://www.hoge2ch.net/hogeboard/dat/12345.dat" なら "http://www.hoge2ch.net/hogeboard/dat/" + const std::string url_datbase(); + + // dat ファイルのURLのパスを返す + // (例) "http://www.hoge2ch.net/hogeboard/dat/12345.dat" なら "/hogeboard/dat/" + virtual const std::string url_datpath(); + + // read.cgi のURLのベースアドレスを返す + // (例) "http://www.hoge2ch.net/test/read.cgi/hogeboard/12345" なら "http://www.hoge2ch.net/test/read.cgi/hogeboard/" + const std::string url_readcgibase(); + + // read.cgi のURLのパスを返す + // (例) "http://www.hoge2ch.net/test/read.cgi/hogeboard/12345" なら "/test/read.cgi/hogeboard/" + virtual const std::string url_readcgipath(); + + // bbscgi のURLのベースアドレス + const std::string url_bbscgibase(); + + // subbbscgi のURLのベースアドレス + const std::string url_subbbscgibase(); + + // article クラスのポインタ取得 + // それぞれの違いはソースのコメントを参照 + ArticleBase* get_article( const std::string id ); + ArticleBase* get_article_create( const std::string id ); + ArticleBase* get_article_fromURL( const std::string url ); + + void download_subject(); + + // 新スレ作成用のメッセージ変換 + virtual const std::string create_newarticle_message( const std::string& subject, + const std::string& name, const std::string& mail, const std::string& msg ) + { + return std::string(); + } + + // 新スレ作成用のbbscgi のURL + virtual const std::string url_bbscgi_new() { return std::string(); } + + // 新スレ作成用のsubbbscgi のURL + virtual const std::string url_subbbscgi_new() { return std::string(); } + + void read_info(); + void save_info(); + + private: + + void clear(); + + // キャッシュのファイル名が正しいかどうか + virtual bool is_valid( const std::string& filename ){ return false; } + + virtual void create_loaderdata( JDLIB::LOADERDATA& data ); + virtual void receive_data( const char* data, size_t size ); + virtual void receive_finish(); + + virtual ArticleBase* append_article( const std::string& id, bool cached ); + virtual void parse_subject( const char* str_subject_txt ){} + + void read_board_info(); + void append_all_article(); + + void save_summary(); + void save_board_info(); + void save_jdboard_info(); + + // setting.txtの読み込み及びダウンロード + virtual void load_setting(){} + virtual void download_setting(){} + }; +} + +#endif diff --git a/src/dbtree/boardfactory.cpp b/src/dbtree/boardfactory.cpp new file mode 100644 index 000000000..5c634e200 --- /dev/null +++ b/src/dbtree/boardfactory.cpp @@ -0,0 +1,25 @@ +// ライセンス: 最新のGPL + +#include "boardfactory.h" +#include "board2ch.h" +#include "board2chcompati.h" +#include "boardjbbs.h" +#include "boardmachi.h" + +#include "global.h" + +DBTREE::BoardBase* DBTREE::BoardFactory( int type, const std::string& root, const std::string& path_board, const std::string& name ) +{ + switch( type ) + { + case TYPE_BOARD_2CH: return new DBTREE::Board2ch( root, path_board, name ); + + case TYPE_BOARD_2CH_COMPATI: return new DBTREE::Board2chCompati( root, path_board, name ); + + case TYPE_BOARD_JBBS:return new DBTREE::BoardJBBS( root, path_board, name ); + + case TYPE_BOARD_MACHI:return new DBTREE::BoardMachi( root, path_board, name ); + + default: return NULL; + } +} diff --git a/src/dbtree/boardfactory.h b/src/dbtree/boardfactory.h new file mode 100644 index 000000000..4ce7d2e8c --- /dev/null +++ b/src/dbtree/boardfactory.h @@ -0,0 +1,20 @@ +// ライセンス: 最新のGPL + +// +// BoardBaseのファクトリー +// + +#ifndef _BOARDFACTORY_H +#define _BOARDFACTORY_H + +#include + +namespace DBTREE +{ + class BoardBase; + + DBTREE::BoardBase* BoardFactory( int type, const std::string& root, const std::string& path_board, const std::string& name ); +} + + +#endif diff --git a/src/dbtree/boardjbbs.cpp b/src/dbtree/boardjbbs.cpp new file mode 100644 index 000000000..68d856335 --- /dev/null +++ b/src/dbtree/boardjbbs.cpp @@ -0,0 +1,242 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "boardjbbs.h" +#include "articlejbbs.h" + +#include "jdlib/miscutil.h" +#include "jdlib/miscmsg.h" +#include "jdlib/loaderdata.h" + +#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( "EUC-JP" ); +} + + +// +// キャッシュのファイル名が正しいか +// +bool BoardJBBS::is_valid( const std::string& filename ) +{ + if( filename.length() != 10 ) return false; + + unsigned int dig, n; + MISC::str_to_uint( filename.c_str(), dig, n ); + if( dig != n ) return false; + if( dig != 10 ) return false; + + return true; +} + + + + +// +// 新しくArticleBaseクラスを追加してそのポインタを返す +// +// cached : HDD にキャッシュがあるならtrue +// +ArticleBase* BoardJBBS::append_article( const std::string& id, bool cached ) +{ + if( empty() ) return get_article_null(); + + ArticleBase* article = new DBTREE::ArticleJBBS( url_datbase(), id, cached ); + if( article ) get_list_article().push_back( article ); + else return get_article_null(); + + return article; +} + + + +// +// rawmode のURLのパスを返す +// +// (例) "/bbs/rawmode.cgi/board/" (最初と最後に '/' がつく) +// +const std::string BoardJBBS::url_datpath() +{ + if( empty() ) return std::string(); + + return "/bbs/rawmode.cgi" + get_path_board() + "/"; +} + + + +const 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=" << 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.livedoor.jp/bbs/write.cgi/computer/123/new/" +// +// +const std::string BoardJBBS::url_bbscgi_new() +{ + return url_bbscgibase() + get_id() + "/new/"; +} + + +// +// 新スレ作成時のsubbbscgi のURL +// +// (例) "http://jbbs.livedoor.jp/bbs/write.cgi/computer/123/new/" +// +const std::string BoardJBBS::url_subbbscgi_new() +{ + return url_subbbscgibase() + get_id() + "/new/"; +} + + + +// +// 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; + char str_tmp[ 1024 ]; + + ArticleBase* article_first = NULL; + + while( *pos != '\0' ){ + + const char* str_id_dat; + int lng_id_dat = 0; + const char* str_subject; + int lng_subject = 0; + char str_num[ 16 ]; + + // 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 ); + + // レス数取得 + ++pos; + int i = 0; + while( *pos != ')' && *pos != '\0' && *pos != '\n' && i < 16 ) str_num[ i++ ] = *( pos++ ); + + // 壊れてる + if( *pos == '\0' ) break; + if( *pos == '\n' ) { ++pos; continue; } + + str_num[ i ] = '\0'; + ++pos; + + // id, subject, number 取得 + memcpy( str_tmp, str_id_dat, lng_id_dat ); + str_tmp[ lng_id_dat ] = '\0'; + std::string id = MISC::remove_space( str_tmp ); + + memcpy( str_tmp, str_subject, lng_subject ); + str_tmp[ lng_subject ] = '\0'; + std::string subject = MISC::remove_space( str_tmp ); + subject = MISC::replace_str( subject, "<", "<" ); + subject = MISC::replace_str( subject, ">", ">" ); + + int number = atol( str_num ); + +#ifdef _DEBUG + std::cout << id << " " << number << " " << subject << std::endl; +#endif + + // DBに登録されてるならarticle クラスの情報更新 + ArticleBase* article = get_article( id ); + + // DBにないなら新規に article クラスを追加 + // + // なおRoot::get_board() 経由で void BoardBase::read_info()及び + // BoardBase::read_all_article_info() が既に呼ばれているので + // DBに無いということはキャッシュにも無いということ。よって append_article()で cached = false + + if( article->empty() ) article = append_article( id, false ); + + // スレ情報更新 + if( article ){ + article->read_info(); + article->set_subject( subject ); + article->set_number( number ); + + // boardビューに表示するリスト更新 + // JBBSは最初と最後の行が同じになる仕様があるので最後の行を除く + bool pushback = true; + if( ! article_first ) article_first = article; + else if( article == article_first ) pushback = false; + + if( pushback ){ + article->set_current( true ); + get_list_subject().push_back( article ); + } + } + } +} diff --git a/src/dbtree/boardjbbs.h b/src/dbtree/boardjbbs.h new file mode 100644 index 000000000..f3a14191e --- /dev/null +++ b/src/dbtree/boardjbbs.h @@ -0,0 +1,39 @@ +// ライセンス: 最新のGPL + +// +// JBBS 型板 +// + +#ifndef _BOARDJBBS_H +#define _BOARDJBBS_H + +#include "boardbase.h" + +namespace DBTREE +{ + class BoardJBBS : public BoardBase + { + public: + + BoardJBBS( const std::string& root, const std::string& path_board,const std::string& name ); + + virtual const std::string url_datpath(); + + // 新スレ作成用のメッセージ変換 + virtual const std::string create_newarticle_message( const std::string& subject, + const std::string& name, const std::string& mail, const std::string& msg ); + // 新スレ作成用のbbscgi のURL + virtual const std::string url_bbscgi_new(); + + // 新スレ作成用のsubbbscgi のURL + virtual const std::string url_subbbscgi_new(); + + private: + + virtual bool is_valid( const std::string& filename ); + virtual ArticleBase* append_article( const std::string& id, bool cached ); + virtual void parse_subject( const char* str_subject_txt ); + }; +} + +#endif diff --git a/src/dbtree/boardmachi.cpp b/src/dbtree/boardmachi.cpp new file mode 100644 index 000000000..7387e1f2e --- /dev/null +++ b/src/dbtree/boardmachi.cpp @@ -0,0 +1,290 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "boardmachi.h" +#include "articlemachi.h" + +#include "jdlib/miscutil.h" +#include "jdlib/miscmsg.h" +#include "jdlib/loaderdata.h" + +#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( "" ); + + // readcgi(read.pl) のURLは特殊なので url_readcgibaseをオーバロードする + set_path_readcgi( "" ); + + set_path_bbscgi( "/bbs/write.cgi" ); + set_path_subbbscgi( "/bbs/write.cgi" ); + set_subjecttxt( "subject.txt" ); + set_ext( "" ); + set_id( path_board.substr( 1 ) ); // 先頭の '/' を除く +#ifdef NOUSE_MS932 + set_charset( "CP932" ); +#else + set_charset( "MS932" ); +#endif +} + + + +// +// url がこの板のものかどうか +// +bool BoardMachi::equal( const std::string& url ) +{ + if( url.find( get_root() ) == 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 ) +{ + if( filename.length() != 10 ) return false; + + unsigned int dig, n; + MISC::str_to_uint( filename.c_str(), dig, n ); + if( dig != n ) return false; + if( dig != 10 ) return false; + + return true; +} + + + + +// +// 新しくArticleBaseクラスを追加してそのポインタを返す +// +// cached : HDD にキャッシュがあるならtrue +// +ArticleBase* BoardMachi::append_article( const std::string& id, bool cached ) +{ + if( empty() ) return get_article_null(); + + ArticleBase* article = new DBTREE::ArticleMachi( url_datbase(), id, cached ); + if( article ) get_list_article().push_back( article ); + else return get_article_null(); + + return article; +} + + +// +// スレの url を read.cgi型のurlに変換 +// +// url がスレッドのURLで無い時はempty()が返る +// num_from と num_to が 0 で無い時はスレ番号を付ける +// +// (例) "http://hoge.machi.to/bbs/read.pl?BBS=board&KEY=12345", num_from = 12, num_to = 15 のとき +// +// 戻り値 : "http://hoge.machi.to/bbs/read.pl?BBS=board&KEY=12345&START=12&END=15" +// +const std::string BoardMachi::url_readcgi( const std::string& url, int num_from, int num_to ) +{ + if( empty() ) return std::string(); + +#ifdef _DEBUG + std::cout << "BoardMachi::url_readcgi : " << url << " from " << num_from << " to " << num_to << std::endl; +#endif + + ArticleBase* article = get_article_fromURL( url ); + if( !article ) return std::string(); + + std::string readcgi = url_readcgibase() + article->get_key(); + +#ifdef _DEBUG + std::cout << "BoardMachi::url_readcgi : to " << readcgi << std::endl; +#endif + + if( num_from > 0 || num_to > 0 ){ + + std::ostringstream ss; + ss << readcgi; + if( num_from > 0 ) ss << "&START="<< num_from << "&END="; + if( num_to > num_from ) ss << num_to; + else ss << num_from; + + return ss.str(); + } + + return readcgi; +} + + +// +// pread.pl のURLのパスを返す +// +// (例) "/bbs/read.pl?BBS=board&KEY=" (最初に '/' がつく) +// +const std::string BoardMachi::url_datpath() +{ + if( empty() ) return std::string(); + + return "/bbs/read.pl?BBS=" + get_id() + "&KEY="; +} + +// +// まちBBSはdat型とreadcgi型のURLが同じ +// +const std::string BoardMachi::url_readcgipath() +{ + return url_datpath(); +} + + + +const 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 +// +const std::string BoardMachi::url_bbscgi_new() +{ + return std::string(); +} + + +// +// 新スレ作成時のsubbbscgi のURL +// +const std::string BoardMachi::url_subbbscgi_new() +{ + return std::string(); +} + + + +// +// subject.txt から Aarticle のリストにアイテムを追加・更新 +// +void BoardMachi::parse_subject( const char* str_subject_txt ) +{ +#ifdef _DEBUG + std::cout << "BoardMachi::parse_subject\n"; +#endif + + const char* pos = str_subject_txt; + char str_tmp[ 1024 ]; + + ArticleBase* article_first = NULL; + + while( *pos != '\0' ){ + + const char* str_id_dat; + int lng_id_dat = 0; + const char* str_subject; + int lng_subject = 0; + char str_num[ 16 ]; + + // 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 ); + + // レス数取得 + ++pos; + int i = 0; + while( *pos != ')' && *pos != '\0' && *pos != '\n' && i < 16 ) str_num[ i++ ] = *( pos++ ); + + // 壊れてる + if( *pos == '\0' ) break; + if( *pos == '\n' ) { ++pos; continue; } + + str_num[ i ] = '\0'; + ++pos; + + // id, subject, number 取得 + memcpy( str_tmp, str_id_dat, lng_id_dat ); + str_tmp[ lng_id_dat ] = '\0'; + std::string id = MISC::remove_space( str_tmp ); + + memcpy( str_tmp, str_subject, lng_subject ); + str_tmp[ lng_subject ] = '\0'; + std::string subject = MISC::remove_space( str_tmp ); + subject = MISC::replace_str( subject, "<", "<" ); + subject = MISC::replace_str( subject, ">", ">" ); + + int number = atol( str_num ); + +#ifdef _DEBUG +// std::cout << id << " " << number << " " << subject << std::endl; +#endif + + // DBに登録されてるならarticle クラスの情報更新 + ArticleBase* article = get_article( id ); + + // DBにないなら新規に article クラスを追加 + // + // なおRoot::get_board() 経由で void BoardBase::read_info()及び + // BoardBase::read_all_article_info() が既に呼ばれているので + // DBに無いということはキャッシュにも無いということ。よって append_article()で cached = false + + if( article->empty() ) article = append_article( id, false ); + + // スレ情報更新 + if( article ){ + + article->read_info(); + article->set_subject( subject ); + article->set_number( number ); + + // boardビューに表示するリスト更新 + // Machiは最初と最後の行が同じになる仕様があるので最後の行を除く + bool pushback = true; + if( ! article_first ) article_first = article; + else if( article == article_first ) pushback = false; + + if( pushback ){ + article->set_current( true ); + get_list_subject().push_back( article ); + } + } + } +} diff --git a/src/dbtree/boardmachi.h b/src/dbtree/boardmachi.h new file mode 100644 index 000000000..66ec1a356 --- /dev/null +++ b/src/dbtree/boardmachi.h @@ -0,0 +1,44 @@ +// ライセンス: 最新のGPL + +// +// まち 型板 +// + +#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 ); + + // url がこの板のものかどうか + virtual bool equal( const std::string& url ); + + virtual const std::string url_readcgi( const std::string& url, int num_from, int num_to ); + virtual const std::string url_datpath(); + virtual const std::string url_readcgipath(); + + // 新スレ作成用のメッセージ変換 + virtual const std::string create_newarticle_message( const std::string& subject, + const std::string& name, const std::string& mail, const std::string& msg ); + // 新スレ作成用のbbscgi のURL + virtual const std::string url_bbscgi_new(); + + // 新スレ作成用のsubbbscgi のURL + virtual const std::string url_subbbscgi_new(); + + private: + + virtual bool is_valid( const std::string& filename ); + virtual ArticleBase* append_article( const std::string& id, bool cached ); + virtual void parse_subject( const char* str_subject_txt ); + }; +} + +#endif diff --git a/src/dbtree/interface.cpp b/src/dbtree/interface.cpp new file mode 100644 index 000000000..5195ed0b6 --- /dev/null +++ b/src/dbtree/interface.cpp @@ -0,0 +1,538 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "interface.h" +#include "root.h" +#include "boardbase.h" +#include "articlebase.h" + +#include "jdlib/miscutil.h" + +#include "global.h" + +#include + +// インスタンスは Core でひとつだけ作って、Coreのデストラクタでdeleteする +DBTREE::Root *instance_dbtree_root = NULL; + + +void DBTREE::create_root() +{ + if( ! instance_dbtree_root ) instance_dbtree_root = new DBTREE::Root(); +} + + +void DBTREE::delete_root() +{ + if( instance_dbtree_root ) delete instance_dbtree_root; +} + + +////////////////////////////////////// +// +// ツリーの構成要素のポインタ取得 +// +// root から葉っぱに向かって順に取得していく +// + +DBTREE::Root* DBTREE::get_root() +{ + assert( instance_dbtree_root != NULL ); + return instance_dbtree_root; +} + +DBTREE::BoardBase* DBTREE::get_board( const std::string& url ) +{ + DBTREE::BoardBase* board = DBTREE::get_root()->get_board( url ); + assert( board != NULL ); + return board; +} + +DBTREE::ArticleBase* DBTREE::get_article( const std::string& url ) +{ + DBTREE::ArticleBase* article = DBTREE::get_board( url )->get_article_fromURL( url ); + assert( article != NULL ); + return article; +} + +////////////////////////////////////// + +const std::string DBTREE::url_subject( const std::string& url ) +{ + return DBTREE::get_board( url )->url_subject(); +} + + +const std::string DBTREE::url_root( const std::string& url ) +{ + return DBTREE::get_board( url )->url_root(); +} + + +const std::string DBTREE::url_boardbase( const std::string& url ) +{ + return DBTREE::get_board( url )->url_boardbase(); +} + + +const std::string DBTREE::url_datbase( const std::string& url ) +{ + return DBTREE::get_board( url )->url_datbase(); +} + + +// urlをdat型のurlに変換 +const std::string DBTREE::url_dat( const std::string& url, int& num_from, int& num_to ) +{ + return DBTREE::get_board( url )->url_dat( url, num_from, num_to ); +} + + +// urlをdat型のurlに変換(簡易版) +const std::string DBTREE::url_dat( const std::string& url ) +{ + int num_from, num_to; + return url_dat( url, num_from, num_to ); +} + + +// url を read.cgi型のurlに変換 +const 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 ); +} + + +const std::string DBTREE::url_bbscgibase( const std::string& url ) +{ + return DBTREE::get_board( url )->url_bbscgibase(); +} + +const std::string DBTREE::url_subbbscgibase( const std::string& url ) +{ + return DBTREE::get_board( url )->url_subbbscgibase(); +} + + +const std::string DBTREE::url_bbscgi( const std::string& url ) +{ + return DBTREE::get_article( url )->url_bbscgi(); +} + +const std::string DBTREE::url_subbbscgi( const std::string& url ) +{ + return DBTREE::get_article( url )->url_subbbscgi(); +} + +const std::string DBTREE::url_bbscgi_new( const std::string& url ) +{ + return DBTREE::get_board( url )->url_bbscgi_new(); +} + +const std::string DBTREE::url_subbbscgi_new( const std::string& url ) +{ + return DBTREE::get_board( url )->url_subbbscgi_new(); +} + + +const std::string& DBTREE::get_xml_bbsmenu() +{ + return get_root()->xml_bbsmenu(); +} + + +const std::string& DBTREE::get_xml_etc() +{ + return get_root()->xml_etc(); +} + + +void DBTREE::download_bbsmenu() +{ + get_root()->download_bbsmenu(); +} + + +const std::string DBTREE::board_path( const std::string& url ) +{ + return DBTREE::get_board( url )->get_path_board(); +} + + +const std::string DBTREE::board_id( const std::string& url ) +{ + return DBTREE::get_board( url )->get_id(); +} + +// 更新時間 +const time_t DBTREE::board_time_modified( const std::string& url ) +{ + return DBTREE::get_board( url )->time_modified(); +} + + +const std::string DBTREE::board_name( const std::string& url ) +{ + return DBTREE::get_board( url )->get_name(); +} + + +const std::string DBTREE::board_subjecttxt( const std::string& url ) +{ + return DBTREE::get_board( url )->get_subjecttxt(); +} + + +const std::string DBTREE::board_charset( const std::string& url ) +{ + return DBTREE::get_board( url )->get_charset(); +} + + +const std::string DBTREE::board_cookie_for_write( const std::string& url ) +{ + return DBTREE::get_board( url )->cookie_for_write(); +} + + +void DBTREE::board_set_cookie_for_write( const std::string& url, const std::string& cookie ) +{ + DBTREE::get_board( url )->set_cookie_for_write( cookie ); +} + + +const std::string DBTREE::board_hana_for_write( const std::string& url ) +{ + return DBTREE::get_board( url )->hana_for_write(); +} + + +void DBTREE::board_set_hana_for_write( const std::string& url, const std::string& hana ) +{ + DBTREE::get_board( url )->set_hana_for_write( hana ); +} + +const std::string DBTREE::board_ext( const std::string& url ) +{ + return DBTREE::get_board( url )->get_ext(); +} + + +const 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_subject( const std::string& url ) +{ + DBTREE::get_board( url )->download_subject(); +} + +void DBTREE::board_stop_load( const std::string& url ) +{ + DBTREE::get_board( url )->stop_load(); +} + + +std::list< DBTREE::ArticleBase* >& DBTREE::board_list_subject( const std::string& url ) +{ + return DBTREE::get_board( url )->get_list_subject(); +} + + +const 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 ) +{ + return DBTREE::get_board( url )->set_view_sort_column( column ); +} + + +const bool DBTREE::board_view_sort_ascend( const std::string& url ) +{ + return DBTREE::get_board( url )->get_view_sort_ascend(); +} + + +void DBTREE::board_set_view_sort_ascend( const std::string& url, bool ascend ) +{ + return DBTREE::get_board( url )->set_view_sort_ascend( ascend ); +} + + +// 拡張子付き +const std::string DBTREE::article_id( const std::string& url ) +{ + std::string id = DBTREE::get_article( url )->get_id(); + return id; +} + + +// idから拡張子を取ったもの +const std::string DBTREE::article_key( const std::string& url ) +{ + std::string key = DBTREE::get_article( url )->get_key(); + return key; +} + + +const time_t DBTREE::article_since_time( const std::string& url ) +{ + return DBTREE::get_article( url )->get_since_time(); +} + + +const std::string DBTREE::article_since_date( const std::string& url ) +{ + return DBTREE::get_article( url )->get_since_date(); +} + + +// 更新時間 +const time_t DBTREE::article_time_modified( const std::string& url ) +{ + return DBTREE::get_article( url )->get_time_modified(); +} + + +const int DBTREE::article_status( const std::string& url ) +{ + return DBTREE::get_article( url )->get_status(); +} + + +void DBTREE::article_reset_status( const std::string& url ) +{ + DBTREE::get_article( url )->reset_status(); +} + + +const int DBTREE::article_code( const std::string& url ) +{ + return DBTREE::get_article( url )->get_code(); +} + +const std::string DBTREE::article_str_code( const std::string& url ) +{ + return DBTREE::get_article( url )->get_str_code(); +} + +const std::string DBTREE::article_ext_err( const std::string& url ) +{ + return DBTREE::get_article( url )->get_ext_err(); +} + +const std::string DBTREE::article_subject( const std::string& url ) +{ + return DBTREE::get_article( url )->get_subject(); +} + + +const int DBTREE::article_number( const std::string& url ) +{ + return DBTREE::get_article( url )->get_number(); +} + +const int DBTREE::article_number_load( const std::string& url ) +{ + return DBTREE::get_article( url )->get_number_load(); +} + +const 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 ); +} + +const int DBTREE::article_number_new( const std::string& url ) +{ + return DBTREE::get_article( url )->get_number_new(); +} + +void DBTREE::article_download_dat( const std::string& url ) +{ + DBTREE::get_article( url )->download_dat(); +} + + +const std::string& DBTREE::get_agent( const std::string& url ) +{ + return DBTREE::get_board( url )->get_agent(); +} + +const std::string DBTREE::get_proxy_host( const std::string& url ) +{ + return DBTREE::get_board( url )->get_proxy_host(); +} + +const int DBTREE::get_proxy_port( const std::string& url ) +{ + return DBTREE::get_board( url )->get_proxy_port(); +} + +const std::string DBTREE::get_proxy_host_w( const std::string& url ) +{ + return DBTREE::get_board( url )->get_proxy_host_w(); +} + +const int DBTREE::get_proxy_port_w( const std::string& url ) +{ + return DBTREE::get_board( url )->get_proxy_port_w(); +} + +const std::string DBTREE::settingtxt( const std::string& url ) +{ + return DBTREE::get_board( url )->settingtxt(); +} + + +const std::string DBTREE::default_noname( const std::string& url ) +{ + return DBTREE::get_board( url )->default_noname(); +} + +const int DBTREE::line_number( const std::string& url ) +{ + return DBTREE::get_board( url )->line_number(); +} + +const int DBTREE::message_count( const std::string& url ) +{ + return DBTREE::get_board( url )->message_count(); +} + +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 ); +} + +const 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 ); +} + +const 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 ); +} + + +const 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 ); +} + + +const 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 ); +} + + +// キャッシュ削除 +void DBTREE::delete_article( const std::string& url ) +{ + DBTREE::get_article( url )->delete_cache(); +} + + + +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(); +} + + + + +std::list< std::string > DBTREE::get_abone_list_id( const std::string& url ) +{ + return DBTREE::get_article( url )->get_abone_list_id(); +} + + +std::list< std::string > DBTREE::get_abone_list_name( const std::string& url ) +{ + return DBTREE::get_article( url )->get_abone_list_name(); +} + + +void DBTREE::reset_abone( const std::string& url, std::list< std::string >& ids, std::list< std::string >& names ) +{ + DBTREE::get_article( url )->reset_abone( ids, names ); +} + + +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 ); +} + + +const bool DBTREE::is_bookmarked( const std::string& url, int number ) +{ + return DBTREE::get_article( url )->is_bookmarked( number ); +} + + +void DBTREE::set_bookmark( const std::string& url, int number, bool set ) +{ + DBTREE::get_article( url )->set_bookmark( number, set ); +} + diff --git a/src/dbtree/interface.h b/src/dbtree/interface.h new file mode 100644 index 000000000..32453bb6b --- /dev/null +++ b/src/dbtree/interface.h @@ -0,0 +1,154 @@ +// ライセンス: 最新のGPL + +// +// データベースへのインターフェース関数 +// + +#ifndef _INTERFACE_H +#define _INTERFACE_H + +#include +#include + +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の変換関係 + const std::string url_subject( const std::string& url ); + const std::string url_root( const std::string& url ); + const std::string url_boardbase( const std::string& url ); + const std::string url_datbase( const std::string& url ); + + // dat型のurlに変換 + const std::string url_dat( const std::string& url, int& num_from, int& num_to ); + + // dat型のurlに変換(簡易版) + const std::string url_dat( const std::string& url ); + + // read.cgi型のurlに変換 + const std::string url_readcgi( const std::string& url, int num_from, int num_to ); + + const std::string url_bbscgibase( const std::string& url ); + const std::string url_subbbscgibase( const std::string& url ); + const std::string url_bbscgi( const std::string& url ); + const std::string url_subbbscgi( const std::string& url ); + const std::string url_bbscgi_new( const std::string& url ); + const std::string url_subbbscgi_new( const std::string& url ); + + // bbslist系 + const std::string& get_xml_bbsmenu(); + const std::string& get_xml_etc(); + void download_bbsmenu(); + + // board 系 + const std::string board_path( const std::string& url ); + const std::string board_id( const std::string& url ); + const time_t board_time_modified( const std::string& url ); + const std::string board_name( const std::string& url ); + const std::string board_subjecttxt( const std::string& url ); + const std::string board_charset( const std::string& url ); + const std::string board_cookie_for_write( const std::string& url ); + void board_set_cookie_for_write( const std::string& url, const std::string& cookie ); + const std::string board_hana_for_write( const std::string& url ); + void board_set_hana_for_write( const std::string& url, const std::string& hana ); + const std::string board_ext( const std::string& url ); + const std::string board_str_code( const std::string& url ); + void board_save_info( const std::string& url ); + void board_download_subject( const std::string& url ); + void board_stop_load( const std::string& url ); + std::list< DBTREE::ArticleBase* >& board_list_subject( const std::string& url ); + const int board_view_sort_column( const std::string& url ); + void board_set_view_sort_column( const std::string& url, int column ); + const bool board_view_sort_ascend( const std::string& url ); + void board_set_view_sort_ascend( const std::string& url, bool ascend ); + + + // article 系 + const std::string article_id( const std::string& url ); // 拡張子込み "12345.dat" みたいに + const std::string article_key( const std::string& url ); // idから拡張子を取ったもの。書き込み用 + const time_t article_since_time( const std::string& url ); + const std::string article_since_date( const std::string& url ); + const time_t article_time_modified( const std::string& url ); + const int article_status( const std::string& url ); + void article_reset_status( const std::string& url ); + const int article_code( const std::string& url ); + const std::string article_str_code( const std::string& url ); + const std::string article_ext_err( const std::string& url ); + const std::string article_subject( const std::string& url ); + const int article_number( const std::string& url ); + const int article_number_load( const std::string& url ); + const int article_number_seen( const std::string& url ); + void article_set_number_seen( const std::string& url, int seen ); + const int article_number_new( const std::string& url ); + void article_download_dat( const std::string& url ); + + void delete_article( const std::string& url ); // キャッシュ削除 + void article_update_writetime( const std::string& url ); + size_t article_lng_dat( const std::string& url ); + + // agent, プロキシ + + const std::string& get_agent( const std::string& url ); + const std::string get_proxy_host( const std::string& url ); // 読み込み用 + const int get_proxy_port( const std::string& url ); + const std::string get_proxy_host_w( const std::string& url ); // 書き込み用 + const int get_proxy_port_w( const std::string& url ); + + // setting.txt + const std::string settingtxt( const std::string& url ); + + // 書き込み関係 + + // デフォルトの名無し名 + const std::string default_noname( const std::string& url ); + + // 最大改行数/2 + const int line_number( const std::string& url ); + + // 最大書き込みバイト数 + const int message_count( 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 ); + const 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 ); + const bool write_fixmail( const std::string& url ); + void set_write_fixmail( const std::string& url, bool set ); + + // ポストするメッセージの作成 + const std::string create_write_message( const std::string& url, + const std::string& name, const std::string& mail, const std::string& msg ); + const 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::list< std::string > get_abone_list_id( const std::string& url ); + std::list< std::string > get_abone_list_name( const std::string& url ); + void reset_abone( const std::string& url, std::list< std::string >& ids, std::list< std::string >& names ); + void add_abone_id( const std::string& url, const std::string& id ); + void add_abone_name( const std::string& url, const std::string& name ); + + // ブックマーク + const bool is_bookmarked( const std::string& url, int number ); + void set_bookmark( const std::string& url, int number, bool set ); +} + +#endif diff --git a/src/dbtree/node.h b/src/dbtree/node.h new file mode 100644 index 000000000..9fe570a59 --- /dev/null +++ b/src/dbtree/node.h @@ -0,0 +1,89 @@ +// ライセンス: 最新のGPL + +// +// ノードツリーのノード +// + +#ifndef _NODE_H +#define _NODE_H + + +namespace DBIMG +{ + class Img; +} + + +namespace DBTREE +{ + // NODE::type + enum + { + NODE_HEADER = 0, // ヘッダ + NODE_TEXT, // テキスト + NODE_LINK, // リンク + NODE_BR, // 改行 + NODE_DOWN_LEFT, // 左マージンのレベルをひとつ下げる。ヘッダの時点でレベル1 + // スペース + NODE_ZWSP, + NODE_HAIRSP, + NODE_THINSP, + NODE_ENSP, + NODE_EMSP, + + NODE_NONE + }; + + struct NODE; + + // ヘッダ拡張情報 + struct HEADERINFO + { + NODE* next_header; + + NODE* node_res; // レス番号ノードのポインタ + int num_reference; // 参照数 + + char* name; // 名前 + + NODE* node_id_name; // 名前IDリンクノードのポインタ + int num_id_name; // 同じIDのレスの個数 + }; + + + // リンク情報 + struct LINKINFO + { + bool image; // 画像かどうか + char* link; // リンクURL + DBIMG::Img* img; // 画像データクラスへのポインタ(危険だが高速化のため直接アクセス、deleteしないこと) + + // image == true かつ img == NULL ならまだ img は未取得 + // 実際にノードが画面に表示された際に img のポインタを取得して画像の状態を取得する + // 詳しくは ArticleViewBase::draw_one_node を参照 + }; + + + // ノード構造体 + struct NODE + { + unsigned char type; + + short id_header; // ヘッダID ( つまりレス番号、ルートヘッダは0 ) + short id; // ヘッダノードから順に 0,1,2,.... + NODE* next_node; // 最終ノードはNULL + + char* text; + unsigned char color_text; // 色 + bool bold; + + // ヘッダ拡張情報 + HEADERINFO* headinfo; + + // リンク情報 + LINKINFO* linkinfo; + }; +} + + +#endif diff --git a/src/dbtree/nodetree2ch.cpp b/src/dbtree/nodetree2ch.cpp new file mode 100644 index 000000000..ac76912a6 --- /dev/null +++ b/src/dbtree/nodetree2ch.cpp @@ -0,0 +1,127 @@ +// ライセンス: 最新のGPL + +//#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; + + +NodeTree2ch::NodeTree2ch( const std::string& url, const std::string& org_url, const std::string& date_modified ) + : NodeTree2chCompati( url, date_modified ) + , m_org_url( org_url ), m_use_offlaw( false ) +{ +#ifdef _DEBUG + std::cout << "NodeTree2ch::NodeTree2ch url = " << url << std::endl + << "org_url = " << m_org_url << " modified = " << date_modified << std::endl; +#endif +} + + + +NodeTree2ch::~NodeTree2ch() +{ +#ifdef _DEBUG + std::cout << "NodeTree2ch::~NodeTree2ch : " << get_url() << std::endl; +#endif +} + + +// +// ロード用データ作成 +// +void NodeTree2ch::create_loaderdata( JDLIB::LOADERDATA& data ) +{ +#ifdef _DEBUG + std::cout << "NodeTree2ch::create_loaderdata : " << get_url() << std::endl; +#endif + + data.url = std::string(); + + // dat 落ちしていたらofflaw経由でアクセス + if( m_use_offlaw ){ + + JDLIB::Regex regex; + if( ! regex.exec( "(http://[^/]*)(/.*)/dat(/.*)\\.dat$", m_org_url ) ) return; + + std::ostringstream ss; + ss << regex.str( 1 ) << "/test/offlaw.cgi" << regex.str( 2 ) << regex.str( 3 ) + << "/?raw=." << lng_dat(); + + std::string sid = LOGIN::get_login2ch()->get_sessionid(); + ss << "&sid=" << MISC::url_encode( sid.c_str(), sid.length() ); + + // レジュームは無し + set_resume( false ); + data.byte_readfrom = 0; + + data.url = ss.str(); + +#ifdef _DEBUG + std::cout << "use offlaw url = " << data.url << std::endl; +#endif + } + + // 普通の読み込み + else{ + + data.url = get_url(); + + // レジューム設定 + // 1byte前からレジュームして '\n' が返ってこなかったらあぼーんがあったってこと + if( lng_dat() ) { + data.byte_readfrom = lng_dat() -1; + set_resume( true ); + } + else set_resume( false ); + } + + 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.size_buf = CONFIG::get_loader_bufsize(); + data.timeout = CONFIG::get_loader_timeout(); + + if( ! date_modified().empty() ) data.modified = date_modified(); +} + + +// +// ロード完了 +// +void NodeTree2ch::receive_finish() +{ +#ifdef _DEBUG + std::cout << "NodeTree2ch::receive_finish : " << get_url() << std::endl; + std::cout << "code = " << get_code() << std::endl; +#endif + + // オンライン、ログインしている、かつdat落ちの場合はofflaw.cgi経由で旧URLで再取得 + if( LOGIN::get_login2ch()->login_now() && SESSION::is_online() && ! m_use_offlaw + && ( get_code() == HTTP_REDIRECT || get_code() == HTTP_NOT_FOUND ) + ){ +#ifdef _DEBUG + std::cout << "reload by offlaw\n"; +#endif + m_use_offlaw = true; + download_dat(); + return; + } + + NodeTreeBase::receive_finish(); +} diff --git a/src/dbtree/nodetree2ch.h b/src/dbtree/nodetree2ch.h new file mode 100644 index 000000000..8a8fd9e19 --- /dev/null +++ b/src/dbtree/nodetree2ch.h @@ -0,0 +1,34 @@ +// ライセンス: 最新のGPL + +// +// 2ch型ノードツリー +// + +#ifndef _NODETREE2ch_H +#define _NODETREE2ch_H + +#include "nodetree2chcompati.h" + +namespace DBTREE +{ + class NodeTree2ch : public NodeTree2chCompati + { + std::string m_org_url; // 移転前のオリジナルURL + bool m_use_offlaw; // offlawを使用する + + public: + + NodeTree2ch( const std::string& url, const std::string& org_url, const std::string& date_modified ); + ~NodeTree2ch(); + + protected: + + virtual void create_loaderdata( JDLIB::LOADERDATA& data ); + + private: + + virtual void receive_finish(); + }; +} + +#endif diff --git a/src/dbtree/nodetree2chcompati.cpp b/src/dbtree/nodetree2chcompati.cpp new file mode 100644 index 000000000..44108a484 --- /dev/null +++ b/src/dbtree/nodetree2chcompati.cpp @@ -0,0 +1,155 @@ +// ライセンス: 最新のGPL + +//#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 ) + , m_iconv( 0 ) +{ +#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 + + clear(); +} + + + +// +// バッファなどのクリア +// +void NodeTree2chCompati::clear() +{ +#ifdef _DEBUG + std::cout << "NodeTree2chCompati::clear : " << get_url() << std::endl; +#endif + + NodeTreeBase::clear(); + + // iconv 削除 + if( m_iconv ) delete m_iconv; + m_iconv = NULL; +} + + + +// +// ロード実行前に呼ぶ初期化関数 +// +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 = new JDLIB::Iconv( charset ); +} + + + +// +// キャッシュに保存する前の前処理 +// +// 先頭にrawモードのステータスが入っていたら取り除く +// +char* NodeTree2chCompati::process_raw_lines( char* rawlines ) +{ + char* pos = rawlines; + + if( *pos == '+' || *pos == '-' ){ + + 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; + +#ifdef _DEBUG + std::cout << "NodeTree2chCompati::process_raw_lines : raw mode status = " << status << std::endl; +#endif + + if( status != 0 ){ + + // この行を飛ばす + 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 != NULL ); + + // バッファ自体は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( lng_dat() ) { + data.byte_readfrom = 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.size_buf = CONFIG::get_loader_bufsize(); + data.timeout = CONFIG::get_loader_timeout(); + + if( ! date_modified().empty() ) data.modified = date_modified(); +} diff --git a/src/dbtree/nodetree2chcompati.h b/src/dbtree/nodetree2chcompati.h new file mode 100644 index 000000000..352749704 --- /dev/null +++ b/src/dbtree/nodetree2chcompati.h @@ -0,0 +1,39 @@ +// ライセンス: 最新のGPL + +// +// 2ch互換型ノードツリー +// + +#ifndef _NODETREE2CHCOMPATI_H +#define _NODETREE2CHCOMPATI_H + +#include "nodetreebase.h" + +namespace JDLIB +{ + class Iconv; +} + +namespace DBTREE +{ + class NodeTree2chCompati : public NodeTreeBase + { + JDLIB::Iconv* m_iconv; + + public: + + NodeTree2chCompati( const std::string url, const std::string& date_modified ); + virtual ~NodeTree2chCompati(); + + protected: + + virtual void clear(); + virtual void init_loading(); + virtual char* process_raw_lines( char* rawlines ); + virtual const char* raw2dat( char* rawlines, int& byte ); + + virtual void create_loaderdata( JDLIB::LOADERDATA& data ); + }; +} + +#endif diff --git a/src/dbtree/nodetreebase.cpp b/src/dbtree/nodetreebase.cpp new file mode 100644 index 000000000..af9282e11 --- /dev/null +++ b/src/dbtree/nodetreebase.cpp @@ -0,0 +1,1329 @@ +// ライセンス: 最新のGPL + +//#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 "global.h" +#include "httpcode.h" +#include "colorid.h" +#include "command.h" +#include "cache.h" +#include "session.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 + + +#define SECTION_NUM 5 +#define LNG_RES 16 +#define LNG_ID 64 +#define LNG_LINK 256 +#define RANGE_REF 20 +#define MAX_LINK_DIGIT 4 // レスアンカーでMAX_LINK_DIGIT 桁までリンクにする + +#define MAXSISE_OF_LINES ( 512 * 1024 ) // ロード時に1回の呼び出しで読み込まれる最大データサイズ +#define SIZE_OF_HEAP ( MAXSISE_OF_LINES + 64 ) + +using namespace DBTREE; + + +NodeTreeBase::NodeTreeBase( const std::string url, const std::string& modified ) + : SKELETON::Loadable(), + m_url( url ), + m_lng_dat( 0 ), + m_resume ( 0 ), + m_broken( 0 ), + m_heap( SIZE_OF_HEAP ), + m_buffer_lines( 0 ), + m_parsed_text( 0 ), + m_fout ( 0 ) +{ + set_date_modified( modified ); + + clear(); + + // ヘッダのポインタの配列作成 + m_vec_header = ( NODE** ) m_heap.heap_alloc( sizeof( NODE* ) * MAX_RESNUMBER ); + + // ルートヘッダ作成。中は空。 + m_id_header = -1; // ルートヘッダIDが 0 になるように -1 + NODE* tmpnode = create_header_node(); + m_vec_header[ m_id_header ] = tmpnode; + + m_default_noname = DBTREE::default_noname( m_url ); + +#ifdef _DEBUG + std::cout << "NodeTreeBase::NodeTreeBase url = " << m_url << " modified = " << date_modified() + << " noname = " << m_default_noname << std::endl; +#endif +} + + +NodeTreeBase::~NodeTreeBase() +{ +#ifdef _DEBUG + std::cout << "NodeTreeBase::~NodeTreeBase : " << m_url << std::endl; +#endif + + clear(); +} + + +bool NodeTreeBase::empty() +{ + return m_url.empty(); +} + + +// +// バッファなどのクリア +// +void NodeTreeBase::clear() +{ + if( m_buffer_lines ) free( m_buffer_lines ); + m_buffer_lines = NULL; + m_byte_buffer_lines_left = 0; + + if( m_parsed_text ) free( m_parsed_text ); + m_parsed_text = NULL; + + if( m_fout ) fclose( m_fout ); + m_fout = NULL; + + m_ext_err = std::string(); +} + + + +// +// 総レス数 +// +// ロード中は m_id_header 番のレスはまだ処理中なので m_id_header -1 を返す +// +const int NodeTreeBase::get_res_number() +{ + if( is_loading() ) return m_id_header -1; + + return m_id_header; +} + + +// +// number 番のレスのヘッダのポインタを返す +// +NODE* NodeTreeBase::res_header( int number ) +{ + if( number > m_id_header || number <= 0 ) return NULL; + + return m_vec_header[ number ]; +} + + +// +// 指定したID の重複数( = 発言数 ) +// +// 下のnum_id_name( int number )と違って検索するので遅い +// +int NodeTreeBase::get_num_id_name( const std::string& 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( int number ) +{ + NODE* head = res_header( number ); + if( ! head ) return 0; + + return head->headinfo->num_id_name; +} + + + +// +// number番のレスに含まれるURLをすべてリストにして取得 +// +std::list< std::string > NodeTreeBase::get_URLs( int number ) +{ + std::list< std::string > list_urls; + + NODE* node = res_header( number ); + + while( node ){ + + if( node->type == NODE_LINK ) list_urls.push_back( node->linkinfo->link ); + node = node->next_node; + } + +#ifdef _DEBUG + std::cout << "NodeTreeBase::get_URLs\n"; + std::list < std::string >::iterator it; + for( it = list_urls.begin(); it != list_urls.end(); ++it ) std::cout << *it << std::endl; +#endif + + return list_urls; +} + + +// +// URL を含むレス番号をリストにして取得 +// +std::list< int > NodeTreeBase::get_res_with_url() +{ + std::list< int > list_res; + for( int i = 1; i <= m_id_header; ++i ){ + + NODE* node = res_header( i ); + while( node ){ + + if( node->type == NODE_LINK + && std::string( node->linkinfo->link ).find( "http://" ) == 0 ){ + list_res.push_back( i ); + break; + } + node = node->next_node; + } + } + + return list_res; +} + + + +// +// number番のレスを参照しているレスの番号全てをリストにして取得 +// +std::list< int > NodeTreeBase::get_reference( int number ) +{ + std::list< int > list_ref; + for( int i = number + 1; i <= m_id_header; ++i ){ + + std::list< std::string > list_urls = get_URLs( i ); + std::list < std::string >::iterator it; + for( it = list_urls.begin(); it != list_urls.end(); ++it ){ + + std::string& url = *( it ); + if( url.find( PROTO_ANCHORE ) == 0 ){ + + std::string str_num = url.substr( strlen( PROTO_ANCHORE) ); + int num_from = atol( str_num.c_str() ); + int num_to = num_from; + unsigned int i2; + if( ( i2 = str_num.find( "-" ) ) != std::string::npos ) num_to = atol( str_num.substr( i2 +1 ).c_str() ); + + const int range = RANGE_REF; // >>1-1000 みたいなアンカーは弾く + if( num_to >= num_from && num_to - num_from < range && num_from <= number && number <= num_to ) { + list_ref.push_back( i ); + break; + } + } + } + } + +#ifdef _DEBUG + std::cout << "NodeTreeBase::get_reference\n"; + std::list < int >::iterator it; + for( it = list_ref.begin(); it != list_ref.end(); ++it ) std::cout << *it << std::endl; +#endif + + return list_ref; +} + + + +// +// number 番のレスの文字列を返す +// +// ref == true なら先頭に ">" を付ける +// +const std::string NodeTreeBase::get_res_str( int number, bool ref ) +{ + std::string str_res; + +#ifdef _DEBUG + std::cout << "NodeTreeBase::get_res_str : num = " << number << std::endl; +#endif + + NODE* node = res_header( number ); + if( ! node ) return std::string(); + + if( ref ) str_res += "> "; + + while( node ){ + + if( node->type == DBTREE::NODE_BR ){ + str_res += "\n"; + if( ref ) str_res += "> "; + } + else if( node->text ){ + str_res += node->text; + } + + node = node->next_node; + } + + str_res += "\n"; + +#ifdef _DEBUG +// std::cout << str_res << std::endl; +#endif + + return str_res; +} + + + +// +// number番を書いた人の名前を取得 +// +const std::string NodeTreeBase::get_name( int number ) +{ + NODE* head = res_header( number ); + if( ! head ) return std::string(); + if( ! head->headinfo->name ) return std::string(); + + return head->headinfo->name; +} + + + +// +// number番の ID 取得 +// +const std::string NodeTreeBase::get_id_name( int number ) +{ + NODE* head = res_header( number ); + if( ! head ) return std::string(); + if( ! head->headinfo->node_id_name ) return std::string(); + + return head->headinfo->node_id_name->linkinfo->link; +} + + + +// +// 基本ノード作成 +// +NODE* NodeTreeBase::createNode() +{ + NODE* tmpnode = ( NODE* ) m_heap.heap_alloc( sizeof( NODE ) ); + + tmpnode->id_header = m_id_header; + tmpnode->id = m_id_node++; + if( m_node_previous ) m_node_previous->next_node = tmpnode; + m_node_previous = tmpnode; + + return tmpnode; +} + + +// +// ヘッダノード作成 +// +NODE* NodeTreeBase::create_header_node() +{ + ++m_id_header; + m_id_node = 0; + m_node_previous = NULL; + + NODE* tmpnode = createNode(); + tmpnode->type = NODE_HEADER; + + // ヘッダ情報 + tmpnode->headinfo = ( HEADERINFO* )m_heap.heap_alloc( sizeof( HEADERINFO ) ); + if( m_id_header >= 2 ) m_vec_header[ m_id_header -1 ]->headinfo->next_header = tmpnode; + + return tmpnode; +} + + +// +// 改行ノード作成 +// +NODE* NodeTreeBase::createBrNode() +{ + NODE* tmpnode = createNode(); + tmpnode->type = NODE_BR; + + // 便宜的に'\0'をセットする + tmpnode->text = ( char* )m_heap.heap_alloc( 2 ); + tmpnode->text[ 0 ] = '\0'; + + return tmpnode; +} + + +// +// スペースノード +// +NODE* NodeTreeBase::createSpNode( const int& type ) +{ + NODE* tmpnode = createNode(); + tmpnode->type = type; + return tmpnode; +} + + +// +// マージンレベル下げノード作成 +// +NODE* NodeTreeBase::create_node_downleft() +{ + NODE* tmpnode = createNode(); + tmpnode->type = NODE_DOWN_LEFT; + + return tmpnode; +} + + +// +// リンクノード作成 +// +NODE* NodeTreeBase::create_linknode( const char* text, int n, const char* link, int n_link, int color_text, bool bold, bool img ) +{ + NODE* tmpnode = createTextNodeN( text, n, color_text, bold ); + if( tmpnode ){ + tmpnode->type = NODE_LINK; + + // リンク情報セット + tmpnode->linkinfo = ( LINKINFO* )m_heap.heap_alloc( sizeof( LINKINFO ) ); + + if( img ) tmpnode->linkinfo->image = img; + tmpnode->linkinfo->link = ( char* )m_heap.heap_alloc( n_link +4 ); + memcpy( tmpnode->linkinfo->link, link, n_link ); tmpnode->linkinfo->link[ n_link ] = '\0'; + } + + return tmpnode; +} + + + +// +// テキストノード作成 +// +NODE* NodeTreeBase::createTextNode( const char* text, int color_text, bool bold ) +{ + return createTextNodeN( text, strlen( text ), color_text, bold ); +} + + + +// +// テキストノード作成( サイズ指定 ) +// +NODE* NodeTreeBase::createTextNodeN( const char* text, int n, int color_text, bool bold ) +{ + if( n <= 0 ) return NULL; + + NODE* tmpnode = createNode(); + + if( tmpnode ){ + tmpnode->type = NODE_TEXT; + + tmpnode->text = ( char* )m_heap.heap_alloc( n +8 ); + memcpy( tmpnode->text, text, n ); tmpnode->text[ n ] = '\0'; + tmpnode->color_text = color_text; + tmpnode->bold = bold; + } + + return tmpnode; +} + + +// +// html をコメントとして追加 +// +// パースして追加したノードのポインタを返す +// +NODE* NodeTreeBase::append_html( const std::string& html ) +{ + if( is_loading() ) return NULL; + if( html.empty() ) return NULL; + + NODE* tmpnode = create_header_node(); + m_vec_header[ m_id_header ] = tmpnode; + + init_loading(); + parse_html( html.c_str(), html.length(), COLOR_CHAR ); + clear(); + + return tmpnode; +} + + + +// +// dat を追加 +// +// パースして追加したノードのポインタを返す +// +NODE* NodeTreeBase::append_dat( const std::string& dat ) +{ + if( is_loading() ) return NULL; + if( dat.empty() ) return NULL; + + 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::is_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; + init_loading(); + while( size < str.length() ){ + size_t size_tmp = MIN( MAXSISE_OF_LINES, str.length() - size ); + receive_data( data + size, size_tmp ); + size += size_tmp; + } + receive_finish(); + } + } +} + + +// +// ロード実行前に呼ぶ初期化関数 +// +// charcode は入力データの文字コード( empty()ならコード変換しない) +// +void NodeTreeBase::init_loading() +{ + clear(); + + // 一時バッファ作成 + if( ! m_buffer_lines ) m_buffer_lines = ( char* ) malloc( MAXSISE_OF_LINES ); + if( ! m_parsed_text ) m_parsed_text = ( char* ) malloc( MAXSISE_OF_LINES ); +} + + + +// +// ロード開始 +// +void NodeTreeBase::download_dat() +{ +#ifdef _DEBUG + std::cout << "NodeTreeBase::download_dat : " << m_url << std::endl; +#endif + + if( is_loading() ) return; + + // オフライン + if( ! SESSION::is_online() ){ + + set_str_code( "" ); + + // ディスパッチャ経由でreceive_finish()を呼ぶ + finish(); + + return; + } + + // + // オンライン + // + + init_loading(); + + // 保存ディレクトリ作成(無ければ) + std::string path_board_root = CACHE::path_board_root( m_url ); + +#ifdef _DEBUG + std::cout << "boad_root = " << path_board_root << std::endl; +#endif + + if( CACHE::jdmkdir( path_board_root ) ){ + + // 保存ファイルオープン + std::string path_cache = CACHE::path_dat( m_url ); + +#ifdef _DEBUG + std::cout << "open " << path_cache.c_str() << std::endl; +#endif + + m_fout = fopen( path_cache.c_str(), "ab" ); + if( m_fout == NULL ){ + MISC::ERRMSG( "fopen failed : " + path_cache ); + } + } + else{ + MISC::ERRMSG( "could not create " + path_board_root ); + } + + // ロード開始 + // ロード完了したら receive_finish() が呼ばれる + JDLIB::LOADERDATA data; + create_loaderdata( data ); + 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; +#endif + + return; + } + + // レジューム処理 + // レジュームした時に先頭が '\n' かチェック + // '\n'で無ければあぼーんがあったということ + if( m_resume ){ + + if( data[ 0 ] != '\n' ){ + m_broken = true; + MISC::ERRMSG( "failed to resume" ); + } + else{ + ++data; + --size; + } + + m_resume = false; + } + + if( !size ) return; + + // バッファが '\n' で終わるように調整 + const char* pos = data + size -1; + while( *pos != '\n' && pos != data ) --pos; + + // 前回の残りのデータに新しいデータを付け足して add_raw_lines()にデータを送る + size_t size_in = ( int )( pos - data ) + 1; + if( size_in > 1 ){ + memcpy( m_buffer_lines + m_byte_buffer_lines_left , data, size_in ); + m_buffer_lines[ m_byte_buffer_lines_left + size_in ] = '\0'; + add_raw_lines( m_buffer_lines ); + } + + // 残りの分をバッファにコピーしておく + m_byte_buffer_lines_left = size - size_in; + if( m_byte_buffer_lines_left ) memcpy( m_buffer_lines, data + size_in, m_byte_buffer_lines_left ); +} + + + +// +// ロード完了 +// +void NodeTreeBase::receive_finish() +{ + if( get_code() != HTTP_INIT + && get_code() != HTTP_OK + && get_code() != HTTP_PARTIAL_CONTENT + && get_code() != HTTP_NOT_MODIFIED + ){ + std::ostringstream err; + err << m_url << " load failed. : " << get_str_code(); + MISC::ERRMSG( err.str() ); + } + + // Requested Range Not Satisfiable + if( get_code() == HTTP_RANGE_ERR ) m_broken = true; + + // データがロードされなかったらキャッシュを消す + if( get_res_number() == 0 ){ + + std::string path = CACHE::path_dat( m_url ); + if( CACHE::is_file_exists( path ) == CACHE::EXIST_FILE ) unlink( path.c_str() ); + set_date_modified( std::string() ); + } + + // その他、何かエラーがあったらmodifiedをクリアしておく + if( !get_ext_err().empty() ) set_date_modified( std::string() ); + +#ifdef _DEBUG + std::cout << "NodeTreeBase::receive_finish lng = " << m_lng_dat << " code = " << get_code() << " " << get_str_code() << std::endl; +#endif + + // 親 article クラスにシグナルを打ってツリー構造が変わったことを教える + m_sig_finished.emit(); + + clear(); +} + + +// +// 鯖から生の(複数)行のデータを受け取ってdat形式に変換して add_one_dat_line() に出力 +// +void NodeTreeBase::add_raw_lines( char* rawlines ) +{ + // 保存前にrawデータを加工 + rawlines = process_raw_lines( rawlines ); + + // キャッシュに保存 + size_t lng = strlen( rawlines ); + 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" ); + } + } + + // 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 ) ) != '\0' ) ++pos; + + // データベース更新 + // Article::slot_node_updated()が呼び出される + if( num_before != m_id_header ) 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; + + int i; + NODE* header = create_header_node(); + m_vec_header[ m_id_header ] = header; + + // レス番号 + char tmplink[ LNG_RES ], tmpstr[ LNG_RES ]; + snprintf( tmpstr, LNG_RES, "%d", header->id_header ); + snprintf( tmplink, LNG_RES,"%s%d", PROTO_RES, header->id_header ); + header->headinfo->node_res = create_linknode( tmpstr, strlen( tmpstr ) , tmplink, strlen( tmplink ), COLOR_CHAR_LINK, true ); + + const char* section[ SECTION_NUM ]; + int section_lng[ SECTION_NUM ]; + + // セクション分けしながら壊れてないかチェック + for( i = 0; i < SECTION_NUM ; ++i ){ + + section[ i ] = pos; + while( !( *pos == '<' && *( pos + 1 ) == '>' ) && ( *pos != '\0' && *pos != '\n' ) ) ++pos; + section_lng[ i ] = ( int )( pos - section[ i ] ); + + if( ( *pos == '\0' || *pos == '\n' ) && i < SECTION_NUM -1 ) break; // 壊れてる + + if ( !( *pos == '\0' || *pos == '\n' ) ) pos += 2; // "<>"の分 + } + + // 壊れている + if( i != SECTION_NUM ){ + +#ifdef _DEBUG + std::cout << header->id_header << " is broken section = " << i << std::endl; + std::cout << datline << std::endl; +#endif + + createTextNode( "broken", COLOR_CHAR ); + createBrNode(); + createBrNode(); + + return pos; + } + + // 名前 + parseName( header, section[ 0 ], section_lng[ 0 ] ); + + // メール + parseMail( section[ 1 ], section_lng[ 1 ] ); + + // 日付とID + parse_date_id( header, section[ 2 ], section_lng[ 2 ] ); + + // 改行してレベル下げ + createBrNode(); + create_node_downleft(); + create_node_downleft(); + + // 本文 + parse_html( section[ 3 ], section_lng[ 3 ], COLOR_CHAR ); + + // サブジェクト + if( header->id_header == 1 ){ + m_subject = std::string( section[ 4 ] ).substr( 0, section_lng[ 4 ] ); + +#ifdef _DEBUG + std::cout << "subject = " << m_subject << std::endl; +#endif + } + + return pos; +} + + +// +// 名前 +// +void NodeTreeBase::parseName( NODE* header, const char* str, int lng ) +{ + const bool digitlink = true; + const bool bold = true; + int pos_trip_begin = 0; + int pos_trip_end = 0; + int i; + + NODE* node = createTextNode( " 名前:", COLOR_CHAR ); + + // トリップ付きの時は中の数字をリンクにしない + for( i = 0; i < lng; ++i ) if( str[ i ] == '<' && str[ i+2 ] == 'b' ) break; + if( i != lng ){ + + pos_trip_begin = i; + pos_trip_end = lng -1; + for( i = pos_trip_begin + 4; i < lng; ++i ){ + if( str[ i ] == '<' && str[ i+1 ] == 'b' ){ + pos_trip_end = i + 2; + break; + } + } + +#ifdef _DEBUG + char tmp_str[256]; + memset( tmp_str, 0, 256); + memcpy(tmp_str, str, lng); + + std::cout << "NodeTreeBase::parseName trip = " << tmp_str + << " begin = " << pos_trip_begin << " end = " << pos_trip_end << std::endl; +#endif + // トリップの前(名前部分) + parse_html( str, pos_trip_begin, COLOR_CHAR_NAME, digitlink, bold ); + + // トリップ + parse_html( str + pos_trip_begin, pos_trip_end - pos_trip_begin + 1, COLOR_CHAR_NAME, false, bold ); + + // トリップの後 + if( pos_trip_end < lng-1 ) + parse_html( str + pos_trip_end + 1, lng - pos_trip_end - 1, COLOR_CHAR_NAME, digitlink, bold ); + } + + // デフォルト名無しと同じときはアンカーを作らない + else if( strncmp( m_default_noname.data(), str, lng ) == 0 ) parse_html( str, lng, COLOR_CHAR_NAME, false, bold ); + + // 通常の場合は中に数字があったらアンカーにする + else parse_html( str, lng, COLOR_CHAR_NAME, digitlink, bold ); + + // プレインな名前取得 + node = node->next_node; + std::string str_tmp; + while( node ){ + if( node->text ) str_tmp += node->text; + node = node->next_node; + } + + header->headinfo->name = ( char* )m_heap.heap_alloc( str_tmp.length() +2 ); + memcpy( header->headinfo->name, str_tmp.c_str(), str_tmp.length() ); +} + + +// +// メール +// +void NodeTreeBase::parseMail( const char* str, int lng ) +{ + createTextNode( " [", COLOR_CHAR ); + parse_html( str, lng, COLOR_CHAR, true ); + createTextNode( "]:", COLOR_CHAR ); +} + + +// +// 日付とIDとBE +// +void NodeTreeBase::parse_date_id( NODE* header, const char* str, int lng ) +{ + int start = 0; + int lng_text = 0; + char tmplink[ LNG_LINK ]; + + int lng_id_tmp; + char tmpid[ LNG_ID ]; + + for(;;){ + + // 空白を飛ばす + while( start + lng_text < lng && str[ start + lng_text ] == ' ' ) ++lng_text; + + int start_block = start + lng_text; + int lng_block = 0; + while( start_block + lng_block < lng && str[ start_block + lng_block ] != ' ' ) ++lng_block; + if( !lng_block ) break; + + // ID ( ??? の時は除く ) + if( str[ start_block ] == 'I' && str[ start_block + 1 ] == 'D' && str[ start_block + 3 ] != '?' ){ + + // フラッシュ + if( lng_text ) createTextNodeN( str + start, lng_text, COLOR_CHAR ); + start = start_block + lng_block; + lng_text = 0; + + // id 取得 + lng_id_tmp = lng_block -3; + memcpy( tmpid, str + start_block + 3, 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 ); + + // リンク作成 + NODE* node_id_name = create_linknode( "ID:", 3 , tmplink, strlen( tmplink ), COLOR_CHAR ); + createTextNodeN( tmpid, lng_id_tmp, COLOR_CHAR); + + // ヘッダにリンクノードへのポインタを登録 + header->headinfo->node_id_name = node_id_name; + + // 発言回数を調べる + count_id_name( header, tmplink ); + } + + // BE: + else if( str[ start_block ] == 'B' && str[ start_block + 1 ] == 'E' ){ + + // フラッシュ + if( lng_text ) createTextNodeN( str + start, lng_text, COLOR_CHAR ); + start = start_block + lng_block; + lng_text = 0; + + // id 取得 + lng_id_tmp = 0; + while( str[ start_block + lng_id_tmp ] != '-' && lng_id_tmp < lng_block ) ++lng_id_tmp; + lng_id_tmp -= 3; + memcpy( tmpid, str + start_block + 3, 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_linknode( "BE:", 3 , tmplink, strlen( tmplink ), COLOR_CHAR ); + createTextNodeN( tmpid, lng_id_tmp, COLOR_CHAR); + } + + // テキスト + else lng_text += lng_block; + } + + // フラッシュ + if( lng_text ) createTextNodeN( str + start, lng_text, COLOR_CHAR ); +} + + +// +// HTMLパーサ +// +// digitlink : true の時は数字が現れたら手当たり次第にアンカーにする( parseName() などで使う ) +// false なら数字の前に >> がついてるときだけアンカーにする +// +// bold : ボールド標示 +// +void NodeTreeBase::parse_html( const char* str, int lng, int color_text, bool digitlink, bool bold ) +{ + const char* pos = str; + const char* pos_end = str + lng; + int lng_text = 0; + + // 行頭の1個以上の空白は除く + if( ! digitlink ) while( *pos == ' ' && *( pos + 1 ) != ' ' ) ++pos; + + for( ; pos < pos_end; ++pos ){ + + + /////////////////////// + // HTMLタグ + if( *pos == '<' ){ + + // 改行
+ if( *( pos + 1 ) == 'b' && *( pos + 2 ) == 'r' ){ + + // フラッシュ + if( *( pos - 1 ) == ' ' ) --lng_text; // 改行前の空白を取り除く + createTextNodeN( m_parsed_text, lng_text, color_text, bold ); lng_text = 0; + + pos += 4; + + // 改行ノード作成 + createBrNode(); + + //改行直後の空白を取り除く + if( *pos == ' ' ) ++pos; + + // 行頭の1個以上の空白を除く + while( *pos == ' ' && *( pos + 1 ) != ' ' ) ++pos; + } + + // + else if( *( pos + 1 ) == '/' && *( pos + 2 ) == 'a' && *( pos + 3 ) == '>' ) pos += 4; + + // その他のタグは無視。タグを取り除いて中身だけを見る + else { + + // フラッシュ + createTextNodeN( m_parsed_text, lng_text, color_text, bold ); lng_text = 0; + + while( pos < pos_end && *pos != '>' ) ++pos; + ++pos; + } + + // forのところで++されるので--しとく + --pos; + continue; + } + + + /////////////////////// + // アンカーのチェック + int n_in = 0; + int n_out = 0; + char tmpstr[ LNG_LINK ], tmplink[ LNG_LINK ]; + if( check_anchor( pos, n_in, tmpstr, tmplink, LNG_LINK, 2 * digitlink ) ){ + + // フラッシュしてからリンクノードつくる + createTextNodeN( m_parsed_text, lng_text, color_text, bold ); lng_text = 0; + + create_linknode( tmpstr, strlen( tmpstr), tmplink, strlen( tmplink ), COLOR_CHAR_LINK, bold ); + pos += n_in; + + // , や = が続くとき + while( check_anchor( pos, n_in, tmpstr, tmplink, LNG_LINK, 1 ) ){ + create_linknode( tmpstr, strlen( tmpstr), tmplink, strlen( tmplink ), COLOR_CHAR_LINK, bold ); + pos += n_in; + } + + // forのところで++されるので--しとく + --pos; + continue; + } + + // digitlink = true の時は数字が長すぎるときは飛ばす( 例えば 名前: 12345678 みたいなとき ) + if( digitlink ){ + --n_in; + while( n_in-- > 0 ) m_parsed_text[ lng_text++ ] = *(pos++); + } + + /////////////////////// + // http, ttp のチェック + int create_link = 0; + if( *( pos ) == 'h' && *( pos + 1 ) == 't' && *( pos + 2 ) == 't' && *( pos + 3 ) == 'p' + && *( pos + 4 ) == ':' && *( pos + 5 ) == '/' && *( pos + 6 ) == '/' ) create_link = 6; + if( *( pos ) == 't' && *( pos + 1 ) == 't' && *( pos + 2 ) == 'p' + && *( pos + 3 ) == ':' && *( pos + 4 ) == '/' && *( pos + 5 ) == '/' ) create_link = 5; + if( create_link ){ + + // フラッシュしてからリンクノードつくる + createTextNodeN( m_parsed_text, lng_text, color_text, bold ); lng_text = 0; + + int n = 0; + while( *( pos + n ) != '<' && *( pos + n ) != ' ' && pos + n < pos_end + && *( pos + n ) > 0 // 半角文字が続く限り + ) ++n; + + // m_parsed_text に http://〜をコピー + int offset = 0; + if( create_link == 5 ){ // ttp:// の場合 + m_parsed_text[ 0 ] = 'h'; + offset = 1; + } + memcpy( m_parsed_text + offset, pos, n ); + + // リンクノード作成 + bool img = false; + int color_tmp = COLOR_CHAR_LINK; + + // 画像かどうか + if( DBIMG::is_loadable( m_parsed_text, n + offset ) ){ + img = true; + color_tmp = COLOR_IMG_NOCACHE; + } + + create_linknode( m_parsed_text + offset, n, m_parsed_text , n + offset, color_tmp, bold, img ); + pos += n; + + // forのところで++されるので--しとく + --pos; + continue; + } + + + /////////////////////// + // 特殊文字デコード + if( *pos == '&' ){ + + int ret_decode = DBTREE::decode_char( pos, n_in, m_parsed_text + lng_text, n_out ); + + if( ret_decode != NODE_NONE ){ + + // 文字以外の空白ノードならフラッシュして空白ノード追加 + if( ret_decode != NODE_TEXT ){ + createTextNodeN( m_parsed_text, lng_text, color_text, bold ); + lng_text = 0; + createSpNode( ret_decode ); + } + else lng_text += n_out; + + pos += n_in; + + // forのところで++されるので--しとく + --pos; + continue; + } + } + + m_parsed_text[ lng_text++ ] = *pos; + } + + createTextNodeN( m_parsed_text, lng_text, color_text, bold ); +} + + + +// +// アンカーが現れたかチェックして文字列を取得する関数 +// +// 入力 +// str_in : 入力文字列の先頭アドレス +// mode : 0 なら >> が先頭に無ければアンカーにしない、1 なら,か=があればアンカーにする、2 なら数字が先頭に来たらアンカーにする +// +// 出力 +// n_in : str_in から何バイト読み取ったか +// str_out : (画面に表示される)文字列 +// str_link : リンクの文字列 +// lng_link : str_linkのバッファサイズ +// +// 戻り値 : アンカーが現れれば true +// +bool NodeTreeBase::check_anchor( const char* str_in, int& n_in, char* str_out, char* str_link, int lng_link, int mode ) +{ + char tmp_out[ 64 ]; + int lng_out = 0; + int num, num_to; + 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++ ] = 0xef; + tmp_out[ lng_out++ ] = 0xbc; + tmp_out[ lng_out++ ] = 0x9e; + pos += 3; + } + else if( i == 0 ) return false; + } + } + + // カンマかイコールをチェック + else if( mode == 1 ){ + + if( *( pos ) == '=' || *( pos ) == ',' ){ + tmp_out[ lng_out ] = *( pos ); + ++lng_out; + ++pos; + } + + // utf-8で"、" + else if( ( unsigned char )( *pos ) == 0xe3 && ( unsigned char ) ( *( pos + 1 ) ) == 0x80 + && ( unsigned char ) ( *( pos + 2 ) ) == 0x81 ){ + tmp_out[ lng_out++ ] = 0xe3; + tmp_out[ lng_out++ ] = 0x80; + tmp_out[ lng_out++ ] = 0x81; + pos += 3; + } + + else return false; + } + + // 数字かチェック + unsigned int n, dig; + num = num_to = MISC::str_to_uint( pos, dig, n ); + if( dig == 0 || dig > MAX_LINK_DIGIT || num == 0 ){ + + // モード2で数字が長すぎるときは飛ばす + if( mode == 2 && dig > MAX_LINK_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'; + + // アンカー文字 + snprintf( str_link, lng_link, "%s%d", PROTO_ANCHORE, num ); + pos += n; + lng_out += n; + + + // dat形式では ">>数字"のパターンの場合には後にがついてるのでのぞく + if( *( pos ) == '<' && *( pos + 1 ) == '/' && *( pos + 2 ) == 'a' && *( pos + 3 ) == '>' ) pos += 4; + + + // "-" でつながってる場合同じことをもう一回 + 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 ){ + + num_to = MISC::str_to_uint( pos + offset, dig, n ); + if( dig && dig <= MAX_LINK_DIGIT && num ){ + + // 画面に表示する文字 + memcpy( str_out + lng_out, pos, offset + n ); + str_out[ lng_out + offset + n ] = '\0'; + + // アンカー文字をもう一度作成 + snprintf( str_link, lng_link, "%s%d-%d", PROTO_ANCHORE, num, num_to ); + pos += offset + n; + lng_out += offset + n; + } + } + + //">>数字-数字"のパターンの時にをのぞく + if( *( pos ) == '<' && *( pos + 1 ) == '/' && *( pos + 2 ) == 'a' && *( pos + 3 ) == '>' ) pos += 4; + + n_in = ( int )( pos - str_in ); + + + // 参照数チェック + const int range = RANGE_REF; // >>1-1000 みたいなアンカーは弾く + if( num_to >= num && num_to - num < range ){ + + for( int i = MAX( 1, num ); i <= MIN( m_id_header -1, num_to ) ; ++i ){ + + NODE* tmphead = m_vec_header[ i ]; + if( tmphead && tmphead->headinfo && tmphead->headinfo->node_res ){ + + ++( tmphead->headinfo->num_reference ); + if( tmphead->headinfo->num_reference >= 3 ) tmphead->headinfo->node_res->color_text = COLOR_CHAR_LINK_RED; + else if( tmphead->headinfo->num_reference >= 1 ) tmphead->headinfo->node_res->color_text = COLOR_CHAR_LINK_PUR; + } + } + } + + return true; +} + + +// +// 名前IDが何回現れたかカウントする ( parse_date_id() 参照 ) +// +void NodeTreeBase::count_id_name( NODE* header, const char* str_id ) +{ + // 同じIDのレスの個数 + if( header->headinfo->node_id_name ){ + + int num_id_name = 1; + for( int i = 1 ; i < header->id_header; ++i ){ + if( m_vec_header[ i ]->headinfo->node_id_name + && strcmp( str_id, m_vec_header[ i ]->headinfo->node_id_name->linkinfo->link ) == 0 ){ + + // 対象よりも前のヘッダ情報を更新 + NODE* tmphead = m_vec_header[ i ]; + ++( tmphead->headinfo->num_id_name ); + if( tmphead->headinfo->num_id_name >= 4 ) tmphead->headinfo->node_id_name->color_text = COLOR_CHAR_LINK_RED; + else if( tmphead->headinfo->num_id_name >= 2 ) tmphead->headinfo->node_id_name->color_text = COLOR_CHAR_LINK; + + ++num_id_name; + } + } + + // ヘッダ情報更新 + header->headinfo->num_id_name = num_id_name; + if( num_id_name >= 4 ) header->headinfo->node_id_name->color_text = COLOR_CHAR_LINK_RED; + else if( num_id_name >= 2 ) header->headinfo->node_id_name->color_text = COLOR_CHAR_LINK; + } +} diff --git a/src/dbtree/nodetreebase.h b/src/dbtree/nodetreebase.h new file mode 100644 index 000000000..3051e6df2 --- /dev/null +++ b/src/dbtree/nodetreebase.h @@ -0,0 +1,185 @@ +// ライセンス: 最新のGPL + +// +// ノードツリー( DOMみたいな木構造 )のベースクラス および DAT & HTMLパーサ +// + +#ifndef _NODETREEBASE_H +#define _NODETREEBASE_H + +#include "node.h" + +#include "skeleton/loadable.h" + +#include "jdlib/heap.h" + +namespace JDLIB +{ + class LOADERDATA; +} + +namespace DBTREE +{ + //ノードツリーのベースクラス + 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 ) + size_t m_lng_dat; + + // true ならレジューム読み込み + bool m_resume; + + // 現在処理中のヘッダ番号( つまりロード中でないなら総レス数になる ) + int m_id_header; + + // サーバー側であぼーんがあったりしてスレが壊れている + bool m_broken; + + JDLIB::HEAP m_heap; + NODE** m_vec_header; // レスのヘッダのポインタの配列 + + std::string m_subject; + + // ロード用変数 + char* m_buffer_lines; + int m_byte_buffer_lines_left; + char* m_parsed_text; + + // キャッシュ保存用ファイルハンドラ + FILE *m_fout; + + // パース用雑用変数 + NODE* m_node_previous; + int m_id_node; + + // その他のエラーメッセージ + std::string m_ext_err; + + protected: + + void set_resume( bool resume ) { m_resume = resume; } + void set_broken( bool broken ) { m_broken = broken; } + const int id_header() const { 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 ); + virtual ~NodeTreeBase(); + + bool empty(); + + 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; } + const int get_res_number(); + const size_t lng_dat() const { return m_lng_dat; } + const bool is_broken() const{ return m_broken; } + const std::string& get_ext_err() const { return m_ext_err; } + + // number番のレスのヘッダノードのポインタを返す + NODE* res_header( int number ); + + // number番の名前 + const std::string get_name( int number ); + + // number番のID + const 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 ); + + // number番に含まれるURLをすべてリストにして取得 + std::list< std::string > get_URLs( int number ); + + // URL を含むレス番号をリストにして取得 + std::list< int > get_res_with_url(); + + // number番のレスを参照しているレス番号をリストにして取得 + std::list< int > get_reference( int number ); + + // number番のレスの文字列を返す + // ref == true なら先頭に ">" を付ける + const std::string get_res_str( int number, bool ref = false ); + + // 明示的にhtml を加える + // パースして追加したノードのポインタを返す + // html は UTF-8 であること + NODE* append_html( const std::string& html ); + + // 明示的にdat を加える + // パースして追加したノードのポインタを返す + // dat は UTF-8 であること + NODE* append_dat( const std::string& dat ); + + // ロード開始 + void download_dat(); + + 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; + } + + virtual void receive_data( const char* data, size_t size ); + virtual void receive_finish(); + + private: + + NODE* createNode(); + NODE* create_header_node(); + NODE* createBrNode(); + NODE* createSpNode( const int& type ); + NODE* create_node_downleft(); + NODE* create_linknode( const char* text, int n, + const char* link, int n_link, int color_text, bool bold = false, bool img = false ); + NODE* createTextNode( const char* text, int color_text, bool bold = false ); + NODE* createTextNodeN( const char* text, int n, int color_text, bool bold = false ); + + // 以下、構文解析用関数 + void add_raw_lines( char* rawines ); + const char* add_one_dat_line( const char* datline ); + + void parseName( NODE* header, const char* str, int lng ); + void parseMail( const char* str, int lng ); + void parse_date_id( NODE* header, const char* str, int lng ); + void parse_html( const char* str, int lng, int color_text, bool digitlink = false, bool bold = false ); + void parseBr( ); + + bool check_anchor( const char* str_in, int& n, char* str_out, char* str_link, int lng_link, int mode = 0 ); + int str_to_int( const char* str, int& n ); + void count_id_name( NODE* header, const char* str_id ); + }; +} + +#endif diff --git a/src/dbtree/nodetreejbbs.cpp b/src/dbtree/nodetreejbbs.cpp new file mode 100644 index 000000000..2bf5dcfda --- /dev/null +++ b/src/dbtree/nodetreejbbs.cpp @@ -0,0 +1,258 @@ +// ライセンス: 最新のGPL + +//#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( byte + lng_sec[ num ] < BUF_SIZE_ICONV_OUT ); \ +memcpy( m_decoded_lines + byte, lines + pos_sec[ num ], lng_sec[ num ] ); \ +byte += lng_sec[ num ]; \ +} } while( 0 ) + + +using namespace DBTREE; + + +NodeTreeJBBS::NodeTreeJBBS( const std::string url, const std::string& date_modified ) + : NodeTreeBase( url, date_modified ) + , m_iconv( 0 ) + , m_decoded_lines( 0 ) +{ +#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 + + clear(); +} + + +// +// バッファなどのクリア +// +void NodeTreeJBBS::clear() +{ +#ifdef _DEBUG + std::cout << "NodeTreeJBBS::clear : " << get_url() << std::endl; +#endif + NodeTreeBase::clear(); + + // iconv 削除 + if( m_iconv ) delete m_iconv; + m_iconv = NULL; + + if( m_decoded_lines ) free( m_decoded_lines ); + m_decoded_lines = NULL; +} + + + +// +// ロード実行前に呼ぶ初期化関数 +// +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 = new JDLIB::Iconv( charset ); + + if( ! m_decoded_lines ) m_decoded_lines = ( char* )malloc( 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.size_buf = CONFIG::get_loader_bufsize(); + data.timeout = CONFIG::get_loader_timeout(); + + if( ! date_modified().empty() ) data.modified = date_modified(); + +#ifdef _DEBUG + std::cout << "NodeTreeJBBS::create_loader : " << data.url << std::endl; +#endif +} + + +// +// raw データを dat 形式に変換 +// + +#define MIN_SECTION 5 +#define MAX_SECTION 8 + +const char* NodeTreeJBBS::raw2dat( char* rawlines, int& byte ) +{ + assert( m_iconv != NULL ); + assert( m_decoded_lines != NULL ); + + 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 + + // セクション分けして再合成する + 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 + char broken_str[] = "あぼ〜ん<><>あぼ〜ん<>あぼ〜ん<>\n"; + int lng_broken = strlen( broken_str ); + memcpy( m_decoded_lines + byte, broken_str, lng_broken ); + byte += lng_broken; + ++number; + } + + // 名前 + APPEND_SECTION( 1 ); + memcpy( m_decoded_lines + byte, "<>", 2 ); + byte += 2; + + // メアド + APPEND_SECTION( 2 ); + memcpy( m_decoded_lines + byte, "<>", 2 ); + byte += 2; + + // 日付 + APPEND_SECTION( 3 ); + + // ID + int i = 6; + if( lng_sec[ i ] ){ + + memcpy( m_decoded_lines + byte, " ID:", 4 ); + byte += 4; + + memcpy( m_decoded_lines + byte, lines + pos_sec[ i ], lng_sec[ i ] ); + byte += lng_sec[ i ]; + } + memcpy( m_decoded_lines + byte, "<>", 2 ); + byte += 2; + + // 本文 + APPEND_SECTION( 4 ); + memcpy( m_decoded_lines + byte, "<>", 2 ); + byte += 2; + + // タイトル + APPEND_SECTION( 5 ); + + m_decoded_lines[ byte++ ] = '\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( lines[ pos ] != '\n' && pos < byte_lines ) ++pos; + ++pos; + section = 0; + memset( lng_sec, 0, sizeof( int ) * MAX_SECTION ); + } + } + } + + m_decoded_lines[ byte ] = '\0'; + +#ifdef _DEBUG + std::cout << "byte = " << byte << std::endl; +#endif + + return m_decoded_lines; +} diff --git a/src/dbtree/nodetreejbbs.h b/src/dbtree/nodetreejbbs.h new file mode 100644 index 000000000..a9d0637a2 --- /dev/null +++ b/src/dbtree/nodetreejbbs.h @@ -0,0 +1,39 @@ +// ライセンス: 最新のGPL + +// +// JBBS型ノードツリー +// + +#ifndef _NODETREEJBBS_H +#define _NODETREEJBBS_H + +#include "nodetreebase.h" + +namespace JDLIB +{ + class Iconv; +} + + +namespace DBTREE +{ + class NodeTreeJBBS : public NodeTreeBase + { + JDLIB::Iconv* m_iconv; + char* m_decoded_lines; + + public: + + NodeTreeJBBS( const std::string url, const std::string& date_modified ); + ~NodeTreeJBBS(); + + protected: + + virtual void clear(); + virtual void init_loading(); + virtual void create_loaderdata( JDLIB::LOADERDATA& data ); + virtual const char* raw2dat( char* rawlines, int& byte ); + }; +} + +#endif diff --git a/src/dbtree/nodetreemachi.cpp b/src/dbtree/nodetreemachi.cpp new file mode 100644 index 000000000..b882a1f35 --- /dev/null +++ b/src/dbtree/nodetreemachi.cpp @@ -0,0 +1,276 @@ +// ライセンス: 最新のGPL + +//#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 + + +using namespace DBTREE; + + +NodeTreeMachi::NodeTreeMachi( const std::string url, const std::string& date_modified ) + : NodeTreeBase( url, date_modified ) + , m_regex( 0 ) + , m_iconv( 0 ) + , m_decoded_lines( 0 ) + , m_buffer( 0 ) +{ +#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 + + clear(); +} + + +// +// バッファなどのクリア +// +void NodeTreeMachi::clear() +{ +#ifdef _DEBUG + std::cout << "NodeTreeMachi::clear : " << get_url() << std::endl; +#endif + NodeTreeBase::clear(); + + // regex 削除 + if( m_regex ) delete m_regex; + m_regex = NULL; + + // iconv 削除 + if( m_iconv ) delete m_iconv; + m_iconv = NULL; + + if( m_decoded_lines ) free( m_decoded_lines ); + m_decoded_lines = NULL; + + if( m_buffer ) free( m_buffer ); + m_buffer = NULL; +} + + + +// +// ロード実行前に呼ぶ初期化関数 +// +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 = new JDLIB::Regex(); + + // iconv 初期化 + std::string charset = DBTREE::board_charset( get_url() ); + if( ! m_iconv ) m_iconv = new JDLIB::Iconv( charset ); + + if( ! m_decoded_lines ) m_decoded_lines = ( char* )malloc( BUF_SIZE_ICONV_OUT ); + if( ! m_buffer ) m_buffer = ( char* )malloc( BUF_SIZE_ICONV_OUT + 64 ); + + m_tmp_buffer = std::string(); +} + + + + +// +// ロード用データ作成 +// +void NodeTreeMachi::create_loaderdata( JDLIB::LOADERDATA& data ) +{ + std::stringstream ss; + ss << get_url(); + + // レジュームはしない代わりにスレを直接指定 + set_resume( false ); + if( id_header() ) ss << "&START=" << 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.size_buf = CONFIG::get_loader_bufsize(); + data.timeout = CONFIG::get_loader_timeout(); + + if( ! date_modified().empty() ) data.modified = date_modified(); + +#ifdef _DEBUG + std::cout << "NodeTreeMachi::create_loader : " << data.url << std::endl; +#endif +} + + + +// +// キャッシュに保存する前の前処理 +// +// 余計なタグを取り除いて本文だけ取り出す +// +char* NodeTreeMachi::process_raw_lines( char* rawlines ) +{ + std::string buffer; + + // 入力データを行ごとに分割して本文だけ取り出す + std::list< std::string > lines = MISC::get_lines( rawlines, true ); + std::list< std::string >::iterator it; + for( it = lines.begin(); it != lines.end(); ++it ){ + + std::string& line = (*it); + + if( m_tmp_buffer.empty() ){ + + if( line.find( "
" ) == 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( 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(); + } + + int byte = buffer.length(); + memcpy( m_buffer, buffer.c_str(), byte ); + m_buffer[ byte ] = '\0'; + + return m_buffer; +} + + +// +// raw データを dat 形式に変換 +// +const char* NodeTreeMachi::raw2dat( char* rawlines, int& byte ) +{ +#ifdef _DEBUG + std::cout << "NodeTreeMachi::raw2dat\n"; +#endif + + int next = id_header() + 1; + + std::string reg( "
([1-9][0-9]*) ?名前:(|]*>) ?(]*>)?([^<]*)()? ?.+ ?投稿日: ?([^<]*)( ]*>\\[ ?(.*) ?\\])?
?(.*) ?

$" ); + + 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, true ); + std::list< std::string >::iterator it; + for( it = lines.begin(); it != lines.end(); ++it ){ + + std::string& line = (*it); + if( line.empty() ) continue; + + if( ! m_regex->exec( reg, line ) ){ +#if _DEBUG + std::cout << "失敗\n"; + std::cout << line << std::endl; +#endif + continue; + } + +#if _DEBUG +/* + std::cout << "1 " << m_regex->str( 1 ) << std::endl; + std::cout << "2 " << m_regex->str( 2 ) << std::endl; + std::cout << "3 " << m_regex->str( 3 ) << std::endl; + std::cout << "4 " << m_regex->str( 4 ) << std::endl; + std::cout << "5 " << m_regex->str( 5 ) << std::endl; + std::cout << "6 " << m_regex->str( 6 ) << std::endl; + std::cout << "7 " << m_regex->str( 7 ) << std::endl; + std::cout << "8 " << m_regex->str( 8 ) << std::endl; + std::cout << "9 " << m_regex->str( 9 ) << std::endl; + std::cout << "10 " << m_regex->str( 10 ) << std::endl; +*/ +#endif + int num = atoi( m_regex->str( 1 ).c_str() ); + std::string name = m_regex->str( 5 ); + std::string mail = m_regex->str( 3 ); + std::string date = m_regex->str( 7 ); + if( !m_regex->str( 9 ).empty() ) date += " HOST:" + m_regex->str( 9 ); + std::string body = m_regex->str( 10 ); + + while( next < num ){ +#ifdef _DEBUG + std::cout << "abone = " << num << std::endl; +#endif + buffer += "あぼ〜ん<><><> あほ〜ん <><>\n"; + next++; + } + + buffer += name + "<>" + mail + "<>" + date + "<> " + body + " <><>\n"; + next++; + } + + +// m_decoded_lines; + +// byte = 0; +// rawlines[ 0 ] = '\0'; +// return NULL; + + if( buffer.length() > BUF_SIZE_ICONV_OUT ){ + MISC::ERRMSG( "buffer over flow in NodeTreeMachi::process_raw_lines" ); + buffer = std::string(); + } + + byte = buffer.length(); + memcpy( m_decoded_lines, buffer.c_str(), byte ); + m_decoded_lines[ byte ] = '\0'; + +// std::cout << "byte = " << byte << std::endl << m_decoded_lines << std::endl; + + return m_decoded_lines; +} diff --git a/src/dbtree/nodetreemachi.h b/src/dbtree/nodetreemachi.h new file mode 100644 index 000000000..16d6b7a20 --- /dev/null +++ b/src/dbtree/nodetreemachi.h @@ -0,0 +1,45 @@ +// ライセンス: 最新のGPL + +// +// Machi型ノードツリー +// + +#ifndef _NODETREEMACHI_H +#define _NODETREEMACHI_H + +#include "nodetreebase.h" + +namespace JDLIB +{ + class Iconv; + class Regex; +} + + +namespace DBTREE +{ + class NodeTreeMachi : public NodeTreeBase + { + JDLIB::Regex* m_regex; + JDLIB::Iconv* m_iconv; + char* m_decoded_lines; + char* m_buffer; + + std::string m_tmp_buffer; + + public: + + NodeTreeMachi( const std::string url, const std::string& date_modified ); + ~NodeTreeMachi(); + + protected: + + virtual void clear(); + virtual void init_loading(); + virtual void create_loaderdata( JDLIB::LOADERDATA& data ); + virtual char* process_raw_lines( char* rawlines ); + virtual const char* raw2dat( char* rawlines, int& byte ); + }; +} + +#endif diff --git a/src/dbtree/root.cpp b/src/dbtree/root.cpp new file mode 100644 index 000000000..4d23ba73e --- /dev/null +++ b/src/dbtree/root.cpp @@ -0,0 +1,695 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +//#define _SHOW_BOARD +#include "jddebug.h" + +#include "root.h" +#include "boardfactory.h" +#include "boardbase.h" +#include "articlebase.h" + +#include "jdlib/jdiconv.h" +#include "jdlib/jdregex.h" +#include "jdlib/miscutil.h" +#include "jdlib/miscmsg.h" +#include "jdlib/loaderdata.h" + +#include "global.h" +#include "command.h" +#include "config/globalconf.h" +#include "cache.h" + +#include + +#define SIZE_OF_RAWDATA ( 2 * 1024 * 1024 ) // bbsmenu.html の最大サイズ + +using namespace DBTREE; + + +// is_moved() の戻り値 +enum +{ + BOARD_MOVED = 0, + BOARD_NEW, + BOARD_EXISTS +}; + + +Root::Root() + : SKELETON::Loadable() + , m_rawdata ( 0 ) + , m_lng_rawdata( 0 ) + , m_board_null( 0 ) +{ + m_xml_bbsmenu.clear(); + clear(); + clear_load_data(); + load_movetable(); + load_cache(); + load_etc(); + + m_board_null = new DBTREE::BoardBase( "", "", "" ); +} + + + +// +// デストラクタで子Boardクラスをすべて削除 +// +Root::~Root() +{ +#ifdef _DEBUG + std::cout << "Root::~Root\n"; +#endif + + clear(); + + std::list< BoardBase* >::iterator it; + for( it = m_list_board.begin(); it != m_list_board.end(); ++it ) delete *( it ); + + if( m_board_null ) delete m_board_null; +} + + +void Root::clear() +{ + if( m_rawdata ) free( m_rawdata ); + m_rawdata = NULL; + m_lng_rawdata = 0; +} + + +// +// URLから BoardBase を取得する関数 +// +// count は無限再帰呼び出し禁止用 +// +BoardBase* Root::get_board( const std::string& url, int count ) +{ +#ifdef _DEBUG + std::cout << "Root::get_board : count = " << count << " url = " << url << std::endl; +#endif + + if( count > 20 ){ + MISC::ERRMSG( "Root::get_board: could not find board" ); + return m_board_null; + } + + // 先頭が http:// でなかったら足して再帰呼び出し + if( url.find( "http://" ) != 0 ) return get_board( "http://" + url , count + 1 ); + + // サーチ + std::list< BoardBase* >::iterator it; + for( it = m_list_board.begin(); it != m_list_board.end(); ++it ){ + + BoardBase* board = *( it ); + if( board->equal( url ) ){ + board->read_info(); // 板情報の取得( 詳しくはBoardBase::read_info()をみること ) + return board; + } + } + + // 移転した時はrootを付け変えて再帰呼び出し + std::list< MOVETABLE >::iterator it_move; + for( it_move = m_movetable.begin(); it_move != m_movetable.end(); ++it_move ){ + + if( url.find( ( *it_move ).path_board ) != std::string::npos + && url.find( ( *it_move ).old_root ) == 0 ){ + + std::string new_url = ( *it_move ).new_root + ( *it_move ).path_board + "/"; + +#ifdef _DEBUG + std::cout << url << " is moved to " << new_url << std::endl; +#endif + return get_board( new_url, count + 1 ); + } + } + + // 2ch型の場合、板パスを見てもし一致したら新ホストに移転したと判断して移転テーブルを更新する + if( is_2ch( url ) ){ + + std::list< BoardBase* >::iterator it; + for( it = m_list_board.begin(); it != m_list_board.end(); ++it ){ + + BoardBase* board = *( it ); + + if( url.find( board->get_path_board() + "/" ) != std::string::npos ){ + + // 板移転テーブルを更新 + MOVETABLE movetable; + movetable.old_root = MISC::get_hostname( url ); + movetable.new_root = board->get_root(); + movetable.path_board = board->get_path_board(); + m_movetable.push_back( movetable ); + + std::ostringstream ss; + ss << board->get_name() << " is moved" << std::endl + << "old url = " << movetable.old_root + board->get_path_board() << "/" << std::endl + << "new url = " << board->url_boardbase() << std::endl; + MISC::MSG( ss.str() ); + + //移転テーブル保存 + save_movetable(); + + board->read_info(); + return board; + } + } + + // 最後が "/" で終わってなかったら足して再帰呼び出し + if( url[ url.length() ] != '/' ) return get_board( url + "/" , count + 1 ); + } + + // それでも見つからなかったらNULLクラスを返す + +#ifdef _DEBUG + std::cout << "not found\nreturn Board_Null\n"; +#endif + + return m_board_null; +} + + +// ローカルキャッシュから板一覧XML読み込み +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 + + if( CACHE::load_rawdata( file_in, m_xml_bbsmenu ) ){ + + // xml の内容からDBに板を登録 + update_boards( m_xml_bbsmenu ); + } +} + + +// +// サーバから bbsmenu.html を読み込んで xml に変換開始 +// +// 読み終わったらreceive_finish()でXMLに変換して"update_bbslist"コマンド発行 +// +void Root::download_bbsmenu() +{ + if( is_loading() ) return; + + clear(); + m_xml_bbsmenu.clear(); + m_rawdata = ( char* )malloc( SIZE_OF_RAWDATA ); + + JDLIB::LOADERDATA data; + data.url = CONFIG::get_url_bbsmenu(); + data.agent = CONFIG::get_agent_for2ch(); + if( CONFIG::get_use_proxy_for2ch() ) data.host_proxy = CONFIG::get_proxy_for2ch(); + else data.host_proxy = std::string(); + data.port_proxy = CONFIG::get_proxy_port_for2ch(); + data.size_buf = CONFIG::get_loader_bufsize(); + data.timeout = CONFIG::get_loader_timeout(); + + start_load( data ); +} + + +// +// bbsmenu 受信中 +// +void Root::receive_data( const char* data, size_t size ) +{ + memcpy( m_rawdata + m_lng_rawdata , data, size ); + m_lng_rawdata += size; +} + + +// +// bbsmenu 受信完了 +// +void Root::receive_finish() +{ + // 文字コードを変換してXML作成 +#ifdef NOUSE_MS932 + JDLIB::Iconv* libiconv = new JDLIB::Iconv( "CP932" ); +#else + JDLIB::Iconv* libiconv = new JDLIB::Iconv( "MS932" ); +#endif + int byte_out; + const char* rawdata_utf8 = libiconv->convert( m_rawdata , m_lng_rawdata, byte_out ); + bbsmenu2xml( rawdata_utf8 ); + + if( !m_xml_bbsmenu.empty() ){ + + // データベース更新 + update_boards( m_xml_bbsmenu ); + + // XML保存 + std::string file_out = CACHE::path_xml_listmain(); + CACHE::save_rawdata( file_out, m_xml_bbsmenu.data(), m_xml_bbsmenu.length() ); +#ifdef _DEBUG + std::cout << "Root::receive_finish save to " << file_out << std::endl; +#endif + // 板リストview更新 + CORE::core_set_command( "update_bbslist" ); + } + + if( libiconv ) delete libiconv; + clear(); +} + + + +// +// bbsmenu.html -> xml 変換 +// +void Root::bbsmenu2xml( const std::string& menu ) +{ + JDLIB::Regex regex; + m_xml_bbsmenu.clear(); + + std::list< std::string > lines = MISC::get_lines( menu ); + + // 行単位でパースしていく + std::string str_category; + std::list< std::string >::iterator it; + for( it = lines.begin(); it != lines.end(); ++it ){ + + std::string& line = (*it); + + // カテゴリに入る + unsigned int i; + if( ( i = line.find( "

" ) ) != std::string::npos ){ + + if( ! regex.exec( " ?

(.*)
.*", line ) ) continue; + + if( ! str_category.empty() ) m_xml_bbsmenu += "\n"; + str_category = MISC::remove_space( regex.str( 1 ) ); + + if( str_category == "チャット" + || str_category == "ツール類" + || str_category == "他のサイト" ) str_category = std::string(); + else{ + m_xml_bbsmenu += "\n"; + } + } + + // URLと板名取得 + if( ! str_category.empty() ){ + + if( ! regex.exec( " ?
]*/) ?(TARGET=_blank)?>(.*).*", line ) ) continue; + + bool link = false; + std::string url = MISC::remove_space( regex.str( 1 ) ); + std::string name = MISC::remove_space( regex.str( 3 ) ); + + // url, nameのチェック + if( !regex.exec( "http://.*/.*/", url ) ) link = true; + if( name.empty() ) continue; + + // XML に追加 + std::ostringstream st_xml; + st_xml.clear(); + if( link ) st_xml << "\n"; + + m_xml_bbsmenu += st_xml.str(); + } + } + if( ! str_category.empty() ) m_xml_bbsmenu += "\n"; +} + + +// +// XML に含まれる板情報を取り出してデータベースを更新 +// +void Root::update_boards( const std::string xml ) +{ +#ifdef _DEBUG + std::cout << "Root::update_boards\n"; +#endif + + JDLIB::Regex regex; + + m_move_info = std::string(); + + // XMLを行ごとにばらしてタグを見る + std::list< std::string > lines = MISC::get_lines( xml ); + std::list< std::string >::iterator it; + for( it = lines.begin(); it != lines.end(); ++it ){ + + std::string& line = (*it); + + if( ! regex.exec( " ?.*", line ) ) continue; + + std::string url = MISC::remove_space( regex.str( 1 ) ); + std::string name = MISC::remove_space( regex.str( 2 ) ); + + //板情報セット + set_board( url, name ); + } + + // 移転があった + if( ! m_move_info.empty() ){ + + Gtk::MessageDialog mdiag( "移転一覧\n" + m_move_info ); mdiag.run(); + + //移転テーブル保存 + save_movetable(); + } +} + + +// +// 板のタイプに合わせて板情報をセット +// ついでに移転とかの判定もする +// +bool Root::set_board( const std::string& url, const std::string& name ) +{ +#ifdef _SHOW_BOARD + std::cout << url << " " << name << std::endl; +#endif + + JDLIB::Regex regex; + std::string root; + std::string path_board; + + // 板のタイプを判定 + int type; + + // 2ch + if( is_2ch( url ) ){ + + if( ! regex.exec( "(http://[^/]*)/([^/]*)/$" , url ) ) return false; + root = regex.str( 1 ); + path_board = "/" + regex.str( 2 ); + + type = TYPE_BOARD_2CH; + } + + // JBBS + else if( is_JBBS( url ) ){ + + if( ! regex.exec( "(http://[^/]*)/(.*)/$" , url ) ) return false; + root = "http://jbbs.livedoor.jp"; + path_board = "/" + regex.str( 2 ); + + type = TYPE_BOARD_JBBS; + } + + // まち + else if( is_machi( url ) ){ + + if( ! regex.exec( "(http://[^/]*)/([^/]*)/$" , url ) ) return false; + root = regex.str( 1 ); + path_board = "/" + regex.str( 2 ); + + type = TYPE_BOARD_MACHI; + } + + // その他は互換型 + else{ + + if( ! regex.exec( "(http://.*)/([^/]*)/$" , url ) ) return false; + root = regex.str( 1 ); + path_board = "/" + regex.str( 2 ); + + type = TYPE_BOARD_2CH_COMPATI; + } + + // 移転チェック + BoardBase* board = NULL; + int stat = is_moved( root, path_board, name, &board ); + +#ifdef _SHOW_BOARD + std::cout << "root = " << root << " path_board = " << path_board <<" type = " << type << " stat = " << stat << std::endl; +#endif + + // 新板登録 + if( stat == BOARD_NEW ){ + board = DBTREE::BoardFactory( type, root, path_board, name ); + if( board ) m_list_board.push_back( board ); + } + + // 移転処理 + else if( stat == BOARD_MOVED && board ){ + + std::string old_root = board->get_root(); + std::string old_path_board = board->get_path_board(); + std::string old_url = board->url_boardbase(); + std::string old_path = CACHE::path_board_root( old_url ); + + // DB更新 + board->update_root( root ); + + std::string new_url = board->url_boardbase(); + std::string new_path = CACHE::path_board_root( new_url ); + + std::ostringstream ss; + ss << board->get_name() << " is moved" << std::endl + << " old url = " << old_url << std::endl + << " new url = " << new_url << std::endl; + MISC::MSG( ss.str() ); + + // もしキャッシュが存在したら移動して移転テーブル更新 + if( CACHE::is_file_exists( old_path ) == CACHE::EXIST_DIR ){ + + // キャッシュがある場合はダイアログに表示 + m_move_info += ss.str(); + + // 移動先に同名のファイルかフォルダ何かあったらリネームしてバックアップをとっておく + if( CACHE::is_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 ); + } + + // 板移転テーブルを更新 + MOVETABLE movetable; + movetable.old_root = old_root; + movetable.new_root = root; + movetable.path_board = board->get_path_board(); + m_movetable.push_back( movetable ); + +#ifdef _DEBUG + std::cout << "movetable was updated.\n"; +#endif + } + } + + 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 ) +{ + std::list< BoardBase* >::iterator it; + for( it = m_list_board.begin(); it != m_list_board.end(); ++it ){ + + BoardBase* board = *( it ); + + if( board->get_path_board() == path_board ){ + + // 既にリストに登録されてる + if( board->get_root() == root ) return BOARD_EXISTS; + + // 名前が同じなら移転 + if( board->get_name() == name ){ + *board_old = board; + return BOARD_MOVED; + } + } + } + + return BOARD_NEW; +} + + + +// +// 外部板読み込み +// +// etc.txt(Navi2ch互換) を読み込んで外部板登録してXMLに変換 +// +void Root::load_etc() +{ + m_xml_etc.clear(); + + 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 ); + std::list< std::string >::iterator it; + for( it = list_etc.begin(); it != list_etc.end(); ++it ){ + + // 名前 + std::string name; + name = *( it++ ); + if( it == list_etc.end() ) break; + + // url + std::string url; + url = *( it++ ); + if( it == list_etc.end() ) break; + +#ifdef _DEBUG + std::cout << "etc board : " << url << " " << name << std::endl; +#endif + + // DBに登録 + if( set_board( url, name ) ){ + + m_xml_etc += "\n"; + } + + } + } +} + + + +// +// 移転テーブル読み込み +// +void Root::load_movetable() +{ +#ifdef _DEBUG + std::cout << "Root::load_movetable\n"; +#endif + + std::string file_move = CACHE::path_movetable(); + std::string movetable; + if( CACHE::load_rawdata( file_move, movetable ) ){ + + std::list< std::string > list_table = MISC::get_lines( movetable ); + std::list< std::string >::iterator it; + for( it = list_table.begin(); it != list_table.end(); ++it ){ + + std::list< std::string > lines = MISC::split_line( *it ); + + if( lines.size() == 3 ){ + + std::list< std::string >::iterator it2 = lines.begin(); + + MOVETABLE movetable; + movetable.old_root = *(it2++); + movetable.new_root = *(it2++); + movetable.path_board = *(it2++); + m_movetable.push_back( movetable ); + } + } + } + +#ifdef _DEBUG + std::cout << "MOVETABLE : \n"; + std::list< MOVETABLE >::iterator it_move; + for( it_move = m_movetable.begin(); it_move != m_movetable.end(); ++it_move ) + std::cout << ( *it_move ).old_root << ( *it_move ).path_board + << " -> " << ( *it_move ).new_root << ( *it_move ).path_board << std::endl; +#endif + + +} + + +// +// 移転テーブル保存 +// +void Root::save_movetable() +{ +#ifdef _DEBUG + std::cout << "Root::save_movetable\n"; +#endif + std::string file_move = CACHE::path_movetable(); + std::ostringstream movetable; + + std::list< MOVETABLE >::iterator it_move; + for( it_move = m_movetable.begin(); it_move != m_movetable.end(); ++it_move ){ + + movetable <<( *it_move ).old_root << " " + << ( *it_move ).new_root << " " << ( *it_move ).path_board << 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 ) +{ + std::string hostname = MISC::get_hostname( url ); + + if( hostname.find( ".2ch.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 ) +{ + std::string hostname = MISC::get_hostname( url ); + + if( hostname.find( "jbbs.livedoor.jp" ) != std::string::npos + || hostname.find( "jbbs.shitaraba.com" ) != std::string::npos ) return true; + + return false; +} + + +// +// まち型のURLかどうか +// +bool Root::is_machi( const std::string& url ) +{ + std::string hostname = MISC::get_hostname( url ); + + if( hostname.find( ".machi.to" ) != std::string::npos ) return true; + + return false; +} diff --git a/src/dbtree/root.h b/src/dbtree/root.h new file mode 100644 index 000000000..05238efba --- /dev/null +++ b/src/dbtree/root.h @@ -0,0 +1,104 @@ +// ライセンス: 最新のGPL + +// データベースのルートクラス +// +// クラス図 [ Root ] ---> [ BoardBase ] ---> [ ArticleBase ] ---> [ NodeTreeBase ] +// + +#ifndef _ROOT_H +#define _ROOT_H + +#include "skeleton/loadable.h" + +#include +#include + + +namespace DBTREE +{ + class BoardBase; + + // 鯖移転テーブル + // + // テーブルを更新するタイミングは下のふたつ + // + // (1) bbsmenuを読み込んで移転していた場合( Root::set_board() ) + // + // 現在のホストを新ホストに移動する。キャッシュも移動する + // + // (2) 参照しようとした板が無かった時( Root::get_board() ) + // + // 参照した古いホストの移動先を現在のホストに設定する。キャッシュは移動しない + // + struct MOVETABLE + { + std::string old_root; + std::string new_root; + std::string path_board; + }; + + + class Root : public SKELETON::Loadable + { + // Boardクラス のキャッシュ + // Boardクラスは一度作ったら~Root()以外ではdeleteしないこと + std::list< BoardBase* > m_list_board; + + // 鯖移転テーブル + std::list< MOVETABLE > m_movetable; + + std::string m_xml_bbsmenu; + char* m_rawdata; + size_t m_lng_rawdata; + std::string m_xml_etc; // 外部板のXML + std::string m_move_info; + + // NULL board クラス + BoardBase* m_board_null; + + public: + + Root(); + ~Root(); + + // 板一覧、外部板一覧のxml + const std::string& xml_bbsmenu() const { return m_xml_bbsmenu; } + const std::string& xml_etc() const { return m_xml_etc; } + + // Board クラスのポインタ取得 + BoardBase* get_board( const std::string& url, int count = 0 ); + + // bbsmenuのダウンロード + void download_bbsmenu(); + + private: + + // bbsmenuのダウンロード用関数 + void clear(); + virtual void receive_data( const char* data, size_t size ); + virtual void receive_finish(); + void bbsmenu2xml( const std::string& menu ); + + // XML に含まれる板情報を取り出してデータベースを更新 + void update_boards( const std::string xml ); + + void load_cache(); + bool set_board( const std::string& url, const std::string& name ); + int is_moved( const std::string& root, + const std::string& path_board, + const std::string& name, + BoardBase** board_old ); + + void load_etc(); + void load_movetable(); + void save_movetable(); + + // urlのタイプ判定 + bool is_2ch( const std::string& url ); + bool is_JBBS( const std::string& url ); + bool is_machi( const std::string& url ); + }; +} + + +#endif diff --git a/src/dbtree/settingloader.cpp b/src/dbtree/settingloader.cpp new file mode 100644 index 000000000..7a078c084 --- /dev/null +++ b/src/dbtree/settingloader.cpp @@ -0,0 +1,216 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "settingloader.h" +#include "interface.h" + +#include "jdlib/jdiconv.h" +#include "jdlib/confloader.h" +#include "jdlib/loaderdata.h" + +#include "config/globalconf.h" + +#include "httpcode.h" +#include "session.h" +#include "cache.h" + +#define SIZE_OF_RAWDATA ( 2 * 1024 * 1024 ) + +#define SETTING_TXT "SETTING.TXT" + + +using namespace DBTREE; + + +SettingLoader::SettingLoader( const std::string& url_boadbase ) + : SKELETON::Loadable() + , m_loaded( 0 ) + , m_url_boadbase( url_boadbase ) + , m_rawdata( 0 ) + , m_lng_rawdata( 0 ) + , m_line_number( 0 ) + , m_message_count( 0 ) +{ +#ifdef _DEBUG + std::cout << "SettingLoader::SettingLoader : " << m_url_boadbase << std::endl; +#endif + + clear(); +} + + + +SettingLoader::~SettingLoader() +{ +#ifdef _DEBUG + std::cout << "SettingLoader::~SettingLoader : " << m_url_boadbase << std::endl; +#endif + + clear(); +} + + +void SettingLoader::clear() +{ + if( m_rawdata ) free( m_rawdata ); + m_rawdata = NULL; + m_lng_rawdata = 0; +} + + +// +// パース +// +void SettingLoader::parse( const std::string& setting ) +{ + JDLIB::ConfLoader cf( "", setting ); + + m_default_noname = cf.get_option( "BBS_NONAME_NAME", "No Name" ); + m_line_number = cf.get_option( "BBS_LINE_NUMBER", 0 ); + m_message_count = cf.get_option( "BBS_MESSAGE_COUNT", 0 ); +} + + +// +// キャッシュからSETTING.TXTをロード +// +void SettingLoader::load_setting() +{ +#ifdef _DEBUG + std::cout << "SettingLoader::load_setting " << m_url_boadbase << std::endl; +#endif + if( is_loading() ) return; + if( m_loaded ) return; // 読み込み済み + + std::string path = CACHE::path_board_root( m_url_boadbase ) + SETTING_TXT; + + clear(); + m_rawdata = ( char* )malloc( SIZE_OF_RAWDATA ); + m_lng_rawdata = CACHE::load_rawdata( path, m_rawdata, SIZE_OF_RAWDATA ); + + // UTF-8に変換しておく + JDLIB::Iconv* libiconv = new JDLIB::Iconv( DBTREE::board_charset( m_url_boadbase ) ); + int byte_out; + m_settingtxt = libiconv->convert( m_rawdata , m_lng_rawdata, byte_out ); + delete libiconv; + clear(); + +#ifdef _DEBUG + std::cout << m_settingtxt << std::endl; +#endif + + parse( m_settingtxt ); + set_code( HTTP_OK ); +} + + +// +// SETTING.TXT ダウンロード +// +void SettingLoader::download_setting() +{ +#ifdef _DEBUG + std::cout << "SettingLoader::download_setting " << m_url_boadbase << std::endl; +#endif + if( is_loading() ) return; + if( m_loaded ) return; // 読み込み済み + + clear(); + m_rawdata = ( char* )malloc( SIZE_OF_RAWDATA ); + + // オフライン + if( ! SESSION::is_online() ){ + + set_str_code( "" ); + + // ディスパッチャ経由でreceive_finish()を呼ぶ + finish(); + return; + } + + JDLIB::LOADERDATA data; + data.url = m_url_boadbase + SETTING_TXT; + 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.size_buf = CONFIG::get_loader_bufsize(); + data.timeout = CONFIG::get_loader_timeout(); + + if( ! start_load( data ) ){ + clear(); + } +} + + +// +// ローダよりsubject.txt受信 +// +void SettingLoader::receive_data( const char* data, size_t size ) +{ + memcpy( m_rawdata + m_lng_rawdata , data, size ); + m_lng_rawdata += size; + assert( m_lng_rawdata < SIZE_OF_RAWDATA ); +} + + + +// +// ロード完了 +// +void SettingLoader::receive_finish() +{ + bool read_from_cache = false; + +#ifdef _DEBUG + std::cout << "SettingLoader::receive_finish code = " << get_str_code() << std::endl; +#endif + + std::string path = CACHE::path_board_root( m_url_boadbase ) + SETTING_TXT; + + // 失敗時、オフライン時はキャッシュから読み込み + if( get_code() != HTTP_OK || !SESSION::is_online() ){ + + m_lng_rawdata = CACHE::load_rawdata( path, m_rawdata, SIZE_OF_RAWDATA ); + read_from_cache = true; + +#ifdef _DEBUG + std::cout << "read from " << path << std::endl; +#endif + } + + if( m_lng_rawdata == 0 ){ + clear(); + return; + } + + set_code( HTTP_OK ); + set_str_code( std::string() ); + m_loaded = true; + + // キャッシュに保存 + if( SESSION::is_online() && ! read_from_cache ){ + + if( CACHE::mkdir_boardroot( m_url_boadbase ) ){ + + CACHE::save_rawdata( path, m_rawdata, m_lng_rawdata ); + +#ifdef _DEBUG + std::cout << "save to " << path << std::endl; +#endif + } + } + + // UTF-8に変換しておく + JDLIB::Iconv* libiconv = new JDLIB::Iconv( DBTREE::board_charset( m_url_boadbase ) ); + int byte_out; + m_settingtxt = libiconv->convert( m_rawdata , m_lng_rawdata, byte_out ); + delete libiconv; + clear(); + +#ifdef _DEBUG + std::cout << m_settingtxt << std::endl; +#endif + parse( m_settingtxt ); +} diff --git a/src/dbtree/settingloader.h b/src/dbtree/settingloader.h new file mode 100644 index 000000000..af3da6f65 --- /dev/null +++ b/src/dbtree/settingloader.h @@ -0,0 +1,62 @@ +// ライセンス: 最新のGPL +// +// 2chのSETTING.TXTのローダ +// + +#ifndef _SETTINGLOADER_H +#define _SETTINGLOADER_H + +#include "skeleton/loadable.h" + +#include + +namespace JDLIB +{ + class LOADERDATA; +} + + +namespace DBTREE +{ + class SettingLoader : public SKELETON::Loadable + { + bool m_loaded; // 読み込み済みか + + std::string m_url_boadbase; + char* m_rawdata; + int m_lng_rawdata; + + std::string m_settingtxt; + + // デフォルト名無し + std::string m_default_noname; + + // 最大改行数/2 + int m_line_number; + + // 最大書き込みバイト数 + int m_message_count; + + public: + SettingLoader( const std::string& url_boardbase ); + ~SettingLoader(); + + const std::string& settingtxt() const { return m_settingtxt; } + const std::string& default_noname() const { return m_default_noname; } + const int line_number() { return m_line_number; } + const int message_count() { return m_message_count; } + + void load_setting(); + void download_setting(); + + private: + + void clear(); + void parse( const std::string& setting ); + + virtual void receive_data( const char* data, size_t size ); + virtual void receive_finish(); + }; +} + +#endif diff --git a/src/dbtree/spchar_decoder.cpp b/src/dbtree/spchar_decoder.cpp new file mode 100644 index 000000000..37975a690 --- /dev/null +++ b/src/dbtree/spchar_decoder.cpp @@ -0,0 +1,202 @@ +// ライセンス: 最新のGPL + +#include "spchar_decoder.h" +#include "node.h" + +#include "jdlib/miscutil.h" + +#include +#include + + +// +// &〜みたいな特殊文字をデコードする +// +// in_char : 入力文字列, in_char[ 0 ] = '&' となっていること +// out_char : 出力文字列 +// n_in : 入力で使用した文字数 +// n_out : 出力文字数 +// +// 戻り値 : node.h で定義したノード番号 +// +int DBTREE::decode_char( const char* in_char, int& n_in, char* out_char, int& n_out ) +{ + int ret = DBTREE::NODE_TEXT; + + // gt + if( in_char[ 1 ] == 'g' && in_char[ 2 ] == 't' && in_char[ 3 ] == ';' ){ + n_in = 4; + n_out = 1; + out_char[ 0 ] = '>'; + } + + // lt + else if( in_char[ 1 ] == 'l' && in_char[ 2 ] == 't' && in_char[ 3 ] == ';' ){ + n_in = 4; + n_out = 1; + out_char[ 0 ] = '<'; + } + + // amp + else if( in_char[ 1 ] == 'a' && in_char[ 2 ] == 'm' && in_char[ 3 ] == 'p' && in_char[ 4 ] == ';' ){ + n_in = 5; + n_out = 1; + out_char[ 0 ] = '&'; + } + + // quot + else if( in_char[ 1 ] == 'q' && in_char[ 2 ] == 'u' && in_char[ 3 ] == 'o' && in_char[ 4 ] == 't' && in_char[ 5 ] == ';' ){ + n_in = 6; + n_out = 1; + out_char[ 0 ] = '"'; + } + + // hearts + else if( in_char[ 1 ] == 'h' && in_char[ 2 ] == 'e' && in_char[ 3 ] == 'a' && in_char[ 4 ] == 'r' + && in_char[ 5 ] == 't' && in_char[ 6 ] == 's' && in_char[ 7 ] == ';' ){ + n_in = 8; + n_out = MISC::ucs2utf8( 9829, out_char ); + } + + // スペース + + // nbsp + else if( in_char[ 1 ] == 'n' && in_char[ 2 ] == 'b' + && in_char[ 3 ] == 's' && in_char[ 4 ] == 'p' && in_char[ 5 ] == ';' ){ + n_in = 6; + n_out = 1; + out_char[ 0 ] = ' '; + } + + // zwsp + else if( in_char[ 1 ] == 'z' && in_char[ 2 ] == 'w' && in_char[ 3 ] == 's' && in_char[ 4 ] == 'p' + && in_char[ 5 ] == ';' ){ + n_in = 6; + n_out = 0; + ret = DBTREE::NODE_ZWSP; + } + + // thinsp + else if( in_char[ 1 ] == 't' && in_char[ 2 ] == 'h' && in_char[ 3 ] == 'i' && in_char[ 4 ] == 'n' + && in_char[ 5 ] == 's' && in_char[ 6 ] == 'p' && in_char[ 7 ] == ';' ){ + n_in = 8; + n_out = 0; + ret = DBTREE::NODE_THINSP; + } + + // ensp + else if( in_char[ 1 ] == 'e' && in_char[ 2 ] == 'n' && in_char[ 3 ] == 's' && in_char[ 4 ] == 'p' + && in_char[ 5 ] == ';' ){ + n_in = 6; + n_out = 0; + ret = DBTREE::NODE_ENSP; + } + + // emsp + else if( in_char[ 1 ] == 'e' && in_char[ 2 ] == 'm' && in_char[ 3 ] == 's' && in_char[ 4 ] == 'p' + && in_char[ 5 ] == ';' ){ + n_in = 6; + n_out = 0; + ret = DBTREE::NODE_EMSP; + } + + // zwnj, zwj, lrm, rlm は今のところ無視 + else if( in_char[ 1 ] == 'z' && in_char[ 2 ] == 'w' && in_char[ 3 ] == 'n' && in_char[ 4 ] == 'j' + && in_char[ 5 ] == ';' ){ + n_in = 6; + n_out = 0; + ret = DBTREE::NODE_ZWSP; + } + else if( in_char[ 1 ] == 'z' && in_char[ 2 ] == 'w' && in_char[ 3 ] == 'j' && in_char[ 4 ] == ';' ){ + n_in = 5; + n_out = 0; + ret = DBTREE::NODE_ZWSP; + } + else if( in_char[ 1 ] == 'l' && in_char[ 2 ] == 'r' && in_char[ 3 ] == 'm' && in_char[ 4 ] == ';' ){ + n_in = 5; + n_out = 0; + ret = DBTREE::NODE_ZWSP; + } + else if( in_char[ 1 ] == 'r' && in_char[ 2 ] == 'l' && in_char[ 3 ] == 'm' && in_char[ 4 ] == ';' ){ + n_in = 5; + n_out = 0; + ret = DBTREE::NODE_ZWSP; + } + + // 数字参照 + else if( in_char[ 1 ] == '#' ) ret = decode_char_number( in_char, n_in, out_char, n_out ); + + else ret = NODE_NONE; + + out_char[ n_out ] = '\0'; + return ret; +} + + + +// +// 数字参照 +// +// in_char[1] == "#" であること +// +int DBTREE::decode_char_number( const char* in_char, int& n_in, char* out_char, int& n_out ) +{ + int ret = DBTREE::NODE_TEXT; + int lng = 0; + char str_num[ 16 ]; + + n_in = n_out = 0; + + if( in_char[ 2 ] == ';' ) return NODE_NONE; + else if( in_char[ 3 ] == ';' ) lng = 1; + else if( in_char[ 4 ] == ';' ) lng = 2; + else if( in_char[ 5 ] == ';' ) lng = 3; + else if( in_char[ 6 ] == ';' ) lng = 4; + else if( in_char[ 7 ] == ';' ) lng = 5; + else return NODE_NONE; + + memcpy( str_num, in_char + 2, lng ); + str_num[ lng ] = '\0'; + + int num = atoi( str_num ); + + switch( num ){ + + case 8194: + ret = DBTREE::NODE_ENSP; + break; + + case 8195: + ret = DBTREE::NODE_EMSP; + break; + + case 8201: + ret = DBTREE::NODE_THINSP; + break; + + case 8202: + ret = DBTREE::NODE_HAIRSP; + break; + + case 8203: + ret = DBTREE::NODE_ZWSP; + break; + + //zwnj,zwj,lrm,rlm は今のところ無視 + case 8204: + case 8205: + case 8206: + case 8207: + ret = DBTREE::NODE_ZWSP; + break; + + default: + n_out = MISC::ucs2utf8( num, out_char ); + if( ! n_out ) return NODE_NONE; + } + + n_in = 3 + lng; + + return ret; +} + diff --git a/src/dbtree/spchar_decoder.h b/src/dbtree/spchar_decoder.h new file mode 100644 index 000000000..386703f10 --- /dev/null +++ b/src/dbtree/spchar_decoder.h @@ -0,0 +1,16 @@ +// ライセンス: 最新のGPL + +// +// 特殊HTML文字のデコード関数 +// + +#ifndef _SPCHAR_DECODER_H +#define _SPCHAR_DECODER_H + +namespace DBTREE +{ + int decode_char( const char* in_char, int& n_in, char* out_char, int& n_out ); + int decode_char_number( const char* in_char, int& n_in, char* out_char, int& n_out ); +} + +#endif diff --git a/src/dndmanager.cpp b/src/dndmanager.cpp new file mode 100644 index 000000000..b197a3cd1 --- /dev/null +++ b/src/dndmanager.cpp @@ -0,0 +1,94 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" +#include "dndmanager.h" + + +CORE::DND_Manager* instance_dnd_manager = NULL; + + +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 = NULL; +} + + +void CORE::DND_Begin( const std::string& url_from ) +{ + CORE::DND_Manager* manager = CORE::get_dnd_manager(); + if( manager ) manager->begin( url_from ); +} + + +void CORE::DND_End() +{ + CORE::DND_Manager* manager = CORE::get_dnd_manager(); + if( manager ) manager->end(); +} + + +const bool CORE::DND_Now_dnd() +{ + CORE::DND_Manager* manager = CORE::get_dnd_manager(); + if( manager ) return manager->now_dnd(); + + return false; +} + + +const std::string CORE::DND_Url_from() +{ + CORE::DND_Manager* manager = CORE::get_dnd_manager(); + if( manager ) return manager->url_from(); + + return std::string(); +} + + +/////////////////////////////////////////////// + +using namespace CORE; + + +DND_Manager::DND_Manager() + : m_dnd( 0 ) +{} + + +DND_Manager::~DND_Manager() +{ +#ifdef _DEBUG + std::cout << "CORE::~DND_Manager\n"; +#endif +} + + +// DnD 開始 +void DND_Manager::begin( const std::string& url_from ) +{ + if( m_dnd ) return; + m_dnd = true; + m_url_from = url_from; + m_sig_dnd_begin.emit(); +} + + +// DnD終了 +void DND_Manager::end() +{ + if( !m_dnd ) return; + m_dnd = false; + m_url_from = std::string(); + m_sig_dnd_end.emit(); +} diff --git a/src/dndmanager.h b/src/dndmanager.h new file mode 100644 index 000000000..25c7fc983 --- /dev/null +++ b/src/dndmanager.h @@ -0,0 +1,62 @@ +// ライセンス: 最新のGPL + +// +// ドラッグ&ドロップの管理クラス +// + +#ifndef _DNDMANAGER_H +#define _DNDMANAGER_H + +#include +#include + +namespace CORE +{ + class DND_Manager + { + // D&D 開始と終了時にシグナルを出す + typedef sigc::signal< void > SIG_DND_BEGIN; + typedef sigc::signal< void > SIG_DND_END; + + SIG_DND_END m_sig_dnd_begin; + SIG_DND_END m_sig_dnd_end; + + // 開始したwidgetのurl + std::string m_url_from; + + // true ならd&d中 + bool m_dnd; + + public: + + DND_Manager(); + virtual ~DND_Manager(); + + SIG_DND_END sig_dnd_begin(){ return m_sig_dnd_begin; } + SIG_DND_END sig_dnd_end(){ return m_sig_dnd_end; } + + const bool now_dnd() const{ return m_dnd; } + const std::string& url_from() const { return m_url_from; } + + // DnD 開始 + void begin( const std::string& url_from ); + + // DnD終了 + void end(); + }; + + /////////////////////////////////////// + // インターフェース + + DND_Manager* get_dnd_manager(); + void delete_dnd_manager(); + + void DND_Begin( const std::string& url_from ); + void DND_End(); + + const bool DND_Now_dnd(); + const std::string DND_Url_from(); +} + + +#endif diff --git a/src/global.h b/src/global.h new file mode 100644 index 000000000..0d20d61f0 --- /dev/null +++ b/src/global.h @@ -0,0 +1,116 @@ +/* グローバルな定数やstructなど */ + +#ifndef _GLOBAL_H +#define _GLOBAL_H + +#include + + +// msec 内部クロックの周期 +#define TIMER_TIMEOUT 50 + +// 最大表示可能レス数 +#define MAX_RESNUMBER 11000 + + +// マウスジェスチャの最大ストローク +#define MAX_MG_LNG 5 + + +// 画像アイコンの大きさ +#define ICON_SIZE 32 + + +// +// str をクリップボードにコピー +// +#define COPYCLIP( str ) do{ \ + Glib::RefPtr< Gtk::Clipboard > clip = Gtk::Clipboard::get(); \ + clip->set_text( str ); \ + clip = Gtk::Clipboard::get( GDK_SELECTION_PRIMARY ); \ + clip->set_text( str ); \ +} while( false ) + + +// スレッド状態( or を取る) +enum +{ + STATUS_UNKNOWN = 0, // 不明 + STATUS_NORMAL = 1, // 通常 + STATUS_OLD = 2, // DAT落ち or 板が移転した + STATUS_BROKEN = 4, // あぼーんなどで壊れている +}; + + +// CoreやAdminクラスで使うコマンド構造体 +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; +}; + + +// プロトコル +#define PROTO_ANCHORE "anc://" +#define PROTO_RES "res://" +#define PROTO_ID "ID://" +#define PROTO_BE "BE://" + + +// 仮想 URL + +#define URL_LOGIN2CH "jdlogin://login2ch" + +#define URL_BBSLISTADMIN "jdadmin://bbslist" +#define URL_BBSLISTVIEW "jdview://bbslist" +#define URL_FAVORITEVIEW "jdview://favorite" +#define URL_ETCVIEW "jdview://etc" + +#define URL_BOARDADMIN "jdadmin://board" + +#define URL_ARTICLEADMIN "jdadmin://article" + +#define URL_IMAGEADMIN "jdadmin://image" + +#define URL_MESSAGEADMIN "jdadmin://message" + +// フォントID +enum +{ + FONT_MAIN = 0, + FONT_POPUP, + FONT_NUM +}; + + +// タイプ +enum +{ + // 板のタイプ + TYPE_BOARD_2CH = 0, + TYPE_BOARD_2CH_COMPATI, // 2ch 互換 + TYPE_BOARD_JBBS, // したらば + TYPE_BOARD_MACHI, // まち + TYPE_BOARD_UNKNOWN, + + // その他一般的なデータタイプ + TYPE_BOARD, + TYPE_THREAD, + TYPE_IMAGE, + TYPE_DIR, + TYPE_DIR_END, // お気に入りの追加の時にサブディレクトリの終了の意味で使う + TYPE_COMMENT, + TYPE_LINK, + + TYPE_UNKNOWN +}; + + + +#endif diff --git a/src/historymenu.cpp b/src/historymenu.cpp new file mode 100644 index 000000000..725364bb6 --- /dev/null +++ b/src/historymenu.cpp @@ -0,0 +1,76 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "historymenu.h" +#include "historysubmenu.h" +#include "global.h" +#include "cache.h" + +using namespace CORE; + + +HistoryMenu::HistoryMenu() + : Gtk::MenuItem( "_History", true ) +{ + m_submenu = Gtk::manage( new CORE::HistorySubMenu( CACHE::path_xml_history() ) ); + set_submenu( *m_submenu ); + signal_activate().connect( sigc::mem_fun( *this, &HistoryMenu::slot_activate_menu ) ); + + // セパレータ + Gtk::MenuItem* item = Gtk::manage( new Gtk::SeparatorMenuItem() ); + m_submenu->prepend( *item ); + + // 板サブメニュー + item = Gtk::manage( new Gtk::MenuItem( "板履歴" ) ); + m_submenu_board = Gtk::manage( new CORE::HistorySubMenu( CACHE::path_xml_history_board() ) ); + item->set_submenu( *m_submenu_board ); + m_submenu->prepend( *item ); + + // セパレータ + item = Gtk::manage( new Gtk::SeparatorMenuItem() ); + m_submenu->append( *item ); + + // クリア + item = Gtk::manage( new Gtk::MenuItem( "履歴クリア" ) ); + m_submenu->append( *item ); + item->signal_activate().connect( sigc::mem_fun( *this, &HistoryMenu::slot_clear ) ); +} + + +HistoryMenu::~HistoryMenu() +{ +#ifdef _DEBUG + std::cout << "HistoryMenu::~HistoryMenu\n"; +#endif +} + + +void HistoryMenu::append( const std::string& url, const std::string& name, int type ) +{ + if( type == TYPE_THREAD && m_submenu ) m_submenu->append_item( url, name, type ); + if( type == TYPE_BOARD && m_submenu_board ) m_submenu_board->append_item( url, name, type ); +} + + +// activeになった時にラベルをセットする +void HistoryMenu::slot_activate_menu() +{ +#ifdef _DEBUG + std::cout << "HistoryMenu::slot_activate_menu\n"; +#endif + if( m_submenu ) m_submenu->set_menulabel(); + if( m_submenu_board ) m_submenu_board->set_menulabel(); +} + + +// 履歴のクリア +void HistoryMenu::slot_clear() +{ + Gtk::MessageDialog mdiag( "履歴を全てクリアしますか?", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_OK_CANCEL ); + if( mdiag.run() != Gtk::RESPONSE_OK ) return; + + if( m_submenu ) m_submenu->clear(); + if( m_submenu_board ) m_submenu_board->clear(); +} diff --git a/src/historymenu.h b/src/historymenu.h new file mode 100644 index 000000000..45191fb51 --- /dev/null +++ b/src/historymenu.h @@ -0,0 +1,38 @@ +// ライセンス: 最新のGPL + +// +// 履歴メニュー +// + +#ifndef _HISTORYMENU_H +#define _HISTORYMENU_H + +#include + +namespace CORE +{ + class HistorySubMenu; + + class HistoryMenu : public Gtk::MenuItem + { + CORE::HistorySubMenu* m_submenu; + CORE::HistorySubMenu* m_submenu_board; + + public: + + HistoryMenu(); + ~HistoryMenu(); + + void append( const std::string& url, const std::string& name, int type ); + + private: + + // メニューがactiveになった時にラベルをセットする + void slot_activate_menu(); + + // 履歴クリア + void slot_clear(); + }; +} + +#endif diff --git a/src/historysubmenu.cpp b/src/historysubmenu.cpp new file mode 100644 index 000000000..7a58f167b --- /dev/null +++ b/src/historysubmenu.cpp @@ -0,0 +1,244 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "historysubmenu.h" +#include "global.h" +#include "command.h" +#include "cache.h" +#include "xml.h" + +#include "dbtree/interface.h" + +#include "jdlib/miscutil.h" + +#include "config/globalconf.h" + +#include +#include + +using namespace CORE; + +#define HIST_NONAME "--------------" + + +HistorySubMenu::HistorySubMenu( const std::string path_xml ) + : Gtk::Menu(), + m_path_xml( path_xml ) +{ + // メニュー項目作成 + for( int i = 0; i < CONFIG::get_history_size(); ++i ){ + Gtk::MenuItem* item = Gtk::manage( new Gtk::MenuItem( HIST_NONAME ) ); + m_itemlist.push_back( item ); + append( *item ); + item->signal_activate().connect( sigc::bind( sigc::mem_fun( *this, &HistorySubMenu::slot_activate ), i )); + + CORE::HIST_ITEM* histitem = new CORE::HIST_ITEM; + histitem->type = TYPE_UNKNOWN; + m_histlist.push_back( histitem ); + } + + std::string xml; + CACHE::load_rawdata( m_path_xml, xml ); + xml2list( xml ); +} + + + +HistorySubMenu::~HistorySubMenu() +{ +#ifdef _DEBUG + std::cout << "HistorySubMenu::~HistorySubMenu\n"; +#endif + + // XML保存 + CACHE::save_rawdata( m_path_xml, list2xml() ); + + std::list< CORE::HIST_ITEM* >::iterator it = m_histlist.begin(); + for(; it != m_histlist.end(); ++it ) delete ( *it ); +} + + +// 履歴のクリア +void HistorySubMenu::clear() +{ + std::list< CORE::HIST_ITEM* >::iterator it = m_histlist.begin(); + for(; it != m_histlist.end(); ++it ){ + (*it)->url = std::string(); + (*it)->name = std::string(); + (*it)->type = TYPE_UNKNOWN; + } + + std::list< Gtk::MenuItem* >::iterator it_item = m_itemlist.begin(); + for(; it_item != m_itemlist.end(); ++it_item ){ + dynamic_cast< Gtk::Label* >( (*it_item)->get_child() )->set_text( HIST_NONAME ); + } +} + + +void HistorySubMenu::append_item( const std::string& url, const std::string& name, int type ) +{ + std::list< CORE::HIST_ITEM* >::iterator it; + CORE::HIST_ITEM* item = NULL; + + it = m_histlist.begin(); + for(; it != m_histlist.end(); ++it ){ + + item = (*it); + assert( item ); + + // 同じURLがあったら先頭に持ってくる + if( item->type == type && item->url == url ){ + m_histlist.remove( item ); + m_histlist.push_front( item ); + return; + } + + // emptyを見付けたら値をセットして先頭に持ってくる + if( item->url.empty() ){ + item->url = url; + item->name = name; + item->type = type; + m_histlist.remove( item ); + m_histlist.push_front( item ); + return; + } + } + + // 一番最後のitemを削除して先頭に持ってくる + assert( item ); + item->url = url; + item->name = name; + item->type = type; + m_histlist.remove( item ); + m_histlist.push_front( item ); +} + + + +// +// XML->list 変換 +// +void HistorySubMenu::xml2list( const std::string& xml ) +{ +#ifdef _DEBUG + std::cout << "HistorySubMenu::xml2list\n"; + std::cout << xml << std::endl; +#endif + + std::list< std::string > lines = MISC::get_lines( xml ); + if( lines.empty() ) return; + + std::list< CORE::HIST_ITEM* >::iterator it_hist = m_histlist.begin(); + std::list< std::string >::iterator it = lines.begin(); + for( ; it != lines.end(); ++it ){ + + std::string url; + std::string name; + std::string& line = *( it ); + + int type = XML::get_type( line, url, name ); + if( type != TYPE_UNKNOWN && !url.empty() ){ + ( *it_hist )->url = url; + ( *it_hist )->name = name; + ( *it_hist )->type = type; + ++it_hist; + if( it_hist == m_histlist.end() ) break; + } + } +} + + + +// +// list->XML 変換 +// +std::string HistorySubMenu::list2xml() +{ + std::stringstream xml; + + std::list< CORE::HIST_ITEM* >::iterator it = m_histlist.begin(); + for(; it != m_histlist.end(); ++it ){ + + Glib::ustring url = ( *it )->url; + Glib::ustring name = ( *it )->name; + int type = ( *it )->type; + + switch( type ){ + + case TYPE_BOARD: // 板 + XML_MAKE_BOARD(url,name); + break; + + case TYPE_THREAD: // スレ + XML_MAKE_THREAD(url,name); + break; + } + } + +#ifdef _DEBUG + std::cout << "HistoryMenu::list2xml\n"; + std::cout << xml.str() << std::endl; +#endif + + return xml.str(); +} + + + +// メニューアイテムがactiveになった +void HistorySubMenu::slot_activate( int i ) +{ +#ifdef _DEBUG + std::cout << "HistorySubMenu::slot_activate" << i << std::endl; +#endif + std::list< CORE::HIST_ITEM* >::iterator it = m_histlist.begin(); + for( int i2 = 0; i2 < i && it != m_histlist.end() ; ++it, ++i2 ); + if( it == m_histlist.end() ) return; + + std::string& url = ( *it )->url; + int type = ( *it )->type; + if( !url.empty() ){ +#ifdef _DEBUG + std::cout << "open " << url << std::endl; +#endif + if( type == TYPE_THREAD ) CORE::core_set_command( "open_article" , url, "true" ); + else if( type == TYPE_BOARD ) CORE::core_set_command( "open_board" , url, "true" ); + } +} + + + +// ラベルをセット +void HistorySubMenu::set_menulabel() +{ +#ifdef _DEBUG + std::cout << "HistorySubMenu::set_menulabel\n"; +#endif + + std::list< CORE::HIST_ITEM* >::iterator it_hist = m_histlist.begin(); + std::list< Gtk::MenuItem* >::iterator it_item = m_itemlist.begin(); + for(; it_hist != m_histlist.end(); ++it_hist, ++it_item ){ + if( !( *it_hist )->url.empty() ){ + + std::string url = ( *it_hist )->url; + std::string name = ( *it_hist )->name; + int type = ( *it_hist )->type; + + if( url.empty() ) name = HIST_NONAME; + else if( name.empty() ){ + + if( type == TYPE_BOARD ) name = DBTREE::board_name( url ); + else if( type == TYPE_THREAD ) name = DBTREE::article_subject( url ); + + if( name.empty() ) name = "???"; + else ( *it_hist )->name = name; + } + + dynamic_cast< Gtk::Label* >( (*it_item)->get_child() )->set_text( name ); + } + } +} + + diff --git a/src/historysubmenu.h b/src/historysubmenu.h new file mode 100644 index 000000000..6a9412580 --- /dev/null +++ b/src/historysubmenu.h @@ -0,0 +1,49 @@ +// ライセンス: 最新のGPL + +// +// 履歴サブメニュー +// + +#ifndef _HISTORYSUBMENU_H +#define _HISTORYSUBMENU_H + +#include +#include + +namespace CORE +{ + struct HIST_ITEM + { + std::string url; + std::string name; + int type; + }; + + class HistorySubMenu : public Gtk::Menu + { + std::string m_path_xml; + std::list< Gtk::MenuItem* > m_itemlist; + std::list< CORE::HIST_ITEM* > m_histlist; + + public: + + HistorySubMenu( const std::string path_xml ); + ~HistorySubMenu(); + + void clear(); + void append_item( const std::string& url, const std::string& name, int type ); + + // ラベルをセット + void set_menulabel(); + + private: + + void xml2list( const std::string& xml ); + std::string list2xml(); + + // メニューアイテムがactiveになった + virtual void slot_activate( int i ); + }; +} + +#endif diff --git a/src/httpcode.h b/src/httpcode.h new file mode 100644 index 000000000..1be58d47c --- /dev/null +++ b/src/httpcode.h @@ -0,0 +1,20 @@ +/* HTTP コード */ + +#ifndef _HTTPCODE_H +#define _HTTPCODE_H + +enum +{ + HTTP_ERR = -1, + HTTP_INIT = 0, + HTTP_CANCEL = 1, + HTTP_OK = 200, + HTTP_PARTIAL_CONTENT = 206, + HTTP_REDIRECT = 302, + HTTP_NOT_MODIFIED = 304, + HTTP_NOT_FOUND = 404, + HTTP_TIMEOUT = 408, + HTTP_RANGE_ERR = 416 +}; + +#endif diff --git a/src/icons/icon_jd16.h b/src/icons/icon_jd16.h new file mode 100644 index 000000000..70d293588 --- /dev/null +++ b/src/icons/icon_jd16.h @@ -0,0 +1,75 @@ +/* GdkPixbuf RGBA C-Source image dump */ + +#ifdef __SUNPRO_C +#pragma align 4 (icon_jd16_png) +#endif +#ifdef __GNUC__ +static const guint8 icon_jd16_png[] __attribute__ ((__aligned__ (4))) = +#else +static const guint8 icon_jd16_png[] = +#endif +{ "" + /* Pixbuf magic (0x47646b50) */ + "GdkP" + /* length: header (24) + pixel_data (1024) */ + "\0\0\4\30" + /* pixdata_type (0x1010002) */ + "\1\1\0\2" + /* rowstride (64) */ + "\0\0\0@" + /* width (16) */ + "\0\0\0\20" + /* height (16) */ + "\0\0\0\20" + /* pixel_data: */ + "\0\0\0\216\6\6\6\375\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\3\3\3\377\11\11\11\371\2\2\2" + "\316\"\356\"\0\"\356\"\0\6\6\6\375\335\335\335\377\335\335\335\377\335" + "\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335" + "\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335" + "\377\335\335\335\377\23\23\23\377\5\5\5\375\"\356\"\0\0\0\0\377\335\335" + "\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335" + "\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377" + "\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\20\20" + "\20\377\1\1\1\311\0\0\0\377\335\335\335\377\335\335\335\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\335\335\335\377\335\335\335\377\0\0\0\377\0\0\0" + "\377\0\0\0\377\0\0\0\377\335\335\335\377\335\335\335\377\335\335\335" + "\377\10\10\10\372\0\0\0\377\335\335\335\377\335\335\335\377\0\0\0\377" + "\1\1\1\377\0\0\0\377\335\335\335\377\335\335\335\377\0\0\0\377\0\0\0" + "\377\2\2\2\377\0\0\0\377\0\0\0\377\335\335\335\377\335\335\335\377\4" + "\4\4\377\0\0\0\377\335\335\335\377\335\335\335\377\335\335\335\377\17" + "\17\17\377\0\0\0\377\335\335\335\377\335\335\335\377\0\0\0\377\7\7\7" + "\377\335\335\335\377\0\0\0\377\0\0\0\377\335\335\335\377\335\335\335" + "\377\0\0\0\377\0\0\0\377\335\335\335\377\335\335\335\377\335\335\335" + "\377\17\17\17\377\0\0\0\377\335\335\335\377\335\335\335\377\0\0\0\377" + "\7\7\7\377\335\335\335\377\0\0\0\377\0\0\0\377\335\335\335\377\335\335" + "\335\377\0\0\0\377\0\0\0\377\335\335\335\377\335\335\335\377\335\335" + "\335\377\17\17\17\377\0\0\0\377\335\335\335\377\335\335\335\377\0\0\0" + "\377\7\7\7\377\335\335\335\377\0\0\0\377\0\0\0\377\335\335\335\377\335" + "\335\335\377\0\0\0\377\0\0\0\377\335\335\335\377\335\335\335\377\335" + "\335\335\377\17\17\17\377\0\0\0\377\335\335\335\377\335\335\335\377\0" + "\0\0\377\7\7\7\377\335\335\335\377\0\0\0\377\0\0\0\377\335\335\335\377" + "\335\335\335\377\0\0\0\377\0\0\0\377\335\335\335\377\335\335\335\377" + "\335\335\335\377\17\17\17\377\0\0\0\377\335\335\335\377\335\335\335\377" + "\0\0\0\377\7\7\7\377\335\335\335\377\0\0\0\377\0\0\0\377\335\335\335" + "\377\335\335\335\377\0\0\0\377\0\0\0\377\335\335\335\377\335\335\335" + "\377\335\335\335\377\7\7\7\377\0\0\0\377\335\335\335\377\335\335\335" + "\377\0\0\0\377\7\7\7\377\335\335\335\377\0\0\0\377\0\0\0\377\335\335" + "\335\377\335\335\335\377\0\0\0\377\0\0\0\377\335\335\335\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\335\335\335\377\335\335\335\377\0\0\0" + "\377\0\0\0\377\2\2\2\377\0\0\0\377\0\0\0\377\335\335\335\377\335\335" + "\335\377\0\0\0\377\0\0\0\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0" + "\0\377\0\0\0\377\335\335\335\377\335\335\335\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\335\335\335\377\335\335\335\377\335\335\335\377" + "\7\7\7\370\0\0\0\377\335\335\335\377\335\335\335\377\335\335\335\377" + "\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335" + "\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335" + "\335\377\335\335\335\377\26\26\26\377\0\0\0\276\6\6\6\375\335\335\335" + "\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377" + "\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335" + "\335\335\377\335\335\335\377\335\335\335\377000\377\3\3\3\377\"\356\"" + "\0\0\0\0\220\5\5\5\375\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0" + "\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\6\6\6\375\5\5" + "\5\347\"\356\"\0\"\356\"\0"}; + + diff --git a/src/icons/icon_jd32.h b/src/icons/icon_jd32.h new file mode 100644 index 000000000..3c28afd7f --- /dev/null +++ b/src/icons/icon_jd32.h @@ -0,0 +1,203 @@ +/* GdkPixbuf RGBA C-Source image dump */ + +#ifdef __SUNPRO_C +#pragma align 4 (icon_jd32_png) +#endif +#ifdef __GNUC__ +static const guint8 icon_jd32_png[] __attribute__ ((__aligned__ (4))) = +#else +static const guint8 icon_jd32_png[] = +#endif +{ "" + /* Pixbuf magic (0x47646b50) */ + "GdkP" + /* length: header (24) + pixel_data (4096) */ + "\0\0\20\30" + /* pixdata_type (0x1010002) */ + "\1\1\0\2" + /* rowstride (128) */ + "\0\0\0\200" + /* width (32) */ + "\0\0\0\40" + /* height (32) */ + "\0\0\0\40" + /* pixel_data: */ + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0'\0\0\0u\0\0\0v\0\0\0v\0\0\0v\0\0\0v\0" + "\0\0v\0\0\0v\0\0\0v\0\0\0v\0\0\0v\0\0\0v\0\0\0v\0\0\0v\0\0\0v\0\0\0v" + "\0\0\0v\0\0\0v\0\0\0v\0\0\0s\0\0\0\37\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\0\0\0\0\0\0+\0\0\0\361\0\0\0\377\0" + "\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\365\0\0\0\317\0\0\0\15\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\333\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\370\0\0\0s\0" + "\0\0\2\0\0\0\0\0\0\0\0\0\0\0\35\0\0\0\376\0\0\0\377&&&\377\335\335\335" + "\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377" + "\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335" + "\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335" + "\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335" + "\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\220\0\0\0\2\0\0\0\0\0\0\0w\0\0\0" + "\377\0\0\0\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335" + "\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335" + "\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377" + "\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335" + "\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335" + "\335\377\335\335\335\377\325\325\325\377\335\335\335\377\3\3\3\377\0" + "\0\0\377\0\0\0\370\0\0\0#\0\0\0\0\0\0\0x\0\0\0\377\0\0\0\377\335\335" + "\335\377\335\335\335\377\335\335\335\377\333\333\333\377\335\335\335" + "\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377" + "\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335" + "\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335" + "\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335" + "\377\335\335\335\377\335\335\335\377\335\335\335\377\0\0\0\377\0\0\0" + "\377\0\0\0\266\0\0\0\6\0\0\0x\0\0\0\377\0\0\0\377\335\335\335\377\335" + "\335\335\377\335\335\335\377\330\330\330\377\0\0\0\377\0\0\0\377\0\0" + "\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335\335\377\335\335\335\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\24\24\24\377\335\335\335\377\335\335\335\377\335\335\335\377" + "\323\323\323\377\335\335\335\377\0\0\0\377\0\0\0\366\0\0\0\40\0\0\0x" + "\0\0\0\377\0\0\0\377\335\335\335\377\335\335\335\377\335\335\335\377" + "\330\330\330\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0" + "\0\0\377\335\335\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\0\0" + "\0\377\0\0\0\376\0\0\0*\0\0\0x\0\0\0\377\0\0\0\377\335\335\335\377\335" + "\335\335\377\335\335\335\377\330\330\330\377\0\0\0\377\0\0\0\377\0\0" + "\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335\335\377\335\335\335\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\40\40\40\377\335\335\335\377\335\335\335" + "\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0J\0\0\0x\0\0\0\377\0\0" + "\0\377\335\335\335\377\335\335\335\377\335\335\335\377\330\330\330\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335" + "\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0" + "\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335" + "\335\335\377\335\335\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0" + "\0~\0\0\0x\0\0\0\377\0\0\0\377\335\335\335\377\335\335\335\377\335\335" + "\335\377\335\335\335\377\335\335\335\377\335\335\335\377\0\0\0\377\0" + "\0\0\377\0\0\0\377\0\0\0\377\335\335\335\377\335\335\335\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\203\203\203\377\11\11\11\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335\335\377\335\335\335" + "\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\215\0\0\0x\0\0\0\377\0" + "\0\0\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335" + "\377\335\335\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0" + "\0\0\377\335\335\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\323\323\323\377\0\0\0\377\0\0\0\377\0\0\0\377\0" + "\0\0\377\0\0\0\377\335\335\335\377\335\335\335\377\335\335\335\377\0" + "\0\0\377\0\0\0\377\0\0\0\215\0\0\0x\0\0\0\377\0\0\0\377\335\335\335\377" + "\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335" + "\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335\335\377" + "\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\323" + "\323\323\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335" + "\335\377\335\335\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\215" + "\0\0\0x\0\0\0\377\0\0\0\377\335\335\335\377\335\335\335\377\335\335\335" + "\377\335\335\335\377\335\335\335\377\335\335\335\377\0\0\0\377\0\0\0" + "\377\0\0\0\377\0\0\0\377\335\335\335\377\335\335\335\377\0\0\0\377\0" + "\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\323\323\323\377\0\0\0\377\0\0" + "\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335\335\377\335\335\335\377" + "\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\215\0\0\0x\0\0\0\377\0\0\0" + "\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377" + "\335\335\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0" + "\377\335\335\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0" + "\0\0\377\0\0\0\377\323\323\323\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0" + "\0\377\0\0\0\377\335\335\335\377\335\335\335\377\335\335\335\377\0\0" + "\0\377\0\0\0\377\0\0\0\215\0\0\0x\0\0\0\377\0\0\0\377\335\335\335\377" + "\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335" + "\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335\335\377" + "\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\323" + "\323\323\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335" + "\335\377\335\335\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\215" + "\0\0\0x\0\0\0\377\0\0\0\377\335\335\335\377\335\335\335\377\335\335\335" + "\377\335\335\335\377\335\335\335\377\335\335\335\377\0\0\0\377\0\0\0" + "\377\0\0\0\377\0\0\0\377\335\335\335\377\335\335\335\377\0\0\0\377\0" + "\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\323\323\323\377\0\0\0\377\0\0" + "\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335\335\377\335\335\335\377" + "\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\215\0\0\0x\0\0\0\377\0\0\0" + "\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377" + "\335\335\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0" + "\377\335\335\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0" + "\0\0\377\0\0\0\377\323\323\323\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0" + "\0\377\0\0\0\377\335\335\335\377\335\335\335\377\335\335\335\377\0\0" + "\0\377\0\0\0\377\0\0\0\215\0\0\0x\0\0\0\377\0\0\0\377\335\335\335\377" + "\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335" + "\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335\335\377" + "\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\323" + "\323\323\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335" + "\335\377\335\335\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\215" + "\0\0\0x\0\0\0\377\0\0\0\377\335\335\335\377\335\335\335\377\335\335\335" + "\377\335\335\335\377\335\335\335\377\335\335\335\377\0\0\0\377\0\0\0" + "\377\0\0\0\377\0\0\0\377\335\335\335\377\335\335\335\377\0\0\0\377\0" + "\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\323\323\323\377\0\0\0\377\0\0" + "\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335\335\377\335\335\335\377" + "\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\215\0\0\0x\0\0\0\377\0\0\0" + "\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377" + "\335\335\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0" + "\377\335\335\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0" + "\0\0\377\0\0\0\377\323\323\323\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0" + "\0\377\0\0\0\377\335\335\335\377\335\335\335\377\335\335\335\377\0\0" + "\0\377\0\0\0\377\0\0\0\215\0\0\0x\0\0\0\377\0\0\0\377\335\335\335\377" + "\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335" + "\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335\335\377" + "\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377ww" + "w\377\11\11\11\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335\335" + "\377\335\335\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\215\0" + "\0\0x\0\0\0\377\0\0\0\377\335\335\335\377\335\335\335\377\0\0\0\377\0" + "\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\335\335\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0" + "\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0" + "\0\377\335\335\335\377\335\335\335\377\335\335\335\377\0\0\0\377\0\0" + "\0\377\0\0\0\215\0\0\0x\0\0\0\377\0\0\0\377\335\335\335\377\335\335\335" + "\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0" + "\0\377\0\0\0\377\335\335\335\377\335\335\335\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\6\6\6\377\335\335\335\377\335\335\335\377\335\335\335\377" + "\0\0\0\377\0\0\0\377\0\0\0\202\0\0\0x\0\0\0\377\0\0\0\377\335\335\335" + "\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\11\11\11\377\335\335\335\377\335\335\335\377\0\0" + "\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0" + "\0\0\377\0\0\0\377\0\0\0\377\335\335\335\377\335\335\335\377\335\335" + "\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0M\0\0\0x\0\0\0\377" + "\0\0\0\377\335\335\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0" + "\377\0\0\0\377\0\0\0\377\0\0\0\377\7\7\7\377\335\335\335\377\335\335" + "\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0" + "\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335\335\377\335\335" + "\335\377\335\335\335\377\335\335\335\377\335\335\335\377\0\0\0\377\0" + "\0\0\375\0\0\0""1\0\0\0x\0\0\0\377\0\0\0\377\335\335\335\377\335\335" + "\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335" + "\377\335\335\335\377\335\335\335\377\335\335\335\377\322\322\322\377" + "\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335" + "\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335" + "\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335" + "\377\335\335\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\334\0" + "\0\0&\0\0\0w\0\0\0\377\0\0\0\377\335\335\335\377\335\335\335\377\335" + "\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335" + "\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335" + "\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377" + "\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335" + "\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335" + "\335\377\23\23\23\377\0\0\0\377\0\0\0\377\0\0\0f\0\0\0\14\0\0\0\40\0" + "\0\0\376\0\0\0\377'''\377\335\335\335\377\335\335\335\377\335\335\335" + "\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377" + "\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335" + "\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335" + "\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335" + "\377\335\335\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0" + "\0\0\314\0\0\0+\0\0\0\0\0\0\0\2\0\0\0\334\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\267\0\0\0:\0\0\0\11\0\0\0\0\0\0\0\0\0\0\0" + "9\0\0\0\355\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\370\0\0\0\343\0\0\0\201\0\0\0,\0\0\0\4\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\40\0\0\0<\0\0\0\205\0\0\0\205\0\0" + "\0\205\0\0\0\205\0\0\0\205\0\0\0\205\0\0\0\205\0\0\0\205\0\0\0\205\0" + "\0\0\205\0\0\0\205\0\0\0\205\0\0\0\205\0\0\0\205\0\0\0\205\0\0\0\205" + "\0\0\0\205\0\0\0\205\0\0\0\205\0\0\0\203\0\0\0>\0\0\0.\0\0\0\"\0\0\0" + "\25\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"}; + + diff --git a/src/icons/icon_jd48.h b/src/icons/icon_jd48.h new file mode 100644 index 000000000..99b6f091e --- /dev/null +++ b/src/icons/icon_jd48.h @@ -0,0 +1,414 @@ +/* GdkPixbuf RGBA C-Source image dump */ + +#ifdef __SUNPRO_C +#pragma align 4 (icon_jd48_png) +#endif +#ifdef __GNUC__ +static const guint8 icon_jd48_png[] __attribute__ ((__aligned__ (4))) = +#else +static const guint8 icon_jd48_png[] = +#endif +{ "" + /* Pixbuf magic (0x47646b50) */ + "GdkP" + /* length: header (24) + pixel_data (9216) */ + "\0\0$\30" + /* pixdata_type (0x1010002) */ + "\1\1\0\2" + /* rowstride (192) */ + "\0\0\0\300" + /* width (48) */ + "\0\0\0""0" + /* height (48) */ + "\0\0\0""0" + /* pixel_data: */ + "\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\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\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\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\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\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\0\0\0\0\0\0" + "\0\0\0\12\0\0\0\300\0\0\0\356\0\0\0\374\0\0\0\374\0\0\0\374\0\0\0\374" + "\0\0\0\374\0\0\0\374\0\0\0\374\0\0\0\374\0\0\0\374\0\0\0\374\0\0\0\374" + "\0\0\0\374\0\0\0\374\0\0\0\374\0\0\0\374\0\0\0\374\0\0\0\374\0\0\0\374" + "\0\0\0\374\0\0\0\374\0\0\0\374\0\0\0\374\0\0\0\374\0\0\0\374\0\0\0\374" + "\0\0\0\374\0\0\0\374\0\0\0\374\0\0\0\366\0\0\0\357\0\0\0\343\0\0\0\261" + "\0\0\0Z\0\0\0\5\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\0\0\0\0\0\0\0\0\0\0R\0\0\0\375\0\0\0\377\0\0\0\377\0\0\0" + "\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0" + "\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0" + "\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\373\0\0\0\256" + "\0\0\0\32\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\21\0\0\0\372\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0" + "\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0" + "\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\373\0\0\0f\0" + "\0\0\5\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\275\0\0\0\377\0" + "\0\0\377\0\0\0\377\0\0\0\377\12\12\12\377\21\21\21\377\21\21\21\377\21" + "\21\21\377\21\21\21\377\21\21\21\377\21\21\21\377\21\21\21\377\21\21" + "\21\377\21\21\21\377\21\21\21\377\21\21\21\377\21\21\21\377\21\21\21" + "\377\21\21\21\377\21\21\21\377\21\21\21\377\21\21\21\377\21\21\21\377" + "\21\21\21\377\21\21\21\377\21\21\21\377\21\21\21\377\21\21\21\377\21" + "\21\21\377\21\21\21\377\21\21\21\377\21\21\21\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\376" + "\0\0\0\220\0\0\0\15\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\355\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\335\335\335\377\335\335\335\377\335\335" + "\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335" + "\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377" + "\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335" + "\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335" + "\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335" + "\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377" + "\335\335\335\377\335\335\335\377\335\335\335\377\0\0\0\377\13\13\13\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\376\0\0\0r\0\0\0\10\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\373\0\0\0\377\0\0\0\377\12\12\12\377\334" + "\334\334\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335" + "\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335" + "\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377" + "\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335" + "\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335" + "\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335" + "\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377" + "\335\335\335\377\335\335\335\377\335\335\335\377\334\334\334\377\0\0" + "\0\377\4\4\4\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\372\0\0\0:\0\0\0" + "\1\0\0\0\0\0\0\0\0\0\0\0\374\0\0\0\377\0\0\0\377\25\25\25\377\335\335" + "\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335" + "\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377" + "\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335" + "\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335" + "\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335" + "\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377" + "\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335" + "\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335" + "\335\377\335\335\335\377\5\5\5\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0" + "\0\261\0\0\0\30\0\0\0\0\0\0\0\0\0\0\0\374\0\0\0\377\0\0\0\377\25\25\25" + "\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377" + "\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0" + "\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335\335\377\335" + "\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0" + "\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0" + "\0\377\0\0\0\377\335\335\335\377\335\335\335\377\335\335\335\377\335" + "\335\335\377\335\335\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0" + "\0\377\0\0\0\366\0\0\0""3\0\0\0\0\0\0\0\0\0\0\0\374\0\0\0\377\0\0\0\377" + "\25\25\25\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335" + "\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0" + "\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335\335" + "\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\20\20\20\377\0\0\0\377\335\335\335\377\335\335\335\377\335" + "\335\335\377\335\335\335\377\335\335\335\377\31\31\31\377\0\0\0\377\0" + "\0\0\377\0\0\0\377\0\0\0t\0\0\0\15\0\0\0\0\0\0\0\374\0\0\0\377\0\0\0" + "\377\25\25\25\377\335\335\335\377\335\335\335\377\335\335\335\377\335" + "\335\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335" + "\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0" + "\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0" + "\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335\335\377\335" + "\335\335\377\335\335\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0" + "\0\377\0\0\0\377\0\0\0\263\0\0\0\35\0\0\0\0\0\0\0\374\0\0\0\377\0\0\0" + "\377\25\25\25\377\335\335\335\377\335\335\335\377\335\335\335\377\335" + "\335\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335" + "\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0" + "\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0" + "\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335\335" + "\377\335\335\335\377\335\335\335\377\335\335\335\377\0\0\0\377\0\0\0" + "\377\0\0\0\377\0\0\0\336\0\0\0*\0\0\0\0\0\0\0\374\0\0\0\377\0\0\0\377" + "\25\25\25\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335" + "\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0" + "\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335\335" + "\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377///\377\326\326\326\377\335\335" + "\335\377\335\335\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\357\0\0\0""5\0\0\0\0\0\0\0\374\0\0\0\377\0\0\0\377\25\25\25\377" + "\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335" + "\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0" + "\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335\335\377\335\335" + "\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\2\2\2\377\335\335\335\377\335\335\335" + "\377\335\335\335\377\331\331\331\377\0\0\0\377\0\0\0\377\0\0\0\377\0" + "\0\0\365\0\0\0""9\0\0\0\0\0\0\0\374\0\0\0\377\0\0\0\377\25\25\25\377" + "\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335" + "\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0" + "\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335\335\377\335\335" + "\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335\335\377\335\335\335" + "\377\335\335\335\377\335\335\335\377\12\12\12\377\0\0\0\377\0\0\0\377" + "\0\0\0\371\0\0\0=\0\0\0\0\0\0\0\374\0\0\0\377\0\0\0\377\25\25\25\377" + "\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335" + "\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335" + "\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0" + "\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335\335\377\0\0\0\377\0\0\0" + "\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335" + "\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\17\17\17" + "\377\0\0\0\377\0\0\0\377\0\0\0\373\0\0\0@\0\0\0\0\0\0\0\374\0\0\0\377" + "\0\0\0\377\25\25\25\377\335\335\335\377\335\335\335\377\335\335\335\377" + "\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335" + "\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0" + "\377\0\0\0\377\335\335\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0" + "\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335\335" + "\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\335\335\335\377\335\335\335\377\335\335\335\377" + "\335\335\335\377\17\17\17\377\0\0\0\377\0\0\0\377\0\0\0\373\0\0\0@\0" + "\0\0\0\0\0\0\374\0\0\0\377\0\0\0\377\25\25\25\377\335\335\335\377\335" + "\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335" + "\335\377\335\335\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335\335\377\335\335\335" + "\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0" + "\0\377\0\0\0\377\335\335\335\377\335\335\335\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335\335\377\335" + "\335\335\377\335\335\335\377\335\335\335\377\17\17\17\377\0\0\0\377\0" + "\0\0\377\0\0\0\373\0\0\0@\0\0\0\0\0\0\0\374\0\0\0\377\0\0\0\377\25\25" + "\25\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377" + "\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\0\0" + "\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335" + "\335\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335\335\377\335\335\335" + "\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0" + "\0\377\335\335\335\377\334\334\334\377\335\335\335\377\335\335\335\377" + "\17\17\17\377\0\0\0\377\0\0\0\377\0\0\0\373\0\0\0@\0\0\0\0\0\0\0\374" + "\0\0\0\377\0\0\0\377\25\25\25\377\335\335\335\377\335\335\335\377\335" + "\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335" + "\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0" + "\0\377\0\0\0\377\0\0\0\377\335\335\335\377\335\335\335\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\335\335\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0" + "\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335\335\377\333\333\333\377\335" + "\335\335\377\335\335\335\377\17\17\17\377\0\0\0\377\0\0\0\377\0\0\0\373" + "\0\0\0@\0\0\0\0\0\0\0\374\0\0\0\377\0\0\0\377\25\25\25\377\335\335\335" + "\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377" + "\335\335\335\377\335\335\335\377\335\335\335\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335\335\377\335" + "\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0" + "\377\0\0\0\377\0\0\0\377\335\335\335\377\335\335\335\377\0\0\0\377\0" + "\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335\335" + "\377\335\335\335\377\335\335\335\377\335\335\335\377\17\17\17\377\0\0" + "\0\377\0\0\0\377\0\0\0\373\0\0\0@\0\0\0\0\0\0\0\374\0\0\0\377\0\0\0\377" + "\25\25\25\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335" + "\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335" + "\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0" + "\0\377\335\335\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335\335\377\335" + "\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0" + "\377\0\0\0\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335" + "\335\377\17\17\17\377\0\0\0\377\0\0\0\377\0\0\0\373\0\0\0@\0\0\0\0\0" + "\0\0\374\0\0\0\377\0\0\0\377\25\25\25\377\335\335\335\377\335\335\335" + "\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377" + "\335\335\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0" + "\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335\335\377\335\335\335\377\0" + "\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\335\335\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0" + "\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335\335\377\335\335" + "\335\377\335\335\335\377\335\335\335\377\17\17\17\377\0\0\0\377\0\0\0" + "\377\0\0\0\373\0\0\0@\0\0\0\0\0\0\0\374\0\0\0\377\0\0\0\377\25\25\25" + "\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377" + "\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\0\0" + "\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335" + "\335\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335\335\377\335\335\335" + "\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0" + "\0\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377" + "\17\17\17\377\0\0\0\377\0\0\0\377\0\0\0\373\0\0\0@\0\0\0\0\0\0\0\374" + "\0\0\0\377\0\0\0\377\25\25\25\377\335\335\335\377\335\335\335\377\335" + "\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335" + "\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0" + "\0\377\0\0\0\377\0\0\0\377\335\335\335\377\335\335\335\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\335\335\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0" + "\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335\335\377\335\335\335\377\335" + "\335\335\377\335\335\335\377\17\17\17\377\0\0\0\377\0\0\0\377\0\0\0\373" + "\0\0\0@\0\0\0\0\0\0\0\374\0\0\0\377\0\0\0\377\25\25\25\377\335\335\335" + "\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377" + "\335\335\335\377\335\335\335\377\335\335\335\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335\335\377\335" + "\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0" + "\377\0\0\0\377\0\0\0\377\335\335\335\377\335\335\335\377\0\0\0\377\0" + "\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335\335" + "\377\335\335\335\377\335\335\335\377\335\335\335\377\17\17\17\377\0\0" + "\0\377\0\0\0\377\0\0\0\373\0\0\0@\0\0\0\0\0\0\0\374\0\0\0\377\0\0\0\377" + "\25\25\25\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335" + "\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335" + "\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0" + "\0\377\335\335\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335\335\377\335" + "\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0" + "\377\0\0\0\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335" + "\335\377\17\17\17\377\0\0\0\377\0\0\0\377\0\0\0\373\0\0\0@\0\0\0\0\0" + "\0\0\374\0\0\0\377\0\0\0\377\25\25\25\377\335\335\335\377\335\335\335" + "\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377" + "\335\335\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0" + "\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335\335\377\335\335\335\377\0" + "\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\335\335\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0" + "\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335\335\377\335\335" + "\335\377\335\335\335\377\335\335\335\377\17\17\17\377\0\0\0\377\0\0\0" + "\377\0\0\0\373\0\0\0@\0\0\0\0\0\0\0\374\0\0\0\377\0\0\0\377\25\25\25" + "\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377" + "\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\0\0" + "\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335" + "\335\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335\335\377\335\335\335" + "\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0" + "\0\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377" + "\17\17\17\377\0\0\0\377\0\0\0\377\0\0\0\373\0\0\0@\0\0\0\0\0\0\0\374" + "\0\0\0\377\0\0\0\377\25\25\25\377\335\335\335\377\335\335\335\377\335" + "\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335" + "\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0" + "\0\377\0\0\0\377\0\0\0\377\335\335\335\377\335\335\335\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\335\335\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0" + "\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335\335\377\335\335\335\377\335" + "\335\335\377\335\335\335\377\17\17\17\377\0\0\0\377\0\0\0\377\0\0\0\373" + "\0\0\0@\0\0\0\0\0\0\0\374\0\0\0\377\0\0\0\377\25\25\25\377\335\335\335" + "\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377" + "\335\335\335\377\335\335\335\377\335\335\335\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335\335\377\335" + "\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0" + "\377\0\0\0\377\0\0\0\377\335\335\335\377\335\335\335\377\0\0\0\377\0" + "\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335\335" + "\377\335\335\335\377\335\335\335\377\335\335\335\377\17\17\17\377\0\0" + "\0\377\0\0\0\377\0\0\0\373\0\0\0@\0\0\0\0\0\0\0\374\0\0\0\377\0\0\0\377" + "\25\25\25\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335" + "\335\377\335\335\335\377\335\335\335\377\335\335\335\377\0\0\0\377\0" + "\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\335\335\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0" + "\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335\335\377\335\335" + "\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335" + "\377\17\17\17\377\0\0\0\377\0\0\0\377\0\0\0\373\0\0\0@\0\0\0\0\0\0\0" + "\374\0\0\0\377\0\0\0\377\25\25\25\377\335\335\335\377\335\335\335\377" + "\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\11\11\11\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\335\335\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0" + "\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335\335\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\17\17" + "\17\377\0\0\0\377\0\0\0\377\0\0\0\373\0\0\0@\0\0\0\0\0\0\0\374\0\0\0" + "\377\0\0\0\377\25\25\25\377\335\335\335\377\335\335\335\377\335\335\335" + "\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0" + "\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335\335" + "\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335\335\377\335" + "\335\335\377\335\335\335\377\335\335\335\377\17\17\17\377\0\0\0\377\0" + "\0\0\377\0\0\0\373\0\0\0@\0\0\0\0\0\0\0\374\0\0\0\377\0\0\0\377\25\25" + "\25\377\335\335\335\377\335\335\335\377\335\335\335\377\0\0\0\377\0\0" + "\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0" + "\0\0\377\0\0\0\377\0\0\0\377\3\3\3\377\335\335\335\377\335\335\335\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\2\2\2\377\335\335\335\377\335\335\335\377\335\335" + "\335\377\335\335\335\377\15\15\15\377\0\0\0\377\0\0\0\377\0\0\0\373\0" + "\0\0@\0\0\0\0\0\0\0\374\0\0\0\377\0\0\0\377\25\25\25\377\335\335\335" + "\377\335\335\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0" + "\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\26\26\26\377\335\335\335\377\335\335\335\377\0\0\0\377\0\0" + "\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0" + "\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\15\15\15\377\335\335\335\377\335\335\335\377\335\335\335\377" + "\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\364\0\0\0@\0\0\0" + "\0\0\0\0\374\0\0\0\377\0\0\0\377\25\25\25\377\335\335\335\377\335\335" + "\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0" + "\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0" + "\0\0\377\335\335\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\0\0" + "\0\377\0\0\0\377\0\0\0\377\0\0\0\341\0\0\0:\0\0\0\0\0\0\0\374\0\0\0\377" + "\0\0\0\377\25\25\25\377\335\335\335\377\335\335\335\377\335\335\335\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335\335\377\335\335\335" + "\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\335\335\335\377\335\335\335" + "\377\335\335\335\377\335\335\335\377\335\335\335\377\0\0\0\377\0\0\0" + "\377\0\0\0\377\0\0\0\307\0\0\0""0\0\0\0\0\0\0\0\374\0\0\0\377\0\0\0\377" + "\25\25\25\377\335\335\335\377\335\335\335\377\335\335\335\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\3\3\3\377\335\335\335\377\335\335\335\377\335\335\335\377" + "\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0" + "\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\335\335\335\377\335\335\335\377\333\333" + "\333\377\335\335\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\243\0\0\0'\0\0\0\0\0\0\0\374\0\0\0\377\0\0\0\377\25" + "\25\25\377\335\335\335\377\335\335\335\377\335\335\335\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335" + "\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\312\312\312\377\335\335\335\377\335\335\335\377" + "\335\335\335\377\335\335\335\377\335\335\335\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\376\0\0\0V\0\0\0\35\0\0\0\0\0\0\0\374\0\0\0\377\0\0" + "\0\377\25\25\25\377\335\335\335\377\335\335\335\377\335\335\335\377\335" + "\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335" + "\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335" + "\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377" + "\335\335\335\377\335\335\335\377\321\321\321\377\316\316\316\377\316" + "\316\316\377\316\316\316\377\316\316\316\377\316\316\316\377\316\316" + "\316\377\316\316\316\377\316\316\316\377\316\316\316\377\322\322\322" + "\377\324\324\324\377\327\327\327\377\333\333\333\377\335\335\335\377" + "\335\335\335\377\335\335\335\377\333\333\333\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\334\0\0\0@\0\0\0\13\0\0\0\0\0\0\0\373\0\0" + "\0\377\0\0\0\377\11\11\11\377\334\334\334\377\335\335\335\377\335\335" + "\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335" + "\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377" + "\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335" + "\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335" + "\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335" + "\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377" + "\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335" + "\335\335\377\335\335\335\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0h\0\0\0""2\0\0\0\0\0\0\0\0\0\0\0\354\0\0\0" + "\377\0\0\0\377\0\0\0\377\0\0\0\377\334\334\334\377\335\335\335\377\335" + "\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335" + "\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335" + "\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377" + "\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335" + "\335\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335" + "\335\377\335\335\335\377\335\335\335\377\335\335\335\377\335\335\335" + "\377\335\335\335\377\334\334\334\377\335\335\335\377\0\0\0\377\0\0\0" + "\377\4\4\4\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\314\0\0" + "\0@\0\0\0\22\0\0\0\0\0\0\0\0\0\0\0\273\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\5\5\5\377\13\13\13\377\13\13\13\377\13\13\13\377\13\13\13" + "\377\13\13\13\377\13\13\13\377\13\13\13\377\13\13\13\377\13\13\13\377" + "\13\13\13\377\13\13\13\377\13\13\13\377\13\13\13\377\13\13\13\377\13" + "\13\13\377\13\13\13\377\13\13\13\377\13\13\13\377\13\13\13\377\13\13" + "\13\377\13\13\13\377\13\13\13\377\13\13\13\377\13\13\13\377\13\13\13" + "\377\13\13\13\377\13\13\13\377\13\13\13\377\0\0\0\377\0\0\0\377\0\0\0" + "\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\343\0\0" + "\0D\0\0\0*\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0&\0\0\0\370\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\322\0\0\0A\0\0\0""2\0\0\0\4\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\2\0\0\0h\0\0\0\373\0\0\0\377\0\0\0\377\0\0\0\377\0\0" + "\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0" + "\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\356\0\0\0n\0" + "\0\0@\0\0\0""0\0\0\0\2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\14\0\0\0>\0\0\0\274\0\0\0\346\0\0\0\367\0\0\0\370\0\0\0\370\0\0\0\370" + "\0\0\0\370\0\0\0\370\0\0\0\370\0\0\0\370\0\0\0\370\0\0\0\370\0\0\0\370" + "\0\0\0\370\0\0\0\370\0\0\0\370\0\0\0\370\0\0\0\370\0\0\0\370\0\0\0\370" + "\0\0\0\370\0\0\0\370\0\0\0\370\0\0\0\370\0\0\0\370\0\0\0\370\0\0\0\370" + "\0\0\0\370\0\0\0\370\0\0\0\370\0\0\0\370\0\0\0\365\0\0\0\351\0\0\0\332" + "\0\0\0\273\0\0\0N\0\0\0@\0\0\0""9\0\0\0\26\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\0\0\3\0\0\0%\0\0\0""5\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@\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@\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\77\0\0\0""8\0\0\0""0\0\0\0#\0\0\0\20\0\0\0\1\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"}; + + diff --git a/src/image/Makefile.am b/src/image/Makefile.am new file mode 100644 index 000000000..a2dfa3c45 --- /dev/null +++ b/src/image/Makefile.am @@ -0,0 +1,32 @@ +noinst_LIBRARIES = libimage.a + +libimage_a_SOURCES = \ + imageadmin.cpp \ + preference.cpp \ +\ + imageareabase.cpp \ + imagearea.cpp \ + imageareaicon.cpp \ + imageareapopup.cpp \ +\ + imageviewbase.cpp \ + imageview.cpp \ + imageviewicon.cpp \ + imageviewpopup.cpp + +noinst_HEADERS = \ + imageadmin.h \ + preference.h \ +\ + imageareabase.h \ + imagearea.h \ + imageareaicon.h \ + imageareapopup.h \ +\ + imageviewbase.h \ + imageview.h \ + imageviewicon.h \ + imageviewpopup.h + +AM_CXXFLAGS = @GTKMM_CFLAGS@ +INCLUDES = -I$(top_srcdir)/src diff --git a/src/image/imageadmin.cpp b/src/image/imageadmin.cpp new file mode 100644 index 000000000..6c0cb9495 --- /dev/null +++ b/src/image/imageadmin.cpp @@ -0,0 +1,784 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "imageadmin.h" + +#include "dbimg/imginterface.h" + +#include "skeleton/view.h" + +#include "command.h" +#include "cache.h" +#include "viewfactory.h" +#include "global.h" +#include "session.h" + +#include "jdlib/miscutil.h" +#include "jdlib/miscmsg.h" + +IMAGE::ImageAdmin *instance_imageadmin = NULL; + +IMAGE::ImageAdmin* IMAGE::get_admin() +{ + if( ! instance_imageadmin ) instance_imageadmin = new IMAGE::ImageAdmin( URL_IMAGEADMIN ); + assert( instance_imageadmin ); + + return instance_imageadmin; +} + +void IMAGE::delete_admin() +{ + if( instance_imageadmin ) delete instance_imageadmin; + instance_imageadmin = NULL; +} + + +using namespace IMAGE; + +ImageAdmin::ImageAdmin( const std::string& url ) + : SKELETON::Admin( url ) + , m_scroll( SCROLL_NO ) +{ + m_scrwin.add( m_iconbox ); + m_scrwin.set_policy( Gtk::POLICY_NEVER, Gtk::POLICY_NEVER ); + m_scrwin.set_size_request( ICON_SIZE , ICON_SIZE + 4); + + m_left.set_label( "<" ); + m_right.set_label( ">" ); + + m_left.signal_pressed().connect( sigc::mem_fun( *this, &ImageAdmin::slot_press_left ) ); + m_right.signal_pressed().connect( sigc::mem_fun( *this, &ImageAdmin::slot_press_right ) ); + m_left.signal_released().connect( sigc::mem_fun( *this, &ImageAdmin::slot_release_left ) ); + m_right.signal_released().connect( sigc::mem_fun( *this, &ImageAdmin::slot_release_right ) ); + + + 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 + + // 開いているURLを保存 + SESSION::set_image_URLs( get_URLs() ); + SESSION::set_image_page( get_current_page() ); +} + + + +// 前回開いていたURLを復元 +void ImageAdmin::restore() +{ + std::list< std::string > list_tmp; + std::list< std::string >::iterator it_tmp; + + list_tmp = SESSION::image_URLs(); + it_tmp = list_tmp.begin(); + for( ; it_tmp != list_tmp.end(); ++it_tmp ){ + + if( !(*it_tmp).empty() ){ + COMMAND_ARGS command_arg; + command_arg.command = "open_view"; + command_arg.url = (*it_tmp); + + open_view( command_arg ); + } + } + + SKELETON::View* view = get_nth_icon( SESSION::image_page() ); + if( view ) switch_img( view->get_url() ); +} + + +// +// ページが含まれていないか +// +bool ImageAdmin::empty() +{ + return ( m_list_view.size() == 0 ); +} + + + +// +// 含まれているページのURLのリスト取得 +// +std::list< std::string > ImageAdmin::get_URLs() +{ + std::list< std::string > urls; + Gtk::Box_Helpers::BoxList::iterator it = m_iconbox.children().begin(); + for(; it != m_iconbox.children().end(); ++it ){ + SKELETON::View* view = dynamic_cast< SKELETON::View* >( it->get_widget() ); + if( view ) urls.push_back( view->get_url() ); + } + + return urls; +} + + + +// +// コアからのクロック入力 +// +// 各viewにクロックを渡すだけ +// +void ImageAdmin::clock_in() +{ + // アイコンにクロックを送る + Gtk::Box_Helpers::BoxList::iterator it = m_iconbox.children().begin(); + for(; it != m_iconbox.children().end(); ++it ){ + SKELETON::View* view = dynamic_cast< SKELETON::View* >( it->get_widget() ); + if( view ) view->clock_in(); + } + + // アクティブなviewにだけクロックを送る + // admin がフォーカスされていないなら何もしない + if( has_focus() ){ + 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; + } + } +} + + + +// +// 現在表示されているページ番号 +// +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 ); + + // すべて保存 + 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_all_views" ) close_other_views( std::string() ); +} + + + +// +// 画像を開く +// +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 ) ){ + switch_img( command.url ); + return; + } + + // アイコン作成 & 表示 + SKELETON::View* icon = Gtk::manage( CORE::ViewFactory( CORE::VIEW_IMAGEICON, command.url ) ); + if( icon ){ + 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作成 + SKELETON::View* view = Gtk::manage( CORE::ViewFactory( CORE::VIEW_IMAGEVIEW, command.url ) ); + if( view ){ + view->show_view(); + m_list_view.push_back( view ); + } + + switch_img( command.url ); +} + + + +// +// タブの切替え +// +void ImageAdmin::tab_left() +{ + if( m_iconbox.children().size() == 1 ) return; + + SKELETON::View* view; + std::string url_to; + SKELETON::View* icon = get_current_icon(); + Gtk::Box_Helpers::BoxList::iterator it = m_iconbox.children().begin(); + for(; it != m_iconbox.children().end(); ++it ){ + view = dynamic_cast< SKELETON::View* >( it->get_widget() ); + if( view ){ + if( view == icon ) break; + url_to = view->get_url(); + } + } + + // 一番最後へ戻る + if( url_to.empty() ){ + it = m_iconbox.children().end(); + view = dynamic_cast< SKELETON::View* >( (--it)->get_widget() ); + if( view ) url_to = view->get_url(); + } + + if( !url_to.empty() ) switch_img( url_to ); + focus_current_view(); +} + + +void ImageAdmin::tab_right() +{ + if( m_iconbox.children().size() == 1 ) return; + + std::string url_to; + SKELETON::View* icon = get_current_icon(); + Gtk::Box_Helpers::BoxList::iterator it = m_iconbox.children().begin(); + for(; it != m_iconbox.children().end(); ++it ){ + SKELETON::View* view = dynamic_cast< SKELETON::View* >( it->get_widget() ); + if( view == icon ){ + + ++it; + // 一番最初へ戻る + if( it == m_iconbox.children().end() ) it = m_iconbox.children().begin(); + + view = dynamic_cast< SKELETON::View* >( it->get_widget() ); + if( view ) url_to = view->get_url(); + break; + } + } + + if( !url_to.empty() ) switch_img( url_to ); + focus_current_view(); +} + + + +// +// タブアイコンの並び替え +// +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 ); + } +} + + + + + +// +// 指定したビューを再描画 +// +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 ); + + // 現在表示中のviewを閉じた場合は次か前の画像に切り替える + if( view && view == get_current_view() ){ + + m_view.remove(); + + SKELETON::View* view_prev = NULL; + SKELETON::View* view_next = NULL; + Gtk::Box_Helpers::BoxList::iterator it = m_iconbox.children().begin(); + for(; it != m_iconbox.children().end(); ++it ){ + SKELETON::View* view_tmp = dynamic_cast< SKELETON::View* >( it->get_widget() ); + if( view_tmp->get_url() == url ){ + if( ++it != m_iconbox.children().end() ) view_next = dynamic_cast< SKELETON::View* >( it->get_widget() ); + 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( view ); + delete view; + } + + if( m_iconbox.children().size() == 0 ) CORE::core_set_command( "empty_page", get_url() ); + else if( ! url_next.empty() ) switch_img( url_next ); +} + + +// +// url 以外の画像を閉じる +// +void ImageAdmin::close_other_views( const std::string& url ) +{ + Gtk::Box_Helpers::BoxList::iterator it = m_iconbox.children().begin(); + for(; it != m_iconbox.children().end(); ++it ){ + SKELETON::View* view = dynamic_cast< SKELETON::View* >( it->get_widget() ); + if( view && view->get_url() != url ) set_command( "close_view", view->get_url() ); + } +} + + + +// +// 現在のviewをフォーカスする +// +// 他のクラスからは直接呼ばないで、set_command()経由で呼ぶこと +// +void ImageAdmin::focus_current_view() +{ +#ifdef _DEBUG + std::cout << "ImageAdmin::focus_current_view\n"; +#endif + + SKELETON::View* view_icon = get_current_icon(); + if( view_icon ) { + + focus_out_all(); + + view_icon->focus_view(); + CORE::core_set_command( "set_url", view_icon->get_url() ); + + SKELETON::View* view = get_current_view(); + if( view ) CORE::core_set_command( "set_status", "", view->get_status() ); + } +} + + + + +// +// 全アイコンのフォーカスをはずす +// +void ImageAdmin::focus_out_all() +{ + Gtk::Box_Helpers::BoxList::iterator it = m_iconbox.children().begin(); + for(; it != m_iconbox.children().end(); ++it ){ + SKELETON::View* view = dynamic_cast< SKELETON::View* >( it->get_widget() ); + 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 + + // 画像切り替え + int page = 0; + std::list< SKELETON::View* >::iterator it_view; + for( it_view = m_list_view.begin(); it_view != m_list_view.end(); ++it_view ){ + + SKELETON::View* view = ( *it_view ); + if( view->get_url() == url ){ + + if( view != 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(); + + // アイコン切り替え + SKELETON::View* view_icon = get_icon( url, page ); + if( view_icon ) view_icon->set_command( "switch_icon" ); + + // タブをスクロール + Gtk::Adjustment* 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 ); + } + } +} + + + +// +// アイコン取得 +// +// pos にアイコンの位置が入る(見付からないときは-1) +// +SKELETON::View* ImageAdmin::get_icon( const std::string& url, int& pos ) +{ + Gtk::Box_Helpers::BoxList::iterator it = m_iconbox.children().begin(); + for( pos = 0; it != m_iconbox.children().end(); ++it, ++pos ){ + SKELETON::View* view = dynamic_cast< SKELETON::View* >( it->get_widget() ); + if( view && view->get_url() == url ) return view; + } + + pos = -1; + return NULL; +} + +// 簡易版 +SKELETON::View* ImageAdmin::get_icon( const std::string& url) +{ + int pos; + return get_icon( url, pos ); +} + + +// +// アイコン取得(番号で) +// +SKELETON::View* ImageAdmin::get_nth_icon( unsigned int n ) +{ + if( n >= m_iconbox.children().size() ) return NULL; + + Gtk::Box_Helpers::BoxList::iterator it = m_iconbox.children().begin(); + for( unsigned int i = 0; i < n; ++i, ++it ); + + return dynamic_cast< SKELETON::View* >( it->get_widget() ); +} + + + +// +// カレントアイコン取得 +// +SKELETON::View* ImageAdmin::get_current_icon() +{ + SKELETON::View* view = get_current_view(); + if( !view ) return NULL; + return get_icon( view->get_url() ); +} + + + + +// +// view 取得 +// +SKELETON::View* ImageAdmin::get_view( const std::string& url ) +{ + std::list< SKELETON::View* >::iterator it_view; + for( it_view = m_list_view.begin(); it_view != m_list_view.end(); ++it_view ){ + if( ( *it_view )->get_url() == url ) return ( *it_view ); + } + + return NULL; +} + + + +// +// カレント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 + + Gtk::Adjustment* 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() +{ + m_scroll = SCROLL_LEFT; + m_counter_scroll = 0; + scroll_tab( m_scroll ); +} + +//右押した +void ImageAdmin::slot_press_right() +{ + m_scroll = SCROLL_RIGHT; + m_counter_scroll = 0; + scroll_tab( m_scroll ); +} + + +//左離した +void ImageAdmin::slot_release_left() +{ + m_scroll = SCROLL_NO; +} + +// 右離した +void ImageAdmin::slot_release_right() +{ + m_scroll = SCROLL_NO; +} + +// +// すべて保存 +// +void ImageAdmin::save_all() +{ +#ifdef _DEBUG + std::cout << "ImageAdmin::save_all\n"; +#endif + + int overwrite = 0; // -1 なら全てNO、1ならすべてYES + + std::list< std::string > list_urls = get_URLs(); + + // ディレクトリ選択 + Gtk::FileChooserDialog diag( "save", Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER ); + diag.add_button( Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL ); + diag.add_button( Gtk::Stock::SAVE, Gtk::RESPONSE_ACCEPT ); + + diag.set_current_folder( SESSION::dir_img_save() ); + + if( diag.run() == Gtk::RESPONSE_ACCEPT ){ + + diag.hide(); + + std::string path_dir = 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 ); + + std::list< std::string >::iterator it = list_urls.begin(); + for( ; it != list_urls.end(); ++it ){ + + std::string url = (*it); + if( ! DBIMG::is_cached( url ) ) continue; + + std::string path_from = CACHE::path_img( 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::is_file_exists( path_to ) == CACHE::EXIST_FILE ){ + + // すべて上書き + if( overwrite == 1 ) CACHE::jdcopy( path_from, path_to ); + else if( overwrite != -1 ){ + + switch_img( url ); + + for(;;){ + + Gtk::MessageDialog mdiag( "ファイルが存在します。ファイル名を変更しますか?", + false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_NONE ); + mdiag.add_button( Gtk::Stock::NO, Gtk::RESPONSE_NO ); + mdiag.add_button( Gtk::Stock::YES, Gtk::RESPONSE_YES ); + mdiag.add_button( "上書き", Gtk::RESPONSE_YES + 100 ); + mdiag.add_button( "すべていいえ", Gtk::RESPONSE_NO + 200 ); + mdiag.add_button( "すべて上書き", Gtk::RESPONSE_YES + 200 ); + int ret = mdiag.run(); + mdiag.hide(); + + switch( ret ){ + + // すべて上書き + case Gtk::RESPONSE_YES + 200: + overwrite = 1; + // 上書き + case Gtk::RESPONSE_YES + 100: + CACHE::jdcopy( path_from, path_to ); + break; + + // 名前変更 + case Gtk::RESPONSE_YES: + if( ! DBIMG::save( url, path_to ) ) continue; + break; + + // すべていいえ + case Gtk::RESPONSE_NO + 200: + overwrite = -1; + break; + + default: + break; + } + + break; + } + + } + + } + else CACHE::jdcopy( path_from, path_to ); + } + + } + else MISC::ERRMSG( "can't create " + path_dir ); + } +} diff --git a/src/image/imageadmin.h b/src/image/imageadmin.h new file mode 100644 index 000000000..1f79cba24 --- /dev/null +++ b/src/image/imageadmin.h @@ -0,0 +1,90 @@ +// ライセンス: 最新のGPL + +// +// 板の管理クラス +// +#ifndef _IMAGEADMIN_H +#define _IMAGEADMIN_H + +#include "skeleton/admin.h" + +#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; + + // Gtk::manageで作ってるので view は deleteしなくても良い + std::list< SKELETON::View* > m_list_view; + + int m_scroll; + int m_counter_scroll; + + public: + + ImageAdmin( const std::string& url ); + ~ImageAdmin(); + + Gtk::HBox& tab() { return m_tab; } + Gtk::EventBox& view() { return m_view; } + + virtual bool empty(); + virtual std::list< std::string > get_URLs(); + virtual void clock_in(); + + virtual int get_current_page(); + + protected: + virtual void command_local( const COMMAND_ARGS& command ); + + private: + + virtual void restore(); + virtual void open_view( const COMMAND_ARGS& command ); + virtual void tab_left(); + virtual void tab_right(); + virtual void redraw_view( const std::string& url ); + virtual void redraw_current_view(); + virtual void close_view( const std::string& url ); + virtual void focus_current_view(); + void close_other_views( const std::string& url ); + void reorder( const std::string& url_from, const std::string& url_to ); + 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( unsigned int n ); + SKELETON::View* get_current_icon(); + SKELETON::View* get_view( const std::string& url ); + SKELETON::View* get_current_view(); + + // スクロール + void scroll_tab( int scroll ); + void slot_press_left(); + void slot_press_right(); + void slot_release_left(); + void slot_release_right(); + + void save_all(); + }; + + IMAGE::ImageAdmin* get_admin(); + void delete_admin(); +}; + + +#endif diff --git a/src/image/imagearea.cpp b/src/image/imagearea.cpp new file mode 100644 index 000000000..abf181153 --- /dev/null +++ b/src/image/imagearea.cpp @@ -0,0 +1,112 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "imagearea.h" + +#include "dbimg/img.h" + +#include "jdlib/miscmsg.h" + +#include "cache.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 IMAGE; + + +ImageAreaMain::ImageAreaMain( const std::string& url ) + : ImageAreaBase( url ) +{ +#ifdef _DEBUG + std::cout << "ImageAreaMain::ImageAreaMain url = " << url << std::endl; +#endif + + show_image(); +} + + + +// +// 表示 +// +void ImageAreaMain::show_image() +{ +#ifdef _DEBUG + std::cout << "ImageAreaMain::show_image url = " << get_url() << std::endl; +#endif + + set_errmsg( std::string() ); + int width_max = get_width(); + int height_max = get_height(); + + 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; + } + + // まだrealizeしてなくてウィンドウサイズが取得できていないのでImageViewMain::clock_in()経由で後でもう一度呼ぶ + if( ! is_ready() && ( width_max <= 1 || height_max <= 1 ) ) return; + + bool mosaic = get_img()->get_mosaic(); + bool zoom_to_fit = get_img()->is_zoom_to_fit(); + int size = get_img()->get_size(); + + clear(); + + try{ + + // 画像ロード + std::string path_cache = CACHE::path_img( get_url() ); + Glib::RefPtr< Gdk::Pixbuf > pixbuf; + pixbuf = Gdk::Pixbuf::create_from_file( path_cache ); + set_width_org( pixbuf->get_width() ); + set_height_org( pixbuf->get_height() ); + set_width( get_width_org() ); + set_height( get_height_org() ); + + // スケール調整 + bool do_scale = false; + double scale = 0; + + // 画面サイズに合わせる + if( zoom_to_fit ){ + double scale_w = ( double ) width_max / get_width(); + double scale_h = ( double ) height_max / get_height(); + scale = MIN( scale_w, scale_h ); + if( scale < 1 ) do_scale = true; + } + + else{ + + // サイズ変更 + if( size != 100 ){ + scale = size/100.; + do_scale = true; + } + } + + set_image( pixbuf, mosaic, do_scale, scale ); + + //データベースのサイズ情報更新 + get_img()->set_size( get_width() * 100 / get_width_org() ); + } + catch( Glib::Error& err ) + { + set_errmsg( err.what() ); + MISC::ERRMSG( get_errmsg() ); + set_width( width_max ); + set_height( width_max ); + } + + set_ready( true ); +} diff --git a/src/image/imagearea.h b/src/image/imagearea.h new file mode 100644 index 000000000..c4eb231fa --- /dev/null +++ b/src/image/imagearea.h @@ -0,0 +1,23 @@ +// ライセンス: 最新のGPL + +// +// メイン画像クラス +// + +#ifndef _IMAGEAREA_H +#define _IMAGEAREA_H + +#include "imageareabase.h" + +namespace IMAGE +{ + class ImageAreaMain : public ImageAreaBase + { + public: + ImageAreaMain( const std::string& url ); + + virtual void show_image(); + }; +} + +#endif diff --git a/src/image/imageareabase.cpp b/src/image/imageareabase.cpp new file mode 100644 index 000000000..c08b7a9f4 --- /dev/null +++ b/src/image/imageareabase.cpp @@ -0,0 +1,79 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "imageareabase.h" + +#include "dbimg/imginterface.h" +#include "dbimg/img.h" + +#include "httpcode.h" +#include "cache.h" + +using namespace IMAGE; + + +ImageAreaBase::ImageAreaBase( const std::string& url ) + : m_url( url ), + m_img ( DBIMG::get_img( m_url ) ), + m_ready( false ), + m_width_org( 0 ), + m_height_org( 0 ), + m_width( 0 ), + m_height( 0 ) +{ + assert( m_img ); + + set_alignment( Gtk::ALIGN_CENTER, Gtk::ALIGN_CENTER ); +} + + + +ImageAreaBase::~ImageAreaBase() +{ +#ifdef _DEBUG + std::cout << "ImageAreaBase::~ImageArea url = " << m_url << std::endl; +#endif +} + + + +// +// キャッシュされてるか判定 +// +const bool ImageAreaBase::is_cached() +{ + if( m_img->is_loading() ) return false; + return ( m_img->is_cached() && m_img->get_code() == HTTP_OK ); +} + + + +// +// 画像をセット +// +void ImageAreaBase::set_image( Glib::RefPtr< Gdk::Pixbuf >& pixbuf, bool mosaic, bool do_scale, double scale ) +{ + if( do_scale ){ + m_width = ( int ) ( m_width * scale ); + m_height = ( int ) ( m_height * scale ); + } + + // モザイク表示 + if( mosaic ){ + int size_mosaic = 20; // モザイク画像は 1/size_mosaic にしてもとのサイズに直す + if( m_width_org / size_mosaic < 16 ) size_mosaic = m_width_org / 16; + + Glib::RefPtr< Gdk::Pixbuf > pixbuf2; + pixbuf2 = pixbuf->scale_simple( m_width_org / size_mosaic, m_height_org / size_mosaic, Gdk::INTERP_NEAREST ); + set( pixbuf2->scale_simple( m_width, m_height, Gdk::INTERP_NEAREST ) ); + } + + // 通常表示 + else{ + + if( do_scale ) set( pixbuf->scale_simple( m_width, m_height, Gdk::INTERP_NEAREST ) ); + else set( CACHE::path_img( m_url ) ); + } +} diff --git a/src/image/imageareabase.h b/src/image/imageareabase.h new file mode 100644 index 000000000..6425cdfdc --- /dev/null +++ b/src/image/imageareabase.h @@ -0,0 +1,73 @@ +// ライセンス: 最新のGPL + +// +// 画像クラスのベースクラス +// + +#ifndef _IMAGEAREABASE_H +#define _IMAGEAREABASE_H + +#include + +#include "jdlib/constptr.h" + + +namespace DBIMG +{ + class Img; +} + + +namespace IMAGE +{ + class ImageAreaBase : public Gtk::Image + { + std::string m_url; + JDLIB::ConstPtr< DBIMG::Img > m_img; + + std::string m_errmsg; // エラーメッセージ + + bool m_ready; // show_image()が呼ばれたらtrueにセット + + int m_width_org; + int m_height_org; + int m_width; + int m_height; + + + + protected: + + JDLIB::ConstPtr< 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_org( int width_org ){ m_width_org = width_org; } + void set_height_org( int height_org ){ m_height_org = height_org; } + void set_width( int width ){ m_width = width; } + void set_height( int height ){ m_height = height; } + + public: + + ImageAreaBase( const std::string& url ); + ~ImageAreaBase(); + + const std::string& get_url() const{ return m_url;} + const std::string& get_errmsg() const{ return m_errmsg;} + const bool is_ready() const { return m_ready; } + const int get_width_org() const { return m_width_org; } + const int get_height_org() const { return m_height_org; } + const int get_width() const { return m_width; } + const int get_height() const { return m_height; } + + virtual void show_image(){} + void set_fit_in_win( bool fit ); + const bool is_cached(); + + protected: + + void set_image( Glib::RefPtr< Gdk::Pixbuf >& pixbuf, bool mosaic, bool do_scale, double scale ); + }; +} + +#endif diff --git a/src/image/imageareaicon.cpp b/src/image/imageareaicon.cpp new file mode 100644 index 000000000..388039b5e --- /dev/null +++ b/src/image/imageareaicon.cpp @@ -0,0 +1,179 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "imageareaicon.h" + +#include "dbimg/img.h" + +#include "jdlib/miscmsg.h" + +#include "cache.h" +#include "global.h" +#include "httpcode.h" + + +typedef void* ( *FUNC )( void * ); + +using namespace IMAGE; + +ImageAreaIcon::ImageAreaIcon( const std::string& url ) + : ImageAreaBase( url ) + , m_thread_running( false ) + , m_shown( false ) +{ +#ifdef _DEBUG + std::cout << "ImageAreaIcon::ImageAreaIcon url = " << url << std::endl; +#endif + + m_disp.connect( sigc::mem_fun( *this, &ImageAreaIcon::slot_set_image ) ); + + set_width( ICON_SIZE ); + set_height( ICON_SIZE ); + + show_image_thread(); +} + + + +ImageAreaIcon::~ImageAreaIcon() +{} + + +// +// 表示 +// +// メインスレッドで大きい画像を開くと反応が無くなるので別の +// スレッドを起動して開く。スレッドはデタッチしておく +// +void ImageAreaIcon::show_image() +{ + if( m_thread_running ) return; // スレッド動作中 + + // 既に画像が表示されている + if( m_shown && is_cached() ) return; + + m_shown = false; + + int status; + if( ( status = pthread_create( &m_thread, NULL, ( FUNC ) launcher, ( void * ) this ) )){ + MISC::ERRMSG( "ImageAreaIcon::show_image : could not start thread" ); + } + else{ + m_thread_running = true; + pthread_detach( m_thread ); + } +} + + +// +// スレッドのランチャ (static) +// +void* ImageAreaIcon::launcher( void* dat ) +{ + ImageAreaIcon* tt = ( ImageAreaIcon * ) dat; + tt->show_image_thread(); + return 0; +} + + + +// +// 表示スレッド +// +void ImageAreaIcon::show_image_thread() +{ +#ifdef _DEBUG + std::cout << "ImageAreaIcon::show_image_thread url = " << get_url() << std::endl; +#endif + + set_ready( false ); + + // キャッシュされてない時は読み込みorエラーマークを表示 + if( ! is_cached() ){ + + // インジゲータ画像作成 + 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, get_width()/4, get_height()/4 ); + m_pixbuf_loading = Gdk::Pixbuf::create( Gdk::COLORSPACE_RGB, false, 8, get_width()/4, get_height()/4 ); + + assert( m_pixbuf ); + assert( m_pixbuf_err ); + assert( m_pixbuf_loading ); + + m_pixbuf->fill( 0xffffff00 ); + m_pixbuf_loading->fill( 0xffbf0000 ); + m_pixbuf_err->fill( 0xff000000 ); + } + + // 読み込み中 + if( get_img()->is_loading() + || get_img()->get_code() == HTTP_INIT ) m_pixbuf_loading->copy_area( 0, 0, get_width()/4, get_height()/4, m_pixbuf, 4, 4 ); + + // エラー + else m_pixbuf_err->copy_area( 0, 0, get_width()/4, get_height()/4, m_pixbuf, 4, 4 ); + + // 表示 + // ImageAreaIcon::slot_set_image()を呼び出す + m_disp.emit(); + } + + // 画像を読み込んで縮小表示 + else{ + + try{ + + // 画像ロード + std::string path_cache = CACHE::path_img( get_url() ); + Glib::RefPtr< Gdk::Pixbuf > pixbuf; + pixbuf = Gdk::Pixbuf::create_from_file( path_cache ); + set_width_org( pixbuf->get_width() ); + set_height_org( pixbuf->get_height() ); + + // 縮小比率を計算して縮小 + + double scale; + double scale_w = ( double ) ICON_SIZE / get_width_org(); + double scale_h = ( double ) ICON_SIZE / get_height_org(); + scale = MIN( scale_w, scale_h ); + set_width( (int)( get_width_org() * scale ) ); + set_height( (int)( get_height_org() * scale ) ); + m_pixbuf_icon = pixbuf->scale_simple( get_width(), get_height(), Gdk::INTERP_NEAREST ); + + // 表示 + // mageAreaIcon::slot_set_image()を呼び出す + m_disp.emit(); + + m_shown = true; + } + catch( Glib::Error& err ) + { + set_errmsg( err.what() ); + MISC::ERRMSG( get_errmsg() ); + } + } + +#ifdef _DEBUG + std::cout << "ImageAreaIcon::show_image_thread finished\n"; +#endif + + m_thread_running = false; + set_ready( true ); +} + + +// +// 表示 +// +// スレッドの中でset()すると固まるときがあるのでディスパッチャで +// メインスレッドに戻してからセットする +// +void ImageAreaIcon::slot_set_image() +{ + clear(); + if( m_pixbuf_icon ) set( m_pixbuf_icon ); + else if( m_pixbuf ) set( m_pixbuf ); +} diff --git a/src/image/imageareaicon.h b/src/image/imageareaicon.h new file mode 100644 index 000000000..8f7d2d287 --- /dev/null +++ b/src/image/imageareaicon.h @@ -0,0 +1,42 @@ +// ライセンス: 最新のGPL + +// +// アイコン画像クラス +// + +#ifndef _IMAGEAREAICON_H +#define _IMAGEAREAICON_H + +#include "imageareabase.h" + +#include + +namespace IMAGE +{ + class ImageAreaIcon : public ImageAreaBase + { + Glib::Dispatcher m_disp; + + bool m_thread_running; + pthread_t m_thread; + bool m_shown; + + 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: + ImageAreaIcon( const std::string& url ); + ~ImageAreaIcon(); + + virtual void show_image(); + + private: + static void* launcher( void* ); + virtual void show_image_thread(); + void slot_set_image(); + }; +} + +#endif diff --git a/src/image/imageareapopup.cpp b/src/image/imageareapopup.cpp new file mode 100644 index 000000000..a267c1141 --- /dev/null +++ b/src/image/imageareapopup.cpp @@ -0,0 +1,87 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "imageareapopup.h" + +#include "dbimg/img.h" + +#include "jdlib/miscmsg.h" + +#include "config/globalconf.h" + +#include "cache.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 IMAGE; + +ImageAreaPopup::ImageAreaPopup( const std::string& url ) + : ImageAreaBase( url ) +{ +#ifdef _DEBUG + std::cout << "ImageAreaPopup::ImageAreaPopup url = " << url << std::endl; +#endif + + show_image(); +} + + + +// +// 表示 +// +void ImageAreaPopup::show_image() +{ +#ifdef _DEBUG + std::cout << "ImageAreaPopup::show_image url = " << get_url() << std::endl; +#endif + + set_errmsg( std::string() ); + bool mosaic = get_img()->get_mosaic(); + int width_max = CONFIG::get_imgpopup_width(); + int height_max = CONFIG::get_imgpopup_height(); + + clear(); + + try{ + + // 画像ロード + std::string path_cache = CACHE::path_img( get_url() ); + Glib::RefPtr< Gdk::Pixbuf > pixbuf; + pixbuf = Gdk::Pixbuf::create_from_file( path_cache ); + set_width_org( pixbuf->get_width() ); + set_height_org( pixbuf->get_height() ); + set_width( get_width_org() ); + set_height( get_height_org() ); + + // スケール調整 + bool do_scale = false; + double scale; + + double scale_w = ( double ) width_max / get_width(); + double scale_h = ( double ) height_max / get_height(); + scale = MIN( scale_w, scale_h ); + if( scale < 1 ) do_scale = true; + + set_image( pixbuf, mosaic, do_scale, scale ); + } + catch( Glib::Error& err ) + { + set_errmsg( err.what() ); + MISC::ERRMSG( get_errmsg() ); + set_width( width_max ); + set_height( width_max ); + } + + set_ready( true ); +} diff --git a/src/image/imageareapopup.h b/src/image/imageareapopup.h new file mode 100644 index 000000000..864095bdc --- /dev/null +++ b/src/image/imageareapopup.h @@ -0,0 +1,23 @@ +// ライセンス: 最新のGPL + +// +// ポップアップ画像クラス +// + +#ifndef _IMAGEAREAPOPUP_H +#define _IMAGEAREAPOPUP_H + +#include "imageareabase.h" + +namespace IMAGE +{ + class ImageAreaPopup : public ImageAreaBase + { + public: + ImageAreaPopup( const std::string& url ); + + virtual void show_image(); + }; +} + +#endif diff --git a/src/image/imageview.cpp b/src/image/imageview.cpp new file mode 100644 index 000000000..8fc2ef305 --- /dev/null +++ b/src/image/imageview.cpp @@ -0,0 +1,308 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "imageview.h" +#include "imagearea.h" + +#include "dbimg/img.h" + +#include "command.h" +#include "httpcode.h" + +#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 IMAGE; + + +ImageViewMain::ImageViewMain( const std::string& url ) + : ImageViewBase( url ), + m_scrwin( 0 ), + m_length_prev( 0 ), + m_show_status( false ), + m_show_label( 0 ) +{ +#ifdef _DEBUG + std::cout << "ImageViewMain::ImageViewMain : " << get_url() << std::endl; +#endif + + // スクロールウィンドウを作って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(); +} + + +// +// クロック入力 +// +void ImageViewMain::clock_in() +{ + // viewがアクティブになった(クロック入力が来た)ときにステータス表示 + if( m_show_status ){ + m_show_status = false; + CORE::core_set_command( "set_status", "", get_status() ); + } + + // ロード中 + if( loading() ){ + + // 読み込みサイズの表示更新 + if( get_img()->is_loading() ) show_status(); + + // ロード完了 + // 次にclock_in()が呼ばれたら下のelseの中に入る + else{ + + set_loading( false ); + show_view(); + show_status(); + } + } + + // ロード中でない + else{ + + // バックグラウンドで開いた時やロード直後に画像を表示すると重くなるので + // ビューがアクティブになった(クロック入力が来た) 時点で画面を表示する + if( get_imagearea() && ! get_imagearea()->is_ready() ) { + + get_imagearea()->show_image(); + + // 読み込みエラーが起きたらimageareaを除いてラベルを貼る + if( ! get_imagearea()->get_errmsg().empty() ){ + set_status( get_imagearea()->get_errmsg() ); + remove_imagearea(); + set_label(); + } + + show_status(); + } + + } +} + + + +// +// ラベルを貼る +// +void ImageViewMain::set_label() +{ + if( !m_show_label ){ + m_label.set_text( get_status() ); + 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( loading() ) 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_loading() ){ + +#ifdef _DEBUG + std::cout << "now loading\n"; +#endif + set_loading( true ); + m_length_prev = 0; + set_status( "loading..." ); + 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{ + + set_status( get_img()->get_str_code() ); + m_show_status = true; // viewがアクティブになった時点でステータス表示 + + set_label(); + } + + show_all_children(); +} + + + +// +// ステータス表示 +// +void ImageViewMain::show_status() +{ + if( ! loading() ){ + + // 画像が表示されていたら画像情報 + if( get_imagearea() ){ + + std::stringstream ss; + ss << get_imagearea()->get_width_org() << " x " << get_imagearea()->get_height_org(); + if( get_imagearea()->get_width_org() ) + ss << " (" << get_img()->get_size() << " %)"; + ss << " " << get_img()->total_length()/1024 << " kb "; + if( get_img()->is_protected() ) ss << " キャッシュ保護されています"; + + set_status( ss.str() ); + } + + // エラー(ネットワーク系) + else if( get_img()->get_code() != HTTP_OK ) set_status( get_img()->get_str_code() ); + + // ステータス標示 + CORE::core_set_command( "set_status", "", get_status() ); + if( m_show_label ) m_label.set_text( get_status() ); + +#ifdef _DEBUG + std::cout << "ImageViewMain::show_status : " << get_status() << std::endl;; +#endif + } + + // ロード中 + else{ + + // 読み込みサイズが更新した場合 + if( m_length_prev != get_img()->current_length() ){ + + m_length_prev = get_img()->current_length(); + + char tmpstr[ 256 ]; + snprintf( tmpstr, 256, "%d k / %d k", m_length_prev/1024, get_img()->total_length()/1024 ); + set_status( tmpstr ); + + // ステータス標示 + CORE::core_set_command( "set_status", "", get_status() ); + if( m_show_label ) m_label.set_text( get_status() ); + +#ifdef _DEBUG + std::cout << "ImageViewMain::show_status : " << get_status() << std::endl;; +#endif + } + } +} + + +// +// ポップアップメニュー +// +void ImageViewMain::show_popupmenu() +{ + Gtk::Menu* popupmenu = dynamic_cast< Gtk::Menu* >( ui_manager()->get_widget( "/popup_menu" ) ); + if( popupmenu ) popupmenu->popup( 0, gtk_get_current_event_time() ); +} + + + + +// +// ボタンクリック +// +bool ImageViewMain::slot_button_press_imagearea( GdkEventButton* event ) +{ + ImageViewBase::slot_button_press_imagearea( event ); + + // ドラッグして画像移動するときの起点 + m_x_motion = event->x_root; + m_y_motion = event->y_root; + + return true; +} + + + + +// +// マウスモーション +// +bool ImageViewMain::slot_motion_notify_imagearea( GdkEventMotion* event ) +{ + ImageViewBase::slot_motion_notify_imagearea( event ); + + // スクロールバー移動 + if( m_scrwin && event->state == GDK_BUTTON1_MASK ){ + + Gtk::Adjustment* hadj = m_scrwin->get_hadjustment(); + Gtk::Adjustment* vadj = m_scrwin->get_vadjustment(); + + gdouble dx = event->x_root - m_x_motion; + gdouble dy = event->y_root - m_y_motion; + + 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 ) ) ); + } + + return true; +} diff --git a/src/image/imageview.h b/src/image/imageview.h new file mode 100644 index 000000000..a680b904e --- /dev/null +++ b/src/image/imageview.h @@ -0,0 +1,44 @@ +// ライセンス: 最新のGPL + +// +// 画像ビュークラス +// + +#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; + unsigned int m_length_prev; + bool m_show_status; + bool m_show_label; + + public: + ImageViewMain( const std::string& url ); + + virtual void clock_in(); + virtual void show_view(); + + protected: + virtual void show_status(); + virtual void show_popupmenu(); + + virtual bool slot_button_press_imagearea( GdkEventButton* event ); + virtual bool slot_motion_notify_imagearea( GdkEventMotion* event ); + + private: + void set_label(); + void remove_label(); + }; +} + +#endif + diff --git a/src/image/imageviewbase.cpp b/src/image/imageviewbase.cpp new file mode 100644 index 000000000..1415aaa8b --- /dev/null +++ b/src/image/imageviewbase.cpp @@ -0,0 +1,809 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "imageadmin.h" +#include "imageviewbase.h" +#include "imageareabase.h" +#include "preference.h" + +#include "dbtree/interface.h" + +#include "dbimg/imginterface.h" +#include "dbimg/img.h" + +#include "jdlib/miscutil.h" + +#include "cache.h" +#include "command.h" +#include "sharedbuffer.h" +#include "global.h" +#include "controlutil.h" +#include "controlid.h" + +#include + + +#define SIZE_MENU { 25, 50, 75, 100, 150, 200, 300, 400 } + + + +using namespace IMAGE; + + +ImageViewBase::ImageViewBase( const std::string& url, const std::string& arg1, const std::string& arg2 ) + : SKELETON::View( url ), + m_loading( false ), + m_enable_menuslot( true ) +{ + // 高速化のためデータベースに直接アクセス + m_img = DBIMG::get_img( get_url() ); + assert( m_img ); + + // マウスジェスチャ可能 + SKELETON::View::set_enable_mg( true ); + + // コントロールモード設定 + SKELETON::View::get_control().set_mode( CONTROL::MODE_IMAGE ); +} + + + +ImageViewBase::~ImageViewBase() +{ +#ifdef _DEBUG + std::cout << "ImageViewBase::~ImageViewBase : " << get_url() << std::endl; +#endif +} + + +// +// 共通セットアップ +// +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_flags( m_event.get_flags() | Gtk::CAN_FOCUS ); + m_event.add_events( Gdk::POINTER_MOTION_MASK ); + + m_event.signal_key_press_event().connect( sigc::mem_fun(*this, &ImageViewBase::slot_key_press_imagearea ) ); + m_event.signal_button_press_event().connect( sigc::mem_fun( *this, &ImageViewBase::slot_button_press_imagearea ) ); + m_event.signal_button_release_event().connect( sigc::mem_fun( *this, &ImageViewBase::slot_button_release_imagearea ) ); + m_event.signal_motion_notify_event().connect( sigc::mem_fun( *this, &ImageViewBase::slot_motion_notify_imagearea ) ); + 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( "LoadStop", "ロード中止"), sigc::mem_fun( *this, &ImageViewBase::stop ) ); + action_group()->add( Gtk::Action::create( "Reload", "強制再読み込み"), 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", "サイズ変更" ) ); + + // サイズ + 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 = MISC::itostr( tmp_size ); + Glib::RefPtr< Gtk::Action > action = Gtk::Action::create( "Size" + str_size, str_size + "%" ); + action_group()->add( action, sigc::bind< int >( sigc::mem_fun( *this, &ImageViewBase::slot_resize_image ), tmp_size ) ); + } + + action_group()->add( Gtk::Action::create( "Quit", "Quit" ), sigc::mem_fun( *this, &ImageViewBase::close_view ) ); + + action_group()->add( Gtk::Action::create( "CloseOther_Menu", "他の画像を閉じる" ) ); + action_group()->add( Gtk::Action::create( "CloseOther", "閉じる" ), sigc::mem_fun( *this, &ImageViewBase::slot_close_other_views ) ); + + action_group()->add( Gtk::Action::create( "CloseAll_Menu", "全ての画像を閉じる" ) ); + action_group()->add( Gtk::Action::create( "CloseAll", "閉じる" ), + sigc::mem_fun( *this, &ImageViewBase::slot_close_all_views ) ); + + action_group()->add( Gtk::Action::create( "OpenBrowser", "ブラウザで開く"), + sigc::mem_fun( *this, &ImageViewBase::slot_open_browser ) ); + action_group()->add( Gtk::Action::create( "OpenRef", "参照元のレスを開く"), sigc::mem_fun( *this, &ImageViewBase::slot_open_ref ) ); + action_group()->add( Gtk::Action::create( "CopyURL", "URLをコピー"), 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", "すべて保存"), sigc::mem_fun( *this, &ImageViewBase::slot_save_all ) ); + + action_group()->add( Gtk::Action::create( "DeleteMenu", "Delete" ) ); + action_group()->add( Gtk::Action::create( "DeleteImage", "削除する"), sigc::mem_fun( *this, &ImageViewBase::delete_view ) ); + action_group()->add( Gtk::ToggleAction::create( "ProtectImage", "キャッシュを保護する", std::string(), false ), + sigc::mem_fun( *this, &ImageViewBase::slot_toggle_protectimage ) ); + + action_group()->add( Gtk::Action::create( "Preference", "Property"), sigc::mem_fun( *this, &ImageViewBase::slot_preference ) ); + + ui_manager() = Gtk::UIManager::create(); + ui_manager()->insert_action_group( action_group() ); + + Glib::ustring str_ui = + + "" + + "" + + "" + "" + + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + + "" + "" + "" + + "" + "" + + "" + "" + "" + + "" + "" + "" + + "" + "" + "" + "" + "" + + "" + + + "" + + ////////////////////////// + + // アイコンのメニュー + + "" + + "" + "" + "" + "" + "" + "" + "" + "" + + "" + "" + "" + + "" + "" + + "" + "" + "" + + "" + "" + "" + + "" + "" + "" + "" + "" + + "" + + "" + + ""; + + ui_manager()->add_ui_from_string( str_ui ); + + + // ポップアップメニューにキーアクセレータを表示 + 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 ); +} + + + +// +// ImageAreaBaseのセット +// +void ImageViewBase::set_imagearea( ImageAreaBase* imagearea ) +{ + assert( imagearea ); + + m_imagearea.clear(); + 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(); + m_imagearea.clear(); + } +} + + + +// +// コマンド +// +bool ImageViewBase::set_command( const std::string& command, const std::string& arg ) +{ + if( command == "switch_icon" ) switch_icon(); + + return true; +} + + + +// +// 再読込 +// +void ImageViewBase::reload() +{ +#ifdef _DEBUG + std::cout << "ImageViewBase::reload url = " << get_url() << std::endl; +#endif + + m_img->download_img(); +} + + + + +// +// ロード停止 +// +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::close_view() +{ + IMAGE::get_admin()->set_command( "close_view", get_url() ); + CORE::core_set_command( "switch_image" ); +} + + + +// +// 他の画像を閉じる +// +void ImageViewBase::slot_close_other_views() +{ + IMAGE::get_admin()->set_command( "close_other_views", get_url() ); +} + + +// +// 全ての画像を閉じる +// +void ImageViewBase::slot_close_all_views() +{ + IMAGE::get_admin()->set_command( "close_all_views" ); +} + + +// +// プロパティ +// +void ImageViewBase::slot_preference() +{ + Preferences pref( get_url() ); + pref.run(); +} + + +// +// 削除 +// +void ImageViewBase::delete_view() +{ + CORE::core_set_command( "delete_image", get_url() ); + CORE::core_set_command( "switch_image" ); +} + + + +// +// viewの操作 +// +void ImageViewBase::operate_view( const int& control ) +{ +#ifdef _DEBUG + std::cout << "ImageViewBase::operate_view control = " << control << std::endl; +#endif + + switch( control ){ + + case CONTROL::CancelMosaic: + 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::Reload: + reload(); + break; + + case CONTROL::StopLoading: + stop(); + break; + + case CONTROL::Quit: + close_view(); + break; + + case CONTROL::TabLeft: + IMAGE::get_admin()->set_command( "tab_left" ); + break; + + case CONTROL::TabRight: + IMAGE::get_admin()->set_command( "tab_right" ); + break; + + // article に切り替え + case CONTROL::Left: + CORE::core_set_command( "switch_article" ); + break; + + case CONTROL::ToggleArticle: + CORE::core_set_command( "toggle_article" ); + break; + + case CONTROL::Save: + slot_save(); + break; + + case CONTROL::Delete: + + if( !m_img->is_protected() ){ + Gtk::MessageDialog mdiag( "画像を削除しますか?", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_OK_CANCEL ); + if( mdiag.run() != Gtk::RESPONSE_OK ) return; + delete_view(); + } + break; + } +} + + + + + +// +// キープレスイベント +// +bool ImageViewBase::slot_key_press_imagearea( GdkEventKey* event ) +{ +#ifdef _DEBUG + std::cout << "ImageViewBase::slot_key_press_imagearea url = " << get_url() << std::endl; +#endif + + operate_view( SKELETON::View::get_control().key_press( event ) ); + + return true; +} + + + +// +// ボタンクリック +// +bool ImageViewBase::slot_button_press_imagearea( GdkEventButton* event ) +{ +#ifdef _DEBUG + std::cout << "ImageViewBase::slot_button_press_event url = " << get_url() << std::endl; +#endif + + // ダブルクリック + m_dblclick = false; + if( event->type == GDK_2BUTTON_PRESS ) m_dblclick = true; + + // マウスジェスチャ + SKELETON::View::get_control().MG_start( event ); + + return true; +} + + + +// +// マウスボタンのリリースイベント +// +bool ImageViewBase::slot_button_release_imagearea( GdkEventButton* event ) +{ + /// マウスジェスチャ + int mg = SKELETON::View::get_control().MG_end( event ); + 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( SKELETON::View::get_control().button_alloted( event, CONTROL::ClickButton ) ){ + IMAGE::get_admin()->set_command( "switch_image", get_url() ); + CORE::core_set_command( "switch_image" ); + } + + // 再読み込み + else if( SKELETON::View::get_control().button_alloted( event, CONTROL::ReloadTabButton ) ) reload(); + + // 閉じる + else if( SKELETON::View::get_control().button_alloted( event, CONTROL::CloseTabButton ) ) close_view(); + + // ポップアップメニュー + else if( SKELETON::View::get_control().button_alloted( event, CONTROL::PopupmenuButton ) ){ + + // toggle アクションを activeにするとスロット関数が呼ばれるので処理しないようにする + m_enable_menuslot = false; + + Glib::RefPtr< Gtk::Action > act; + Glib::RefPtr< Gtk::ToggleAction > tact; + + bool current_protect = m_img->is_protected(); + + // モザイク + act = action_group()->get_action( "CancelMosaic" ); + if( m_img->is_cached() && m_img->get_mosaic() ) act->set_sensitive( true ); + else act->set_sensitive( false ); + + // 参照元スレ + act = action_group()->get_action( "OpenRef" ); + if( ! m_img->refurl().empty() ) act->set_sensitive( true ); + else act->set_sensitive( false ); + + // 保護 + act = action_group()->get_action( "ProtectImage" ); + tact = Glib::RefPtr< Gtk::ToggleAction >::cast_dynamic( act ); + if( current_protect ) tact->set_active( true ); + else tact->set_active( false ); + + // 削除 + act = action_group()->get_action( "DeleteImage" ); + if( m_img->is_cached() && ! m_img->is_protected() ) act->set_sensitive( true ); + else act->set_sensitive( false ); + + // ロード停止 + act = action_group()->get_action( "LoadStop" ); + if( m_img->is_loading() ) act->set_sensitive( true ); + else act->set_sensitive( false ); + + m_enable_menuslot = true; + + show_popupmenu(); // 各派生ビュー別に表示するメニューを変える + } + + + event->type = type_copy; + + return true; +} + + + +// +// マウスモーション +// +bool ImageViewBase::slot_motion_notify_imagearea( GdkEventMotion* event ) +{ + /// マウスジェスチャ + SKELETON::View::get_control().MG_motion( event ); + + return true; +} + + +// +// 強制再読み込み +// +void ImageViewBase::slot_reload_force() +{ + if( ! m_enable_menuslot ) return; + + m_img->set_code( 0 ); + reload(); + CORE::core_set_command( "redraw", get_url() ); +} + + +// +// モザイク解除 +// +void ImageViewBase::slot_cancel_mosaic() +{ + if( ! m_enable_menuslot ) return; + + if( ! m_img->is_cached() ) return; + m_img->set_mosaic( false ); + CORE::core_set_command( "redraw", get_url() ); +} + + + +// +// ウィンドウにサイズを合わせる +// +void ImageViewBase::slot_fit_win() +{ + 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_open_browser() +{ + if( ! m_enable_menuslot ) return; + + std::string url = get_url(); + if( m_img->is_cached() ) url = "file://" + CACHE::path_img( get_url() ); + CORE::core_set_command( "open_url_browser", url ); +} + + + +// +// 参照元を開く +// +void ImageViewBase::slot_open_ref() +{ + if( ! m_enable_menuslot ) return; + + std::string refurl = m_img->refurl(); + + int center, from, to; + std::string url = DBTREE::url_dat( refurl, center, to ); + 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(), MISC::itostr( center ) ); +} + + + +// +// URLをクリップボードにコピー +// +void ImageViewBase::slot_copy_url() +{ + if( ! m_enable_menuslot ) return; + + COPYCLIP( get_url() ); +} + + +// +// 保存メニュー +// +void ImageViewBase::slot_save() +{ + if( ! m_enable_menuslot ) return; + + m_img->save( 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::set_image_to_buffer() +{ + CORE::DATA_INFO info; + info.type = TYPE_IMAGE; + info.url = get_url(); + info.name = get_url(); + + CORE::SBUF_clear_info(); + CORE::SBUF_append( info ); +} diff --git a/src/image/imageviewbase.h b/src/image/imageviewbase.h new file mode 100644 index 000000000..61f8c917c --- /dev/null +++ b/src/image/imageviewbase.h @@ -0,0 +1,114 @@ +// ライセンス: 最新のGPL + +// +// 画像ビューのベースクラス +// + +#ifndef _IMAGEVIEWBASE_H +#define _IMAGEVIEWBASE_H + +#include "skeleton/view.h" +#include "skeleton/admin.h" + +#include "jdlib/constptr.h" + +#include + +#include + +namespace DBIMG +{ + class Img; +} + + +namespace IMAGE +{ + class ImageAreaBase; + + // + // ビューのベースクラス + // + class ImageViewBase : public SKELETON::View + { + JDLIB::ConstPtr< DBIMG::Img > m_img; + + // Gtk::manage で作っているのでdeleteしなくても良い + JDLIB::ConstPtr< ImageAreaBase > m_imagearea; + + bool m_loading; + Gtk::EventBox m_event; + bool m_dblclick; + + bool m_enable_menuslot; + + protected: + + JDLIB::ConstPtr< DBIMG::Img >& get_img(){ return m_img;} + + JDLIB::ConstPtr< ImageAreaBase >& get_imagearea(){ return m_imagearea; } + void set_imagearea( ImageAreaBase* imagearea ); + void remove_imagearea(); + + const bool loading() const{ return m_loading; } + void set_loading( bool loading ){ m_loading = loading; } + Gtk::EventBox& get_event(){ return m_event; } + + public: + + ImageViewBase( const std::string& url, const std::string& arg1 = std::string(), const std::string& arg2 = std::string() ); + virtual ~ImageViewBase(); + + // コマンド + virtual bool set_command( const std::string& command, const std::string& arg = std::string() ); + + // SKELETON::View の関数のオーバロード + virtual void reload(); + virtual void stop(); + virtual void redraw_view(); + virtual void close_view(); + virtual void delete_view(); + virtual void operate_view( const int& control ); + + protected: + + void setup_common(); + void set_image_to_buffer(); + + virtual void show_popupmenu(){} // popupメニューは派生ビューで指定する + + virtual bool slot_button_press_imagearea( GdkEventButton* event ); + virtual bool slot_motion_notify_imagearea( GdkEventMotion* event ); + bool slot_button_release_imagearea( GdkEventButton* event ); + + private: + + virtual void show_status(){} + virtual void add_image(){} + virtual void switch_icon(){} + + void zoom_in_out( bool zoomin ); + + bool slot_key_press_imagearea( GdkEventKey* event ); + void slot_reload_force(); + void slot_cancel_mosaic(); + void slot_fit_win(); + void slot_zoom_in(); + void slot_zoom_out(); + void slot_resize_image( int size ); + void slot_open_browser(); + void slot_open_ref(); + void slot_copy_url(); + void slot_save(); + void slot_save_all(); + void slot_favorite(); + void slot_toggle_protectimage(); + void slot_close_other_views(); + void slot_close_all_views(); + void slot_preference(); + }; + +} + +#endif + diff --git a/src/image/imageviewicon.cpp b/src/image/imageviewicon.cpp new file mode 100644 index 000000000..a0c892fbd --- /dev/null +++ b/src/image/imageviewicon.cpp @@ -0,0 +1,222 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "imageadmin.h" +#include "imageviewicon.h" +#include "imageareaicon.h" + +#include "dbimg/img.h" + +#include "global.h" +#include "dndmanager.h" +#include "sharedbuffer.h" +#include "cache.h" +#include "command.h" + +// 枠を描く +#define DRAW_FRAME( color ) m_event_frame->modify_bg( Gtk::STATE_NORMAL, color ); + +using namespace IMAGE; + + +ImageViewIcon::ImageViewIcon( const std::string& url ) + : ImageViewBase( url ) +{ +#ifdef _DEBUG + std::cout << "ImageViewIcon::ImageViewIcon : " << get_url() << std::endl; +#endif + + //枠を描くために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( Gdk::Color( "white" ) ); + + setup_common(); + + // D&D可能にする + std::list< Gtk::TargetEntry > targets; + targets.push_back( Gtk::TargetEntry( "text/plain", Gtk::TARGET_SAME_APP, 0 ) ); + get_event().drag_source_set( targets, Gdk::BUTTON1_MASK ); + get_event().drag_dest_set( targets ); + + get_event().signal_drag_begin().connect( sigc::mem_fun( *this, &ImageViewIcon::slot_drag_begin ) ); + get_event().signal_drag_motion().connect( sigc::mem_fun( *this, &ImageViewIcon::slot_drag_motion ) ); + get_event().signal_drag_drop().connect( sigc::mem_fun( *this, &ImageViewIcon::slot_drag_drop ) ); + 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 + + // スレッドを止めるために明示的にクリアする + get_imagearea().clear(); +} + + + +// +// クロック入力 +// +void ImageViewIcon::clock_in() +{ + // ロード終了 + if( get_imagearea() && loading() && ! get_img()->is_loading() ){ + + set_loading( false ); + + // 画像表示 + show_view(); + get_imagearea()->show_image(); + } +} + + + +// +// フォーカスイン +// +void ImageViewIcon::focus_view() +{ + DRAW_FRAME( Gdk::Color( "red" ) ); + get_event().grab_focus(); +} + + +// +// フォーカスアウト +// +void ImageViewIcon::focus_out() +{ + SKELETON::View::focus_out(); + DRAW_FRAME( Gdk::Color( "white" ) ); +} + + + +// +// 表示 +// +void ImageViewIcon::show_view() +{ + if( loading() ) return; + +#ifdef _DEBUG + std::cout << "ImageViewIcon::show_view url = " << get_url() << std::endl; +#endif + + // 画像が既に表示しているなら再描画 + if( get_imagearea() ){ + + get_imagearea()->show_image(); + return; + } + + // 読み込み中 + if( get_img()->is_loading() ) set_loading( true ); + + // 画像貼り付け + set_imagearea( Gtk::manage( new ImageAreaIcon( get_url() ) ) ); + show_all_children(); +} + + + +// +// アイコンが切り替わった +// +void ImageViewIcon::switch_icon() +{ + DRAW_FRAME( Gdk::Color( "red" ) ); +} + + + + +// +// ポップアップメニュー +// +void ImageViewIcon::show_popupmenu() +{ + Gtk::Menu* popupmenu = dynamic_cast< Gtk::Menu* >( ui_manager()->get_widget( "/popup_menu_icon" ) ); + if( popupmenu ) popupmenu->popup( 0, gtk_get_current_event_time() ); +} + + + + +// +// 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( get_url() ); + set_image_to_buffer(); +} + + + +// +// D&D中 +// +bool ImageViewIcon::slot_drag_motion( const Glib::RefPtr& context, int x, int y, guint time ) +{ +#ifdef _DEBUG + std::cout << "ImageViewIcon::slot_drag_motion url = " << get_url() << std::endl; +#endif + + return true; +} + + + +// +// ドロップされた +bool ImageViewIcon::slot_drag_drop( const Glib::RefPtr& context, int x, int y, guint time ) +{ +#ifdef _DEBUG + std::cout << "ImageViewIcon::slot_on_drag_drop url = " << get_url() << std::endl; +#endif + + // 他のアイコンからドロップされた場合は入れ換え + std::list< CORE::DATA_INFO > infolist = CORE::SBUF_infolist(); + std::list< CORE::DATA_INFO >::iterator it = infolist.begin(); + CORE::DATA_INFO& info = ( *it ); + if( info.type == TYPE_IMAGE ){ + +#ifdef _DEBUG + std::cout << "url_from = " << info.url << std::endl; +#endif + IMAGE::get_admin()->set_command( "reorder", get_url(), info.url, get_url() ); + } + + return true; +} + + + +// +// 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(); +} + + + diff --git a/src/image/imageviewicon.h b/src/image/imageviewicon.h new file mode 100644 index 000000000..fea00c157 --- /dev/null +++ b/src/image/imageviewicon.h @@ -0,0 +1,42 @@ +// ライセンス: 最新のGPL + +// +// 画像アイコンクラス +// + +#ifndef _IMAGEVIEWICON_H +#define _IMAGEVIEWICON_H + +#include "imageviewbase.h" + +namespace IMAGE +{ + class ImageViewIcon : public ImageViewBase + { + Gtk::EventBox* m_event_frame; + + public: + ImageViewIcon( const std::string& url ); + virtual ~ImageViewIcon(); + + virtual void clock_in(); + virtual void focus_view(); + virtual void focus_out(); + virtual void show_view(); + + protected: + virtual void show_popupmenu(); + + private: + + virtual void switch_icon(); + + void slot_drag_begin( const Glib::RefPtr& context ); + bool slot_drag_motion( const Glib::RefPtr& context, int x, int y, guint time ); + bool slot_drag_drop( const Glib::RefPtr& context, int x, int y, guint time ); + void slot_drag_end( const Glib::RefPtr< Gdk::DragContext >& context ); + }; +} + +#endif + diff --git a/src/image/imageviewpopup.cpp b/src/image/imageviewpopup.cpp new file mode 100644 index 000000000..d779f0370 --- /dev/null +++ b/src/image/imageviewpopup.cpp @@ -0,0 +1,163 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "imageviewpopup.h" +#include "imageareapopup.h" + +#include "dbimg/img.h" + +#include "config/globalconf.h" + +// 枠を描く +#define DRAW_FRAME( color ) m_event_frame->modify_bg( Gtk::STATE_NORMAL, color ); + +using namespace IMAGE; + +ImageViewPopup::ImageViewPopup( const std::string& url ) + : ImageViewBase( url ) + , m_label( NULL ) + , m_length_prev( 0 ) +{ + + //枠を描くために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 ); + + // 色の設定 + Gdk::Color color_bg; + const int *rgb = CONFIG::get_color_back(); + color_bg.set_rgb( rgb[ 0 ], rgb[ 1 ], rgb[ 2 ] ); + get_event().modify_bg( Gtk::STATE_NORMAL, color_bg ); + DRAW_FRAME( Gdk::Color( "black" ) ); + + setup_common(); +} + + + + +// +// クロック入力 +// +void ImageViewPopup::clock_in() +{ + // ロード中 + if( loading() ){ + + // 読み込みサイズの表示更新 + if( get_img()->is_loading() ) update_label(); + + // ロード完了 + else { + + set_loading( false ); + + // 画像表示 + show_view(); + + // リサイズ依頼 + sig_resize_popup().emit(); + } + } +} + + + +// +// ラベルを貼る +// +void ImageViewPopup::set_label( const std::string& status ) +{ + if( !m_label ){ + m_label = Gtk::manage( new Gtk::Label() ); + assert( m_label ); + get_event().add( *m_label ); + m_label->set_text( status ); + m_label->show(); + } +} + + + +// +// ラベル削除 +// +void ImageViewPopup::remove_label() +{ + if( m_label ){ + get_event().remove(); + delete m_label; + m_label = NULL; + } +} + + + +// +// 表示 +// +void ImageViewPopup::show_view() +{ + if( loading() ) return; + if( get_imagearea() ) return; // 画像を既に表示している + +#ifdef _DEBUG + std::cout << "ImageViewPopup::show_view url = " << get_url() << std::endl; +#endif + + // 読み込み中 + if( get_img()->is_loading() ){ + set_loading( true ); + m_length_prev = 0; + } + else set_loading( false ); + + // 読み込み中でなくてキャッシュがあったら画像貼り付け + if( ! loading() && get_img()->is_cached() ){ + + ImageAreaBase* imagearea = new ImageAreaPopup( get_url() ); + imagearea->show_image(); + + if( imagearea->get_errmsg().empty() ){ + remove_label(); + set_imagearea( imagearea ); + } + + // 画像よみこみエラー + else set_label( imagearea->get_errmsg() ); + } + + // 画像を貼ってないならラベルを貼る + else if( ! get_imagearea() ){ + + set_label( "please wait" ); + + // エラー表示 + if( ! loading() && ! get_img()->is_cached() ) m_label->set_text( get_img()->get_str_code( ) ); + } + + 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(); + + char tmpstr[ 256 ]; + snprintf( tmpstr, 256, "%d k / %d k", m_length_prev/1024, get_img()->total_length()/1024 ); + m_label->set_text( tmpstr ); + } +} + diff --git a/src/image/imageviewpopup.h b/src/image/imageviewpopup.h new file mode 100644 index 000000000..98e7aaa0b --- /dev/null +++ b/src/image/imageviewpopup.h @@ -0,0 +1,37 @@ +// ライセンス: 最新のGPL + +// +// 画像ポップアップクラス +// + +#ifndef _IMAGEVIEWPOPUP_H +#define _IMAGEVIEWPOPUP_H + +#include "imageviewbase.h" + +namespace IMAGE +{ + class ImageViewPopup : public ImageViewBase + { + Gtk::EventBox* m_event_frame; + Gtk::Label* m_label; + unsigned int m_length_prev; + + public: + + ImageViewPopup( const std::string& url ); + + virtual void clock_in(); + virtual void show_view(); + + private: + + void update_label(); + void set_label( const std::string& status ); + void remove_label(); + }; + +} + +#endif + diff --git a/src/image/preference.cpp b/src/image/preference.cpp new file mode 100644 index 000000000..9b120cb93 --- /dev/null +++ b/src/image/preference.cpp @@ -0,0 +1,40 @@ +// ライセンス: 最新のGPL + +#include "preference.h" + +#include "dbtree/interface.h" + +#include "dbimg/imginterface.h" + +#include "cache.h" + +using namespace IMAGE; + +Preferences::Preferences( const std::string& url ) + : SKELETON::PrefDiag( url, false ) + ,m_label_url( "URL : ", get_url() ) + ,m_label_cache( "ローカルキャッシュパス : ", CACHE::path_img( get_url() ) ) + ,m_label_ref( "参照元スレ : " ) + ,m_label_url_ref( "参照元スレのURL : " ) +{ + // 一般 + int num_from, num_to; + std::string refurl = DBIMG::refurl( get_url() ); + std::string daturl = DBTREE::url_dat( refurl, num_from, num_to ); + 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_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_label_url_ref, Gtk::PACK_SHRINK ); + + set_title( "プロパティ" ); + get_vbox()->pack_start( m_vbox_info ); + resize( 600, 400 ); + show_all_children(); +} diff --git a/src/image/preference.h b/src/image/preference.h new file mode 100644 index 000000000..d811d3f05 --- /dev/null +++ b/src/image/preference.h @@ -0,0 +1,28 @@ +// ライセンス: 最新のGPL + +#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; + SKELETON::LabelEntry m_label_url_ref; + + public: + Preferences( const std::string& url ); + }; + +} + +#endif diff --git a/src/jddebug.h b/src/jddebug.h new file mode 100644 index 000000000..25eaa00e5 --- /dev/null +++ b/src/jddebug.h @@ -0,0 +1,14 @@ +// ライセンス: 最新のGPL + +#ifndef _JDDEBUG_H +#define _JDDEBUG_H + +#ifndef _DEBUG +#define NDEBUG +#else +#include +#endif + +#include + +#endif diff --git a/src/jdlib/Makefile.am b/src/jdlib/Makefile.am new file mode 100644 index 000000000..8eddee08b --- /dev/null +++ b/src/jdlib/Makefile.am @@ -0,0 +1,27 @@ +noinst_LIBRARIES = libjdlib.a + +libjdlib_a_SOURCES = \ + miscutil.cpp \ + miscmsg.cpp \ + misctime.cpp \ + heap.cpp \ + jdiconv.cpp \ + loader.cpp \ + confloader.cpp \ + jdregex.cpp + +noinst_HEADERS = \ + miscutil.h \ + miscmsg.h \ + misctime.h \ + constptr.h \ + refptr_lock.h \ + heap.h \ + jdiconv.h \ + loader.h \ + loaderdata.h \ + confloader.h \ + jdregex.h + +AM_CXXFLAGS = @GTKMM_CFLAGS@ +INCLUDES = -I$(top_srcdir)/src diff --git a/src/jdlib/confloader.cpp b/src/jdlib/confloader.cpp new file mode 100644 index 000000000..16abb1c6a --- /dev/null +++ b/src/jdlib/confloader.cpp @@ -0,0 +1,149 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "confloader.h" +#include "miscutil.h" +#include "cache.h" + +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() ){ + + std::list< std::string > lines = MISC::get_lines( str_conf ); + if( lines.size() == 0 ) return; + + std::list < std::string >::iterator it = lines.begin(); + for( ; it != lines.end(); ++it ){ + + std::string line = MISC::remove_space( ( *it ) ); + + unsigned int i = line.find( "=" ); + if( i != std::string::npos ){ + + ConfData data; + data.name = MISC::remove_space( line.substr( 0, i ) ); + data.value = MISC::remove_space( line.substr( i +1 ) ); + m_data.push_back( data ); +#ifdef _DEBUG + std::cout << data.name << " = " << data.value << std::endl; +#endif + } + } + } +} + + + +const bool ConfLoader::empty() +{ + return !( m_data.size() ); +} + + + +// 保存 +void ConfLoader::save() +{ + if( m_file.empty() ) return; + + std::string str_conf; + + std::list < ConfData >::iterator it = m_data.begin(); + for( ; it != m_data.end(); ++it ) str_conf += (*it).name + " = " + (*it).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; + + std::list < ConfData >::iterator it = m_data.begin(); + for( ; it != m_data.end(); ++it ){ + if( (*it).name == name ){ + (*it).value = value; + return; + } + } + + // 追加 + ConfData data; + data.name = name; + data.value = value; + m_data.push_back( data ); +} + +// 値を変更 (int型) +void ConfLoader::update( const std::string& name, const int value ) +{ + const int buflng = 256; + char str_value[ buflng ]; + snprintf( str_value, buflng, "%d", value ); + update( name, std::string( str_value ) ); +} + + +// +// string 型 +// +// dflt はデフォルト値 +// +std::string ConfLoader::get_option( const std::string& name, std::string dflt ) +{ + if( name.empty() ) return std::string(); + + std::list < ConfData >::iterator it = m_data.begin(); + for( ; it != m_data.end(); ++it ){ + if( (*it).name == name ) return (*it).value; + } + + return dflt; +} + + + +// +// int 型 +// +int ConfLoader::get_option( const std::string& name, int dflt ) +{ + std::string val = get_option( name, std::string() ); + + if( val.empty() ) return dflt; + + val = MISC::toupper_str( val ); + + if( val == "TRUE" || val == "T" || val == "true" || val == "t" ) return 1; + if( val == "FALSE" || val == "F" || val == "false"|| val == "f" ) return 0; + + return atoi( val.c_str() ); +} + diff --git a/src/jdlib/confloader.h b/src/jdlib/confloader.h new file mode 100644 index 000000000..06dc370ea --- /dev/null +++ b/src/jdlib/confloader.h @@ -0,0 +1,51 @@ +// ライセンス: 最新のGPL + +// +// コンフィグファイルのローダ +// + +#ifndef _CONFLOADER_H +#define _CONFLOADER_H + +#include +#include + + +namespace JDLIB +{ + struct ConfData + { + std::string name; + std::string value; + }; + + + class ConfLoader + { + std::string m_file; + std::list< ConfData > m_data; + + public: + + // file : 設定ファイル + // str_conf : 設定文字列 + // もしstr_confがemptyの時はfileから読み込む + ConfLoader( const std::string& file, std::string str_conf ); + + const bool empty(); + + // 保存 + void save(); + + // 値を変更 + // name が無い場合は綱目を追加 + void update( const std::string& name, const std::string& value ); + void update( const std::string& name, const int value ); + + // 値取得 + std::string get_option( const std::string& name, std::string dflt ); + int get_option( const std::string& name, int dflt ); + }; +} + +#endif diff --git a/src/jdlib/constptr.h b/src/jdlib/constptr.h new file mode 100644 index 000000000..51e505dc2 --- /dev/null +++ b/src/jdlib/constptr.h @@ -0,0 +1,46 @@ +// ライセンス: 最新のGPL + +// +// constなスマートポインタ +// + +#ifndef _CONSTPTR_H +#define _CONSTPTR_H + +namespace JDLIB +{ + template < typename T > + class ConstPtr + { + T *m_p; + + public: + + T* operator -> (){ return m_p; } + bool operator == ( const T *p ) const { return( m_p == p ); } + bool operator != ( const T *p ) const { return( m_p != p ); } + bool operator ! () const { return ( m_p == NULL ); } + T& operator * () const { return *m_p; } + operator bool () const { return ( m_p != NULL ); } + T& operator [] ( const int i ){ return m_p[ i ]; } + + ConstPtr< T >& operator = ( const ConstPtr< T >& a ){ m_p = a.m_p; return *this; } + ConstPtr< T >& operator = ( ConstPtr< T >& a ){ m_p = a.m_p; return *this; } + ConstPtr< T >& operator = ( const T *p ){ m_p = p; return *this; } + ConstPtr< T >& operator = ( T *p ){ m_p = p; return *this; } + + void reset() { m_p = NULL; } + + // clear は deleteも実行 + void clear(){ + if( m_p ) delete m_p; + reset(); + } + + ConstPtr() : m_p (0){} + ConstPtr( T *p ) : m_p (p){} + ConstPtr( const T *p ) : m_p (p){} + }; +} + +#endif diff --git a/src/jdlib/heap.cpp b/src/jdlib/heap.cpp new file mode 100644 index 000000000..6fd72c3a1 --- /dev/null +++ b/src/jdlib/heap.cpp @@ -0,0 +1,56 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "heap.h" + +using namespace JDLIB; + +HEAP::HEAP( long blocksize ) + : m_max( blocksize ), + m_used( 0 ), + m_total_size( 0 ) +{} + + +HEAP::~HEAP() +{ +#ifdef _DEBUG + std::cout << "HEAP::~HEAP : size " << m_size / 1024 << "k\n"; +#endif + + clear(); +} + + +void HEAP::clear() +{ + m_total_size = 0; + m_used = 0; + + std::list< unsigned char* >::iterator it; + for( it = m_heap_list.begin(); it != m_heap_list.end(); ++it ){ + free( (*it) ); + } + m_heap_list.clear(); +} + + + +unsigned char* HEAP::heap_alloc( long n ) +{ + assert( n > 0 && n <= m_max ); + + if( m_used == 0 || m_used + n > m_max ){ + m_heap_list.push_back( ( unsigned char* )malloc( m_max ) ); + memset( m_heap_list.back(), 0, m_max ); + m_used = 0; + } + + unsigned char* heap = m_heap_list.back() + m_used; + m_used += n + 4; + m_total_size += n + 4; + + return heap; +} diff --git a/src/jdlib/heap.h b/src/jdlib/heap.h new file mode 100644 index 000000000..435d409b8 --- /dev/null +++ b/src/jdlib/heap.h @@ -0,0 +1,30 @@ +// ライセンス: 最新のGPL + +// ヒープクラス + +#ifndef _HEAP_H +#define _HEAP_H + +#include +#include + +namespace JDLIB +{ + class HEAP + { + std::list< unsigned char* >m_heap_list; + long m_max; // ブロックサイズ + long m_used; // ブロック内の使用量 + long m_total_size; // トータルサイズ + + public: + HEAP( long blocksize ); + ~HEAP(); + + void clear(); + + unsigned char* heap_alloc( long n ); + }; +} + +#endif diff --git a/src/jdlib/jdiconv.cpp b/src/jdlib/jdiconv.cpp new file mode 100644 index 000000000..35dc306f0 --- /dev/null +++ b/src/jdlib/jdiconv.cpp @@ -0,0 +1,142 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "jdiconv.h" +#include "miscmsg.h" + +#include + +using namespace JDLIB; + +Iconv::Iconv( const std::string& coding_from, const std::string& coding_to ) + : m_buf_in( 0 ), m_buf_out( 0 ), 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 = iconv_open( coding_to.c_str(), m_coding_from.c_str() ); + if( m_cd == ( iconv_t ) -1 ){ + MISC::ERRMSG( "can't open iconv coding = " + m_coding_from + " to " + coding_to ); + } + m_byte_left_in = 0; +} + +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 != ( iconv_t ) -1 ) 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 == ( iconv_t ) -1 ) return NULL; + + 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 + + int ret = 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 + assert( byte_left_out >= 0 ); + + // エラー + if( ret == -1 ){ + + if( errno == EILSEQ ){ + + char str_tmp[256]; + unsigned char code0 = *m_buf_in_tmp; + unsigned char code1 = *(m_buf_in_tmp+1); + unsigned char code2 = *(m_buf_in_tmp+2); + +#ifdef NOUSE_MS932 + if( m_coding_from == "CP932" ) +#else + if( m_coding_from == "MS932" ) +#endif + { + + // 空白(0xa0) + if( code0 == 0xa0 ){ + *m_buf_in_tmp = 0x20; + continue; + } + + // マッピング失敗 + // □(0x81a0)を表示する + if( ( code0 >= 0x81 && code0 <=0x9F ) + || ( code0 >= 0xe0 && code0 <=0xef ) ){ + + *m_buf_in_tmp = 0x81; + *(m_buf_in_tmp+1) = 0xa0; + + snprintf( str_tmp, 256, "iconv 0x%x%x -> □ (0x81a0) ", code0, code1 ); + MISC::MSG( str_tmp ); + continue; + } + } + + //その他、1文字を空白にして続行 + snprintf( str_tmp, 256, "iconv EILSEQ left = %d code = %x %x %x", m_byte_left_in, code0, code1, code2 ); + MISC::ERRMSG( str_tmp ); + *m_buf_in_tmp = 0x20; + } + + else if( errno == EINVAL ){ + MISC::ERRMSG( "iconv EINVAL\n" ); + break; + } + + else if( errno == E2BIG ){ + MISC::ERRMSG( "iconv E2BIG\n" ); + 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; +} diff --git a/src/jdlib/jdiconv.h b/src/jdlib/jdiconv.h new file mode 100644 index 000000000..df41d7dff --- /dev/null +++ b/src/jdlib/jdiconv.h @@ -0,0 +1,37 @@ +// ライセンス: 最新のGPL + +#ifndef _JDICONV_H +#define _JDICONV_H + +#include +#include + +// iconv の内部で確保するバッファサイズ(バイト) +// BUF_SIZE_ICONV_IN を超える入力は扱えないので注意 +#define BUF_SIZE_ICONV_IN ( 1024 * 1024 ) +#define BUF_SIZE_ICONV_OUT ( BUF_SIZE_ICONV_IN /2 * 3 ) + +namespace JDLIB +{ + class Iconv + { + iconv_t m_cd; + + 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_from, const std::string& coding_to = "UTF-8" ); + ~Iconv(); + + const char* convert( char* str_in, int size_in, int& size_out ); + }; +} + +#endif diff --git a/src/jdlib/jdregex.cpp b/src/jdlib/jdregex.cpp new file mode 100644 index 000000000..f0902c072 --- /dev/null +++ b/src/jdlib/jdregex.cpp @@ -0,0 +1,83 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "jdregex.h" + +#include + +#define REGEX_MAX_NMATCH 32 + +using namespace JDLIB; + +Regex::Regex() +{ + m_results.clear(); + m_pos.clear(); +} + + +Regex::~Regex() +{} + + + +// icase : true なら大小無視 +// newline : true なら . に改行をマッチさせない +bool Regex::exec( const std::string reg, const std::string& target, unsigned int offset, bool icase, bool newline ) +{ + regex_t preg; + regmatch_t pmatch[ REGEX_MAX_NMATCH ]; + + if( reg.empty() ) return false; + if( target.empty() ) return false; + if( target.length() <= offset ) return false; + + m_pos.clear(); + m_results.clear(); + + int cflags = REG_EXTENDED; + if( newline ) cflags |= REG_NEWLINE; + if( icase ) cflags |= REG_ICASE; + + if( regcomp( &preg, reg.c_str(), cflags ) != 0 ){ + regfree( &preg ); + return false; + } + + if( regexec( &preg, target.c_str() + offset, REGEX_MAX_NMATCH, pmatch, 0 ) != 0 ){ + regfree( &preg ); + return false; + } + + for( int i = 0; i < REGEX_MAX_NMATCH; ++i ){ + + int so = offset + pmatch[ i ].rm_so; + int eo = offset + pmatch[ i ].rm_eo; + + m_pos.push_back( so ); + + if( so >= 0 && eo >= 0 ) m_results.push_back( target.substr( so, eo - so ) ); + else m_results.push_back( std::string() ); + } + + regfree( &preg ); + return true; +} + + +const std::string Regex::str( unsigned int num ) +{ + if( m_results.size() > num ) return m_results[ num ]; + + return std::string(); +} + + +const int Regex::pos( unsigned int num ) +{ + if( m_results.size() > num ) return m_pos[ num ]; + + return -1; +} diff --git a/src/jdlib/jdregex.h b/src/jdlib/jdregex.h new file mode 100644 index 000000000..f721b00d8 --- /dev/null +++ b/src/jdlib/jdregex.h @@ -0,0 +1,30 @@ +// ライセンス: 最新のGPL + +#ifndef _JDREGEX_H +#define _JDREGEX_H + +#include +#include + +namespace JDLIB +{ + class Regex + { + std::vector< int > m_pos; + std::vector< std::string > m_results; + + public: + + Regex(); + ~Regex(); + + // icase : true なら大小無視 + // newline : true なら . に改行をマッチさせない + bool exec( const std::string reg, const std::string& target, unsigned int offset = 0 + , bool icase = false, bool newline = true ); + const int pos( unsigned int num ); + const std::string str( unsigned int num ); + }; +} + +#endif diff --git a/src/jdlib/loader.cpp b/src/jdlib/loader.cpp new file mode 100644 index 000000000..18812ebfa --- /dev/null +++ b/src/jdlib/loader.cpp @@ -0,0 +1,1323 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +//#define _DEBUG_CHUNKED +#include "jddebug.h" + +#include "loader.h" +#include "miscmsg.h" + +#include "skeleton/loadable.h" + +#include "httpcode.h" + +#include +#include + +#include +#include +#include +#include +#include + +#ifndef NOUSE_SSL +#include +#endif + +// 最大スレッド数 +#define MAX_LOADER 6 + +// 読み込みバッファの最小値 (byte) +#define LNG_BUF_MIN ( 16 * 1024 ) + +// タイムアウトの最小値 (秒) +#define TIMEOUT_MIN 10 + + +typedef void* ( *FUNC )( void * ); + + +// +// トークン処理 +// +// MAX_LOADER 個を越えるローダは作成できない +// + +int token_loader = 0; + +int JDLIB::token() +{ +#ifdef _DEBUG + std::cout << "token : token = " << token_loader << std::endl; +#endif + + return token_loader; +} + +// トークン取得 +void JDLIB::get_token() +{ +#ifdef _DEBUG + std::cout << "get_token : token = " << token_loader << std::endl; +#endif + ++token_loader; +} + +// トークン返す +void JDLIB::return_token() +{ + --token_loader; + assert( token_loader >= 0 ); + +#ifdef _DEBUG + std::cout << "return_token : token = " << token_loader << std::endl; +#endif +} + + +// +// loader作成関数 +// +// 基本的にこれを使ってloaderを作成すること +// loaderがたくさん動いてるときはNULLを返す +// +JDLIB::Loader* JDLIB::create_loader() +{ + if( JDLIB::token() >= MAX_LOADER ){ +#ifdef _DEBUG + std::cout << "JDLIB::create_loader : could not create loader\n"; +#endif + return NULL; + } + + return new JDLIB::Loader(); +} + + +// +// mainの最後でローダが動いていないかチェックする関数 +// +void JDLIB::check_loader_alive() +{ +#ifdef _DEBUG + std::cout << "JDLIB::check_loader_alive loader = " << token_loader << std::endl; +#endif + + if( token_loader ){ + MISC::ERRMSG( "loaders are still moving." ); + assert( false ); + } +} + + +using namespace JDLIB; + + + +/////////////////////////////////////////////////// + + + +Loader::Loader() + : m_addrinfo( 0 ), + m_loading( 0 ), + m_thread( 0 ), + m_buf( 0 ), + m_buf_zlib_in ( 0 ), + m_buf_zlib_out ( 0 ), + m_use_zlib ( 0 ) +{ +#ifdef _DEBUG + std::cout << "Loader::Loader : loader was created.\n"; +#endif + + clear(); +} + + +Loader::~Loader() +{ + clear(); + +#ifdef _DEBUG + std::cout << "Loader::~Loader : url = " << m_data.url << std::endl; +#endif + + assert( ! m_loading ); +} + + +void Loader::clear() +{ + stop(); + wait(); + + m_loadable = NULL; + + m_use_chunk = false; + + if( m_buf ) free( m_buf ); + m_buf = NULL; + + if( m_buf_zlib_in ) free( m_buf_zlib_in ); + m_buf_zlib_in = NULL; + + if( m_buf_zlib_out ) free( m_buf_zlib_out ); + m_buf_zlib_out = NULL; + + if( m_use_zlib ) inflateEnd( &m_zstream ); + m_use_zlib = false; +} + + +void Loader::wait() +{ + if( m_thread ) pthread_join( m_thread, NULL ); + m_thread = 0; +} + + +// +// ダウンロード開始 +// +// data_in でセットする必要がある項目 ( url は必須 ) +// +// url +// head ( true なら HEAD 送信 ) +// port ( == 0 ならプロトコルを見て自動認識 ) +// str_post( != empty なら POST する。UTF-8であること ) +// modified +// byte_readfrom ( != 0 ならその位置からレジューム) +// agent +// referer +// host_proxy ( != empty ならproxy使用 ) +// port_proxy ( == 0 なら 8080 ) +// size_buf ( バッファサイズ, k 単位で指定。 == 0 ならデフォルトサイズ(LNG_BUF_MIN)使用) +// timeout ( タイムアウト秒。==0 ならデフォルト( TIMEOUT )使用 ) +// +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_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; + unsigned long i = m_data.url.find( "://", 0 ); // "http://" とつけるのは呼び出し側の責任で + if( i == std::string::npos ){ + + m_data.code = HTTP_ERR; + m_data.str_code = "could nod get protocol : " + m_data.url; + MISC::ERRMSG( m_data.str_code ); + return false; + } + i += 3; + m_data.protocol = data_in.url.substr( 0, i ); + + unsigned long 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 ); + i = i2; 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 = 80; + +#ifndef NOUSE_SSL + // https + else if( m_data.protocol.find( "https://" ) != std::string::npos ){ + m_data.use_ssl = true; + m_data.async = false; + m_data.port = 443;; + } +#endif + + // その他 + else{ + + m_data.code = HTTP_ERR; + m_data.str_code = "unknown protocol : " + m_data.url; + MISC::ERRMSG( m_data.str_code ); + return false; + } + } + +#ifndef NOUSE_SSL + // 明示的にssl使用指定 + if( data_in.use_ssl ){ + m_data.use_ssl = true; + m_data.async = false; + m_data.port = 443; + } +#else + m_data.use_ssl = false; +#endif + + // その他 + 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.agent = data_in.agent; + m_data.referer = data_in.referer; + m_data.cookie = data_in.cookie; + m_data.host_proxy = data_in.host_proxy; + m_data.port_proxy = data_in.port_proxy; + if( m_data.port_proxy == 0 ) m_data.port_proxy = 8080; + m_data.timeout = MAX( TIMEOUT_MIN, data_in.timeout ); + m_data.ex_field = data_in.ex_field; + +#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 << "agent: " << m_data.agent << std::endl; + std::cout << "referer: " << m_data.referer << std::endl; + std::cout << "cookie: " << m_data.cookie << std::endl; + std::cout << "proxy: " << m_data.host_proxy << std::endl; + std::cout << "port of proxy: " << m_data.port_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 << "\n"; +#endif + + // スレッドを起動して run_main() 実行 + m_stop = false; + int status; + if( ( status = pthread_create( &m_thread, NULL, ( FUNC ) launcher, ( void * ) this ) )){ + + m_data.code = HTTP_ERR; + m_data.str_code = std::string( "pthread_create failed : " ) + strerror( status ); + MISC::ERRMSG( m_data.str_code ); + return false; + } + m_loading = true; + + return true; +} + + +// +// スレッドのランチャ (static) +// +void* Loader::launcher( void* dat ) +{ + Loader* tt = ( Loader * ) dat; + tt->run_main(); + return 0; +} + + +// +// 実際の処理部 +// +void Loader::run_main() +{ + // エラーメッセージ + std::string errmsg; + + // トークン取得 + get_token(); + + int soc = -1; // ソケットID + bool use_proxy = ( ! m_data.host_proxy.empty() ); + +#ifndef NOUSE_SSL + // ssl用 + SSL_CTX *ctx = NULL; + SSL* ssl = NULL; +#else + bool ssl = false; +#endif + + // 送信メッセージ作成 + 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 ); + 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.url; + goto EXIT_LOADING; + } + + // ソケット作成 + soc = socket( m_addrinfo ->ai_family, m_addrinfo ->ai_socktype, m_addrinfo ->ai_protocol ); + if ( soc < 0 ){ + 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; + 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 + } + +#ifndef NOUSE_SSL + // ssl 初期化 + if( m_data.use_ssl ){ + + SSL_library_init(); + ctx = SSL_CTX_new( SSLv23_client_method() ); + if( !ctx ){ + m_data.code = HTTP_ERR; + errmsg = "SSL_CTX_new failed : " + m_data.url; + goto EXIT_LOADING; + } + + ssl = SSL_new( ctx ); + if( !ssl ){ + m_data.code = HTTP_ERR; + errmsg = "SSL_new failed : " + m_data.url; + goto EXIT_LOADING; + } + + if( SSL_set_fd( ssl, soc ) == 0 ){ + m_data.code = HTTP_ERR; + errmsg = "SSL_set_fd : " + m_data.url; + goto EXIT_LOADING; + } + + if( SSL_connect( ssl ) != 1 ){ + m_data.code = HTTP_ERR; + errmsg = "SSL_connect : " + m_data.url; + goto EXIT_LOADING; + } + +#ifdef _DEBUG + std::cout << "init ssl ok\n"; +#endif + } +#endif + + // 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 + ssize_t tmpsize = send( soc, msg_send.data(), send_size , MSG_NOSIGNAL ); + if( tmpsize == 0 + || ( tmpsize < 0 && !( errno == EWOULDBLOCK || errno == EINTR ) ) ){ + + m_data.code = HTTP_ERR; + errmsg = "send failed : " + m_data.url; + goto EXIT_LOADING; + } + + if( tmpsize > 0 ) send_size -= tmpsize; + } + + if( m_stop ) goto EXIT_LOADING; + +#ifdef _DEBUG + std::cout << "send ok\n"; +#endif + } + +#ifndef NOUSE_SSL + // SSL使用 + else{ + + if( SSL_write( ssl, msg_send.data(), strlen( msg_send.data() ) ) < 0 ){ + + m_data.code = HTTP_ERR; + errmsg = "send failed(SSL) : " + m_data.url; + goto EXIT_LOADING; + } + } +#endif + + // 受信用バッファを作ってメッセージ受信 + size_t mrg; + mrg = 64; // 一応オーバーフロー避けのおまじない + assert( m_buf == NULL ); + m_buf = ( char* )malloc( m_lng_buf + mrg ); + + bool receiving_header; + + // 受信開始 + 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 ){ + + // 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"; + goto EXIT_LOADING; + } + + } + +#ifndef NOUSE_SSL + // SSL + else{ + + tmpsize = SSL_read( ssl, m_buf + read_size, m_lng_buf - read_size - mrg ); + if( tmpsize < 0 ){ + m_data.code = HTTP_ERR; + errmsg = "SSL_read() failed"; + goto EXIT_LOADING; + } + } +#endif + + if( tmpsize == 0 ) break; + if( tmpsize > 0 ) read_size += tmpsize; + } + + m_buf[ read_size + 1 ] = '\0'; + + // 停止指定 + if( m_stop ) break; + + // サーバ側がcloseした + if( read_size == 0 ) break; + + // ヘッダ取得 + if( receiving_header ){ + + if( ! receive_header( m_buf, read_size ) ){ + + m_data.code = HTTP_ERR; + errmsg = "invalid header : " + m_data.url; + goto EXIT_LOADING; + } + receiving_header = false; + if( ! read_size ) continue; + } + + 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 ) continue; + } + + // 圧縮されていない時はそのままコールバック呼び出し + 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; + } + + } while( !m_stop ); + + // 終了処理 +EXIT_LOADING: + +#ifndef NOUSE_SSL + // ssl クローズ + if( ssl ){ + SSL_shutdown( ssl ); + SSL_free( ssl ); + } + if( ctx ) SSL_CTX_free( ctx ); +#endif + + if( soc >= 0 ){ + + // 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 >= 0 ) close( soc ); + + // addrinfo開放 + if( m_addrinfo ) freeaddrinfo( m_addrinfo ); + m_addrinfo = NULL; + + // トークン返す + return_token(); + + // Loadable::finish()をコールバックして終わり + if( m_loadable ) m_loadable->finish(); + +#ifdef _DEBUG + std::cout << "Loader::run_main : finish loading : " << m_data.url << std::endl;; + std::cout << "read size : " << m_data.length_current << std::endl;; + std::cout << "data size : " << m_data.size_data << std::endl;; + std::cout << "code : " << m_data.code << std::endl << std::endl; +#endif + + m_loading = false; +} + + + +// +// addrinfo 取得 +// +struct addrinfo* Loader::get_addrinfo( const std::string& hostname, int port ) +{ + if( port < 0 || port > 65535 ) return NULL; + if( hostname.empty() ) return NULL; + + int ret; + struct addrinfo hints, *res; + const int poststrlng = 256; + char port_str[ poststrlng ]; + memset( &hints, 0, sizeof( addrinfo ) ); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + snprintf( port_str, poststrlng, "%d", port ); + ret = getaddrinfo( hostname.c_str(), port_str, &hints, &res ); + if( ret ) { + MISC::ERRMSG( m_data.str_code ); + return NULL; + } + +#ifdef _DEBUG + std::cout << "host = " << hostname + << ", ip =" << inet_ntoa( ( ( sockaddr_in* )( res->ai_addr ) )->sin_addr ) << std::endl; +#endif + + return res; +} + + + +// +// 送信メッセージ作成 +// +std::string Loader::create_msg_send() +{ + 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.agent.empty() ) msg << "User-Agent: " << m_data.agent << "\r\n"; + if( ! m_data.referer.empty() ) msg << "Referer: " << m_data.referer << "\r\n"; + if( ! m_data.cookie.empty() ) msg << "Cookie: " << m_data.cookie << "\r\n"; + if( ! m_data.modified.empty() ) msg << "If-Modified-Since: " << m_data.modified << "\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"; + + // 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"; + } + msg << "\r\n"; + + return msg.str(); +} + + +// +// サーバから送られてきた生データからヘッダ取得 +// +// 処理を簡単にするためにヘッダはバッファ内にすべてあると仮定(バッファ超えたらエラー) +// +// 入力 +// buf : 生データ +// readsize: 生データサイズ +// +// 出力 +// buf : ヘッダが取り除かれたデータ +// readsize: 出力データサイズ +// +bool Loader::receive_header( char* buf, size_t& read_size ) +{ + 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 = m_data.str_header.find( "\n\n" ); + if( lng_header == std::string::npos ){ + MISC::ERRMSG( "could not find HTML header" ); +#ifdef _DEBUG + std::cout << "Loader::receive_header : read_size = " << read_size << std::endl; + std::cout << m_data.str_header << std::endl; +#endif + return false; + } + + lng_header += 2; + m_data.str_header.resize( lng_header ); + +#ifdef _DEBUG + std::cout << "\nheader : " << lng_header << " byte\n"; + std::cout << m_data.str_header << std::endl; +#endif + + if( ! analyze_header() ) return false; + + // 残りのデータを前に移動 + lng_header += 2; + read_size -= lng_header; + if( read_size ){ + + memmove( buf, buf+ lng_header, read_size ); + buf[ read_size ] = '\0'; + } + + return true; +} + + +// +// ヘッダ解析 +// +bool Loader::analyze_header() +{ + // コード + m_data.code = HTTP_INIT; + m_data.str_code.clear(); + 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; + } + size_t i = str_tmp.find( " " ); + if( i == std::string::npos ){ + MISC::ERRMSG( "could not find HTTP/1.1" ); + return false; + } + m_data.code = atoi( str_tmp.substr( 0, i ).c_str() ); + + // サイズ + 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 + // とりあえず METAデータは無視 + m_data.cookie = analyze_header_option( "Set-Cookie: " ); + + // Location + if( m_data.code == HTTP_REDIRECT ) 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; + std::cout << "cookie = " << m_data.cookie << 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 << "\n"; +#endif + + return true; +} + + +// +// analyze_header() から呼ばれるオプションの値を取り出す関数 +// +std::string Loader::analyze_header_option( char* option ) +{ + size_t i = 0, i2 = 0; + i = m_data.str_header.find( option, 0 ); + if( i != std::string::npos ){ + 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 + strlen( option ), i2 - ( i + strlen( option ) ) ); + } + + return std::string(); +} + + + +// +// 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, NULL, 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" と "\n" の間でサーバからの入力が分かれる時がある) + if( m_status_chunk == 2 ){ + + unsigned char c = buf[ pos_chunk ]; + if( c != '\r' && c != '\n' ){ + MISC::ERRMSG( "broken chunked data." ); + return false; + } + + // \r\nが来たらサイズ部に戻る + if( c == '\r' ) ++pos_chunk; else break; + if( buf[ pos_chunk ] == '\n' ) ++pos_chunk; else break; + +#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; + +#ifdef USE_OLDZLIB + m_check_gzheader = true; +#endif + + // 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; + +#ifdef USE_OLDZLIB +#ifdef _DEBUG + std::cout << "use old zlib\n"; +#endif + if ( inflateInit2( &m_zstream, -MAX_WBITS ) != Z_OK ) +#else +#ifdef _DEBUG + std::cout << "use zlib12\n"; +#endif + if ( inflateInit2( &m_zstream, 15 + 32 ) != Z_OK ) // デフォルトの15に+32する( windowBits = 47 )と自動でヘッダ認識 +#endif + { + + MISC::ERRMSG( "inflateInit2 failed." ); + return false; + } + + assert( m_buf_zlib_in == NULL ); + assert( m_buf_zlib_out == NULL ); + m_buf_zlib_in = ( Bytef* )malloc( m_lng_buf_zlib_in + 64 ); + m_buf_zlib_out = ( Bytef* )malloc( m_lng_buf_zlib_out + 64 ); + + return true; +} + + + +#ifdef USE_OLDZLIB +// +// gzip のヘッダチェック +// +// 戻り値 : ヘッダのサイズ(エラーなら-1) +// +// TODO: size がヘッダサイズよりも小さいと必ず失敗する +// +int Loader::check_gzheader( char* buf, size_t& size ) +{ +#ifdef _DEBUG + std::cout << "Loader::check_gzheader\n"; +#endif + + m_check_gzheader = false; + + unsigned int pos = 0; + + if( ( ( unsigned char* )buf )[ pos++ ] != 0x1f || ( ( unsigned char* )buf )[ pos++ ] != 0x8b ) return -1; + + // CM(1byte) + if( buf[ pos++ ] != 0x08 ) return false; + + // FLG(1byte) + unsigned char flag =( ( unsigned char* )buf )[ pos++ ]; +#ifdef _DEBUG + std::cout << "flag = " << std::hex << ( unsigned int )flag << std::dec << std::endl; +#endif + if( flag & 0xe0 ) return -1; + + // MTIME(4byte), XFL(1byte), OS(1byte)を飛ばす + pos += 6; + + // XLEN(2byte) + 拡張フィールド + if( flag & 4 ){ + unsigned short lngext; + memcpy( &lngext, buf + pos, sizeof( unsigned short ) ); +#ifdef _DEBUG + std::cout << "FEXTRA lng = " << lngext << std::endl; +#endif + pos += sizeof( unsigned short ) + lngext; + } + + // NAME + if( flag & 8 ){ + int i = 0; + char tmp_str[ 256 ]; + while( pos < size && i < 256 && buf[ pos ] != '\0' ) tmp_str[ i++ ] = buf[ pos++ ]; + tmp_str[ i ] = buf[ pos++ ]; + if( tmp_str[ i ] != '\0' ) return -1; +#ifdef _DEBUG + std::cout << "NAME = " << tmp_str << std::endl; +#endif + } + + // COMMENT + if( flag & 16 ){ + int i = 0; + char tmp_str[ 256 ]; + while( pos < size && i < 256 && buf[ pos ] != '\0' ) tmp_str[ i++ ] = buf[ pos++ ]; + tmp_str[ i ] = buf[ pos++ ]; + if( tmp_str[ i ] != '\0' ) return -1; +#ifdef _DEBUG + std::cout << "COMMENT = " << tmp_str << std::endl; +#endif + } + + // CRC16 + if(flag & 2 ){ + pos += 2; +#ifdef _DEBUG + std::cout << "CRC16\n"; +#endif + } + + if( pos >= size ) return -1; + +#ifdef _DEBUG + std::cout << "header size = " << pos << std::endl; +#endif + + return pos; +} +#endif + + + +// +// unzipしてコールバック呼び出し +// +bool Loader::unzip( char* buf, size_t& read_size ) +{ +#ifdef USE_OLDZLIB + // gzip のヘッダを飛ばす + if( m_check_gzheader ){ + int headsize = check_gzheader( buf, read_size ); + if( headsize < 0 ){ + + MISC::ERRMSG( "invalid gzip header : " + m_data.url ); + return false; + } + buf += headsize; + read_size -= headsize; + } +#endif + + // 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; + byte_out = 0; + + // 解凍 + 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 ); + +#ifdef USE_OLDZLIB + if( ret == Z_STREAM_END ){ + m_zstream.avail_in = 0; // CRC32(4byte)とISIZE(4byte)の分を引く + return true; + } +#endif + } + 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( int fd, 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 , NULL , NULL , &timeout ); + else ret = select( fd+1 , NULL, &fdset , NULL , &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 << " timeout = " << count << std::endl; +#endif + } + + return false; +} diff --git a/src/jdlib/loader.h b/src/jdlib/loader.h new file mode 100644 index 000000000..c47b3572e --- /dev/null +++ b/src/jdlib/loader.h @@ -0,0 +1,124 @@ +// ライセンス: 最新のGPL + +// +// ファイルローダ +// +// このまま使うよりも SKELETON::Loadable と組み合わせた方が楽 +// + +#ifndef _LOADER_H +#define _LOADER_H + +#include "loaderdata.h" + +#include +#include + +#include +#include + + +// zlibが1.2よりバージョンが低いか判定する +#ifndef ZLIB_VERNUM +#define ZLIB_VERNUM 0x1000 +#endif +#if ZLIB_VERNUM < 0x1200 +#define USE_OLDZLIB +#endif + + +namespace SKELETON +{ + class Loadable; +} + + +namespace JDLIB +{ + class Loader + { + LOADERDATA m_data; + struct addrinfo* m_addrinfo; + + bool m_stop; // = true にするとスレッド停止 + bool m_loading; + pthread_t m_thread; + SKELETON::Loadable* m_loadable; + + // 読み込みバッファ + 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; +#ifdef USE_OLDZLIB + bool m_check_gzheader; +#endif + + public: + + Loader(); + ~Loader(); + + const 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(){ m_stop = true; } + + private: + + static void* launcher( void* ); + + void clear(); + void run_main(); + struct addrinfo* get_addrinfo( const std::string& hostname, int port ); + std::string create_msg_send(); + bool wait_recv_send( int fd, bool recv ); + + // ヘッダ用 + bool receive_header( char* buf, size_t& read_size ); + bool analyze_header(); + std::string analyze_header_option( char* option ); + + // chunk用 + bool skip_chunk( char* buf, size_t& read_size ); + + // unzip 用 + bool init_unzip(); + bool unzip( char* buf, size_t& read_size ); +#ifdef USE_OLDZLIB + int check_gzheader( char* buf, size_t& size ); +#endif + }; + + + // ローダ作成関係 + + int token(); + void get_token(); + void return_token(); + + // loader作成関数 + Loader* create_loader(); + + // mainの最後でローダが動いていないかチェックする関数 + void check_loader_alive(); +} + +#endif diff --git a/src/jdlib/loaderdata.h b/src/jdlib/loaderdata.h new file mode 100644 index 000000000..0c2aa4365 --- /dev/null +++ b/src/jdlib/loaderdata.h @@ -0,0 +1,92 @@ +// ライセンス: 最新のGPL + +// +// ファイルローダに渡すデータクラス +// + +#ifndef _LOADERDATA_H +#define _LOADERDATA_H + +#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; // 非同期ソケット使用 + + std::string str_post; + + std::string host_proxy; + long port_proxy; + + std::string agent; + std::string referer; + 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; + std::string location; + std::string contenttype; + size_t size_buf; + long timeout; + + LOADERDATA(){ init(); } + + void 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; + + str_post.clear(); + + host_proxy.clear(); + port_proxy = 0; + + agent.clear(); + referer.clear(); + ex_field.clear(); + + str_header.clear(); + code = -1; + str_code.clear(); + date.clear(); + modified.clear(); + byte_readfrom = 0; + cookie.clear(); + contenttype.clear(); + size_buf = 0; + timeout = 0; + } + }; +} + +#endif diff --git a/src/jdlib/miscmsg.cpp b/src/jdlib/miscmsg.cpp new file mode 100644 index 000000000..ac407153d --- /dev/null +++ b/src/jdlib/miscmsg.cpp @@ -0,0 +1,43 @@ +// ライセンス: 最新のGPL + +//#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; +} diff --git a/src/jdlib/miscmsg.h b/src/jdlib/miscmsg.h new file mode 100644 index 000000000..e56367a82 --- /dev/null +++ b/src/jdlib/miscmsg.h @@ -0,0 +1,19 @@ +// ライセンス: 最新のGPL + +// メッセージ表示関数 + +#ifndef _MISCMSG_H +#define _MISCMSG_H + +#include + +namespace MISC +{ + // エラー出力 + void ERRMSG( const std::string& err ); + + // メッセージ + void MSG( const std::string& msg ); +} + +#endif diff --git a/src/jdlib/misctime.cpp b/src/jdlib/misctime.cpp new file mode 100644 index 000000000..43de6ef81 --- /dev/null +++ b/src/jdlib/misctime.cpp @@ -0,0 +1,65 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "misctime.h" + +#include +#include + + +// +// timeval を str に変換 +// +std::string MISC::timevaltostr( struct timeval& tv ) +{ + std::ostringstream sstr; + sstr << ( tv.tv_sec >> 16 ) << " " << ( tv.tv_sec & 0xffff ) << " " << tv.tv_usec; + return sstr.str(); +} + + +// +// 時刻を紀元からの経過秒に直す +// +time_t MISC::datetotime( const std::string& date ) +{ + if( date.empty() ) return 0; + + struct tm tm_out; + memset( &tm_out, 0, sizeof( struct tm ) ); + if( strptime( date.c_str(), "%a, %d %b %Y %T %Z", &tm_out ) == NULL ) return 0; + + time_t t_ret = timegm( &tm_out ); + +#ifdef _DEBUG + std::cout << "MISC::datetotime " << date << " -> " << t_ret << std::endl; +#endif + + return t_ret; +} + + +// +// time_t を月日の文字列に変換 +// +std::string MISC::timettostr( time_t time_from ) +{ + std::string str_ret; + + struct tm tm_tmp; + if( localtime_r( &time_from, &tm_tmp ) ){ + std::stringstream ss; + ss << ( 1900 + tm_tmp.tm_year ) << "/" << ( 1 + tm_tmp.tm_mon ) << "/" + << tm_tmp.tm_mday << " " << tm_tmp.tm_hour << ":" << tm_tmp.tm_min; + + str_ret = ss.str(); + } + +#ifdef _DEBUG + std::cout << "MISC::timettostr " << time_from << " -> " << str_ret << std::endl; +#endif + + return str_ret; +} diff --git a/src/jdlib/misctime.h b/src/jdlib/misctime.h new file mode 100644 index 000000000..5e516f5a8 --- /dev/null +++ b/src/jdlib/misctime.h @@ -0,0 +1,24 @@ +// ライセンス: 最新のGPL + +// 時間関係の関数 + +#ifndef _MISCTIME_H +#define _MISCTIME_H + +#include + +namespace MISC +{ + // timeval を str に変換 + std::string timevaltostr( struct timeval& tv ); + + // 時刻の文字列を紀元からの経過秒に直す + // (例) Tue, 27 Dec 2005 14:28:10 GMT -> 1135693690 + time_t datetotime( const std::string& date ); + + // time_t を月日の文字列に変換 + // (例) 1135785252 -> 2005/12/29 0:54 + std::string timettostr( time_t time_from ); +} + +#endif diff --git a/src/jdlib/miscutil.cpp b/src/jdlib/miscutil.cpp new file mode 100644 index 000000000..eb5600fb5 --- /dev/null +++ b/src/jdlib/miscutil.cpp @@ -0,0 +1,585 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "miscutil.h" +#include "miscmsg.h" +#include "jdiconv.h" + +#include + + +// +// str を "\n" ごとに区切ってlistにして出力 +// +std::list< std::string > MISC::get_lines( const std::string& str, bool rm_space ){ + + std::list< std::string > lines; + unsigned int i = 0,i2 = 0, r = 0; + while ( ( i2 = str.find( "\n", i ) ) != std::string::npos ){ + r = 0; + if( str[ i2 - 1 ] == '\r' ) r = 1; + if( i2 - i > 0 ){ + std::string str_tmp = str.substr( i, i2 - i - r ); + + if( rm_space ) lines.push_back( remove_space( str_tmp ) ); + else lines.push_back( str_tmp ); + } + i = i2 + 1; + } + + // 最後の行 + if( i != str.length() +1 ){ + if( rm_space ) lines.push_back( remove_space( str.substr( i ) ) ); + else 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; + int pos = 0, length = 0; + std::string str2 = remove_space( str ); + const char* data = str2.c_str(); + if( data[ 0 ] != '(' ) return lists; + + pos = 1; + + while( data[ pos ] != '\0' ){ + + // 空白削除 + while( data[ pos ] == ' ' && data[ pos ] != '\0' ) ++pos; + if( data[ pos ] == '\0' ) break; + + 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 + std::list < std::string >::iterator it; + for( it = lists.begin(); it != lists.end(); ++it ) std::cout << "[" << *it << "]" << std::endl; +#endif + + return lists; +} + +// +// strを空白または "" 単位で区切って list で出力 +// +std::list< std::string > MISC::split_line( const std::string& str ) +{ + std::string str_space = " "; + int lng_space = str_space.length(); + bool dquote; + + std::list< std::string > list_str; + + size_t i = 0, i2 = 0, lng = str.length(); + for(;;){ + + // 空白を取る + while( 1 ){ + + // 半角 + if( str[ i ] == ' ' ) ++i; + + // 全角 + else if( str[ i ] == str_space[ 0 ] && + str[ i +1 ] == str_space[ 1 ] && + ( lng_space == 2 || str[ i +2 ] == str_space[ 2 ] ) ) i += lng_space; + + else break; + } + + // " から始まる ( \"は除く ) + dquote = false; + if( str[ i ] == '\"' && str[ i -1 ] != '\\' ){ + dquote = true; + ++i; + } + + // 空白か " を探す + i2 = i; + int 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 ] && + ( lng_space == 2 || 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, 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 = ( 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; +} + + + + +// +// strの前後の空白削除 +// +std::string MISC::remove_space( const std::string& str ) +{ + std::string str_space = " "; + int lng_space = str_space.length(); + + std::string str_out; + int lng = str.length(); + + if( lng == 0 ) return str; + if( str.find( " " ) == std::string::npos ) return str; + + // 前 + size_t i = 0; + while( 1 ){ + + // 半角 + if( str[ i ] == ' ' ) ++i; + + // 全角 + else if( str[ i ] == str_space[ 0 ] && + str[ i +1 ] == str_space[ 1 ] && + ( lng_space == 2 || str[ i +2 ] == str_space[ 2 ] ) ) i += lng_space; + else break; + } + + // 後 + int i2 = lng -1; + while( 1 ){ + + // 半角 + if( str[ i2 ] == ' ' ) --i2; + + // 全角 + else if( str[ i2 - lng_space +1 ] == str_space[ 0 ] && + str[ i2 - lng_space +2 ] == str_space[ 1 ] && + ( lng_space == 2 || str[ i2 - lng_space +3 ] == str_space[ 2 ] ) ) i2 -= lng_space; + else break; + } + + str_out = str.substr( i, i2 - i + 1 ); + + return str_out; +} + + + +// +// str1, str2 に囲まれた文字列を切り出す +// +std::string MISC::cut_str( const std::string& str, const std::string& str1, const std::string& str2 ) +{ + unsigned int i = str.find( str1 ); + if( i == std::string::npos ) return std::string(); + i += str1.length(); + unsigned int 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 ) +{ + std::string str_out; + unsigned int i, pos = 0; + while( ( i = str.find( str1 , pos ) ) != std::string::npos ){ + + str_out += str.substr( pos, ( i - pos ) ) + str2; + pos += ( i - pos ) + str1.length(); + } + + str_out += str.substr( pos ); + 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, "\\\"", "\"" ); +} + + + +// +// 文字列(utf-8も) -> 整数変換 +// +// (例) "123" -> 123 +// +// 入力: +// str +// +// 出力: +// dig: 桁数、0なら失敗 +// n : str から何バイト読み取ったか +// +// 戻り値: 数値 +// +int MISC::str_to_uint( const char* str, unsigned int& dig, unsigned int& n ) +{ + int out = 0; + dig = 0; + n = 0; + while( *str != '\0' ){ + + unsigned char in = (*str); + + if( '0' <= in && in <= '9' ){ + + out = out*10 + ( in - '0' ); + ++dig; + ++str; + ++n; + } + + else{ + // utf-8 + unsigned char in2 = (* ( str +1 )); + unsigned char in3 = (* ( str +2 )); + if( in == 0xef && in2 == 0xbc && ( 0x90 <= in3 && in3 <= 0x99 ) ){ + out = out*10 + ( in3 - 0x90 ); + ++dig; + str += 3; + n += 3; + } + + else break; + } + } + + return out; +} + + + +// 数字 -> 文字変換 +std::string MISC::itostr( int n ) +{ + std::ostringstream ss; + ss << n; + + return ss.str(); +} + + +// +// url エンコード +// +std::string MISC::url_encode( const char* str, 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 != '_' )){ + + snprintf( str_tmp, tmplng , "\%%%02x", c ); + } + else { + str_tmp[ 0 ] = c; + str_tmp[ 1 ] = '\0'; + } + + str_encoded += str_tmp; + } + + return str_encoded; +} + + + +// +// 文字コード変換して url エンコード +// +std::string MISC::charset_url_encode( const std::string& str, const std::string& charset ) +{ + if( charset.empty() ) return MISC::url_encode( str.c_str(), str.length() ); + + char* str_bk = ( char* ) malloc( str.length() + 64 ); + strcpy( str_bk, str.c_str() ); + + JDLIB::Iconv* libiconv = new JDLIB::Iconv( "UTF-8", charset.c_str() ); + int byte_out; + const char* str_enc = libiconv->convert( str_bk, strlen( str_bk ), byte_out ); + std::string str_encoded = MISC::url_encode( str_enc, strlen( str_enc ) ); + delete libiconv; + free( str_bk ); + + return str_encoded; +} + + + +// +// utf-8 -> ucs2 変換 +// +// utfstr : 入力文字 (UTF-8) +// byte : 長さ(バイト) utfstr が ascii なら 1, UTF-8 なら 2 or 3 を入れて返す +// +// 戻り値 : ucs2 +// +int MISC::utf8toucs2( const char* utfstr, int& byte ) +{ + int ucs2 = 0; + byte = 0; + + if( utfstr[ 0 ] == '\0' ) return ucs2; + + if( ( ( unsigned char ) utfstr[ 0 ] & 0x80 ) == 0 ){ // ascii + byte = 1; + ucs2 = utfstr[ 0 ]; + } + + else if( ( ( unsigned char ) utfstr[ 0 ] & 0x20 ) == 0 ){ + byte = 2; + ucs2 = utfstr[ 0 ] & 0x1f; + ucs2 = ( ucs2 << 6 ) + ( utfstr[ 1 ] & 0x3f ); + } + + else if( ( ( unsigned char ) utfstr[ 0 ] & 0x10 ) == 0 ){ + byte = 3; + ucs2 = utfstr[ 0 ] & 0x0f; + ucs2 = ( ucs2 << 6 ) + ( utfstr[ 1 ] & 0x3f ); + ucs2 = ( ucs2 << 6 ) + ( utfstr[ 2 ] & 0x3f ); + } + + else{ + byte = 4; + ucs2 = utfstr[ 0 ] & 0x07; + ucs2 = ( ucs2 << 6 ) + ( utfstr[ 1 ] & 0x3f ); + ucs2 = ( ucs2 << 6 ) + ( utfstr[ 2 ] & 0x3f ); + ucs2 = ( ucs2 << 6 ) + ( utfstr[ 3 ] & 0x3f ); + } + + return ucs2; +} + + + + +// +// ucs2 -> utf8 変換 +// +// 戻り値 : バイト数 +// +int MISC::ucs2utf8( 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; +} + + + +// +// str を大文字化 +// +std::string MISC::toupper_str( const std::string& str ) +{ + std::string str_out; + for( unsigned int i = 0; i < str.length() ; ++i ) str_out += toupper( str[ i ] ); + + return str_out; +} + + +// +// list 内のアイテムを全部大文字化 +// +std::list< std::string > MISC::toupper_list( std::list< std::string >& list_str ) +{ + std::list< std::string > list_out; + std::list< std::string >::iterator it = list_str.begin(); + for( ; it != list_str.end() ; ++it ) list_out.push_back( MISC::toupper_str( *it ) ); + + return list_out; +} + + + +// +// str を小文字化 +// +std::string MISC::tolower_str( const std::string& str ) +{ + std::string str_out; + + for( unsigned int i = 0; i < str.length() ; ++i ) str_out += tolower( str[ i ] ); + + return str_out; +} + + + +// +// path からホスト名だけ取り出す +// +std::string MISC::get_hostname( const std::string& path ) +{ + if( path.find( "http://" ) == std::string::npos ) return std::string(); + unsigned int i = path.find( "/", strlen( "http://" ) ); + if( i == std::string::npos ) return std::string(); + + return path.substr( 0, i ); +} + + + +// +// path からファイル名だけ取り出す +// +std::string MISC::get_filename( const std::string& path ) +{ + if( path.empty() ) return std::string(); + + unsigned int 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(); + + unsigned int i = path.rfind( "/" ); + if( i == std::string::npos ) return std::string(); + + return path.substr( 0, i+1 ); +} diff --git a/src/jdlib/miscutil.h b/src/jdlib/miscutil.h new file mode 100644 index 000000000..f657a40d0 --- /dev/null +++ b/src/jdlib/miscutil.h @@ -0,0 +1,79 @@ +// ライセンス: 最新のGPL + +// 文字列関係の関数 + +#ifndef _MISCUTIL_H +#define _MISCUTIL_H + +#include +#include + +namespace MISC +{ + // str を "\n" ごとに区切ってlistにして出力 + // rm_space == true なら各行の前後の空白を削る + std::list< std::string > get_lines( const std::string& str, bool rm_space = false ); + + // strを空白または "" 単位で区切って list で出力 + std::list< std::string > split_line( const std::string& str ); + + // strを delimで区切って list で出力 + std::list< std::string > StringTokenizer( const std::string& str, char delim ); + + // emacs lisp のリスト型を要素ごとにlistにして出力 + std::list< std::string > get_elisp_lists( const std::string& str ); + + // strの前後の空白削除 + std::string remove_space( const std::string& str ); + + // 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 ); + + // " を \" に置き換え + std::string replace_quot( const std::string& str ); + + // \" を " に置き換え + std::string recover_quot( const std::string& str ); + + //文字 -> 整数変換 + int str_to_uint( const char* str, unsigned int& dig, unsigned int& n ); + + // 数字 -> 文字変換 + std::string itostr( int n ); + + // urlエンコード + std::string url_encode( const char* str, size_t n ); + + //文字コード変換して url エンコード + std::string charset_url_encode( const std::string& str, const std::string& charset ); + + // utf-8 -> ucs2 変換 + int utf8toucs2( const char* utfstr, int& byte ); + + // ucs2 -> utf8 変換 + int ucs2utf8( int ucs2, char* utfstr ); + + // str を大文字化 + std::string toupper_str( const std::string& str ); + + // list 内のアイテムを全部大文字化 + std::list< std::string > toupper_list( std::list< std::string >& list_str ); + + //str を小文字化 + std::string tolower_str( const std::string& str ); + + // path からホスト名だけ取り出す + std::string get_hostname( const std::string& path ); + + // path からファイル名だけ取り出す + std::string get_filename( const std::string& path ); + + // path からファイル名を除いてディレクトリだけ取り出す + std::string get_dir( const std::string& path ); +} + + +#endif diff --git a/src/jdlib/refptr_lock.h b/src/jdlib/refptr_lock.h new file mode 100644 index 000000000..bef1e4374 --- /dev/null +++ b/src/jdlib/refptr_lock.h @@ -0,0 +1,56 @@ +// ライセンス: 最新のGPL + +// +// ロック付きリファレンスクラスのテンプレート +// +// 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 = NULL; + } + } + + void set( T *p ){ + clear(); + m_p = p; + if( m_p ) m_p->lock(); + } + + + T* operator -> (){ return m_p; } + bool operator == ( const T *p ) const { return( m_p == p ); } + bool operator != ( const T *p ) const { return( m_p != p ); } + bool operator ! () const { return ( m_p == NULL ); } + operator bool () const { return ( m_p != NULL ); } + + RefPtr_Lock< T >& operator = ( const RefPtr_Lock< T >& a ){ set( a.m_p ); return *this; } + RefPtr_Lock< T >& operator = ( RefPtr_Lock< T >& a ){ set( a.m_p ); return *this; } + RefPtr_Lock< T >& operator = ( const T *p ){ set( p ); return *this; } + RefPtr_Lock< T >& operator = ( T *p ){ set( p ); return *this; } + + RefPtr_Lock() : m_p (0){} + RefPtr_Lock( const RefPtr_Lock< T >& a ): m_p (0){ set( a.m_p ); } + RefPtr_Lock( RefPtr_Lock< T >& a ) : m_p (0){ set( a.m_p ); } + RefPtr_Lock( T *p ) : m_p (0){ set( p ); } + RefPtr_Lock( const T *p ) : m_p (0){ set( p ); } + + virtual ~RefPtr_Lock(){ clear();} + }; +} + +#endif diff --git a/src/jdversion.h b/src/jdversion.h new file mode 100644 index 000000000..2497837c4 --- /dev/null +++ b/src/jdversion.h @@ -0,0 +1,11 @@ +// バージョン情報 + +#ifndef _JDVER_H +#define _JDVER_H + +#define JDCOPYRIGHT "(c) 2006 JD project" +#define JDVERSIONSTR "1.50.060601" +#define JDVERSION 150060601 +#define JDURL "http://www.geocities.jp/jd4linux/" + +#endif diff --git a/src/login2ch.cpp b/src/login2ch.cpp new file mode 100644 index 000000000..67b3a2b00 --- /dev/null +++ b/src/login2ch.cpp @@ -0,0 +1,193 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "login2ch.h" +#include "global.h" +#include "httpcode.h" +#include "session.h" +#include "command.h" + +#include "config/globalconf.h" + +#include "jdlib/loaderdata.h" +#include "jdlib/miscmsg.h" + +#define SIZE_OF_RAWDATA ( 64 * 1024 ) + +LOGIN::Login2ch* instance_login2ch = NULL; + +LOGIN::Login2ch* LOGIN::get_login2ch() +{ + if( ! instance_login2ch ) instance_login2ch = new LOGIN::Login2ch(); + assert( instance_login2ch ); + + return instance_login2ch; +} + + +void LOGIN::delete_login2ch() +{ + if( instance_login2ch ) delete instance_login2ch; + instance_login2ch = NULL; +} + + +using namespace LOGIN; + + +Login2ch::Login2ch() + : SKELETON::Login( URL_LOGIN2CH ) + , m_rawdata( 0 ), m_lng_rawdata( 0 ) +{ +#ifdef _DEBUG + std::cout << "Login2ch::Login2ch\n"; +#endif +} + + +Login2ch::~Login2ch() +{ +#ifdef _DEBUG + std::cout << "Login2ch::~Login2ch\n"; +#endif + + if( m_rawdata ) free( m_rawdata ); +} + + + +// +// ログアウト +// +void Login2ch::logout() +{ +#ifdef _DEBUG + std::cout << "Login2ch::logout\n"; +#endif + if( is_loading() ) return; + + set_login_now( false ); + set_sessionid( std::string() ); +} + + +// +// ログイン開始 +// +void Login2ch::start_login() +{ +#ifdef _DEBUG + std::cout << "Login2ch::start_login url = " << CONFIG::get_url_login2ch() << std::endl; +#endif + + set_str_code( "" ); + + if( is_loading() ) return; + if( ! SESSION::is_online() ){ + + // ディスパッチャ経由でreceive_finish()を呼ぶ + finish(); + return; + } + if( get_username().empty() || get_passwd().empty() ){ + + finish(); + return; + } + + JDLIB::LOADERDATA 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"; + 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.size_buf = CONFIG::get_loader_bufsize(); + data.timeout = CONFIG::get_loader_timeout(); + + data.str_post = "ID="; + data.str_post += get_username(); + data.str_post += "&PW="; + data.str_post += get_passwd(); + + logout(); + if( ! m_rawdata ) m_rawdata = ( char* )malloc( SIZE_OF_RAWDATA ); + memset( m_rawdata, 0, SIZE_OF_RAWDATA ); + m_lng_rawdata = 0; + + start_load( data ); +} + + +// +// データ受信 +// +void Login2ch::receive_data( const char* data, size_t size ) +{ +#ifdef _DEBUG + std::cout << "Login2ch::receive_data\n"; +#endif + + memcpy( m_rawdata + m_lng_rawdata , data, size ); + m_lng_rawdata += size; + assert( m_lng_rawdata < SIZE_OF_RAWDATA ); +} + + +// +// データ受信完了 +// +void Login2ch::receive_finish() +{ +#ifdef _DEBUG + std::cout << "Login2ch::receive_finish code = " << get_code() << " lng_rawdata = " << m_lng_rawdata << std::endl; + if( m_rawdata ) std::cout << m_rawdata << std::endl; +#endif + + std::string sid; + bool show_err = true; + + if( m_rawdata && get_code() == HTTP_OK ){ + + // SID 取得 + sid = std::string( m_rawdata ); + + if( sid.find( "SESSION-ID=" ) == 0 ){ + + sid = sid.substr( strlen( "SESSION-ID=" ) ); +#ifdef _DEBUG + std::cout << "sid = " << sid << std::endl; +#endif + if( sid.find( "ERROR" ) != 0 ){ + set_login_now( true ); + set_sessionid( sid ); + show_err = false; + } + 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() ){ + Gtk::MessageDialog mdiag( "オフラインです" ); + mdiag.run(); + } + else if( get_username().empty() || get_passwd().empty() ){ + Gtk::MessageDialog mdiag( "IDかパスワードが空白です" ); + mdiag.run(); + } + else if( show_err ){ + std::string str_err = "ログインに失敗しました。\n"; + str_err += get_str_code(); + Gtk::MessageDialog mdiag( str_err ); + mdiag.run(); + } + + // コアに受信完了を知らせる + CORE::core_set_command( "login2ch_finished", "" ); +} diff --git a/src/login2ch.h b/src/login2ch.h new file mode 100644 index 000000000..473aa0e50 --- /dev/null +++ b/src/login2ch.h @@ -0,0 +1,41 @@ +// ライセンス: 最新のGPL + +// +// 2chへのログイン管理クラス +// +// セッション管理やログイン、パスワードの保存などを行う +// + +#ifndef _LOGIN2CH_H +#define _LOGIN2CH_H + +#include "skeleton/login.h" + +namespace LOGIN +{ + class Login2ch : public SKELETON::Login + { + char* m_rawdata; + int m_lng_rawdata; + + public: + + Login2ch(); + virtual ~Login2ch(); + + virtual void start_login(); + virtual void logout(); + + private: + + virtual void receive_data( const char* , size_t ); + virtual void receive_finish(); + }; + + + Login2ch* get_login2ch(); + void delete_login2ch(); +} + + +#endif diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 000000000..c26663f01 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,399 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "config/globalconf.h" + +#include "winmain.h" +#include "cache.h" + +#include "jdlib/miscmsg.h" +#include "jdlib/miscutil.h" + +#include +#include +#include +#include + +#ifdef USE_GNOMEUI +#include +#endif + +#ifdef USE_XSMP +#include +#include + +struct XSMPDATA +{ + SmcConn smc_connect; + IceConn ice_connect; + guint id_process_message; +}; +#endif + +WinMain* Win_Main = NULL; + +// ロック +// 既にロックされていたら false +bool lock_jd() +{ + std::string path = CACHE::path_lock(); + + if( CACHE::is_file_exists( path ) == CACHE::EXIST_FILE ) return false; + + std::ofstream ofs; + ofs.open( path.c_str() ); + if( !ofs.is_open() ){ + MISC::ERRMSG( "can't open " + path ); + } + else{ + + ofs << "lock"; + ofs.close(); + } + + return true; +} + + +// ロック解除 +void unlock_jd() +{ + std::string path = CACHE::path_lock(); + + if( CACHE::is_file_exists( path ) == CACHE::EXIST_FILE ) unlink( path.c_str() ); +} + + + +// バックアップ復元 +void restore_bkup() +{ + struct timeval tv; + struct timezone tz; + gettimeofday( &tv, &tz ); + + std::string path_main = CACHE::path_xml_listmain(); + std::string path_favor = CACHE::path_xml_favorite(); + std::string path_main_bkup = CACHE::path_xml_listmain_bkup(); + std::string path_favor_bkup = CACHE::path_xml_favorite_bkup(); + std::string path_main_old = path_main + "." + MISC::itostr( tv.tv_sec ); + std::string path_favor_old = path_favor + "." + MISC::itostr( tv.tv_sec ); + + bool bkup_main = ( CACHE::is_file_exists( path_main_bkup ) == CACHE::EXIST_FILE ); + bool bkup_favor = ( CACHE::is_file_exists( path_favor_bkup ) == CACHE::EXIST_FILE ); + + if( bkup_main || bkup_favor ){ + + Gtk::MessageDialog* mdiag + = new Gtk::MessageDialog( "前回の起動時に正しくJDが終了されませんでした。\n\n板リストとお気に入りをバックアップファイルから復元しますか?\nキャンセルを押すとバックアップファイルを削除します。", + false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_OK_CANCEL ); + int ret = mdiag->run(); + delete mdiag; + if( ret == Gtk::RESPONSE_OK ){ + + if( bkup_main ){ + rename( path_main.c_str(), path_main_old.c_str() ); + rename( path_main_bkup.c_str(), path_main.c_str() ); + } + if( bkup_favor ){ + rename( path_favor.c_str(), path_favor_old.c_str() ); + rename( path_favor_bkup.c_str(), path_favor.c_str() ); + } + + std::string msg = "更新しました。\n\n古いリストはそれぞれ\n\n"; + + if( bkup_main ) msg += path_main_old + "\n"; + if( bkup_favor ) msg += path_favor_old + "\n"; + + msg += "\nに移動しました。"; + + mdiag = new Gtk::MessageDialog( msg ); + mdiag->run(); + delete mdiag; + } + else{ + if( bkup_main ) unlink( path_main_bkup.c_str() ); + if( bkup_favor ) unlink( path_favor_bkup.c_str() ); + } + } +} + + + +// 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->shutdown(); + unlock_jd(); + } + + exit(0); +} + + + +// GNOMEUI によるセッション管理 +#ifdef USE_GNOMEUI + +// gnomeセッションマネージャから "save_yourself" シグナルを受け取った +static int save_yourself_gnome( GnomeClient *client, + int phase, + GnomeSaveStyle save_style, + int shutdown, + GnomeInteractStyle interact_style, + int fast, + gpointer client_data + ) +{ + + if( Win_Main ) Win_Main->save_session(); + unlock_jd(); + + return TRUE; +} + +#endif + + + +// 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(); + unlock_jd(); + } + + 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, NULL, NULL ); + +#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 = NULL; + return FALSE; + } + + return TRUE; +} + + +void ice_watch_proc( IceConn ice_connect, + IcePointer client_data, + Bool opening, + IcePointer *watch_data ) +{ + XSMPDATA *xsmpdata = ( XSMPDATA* ) 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, + NULL ); + g_io_channel_unref( channel ); + } + } +} + + + +// XSMP初期化関数 +void xsmp_session_init( XSMPDATA* xsmpdata ) +{ + if( xsmpdata->smc_connect ) return; + if( g_getenv("SESSION_MANAGER") == NULL ) 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 = NULL; + gchar errstr[ 1024 ]; + xsmpdata->smc_connect + = SmcOpenConnection( NULL, NULL, SmProtoMajor, SmProtoMinor, + SmcSaveYourselfProcMask | SmcDieProcMask | SmcSaveCompleteProcMask | SmcShutdownCancelledProcMask, + &smc_callbacks, + NULL, + &id, + 1024, errstr ); + if( ! xsmpdata->smc_connect ){ + MISC::ERRMSG( "SmcOpenConnection failed." ); + return; + } +} + + +// XSMP終了関数 +void xsmp_session_end( XSMPDATA* xsmpdata ) +{ + if( xsmpdata->smc_connect ) SmcCloseConnection( xsmpdata->smc_connect, 0, NULL ); + xsmpdata->smc_connect = NULL; +} + +#endif + +//////////////////////////////////////////////////////////// + +int main( int argc, char **argv ) +{ + // 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, NULL ) != 0 + || sigaction( SIGINT, &sigact, NULL ) != 0 + || sigaction( SIGQUIT, &sigact, NULL ) != 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 + + // GNOMEUIによるセッション管理 +#ifdef USE_GNOMEUI + +#ifdef _DEBUG + std::cout << "USE_GNOMEUI\n"; +#endif + + // gnomeセッションマネージャとつないでログアウト時に"save_yourself"シグナルをもらう + gnome_init( "jd", "1.0", argc, argv ); + GnomeClient *client_gnome = gnome_master_client(); + if( client_gnome ) gtk_signal_connect( GTK_OBJECT( client_gnome ), "save_yourself", GTK_SIGNAL_FUNC( save_yourself_gnome ), NULL ); + else MISC::ERRMSG( "failed to connect to gnome session manager" ); +#endif + + // 全体設定ロード + bool init = !( CONFIG::init_config() ); + + // 初回起動時にルートを作る + if( init ) CACHE::mkdir_root(); + + // ロック + if( ! lock_jd() ){ + + Gtk::MessageDialog* mdiag = new Gtk::MessageDialog( "JDは既に起動しています。起動しますか?", + false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_OK_CANCEL ); + int ret = mdiag->run(); + delete mdiag; + if( ret != Gtk::RESPONSE_OK ) return 0; + } + + // バックアップファイル復元 + restore_bkup(); + + Win_Main = new WinMain( init ); + if( Win_Main ){ + + m.run( *Win_Main ); + + delete Win_Main; + Win_Main = NULL; + } + +#ifdef USE_XSMP + xsmp_session_end( &xsmpdata ); +#endif + + // ロック解除 + unlock_jd(); + + return 0; +} diff --git a/src/message/Makefile.am b/src/message/Makefile.am new file mode 100644 index 000000000..8612c2474 --- /dev/null +++ b/src/message/Makefile.am @@ -0,0 +1,18 @@ +noinst_LIBRARIES = libmessage.a + +libmessage_a_SOURCES = \ + messageadmin.cpp \ + messagewin.cpp \ + messageviewbase.cpp \ + messageview.cpp \ + post.cpp + +noinst_HEADERS = \ + messageadmin.h \ + messagewin.h \ + messageviewbase.h \ + messageview.h \ + post.h + +AM_CXXFLAGS = @GTKMM_CFLAGS@ +INCLUDES = -I$(top_srcdir)/src diff --git a/src/message/messageadmin.cpp b/src/message/messageadmin.cpp new file mode 100644 index 000000000..bef718ed2 --- /dev/null +++ b/src/message/messageadmin.cpp @@ -0,0 +1,188 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "messageadmin.h" +#include "messagewin.h" + +#include "skeleton/view.h" + +#include "dbtree/interface.h" + +#include "viewfactory.h" +#include "command.h" +#include "global.h" +#include "controlid.h" + +MESSAGE::MessageAdmin* instance_messageadmin = NULL; + +MESSAGE::MessageAdmin* MESSAGE::get_admin() +{ + if( ! instance_messageadmin ) instance_messageadmin = new MESSAGE::MessageAdmin(); + return instance_messageadmin; + +} + +void MESSAGE::delete_admin() +{ + if( instance_messageadmin ) delete instance_messageadmin; + instance_messageadmin = NULL; +} + +using namespace MESSAGE; + + +MessageAdmin::MessageAdmin() + : m_win( 0 ), + m_view( 0 ) +{ + m_disp.connect( sigc::mem_fun( *this, &MessageAdmin::exec_command ) ); +} + + +MessageAdmin::~MessageAdmin() +{ +#ifdef _DEBUG + std::cout << "MessageAdmin::~MessageAdmin\n"; +#endif + + close_view(); +} + + +// +// コマンドセット +// +void MessageAdmin::set_command( const std::string& command, const std::string& url, const std::string& arg1 ) +{ +#ifdef _DEBUG + std::cout << "MessageAdmin::set_command : " << command << " " + << url << " " << arg1 << " " << std::endl; +#endif + + COMMAND_ARGS command_arg; + command_arg.command = command; + command_arg.url = url; + command_arg.arg1 = arg1; + m_list_command.push_back( command_arg ); + m_disp.emit(); +} + + +// +// コマンド実行 +// +void MessageAdmin::exec_command() +{ + if( m_list_command.size() == 0 ) return; + + COMMAND_ARGS command = m_list_command.front(); + m_list_command.pop_front(); + + if( command.command == "open_view" ) open_view( command.url, command.arg1, false ); + + else if( command.command == "create_new_thread" ) open_view( command.url, command.arg1, true ); + + else if( command.command == "close_currentview" ){ + + if( m_view && m_view->set_command( "loading" ) ){ + Gtk::MessageDialog mdiag( "書き込み中です" ); + mdiag.run(); + return; + } + + if( m_view && ! m_view->set_command( "empty" ) ){ + Gtk::MessageDialog mdiag( "編集中のメッセージを破棄しますか?", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_OK_CANCEL ); + if( mdiag.run() == Gtk::RESPONSE_OK ); + else return; + } + + close_view(); + } + + else if( command.command == "focus_view" ) focus_view(); + + // view の操作 + else if( command.command == "exec_Write" ){ + if( m_view ) m_view->operate_view( CONTROL::ExecWrite ); + } + else if( command.command == "tab_left" ){ + if( m_view ) m_view->operate_view( CONTROL::TabLeft ); + } + else if( command.command == "tab_right" ){ + if( m_view ) m_view->operate_view( CONTROL::TabRight ); + } +} + + +// +// 閉じる +// +void MessageAdmin::close_view() +{ + if( m_win ) delete m_win; + if( m_view ) delete m_view; + + m_view = NULL; + m_win = NULL; +} + + +// +// フォーカス +// +void MessageAdmin::focus_view() +{ + if( m_view ) m_view->focus_view(); +} + + + +// +// 開く +// +// new_thread = true なら新スレを立てる +// +void MessageAdmin::open_view( const std::string& url, const std::string& msg, bool new_thread ) +{ +#ifdef _DEBUG + std::cout << "MessageAdmin::open_view " << url << std::endl; +#endif + + if( m_view && m_view->set_command( "loading" ) ){ + Gtk::MessageDialog mdiag( "書き込み中です" ); + mdiag.run(); + return; + } + + if( m_view && ! m_view->set_command( "empty" ) ){ + Gtk::MessageDialog mdiag( "編集中のメッセージを破棄しますか?", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_OK_CANCEL ); + if( mdiag.run() == Gtk::RESPONSE_OK ); + else return; + } + + close_view(); + + std::string url_msg; + int type; + CORE::VIEWFACTORY_ARGS args; + if( ! new_thread ){ + type = CORE::VIEW_MESSAGE; + args.arg1 = msg; + url_msg = url; + } + + // 新スレ + // スレッドの id は 0000000000.(各板別の拡張子) とする。 + else{ + type = CORE::VIEW_NEWTHREAD; + args.arg1 = msg; + url_msg = DBTREE::url_datbase( url ) + "0000000000" + DBTREE::board_ext( url ); + } + + m_view = CORE::ViewFactory( type, url_msg, args ); + m_win = new MESSAGE::MessageWin(); + m_win->add( *m_view ); + m_win->show_all(); +} diff --git a/src/message/messageadmin.h b/src/message/messageadmin.h new file mode 100644 index 000000000..f1ff49b34 --- /dev/null +++ b/src/message/messageadmin.h @@ -0,0 +1,53 @@ +// ライセンス: 最新のGPL + +// +// 書き込みウィンドウの管理クラス +// + +#ifndef _MESSAGEADMIN_H +#define _MESSAGEADMIN_H + +#include +#include + +struct COMMAND_ARGS; + +namespace SKELETON +{ + class View; +} + +namespace MESSAGE +{ + class MessageWin; + + // SKELETON::Admin を継承していない(独自Adminクラス) + class MessageAdmin + { + Glib::Dispatcher m_disp; + std::list< COMMAND_ARGS > m_list_command; + + MessageWin* m_win; + SKELETON::View* m_view; + + public: + + MessageAdmin(); + ~MessageAdmin(); + + void set_command( const std::string& command, const std::string& url = std::string() , const std::string& arg1 = std::string() ); + + private: + + void exec_command(); + + void open_view( const std::string& url, const std::string& msg, bool new_thread ); + void close_view(); + void focus_view(); + }; + + MESSAGE::MessageAdmin* get_admin(); + void delete_admin(); +} + +#endif diff --git a/src/message/messageview.cpp b/src/message/messageview.cpp new file mode 100644 index 000000000..8582aa2a8 --- /dev/null +++ b/src/message/messageview.cpp @@ -0,0 +1,152 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "messageview.h" +#include "post.h" + +#include "jdlib/miscutil.h" + +#include "dbtree/interface.h" + +#include "command.h" + +#include + +using namespace MESSAGE; + + MessageViewMain::MessageViewMain( const std::string& url, const std::string& msg ) + : MessageViewBase( url ) +{ + setup_view(); + set_message( msg ); +} + + +MessageViewMain::~MessageViewMain() +{ + save_name(); +} + + +// +// サブジェクトの背景色を変える +// +void MessageViewMain::on_realize() +{ + SKELETON::View::on_realize(); + + Gdk::Color color_bg = get_style()->get_bg( Gtk::STATE_NORMAL ); + get_entry_subject().modify_base( get_entry_subject().get_state(), color_bg ); + + get_text_message().focus_view(); +} + + + + +// +// ポストするメッセージ作成 +// +std::string MessageViewMain::create_message() +{ + std::string msg = get_text_message().get_text(); + std::string name = get_entry_name().get_text(); + std::string mail = get_entry_mail().get_text(); + + if( msg.empty() ){ + Gtk::MessageDialog mdiag( "本文が空白です" ); mdiag.run(); + return std::string(); + } + + return DBTREE::create_write_message( get_url(), name, mail, msg ); +} + + +// +// 書き込み +// +// 書き込みが終わったら MessageViewBase::post_fin()が呼ばれる +// +void MessageViewMain::write() +{ + std::string msg = create_message(); + if( msg.empty() ) return; + 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(); + + get_entry_subject().set_editable( true ); + get_entry_subject().set_activates_default( true ); + get_entry_subject().set_has_frame( true ); + get_entry_subject().set_text( std::string() ); + + set_message( msg ); + + get_entry_subject().grab_focus(); +} + + + + +// +// ポストするメッセージ作成 +// +std::string MessageViewNew::create_message() +{ + std::string subject = get_entry_subject().get_text(); + std::string msg = get_text_message().get_text(); + std::string name = get_entry_name().get_text(); + std::string mail = get_entry_mail().get_text(); + + if( subject.empty() ){ + Gtk::MessageDialog mdiag( "スレタイトルが空白です" ); mdiag.run(); + return std::string(); + } + + if( msg.empty() ){ + Gtk::MessageDialog mdiag( "本文が空白です" ); mdiag.run(); + return std::string(); + } + + Gtk::MessageDialog mdiag( "新スレを作成しますか?", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_OK_CANCEL ); + if( mdiag.run() == Gtk::RESPONSE_OK ) return DBTREE::create_newarticle_message( get_url(), subject, name, mail, msg ); + + return std::string(); +} + + +// +// 書き込み +// +// 書き込みが終わったら MessageViewBase::post_fin()が呼ばれる +// +void MessageViewNew::write() +{ + std::string msg = create_message(); + if( msg.empty() ) return; + post_msg( msg, true ); +} + + + +void MessageViewNew::reload() +{ + CORE::core_set_command( "open_board", DBTREE::url_subject( get_url() ), "true" ); +} diff --git a/src/message/messageview.h b/src/message/messageview.h new file mode 100644 index 000000000..41c2c1cc3 --- /dev/null +++ b/src/message/messageview.h @@ -0,0 +1,46 @@ +// ライセンス: 最新のGPL + +#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 ); + virtual ~MessageViewMain(); + + virtual void reload(); + + private: + virtual void write(); + virtual std::string create_message(); + + protected: + virtual void on_realize(); + }; + + + // 新スレ立て用ビュー + class MessageViewNew : public MessageViewBase + { + public: + MessageViewNew( const std::string& url, const std::string& msg ); + virtual ~MessageViewNew(){} + + virtual void reload(); + + private: + virtual void write(); + virtual std::string create_message(); + }; + +} + + + +#endif diff --git a/src/message/messageviewbase.cpp b/src/message/messageviewbase.cpp new file mode 100644 index 000000000..fdf7bcb4b --- /dev/null +++ b/src/message/messageviewbase.cpp @@ -0,0 +1,402 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "messageadmin.h" +#include "messageviewbase.h" +#include "post.h" + +#include "jdlib/miscutil.h" +#include "jdlib/misctime.h" + +#include "dbtree/interface.h" + +#include "httpcode.h" +#include "command.h" +#include "viewfactory.h" +#include "controlutil.h" +#include "controlid.h" + +#include +#include + + +using namespace MESSAGE; + + +MessageViewBase::MessageViewBase( const std::string& url ) + : SKELETON::View( url ), + m_post( 0 ), + m_preview( 0 ), + m_button_write( Gtk::Stock::NEW ), + m_button_cancel( Gtk::Stock::CLOSE ) +{ +#ifdef _DEBUG + std::cout << "MessageViewBase::MessageViewBase " << get_url() << std::endl; +#endif + + // コントロールモード設定 + get_control().set_mode( CONTROL::MODE_MESSAGE ); +} + + + +MessageViewBase::~MessageViewBase() +{ +#ifdef _DEBUG + std::cout << "MessageViewBase::~MessageViewBase " << get_url() << std::endl; +#endif + + if( m_preview ) delete m_preview; + m_preview = NULL; + + if( m_post ) delete m_post; + m_post = NULL; +} + + + +// +// セットアップ +// +void MessageViewBase::setup_view() +{ + pack_widget(); +} + + +// +// コマンド +// +bool MessageViewBase::set_command( const std::string& command, const std::string& arg ) +{ + if( command == "empty" ) return get_message().empty(); + + if( command == "loading" ){ + if( !m_post ) return false; + return m_post->is_loading(); + } + + 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::pack_widget() +{ + // Gtk::Label を使うと勝手にリサイズするときがあるので + // Gtk::Entry を使う。背景色を変えるときは on_realize() で指定する。 + m_entry_subject.set_editable( false ); + m_entry_subject.set_activates_default( false ); + m_entry_subject.set_has_frame( false ); + m_entry_subject.set_text( DBTREE::article_subject( get_url() ) ); + + m_label_board.set_text( "[ " + DBTREE::board_name( get_url() ) + " ] " ); + + m_button_write.signal_clicked().connect( sigc::mem_fun( *this, &MessageViewBase::slot_write_clicked ) ); + m_button_cancel.signal_clicked().connect( sigc::mem_fun( *this, &MessageViewBase::slot_cancel_clicked ) ); + + m_tooltip.set_tip( m_button_write, CONTROL::get_label_motion( CONTROL::ExecWrite ) ); + m_tooltip.set_tip( m_button_cancel, CONTROL::get_label_motion( CONTROL::CancelWrite ) ); + + m_toolbar.pack_start( m_label_board, Gtk::PACK_SHRINK ); + m_toolbar.pack_start( m_entry_subject, Gtk::PACK_EXPAND_WIDGET, 2 ); + m_toolbar.pack_end( m_button_cancel, Gtk::PACK_SHRINK ); + m_toolbar.pack_end( m_button_write, Gtk::PACK_SHRINK ); + + // 書き込みビュー + m_label_name.set_alignment( Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER ); + m_label_mail.set_alignment( Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER ); + m_label_name.set_text( " name " ); + m_label_mail.set_text( " mail " ); + + m_check_fixname.set_label( "fix" ); + m_check_fixmail.set_label( "fix" ); + + if( DBTREE::write_fixname( get_url() ) ){ + m_check_fixname.set_active(); + m_entry_name.set_text( DBTREE::write_name( get_url() ) ); + } + if( DBTREE::write_fixmail( get_url() ) ){ + m_check_fixmail.set_active(); + m_entry_mail.set_text( DBTREE::write_mail( get_url() ) ); + } + else m_entry_mail.set_text( "sage" ); + + m_hbox_name_mail.pack_start( m_label_name, Gtk::PACK_SHRINK ); + m_hbox_name_mail.pack_start( m_check_fixname, Gtk::PACK_SHRINK ); + m_hbox_name_mail.pack_start( m_entry_name ); + m_hbox_name_mail.pack_start( m_label_mail, Gtk::PACK_SHRINK ); + m_hbox_name_mail.pack_start( m_check_fixmail, Gtk::PACK_SHRINK ); + m_hbox_name_mail.pack_start( m_entry_mail ); + + m_msgview.pack_start( m_hbox_name_mail, Gtk::PACK_SHRINK ); + m_msgview.pack_start( m_text_message ); + + m_text_message.sig_key_release().connect( sigc::mem_fun(*this, &MessageViewBase::slot_key_release ) ); + + // プレビュー + m_preview = CORE::ViewFactory( CORE::VIEW_ARTICLEPREVIEW, get_url() ); + + m_notebook.append_page( m_msgview, "Message" ); + m_notebook.append_page( *m_preview, "Preview" ); + m_notebook.signal_switch_page().connect( sigc::mem_fun( *this, &MessageViewBase::slot_switch_page ) ); + + pack_start( m_toolbar, Gtk::PACK_SHRINK ); + pack_start( m_notebook ); +} + + + +void MessageViewBase::focus_view() +{ + m_text_message.focus_view(); +} + + + +// +// viewの操作 +// +void MessageViewBase::operate_view( const int& control ) +{ + if( control == CONTROL::None ) return; + + switch( control ){ + + // 書き込まずに閉じる + case CONTROL::CancelWrite: + slot_cancel_clicked(); + break; + + // 書き込み実行 + case CONTROL::ExecWrite: + slot_write_clicked(); + break; + + case CONTROL::TabLeft: + tab_left(); + break; + + case CONTROL::TabRight: + tab_right(); + break; + } +} + + + +// +// 書き込むボタン押した +// +void MessageViewBase::slot_write_clicked() +{ +/* 書き込み確認(やってみたらうざかったので様子見) + + Gtk::Menu* popupmenu = dynamic_cast< Gtk::Menu* >( m_ui_manager->get_widget( "/popup_menu_write" ) ); + if( popupmenu ) popupmenu->popup( 0, gtk_get_current_event_time() ); +*/ + + if( m_post && m_post->is_loading() ){ + Gtk::MessageDialog mdiag( "書き込み中です" ); + mdiag.run(); + return; + } + + write(); +} + + +// +// キャンセルボタンを押した +// +void MessageViewBase::slot_cancel_clicked() +{ + close_view(); +} + + +// +// テキストビューのキー操作 +// +bool MessageViewBase::slot_key_release( GdkEventKey* event ) +{ +#ifdef _DEBUG + 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_release" + << " key = " << key + << " ctrl = " << ctrl + << " shift = " << shift + << " alt = " << alt << std::endl; +#endif + + operate_view( SKELETON::View::get_control().key_press( event ) ); + + return true; +} + + + +// +// 閉じる +// +void MessageViewBase::close_view() +{ +#ifdef _DEBUG + std::cout << "MessageViewBase::close_view\n"; +#endif + + MESSAGE::get_admin()->set_command( "close_currentview" ); +} + + + +// +// タブ左移動 +// +void MessageViewBase::tab_left() +{ + int page = m_notebook.get_current_page(); + if( page != 1 ) return; + + m_notebook.set_current_page( 0 ); + focus_view(); +} + + +// +// タブ右移動 +// +void MessageViewBase::tab_right() +{ + int page = m_notebook.get_current_page(); + if( page != 0 ) return; + + m_notebook.set_current_page( 1 ); + m_preview->focus_view(); +} + + + +// +// 書き込み +// +void MessageViewBase::post_msg( const std::string& msg, bool new_article ) +{ + if( m_post ) delete m_post; + m_post = new Post( this, get_url(), msg, new_article ); + m_post->sig_fin().connect( sigc::mem_fun( *this, &MessageViewBase::post_fin ) ); + m_post->post_msg(); +} + + + +// +// 書き込みが終わったら呼ばれる +// +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_REDIRECT && ! location.empty() ) // (まちBBSなどで)リダイレクトした場合 + ){ + m_text_message.set_text( std::string() ); + close_view(); + reload(); + } + + // タイムアウト + else if( code == HTTP_TIMEOUT ){ + + Gtk::MessageDialog mdiag( "タイムアウトしました\n\n書き込み自体は成功している可能性があります。\nメッセージのバックアップをとってからスレを再読み込みして下さい。" ); + mdiag.run(); + } + + // 失敗 + else if( code != HTTP_CANCEL ){ + + Gtk::MessageDialog mdiag( "書き込みに失敗しました\n\n" + m_post->errmsg(), false, Gtk::MESSAGE_ERROR ); + mdiag.run(); + } +} + + +// +// タブのページが切り替わったら呼ばれるslot +// +void MessageViewBase::slot_switch_page( GtkNotebookPage*, guint page ) +{ +#ifdef _DEBUG + std::cout << "MessageViewBase::slot_switch_page : " << get_url() << " page = " << page << std::endl; +#endif + + // プレビュー表示 + if( m_preview && page == 1 ){ + + std::string msg = m_text_message.get_text(); + msg = MISC::replace_str( msg, "<", "<" ); + msg = MISC::replace_str( msg, ">", ">" ); + msg = MISC::replace_str( msg, "\n", "
" ); + + std::stringstream ss; + + if( ! m_entry_name.get_text().empty() ) ss << m_entry_name.get_text(); + else ss << DBTREE::default_noname( get_url() ); + + ss << "<>" << m_entry_mail.get_text() << "<>"; + + struct timeval tv; + struct timezone tz; + gettimeofday( &tv, &tz ); + ss << MISC::timettostr( tv.tv_sec ); + + ss << " ID:???" << "<>" << msg << "<>\n"; + +#ifdef _DEBUG + std::cout << ss.str() << std::endl; +#endif + + m_preview->set_command( "append_dat", ss.str() ); + } + else MESSAGE::get_admin()->set_command( "focus_view" ); +} diff --git a/src/message/messageviewbase.h b/src/message/messageviewbase.h new file mode 100644 index 000000000..853cde083 --- /dev/null +++ b/src/message/messageviewbase.h @@ -0,0 +1,89 @@ +// ライセンス: 最新のGPL + +#ifndef _MESSAGEVIEWBASE_H +#define _MESSAGEVIEWBASE_H + +#include "skeleton/view.h" +#include "skeleton/editview.h" +#include "skeleton/imgbutton.h" + +namespace MESSAGE +{ + class Post; + + class MessageViewBase : public SKELETON::View + { + Post* m_post; + + Gtk::Notebook m_notebook; + SKELETON::View* m_preview; + Gtk::VBox m_msgview; + + Gtk::HBox m_hbox_name_mail; + Gtk::Label m_label_name; + Gtk::Label m_label_mail; + Gtk::CheckButton m_check_fixname; + Gtk::CheckButton m_check_fixmail; + + Gtk::HBox m_toolbar; + Gtk::Label m_label_board; + SKELETON::ImgButton m_button_write; + SKELETON::ImgButton m_button_cancel; + Gtk::Tooltips m_tooltip; + + Gtk::Entry m_entry_subject; + Gtk::Entry m_entry_name; + Gtk::Entry m_entry_mail; + SKELETON::EditView m_text_message; + + protected: + + Gtk::Entry& get_entry_subject() { return m_entry_subject; } + Gtk::Entry& get_entry_name(){ return m_entry_name; } + Gtk::Entry& get_entry_mail(){ return m_entry_mail; } + SKELETON::EditView& get_text_message() { return m_text_message; } + + public: + + MessageViewBase( const std::string& url ); + ~MessageViewBase(); + + // コマンド + virtual bool set_command( const std::string& command, const std::string& arg = std::string() ); + + // SKELETON::View の関数のオーバロード + virtual void reload(){} + virtual void close_view(); + virtual void focus_view(); + virtual void operate_view( const int& control ); + + void post_fin(); + + private: + + virtual void write(){}; + + void tab_left(); + void tab_right(); + + void slot_write_clicked(); + void slot_cancel_clicked(); + bool slot_key_release( GdkEventKey* event ); + virtual void slot_switch_page( GtkNotebookPage*, guint page ); + + virtual std::string create_message(){ return std::string() ;} + + protected: + + void set_message( const std::string& msg ){ m_text_message.set_text( msg ); } + Glib::ustring get_message(){ return m_text_message.get_text(); } + + void post_msg( const std::string& msg, bool new_article ); + + void save_name(); + virtual void setup_view(); + virtual void pack_widget(); + }; +} + +#endif diff --git a/src/message/messagewin.cpp b/src/message/messagewin.cpp new file mode 100644 index 000000000..7c97fc949 --- /dev/null +++ b/src/message/messagewin.cpp @@ -0,0 +1,90 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "messageadmin.h" +#include "messagewin.h" + +#include "session.h" +#include "command.h" + +using namespace MESSAGE; + + +MessageWin::MessageWin() +{ + // サイズ設定 + int x = SESSION::mes_x(); + int y = SESSION::mes_y(); + int w = SESSION::mes_width(); + int h = SESSION::mes_height(); + m_maximized = SESSION::mes_maximized(); + +#ifdef _DEBUG + std::cout << "MessageWin::MessageWin x y w h = " << x << " " << y << " " << w << " " << h << std::endl; +#endif + + resize( w, h ); + move( x, y ); + if( m_maximized ) maximize(); + + property_window_position().set_value( Gtk::WIN_POS_NONE ); + if( CORE::get_toplevel() ) set_transient_for( *dynamic_cast< Gtk::Window* >( CORE::get_toplevel() ) ); +} + + + +MessageWin::~MessageWin() +{ +#ifdef _DEBUG + std::cout << "MessageWin::~MessageWin\n"; +#endif + + // ウィンドウサイズを保存 + int width, height;; + int x = 0; + int y = 0; + get_size( width, height ); + if( get_window() ) get_window()->get_root_origin( x, y ); + +#ifdef _DEBUG + std::cout << "window size : x = " << x << " y = " << y << " w = " << width << " h = " << height + << " max = " << m_maximized << std::endl; +#endif + + if( !m_maximized ){ + SESSION::set_mes_x( x ); + SESSION::set_mes_y( y ); + SESSION::set_mes_width( width ); + SESSION::set_mes_height( height ); + } + SESSION::set_mes_maximized( m_maximized ); +} + + + + +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; +} + + + +bool MessageWin::on_window_state_event( GdkEventWindowState* event ) +{ + m_maximized = event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED; + +#ifdef _DEBUG + std::cout << "MessageWin::on_window_state_event : maximized = " << m_maximized << std::endl; +#endif + + return Gtk::Window::on_window_state_event( event ); +} diff --git a/src/message/messagewin.h b/src/message/messagewin.h new file mode 100644 index 000000000..b732307b0 --- /dev/null +++ b/src/message/messagewin.h @@ -0,0 +1,25 @@ +// ライセンス: 最新のGPL + +#ifndef _MESSAGEWIN_H +#define _MESSAGEWIN_H + +#include + +namespace MESSAGE +{ + class MessageWin : public Gtk::Window + { + bool m_maximized; + + public: + MessageWin(); + ~MessageWin(); + + protected: + virtual bool on_delete_event( GdkEventAny* event ); + virtual bool on_window_state_event( GdkEventWindowState* event ); + }; +} + + +#endif diff --git a/src/message/post.cpp b/src/message/post.cpp new file mode 100644 index 000000000..4c2b003ff --- /dev/null +++ b/src/message/post.cpp @@ -0,0 +1,312 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "post.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" + + +using namespace MESSAGE; + +#define 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_rawdata( 0 ), + m_lng_rawdata( 0 ), + m_count( 0 ), + m_subbbs( 0 ), + m_new_article( new_article ), + m_writingdiag( 0 ) +{ + clear(); +} + + + +Post::~Post() +{ +#ifdef _DEBUG + std::cout << "Post::~Post " << m_url << std::endl; +#endif + + clear(); +} + + + +void Post::clear() +{ + if( m_rawdata ) free( m_rawdata ); + m_rawdata = NULL; + + if( m_writingdiag ) delete m_writingdiag; + m_writingdiag = NULL; +} + + +// +// ポスト実行 +// +void Post::post_msg() +{ + if( is_loading() ) return; + + clear(); + m_rawdata = ( char* )malloc( SIZE_OF_RAWDATA ); + m_lng_rawdata = 0; + + // 書き込み中ダイアログ表示 + Gtk::Window* toplevel = dynamic_cast< Gtk::Window* >( m_parent->get_toplevel() ); + if( toplevel ){ + m_writingdiag = new Gtk::MessageDialog( *toplevel, "書き込み中・・・", false, Gtk::MESSAGE_INFO, Gtk::BUTTONS_NONE, false ); + m_writingdiag->show(); + } + + JDLIB::LOADERDATA data; + + // 通常書き込み + if( ! m_new_article ){ + if( ! m_subbbs ) data.url = DBTREE::url_bbscgi( m_url ); // 1回目の投稿先 + else data.url = DBTREE::url_subbbscgi( m_url ); // 2回目の投稿先 + } + + // 新スレ作成 + else{ + if( ! m_subbbs ) data.url = DBTREE::url_bbscgi_new( m_url ); // 1回目の投稿先 + else data.url = DBTREE::url_subbbscgi_new( m_url ); // 2回目の投稿先 + } + + data.agent = DBTREE::get_agent( m_url ); + data.referer = DBTREE::url_boardbase( m_url ); + data.cookie = DBTREE::board_cookie_for_write( 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.size_buf = CONFIG::get_loader_bufsize(); + data.timeout = CONFIG::get_loader_timeout_post(); + +#ifdef _DEBUG + std::cout << "Post::post_msg : " << std::endl + << "url = " << data.url << std::endl + << "agent = " << data.agent << std::endl + << "referer = " << data.referer << std::endl + << "cookie = " << data.cookie << 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; + + memcpy( m_rawdata + m_lng_rawdata , data, size ); + m_lng_rawdata += size; + m_rawdata[ m_lng_rawdata ] = '\0'; +} + + + +// +// ローダがsendを終了したので戻り値解析 +// +void Post::receive_finish() +{ +#ifdef _DEBUG + std::cout << "Post::receive_finish\n"; +#endif + + std::string charset = DBTREE::board_charset( m_url ); + JDLIB::Iconv* libiconv = new JDLIB::Iconv( charset ); + int byte_out; + std::string str = libiconv->convert( m_rawdata, m_lng_rawdata, byte_out ); + delete libiconv; + +#ifdef _DEBUG + std::cout << "code = " << get_code() << std::endl; + std::cout << str << std::endl; +#endif + + clear(); + + /////////////////// + + // ポスト失敗 + if( get_code() != HTTP_OK + && ! ( get_code() == HTTP_REDIRECT && ! location().empty() ) // リダイレクトは成功(かもしれない) + ){ + m_errmsg = get_str_code(); + m_sig_fin.emit(); + return; + } + + // 以下、code == 200 or 302 で locationがセットされている(リダイレクト) の場合 + + JDLIB::Regex regex; + + std::string title; + std::string tag_2ch; + std::string msg; + std::string hana; + std::string conf; + + regex.exec( ".*([^<]*).*", str, 0, true ); + title = MISC::remove_space( regex.str( 1 ) ); + + regex.exec( ".*([^<]*).*", str ); + m_errmsg = MISC::remove_space( regex.str( 1 ) ); + + regex.exec( ".*2ch_X:([^\\-]*)\\-\\->.*", str ); + tag_2ch = MISC::remove_space( regex.str( 1 ) ); + + regex.exec( ".*([^<]*).*", str ); + conf = MISC::remove_space( regex.str( 1 ) ); + + regex.exec( ".*.*(.*).*", "\n" ); + Gtk::MessageDialog* mdiag = new Gtk::MessageDialog( diagmsg, false, + Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_NONE, false ); + Gtk::CheckButton ckbt( "次回から表示しない(_D)", true ); + mdiag->get_vbox()->pack_start( ckbt, Gtk::PACK_SHRINK ); + mdiag->add_button( Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL ); + + // OKボタンをデフォルトにする + Gtk::Button okbt( Gtk::Stock::OK ); + mdiag->add_action_widget( okbt, Gtk::RESPONSE_OK ); + okbt.set_flags( Gtk::CAN_DEFAULT ); + okbt.grab_default(); + okbt.grab_focus(); + + mdiag->show_all_children(); + + int ret = mdiag->run(); + bool ckbt_active = ckbt.get_active(); + delete mdiag; + + if( ret != Gtk::RESPONSE_OK ){ + set_code( HTTP_CANCEL ); + m_sig_fin.emit(); + return; + } + CONFIG::set_always_write_ok( ckbt_active ); + } + + set_cookie_hana( cookie(), hana ); + + ++m_count; // 永久ループ防止 + post_msg(); + + return; + } + + // スレ立て時の書き込み確認 + else if( m_count < 2 + && ! m_subbbs && conf.find( "書き込み確認" ) != std::string::npos ){ + + set_cookie_hana( cookie(), hana ); + + // subbbs.cgi にポスト先を変更してもう一回ポスト + m_subbbs = true; + ++m_count; // 永久ループ防止 + post_msg(); + return; + } + + // その他エラー + else{ + +#ifdef _DEBUG + std::cout << "Error" << std::endl; + std::cout << m_errmsg << std::endl; +#endif + set_code( HTTP_ERR ); + m_sig_fin.emit(); + return; + } +} + + + +// +// データベースにクッキーとhanaを登録 +// +void Post::set_cookie_hana( const std::string& cookie, const std::string& hana ) +{ + if( DBTREE::board_cookie_for_write( m_url ).empty() + && ! cookie.empty() ) DBTREE::board_set_cookie_for_write( m_url, cookie ); + + if( DBTREE::board_hana_for_write( m_url ).empty() + && ! hana.empty() ){ + DBTREE::board_set_hana_for_write( m_url, hana ); + if( m_msg.find( "hana=" ) == std::string::npos ) m_msg += "&hana=" + hana; + } +} + diff --git a/src/message/post.h b/src/message/post.h new file mode 100644 index 000000000..c3787e43e --- /dev/null +++ b/src/message/post.h @@ -0,0 +1,60 @@ +// ライセンス: 最新のGPL + +// +// 記事投稿クラス +// + +#ifndef _POST_H +#define _POST_H + +#include "skeleton/loadable.h" + +#include +#include + + +namespace MESSAGE +{ + 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_errmsg; + char* m_rawdata; + size_t m_lng_rawdata; + + int m_count; // 書き込み確認時の永久ループ防止用 + bool m_subbbs; // true なら subbbs.cgiにpostする + bool m_new_article; // 新スレ作成 + + // 書き込んでいますのダイアログ + Gtk::MessageDialog* m_writingdiag; + + 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& errmsg() const { return m_errmsg; } + + void post_msg(); + + private: + void clear(); + + virtual void receive_data( const char* data, size_t size ); + virtual void receive_finish(); + + void set_cookie_hana( const std::string& cookie, const std::string& hana ); + }; + +} + +#endif diff --git a/src/passwdpref.h b/src/passwdpref.h new file mode 100644 index 000000000..ed981a70c --- /dev/null +++ b/src/passwdpref.h @@ -0,0 +1,88 @@ +// ライセンス: 最新のGPL + +// パスワード設定ダイアログ + +#ifndef _PASSWDPREF_H +#define _PASSWDPREF_H + +#include "skeleton/prefdiag.h" +#include "skeleton/label_entry.h" + +#include "login2ch.h" +#include "jdlib/miscutil.h" + +namespace CORE +{ + class PasswdFrame : public Gtk::Frame + { + Gtk::VBox m_vbox; + Gtk::HBox m_hbox; + Gtk::Label m_label_id; + Gtk::Label m_label_passwd; + + public: + + Gtk::Entry entry_id; + Gtk::Entry entry_passwd; + + PasswdFrame( const std::string& title ) + : m_label_id( "ID" ), m_label_passwd( "password" ) + { + m_hbox.set_spacing( 8 ); + + m_hbox.pack_start( m_label_id, Gtk::PACK_SHRINK ); + m_hbox.pack_start( entry_id ); + + m_hbox.pack_start( m_label_passwd, Gtk::PACK_SHRINK ); + m_hbox.pack_start( entry_passwd, Gtk::PACK_SHRINK ); + entry_passwd.set_visibility( false ); + + 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 PasswdPref : public SKELETON::PrefDiag + { + // 2chログイン用 + PasswdFrame m_frame_2ch; + SKELETON::LabelEntry m_label_sid_2ch; + + // OK押した + virtual void slot_ok_clicked(){ + + // 2ch + LOGIN::get_login2ch()->set_username( MISC::remove_space( m_frame_2ch.entry_id.get_text() ) ); + LOGIN::get_login2ch()->set_passwd( MISC::remove_space( m_frame_2ch.entry_passwd.get_text() ) ); + } + + public: + + PasswdPref( const std::string& url ) + : SKELETON::PrefDiag( url ) + , m_frame_2ch( "2chログイン用" ) + , m_label_sid_2ch( "SID : ", LOGIN::get_login2ch()->get_sessionid() ) + { + // 2ch用 + m_frame_2ch.entry_id.set_text( LOGIN::get_login2ch()->get_username() ); + m_frame_2ch.entry_passwd.set_text( LOGIN::get_login2ch()->get_passwd() ); + + get_vbox()->set_spacing( 4 ); + get_vbox()->pack_start( m_frame_2ch ); + get_vbox()->pack_start( m_label_sid_2ch ); + + set_title( "パスワード設定" ); + show_all_children(); + } + + virtual ~PasswdPref(){} + }; + +} + +#endif diff --git a/src/proxypref.h b/src/proxypref.h new file mode 100644 index 000000000..6c2b11498 --- /dev/null +++ b/src/proxypref.h @@ -0,0 +1,122 @@ +// ライセンス: 最新のGPL + +// プロキシ設定ダイアログ + +#ifndef _PROXYPREF_H +#define _PROXYPREF_H + +#include "skeleton/prefdiag.h" + +#include "config/globalconf.h" + +#include "jdlib/miscutil.h" + +namespace CORE +{ + class ProxyFrame : public Gtk::Frame + { + Gtk::VBox m_vbox; + Gtk::HBox m_hbox; + Gtk::Label m_label_host; + Gtk::Label m_label_port; + + public: + + Gtk::CheckButton ckbt; + Gtk::Entry entry_host; + Gtk::Entry entry_port; + + ProxyFrame( const std::string& title ) + : m_label_host( "host" ), m_label_port( "port" ), ckbt( "使用する" ) + { + m_hbox.set_spacing( 8 ); + m_hbox.pack_start( ckbt, Gtk::PACK_SHRINK ); + + m_hbox.pack_start( m_label_host, Gtk::PACK_SHRINK ); + m_hbox.pack_start( entry_host ); + + m_hbox.pack_start( m_label_port, Gtk::PACK_SHRINK ); + 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 + { + // 2ch読み込み用 + ProxyFrame m_frame_2ch; + + // 2ch書き込み用 + ProxyFrame m_frame_2ch_w; + + // 一般用 + ProxyFrame m_frame_data; + + // OK押した + virtual void slot_ok_clicked(){ + + // 2ch + if( m_frame_2ch.ckbt.get_active() ) CONFIG::set_use_proxy_for2ch( true ); + else CONFIG::set_use_proxy_for2ch( false ); + 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書き込み用 + if( m_frame_2ch_w.ckbt.get_active() ) CONFIG::set_use_proxy_for2ch_w( true ); + else CONFIG::set_use_proxy_for2ch_w( false ); + 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() ) ); + + // 一般 + if( m_frame_data.ckbt.get_active() ) CONFIG::set_use_proxy_for_data( true ); + else CONFIG::set_use_proxy_for_data( false ); + 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( const std::string& url ) + : SKELETON::PrefDiag( url ) + , m_frame_2ch( "2ch読み込み用" ), m_frame_2ch_w( "2ch書き込み用" ), m_frame_data( "その他のサーバ用" ) + { + // 2ch用 + if( CONFIG::get_use_proxy_for2ch() ) m_frame_2ch.ckbt.set_active( true ); + else m_frame_2ch.ckbt.set_active( false ); + m_frame_2ch.entry_host.set_text( CONFIG::get_proxy_for2ch() ); + m_frame_2ch.entry_port.set_text( MISC::itostr( CONFIG::get_proxy_port_for2ch() ) ); + + // 2ch書き込み用 + if( CONFIG::get_use_proxy_for2ch_w() ) m_frame_2ch_w.ckbt.set_active( true ); + else m_frame_2ch_w.ckbt.set_active( false ); + m_frame_2ch_w.entry_host.set_text( CONFIG::get_proxy_for2ch_w() ); + m_frame_2ch_w.entry_port.set_text( MISC::itostr( CONFIG::get_proxy_port_for2ch_w() ) ); + + // 一般用 + if( CONFIG::get_use_proxy_for_data() ) m_frame_data.ckbt.set_active( true ); + else m_frame_data.ckbt.set_active( false ); + m_frame_data.entry_host.set_text( CONFIG::get_proxy_for_data() ); + m_frame_data.entry_port.set_text( MISC::itostr( CONFIG::get_proxy_port_for_data() ) ); + + get_vbox()->set_spacing( 4 ); + get_vbox()->pack_start( m_frame_2ch ); + get_vbox()->pack_start( m_frame_2ch_w ); + get_vbox()->pack_start( m_frame_data ); + + set_title( "プロキシ設定" ); + show_all_children(); + } + + virtual ~ProxyPref(){} + }; + +} + +#endif diff --git a/src/session.cpp b/src/session.cpp new file mode 100644 index 000000000..11317531a --- /dev/null +++ b/src/session.cpp @@ -0,0 +1,359 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "session.h" +#include "cache.h" + +#include "jdlib/confloader.h" +#include "jdlib/miscutil.h" + +#include + +int mode_pane; +bool mode_online; + +int win_x; +int win_y; +int win_width; +int win_height; +bool win_maximized; + +int win_hpane_main_pos; +int win_vpane_main_pos; +int win_hpane_main_r_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< std::string > article_urls; +std::list< std::string > image_urls; + +int board_col_mark; +int board_col_id; +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_speed; + +bool win_show_urlbar; + +int win_mes_x; +int win_mes_y; +int win_mes_width; +int win_mes_height; +bool win_mes_maximized; + +std::string img_dir_img_save; + +///////////////////////////////////// + + +// セッション情報読み込み +void SESSION::init_session() +{ +#ifdef _DEBUG + std::cout << "SESSION::init_session\n"; +#endif + std::string str_tmp; + std::list< std::string > list_tmp; + std::list< std::string >::iterator it_tmp; + + JDLIB::ConfLoader cf( CACHE::path_session(), std::string() ); + + // オンライン + mode_online = cf.get_option( "mode_online", true ); + + // paneのモード + mode_pane = cf.get_option( "mode_pane", 0 ); + + win_x = cf.get_option( "x", 0 ); + win_y = cf.get_option( "y", 0 ); + win_width = cf.get_option( "width", 800 ); + win_height = cf.get_option( "height", 600 ); + win_maximized = cf.get_option( "maximized", false ); + + win_show_urlbar = cf.get_option( "show_urlbar", true ); + + win_hpane_main_pos = cf.get_option( "hpane_main_pos", 190 ); + win_vpane_main_pos = cf.get_option( "vpane_main_pos", 200 ); + win_hpane_main_r_pos = cf.get_option( "hpane_main_r_pos", 300 ); + + win_notebook_main_page = cf.get_option( "notebook_main_page", 0 ); + + win_bbslist_page = cf.get_option( "bbslist_page", 0 ); + win_board_page = cf.get_option( "board_page", 0 ); + win_article_page = cf.get_option( "article_page", 0 ); + win_image_page = cf.get_option( "image_page", 0 ); + + str_tmp = cf.get_option( "board_urls", ""); + if( ! str_tmp.empty() ){ + list_tmp = MISC::split_line( str_tmp ); + it_tmp = list_tmp.begin(); + for( ; it_tmp != list_tmp.end(); ++it_tmp ) if( !(*it_tmp).empty() ) board_urls.push_back( (*it_tmp)); + } + + str_tmp = cf.get_option( "article_urls", ""); + if( ! str_tmp.empty() ){ + list_tmp = MISC::split_line( str_tmp ); + it_tmp = list_tmp.begin(); + for( ; it_tmp != list_tmp.end(); ++it_tmp ) if( !(*it_tmp).empty() ) article_urls.push_back( (*it_tmp)); + } + + str_tmp = cf.get_option( "image_urls", ""); + if( ! str_tmp.empty() ){ + list_tmp = MISC::split_line( str_tmp ); + it_tmp = list_tmp.begin(); + for( ; it_tmp != list_tmp.end(); ++it_tmp ) if( !(*it_tmp).empty() ) image_urls.push_back( (*it_tmp)); + } + + board_col_mark = cf.get_option( "col_mark", 30 ); + board_col_id = cf.get_option( "col_id", 30 ); + board_col_subject = cf.get_option( "col_subject", 350 ); + board_col_number = cf.get_option( "col_number", 50 ); + board_col_load = cf.get_option( "col_load", 50 ); + board_col_new = cf.get_option( "col_new", 50 ); + board_col_since = cf.get_option( "col_since", 100 ); + board_col_write = cf.get_option( "col_write", 100 ); + board_col_speed = cf.get_option( "col_speed", 50 ); + + win_mes_x = cf.get_option( "mes_x", 0 ); + win_mes_y = cf.get_option( "mes_y", 0 ); + win_mes_width = cf.get_option( "mes_width", 300 ); + win_mes_height = cf.get_option( "mes_height", 300 ); + win_mes_maximized = cf.get_option( "mes_maximized", false ); + + img_dir_img_save = cf.get_option( "img_dir_img_save", "" ); + +#ifdef _DEBUG + std::cout << "x=" << win_x << std::endl + << "y=" << win_y << std::endl + << "w=" << win_width << std::endl + << "h=" << win_height << std::endl + << "m=" << win_maximized << std::endl + << "urlbar=" << win_show_urlbar << 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 + + << "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"; + it_tmp = board_urls.begin(); + for( ; it_tmp != board_urls.end(); ++it_tmp ) if( !(*it_tmp).empty() ) std::cout << (*it_tmp); + + std::cout << "article_urls\n"; + it_tmp = article_urls.begin(); + for( ; it_tmp != article_urls.end(); ++it_tmp ) if( !(*it_tmp).empty() ) std::cout << (*it_tmp); + + std::cout << "image_urls\n"; + it_tmp = image_urls.begin(); + for( ; it_tmp != image_urls.end(); ++it_tmp ) if( !(*it_tmp).empty() ) std::cout << (*it_tmp); + + std::cout << "columns\n" + << board_col_mark << std::endl + << board_col_id << 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 + + << "wx=" << win_mes_x << std::endl + << "wy=" << win_mes_y << std::endl + << "ww=" << win_mes_width << std::endl + << "wh=" << win_mes_height << std::endl + << "wm=" << win_mes_maximized << std::endl; +#endif +} + + + +// セッション情報保存 +void SESSION::save_session() +{ + std::string str_board_urls; + std::string str_article_urls; + std::string str_image_urls; + + std::list< std::string >::iterator it = board_urls.begin(); + for( ; it != board_urls.end(); ++it ){ + if( ! ( *it ).empty() ) str_board_urls += " \"" + ( *it ) + "\""; + } + it = article_urls.begin(); + for( ; it != article_urls.end(); ++it ){ + if( ! ( *it ).empty() ) str_article_urls += " \"" + ( *it ) + "\""; + } + it = image_urls.begin(); + for( ; it != image_urls.end(); ++it ){ + if( ! ( *it ).empty() ) str_image_urls += " \"" + ( *it ) + "\""; + } + + std::ostringstream oss; + oss << "mode_pane = " << mode_pane << std::endl + << "mode_online = " << mode_online << std::endl + << "x = " << win_x << std::endl + << "y = " << win_y << std::endl + << "width = " << win_width << std::endl + << "height = " << win_height << std::endl + << "maximized = " << win_maximized << std::endl + << "show_urlbar = " << win_show_urlbar << 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 + + << "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 + + << "board_urls = " << str_board_urls << std::endl + << "article_urls = " << str_article_urls << std::endl + << "image_urls = " << str_image_urls << std::endl + + << "col_mark = " << board_col_mark << std::endl + << "col_id = " << board_col_id << 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_speed = " << board_col_speed << std::endl + + << "mes_x = " << win_mes_x << std::endl + << "mes_y = " << win_mes_y << std::endl + << "mes_width = " << win_mes_width << std::endl + << "mes_height = " << win_mes_height << std::endl + << "mes_maximized = " << win_mes_maximized << std::endl + + << "img_dir_img_save = " << img_dir_img_save << std::endl; + + CACHE::save_rawdata( CACHE::path_session(), oss.str() ); + +#ifdef _DEBUG + std::cout << "SESSION::save_session\n" << oss.str() << std::endl; +#endif + +} + + +const int SESSION::get_mode_pane() { return mode_pane; } +void SESSION::set_mode_pane( int mode ){ mode_pane = mode; } + +const bool SESSION::is_online(){ return mode_online; } +void SESSION::set_online( bool mode ){ mode_online = mode; } + +int SESSION::x(){ return win_x; } +int SESSION::y(){ return win_y; } +int SESSION::width(){ return win_width; } +int SESSION::height(){ return win_height; } +bool SESSION::maximized(){ return win_maximized; } +bool SESSION::show_urlbar(){ return win_show_urlbar; } + +void SESSION::set_x( int x ){ win_x = x; } +void SESSION::set_y( int y ){ win_y = y; } +void SESSION::set_width( int width ){ win_width = width; } +void SESSION::set_height( int height ){ win_height = height; } +void SESSION::set_maximized( bool maximized ){ win_maximized = maximized; } +void SESSION::set_show_urlbar( bool showurl ){ win_show_urlbar = 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; } + +void SESSION::set_hpane_main_pos( int pos ){ win_hpane_main_pos = pos; } +void SESSION::set_vpane_main_pos( int pos ){ win_vpane_main_pos = pos; } +void SESSION::set_hpane_main_r_pos( int pos ){ win_hpane_main_r_pos = pos; } + +// メインnotebookのページ番号 +int SESSION::notebook_main_page(){ return win_notebook_main_page; } +void SESSION::set_notebook_main_page( int page ){ win_notebook_main_page = page; } + +// bbslistの開いてるページ番号 +int SESSION::bbslist_page(){ return win_bbslist_page; } +void SESSION::set_bbslist_page( int page ){ win_bbslist_page = page; } + + +// 前回閉じたときに開いていたboardのページ番号とURL +int SESSION::board_page(){ return win_board_page; } +void SESSION::set_board_page( int page ){ win_board_page = page; } +const std::list< std::string >& SESSION::board_URLs(){ return board_urls; } +void SESSION::set_board_URLs( const std::list< std::string >& urls ){ board_urls = urls; } + + +// 前回閉じたときに開いていたarticleのページ番号とURL +int SESSION::article_page(){ return win_article_page; } +void SESSION::set_article_page( int page ){ win_article_page = page; } +const std::list< std::string >& SESSION::article_URLs(){ return article_urls; } +void SESSION::set_article_URLs( const std::list< std::string >& urls ){ article_urls = urls; } + + +// 前回閉じたときに開いていたimageのページ番号とURL +int SESSION::image_page(){ return win_image_page; } +void SESSION::set_image_page( 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; } + + +// board ビューの列幅 +int SESSION::col_mark(){ return board_col_mark; } +int SESSION::col_id(){ return board_col_id; } +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_speed(){ return board_col_speed; } + +void SESSION::set_col_mark( int width ){ board_col_mark = width; } +void SESSION::set_col_id( int width ){ board_col_id = width; } +void SESSION::set_col_subject( int width ){ board_col_subject = width; } +void SESSION::set_col_number( int width ){ board_col_number = width; } +void SESSION::set_col_load( int width ){ board_col_load = width; } +void SESSION::set_col_new( int width ){ board_col_new = width; } +void SESSION::set_col_since( int width ){ board_col_since = width; } +void SESSION::set_col_write( int width ){ board_col_write = width; } +void SESSION::set_col_speed( int width ){ board_col_speed = width; } + + +// message ウィンドウの位置 +int SESSION::mes_x(){ return win_mes_x; } +int SESSION::mes_y(){ return win_mes_y; } +int SESSION::mes_width(){ return win_mes_width; } +int SESSION::mes_height(){ return win_mes_height; } +bool SESSION::mes_maximized(){ return win_mes_maximized; } + +void SESSION::set_mes_x( int x ){ win_mes_x = x; } +void SESSION::set_mes_y( int y ){ win_mes_y = y; } +void SESSION::set_mes_width( int width ){ win_mes_width = width; } +void SESSION::set_mes_height( int height ){ win_mes_height = height; } +void SESSION::set_mes_maximized( bool maximized ){ win_mes_maximized = maximized; } + + +// 最後に画像を保存したディレクトリ +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; } diff --git a/src/session.h b/src/session.h new file mode 100644 index 000000000..ef47cf019 --- /dev/null +++ b/src/session.h @@ -0,0 +1,110 @@ +// ライセンス: 最新のGPL +// +// 座標などのウィンドウ情報とかのセッション情報 +// + +#ifndef _SESSION_H +#define _SESSION_H + +#include + +namespace SESSION +{ + void init_session(); + void save_session(); + + const int get_mode_pane(); + void set_mode_pane( int mode ); + + const bool is_online(); + void set_online( bool mode ); + + int x(); + int y(); + int width(); + int height(); + bool maximized(); + bool show_urlbar(); + + void set_x( int x ); + void set_y( int y ); + void set_width( int width ); + void set_height( int height ); + void set_maximized( bool maximized ); + void set_show_urlbar( bool showbar ); + + /// メインウィンドウのペインの敷居の位置 + int hpane_main_pos(); + int vpane_main_pos(); + int hpane_main_r_pos(); + void set_hpane_main_pos( int pos ); + void set_vpane_main_pos( int pos ); + void set_hpane_main_r_pos( int pos ); + + // メインnotebookのページ番号 + int notebook_main_page(); + void set_notebook_main_page( int page ); + + // bbslistの開いてるページ番号 + int bbslist_page(); + void set_bbslist_page( int page ); + + + // 前回閉じたときに開いていたboardのページ番号とURL + int board_page(); + void set_board_page( int page ); + const std::list< std::string >& board_URLs(); + void set_board_URLs( const std::list< std::string >& urls ); + + // 前回閉じたときに開いていたarticleのページ番号とURL + int article_page(); + void set_article_page( int page ); + const std::list< std::string >& article_URLs(); + void set_article_URLs( const std::list< std::string >& urls ); + + + // 前回閉じたときに開いていたimageのページ番号とURL + int image_page(); + void set_image_page( int page ); + const std::list< std::string >& image_URLs(); + void set_image_URLs( const std::list< std::string >& urls ); + + + // board ビューの列幅 + int col_mark(); + int col_id(); + int col_subject(); + int col_number(); + int col_load(); + int col_new(); + int col_since(); + int col_write(); + int col_speed(); + void set_col_mark( int width ); + void set_col_id( int width ); + void set_col_subject( int width ); + void set_col_number( int width ); + void set_col_load( int width ); + void set_col_new( int width ); + void set_col_since( int width ); + void set_col_write( int width ); + void set_col_speed( int width ); + + // message ウィンドウの位置 + int mes_x(); + int mes_y(); + int mes_width(); + int mes_height(); + bool mes_maximized(); + void set_mes_x( int x ); + void set_mes_y( int y ); + void set_mes_width( int width ); + void set_mes_height( int height ); + void set_mes_maximized( bool maximized ); + + // 最後に画像を保存したディレクトリ + const std::string& dir_img_save(); + void set_dir_img_save( const std::string& dir ); +} + +#endif diff --git a/src/sharedbuffer.cpp b/src/sharedbuffer.cpp new file mode 100644 index 000000000..aa46165d2 --- /dev/null +++ b/src/sharedbuffer.cpp @@ -0,0 +1,29 @@ +// ライセンス: 最新のGPL + +#include "sharedbuffer.h" + +std::list< CORE::DATA_INFO > shared_infolist; + + +int CORE::SBUF_size() +{ + return shared_infolist.size(); +} + + +void CORE:: SBUF_clear_info() +{ + shared_infolist.clear(); +} + + +void CORE::SBUF_append( const DATA_INFO& info ) +{ + shared_infolist.push_back( info ); +} + + +const std::list< CORE::DATA_INFO >& CORE::SBUF_infolist() +{ + return shared_infolist; +} diff --git a/src/sharedbuffer.h b/src/sharedbuffer.h new file mode 100644 index 000000000..0f892b613 --- /dev/null +++ b/src/sharedbuffer.h @@ -0,0 +1,36 @@ +// ライセンス: 最新のGPL + +// +// 共有バッファ +// +// クラス間で情報をやり取りするときに使う +// + +#ifndef _SHAREDBUFFER_H +#define _SHAREDBUFFER_H + +#include +#include + +namespace CORE +{ + // やりとりするデータ + // リスト型で複数保存可能 + struct DATA_INFO + { + int type; // global.hで定義しているデータタイプ + std::string url; + std::string name; + + // ユーザー定義 + std::string user1; + std::string user2; + }; + + int SBUF_size(); + void SBUF_clear_info(); + void SBUF_append( const DATA_INFO& info ); + const std::list< DATA_INFO >& SBUF_infolist(); +} + +#endif diff --git a/src/skeleton/Makefile.am b/src/skeleton/Makefile.am new file mode 100644 index 000000000..69f43553f --- /dev/null +++ b/src/skeleton/Makefile.am @@ -0,0 +1,31 @@ +noinst_LIBRARIES = libskeleton.a + +libskeleton_a_SOURCES = \ + admin.cpp \ + loadable.cpp \ + tooltip.cpp \ + treeview.cpp \ + view.cpp \ + dragnote.cpp \ + entry.cpp \ + login.cpp + +noinst_HEADERS = \ + admin.h \ + loadable.h \ + lockable.h \ + tooltip.h \ + treeview.h \ + view.h \ + editview.h \ + dragnote.h \ + tablabel.h \ + imgbutton.h \ + popupwin.h \ + entry.h \ + label_entry.h \ + prefdiag.h \ + login.h + +AM_CXXFLAGS = @GTKMM_CFLAGS@ +INCLUDES = -I$(top_srcdir)/src diff --git a/src/skeleton/admin.cpp b/src/skeleton/admin.cpp new file mode 100644 index 000000000..996d0049b --- /dev/null +++ b/src/skeleton/admin.cpp @@ -0,0 +1,968 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "admin.h" +#include "view.h" + +#include "dbtree/interface.h" + +#include "command.h" +#include "global.h" +#include "controlutil.h" +#include "controlid.h" + + +using namespace SKELETON; + + +Admin::Admin( const std::string& url ) + : m_url( url ) + , m_focus( false ) + , m_adjust_reserve( false ) + , m_pre_width( -1 ) +{ + m_disp.connect( sigc::mem_fun( *this, &Admin::exec_command ) ); + + m_notebook.signal_switch_page().connect( sigc::mem_fun( *this, &Admin::slot_switch_page ) ); + m_notebook.set_scrollable( true ); + + 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 ) ); + + // D&D + m_notebook.sig_drag_begin().connect( sigc::mem_fun(*this, &Admin::slot_drag_begin ) ); + m_notebook.sig_drag_motion().connect( sigc::mem_fun(*this, &Admin::slot_drag_motion ) ); + m_notebook.sig_drag_drop().connect( sigc::mem_fun(*this, &Admin::slot_drag_drop ) ); + m_notebook.sig_drag_end().connect( sigc::mem_fun(*this, &Admin::slot_drag_end ) ); + + m_list_command.clear(); + + // 右クリックメニュー + 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::Action::create( "CloseOther_Menu", "他のタブを閉じる" ) ); + m_action_group->add( Gtk::Action::create( "CloseOther", "閉じる" ), sigc::mem_fun( *this, &Admin::slot_close_other_tabs ) ); + m_action_group->add( Gtk::Action::create( "CloseAll_Menu", "全てのタブを閉じる" ) ); + m_action_group->add( Gtk::Action::create( "CloseAll", "閉じる" ), sigc::mem_fun( *this, &Admin::slot_close_all_tabs ) ); + m_action_group->add( Gtk::Action::create( "OpenBrowser", "ブラウザで開く" ), sigc::mem_fun( *this, &Admin::slot_open_by_browser ) ); + m_action_group->add( Gtk::Action::create( "CopyURL", "URLをコピー" ), sigc::mem_fun( *this, &Admin::slot_copy_url ) ); + + 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" ) ); + CONTROL::set_menu_motion( popupmenu ); +} + + +Admin::~Admin() +{ +#ifdef _DEBUG + std::cout << "Admin::~Admin " << m_url << std::endl; +#endif + 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 ); + if( view ) delete view; + } + } + m_list_command.clear(); +} + + +// SIGHUPを受け取った +void Admin::shutdown() +{ + 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( i ) ); + if( view ) view->shutdown(); + } + } +} + + + +// +// ページが含まれていないか +// +bool Admin::empty() +{ + return ( m_notebook.get_n_pages() == 0 ); +} + + + +// +// 含まれているページのURLのリスト取得 +// +std::list Admin::get_URLs() +{ + std::list urls; + + 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(); + if( m_adjust_reserve ) adjust_tabwidth( false ); + + m_notebook.clock_in(); +} + + + +// +// コマンド受付 +// +// 違うスレッドからもコマンドが来るので、すぐにはコマンドを実行 +// しないで、一旦Dispatcherでメインスレッドにコマンドを渡してからメインスレッドで実行する +// +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 ) +{ +#ifdef _DEBUG + std::cout << "Admin::set_command : " << command << " " << url << std::endl + << arg1 << " " << arg2 << std::endl + << arg3 << " " << arg4 << std::endl + << arg5 << " " << arg6 << std::endl; +#endif + + 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; + m_list_command.push_back( command_arg ); + m_disp.emit(); +} + + +// +// コマンド実行 +// +void Admin::exec_command() +{ + if( m_list_command.size() == 0 ) return; + + COMMAND_ARGS command = m_list_command.front(); + m_list_command.pop_front(); + +#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(); + } + + // viewを開く + else if( command.command == "open_view" ){ + open_view( command ); + } + else if( command.command == "switch_view" ){ + switch_view( command.url ); + } + else if( command.command == "tab_left" ){ + tab_left(); + } + else if( command.command == "tab_right" ){ + tab_right(); + } + else if( command.command == "redraw" ){ + redraw_view( command.url ); + } + else if( command.command == "redraw_current_view" ){ + redraw_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 == "close_view" ){ + if( command.arg1 == "true" ) close_all_view( command.url ); + else close_view( command.url ); + } + else if( command.command == "close_currentview" ){ + close_current_view(); + } + else if( command.command == "set_page" ){ + set_current_page( atoi( command.arg1.c_str() ) ); + } + + // フォーカスイン、アウト + 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(); + } + + // タブに文字をセット、タブ幅調整 + else if( command.command == "set_tablabel" ){ + set_tablabel( command.url, command.arg1 ); + } + else if( command.command == "adjust_tabwidth" ){ + adjust_tabwidth( ( command.arg1 == "true" ) ); + } + + // 全てのビューを再描画 + else if( command.command == "relayout_all" ){ + + std::list< SKELETON::View* > list_view = get_list_view(); + std::list< SKELETON::View* >::iterator it = list_view.begin(); + for( ; it != list_view.end(); ++it ){ + SKELETON::View* view = ( *it ); + if( view ) view->relayout(); + } + } + + // 個別のコマンド処理 + else command_local( command ); +} + + +// +// ビューを開く +// +// command.arg1: "true" なら新しいtabを開く, "right" ならアクティブなtabの右に、"left"なら左に開く +// command.arg2: "true" なら既にurlを開いているかチェックしない +// +void Admin::open_view( const COMMAND_ARGS& command ) +{ +#ifdef _DEBUG + std::cout << "Admin::open_view : " << command.url << std::endl; +#endif + SKELETON::View* view; + SKELETON::View* current_view = get_current_view(); + + // 現在のviewのフォーカスを外し、列幅保存 + if( current_view ) { + current_view->focus_out(); + } + + // urlを既に開いていたら表示してリロード + if( ! ( command.arg2 == "true" ) ){ + view = get_view( command.url ); + if( view ){ + + int page = m_notebook.page_num( *view ); +#ifdef _DEBUG + std::cout << "page = " << page << std::endl; +#endif + m_notebook.set_current_page( page ); + view->show_view(); + focus_current_view(); + return; + } + } + + view = create_view( command ); + if( !view ) return; + + int page = m_notebook.get_current_page(); + bool open_tab = ( page == -1 || command.arg1 == "true" || command.arg1 == "right" || command.arg1 == "left" ); + + // タブで表示 + if( open_tab ){ + +#ifdef _DEBUG + std::cout << "append page\n"; +#endif + if( page != -1 && command.arg1 == "right" ) m_notebook.insert_page( *view , view->get_tab_label(), page+1 ); + else if( page != -1 && command.arg1 == "left" ) m_notebook.insert_page( *view , view->get_tab_label(), page ); + else m_notebook.append_page( *view , view->get_tab_label() ); + } + + // 開いてるviewを消してその場所に表示 + else{ +#ifdef _DEBUG + std::cout << "replace page\n"; +#endif + m_notebook.insert_page( *view, view->get_tab_label(), page ); + m_notebook.remove_page( page + 1 ); + if( current_view ) delete current_view; + } + + view->show(); + view->show_view(); + m_notebook.set_current_page( m_notebook.page_num( *view ) ); + focus_current_view(); +} + + + +// +// ビュー切り替え +// +// 指定したURLのビューに切り替える +// +void Admin::switch_view( const std::string& url ) +{ + SKELETON::View* view = get_view( url ); + if( view ){ + + int page = m_notebook.page_num( *view ); + m_notebook.set_current_page( page ); + focus_view( page ); + } +} + + + +// +// タブ左移動 +// +void Admin::tab_left() +{ + int pages = m_notebook.get_n_pages(); + if( pages == 1 ) return; + + int page = m_notebook.get_current_page(); + if( page == -1 ) return; + + if( page == 0 ) page = pages; + + m_notebook.set_current_page( --page ); +} + + + +// +// タブ右移動 +// +void Admin::tab_right() +{ + int pages = m_notebook.get_n_pages(); + if( pages == 1 ) return; + + int page = m_notebook.get_current_page(); + if( page == -1 ) return; + + if( page == pages -1 ) page = -1; + + m_notebook.set_current_page( ++page ); +} + + + + +// +// ビューを再描画 +// +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(); +} + + +// +// 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 ); + + std::list< SKELETON::View* >::iterator it = list_view.begin(); + for( ; it != list_view.end(); ++it ){ + if( ( *it ) == current_view ) ( *it )->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(); + m_notebook.remove_page( page ); + delete view; + +#ifdef _DEBUG + std::cout << "Admin::close_view : delete page = " << page << std::endl; +#endif + + if( m_notebook.get_n_pages() == 0 ){ +#ifdef _DEBUG + std::cout << "empty\n"; +#endif + CORE::core_set_command( "empty_page", m_url ); + } + else{ + + if( page == current_page ){ + SKELETON::View* newview = dynamic_cast< View* >( m_notebook.get_nth_page( page ) ); + if( newview ) switch_view( newview->get_url() ); + } + set_command( "adjust_tabwidth", "", "true" ); + } +} + + + +// +// 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 ); + + std::list< View* >::iterator it = list_view.begin(); + for( ; it != list_view.end(); ++it ){ + + SKELETON::View* view = ( *it ); + close_view( view ); + } +} + + + +// +// 現在のビューを閉じる +// +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 + + SKELETON::View* view = get_view( url ); + if( view ) view->update_item( id ); +} + + +// +// ビューに更新終了を知らせる +// +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(); +} + + +// +// フォーカスする +// +// URLバーやステータス表示も更新する +// +void Admin::focus_view( int page ) +{ +#ifdef _DEBUG + std::cout << "Admin::focus_view : " << m_url << " page = " << page << std::endl; +#endif + + SKELETON::View* view = dynamic_cast< View* >( m_notebook.get_nth_page( page ) ); + if( view ) { + view->focus_view(); + CORE::core_set_command( "set_url", view->url_for_copy() ); + CORE::core_set_command( "set_status", "", view->get_status() ); + } +} + + + +// +// 現在のviewをフォーカスする +// +void Admin::focus_current_view() +{ +#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 + + SKELETON::View* view = get_current_view(); + if( view ) view->focus_out(); + m_notebook.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 ){ + + view->get_tab_label().set_text( "" ); // adjust_tabwidth() でタブをリサイズする + view->get_tab_label().set_fulltext( str_label ); + + // タブ幅再設定 + set_command( "adjust_tabwidth", "", "true" ); + } +} + + + +// +// タブの幅調整 +// +// force = true なら必ず変更 +// +void Admin::adjust_tabwidth( bool force ) +{ + const int mrg = 30; + + int width_notebook = m_notebook.get_width() - mrg; + + // 前回の呼び出しと幅が同じ時は一旦returnして + // クロック入力時に改めて adjust_tabwidth() を呼び出す + if( !force && width_notebook == m_pre_width ){ + m_adjust_reserve = true; + return; + } + +#ifdef _DEBUG + std::cout << "Admin::adjust_tabwidth\n"; +#endif + + if( width_notebook != m_pre_width ) m_pre_width = width_notebook; + if( !m_adjust_reserve ) m_adjust_reserve = false; + +#ifdef _DEBUG + std::cout << "width_notebook = " << width_notebook << std::endl; +#endif + + m_notebook.adjust_tabwidth( force ); +} + + + +// +// ビュークラス取得 +// +// use_find が true なら == の代わりに findを使う +// +View* Admin::get_view( const std::string& url, bool use_find ) +{ + 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 ){ + if( view->get_url() == url ) return view; + else if( use_find && view->get_url().find ( url ) != std::string::npos ) return view; + } + } + } + + return NULL; +} + + + +// +// ビュークラスのリスト取得 +// +// url を含むビューのリストを返す +// +std::list< View* > Admin::get_list_view( const std::string& url ) +{ + std::list< View* > list_view; + + 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; + + 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 NULL; + SKELETON::View* view = dynamic_cast< View* >( m_notebook.get_nth_page( page ) ); + + return view; +} + + + + +// +// 現在表示されているページ番号 +// +void Admin::set_current_page( int page ) +{ + m_notebook.set_current_page( page ); +} + + + +// +// 現在表示されているページ番号 +// +int Admin::get_current_page() +{ + return m_notebook.get_current_page(); +} + + + +// +// notebookのタブのページが切り替わったら呼ばれるslot +// +void Admin::slot_switch_page( GtkNotebookPage*, guint page ) +{ +#ifdef _DEBUG + std::cout << "Admin::slot_switch_page : " << m_url << " page = " << page << std::endl; +#endif + + focus_view( page ); +} + + +// +// タブを閉じる +// +void Admin::slot_tab_close( 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( 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 ) +{ +#ifdef _DEBUG + std::cout << "Admin::slot_tab_menu " << page << std::endl; +#endif + + m_clicked_page = page; + + Gtk::Menu* popupmenu = dynamic_cast< Gtk::Menu* >( m_ui_manager->get_widget( "/popup_menu" ) ); + if( popupmenu ) popupmenu->popup( 0, gtk_get_current_event_time() ); +} + + +// +// 右クリックメニューの閉じる +// +void Admin::slot_close_tab() +{ +#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_close_other_tabs() +{ +#ifdef _DEBUG + std::cout << "Admin::slot_close_other_tabs " << m_clicked_page << std::endl; +#endif + + std::string url; + SKELETON::View* view = dynamic_cast< View* >( m_notebook.get_nth_page( m_clicked_page ) ); + if( view ) url = view->get_url(); + + int pages = m_notebook.get_n_pages(); + for( int i = 0; i < pages; ++i ){ + 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::slot_close_all_tabs() +{ +#ifdef _DEBUG + std::cout << "Admin::slot_close_all_tabs " << m_clicked_page << std::endl; +#endif + + 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() ); + } +} + + + +// +// 右クリックメニューのブラウザで開く +// +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 ) COPYCLIP( view->url_for_copy() ); +} diff --git a/src/skeleton/admin.h b/src/skeleton/admin.h new file mode 100644 index 000000000..fd85e5e62 --- /dev/null +++ b/src/skeleton/admin.h @@ -0,0 +1,144 @@ +// ライセンス: 最新のGPL + +// Viewを管理するクラス +// 派生クラスはcreate_view()をオーバロードすること +// Adminへの命令はすべてset_command()を通じておこなうこと + +#ifndef _ADMIN_H +#define _ADMIN_H + +#include +#include +#include + +#include "dragnote.h" + +struct COMMAND_ARGS; + +namespace SKELETON +{ + class View; + + class Admin + { + std::string m_url; + DragableNoteBook m_notebook; + + bool m_focus; + + bool m_adjust_reserve; // adjust予約 + int m_pre_width; + + Glib::Dispatcher m_disp; + 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; + + public: + + Admin( const std::string& url ); + virtual ~Admin(); + + virtual bool empty(); + const std::string& get_url() const{ return m_url; } + Gtk::Notebook& get_gtknotebook(){ return m_notebook; } + + // フォーカスされているか + const bool has_focus() const { return m_focus; } + + // 含まれているページのURLのリスト取得 + virtual std::list< std::string > get_URLs(); + + // Core からのクロック入力。 + // Coreでタイマーをひとつ動かして全体の同期を取るようにしているので + // 一定時間毎に clock_in() が Core から呼び出される + virtual void clock_in(); + + // コマンド入力 + virtual 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() + ); + + // 現在表示してるページ番号 + // 表示ページを指定したいときは "set_page" コマンドを使う + virtual int get_current_page(); + + // SIGHUPを受け取ったときの処理 + virtual void shutdown(); + + protected: + + DragableNoteBook& get_notebook(){ return m_notebook; } + + virtual void exec_command(); + + // 派生クラス固有のコマンドを実行 + virtual void command_local( const COMMAND_ARGS& command ){} + + virtual void restore(){} // 起動時に前回の状態を回復 + virtual void open_view( const COMMAND_ARGS& command ); + virtual void switch_view( const std::string& url ); + virtual void tab_left(); + virtual void tab_right(); + virtual void redraw_view( const std::string& url ); + virtual void redraw_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 close_view( View* view ); + virtual void close_all_view( const std::string& url ); + virtual void close_current_view(); + 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 adjust_tabwidth( bool force ); + + // D&D + // 派生クラス別にD&Dの処理を行う + virtual void slot_drag_begin( int page ){} + virtual void slot_drag_motion(){} + virtual void slot_drag_drop( int page ){} + virtual void slot_drag_end(){} + + virtual View* create_view( const COMMAND_ARGS& command ){ return NULL; }; + virtual View* get_view( const std::string& url, bool use_find = false ); + std::list< View* > get_list_view( const std::string& url ); + std::list< View* > get_list_view(); + virtual View* get_current_view(); + virtual void set_current_page( int page ); + + // notebookのタブが切り替わったときに呼ばれるslot + virtual void slot_switch_page( GtkNotebookPage*, guint page ); + + // タブを閉じる + virtual void slot_tab_close( int page ); + + // タブ再読み込み + virtual void slot_tab_reload( int page ); + + // タブメニュー表示 + virtual void slot_tab_menu( int page, int x, int y ); + + // 右クリックメニュー + virtual void slot_close_tab(); + virtual void slot_close_other_tabs(); + virtual void slot_close_all_tabs(); + virtual void slot_open_by_browser(); + virtual void slot_copy_url(); + }; +} + +#endif diff --git a/src/skeleton/dragnote.cpp b/src/skeleton/dragnote.cpp new file mode 100644 index 000000000..656202e07 --- /dev/null +++ b/src/skeleton/dragnote.cpp @@ -0,0 +1,372 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "dragnote.h" +#include "tablabel.h" + +#include "controlid.h" + +using namespace SKELETON; + +// タブに貼っているラベルのサイズを取得 +#define GET_LABELWIDTH(tab,mrg) tab->get_label().get_layout()->get_pixel_ink_extents().get_width()+mrg + + +// タブのラベルのリサイズ +#define RESIZE_TAB(tab,lng) \ +do{ \ +Glib::ustring ulabel( tab->get_fulltext() ); \ +ulabel.resize( lng ); \ +tab->get_label().set_text( ulabel ); \ +} while( false ) + + + +DragableNoteBook::DragableNoteBook() + : Gtk::Notebook() + , m_page( -1 ) + , m_drag( 0 ) +{ + add_events( Gdk::POINTER_MOTION_MASK ); + add_events( Gdk::LEAVE_NOTIFY_MASK ); +} + + +// +// クロック入力 +// +void DragableNoteBook::clock_in() +{ + m_tooltip.clock_in(); +} + + +// +// フォーカスアウト +// +void DragableNoteBook::focus_out() +{ + m_tooltip.hide_tooltip(); +} + + + +// マウスを動かした +bool DragableNoteBook::on_motion_notify_event( GdkEventMotion* event ) +{ + SKELETON::TabLabel* tab; + int page = get_page_under_mouse(); + +#ifdef _DEBUG + std::cout << "DragableNoteBook::on_motion_notify_event page = " << page << std::endl; +#endif + + if( page >= 0 && page < get_n_pages() ){ + tab = dynamic_cast< SKELETON::TabLabel* >( get_tab_label( *get_nth_page( page ) ) ); + if( tab ) m_tooltip.set_text( tab->get_fulltext() ); + } + else m_tooltip.hide_tooltip(); + + return Gtk::Notebook::on_motion_notify_event( event ); +} + + +// マウスが出た +bool DragableNoteBook::on_leave_notify_event( GdkEventCrossing* event ) +{ + int page = get_page_under_mouse(); + +#ifdef _DEBUG + std::cout << "DragableNoteBook::on_leave_notify_event page = " << page << std::endl; +#endif + + if( page < 0 || page >= get_n_pages() ) m_tooltip.hide_tooltip(); + + return Gtk::Notebook::on_leave_notify_event( event ); +} + + + +// +// タブの幅調整 +// +// force = true なら必ず変更 +// +void DragableNoteBook::adjust_tabwidth( bool force ) +{ +#ifdef _DEBUG + std::cout << "DragableNoteBook::adjust_tabwidth\n"; +#endif + + const int mrg = 30; + const int mrg_tab = 10; + + int width_notebook = get_width() - mrg; + int pages = get_n_pages(); + +#ifdef _DEBUG + std::cout << "width_notebook = " << width_notebook << " page = " << pages << std::endl; +#endif + + if( pages ){ + + // タブ幅の平均値 + int avg_width_tab = width_notebook / MAX( 3, pages ); + + // 縮める + for( int i = 0; i < pages; ++i ){ + + SKELETON::TabLabel* tab = dynamic_cast< SKELETON::TabLabel* >( get_tab_label( *get_nth_page( i ) ) ); + if( tab ){ + + for(;;){ + + int width = GET_LABELWIDTH( tab, mrg_tab ); +#ifdef _DEBUG + std::cout << "s " << i << " " << width << " / " << avg_width_tab << " " << tab->get_label().get_text() << std::endl; +#endif + int lng = tab->get_label().get_text().length() -1; + if( width < avg_width_tab || lng < 0 ) break; + RESIZE_TAB( tab, lng ); + } + } + } + + // 伸ばす + int width_total = 0; + for( int i = 0; i < pages; ++i ){ + + SKELETON::TabLabel* tab = dynamic_cast< SKELETON::TabLabel* >( get_tab_label( *get_nth_page( i ) ) ); + if( tab ){ + + for(;;){ + + if( tab->get_label().get_text() == tab->get_fulltext() ) break; + int lng = tab->get_label().get_text().length() +1; + RESIZE_TAB( tab, lng ); + + // 越えたらひとつ戻してbreak; + int width = GET_LABELWIDTH( tab, mrg_tab ); +#ifdef _DEBUG + std::cout << "w " << i << " " << width << " / " << avg_width_tab << " " << tab->get_label().get_text() << std::endl; +#endif + if( width_total + width > avg_width_tab * ( i + 1 ) ){ + RESIZE_TAB( tab, --lng ); + break; + } + } + width_total += GET_LABELWIDTH( tab, mrg_tab ); + } + } + } +} + + + +// +// D&D設定 +// +void DragableNoteBook::set_dragable( bool dragable ) +{ + if( dragable ){ + + // クリックボタンの割り当て取得 + GdkEventButton event; + if( ! m_control.get_eventbutton( CONTROL::ClickButton, event ) ) return; + + std::list< Gtk::TargetEntry > targets; + targets.push_back( Gtk::TargetEntry( "text/plain", Gtk::TARGET_SAME_WIDGET, 0 ) ); + + switch( event.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; + } + + drag_dest_set( targets ); + } + else{ + drag_source_unset(); + drag_dest_unset(); + } +} + + + +bool DragableNoteBook::on_button_press_event( GdkEventButton* event ) +{ + // ボタンを押した時点でのページ番号を記録しておく + m_page = get_page_under_mouse(); + if( m_page >= get_n_pages() ) m_page = get_n_pages() -1; + m_drag = false; + + // ダブルクリック + m_dblclick = false; + if( event->type == GDK_2BUTTON_PRESS ) m_dblclick = true; + +#ifdef _DEBUG + std::cout << "DragableNoteBook::on_button_press_event from " << m_page << std::endl; + std::cout << "x = " << (int)event->x_root << " y = " << (int)event->y_root + << " dblclick = " << m_dblclick << std::endl; +#endif + + return true; +} + + + + +bool DragableNoteBook::on_button_release_event( GdkEventButton* event ) +{ + int x = (int)event->x_root; + 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_drag // D&D中は切替えない + + // なぜかviewをクリックしても呼ばれるので m_page の値で on_button_press_event が呼ばれたかチェック + && m_page != -1 + ){ + + // ダブルクリックの処理のため一時的にtypeを切替える + GdkEventType type_copy = event->type; + if( m_dblclick ) event->type = GDK_2BUTTON_PRESS; + + // ページ切替え + if( m_control.button_alloted( event, CONTROL::ClickButton ) ) set_current_page( m_page ); + + // タブを閉じる + else if( m_control.button_alloted( event, CONTROL::CloseTabButton ) ){ + m_sig_tab_close.emit( m_page ); + + // タブにページが残ってなかったらそのままreturnしないと落ちる + if( get_n_pages() == 0 ){ + m_page = -1; + return false; + } + } + + // タブを再読み込み + 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 Gtk::Notebook::on_button_release_event( event ); +} + + + +void DragableNoteBook::on_drag_begin( const Glib::RefPtr< Gdk::DragContext >& context ) +{ +#ifdef _DEBUG + std::cout << "DragableNoteBook::on_drag_begin \n"; +#endif + m_drag = true; + m_sig_drag_begin.emit( m_page ); +} + + +bool DragableNoteBook::on_drag_motion( const Glib::RefPtr& context, int x, int y, guint time ) +{ +#ifdef _DEBUG + std::cout << "DragableNoteBook::on_drag_motion\n"; +#endif + + m_sig_drag_motion.emit(); + return Gtk::Notebook::on_drag_motion( context, x, y, time ); +} + + + +bool DragableNoteBook::on_drag_drop( const Glib::RefPtr& context, int x, int y, guint time ) +{ + int page = get_page_under_mouse(); + if( page >= get_n_pages() ) page = get_n_pages() -1; + +#ifdef _DEBUG + std::cout << "DragableNoteBook::on_drag_drop page = " << page << std::endl; +#endif + + if( m_drag ){ + + // ドラッグ前とページが変わっていたら入れ替え + if( m_page != -1 && page != -1 && m_page != page ){ + reorder_child( *get_nth_page( m_page ), page ); + m_page = -1; + } + } + + m_sig_drag_drop.emit( page ); + return Gtk::Notebook::on_drag_drop( context, x, y, time ); +} + + + +void DragableNoteBook::on_drag_end( const Glib::RefPtr< Gdk::DragContext >& context ) +{ +#ifdef _DEBUG + std::cout << "DragableNoteBook::on_drag_end\n"; +#endif + + m_sig_drag_end.emit(); + m_drag = false; +} + + +// +// マウスの下にあるタブの番号を取得 +// +// タブ上で無いときは-1を返す +// タブの右側の場合は 最大タブ番号 + 1 を返す +// +int DragableNoteBook::get_page_under_mouse() +{ + int x, y; + int width = 0, height = 0, i; + + for( i = 0; i < get_n_pages() ; ++i ){ + + SKELETON::TabLabel* tab = dynamic_cast< SKELETON::TabLabel* >( get_tab_label( *get_nth_page( i ) ) ); + if( !tab ){ +#ifdef _DEBUG + std::cout << "DragableNoteBook::get_page_under_mouse: tab = NULL\n"; +#endif + return -1; + } + + // HBoxは(x,y)座標が取得できないので、幅と高さとマウスの位置からタブの中に + // マウスがあるかどうか判定する + width = tab->get_allocation().get_width(); + height = tab->get_allocation().get_height(); + tab->get_pointer( x, y ); + +#ifdef _DEBUG +// std::cout << "DragableNoteBook::get_page_under_mouse: x = " << x << " y = " << y +// << " xx = " << xx << " yy = " << yy << " w = " << width << " h = " << height << std::endl; +#endif + + if( x >= 0 && x <= width && y >= 0 && y <= height ) return i; + } + + // 右の空白部分をクリック + if( x > 0 ) return i; + + return -1; +} + diff --git a/src/skeleton/dragnote.h b/src/skeleton/dragnote.h new file mode 100644 index 000000000..540da64a4 --- /dev/null +++ b/src/skeleton/dragnote.h @@ -0,0 +1,96 @@ +// ライセンス: 最新のGPL +// +// タブをドラッグしてページを入れ替え可能なNoteBook + + +#ifndef _DRAGNOTE_H +#define _DRAGNOTE_H + +#include "tooltip.h" + +#include "control.h" + +#include + +namespace SKELETON +{ + 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; + + // D&D + typedef sigc::signal< void, int > SIG_DRAG_BEGIN; + typedef sigc::signal< void > SIG_DRAG_MOTION; + typedef sigc::signal< void, int > SIG_DRAG_DROP; + typedef sigc::signal< void > SIG_DRAG_END; + + class DragableNoteBook : public Gtk::Notebook + { + SIG_TAB_CLOSE m_sig_tab_close; + SIG_TAB_RELOAD m_sig_tab_reload; + SIG_TAB_MENU m_sig_tab_menu; + + SIG_DRAG_BEGIN m_sig_drag_begin; + SIG_DRAG_MOTION m_sig_drag_motion; + SIG_DRAG_DROP m_sig_drag_drop; + SIG_DRAG_END m_sig_drag_end; + + int m_page; + bool m_drag; + bool m_dblclick; + + // 入力コントローラ + CONTROL::Control m_control; + + Tooltip m_tooltip; + + public: + + 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_DRAG_BEGIN sig_drag_begin() { return m_sig_drag_begin; } + SIG_DRAG_MOTION sig_drag_motion() { return m_sig_drag_motion; } + SIG_DRAG_DROP sig_drag_drop() { return m_sig_drag_drop; } + SIG_DRAG_END sig_drag_end() { return m_sig_drag_end; } + + DragableNoteBook(); + + void clock_in(); + void focus_out(); + + void set_dragable( bool dragable ); + + // マウスの下にあるタブの番号 + int get_page_under_mouse(); + + void adjust_tabwidth( bool force ); + + protected: + + // コントローラ + CONTROL::Control& get_control(){ return m_control; } + + // 呼び出される順番は + // + // (1) on_button_press_event + // (2) on_drag_begin + // (3) on_button_release_event + // (4) on_drag_drop + // (5) on_drag_end + // + virtual void on_drag_begin( const Glib::RefPtr< Gdk::DragContext>& context ); + virtual bool on_drag_motion( const Glib::RefPtr& context, int x, int y, guint time ); + virtual bool on_drag_drop( const Glib::RefPtr& context, int x, int y, guint time ); + virtual void on_drag_end( const Glib::RefPtr< Gdk::DragContext>& context ); + + virtual bool on_button_press_event( GdkEventButton* event ); + virtual bool on_button_release_event( GdkEventButton* event ); + + virtual bool on_motion_notify_event( GdkEventMotion* event ); + virtual bool on_leave_notify_event( GdkEventCrossing* event ); + }; +} + +#endif diff --git a/src/skeleton/editview.h b/src/skeleton/editview.h new file mode 100644 index 000000000..7bc6cd34b --- /dev/null +++ b/src/skeleton/editview.h @@ -0,0 +1,70 @@ +// ライセンス: 最新のGPL + +#ifndef _EDITVIEW_H +#define _EDITVIEW_H + +#include + +namespace SKELETON +{ + typedef sigc::signal< bool, GdkEventKey* > SIG_KEY_PRESS; + typedef sigc::signal< bool, GdkEventKey* > SIG_KEY_RELEASE; + + // キーのプレスとリリースをフックする + class EditTextView : public Gtk::TextView + { + SIG_KEY_PRESS m_sig_key_press; + SIG_KEY_RELEASE m_sig_key_release; + + public: + + SIG_KEY_PRESS sig_key_press(){ return m_sig_key_press; } + SIG_KEY_RELEASE sig_key_release(){ return m_sig_key_release; } + + EditTextView(){} + + protected: + + virtual bool on_key_press_event( GdkEventKey* event ) + { + m_sig_key_press.emit( event ); + return Gtk::TextView::on_key_press_event( event ); + } + + virtual bool on_key_release_event( GdkEventKey* event ) + { + m_sig_key_release.emit( event ); + return Gtk::TextView::on_key_release_event( event ); + } + }; + + + class EditView : public Gtk::ScrolledWindow + { + EditTextView m_textview; + + public: + + EditView(){ + set_policy( Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC ); + + add( m_textview ); + + show_all_children(); + } + virtual ~EditView(){} + + SIG_KEY_PRESS sig_key_press(){ return m_textview.sig_key_press(); } + SIG_KEY_RELEASE sig_key_release(){ return m_textview.sig_key_release(); } + + Gtk::TextView& textview(){ return m_textview; } + + void set_text( const Glib::ustring& text ){ m_textview.get_buffer()->set_text( text ); } + Glib::ustring get_text(){ return m_textview.get_buffer()->get_text(); } + int get_line_count(){ return m_textview.get_buffer()->get_line_count(); } + + void focus_view(){ m_textview.grab_focus(); } + }; +} + +#endif diff --git a/src/skeleton/entry.cpp b/src/skeleton/entry.cpp new file mode 100644 index 000000000..6e7c41b31 --- /dev/null +++ b/src/skeleton/entry.cpp @@ -0,0 +1,31 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "entry.h" + +#include "controlid.h" + +using namespace SKELETON; + + +// キー入力のフック +bool JDEntry::on_key_release_event( GdkEventKey* event ) +{ + bool ret = Gtk::Entry::on_key_release_event( event ); + int controlid = m_control.key_press( event ); + + switch( controlid ){ + + case CONTROL::DrawOutAnd: + m_sig_operate.emit( CONTROL::DrawOutAnd ); + break; + + case CONTROL::Cancel: + m_sig_operate.emit( CONTROL::Cancel ); + break; + } + + return ret; +} diff --git a/src/skeleton/entry.h b/src/skeleton/entry.h new file mode 100644 index 000000000..94f62215f --- /dev/null +++ b/src/skeleton/entry.h @@ -0,0 +1,37 @@ +// ライセンス: 最新のGPL +// +// キーボードフックしたentryクラス +// + +#ifndef _ENTRY_H +#define _ENTRY_H + +#include "control.h" + +#include + +namespace SKELETON +{ + class JDEntry : public Gtk::Entry + { + typedef sigc::signal< void, int > SIG_OPERATE; + + SIG_OPERATE m_sig_operate; + + // 入力コントローラ + CONTROL::Control m_control; + + public: + + SIG_OPERATE signal_operate(){ return m_sig_operate; } + + JDEntry(){} + + protected: + + // キー入力のフック + virtual bool on_key_release_event( GdkEventKey* event ); + }; +} + +#endif diff --git a/src/skeleton/imgbutton.h b/src/skeleton/imgbutton.h new file mode 100644 index 000000000..a1b29200f --- /dev/null +++ b/src/skeleton/imgbutton.h @@ -0,0 +1,59 @@ +// ライセンス: 最新のGPL + +// 画像つきボタン + +#ifndef _IMGBUTTON_H +#define _IMGBUTTON_H + +#include +#include + +namespace SKELETON +{ + + class ImgButton : public Gtk::Button + { + Gtk::Image* m_img; + Gtk::Label m_label; + Gtk::HBox m_hbox; + + public: + + ImgButton( const Gtk::StockID& stock_id, const std::string label = std::string() ){ + + m_img = Gtk::manage( new Gtk::Image( stock_id, Gtk::ICON_SIZE_MENU ) ); + + if( label.empty() ) add( *m_img ); + else { + m_label.set_text( label ); + m_hbox.pack_start( *m_img ); + m_hbox.pack_start( m_label, Gtk::PACK_SHRINK, 2 ); + add( m_hbox ); + } + + set_focus_on_click( false ); + } + }; + + + class ImgToggleButton : public Gtk::ToggleButton + { + Gtk::Image* m_img; + + public: + + ImgToggleButton(){} + + void set_img( const Gtk::StockID& stock_id ){ + + m_img = Gtk::manage( new Gtk::Image( stock_id, Gtk::ICON_SIZE_MENU ) ); + add( *m_img ); + set_focus_on_click( false ); + } + }; + + + +} + +#endif diff --git a/src/skeleton/label_entry.h b/src/skeleton/label_entry.h new file mode 100644 index 000000000..590a192a4 --- /dev/null +++ b/src/skeleton/label_entry.h @@ -0,0 +1,51 @@ +// ライセンス: 最新のGPL + +// +// ラベル + エントリー +// +// プロパティの表示用 +// + +#ifndef _LABEL_ENTRY_H +#define _LABEL_ENTRY_H + +#include + +namespace SKELETON +{ + class LabelEntry : public Gtk::HBox + { + Gtk::HBox m_hbox; + Gtk::Label m_label; + Gtk::Entry m_entry; + + public: + + LabelEntry( const std::string& label, const std::string& text = std::string() ){ + + m_label.set_text( label ); + + m_entry.set_editable( false ); + m_entry.set_activates_default( false ); + m_entry.set_has_frame( false ); + m_entry.set_text( text ); + + pack_start( m_label, Gtk::PACK_SHRINK ); + pack_start( m_entry ); + } + + void set_text( const std::string& text ){ m_entry.set_text( text ); } + + virtual void on_realize() + { + Gtk::HBox::on_realize(); + + // entryの背景色を変える + Gdk::Color color_bg = get_style()->get_bg( Gtk::STATE_NORMAL ); + m_entry.modify_base( m_entry.get_state(), color_bg ); + } + + }; +} + +#endif diff --git a/src/skeleton/loadable.cpp b/src/skeleton/loadable.cpp new file mode 100644 index 000000000..b9076b1eb --- /dev/null +++ b/src/skeleton/loadable.cpp @@ -0,0 +1,235 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "loadable.h" + +#include "jdlib/loader.h" +#include "jdlib/misctime.h" + +#include "httpcode.h" + +using namespace SKELETON; + +Loadable::Loadable() + : m_loader( 0 ) + , m_enable_disp( true ) +{ + m_disp.connect( sigc::mem_fun( *this, &SKELETON::Loadable::finish_disp ) ); + clear_load_data(); +} + + +Loadable::~Loadable() +{ + // dispatch禁止 + // loadableのインスタンスが無くなった後でdispachすると落ちる + m_enable_disp = false; + + delete_loader(); +} + + +// 完全クリア +void Loadable::clear_load_data() +{ + m_code = HTTP_INIT; + m_str_code = std::string(); + m_date_modified = std::string(); + m_cookie = std::string(); + m_location = std::string(); + m_total_length = 0; + m_current_length = 0; +} + + + +const bool Loadable::is_loading() +{ + if( ! m_loader ) return false; + + // m_loader != NULL ならローダ起動中ってこと + return true; +} + + +// +// 更新時刻 +// +time_t Loadable::time_modified() +{ + time_t time_out; + time_out = MISC::datetotime( m_date_modified ); + if( time_out == 0 ) time_out = time( NULL ) - 600; + return time_out; +} + + + + +void Loadable::delete_loader() +{ + if( m_loader ) delete m_loader; + m_loader = NULL; +} + + + + +// +// ロード開始 +// +bool Loadable::start_load( const JDLIB::LOADERDATA& data ) +{ + if( is_loading() ) return false; + + assert( m_loader == NULL ); + m_loader = JDLIB::create_loader(); + + // 他にローダが沢山動いているとローダを作れない + if( ! m_loader ){ + m_code = HTTP_ERR; + m_str_code = "ローダを作成できません"; + delete_loader(); + return false; + } + + // 情報初期化 + // 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(); + delete_loader(); + 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()を呼ばれたらディスパッチしてメインスレッドに処理を移す +// そうしないと色々不具合が生じる +void Loadable::finish() +{ +#ifdef _DEBUG + std::cout << "Loadable::finish\n"; +#endif + if( m_enable_disp ) m_disp.emit(); +} + + +// +// ローダを削除してreceive_finish()をコール +// +void Loadable::finish_disp() +{ +#ifdef _DEBUG + std::cout << "Loadable::finish_disp\n"; +#endif + + // ローダを削除する前に情報保存 + m_code = get_loader_code(); + if( ! get_loader_str_code().empty() ) m_str_code = get_loader_str_code(); + if( ! get_loader_modified().empty() ) m_date_modified = get_loader_modified(); + if( ! get_loader_cookie().empty() ) m_cookie = get_loader_cookie(); + if( ! get_loader_location().empty() ) m_location = get_loader_location(); + +#ifdef _DEBUG + std::cout << "delete_loader\n"; +#endif + + delete_loader(); + +#ifdef _DEBUG + std::cout << "code = " << m_code << std::endl; + std::cout << "str_code = " << m_str_code << std::endl; + std::cout << "modified = " << m_date_modified << std::endl; + std::cout << "cookie = " << m_cookie << 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(); +} + + + +// ローダから各種情報の取得 + +const int Loadable::get_loader_code() +{ + if( ! m_loader ) return HTTP_INIT; + + return m_loader->data().code; +} + + +const std::string Loadable::get_loader_str_code() +{ + if( ! m_loader ) return std::string(); + + return m_loader->data().str_code; +} + + +const std::string Loadable::get_loader_modified() +{ + if( ! m_loader ) return std::string(); + + return m_loader->data().modified; +} + + +const std::string Loadable::get_loader_cookie() +{ + if( ! m_loader ) return std::string(); + + return m_loader->data().cookie; +} + + +const std::string Loadable::get_loader_location() +{ + if( ! m_loader ) return std::string(); + + return m_loader->data().location; +} + + +const size_t Loadable::get_loader_length() +{ + if( ! m_loader ) return 0; + + return m_loader->data().length; +} diff --git a/src/skeleton/loadable.h b/src/skeleton/loadable.h new file mode 100644 index 000000000..785221a1a --- /dev/null +++ b/src/skeleton/loadable.h @@ -0,0 +1,130 @@ +// ライセンス: 最新のGPL + +// +// ロード可能クラスの基底クラス +// JDLIB::Loaderを使ってデータを受信する +// +// ・start_load() でロード開始。JDLIB::LOADERDATA data を引数に渡す +// ・stop_load() で停止 +// +// ロード中はreceive_data()がコールバックされてデータが送られる +// ロードが終わると receive_finish() がコールバックされる +// +// 詳しい流れは次の通り +/* + +(1) start_load() の JDLIB::create_loader() でローダが作成される +(2) m_loader->run() でロード開始 + +--------ここから別スレッド内で実行 + +(3) ローダがデータを受け取るとreceive()がコールバックされてhttpコードやサイズを取得する +(4) receive()からreceive_data()が呼び出される +(5) ロードが終了したらfinish()が呼ばれてディスパッチャを使ってメインスレッドに制御を戻す + +--------ここからメインスレッドに戻る + +(6) ディスパッチャ経由でfinish_disp()が呼ばれる +(7) クッキー、更新時刻などを取得する +(8) delete_loader()でローダの停止を待って削除する +(9) receive_finish()を呼び出す + +注意点 + +・receive_data()は別スレッド内で実行される +・receive_finish() はメインスレッド内で実行される +・receive_finish()が呼ばれた時点で既にローダは削除されているので明示的に削除する必要はない + +*/ + + +#ifndef _LOADABLE_H +#define _LOADABLE_H + +#include + +namespace JDLIB +{ + class Loader; + class LOADERDATA; +} + + +namespace SKELETON +{ + class Loadable + { + Glib::Dispatcher m_disp; + JDLIB::Loader* m_loader; + bool m_enable_disp; + + // ローダからコピーしたデータ + int m_code; + std::string m_str_code; + std::string m_date_modified; + std::string m_cookie; + std::string m_location; + size_t m_total_length; + size_t m_current_length; + + public: + + Loadable(); + virtual ~Loadable(); + + void clear_load_data(); + const 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& cookie() const { return m_cookie; } + const std::string& location() const { return m_location; } + + const size_t total_length() const { return m_total_length; } + void set_total_length( int length ){ m_total_length = length; } + + const size_t current_length() const { return m_current_length; } + void set_current_length( int length ){ m_current_length = length; } + + // 更新時刻関係 + time_t time_modified(); + const std::string& 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 ); + + // ディスパッチャ経由で finish_disp()、 receive_finish()を呼ぶ + // ロード直後に直接 receive_finish() は呼ばないこと + void finish(); + + // ロード開始 + // パラメータは Loader::run()の説明をみること + bool start_load( const JDLIB::LOADERDATA& data ); + + // ロード停止 + void stop_load(); + + private: + + virtual void receive_data( const char* , size_t ){}; + virtual void receive_finish(){}; + + void delete_loader(); + void finish_disp(); + + const int get_loader_code(); + const std::string get_loader_str_code(); + const std::string get_loader_modified(); + const std::string get_loader_cookie(); + const std::string get_loader_location(); + const size_t get_loader_length(); + }; +} + +#endif diff --git a/src/skeleton/lockable.h b/src/skeleton/lockable.h new file mode 100644 index 000000000..cc968eb1e --- /dev/null +++ b/src/skeleton/lockable.h @@ -0,0 +1,39 @@ +// ライセンス: 最新のGPL + +// +// ロック可能クラス ( RefPtr と組み合わせて使う ) +// +// Lockable を継承したクラスを JDLIB::RefPtr_Lock 経由で呼ぶことによってロックを掛ける +// ロックが外れたら unlook_impl が呼ばれる +// + +#ifndef _LOCKABLE_H +#define _LOCKABLE_H + +namespace SKELETON +{ + class Lockable + { + int m_lock; + + public: + + Lockable() :m_lock( 0 ){} + + virtual ~Lockable(){} + + const 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 diff --git a/src/skeleton/login.cpp b/src/skeleton/login.cpp new file mode 100644 index 000000000..e2e8d2f38 --- /dev/null +++ b/src/skeleton/login.cpp @@ -0,0 +1,92 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "login.h" + +#include "cache.h" + +#include "jdlib/confloader.h" + +#include +#include // chmod +#include + +using namespace SKELETON; + +Login::Login( const std::string& url ) + : m_url( url ), m_login_now( 0 ), m_save_info( 0 ) +{ +#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( "username", "" ); + m_passwd = cf.get_option( "passwd", "" ); +} + + +// +// パスワードやユーザー名書き込み +// +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 ); +} diff --git a/src/skeleton/login.h b/src/skeleton/login.h new file mode 100644 index 000000000..80a958589 --- /dev/null +++ b/src/skeleton/login.h @@ -0,0 +1,56 @@ +// ライセンス: 最新のGPL + +// +// 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; + + public: + + Login( const std::string& url ); + virtual ~Login(); + + const 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& sessionid ){ m_sessionid = sessionid; } + + virtual void start_login(){} + virtual void logout(){} + + private: + void read_info(); + void save_info(); + }; +} + + +#endif diff --git a/src/skeleton/popupwin.h b/src/skeleton/popupwin.h new file mode 100644 index 000000000..ddf9b5952 --- /dev/null +++ b/src/skeleton/popupwin.h @@ -0,0 +1,121 @@ +// ライセンス: 最新のGPL + +// +// ポップアップウィンドウ +// +// SKELETON::Viewをポップアップ表示する。 +// 表示するSKELETON::Viewはコンストラクタでdeleteするので呼出元でdeleteしなくても良い +// + +#ifndef _POPUPWIN_H +#define _POPUPWIN_H + +#include "view.h" + + +namespace SKELETON +{ + class PopupWin : public Gtk::Window + { + SIG_HIDE_POPUP m_sig_hide_popup; + + Gtk::Widget* m_parent; + SKELETON::View* m_view; + int m_mrg_y; // ポップアップとマウスカーソルの間のマージン + + public: + + // 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; } + + // ポップアップウィンドウの座標と幅と高さを計算して移動とリサイズ + void slot_resize_popup() + { + if( ! m_view ) return; + + // マウス座標 + int x_mouse, y_mouse; + Gdk::ModifierType mod; + Gdk::Display::get_default()->get_pointer( x_mouse, y_mouse, mod ); + + // クライアントのサイズを取得 + int width_client = m_view->width_client(); + int height_client = m_view->height_client(); + + int width_desktop = m_parent->get_screen()->get_width(); + int height_desktop = m_parent->get_screen()->get_height(); + + // x 座標 と幅 + int x_popup; + int width_popup; + if( x_mouse + width_client <= width_desktop ) { + x_popup = x_mouse; + width_popup = width_client; + } + else { + x_popup = MAX( 0, width_desktop - width_client ); + width_popup = width_desktop - x_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( 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(); + } + + + PopupWin( Gtk::Widget* parent, SKELETON::View* view, int mrg_y ) + : Gtk::Window( Gtk::WINDOW_POPUP ), + m_parent( parent ), + m_view( view ), + 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->is_toplevel() ) set_transient_for( *( ( Gtk::Window* )toplevel ) ); + slot_resize_popup(); + } + + ~PopupWin() + { + if( m_view ) delete m_view; + } + }; +} + + +#endif diff --git a/src/skeleton/prefdiag.h b/src/skeleton/prefdiag.h new file mode 100644 index 000000000..de6a01783 --- /dev/null +++ b/src/skeleton/prefdiag.h @@ -0,0 +1,33 @@ +// ライセンス: 最新のGPL + +// 設定ダイアログの基底クラス + +#ifndef _PREFDIAG_H +#define _PREFDIAG_H + +#include + +namespace SKELETON +{ + class PrefDiag : public Gtk::Dialog + { + std::string m_url; + virtual void slot_ok_clicked(){} + + public: + + PrefDiag( const std::string& url, bool add_cancel = true ) : m_url( url ) + { + if( add_cancel ) add_button( Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL ); + + add_button( Gtk::Stock::OK, Gtk::RESPONSE_OK ) + ->signal_clicked().connect( sigc::mem_fun(*this, &PrefDiag::slot_ok_clicked ) ); + } + + virtual ~PrefDiag(){} + + const std::string& get_url() const { return m_url; } + }; +} + +#endif diff --git a/src/skeleton/tablabel.h b/src/skeleton/tablabel.h new file mode 100644 index 000000000..ceabc7653 --- /dev/null +++ b/src/skeleton/tablabel.h @@ -0,0 +1,35 @@ +// ライセンス: 最新のGPL + +#ifndef _TABLABEL_H +#define _TABLABEL_H + +#include +#include + +#include "control.h" + +namespace SKELETON +{ + class TabLabel : public Gtk::HBox + { + Gtk::Label m_label; + + // ラベルに表示する文字列の全体 + std::string m_fulltext; + + public: + + TabLabel(){ + pack_start( m_label ); + show_all_children(); + } + + Gtk::Label& get_label() { return m_label; } + + void set_fulltext( const std::string& label ){ m_fulltext = label; } + const std::string& get_fulltext() const { return m_fulltext; } + void set_text( const std::string& label ){ m_label.set_text( label ); } + }; +} + +#endif diff --git a/src/skeleton/tooltip.cpp b/src/skeleton/tooltip.cpp new file mode 100644 index 000000000..6cac3c50a --- /dev/null +++ b/src/skeleton/tooltip.cpp @@ -0,0 +1,125 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "tooltip.h" + +#include "global.h" + +using namespace SKELETON; + +// ツールチップ +Tooltip::Tooltip() + : Gtk::Window( Gtk::WINDOW_POPUP ), + m_counter( 0 ), + m_min_width( 0 ) +{ + // 背景色をテーマに合わせる + set_name( "gtk-tooltips" ); + set_border_width( 4 ); + add( m_label ); + show_all_children(); +} + + +void Tooltip::on_realize() +{ + Gtk::Window::on_realize(); + + Glib::RefPtr< Gdk::Window > window = get_window(); + m_gc = Gdk::GC::create( window ); +} + + + +bool Tooltip::on_expose_event( GdkEventExpose* event ) +{ + Gtk::Window::on_expose_event( event ); + + // 黒枠を描く + Glib::RefPtr< Gdk::Window > window = get_window(); + m_gc->set_foreground( Gdk::Color( "black" ) ); + window->draw_rectangle( m_gc, false, 0, 0, get_width()-1, get_height()-1 ); + return true; +} + + +// +// クロック入力 +// +void Tooltip::clock_in() +{ + const int popup_time = 500 // msec + / TIMER_TIMEOUT ; + + // カウンターが一杯になったらツールチップ表示 + ++m_counter; + if( m_counter == popup_time ) show_tooltip(); +} + + + + +void Tooltip::set_text( const std::string& text ) +{ + if( m_label.get_text() == text ) return; + +#ifdef _DEBUG + std::cout << "Tooltip::set_text" << text << std::endl; +#endif + + hide_tooltip(); + resize( 1, 1 ); + m_label.set_text( text ); + m_counter = 0; +} + + + +// +// 表示 +// +void Tooltip::show_tooltip() +{ + if( m_label.get_text().empty() ) return; + +#ifdef _DEBUG + std::cout << "Tooltip::show_tooltip" << m_label.get_text() << std::endl; +#endif + + int x_mouse, y_mouse; + Gdk::ModifierType mod; + Gdk::Display::get_default()->get_pointer( x_mouse, y_mouse, mod ); + + // 一度画面外にshow()して幅を確定してから、もし m_min_width よりも + // 幅が大きければマウスの位置に移動する + move( -100, -100 ); + show(); + int width = get_width(); + +#ifdef _DEBUG + std::cout << "width / min_width = " << width << " / " << m_min_width << std::endl; +#endif + if( width >= m_min_width ){ + + // 画面外にはみださないように調整 + const int mrg = 30; + int width_desktop = get_screen()->get_width(); + if( x_mouse + width > width_desktop ) x_mouse = MAX( 0, width_desktop - width ); + + move( x_mouse, y_mouse - mrg ); + } +} + + +void Tooltip::hide_tooltip() +{ +#ifdef _DEBUG + std::cout << "Tooltip::hide_tooltip\n"; +#endif + + m_counter = 10000; + hide(); + m_label.set_text( std::string() ); +} diff --git a/src/skeleton/tooltip.h b/src/skeleton/tooltip.h new file mode 100644 index 000000000..a88598b31 --- /dev/null +++ b/src/skeleton/tooltip.h @@ -0,0 +1,41 @@ +// ライセンス: 最新のGPL +// +// 自前のツールチップクラス +// + +#ifndef _TOOLTIP_H +#define _TOOLTIP_H + +#include + +namespace SKELETON +{ + class Tooltip : public Gtk::Window + { + Glib::RefPtr< Gdk::GC > m_gc; + Gtk::Label m_label; + + int m_counter; + + // 横幅が m_min_width より大きければ表示 + int m_min_width; + + public: + + Tooltip(); + void clock_in(); + + void set_text( const std::string& text ); + void set_min_width( const int& min_width ){ m_min_width = min_width; } + + void show_tooltip(); + void hide_tooltip(); + + protected: + virtual void on_realize(); + virtual bool on_expose_event( GdkEventExpose* event ); + }; +} + + +#endif diff --git a/src/skeleton/treeview.cpp b/src/skeleton/treeview.cpp new file mode 100644 index 000000000..793520df9 --- /dev/null +++ b/src/skeleton/treeview.cpp @@ -0,0 +1,614 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "treeview.h" +#include "view.h" +#include "popupwin.h" + +#include "config/globalconf.h" + +#include "controlid.h" + +#ifndef MAX +#define MAX( a, b ) ( a > b ? a : b ) +#endif + + +#ifndef MIN +#define MIN( a, b ) ( a < b ? a : b ) +#endif + + +// row -> tree 変換 +#define GET_PATH( row ) get_model()->get_path( row ) + +using namespace SKELETON; + + +JDTreeView::JDTreeView() + : m_reorderable( 0 ), + m_drag( 0 ), + m_popup_win( 0 ), + m_column_for_height( 0 ) +{ +#ifdef _DEBUG + std::cout << "JDTreeView::JDTreeView\n"; +#endif + + set_enable_search( false ); + set_rules_hint( false ); + add_events( Gdk::BUTTON_PRESS_MASK ); + add_events( Gdk::KEY_PRESS_MASK ); + add_events( Gdk::KEY_RELEASE_MASK ); + add_events( Gdk::POINTER_MOTION_MASK ); + add_events( Gdk::LEAVE_NOTIFY_MASK ); + add_events( Gdk::SCROLL_MASK ); + + init_color(); + init_font(); + + get_selection()->set_mode( Gtk::SELECTION_MULTIPLE ); +} + + +JDTreeView::~JDTreeView() +{ + delete_popup(); +} + + + +// +// 色初期化 +// +void JDTreeView::init_color() +{ + // 背景色 + Gdk::Color color; + const int *rgb; + rgb = CONFIG::get_color_back_tree(); + color.set_rgb( rgb[ 0 ], rgb[ 1 ], rgb[ 2 ] ); + modify_base( get_state(), color ); +} + + +// +// フォント初期化 +// +void JDTreeView::init_font() +{ + Pango::FontDescription pfd( CONFIG::get_fontname_tree() ); + pfd.set_weight( Pango::WEIGHT_NORMAL ); + modify_font( pfd ); +} + + +// +// D&D可にする +// +void JDTreeView::set_reorderable_view( bool reorderable ) +{ + if( reorderable ){ + + m_reorderable = true; + std::list< Gtk::TargetEntry > targets; + targets.push_back( Gtk::TargetEntry( "text/plain", Gtk::TARGET_SAME_APP, 0 ) ); + drag_source_set( targets, Gdk::BUTTON1_MASK ); + drag_dest_set( targets ); + } + else{ + m_reorderable = false; + drag_source_unset(); + drag_dest_unset(); + } +} + + + +// +// クロック入力 +// +void JDTreeView::clock_in() +{ + m_tooltip.clock_in(); +} + + + +// +// ツールチップに文字をセット +// +void JDTreeView::set_str_tooltip( const std::string& text ) +{ + m_tooltip.set_text( text ); +} + + +// +// ツールチップ最小幅設定 +// +void JDTreeView::set_tooltip_min_width( const int& min_width ) +{ + m_tooltip.set_min_width( min_width); +} + + +// +// ツールチップを表示 +// +void JDTreeView::show_tooltip() +{ + m_tooltip.show_tooltip(); +} + + +// +// ツールチップ隠す +// +void JDTreeView::hide_tooltip() +{ + m_tooltip.hide_tooltip(); +} + + +// +// ポップアップウィンドウ表示 +// +void JDTreeView::show_popup( const std::string& url, View* view ) +{ + const int mrg = 10; + + if( m_popup_win == NULL || m_popup_win->view() != view ){ + delete_popup(); + m_popup_win = new PopupWin( this, view, mrg ); + m_pre_popup_url = url; + } +} + + +// +// ポップアップウィンドウ削除 +// +void JDTreeView::delete_popup() +{ + if( m_popup_win ){ + delete m_popup_win; + m_popup_win = NULL; + m_pre_popup_url = std::string(); + } +} + + + + +// +// 先頭へ +// +void JDTreeView::goto_top() +{ + Gtk::TreePath path = GET_PATH( *( get_model()->children().begin() ) ); + scroll_to_row( path, 0 ); + set_cursor( path ); +} + + +// +// 一番最後へ +// +void JDTreeView::goto_bottom() +{ + Gtk::TreePath path = GET_PATH( *( get_model()->children().rbegin() ) ); + scroll_to_row( path, 0 ); + set_cursor( path ); +} + + + +// +// 選択行を上へ移動 +// +void JDTreeView::row_up() +{ + Gtk::TreePath path = get_current_path(); + if( !get_row( path ) ) return; + + path = prev_path( path ); + set_cursor( path ); +} + + + +// +// 選択行を下へ移動 +// +void JDTreeView::row_down() +{ + Gtk::TreePath path = get_current_path(); + if( !get_row( path ) ) return; + + path = next_path( path ); + if( path.get_depth() && get_row( path ) ) set_cursor( path ); +} + + + +// +// path の前の path を取得 +// +// check_expand = true なら行が開いてるかチェックして開いて無い時はdown()しない +// +Gtk::TreePath JDTreeView::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.get_depth() >= 2 ) path_out.up(); + + return path_out;; +} + + + +// +// path の次の path を取得 +// +// check_expand = true なら行が開いてるかチェックして開いて無い時はdown()しない +// +Gtk::TreePath JDTreeView::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.get_depth() >=2 ) ) path_out.up(); + + return path_out; +} + + + + +// +// path->row 変換 +// +Gtk::TreeModel::Row JDTreeView::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 JDTreeView::expand_parents( const Gtk::TreePath& path ) +{ + for( int level = 1; level < path.get_depth(); ++level ){ + + Gtk::TreeModel::Row row_tmp = get_row( path ); + for( int i = 0; i < path.get_depth() - level; ++i ){ + if( row_tmp.parent() ) row_tmp = *( row_tmp.parent() ); + } + Gtk::TreePath path_tmp = GET_PATH( row_tmp ); + expand_row( path_tmp, false ); + } +} + + + + +bool JDTreeView::on_button_press_event( GdkEventButton* event ) +{ + m_drag = false; + m_sig_button_press.emit( event ); + + // 複数行選択時の動作 + if( get_selection()->get_selected_rows().size() >= 2 ){ + + // ポップアップメニューボタンを押しても選択解除しない + if( m_control.button_alloted( event, CONTROL::PopupmenuButton ) ) return true; + + // 普通にクリックしても選択解除しない + // 選択解除はD&Dしてなかったら on_button_release_event()で行う + if( !( event->state & GDK_CONTROL_MASK ) ) return true; + } + + return Gtk::TreeView::on_button_press_event( event ); +} + + + + + +bool JDTreeView::on_button_release_event( GdkEventButton* event ) +{ + if( !( event->state & GDK_CONTROL_MASK ) ){ // ctrl + クリック( 複数選択 )してない場合 + + Gtk::TreeModel::Path path = get_path_under_xy( (int)event->x, (int)event->y ); + + // 何もないところをクリックしたら選択解除 + if( !get_row( path ) ) get_selection()->unselect_all(); + + // ポップアップメニューボタン以外のボタンを押したとき、ドラッグ中で無ければ選択 + if( ! m_control.button_alloted( event, CONTROL::PopupmenuButton ) + && get_row( path ) && !m_drag ) set_cursor( path ); + + if( !m_drag ) m_sig_button_release.emit( event ); + } + + return Gtk::TreeView::on_button_release_event( event ); +} + + + +// +// D&D開始 +// +// このtreeがソースで無い時は呼ばれないのに注意 +// +void JDTreeView::on_drag_begin( const Glib::RefPtr< Gdk::DragContext >& context ) +{ + Gtk::TreeModel::Path path = get_path_under_mouse(); + +#ifdef _DEBUG + std::cout << "JDTreeView::on_drag_begin path = " << path.to_string() << std::endl; +#endif + + m_drag = true; + m_sig_drag_begin.emit(); + + return Gtk::TreeView::on_drag_begin( context ); +} + + +// +// D&D中にマウスを動かした +// +// 他のwidgetがソースの時も呼ばれるのに注意 +// またドラッグ中は on_motion_notify_event() は呼ばれない +// +bool JDTreeView::on_drag_motion( const Glib::RefPtr& context, int x, int y, guint time ) +{ + + Gtk::TreeModel::Path path = get_path_under_mouse(); +#ifdef _DEBUG + std::cout << "JDTreeView::on_drag_motion x = " << x << " y = " << y << " path = " << path.to_string() << std::endl; +#endif + + m_sig_drag_motion.emit( path ); + + return Gtk::TreeView::on_drag_motion( context, x, y, time ); +} + + +// +// D&Dでドロップされた +// +// 他のwidgetがソースの時も呼ばれるのに注意 +// +bool JDTreeView::on_drag_drop( const Glib::RefPtr& context, int x, int y, guint time ) +{ +#ifdef _DEBUG + std::cout << "JDTreeView::on_drag_drop\n"; +#endif + + Gtk::TreeModel::Path path = get_path_under_mouse(); +#ifdef _DEBUG + std::cout << "dest x = " << x << " y = " << y << " path = " << path.to_string() << std::endl; +#endif + m_sig_drag_drop.emit( path ); + + return Gtk::TreeView::on_drag_drop( context, x, y, time ); +} + + +// +// D&D 終了 +// +// このtreeがソースで無い時は呼ばれないのに注意 +// +void JDTreeView::on_drag_end( const Glib::RefPtr< Gdk::DragContext >& context ) +{ +#ifdef _DEBUG + std::cout << "JDTreeView::on_drag_end\n"; +#endif + + m_drag = false; + m_sig_drag_end.emit(); + + return Gtk::TreeView::on_drag_end( context ); +} + + + +bool JDTreeView::on_key_press_event( GdkEventKey* event ) +{ + m_sig_key_press.emit( event ); + + return true; +} + + +bool JDTreeView::on_key_release_event( GdkEventKey* event ) +{ + m_sig_key_release.emit( event ); + + return true; +} + + +// マウスを動かした +bool JDTreeView::on_motion_notify_event( GdkEventMotion* event ) +{ +#ifdef _DEBUG +// std::cout << "JDTreeView::on_motion_notify_event x = " << event->x << " y = " << event->y << std::endl; +#endif + m_sig_motion.emit( event ); + + return Gtk::TreeView::on_motion_notify_event( event ); +} + + +// マウスのwheelを回した +bool JDTreeView::on_scroll_event( GdkEventScroll* event ) +{ + Gtk::Adjustment *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 ); + adj->set_value( val ); + +#ifdef _DEBUG + std::cout << "JDTreeView::on_scroll_event\n"; + + std::cout << "scr_inc = " << scr_inc << std::endl; + std::cout << "lower = " << adj->get_lower() << std::endl; + std::cout << "upper = " << 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 + + return true; +} + + + +bool JDTreeView::on_leave_notify_event( GdkEventCrossing* event ) +{ + m_tooltip.hide_tooltip(); + delete_popup(); + return Gtk::TreeView::on_leave_notify_event( event ); +} + + + + +// +// 現在フォーカスしてる行の最初のパスを取得 +// +Gtk::TreeModel::Path JDTreeView::get_current_path() +{ + Gtk::TreeModel::Path path; + + std::list< Gtk::TreeModel::Path > paths = get_selection()->get_selected_rows(); + if( paths.size() ){ + + std::list< Gtk::TreeModel::Path >::iterator it = paths.begin(); + path = ( *it ); + } + + return path; +} + + + +// +// x, y 座標の下のパスを取得 +// +Gtk::TreeModel::Path JDTreeView::get_path_under_xy( int x, int y ) +{ + 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 JDTreeView::get_path_under_mouse() +{ + int x, y; + get_pointer( x, y ); + return get_path_under_xy( x, y ); +} + + +// +// 現在のマウスポインタの下のセルの幅高さとセル内での座標を取得 +// +void JDTreeView::get_cell_xy_wh( int& cell_x, int& cell_y, int& cell_w, int& cell_h ) +{ + cell_x = cell_y = cell_w = cell_h = -1; + + Gtk::TreeModel::Path path; + Gtk::TreeViewColumn* column; + int x, y, o_x, o_y; + Gdk::Rectangle rect; + + get_pointer( x, y ); + get_path_at_pos( x, y, path, column, cell_x, cell_y ); + if( column ) column->cell_get_size( rect, o_x, o_y, cell_w, cell_h ); +} + + +// +// 行のセルの高さ +// +int JDTreeView::get_row_height() +{ + 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; +} + + + +// +// 選択中の Gtk::TreeModel::iterator のリストを取得 +// +// 削除などを実行してから get_model()->get_iter() するとパスが変わってエラーが出るので +// 先に iterator だけ取得しておく +// +std::list< Gtk::TreeModel::iterator > JDTreeView::get_selected_iterators() +{ + std::list< Gtk::TreeModel::iterator > list_it; + + if( get_model() ){ + + std::list< Gtk::TreeModel::Path > paths = get_selection()->get_selected_rows(); + std::list< Gtk::TreeModel::Path >::iterator it = paths.begin(); + for( ; it != paths.end(); ++it ) list_it.push_back( get_model()->get_iter( ( *it ) ) ); + } + + return list_it; +} + + diff --git a/src/skeleton/treeview.h b/src/skeleton/treeview.h new file mode 100644 index 000000000..1b8f9bf3b --- /dev/null +++ b/src/skeleton/treeview.h @@ -0,0 +1,164 @@ +// ライセンス: 最新のGPL +// +// treeviewクラス +// + +#ifndef _TREEVIEW_H +#define _TREEVIEW_H + +#include "tooltip.h" + +#include "control.h" + +#include +#include + +namespace SKELETON +{ + class View; + class PopupWin; + + class JDTreeView : public Gtk::TreeView + { + typedef sigc::signal< bool, GdkEventKey* > SIG_KEY_PRESS; + typedef sigc::signal< bool, GdkEventKey* > SIG_KEY_RELEASE; + typedef sigc::signal< bool, GdkEventButton* > SIG_BUTTON_PRESS; + typedef sigc::signal< bool, GdkEventButton* > SIG_BUTTON_RELEASE; + typedef sigc::signal< bool, GdkEventMotion* > SIG_MOTION; + + typedef sigc::signal< void > SIG_DRAG_BEGIN; + typedef sigc::signal< void, Gtk::TreeModel::Path > SIG_DRAG_MOTION; + typedef sigc::signal< void, Gtk::TreeModel::Path > SIG_DRAG_DROP; + typedef sigc::signal< void > SIG_DRAG_END; + + + SIG_KEY_PRESS m_sig_key_press; + SIG_KEY_RELEASE m_sig_key_release; + SIG_BUTTON_PRESS m_sig_button_press; + SIG_BUTTON_RELEASE m_sig_button_release; + SIG_MOTION m_sig_motion; + + SIG_DRAG_BEGIN m_sig_drag_begin; + SIG_DRAG_MOTION m_sig_drag_motion; + SIG_DRAG_DROP m_sig_drag_drop; + SIG_DRAG_END m_sig_drag_end; + + bool m_reorderable; + bool m_drag; + + Tooltip m_tooltip; + + // ポップアップウィンドウ用 + PopupWin* m_popup_win; + std::string m_pre_popup_url; + + // get_row_height() で高さを取得するためのcolumn番号 + int m_column_for_height; + + // 入力コントローラ + CONTROL::Control m_control; + + 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; } + SIG_BUTTON_RELEASE sig_button_release() { return m_sig_button_release; } + SIG_MOTION sig_motion() { return m_sig_motion; } + + SIG_DRAG_BEGIN sig_drag_begin() { return m_sig_drag_begin; } + SIG_DRAG_MOTION sig_drag_motion() { return m_sig_drag_motion; } + SIG_DRAG_DROP sig_drag_drop() { return m_sig_drag_drop; } + SIG_DRAG_END sig_drag_end() { return m_sig_drag_end; } + + JDTreeView(); + ~JDTreeView(); + + + const bool reorderable() const { return m_reorderable; } + void clock_in(); + + // 色初期化 + void init_color(); + + // フォント初期化 + void init_font(); + + // D&D可で並び替え可 + void set_reorderable_view( bool reorderable ); + + // 現在フォーカスしてる行の最初のパスを取得 + Gtk::TreeModel::Path get_current_path(); + + //x, y 座標の下のパスを取得 + Gtk::TreeModel::Path get_path_under_xy( int x, int y ); + + // 現在のマウスポインタの下のパスを取得 + Gtk::TreeModel::Path get_path_under_mouse(); + + // 現在のマウスポインタの下のセルの幅高さとセル内での座標を取得 + void get_cell_xy_wh( int& cell_x, int& cell_y, int& cell_w, int& cell_h ); + + // 選択中の Gtk::TreeModel::iterator のリストを取得 + std::list< Gtk::TreeModel::iterator > get_selected_iterators(); + + // ツールチップ表示 + // set_tooltip_min_width()で指定した幅よりもツールチップが広い場合は表示 + void set_str_tooltip( const std::string& str ); + void set_tooltip_min_width( const int& min_width ); + void hide_tooltip(); + void show_tooltip(); + + // ポップアップウィンドウ表示 + const std::string& pre_popup_url() const { return m_pre_popup_url; } + void show_popup( const std::string& url, View* view ); + void delete_popup(); + + // 選択行の移動 + void goto_top(); + void goto_bottom(); + void row_up(); + void row_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 ); + + // 行のセルの高さ + int get_row_height(); + void set_column_for_height( int column ){ m_column_for_height = column; } + + protected: + + // 呼び出される順番は + // + // (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 + // + virtual bool on_button_press_event( GdkEventButton* event ); + virtual void on_drag_begin( const Glib::RefPtr< Gdk::DragContext>& context ); + virtual bool on_drag_motion( const Glib::RefPtr& context, int x, int y, guint time ); + virtual bool on_button_release_event( GdkEventButton* event ); + virtual bool on_drag_drop( const Glib::RefPtr& context, int x, int y, guint time ); + virtual void on_drag_end( const Glib::RefPtr< Gdk::DragContext>& context ); + + virtual bool on_key_press_event( GdkEventKey* event ); + virtual bool on_key_release_event( GdkEventKey* event ); + + virtual bool on_motion_notify_event( GdkEventMotion* event ); + virtual bool on_scroll_event( GdkEventScroll* event ); + virtual bool on_leave_notify_event( GdkEventCrossing* event ); + }; +} + +#endif diff --git a/src/skeleton/view.cpp b/src/skeleton/view.cpp new file mode 100644 index 000000000..bf77e539c --- /dev/null +++ b/src/skeleton/view.cpp @@ -0,0 +1,13 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "view.h" + +using namespace SKELETON; + + +View::View( const std::string& url, const std::string& arg1 ,const std::string& arg2 ) + : m_url( url ), m_status( std::string() ), m_enable_mg( 0 ) +{} diff --git a/src/skeleton/view.h b/src/skeleton/view.h new file mode 100644 index 000000000..05f16151b --- /dev/null +++ b/src/skeleton/view.h @@ -0,0 +1,120 @@ +// ライセンス: 最新のGPL + +#ifndef _VIEW_H +#define _VIEW_H + +#include +#include + +#include "tablabel.h" + +#include "control.h" + +namespace SKELETON +{ + // 自分がポップアップviewの時に(ポップアップウィンドウ経由で)親widgetにhideを依頼するシグナル + typedef sigc::signal< void > SIG_HIDE_POPUP; + + // 自分がポップアップviewの時にポップアップウィンドウにリサイズを依頼するシグナル + 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; + + // notebookのタブに張り付けるラベル + SKELETON::TabLabel m_label; + + // クライアント領域の幅、高さ + int m_width_client; + int m_height_client; + + // タブに表示する文字列 + // m_str_label にラベルの文字列を入れた後に Adminに set_tablabel コマンドを送ると表示される + std::string m_str_label; + + // 入力コントローラ + CONTROL::Control m_control; + + // ポップアップメニュー + Glib::RefPtr< Gtk::ActionGroup > m_action_group; + Glib::RefPtr< Gtk::UIManager > m_ui_manager; + + // メインウィンドウのステータスバーに表示する文字 + std::string m_status; + + // true ならマウスジェスチャ使用 + bool m_enable_mg; + + protected: + + // 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_status( const std::string& status ){ m_status = status; } + + // マウスジェスチャ + void set_enable_mg( bool mg ){ m_enable_mg = mg; } + const bool enable_mg() const { return m_enable_mg; } + + + public: + + SIG_HIDE_POPUP sig_hide_popup(){ return m_sig_hide_popup; } + SIG_RESIZE_POPUP sig_resize_popup(){ return m_sig_resize_popup; } + + View( const std::string& url, const std::string& arg1 = std::string(), const std::string& arg2 = std::string() ); + virtual ~View(){} + + virtual const std::string& get_url(){ return m_url; } + void set_url( const std::string& url ){ m_url = url; } + + // 各view個別のコマンド + virtual bool set_command( const std::string& command, const std::string& arg = std::string() ){ return true; } + + // コピー用のURL + virtual const std::string url_for_copy(){ return m_url; } + + // メインウィンドウのステータスバーに表示する文字列 + virtual const std::string& get_status(){ return m_status; } + + // クライアント領域の幅、高さ + virtual const int width_client(){ return m_width_client; } + virtual const int height_client(){ return m_height_client; } + void set_width_client( int val ){ m_width_client = val; } + void set_height_client( int val ){ m_height_client = val; } + + // notebookのタブに張り付けるラベル + SKELETON::TabLabel& get_tab_label() { return m_label; } + + // shutdown( SIGHUP )用 + virtual void shutdown(){} + + virtual void clock_in(){} + 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(){ m_control.MG_reset(); } + virtual void close_view(){} + virtual void delete_view(){} + virtual void update_item( const std::string& ){} + virtual void operate_view( const int& ){} + virtual void goto_top(){} + virtual void goto_bottom(){} + }; +} + +#endif diff --git a/src/viewfactory.cpp b/src/viewfactory.cpp new file mode 100644 index 000000000..ca5556dff --- /dev/null +++ b/src/viewfactory.cpp @@ -0,0 +1,106 @@ +// ライセンス: 最新のGPL + +#include "viewfactory.h" + +#include "bbslist/bbslistview.h" +#include "bbslist/etclistview.h" +#include "bbslist/favoriteview.h" +#include "bbslist/selectlistview.h" + +#include "board/boardview.h" + +#include "article/articleview.h" +#include "article/articleviewpreview.h" +#include "article/articleviewpopup.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 args ) +{ + switch( type ) + { + case VIEW_BBSLISTVIEW: + return new BBSLIST::BBSListViewMain( url, args.arg1, args.arg2 ); + + case VIEW_ETCLIST: + return new BBSLIST::EtcListView( url, args.arg1, args.arg2 ); + + case VIEW_FAVORITELIST: + return new BBSLIST::FavoriteListView( url, args.arg1, args.arg2 ); + + case VIEW_SELECTLIST: + return new BBSLIST::SelectListView( url, args.arg1, args.arg2 ); + + ////////////////// + + case VIEW_BOARDVIEW: + return new BOARD::BoardView( url, args.arg1, args.arg2 ); + + ///////////////// + + case VIEW_ARTICLEVIEW: + return new ARTICLE::ArticleViewMain( url ); + + case VIEW_ARTICLERES: + return new ARTICLE::ArticleViewRes( url, args.arg1, ( args.arg2 == "true" ), args.arg3 ); + + case VIEW_ARTICLEID: + return new ARTICLE::ArticleViewID( url, args.arg1 ); + + case VIEW_ARTICLEBM: + return new ARTICLE::ArticleViewBM( url ); + + case VIEW_ARTICLEURL: + return new ARTICLE::ArticleViewURL( url ); + + case VIEW_ARTICLEREFER: + return new ARTICLE::ArticleViewRefer( url, args.arg1 ); + + case VIEW_ARTICLEDRAWOUT: + return new ARTICLE::ArticleViewDrawout( url, args.arg1, ( args.arg2 == "OR" ) ); + + case VIEW_ARTICLEPREVIEW: + return new ARTICLE::ArticleViewPreview( url ); + + ///////////////// + + case VIEW_ARTICLEPOPUPHTML: + return new ARTICLE::ArticleViewPopupHTML( url, args.arg1 ); + + case VIEW_ARTICLEPOPUPRES: + return new ARTICLE::ArticleViewPopupRes( url, args.arg1, ( args.arg2 == "true" ), ( args.arg3 == "true" ) ); + + case VIEW_ARTICLEPOPUPID: + return new ARTICLE::ArticleViewPopupID( url, args.arg1 ); + + case VIEW_ARTICLEPOPUPREFER: + return new ARTICLE::ArticleViewPopupRefer( url, args.arg1 ); + + ///////////////// + + 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, args.arg1 ); + + case VIEW_NEWTHREAD: + return new MESSAGE::MessageViewNew( url, args.arg1 ); + + default: + return NULL; + } +} + diff --git a/src/viewfactory.h b/src/viewfactory.h new file mode 100644 index 000000000..80341c97d --- /dev/null +++ b/src/viewfactory.h @@ -0,0 +1,64 @@ +// ライセンス: 最新のGPL + +// +// SKELETON::VIEWのファクトリー +// + +#ifndef _VIEWFACTORY_H +#define _VIEWFACTORY_H + +#include +#include + +namespace SKELETON +{ + class View; +} + +namespace CORE +{ + enum + { + VIEW_BBSLISTVIEW, + VIEW_FAVORITELIST, + VIEW_ETCLIST, + VIEW_SELECTLIST, + + VIEW_BOARDVIEW, + + VIEW_ARTICLEVIEW, + VIEW_ARTICLERES, + VIEW_ARTICLEID, + VIEW_ARTICLEBM, + VIEW_ARTICLEURL, + VIEW_ARTICLEREFER, + VIEW_ARTICLEDRAWOUT, + VIEW_ARTICLEPREVIEW, + + VIEW_ARTICLEPOPUPHTML, + VIEW_ARTICLEPOPUPRES, + VIEW_ARTICLEPOPUPID, + VIEW_ARTICLEPOPUPREFER, + + 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 args = VIEWFACTORY_ARGS() ); +} + +#endif diff --git a/src/winmain.cpp b/src/winmain.cpp new file mode 100644 index 000000000..6a8595cac --- /dev/null +++ b/src/winmain.cpp @@ -0,0 +1,132 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "winmain.h" +#include "command.h" +#include "core.h" +#include "session.h" + +#include "icons/icon_jd16.h" +#include "icons/icon_jd32.h" +#include "icons/icon_jd48.h" + +#include "jdlib/loader.h" + +WinMain::WinMain( bool init ) + : Gtk::Window( Gtk::WINDOW_TOPLEVEL ) + , m_core( 0 ), m_maximized( 0 ) +{ +#ifdef _DEBUG + std::cout << "WinMain::WinMain init = " << init << std::endl; +#endif + + setlocale( LC_ALL, "ja_JP.UTF-8" ); + + // GLIBのスレッドシステム初期化 + if( !Glib::thread_supported() ) Glib::thread_init(); + assert( Glib::thread_supported() ); + + // アイコン + std::list< Glib::RefPtr< Gdk::Pixbuf > > list_icons; + list_icons.push_back( Gdk::Pixbuf::create_from_inline( sizeof( icon_jd16_png ), icon_jd16_png ) ); + list_icons.push_back( Gdk::Pixbuf::create_from_inline( sizeof( icon_jd32_png ), icon_jd32_png ) ); + list_icons.push_back( Gdk::Pixbuf::create_from_inline( sizeof( icon_jd48_png ), icon_jd48_png ) ); + Gtk::Window::set_default_icon_list( list_icons ); + + // セッション回復 + SESSION::init_session(); + + // サイズ設定 + resize( SESSION::width(), SESSION::height() ); + move( SESSION::x(), SESSION::y() ); + if( SESSION::maximized() ) maximize(); + + set_modal( false ); + property_window_position().set_value( Gtk::WIN_POS_NONE ); + set_resizable( true ); + property_destroy_with_parent().set_value( false ); + + // 後はcoreを作って任せる + m_core = new class CORE::Core( *this ); + m_core->run( init ); +} + + +WinMain::~WinMain() +{ +#ifdef _DEBUG + std::cout << "WinMain::~WinMain\n"; +#endif + + save_session(); +} + + +// 緊急シャットダウン +void WinMain::shutdown() +{ +#ifdef _DEBUG + std::cout << "WinMain::shutdown\n"; +#endif + + if( m_core ) m_core->shutdown(); +} + + +// 通常のセッション保存 +void WinMain::save_session() +{ +#ifdef _DEBUG + std::cout << "WinMain::save_session\n"; +#endif + + if( m_core ){ + + delete m_core; + m_core = NULL; + + if( get_window() ){ + + // ウィンドウのサイズや位置を保存 + int x, y, width, height;; + get_window()->get_root_origin( x, y ); + get_size( width, height ); + x = MAX( 0, x ); + y = MAX( 0, y ); + +#ifdef _DEBUG + std::cout << "window size : x = " << x << " y = " << y << " w = " << width << " h = " << height + << " max = " << m_maximized << std::endl; +#endif + + if( !m_maximized ){ + SESSION::set_x( x ); + SESSION::set_y( y ); + SESSION::set_width( width ); + SESSION::set_height( height ); + } + SESSION::set_maximized( m_maximized ); + } + + SESSION::save_session(); + + JDLIB::check_loader_alive(); + } +} + + +bool WinMain::on_window_state_event( GdkEventWindowState* event ) +{ + m_maximized = event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED; + +#ifdef _DEBUG + std::cout << "WinMain::on_window_state_event : maximized = " << m_maximized << std::endl; +#endif + + // タブ幅調整 + CORE::core_set_command( "adjust_tabwidth" ); + + return Gtk::Window::on_window_state_event( event ); +} diff --git a/src/winmain.h b/src/winmain.h new file mode 100644 index 000000000..23625e4dc --- /dev/null +++ b/src/winmain.h @@ -0,0 +1,36 @@ +// ライセンス: 最新のGPL +// +// メインウィンドウとmain関数 +// + +#ifndef _MAINWIN_H +#define _MAINWIN_H + +#include + +namespace CORE +{ + class Core; +} + +class WinMain : public Gtk::Window +{ + CORE::Core* m_core; + bool m_maximized; + + public: + WinMain( bool init ); + ~WinMain(); + + // 緊急シャットダウン + void shutdown(); + + // 通常のセッション保存 + void save_session(); + + protected: + virtual bool on_window_state_event( GdkEventWindowState* event ); +}; + + +#endif diff --git a/src/xml.cpp b/src/xml.cpp new file mode 100644 index 000000000..086b76c84 --- /dev/null +++ b/src/xml.cpp @@ -0,0 +1,60 @@ +// ライセンス: 最新のGPL + +//#define _DEBUG +#include "jddebug.h" + +#include "xml.h" +#include "global.h" + +#include "jdlib/miscutil.h" +#include "jdlib/jdregex.h" + +// 行のタイプ判定 +int XML::get_type( const std::string& line, std::string& url, std::string& name ) +{ + JDLIB::Regex regex; + + int type = TYPE_UNKNOWN; + + for(;;){ + + if( regex.exec( "", line, 0, true ) ){ + type = TYPE_THREAD; + break; + } + if( regex.exec( "", line, 0, true ) ){ + type = TYPE_BOARD; + break; + } + if( regex.exec( "", line, 0, true ) ){ + type = TYPE_DIR; + break; + } + if( regex.exec( "", line, 0, true ) ){ + type = TYPE_IMAGE; + break; + } + if( regex.exec( "", line, 0, true ) ){ + type = TYPE_COMMENT; + break; + } + if( regex.exec( "", line, 0, true ) ){ + type = TYPE_LINK; + break; + } + +#ifdef _DEBUG + std::cout << "XML::get_type failed : " << line << std::endl; +#endif + return type; + } + + url = regex.str( 2 ); + name = MISC::recover_quot( regex.str( 3 ) ); + +#ifdef _DEBUG + std::cout << url << " " << name << std::endl; +#endif + + return type; +} diff --git a/src/xml.h b/src/xml.h new file mode 100644 index 000000000..019e852cf --- /dev/null +++ b/src/xml.h @@ -0,0 +1,38 @@ +// ライセンス: 最新のGPL + +// +// XML関係のマクロとか +// + +#ifndef _XML_H +#define _XML_H + +#include + +#define XML_MAKE_DIR(name) do{ \ + xml << "\n"; }while(0) + +#define XML_MAKE_BOARD(url,name) do{ \ + xml << "\n"; }while(0) + +#define XML_MAKE_THREAD(url,name) do{ \ + xml << "\n"; }while(0) + +#define XML_MAKE_IMAGE(url,name) do{ \ + xml << "\n"; }while(0) + +#define XML_MAKE_COMMENT(name) do{ \ + xml << "\n"; }while(0) + +#define XML_MAKE_LINK(url,name) do{ \ + xml << "\n"; }while(0) + +namespace XML +{ + // 行のパースとタイプ判定 + // url, name に値が入る。戻り値はタイプ + int get_type( const std::string& line, std::string& url, std::string& name ); +} + + +#endif