--- /dev/null
+### -*-python-*-
+###
+### Utility module for xtoys Python programs
+###
+### (c) 2007 Straylight/Edgeware
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This file is part of the Edgeware X tools collection.
+###
+### X tools is free software; you can redistribute it and/or modify
+### it under the terms of the GNU General Public License as published by
+### the Free Software Foundation; either version 2 of the License, or
+### (at your option) any later version.
+###
+### X tools 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 X tools; if not, write to the Free Software Foundation,
+### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+###--------------------------------------------------------------------------
+### External dependencies.
+
+import os as OS
+import optparse as O
+from sys import stdin, stdout, exit, argv
+
+import pygtk
+pygtk.require('2.0')
+import gtk as GTK
+GDK = GTK.gdk
+import gobject as GO
+del pygtk
+
+###--------------------------------------------------------------------------
+### Reasons for living.
+
+_reasons = 0
+
+def addreason():
+ """Add a reason."""
+ global _reasons
+ _reasons += 1
+
+def delreason():
+ """Drop a reason. When reasons reach zero, the main loop stops."""
+ global _reasons
+ _reasons -= 1
+ if _reasons == 0:
+ GTK.main_quit()
+
+###--------------------------------------------------------------------------
+### General utilities.
+
+def make_optparse(options, **kw):
+ """
+ Construct an option parser object.
+
+ The KW are keyword arguments to be passed to OptionParser. The OPTIONS are
+ a list of (SHORT, LONG, KW) triples; the SHORT and LONG strings do /not/
+ have leading dashes.
+ """
+ op = O.OptionParser(**kw)
+ for short, long, kw in options:
+ names = ['--%s' % long]
+ if short is not None:
+ names.append('-%s' % short)
+ op.add_option(*names, **kw)
+ return op
+
+###--------------------------------------------------------------------------
+### Message boxes.
+
+class MessageButton (object):
+ """
+ An object storing information about a button in a Message.
+ """
+
+ def __init__(me, label, value = None, *options):
+ """
+ Initialize a button definition.
+
+ If LABEL is a tuple, then it should have the form
+ (LABEL, VALUE, OPTIONS...); the initialization parser is applied to its
+ contents. This is not done recursively.
+
+ If LABEL is a string, and VALUE and OPTIONS are omitted, then it is
+ parsed as OPT:OPT:...:LABEL, where OPT is either an option (see below) or
+ `=VALUE'. Only one VALUE may be given.
+
+ The LABEL is the label to put on the button, or the GTK stock id. The
+ VALUE is the value to return from Message.ask if the button is chosen.
+ The OPTIONS are:
+
+ * 'default': this is the default button
+ * 'cancel': this is the cancel button
+ """
+ if value is not None or len(options) > 0:
+ me._doinit(label, value, *options)
+ elif isinstance(label, tuple):
+ me._doinit(*label)
+ else:
+ i = 0
+ options = []
+ while 0 <= i < len(label):
+ if label[i] == '!':
+ i += 1
+ break
+ j = label.find(':', i)
+ if j < 0:
+ break
+ if label[i] == '=':
+ if value is not None:
+ raise ValueError, 'Duplicate value in button spec %s' % label
+ value = label[i + 1:j]
+ else:
+ options.append(label[i:j])
+ i = j + 1
+ label = label[i:]
+ me._doinit(label, value, *options)
+
+ def _doinit(me, label, value = None, *options):
+ """
+ Does the work of processing the initialization parameters.
+ """
+ me.label = label
+ if value is None:
+ me.value = label
+ else:
+ me.value = value
+ me.defaultp = me.cancelp = False
+ for opt in options:
+ if opt == 'default':
+ me.defaultp = True
+ elif opt == 'cancel':
+ me.cancelp = True
+ else:
+ raise ValueError, 'unknown button option %s' % opt
+
+class Message (GTK.MessageDialog):
+ """
+ A simple message-box window: contains text and some buttons.
+
+ See __init__ for the usage instructions.
+ """
+
+ ## Mapping from Pythonic strings to GTK constants.
+ dboxtype = {'info': GTK.MESSAGE_INFO,
+ 'warning': GTK.MESSAGE_WARNING,
+ 'question': GTK.MESSAGE_QUESTION,
+ 'error': GTK.MESSAGE_ERROR}
+
+ def __init__(me,
+ title = None,
+ type = 'info',
+ message = '',
+ headline = None,
+ buttons = [],
+ markupp = False):
+ """
+ Report a message to the user and get a response back.
+
+ The TITLE is placed in the window's title bar.
+
+ The TYPE controls what kind of icon is placed in the window; it should be
+ one of 'info', 'warning', 'question' or 'error'.
+
+ The MESSAGE is the string which should be displayed. It may have
+ multiple lines. There may also be a HEADLINE message. The messages are
+ parsed for Pango markup if MARKUPP is set.
+
+ The BUTTONS are a list of buttons to show, right to left. Each one
+ should be a string which may either be a GTK stock tag or a plain label
+ string. This may be prefixed, optionally, by `!' to ignore other prefix
+ characters, `+' to make this button the default, or `:' to make this the
+ `cancel' button, chosen by pressing escape. If no button is marked as
+ cancel, we just use the first.
+ """
+
+ ## Initialize superclasses.
+ GTK.MessageDialog.__init__(me, type = me.dboxtype.get(type))
+
+ ## Set the title.
+ if title is None:
+ title = OS.path.basename(argv[0])
+ me.set_title(title)
+
+ ## Add the message strings.
+ me.set_property('use-markup', markupp)
+ if headline is None:
+ me.set_property('text', message)
+ else:
+ me.set_property('text', headline)
+ me.set_property('secondary-text', message)
+ me.set_property('secondary-use-markup', markupp)
+
+ ## Include the buttons.
+ if len(buttons) == 0:
+ buttons = [MessageButton('gtk-ok', True, 'default', 'cancel')]
+ else:
+ buttons = buttons[:]
+ buttons.reverse()
+ me.buttons = [isinstance(b, MessageButton) and b or MessageButton(b)
+ for b in buttons]
+ cancel = -1
+ default = -1
+ for i in xrange(len(buttons)):
+ button = me.buttons[i]
+ label = button.label
+ if GTK.stock_lookup(label) is None:
+ b = GTK.Button(label = label)
+ else:
+ b = GTK.Button(stock = label)
+ if button.defaultp:
+ default = i
+ if button.cancelp:
+ cancel = i
+ b.set_flags(b.flags() | GTK.CAN_DEFAULT)
+ button.widget = b
+ me.add_action_widget(b, i)
+
+ ## Choose default buttons.
+ if cancel == -1:
+ cancel = 0
+ if default == -1:
+ default = len(me.buttons) - 1
+ me.cancel = cancel
+ me.default = default
+
+ ## Connect up the handlers and go.
+ me.connect('key-press-event', me.keypress, me.buttons[cancel])
+ me.buttons[default].widget.grab_default()
+
+ def keypress(me, win, event, cancel):
+ """
+ Handle key-press events.
+
+ If the EVENT was an escape-press, then activate the CANCEL button.
+ """
+ key = GDK.keyval_name(event.keyval)
+ if key != 'Escape':
+ return False
+ cancel.widget.activate()
+ return True
+
+ def ask(me):
+ """
+ Display the message, and wait for a response.
+
+ The return value is the label of the button which was chosen.
+ """
+ me.show_all()
+ r = me.run()
+ if r < 0:
+ r = me.cancel
+ me.hide()
+ return me.buttons[r].value
+
+###----- That's all, folks --------------------------------------------------