月別アーカイブ: 2022年11月

GTK4: Adw.ComboRow

GTK+ 4.10 にて Gtk.ComboBox は廃止になる。
Adw に ComboRow というものがあるね。

Adw.ComboRow

ちょっとまて、コレ Nautilus 43 の設定にあるのそのまんまじゃん。
しかも Adw.PreferencesGroup というソレっぽいものもあるという。

それよりも、GtkStringList という GListStore 派生 class があったのかい!
文字列のみで使うなら当然 ListView や GridView にも使えます。
そんなこんなで。

#!/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(Gtk.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)
        # GListStore
        slist = Gtk.StringList()
        slist.append('OM-1')
        slist.append('GH6')
        rlist = Gtk.StringList()
        rlist.append('PRO レンズ')
        rlist.append('パナライカ')
        # combo
        self.r1 = Adw.ComboRow(model=slist, title='カメラ')
        self.r2 = Adw.ComboRow(model=rlist, title='レンズ')
        # Group
        group = Adw.PreferencesGroup()
        group.add(self.r1)
        group.add(self.r2)
        # Button
        button = Gtk.Button(label='確認', margin_top=12, margin_bottom=12)
        button.connect('clicked', self.on_button_clicked)
        # pack
        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, margin_start=48, margin_end=48)
        box.append(Gtk.Label(label='欲しい', halign=Gtk.Align.START, margin_top=12, margin_bottom=12))
        box.append(group)
        box.append(button)
        self.set_child(box)
        self.set_default_size(400, 300)

    def on_button_clicked(self, button):
        '''
            値の取り出し
        '''
        s1 = self.r1.get_selected_item().get_string()
        s2 = self.r2.get_selected_item().get_string()
        self.set_title(f'{s1}, {s2}')

app = Gtk.Application()
app.connect('activate', lambda a: Win(a).present())
app.run()

combo

予想以上に簡単だった、margin は勘で割り当てした適当な値です。
変更を即座に適用させたい場合は activated シグナルを処理。
いや GTK+ アプリの設定は基本「適用」ボタンなんて無い文化ですし。

値の取り出しはもちろん selected プロパティの UINT 値を利用する手段もある。
そのほうが設定保存等で都合がいいけど GSettings 的には文字列のほうが良さげ。

しかし GTK+ も Adw のおかげで型がいっぱい増えてなんというか。
UI の統一を狙うなら 4.10 の新規 Widget は Adw にまかせたほうがとか。

GTK4: Adw.MessageDialog

GTK+ 4.10 にて Gtk.MessageDialog は廃止になる。
変わりに Gtk.AlertDialog を、とドキュメントには書いている。
しかし、Adw 1.2 には Adw.MessageDialog が追加されている。

Adw.MessageDialog

どちらを使えと?
まあ現状 AlertDialog は使えないしコッチを試してみよう。

Gtk のは GtkDialog ベースだったが Adw のは GtkWindow ベース。
DialogFlags や ButtonsType のような単純明快な定数も用意されていない。
heading にメッセージを body に詳細を、ということみたい。
GtkWindow ベースなので parent を NULL にすれば単体でも使える。

#!/usr/bin/env python3

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

class Win(Adw.MessageDialog):
    def __init__(self, a):
        Adw.MessageDialog.__init__(self, application=a)
        # Create
        self.set_heading('Message')
        self.set_body('body')
        self.add_response('ok', 'OK')

app = Adw.Application()
app.connect('activate', lambda a: Win(a).present())
app.run()

adw_message

親ウインドウが無いというメッセージは一応出るようです。
ついでに app の最短表記を思いついたので今後はコレで。
これだけじゃ使い道が解らないので普通なサンプルコード。

#!/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(Gtk.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)
        # Button
        button = Gtk.Button(label='This is Button\nShow Message Dialog')
        button.connect('clicked', self.on_button_clicked)
        # Toast
        self.toast = Adw.ToastOverlay(child=button)
        # pack
        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        box.append(self.toast)
        self.set_child(box)
        self.set_default_size(600, 300)

    def on_button_clicked(self, button):
        dlg = Adw.MessageDialog.new(self, '何が撮りたい?', '選んでください')
        dlg.add_response('bird', '野鳥(_b)')
        dlg.add_response('portrait', 'ポートレート(_p)')
        dlg.add_response('train', '電車(_d)')
        dlg.connect('response', self.on_message_response)
        dlg.present()

    def on_message_response(self, dlg, response):
        match response:
            case 'bird':
                toast = Adw.Toast(title='貴方は変態です')
            case 'portrait':
                toast = Adw.Toast(title='貴方はスケベです')
            case 'train':
                toast = Adw.Toast(title='貴方は頭がおかしいです')
            case 'close':
                toast = Adw.Toast(title='Esc 押さないで')
            case _:
                toast = Adw.Toast(title='不明なレスポンス')
        self.toast.add_toast(toast)

app = Gtk.Application()
app.connect('activate', lambda a: Win(a).present())
app.run()

adw_message

定数が無いおかげでむしろ簡単になったって感じですね。
Alt キーのニーモニックも普通に使えるようです。
Python の match 文と相性がいいのも嬉しいです。
ついでに、ウインドウを小さくするとボタンが縦並びになったりする。

