From 2e2851dc13d73352530dd4495c7e05603b2e520d Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 10 Apr 2024 23:38:38 +0200 Subject: Adding upstream version 2.1.2~dev0+20240219. Signed-off-by: Daniel Baumann --- deluge/ui/gtk3/statusbar.py | 578 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 578 insertions(+) create mode 100644 deluge/ui/gtk3/statusbar.py (limited to 'deluge/ui/gtk3/statusbar.py') diff --git a/deluge/ui/gtk3/statusbar.py b/deluge/ui/gtk3/statusbar.py new file mode 100644 index 0000000..0a2e800 --- /dev/null +++ b/deluge/ui/gtk3/statusbar.py @@ -0,0 +1,578 @@ +# +# Copyright (C) 2007-2008 Andrew Resch +# +# 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. +# + +import logging + +from gi.repository import Gtk +from gi.repository.GLib import timeout_add + +import deluge.component as component +from deluge.common import fsize, fspeed, get_pixmap +from deluge.configmanager import ConfigManager +from deluge.ui.client import client + +from .common import build_menu_radio_list +from .dialogs import OtherDialog + +log = logging.getLogger(__name__) + + +class StatusBarItem: + def __init__( + self, + image=None, + stock=None, + icon=None, + text=None, + markup=False, + callback=None, + tooltip=None, + ): + self._widgets = [] + self._ebox = Gtk.EventBox() + self._hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, spacing=3) + self._image = Gtk.Image() + self._label = Gtk.Label() + if image or icon or stock: + self._hbox.add(self._image) + self._hbox.add(self._label) + self._ebox.add(self._hbox) + + # Add image from file or stock + if image: + self.set_image_from_file(image) + if stock: + self.set_image_from_stock(stock) + if icon: + self.set_image_from_icon(icon) + + # Add text + if markup: + self.set_markup(text) + else: + self.set_text(text) + + if callback is not None: + self.set_callback(callback) + + if tooltip: + self.set_tooltip(tooltip) + + self.show_all() + + def set_callback(self, callback): + self._ebox.connect('button-press-event', callback) + + def show_all(self): + self._ebox.show() + self._hbox.show() + self._image.show() + + def set_image_from_file(self, image): + self._image.set_from_file(image) + + def set_image_from_stock(self, stock): + self._image.set_from_stock(stock, Gtk.IconSize.MENU) + + def set_image_from_icon(self, icon): + self._image.set_from_icon_name(icon, Gtk.IconSize.MENU) + + def set_text(self, text): + if not text: + self._label.hide() + elif self._label.get_text() != text: + self._label.set_text(text) + self._label.show() + + def set_markup(self, text): + if not text: + self._label.hide() + elif self._label.get_text() != text: + self._label.set_markup(text) + self._label.show() + + def set_tooltip(self, tip): + if self._ebox.get_tooltip_text() != tip: + self._ebox.set_tooltip_text(tip) + + def get_widgets(self): + return self._widgets + + def get_eventbox(self): + return self._ebox + + def get_text(self): + return self._label.get_text() + + +class StatusBar(component.Component): + def __init__(self): + component.Component.__init__(self, 'StatusBar', interval=3) + main_builder = component.get('MainWindow').get_builder() + self.statusbar = main_builder.get_object('statusbar') + self.config = ConfigManager('gtk3ui.conf') + + # Status variables that are updated via callback + self.max_connections_global = -1 + self.num_connections = 0 + self.max_download_speed = -1.0 + self.download_rate = '' + self.max_upload_speed = -1.0 + self.upload_rate = '' + self.dht_nodes = 0 + self.dht_status = False + self.health = False + self.download_protocol_rate = 0.0 + self.upload_protocol_rate = 0.0 + + self.config_value_changed_dict = { + 'max_connections_global': self._on_max_connections_global, + 'max_download_speed': self._on_max_download_speed, + 'max_upload_speed': self._on_max_upload_speed, + 'dht': self._on_dht, + } + self.current_warnings = [] + # Add hbox to the statusbar after removing the initial label widget + self.hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, spacing=10) + self.hbox.set_margin_top(2) + self.hbox.set_margin_bottom(3) + frame = self.statusbar.get_children()[0] + frame.remove(frame.get_children()[0]) + frame.add(self.hbox) + self.statusbar.show_all() + # Create the not connected item + self.not_connected_item = StatusBarItem( + icon='network-offline-symbolic', + text=_('Not Connected'), + callback=self._on_notconnected_item_clicked, + ) + # Show the not connected status bar + self.show_not_connected() + + # Hide if necessary + self.visible(self.config['show_statusbar']) + + client.register_event_handler( + 'ConfigValueChangedEvent', self.on_configvaluechanged_event + ) + + def start(self): + # Add in images and labels + self.remove_item(self.not_connected_item) + + self.connections_item = self.add_item( + icon='network-transmit-receive-symbolic', + callback=self._on_connection_item_clicked, + tooltip=_('Connections (Limit)'), + pack_start=True, + ) + + self.download_item = self.add_item( + image=get_pixmap('downloading16.png'), + callback=self._on_download_item_clicked, + tooltip=_('Download Speed (Limit)'), + pack_start=True, + ) + + self.upload_item = self.add_item( + image=get_pixmap('seeding16.png'), + callback=self._on_upload_item_clicked, + tooltip=_('Upload Speed (Limit)'), + pack_start=True, + ) + + self.traffic_item = self.add_item( + image=get_pixmap('traffic16.png'), + callback=self._on_traffic_item_clicked, + tooltip=_('Protocol Traffic (Down:Up)'), + pack_start=True, + ) + + self.dht_item = StatusBarItem( + image=get_pixmap('dht16.png'), tooltip=_('DHT Nodes') + ) + + self.diskspace_item = self.add_item( + icon='drive-harddisk-symbolic', + callback=self._on_diskspace_item_clicked, + tooltip=_('Free Disk Space'), + pack_start=True, + ) + + self.external_ip_item = self.add_item( + tooltip=_('External IP Address'), + text=_('IP %s') % _('n/a'), + markup=True, + pack_start=True, + ) + + self.health_item = self.add_item( + icon='network-error-symbolic', + text=_('Port Issue'), + markup=True, + tooltip=_('No incoming connections, check port forwarding'), + callback=self._on_health_icon_clicked, + ) + + self.health = False + + def update_config_values(configs): + self._on_max_connections_global(configs['max_connections_global']) + self._on_max_download_speed(configs['max_download_speed']) + self._on_max_upload_speed(configs['max_upload_speed']) + self._on_dht(configs['dht']) + + # Get some config values + client.core.get_config_values( + ['max_connections_global', 'max_download_speed', 'max_upload_speed', 'dht'] + ).addCallback(update_config_values) + + def stop(self): + # When stopped, we just show the not connected thingy + try: + self.remove_item(self.connections_item) + self.remove_item(self.dht_item) + self.remove_item(self.download_item) + self.remove_item(self.upload_item) + self.remove_item(self.not_connected_item) + self.remove_item(self.health_item) + self.remove_item(self.traffic_item) + self.remove_item(self.diskspace_item) + self.remove_item(self.external_ip_item) + except Exception as ex: + log.debug('Unable to remove StatusBar item: %s', ex) + self.show_not_connected() + + def visible(self, visible): + if visible: + self.statusbar.show() + else: + self.statusbar.hide() + + self.config['show_statusbar'] = visible + + def show_not_connected(self): + self.hbox.pack_start(self.not_connected_item.get_eventbox(), False, False, 0) + + def add_item( + self, + image=None, + stock=None, + icon=None, + text=None, + markup=False, + callback=None, + tooltip=None, + pack_start=False, + ): + """Adds an item to the status bar""" + # The return tuple.. we return whatever widgets we add + item = StatusBarItem(image, stock, icon, text, markup, callback, tooltip) + if pack_start: + self.hbox.pack_start(item.get_eventbox(), False, False, 0) + else: + self.hbox.pack_end(item.get_eventbox(), False, False, 0) + return item + + def remove_item(self, item): + """Removes an item from the statusbar""" + if item.get_eventbox() in self.hbox.get_children(): + try: + self.hbox.remove(item.get_eventbox()) + except Exception as ex: + log.debug('Unable to remove widget: %s', ex) + + def add_timeout_item( + self, seconds=3, image=None, stock=None, icon=None, text=None, callback=None + ): + """Adds an item to the StatusBar for seconds""" + item = self.add_item(image, stock, icon, text, callback) + # Start a timer to remove this item in seconds + timeout_add(seconds * 1000, self.remove_item, item) + + def display_warning(self, text, callback=None): + """Displays a warning to the user in the status bar""" + if text not in self.current_warnings: + item = self.add_item( + icon='dialog-warning-symbolic', text=text, callback=callback + ) + self.current_warnings.append(text) + timeout_add(3000, self.remove_warning, item) + + def remove_warning(self, item): + self.current_warnings.remove(item.get_text()) + self.remove_item(item) + + def clear_statusbar(self): + def remove(child): + self.hbox.remove(child) + + self.hbox.foreach(remove) + + def send_status_request(self): + # Sends an async request for data from the core + keys = [ + 'peer.num_peers_connected', + 'upload_rate', + 'download_rate', + 'payload_upload_rate', + 'payload_download_rate', + 'net.sent_bytes', + 'net.recv_bytes', + 'net.sent_payload_bytes', + 'net.recv_payload_bytes', + ] + + if self.dht_status: + keys.append('dht.dht_nodes') + + if not self.health: + keys.append('net.has_incoming_connections') + + client.core.get_session_status(keys).addCallback(self._on_get_session_status) + client.core.get_free_space().addCallback(self._on_get_free_space) + client.core.get_external_ip().addCallback(self._on_get_external_ip) + + def on_configvaluechanged_event(self, key, value): + """ + This is called when we receive a ConfigValueChangedEvent from + the core. + """ + if key in self.config_value_changed_dict: + self.config_value_changed_dict[key](value) + + def _on_max_connections_global(self, max_connections): + self.max_connections_global = max_connections + self.update_connections_label() + + def _on_dht(self, value): + self.dht_status = value + if value: + self.hbox.pack_start(self.dht_item.get_eventbox(), False, False, 0) + self.send_status_request() + else: + self.remove_item(self.dht_item) + + def _on_get_session_status(self, status): + self.download_rate = fspeed( + status['payload_download_rate'], precision=0, shortform=True + ) + self.upload_rate = fspeed( + status['payload_upload_rate'], precision=0, shortform=True + ) + self.download_protocol_rate = ( + status['download_rate'] - status['payload_download_rate'] + ) // 1024 + self.upload_protocol_rate = ( + status['upload_rate'] - status['payload_upload_rate'] + ) // 1024 + self.num_connections = status['peer.num_peers_connected'] + self.update_download_label() + self.update_upload_label() + self.update_traffic_label() + self.update_connections_label() + + if 'dht.dht_nodes' in status: + self.dht_nodes = status['dht.dht_nodes'] + self.update_dht_label() + + if 'net.has_incoming_connections' in status: + self.health = status['net.has_incoming_connections'] + if self.health: + self.remove_item(self.health_item) + + def _on_get_free_space(self, space): + if space >= 0: + self.diskspace_item.set_markup( + '%s' % fsize(space, shortform=True) + ) + else: + self.diskspace_item.set_markup( + '' + _('Error') + '' + ) + + def _on_max_download_speed(self, max_download_speed): + self.max_download_speed = max_download_speed + self.update_download_label() + + def _on_max_upload_speed(self, max_upload_speed): + self.max_upload_speed = max_upload_speed + self.update_upload_label() + + def _on_get_external_ip(self, external_ip): + ip = external_ip if external_ip else _('n/a') + self.external_ip_item.set_markup(_('IP %s') % ip) + + def update_connections_label(self): + # Set the max connections label + if self.max_connections_global < 0: + label_string = '%s' % self.num_connections + else: + label_string = '{} ({})'.format( + self.num_connections, + self.max_connections_global, + ) + + self.connections_item.set_markup(label_string) + + if self.num_connections: + self.connections_item.set_image_from_icon( + 'network-transmit-receive-symbolic' + ) + else: + self.connections_item.set_image_from_icon('network-idle-symbolic') + + def update_dht_label(self): + # Set the max connections label + self.dht_item.set_markup('%s' % (self.dht_nodes)) + + def update_download_label(self): + # Set the download speed label + if self.max_download_speed <= 0: + label_string = self.download_rate + else: + label_string = '%s (%i %s)' % ( + self.download_rate, + self.max_download_speed, + _('K/s'), + ) + + self.download_item.set_markup(label_string) + + def update_upload_label(self): + # Set the upload speed label + if self.max_upload_speed <= 0: + label_string = self.upload_rate + else: + label_string = '%s (%i %s)' % ( + self.upload_rate, + self.max_upload_speed, + _('K/s'), + ) + + self.upload_item.set_markup(label_string) + + def update_traffic_label(self): + label_string = '%i:%i %s' % ( + self.download_protocol_rate, + self.upload_protocol_rate, + _('K/s'), + ) + self.traffic_item.set_markup(label_string) + + def update(self): + self.send_status_request() + + def set_limit_value(self, widget, core_key): + log.debug('_on_set_unlimit_other %s', core_key) + other_dialog_info = { + 'max_download_speed': ( + _('Download Speed Limit'), + _('Set the maximum download speed'), + _('K/s'), + 'downloading.svg', + self.max_download_speed, + ), + 'max_upload_speed': ( + _('Upload Speed Limit'), + _('Set the maximum upload speed'), + _('K/s'), + 'seeding.svg', + self.max_upload_speed, + ), + 'max_connections_global': ( + _('Incoming Connections'), + _('Set the maximum incoming connections'), + '', + 'network-transmit-receive-symbolic', + self.max_connections_global, + ), + } + + def set_value(value): + log.debug('value: %s', value) + if value is None: + return + elif value == 0: + value = -1 + # Set the config in the core + if value != getattr(self, core_key): + client.core.set_config({core_key: value}) + + if widget.get_name() == 'unlimited': + set_value(-1) + elif widget.get_name() == 'other': + + def dialog_finished(response_id): + if response_id == Gtk.ResponseType.OK: + set_value(dialog.get_value()) + + dialog = OtherDialog(*other_dialog_info[core_key]) + dialog.run().addCallback(set_value) + else: + value = widget.get_children()[0].get_text().split(' ')[0] + set_value(value) + + def _on_download_item_clicked(self, widget, event): + self.menu = build_menu_radio_list( + self.config['tray_download_speed_list'], + self._on_set_download_speed, + self.max_download_speed, + _('K/s'), + show_notset=True, + show_other=True, + ) + self.menu.show_all() + self.menu.popup(None, None, None, None, event.button, event.time) + + def _on_set_download_speed(self, widget): + log.debug('_on_set_download_speed') + self.set_limit_value(widget, 'max_download_speed') + + def _on_upload_item_clicked(self, widget, event): + self.menu = build_menu_radio_list( + self.config['tray_upload_speed_list'], + self._on_set_upload_speed, + self.max_upload_speed, + _('K/s'), + show_notset=True, + show_other=True, + ) + self.menu.show_all() + self.menu.popup(None, None, None, None, event.button, event.time) + + def _on_set_upload_speed(self, widget): + log.debug('_on_set_upload_speed') + self.set_limit_value(widget, 'max_upload_speed') + + def _on_connection_item_clicked(self, widget, event): + self.menu = build_menu_radio_list( + self.config['connection_limit_list'], + self._on_set_connection_limit, + self.max_connections_global, + show_notset=True, + show_other=True, + ) + self.menu.show_all() + self.menu.popup(None, None, None, None, event.button, event.time) + + def _on_set_connection_limit(self, widget): + log.debug('_on_set_connection_limit') + self.set_limit_value(widget, 'max_connections_global') + + def _on_health_icon_clicked(self, widget, event): + component.get('Preferences').show('network') + + def _on_notconnected_item_clicked(self, widget, event): + component.get('ConnectionManager').show() + + def _on_traffic_item_clicked(self, widget, event): + component.get('Preferences').show('network') + + def _on_diskspace_item_clicked(self, widget, event): + component.get('Preferences').show('downloads') -- cgit v1.2.3