OSDN Git Service

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