#! @PYTHON@ ### -*-python-*- ### ### Utility module for xtoys Python programs ### ### (c) 2007 Straylight/Edgeware ### ###----- Licensing notice --------------------------------------------------- ### ### This file is part of the Edgeware XT tools collection. ### ### XT 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. ### ### XT 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 XT tools; if not, write to the Free Software Foundation, ### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. VERSION = '@VERSION@' ###-------------------------------------------------------------------------- ### External dependencies. import optparse as O from sys import stdout, stderr, exit import os as OS import errno as E import xtoys as XT GTK, GDK, GO = XT.GTK, XT.GDK, XT.GO ###-------------------------------------------------------------------------- ### Entry classes. ### These package up the mess involved with the different kinds of dialogue ### box xgetline can show. The common interface is informal, but looks like ### this: ### ### ready(): prepare the widget for action ### value(): extract the value the user entered ### setvalue(VALUE): show VALUE as the existing value in the widget ### sethistory(HISTORY): store the HISTORY in the widget's history list ### gethistory(): extract the history list back out again def setup_entry(entry): """Standard things to do to an entry widget.""" entry.grab_focus() entry.set_activates_default(True) class SimpleEntry (GTK.Entry): """A plain old Entry widget with no bells or whistles.""" def setvalue(me, value): me.set_text(value) def ready(me): setup_entry(me) def value(me): return me.get_text() def gethistory(me): return () class ModelMixin (object): """ A helper mixin for classes which make use of a TreeModel. It provides the sethistory and gethistory methods for the common widget interface, and can produce a CellRenderer for stuffing into the viewer widget, whatever that might be. """ def __init__(me): """Initialize the ModelMixin.""" me.model = GTK.ListStore(GO.TYPE_STRING) me.set_model(me.model) def setlayout(me): """Insert a CellRenderer for displaying history items into the widget.""" cell = GTK.CellRendererText() me.pack_start(cell) me.set_attributes(cell, text = 0) def sethistory(me, history): """Write the HISTORY into the model.""" for line in history: me.model.append([line]) def gethistory(me): """Extract the history from the model.""" return (l[0] for l in me.model) class Combo (ModelMixin, GTK.ComboBox): """ A widget which uses a non-editable Combo box for entry. """ def __init__(me): """Initialize the widget.""" GTK.ComboBox.__init__(me) ModelMixin.__init__(me) me.setlayout() def sethistory(me, history): """ Insert the HISTORY. We have to select some item, so it might as well be the first one. """ ModelMixin.sethistory(me, history) me.set_active(0) def ready(me): """Nothing special needed to make us ready.""" pass def setvalue(me, value): """ Store a value in the widget. This involves finding it in the list and setting it by index. I suppose I could keep a dictionary, but it seems bad to have so many copies. """ for i in xrange(len(me.model)): if me.model[i][0] == value: me.set_active(i) def value(me): """Extract the current selection.""" return me.model[me.get_active()][0] class ComboEntry (ModelMixin, GTK.ComboBoxEntry): """ A widget which uses an editable combo box. """ def __init__(me): """ Initialize the widget. """ GTK.ComboBoxEntry.__init__(me) ModelMixin.__init__(me) me.set_text_column(0) def ready(me): """ Set up the entry widget. We grab the arrow keys to step through the history. """ setup_entry(me.child) me.child.connect('key-press-event', me.press) def press(me, _, event): """ Handle key-press events. Specifically, up and down to move through the history. """ if GDK.keyval_name(event.keyval) in ('Up', 'Down'): me.popup() return True return False def setvalue(me, value): me.child.set_text(value) def value(me): return me.child.get_text() ###-------------------------------------------------------------------------- ### Utility functions. def chomped(lines): """For each line in LINES, generate a line without trailing newline.""" for line in lines: if line != '' and line[-1] == '\n': line = line[:-1] yield line ###-------------------------------------------------------------------------- ### Create the window. def escape(_, event, win): """Key-press handler: on escape, destroy WIN.""" if GDK.keyval_name(event.keyval) == 'Escape': win.destroy() return True return False def accept(_, entry, win): """OK button handler: store user's value and end.""" global result result = entry.value() win.destroy() return True def make_window(opts): """ Make and return the main window. """ ## Create the window. win = GTK.Window(GTK.WINDOW_TOPLEVEL) win.set_title(opts.title) win.set_position(GTK.WIN_POS_MOUSE) win.connect('destroy', lambda _: XT.delreason()) ## Make a horizontal box for the widgets. box = GTK.HBox(spacing = 4) box.set_border_width(4) win.add(box) ## Choose the appropriate widget. if opts.file is None: entry = SimpleEntry() opts.history = False if opts.invisible: entry.set_visibility(False) else: if opts.nochoice: entry = Combo() else: entry = ComboEntry() try: entry.sethistory(chomped(open(opts.file, 'r'))) except IOError, error: if error.errno == E.ENOENT and opts.history: pass else: raise ## If we have a prompt, insert it. if opts.prompt is not None: label = GTK.Label(opts.prompt) label.set_properties(mnemonic_widget = entry, use_underline = True) box.pack_start(label, False) ## Insert the widget and configure it. box.pack_start(entry, True) if opts.default == '@': try: entry.setvalue(entry.gethistory.__iter__.next()) except StopIteration: pass elif opts.default is not None: entry.setvalue(opts.default) entry.ready() ## Sort out the OK button. ok = GTK.Button('OK') ok.set_flags(ok.flags() | GTK.CAN_DEFAULT) box.pack_start(ok, False) ok.connect('clicked', accept, entry, win) ok.grab_default() ## Handle escape. win.connect('key-press-event', escape, win) ## Done. win.show_all() return entry ###-------------------------------------------------------------------------- ### Option parsing. def parse_args(): """ Parse the command line, returning a triple (PARSER, OPTS, ARGS). """ op = XT.make_optparse \ ([('H', 'history', {'action': 'store_true', 'dest': 'history', 'help': "With `--list', update with new string."}), ('M', 'history-max', {'type': 'int', 'dest': 'histmax', 'help': "Maximum number of items written back to file."}), ('d', 'default', {'dest': 'default', 'help': "Set the default entry."}), ('i', 'invisible', {'action': 'store_true', 'dest': 'invisible', 'help': "Don't show the user's string as it's typed."}), ('l', 'list', {'dest': 'file', 'help': "Read FILE into a drop-down list."}), ('n', 'no-choice', {'action': 'store_true', 'dest': 'nochoice', 'help': "No free text input: user must choose item from list."}), ('p', 'prompt', {'dest': 'prompt', 'help': "Set the window's prompt string."}), ('t', 'title', {'dest': 'title', 'help': "Set the window's title string."})], version = VERSION, usage = '%prog [-Hin] [-M HISTMAX] [-p PROMPT] [-l FILE] [-t TITLE]') op.set_defaults(title = 'Input request', invisible = False, prompt = None, file = None, nochoice = False, history = False, histmax = 20) opts, args = op.parse_args() return op, opts, args ###-------------------------------------------------------------------------- ### Main program. result = None def main(): ## Startup. op, opts, args = parse_args() if len(args) > 0: op.print_usage(stderr) exit(1) entry = make_window(opts) XT.addreason() GTK.main() ## Closedown. if result is None: exit(1) if opts.history: try: new = '%s.new' % opts.file out = open(new, 'w') print >>out, result i = 0 for l in entry.gethistory(): if opts.histmax != 0 and i >= opts.histmax: break if l != result: print >>out, l i += 1 out.close() OS.rename(new, opts.file) finally: try: OS.unlink(new) except OSError, err: if err.errno != E.ENOENT: raise print result exit(0) if __name__ == '__main__': main() ###----- That's all, folks --------------------------------------------------