GTK+」タグアーカイブ

GTK4: ListView and ColumnView

前回は少し勘違いをしてました。
GtkListView を GtkListBox だと思い込んでいた、反省。

ListView は GTK4 で追加された新規のリストビューでした。
ただし 1 カラム限定、それいったい何に使うんだろう?は置いておいて。
複数カラムにするには ColumnView を使う、ということらしい。

Gtk.ListView

Gtk.ColumnView

ListView のほうにはサンプルコードがある。
でも create_application_list って関数何よ、これじゃよくワカンネェよ。
model は GListModel 派生なので Gio.ListStore にすればいいようです。

ただ ListStore には GObject.Object 派生しか append できないみたいなんですが。
GObject.TYPE_STRING 等を指定しても例外、丁度いい class も見つからない。
PyGObject の制限なのかどうかは知らないけど文字列のリストを入れたいんですけど。
ということでちょっぴり手抜きな Item class を作ってサンプルコードを書いてみた。

#!/usr/bin/env python3

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

class Items(GObject.Object):
    def __init__(self, items):
        self.items = items
        super(Items, self).__init__()

class TestWindow(Gtk.ApplicationWindow):
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app, title='ListView')
        #
        model = Gio.ListStore()
        model.append(Items(['OM SYSTEM', 'pen']))
        model.append(Items(['LUMIX', 'GH6']))
        #
        sel_model = Gtk.SingleSelection(model=model)
        #
        factory1 = Gtk.SignalListItemFactory()
        factory1.connect('setup', self.on_listitem_setup)
        factory1.connect('bind', self.on_listitem_bind1)
        #
        listnview = Gtk.ListView(model=sel_model, factory=factory1)
        listnview.connect('activate', self.on_columnview_activate)
        #
        self.set_child(listnview)

    def on_listitem_setup(self, factory, item):
        label = Gtk.Label(label='sexxx')
        item.set_child(label)

    def on_listitem_bind1(self, factory, item):
        l = item.get_item()
        label = item.get_child()
        label.props.label = l.items[0]

    def on_columnview_activate(self, list, pos):
        print(1)

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)

表示はレンダラではなく Widget を使うようになりました。

リストビューらしい複数カラム表示にするなら ColumnView を使う。
ほとんど同じ手段です、ListView は単なるベースクラスなのかな。
カラムの数だけ ColumnViewColumn を追加して使います。

#!/usr/bin/env python3

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

class Items(GObject.Object):
    def __init__(self, items):
        self.items = items
        super(Items, self).__init__()

class TestWindow(Gtk.ApplicationWindow):
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app, title='ColumnView')
        #
        model = Gio.ListStore()
        model.append(Items(['OM SYSTEM', 'pen']))
        model.append(Items(['LUMIX', 'GH6']))
        #
        sel_model = Gtk.SingleSelection(model=model)
        #
        factory1 = Gtk.SignalListItemFactory()
        factory1.connect('setup', self.on_listitem_setup)
        factory1.connect('bind', self.on_listitem_bind1)
        #
        factory2 = Gtk.SignalListItemFactory()
        factory2.connect('setup', self.on_listitem_setup)
        factory2.connect('bind', self.on_listitem_bind2)
        #
        columnview = Gtk.ColumnView(model=sel_model)
        columnview.connect('activate', self.on_columnview_activate)
        #
        column1 = Gtk.ColumnViewColumn(title='Camera', factory=factory1)
        columnview.append_column(column1)
        column2 = Gtk.ColumnViewColumn(title='Model', factory=factory2)
        columnview.append_column(column2)
        #
        self.set_child(columnview)

    def on_listitem_setup(self, factory, item):
        label = Gtk.Label(label='sexxx')
        item.set_child(label)

    def on_listitem_bind1(self, factory, item):
        l = item.get_item()
        label = item.get_child()
        label.props.label = l.items[0]

    def on_listitem_bind2(self, factory, item):
        l = item.get_item()
        label = item.get_child()
        label.props.label = l.items[1]

    def on_columnview_activate(self, list, pos):
        print(1)

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

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

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

で。

column

解り辛いですよね、でも表示が Widget なので GTK3 以前よりはマシ。
チェックボックスや画像を入れたくなっても Widget の知識でやれますんで。
以前はレンダラという専用のものを使う必要がありました。

Gtk(PyGObject) Tips | レンダラ – Paepoi

しかし Nautilus 43 みたくモダンな選択表示にするにはどうすればいいんだろう?
メッセージが Adw.ToastOverlay になったので Adw だと思ったけど無いんだよな。
それは今後の課題ということで、最後に。

jyoubitaki

本日はジョウビタキが綺麗に撮れました。

GTK4: GtkInscription

そういえば GTK4 に GtkInscription が追加されるんだっけ。
Gtk.Inscription

