OSDN Git Service

Add BoardPluginBase and export widget_tree.
[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 import sys
28 import itertools
29
30 import board_data
31 import uri_opener
32 import misc
33 from threadlistmodel import ThreadListModel
34 from BbsType import bbs_type_judge_uri
35 import config
36 import session
37 import winwrapbase
38 from misc import ThreadInvoker
39 import bookmark_list
40 import bookmark_window
41 import board_plugins
42 import board_column
43 import board_states
44
45 GLADE_FILENAME = "board_window.glade"
46
47 def open_board(uri, update=False):
48     if not uri:
49         raise ValueError, "parameter must not be empty"
50
51     bbs_type = bbs_type_judge_uri.get_type(uri)
52     uri = bbs_type.get_uri_base()  # use strict uri
53
54     winwrap = session.get_window(uri)
55     if winwrap:
56         # already opened
57         winwrap.window.present()
58         if update:
59             winwrap.load(update)
60     else:
61         winwrap = WinWrap(bbs_type.uri)  # pass the original uri
62         winwrap.load(update)
63
64
65 class WinWrap(winwrapbase.WinWrapBase, board_data.BoardData):
66
67     def __init__(self, uri):
68
69         self.bbs_type = bbs_type_judge_uri.get_type(uri)
70         board_data.BoardData.__init__(self, self.bbs_type)
71
72         glade_path = os.path.join(config.glade_dir, GLADE_FILENAME)
73         self.widget_tree = gtk.glade.XML(glade_path)
74         self._get_widgets()
75
76         self.widget_tree.signal_autoconnect(self)
77
78         self.window.set_title(self.bbs_type.uri)
79         self.treeview.set_model(ThreadListModel())
80         self.toolbar.unset_style()
81
82         renderer = gtk.CellRendererText()
83
84         for column_class in board_column.tree_view_column_list:
85             column_class(renderer, self.treeview)
86
87         self.treeview.set_fixed_height_mode(True)
88
89         # menu plugins
90         board_plugins.load(self.widget_tree)
91
92         self.restore()
93         self.window.show()
94
95         self.created()
96
97     def _get_widgets(self):
98         self.window = self.widget_tree.get_widget("window_board")
99         self.treeview = self.widget_tree.get_widget("treeview")
100         self.popupmenu = self.widget_tree.get_widget("popup_treeview_menu")
101         self.toolbar = self.widget_tree.get_widget("toolbar")
102         self.statusbar = self.widget_tree.get_widget("appbar")
103         self.progress = self.statusbar.get_progress()
104         self.filterbar = self.widget_tree.get_widget(
105             "bonobodockitem_filterbar")
106         self.entry_filterbar = self.widget_tree.get_widget("entry_filterbar")
107
108     def set_status(self, text):
109         self.statusbar.set_status(text)
110
111     def set_fraction(self, fraction):
112         self.progress.set_fraction(fraction)
113
114     def destroy(self):
115         self.save()
116         self.window.destroy()
117
118     def get_uri(self):
119         return self.bbs_type.get_uri_base()
120
121     def open_thread(self):
122         treeselection = self.treeview.get_selection()
123         model, iter = treeselection.get_selected()
124         if not iter:
125             return
126
127         thread = model.get_value(iter, ThreadListModel.column_names.index("id"))
128         title = model.get_value(
129             iter, ThreadListModel.column_names.index("title"))
130         print thread + ':"' + title + '"', "activated"
131
132         res = model.get_value(iter, ThreadListModel.column_names.index("res"))
133         lineCount = model.get_value(
134             iter, ThreadListModel.column_names.index("lineCount"))
135
136         update = res > lineCount
137
138         bbs_type_for_thread = self.bbs_type.clone_with_thread(thread)
139         uri_opener.open_uri(bbs_type_for_thread.get_thread_uri(), update)
140
141     def update_datastore(self, new_list):
142         print "reflesh datastore"
143
144         self.treeview.get_model().set_list(new_list)
145
146         # redraw visible area after set list to model
147         self.treeview.queue_draw()
148
149         print "end"
150
151     def on_thread_idx_updated(self, thread_uri, idx_dic):
152         if not thread_uri or not idx_dic:
153             return
154
155         # nothing to do if thread_uri does not belong to this board.
156         bbs_type = bbs_type_judge_uri.get_type(thread_uri)
157         if not bbs_type.is_thread() \
158                or not bbs_type.is_same_board(self.bbs_type):
159             return
160
161         thread = bbs_type.thread
162
163         model = self.treeview.get_model()
164         if model:
165             idx_dic["id"] = thread
166             try:
167                 idx_dic["lastModified"] =  misc.httpdate_to_secs(
168                     idx_dic["lastModified"])
169             except ValueError:
170                 idx_dic["lastModified"] = 0
171             model.modify_row(idx_dic)
172
173     def load(self, update=False):
174
175         def set_id(thread_id, item_dict):
176             item_dict["id"] = thread_id
177             return item_dict
178
179         def conv_dictdict_to_listdict(dictdict):
180             key_iter = dictdict.iterkeys()
181             value_iter = dictdict.itervalues()
182             iterable = itertools.imap(set_id, key_iter, value_iter)
183             return [item_dict for item_dict in iterable]
184
185         def load_local():
186             datalist = self.load_idxfiles()
187             self.merge_local_subjecttxt(datalist)
188             new_list = conv_dictdict_to_listdict(datalist)
189             gobject.idle_add(self.update_datastore, new_list)
190
191         def get_remote(datalist):
192             print "start get subject.txt"
193             self.merge_remote_subjecttxt(datalist)
194             new_list = conv_dictdict_to_listdict(datalist)
195             gobject.idle_add(self.update_datastore, new_list)
196
197         def on_end():
198             def reset_progress():
199                 self.progress.set_fraction(0.0)
200                 self.progress.hide()
201             gobject.idle_add(reset_progress)
202
203         def deep_copy():
204             def init_some(dic):
205                 dic["num"] = 0
206                 dic["res"] = 0
207                 dic["average"] = 0
208                 return dic
209
210             model = self.treeview.get_model()
211             if model:
212                 iterable = model.original_list
213
214                 # unmerge subject.txt
215                 iterable = itertools.ifilter(
216                     lambda dic: dic["lineCount"] > 0, iterable)
217                 iterable = itertools.imap(lambda dic: dic.copy(), iterable)
218                 iterable = itertools.imap(init_some, iterable)
219
220                 new_dict = dict([(dic["id"], dic) for dic in iterable])
221             else:
222                 new_dict = {}
223             return new_dict
224
225         sbj_path = misc.get_board_subjecttxt_path(self.bbs_type)
226         sbj_exists = os.path.exists(sbj_path)
227
228         self.progress.show()
229
230         if update or not sbj_exists:
231             new_dict = deep_copy()
232             t = ThreadInvoker(on_end, lambda *x: get_remote(new_dict))
233             t.start()
234         else:
235             t = ThreadInvoker(on_end, load_local)
236             t.start()
237
238     def save(self):
239         try:
240             states_path = misc.get_board_states_path(self.bbs_type)
241             dirname = os.path.dirname(states_path)
242
243             # save only if board dir exists.
244             if os.path.exists(dirname):
245                 window_width, window_height = self.window.get_size()
246                 toolbar_visible = self.toolbar.parent.get_property("visible")
247                 statusbar_visible = self.statusbar.get_property("visible")
248                 filterbar_visible = self.filterbar.get_property("visible")
249
250                 columns = self.treeview.get_columns()
251                 order = ""
252                 width = ""
253                 for column in columns:
254                     if order:
255                         order += ","
256                     order += column.id
257                     if width:
258                         width += ","
259                     width += str(column.get_width())
260
261                 sort_column_name, sort_reverse = \
262                                   self.treeview.get_model().get_sort()
263                 sort_reverse = str(sort_reverse)
264
265                 f = file(states_path, "w")
266
267                 f.write("columns=" + order + "\n")
268                 f.write("widths=" + width + "\n")
269                 f.write("sort_column=" + sort_column_name + "\n")
270                 f.write("sort_reverse=" + sort_reverse + "\n")
271                 f.write("window_width=" + str(window_width) + "\n")
272                 f.write("window_height=" + str(window_height) + "\n")
273                 f.write("toolbar_visible=" + str(toolbar_visible) + "\n")
274                 f.write("statusbar_visible=" + str(statusbar_visible) + "\n")
275                 f.write("filterbar_visible=" + str(filterbar_visible) + "\n")
276
277                 f.close()
278         except:
279             traceback.print_exc()
280
281     def restore(self):
282         try:
283             states_dict = board_states.states_file_to_dict(self.bbs_type)
284
285             self.treeview.get_model().sort(
286                 states_dict["sort_column"], True, states_dict["sort_reverse"])
287
288             # set column order before set column width
289             treeviewcolumn = dict(
290                 [(cln.id, cln) for cln in self.treeview.get_columns()])
291             base_column = None
292             for column_name in states_dict["columns"]:
293                 if column_name in treeviewcolumn:
294                     column = treeviewcolumn[column_name]
295                     self.treeview.move_column_after(column, base_column)
296                     base_column = column
297
298             # set column width afeter set column order
299             for width, column in itertools.izip(
300                 states_dict["widths"], self.treeview.get_columns()):
301                 if width:
302                     column.set_fixed_width(width)
303
304             self.window.set_default_size(
305                 states_dict["window_width"], states_dict["window_height"])
306
307             if not states_dict["toolbar_visible"]:
308                 gobject.idle_add(self.toolbar.parent.hide,
309                                  priority=gobject.PRIORITY_HIGH)
310             if not states_dict["statusbar_visible"]:
311                 gobject.idle_add(self.statusbar.hide,
312                                  priority=gobject.PRIORITY_HIGH)
313             if not states_dict["filterbar_visible"]:
314                 gobject.idle_add(self.filterbar.hide,
315                                  priority=gobject.PRIORITY_HIGH)
316         except:
317             traceback.print_exc()
318
319     def on_menu_toolbar_activate(self, widget):
320         if self.toolbar.parent.get_property("visible"):
321             self.toolbar.parent.hide()
322         else:
323             self.toolbar.parent.show()
324
325     def on_menu_statusbar_activate(self, widget):
326         if self.statusbar.get_property("visible"):
327             self.statusbar.hide()
328         else:
329             self.statusbar.show()
330
331
332     # signal handlers
333
334     # menu and toolbutton
335
336     def on_menu_add_bookmark_activate(self, widget):
337         bookmark_list.bookmark_list.add_bookmark_with_edit(
338             uri=self.bbs_type.uri)
339
340     def on_menu_close_activate(self, widget):
341         self.destroy()
342
343     def on_menu_delete_activate(self, widget):
344         selection = self.treeview.get_selection()
345         model, iter = selection.get_selected()
346         if not iter:
347             return
348         thread = model.get_value(
349             iter, ThreadListModel.column_names.index("id"))
350         
351         bbs_type_for_thread = self.bbs_type.clone_with_thread(thread)
352
353         dat_path = misc.get_thread_dat_path(bbs_type_for_thread)
354         try:
355             os.remove(dat_path)
356         except OSError:
357             traceback.print_exc()
358         idx_path = misc.get_thread_idx_path(bbs_type_for_thread)
359         try:
360             os.remove(idx_path)
361         except OSError:
362             traceback.print_exc()
363         states_path = misc.get_thread_states_path(bbs_type_for_thread)
364         try:
365             os.remove(states_path)
366         except OSError:
367             traceback.print_exc()
368
369     def on_menu_filter_activate(self, widget):
370         self.filterbar.show()
371         self.entry_filterbar.grab_focus()
372
373     def on_menu_manage_bookmarks_activate(self, widget):
374         bookmark_window.open()
375
376     def on_menu_open_activate(self, widget):
377         self.open_thread()
378
379     def on_menu_quit_activate(self, widget):
380         session.main_quit()
381
382     def on_menu_refresh_activate(self, widget):
383         self.load(True)
384
385
386     # window
387
388     def on_window_board_delete_event(self, widget, event):
389         self.save()
390         return False
391
392     def on_window_board_destroy(self, widget):
393         self.popupmenu.destroy()
394         for column in self.treeview.get_columns():
395             column.destroy()
396         self.destroyed()
397
398
399     # treeview
400
401     def on_treeview_row_activated(self, widget, path, view_column):
402         self.open_thread()
403
404     def on_treeview_button_press_event(self, widget, event):
405         if event.button == 3:
406             x = int(event.x)
407             y = int(event.y)
408             time = event.time
409             pthinfo = widget.get_path_at_pos(x, y)
410             if pthinfo is not None:
411                 path, col, cellx, celly = pthinfo
412                 widget.grab_focus()
413                 widget.set_cursor(path, col, 0)
414                 self.popupmenu.popup(None, None, None, event.button, time)
415             return 1
416
417
418     # filterbar
419
420     def on_entry_filterbar_activate(self, widget):
421         text = widget.get_text()
422
423         def func(model, item):
424             try:
425                 item["title"].index(text)
426             except ValueError:
427                 return False
428             else:
429                 return True
430
431         model = self.treeview.get_model()
432         if model:
433             if text:
434                 filter_func = func
435             else:
436                 filter_func = None
437             model.refilter(filter_func)
438
439     def on_toolbutton_filterbar_close_clicked(self, widget):
440         self.filterbar.hide()
441
442     def on_button_filterbar_clear_clicked(self, widget):
443         self.entry_filterbar.set_text("")
444         model = self.treeview.get_model()
445         if model:
446             model.refilter(None)