[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:
parent
813261df07
commit
ea7ef950a3
|
@ -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:
|
||||
filename = "deluge.png"
|
||||
try:
|
||||
return gtk.gdk.pixbuf_new_from_file_at_size(deluge.common.get_pixmap("deluge.svg"), \
|
||||
size, size)
|
||||
except Exception, e:
|
||||
log.warning(e)
|
||||
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))
|
||||
|
||||
item_text = str(value)
|
||||
if suffix:
|
||||
item_text += " " + suffix
|
||||
menuitem = gtk.RadioMenuItem(group, item_text)
|
||||
group = menuitem
|
||||
|
||||
if value == pref_value and pref_value != None:
|
||||
if pref_value and value == pref_value:
|
||||
menuitem.set_active(True)
|
||||
|
||||
if callback != None:
|
||||
if callback:
|
||||
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)
|
||||
|
||||
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)
|
||||
|
|
|
@ -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,6 +69,13 @@ class BaseDialog(gtk.Dialog):
|
|||
self.set_default_size(200, 100)
|
||||
hbox = gtk.HBox(spacing=5)
|
||||
image = gtk.Image()
|
||||
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)
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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()
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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,9 +104,9 @@ 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"))
|
||||
except TypeError:
|
||||
|
@ -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)
|
||||
|
@ -259,13 +262,15 @@ class SystemTray(component.Component):
|
|||
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)
|
||||
_("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)
|
||||
_("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)
|
||||
|
@ -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]
|
||||
def set_value(value):
|
||||
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:
|
||||
if value is None:
|
||||
return
|
||||
elif value == 0:
|
||||
value = -1
|
||||
# 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)
|
||||
|
||||
|
|
Loading…
Reference in New Issue