[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