OSDN Git Service

Add a border to the popups.
[fukui-no-namari/dialektos.git] / src / thread_window.cxx
1 /*
2  * Copyright (C) 2009 by Aiwota Programmer
3  * aiwotaprog@tetteke.tk
4  *
5  * This file is part of Dialektos.
6  *
7  * Dialektos is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation, either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * Dialektos is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with Dialektos.  If not, see <http://www.gnu.org/licenses/>.
19  */
20
21 #include "thread_window.hxx"
22
23 #include <glibmm/convert.h>
24 #include <gtkmm/stock.h>
25 #include <boost/date_time/posix_time/posix_time.hpp>
26 #include <boost/filesystem.hpp>
27 #include <boost/foreach.hpp>
28 #include <boost/format.hpp>
29 #include <iostream>
30 #include <fstream>
31 #include <sstream>
32 #include <cstdlib>
33 #include "bbs_detail_base.hxx"
34 #include "thread_idx.hxx"
35 #include "http_get.hxx"
36 #include "misc.hxx"
37 #include "uri_opener.hxx"
38 #include "thread_window_state.hxx"
39 #include "board_window.hxx"
40
41
42 namespace dialektos {
43
44
45 void ThreadWindow::create(std::auto_ptr<bbs_detail::Base> bbs) {
46   regist(new ThreadWindow(bbs));
47 }
48
49 ThreadWindow::ThreadWindow(std::auto_ptr<bbs_detail::Base> bbs) :
50   SuperClass(),
51   text_view_(new text_view::TextView),
52   scrolled_(text_view_->get_adjustment()),
53   bbs_(bbs), http_getter_(), idx_() {
54
55   // additional menuitems for board window
56   action_group_->add(Gtk::Action::create("FileDelete", Gtk::Stock::DELETE),
57       sigc::mem_fun(*this, &ThreadWindow::on_action_file_delete));
58   action_group_->add(
59       Gtk::Action::create("FileBoard", Gtk::Stock::GO_UP, "Show _Board"),
60       sigc::mem_fun(*this, &ThreadWindow::on_action_file_board));
61   action_group_->add(
62       Gtk::Action::create(
63           "FileCloseRemoveHistory", Gtk::Stock::CLEAR, "CloseH",
64           "Close and Remove from History"),
65       sigc::mem_fun(*this,
66           &ThreadWindow::on_action_file_close_remove_history));
67
68   Glib::ustring ui =
69     "<ui>"
70     "  <menubar name='MenuBar'>"
71     "    <menu action='MenuFile'>"
72     "      <menuitem action='FileDelete'/>"
73     "      <menuitem action='FileCloseRemoveHistory'/>"
74     "      <separator/>"
75     "      <menuitem action='FileBoard'/>"
76     "    </menu>"
77     "  </menubar>"
78     "  <toolbar name='ToolBar'>"
79     "    <toolitem action='FileCloseRemoveHistory'/>"
80     "  </toolbar>"
81     "</ui>";
82
83   ui_manager_->add_ui_from_string(ui);
84
85   add(scrolled_);
86   initialize_text_view();
87
88   ThreadWindowState state;
89   state.from_xml(boost::filesystem::path(bbs_->get_thread_state_path()));
90   set_default_size(state.width, state.height);
91   if (state.menubar) menubar_->show();
92   if (state.toolbar) toolbar_->show();
93   if (state.statusbar) statusbar_.show();
94
95   show_all();
96
97   load();
98   text_view_->jump_to_res_num(state.displayed_res_num);
99 }
100
101 void ThreadWindow::initialize_text_view() {
102   scrolled_.add(*text_view_);
103
104   text_view_->signal_button_press_event().connect_notify(
105       sigc::mem_fun(*this, &ThreadWindow::on_child_button_press));
106   text_view_->signal_uri_clicked().connect(
107       sigc::mem_fun(*this, &ThreadWindow::on_uri_clicked));
108
109   text_view_->show();
110 }
111
112 bool ThreadWindow::load() {
113   using namespace boost::posix_time;
114   ptime start = microsec_clock::local_time();
115
116   if (bbs_) {
117
118     bbs_->load_thread(*text_view_);
119     text_view_->relayout();
120     text_view_->queue_draw();
121
122     std::cout <<
123     to_simple_string(microsec_clock::local_time() - start) << std::endl;
124
125     idx_ = ThreadIdx::from_xml(bbs_->get_thread_idx_path());
126     set_title(idx_.title_);
127     histories.push(bbs_->get_thread_uri(), idx_.title_);
128
129     if (text_view_->get_res_num() == 0) on_action_view_refresh();
130   }
131
132   return false;
133 }
134
135 bool ThreadWindow::is_same(const bbs_detail::Base& bbs) const {
136   const bbs_detail::Base& lhs = *bbs_;
137   return lhs == bbs;
138 }
139
140 std::string ThreadWindow::get_uri() const {
141   return bbs_->get_thread_uri();
142 }
143
144
145 void ThreadWindow::on_action_view_refresh() {
146   if (http_getter_) return;
147
148   statusbar_.push("HTTP/1.0 GET...");
149
150   const std::string uri = bbs_->get_thread_dat_uri();
151   http::Header request_header = bbs_->get_thread_dat_request_header();
152   if (!request_header.get_range().empty()) {
153     request_header.set_if_modified_since(idx_.last_modified_);
154     request_header.set_if_none_match(idx_.etag_);
155   }
156
157   http_getter_.reset(new http::GetInThread(uri, request_header));
158   http_getter_->signal_end().connect(
159       sigc::mem_fun(*this, &ThreadWindow::on_http_get_end));
160   http_getter_->run();
161 }
162
163 void ThreadWindow::on_action_view_stop() {
164   if (http_getter_) http_getter_->cancel();
165 }
166
167 void ThreadWindow::on_action_edit_copy() {
168   const Glib::ustring selected = text_view_->get_selected_text();
169   Gtk::Clipboard::get()->set_text(selected);
170 }
171
172 void ThreadWindow::on_action_file_delete() {
173   const boost::filesystem::path dat(bbs_->get_thread_file_path());
174   try {
175     if (boost::filesystem::exists(dat)) boost::filesystem::remove(dat);
176   } catch (const boost::filesystem::filesystem_error& e) {
177     std::cerr << e.what() << std::endl;
178   }
179
180   const boost::filesystem::path xml(bbs_->get_thread_idx_path());
181   try {
182     if (boost::filesystem::exists(xml)) boost::filesystem::remove(xml);
183   } catch (const boost::filesystem::filesystem_error& e) {
184     std::cerr << e.what() << std::endl;
185   }
186 }
187
188 void ThreadWindow::on_action_file_board() {
189   uri_opener::open(bbs_->get_board_uri());
190 }
191
192 void ThreadWindow::on_action_file_close_remove_history() {
193   histories.erase(bbs_->get_thread_uri());
194
195   std::vector<ApplicationWindow*> closes;
196   closes.push_back(this);
197   close_windows(closes);
198 }
199
200 void ThreadWindow::on_http_get_end(bool success) {
201 //  const std::string uri = http_getter_->get_uri();
202 //  const http::Header request_header = http_getter_->get_request_header();
203   const http::Response response = http_getter_->get_response();
204   const boost::system::error_code err = http_getter_->get_error();
205   http_getter_.reset(0);
206   if (err) {
207     statusbar_.push(err.message());
208     return;
209   }
210   if (!success) {
211     statusbar_.push("Canceled.");
212     return;
213   }
214
215   on_refresh_end(response.get_status_line(), response.get_header());
216
217   const int code = response.get_status_line().get_code();
218   if (code != 200 && code != 206) return;
219
220   if (code == 200) {
221     // initialize text_view_
222     scrolled_.remove(*text_view_);
223     text_view_.reset(new text_view::TextView);
224     scrolled_.set_adjustment(text_view_->get_adjustment());
225
226     initialize_text_view();
227   }
228
229   bbs_->load_thread_from_string(response.get_content(), *text_view_);
230   text_view_->relayout();
231   text_view_->queue_draw();
232
233   save_content(response);
234   inform_to_board(idx_);
235 }
236
237 void ThreadWindow::on_refresh_end(const http::StatusLine& status,
238     const http::Header& header) {
239   std::string message = status.get_line();
240   const std::string last_modified = header.get_last_modified();
241   if (!last_modified.empty()) {
242     message += " ";
243     message += last_modified;
244   }
245   statusbar_.push(message);
246 }
247
248 void ThreadWindow::save_content(const http::Response& response) {
249   const int code = response.get_status_line().get_code();
250   if (code != 200 && code != 206) return;
251
252   if (!misc::create_directories(boost::filesystem::path(
253       bbs_->get_thread_file_path()).parent_path())) return;
254   std::ofstream ofs(bbs_->get_thread_file_path().c_str(),
255       code == 200 ?
256           std::ios::out | std::ios::trunc : std::ios::out | std::ios::app);
257   ofs << response.get_content();
258   ofs.close();
259
260   if (code == 200) {
261     idx_.title_ = bbs_->get_title_from_string(response.get_content());
262     set_title(idx_.title_);
263     histories.push(bbs_->get_thread_uri(), idx_.title_);
264   }
265
266   idx_.last_modified_ = response.get_header().get_last_modified();
267   idx_.etag_ = response.get_header().get_etag();
268
269   idx_.line_count_ = text_view_->get_res_num();
270
271   idx_.to_xml(boost::filesystem::path(bbs_->get_thread_idx_path()));
272 }
273
274 void ThreadWindow::save_state() const {
275   ThreadWindowState state;
276   state.width = get_width();
277   state.height = get_height();
278   state.menubar = menubar_->is_visible();
279   state.toolbar = toolbar_->is_visible();
280   state.statusbar = statusbar_.is_visible();
281   state.displayed_res_num = text_view_->get_displayed_res_num();
282   state.to_xml(boost::filesystem::path(bbs_->get_thread_state_path()));
283 }
284
285 void ThreadWindow::on_informed_from_board(const int res_num) {
286   if (res_num == 0) {
287     statusbar_.push("Dropped.");
288   } else if (res_num > idx_.line_count_) {
289     std::stringstream ss;
290     ss << boost::format("Update. %1% -> %2%") % idx_.line_count_ % res_num;
291     statusbar_.push(ss.str());
292   }
293 }
294
295
296 void ThreadWindow::inform_to_board(const ThreadIdx& idx) const {
297   BOOST_FOREACH(ApplicationWindow& window, windows) {
298     if (BoardWindow* board_window = dynamic_cast<BoardWindow*>(&window)) {
299       board_window->on_informed_from(*bbs_, idx);
300     }
301   }
302 }
303
304 void ThreadWindow::on_uri_clicked(const std::string& uri) const {
305   if (uri_opener::open(uri)) return;
306   std::stringstream ss;
307   ss << boost::format("firefox %1%") % uri;
308   std::system(ss.str().c_str());
309 }
310
311 } // namespace dialektos