OSDN Git Service

Multithreads are abandoned. Alternatly, The asyncore substitutes.(#16776)
[fukui-no-namari/fukui-no-namari.git] / src / FukuiNoNamari / thread_window.py
index 7688e4e..974a57e 100644 (file)
@@ -33,22 +33,26 @@ import traceback
 import itertools
 import os
 import sys
+from StringIO import StringIO
 
 import misc
-from misc import FileWrap, ThreadInvoker
+from misc import FileWrap
 import datfile
 import barehtmlparser
 import idxfile
 import session
 import board_window
 import uri_opener
-from http_sub import HTTPRedirectHandler302, HTTPDebugHandler
 from BbsType import bbs_type_judge_uri
 from BbsType import bbs_type_exception
 import config
 import winwrapbase
 import bookmark_list
 import bookmark_window
+import thread_view
+import thread_popup
+import submit_window
+import network_manager
 
 GLADE_FILENAME = "thread_window.glade"
 
@@ -73,215 +77,194 @@ def open_thread(uri, update=False):
         winwrap.load(update)
 
     # jump to the res if necessary.
-    strict_uri = winwrap.bbs_type.get_thread_uri()
-    if (winwrap.bbs_type.uri != strict_uri and
-        winwrap.bbs_type.uri.startswith(strict_uri)):
-        resnum = winwrap.bbs_type.uri[len(strict_uri):]
+    strict_uri = bbs_type.get_thread_uri()
+    if (bbs_type.uri != strict_uri and
+        bbs_type.uri.startswith(strict_uri)):
+        resnum = bbs_type.uri[len(strict_uri):]
         match = re.match("\d+", resnum)
         if match:
             resnum = int(match.group())
             winwrap.jump_to_res(resnum)
 
-class WinWrap(winwrapbase.WinWrapBase):
-    hovering_over_link = False
-    hand_cursor = gtk.gdk.Cursor(gtk.gdk.HAND2)
-    regular_cursor = gtk.gdk.Cursor(gtk.gdk.XTERM)
-
-
-    def relayout(self):
-        width = self.drawingarea.allocation.width
-        sum_height = 0
-        for layout in self.pangolayout:
-            layout.set_width(width * pango.SCALE)
-            layout.posY = sum_height
-            x, y = layout.get_pixel_size()
-            sum_height += y
-        self.adjustment.upper = sum_height
-
-    def draw_viewport(self):
-        view_y = self.adjustment.get_value()
-        self.drawingarea.window.draw_rectangle(
-            self.drawingarea.style.base_gc[0],
-            True, 0, 0,
-            self.drawingarea.allocation.width,
-            self.drawingarea.allocation.height)
-
-        gc = self.drawingarea.window.new_gc()
-        for layout in self.pangolayout:
-            w, h = layout.get_pixel_size()
-            layout_top = layout.posY
-            layout_bottom = layout.posY + h
-            area_top = view_y
-            area_bottom = view_y + self.drawingarea.allocation.height
-            if layout_top <= area_bottom and layout_bottom >= area_top:
-                self.drawingarea.window.draw_layout(
-                    gc, 0, layout.posY - int(view_y), layout)
-
-    def on_drawingarea_expose_event(self, widget, event, data=None):
-        self.draw_viewport()
-
-    def on_drawingarea_size_allocate(self, widget, allocation, data=None):
-        if allocation.width != self.drawingarea.prev_width:
-            self.relayout()
-            self.drawingarea.prev_width = allocation.width
-        self.adjustment.page_size = self.drawingarea.allocation.height
-        self.adjustment.page_increment = self.drawingarea.allocation.height
-
-    def on_drawingarea_button_press_event(self, widget, event, data=None):
-        self.drawingarea.queue_draw()
-
-    def on_vscrollbar_value_changed(self, widget, data=None):
-        self.drawingarea.queue_draw()
-
-    def on_drawingarea_scroll_event(self, widget, event, data=None):
-        if event.direction == gtk.gdk.SCROLL_UP:
-            self.adjustment.value -= 66.476200804
-            if self.adjustment.value < self.adjustment.lower:
-                self.adjustment.value = self.adjustment.lower
-        if event.direction == gtk.gdk.SCROLL_DOWN:
-            self.adjustment.value += 66.476200804
-            max_value = self.adjustment.upper - self.adjustment.page_size
-            if self.adjustment.value > max_value:
-                self.adjustment.value = max_value
 
-    def __init__(self, uri):
-        self.pangolayout = []
+class HTMLParserToThreadView:
+    def __init__(self, threadview, resnum, left_margin):
+        self.threadview = threadview
+        self.resnum = resnum
+        self.left_margin = left_margin
+        self.initialize()
 
-        from BbsType import bbs_type_judge_uri
-        from BbsType import bbs_type_exception
-        self.bbs_type = bbs_type_judge_uri.get_type(uri)
-        if not self.bbs_type.is_thread():
-            raise bbs_type_exception.BbsTypeError, \
-                  "the uri does not represent thread: " + uri
-        self.size = 0
-        self.num = 0
-        self.title = ""
-        self.lock_obj = False
-        self.jump_request_num = 0
-        self.progress = False
+    def set_left_margin(self, left_margin):
+        self.left_margin = left_margin
 
-        glade_path = os.path.join(config.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.toolbar.unset_style()
-        self.statusbar = self.widget_tree.get_widget("statusbar")
-        self.drawingarea = self.widget_tree.get_widget("drawingarea")
-        self.vscrollbar = self.widget_tree.get_widget("vscrollbar")
-        self.adjustment = self.vscrollbar.get_adjustment()
-        self.adjustment.step_increment = 20
+    def initialize(self):
+        self.layout = None
 
-        self.drawingarea.prev_width = 0
+    def on_new_line(self):
+        self.to_thread_view()
+        self.layout = self.threadview.create_res_layout(
+            self.left_margin, self.resnum)
 
-        self.initialize_buffer()
+    def from_html_parser(self, data, bold, href):
+        if self.layout == None:
+            self.layout = self.threadview.create_res_layout(
+                self.left_margin, self.resnum)
 
-        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_delete_activate": self.on_delete_activate,
-                  "on_drawingarea_expose_event": self.on_drawingarea_expose_event,
-                  "on_drawingarea_size_allocate":
-                  self.on_drawingarea_size_allocate,
-                  "on_thread_window_delete_event":
-                  self.on_thread_window_delete_event,
-                  "on_drawingarea_button_press_event":
-                  self.on_drawingarea_button_press_event,
-                  "on_drawingarea_scroll_event":
-                  self.on_drawingarea_scroll_event,
-                  "on_vscrollbar_value_changed":
-                  self.on_vscrollbar_value_changed,
-                  "on_add_bookmark_activate": self.on_add_bookmark_activate,
-                  "on_manage_bookmarks_activate": \
-                  self.on_manage_bookmarks_activate,
-                  "on_thread_window_destroy": self.on_thread_window_destroy}
-        self.widget_tree.signal_autoconnect(sigdic)
+        self.layout.add_text(data, bold, href)
 
-        self.restore()
-        self.window.show_all()
+    def to_thread_view(self):
+        if self.layout is not None:
+            self.threadview.add_layout(self.layout)
+            self.initialize()
 
-        self.created()
 
-    def initialize_buffer(self):
-        self.textbuffer = gtk.TextBuffer()
+class LoadDat:
 
-        self.enditer = self.textbuffer.get_end_iter()
-        self.boldtag = self.textbuffer.create_tag(weight=pango.WEIGHT_BOLD)
-        self.leftmargintag = self.textbuffer.create_tag()
-        self.leftmargintag.set_property("left-margin", 20)
+    def __init__(self, bbs_type, threadwindow, threadview, statusbar):
+        self.bbs_type = bbs_type
+        self.threadwindow = threadwindow
+        self.threadview = threadview
+        self.statusbar = statusbar
+        self.lock_obj = False
+        self.jump_request_num = 0
+        self.size = 0
+        self.num = 0
+        self.request_headers = None
+        self.response_headers = None
 
-        self.pangolayout = []
+        self.statusbar_context_id = self.statusbar.get_context_id(
+            "Thread Window Status")
+        self.statusbar.push(self.statusbar_context_id, "OK.")
 
-    def destroy(self):
-        self.save()
-        self.window.destroy()
+    def initialize(self):
+        self.num = 0
+        self.size = 0
+        self.threadview.initialize_buffer()
 
-    def get_uri(self):
-        return self.bbs_type.get_thread_uri()
+    def jump_request(self, res_num):
+        if not self.threadview.jump_to_res(res_num):
+            self.jump_request_num = res_num
 
-    def on_compose_clicked(self, widget):
-        import submit_window
-        submit_window.open(self.bbs_type.get_thread_uri())
+    def _do_jump_if_need(self):
+        if self.jump_request_num:
+            num = self.jump_request_num
+            self.jump_request_num = 0
+            return self.threadview.jump_to_res(num)
 
-    def on_toolbar_activate(self, widget):
-        if self.toolbar.get_property("visible"):
-            self.toolbar.hide()
-        else:
-            self.toolbar.show()
+    def _do_jump(self):
+        if not self._do_jump_if_need():
+            return self.threadview.jump_to_the_end()
 
-    def on_statusbar_activate(self, widget):
-        if self.statusbar.get_property("visible"):
-            self.statusbar.hide()
+    def _load(self):
+        dat_path = misc.get_thread_dat_path(self.bbs_type)
+        try:
+            fd = file(dat_path)
+        except IOError:
+            raise misc.StopChainException()
         else:
-            self.statusbar.show()
+            i = 0
+            for line in fd:
+                self.append_rawres_to_buffer(line)
+                yield
+            fd.close()
+            self._do_jump()
 
-    def on_close_activate(self, widget):
-        self.destroy()
+    def _on_end(self):
+        self.request_headers = None
+        self.response_headers = None
+        self._un_lock()
 
-    def on_thread_window_delete_event(self, widget, event):
-        self.save()
-        return False
-        
-    def on_thread_window_destroy(self, widget):
-        self.destroyed()
+    def _lock(self):
+        if self.lock_obj:
+            print "Busy."
+            return False
+        self.lock_obj = True
+        return True
 
-    def on_quit_activate(self, widget):
-        session.main_quit()
+    def _un_lock(self):
+        self.lock_obj = False
 
-    def on_add_bookmark_activate(self, widget):
-        bookmark_list.bookmark_list.add_bookmark_with_edit(
-            name=self.title, uri=self.bbs_type.uri)
+    def load(self):
+        self.jump_request_num = 0
+        if self._lock():
+            misc.chain(lambda *args: None, self._on_end, self._load())
 
-    def on_manage_bookmarks_activate(self, widget):
-        bookmark_window.open()
+    def _load_and_save(self, fd):
+        dat_path = misc.get_thread_dat_path(self.bbs_type)
+        try:
+            save_fd = FileWrap(dat_path)
+            save_fd.seek(self.size)
+        except IOError:
+            raise misc.StopChainException()
+
+        for line in fd:
+            if not line.endswith("\n"):
+                # the last line is not terminated with '\n'
+                print "does not end with \\n. maybe incomplete"
+                self.response_headers["lastmodified"] = None
+                self.response_headers["etag"] = None
+                raise misc.StopChainException()
+            save_fd.write(line)
+            self.append_rawres_to_buffer(line)
+            yield
+        self.threadview.queue_draw()
+        self._do_jump_if_need()
+
+    def on_received(self, res):
+        headers = res.headers
+        status = res.status
+
+        if "Last-modified".capitalize() in headers:
+            self.statusbar.pop(self.statusbar_context_id)
+            self.statusbar.push(self.statusbar_context_id,
+                "%s [%s]" % (status, headers["last-modified".capitalize()]))
+        else:
+            self.statusbar.pop(self.statusbar_context_id)
+            self.statusbar.push(self.statusbar_context_id, "%s" % status)
 
-    def on_show_board_activate(self, widget):
-        board_window.open_board(self.bbs_type.get_uri_base())
+        version, code, msg = status.split(None, 2)
+        code = int(code)
+        if code != 200 and code != 206:
+            self._on_end()
+            return
+        res.code = code
 
-    def on_delete_activate(self, widget):
-        try:
-            dat_path = misc.get_thread_dat_path(self.bbs_type)
-            os.remove(dat_path)
-        except OSError:
-            traceback.print_exc()
-        try:
-            idx_path = misc.get_thread_idx_path(self.bbs_type)
-            os.remove(idx_path)
-        except OSError:
-            traceback.print_exc()
-        try:
-            states_path = misc.get_thread_states_path(self.bbs_type)
-            os.remove(states_path)
-        except OSError:
-            traceback.print_exc()
+        if "Range".capitalize() in self.request_headers and code == 200:
+            self.initialize()
+
+        self.response_headers = headers
+        fd = StringIO(res.message)
+
+        def save_and_end():
+            try:
+                self._saveidx()
+            finally:
+                self._on_end()
+
+        misc.chain(lambda *args: None, save_and_end, self._load_and_save(fd))
+
+    def _saveidx(self):
+        lastmod = ""
+        etag = ""
+        if "last-modified".capitalize() in self.response_headers:
+            lastmod = self.response_headers["last-modified".capitalize()]
+        if "etag".capitalize() in self.response_headers:
+            etag = self.response_headers["etag".capitalize()]
+
+        if self.num > 0:
+            # save idx
+            idx_dic = {"title": self.title, "lineCount": self.num,
+                       "lastModified": lastmod, "etag": etag}
+            idxfile.save_idx(self.bbs_type, idx_dic)
+
+            session.thread_idx_updated(self.bbs_type.get_thread_uri(), idx_dic)
+
+    def _http_get_dat(self):
 
-    def http_get_dat(self, on_get_res):
         datfile_url = self.bbs_type.get_dat_uri()
 
+        headers = {}
+
         idx_dic = idxfile.load_idx(self.bbs_type)
         lastmod = idx_dic["lastModified"]
         etag = idx_dic["etag"]
@@ -296,151 +279,53 @@ class WinWrap(winwrapbase.WinWrapBase):
             req.add_header("If-None-Match", etag)
 
         req = self.bbs_type.set_extra_dat_request(req, self)
-
-        opener = urllib2.build_opener(HTTPRedirectHandler302, HTTPDebugHandler)
         try:
-            res = opener.open(req)
-        except urllib2.HTTPError, e:
-            pass
-#             gobject.idle_add(
-#                 lambda x: self.statusbar.push(0, x), "%d %s" % (e.code, e.msg))
+            network_manager.request_get(req.get_full_url(),
+                req.headers, self.on_received)
+        except network_manager.BusyException:
+            self._on_end()
+            self.statusbar.pop(self.statusbar_context_id)
+            self.statusbar.push(self.statusbar_context_id,
+                "The network is busy. Try later.")
+        except:
+            self._on_end()
+            self.statusbar.pop(self.statusbar_context_id)
+            self.statusbar.push(self.statusbar_context_id,
+                str(sys.exc_info()))
         else:
-            headers = res.info()
-#             gobject.idle_add(
-#                 lambda x: self.statusbar.push(0, x), "%d %s" % (res.code, res.msg))
-
-            maybe_incomplete = False
-            for line in res:
-                if not line.endswith("\n"):
-                    maybe_incomplete = True
-                    print "does not end with \\n. maybe incomplete"
-                    break
-                on_get_res(line)
-
-            res.close()
-
-            if maybe_incomplete:
-                lastmod = None
-                etag = None
-            else:
-                if "Last-Modified" in headers:
-                    lastmod = headers["Last-Modified"]
-                if "ETag" in headers:
-                    etag = headers["Etag"]
-
-            if self.num > 0:
-                # save idx
-                idx_dic = {"title": self.title, "lineCount": self.num,
-                       "lastModified": lastmod, "etag": etag}
-                idxfile.save_idx(self.bbs_type, idx_dic)
-
-                gobject.idle_add(session.thread_idx_updated,
-                                 self.bbs_type.get_thread_uri(), idx_dic)
-
-    def update(self, widget=None):
-
-        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_type)
-            if line_count < self.num:
-                self.num = 0
-                self.size = 0
+            self.statusbar.pop(self.statusbar_context_id)
+            self.statusbar.push(self.statusbar_context_id, "GET...")
+            self.request_headers = req.headers
 
