class ThreePaneViewer (Viewer):
    """A viewer with a treeview, listview, and editor"""

    def __init__(self, app, main_window, viewerid=None):
        Viewer.__init__(self, app, main_window, viewerid,
                        viewer_name="three_pane_viewer")
        self._ui_ready = False

        # node selections        
        self._current_page = None     # current page in editor
        self._treeview_sel_nodes = [] # current selected nodes in treeview
        self._queue_list_select = []  # nodes to select in listview after treeview change
        self._new_page_occurred = False
        self.back_button = None
        self._view_mode = DEFAULT_VIEW_MODE

        self.connect("history-changed", self._on_history_changed)

        #=========================================
        # widgets


        # treeview
        self.treeview = KeepNoteTreeView()
        self.treeview.set_get_node(self._app.get_node)
        self.treeview.connect("select-nodes", self._on_tree_select)
        self.treeview.connect("delete-node", self.on_delete_node)
        self.treeview.connect("error", lambda w,t,e: self.emit("error", t, e))
        self.treeview.connect("edit-title", self._on_edit_title)
        self.treeview.connect("goto-node", self.on_goto_node)
        self.treeview.connect("activate-node", self.on_activate_node)
        self.treeview.connect("drop-file", self._on_attach_file)
        
        # listview
        self.listview = KeepNoteListView()
        self.listview.set_get_node(self._app.get_node)
        self.listview.connect("select-nodes", self._on_list_select)
        self.listview.connect("delete-node", self.on_delete_node)
        self.listview.connect("goto-node", self.on_goto_node)
        self.listview.connect("activate-node", self.on_activate_node)
        self.listview.connect("goto-parent-node",
                              lambda w: self.on_goto_parent_node())
        self.listview.connect("error", lambda w,t,e: self.emit("error", t, e))
        self.listview.connect("edit-title", self._on_edit_title)
        self.listview.connect("drop-file", self._on_attach_file)
        self.listview.on_status = self.set_status  # TODO: clean up
        
        # editor
        #self.editor = KeepNoteEditor(self._app)
        #self.editor = RichTextEditor(self._app)
        self.editor = ContentEditor(self._app)
        rich_editor = RichTextEditor(self._app)
        self.editor.add_editor("text/xhtml+xml", rich_editor)
        self.editor.add_editor("text", TextEditor(self._app))
        self.editor.set_default_editor(rich_editor)

        self.editor.connect("view-node", self._on_editor_view_node)
        self.editor.connect("child-activated", self._on_child_activated)
        self.editor.connect("visit-node", lambda w, n: self.goto_node(n, False))
        self.editor.connect("error", lambda w,t,e: self.emit("error", t, e))
        self.editor.connect("window-request", lambda w,t: 
                            self.emit("window-request", t))
        self.editor.view_pages([])
        
        self.editor_pane = gtk.VBox(False, 5)
        self.editor_pane.pack_start(self.editor, True, True, 0)


        #=====================================
        # layout

        # TODO: make sure to add underscore for these variables

        # create a horizontal paned widget
        self.hpaned = gtk.HPaned()
        self.pack_start(self.hpaned, True, True, 0)
        self.hpaned.set_position(DEFAULT_HSASH_POS)

                
        # layout major widgets
        self.paned2 = gtk.VPaned()
        self.hpaned.add2(self.paned2)
        self.paned2.set_position(DEFAULT_VSASH_POS)
        
        # treeview and scrollbars
        sw = gtk.ScrolledWindow()
        sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        sw.set_shadow_type(gtk.SHADOW_IN)
        sw.add(self.treeview)
        self.hpaned.add1(sw)
        
        # listview with scrollbars
        self.listview_sw = gtk.ScrolledWindow()
        self.listview_sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        self.listview_sw.set_shadow_type(gtk.SHADOW_IN)
        self.listview_sw.add(self.listview)
        self.paned2.add1(self.listview_sw)
        #self.paned2.child_set_property(self.listview_sw, "shrink", True)
        
        # layout editor
        self.paned2.add2(self.editor_pane)
        
        self.treeview.grab_focus()


    def set_notebook(self, notebook):
        """Set the notebook for the viewer"""

        # add/remove reference to notebook
        self._app.ref_notebook(notebook)
        if self._notebook is not None:
            self._app.unref_notebook(self._notebook)

        # deregister last notebook, if it exists
        if self._notebook:
            self._notebook.node_changed.remove(
                self.on_notebook_node_changed)

        # setup listeners
        if notebook:
            notebook.node_changed.add(self.on_notebook_node_changed)


        # set notebook
        self._notebook = notebook
        self.editor.set_notebook(notebook)
        self.listview.set_notebook(notebook)
        self.treeview.set_notebook(notebook)
        
        if self.treeview.get_popup_menu():
            self.treeview.get_popup_menu().iconmenu.set_notebook(notebook)
            self.listview.get_popup_menu().iconmenu.set_notebook(notebook)

        # restore selections
        self._load_selections()

        # put focus on treeview
        self.treeview.grab_focus()


    def load_preferences(self, app_pref, first_open=False):
        """Load application preferences"""

        p = app_pref.get("viewers", "three_pane_viewer", define=True)
        self.set_view_mode(p.get("view_mode", DEFAULT_VIEW_MODE))
        self.paned2.set_property("position-set", True)
        self.hpaned.set_property("position-set", True)
        self.paned2.set_position(p.get("vsash_pos", DEFAULT_VSASH_POS))
        self.hpaned.set_position(p.get("hsash_pos", DEFAULT_HSASH_POS))


        self.listview.set_date_formats(app_pref.get("timestamp_formats"))
        self.listview.set_rules_hint(
            app_pref.get("look_and_feel", "listview_rules",
                         default=True))
        try:
            # if this version of GTK doesn't have tree-lines, ignore it
            self.treeview.set_property(
                "enable-tree-lines",
                app_pref.get("look_and_feel", "treeview_lines", default=True))
        except:
            pass
        
        self.editor.load_preferences(app_pref, first_open)
        
        # reload ui
        if self._ui_ready:
            self.remove_ui(self._main_window)
            self.add_ui(self._main_window)


    def save_preferences(self, app_pref):
        """Save application preferences"""
        
        p = app_pref.get("viewers", "three_pane_viewer")
        p["view_mode"] = self._view_mode
        p["vsash_pos"] = self.paned2.get_position()
        p["hsash_pos"] = self.hpaned.get_position()

        self.editor.save_preferences(app_pref)


    def save(self):
        """Save the current notebook"""

        self.editor.save()
        self._save_selections()
               

    def on_notebook_node_changed(self, nodes):
        """Callback for when notebook node is changed"""

        #if self._current_page in nodes:
        #    self.emit("current-node", self._current_page)
        self.emit("modified", True)


    def undo(self):
        """Undo the last action in the viewer"""
        self.editor.undo()        

    def redo(self):
        """Redo the last action in the viewer"""
        self.editor.redo()


    def get_editor(self):
        return self.editor


    def set_status(self, text, bar="status"):
        """Set a status message"""
        self.emit("status", text, bar)

    def set_view_mode(self, mode):
        """
        Sets view mode for ThreePaneViewer

        modes:
            "vertical"
            "horizontal"
        """

        vsash = self.paned2.get_position()

        # detach widgets
        self.paned2.remove(self.listview_sw)
        self.paned2.remove(self.editor_pane)
        self.hpaned.remove(self.paned2)

        # remake paned2
        if mode == "vertical":
            # create a vertical paned widget
            self.paned2 = gtk.VPaned()
        else:
            # create a horizontal paned widget
            self.paned2 = gtk.HPaned()
        
        self.paned2.set_position(vsash)
        self.paned2.show()        
        
        self.hpaned.add2(self.paned2)
        self.hpaned.show()
        
        self.paned2.add1(self.listview_sw)
        self.paned2.add2(self.editor_pane)

        # record preference
        self._view_mode = mode



    def _load_selections(self):
        """Load previous node selections from notebook preferences"""
        
        if self._notebook:
            info = self._notebook.pref.get("viewers", "ids", 
                                           self._viewerid, define=True)

            # load selections
            nodes = [node for node in (
                    self._notebook.get_node_by_id(i)
                    for i in info.get(
                        "selected_treeview_nodes", []))
                     if node is not None]
            self.treeview.select_nodes(nodes)
            nodes = [node for node in (
                    self._notebook.get_node_by_id(i)
                    for i in info.get(
                        "selected_listview_nodes", []))
                     if node is not None]
            self.listview.select_nodes(nodes)

    def _save_selections(self):
        """Save node selections into notebook preferences"""
        
        if self._notebook is not None:
            info = self._notebook.pref.get("viewers", "ids", 
                                           self._viewerid, define=True)
            
            # save selections
            info["selected_treeview_nodes"] = [
                node.get_attr("nodeid")
                for node in self.treeview.get_selected_nodes()]
            info["selected_listview_nodes"] = [
                node.get_attr("nodeid")
                for node in self.listview.get_selected_nodes()]
            self._notebook.set_preferences_dirty()


    #===============================================
    # node operations


    def get_current_page(self):
        """Returns the currently focused page"""
        return self._current_page


    def get_selected_nodes(self):
        """
        Returns  a list of selected nodes.
        """

        if self.treeview.is_focus():
            return self.treeview.get_selected_nodes()
        else:
            nodes = self.listview.get_selected_nodes()
            if len(nodes) == 0:
                return self.treeview.get_selected_nodes()
            else:
                return nodes


    def _on_history_changed(self, viewer, history):
        """Callback for when node browse history changes"""
        
        if self._ui_ready and self.back_button:
            self.back_button.set_sensitive(history.has_back())
            self.forward_button.set_sensitive(history.has_forward())


    def get_focused_widget(self, default=None):
        """Returns the currently focused widget"""
        
        if self.treeview.is_focus():
            return self.treeview
        if self.listview.is_focus():
            return self.listview
        else:
            return default
    

    def on_delete_node(self, widget, nodes=None):
        """Callback for deleting a node"""
        
        # get node to delete
        if nodes is None:
            nodes = self.get_selected_nodes()
            
        if len(nodes) == 0:
            return

        
        if self._main_window.confirm_delete_nodes(nodes):
            # change selection
            if len(nodes) == 1:
                node = nodes[0]
                widget = self.get_focused_widget(self.listview)
                parent = node.get_parent()
                children = parent.get_children()
                i = children.index(node)

                if i < len(children) - 1:
                    widget.select_nodes([children[i+1]])
                else:
                    widget.select_nodes([parent])
            else:
                widget = self.get_focused_widget(self.listview)
                widget.select_nodes([])

            # perform delete
            try:
                for node in nodes:
                    node.trash()
            except NoteBookError, e:
                self.emit("error", e.msg, e)
