OSDN Git Service

Destroy popupmenu and treeview columns.
[fukui-no-namari/fukui-no-namari.git] / src / FukuiNoNamari / board_window.py
index d60f086..4245dd7 100644 (file)
@@ -23,6 +23,9 @@ import os
 import time
 import gobject
 import gconf
+import traceback
+import sys
+import itertools
 
 import board_data
 import uri_opener
@@ -31,15 +34,22 @@ from threadlistmodel import ThreadListModel
 from BbsType import bbs_type_judge_uri
 import config
 import session
+import winwrapbase
+from misc import ThreadInvoker
+import bookmark_list
+import bookmark_window
+import board_plugins
+import board_column
 
-GLADE_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)),
-                         "..", "data")
 GLADE_FILENAME = "board_window.glade"
 
 def open_board(uri, update=False):
     if not uri:
         raise ValueError, "parameter must not be empty"
 
+    bbs_type = bbs_type_judge_uri.get_type(uri)
+    uri = bbs_type.get_uri_base()  # use strict uri
+
     winwrap = session.get_window(uri)
     if winwrap:
         # already opened
@@ -47,184 +57,68 @@ def open_board(uri, update=False):
         if update:
             winwrap.load(update)
     else:
-        winwrap = WinWrap(uri)
-        session.window_created(uri, winwrap)
+        winwrap = WinWrap(bbs_type.uri)  # pass the original uri
         winwrap.load(update)
 
 
-class WinWrap:
+class WinWrap(winwrapbase.WinWrapBase, board_data.BoardData):
 
     def __init__(self, uri):
 
         self.bbs_type = bbs_type_judge_uri.get_type(uri)
-        self.bbs = self.bbs_type.bbs_type
-        self.host = self.bbs_type.host
-        self.board = self.bbs_type.board
-        self.uri = self.bbs_type.uri
-        
-        glade_path = os.path.join(GLADE_DIR, GLADE_FILENAME)
-        self.widget_tree = gtk.glade.XML(glade_path)
+        board_data.BoardData.__init__(self, self.bbs_type)
 
-        self.window = self.widget_tree.get_widget("board_window")
+        glade_path = os.path.join(config.glade_dir, GLADE_FILENAME)
+        self.widget_tree = gtk.glade.XML(glade_path)
+        self._get_widgets()
 
-        self.window.set_title(self.uri)
+        self.widget_tree.signal_autoconnect(self)
 
-        self.treeview = self.widget_tree.get_widget("treeview")
+        self.window.set_title(self.bbs_type.uri)
         self.treeview.set_model(ThreadListModel())
-
-        self.popupmenu = self.widget_tree.get_widget("popup_menu")
-        self.toolbar = self.widget_tree.get_widget("toolbar")
         self.toolbar.unset_style()
-        self.statusbar = self.widget_tree.get_widget("appbar")
 
         renderer = gtk.CellRendererText()
 
