Python」タグアーカイブ

ClutterActor on GtkContainer

Clutter は魅力的ではあるが描写の全部を自前で用意する必要がある。
つまり GtkButton, GtkListView のような Widget は無い。

それなら ClutterActor を GtkContainer 上に pack できればよくね?
やってみると普通に拒否、全くの別物だからそりゃ当然の話だ。

しかし GNOME3 自体はしっかり連携している。
知らないだけで何か方法があるはずだ。

/usr/lib64/girepository-1.0
をよく見ると GtkClutter というものがある。

Clutter Gtk integration on Vimeo

WPF のような新規ウイジェットではなく GTK+ をエフェクトするみたい。
そりゃ GTK+3 自体が新規だし元から連携していてもおかしくない。

Clutter-Gtk 1.0.2 Reference Manual

どうやら GtkClutterWindow は GtkWindow のサブクラス。
ClutterActor が表面に張り付いている状態って認識でいいのかな?
Python からは get_stage() で ClutterStage が取得できそうである。

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

import sys
from gi.repository import Gtk, Clutter, GtkClutter, ClutterX11

def quit(widget, data=None):
    Gtk.main_quit()

#ClutterX11.set_use_argb_visual(True)
#ClutterX11.set_display(ClutterX11.get_default_display())
#ClutterX11.disable_event_retrieval()
Clutter.init(sys.argv)

w = GtkClutter.Window()
w.connect("destroy", quit)
w.set_title("GtkClutter.Window")
w.resize(300, 200)

stage = w.get_stage()
stage.set_size(300.0, 200.0)
w.show_all()
Gtk.main()

WARNING 出まくり、初期化とか色々やってみたけど駄目だった。
とりあえず get_stage() だけはできている、使い方が悪いのだろう。
ついでにこの場合 delete-event ではなく destroy 指定でないと終了しない。

直感でやっても上手くいかない、もっと良い方法は無いだろうかと検索。

Stage Widget

gtk_clutter_embed_new ()
で作成した Object なら Gtk+ コンテナに入れられるようだ。
試すと上手くいったので ClutterGst でやってみる。

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

import sys
from gi.repository import Gtk, Clutter, GtkClutter, ClutterGst

class ClutterEmbedTest(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self)
        # Video Texture
        tx = ClutterGst.VideoTexture()
        tx.set_uri("file:///home/sasakima-nao/movie/bike/ninjya1000_1.mp4")
        # Create the Clutter Widget
        embed = GtkClutter.Embed.new()
        embed.connect("size-allocate", self.on_size_allocate, tx)
        # ClutterStage
        stage = embed.get_stage()
        stage.add_actor(tx)
        # Buttons
        play = Gtk.Button(stock=Gtk.STOCK_MEDIA_PLAY)
        play.connect("clicked", self.on_play, tx)
        # packing
        vbox = Gtk.VBox()
        vbox.pack_start(embed, True, True, 0)
        vbox.pack_start(play, False, False, 0)
        self.add(vbox)
        self.set_title("ClutterEmbedTest")
        self.connect("delete-event", self.on_quit)
        self.show_all()

    def on_size_allocate(self, widget, allocation, tx):
        w = widget.get_allocation().width
        h = widget.get_allocation().height
        tx.set_size(w, h)

    def on_play(self, widget, tx):
        tx.set_playing(True)

    def on_quit(self, widget, data=None):
        Gtk.main_quit()

if __name__ == '__main__':
    # initialized
    Clutter.init(sys.argv)
    ClutterGst.init(0, "")
    ClutterEmbedTest()
    Gtk.main()

よしコレなら何も警告は出ない。
警告が出ないようガンバった WPF 3.0 アプリが 3.5 で無意味にされた過去が…
初心者はワケワカだろうけど、こんな苦労も数年後には無意味になるのでしょう。

やはり親ウインドウサイズに連動しないので size-allocate シグナルを利用。
ClutterActor のサイズは float だけど int のままサイズ変更できた。
コレで GTK+ Widget と ClutterActor の共存アプリケーションができる。

アスペクト比保持とかはどうやるんだろう?
というか最初のビデオでは GTK+ widget にエフェクトが掛かっていたような…
まだ先は長い、リファレンスマニュアル無しで作るのは狂気だがコレが楽しい。

ClutterGst PyGI

どうも GTK3 は xid を得る方法も全然違うようで。
python – DrawingArea Cannot Get XID – Stack Overflow

