OSDN Git Service

Window managing auxiliary is added. It shows only same board windows. (#16374)
[fukui-no-namari/fukui-no-namari.git] / src / FukuiNoNamari / thread_window.py
index eb4ce60..6ce6394 100644 (file)
@@ -32,6 +32,7 @@ import gconf
 import traceback
 import itertools
 import os
+import sys
 
 import misc
 from misc import FileWrap, ThreadInvoker
@@ -48,9 +49,10 @@ import config
 import winwrapbase
 import bookmark_list
 import bookmark_window
+import thread_view
+import thread_popup
+import submit_window
 
-GLADE_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)),
-                         "..", "data")
 GLADE_FILENAME = "thread_window.glade"
 
 def open_thread(uri, update=False):
@@ -74,7 +76,48 @@ def open_thread(uri, update=False):
         winwrap.load(update)
 
     # jump to the res if necessary.
-    winwrap.jump_to_res(bbs_type.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 HTMLParserToThreadView:
+    def __init__(self, threadview, resnum, left_margin):
+        self.threadview = threadview
+        self.resnum = resnum
+        self.left_margin = left_margin
+        self.initialize()
+
+    def set_left_margin(self, left_margin):
+        self.left_margin = left_margin
+
+    def initialize(self):
+        self.layout = None
+
+    def on_new_line(self):
+        self.to_thread_view()
+        self.layout = self.threadview.create_res_layout(
+            self.left_margin, self.resnum)
+
+    def from_html_parser(self, data, bold, href):
+        if self.layout == None:
+            self.layout = self.threadview.create_res_layout(
+                self.left_margin, self.resnum)
+
+        self.layout.add_text(data, bold, href)
+
+    def to_thread_view(self):
+        if self.layout is not None:
+            # gobject.idle_add(self.threadview.add_layout, self.layout)
+            gtk.gdk.threads_enter()
+            self.threadview.add_layout(self.layout)
+            gtk.gdk.threads_leave()
+            self.initialize()
 
 
 class WinWrap(winwrapbase.WinWrapBase):
@@ -82,7 +125,6 @@ class WinWrap(winwrapbase.WinWrapBase):
     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
         from BbsType import bbs_type_exception
@@ -97,53 +139,62 @@ class WinWrap(winwrapbase.WinWrapBase):
         self.jump_request_num = 0
         self.progress = False
 
-        glade_path = os.path.join(GLADE_DIR, GLADE_FILENAME)
+        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._get_widgets()
+        self.widget_tree.signal_autoconnect(self)
+
         self.toolbar.unset_style()
-        self.statusbar = self.widget_tree.get_widget("appbar")
-        self.textview = self.widget_tree.get_widget("textview")
-        self.textview.drag_dest_unset()
+
+        self.threadview = thread_view.ThreadView()
+        self.threadpopup = thread_popup.ThreadPopup(self.bbs_type)
+        self.threadpopup.push_thread_view(self.threadview)
+        self.vbox.pack_start(self.threadview)
+        self.vbox.reorder_child(self.threadview, 2)
+        self.window.set_focus(self.threadview.drawingarea)
+
+        self._get_popupmenu_widgets()
+
+        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.statusbar_context_id = self.statusbar.get_context_id(
+            "Thread Window Status")
+        self.statusbar.push(self.statusbar_context_id, "OK.")
 
         self.initialize_buffer()
 
-        self.hint = HintWrap()
-
-        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_thread_window_delete_event":
-                  self.on_thread_window_delete_event,
-                  "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.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.restore()
-        self.window.show()
+        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 _get_popupmenu_widgets(self):
+        self.threadview.popupmenu = self.widget_tree.get_widget(
+            "popup_threadview_menu")
+        self.threadview.menu_openuri = self.widget_tree.get_widget(
+            "popup_threadview_menu_openuri")
+        self.threadview.menu_copylinkaddress = self.widget_tree.get_widget(
+            "popup_threadview_menu_copylinkaddress")
+        self.threadview.menu_separator_link = self.widget_tree.get_widget(
+            "popup_threadview_menu_separator_link")
+        self.threadview.menu_copyselection = self.widget_tree.get_widget(
+            "popup_threadview_menu_copyselection")
+        self.threadview.menu_openasuri = self.widget_tree.get_widget(
+            "popup_threadview_menu_openasuri")
+        self.threadview.menu_separator_selection = self.widget_tree.get_widget(
+            "popup_threadview_menu_separator_selection")
+
     def initialize_buffer(self):
-        self.textbuffer = gtk.TextBuffer()
-        self.textview.set_buffer(self.textbuffer)
-        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)
+        self.threadview.initialize_buffer()
 
     def destroy(self):
         self.save()