-        self.treeviewcolumn = {}
-        for i in range(1, len(ThreadListModel.column_names)):
-            column_name = ThreadListModel.column_names[i]
-            self.treeviewcolumn[column_name] = gtk.TreeViewColumn(
-                column_name, renderer)
-            self.treeviewcolumn[column_name].set_resizable(True)
-            self.treeviewcolumn[column_name].set_reorderable(True)
-            self.treeviewcolumn[column_name].set_clickable(True)
-            self.treeviewcolumn[column_name].set_cell_data_func(
-                renderer, self.on_cell_data, column_name)
-            self.treeviewcolumn[column_name].connect(
-                "clicked", self.on_column_clicked, column_name)
-            self.treeview.append_column(self.treeviewcolumn[column_name])
-
-        self.treeviewcolumn["lastModified"].set_cell_data_func(
-            renderer, self.on_data_lastmodified)
-
-        sigdic = {"on_board_window_destroy": self.on_board_window_destroy,
-                  "on_quit_activate": self.on_quit_activate,
-                  "on_refresh_activate": self.on_refresh_activate,
-                  "on_treeview_row_activated":
-                  lambda w,p,v: self.on_open_thread(w),
-                  "on_treeview_button_press_event":
-                  self.on_treeview_button_press_event,
-                  "on_close_activate":
-                  self.on_close_activate,
-                  "on_toolbar_activate": self.on_toolbar_activate,
-                  "on_statusbar_activate": self.on_statusbar_activate,
-                  "on_board_window_delete_event":
-                  self.on_board_window_delete_event,
-                  "on_popup_menu_open_activate": self.on_open_thread}
-        self.widget_tree.signal_autoconnect(sigdic)
-
-        self.gconf_client = gconf.client_get_default()
-        self.gconf_key_base = "/apps/" + config.APPNAME.lower() + \
-                              "/board_states/"
-
-        width = self.gconf_client.get_int(
-            self.gconf_key_base + "window_width")
-        height = self.gconf_client.get_int(
-            self.gconf_key_base + "window_height")
-        self.window.set_default_size(width, height)
-
-        if not self.gconf_client.get_bool(self.gconf_key_base + "toolbar"):
-            self.toolbar.parent.hide()
-        if not self.gconf_client.get_bool(self.gconf_key_base + "statusbar"):
-            self.statusbar.hide()
+        for column_class in board_column.tree_view_column_list:
+            column_class(renderer, self.treeview)
 
-        self.window.show()
+        self.treeview.set_fixed_height_mode(True)
 
-    def on_toolbar_activate(self, widget):
-        if self.toolbar.parent.get_property("visible"):
-            self.toolbar.parent.hide()
-            self.gconf_client.set_bool(self.gconf_key_base + "toolbar", False)
-        else:
-            self.toolbar.parent.show()
-            self.gconf_client.set_bool(self.gconf_key_base + "toolbar", True)
+        # menu plugins
+        board_plugins.load(self.treeview, self.menu_edit)
 
-    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 updated_thread_highlight(self, column, cell, model, iter):
-
-        def is_updated_thread():
-            res = model.get_value(
-                iter, ThreadListModel.column_names.index("res"))
-            linecount = model.get_value(
-                iter, ThreadListModel.column_names.index("lineCount"))
-            return res != 0 and linecount != 0 and res > linecount
-
-        if is_updated_thread():
-            cell.set_property("weight", 800)
-        else:
-            cell.set_property("weight", 400)
-
-    def on_cell_data(self, column, cell, model, iter, column_name):
-        self.updated_thread_highlight(column, cell, model, iter)
-        column_num = ThreadListModel.column_names.index(column_name)
-        value = model.get_value(iter, column_num)
-        if model.get_column_type(column_num) == gobject.TYPE_INT:
-            if value == 0:
-                cell.set_property("text", "")
-            else:
-                cell.set_property("text", str(value))
-        else:
-            cell.set_property("text", value)
-
-    def on_data_lastmodified(self, column, cell, model, iter, user_data=None):
-        self.updated_thread_highlight(column, cell, model, iter)
-        lastmod = model.get_value(
-            iter, ThreadListModel.column_names.index("lastModified"))
-        if lastmod == 0:
-            cell.set_property("text", "")
-        else:
-            cell.set_property("text", time.strftime(
-                "%Y/%m/%d(%a) %H:%M:%S", time.localtime(lastmod)))
+        self.restore()
+        self.window.show()
 
-    def on_board_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.created()
 
-        return False
+    def _get_widgets(self):
+        self.window = self.widget_tree.get_widget("window_board")
+        self.treeview = self.widget_tree.get_widget("treeview")
+        self.popupmenu = self.widget_tree.get_widget("popup_treeview_menu")
+        self.toolbar = self.widget_tree.get_widget("toolbar")
+        self.statusbar = self.widget_tree.get_widget("appbar")
+        self.progress = self.statusbar.get_progress()
+        self.filterbar = self.widget_tree.get_widget(
+            "bonobodockitem_filterbar")
+        self.entry_filterbar = self.widget_tree.get_widget("entry_filterbar")
+        self.menu_edit = self.widget_tree.get_widget("menu_edit").get_submenu()
 