-                gobject.idle_add(self.initialize_buffer)
+    def update(self):
 
-            if line_count > self.num:
-                datfile.load_dat_partly(
-                    self.bbs_type, self.append_rawres_to_buffer, self.num+1)
-
-                def do_jump():
-                    if self.jump_request_num:
-                        if self.jump_request_num <= num:
-                            num = self.jump_request_num
-                            self.jump_request_num = 0
-                            self.jump_to_res(num)
-                    else:
-                        self.jump_to_the_end()
-
-                gobject.idle_add(do_jump)
-
-        def get():
-            dat_path = misc.get_thread_dat_path(self.bbs_type)
-            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
-                    self.jump_to_res(num)
-
-            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():
-            datfile.load_dat(self.bbs_type, self.append_rawres_to_buffer)
-
-        def jump():
-
-            def do_jump():
-                if self.jump_request_num:
-                    num = self.jump_request_num
-                    self.jump_request_num = 0
-                    self.jump_to_res(num)
-                else:
-                    self.jump_to_the_end()
-
-            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, jump)
-            t.start()
+        if not self._lock():
+            return
+
+        line_count = datfile.get_dat_line_count(self.bbs_type)
+        if line_count != self.num:
+            # load dat file once more
+            self.num = 0
+            self.size = 0
+            self.threadview.initialize_buffer()
+            def end_and_get():
+                self._on_end()
+                self._http_get_dat()
+            misc.chain(lambda *args: None, end_and_get, self._load())
+        else:
+            self._http_get_dat()
 
     def append_rawres_to_buffer(self, line):
         self.size += len(line)
         self.num += 1
 