@@ -152,156 +203,44 @@ class WinWrap(winwrapbase.WinWrapBase):
     def get_uri(self):
         return self.bbs_type.get_thread_uri()
 
-    def on_compose_clicked(self, widget):
-        import submit_window
+    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 on_toolbar_activate(self, widget):
-        if self.toolbar.parent.get_property("visible"):
-            self.toolbar.parent.hide()
+    def _toggle_toolbar(self):
+        if self.toolbar.get_property("visible"):
+            self.toolbar.hide()
         else:
-            self.toolbar.parent.show()
+            self.toolbar.show()
 
-    def on_statusbar_activate(self, widget):
+    def _toggle_statusbar(self):
         if self.statusbar.get_property("visible"):
             self.statusbar.hide()
         else:
             self.statusbar.show()
 
-    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
-        href = ""
-
-        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)
-            if href:
-                if not href.startswith("http://"):
-                    href = urlparse.urljoin(self.bbs_type.get_uri_base(), href)
-
-                strict_uri = self.bbs_type.get_thread_uri()
-                if href != strict_uri and href.startswith(strict_uri):
-                    resnum = href[len(strict_uri):]
-                    match = re.match("\d+", resnum)
-                    if match:
-                        resnum = int(match.group())
-                        if not self.hint.visible(resnum):
-                            mark = self.textbuffer.get_mark(str(resnum))
-                            n_mark = self.textbuffer.get_mark(str(resnum+1))
-                            if mark and n_mark:
-                                iter = self.textbuffer.get_iter_at_mark(mark)
-                                n_iter = self.textbuffer.get_iter_at_mark(
-                                    n_mark)
-                                text = self.textbuffer.get_text(
-                                    iter, n_iter, False)
-                                if text:
-                                    iter = self.textview.get_iter_at_location(
-                                        x, y)
-                                    rect = self.textview.get_iter_location(
-                                        iter)
-                                    x, y = \
-                                       self.textview.buffer_to_window_coords(
-                                        gtk.TEXT_WINDOW_WIDGET, rect.x, rect.y)
-                                    x, y = self.textview.translate_coordinates(
-                                        self.window, x, y)
-                                    wx, wy = self.window.get_position()
-                                    x += wx
-                                    y += wy
-                                    y += rect.height
-                                    self.hint.show(x, y, text, resnum)
-        else:
-            widget.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(
-                self.regular_cursor)
-            self.hint.destroy()
-
-    def on_close_activate(self, widget):
+    def _close_window(self):
         self.destroy()
 
-    def on_thread_window_delete_event(self, widget, event):
-        self.save()
-        return False
-        
-    def on_thread_window_destroy(self, widget):
-        self.destroyed()
-
-    def on_quit_activate(self, widget):
+    def _quit_session(self):
         session.main_quit()
 
-    def on_add_bookmark_activate(self, widget):
+    def _regist_as_bookmark(self):
         bookmark_list.bookmark_list.add_bookmark_with_edit(
             name=self.title, uri=self.bbs_type.uri)
 
-    def on_manage_bookmarks_activate(self, widget):
+    def _manage_bookmarks(self):
         bookmark_window.open()
 
-    def on_show_board_activate(self, widget):
+    def _show_board(self):
         board_window.open_board(self.bbs_type.get_uri_base())
 
-    def on_delete_activate(self, widget):
+    def _delete_log(self):
         try:
             dat_path = misc.get_thread_dat_path(self.bbs_type)
             os.remove(dat_path)
@@ -318,6 +257,28 @@ class WinWrap(winwrapbase.WinWrapBase):
         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 http_get_dat(self, on_get_res):
         datfile_url = self.bbs_type.get_dat_uri()
 
