OSDN Git Service

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