[PATCH 05/13] Fix bits and pieces to make TUI hub and spoke model work + example Hub and Password spoke

Martin Sivak msivak at redhat.com
Wed Aug 8 08:52:43 UTC 2012


---
 Makefile.am                             |   15 ++++++++++
 pyanaconda/ui/__init__.py               |   25 +---------------
 pyanaconda/ui/common.py                 |   34 ++++++++++++++++++++--
 pyanaconda/ui/gui/tools/run-spoke.py    |    7 ++---
 pyanaconda/ui/tui/__init__.py           |   47 ++++++++++++++++++-------------
 pyanaconda/ui/tui/hubs/__init__.py      |   42 ++++++++++++++++-----------
 pyanaconda/ui/tui/simpleline/base.py    |   46 ++++++++++++++++++++----------
 pyanaconda/ui/tui/simpleline/widgets.py |    8 +++---
 pyanaconda/ui/tui/spokes/__init__.py    |   17 +++++------
 9 files changed, 148 insertions(+), 93 deletions(-)

diff --git a/Makefile.am b/Makefile.am
index b44aa2d..9085d4e 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -126,6 +126,21 @@ runhub:
 	GI_TYPELIB_PATH=widgets/src/ \
 	pyanaconda/ui/gui/tools/run-hub.py ${HUB_MODULE} ${HUB_CLASS}
 
+runtextspoke:
+	ANACONDA_DATA=${PWD}/data \
+	ANACONDA_INSTALL_CLASSES=${PWD}/pyanaconda/installclasses \
+	PYTHONPATH=.:pyanaconda/isys/.libs:widgets/python/:widgets/src/.libs/ \
+	LD_LIBRARY_PATH=widgets/src/.libs \
+	pyanaconda/ui/tui/tools/run-spoke.py ${SPOKE_MODULE} ${SPOKE_CLASS}
+
+runtexthub:
+	ANACONDA_DATA=${PWD}/data \
+	ANACONDA_INSTALL_CLASSES=${PWD}/pyanaconda/installclasses \
+	PYTHONPATH=.:pyanaconda/isys/.libs:widgets/python/:widgets/src/.libs/ \
+	LD_LIBRARY_PATH=widgets/src/.libs \
+	pyanaconda/ui/tui/tools/run-hub.py ${HUB_MODULE} ${HUB_CLASS}
+
+
 runglade:
 	ANACONDA_DATA=${PWD}/data \
 	ANACONDA_WIDGETS_OVERRIDES=${PWD}/widgets/python \
diff --git a/pyanaconda/ui/__init__.py b/pyanaconda/ui/__init__.py
index fcc6628..91252c1 100644
--- a/pyanaconda/ui/__init__.py
+++ b/pyanaconda/ui/__init__.py
@@ -22,8 +22,7 @@
 __all__ = ["UserInterface", "collect"]
 
 import os
-import importlib
-import inspect
+from common import collect
 
 class UserInterface(object):
     """This is the base class for all kinds of install UIs.  It primarily
@@ -112,25 +111,3 @@ class UserInterface(object):
                                         key=lambda obj: obj.priority))
 
         return actionClasses
-
-def collect(module_pattern, path, pred):
-    """Traverse the directory (given by path) and find all classes that match
-       the given predicate.  This is then returned as a list of classes.
-
-       It is suggested you use collect_categories or collect_spokes instead of
-       this lower-level method.
-    """
-    retval = []
-    for module_file in os.listdir(path):
-        if not module_file.endswith(".py") or module_file == "__init__.py":
-            continue
-
-        mod_name = module_file[:-3]
-        module = importlib.import_module(module_pattern % mod_name)
-
-        p = lambda obj: inspect.isclass(obj) and pred(obj)
-
-        for (name, val) in inspect.getmembers(module, p):
-            retval.append(val)
-
-    return retval
diff --git a/pyanaconda/ui/common.py b/pyanaconda/ui/common.py
index 03fa49d..b8944c1 100644
--- a/pyanaconda/ui/common.py
+++ b/pyanaconda/ui/common.py
@@ -1,3 +1,7 @@
+import os
+import importlib
+import inspect
+
 class UIObject(object):
     """This is the base class from which all other UI classes are derived.  It
        thus contains only attributes and methods that are common to everything
