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

GtkMenuButton F10

Y901x 1.1.2 公開、日は変わってしまったけど。

y901x112

メニューバーを取り払ったよ。
ラジオメニューじゃ解りづらい再生速度アスペクト比切り替えもステータスバーに移した。

GtkHeaderBar and GStreamer | PaePoi
Mini GtkButton | PaePoi

つーか、今頃になってやっと上記を実装した、半年も前だったのか。
使い道の例だと思ってください、多分作った本人しか使っていないアプリなので。

所で F10 キーでメインメニューがドロップするのは知っているよね。
GtkMenuButton ではこれを自分で実装しないといけないみたい。
こんな感じでいいみたい、F10 ってシステム管理じゃなかったんだ。
ちょっと具体的に追記

#!/usr/bin/env python3

class Win(Gtk.ApplicationWindow):
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app)
        # GtkAccelGroup
        accelgroup = Gtk.AccelGroup.new()
        self.add_accel_group(accelgroup)
        # Preference button
        preferencebutton = Gtk.MenuButton.new()
        image_gear = Gtk.Image.new_from_icon_name("emblem-system-symbolic", Gtk.IconSize.MENU)
        preferencebutton.set_image(image_gear)
        # Set F10 Accel
        preferencebutton.add_accelerator("clicked", accelgroup, Gdk.KEY_F10, 0, Gtk.AccelFlags.VISIBLE)
        # HeaderBar
        headerbar = Gtk.HeaderBar()
        headerbar.pack_end(preferencebutton)

ついでに今頃気が付いた。
Alt+F10 で最大化切り替えできる、これは便利だ。
まあシステム設定で変更できるんだけど。

後 Y901 伝統の Z キーでフルスクリーンは実装できなかった。
一応強引な手段もあるけど GNOME デフォルトの F11 のみでいいかなと。

次は Alt+Enter でプロパティ表示を実装したいな。
一度弄り始めると急に色々思い付くんだよね。

