[GTKUI] Add new OtherDialog to dialogs

This adds a new OtherDialog to dialogs so that will use Deferred to prevent
the dialog loop locking up the mainwindow.
Remove old `Other` dialog from common and cleanup up the file.
Fixes #2401, context menus for torrents not showing current value.
Fixes #2400, add a stop_at_ratio context menu.
Change the protocol rate to display as int.
This commit is contained in:
Calum Lind 2014-02-22 13:07:33 +00:00
parent 813261df07
commit ea7ef950a3
6 changed files with 336 additions and 306 deletions

View File

@ -1,128 +1,93 @@
#
# common.py
# -*- coding: utf-8 -*-
#
# Copyright (C) 2008 Marcos Pinto ('markybob') <markybob@gmail.com>
#
# Deluge is free software.
# 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.
#
# 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.
#
#
"""Common functions for various parts of gtkui to use."""
import os
import pygtk
pygtk.require('2.0')
import gtk
import logging
import cPickle
import shutil
import deluge.component as component
import pygtk
pygtk.require('2.0')
import gtk
from gobject import GError
import deluge.common
log = logging.getLogger(__name__)
def get_logo(size):
"""Returns a deluge logo pixbuf based on the size parameter."""
"""A Deluge logo.
Params:
size (int): Size of logo in pixels
Returns:
gtk.gdk.Pixbuf: deluge logo
"""
filename = "deluge.svg"
if deluge.common.windows_check() or deluge.common.osx_check():
return gtk.gdk.pixbuf_new_from_file_at_size(deluge.common.get_pixmap("deluge.png"), \
size, size)
else:
try:
return gtk.gdk.pixbuf_new_from_file_at_size(deluge.common.get_pixmap("deluge.svg"), \
size, size)
except Exception, e:
log.warning(e)
filename = "deluge.png"
try:
return gtk.gdk.pixbuf_new_from_file_at_size(deluge.common.get_pixmap(filename), size, size)
except GError as ex:
log.warning(ex)
def build_menu_radio_list(value_list, callback, pref_value=None,
suffix=None, show_notset=False, notset_label=None, notset_lessthan=0,
show_other=False, show_activated=False, activated_label=None):
# Build a menu with radio menu items from a list and connect them to
# the callback. The pref_value is what you would like to test for the
# default active radio item.
if notset_label is None:
notset_label = _("Unlimited")
if activated_label is None:
activated_label = _("Activated")
def build_menu_radio_list(value_list, callback, pref_value=None, suffix=None, show_notset=False,
notset_label="", notset_lessthan=0, show_other=False):
"""Build a menu with radio menu items from a list and connect them to the callback.
Params:
value_list [list]: List of values to build into a menu.
callback (function): The function to call when menu item is clicked.
pref_value (int): A preferred value to insert into value_list
suffix (str): Append a suffix the the menu items in value_list.
show_notset (bool): Show the unlimited menu item.
notset_label (str): The text for the unlimited menu item.
notset_lessthan (int): Activates the unlimited menu item if pref_value is less than this.
show_other (bool): Show the `Other` menu item.
The pref_value is what you would like to test for the default active radio item.
Returns:
gtk.Menu: The menu radio
"""
menu = gtk.Menu()
group = None
if show_activated is False:
if pref_value > -1 and pref_value not in value_list:
value_list.pop()
value_list.append(pref_value)
for value in sorted(value_list):
if suffix != None:
menuitem = gtk.RadioMenuItem(group, str(value) + " " + \
suffix)
else:
menuitem = gtk.RadioMenuItem(group, str(value))
if pref_value > -1 and pref_value not in value_list:
value_list.pop()
value_list.append(pref_value)
group = menuitem
if value == pref_value and pref_value != None:
menuitem.set_active(True)
if callback != None:
menuitem.connect("toggled", callback)
menu.append(menuitem)
if show_activated is True:
for value in sorted(value_list):
menuitem = gtk.RadioMenuItem(group, str(activated_label))
group = menuitem
if value == pref_value and pref_value != None:
menuitem.set_active(True)
if callback != None:
menuitem.connect("toggled", callback)
menu.append(menuitem)
for value in sorted(value_list):
item_text = str(value)
if suffix:
item_text += " " + suffix
menuitem = gtk.RadioMenuItem(group, item_text)
group = menuitem
if pref_value and value == pref_value:
menuitem.set_active(True)
if callback:
menuitem.connect("toggled", callback)
menu.append(menuitem)
if show_notset:
menuitem = gtk.RadioMenuItem(group, notset_label)
menuitem.set_name("unlimited")
if pref_value < notset_lessthan and pref_value != None:
menuitem.set_active(True)
if show_activated and pref_value == 1:
if pref_value and pref_value < notset_lessthan:
menuitem.set_active(True)
menuitem.connect("toggled", callback)
menu.append(menuitem)
# Add the Other... menuitem
if show_other is True:
if show_other:
menuitem = gtk.SeparatorMenuItem()
menu.append(menuitem)
menuitem = gtk.MenuItem(_("Other..."))
@ -132,83 +97,25 @@ def build_menu_radio_list(value_list, callback, pref_value=None,
return menu
def show_other_dialog(header, type_str, image_stockid=None, image_filename=None, default=0):
"""
Shows a dialog with `header` as the header text and `type_str`
as the type text. The type of spinbutton (int or float) is determined by
`default` type.
:param header: str, the header label text
:param type_str: str, the type label text, what comes after the spinbutton
:param image_stockid: gtkStockId, the stock id of the image in the header
:param image_filename: str, filename of icon in pixmaps folder
:param default: the default value in the spinbutton
:returns: None, int or float from spinbutton depending on `default`.
None is returned if the user clicks on Cancel.
:rtype: None, int or float
:raises TypeError: if `default` is not of type int or float
"""
if type(default) != int and type(default) != float:
raise TypeError("default value needs to be an int or float")
builder = gtk.Builder()
builder.add_from_file(deluge.common.resource_filename(
"deluge.ui.gtkui", os.path.join("glade", "other_dialog.ui")
))
dialog = builder.get_object("other_dialog")
dialog.set_transient_for(component.get("MainWindow").window)
dialog.set_position(gtk.WIN_POS_CENTER_ON_PARENT)
dialog.set_title("")
builder.get_object("label_header").set_markup("<b>" + header + "</b>")
builder.get_object("label_type").set_text(type_str)
if image_stockid:
builder.get_object("image").set_from_stock(image_stockid, gtk.ICON_SIZE_LARGE_TOOLBAR)
if image_filename:
# Hack for Windows since it doesn't support svg
if os.path.splitext(image_filename)[1] == ".svg" and (deluge.common.windows_check() or deluge.common.osx_check()):
image_filename = os.path.splitext(image_filename)[0] + "16.png"
pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(
deluge.common.get_pixmap(image_filename), 32, 32)
builder.get_object("image").set_from_pixbuf(pixbuf)
spinbutton = builder.get_object("spinbutton")
if type(default) == float:
spinbutton.set_digits(1)
# Set default value and select text
spinbutton.set_value(default)
spinbutton.select_region(0, -1)
value = None
response = dialog.run()
if response == gtk.RESPONSE_OK:
if type(default) == int:
value = spinbutton.get_value_as_int()
else:
value = spinbutton.get_value()
dialog.destroy()
return value
def reparent_iter(treestore, itr, parent, move_siblings=False):
"""
This effectively moves itr plus it's children to be a child of parent in treestore
:param treestore: gtkTreeStore, the treestore
:param itr: gtkTreeIter, the iter to move
:param parent: gtkTreeIter, the new parent for itr
:param move_siblings: bool. if True, it will move all itr's siblings to parent
Params:
treestore (gtkTreeStore): the treestore
itr (gtkTreeIter): the iter to move
parent (gtkTreeIter): the new parent for itr
move_siblings (bool): if True, it will move all itr's siblings to parent
"""
src = itr
def move_children(i, dest):
while i:
n = treestore.append(dest, treestore.get(i, *xrange(treestore.get_n_columns())))
n_cols = treestore.append(dest, treestore.get(i, *xrange(treestore.get_n_columns())))
to_remove = i
if treestore.iter_children(i):
move_children(treestore.iter_children(i), n)
move_children(treestore.iter_children(i), n_cols)
if i != src:
i = treestore.iter_next(i)
else:
@ -219,12 +126,15 @@ def reparent_iter(treestore, itr, parent, move_siblings=False):
move_children(itr, parent)
def get_deluge_icon():
"""
Returns the deluge icon for use in setting a dialogs icon. It will first
attempt to get the icon from the theme and will fallback to using an image
"""The deluge icon for use in dialogs.
It will first attempt to get the icon from the theme and will fallback to using an image
that is distributed with the package.
Returns:
gtk.gdk.Pixbuf: the deluge icon
"""
if deluge.common.windows_check():
return get_logo(32)
@ -232,18 +142,19 @@ def get_deluge_icon():
try:
icon_theme = gtk.icon_theme_get_default()
return icon_theme.load_icon("deluge", 64, 0)
except:
except GError:
return get_logo(64)
def associate_magnet_links(overwrite=False):
"""
Associates magnet links to Deluge.
:param overwrite: if this is True, the current setting will be overwritten
:type overwrite: bool
:returns: True if association was set
:rtype: bool
Params:
overwrite (bool): if this is True, the current setting will be overwritten
Returns:
bool: True if association was set
"""
if not deluge.common.windows_check():
# gconf method is only available in a GNOME environment
@ -267,10 +178,13 @@ def associate_magnet_links(overwrite=False):
return False
return False
def save_pickled_state_file(filename, state):
"""Save a file in the config directory and creates a backup
filename: Filename to be saved to config
state: The data to be pickled and written to file
Params:
filename (str): Filename to be saved to config
state (state): The data to be pickled and written to file
"""
from deluge.configmanager import get_config_dir
filepath = os.path.join(get_config_dir(), "gtkui_state", filename)
@ -298,10 +212,15 @@ def save_pickled_state_file(filename, state):
log.info("Restoring backup of %s from: %s", filename, filepath_bak)
shutil.move(filepath_bak, filepath)
def load_pickled_state_file(filename):
"""Loads a file from the config directory, attempting backup if original fails to load.
filename: Filename to be loaded from config
returns unpickled state
Params:
filename (str): Filename to be loaded from config
Returns:
state: the unpickled state
"""
from deluge.configmanager import get_config_dir
filepath = os.path.join(get_config_dir(), "gtkui_state", filename)
@ -313,7 +232,7 @@ def load_pickled_state_file(filename):
try:
with open(_filepath, "rb") as _file:
state = cPickle.load(_file)
except (IOError, cPickle.UnpicklingError), ex:
except (IOError, cPickle.UnpicklingError) as ex:
log.warning("Unable to load %s: %s", _filepath, ex)
else:
log.info("Successfully loaded %s: %s", filename, _filepath)

View File

@ -31,13 +31,13 @@
# 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 gtk
from twisted.internet import defer
from deluge.ui.gtkui import common
import deluge.component as component
import deluge.common
class BaseDialog(gtk.Dialog):
@ -69,7 +69,14 @@ class BaseDialog(gtk.Dialog):
self.set_default_size(200, 100)
hbox = gtk.HBox(spacing=5)
image = gtk.Image()
image.set_from_stock(icon, gtk.ICON_SIZE_DIALOG)
if not gtk.stock_lookup(icon) and (icon.endswith(".svg") or icon.endswith(".png")):
# Hack for Windows since it doesn't support svg
if icon.endswith(".svg") and (deluge.common.windows_check() or deluge.common.osx_check()):
icon = icon.rpartition(".svg")[0] + "16.png"
pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(deluge.common.get_pixmap(icon), 32, 32)
image.set_from_pixbuf(pixbuf)
else:
image.set_from_stock(icon, gtk.ICON_SIZE_DIALOG)
image.set_alignment(0.5, 0.0)
hbox.pack_start(image, False, False)
vbox = gtk.VBox(spacing=5)
@ -105,6 +112,7 @@ class BaseDialog(gtk.Dialog):
self.show()
return self.deferred
class YesNoDialog(BaseDialog):
"""
Displays a dialog asking the user to select Yes or No to a question.
@ -122,9 +130,10 @@ class YesNoDialog(BaseDialog):
header,
text,
gtk.STOCK_DIALOG_QUESTION,
(gtk.STOCK_YES, gtk.RESPONSE_YES, gtk.STOCK_NO, gtk.RESPONSE_NO),
(gtk.STOCK_NO, gtk.RESPONSE_NO, gtk.STOCK_YES, gtk.RESPONSE_YES),
parent)
class InformationDialog(BaseDialog):
"""
Displays an information dialog.
@ -144,6 +153,7 @@ class InformationDialog(BaseDialog):
(gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE),
parent)
class ErrorDialog(BaseDialog):
"""
Displays an error dialog with optional details text for more information.
@ -193,6 +203,7 @@ class ErrorDialog(BaseDialog):
self.vbox.pack_start(sw)
self.vbox.show_all()
class AuthenticationDialog(BaseDialog):
"""
Displays a dialog with entry fields asking for username and password.
@ -213,7 +224,7 @@ class AuthenticationDialog(BaseDialog):
table = gtk.Table(2, 2, False)
self.username_label = gtk.Label()
self.username_label.set_markup(_("<b>Username:</b>"))
self.username_label.set_markup("<b>" + _("Username:") + "</b>")
self.username_label.set_alignment(1.0, 0.5)
self.username_label.set_padding(5, 5)
self.username_entry = gtk.Entry()
@ -221,7 +232,7 @@ class AuthenticationDialog(BaseDialog):
table.attach(self.username_entry, 1, 2, 0, 1)
self.password_label = gtk.Label()
self.password_label.set_markup(_("<b>Password:</b>"))
self.password_label.set_markup("<b>" + _("Password:") + "</b>")
self.password_label.set_alignment(1.0, 0.5)
self.password_label.set_padding(5, 5)
self.password_entry = gtk.Entry()
@ -249,6 +260,7 @@ class AuthenticationDialog(BaseDialog):
def on_password_activate(self, widget):
self.response(gtk.RESPONSE_OK)
class AccountDialog(BaseDialog):
def __init__(self, username=None, password=None, authlevel=None,
levels_mapping=None, parent=None):
@ -273,7 +285,7 @@ class AccountDialog(BaseDialog):
table = gtk.Table(2, 3, False)
self.username_label = gtk.Label()
self.username_label.set_markup(_("<b>Username:</b>"))
self.username_label.set_markup("<b>" + _("Username:") + "</b>")
self.username_label.set_alignment(1.0, 0.5)
self.username_label.set_padding(5, 5)
self.username_entry = gtk.Entry()
@ -281,7 +293,7 @@ class AccountDialog(BaseDialog):
table.attach(self.username_entry, 1, 2, 0, 1)
self.authlevel_label = gtk.Label()
self.authlevel_label.set_markup(_("<b>Authentication Level:</b>"))
self.authlevel_label.set_markup("<b>" + _("Authentication Level:") + "</b>")
self.authlevel_label.set_alignment(1.0, 0.5)
self.authlevel_label.set_padding(5, 5)
@ -289,7 +301,7 @@ class AccountDialog(BaseDialog):
active_idx = None
for idx, level in enumerate(levels_mapping.keys()):
self.authlevel_combo.append_text(level)
if authlevel and authlevel==level:
if authlevel and authlevel == level:
active_idx = idx
elif not authlevel and level == 'DEFAULT':
active_idx = idx
@ -301,7 +313,7 @@ class AccountDialog(BaseDialog):
table.attach(self.authlevel_combo, 1, 2, 1, 2)
self.password_label = gtk.Label()
self.password_label.set_markup(_("<b>Password:</b>"))
self.password_label.set_markup("<b>" + _("Password:") + "</b>")
self.password_label.set_alignment(1.0, 0.5)
self.password_label.set_padding(5, 5)
self.password_entry = gtk.Entry()
@ -331,3 +343,63 @@ class AccountDialog(BaseDialog):
combobox = self.authlevel_combo
level = combobox.get_model()[combobox.get_active()][0]
return level
class OtherDialog(BaseDialog):
"""
Displays a dialog with a spinner for setting a value.
Returns:
int or float:
"""
def __init__(self, header, text="", unit_text="", icon=None, default=0, parent=None):
self.value_type = type(default)
if self.value_type not in (int, float):
raise TypeError("default value needs to be an int or float")
if not icon:
icon = gtk.STOCK_DIALOG_INFO
super(OtherDialog, self).__init__(
header,
text,
icon,
(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_APPLY, gtk.RESPONSE_OK),
parent)
hbox = gtk.HBox(spacing=5)
alignment_spacer = gtk.Alignment()
hbox.pack_start(alignment_spacer)
alignment_spin = gtk.Alignment(1, 0.5, 1, 1)
adjustment_spin = gtk.Adjustment(value=-1, lower=-1, upper=2097151, step_incr=1, page_incr=10)
self.spinbutton = gtk.SpinButton(adjustment_spin)
self.spinbutton.set_value(default)
self.spinbutton.select_region(0, -1)
self.spinbutton.set_width_chars(6)
self.spinbutton.set_alignment(1)
self.spinbutton.set_max_length(6)
if self.value_type is float:
self.spinbutton.set_digits(1)
alignment_spin.add(self.spinbutton)
hbox.pack_start(alignment_spin, expand=False)
label_type = gtk.Label()
label_type.set_text(unit_text)
label_type.set_alignment(0.0, 0.5)
hbox.pack_start(label_type)
self.vbox.pack_start(hbox, False, False, padding=5)
self.vbox.show_all()
def _on_delete_event(self, widget, event):
self.deferred.callback(None)
self.destroy()
def _on_response(self, widget, response):
value = None
if response == gtk.RESPONSE_OK:
if self.value_type is int:
value = self.spinbutton.get_value_as_int()
else:
value = self.spinbutton.get_value()
self.deferred.callback(value)
self.destroy()

View File

@ -60,6 +60,15 @@
<property name="use_stock">False</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="menuitem_stop_seed_at_ratio">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_action_appearance">False</property>
<property name="label" translatable="yes">Stop seed at _ratio</property>
<property name="use_underline">True</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="menuitem_auto_managed">
<property name="visible">True</property>

View File

@ -44,13 +44,13 @@ import deluge.error
import deluge.component as component
from deluge.ui.client import client
import deluge.common
import common
import dialogs
from deluge.configmanager import ConfigManager
from deluge.ui.gtkui.path_chooser import PathChooser
log = logging.getLogger(__name__)
class MenuBar(component.Component):
def __init__(self):
@ -85,7 +85,7 @@ class MenuBar(component.Component):
self.builder.get_object("upload-limit-image").set_from_file(deluge.common.get_pixmap("seeding16.png"))
for menuitem in ("menuitem_down_speed", "menuitem_up_speed",
"menuitem_max_connections", "menuitem_upload_slots"):
"menuitem_max_connections", "menuitem_upload_slots"):
submenu = gtk.Menu()
item = gtk.MenuItem(_("Set Unlimited"))
item.set_name(menuitem)
@ -108,6 +108,17 @@ class MenuBar(component.Component):
submenu.show_all()
self.builder.get_object("menuitem_auto_managed").set_submenu(submenu)
submenu = gtk.Menu()
item = gtk.MenuItem(_("Disable"))
item.connect("activate", self.on_menuitem_set_stop_seed_at_ratio_disable)
submenu.append(item)
item = gtk.MenuItem(_("Enable..."))
item.set_name("menuitem_stop_seed_at_ratio")
item.connect("activate", self.on_menuitem_set_other)
submenu.append(item)
submenu.show_all()
self.builder.get_object("menuitem_stop_seed_at_ratio").set_submenu(submenu)
self.torrentmenu = self.builder.get_object("torrent_menu")
self.menu_torrent = self.main_builder.get_object("menu_torrent")
@ -121,7 +132,6 @@ class MenuBar(component.Component):
self.main_builder.get_object("sidebar_show_zero").set_active(self.config["sidebar_show_zero"])
self.main_builder.get_object("sidebar_show_trackers").set_active(self.config["sidebar_show_trackers"])
### Connect main window Signals ###
component.get("MainWindow").connect_signals({
## File Menu
@ -144,8 +154,8 @@ class MenuBar(component.Component):
"on_menuitem_faq_activate": self.on_menuitem_faq_activate,
"on_menuitem_community_activate": self.on_menuitem_community_activate,
"on_menuitem_about_activate": self.on_menuitem_about_activate,
"on_menuitem_sidebar_zero_toggled":self.on_menuitem_sidebar_zero_toggled,
"on_menuitem_sidebar_trackers_toggled":self.on_menuitem_sidebar_trackers_toggled
"on_menuitem_sidebar_zero_toggled": self.on_menuitem_sidebar_zero_toggled,
"on_menuitem_sidebar_trackers_toggled": self.on_menuitem_sidebar_trackers_toggled
})
# Connect menubar signals
@ -204,8 +214,7 @@ class MenuBar(component.Component):
if client.get_auth_level() == deluge.common.AUTH_LEVEL_ADMIN:
# Get known accounts to allow changing ownership
client.core.get_known_accounts().addCallback(
self._on_known_accounts).addErrback(self._on_known_accounts_fail
)
self._on_known_accounts).addErrback(self._on_known_accounts_fail)
def stop(self):
log.debug("MenuBar stopping")
@ -315,6 +324,7 @@ class MenuBar(component.Component):
def on_menuitem_open_folder_activate(self, data=None):
log.debug("on_menuitem_open_folder")
def _on_torrent_status(status):
deluge.common.open_file(status["save_path"])
for torrent_id in component.get("TorrentView").get_selected_torrents():
@ -422,36 +432,54 @@ class MenuBar(component.Component):
def on_menuitem_set_other(self, widget):
log.debug("widget.name: %s", widget.name)
funcs = {
"menuitem_down_speed": client.core.set_torrent_max_download_speed,
"menuitem_up_speed": client.core.set_torrent_max_upload_speed,
"menuitem_max_connections": client.core.set_torrent_max_connections,
"menuitem_upload_slots": client.core.set_torrent_max_upload_slots
}
# widget: (header, type_str, image_stockid, image_filename, default)
other_dialog_info = {
"menuitem_down_speed": (
_("Set Maximum Download Speed"),
_("KiB/s"), None, "downloading.svg", -1.0
),
"menuitem_up_speed": (
_("Set Maximum Upload Speed"),
_("KiB/s"), None, "seeding.svg", -1.0
),
"menuitem_max_connections": (
_("Set Maximum Connections"), "", gtk.STOCK_NETWORK, None, -1
),
"menuitem_upload_slots": (
_("Set Maximum Upload Slots"),
"", gtk.STOCK_SORT_ASCENDING, None, -1
)
status_map = {
"menuitem_down_speed": ["max_download_speed", "max_download_speed"],
"menuitem_up_speed": ["max_upload_speed", "max_upload_speed"],
"menuitem_max_connections": ["max_connections", "max_connections_global"],
"menuitem_upload_slots": ["max_upload_slots", "max_upload_slots_global"],
"menuitem_stop_seed_at_ratio": ["stop_ratio", "stop_seed_ratio"]
}
# Show the other dialog
value = common.show_other_dialog(*other_dialog_info[widget.name])
if value and widget.name in funcs:
for torrent in component.get("TorrentView").get_selected_torrents():
funcs[widget.name](torrent, value)
other_dialog_info = {
"menuitem_down_speed": [_("Download Speed Limit"), _("Set the maximum download speed"),
_("KiB/s"), "downloading.svg"],
"menuitem_up_speed": [_("Upload Speed Limit"), _("Set the maximum upload speed"),
_("KiB/s"), "seeding.svg"],
"menuitem_max_connections": [_("Incoming Connections"), _("Set the maximum incoming connections"),
"", gtk.STOCK_NETWORK],
"menuitem_upload_slots": [_("Peer Upload Slots"), _("Set the maximum upload slots"),
"", gtk.STOCK_SORT_ASCENDING],
"menuitem_stop_seed_at_ratio": [_("Stop Seed At Ratio"), "Stop torrent seeding at ratio", "", None]
}
core_key = status_map[widget.name][0]
core_key_global = status_map[widget.name][1]
def _on_torrent_status(status):
other_dialog = other_dialog_info[widget.name]
# Add the default using status value
if status:
other_dialog.append(status[core_key_global])
def set_value(value):
if value is not None:
if value == 0:
value += -1
options = {core_key: value}
if core_key == "stop_ratio":
options["stop_at_ratio"] = True
client.core.set_torrent_options(torrent_ids, options)
dialog = dialogs.OtherDialog(*other_dialog)
dialog.run().addCallback(set_value)
torrent_ids = component.get("TorrentView").get_selected_torrents()
if len(torrent_ids) == 1:
core_key_global = core_key
d = component.get("SessionProxy").get_torrent_status(torrent_ids[0], {core_key})
else:
d = client.core.get_config_values([core_key_global])
d.addCallback(_on_torrent_status)
def on_menuitem_set_automanaged_on(self, widget):
for torrent in component.get("TorrentView").get_selected_torrents():
@ -461,6 +489,10 @@ class MenuBar(component.Component):
for torrent in component.get("TorrentView").get_selected_torrents():
client.core.set_torrent_auto_managed(torrent, False)
def on_menuitem_set_stop_seed_at_ratio_disable(self, widget):
client.core.set_torrent_options(component.get("TorrentView").get_selected_torrents(),
{"stop_at_ratio": False})
def on_menuitem_sidebar_zero_toggled(self, widget):
self.config["sidebar_show_zero"] = widget.get_active()
component.get("FilterTreeView").update()

View File

@ -41,7 +41,8 @@ import logging
from deluge.ui.client import client
import deluge.component as component
import deluge.common
import common
from deluge.ui.gtkui import common
from deluge.ui.gtkui import dialogs
from deluge.configmanager import ConfigManager
log = logging.getLogger(__name__)
@ -123,7 +124,7 @@ class StatusBar(component.Component):
self.config = ConfigManager("gtkui.conf")
# Status variables that are updated via callback
self.max_connections = -1
self.max_connections_global = -1
self.num_connections = 0
self.max_download_speed = -1.0
self.download_rate = ""
@ -292,12 +293,11 @@ class StatusBar(component.Component):
This is called when we receive a ConfigValueChangedEvent from
the core.
"""
if key in self.config_value_changed_dict.keys():
self.config_value_changed_dict[key](value)
def _on_max_connections_global(self, max_connections):
self.max_connections = max_connections
self.max_connections_global = max_connections
self.update_connections_label()
def _on_dht(self, value):
@ -345,10 +345,10 @@ class StatusBar(component.Component):
def update_connections_label(self):
# Set the max connections label
if self.max_connections < 0:
if self.max_connections_global < 0:
label_string = "%s" % self.num_connections
else:
label_string = "%s (%s)" % (self.num_connections, self.max_connections)
label_string = "%s (%s)" % (self.num_connections, self.max_connections_global)
self.connections_item.set_text(label_string)
@ -377,13 +377,47 @@ class StatusBar(component.Component):
self.upload_item.set_text(label_string)
def update_traffic_label(self):
label_string = "%.2f/%.2f %s" % (self.download_protocol_rate, self.upload_protocol_rate, _("KiB/s"))
label_string = "%i/%i %s" % (self.download_protocol_rate, self.upload_protocol_rate, _("KiB/s"))
self.traffic_item.set_text(label_string)
def update(self):
# Send status request
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"),
_("KiB/s"), "downloading.svg", self.max_download_speed),
"max_upload_speed": (_("Upload Speed Limit"), _("Set the maximum upload speed"),
_("KiB/s"), "seeding.svg", self.max_upload_speed),
"max_connections_global": (_("Incoming Connections"), _("Set the maximum incoming connections"),
"", gtk.STOCK_NETWORK, 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.RESPONSE_OK:
set_value(dialog.get_value())
dialog = dialogs.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):
menu = common.build_menu_radio_list(
self.config["tray_download_speed_list"],
@ -395,22 +429,7 @@ class StatusBar(component.Component):
def _on_set_download_speed(self, widget):
log.debug("_on_set_download_speed")
if widget.get_name() == "unlimited":
value = -1
elif widget.get_name() == "other":
value = common.show_other_dialog(
_("Set Maximum Download Speed"), _("KiB/s"), None, "downloading.svg", self.max_download_speed)
if value is None:
return
else:
value = float(widget.get_children()[0].get_text().split(" ")[0])
log.debug("value: %s", value)
# Set the config in the core
if value != self.max_download_speed:
client.core.set_config({"max_download_speed": value})
self.set_limit_value(widget, "max_download_speed")
def _on_upload_item_clicked(self, widget, event):
menu = common.build_menu_radio_list(
@ -423,49 +442,19 @@ class StatusBar(component.Component):
def _on_set_upload_speed(self, widget):
log.debug("_on_set_upload_speed")
if widget.get_name() == "unlimited":
value = -1
elif widget.get_name() == "other":
value = common.show_other_dialog(
_("Set Maximum Upload Speed"), _("KiB/s"), None, "seeding.svg", self.max_upload_speed)
if value is None:
return
else:
value = float(widget.get_children()[0].get_text().split(" ")[0])
log.debug("value: %s", value)
# Set the config in the core
if value != self.max_upload_speed:
client.core.set_config({"max_upload_speed": value})
self.set_limit_value(widget, "max_upload_speed")
def _on_connection_item_clicked(self, widget, event):
menu = common.build_menu_radio_list(
self.config["connection_limit_list"],
self._on_set_connection_limit,
self.max_connections, show_notset=True, show_other=True)
self.max_connections_global, show_notset=True, show_other=True)
menu.show_all()
menu.popup(None, None, None, event.button, event.time)
def _on_set_connection_limit(self, widget):
log.debug("_on_set_connection_limit")
if widget.get_name() == "unlimited":
value = -1
elif widget.get_name() == "other":
value = common.show_other_dialog(
_("Set Maximum Connections"), "", gtk.STOCK_NETWORK, None, self.max_connections)
if value is None:
return
else:
value = int(widget.get_children()[0].get_text().split(" ")[0])
log.debug("value: %s", value)
# Set the config in the core
if value != self.max_connections:
client.core.set_config({"max_connections_global": value})
self.set_limit_value(widget, "max_connections_global")
def _on_health_icon_clicked(self, widget, event):
component.get("Preferences").show("Network")

View File

@ -46,10 +46,12 @@ import deluge.component as component
from deluge.ui.client import client
import deluge.common
from deluge.configmanager import ConfigManager
from deluge.ui.gtkui import dialogs
import common
log = logging.getLogger(__name__)
class SystemTray(component.Component):
def __init__(self):
component.Component.__init__(self, "SystemTray", interval=4)
@ -102,11 +104,11 @@ class SystemTray(component.Component):
self.tray_menu = self.builder.get_object("tray_menu")
if appindicator and self.config["enable_appindicator"]:
log.debug("Enabling the Application Indicator..")
self.indicator = appindicator.Indicator (
"deluge", "deluge", appindicator.CATEGORY_APPLICATION_STATUS)
log.debug("Enabling the Application Indicator...")
self.indicator = appindicator.Indicator("deluge", "deluge",
appindicator.CATEGORY_APPLICATION_STATUS)
try:
self.indicator.set_property ("title", _("Deluge"))
self.indicator.set_property("title", _("Deluge"))
except TypeError:
# Catch 'title' property error for previous appindicator versions
pass
@ -139,9 +141,9 @@ class SystemTray(component.Component):
self.tray.connect("popup-menu", self.on_tray_popup)
self.builder.get_object("download-limit-image").set_from_file(
deluge.common.get_pixmap("downloading16.png"))
deluge.common.get_pixmap("downloading16.png"))
self.builder.get_object("upload-limit-image").set_from_file(
deluge.common.get_pixmap("seeding16.png"))
deluge.common.get_pixmap("seeding16.png"))
client.register_event_handler("ConfigValueChangedEvent", self.config_value_changed)
if client.connected():
@ -245,9 +247,10 @@ class SystemTray(component.Component):
else:
max_upload_speed = "%s %s" % (max_upload_speed, _("KiB/s"))
msg = '%s\n%s: %s (%s)\n%s: %s (%s)' % (\
_("Deluge"), _("Down"), self.download_rate, \
max_download_speed, _("Up"), self.upload_rate, max_upload_speed)
msg = '%s\n%s: %s (%s)\n%s: %s (%s)' % (
_("Deluge"), _("Down"), self.download_rate,
max_download_speed, _("Up"), self.upload_rate, max_upload_speed
)
# Set the tooltip
self.tray.set_tooltip(msg)
@ -257,15 +260,17 @@ class SystemTray(component.Component):
def build_tray_bwsetsubmenu(self):
# Create the Download speed list sub-menu
submenu_bwdownset = common.build_menu_radio_list(
self.config["tray_download_speed_list"], self.on_tray_setbwdown,
self.max_download_speed,
_("KiB/s"), show_notset=True, show_other=True)
self.config["tray_download_speed_list"], self.on_tray_setbwdown,
self.max_download_speed,
_("KiB/s"), show_notset=True, show_other=True
)
# Create the Upload speed list sub-menu
submenu_bwupset = common.build_menu_radio_list(
self.config["tray_upload_speed_list"], self.on_tray_setbwup,
self.max_upload_speed,
_("KiB/s"), show_notset=True, show_other=True)
self.config["tray_upload_speed_list"], self.on_tray_setbwup,
self.max_upload_speed,
_("KiB/s"), show_notset=True, show_other=True
)
# Add the sub-menus to the tray menu
self.builder.get_object("menuitem_download_limit").set_submenu(
submenu_bwdownset)
@ -276,7 +281,7 @@ class SystemTray(component.Component):
submenu_bwdownset.show_all()
submenu_bwupset.show_all()
def disable(self,invert_app_ind_conf=False):
def disable(self, invert_app_ind_conf=False):
"""Disables the system tray icon or appindicator."""
try:
if invert_app_ind_conf:
@ -347,8 +352,7 @@ class SystemTray(component.Component):
if deluge.common.windows_check():
popup_function = None
button = 0
self.tray_menu.popup(None, None, popup_function,
button, activate_time, status_icon)
self.tray_menu.popup(None, None, popup_function, button, activate_time, status_icon)
def on_menuitem_show_deluge_activate(self, menuitem):
log.debug("on_menuitem_show_deluge_activate")
@ -391,16 +395,18 @@ class SystemTray(component.Component):
#ignore previous radiomenuitem value
if not widget.get_active():
return
self.setbwlimit(widget, _("Set Maximum Download Speed"), "max_download_speed",
"tray_download_speed_list", self.max_download_speed, "downloading.svg")
self.setbwlimit(widget, _("Download Speed Limit"), _("Set the maximum download speed"),
"max_download_speed", "tray_download_speed_list", self.max_download_speed,
"downloading.svg")
def on_tray_setbwup(self, widget, data=None):
if isinstance(widget, gtk.RadioMenuItem):
#ignore previous radiomenuitem value
if not widget.get_active():
return
self.setbwlimit(widget, _("Set Maximum Upload Speed"), "max_upload_speed",
"tray_upload_speed_list", self.max_upload_speed, "seeding.svg")
self.setbwlimit(widget, _("Upload Speed Limit"), _("Set the maximum upload speed"),
"max_upload_speed", "tray_upload_speed_list", self.max_upload_speed,
"seeding.svg")
def _on_window_hide(self, widget, data=None):
"""_on_window_hide - update the menuitem's status"""
@ -412,20 +418,23 @@ class SystemTray(component.Component):
log.debug("_on_window_show")
self.builder.get_object("menuitem_show_deluge").set_active(True)
def setbwlimit(self, widget, string, core_key, ui_key, default, image):
def setbwlimit(self, widget, header, text, core_key, ui_key, default, image):
"""Sets the bandwidth limit based on the user selection."""
value = widget.get_children()[0].get_text().split(" ")[0]
log.debug('setbwlimit: %s', value)
if widget.get_name() == "unlimited":
value = -1
if widget.get_name() == "other":
value = common.show_other_dialog(string, _("KiB/s"), None, image, default)
if value == None:
def set_value(value):
log.debug('setbwlimit: %s', value)
if value is None:
return
elif value == 0:
value = -1
# Set the config in the core
client.core.set_config({core_key: value})
# Set the config in the core
client.core.set_config({core_key: value})
if widget.get_name() == "unlimited":
set_value(-1)
elif widget.get_name() == "other":
dialog = dialogs.OtherDialog(header, text, _("KiB/s"), image, default)
dialog.run().addCallback(set_value)
else:
set_value(widget.get_children()[0].get_text().split(" ")[0])
def unlock_tray(self, is_showing_dlg=[False]):
try:
@ -445,8 +454,8 @@ class SystemTray(component.Component):
entered_pass.set_visibility(False)
self.tray_lock = gtk.Dialog(title="", parent=self.window.window,
buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK,
gtk.RESPONSE_OK))
buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
gtk.STOCK_OK, gtk.RESPONSE_OK))
self.tray_lock.set_default_response(gtk.RESPONSE_OK)
self.tray_lock.set_has_separator(False)