@@ -78,6 +82,10 @@ class UIObject(object):
         """
         raise TypeError("UIObject.window has to be overriden")
 
+    @property
+    def data(self):
+        return self._data
+
 class Spoke(UIObject):
     """A Spoke is a single configuration screen.  There are several different
        places where a Spoke can be displayed, each of which will have its own
@@ -189,6 +197,8 @@ class Spoke(UIObject):
         raise NotImplementedError
 
 class NormalSpoke(Spoke):
+    priority = 100
+
     @property
     def indirect(self):
         """If this property returns True, then this spoke is considered indirect.
@@ -245,8 +255,6 @@ class StandaloneSpoke(NormalSpoke):
     preForHub = None
     postForHub = None
 
-    priority = 100
-
     def __init__(self, data, storage, payload, instclass):
         """Create a StandaloneSpoke instance."""
         if self.__class__ is StandaloneSpoke:
@@ -342,3 +350,25 @@ class Hub(UIObject):
         self.storage = storage
         self.payload = payload
         self.instclass = instclass
+
+def collect(module_pattern, path, pred):
+    """Traverse the directory (given by path) and find all classes that match
+       the given predicate.  This is then returned as a list of classes.
+
+       It is suggested you use collect_categories or collect_spokes instead of
+       this lower-level method.
+    """
+    retval = []
+    for module_file in os.listdir(path):
+        if not module_file.endswith(".py") or module_file == "__init__.py":
+            continue
+
+        mod_name = module_file[:-3]
+        module = importlib.import_module(module_pattern % mod_name)
+
+        p = lambda obj: inspect.isclass(obj) and pred(obj)
+
+        for (name, val) in inspect.getmembers(module, p):
+            retval.append(val)
+
+    return retval
diff --git a/pyanaconda/ui/gui/tools/run-spoke.py b/pyanaconda/ui/gui/tools/run-spoke.py
index 6114155..afb6b01 100755
--- a/pyanaconda/ui/gui/tools/run-spoke.py
+++ b/pyanaconda/ui/gui/tools/run-spoke.py
@@ -44,13 +44,13 @@ initThreading()
 # And get the name of the module which represents it
 if os.path.basename(sys.argv[0]) == "run-spoke.py":
     spokeModuleName = "pyanaconda.ui.gui.spokes.%s" % sys.argv[1]
-    from pyanaconda.ui.gui.spokes import Spoke
+    from pyanaconda.ui.common import Spoke
     spokeBaseClass = Spoke
     spokeText = "spoke"
     SpokeText = "Spoke"
 elif os.path.basename(sys.argv[0]) == "run-hub.py":
     spokeModuleName = "pyanaconda.ui.gui.hubs.%s" % sys.argv[1]
-    from pyanaconda.ui.gui.hubs import Hub
+    from pyanaconda.ui.common import Hub
     spokeBaseClass = Hub
     spokeText = "hub"
     SpokeText = "Hub"
@@ -76,10 +76,9 @@ except IndexError:
             if issubclass(v, spokeBaseClass) and v != spokeBaseClass:
                 spokeClassName = k
                 spokeClass = v
-                break
         except TypeError:
             pass
-    
+
 if not spokeClass:
     try:
         spokeClass = getattr(spokeModule, spokeClassName)
diff --git a/pyanaconda/ui/tui/__init__.py b/pyanaconda/ui/tui/__init__.py
index e1a36f5..22acf1c 100644
--- a/pyanaconda/ui/tui/__init__.py
+++ b/pyanaconda/ui/tui/__init__.py
@@ -1,20 +1,18 @@
 from pyanaconda import ui
+from pyanaconda.ui import common
 import simpleline as tui
 
-from hubs.summary import SummaryHub
-from hubs.progress import ProgressHub
-from spokes import StandaloneSpoke
-
 class ErrorDialog(tui.UIScreen):
     title = u"Error"
 
-    def __init__(self, data, message):
+    def __init__(self, app, message):
+        tui.UIScreen.__init__(self, app)
         self._message = message
 
     def refresh(self, args = None):
         tui.UIScreen.refresh(self, args)
         text = tui.TextWidget(self._message)
-        self.window.append(tui.CenterWidget(text))
+        self._window.append(tui.CenterWidget(text))
 
     def prompt(self):
         return u"Press enter to exit."
@@ -25,15 +23,17 @@ class ErrorDialog(tui.UIScreen):
 class YesNoDialog(tui.UIScreen):
     title = u"Question"
 
-    def __init__(self, data, message):
+    def __init__(self, app, message):
+        tui.UIScreen.__init__(self, app)
         self._message = message
         self._response = None
 
     def refresh(self, args = None):
         tui.UIScreen.refresh(self, args)
         text = tui.TextWidget(self._message)
-        self.window.append(tui.CenterWidget(text))
-        self.window.append(u"")
+        self._window.append(tui.CenterWidget(text))
+        self._window.append(u"")
+        return True
 
     def prompt(self):
         return u"Please respond 'yes' or 'no': "
@@ -50,28 +50,37 @@ class YesNoDialog(tui.UIScreen):
             return None
 
         else:
-            return key
+            return False
+
+    @property
+    def answer(self):
+        return self._response
 
 class TextUserInterface(ui.UserInterface):
 
     def __init__(self, storage, payload, instclass):
         ui.UserInterface.__init__(self, storage, payload, instclass)
         self._app = None
-        self._hubs = []
 
     def setup(self, data):
         """Construct all the objects required to implement this interface.
            This method must be provided by all subclasses.
         """
         self._app = tui.App(u"Anaconda", yes_or_no_question = YesNoDialog)
-        self._hubs = []
+
+        from hubs.summary import SummaryHub
+        #from hubs.progress import ProgressHub
+        self._hubs = [SummaryHub]
+
+
+        from spokes import StandaloneSpoke
 
         # First, grab a list of all the standalone spokes.
         path = os.path.join(os.path.dirname(__file__), "spokes")
         actionClasses = self.getActionClasses("pyanaconda.ui.tui.spokes.%s", path, self._hubs, StandaloneSpoke)
 
         for klass in actionClasses:
-            obj = klass(data, self.storage, self.payload, self.instclass)
+            obj = klass(self._app, data, self.storage, self.payload, self.instclass)
 
             # If we are doing a kickstart install, some standalone spokes
             # could already be filled out.  In taht case, we do not want
@@ -100,7 +109,7 @@ class TextUserInterface(ui.UserInterface):
            In the code, this method should be used sparingly and only for
            critical errors that anaconda cannot figure out how to recover from.
         """
