[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 @@
# # -*- coding: utf-8 -*-
# common.py
# #
# Copyright (C) 2008 Marcos Pinto ('markybob') <markybob@gmail.com> # 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.""" """Common functions for various parts of gtkui to use."""
import os import os
import pygtk
pygtk.require('2.0')
import gtk
import logging import logging
import cPickle import cPickle
import shutil import shutil
import deluge.component as component import pygtk
pygtk.require('2.0')
import gtk
from gobject import GError
import deluge.common import deluge.common
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
def get_logo(size): 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(): 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"), \ filename = "deluge.png"
size, size) try:
else: return gtk.gdk.pixbuf_new_from_file_at_size(deluge.common.get_pixmap(filename), size, size)
try: except GError as ex:
return gtk.gdk.pixbuf_new_from_file_at_size(deluge.common.get_pixmap("deluge.svg"), \ log.warning(ex)
size, size)
except Exception, e:
log.warning(e)
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: def build_menu_radio_list(value_list, callback, pref_value=None, suffix=None, show_notset=False,
activated_label = _("Activated") 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() menu = gtk.Menu()
group = None 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 pref_value > -1 and pref_value not in value_list:
if suffix != None: value_list.pop()
menuitem = gtk.RadioMenuItem(group, str(value) + " " + \ value_list.append(pref_value)
suffix)
else:
menuitem = gtk.RadioMenuItem(group, str(value))
group = menuitem for value in sorted(value_list):
item_text = str(value)
if value == pref_value and pref_value != None: if suffix:
menuitem.set_active(True) item_text += " " + suffix
menuitem = gtk.RadioMenuItem(group, item_text)
if callback != None: group = menuitem
menuitem.connect("toggled", callback) if pref_value and value == pref_value:
menuitem.set_active(True)
menu.append(menuitem) if callback:
menuitem.connect("toggled", callback)
if show_activated is True: menu.append(menuitem)
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: if show_notset:
menuitem = gtk.RadioMenuItem(group, notset_label) menuitem = gtk.RadioMenuItem(group, notset_label)
menuitem.set_name("unlimited") menuitem.set_name("unlimited")
if pref_value < notset_lessthan and pref_value != None: if pref_value and pref_value < notset_lessthan:
menuitem.set_active(True)
if show_activated and pref_value == 1:
menuitem.set_active(True) menuitem.set_active(True)
menuitem.connect("toggled", callback) menuitem.connect("toggled", callback)
menu.append(menuitem) menu.append(menuitem)
# Add the Other... menuitem if show_other:
if show_other is True:
menuitem = gtk.SeparatorMenuItem() menuitem = gtk.SeparatorMenuItem()
menu.append(menuitem) menu.append(menuitem)
menuitem = gtk.MenuItem(_("Other...")) menuitem = gtk.MenuItem(_("Other..."))
@ -132,83 +97,25 @@ def build_menu_radio_list(value_list, callback, pref_value=None,
return menu 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): 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 This effectively moves itr plus it's children to be a child of parent in treestore
:param treestore: gtkTreeStore, the treestore Params:
:param itr: gtkTreeIter, the iter to move treestore (gtkTreeStore): the treestore
:param parent: gtkTreeIter, the new parent for itr itr (gtkTreeIter): the iter to move
:param move_siblings: bool. if True, it will move all itr's siblings to parent parent (gtkTreeIter): the new parent for itr
move_siblings (bool): if True, it will move all itr's siblings to parent
""" """
src = itr src = itr
def move_children(i, dest): def move_children(i, dest):
while i: 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 to_remove = i
if treestore.iter_children(i): if treestore.iter_children(i):
move_children(treestore.iter_children(i), n) move_children(treestore.iter_children(i), n_cols)
if i != src: if i != src:
i = treestore.iter_next(i) i = treestore.iter_next(i)
else: else:
@ -219,12 +126,15 @@ def reparent_iter(treestore, itr, parent, move_siblings=False):
move_children(itr, parent) move_children(itr, parent)
def get_deluge_icon(): def get_deluge_icon():
""" """The deluge icon for use in dialogs.
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 It will first attempt to get the icon from the theme and will fallback to using an image
that is distributed with the package. that is distributed with the package.
Returns:
gtk.gdk.Pixbuf: the deluge icon
""" """
if deluge.common.windows_check(): if deluge.common.windows_check():
return get_logo(32) return get_logo(32)
@ -232,18 +142,19 @@ def get_deluge_icon():
try: try:
icon_theme = gtk.icon_theme_get_default() icon_theme = gtk.icon_theme_get_default()
return icon_theme.load_icon("deluge", 64, 0) return icon_theme.load_icon("deluge", 64, 0)
except: except GError:
return get_logo(64) return get_logo(64)
def associate_magnet_links(overwrite=False): def associate_magnet_links(overwrite=False):
""" """
Associates magnet links to Deluge. Associates magnet links to Deluge.
:param overwrite: if this is True, the current setting will be overwritten Params:
:type overwrite: bool overwrite (bool): if this is True, the current setting will be overwritten
:returns: True if association was set
:rtype: bool
Returns:
bool: True if association was set
""" """
if not deluge.common.windows_check(): if not deluge.common.windows_check():
# gconf method is only available in a GNOME environment # gconf method is only available in a GNOME environment
@ -267,10 +178,13 @@ def associate_magnet_links(overwrite=False):
return False return False
return False return False
def save_pickled_state_file(filename, state): def save_pickled_state_file(filename, state):
"""Save a file in the config directory and creates a backup """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 from deluge.configmanager import get_config_dir
filepath = os.path.join(get_config_dir(), "gtkui_state", filename) 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) log.info("Restoring backup of %s from: %s", filename, filepath_bak)
shutil.move(filepath_bak, filepath) shutil.move(filepath_bak, filepath)
def load_pickled_state_file(filename): def load_pickled_state_file(filename):
"""Loads a file from the config directory, attempting backup if original fails to load. """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 from deluge.configmanager import get_config_dir
filepath = os.path.join(get_config_dir(), "gtkui_state", filename) filepath = os.path.join(get_config_dir(), "gtkui_state", filename)
@ -313,7 +232,7 @@ def load_pickled_state_file(filename):
try: try:
with open(_filepath, "rb") as _file: with open(_filepath, "rb") as _file:
state = cPickle.load(_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) log.warning("Unable to load %s: %s", _filepath, ex)
else: else:
log.info("Successfully loaded %s: %s", filename, _filepath) 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 # this exception statement from your version. If you delete this exception
# statement from all source files in the program, then also delete it here. # statement from all source files in the program, then also delete it here.
# #
import gtk import gtk
from twisted.internet import defer from twisted.internet import defer
from deluge.ui.gtkui import common from deluge.ui.gtkui import common
import deluge.component as component import deluge.component as component
import deluge.common
class BaseDialog(gtk.Dialog): class BaseDialog(gtk.Dialog):
@ -69,7 +69,14 @@ class BaseDialog(gtk.Dialog):
self.set_default_size(200, 100) self.set_default_size(200, 100)
hbox = gtk.HBox(spacing=5) hbox = gtk.HBox(spacing=5)
image = gtk.Image() 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) image.set_alignment(0.5, 0.0)
hbox.pack_start(image, False, False) hbox.pack_start(image, False, False)
vbox = gtk.VBox(spacing=5) vbox = gtk.VBox(spacing=5)
@ -105,6 +112,7 @@ class BaseDialog(gtk.Dialog):
self.show() self.show()
return self.deferred return self.deferred
class YesNoDialog(BaseDialog): class YesNoDialog(BaseDialog):
""" """
Displays a dialog asking the user to select Yes or No to a question. Displays a dialog asking the user to select Yes or No to a question.
@ -122,9 +130,10 @@ class YesNoDialog(BaseDialog):
header, header,
text, text,
gtk.STOCK_DIALOG_QUESTION, 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) parent)
class InformationDialog(BaseDialog): class InformationDialog(BaseDialog):
""" """
Displays an information dialog. Displays an information dialog.
@ -144,6 +153,7 @@ class InformationDialog(BaseDialog):
(gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE), (gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE),
parent) parent)
class ErrorDialog(BaseDialog): class ErrorDialog(BaseDialog):
""" """
Displays an error dialog with optional details text for more information. 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.pack_start(sw)
self.vbox.show_all() self.vbox.show_all()
class AuthenticationDialog(BaseDialog): class AuthenticationDialog(BaseDialog):
""" """
Displays a dialog with entry fields asking for username and password. Displays a dialog with entry fields asking for username and password.
@ -213,7 +224,7 @@ class AuthenticationDialog(BaseDialog):
table = gtk.Table(2, 2, False) table = gtk.Table(2, 2, False)
self.username_label = gtk.Label() 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_alignment(1.0, 0.5)
self.username_label.set_padding(5, 5) self.username_label.set_padding(5, 5)
self.username_entry = gtk.Entry() self.username_entry = gtk.Entry()
@ -221,7 +232,7 @@ class AuthenticationDialog(BaseDialog):
table.attach(self.username_entry, 1, 2, 0, 1) table.attach(self.username_entry, 1, 2, 0, 1)
self.password_label = gtk.Label() 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_alignment(1.0, 0.5)
self.password_label.set_padding(5, 5) self.password_label.set_padding(5, 5)
self.password_entry = gtk.Entry() self.password_entry = gtk.Entry()
@ -249,6 +260,7 @@ class AuthenticationDialog(BaseDialog):
def on_password_activate(self, widget): def on_password_activate(self, widget):
self.response(gtk.RESPONSE_OK) self.response(gtk.RESPONSE_OK)
class AccountDialog(BaseDialog): class AccountDialog(BaseDialog):
def __init__(self, username=None, password=None, authlevel=None, def __init__(self, username=None, password=None, authlevel=None,
levels_mapping=None, parent=None): levels_mapping=None, parent=None):
@ -273,7 +285,7 @@ class AccountDialog(BaseDialog):
table = gtk.Table(2, 3, False) table = gtk.Table(2, 3, False)
self.username_label = gtk.Label() 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_alignment(1.0, 0.5)
self.username_label.set_padding(5, 5) self.username_label.set_padding(5, 5)
self.username_entry = gtk.Entry() self.username_entry = gtk.Entry()
@ -281,7 +293,7 @@ class AccountDialog(BaseDialog):
table.attach(self.username_entry, 1, 2, 0, 1) table.attach(self.username_entry, 1, 2, 0, 1)
self.authlevel_label = gtk.Label() 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_alignment(1.0, 0.5)
self.authlevel_label.set_padding(5, 5) self.authlevel_label.set_padding(5, 5)
@ -289,7 +301,7 @@ class AccountDialog(BaseDialog):
active_idx = None active_idx = None
for idx, level in enumerate(levels_mapping.keys()): for idx, level in enumerate(levels_mapping.keys()):
self.authlevel_combo.append_text(level) self.authlevel_combo.append_text(level)
if authlevel and authlevel==level: if authlevel and authlevel == level:
active_idx = idx active_idx = idx
elif not authlevel and level == 'DEFAULT': elif not authlevel and level == 'DEFAULT':
active_idx = idx active_idx = idx
@ -301,7 +313,7 @@ class AccountDialog(BaseDialog):
table.attach(self.authlevel_combo, 1, 2, 1, 2) table.attach(self.authlevel_combo, 1, 2, 1, 2)
self.password_label = gtk.Label() 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_alignment(1.0, 0.5)
self.password_label.set_padding(5, 5) self.password_label.set_padding(5, 5)
self.password_entry = gtk.Entry() self.password_entry = gtk.Entry()
@ -331,3 +343,63 @@ class AccountDialog(BaseDialog):
combobox = self.authlevel_combo combobox = self.authlevel_combo
level = combobox.get_model()[combobox.get_active()][0] level = combobox.get_model()[combobox.get_active()][0]
return level 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> <property name="use_stock">False</property>
</object> </object>
</child> </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> <child>
<object class="GtkMenuItem" id="menuitem_auto_managed"> <object class="GtkMenuItem" id="menuitem_auto_managed">
<property name="visible">True</property> <property name="visible">True</property>

