GTK+」タグアーカイブ

PyGObject handle_local_options

GNOME 3.32 から ApplicationMenu は無くなったはずだけど。
Gedit や Nautilus の Global Menu には「新しいウインドウ」がある。
自作 GTK+ アプリにもコレを付けてみたい。

“New terminal” desktop action does nothing on Gnome ? Issue #427 ? gnunn1/tilix ? GitHub

Desktop Entry Specification

色々探してこんなのを見つける。
*.desktop のほうに定義するのね、なるほど。

いやまて。
つまり –new-window というオプションを用意しなきゃいけないヤン!

手抜きなオプション定義にしていたけどキチンと作らないと。
GApplication の handle_local_options シグナルを使えばいいようだ。

Using g_application_add_main_option_entries() with PyGObject ? GitHub

GOptionEntry を使おうとしたら PyGObject だとメンドクサ!
g_application_add_main_option のほうが短いコードでイケるのでコッチで。
とりあえず –version を表示するオプションで上手くいったサンプルコード。

#!/usr/bin/env python3

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

class Win(Gtk.ApplicationWindow):
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app, title='Oppai')
        self.show_all()

class App(Gtk.Application):
    def __init__(self):
        Gtk.Application.__init__(self)
        # add --version, -v option
        self.add_main_option('version', b'v', GLib.OptionFlags.NONE, GLib.OptionArg.NONE, 'Show Oppai Version', None)

    def do_handle_local_options(self, options):
        # get option
        if options.lookup_value('version', GLib.VariantType.new('b')):
            self.show_version()
            return 0
        return -1

    def do_activate(self):
        self.window = Win(self)
        self.window.present()

    def show_version(self):
        print('Oppai 0.0.1')

if __name__ == '__main__':
    app = App()
    app.run(sys.argv)

にて。

–help は全自動で定義される。
add_main_option の第二引数を使う場合はバイナリにするのをお忘れなく。
handle_local_options ハンドラはゼロを戻すとそのまま終了してくれる。
それ以外の説明はいらないよね。

さて肝心の –new-window を定義したら色々困ったぞ。
handle_local_options は startup シグナルより前に飛んでくる。
なので何も定義されていない状態。
しかも GApplication は同じ application-id の app に引数を転送して終了する。
handle_local_options ハンドラからは転送先 app を参照することは不可能。
つまり GtkApplicationWindow はこのハンドラ内では作れないってことだ。

GSettings にフラグを用意するというかなり強引な方法で定義はできた。
コマンドでは上手くいった、よし comipoli.desktop に追記だ。
表示されないんですけど。。。。。
まだ他にやらないといけないことがあるのだろうか?

GLib(PyGObject) Tips

内容が古いと解っていながら放置していた PyGObject Tips 以下。
整理したり文字列の fstring 化等をやってとりあえず GLib のページだけ。
GLib(PyGObject) Tips – L’Isola di Niente

それでは寂しいので GIOChannel 関連を Gjs のページからもってくる。
GIOChannel(PyGObject) Tips – L’Isola di Niente

いざ書き換えしてみたらマジで内容が古かった、こりゃアカン。
盆休みくらいまでには全部書き換えしたいな、ポケ GO も飽きたし。

PyGObject で作りたいものは今は無いけどページを作っていたら何か思い付くかも。
PyObjC では実はあるんだけど手段で詰まってアプリもページも止まっていることは内緒。
zsh 関連は次の macOS アップデートから再開、今やってもなぁって感じだし。

しかし Python を始める人が増えた感じだよね。
C 言語のポインタを理解できない人がオブジェクトなんて理解できるのか疑問なんだけど。
Python でアプリが作れる人って実は C でも作れるけど面倒だから Python を使っている。
っていう人しかいないんだよね。

GtkPopover motion-notify-event

前回書いた GtkPopover を motion-notify-event で処理する件。
フルスクリーンかつポップオーバー表示時はシグナルを無視する。
という超単純な手段でイケた、わっはっは。

