4 ### Utility module for xtoys Python programs
6 ### (c) 2007 Straylight/Edgeware
9 ###----- Licensing notice ---------------------------------------------------
11 ### This file is part of the Edgeware XT tools collection.
13 ### XT tools is free software; you can redistribute it and/or modify
14 ### it under the terms of the GNU General Public License as published by
15 ### the Free Software Foundation; either version 2 of the License, or
16 ### (at your option) any later version.
18 ### XT tools is distributed in the hope that it will be useful,
19 ### but WITHOUT ANY WARRANTY; without even the implied warranty of
20 ### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 ### GNU General Public License for more details.
23 ### You should have received a copy of the GNU General Public License
24 ### along with XT tools; if not, write to the Free Software Foundation,
25 ### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
29 ###--------------------------------------------------------------------------
30 ### External dependencies.
33 from sys import stdout, stderr, exit
38 GTK, GDK, GO = XT.GTK, XT.GDK, XT.GO
40 ###--------------------------------------------------------------------------
43 ### These package up the mess involved with the different kinds of dialogue
44 ### box xgetline can show. The common interface is informal, but looks like
47 ### ready(): prepare the widget for action
48 ### value(): extract the value the user entered
49 ### setvalue(VALUE): show VALUE as the existing value in the widget
50 ### sethistory(HISTORY): store the HISTORY in the widget's history list
51 ### gethistory(): extract the history list back out again
53 def setup_entry(entry):
54 """Standard things to do to an entry widget."""
56 entry.set_activates_default(True)
58 class SimpleEntry (GTK.Entry):
59 """A plain old Entry widget with no bells or whistles."""
60 def setvalue(me, value):
69 class ModelMixin (object):
71 A helper mixin for classes which make use of a TreeModel.
73 It provides the sethistory and gethistory methods for the common widget
74 interface, and can produce a CellRenderer for stuffing into the viewer
75 widget, whatever that might be.
79 """Initialize the ModelMixin."""
80 me.model = GTK.ListStore(GO.TYPE_STRING)
81 me.set_model(me.model)
84 """Insert a CellRenderer for displaying history items into the widget."""
85 cell = GTK.CellRendererText()
87 me.set_attributes(cell, text = 0)
89 def sethistory(me, history):
90 """Write the HISTORY into the model."""
92 me.model.append([line])
95 """Extract the history from the model."""
96 return (l[0] for l in me.model)
98 class Combo (ModelMixin, GTK.ComboBox):
100 A widget which uses a non-editable Combo box for entry.
104 """Initialize the widget."""
105 GTK.ComboBox.__init__(me)
106 ModelMixin.__init__(me)
109 def sethistory(me, history):
113 We have to select some item, so it might as well be the first one.
115 ModelMixin.sethistory(me, history)
119 """Nothing special needed to make us ready."""
122 def setvalue(me, value):
124 Store a value in the widget.
126 This involves finding it in the list and setting it by index. I suppose
127 I could keep a dictionary, but it seems bad to have so many copies.
129 for i in xrange(len(me.model)):
130 if me.model[i][0] == value:
134 """Extract the current selection."""
135 return me.model[me.get_active()][0]
137 class ComboEntry (ModelMixin, GTK.ComboBoxEntry):
139 A widget which uses an editable combo box.
144 Initialize the widget.
146 GTK.ComboBoxEntry.__init__(me)
147 ModelMixin.__init__(me)
148 me.set_text_column(0)
152 Set up the entry widget.
154 We grab the arrow keys to step through the history.
156 setup_entry(me.child)
157 me.child.connect('key-press-event', me.press)
159 def press(me, _, event):
161 Handle key-press events.
163 Specifically, up and down to move through the history.
165 if GDK.keyval_name(event.keyval) in ('Up', 'Down'):
170 def setvalue(me, value):
171 me.child.set_text(value)
173 return me.child.get_text()
175 ###--------------------------------------------------------------------------
176 ### Utility functions.
179 """For each line in LINES, generate a line without trailing newline."""
181 if line != '' and line[-1] == '\n':
185 ###--------------------------------------------------------------------------
186 ### Create the window.
188 def escape(_, event, win):
189 """Key-press handler: on escape, destroy WIN."""
190 if GDK.keyval_name(event.keyval) == 'Escape':
195 def accept(_, entry, win):
196 """OK button handler: store user's value and end."""
198 result = entry.value()
202 def make_window(opts):
204 Make and return the main window.
207 ## Create the window.
208 win = GTK.Window(GTK.WINDOW_TOPLEVEL)
209 win.set_title(opts.title)
210 win.set_position(GTK.WIN_POS_MOUSE)
211 win.connect('destroy', lambda _: XT.delreason())
213 ## Make a horizontal box for the widgets.
214 box = GTK.HBox(spacing = 4)
215 box.set_border_width(4)
218 ## If we have a prompt, insert it.
219 if opts.prompt is not None:
220 box.pack_start(GTK.Label(opts.prompt), False)
222 ## Choose the appropriate widget.
223 if opts.file is None:
224 entry = SimpleEntry()
227 entry.set_visibility(False)
234 entry.sethistory(chomped(open(opts.file, 'r')))
235 except IOError, error:
236 if error.errno == E.ENOENT and opts.history:
241 ## Insert the widget and configure it.
242 box.pack_start(entry, True)
243 if opts.default == '@':
245 entry.setvalue(entry.gethistory.__iter__.next())
246 except StopIteration:
248 elif opts.default is not None:
249 entry.setvalue(opts.default)
252 ## Sort out the OK button.
253 ok = GTK.Button('OK')
254 ok.set_flags(ok.flags() | GTK.CAN_DEFAULT)
255 box.pack_start(ok, False)
256 ok.connect('clicked', accept, entry, win)
260 win.connect('key-press-event', escape, win)
266 ###--------------------------------------------------------------------------
271 Parse the command line, returning a triple (PARSER, OPTS, ARGS).
274 op = XT.make_optparse \
276 {'action': 'store_true', 'dest': 'history',
277 'help': "With `--list', update with new string."}),
279 {'type': 'int', 'dest': 'histmax',
280 'help': "Maximum number of items written back to file."}),
283 'help': "Set the default entry."}),
285 {'action': 'store_true', 'dest': 'invisible',
286 'help': "Don't show the user's string as it's typed."}),
289 'help': "Read FILE into a drop-down list."}),
291 {'action': 'store_true', 'dest': 'nochoice',
292 'help': "No free text input: user must choose item from list."}),
295 'help': "Set the window's prompt string."}),
298 'help': "Set the window's title string."})],
300 usage = '%prog [-Hin] [-M HISTMAX] [-p PROMPT] [-l FILE] [-t TITLE]')
302 op.set_defaults(title = 'Input request',
310 opts, args = op.parse_args()
311 return op, opts, args
313 ###--------------------------------------------------------------------------
321 op, opts, args = parse_args()
323 op.print_usage(stderr)
325 entry = make_window(opts)
334 new = '%s.new' % opts.file
338 for l in entry.gethistory():
339 if opts.histmax != 0 and i >= opts.histmax:
345 OS.rename(new, opts.file)
350 if err.errno != E.ENOENT:
355 if __name__ == '__main__':
358 ###----- That's all, folks --------------------------------------------------