Example #2
0
class ThreePaneViewer (Viewer):
    """A viewer with a treeview, listview, and editor"""

    def __init__(self, app, main_window, viewerid=None):
        Viewer.__init__(self, app, main_window, viewerid,
                        viewer_name="three_pane_viewer")
        self._ui_ready = False

        # node selections        
        self._current_page = None     # current page in editor
        self._treeview_sel_nodes = [] # current selected nodes in treeview
        self._queue_list_select = []  # nodes to select in listview after treeview change
        self._new_page_occurred = False
        self.back_button = None
        self._view_mode = DEFAULT_VIEW_MODE

        self.connect("history-changed", self._on_history_changed)

        #=========================================
        # widgets


        # treeview
        self.treeview = KeepNoteTreeView()
        self.treeview.connect("select-nodes", self._on_tree_select)
        self.treeview.connect("delete-node", self.on_delete_node)
        self.treeview.connect("error", lambda w,t,e: self.emit("error", t, e))
        self.treeview.connect("edit-title", self._on_edit_title)
        self.treeview.connect("goto-node", self.on_goto_node)
        self.treeview.connect("activate-node", self.on_activate_node)
        self.treeview.connect("drop-file", self._on_attach_file)
        
        # listview
        self.listview = KeepNoteListView()
        self.listview.connect("select-nodes", self._on_list_select)
        self.listview.connect("delete-node", self.on_delete_node)
        self.listview.connect("goto-node", self.on_goto_node)
        self.listview.connect("activate-node", self.on_activate_node)
        self.listview.connect("goto-parent-node",
                              lambda w: self.on_goto_parent_node())
        self.listview.connect("error", lambda w,t,e: self.emit("error", t, e))
        self.listview.connect("edit-title", self._on_edit_title)
        self.listview.connect("drop-file", self._on_attach_file)
        self.listview.on_status = self.set_status  # TODO: clean up
        
        # editor
        #self.editor = KeepNoteEditor(self._app)
        #self.editor = RichTextEditor(self._app)
        self.editor = ContentEditor(self._app)
        rich_editor = RichTextEditor(self._app)
        self.editor.add_editor("text/xhtml+xml", rich_editor)
        self.editor.add_editor("text", TextEditor(self._app))
        self.editor.set_default_editor(rich_editor)

        self.editor.connect("view-node", self._on_editor_view_node)
        self.editor.connect("child-activated", self._on_child_activated)
        self.editor.connect("visit-node", lambda w, n: self.goto_node(n, False))
        self.editor.connect("error", lambda w,t,e: self.emit("error", t, e))
        self.editor.connect("window-request", lambda w,t: 
                            self.emit("window-request", t))
        self.editor.view_pages([])
        
        self.editor_pane = gtk.VBox(False, 5)
        self.editor_pane.pack_start(self.editor, True, True, 0)


        #=====================================
        # layout

        # TODO: make sure to add underscore for these variables

        # create a horizontal paned widget
        self.hpaned = gtk.HPaned()
        self.pack_start(self.hpaned, True, True, 0)
        self.hpaned.set_position(DEFAULT_HSASH_POS)

                
        # layout major widgets
        self.paned2 = gtk.VPaned()
        self.hpaned.add2(self.paned2)
        self.paned2.set_position(DEFAULT_VSASH_POS)
        
        # treeview and scrollbars
        sw = gtk.ScrolledWindow()
        sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        sw.set_shadow_type(gtk.SHADOW_IN)
        sw.add(self.treeview)
        self.hpaned.add1(sw)
        
        # listview with scrollbars
        self.listview_sw = gtk.ScrolledWindow()
        self.listview_sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        self.listview_sw.set_shadow_type(gtk.SHADOW_IN)
        self.listview_sw.add(self.listview)
        self.paned2.add1(self.listview_sw)
        
        # layout editor
        self.paned2.add2(self.editor_pane)
        
        self.treeview.grab_focus()


    def set_notebook(self, notebook):
        """Set the notebook for the viewer"""

        # add/remove reference to notebook
        self._app.ref_notebook(notebook)
        if self._notebook is not None:
            self._app.unref_notebook(self._notebook)

        # deregister last notebook, if it exists
        if self._notebook:
            self._notebook.node_changed.remove(
                self.on_notebook_node_changed)

        # setup listeners
        if notebook:
            notebook.node_changed.add(self.on_notebook_node_changed)


        # set notebook
        self._notebook = notebook
        self.editor.set_notebook(notebook)
        self.listview.set_notebook(notebook)
        self.treeview.set_notebook(notebook)
        
        if self.treeview.get_popup_menu():
            self.treeview.get_popup_menu().iconmenu.set_notebook(notebook)
            self.listview.get_popup_menu().iconmenu.set_notebook(notebook)

        # restore selections
        self._load_selections()

        # put focus on treeview
        self.treeview.grab_focus()


    def load_preferences(self, app_pref, first_open=False):
        """Load application preferences"""

        p = app_pref.get("viewers", "three_pane_viewer", define=True)
        self.set_view_mode(p.get("view_mode", DEFAULT_VIEW_MODE))
        self.paned2.set_position(p.get("vsash_pos", DEFAULT_VSASH_POS))
        self.hpaned.set_position(p.get("hsash_pos", DEFAULT_HSASH_POS))


        self.listview.set_date_formats(app_pref.get("timestamp_formats"))
        self.listview.set_rules_hint(
            app_pref.get("look_and_feel", "listview_rules",
                         default=True))
        try:
            # if this version of GTK doesn't have tree-lines, ignore it
            self.treeview.set_property(
                "enable-tree-lines",
                app_pref.get("look_and_feel", "treeview_lines", default=True))
        except:
            pass
        
        self.editor.load_preferences(app_pref, first_open)
        
        # reload ui
        if self._ui_ready:
            self.remove_ui(self._main_window)
            self.add_ui(self._main_window)


    def save_preferences(self, app_pref):
        """Save application preferences"""
        
        p = app_pref.get("viewers", "three_pane_viewer")
        p["view_mode"] = self._view_mode
        p["vsash_pos"] = self.paned2.get_position()
        p["hsash_pos"] = self.hpaned.get_position()

        self.editor.save_preferences(app_pref)


    def save(self):
        """Save the current notebook"""

        self.editor.save()
        self._save_selections()
               

    def on_notebook_node_changed(self, nodes, recurse):
        """Callback for when notebook node is changed"""

        #if self._current_page in nodes:
        #    self.emit("current-node", self._current_page)
        self.emit("modified", True)


    def undo(self):
        """Undo the last action in the viewer"""
        self.editor.undo()        

    def redo(self):
        """Redo the last action in the viewer"""
        self.editor.redo()


    def set_status(self, text, bar="status"):
        """Set a status message"""
        self.emit("status", text, bar)

    def set_view_mode(self, mode):
        """
        Sets view mode for ThreePaneViewer

        modes:
            "vertical"
            "horizontal"
        """

        vsash = self.paned2.get_position()

        # detach widgets
        self.paned2.remove(self.listview_sw)
        self.paned2.remove(self.editor_pane)
        self.hpaned.remove(self.paned2)

        # remake paned2
        if mode == "vertical":
            # create a vertical paned widget
            self.paned2 = gtk.VPaned()
        else:
            # create a horizontal paned widget
            self.paned2 = gtk.HPaned()
        
        self.paned2.set_position(vsash)
        self.paned2.show()        
        
        self.hpaned.add2(self.paned2)
        self.hpaned.show()
        
        self.paned2.add1(self.listview_sw)
        self.paned2.add2(self.editor_pane)

        # record preference
        self._view_mode = mode



    def _load_selections(self):
        """Load previous node selections from notebook preferences"""
        
        if self._notebook:
            info = self._notebook.pref.get("viewers", "ids", 
                                           self._viewerid, define=True)

            # load selections
            nodes = [node for node in (
                    self._notebook.get_node_by_id(i)
                    for i in info.get(
                        "selected_treeview_nodes", []))
                     if node is not None]
            self.treeview.select_nodes(nodes)
            nodes = [node for node in (
                    self._notebook.get_node_by_id(i)
                    for i in info.get(
                        "selected_listview_nodes", []))
                     if node is not None]
            self.listview.select_nodes(nodes)

    def _save_selections(self):
        """Save node selections into notebook preferences"""
        
        if self._notebook is not None:
            info = self._notebook.pref.get("viewers", "ids", 
                                           self._viewerid, define=True)
            
            # save selections
            info["selected_treeview_nodes"] = [
                node.get_attr("nodeid")
                for node in self.treeview.get_selected_nodes()]
            info["selected_listview_nodes"] = [
                node.get_attr("nodeid")
                for node in self.listview.get_selected_nodes()]
            self._notebook.set_preferences_dirty()


    #===============================================
    # node operations


    def get_current_page(self):
        """Returns the currently focused page"""
        return self._current_page


    def get_selected_nodes(self):
        """
        Returns  a list of selected nodes.
        """

        if self.treeview.is_focus():
            return self.treeview.get_selected_nodes()
        else:
            nodes = self.listview.get_selected_nodes()
            if len(nodes) == 0:
                return self.treeview.get_selected_nodes()
            else:
                return nodes


    def _on_history_changed(self, viewer, history):
        """Callback for when node browse history changes"""
        
        if self._ui_ready and self.back_button:
            self.back_button.set_sensitive(history.has_back())
            self.forward_button.set_sensitive(history.has_forward())


    def get_focused_widget(self, default=None):
        """Returns the currently focused widget"""
        
        if self.treeview.is_focus():
            return self.treeview
        if self.listview.is_focus():
            return self.listview
        else:
            return default
    

    def on_delete_node(self, widget, nodes=None):
        """Callback for deleting a node"""
        
        # get node to delete
        if nodes is None:
            nodes = self.get_selected_nodes()
            
        if len(nodes) == 0:
            return

        
        if self._main_window.confirm_delete_nodes(nodes):
            # change selection
            if len(nodes) == 1:
                node = nodes[0]
                widget = self.get_focused_widget()
                parent = node.get_parent()
                children = parent.get_children()
                i = children.index(node)
                if i < len(children) - 1:
                    widget.select_nodes([children[i+1]])
                else:
                    widget.select_nodes([parent])
            else:
                widget = self.get_focused_widget()
                widget.select_nodes([])

            # perform delete
            try:
                for node in nodes:
                    node.trash()
            except NoteBookError, e:
                self.emit("error", e.msg, e)