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

Clutter @ Mouse Drag

Y901x beta は Clutter でシークバーを作っている。
最初は詰まりまくった、なんたって実際に利用するアプリに使うのは初めて。

普通に button-press-event を使ったら全然動作しなかったし。
実際 clutter_actor_set_reactive 関数を見つけるまで手段に超苦しむことに。

やはり ClutterCanvas で書き込もうとも考えた、昔ながらの方法。
でもそれ OpenGL である Clutter 上で Cairo を使うってことだ、変だよ。
しかも何故かエラー出まくり、cairo_t 引数が null になるしワケワカメ。

何か手段は無いかと試行錯誤してまさかの結論が出た。
そういえば Drag and Drop 操作はシークバーと同じ動作だと思いついた。
マウスで掴んで動かして離す、これはイケそう。

ClutterDragAction: Clutter Reference Manual

おぉ、ClutterAction にはこんなものがあるじゃないか。
コレでトラフ画像の ClutterActor を動かせばシークバーとして使えそう。
引数から X 値を得て横方向だけ動くようにすれば従来どおりの手段が使える。
と思ったけど…

#!/usr/bin/gjs

const Lang = imports.lang;
const Clutter = imports.gi.Clutter;

const ClWin = new Lang.Class({
    Name: 'ClWin',
    Extends: Clutter.Stage,

    _init: function() {
        this.parent();
        this.connect("hide", Lang.bind(this, function() {
            Clutter.main_quit();
        }));
        let actor = new Clutter.Actor();
        actor.set_background_color(Clutter.Color.new(255, 0, 0, 255));
        actor.set_size(50, 50);
        this.add_child(actor);
        // DnD
        let dgaction = new Clutter.DragAction();
        //dgaction.connect("drag-begin", Lang.bind(this, function(action, actor, event_x) {
        //    print(event_x);
        //}));
        dgaction.connect("drag-motion", Lang.bind(this, function(action, actor, delta_x) {
    		//actor.set_position(delta_x, 0);
            print(delta_x);
        }));
        //dgaction.connect("drag-end", Lang.bind(this, function(action, actor, event_x) {
        //    print(event_x);
        //}));
        actor.add_action(dgaction, true);
        actor.set_reactive(true);

        this.set_size(300, 150);
        this.show_all();
    }
});

Clutter.init(null);
new ClWin();
Clutter.main();

clutter_dnd

ハンドラ指定しただけで ClutterActor 自体がドラッグされてしまうジャン!
しかも動かすだけなら drag-motion のみ処理すればよかった。
これってそういう Action だったのNE!

更に引数の delta_x 値はドラッグされている ClutterActor 上の値だった。
つまり変化無し、これじゃシークバーなんかに使えないっつーの。
今回は無駄骨だったけどいつかこの経験が役に立つ、といいな。

だけど、コレをやったおかげで clutter_actor_set_reactive 関数を見つけた。
上記リンク先の Description に普通に書いてあったという。

this.is_drag = false;
let actor = new Clutter.Actor();
actor.connect("button-press-event", Lang.bind(this, function(actor, event) {
    this.is_drag = true;
    let [x, y] = event.get_coords();
    print("x= " + x + "; y= " + y);
}));
actor.set_reactive(true); // !!!

あら動いた、シグナルを有効にするにはやはり必須なのね。

motion-notify-event が無いのも困った。
motion-event だと Drag していなくても反応する、回避は簡単だが。