特殊用途に使う Widget なので通常は GtkLabel を使ってくれ。
ということらしいけど、つまり文字列がはみ出すラベルってことね。

#!/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):
        try:
            Gtk.ApplicationWindow.__init__(self, application=app)
            # Label and Inscription
            label = Gtk.Label(label='文字列に合わせて拡大されます')
            inscription = Gtk.Inscription(text='文字列に合わせて拡大されません、はみ出します')
            # pack
            vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
            vbox.append(label)
            vbox.append(inscription)
            self.set_child(vbox)
        except Exception as e:
            print(e, file=sys.stderr)
            app.quit()

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)

inscription

なんだそれだけだったのか。
カラム部分とかコンテナが拡大されたら困る場所に使うってことですね。

ところで、GTK4 のドキュメントが親切になっている。
deprecated つまり廃止予定を明確にしてくれています。

Gtk ? 4.0

TreeView がやっと廃止、マウス必須な時代遅れ UI ですからね。
TreeStore だけでなく ListStore も廃止、リストビュー表示もできなくなる。
一応、GtkListView は昔ながらのリストビューではなくスマホの設定画面みたいな UI です。
あれ、Nautilus 43 のリストビュー表示は?
と思うかもだけどアレは ColumnView なんですね、いや今知ったんだけど。

伴ってあの解り辛いレンダラ関連が全面廃止。
あぁ、それで Inscription という Widget が必要になったということか。

ダイアログ関連を全面見直し。
いやそれ何故 GTK4 の最初でやらなかったのか。

ComboBox も廃止。いや困るだろと思ったけど Popover があったわ。
Nautilus 43 の設定もそうなっていた、はよ移行しろってことですね。

StyleContext も廃止。
CSS の適用は今後どうするんだろう?調べなきゃ。

GTK4 の方向性が見えてきましたね。
次は GtkColumnView あたりを調べてみるかな。

GTK4: TextView

Fedora 36 には adwaita-1-demo が最初から入っていると以前書いた。
GTK Demo(gtk4-demo) は gnome-softwere から入手できるので入れてみる。

demo

なんか勝手に 4 ヶも入ってしまったけどまあいい。
こっちはソースが直接見られるので解りやすいですね。

demo

TextView は文字列中に Widget を入れることができるのか。
よし PyGObject でやってみよう。

#!/usr/bin/env python3

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

class Win(Gtk.ApplicationWindow):
    '''
        GTK4: TextView in Button
    '''
    def __init__(self, a):
        Gtk.ApplicationWindow.__init__(self, application=a)
        # Button
        button = Gtk.Button(label='Button')
        button.connect('clicked', self.on_button_clicked)
        # TextView
        self.view = Gtk.TextView()
        buf = self.view.get_buffer()
        it = buf.get_start_iter()
        buf.insert(it, 'This')
        anchor = buf.create_child_anchor(it)
        self.view.add_child_at_anchor(button, anchor)
        buf.insert(it, 'click!')
        # pack
        sw = Gtk.ScrolledWindow()
        sw.set_child(self.view)
        self.set_child(sw)
        self.set_default_size(400, 300)

    def on_button_clicked(self, button):
        with open(__file__) as f:
            s = f.read()
            self.view.props.buffer.set_text(s)

def app_activate(a):
    w = Win(a)
    w.present()

app = Gtk.Application()
app.connect('activate', app_activate)
app.run()

demo

マジでできた、必要性は疑問だけど。
他 tag の利用方法なんかもコレで解るね。
GTK4 はこんな感じで勉強すればいいと思います。

ところで SVG エディタなんだけど。
Vectornator という Mac のアプリを使うことにしました。
M1 Mac なのに Raw 現像専用じゃもったいない。

GTK4: Clipboard

ビックリ、Fedora 36 に何故か Python2 が入っていた!

gimp

Gimp にくっ付いてきたのか、PyGtk まで。
てか Gimp で使う Python って 2 のままなのかよ。

そりゃ Gimp 自体が GTK2 だし。
GTK って今は GNU Tool Kit という意味だし。
もしかして開発止まっている?
別の SVG エディタを探すか、mac で探したほうがいいような。

ということで平日は軽く GTK4 でクリップボード。

#!/usr/bin/env python3

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

