GTK+」タグアーカイブ

GTK4: activate-link

先日書いた GtkLabel のマークアップで a href ですけど。
GTK4 ではクリックすると activate-link シグナルが発生するようだ。
確認ダイアログを出して振り分けなんかができますね。
ということで書いてみたんですけど。

#!/usr/bin/env python3

import gi, sys
gi.require_version('Gtk', '4.0')
from gi.repository import Gtk, GLib, Gio

class TestWindow(Gtk.ApplicationWindow):
    def __init__(self, app):
        try:
            Gtk.ApplicationWindow.__init__(self,
                application=app,
                default_width=300,
                default_height=300)
            label = Gtk.Label(
                label = '<a href="trash:///">Open the Trash</a>',
                use_markup = True)
            label.connect('activate-link', self.on_label_activate_link)
            self.set_child(label)
        except Exception as e:
            print(e, file=sys.stderr)
            app.quit()

    def on_label_activate_link(self, label, uri):
        msg = Gtk.MessageDialog(
            buttons = Gtk.ButtonsType.YES_NO,
            modal = True,
            transient_for = self,
            text = 'Do you want to open the trash can?')
        msg.connect('response', self.on_label_message_response, uri)
        msg.show()
        # Stop the Open Link
        return True

    def on_label_message_response(self, dialog, response_id, uri):
        if response_id == Gtk.ResponseType.YES:
            GLib.spawn_command_line_async(f'gio open {uri}')
        dialog.destroy()

class TestApplication(Gtk.Application):
    def __init__(self):
        Gtk.Application.__init__(self, application_id='org.omsystem.pen')

    def do_activate(self):
        w = TestWindow(self)
        w.present()

app = TestApplication()
app.run(sys.argv)

ちと面倒。

activate_link のハンドラは True を戻さないと普通に実行してしまう。
msg.show() は非同期なのでとにかく True を戻す。
uri の処理は別シグナルなので user_data に入れて渡す。
実行は gio コマンドで、という流れになりました。

複数のリンクを作ったら全部コレ書かなきゃいけないのか、みたいな。
いやプログラミングってそういうものですけど。

GTK4: GtkMessageDialog

GTK4 は GtkMessageDialog が面倒になっていた。

#!/usr/bin/env python3

import gi, sys
gi.require_version('Gtk', '4.0')
from gi.repository import Gtk, GLib

def on_message_response(dialog, response_id):
    dialog.destroy()
    loop.quit()

msg = Gtk.MessageDialog(
    buttons = Gtk.ButtonsType.OK,
    text = 'GTK4 MessageDialog Sample Code')
msg.connect('response', on_message_response)

#res = msg.run() # GTK3
msg.show()

loop = GLib.MainLoop.new(None, False)
loop.run()

GtkMessageDialog

run が廃止、戻り値はシグナルで受け取りする必要がある。
シグナルってことはメインループが必要ということで。
GTK3 までのように単独で使えるほうが変だったのを見直したようで。

おまけに親ウインドウが無いと警告まで出るように。
いやまあ、通常の利用ではそれで問題ないんだけどさ。

シグナルのハンドラは Gjs ならいいけど PyGObject だと面倒だな。
表示させるだけならラムダ式にすればいいかと。

ついでに、GtkLabel のマークアップって a href とかもイケるんだね。
GTK3 の時からイケたみたい、use-markup プロパティがある場所は全部使える。
上記をウインドウから使うサンプルと合わせてこんな感じ。

#!/usr/bin/env python3

import gi, sys
gi.require_version('Gtk', '4.0')
from gi.repository import Gtk, GLib

class TestWindow(Gtk.ApplicationWindow):
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app)
        try:
            button = Gtk.Button(label = 'Show Message Dialog')
            button.connect('clicked', self.on_button_clicked)
            self.set_child(button)
        except Exception as e:
            print(e, file=sys.stderr)
            app.quit()
        self.present()

    def on_button_clicked(self, button):
        msg = Gtk.MessageDialog(
            buttons = Gtk.ButtonsType.OK,
            modal = True,
            text = '<a href="trash:///">Open the Trash</a>',
            transient_for = self,
            use_markup = True)
        msg.connect('response', lambda dlg, i: dlg.destroy())
        msg.show()