expose-event が draw に変わっていたりでもうよく解らない。
PyGI で Gst を扱う情報が見つからず全然アプリ開発が進まない。

ClutterGst というのがあるけどよく解らないので検索。

PyClutter video tutorial ? Hindsight Labs

こんなのを見つけたけど古いバインディングだ。
PyGI で書き直して実験してみることにした。

いくら探しても ClutterGst の Reference Manual が見つからない。
しかたがないので dir() で漁って例外で確認しているけどよくワカラン。
例外で playbin2 がどうとか出た、描写エンジンは playbin2 なんだね。

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

import sys
from gi.repository import Clutter, ClutterGst

class TestStage(Clutter.Stage):
    def __init__(self):
        Clutter.Stage.__init__(self)
        # ClutterGst
        self.tx = ClutterGst.VideoTexture()
        self.tx.set_uri("file:///home/sasakima-nao/movie/bike/ninjya1000.flv")
        self.tx.set_playing(True)
        self.add_actor(self.tx)
        # self
        self.connect("destroy", self.quit)
        self.set_title("ClutterGst Test")
        # Not linked to the window size
        self.set_size(400, 400)
        self.show_all()

    def quit(self, widget):
        Clutter.main_quit()

if __name__ == '__main__':
    init = Clutter.init(sys.argv)
    if init[0] == Clutter.InitError.SUCCESS:
        # Param (argc, argv) ???
        ClutterGst.init(0, "")
        TestStage()
        Clutter.main()

ClutterGst も専用の初期化が必要であるようです。
パラメータが int と str だったけどもしかして C 言語方式?
sys.argv シーケンスでは展開してくれない。

pygst みたく Window 枠に合わせて描写されない。
当然 DirectShow みたくウインドウより大きな映像ははみ出す。

絶対位置描写だと又ウインドウサイズ計算が面倒くさくなる…
設定とかあるのかな、もう少し弄くってみるけど。

GtkFontChooserDialog

Ubuntu 11.10 で Gedit のフォント選択が変わっていた。
というか思いっきり文字化けしているので調べてみた。

GNOME 3.2 リリースノート

GtkFontChooserDialog という新規ウイジェットを使っているみたいね。
それならばチト Python で作ってみよう。
gi の動的バインディングは pygtk 時のようにプロジェクトの方々が静的バインドしてくれるのを待たなくても即利用できるようになったのが嬉しいね。

GtkFontChooserDialog

単なる GtkDialog のサブクラスのようだ。
ならば run() destroy() で利用できるはず。
てゆーか専用メソッドもプロパティも無いのではどうやって得るのだ?
多分ドキュメント化の遅れだろうけど。

GtkFontChooser

dir() で漁るとどうやら GtkFontChooser の関数がダイアログに直接利用できるようだ。
それならばこうしてみよう。

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

from gi.repository import Gtk

dlg = Gtk.FontChooserDialog("GTK+ 3.2 new Dlg")
if dlg.run() == Gtk.ResponseType.OK:
    print dlg.get_font()
    print dlg.get_font_size() / 1024
dlg.destroy()

で、選択した font の名前とサイズが表示できる。
というか Gedit の文字化けとまったく同じになるじゃん!

とはいえ、こんな使い方をする人はいないだろう。
以下は GtkEntry の Font を選択して変更するサンプル。

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

from gi.repository import Gtk, Gio

class Win(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self)
        self.set_title("font test")
        entry = Gtk.Entry()
        entry.set_text("homurachan")
        button = Gtk.Button("Select Font")
        button.connect("clicked", self.on_clicked, entry)
        vbox = Gtk.VBox()
        vbox.pack_start(button, False, False, 0)
        vbox.pack_start(entry, False, False, 0)
        self.add(vbox)
        self.show_all()

    def on_clicked(self, widget, entry):
        dlg = Gtk.FontChooserDialog("Select Font")
        if dlg.run() == Gtk.ResponseType.OK:
            desc = dlg.get_font_desc()
            entry.modify_font(desc)
        dlg.destroy()

class App(Gtk.Application):
    def __init__(self):
        Gtk.Application.__init__(
                self,
                application_id="apps.test.font",
                flags=Gio.ApplicationFlags.FLAGS_NONE)
        self.connect("activate", self.on_activate)
        
    def on_activate(self, data=None):
        w = Win()
        w.set_application(self)
    
