Programming」カテゴリーアーカイブ

Drag a string from the GTK+ Application

GTK+ Drag and Drop #(以下 DnD)
で検索すると自アプリ内で DnD が完結するサンプルコードしか見当たらない。
そんな使い道がゼロに等しいサンプルコードではアプリを作る面白さが伝わらない。

実際の話 GUI アプリで DnD って text/uri-list のドロップ以外の使い道がほとんど無い。
text/uri-list は Content Type のことです。

GTK+ や Qt はプロセス間で選択文字列の OLE DnD がデフォルトで可能だったりするし。
Gedit で文字列選択して Firefox の検索バーに DnD するとあら不思議みたいな。
なので Drag 関連はいままで無視してきたけど勉強せねば。

ということで。

GtkTreeView にある文字列を別アプリにドロップなんてどうだろう。
文字列なら Content Type は text/plain ですね。

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

from gi.repository import Gtk, Gdk

class TextDragWin(Gtk.Window):
    def __init__(self):
        """
            TreeView Text "text/plain" Drag
        """
        Gtk.Window.__init__(self)
        self.connect("delete-event", Gtk.main_quit)
        # TreeView
        liststore = Gtk.ListStore.new([str])
        for s in ("CBR1000RR", "YZF-R1", "GSX-R1000", "ZX-10R"):
            liststore.append([s])
        cell = Gtk.CellRendererText.new()
        column = Gtk.TreeViewColumn("Super Sports", cell, text=0)
        treeview = Gtk.TreeView.new_with_model(liststore)
        treeview.append_column(column)
        # Drag Source
        #dnd_list = Gtk.TargetEntry.new("text/plain", 0, 0) # Error
        dnd_list = ("text/plain", 0, 0)
        treeview.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK, [dnd_list], Gdk.DragAction.COPY)
        treeview.connect("drag-data-get", self.on_drag_data_get)
        #
        self.add(treeview)
        self.show_all()

    def on_drag_data_get(self, widget, context, data, info, time):
        """
            data @ GtkSelectionData
        """
        selection = widget.get_selection()
        model, it = selection.get_selected()
        text = model.get_value(it, 0)
        data.set_text(text, -1)

TextDragWin()
Gtk.main()

listview_dnd

GtkTargetEntry に text/plain を指定。
Drop 時と違ってタプルのリストという PyGtk と同じ指定でないとエラーになる。
PyGI は互換との整合性がまだまだみたい。

gtk_tree_view_enable_model_drag_source はこんな感じで指定。
GDK_BUTTON1_MASK はマウス左ボタンのこと。

drag-data-get シグナルで GtkTreeView で選択状態の文字列を得る。
それを GtkSelectionData に突っ込むという処理を入れただけ。

ドロップターゲット側が text/plain を受け入れる gtk_drag_dest_set を行っているなら受け入れるエフェクトが入る。
ドロップすると GtkSelectionData から文字列を取得し相応の処理を行ってくれる。
と、これだけで別プロセスのアプリでも文字列を普通にドロップできるようだ。

こんなに簡単だったのか、今まで何故知らなかったのだw
そういうことなら Drag の使い道はある、かな…

Transparent GtkWindow

前回 GtkWindow の枠を消した、これで Google Chrome みたいなウインドウ…
とか書いたけどウインドウ自体を透過させなきゃ四角いウインドウしか作れない。
透明にできれば角を丸くなんかは DrawingArea でなんとかなるはず。

python – Making Gtk.Window Transparent? – Ask Ubuntu

アッサリ見つかった。
ついでに自分のストックから PyGtk 版も出てきた。

せっかくなので PyGtk との違いを見るため両方で書いてみる。

透明なだけでは面白くないので透過付き画像を表示させる処理も。
私は絵心が壊滅的なので Text という文字を Gimp で書いただけですが…
アーカイブも置いておきますので自作して試してくださると嬉しい。
visual.tar.gz

