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

G_APPLICATION_HANDLES_OPEN @ C, PyGI, Seed

PyGI で GtkApplication の G_APPLICATION_HANDLES_OPEN が上手くいかない。
引数の有る無しで activate か open シグナルに振り分けされる処理だが…
引数を渡せば open のシグナルが来るのだけど files は空ッポの配列になる。
もしかしてと思い Seed で使ってみたがコレも上手く行かない。

GtkApplication

bloat_pad とかいうサンプルがあるのに…
まてよ、C 言語でなら本当に上手くいくのだろうか?
C で GTK+ をやるつもりは無かったけど GTK+ ヘッダを入れて実験してみよう。

ついでだから Fedora 16 で GTK+ ヘッダ導入方法を少し。

gcc は誰でも解るからいいとして、とうの昔に私は導入済みだし。
「追加/削除」にて gtk3 で探せばアッサリヘッダ群は見つかる。

gtk-devel* だけチェックすれば cairo とかのヘッダも勝手に入る。

devhelp も入れておくと web でマニュアルを読むより楽。
anjuta は個人的にはいらない、IDE の使い方を覚えるのと Makefile の書き方を覚えるのとでどちらの時間がもったいないと思うかで決めればいいと思う。

さて準備はできた、Makefile を作って bloat_pad とやらをビルド。
あたりまえのように files には GFile のリストが入っていた。

でもこのコードではイマイチ理解ができないので単純なコードで作ってみる。
C は普通に g_signal_connect させてみた。

#include <gtk/gtk.h>

static void
newwin (GApplication *app,
        const gchar  *title)
{
    GtkWidget *window;

    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    gtk_window_set_application (GTK_WINDOW (window), GTK_APPLICATION (app));
    gtk_window_set_title(GTK_WINDOW (window), title);
    gtk_window_resize(GTK_WINDOW (window), 200, 10);
    gtk_widget_show_all (GTK_WIDGET (window));
}

static void
on_activate (GApplication *application)
{
    newwin (application, "activate");
}

static void
on_open (GApplication  *application,
         GFile        **files,
         gint           n_files,
         const gchar   *hint)
{
    gint i;

    newwin (application, "open");

    for (i = 0; i < n_files; i++)
        printf ("%s\n", g_file_get_uri(files[i]));
}

int
main (int argc, char **argv)
{
    gtk_init (NULL, NULL);
    GtkApplication *app;
    int status;

    app = gtk_application_new ("org.gtk.apptest",
            G_APPLICATION_HANDLES_OPEN);
    g_signal_connect(G_OBJECT(app), "activate",
            G_CALLBACK (on_activate), NULL);
    g_signal_connect(G_OBJECT(app), "open",
            G_CALLBACK (on_open), NULL);

    status = g_application_run (G_APPLICATION (app), argc, argv);
    g_object_unref (app);

    return status;
}

WARNING で Files==[] になる PyGI
sys.argv をタプルにしてみたが結果は同じだった…

#!/usr/bin/env python
#-*- coding:utf-8 -*-

import sys
from gi.repository import Gtk, Gio

class Win(Gtk.Window):
    def __init__(self, app, title):
        Gtk.Window.__init__(self)
        self.set_application(app)
        self.set_title(title)
        self.resize(200, 10)
        self.show_all()

def on_activate(application):
    Win(application, "activate")

def on_open(application, files, n_file, hint):
    print files
    Win(application, "open")
    """
    for f in files:
        print f.get_uri()

    Warning: g_value_get_boxed: assertion `G_VALUE_HOLDS_BOXED (value)' failed
    """

Gtk.init(sys.argv)
app = Gtk.Application.new(
        application_id="apps.test.helloworld",
        flags=Gio.ApplicationFlags.HANDLES_OPEN )
app.connect("activate", on_activate)
app.connect("open", on_open)
ARGV = tuple(sys.argv)
app.run(ARGV)

WARNING は出ないけど files がワケワカになる Seed
Seed は set_title しなくてもコレでタイトルは指定できるよ。
それと Seed.argv は配列を一つ減らさないと認識してくれなかった。

