C/C++」タグアーカイブ

ApplicationMenu

プログラミング関係は次回!
と書いてから一ヶ月も放置してしまった…

とはいえ何もやっていなかったわけではなく
GNOME 3.4 リリースノート
の「アプリケーションメニュー」を PyGI でやろうと四苦八苦していた。

なんか Linux も Windows もメニューバーを無くそうと試行錯誤しているね。
そりゃまあキーボードとマウスからタッチパネルに絶賛変革中だからだろうけど。
Windows がリボンを作った時はナンジャコリャだったけど先を見ていたと今なら解る。

C のサンプルコードは公式の以下にあるのだが
GtkApplication
GtkApplicationWindow
GActionMap

GtkActionEntry はシーケンス(つまりリスト)でよかったが
GActionEntry は上手く行かなかった。
こんな感じでイケると思ったけどシーケンスでは Gio.ActionEntry ではないと例外になる。

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

from gi.repository import Gtk, Gio

menu_xml = """
<interface>
    <menu id='app-menu'>
        <section>
            <item>
                <attribute name='label' translatable='yes'>_MessageBox</attribute>
                <attribute name='action'>app.messagebox</attribute>
                <attribute name='accel'>&lt;Primary&gt;m</attribute>
            </item>
            <item>
                <attribute name='label' translatable='yes'>_Quit</attribute>
                <attribute name='action'>app.quit</attribute>
                <attribute name='accel'>&lt;Primary&gt;q</attribute>
            </item>
        </section>
    </menu>
</interface>"""

class Win(Gtk.ApplicationWindow):
    def __init__(self, app):
        Gtk.Window.__init__(self)
        self.set_application(app)
        self.show_all()

def on_messagebox(action, parameter, user_data):
    dlg = Gtk.MessageDialog(
            None,
            Gtk.DialogFlags.MODAL,
            Gtk.MessageType.INFO,
            Gtk.ButtonsType.OK,
            "text")
    r = dlg.run()  
    dlg.destroy()
    return r

def on_quit(action, parameter, user_data):
    user_data.quit()

class App(Gtk.Application):
    def __init__(self):
        Gtk.Application.__init__(self,
            application_id="apps.test.appmenu",
            flags=Gio.ApplicationFlags.FLAGS_NONE)
        self.connect("startup", self.on_startup)
        self.connect("activate", self.on_activate)

    def on_startup(self, data=None):
        try:
            # GActionEntry [] a Gio.ActionEntry
            aes = [ ("messagebox", on_messagebox, None, None, None),
                    ("quit", on_quit, None, None, None) ]
            ###
            self.add_action_entries(aes, len(aes), self)
            #=> Expected Gio.ActionEntry, but got StructMeta
            ###
            builder = Gtk.Builder()
            builder.add_from_string(menu_xml)
            menubar = builder.get_object("app-menu")
            self.set_app_menu(menubar)
        except Exception, e:
            print e
            self.quit()

    def on_activate(self, data=None):
        w = Win(app)

if __name__ == "__main__":
    app = App()
    app.run(None)

全部 const 指定だしスタック上のデータでないと駄目なのかな?
それだとヒープ上にしか変数を作れない動的言語はお手上げになるんだけど。

海外を探しても PyGI のコードはほとんど見つからない。
現状ではみんな試行錯誤して様子見しているかブン投げているかだろうな。
はたしてこの動的バインディングは熟成して普及するのであろうか?

ええい、今回もとりあえず C 言語でコンパイルだけやってみるか。

Fedora は初期状態では gcc すら入っていない。
gcc, gtk3-devel, gtk3-devel-docs でとりあえず環境は揃う。
ついでに anjuta を導入、vala なんかもオマケで入る。
これらについては気が向いたら何か書く。

c_appmenu.tar.gz

うん、C ならやはり普通にアプリケーションメニューは使える。
それなら DLL にすれば、って動的言語な意味ネェ…
C が一部でも必要ならソースコードのみで配れる C で全部作ったほうが楽。
Python と DLL では最低 x86, x86_64 用の2つを配るはめになるわけで。

ということで当面は C 言語とお付き合いになりそうです。
難しくはないんだけど、ただただ面倒くさいのが難点。

Trigraph

先日書いたように半月前に Fedora マシンを英語キーボードに変えた。
VirtualBox 仮想マシンの Windows Vista, XP も英語配列に変更。
「英語キーボード Vista」と検索すればワラワラ見つかるレジストリ書き換えで。
XP は簡単、どちらも仮想マシン状態でも方法は同じで変更できるようだ。

IME も問題ない、Windows では Alt+~ で切り替えなのね。
ホストの GNOME3 複数ウインドウ切り替え機能より優先されるようだ。
てゆーか間違えるわ、あんまり使わないからいいけど。

ついでなのでキーボードネタでも少し。

有名かもしれないけど C 言語では Trigraph という三文字表記が使える。
Digraphs and trigraphs – Wikipedia, the free encyclopedia