-    def on_board_window_destroy(self, widget):
-        pass
+    def set_status(self, text):
+        self.statusbar.set_status(text)
 
-    def on_quit_activate(self, widget):
-        session.main_quit()
+    def set_fraction(self, fraction):
+        self.progress.set_fraction(fraction)
 
-    def on_close_activate(self, widget):
+    def destroy(self):
+        self.save()
         self.window.destroy()
 
-    def on_refresh_activate(self, widget):
-        t = board_data.GetRemote(
-            self.bbs, self.board, self.bbs_type.get_subject_txt_uri(),
-            self.update_datastore)
-        t.start()
+    def get_uri(self):
+        return self.bbs_type.get_uri_base()
 
-    def on_column_clicked(self, treeviewcolumn, column_name):
-        model = self.treeview.get_model()
-        if model:
-            model.sort(column_name)
-            self.reset_sort_indicator()
-
-    def reset_sort_indicator(self):
-        model = self.treeview.get_model()
-        if model:
-            sort_column_name, sort_reverse = model.get_sort()
-            for name,column in self.treeviewcolumn.iteritems():
-                column.set_sort_indicator(False)
-            if sort_column_name != "num" or sort_reverse:
-                self.treeviewcolumn[sort_column_name].set_sort_indicator(True)
-                if sort_reverse:
-                    self.treeviewcolumn[sort_column_name].set_sort_order(
-                        gtk.SORT_DESCENDING)
-                else:
-                    self.treeviewcolumn[sort_column_name].set_sort_order(
-                        gtk.SORT_ASCENDING)
-        
-    def on_open_thread(self, widget):
+    def open_thread(self):
         treeselection = self.treeview.get_selection()
         model, iter = treeselection.get_selected()
         if not iter:
@@ -244,67 +138,15 @@ class WinWrap:
         bbs_type_for_thread = self.bbs_type.clone_with_thread(thread)
         uri_opener.open_uri(bbs_type_for_thread.get_thread_uri(), update)
 
-    def on_treeview_button_press_event(self, widget, event):
-        if event.button == 3:
-            x = int(event.x)
-            y = int(event.y)
-            time = event.time
-            pthinfo = widget.get_path_at_pos(x, y)
-            if pthinfo is not None:
-                path, col, cellx, celly = pthinfo
-                widget.grab_focus()
-                widget.set_cursor(path, col, 0)
-                self.popupmenu.popup(None, None, None, event.button, time)
-            return 1
-
-    def update_datastore(self, datalist, lastmod):
+    def update_datastore(self, new_list):
         print "reflesh datastore"
 
-        try:
-            lastmod = misc.httpdate_to_secs(lastmod)
-        except:
-            lastmod = 0
-
-        time_start = time.time()
-        list_list = []
-        for id, dic in datalist.iteritems():
-            dic["id"] = id
-
-            # average
-            if lastmod == 0 or dic["num"] == 0:
-                dic["average"] = 0
-            else:
-                res = dic["res"]
-                start = int(id)
-                # avoid the Last-Modified time of subject.txt and
-                # the build time of thread is equal (zero division)
-                dur = lastmod - start
-                if dur == 0:
-                    dic["average"] = 999999
-                else:
-                    dic["average"] = int(res * 60 * 60 * 24 / dur)
-
-            # lastModified
-            httpdate = dic["lastModified"]
-            try:
-                secs = misc.httpdate_to_secs(httpdate)
-                dic["lastModified"] = secs
-            except ValueError:
-                dic["lastModified"] = 0
-            
-            list_list.append(dic)
-
-        model = self.treeview.get_model()
-        model.set_list(list_list)
+        self.treeview.get_model().set_list(new_list)
 
         # redraw visible area after set list to model
         self.treeview.queue_draw()
 
-        self.reset_sort_indicator()
-
         print "end"