class TestApplication(Gtk.Application):
    def __init__(self):
        Gtk.Application.__init__(self, application_id='org.olympus.e-m5mark3')

    def do_activate(self):
        w = TestWindow(self)
        w.present()

app = TestApplication()
app.run()

GtkLabel

show は非同期なので show の後は何も処理を書いたら駄目なので注意。

ところで。

GNOME 開発センター(旧)

以前からあったのかどうかは知らないけど旧ページを見つけた。
developer-old としているので多分更新はしないんだろうけど。
API を個別サイトで参照するの面倒なんだよね。

Notify Signal

すっかり写真ブログ化しているこのブログですが。
今月末には新しい Fedora が出るはずなのでそろそろ本筋でも。
GTK4 のサンプルコードもそろそろ出そろってきたかなって。

GitHub – johnfactotum/quick-lookup: Simple GTK dictionary application powered by Wiktionary

このあたりが解りやすいかな。
省略表記多すぎ、てか文末セミコロンすら絶対に書かない派なのね。
まあ Gjs で GTK4 はこんなふうに書けばいいのかって参考にはなる。

気になったのは notify::is-active シグナル。
アクティブ化の真偽値をこのシグナルで捕まえられるのかな。

#!/usr/bin/gjs

imports.gi.versions.Gtk = '4.0'
const {GObject, Gtk} = imports.gi;

var TestWindow = GObject.registerClass({
    GTypeName: 'TestWindow'
}, class TestWindow extends Gtk.ApplicationWindow {
    _init(app) {
        super._init({
            application: app,
            title: '1'
        });
        try {
            this.connect('notify::is-active', () => {
                if (this.is_active) this.title += '1';
            });
        } catch(e) {
            printerr(`@@@Error@@@@:\n${e.message}`);
            app.quit();
        }
    }
});

var TestApplication = GObject.registerClass({
    GTypeName: 'TestApplication'
}, class TestApplication extends Gtk.Application {
    _init() {
        super._init({
            application_id: 'org.lumix.gh6'
        });
    }
    vfunc_activate() {
        let w = new TestWindow(this);
        w.present();
    }
});

let app = new TestApplication();
app.run(null);

おぉコレは使いどころがあるぞ。
昔筆者がやってた初回起動時間のセコい短縮なんかにも使えるね。
ウインド表示直後に処理 | Paepoi Blog

ところでこの Notify というシグナルって何だろう?

GObject.Object::notify

どうやら GObject のシグナルらしい。
そして is-active は GtkWindow のプロパティ。

Gtk.Window

つまりこのシグナルは何かプロパティをセットした時に吐きだすようだ。
別のプロパティで試してみよう、PyGObject でもイケるかな?

#!/usr/bin/env python3

import gi, sys
gi.require_version('Gtk', '4.0')
from gi.repository import Gtk

class TestWindow(Gtk.ApplicationWindow):
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app)
        try:
            self.connect('notify::default-width', self.on_width_change)
        except Exception as e:
            print(e, file=sys.stderr)
            app.quit()

    def on_width_change(self, this, pspec):
        self.set_title(f'width: {self.props.default_width}')

class TestApplication(Gtk.Application):
    def __init__(self):
        Gtk.Application.__init__(self, application_id='org.olympus.pen')

    def do_activate(self):
        w = TestWindow(self)
        w.present()

app = TestApplication()
app.run()

width

イケた、ウインドウの横サイズを変更すると即座にタイトルが変更される。
Notify::** シグナルでプロパティを監視することが可能なんですね。
引数の GParamSpec から値を得ることもできると思うけど手段がワカラン。
プロパティを見ればいいだけなので別にいいか。

GTK4 main loop

GTK4 は PyGObject ばかりやっていたけど。
たまには Gjs もやろうかなって、gir なのは同じなんだけど。

gjs/gtk4-template.js at master ? GNOME/gjs ? GitHub

あれ?
GtkApplication を使わないでイケる方法があったんだ。
同様なはずなので PyGObject で書いてみる。

#!/usr/bin/env python3

import gi
gi.require_version('Gtk', '4.0')
from gi.repository import Gtk, GLib

