[node-patches] Change in ovirt-node[master]: A concept for change handling

fabiand at fedoraproject.org fabiand at fedoraproject.org
Tue Dec 11 20:09:31 UTC 2012


Fabian Deutsch has uploaded a new change for review.

Change subject: A concept for change handling
......................................................................

A concept for change handling

A plugin provides a model which is modified by the UI. The model is not
edited in place but a series of patches is collected and later (when
saved) provided to the plugin, which can handle the changes.

Change-Id: Ic8183b700f46c25bc7a4e03c20e5adf76d8935b9
Signed-off-by: Fabian Deutsch <fabiand at fedoraproject.org>
---
M scripts/tui/.gitignore
A scripts/tui/README
D scripts/tui/src/README
M scripts/tui/src/ovirt/__init__.py
M scripts/tui/src/ovirt/node/__init__.py
A scripts/tui/src/ovirt/node/pattern.py
M scripts/tui/src/ovirt/node/plugins/__init__.py
M scripts/tui/src/ovirt/node/plugins/example.py
M scripts/tui/src/ovirt/node/plugins/features.py
M scripts/tui/src/ovirt/node/plugins/usage.py
M scripts/tui/src/ovirt/node/tui.py
11 files changed, 478 insertions(+), 76 deletions(-)


  git pull ssh://gerrit.ovirt.org:29418/ovirt-node refs/changes/63/9863/1

diff --git a/scripts/tui/.gitignore b/scripts/tui/.gitignore
index c7c62df..171bbb9 100644
--- a/scripts/tui/.gitignore
+++ b/scripts/tui/.gitignore
@@ -1,2 +1,4 @@
 *~
 app.log
