OSDN Git Service

f1132bf4cbe9be4f63e3cf7e90e39ce85053204c
[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         self.bbs = self.bbs_type.bbs_type
64         self.host = self.bbs_type.host
65         self.board = self.bbs_type.board
66         self.uri = self.bbs_type.uri
67         
68         glade_path = os.path.join(GLADE_DIR, GLADE_FILENAME)
69         self.widget_tree = gtk.glade.XML(glade_path)
70
71         self.window = self.widget_tree.get_widget("board_window")
72
73         self.window.set_title(self.uri)
74
75         self.treeview = self.widget_tree.get_widget("treeview")
76         self.treeview.set_model(ThreadListModel())
77
78         self.popupmenu = self.widget_tree.get_widget("popup_menu")
79         self.toolbar = self.widget_tree.get_widget("toolbar")
80         self.toolbar.unset_style()
81         self.statusbar = self.widget_tree.get_widget("appbar")
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_popup_menu_open_activate": self.on_open_thread}
123         self.widget_tree.signal_autoconnect(sigdic)
124
125         self.restore()
126         self.window.show()
127
128         self.created()
129
130     def set_status(self, text):
131         self.statusbar.set_status(text)
132
133     def destroy(self):
134         self.save()
135         self.window.destroy()
136
137     def get_uri(self):
138         return self.bbs_type.get_uri_base()
139
140     def on_toolbar_activate(self, widget):
141         if self.toolbar.parent.get_property("visible"):
142             self.toolbar.parent.hide()
143         else:
144             self.toolbar.parent.show()
145
146     def on_statusbar_activate(self, widget):
147         if self.statusbar.get_property("visible"):
148             self.statusbar.hide()
149         else:
150             self.statusbar.show()
151
152     def updated_thread_highlight(self, column, cell, model, iter):
153
154         def is_updated_thread():
155             res = model.get_value(
156                 iter, ThreadListModel.column_names.index("res"))
157             linecount = model.get_value(
158                 iter, ThreadListModel.column_names.index("lineCount"))
159             return res != 0 and linecount != 0 and res > linecount
160
161         if is_updated_thread():
162             cell.set_property("weight", 800)
163         else:
164             cell.set_property("weight", 400)
165
166     def on_cell_data(self, column, cell, model, iter, column_name):
167         self.updated_thread_highlight(column, cell, model, iter)
168         column_num = ThreadListModel.column_names.index(column_name)
169         value = model.get_value(iter, column_num)
170         if model.get_column_type(column_num) \
171            in (gobject.TYPE_INT, gobject.TYPE_DOUBLE):
172             if value == 0:
173                 cell.set_property("text", "")
174             else:
175                 cell.set_property("text", str(value))
176         else:
177             cell.set_property("text", value)
178
179     def on_data_lastmodified(self, column, cell, model, iter, user_data=None):
180         self.updated_thread_highlight(column, cell, model, iter)
181         lastmod = model.get_value(
182             iter, ThreadListModel.column_names.index("lastModified"))
183         if lastmod == 0:
184             cell.set_property("text", "")
185         else:
186             cell.set_property("text", time.strftime(
187                 "%Y/%m/%d(%a) %H:%M:%S", time.localtime(lastmod)))
188
189     def on_board_window_delete_event(self, widget, event):
190         self.save()
191         return False
192
193     def on_board_window_destroy(self, widget):
194         self.destroyed()
195
196     def on_quit_activate(self, widget):
197         session.main_quit()
198
199     def on_close_activate(self, widget):
200         self.destroy()
201
202     def on_refresh_activate(self, widget):
203         self.load(True)
204
205     def on_column_clicked(self, treeviewcolumn, column_name):
206         model = self.treeview.get_model()
207         if model:
208             model.sort(column_name)
209             self.reset_sort_indicator()
210
211     def reset_sort_indicator(self):
212         model = self.treeview.get_model()
213         if model:
214             sort_column_name, sort_reverse = model.get_sort()
215             for name,column in self.treeviewcolumn.iteritems():
216                 column.set_sort_indicator(False)
217             if sort_column_name != "num" or sort_reverse:
218                 self.treeviewcolumn[sort_column_name].set_sort_indicator(True)
219                 if sort_reverse:
220                     self.treeviewcolumn[sort_column_name].set_sort_order(
221                         gtk.SORT_DESCENDING)
222                 else:
223                     self.treeviewcolumn[sort_column_name].set_sort_order(
224                         gtk.SORT_ASCENDING)
225         
226     def on_open_thread(self, widget):
227         treeselection = self.treeview.get_selection()
228         model, iter = treeselection.get_selected()
229         if not iter:
230             return
231
232         thread = model.get_value(iter, ThreadListModel.column_names.index("id"))
233         title = model.get_value(
234             iter, ThreadListModel.column_names.index("title"))
235         print thread + ':"' + title + '"', "activated"
236
237         res = model.get_value(iter, ThreadListModel.column_names.index("res"))
238         lineCount = model.get_value(
239             iter, ThreadListModel.column_names.index("lineCount"))
240
241         update = res > lineCount
242
243         bbs_type_for_thread = self.bbs_type.clone_with_thread(thread)
244         uri_opener.open_uri(bbs_type_for_thread.get_thread_uri(), update)
245
246     def on_treeview_button_press_event(self, widget, event):
247         if event.button == 3:
248             x = int(event.x)
249             y = int(event.y)
250             time = event.time
251             pthinfo = widget.get_path_at_pos(x, y)
252             if pthinfo is not None:
253                 path, col, cellx, celly = pthinfo
254                 widget.grab_focus()
255                 widget.set_cursor(path, col, 0)
256                 self.popupmenu.popup(None, None, None, event.button, time)
257             return 1
258
259     def update_datastore(self, datalist):
260         print "reflesh datastore"
261
262         list_list = []
263         for id, dic in datalist.iteritems():
264             dic["id"] = id
265
266             # lastModified
267             httpdate = dic["lastModified"]
268             try:
269                 secs = misc.httpdate_to_secs(httpdate)
270                 dic["lastModified"] = secs
271             except ValueError:
272                 dic["lastModified"] = 0
273
274             list_list.append(dic)
275
276         model = self.treeview.get_model()
277         model.set_list(list_list)
278
279         # redraw visible area after set list to model
280         self.treeview.queue_draw()
281
282         self.reset_sort_indicator()
283
284         print "end"
285
286     def on_thread_idx_updated(self, thread_uri, idx_dic):
287         if not thread_uri or not idx_dic:
288             return
289
290         # nothing to do if thread_uri does not belong to this board.
291         bbs_type = bbs_type_judge_uri.get_type(thread_uri)
292         if bbs_type.bbs_type != self.bbs \
293            or bbs_type.board != self.board or not bbs_type.is_thread():
294             return
295
296         thread = bbs_type.thread
297
298         model = self.treeview.get_model()
299         if model:
300             idx_dic["id"] = thread
301             try:
302                 idx_dic["lastModified"] =  misc.httpdate_to_secs(
303                     idx_dic["lastModified"])
304             except ValueError:
305                 idx_dic["lastModified"] = 0
306             model.modify_row(idx_dic)
307
308     def load(self, update=False):
309
310         def load_local():
311             datalist = self.load_idxfiles()
312             self.merge_local_subjecttxt(datalist)
313             gobject.idle_add(self.update_datastore, datalist)
314
315         def get_remote():
316             print "start get subject.txt"
317             datalist = self.load_idxfiles()
318             self.merge_remote_subjecttxt(datalist)
319             gobject.idle_add(self.update_datastore, datalist)
320
321         sbj_path = misc.get_board_subjecttxt_path(
322             self.bbs_type.bbs_type, self.bbs_type.board)
323         sbj_exists = os.path.exists(sbj_path)
324
325         if update or not sbj_exists:
326             t = ThreadInvoker(lambda *args: -1, get_remote)
327             t.start()
328         else:
329             t = ThreadInvoker(lambda *args: -1, load_local)
330             t.start()
331
332     def save(self):
333         try:
334             states_path = misc.get_board_states_path(
335                 self.bbs_type.bbs_type, self.bbs_type.board)
336             dirname = os.path.dirname(states_path)
337
338             # save only if board dir exists.
339             if os.path.exists(dirname):
340                 window_width, window_height = self.window.get_size()
341                 toolbar_visible = self.toolbar.parent.get_property("visible")
342                 statusbar_visible = self.statusbar.get_property("visible")
343
344                 columns = self.treeview.get_columns()
345                 order = ""
346                 width = ""
347                 for column in columns:
348                     for name, kolumn in self.treeviewcolumn.iteritems():
349                         if column == kolumn:
350                             if order:
351                                 order += ","
352                             order += name
353                             if width:
354                                 width += ","
355                             width += str(column.get_width())
356                             break
357                 sort_column_name, sort_reverse = \
358                                   self.treeview.get_model().get_sort()
359                 sort_reverse = str(sort_reverse)
360
361                 f = file(states_path, "w")
362
363                 f.write("columns=" + order + "\n")
364                 f.write("widths=" + width + "\n")
365                 f.write("sort_column=" + sort_column_name + "\n")
366                 f.write("sort_reverse=" + sort_reverse + "\n")
367                 f.write("window_width=" + str(window_width) + "\n")
368                 f.write("window_height=" + str(window_height) + "\n")
369                 f.write("toolbar_visible=" + str(toolbar_visible) + "\n")
370                 f.write("statusbar_visible=" + str(statusbar_visible) + "\n")
371
372                 f.close()
373         except:
374             traceback.print_exc()
375
376     def restore(self):
377         try:
378             window_height = 600
379             window_width = 600
380             toolbar_visible = True
381             statusbar_visible = True
382
383             try:
384                 key_base = config.gconf_app_key_base() + "/board_states"
385                 gconf_client = gconf.client_get_default()
386                 width = gconf_client.get_int(key_base + "/window_width")
387                 height = gconf_client.get_int(key_base + "/window_height")
388                 toolbar_visible = gconf_client.get_bool(
389                     key_base + "/toolbar")
390                 statusbar_visible = gconf_client.get_bool(
391                     key_base + "/statusbar")
392                 if width != 0:
393                     window_width = width
394                 if height != 0:
395                     window_height = height
396             except:
397                 traceback.print_exc()
398
399             states_path = misc.get_board_states_path(
400                 self.bbs_type.bbs_type, self.bbs_type.board)
401             if os.path.exists(states_path):
402                 sort_column_name = "num"
403                 sort_reverse = False
404                 for line in file(states_path):
405                     if line.startswith("columns="):
406                         line = line[len("columns="):].rstrip("\n")
407                         base_column = None
408                         for name in line.split(","):
409                             if name in self.treeviewcolumn:
410                                 column = self.treeviewcolumn[name]
411                                 self.treeview.move_column_after(
412                                     column, base_column)
413                                 base_column = column
414                     elif line.startswith("widths="):
415                         line = line[len("widths="):].rstrip("\n")
416                         columns = self.treeview.get_columns()
417                         for i, width in enumerate(line.split(",")):
418                             try:
419                                 width = int(width)
420                             except:
421                                 pass
422                             else:
423                                 if i < len(columns):
424                                     columns[i].set_fixed_width(width)
425                     elif line.startswith("sort_column="):
426                         kolumn_name = line[len("sort_column="):].rstrip("\n")
427                         if kolumn_name in ThreadListModel.column_names:
428                             sort_column_name = kolumn_name
429                     elif line.startswith("sort_reverse="):
430                         reverse = line[len("sort_reverse="):].rstrip("\n")
431                         sort_reverse = reverse == "True"
432                     elif line.startswith("window_height="):
433                         height = window_height
434                         try:
435                             height = int(
436                                 line[len("window_height="):].rstrip("\n"))
437                         except:
438                             pass
439                         else:
440                             window_height = height
441                     elif line.startswith("window_width="):
442                         width = window_width
443                         try:
444                             width = int(
445                                 line[len("window_width="):].rstrip("\n"))
446                         except:
447                             pass
448                         else:
449                             window_width = width
450                     elif line.startswith("toolbar_visible="):
451                         tbar = line[len("toolbar_visible="):].rstrip("\n")
452                         toolbar_visible = tbar == "True"
453                     elif line.startswith("statusbar_visible="):
454                         sbar = line[len("statusbar_visible="):].rstrip("\n")
455                         statusbar_visible = sbar == "True"
456
457                 self.treeview.get_model().sort(
458                     sort_column_name, True, sort_reverse)
459
460             self.window.set_default_size(window_width, window_height)
461
462             if not toolbar_visible:
463                 gobject.idle_add(self.toolbar.parent.hide)
464             if not statusbar_visible:
465                 gobject.idle_add(self.statusbar.hide)
466         except:
467             traceback.print_exc()