4.10. アドオングラフィカルユーザーインターフェース (GUI) のサポートの追加

本セクションでは、以下の大まかな手順を実行することで、アドオンのグラフィカルユーザーインターフェース (GUI) にサポートを追加する方法を説明します。

  1. Normalspoke クラスに必要な属性を定義します。
  2. __init__initialize メソッドを定義します。
  3. refreshapply、および execute メソッドを定義します。
  4. status および readycompleted および mandatory のプロパティーを定義します。

前提条件

  • アドオンには、キックスタートのサポートが含まれています。「Anaconda アドオン構造」 を参照してください。
  • Anaconda 固有の Gtk ウィジェット (SpokeWindow など) が含まれる anaconda-widgets および anaconda-widgets-devel パッケージをインストールします。

手順

  • 以下の例に従って、アドオングラフィカルユーザーインターフェース (GUI) のサポートを追加するために必要なすべての定義で以下のモジュールを作成します。

例4.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 ファイルで定義されるウィジェットを参照します。この他に、以下の 2 つの重要な属性があります。

  • category。この値は、com_example_hello_world.gui.categories モジュールの HelloWorldCategory クラスからインポートされます。アドオンへのパスが sys.path にある HelloWorldCategory。これにより、com_example_hello_world パッケージから値をインポートできます。category 属性は N_ function 名の一部で、変換用の文字列をマークしますが、変換は後の段階で行われるため、変換されていない文字列のバージョンを返します。
  • タイトル。定義内にアンダースコアが 1 つ含まれています。title 属性アンダースコアは、タイトル自体の先頭をマークし、Alt+H キーボードショートカットを使用してスポークに到達できるようにします。

通常、クラス定義のヘッダーとクラス attributes の定義に続くのは、クラスのインスタンスを初期化するコンストラクターです。Anaconda グラフィカルインターフェースオブジェクトの場合、新しいインスタンスの初期化には __init__ メソッドおよび initialize メソッドの 2 つのメソッドがあります。

このようなメソッドが 2 つある理由は、spoke の初期化に時間がかかる可能性があるため、あるタイミングで GUI オブジェクトがメモリーに作成され、別のタイミングで完全に初期化される可能性があるためです。したがって、__init__ メソッドは親の _init__ メソッドのみを呼び出し、たとえば、GUI 以外の属性を初期化する必要があります。一方、インストーラーのグラフィカルユーザーインターフェースの初期化時に呼び出される initialize メソッドは、スポークの完全な初期化を完了する必要があります。

Hello World add-on の例で、以下のようにこの 2 つのメソッドを定義します。__init__ メソッドに渡される引数の数および説明をメモしてください。

例4.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__ メソッドに渡されるデータパラメーターは、すべてのデータが保存されるキックスタートファイルのメモリー内ツリーのような表示になります。ancestor の __init__ メソッドのいずれかで、self.data 属性に格納されます。これにより、クラス内の他のすべてのメソッドで構造の読み取りおよび修正が可能になります。

注記

storage object は RHEL8 以降利用できなくなりました。アドオンがストレージ設定と対話する必要がある場合は、Storage DBus モジュールを使用します。

HelloWorldData クラスは 「Hello World アドオンの例」 ですでに定義されているため、このアドオンの self.data にサブツリーがすでに存在します。クラスのインスタンスである root は self.data.addons.com_example_hello_world として利用できます。

ancestor の __init__ が実行するもう 1 つのアクションは、spoke’s .glade で GtkBuilder のインスタンスを初期化し、これを self.builder として保存することです。initialize メソッドはこれを使用して、キックスタートファイルの %addon セクションにあるテキストを表示し、変更するために使用される GtkTextEntry を取得します。

__init__ および initialize メソッドは両方とも、スポークの作成時に重要となります。ただし、スポークの主な役割は、スポークの値の表示と設定を変更または確認したいユーザーがアクセスすることです。これを有効にするには、その他の 3 つの方法を使用できます。

  • refresh: スポークがアクセスされようとするときに呼び出されます。このメソッドは、スポーク (主に UI 要素) の状態を更新し、表示されるデータが内部データ構造と一致するようにします。これにより、self.data 構造に保存されている現在の値が表示されるようにします。
  • apply: スポークが残っている場合に呼び出され、UI 要素の値を self.data 構造に戻す際に使用されます。
  • execute: ユーザーがスポークを離れる場合に呼び出され、スポークの新しい状態に基づいてランタイムの変更を実行する際に使用されます。

これらの関数は、以下のように Hello World アドオンのサンプルに実装されます。

例4.6 更新、適用、および実行メソッドの定義

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

複数の追加のメソッドを使用して、スポークの状態を制御できます。

  • ready: スポークへアクセスできるかどうかを判断します。値が「False」の場合は spoke へアクセスできません。たとえば、パッケージソースを設定する前に Package Selection スポークにアクセスできません。
  • completed - スポークが完了しているかどうかを確認します。
  • mandatory: スポークが必須かどうかを判別します。たとえば、自動パーティションを使用する場合でも、常にアクセスする必要がある Installation Destination スポークが必須かどうか判別します。

これらの属性はすべて、インストールプロセスの現在の状態に基づいて動的に決定する必要があります。

以下は、Hello World アドオンでのこれらのメソッドの実装例です。これには、HelloWorldData クラスの text 属性に特定の値を設定する必要があります。

例4.7 準備完了、完了、および必須メソッドの定義

@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

これらのプロパティーが定義された後、スポークはそのアクセス可能性と完全性を制御できますが、内部で構成された値のサマリーを提供することはできません。スポークにアクセスして、スポークがどのように構成されているかを確認する必要がありますが、これは望ましくない場合があります。このため、status という追加のプロパティーが存在します。このプロパティーには、設定された値の短いサマリーを含む 1 行のテキストが含まれ、スポークタイトルの下のハブに表示することができます。

status プロパティーは、以下のように Hello World の例のアドオンで定義されます。

例4.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) とキックスタートを示す完全なサポートがあります。

注記

ここで示した例は非常にシンプルで、制御を含むものはありません。GUI で機能的かつインタラクティブなスポークを開発するには、Python Gtk プログラミングに関する知識が必要です。

主な制限の 1 つとして、それぞれのスポークに独自のメインウィンドウ (SpokeWindow ウィジェットのインスタンス) が必要である点が挙げられます。このウィジェットは、Anaconda 固有の他のウィジェットとともに、anaconda-widgets パッケージにあります。Glade など、GUI サポートでアドオンの開発に必要な他のファイルは、anaconda-widgets-devel パッケージで見つけることができます。

グラフィカルインターフェースのサポートモジュールに、必要な方法をすべて含むと、以下のセクションを使用してテキストベースのユーザーインターフェースのサポートを追加するか、「Anaconda アドオンのデプロイおよびテスト」 を引き続き使用してアドオンをテストすることができます。