Red Hat Training

A Red Hat training course is available for Red Hat Enterprise Linux

5.6.3. テキスト形式のユーザーインターフェイス

Anaconda は Kickstart および GUI のほかに、テキストベースのインターフェイスもサポートしています。このインターフェイスは機能面では制限がありますが、システムによってはこれが唯一の対話式インストール方法となることがあります。テキストベースインターフェイスとグラフィカルインターフェイスの違い、および TUI の制限の詳細については、次を参照してください。「Anaconda の概要」 .
アドオンにテキストインターフェイスのサポートを追加するには、次の説明に従って、tui ディレクトリーの下に一連の新しいサブパッケージを作成します。「Anaconda アドオンの構造」 .
インストーラーでのテキストモードのサポートは simpleline ユーティリティーをベースとしており、これは非常にシンプルなユーザーの対話のみを可能にするものです。これはカーソルの操作はできず (ラインプリンターのような動作になります)、色およびフォントのカスタマイズといった視覚的拡張機能もありません。
内部的には、simpleline ツールキットには、AppUIScreenWidget の 3 つの主要クラスがあります。画面に表示 (プリント) する情報を格納しているユニットである Widget は、App クラスの単一インスタンスで切り替えられる UIScreens に配置されます。基本的な要素のほかには hubsspokes および dialogs があり、これらはすべてグラフィカルインターフェイスと同様の各種ウィジェットを格納しています。
アドオンで最重要となるクラスは NormalTUISpokepyanaconda.ui.tui.spokes パッケージで定義される他の各種クラスです。これらのクラスはすべて TUIObject クラスに基づいています。それ自体は、前の章で説明した GUIObject クラスと同等です。各 TUI スポークは、NormalTUISpoke クラスを継承する Python クラスであり、API で定義される特別な引数とメソッドをオーバーライドします。テキストインターフェイスは GUI よりも簡単なため、引数は以下の 2 つのみになります。
  • title: GUI の title引数の場合と同様に、スポークのタイトルを指定します。
  • category: スポークのカテゴリーを文字列として判別します。カテゴリー名はどこにも表示されず、グループ化にのみ使用されます。
    注記
    カテゴリーは GUI とは異なる処理です。[5] 既存のカテゴリーを新しいスポークに割り当てることが推奨されます。新しいカテゴリーを作成するには、Anaconda にパッチを適用する必要があり、ほとんどメリットがありません。
また、各スポークは __init__initializerefreshrefreshapplyexecuteinputprompt、および プロパティー (readycompletedmandatory、および status) をオーバーライドすることが期待されています。これらはすべて、「グラフィカルユーザーインターフェイス」 .
以下ではシンプルな TUI スポークを Hello World のサンプルアドオンに実装する例です。

例14 シンプルな 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: ")
ancestor の __init__ のみを呼び出す場合は __init__ メソッドを上書きする必要はありませんが、この例のコメントでは、一般的な方法でスポーククラスのコンストラクターへ渡された引数を記述します。
initialize メソッドはスポークの内部引数のデフォルト値を設定し、これは refresh メソッドで更新され、 Kickstart データの更新に apply メソッドが使用します。この 2 つのメソッドが GUI のものと違う点は、refresh メソッドの戻り値のタイプ (None ではなく bool) と、それらが取る追加の args 引数のみです。返された値の意味はコメントで説明されています。このスポークにユーザー入力が必要かどうかに関係なく、アプリケーション (App クラスインスタンス) に指示します。追加の args 引数は、スポークに追加情報を渡す予定の場合に使用されます。
execute メソッドは、GUI の同等のメソッドと同じ目的で、この場合はメソッドは何もしません。
inputprompt メソッドはテキストインターフェイスに固有のものです。キックスタートまたは GUI には同等のものはありません。この 2 つのメソッドはユーザーの対話に使用されます。
prompt メソッドは、スポークのコンテンツがプリントされた後に表示されるプロンプトを返します。このプロンプトに文字列を入力すると、これが input メソッドに渡されて処理されます。input メソッドはこの文字列のタイプと値に応じてアクションを実行します。上記の例は任意の値を要求してから、それを内部属性 (key) として保存します。より複雑なアドオンでは通常、c を "continue" または r を "refresh" として解析する、数字を整数に変換する、新たな画面を表示するもしくはブール値を切り替えるなど、簡単ではないアクションを実行する必要があります。
input クラスの戻り値は、INPUT_PROCESSEDINPUT_DISCARDED の定数 (これらは両方とも pyanaconda.constants_text モジュールで定義) となるか、もしくは 入力文字列そのもの (この入力が別の画面で処理される場合) である必要があります。
グラフィカルモードとは対照的に、スポークを離れる際に apply メソッドは自動的に呼び出されません。input メソッドから明示的に呼び出す必要があります。同じことがスポークの画面閉鎖 (非表示) にも該当し、これは close メソッドの呼び出しで実行します。
別の画面を表示するには (例えば、別のスポークで入力された追加情報が必要な場合など)、別の TUIObject をインスタンス化し、self.app.switch_screen*App メソッドの 1 つを呼び出します。
テキストベースのインターフェイスの制限により、TUI スポークは非常によく似た構造を持つ傾向があり、ユーザーがチェックまたはチェックを外して入力する必要があるチェックボックスまたはエントリーの一覧で設定されます。以前の段落では、メソッドが利用可能かつ提供されるデータの出力と処理に対処する TUI スポークを実装する方法が示されました。しかし、pyanaconda.ui.tui.spokes パッケージから EditTUISpoke クラスを使用し、これを実行する別の方法があります。このクラスを継承すると、設定するフィールドと属性を指定するだけで通常の TUI スポークを実装できます。以下の例ではこの方法を示しています。

例15 EditTUISpoke を使ったテキストインターフェイスのスポークの定義

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 = "localization"

    # 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 は、ユーザーが入力した値を保存するデータコンテナーとして機能します。HelloWorldEditSpoke クラスはチェックボックス 1 つとエントリー 2 つの簡単なスポークを定義し、これらはすべて Entry クラスとしてインポートされる EditTUISpokeEntry のインスタンスになります。最初のインスタンスはスポークが表示されるたびに表示され、2 つ目のインスタンスは 1 つ目に空以外の値が含まれる場合にのみ表示されます。
EditTUISpoke クラスについての詳細は、上記の例にあるコメントを参照してください。


[5] これは、今後も改善された方法 (GUI) に固定される可能性が高いです。