Python」タグアーカイブ

GtkTable to GtkGrid

GtkTable は GTK+3.2 で廃止になっていた。
当面は使えるみたい、だけどいつ無くなるか解らないって怖い。

seemex は GTK+3.0 の時に作ったし公開終了したからからセフセフ。
GtkVBox や GtkHBox という非推奨も使っていたりするがもうシラネ。

しかし GtkGrid を代替で使えということだけど全然引数が違っている。

まず GtkTable は左上が頂点で絶対値にて四角形を指定だった。
GtkGrid は幅と高さを指定、これでは全置換できないので全書き換えになる。

それより attach(…) の引数から Fill や padding を指定することができない。
それらしき関数も見当たらない、ヘルプもチト解りにくい。
どうやらそれらは GtkWidget 側のプロパティで指定するようだ。
こんなプロパティがあったことすら知らなかったが。

GtkWidget.html#GtkWidget–expand

リサイズ時に引き伸ばすなら expand Property を True にする。
padding Property は無いけど margin Property でイケそうだ。

Gtk+ はコンテナ側にこの指定があるのが少し不便だった。
どう考えても子ウイジェット側にあったほうがウイジェットの入れ替えやパレントの変更がスムース、実際 Windows の WPF はそうなっている。
やはり代えたいのだろうと憶測。
GtkBox なんかも今後そうなっていくかもしれない。
reparent した後に set_child_packing 必須って意味わかんなかったし。

さて、これが解ったところで。

いつものように画像検索から面白そうなことをやっていそうな人を探す。
日本人には最初から期待していない、おかげでリンク先が海外ばかりなこのブログ。
しかし今回はイマイチなものしか見当たらない、
教科書的というか、実用的な解説をしている人が見当たらないというか。

しかたがないので自力でやって確認してみる。
seemex の編集部分に使っていた GtkTable を抜いて GtkGrid に書き換えてみた。
非推奨関数とテキトーだったところは書き換え、外国人が解るようにヘタクソな英語で。

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

from gi.repository import Gtk

EDITOR_LABELS = ("Name", "Key", "URL", "Query")
EDITOR_CHECK = "Is POST"
ENTER_BUTTON = "_Edit"

class TableWin(Gtk.Window):
    def __init__(self):
        """
            GtkTable has been deprecated. Use GtkGrid instead.
            table.attach (  child,
                            left_attach,
                            right_attach,
                            top_attach,
                            bottom_attach,
                            xoptions=Gtk.AttachOptions.EXPAND,
                            yoptions=Gtk.AttachOptions.EXPAND,
                            xpadding=0,
                            ypadding=0 )
        """
        Gtk.Window.__init__(self)
        self.set_title("Table")
        self.connect("delete-event", Gtk.main_quit)
        # GtkTable
        table = Gtk.Table(5, 3)
        # Edit Widget
        self.edit_name = Gtk.Entry()
        table.attach(self.edit_name, 1, 2, 0, 1)
        self.edit_key = Gtk.Entry()
        table.attach(self.edit_key, 3, 4, 0, 1, Gtk.AttachOptions.FILL)
        self.edit_url = Gtk.Entry()
        table.attach(self.edit_url, 1, 5, 1, 2)
        self.edit_query = Gtk.Entry()
        table.attach(self.edit_query, 1, 4, 2, 3)
        self.check_post = Gtk.CheckButton.new_with_label(EDITOR_CHECK)
        table.attach(self.check_post, 4, 5, 2, 3, Gtk.AttachOptions.FILL)
        # Label
        labels = []
        for label in EDITOR_LABELS:
            labels.append(Gtk.Label(label))
        table.attach(labels[0], 0, 1, 0, 1, Gtk.AttachOptions.FILL)
        table.attach(labels[1], 2, 3, 0, 1, Gtk.AttachOptions.FILL, xpadding=10)
        table.attach(labels[2], 0, 1, 1, 2, Gtk.AttachOptions.FILL)
        table.attach(labels[3], 0, 1, 2, 3, Gtk.AttachOptions.FILL)
        # Button
        self.button = Gtk.Button.new_with_mnemonic(ENTER_BUTTON)
        table.attach(self.button, 4, 5, 0, 1, Gtk.AttachOptions.FILL)
        #
        self.add(table)
        self.show_all()

TableWin()
Gtk.main()
#!/usr/bin/env python
#-*- coding:utf-8 -*-

from gi.repository import Gtk

EDITOR_LABELS = ("Name", "Key", "URL", "Query")
EDITOR_CHECK = "Is POST"
ENTER_BUTTON = "_Edit"