class TestWindow(Gtk.Window):
    def __init__(self):
        Gtk.ApplicationWindow.__init__(self)
        btn = Gtk.Button(label='The best camera for wild birds')
        btn.connect('clicked', self.on_button_clicked)
        self.label = Gtk.Label(label='...')
        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        box.append(btn)
        box.append(self.label)
        self.set_child(box)
        #self.connect('delete-event', self.on_close_request) # v3
        self.connect('close-request', self.on_close_request)
        self.present()

    def on_button_clicked(self, widget):
        self.label.set_text('MICRO FOUR THIRDS !')

    def on_close_request(self, widget):
        loop.quit()

loop = GLib.MainLoop.new(None, False)
#GLib.set_prgname('LUMIX')
TestWindow()
loop.run()

mft

GMainLoop でも良かったようです、知らなかった。

でも何故かこの方法ではタイトルバーに何も表示されないね。
アプリ名表示は使っていないはずの GtkApplication になる、なんだかな。
ウインドウを作る前に g_set_prgname すればいいんだけどさ。

GtkApplication を使えば今までどおりファイル名が表示される。
いや application-id を指定するとそちらが優先されるみたい。
g_set_prgname 無指定の場合だけの話ですけど。

close-request は delete-event の名前が変わっただけです。
×ボタンを押したよってシグナル、destroy は破棄されたよってシグナル。
GtkApplication を使う場合はこんな処理いりませんからね。

しかしのんびりしている間に GNOME 41 が出てしまった。

GNOME 41 Release Notes

Multitasking って GSettings で変更できていたジャン。
GUI 設定が付いただけ、いや筆者はデフォルトで使うけど。
それよりランチャを左に戻す設定が欲しい、macOS でも左にしているんだ。
ちなみに普段は隱して三本指スワイプでランチャを出す GNOME3 式で使っている。

The GTK Project

GTK の stable は 4.2.1 か、こっちは今までどおり奇数が開発版なのか。
でもドキュメントは 4.5 かよ、バージョンの関連性がもうよくワカラン。

関係ないけどサンプルコードでカメラネタはやっぱりイマイチだ。
やっぱりスズキのバイクが一番だなって。

GTK4 Clipboard (String)

GTK4 で Clipboard を使うテスト。
文字列のコピペはできた、ただし自分のインスタンス内のみで。

#!/usr/bin/env python3

import gi
gi.require_version('Gtk', '4.0')
#gi.require_version('Gdk', '4.0') # not need
from gi.repository import Gtk, Gdk

class TestWindow(Gtk.ApplicationWindow):
    '''
        GTK4 Clipboard @ String Copy
        This Code is Instance Only
    '''
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app)
        self.clip = self.props.display.get_primary_clipboard()
        print(self.clip) #=> GdkWaylandPrimary (GdkClipboard)
        '''
        self.clip = self.get_clipboard()
        self.clip = self.props.display.get_clipboard()

        display = Gdk.Display.get_default()
        self.clip = display.get_primary_clipboard()
        self.clip = display.get_clipboard()

        # local is All True...
        print(self.clip.props.local)
        '''
        self.clip.set('スズキのバイクはカッコイイ')
        print(self.clip.get_formats().to_string()) #=> { gchararray, text/plain;charset=utf-8, text/plain }
        #
        #self.clip.set_text('No GIR Binding') # Error!
        #
        self.clip.read_text_async(None, self.read_text_async_cb)
        #
        self.label = Gtk.Label(label='Motor Cycle')
        self.set_child(self.label)
        self.set_default_size(400, 100)
        self.present()

    def read_text_async_cb(self, clip, res):
        text = clip.read_text_finish(res)
        self.label.set_label(text)

class TestApplication(Gtk.Application):
    def __init__(self):
        Gtk.Application.__init__(self, application_id='org.suzuki.katana')

    def do_activate(self):
        TestWindow(self)

app = TestApplication()
app.run()

clip

何をどうやっても local Property が True になってしまう。
False にしないと Gedit 等の外部インスタンスとはコピペできない。
バグなのか?
他にやらないといけないことがあるのか?

それと set_text 等がバインドされていないのはどうなんだ?
set で文字列を渡したら普通にセットできたけど「ん?」って感じ。
バイナリの転送は GdkContentProvider でいいのかな?