-        if not self.title and self.num == 1:
+        if self.num == 1:
             title = self.bbs_type.get_title_from_dat(line)
             if title:
                 self.title = title
-                gobject.idle_add(self.window.set_title, title)
-
-        self.res_queue = []
+                self.threadwindow.set_title(title)
 
         line = line.decode(self.bbs_type.encoding, "replace")
         m = self.bbs_type.dat_reg.match(line)
@@ -466,21 +351,18 @@ class WinWrap(winwrapbase.WinWrapBase):
                     date += " ID:" + id
             self.reselems_to_buffer(num, name, mail, date, msg)
         else:
-            self.res_queue.append((str(self.num)+"\n", False, None, False))
-            self.res_queue.append((line, False, None, True))
+            self.reselems_to_buffer(
+                str(self.num), "Invalid Name", "Invalid Mail",
+                "Invalid Date", line)
             print "maybe syntax error.", self.num, line
 
-        def process_res_queue(res_queue, num):
-            self.process_queue(res_queue, num)
-            # 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):
+        pipe = HTMLParserToThreadView(self.threadview, num, 0)
         p = barehtmlparser.BareHTMLParser(
-            lambda d,b,h: self.res_queue.append((d,b,h,False)))
+            pipe.from_html_parser, pipe.on_new_line)
+
+        # First, create a pango layout for num,name,mail,date
+        # 'margin left' is 0
         # number
         p.feed(str(num) + " ")
 
