5.6.3. テキスト形式のユーザーインターフェイス
Anaconda は Kickstart および GUI のほかに、テキストベースのインターフェイスもサポートしています。このインターフェイスは機能面では制限がありますが、システムによってはこれが唯一の対話式インストール方法となることがあります。テキストベースインターフェイスとグラフィカルインターフェイスの違い、および TUI の制限の詳細については、次を参照してください。「Anaconda の概要」 .
アドオンにテキストインターフェイスのサポートを追加するには、次の説明に従って、
tui
ディレクトリーの下に一連の新しいサブパッケージを作成します。「Anaconda アドオンの構造」 .
インストーラーでのテキストモードのサポートは
simpleline
ユーティリティーをベースとしており、これは非常にシンプルなユーザーの対話のみを可能にするものです。これはカーソルの操作はできず (ラインプリンターのような動作になります)、色およびフォントのカスタマイズといった視覚的拡張機能もありません。
内部的には、
simpleline
ツールキットには、App
、UIScreen
、Widget
の 3 つの主要クラスがあります。画面に表示 (プリント) する情報を格納しているユニットである Widget は、App
クラスの単一インスタンスで切り替えられる UIScreens に配置されます。基本的な要素のほかには hubs、spokes および dialogs があり、これらはすべてグラフィカルインターフェイスと同様の各種ウィジェットを格納しています。
アドオンで最重要となるクラスは
NormalTUISpoke
と pyanaconda.ui.tui.spokes
パッケージで定義される他の各種クラスです。これらのクラスはすべて TUIObject
クラスに基づいています。それ自体は、前の章で説明した GUIObject
クラスと同等です。各 TUI スポークは、NormalTUISpoke
クラスを継承する Python クラスであり、API で定義される特別な引数とメソッドをオーバーライドします。テキストインターフェイスは GUI よりも簡単なため、引数は以下の 2 つのみになります。
title
: GUI のtitle
引数の場合と同様に、スポークのタイトルを指定します。category
: スポークのカテゴリーを文字列として判別します。カテゴリー名はどこにも表示されず、グループ化にのみ使用されます。注記カテゴリーは GUI とは異なる処理です。[5] 既存のカテゴリーを新しいスポークに割り当てることが推奨されます。新しいカテゴリーを作成するには、Anaconda にパッチを適用する必要があり、ほとんどメリットがありません。
また、各スポークは
__init__
、initialize
、refresh
、refresh
、apply
、execute
、input
、prompt
、および プロパティー (ready
、completed
、mandatory
、および 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 の同等のメソッドと同じ目的で、この場合はメソッドは何もしません。
input
と prompt
メソッドはテキストインターフェイスに固有のものです。キックスタートまたは GUI には同等のものはありません。この 2 つのメソッドはユーザーの対話に使用されます。
prompt
メソッドは、スポークのコンテンツがプリントされた後に表示されるプロンプトを返します。このプロンプトに文字列を入力すると、これが input
メソッドに渡されて処理されます。input
メソッドはこの文字列のタイプと値に応じてアクションを実行します。上記の例は任意の値を要求してから、それを内部属性 (key
) として保存します。より複雑なアドオンでは通常、c
を "continue" または r
を "refresh" として解析する、数字を整数に変換する、新たな画面を表示するもしくはブール値を切り替えるなど、簡単ではないアクションを実行する必要があります。
input
クラスの戻り値は、INPUT_PROCESSED
か INPUT_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
クラスについての詳細は、上記の例にあるコメントを参照してください。