Dynamic Menu in GtkUIManager

PyGtk でメニュー項目の動的な追加と削除。
コレををやらないとマルチトラック音声の切り替え機能が実装できない。

GtkUIManager を使うのが一番簡単、てか私はこの方法しか知らない。
Gedit プラグインでメニューを追加するのと同じ方法でいいはず。

Gedit プラグインの作り方 – L’Isola di Niente

切り替え自体はラジオメニューに、Totem もそうなっているし。
ということは GtkActionGroup には以前書いたコレを指定すればいいかな。

GtkRadioAction でラヂオメニューとツールバーを同期させる

しらない間に自身で方法だけは書いていた。
この2つをまとめれば動的なメニュー項目の追加削除ができそうなのでやってみる。

class AudioMenu(object):
    __slots__ = ["uimanager", "ui_id", "action_group"]
    def __init__(self, uimanager):
        self.uimanager = uimanager
        self.ui_id = -1

    def add_menu(self, actions, xml, callback, value):
        self.action_group = gtk.ActionGroup("AudioMenuActions")
        self.action_group.add_radio_actions(actions, value, callback)
        self.uimanager.insert_action_group(self.action_group, -1)
        self.ui_id = self.uimanager.add_ui_from_string(xml)

    def remove_menu(self):
        if self.ui_id != -1:
            self.uimanager.remove_ui(self.ui_id)
            self.uimanager.remove_action_group(self.action_group)
            self.uimanager.ensure_update()
            self.ui_id = -1
            del self.action_group

一ヶ所でしか使わないけど class にしたほうがメンテナンスが楽なので。

GtkRadioActionEntry List とメニュー XML とコールバックと選択するラジオメニュー値
を引数に渡してメニューを作成する。
メニューの削除は Gedit プラグインとまったく同じで大丈夫だろう。
GtkActionGroup は同じアクションを追記してしまうので都度新規作成と削除をする。

この引数に指定するコールバックを本体クラスに。

def on_soundmenu(self, action, current):
    """
        Multi Track Audio Select
    """
    n = action.get_current_value()
    self.player.set_property("current-audio", n)
    # 下記は本体側の設定保存用
    self.settingwin.n_audio = n

これだけでラジオメニューのどこが選択されているかで勝手にトラック選択になる。

後は GtkActionGroup の List と XML 文字列を黙々と完成させればいい。
とりあえず XML の大雑把なテンプレートを用意。

audio_str = """<ui>
    <menubar name="MenuBar">
        <menu action="File">
            <menu action="sound">
            {0}
            </menu>
        </menu>
    </menubar>
    <popup name="pop_main">
        <menu action="sound">
        {0}
        </menu>
    </popup>
</ui>
"""

やはり Gedit プラグイン同様にメニューの XML を用意する。
自分で作った XML ツリーだからまったく迷わないwww

str.format() なら一つの引数で {0} に入るから間違えない。
新しいフォーマッタはこういう場合には便利だね。

asink = self.player.get_property("audio-sink")
if asink:
    # versioon 0.3.6
    # 一度メニューを空にする
    self.audiomenu.remove_menu()
    # オーディオトラック数で振り分ける
    n_audio = self.player.get_property("n-audio")
    if n_audio == 1:
        self.player.set_property("current-audio", 0)
    else:
        actions = []
        x = ""
        for i in range(n_audio):
            a = "sound{0}".format(i)
            s = "トラック #{0}".format(i)
            # GtkRadioActionEntry の List を作成
            # name, stock_id, label, accelerator, tooltip, value
            t = (a, None, s, None, s, i)
            actions.append(t)
            x += '<menuitem action="{0}"/>'.format(a)
        xml = audio_str.format(x)
        # メニューに追加
        self.audiomenu.add_menu(actions, xml, self.on_soundmenu, self.settingwin.n_audio)
        self.player.set_property("current-audio", self.settingwin.n_audio)

こんな感じで for ループでひたすら文字列を加工し作成。
GtkRadioActionEntry 作成は PyGtk の場合は単なるタプルでいいので簡単。
GtkMenuItem の XML も当然文字列なので同時に作って最後にテンプレートに流し込む。

新しいことは得に何もやっていないのでスンナリと動的メニューが完成した。
まぁ GTK+ と GStreamer に命令を送っているだけのチッポケなアプリですからね。