OSDN Git Service

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