class SidebarExt(object): # Section: Initialization def __init__(self, plugin): self._plugin = plugin self._filterview = deluge.component.get("FilterTreeView") self._state = \ self._plugin.config["daemon"][self._plugin.daemon]["sidebar_state"] self._store = None self._tree = None self._menu = None self._dnd_src_proxy = None self._dnd_dest_proxy = None self._handlers = [] try: self._store = plugin.store.copy() if __debug__: RT.register(self._store, __name__) log.debug("Setting up widgets...") self._create_label_tree() log.debug("Installing widgets...") self._install_label_tree() self._register_handlers() self._enable_dnd() log.debug("Loading state...") self._load_state() self._scroll_to_nearest_id(self._state["selected"]) log.debug("Creating menu...") self._create_menu() self._plugin.register_update_func(self.update_store) except: self.unload() raise def _register_handlers(self): self._register_handler(self._filterview.sidebar.notebook, "switch-page", self._on_switch_page) # Section: Deinitialization def unload(self): self._plugin.deregister_update_func(self.update_store) self._disable_dnd() self._deregister_handlers() self._uninstall_label_tree() self._destroy_label_tree() self._destroy_menu() self._destroy_store() self._plugin.config.save() def _deregister_handlers(self): for widget, handle in self._handlers: if widget.handler_is_connected(handle): widget.disconnect(handle) def _destroy_store(self): if self._store: self._store.destroy() self._store = None # Section: Public def is_active_page(self): cur_page = self._filterview.sidebar.notebook.get_current_page() page = self._filterview.sidebar.notebook.page_num(self._tree.get_parent()) return cur_page == page def select_labels(self, ids): selection = self._tree.get_selection() selection.handler_block_by_func(self._on_selection_changed) selection.unselect_all() if ids: path = self._scroll_to_nearest_id(ids) if path: self._tree.set_cursor(path) for id in ids: self._select_label(id) selection.handler_unblock_by_func(self._on_selection_changed) if not self.is_active_page(): self._make_active_page() else: selection.emit("changed") def get_selected_labels(self): return list(self._state["selected"]) # Section: Public: Update def update_store(self, store): def restore_adjustment(value): adj = self._tree.parent.get_vadjustment() upper = adj.get_upper() - adj.get_page_size() if value > upper: value = upper adj.set_value(value) self._store.destroy() self._store = store.copy() if __debug__: RT.register(self._store, __name__) value = self._tree.parent.get_vadjustment().get_value() gobject.idle_add(restore_adjustment, value) if self._tree.window: self._tree.window.freeze_updates() gobject.idle_add(self._tree.window.thaw_updates) selection = self._tree.get_selection() selection.handler_block_by_func(self._on_selection_changed) self._tree.set_model(self._store.model) self._load_state() selection.handler_unblock_by_func(self._on_selection_changed) # Section: General def _register_handler(self, obj, signal, func, *args, **kwargs): handle = obj.connect(signal, func, *args, **kwargs) self._handlers.append((obj, handle)) def _get_nearest_path(self, paths): def get_dist_from_visible(path): visible = self._tree.get_visible_rect() column = self._tree.get_column(0) rect = self._tree.get_background_area(path, column) if rect.y < 0: dist = -rect.y else: dist = rect.y + rect.height - visible.height return dist if not paths: return None nearest_path = paths[0] nearest_dist = get_dist_from_visible(nearest_path) for path in paths: dist = get_dist_from_visible(path) if dist < nearest_dist: nearest_path = path nearest_dist = dist return nearest_path def _scroll_to_nearest_id(self, ids): paths = [y for y in (self._store.get_model_path(x) for x in ids) if y] path = self._get_nearest_path(paths) if path: parent_path = path[:-1] if parent_path: self._tree.expand_to_path(parent_path) self._tree.scroll_to_cell(path) return path return None # Section: Label Tree def _create_label_tree(self): def render_cell_data(column, cell, model, iter): id, data = model[iter] count = data["count"] if self._plugin.config["common"]["filter_include_sublabels"]: count += data["descendents"]["count"] label_str = "%s (%s)" % (data["name"], count) cell.set_property("text", label_str) def search_func(model, column, key, iter): id, data = model[iter] if data["fullname"].lower().startswith(key.lower()): return False if key.endswith("/"): if data["fullname"].lower() == key[:-1].lower(): self._tree.expand_to_path(model.get_path(iter)) return True tree = gtk.TreeView() column = gtk.TreeViewColumn(DISPLAY_NAME) renderer = gtk.CellRendererText() column.pack_start(renderer, False) column.set_cell_data_func(renderer, render_cell_data) tree.append_column(column) tree.set_name("%s_tree_view" % MODULE_NAME) tree.set_headers_visible(False) tree.set_enable_tree_lines(True) tree.set_search_equal_func(search_func) tree.set_model(self._store.model) tree.get_selection().set_mode(gtk.SELECTION_MULTIPLE) tree.connect("button-press-event", self._on_button_pressed) tree.connect("row-collapsed", self._on_row_collapsed) tree.connect("row-expanded", self._on_row_expanded) tree.get_selection().connect("changed", self._on_selection_changed) self._tree = tree if __debug__: RT.register(tree, __name__) if __debug__: RT.register(column, __name__) if __debug__: RT.register(renderer, __name__) def _install_label_tree(self): self._filterview.sidebar.add_tab(self._tree, MODULE_NAME, DISPLAY_NAME) # Override style so expanders are indented name = self._tree.get_name() path = self._tree.path() rc_string = """ style '%s' { GtkTreeView::indent-expanders = 1 } widget '%s' style '%s' """ % (name, path, name) gtk.rc_parse_string(rc_string) gtk.rc_reset_styles(self._tree.get_toplevel().get_settings()) def _uninstall_label_tree(self): if MODULE_NAME in self._filterview.sidebar.tabs: self._filterview.sidebar.remove_tab(MODULE_NAME) def _destroy_label_tree(self): if self._tree: self._tree.destroy() self._tree = None # Section: Context Menu def _create_menu(self): def on_add(widget): try: dialog = AddLabelDialog(self._plugin, ID_NULL) if __debug__: RT.register(dialog, __name__) dialog.show() except: log.exception("Error initializing AddLabelDialog") pass def on_sublabel(widget): try: id = self._menu.get_title() dialog = AddLabelDialog(self._plugin, id) if __debug__: RT.register(dialog, __name__) dialog.show() except: log.exception("Error initializing AddLabelDialog") pass def on_rename(widget): try: id = self._menu.get_title() dialog = RenameLabelDialog(self._plugin, id) if __debug__: RT.register(dialog, __name__) dialog.show() except: log.exception("Error initializing RenameLabelDialog") pass def on_remove(widget): id = self._menu.get_title() client.labelplus.remove_label(id) def on_option(widget): try: id = self._menu.get_title() dialog = LabelOptionsDialog(self._plugin, id) if __debug__: RT.register(dialog, __name__) dialog.show() except: log.exception("Error initializing LabelOptionsDialog") pass def on_show_menu(widget): self._menu.show_all() id = self._menu.get_title() if id in RESERVED_IDS: for i in range(1, 7): items[i].hide() menu = gtk.Menu() menu.connect("show", on_show_menu) items = labelplus.gtkui.common.gtklib.menu_add_items(menu, 0, ( ((ImageMenuItem, gtk.STOCK_ADD, _("_Add Label")), on_add), ((gtk.SeparatorMenuItem,),), ((ImageMenuItem, gtk.STOCK_ADD, _("Add Sub_label")), on_sublabel), ((ImageMenuItem, gtk.STOCK_EDIT, _("Re_name Label")), on_rename), ((ImageMenuItem, gtk.STOCK_REMOVE, _("_Remove Label")), on_remove), ((gtk.SeparatorMenuItem,),), ((ImageMenuItem, gtk.STOCK_PREFERENCES, _("Label _Options")), on_option), )) self._menu = menu if __debug__: RT.register(self._menu, __name__) def _destroy_menu(self): if self._menu: self._menu.destroy() self._menu = None # Section: Drag and Drop def _enable_dnd(self): # Source Proxy def load_row(widget, path, col, selection, *args): model = widget.get_model() iter_ = model.get_iter(path) path_str = model.get_string_from_iter(iter_) selection.set("TEXT", 8, path_str) return True def get_drag_icon(widget, x, y): return (icon_single, 0, 0) # Destination Proxy def check_dest_id(widget, path, col, pos, selection, *args): model = widget.get_model() id = model[path][LABEL_ID] if id == ID_NONE or self._store.is_user_label(id): return True def receive_ids(widget, path, col, pos, selection, *args): try: torrent_ids = cPickle.loads(selection.data) except: return False model = widget.get_model() id = model[path][LABEL_ID] if id == ID_NONE or self._store.is_user_label(id): log.info("Setting label %r on %r", self._store[id]["fullname"], torrent_ids) client.labelplus.set_torrent_labels(torrent_ids, id) return True def check_dest_row(widget, path, col, pos, selection, *args): model = widget.get_model() id = model[path][LABEL_ID] try: src_path = selection.data src_id = model[src_path][LABEL_ID] except IndexError: return False if (id == src_id or labelplus.common.label.is_ancestor(src_id, id) or not self._store.is_user_label(src_id)): return False if id == ID_NONE: children = self._store.get_descendent_ids(ID_NULL, 1) elif self._store.is_user_label(id): children = self._store[id]["children"] else: return False src_name = self._store[src_id]["name"] for child in children: if child in self._store and self._store[child]["name"] == src_name: return False return True def receive_row(widget, path, col, pos, selection, *args): if not check_dest_row(widget, path, col, pos, selection, *args): return False model = widget.get_model() dest_id = model[path][LABEL_ID] src_path = selection.data src_id = model[src_path][LABEL_ID] src_name = self._store[src_id]["name"] if dest_id != ID_NONE: dest_name = "%s/%s" % (self._store[dest_id]["fullname"], src_name) else: dest_id = ID_NULL dest_name = src_name log.info("Renaming label: %r -> %r", self._store[src_id]["fullname"], dest_name) client.labelplus.move_label(src_id, dest_id, src_name) # Default drag source will delete row on success, so return failure return False icon_single = self._tree.render_icon(gtk.STOCK_DND, gtk.ICON_SIZE_DND) src_target = DragTarget( name="label_row", scope=gtk.TARGET_SAME_APP, action=gtk.gdk.ACTION_MOVE, data_func=load_row, ) self._dnd_src_proxy = TreeViewDragSourceProxy(self._tree, get_drag_icon) self._dnd_src_proxy.add_target(src_target) if __debug__: RT.register(src_target, __name__) if __debug__: RT.register(self._dnd_src_proxy, __name__) ids_target = DragTarget( name="torrent_ids", scope=gtk.TARGET_SAME_APP, action=gtk.gdk.ACTION_MOVE, pos=gtk.TREE_VIEW_DROP_INTO_OR_BEFORE, data_func=receive_ids, aux_func=check_dest_id, ) row_target = DragTarget( name="label_row", scope=gtk.TARGET_SAME_APP, action=gtk.gdk.ACTION_MOVE, pos=gtk.TREE_VIEW_DROP_INTO_OR_BEFORE, data_func=receive_row, aux_func=check_dest_row, ) self._dnd_dest_proxy = TreeViewDragDestProxy(self._tree) self._dnd_dest_proxy.add_target(ids_target) self._dnd_dest_proxy.add_target(row_target) if __debug__: RT.register(ids_target, __name__) if __debug__: RT.register(row_target, __name__) if __debug__: RT.register(self._dnd_dest_proxy, __name__) def _disable_dnd(self): if self._dnd_src_proxy: self._dnd_src_proxy.unload() self._dnd_src_proxy = None if self._dnd_dest_proxy: self._dnd_dest_proxy.unload() self._dnd_dest_proxy = None # Section: Widget State def _load_state(self): if self._plugin.initialized: # Load expanded labels from last session for id in sorted(self._state["expanded"], reverse=True): iter = self._store.get_model_iter(id) if iter and self._store.model.iter_has_child(iter): path = self._store.model.get_path(iter) self._tree.expand_to_path(path) else: self._state["expanded"].remove(id) # Select labels from last session for id in list(self._state["selected"]): if not self._select_label(id): self._state["selected"].remove(id) # Section: Widget Modifiers def _make_active_page(self): page = self._filterview.sidebar.notebook.page_num(self._tree.get_parent()) self._filterview.sidebar.notebook.set_current_page(page) def _select_label(self, id): if id in self._store: path = self._store.get_model_path(id) if path: parent_path = path[:-1] if parent_path: self._tree.expand_to_path(parent_path) self._tree.get_selection().select_path(path) return True return False # Section: Widget Handlers def _on_button_pressed(self, widget, event): x, y = event.get_coords() path_info = widget.get_path_at_pos(int(x), int(y)) if not path_info: return path, column, cell_x, cell_y = path_info id, data = widget.get_model()[path] if event.button == 1 and event.type == gtk.gdk._2BUTTON_PRESS: if self._store.is_user_label(id): try: dialog = LabelOptionsDialog(self._plugin, id) if __debug__: RT.register(dialog, __name__) dialog.show() except: log.exception("Error initializing LabelOptionsDialog") pass elif event.button == 3: self._menu.set_title(id) self._menu.popup(None, None, None, event.button, event.time) return True def _on_row_expanded(self, widget, iter, path): id = widget.get_model()[iter][LABEL_ID] if id not in self._state["expanded"]: self._state["expanded"].append(id) def _on_row_collapsed(self, widget, iter, path): id = widget.get_model()[iter][LABEL_ID] for item in list(self._state["expanded"]): if labelplus.common.label.is_ancestor(id, item): self._state["expanded"].remove(item) if id in self._state["expanded"]: self._state["expanded"].remove(id) def _on_selection_changed(self, widget): ids = [] model, paths = widget.get_selected_rows() if paths: for path in paths: id, data = model[path] ids.append(id) self._state["selected"] = ids if self.is_active_page(): ext = self._plugin.get_extension("TorrentViewExt") if ext and not ext.is_filter(ids): ext.set_filter(ids) # Section: Deluge Handlers def _on_switch_page(self, widget, page, page_num): child = widget.get_nth_page(page_num) if self._tree.is_ancestor(child): gobject.idle_add(self._tree.get_selection().emit, "changed") elif self._filterview.label_view.is_ancestor(child): gobject.idle_add(self._filterview.label_view.get_selection().emit, "changed")
class SidebarExt(object): # Section: Initialization def __init__(self, plugin): self._plugin = plugin self._filterview = deluge.component.get("FilterTreeView") self._state = \ self._plugin.config["daemon"][self._plugin.daemon]["sidebar_state"] self._store = None self._tree = None self._menu = None self._dnd_src_proxy = None self._dnd_dest_proxy = None self._handlers = [] try: self._store = plugin.store.copy() if __debug__: RT.register(self._store, __name__) log.debug("Setting up widgets...") self._create_label_tree() log.debug("Installing widgets...") self._install_label_tree() self._register_handlers() self._enable_dnd() log.debug("Loading state...") self._load_state() self._scroll_to_nearest_id(self._state["selected"]) log.debug("Creating menu...") self._create_menu() self._plugin.register_update_func(self.update_store) except: self.unload() raise def _register_handlers(self): self._register_handler(self._filterview.sidebar.notebook, "switch-page", self._on_switch_page) # Section: Deinitialization def unload(self): self._plugin.deregister_update_func(self.update_store) self._disable_dnd() self._deregister_handlers() self._uninstall_label_tree() self._destroy_label_tree() self._destroy_menu() self._destroy_store() self._plugin.config.save() def _deregister_handlers(self): for widget, handle in self._handlers: if widget.handler_is_connected(handle): widget.disconnect(handle) def _destroy_store(self): if self._store: self._store.destroy() self._store = None # Section: Public def is_active_page(self): cur_page = self._filterview.sidebar.notebook.get_current_page() page = self._filterview.sidebar.notebook.page_num( self._tree.get_parent()) return cur_page == page def select_labels(self, ids): selection = self._tree.get_selection() selection.handler_block_by_func(self._on_selection_changed) selection.unselect_all() if ids: path = self._scroll_to_nearest_id(ids) if path: self._tree.set_cursor(path) for id in ids: self._select_label(id) selection.handler_unblock_by_func(self._on_selection_changed) if not self.is_active_page(): self._make_active_page() else: selection.emit("changed") def get_selected_labels(self): return list(self._state["selected"]) # Section: Public: Update def update_store(self, store): def restore_adjustment(value): adj = self._tree.get_parent().get_vadjustment() upper = adj.get_upper() - adj.get_page_size() if value > upper: value = upper adj.set_value(value) self._store.destroy() self._store = store.copy() if __debug__: RT.register(self._store, __name__) value = self._tree.get_parent().get_vadjustment().get_value() GLib.idle_add(restore_adjustment, value) if self._tree.get_window(): self._tree.get_window().freeze_updates() GLib.idle_add(self._tree.get_window().thaw_updates) selection = self._tree.get_selection() selection.handler_block_by_func(self._on_selection_changed) self._tree.set_model(self._store.model) self._load_state() selection.handler_unblock_by_func(self._on_selection_changed) # Section: General def _register_handler(self, obj, signal, func, *args, **kwargs): handle = obj.connect(signal, func, *args, **kwargs) self._handlers.append((obj, handle)) def _get_nearest_path(self, paths): def get_dist_from_visible(path): visible = self._tree.get_visible_rect() column = self._tree.get_column(0) rect = self._tree.get_background_area(path, column) if rect.y < 0: dist = -rect.y else: dist = rect.y + rect.height - visible.height return dist if not paths: return None nearest_path = paths[0] nearest_dist = get_dist_from_visible(nearest_path) for path in paths: dist = get_dist_from_visible(path) if dist < nearest_dist: nearest_path = path nearest_dist = dist return nearest_path def _scroll_to_nearest_id(self, ids): paths = [y for y in (self._store.get_model_path(x) for x in ids) if y] path = self._get_nearest_path(paths) if path: parent_path = path[:-1] if parent_path: self._tree.expand_to_path( Gtk.TreePath.new_from_indices(parent_path)) self._tree.scroll_to_cell(path) return path return None # Section: Label Tree def _create_label_tree(self): def render_cell_data(column, cell, model, iter, *data): id, data = model[iter] count = data["count"] if self._plugin.config["common"]["filter_include_sublabels"]: count += data["descendents"]["count"] label_str = "%s (%s)" % (data["name"], count) cell.set_property("text", label_str) def search_func(model, column, key, iter): id, data = model[iter] if data["fullname"].lower().startswith(key.lower()): return False if key.endswith("/"): if data["fullname"].lower() == key[:-1].lower(): self._tree.expand_to_path(model.get_path(iter)) return True tree = Gtk.TreeView() column = Gtk.TreeViewColumn(DISPLAY_NAME) renderer = Gtk.CellRendererText() column.pack_start(renderer, False) column.set_cell_data_func(renderer, render_cell_data) tree.append_column(column) tree.set_name("%s_tree_view" % MODULE_NAME) tree.set_headers_visible(False) tree.set_enable_tree_lines(True) tree.set_search_equal_func(search_func) tree.set_model(self._store.model) tree.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE) tree.connect("button-press-event", self._on_button_pressed) tree.connect("row-collapsed", self._on_row_collapsed) tree.connect("row-expanded", self._on_row_expanded) tree.get_selection().connect("changed", self._on_selection_changed) self._tree = tree if __debug__: RT.register(tree, __name__) if __debug__: RT.register(column, __name__) if __debug__: RT.register(renderer, __name__) def _install_label_tree(self): self._filterview.sidebar.add_tab(self._tree, MODULE_NAME, DISPLAY_NAME) # Override style so expanders are indented name = safe_get_name(self._tree) #path = self._tree.get_path() #rc_string = """ # style '%s' { GtkTreeView::indent-expanders = 1 } # widget '%s' style '%s' #""" % (name, path, name) #Gtk.rc_parse_string(rc_string) #Gtk.rc_reset_styles(self._tree.get_toplevel().get_settings()) style_provider = Gtk.CssProvider() style_provider.load_from_data(b""" %s { -GtkTreeView-indent-expanders: true; } """ % name.encode('utf8')) self._tree.get_style_context().add_provider( style_provider, Gtk.STYLE_PROVIDER_PRIORITY_USER) def _uninstall_label_tree(self): if MODULE_NAME in self._filterview.sidebar.tabs: self._filterview.sidebar.remove_tab(MODULE_NAME) def _destroy_label_tree(self): if self._tree: self._tree.destroy() self._tree = None # Section: Context Menu def _create_menu(self): def on_add(widget): try: dialog = AddLabelDialog(self._plugin, ID_NULL) if __debug__: RT.register(dialog, __name__) dialog.show() except: log.exception("Error initializing AddLabelDialog") pass def on_sublabel(widget): try: id = self._menu.get_title() dialog = AddLabelDialog(self._plugin, id) if __debug__: RT.register(dialog, __name__) dialog.show() except: log.exception("Error initializing AddLabelDialog") pass def on_rename(widget): try: id = self._menu.get_title() dialog = RenameLabelDialog(self._plugin, id) if __debug__: RT.register(dialog, __name__) dialog.show() except: log.exception("Error initializing RenameLabelDialog") pass def on_remove(widget): id = self._menu.get_title() client.labelplus.remove_label(id) def on_option(widget): try: id = self._menu.get_title() dialog = LabelOptionsDialog(self._plugin, id) if __debug__: RT.register(dialog, __name__) dialog.show() except: log.exception("Error initializing LabelOptionsDialog") pass def on_show_menu(widget): self._menu.show_all() id = self._menu.get_title() if id in RESERVED_IDS: for i in range(1, 7): items[i].hide() menu = Gtk.Menu() menu.connect("show", on_show_menu) items = labelplus.gtkui.common.gtklib.menu_add_items( menu, 0, ( ((ImageMenuItem, { 'stock_id': 'list-add', 'label': _("_Add Label") }), on_add), ((Gtk.SeparatorMenuItem, ), ), ((ImageMenuItem, { 'stock_id': 'list-add', 'label': _("Add Sub_label") }), on_sublabel), ((ImageMenuItem, { 'stock_id': 'gtk-edit', 'label': _("Re_name Label") }), on_rename), ((ImageMenuItem, { 'stock_id': 'list-remove', 'label': _("_Remove Label") }), on_remove), ((Gtk.SeparatorMenuItem, ), ), ((ImageMenuItem, { 'stock_id': 'preferences-system', 'label': _("Label _Options") }), on_option), )) menu.set_reserve_toggle_size(False) self._menu = menu if __debug__: RT.register(self._menu, __name__) def _destroy_menu(self): if self._menu: self._menu.destroy() self._menu = None # Section: Drag and Drop def _enable_dnd(self): # Source Proxy def load_row(widget, path, col, selection, *args): model = widget.get_model() iter_ = model.get_iter(path) path_str = bytes(model.get_string_from_iter(iter_), encoding='utf8') selection.set(Gdk.Atom.intern("TEXT", False), 8, path_str) return True def get_drag_icon(widget, x, y): return (icon_single, 0, 0) # Destination Proxy def check_dest_id(widget, path, col, pos, selection, *args): model = widget.get_model() id = model[path][LABEL_ID] if id == ID_NONE or self._store.is_user_label(id): return True def receive_ids(widget, path, col, pos, selection, *args): try: torrent_ids = pickle.loads(selection.get_data()) except: return False model = widget.get_model() id = model[path][LABEL_ID] if id == ID_NONE or self._store.is_user_label(id): log.info("Setting label %r on %r", self._store[id]["fullname"], torrent_ids) client.labelplus.set_torrent_labels(torrent_ids, id) return True def check_dest_row(widget, path, col, pos, selection, *args): model = widget.get_model() id = model[path][LABEL_ID] try: src_path = str(selection.get_data(), encoding='utf8') src_id = model[src_path][LABEL_ID] except IndexError: return False if (id == src_id or labelplus.common.label.is_ancestor(src_id, id) or not self._store.is_user_label(src_id)): return False if id == ID_NONE: children = self._store.get_descendent_ids(ID_NULL, 1) elif self._store.is_user_label(id): children = self._store[id]["children"] else: return False src_name = self._store[src_id]["name"] for child in children: if child in self._store and self._store[child][ "name"] == src_name: return False return True def receive_row(widget, path, col, pos, selection, *args): if not check_dest_row(widget, path, col, pos, selection, *args): return False model = widget.get_model() dest_id = model[path][LABEL_ID] src_path = str(selection.get_data(), encoding='utf8') src_id = model[src_path][LABEL_ID] src_name = self._store[src_id]["name"] if dest_id != ID_NONE: dest_name = "%s/%s" % (self._store[dest_id]["fullname"], src_name) else: dest_id = ID_NULL dest_name = src_name log.info("Renaming label: %r -> %r", self._store[src_id]["fullname"], dest_name) client.labelplus.move_label(src_id, dest_id, src_name) # Default drag source will delete row on success, so return failure return False icon_single = Gtk.IconTheme.get_default().load_icon( 'gtk-dnd', Gtk.IconSize.DND, 0) src_target = DragTarget( name="label_row", scope=Gtk.TargetFlags.SAME_APP, action=Gdk.DragAction.MOVE, data_func=load_row, ) self._dnd_src_proxy = TreeViewDragSourceProxy(self._tree, get_drag_icon) self._dnd_src_proxy.add_target(src_target) if __debug__: RT.register(src_target, __name__) if __debug__: RT.register(self._dnd_src_proxy, __name__) ids_target = DragTarget( name="torrent_ids", scope=Gtk.TargetFlags.SAME_APP, action=Gdk.DragAction.MOVE, pos=Gtk.TreeViewDropPosition.INTO_OR_BEFORE, data_func=receive_ids, aux_func=check_dest_id, ) row_target = DragTarget( name="label_row", scope=Gtk.TargetFlags.SAME_APP, action=Gdk.DragAction.MOVE, pos=Gtk.TreeViewDropPosition.INTO_OR_BEFORE, data_func=receive_row, aux_func=check_dest_row, ) self._dnd_dest_proxy = TreeViewDragDestProxy(self._tree) self._dnd_dest_proxy.add_target(ids_target) self._dnd_dest_proxy.add_target(row_target) if __debug__: RT.register(ids_target, __name__) if __debug__: RT.register(row_target, __name__) if __debug__: RT.register(self._dnd_dest_proxy, __name__) def _disable_dnd(self): if self._dnd_src_proxy: self._dnd_src_proxy.unload() self._dnd_src_proxy = None if self._dnd_dest_proxy: self._dnd_dest_proxy.unload() self._dnd_dest_proxy = None # Section: Widget State def _load_state(self): if self._plugin.initialized: # Load expanded labels from last session for id in sorted(self._state["expanded"], reverse=True): iter = self._store.get_model_iter(id) if iter and self._store.model.iter_has_child(iter): path = self._store.model.get_path(iter) self._tree.expand_to_path(path) else: self._state["expanded"].remove(id) # Select labels from last session for id in list(self._state["selected"]): if not self._select_label(id): self._state["selected"].remove(id) # Section: Widget Modifiers def _make_active_page(self): page = self._filterview.sidebar.notebook.page_num( self._tree.get_parent()) self._filterview.sidebar.notebook.set_current_page(page) def _select_label(self, id): if id in self._store: path = self._store.get_model_path(id) if path: parent_path = path[:-1] if parent_path: self._tree.expand_to_path( Gtk.TreePath.new_from_indices(parent_path)) self._tree.get_selection().select_path(path) return True return False # Section: Widget Handlers def _on_button_pressed(self, widget, event): x, y = event.get_coords() path_info = widget.get_path_at_pos(int(x), int(y)) if not path_info: return path, column, cell_x, cell_y = path_info id, data = widget.get_model()[path] if event.button == 1 and event.type == Gdk.EventType._2BUTTON_PRESS: if self._store.is_user_label(id): try: dialog = LabelOptionsDialog(self._plugin, id) if __debug__: RT.register(dialog, __name__) dialog.show() except: log.exception("Error initializing LabelOptionsDialog") pass elif event.button == 3: self._menu.set_title(id) self._menu.popup(None, None, None, None, event.button, event.time) return True def _on_row_expanded(self, widget, iter, path): id = widget.get_model()[iter][LABEL_ID] if id not in self._state["expanded"]: self._state["expanded"].append(id) def _on_row_collapsed(self, widget, iter, path): id = widget.get_model()[iter][LABEL_ID] for item in list(self._state["expanded"]): if labelplus.common.label.is_ancestor(id, item): self._state["expanded"].remove(item) if id in self._state["expanded"]: self._state["expanded"].remove(id) def _on_selection_changed(self, widget): ids = [] model, paths = widget.get_selected_rows() if paths: for path in paths: id, data = model[path] ids.append(id) self._state["selected"] = ids if self.is_active_page(): ext = self._plugin.get_extension("TorrentViewExt") if ext and not ext.is_filter(ids): ext.set_filter(ids) # Section: Deluge Handlers def _on_switch_page(self, widget, page, page_num): child = widget.get_nth_page(page_num) if self._tree.is_ancestor(child): GLib.idle_add(self._tree.get_selection().emit, "changed") elif self._filterview.treeview.is_ancestor(child): GLib.idle_add(self._filterview.treeview.get_selection().emit, "changed")
def _enable_dnd(self): # Source Proxy def load_row(widget, path, col, selection, *args): model = widget.get_model() iter_ = model.get_iter(path) path_str = model.get_string_from_iter(iter_) selection.set("TEXT", 8, path_str) return True def get_drag_icon(widget, x, y): return (icon_single, 0, 0) # Destination Proxy def check_dest_id(widget, path, col, pos, selection, *args): model = widget.get_model() id = model[path][LABEL_ID] if id == ID_NONE or self._store.is_user_label(id): return True def receive_ids(widget, path, col, pos, selection, *args): try: torrent_ids = cPickle.loads(selection.data) except: return False model = widget.get_model() id = model[path][LABEL_ID] if id == ID_NONE or self._store.is_user_label(id): log.info("Setting label %r on %r", self._store[id]["fullname"], torrent_ids) client.labelplus.set_torrent_labels(torrent_ids, id) return True def check_dest_row(widget, path, col, pos, selection, *args): model = widget.get_model() id = model[path][LABEL_ID] try: src_path = selection.data src_id = model[src_path][LABEL_ID] except IndexError: return False if (id == src_id or labelplus.common.label.is_ancestor(src_id, id) or not self._store.is_user_label(src_id)): return False if id == ID_NONE: children = self._store.get_descendent_ids(ID_NULL, 1) elif self._store.is_user_label(id): children = self._store[id]["children"] else: return False src_name = self._store[src_id]["name"] for child in children: if child in self._store and self._store[child]["name"] == src_name: return False return True def receive_row(widget, path, col, pos, selection, *args): if not check_dest_row(widget, path, col, pos, selection, *args): return False model = widget.get_model() dest_id = model[path][LABEL_ID] src_path = selection.data src_id = model[src_path][LABEL_ID] src_name = self._store[src_id]["name"] if dest_id != ID_NONE: dest_name = "%s/%s" % (self._store[dest_id]["fullname"], src_name) else: dest_id = ID_NULL dest_name = src_name log.info("Renaming label: %r -> %r", self._store[src_id]["fullname"], dest_name) client.labelplus.move_label(src_id, dest_id, src_name) # Default drag source will delete row on success, so return failure return False icon_single = self._tree.render_icon(gtk.STOCK_DND, gtk.ICON_SIZE_DND) src_target = DragTarget( name="label_row", scope=gtk.TARGET_SAME_APP, action=gtk.gdk.ACTION_MOVE, data_func=load_row, ) self._dnd_src_proxy = TreeViewDragSourceProxy(self._tree, get_drag_icon) self._dnd_src_proxy.add_target(src_target) if __debug__: RT.register(src_target, __name__) if __debug__: RT.register(self._dnd_src_proxy, __name__) ids_target = DragTarget( name="torrent_ids", scope=gtk.TARGET_SAME_APP, action=gtk.gdk.ACTION_MOVE, pos=gtk.TREE_VIEW_DROP_INTO_OR_BEFORE, data_func=receive_ids, aux_func=check_dest_id, ) row_target = DragTarget( name="label_row", scope=gtk.TARGET_SAME_APP, action=gtk.gdk.ACTION_MOVE, pos=gtk.TREE_VIEW_DROP_INTO_OR_BEFORE, data_func=receive_row, aux_func=check_dest_row, ) self._dnd_dest_proxy = TreeViewDragDestProxy(self._tree) self._dnd_dest_proxy.add_target(ids_target) self._dnd_dest_proxy.add_target(row_target) if __debug__: RT.register(ids_target, __name__) if __debug__: RT.register(row_target, __name__) if __debug__: RT.register(self._dnd_dest_proxy, __name__)
def _enable_dnd(self): # Source Proxy def load_row(widget, path, col, selection, *args): model = widget.get_model() iter_ = model.get_iter(path) path_str = bytes(model.get_string_from_iter(iter_), encoding='utf8') selection.set(Gdk.Atom.intern("TEXT", False), 8, path_str) return True def get_drag_icon(widget, x, y): return (icon_single, 0, 0) # Destination Proxy def check_dest_id(widget, path, col, pos, selection, *args): model = widget.get_model() id = model[path][LABEL_ID] if id == ID_NONE or self._store.is_user_label(id): return True def receive_ids(widget, path, col, pos, selection, *args): try: torrent_ids = pickle.loads(selection.get_data()) except: return False model = widget.get_model() id = model[path][LABEL_ID] if id == ID_NONE or self._store.is_user_label(id): log.info("Setting label %r on %r", self._store[id]["fullname"], torrent_ids) client.labelplus.set_torrent_labels(torrent_ids, id) return True def check_dest_row(widget, path, col, pos, selection, *args): model = widget.get_model() id = model[path][LABEL_ID] try: src_path = str(selection.get_data(), encoding='utf8') src_id = model[src_path][LABEL_ID] except IndexError: return False if (id == src_id or labelplus.common.label.is_ancestor(src_id, id) or not self._store.is_user_label(src_id)): return False if id == ID_NONE: children = self._store.get_descendent_ids(ID_NULL, 1) elif self._store.is_user_label(id): children = self._store[id]["children"] else: return False src_name = self._store[src_id]["name"] for child in children: if child in self._store and self._store[child][ "name"] == src_name: return False return True def receive_row(widget, path, col, pos, selection, *args): if not check_dest_row(widget, path, col, pos, selection, *args): return False model = widget.get_model() dest_id = model[path][LABEL_ID] src_path = str(selection.get_data(), encoding='utf8') src_id = model[src_path][LABEL_ID] src_name = self._store[src_id]["name"] if dest_id != ID_NONE: dest_name = "%s/%s" % (self._store[dest_id]["fullname"], src_name) else: dest_id = ID_NULL dest_name = src_name log.info("Renaming label: %r -> %r", self._store[src_id]["fullname"], dest_name) client.labelplus.move_label(src_id, dest_id, src_name) # Default drag source will delete row on success, so return failure return False icon_single = Gtk.IconTheme.get_default().load_icon( 'gtk-dnd', Gtk.IconSize.DND, 0) src_target = DragTarget( name="label_row", scope=Gtk.TargetFlags.SAME_APP, action=Gdk.DragAction.MOVE, data_func=load_row, ) self._dnd_src_proxy = TreeViewDragSourceProxy(self._tree, get_drag_icon) self._dnd_src_proxy.add_target(src_target) if __debug__: RT.register(src_target, __name__) if __debug__: RT.register(self._dnd_src_proxy, __name__) ids_target = DragTarget( name="torrent_ids", scope=Gtk.TargetFlags.SAME_APP, action=Gdk.DragAction.MOVE, pos=Gtk.TreeViewDropPosition.INTO_OR_BEFORE, data_func=receive_ids, aux_func=check_dest_id, ) row_target = DragTarget( name="label_row", scope=Gtk.TargetFlags.SAME_APP, action=Gdk.DragAction.MOVE, pos=Gtk.TreeViewDropPosition.INTO_OR_BEFORE, data_func=receive_row, aux_func=check_dest_row, ) self._dnd_dest_proxy = TreeViewDragDestProxy(self._tree) self._dnd_dest_proxy.add_target(ids_target) self._dnd_dest_proxy.add_target(row_target) if __debug__: RT.register(ids_target, __name__) if __debug__: RT.register(row_target, __name__) if __debug__: RT.register(self._dnd_dest_proxy, __name__)