+*.pyc
+*.pyo
diff --git a/scripts/tui/README b/scripts/tui/README
new file mode 100644
index 0000000..1dd35bc
--- /dev/null
+++ b/scripts/tui/README
@@ -0,0 +1,21 @@
+A simple system to build a TUI (based on urwid) by plugins.
+
+$ sudo yum install python python-urwind
+$ cd src
+$ ./app
+
+Features
+--------
+- Clean separation between UI and data
+- Pluggable
+- UI
+  - Resizable UI
+  - Mouse support
+  - Event based
+- Model
+  - With validation support
+
+
+Sources
+-------
+http://excess.org/urwid/
diff --git a/scripts/tui/src/README b/scripts/tui/src/README
deleted file mode 100644
index ae61415..0000000
--- a/scripts/tui/src/README
+++ /dev/null
@@ -1,16 +0,0 @@
-A simple system to build a TUI (based on urwid) by plugins.
-
-$ ./app
-
-Features
---------
-- Resizable UI
-- Event based
-- Pluggable
-- Clean separation between UI and data
-- Mouse support
-
-
-Sources
--------
-http://excess.org/urwid/
diff --git a/scripts/tui/src/ovirt/__init__.py b/scripts/tui/src/ovirt/__init__.py
index e69de29..6d10dcd 100644
--- a/scripts/tui/src/ovirt/__init__.py
+++ b/scripts/tui/src/ovirt/__init__.py
@@ -0,0 +1 @@
+# oVirt Module
diff --git a/scripts/tui/src/ovirt/node/__init__.py b/scripts/tui/src/ovirt/node/__init__.py
index e69de29..26558f8 100644
--- a/scripts/tui/src/ovirt/node/__init__.py
+++ b/scripts/tui/src/ovirt/node/__init__.py
@@ -0,0 +1 @@
+# oVirt Node Module
diff --git a/scripts/tui/src/ovirt/node/pattern.py b/scripts/tui/src/ovirt/node/pattern.py
new file mode 100644
index 0000000..24f44ca
--- /dev/null
+++ b/scripts/tui/src/ovirt/node/pattern.py
@@ -0,0 +1,63 @@
+#!/bin/env python
+
+import re
+import logging
+
+import ovirt.node.plugins
+
+
+logging.basicConfig(level=logging.DEBUG)
+LOGGER = logging.getLogger(__name__)
+
+
+class RegexValidationWrapper(str):
+    # pattern defined by subclass
+    # description defined by subclass
+    __exception_msg = "The field must contain {description}."
+
+    def __call__(self, value):
+        if re.search(self.pattern, value) == None:
+            msg = self.__exception_msg.format(description=self.description)
+            raise ovirt.node.plugins.InvalidData(msg)
+        return True
+
+
+class Text(RegexValidationWrapper):
+    description = "anything"
+    pattern = ".*"
+
+    def __init__(self, min_length=0):
+        if min_length > 0:
+            self.pattern = ".{%d}" % min_length
+            self.description += " (min. %d chars)" % min_length
+
+
+class Number(RegexValidationWrapper):
+    description = "a number"
+    pattern = "^\d+$"
+
+
+class NoSpaces(RegexValidationWrapper):
+    description = "a string without spaces"
+    pattern = "^\S*$"
+
+
+class ValidHostname(RegexValidationWrapper):
+    description = "a valid hostname"
+    pattern = ("^(([a-zA-Z]|[a-zA-Z][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)" +
+               "*([A-Za-z]|[A-Za-z][A-Za-z0-9\-]*[A-Za-z0-9])$")
+
+
+class ValidIPv4Address(RegexValidationWrapper):
+    description = "a valid IPv4 address"
+    pattern = ("^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.)" +
+               "{3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$")
+
+
+class ValidIPv6Address(RegexValidationWrapper):
+    description = "a valid IPv6 address"
+    pattern = ("/^(?>(?>([a-f0-9]{1,4})(?>:(?1)){7}|(?!(?:.*[a-f0-9](?>:|$" +
+               ")){7,})((?1)(?>:(?1)){0,5})?::(?2)?)|(?>(?>(?1)(?>:(?1)){5}" +
+               ":|(?!(?:.*[a-f0-9]:){5,})(?3)?::(?>((?1)(?>:(?1)){0,3}):)?)?" +
+               "(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?>\.(?" +
+               "4)){3}))$/iD")
diff --git a/scripts/tui/src/ovirt/node/plugins/__init__.py b/scripts/tui/src/ovirt/node/plugins/__init__.py
index 73f1b17..f152529 100644
--- a/scripts/tui/src/ovirt/node/plugins/__init__.py
+++ b/scripts/tui/src/ovirt/node/plugins/__init__.py
@@ -1,5 +1,4 @@
 
-import os
 import pkgutil
 import logging
 
@@ -7,13 +6,18 @@
 
 LOGGER = logging.getLogger(__name__)
 
+
 def __walk_plugins():
+    """Used to find all plugins
+    """
     package = ovirt.node.plugins
     for importer, modname, ispkg in pkgutil.iter_modules(package.__path__):
         yield (importer, modname, ispkg)
 
 
 def load_all():
+    """Load all plugins
+    """
     modules = []
     for importer, modname, ispkg in __walk_plugins():
         #print("Found submodule %s (is a package: %s)" % (modname, ispkg))
@@ -24,28 +28,201 @@
 
 
 class NodePlugin(object):
+    """
+    Basically a plugin provides a model which is changed by the UI (using the
+    model() method)
+    When the user "saves", the changes are provided to the plugin which can
+    then decide what to do with the changes.
+
+    Flow:
+
+    1. [Start UI]
+    2. [Change UI] triggers check_changes(), exception leads to UI dialog
+    3. [Save UI]   triggers save_changes(),  exception leads to UI dialog
+
+
+    Errors are propagated back by using Errors/Exceptions.
+    """
+
+    _changes = None
+
+    def __init__(self):
+        self._changes = {}
+
     def name(self):