class GridWin(Gtk.Window):
    def __init__(self):
        """
            GtkGrid
            grid.attach(child, left, top, width, height)
            or
            grid.attach_next_to(child, sibling, GtkPositionType, width, height)
            
            GtkAttachOptions to GtkWidget 'expand' Property
            Padding to GtkWidget 'margin' Property
        """
        Gtk.Window.__init__(self)
        self.set_title("Grid")
        self.connect("delete-event", Gtk.main_quit)
        # GtkGrid
        grid = Gtk.Grid.new()
        # Edit Widget
        self.edit_name = Gtk.Entry()
        # Substitute Gtk.AttachOptions.EXPAND
        self.edit_name.props.expand = True
        grid.attach(self.edit_name, 1, 0, 1, 1)
        self.edit_key = Gtk.Entry()
        grid.attach(self.edit_key, 3, 0, 1, 1)
        self.edit_url = Gtk.Entry()
        self.edit_url.props.expand = True
        grid.attach(self.edit_url, 1, 1, 5, 1)
        self.edit_query = Gtk.Entry()
        self.edit_query.props.expand = True
        grid.attach(self.edit_query, 1, 2, 3, 1)
        self.check_post = Gtk.CheckButton.new_with_label(EDITOR_CHECK)
        grid.attach(self.check_post, 4, 2, 1, 1)
        # Label
        labels = []
        for label in EDITOR_LABELS:
            labels.append(Gtk.Label(label))
        grid.attach(labels[0], 0, 0, 1, 1)
        grid.attach(labels[1], 2, 0, 1, 1)
        grid.attach(labels[2], 0, 1, 1, 1)
        grid.attach(labels[3], 0, 2, 1, 1)
        # Substitute xpadding
        labels[1].props.margin_left = 10
        labels[1].props.margin_right = 10
        # Button
        self.button = Gtk.Button.new_with_mnemonic(ENTER_BUTTON)
        grid.attach(self.button, 4, 0, 1, 1)
        self.add(grid)
        self.show_all()

GridWin()
Gtk.main()

gtkgrid

よしリサイズでの引き伸ばされかたも完全に同じだ。
GtkGrid はデフォルトが Fill なので引き伸ばしたい Widget の expand を True にすればいいということなのね。

つか、やっぱりコードは似ているようで全然違うわw
子ウイジェット側で引き延ばし指定を行うほうがやはり理解しやすいなと思う。
たとえ記述量が多くなっても理解しやすいほうがいいなと。

GtkButtonBox

GtkButtonBox って何に使うか解らなかった。
検索したら面白そうなのを見つけた。

PHP-GTK по-русски: Группирование кнопок

ドメインは jp だけど思いっきりロシア語なのは何故だろう。
モチロン読めないけど PHP-GTK コードはアルファベットなので解る。
PHP-GTK って使っている人がいるんだな…

Standard Enumerations

GTK_BUTTONBOX_DEFAULT_STYLE って定義は無いんだけど。
デフォルトを調べたら EDGE だった、EDGE とたしかに見た目は同じだね。

edge

つかこのコードだと hbox_main っていらなくね?
それと GtkHButtonBox も GtkHBox 同様に非推奨なのね。
Gtk.ButtonBox.new(Gtk.Orientation.HORIZONTAL) でいいみたい。

その辺りを考慮して PyGI で書き換えるとこんな感じか。

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

from gi.repository import Gtk

window = Gtk.Window()
window.set_position(Gtk.WindowPosition.CENTER)
window.set_size_request(450, -1)
window.connect('destroy', Gtk.main_quit)
 
vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
 
dic = {
    'Spread': Gtk.ButtonBoxStyle.SPREAD,
    'Edge(Default)': Gtk.ButtonBoxStyle.EDGE,
    'Start': Gtk.ButtonBoxStyle.START,
    'End': Gtk.ButtonBoxStyle.END,
    'Center': Gtk.ButtonBoxStyle.CENTER }
 
for key, value in dic.iteritems():
    box = Gtk.ButtonBox.new(Gtk.Orientation.HORIZONTAL)
    box.set_layout(value)
    box.add(Gtk.Button.new_from_stock(Gtk.STOCK_YES))
    box.add(Gtk.Button.new_from_stock(Gtk.STOCK_CANCEL))
    box.add(Gtk.Button.new_from_stock(Gtk.STOCK_NO))
    frame = Gtk.Frame.new(key)
    frame.set_shadow_type(Gtk.ShadowType.IN)
    frame.add(box)
    vbox.pack_start(frame, False, False, 5)
 
