Glade3 におけるカスタム PyGTK ウィジット: パート2: カスタムアダプタ

Custom PyGTK Widgets in Glade3: Part 2: Custom widget adaptors by Ali Afshar

# 本稿は上記リンク元の和訳になります
# 本稿の規約・ライセンス等はオリジナルのそれに従います
# 転載ミス、誤訳等については適宜修正します

前回の投稿ではカスタム glade ウィジットについて、私たちは、ユーザインタフェースデザイナで使えるように PyGTK カスタムウィジットを glade-3 に追加する方法について簡潔に説明しました。本稿では、追加の振る舞いを定義するために、PyGTK ウィジット向けにカスタムアダプタを作成する方法を含めるようにしました。
例として、私が "Service View" と呼んでいるカスタムウィジットを使用します。私たちは、主要部分において、このウィジットがコンテンツセクションを持ち、下部でクローズボタンも含みものを予測します。私たちは、一般的なドックに収納可能なビューとして、トップレベルウィンドウというよりはむしろダイアログのような類のウィジットを使います。これは全く無意味のように見えるかもしれません。しかし、それは、ノートブックにおいて、デバッガ、ターミナル、ドキュメントブラウザ等々の IDE のように、異なったビューを作成するアプリケーションの便利なコンポーネントになります。これらは同一の基本的なレイアウトを共有することができます。しかし、ウィジットの1つの主要部分をただ置き換えるだけです。

先ずウィジットを書いてみましょう:

import gtk

class ServiceView(gtk.VBox):

__gtype_name__ = 'ServiceView'

def __init__(self):
    self.frame = gtk.Frame()
    self.pack_start(self.frame)
    self._bb = gtk.HButtonBox()
    self._bb.set_layout(gtk.BUTTONBOX_END)
    self._bb.pack_start(gtk.Button(stock=gtk.STOCK_CLOSE))
    self.pack_start(self._bb, expand=False)

このウィジットのように、私たちはフレーム内部で1つの子コンポーネントを追加させたいです。また self.frame のラベルをセットすることもできます。

私たちが、以下のようなカタログ(pywidgets.xml)を使用する Glade3 へこのウィジットを追加する場合:










そして、そのようなウィジットを単純にインポートするコード(pywidgets.py)をサポートします。

from serviceview import ServiceView

(モジュール "serviceview.py" では python のパスが通っていて、上述のウィジットを含みます)

正しい場所にカタログとモジュールファイルをインストールして、glade-3 を起動してください。ウィンドウを作成して、そこへ私たちのカスタムウィジットを追加してください。すぐにウィジットの要求されたサイズに応じて、入力ダイアログがクエリされるでしょう。この応答をするとすぐに、ウィジットは作成されます。

問題? そうです。それは以下の項目において動作しません。:

  • ウィジットのサイズを尋ねるクエリ
  • フレームの外側へ追加された余分なグレーセクション
  • フレームのコンテンツとそのラベルを変更できない機能


これらの全ての問題は同じ要因です。私たちのウィジットは gtk.VBox のサブクラスなので、そのライフタイムの延長上で VBox として取り扱われます。

そのため、どうやってこれらの問題を解決するのでしょうか?2つの事が必要です。先ずカタログファイルの幾つかのプロパティをオーバーライドします、次にカスタムアダプタを作成します。(私たちはそのアダプタから、これらの全ての事ができるようにするのに1日必要かもしれません。しかし、ただ今は不可能です。それは、"hybrid API" を持つ glade-3 開発者によって明らかにされた1つの課題です)

カタログを修正する

ここで read the excellent documentation を忘れないでください。glade とデフォルトカタログにおけるウィジットはクエリのための入力ウィンドウを開始する機能を提供します。これは、真っ当なデフォルトとして全く必要ではありません。その後、通常の方法において要求されるように変更されました。しかし、それは推測する幾つかの状況において使い易いものです。この例では、Box ウィジットのサブクラスを使用しているので、私たちが求めるサイズが尋ねられるでしょう。サイズは Box の本当のプロパティではなく、仮想プロパティです。Glade が、どのようにしてそれを表示するかを知るのを助けます。もし、デフォルトカタログのこのパートを見ると、私たちはこの現象を確認することができます:


...


glade_standard_int_spec
The number of items in the
box
glade_gtk_box_verify_size


...


glade-widget-class タグ内部のプロパティタグ内部にある各々のプロパティタグは、その id 属性を持たなければなりません。しかし、それは、ここではオプションとして示されています。ここで、私たちにとって重要な1つは、表示されるクエリボックスを引き起こす query="True" です。そして、"override" プロパティのソートにおいて、自身のカタログを置き換えることができます。プロパティに save="False" がセットされることを注意してください。それは本当のプロパティではないため、仮想プロパティです。そして glade XML ファイルにも書き込まれません

クエリボックスを無効にせずに、そうしたい場合:

  1. このデフォルト値にゼロをセットして、グレーなエリアがないこと、余分なウィジットがボックスに追加されない事を示す
  2. サイズウィジットの隠蔽 又は 無効化


再度、ドキュメントを見てください。私たちは default="0" とデフォルト値をセットする事と、そのプロパティを disabled="True" で無効化する、又は visible="False" で隠蔽する事を発見しました。

つまり、私たちのカタログのクラス定義は以下になります:






カタログを再インストールしてください。ウィンドウを作成して、ウィジットを追加してください。スタートサイズを尋ねられることはなく、プロパティリストにサイズ値も表れません。ここでは必要ない残された Box のプロパティがあります。それらを削除する事は、読者のための作業として残しておいて、次節へ進みます。

カスタムアダプタを使用する


アダプタクラスはサポートコードモジュールの中で表され、カタログを参照します。例えば:




ServiceView アダプタの __gtype_name__ を登録するクラスを参照します。そのクラスは glade.get_adaptor_for_type('SomeGTKType') から継承し、VBox サブクラスのケースでは、私たちのアダプタスーパークラス(自動的にそれ自身を使用した)として glade.get_adaptor_for_type('GtkVBox') を使用できました。

import glade

class ServiceViewAdaptor(glade.get_adaptor_for_type('GtkVBox')):
    __gtype_name__ = 'ServiceViewAdaptor'

アダプタコード内で、glade は、実行時のみ有効になる特殊な Python モジュールです。そして glade ユーザインタフェースバインディングを含みます。一見したければ、実行時にそのドキュメントを取得できます。しかし、次の投稿時にこの機能が実行されるでしょう。

今のところ何もオーバーライドしないので、私たちの実行コードは、以前のものと正確に同様の振る舞いをします。しかし、(何もしない)カスタムアダプタと名付けれています

今、カスタムアダプタのメソッドを追加して起動することができます。一般的に、有効なメソッドは以下のように呼ばれるでしょう。:

do_something(self, to_object, *args)


その何らかの動作が起こる場所で to_object は、ユーザインタフェースのオブジェクトとして作成されます。最初のメソッドの例です:

def do_post_create(self, obj, reason):
    # このウィジットへ追加できるようにするために Glade から都合良く見えるグレーエリアの1つを作成します
    obj.frame.add(gobject.new('GladePlaceholder'))


このステージでは、好みで、あなたのカタログとサポートコードをインストールできます。そして、このウィジットをテストしてください。私たちのフレーム内部でグレーエリアが正しく表示されるのが確認されるでしょう。今、ラベルのように、そこに何かを追加しようとしてください。そのラベルは私たちが望まない VBox の最後にただ追加されたことに気付くでしょう。これを修正するために、次の子メソッドをオーバーライドする必要があります:

def do_get_children(self, obj):
    """glade がウィジットの子を取得する"""
    return obj.frame.get_children()

def do_add(self, obj, child):
    """ウィジットへ子を追加する"""
    obj.frame.add(child)

def do_remove(self, sv, child):
    """ウィジットから子を削除する"""
    obj.frame.remove(child)

def do_replace_child(self, obj, old, new):
    """
    ウィジットの子を置き換える
    これはあなたが削除するとき glade によって呼ばれる事に注意してください
    又は、実際に GladePlaceholder でウィジットが置き換わるのでウィジットを切り捨てます
    """
    obj.frame.remove(old)
    obj.frame.add(new)

