OSDN Git Service

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