diff --git a/deluge/ui/gtkui/glade/main_window.glade b/deluge/ui/gtkui/glade/main_window.glade index 43a10c8f0..10459355c 100644 --- a/deluge/ui/gtkui/glade/main_window.glade +++ b/deluge/ui/gtkui/glade/main_window.glade @@ -781,6 +781,152 @@ + + True + False + + + gtk-open + True + False + False + True + True + + + + + + True + False + + + + + _Expand All + True + False + False + True + False + + + + True + False + gtk-zoom-fit + 1 + + + + + + + True + False + + + + + _Do Not Download + True + False + False + True + False + + + + True + False + gtk-no + 1 + + + + + + + _Normal Priority + True + False + False + True + False + + + + True + False + gtk-yes + 1 + + + + + + + _High Priority + True + False + False + True + False + + + + True + False + gtk-go-up + 1 + + + + + + + Hi_ghest Priority + True + False + False + True + False + + + + True + False + gtk-goto-top + 1 + + + + + + + True + False + + + _Add Peer + True + False + Add a peer by its IP + False + True + False + + + + True + False + gtk-add + 1 + + + + + False GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -1255,15 +1401,29 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 - + True False - True - 0.10000000149 + + + True + False + True + 0.10000000149 + + + False + False + 0 + + + + + False - False + True 0 diff --git a/deluge/ui/gtkui/glade/preferences_dialog.glade b/deluge/ui/gtkui/glade/preferences_dialog.glade index 9c41900ff..6f1b7c7ac 100644 --- a/deluge/ui/gtkui/glade/preferences_dialog.glade +++ b/deluge/ui/gtkui/glade/preferences_dialog.glade @@ -2249,11 +2249,13 @@ Use at your own risk if you wish to help us debug this new feature. False True + True False - Show pieces bar instead of the progress bar (<b>EXPERIMENTAL!!!</b>) + Show a pieces bar in the torrent's +status tab (<b>EXPERIMENTAL!!!</b>) True @@ -2264,6 +2266,245 @@ this new feature. 1 + + + True + + + True + False + 25 + + + True + False + 4 + 3 + 5 + 1 + + + True + False + 1 + Completed: + + + GTK_FILL + + + + + True + True + True + False + 0 + #000000000000 + + + + 1 + 2 + + + + + + True + False + 1 + Downloading: + + + 1 + 2 + GTK_FILL + + + + + True + True + True + False + 0 + #000000000000 + + + + 1 + 2 + 1 + 2 + + + + + + True + False + 1 + Waiting: + + + 2 + 3 + GTK_FILL + + + + + True + True + True + False + 0 + #000000000000 + + + + 1 + 2 + 2 + 3 + + + + + + True + False + 1 + Missing: + + + 3 + 4 + GTK_FILL + + + + + True + True + True + False + 0 + #000000000000 + + + + 1 + 2 + 3 + 4 + + + + + + gtk-revert-to-saved + True + False + True + True + Revert color to default + False + True + right + + + + 2 + 3 + + + + + + gtk-revert-to-saved + True + False + True + True + Revert color to default + False + True + right + + + + 2 + 3 + 1 + 2 + + + + + + gtk-revert-to-saved + True + False + True + True + Revert color to default + False + True + right + + + + 2 + 3 + 2 + 3 + + + + + + gtk-revert-to-saved + True + False + True + True + Revert color to default + False + True + right + + + + 2 + 3 + 3 + 4 + + + + + + + + + + True + False + Piece Colors + + + label_item + + + + + False + False + 2 + + diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index b87c56a2f..92da7715e 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -157,7 +157,18 @@ DEFAULT_PREFS = { "sidebar_show_trackers": True, "sidebar_position": 170, "show_rate_in_title": False, - "createtorrent.trackers": [] + "createtorrent.trackers": [], + "show_piecesbar": False, +# "pieces_colors": [ +# [65535, 0, 0], +# [4874, 56494, 0], +# [65535, 55255, 0], +# [4883, 26985, 56540] +# ], + "pieces_color_missing": [65535, 0, 0], + "pieces_color_waiting": [4874, 56494, 0], + "pieces_color_downloading": [65535, 55255, 0], + "pieces_color_completed": [4883, 26985, 56540], } class GtkUI(object): diff --git a/deluge/ui/gtkui/piecesbar.py b/deluge/ui/gtkui/piecesbar.py new file mode 100644 index 000000000..9f358e5ef --- /dev/null +++ b/deluge/ui/gtkui/piecesbar.py @@ -0,0 +1,184 @@ +# -*- coding: utf-8 -*- +# +# listview.py +# +# Copyright (C) 2011 Pedro Algarvio +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 3 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. +# +# + +import pygtk +pygtk.require('2.0') +import gtk +import gtk.gdk +import cairo +import logging +from deluge.configmanager import ConfigManager + +log = logging.getLogger(__name__) + + +COLOR_STATES = { + 0: "missing", + 1: "waiting", + 2: "downloading", + 3: "completed" +} + +class PiecesBar(gtk.DrawingArea): + # Draw in response to an expose-event + __gsignals__ = {"expose-event": "override"} + + def __init__(self): + gtk.DrawingArea.__init__(self) + self.set_size_request(-1, 25) + self.gtkui_config = ConfigManager("gtkui.conf") + self.width = 0 + self.height = 0 + self.pieces = () + self.num_pieces = None + self.connect('size-allocate', self.do_size_allocate_event) + self.set_colormap(self.get_screen().get_rgba_colormap()) + self.show() + + def do_size_allocate_event(self, widget, size): + self.width = size.width + self.height = size.height + + # Handle the expose-event by drawing + def do_expose_event(self, event=None): + # Create the cairo context + cr = self.window.cairo_create() + + # Restrict Cairo to the exposed area; avoid extra work + cr.rectangle(event.area.x, event.area.y, + event.area.width, event.area.height) + cr.clip() + +# # Sets the operator to clear which deletes everything below where +# # an object is drawn +# cr.set_operator(cairo.OPERATOR_CLEAR) +# # Makes the mask fill the entire area +# cr.rectangle(0.0, 0.0, event.area.width, event.area.height) +# # Deletes everything in the window (since the compositing operator +# # is clear and mask fills the entire window +# cr.fill() +# # Set the compositing operator back to the default +# cr.set_operator(cairo.OPERATOR_OVER) + + + cr.set_line_width(max(cr.device_to_user_distance(0.5, 0.5))) +# bgColor = self.window.get_style().copy().bg[gtk.STATE_NORMAL] +# cr.set_source_rgba(0.53, 0.53, 0.53, 1.0) # Transparent +# cr.set_source_rgba(0.8, 0.8, 0.8, 1.0) # Transparent +# cr.set_source_rgb(0.3, 0.3, 0.3) # Transparent + cr.set_source_rgb(0.1, 0.1, 0.1) # Transparent +# cr.set_source_rgb(bgColor.red/65535.0, bgColor.green/65535.0, bgColor.blue/65535.0) +# cr.set_operator(cairo.OPERATOR_SOURCE) + cr.rectangle(0.0, 0.0, event.area.width, event.area.height) + cr.stroke() +# # Set the compositing operator back to the default +# cr.set_operator(cairo.OPERATOR_OVER) + + + if not self.pieces and self.num_pieces is not None: + # Complete Torrent + piece_height = self.height - 2 + piece_width = self.width*1.0/self.num_pieces + start = 1 + for _ in range(self.num_pieces): + # Like this to keep same aspect ratio + color = self.gtkui_config["pieces_color_%s" % COLOR_STATES[3]] + cr.set_source_rgb( + color[0]/65535.0, + color[1]/65535.0, + color[2]/65535.0, + ) +# cr.set_source_rgba(*list(self.colors[3])+[0.5]) + cr.rectangle(start, 1, piece_width, piece_height) + cr.fill() + start += piece_width + return + + if not self.pieces: + return + + # Create the cairo context + start_pos = 1 + num_pieces = self.num_pieces and self.num_pieces or len(self.pieces) + piece_width = self.width*1.0/num_pieces + piece_height = self.height - 2 + + for state in self.pieces: +# cr.set_source_rgb(*self.colors[state]) +# cr.set_source_rgb(*self.colors[state]) + + color = self.gtkui_config["pieces_color_%s" % COLOR_STATES[state]] + cr.set_source_rgb( + color[0]/65535.0, + color[1]/65535.0, + color[2]/65535.0, + ) +# cr.set_source_rgba(*list(self.colors[state])+[0.5]) + cr.rectangle(start_pos, 1, piece_width, piece_height) + cr.fill() + start_pos += piece_width + + def set_pieces(self, pieces, num_pieces): + if pieces != self.pieces: + self.pieces = pieces + self.num_pieces = num_pieces + self.update() + + def clear(self): + self.pieces = [] + self.num_pieces = None + self.update() + + def update(self): + self.queue_draw() + + def get_text(self): + return "" + + def set_text(self, text): + pass + +# def on_pieces_colors_updated(self, key, colors): +# log.debug("Pieces bar got config change for key: %s values: %s", +# key, colors) +# if key != "pieces_colors": +# return +# # Make sure there's no state color missong +# self.colors[0] = [c/65535.0 for c in colors[0]] +# self.colors[1] = [c/65535.0 for c in colors[1]] +# self.colors[2] = [c/65535.0 for c in colors[2]] +# self.colors[3] = [c/65535.0 for c in colors[3]] +# self.update() diff --git a/deluge/ui/gtkui/preferences.py b/deluge/ui/gtkui/preferences.py index 96950e0da..aeadaf9cc 100644 --- a/deluge/ui/gtkui/preferences.py +++ b/deluge/ui/gtkui/preferences.py @@ -53,6 +53,14 @@ import deluge.configmanager log = logging.getLogger(__name__) ACCOUNTS_USERNAME, ACCOUNTS_LEVEL, ACCOUNTS_PASSWORD = range(3) +COLOR_MISSING, COLOR_WAITING, COLOR_DOWNLOADING, COLOR_COMPLETED = range(4) + +COLOR_STATES = { + "missing": COLOR_MISSING, + "waiting": COLOR_WAITING, + "downloading": COLOR_DOWNLOADING, + "completed": COLOR_COMPLETED +} class Preferences(component.Component): def __init__(self): @@ -152,9 +160,24 @@ class Preferences(component.Component): "on_accounts_add_clicked": self._on_accounts_add_clicked, "on_accounts_delete_clicked": self._on_accounts_delete_clicked, "on_accounts_edit_clicked": self._on_accounts_edit_clicked, - "on_alocation_toggled": self._on_alocation_toggled + "on_alocation_toggled": self._on_alocation_toggled, + "on_piecesbar_toggle_toggled": self._on_piecesbar_toggle_toggled, + "on_completed_color_set": self._on_completed_color_set, + "on_revert_color_completed_clicked": self._on_revert_color_completed_clicked, + "on_downloading_color_set": self._on_downloading_color_set, + "on_revert_color_downloading_clicked": self._on_revert_color_downloading_clicked, + "on_waiting_color_set": self._on_waiting_color_set, + "on_revert_color_waiting_clicked": self._on_revert_color_waiting_clicked, + "on_missing_color_set": self._on_missing_color_set, + "on_revert_color_missing_clicked": self._on_revert_color_missing_clicked }) + from deluge.ui.gtkui.gtkui import DEFAULT_PREFS + self.COLOR_DEFAULTS = {} + for key in ("missing", "waiting", "downloading", "completed"): + self.COLOR_DEFAULTS[key] = DEFAULT_PREFS["pieces_color_%s" % key][:] + del DEFAULT_PREFS + # These get updated by requests done to the core self.all_plugins = [] self.enabled_plugins = [] @@ -527,6 +550,13 @@ class Preferences(component.Component): self.gtkui_config["classic_mode"]) self.glade.get_widget("chk_show_rate_in_title").set_active( self.gtkui_config["show_rate_in_title"]) + self.glade.get_widget("piecesbar_toggle").set_active( + self.gtkui_config["show_piecesbar"] + ) + self.__set_color("completed", from_config=True) + self.__set_color("downloading", from_config=True) + self.__set_color("waiting", from_config=True) + self.__set_color("missing", from_config=True) ## Other tab ## self.glade.get_widget("chk_show_new_releases").set_active( @@ -578,6 +608,13 @@ class Preferences(component.Component): self.glade.get_widget("chk_show_dialog").get_active() new_gtkui_config["focus_add_dialog"] = \ self.glade.get_widget("chk_focus_dialog").get_active() + + for state in ("missing", "waiting", "downloading", "completed"): + color = self.glade.get_widget("%s_color" % state).get_color() + new_gtkui_config["pieces_color_%s" % state] = [ + color.red, color.green, color.blue + ] + new_core_config["copy_torrent_file"] = \ self.glade.get_widget("chk_copy_torrent_file").get_active() new_core_config["del_copy_torrent_file"] = \ @@ -916,6 +953,7 @@ class Preferences(component.Component): def on_test_port_clicked(self, data): log.debug("on_test_port_clicked") + def on_get_test(status): if status: self.glade.get_widget("port_img").set_from_stock(gtk.STOCK_YES, 4) @@ -924,6 +962,8 @@ class Preferences(component.Component): self.glade.get_widget("port_img").set_from_stock(gtk.STOCK_DIALOG_WARNING, 4) self.glade.get_widget("port_img").show() client.core.test_listen_port().addCallback(on_get_test) + # XXX: Consider using gtk.Spinner() instead of the loading gif + # It requires gtk.ver > 2.12 self.glade.get_widget("port_img").set_from_file( deluge.common.get_pixmap('loading.gif') ) @@ -1210,3 +1250,60 @@ class Preferences(component.Component): full_allocation_active = self.glade.get_widget("radio_full_allocation").get_active() self.glade.get_widget("chk_prioritize_first_last_pieces").set_sensitive(full_allocation_active) self.glade.get_widget("chk_sequential_download").set_sensitive(full_allocation_active) + + def _on_piecesbar_toggle_toggled(self, widget): + self.gtkui_config['show_piecesbar'] = widget.get_active() + colors_widget = self.glade.get_widget("piecebar_colors_expander") + colors_widget.set_visible(widget.get_active()) + + def _on_completed_color_set(self, widget): + self.__set_color("completed") + + def _on_revert_color_completed_clicked(self, widget): + self.__revert_color("completed") + + def _on_downloading_color_set(self, widget): + self.__set_color("downloading") + + def _on_revert_color_downloading_clicked(self, widget): + self.__revert_color("downloading") + + def _on_waiting_color_set(self, widget): + self.__set_color("waiting") + + def _on_revert_color_waiting_clicked(self, widget): + self.__revert_color("waiting") + + def _on_missing_color_set(self, widget): + self.__set_color("missing") + + def _on_revert_color_missing_clicked(self, widget): + self.__revert_color("missing") + + def __set_color(self, state, from_config=False): + if from_config: + color = gtk.gdk.Color(*self.gtkui_config["pieces_color_%s" % state]) + log.debug("Setting %r color state from config to %s", state, + (color.red, color.green, color.blue)) + self.glade.get_widget("%s_color" % state).set_color(color) + else: + color = self.glade.get_widget("%s_color" % state).get_color() + log.debug("Setting %r color state to %s", state, + (color.red, color.green, color.blue)) + self.gtkui_config["pieces_color_%s" % state] = [ + color.red, color.green, color.blue + ] + self.gtkui_config.save() + self.gtkui_config.apply_set_functions("pieces_colors") + + self.glade.get_widget("revert_color_%s" % state).set_sensitive( + [color.red, color.green, color.blue] != self.COLOR_DEFAULTS[state] + ) + + def __revert_color(self, state, from_config=False): + log.debug("Reverting %r color state", state) + self.glade.get_widget("%s_color" % state).set_color( + gtk.gdk.Color(*self.COLOR_DEFAULTS[state]) + ) + self.glade.get_widget("revert_color_%s" % state).set_sensitive(False) + self.gtkui_config.apply_set_functions("pieces_colors") diff --git a/deluge/ui/gtkui/status_tab.py b/deluge/ui/gtkui/status_tab.py index 926957a8c..8d9c844b9 100644 --- a/deluge/ui/gtkui/status_tab.py +++ b/deluge/ui/gtkui/status_tab.py @@ -42,7 +42,9 @@ import logging from deluge.ui.client import client import deluge.component as component import deluge.common +from deluge.configmanager import ConfigManager from deluge.ui.gtkui.torrentdetails import Tab +from deluge.ui.gtkui.piecesbar import PiecesBar log = logging.getLogger(__name__) @@ -71,12 +73,17 @@ class StatusTab(Tab): Tab.__init__(self) # Get the labels we need to update. # widgetname, modifier function, status keys - glade = component.get("MainWindow").main_glade + self.glade = glade = component.get("MainWindow").main_glade self._name = "Status" self._child_widget = glade.get_widget("status_tab") self._tab_label = glade.get_widget("status_tab_label") - + self.config = ConfigManager("gtkui.conf") + self.config.register_set_function( + "show_piecesbar", + self.on_show_pieces_bar_config_changed, + apply_now=True + ) self.label_widgets = [ (glade.get_widget("summary_pieces"), fpeer_size_second, ("num_pieces", "piece_length")), (glade.get_widget("summary_availability"), fratio, ("distributed_copies",)), @@ -118,6 +125,9 @@ class StatusTab(Tab): "tracker_status", "max_connections", "max_upload_slots", "max_upload_speed", "max_download_speed", "active_time", "seeding_time", "seed_rank", "is_auto_managed", "time_added"] + if self.config['show_piecesbar']: + status_keys.append("pieces") + component.get("SessionProxy").get_torrent_status( selected, status_keys).addCallback(self._on_get_torrent_status) @@ -155,9 +165,29 @@ class StatusTab(Tab): fraction = status["progress"] / 100 if w.get_fraction() != fraction: w.set_fraction(fraction) + if self.config['show_piecesbar']: + self.piecesbar.set_pieces(status['pieces'], status["num_pieces"]) + + def on_show_pieces_bar_config_changed(self, key, show): + self.show_pieces_bar(show) + + def show_pieces_bar(self, show): + if hasattr(self, 'piecesbar'): + if show: + self.piecesbar.show() + else: + self.piecesbar.hide() + else: + if show: + self.piecesbar = PiecesBar() + self.glade.get_widget("status_progress_vbox").pack_start( + self.piecesbar, False, False, 5 + ) def clear(self): for widget in self.label_widgets: widget[0].set_text("") component.get("MainWindow").main_glade.get_widget("progressbar").set_fraction(0.0) + if self.config['show_piecesbar']: + self.piecesbar.clear()