@@ -334,18 +295,38 @@ class WinWrap(winwrapbase.WinWrapBase):
         if etag:
             req.add_header("If-None-Match", etag)
 
+        def push():
+            self.statusbar.pop(self.statusbar_context_id)
+            self.statusbar.push(self.statusbar_context_id, "GET...")
+        gobject.idle_add(push)
+
         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:
-            gobject.idle_add(
-                self.statusbar.set_status, "%d %s" % (e.code, e.msg))
+            def push(code, msg):
+                message = "%d %s" % (code, msg)
+                self.statusbar.pop(self.statusbar_context_id)
+                self.statusbar.push(self.statusbar_context_id, message)
+            gobject.idle_add(push, e.code, e.msg)
         else:
             headers = res.info()
-            gobject.idle_add(
-                self.statusbar.set_status, "%d %s" % (res.code, res.msg))
+
+            if "Last-Modified" in headers:
+                la = headers["Last-Modified"]
+                def push(code, msg, lastm):
+                    message = "%d %s [%s]" % (code, msg, lastm)
+                    self.statusbar.pop(self.statusbar_context_id)
+                    self.statusbar.push(self.statusbar_context_id, message)
+                gobject.idle_add(push, res.code, res.msg, la)
+            else:
+                def push(code, msg):
+                    message = "%d %s" % (code, msg)
+                    self.statusbar.pop(self.statusbar_context_id)
+                    self.statusbar.push(self.statusbar_context_id, message)
+                gobject.idle_add(push, res.code, res.msg)
 
             maybe_incomplete = False
             for line in res:
@@ -375,16 +356,11 @@ class WinWrap(winwrapbase.WinWrapBase):
                 gobject.idle_add(session.thread_idx_updated,
                                  self.bbs_type.get_thread_uri(), idx_dic)
 
-    def update(self, widget=None):
+    def update(self):
 
         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
@@ -396,20 +372,16 @@ class WinWrap(winwrapbase.WinWrapBase):
                 datfile.load_dat_partly(
                     self.bbs_type, self.append_rawres_to_buffer, self.num+1)
 
-                def do_jump(num):
+                def do_jump():
                     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)
+                            self.jump_to_res(num)
                     else:
-                        self.jump_to_the_end(num)
+                        self.jump_to_the_end()
 
-                gobject.idle_add(do_jump, self.num)
+                gobject.idle_add(do_jump)
 
         def get():
             dat_path = misc.get_thread_dat_path(self.bbs_type)
@@ -421,15 +393,16 @@ class WinWrap(winwrapbase.WinWrapBase):
                 self.append_rawres_to_buffer(line)
 
             self.http_get_dat(save_line_and_append_to_buffer)
+            gtk.gdk.threads_enter()
+            self.threadview.redraw()
+            gtk.gdk.threads_leave()
             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)
+                    self.jump_to_res(num)
 
             gobject.idle_add(do_jump)
 
@@ -450,25 +423,19 @@ class WinWrap(winwrapbase.WinWrapBase):
         self.jump_request_num = 0
 
         def load():
-
-            def create_mark():
-                self.textbuffer.create_mark("1", self.enditer, True)
-            gobject.idle_add(create_mark)
-
             datfile.load_dat(self.bbs_type, self.append_rawres_to_buffer)
+
         def jump():
 
-            def do_jump(num):
+            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)
+                    self.jump_to_res(num)
                 else:
-                    self.jump_to_the_end(num)
+                    self.jump_to_the_end()
 
-            gobject.idle_add(do_jump, self.num)
+            gobject.idle_add(do_jump)
 
         if self.lock():
 
@@ -490,8 +457,6 @@ class WinWrap(winwrapbase.WinWrapBase):
                 self.title = title
                 gobject.idle_add(self.window.set_title, title)
 
-        self.res_queue = []
-
         line = line.decode(self.bbs_type.encoding, "replace")
         m = self.bbs_type.dat_reg.match(line)
         if m:
@@ -516,21 +481,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)
-            # 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) + " ")
 