window.add(vbox)
window.show_all()
Gtk.main()

gtk_button_box

右寄せとか完全等間隔とか色々指定できるんだね。
なるほど、使い道は微妙だけどこんなコンテナもあるということで。

Cairo for Python

Cairo Tutorial for Python Programmers

のサンプルコードで値が異様に小さい理由が解らなかった。
単純にコピペすると 1px にしか描写しない、なので DrawingArea サイズ得て計算する方法を覚書ページに書いた。

なんてことない、cairo_scale() で cairo のほうをサイズ指定すればよかったのね。

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

from gi.repository import Gtk
import cairo

class DrawTest(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self)
        da = Gtk.DrawingArea()
        da.connect("draw", self.on_draw)
        #da.set_double_buffered(False)
        self.add(da)
        self.connect("delete-event", Gtk.main_quit)
        self.resize(300, 300)
        self.show_all()

    def on_draw(self, widget, cr):
        # Get DrawingArea Size
        width = widget.get_allocated_width()
        height = widget.get_allocated_height()
        # cairo Change Size
        cr.scale(width, height)
        #
        cr.set_source_rgb(0, 0, 0)
        cr.move_to(0, 0)
        cr.line_to(1, 1)
        cr.move_to(1, 0)
        cr.line_to(0, 1)
        cr.set_line_width(0.2)
        cr.stroke()

        cr.rectangle(0, 0, 0.5, 0.5)
        cr.set_source_rgba(1, 0, 0, 0.80)
        cr.fill()

        cr.rectangle(0, 0.5, 0.5, 0.5)
        cr.set_source_rgba(0, 1, 0, 0.60)
        cr.fill()

        cr.rectangle(0.5, 0, 0.5, 0.5)
        cr.set_source_rgba(0, 0, 1, 0.40)
        cr.fill()

if __name__ == '__main__':
    w = DrawTest()
    Gtk.main()

cairo_python

cr.scale(width, height)
だけでサンプルコードがそのまんまコピペできた。
とにかく覚書ページの書き換えか、あーあ。

Boxes 仮想マシンの Lubuntu 上でも問題なく動いた。
しかしこのサンプルは一旦領域を塗りつぶす処理が入っていないのでダブルバッファを無効ににしてリサイズすると悲惨だ。

clutter – A toolkit for creating fast, portable, compelling dynamic UIs

このサンプルを見て気がついた、Clutter での 2D 表示も cairo なのね。
しかし Ubuntu には Clutter がデフォルトで入らないので困る。
海外で GTK+ 等のコードを検索すると皆 Ubuntu ばかり使っていて正直驚く。

ということで PyGI で DrawingArea に表示に作り替えてみた。

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

from gi.repository import Gtk, GLib
import cairo, math

class DrawTest(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self)
        da = Gtk.DrawingArea()
        da.connect("draw", self.on_draw)
        self.add(da)
        self.connect("delete-event", Gtk.main_quit)
        self.resize(150, 300)
        self.show_all()
        GLib.timeout_add(1000, self.on_timer, da)

    def on_timer(self, da):
        da.queue_draw()
        return True

    def on_draw(self, widget, cr):
        # get the current time and compute the angles
        now = GLib.DateTime.new_now_local()
        seconds = GLib.DateTime.get_second(now) * GLib.PI / 30
        minutes = GLib.DateTime.get_minute(now) * GLib.PI / 30
        hours = GLib.DateTime.get_hour(now) * GLib.PI / 6
        # scale the modelview to the size of the surface
        width = widget.get_allocated_width()
        height = widget.get_allocated_height()
        cr.scale(width, height)
        cr.set_line_cap(cairo.LINE_CAP_ROUND)
        cr.set_line_width(0.1)
        # the black rail that holds the seconds indicator
        cr.set_source_rgb(0, 0, 0)
        cr.translate(0.5, 0.5)
        cr.arc(0, 0, 0.4, 0, GLib.PI * 2)
        cr.stroke()
        # the seconds hand
        cr.set_source_rgb(1, 1, 1)
        cr.move_to(0, 0)
        cr.arc(math.sin(seconds) * 0.4, - math.cos(seconds) * 0.4, 0.05, 0, GLib.PI * 2)
        cr.stroke()
        # the minutes hand
        cr.set_source_rgb(1, 1, 0)
        cr.move_to(0, 0)
        cr.line_to(math.sin(minutes) * 0.4, - math.cos(minutes) * 0.4)
        cr.stroke()
        # the hours hand
        cr.set_source_rgb(1, 0, 0)
        cr.move_to(0, 0)
        cr.line_to(math.sin(hours) * 0.2, - math.cos(hours) * 0.2)
        cr.stroke()

