OSDN Git Service

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