-        error_window = ErrorDialog(message)
+        error_window = ErrorDialog(self._app, message)
         self._app.switch_window(error_window)
 
     def showYesNoQuestion(self, message):
@@ -114,16 +123,16 @@ class TextUserInterface(ui.UserInterface):
            times where anaconda cannot make a reasonable decision.  We don't
            want to overwhelm the user with choices.
         """
-        question_window = YesNoDialog(message)
+        question_window = YesNoDialog(self._app, message)
         self._app.switch_window_modal(question_window)
         return question_window.answer
 
 class TUIObject(tui.UIScreen, common.UIObject):
     title = u"Default title"
 
-    def __init__(self, app):
+    def __init__(self, app, data):
         tui.UIScreen.__init__(self, app)
-        common.UIObject.__init__(self)
+        common.UIObject.__init__(self, data)
 
     @property
     def showable(self):
@@ -135,9 +144,9 @@ class TUIObject(tui.UIScreen, common.UIObject):
     def initialize(self):
         pass
 
-    def refresh(self):
+    def refresh(self, args = None):
         """Put everything to display into self.window list."""
-        pass
+        tui.UIScreen.refresh(self, args)
 
     def retranslate(self):
         # do retranslation stuff
diff --git a/pyanaconda/ui/tui/hubs/__init__.py b/pyanaconda/ui/tui/hubs/__init__.py
index a4343d4..33a122e 100644
--- a/pyanaconda/ui/tui/hubs/__init__.py
+++ b/pyanaconda/ui/tui/hubs/__init__.py
@@ -1,36 +1,44 @@
 from .. import simpleline as tui
 from pyanaconda.ui.tui import TUIObject
+from pyanaconda.ui.tui.spokes import collect_spokes
+from pyanaconda.ui import common
 
-class TUIHub(TUIObject):
-    spokes = []
+class TUIHub(TUIObject, common.Hub):
+    categories = []
     title = "Default HUB title"
 
-    def __init__(self, app, data):
-        TUIObject.__init__(self, app)
+    def __init__(self, app, data, storage, payload, instclass):
+        TUIObject.__init__(self, app, data)
+        common.Hub.__init__(self, data, storage, payload, instclass)
         self._spokes = {}
+        self._keys = {}
         self._spoke_count = 0
 
-        for s in self.spokes:
-            spoke = s(app, data)
-            spoke.initialize()
+        for c in self.categories:
+            spokes = collect_spokes(c)
+            for s in sorted(spokes, key = lambda s: s.priority):
+                spoke = s(app, data, storage, payload, instclass)
+                spoke.initialize()
 
-            if not spoke.showable:
-                spoke.teardown()
-                del spoke
-                continue
+                if not spoke.showable:
+                    spoke.teardown()
+                    del spoke
+                    continue
+
+                self._spoke_count += 1
+                self._keys[self._spoke_count] = spoke
+                self._spokes[spoke.__class__.__name__] = spoke
 
-            self._spoke_count += 1
-            self._spokes[self._spoke_count] = spoke
 
     def refresh(self, args = None):
-        common.UIObject.refresh(self, args)
+        TUIObject.refresh(self, args)
 
         def _prep(i, w):
             number = tui.TextWidget("%2d)" % i)
             return tui.ColumnWidget([(3, [number]), (None, [w])], 1)
 
-        left = [_prep(i, w) for i,w in self._spokes.iteritems() if i % 2 == 1]
-        right = [_prep(i, w) for i,w in self._spokes.iteritems() if i % 2 == 0]
+        left = [_prep(i, w) for i,w in self._keys.iteritems() if i % 2 == 1]
+        right = [_prep(i, w) for i,w in self._keys.iteritems() if i % 2 == 0]
 
         c = tui.ColumnWidget([(39, left), (39, right)], 2)
         self._window.append(c)
@@ -40,7 +48,7 @@ class TUIHub(TUIObject):
     def input(self, key):
         try:
             number = int(key)
-            self.app.switch_screen_with_return(self._spokes[number])
+            self.app.switch_screen_with_return(self._keys[number])
             return None
 
         except (ValueError, KeyError):
diff --git a/pyanaconda/ui/tui/simpleline/base.py b/pyanaconda/ui/tui/simpleline/base.py
index 715b097..573f28b 100644
--- a/pyanaconda/ui/tui/simpleline/base.py
+++ b/pyanaconda/ui/tui/simpleline/base.py
@@ -21,6 +21,8 @@
 
 __all__ = ["ExitMainLoop", "App", "UIScreen", "Widget"]
 
+import readline
+
 class ExitAllMainLoops(Exception):
     pass
 
@@ -29,9 +31,10 @@ class ExitMainLoop(Exception):
 
 
 class App(object):
-    def __init__(self, title, yes_or_no_question = None):
+    def __init__(self, title, yes_or_no_question = None, width = 80):
         self._header = title
-        self._spacer = "\n".join(2*[80*"="])
+        self._spacer = "\n".join(2*[width*"="])
+        self._width = width
         self.quit_question = yes_or_no_question
 
         # screen stack contains triplets
@@ -54,7 +57,7 @@ class App(object):
     def switch_screen_modal(self, ui, args = None):
         """Starts a new screen right away, so the caller can collect data back. When the new screen is closed, the caller is redisplayed."""
         self._screens.append((ui, args, True))
-        self.redraw()
+        self._do_redraw()
 
     def schedule_screen(self, ui, args = None):
         """Add screen to the bottom of the stack."""
@@ -82,8 +85,6 @@ class App(object):
             raise ExitMainLoop()
 
         screen, args, newloop = self._screens[-1]
-        input_needed = screen.refresh(args)
-        screen.window.show_all()
 
         if newloop == True:
             self._screens.pop()
@@ -91,8 +92,10 @@ class App(object):
             self.mainloop()
             self.redraw()
             input_needed = False # we have to skip input once, to redisplay the screen first
-
-        self._redraw = False
+        else:
+            input_needed = screen.refresh(args)
+            screen.window.show_all()
+            self._redraw = False
 
         return input_needed
 
@@ -107,14 +110,23 @@ class App(object):
         last_screen = None
         error_counter = 0
         while self._screens:
+            if self._redraw:
+                print self._spacer
+
             try:
                 if self._redraw or last_screen != self._screens[-1]:
                     if not self._do_redraw():
                         continue
 
-                last_screen = self._screens[-1]
+                last_screen = self._screens[-1][0]
+
+                prompt = last_screen.prompt()
+                if prompt is None:
+                    self.redraw()
+                    continue
+
+                c = raw_input(prompt)
 
-                c = raw_input(last_screen.prompt())
                 if not self.input(c):
                     error_counter += 1
 
@@ -123,7 +135,6 @@ class App(object):
 
                 if self._redraw:
                     error_counter = 0
-                    print self._spacer
             except ExitMainLoop:
                 break
             except ExitAllMainLoops:
@@ -140,10 +151,11 @@ class App(object):
             self.close_screen()
             return True
 
-        elif self._screens and (key == 'quit'):
+        elif self._screens and (key == 'q'):
             if self.quit_question:
-                self.switch_screen_modal(self.quit_question(u"Do you really want to quit?"))
-                if self.quit_question.answer:
+                d = self.quit_question(self, u"Do you really want to quit?")
+                self.switch_screen_modal(d)
+                if d.answer:
                     raise ExitAllMainLoops()
             return True
 
@@ -160,6 +172,9 @@ class App(object):
     def store(self):
         return self._store
 
+    @property
+    def width(self):
+        return self._width
 
 class UIScreen(object):
     title = u"Screen.."
@@ -170,7 +185,7 @@ class UIScreen(object):
 
     def refresh(self, args = None):
         """Method which prepares the screen to self._window. If user input is requested, return True."""
-        self.window = [self.title, u""]
+        self._window = [self.title, u""]
 
     @property
     def window(self):
@@ -179,7 +194,7 @@ class UIScreen(object):
     def show_all(self):
         for w in self._window:
             if hasattr(w, "render"):
-                w.render(app.width)
+                w.render(self.app.width)
             print unicode(w)
 
     show = show_all
@@ -192,6 +207,7 @@ class UIScreen(object):
         return key
 
     def prompt(self):
+        """Return the text to be shown as prompt or handle the prompt and return None."""
         return u"\tPlease make your choice from above ['q' to quit]: "
 
     @property
diff --git a/pyanaconda/ui/tui/simpleline/widgets.py b/pyanaconda/ui/tui/simpleline/widgets.py
index 2d79e3f..8dde043 100644
--- a/pyanaconda/ui/tui/simpleline/widgets.py
+++ b/pyanaconda/ui/tui/simpleline/widgets.py
@@ -31,7 +31,7 @@ class TextWidget(base.Widget):
         self._text = text
 
     def render(self, width):
-        self.clear()
+        base.Widget.render(self, width)
         self.write(self._text, width = width)
 
 class CenterWidget(base.Widget):
@@ -40,7 +40,7 @@ class CenterWidget(base.Widget):
         self._w = w
 
     def render(self, width):
-        self.clear()
+        base.Widget.render(self, width)
         self._w.render(width)
         self.draw(self._w, col = (width - self._w.width) / 2)
 
@@ -60,7 +60,7 @@ class ColumnWidget(base.Widget):
         self._columns = columns
 
     def render(self, width):
-        self.clear()
+        base.Widget.render(self, width)
 
         x = 0
         for col_width,col in self._columns:
@@ -87,7 +87,7 @@ class CheckboxWidget(base.Widget):
         self._completed = completed
 
     def render(self, width):
-        self.clear()
+        base.Widget.render(self, width)
 
         if self.completed:
             checkchar = "x"
diff --git a/pyanaconda/ui/tui/spokes/__init__.py b/pyanaconda/ui/tui/spokes/__init__.py
index 5727d0c..153b79c 100644
--- a/pyanaconda/ui/tui/spokes/__init__.py
+++ b/pyanaconda/ui/tui/spokes/__init__.py
@@ -1,18 +1,19 @@
 from .. import simpleline as tui
 from pyanaconda.ui.tui import TUIObject
-from pyanaconda.ui import common
+from pyanaconda.ui.common import Spoke, StandaloneSpoke, NormalSpoke, PersonalizationSpoke, collect
+import os
 
 __all__ = ["TUISpoke", "StandaloneSpoke", "NormalSpoke", "PersonalizationSpoke",
            "collect_spokes", "collect_categories"]
 
-class TUISpoke(TUIObject, tui.Widget, common.Spoke):
+class TUISpoke(TUIObject, tui.Widget, Spoke):
     title = u"Default spoke title"
     category = u""
 
     def __init__(self, app, ksdata, storage, payload, instclass):
-        TUIObject.__init__(self, app)
+        TUIObject.__init__(self, app, ksdata)
         tui.Widget.__init__(self)
-        common.Spoke.__init__(self, ksdata, storage, payload, instclass)
+        Spoke.__init__(self, ksdata, storage, payload, instclass)
 
     @property
     def status(self):
@@ -35,20 +36,20 @@ class TUISpoke(TUIObject, tui.Widget, common.Spoke):
         c.render(width)
         self.draw(c)
 
-class StandaloneTUISpoke(TUISpoke, common.StandaloneSpoke):
+class StandaloneTUISpoke(TUISpoke, StandaloneSpoke):
     pass
 
-class NormalTUISpoke(TUISpoke, common.NormalSpoke):
+class NormalTUISpoke(TUISpoke, NormalSpoke):
     pass
 
-class PersonalizationTUISpoke(TUISpoke, common.PersonalizationSpoke):
+class PersonalizationTUISpoke(TUISpoke, PersonalizationSpoke):
     pass
 
 def collect_spokes(category):
     """Return a list of all spoke subclasses that should appear for a given
        category.
     """
-    return collect("pyanaconda.ui.tui.spokes.%s", os.path.dirname(__file__), lambda obj: hasattr(obj, "category") and obj.category != None and obj.category.__name__ == category)
+    return collect("pyanaconda.ui.tui.spokes.%s", os.path.dirname(__file__), lambda obj: hasattr(obj, "category") and obj.category != None and obj.category == category)
 
 def collect_categories():
     classes = collect("pyanaconda.ui.tui.spokes.%s", os.path.dirname(__file__), lambda obj: hasattr(obj, "category") and obj.category != None and obj.category != "")
-- 
1.7.10.4



More information about the anaconda-patches mailing list