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

get_modifier_state

我がアプリに矢印キーの代わりにマウスで押すボタンを付けた。
出した後で気が付いた、矢印キーの設定と連動していない。

まあ普通に GdkEvent を作って emit すれば…じゃない!
このアプリでは Ctrl 等の装飾キー状態も必要だった。

ボタンクリックのハンドラにて現在どの装飾キーが押されているかを取得。
それって手段があるのか?ってレベルの話だな。
ガチでアプリを作っている人でないと一生気にならなそうだ。

get_modifier

なんだ devhelp で get_modifier と検索したらアッサリ。
早速試してみよう。

#!/usr/bin/env python3

import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk
 
class ButtonWin(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self)
        button = Gtk.Button(label="modifier = ?")
        button.connect("clicked", self.on_button_clicked)
        self.connect("delete-event", Gtk.main_quit)
        self.add(button)
        self.show_all()

    def on_button_clicked(self, button):
        kmp = Gdk.Keymap.get_default()
        state = kmp.get_modifier_state()
        if state == 0:
            button.set_label("modifier = NULL")
        elif state == 1:
            button.set_label("modifier = Shift")
        elif state == 4:
            button.set_label("modifier = Ctrl")
        elif state == 5:
            button.set_label("modifier = Shift+Ctrl")
        elif state == 8:
            button.set_label("modifier = Alt")
        elif state == 9:
            button.set_label("modifier = Shift+Alt")
        elif state == 12:
            button.set_label("modifier = Ctrl+Alt")
        elif state == 13:
            button.set_label("modifier = Shift+Ctrl+Alt")
        else:
            button.set_label("modifier = ???")

ButtonWin()
Gtk.main()

test1

これでボタンからでも装飾キーの状態が判別できるね。
後は GdkEvent を作って key-press-event に投げれば…

#!/usr/bin/env python3

import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk
 
class ButtonWin(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self)
        button = Gtk.Button(label="Press Key 'A'")
        button.connect("clicked", self.on_button_clicked)
        self.connect("delete-event", Gtk.main_quit)
        self.connect("key-press-event", self.on_key_press_event)
        self.add(button)
        self.show_all()

    def on_button_clicked(self, button):
        """
            Gdk-WARNING **:
            Event with type 8 not holding a GdkDevice.
            It is most likely synthesized outside Gdk/GTK+

        """
        kmp = Gdk.Keymap.get_default()
        event = Gdk.Event()
        event.type = Gdk.EventType.KEY_PRESS
        event.state = kmp.get_modifier_state()
        event.keyval = Gdk.KEY_a
        event.window = self.props.window
        event.put()

    def on_key_press_event(self, widget, event):
        if event.state == Gdk.ModifierType.CONTROL_MASK:
            self.set_title("Ctrl+{0}".format(chr(event.keyval)))
        else:
            self.set_title("{0}".format(chr(event.keyval)))

ButtonWin()
Gtk.main()

test2

これで Ctrl を押しながらボタンクリックも Ctrl+a にはなるけど。
GdkDevice がどうのとワーニングになってしまう。

キーボードの指定か何か必要なのか?よくワカンネェYO!
よし必殺技だ、ハンドラを関数として呼び出ししてまえ。

#!/usr/bin/env python3

import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk
 
class ButtonWin(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self)
        button = Gtk.Button(label="Press Key 'A'")
        button.connect("clicked", self.on_button_clicked)
        self.connect("delete-event", Gtk.main_quit)
        self.connect("key-press-event", self.on_key_press_event)
        self.add(button)
        self.show_all()

    def on_button_clicked(self, button):
        """
            call on_key_press_event
        """
        kmp = Gdk.Keymap.get_default()
        event = Gdk.Event()
        event.state = kmp.get_modifier_state()
        event.keyval = Gdk.KEY_a
        #event.put()
        self.on_key_press_event(None, event)

    def on_key_press_event(self, widget, event):
        """
            OK
        """
        if event.state == Gdk.ModifierType.CONTROL_MASK:
            self.set_title("Ctrl+{0}".format(chr(event.keyval)))
        else:
            self.set_title("{0}".format(chr(event.keyval)))

ButtonWin()
Gtk.main()

