OSDN Git Service

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