Y901x」タグアーカイブ

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 に命令を送っているだけのチッポケなアプリですからね。

gnomevfs to gio.File

GIO tutorial: File operations ? Johannes Sasongko’s blog

こんな Blog を見つけた。
そうか、こうやれば gio.File からファイルタイプが取得できるんだ。

これでやっと gnomevfs の呪縛から逃れられそうだ。
もうすぐというか GNOME 3.0 から使えなくなるはず。

PyGObject Reference Manual
gio Constants#gio-file-attribute-constants

上記を見れば名前空間は何を指定すればいいか解るね。
standard::type 指定だと GFileType が戻ってくるので standard::content-type かな。

#!/usr/bin/env python
#-*- coding:utf-8 -*-

import sys
import gio

s = sys.argv[1]
f = gio.File(s)
info = f.query_info("standard::content-type")

print "name: {0}\ntype: {1}".format(s, info.get_content_type())

おぉ、これで拡張子が無くても content_type が取得できる!
MIME Type とずっと書いていたけど content_type であったみたい…

沢山の info を query するにはコンマ区切りで書くかワイルドカードを使う。
ただしコンマの前後にスペースを入れると上手くいかないのは何故だろう。

#!/usr/bin/env python
#-*- coding:utf-8 -*-

import sys
import gio

s = sys.argv[1]
f = gio.File(s)

# Not Space
info = f.query_info("standard::type,standard::size,standard::content-type")
#info = f.query_info("*")

print info.get_file_type()
print info.get_size()
print info.get_content_type()

ついでに GStreamer から得られる uri も gio で変換できるようだ。

uri = self.player.get_property("uri")
#t = urllib.unquote(uri)[7:]
t = gio.File(uri).get_parse_name()

意図的に日本語ファイル名にしても問題なく変換できた。
というより gio.File() ってフルパスでも uri でもどっちでもいいんだね。
とにかくこれなら urllib モジュールもいらないな、gio スゲェ便利。

ということで Y901x 0.3.4 公開。

そうそう、0.3.3 で dbus を取っ払ったんで本体は gtk.Window 派生に変更した。
多重起動防止はなくなったけどやっと普通な PyGtk アプリになった感じ。
追加機能はほとんどやらずにこんなことばかりやっていていいものか…

register_sinkfunc

Y901x を機動すると以下の推奨メッセージが出るようになった。

** Message: pygobject_register_sinkfunc is deprecated (GstObject)

問題なく動くからあまり気にしていなかったけどそろっそろ…
海外を探しても関係ないバグ情報ばかりで困っていたのだが…

ふと思いついて rhythmbox で試してみても同じだった。

どうせ見つからないと思うけど日本語で探してみた。
んー、これはもしかしなくても ATI なのが悪いのかな。

Ubuntu日本語フォーラム / Moovidaメディアセンターが起動できません

ということで GStreamer がおかしいのだろう。
お手上げだ。

ついでに発見。
Y901x のフルスクリーンは全体サイズをフルスクリーンにしていた。
けど GDK だけフルスクリーンでもイケたと何を今更知った。
GtkBox に配置しているパーツは hide しなきゃいけないみたいだけど。

def change_fullscreen(self):
    if self.p_filename == "":
        return
    if self.fullscreen:
        self.fullscreen = False
        self.w.window.unfullscreen()
        #self.w.unfullscreen()
        self.fullobj.destroy()
        self.fullobj = None
        self.menubar.show()
        if self.full_list:
            self.listbox.show()
        #self.w.resize(self.fullsize.width, self.fullsize.height)
    else:
        self.fullscreen = True
        #cx, cy = self.w.get_size()
        #self.fullsize.width = cx
        #self.fullsize.height = cy
        self.full_list = self.listbox.get_property("visible")
        if self.full_list:
            self.listbox.hide()
        self.menubar.hide()
        self.fullobj = CFullCtrl(
                self.hbox_ctrl,
                self.toolbox,
                self.vbox_main,
                self.statusbar
            )
        #self.w.fullscreen()
        self.w.window.fullscreen()

つまり gtk.Window.window.fullscreen() 処理だけでよかった。
全体をフルスクリーンにして後でサイズを戻す処理は必要ないんだね。

コレならフルスクリーン中にリストを出す処理も簡単に実装できるかな?
そういえばリストを下に移動する処理を入れるって前書いたっけな。
ユーザーは多分自分だけだと思うからどうするのも勝手だけど。

text in liststore

そろそろ Y901x に機能追加をしようと先日より少しづつ弄くっております。
作った本人以外に使っている人がいないかもしれないがイイじゃないか。
少なくとも作っている本人は使っているんだから。