なんというアホな処理。
でもこれで Ctrl+Click でも同じハンドラという狙ったとおりになる。
実装する時はキチンと関数に分離しとこう。

Next Button

前々回の更新で Comipoli にマウスのスワイプで次ページ機能を付けた。
二週間使ってみたけど駄目だこりゃ、どうしても使い辛い。

スワイプはタッチパネルだから直感的に使えるとよく解った。
マウスはやはり移動とクリック以外をさせてはいけない。
eog の次画像表示がボタンになったのは多分散々議論した結果なのだろう。

それとフルスクリーン化はやはりダブルクリックですよね。
スワイプ操作とバッティングするので外したけどあったほうが良さげ。
なんたって操作性を eog 同様にしたいんだからそうするべき。

ということで Comipoli にもストック画像でボタンを追加。
具体的には go-next-symbolic 等を。

GTK3 Demo – L’Isola di Niente

ってコレはどうやって ClutterImage に取り込みするのだ?
GtkButton なら簡単なんだけど、手段が解らない。

しかたがないので自作画像で。
XPM は透過属性があるので RGBA_8888 を指定。
#2E2E2E でダークテーマの色とほぼ一致するみたい。

#!/usr/bin/env python3

from gi.repository import GdkPixbuf, Clutter, Cogl

ICON_RIGHT = [
"32 32 3 1",
" 	c None",
".	c #2E2E2E",
"+	c #FFFFFF",
".........+......................",
".........++.....................",
".........+++....................",
".........++++...................",
".........+++++..................",
".........++++++.................",
".........+++++++................",
".........++++++++...............",
".........+++++++++..............",
".........++++++++++.............",
".........+++++++++++............",
".........++++++++++++...........",
".........+++++++++++++..........",
".........++++++++++++++.........",
".........+++++++++++++++........",
".........+++++++++++++++........",
".........+++++++++++++++........",
".........+++++++++++++++........",
".........++++++++++++++.........",
".........+++++++++++++..........",
".........++++++++++++...........",
".........+++++++++++............",
".........++++++++++.............",
".........+++++++++..............",
".........++++++++...............",
".........+++++++................",
".........++++++.................",
".........+++++..................",
".........++++...................",
".........+++....................",
".........++.....................",
".........+......................"]

ICON_LEFT = [
"32 32 3 1",
" 	c None",
".	c #2E2E2E",
"+	c #FFFFFF",
"......................+.........",
".....................++.........",
"....................+++.........",
"...................++++.........",
"..................+++++.........",
".................++++++.........",
"................+++++++.........",
"...............++++++++.........",
"..............+++++++++.........",
".............++++++++++.........",
"............+++++++++++.........",
"...........++++++++++++.........",
"..........+++++++++++++.........",
".........++++++++++++++.........",
"........+++++++++++++++.........",
"........+++++++++++++++.........",
"........+++++++++++++++.........",
"........+++++++++++++++.........",
".........++++++++++++++.........",
"..........+++++++++++++.........",
"...........++++++++++++.........",
"............+++++++++++.........",
".............++++++++++.........",
"..............+++++++++.........",
"...............++++++++.........",
"................+++++++.........",
".................++++++.........",
"..................+++++.........",
"...................++++.........",
"....................+++.........",
".....................++.........",
"......................+........."]

class ComipoliGoButton(Clutter.Actor):
    def __init__(self, right):
        Clutter.Actor.__init__(self)
        if right:
            pixbuf = GdkPixbuf.Pixbuf.new_from_xpm_data(ICON_RIGHT)
        else:
            pixbuf = GdkPixbuf.Pixbuf.new_from_xpm_data(ICON_LEFT)
        image = Clutter.Image()
        image.set_data(
            pixbuf.get_pixels(),
            Cogl.PixelFormat.RGBA_8888,
            pixbuf.get_width(),
            pixbuf.get_height(),
            pixbuf.get_rowstride()
        )
        self.set_content(image)
        self.connect("enter-event", self.on_enter_event)
        self.connect("leave-event", self.on_leave_event)
        self.props.opacity = 0x00
        self.set_reactive(True)

    def on_enter_event(self, actor, event):
        self.props.opacity = 0xff

    def on_leave_event(self, actor, event):
        self.props.opacity = 0x00