これらのメソッドの実装は、特に straight-forward で、私たちの目的を実現します。そのトリックは何をするのかを知っています。get_children メソッドは適当な場所に配置される必要があります。そのため、Glade ウィジットは階層的に子ウィジットを正しく表示します。do_add, do_remove と do_replace メソッドは、ウィジットを追加、削除、置換する機能を持ちます。
再度、あなたがカタログとサポートモジュールをインストールするならば、それが実際に動作することに気付くでしょう。そのウィジットは正しい場所に追加され、あなたのウィジットの1つの子として階層的に表示されます。子プロパティについて GTK から警告を受け取るでしょう。それは child_property メソッドを書くことで解消されます:

def do_child_get_property(self, obj, child, prop):
    """プロパティ値を取り出す"""
    if prop in ['expand', 'fill']:
        return True
    elif prop == 'padding':
        return 0
    elif prop == 'position':
        return 0
    elif prop == 'pack-type':
        return gtk.PACK_START
    return True

def do_child_set_property(self, obj, child, prop, val):
    """プロパティ値をセットする"""
    if prop in ['expand', 'fill', 'padding', 'pack-type']:
        pass

私たちのウィジットは本当のコンテナ(アダプタを介するのみですが)のように振る舞うので、それは子プロパティをセットする方法に関してクエリを取得するでしょう。これらのほとんどが私たちにとって重要です。なぜなら、フレームは拡張性とパッキングの方法がとても少なく、大箱であるためにスペースがありません。従って、シンプルな値をただ返すことができます。同様にそこに何かをセットする必要がありません。

やってみよう

今回 Glade で私たちの高機能なウィジットを紹介しました。作ってみましょう。新しいプロジェクトを作成し、ウィンドウに私たちのウィジットを追加し、コンテナへサンプルウィジットを追加した後、幾つかのコード('test.glade' のようなファイル)と共にロードしてみてください:

import gtk
import gtk.glade

# glade がロードできるように gobject でこのタイプを登録するのを確認する
from serviceview import ServiceView

gtk.glade.XML('test.glade').get_widget('window1').show_all()
gtk.main()

それはどう見えたでしょうか?ひどい!そうです。glade ローダーは、フレーム内部というよりむしろ VBox の内部に子ウィジットを配置します!大災難だ!glade ローダーがしている事をあなたが理解するまではそうでしょう。それは子を追加するために ServiceView オブジェクトでメソッドを呼んでいます。

なぜなら VBox のサブクラスだからです。このメソッドは(最後にオブジェクトを追加するために)VBox 群用として振る舞います。取るに足らない複雑さがあります。私たちが Pythongtk.VBox で有効なメソッドを追加することは Glade が使用しているモノではありません。実際は Glade も使えるメソッドを使用します。Glade が使用するメソッドをオーバーライドすることは、"C add をオーバーライドする"といった種類の方法で do_add メソッドの実装を必要とします。

かつて、私たちはそのように実装しました。:

def do_add(self, child):
    self.frame.add(child)

そのウィジットは正しい場所で子としてロードします。

ディスカッション

このシステムで試しにやってみたい人たちがいるかもしれません。今はウィジット自身の中で do_add を、カスタムアダプタの do_add メソッドを削除するために実装しました。そして、私たちが望む実際の動作として理解するでしょう。あなたは、私たちのアダプタのためにスーパークラスとして GtkFrame アダプタを使用することができました。そして、正しい機能を持っていると理解します。

あなたのウィジット又はアダプタの、機能的なウィジットを実装する場所に関しての普遍の疑問へ繋がります。私たちはウィジット内の全てのコンテナインタフェースをオーバーライドし、アダプタなしでも正しく動作しました。しかし、もはや VBox のような振る舞いができないという不都合もあります。

一般的に、あなたが決める方向性は、自身の要求をベースとすべきです。しかし、それはその時に Glade3 を使ってミックスする必要があります。:

  1. Glade3 のためのカタログ
  2. Glade3 のためのカスタムアダプタ
  3. Glade ローダーのためのウィジットメソッド


このシリーズはまだ続きます:

  • カスタムウィジットエディタ
  • Python からの glade UI 制御


注意: API は早期に変更され、後方互換性は重要視していません。従って、如何なるコードも使い捨てとして用意されています。