Ubuntu での動作確認は明日やろう(ぉい!
個人的には超有名な某ゲームエンジンと同じ名前であるあの糞デスクトップ環境はガン無視したいんだけどユーザー数がなぁ…

GtkToggleButton and keyboard

GtkToggleButton のシグナルには activate と clicked がある。

activate は On 時のみ、clicked は On/Off 両方シグナルが発生する。
マウス等でクリック時だけでなく gtk_button_clicked() 関数にも反応する。

なので GtkToggleButton を Off させる場合に困る場合がある。
トグルなので二度押しで上るわけだが複数ボタンで排他制御を行う場合に。
更にキーボードでも同様に動作するようにとなると…

activate では二度押しで上るのを検知できない。
clicked では排他で上るボタンからもシグナルが飛んでくる。

activate は使えそうもないので clicked にするしかない。
Off 検知を逃すのとキーボードでつじつまを合わせるのに一苦労。
そのためアクロバチックなコードを今まで書いていた。

class Y901window(Gtk.ApplicationWindow):
    def __init__(self, app):
        #
        # etc...
        #
        self.toolbox.one.connect("clicked", self.on_rep_btn, 1)
        self.toolbox.all.connect("clicked", self.on_rep_btn, 2)
        self.toolbox.rdm.connect("clicked", self.on_rep_btn, 3)
        #
        # etc...
        #

    def on_keydown(self, accelGroup, window, keyval, modifier):
        if keyval == Gdk.KEY_F7:
            self.toolbox.one.clicked()
        elif keyval == Gdk.KEY_F8:
            self.toolbox.all.clicked()
        elif keyval == Gdk.KEY_F9:
            self.toolbox.rdm.clicked()
        ''' old
        if keyval == Gdk.KEY_F7:
            self.on_rep_btn(None, 1)
        elif keyval == Gdk.KEY_F8:
            self.on_rep_btn(None, 2)
        elif keyval == Gdk.KEY_F9:
            self.on_rep_btn(None, 3)'''

    def on_rep_btn(self, widget, num):
        if widget.get_active():
            if self.settingwin.repeat != num:
                self.toolbox.one.set_active(num == 1)
                self.toolbox.all.set_active(num == 2)
                self.toolbox.rdm.set_active(num == 3)
                self.settingwin.repeat = num
        else:
            if not self.toolbox.one.get_active() and not self.toolbox.all.get_active() and not self.toolbox.rdm.get_active():
                self.settingwin.repeat = 0
        ''' old
        if widget == None:
            if num == 0:
                self.toolbox.one.set_active(False)
                self.toolbox.all.set_active(False)
                self.toolbox.rdm.set_active(False)
            elif num == 3:
                self.toolbox.one.set_active(False)
                self.toolbox.all.set_active(False)
                self.toolbox.rdm.set_active(True)
            elif num == 2:
                self.toolbox.one.set_active(False)
                self.toolbox.all.set_active(True)
                self.toolbox.rdm.set_active(False)
            elif num == 1:
                self.toolbox.one.set_active(True)
                self.toolbox.all.set_active(False)
                self.toolbox.rdm.set_active(False)
        else:
            if widget.get_active():
                if self.settingwin.repeat != num:
                    action = self.actiongroup.get_action(ac2_str[num])
                    action.set_current_value(num)
                    if num != 3:
                        self.toolbox.rdm.set_active(False)
                    if num != 2:
                        self.toolbox.all.set_active(False)
                    if num != 1:
                        self.toolbox.one.set_active(False)
            else:
                if self.settingwin.repeat == num:
                    action = self.actiongroup.get_action(ac2_str[0])
                    action.set_current_value(0)
                    self.toolbox.one.set_active(False)
                    self.toolbox.all.set_active(False)
                    self.toolbox.rdm.set_active(False)'''

最初に書いたとおり gtk_button_clicked() 関数にも反応だからコッチを使う。
これでマウスクリックと直接呼び出しとを振り分けする必要はなくなった。

排他を if 文で振り分けしていた部分はこの手があった。
今まで何をやってきたのかと情け無くなった。

排他トグルで All Off は All Off の場合のみ処理すればいい。
それ以外だったら必ずどこかが On になっているということだから。
散々悩んだことなのに解ってしまえばこんなにアッサリ。

ということで、こんなにコードが短くなった。
強引に短くするドヤ顔コードは吐き気がするけど正攻法で短くするのは気持ちいい。

GtkActionGroup の処理が取り除かれているけど気にしないで。
メニューバーを GNOME の意向に合わせ取っ払っているだけなので。
iPhone を使うようになってから更にメニューバーが邪魔に思えてきた。
何故あんなのが普及したのか謎と考えるくらいに。

gst direction next frame

JavaScript でスマホゲームもいいけどメインの GTK+ もやらねば。
久々に Y901x の更新、ポーズからコマ戻しが上手くいく方法を見つけたので。

# self.player @ playbin
# self.settingwin @ My Setting

def set_next_frame(self, rate):
    """
        1 frame up down (rate 1.0 or -1.0)
    """
    if self.settingwin.direction != rate:
        self.settingwin.direction = rate
        self.set_playback_direction(rate)
    event = Gst.Event.new_step(Gst.Format.BUFFERS, 1, 1.0, True, False)
    self.player.send_event(event)

def set_playback_direction(self, rate):
    """
        Change direction
    """
    pos = self.player.query_position(Gst.Format.TIME)[1]
    if rate >= 0.0:
        self.player.seek(
                rate,
                Gst.Format.TIME,
                Gst.SeekFlags.FLUSH | Gst.SeekFlags.ACCURATE,
                Gst.SeekType.SET, pos,
                Gst.SeekType.SET, -1)
    else:
        self.player.seek(
                rate,
                Gst.Format.TIME,
                Gst.SeekFlags.FLUSH | Gst.SeekFlags.ACCURATE,
                Gst.SeekType.SET, 0,
                Gst.SeekType.SET, pos)

def on_play(self, widget, data=None):
    if self.settingwin.direction == -1.0:
        self.settingwin.direction = 1.0
        self.set_playback_direction(1.0)
    if self.player.get_state(1)[1] == Gst.State.PLAYING:
        self.player.set_state(Gst.State.PAUSED)
    else:
        self.player.set_state(Gst.State.PLAYING)

逆転させる手続きを分離して send_event するだけだった。
GLib.idle_add とか色々試したのに実はこんなに簡単だった…

これなら関数を抜けるので逆転した後で 1 コマ移動ということになるのかな。
再生開始で正転に戻すのを忘れないようにと。

それと手段が全然解らず困り果てていたシークバー上のマウスホイール。
event.direction 引数が Gdk.ScrollDirection.SMOOTH になるのよ。
Gdk.ScrollDirection.UP, DOWN になる記事以外見当たらなかったし。

http://nullege.com/codes/show/src%40p%40i%40pitivi-HEAD%40pitivi%40utils%40widgets.py/1035/gi.repository.Gdk.ScrollDirection.UP/python

そういうことだったのか!
やっぱり GPL のコードを見たほうが圧倒的に参考になるね。
何も作品を作っていなさそうな人って何故勉強しているのか、日本人に多過ぎ。

def on_seek_scroll_event(self, widget, event):
    """
        Mouse Wheel event
        widget @ GtkScale
    """
    delta = 0
    if event.direction in [Gdk.ScrollDirection.UP, Gdk.ScrollDirection.RIGHT]:
        delta = 1
    elif event.direction in [Gdk.ScrollDirection.DOWN, Gdk.ScrollDirection.LEFT]:
        delta = -1
    elif event.direction in [Gdk.ScrollDirection.SMOOTH]:
        unused_res, delta_x, delta_y = event.get_scroll_deltas()
        if delta_x:
            delta = math.copysign(1, delta_x)
        elif delta_y:
            delta = math.copysign(1, -delta_y)
    if delta:
        location = widget.get_value() - (delta * 10000000000)
        if location > 0 and location < self.playinfo.duration:
            self.set_play_position(location)
            self.put_time_status(location)

これで約 10 秒送りなホイールスクロールが可能になった。
Fedora 20 でしか試していないけど多分 GNOME なら大丈夫だろう。
次バージョン早く出ないかなぁ、20 が安定しているからいいけど半年サイクルに慣れ過ぎた。

Gtk+ 3.10 on Ubuntu 14.04

Ubuntu 14.04 が出たが Fedora な筆者はどうでも…よくない。
GtkHeaderBar や GtkStackSwitcher は Unity 環境ではどうなるか確認せねば。
使用感とかは他人に任せてと。

筆者が書いた下記サンプルコードで試してみる。
GTK+ 3.10 – L’Isola di Niente

gtkheaderbar

GtkHeaderBar はそのまま使えるけど標準とは異なるものになってしまう。
つまり標準の Nautilus とかは改造しているということだったのか。

それに枠に影がなくなるのでウインドウが重なると境界が解らない。
更に最大化しても上部バーと一体化せず一段下へと標準とは異なる動作に。
というか、リサイズできなくなる。

なんだよ、つまり GtkHeaderBar は使えないということじゃないか。
詰めが甘いのか意図的なのかは解らないけど。
GtkStack, GtkStackSwitcher, GtkListBox は問題なく使えるようです。

GtkHeaderBar and GStreamer | PaePoi

GTK+ 3.10 なのだからこの動画の画面が出ない症状は同じだった。
いや GNOME だと真っ黒だけど Unity だと透明になる、音だけなのは同じ。
でも Totem 3.12 は GtkHeaderBar 採用になっているんだよな。
このあたりは Fedora 21 の時に。

appmenu

ApplicationMenu は GNOME と同様になるようにしたようです。
なのに何故か Nautilus 等のメニューバーは古いものを採用。

nautilus_gnome

menubar_ubuntu

同じ Nautilus 3.10.1 のはずなのに何だよこの違い。
というかこれなら 3.8 のままでいいじゃないか、と思うのだが。

他にも GNOME 3.10 を知っている人ならツッコミ所が多々。
3.10 で GtkStackSwitcher になった部分が全部 GtkNotebook に変更って。
ユニバーサルアクセス設定等の GtkListBox も同様、これは古いアプリのまま?
GtkHeaderBar 以外は特に問題ないのに何故古いままな UI に戻すやら。

他にも GNOME がボタンをアイコン化した部分等も何故か文字列ボタンに戻している。
GNOME プロジェクトの成果物に昔ながらの UI をわざわざ被せた感じ。
おかしいな、Unity ってタッチパネル向けに振った UI じゃなかったか?

独自にやっていたらベースの GNOME アプリ側もやってきたので色々あるだろうけど。
3.12 は Gedit も Totem も GtkHeaderBar になるんだが、どうするのだろう。

当面 GTK+ アプリは Unity 環境専用ビルドを別途で用意する必要あり。
なんて嫌だぞこのやろう。

Gjs I/O Stream

以前下記で Seed, Python で Gio による読み書きをやったのを Gjs にしてみた。
Gio Streaming I/O @ Seed and PyGI | PaePoi

GCancellable 等を指定(null でいい)しないといけなかった。
他色々と Seed より Python に似ていて困惑。

/*
    Gjs Streaming I/O File Read and Write Sample
*/

const Gio = imports.gi.Gio;
const Format = imports.format;

String.prototype.format = Format.format;

let filename = "test_gjs.txt";
const lines = "Japan\n\n日本語";

// Write
let f = Gio.file_new_for_path(filename);
let fstream = f.replace(null, false, Gio.FileCreateFlags.NONE, null);
try {
    // Error, This is no Exception.
    // let dstream = new Gio.DataOutputStream(fstream);
    // g_output_stream_write: assertion 'G_IS_OUTPUT_STREAM (stream)' failed
    let dstream = new Gio.DataOutputStream({base_stream:fstream});
    dstream.put_string(lines, null);
} catch (e) {
    // Without meaning
    log("*** ERROR WRITE: " + e.message);
}
fstream.close(null);

// Read
f = Gio.file_new_for_path(filename);
fstream = f.read(null);
// Same as above
let dstream = new Gio.DataInputStream({base_stream:fstream});
while (1) {
    let [text, len] = dstream.read_line_utf8(null);
    if (text == null) break;
    print("%s(%d)".format(text, len));
    // or print(text + "(" + len + ")");
}
fstream.close(null);

/* output
Japan(5)
(0)
日本語(9)
*/

DataOutputStream 等の引数は json で指定しないと認識してくれない。
現時点では理由が解らない、GOutputStream のサブクラスと認識できないのか?
とにかく Error で困ったら json 指定にすればなんとかなるっぽい。

しかも GError 引数なのに例外にならず普通にエラー。
try ブロックを書いたけど無意味だった、これはちと困る。

おまけで let 変数のスコープは try ブロック内でも有効だと解った。
Python と同様にするには var を利用、注意しよう。

// var is OK
let f = Gio.file_new_for_path("a.txt");
try {
    // var fstream = f.replace(null, false, Gio.FileCreateFlags.NONE, null);
    let fstream = f.replace(null, false, Gio.FileCreateFlags.NONE, null);
} catch (e) {
    //
}
fstream.close(null); // ReferenceError
# Python is OK
f = Gio.file_new_for_path(filename)
try:
    fstream = f.replace("", False, Gio.FileCreateFlags.NONE, None)
except Exception as e:
    pass
fstream.close(None)

Seed にある c_new という意味不明な指定が無いのはちょっぴり嬉しいね。
それ以外は全部 Gjs のほうが面倒臭いけど。

戻り値が Python 同様に 2 つ以上である場合がある。
Python はタプルなんだけど、タプルが無い言語でもこの手があったか。
まあどちらも引数に渡された変数の値を変更できない言語だし。

どうも PyGObject の手段を参考に作っているような気がする。
いや逆かも、というか GLib 側の意向でこうなったのかな?

とにかく Python 屋なら Seed より Gjs のほうがなじみやすいという結論で。