それと前々回書いた F10 でポップオーバーが斜めに出る件。
ヘッダーを出した後 g_idle_add で一旦ハンドラを抜けてから表示。
という超単純な手段でイケた、g_idle_add はマジ便利。

困ったのがポップオーバーを出した時の Esc 処理。
ポップオーバーが割り込みで非表示になるけどシグナルも飛んでくる。
上手く逃がさないとフリーズする。

class ComipoliWindow(Gtk.ApplicationWindow):
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app)
        self.connect('motion-notify-event', self.on_motion_notify_event)
        // etc...
    def on_motion_notify_event(self, widget, event):
        if self.is_fullscreen:
            if self.menuf.popover.props.visible:
                return
            // etc...
    def key_press(self, state, keyval):
        # CapsLock?
        keymap = Gdk.Keymap.get_default()
        _state = state ^ Gdk.ModifierType.LOCK_MASK if keymap.get_caps_lock_state() else state
        # UpperCase?
        val = Gdk.keyval_to_lower(keyval) if Gdk.keyval_is_upper(keyval) else keyval
        if val == Gdk.KEY_F10:
            if self.is_fullscreen:
                self.upperbar.show()
                GLib.idle_add(self.show_fullscreen_menu)
            else:
                self.menu.clicked()
        elif val == Gdk.KEY_F11:
            self.toggle_fullscreen()
        elif val == Gdk.KEY_Escape:
            if self.upperbar.props.visible:
                self.upperbar.hide()
            elif self.is_fullscreen:
                self.toggle_fullscreen()
    def show_fullscreen_menu(self):
        self.menuf.clicked()
        return False

みたいな。

0.3.4 までボタンしか並べていなかったからこんな罠があるとは思わなかった。
不具合をなんとかするの楽しいわい、ただしマゾではない。

ところで。
Fedora 30 にアップグレードしたら Gedit 外部ツールのデフォルトが消えていた。
いや筆者はバックアップを持っているからコピーすればいいんだけど。
同じように消えてしまった人用にデフォルトコマンドを書いておきます。

# open-terminal-here (端末を開く)

#!/bin/sh
gnome-terminal --working-directory=$GEDIT_CURRENT_DOCUMENT_DIR &

# remove-trailing-spaces (末尾のスペースを取り除く)
# 入力を編集中のドキュメント、出力を置き換えるにする

#!/bin/sh
sed 's/[[:blank:]]*$//'

build, run-command, send-to-fpaste はいらないよね。
筆者は使ったことがない。

GtkPopover

Fedora 30 の準備をそろそろしなければ。

GNOME が 3.32 になって ApplicationMenu が非推奨に。
GTK3 アプリ以外には広がらなかったからしかたがないかもね。
Android のメニューボタンみたいなものだし広まってもよさげだったのにね。
何故か反 Apple みたいな輩がボロクソに言っていたり、根暗ってマジで意味不明。

我がアプリもハンバーガーメニューに変更しなきゃ、面倒だなぁ。
と思ったんだけーが。

#!/usr/bin/env python3

from gi.repository import Gtk

class ComipoliMenuButton(Gtk.Button):
    def __init__(self):
        # 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)
        # Hamburger Image
        image = Gtk.Image(icon_name='open-menu-symbolic')
        # init
        Gtk.Button.__init__(self, can_focus=False, focus_on_click=False, image=image, visible=True)
        # Popover
        self.popover = Gtk.Popover(relative_to=self)
        self.popover.add(vbox)

    def do_clicked(self):
        if self.popover.props.visible:
            self.popover.hide()
        else:
            self.popover.show_all()

を作ってみた。

GtkApplication から Gio.Menu だけを消す。
app.*** と指定していたアクション名を GtkModelButton でそのまま指定。
後は掟どおりにパッキング、relative-to に自分を指定。
GtkPopover のクリック処理もオーバーライドでやってしまえ。

