2 # Kiwi: a Framework and Enhanced Widgets for Python
4 # Copyright (C) 2005-2007 Async Open Source
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU Lesser General Public License
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU Lesser General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 # Author(s): Johan Dahlin <jdahlin@async.com.br>
29 __all__
= ['error', 'info', 'messagedialog', 'warning', 'yesno', 'save',
30 'open', 'HIGAlertDialog', 'BaseDialog']
32 _
= lambda m
: gettext
.dgettext('kiwi', m
)
35 gtk
.MESSAGE_INFO
: gtk
.STOCK_DIALOG_INFO
,
36 gtk
.MESSAGE_WARNING
: gtk
.STOCK_DIALOG_WARNING
,
37 gtk
.MESSAGE_QUESTION
: gtk
.STOCK_DIALOG_QUESTION
,
38 gtk
.MESSAGE_ERROR
: gtk
.STOCK_DIALOG_ERROR
,
43 gtk
.BUTTONS_OK
: (gtk
.STOCK_OK
, gtk
.RESPONSE_OK
,),
44 gtk
.BUTTONS_CLOSE
: (gtk
.STOCK_CLOSE
, gtk
.RESPONSE_CLOSE
,),
45 gtk
.BUTTONS_CANCEL
: (gtk
.STOCK_CANCEL
, gtk
.RESPONSE_CANCEL
,),
46 gtk
.BUTTONS_YES_NO
: (gtk
.STOCK_NO
, gtk
.RESPONSE_NO
,
47 gtk
.STOCK_YES
, gtk
.RESPONSE_YES
),
48 gtk
.BUTTONS_OK_CANCEL
: (gtk
.STOCK_CANCEL
, gtk
.RESPONSE_CANCEL
,
49 gtk
.STOCK_OK
, gtk
.RESPONSE_OK
)
52 class HIGAlertDialog(gtk
.Dialog
):
53 def __init__(self
, parent
, flags
,
54 type=gtk
.MESSAGE_INFO
, buttons
=gtk
.BUTTONS_NONE
):
55 if not type in _IMAGE_TYPES
:
57 "type must be one of: %s", ', '.join(_IMAGE_TYPES
.keys()))
58 if not buttons
in _BUTTON_TYPES
:
60 "buttons be one of: %s", ', '.join(_BUTTON_TYPES
.keys()))
62 gtk
.Dialog
.__init
__(self
, '', parent
, flags
)
63 self
.set_border_width(5)
64 self
.set_resizable(False)
65 self
.set_has_separator(False)
66 # Some window managers (ION) displays a default title (???) if
67 # the specified one is empty, workaround this by setting it
68 # to a single space instead
70 self
.set_skip_taskbar_hint(True)
71 self
.vbox
.set_spacing(14)
73 # It seems like get_accessible is not available on windows, go figure
74 if hasattr(self
, 'get_accessible'):
75 self
.get_accessible().set_role(atk
.ROLE_ALERT
)
77 self
._primary
_label
= gtk
.Label()
78 self
._secondary
_label
= gtk
.Label()
79 self
._details
_label
= gtk
.Label()
80 self
._image
= gtk
.image_new_from_stock(_IMAGE_TYPES
[type],
82 self
._image
.set_alignment(0.5, 0.0)
84 self
._primary
_label
.set_use_markup(True)
85 for label
in (self
._primary
_label
, self
._secondary
_label
,
87 label
.set_line_wrap(True)
88 label
.set_selectable(True)
89 label
.set_alignment(0.0, 0.5)
91 hbox
= gtk
.HBox(False, 12)
92 hbox
.set_border_width(5)
93 hbox
.pack_start(self
._image
, False, False)
95 vbox
= gtk
.VBox(False, 0)
96 hbox
.pack_start(vbox
, False, False)
97 vbox
.pack_start(self
._primary
_label
, False, False)
98 vbox
.pack_start(self
._secondary
_label
, False, False)
100 self
._expander
= gtk
.expander_new_with_mnemonic(
101 _("Show more _details"))
102 self
._expander
.set_spacing(6)
103 self
._expander
.add(self
._details
_label
)
104 vbox
.pack_start(self
._expander
, False, False)
105 self
.vbox
.pack_start(hbox
, False, False)
107 self
._expander
.hide()
108 self
.add_buttons(*_BUTTON_TYPES
[buttons
])
109 self
.label_vbox
= vbox
111 def set_primary(self
, text
):
112 self
._primary
_label
.set_markup(
113 "<span weight=\"bold\" size=\"larger\">%s</span>" % text
)
115 def set_secondary(self
, text
):
116 self
._secondary
_label
.set_markup(text
)
118 def set_details(self
, text
):
119 self
._details
_label
.set_text(text
)
120 self
._expander
.show()
122 def set_details_widget(self
, widget
):
123 self
._expander
.remove(self
._details
_label
)
124 self
._expander
.add(widget
)
126 self
._expander
.show()
128 class BaseDialog(gtk
.Dialog
):
129 def __init__(self
, parent
=None, title
='', flags
=0, buttons
=()):
130 if parent
and not isinstance(parent
, gtk
.Window
):
131 raise TypeError("parent needs to be None or a gtk.Window subclass")
133 if not flags
and parent
:
134 flags
&= (gtk
.DIALOG_MODAL |
135 gtk
.DIALOG_DESTROY_WITH_PARENT
)
137 gtk
.Dialog
.__init
__(self
, title
=title
, parent
=parent
,
138 flags
=flags
, buttons
=buttons
)
139 self
.set_border_width(6)
140 self
.set_has_separator(False)
141 self
.vbox
.set_spacing(6)
143 def messagedialog(dialog_type
, short
, long=None, parent
=None,
144 buttons
=gtk
.BUTTONS_OK
, default
=-1):
145 """Create and show a MessageDialog.
147 @param dialog_type: one of constants
149 - gtk.MESSAGE_WARNING
150 - gtk.MESSAGE_QUESTION
152 @param short: A header text to be inserted in the dialog.
153 @param long: A long description of message.
154 @param parent: The parent widget of this dialog
155 @type parent: a gtk.Window subclass
156 @param buttons: The button type that the dialog will be display,
157 one of the constants:
163 - gtk.BUTTONS_OK_CANCEL
164 or a tuple or 2-sized tuples representing label and response. If label
165 is a stock-id a stock icon will be displayed.
166 @param default: optional default response id
168 if buttons
in (gtk
.BUTTONS_NONE
, gtk
.BUTTONS_OK
, gtk
.BUTTONS_CLOSE
,
169 gtk
.BUTTONS_CANCEL
, gtk
.BUTTONS_YES_NO
,
170 gtk
.BUTTONS_OK_CANCEL
):
171 dialog_buttons
= buttons
174 if buttons
is not None and type(buttons
) != tuple:
176 "buttons must be a GtkButtonsTypes constant or a tuple")
177 dialog_buttons
= gtk
.BUTTONS_NONE
179 if parent
and not isinstance(parent
, gtk
.Window
):
180 raise TypeError("parent must be a gtk.Window subclass")
182 d
= HIGAlertDialog(parent
=parent
, flags
=gtk
.DIALOG_MODAL
,
183 type=dialog_type
, buttons
=dialog_buttons
)
185 for text
, response
in buttons
:
186 d
.add_buttons(text
, response
)
191 if isinstance(long, gtk
.Widget
):
192 d
.set_details_widget(long)
193 elif isinstance(long, basestring
):
197 "long must be a gtk.Widget or a string, not %r" % long)
200 d
.set_default_response(default
)
203 d
.set_transient_for(parent
)
210 def _simple(type, short
, long=None, parent
=None, buttons
=gtk
.BUTTONS_OK
,
212 if buttons
== gtk
.BUTTONS_OK
:
213 default
= gtk
.RESPONSE_OK
214 return messagedialog(type, short
, long,
215 parent
=parent
, buttons
=buttons
,
218 def error(short
, long=None, parent
=None, buttons
=gtk
.BUTTONS_OK
, default
=-1):
219 return _simple(gtk
.MESSAGE_ERROR
, short
, long, parent
=parent
,
220 buttons
=buttons
, default
=default
)
222 def info(short
, long=None, parent
=None, buttons
=gtk
.BUTTONS_OK
, default
=-1):
223 return _simple(gtk
.MESSAGE_INFO
, short
, long, parent
=parent
,
224 buttons
=buttons
, default
=default
)
226 def warning(short
, long=None, parent
=None, buttons
=gtk
.BUTTONS_OK
, default
=-1):
227 return _simple(gtk
.MESSAGE_WARNING
, short
, long, parent
=parent
,
228 buttons
=buttons
, default
=default
)
230 def yesno(text
, parent
=None, default
=gtk
.RESPONSE_YES
,
231 buttons
=gtk
.BUTTONS_YES_NO
):
232 return messagedialog(gtk
.MESSAGE_WARNING
, text
, None, parent
,
233 buttons
=buttons
, default
=default
)
235 def open(title
='', parent
=None, patterns
=None, folder
=None, filter=None):
236 """Displays an open dialog.
237 @param title: the title of the folder, defaults to 'Select folder'
238 @param parent: parent gtk.Window or None
239 @param patterns: a list of pattern strings ['*.py', '*.pl'] or None
240 @param folder: initial folder or None
241 @param filter: a filter to use or None, is incompatible with patterns
245 if patterns
and ffilter
:
246 raise TypeError("Can't use patterns and filter at the same time")
248 filechooser
= gtk
.FileChooserDialog(title
or _('Open'),
250 gtk
.FILE_CHOOSER_ACTION_OPEN
,
251 (gtk
.STOCK_CANCEL
, gtk
.RESPONSE_CANCEL
,
252 gtk
.STOCK_OPEN
, gtk
.RESPONSE_OK
))
254 if patterns
or ffilter
:
256 ffilter
= gtk
.FileFilter()
257 for pattern
in patterns
:
258 ffilter
.add_pattern(pattern
)
259 filechooser
.set_filter(ffilter
)
260 filechooser
.set_default_response(gtk
.RESPONSE_OK
)
263 filechooser
.set_current_folder(folder
)
265 response
= filechooser
.run()
266 if response
!= gtk
.RESPONSE_OK
:
267 filechooser
.destroy()
270 path
= filechooser
.get_filename()
271 if path
and os
.access(path
, os
.R_OK
):
272 filechooser
.destroy()
275 abspath
= os
.path
.abspath(path
)
277 error(_('Could not open file "%s"') % abspath
,
278 _('The file "%s" could not be opened. '
279 'Permission denied.') % abspath
)
281 filechooser
.destroy()
284 def selectfolder(title
='', parent
=None, folder
=None):
285 """Displays a select folder dialog.
286 @param title: the title of the folder, defaults to 'Select folder'
287 @param parent: parent gtk.Window or None
288 @param folder: initial folder or None
291 filechooser
= gtk
.FileChooserDialog(
292 title
or _('Select folder'),
294 gtk
.FILE_CHOOSER_ACTION_SELECT_FOLDER
,
295 (gtk
.STOCK_CANCEL
, gtk
.RESPONSE_CANCEL
,
296 gtk
.STOCK_OK
, gtk
.RESPONSE_OK
))
299 filechooser
.set_current_folder(folder
)
301 filechooser
.set_default_response(gtk
.RESPONSE_OK
)
303 response
= filechooser
.run()
304 if response
!= gtk
.RESPONSE_OK
:
305 filechooser
.destroy()
308 path
= filechooser
.get_filename()
309 if path
and os
.access(path
, os
.R_OK | os
.X_OK
):
310 filechooser
.destroy()
313 abspath
= os
.path
.abspath(path
)
315 error(_('Could not select folder "%s"') % abspath
,
316 _('The folder "%s" could not be selected. '
317 'Permission denied.') % abspath
)
319 filechooser
.destroy()
322 def ask_overwrite(filename
, parent
=None):
323 submsg1
= _('A file named "%s" already exists') % os
.path
.abspath(filename
)
324 submsg2
= _('Do you wish to replace it with the current one?')
325 text
= ('<span weight="bold" size="larger">%s</span>\n\n%s\n'
326 % (submsg1
, submsg2
))
327 result
= messagedialog(gtk
.MESSAGE_ERROR
, text
, parent
=parent
,
328 buttons
=((gtk
.STOCK_CANCEL
,
329 gtk
.RESPONSE_CANCEL
),
332 return result
== gtk
.RESPONSE_YES
334 def save(title
='', parent
=None, current_name
='', folder
=None):
335 """Displays a save dialog."""
336 filechooser
= gtk
.FileChooserDialog(title
or _('Save'),
338 gtk
.FILE_CHOOSER_ACTION_SAVE
,
339 (gtk
.STOCK_CANCEL
, gtk
.RESPONSE_CANCEL
,
340 gtk
.STOCK_SAVE
, gtk
.RESPONSE_OK
))
342 filechooser
.set_current_name(current_name
)
343 filechooser
.set_default_response(gtk
.RESPONSE_OK
)
346 filechooser
.set_current_folder(folder
)
350 response
= filechooser
.run()
351 if response
!= gtk
.RESPONSE_OK
:
355 path
= filechooser
.get_filename()
356 if not os
.path
.exists(path
):
359 if ask_overwrite(path
, parent
):
361 filechooser
.destroy()
364 def password(primary
='', secondary
='', parent
=None):
366 Shows a password dialog and returns the password entered in the dialog
367 @param primary: primary text
368 @param secondary: secondary text
369 @param parent: a gtk.Window subclass or None
370 @returns: the password or None if none specified
371 @rtype: string or None
374 raise ValueError("primary cannot be empty")
376 d
= HIGAlertDialog(parent
=parent
, flags
=gtk
.DIALOG_MODAL
,
377 type=gtk
.MESSAGE_QUESTION
,
378 buttons
=gtk
.BUTTONS_OK_CANCEL
)
379 d
.set_default_response(gtk
.RESPONSE_OK
)
381 d
.set_primary(primary
+ '\n')
384 d
.set_secondary(secondary
)
387 hbox
.set_border_width(6)
389 d
.label_vbox
.pack_start(hbox
)
391 label
= gtk
.Label(_('Password:'))
393 hbox
.pack_start(label
, False, False)
396 entry
.set_invisible_char(u
'\u2022')
397 entry
.set_visibility(False)
400 d
.add_action_widget(entry
, gtk
.RESPONSE_OK
)
401 # FIXME: Is there another way of connecting widget::activate to a response?
402 d
.action_area
.remove(entry
)
403 hbox
.pack_start(entry
, True, True, 12)
407 if response
== gtk
.RESPONSE_OK
:
408 password
= entry
.get_text()
415 yesno('Kill?', default
=gtk
.RESPONSE_NO
)
417 info('Some information displayed not too long\nbut not too short',
418 long=('foobar ba asdjaiosjd oiadjoisjaoi aksjdasdasd kajsdhakjsdh\n'
419 'askdjhaskjdha skjdhasdasdjkasldj alksdjalksjda lksdjalksdj\n'
420 'asdjaslkdj alksdj lkasjdlkjasldkj alksjdlkasjd jklsdjakls\n'
421 'ask;ldjaklsjdlkasjd alksdj laksjdlkasjd lkajs kjaslk jkl\n'),
422 default
=gtk
.RESPONSE_OK
,
425 error('An error occurred', gtk
.Button('Woho'))
426 error('Unable to mount the selected volume.',
427 'mount: can\'t find /media/cdrom0 in /etc/fstab or /etc/mtab')
428 print open(title
='Open a file', patterns
=['*.py'])
429 print save(title
='Save a file', current_name
='foobar.py')
431 print password('Administrator password',
432 'To be able to continue the wizard you need to enter the '
433 'administrator password for the database on host anthem')
436 if __name__
== '__main__':