aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'pym/dcdialog.py')
-rw-r--r--pym/dcdialog.py412
1 files changed, 412 insertions, 0 deletions
diff --git a/pym/dcdialog.py b/pym/dcdialog.py
new file mode 100644
index 000000000..64d39983c
--- /dev/null
+++ b/pym/dcdialog.py
@@ -0,0 +1,412 @@
+#
+# Changes and extensions by Carlos Castillo...
+#
+cvs_id_string="$Id: dcdialog.py,v 1.1.2.1 2005/01/16 02:35:33 carpaski Exp $"[5:-2]
+
+#
+# Module: dialog.py
+# Copyright (c) 2000 Robb Shecter <robb@acm.org>
+# All rights reserved.
+# This source is covered by the GNU GPL.
+#
+# This module is a Python wrapper around the Linux "dialog" utility
+# by Savio Lam and Stuart Herbert. My goals were to make dialog as
+# easy to use from Python as possible. The demo code at the end of
+# the module is a good example of how to use it. To run the demo,
+# execute:
+#
+# python dialog.py
+#
+# This module has one class in it, "Dialog". An application typically
+# creates an instance of it, and possibly sets the background title option.
+# Then, methods can be called on it for interacting with the user.
+#
+# I wrote this because I want to use my 486-33 laptop as my main
+# development computer (!), and I wanted a way to nicely interact with the
+# user in console mode. There are apparently other modules out there
+# with similar functionality, but they require the Python curses library.
+# Writing this module from scratch was easier than figuring out how to
+# recompile Python with curses enabled. :)
+#
+# One interesting feature is that the menu and selection windows allow
+# *any* objects to be displayed and selected, not just strings.
+#
+# TO DO:
+# Add code so that the input buffer is flushed before a dialog box is
+# shown. This would make the UI more predictable for users. This
+# feature could be turned on and off through an instance method.
+# Drop using temporary files when interacting with 'dialog'
+# (it's possible -- I've already tried :-).
+# Try detecting the terminal window size in order to make reasonable
+# height and width defaults. Hmmm - should also then check for
+# terminal resizing...
+# Put into a package name to make more reusable - reduce the possibility
+# of name collisions.
+#
+# NOTES:
+# there is a bug in (at least) Linux-Mandrake 7.0 Russian Edition
+# running on AMD K6-2 3D that causes core dump when 'dialog'
+# is running with --gauge option;
+# in this case you'll have to recompile 'dialog' program.
+#
+# Modifications:
+# Jul 2000, Sultanbek Tezadov (http://sultan.da.ru)
+# Added:
+# - 'gauge' widget *)
+# - 'title' option to some widgets
+# - 'checked' option to checklist dialog; clicking "Cancel" is now
+# recognizable
+# - 'selected' option to radiolist dialog; clicking "Cancel" is now
+# recognizable
+# - some other cosmetic changes and improvements
+#
+
+import os
+from tempfile import mktemp
+from string import split
+from time import sleep
+
+#
+# Path of the dialog executable
+#
+DIALOG="/usr/bin/dialog"
+
+
+class Dialog:
+ def __init__(self):
+ self.__bgTitle = '' # Default is no background title
+
+
+ def setBackgroundTitle(self, text):
+ self.__bgTitle = '--backtitle "%s"' % text
+
+
+ def __perform(self, cmd):
+ """Do the actual work of invoking dialog and getting the output."""
+ fName = mktemp()
+ rv = os.system('%s %s %s 2> %s' % (DIALOG, self.__bgTitle, cmd, fName))
+ f = open(fName)
+ output = f.readlines()
+ f.close()
+ os.unlink(fName)
+ return (rv, output)
+
+
+ def __perform_no_options(self, cmd):
+ """Call dialog w/out passing any more options. Needed by --clear."""
+ return os.system(DIALOG + ' ' + cmd)
+
+
+ def __handleTitle(self, title):
+ if len(title) == 0:
+ return ''
+ else:
+ return '--title "%s" ' % title
+
+
+ def yesno(self, text, height=10, width=30, title=''):
+ """
+ Put a Yes/No question to the user.
+ Uses the dialog --yesno option.
+ Returns a 1 or a 0.
+ """
+ (code, output) = self.__perform(self.__handleTitle(title) +\
+ '--yesno "%s" %d %d' % (text, height, width))
+ return code == 0
+
+
+ def msgbox(self, text, height=10, width=30, title=''):
+ """
+ Pop up a message to the user which has to be clicked
+ away with "ok".
+ """
+ self.__perform(self.__handleTitle(title) +\
+ '--msgbox "%s" %d %d' % (text, height, width))
+
+
+ def infobox(self, text, height=10, width=30):
+ """Make a message to the user, and return immediately."""
+ self.__perform('--infobox "%s" %d %d' % (text, height, width))
+
+
+ def inputbox(self, text, height=10, width=30, init='', title=''):
+ """
+ Request a line of input from the user.
+ Returns the user's input or None if cancel was chosen.
+ """
+ (c, o) = self.__perform(self.__handleTitle(title) +\
+ '--inputbox "%s" %d %d "%s"' % (text, height, width, init))
+ try:
+ return o[0]
+ except IndexError:
+ if c == 0: # empty string entered
+ return ''
+ else: # canceled
+ return None
+
+
+ def textbox(self, filename, height=20, width=60, title=None):
+ """Display a file in a scrolling text box."""
+ if title is None:
+ title = filename
+ self.__perform(self.__handleTitle(title) +\
+ ' --textbox "%s" %d %d' % (filename, height, width))
+
+
+ def menu(self, text, height=15, width=54, list=[]):
+ """
+ Display a menu of options to the user. This method simplifies the
+ --menu option of dialog, which allows for complex arguments. This
+ method receives a simple list of objects, and each one is assigned
+ a choice number.
+ The selected object is returned, or None if the dialog was canceled.
+ """
+ menuheight = height - 8
+ pairs = map(lambda i, item: (i + 1, item), range(len(list)), list)
+ choices = reduce(lambda res, pair: res + '%d "%s" ' % pair, pairs, '')
+ (code, output) = self.__perform('--menu "%s" %d %d %d %s' %\
+ (text, height, width, menuheight, choices))
+ try:
+ return list[int(output[0]) - 1]
+ except IndexError:
+ return None
+
+ def menu_ext(self, text, height=15, width=54, list=[], list2=[]):
+ """
+ Extended the method above for (string, string) pairs, for GLIS UI
+ """
+ menuheight = height - 8
+ pairs = []
+ for i in range(len(list)):
+ pairs.append((list2[i],list[i]))
+ #pairs = map(lambda i, item: (i + 1, item), range(len(list)), list)
+ choices = reduce(lambda res, pair: res + '%s "%s" ' % pair, pairs, '')
+ (code, output) = self.__perform('--menu "%s" %d %d %d %s' %\
+ (text, height, width, menuheight, choices))
+ try:
+ return output[0]
+ except IndexError:
+ return None
+
+
+ def checklist(self, text, height=15, width=54, list=[], checked=None):
+ """
+ Returns a list of the selected objects.
+ Returns an empty list if nothing was selected.
+ Returns None if the window was canceled.
+ checked -- a list of boolean (0/1) values; len(checked) must equal
+ len(list).
+ """
+ if checked is None:
+ checked = [0]*len(list)
+ menuheight = height - 8
+ triples = map(
+ lambda i, item, onoff, fs=('off', 'on'): (i + 1, item, fs[onoff]),
+ range(len(list)), list, checked)
+ choices = reduce(lambda res, triple: res + '%d "%s" %s ' % triple,
+ triples, '')
+ (c, o) = self.__perform('--checklist "%s" %d %d %d %s' %\
+ (text, height, width, menuheight, choices))
+ try:
+ output = o[0]
+ indexList = map(lambda x: int(x[1:-1]), split(output))
+ objectList = filter(lambda item, list=list, indexList=indexList:
+ list.index(item) + 1 in indexList,
+ list)
+ return objectList
+ except IndexError:
+ if c == 0: # Nothing was selected
+ return []
+ return None # Was canceled
+
+ def checklist_ext(self, text, height=15, width=54, list=[], list2=[], checked=None):
+ """
+ Returns a list of the selected objects.
+ Returns an empty list if nothing was selected.
+ Returns None if the window was canceled.
+ checked -- a list of boolean (0/1) values; len(checked) must equal
+ len(list).
+ """
+ if checked is None:
+ checked = [0]*len(list)
+ menuheight = height - 8
+ triples = []
+ #equally 3 lines, much more readable
+ fs = ('off','on')
+ for i in range(len(list)):
+ triples.append((list2[i],list[i],fs[checked[i]]))
+
+## triples = map(
+## lambda i, item, onoff, fs=('off', 'on'): (i + 1, item, fs[onoff]),
+## range(len(list)), list, checked)
+ choices = reduce(lambda res, triple: res + '%s "%s" %s ' % triple,
+ triples, '')
+ (c, o) = self.__perform('--checklist "%s" %d %d %d %s' %\
+ (text, height, width, menuheight, choices))
+ try:
+ output = o[0]
+ return split(output)
+## indexList = map(lambda x: int(x[1:-1]), split(output))
+## objectList = filter(lambda item, list=list, indexList=indexList:
+## list.index(item) + 1 in indexList,
+## list)
+## return objectList
+ except IndexError:
+ if c == 0: # Nothing was selected
+ return []
+ return None # Was canceled
+
+
+ def radiolist(self, text, height=15, width=54, list=[], selected=0):
+ """
+ Return the selected object.
+ Returns empty string if no choice was selected.
+ Returns None if window was canceled.
+ selected -- the selected item (must be between 1 and len(list)
+ or 0, meaning no selection).
+ """
+ menuheight = height - 8
+ triples = map(lambda i, item: (i + 1, item, 'off'),
+ range(len(list)), list)
+ if selected:
+ i, item, tmp = triples[selected - 1]
+ triples[selected - 1] = (i, item, 'on')
+ choices = reduce(lambda res, triple: res + '%d "%s" %s ' % triple,
+ triples, '')
+ (c, o) = self.__perform('--radiolist "%s" %d %d %d %s' %\
+ (text, height, width, menuheight, choices))
+ try:
+ return list[int(o[0]) - 1]
+ except IndexError:
+ if c == 0:
+ return ''
+ return None
+
+
+
+ def clear(self):
+ """
+ Clear the screen. Equivalent to the dialog --clear option.
+ """
+ self.__perform_no_options('--clear')
+
+
+ def scrollbox(self, text, height=20, width=60, title=''):
+ """
+ This is a bonus method. The dialog package only has a function to
+ display a file in a scrolling text field. This method allows any
+ string to be displayed by first saving it in a temp file, and calling
+ --textbox.
+ """
+ fName = mktemp()
+ f = open(fName, 'w')
+ f.write(text)
+ f.close()
+ self.__perform(self.__handleTitle(title) +\
+ '--textbox "%s" %d %d' % (fName, height, width))
+ os.unlink(fName)
+
+
+ def gauge_start(self, perc=0, text='', height=8, width=54, title=''):
+ """
+ Display gauge output window.
+ Gauge normal usage (assuming that there is an instace of 'Dialog'
+ class named 'd'):
+ d.gauge_start()
+ # do something
+ d.gauge_iterate(10) # passed throgh 10%
+ # ...
+ d.gauge_iterate(100, 'any text here') # work is done
+ d.stop_gauge() # clean-up actions
+ """
+ cmd = self.__handleTitle(title) +\
+ '--gauge "%s" %d %d %d' % (text, height, width, perc)
+ cmd = '%s %s %s 2> /dev/null' % (DIALOG, self.__bgTitle, cmd)
+ self.pipe = os.popen(cmd, 'w')
+ #/gauge_start()
+
+
+ def gauge_iterate(self, perc, text=''):
+ """
+ Update percentage point value.
+
+ See gauge_start() function above for the usage.
+ """
+ if text:
+ text = 'XXX\n%d\n%s\nXXX\n' % (perc, text)
+ else:
+ text = '%d\n' % perc
+ self.pipe.write(text)
+ self.pipe.flush()
+ #/gauge_iterate()
+
+
+ def gauge_stop(self):
+ """
+ Finish previously started gauge.
+
+ See gauge_start() function above for the usage.
+ """
+ self.pipe.close()
+ #/gauge_stop()
+
+
+
+#
+# DEMO APPLICATION
+#
+if __name__ == '__main__':
+ """
+ This demo tests all the features of the class.
+ """
+ d = Dialog()
+ d.setBackgroundTitle('dialog.py demo')
+
+ d.infobox(
+ "One moment... Just wasting some time here to test the infobox...")
+ sleep(3)
+
+ if d.yesno("Do you like this demo?"):
+ d.msgbox("Excellent! Here's the source code:")
+ else:
+ d.msgbox("Send your complaints to /dev/null")
+
+ d.textbox("dialog.py")
+
+ name = d.inputbox("What's your name?", init="Snow White")
+ fday = d.menu("What's your favorite day of the week?",
+ list=["Monday", "Tuesday", "Wednesday", "Thursday",
+ "Friday (The best day of all)", "Saturday", "Sunday"])
+ food = d.checklist("What sandwich toppings do you like?",
+ list=["Catsup", "Mustard", "Pesto", "Mayonaise", "Horse radish",
+ "Sun-dried tomatoes"], checked=[0,0,0,1,1,1])
+ sand = d.radiolist("What's your favorite kind of sandwich?",
+ list=["Hamburger", "Hotdog", "Burrito", "Doener", "Falafel",
+ "Bagel", "Big Mac", "Whopper", "Quarter Pounder",
+ "Peanut Butter and Jelly", "Grilled cheese"], selected=4)
+
+ # Prepare the message for the final window
+ bigMessage = "Here are some vital statistics about you:\n\nName: " + name +\
+ "\nFavorite day of the week: " + fday +\
+ "\nFavorite sandwich toppings:\n"
+ for topping in food:
+ bigMessage = bigMessage + " " + topping + "\n"
+ bigMessage = bigMessage + "Favorite sandwich: " + str(sand)
+
+ d.scrollbox(bigMessage)
+
+ #<># Gauge Demo
+ d.gauge_start(0, 'percentage: 0', title='Gauge Demo')
+ for i in range(1, 101):
+ if i < 50:
+ msg = 'percentage: %d' % i
+ elif i == 50:
+ msg = 'Over 50%'
+ else:
+ msg = ''
+ d.gauge_iterate(i, msg)
+ sleep(0.1)
+ d.gauge_stop()
+ #<>#
+
+ d.clear()