@@ -492,83 +374,175 @@ class WinWrap(winwrapbase.WinWrapBase):
 
         # date
         p.feed(date)
-        p.feed("<br>")
+        p.flush()
+
+        pipe.to_thread_view()
+
 
+        # Second, create a pango layout for message
+        # 'margin left' is 20
         # msg
-        p.reset_func(lambda d,b,h: self.res_queue.append((d,b,h,True)))
+        pipe.set_left_margin(20)
         p.feed(msg.lstrip(" "))
 
-        p.feed("<br><br>")
+        p.feed("<br>")
         p.close()
 
-    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, num):
-        text = ""
-        for data, bold, href, margin in queue:
-            text += data
-        layout = self.drawingarea.create_pango_layout(text)
-        layout.set_wrap(pango.WRAP_CHAR)
-        layout.posY = 0
-        layout.resnum = num
-        self.pangolayout.append(layout)
-        self.relayout()
-        self.drawingarea.queue_draw()
-#             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)
+        pipe.to_thread_view()
+
+
+class WinWrap(winwrapbase.WinWrapBase):
+
+    def __init__(self, uri):
+        self.bbs_type = bbs_type_judge_uri.get_type(uri)
+        if not self.bbs_type.is_thread():
+            raise bbs_type_exception.BbsTypeError, \
+                  "the uri does not represent thread: " + uri
+
+        glade_path = os.path.join(config.glade_dir, GLADE_FILENAME)
+        self.widget_tree = gtk.glade.XML(glade_path)
+        self._get_widgets()
+        self.widget_tree.signal_autoconnect(self)
+
+        self.toolbar.unset_style()
+
+        self.threadview = thread_view.ThreadView()
+        container = thread_view.ThreadViewContainer(self.threadview)
+        self.threadpopup = thread_popup.ThreadPopup(self.bbs_type)
+        self.threadpopup.push_thread_view(self.threadview)
+        self.vbox.pack_start(container)
+        self.vbox.reorder_child(container, 2)
+        self.window.set_focus(self.threadview)
+
+        self.threadview.connect(
+            "populate-popup", self.on_thread_view_populate_popup)
+        self.threadview.connect(
+            "uri-clicked-event", self.on_thread_view_uri_clicked)
+        self.threadpopup.connect(
+            "uri-clicked-event", self.on_thread_popup_uri_clicked)
+
+        self.initialize_buffer()
+
+        self.dat_load = LoadDat(self.bbs_type, self.window,
+            self.threadview, self.statusbar)
+
+        self.restore()
+        self.window.show_all()
+
+        self.created()
+
+    def _get_widgets(self):
+        self.window = self.widget_tree.get_widget("thread_window")
+        self.toolbar = self.widget_tree.get_widget("toolbar")
+        self.statusbar = self.widget_tree.get_widget("statusbar")
+        self.vbox = self.widget_tree.get_widget("vbox")
+
+    def initialize_buffer(self):
+        self.threadview.initialize_buffer()
+
+    def destroy(self):
+        self.save()
+        self.window.destroy()
+
+    def get_uri(self):
+        return self.bbs_type.get_thread_uri()
+
+    def show(self):
+        self.window.deiconify()
+
+    def hide(self):
+        self.window.iconify()
+
+    def _show_submit_window(self):
+        submit_window.open(self.bbs_type.get_thread_uri())
+
+    def _toggle_toolbar(self):
+        if self.toolbar.get_property("visible"):
+            self.toolbar.hide()
+        else:
+            self.toolbar.show()
+
+    def _toggle_statusbar(self):
+        if self.statusbar.get_property("visible"):
+            self.statusbar.hide()
+        else:
+            self.statusbar.show()
+
+    def _close_window(self):
+        self.destroy()
+
+    def _quit_session(self):
+        session.main_quit()
+
+    def _regist_as_bookmark(self):
+        title = self.window.get_title()
+        bookmark_list.bookmark_list.add_bookmark_with_edit(
+            name=title, uri=self.bbs_type.uri)
+
+    def _manage_bookmarks(self):
+        bookmark_window.open()
+
+    def _show_board(self):
+        board_window.open_board(self.bbs_type.get_uri_base())
+
+    def _delete_log(self):
+        try:
+            dat_path = misc.get_thread_dat_path(self.bbs_type)
+            os.remove(dat_path)
+        except OSError:
+            traceback.print_exc()
+        try:
+            idx_path = misc.get_thread_idx_path(self.bbs_type)
+            os.remove(idx_path)
+        except OSError:
+            traceback.print_exc()
+        try:
+            states_path = misc.get_thread_states_path(self.bbs_type)
+            os.remove(states_path)
+        except OSError:
+            traceback.print_exc()
+
+    def _open_uri(self, uri):
+        if not uri.startswith("http://"):
+            # maybe a relative uri.
+            uri = urlparse.urljoin(self.bbs_type.get_uri_base(), uri)
+
+        try:
+            uri_opener.open_uri(uri)
+        except bbs_type_exception.BbsTypeError:
+            # not supported, show with the web browser.
+            gnome.url_show(uri)
+
+    def _copy_text_to_clipboard(self, text):
+        if text and len(text) > 0:
+            clip = gtk.Clipboard()
+            text = text.encode("utf8")
+            clip.set_text(text, len(text))
+
+    def _modify_uri(self, uri):
+        if not uri.startswith("http://"):
+            uri = "http://" + uri
+        return uri
+
+    def update(self):
+        self.dat_load.update()
+
+    def load_dat(self):
+        self.dat_load.load()
 
     def jump(self, value):
