[PATCH 1/2] Move the add_check stuff into helper classes.
David Shea
dshea at redhat.com
Tue Jan 7 15:32:28 UTC 2014
This moves the input checking code out of GUIObject and NormalSpoke and
kills the GUIDialog class. Split the functionality into three classes:
one without anything GUI-specific, one which adds some functionality for
working with GtkEditable widgets, and one which adds some functionality
for working with GUI spokes.
---
pyanaconda/ui/gui/__init__.py | 277 -----------------------------------
pyanaconda/ui/gui/spokes/__init__.py | 7 -
pyanaconda/ui/gui/spokes/password.py | 53 ++++---
pyanaconda/ui/gui/spokes/user.py | 106 +++++++-------
pyanaconda/ui/helpers.py | 234 ++++++++++++++++++++++++++++-
5 files changed, 313 insertions(+), 364 deletions(-)
diff --git a/pyanaconda/ui/gui/__init__.py b/pyanaconda/ui/gui/__init__.py
index e2e0cfc..38f35cf 100644
--- a/pyanaconda/ui/gui/__init__.py
+++ b/pyanaconda/ui/gui/__init__.py
@@ -42,96 +42,6 @@ SCREENSHOT_DELAY = 1 # in seconds
ANACONDA_WINDOW_GROUP = Gtk.WindowGroup()
-class GUICheck(object):
- """Handle an input validation check."""
-
- # Use as a return value to indicate a passed check
- CHECK_OK = None
-
- def __init__(self, parent, editable, run_check, check_data, set_error):
- """Create a new input validation check.
-
- :param parent: The parent GUIObject. When a check state changes,
- the GUICheck will call set_error(check, check-state)
- :type parent: GUIObject
-
- :param editable: The input field being checked
- :type editable: GtkEditable
-
- :param run_check: The check function. The function is called as
- check(editable, check_data). The return value is an
- error state object or CHECK_OK if the check succeeds.
- :type run_check: function
-
- :param check_data: An optional parameter passed to check().
-
- :param set_error: A function called when the state of this check
- changes. The parameters are (GUICheck, run_check_return).
- The return value is ignored.
- :type set_error: function
- """
-
- self._parent = parent
- self._editable = editable
- self._run_check = run_check
- self._check_data = check_data
- self._set_error = set_error
-
- # Set to the Gtk handler ID in enable()
- self._handler_id = None
-
- # Initial check state
- self._check_status = None
-
- self.enable()
-
- def enable(self):
- """Enable the check.
-
- enable() does not check the current state of the input field. To
- check the current state, run update_check_status() after enable().
- """
- if not self._handler_id:
- self._handler_id = self._editable.connect_after("changed", self.update_check_status)
-
- def disable(self):
- """Disable the check. The check will no longer appear in failed_checks,
- but disabling the check does not call set_error to update the
- GUIObject's state.
- """
- if self._handler_id:
- self._editable.disconnect(self._handler_id)
- self._handler_id = None
- self._check_status = None
-
- def update_check_status(self, editable=None, check_data=None):
- """Run an input validation check."""
-
- # Allow check parameters to be overriden in parameters
- if editable is None:
- editable = self._editable
- if check_data is None:
- check_data = self._check_data
-
- new_check_status = self._run_check(editable, check_data)
- check_status_changed = (self._check_status != new_check_status)
- self._check_status = new_check_status
-
- if check_status_changed:
- self._set_error(self, self._check_status)
-
- @property
- def check_status(self):
- return self._check_status
-
- @property
- def editable(self):
- return self._editable
-
- @property
- def check_data(self):
- return self._check_data
-
class GUIObject(common.UIObject):
"""This is the base class from which all other GUI classes are derived. It
thus contains only attributes and methods that are common to everything
@@ -214,8 +124,6 @@ class GUIObject(common.UIObject):
Keybinder.init()
Keybinder.bind("<Shift>Print", self._handlePrntScreen, [])
- self._check_list = []
-
def _findUIFile(self):
path = os.environ.get("UIPATH", "./:/tmp/updates/:/tmp/updates/ui/:/usr/share/anaconda/ui/")
dirs = path.split(":")
@@ -294,173 +202,6 @@ class GUIObject(common.UIObject):
"""
self.window.set_warning(msg)
- def add_check(self, editable, run_check, check_data=None, set_error=None):
- """Add an input validation check to this object.
-
- This function creates new GUICheck object and adds it to this
- GUIObject. The check is run any time the input field changes.
- If the result of a check changes, the check object will call
- the set_error function. By default, set_error will call
- self.set_warning with the status of the first failed check.
-
- :param editable: the input field to validate
- :type editable: GtkEditable
-
- :param run_check: a function called to validate the input field. The
- parameters are (editable, check_data). The return
- value is an error state object or GUICheck.CHECK_OK if
- the check passes.
- :type run_check: function
-
- :param check_data: additional data to pass to the check function
-
- :param set_error: a function called when a check changes state. The
- parameters are (GUICheck, run_check_return). The
- return value is ignored.
- :type set_error: function
-
- :returns: A check object
- :rtype: GUICheck
- """
-
- if not set_error:
- set_error = self.set_check_error
-
- checkRef = GUICheck(self, editable, run_check, check_data, set_error)
- self._check_list.append(checkRef)
- return checkRef
-
- def add_re_check(self, editable, regex, message, set_error=None):
- """Add a check using a regular expresion.
-
- :param editable: the input field to validate
- :type editable: GtkEditable
-
- :param regex: the regular expression to use to check the input
- :type regex: re.RegexObject
-
- :param message: The message to set if the regex does not match
- :type message: str
-
- :param set_error: a function called when a check changes state. The
- parameters are (GUICheck, run_check_return). The
- return value is ignored.
- :type set_error: function
-
- :returns: A check object
- :rtype: GUICheck
- """
- if not set_error:
- set_error = self.set_check_error
- return self.add_check(editable=editable, run_check=check_re,
- check_data={'regex': regex, 'message': message}, set_error=set_error)
-
- def set_check_error(self, check, check_return):
- """Update the warning with the input validation check error."""
- # Grab the first failed check
- failed_check = next(self.failed_checks, None)
-
- self.clear_info()
- if failed_check:
- self.set_warning(failed_check.check_status)
- self.window.show_all()
-
- @property
- def failed_checks(self):
- """A generator of all failed input checks"""
- return (c for c in self._check_list if c.check_status)
-
- @property
- def checks(self):
- """An iterator over all input checks"""
- return self._check_list.__iter__()
-
-class GUIDialog(GUIObject):
- """This is an abstract for creating dialog windows. It displays an error
- message when an input validation fails.
-
- GUIDialog does not define where errors are displayed, so classes
- that derive from GUIDialog must define error labels and include them
- as the check_data parameter to add_check. More than one check can use
- the same label: the message from the first failed check will update the
- label.
- """
-
- def __init__(self, data):
- if self.__class__ is GUIDialog:
- raise TypeError("GUIDialog is an abstract class")
-
- GUIObject.__init__(self, data)
-
- def add_check_with_error_label(self, editable, error_label, run_check,
- check_data=None, set_error=None):
- """Add an input validation check to this dialog. The error_label will
- be added to the check_data for the validation check and will be
- used to display the error message if the check fails.
-
- :param editable: the input field to validate
- :type editable: GtkEditable
-
- :param error_label: the label in which to display the error data
- :type error_label: GtkLabel
-
- :param run_check: a function called to validate the input field. The
- parameters are (editable, check_data). The return
- value is an error state object or GUICheck.CHECK_OK if
- the check passes.
- :type run_check: function
-
- :param check_data: additional data to pass to the check function
-
- :param set_error: a function called when a check changes state. The
- parameters are (GUICheck, run_check_return). The
- return value is ignored.
- :type set_error: function
-
- :returns: A check object
- :rtype: GUICheck
- """
- if not set_error:
- set_error = self.set_check_error
-
- return self.add_check(editable=editable, run_check=run_check,
- check_data={'error_label': error_label, 'message': check_data},
- set_error=set_error)
-
- def add_re_check_with_error_label(self, editable, error_label, regex, message, set_error=None):
- """Add a check using a regular expression."""
- # Use the GUIObject function so we can create the check_data dictionary here
- if not set_error:
- set_error = self.set_check_error
-
- return self.add_check(editable=editable, run_check=check_re,
- check_data={'error_label': error_label, 'message': message, 'regex': regex},
- set_error=set_error)
-
- def set_check_error(self, check, check_return):
- """Update all input check failure messages.
-
- If multiple checks use the same GtkLabel, only the first one will
- be used.
- """
-
- # If the signaling check passed, clear its error label
- if not check_return:
- if 'error_label' in check.check_data:
- check.check_data['error_label'].set_text('')
-
- # Keep track of which labels have errors set. If we see an error for
- # a label that's already been set, skip it.
- labels_seen = []
- for failed_check in self.failed_checks:
- if not 'error_label' in failed_check.check_data:
- continue
-
- label = failed_check.check_data['error_label']
- if label not in labels_seen:
- labels_seen.append(label)
- label.set_text(failed_check.check_status)
-
class QuitDialog(GUIObject):
builderObjects = ["quitDialog"]
mainWidgetName = "quitDialog"
@@ -789,21 +530,3 @@ class GraphicalExceptionHandlingIface(meh.ui.gui.GraphicalIntf):
unbusyCursor()
return exc_window
-
-def check_re(editable, data):
- """Perform an input validation check against a regular expression.
-
- :param editable: The input field being checked
- :type editable: GtkEditable
-
- :param data: The check_data set in add_check. This data must
- be a dictionary that includes the keys
- 'regex' and 'message'.
- :type data: dict
-
- :returns: error_data if the check fails, otherwise GUICheck.CHECK_OK.
- """
- if data['regex'].match(editable.get_text()):
- return GUICheck.CHECK_OK
- else:
- return data['message']
diff --git a/pyanaconda/ui/gui/spokes/__init__.py b/pyanaconda/ui/gui/spokes/__init__.py
index cc91ef6..0cd44b3 100644
--- a/pyanaconda/ui/gui/spokes/__init__.py
+++ b/pyanaconda/ui/gui/spokes/__init__.py
@@ -91,13 +91,6 @@ class NormalSpoke(Spoke, common.NormalSpoke):
def on_back_clicked(self, window):
from gi.repository import Gtk
- # Look for failed checks
- failed_check = next(self.failed_checks, None)
- if failed_check:
- # Set the focus to the first failed check and stay in the spoke
- failed_check.editable.grab_focus()
- return
-
self.window.hide()
Gtk.main_quit()
diff --git a/pyanaconda/ui/gui/spokes/password.py b/pyanaconda/ui/gui/spokes/password.py
index b66e2d5..0246643 100644
--- a/pyanaconda/ui/gui/spokes/password.py
+++ b/pyanaconda/ui/gui/spokes/password.py
@@ -22,10 +22,10 @@
from pyanaconda.i18n import _, CN_
from pyanaconda.users import cryptPassword, validatePassword
-from pyanaconda.ui.gui import GUICheck
from pyanaconda.ui.gui.spokes import NormalSpoke
from pyanaconda.ui.gui.categories.user_settings import UserSettingsCategory
from pyanaconda.ui.common import FirstbootSpokeMixIn
+from pyanaconda.ui.helpers import GUISpokeInputCheckHandler, InputCheck
from pyanaconda.constants import PASSWORD_EMPTY_ERROR, PASSWORD_CONFIRM_ERROR_GUI,\
PASSWORD_STRENGTH_DESC, PASSWORD_WEAK, PASSWORD_WEAK_WITH_ERROR,\
@@ -34,7 +34,7 @@ from pyanaconda.constants import PASSWORD_EMPTY_ERROR, PASSWORD_CONFIRM_ERROR_GU
__all__ = ["PasswordSpoke"]
-class PasswordSpoke(FirstbootSpokeMixIn, NormalSpoke):
+class PasswordSpoke(FirstbootSpokeMixIn, NormalSpoke, GUISpokeInputCheckHandler):
builderObjects = ["passwordWindow"]
mainWidgetName = "passwordWindow"
@@ -47,6 +47,7 @@ class PasswordSpoke(FirstbootSpokeMixIn, NormalSpoke):
def __init__(self, *args):
NormalSpoke.__init__(self, *args)
+ GUISpokeInputCheckHandler.__init__(self)
self._kickstarted = False
def initialize(self):
@@ -100,7 +101,7 @@ class PasswordSpoke(FirstbootSpokeMixIn, NormalSpoke):
def refresh(self):
# Enable the input checks in case they were disabled on the last exit
for check in self.checks:
- check.enable()
+ check.enabled = True
self.pw.grab_focus()
self.pw.emit("changed")
@@ -140,47 +141,44 @@ class PasswordSpoke(FirstbootSpokeMixIn, NormalSpoke):
def completed(self):
return bool(self.data.rootpw.password or self.data.rootpw.lock)
- def _checkPasswordEmpty(self, editable, data):
+ def _checkPasswordEmpty(self, inputcheck):
"""Check whether a password has been specified at all."""
# If the password was set by kickstart, skip this check
if self._kickstarted:
- return GUICheck.CHECK_OK
+ return InputCheck.CHECK_OK
- if not editable.get_text():
- if editable == self.pw:
+ if not self.get_input(inputcheck.input_obj):
+ if inputcheck.input_obj == self.pw:
return _(PASSWORD_EMPTY_ERROR)
else:
return _(PASSWORD_CONFIRM_ERROR_GUI)
else:
- return GUICheck.CHECK_OK
+ return InputCheck.CHECK_OK
- def _checkPasswordConfirm(self, editable=None, reset_status=None):
+ def _checkPasswordConfirm(self, inputcheck):
"""Check whether the password matches the confirmation data."""
- # This check is triggered by changes to either the password field or the
- # confirmation field. If this method is being run from a successful check
- # to reset the status, just return success
- if reset_status:
- return GUICheck.CHECK_OK
-
pw = self.pw.get_text()
confirm = self.confirm.get_text()
# Skip the check if no password is required
if (not pw and not confirm) and self._kickstarted:
- result = GUICheck.CHECK_OK
+ result = InputCheck.CHECK_OK
elif confirm and (pw != confirm):
result = _(PASSWORD_CONFIRM_ERROR_GUI)
else:
- result = GUICheck.CHECK_OK
+ result = InputCheck.CHECK_OK
# If the check succeeded, reset the status of the other check object
- if result == GUICheck.CHECK_OK:
- if editable == self.confirm:
- self._password_check.update_check_status(check_data=True)
+ # Disable the current check to prevent a cycle
+ inputcheck.enabled = False
+ if result == InputCheck.CHECK_OK:
+ if inputcheck == self._confirm_check:
+ self._password_check.update_check_status()
else:
- self._confirm_check.update_check_status(check_data=True)
+ self._confirm_check.update_check_status()
+ inputcheck.enabled = True
return result
@@ -213,7 +211,7 @@ class PasswordSpoke(FirstbootSpokeMixIn, NormalSpoke):
self.pw_bar.set_value(val)
self.pw_label.set_text(text)
- def _checkPasswordStrength(self, editable=None, data=None):
+ def _checkPasswordStrength(self, inputcheck):
"""Update the error message based on password strength.
Convert the strength set by _updatePwQuality into an error message.
@@ -224,7 +222,7 @@ class PasswordSpoke(FirstbootSpokeMixIn, NormalSpoke):
# Skip the check if no password is required
if (not pw and not confirm) and self._kickstarted:
- return GUICheck.CHECK_OK
+ return InputCheck.CHECK_OK
# Check for validity errors
if (not self._pwq_valid) and (self._pwq_error):
@@ -235,7 +233,7 @@ class PasswordSpoke(FirstbootSpokeMixIn, NormalSpoke):
if pwstrength < 2:
# If Done has been clicked twice, waive the check
if self._waivePasswordClicks > 1:
- return GUICheck.CHECK_OK
+ return InputCheck.CHECK_OK
elif self._waivePasswordClicks == 1:
if self._pwq_error:
return _(PASSWORD_WEAK_CONFIRM_WITH_ERROR) % self._pwq_error
@@ -247,7 +245,7 @@ class PasswordSpoke(FirstbootSpokeMixIn, NormalSpoke):
else:
return _(PASSWORD_WEAK)
else:
- return GUICheck.CHECK_OK
+ return InputCheck.CHECK_OK
def on_back_clicked(self, button):
# Add a click and re-check the password strength
@@ -257,6 +255,7 @@ class PasswordSpoke(FirstbootSpokeMixIn, NormalSpoke):
# If neither the password nor the confirm field are set, skip the checks
if (not self.pw.get_text()) and (not self.confirm.get_text()):
for check in self.checks:
- check.disable()
+ check.enabled = False
- NormalSpoke.on_back_clicked(self, button)
+ if GUISpokeInputCheckHandler.on_back_clicked(self, button):
+ NormalSpoke.on_back_clicked(self, button)
diff --git a/pyanaconda/ui/gui/spokes/user.py b/pyanaconda/ui/gui/spokes/user.py
index beaeae9..aeb5a7a 100644
--- a/pyanaconda/ui/gui/spokes/user.py
+++ b/pyanaconda/ui/gui/spokes/user.py
@@ -19,14 +19,17 @@
# Red Hat Author(s): Martin Sivak <msivak at redhat.com>
#
+import re
+
from pyanaconda.i18n import _, CN_
from pyanaconda.users import cryptPassword, validatePassword, guess_username
from pyanaconda.ui.gui.spokes import NormalSpoke
-from pyanaconda.ui.gui import GUIObject, GUIDialog, check_re, GUICheck
+from pyanaconda.ui.gui import GUIObject
from pyanaconda.ui.gui.categories.user_settings import UserSettingsCategory
from pyanaconda.ui.common import FirstbootSpokeMixIn
from pyanaconda.ui.gui.utils import enlightbox
+from pyanaconda.ui.helpers import GUISpokeInputCheckHandler, GUIInputCheckHandler, InputCheck
from pykickstart.constants import FIRSTBOOT_RECONFIG
from pyanaconda.constants import ANACONDA_ENVIRON, FIRSTBOOT_ENVIRON,\
@@ -37,31 +40,32 @@ from pyanaconda.regexes import GECOS_VALID, USERNAME_VALID, GROUPNAME_VALID, GRO
__all__ = ["UserSpoke", "AdvancedUserDialog"]
-def _checkUsername(editable, data):
- """Validate a username. Allow empty usernames."""
- if not (editable.get_text()):
- return GUICheck.CHECK_OK
- else:
- return check_re(editable, data)
-
-def _validateGroups(editable, data):
- groups_list = editable.get_text().split(",")
-
- # Check each group name in the list
- for group in groups_list:
- group_name = GROUPLIST_FANCY_PARSE.match(group).group('name')
- if not GROUPNAME_VALID.match(group_name):
- return _("Invalid group name: %s") % group_name
-
- return GUICheck.CHECK_OK
-
-class AdvancedUserDialog(GUIDialog):
+class AdvancedUserDialog(GUIObject, GUIInputCheckHandler):
builderObjects = ["advancedUserDialog", "uid", "gid"]
mainWidgetName = "advancedUserDialog"
uiFile = "spokes/advanced_user.glade"
+ def set_status(self, inputcheck):
+ # Set or clear the groups error label based on the check status
+ if inputcheck.check_status == InputCheck.CHECK_OK:
+ self._groupsError.set_text('')
+ else:
+ self._groupsError.set_text(inputcheck.check_status)
+
+ def _validateGroups(self, inputcheck):
+ groups_list = self.get_input(inputcheck.input_obj).split(",")
+
+ # Check each group name in the list
+ for group in groups_list:
+ group_name = GROUPLIST_FANCY_PARSE.match(group).group('name')
+ if not GROUPNAME_VALID.match(group_name):
+ return _("Invalid group name: %s") % group_name
+
+ return InputCheck.CHECK_OK
+
def __init__(self, user, groupDict, data):
- GUIDialog.__init__(self, data)
+ GUIObject.__init__(self, data)
+ GUIInputCheckHandler.__init__(self)
self._user = user
self._groupDict = groupDict
@@ -85,9 +89,7 @@ class AdvancedUserDialog(GUIDialog):
self._grabObjects()
# Validate the group input box
- self.add_check_with_error_label(editable=self._tGroups,
- error_label=self._groupsError,
- run_check=_validateGroups)
+ self.add_check(self._tGroups, self._validateGroups)
def _apply_checkboxes(self, _editable = None, data = None):
"""Update the state of this screen according to the
@@ -184,7 +186,7 @@ class AdvancedUserDialog(GUIDialog):
return rc
-class UserSpoke(FirstbootSpokeMixIn, NormalSpoke):
+class UserSpoke(FirstbootSpokeMixIn, NormalSpoke, GUISpokeInputCheckHandler):
builderObjects = ["userCreationWindow"]
mainWidgetName = "userCreationWindow"
@@ -214,6 +216,7 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke):
def __init__(self, *args):
NormalSpoke.__init__(self, *args)
+ GUISpokeInputCheckHandler.__init__(self)
self._oldweak = None
def initialize(self):
@@ -290,8 +293,8 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke):
self.add_check(self.confirm, self._checkPasswordEmpty)
# Allow empty usernames so the spoke can be exited without creating a user
- self.add_check(self.username, _checkUsername,
- {'regex': USERNAME_VALID, 'message': _("Invalid username")})
+ self.add_re_check(self.username, re.compile(USERNAME_VALID.pattern + r'|^$'),
+ _("Invalid username"))
self.add_re_check(self.fullname, GECOS_VALID, _("Full name cannot contain colon characters"))
@@ -302,7 +305,7 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke):
def refresh(self):
# Enable the input checks in case they were disabled on the last exit
for check in self.checks:
- check.enable()
+ check.enabled = True
self.username.set_text(self._user.name)
self.fullname.set_text(self._user.gecos)
@@ -461,7 +464,7 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke):
self.username.set_text(username)
self.guesser[self.username] = True
- def _checkPasswordEmpty(self, editable, data):
+ def _checkPasswordEmpty(self, inputcheck):
"""Check whether a password has been specified at all.
This check is used for both the password and the confirmation.
@@ -469,46 +472,43 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke):
# If the password was set by kickstart, skip the strength check
if self._user.password_kickstarted:
- return GUICheck.CHECK_OK
+ return InputCheck.CHECK_OK
# Skip the check if no password is required
if (not self.usepassword.get_active()) or self._user.password_kickstarted:
- return GUICheck.CHECK_OK
- elif not editable.get_text():
- if editable == self.pw:
+ return InputCheck.CHECK_OK
+ elif not self.get_input(inputcheck.input_obj):
+ if inputcheck.input_obj == self.pw:
return _(PASSWORD_EMPTY_ERROR)
else:
return _(PASSWORD_CONFIRM_ERROR_GUI)
else:
- return GUICheck.CHECK_OK
+ return InputCheck.CHECK_OK
- def _checkPasswordConfirm(self, editable=None, reset_status=None):
+ def _checkPasswordConfirm(self, inputcheck):
"""If the user has entered confirmation data, check whether it matches the password."""
- # This check is triggered by changes to either the password field or the
- # confirmation field. If this method is being run from a successful check
- # to reset the status, just return success
- if reset_status:
- return GUICheck.CHECK_OK
-
# Skip the check if no password is required
if (not self.usepassword.get_active()) or self._user.password_kickstarted:
- result = GUICheck.CHECK_OK
+ result = InputCheck.CHECK_OK
elif self.confirm.get_text() and (self.pw.get_text() != self.confirm.get_text()):
result = _(PASSWORD_CONFIRM_ERROR_GUI)
else:
- result = GUICheck.CHECK_OK
+ result = InputCheck.CHECK_OK
# If the check succeeded, reset the status of the other check object
- if result == GUICheck.CHECK_OK:
- if editable == self.confirm:
- self._password_check.update_check_status(check_data=True)
+ # Disable the current check to prevent a cycle
+ inputcheck.enabled = False
+ if result == InputCheck.CHECK_OK:
+ if inputcheck == self._confirm_check:
+ self._password_check.update_check_status()
else:
- self._confirm_check.update_check_status(check_data=True)
+ self._confirm_check.update_check_status()
+ inputcheck.enabled = True
return result
- def _checkPasswordStrength(self, editable=None, data=None):
+ def _checkPasswordStrength(self, inputcheck):
"""Update the error message based on password strength.
The password strength has already been checked in _updatePwQuality, called
@@ -523,7 +523,7 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke):
# Skip the check if no password is required
if (not self.usepassword.get_active()) or \
((not self.pw.get_text()) and (self._user.password_kickstarted)):
- return GUICheck.CHECK_OK
+ return InputCheck.CHECK_OK
# If the password failed the validity check, fail this check
if (not self._pwq_valid) and (self._pwq_error):
@@ -534,7 +534,7 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke):
if pwstrength < 2:
# If Done has been clicked twice, waive the check
if self._waivePasswordClicks > 1:
- return GUICheck.CHECK_OK
+ return InputCheck.CHECK_OK
elif self._waivePasswordClicks == 1:
if self._pwq_error:
return _(PASSWORD_WEAK_CONFIRM_WITH_ERROR) % self._pwq_error
@@ -546,7 +546,7 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke):
else:
return _(PASSWORD_WEAK)
else:
- return GUICheck.CHECK_OK
+ return InputCheck.CHECK_OK
def on_advanced_clicked(self, _button, data=None):
"""Handler for the Advanced.. button. It starts the Advanced dialog
@@ -577,5 +577,7 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke):
if not self.username.get_text():
for check in self.checks:
check.disable()
- NormalSpoke.on_back_clicked(self, button)
+
+ if GUISpokeInputCheckHandler.on_back_clicked(self, button):
+ NormalSpoke.on_back_clicked(self, button)
diff --git a/pyanaconda/ui/helpers.py b/pyanaconda/ui/helpers.py
index b517412..eee4d40 100644
--- a/pyanaconda/ui/helpers.py
+++ b/pyanaconda/ui/helpers.py
@@ -55,7 +55,7 @@
# Mixin.data, so UIObject.data satisfies the requirment that Mixin.data be
# overriden.
-from abc import ABCMeta, abstractproperty
+from abc import ABCMeta, abstractproperty, abstractmethod
from pyanaconda import constants
from pyanaconda.threads import threadMgr, AnacondaThread
@@ -188,3 +188,235 @@ class SourceSwitchHandler(object):
self._clean_hdd_iso()
self.data.method.method = None
+
+class InputCheck(object):
+ """Handle an input validation check.
+
+ This class is used by classes that implement InputCheckHandler to
+ manage and manipulate input validation check instances.
+ """
+
+ # Use as a return value to indicate a passed check
+ CHECK_OK = None
+
+ # Read-only properties
+ input_obj = property(lambda s: s._input_obj,
+ doc="The input to check.")
+ run_check = property(lambda s: s._run_check,
+ doc="A function to call to perform the input check.")
+ data = property(lambda s: s._data,
+ doc="Optional data associated with the input check.")
+ set_status = property(lambda s: s._set_status,
+ doc="A function called when the status changes.")
+ check_status = property(lambda s: s._check_status,
+ doc="The current status of the check")
+
+ def __init__(self, parent, input_obj, run_check, data=None):
+ """Create a new input validation check.
+
+ :param InputCheckHandler parent: The InputCheckHandler object to which this
+ check is being added.
+
+ :param function input_obj: An object representing the input to check.
+
+ :param function run_check: A function to call to perform the input check. This
+ function is called with the InputCheck object as a
+ parameter. The return value an object representing
+ the error state, or CHECK_OK if the check succeeds.
+
+ :param data: Optional data associated with the input check
+ """
+ self._parent = parent
+ self._input_obj = input_obj
+ self._run_check = run_check
+ self._data = data
+ self._check_status = None
+ self._enabled = True
+
+ def update_check_status(self):
+ """Run an input validation check."""
+ if not self.enabled:
+ return
+
+ new_check_status = self._run_check(self)
+ check_status_changed = (self.check_status != new_check_status)
+ self._check_status = new_check_status
+
+ if check_status_changed:
+ self._parent.set_status(self)
+
+ @property
+ def enabled(self):
+ return self._enabled
+
+ @enabled.setter
+ def enabled(self, value):
+ self._enabled = value
+
+ # If disabling the check, clear the status
+ if not value:
+ self._check_status = None
+
+class InputCheckHandler(object):
+ """Provide a framework for adding input validation checks to a screen.
+
+ This helper class provides a mean of defining and associating input
+ validation checks with an input screen. Running the checks and acting
+ upon the results is left up to the subclasses. Classes implementing
+ InputCheckHandler should ensure that the checks are run at the
+ appropriate times (e.g., calling InputCheck.update_check_status when
+ input is changed), and that input for the screen is not accepted if
+ self.failed_checks is not empty.
+
+ See GUIInputCheckHandler and GUISpokeInputCheckHandler for additional
+ functionality.
+ """
+
+ __metaclass__ = ABCMeta
+
+ def __init__(self):
+ self._check_list = []
+
+ def _check_re(self, inputcheck):
+ """Perform an input validation check against a regular expression."""
+ if inputcheck.data['regex'].match(self.get_input(inputcheck.input_obj)):
+ return inputcheck.CHECK_OK
+ else:
+ return inputcheck.data['message']
+
+ @abstractmethod
+ def get_input(self, input_obj):
+ """Return the input string from an input object.
+
+ :param input_obj: The input object
+
+ :returns: An input string
+ :rtype: str
+ """
+ pass
+
+ @abstractmethod
+ def set_status(self, inputcheck):
+ """Update the status of the window from the input validation results.
+
+ This function could, for example, set or clear an error on the window,
+ or display a message near an input area with invalid data.
+
+ :param InputCheck inputcheck: The InputCheck object whose status last changed.
+ """
+ pass
+
+ def add_check(self, input_obj, run_check, data=None):
+
+ """Add an input validation check to this object.
+
+ :param input_obj: An object representing the input to check.
+
+ :param function run_check: A function to call to perform the input check. This
+ function is called with the InputCheck object as a
+ parameter. The return value an object representing
+ the error state, or CHECK_OK if the check succeeds.
+
+ :param data: Optional data associated with the input check
+
+ :returns: The InputCheck object created.
+ :rtype: InputCheck
+ """
+ checkRef = InputCheck(self, input_obj, run_check, data)
+ self._check_list.append(checkRef)
+ return checkRef
+
+ def add_re_check(self, input_obj, regex, message):
+ """Add a check using a regular expression.
+
+ :param function input_obj: An object representing the input to check.
+
+ :param re.RegexObject regex: The regular expression to check input against.
+
+ :param str message: A message to return for failed checks
+
+ :returns: The InputCheck object created.
+ :rtype: InputCheck
+ """
+ return self.add_check(input_obj=input_obj, run_check=self._check_re,
+ data={'regex': regex, 'message': message})
+
+ @property
+ def failed_checks(self):
+ """A generator of all failed input checks"""
+ return (c for c in self._check_list if c.check_status != InputCheck.CHECK_OK)
+
+ @property
+ def checks(self):
+ """An iterator over all input checks"""
+ return self._check_list.__iter__()
+
+# pylint: disable-msg=W0223
+class GUIInputCheckHandler(InputCheckHandler):
+ """Provide InputCheckHandler functionality for Gtk input screens.
+
+ This class assumes that all input objects are of type GtkEditable and
+ attaches InputCheck.update_check_status to the changed signal.
+ """
+
+ __metaclass__ = ABCMeta
+
+ def _update_check_status(self, editable, inputcheck):
+ inputcheck.update_check_status()
+
+ def get_input(self, input_obj):
+ return input_obj.get_text()
+
+ def add_check(self, input_obj, run_check, data=None):
+ checkRef = InputCheckHandler.add_check(self, input_obj, run_check, data)
+ input_obj.connect_after("changed", self._update_check_status, checkRef)
+ return checkRef
+
+class GUISpokeInputCheckHandler(GUIInputCheckHandler):
+ """Provide InputCheckHandler functionality for graphical spokes.
+
+ This class implements set_status to set a message in the warning area of
+ the spoke window and provides an implementation of on_back_clicked to
+ prevent the user from exiting a spoke with bad input.
+ """
+
+ __metaclass__ = ABCMeta
+
+ def set_status(self, inputcheck):
+ """Update the warning with the input validation error from the first
+ failed check.
+ """
+ failed_check = next(self.failed_checks, None)
+
+ self.clear_info()
+ if failed_check:
+ self.set_warning(failed_check.check_status)
+ self.window.show_all()
+
+ @abstractmethod
+ def clear_info(self):
+ pass
+
+ @abstractmethod
+ def set_warning(self, msg):
+ pass
+
+ @abstractproperty
+ def window(self):
+ pass
+
+ @abstractmethod
+ def on_back_clicked(self, window):
+ """Check whether the input validation checks allow the spoke to be exited.
+
+ Unlike NormalSpoke.on_back_clicked, this function returns a boolean value.
+ Classes implementing this class should run GUISpokeInputCheckHandler.on_back_clicked,
+ and if it succeeded, run NormalSpoke.on_back_clicked.
+ """
+ failed_check = next(self.failed_checks, None)
+
+ if failed_check:
+ failed_check.input_obj.grab_focus()
+ return False
+ else:
+ return True
--
1.8.5.2
More information about the anaconda-patches
mailing list