#!/usr/bin/env seed

Gio = imports.gi.Gio;
Gtk = imports.gi.Gtk;

Win = new GType({
    parent: Gtk.Window.type,
    name: "SeedWindow",
    init: function (title){
        this.resize(200, 10);
        this.show_all();
    }
});

Gtk.init (null, null);

var app = new Gtk.Application({
    application_id:"apps.test.app",
    flags:Gio.ApplicationFlags.HANDLES_OPEN
});
app.signal.activate.connect( function(application) {
    var w = new Win({title:"activate"});
    w.set_application(application);
});
app.signal.open.connect( function(application, files, n_file, hint) {
    print(files);
    var w = new Win({title:"open"});
    w.set_application(application);
    /*
    for (var i=0; i<n_file; i++) {
        print( files[i].get_uri() );
    }
    TypeError 'undefined' is not an object (evaluating 'files[i].get_uri')
    */
});
var r = new Array();
for (var i=1; i<Seed.argv.length; i++)
    r.push(Seed.argv[i]);
app.run(r.length, r);

Makefile 入りアーカイイブも置いておく。
Wordpress って tar.xz だとアップロードができないじゃん!
まあいい、tar.gz で。

app.tar.gz

実行、引数を入れればドレもタイトルバーが open になるのが解る。
でも上手くいくのは C のみ、Seed はどうにかできそうだが今はワカンネ。
Python は PyGI の処理なのか、gi 自体の限界なのか…

ついでに解ったけど存在しないファイル名でも files に含まれるのね。

てなわけで、現状では HANDLES_OPEN にしたいなら C でやれってことですね。
しかし久々の C 言語はやっぱり面倒くさい。

Gio Streaming I/O @ Seed and PyGI

そういえば Seed で環境変数を使うにはどうすればいいのだろう?
Python のように便利な標準モジュールみたいなのは無いわけで。
多分 glib 等に頼るしかないのだろう。

Miscellaneous Utility Functions

glib に env 関連はあるね。

#!/usr/bin/env seed

GLib = imports.gi.GLib;

Seed.print(GLib.getenv("HOME"))

GLib.setenv("HOGE", "Madoka Magica")
Seed.print(GLib.getenv("HOGE"))

これでイケる、GLib.get_home_dir() でも $HOME は取れる。
でも日本語を getenv だと ? になって print されてしまった。

インタラクティブシェルなら問題なく日本語表示している。
? の文字数は合っているから UTF-8 だと認識はしているみたいだが何故だろう。
Python の coding:utf-8 指定のようなものが何か必要なのか?

そういえばファイルを読み書きする open() も無いよな。
gio で読み書きするしか方法が無いと思うけど。

Seed/Tutorial – GNOME Live!
Seed/Tutorial/Simple_file_io – GNOME Live!

GDataInputStream, GDataOutputStream を噛ます必要があるようです。
Streaming I/O
なんか面倒くさいけど以下で読み書きできた。
読み込みはよくある一行毎に処理する方法にしている。

#!/usr/bin/env seed

/*
    Streaming I/O File Read and Write Sample
    if the Seed
    ( null parameter is not needed )
*/

Gio = imports.gi.Gio;

var filename = "test_js.txt";
var lines = "madoka\nhomura\nmami\nsayaka\nkyoko";

// Write
var f = Gio.file_new_for_path(filename);
var fstream = f.replace();
var dstream = new Gio.DataOutputStream.c_new(fstream);
dstream.put_string(lines);
fstream.close();

// Read
f = Gio.file_new_for_path(filename);
fstream = f.read();
dstream = new Gio.DataInputStream.c_new(fstream);
while (1) {
    var text = dstream.read_line_utf8();
    if (text == null) break;
    Seed.printf("%s(%d)", text, text.length)
}
fstream.close();

日本語でも書き込みはできた、けれどアウトプットはやはり ? になる。
それと length を取得する方法が無いみたい、int では値渡しになるし…

コレで gi にて Streaming I/O を使う方法が解ったぞと。
ということは PyGI で同様にするには以下のように。
ほぼ同じだったけど微妙に違う。

