1 # Copyright (C) 2006 by Aiwota Programmer
2 # aiwotaprog@tetteke.tk
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 2 of the License, or
7 # (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
31 from threadlistmodel import ThreadListModel
32 from BbsType import bbs_type_judge_uri
37 GLADE_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)),
39 GLADE_FILENAME = "board_window.glade"
41 def open_board(uri, update=False):
43 raise ValueError, "parameter must not be empty"
45 winwrap = session.get_window(uri)
48 winwrap.window.present()
52 winwrap = WinWrap(uri)
56 class WinWrap(winwrapbase.WinWrapBase):
58 def __init__(self, uri):
60 self.bbs_type = bbs_type_judge_uri.get_type(uri)
61 self.bbs = self.bbs_type.bbs_type
62 self.host = self.bbs_type.host
63 self.board = self.bbs_type.board
64 self.uri = self.bbs_type.uri
66 glade_path = os.path.join(GLADE_DIR, GLADE_FILENAME)
67 self.widget_tree = gtk.glade.XML(glade_path)
69 self.window = self.widget_tree.get_widget("board_window")
71 self.window.set_title(self.uri)
73 self.treeview = self.widget_tree.get_widget("treeview")
74 self.treeview.set_model(ThreadListModel())
76 self.popupmenu = self.widget_tree.get_widget("popup_menu")
77 self.toolbar = self.widget_tree.get_widget("toolbar")
78 self.toolbar.unset_style()
79 self.statusbar = self.widget_tree.get_widget("appbar")
81 renderer = gtk.CellRendererText()
83 self.treeviewcolumn = {}
84 for i in range(1, len(ThreadListModel.column_names)):
85 column_name = ThreadListModel.column_names[i]
86 self.treeviewcolumn[column_name] = gtk.TreeViewColumn(
87 column_name, renderer)
88 self.treeviewcolumn[column_name].set_resizable(True)
89 self.treeviewcolumn[column_name].set_reorderable(True)
90 self.treeviewcolumn[column_name].set_clickable(True)
91 self.treeviewcolumn[column_name].set_cell_data_func(
92 renderer, self.on_cell_data, column_name)
93 self.treeviewcolumn[column_name].connect(
94 "clicked", self.on_column_clicked, column_name)
95 self.treeviewcolumn[column_name].set_sizing(
96 gtk.TREE_VIEW_COLUMN_FIXED)
97 self.treeviewcolumn[column_name].set_min_width(20)
98 self.treeviewcolumn[column_name].set_fixed_width(
99 ThreadListModel.column_width[i])
100 self.treeview.append_column(self.treeviewcolumn[column_name])
102 self.treeviewcolumn["lastModified"].set_cell_data_func(
103 renderer, self.on_data_lastmodified)
105 self.treeview.set_fixed_height_mode(True)
107 sigdic = {"on_board_window_destroy": self.on_board_window_destroy,
108 "on_quit_activate": self.on_quit_activate,
109 "on_refresh_activate": self.on_refresh_activate,
110 "on_treeview_row_activated":
111 lambda w,p,v: self.on_open_thread(w),
112 "on_treeview_button_press_event":
113 self.on_treeview_button_press_event,
115 self.on_close_activate,
116 "on_toolbar_activate": self.on_toolbar_activate,
117 "on_statusbar_activate": self.on_statusbar_activate,
118 "on_board_window_delete_event":
119 self.on_board_window_delete_event,
120 "on_popup_menu_open_activate": self.on_open_thread}
121 self.widget_tree.signal_autoconnect(sigdic)
130 self.window.destroy()
133 return self.bbs_type.get_uri_base()
135 def on_toolbar_activate(self, widget):
136 if self.toolbar.parent.get_property("visible"):
137 self.toolbar.parent.hide()
139 self.toolbar.parent.show()
141 def on_statusbar_activate(self, widget):
142 if self.statusbar.get_property("visible"):
143 self.statusbar.hide()
145 self.statusbar.show()
147 def updated_thread_highlight(self, column, cell, model, iter):
149 def is_updated_thread():
150 res = model.get_value(
151 iter, ThreadListModel.column_names.index("res"))
152 linecount = model.get_value(
153 iter, ThreadListModel.column_names.index("lineCount"))
154 return res != 0 and linecount != 0 and res > linecount
156 if is_updated_thread():
157 cell.set_property("weight", 800)
159 cell.set_property("weight", 400)
161 def on_cell_data(self, column, cell, model, iter, column_name):
162 self.updated_thread_highlight(column, cell, model, iter)
163 column_num = ThreadListModel.column_names.index(column_name)
164 value = model.get_value(iter, column_num)
165 if model.get_column_type(column_num) == gobject.TYPE_INT:
167 cell.set_property("text", "")
169 cell.set_property("text", str(value))
171 cell.set_property("text", value)
173 def on_data_lastmodified(self, column, cell, model, iter, user_data=None):
174 self.updated_thread_highlight(column, cell, model, iter)
175 lastmod = model.get_value(
176 iter, ThreadListModel.column_names.index("lastModified"))
178 cell.set_property("text", "")
180 cell.set_property("text", time.strftime(
181 "%Y/%m/%d(%a) %H:%M:%S", time.localtime(lastmod)))
183 def on_board_window_delete_event(self, widget, event):
187 def on_board_window_destroy(self, widget):
190 def on_quit_activate(self, widget):
193 def on_close_activate(self, widget):
196 def on_refresh_activate(self, widget):
197 t = board_data.GetRemote(
198 self.bbs, self.board, self.bbs_type.get_subject_txt_uri(),
199 self.update_datastore)
202 def on_column_clicked(self, treeviewcolumn, column_name):
203 model = self.treeview.get_model()
205 model.sort(column_name)
206 self.reset_sort_indicator()
208 def reset_sort_indicator(self):
209 model = self.treeview.get_model()
211 sort_column_name, sort_reverse = model.get_sort()
212 for name,column in self.treeviewcolumn.iteritems():
213 column.set_sort_indicator(False)
214 if sort_column_name != "num" or sort_reverse:
215 self.treeviewcolumn[sort_column_name].set_sort_indicator(True)
217 self.treeviewcolumn[sort_column_name].set_sort_order(
220 self.treeviewcolumn[sort_column_name].set_sort_order(
223 def on_open_thread(self, widget):
224 treeselection = self.treeview.get_selection()
225 model, iter = treeselection.get_selected()
229 thread = model.get_value(iter, ThreadListModel.column_names.index("id"))
230 title = model.get_value(
231 iter, ThreadListModel.column_names.index("title"))
232 print thread + ':"' + title + '"', "activated"
234 res = model.get_value(iter, ThreadListModel.column_names.index("res"))
235 lineCount = model.get_value(
236 iter, ThreadListModel.column_names.index("lineCount"))
238 update = res > lineCount
240 bbs_type_for_thread = self.bbs_type.clone_with_thread(thread)
241 uri_opener.open_uri(bbs_type_for_thread.get_thread_uri(), update)
243 def on_treeview_button_press_event(self, widget, event):
244 if event.button == 3:
248 pthinfo = widget.get_path_at_pos(x, y)
249 if pthinfo is not None:
250 path, col, cellx, celly = pthinfo
252 widget.set_cursor(path, col, 0)
253 self.popupmenu.popup(None, None, None, event.button, time)
256 def update_datastore(self, datalist, lastmod):
257 print "reflesh datastore"
260 lastmod = misc.httpdate_to_secs(lastmod)
264 time_start = time.time()
266 for id, dic in datalist.iteritems():
270 if lastmod == 0 or dic["num"] == 0:
275 # avoid the Last-Modified time of subject.txt and
276 # the build time of thread is equal (zero division)
277 dur = lastmod - start
279 dic["average"] = 999999
281 dic["average"] = int(res * 60 * 60 * 24 / dur)
284 httpdate = dic["lastModified"]
286 secs = misc.httpdate_to_secs(httpdate)
287 dic["lastModified"] = secs
289 dic["lastModified"] = 0
291 list_list.append(dic)
293 model = self.treeview.get_model()
294 model.set_list(list_list)
296 # redraw visible area after set list to model
297 self.treeview.queue_draw()
299 self.reset_sort_indicator()
302 time_end = time.time()
303 print time_end - time_start
305 def on_thread_idx_updated(self, thread_uri, idx_dic):
306 if not thread_uri or not idx_dic:
309 # nothing to do if thread_uri does not belong to this board.
310 bbs_type = bbs_type_judge_uri.get_type(thread_uri)
311 if bbs_type.bbs_type != self.bbs \
312 or bbs_type.board != self.board or not bbs_type.is_thread():
315 thread = bbs_type.thread
317 model = self.treeview.get_model()
319 idx_dic["id"] = thread
321 idx_dic["lastModified"] = misc.httpdate_to_secs(
322 idx_dic["lastModified"])
324 idx_dic["lastModified"] = 0
325 model.modify_row(idx_dic)
327 def load(self, update=False):
328 sbj_path = misc.get_board_subjecttxt_path(
329 self.bbs_type.bbs_type, self.bbs_type.board)
330 sbj_exists = os.path.exists(sbj_path)
332 if update or not sbj_exists:
333 t = board_data.GetRemote(
334 self.bbs, self.board, self.bbs_type.get_subject_txt_uri(),
335 self.update_datastore)
338 t = board_data.LoadLocal(
339 self.bbs, self.board, self.update_datastore)
344 states_path = misc.get_board_states_path(
345 self.bbs_type.bbs_type, self.bbs_type.board)
346 dirname = os.path.dirname(states_path)
348 # save only if board dir exists.
349 if os.path.exists(dirname):
350 window_width, window_height = self.window.get_size()
351 toolbar_visible = self.toolbar.parent.get_property("visible")
352 statusbar_visible = self.statusbar.get_property("visible")
354 columns = self.treeview.get_columns()
357 for column in columns:
358 for name, kolumn in self.treeviewcolumn.iteritems():
365 width += str(column.get_width())
367 sort_column_name, sort_reverse = \
368 self.treeview.get_model().get_sort()
369 sort_reverse = str(sort_reverse)
371 f = file(states_path, "w")
373 f.write("columns=" + order + "\n")
374 f.write("widths=" + width + "\n")
375 f.write("sort_column=" + sort_column_name + "\n")
376 f.write("sort_reverse=" + sort_reverse + "\n")
377 f.write("window_width=" + str(window_width) + "\n")
378 f.write("window_height=" + str(window_height) + "\n")
379 f.write("toolbar_visible=" + str(toolbar_visible) + "\n")
380 f.write("statusbar_visible=" + str(statusbar_visible) + "\n")
384 traceback.print_exc()
390 toolbar_visible = True
391 statusbar_visible = True
394 key_base = config.gconf_app_key_base() + "/board_states"
395 gconf_client = gconf.client_get_default()
396 width = gconf_client.get_int(key_base + "/window_width")
397 height = gconf_client.get_int(key_base + "/window_height")
398 toolbar_visible = gconf_client.get_bool(
399 key_base + "/toolbar")
400 statusbar_visible = gconf_client.get_bool(
401 key_base + "/statusbar")
405 window_height = height
407 traceback.print_exc()
409 states_path = misc.get_board_states_path(
410 self.bbs_type.bbs_type, self.bbs_type.board)
411 if os.path.exists(states_path):
412 sort_column_name = "num"
414 for line in file(states_path):
415 if line.startswith("columns="):
416 line = line[len("columns="):].rstrip("\n")
418 for name in line.split(","):
419 if name in self.treeviewcolumn:
420 column = self.treeviewcolumn[name]
421 self.treeview.move_column_after(
424 elif line.startswith("widths="):
425 line = line[len("widths="):].rstrip("\n")
426 columns = self.treeview.get_columns()
427 for i, width in enumerate(line.split(",")):
434 columns[i].set_fixed_width(width)
435 elif line.startswith("sort_column="):
436 kolumn_name = line[len("sort_column="):].rstrip("\n")
437 if kolumn_name in ThreadListModel.column_names:
438 sort_column_name = kolumn_name
439 elif line.startswith("sort_reverse="):
440 reverse = line[len("sort_reverse="):].rstrip("\n")
441 sort_reverse = reverse == "True"
442 elif line.startswith("window_height="):
443 height = window_height
446 line[len("window_height="):].rstrip("\n"))
450 window_height = height
451 elif line.startswith("window_width="):
455 line[len("window_width="):].rstrip("\n"))
460 elif line.startswith("toolbar_visible="):
461 tbar = line[len("toolbar_visible="):].rstrip("\n")
462 toolbar_visible = tbar == "True"
463 elif line.startswith("statusbar_visible="):
464 sbar = line[len("statusbar_visible="):].rstrip("\n")
465 statusbar_visible = sbar == "True"
467 self.treeview.get_model().sort(
468 sort_column_name, True, sort_reverse)
470 self.window.set_default_size(window_width, window_height)
472 if not toolbar_visible:
473 gobject.idle_add(self.toolbar.parent.hide)
474 if not statusbar_visible:
475 gobject.idle_add(self.statusbar.hide)
477 traceback.print_exc()