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

GTK4: HeaderBar

GTK4 では GtkHeaderBar の set_title が無くなった。
というよりタイトル表示部が Widget に固定されてしまった。

いや set_title 関数は自分で定義すればいい。
後は Widget として GtkLabel を入れれば今までのようにはできる。

とはいえそれじゃ普通のラベルだから Bold 化とかしなきゃいけないな。
サブタイトルは薄く小さくしなきゃだし、CSS でやるのかな?

そういえばサブタイトルは gnome-text-editor が実装している。
どうやっているのかソースを覗いてみよう、GPL っていいですね。

src/editor-window.ui ? main ? GNOME / gnome-text-editor ? GitLab

コレを真似すればいいんだけど、attributes って何だ?
それを解っていないと定義も糞もないコピペアプリにしかならない。
調べると Pango 関連らしい、よしコードで同じになるように書いてみよう。

#!/usr/bin/env python3

from gi.repository import Gtk, Pango

class ComipoliHeaderBar(Gtk.HeaderBar):
    def __init__(self):
        Gtk.HeaderBar.__init__(self)
        # LR Button
        self.lr_button = Gtk.ToggleButton(label='L<-R', can_focus=False, focus_on_click=False)
        self.pack_start(self.lr_button)
        # Grid Button
        self.grid_button = Gtk.Button(icon_name='view-grid-symbolic', can_focus=False, focus_on_click=False)
        self.pack_start(self.grid_button)
        # Main Title
        bold = Pango.attr_weight_new(Pango.Weight.BOLD)
        alistm = Pango.AttrList()
        alistm.insert(bold)
        self.title = Gtk.Label(label='', single_line_mode=True, ellipsize=Pango.EllipsizeMode.END, attributes=alistm)
        # Sub Title
        subs = Pango.attr_scale_new(0.8222)
        suba = Pango.attr_foreground_alpha_new(32767)
        alists = Pango.AttrList()
        alists.insert(subs)
        alists.insert(suba)
        self.subtitle = Gtk.Label(label='--', single_line_mode=True, ellipsize=Pango.EllipsizeMode.END, attributes=alists)
        # Title pack
        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, valign=Gtk.Align.CENTER)
        box.append(self.title)
        box.append(self.subtitle)
        self.set_title_widget(box)
        # Menu Button pack @ after Window.__init__

    def set_title(self, text):
        self.title.set_label(text)

    def set_subtitle(self, text):
        self.subtitle.set_label(text)

header_bar

まったく同じになろうようにしてみた、滅茶苦茶面倒だった。
GtkBuilder はあんな単純表記を読んでコレをやっているのか、凄いんだな。
とにかく、attributes の指定は Pango の関数と値の省略表記ですね。

gnome-text-editor は GtkCenterBox を挟んでいるけどさ。
アレは別の用途なのでこういう場合はいらない。

そういえばタイトルが長い場合に収めるためウインドウが大きくされるのも防がないと。
ellipsize プロパティを定義すると自動でやってくれるみたい、なるほど。
後はウインドウが非アクティブになった時にタイトルを薄くすればオケ。

残りの GTK4 化は今までに書いたことの応用でイケそうだ。
何か見つけたら書いていきます。

Gdk.Cursor.new_from_name

ただ、矢印だけのカーソルは無くなってしまったようで。
とはいえ、そもそも作った本人はマウスでページめくりをしたことが無い。
ということで GTK4 版はマウスカーソルの変更は無しにする予定。

えっ夏鳥?昨日も今日もオケラでションボリしてます。

GTK4: Menu

現在 Comipoli の GTK4 化の準備をしているんだが。

まずメニュー。
GTK3 は GtkModelButton を GtkBox に入れソレを GtkPopoverMenu に。
だったけど GTK4 は GtkModelButton が廃止されている。

なので GTK4 は GMenuItem を GtkPopoverMenu に入れろとあるんだが。
GMenuItem をコードで作る方法はどこにも書いていない。

Gtk.PopoverMenu

しかたがないので XML で作る。

それから、GtkButton の image プロパティが廃止されていた。
代わりに icon-name が追加、つまり GtkImage を時前で用意しなくてもいい。