+        """Returns the name of the plugin.
+        This is used as the entry for the navigation list.
+
+        Returns:
+            The name of the plugin
+        """
+        raise Exception("Not yet implemented.")
+
+    def model(self):
+        """Returns the model of the plugin.
+        A model is just a dict (key-PatternObj) where each path (key) maps to a
+        value which is modified by changes in the UI.
+
+        Returns:
+            The model (dict) of the plugin
+        """
+        raise Exception("Not yet implemented.")
+
+    def validators(self):
+        """Returns a dict of validators.
+        The dict, mapping a (model) path to a validator is used to validate
+        new model values (a change).
+        It can but does not need to contain an entry for the paths in the
+        model.
+
+        Returns:
+            A dict of validators
+        """
+
+    def has_ui(self):
+        """Determins if a page for this should be displayed in the UI
+
+        Returns:
+            True if a page shall be displayed, False otherwise
+        """
+        return True
+
+    def ui_content(self):
+        """Describes the UI this plugin requires
+        This is an ordered list of (path, widget) tuples.
+        """
+        raise Exception("Not yet implemented.")
+
+    def on_change(self, changes):
+        """Applies the changes to the plugins model, will do all required logic
+        return True if succeeds, otherwie false or throw an error
+
+        Args:
+            changes (dict): changes which shall be checked against the model
+        Returns:
+            True on success, or False otherwie
+        Raises:
+            Errors
+        """
+        raise Exception("Not yet implemented.")
+
+    def on_merge(self, changes):
+        """Handles the changes and throws an Exception if something goes wrong
+        Needs to be implemented by any subclass
+
+        Args:
+            changes (dict): changes which shall be applied to the model
+        Returns:
+            True on success, or False otherwie
+        Raises:
+            Errors
+        """
         raise Exception("Not yet implemented.")
 
     def ui_name(self):
         return self.name()
 
-    def ui_on_change(self, model):
+    def _on_ui_change(self, change):
         """Called when some widget was changed
+        change is expected to be a dict.
         """
-        LOGGER.debug("changed: " + str(model))
+        assert type(change) is dict
+        LOGGER.debug("Model change: " + str(change))
+        self.on_change(change)
+        self._changes.update(change)
+        LOGGER.debug(self._changes)
 
-    def ui_on_save(self, model):
+    def _on_ui_save(self):
         """Called when data should be saved
+        Calls merge_changes, but only with values that really changed
         """
-        LOGGER.debug("saved")
+        LOGGER.debug("Request to apply model changes")
+        if self._changes:
+            real_changes = {}
+            for key, value in self._changes.items():
+                if value == self.model()[key]:
+                    LOGGER.debug(("Skipping pseudo-change of '%s', value " + \
+                                  "did not change") % key)
+                else:
+                    real_changes[key] = value
+            return self.on_merge(real_changes)
+        else:
+            LOGGER.debug("No changes detected")
+        return True
 
 
-class Label(object):
+class Widget(object):
+    _enabled = True
+    _signals = None
+
+    def enabled(self, is_enabled=None):
+        if is_enabled in [True, False]:
+            self.emit_signal("enabled[change]", is_enabled)
+            self._enabled = is_enabled
+        return self._enabled
+
+    def connect_signal(self, name, cb):
+        if self._signals is None:
+            self._signals = {}
+        if name not in self._signals:
+            self._signals[name] = []
+        self._signals[name].append(cb)
+
+    def emit_signal(self, name, userdata=None):
+        if self._signals is None or \
+           name not in self._signals:
+            return False
+        for cb in self._signals[name]:
+            LOGGER.debug("CB for sig %s: %s" % (name, cb))
+            cb(self, userdata)
+
+
+class Label(Widget):
+    """Represents a r/o label
+    """
     def __init__(self, label):
         self.label = label
 
-class Entry(object):
-    def __init__(self, path, label):
-        self.path = path
+
+class Header(Label):
+    pass
+
+
+class Entry(Widget):
+    """Represents an entry field
+    TODO multiline
+    """
+    def __init__(self, label, value=None, initial_value_from_model=True,
+                 enabled=True):
         self.label = label