とりあえずファイルリストを Y901 と同様な下に位置変更できるようにしたい。
変更方法は Y901 と同じリストの右クリックメニューと Ctrl+F12 でいいだろう。
ということでリストに右クリックメニューを付けて上手く動くか実験中。
位置変更は空の GtkBox を配置してパレント変更するだけなので難しくないと思う。

そういえば現行では辞書で保持している設定を __slots__ 付き class に変更したい。
そうしておいたほうが後々の変更で凡ミスを防げる確率が高くなるはず。

self.setting = {"mimes": mimes,
                "position": False,
                "init_size_on": False,
                "init_size_val": 0,
                "esc_exit": False,
                "mem_rep": False,
                "mem_aspect": False,
                "loop": False,
                "severity": 25,
                "aspect_rate": [1,1],
                "set_size": [[320,240],[640,480],[1280,720]]}

# ↓

class CSetting(object):
    __slots__ = [
            "mimes", "position", "init_size_on", "init_size_val", "esc_exit",
            "mem_rep", "mem_aspect", "endressloop", "severity", "aspect_rate",
            "set_size"]
    def __init__(self):
        self.mimes = MIMES
        self.position = False
        self.init_size_on = False
        self.init_size_val = 0
        self.esc_exit = False
        self.mem_rep = False
        self.mem_aspect = False
        self.endressloop = False
        self.severity = 25
        self.aspect_rate = [1,1]
        self.set_size = [[320,240],[640,480],[1280,720]]

定数扱いなのに小文字だった mimes とかはキチンと大文字に変えて…
loop とか自分でもドレに相当するか解りにくい変数名は解りやすく変えて…
今になって見ると我ながら初心者丸出しで切ないよ。

setting[

で検索して地味に手書きで書き換えたけどドットを忘れるとかで間違えまくる。
ドット忘れだと動的言語は新規変数と扱ってしまうから間違いを見つけるのが大変。
問題なく動いている所をわざわざ書き換えてバグを作っているような気がするわな。

ところでリスト中にファイル名が存在するかを調べるのに

class CListBox(gtk.HBox):
    def __init__(self, window):
        self.sw = gtk.ScrolledWindow()
        self.view = gtk.TreeView()
        self.sw.add(self.view)
        self.liststore = gtk.ListStore(str)
        self.view.set_model(self.liststore)
        # etc...

    def is_text(self, text):
        # text in ListStore ?
        model = self.view.get_model()
        it = model.get_iter_first()
        while it:
            if model.get_value(it, 0) == text:
                return True
            it = model.iter_next(it)
        return False

こんな is_text というメソッドを作っていたのですが

def is_text(self, text):
    return text in [ r[0] for r in self.liststore ]

コレだけでイケたのね…
PyGtk って知れば知るほど親切な仕様だ。

とにかく書き換え箇所が多いので当面は自分でバグ探しになりそう。
ということでいつものようにバックアップ。
y901x-0.3.2b1.tar.gz

Human sort 2

仕事関連でドタバタしており Blog の更新が止まりがちになっています。
今月は落ち着きそうな希望が少々持てるのでちょっぴりがんばってみる予定。
ということで

前々回(って半月前か…)

の自然順ソート仕様で我が Y901x のリストアップを色々試していた。
サボっていたのではなく地味にテストはしていた、とにかく解ったのは問題だらけ!

最大の問題は aa.mp4 aa1.mp4 等というファイルがあったとき。
Nautilus では aa.mp4 が当然のように上になる、前々回の関数では逆になってしまう。

原因が解らなくてしばらく悩んだけど解ってしまえば単純。
前々回のコードではドットは単なる文字列の一部としか認識しないからだ。
分離され整数変換された値とドットという文字列を比較ならば当然ドットのほうが大きな値と認識する。
結果 aa.mp4 のほうが aa1.mp4 より大きいと認識するファイル名になってしまう。

なんだそれ…
これじゃ拡張子が付いたファイル名では全部同じようになってしまう。
いや Linux の場合はそれだけじゃない、ドットファイルという文化があるのだから。
以下のようなファイル名を用意してテスト、nautilus は隠しファイルを表示設定に。

Python の文字列比較は先頭から一文字単位で照合しているだけ、当然の結果。
整数文字列の比較は確かに行っているけどこうなってしまう、落とし穴が深すぎだった。
Windows の Explorer 名前順ではドットファイルは先頭になるが Nautilus は日本語より下。
同じ自然順ソートでもドットをどう扱うかが違うようで。

とにかくファイル名の場合は前々回のソート関数では Nautilus とは同じにはならない。
というかドットファイルどころかバックアップファイルにも無考慮だと今頃気がつく私であった。
後日に続く(終わりカヨ!