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

Gedit(GtkSourceView) Alt+Up/Down

Gedit で何気無く Alt+Up を叩いたら行が上下で入れ替わった。
複数行を選択して同様に叩いても動作するようだ。

GtkSourceView 3 Reference Manual: GtkSourceView

GtkSourceView の標準機能だった、GtkTextView ではできません。
本当にウイジェットだけで動作するかチト試してみる。

#!/usr/bin/env python3

from gi.repository import Gtk, GtkSource

TEXT = """

TextBlock

Press `Alt + Up`
Move the Selection Text
"""

class AltUpTest(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self)
        # Create GtkSourceView
        view = GtkSource.View()
        view.set_show_line_numbers(True)
        # buffer
        buf = view.get_buffer()
        buf.set_text(TEXT)
        # Select
        it = buf.get_iter_at_offset(13)
        itend = buf.get_end_iter()
        buf.select_range(it, itend)
        # self
        self.add(view)
        self.connect("delete-event", Gtk.main_quit)
        self.set_title("Test")
        self.show_all()

AltUpTest()
Gtk.main()

alt_up

マジだった。
つまり GtkSourceView 採用のアプリなら全部可能だということ。
DnD 編集が標準だったり GTK+ 便利すぎだろ。

Windows 用 Gedit 2.30 でも同様。
ただし Alt+Left/Right での英単語移動は GTK3 からなので使えない。

Gedit を五年以上使い続けているのに今頃気が付いた私って…

Vala etc…

昔書いた Vala コードをもう少しマシなサンプルコードに。
引数で指定したテキストファイルをシングルインスタンスで読み込むように。
多重起動防止処理に startup シグナルを利用してみたけど

startup_error

チェーンアップに失敗って何よ、Google 翻訳役に立たネェ。
一応多重起動防止処理は問題なく行なわれるけど気持ち悪い。
とにかく何かがマズいのだろうと色々検索して以下を見つける。

ApplicationWindow

protected override void startup () {
    base.startup ();
    //etc...
}

startup ハンドラで base.startup を呼ぶ必要があるようです。
何故だかよく解らないけどそういうもんだと納得しておこう。
とにかくそんなこんなでこんなコードを書いてみた。

using Gtk;

/**
 * Single Instance Window
 * valac --pkg gtk+-3.0 hoge.vala
 */
public class TextReader : Window {

    private Notebook _note;

    public TextReader () {
        // Property Set
        Object ( title: "Hoge", default_width: 500, default_height: 400 );
        _note = new Notebook ();
        this.add ( _note );
        this.show_all ();
    }
    public void create_tab ( File? file = null ) {
        if ( file == null ) {
            new_contents ( "New Document", "" );
        } else {
            try {
                var fstream = file.read();
                var dstream = new DataInputStream(fstream);
                var sb = new StringBuilder();
                string line;
                while ( (line = dstream.read_line_utf8(null)) != null ) {
                    sb.append( line );
                    sb.append_c( '\n' );
                }
                new_contents ( file.get_basename(), sb.str );
            } catch ( IOError e ) {
                 messagebox ( "IOError", e.message );
                 return;
            } catch ( Error e ) {
                messagebox ( "Error", e.message );
                return;
            } 
        }
    }
    private void new_contents ( string title, string text ) {
        var view = new TextView ();
        view.show ();
        var sw = new ScrolledWindow ( null, null );
        sw.add ( view );
        sw.show ();
        var label = new Label ( title );
        label.show ();
        if ( text != "" ) {
            var buf = view.get_buffer ();
            buf.set_text ( text );
        }
        var n = _note.append_page(sw, label);
        _note.set_current_page (n);
    }
    private void messagebox ( string title, string s ) {
        var dlg = new MessageDialog( this, DialogFlags.MODAL,
                    MessageType.WARNING, ButtonsType.OK, s );
        dlg.set_title ( title ) ;
        dlg.run ();
        dlg.destroy ();
    }
}

public class App : Gtk.Application {

    private TextReader _win;

    public App () {
        Object (application_id:"apps.test.textreader", flags:ApplicationFlags.HANDLES_OPEN );
    }
    protected override void startup () {
        base.startup ();
        _win = new TextReader ();
        add_window( _win );
    }
    protected override void activate () {
        _win.create_tab ();
        _win.present();
    }
    protected override void open ( File[] files, string hint ) {
        foreach ( var file in files ) {
            _win.create_tab ( file );
        }
        _win.present();
    }
    public static int main ( string[] args ) {
        Gtk.init ( ref args );
        var app = new App ();
        return app.run ( args );
    }
}

なかなか面白いことをみつけた。

