/[gentoo-src]/portage/pym/dcdialog.py
Gentoo

Contents of /portage/pym/dcdialog.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.2 - (show annotations) (download) (as text)
Sat Feb 26 06:35:20 2005 UTC (9 years, 10 months ago) by jstubbs
Branch: MAIN
CVS Tags: HEAD
Branch point for: portage_2_1
Changes since 1.1: +1 -0 lines
File MIME type: text/x-python
Brought forward changes from portage_2_0

1 #
2 # Changes and extensions by Carlos Castillo...
3 #
4 cvs_id_string="$Id: dcdialog.py,v 1.1.2.1 2005/01/16 02:35:33 carpaski Exp $"[5:-2]
5
6 #
7 # Module: dialog.py
8 # Copyright (c) 2000 Robb Shecter <robb@acm.org>
9 # All rights reserved.
10 # This source is covered by the GNU GPL.
11 #
12 # This module is a Python wrapper around the Linux "dialog" utility
13 # by Savio Lam and Stuart Herbert. My goals were to make dialog as
14 # easy to use from Python as possible. The demo code at the end of
15 # the module is a good example of how to use it. To run the demo,
16 # execute:
17 #
18 # python dialog.py
19 #
20 # This module has one class in it, "Dialog". An application typically
21 # creates an instance of it, and possibly sets the background title option.
22 # Then, methods can be called on it for interacting with the user.
23 #
24 # I wrote this because I want to use my 486-33 laptop as my main
25 # development computer (!), and I wanted a way to nicely interact with the
26 # user in console mode. There are apparently other modules out there
27 # with similar functionality, but they require the Python curses library.
28 # Writing this module from scratch was easier than figuring out how to
29 # recompile Python with curses enabled. :)
30 #
31 # One interesting feature is that the menu and selection windows allow
32 # *any* objects to be displayed and selected, not just strings.
33 #
34 # TO DO:
35 # Add code so that the input buffer is flushed before a dialog box is
36 # shown. This would make the UI more predictable for users. This
37 # feature could be turned on and off through an instance method.
38 # Drop using temporary files when interacting with 'dialog'
39 # (it's possible -- I've already tried :-).
40 # Try detecting the terminal window size in order to make reasonable
41 # height and width defaults. Hmmm - should also then check for
42 # terminal resizing...
43 # Put into a package name to make more reusable - reduce the possibility
44 # of name collisions.
45 #
46 # NOTES:
47 # there is a bug in (at least) Linux-Mandrake 7.0 Russian Edition
48 # running on AMD K6-2 3D that causes core dump when 'dialog'
49 # is running with --gauge option;
50 # in this case you'll have to recompile 'dialog' program.
51 #
52 # Modifications:
53 # Jul 2000, Sultanbek Tezadov (http://sultan.da.ru)
54 # Added:
55 # - 'gauge' widget *)
56 # - 'title' option to some widgets
57 # - 'checked' option to checklist dialog; clicking "Cancel" is now
58 # recognizable
59 # - 'selected' option to radiolist dialog; clicking "Cancel" is now
60 # recognizable
61 # - some other cosmetic changes and improvements
62 #
63
64 import os
65 from tempfile import mktemp
66 from string import split
67 from time import sleep
68
69 #
70 # Path of the dialog executable
71 #
72 DIALOG="/usr/bin/dialog"
73
74
75 class Dialog:
76 def __init__(self):
77 self.__bgTitle = '' # Default is no background title
78
79
80 def setBackgroundTitle(self, text):
81 self.__bgTitle = '--backtitle "%s"' % text
82
83
84 def __perform(self, cmd):
85 """Do the actual work of invoking dialog and getting the output."""
86 fName = mktemp()
87 rv = os.system('%s %s %s 2> %s' % (DIALOG, self.__bgTitle, cmd, fName))
88 f = open(fName)
89 output = f.readlines()
90 f.close()
91 os.unlink(fName)
92 return (rv, output)
93
94
95 def __perform_no_options(self, cmd):
96 """Call dialog w/out passing any more options. Needed by --clear."""
97 return os.system(DIALOG + ' ' + cmd)
98
99
100 def __handleTitle(self, title):
101 if len(title) == 0:
102 return ''
103 else:
104 return '--title "%s" ' % title
105
106
107 def yesno(self, text, height=10, width=30, title=''):
108 """
109 Put a Yes/No question to the user.
110 Uses the dialog --yesno option.
111 Returns a 1 or a 0.
112 """
113 (code, output) = self.__perform(self.__handleTitle(title) +\
114 '--yesno "%s" %d %d' % (text, height, width))
115 return code == 0
116
117
118 def msgbox(self, text, height=10, width=30, title=''):
119 """
120 Pop up a message to the user which has to be clicked
121 away with "ok".
122 """
123 self.__perform(self.__handleTitle(title) +\
124 '--msgbox "%s" %d %d' % (text, height, width))
125
126
127 def infobox(self, text, height=10, width=30):
128 """Make a message to the user, and return immediately."""
129 self.__perform('--infobox "%s" %d %d' % (text, height, width))
130
131
132 def inputbox(self, text, height=10, width=30, init='', title=''):
133 """
134 Request a line of input from the user.
135 Returns the user's input or None if cancel was chosen.
136 """
137 (c, o) = self.__perform(self.__handleTitle(title) +\
138 '--inputbox "%s" %d %d "%s"' % (text, height, width, init))
139 try:
140 return o[0]
141 except IndexError:
142 if c == 0: # empty string entered
143 return ''
144 else: # canceled
145 return None
146
147
148 def textbox(self, filename, height=20, width=60, title=None):
149 """Display a file in a scrolling text box."""
150 if title is None:
151 title = filename
152 self.__perform(self.__handleTitle(title) +\
153 ' --textbox "%s" %d %d' % (filename, height, width))
154
155
156 def menu(self, text, height=15, width=54, list=[]):
157 """
158 Display a menu of options to the user. This method simplifies the
159 --menu option of dialog, which allows for complex arguments. This
160 method receives a simple list of objects, and each one is assigned
161 a choice number.
162 The selected object is returned, or None if the dialog was canceled.
163 """
164 menuheight = height - 8
165 pairs = map(lambda i, item: (i + 1, item), range(len(list)), list)
166 choices = reduce(lambda res, pair: res + '%d "%s" ' % pair, pairs, '')
167 (code, output) = self.__perform('--menu "%s" %d %d %d %s' %\
168 (text, height, width, menuheight, choices))
169 try:
170 return list[int(output[0]) - 1]
171 except IndexError:
172 return None
173
174 def menu_ext(self, text, height=15, width=54, list=[], list2=[]):
175 """
176 Extended the method above for (string, string) pairs, for GLIS UI
177 """
178 menuheight = height - 8
179 pairs = []
180 for i in range(len(list)):
181 pairs.append((list2[i],list[i]))
182 #pairs = map(lambda i, item: (i + 1, item), range(len(list)), list)
183 choices = reduce(lambda res, pair: res + '%s "%s" ' % pair, pairs, '')
184 (code, output) = self.__perform('--menu "%s" %d %d %d %s' %\
185 (text, height, width, menuheight, choices))
186 try:
187 return output[0]
188 except IndexError:
189 return None
190
191
192 def checklist(self, text, height=15, width=54, list=[], checked=None):
193 """
194 Returns a list of the selected objects.
195 Returns an empty list if nothing was selected.
196 Returns None if the window was canceled.
197 checked -- a list of boolean (0/1) values; len(checked) must equal
198 len(list).
199 """
200 if checked is None:
201 checked = [0]*len(list)
202 menuheight = height - 8
203 triples = map(
204 lambda i, item, onoff, fs=('off', 'on'): (i + 1, item, fs[onoff]),
205 range(len(list)), list, checked)
206 choices = reduce(lambda res, triple: res + '%d "%s" %s ' % triple,
207 triples, '')
208 (c, o) = self.__perform('--checklist "%s" %d %d %d %s' %\
209 (text, height, width, menuheight, choices))
210 try:
211 output = o[0]
212 indexList = map(lambda x: int(x[1:-1]), split(output))
213 objectList = filter(lambda item, list=list, indexList=indexList:
214 list.index(item) + 1 in indexList,
215 list)
216 return objectList
217 except IndexError:
218 if c == 0: # Nothing was selected
219 return []
220 return None # Was canceled
221
222 def checklist_ext(self, text, height=15, width=54, list=[], list2=[], checked=None):
223 """
224 Returns a list of the selected objects.
225 Returns an empty list if nothing was selected.
226 Returns None if the window was canceled.
227 checked -- a list of boolean (0/1) values; len(checked) must equal
228 len(list).
229 """
230 if checked is None:
231 checked = [0]*len(list)
232 menuheight = height - 8
233 triples = []
234 #equally 3 lines, much more readable
235 fs = ('off','on')
236 for i in range(len(list)):
237 triples.append((list2[i],list[i],fs[checked[i]]))
238
239 ## triples = map(
240 ## lambda i, item, onoff, fs=('off', 'on'): (i + 1, item, fs[onoff]),
241 ## range(len(list)), list, checked)
242 choices = reduce(lambda res, triple: res + '%s "%s" %s ' % triple,
243 triples, '')
244 (c, o) = self.__perform('--checklist "%s" %d %d %d %s' %\
245 (text, height, width, menuheight, choices))
246 try:
247 output = o[0]
248 return split(output)
249 ## indexList = map(lambda x: int(x[1:-1]), split(output))
250 ## objectList = filter(lambda item, list=list, indexList=indexList:
251 ## list.index(item) + 1 in indexList,
252 ## list)
253 ## return objectList
254 except IndexError:
255 if c == 0: # Nothing was selected
256 return []
257 return None # Was canceled
258
259
260 def radiolist(self, text, height=15, width=54, list=[], selected=0):
261 """
262 Return the selected object.
263 Returns empty string if no choice was selected.
264 Returns None if window was canceled.
265 selected -- the selected item (must be between 1 and len(list)
266 or 0, meaning no selection).
267 """
268 menuheight = height - 8
269 triples = map(lambda i, item: (i + 1, item, 'off'),
270 range(len(list)), list)
271 if selected:
272 i, item, tmp = triples[selected - 1]
273 triples[selected - 1] = (i, item, 'on')
274 choices = reduce(lambda res, triple: res + '%d "%s" %s ' % triple,
275 triples, '')
276 (c, o) = self.__perform('--radiolist "%s" %d %d %d %s' %\
277 (text, height, width, menuheight, choices))
278 try:
279 return list[int(o[0]) - 1]
280 except IndexError:
281 if c == 0:
282 return ''
283 return None
284
285
286
287 def clear(self):
288 """
289 Clear the screen. Equivalent to the dialog --clear option.
290 """
291 self.__perform_no_options('--clear')
292
293
294 def scrollbox(self, text, height=20, width=60, title=''):
295 """
296 This is a bonus method. The dialog package only has a function to
297 display a file in a scrolling text field. This method allows any
298 string to be displayed by first saving it in a temp file, and calling
299 --textbox.
300 """
301 fName = mktemp()
302 f = open(fName, 'w')
303 f.write(text)
304 f.close()
305 self.__perform(self.__handleTitle(title) +\
306 '--textbox "%s" %d %d' % (fName, height, width))
307 os.unlink(fName)
308
309
310 def gauge_start(self, perc=0, text='', height=8, width=54, title=''):
311 """
312 Display gauge output window.
313 Gauge normal usage (assuming that there is an instace of 'Dialog'
314 class named 'd'):
315 d.gauge_start()
316 # do something
317 d.gauge_iterate(10) # passed throgh 10%
318 # ...
319 d.gauge_iterate(100, 'any text here') # work is done
320 d.stop_gauge() # clean-up actions
321 """
322 cmd = self.__handleTitle(title) +\
323 '--gauge "%s" %d %d %d' % (text, height, width, perc)
324 cmd = '%s %s %s 2> /dev/null' % (DIALOG, self.__bgTitle, cmd)
325 self.pipe = os.popen(cmd, 'w')
326 #/gauge_start()
327
328
329 def gauge_iterate(self, perc, text=''):
330 """
331 Update percentage point value.
332
333 See gauge_start() function above for the usage.
334 """
335 if text:
336 text = 'XXX\n%d\n%s\nXXX\n' % (perc, text)
337 else:
338 text = '%d\n' % perc
339 self.pipe.write(text)
340 self.pipe.flush()
341 #/gauge_iterate()
342
343
344 def gauge_stop(self):
345 """
346 Finish previously started gauge.
347
348 See gauge_start() function above for the usage.
349 """
350 self.pipe.close()
351 #/gauge_stop()
352
353
354
355 #
356 # DEMO APPLICATION
357 #
358 if __name__ == '__main__':
359 """
360 This demo tests all the features of the class.
361 """
362 d = Dialog()
363 d.setBackgroundTitle('dialog.py demo')
364
365 d.infobox(
366 "One moment... Just wasting some time here to test the infobox...")
367 sleep(3)
368
369 if d.yesno("Do you like this demo?"):
370 d.msgbox("Excellent! Here's the source code:")
371 else:
372 d.msgbox("Send your complaints to /dev/null")
373
374 d.textbox("dialog.py")
375
376 name = d.inputbox("What's your name?", init="Snow White")
377 fday = d.menu("What's your favorite day of the week?",
378 list=["Monday", "Tuesday", "Wednesday", "Thursday",
379 "Friday (The best day of all)", "Saturday", "Sunday"])
380 food = d.checklist("What sandwich toppings do you like?",
381 list=["Catsup", "Mustard", "Pesto", "Mayonaise", "Horse radish",
382 "Sun-dried tomatoes"], checked=[0,0,0,1,1,1])
383 sand = d.radiolist("What's your favorite kind of sandwich?",
384 list=["Hamburger", "Hotdog", "Burrito", "Doener", "Falafel",
385 "Bagel", "Big Mac", "Whopper", "Quarter Pounder",
386 "Peanut Butter and Jelly", "Grilled cheese"], selected=4)
387
388 # Prepare the message for the final window
389 bigMessage = "Here are some vital statistics about you:\n\nName: " + name +\
390 "\nFavorite day of the week: " + fday +\
391 "\nFavorite sandwich toppings:\n"
392 for topping in food:
393 bigMessage = bigMessage + " " + topping + "\n"
394 bigMessage = bigMessage + "Favorite sandwich: " + str(sand)
395
396 d.scrollbox(bigMessage)
397
398 #<># Gauge Demo
399 d.gauge_start(0, 'percentage: 0', title='Gauge Demo')
400 for i in range(1, 101):
401 if i < 50:
402 msg = 'percentage: %d' % i
403 elif i == 50:
404 msg = 'Over 50%'
405 else:
406 msg = ''
407 d.gauge_iterate(i, msg)
408 sleep(0.1)
409 d.gauge_stop()
410 #<>#
411
412 d.clear()

  ViewVC Help
Powered by ViewVC 1.1.20