GTK+」タグアーカイブ

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 共通にできるかなって。
こりゃ無理だ。

PyGObject Poppler

Web で配布されているコミックは PDF であることもある。
我がアプリで PDF も読み込みできるといいんだけど。

Poppler – Wikipedia

こんなライブラリがあることを知った。

Fedora デフォルトには普通に Gir で入っていたりするし。
そりゃ Evince が使っているし。

Poppler 0.18 (0.69.0) – Poppler 0.18

ドキュメントもある。
関数名も解りやすいし適当に書いてみよう。

#!/usr/bin/env python3

import gi, cairo
gi.require_version('Gtk', '3.0')
gi.require_version('Poppler', '0.18')
from gi.repository import Gtk, Gio, Poppler

PDFFILE = 'りんごの色.pdf'
 
class PdfWin(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self)
        self.connect('delete-event', Gtk.main_quit)
        # PDF file
        f = Gio.File.new_for_path(PDFFILE)
        self.pdf = Poppler.Document.new_from_gfile(f)
        self.max_page = self.pdf.get_n_pages()
        self.num = 0
        # button
        button = Gtk.Button.new_with_label('Next Page')
        button.connect('clicked', self.on_button_clicked)
        # DrawingArea
        self.canvas = Gtk.DrawingArea()
        self.canvas.connect('draw', self.on_draw)
        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        vbox.pack_start(button, False, False, 0)
        vbox.pack_end(self.canvas, True, True, 0)
        self.add(vbox)
        self.resize(400, 400)
        self.show_all()
 
    def on_button_clicked(self, widget):
        if self.num < self.max_page-1:
            self.canvas.queue_draw()
            self.num += 1

    def on_draw(self, widget, cr):
        aw = widget.get_allocated_width()
        ah = widget.get_allocated_height()
        page = self.pdf.get_page(self.num)
        w, h = page.get_size()
        if aw * h > ah * w:
            n = ah/h
            matrix = cairo.Matrix(n, 0, 0, n, aw/2-w*n/2, 0)
            cr.transform(matrix)
        else:
            n = aw/w
            matrix = cairo.Matrix(n, 0, 0, n, n, ah/2-h*n/2)
            cr.transform(matrix)
        page.render(cr)
 
PdfWin()
Gtk.main()

こんなにアッサリ。。。
何もインストールしないでも表示するだけならマジでこれだけ。

cairo のドキュメントを見ると PDFSurface なんてサーフェスがあるね。
こんなに面白かったのか cairo って。

Cairo Matrix

前回の続き。
Cairo.Matrix で GdkPixbuf をリサイズできるんだよね。
それなら draw シグナルのハンドラでそのまま描写すればいいジャン!

でも Matrix のリサイズは RGBA ベースじゃないだろうな?
ClutterImage ではスクリーントーンが縮小で潰れたんだよね。
screentone 2D and 3D | PaePoi

OpenGL ではないから大丈夫だと思う、ただリサイズは超ヌルヌルだった。
速度といっしょにそんな所も見ていこう。

#!/usr/bin/env python3

import gi, sys, cairo, time
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk, GLib, GdkPixbuf

class AWindow(Gtk.ApplicationWindow):
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app)
        self.d = Gtk.DrawingArea()
        self.d.connect('draw', self.on_draw)
        self.add(self.d)
        self.pixbuf1 = GdkPixbuf.Pixbuf.new_from_file('003.png')
        self.pixbuf2 = GdkPixbuf.Pixbuf.new_from_file('004.png')
        self.resize(1600, 900)
        self.show_all()

    def on_draw(self, widget, cr):
        aw = widget.get_allocated_width()
        ah = widget.get_allocated_height()
        # __do__
        t = time.time()
        # Save Matrix
        cr.save()
        # Right Page
        w = self.pixbuf1.get_width()
        h = self.pixbuf1.get_height()
        n = ah/h
        matrix = cairo.Matrix(n, 0, 0, n, aw/2, n)
        cr.transform(matrix) # no! cr.set_matrix(matrix)
        Gdk.cairo_set_source_pixbuf(cr, self.pixbuf1, 0, 0)
        cr.paint()
        # Reset
        cr.restore()
        # Left Page
        w = self.pixbuf2.get_width()
        h = self.pixbuf2.get_height()
        n = ah/h
        matrix = cairo.Matrix(n, 0, 0, n, aw/2-w*n, n)
        cr.transform(matrix)
        Gdk.cairo_set_source_pixbuf(cr, self.pixbuf2, 0, 0)
        cr.paint()
        # __done__
        print('matrix  : {}'.format(time.time() - t))

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

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

AApplication().run(sys.argv)

Matrix 版

#!/usr/bin/env python3

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

class AWindow(Gtk.ApplicationWindow):
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app)
        self.d = Gtk.DrawingArea()
        self.d.connect('draw', self.on_draw)
        self.add(self.d)
        self.pixbuf1 = GdkPixbuf.Pixbuf.new_from_file('003.png')
        self.pixbuf2 = GdkPixbuf.Pixbuf.new_from_file('004.png')
        self.resize(1600, 900)
        self.show_all()

    def on_draw(self, widget, cr):
        aw = widget.get_allocated_width()
        ah = widget.get_allocated_height()
        # __do__
        t = time.time()
        # Right Page
        w = self.pixbuf1.get_width()
        h = self.pixbuf1.get_height()
        n = ah*w/h
        buf = self.pixbuf1.scale_simple(n, ah, GdkPixbuf.InterpType.BILINEAR)
        Gdk.cairo_set_source_pixbuf(cr, buf, aw/2, 0)
        cr.paint()
        # Left Page
        w = self.pixbuf2.get_width()
        h = self.pixbuf2.get_height()
        n = ah*w/h
        buf = self.pixbuf2.scale_simple(n, ah, GdkPixbuf.InterpType.BILINEAR)
        Gdk.cairo_set_source_pixbuf(cr, buf, aw/2-n, 0)
        cr.paint()
        # __done__
        print('scale:  {}'.format(time.time() - t))

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

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

AApplication().run(sys.argv)

scale_simple 版

差がなかった。
小さく描写だと scale_simple、大きく描写だと Matrix のほうがチビッと速い。
とはいえ人間の目で解るような違いは何をやっても出ない。

画質も違いが解らないレベル。
ただリサイズのスムースさは Matrix 版のほうが気のせいレベルで上かも。

cr.set_matrix(matrix) はしちゃだめよ。
原点がウインドウの枠どころか影までの所になってワケワカメになるよ。
GtkWindow の decorated property を False で使うならいいけど。

Matrix 版と ClutterImage 版 comipoli との比較、
スクリーントーンの縮小も問題無し、心配不要だった。

結論、変わらねー。
せっかく勉強したんだから次の comipoli は Matrix でいこう。