~ubuntu-branches/ubuntu/natty/miro/natty

« back to all changes in this revision

Viewing changes to lib/dialogs.py

  • Committer: Bazaar Package Importer
  • Author(s): Bryce Harrington
  • Date: 2011-01-22 02:46:33 UTC
  • mfrom: (1.4.10 upstream) (1.7.5 experimental)
  • Revision ID: james.westby@ubuntu.com-20110122024633-kjme8u93y2il5nmf
Tags: 3.5.1-1ubuntu1
* Merge from debian.  Remaining ubuntu changes:
  - Use python 2.7 instead of python 2.6
  - Relax dependency on python-dbus to >= 0.83.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Miro - an RSS based video player application
 
2
# Copyright (C) 2005-2010 Participatory Culture Foundation
 
3
#
 
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.
 
8
#
 
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.
 
13
#
 
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
 
17
#
 
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
 
20
# library.
 
21
#
 
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.
 
28
 
 
29
"""Handle dialog popups.
 
30
 
 
31
Simple Choices
 
32
 
 
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.
 
36
Here's an example::
 
37
 
 
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:
 
45
            # handle yes response
 
46
        elif dialog.choice == dialag.BUTTON_NO:
 
47
            # handle no respnose
 
48
    dialog.run(handlePizzaAnswer)
 
49
 
 
50
 
 
51
Advanced usage
 
52
 
 
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.
 
58
 
 
59
 
 
60
Frontend requirements
 
61
 
 
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()``.
 
66
 
 
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)``.
 
70
 
 
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.
 
76
"""
 
77
 
 
78
import threading
 
79
 
 
80
from miro import eventloop
 
81
from miro import signals
 
82
from miro import util
 
83
from miro.gtcache import gettext as _
 
84
 
 
85
class DialogButton(object):
 
86
    def __init__(self, text):
 
87
        self.text = text
 
88
    def __eq__(self, other):
 
89
        return isinstance(other, DialogButton) and self.text == other.text
 
90
    def __str__(self):
 
91
        return "DialogButton(%r)" % util.stringify(self.text)
 
92
 
 
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"))
 
132
 
 
133
class Dialog(object):
 
134
    """Abstract base class for dialogs.
 
135
    """
 
136
    def __init__(self, title, description, buttons):
 
137
        self.title = title
 
138
        self.description = description
 
139
        self.buttons = buttons
 
140
        self.event = threading.Event()
 
141
 
 
142
    def run(self, callback):
 
143
        self.callback = callback
 
144
        self.choice = None
 
145
        signals.system.new_dialog(self)
 
146
 
 
147
    def run_blocking(self):
 
148
        """Run the dialog, blocking for a response from the frontend.
 
149
 
 
150
        Returns the user's choice.
 
151
        """
 
152
        self.run(None)
 
153
        self.event.wait()
 
154
        return self.choice
 
155
 
 
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.
 
160
        """
 
161
        self.choice = choice
 
162
        self.event.set()
 
163
        if self.callback is not None:
 
164
            eventloop.add_urgent_call(self.callback,
 
165
                                    "%s callback" % self.__class__,
 
166
                                    args=(self,))
 
167
 
 
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.
 
171
    """
 
172
    def __init__(self, title, description):
 
173
        Dialog.__init__(self, title, description, [BUTTON_OK])
 
174
 
 
175
    def run(self, callback=None):
 
176
        Dialog.run(self, callback)
 
177
 
 
178
    def run_callback(self, choice):
 
179
        if self.callback is not None:
 
180
            Dialog.run_callback(self, choice)
 
181
 
 
182
class ChoiceDialog(Dialog):
 
183
    """Give the user a choice of 2 options (Yes/No, Ok/Cancel,
 
184
    Migrate/Don't Migrate, etc.)
 
185
    """
 
186
    def __init__(self, title, description, default_button, other_button):
 
187
        super(ChoiceDialog, self).__init__(title, description,
 
188
                                           [default_button, other_button])
 
189
 
 
190
class ThreeChoiceDialog(Dialog):
 
191
    """Give the user a choice of 3 options (e.g. Remove entry/
 
192
    Delete file/Cancel).
 
193
    """
 
194
    def __init__(self, title, description, default_button, second_button,
 
195
                 third_button):
 
196
        super(ThreeChoiceDialog, self).__init__(title, description,
 
197
                                                [default_button,
 
198
                                                 second_button,
 
199
                                                 third_button])
 
200
 
 
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.
 
206
 
 
207
    The buttons are always BUTTON_OK and BUTTON_CANCEL.
 
208
    """
 
209
    def __init__(self, location, realm, prefill_user=None,
 
210
            prefill_password=None):
 
211
        desc = _(
 
212
            '%(authtype)s %(url)s requires a username and password for '
 
213
            '"%(realm)s".',
 
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
 
219
 
 
220
    def run_callback(self, choice, username='', password=''):
 
221
        self.username = username
 
222
        self.password = password
 
223
        super(HTTPAuthDialog, self).run_callback(choice)
 
224
 
 
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.
 
229
    """
 
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
 
236
 
 
237
    def run_callback(self, choice, value=None):
 
238
        self.value = value
 
239
        super(TextEntryDialog, self).run_callback(choice)
 
240
 
 
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.
 
246
    """
 
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
 
253
 
 
254
    def run_callback(self, choice, checkbox_value=False):
 
255
        self.checkbox_value = checkbox_value
 
256
        super(CheckboxDialog, self).run_callback(choice)
 
257
 
 
258
class CheckboxTextboxDialog(CheckboxDialog):
 
259
    """Like ``CheckboxDialog`` but also with a text area. Used for
 
260
    capturing bug report data.
 
261
    """
 
262
    def __init__(self, title, description, checkbox_text,
 
263
                 checkbox_value, textbox_value, default_button, other_button):
 
264
        super(CheckboxTextboxDialog, self).__init__(title, description,
 
265
                                                    checkbox_text,
 
266
                                                    checkbox_value,
 
267
                                                    default_button,
 
268
                                                    other_button)
 
269
        self.textbox_value = textbox_value
 
270
 
 
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)