Re: Tk Entry widget with emacs/readline style key-bindings

Guido.van.Rossum@cwi.nl
Mon, 06 Jun 1994 16:05:12 +0200

Thanks -- I have copied your code to a similar module EmacsText.py.

I also include an augmented and improved Tkinter.py (e.g. added menu
and text methods and fixed a bug in Toplevel). This also incorporates
Bill Jansen's "packing: {...}" option to initial configuration, but
not his "endmainloop" method (which is just as easily done using the
standard "exit").

And while I'm at it, here's the "rmt" application from Ousterhout's
book translated to Python. Have fun!

--Guido van Rossum, CWI, Amsterdam <Guido.van.Rossum@cwi.nl>
URL: <http://www.cwi.nl/cwi/people/Guido.van.Rossum.html>

#! /bin/sh
# This is a shell archive. Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file". To overwrite existing
# files, type "sh file -c". You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g.. If this archive is complete, you
# will see the following message at the end:
# "End of archive 1 (of 1)."
# Contents: EmacsText.py Tkinter.py dialog.py rmt.py
# Wrapped by guido@voorn on Mon Jun 6 14:03:10 1994
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'EmacsText.py' -a "${1}" != "-c" ; then
echo shar: Will not clobber existing file \"'EmacsText.py'\"
else
echo shar: Extracting \"'EmacsText.py'\" \(1564 characters\)
sed "s/^X//" >'EmacsText.py' <<'END_OF_FILE'
Xfrom Tkinter import *
X
Xclass EmacsText(Text):
X
X def __init__(self, master=None, cmd={}):
X Text.__init__(self, master, cmd)
X self.killed = ''
X self.bind('<Control-a>', self.beginning_of_line)
X self.bind('<Control-e>', self.end_of_line)
X self.bind('<Control-d>', self.delete_char)
X self.bind('<Control-f>', self.forward_char)
X self.bind('<Right>', self.forward_char)
X self.bind('<Control-b>', self.backward_char)
X self.bind('<Left>', self.backward_char)
X self.bind('<Control-k>', self.kill_line)
X self.bind('<Control-y>', self.yank)
X
X def delete(self, index1, index2 = None):
X if index2 is None:
X self.cmd('delete', index1)
X else:
X self.cmd('delete', index1, index2)
X
X def get(self, index1, index2 = None):
X if index2 is None:
X return self.cmd('get', index1)
X else:
X return self.cmd('get', index1, index2)
X
X def insert(self, index, chars):
X self.cmd('insert', index, chars)
X
X def mark(self, option, *args):
X apply(self.cmd, ('mark', option) + args)
X
X def beginning_of_line(self):
X self.mark('set', 'insert', 'insert linestart')
X
X def end_of_line(self):
X self.mark('set', 'insert', 'insert lineend')
X
X def delete_char(self):
X self.delete('insert')
X
X def forward_char(self):
X self.mark('set', 'insert', 'insert + 1 char')
X
X def backward_char(self):
X self.mark('set', 'insert', 'insert - 1 char')
X
X def kill_line(self):
X self.killed = self.get('insert', 'insert lineend')
X self.delete('insert', 'insert lineend')
X
X def yank(self):
X self.insert('insert', self.killed)
X
Xif __name__ == '__main__':
X t = EmacsText()
X t.pack()
X t.mainloop()
END_OF_FILE
if test 1564 -ne `wc -c <'EmacsText.py'`; then
echo shar: \"'EmacsText.py'\" unpacked with wrong size!
fi
# end of 'EmacsText.py'
fi
if test -f 'Tkinter.py' -a "${1}" != "-c" ; then
echo shar: Will not clobber existing file \"'Tkinter.py'\"
else
echo shar: Extracting \"'Tkinter.py'\" \(8589 characters\)
sed "s/^X//" >'Tkinter.py' <<'END_OF_FILE'
X#$Id: Tkinter.py,v 3.6 1994/06/04 01:05:22 lumholt Exp $
Ximport tkinter
X
Xclass Wm:
X # XXX This doesn't work for wm commands receiving more than one
X # XXX argument or none at all (e.g. aspect, [de]iconify)
X def __getitem__(self, key):
X return self.tk.call('wm', key, self.pathName)
X def __setitem__(self, key, value):
X self.tk.call('wm', key, self.pathName, value)
X def config(self, cnf={}):
X for k in cnf.keys():
X Wm.__setitem__(self, k, cnf[k])
X wm = config
X
Xclass Tk(Wm):
X pathName = '.'
X def __init__(self, screenName=None, baseName=None,
X className='Tk'):
X if baseName is None:
X import sys, os
X baseName = os.path.basename(sys.argv[0])
X if baseName[-3:] == '.py': baseName = baseName[:-3]
X self.tk = tkinter.create(screenName, baseName, className)
X def __del__(self):
X self.tk.call('destroy', '.')
X def __str__(self):
X return '.'
X def mainloop(self):
X self.tk.mainloop()
X def after(self, ms, func=None, *args):
X if not func:
X self.tk.call('after', ms)
X else:
X name = `id(func)`
X self.tk.createcommand(name, func)
X apply(self.tk.call, ('after', ms, name) + args)
X def focus(self, *args):
X return apply(self.tk.call, ('focus',) + args)
X def grab(self, *args): # XXX '-global' option!
X return self.tk.split(apply(self.tk.call, ('grab',) + args))
X def lower(self, *args):
X return apply(self.tk.call, ('lower',) + args)
X def selection(self, *args):
X return apply(self.tk.call, ('selection',) + args)
X def send(self, *args):
X return apply(self.tk.call, ('send',) + args)
X def colormodel(self, *value):
X return apply(self.tk.call,
X ('tk', 'colormodel', self.pathName) + value)
X def winfo(self, *args):
X return apply(self.tk.call, ('winfo',) + args)
X def update(self, *args):
X return apply(self.tk.call, ('update',) + args)
X def bind(self, sequence=None, func=None, substitution=()):
X if sequence == None:
X return self.tk.splitlist(
X self.tk.call('bind', self.pathName))
X self.tk.createcommand(`id(func)`, func)
X self.tk.call('bind', self.pathName, sequence,
X (`id(func)`,) + substitution)
X def cmd(self, *args):
X return apply(self.tk.call, args)
X
Xclass Pack:
X def __setitem__(self, key, value):
X self.tk.call('pack', 'configure',
X self.pathName, '-' + key, value)
X def config(self, cnf={}):
X if cnf == {}:
X self.tk.call('pack', 'configure', self.pathName)
X else:
X for k in cnf.keys():
X Pack.__setitem__(self, k, cnf[k])
X pack = config
X def cmd(self, option, *args):
X return apply(self.tk.call,
X ('pack', option, self.pathName) + args)
X
Xclass Place:
X def __setitem__(self, key, value):
X self.tk.call('place', 'configure',
X self.pathName, '-' + key, value)
X def config(self, cnf={}):
X if cnf == {}:
X self.tk.call('place', 'configure', self.pathName)
X else:
X for k in cnf.keys():
X Pack.__setitem__(self, k, cnf[k])
X place = config
X def cmd(self, option, *args):
X return apply(self.tk.call,
X ('pack', option, self.pathName) + args)
X
Xdef func():
X pass
X
Xclass Widget(Pack, Place, Tk):
X def __init__(self, master, widgetName, cnf={}, extra=()):
X if not master:
X master = self.master = Tk()
X self.tk = master.tk
X if master.pathName=='.':
X self.pathName = '.' + `id(self)`
X else:
X self.pathName = master.pathName + '.' + `id(self)`
X self.widgetName = widgetName
X apply(self.tk.call, (widgetName, self.pathName) + extra)
X if (cnf.has_key('packing')):
X packing = cnf['packing']
X del cnf['packing']
X else:
X packing = None
X for k in cnf.keys():
X Widget.__setitem__(self, k, cnf[k])
X if packing:
X self.pack(packing)
X def __getitem__(self, key):
X v = self.tk.split(self.tk.call(self.pathName,
X 'configure',
X '-' + key))
X return v[3]
X def __setitem__(self, key, value):
X if type(value) in (type(Widget.__init__), type(func)):
X self.tk.createcommand(`id(value)`, value)
X self.tk.call(self.pathName, 'configure',
X '-' + key, id(value))
X else:
X self.tk.call(self.pathName, 'configure',
X '-' + key, value)
X def config(self, cnf={}):
X for k in cnf.keys():
X Widget.__setitem__(self, k, cnf[k])
X def cmd(self, option, *args):
X return apply(self.tk.call, (self.pathName, option) + args)
X def __str__(self):
X return self.pathName
X def __del__(self):
X self.tk.call('destroy', self.pathName)
X destroy = __del__
X
Xclass Toplevel(Widget):
X def __init__(self, master=None, cnf={}):
X extra = ()
X if cnf.has_key('screen'):
X extra = ('-screen', cnf['screen'])
X del cnf['screen']
X if cnf.has_key('class'):
X extra = extra + ('-class', cnf['class'])
X del cnf['class']
X Widget.__init__(self, master, 'toplevel', cnf, extra)
X self.wm({'iconname': self.tk.call('wm', 'title', '.')})
X self.wm({'title': self.tk.call('wm', 'title', '.')})
X for k in cnf.keys():
X Toplevel.__setitem__(k, cnf[k])
X
Xclass Button(Widget):
X def __init__(self, master=None, cnf={}):
X Widget.__init__(self, master, 'button', cnf)
X
Xclass Canvas(Widget):
X def __init__(self, master=None, cnf={}):
X Widget.__init__(self, master, 'canvas', cnf)
X
Xclass Checkbutton(Widget):
X def __init__(self, master=None, cnf={}):
X Widget.__init__(self, master, 'checkbutton', cnf)
X
Xclass Entry(Widget):
X def __init__(self, master=None, cnf={}):
X Widget.__init__(self, master, 'entry', cnf)
X
Xclass Frame(Widget):
X def __init__(self, master=None, cnf={}):
X Widget.__init__(self, master, 'frame', cnf)
X def tk_menuBar(self, *args):
X apply(self.tk.call, ('tk_menuBar', self.pathName) + args)
X
Xclass Label(Widget):
X def __init__(self, master=None, cnf={}):
X Widget.__init__(self, master, 'label', cnf)
X
Xclass Listbox(Widget):
X def __init__(self, master=None, cnf={}):
X Widget.__init__(self, master, 'listbox', cnf)
X
Xclass Menu(Widget):
X def __init__(self, master=None, cnf={}):
X Widget.__init__(self, master, 'menu', cnf)
X def activate(self, index):
X self.cmd('activate', index)
X def add(self, itemtype, cnf = {}):
X args = ()
X for k, v in cnf.items():
X if type(v) in (type(func), type(Menu.__init__)):
X self.tk.createcommand(`id(v)`, v)
X v = `id(v)`
X args = args + ('-'+k, v)
X return apply(self.cmd, ('add', itemtype) + args)
X def delete(self, index1, index2 = None):
X if index2 is None: self.cmd('delete', index1)
X else: self.cmd('delete', index1, index2)
X def disable(self, index): # XXX obsolete
X self.cmd('disable', index)
X def enable(self, index): # XXX obsolete
X self.cmd('enable', index)
X def entryconfigure(self, index, cnf = {}):
X args = ()
X for k, v in cnf.items():
X args = args + ('-'+k, v)
X return apply(self.cmd, ('entryconfigure', type) + args)
X def index(self, index):
X return self.cmd('index', index)
X def invoke(self, index):
X return self.cmd('invoke', index)
X def post(self, x, y):
X return self.cmd('post', x, y)
X def unpost(self):
X self.cmd('unpost')
X def yposition(self, index):
X return self.cmd('yposition', index)
X
Xclass Menubutton(Widget):
X def __init__(self, master=None, cnf={}):
X Widget.__init__(self, master, 'menubutton', cnf)
X
Xclass Message(Widget):
X def __init__(self, master=None, cnf={}):
X Widget.__init__(self, master, 'message', cnf)
X
Xclass Radiobutton(Widget):
X def __init__(self, master=None, cnf={}):
X Widget.__init__(self, master, 'radiobutton', cnf)
X
Xclass Scale(Widget):
X def __init__(self, master=None, cnf={}):
X Widget.__init__(self, master, 'scale', cnf)
X
Xclass Scrollbar(Widget):
X def __init__(self, master=None, cnf={}):
X Widget.__init__(self, master, 'scrollbar', cnf)
X
Xclass Text(Widget):
X def __init__(self, master=None, cnf={}):
X Widget.__init__(self, master, 'text', cnf)
X def compare(self, index1, op, index2):
X return self.cmd('compare', index1, op, index2)
X def debug(self, flag = None):
X if flag is None: return self.cmd('debug')
X else: self.cmd('debug', not not flag)
X def delete(self, index1, index2 = None):
X if index2 is None: self.cmd('delete', index1)
X else: self.cmd('delete', index1, index2)
X def get(self, index1, index2 = None):
X if index2 is None: return self.cmd('get', index1)
X else: return self.cmd('get', index1, index2)
X def index(self, index):
X return self.cmd('index', index)
X def insert(self, index, chars):
X self.cmd('insert', index, chars)
X def mark(self, option, *args):
X apply(self.cmd, ('mark', option) + args)
X def scan(self, option, *args):
X apply(self.cmd, ('scan', option) + args)
X def tag(self, option, *args):
X apply(self.cmd, ('tag', option) + args)
X def tag_configure(self, option, cnf = {}):
X args = ()
X for k, v in cnf.items():
X args = args + ('-'+k, v)
X apply(self.cmd, ('tag', 'configure', option) + args)
X def yview(self, *args):
X # Should check for yview ?-pickplace? what
X apply(self.cmd, ('yview',) + args)
END_OF_FILE
if test 8589 -ne `wc -c <'Tkinter.py'`; then
echo shar: \"'Tkinter.py'\" unpacked with wrong size!
fi
# end of 'Tkinter.py'
fi
if test -f 'dialog.py' -a "${1}" != "-c" ; then
echo shar: Will not clobber existing file \"'dialog.py'\"
else
echo shar: Extracting \"'dialog.py'\" \(3408 characters\)
sed "s/^X//" >'dialog.py' <<'END_OF_FILE'
X# A Python function that generates dialog boxes with a text message,
X# optional bitmap, and any number of buttons.
X# Cf. Ousterhout, Tcl and the Tk Toolkit, Figs. 27.2-3, pp. 269-270.
X
Xfrom Tkinter import *
X
X# (This is missing in the distributed Tkinter)
Xclass Message(Widget):
X def __init__(self, master=None, cnf={}):
X Widget.__init__(self, master, 'message', cnf)
X
Xdef dialog(master, title, text, bitmap, default, *args):
X
X # 1. Create the top-level window and divide it into top
X # and bottom parts.
X
X w = Toplevel(master, {'class': 'Dialog'})
X w.tk.call('global', 'button')
X
X top = Frame(w, {'relief': 'raised', 'bd': 1,
X 'packing': {'side': 'top', 'fill': 'both'}})
X bot = Frame(w, {'relief': 'raised', 'bd': 1,
X 'packing': {'side': 'bottom', 'fill': 'both'}})
X
X # 2. Fill the top part with the bitmap and message.
X
X msg = Message(top,
X {'width': '3i',
X 'text': text,
X 'font': '-Adobe-Times-Medium-R-Normal-*-180-*',
X 'packing': { 'side': 'right', 'expand': 1,
X 'fill': 'both',
X 'padx': '3m', 'pady': '3m'}})
X if bitmap:
X bm = Label(top, {'bitmap': bitmap,
X 'packing': { 'side': 'left',
X 'padx': '3m', 'pady': '3m'}})
X
X # 3. Create a row of buttons at the bottom of the dialog.
X
X buttons = []
X i = 0
X for but in args:
X b = Button(bot, {'text': but,
X 'command': ('set', 'button', i)})
X buttons.append(b)
X if i == default:
X bd = Frame(bot, {'relief': 'sunken', 'bd': 1,
X 'packing': { 'side': 'left', 'expand': 1,
X 'padx': '3m', 'pady': '2m'}})
X w.tk.call('raise', b)
X b.pack ({'in': bd, 'side': 'left',
X 'padx': '2m', 'pady': '2m',
X 'ipadx': '2m', 'ipady': '1m'})
X else:
X b.pack ({'side': 'left', 'expand': 1,
X 'padx': '3m', 'pady': '3m',
X 'ipady': '2m', 'ipady': '1m'})
X i = i+1
X
X # 4. Set up a binding for <Return>, if there's a default,
X # set a grab, and claim the focus too.
X
X if default >= 0:
X w.bind('<Return>',
X lambda b=buttons[default], i=default:
X (b.cmd('flash'),
X b.tk.call('set', 'button', i)))
X
X oldFocus = w.tk.call('focus')
X w.tk.call('grab', 'set', w)
X w.tk.call('focus', w)
X
X # 5. Wait for the user to respond, then restore the focus
X # and return the index of the selected button.
X
X w.tk.call('tkwait', 'variable', 'button')
X w.tk.call('destroy', w)
X w.tk.call('focus', oldFocus)
X return w.tk.call('set', 'button')
X
X# The rest is the test program.
X
Xdef go():
X i = dialog(mainWidget,
X 'Not Responding',
X "The file server isn't responding right now; "
X "I'll keep trying.",
X '',
X -1,
X 'OK')
X print 'pressed button', i
X i = dialog(mainWidget,
X 'File Modified',
X 'File "tcl.h" has been modified since '
X 'the last time it was saved. '
X 'Do you want to save it before exiting the application?',
X 'warning',
X 0,
X 'Save File',
X 'Discard Changes',
X 'Return To Editor')
X print 'pressed button', i
X
Xdef test():
X import sys
X global mainWidget
X mainWidget = Frame()
X Pack.config(mainWidget)
X start = Button(mainWidget,
X {'text': 'Press Here To Start', 'command': go})
X start.pack()
X endit = Button(mainWidget,
X {'text': 'Exit',
X 'command': 'exit',
X 'packing': {'fill' : 'both'}})
X mainWidget.tk.mainloop()
X
Xif __name__ == '__main__':
X test()
END_OF_FILE
if test 3408 -ne `wc -c <'dialog.py'`; then
echo shar: \"'dialog.py'\" unpacked with wrong size!
fi
# end of 'dialog.py'
fi
if test -f 'rmt.py' -a "${1}" != "-c" ; then
echo shar: Will not clobber existing file \"'rmt.py'\"
else
echo shar: Extracting \"'rmt.py'\" \(4494 characters\)
sed "s/^X//" >'rmt.py' <<'END_OF_FILE'
X#! /ufs/guido/bin/sgi/tkpython
X
X# A Python program implementing rmt, an application for remotely
X# controlling other Tk applications.
X# Cf. Ousterhout, Tcl and the Tk Toolkit, Figs. 27.5-8, pp. 273-276.
X
X# Note that because of forward references in the original, we
X# sometimes delay bindings until after the corresponding procedure is
X# defined. We also introduce names for some unnamed code blocks in
X# the original because of restrictions on lambda forms in Python.
X
Xfrom Tkinter import *
X
X# 1. Create basic application structure: menu bar on top of
X# text widget, scrollbar on right.
X
Xroot = Tk()
Xtk = root.tk
XmBar = Frame(root, {'relief': 'raised', 'bd': 2,
X 'packing': {'side': 'top', 'fill': 'x'}})
Xs = Scrollbar(root, {'relief': 'flat',
X 'packing': {'side': 'right', 'fill': 'y'}})
Xt = Text(root, {'relief': 'raised', 'bd': 2, 'yscrollcommand': (s, 'set'),
X 'setgrid': 1,
X 'packing': {'side': 'left', 'fill': 'both', 'expand': 1}})
Xt.tag_configure('bold', {'font': '-Adobe-Courier-Bold-R-Normal-*-120-*'})
Xs['command'] = (t, 'yview')
Xroot.wm({'title': 'Tk Remote Controller', 'iconname': 'Tk Remote'})
X
X# 2. Create menu button and menus.
X
Xfile = Menubutton(mBar, {'text': 'File', 'underline': 0,
X 'packing': {'side': 'left'}})
Xfile_m = Menu(file)
Xfile['menu'] = file_m
Xfile_m_apps = Menu(file_m)
Xfile_m.add('cascade', {'label': 'Select Application', 'underline': 0,
X 'menu': file_m_apps})
Xfile_m.add('command', {'label': 'Quit', 'underline': 0, 'command': 'exit'})
X
X# 3. Create bindings for text widget to allow commands to be
X# entered and information to be selected. New characters
X# can only be added at the end of the text (can't ever move
X# insertion point).
X
Xdef single1(xs, ys, W):
X x = tk.getint(xs)
X y = tk.getint(ys)
X tk.setvar('tk_priv(selectMode)', 'char')
X t.mark('set', 'anchor', '@%d,%d' % (x, y))
X # Should focus W
Xt.bind('<1>', single1, ('%x', '%y', '%W'))
X
Xdef double1(xs, ys):
X x = tk.getint(xs)
X y = tk.getint(ys)
X tk.setvar('tk_priv(selectMode)', 'word')
X tk.call('tk_textSelectTo', t, '@%d,%d' % (x, y))
Xt.bind('<Double-1>', double1, ('%x', '%y'))
X
Xdef triple1(xs, ys):
X x = tk.getint(xs)
X y = tk.getint(ys)
X tk.setvar('tk_priv(selectMode)', 'line')
X tk.call('tk_textSelectTo', t, '@%d,%d' % (x, y))
Xt.bind('<Triple-1>', triple1, ('%x', '%y'))
X
Xdef returnkey():
X t.insert('insert', '\n')
X invoke()
Xt.bind('<Return>', returnkey)
X
Xdef controlv():
X t.insert('insert', tk.call('selection', 'get'))
X t.yview('-pickplace', 'insert')
X if t.index('insert')[-2:] == '.0':
X invoke()
Xt.bind('<Control-v>', controlv)
X
X# 4. Procedure to backspace over one character, as long as
X# the character isn't part of the prompt.
X
Xdef backspace():
X if t.index('promptEnd') != t.index('insert - 1 char'):
X t.delete('insert - 1 char', 'insert')
X t.yview('-pickplace', 'insert')
Xt.bind('<BackSpace>', backspace)
Xt.bind('<Control-h>', backspace)
Xt.bind('<Delete>', backspace)
X
X
X# 5. Procedure that's invoked when return is typed: if
X# there's not yet a complete command (e.g. braces are open)
X# then do nothing. Otherwise, execute command (locally or
X# remotely), output the result or error message, and issue
X# a new prompt.
X
Xdef invoke():
X cmd = t.get('promptEnd + 1 char', 'insert')
X if tk.getboolean(tk.call('info', 'complete', cmd)):
X if app == tk.call('winfo', 'name', '.'):
X msg = tk.call('eval', cmd)
X else:
X msg = tk.call('send', app, cmd)
X if msg:
X t.insert('insert', msg + '\n')
X prompt()
X t.yview('-pickplace', 'insert')
X
Xdef prompt():
X t.insert('insert', app + ': ')
X t.mark('set', 'promptEnd', 'insert - 1 char')
X t.tag('add', 'bold', 'insert linestart', 'promptEnd')
X
X# 6. Procedure to select a new application. Also changes
X# the prompt on the current command line to reflect the new
X# name.
X
Xdef newApp(appName):
X global app
X app = appName
X t.delete('promptEnd linestart', 'promptEnd')
X t.insert('promptEnd', appName + ':')
X t.tag('add', 'bold', 'promptEnd linestart', 'promptEnd')
X
XnewApp_tcl = `id(newApp)`
Xtk.createcommand(newApp_tcl, newApp)
X
Xdef fillAppsMenu():
X file_m_apps.add('command')
X file_m_apps.delete(0, 'last')
X names = tk.splitlist(tk.call('winfo', 'interps'))
X names = map(None, names) # convert tuple to list
X names.sort()
X for name in names:
X file_m_apps.add('command', {'label': name,
X 'command': (newApp_tcl, name)})
X
Xfile_m_apps['postcommand'] = fillAppsMenu
XmBar.tk_menuBar(file)
X
X# 7. Miscellaneous initialization.
X
Xapp = tk.call('winfo', 'name', '.')
Xprompt()
Xtk.call('focus', t)
X
Xroot.mainloop()
END_OF_FILE
if test 4494 -ne `wc -c <'rmt.py'`; then
echo shar: \"'rmt.py'\" unpacked with wrong size!
fi
chmod +x 'rmt.py'
# end of 'rmt.py'
fi
echo shar: End of archive 1 \(of 1\).
cp /dev/null ark1isdone
MISSING=""
for I in 1 ; do
if test ! -f ark${I}isdone ; then
MISSING="${MISSING} ${I}"
fi
done
if test "${MISSING}" = "" ; then
echo You have the archive.
rm -f ark[1-9]isdone
else
echo You still need to unpack the following archives:
echo " " ${MISSING}
fi
## End of shell archive.
exit 0