if __name__ == "__main__":
    app = App()
    app.run(None)

とりあえず fedora 16 ではアルファベットになっていることを祈る。
どこで設定しているのだろう。

しかし Unity 2D で使っているせいかもしれないけど…
Gedit にファイルドロップすると落ちるんだが…
他に外部ツール設定で範囲の languages を指定しようと思ってもポップアップがスクロールできないし、dconf-editor で gedit の smart-home-end の設定をしようとすると説明欄が最大化して何も見えなくなるし…
これではまったく使い物にならない、不具合が多すぎる。

Ubuntu 11.10 Python GTK+3

まったく気がつかない間に Ubuntu 11.10 が出ていた。
IT 系サイトも見ていたはずなんだけど…
一時期の盛り上がりは何だったんだ…

とにかく 11.10 は GTK+3 が使えるようになったかチェックだ。

日本語 Remix は今回も 32bit 版のみ、ガッカリだよ。
インストール方法は CD を入れればバカでも解るので解説はいらない。

Fedora 15 同様にアップデートではパスワード要求されなくなったみたい。
他、使用感とかは他サイトにまかせるとして。
以下は pygi 等のプログラミング関連。

やはり Nautilus や Gedit の GNOME アプリは GTK+3 になっている。
まず Python で GTK+3 や Clutter が利用できるかチェックする。

import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Clutter

gi で Gtk3 を指定しても例外にならない、よし GTK+3 は使える。
というかデフォルトが 3.0 側になるので GTK+2 の場合は 2.0 を指定。
Clutter は使えない、3D 機能は compiz なのだから当然かもね。

まてよ?
GTK+3 化したのにテーマが変わっていないということは…

for PyGtk (GTK+2)

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

import gtk

class Win(gtk.Window):
    def __init__(self):
        gtk.Window.__init__(self)
        self.set_title("Gtk2")
        button = gtk.CheckButton("GTK+2 GtkCheckButton")
        button.set_property("active", True)
        self.add(button)
        self.connect("delete-event", gtk.main_quit)
        self.show_all()
    
if __name__ == "__main__":
    Win()
    gtk.main()

for PyGi (Gtk+3)

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

from gi.repository import Gtk

class Win(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self)
        self.set_title("Gtk3")
        button = Gtk.CheckButton("GTK+3 GtkCheckButton")
        button.set_property("active", True)
        self.add(button)
        self.connect("delete-event", Gtk.main_quit)
        self.show_all()
    
if __name__ == "__main__":
    Win()
    Gtk.main()

やっぱり見た目は同じになるようだ。
これではいったいどのアプリケーションが GTK+3 化されたのか解り辛い。

しかも GTK+2 だとテーマエンジンが無いと WARNING が出るし。
アップグレードなら GTK+2 テーマエンジンは残ると思うけど。
つまり Firefox も GTK+2 なので WARNING が echo されると…

Fedora から半年遅れで GTK+3 を投入したのにコレだよ。
相変わらず Ubuntu はツメが甘いままリリースするようだ。

とにかく python から GTK+3 が使えるのは解った。
もう少し細かいことに気がついたら後日追記します。

Python with GTK+3 WebKit Browser 2

いやいや。
Original Webkit Browser @ Fedora 15 | PaePoi
の最後で app.run(sys.argv) と書いていたんだが…
イザ引数を指定してみるとファイルを開けないというエラーになるじゃん。

GApplication#GApplicationFlags

GtkApplication 作成時の GApplicationFlags の指定が悪いのかも。
と HANDLES_OPEN に変更すると今度は展開できないという Warning になる。

展開できれば on_open の files 引数に GFile がリストになって…
いや引数がファイルであるとは限らない、URI や設定の可能性もあるし多分フラグの使い方が違うのだろう。
どっちにしろ app.run(None) に書き換えるしかなさそうだ。

多重起動防止処理にして引数を起動している Window に渡したいのだが…
activate シグナルの時点で GtkApplication にアトリビュートとしてくっ付けた変数値は前回起動時の値にどうしても戻される。
合体するのではなく既存の GtkApplication に転送されているのかな?
GSettings とかで他の場所に URI を一時保存するしか無いかな…

それと delete-event 処理を入れていなかった。
閉じるボタンを押すと飛んでくるシグナルね、ここで設定保存する予定なので。
メニューのハンドラからハンドラを呼べばツジツマが合う