+        self.value = value
+        self.initial_value_from_model = initial_value_from_model
+        self._enabled = enabled
+
+
+class Password(Entry):
+    pass
+
+
+class InvalidData(Exception):
+    """E.g. if a string contains characters which are not allowed
+    """
+    def __init__(self, msg):
+        self.message = msg
+
+    def __str__(self):
+        return repr(self.message)
+
+
+class Concern(InvalidData):
+    """E.g. if a password is not secure enough
+    """
+    def __init__(self, msg):
+        self.message = msg
+
+    def __str__(self):
+        return repr(self.message)
+
+
+class ContentRefreshRequest(Exception):
+    pass
diff --git a/scripts/tui/src/ovirt/node/plugins/example.py b/scripts/tui/src/ovirt/node/plugins/example.py
index dfa26e1..366a386 100644
--- a/scripts/tui/src/ovirt/node/plugins/example.py
+++ b/scripts/tui/src/ovirt/node/plugins/example.py
@@ -4,25 +4,85 @@
 import logging
 
 import ovirt.node.plugins
+import ovirt.node.pattern
+from ovirt.node.plugins import Header, Entry, Password
 
 LOGGER = logging.getLogger(__name__)
 
+
 class Plugin(ovirt.node.plugins.NodePlugin):
+    _model = None
+    _widgets = None
+
     def name(self):
         return os.path.basename(__file__)
 
+    def model(self):
+        """Returns the model of this plugin
+        This is expected to parse files and all stuff to build up the model.
+        """
+        if not self._model:
+            self._model = {
+                "foo.hostname": "example.com",
+                "foo.port": "8080",
+                "foo.password": "secret",
+            }
+        return self._model
+
+    def validators(self):
+        return {
+                "foo.hostname": ovirt.node.pattern.ValidHostname(),
+                "foo.port": ovirt.node.pattern.Number(),
+                "foo.password": lambda v: "No space allowed." \
+                                          if " " in v else None
+            }
+
     def ui_content(self):
-        widgets = []
-        widgets.append(ovirt.node.plugins.Label("Subsection"))
-        widgets.append(ovirt.node.plugins.Entry("foo.bar", label=__file__))
+        """Describes the UI this plugin requires
+        This is an ordered list of (path, widget) tuples.
+        """
+        widgets = [
+            ("foo.section", Header("Subsection")),
+            ("foo.hostname", Entry(label="Hostname")),
+            ("foo.port", Entry(label="Port")),
+            ("foo.password", Password(label="Password")),
+        ]
+        self._widgets = dict(widgets)
         return widgets
 
-    def ui_on_change(self, model):
-        """Called when some widget was changed
+    def on_change(self, changes):
+        """Applies the changes to the plugins model, will do all required logic
         """
-        LOGGER.debug("changed: " + str(model))
+        LOGGER.debug("checking %s" % changes)
+        if "foo.bar" in changes:
+            LOGGER.debug("Found foo.bar")
 
-    def ui_on_save(self, model):
-        """Called when data should be saved
+            if "/" in changes["foo.bar"]:
+                raise ovirt.node.plugins.InvalidData("No slash allowed")
+
+            if len(changes["foo.bar"]) < 5:
+                raise ovirt.node.plugins.Concern("Should be at least 5 chars")
+
+            self._model.update(changes)
+
+            if "dis" in changes["foo.bar"]:
+                self._widgets["foo.bar2"].enabled(False)
+                LOGGER.debug("change to dis")
+                #raise ovirt.node.plugins.ContentRefreshRequest()
+            else:
+                self._widgets["foo.bar2"].enabled(True)
+
+        if "foo.bar2" in changes:
+            LOGGER.debug("Found foo.bar2")
+
+            if "/" in changes["foo.bar2"]:
+                raise ovirt.node.plugins.InvalidData("No slashes allowed")
+
+        return True
+
+    def on_merge(self, changes):
+        """Applies the changes to the plugins model, will do all required logic
         """
