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()