5.10. 对附加图形用户界面(GUI)添加支持

这部分描述了如何通过执行以下高级步骤来为您的附加组件的图形用户界面(GUI)添加支持:

  1. 定义 Normalspoke 类所需的属性
  2. 定义 __init__initialize 方法
  3. 定义 refreshapplyexecute 方法
  4. 定义 statusreadycompletedmandatory 属性

先决条件

  • 您的附加组件包括对 Kickstart 的支持。请参阅 Anaconda 附加组件结构
  • 安装 anaconda-widgets 和 anaconda-widgets-devel 软件包,其中包含特定于 Anaconda 的 Gtk 小部件,如 SpokeWindow

流程

  • 根据以下示例,创建带有所有必要的定义的以下模块,来添加对 Add-on 图形用户界面(GUI)的支持。

例 5.4. 定义 Normalspoke 类所需的属性:

# 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")

__all__ 属性导出 spoke 类,后跟包括之前在 GUI 附加组件基本功能 中提到的属性定义的其定义的第一行。这些属性值引用 com_example_hello_world/gui/spokes/hello.glade 文件中定义的小部件。还有两个值得注意的属性:

  • category,它的值从 com_example_hello_world.gui.gui.categories 模块的 HelloWorldCategory 类导入。附加组件路径 HelloWorldCategory 位于 sys.path 中,因此值可以从 com_example_hello_world 软件包导入。category 属性是 N_function 名称的一部分,用于标记要转换的字符串;但会返回字符串的非转换版本,因为转换发生在后续阶段。
  • title,其定义中包含一个下划线。title 属性下划线标记标题本身的开头,并使用 Alt+H 键盘快捷键使 spoke 可访问。

通常在类定义标头和类 属性 定义后面是初始化类实例的构造器。如果是 Anaconda 图形界面对象,有两初始化新实例的种方法:__init__ 方法和 initialize 方法。

这两个函数背后的原因是,GUI 对象可以一次在内存中创建 ,并在不同时间完全初始化,而 spoke 初始化可能会很耗时。因此,__init__ 方法应只调用父类的 __init__ 方法,例如初始化非 GUI 属性。另一方面,安装程序图形用户界面初始化时调用的 initialize 方法应该完成 spoke 的整个初始化过程。

Hello World add-on 示例中,定义了如下两种方法:注意传给 __init__ 方法的编号和描述参数。

例 5.5. 定义 __init__ 和初始化方法:

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, bootloader, 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")

传给 __init__ 方法的数据参数是存储所有数据的 Kickstart 文件的内存树状表示。在祖先的一个 __init__ 方法中,它存储在 self.data 属性中,这个属性允许类中的所有其他方法读取和修改结构。

注意

从 RHEL8 开始,存储对象 不再可用。如果您的附加组件需要与存储配置进行交互,请使用 Storage DBus 模块。

由于 HelloWorldData 类已在 Hello World 附加组件示例 中定义了,因此此附加组件的 self.data 中已有一个子树。它的根(一个类的实例)作为 self.data.addons.com_example_hello_world 提供。

祖先的 __init__ 的所做的另一个操作是使用 spoke 的 .glade 文件初始化 GtkBuilder 的实例,并将它存储为 self.builderinitialize 方法使用这个来获取用于显示和修改 Kickstart 文件的 %addon 部分中文本的 GtkTextEntry

在创建 spoke 时,__init__initialize 方法都很重要。但是,spoke 的主要作用是希望被更改或查看 spoke 值显示和集合的用户访问。要启用此功能,可以使用其他三种方法:

  • refresh - 在要访问 spoke 时调用 ;此方法会刷新 spoke 的状态,主要是它的 UI 元素,以确保显示的数据与内部数据结构相匹配,并通过它来确保显示 self.data 结构中存储的当前值。
  • apply - 当 spoke 离开时调用,用于将 UI 元素的值存储回 self.data 结构。
  • execute - 当用户离开 spoke 时调用,用于根据 spoke 的新状态来执行任何运行时更改。

这些功能在 Hello World 附加组件示例中以以下方式实现:

例 5.6. 定义 refresh 、apply 和 execute 方法

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

您可以使用几个额外的方法来控制 spoke 的状态:

  • ready - 确定 spoke 是否准备好被访问;如果值为"False",则不能访问 spoke,例如,在配置软件包源之前的 Package Selection spoke。
  • completed - 确定 spoke 是否已完成。
  • mandatory - 确定 spoke 是强制还是非强制的,例如,Installation Destination spoke,其必须一直被访问,即使您想使用自动分区。

所有这些属性都需要根据安装过程的当前状态动态确定。

以下是在 Hello World 附加组件中实现这些方法的示例,这需要在 HelloWorldData 类的文本属性中设置一个特定的值:

例 5.7. 定义 ready 、completed 和 mandatory 方法

@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

在定义了这些属性后,spoke 可以控制其可访问性和完整性,但不能提供其中配置的值的摘要 - 您必须访问 spoke 以查看它是如何配置的,这可能不是必需的。因此,存在名为 status 的额外属性。此属性包含一文本行,并带有已配置值的简短摘要,然后其可显示在 spoke 标题下的 hub 中。

status 属性定义在 Hello World 示例附加组件中,如下所示:

例 5.8. 定义 status 属性

@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))

在定义了示例中描述的所有属性后,附加组件完全支持显示图形用户界面(GUI)以及 Kickstart。

注意

此处演示的示例非常简单,不包含任何控制;需要掌握 Python Gtk 编程知识才能在 GUI 中开发功能性的、交互式 spoke 。

一个值得注意的限制是每个 spoke 都必须有自己的主窗口,即 SpokeWindow 小部件的一个实例。此小部件以及其他特定于 Anaconda 的小部件可在 anaconda-widgets 软件包中找到。您可以在 anaconda-widgets-devel 软件包中找到使用 GUI 支持开发附加组件所需的其他文件,如 Glade 定义。

一旦图形界面支持模块包含所有必要的方法,您可以继续使用以下部分来添加对基于文本的用户界面的支持,或者您可以继续 部署和测试 Anaconda 附加组件 ,并测试附加组件。