def on_button_press_event(_, event): """Callback handler only used for mouse clicks.""" if event.button != 3: return menu = PopupMenu() menu.simple_add_checkbox('Pin file', None) menu.simple_add('Show History', None) menu.simple_add_separator() menu.simple_add('Import file', None) menu.simple_add('Export file', None) menu.simple_add('Remove', None) menu.simple_add('New Folder', None) menu.simple_add_separator() menu.simple_add('Open in file browser', None) menu.simple_popup(event)
class PathTreeView(Gtk.TreeView): """A GtkTreeView that is readily configured for using PathTreeModel""" def __init__(self): Gtk.TreeView.__init__(self) # Enable separator lines: self.set_grid_lines(Gtk.TreeViewGridLines.NONE) self.set_enable_tree_lines(True) # Make selecting multiple rows possible: self.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE) # Small spedup: self.set_fixed_height_mode(True) # Enable querying of the tooltip column: self.set_tooltip_column(Column.TOOLTIP) # Configure the column rendering: self.append_column(_create_column( 'Path', Column.PATH, [ (CellRendererLint(), False, False, dict(tag=Column.TAG)), (Gtk.CellRendererText(), False, True, dict(text=Column.PATH)), ], 240 )) self.append_column(_create_column( 'Size', Column.SIZE, [(CellRendererSize(), True, False, dict(size=Column.SIZE))], 70 )) self.append_column(_create_column( 'Count', Column.COUNT, [(CellRendererCount(), True, False, dict(count=Column.COUNT))], 90 )) self.append_column( _create_column( 'Changed', Column.MTIME, [ (CellRendererModifiedTime(), True, False, dict( mtime=Column.MTIME))], 100)) self.connect( 'button-press-event', PathTreeView.on_button_press_event ) # Shut up, pylint. self._menu = None def set_model(self, model): """Overwrite Gtk.TreeView.set_model, but expand sub root paths""" Gtk.TreeView.set_model(self, model) self.expand_all() def get_selected_nodes(self): """Extra convinience method for getting the currently selected nodes""" model, rows = self.get_selection().get_selected_rows() for tp_path in rows: node = model.trie.resolve(tp_path.get_indices()) yield node def get_selected_node(self): """Return the first selected node or None.""" try: return next(self.get_selected_nodes()) except StopIteration: return None def on_button_press_event(self, event): """Callback handler only used for mouse clicks.""" if event.button != 3: return menu = self.on_show_menu() if menu: menu.simple_popup(event) ####################### # MENU ENTRY HANDLING # ####################### def on_show_menu(self): """Called during the button-press-event to show the actual menu.""" # HACK: bind to self, since the ref would get lost. self._menu = PopupMenu() self._menu.simple_add('Toggle all', self.on_toggle_all) self._menu.simple_add('Toggle selected', self.on_toggle_selected) self._menu.simple_add_separator() self._menu.simple_add('Expand all', self.on_expand_all) self._menu.simple_add('Collapse all', self.on_collapse_all) self._menu.simple_add_separator() self._menu.simple_add('Open item', self.on_open_folder) self._menu.simple_add( 'Copy path to clipboard', self.on_copy_to_clipboard ) return self._menu def on_open_folder(self, _): """Open the selected item or folder via xdg-open.""" node = self.get_selected_node() if node is None: return try: LOGGER.info('Calling xdg-open %s', node.build_path()) Gio.Subprocess.new( ['xdg-open', node.build_path()], 0 ) except GLib.Error as err: LOGGER.exception('Could not open directory via xdg-open') def on_copy_to_clipboard(self, _): """Copy the currently selected full path to the clipboard.""" node = self.get_selected_node() if node is None: return path = node.build_path() clipboard = Gtk.Clipboard.get_default(Gdk.Display.get_default()) clipboard.set_text(path, len(path)) def _toggle_tag_state(self, node_iter): """Iterate over `node_iter` and toggle the `tag` state.""" model = self.get_model() for node in node_iter: current, new = node[Column.TAG], NodeState.NONE if current is NodeState.DUPLICATE: new = NodeState.ORIGINAL elif current is NodeState.ORIGINAL: new = NodeState.DUPLICATE self.update_node(node, Column.TAG, new) def on_toggle_all(self, _): """Toggle all nodes in the current visible model.""" model = self.get_model() self._toggle_tag_state(model.trie) def on_toggle_selected(self, _): """Toggle all selected nodes in the current visible model.""" nodes = list(self.get_selected_nodes()) self._toggle_tag_state(nodes) # Note: this is the *full* trie. model = self.get_model() trie = model.trie for node in nodes: # Json documents with all related twins: group = trie.group(node[Column.CKSUM]) if not group: continue # List of PathNodes which are twins: has_original = False for twin_node in group: if twin_node[Column.TAG] is NodeState.ORIGINAL: has_original = True break # All good: if has_original: continue # No original in group, set first twin as original. for twin_node in group: if twin_node is not node: self.update_node(twin_node, Column.TAG, NodeState.ORIGINAL) break def on_expand_all(self, _): """Just expand everything in the tree.""" self.expand_all() def on_collapse_all(self, _): """Just collpase everything in the tree.""" self.collapse_all() def set_twin(self, view): self.twin_view = view def update_node(self, node, column_id, value): main_model, twin_model = self.get_model(), self.twin_view.get_model() for model in [m for m in (main_model, twin_model) if m]: model.trie.update_node(node, column_id, value) for child in model.trie.group(node[Column.CKSUM]): model.mark_for_update(child)
class PathTreeView(Gtk.TreeView): """A GtkTreeView that is readily configured for using PathTreeModel""" def __init__(self): Gtk.TreeView.__init__(self) # Enable separator lines: self.set_grid_lines(Gtk.TreeViewGridLines.NONE) self.set_enable_tree_lines(True) # Make selecting multiple rows possible: self.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE) # Small spedup: self.set_fixed_height_mode(True) # Enable querying of the tooltip column: self.set_tooltip_column(Column.TOOLTIP) # Configure the column rendering: self.append_column(_create_column( 'Path', Column.PATH, [ (CellRendererLint(), False, False, dict(tag=Column.TAG)), (Gtk.CellRendererText(), False, True, dict(text=Column.PATH)), ], 240 )) self.append_column(_create_column( 'Size', Column.SIZE, [(CellRendererSize(), True, False, dict(size=Column.SIZE))], 70 )) self.append_column(_create_column( 'Count', Column.COUNT, [(CellRendererCount(), True, False, dict(count=Column.COUNT))], 90 )) self.append_column( _create_column( 'Changed', Column.MTIME, [ (CellRendererModifiedTime(), True, False, dict( mtime=Column.MTIME))], 100)) self.connect( 'button-press-event', PathTreeView.on_button_press_event ) # Shut up, pylint. self._menu = None def set_model(self, model): """Overwrite Gtk.TreeView.set_model, but expand sub root paths""" Gtk.TreeView.set_model(self, model) self.expand_all() def get_selected_nodes(self): """Extra convinience method for getting the currently selected nodes""" model, rows = self.get_selection().get_selected_rows() for tp_path in rows: node = model.trie.resolve(tp_path.get_indices()) yield node def get_selected_node(self): """Return thefirst selected node or None.""" try: return next(self.get_selected_nodes()) except StopIteration: return None def on_button_press_event(self, event): """Callback handler only used for mouse clicks.""" if event.button != 3: return menu = self.on_show_menu() if menu: menu.simple_popup(event) ####################### # MENU ENTRY HANDLING # ####################### def on_show_menu(self): """Called during the button-press-event to show the actual menu.""" # HACK: bind to self, since the ref would get lost. self._menu = PopupMenu() self._menu.simple_add('Toggle all', self.on_toggle_all) self._menu.simple_add('Toggle selected', self.on_toggle_selected) self._menu.simple_add_separator() self._menu.simple_add('Expand all', self.on_expand_all) self._menu.simple_add('Collapse all', self.on_collapse_all) self._menu.simple_add_separator() self._menu.simple_add('Open item', self.on_open_folder) self._menu.simple_add( 'Copy path to clipboard', self.on_copy_to_clipboard ) return self._menu def on_open_folder(self, _): """Open the selected item or folder via xdg-open.""" node = self.get_selected_node() if node is None: return try: LOGGER.info('Calling xdg-open %s', node.build_path()) Gio.Subprocess.new( ['xdg-open', node.build_path()], 0 ) except GLib.Error as err: LOGGER.exception('Could not open directory via xdg-open') def on_copy_to_clipboard(self, _): """Copy the currently selected full path to the clipboard.""" node = self.get_selected_node() if node is None: return path = node.build_path() clipboard = Gtk.Clipboard.get_default(Gdk.Display.get_default()) clipboard.set_text(path, len(path)) def _toggle_tag_state(self, node_iter): """Iterate over `node_iter` and toggle the `tag` state.""" model = self.get_model() for node in node_iter: current, new = node[Column.TAG], NodeState.NONE if current is NodeState.DUPLICATE: new = NodeState.ORIGINAL elif current is NodeState.ORIGINAL: new = NodeState.DUPLICATE self.update_node(node, Column.TAG, new) def on_toggle_all(self, _): """Toggle all nodes in the current visible model.""" model = self.get_model() self._toggle_tag_state(model.trie) def on_toggle_selected(self, _): """Toggle all selected nodes in the current visible model.""" nodes = list(self.get_selected_nodes()) self._toggle_tag_state(nodes) # Note: this is the *full* trie. model = self.get_model() trie = model.trie for node in nodes: # Json documents with all related twins: group = trie.group(node[Column.CKSUM]) if not group: continue # List of PathNodes which are twins: has_original = False for twin_node in group: if twin_node[Column.TAG] is NodeState.ORIGINAL: has_original = True break # All good: if has_original: continue # No original in group, set first twin as original. for twin_node in group: if twin_node is not node: self.update_node(twin_node, Column.TAG, NodeState.ORIGINAL) break def on_expand_all(self, _): """Just expand everything in the tree.""" self.expand_all() def on_collapse_all(self, _): """Just collpase everything in the tree.""" self.collapse_all() def set_twin(self, view): self.twin_view = view def update_node(self, node, column_id, value): main_model, twin_model = self.get_model(), self.twin_view.get_model() for model in [m for m in (main_model, twin_model) if m]: model.trie.update_node(node, column_id, value) for child in model.trie.group(node[Column.CKSUM]): model.mark_for_update(child)