って、コレだけで動いてしまうジャン!
本体に新たなハンドラを書く必要もアクションを新規で作る必要も無かった。
つまり変更は超簡単です、本体に F10 でメニュードロップ追加を忘れずに。

後はフルスクリーン時の F10 キーをどうするかだ、斜めにポップアップするんだが。
それとデザインがなんかイマイチ、Fedora 30 が出るまでに色々考えておく。
てか Y901x はどうしよう、スタンドアロンの動画プレイヤーってもういらなくね?

いやまあ、AppKit では分離があたりまえなので同じようにできないかなって。
AppKit より簡単だった、フレームワークは色々使ってみると経験値が上がるね。
そんなことより、やっぱり RealForce は快適だ!

WebKit on PyGObject and PyObjC

WebKit バインドは PyObjC に含まれている。
Fedora の gir には WebKit2 が含まれている。
もしかしたら共通コードでイケるんでね?

#!/usr/bin/env python3

import objc
from AppKit import *
from WebKit import *

class WView(WKWebView):
    def init(self):
        config = WKWebViewConfiguration.new()
        objc.super(WView, self).initWithFrame_configuration_(NSZeroRect, config)
        return self

class Win(NSWindow):
    def init(self):
        frame = NSMakeRect(100, 400, 900, 600)
        objc.super(Win, self).initWithContentRect_styleMask_backing_defer_(
            frame,
            NSTitledWindowMask |
            NSClosableWindowMask |
            NSResizableWindowMask |
            NSMiniaturizableWindowMask,
            NSBackingStoreBuffered,
            False)
        self.setTitle_('Web')
        self.setDelegate_(self)
        # view
        self.webview = WView.new()
        self.webview.setFrameSize_(self.contentView().frame().size)
        self.contentView().addSubview_(self.webview)
        # url
        url = NSURL.URLWithString_('https://www.google.com/')
        req = NSURLRequest.requestWithURL_(url)
        self.webview.loadRequest_(req)
        return self

    def windowDidResize_(self, sender):
        self.webview.setFrameSize_(self.contentView().frame().size)

class Menu(NSMenu):
    def init(self):
        objc.super(Menu, self).init()
        item_app  = NSMenuItem.new()
        self.addItem_(item_app)
        menu_app = NSMenu.new()
        item_app.setSubmenu_(menu_app)
        item_quit = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_('Quit', 'terminate:', 'q')
        menu_app.addItem_(item_quit)
        return self

NSApplication.sharedApplication()
window = Win.new()
window.makeKeyAndOrderFront_(window)
NSApp.setMainMenu_(Menu.new())
NSApp.activateIgnoringOtherApps_(True)
NSApp.run()

PyObjC

#!/usr/bin/env python3

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

class WebView(WebKit2.WebView):
    def __init__(self, ctx):
        WebKit2.WebView.__init__(self, web_context=ctx)

class Win(Gtk.ApplicationWindow):
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app)
        #
        ctx = WebKit2.WebContext()
        self.webkit = WebView(ctx)
        self.add(self.webkit)
        self.webkit.load_uri('https://www.google.com/')
        #
        self.resize(900, 600)
        self.show_all()

class App(Gtk.Application):
    __gtype_name__ = 'App'
    def __init__(self):
        GLib.set_prgname('App')
        Gtk.Application.__init__(self)

    def do_startup(self):
        Gtk.Application.do_startup(self)
        Win(self)
    
    def do_activate(self):
        self.props.active_window.present()

App().run(sys.argv)

PyGObject

全然違ったwwwww
Apple さん、なんで URL は文字列じゃダメなの?
GNOME さん、WebKit2.WebView() ではエラーになるんですけど?

実は我がアプリに本棚機能を付けようかと考えて。
WebKit からローカル HTML でやれば GNOME, macOS 共通にできるかなって。
こりゃ無理だ。