actor.connect("motion-event", Lang.bind(this, function(actor, event) {
    if (this.is_drag) {

これでなんとかシークバーに利用する用の値は得ることができる。

さて実際の実装方法だ。
ClutterActor は特定領域の塗り潰しができないっぽい、Canvas は使いたくない。
ならば別色の ClutterActor をもう一つ用意して被せれば、俺って天才だ。
せっかくだからデジタル表示も被せちゃえ、ClutterText は背景透過だし。

event.x を得て親の横幅で割った比率を被せた ClutterActor の横幅に適用。
という我ながら大雑把な手段なのに理想どおりに動いた。
しかもその値を ClutterGst にも適用できるというオマケ付きで。

それにしても GUI アプリを作るのって本当に楽しいですね。
思ったとおりに動いてくれた時の嬉しさが CUI の比じゃない。

Gtk TreeView (Gjs, PyGObject)

Gjs で ListView(TreeView) にてハマったので覚書。
作り方自体は以下のページ手順で問題ないです。

TreeView with ListStore (JavaScript)

問題は PyGObject のコードから移植する場合。
PyGObject は実は PyGtk プロジェクトの記法を沢山取り入れている。
そのため記述が簡単だけど C の手段から掛け離れている場合が多々ある。

gtk_liststore_append はこんなに別物

/*@ PyGtk and PyGObject
liststore.append([data1, data2])

@ C
GtkTreeIter* it;
gtk_list_store_append(liststore, it);
gtk_list_store_set(liststore, it, 0, data1, 1, data2, -1);
*/

// Gjs
let it = liststore.append();
liststore.set(it, [0, 1], [data1, data2]);

PyGObject, PyGtk ってこんなに省略さていたのか!
C で複数セットの説明は省略、Gjs はナルホドと思うバインド。
PyGtk 様は必ずセットで行うのだから、とまさかのココまで省略したっぽい。
そのまんま PyGObject に採用されてしまったということですね。

gtk_tree_selection_get_selected なんかも。

/* PyGObject
model, it = selection.get_selected()
*/
let [isSelected, model, it] = selection.get_selected();

Python という言語は結果が同じ手段を一つしか提供しないことで有名。
上記は it が None かどうかを調べればいいので isSelected なんて不要。
憶測だけどそういうことなのだろうか。
しかも Gjs は上記で戻り値を二つにしてもエラーにならないから困る。

let [model, it] = selection.get_selected();
print(model);

みたく printf debug を駆使してやっとエラーの原因が解るの繰り返し。
Python コードから移植するより devhelp を見たほうが速い気がする。
複雑なツリービューだともっと面倒臭いかも。

そんなこんながありまして。
Y901x は本サイトでベータ公開となりました。

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

だって早く作らないと作った本人が拡大表示すらできない環境だもん。
SKYLAKE Fedora 環境は頗る安定しています。

array.forEach (this == array)

JavaScript は本当に奇妙な言語です。

#!/usr/bin/gjs

const Lang = imports.lang;

const TestClass = new Lang.Class({
    Name: 'TestClass',

    _init: function() {

        this.arr = [12, 34, 56];
        this.str = "String";

        for (let i=0; i<this.arr.length; i++) {
            print(this.str);
            print(this.arr[i]);
        }
        this.arr.forEach(function(s) {
            print(this.str);
            print(s);
        });
    }
});
new TestClass();

/* output
String
12
String
34
String
56
undefined
12
undefined
34
undefined
56
*/

ループは forEach が解かり易いので多用していたけどコレにハマった。
Gjs はそもそも class が無い JavaScript にアクロバットな対応をしている。
this は C++ の this では無いと気が付くまで半日近く使ってしまった。

JavaScript の this は呼び出し元という意味。
つまり上記では forEach の this は this.arr である。
this.arr.str なんて宣言していないから undefined ということです。

foreach

こんな深い階層で this を使おうとした筆者が悪いんだけどさ。
Gjs だけではないので注意しようね。

set_accels_for_action

前回書いたように Y901x の ClutterGst 化をしているのですが。
マジで何を今更なことを見つけた。

GtkApplication: GTK+ 3 Reference Manual#gtk-application-add-accelerator

add_accelerator は 3.14 以降は使うなってさ。
おぃおぃ Gedit 標準プラグインが今でも普通に使っているんですけど。

とりあえず Gjs だとこんな感じでいいみたいみたい。
Gio.SimpleAction のほうは今までどおりでいい。
次回の更新で自作の奴も書き換えるか。

const Y901Application = new Lang.Class({
    Name: 'Y901Application',
    Extends: Gtk.Application,

    _init: function() {
        this.parent({});
        //this.add_accelerator("<Control>Q", "win.ctrl_q_action", null);
        this.set_accels_for_action("win.ctrl_q_action", ["<Control>Q"]);
    },
    vfunc_open: function(files, n_file, hint) {
        let uri = files[0].get_uri();
        let w = new Y901Window(this);
        w.set_uri(uri);
    },
    vfunc_activate: function() {
        new Y901Window(this);
    }
});

ついでに Gjs で上記のように open をオーバーライドするには。

let argv = ["y901main.js"];
ARGV.forEach(function(s) {
    argv.push(s);
});
GtkClutter.init(null);
ClutterGst.init(null);
let application = new Y901Application();
application.run(argv);

みたいに配列の先頭に何か突っ込まないと動作しない。
何故こんな仕様にしてしまったんだろう?

他色々と苦しみながらこんな所までやってみた。
DnD で再生、スペースキーでポーズ、シークバーも一応使える。

zep

我ながら以前の面影が何も無いけどどうしよう、名前は変えようか?
シークバーはコラム無しだしポーズボタンも無いし拡大はマウスのみだし。
シークバーも Clutter で作っているので、まだ手探り中なので。

y901x-1.1.x0.tar.gz

ここまでのソースを置いておきます、単なるバックアップですけど。
もし筆者が死んだら誰か続きをやってね。

osascript Filenames

今回は osascript でファイル名関連をば。

OS?X 10.10 Release Notes

を見つけてやっと Objective-C からの変換方法が解ってきた。
物凄く下のほうにあって見つけにくかったよ。
たとえばこんなふうに引数が二つ以上の場合は先頭を大文字にして合体。

/*
contentsOfDirectoryAtPath:error:
to
contentsOfDirectoryAtPathError
*/

fm = $.NSFileManager.defaultManager
files = fm.contentsOfDirectoryAtPathError(pathB, null)

– fileExistsAtPath:isDirectory: – NSFileManager Class Reference

つまり Objective-C の文法を少し理解していないと変換すらできない。
コロンって引数だったのか、解ってしまえば楽勝だけど。

ディレクトリ内容列挙は NSFileManager でできるようだ。
ということでコードを書いてみる。
実は var とセミコロンはいらないけどキモいので書くことにした。

#!/usr/bin/osascript

ObjC.import("Cocoa");

// NSString
var pathA = $.NSString.alloc.initWithString("~/Documents/");
// ~ to $HOME
var pathB = pathA.stringByExpandingTildeInPath;
console.log("Path @ " + pathB.UTF8String);
// to URI
var uri = $.NSURL.fileURLWithPath(pathB);
console.log("URI @ " + uri.absoluteString.UTF8String);

// FileManager
var fm = $.NSFileManager.defaultManager;
// get files
var files = fm.contentsOfDirectoryAtPathError(pathB, null);
for (var i=0; i<files.count; i++) {
    // error files[i]
    var filename = files.objectAtIndex(i).UTF8String;
    // remove dot file
    if (filename[0] != ".") {
        console.log(filename);
    }
}

おかげで気が付いた、NSArray は添字ではアクセスできない。
JavaScript の配列でないからか、当然 forEach も length も使えない。
UTF8String に変換した後の文字列は添字可能、色々戸惑う。
バインドしているのではなく明確に型を分けているということみたい。

そんなこんなで。
Mac では JavaScript だけでやっていこうと思っていたけどコリャ無理だ。
Objective-C の勉強も始めますか、Fedora では使わないと思うけど。