OSDN Git Service

Add filterbar.
[fukui-no-namari/fukui-no-namari.git] / src / FukuiNoNamari / board_window.py
1 # Copyright (C) 2006 by Aiwota Programmer
2 # aiwotaprog@tetteke.tk
3 #
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.
8 #
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.
13 #
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
17
18 import pygtk
19 pygtk.require('2.0')
20 import gtk
21 import gtk.glade
22 import os
23 import time
24 import gobject
25 import gconf
26 import traceback
27
28 import board_data
29 import uri_opener
30 import misc
31 from threadlistmodel import ThreadListModel
32 from BbsType import bbs_type_judge_uri
33 import config
34 import session
35 import winwrapbase
36 from misc import ThreadInvoker
37
38 GLADE_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)),
39                          "..", "data")
40 GLADE_FILENAME = "board_window.glade"
41
42 def open_board(uri, update=False):
43     if not uri:
44         raise ValueError, "parameter must not be empty"
45
46     winwrap = session.get_window(uri)
47     if winwrap:
48         # already opened
49         winwrap.window.present()
50         if update:
51             winwrap.load(update)
52     else:
53         winwrap = WinWrap(uri)
54         winwrap.load(update)
55
56
57 class WinWrap(winwrapbase.WinWrapBase, board_data.BoardData):
58
59     def __init__(self, uri):
60
61         self.bbs_type = bbs_type_judge_uri.get_type(uri)
62         board_data.BoardData.__init__(self, self.bbs_type)
63         self.bbs = self.bbs_type.bbs_type
64         self.host = self.bbs_type.host
65         self.board = self.bbs_type.board
66         self.uri = self.bbs_type.uri
67         
68         glade_path = os.path.join(GLADE_DIR, GLADE_FILENAME)
69         self.widget_tree = gtk.glade.XML(glade_path)
70
71         self.window = self.widget_tree.get_widget("board_window")
72
73         self.window.set_title(self.uri)
74
75         self.treeview = self.widget_tree.get_widget("treeview")
76         self.treeview.set_model(ThreadListModel())
77
78         self.popupmenu = self.widget_tree.get_widget("popup_menu")
79         self.toolbar = self.widget_tree.get_widget("toolbar")
80         self.toolbar.unset_style()
81         self.statusbar = self.widget_tree.get_widget("appbar")
82         self.filterbar = self.widget_tree.get_widget("bonobodockitem_filterbar")
83         self.entry_filterbar = self.widget_tree.get_widget("entry_filterbar")
84
85         renderer = gtk.CellRendererText()
86
87         self.treeviewcolumn = {}
88         for i in range(1, len(ThreadListModel.column_names)):
89             column_name = ThreadListModel.column_names[i]
90             self.treeviewcolumn[column_name] = gtk.TreeViewColumn(
91                 column_name, renderer)
92             self.treeviewcolumn[column_name].set_resizable(True)
93             self.treeviewcolumn[column_name].set_reorderable(True)
94             self.treeviewcolumn[column_name].set_clickable(True)
95             self.treeviewcolumn[column_name].set_cell_data_func(
96                 renderer, self.on_cell_data, column_name)
97             self.treeviewcolumn[column_name].connect(
98                 "clicked", self.on_column_clicked, column_name)
99             self.treeviewcolumn[column_name].set_sizing(
100                 gtk.TREE_VIEW_COLUMN_FIXED)
101             self.treeviewcolumn[column_name].set_min_width(20)
102             self.treeviewcolumn[column_name].set_fixed_width(
103                 ThreadListModel.column_width[i])
104             self.treeview.append_column(self.treeviewcolumn[column_name])
105
106         self.treeviewcolumn["lastModified"].set_cell_data_func(
107             renderer, self.on_data_lastmodified)
108
109         self.treeview.set_fixed_height_mode(True)
110
111         sigdic = {"on_board_window_destroy": self.on_board_window_destroy,
112                   "on_quit_activate": self.on_quit_activate,
113                   "on_refresh_activate": self.on_refresh_activate,
114                   "on_treeview_row_activated":
115                   lambda w,p,v: self.on_open_thread(w),
116                   "on_treeview_button_press_event":
117                   self.on_treeview_button_press_event,
118                   "on_close_activate":
119                   self.on_close_activate,
120                   "on_toolbar_activate": self.on_toolbar_activate,
121                   "on_statusbar_activate": self.on_statusbar_activate,
122                   "on_board_window_delete_event":
123                   self.on_board_window_delete_event,
124                   "on_entry_filterbar_activate": self.on_entry_filterbar_activate,
125                   "on_filter_activate": self.on_filter_activate,
126                   "on_toolbutton_filterbar_close_clicked":
127                   self.on_toolbutton_filterbar_close_clicked,
128                   "on_popup_menu_open_activate": self.on_open_thread}
129         self.widget_tree.signal_autoconnect(sigdic)
130
131         self.restore()
132         self.window.show()
133
134         self.created()
135
136     def set_status(self, text):
137         self.statusbar.set_status(text)
138
139     def destroy(self):
140         self.save()
141         self.window.destroy()
142
143     def get_uri(self):
144         return self.bbs_type.get_uri_base()
145
146     def on_toolbar_activate(self, widget):
147         if self.toolbar.parent.get_property("visible"):
148             self.toolbar.parent.hide()
149         else:
150             self.toolbar.parent.show()
151
152     def on_statusbar_activate(self, widget):
153         if self.statusbar.get_property("visible"):
154             self.statusbar.hide()
155         else:
156             self.statusbar.show()
157
158     def on_filter_activate(self, widget):
159         self.filterbar.show()
160         self.entry_filterbar.grab_focus()
161
162     def on_entry_filterbar_activate(self, widget):
163         text = widget.get_text()
164
165         def func(model, item):
166             try:
167                 item["title"].index(text)
168             except ValueError:
169                 return False
170             else:
171                 return True
172
173         model = self.treeview.get_model()
174         if model:
175             if text:
176                 model.set_filter_func(func)
177             else:
178                 model.set_filter_func(None)
179             model.refilter()
180
181     def on_toolbutton_filterbar_close_clicked(self, widget):
182         self.filterbar.hide()
183
184     def updated_thread_highlight(self, column, cell, model, iter):
185
186         def is_updated_thread():
187             res = model.get_value(
188                 iter, ThreadListModel.column_names.index("res"))
189             linecount = model.get_value(
190                 iter, ThreadListModel.column_names.index("lineCount"))
191             return res != 0 and linecount != 0 and res > linecount
192
193         if is_updated_thread():
194             cell.set_property("weight", 800)
195         else:
196             cell.set_property("weight", 400)
197
198     def on_cell_data(self, column, cell, model, iter, column_name):
199         self.updated_thread_highlight(column, cell, model, iter)
200         column_num = ThreadListModel.column_names.index(column_name)
201         value = model.get_value(iter, column_num)
202         if model.get_column_type(column_num) \
203            in (gobject.TYPE_INT, gobject.TYPE_DOUBLE):
204             if value == 0:
205                 cell.set_property("text", "")
206             else:
207                 cell.set_property("text", str(value))
208         else:
209             cell.set_property("text", value)
210
211     def on_data_lastmodified(self, column, cell, model, iter, user_data=None):
212         self.updated_thread_highlight(column, cell, model, iter)
213         lastmod = model.get_value(
214             iter, ThreadListModel.column_names.index("lastModified"))
215         if lastmod == 0:
216             cell.set_property("text", "")
217         else:
218             cell.set_property("text", time.strftime(
219                 "%Y/%m/%d(%a) %H:%M:%S", time.localtime(lastmod)))
220
221     def on_board_window_delete_event(self, widget, event):
222         self.save()
223         return False
224
225     def on_board_window_destroy(self, widget):
226         self.destroyed()
227
228     def on_quit_activate(self, widget):
229         session.main_quit()
230
231     def on_close_activate(self, widget):
232         self.destroy()
233
234     def on_refresh_activate(self, widget):
235         self.load(True)
236
237     def on_column_clicked(self, treeviewcolumn, column_name):
238         model = self.treeview.get_model()
239         if model:
240             model.sort(column_name)
241             self.reset_sort_indicator()
242
243     def reset_sort_indicator(self):
244         model = self.treeview.get_model()
245         if model:
246             sort_column_name, sort_reverse = model.get_sort()
247             for name,column in self.treeviewcolumn.iteritems():
248                 column.set_sort_indicator(False)
249             if sort_column_name != "num" or sort_reverse:
250                 self.treeviewcolumn[sort_column_name].set_sort_indicator(True)
251                 if sort_reverse:
252                     self.treeviewcolumn[sort_column_name].set_sort_order(
253                         gtk.SORT_DESCENDING)
254                 else:
255                     self.treeviewcolumn[sort_column_name].set_sort_order(
256                         gtk.SORT_ASCENDING)
257         
258     def on_open_thread(self, widget):
259         treeselection = self.treeview.get_selection()
260         model, iter = treeselection.get_selected()
261         if not iter:
262             return
263
264         thread = model.get_value(iter, ThreadListModel.column_names.index("id"))
265         title = model.get_value(
266             iter, ThreadListModel.column_names.index("title"))
267         print thread + ':"' + title + '"', "activated"
268
269         res = model.get_value(iter, ThreadListModel.column_names.index("res"))
270         lineCount = model.get_value(
271             iter, ThreadListModel.column_names.index("lineCount"))
272
273         update = res > lineCount
274
275         bbs_type_for_thread = self.bbs_type.clone_with_thread(thread)
276         uri_opener.open_uri(bbs_type_for_thread.get_thread_uri(), update)
277
278     def on_treeview_button_press_event(self, widget, event):
279         if event.button == 3:
280             x = int(event.x)
281             y = int(event.y)
282             time = event.time
283             pthinfo = widget.get_path_at_pos(x, y)
284             if pthinfo is not None:
285                 path, col, cellx, celly = pthinfo
286                 widget.grab_focus()
287                 widget.set_cursor(path, col, 0)
288                 self.popupmenu.popup(None, None, None, event.button, time)
289             return 1
290
291     def update_datastore(self, datalist):
292         print "reflesh datastore"
293
294         list_list = []
295         for id, dic in datalist.iteritems():
296             dic["id"] = id
297
298             # lastModified
299             httpdate = dic["lastModified"]
300             try:
301                 secs = misc.httpdate_to_secs(httpdate)
302                 dic["lastModified"] = secs
303             except ValueError:
304                 dic["lastModified"] = 0
305
306             list_list.append(dic)
307
308         model = self.treeview.get_model()
309         model.set_list(list_list)
310
311         # redraw visible area after set list to model
312         self.treeview.queue_draw()
313
314         self.reset_sort_indicator()
315
316         print "end"
317
318     def on_thread_idx_updated(self, thread_uri, idx_dic):
319         if not thread_uri or not idx_dic:
320             return
321
322         # nothing to do if thread_uri does not belong to this board.
323         bbs_type = bbs_type_judge_uri.get_type(thread_uri)
324         if bbs_type.bbs_type != self.bbs \
325            or bbs_type.board != self.board or not bbs_type.is_thread():
326             return
327
328         thread = bbs_type.thread
329
330         model = self.treeview.get_model()
331         if model:
332             idx_dic["id"] = thread
333             try:
334                 idx_dic["lastModified"] =  misc.httpdate_to_secs(
335                     idx_dic["lastModified"])
336             except ValueError:
337                 idx_dic["lastModified"] = 0
338             model.modify_row(idx_dic)
339
340     def load(self, update=False):
341
342         def load_local():
343             datalist = self.load_idxfiles()
344             self.merge_local_subjecttxt(datalist)
345             gobject.idle_add(self.update_datastore, datalist)
346
347         def get_remote():
348             print "start get subject.txt"
349             datalist = self.load_idxfiles()
350             self.merge_remote_subjecttxt(datalist)
351             gobject.idle_add(self.update_datastore, datalist)
352
353         sbj_path = misc.get_board_subjecttxt_path(
354             self.bbs_type.bbs_type, self.bbs_type.board)
355         sbj_exists = os.path.exists(sbj_path)
356
357         if update or not sbj_exists:
358             t = ThreadInvoker(lambda *args: -1, get_remote)
359             t.start()
360         else:
361             t = ThreadInvoker(lambda *args: -1, load_local)
362             t.start()
363
364     def save(self):
365         try:
366             states_path = misc.get_board_states_path(
367                 self.bbs_type.bbs_type, self.bbs_type.board)
368             dirname = os.path.dirname(states_path)
369
370             # save only if board dir exists.
371             if os.path.exists(dirname):
372                 window_width, window_height = self.window.get_size()
373                 toolbar_visible = self.toolbar.parent.get_property("visible")
374                 statusbar_visible = self.statusbar.get_property("visible")
375                 filterbar_visible = self.filterbar.get_property("visible")
376
377                 columns = self.treeview.get_columns()
378                 order = ""
379                 width = ""
380                 for column in columns:
381                     for name, kolumn in self.treeviewcolumn.iteritems():
382                         if column == kolumn:
383                             if order:
384                                 order += ","
385                             order += name
386                             if width:
387                                 width += ","
388                             width += str(column.get_width())
389                             break
390                 sort_column_name, sort_reverse = \
391                                   self.treeview.get_model().get_sort()
392                 sort_reverse = str(sort_reverse)
393
394                 f = file(states_path, "w")
395
396                 f.write("columns=" + order + "\n")
397                 f.write("widths=" + width + "\n")
398                 f.write("sort_column=" + sort_column_name + "\n")
399                 f.write("sort_reverse=" + sort_reverse + "\n")
400                 f.write("window_width=" + str(window_width) + "\n")
401                 f.write("window_height=" + str(window_height) + "\n")
402                 f.write("toolbar_visible=" + str(toolbar_visible) + "\n")
403                 f.write("statusbar_visible=" + str(statusbar_visible) + "\n")
404                 f.write("filterbar_visible=" + str(filterbar_visible) + "\n")
405
406                 f.close()
407         except:
408             traceback.print_exc()
409
410     def restore(self):
411         try:
412             window_height = 600
413             window_width = 600
414             toolbar_visible = True
415             statusbar_visible = True
416             filterbar_visible = False
417
418             try:
419                 key_base = config.gconf_app_key_base() + "/board_states"
420                 gconf_client = gconf.client_get_default()
421                 width = gconf_client.get_int(key_base + "/window_width")
422                 height = gconf_client.get_int(key_base + "/window_height")
423                 toolbar_visible = gconf_client.get_bool(key_base + "/toolbar")
424                 statusbar_visible = gconf_client.get_bool(key_base + "/statusbar")
425                 filterbar_visible = gconf_client.get_bool(key_base + "/filterbar")
426
427                 if width != 0:
428                     window_width = width
429                 if height != 0:
430                     window_height = height
431             except:
432                 traceback.print_exc()
433
434             states_path = misc.get_board_states_path(
435                 self.bbs_type.bbs_type, self.bbs_type.board)
436             if os.path.exists(states_path):
437                 sort_column_name = "num"
438                 sort_reverse = False
439                 for line in file(states_path):
440                     if line.startswith("columns="):
441                         line = line[len("columns="):].rstrip("\n")
442                         base_column = None
443                         for name in line.split(","):
444                             if name in self.treeviewcolumn:
445                                 column = self.treeviewcolumn[name]
446                                 self.treeview.move_column_after(
447                                     column, base_column)
448                                 base_column = column
449                     elif line.startswith("widths="):
450                         line = line[len("widths="):].rstrip("\n")
451                         columns = self.treeview.get_columns()
452                         for i, width in enumerate(line.split(",")):
453                             try:
454                                 width = int(width)
455                             except:
456                                 pass
457                             else:
458                                 if i < len(columns):
459                                     columns[i].set_fixed_width(width)
460                     elif line.startswith("sort_column="):
461                         kolumn_name = line[len("sort_column="):].rstrip("\n")
462                         if kolumn_name in ThreadListModel.column_names:
463                             sort_column_name = kolumn_name
464                     elif line.startswith("sort_reverse="):
465                         reverse = line[len("sort_reverse="):].rstrip("\n")
466                         sort_reverse = reverse == "True"
467                     elif line.startswith("window_height="):
468                         height = window_height
469                         try:
470                             height = int(
471                                 line[len("window_height="):].rstrip("\n"))
472                         except:
473                             pass
474                         else:
475                             window_height = height
476                     elif line.startswith("window_width="):
477                         width = window_width
478                         try:
479                             width = int(
480                                 line[len("window_width="):].rstrip("\n"))
481                         except:
482                             pass
483                         else:
484                             window_width = width
485                     elif line.startswith("toolbar_visible="):
486                         tbar = line[len("toolbar_visible="):].rstrip("\n")
487                         toolbar_visible = tbar == "True"
488                     elif line.startswith("statusbar_visible="):
489                         sbar = line[len("statusbar_visible="):].rstrip("\n")
490                         statusbar_visible = sbar == "True"
491                     elif line.startswith("filterbar_visible="):
492                         fbar = line[len("filterbar_visible="):].rstrip("\n")
493                         filterbar_visible = fbar == "True"
494
495                 self.treeview.get_model().sort(
496                     sort_column_name, True, sort_reverse)
497
498             self.window.set_default_size(window_width, window_height)
499
500             if not toolbar_visible:
501                 gobject.idle_add(self.toolbar.parent.hide)
502             if not statusbar_visible:
503                 gobject.idle_add(self.statusbar.hide)
504             if not filterbar_visible:
505                 gobject.idle_add(self.filterbar.hide)
506         except:
507             traceback.print_exc()