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