-        LOGGER.debug("saved: " + str(model))
+        LOGGER.debug("saving %s" % changes)
+        # Look for conflicts etc
+        self._model.update(changes)
diff --git a/scripts/tui/src/ovirt/node/plugins/features.py b/scripts/tui/src/ovirt/node/plugins/features.py
index b0dd4f4..2c7ee69 100644
--- a/scripts/tui/src/ovirt/node/plugins/features.py
+++ b/scripts/tui/src/ovirt/node/plugins/features.py
@@ -1,9 +1,9 @@
 
 
-import os.path
 import logging
 
 import ovirt.node.plugins
+
 
 LOGGER = logging.getLogger(__name__)
 
@@ -14,11 +14,13 @@
 - Press <ESC>
 """
 
+
 class Plugin(ovirt.node.plugins.NodePlugin):
     def name(self):
         return "Features"
 
     def ui_content(self):
-        widgets = []
-        widgets.append(ovirt.node.plugins.Label(features))
+        widgets = [
+            ("features.info", ovirt.node.plugins.Label(features))
+        ]
         return widgets
diff --git a/scripts/tui/src/ovirt/node/plugins/usage.py b/scripts/tui/src/ovirt/node/plugins/usage.py
index afe149f..0131778 100644
--- a/scripts/tui/src/ovirt/node/plugins/usage.py
+++ b/scripts/tui/src/ovirt/node/plugins/usage.py
@@ -1,23 +1,28 @@
 
 
-import os.path
 import logging
 
 import ovirt.node.plugins
 
+
 LOGGER = logging.getLogger(__name__)
 
-usage = """Plugins need to be derived from a provided class and need to implement a couple of methods.
-Data is only passed via a dictionary between the UI and the plugin, this way it should be also easier to test plugins.
+usage = """Plugins need to be derived from a provided class and need to \
+implement a couple of methods.
+Data is only passed via a dictionary between the UI and the plugin, this way \
+it should be also easier to test plugins.
 
-The plugin (one python file) just needs to be dropped into a specififc directory to get picked up (ovirt/node/plugins/) and is  a python file.
+The plugin (one python file) just needs to be dropped into a specififc \
+directory to get picked up (ovirt/node/plugins/) and is  a python file.
 """
+
 
 class Plugin(ovirt.node.plugins.NodePlugin):
     def name(self):
         return "Usage"
 
     def ui_content(self):
-        widgets = []
-        widgets.append(ovirt.node.plugins.Label(usage))
+        widgets = [
+            ("usage.info", ovirt.node.plugins.Label(usage))
+        ]
         return widgets
diff --git a/scripts/tui/src/ovirt/node/tui.py b/scripts/tui/src/ovirt/node/tui.py
index 23dbb4c..691b16c 100644
--- a/scripts/tui/src/ovirt/node/tui.py
+++ b/scripts/tui/src/ovirt/node/tui.py
@@ -17,6 +17,7 @@
 class SelectableText(urwid.Text):
     def selectable(self):
         return True
+
     def keypress(self, size, key):
         return key
 
@@ -37,8 +38,12 @@
                ('reveal focus', 'white', 'light blue', 'standout'),
                ('main.menu', 'black', ''),
                ('main.menu.frame', 'light gray', ''),
-               ('plugin.entry', 'dark gray', ''),
-               ('plugin.entry.frame', 'light gray', ''),]
+               ('plugin.widget.entry', 'dark gray', ''),
+               ('plugin.widget.entry.frame', 'light gray', ''),
+               ('plugin.widget.disabled', 'light gray', 'dark gray'),
+               ('plugin.widget.notice', 'light red', ''),
+               ('plugin.widget.header', 'light blue', 'light gray'),
+               ]
 
     def __init__(self):
         pass
@@ -46,18 +51,22 @@
     def __build_pages_list(self):
         items = []
         for title, plugin in self.__pages.items():
