OSDN Git Service

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