#!/usr/bin/env python3

from gi.repository import Gtk

menu_model = '''
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<menu id='comipoli-menu'>
  <section>
    <item>
      <attribute name='label' translatable='yes'>_Open</attribute>
      <attribute name='action'>app.new_file_action</attribute>
    </item>
    <item>
      <attribute name='label' translatable='yes'>_New Window</attribute>
      <attribute name='action'>app.new_window_action</attribute>
    </item>
    <item>
      <attribute name='label' translatable='yes'>_Preference</attribute>
      <attribute name='action'>app.preference_action</attribute>
    </item>
    <item>
      <attribute name='label' translatable='yes'>_Keyboard Shortcut</attribute>
      <attribute name='action'>app.shortcut_action</attribute>
    </item>
    <item>
      <attribute name='label' translatable='yes'>_About</attribute>
      <attribute name='action'>app.about_action</attribute>
    </item>
    <item>
      <attribute name='label' translatable='yes'>_Quit</attribute>
      <attribute name='action'>app.quit_action</attribute>
    </item>
  </section>
</menu>
</interface>
'''

class ComipoliMenuButton(Gtk.MenuButton):
    def __init__(self):
        ''' GTK3
        # MenuIten
        menu_open  = Gtk.ModelButton(active=True, action_name='app.new_file_action', text='_Open', use_markup=True)
        menu_new   = Gtk.ModelButton(active=True, action_name='app.new_window_action', text='_New Window', use_markup=True)
        menu_pref  = Gtk.ModelButton(active=True, action_name='app.preference_action', text='_Preference', use_markup=True)
        menu_kbd   = Gtk.ModelButton(active=True, action_name='app.shortcut_action', text='_Keyboard Shortcut', use_markup=True)
        menu_about = Gtk.ModelButton(active=True, action_name='app.about_action', text='_About', use_markup=True)
        menu_quit  = Gtk.ModelButton(active=True, action_name='app.quit_action', text='_Quit', use_markup=True)
        # Box
        vbox = Gtk.Box(visible=True, margin=10, orientation=Gtk.Orientation.VERTICAL)
        vbox.pack_start(menu_open, False, False, 0)
        vbox.pack_start(Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL), False, False, 0)
        vbox.pack_start(menu_new, False, False, 0)
        vbox.pack_start(menu_pref, False, False, 0)
        vbox.pack_start(menu_kbd, False, False, 0)
        vbox.pack_start(menu_about, False, False, 0)
        vbox.pack_start(Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL), False, False, 0)
        vbox.pack_start(menu_quit, False, False, 0)
        # PopoverMenu
        self.popover = Gtk.PopoverMenu()
        self.popover.add(vbox)
        '''
        builder = Gtk.Builder.new_from_string(menu_model, len(menu_model))
        model = builder.get_object('comipoli-menu')
        self.popover = Gtk.PopoverMenu.new_from_model(model)
        ''' GTK3
        #image = Gtk.Image(icon_name='open-menu-symbolic')
        #Gtk.MenuButton.__init__(self, can_focus=False, focus_on_click=False, image=image, visible=True, popover=self.popover)
        '''
        Gtk.MenuButton.__init__(self, can_focus=False, focus_on_click=False, icon_name='open-menu-symbolic', visible=True, popover=self.popover)

    def do_toggled(self):
        if self.props.active:
            self.popover.show_all()
        else:
            self.popover.hide()

丸ごと作り替えだった。

menu_button

そうそう、GtkShortcutsWindow の pack_start が廃止。
かつ GtkBox のサブクラスに、なので append で配置する。
orientation=Gtk.Orientation.HORIZONTAL 指定を忘れずに。
最初縦に配置されてしばらく悩んでしまった。
そんなことより。

shortcut

GtkShortcutsWindow を表示すると上記エラーを吐く。
もちろんサーチバーは出ない、これバグだよな。
Fedora 36 では修正されているのかな?

まだ色々とあるけど解決していないのでまた今度。

bash 5.1