@@ -542,39 +504,30 @@ 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):
-        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)
+        pipe.to_thread_view()
+
+    def jump(self, value):
+        gobject.idle_add(self.threadview.jump, value)
 
-    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 jump_to_layout(self, layout):
+        gobject.idle_add(self.threadview.jump_to_layout, layout)
+        
+    def jump_to_the_end(self):
+        gobject.idle_add(self.threadview.jump_to_the_end)
 
     def lock(self):
         if self.lock_obj:
@@ -589,19 +542,10 @@ class WinWrap(winwrapbase.WinWrapBase):
         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 jump_to_res(self, resnum):
+        if self.threadview.jump_to_res(resnum):
+            return
+        self.jump_request_num = resnum
 
     def load(self, update=False):
         dat_path = misc.get_thread_dat_path(self.bbs_type)
@@ -619,7 +563,7 @@ class WinWrap(winwrapbase.WinWrapBase):
             # save only if dat file exists.
             if os.path.exists(dat_path):
                 window_width, window_height = self.window.get_size()
-                toolbar_visible = self.toolbar.parent.get_property("visible")
+                toolbar_visible = self.toolbar.get_property("visible")
                 statusbar_visible = self.statusbar.get_property("visible")
 
                 dirname = os.path.dirname(states_path)
@@ -691,7 +635,7 @@ class WinWrap(winwrapbase.WinWrapBase):
             self.window.set_default_size(window_width, window_height)
 
             if not toolbar_visible:
-                gobject.idle_add(self.toolbar.parent.hide,
+                gobject.idle_add(self.toolbar.hide,
                                  priority=gobject.PRIORITY_HIGH)
             if not statusbar_visible:
                 gobject.idle_add(self.statusbar.hide,
@@ -700,48 +644,92 @@ class WinWrap(winwrapbase.WinWrapBase):
             traceback.print_exc()
 
 
-class HintWrap:
+    # signal handlers
+    
+    def on_thread_view_uri_clicked(self, widget, uri):
+        self._open_uri(uri)
 
-    def __init__(self):
-        self.window = None
-        self.textview = None
-        self.nums = None
+    def on_thread_popup_uri_clicked(self, widget, threadview, uri):
+        self._open_uri(uri)
 
-    def __del__(self):
-        print "destruct"
-        self.destroy()
+    def on_thread_window_delete_event(self, widget, event):
+        self.save()
+        return False
 
-    def destroy(self):
-        if self.window:
-            self.window.destroy()
-        self.window = None
-        self.textview = None
-        self.nums = None
+    def on_thread_window_destroy(self, widget):
+        self.destroyed()
 
-    def show(self, x, y, text, *nums):
-        self.destroy()
 
-        self.window = gtk.Window(gtk.WINDOW_POPUP)
-        self.window.set_default_size(400, 10)
-        self.window.move(x, y)
 
-        self.textview = gtk.TextView()
-        self.window.add(self.textview)
 
-        self.textview.set_wrap_mode(gtk.WRAP_CHAR)
-        self.textview.set_editable(False)
+    # menu commands
 
-        buffer = self.textview.get_buffer()
-        buffer.set_text(text.rstrip())
-        self.nums = nums
+    # menu file
 
-        self.window.show_all()
+    def on_menu_file_show_board_activate(self, widget):
+        self._show_board()
 
-    def visible(self, *nums):
-        if not self.nums or len(self.nums) != len(nums):
-            return False
+    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_openuri_activate(self, widget):
+        self._open_uri(widget.uri)
+
+    def on_popup_threadview_menu_copylinkaddress_activate(self, widget):
+        self._copy_text_to_clipboard(widget.uri)
+
+    def on_popup_threadview_menu_copyselection_activate(self, widget):
+        text = self.threadview.get_selected_text()
+        self._copy_text_to_clipboard(text)
+
+    def on_popup_threadview_menu_openasuri_activate(self, widget):
+        text = self.threadview.get_selected_text()
+        uri = self._modify_uri(text)
+        self._open_uri(uri)
 
-        for num, mun in itertools.izip(self.nums, nums):
-            if num != mun:
-                return False
-        return True
+    def on_popup_threadview_menu_refresh_activate(self, widget):
+        self.update()