PyGtk

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

import gtk, cairo

class ColormapWin(gtk.Window):
    """
        PyGtk(GTK2) Version
    """
    def __init__(self):
        """
            gtk.main_quit is Double Click
        """
        gtk.Window.__init__(self)
        self.connect("delete-event", gtk.main_quit)
        self.connect("expose-event", self.on_expose)
        # GdkColormap
        screen = self.get_screen()
        colormap = screen.get_rgba_colormap()
        if colormap != None and screen.is_composited():
            self.set_colormap(colormap)
        else:
            print "no Composited..."
        self.set_app_paintable(True)
        # Transparent background image
        self.pixbuf = gtk.gdk.pixbuf_new_from_file("test.xpm")
        # Mouse Click
        self.set_events(gtk.gdk.BUTTON_PRESS_MASK)
        self.connect("button-press-event", self.on_button_press_event)
        #
        self.set_decorated(False)
        self.resize(320, 240)
        self.show_all()

    def on_expose(self, widget, event=None):
        """
            cr is gtk.gdk.CairoContext
        """
        cr = widget.window.cairo_create()
        cr.set_source_rgba(1.0, 1.0, 1.0, 0.0)
        cr.set_operator(cairo.OPERATOR_SOURCE)
        cr.set_source_pixbuf(self.pixbuf, 0, 0)
        cr.paint()

    def on_button_press_event(self, widget, event=None):
        """
            Mouse Drag and Quit
        """
        if event.type == gtk.gdk.BUTTON_PRESS:
            self.begin_move_drag(event.button, int(event.x_root), int(event.y_root), event.time)
        elif event.type == gtk.gdk._2BUTTON_PRESS:
            gtk.main_quit()

ColormapWin()
gtk.main()

pygi

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

from gi.repository import Gtk, Gdk, GdkPixbuf
import cairo

class VisualWin(Gtk.Window):
    """
        PyGI(GTK3) Version
    """
    def __init__(self):
        """
            Gtk.main_quit is Double Click
        """
        Gtk.Window.__init__(self)
        self.connect("delete-event", Gtk.main_quit)
        # 'expose-event' to 'draw'
        self.connect("draw", self.on_draw)
        # GdkColormap to GdkVisual
        screen = self.get_screen()
        visual = screen.get_rgba_visual()
        if visual != None and screen.is_composited():
            self.set_visual(visual)
        else:
            print "no Composited..."
        self.set_app_paintable(True)
        # Transparent background image
        self.pixbuf = GdkPixbuf.Pixbuf.new_from_file("test.xpm")
        # Mouse Click
        self.set_events(Gdk.EventMask.BUTTON_PRESS_MASK)
        self.connect("button-press-event", self.on_button_press_event)
        #
        self.set_decorated(False)
        self.resize(320, 240)
        self.show_all()

    def on_draw(self, widget, cr):
        """
            cr is cairo.Context
        """
        cr.set_source_rgba(1.0, 1.0, 1.0, 0.0)
        cr.set_operator(cairo.OPERATOR_SOURCE)
        Gdk.cairo_set_source_pixbuf(cr, self.pixbuf, 0, 0)
        cr.paint()

    def on_button_press_event(self, widget, event, data=None):
        """
            Mouse Drag and Quit
        """
        if event.type == Gdk.EventType.BUTTON_PRESS:
            self.begin_move_drag(event.button, event.x_root, event.y_root, event.time)
        elif event.type == Gdk.EventType.DOUBLE_BUTTON_PRESS:
            Gtk.main_quit()

VisualWin()
Gtk.main()

Gnome Shell 3D
gnome3_3d

Unity 3D
ubuntu_unity

Fedora でも Ubuntu でも問題ない。
枠も無くウインドウは透過しているけど画像は綺麗に表示できている。

