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 ## Choose the appropriate widget.
219 if opts.file is None:
220 entry = SimpleEntry()
223 entry.set_visibility(False)
230 entry.sethistory(chomped(open(opts.file, 'r')))
231 except IOError, error:
232 if error.errno == E.ENOENT and opts.history:
237 ## If we have a prompt, insert it.
238 if opts.prompt is not None:
239 label = GTK.Label(opts.prompt)
240 label.set_properties(mnemonic_widget = entry,
241 use_underline = True)
242 box.pack_start(label, False)
244 ## Insert the widget and configure it.
245 box.pack_start(entry, True)
246 if opts.default == '@':
248 entry.setvalue(entry.gethistory.__iter__.next())
249 except StopIteration:
251 elif opts.default is not None:
252 entry.setvalue(opts.default)
255 ## Sort out the OK button.
256 ok = GTK.Button('OK')
257 ok.set_flags(ok.flags() | GTK.CAN_DEFAULT)
258 box.pack_start(ok, False)
259 ok.connect('clicked', accept, entry, win)
263 win.connect('key-press-event', escape, win)
269 ###--------------------------------------------------------------------------
274 Parse the command line, returning a triple (PARSER, OPTS, ARGS).
277 op = XT.make_optparse \
279 {'action': 'store_true', 'dest': 'history',
280 'help': "With `--list', update with new string."}),
282 {'type': 'int', 'dest': 'histmax',
283 'help': "Maximum number of items written back to file."}),
286 'help': "Set the default entry."}),
288 {'action': 'store_true', 'dest': 'invisible',
289 'help': "Don't show the user's string as it's typed."}),
292 'help': "Read FILE into a drop-down list."}),
294 {'action': 'store_true', 'dest': 'nochoice',
295 'help': "No free text input: user must choose item from list."}),
298 'help': "Set the window's prompt string."}),
301 'help': "Set the window's title string."})],
303 usage = '%prog [-Hin] [-M HISTMAX] [-p PROMPT] [-l FILE] [-t TITLE]')
305 op.set_defaults(title = 'Input request',
313 opts, args = op.parse_args()
314 return op, opts, args
316 ###--------------------------------------------------------------------------
324 op, opts, args = parse_args()
326 op.print_usage(stderr)
328 entry = make_window(opts)
337 new = '%s.new' % opts.file
341 for l in entry.gethistory():
342 if opts.histmax != 0 and i >= opts.histmax:
348 OS.rename(new, opts.file)
353 if err.errno != E.ENOENT:
358 if __name__ == '__main__':
361 ###----- That's all, folks --------------------------------------------------