-        def j():
-            if value > self.adjustment.upper - self.adjustment.page_size:
-                self.jump_to_the_end()
-            else:
-                self.adjustment.set_value(value)
-        gobject.idle_add(j)
+        gobject.idle_add(self.threadview.jump, value)
 
     def jump_to_layout(self, layout):
-        gobject.idle_add(lambda : self.jump(layout.posY))
+        gobject.idle_add(self.threadview.jump_to_layout, layout)
         
     def jump_to_the_end(self):
-        def j():
-            self.adjustment.set_value(
-                self.adjustment.upper - self.adjustment.page_size)
-        gobject.idle_add(j)
-#        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:
-            print "get lock"
-            self.lock_obj = True
-            return True
-
-    def un_lock(self):
-        self.lock_obj = False
-        print "unlock"
+        gobject.idle_add(self.threadview.jump_to_the_end)
 
     def jump_to_res(self, resnum):
-        for layout in self.pangolayout:
-            if layout.resnum == resnum:
-                self.jump_to_layout(layout)
-                return
-        self.jump_request_num = resnum
+        if self.threadview.jump_to_res(resnum):
+            return
+        self.dat_load.jump_request(resnum)
 
     def load(self, update=False):
         dat_path = misc.get_thread_dat_path(self.bbs_type)
