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

GTK4: MenuButton

以前 GTK4 のメニューサンプルコードを書いたけど。
toggled なんてシグナルは GTK4 版には無かったね。
エラーが出なかったのは GTK4 に無視されていただけだったようだ。

んで、GTK4 版 GtkMenuButton には primary というプロパティが追加されていた。
コレを True にすると F10 キーでポップアップできるようになる。

ただ、それを利用するとフルスクリーンで使えない。
フルスクリーンにしないアプリなら問題ないんだけど。

それとコレを実装していて気が付いたんだけど。
GTK4 はボタン類の cricked 関数が全部消えている。
つまりコードで模擬クリックすることができなくなってしまった。
アレ便利だったのに、しかたがないから別の手段で。

ということで GTK4 にてフルスクリーン用メニューが別にある場合。
F10 を振り分けする必要がある。

class ComipoliApplication(Gtk.Application):
    def __init__(self, version):
        # etc...

    def do_startup(self):
        Gtk.Application.do_startup(self)
        self.set_accels_for_action('win.action_f10', ['F10'])

GtkApplication.

class ComipoliWindow(Gtk.ApplicationWindow):
    def __init__(self, app):
        # etc...
        action_f10 = Gio.SimpleAction.new('action_f10', None)
        action_f10.connect('activate', self.on_action_function_key, 10)
        self.add_action(action_f10)

    def on_action_function_key(self, action, param, num):
        if num == 10:
            if self.is_fullscreen:
                self.upperbar.show()
                GLib.idle_add(self.show_fullscreen_menu)
            else:
                self.menu.popup()

    def show_fullscreen_menu(self):
        self.menuf.popup()
        return False

GtkApplicationWindow.

コレでなんとかなったけど F10 の二度押しで消すことができない。
おまけにポップアップ状態から矢印キーでメニュー選択もできない。
gnome-text-editor も同じだから仕様なんだろうね。

それより、ポップアップを出すとフォーカスを取られてキー操作を受け付けない。
GtkMenuButton の popdown 関数は何のためにあるのだ?
これを利用して F10 二度押しを実装しようとしたけど、無理っぽい。

ということで GTK4 でポップアップを消すには Esc を使いましょう。
逆に言えば現状 GTK4 製かどうかはソレで見分けできる。

今回は GTK4 でのショートカット実装方法でした。
<Control>W とかも同じ方法で実装できるよ。

GTK4: FileChooserDialog

GTK4 で GtkFileChooserNative を何故か作れない。
理由は解らないけど何をやっても無反応。

しかたがないので GtkFileChooserDialog を使う。
Open ボタンを青くしたいんだがどうすればいいのかな?

class ComipoliWindow(Gtk.ApplicationWindow):
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app)
        # etc...

    def open_dialog(self):
        ft = Gtk.FileFilter()
        ft.set_name('Comic Book Archive')
        ft.add_mime_type('application/x-cbz')
        ft.add_mime_type('application/vnd.comicbook+zip')
        if self.app.is_pdf:
            ft.add_mime_type('application/pdf')
        if self.app.is_unrar:
            ft.add_mime_type('application/x-cbr')
        if self.app.is_7za:
            ft.add_mime_type('application/x-cb7')
        #dialog = Gtk.FileChooserNative.new('Open', self, Gtk.FileChooserAction.OPEN, '_Open', '_Cancel')
        dialog = Gtk.FileChooserDialog(title='Open', action=Gtk.FileChooserAction.OPEN, modal=True)
        dialog.add_buttons('_Cancel', Gtk.ResponseType.CANCEL, '_Open', Gtk.ResponseType.ACCEPT)
        dialog.set_transient_for(self)
        dialog.add_filter(ft)
        # CSS CLASS
        open_button = dialog.get_widget_for_response(Gtk.ResponseType.ACCEPT)
        open_button.add_css_class('suggested-action')
        cancel_button = dialog.get_widget_for_response(Gtk.ResponseType.CANCEL)
        cancel_button.add_css_class('destructive-action')
        if len(self.archive) > 0:
            d = GLib.path_get_dirname(self.archive.path)
            dialog.set_current_folder(Gio.file_new_for_path(d))
        dialog.connect('response', self.on_open_dialog_response)
        dialog.show()

    def on_open_dialog_response(self, dialog, response_id):
        if response_id == Gtk.ResponseType.ACCEPT:
            f = dialog.get_file()
            self.set_uri(f.get_uri())
        dialog.destroy()

filechooser

add_css_class でイケた。
destructive-action は例です、本当は remove か destroy になる場合に指定します。

この add_css_class で指定する文字列なんですが。
GTK4 のほうで見つからないので GTK3 のヘルプを見る。

Gtk ? 3.0

Constants つまり定数の STYLE_*** の中身。
define されている文字列をそのまま書けば適用される。
いや小文字にしてアンダーバーをハイフンにすればいいんですけどね。
というかボタン用は上記2つしか無いんですけどね。

よく見ると title や subtitle のスタイルクラスもあるな。
ということは。

#!/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
        self.title = Gtk.Label(label='', single_line_mode=True, ellipsize=Pango.EllipsizeMode.END)
        self.title.add_css_class('title')
        # Sub Title
        self.subtitle = Gtk.Label(label='--', single_line_mode=True, ellipsize=Pango.EllipsizeMode.END)
        self.subtitle.add_css_class('subtitle')
        # 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)

    def set_active(self, is_active):
        self.title.set_sensitive(is_active)

前回の HeaderBar に試してみたらイケた、メッチャシンプルになった。
こんな手段があったのか、まだまだ勉強しなきゃ駄目だな。

GtkFileFilter は GTK3 とまったく同じでした。
いやー野鳥が見つからないとガンガン進むな、まさかの三日連続とは...

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 ページのできているところまでもアップロードして。

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