で。
eog の表示はウザいのでマウスカーソルが乗った時だけ表示させる。
ボタンを大きくしたいので縦に引伸し、これはウインドウ側に実装。

Linux アプリケーション – L’Isola di Niente

で上記スクリーンショットのようになりました。
いや、バージョンが上がったら画像はまた変更するけど。
ストックの取り込みが解ったら変更するかもしれない。

ウインドウの隅にカーソルを移動するだけだから直感的に使えると思う。
そういえばマウスジェスチャってすっかり廃れたよね。
マウスボタンを押しながら移動ってやっぱり全然直感的じゃないのよ。

キモヲタならロッカージェスチャとかホイールのほうがいいとか言うかも。
それを言うのは Windows しか使えない人だけですから。
特に右クリックはコンテキストメニュー以外には使ってはいけない。
というのが現在の Mac と GNOME が共通する思想です。

Comipoli beta3

Comipoli beta2 のメモリ展開に凄い問題が見つかった。
3000x4000px over の画像を役二百枚まとめた CBZ を試しに作ってみた。
ソレを読み込むと…

memory_swap

8GB もあるメモリがアッサリ埋まって swap 領域に突入。
SSD だから我慢できる程度の遅さだけど HDD だと死ねるレベル。

JPEG 圧縮を zip 圧縮しているからファイルサイズは 150MB 程度なのに。
GdkPixbuf に展開するとこんなサイズになってしまうのか。

16GB あれば、、、、、いやそうじゃない。
とにかくメモリ使用量をなんとかしなきゃ。

都度読み込みならたとえ 100000x100000px でも問題無い…
そこまでいくと展開が超遅くなるから誰もやらないってばさ。

JPEG を GdkPixbuf に変換せずにバイナリのまま配列なら…
前後処理丸ごと作り替えになるし表示効率も悪くなるな。
そもそも Python でバイナリの配列も実体はポインタなのかな?
ここらは C でやったほうがモヤモヤしなくて済むのだが。

単純に一定の大きさ以上は縮小が一番効率良さげ。
現在のディスプレイ市場は 1080p が主流。
ならば 1080p よりデカいなら縮小という手段が多分最高効率だと思う。

def iter_zip(self):
    # unzip
    with zipfile.ZipFile(self.zip.get_path()) as o:
        l = o.namelist()
        l.sort()
        self.datas.clear()
        for name in l:
            ext = os.path.splitext(name)[1].lower()
            if ext == ".jpg" or ext == ".jpeg" or ext == ".png":
                data = o.read(name)
                stream = Gio.MemoryInputStream.new_from_data(data)
                p = GdkPixbuf.Pixbuf.new_from_stream(stream)
                # Escape Swap
                if p.get_height() > 1080:
                    rate = p.get_width() / p.get_height()
                    p = GdkPixbuf.Pixbuf.scale_simple(p, 1080 * rate, 1080, GdkPixbuf.InterpType.BILINEAR)
                stream.close()
                yield p

手抜きに見えるけどフルスクリーン時の描写処理が速くなるメリットがある。
と思えるけど Comipoli ではソコは OpenGL がやるので無意味なことは秘密だよ。

いや問題はメモリ使用量だってば!
再起動して同じファイルを読み込んでみる

memory_after

1/10 以下に、まさかこんなに違うとは。
これだったらメモリは 4GB あれば問題無さそう、今時なら普通だよね。

でもやはり展開時間は倍近くになる。
バックグラウンド展開だから気にならないとはいえ、やはり遅い。
czipfile なんて早いらしいモジュールは Python2 用しか見当たらない。

ただデータの転送量が減るおかげか動作が少し速くなるメリットも。
とりあえず又問題が出るまでコレでいこう、まだベータだし。

Kivy GStreamer

動画プレイヤーを 16 年も作り続けてきた筆者が Mac で Kivy を。
だったらこのネタをやらないわけにはいかないだろう。

#!/usr/bin/env kivy

import kivy
kivy.require('1.9.1')

from kivy.app import App
from kivy.uix.videoplayer import VideoPlayer

VIDEO_PATH = "/Users/sasakima-nao/Movies/nmax.wmv"

class YuliemApp(App):
    def build(self):
        playbin = VideoPlayer(source=VIDEO_PATH, state="play",
            options={"allow_stretch": True})
        return playbin