if __name__ == '__main__':
    w = DrawTest()
    Gtk.main()

cairo_timer

GLib に sinf 関数くらい有りそうなのに見つからなかったので math を利用。
GLib.Math.sinf ? glib-2.0
Vala なら有るんだが、C 言語に sinf 関数があるから不要ということか。
それから色はテキトーです。

うん、基本的に cairo で使う数値は 0.0〜1.0 でいいみたい。
スマートフォンのような拡縮を考えるとそういう方向になるわけで。
作り手としては画面サイズ計算をする必要が無いというのも嬉しいですね。

PyGI Notify

usb_notyfy

GNOME3 マシンに USB メモリを刺した時等に出る Nothfy も DBus なんだね。
Na zdraví PyGI! ? Martin Pitt

うーん、やっぱり DBus は敷居が高いぞ。
GDBusProxy

(susssasa{sv}i) というワケワカメな表記は %s みたいなバリアント型の型指定子のようだ、Nothfy を使う場合にはコレで固定みたい。
一番上みたく作れというなら発狂するけどたしかに下方は簡単。
ただ一番下の result は long だったので添字はいらなかった。

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

from gi.repository import Gio, GLib

d = Gio.bus_get_sync(Gio.BusType.SESSION, None)
notify = Gio.DBusProxy.new_sync(d, 0, None, 'org.freedesktop.Notifications',
    '/org/freedesktop/Notifications', 'org.freedesktop.Notifications', None)

result = notify.Notify('(susssasa{sv}i)', 'test', 1, 'gtk-ok', 'Hello World!',
    'Subtext', [], {}, 10000)
# result type is long
print result #[0]

ただ Notify を使いたいだけなら Libnotify がある。

Libnotify Reference Manual

多分上記をラッピングしているだけだと思うけど。

同じものを Libnotify で作ろうと思ったけど引数の 1 が何か解らない。
NotifyUrgency 列挙体くらいしか相当するものが無いけど違ったし。
実際数値を何に変えても動作するので無視してもよさげだが。

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

from gi.repository import Notify

Notify.init("test")
# new(summary, body, icon)
notify = Notify.Notification.new("Hello World", "Subtext", "gtk-ok")
notify.set_timeout(10000)
notify.show()
print notify.props.id

とりあえず同じものができた。
result は int だったけど notify.show() の戻り値は bool なので困った。
プロパティの id と一致するようだ、表示毎に繰り上がるだけだったりする。

他にアップデート通知のようにボタンを表示して処理したい場合がある。
これは add_action で簡単に作成できるようだ。

ただし notify.show() は表示したら即制御を戻すのでコマンドを抜けてしまう。
なのでボタンを押した後の処理を入れるにはメインループが必要。

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

from gi.repository import Notify, Gtk

def on_callback(notifiaction, action, data=None):
    dlg = Gtk.MessageDialog(
            None,
            Gtk.DialogFlags.MODAL,
            Gtk.MessageType.INFO,
            Gtk.ButtonsType.OK,
            action)
    dlg.set_title("message")  
    dlg.run()  
    dlg.destroy()
    # quit
    Gtk.main_quit()

Notify.init("test")
notify = Notify.Notification.new("Hello World", "Subtext", "gtk-ok")

# Set Button
notify.add_action("Action Text", "Button Text", on_callback, None, None)
# Do not sink
notify.set_urgency(Notify.Urgency.CRITICAL)

notify.set_timeout(10000)
notify.show()

# main loop
Gtk.main()

nothfy_button

action 引数で振り分けもできるみたい。
これなら簡単だし結構使いみちがありそうです。

Python with GTK3 and Gst 1.0

GStreamer 1.0 を PyGI で試してみた。
Seekbar は面倒くさいので付けていない、Play/Pause ボタンのみ。
動画ファイルをドラッグアンドドロップすれば再生できるサンプル。

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

import gi
gi.require_version("Gtk", "3.0")
gi.require_version("Gst", "1.0")

"""
    GdkX11   @ get_xid()
    GstVideo @ xvimagesink
"""

from gi.repository import GObject, Gst, Gtk, Gdk, GdkX11, GstVideo

