summaryrefslogtreecommitdiffstats
path: root/deluge/ui/gtk3/dialogs.py
diff options
context:
space:
mode:
Diffstat (limited to 'deluge/ui/gtk3/dialogs.py')
-rw-r--r--deluge/ui/gtk3/dialogs.py498
1 files changed, 498 insertions, 0 deletions
diff --git a/deluge/ui/gtk3/dialogs.py b/deluge/ui/gtk3/dialogs.py
new file mode 100644
index 0000000..db337d3
--- /dev/null
+++ b/deluge/ui/gtk3/dialogs.py
@@ -0,0 +1,498 @@
+#
+# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
+#
+# 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.
+#
+
+# pylint: disable=super-on-old-class
+
+from collections import namedtuple
+
+from gi.repository import Gtk
+from twisted.internet import defer
+
+import deluge.component as component
+from deluge.common import windows_check
+
+from .common import get_deluge_icon, get_pixbuf
+
+Account = namedtuple('Account', 'username password authlevel')
+
+
+class BaseDialog(Gtk.Dialog):
+ """
+ Base dialog class that should be used with all dialogs.
+ """
+
+ def __init__(self, header, text, icon, buttons, parent=None):
+ """
+ :param header: str, the header portion of the dialog
+ :param text: str, the text body of the dialog
+ :param icon: icon name from icon theme or icon filename.
+ :param buttons: tuple, of icon name and responses
+ :param parent: gtkWindow, the parent window, if None it will default to the
+ MainWindow
+ """
+ super().__init__(
+ title=header,
+ parent=parent if parent else component.get('MainWindow').window,
+ flags=Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
+ buttons=buttons,
+ )
+
+ self.set_icon(get_deluge_icon())
+
+ self.connect('delete-event', self._on_delete_event)
+ self.connect('response', self._on_response)
+
+ # Setup all the formatting and such to make our dialog look pretty
+ self.set_border_width(5)
+ self.set_default_size(200, 100)
+ hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, spacing=5)
+ image = Gtk.Image()
+ if icon.endswith('.svg') or icon.endswith('.png'):
+ # Hack for Windows since it doesn't support svg
+ if icon.endswith('.svg') and windows_check():
+ icon = icon.rpartition('.svg')[0] + '16.png'
+ image.set_from_pixbuf(get_pixbuf(icon, 24))
+ else:
+ image.set_from_icon_name(icon, Gtk.IconSize.LARGE_TOOLBAR)
+ image.set_alignment(0.5, 0.0)
+ hbox.pack_start(image, False, False, 0)
+ vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, spacing=5)
+ tlabel = Gtk.Label(label=text)
+ tlabel.set_use_markup(True)
+ tlabel.set_line_wrap(True)
+ tlabel.set_alignment(0.0, 0.5)
+ vbox.pack_start(tlabel, False, False, 0)
+ hbox.pack_start(vbox, False, False, 0)
+ self.vbox.pack_start(hbox, False, False, 0)
+ self.vbox.set_spacing(5)
+ self.vbox.show_all()
+
+ def _on_delete_event(self, widget, event):
+ self.destroy()
+ self.deferred.callback(Gtk.ResponseType.DELETE_EVENT)
+
+ def _on_response(self, widget, response):
+ self.destroy()
+ self.deferred.callback(response)
+
+ def run(self):
+ """
+ Shows the dialog and returns a Deferred object. The deferred, when fired
+ will contain the response ID.
+ """
+ self.deferred = defer.Deferred()
+ self.show()
+ return self.deferred
+
+
+class YesNoDialog(BaseDialog):
+ """
+ Displays a dialog asking the user to select Yes or No to a question.
+
+ When run(), it will return either a Gtk.ResponseType.YES or a Gtk.ResponseType.NO.
+
+ """
+
+ def __init__(self, header, text, parent=None):
+ """
+ :param header: see `:class:BaseDialog`
+ :param text: see `:class:BaseDialog`
+ :param parent: see `:class:BaseDialog`
+ """
+ super().__init__(
+ header,
+ text,
+ 'dialog-question',
+ (_('_No'), Gtk.ResponseType.NO, _('_Yes'), Gtk.ResponseType.YES),
+ parent,
+ )
+ # Use the preferred size calculated from the content
+ self.set_default_size(-1, -1)
+
+
+class InformationDialog(BaseDialog):
+ """
+ Displays an information dialog.
+
+ When run(), it will return a Gtk.ResponseType.CLOSE.
+ """
+
+ def __init__(self, header, text, parent=None):
+ """
+ :param header: see `:class:BaseDialog`
+ :param text: see `:class:BaseDialog`
+ :param parent: see `:class:BaseDialog`
+ """
+ super().__init__(
+ header,
+ text,
+ 'dialog-information',
+ (_('_Close'), Gtk.ResponseType.CLOSE),
+ parent,
+ )
+
+
+class ErrorDialog(BaseDialog):
+ """
+ Displays an error dialog with optional details text for more information.
+
+ When run(), it will return a Gtk.ResponseType.CLOSE.
+ """
+
+ def __init__(self, header, text, parent=None, details=None, traceback=False):
+ """
+ :param header: see `:class:BaseDialog`
+ :param text: see `:class:BaseDialog`
+ :param parent: see `:class:BaseDialog`
+ :param details: extra information that will be displayed in a
+ scrollable textview
+ :type details: string
+ :param traceback: show the traceback information in the details area
+ :type traceback: bool
+ """
+ super().__init__(
+ header, text, 'dialog-error', (_('_Close'), Gtk.ResponseType.CLOSE), parent
+ )
+
+ if traceback:
+ import sys
+ import traceback
+
+ tb = sys.exc_info()
+ tb = traceback.format_exc(tb[2])
+ if details:
+ details += '\n' + tb
+ else:
+ details = tb
+
+ if details:
+ self.set_default_size(600, 400)
+ textview = Gtk.TextView()
+ textview.set_editable(False)
+ textview.get_buffer().set_text(details)
+ sw = Gtk.ScrolledWindow()
+ sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
+ sw.set_shadow_type(Gtk.ShadowType.IN)
+ sw.add(textview)
+ label = Gtk.Label(label=_('Details:'))
+ label.set_alignment(0.0, 0.5)
+ self.vbox.pack_start(label, False, False, 0)
+ self.vbox.pack_start(sw, True, True, 0)
+ self.vbox.show_all()
+
+
+class AuthenticationDialog(BaseDialog):
+ """
+ Displays a dialog with entry fields asking for username and password.
+
+ When run(), it will return either a Gtk.ResponseType.CANCEL or a
+ Gtk.ResponseType.OK.
+ """
+
+ def __init__(self, err_msg='', username=None, parent=None):
+ """
+ :param err_msg: the error message we got back from the server
+ :type err_msg: string
+ """
+ super().__init__(
+ _('Authenticate'),
+ err_msg,
+ 'dialog-password',
+ (_('_Cancel'), Gtk.ResponseType.CANCEL, _('C_onnect'), Gtk.ResponseType.OK),
+ parent,
+ )
+
+ table = Gtk.Table(2, 2, False)
+ self.username_label = Gtk.Label()
+ 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()
+ table.attach(self.username_label, 0, 1, 0, 1)
+ 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_alignment(1.0, 0.5)
+ self.password_label.set_padding(5, 5)
+ self.password_entry = Gtk.Entry()
+ self.password_entry.set_visibility(False)
+ self.password_entry.connect('activate', self.on_password_activate)
+ table.attach(self.password_label, 0, 1, 1, 2)
+ table.attach(self.password_entry, 1, 2, 1, 2)
+
+ self.vbox.pack_start(table, False, False, padding=5)
+ self.set_focus(self.password_entry)
+ if username:
+ self.username_entry.set_text(username)
+ self.username_entry.set_editable(False)
+ self.set_focus(self.password_entry)
+ else:
+ self.set_focus(self.username_entry)
+ self.show_all()
+
+ def get_username(self):
+ return self.username_entry.get_text()
+
+ def get_password(self):
+ return self.password_entry.get_text()
+
+ def on_password_activate(self, widget):
+ self.response(Gtk.ResponseType.OK)
+
+
+class AccountDialog(BaseDialog):
+ def __init__(
+ self,
+ username=None,
+ password=None,
+ authlevel=None,
+ levels_mapping=None,
+ parent=None,
+ ):
+ if username:
+ super().__init__(
+ _('Edit Account'),
+ _('Edit existing account'),
+ 'dialog-information',
+ (
+ _('_Cancel'),
+ Gtk.ResponseType.CANCEL,
+ _('_Apply'),
+ Gtk.ResponseType.OK,
+ ),
+ parent,
+ )
+ else:
+ super().__init__(
+ _('New Account'),
+ _('Create a new account'),
+ 'dialog-information',
+ (_('_Cancel'), Gtk.ResponseType.CANCEL, _('_Add'), Gtk.ResponseType.OK),
+ parent,
+ )
+
+ self.account = None
+
+ table = Gtk.Table(2, 3, False)
+ username_label = Gtk.Label()
+ username_label.set_markup('<b>' + _('Username:') + '</b>')
+ username_label.set_alignment(1.0, 0.5)
+ username_label.set_padding(5, 5)
+ self.username_entry = Gtk.Entry()
+ table.attach(username_label, 0, 1, 0, 1)
+ table.attach(self.username_entry, 1, 2, 0, 1)
+
+ authlevel_label = Gtk.Label()
+ authlevel_label.set_markup('<b>' + _('Authentication Level:') + '</b>')
+ authlevel_label.set_alignment(1.0, 0.5)
+ authlevel_label.set_padding(5, 5)
+
+ # combo_box_new_text is deprecated but no other pygtk alternative.
+ self.authlevel_combo = Gtk.ComboBoxText()
+ active_idx = None
+ for idx, level in enumerate(levels_mapping):
+ self.authlevel_combo.append_text(level)
+ if authlevel and authlevel == level:
+ active_idx = idx
+ elif not authlevel and level == 'DEFAULT':
+ active_idx = idx
+
+ if active_idx is not None:
+ self.authlevel_combo.set_active(active_idx)
+
+ table.attach(authlevel_label, 0, 1, 1, 2)
+ table.attach(self.authlevel_combo, 1, 2, 1, 2)
+
+ password_label = Gtk.Label()
+ password_label.set_markup('<b>' + _('Password:') + '</b>')
+ password_label.set_alignment(1.0, 0.5)
+ password_label.set_padding(5, 5)
+ self.password_entry = Gtk.Entry()
+ self.password_entry.set_visibility(False)
+ table.attach(password_label, 0, 1, 2, 3)
+ table.attach(self.password_entry, 1, 2, 2, 3)
+
+ self.vbox.pack_start(table, False, False, padding=5)
+ if username:
+ self.username_entry.set_text(username)
+ self.username_entry.set_editable(False)
+ else:
+ self.set_focus(self.username_entry)
+
+ if password:
+ self.password_entry.set_text(username)
+
+ self.vbox.show_all()
+
+ def _on_response(self, widget, response):
+ if response == Gtk.ResponseType.OK:
+ self.account = Account(
+ self.username_entry.get_text(),
+ self.password_entry.get_text(),
+ self.authlevel_combo.get_active_text(),
+ )
+ self.destroy()
+ self.deferred.callback(response)
+
+
+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 = 'dialog-information'
+
+ super().__init__(
+ header,
+ text,
+ icon,
+ (_('_Cancel'), Gtk.ResponseType.CANCEL, _('_Apply'), Gtk.ResponseType.OK),
+ parent,
+ )
+
+ hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, spacing=5)
+ alignment_spacer = Gtk.Alignment()
+ hbox.pack_start(alignment_spacer, True, True, 0)
+ alignment_spin = Gtk.Alignment(xalign=1, yalign=0.5, xscale=1, yscale=1)
+ adjustment_spin = Gtk.Adjustment(
+ value=-1, lower=-1, upper=2097151, step_increment=1, page_increment=10
+ )
+ self.spinbutton = Gtk.SpinButton(
+ adjustment=adjustment_spin, climb_rate=0, digits=0
+ )
+ 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, False, True, 0)
+ label_type = Gtk.Label()
+ label_type.set_text(unit_text)
+ label_type.set_alignment(0.0, 0.5)
+ hbox.pack_start(label_type, True, True, 0)
+
+ 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.ResponseType.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()
+
+
+class PasswordDialog(BaseDialog):
+ """
+ Displays a dialog with an entry field asking for a password.
+
+ When run(), it will return either a Gtk.ResponseType.CANCEL or a Gtk.ResponseType.OK.
+ """
+
+ def __init__(self, password_msg='', parent=None):
+ """
+ :param password_msg: the error message we got back from the server
+ :type password_msg: string
+ """
+ super().__init__(
+ header=_('Password Protected'),
+ text=password_msg,
+ icon='dialog-password',
+ buttons=(
+ _('_Cancel'),
+ Gtk.ResponseType.CANCEL,
+ _('_OK'),
+ Gtk.ResponseType.OK,
+ ),
+ parent=parent,
+ )
+
+ table = Gtk.Table(1, 2, False)
+ self.password_label = Gtk.Label()
+ 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()
+ self.password_entry.set_visibility(False)
+ self.password_entry.connect('activate', self.on_password_activate)
+ table.attach(self.password_label, 0, 1, 1, 2)
+ table.attach(self.password_entry, 1, 2, 1, 2)
+
+ self.vbox.pack_start(table, False, False, padding=5)
+ self.set_focus(self.password_entry)
+
+ self.show_all()
+
+ def get_password(self):
+ return self.password_entry.get_text()
+
+ def on_password_activate(self, widget):
+ self.response(Gtk.ResponseType.OK)
+
+
+class CopyMagnetDialog(BaseDialog):
+ """
+ Displays a dialog with a magnet URI
+ """
+
+ def __init__(self, torrent_magnet='', parent=None):
+ super().__init__(
+ header=_('Copy Magnet URI'),
+ text='',
+ icon='magnet_copy.svg',
+ buttons=(_('_Close'), Gtk.ResponseType.CLOSE),
+ parent=parent,
+ )
+ self.copied = False
+
+ table = Gtk.Table(1, 2, False)
+ self.magnet_entry = Gtk.Entry()
+ self.magnet_entry.set_text(torrent_magnet)
+ self.magnet_entry.set_editable(False)
+ self.magnet_entry.connect('copy-clipboard', self.on_copy_emitted)
+ table.attach(self.magnet_entry, 0, 1, 0, 1)
+
+ copy_button = Gtk.Button.new_with_label(_('Copy'))
+ copy_button.connect('clicked', self.on_copy_clicked)
+ table.attach(copy_button, 1, 2, 0, 1)
+
+ self.vbox.pack_start(table, False, False, padding=5)
+ self.set_focus(self.magnet_entry)
+
+ self.show_all()
+
+ def on_copy_clicked(self, widget):
+ self.magnet_entry.select_region(0, -1)
+ self.magnet_entry.copy_clipboard()
+ self.magnet_entry.set_position(0)
+ self.copied = True
+
+ def on_copy_emitted(self, widget):
+ self.copied = True