2 # $Id: dialog.py,v 1.4 2012/06/29 09:33:18 tom Exp $
4 # Copyright (c) 2000 Robb Shecter <robb@acm.org>
6 # This source is covered by the GNU GPL.
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,
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.
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. :)
27 # One interesting feature is that the menu and selection windows allow
28 # *any* objects to be displayed and selected, not just strings.
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
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.
49 # Jul 2000, Sultanbek Tezadov (http://sultan.da.ru)
52 # - 'title' option to some widgets
53 # - 'checked' option to checklist dialog; clicking "Cancel" is now
55 # - 'selected' option to radiolist dialog; clicking "Cancel" is now
57 # - some other cosmetic changes and improvements
61 from tempfile import mktemp
62 from string import split
63 from time import sleep
66 # Path of the dialog executable
68 DIALOG = os.getenv("DIALOG");
74 self.__bgTitle = '' # Default is no background title
77 def setBackgroundTitle(self, text):
78 self.__bgTitle = '--backtitle "%s"' % text
81 def __perform(self, cmd):
82 """Do the actual work of invoking dialog and getting the output."""
84 rv = os.system('%s %s %s 2> %s' % (DIALOG, self.__bgTitle, cmd, fName))
86 output = f.readlines()
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)
97 def __handleTitle(self, title):
101 return '--title "%s" ' % title
104 def yesno(self, text, height=10, width=30, title=''):
106 Put a Yes/No question to the user.
107 Uses the dialog --yesno option.
110 (code, output) = self.__perform(self.__handleTitle(title) +\
111 '--yesno "%s" %d %d' % (text, height, width))
115 def msgbox(self, text, height=10, width=30, title=''):
117 Pop up a message to the user which has to be clicked
120 self.__perform(self.__handleTitle(title) +\
121 '--msgbox "%s" %d %d' % (text, height, width))
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))
129 def inputbox(self, text, height=10, width=30, init='', title=''):
131 Request a line of input from the user.
132 Returns the user's input or None if cancel was chosen.
134 (c, o) = self.__perform(self.__handleTitle(title) +\
135 '--inputbox "%s" %d %d "%s"' % (text, height, width, init))
139 if c == 0: # empty string entered
145 def textbox(self, filename, height=20, width=60, title=None):
146 """Display a file in a scrolling text box."""
149 self.__perform(self.__handleTitle(title) +\
150 ' --textbox "%s" %d %d' % (filename, height, width))
153 def menu(self, text, height=15, width=54, list=[]):
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
159 The selected object is returned, or None if the dialog was canceled.
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))
167 return list[int(output[0]) - 1]
172 def checklist(self, text, height=15, width=54, list=[], checked=None):
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
181 checked = [0]*len(list)
182 menuheight = height - 8
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,
188 (c, o) = self.__perform('--checklist "%s" %d %d %d %s' %\
189 (text, height, width, menuheight, choices))
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,
198 if c == 0: # Nothing was selected
200 return None # Was canceled
203 def radiolist(self, text, height=15, width=54, list=[], selected=0):
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).
211 menuheight = height - 8
212 triples = map(lambda i, item: (i + 1, item, 'off'),
213 range(len(list)), list)
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,
219 (c, o) = self.__perform('--radiolist "%s" %d %d %d %s' %\
220 (text, height, width, menuheight, choices))
222 return list[int(o[0]) - 1]
231 Clear the screen. Equivalent to the dialog --clear option.
233 self.__perform_no_options('--clear')
236 def scrollbox(self, text, height=20, width=60, title=''):
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
247 self.__perform(self.__handleTitle(title) +\
248 '--textbox "%s" %d %d' % (fName, height, width))
252 def gauge_start(self, perc=0, text='', height=8, width=54, title=''):
254 Display gauge output window.
255 Gauge normal usage (assuming that there is an instace of 'Dialog'
259 d.gauge_iterate(10) # passed throgh 10%
261 d.gauge_iterate(100, 'any text here') # work is done
262 d.stop_gauge() # clean-up actions
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')
271 def gauge_iterate(self, perc, text=''):
273 Update percentage point value.
275 See gauge_start() function above for the usage.
278 text = 'XXX\n%d\n%s\nXXX\n' % (perc, text)
281 self.pipe.write(text)
286 def gauge_stop(self):
288 Finish previously started gauge.
290 See gauge_start() function above for the usage.
300 if __name__ == '__main__':
302 This demo tests all the features of the class.
305 d.setBackgroundTitle('dialog.py demo')
308 "One moment... Just wasting some time here to test the infobox...")
311 if d.yesno("Do you like this demo?"):
312 d.msgbox("Excellent! Here's the source code:")
314 d.msgbox("Send your complaints to /dev/null")
316 d.textbox("dialog.py")
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)
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"
335 bigMessage = bigMessage + " " + topping + "\n"
336 bigMessage = bigMessage + "Favorite sandwich: " + str(sand)
338 d.scrollbox(bigMessage)
341 d.gauge_start(0, 'percentage: 0', title='Gauge Demo')
342 for i in range(1, 101):
344 msg = 'percentage: %d' % i
349 d.gauge_iterate(i, msg)