Fedora 35 の bash は 5.1.8 である。
何を今更だが 5.1 で大文字小文字変換に新たな手段が追加されていた。
U u L 指定らしいんだけど、どう指定すればいいのかはドコにも書いていない。

How to convert a string to lower case in Bash? – Stack Overflow

やっと見つけた、fedorqui さんありがとう。

#!/bin/sh

pen='olympus PEN'

echo ${pen@U}
#=> OLYMPUS PEN

echo ${pen@L}
#=> olympus pen

echo ${pen@u}
#=> Olympus PEN

結局ブレースが必要なのか。
zsh みたく $pen:U とはできなかったのだろうか。
u は先頭文字以外を無視するだけだし使い道が無さそう。

#!/bin/sh

pen='olympus PEN'

echo ${pen^^}
#=> OLYMPUS PEN

echo ${pen,,}
#=> olympus pen

echo ${pen~~}
#=> OLYMPUS pen

今までのとタイプ数が変わっていないし、反転指定は無いし。
いやまあ、こんな手段もありますよってことで。

ということでコレを Tips ページに追記して。
こないだ調べたシェル関連も忘れないうちに追記して。
ついでに GTK4 ページのできているところまでもアップロードして。

よくみたら前の更新は昨年の今日だった、放置しすぎだ!
そんなこんなで、一年ぶりに本サイト更新です。

GTK4: GtkRadioButton to GtkCheckButton

GTK4 は GtkRadioButton が廃止されていた。
え、GTK4 製な gnome-text-editor のメニューにはラジオボタンがあるぞ?
継承元である GtkCheckButton のドキュメントを見てみた。

Gtk.CheckButton

なるほど、GtkCheckButton に統合されたってことなんだね。
グループ化すると自動的に丸い形になるのか、よしやってみよう。

#!/usr/bin/env python3

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

class RadioWin(Gtk.ApplicationWindow):
    '''
        GTK4: GtkRadioButton
    '''
    def __init__(self, a):
        Gtk.ApplicationWindow.__init__(self, application=a)
        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        root = None
        for s in ['Leica', 'SIGMA', 'Panasonic']:
            # GtkRadioButton to GtkCheckButton
            r = Gtk.RadioButton(label=s, group=root)
            #r = Gtk.CheckButton(label=s, group=root)
            r.connect('toggled', self.on_radio_toggled)
            if not root:
                root = r
                r.props.active = True
            vbox.pack_start(r, False, False, 0)
            #vbox.append(r)
        self.add(vbox)
        #self.set_child(vbox)
        self.show_all()
 
    def on_radio_toggled(self, button):
        if button.props.active:
            self.props.title = button.props.label

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

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

GTK3

#!/usr/bin/env python3

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

class RadioWin(Gtk.ApplicationWindow):
    '''
        GTK4: GtkRadioButton
    '''
    def __init__(self, a):
        Gtk.ApplicationWindow.__init__(self, application=a)
        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        root = None
        for s in ['Leica', 'SIGMA', 'Panasonic']:
            # GtkRadioButton to GtkCheckButton
            #r = Gtk.RadioButton(label=s, group=root)
            r = Gtk.CheckButton(label=s, group=root)
            r.connect('toggled', self.on_radio_toggled)
            if not root:
                root = r
                r.props.active = True
            #vbox.pack_start(r, False, False, 0)
            vbox.append(r)
        #self.add(vbox)
        self.set_child(vbox)
        #self.show_all()
 
    def on_radio_toggled(self, button):
        if button.props.active:
            self.props.title = button.props.label

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

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

GTK4

RadioButton

うん唖然とするくらい同じだ、これなら GTK4 化に困らない。
そんなことより、GTK3 と GTK4 って思っていたより違うんだね。

GTK3 はデフォルトが非表示なせいか最初の toggled シグナルが発生しない。
GTK4 のウインドウのほうが少しだけ大きい、なんでだろう。
非アクティブ時のタイトルバー文字列が GTK4 だと濃いまま。
いやタイトルバーはヘッダーバーにすれば普通に薄くなるみたいですけど。

関係ないけど GtkApplication 部分はコレが一番短く書けるな。
今後のサンプルコードはコレでいこうと思う。