View File

@ -44,13 +44,13 @@ import deluge.error
import deluge.component as component import deluge.component as component
from deluge.ui.client import client from deluge.ui.client import client
import deluge.common import deluge.common
import common
import dialogs import dialogs
from deluge.configmanager import ConfigManager from deluge.configmanager import ConfigManager
from deluge.ui.gtkui.path_chooser import PathChooser from deluge.ui.gtkui.path_chooser import PathChooser
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class MenuBar(component.Component): class MenuBar(component.Component):
def __init__(self): 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")) 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", for menuitem in ("menuitem_down_speed", "menuitem_up_speed",
"menuitem_max_connections", "menuitem_upload_slots"): "menuitem_max_connections", "menuitem_upload_slots"):
submenu = gtk.Menu() submenu = gtk.Menu()
item = gtk.MenuItem(_("Set Unlimited")) item = gtk.MenuItem(_("Set Unlimited"))
item.set_name(menuitem) item.set_name(menuitem)
@ -108,6 +108,17 @@ class MenuBar(component.Component):
submenu.show_all() submenu.show_all()
self.builder.get_object("menuitem_auto_managed").set_submenu(submenu) 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.torrentmenu = self.builder.get_object("torrent_menu")
self.menu_torrent = self.main_builder.get_object("menu_torrent") 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_zero").set_active(self.config["sidebar_show_zero"])
self.main_builder.get_object("sidebar_show_trackers").set_active(self.config["sidebar_show_trackers"]) self.main_builder.get_object("sidebar_show_trackers").set_active(self.config["sidebar_show_trackers"])
### Connect main window Signals ### ### Connect main window Signals ###
component.get("MainWindow").connect_signals({ component.get("MainWindow").connect_signals({
## File Menu ## File Menu
@ -144,8 +154,8 @@ class MenuBar(component.Component):
"on_menuitem_faq_activate": self.on_menuitem_faq_activate, "on_menuitem_faq_activate": self.on_menuitem_faq_activate,
"on_menuitem_community_activate": self.on_menuitem_community_activate, "on_menuitem_community_activate": self.on_menuitem_community_activate,
"on_menuitem_about_activate": self.on_menuitem_about_activate, "on_menuitem_about_activate": self.on_menuitem_about_activate,
"on_menuitem_sidebar_zero_toggled":self.on_menuitem_sidebar_zero_toggled, "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_trackers_toggled": self.on_menuitem_sidebar_trackers_toggled
}) })
# Connect menubar signals # Connect menubar signals
@ -204,8 +214,7 @@ class MenuBar(component.Component):
if client.get_auth_level() == deluge.common.AUTH_LEVEL_ADMIN: if client.get_auth_level() == deluge.common.AUTH_LEVEL_ADMIN:
# Get known accounts to allow changing ownership # Get known accounts to allow changing ownership
client.core.get_known_accounts().addCallback( 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): def stop(self):
log.debug("MenuBar stopping") log.debug("MenuBar stopping")
@ -315,6 +324,7 @@ class MenuBar(component.Component):
def on_menuitem_open_folder_activate(self, data=None): def on_menuitem_open_folder_activate(self, data=None):
log.debug("on_menuitem_open_folder") log.debug("on_menuitem_open_folder")
def _on_torrent_status(status): def _on_torrent_status(status):
deluge.common.open_file(status["save_path"]) deluge.common.open_file(status["save_path"])
for torrent_id in component.get("TorrentView").get_selected_torrents(): 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): def on_menuitem_set_other(self, widget):
log.debug("widget.name: %s", widget.name) log.debug("widget.name: %s", widget.name)
funcs = { status_map = {
"menuitem_down_speed": client.core.set_torrent_max_download_speed, "menuitem_down_speed": ["max_download_speed", "max_download_speed"],
"menuitem_up_speed": client.core.set_torrent_max_upload_speed, "menuitem_up_speed": ["max_upload_speed", "max_upload_speed"],
"menuitem_max_connections": client.core.set_torrent_max_connections, "menuitem_max_connections": ["max_connections", "max_connections_global"],
"menuitem_upload_slots": client.core.set_torrent_max_upload_slots "menuitem_upload_slots": ["max_upload_slots", "max_upload_slots_global"],
} "menuitem_stop_seed_at_ratio": ["stop_ratio", "stop_seed_ratio"]
# 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
)
} }
# Show the other dialog other_dialog_info = {
value = common.show_other_dialog(*other_dialog_info[widget.name]) "menuitem_down_speed": [_("Download Speed Limit"), _("Set the maximum download speed"),
if value and widget.name in funcs: _("KiB/s"), "downloading.svg"],
for torrent in component.get("TorrentView").get_selected_torrents(): "menuitem_up_speed": [_("Upload Speed Limit"), _("Set the maximum upload speed"),
funcs[widget.name](torrent, value) _("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): def on_menuitem_set_automanaged_on(self, widget):
for torrent in component.get("TorrentView").get_selected_torrents(): 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(): for torrent in component.get("TorrentView").get_selected_torrents():
client.core.set_torrent_auto_managed(torrent, False) 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): def on_menuitem_sidebar_zero_toggled(self, widget):
self.config["sidebar_show_zero"] = widget.get_active() self.config["sidebar_show_zero"] = widget.get_active()
component.get("FilterTreeView").update() component.get("FilterTreeView").update()

View File

@ -41,7 +41,8 @@ import logging
from deluge.ui.client import client from deluge.ui.client import client
import deluge.component as component import deluge.component as component
import deluge.common import deluge.common
import common from deluge.ui.gtkui import common
from deluge.ui.gtkui import dialogs
from deluge.configmanager import ConfigManager from deluge.configmanager import ConfigManager
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -123,7 +124,7 @@ class StatusBar(component.Component):
self.config = ConfigManager("gtkui.conf") self.config = ConfigManager("gtkui.conf")
# Status variables that are updated via callback # Status variables that are updated via callback
self.max_connections = -1 self.max_connections_global = -1
self.num_connections = 0 self.num_connections = 0
self.max_download_speed = -1.0 self.max_download_speed = -1.0
self.download_rate = "" self.download_rate = ""
@ -292,12 +293,11 @@ class StatusBar(component.Component):
This is called when we receive a ConfigValueChangedEvent from This is called when we receive a ConfigValueChangedEvent from
the core. the core.
""" """
if key in self.config_value_changed_dict.keys(): if key in self.config_value_changed_dict.keys():
self.config_value_changed_dict[key](value) self.config_value_changed_dict[key](value)
def _on_max_connections_global(self, max_connections): def _on_max_connections_global(self, max_connections):
self.max_connections = max_connections self.max_connections_global = max_connections
self.update_connections_label() self.update_connections_label()
def _on_dht(self, value): def _on_dht(self, value):
@ -345,10 +345,10 @@ class StatusBar(component.Component):
def update_connections_label(self): def update_connections_label(self):
# Set the max connections label # Set the max connections label
if self.max_connections < 0: if self.max_connections_global < 0:
label_string = "%s" % self.num_connections label_string = "%s" % self.num_connections
else: 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) self.connections_item.set_text(label_string)
@ -377,13 +377,47 @@ class StatusBar(component.Component):
self.upload_item.set_text(label_string) self.upload_item.set_text(label_string)
def update_traffic_label(self): 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) self.traffic_item.set_text(label_string)
def update(self): def update(self):
# Send status request # Send status request
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): def _on_download_item_clicked(self, widget, event):
menu = common.build_menu_radio_list( menu = common.build_menu_radio_list(
self.config["tray_download_speed_list"], self.config["tray_download_speed_list"],
@ -395,22 +429,7 @@ class StatusBar(component.Component):
def _on_set_download_speed(self, widget): def _on_set_download_speed(self, widget):
log.debug("_on_set_download_speed") log.debug("_on_set_download_speed")
self.set_limit_value(widget, "max_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})
def _on_upload_item_clicked(self, widget, event): def _on_upload_item_clicked(self, widget, event):
menu = common.build_menu_radio_list( menu = common.build_menu_radio_list(
@ -423,49 +442,19 @@ class StatusBar(component.Component):
def _on_set_upload_speed(self, widget): def _on_set_upload_speed(self, widget):
log.debug("_on_set_upload_speed") log.debug("_on_set_upload_speed")
self.set_limit_value(widget, "max_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})
def _on_connection_item_clicked(self, widget, event): def _on_connection_item_clicked(self, widget, event):
menu = common.build_menu_radio_list( menu = common.build_menu_radio_list(
self.config["connection_limit_list"], self.config["connection_limit_list"],
self._on_set_connection_limit, 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.show_all()
menu.popup(None, None, None, event.button, event.time) menu.popup(None, None, None, event.button, event.time)
def _on_set_connection_limit(self, widget): def _on_set_connection_limit(self, widget):
log.debug("_on_set_connection_limit") log.debug("_on_set_connection_limit")
self.set_limit_value(widget, "max_connections_global")
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})
def _on_health_icon_clicked(self, widget, event): def _on_health_icon_clicked(self, widget, event):
component.get("Preferences").show("Network") component.get("Preferences").show("Network")

View File

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