OSDN Git Service

replace scrolled window and viewport with hbox and scrollbar
[fukui-no-namari/fukui-no-namari.git] / src / FukuiNoNamari / thread_window.py
index 42ba4b7..7688e4e 100644 (file)
@@ -29,21 +29,27 @@ import gnome
 import gobject
 import threading
 import gconf
+import traceback
+import itertools
+import os
+import sys
 
 import misc
+from misc import FileWrap, ThreadInvoker
 import datfile
 import barehtmlparser
 import idxfile
 import session
 import board_window
 import uri_opener
-from http_sub import HTTPRedirectHandler302
+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
 
-GLADE_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)),
-                         "..", "data")
 GLADE_FILENAME = "thread_window.glade"
 
 def open_thread(uri, update=False):
@@ -64,67 +70,89 @@ def open_thread(uri, update=False):
             winwrap.load(update)
     else:
         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:
+    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):]
+        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 = []
+
         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.bbs = self.bbs_type.bbs_type
-        self.board = self.bbs_type.board
-        self.thread = self.bbs_type.thread
-        self.host = self.bbs_type.host
-        self.uri = self.bbs_type.uri
         self.size = 0
         self.num = 0
         self.title = ""
@@ -132,18 +160,20 @@ class WinWrap:
         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.toolbar.unset_style()
-        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()
-        self.boldtag = self.textbuffer.create_tag(weight=pango.WEIGHT_BOLD)
-        self.leftmargintag = self.textbuffer.create_tag()
-        self.leftmargintag.set_property("left-margin", 20)
+        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
+
+        self.drawingarea.prev_width = 0
+
+        self.initialize_buffer()
 
         sigdic = {"on_refresh_activate": self.update,
                   "on_compose_activate": self.on_compose_clicked,
@@ -153,203 +183,159 @@ class WinWrap:
                   "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.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_all()
 
-        self.gconf_client = gconf.client_get_default()
-        self.gconf_key_base = "/apps/" + config.APPNAME.lower() + \
-                              "/thread_states/"
+        self.created()
 
-        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)
+    def initialize_buffer(self):
+        self.textbuffer = gtk.TextBuffer()
 
-        self.window.show()
+        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)
 
-        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.pangolayout = []
+
+    def destroy(self):
+        self.save()
+        self.window.destroy()
+
+    def get_uri(self):
+        return self.bbs_type.get_thread_uri()
 
     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)
+        if self.toolbar.get_property("visible"):
+            self.toolbar.hide()
         else:
-            self.toolbar.parent.show()
-            self.gconf_client.set_bool(self.gconf_key_base + "toolbar", True)
+            self.toolbar.show()
 
     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()
+        self.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)
-
+        self.save()
         return False
         
     def on_thread_window_destroy(self, widget):
-        -1
+        self.destroyed()
 
     def on_quit_activate(self, widget):
         session.main_quit()
 
+    def on_add_bookmark_activate(self, widget):
+        bookmark_list.bookmark_list.add_bookmark_with_edit(
+            name=self.title, uri=self.bbs_type.uri)
+
+    def on_manage_bookmarks_activate(self, widget):
+        bookmark_window.open()
+
     def on_show_board_activate(self, widget):
         board_window.open_board(self.bbs_type.get_uri_base())
 
+    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()
+
     def http_get_dat(self, on_get_res):
         datfile_url = self.bbs_type.get_dat_uri()
 
-        idx_dic = idxfile.load_idx(self.bbs, self.board, self.thread)
+        idx_dic = idxfile.load_idx(self.bbs_type)
         lastmod = idx_dic["lastModified"]
         etag = idx_dic["etag"]
 
         req = urllib2.Request(datfile_url)
+        req.add_header("User-agent", config.User_Agent)
         if self.size > 0:
             req.add_header("Range", "bytes=" + str(self.size) + "-")
         if lastmod:
             req.add_header("If-Modified-Since", lastmod)
         if etag:
             req.add_header("If-None-Match", etag)
-        print req.headers
-
-        opener = urllib2.build_opener(HTTPRedirectHandler302)
-        res = opener.open(req)
-        headers = res.info()
-        print headers
-
-        line = res.readline()
-        maybe_incomplete = False
-        while line:
-            if not line.endswith("\n"):
-                maybe_incomplete = True
-                print "does not end with \\n. maybe incomplete"
-                break
-            on_get_res(line)
-            line = res.readline()
-
-        res.close()
-
-        if maybe_incomplete:
-            lastmod = None
-            etag = None
+
+        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))
         else:
-            if "Last-Modified" in headers:
-                lastmod = headers["Last-Modified"]
-            if "ETag" in headers:
-                etag = headers["Etag"]
-
-        if self.num > 0:
-            if not self.title:
-                title = datfile.get_title_from_dat(
-                    self.bbs, self.board, self.thread)
-                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)
-
-            gobject.idle_add(session.thread_idx_updated,
-                             self.bbs_type.get_thread_uri(), idx_dic)
+            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):
 
@@ -361,31 +347,30 @@ class WinWrap:
                     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)
+            line_count = datfile.get_dat_line_count(self.bbs_type)
+            if line_count < self.num:
+                self.num = 0
+                self.size = 0
+
+                gobject.idle_add(self.initialize_buffer)
+
             if line_count > self.num:
                 datfile.load_dat_partly(
-                    self.bbs, self.board, self.thread,
-                    self.append_rawres_to_buffer, self.num+1)
+                    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, self.board, self.thread)
+            dat_path = misc.get_thread_dat_path(self.bbs_type)
             dat_file = FileWrap(dat_path)
 
             def save_line_and_append_to_buffer(line):
