GtkDrawingArea Custom Drawing

前回の続き、GtkDrawingArea です。
フォトレタッチのようにマウス軌跡の連続線を GTK+ で描きたい。
って探すまでもなく GTK+ 3 Reference Manual に書いてあった。

Custom Drawing: GTK+ 3 Reference Manual

つまり軌跡を残すには draw シグナル引数にある cairo_t の中に描写しない。
サーフェス(面の意味)を別に作ってそちらに描写する、当然追記になる。
そのまま gtk_widget_queue_draw 関数を呼び出し draw シグナルを送る。
ハンドラにて先程のサーフェスを cairo_t にセットする。

解ってしまえばそりゃそうだ、みたいな。

しかし gjs へのバインディング記法がサッパリ解らない。
Gir の cairo は何もできないし、使おうとした人なら解ってくれるよね。
Gdk.cairo*** も GTK+ と同様に書き換えるとエラー。
cr.set_source_rgb みたいなバインドではないようだ、うーん。

installed-tests/js/testCairo.js ? master ? GNOME / gjs ? GitLab

散々遠回りをしてやっとこんなのを捜し出した。
リソースにある cairo を使えばいいってことね。
しかしコッチは GTK+ と違って C 言語とかなり違う表記を強いられる。

// C
// JavaScript

cairo_create (surface);
new Cairo.Context(surface);

cairo_set_source_surface (cr, surface, 0, 0);
cr.setSourceSurface(surface, 0, 0);

cairo_set_source_rgb (cr, 1, 1, 1);
cr.setSource(Cairo.SolidPattern.createRGB(1, 1, 1));

gdk_window_create_similar_surface
widget.window.create_similar_surface
//Gdk.Window.create_similar_surface // Error

CAIRO_CONTENT_COLOR
Cairo.Content.COLOR

てな感じ。

ということで、実際に上手くいったソース。

#!/usr/bin/gjs

// Resource
const System = imports.system;
const Cairo = imports.cairo;
// Gir
const GObject = imports.gi.GObject;
const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;
const Gtk = imports.gi.Gtk;
const Gdk = imports.gi.Gdk;

var DrawingWindow = GObject.registerClass({
    GTypeName: "DrawingWindow"
}, class DrawingWindow extends Gtk.ApplicationWindow {
    _init(app) {
        super._init({application: app});
        // var
        this.surface = null;
        // HeadreBar
        let hb = new Gtk.HeaderBar({show_close_button: true});
        let clearButton = new Gtk.Button({label: "Clear"});
        clearButton.connect("clicked", ()=> {
            this.clearSurface();
            this.canvas.queue_draw();
        });
        hb.pack_start(clearButton);
        this.set_titlebar(hb);
        // DrawingArea
        this.canvas = new Gtk.DrawingArea();
        this.canvas.set_events(
            Gdk.EventMask.BUTTON_PRESS_MASK |
            Gdk.EventMask.BUTTON_RELEASE_MASK |
            Gdk.EventMask.BUTTON1_MOTION_MASK |
            Gdk.EventMask.STRUCTURE_MASK );
        this.canvas.connect("configure-event", (widget, event)=> {
            this.surface = this.canvas.window.create_similar_surface(
                Cairo.Content.COLOR,
                widget.get_allocated_width(),
                widget.get_allocated_height() );
            this.clearSurface();
            return true;
        });
        this.canvas.connect("button-release-event", (widget, event)=> {
            return false;
        });
        this.canvas.connect("button-press-event", (widget, event)=> {
            if (event.get_button()[1] === 1) {
                let [res, x, y] = event.get_coords();
                hb.set_title(`${x}/${y}`);
                this.drawBrush(x, y);
            }
            return false;
        });
        this.canvas.connect("motion-notify-event", (widget, event)=> {
            let [res, x, y] = event.get_coords();
            hb.set_title(`${x}/${y}`);
            this.drawBrush(x, y);
            return false;
        });
        this.canvas.connect("draw", (widget, cr)=> {
            cr.setSourceSurface(this.surface, 0, 0);
            cr.paint();
            return false;
        });
        this.add(this.canvas);
        // this
        this.resize(400, 400);
        this.show_all();
    }
    drawBrush(x, y) {
        let cr = new Cairo.Context(this.surface);
        cr.arc(x, y, 10.0, 0.0, 2*Math.PI);
        cr.fill();
        //this.canvas.queue_draw();
        this.canvas.queue_draw_area(x-10, y-10, 20, 20);
    }
    clearSurface() {
        let cr = new Cairo.Context(this.surface);
        cr.setSource(Cairo.SolidPattern.createRGB(1, 1, 1));
        cr.paint();
    }
});

