X-Git-Url: http://git.sourceforge.jp/view?p=fukui-no-namari%2Ffukui-no-namari.git;a=blobdiff_plain;f=src%2FHage1%2Fthread_window.py;h=94969ea86b63dab3c9a73df365fdbca1a82e252e;hp=025f33908a6a64182cee788a10678c85e35b2860;hb=6aac5b490c6ea7a1b681711db3662ccf2022b410;hpb=e767eb15b9cf477a3c01bf8ffe8bee35691bd955
diff --git a/src/Hage1/thread_window.py b/src/Hage1/thread_window.py
index 025f339..94969ea 100644
--- a/src/Hage1/thread_window.py
+++ b/src/Hage1/thread_window.py
@@ -24,6 +24,11 @@ import codecs
import re
import pango
import urllib2
+import urlparse
+import gnome
+import gobject
+import threading
+import gconf
import misc
import datfile
@@ -31,27 +36,82 @@ import barehtmlparser
import idxfile
import session
import board_window
+import uri_opener
from http_sub import HTTPRedirectHandler302
+from BbsType import bbs_type_judge_uri
+from BbsType import bbs_type_exception
+import config
GLADE_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)),
"..", "data")
GLADE_FILENAME = "thread_window.glade"
-def open_thread(uri):
+def open_thread(uri, update=False):
if not uri:
raise ValueError, "parameter must not be empty"
+ bbs_type = bbs_type_judge_uri.get_type(uri)
+ if not bbs_type.is_thread():
+ raise bbs_type_exception.BbsTypeError, \
+ "the uri does not represent thread: " + uri
+ uri = bbs_type.get_thread_uri() # use strict thread uri
+
winwrap = session.get_window(uri)
if winwrap:
# already opened
winwrap.window.present()
- pass
+ if update:
+ winwrap.load(update)
else:
- win_wrap = WinWrap(uri)
- session.window_created(uri, win_wrap)
+ winwrap = WinWrap(bbs_type.uri) # pass original uri
+ session.window_created(uri, winwrap)
+ winwrap.load(update)
+
+ # jump to the res if necessary.
+ winwrap.jump_to_res(bbs_type.uri)
+
+
+class ThreadInvoker(threading.Thread):
+ def __init__(self, on_end, *methods):
+ super(ThreadInvoker, self).__init__()
+ self.on_end = on_end
+ self.methods = methods
+ def run(self):
+ try:
+ for m in self.methods:
+ m()
+ finally:
+ self.on_end()
+
+
+class FileWrap:
+ def __init__(self, path):
+ self._file = None
+ self._path = path
+ def __del__(self):
+ self.close()
+ def seek(self, size):
+ self.file().seek(size)
+ def write(self, data):
+ self.file().write(data)
+ def close(self):
+ if self._file:
+ self._file.close()
+ self._file = None
+ def file(self):
+ if not self._file:
+ basedir = os.path.dirname(self._path)
+ if not os.path.isdir(basedir):
+ os.makedirs(basedir)
+ self._file = file(self._path, "a+")
+ return self._file
class WinWrap:
+ hovering_over_link = False
+ hand_cursor = gtk.gdk.Cursor(gtk.gdk.HAND2)
+ regular_cursor = gtk.gdk.Cursor(gtk.gdk.XTERM)
+
def __init__(self, uri):
from BbsType import bbs_type_judge_uri
@@ -67,11 +127,16 @@ class WinWrap:
self.uri = self.bbs_type.uri
self.size = 0
self.num = 0
- self.title = None
+ self.title = ""
+ self.lock_obj = False
+ self.jump_request_num = 0
+ self.progress = False
glade_path = os.path.join(GLADE_DIR, GLADE_FILENAME)
self.widget_tree = gtk.glade.XML(glade_path)
self.window = self.widget_tree.get_widget("thread_window")
+ self.toolbar = self.widget_tree.get_widget("toolbar")
+ self.statusbar = self.widget_tree.get_widget("appbar")
self.textview = self.widget_tree.get_widget("textview")
self.textbuffer = self.textview.get_buffer()
self.enditer = self.textbuffer.get_end_iter()
@@ -79,19 +144,146 @@ class WinWrap:
self.leftmargintag = self.textbuffer.create_tag()
self.leftmargintag.set_property("left-margin", 20)
- sigdic = {"on_toolbutton_refresh_clicked": self.update,
+ sigdic = {"on_refresh_activate": self.update,
+ "on_compose_activate": self.on_compose_clicked,
+ "on_toolbar_activate": self.on_toolbar_activate,
+ "on_statusbar_activate": self.on_statusbar_activate,
"on_refresh_activate": self.update,
"on_close_activate": self.on_close_activate,
"on_quit_activate": self.on_quit_activate,
"on_show_board_activate": self.on_show_board_activate,
+ "on_thread_window_delete_event":
+ self.on_thread_window_delete_event,
"on_thread_window_destroy": self.on_thread_window_destroy}
self.widget_tree.signal_autoconnect(sigdic)
- self.load_dat()
+ self.textview.connect("event-after", self.on_event_after)
+ self.textview.connect("motion-notify-event",
+ self.on_motion_notify_event)
+ self.textview.connect("visibility-notify-event",
+ self.on_visibility_notify_event)
+
+ self.gconf_client = gconf.client_get_default()
+ self.gconf_key_base = "/apps/" + config.APPNAME.lower() + \
+ "/thread_states/"
+
+ width = self.gconf_client.get_int(
+ self.gconf_key_base + "window_width")
+ height = self.gconf_client.get_int(
+ self.gconf_key_base + "window_height")
+ self.window.set_default_size(width, height)
+
+ if not self.gconf_client.get_bool(self.gconf_key_base + "toolbar"):
+ self.toolbar.parent.hide()
+ if not self.gconf_client.get_bool(self.gconf_key_base + "statusbar"):
+ self.statusbar.hide()
+
+ self.window.show()
+
+ def on_compose_clicked(self, widget):
+ import submit_window
+ submit_window.open(self.bbs_type.get_thread_uri())
+
+ def on_toolbar_activate(self, widget):
+ if self.toolbar.parent.get_property("visible"):
+ self.toolbar.parent.hide()
+ self.gconf_client.set_bool(self.gconf_key_base + "toolbar", False)
+ else:
+ self.toolbar.parent.show()
+ self.gconf_client.set_bool(self.gconf_key_base + "toolbar", True)
+
+ def on_statusbar_activate(self, widget):
+ if self.statusbar.get_property("visible"):
+ self.statusbar.hide()
+ self.gconf_client.set_bool(self.gconf_key_base+"statusbar", False)
+ else:
+ self.statusbar.show()
+ self.gconf_client.set_bool(self.gconf_key_base + "statusbar", True)
+
+ def on_event_after(self, widget, event):
+ if event.type != gtk.gdk.BUTTON_RELEASE:
+ return False
+ if event.button != 1:
+ return False
+ buffer = widget.get_buffer()
+
+ try:
+ start, end = buffer.get_selection_bounds()
+ except ValueError:
+ pass
+ else:
+ if start.get_offset() != end.get_offset():
+ return False
+
+ x, y = widget.window_to_buffer_coords(
+ gtk.TEXT_WINDOW_WIDGET, int (event.x), int(event.y))
+ iter = widget.get_iter_at_location(x, y)
+ if not iter.has_tag(self.leftmargintag) or x > 20:
+ tags = iter.get_tags()
+ for tag in tags:
+ href = tag.get_data("href")
+ if href:
+ self.on_link_clicked(widget, href)
+ return False
+
+ def on_link_clicked(self, widget, href):
+
+ if not href.startswith("http://"):
+ # maybe a relative uri.
+ href = urlparse.urljoin(self.bbs_type.get_uri_base(), href)
+
+ try:
+ uri_opener.open_uri(href)
+ except bbs_type_exception.BbsTypeError:
+ # not supported, show with the web browser.
+ gnome.url_show(href)
+
+ def on_motion_notify_event(self, widget, event):
+ x, y = widget.window_to_buffer_coords(
+ gtk.TEXT_WINDOW_WIDGET, int(event.x), int(event.y))
+ self.set_cursor_if_appropriate(widget, x, y)
+ widget.window.get_pointer()
+ return False
+
+ def on_visibility_notify_event(self, widget, event):
+ wx, wy, mod = widget.window.get_pointer()
+ bx, by = widget.window_to_buffer_coords(gtk.TEXT_WINDOW_WIDGET, wx, wy)
+
+ self.set_cursor_if_appropriate(widget, bx, by)
+ return False
+
+ def set_cursor_if_appropriate(self, widget, x, y):
+ hovering = False
+
+ buffer = widget.get_buffer()
+ iter = widget.get_iter_at_location(x, y)
+ if not iter.has_tag(self.leftmargintag) or x > 20:
+ tags = iter.get_tags()
+ for tag in tags:
+ href = tag.get_data("href")
+ if href:
+ hovering = True
+
+ if hovering != self.hovering_over_link:
+ self.hovering_over_link = hovering
+
+ if self.hovering_over_link:
+ widget.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(
+ self.hand_cursor)
+ else:
+ widget.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(
+ self.regular_cursor)
def on_close_activate(self, widget):
self.window.destroy()
+ def on_thread_window_delete_event(self, widget, event):
+ w, h = widget.get_size()
+ self.gconf_client.set_int(self.gconf_key_base + "window_width", w)
+ self.gconf_client.set_int(self.gconf_key_base + "window_height", h)
+
+ return False
+
def on_thread_window_destroy(self, widget):
-1
@@ -145,95 +337,226 @@ class WinWrap:
if self.num > 0:
if not self.title:
- self.title = datfile.get_title_from_dat(
+ title = datfile.get_title_from_dat(
self.bbs, self.board, self.thread)
- if self.title:
- self.window.set_title(self.title)
+ if title:
+ self.title = title
+ gobject.idle_add(self.window.set_title, title)
# save idx
idx_dic = {"title": self.title, "lineCount": self.num,
"lastModified": lastmod, "etag": etag}
idxfile.save_idx(self.bbs, self.board, self.thread, idx_dic)
- session.thread_idx_updated(
- self.bbs_type.get_thread_uri(), idx_dic)
+ gobject.idle_add(session.thread_idx_updated,
+ self.bbs_type.get_thread_uri(), idx_dic)
def update(self, widget=None):
- line_count = datfile.get_dat_line_count(
- self.bbs, self.board, self.thread)
-
- if line_count > self.num:
- datfile.load_dat_partly(
- self.bbs, self.board, self.thread,
- self.append_rawres_to_buffer, self.num+1)
-
- dat_path = misc.get_thread_dat_path(self.bbs, self.board, self.thread)
- dat_file = file(dat_path, "a+")
- def save_line_and_append_to_buffer(line):
- dat_file.seek(self.size)
- dat_file.write(line)
- self.append_rawres_to_buffer(line)
-
- self.http_get_dat(save_line_and_append_to_buffer)
- dat_file.close()
+ self.jump_request_num = 0
+
+ def load():
+ if self.num == 0:
+ def create_mark():
+ self.textbuffer.create_mark("1", self.enditer, True)
+ gobject.idle_add(create_mark)
+
+ line_count = datfile.get_dat_line_count(
+ self.bbs, self.board, self.thread)
+ if line_count > self.num:
+ datfile.load_dat_partly(
+ self.bbs, self.board, self.thread,
+ self.append_rawres_to_buffer, self.num+1)
+
+ def do_jump(num):
+ if self.jump_request_num:
+ if self.jump_request_num <= num:
+ # jump if enable, otherwize jump later.
+ num = self.jump_request_num
+ self.jump_request_num = 0
+ mark = self.textbuffer.get_mark(str(num))
+ if mark:
+ self.textview.scroll_to_mark(
+ mark, 0, True, 0, 0)
+ else:
+ self.jump_to_the_end(num)
+
+ gobject.idle_add(do_jump, self.num)
+
+ def get():
+ dat_path = misc.get_thread_dat_path(
+ self.bbs, self.board, self.thread)
+ dat_file = FileWrap(dat_path)
+
+ def save_line_and_append_to_buffer(line):
+ dat_file.seek(self.size)
+ dat_file.write(line)
+ self.append_rawres_to_buffer(line)
+
+ self.http_get_dat(save_line_and_append_to_buffer)
+ dat_file.close()
+
+ def do_jump():
+ if self.jump_request_num:
+ num = self.jump_request_num
+ self.jump_request_num = 0
+ mark = self.textbuffer.get_mark(str(num))
+ if mark:
+ self.textview.scroll_to_mark(mark, 0, True, 0, 0)
+
+ gobject.idle_add(do_jump)
+
+ if self.lock():
+
+ def on_end():
+ self.un_lock()
+ self.progress = False
+
+ self.progress = True
+ t = ThreadInvoker(lambda : gobject.idle_add(on_end), load, get)
+ t.start()
def load_dat(self):
+
self.size = 0
self.num = 0
+ self.jump_request_num = 0
+
+ def load():
- self.title = datfile.get_title_from_dat(
- self.bbs, self.board, self.thread)
- if self.title:
- self.window.set_title(self.title)
+ def create_mark():
+ self.textbuffer.create_mark("1", self.enditer, True)
+ gobject.idle_add(create_mark)
- datfile.load_dat(self.bbs, self.board, self.thread,
- self.append_rawres_to_buffer)
- self.textview.scroll_to_mark(self.textbuffer.get_insert(), 0)
+ datfile.load_dat(self.bbs, self.board, self.thread,
+ self.append_rawres_to_buffer)
+ def jump():
+ def do_jump(num):
+ if self.jump_request_num:
+ num = self.jump_request_num
+ self.jump_request_num = 0
+ mark = self.textbuffer.get_mark(str(num))
+ if mark:
+ self.textview.scroll_to_mark(mark, 0, True, 0, 0)
+ else:
+ self.jump_to_the_end(num)
+
+ gobject.idle_add(do_jump, self.num)
+
+ if self.lock():
+
+ def on_end():
+ self.un_lock()
+ self.progress = False
+
+ self.progress = True
+ t = ThreadInvoker(lambda : gobject.idle_add(on_end), load, jump)
+ t.start()
def append_rawres_to_buffer(self, line):
self.size += len(line)
self.num += 1
+ if not self.title and self.num == 1:
+ title = datfile.do_get_title_from_dat(line)
+ if title:
+ self.title = title
+ gobject.idle_add(self.window.set_title, title)
+
h = lambda name,mail,date,msg: self.reselems_to_buffer(
self.num, name, mail, date, msg)
+ self.res_queue = []
datfile.split_line_to_elems(line.decode("cp932", "replace"), h)
-
+
+ def process_res_queue(res_queue, num):
+ self.process_queue(res_queue)
+ # for next res
+ self.textbuffer.create_mark(str(num+1), self.enditer, True)
+
+ gobject.idle_add(
+ process_res_queue, self.res_queue, self.num)
+
def reselems_to_buffer(self, num, name, mail, date, msg):
+ p = barehtmlparser.BareHTMLParser(
+ lambda d,b,h: self.res_queue.append((d,b,h,False)))
# number
- self.textbuffer.insert(self.enditer, str(num) + " ")
+ p.feed(str(num) + " ")
# name
- p = barehtmlparser.BareHTMLParser(self.untiedata_to_buffer)
p.feed("" + name + "")
- p.close()
# mail
- self.textbuffer.insert(self.enditer, "[" + mail + "]")
+ p.feed("[" + mail + "]")
# date
p.feed(date)
- p.close()
- self.textbuffer.insert(self.enditer, "\n")
+ p.feed("
")
# msg
- p.reset_func(self.untiedata_to_buffer_with_leftmargin)
- p.feed(msg)
- p.close()
+ p.reset_func(lambda d,b,h: self.res_queue.append((d,b,h,True)))
+ p.feed(msg.lstrip(" "))
- self.textbuffer.insert(self.enditer, "\n\n")
+ p.feed("
")
+ p.close()
- def untiedata_to_buffer(self, data, bold, href):
- if bold:
- self.textbuffer.insert_with_tags(self.enditer, data, self.boldtag)
+ def href_tag(self, href):
+ tag = self.textbuffer.create_tag(underline=pango.UNDERLINE_SINGLE)
+ tag.set_data("href", href)
+ return tag
+
+ def process_queue(self, queue):
+ for data, bold, href, margin in queue:
+ taglist = []
+ if bold:
+ taglist.append(self.boldtag)
+ if href:
+ taglist.append(self.href_tag(href))
+ if margin:
+ taglist.append(self.leftmargintag)
+
+ if taglist:
+ self.textbuffer.insert_with_tags(self.enditer, data, *taglist)
+ else:
+ self.textbuffer.insert(self.enditer, data)
+
+ def jump_to_the_end(self, num):
+ mark = self.textbuffer.get_mark(str(num+1))
+ if mark:
+ self.textview.scroll_to_mark(mark, 0)
+
+ def lock(self):
+ if self.lock_obj:
+ print "locked, try later."
+ return False
else:
- self.textbuffer.insert(self.enditer, data)
-
- def untiedata_to_buffer_with_leftmargin(self, data, bold, href):
- if bold:
- self.textbuffer.insert_with_tags(self.enditer, data,
- self.boldtag, self.leftmargintag)
+ print "get lock"
+ self.lock_obj = True
+ return True
+
+ def un_lock(self):
+ self.lock_obj = False
+ print "unlock"
+
+ def jump_to_res(self, uri):
+ strict_uri = self.bbs_type.get_thread_uri()
+ if uri != strict_uri and uri.startswith(strict_uri):
+ resnum = uri[len(strict_uri):]
+ match = re.match("\d+", resnum)
+ if match:
+ resnum = match.group()
+ mark = self.textbuffer.get_mark(resnum)
+ if mark:
+ self.textview.scroll_to_mark(mark, 0, True, 0, 0)
+ elif self.progress:
+ # try later.
+ self.jump_request_num = int(resnum)
+
+ def load(self, update=False):
+ dat_path = misc.get_thread_dat_path(
+ self.bbs_type.bbs_type, self.bbs_type.board, self.bbs_type.thread)
+ dat_exists = os.path.exists(dat_path)
+ if update or not dat_exists:
+ self.update()
else:
- self.textbuffer.insert_with_tags(self.enditer, data,
- self.leftmargintag)
+ self.load_dat()