OSDN Git Service

Fix a bug.
[fukui-no-namari/fukui-no-namari.git] / src / Hage1 / thread_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.path
23 import codecs
24 import re
25 import pango
26 import urllib2
27
28 import misc
29 import datfile
30 import barehtmlparser
31 import idxfile
32 import session
33 import board_window
34 from http_sub import HTTPRedirectHandler302
35
36 GLADE_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)),
37                          "..", "data")
38 GLADE_FILENAME = "thread_window.glade"
39
40 def open_thread(uri):
41     if not uri:
42         raise ValueError, "parameter must not be empty"
43
44     winwrap = session.get_window(uri)
45     if winwrap:
46         # already opened
47         winwrap.window.present()
48         pass
49     else:
50         win_wrap = WinWrap(uri)
51         session.window_created(uri, win_wrap)
52
53
54 class WinWrap:
55     hovering_over_link = False
56     hand_cursor = gtk.gdk.Cursor(gtk.gdk.HAND2)
57     regular_cursor = gtk.gdk.Cursor(gtk.gdk.XTERM)
58
59
60     def __init__(self, uri):
61         from BbsType import bbs_type_judge_uri
62         from BbsType import bbs_type_exception
63         self.bbs_type = bbs_type_judge_uri.get_type(uri)
64         if not self.bbs_type.is_thread():
65             raise bbs_type_exception.BbsTypeError, \
66                   "the uri does not represent thread: " + uri
67         self.bbs = self.bbs_type.bbs_type
68         self.board = self.bbs_type.board
69         self.thread = self.bbs_type.thread
70         self.host = self.bbs_type.host
71         self.uri = self.bbs_type.uri
72         self.size = 0
73         self.num = 0
74         self.title = None
75
76         glade_path = os.path.join(GLADE_DIR, GLADE_FILENAME)
77         self.widget_tree = gtk.glade.XML(glade_path)
78         self.window = self.widget_tree.get_widget("thread_window")
79         self.textview = self.widget_tree.get_widget("textview")
80         self.textbuffer = self.textview.get_buffer()
81         self.enditer = self.textbuffer.get_end_iter()
82         self.boldtag = self.textbuffer.create_tag(weight=pango.WEIGHT_BOLD)
83         self.leftmargintag = self.textbuffer.create_tag()
84         self.leftmargintag.set_property("left-margin", 20)
85
86         sigdic = {"on_toolbutton_refresh_clicked": self.update,
87                   "on_refresh_activate": self.update,
88                   "on_close_activate": self.on_close_activate,
89                   "on_quit_activate": self.on_quit_activate,
90                   "on_show_board_activate": self.on_show_board_activate,
91                   "on_thread_window_destroy": self.on_thread_window_destroy}
92         self.widget_tree.signal_autoconnect(sigdic)
93
94         self.textview.connect("event-after", self.on_event_after)
95         self.textview.connect("motion-notify-event",
96                               self.on_motion_notify_event)
97         self.textview.connect("visibility-notify-event",
98                               self.on_visibility_notify_event)
99
100         self.load_dat()
101
102     def on_event_after(self, widget, event):
103         if event.type != gtk.gdk.BUTTON_RELEASE:
104             return False
105         if event.button != 1:
106             return False
107         buffer = widget.get_buffer()
108
109         try:
110             start, end = buffer.get_selection_bounds()
111         except ValueError:
112             pass
113         else:
114             if start.get_offset() != end.get_offset():
115                 return False
116
117         x, y = widget.window_to_buffer_coords(
118             gtk.TEXT_WINDOW_WIDGET, int (event.x), int(event.y))
119         iter = widget.get_iter_at_location(x, y)
120         if not iter.has_tag(self.leftmargintag) or x > 20:
121             tags = iter.get_tags()
122             for tag in tags:
123                 href = tag.get_data("href")
124                 if href:
125                     self.on_link_clicked(widget, href)
126         return False
127
128     def on_link_clicked(self, widget, href):
129         if href.startswith("http://"):
130             from BbsType import bbs_type_exception
131             import uri_opener
132             try:
133                 uri_opener.open_uri(href)
134             except bbs_type_exception.BbsTypeError:
135                 import gnome
136                 gnome.url_show(href)
137
138     def on_motion_notify_event(self, widget, event):
139         x, y = widget.window_to_buffer_coords(
140             gtk.TEXT_WINDOW_WIDGET, int(event.x), int(event.y))
141         self.set_cursor_if_appropriate(widget, x, y)
142         widget.window.get_pointer()
143         return False
144
145     def on_visibility_notify_event(self, widget, event):
146         wx, wy, mod = widget.window.get_pointer()
147         bx, by = widget.window_to_buffer_coords(gtk.TEXT_WINDOW_WIDGET, wx, wy)
148
149         self.set_cursor_if_appropriate(widget, bx, by)
150         return False
151
152     def set_cursor_if_appropriate(self, widget, x, y):
153         hovering = False
154
155         buffer = widget.get_buffer()
156         iter = widget.get_iter_at_location(x, y)
157         if not iter.has_tag(self.leftmargintag) or x > 20:
158             tags = iter.get_tags()
159             for tag in tags:
160                 href = tag.get_data("href")
161                 if href:
162                     hovering = True
163
164         if hovering != self.hovering_over_link:
165             self.hovering_over_link = hovering
166
167         if self.hovering_over_link:
168             widget.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(
169                 self.hand_cursor)
170         else:
171             widget.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(
172                 self.regular_cursor)
173
174     def on_close_activate(self, widget):
175         self.window.destroy()
176
177     def on_thread_window_destroy(self, widget):
178         -1
179
180     def on_quit_activate(self, widget):
181         session.main_quit()
182
183     def on_show_board_activate(self, widget):
184         board_window.open_board(self.bbs_type.get_uri_base())
185
186     def http_get_dat(self, on_get_res):
187         datfile_url = self.bbs_type.get_dat_uri()
188
189         idx_dic = idxfile.load_idx(self.bbs, self.board, self.thread)
190         lastmod = idx_dic["lastModified"]
191         etag = idx_dic["etag"]
192
193         req = urllib2.Request(datfile_url)
194         if self.size > 0:
195             req.add_header("Range", "bytes=" + str(self.size) + "-")
196         if lastmod:
197             req.add_header("If-Modified-Since", lastmod)
198         if etag:
199             req.add_header("If-None-Match", etag)
200         print req.headers
201
202         opener = urllib2.build_opener(HTTPRedirectHandler302)
203         res = opener.open(req)
204         headers = res.info()
205         print headers
206
207         line = res.readline()
208         maybe_incomplete = False
209         while line:
210             if not line.endswith("\n"):
211                 maybe_incomplete = True
212                 print "does not end with \\n. maybe incomplete"
213                 break
214             on_get_res(line)
215             line = res.readline()
216
217         res.close()
218
219         if maybe_incomplete:
220             lastmod = None
221             etag = None
222         else:
223             if "Last-Modified" in headers:
224                 lastmod = headers["Last-Modified"]
225             if "ETag" in headers:
226                 etag = headers["Etag"]
227
228         if self.num > 0:
229             if not self.title:
230                 self.title = datfile.get_title_from_dat(
231                     self.bbs, self.board, self.thread)
232                 if self.title:
233                     self.window.set_title(self.title)
234             # save idx
235             idx_dic = {"title": self.title, "lineCount": self.num,
236                    "lastModified": lastmod, "etag": etag}
237             idxfile.save_idx(self.bbs, self.board, self.thread, idx_dic)
238
239             session.thread_idx_updated(
240                 self.bbs_type.get_thread_uri(), idx_dic)
241
242     def update(self, widget=None):
243         line_count = datfile.get_dat_line_count(
244             self.bbs, self.board, self.thread)
245
246         if line_count > self.num:
247             datfile.load_dat_partly(
248                 self.bbs, self.board, self.thread,
249                 self.append_rawres_to_buffer, self.num+1)
250
251
252         class FileWrap:
253             def __init__(self, path):
254                 self._file = None
255                 self._path = path
256             def __del__(self):
257                 self.close()
258             def seek(self, size):
259                 self.file().seek(size)
260             def write(self, data):
261                 self.file().write(data)
262             def close(self):
263                 if self._file:
264                     self._file.close()
265                     self._file = None
266             def file(self):
267                 if not self._file:
268                     basedir = os.path.dirname(self._path)
269                     if not os.path.isdir(basedir):
270                         os.makedirs(basedir)
271                     self._file = file(self._path, "a+")
272                 return self._file
273
274         dat_path = misc.get_thread_dat_path(self.bbs, self.board, self.thread)
275         dat_file = FileWrap(dat_path)
276
277         def save_line_and_append_to_buffer(line):
278             dat_file.seek(self.size)
279             dat_file.write(line)
280             self.append_rawres_to_buffer(line)
281
282         self.http_get_dat(save_line_and_append_to_buffer)
283         dat_file.close()
284
285     def load_dat(self):
286         self.size = 0
287         self.num = 0
288
289         self.title = datfile.get_title_from_dat(
290             self.bbs, self.board, self.thread)
291         if self.title:
292             self.window.set_title(self.title)
293
294         datfile.load_dat(self.bbs, self.board, self.thread,
295                          self.append_rawres_to_buffer)
296         self.textview.scroll_to_mark(self.textbuffer.get_insert(), 0)
297
298
299     def append_rawres_to_buffer(self, line):
300         self.size += len(line)
301         self.num += 1
302
303         h = lambda name,mail,date,msg: self.reselems_to_buffer(
304             self.num, name, mail, date, msg)
305
306         datfile.split_line_to_elems(line.decode("cp932", "replace"), h)
307         
308     def reselems_to_buffer(self, num, name, mail, date, msg):
309         p = barehtmlparser.BareHTMLParser(self.untiedata_to_buffer)
310         # number
311         p.feed(str(num) + " ")
312
313         # name
314         p.feed("<b>" + name + "</b>")
315
316         # mail
317         p.feed("[" + mail + "]")
318
319         # date
320         p.feed(date)
321         p.feed("<br>")
322
323         # msg
324         p.reset_func(self.untiedata_to_buffer_with_leftmargin)
325         p.feed(msg.lstrip(" "))
326
327         p.feed("<br><br>")
328         p.close()
329
330     def href_tag(self, href):
331         tag = self.textbuffer.create_tag(underline=pango.UNDERLINE_SINGLE)
332         tag.set_data("href", href)
333         return tag
334
335     def untiedata_to_buffer(self, data, bold, href):
336         if bold:
337             if href:
338                 self.textbuffer.insert_with_tags(
339                     self.enditer, data, self.boldtag, self.href_tag(href))
340             else:                
341                 self.textbuffer.insert_with_tags(
342                     self.enditer, data, self.boldtag)
343         else:
344             if href:
345                 self.textbuffer.insert(self.enditer, data, self.href(href))
346             else:
347                 self.textbuffer.insert(self.enditer, data)
348
349     def untiedata_to_buffer_with_leftmargin(self, data, bold, href):
350         if bold:
351             if href:
352                 self.textbuffer.insert_with_tags(
353                     self.enditer, data, self.boldtag,
354                     self.leftmargintag,self.href_tag(href))
355             else:
356                 self.textbuffer.insert_with_tags(
357                     self.enditer, data, self.boldtag, self.leftmargintag)
358         else:
359             if href:
360                 self.textbuffer.insert_with_tags(
361                     self.enditer, data,
362                     self.leftmargintag, self.href_tag(href))
363             else:
364                 self.textbuffer.insert_with_tags(
365                     self.enditer, data, self.leftmargintag)