public class ClassName : Window {
    public ClassName () {
        Object ( title: "Hoge", default_width: 500, default_height: 400 );

Python

class ClassName (Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self, title="Hoge", default_width=500, default_height=400):

Vala もこんな感じでプロパティセットが可能なんだね。
base ではなく Object なのか、何か凄く変です。
Python のほうが変という人のほうが多いという事実は置いておいて。

それより関数のパラメータをオブジェクトか NULL かで振り分けたい場合。
C ならポインタなのでそのまんま NULL かどうかを見分けできる、Python も同様。
型指定が厳密な C# の場合はオーバーロードを利用すると思います。

Vala は C に変換されるからそのままと思いきや C# と同じく型指定に厳密。
以下のようにすると見事にコンパイルエラーになってしまう。

public void create_tab ( File file ) {
    if ( file == null ) {
//etc...

// Compile Error
_win.create_tab (null);

最初は何故か全然解らず困ったけど多分 C# に合わせたということだろう。
しかし Vala はオーバーロードができないぞ、何か別の手段があるはずだ。
valadoc.org の cancellable 引数等の説明と同じように書いてみたらイケた。

public void create_tab ( File? file = null ) {
    if ( file == null ) {
//etc...

// OK
_win.create_tab ();

コレも凄く変です。
Python のほうが(以下略

後 GtkScrolledWindow は引数が必要だとか PyGI とは微妙に違うのね。
なんだかんだで勉強になった、実践的なコードを書くと色々気が付くものだ。
とにかく筆者は var と new と中括弧とセミコロンが面倒臭いです。

g_type_init

Vala の覚書ページを書き換えようとテスト。

init_error

なんだそりゃwww
警告が出るだけでビルドは問題なく行われるようだけど。

調べてみると GLib 2.36 からマジで g_type_init を廃止したようだ。
Fedora 19 の GLib はたしかに 2.36 だった。
Python ばかり使っているとこういうの気が付かないよね。

GIO入門 – ふとしの日記

#include <gio/gio.h>

/* gcc no_init.c `pkg-config --cflags --libs gio-2.0` */

gint
main (gint argc, gchar* argv[])
{
	GFile *gf;
	/* GLib >= 2.36 
	g_type_init ();*/
	gf = g_file_new_for_path ("sample.txt");
	return 0;
}

g_type_init は本当に不要になっているよふとしさん。
しかし Vala の場合はジェネレータが勝手に書き足すのだが…

[vala] codegen: Do not call g_type_init when targeting GLib >= 2.36

対策版は出ているのか、って Oct 2012 だから一年前かよ。
Fedora 19 パッケージの 0.20.1 は April 2013 のはずなのだが。

Lubuntu 13.10 の GLib は 2.38。
valac のパッケージを Synaptic で調べると同じ 0.20.1 か。
インストールしてみたけど、やはり症状は同じであった。

本家からソースを落として自力 make するか修正を待つか、うーん。
Vala のバージョンと GLib, GTK+ のバージョンは関係ってあるのか?
そのあたりがよく解らないから自力ビルドは避けたいのだが。

ビルドは可能なんだからこのままでもいいか、一番安全だし。

しかし `g_type_init vala` で検索しても日本語がまったく出てこない。
つまり日本人で Vala を使っている人はいないってことなのかも…

Python ** Asterisk

PyGI のオブジェクトは作成時の引数にて

#!/usr/bin/env python3

from gi.repository import Gtk

win = Gtk.Window(title="Test", default_width=500)
win.connect("delete-event", Gtk.main_quit)
win.show()
Gtk.main()

というように property を引数で指定できる。
今まであまり気にしていなかったけど、つまりこういうことだと気が付いた。

#!/usr/bin/env python3

from gi.repository import Gtk

class Win (Gtk.Window):
    def __init__(self, **args):
        #Gtk.Window.__init__(self, **args)
        Gtk.Window.__init__(self)
        self.connect("delete-event", Gtk.main_quit)
        # Own Property set
        for key, value in args.items():
            self.set_property(key, value)
        self.show()

Win(title="Test", default_width=500)
Gtk.main()

アスタリスク二つだと未定義キーワードの引数を受け取れるのは有名かと。
未定義キーワードは文字列になり辞書として扱える。
こういう本当に有用な形で実装されると「うわー便利!」と実感できますね。
今まで使い道が解らなかっただけだったりするけど。

こんなに便利なら IronPython でも同様にしてやろうと思ったけど…

import wpf
from System.Windows import *

win = Window(Title="Titlebar", Width=300, Height=100)
app = Application()
app.run(win)

既に実装されていた。
IronPython の開発者恐るべし。

g_idle_add

Gedit for Windows part3 | PaePoi
を少し改造しようと調べている時に g_idle_add という GLib の関数を知った。
筆者は何も知らないな、もっと勉強しなければいけないようだ。

何も処理を行っていないアイドル時にシグナルを送りつける。
ハンドラにて True を戻すとループ継続、False を戻すとループが止る。
g_timeout_add と同様みたい、ちとテスト。

#!/usr/bin/env python3

from gi.repository import Gtk, GLib

class IdleTest(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self)
        self.connect("delete-event", Gtk.main_quit)
        self.label = Gtk.Label("0")
        button = Gtk.Button.new_with_label("Time consuming process")
        button.connect("clicked", self.on_clicked)
        vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
        vbox.pack_start(self.label, False, False, 0)
        vbox.pack_start(button, False, False, 0)
        self.add(vbox)
        self.show_all()
        # var
        self.count = 0
        # Idle
        GLib.idle_add(self.on_idle)

    def on_idle(self):
        if self.count == 1000000:
            return False
        self.count += 1
        self.label.set_text("{0}".format(self.count))
        return True

    def on_clicked(self, widget, data=None):
        s = ""
        for i in range(10000000):
            s += "homura"

IdleTest()
Gtk.main()

g_idle_add1

ボタンを押して何か処理を行っている最中は見事にシグナルは停止する。
なるほど、同一プロセスで何もしていない時だけシグナルが発生するのか。

#!/usr/bin/env python3

from gi.repository import Gtk, GLib

class IdleTest(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self)
        self.connect("delete-event", Gtk.main_quit)
        self.label1 = Gtk.Label("0")
        self.label2 = Gtk.Label("0")
        vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
        vbox.pack_start(self.label1, False, False, 0)
        vbox.pack_start(self.label2, False, False, 0)
        self.add(vbox)
        self.show_all()
        # var
        self.count1 = 0
        self.count2 = 0
        # Idle
        GLib.idle_add(self.on_idle)

    def on_idle(self):
        if self.count1 == 100000:
            return False
        if self.count1 == 50000:
            GLib.idle_add(self.on_idle2)
        self.count1 += 1
        self.label1.set_text("{0}".format(self.count1))
        return True

    def on_idle2(self):
        if self.count2 == 100000:
            return False
        self.count2 += 1
        self.label2.set_text("{0}".format(self.count2))
        return True

IdleTest()
Gtk.main()

2 つ作成しても別スレッドとして動作するみたいですね。
上記のような使い方は CPU 負荷が凄いのでヤメたほうがいいと一応。

これがどういう時に便利かというと簡易スレッド分離。
ランチャを作成し、起動したことを書き出す。
そして終了コードを調べて表示したい等とする。

#!/usr/bin/env python3

from gi.repository import Gtk
import subprocess

class IdleTest(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self)
        self.connect("delete-event", Gtk.main_quit)
        view = Gtk.TextView()
        view.set_size_request(200, 100)
        self.buf = view.get_buffer()
        button = Gtk.Button.new_with_label("gnome-calculator")
        button.connect("clicked", self.on_clicked)
        vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
        vbox.pack_start(view, True, True, 0)
        vbox.pack_end(button, False, False, 0)
        self.add(vbox)
        self.show_all()

    def on_clicked(self, widget, data=None):
        self.buf.set_text("Do gnome-calculator\n\n")
        #
        it = self.buf.get_end_iter()
        retcode = subprocess.call(["gnome-calculator"])
        if retcode == 0:
            self.buf.insert(it, "Sucsess")
        else:
            self.buf.insert(it, "Error: {0}".format(retcode))

IdleTest()
Gtk.main()

動きそうな気がするけど実際は on_clicked を抜けるまで何も書き出されない。
なので on_clicked にて g_idle_add を入れてスレッドを別にする。

#!/usr/bin/env python3

from gi.repository import Gtk, GLib
import subprocess

class IdleTest(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self)
        self.connect("delete-event", Gtk.main_quit)
        view = Gtk.TextView()
        view.set_size_request(200, 100)
        self.buf = view.get_buffer()
        button = Gtk.Button.new_with_label("gnome-calculator")
        button.connect("clicked", self.on_clicked)
        vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
        vbox.pack_start(view, True, True, 0)
        vbox.pack_end(button, False, False, 0)
        self.add(vbox)
        self.show_all()

    def on_clicked(self, widget, data=None):
        self.buf.set_text("Do gnome-calculator\n\n")
        GLib.idle_add(self.on_idle)

    def on_idle(self):
        it = self.buf.get_end_iter()
        retcode = subprocess.call(["gnome-calculator"])
        if retcode == 0:
            self.buf.insert(it, "Sucsess")
        else:
            self.buf.insert(it, "Error: {0}".format(retcode))
        return False

IdleTest()
Gtk.main()

g_idle_add2

これなら on_clicked は抜けランチャの終了待機も問題なく行われる。
つまりいつ終るか解らない処理を待つ必要がある場合に利用できる。
こんな感じで簡易なスレッドが必要な場合はもうコレで充分ですよね。