chiark / gitweb /
xscsize.c, etc.: Report geometry of individual monitors.
[xtoys] / xgetline.in
1 #! @PYTHON@
2 ### -*-python-*-
3 ###
4 ### Utility module for xtoys Python programs
5 ###
6 ### (c) 2007 Straylight/Edgeware
7 ###
8
9 ###----- Licensing notice ---------------------------------------------------
10 ###
11 ### This file is part of the Edgeware XT tools collection.
12 ###
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.
17 ###
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.
22 ###
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.
26
27 VERSION = '@VERSION@'
28
29 ###--------------------------------------------------------------------------
30 ### External dependencies.
31
32 import optparse as O
33 from sys import stdout, stderr, exit
34 import os as OS
35 import errno as E
36
37 import xtoys as XT
38 GTK, GDK, GO = XT.GTK, XT.GDK, XT.GO
39
40 ###--------------------------------------------------------------------------
41 ### Entry classes.
42
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
45 ### this:
46 ###
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
52
53 def setup_entry(entry):
54   """Standard things to do to an entry widget."""
55   entry.grab_focus()
56   entry.set_activates_default(True)
57
58 class SimpleEntry (GTK.Entry):
59   """A plain old Entry widget with no bells or whistles."""
60   def setvalue(me, value):
61     me.set_text(value)
62   def ready(me):
63     setup_entry(me)
64   def value(me):
65     return me.get_text()
66   def gethistory(me):
67     return ()
68
69 class ModelMixin (object):
70   """
71   A helper mixin for classes which make use of a TreeModel.
72
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.
76   """
77
78   def __init__(me):
79     """Initialize the ModelMixin."""
80     me.model = GTK.ListStore(GO.TYPE_STRING)
81     me.set_model(me.model)
82
83   def setlayout(me):
84     """Insert a CellRenderer for displaying history items into the widget."""
85     cell = GTK.CellRendererText()
86     me.pack_start(cell)
87     me.set_attributes(cell, text = 0)
88
89   def sethistory(me, history):
90     """Write the HISTORY into the model."""
91     for line in history:
92       me.model.append([line])
93
94   def gethistory(me):
95     """Extract the history from the model."""
96     return (l[0] for l in me.model)
97
98 class Combo (ModelMixin, GTK.ComboBox):
99   """
100   A widget which uses a non-editable Combo box for entry.
101   """
102
103   def __init__(me):
104     """Initialize the widget."""
105     GTK.ComboBox.__init__(me)
106     ModelMixin.__init__(me)
107     me.setlayout()
108
109   def sethistory(me, history):
110     """
111     Insert the HISTORY.
112
113     We have to select some item, so it might as well be the first one.
114     """
115     ModelMixin.sethistory(me, history)
116     me.set_active(0)
117
118   def ready(me):
119     """Nothing special needed to make us ready."""
120     pass
121
122   def setvalue(me, value):
123     """
124     Store a value in the widget.
125
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.
128     """
129     for i in xrange(len(me.model)):
130       if me.model[i][0] == value:
131         me.set_active(i)
132
133   def value(me):
134     """Extract the current selection."""
135     return me.model[me.get_active()][0]
136
137 class ComboEntry (ModelMixin, GTK.ComboBoxEntry):
138   """
139   A widget which uses an editable combo box.
140   """
141
142   def __init__(me):
143     """
144     Initialize the widget.
145     """
146     GTK.ComboBoxEntry.__init__(me)
147     ModelMixin.__init__(me)
148     me.set_text_column(0)
149
150   def ready(me):
151     """
152     Set up the entry widget.
153
154     We grab the arrow keys to step through the history.
155     """
156     setup_entry(me.child)
157     me.child.connect('key-press-event', me.press)
158
159   def press(me, _, event):
160     """
161     Handle key-press events.
162
163     Specifically, up and down to move through the history.
164     """
165     if GDK.keyval_name(event.keyval) in ('Up', 'Down'):
166       me.popup()
167       return True
168     return False
169
170   def setvalue(me, value):
171     me.child.set_text(value)
172   def value(me):
173     return me.child.get_text()
174
175 ###--------------------------------------------------------------------------
176 ### Utility functions.
177
178 def chomped(lines):
179   """For each line in LINES, generate a line without trailing newline."""
180   for line in lines:
181     if line != '' and line[-1] == '\n':
182       line = line[:-1]
183     yield line
184
185 ###--------------------------------------------------------------------------
186 ### Create the window.
187
188 def escape(_, event, win):
189   """Key-press handler: on escape, destroy WIN."""
190   if GDK.keyval_name(event.keyval) == 'Escape':
191     win.destroy()
192     return True
193   return False
194
195 def accept(_, entry, win):
196   """OK button handler: store user's value and end."""
197   global result
198   result = entry.value()
199   win.destroy()
200   return True
201
202 def make_window(opts):
203   """
204   Make and return the main window.
205   """
206
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())
212
213   ## Make a horizontal box for the widgets.
214   box = GTK.HBox(spacing = 4)
215   box.set_border_width(4)
216   win.add(box)
217
218   ## Choose the appropriate widget.
219   if opts.file is None:
220     entry = SimpleEntry()
221     opts.history = False
222     if opts.invisible:
223       entry.set_visibility(False)
224   else:
225     if opts.nochoice:
226       entry = Combo()
227     else:
228       entry = ComboEntry()
229     try:
230       entry.sethistory(chomped(open(opts.file, 'r')))
231     except IOError, error:
232       if error.errno == E.ENOENT and opts.history:
233         pass
234       else:
235         raise
236
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)
243
244   ## Insert the widget and configure it.
245   box.pack_start(entry, True)
246   if opts.default == '@':
247     try:
248       entry.setvalue(entry.gethistory.__iter__.next())
249     except StopIteration:
250       pass
251   elif opts.default is not None:
252     entry.setvalue(opts.default)
253   entry.ready()
254
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)
260   ok.grab_default()
261
262   ## Handle escape.
263   win.connect('key-press-event', escape, win)
264
265   ## Done.
266   win.show_all()
267   return entry
268
269 ###--------------------------------------------------------------------------
270 ### Option parsing.
271
272 def parse_args():
273   """
274   Parse the command line, returning a triple (PARSER, OPTS, ARGS).
275   """
276
277   op = XT.make_optparse \
278        ([('H', 'history',
279           {'action': 'store_true', 'dest': 'history',
280            'help': "With `--list', update with new string."}),
281          ('M', 'history-max',
282           {'type': 'int', 'dest': 'histmax',
283            'help': "Maximum number of items written back to file."}),
284          ('d', 'default',
285           {'dest': 'default',
286            'help': "Set the default entry."}),
287          ('i', 'invisible',
288           {'action': 'store_true', 'dest': 'invisible',
289            'help': "Don't show the user's string as it's typed."}),
290          ('l', 'list',
291           {'dest': 'file',
292            'help': "Read FILE into a drop-down list."}),
293          ('n', 'no-choice',
294           {'action': 'store_true', 'dest': 'nochoice',
295            'help': "No free text input: user must choose item from list."}),
296          ('p', 'prompt',
297           {'dest': 'prompt',
298            'help': "Set the window's prompt string."}),
299          ('t', 'title',
300           {'dest': 'title',
301            'help': "Set the window's title string."})],
302         version = VERSION,
303         usage = '%prog [-Hin] [-M HISTMAX] [-p PROMPT] [-l FILE] [-t TITLE]')
304
305   op.set_defaults(title = 'Input request',
306                   invisible = False,
307                   prompt = None,
308                   file = None,
309                   nochoice = False,
310                   history = False,
311                   histmax = 20)
312
313   opts, args = op.parse_args()
314   return op, opts, args
315
316 ###--------------------------------------------------------------------------
317 ### Main program.
318
319 result = None
320
321 def main():
322
323   ## Startup.
324   op, opts, args = parse_args()
325   if len(args) > 0:
326     op.print_usage(stderr)
327     exit(1)
328   entry = make_window(opts)
329   XT.addreason()
330   GTK.main()
331
332   ## Closedown.
333   if result is None:
334     exit(1)
335   if opts.history:
336     try:
337       new = '%s.new' % opts.file
338       out = open(new, 'w')
339       print >>out, result
340       i = 0
341       for l in entry.gethistory():
342         if opts.histmax != 0 and i >= opts.histmax:
343           break
344         if l != result:
345           print >>out, l
346           i += 1
347       out.close()
348       OS.rename(new, opts.file)
349     finally:
350       try:
351         OS.unlink(new)
352       except OSError, err:
353         if err.errno != E.ENOENT:
354           raise
355   print result
356   exit(0)
357
358 if __name__ == '__main__':
359   main()
360
361 ###----- That's all, folks --------------------------------------------------