-        time_end = time.time()
-        print time_end - time_start
 
     def on_thread_idx_updated(self, thread_uri, idx_dic):
         if not thread_uri or not idx_dic:
@@ -312,8 +154,8 @@ class WinWrap:
 
         # nothing to do if thread_uri does not belong to this board.
         bbs_type = bbs_type_judge_uri.get_type(thread_uri)
-        if bbs_type.bbs_type != self.bbs \
-           or bbs_type.board != self.board or not bbs_type.is_thread():
+        if not bbs_type.is_thread() \
+               or not bbs_type.is_same_board(self.bbs_type):
             return
 
         thread = bbs_type.thread
@@ -329,16 +171,351 @@ class WinWrap:
             model.modify_row(idx_dic)
 
     def load(self, update=False):
-        sbj_path = misc.get_board_subjecttxt_path(
-            self.bbs_type.bbs_type, self.bbs_type.board)
+
+        def modify_dict(item_dict):
+            # lastModified, httpdate to second
+            httpdate = item_dict["lastModified"]
+            try:
+                secs = misc.httpdate_to_secs(httpdate)
+            except ValueError:
+                item_dict["lastModified"] = 0
+            else:
+                item_dict["lastModified"] = secs
+            return item_dict
+
+        def set_id(thread_id, item_dict):
+            item_dict["id"] = thread_id
+            return item_dict
+
+        def conv_dictdict_to_listdict(dictdict):
+            key_iter = dictdict.iterkeys()
+            value_iter = dictdict.itervalues()
+            iterable = itertools.imap(set_id, key_iter, value_iter)
+            iterable = itertools.imap(modify_dict, iterable)
+            return [item_dict for item_dict in iterable]
+
+        def load_local():
+            datalist = self.load_idxfiles()
+            self.merge_local_subjecttxt(datalist)
+            new_list = conv_dictdict_to_listdict(datalist)
+            gobject.idle_add(self.update_datastore, new_list)
+
+        def get_remote():
+            print "start get subject.txt"
+            datalist = self.load_idxfiles()
+            self.merge_remote_subjecttxt(datalist)
+            new_list = conv_dictdict_to_listdict(datalist)
+            gobject.idle_add(self.update_datastore, new_list)
+
+        def on_end():
+            def reset_progress():
+                self.progress.set_fraction(0.0)
+                self.progress.hide()
+            gobject.idle_add(reset_progress)
+
+        sbj_path = misc.get_board_subjecttxt_path(self.bbs_type)
         sbj_exists = os.path.exists(sbj_path)
 
+        self.progress.show()
+
         if update or not sbj_exists:
-            t = board_data.GetRemote(
-                self.bbs, self.board, self.bbs_type.get_subject_txt_uri(),
-                self.update_datastore)
+            t = ThreadInvoker(on_end, get_remote)
             t.start()
         else:
-            t = board_data.LoadLocal(
-                self.bbs, self.board, self.update_datastore)
+            t = ThreadInvoker(on_end, load_local)
             t.start()