adw_message

なんだよ凄く便利じゃないの。
Adw.Toast ではモーダルにできないのでやはり必要ですね。
今後メッセージはこれでいこう。

GTK4: GridView

GTK4 は TreeView の他に IconView も廃止なんだね。
GlidView が後継のようで、Nautilus 43 のもコレっぽい。

Gtk.GridView

GtkDemo にサンプルが無い、自力で試してみよう。

#!/usr/bin/env python3

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

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

class TestWindow(Gtk.ApplicationWindow):
    def __init__(self, app):
        # Set Adwaita Style
        manager = Adw.StyleManager.get_default()
        manager.set_color_scheme(Adw.ColorScheme.DEFAULT)
        # init
        Gtk.ApplicationWindow.__init__(self, application=app, title='GridView')
        #
        model = Gio.ListStore()
        model.append(Items(['OM SYSTEM', 'pen']))
        model.append(Items(['LUMIX', 'GH6']))
        #
        #sel_model = Gtk.SingleSelection(model=model)
        sel_model = Gtk.MultiSelection(model=model)
        #
        factory1 = Gtk.SignalListItemFactory()
        factory1.connect('setup', self.on_listitem_setup)
        factory1.connect('bind', self.on_listitem_bind1)
        #
        listnview = Gtk.GridView(model=sel_model, factory=factory1)
        listnview.connect('activate', self.on_columnview_activate)
        #
        self.set_child(listnview)
        self.set_default_size(200, 200)

    def on_listitem_setup(self, factory, item):
        label = Gtk.Label(label='oman')
        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.om1')

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

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

grid

ちょっと驚いた、という不正確な日本語での表現にしておくけどナルホドなぁ。
完全に GTK4 の ListView と同様の手順、いや当然狙ってそうしたのだろう。
Box を使ってレイアウトすれば Nautilus 43 の見た目にはできるね。

ついでに。
init 直後に書いた Adw 用途の命令って GTK4 全部に影響するのね。
上記のまま全体の DarkMode にしてみたら。

grid

あぁ macOS や iOS の DarkMode は開発者に丸投げだったもんな。
GNOME はこうしたんですね、いや勘違いかもしれないけれど。

Python: open r+

Fedora 37 で今頃気がついたけど。
Anthy の辞書がまた PageDown でページ送りができなくなっているじゃん。

/usr/share/ibus-anthy/engine/engine.py は八月に更新されているな。
バッチは当ててくれなかったのか、気がつかなかったようで。
do_page_down なんてメソッドは定義されていないよメンテナさん。
しかたがない、今回も自前バッチを。

#!/usr/bin/env python3

'''
    Fedora 37 の Anthy で PageUp(Down) にて辞書送りできないのを修正
    コピペして sudo で実行しログインのやりなおし
    万が一失敗したら gnome-softwere から入れ直しで元通り
'''

import os

os.chdir('/usr/share/ibus-anthy/engine')

with open('engine.py') as f:
    src = f.read()
    dst = src.replace('do_page_up()', '__page_up(0)', 1).replace('do_page_down()', '__page_down(0)', 1)
    with open('engine.py', 'w') as g:
        g.write(dst)

今回は replace で。

いや Python の open には r+ という読み書きモードがあるんだけど?
と思うかもだけど r+ は使わないほうがいいよ。

#!/usr/bin/env python3

import os

with open('rw.txt', 'w') as f:
    f.write('ABCDEFG')

with open('rw.txt', 'r+') as f:
    src = f.read()
    #
    # 巻き戻すのを忘れずに
    f.seek(0)
    #
    # r+ での書き込みは以前の内容が残る
    dst = src.replace('ABCDE', 'a', 1)
    f.write(dst)
    #
    # 確認
    f.seek(0)
    print(f.read())
    #
    #=> 'aFGDEFG'

ね。
書き出しが短いと最初の語尾が残ってしまうんです。
文字数を合わせる、又はそれ以上なら問題ないんですけど。
ただ文字数を合わせても日本語だと以下のように。

#!/usr/bin/env python3

import os

with open('rw2.txt', 'w') as f:
    f.write('あいうえお')

with open('rw2.txt', 'r+') as f:
    src = f.read()
    f.seek(0)
    #
    # write は UTF-8 で書き出す
    dst = src.replace('あいう', 'aiu', 1)
    f.write(dst)
    #
    # 確認
    f.seek(0)
    print(f.read())
    #
    #=> 'aiuえおえお'

日本語はほぼ 3byte なので。
対策はあるんだろうけど、一番の対策は使わないことですよね。

ところで Nautilus 43 のリネームで入力メソッド切り替えが初回が無視される。
いやこれ US 配列キーボード愛用者以外には関係ないといえばそうなんですけど。
Ctrl+J のほうを使えばいいんだけど macOS と同じコッチを使ってしまうし。

Fedora とは関係ないけど GTK4 になった GHex もなんかおかしいな。

ghex font

Source Code Pro 以外のフォントだとはみ出すんだが。
なにがどうしてこうなるのか全然わからん。

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

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