どうでもいいけど ~/bin に入れたので拡張子を取っ払ってみたら…
[Qt Designer ファイル] になっちゃった!

#!/usr/bin/env python と先頭に書いても XML 部分を優先して認識するっぽい。
GIO による content-type 認識はイマイチであるようだ。
XML 部分を最後のほうに移動してなんとかなった。

変更箇所多すぎ…
前回書いた local アクセスの件もあるし丸ごと書き換えるとするか。
せっかくなので「進む」「戻る」「ホーム」のボタンも付けた。

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

import sys, os
from gi.repository import WebKit, Gtk, Gio, Gdk

HOME_URI = "http://palepoli.skr.jp/wp/"
APP_NAME = "wkitx"

class WebKitWindow(Gtk.Window):
    """
        WebKit PyGi Version
        from Fedora 15 x86_64
    """
    def __init__(self):
        # Window
        Gtk.Window.__init__(self)
        self.resize(640, 480)
        self.set_title(APP_NAME)
        self.connect("delete-event", self.on_delete)
        # GtkUIManager and GtkAccelGroup
        self.uimanager = Gtk.UIManager()
        accelgroup = self.uimanager.get_accel_group()
        self.add_accel_group(accelgroup)
        # GtkActionGroup
        self.actiongroup = Gtk.ActionGroup("seeme_menu")
        # GtkActionEntry
        self.ac = [ ("forward", Gtk.STOCK_GO_FORWARD, "_Forward", "<Alt>Right", "Forward", self.on_forward),
                    ("back", Gtk.STOCK_GO_BACK, "_Back", "<Alt>Left", "Back", self.on_back),
                    ("reload", Gtk.STOCK_REFRESH, "_Reload", "F5", "Reload", self.on_reload),
                    ("quit", Gtk.STOCK_QUIT, "_Quit", "<Control>Q", "Quit", self.on_quit),
                    ("file", None, "_File") ]
        self.actiongroup.add_actions(self.ac)
        self.uimanager.insert_action_group(self.actiongroup, 0)
        self.uimanager.add_ui_from_string(menu_xml)
        # menubar
        menubar = self.uimanager.get_widget("/MenuBar")
        # entry
        self.entry = Gtk.Entry()
        self.entry.connect("activate", self.on_entry_activate)
        # toolbar
        toolbar = Gtk.HBox()
        toolbar.set_border_width(3)
        back = FlatImageButton(Gtk.Image.new_from_stock(Gtk.STOCK_GO_BACK, Gtk.IconSize.BUTTON))
        back.connect("clicked", self.on_back)
        forward = FlatImageButton(Gtk.Image.new_from_stock(Gtk.STOCK_GO_FORWARD, Gtk.IconSize.BUTTON))
        forward.connect("clicked", self.on_forward)
        home = FlatImageButton(Gtk.Image.new_from_stock(Gtk.STOCK_HOME, Gtk.IconSize.BUTTON))
        home.connect("clicked", self.on_home)
        toolbar.pack_start(back, False, False, 0)
        toolbar.pack_start(forward, False, False, 0)
        toolbar.pack_end(home, False, False, 0)
        toolbar.pack_start(self.entry, True, True, 0)
        # WebKit
        self.webview = WebKit.WebView()
        self.webview.connect("load-started", self.on_load_started)
        self.webview.connect("load-finished", self.on_load_finished)
        self.webview.connect("title-changed", self.on_title_changed)
        self.webview.connect("hovering-over-link", self.on_hovering_over_link)
        # ScrollWindow
        sw = Gtk.ScrolledWindow()
        sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
        sw.add(self.webview)
        # statusbar
        self.statusbar = Gtk.Statusbar()
        # setting
        setting = self.webview.get_settings()
        setting.set_property("enable-file-access-from-file-uris", True)
        # main packing
        vbox = Gtk.VBox()
        vbox.pack_start(menubar, False, True, 0)
        vbox.pack_start(toolbar, False, True, 0)
        vbox.pack_start(sw, True, True, 0)
        vbox.pack_start(self.statusbar, False, True, 0)
        self.add(vbox)
        self.show_all()
        self.set_focus(self.webview)
        # argv
        if len(sys.argv) > 1:
            self.webview.load_uri("file://"+ os.path.abspath(sys.argv[1]))
        else:
            self.webview.load_uri(HOME_URI)

    def on_delete(self, widget, data=None):
        pass

    def on_load_started(self, webview, frame):
        self.statusbar.push(0, "Loading...")

    def on_load_finished(self, webview, frame):
        self.statusbar.push(0, "")

    def on_title_changed(self, webview, frame, title):
        self.set_title("{0} - {1}".format(title, APP_NAME))
        self.entry.set_text(webview.get_uri())

    def on_hovering_over_link(self, webview, title, uri):
        if uri:
            self.statusbar.push(0, uri)
        else:
            self.statusbar.push(0, "")

    def on_entry_activate(self, entry):
        self.webview.load_uri(entry.get_text())

    def on_back(self, widget, data=None):
        if self.webview.can_go_back():
            self.webview.go_back()

    def on_forward(self, widget, data=None):
        if self.webview.can_go_forward():
            self.webview.go_forward()

    def on_reload(self, widget, data=None):
        self.webview.reload()

    def on_home(self, widget, data=None):
        self.webview.load_uri(HOME_URI)

    def on_quit(self, widget, data=None):
        app = self.get_application()
        app.remove_window(self)
        self.on_delete(widget)