+
+    def save(self):
+        try:
+            states_path = misc.get_board_states_path(self.bbs_type)
+            dirname = os.path.dirname(states_path)
+
+            # save only if board dir exists.
+            if os.path.exists(dirname):
+                window_width, window_height = self.window.get_size()
+                toolbar_visible = self.toolbar.parent.get_property("visible")
+                statusbar_visible = self.statusbar.get_property("visible")
+                filterbar_visible = self.filterbar.get_property("visible")
+
+                columns = self.treeview.get_columns()
+                order = ""
+                width = ""
+                for column in columns:
+                    if order:
+                        order += ","
+                    order += column.id
+                    if width:
+                        width += ","
+                    width += str(column.get_width())
+
+                sort_column_name, sort_reverse = \
+                                  self.treeview.get_model().get_sort()
+                sort_reverse = str(sort_reverse)
+
+                f = file(states_path, "w")
+
+                f.write("columns=" + order + "\n")
+                f.write("widths=" + width + "\n")
+                f.write("sort_column=" + sort_column_name + "\n")
+                f.write("sort_reverse=" + sort_reverse + "\n")
+                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.write("filterbar_visible=" + str(filterbar_visible) + "\n")
+
+                f.close()
+        except:
+            traceback.print_exc()
+
+    def restore(self):
+        states_dict = dict([
+            ("columns", ()), ("widths", ()), ("sort_column", "num"),
+            ("sort_reverse", False), ("window_height", 600),
+            ("window_width", 600), ("toolbar_visible", True),
+            ("statusbar_visible", True), ("filterbar_visible", False)])
+
+        interest_list = [
+            "columns", "widths", "sort_column", "sort_reverse",
+            "window_height", "window_width", "toolbar_visible",
+            "statusbar_visible", "filterbar_visible"]
+
+        interest_type_list = (
+            (list, str), (list, int), (str, None), (bool, None), (int, None),
+            (int, None), (bool, None), (bool, None), (bool, None))
+
+        def key_value_generator():
+
+            def generate_pair():
+                states_path = misc.get_board_states_path(self.bbs_type)
+                try:
+                    for line in file(states_path):
+                        key_equal_value = line.rstrip()
+                        try:
+                            index = key_equal_value.index("=")
+                        except ValueError:
+                            pass
+                        else:
+                            key = key_equal_value[:index]
+                            if key in interest_list:
+                                value = key_equal_value[index+1:]
+                                yield key, value
+                except IOError:
+                    traceback.print_exc()
+
+            def to_int(value):
+                try:
+                    return int(value)
+                except:
+                    return 0
+
+            for key, value in generate_pair():
+                list_index = interest_list.index(key)
+                key_type, key_type_extra = interest_type_list[list_index]
+                if key_type == str:
+                    yield key, value
+                elif key_type == int:
+                    yield key, to_int(value)
+                elif key_type == bool:
+                    value = value == "True"
+                    yield key, value
+                elif key_type == list:
+                    if key_type_extra == str:
+                        yield key, value.split(",")
+                    elif key_type_extra == int:
+                        yield key, [i for i in itertools.imap(
+                            to_int, value.split(","))]
+                    else:
+                        print key, "not supported",
+                        key_type, key_type_extra, value
+                else:
+                    print key, "not supported", key_type, value
+
+        def load_gconf():
+            key_base = config.gconf_app_key_base() + "/board_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")
+            states_dict["toolbar_visible"] = gconf_client.get_bool(
+                key_base + "/toolbar")
+            states_dict["statusbar_visible"] = gconf_client.get_bool(
+                key_base + "/statusbar")
+            states_dict["filterbar_visible"] = gconf_client.get_bool(
+                key_base + "/filterbar")
+
+            if width != 0:
+                states_dict["window_width"] = width
+            if height != 0:
+                states_dict["window_height"] = height
+
+        try:
+            try:
+                load_gconf()
+            except:
+                traceback.print_exc()
+
+            for key, value in key_value_generator():
+                states_dict[key] = value
+
+            self.treeview.get_model().sort(
+                states_dict["sort_column"], True, states_dict["sort_reverse"])
+
+            # set column order before set column width
+            treeviewcolumn = dict(
+                [(cln.id, cln) for cln in self.treeview.get_columns()])
+            base_column = None
+            for column_name in states_dict["columns"]:
+                if column_name in treeviewcolumn:
+                    column = treeviewcolumn[column_name]
+                    self.treeview.move_column_after(column, base_column)
+                    base_column = column
+
+            # set column width afeter set column order
+            for width, column in itertools.izip(
+                states_dict["widths"], self.treeview.get_columns()):
+                if width:
+                    column.set_fixed_width(width)
+
+            self.window.set_default_size(
+                states_dict["window_width"], states_dict["window_height"])
+
+            if not states_dict["toolbar_visible"]:
+                gobject.idle_add(self.toolbar.parent.hide,
+                                 priority=gobject.PRIORITY_HIGH)
+            if not states_dict["statusbar_visible"]:
+                gobject.idle_add(self.statusbar.hide,
+                                 priority=gobject.PRIORITY_HIGH)
+            if not states_dict["filterbar_visible"]:
+                gobject.idle_add(self.filterbar.hide,
+                                 priority=gobject.PRIORITY_HIGH)
+        except:
+            traceback.print_exc()
+
+    def on_menu_toolbar_activate(self, widget):
+        if self.toolbar.parent.get_property("visible"):
+            self.toolbar.parent.hide()
+        else:
+            self.toolbar.parent.show()
+
+    def on_menu_statusbar_activate(self, widget):
+        if self.statusbar.get_property("visible"):
+            self.statusbar.hide()
+        else:
+            self.statusbar.show()
+
+
+    # signal handlers
+
+    # menu and toolbutton
+
+    def on_menu_add_bookmark_activate(self, widget):
+        bookmark_list.bookmark_list.add_bookmark_with_edit(
+            uri=self.bbs_type.uri)
+
+    def on_menu_close_activate(self, widget):
+        self.destroy()
+
+    def on_menu_delete_activate(self, widget):
+        selection = self.treeview.get_selection()
+        model, iter = selection.get_selected()
+        if not iter:
+            return
+        thread = model.get_value(
+            iter, ThreadListModel.column_names.index("id"))
+        
+        bbs_type_for_thread = self.bbs_type.clone_with_thread(thread)
+
+        dat_path = misc.get_thread_dat_path(bbs_type_for_thread)
+        try:
+            os.remove(dat_path)
+        except OSError:
+            traceback.print_exc()
+        idx_path = misc.get_thread_idx_path(bbs_type_for_thread)
+        try:
+            os.remove(idx_path)
+        except OSError:
+            traceback.print_exc()
+        states_path = misc.get_thread_states_path(bbs_type_for_thread)
+        try:
+            os.remove(states_path)
+        except OSError:
+            traceback.print_exc()
+
+    def on_menu_filter_activate(self, widget):
+        self.filterbar.show()
+        self.entry_filterbar.grab_focus()
+
+    def on_menu_manage_bookmarks_activate(self, widget):
+        bookmark_window.open()
+
+    def on_menu_open_activate(self, widget):
+        self.open_thread()
+
+    def on_menu_quit_activate(self, widget):
+        session.main_quit()
+
+    def on_menu_refresh_activate(self, widget):
+        self.load(True)
+
+
+    # window
+
+    def on_window_board_delete_event(self, widget, event):
+        self.save()
+        return False
+
+    def on_window_board_destroy(self, widget):
+        self.popupmenu.destroy()
+        for column in self.treeview.get_columns():
+            column.destroy()
+        self.destroyed()
+
+
+    # treeview
+
+    def on_treeview_row_activated(self, widget, path, view_column):
+        self.open_thread()
+
+    def on_treeview_button_press_event(self, widget, event):
+        if event.button == 3:
+            x = int(event.x)
+            y = int(event.y)
+            time = event.time
+            pthinfo = widget.get_path_at_pos(x, y)
+            if pthinfo is not None:
+                path, col, cellx, celly = pthinfo
+                widget.grab_focus()
+                widget.set_cursor(path, col, 0)
+                self.popupmenu.popup(None, None, None, event.button, time)
+            return 1
+
+
+    # filterbar
+
+    def on_entry_filterbar_activate(self, widget):
+        text = widget.get_text()
+
+        def func(model, item):
+            try:
+                item["title"].index(text)
+            except ValueError:
+                return False
+            else:
+                return True
+
+        model = self.treeview.get_model()
+        if model:
+            if text:
+                filter_func = func
+            else:
+                filter_func = None
+            model.refilter(filter_func)
+
+    def on_toolbutton_filterbar_close_clicked(self, widget):
+        self.filterbar.hide()
+
+    def on_button_filterbar_clear_clicked(self, widget):
+        self.entry_filterbar.set_text("")
+        model = self.treeview.get_model()
+        if model:
+            model.refilter(None)