#!/usr/bin/env python
#-*- coding:utf-8 -*-

'''
    Streaming I/O File Read and Write Sample
    if the Python
    ( All None parameter @ GCancellable )
'''

from gi.repository import Gio

filename = "test_py.txt"
lines = "madoka\nhomura\nmami\nsayaka\nkyoko"

# Write
f = Gio.file_new_for_path(filename)
fstream = f.replace("", False, Gio.FileCreateFlags.NONE, None)
dstream = Gio.DataOutputStream.new(fstream)
dstream.put_string(lines, None)
fstream.close(None)

# Read
f = Gio.file_new_for_path(filename)
fstream = f.read(None)
dstream = Gio.DataInputStream.new(fstream)
while 1:
    text, length = dstream.read_line_utf8(None)
    if text == None:
        break
    print "{0}({1})".format(text, length)
fstream.close(None)

Seed は基本的に NULL でいい引数は書く必要は無い、書いても結果は同じ。
Python は GCancellable を強要する、None で通常なら問題ない。
つか Python はやっぱりタプルを戻す、つまり Python なら length も取れる。
慣れていないと困惑するよなこの仕様。

DataInputStream の c_new って何だろう?

#!/usr/bin/env seed

Gio = imports.gi.Gio;

for (var s in Gio.DataInputStream) {
    Seed.print(s);
}
/* output
type
c_new
prototype
*/

static 状態では prototype のメソッドを使えないということか。
そういうことなら new という名前でイイと思うが理由があるのだろう。

とりあえずこれだけ解れば小物スクリプト程度なら作れると思う。

どうでもいいけど。

var f = Gio.File.new_for_path(fn)
var f = Gio.file_new_for_path(fn)

PyGI でも同じである、gi ってどっちでもいいんだね。

Gedit External Tools and Seed argv

Linux に移行してからテキスト編集のほぼすべてで Gedit を愛用している。
Windows から Linux に移行できたのも Gedit が存在したからなのは確実といえる。
Gedit をシンプルという人はただの無知、Windows 的思考を早く捨てろといいたい。
Windows ではほとんど意味が無くなった環境変数が Linux では重要なのです。

前置きはこれくらいにして。
私は Python コードの Debug に長いこと以下の外部ツールを使っていた。

#! /bin/sh
python $GEDIT_CURRENT_DOCUMENT_PATH

を F5 キーに割り付けて結果をボトムペインに表示させている。
しかし最近 Seed を少しかじってたりするが、つい F5 を押してしまう。
F6 等に割り付けしても ibus に取られるのか無視されるし何より間違える。

それなら F5 からソースによりで振り分けしてしまえばいいじゃないか!
環境変数 $GEDIT_CURRENT_DOCUMENT_TYPE で ContentType が得られるので

#!/bin/sh
PYTHON="text/x-python"
SEED="application/javascript"
HTML="text/html"
BASH="application/x-shellscript"
echo @Lunning $GEDIT_CURRENT_DOCUMENT_TYPE
if [ $GEDIT_CURRENT_DOCUMENT_TYPE = $PYTHON ]; then
    python $GEDIT_CURRENT_DOCUMENT_PATH
elif [ $GEDIT_CURRENT_DOCUMENT_TYPE = $SEED ]; then
    seed $GEDIT_CURRENT_DOCUMENT_PATH
elif [ $GEDIT_CURRENT_DOCUMENT_TYPE = $HTML ]; then
    google-chrome $GEDIT_CURRENT_DOCUMENT_PATH
elif [ $GEDIT_CURRENT_DOCUMENT_TYPE = $BASH ]; then
    sh $GEDIT_CURRENT_DOCUMENT_PATH
else
    echo Non Support File
fi

こうしておけば ContentType 次第でコマンドを変更できるじゃない。
私が現在利用するのはコレくらいだ、好みで追記とかすればいい。
ちなみに Perl の ContentType は application/x-perl である。
一部の人は「Opera じゃないのか?」とツッコまないでください。