PyGtk と PyGI もあまり違いは無いようだ。
GdkColormap が GdkVisual に変わったくらいで後はもうお馴染みの違いだけ。
デスクトップアクセサリを作る程度ならこれで簡単に作れるかな。

と思ったけど。
GNOME 3 を強制フォールバックにしたら真っ黒に、2D では合成できないようだ。

gnome_2d

Lubuntu でもダメだった。

lubuntu

これじゃ「透過するアプリですヨン」って配れないじゃん。
デスクトップ Linux は仮想マシンな人がほとんどという現実だもの。

つまり Google Chrome みたいなウインドウはこの方法では作れないよ。
うーん、まだまだ勉強しなければ。

No frame GtkWindow

Gtk.Window は Gtk.WindowType.POPUP 指定で作成すると枠無しになる。
でもタスクバーには現れないしフォーカスも持てない。

結局アクセサリとしてしか使い道が無いのかなと思っていた。
そういえば google-chrome はどうやって枠を無くしているんだろう?
気になったので調べてみた。

GtkWindow のプロパティをよく見ると decorated がある。
False にすると枠無しにできるっぽい、ならば実験。

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

from gi.repository import Gtk, Gdk

class Win(Gtk.Window):
    def __init__(self):
        """
            Switch the frame
        """
        Gtk.Window.__init__(self)
        self.connect("delete-event", Gtk.main_quit)
        # Mouse Click
        self.set_events(Gdk.EventMask.BUTTON_PRESS_MASK)
        self.connect("button-press-event", self.on_button_press_event)
        self.show_all()

    def on_button_press_event(self, widget, event, data=None):
        """
            Double-click on each switch.
            Have the focus.
            Moving without the frame.
        """
        if event.type == Gdk.EventType.BUTTON_PRESS:
            self.begin_move_drag( event.button, event.x_root, event.y_root, event.time)
        elif event.type == Gdk.EventType.DOUBLE_BUTTON_PRESS:
            self.props.decorated = self.props.decorated == False

Win()
Gtk.main()

ダブルクリックで切り替わる。
ついでにクライアントエリアをマウスで掴んで移動できるようにしてみた。
よししっかり切り替わるしフォーカスも持てるし移動もできる。

これで google-chrome みたいなウインドウが作れるね。

ついでに気がついた、 GTK+ 3.6 からみたいだけど
GDK_DOUBLE_BUTTON_PRESS, GDK_TRIPLE_BUTTON_PRESS
というエイリアスが追加されていたのを知った。

Python のアトリビュートは数値で始めることができないからって
Gdk.EventType._2BUTTON_PRESS って何か変だったし。

ということで覚書ページも更新っと。
ウインドウを作る – L’Isola di Niente

Gtk+ Container

GTK+ コンテナの使い方をまとめるのに一週間掛かった。
つかコンテナってこんなにいっぱいあったのか。
絶対に必要になるので GtkScrolledWindow なんかも追加しているけど。

コンテナ – L’Isola di Niente
オーナメント – L’Isola di Niente

GtkLayout とかGtkAlignment とか何に使うかよくワカラン。
もっと面白いサンプルコードにしたいけど思いつかない。

GtkOverlay も面白そうなのに使い方サンプルが見当たらない。
Pastebin でなんとか見つけた。
[C] GtkOverlay test – Pastebin.com

overlay

add_overlay で指定した Widget が add した Widget に被さるのね。
DrawinArea じゃ長くなりすぎだからサンプルコードは Label にしたけど。

海外を探しても devhelp を見れば解るようなことしか見つからなくて困る。
コンテナは大抵は GtkBox のみで事足りるわけなんですけど。

「おぉこんなことができたんだ!」
となるのがアプリを作る最大の楽しみなのにと思う私である。

以上チップスページ追加のお知らせでした。

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
子ウイジェット側で引き延ばし指定を行うほうがやはり理解しやすいなと思う。
たとえ記述量が多くなっても理解しやすいほうがいいなと。