[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