class Win(Gtk.ApplicationWindow):
    '''
        GTK4: Clipboard Copy and Peast
    '''
    def __init__(self, a):
        Gtk.ApplicationWindow.__init__(self, application=a)
        # Copy Button
        cbutton = Gtk.Button(label='Copy')
        cbutton.connect('clicked', self.on_button_c_clicked)
        # Paste Button
        pbutton = Gtk.Button(label='Paste')
        pbutton.connect('clicked', self.on_button_p_clicked)
        # Entry
        self.entry = Gtk.Entry()
        #pack
        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        box.append(cbutton)
        box.append(pbutton)
        box.append(self.entry)
        self.set_child(box)

    def on_button_c_clicked(self, button):
        # entry text
        text = self.entry.get_buffer().get_text()
        clipboard = button.get_clipboard()
        clipboard.set(text)

    def on_button_p_clicked(self, button):
        clipboard = button.get_clipboard()
        clipboard.read_text_async(None, self.on_read_text_async)

    def on_read_text_async(self, clipboard, res):
        text = clipboard.read_text_finish(res)
        self.entry.get_buffer().set_text(text, -1)

def app_activate(a):
    w = Win(a)
    w.present()

app = Gtk.Application()
app.connect('activate', app_activate)
app.run()

文字列以外はあまり使わないと思うので文字列特化。

最初 init で文字列を入れようとしたけど全然駄目だった。
クリップボードは init を抜けてから操作しましょう。

GtkClipboard が GdkClipboard に代わっているけど GTK3 とほぼ同じ。
get_clipboard はどの Widget でもいいみたい、変な感じだけど。
set_text がバインドされていなくて戸惑うけど実はコレでいい。
読み込みは非同期にしないと上手くいかないみたい。

WordPress が 6.0 になった動作確認書き込みでした。

GNOME Adw

GNOME には GTK+ だけでなく Adw というものもあります。

Adw ? 1

モダンな GNOME アプリを作る GTK4 ベースな部品だそうです。
Fedora 36 にはデモアプリが最初から入っています、以下のコマンドで。

$ adwaita-1-demo

adw_demo

この demo のソースは以下にあります。

demo ? main ? GNOME / libadwaita ? GitLab

他にサンプルコードが以下にありました。

examples ? 2bf94101 ? GNOME / libadwaita ? GitLab

かなーり解り辛いです、Linux はこんなもんです。
細かいことはリンク先のソースや英語を読んでもらうとして。

#!/usr/bin/env python3

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

class Win(Adw.ApplicationWindow):
    '''
        Adw: Sample Code
    '''
    def __init__(self, a):
        # Set Adwaita Style
        manager = Adw.StyleManager.get_default()
        manager.set_color_scheme(Adw.ColorScheme.DEFAULT)
        # init
        Gtk.ApplicationWindow.__init__(self, application=a)
        # TitleBar
        titlebar = Adw.HeaderBar()
        # Tab
        view = Adw.TabView()
        tabbar = Adw.TabBar(view=view, autohide=False)
        # First Page
        label = Gtk.Label(label='First Page\nPress Alt+2')
        page1 = view.append(label)
        page1.set_title('tab1')
        # Second Page
        button = Gtk.Button(label='This is Button\nShow Toast', vexpand=True, halign=Gtk.Align.START)
        button.connect('clicked', self.on_button_clicked)
        page2 = view.append(button)
        page2.set_title('tab2')
        # Toast
        self.toast = Adw.ToastOverlay(child=view)
        # pack
        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        box.append(titlebar)
        box.append(tabbar)
        box.append(self.toast)
        self.set_content(box)
        self.set_default_size(400, 300)

    def on_button_clicked(self, button):
        toast = Adw.Toast(title='This is a Toast Message')#, priority=Adw.ToastPriority.HIGH, timeout=0)
        self.toast.add_toast(toast)

def app_activate(a):
    w = Win(a)
    w.present()

app = Gtk.Application()
app.connect('activate', app_activate)
app.run()

色々試してみました。

adw

まず Adw.StyleManager で DEFAULT を指定します。
無くても動きますがコレをやらないとこの見た目にはなりません。

AdwWindow という GtkWindow のサブクラスがあります。
なんとタイトルバーがありません、set_titlebar 関数すら省かれている。
GtkBox と AdwHeaderBar を使えばタイトルバーは再現できるようです。
GtkHeaderBar を入れても普通に動きます。

部品設置には set_child ではなく set_content を使います。
普通に GtkWidget が配置できます、何故関数名を変えたのか解りません。
正直、存在意義がよく解らないけど理由があるんだろうなって。

Adw.TabView

AdwTabView は設置しただけでリンク先のショートカットが有効になる。
便利だけれど AdwTabBar を配置すると Ctrl+PageDown は動作しなかった。
いや普通 Alt+数値キーですよね、Google Chrome や Gedit もそうですし。

Adw.Toast

GtkOverLay を駆使するより簡単にアプリ内 Notify が可能です。
Adw.StyleManager を指定しないと背景が無い残念な Notify になる。
これは便利ですね、ただ Nautilus のように上から出せないのかな?

これ以外は demo をご覧ください。
サンプルコードはこの demo くらいしかありませんでした。