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 を使っている人はいないってことなのかも…

History function

GNOME desktop をデフォルトで利用していて一番困ること。
イチイチ履歴を押し付けてくることに尽きる。

同一ファイルを幾度も編集する業界の輩には重宝するのだろうと考えるけど。
マルチな使い方をしている大半の御仁はマジでウンザリ。
万人向けなんて不可能、流行なんて時間単位で流動する。

Nautilus の「最近使ったファイル」とか
Gedit 等の編集アプリで履歴を無意味に表示する機能等々…
etc…

PHP, Python, Vala, C 等々を常用する筆者なんて一回も利用したことが無い。
いや、一応幾度かはあるんだけど別の手段があるし。

ならば、とアプリ毎に保存するか破棄するか選択できるのが理想。
と思い付くのが普通、で、ソレをアプリ開発者に選ばせたのが Windows で。
そんな輩はつまり…としたのが GNOME3 なのよね。

[設定]->[プライバシー]->[使用と履歴]

を off にするだけで GNOME アプリ全部の履歴機能を無効にできる。
アプリケーション側で保持する手段のアプリの場合はお手上げですが。

少なくとも Nautilus の「最近使ったファイル」はこれで表示されなくなる。
ウザいにもほどがあるコモンダイアログの履歴も出なくなる。
ソレだけで嬉しいよ。
GNOME デフォルトアプリ全部で履歴が参照できなくなるけど私的にコッチのほうがイイや!
人間って一番利用するものに合わせて進化するのよね、生食文化の日本人とか。

GNOME はこういう面をアピールしたほうがいいと思うのだけど。
現状では傍目でバージョンアップ毎に低機能になっているようにしか見えない。

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 の開発者恐るべし。