だいぶ GTK4 も解ってきたしまとめや自アプリの GTK4 化を。
したいんだけど Fedora 35 の PyGObject は DnD に不具合があるので。
text/uri-list の取得ができずファイルのドロップで開くことができない。
次の Fedora で修正されているはずなのでそれから本格的に。

GTK4: gtk_window_begin_move_drag

前々回に約束したドラッグで移動させる処理をなんとかした。

gtk_window_begin_move_drag は GTK4 で廃止された。
けれどこの関数って Gdk の処理を簡素化しただけだったみたい。
ということで下記をご覧ください。

#!/usr/bin/env python3
 
import gi, sys
gi.require_version('Gtk', '4.0')
from gi.repository import Gtk, GdkPixbuf, Gdk, Gio

# Picture File
PNGFILE = 'test.png'

# CSS
APP_CSS = 'window { background-color: rgba(255, 255, 255, 0); }'.encode('utf-8')

class Win(Gtk.ApplicationWindow):
    '''
        GTK4: No Decorated Window
    '''
    def __init__(self, app):
        try:
            Gtk.ApplicationWindow.__init__(self, application=app, decorated=False)
            # Transparent
            provider = Gtk.CssProvider()
            provider.load_from_data(APP_CSS)
            context = self.get_style_context()
            context.add_provider_for_display(
                self.get_display(),
                provider,
                Gtk.STYLE_PROVIDER_PRIORITY_USER)
            # Mouse Move Signal
            click = Gtk.GestureClick()
            click.connect('pressed', self.on_gesture_click_pressed)
            self.add_controller(click)
            # Draw
            self.pixbuf = GdkPixbuf.Pixbuf.new_from_file(PNGFILE)
            da = Gtk.DrawingArea()
            da.set_draw_func(self.da_draw_func)
            self.set_child(da)
            # Resize
            self.set_default_size(self.pixbuf.get_width(), self.pixbuf.get_height())
        except Exception as e:
            print(e, file=sys.stderr)
            app.quit()

    def da_draw_func(self, da, cr, width, height):
        Gdk.cairo_set_source_pixbuf(cr, self.pixbuf, 0, 0)
        cr.paint()

    def on_gesture_click_pressed(self, click, n_press, x, y):
        '''
            GTK4: gtk_window_begin_move_drag
        '''
        button = click.get_button()
        toplevel = self.get_surface() # GdkToplevel
        display = self.get_display()
        seat = display.get_default_seat()
        device = seat.get_pointer()
        s, win_x, win_y = device.get_surface_at_position()
        #print(f'{win_x}, {win_y}')
        time = device.get_timestamp()
        toplevel.begin_move(device, button, win_x, win_y, time)

class App(Gtk.Application):
    def __init__(self):
        Gtk.Application.__init__(self)

    def do_startup(self):
        Gtk.Application.do_startup(self)
        # set Ctrl+Q
        self.set_accels_for_action('app.quit_action', ['<Control>Q'])
        quit_action = Gio.SimpleAction(name='quit_action')
        self.add_action(quit_action)
        quit_action.connect('activate', lambda a, p: self.quit())
        #
        Win(self)

    def do_activate(self):
        self.props.active_window.present()

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

Gdk の gdk_toplevel_begin_move という関数を使うんだけど。
与える引数がが gtk_window_begin_move_drag と同じじゃん。
いやその引数は自力で得る必要があるんだけーがさ。
とにかく上記で枠無しウインドウの移動ができるようになりました。

注意点として、前々回は released シグナルにしていたけど。
マウスドラッグは pressed シグナルでやらないと動作しません、当然だよね。
これに気が付かず筆者は一時間くらい無駄な時間を使ってしまった。

それと、コレをやると W クリックを検出できなかった。
press した時点で移動処理に入るので初回にリセットされるようだ。
非同期にするとか何か手段はあるのだろうけど、今日はココまで。
今回は Ctrl+Q で終了するようにした、GTK3 と変わっていなかった。

解ってしまえばこんなに簡単だったのね。
GtkGestureDrag でなんとかしようと無駄なコードをイッパイ書いたよ、ばかやろう。