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-node", self._on_edit_node) 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-node", self._on_edit_node) 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_nodes([]) 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) colors = self._notebook.pref.get("colors", default=DEFAULT_COLORS) \ if self._notebook else DEFAULT_COLORS self.treeview.get_popup_menu().fgcolor_menu.set_colors(colors) self.treeview.get_popup_menu().bgcolor_menu.set_colors(colors) self.listview.get_popup_menu().fgcolor_menu.set_colors(colors) self.listview.get_popup_menu().bgcolor_menu.set_colors(colors) # 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.load_preferences(app_pref, first_open) 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.listview.save_preferences(app_pref) self.editor.save_preferences(app_pref) def save(self): """Save the current notebook""" self.listview.save() self.editor.save() self._save_selections() def on_notebook_node_changed(self, nodes): """Callback for when notebook node is changed""" 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): """Returns node editor""" return self.editor.get_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_node(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)
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-node", self._on_edit_node) 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-node", self._on_edit_node) 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_nodes([]) 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) colors = self._notebook.pref.get("colors", default=DEFAULT_COLORS) \ if self._notebook else DEFAULT_COLORS self.treeview.get_popup_menu().fgcolor_menu.set_colors(colors) self.treeview.get_popup_menu().bgcolor_menu.set_colors(colors) self.listview.get_popup_menu().fgcolor_menu.set_colors(colors) self.listview.get_popup_menu().bgcolor_menu.set_colors(colors) # 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.load_preferences(app_pref, first_open) 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.listview.save_preferences(app_pref) self.editor.save_preferences(app_pref) def save(self): """Save the current notebook""" self.listview.save() self.editor.save() self._save_selections() def on_notebook_node_changed(self, nodes): """Callback for when notebook node is changed""" 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): """Returns node editor""" return self.editor.get_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_node(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 as e: self.emit("error", e.msg, e) def _on_editor_view_node(self, editor, node): """Callback for when editor views a node""" # record node in history self._history.add(node.get_attr("nodeid")) self.emit("history-changed", self._history) def _on_child_activated(self, editor, textview, child): """Callback for when child widget in editor is activated""" if self._current_page and isinstance(child, richtext.RichTextImage): filename = self._current_page.get_file(child.get_filename()) self._app.run_external_app("image_viewer", filename) def _on_tree_select(self, treeview, nodes): """Callback for treeview selection change""" # do nothing if selection is unchanged if self._treeview_sel_nodes == nodes: return # remember which nodes are selected in the treeview self._treeview_sel_nodes = nodes # view the children of these nodes in the listview self.listview.view_nodes(nodes) # if nodes are queued for selection in listview (via goto parent) # then select them here if len(self._queue_list_select) > 0: self.listview.select_nodes(self._queue_list_select) self._queue_list_select = [] # make sure nodes are also selected in listview self.listview.select_nodes(nodes) def _on_list_select(self, listview, nodes): """Callback for listview selection change""" # remember the selected node if len(nodes) == 1: self._current_page = nodes[0] else: self._current_page = None try: self.editor.view_nodes(nodes) except RichTextError as e: self.emit("error", "Could not load page '%s'." % nodes[0].get_title(), e) self.emit("current-node", self._current_page) def on_goto_node(self, widget, node): """Focus view on a node""" self.goto_node(node, direct=False) def on_activate_node(self, widget, node): """Focus view on a node""" if self.viewing_search(): # if we are in a search, goto node, but not directly self.goto_node(node, direct=False) else: if node and node.has_attr("payload_filename"): # open attached file self._main_window.on_view_node_external_app("file_launcher", node, kind="file") else: # goto node directly self.goto_node(node, direct=True) def on_goto_parent_node(self, node=None): """Focus view on a node's parent""" if node is None: nodes = self.get_selected_nodes() if len(nodes) == 0: return node = nodes[0] # get parent parent = node.get_parent() if parent is not None: self.goto_node(parent, direct=False) def _on_edit_node(self, widget, node, attr, value): """Callback for title edit finishing""" # move cursor to editor after new page has been created if self._new_page_occurred: self._new_page_occurred = False if node.get_attr("content_type") != notebooklib.CONTENT_TYPE_DIR: self.goto_editor() def _on_attach_file(self, widget, parent, index, uri): """Attach document""" self._app.attach_file(uri, parent, index) def _on_attach_file_menu(self): """Callback for attach file action""" nodes = self.get_selected_nodes() if len(nodes) > 0: node = nodes[0] self._app.on_attach_file(node, self.get_toplevel()) def new_node(self, kind, pos, parent=None): """Add a new node to the notebook""" # TODO: think about where this goes if self._notebook is None: return self.treeview.cancel_editing() self.listview.cancel_editing() if parent is None: nodes = self.get_selected_nodes() if len(nodes) == 1: parent = nodes[0] else: parent = self._notebook node = Viewer.new_node(self, kind, pos, parent) self._view_new_node(node) def on_new_dir(self): """Add new folder near selected nodes""" self.new_node(notebooklib.CONTENT_TYPE_DIR, "sibling") def on_new_page(self): """Add new page near selected nodes""" self.new_node(notebooklib.CONTENT_TYPE_PAGE, "sibling") def on_new_child_page(self): """Add new page as child of selected nodes""" self.new_node(notebooklib.CONTENT_TYPE_PAGE, "child") def _view_new_node(self, node): """View a node particular widget""" self._new_page_occurred = True self.goto_node(node) if node in self.treeview.get_selected_nodes(): self.treeview.edit_node(node) else: self.listview.edit_node(node) def _on_rename_node(self): """Callback for renaming a node""" nodes = self.get_selected_nodes() if len(nodes) == 0: return widget = self.get_focused_widget(self.listview) widget.edit_node(nodes[0]) def goto_node(self, node, direct=False): """Move view focus to a particular node""" if node is None: # default node is the one selected in the listview nodes = self.listview.get_selected_nodes() if len(nodes) == 0: return node = nodes[0] if direct: # direct goto: open up treeview all the way to the node self.treeview.select_nodes([node]) else: # indirect goto: do not open up treeview, only listview treenodes = self.treeview.get_selected_nodes() # get path to root path = [] ptr = node while ptr: if ptr in treenodes: # if parent path is already selected then quit path = [] break path.append(ptr) ptr = ptr.get_parent() # find first node that is collapsed node2 = None for node2 in reversed(path): if not self.treeview.is_node_expanded(node2): break # make selections if node2: self.treeview.select_nodes([node2]) # This test might be needed for windows crash if node2 != node: self.listview.select_nodes([node]) def goto_next_node(self): """Move focus to the 'next' node""" widget = self.get_focused_widget(self.treeview) path, col = widget.get_cursor() if path: path2 = path[:-1] + (path[-1] + 1,) if len(path) > 1: it = widget.get_model().get_iter(path[:-1]) nchildren = widget.get_model().iter_n_children(it) else: nchildren = widget.get_model().iter_n_children(None) if path2[-1] < nchildren: widget.set_cursor(path2) def goto_prev_node(self): """Move focus to the 'previous' node""" widget = self.get_focused_widget(self.treeview) path, col = widget.get_cursor() if path and path[-1] > 0: path2 = path[:-1] + (path[-1] - 1,) widget.set_cursor(path2) def expand_node(self, all=False): """Expand the tree beneath the focused node""" widget = self.get_focused_widget(self.treeview) path, col = widget.get_cursor() if path: widget.expand_row(path, all) def collapse_node(self, all=False): """Collapse the tree beneath the focused node""" widget = self.get_focused_widget(self.treeview) path, col = widget.get_cursor() if path: if all: # recursively collapse all notes widget.collapse_all_beneath(path) else: widget.collapse_row(path) def on_copy_tree(self): """Callback for copy on whole tree""" widget = self._main_window.get_focus() if gobject.signal_lookup("copy-tree-clipboard", widget) != 0: widget.emit("copy-tree-clipboard") #============================================ # Search def start_search_result(self): """Start a new search result""" self.treeview.select_nodes([]) self.listview.view_nodes([], nested=False) def add_search_result(self, node): """Add a search result""" self.listview.append_node(node) def end_search_result(self): """End a search result""" # select top result try: self.listview.get_selection().select_path((0,)) except: # don't worry if there isn't anything to select pass def viewing_search(self): """Returns True if we are currently viewing a search result""" return (len(self.treeview.get_selected_nodes()) == 0 and len(self.listview.get_selected_nodes()) > 0) #============================================= # Goto functions def goto_treeview(self): """Switch focus to TreeView""" self.treeview.grab_focus() def goto_listview(self): """Switch focus to ListView""" self.listview.grab_focus() def goto_editor(self): """Switch focus to Editor""" self.editor.grab_focus() #=========================================== # ui def add_ui(self, window): """Add the view's UI to a window""" assert window == self._main_window self._ui_ready = True self._action_group = gtk.ActionGroup("Viewer") self._uis = [] add_actions(self._action_group, self._get_actions()) self._main_window.get_uimanager().insert_action_group( self._action_group, 0) for s in self._get_ui(): self._uis.append( self._main_window.get_uimanager().add_ui_from_string(s)) uimanager = self._main_window.get_uimanager() uimanager.ensure_update() # setup toolbar self.back_button = uimanager.get_widget("/main_tool_bar/Viewer/Back") self.forward_button = uimanager.get_widget("/main_tool_bar/Viewer/Forward") # setup editor self.editor.add_ui(window) # TODO: Try to add accellerator to popup menu #menu = viewer.editor.get_textview().get_popup_menu() #menu.set_accel_group(self._accel_group) #menu.set_accel_path(CONTEXT_MENU_ACCEL_PATH) # treeview context menu menu1 = uimanager.get_widget( "/popup_menus/treeview_popup").get_submenu() self.treeview.set_popup_menu(menu1) menu1.set_accel_path(CONTEXT_MENU_ACCEL_PATH) menu1.set_accel_group(uimanager.get_accel_group()) # treeview icon menu menu1.iconmenu = self._setup_icon_menu() item = uimanager.get_widget( "/popup_menus/treeview_popup/Change Note Icon") item.set_submenu(menu1.iconmenu) item.show() # treeview fg color menu menu1.fgcolor_menu = self._setup_color_menu("fg") item = uimanager.get_widget( "/popup_menus/treeview_popup/Change Fg Color") item.set_submenu(menu1.fgcolor_menu) item.show() # treeview bg color menu menu1.bgcolor_menu = self._setup_color_menu("bg") item = uimanager.get_widget( "/popup_menus/treeview_popup/Change Bg Color") item.set_submenu(menu1.bgcolor_menu) item.show() # listview context menu menu2 = uimanager.get_widget( "/popup_menus/listview_popup").get_submenu() self.listview.set_popup_menu(menu2) menu2.set_accel_group(uimanager.get_accel_group()) menu2.set_accel_path(CONTEXT_MENU_ACCEL_PATH) # listview icon menu menu2.iconmenu = self._setup_icon_menu() item = uimanager.get_widget( "/popup_menus/listview_popup/Change Note Icon") item.set_submenu(menu2.iconmenu) item.show() # listview fg color menu menu2.fgcolor_menu = self._setup_color_menu("fg") item = uimanager.get_widget( "/popup_menus/listview_popup/Change Fg Color") item.set_submenu(menu2.fgcolor_menu) item.show() # listview bg color menu menu2.bgcolor_menu = self._setup_color_menu("bg") item = uimanager.get_widget( "/popup_menus/listview_popup/Change Bg Color") item.set_submenu(menu2.bgcolor_menu) item.show() def _setup_icon_menu(self): """Setup the icon menu""" iconmenu = IconMenu() iconmenu.connect("set-icon", lambda w, i: self._app.on_set_icon( i, u"", self.get_selected_nodes())) iconmenu.new_icon.connect("activate", lambda w: self._app.on_new_icon( self.get_selected_nodes(), self._notebook, self._main_window)) iconmenu.set_notebook(self._notebook) return iconmenu def _setup_color_menu(self, kind): """Setup the icon menu""" def on_set_color(w, color): for node in self.get_selected_nodes(): if kind == "fg": attr = "title_fgcolor" else: attr = "title_bgcolor" if color: node.set_attr(attr, color) else: node.del_attr(attr) def on_set_colors(w, colors): if self._notebook: self._notebook.pref.set("colors", list(colors)) self._app.get_listeners("colors_changed").notify( self._notebook, colors) def on_new_colors(notebook, colors): if self._notebook == notebook: menu.set_colors(colors) colors = self._notebook.pref.get("colors", default=DEFAULT_COLORS) \ if self._notebook else DEFAULT_COLORS menu = ColorMenu(colors) menu.connect("set-color", on_set_color) menu.connect("set-colors", on_set_colors) self._app.get_listeners("colors_changed").add(on_new_colors) return menu def remove_ui(self, window): """Remove the view's UI from a window""" assert self._main_window == window self._ui_ready = False self.editor.remove_ui(self._main_window) for ui in reversed(self._uis): self._main_window.get_uimanager().remove_ui(ui) self._uis = [] self._main_window.get_uimanager().ensure_update() self._main_window.get_uimanager().remove_action_group(self._action_group) self._action_group = None def _get_ui(self): """Returns the UI XML""" # NOTE: I use a dummy menubar popup_menus so that I can have # accelerators on the menus. It is a hack. return [""" <ui> <menubar name="main_menu_bar"> <menu action="File"> <placeholder name="Viewer"> <menuitem action="New Page"/> <menuitem action="New Child Page"/> <menuitem action="New Folder"/> </placeholder> </menu> <menu action="Edit"> <placeholder name="Viewer"> <menuitem action="Attach File"/> <separator/> <placeholder name="Editor"/> </placeholder> </menu> <placeholder name="Viewer"> <placeholder name="Editor"/> <menu action="View"> <menuitem action="View Note in File Explorer"/> <menuitem action="View Note in Text Editor"/> <menuitem action="View Note in Web Browser"/> <menuitem action="Open File"/> </menu> </placeholder> <menu action="Go"> <placeholder name="Viewer"> <menuitem action="Back"/> <menuitem action="Forward"/> <separator/> <menuitem action="Go to Note"/> <menuitem action="Go to Parent Note"/> <menuitem action="Go to Next Note"/> <menuitem action="Go to Previous Note"/> <menuitem action="Expand Note"/> <menuitem action="Collapse Note"/> <menuitem action="Expand All Child Notes"/> <menuitem action="Collapse All Child Notes"/> <separator/> <menuitem action="Go to Tree View"/> <menuitem action="Go to List View"/> <menuitem action="Go to Editor"/> <placeholder name="Editor"/> </placeholder> </menu> <menu action="Tools"> </menu> </menubar> <toolbar name="main_tool_bar"> <placeholder name="Viewer"> <toolitem action="New Folder"/> <toolitem action="New Page"/> <separator/> <toolitem action="Back"/> <toolitem action="Forward"/> <separator/> <placeholder name="Editor"/> </placeholder> </toolbar> <menubar name="popup_menus"> <menu action="treeview_popup"> <menuitem action="New Page"/> <menuitem action="New Child Page"/> <menuitem action="New Folder"/> <menuitem action="Attach File"/> <placeholder name="New"/> <separator/> <menuitem action="Cut"/> <menuitem action="Copy"/> <menuitem action="Copy Tree"/> <menuitem action="Paste"/> <separator/> <menuitem action="Delete Note"/> <menuitem action="Rename Note"/> <menuitem action="Change Note Icon"/> <menuitem action="Change Fg Color"/> <menuitem action="Change Bg Color"/> <menu action="View Note As"> <menuitem action="View Note in File Explorer"/> <menuitem action="View Note in Text Editor"/> <menuitem action="View Note in Web Browser"/> <menuitem action="Open File"/> </menu> </menu> <menu action="listview_popup"> <menuitem action="Go to Note"/> <menuitem action="Go to Parent Note"/> <separator/> <menuitem action="New Page"/> <menuitem action="New Child Page"/> <menuitem action="New Folder"/> <menuitem action="Attach File"/> <placeholder name="New"/> <separator/> <menuitem action="Cut"/> <menuitem action="Copy"/> <menuitem action="Copy Tree"/> <menuitem action="Paste"/> <separator/> <menuitem action="Delete Note"/> <menuitem action="Rename Note"/> <menuitem action="Change Note Icon"/> <menuitem action="Change Fg Color"/> <menuitem action="Change Bg Color"/> <menu action="View Note As"> <menuitem action="View Note in File Explorer"/> <menuitem action="View Note in Text Editor"/> <menuitem action="View Note in Web Browser"/> <menuitem action="Open File"/> </menu> </menu> </menubar> </ui> """] def _get_actions(self): """Returns actions for view's UI""" return map(lambda x: Action(*x), [ ("treeview_popup", None, "", "", None, lambda w: None), ("listview_popup", None, "", "", None, lambda w: None), ("Copy Tree", gtk.STOCK_COPY, _("Copy _Tree"), "<control><shift>C", _("Copy entire tree"), lambda w: self.on_copy_tree()), ("New Page", gtk.STOCK_NEW, _("New _Page"), "<control>N", _("Create a new page"), lambda w: self.on_new_page(), "note-new.png"), ("New Child Page", gtk.STOCK_NEW, _("New _Child Page"), "<control><shift>N", _("Create a new child page"), lambda w: self.on_new_child_page(), "note-new.png"), ("New Folder", gtk.STOCK_DIRECTORY, _("New _Folder"), "<control><shift>M", _("Create a new folder"), lambda w: self.on_new_dir(), "folder-new.png"), ("Attach File", gtk.STOCK_ADD, _("_Attach File..."), "", _("Attach a file to the notebook"), lambda w: self._on_attach_file_menu()), ("Back", gtk.STOCK_GO_BACK, _("_Back"), "", None, lambda w: self.visit_history(-1)), ("Forward", gtk.STOCK_GO_FORWARD, _("_Forward"), "", None, lambda w: self.visit_history(1)), ("Go to Note", gtk.STOCK_JUMP_TO, _("Go to _Note"), "", None, lambda w: self.on_goto_node(None, None)), ("Go to Parent Note", gtk.STOCK_GO_BACK, _("Go to _Parent Note"), "<shift><alt>Left", None, lambda w: self.on_goto_parent_node()), ("Go to Next Note", gtk.STOCK_GO_DOWN, _("Go to Next N_ote"), "<alt>Down", None, lambda w: self.goto_next_node()), ("Go to Previous Note", gtk.STOCK_GO_UP, _("Go to _Previous Note"), "<alt>Up", None, lambda w: self.goto_prev_node()), ("Expand Note", gtk.STOCK_ADD, _("E_xpand Note"), "<alt>Right", None, lambda w: self.expand_node()), ("Collapse Note", gtk.STOCK_REMOVE, _("_Collapse Note"), "<alt>Left", None, lambda w: self.collapse_node()), ("Expand All Child Notes", gtk.STOCK_ADD, _("Expand _All Child Notes"), "<shift><alt>Right", None, lambda w: self.expand_node(True)), ("Collapse All Child Notes", gtk.STOCK_REMOVE, _("Collapse A_ll Child Notes"), "<shift><alt>Left", None, lambda w: self.collapse_node(True)), ("Go to Tree View", None, _("Go to _Tree View"), "<control>T", None, lambda w: self.goto_treeview()), ("Go to List View", None, _("Go to _List View"), "<control>Y", None, lambda w: self.goto_listview()), ("Go to Editor", None, _("Go to _Editor"), "<control>D", None, lambda w: self.goto_editor()), ("Delete Note", gtk.STOCK_DELETE, _("_Delete"), "", None, self.on_delete_node), ("Rename Note", gtk.STOCK_EDIT, _("_Rename"), "", None, lambda w: self._on_rename_node()), ("Change Note Icon", None, _("_Change Note Icon"), "", None, lambda w: None, lookup_icon_filename(None, u"folder-red.png")), ("Change Fg Color", None, _("Change _Fg Color")), ("Change Bg Color", None, _("Change _Bg Color")), ])
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-node", self._on_edit_node) 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-node", self._on_edit_node) 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_nodes([]) 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) colors = (self._notebook.pref.get("colors", default=DEFAULT_COLORS) if self._notebook else DEFAULT_COLORS) self.treeview.get_popup_menu().fgcolor_menu.set_colors(colors) self.treeview.get_popup_menu().bgcolor_menu.set_colors(colors) self.listview.get_popup_menu().fgcolor_menu.set_colors(colors) self.listview.get_popup_menu().bgcolor_menu.set_colors(colors) # 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.load_preferences(app_pref, first_open) 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.listview.save_preferences(app_pref) self.editor.save_preferences(app_pref) def save(self): """Save the current notebook""" self.listview.save() self.editor.save() self._save_selections() def on_notebook_node_changed(self, nodes): """Callback for when notebook node is changed""" 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): """Returns node editor""" return self.editor.get_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_node(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)