5.10. Adding support for the Add-on graphical user interface (GUI)


You can add support to the graphical user interface (GUI) of your add-on. To do so, perform the following high-level steps:

  1. Define Attributes Required for the Normalspoke Class
  2. Define the __init__ and initialize Methods
  3. Define the refresh, apply, and execute Methods
  4. Define the status and the ready, completed and mandatory Properties

Prerequisites

  • Your add-on includes support for Kickstart. See Anaconda add-on structure.
  • Install the anaconda-widgets and anaconda-widgets-devel packages, which contain Gtk widgets specific for Anaconda, such as SpokeWindow.

Procedure

  • Create the following modules with all required definitions to add support for the Add-on graphical user interface (GUI), according to the following examples.

    # will never be translated
    _ = lambda x: x
    N_ = lambda x: x
    
    # the path to addons is in sys.path so we can import things from org_fedora_hello_world
    from org_fedora_hello_world.gui.categories.hello_world import HelloWorldCategory
    from pyanaconda.ui.gui.spokes import NormalSpoke
    
    # export only the spoke, no helper functions, classes or constants
    all = ["HelloWorldSpoke"]
    
    class HelloWorldSpoke(FirstbootSpokeMixIn, NormalSpoke):
        """
        Class for the Hello world spoke. This spoke will be in the Hello world
        category and thus on the Summary hub. It is a very simple example of a unit
        for the Anaconda's graphical user interface. Since it is also inherited form
        the FirstbootSpokeMixIn, it will also appear in the Initial Setup (successor
        of the Firstboot tool).
    
        :see: pyanaconda.ui.common.UIObject
        :see: pyanaconda.ui.common.Spoke
        :see: pyanaconda.ui.gui.GUIObject
        :see: pyanaconda.ui.common.FirstbootSpokeMixIn
        :see: pyanaconda.ui.gui.spokes.NormalSpoke
    
        """
    
        # class attributes defined by API #
    
        # list all top-level objects from the .glade file that should be exposed
        # to the spoke or leave empty to extract everything
        builderObjects = ["helloWorldSpokeWindow", "buttonImage"]
    
        # the name of the main window widget
        mainWidgetName = "helloWorldSpokeWindow"
    
        # name of the .glade file in the same directory as this source
        uiFile = "hello_world.glade"
    
        # category this spoke belongs to
        category = HelloWorldCategory
    
        # spoke icon (will be displayed on the hub)
        # preferred are the -symbolic icons as these are used in Anaconda's spokes
        icon = "face-cool-symbolic"
    
        # title of the spoke (will be displayed on the hub)
        title = N_("_HELLO WORLD")

    The __all__ attribute exports the spoke class, followed by the first lines of its definition including definitions of attributes previously mentioned in GUI Add-on basic features. These attribute values are referencing widgets defined in the com_example_hello_world/gui/spokes/hello.glade file. Two other notable attributes are present:

  • category, which has its value imported from the HelloWorldCategory class from the com_example_hello_world.gui.categories module. The HelloWorldCategory that the path to add-ons is in sys.path so that values can be imported from the com_example_hello_world package. The category attribute is part of the N_ function name, which marks the string for translation; but returns the non-translated version of the string, as the translation happens in a later stage.
  • title, which contains one underscore in its definition. The title attribute underscore marks the beginning of the title itself and makes the spoke reachable by using the Alt+H keyboard shortcut.

    What usually follows the header of the class definition and the class attributes definitions is the constructor that initializes an instance of the class. In case of the Anaconda graphical interface objects, there are two methods initializing a new instance: the __init__ method and the initialize method.

    The reason behind two such functions is that the GUI objects may be created in memory at one time and fully initialized at a different time, as the spoke initialization could be time consuming. Therefore, the __init__ method should only call the parent’s __init__ method and, for example, initialize non-GUI attributes. On the other hand, the initialize method that is called when the installer’s graphical user interface initializes should finish the full initialization of the spoke.

    In the Hello World add-on example, define these two methods as follows. Note the number and description of the arguments passed to the __init__ method. For example:

    def __init__(self, data, storage, payload):
        """
        :see: pyanaconda.ui.common.Spoke.init
        :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, boot loader, etc.)
        :type storage: blivet.Blivet
        :param payload: object storing packaging-related information
        :type payload: pyanaconda.packaging.Payload
    
        """
    
        NormalSpoke.init(self, data, storage, payload)
        self._hello_world_module = HELLO_WORLD.get_proxy()
    
    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 separate thread.
        :see: pyanaconda.ui.common.UIObject.initialize
        """
        NormalSpoke.initialize(self)
        self._entry = self.builder.get_object("textLines")
        self._reverse = self.builder.get_object("reverseCheckButton")

    The data parameter passed to the __init__ method is the in-memory tree-like representation of the Kickstart file where all data is stored. In one of the ancestors' __init__ methods it is stored in the self.data attribute, which allows all other methods in the class to read and modify the structure.

    참고

    The storage object is no longer usable as of RHEL10. If your add-on needs to interact with storage configuration, use the Storage DBus module.

    Because the HelloWorldData class has already been defined in The Hello World addon example, there already is a subtree in self.data for this add-on. Its root, an instance of the class, is available as self.data.addons.com_example_hello_world.

    Another action that an ancestor’s __init__ does is initializing an instance of the GtkBuilder with the spoke’s .glade file and storing it as self.builder. The initialize method uses this to get the GtkTextEntry used to show and modify the text from the Kickstart file’s %addon section.

    The __init__ and initialize methods are both important when the spoke is created. However, the main role of the spoke is to be visited by a user who wants to change or review the spoke’s values shows and sets. To enable this, three other methods are available:

  • refresh - called when the spoke is about to be visited; this method refreshes the state of the spoke, mainly its UI elements, to ensure that the displayed data matches internal data structures and, with that, to ensure that current values stored in the self.data structure are displayed.
  • apply - called when the spoke is left and used to store values from UI elements back into the self.data structure.
  • execute - called when users leave the spoke and used to perform any runtime changes based on the new state of the spoke.

    These functions are implemented in the sample Hello World add-on in the following way:

    def refresh(self):
        """
        The refresh method that is called every time the spoke is displayed.
        It should update the UI elements according to the contents of
        internal data structures.
        :see: pyanaconda.ui.common.UIObject.refresh
        """
        lines = self._hello_world_module.Lines
        self._entry.get_buffer().set_text("".join(lines))
        reverse = self._hello_world_module.Reverse
        self._reverse.set_active(reverse)
    
    def apply(self):
        """
        The apply method that is called when user leaves the spoke. It should
        update the D-Bus service with values set in the GUI elements.
        """
        buf = self._entry.get_buffer()
        text = buf.get_text(buf.get_start_iter(),
                            buf.get_end_iter(),
                            True)
        lines = text.splitlines(True)
        self._hello_world_module.SetLines(lines)
    
        self._hello_world_module.SetReverse(self._reverse.get_active())
    
    def execute(self):
      """
      The execute method that is called when the spoke is exited. It is
      supposed to do all changes to the runtime environment according to
      the values set in the GUI elements.
    
      """
    
      # nothing to do here
      pass

    You can use several additional methods to control the spoke’s state:

  • ready - determines whether the spoke is ready to be visited; if the value is "False", the spoke is not accessible, for example, the Package Selection spoke before a package source is configured.
  • completed - determines if the spoke has been completed.
  • mandatory - determines if the spoke is mandatory or not, for example, the Installation Destination spoke, which must always be visited, even if you want to use automatic partitioning.

    All of these attributes need to be dynamically determined based on the current state of the installation process. Below is a sample implementation of these methods in the Hello World add-on, which requires a certain value to be set in the text attribute of the HelloWorldData class:

    @property
    def ready(self):
        """
        The ready property reports whether the spoke is ready, that is, can be visited
        or not. The spoke is made (in)sensitive based on the returned value of the ready
        property.
    
        :rtype: bool
    
        """
    
        # this spoke is always ready
        return True
    
    
    @property
    def mandatory(self):
        """
        The mandatory property that tells whether the spoke is mandatory to be
        completed to continue in the installation process.
    
        :rtype: bool
    
        """
    
        # this is an optional spoke that is not mandatory to be completed
        return False

    After these properties are defined, the spoke can control its accessibility and completeness, but it cannot provide a summary of the values configured within - you must visit the spoke to see how it is configured, which may not be desired. For this reason, an additional property called status exists. This property contains a single line of text with a short summary of configured values, which can then be displayed in the hub under the spoke title.

    The status property is defined in the Hello World example add-on as follows:

    @property
    def status(self):
        """
        The status property that is a brief string describing the state of the
        spoke. It should describe whether all values are set and if possible
        also the values themselves. The returned value will appear on the hub
        below the spoke's title.
        :rtype: str
        """
        lines = self._hello_world_module.Lines
        if not lines:
            return _("No text added")
        elif self._hello_world_module.Reverse:
            return _("Text set with {} lines to reverse").format(len(lines))
        else:
            return _("Text set with {} lines").format(len(lines))

    After defining all properties described in the examples, the add-on has full support for showing a graphical user interface (GUI) as well as Kickstart.

    참고

    The example demonstrated here is very simple and does not contain any controls; knowledge of Python Gtk programming is required to develop a functional, interactive spoke in the GUI.

    One notable restriction is that each spoke must have its own main window - an instance of the SpokeWindow widget. This widget, along with other widgets specific to Anaconda, is found in the anaconda-widgets package. You can find other files required for development of add-ons with GUI support, such as Glade definitions, in the anaconda-widgets-devel package.

    Once your graphical interface support module contains all necessary methods you can continue with the following section to add support for the text-based user interface.

Red Hat logoGithubredditYoutubeTwitter

자세한 정보

평가판, 구매 및 판매

커뮤니티

Red Hat 문서 정보

Red Hat을 사용하는 고객은 신뢰할 수 있는 콘텐츠가 포함된 제품과 서비스를 통해 혁신하고 목표를 달성할 수 있습니다. 최신 업데이트를 확인하세요.

보다 포괄적 수용을 위한 오픈 소스 용어 교체

Red Hat은 코드, 문서, 웹 속성에서 문제가 있는 언어를 교체하기 위해 최선을 다하고 있습니다. 자세한 내용은 다음을 참조하세요.Red Hat 블로그.

Red Hat 소개

Red Hat은 기업이 핵심 데이터 센터에서 네트워크 에지에 이르기까지 플랫폼과 환경 전반에서 더 쉽게 작업할 수 있도록 강화된 솔루션을 제공합니다.

Theme

© 2026 Red Hat
맨 위로 이동