-            item = SelectableText(title)
-            item.plugin = plugin
-            item = urwid.AttrMap(item, None, 'reveal focus')
-            items.append(item)
+            if plugin.has_ui():
+                item = SelectableText(title)
+                item.plugin = plugin
+                item = urwid.AttrMap(item, None, 'reveal focus')
+                items.append(item)
+            else:
+                LOGGER.info("No UI page for plugin %s" % plugin)
         walker = urwid.SimpleListWalker(items)
         self.__menu_list = urwid.ListBox(walker)
-        def __on_page_change():
+
+        def __on_item_change():
             widget, position = self.__menu_list.get_focus()
             plugin = widget.original_widget.plugin
-            page = self.__build_plugin_widget(plugin)
-            self.__page_frame.body = page
-        urwid.connect_signal(walker, 'modified', __on_page_change)
+            self.__change_to_page(plugin)
+
+        urwid.connect_signal(walker, 'modified', __on_item_change)
         attr_listbox = urwid.AttrMap(self.__menu_list, "main.menu")
         return attr_listbox
 
@@ -76,36 +85,105 @@
         footer = urwid.Text(self.footer, wrap='clip')
         return urwid.Frame(body, header, footer)
 
+    def __build_widget_for_item(self, plugin, path, item):
+        widget = None
+
+        if type(item) is ovirt.node.plugins.Entry or \
+            type(item) is ovirt.node.plugins.Password:
+            label_text = urwid.Text("\n" + item.label + ":")
+            label = label_text
+            mask = None
+            if type(item) is ovirt.node.plugins.Password:
+                mask = "*"
+            edit = urwid.Edit(mask=mask)
+            edit_attrmap = urwid.AttrMap(edit, "plugin.widget.entry")
+            linebox = urwid.LineBox(edit_attrmap)
+            linebox_attrmap = urwid.AttrMap(linebox,
+                                            "plugin.widget.entry.frame")
+            entry = linebox_attrmap
+            main_widget = urwid.Columns([label, entry])
+
+            notice_text = urwid.Text("")
+            notice_attrmap = urwid.AttrMap(notice_text, "plugin.widget.notice")
+            notice_widget = notice_attrmap
+
+            widget = urwid.Pile([main_widget, notice_widget])
+
+            if item.initial_value_from_model:
+                value = plugin.model()[path]
+                if value:
+                    edit.set_edit_text(value)
+
+            def on_change(widget, new_value):
+                LOGGER.debug("Widget content changed for path '%s'" % path)
+
+                try:
+                    if path in plugin.validators():
+                        msg = plugin.validators()[path](new_value)
+                        # True and None are allowed
+                        if msg not in [True, None]:
+                            raise ovirt.node.plugins.InvalidData(msg)
+
+                    plugin._on_ui_change({path: new_value})
+                    notice_text.set_text("")
+
+                except ovirt.node.plugins.Concern as e:
+                    LOGGER.error("Concern when updating: %s" % e)
+
+                except ovirt.node.plugins.InvalidData as e:
+                    notice_text.set_text(e.message)
+                    LOGGER.error("Invalid data when updating: %s" % e)
+
+            urwid.connect_signal(edit, 'change', on_change)
+
+            def foo(w, v):
+                if edit.selectable() == v:
+                    return True
+                else:
+                    edit.selectable = lambda: v
+                    LOGGER.debug("dissing")
+                    if v:
+                        edit_attrmap.set_attr_map({None: ""})
+                    else:
+                        edit_attrmap.set_attr_map({
+                            None: "plugin.widget.disabled"
+                            })
+            item.connect_signal("enabled[change]", foo)
+
+        elif type(item) is ovirt.node.plugins.Header:
+            label = urwid.Text("\n  %s\n" % item.label)
+            label_attrmap = urwid.AttrMap(label, "plugin.widget.header")
+            widget = label_attrmap
+
+        elif type(item) is ovirt.node.plugins.Label:
+            label = urwid.Text(item.label)
+            widget = urwid.AttrMap(label, "plugin.widget.label")
+
+        return widget
+
     def __build_plugin_widget(self, plugin):
         """This method is building the widget for a plugin
         """
         widgets = []
