OSDN Git Service

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