YuliemApp().run()

kivy_gstplayer

と。
特に別途必要なものはなく QuickTime 非対応形式でも再生可能。
VIDEO_PATH は自前のファイルパスに書き換えてね。

正直驚いた、GStreamer がデコーダーを含めて同梱なんだね。
おまけに最初からボタン類やシークバーまで、どんだけ親切なのよ。

kivy.uix.video.Video を使えばボタン類を無しにできる。
でもそれだとビデオサイズがソースサイズ固定になってしまう。
ボタン類は自前描写にしたかったのだが、方法模索中。

Mac でしか試していないので他の環境はわかりません。
何故か何も指定していないのに esc で終了できることに今頃気がつく。
あ、クラス名は気にしないでください。

Kivy BoxLayout

Mac で Kivy (Python3 版)をもう少しやってみた。
Widget が日本語が豆腐になる理由は簡単だった。

「KivyではじめるPythonプログラミング」サポートページ 「Kivyアプリに日本語を表示させる」

こんなことをしなくても普通に ttf ファイルを指定すればいい。
ttc, otf も拡張子まで指定すれば使えるけど otf では何故か文字化けした。
クロスプラットホームにしたいならフォント同梱しか手が無い。

Mac なのに閉じるボタンで終了するのは気になる人もいるかも。
Electron 製アプリが上手くやっているだけに。
最初から command+Q で終了できるようにしているのは評価。

そんなことより、レイアウタがビックリするほど GTK+ と似ている。
ただしパッキング情報は子が持つ、いやソレが普通だと思うんですけど。
更にコンテンツの大きさに合わせて拡大とかはしてくれない。
width, height を固定するには size_hint に NULL を指定する必要あり。

一行エディットとマルチラインエディットは共通で TextInput を使う。
GtkEntry 同様最初から多機能だけど OLE DnD は未対応みたいだな。
日本語が入力できる時とできない時があるがなんだこれ?

__init__(self) も使えるようです。
Python なので親の __init__ を呼び出すのを忘れずに。

そんなこんなを確認するコードを以下に。

#!/usr/bin/env kivy

import kivy

from kivy.app import App
from kivy.uix.button import Button
from kivy.base import EventLoop
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.textinput import TextInput
from kivy.config import Config

class TestApp(App):
    def __init__(self):
        App.__init__(self)
        self.title = "__init__ も使える"

    def build(self):
        #self.title = "タイトルバー"
        Config.set('graphics', 'width', '300')
        Config.set('graphics', 'height', '150')
        Config.set("kivy", "exit_on_escape", 1)
        # Button
        button = Button(
            text="クリックしてください",
            #font_name="Osaka", # Japanese Font
            #font_name="SFNSText-Regular.otf",
            font_name="ヒラギノ角ゴシック W9.ttc",
            valign="top",
            font_size="20sp" # Default 15sp
        )
        button.bind(on_press = self.on_button_press)
        # Entry
        entry = TextInput(
            font_name="osaka",
            multiline=False,
            size_hint=(1, None), # (width_percent, height_percent)
            height=30
        )
        # Packing
        vbox = BoxLayout(orientation="vertical")
        vbox.add_widget(button)
        vbox.add_widget(entry)
        return vbox

    def on_button_press(self, button):
        button.text += ", しちゃった"

TestApp().run()

kivy_layout

レイアウタがあるのは嬉しいけどコンテンツサイズに合わせてくれないのは痛い。
Clutter ではそういう部品を GTK+ に丸投げすることができるんだけど。
OpenGL ES だけで実現しようとするとすると難しいんだろうな。
なんたって 3D なんだから奥行きという概念があるし。

そういえば前回
「エロゲ専用に落ちぶれた某 OS はまだ WindowsForm 大好きだからシラネ!」
と書いた。

ウチのアクセス解析だけで解るんだけど、確認のため日○ソフトウエアを立ち読み。
やっぱり WindowsForm な記事しかない。

実は会社の業務用アプリは最近 VB6 から WindowsForm に進化(?)した。
Windows でプログラミングの勉強をしている人さぁ、現実はコレだから。

WPF が出てからもう十年たっているのに。
筆者がまったく Windows を使わなくなった最大の理由です。