@@ -665,3 +639,85 @@ class WinWrap(winwrapbase.WinWrapBase):
                                  priority=gobject.PRIORITY_HIGH)
         except:
             traceback.print_exc()
+
+
+    # signal handlers
+    
+    def on_thread_view_populate_popup(self, widget, menu):
+        menuitem = gtk.ImageMenuItem(gtk.STOCK_REFRESH)
+        menuitem.connect("activate",
+                         self.on_popup_threadview_menu_refresh_activate)
+        menu.append(menuitem)
+
+    def on_thread_view_uri_clicked(self, widget, uri):
+        self._open_uri(uri)
+
+    def on_thread_popup_uri_clicked(self, widget, threadview, uri):
+        self._open_uri(uri)
+
+    def on_thread_window_delete_event(self, widget, event):
+        self.save()
+        return False
+
+    def on_thread_window_destroy(self, widget):
+        self.destroyed()
+
+
+
+
+    # menu commands
+
+    # menu file
+
+    def on_menu_file_show_board_activate(self, widget):
+        self._show_board()
+
+    def on_menu_file_compose_activate(self, widget):
+        self._show_submit_window()
+
+    def on_menu_file_delete_activate(self, widget):
+        self._delete_log()
+
+    def on_menu_file_close_activate(self, widget):
+        self._close_window()
+
+    def on_menu_file_quit_activate(self, widget):
+        self._quit_session()
+
+    # menu view
+    
+    def on_menu_view_refresh_activate(self, widget):
+        self.update()
+
+    def on_menu_view_toolbar_activate(self, widget):
+        self._toggle_toolbar()
+
+    def on_menu_view_statusbar_activate(self, widget):
+        self._toggle_statusbar()
+
+    # menu bookmarks
+
+    def on_menu_bookmarks_bookmarkthispage_activate(self, widget):
+        self._regist_as_bookmark()
+
+    def on_menu_bookmarks_showbookmarks_activate(self, widget):
+        self._manage_bookmarks()
+
+    # toolbuttons
+    
+    def on_toolbutton_refresh_activate(self, widget):
+        self.update()
+
+    def on_toolbutton_showboard_activate(self, widget):
+        self._show_board()
+
+    def on_toolbutton_compose_activate(self, widget):
+        self._show_submit_window()
+
+    def on_toolbutton_delete_activate(self, widget):
+        self._delete_log()
+
+    # popup menus
+    
+    def on_popup_threadview_menu_refresh_activate(self, widget):
+        self.update()