-        for item in plugin.ui_content():
-            widget = None
-            if type(item) is ovirt.node.plugins.Entry:
-                label = urwid.Filler(urwid.Text(item.label + ":"))
-                edit = urwid.Edit()
-                def on_change(a, b):
-                    plugin.ui_on_change({item.path: edit.get_text()[0]})
-                urwid.connect_signal(edit, 'change', on_change)
-                entry = edit
-                entry = urwid.AttrMap(entry, "plugin.entry")
-                entry = urwid.LineBox(entry)
-                entry = urwid.AttrMap(entry, "plugin.entry.frame")
-                entry = urwid.Filler(entry)
-                widget = urwid.Columns([label, entry])
-            elif type(item) is ovirt.node.plugins.Label:
-                widget = urwid.Filler(urwid.Text(item.label))
-                widget = urwid.AttrMap(widget, "plugin.label")
-            widgets.append(widget)
 
-        save = urwid.Button("Save", self.popup) #plugin.ui_on_save)
+        for path, item in plugin.ui_content():
+            widget = self.__build_widget_for_item(plugin, path, item)
+            widgets.append(("flow", widget))
+
+        save = urwid.Button("Save", lambda x: plugin._on_ui_save())
         save = urwid.Padding(save, "left", width=8)
-        save = urwid.Filler(save, ("fixed bottom", 1))
+        save = urwid.Filler(save, ("fixed top", 1))
         widgets.append(save)
 
-        widget = urwid.Pile(widgets)
+        pile = urwid.Pile(widgets)
+        # FIXME why is this fixed?
+        widget = urwid.Filler(pile, ("fixed top", 1), height=20)
         return widget
+
+    def __change_to_page(self, plugin):
+        plugin_widget = self.__build_plugin_widget(plugin)
+        page = plugin_widget
+        self.__page_frame.body = page
 
     def __filter_hotkeys(self, keys, raw):
         key = str(keys)
@@ -120,11 +198,17 @@
 
     def popup(self, msg=None, buttons=None):
         LOGGER.debug("Launching popup")
+
         class Dialog(urwid.PopUpLauncher):
+
             def create_pop_up(self):
                 return urwid.Filler(urwid.Text("Fooo"))
+
             def get_pop_up_parameters(self):
-                return {'left':0, 'top':1, 'overlay_width':30, 'overlay_height':4}
+                return {'left': 0,
+                        'top': 1,
+                        'overlay_width': 30,
+                        'overlay_height': 4}
         dialog = Dialog(self.__page_frame)
         dialog.open_pop_up()
 
@@ -135,9 +219,11 @@
         class SuspendedScreen(object):
             def __init__(self, loop):
                 self.__loop = loop
+
             def __enter__(self):
                 self.screen = urwid.raw_display.Screen()
                 self.screen.stop()
+
             def __exit__(self, a, b, c):
                 self.screen.start()
                 # Hack to force a screen refresh
@@ -187,7 +273,7 @@
         self.plugins = [m.Plugin() for m in ovirt.node.plugins.load_all()]
 
         for plugin in self.plugins:
-            LOGGER.debug("Adding plugin " + plugin.name())
+            LOGGER.debug("Adding plugin %s" % plugin)
             self.ui.register_plugin(plugin.ui_name(), plugin)
 
     def __drop_to_shell(self):


--
To view, visit http://gerrit.ovirt.org/9863
To unsubscribe, visit http://gerrit.ovirt.org/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Ic8183b700f46c25bc7a4e03c20e5adf76d8935b9
Gerrit-PatchSet: 1
Gerrit-Project: ovirt-node
Gerrit-Branch: master
Gerrit-Owner: Fabian Deutsch <fabiand at fedoraproject.org>



More information about the node-patches mailing list