1
# Miro - an RSS based video player application
2
# Copyright (C) 2005-2010 Participatory Culture Foundation
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
# GNU General Public License for more details.
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18
# In addition, as a special exception, the copyright holders give
19
# permission to link the code of portions of this program with the OpenSSL
22
# You must obey the GNU General Public License in all respects for all of
23
# the code used other than OpenSSL. If you modify file(s) with this
24
# exception, you may extend this exception to your version of the file(s),
25
# but you are not obligated to do so. If you do not wish to do so, delete
26
# this exception statement from your version. If you delete this exception
27
# statement from all source files in the program, then also delete it here.
29
"""Handle dialog popups.
33
For dialogs where you just want to ask the user a question use the
34
``ChoiceDialog class``. Pass it a title, description and a the
35
buttons to display. Then call ``dialog.run``, passing it a callback.
38
dialog = dialog.ChoiceDialog("Do you like pizza?",
39
"Democracy would like to know if you enjoy eating pizza.",
40
dialog.BUTTON_YES, dialog.BUTTON_NO)
41
def handlePizzaAnswer(dialog):
42
if dialog.choice is None:
43
# handle the user closing the dialog windows
44
elif dialog.choice == dialog.BUTTON_YES:
46
elif dialog.choice == dialag.BUTTON_NO:
48
dialog.run(handlePizzaAnswer)
53
For more advanced usage, check out the other ``Dialog`` subclasses.
54
They will probably have different constructor arguments and may have
55
attributes other than choice that will be set. For example, the
56
``HTTPAuthDialog`` has a ``username`` and ``password`` attribute that
57
store what the user entered in the textboxes.
62
Frontends should implement the ``runDialog`` method in
63
``UIBackendDelegate`` class. It inputs a ``Dialog`` subclass and
64
displays it to the user. When the user clicks on a button, or closes
65
the dialog window, the frontend must call ``dialog.run_callback()``.
67
As we add new dialog boxes, the frontend may run into ``Dialog``
68
subclasses that it doesn't recognize. In that case, call
69
``dialog.run_callback(None)``.
71
The frontend can layout the window however it wants, in particular
72
buttons can be arranged with the default on the right or the left
73
depending on the platform. The default button is the 1st button in
74
the list. Frontends should try to recognize standard buttons and
75
display the stock icons for them.
80
from miro import eventloop
81
from miro import signals
83
from miro.gtcache import gettext as _
85
class DialogButton(object):
86
def __init__(self, text):
88
def __eq__(self, other):
89
return isinstance(other, DialogButton) and self.text == other.text
91
return "DialogButton(%r)" % util.stringify(self.text)
93
BUTTON_OK = DialogButton(_("Ok"))
94
BUTTON_APPLY = DialogButton(_("Apply"))
95
BUTTON_CLOSE = DialogButton(_("Close"))
96
BUTTON_CANCEL = DialogButton(_("Cancel"))
97
BUTTON_DONE = DialogButton(_("Done"))
98
BUTTON_YES = DialogButton(_("Yes"))
99
BUTTON_NO = DialogButton(_("No"))
100
BUTTON_QUIT = DialogButton(_("Quit"))
101
BUTTON_CONTINUE = DialogButton(_("Continue"))
102
BUTTON_IGNORE = DialogButton(_("Ignore"))
103
BUTTON_SUBMIT_REPORT = DialogButton(_("Submit Crash Report"))
104
BUTTON_MIGRATE = DialogButton(_("Migrate"))
105
BUTTON_DONT_MIGRATE = DialogButton(_("Don't Migrate"))
106
BUTTON_DOWNLOAD = DialogButton(_("Download"))
107
BUTTON_REMOVE_ENTRY = DialogButton(_("Remove Entry"))
108
BUTTON_DELETE_FILE = DialogButton(_("Delete File"))
109
BUTTON_DELETE_FILES = DialogButton(_("Delete Files"))
110
BUTTON_KEEP_VIDEOS = DialogButton(_("Keep Videos"))
111
BUTTON_DELETE_VIDEOS = DialogButton(_("Delete Videos"))
112
BUTTON_CREATE = DialogButton(_("Create"))
113
BUTTON_CREATE_FEED = DialogButton(_("Create Feed"))
114
BUTTON_CREATE_FOLDER = DialogButton(_("Create Folder"))
115
BUTTON_ADD = DialogButton(_("Add"))
116
BUTTON_ADD_INTO_NEW_FOLDER = DialogButton(_("Add Into New Folder"))
117
BUTTON_KEEP = DialogButton(_("Keep"))
118
BUTTON_DELETE = DialogButton(_("Delete"))
119
BUTTON_REMOVE = DialogButton(_("Remove"))
120
BUTTON_NOT_NOW = DialogButton(_("Not Now"))
121
BUTTON_CLOSE_TO_TRAY = DialogButton(_("Close to Tray"))
122
BUTTON_LAUNCH_MIRO = DialogButton(_("Launch Miro"))
123
BUTTON_DOWNLOAD_ANYWAY = DialogButton(_("Download Anyway"))
124
BUTTON_OPEN_IN_EXTERNAL_BROWSER = DialogButton(_("Open in External Browser"))
125
BUTTON_DONT_INSTALL = DialogButton(_("Don't Install"))
126
BUTTON_SUBSCRIBE = DialogButton(_("Subscribe"))
127
BUTTON_STOP_WATCHING = DialogButton(_("Stop Watching"))
128
BUTTON_RETRY = DialogButton(_("Retry"))
129
BUTTON_START_FRESH = DialogButton(_("Start Fresh"))
130
BUTTON_INCLUDE_DATABASE = DialogButton(_("Include Database"))
131
BUTTON_DONT_INCLUDE_DATABASE = DialogButton(_("Don't Include Database"))
133
class Dialog(object):
134
"""Abstract base class for dialogs.
136
def __init__(self, title, description, buttons):
138
self.description = description
139
self.buttons = buttons
140
self.event = threading.Event()
142
def run(self, callback):
143
self.callback = callback
145
signals.system.new_dialog(self)
147
def run_blocking(self):
148
"""Run the dialog, blocking for a response from the frontend.
150
Returns the user's choice.
156
def run_callback(self, choice):
157
"""Run the callback for this dialog. Choice should be the
158
button that the user clicked, or None if the user closed the
159
window without makeing a selection.
163
if self.callback is not None:
164
eventloop.add_urgent_call(self.callback,
165
"%s callback" % self.__class__,
168
class MessageBoxDialog(Dialog):
169
"""Show the user some info in a dialog box. The only button is
170
Okay. The callback is optional for a message box dialog.
172
def __init__(self, title, description):
173
Dialog.__init__(self, title, description, [BUTTON_OK])
175
def run(self, callback=None):
176
Dialog.run(self, callback)
178
def run_callback(self, choice):
179
if self.callback is not None:
180
Dialog.run_callback(self, choice)
182
class ChoiceDialog(Dialog):
183
"""Give the user a choice of 2 options (Yes/No, Ok/Cancel,
184
Migrate/Don't Migrate, etc.)
186
def __init__(self, title, description, default_button, other_button):
187
super(ChoiceDialog, self).__init__(title, description,
188
[default_button, other_button])
190
class ThreeChoiceDialog(Dialog):
191
"""Give the user a choice of 3 options (e.g. Remove entry/
194
def __init__(self, title, description, default_button, second_button,
196
super(ThreeChoiceDialog, self).__init__(title, description,
201
class HTTPAuthDialog(Dialog):
202
"""Ask for a username and password for HTTP authorization.
203
Frontends should create a dialog with text entries for a username
204
and password. Use prefill_user and prefill_password for the initial
205
values of the entries.
207
The buttons are always BUTTON_OK and BUTTON_CANCEL.
209
def __init__(self, location, realm, prefill_user=None,
210
prefill_password=None):
212
'%(authtype)s %(url)s requires a username and password for '
214
{'authtype': location[0], 'url': location[1], 'realm': realm})
215
super(HTTPAuthDialog, self).__init__(_("Login Required"), desc,
216
(BUTTON_OK, BUTTON_CANCEL))
217
self.prefill_user = prefill_user
218
self.prefill_password = prefill_password
220
def run_callback(self, choice, username='', password=''):
221
self.username = username
222
self.password = password
223
super(HTTPAuthDialog, self).run_callback(choice)
225
class TextEntryDialog(Dialog):
226
"""Like the ChoiceDialog, but also contains a textbox for the user
227
to enter a value into. This is used for things like the create
228
playlist dialog, the rename dialog, etc.
230
def __init__(self, title, description, default_button, other_button,
231
prefill_callback=None, fill_with_clipboard_url=False):
232
super(TextEntryDialog, self).__init__(title, description,
233
[default_button, other_button])
234
self.prefill_callback = prefill_callback
235
self.fill_with_clipboard_url = fill_with_clipboard_url
237
def run_callback(self, choice, value=None):
239
super(TextEntryDialog, self).run_callback(choice)
241
class CheckboxDialog(Dialog):
242
"""Like the ChoiceDialog, but also contains a checkbox for the
243
user to enter a value into. This is used for things like asking
244
whether to show the dialog again. There's also a mesage for the
245
checkbox and an initial value.
247
def __init__(self, title, description, checkbox_text, checkbox_value,
248
default_button, other_button):
249
super(CheckboxDialog, self).__init__(title, description,
250
[default_button, other_button])
251
self.checkbox_text = checkbox_text
252
self.checkbox_value = checkbox_value
254
def run_callback(self, choice, checkbox_value=False):
255
self.checkbox_value = checkbox_value
256
super(CheckboxDialog, self).run_callback(choice)
258
class CheckboxTextboxDialog(CheckboxDialog):
259
"""Like ``CheckboxDialog`` but also with a text area. Used for
260
capturing bug report data.
262
def __init__(self, title, description, checkbox_text,
263
checkbox_value, textbox_value, default_button, other_button):
264
super(CheckboxTextboxDialog, self).__init__(title, description,
269
self.textbox_value = textbox_value
271
def run_callback(self, choice, checkbox_value=False, textbox_value=""):
272
self.textbox_value = textbox_value
273
super(CheckboxTextboxDialog, self).run_callback(choice, checkbox_value)