OSDN Git Service

Use progressbar of board window.
[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
44 GLADE_FILENAME = "board_window.glade"
45
46 def open_board(uri, update=False):
47     if not uri:
48         raise ValueError, "parameter must not be empty"
49
50     bbs_type = bbs_type_judge_uri.get_type(uri)
51     uri = bbs_type.get_uri_base()  # use strict uri
52
53     winwrap = session.get_window(uri)
54     if winwrap:
55         # already opened
56         winwrap.window.present()
57         if update:
58             winwrap.load(update)
59     else:
60         winwrap = WinWrap(bbs_type.uri)  # pass the original uri
61         winwrap.load(update)
62
63
64 class WinWrap(winwrapbase.WinWrapBase, board_data.BoardData):
65
66     def __init__(self, uri):
67
68         self.bbs_type = bbs_type_judge_uri.get_type(uri)
69         board_data.BoardData.__init__(self, self.bbs_type)
70
71         glade_path = os.path.join(config.glade_dir, GLADE_FILENAME)
72         self.widget_tree = gtk.glade.XML(glade_path)
73         self._get_widgets()
74
75         self.widget_tree.signal_autoconnect(self)
76
77         self.window.set_title(self.bbs_type.uri)
78         self.treeview.set_model(ThreadListModel())
79         self.toolbar.unset_style()
80
81         renderer = gtk.CellRendererText()
82
83         for column_class in board_column.tree_view_column_list:
84             column_class(renderer, self.treeview)
85
86         self.treeview.set_fixed_height_mode(True)
87
88         # menu plugins
89         board_plugins.load(self.treeview, self.menu_edit)
90
91         self.restore()
92         self.window.show()
93
94         self.created()
95
96     def _get_widgets(self):
97         self.window = self.widget_tree.get_widget("window_board")
98         self.treeview = self.widget_tree.get_widget("treeview")
99         self.popupmenu = self.widget_tree.get_widget("popup_treeview_menu")
100         self.toolbar = self.widget_tree.get_widget("toolbar")
101         self.statusbar = self.widget_tree.get_widget("appbar")
102         self.progress = self.statusbar.get_progress()
103         self.filterbar = self.widget_tree.get_widget(
104             "bonobodockitem_filterbar")
105         self.entry_filterbar = self.widget_tree.get_widget("entry_filterbar")
106         self.menu_edit = self.widget_tree.get_widget("menu_edit").get_submenu()
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 modify_dict(item_dict):
176             # lastModified, httpdate to second
177             httpdate = item_dict["lastModified"]
178             try:
179                 secs = misc.httpdate_to_secs(httpdate)
180             except ValueError:
181                 item_dict["lastModified"] = 0
182             else:
183                 item_dict["lastModified"] = secs
184             return item_dict
185
186         def set_id(thread_id, item_dict):
187             item_dict["id"] = thread_id
188             return item_dict
189
190         def conv_dictdict_to_listdict(dictdict):
191             key_iter = dictdict.iterkeys()
192             value_iter = dictdict.itervalues()
193             iterable = itertools.imap(set_id, key_iter, value_iter)
194             iterable = itertools.imap(modify_dict, iterable)
195             return [item_dict for item_dict in iterable]
196
197         def load_local():
198             datalist = self.load_idxfiles()
199             self.merge_local_subjecttxt(datalist)
200             new_list = conv_dictdict_to_listdict(datalist)
201             gobject.idle_add(self.update_datastore, new_list)
202
203         def get_remote():
204             print "start get subject.txt"
205             datalist = self.load_idxfiles()
206             self.merge_remote_subjecttxt(datalist)
207             new_list = conv_dictdict_to_listdict(datalist)
208             gobject.idle_add(self.update_datastore, new_list)
209
210         sbj_path = misc.get_board_subjecttxt_path(self.bbs_type)
211         sbj_exists = os.path.exists(sbj_path)
212
213         if update or not sbj_exists:
214             t = ThreadInvoker(lambda *args: -1, get_remote)
215             t.start()
216         else:
217             t = ThreadInvoker(lambda *args: -1, load_local)
218             t.start()
219
220     def save(self):
221         try:
222             states_path = misc.get_board_states_path(self.bbs_type)
223             dirname = os.path.dirname(states_path)
224
225             # save only if board dir exists.
226             if os.path.exists(dirname):
227                 window_width, window_height = self.window.get_size()
228                 toolbar_visible = self.toolbar.parent.get_property("visible")
229                 statusbar_visible = self.statusbar.get_property("visible")
230                 filterbar_visible = self.filterbar.get_property("visible")
231
232                 columns = self.treeview.get_columns()
233                 order = ""
234                 width = ""
235                 for column in columns:
236                     if order:
237                         order += ","
238                     order += column.id
239                     if width:
240                         width += ","
241                     width += str(column.get_width())
242
243                 sort_column_name, sort_reverse = \
244                                   self.treeview.get_model().get_sort()
245                 sort_reverse = str(sort_reverse)
246
247                 f = file(states_path, "w")
248
249                 f.write("columns=" + order + "\n")
250                 f.write("widths=" + width + "\n")
251                 f.write("sort_column=" + sort_column_name + "\n")
252                 f.write("sort_reverse=" + sort_reverse + "\n")
253                 f.write("window_width=" + str(window_width) + "\n")
254                 f.write("window_height=" + str(window_height) + "\n")
255                 f.write("toolbar_visible=" + str(toolbar_visible) + "\n")
256                 f.write("statusbar_visible=" + str(statusbar_visible) + "\n")
257                 f.write("filterbar_visible=" + str(filterbar_visible) + "\n")
258
259                 f.close()
260         except:
261             traceback.print_exc()
262
263     def restore(self):
264         states_dict = dict([
265             ("columns", ()), ("widths", ()), ("sort_column", "num"),
266             ("sort_reverse", False), ("window_height", 600),
267             ("window_width", 600), ("toolbar_visible", True),
268             ("statusbar_visible", True), ("filterbar_visible", False)])
269
270         interest_list = [
271             "columns", "widths", "sort_column", "sort_reverse",
272             "window_height", "window_width", "toolbar_visible",
273             "statusbar_visible", "filterbar_visible"]
274
275         interest_type_list = (
276             (list, str), (list, int), (str, None), (bool, None), (int, None),
277             (int, None), (bool, None), (bool, None), (bool, None))
278
279         def key_value_generator():
280
281             def generate_pair():
282                 states_path = misc.get_board_states_path(self.bbs_type)
283                 try:
284                     for line in file(states_path):
285                         key_equal_value = line.rstrip()
286                         try:
287                             index = key_equal_value.index("=")
288                         except ValueError:
289                             pass
290                         else:
291                             key = key_equal_value[:index]
292                             if key in interest_list:
293                                 value = key_equal_value[index+1:]
294                                 yield key, value
295                 except IOError:
296                     traceback.print_exc()
297
298             def to_int(value):
299                 try:
300                     return int(value)
301                 except:
302                     return 0
303
304             for key, value in generate_pair():
305                 list_index = interest_list.index(key)
306                 key_type, key_type_extra = interest_type_list[list_index]
307                 if key_type == str:
308                     yield key, value
309                 elif key_type == int:
310                     yield key, to_int(value)
311                 elif key_type == bool:
312                     value = value == "True"
313                     yield key, value
314                 elif key_type == list:
315                     if key_type_extra == str:
316                         yield key, value.split(",")
317                     elif key_type_extra == int:
318                         yield key, [i for i in itertools.imap(
319                             to_int, value.split(","))]
320                     else:
321                         print key, "not supported",
322                         key_type, key_type_extra, value
323                 else:
324                     print key, "not supported", key_type, value
325
326         def load_gconf():
327             key_base = config.gconf_app_key_base() + "/board_states"
328             gconf_client = gconf.client_get_default()
329             width = gconf_client.get_int(key_base + "/window_width")
330             height = gconf_client.get_int(key_base + "/window_height")
331             states_dict["toolbar_visible"] = gconf_client.get_bool(
332                 key_base + "/toolbar")
333             states_dict["statusbar_visible"] = gconf_client.get_bool(
334                 key_base + "/statusbar")
335             states_dict["filterbar_visible"] = gconf_client.get_bool(
336                 key_base + "/filterbar")
337
338             if width != 0:
339                 states_dict["window_width"] = width
340             if height != 0:
341                 states_dict["window_height"] = height
342
343         try:
344             try:
345                 load_gconf()
346             except:
347                 traceback.print_exc()
348
349             for key, value in key_value_generator():
350                 states_dict[key] = value
351
352             self.treeview.get_model().sort(
353                 states_dict["sort_column"], True, states_dict["sort_reverse"])
354
355             # set column order before set column width
356             treeviewcolumn = dict(
357                 [(cln.id, cln) for cln in self.treeview.get_columns()])
358             base_column = None
359             for column_name in states_dict["columns"]:
360                 if column_name in treeviewcolumn:
361                     column = treeviewcolumn[column_name]
362                     self.treeview.move_column_after(column, base_column)
363                     base_column = column
364
365             # set column width afeter set column order
366             for width, column in itertools.izip(
367                 states_dict["widths"], self.treeview.get_columns()):
368                 if width:
369                     column.set_fixed_width(width)
370
371             self.window.set_default_size(
372                 states_dict["window_width"], states_dict["window_height"])
373
374             if not states_dict["toolbar_visible"]:
375                 gobject.idle_add(self.toolbar.parent.hide,
376                                  priority=gobject.PRIORITY_HIGH)
377             if not states_dict["statusbar_visible"]:
378                 gobject.idle_add(self.statusbar.hide,
379                                  priority=gobject.PRIORITY_HIGH)
380             if not states_dict["filterbar_visible"]:
381                 gobject.idle_add(self.filterbar.hide,
382                                  priority=gobject.PRIORITY_HIGH)
383         except:
384             traceback.print_exc()
385
386     def on_menu_toolbar_activate(self, widget):
387         if self.toolbar.parent.get_property("visible"):
388             self.toolbar.parent.hide()
389         else:
390             self.toolbar.parent.show()
391
392     def on_menu_statusbar_activate(self, widget):
393         if self.statusbar.get_property("visible"):
394             self.statusbar.hide()
395         else:
396             self.statusbar.show()
397
398
399     # signal handlers
400
401     # menu and toolbutton
402
403     def on_menu_add_bookmark_activate(self, widget):
404         bookmark_list.bookmark_list.add_bookmark_with_edit(
405             uri=self.bbs_type.uri)
406
407     def on_menu_close_activate(self, widget):
408         self.destroy()
409
410     def on_menu_delete_activate(self, widget):
411         selection = self.treeview.get_selection()
412         model, iter = selection.get_selected()
413         if not iter:
414             return
415         thread = model.get_value(
416             iter, ThreadListModel.column_names.index("id"))
417         
418         bbs_type_for_thread = self.bbs_type.clone_with_thread(thread)
419
420         dat_path = misc.get_thread_dat_path(bbs_type_for_thread)
421         try:
422             os.remove(dat_path)
423         except OSError:
424             traceback.print_exc()
425         idx_path = misc.get_thread_idx_path(bbs_type_for_thread)
426         try:
427             os.remove(idx_path)
428         except OSError:
429             traceback.print_exc()
430         states_path = misc.get_thread_states_path(bbs_type_for_thread)
431         try:
432             os.remove(states_path)
433         except OSError:
434             traceback.print_exc()
435
436     def on_menu_filter_activate(self, widget):
437         self.filterbar.show()
438         self.entry_filterbar.grab_focus()
439
440     def on_menu_manage_bookmarks_activate(self, widget):
441         bookmark_window.open()
442
443     def on_menu_open_activate(self, widget):
444         self.open_thread()
445
446     def on_menu_quit_activate(self, widget):
447         session.main_quit()
448
449     def on_menu_refresh_activate(self, widget):
450         self.load(True)
451
452
453     # window
454
455     def on_window_board_delete_event(self, widget, event):
456         self.save()
457         return False
458
459     def on_window_board_destroy(self, widget):
460         self.destroyed()
461
462
463     # treeview
464
465     def on_treeview_row_activated(self, widget, path, view_column):
466         self.open_thread()
467
468     def on_treeview_button_press_event(self, widget, event):
469         if event.button == 3:
470             x = int(event.x)
471             y = int(event.y)
472             time = event.time
473             pthinfo = widget.get_path_at_pos(x, y)
474             if pthinfo is not None:
475                 path, col, cellx, celly = pthinfo
476                 widget.grab_focus()
477                 widget.set_cursor(path, col, 0)
478                 self.popupmenu.popup(None, None, None, event.button, time)
479             return 1
480
481
482     # filterbar
483
484     def on_entry_filterbar_activate(self, widget):
485         text = widget.get_text()
486
487         def func(model, item):
488             try:
489                 item["title"].index(text)
490             except ValueError:
491                 return False
492             else:
493                 return True
494
495         model = self.treeview.get_model()
496         if model:
497             if text:
498                 filter_func = func
499             else:
500                 filter_func = None
501             model.refilter(filter_func)
502
503     def on_toolbutton_filterbar_close_clicked(self, widget):
504         self.filterbar.hide()
505
506     def on_button_filterbar_clear_clicked(self, widget):
507         self.entry_filterbar.set_text("")
508         model = self.treeview.get_model()
509         if model:
510             model.refilter(None)