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