tui
directory as described in Section 5, “Anaconda Add-on Structure”.
simpleline
utility, which only allows very simple user interaction. It does not support cursor movement (instead acting like a line printer) nor any visual enhancements like using different colors or fonts.
simpleline
toolkit: App
, UIScreen
and Widget
. Widgets, which are units containing information to be shown (printed) on the screen, are placed on UIScreens which are switched by a single instance of the App
class. On top of the basic elements, there are hubs, spokes and dialogs, all containing various widgets in a way similar to the graphical interface.
NormalTUISpoke
and various other classes defined in the pyanaconda.ui.tui.spokes
package. All of those classes are based on the TUIObject
class, which itself is an equivalent of the GUIObject
class discussed in the previous chapter. Each TUI spoke is a Python class inheriting from the NormalTUISpoke
class, overriding special arguments and methods defined by the API.
title
, which determines the title of the spoke, and category
, which determines its category (the category name is not displayed anywhere, it is only used for grouping). Both have the same meanings as the equivalent GUI arguments, described in the previous section.
__init__
, initialize
, refresh
, refresh
, apply
, execute
, input
, and prompt
, and properties (ready
, completed
, mandatory
, and status
). All of these have already been described in Section 6.2, “Graphical user interface”.
Example 13. Defining a Simple TUI Spoke
def __init__(self, app, data, storage, payload, instclass): """ :see: pyanaconda.ui.tui.base.UIScreen :see: pyanaconda.ui.tui.base.App :param app: reference to application which is a main class for TUI screen handling, it is responsible for mainloop control and keeping track of the stack where all TUI screens are scheduled :type app: instance of pyanaconda.ui.tui.base.App :param data: data object passed to every spoke to load/store data from/to it :type data: pykickstart.base.BaseHandler :param storage: object storing storage-related information (disks, partitioning, bootloader, etc.) :type storage: blivet.Blivet :param payload: object storing packaging-related information :type payload: pyanaconda.packaging.Payload :param instclass: distribution-specific information :type instclass: pyanaconda.installclass.BaseInstallClass """ NormalTUISpoke.__init__(self, app, data, storage, payload, instclass) self._entered_text = "" def initialize(self): """ The initialize method that is called after the instance is created. The difference between __init__ and this method is that this may take a long time and thus could be called in a separated thread. :see: pyanaconda.ui.common.UIObject.initialize """ NormalTUISpoke.initialize(self) def refresh(self, args=None): """ The refresh method that is called every time the spoke is displayed. It should update the UI elements according to the contents of self.data. :see: pyanaconda.ui.common.UIObject.refresh :see: pyanaconda.ui.tui.base.UIScreen.refresh :param args: optional argument that may be used when the screen is scheduled (passed to App.switch_screen* methods) :type args: anything :return: whether this screen requests input or not :rtype: bool """ self._entered_text = self.data.addons.org_fedora_hello_world.text return True def apply(self): """ The apply method that is called when the spoke is left. It should update the contents of self.data with values set in the spoke. """ self.data.addons.org_fedora_hello_world.text = self._entered_text def execute(self): """ The excecute method that is called when the spoke is left. It is supposed to do all changes to the runtime environment according to the values set in the spoke. """ # nothing to do here pass def input(self, args, key): """ The input method that is called by the main loop on user's input. :param args: optional argument that may be used when the screen is scheduled (passed to App.switch_screen* methods) :type args: anything :param key: user's input :type key: unicode :return: if the input should not be handled here, return it, otherwise return True or False if the input was processed succesfully or not respectively :rtype: bool|unicode """ if key: self._entered_text = key # no other actions scheduled, apply changes self.apply() # close the current screen (remove it from the stack) self.close() return True def prompt(self, args=None): """ The prompt method that is called by the main loop to get the prompt for this screen. :param args: optional argument that can be passed to App.switch_screen* methods :type args: anything :return: text that should be used in the prompt for the input :rtype: unicode|None """ return _("Enter a new text or leave empty to use the old one: ")
__init__
method if it only calls the ancestor's __init__
, but the comments in the example describe the arguments passed to constructors of spoke classes in an understandable way.
initialize
method sets up a default value for the internal attribute of the spoke, which is then updated by the refresh
method and used by the apply
method to update Kickstart data. The only differences in these two methods from their equivalents in the GUI is the return type of the refresh
method (bool instead of None) and an additional args
argument they take. The meaning of the returned value is explained in the comments - it tells the application (the App
class instance) whether this spoke requires user input or not. The additional args
argument is used for passing extra information to the spoke when scheduled.
execute
method has the same purpose as the equivalent method in the GUI; in this case, the method does nothing.
input
and prompt
are specific to the text interface; there are no equivalents in Kickstart or GUI. These two methods are responsible for user interaction.
prompt
method should return a prompt which will be displayed after the content of the spoke is printed. After a string is entered in reaction to the prompt, this string is passed to the input
method for processing. The input
method then processes the entered string and takes action depending on its type and value. The above example asks for any value and then stores it as an internal attribute (key
). In more complicated add-ons, you typically need to perform some non-trivial actions, such as parse c
as "continue" or r
as "refresh", convert numbers into integers, show additional screens or toggle boolean values.
input
class must be either the INPUT_PROCESSED
or INPUT_DISCARDED
constant (both of these are defined in the pyanaconda.constants_text
module), or the input string itself (in case this input should be processed by a different screen).
apply
method is not called automatically when leaving the spoke; it must be called explicitly from the input
method. The same applies to closing (hiding) the spoke's screen, which is done by calling the close
method.
TUIObject
and call one of the self.app.switch_screen*
methods of the App
.
EditTUISpoke
class from the pyanaconda.ui.tui.spokes
package. By inheriting this class, you can implement a typical TUI spoke by only specifying fields and attributes which should be set in it. The example below demonstrates this:
Example 14. Using EditTUISpoke to Define a Text Interface Spoke
class _EditData(object): """Auxiliary class for storing data from the example EditSpoke""" def __init__(self): """Trivial constructor just defining the fields that will store data""" self.checked = False self.shown_input = "" self.hidden_input = "" class HelloWorldEditSpoke(EditTUISpoke): """Example class demonstrating usage of EditTUISpoke inheritance""" title = _("Hello World Edit") category = HelloWorldCategory # simple RE used to specify we only accept a single word as a valid input _valid_input = re.compile(r'\w+') # special class attribute defining spoke's entries as: # Entry(TITLE, ATTRIBUTE, CHECKING_RE or TYPE, SHOW_FUNC or SHOW) # where: # TITLE specifies descriptive title of the entry # ATTRIBUTE specifies attribute of self.args that should be set to the # value entered by the user (may contain dots, i.e. may specify # a deep attribute) # CHECKING_RE specifies compiled RE used for deciding about # accepting/rejecting user's input # TYPE may be one of EditTUISpoke.CHECK or EditTUISpoke.PASSWORD used # instead of CHECKING_RE for simple checkboxes or password entries, # respectively # SHOW_FUNC is a function taking self and self.args and returning True or # False indicating whether the entry should be shown or not # SHOW is a boolean value that may be used instead of the SHOW_FUNC # # :see: pyanaconda.ui.tui.spokes.EditTUISpoke edit_fields = [ Entry("Simple checkbox", "checked", EditTUISpoke.CHECK, True), Entry("Always shown input", "shown_input", _valid_input, True), Entry("Conditioned input", "hidden_input", _valid_input, lambda self, args: bool(args.shown_input)), ] def __init__(self, app, data, storage, payload, instclass): EditTUISpoke.__init__(self, app, data, storage, payload, instclass) # just populate the self.args attribute to have a store for data # typically self.data or a subtree of self.data is used as self.args self.args = _EditData() @property def completed(self): # completed if user entered something non-empty to the Conditioned input return bool(self.args.hidden_input) @property def status(self): return "Hidden input %s" % ("entered" if self.args.hidden_input else "not entered") def apply(self): # nothing needed here, values are set in the self.args tree pass
_EditData
serves as a data container which is used to store values entered by the user. The HelloWorldEditSpoke
class defines a simple spoke with one checkbox and two entries, all of which are instances of the EditTUISpokeEntry
class imported as the Entry
class). The first one is shown every time the spoke is displayed, the second instance is only shown if the first one contains a non-empty value.
EditTUISpoke
class, see the comments in the above example.