class Player(Gtk.Window):
    """
        Simple DnD Video Player
    """
    def __init__(self):
        Gtk.Window.__init__(self)
        self.connect("delete-event", self.on_quit)
        # Status
        self.status = Gst.State.NULL
        # Video Area
        self.video_area = Gtk.DrawingArea()
        # Disable Double Buffered
        self.video_area.set_double_buffered(False)
        # Play/Pause Button
        self.button = Gtk.Button.new_with_label("Null")
        self.button.connect("clicked", self.on_button_clicked)
        # playbin
        self.player = Gst.ElementFactory.make("playbin", None)
        bus = self.player.get_bus()
        bus.add_signal_watch()
        bus.enable_sync_message_emission()
        bus.connect("message", self.on_message)
        bus.connect("sync-message::element", self.on_sync_message)
        # DnD
        dnd_list = Gtk.TargetEntry.new("text/uri-list", 0, 0)
        self.drag_dest_set(
                Gtk.DestDefaults.MOTION
                | Gtk.DestDefaults.HIGHLIGHT
                | Gtk.DestDefaults.DROP,
                [dnd_list],
                Gdk.DragAction.MOVE )
        self.drag_dest_add_uri_targets()
        self.connect("drag-data-received", self.on_drag_data_received)
        # pack
        vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0) 
        vbox.pack_start(self.video_area, True, True, 0)
        vbox.pack_start(self.button, False, False, 0)
        self.add(vbox)
        self.resize(640, 360)
        self.show_all()

    def on_drag_data_received(self, widget, drag_context, x, y, data, info, time):
        uris = data.get_uris()
        self.player.set_state(Gst.State.NULL)
        self.player.props.uri = uris[0]
        self.player.set_state(Gst.State.PLAYING)

    def on_quit(self, widget, data=None):
        self.player.set_state(Gst.State.NULL)
        Gtk.main_quit()

    def on_sync_message(self, bus, message):
        if message.get_structure().get_name() == "prepare-window-handle":
            xid = self.video_area.props.window.get_xid()
            imagesink = message.src
            #imagesink.props.force_aspect_ratio = False
            imagesink.set_window_handle(xid)

    def on_message(self, bus, message):
        t = message.type
        if t == Gst.MessageType.EOS:
            self.player.set_state(Gst.State.NULL)
            self.status = Gst.State.NULL
            self.button.set_label("Null")
        elif t == Gst.MessageType.ERROR:
            self.messagebox(message.parse_error())
            self.emit("delete-event", None)
        elif t == Gst.MessageType.STATE_CHANGED:
            status = message.parse_state_changed()[1]
            if status == Gst.State.PLAYING: 
                if not self.status == Gst.State.PLAYING:
                    self.status = Gst.State.PLAYING
                    self.button.set_label("Pause")
            elif status == Gst.State.PAUSED: 
                if not self.status == Gst.State.PAUSED:
                    self.status = Gst.State.PAUSED
                    self.button.set_label("Play")

    def on_button_clicked(self, widget, data=None):
        if not self.player.props.uri == "":
            if self.status == Gst.State.PLAYING:
                self.player.set_state(Gst.State.PAUSED)
            elif self.status == Gst.State.PAUSED:
                self.player.set_state(Gst.State.PLAYING)

    def messagebox(self, text):
        dlg = Gtk.MessageDialog(
                self,
                Gtk.DialogFlags.MODAL,
                Gtk.MessageType.ERROR,
                Gtk.ButtonsType.OK,
                text)
        dlg.set_title("Error")  
        r = dlg.run()  
        dlg.destroy()
        return r

if __name__ == "__main__":
    GObject.threads_init()
    Gst.init(None)
    Player()
    Gtk.main()

GdkX11 は xid を得るのに必要、GstVideo は DrawingArea に貼り付けるのに必要。
つまり音楽プレイヤーを作るなら別にインポートする必要は無い。

以前リンクしたところは GstPipeline に playbin をセットしていた。
けど普通に playbin 自体を GstPipeline として使えるということみたい。

expose_event が無くなったおかげか xid を得るのに一手間がいらなくなった。
アスペクト比を保持させない方法も PyGtk と同じだった。

STATE_CHANGED メッセージで再生中に Gst.State.PLAYING が延々流れてくるのだが…
PyGtk ではこんなだったかな、覚えていないや。

というか PyGtk と Gst 0.10 で作るのと全然変わっていなかったという。

GStreamer のバージョンが上がったメリットは今の私には解らない。
多分そのうち気がつくだろう、多分。

しかし fedora 18 で検索しても皆 GStreamer なんて一言も触れていない。
Nautilus でサムネイルできなくて皆阿鼻叫喚してるかと思ったけど実際の話メインで使っている人がほとんどいないってことなのね。

しかし今日だけで五回もフリーズした、早く安定しないかなぁ。