@@ -400,9 +385,7 @@ class WinWrap:
                 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)
 
@@ -423,26 +406,19 @@ class WinWrap:
         self.jump_request_num = 0
 
         def load():
+            datfile.load_dat(self.bbs_type, self.append_rawres_to_buffer)
 
-            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):
+            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():
 
@@ -459,21 +435,45 @@ class WinWrap:
         self.num += 1
 
         if not self.title and self.num == 1:
-            title = datfile.do_get_title_from_dat(line)
+            title = self.bbs_type.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)
+
+        line = line.decode(self.bbs_type.encoding, "replace")
+        m = self.bbs_type.dat_reg.match(line)
+        if m:
+            name = m.group("name")
+            mail = m.group("mail")
+            date = m.group("date")
+            msg = m.group("msg")
+            try:
+                num = int(m.group("num"))
+            except IndexError:
+                # use simple counter num
+                num = self.num
+            else:
+                # use num in dat
+                self.num = num
+            try:
+                id = m.group("id")
+            except IndexError:
+                pass
+            else:
+                if id:
+                    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))
+            print "maybe syntax error.", self.num, line
 
         def process_res_queue(res_queue, num):
-            self.process_queue(res_queue)
+            self.process_queue(res_queue, num)
             # for next res
-            self.textbuffer.create_mark(str(num+1), self.enditer, True)
+            #self.textbuffer.create_mark(str(num+1), self.enditer, True)
 
         gobject.idle_add(
             process_res_queue, self.res_queue, self.num)
@@ -506,25 +506,49 @@ class WinWrap:
         tag.set_data("href", href)
         return tag
 
-    def process_queue(self, queue):
+    def process_queue(self, queue, num):
+        text = ""
         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)
+            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)
+
+    def jump(self, value):
+        def j():
+            if value > self.adjustment.upper - self.adjustment.page_size:
+                self.jump_to_the_end()
             else:
-                self.textbuffer.insert(self.enditer, data)
+                self.adjustment.set_value(value)
+        gobject.idle_add(j)
 
-    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(lambda : self.jump(layout.posY))
+        
+    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:
@@ -539,25 +563,105 @@ class WinWrap:
         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):
+        for layout in self.pangolayout:
+            if layout.resnum == resnum:
+                self.jump_to_layout(layout)
+                return
+        self.jump_request_num = 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_path = misc.get_thread_dat_path(self.bbs_type)
         dat_exists = os.path.exists(dat_path)
         if update or not dat_exists:
             self.update()
         else:
             self.load_dat()
+
+    def save(self):
+        try:
+            states_path = misc.get_thread_states_path(self.bbs_type)
+            dat_path = misc.get_thread_dat_path(self.bbs_type)
+
+            # save only if dat file exists.
+            if os.path.exists(dat_path):
+                window_width, window_height = self.window.get_size()
+                toolbar_visible = self.toolbar.get_property("visible")
+                statusbar_visible = self.statusbar.get_property("visible")
+
+                dirname = os.path.dirname(states_path)
+                if not os.path.isdir(dirname):
+                    os.makedirs(dirname)
+
+                f = file(states_path, "w")
+
+                f.write("window_width=" + str(window_width) + "\n")
+                f.write("window_height=" + str(window_height) + "\n")
+                f.write("toolbar_visible=" + str(toolbar_visible) + "\n")
+                f.write("statusbar_visible=" + str(statusbar_visible) + "\n")
+
+                f.close()
+        except:
+            traceback.print_exc()
+
+    def restore(self):
+        try:
+            window_height = 600
+            window_width = 600
+            toolbar_visible = True
+            statusbar_visible = True
+
+            try:
+                key_base = config.gconf_app_key_base() + "/thread_states"
+                gconf_client = gconf.client_get_default()
+                width = gconf_client.get_int(key_base + "/window_width")
+                height = gconf_client.get_int(key_base + "/window_height")
+                toolbar_visible = gconf_client.get_bool(
+                    key_base + "/toolbar")
+                statusbar_visible = gconf_client.get_bool(
+                    key_base + "/statusbar")
+                if width != 0:
+                    window_width = width
+                if height != 0:
+                    window_height = height
+            except:
+                traceback.print_exc()
+
+            states_path = misc.get_thread_states_path(self.bbs_type)
+            if os.path.exists(states_path):
+                for line in file(states_path):
+                    if line.startswith("window_height="):
+                        height = window_height
+                        try:
+                            height = int(
+                                line[len("window_height="):].rstrip("\n"))
+                        except:
+                            pass
+                        else:
+                            window_height = height
+                    elif line.startswith("window_width="):
+                        width = window_width
+                        try:
+                            width = int(
+                                line[len("window_width="):].rstrip("\n"))
+                        except:
+                            pass
+                        else:
+                            window_width = width
+                    elif line.startswith("toolbar_visible="):
+                        tbar = line[len("toolbar_visible="):].rstrip("\n")
+                        toolbar_visible = tbar == "True"
+                    elif line.startswith("statusbar_visible="):
+                        sbar = line[len("statusbar_visible="):].rstrip("\n")
+                        statusbar_visible = sbar == "True"
+
+            self.window.set_default_size(window_width, window_height)
+
+            if not toolbar_visible:
+                gobject.idle_add(self.toolbar.hide,
+                                 priority=gobject.PRIORITY_HIGH)
+            if not statusbar_visible:
+                gobject.idle_add(self.statusbar.hide,
+                                 priority=gobject.PRIORITY_HIGH)
+        except:
+            traceback.print_exc()