??=include <stdio.h>

main() ??<
    printf("%s??/n", "はろーわーるど");
??>

スッゲエ読みにくい、なんだこりゃですが使えるんです。
ただ gcc では無効になっているので -trigraphs オプションが必要。

gcc -trigraphs test.c

日本語をリテラルに使っても何も問題無い。
ついでに試したら VC++ ならオプション無しで普通にビルドできる。
もしかして C# でも使えるかな?と思ったけどダメだった。

どうでもいいが wprintf ってどんな時に使うのだ?
内部が UTF-16 で local が CP932 で .NET 読み書きが UTF-8 ってアホか。
結局コンソールを使う場合はマルチバイトビルドしか選べないわけで…
それは本当にどうでもよくて。

問題があるようで代わりに Digraph というのが C99 で既定されたようだ。
表現できるものが減っているけどコレで全世界でも問題ないと判明したのだろう。

%:include <stdio.h>

main() <%
    printf("%s\n", "はろーわーるど");
%>

gcc ならオプション無しでイケる、Visual Studio 2008 ではダメだった。
Visual Studio 2010 ならイケるのかはシラネ。
そして C#, Python は関係無かった。

つーか何故こんなもんがあるかというと。
フランス語やドイツ語キーボードにはシャープや角括弧や中括弧が無いから。
キー配列 – Wikipedia

でも実は右 Alt キーを利用して打つことはできるようです。
京都産業大学 外国語学部 フランス語学科/Windowsでのフランス語入力
ドイツ語キーボードの配列

ということで C 言語以外は三文字表記なんか気にしなかったようだ。
いや UTF-8 が普及したってのが大きいんだろうけど…
よく考えたら C# なんて # が使えないと言語名さえ変えなくてはいけないもん。
ぶっちゃけ誰もこんな表記方法は使わないだろう、と思うんだが。

ついでに。
「Gedit キーカスタマイズ」とかのワードで検索している人がいるんだな…
それ Windows の文化だから。

Gedit で検索は Ctrl+F で次を検索は Ctrl+G ですよね。
これ、たとえば devhelp もまったく同じなんです。
一つ覚えてしまえば他でも同じキーが使える環境のほうがいいと思うのだが。
そういうところが気に入って GNOME を選んでいる人って少ないのかな…

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 言語はやっぱり面倒くさい。

g_utf8_collate_key_for_filename of ctypes

g_utf8_collate_key_for_filename
がいったいどういう変換をしているかもう少し。

ところで端末に長々と打ち込むのが面倒なので GEdit 外部ツールに以下を指定。
GTK+ を使うには pkg-config 指定が必要なのよね、キーは F7 にした。

/*
gedit tool script

#!/bin/sh
gcc $GEDIT_CURRENT_DOCUMENT_PATH `pkg-config --cflags --libs gtk+-2.0`
*/

#include <stdio.h>
#include <gtk/gtk.h>
#include <glib.h>

int main() {
    gchar c[4];
    int i;
    for (i=0; i<105; i++) {
        sprintf(c, "%d", i);
        gchar *s = g_utf8_collate_key_for_filename (c, -1);
        printf("%s\n", s);
        g_free(s);
    }
    return 0;
}

端末だと解り難いにでリダイレクトしてみる。

十進で桁が増える毎にコロンが一つ付加されていくようだ。
なんだかよく解らない変換だけどこれで自然順ソート比較は上手くいくようだ。
色々やってみたけど結局 Python から ctypes を使う。

14.14.1 ctypesチュートリアル

ココに全部書いているんだが、Linux はちびっと面倒なのね。

glibc = ctypes.cdll.LoadLibrary('libglib-2.0.so.0')
cmpstr = glibc.g_utf8_collate_key_for_filename("a")

とやっても int が戻ってくる、実際にはポインタだが Python にはポインタ型が無いので。
つまり restype をキッチリ指定しないと全部 int になるってことですね。
ということで。

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

import ctypes

glibc = ctypes.cdll.LoadLibrary('libglib-2.0.so.0')
cmpstr = glibc.g_utf8_collate_key_for_filename
cmpstr.restype = ctypes.c_char_p
cmpstr.argtypes = [ctypes.c_char_p, ctypes.c_int]

def sort_nicely(l):
    l.sort(lambda x, y : cmp(cmpstr(x, -1), cmpstr(y, -1)))

たまには lambda を使ってみようと思ったので。
ガベージコレクションなのだからコレでいいはずだけど…
とにかくこれでどうだ?

よし Nautilus とはドットファイルを除けば一致するようになった。
隠しファイルはリストアップに含めないようにする予定なのでどうでもいいけど。

でも Mandriva KDE 上で動かしたら何故か数値ソートしてくれなかった。
って ./ を付け忘れで以前のバージョンを起動しただけだった、GNOME と同様になる。
つか Dolphin のソートって以前の関数とまったく同じ結果じゃん!

これじゃ設定でどちらかに振り分けしてもらうしか両対応の方法が無さそう。