From d1772d410235592b482e3b08b1863f6624d9fe6b Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 19 Feb 2023 15:52:21 +0100 Subject: Adding upstream version 2.0.3. Signed-off-by: Daniel Baumann --- deluge/ui/gtk3/filtertreeview.py | 378 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 378 insertions(+) create mode 100644 deluge/ui/gtk3/filtertreeview.py (limited to 'deluge/ui/gtk3/filtertreeview.py') diff --git a/deluge/ui/gtk3/filtertreeview.py b/deluge/ui/gtk3/filtertreeview.py new file mode 100644 index 0000000..bd781e0 --- /dev/null +++ b/deluge/ui/gtk3/filtertreeview.py @@ -0,0 +1,378 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2008 Martijn Voncken +# 2008 Andrew Resch +# 2014 Calum Lind +# +# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with +# the additional special exception to link portions of this program with the OpenSSL library. +# See LICENSE for more details. +# + +from __future__ import unicode_literals + +import logging +import os +import warnings + +from gi.repository import Gtk +from gi.repository.GdkPixbuf import Pixbuf +from gi.repository.Pango import EllipsizeMode + +import deluge.component as component +from deluge.common import TORRENT_STATE, decode_bytes, resource_filename +from deluge.configmanager import ConfigManager +from deluge.ui.client import client + +from .common import get_pixbuf, get_pixbuf_at_size + +log = logging.getLogger(__name__) + +STATE_PIX = { + 'All': 'all', + 'Downloading': 'downloading', + 'Seeding': 'seeding', + 'Paused': 'inactive', + 'Checking': 'checking', + 'Queued': 'queued', + 'Error': 'alert', + 'Active': 'active', + 'Allocating': 'checking', + 'Moving': 'checking', +} + +TRACKER_PIX = {'All': 'tracker_all', 'Error': 'tracker_warning'} + +FILTER_COLUMN = 5 + + +class FilterTreeView(component.Component): + def __init__(self): + component.Component.__init__(self, 'FilterTreeView', interval=2) + self.config = ConfigManager('gtk3ui.conf') + + self.tracker_icons = component.get('TrackerIcons') + + self.sidebar = component.get('SideBar') + self.treeview = Gtk.TreeView() + self.sidebar.add_tab(self.treeview, 'filters', 'Filters') + + # set filter to all when hidden: + self.sidebar.notebook.connect('hide', self._on_hide) + + # Create the treestore + # cat, value, label, count, pixmap, visible + self.treestore = Gtk.TreeStore(str, str, str, int, Pixbuf, bool) + + # Create the column and cells + column = Gtk.TreeViewColumn('Filters') + column.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) + # icon cell + self.cell_pix = Gtk.CellRendererPixbuf() + column.pack_start(self.cell_pix, expand=False) + column.add_attribute(self.cell_pix, 'pixbuf', 4) + # label cell + cell_label = Gtk.CellRendererText() + cell_label.set_property('ellipsize', EllipsizeMode.END) + column.pack_start(cell_label, expand=True) + column.set_cell_data_func(cell_label, self.render_cell_data, None) + # count cell + self.cell_count = Gtk.CellRendererText() + self.cell_count.set_property('xalign', 1.0) + self.cell_count.set_padding(3, 0) + column.pack_start(self.cell_count, expand=False) + + self.treeview.append_column(column) + + # Style + self.treeview.set_show_expanders(True) + self.treeview.set_headers_visible(False) + self.treeview.set_level_indentation(-21) + # Force theme to use expander-size so we don't cut out entries due to indentation hack. + Gtk.rc_parse_string( + """style "treeview-style" {GtkTreeView::expander-size = 7} + class "GtkTreeView" style "treeview-style" """ + ) + + self.treeview.set_model(self.treestore) + self.treeview.get_selection().connect('changed', self.on_selection_changed) + self.create_model_filter() + + self.treeview.connect('button-press-event', self.on_button_press_event) + + # colors using current theme. + style_ctx = component.get('MainWindow').window.get_style_context() + self.colour_background = style_ctx.get_background_color(Gtk.StateFlags.NORMAL) + self.colour_foreground = style_ctx.get_color(Gtk.StateFlags.NORMAL) + + # filtertree menu + builder = Gtk.Builder() + builder.add_from_file( + resource_filename(__package__, os.path.join('glade', 'filtertree_menu.ui')) + ) + self.menu = builder.get_object('filtertree_menu') + builder.connect_signals(self) + + self.default_menu_items = self.menu.get_children() + + # add Cat nodes: + self.cat_nodes = {} + self.filters = {} + + def start(self): + self.cat_nodes = {} + self.filters = {} + # initial order of state filter: + self.cat_nodes['state'] = self.treestore.append( + None, ['cat', 'state', _('States'), 0, None, False] + ) + for state in ['All', 'Active'] + TORRENT_STATE: + self.update_row('state', state, 0, _(state)) + + self.cat_nodes['tracker_host'] = self.treestore.append( + None, ['cat', 'tracker_host', _('Trackers'), 0, None, False] + ) + self.update_row('tracker_host', 'All', 0, _('All')) + self.update_row('tracker_host', 'Error', 0, _('Error')) + self.update_row('tracker_host', '', 0, _('None')) + + self.cat_nodes['owner'] = self.treestore.append( + None, ['cat', 'owner', _('Owner'), 0, None, False] + ) + self.update_row('owner', 'localclient', 0, _('Admin')) + self.update_row('owner', '', 0, _('None')) + + # We set to this expand the rows on start-up + self.expand_rows = True + + self.selected_path = None + + def stop(self): + self.treestore.clear() + + def create_model_filter(self): + self.model_filter = self.treestore.filter_new() + self.model_filter.set_visible_column(FILTER_COLUMN) + self.treeview.set_model(self.model_filter) + + def cb_update_filter_tree(self, filter_items): + # create missing cat_nodes + for cat in filter_items: + if cat not in self.cat_nodes: + label = _(cat) + if cat == 'label': + label = _('Labels') + self.cat_nodes[cat] = self.treestore.append( + None, ['cat', cat, label, 0, None, False] + ) + + # update rows + visible_filters = [] + for cat, filters in filter_items.items(): + for value, count in filters: + self.update_row(cat, value, count) + visible_filters.append((cat, value)) + + # hide root-categories not returned by core-part of the plugin. + for cat in self.cat_nodes: + self.treestore.set_value( + self.cat_nodes[cat], + FILTER_COLUMN, + True if cat in filter_items else False, + ) + + # hide items not returned by core-plugin. + for f in self.filters: + if f not in visible_filters: + self.treestore.set_value(self.filters[f], FILTER_COLUMN, False) + + if self.expand_rows: + self.treeview.expand_all() + self.expand_rows = False + + if not self.selected_path: + self.select_default_filter() + + def update_row(self, cat, value, count, label=None): + def on_get_icon(icon): + if icon: + self.set_row_image(cat, value, icon.get_filename()) + + if (cat, value) in self.filters: + row = self.filters[(cat, value)] + self.treestore.set_value(row, 3, count) + else: + pix = self.get_pixmap(cat, value) + + if value == '': + if cat == 'label': + label = _('No Label') + elif cat == 'owner': + label = _('No Owner') + elif not label and value: + label = _(value) + + row = self.treestore.append( + self.cat_nodes[cat], [cat, value, label, count, pix, True] + ) + self.filters[(cat, value)] = row + + if cat == 'tracker_host' and value not in ('All', 'Error') and value: + d = self.tracker_icons.fetch(value) + d.addCallback(on_get_icon) + + self.treestore.set_value(row, FILTER_COLUMN, True) + return row + + def render_cell_data(self, column, cell, model, row, data): + cat = model.get_value(row, 0) + label = decode_bytes(model.get_value(row, 2)) + count = model.get_value(row, 3) + + # Supress Warning: g_object_set_qdata: assertion `G_IS_OBJECT (object)' failed + original_filters = warnings.filters[:] + warnings.simplefilter('ignore') + try: + pix = model.get_value(row, 4) + finally: + warnings.filters = original_filters + + self.cell_pix.set_property('visible', True if pix else False) + + if cat == 'cat': + self.cell_count.set_property('visible', False) + cell.set_padding(10, 2) + label = '%s' % label + else: + count_txt = '%s' % count + self.cell_count.set_property('markup', count_txt) + self.cell_count.set_property('visible', True) + cell.set_padding(2, 1) + cell.set_property('markup', label) + + def get_pixmap(self, cat, value): + pix = None + if cat == 'state': + pix = STATE_PIX.get(value, None) + elif cat == 'tracker_host': + pix = TRACKER_PIX.get(value, None) + + if pix: + return get_pixbuf('%s16.png' % pix) + + def set_row_image(self, cat, value, filename): + pix = get_pixbuf_at_size(filename, 16) + row = self.filters[(cat, value)] + self.treestore.set_value(row, 4, pix) + return False + + def on_selection_changed(self, selection): + try: + (model, row) = self.treeview.get_selection().get_selected() + if not row: + log.debug('nothing selected') + return + + cat = model.get_value(row, 0) + value = model.get_value(row, 1) + + filter_dict = {cat: [value]} + if value == 'All' or cat == 'cat': + filter_dict = {} + + component.get('TorrentView').set_filter(filter_dict) + + self.selected_path = model.get_path(row) + + except Exception as ex: + log.debug(ex) + # paths is likely None .. so lets return None + return None + + def update(self): + try: + hide_cat = [] + if not self.config['sidebar_show_trackers']: + hide_cat.append('tracker_host') + if not self.config['sidebar_show_owners']: + hide_cat.append('owner') + client.core.get_filter_tree( + self.config['sidebar_show_zero'], hide_cat + ).addCallback(self.cb_update_filter_tree) + except Exception as ex: + log.debug(ex) + + # Callbacks # + def on_button_press_event(self, widget, event): + """This is a callback for showing the right-click context menu.""" + x, y = event.get_coords() + path = self.treeview.get_path_at_pos(int(x), int(y)) + if not path: + return + path = path[0] + cat = self.model_filter[path][0] + + if event.button == 1: + # Prevent selecting a category label + if cat == 'cat': + if self.treeview.row_expanded(path): + self.treeview.collapse_row(path) + else: + self.treeview.expand_row(path, False) + if not self.selected_path: + self.select_default_filter() + else: + self.treeview.get_selection().select_path(self.selected_path) + return True + + elif event.button == 3: + # assign current cat, value to self: + x, y = event.get_coords() + path = self.treeview.get_path_at_pos(int(x), int(y)) + if not path: + return + row = self.model_filter.get_iter(path[0]) + self.cat = self.model_filter.get_value(row, 0) + self.value = self.model_filter.get_value(row, 1) + self.count = self.model_filter.get_value(row, 3) + + # Show the pop-up menu + self.set_menu_sensitivity() + self.menu.hide() + self.menu.popup(None, None, None, None, event.button, event.time) + self.menu.show() + + if cat == 'cat': + # Do not select the row + return True + + def set_menu_sensitivity(self): + # select-all/pause/resume + sensitive = self.cat != 'cat' and self.count != 0 + for item in self.default_menu_items: + item.set_sensitive(sensitive) + + def select_all(self): + """For use in popup menu.""" + component.get('TorrentView').treeview.get_selection().select_all() + + def on_select_all(self, event): + self.select_all() + + def on_pause_all(self, event): + self.select_all() + func = getattr(component.get('MenuBar'), 'on_menuitem_%s_activate' % 'pause') + func(event) + + def on_resume_all(self, event): + self.select_all() + func = getattr(component.get('MenuBar'), 'on_menuitem_%s_activate' % 'resume') + func(event) + + def _on_hide(self, *args): + self.select_default_filter() + + def select_default_filter(self): + row = self.filters[('state', 'All')] + path = self.treestore.get_path(row) + self.treeview.get_selection().select_path(path) -- cgit v1.2.3