OSDN Git Service

Destroy popupmenu and treeview columns.
[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         def on_end():
211             def reset_progress():
212                 self.progress.set_fraction(0.0)
213                 self.progress.hide()
214             gobject.idle_add(reset_progress)
215
216         sbj_path = misc.get_board_subjecttxt_path(self.bbs_type)
217         sbj_exists = os.path.exists(sbj_path)
218
219         self.progress.show()
220
221         if update or not sbj_exists:
222             t = ThreadInvoker(on_end, get_remote)
223             t.start()
224         else:
225             t = ThreadInvoker(on_end, load_local)
226             t.start()
227
228     def save(self):
229         try:
230             states_path = misc.get_board_states_path(self.bbs_type)
231             dirname = os.path.dirname(states_path)
232
233             # save only if board dir exists.
234             if os.path.exists(dirname):
235                 window_width, window_height = self.window.get_size()
236                 toolbar_visible = self.toolbar.parent.get_property("visible")
237                 statusbar_visible = self.statusbar.get_property("visible")
238                 filterbar_visible = self.filterbar.get_property("visible")
239
240                 columns = self.treeview.get_columns()
241                 order = ""
242                 width = ""
243                 for column in columns:
244                     if order:
245                         order += ","
246                     order += column.id
247                     if width:
248                         width += ","
249                     width += str(column.get_width())
250
251                 sort_column_name, sort_reverse = \
252                                   self.treeview.get_model().get_sort()
253                 sort_reverse = str(sort_reverse)
254
255                 f = file(states_path, "w")
256
257                 f.write("columns=" + order + "\n")
258                 f.write("widths=" + width + "\n")
259                 f.write("sort_column=" + sort_column_name + "\n")
260                 f.write("sort_reverse=" + sort_reverse + "\n")
261                 f.write("window_width=" + str(window_width) + "\n")
262                 f.write("window_height=" + str(window_height) + "\n")
263                 f.write("toolbar_visible=" + str(toolbar_visible) + "\n")
264                 f.write("statusbar_visible=" + str(statusbar_visible) + "\n")
265                 f.write("filterbar_visible=" + str(filterbar_visible) + "\n")
266
267                 f.close()
268         except:
269             traceback.print_exc()
270
271     def restore(self):
272         states_dict = dict([
273             ("columns", ()), ("widths", ()), ("sort_column", "num"),
274             ("sort_reverse", False), ("window_height", 600),
275             ("window_width", 600), ("toolbar_visible", True),
276             ("statusbar_visible", True), ("filterbar_visible", False)])
277
278         interest_list = [
279             "columns", "widths", "sort_column", "sort_reverse",
280             "window_height", "window_width", "toolbar_visible",
281             "statusbar_visible", "filterbar_visible"]
282
283         interest_type_list = (
284             (list, str), (list, int), (str, None), (bool, None), (int, None),
285             (int, None), (bool, None), (bool, None), (bool, None))
286
287         def key_value_generator():
288
289             def generate_pair():
290                 states_path = misc.get_board_states_path(self.bbs_type)
291                 try:
292                     for line in file(states_path):
293                         key_equal_value = line.rstrip()
294                         try:
295                             index = key_equal_value.index("=")
296                         except ValueError:
297                             pass
298                         else:
299                             key = key_equal_value[:index]
300                             if key in interest_list:
301                                 value = key_equal_value[index+1:]
302                                 yield key, value
303                 except IOError:
304                     traceback.print_exc()
305
306             def to_int(value):
307                 try:
308                     return int(value)
309                 except:
310                     return 0
311
312             for key, value in generate_pair():
313                 list_index = interest_list.index(key)
314                 key_type, key_type_extra = interest_type_list[list_index]
315                 if key_type == str:
316                     yield key, value
317                 elif key_type == int:
318                     yield key, to_int(value)
319                 elif key_type == bool:
320                     value = value == "True"
321                     yield key, value
322                 elif key_type == list:
323                     if key_type_extra == str:
324                         yield key, value.split(",")
325                     elif key_type_extra == int:
326                         yield key, [i for i in itertools.imap(
327                             to_int, value.split(","))]
328                     else:
329                         print key, "not supported",
330                         key_type, key_type_extra, value
331                 else:
332                     print key, "not supported", key_type, value
333
334         def load_gconf():
335             key_base = config.gconf_app_key_base() + "/board_states"
336             gconf_client = gconf.client_get_default()
337             width = gconf_client.get_int(key_base + "/window_width")
338             height = gconf_client.get_int(key_base + "/window_height")
339             states_dict["toolbar_visible"] = gconf_client.get_bool(
340                 key_base + "/toolbar")
341             states_dict["statusbar_visible"] = gconf_client.get_bool(
342                 key_base + "/statusbar")
343             states_dict["filterbar_visible"] = gconf_client.get_bool(
344                 key_base + "/filterbar")
345
346             if width != 0:
347                 states_dict["window_width"] = width
348             if height != 0:
349                 states_dict["window_height"] = height
350
351         try:
352             try:
353                 load_gconf()
354             except:
355                 traceback.print_exc()
356
357             for key, value in key_value_generator():
358                 states_dict[key] = value
359
360             self.treeview.get_model().sort(
361                 states_dict["sort_column"], True, states_dict["sort_reverse"])
362
363             # set column order before set column width
364             treeviewcolumn = dict(
365                 [(cln.id, cln) for cln in self.treeview.get_columns()])
366             base_column = None
367             for column_name in states_dict["columns"]:
368                 if column_name in treeviewcolumn:
369                     column = treeviewcolumn[column_name]
370                     self.treeview.move_column_after(column, base_column)
371                     base_column = column
372
373             # set column width afeter set column order
374             for width, column in itertools.izip(
375                 states_dict["widths"], self.treeview.get_columns()):
376                 if width:
377                     column.set_fixed_width(width)
378
379             self.window.set_default_size(
380                 states_dict["window_width"], states_dict["window_height"])
381
382             if not states_dict["toolbar_visible"]:
383                 gobject.idle_add(self.toolbar.parent.hide,
384                                  priority=gobject.PRIORITY_HIGH)
385             if not states_dict["statusbar_visible"]:
386                 gobject.idle_add(self.statusbar.hide,
387                                  priority=gobject.PRIORITY_HIGH)
388             if not states_dict["filterbar_visible"]:
389                 gobject.idle_add(self.filterbar.hide,
390                                  priority=gobject.PRIORITY_HIGH)
391         except:
392             traceback.print_exc()
393
394     def on_menu_toolbar_activate(self, widget):
395         if self.toolbar.parent.get_property("visible"):
396             self.toolbar.parent.hide()
397         else:
398             self.toolbar.parent.show()
399
400     def on_menu_statusbar_activate(self, widget):
401         if self.statusbar.get_property("visible"):
402             self.statusbar.hide()
403         else:
404             self.statusbar.show()
405
406
407     # signal handlers
408
409     # menu and toolbutton
410
411     def on_menu_add_bookmark_activate(self, widget):
412         bookmark_list.bookmark_list.add_bookmark_with_edit(
413             uri=self.bbs_type.uri)
414
415     def on_menu_close_activate(self, widget):
416         self.destroy()
417
418     def on_menu_delete_activate(self, widget):
419         selection = self.treeview.get_selection()
420         model, iter = selection.get_selected()
421         if not iter:
422             return
423         thread = model.get_value(
424             iter, ThreadListModel.column_names.index("id"))
425         
426         bbs_type_for_thread = self.bbs_type.clone_with_thread(thread)
427
428         dat_path = misc.get_thread_dat_path(bbs_type_for_thread)
429         try:
430             os.remove(dat_path)
431         except OSError:
432             traceback.print_exc()
433         idx_path = misc.get_thread_idx_path(bbs_type_for_thread)
434         try:
435             os.remove(idx_path)
436         except OSError:
437             traceback.print_exc()
438         states_path = misc.get_thread_states_path(bbs_type_for_thread)
439         try:
440             os.remove(states_path)
441         except OSError:
442             traceback.print_exc()
443
444     def on_menu_filter_activate(self, widget):
445         self.filterbar.show()
446         self.entry_filterbar.grab_focus()
447
448     def on_menu_manage_bookmarks_activate(self, widget):
449         bookmark_window.open()
450
451     def on_menu_open_activate(self, widget):
452         self.open_thread()
453
454     def on_menu_quit_activate(self, widget):
455         session.main_quit()
456
457     def on_menu_refresh_activate(self, widget):
458         self.load(True)
459
460
461     # window
462
463     def on_window_board_delete_event(self, widget, event):
464         self.save()
465         return False
466
467     def on_window_board_destroy(self, widget):
468         self.popupmenu.destroy()
469         for column in self.treeview.get_columns():
470             column.destroy()
471         self.destroyed()
472
473
474     # treeview
475
476     def on_treeview_row_activated(self, widget, path, view_column):
477         self.open_thread()
478
479     def on_treeview_button_press_event(self, widget, event):
480         if event.button == 3:
481             x = int(event.x)
482             y = int(event.y)
483             time = event.time
484             pthinfo = widget.get_path_at_pos(x, y)
485             if pthinfo is not None:
486                 path, col, cellx, celly = pthinfo
487                 widget.grab_focus()
488                 widget.set_cursor(path, col, 0)
489                 self.popupmenu.popup(None, None, None, event.button, time)
490             return 1
491
492
493     # filterbar
494
495     def on_entry_filterbar_activate(self, widget):
496         text = widget.get_text()
497
498         def func(model, item):
499             try:
500                 item["title"].index(text)
501             except ValueError:
502                 return False
503             else:
504                 return True
505
506         model = self.treeview.get_model()
507         if model:
508             if text:
509                 filter_func = func
510             else:
511                 filter_func = None
512             model.refilter(filter_func)
513
514     def on_toolbutton_filterbar_close_clicked(self, widget):
515         self.filterbar.hide()
516
517     def on_button_filterbar_clear_clicked(self, widget):
518         self.entry_filterbar.set_text("")
519         model = self.treeview.get_model()
520         if model:
521             model.refilter(None)