class App(Gtk.Application):
    def __init__(self):
        Gtk.Application.__init__(
                self,
                application_id="apps.wkitx.webkit",
                flags=Gio.ApplicationFlags.FLAGS_NONE)
        self.connect("activate", self.on_activate)

    def on_activate(self, app):
        l = self.get_windows()
        if l:
            # Is the sys.argv of the previous...
            #if len(sys.argv) > 1:
            #   l[0].webview.load_uri("file://"+ os.path.abspath(sys.argv[1]))
            return
        w = WebKitWindow()
        w.set_application(self)

"""
    etc...
"""

class FlatImageButton(Gtk.Button):
    def __init__(self, image, label=None, stock=None, use_underline=True):
        Gtk.Button.__init__(self, label, stock, use_underline)
        self.set_relief(Gtk.ReliefStyle.NONE)
        self.set_can_focus(False)
        if image:
            self.set_image(image)

def messagebox(parent, text, icon=Gtk.MessageType.WARNING, button=Gtk.ButtonsType.OK):
    dlg = Gtk.MessageDialog(
            parent,
            Gtk.DialogFlags.MODAL,
            icon,
            button,
            text)
    dlg.set_title(APP_NAME)  
    r = dlg.run()  
    dlg.destroy()
    return r

menu_xml = """<ui>
    <menubar name="MenuBar">
        <menu action="file">
            <menuitem action="forward"/>
            <menuitem action="back"/>
            <menuitem action="reload"/>
            <separator/>
            <menuitem action="quit"/>
        </menu>
    </menubar>
</ui>"""

if __name__ == "__main__":
    app = App()
    #app.run(sys.argv)
    app.run(None)

設定変更とかはまだ無いので HOME_URI とかサイズは自分で書き換えてね。
wkitx と仮の名前を付けているが私のことだから多分最後までこのままだろう。

多重起動防止の URI 転送はできないけどパラメータ処理とホームの振り分けは付けた。
Gedit の External Tool から利用できるようにしたかったんだもん。
今まで clipolix から起動していたけど私的にはもう利用できるレベルかなと。
本格的に作るならタブも考えるが、まず URI 転送をどうにかしなければ…

それと、せっかく GTK+3 専用なので GSettings を使おうと思ったけど…

Using GSettings with Python/PyGObject

/usr/share/glib-2.0/schemas ディレクトリに XML schema を置く必要があるんだね。
ということはインストールする前提で作ったアプリにしか使えないなこれは。
でも登録さえ行っていれば

GSettings

settings = Gio.Settings.new("apps.wkitx.conf")
widtth = settings.get_int("window-widtth")

みたく簡単に扱えるのね、後で考える。

Ubuntu 11.10 Beta 2 Screenshots | Linux, BSD, Solaris & Other OS Screenshots | The Leader in Linux, BSD, Solaris & Other OS Screenshots | Screen Shots of Linux Distributions, BSD, Solaris & Other OSes | Linux, BSD, Solaris & Other OS Screenshot Gallery

Ubuntu 11.10 の Nautilus 画像を見ると GTK+3 版になっているみたいね。
ということは 11.10 からは上記コードが動くかもしれない。
現状では Fedora 15 専用なので環境が増えるのは嬉しいよ。