OSDN Git Service

not visible at initializing top level window. set default size before showing.
[fukui-no-namari/fukui-no-namari.git] / src / Hage1 / thread_window.py
index 7d3a786..94969ea 100644 (file)
@@ -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,86 +337,149 @@ 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)
-
-
-        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
-
-        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()
+
+        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
 
-        self.title = datfile.get_title_from_dat(
-            self.bbs, self.board, self.thread)
-        if self.title:
-            self.window.set_title(self.title)
+        def load():
 
-        datfile.load_dat(self.bbs, self.board, self.thread,
-                         self.append_rawres_to_buffer)
-        self.textview.scroll_to_mark(self.textbuffer.get_insert(), 0)
+            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)
+        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(self.untiedata_to_buffer)
+        p = barehtmlparser.BareHTMLParser(
+            lambda d,b,h: self.res_queue.append((d,b,h,False)))
         # number
         p.feed(str(num) + " ")
 
@@ -239,22 +494,69 @@ class WinWrap:
         p.feed("<br>")
 
         # msg
-        p.reset_func(self.untiedata_to_buffer_with_leftmargin)
-        p.feed(msg)
+        p.reset_func(lambda d,b,h: self.res_queue.append((d,b,h,True)))
+        p.feed(msg.lstrip(" "))
 
         p.feed("<br><br>")
         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()