var DrawingApplication = GObject.registerClass({
    GTypeName: "DrawingApplication"
}, class DrawingApplication extends Gtk.Application {
    _init(v) {
        GLib.set_prgname("Program");
        super._init({
            application_id: "org.sasakima.drawwin",
            flags: Gio.ApplicationFlags.HANDLES_OPEN
        });
    }
    vfunc_startup() {
        super.vfunc_startup();
        new DrawingWindow(this);
    }
    vfunc_open(files, hint) {
        this.active_window.present();
    }
    vfunc_activate() {
        this.active_window.present();
    }
});

new DrawingApplication().run([System.programInvocationName].concat(ARGV));

で。

となります。
ImageSurface に画像を指定して文字入れ、なんてのもこの応用で可能。
フォトレタッチとはとても言えないけど手段は解ったということで。

GtkDrawingArea GdkEvent

あけましておめでとうございます。
昨日出した comipoli がバグだらけで今年も正月からプログラミング。

まだおかしい、button-press-event がタイトルバーに反応する。
多分 GtkHeaderBar 部分もクライアントエリアとして認識されているかと。
GNOME は GtkHeaderBar を推奨だけど使うとこんな弊害が色々出るんだよなぁ。

GtkDrawingArea で GdkEvent を取ればいいのだが、手段が解らなかった。
普段あまりネタが無いのに元旦にネタができるという。

日本語で見つかったサイトはココだけだった、GTK2 だけど。
GtkDrawingAreaに図形を表示する
2019 年にもなって手打ち HTML かつスマホ用タグ無しサイトってどうなんだ。。。。。
いやそれはどうでもよくて。

c – In GTK+3, how do I get a drawingarea to respond to mouse events? – Stack Overflow

やはり GTK3 も gtk_widget_add_events と can-focus プロパティか。
とにかくやってみよう。

Events: GDK 3 Reference Manual

GdkEventMask を調べたら GDK_BUTTON1_MOTION_MASK なんて便利そうなものが。
マウス左ボタンを押しっ放しで動かした場合のみ飛んでくるシグナルのようだ。
自分でフラグを用意しなくてもいいのね、早速。

#!/usr/bin/gjs

const System = imports.system;
const GObject = imports.gi.GObject;
const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;
const Gtk = imports.gi.Gtk;
const Gdk = imports.gi.Gdk;

var DrawingWindow = GObject.registerClass({
    GTypeName: "DrawingWindow"
}, class DrawingWindow extends Gtk.ApplicationWindow {
    _init(app) {
        super._init({application: app});
        // var
        this.xy = [false, 0, 0];
        // HeadreBar
        let hb = new Gtk.HeaderBar({show_close_button: true});
        this.set_titlebar(hb);
        // DrawingArea
        let da = new Gtk.DrawingArea();//{can_focus: true});
        //da.set_events(
        da.add_events(
            Gdk.EventMask.BUTTON_PRESS_MASK |
            Gdk.EventMask.BUTTON_RELEASE_MASK |
            Gdk.EventMask.BUTTON1_MOTION_MASK |
            Gdk.EventMask.STRUCTURE_MASK );
        da.connect("button-release-event", (widget, event)=> {
            return false;
        });
        da.connect("button-press-event", (widget, event)=> {
            if (event.get_button()[1] === 1) {
                this.xy = event.get_coords();
                hb.set_title(`${this.xy[1]}/${this.xy[2]}`);
                widget.queue_draw();
            }
            return false;
        });
        da.connect("motion-notify-event", (widget, event)=> {
            this.xy = event.get_coords();
            hb.set_title(`${this.xy[1]}/${this.xy[2]}`);
            widget.queue_draw();
            return false;
        });
        da.connect("draw", (widget, cr)=> {
            cr.arc(this.xy[1], this.xy[2], 10.0, 0.0, 2*Math.PI);
            cr.fill();
        });
        this.add(da);
        // this
        this.resize(400, 400);
        this.show_all();
    }
});

var DrawingApplication = GObject.registerClass({
    GTypeName: "DrawingApplication"
}, class DrawingApplication extends Gtk.Application {
    _init(v) {
        GLib.set_prgname("Program");
        super._init({
            application_id: "org.sasakima.drawwin",
            flags: Gio.ApplicationFlags.HANDLES_OPEN
        });
    }
    vfunc_startup() {
        super.vfunc_startup();
        new DrawingWindow(this);
    }
    vfunc_open(files, hint) {
        this.active_window.present();
    }
    vfunc_activate() {
        this.active_window.present();
    }
});

new DrawingApplication().run([System.programInvocationName].concat(ARGV));

あれ?

cairo のクリアをしていないので追加描写だと思ったら書き換えされる。
逆にフォトレタッチみたく追加で描くにはどうすればいいんだ?
あぁヤルことが増えてしまった、それは後日ということで。
とにかくマウス左ボタンに追従する円のできあがり。