これで Python でも Seed でもボトムペインに結果を出力できる。
そういえば Seed 名前空間にはどんなメソッドがあるのかな?
Seed で Python の dir() に相当するのは for in 文なので

#!/usr/bin/env seed

for (var s in Seed) {
    Seed.print(s);
}

と、こんな感じで利用できます。
なるほど、argv オプションはココから得るのかと解った。
for 文で取り出して、argv.length でコマンド数が得られるようだ。

#!/usr/bin/env seed

for (var i=0; i < Seed.argv.length; i++) {
    Seed.print(Seed.argv[i]);
}

Python とは違い引数の最初に seed が含まれてしまうようだ。
+x して ./ でも結果は同じ、注意しないと間違えそう。

後は for in 文を使って各名前空間のメソッドをボトムペインで調べてと。
かなり Seed プログラミングが楽になったぞと。
いや、間違えて js を Python で、というのが無くなるのが一番嬉しいか。

ここまで Gedit を利用してアプリ作ったりコードをバリバリ書いていても…
Linux 屋からみれば「コイツ vi 使えないの?ププッ!」と思われているかも…
使い方はマジで知らないですけど、サブのエディタは nano ですし。

Gio GFileEnumerator and load_contents

seed をやっている日本人がいた、あぁ日本語で読めるのは素晴らしい。
とはいえ海外でもあまり盛り上がっていないわけですが。
前ページを見て笑った、日本語で探すとそうなってしまうんだよ。

seedでGIOしてみる – ふとしの日記

なるほど、Gio でディレクトリ内容列挙したい場合はこうするのか。
引数は必ずしも JSON でなくてもいいのか?又解らないことが増えた。
while は for in 文でいいんじゃない?と思ったが…
そういえば JavaScript ではオブジェクトメソッドの列挙になるのよね。

ま、私は PyGI でヤルんですけど。
ということで $HOME のファイルを列挙するサンプルコード。
“standard::name” だと byte になるので “standard::display-name” にした。

#!/usr/bin/env python
#-*- coding:utf-8 -*-

import os
from gi.repository import Gio

# set Directory Name
d = Gio.file_new_for_path(os.path.expanduser("~"));
# Get GFileEnumerator
enum = d.enumerate_children(
        Gio.FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
        Gio.FileQueryInfoFlags.NONE,
        None )
for info in enum:
    # info: GFileInfo
    print info.get_display_name()

Python だと当然のように for in 文でイケるようだ。
seed は JavaScript の仕様そのままなので面倒かも。
逆に enumerate_children 等は引数を全部指定する必要がある。
seed は引数が曖昧でいいところもやっぱり JavaScript なのね。

ついでに Gio もう一つ、海外。
GIO tutorial: Stream IO ? Johannes Sasongko’s blog

#!/usr/bin/env python
#-*- coding:utf-8 -*-

import os
from gi.repository import Gio

# set File Name
fn = os.path.expanduser("~/.gtk-bookmarks")
# Create GLocalFile
f = Gio.File.new_for_path(fn)
# load_contents
result, contents, length = f.load_contents(None)
if result:
    print contents

で .gtk-bookmarks 内容が print される。
PyGtk を PyGI に書き換えしたのだが引数も戻り値も微妙に違った。
とにかくファイルの一騎読み込みはこうすればいいみたい。

でも stream のほうは PyGI では上手く行かない。
何か手段があるのだろうけど今はまだ Gio 勉強中。

# おまけ

fedora 16 アップデート通知に Gedit があった。

やっとドラッグアンドドロップ編集で落ちるのを修正してくれた!
ついやってしまっていったい何度書いたコードを無駄にしたことか。

Read in gjotsfile

昨日のコードを .gjotsfile を読み込むように改造してみた。
Gjots2 が保存するファイルは

“\NewEntry” でエントリーの追加
“\NewFolder” で入れ子の作成
“\EndFolder” で入れ子を一階層戻る
ツリータイトルはエントリーの一行め

だけという恐ろしく単純明解な構造である。
ルールもタイトルの一文字めには \ を使わないということだけ。
これなら初心者でも自力で読み書きするコードが書けそうです。

Python でファイルを一行毎に処理する方法は

