root/urwid/trunk/dialog.py

Revision 121, 9.3 kB (checked in by ian, 1 year ago)

add some error checking in render wrapper, canvas combining. Fix input_test.py listbox usage.

  • Property svn:executable set to *
Line 
1 #!/usr/bin/python
2 #
3 # Urwid example similar to dialog(1) program
4 #    Copyright (C) 2004-2007  Ian Ward
5 #
6 #    This library is free software; you can redistribute it and/or
7 #    modify it under the terms of the GNU Lesser General Public
8 #    License as published by the Free Software Foundation; either
9 #    version 2.1 of the License, or (at your option) any later version.
10 #
11 #    This library is distributed in the hope that it will be useful,
12 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
13 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 #    Lesser General Public License for more details.
15 #
16 #    You should have received a copy of the GNU Lesser General Public
17 #    License along with this library; if not, write to the Free Software
18 #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19 #
20 # Urwid web site: http://excess.org/urwid/
21
22 """
23 Urwid example similar to dialog(1) program
24
25 """
26
27 import sys
28
29 import urwid
30 import urwid.raw_display
31
32
33 class DialogExit(Exception):
34         pass
35
36
37 class DialogDisplay:
38         palette = [
39                 ('body','black','light gray', 'standout'),
40                 ('border','black','dark blue'),
41                 ('shadow','white','black'),
42                 ('selectable','black', 'dark cyan'),
43                 ('focus','white','dark blue','bold'),
44                 ('focustext','light gray','dark blue'),
45                 ]
46                
47         def __init__(self, text, height, width, body=None):
48                 width = int(width)
49                 if width <= 0:
50                         width = ('relative', 80)
51                 height = int(height)
52                 if height <= 0:
53                         height = ('relative', 80)
54        
55                 self.body = body
56                 if body is None:
57                         # fill space with nothing
58                         body = urwid.Filler(urwid.Divider(),'top')
59
60                 self.frame = urwid.Frame( body, focus_part='footer')
61                 if text is not None:
62                         self.frame.header = urwid.Pile( [urwid.Text(text),
63                                 urwid.Divider()] )
64                 w = self.frame
65                
66                 # pad area around listbox
67                 w = urwid.Padding(w, ('fixed left',2), ('fixed right',2))
68                 w = urwid.Filler(w, ('fixed top',1), ('fixed bottom',1))
69                 w = urwid.AttrWrap(w, 'body')
70                
71                 # "shadow" effect
72                 w = urwid.Columns( [w,('fixed', 2, urwid.AttrWrap(
73                         urwid.Filler(urwid.Text(('border','  ')), "top")
74                         ,'shadow'))])
75                 w = urwid.Frame( w, footer =
76                         urwid.AttrWrap(urwid.Text(('border','  ')),'shadow'))
77
78                 # outermost border area
79                 w = urwid.Padding(w, 'center', width )
80                 w = urwid.Filler(w, 'middle', height )
81                 w = urwid.AttrWrap( w, 'border' )
82                
83                 self.view = w
84
85
86         def add_buttons(self, buttons):
87                 l = []
88                 for name, exitcode in buttons:
89                         b = urwid.Button( name, self.button_press )
90                         b.exitcode = exitcode
91                         b = urwid.AttrWrap( b, 'selectable','focus' )
92                         l.append( b )
93                 self.buttons = urwid.GridFlow(l, 10, 3, 1, 'center')
94                 self.frame.footer = urwid.Pile( [ urwid.Divider(),
95                         self.buttons ], focus_item = 1)
96
97         def button_press(self, button):
98                 raise DialogExit(button.exitcode)
99
100         def main(self):
101                 self.ui = urwid.raw_display.Screen()
102                 self.ui.register_palette( self.palette )
103                 return self.ui.run_wrapper( self.run )
104
105         def run(self):
106                 self.ui.set_mouse_tracking()
107                 size = self.ui.get_cols_rows()
108                 try:
109                         while True:
110                                 canvas = self.view.render( size, focus=True )
111                                 self.ui.draw_screen( size, canvas )
112                                 keys = None
113                                 while not keys:
114                                         keys = self.ui.get_input()
115                                 for k in keys:
116                                         if urwid.is_mouse_event(k):
117                                                 event, button, col, row = k
118                                                 self.view.mouse_event( size,
119                                                         event, button, col, row,
120                                                         focus=True)
121                                         if k == 'window resize':
122                                                 size = self.ui.get_cols_rows()
123                                         k = self.view.keypress( size, k )
124
125                                         if k:
126                                                 self.unhandled_key( size, k)
127                 except DialogExit, e:
128                         return self.on_exit( e.args[0] )
129                
130         def on_exit(self, exitcode):
131                 return exitcode, ""
132
133         def unhandled_key(self, size, key):
134                 pass
135                
136
137
138 class InputDialogDisplay(DialogDisplay):
139         def __init__(self, text, height, width):
140                 self.edit = urwid.Edit()
141                 body = urwid.ListBox([self.edit])
142                 body = urwid.AttrWrap(body, 'selectable','focustext')
143                
144                 DialogDisplay.__init__(self, text, height, width, body)
145                
146                 self.frame.set_focus('body')
147        
148         def unhandled_key(self, size, k):
149                 if k in ('up','page up'):
150                         self.frame.set_focus('body')
151                 if k in ('down','page down'):
152                         self.frame.set_focus('footer')
153                 if k == 'enter':
154                         # pass enter to the "ok" button
155                         self.frame.set_focus('footer')
156                         self.view.keypress( size, k )
157        
158         def on_exit(self, exitcode):
159                 return exitcode, self.edit.get_edit_text()
160
161        
162 class TextDialogDisplay(DialogDisplay):
163         def __init__(self, file, height, width):
164                 l = []
165                 # read the whole file (being slow, not lazy this time)
166                 for line in open(file).readlines():
167                         l.append( urwid.Text( line.rstrip() ))
168                 body = urwid.ListBox(l)
169                 body = urwid.AttrWrap(body, 'selectable','focustext')
170
171                 DialogDisplay.__init__(self, None, height, width, body)
172
173
174         def unhandled_key(self, size, k):
175                 if k in ('up','page up','down','page down'):
176                         self.frame.set_focus('body')
177                         self.view.keypress( size, k )
178                         self.frame.set_focus('footer')
179
180
181 class ListDialogDisplay(DialogDisplay):
182         def __init__(self, text, height, width, constr, items, has_default):
183                 j = []
184                 if has_default:
185                         k, tail = 3, ()
186                 else:   
187                         k, tail = 2, ("no",)
188                 while items:
189                         j.append( items[:k] + tail )
190                         items = items[k:]
191                                        
192                 l = []
193                 self.items = []
194                 for tag, item, default in j:
195                         w = constr( tag, default=="on" )
196                         self.items.append(w)
197                         w = urwid.Columns( [('fixed', 12, w),
198                                 urwid.Text(item)], 2 )
199                         w = urwid.AttrWrap(w, 'selectable','focus')
200                         l.append(w)
201
202                 lb = urwid.ListBox(l)
203                 lb = urwid.AttrWrap( lb, "selectable" )
204                 DialogDisplay.__init__(self, text, height, width, lb )
205                
206                 self.frame.set_focus('body')
207        
208         def unhandled_key(self, size, k):
209                 if k in ('up','page up'):
210                         self.frame.set_focus('body')
211                 if k in ('down','page down'):
212                         self.frame.set_focus('footer')
213                 if k == 'enter':
214                         # pass enter to the "ok" button
215                         self.frame.set_focus('footer')
216                         self.buttons.set_focus(0)
217                         self.view.keypress( size, k )
218
219         def on_exit(self, exitcode):
220                 """Print the tag of the item selected."""
221                 if exitcode != 0:
222                         return exitcode, ""
223                 s = ""
224                 for i in self.items:
225                         if i.get_state():
226                                 s = i.get_label()
227                                 break
228                 return exitcode, s
229                
230        
231        
232
233 class CheckListDialogDisplay(ListDialogDisplay):
234         def on_exit(self, exitcode):
235                 """
236                 Mimick dialog(1)'s --checklist exit.
237                 Put each checked item in double quotes with a trailing space.
238                 """
239                 if exitcode != 0:
240                         return exitcode, ""
241                 l = []
242                 for i in self.items:
243                         if i.get_state():
244                                 l.append(i.get_label())
245                 return exitcode, "".join(['"'+tag+'" ' for tag in l])
246
247
248
249
250 class MenuItem(urwid.Text):
251         """A custom widget for the --menu option"""
252         def __init__(self, label):
253                 urwid.Text.__init__(self, label)
254                 self.state = False
255         def selectable(self):
256                 return True
257         def keypress(self,size,key):
258                 if key == "enter":
259                         self.state = True
260                         raise DialogExit, 0
261                 return key
262         def mouse_event(self,size,event,button,col,row,focus):
263                 if event=='mouse release':
264                         self.state = True
265                         raise DialogExit, 0
266                 return False
267         def get_state(self):
268                 return self.state
269         def get_label(self):
270                 text, attr = self.get_text()
271                 return text
272
273
274 def do_checklist(text, height, width, list_height, *items):
275         def constr(tag, state):
276                 return urwid.CheckBox(tag, state)
277         d = CheckListDialogDisplay( text, height, width, constr, items, True)
278         d.add_buttons([ ("OK", 0), ("Cancel", 1) ])
279         return d
280        
281 def do_inputbox(text, height, width):
282         d = InputDialogDisplay( text, height, width )
283         d.add_buttons([ ("Exit", 0) ])
284         return d
285
286 def do_menu(text, height, width, menu_height, *items):
287         def constr(tag, state ):
288                 return MenuItem(tag)
289         d = ListDialogDisplay(text, height, width, constr, items, False)
290         d.add_buttons([ ("OK", 0), ("Cancel", 1) ])
291         return d
292
293 def do_msgbox(text, height, width):
294         d = DialogDisplay( text, height, width )
295         d.add_buttons([ ("OK", 0) ])
296         return d
297
298 def do_radiolist(text, height, width, list_height, *items):
299         radiolist = []
300         def constr(tag, state, radiolist=radiolist):
301                 return urwid.RadioButton(radiolist, tag, state)
302         d = ListDialogDisplay( text, height, width, constr, items, True )
303         d.add_buttons([ ("OK", 0), ("Cancel", 1) ])
304         return d
305
306 def do_textbox(file, height, width):
307         d = TextDialogDisplay( file, height, width )
308         d.add_buttons([ ("Exit", 0) ])
309         return d
310
311 def do_yesno(text, height, width):
312         d = DialogDisplay( text, height, width )
313         d.add_buttons([ ("Yes", 0), ("No", 1) ])
314         return d
315
316 MODES={ '--checklist':  (do_checklist,
317                 "text height width list-height [ tag item status ] ..."),
318         '--inputbox':   (do_inputbox,
319                 "text height width"),
320         '--menu':       (do_menu,
321                 "text height width menu-height [ tag item ] ..."),
322         '--msgbox':     (do_msgbox,
323                 "text height width"),
324         '--radiolist':  (do_radiolist,
325                 "text height width list-height [ tag item status ] ..."),
326         '--textbox':    (do_textbox,
327                 "file height width"),
328         '--yesno':      (do_yesno,
329                 "text height width"),
330         }
331        
332
333 def show_usage():
334         """
335         Display a helpful usage message.
336         """
337         modelist = [(mode, help) for (mode, (fn, help)) in MODES.items()]
338         modelist.sort()
339         sys.stdout.write(
340                 __doc__ +
341                 "\n".join(["%-15s %s"%(mode,help) for (mode,help) in modelist])
342                 + """
343
344 height and width may be set to 0 to auto-size.
345 list-height and menu-height are currently ignored.
346 status may be either on or off.
347 """ )
348
349
350 def main():
351         if len(sys.argv) < 2 or not MODES.has_key(sys.argv[1]):
352                 show_usage()
353                 return
354        
355         # Create a DialogDisplay instance
356         fn, help = MODES[sys.argv[1]]
357         d = fn( * sys.argv[2:] )
358        
359         # Run it
360         exitcode, exitstring = d.main()
361        
362         # Exit
363         if exitstring:
364                 sys.stderr.write(exitstring+"\n")
365        
366         sys.exit(exitcode)
367                
368
369 if __name__=="__main__":
370         main()
Note: See TracBrowser for help on using the browser.