マウスだけなら can-focus プロパティはセットする必要が無いみたい。
GDK_KEY_PRESS_MASK 等を使うなら当然必須になると思うけど。

add_events, set_events のどちらでも動いた。
set は初期化、add は既にあるイベントに追加という振り分けみたい。
GtkDrawingArea は元々何もないので同じということね。

今年もこんな感じでいきます。

Automator JXA and shell

サービスがいいMacBookにしておく – ザリガニが見ていた…。

Automator ってコンテキストメニューが作れるんだ!
この方法なら筆者が書いた Finder Script より簡単にシェルが使える。
macOS アプリを JXA (AppleScript) で拡張 – L’Isola di Niente
いや Nautilus スクリプトと同じように使えるというべきか。

では早速。
Mojave ではサービスではなく「クイックアクション」になっています。

リンク先ではテキストの選択だけど Finder での選択もイケそう。
「Finder 項目を表示」を挟む必要があるみたい。
早速同じ AppleScript を試すと、、、、、

あぁ、やはりパス名は古い mac 形式、いつものことだ。
しかしこの input は CR 改行区切りの文字列なのか配列なのか?
JXA でないと筆者は解らないので「JavaScript を実行」に置き換えて。

function run(input, parameters) {
	
	// Your script goes here
	let app = Application.currentApplication();
	app.includeStandardAdditions = true;
	//let s = input.toString().split('\r').join('\n');
	let s = input.join('\n');
	app.displayAlert(s);

	//return input;
}

うん、JXA だと input は JavaScript の配列になるようだ。
こっちだと UNIX パスになるな、何故かはワカンネ。
最後の return は次に続くチェーンが無いなら不要みたい。

んで、名前を付けて保存すると。

~/Library/Services/

以下に保存される、*.workfrow はディレクトリのようだ。
中にある info.plist の NSMenuItem キーからメニューを変名できる。

まて、よく見ると「シェルスクリプトを実行」のアクションがある。
AppleScript, JXA だけでなく直接 bash も使えるようで。
入力を「引数として」の選択肢もある、これならどうなる?

ところでカレントディレクトリは又してもルートなんだろうか。
と思って調べたら $HOME だった、どっちにしろ移動しなきゃ。
ファイル実行ではないので $0 では調べられない、と最初思ったけど。
ファイルを選択してコンテキストメニューなんだから $1 でいいヤン。

cd `dirname $1`
echo "$@" > out.txt

で保存して Finder で何か選択して実行。
選択したファイルがある位置に UNIX 形式パスが IFS 区切りでリダイレクトされるのが確認できるはず。
これでシェルを簡単に使えるようになるのでお好きなコマンドを。
JXA とチェーンを繋げればかなり高度な自動化ができそう。

Macbook Plexus

我が Macbook Air 2018 の天板に早くもキズがぁ!
いや爪は引っかからないし線的な汚れかも。

2011 モデルのゴールドは雑に扱ってもこんなにならなかったのに。
スペースグレイの色が汚れやすいのか、新型の表面処理が原因だろうか?

しかしいくら雑巾で拭いても全然消えない、これは困った。
コンパウンドしかないかなぁと思いながら周りを見ると。。。。。

Plexus があるじゃないの。
バイク乗りならみんな持っている、2 りんかんで売っているし。
いや筆者はビッグ?なスクーター、ってそれはどうでもよくて。

いつもヘルメットを拭いている高級な布に吹きかけ天板をサラッと。
一発で綺麗になった、Plexus ってこんなに凄かったのか!

ということで、Macbook のお手入れにも Plexus は’使えます。
Fedora で使っている RealForce も今度バラした時に使うわ。

colon command

Bash, no-arguments warning, and case decisions – Stack Overflow

このページの

: ${1?"Usage: $0 ARGUMENT"}

がよく解らなかった。
先頭のコロンって何も起こらないコマンドだったはずだけど。
変数中の ? も今まで見たことがなかった。

何もしない組み込みコマンド ":" (コロン)の使い道 – Qiita

凄く詳しい解説をありがとう。

: echo 1
# 無視される

: | echo 1
# 1 が出力される

: touch one.txt > tow.txt
# 空の tow.txt だけが造られる

: ${var=3}
echo $var
# 3 が出力される

なるほど、先頭コロンが何もしないのは最初の引数だけなんだ。
でも :=, :? の場合は副作用が起こるということらしい

それと変数中の ? の正体は :? のようだ、イコールも同様。
:- と同様にコロンは実は無くてもいいということね。

つまり最初のは第一引数が無い場合は ? 以下をエラー出力して終了という意味。
こんな短いコードで実現、って Bash 解りづれぇYO!