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 @@
+
+
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()