path = os.path.expanduser("~/.gjotsfile")
f = open(path)
x = f.read()
f.close()
lines = x.split("\n")
for line in lines:
    hoge

という一番単純な方法と

path = os.path.expanduser("~/.gjotsfile")
f = open(path)
try:
    for line in f:
        hoge
finally:
    f.close()

の方法がある、下の方法のほうが圧倒的に早い。
一度全部読み込んで展開するのかストリームで読むかの差である。
但し行末に改行コードが含まれている、それと try 文にしないと何かあった場合に close できないというオチがあるので注意して使いましょう。

とりあえず上記フラグがあった場合に分岐して if 文を作成。
現在は空状態を示す方法として “\NonTitle” みたいなフラグを追加。
“\NewEntry” で GtkTreeStore にアペンドして読み込んだ部分を空にする。
イテレータを “\NewFolder” で入れ替え “\EndFolder” で書き戻し。

そんな感じにして上手くいったコード。

#!/usr/bin/env python
#-*- coding:utf-8 -*-

import os
from gi.repository import Gtk, Gio, GtkSource

class TreeWin(Gtk.Window):
    """
        GtkTreeView Sample
    """
    def __init__(self):
        Gtk.Window.__init__(self)
        # CellRenderer
        cell = Gtk.CellRendererText()
        column = Gtk.TreeViewColumn("Tree Title", cell)
        column.add_attribute(cell, "text", 0)
        # TreeView and TreeStore
        store = Gtk.TreeStore.new([str, str])
        tree = Gtk.TreeView.new_with_model(store)
        tree.append_column(column)
        # Hide Header
        tree.props.headers_visible = False
        # Signal
        selection = tree.get_selection()
        selection.connect("changed", self.selection_changeed)
        #
        # Data
        self.path = os.path.expanduser("~/.gjotsfile")
        t = ".gjotsfile"
        s = ""
        it = None
        itold = None
        f = open(self.path)
        try:
            for line in f:
                if line == "\\NewEntry\n":
                    if t == ".gjotsfile":
                        it = store.append(it, [t, s])
                    elif t != "\\NoneTitle":
                        store.append(it, [t, s])
                    s = ""
                    t = "\\NoneTitle"
                elif line == "\\NewFolder\n":
                    itold = it.copy()
                    it = store.append(it, [t, s])
                    s = ""
                    t = "\\NoneTitle"
                elif line == "\\EndFolder\n":
                    store.append(it, [t, s])
                    it = itold
                    s = ""
                    t = "\\NoneTitle"
                else:
                    if t == "\\NoneTitle":
                        t = line[:-1]
                    s += "{0}".format(line)
        finally:
            f.close()
        #
        # GtkSourceView
        self.view = GtkSource.View()
        self.view.set_show_line_numbers(True)
        # Paned and ScrolledWindow
        swa = Gtk.ScrolledWindow()
        swa.add(tree)
        swb = Gtk.ScrolledWindow()
        swb.add(self.view)
        hp = Gtk.HPaned()
        hp.add1(swa)
        hp.add2(swb)
        # self
        self.set_title("TreeView Test")
        self.add(hp)
        self.show_all()

    def selection_changeed(self, widget, data=None):
        """
            Params @ widget: GtkTreeSelection
        """
        model, it = widget.get_selected()
        if it:
            buf = self.view.get_buffer()
            buf.set_text(model.get_value(it, 1))

class App(Gtk.Application):
    def __init__(self):
        Gtk.Application.__init__(
                self,
                application_id="apps.test.treeview",
                flags=Gio.ApplicationFlags.FLAGS_NONE)
        self.connect("activate", self.on_activate)
        
    def on_activate(self, data=None):
        w = TreeWin()
        w.set_application(self)
    
if __name__ == "__main__":
    app = App()
    app.run(None)

もう少しスマートにできそうなんだけど…
そういえば Ubuntu でも動くかな、仮想マシンの 11.10 で実験。

よし動く、後は書き込みとツリーの追加削除移動なんかだ。
とりあえず今日はココまで、ノンビリすぎだと思うけど。