OSDN Git Service

Add bookmark.
[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
28 import board_data
29 import uri_opener
30 import misc
31 from threadlistmodel import ThreadListModel
32 from BbsType import bbs_type_judge_uri
33 import config
34 import session
35 import winwrapbase
36 from misc import ThreadInvoker
37 import bookmark_list
38 import bookmark_window
39
40 GLADE_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)),
41                          "..", "data")
42 GLADE_FILENAME = "board_window.glade"
43
44 def open_board(uri, update=False):
45     if not uri:
46         raise ValueError, "parameter must not be empty"
47
48     winwrap = session.get_window(uri)
49     if winwrap:
50         # already opened
51         winwrap.window.present()
52         if update:
53             winwrap.load(update)
54     else:
55         winwrap = WinWrap(uri)
56         winwrap.load(update)
57
58
59 class WinWrap(winwrapbase.WinWrapBase, board_data.BoardData):
60
61     def __init__(self, uri):
62
63         self.bbs_type = bbs_type_judge_uri.get_type(uri)
64         board_data.BoardData.__init__(self, self.bbs_type)
65         
66         glade_path = os.path.join(GLADE_DIR, GLADE_FILENAME)
67         self.widget_tree = gtk.glade.XML(glade_path)
68
69         self.window = self.widget_tree.get_widget("board_window")
70
71         self.window.set_title(self.bbs_type.uri)
72
73         self.treeview = self.widget_tree.get_widget("treeview")
74         self.treeview.set_model(ThreadListModel())
75
76         self.popupmenu = self.widget_tree.get_widget("popup_menu")
77         self.toolbar = self.widget_tree.get_widget("toolbar")
78         self.toolbar.unset_style()
79         self.statusbar = self.widget_tree.get_widget("appbar")
80         self.filterbar = self.widget_tree.get_widget("bonobodockitem_filterbar")
81         self.entry_filterbar = self.widget_tree.get_widget("entry_filterbar")
82
83         renderer = gtk.CellRendererText()
84
85         self.treeviewcolumn = {}
86         for i in range(1, len(ThreadListModel.column_names)):
87             column_name = ThreadListModel.column_names[i]
88             self.treeviewcolumn[column_name] = gtk.TreeViewColumn(
89                 column_name, renderer)
90             self.treeviewcolumn[column_name].set_resizable(True)
91             self.treeviewcolumn[column_name].set_reorderable(True)
92             self.treeviewcolumn[column_name].set_clickable(True)
93             self.treeviewcolumn[column_name].set_cell_data_func(
94                 renderer, self.on_cell_data, column_name)
95             self.treeviewcolumn[column_name].connect(
96                 "clicked", self.on_column_clicked, column_name)
97             self.treeviewcolumn[column_name].set_sizing(
98                 gtk.TREE_VIEW_COLUMN_FIXED)
99             self.treeviewcolumn[column_name].set_min_width(20)
100             self.treeviewcolumn[column_name].set_fixed_width(
101                 ThreadListModel.column_width[i])
102             self.treeview.append_column(self.treeviewcolumn[column_name])
103
104         self.treeviewcolumn["lastModified"].set_cell_data_func(
105             renderer, self.on_data_lastmodified)
106
107         self.treeview.set_fixed_height_mode(True)
108
109         sigdic = {"on_board_window_destroy": self.on_board_window_destroy,
110                   "on_quit_activate": self.on_quit_activate,
111                   "on_refresh_activate": self.on_refresh_activate,
112                   "on_treeview_row_activated":
113                   lambda w,p,v: self.on_open_thread(w),
114                   "on_treeview_button_press_event":
115                   self.on_treeview_button_press_event,
116                   "on_close_activate":
117                   self.on_close_activate,
118                   "on_toolbar_activate": self.on_toolbar_activate,
119                   "on_statusbar_activate": self.on_statusbar_activate,
120                   "on_board_window_delete_event":
121                   self.on_board_window_delete_event,
122                   "on_entry_filterbar_activate": self.on_entry_filterbar_activate,
123                   "on_filter_activate": self.on_filter_activate,
124                   "on_toolbutton_filterbar_close_clicked":
125                   self.on_toolbutton_filterbar_close_clicked,
126                   "on_button_filterbar_clear_clicked":
127                   self.on_button_filterbar_clear_clicked,
128                   "on_delete_activate": self.on_delete_activate,
129                   "on_manage_bookmarks_activate": \
130                   self.on_manage_bookmarks_activate,
131                   "on_add_bookmark_activate": self.on_add_bookmark_activate,
132                   "on_popup_menu_open_activate": self.on_open_thread}
133         self.widget_tree.signal_autoconnect(sigdic)
134
135         self.restore()
136         self.window.show()
137
138         self.created()
139
140     def set_status(self, text):
141         self.statusbar.set_status(text)
142
143     def destroy(self):
144         self.save()
145         self.window.destroy()
146
147     def get_uri(self):
148         return self.bbs_type.get_uri_base()
149
150     def on_toolbar_activate(self, widget):
151         if self.toolbar.parent.get_property("visible"):
152             self.toolbar.parent.hide()
153         else:
154             self.toolbar.parent.show()
155
156     def on_statusbar_activate(self, widget):
157         if self.statusbar.get_property("visible"):
158             self.statusbar.hide()
159         else:
160             self.statusbar.show()
161
162     def on_filter_activate(self, widget):
163         self.filterbar.show()
164         self.entry_filterbar.grab_focus()
165
166     def on_entry_filterbar_activate(self, widget):
167         text = widget.get_text()
168
169         def func(model, item):
170             try:
171                 item["title"].index(text)
172             except ValueError:
173                 return False
174             else:
175                 return True
176
177         model = self.treeview.get_model()
178         if model:
179             if text:
180                 model.set_filter_func(func)
181             else:
182                 model.set_filter_func(None)
183             model.refilter()
184
185     def on_toolbutton_filterbar_close_clicked(self, widget):
186         self.filterbar.hide()
187
188     def on_button_filterbar_clear_clicked(self, widget):
189         self.entry_filterbar.set_text("")
190         model = self.treeview.get_model()
191         if model:
192             model.set_filter_func(None)
193             model.refilter()
194
195     def updated_thread_highlight(self, column, cell, model, iter):
196
197         def is_updated_thread():
198             res = model.get_value(
199                 iter, ThreadListModel.column_names.index("res"))
200             linecount = model.get_value(
201                 iter, ThreadListModel.column_names.index("lineCount"))
202             return res != 0 and linecount != 0 and res > linecount
203
204         if is_updated_thread():
205             cell.set_property("weight", 800)
206         else:
207             cell.set_property("weight", 400)
208
209     def on_cell_data(self, column, cell, model, iter, column_name):
210         self.updated_thread_highlight(column, cell, model, iter)
211         column_num = ThreadListModel.column_names.index(column_name)
212         value = model.get_value(iter, column_num)
213         if model.get_column_type(column_num) \
214            in (gobject.TYPE_INT, gobject.TYPE_DOUBLE):
215             if value == 0:
216                 cell.set_property("text", "")
217             else:
218                 cell.set_property("text", str(value))
219         else:
220             cell.set_property("text", value)
221
222     def on_data_lastmodified(self, column, cell, model, iter, user_data=None):
223         self.updated_thread_highlight(column, cell, model, iter)
224         lastmod = model.get_value(
225             iter, ThreadListModel.column_names.index("lastModified"))
226         if lastmod == 0:
227             cell.set_property("text", "")
228         else:
229             cell.set_property("text", time.strftime(
230                 "%Y/%m/%d(%a) %H:%M:%S", time.localtime(lastmod)))
231
232     def on_board_window_delete_event(self, widget, event):
233         self.save()
234         return False
235
236     def on_board_window_destroy(self, widget):
237         self.destroyed()
238
239     def on_quit_activate(self, widget):
240         session.main_quit()
241
242     def on_close_activate(self, widget):
243         self.destroy()
244
245     def on_refresh_activate(self, widget):
246         self.load(True)
247
248     def on_column_clicked(self, treeviewcolumn, column_name):
249         model = self.treeview.get_model()
250         if model:
251             model.sort(column_name)
252             self.reset_sort_indicator()
253
254     def reset_sort_indicator(self):
255         model = self.treeview.get_model()
256         if model:
257             sort_column_name, sort_reverse = model.get_sort()
258             for name,column in self.treeviewcolumn.iteritems():
259                 column.set_sort_indicator(False)
260             if sort_column_name != "num" or sort_reverse:
261                 self.treeviewcolumn[sort_column_name].set_sort_indicator(True)
262                 if sort_reverse:
263                     self.treeviewcolumn[sort_column_name].set_sort_order(
264                         gtk.SORT_DESCENDING)
265                 else:
266                     self.treeviewcolumn[sort_column_name].set_sort_order(
267                         gtk.SORT_ASCENDING)
268         
269     def on_open_thread(self, widget):
270         treeselection = self.treeview.get_selection()
271         model, iter = treeselection.get_selected()
272         if not iter:
273             return
274
275         thread = model.get_value(iter, ThreadListModel.column_names.index("id"))
276         title = model.get_value(
277             iter, ThreadListModel.column_names.index("title"))
278         print thread + ':"' + title + '"', "activated"
279
280         res = model.get_value(iter, ThreadListModel.column_names.index("res"))
281         lineCount = model.get_value(
282             iter, ThreadListModel.column_names.index("lineCount"))
283
284         update = res > lineCount
285
286         bbs_type_for_thread = self.bbs_type.clone_with_thread(thread)
287         uri_opener.open_uri(bbs_type_for_thread.get_thread_uri(), update)
288
289     def on_add_bookmark_activate(self, widget):
290         bookmark_list.bookmark_list.add_bookmark_with_edit(
291             uri=self.bbs_type.uri)
292
293     def on_manage_bookmarks_activate(self, widget):
294         bookmark_window.open()
295
296     def on_delete_activate(self, widget):
297         selection = self.treeview.get_selection()
298         model, iter = selection.get_selected()
299         if not iter:
300             return
301         thread = model.get_value(
302             iter, ThreadListModel.column_names.index("id"))
303         
304         bbs_type_for_thread = self.bbs_type.clone_with_thread(thread)
305
306         dat_path = misc.get_thread_dat_path(bbs_type_for_thread)
307         try:
308             os.remove(dat_path)
309         except OSError:
310             traceback.print_exc()
311         idx_path = misc.get_thread_idx_path(bbs_type_for_thread)
312         try:
313             os.remove(idx_path)
314         except OSError:
315             traceback.print_exc()
316         states_path = misc.get_thread_states_path(bbs_type_for_thread)
317         try:
318             os.remove(states_path)
319         except OSError:
320             traceback.print_exc()
321
322     def on_treeview_button_press_event(self, widget, event):
323         if event.button == 3:
324             x = int(event.x)
325             y = int(event.y)
326             time = event.time
327             pthinfo = widget.get_path_at_pos(x, y)
328             if pthinfo is not None:
329                 path, col, cellx, celly = pthinfo
330                 widget.grab_focus()
331                 widget.set_cursor(path, col, 0)
332                 self.popupmenu.popup(None, None, None, event.button, time)
333             return 1
334
335     def update_datastore(self, datalist):
336         print "reflesh datastore"
337
338         list_list = []
339         for id, dic in datalist.iteritems():
340             dic["id"] = id
341
342             # lastModified
343             httpdate = dic["lastModified"]
344             try:
345                 secs = misc.httpdate_to_secs(httpdate)
346                 dic["lastModified"] = secs
347             except ValueError:
348                 dic["lastModified"] = 0
349
350             list_list.append(dic)
351
352         model = self.treeview.get_model()
353         model.set_list(list_list)
354
355         # redraw visible area after set list to model
356         self.treeview.queue_draw()
357
358         self.reset_sort_indicator()
359
360         print "end"
361
362     def on_thread_idx_updated(self, thread_uri, idx_dic):
363         if not thread_uri or not idx_dic:
364             return
365
366         # nothing to do if thread_uri does not belong to this board.
367         bbs_type = bbs_type_judge_uri.get_type(thread_uri)
368         if not bbs_type.is_thread() \
369                or not bbs_type.is_same_board(self.bbs_type):
370             return
371
372         thread = bbs_type.thread
373
374         model = self.treeview.get_model()
375         if model:
376             idx_dic["id"] = thread
377             try:
378                 idx_dic["lastModified"] =  misc.httpdate_to_secs(
379                     idx_dic["lastModified"])
380             except ValueError:
381                 idx_dic["lastModified"] = 0
382             model.modify_row(idx_dic)
383
384     def load(self, update=False):
385
386         def load_local():
387             datalist = self.load_idxfiles()
388             self.merge_local_subjecttxt(datalist)
389             gobject.idle_add(self.update_datastore, datalist)
390
391         def get_remote():
392             print "start get subject.txt"
393             datalist = self.load_idxfiles()
394             self.merge_remote_subjecttxt(datalist)
395             gobject.idle_add(self.update_datastore, datalist)
396
397         sbj_path = misc.get_board_subjecttxt_path(self.bbs_type)
398         sbj_exists = os.path.exists(sbj_path)
399
400         if update or not sbj_exists:
401             t = ThreadInvoker(lambda *args: -1, get_remote)
402             t.start()
403         else:
404             t = ThreadInvoker(lambda *args: -1, load_local)
405             t.start()
406
407     def save(self):
408         try:
409             states_path = misc.get_board_states_path(self.bbs_type)
410             dirname = os.path.dirname(states_path)
411
412             # save only if board dir exists.
413             if os.path.exists(dirname):
414                 window_width, window_height = self.window.get_size()
415                 toolbar_visible = self.toolbar.parent.get_property("visible")
416                 statusbar_visible = self.statusbar.get_property("visible")
417                 filterbar_visible = self.filterbar.get_property("visible")
418
419                 columns = self.treeview.get_columns()
420                 order = ""
421                 width = ""
422                 for column in columns:
423                     for name, kolumn in self.treeviewcolumn.iteritems():
424                         if column == kolumn:
425                             if order:
426                                 order += ","
427                             order += name
428                             if width:
429                                 width += ","
430                             width += str(column.get_width())
431                             break
432                 sort_column_name, sort_reverse = \
433                                   self.treeview.get_model().get_sort()
434                 sort_reverse = str(sort_reverse)
435
436                 f = file(states_path, "w")
437
438                 f.write("columns=" + order + "\n")
439                 f.write("widths=" + width + "\n")
440                 f.write("sort_column=" + sort_column_name + "\n")
441                 f.write("sort_reverse=" + sort_reverse + "\n")
442                 f.write("window_width=" + str(window_width) + "\n")
443                 f.write("window_height=" + str(window_height) + "\n")
444                 f.write("toolbar_visible=" + str(toolbar_visible) + "\n")
445                 f.write("statusbar_visible=" + str(statusbar_visible) + "\n")
446                 f.write("filterbar_visible=" + str(filterbar_visible) + "\n")
447
448                 f.close()
449         except:
450             traceback.print_exc()
451
452     def restore(self):
453         try:
454             window_height = 600
455             window_width = 600
456             toolbar_visible = True
457             statusbar_visible = True
458             filterbar_visible = False
459
460             try:
461                 key_base = config.gconf_app_key_base() + "/board_states"
462                 gconf_client = gconf.client_get_default()
463                 width = gconf_client.get_int(key_base + "/window_width")
464                 height = gconf_client.get_int(key_base + "/window_height")
465                 toolbar_visible = gconf_client.get_bool(key_base + "/toolbar")
466                 statusbar_visible = gconf_client.get_bool(key_base + "/statusbar")
467                 filterbar_visible = gconf_client.get_bool(key_base + "/filterbar")
468
469                 if width != 0:
470                     window_width = width
471                 if height != 0:
472                     window_height = height
473             except:
474                 traceback.print_exc()
475
476             states_path = misc.get_board_states_path(self.bbs_type)
477             if os.path.exists(states_path):
478                 sort_column_name = "num"
479                 sort_reverse = False
480                 for line in file(states_path):
481                     if line.startswith("columns="):
482                         line = line[len("columns="):].rstrip("\n")
483                         base_column = None
484                         for name in line.split(","):
485                             if name in self.treeviewcolumn:
486                                 column = self.treeviewcolumn[name]
487                                 self.treeview.move_column_after(
488                                     column, base_column)
489                                 base_column = column
490                     elif line.startswith("widths="):
491                         line = line[len("widths="):].rstrip("\n")
492                         columns = self.treeview.get_columns()
493                         for i, width in enumerate(line.split(",")):
494                             try:
495                                 width = int(width)
496                             except:
497                                 pass
498                             else:
499                                 if i < len(columns):
500                                     columns[i].set_fixed_width(width)
501                     elif line.startswith("sort_column="):
502                         kolumn_name = line[len("sort_column="):].rstrip("\n")
503                         if kolumn_name in ThreadListModel.column_names:
504                             sort_column_name = kolumn_name
505                     elif line.startswith("sort_reverse="):
506                         reverse = line[len("sort_reverse="):].rstrip("\n")
507                         sort_reverse = reverse == "True"
508                     elif line.startswith("window_height="):
509                         height = window_height
510                         try:
511                             height = int(
512                                 line[len("window_height="):].rstrip("\n"))
513                         except:
514                             pass
515                         else:
516                             window_height = height
517                     elif line.startswith("window_width="):
518                         width = window_width
519                         try:
520                             width = int(
521                                 line[len("window_width="):].rstrip("\n"))
522                         except:
523                             pass
524                         else:
525                             window_width = width
526                     elif line.startswith("toolbar_visible="):
527                         tbar = line[len("toolbar_visible="):].rstrip("\n")
528                         toolbar_visible = tbar == "True"
529                     elif line.startswith("statusbar_visible="):
530                         sbar = line[len("statusbar_visible="):].rstrip("\n")
531                         statusbar_visible = sbar == "True"
532                     elif line.startswith("filterbar_visible="):
533                         fbar = line[len("filterbar_visible="):].rstrip("\n")
534                         filterbar_visible = fbar == "True"
535
536                 self.treeview.get_model().sort(
537                     sort_column_name, True, sort_reverse)
538
539             self.window.set_default_size(window_width, window_height)
540
541             if not toolbar_visible:
542                 gobject.idle_add(self.toolbar.parent.hide,
543                                  priority=gobject.PRIORITY_HIGH)
544             if not statusbar_visible:
545                 gobject.idle_add(self.statusbar.hide,
546                                  priority=gobject.PRIORITY_HIGH)
547             if not filterbar_visible:
548                 gobject.idle_add(self.filterbar.hide,
549                                  priority=gobject.PRIORITY_HIGH)
550         except:
551             traceback.print_exc()