From d9fc4acc572c6647a4f27b838d35d27d805d190e Mon Sep 17 00:00:00 2001 From: Jason Stubbs Date: Sun, 28 Aug 2005 08:37:44 +0000 Subject: Migration (without history) of the current stable line to subversion. svn path=/main/branches/2.0/; revision=1941 --- pym/dcdialog.py | 412 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 412 insertions(+) create mode 100644 pym/dcdialog.py (limited to 'pym/dcdialog.py') 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 +# 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() -- cgit v1.2.3-1-g7c22