Commits

Rob Lanphier committed ec38990

Pretty much have things working now

  • Participants
  • Parent commits aa962c3

Comments (0)

Files changed (3)

 urwid6
 urwid7
 urwid8
+urwid9
+urwid10
+# HG changeset patch
+# Parent ad067ddd91065411e9f91e9da2a6ea0c175b7c76
+Created a ui subdirectory, and broke urwid stuff into separate file from the basic ui
+
+diff --git a/paste/script/command.py b/paste/script/command.py
+--- a/paste/script/command.py
++++ b/paste/script/command.py
+@@ -16,7 +16,7 @@
+     from paste.script.util import subprocess24 as subprocess
+ 
+ import pastevars
+-import userinterface
++import ui
+ 
+ NoDefault = pastevars.NoDefault
+     
+@@ -150,7 +150,7 @@
+ 
+     def __init__(self, name):
+         self.command_name = name
+-        self.ui = userinterface.get_user_interface()
++        self.ui = ui.get_user_interface()
+ 
+     max_args = None
+     max_args_error = 'You must provide no more than %(max_args)s arguments'
+diff --git a/paste/script/userinterface.py b/paste/script/ui/__init__.py
+rename from paste/script/userinterface.py
+rename to paste/script/ui/__init__.py
+--- a/paste/script/userinterface.py
++++ b/paste/script/ui/__init__.py
+@@ -1,7 +1,7 @@
+ # (c) 2005-2010 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
+ # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
+ 
+-from pastevars import NoDefault, var
++from paste.script.pastevars import NoDefault, var
+ 
+ try:
+     import urwid
+@@ -10,6 +10,9 @@
+     pass
+ 
+ 
++from paste.script.ui.pasteurwid import UrwidUI
++
++
+ def get_user_interface():
+     """
+     Class factory for UserInterface and friends
+@@ -77,323 +80,4 @@
+                 name, ' '*(max_var-len(name)), value)
+ 
+ 
+-class UrwidUI(UserInterface):    
+-    def challenge(self, prompt, default=NoDefault, should_echo=True):
+-        """
+-        Prompt the user for a variable.
+-        """
+-        
+-        while 1:
+-            chals = [var('variable', prompt, default=default, should_echo=should_echo)]
+-            response = self.challenge_batch(chals, use_names=False)['variable']
+-            if not response:
+-                if default is not NoDefault:
+-                    return default
+-                else:
+-                    continue
+-            else:
+-                return response
+ 
+-    def challenge_batch(self, challenges, 
+-                        title="'paster create' Configuration",
+-                        use_names=True):
+-        """
+-        Return a series of responses
+-        """
+-        fieldset = UrwidFieldSet(challenges, use_names=use_names)
+-        fieldset.header_text = title
+-        fieldset.run()
+-        return fieldset.get_value_dict()
+-
+-    def display_vars(self, vars, title="Variables:"):
+-        vars = vars.items()
+-        vars.sort()
+-        display = UrwidDictDisplay(vars)
+-        display.header_text = title
+-        display.run()
+-
+-
+-class UrwidDialogException(Exception):
+-    pass
+-
+-
+-class ExitUrwidUI(Exception):
+-    def __init__(self, exit_token=None):
+-        self.exit_token = exit_token
+-
+-class UrwidDictDisplay(object):
+-    def __init__(self, showdict):
+-        self.showdict = showdict
+-        self.header_text = 'Variables:'
+-
+-    def _get_field(self, name, value):
+-        """ Display one name/value pair from the dict """
+-        # we don't have hanging indent, but we can stick a bullet out into the 
+-        # left column.
+-        asterisk = urwid.Text(('label', '* '))
+-        label = urwid.Text(('label', name))
+-        colon = urwid.Text(('label', ': '))
+-        field = urwid.Text(('field', value))
+-
+-        # put everything together.  Each column is either 'fixed' for a fixed width,
+-        # or given a 'weight' to help determine the relative width of the column
+-        # such that it can fill the row.
+-        row = urwid.Columns([('fixed', 2, asterisk),
+-                             ('weight', 3, label),
+-                             ('fixed', 2, colon),
+-                             ('weight', 4, field)])
+-
+-        return urwid.Padding(row, ('fixed left', 3), ('fixed right', 3))
+-
+-    def get_widgets(self):
+-        widgets = []
+-        for name, value in self.showdict:
+-            widgets.append(self._get_field(name, value))
+-        return widgets
+-
+-    def get_button_defs(self):
+-        return [('ok', {'label':'  OK'}),
+-                ('cancel', {'label':'Cancel'})]
+-
+-    def run(self):
+-        dialog = UrwidDialog(self)
+-        dialog.header_text = self.header_text
+-        UrwidCancelWrapper(dialog).run()
+-
+-
+-class UrwidYesNo(object):
+-    def __init__(self, question):
+-        self.question = question
+-        self.header_text = 'Question:'
+-
+-    def get_widgets(self):
+-        row = urwid.Text(('field', self.question))
+-        widget = urwid.Padding(row, ('fixed left', 3), ('fixed right', 3))
+-        return [widget]
+-
+-    def get_button_defs(self):
+-        return [('yes', {'label':'Yes'}),
+-                ('no', {'label':'No'})]
+-
+-    def run(self):
+-        dialog = UrwidDialog(self)
+-        dialog.header_text = self.header_text
+-        dialog.run()
+-        self.exit_token = dialog.exit_token
+-
+-
+-class UrwidFieldSet(object):
+-    """
+-    Takes a set of fields, displays a dialog corresponding to those fields, and
+-    returns the values corresponding to those fields.
+-    """
+-    def __init__(self, challenges, use_names=True):
+-        self.getters = {}
+-        self.challenges = challenges
+-        self.use_names = use_names
+-
+-    def get_value_dict(self):
+-        """
+-        Dump everything we've got.
+-        """
+-        retval = {}
+-        for key in self.getters:
+-            retval[key] = self.getters[key]()
+-        return retval
+-
+-    def _set_getter(self, name, function):
+-        """ 
+-        This is where we collect all of the field getter functions.
+-        """
+-        self.getters[name] = function
+-        
+-    def _get_field(self, challenge):
+-        """ Build a field in our form.  Called from _get_dialog_body()"""
+-        # we don't have hanging indent, but we can stick a bullet out into the 
+-        # left column.
+-        asterisk = urwid.Text(('label', '* '))
+-        if self.use_names:
+-            label_text = challenge.full_description()
+-        else:
+-            label_text = challenge.description
+-        label = urwid.Text(('label', label_text))
+-        colon = urwid.Text(('label', ': '))
+-
+-        if issubclass(challenge.type, str):
+-            if challenge.default is NoDefault:
+-                field = urwid.Edit('', '')
+-            else:
+-                field = urwid.Edit('', challenge.default)
+-            def getter():
+-                """ 
+-                Closure around urwid.Edit.get_edit_text(), which we'll
+-                use to scrape the value out when we're all done.
+-                """
+-                return field.get_edit_text()
+-            self._set_getter(challenge.name, getter)
+-        elif issubclass(challenge.type, bool):
+-            field = urwid.CheckBox('')
+-            def getter():
+-                """ 
+-                Closure around urwid.CheckBox.get_state(), which we'll
+-                use to scrape the value out when we're all done. 
+-                """
+-                return field.get_state()
+-            self._set_getter(challenge.name, getter)
+-        else:
+-            raise UrwidDialogException(str(challenge.type))
+-
+-
+-        field = urwid.AttrWrap(field, 'field', 'fieldfocus')
+-
+-        # put everything together.  Each column is either 'fixed' for a fixed width,
+-        # or given a 'weight' to help determine the relative width of the column
+-        # such that it can fill the row.
+-        editwidget = urwid.Columns([('fixed', 2, asterisk),
+-                                    ('weight', 3, label),
+-                                    ('fixed', 2, colon),
+-                                    ('weight', 4, field)])
+-
+-        wrapper = urwid.AttrWrap(editwidget, None, {'label':'labelfocus'})
+-        return urwid.Padding(wrapper, ('fixed left', 3), ('fixed right', 3))
+-
+-    def get_widgets(self):
+-        fieldwidgets = []
+-        for chal in self.challenges:
+-            fieldwidgets.append(self._get_field(chal))
+-        return fieldwidgets
+-
+-    def get_button_defs(self):
+-        return [('ok', {'label':'  OK'}),
+-                ('cancel', {'label':'Cancel'})]
+-
+-    def run(self):
+-        dialog = UrwidDialog(self)
+-        dialog.header_text = self.header_text
+-        UrwidCancelWrapper(dialog).run()
+-
+-
+-class UrwidCancelWrapper(object):
+-    def __init__(self, dialog):
+-        self.dialog = dialog
+-        self.dialog.header_text += " (select 'OK' to continue, 'Cancel' to abort)"
+-        
+-    def run(self):
+-        keepgoing = True
+-        while keepgoing:
+-            self.dialog.run()
+-            keepgoing = self._check_cancel()
+-
+-    def _check_cancel(self):
+-        if self.dialog.exit_token == 'ok':
+-            keepgoing = False
+-        elif self.dialog.exit_token == 'cancel':
+-            yesno = UrwidYesNo('Do you really wish to cancel?')
+-            yesno.header_text = 'Exiting...'
+-            yesno.run()
+-            if yesno.exit_token == 'yes':
+-                print 'Cancelled'
+-                raise SystemExit
+-            elif yesno.exit_token == 'no':
+-                keepgoing = True
+-            else:
+-                raise UrwidDialogException
+-        return keepgoing
+-
+-
+-class UrwidDialog(object):
+-    """
+-    Takes a set of fields, displays a dialog corresponding to those fields, and
+-    returns the values corresponding to those fields.
+-    """
+-    def __init__(self, bodyobj):
+-        self.bodyobj = bodyobj
+-        self.header_text = "'paster create' Configuration"
+-        self.is_constructed = False
+-
+-    def construct(self):
+-        #  Our main loop is going to need four things: 
+-        #  1. frame - the UI with all of its widgets
+-        #  2. palette - style information for the UI
+-        #  3. screen - the engine used to render everything
+-        
+-        #  1. frame - the UI with all of its widgets
+-        header = self._get_dialog_header()
+-        body = self._get_dialog_body()
+-        self.frame = urwid.Frame(body, header=header)
+-
+-        #  2. palette - style information for the UI
+-        self.palette = [
+-            ('body','black','white', 'standout'),
+-            ('header','black','light gray', 'bold'),
+-            ('labelfocus','black', 'white', 'bold, underline'),
+-            ('label','dark blue', 'white'),
+-            ('fieldfocus','black,underline', 'white', 'bold, underline'),
+-            ('field','black', 'white'),
+-            ('button','black','white'),
+-            ('buttonfocus','black','light gray','bold'),
+-            ]
+-
+-        #  3. screen - the engine used to render everything
+-        self.screen = urwid.raw_display.Screen()
+-        self.is_constructed = True
+-
+-    def run(self):
+-        """
+-        Invoke Urwid MainLoop to display the dialog, populating self.getters
+-        """
+-        if not self.is_constructed:
+-            self.construct()
+-
+-        # Putting it all together and running it
+-        try:
+-            urwid.MainLoop(self.frame, self.palette, self.screen).run()
+-        except ExitUrwidUI as inst:
+-            self.exit_token = inst.exit_token
+-
+-    def _get_button_callback(self, name):
+-        def button_callback(button):
+-            raise ExitUrwidUI(exit_token=name)
+-        return button_callback
+-
+-    def _get_buttons(self):
+-        """ renders the ok and cancel buttons.  Called from get_body() """
+-
+-        buttonlist = []
+-        for name, props in self.bodyobj.get_button_defs():
+-            # this is going to be what we actually do when someone clicks the button
+-            b = urwid.Button(props['label'], 
+-                             on_press=self._get_button_callback(name))
+-            buttonlist.append(urwid.AttrWrap(b, 'button', 'buttonfocus'))
+-
+-        return urwid.GridFlow(buttonlist, 10, 7, 1, 'center')
+-
+-    def _get_dialog_header(self):
+-        """ the header of our form, called from run() """
+-        header_text = self.header_text
+-        header = urwid.Text(header_text)
+-        return urwid.AttrWrap(header, 'header')
+-
+-    def _get_dialog_body(self):
+-        """ the body of our form, called from run() """
+-
+-        # build the list of field widgets
+-        bodywidgets = [urwid.Divider(bottom=2)]
+-
+-        bodywidgets.extend(self.bodyobj.get_widgets())
+-        
+-        bodywidgets.append(urwid.Divider(bottom=1)) 
+-
+-        bodywidgets.append(self._get_buttons())
+-
+-        # SimpleListWalker provides simple linear navigation between the widgets
+-        listwalker = urwid.SimpleListWalker(bodywidgets)
+-        
+-        # ListBox is a scrollable frame around a list of elements
+-        listbox = urwid.ListBox(listwalker)
+-        return urwid.AttrWrap(listbox, 'body')
+-
+-
+-if '__main__'==__name__:
+-    main()
+-
+diff --git a/paste/script/ui/pasteurwid.py b/paste/script/ui/pasteurwid.py
+new file mode 100644
+--- /dev/null
++++ b/paste/script/ui/pasteurwid.py
+@@ -0,0 +1,328 @@
++# (c) 2010 Rob Lanphier and contributors; written for Paste (http://pythonpaste.org)
++# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
++
++from paste.script.pastevars import NoDefault, var
++
++try:
++    import urwid
++    import urwid.raw_display
++except ImportError:
++    pass
++
++
++class UrwidUI(object):    
++    def challenge(self, prompt, default=NoDefault, should_echo=True):
++        """
++        Prompt the user for a variable.
++        """
++        
++        while 1:
++            chals = [var('variable', prompt, default=default, should_echo=should_echo)]
++            response = self.challenge_batch(chals, use_names=False)['variable']
++            if not response:
++                if default is not NoDefault:
++                    return default
++                else:
++                    continue
++            else:
++                return response
++
++    def challenge_batch(self, challenges, 
++                        title="'paster create' Configuration",
++                        use_names=True):
++        """
++        Return a series of responses
++        """
++        fieldset = UrwidFieldSet(challenges, use_names=use_names)
++        fieldset.header_text = title
++        fieldset.run()
++        return fieldset.get_value_dict()
++
++    def display_vars(self, vars, title="Variables:"):
++        vars = vars.items()
++        vars.sort()
++        display = UrwidDictDisplay(vars)
++        display.header_text = title
++        display.run()
++
++
++class UrwidDialogException(Exception):
++    pass
++
++
++class ExitUrwidUI(Exception):
++    def __init__(self, exit_token=None):
++        self.exit_token = exit_token
++
++class UrwidDictDisplay(object):
++    def __init__(self, showdict):
++        self.showdict = showdict
++        self.header_text = 'Variables:'
++
++    def _get_field(self, name, value):
++        """ Display one name/value pair from the dict """
++        # we don't have hanging indent, but we can stick a bullet out into the 
++        # left column.
++        asterisk = urwid.Text(('label', '* '))
++        label = urwid.Text(('label', name))
++        colon = urwid.Text(('label', ': '))
++        field = urwid.Text(('field', value))
++
++        # put everything together.  Each column is either 'fixed' for a fixed width,
++        # or given a 'weight' to help determine the relative width of the column
++        # such that it can fill the row.
++        row = urwid.Columns([('fixed', 2, asterisk),
++                             ('weight', 3, label),
++                             ('fixed', 2, colon),
++                             ('weight', 4, field)])
++
++        return urwid.Padding(row, ('fixed left', 3), ('fixed right', 3))
++
++    def get_widgets(self):
++        widgets = []
++        for name, value in self.showdict:
++            widgets.append(self._get_field(name, value))
++        return widgets
++
++    def get_button_defs(self):
++        return [('ok', {'label':'  OK'}),
++                ('cancel', {'label':'Cancel'})]
++
++    def run(self):
++        dialog = UrwidDialog(self)
++        dialog.header_text = self.header_text
++        UrwidCancelWrapper(dialog).run()
++
++
++class UrwidYesNo(object):
++    def __init__(self, question):
++        self.question = question
++        self.header_text = 'Question:'
++
++    def get_widgets(self):
++        row = urwid.Text(('field', self.question))
++        widget = urwid.Padding(row, ('fixed left', 3), ('fixed right', 3))
++        return [widget]
++
++    def get_button_defs(self):
++        return [('yes', {'label':'Yes'}),
++                ('no', {'label':'No'})]
++
++    def run(self):
++        dialog = UrwidDialog(self)
++        dialog.header_text = self.header_text
++        dialog.run()
++        self.exit_token = dialog.exit_token
++
++
++class UrwidFieldSet(object):
++    """
++    Takes a set of fields, displays a dialog corresponding to those fields, and
++    returns the values corresponding to those fields.
++    """
++    def __init__(self, challenges, use_names=True):
++        self.getters = {}
++        self.challenges = challenges
++        self.use_names = use_names
++
++    def get_value_dict(self):
++        """
++        Dump everything we've got.
++        """
++        retval = {}
++        for key in self.getters:
++            retval[key] = self.getters[key]()
++        return retval
++
++    def _set_getter(self, name, function):
++        """ 
++        This is where we collect all of the field getter functions.
++        """
++        self.getters[name] = function
++        
++    def _get_field(self, challenge):
++        """ Build a field in our form.  Called from _get_dialog_body()"""
++        # we don't have hanging indent, but we can stick a bullet out into the 
++        # left column.
++        asterisk = urwid.Text(('label', '* '))
++        if self.use_names:
++            label_text = challenge.full_description()
++        else:
++            label_text = challenge.description
++        label = urwid.Text(('label', label_text))
++        colon = urwid.Text(('label', ': '))
++
++        if issubclass(challenge.type, str):
++            if challenge.default is NoDefault:
++                field = urwid.Edit('', '')
++            else:
++                field = urwid.Edit('', challenge.default)
++            def getter():
++                """ 
++                Closure around urwid.Edit.get_edit_text(), which we'll
++                use to scrape the value out when we're all done.
++                """
++                return field.get_edit_text()
++            self._set_getter(challenge.name, getter)
++        elif issubclass(challenge.type, bool):
++            field = urwid.CheckBox('')
++            def getter():
++                """ 
++                Closure around urwid.CheckBox.get_state(), which we'll
++                use to scrape the value out when we're all done. 
++                """
++                return field.get_state()
++            self._set_getter(challenge.name, getter)
++        else:
++            raise UrwidDialogException(str(challenge.type))
++
++
++        field = urwid.AttrWrap(field, 'field', 'fieldfocus')
++
++        # put everything together.  Each column is either 'fixed' for a fixed width,
++        # or given a 'weight' to help determine the relative width of the column
++        # such that it can fill the row.
++        editwidget = urwid.Columns([('fixed', 2, asterisk),
++                                    ('weight', 3, label),
++                                    ('fixed', 2, colon),
++                                    ('weight', 4, field)])
++
++        wrapper = urwid.AttrWrap(editwidget, None, {'label':'labelfocus'})
++        return urwid.Padding(wrapper, ('fixed left', 3), ('fixed right', 3))
++
++    def get_widgets(self):
++        fieldwidgets = []
++        for chal in self.challenges:
++            fieldwidgets.append(self._get_field(chal))
++        return fieldwidgets
++
++    def get_button_defs(self):
++        return [('ok', {'label':'  OK'}),
++                ('cancel', {'label':'Cancel'})]
++
++    def run(self):
++        dialog = UrwidDialog(self)
++        dialog.header_text = self.header_text
++        UrwidCancelWrapper(dialog).run()
++
++
++class UrwidCancelWrapper(object):
++    def __init__(self, dialog):
++        self.dialog = dialog
++        self.dialog.header_text += " (select 'OK' to continue, 'Cancel' to abort)"
++        
++    def run(self):
++        keepgoing = True
++        while keepgoing:
++            self.dialog.run()
++            keepgoing = self._check_cancel()
++
++    def _check_cancel(self):
++        if self.dialog.exit_token == 'ok':
++            keepgoing = False
++        elif self.dialog.exit_token == 'cancel':
++            yesno = UrwidYesNo('Do you really wish to cancel?')
++            yesno.header_text = 'Exiting...'
++            yesno.run()
++            if yesno.exit_token == 'yes':
++                print 'Cancelled'
++                raise SystemExit
++            elif yesno.exit_token == 'no':
++                keepgoing = True
++            else:
++                raise UrwidDialogException
++        return keepgoing
++
++
++class UrwidDialog(object):
++    """
++    Takes a set of fields, displays a dialog corresponding to those fields, and
++    returns the values corresponding to those fields.
++    """
++    def __init__(self, bodyobj):
++        self.bodyobj = bodyobj
++        self.header_text = "'paster create' Configuration"
++        self.is_constructed = False
++
++    def construct(self):
++        #  Our main loop is going to need four things: 
++        #  1. frame - the UI with all of its widgets
++        #  2. palette - style information for the UI
++        #  3. screen - the engine used to render everything
++        
++        #  1. frame - the UI with all of its widgets
++        header = self._get_dialog_header()
++        body = self._get_dialog_body()
++        self.frame = urwid.Frame(body, header=header)
++
++        #  2. palette - style information for the UI
++        self.palette = [
++            ('body','black','white', 'standout'),
++            ('header','black','light gray', 'bold'),
++            ('labelfocus','black', 'white', 'bold, underline'),
++            ('label','dark blue', 'white'),
++            ('fieldfocus','black,underline', 'white', 'bold, underline'),
++            ('field','black', 'white'),
++            ('button','black','white'),
++            ('buttonfocus','black','light gray','bold'),
++            ]
++
++        #  3. screen - the engine used to render everything
++        self.screen = urwid.raw_display.Screen()
++        self.is_constructed = True
++
++    def run(self):
++        """
++        Invoke Urwid MainLoop to display the dialog, populating self.getters
++        """
++        if not self.is_constructed:
++            self.construct()
++
++        # Putting it all together and running it
++        try:
++            urwid.MainLoop(self.frame, self.palette, self.screen).run()
++        except ExitUrwidUI as inst:
++            self.exit_token = inst.exit_token
++
++    def _get_button_callback(self, name):
++        def button_callback(button):
++            raise ExitUrwidUI(exit_token=name)
++        return button_callback
++
++    def _get_buttons(self):
++        """ renders the ok and cancel buttons.  Called from get_body() """
++
++        buttonlist = []
++        for name, props in self.bodyobj.get_button_defs():
++            # this is going to be what we actually do when someone clicks the button
++            b = urwid.Button(props['label'], 
++                             on_press=self._get_button_callback(name))
++            buttonlist.append(urwid.AttrWrap(b, 'button', 'buttonfocus'))
++
++        return urwid.GridFlow(buttonlist, 10, 7, 1, 'center')
++
++    def _get_dialog_header(self):
++        """ the header of our form, called from run() """
++        header_text = self.header_text
++        header = urwid.Text(header_text)
++        return urwid.AttrWrap(header, 'header')
++
++    def _get_dialog_body(self):
++        """ the body of our form, called from run() """
++
++        # build the list of field widgets
++        bodywidgets = [urwid.Divider(bottom=2)]
++
++        bodywidgets.extend(self.bodyobj.get_widgets())
++        
++        bodywidgets.append(urwid.Divider(bottom=1)) 
++
++        bodywidgets.append(self._get_buttons())
++
++        # SimpleListWalker provides simple linear navigation between the widgets
++        listwalker = urwid.SimpleListWalker(bodywidgets)
++        
++        # ListBox is a scrollable frame around a list of elements
++        listbox = urwid.ListBox(listwalker)
++        return urwid.AttrWrap(listbox, 'body')
++
+# HG changeset patch
+# Parent 85ccd8993dcf910f1726065bcee030b22fe2c476
+Some refactoring to make cancel button behave more sensibly
+
+diff --git a/paste/script/userinterface.py b/paste/script/userinterface.py
+--- a/paste/script/userinterface.py
++++ b/paste/script/userinterface.py
+@@ -101,18 +101,16 @@
+         Return a series of responses
+         """
+         fieldset = UrwidFieldSet(challenges, use_names=use_names)
+-        dialog = UrwidDialog(fieldset)
+-        dialog.header_text = title
+-        dialog.run()
++        fieldset.header_text = title
++        fieldset.run()
+         return fieldset.get_value_dict()
+ 
+     def display_vars(self, vars, title="Variables:"):
+         vars = vars.items()
+         vars.sort()
+-        bodyobj = UrwidDictDisplay(vars)
+-        dialog = UrwidDialog(bodyobj)
+-        dialog.header_text = title
+-        dialog.run()
++        display = UrwidDictDisplay(vars)
++        display.header_text = title
++        display.run()
+ 
+ 
+ class UrwidDialogException(Exception):
+@@ -123,10 +121,10 @@
+     def __init__(self, exit_token=None):
+         self.exit_token = exit_token
+ 
+-
+ class UrwidDictDisplay(object):
+     def __init__(self, showdict):
+         self.showdict = showdict
++        self.header_text = 'Variables:'
+ 
+     def _get_field(self, name, value):
+         """ Display one name/value pair from the dict """
+@@ -153,6 +151,36 @@
+             widgets.append(self._get_field(name, value))
+         return widgets
+ 
++    def get_button_defs(self):
++        return [('ok', {'label':'  OK'}),
++                ('cancel', {'label':'Cancel'})]
++
++    def run(self):
++        dialog = UrwidDialog(self)
++        dialog.header_text = self.header_text
++        UrwidCancelWrapper(dialog).run()
++
++
++class UrwidYesNo(object):
++    def __init__(self, question):
++        self.question = question
++        self.header_text = 'Question:'
++
++    def get_widgets(self):
++        row = urwid.Text(('field', self.question))
++        widget = urwid.Padding(row, ('fixed left', 3), ('fixed right', 3))
++        return [widget]
++
++    def get_button_defs(self):
++        return [('yes', {'label':'Yes'}),
++                ('no', {'label':'No'})]
++
++    def run(self):
++        dialog = UrwidDialog(self)
++        dialog.header_text = self.header_text
++        dialog.run()
++        self.exit_token = dialog.exit_token
++
+ 
+ class UrwidFieldSet(object):
+     """
+@@ -235,6 +263,43 @@
+             fieldwidgets.append(self._get_field(chal))
+         return fieldwidgets
+ 
++    def get_button_defs(self):
++        return [('ok', {'label':'  OK'}),
++                ('cancel', {'label':'Cancel'})]
++
++    def run(self):
++        dialog = UrwidDialog(self)
++        dialog.header_text = self.header_text
++        UrwidCancelWrapper(dialog).run()
++
++
++class UrwidCancelWrapper(object):
++    def __init__(self, dialog):
++        self.dialog = dialog
++        self.dialog.header_text += " (select 'OK' to continue, 'Cancel' to abort)"
++        
++    def run(self):
++        keepgoing = True
++        while keepgoing:
++            self.dialog.run()
++            keepgoing = self._check_cancel()
++
++    def _check_cancel(self):
++        if self.dialog.exit_token == 'ok':
++            keepgoing = False
++        elif self.dialog.exit_token == 'cancel':
++            yesno = UrwidYesNo('Do you really wish to cancel?')
++            yesno.header_text = 'Exiting...'
++            yesno.run()
++            if yesno.exit_token == 'yes':
++                print 'Cancelled'
++                raise SystemExit
++            elif yesno.exit_token == 'no':
++                keepgoing = True
++            else:
++                raise UrwidDialogException
++        return keepgoing
++
+ 
+ class UrwidDialog(object):
+     """
+@@ -244,12 +309,9 @@
+     def __init__(self, bodyobj):
+         self.bodyobj = bodyobj
+         self.header_text = "'paster create' Configuration"
++        self.is_constructed = False
+ 
+-    def run(self):
+-        """
+-        Invoke Urwid MainLoop to display the dialog, populating self.getters
+-        """
+-
++    def construct(self):
+         #  Our main loop is going to need four things: 
+         #  1. frame - the UI with all of its widgets
+         #  2. palette - style information for the UI
+@@ -258,10 +320,10 @@
+         #  1. frame - the UI with all of its widgets
+         header = self._get_dialog_header()
+         body = self._get_dialog_body()
+-        frame = urwid.Frame(body, header=header)
++        self.frame = urwid.Frame(body, header=header)
+ 
+         #  2. palette - style information for the UI
+-        palette = [
++        self.palette = [
+             ('body','black','white', 'standout'),
+             ('header','black','light gray', 'bold'),
+             ('labelfocus','black', 'white', 'bold, underline'),
+@@ -273,40 +335,42 @@
+             ]
+ 
+         #  3. screen - the engine used to render everything
+-        screen = urwid.raw_display.Screen()
++        self.screen = urwid.raw_display.Screen()
++        self.is_constructed = True
++
++    def run(self):
++        """
++        Invoke Urwid MainLoop to display the dialog, populating self.getters
++        """
++        if not self.is_constructed:
++            self.construct()
+ 
+         # Putting it all together and running it
+         try:
+-            urwid.MainLoop(frame, palette, screen).run()
++            urwid.MainLoop(self.frame, self.palette, self.screen).run()
+         except ExitUrwidUI as inst:
+             self.exit_token = inst.exit_token
+-            if self.exit_token == 'cancel':
+-                raise
++
++    def _get_button_callback(self, name):
++        def button_callback(button):
++            raise ExitUrwidUI(exit_token=name)
++        return button_callback
+ 
+     def _get_buttons(self):
+         """ renders the ok and cancel buttons.  Called from get_body() """
+ 
+-        # this is going to be what we actually do when someone clicks the button
+-        def ok_button_callback(button):
+-            raise ExitUrwidUI(exit_token='ok')
++        buttonlist = []
++        for name, props in self.bodyobj.get_button_defs():
++            # this is going to be what we actually do when someone clicks the button
++            b = urwid.Button(props['label'], 
++                             on_press=self._get_button_callback(name))
++            buttonlist.append(urwid.AttrWrap(b, 'button', 'buttonfocus'))
+ 
+-        # leading spaces to center it....seems like there should be a better way
+-        b = urwid.Button('  OK', on_press=ok_button_callback)
+-        okbutton = urwid.AttrWrap(b, 'button', 'buttonfocus')
+-
+-        # second verse, same as the first....
+-        def cancel_button_callback(button):
+-            raise ExitUrwidUI(exit_token='cancel')
+-
+-        b = urwid.Button('Cancel', on_press=cancel_button_callback)
+-        cancelbutton = urwid.AttrWrap(b, 'button', 'buttonfocus')
+-
+-        return urwid.GridFlow([okbutton, cancelbutton], 10, 7, 1, 'center')
++        return urwid.GridFlow(buttonlist, 10, 7, 1, 'center')
+ 
+     def _get_dialog_header(self):
+         """ the header of our form, called from run() """
+         header_text = self.header_text
+-        header_text += " (select 'OK' to continue, 'Cancel' to abort)"
+         header = urwid.Text(header_text)
+         return urwid.AttrWrap(header, 'header')
+