1 # $Id: dialog.py,v 1.3 2004/09/21 00:52:15 tom Exp $
3 # Copyright (c) 2000 Robb Shecter <robb@acm.org>
5 # This source is covered by the GNU GPL.
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,
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.
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. :)
26 # One interesting feature is that the menu and selection windows allow
27 # *any* objects to be displayed and selected, not just strings.
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
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.
48 # Jul 2000, Sultanbek Tezadov (http://sultan.da.ru)
51 # - 'title' option to some widgets
52 # - 'checked' option to checklist dialog; clicking "Cancel" is now
54 # - 'selected' option to radiolist dialog; clicking "Cancel" is now
56 # - some other cosmetic changes and improvements
60 from tempfile import mktemp
61 from string import split
62 from time import sleep
65 # Path of the dialog executable
67 DIALOG = os.getenv("DIALOG");
73 self.__bgTitle = '' # Default is no background title
76 def setBackgroundTitle(self, text):
77 self.__bgTitle = '--backtitle "%s"' % text
80 def __perform(self, cmd):
81 """Do the actual work of invoking dialog and getting the output."""
83 rv = os.system('%s %s %s 2> %s' % (DIALOG, self.__bgTitle, cmd, fName))
85 output = f.readlines()
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)
96 def __handleTitle(self, title):
100 return '--title "%s" ' % title
103 def yesno(self, text, height=10, width=30, title=''):
105 Put a Yes/No question to the user.
106 Uses the dialog --yesno option.
109 (code, output) = self.__perform(self.__handleTitle(title) +\
110 '--yesno "%s" %d %d' % (text, height, width))
114 def msgbox(self, text, height=10, width=30, title=''):
116 Pop up a message to the user which has to be clicked
119 self.__perform(self.__handleTitle(title) +\
120 '--msgbox "%s" %d %d' % (text, height, width))
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))
128 def inputbox(self, text, height=10, width=30, init='', title=''):
130 Request a line of input from the user.
131 Returns the user's input or None if cancel was chosen.
133 (c, o) = self.__perform(self.__handleTitle(title) +\
134 '--inputbox "%s" %d %d "%s"' % (text, height, width, init))
138 if c == 0: # empty string entered
144 def textbox(self, filename, height=20, width=60, title=None):
145 """Display a file in a scrolling text box."""
148 self.__perform(self.__handleTitle(title) +\
149 ' --textbox "%s" %d %d' % (filename, height, width))
152 def menu(self, text, height=15, width=54, list=[]):
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
158 The selected object is returned, or None if the dialog was canceled.
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))
166 return list[int(output[0]) - 1]
171 def checklist(self, text, height=15, width=54, list=[], checked=None):
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
180 checked = [0]*len(list)
181 menuheight = height - 8
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,
187 (c, o) = self.__perform('--checklist "%s" %d %d %d %s' %\
188 (text, height, width, menuheight, choices))
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,
197 if c == 0: # Nothing was selected
199 return None # Was canceled
202 def radiolist(self, text, height=15, width=54, list=[], selected=0):
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).
210 menuheight = height - 8
211 triples = map(lambda i, item: (i + 1, item, 'off'),
212 range(len(list)), list)
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,
218 (c, o) = self.__perform('--radiolist "%s" %d %d %d %s' %\
219 (text, height, width, menuheight, choices))
221 return list[int(o[0]) - 1]
230 Clear the screen. Equivalent to the dialog --clear option.
232 self.__perform_no_options('--clear')
235 def scrollbox(self, text, height=20, width=60, title=''):
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
246 self.__perform(self.__handleTitle(title) +\
247 '--textbox "%s" %d %d' % (fName, height, width))
251 def gauge_start(self, perc=0, text='', height=8, width=54, title=''):
253 Display gauge output window.
254 Gauge normal usage (assuming that there is an instace of 'Dialog'
258 d.gauge_iterate(10) # passed throgh 10%
260 d.gauge_iterate(100, 'any text here') # work is done
261 d.stop_gauge() # clean-up actions
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')
270 def gauge_iterate(self, perc, text=''):
272 Update percentage point value.
274 See gauge_start() function above for the usage.
277 text = 'XXX\n%d\n%s\nXXX\n' % (perc, text)
280 self.pipe.write(text)
285 def gauge_stop(self):
287 Finish previously started gauge.
289 See gauge_start() function above for the usage.
299 if __name__ == '__main__':
301 This demo tests all the features of the class.
304 d.setBackgroundTitle('dialog.py demo')
307 "One moment... Just wasting some time here to test the infobox...")
310 if d.yesno("Do you like this demo?"):
311 d.msgbox("Excellent! Here's the source code:")
313 d.msgbox("Send your complaints to /dev/null")
315 d.textbox("dialog.py")
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)
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"
334 bigMessage = bigMessage + " " + topping + "\n"
335 bigMessage = bigMessage + "Favorite sandwich: " + str(sand)
337 d.scrollbox(bigMessage)
340 d.gauge_start(0, 'percentage: 0', title='Gauge Demo')
341 for i in range(1, 101):
343 msg = 'percentage: %d' % i
348 d.gauge_iterate(i, msg)