1251843Sbapt#!/usr/bin/python 2251843Sbapt# $Id: dialog.py,v 1.4 2012/06/29 09:33:18 tom Exp $ 3217309Snwhitehorn# Module: dialog.py 4217309Snwhitehorn# Copyright (c) 2000 Robb Shecter <robb@acm.org> 5217309Snwhitehorn# All rights reserved. 6217309Snwhitehorn# This source is covered by the GNU GPL. 7217309Snwhitehorn# 8217309Snwhitehorn# This module is a Python wrapper around the Linux "dialog" utility 9217309Snwhitehorn# by Savio Lam and Stuart Herbert. My goals were to make dialog as 10217309Snwhitehorn# easy to use from Python as possible. The demo code at the end of 11217309Snwhitehorn# the module is a good example of how to use it. To run the demo, 12217309Snwhitehorn# execute: 13217309Snwhitehorn# 14217309Snwhitehorn# python dialog.py 15217309Snwhitehorn# 16217309Snwhitehorn# This module has one class in it, "Dialog". An application typically 17217309Snwhitehorn# creates an instance of it, and possibly sets the background title option. 18217309Snwhitehorn# Then, methods can be called on it for interacting with the user. 19217309Snwhitehorn# 20217309Snwhitehorn# I wrote this because I want to use my 486-33 laptop as my main 21217309Snwhitehorn# development computer (!), and I wanted a way to nicely interact with the 22217309Snwhitehorn# user in console mode. There are apparently other modules out there 23217309Snwhitehorn# with similar functionality, but they require the Python curses library. 24217309Snwhitehorn# Writing this module from scratch was easier than figuring out how to 25217309Snwhitehorn# recompile Python with curses enabled. :) 26217309Snwhitehorn# 27217309Snwhitehorn# One interesting feature is that the menu and selection windows allow 28217309Snwhitehorn# *any* objects to be displayed and selected, not just strings. 29217309Snwhitehorn# 30217309Snwhitehorn# TO DO: 31217309Snwhitehorn# Add code so that the input buffer is flushed before a dialog box is 32217309Snwhitehorn# shown. This would make the UI more predictable for users. This 33217309Snwhitehorn# feature could be turned on and off through an instance method. 34217309Snwhitehorn# Drop using temporary files when interacting with 'dialog' 35217309Snwhitehorn# (it's possible -- I've already tried :-). 36217309Snwhitehorn# Try detecting the terminal window size in order to make reasonable 37217309Snwhitehorn# height and width defaults. Hmmm - should also then check for 38217309Snwhitehorn# terminal resizing... 39217309Snwhitehorn# Put into a package name to make more reusable - reduce the possibility 40217309Snwhitehorn# of name collisions. 41217309Snwhitehorn# 42217309Snwhitehorn# NOTES: 43217309Snwhitehorn# there is a bug in (at least) Linux-Mandrake 7.0 Russian Edition 44217309Snwhitehorn# running on AMD K6-2 3D that causes core dump when 'dialog' 45217309Snwhitehorn# is running with --gauge option; 46217309Snwhitehorn# in this case you'll have to recompile 'dialog' program. 47217309Snwhitehorn# 48217309Snwhitehorn# Modifications: 49217309Snwhitehorn# Jul 2000, Sultanbek Tezadov (http://sultan.da.ru) 50217309Snwhitehorn# Added: 51217309Snwhitehorn# - 'gauge' widget *) 52217309Snwhitehorn# - 'title' option to some widgets 53217309Snwhitehorn# - 'checked' option to checklist dialog; clicking "Cancel" is now 54217309Snwhitehorn# recognizable 55217309Snwhitehorn# - 'selected' option to radiolist dialog; clicking "Cancel" is now 56217309Snwhitehorn# recognizable 57217309Snwhitehorn# - some other cosmetic changes and improvements 58217309Snwhitehorn# 59217309Snwhitehorn 60217309Snwhitehornimport os 61217309Snwhitehornfrom tempfile import mktemp 62217309Snwhitehornfrom string import split 63217309Snwhitehornfrom time import sleep 64217309Snwhitehorn 65217309Snwhitehorn# 66217309Snwhitehorn# Path of the dialog executable 67217309Snwhitehorn# 68217309SnwhitehornDIALOG = os.getenv("DIALOG"); 69217309Snwhitehornif DIALOG is None: 70217309Snwhitehorn DIALOG="../dialog"; 71217309Snwhitehorn 72217309Snwhitehornclass Dialog: 73217309Snwhitehorn def __init__(self): 74217309Snwhitehorn self.__bgTitle = '' # Default is no background title 75217309Snwhitehorn 76217309Snwhitehorn 77217309Snwhitehorn def setBackgroundTitle(self, text): 78217309Snwhitehorn self.__bgTitle = '--backtitle "%s"' % text 79217309Snwhitehorn 80217309Snwhitehorn 81217309Snwhitehorn def __perform(self, cmd): 82217309Snwhitehorn """Do the actual work of invoking dialog and getting the output.""" 83217309Snwhitehorn fName = mktemp() 84217309Snwhitehorn rv = os.system('%s %s %s 2> %s' % (DIALOG, self.__bgTitle, cmd, fName)) 85217309Snwhitehorn f = open(fName) 86217309Snwhitehorn output = f.readlines() 87217309Snwhitehorn f.close() 88217309Snwhitehorn os.unlink(fName) 89217309Snwhitehorn return (rv, output) 90217309Snwhitehorn 91217309Snwhitehorn 92217309Snwhitehorn def __perform_no_options(self, cmd): 93217309Snwhitehorn """Call dialog w/out passing any more options. Needed by --clear.""" 94217309Snwhitehorn return os.system(DIALOG + ' ' + cmd) 95217309Snwhitehorn 96217309Snwhitehorn 97217309Snwhitehorn def __handleTitle(self, title): 98217309Snwhitehorn if len(title) == 0: 99217309Snwhitehorn return '' 100217309Snwhitehorn else: 101217309Snwhitehorn return '--title "%s" ' % title 102217309Snwhitehorn 103217309Snwhitehorn 104217309Snwhitehorn def yesno(self, text, height=10, width=30, title=''): 105217309Snwhitehorn """ 106217309Snwhitehorn Put a Yes/No question to the user. 107217309Snwhitehorn Uses the dialog --yesno option. 108217309Snwhitehorn Returns a 1 or a 0. 109217309Snwhitehorn """ 110217309Snwhitehorn (code, output) = self.__perform(self.__handleTitle(title) +\ 111217309Snwhitehorn '--yesno "%s" %d %d' % (text, height, width)) 112217309Snwhitehorn return code == 0 113217309Snwhitehorn 114217309Snwhitehorn 115217309Snwhitehorn def msgbox(self, text, height=10, width=30, title=''): 116217309Snwhitehorn """ 117217309Snwhitehorn Pop up a message to the user which has to be clicked 118217309Snwhitehorn away with "ok". 119217309Snwhitehorn """ 120217309Snwhitehorn self.__perform(self.__handleTitle(title) +\ 121217309Snwhitehorn '--msgbox "%s" %d %d' % (text, height, width)) 122217309Snwhitehorn 123217309Snwhitehorn 124217309Snwhitehorn def infobox(self, text, height=10, width=30): 125217309Snwhitehorn """Make a message to the user, and return immediately.""" 126217309Snwhitehorn self.__perform('--infobox "%s" %d %d' % (text, height, width)) 127217309Snwhitehorn 128217309Snwhitehorn 129217309Snwhitehorn def inputbox(self, text, height=10, width=30, init='', title=''): 130217309Snwhitehorn """ 131217309Snwhitehorn Request a line of input from the user. 132217309Snwhitehorn Returns the user's input or None if cancel was chosen. 133217309Snwhitehorn """ 134217309Snwhitehorn (c, o) = self.__perform(self.__handleTitle(title) +\ 135217309Snwhitehorn '--inputbox "%s" %d %d "%s"' % (text, height, width, init)) 136217309Snwhitehorn try: 137217309Snwhitehorn return o[0] 138217309Snwhitehorn except IndexError: 139217309Snwhitehorn if c == 0: # empty string entered 140217309Snwhitehorn return '' 141217309Snwhitehorn else: # canceled 142217309Snwhitehorn return None 143217309Snwhitehorn 144217309Snwhitehorn 145217309Snwhitehorn def textbox(self, filename, height=20, width=60, title=None): 146217309Snwhitehorn """Display a file in a scrolling text box.""" 147217309Snwhitehorn if title is None: 148217309Snwhitehorn title = filename 149217309Snwhitehorn self.__perform(self.__handleTitle(title) +\ 150217309Snwhitehorn ' --textbox "%s" %d %d' % (filename, height, width)) 151217309Snwhitehorn 152217309Snwhitehorn 153217309Snwhitehorn def menu(self, text, height=15, width=54, list=[]): 154217309Snwhitehorn """ 155217309Snwhitehorn Display a menu of options to the user. This method simplifies the 156217309Snwhitehorn --menu option of dialog, which allows for complex arguments. This 157217309Snwhitehorn method receives a simple list of objects, and each one is assigned 158217309Snwhitehorn a choice number. 159217309Snwhitehorn The selected object is returned, or None if the dialog was canceled. 160217309Snwhitehorn """ 161217309Snwhitehorn menuheight = height - 8 162217309Snwhitehorn pairs = map(lambda i, item: (i + 1, item), range(len(list)), list) 163217309Snwhitehorn choices = reduce(lambda res, pair: res + '%d "%s" ' % pair, pairs, '') 164217309Snwhitehorn (code, output) = self.__perform('--menu "%s" %d %d %d %s' %\ 165217309Snwhitehorn (text, height, width, menuheight, choices)) 166217309Snwhitehorn try: 167217309Snwhitehorn return list[int(output[0]) - 1] 168217309Snwhitehorn except IndexError: 169217309Snwhitehorn return None 170217309Snwhitehorn 171217309Snwhitehorn 172217309Snwhitehorn def checklist(self, text, height=15, width=54, list=[], checked=None): 173217309Snwhitehorn """ 174217309Snwhitehorn Returns a list of the selected objects. 175217309Snwhitehorn Returns an empty list if nothing was selected. 176217309Snwhitehorn Returns None if the window was canceled. 177217309Snwhitehorn checked -- a list of boolean (0/1) values; len(checked) must equal 178217309Snwhitehorn len(list). 179217309Snwhitehorn """ 180217309Snwhitehorn if checked is None: 181217309Snwhitehorn checked = [0]*len(list) 182217309Snwhitehorn menuheight = height - 8 183217309Snwhitehorn triples = map( 184217309Snwhitehorn lambda i, item, onoff, fs=('off', 'on'): (i + 1, item, fs[onoff]), 185217309Snwhitehorn range(len(list)), list, checked) 186217309Snwhitehorn choices = reduce(lambda res, triple: res + '%d "%s" %s ' % triple, 187217309Snwhitehorn triples, '') 188217309Snwhitehorn (c, o) = self.__perform('--checklist "%s" %d %d %d %s' %\ 189217309Snwhitehorn (text, height, width, menuheight, choices)) 190217309Snwhitehorn try: 191217309Snwhitehorn output = o[0] 192217309Snwhitehorn indexList = map(lambda x: int(x[1:-1]), split(output)) 193217309Snwhitehorn objectList = filter(lambda item, list=list, indexList=indexList: 194217309Snwhitehorn list.index(item) + 1 in indexList, 195217309Snwhitehorn list) 196217309Snwhitehorn return objectList 197217309Snwhitehorn except IndexError: 198217309Snwhitehorn if c == 0: # Nothing was selected 199217309Snwhitehorn return [] 200217309Snwhitehorn return None # Was canceled 201217309Snwhitehorn 202217309Snwhitehorn 203217309Snwhitehorn def radiolist(self, text, height=15, width=54, list=[], selected=0): 204217309Snwhitehorn """ 205217309Snwhitehorn Return the selected object. 206217309Snwhitehorn Returns empty string if no choice was selected. 207217309Snwhitehorn Returns None if window was canceled. 208217309Snwhitehorn selected -- the selected item (must be between 1 and len(list) 209217309Snwhitehorn or 0, meaning no selection). 210217309Snwhitehorn """ 211217309Snwhitehorn menuheight = height - 8 212217309Snwhitehorn triples = map(lambda i, item: (i + 1, item, 'off'), 213217309Snwhitehorn range(len(list)), list) 214217309Snwhitehorn if selected: 215217309Snwhitehorn i, item, tmp = triples[selected - 1] 216217309Snwhitehorn triples[selected - 1] = (i, item, 'on') 217217309Snwhitehorn choices = reduce(lambda res, triple: res + '%d "%s" %s ' % triple, 218217309Snwhitehorn triples, '') 219217309Snwhitehorn (c, o) = self.__perform('--radiolist "%s" %d %d %d %s' %\ 220217309Snwhitehorn (text, height, width, menuheight, choices)) 221217309Snwhitehorn try: 222217309Snwhitehorn return list[int(o[0]) - 1] 223217309Snwhitehorn except IndexError: 224217309Snwhitehorn if c == 0: 225217309Snwhitehorn return '' 226217309Snwhitehorn return None 227217309Snwhitehorn 228217309Snwhitehorn 229217309Snwhitehorn def clear(self): 230217309Snwhitehorn """ 231217309Snwhitehorn Clear the screen. Equivalent to the dialog --clear option. 232217309Snwhitehorn """ 233217309Snwhitehorn self.__perform_no_options('--clear') 234217309Snwhitehorn 235217309Snwhitehorn 236217309Snwhitehorn def scrollbox(self, text, height=20, width=60, title=''): 237217309Snwhitehorn """ 238217309Snwhitehorn This is a bonus method. The dialog package only has a function to 239217309Snwhitehorn display a file in a scrolling text field. This method allows any 240217309Snwhitehorn string to be displayed by first saving it in a temp file, and calling 241217309Snwhitehorn --textbox. 242217309Snwhitehorn """ 243217309Snwhitehorn fName = mktemp() 244217309Snwhitehorn f = open(fName, 'w') 245217309Snwhitehorn f.write(text) 246217309Snwhitehorn f.close() 247217309Snwhitehorn self.__perform(self.__handleTitle(title) +\ 248217309Snwhitehorn '--textbox "%s" %d %d' % (fName, height, width)) 249217309Snwhitehorn os.unlink(fName) 250217309Snwhitehorn 251217309Snwhitehorn 252217309Snwhitehorn def gauge_start(self, perc=0, text='', height=8, width=54, title=''): 253217309Snwhitehorn """ 254217309Snwhitehorn Display gauge output window. 255217309Snwhitehorn Gauge normal usage (assuming that there is an instace of 'Dialog' 256217309Snwhitehorn class named 'd'): 257217309Snwhitehorn d.gauge_start() 258217309Snwhitehorn # do something 259217309Snwhitehorn d.gauge_iterate(10) # passed throgh 10% 260217309Snwhitehorn # ... 261217309Snwhitehorn d.gauge_iterate(100, 'any text here') # work is done 262217309Snwhitehorn d.stop_gauge() # clean-up actions 263217309Snwhitehorn """ 264217309Snwhitehorn cmd = self.__handleTitle(title) +\ 265217309Snwhitehorn '--gauge "%s" %d %d %d' % (text, height, width, perc) 266217309Snwhitehorn cmd = '%s %s %s 2> /dev/null' % (DIALOG, self.__bgTitle, cmd) 267217309Snwhitehorn self.pipe = os.popen(cmd, 'w') 268217309Snwhitehorn #/gauge_start() 269217309Snwhitehorn 270217309Snwhitehorn 271217309Snwhitehorn def gauge_iterate(self, perc, text=''): 272217309Snwhitehorn """ 273217309Snwhitehorn Update percentage point value. 274217309Snwhitehorn 275217309Snwhitehorn See gauge_start() function above for the usage. 276217309Snwhitehorn """ 277217309Snwhitehorn if text: 278217309Snwhitehorn text = 'XXX\n%d\n%s\nXXX\n' % (perc, text) 279217309Snwhitehorn else: 280217309Snwhitehorn text = '%d\n' % perc 281217309Snwhitehorn self.pipe.write(text) 282217309Snwhitehorn self.pipe.flush() 283217309Snwhitehorn #/gauge_iterate() 284217309Snwhitehorn 285217309Snwhitehorn 286217309Snwhitehorn def gauge_stop(self): 287217309Snwhitehorn """ 288217309Snwhitehorn Finish previously started gauge. 289217309Snwhitehorn 290217309Snwhitehorn See gauge_start() function above for the usage. 291217309Snwhitehorn """ 292217309Snwhitehorn self.pipe.close() 293217309Snwhitehorn #/gauge_stop() 294217309Snwhitehorn 295217309Snwhitehorn 296217309Snwhitehorn 297217309Snwhitehorn# 298217309Snwhitehorn# DEMO APPLICATION 299217309Snwhitehorn# 300217309Snwhitehornif __name__ == '__main__': 301217309Snwhitehorn """ 302217309Snwhitehorn This demo tests all the features of the class. 303217309Snwhitehorn """ 304217309Snwhitehorn d = Dialog() 305217309Snwhitehorn d.setBackgroundTitle('dialog.py demo') 306217309Snwhitehorn 307217309Snwhitehorn d.infobox( 308217309Snwhitehorn "One moment... Just wasting some time here to test the infobox...") 309217309Snwhitehorn sleep(3) 310217309Snwhitehorn 311217309Snwhitehorn if d.yesno("Do you like this demo?"): 312217309Snwhitehorn d.msgbox("Excellent! Here's the source code:") 313217309Snwhitehorn else: 314217309Snwhitehorn d.msgbox("Send your complaints to /dev/null") 315217309Snwhitehorn 316217309Snwhitehorn d.textbox("dialog.py") 317217309Snwhitehorn 318217309Snwhitehorn name = d.inputbox("What's your name?", init="Snow White") 319217309Snwhitehorn fday = d.menu("What's your favorite day of the week?", 320217309Snwhitehorn list=["Monday", "Tuesday", "Wednesday", "Thursday", 321217309Snwhitehorn "Friday (The best day of all)", "Saturday", "Sunday"]) 322217309Snwhitehorn food = d.checklist("What sandwich toppings do you like?", 323217309Snwhitehorn list=["Catsup", "Mustard", "Pesto", "Mayonaise", "Horse radish", 324217309Snwhitehorn "Sun-dried tomatoes"], checked=[0,0,0,1,1,1]) 325217309Snwhitehorn sand = d.radiolist("What's your favorite kind of sandwich?", 326217309Snwhitehorn list=["Hamburger", "Hotdog", "Burrito", "Doener", "Falafel", 327217309Snwhitehorn "Bagel", "Big Mac", "Whopper", "Quarter Pounder", 328217309Snwhitehorn "Peanut Butter and Jelly", "Grilled cheese"], selected=4) 329217309Snwhitehorn 330217309Snwhitehorn # Prepare the message for the final window 331217309Snwhitehorn bigMessage = "Here are some vital statistics about you:\n\nName: " + name +\ 332217309Snwhitehorn "\nFavorite day of the week: " + fday +\ 333217309Snwhitehorn "\nFavorite sandwich toppings:\n" 334217309Snwhitehorn for topping in food: 335217309Snwhitehorn bigMessage = bigMessage + " " + topping + "\n" 336217309Snwhitehorn bigMessage = bigMessage + "Favorite sandwich: " + str(sand) 337217309Snwhitehorn 338217309Snwhitehorn d.scrollbox(bigMessage) 339217309Snwhitehorn 340217309Snwhitehorn #<># Gauge Demo 341217309Snwhitehorn d.gauge_start(0, 'percentage: 0', title='Gauge Demo') 342217309Snwhitehorn for i in range(1, 101): 343217309Snwhitehorn if i < 50: 344217309Snwhitehorn msg = 'percentage: %d' % i 345217309Snwhitehorn elif i == 50: 346217309Snwhitehorn msg = 'Over 50%' 347217309Snwhitehorn else: 348217309Snwhitehorn msg = '' 349217309Snwhitehorn d.gauge_iterate(i, msg) 350217309Snwhitehorn sleep(0.1) 351217309Snwhitehorn d.gauge_stop() 352217309Snwhitehorn #<># 353217309Snwhitehorn 354217309Snwhitehorn d.clear() 355