カレンダーを使う

Gtk+ではカレンダーウィジェットというのが用意されているので、今回はこれを使ってみる。

カレンダー上の日付を選択すると、その日付をテキストエリアに表示するという簡単なアプリケーションを作ってみた。

(ソースは以下の場所に置いてあります)
http://github.com/egawata/hatena_blog/tree/master/calendar/

 39     //  GtkCalendar オブジェクトの生成
 40     calendar = gtk_calendar_new();
 41 
 42     //  日付表示用エリア
 43     text = gtk_entry_new();

カレンダーオブジェクトを作成する。ついでに、選択された日付を表示するためのテキストエリアも用意しておく。

 47     g_signal_connect(G_OBJECT(calendar), "day-selected",
 48                      G_CALLBACK(disp_day), (gpointer)text);

GtkCalendar はいくつかのシグナルを扱うことができる が、カレンダー上の日付がクリックされたら何か処理を行いたい、ということはよくあるだろう。そのときは "day-selected" というシグナルが発生する。
上記では、"day-selected" シグナルが発生したときに、disp_day 関数(10-22行)をコールするように指示している。


disp_day 関数は、カレンダー上で選択された日付を、年月日の文字列に直してテキストエリアに表示する処理を行っている。

 10 static void disp_day(GtkCalendar *calendar, GtkEntry *text)
 11 {
 12     guint year, month, day;
 13     gchar ymd[100];
 14 
 15     //  カレンダー上で選択された年月日を得る
 16     gtk_calendar_get_date(calendar, &year, &month, &day);
 17     month++;    //  月は 0-11 の値で取得されるので +1
 18 
 19     snprintf(ymd, 100, "%d%d%d日", year, month, day);
 20 
 21     gtk_entry_set_text(text, ymd);
 22 }

gtk_calendar_get_date() を使って、カレンダー上で選択された年月日を取得することができる。
ここで注意したいのは、GtkCalendar は月を 0-11 の範囲で扱うということ。したがって上記のように月を取得すると、実際の月より1少ない値が返ってくる(10月なら 9)。逆にカレンダーの表示月を指定するような場面では、1少ない値を月に指定する必要がある。今回のサンプルでは、年月日を取得したあと、月の値をプラス1している。

 57     g_signal_emit_by_name(calendar, "day-selected", text);

アプリケーション起動直後、カレンダー上では今日の日付が選択された状態になっている。しかしテキストエリアには日付が表示されていない。前述の disp_day 関数が起動後一度も実行されていないからだ。
これではカッコ悪いので、g_signal_emit_by_name() を使って "day-selected" シグナルを意図的に発生させ、今日の日付がテキストエリアに表示された状態にしておく。


単純な例で実用性に乏しいかもしれないが、コールバック関数を、例えば指定された日付の予定を表示するような処理に置き換えれば、スケジュール管理アプリみたいになって良いかも。

メニューバーを使用する

なんか久しぶりすぎるが気にしない。w
今回はメニューの追加について。


メニューの構造は「ファイル」→「新規作成」「開く」など、階層構造になっていることが多い。1つの階層の構造は以下のようなクラスを使った構造で表される。

  GtkMenuShell (メニューバー、サブメニュー)
       GtkMenuItem (メニュー上のアイテム)
       GtkMenuItem
       ....

GtkMenuItem というのは、メニューに表示されるアイテム一つ一つのことで、例えば「ファイル」「新規作成」「開く」など、メニュー上で選択可能なものはすべて GtkMenuItem。

(GtkMenuItem)

GtkMenuShell は、これら GtkMenuItem を入れるためのコンテナ。たとえば画面上部のメニューバーが GtkMenuShell であれば、その上に表示される「ファイル」「編集」などの文字が GtkMenuItem であって、GtkMenuShell は、これらの GtkMenuItem をまとめる役割を行う。

(GtkMenuShell)


また、GtkMenuItem には、サブメニューとして GtkMenuShell を紐付けることができる。これにより、メニューの階層構造を実現できる。

なお、GtkMenuShell というのは抽象クラスであり、実際には以下の2つが使用される。

  • GtkMenuBar : 常時表示されている横長のメニュー
  • GtkMenu : 必要なときにのみ表示されるポップアップメニュー

実際には、最上位のメニューに GtkMenuBar を使用し、それ以下の階層、例えば「ファイル」メニューをクリックしたときに表示されるサブメニューなどに GtkMenu を使用するとよい。


さて実際の例。今回は、メニューにいくつかのアイテムを追加し、それらが選択されたときに、ウィンドウ内の文字列を変化されるサンプルを作ってみた。

(ソースは以下の場所に上げてあります)
http://github.com/egawata/hatena_blog/tree/master/menu

    //  (1)MenuBar 'menubar' を作成する
    menubar = gtk_menu_bar_new();

    //  (1)MenuBar 'menubar' を BOX に追加する
    gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);    

まず一番上の階層であるメニューバーから。メニューバーの生成には gtk_menu_bar_new() を使用する。生成時点では、内容は空。

    //  (2)MenuItem 'lunch' を作成する
    lunch = gtk_menu_item_new_with_label("メニュー");
    …
    
    //  (2)MenuItem 'lunch' を MenuBar 'menubar' に追加する
    gtk_menu_shell_append(GTK_MENU_SHELL(menubar), lunch);

メニューバーに入れるアイテムを作成し、メニューバーに追加する。今回は1つだけ、「メニュー」というアイテムを追加する。

    //  (3)Menu 'lunchmenu' を作成する
    lunchmenu = gtk_menu_new();
    ...
    
    //  (3)Menu 'lunchmenu' を MenuItem 'lunch' のサブメニューとする
    gtk_menu_item_set_submenu(GTK_MENU_ITEM(lunch), lunchmenu);    

先ほど追加したアイテム「メニュー」に、サブメニュー lunchmenu を紐付ける。これにより、メニューバー上の「メニュー」を押すとサブメニューが現れるようになる。ただし現時点では内容は空。

    //  (4a)MenuItem 'menu*' を作成する
    menu1 = gtk_menu_item_new_with_label("カレー");
    menu2 = gtk_menu_item_new_with_label("おにぎり");
    menu3 = gtk_menu_item_new_with_label("俺の塩");

    //  (4b)MenuItem 'menu*' を Menu 'lunchmenu' に追加する
    gtk_menu_shell_append(GTK_MENU_SHELL(lunchmenu), menu1);
    gtk_menu_shell_append(GTK_MENU_SHELL(lunchmenu), menu2);
    gtk_menu_shell_append(GTK_MENU_SHELL(lunchmenu), menu3);

このサブメニューに入れるアイテムを3つ作成し、それをサブメニューに追加していく。

これでメニューが一通り表示されるようにはなったのだが、今の状態では、サブメニュー内の「カレー」などのアイテムを選択しても何も起こらない。メニューが選択されたときに何かをさせたい場合は、コールバック関数を登録する。

    //  (5)MenuItem 'menu*' が選択されたときに呼ばれるコールバック関数を
    //     menu_activated() に設定する。
    //     また、このときの引数として label も渡すようにする
    g_signal_connect(G_OBJECT(menu1), "activate",
                     G_CALLBACK(menu_activated), (gpointer)label);
    g_signal_connect(G_OBJECT(menu2), "activate",
                     G_CALLBACK(menu_activated), (gpointer)label);
    g_signal_connect(G_OBJECT(menu3), "activate",
                     G_CALLBACK(menu_activated), (gpointer)label);

各メニューが選択されたら、関数 menu_activated が呼ばれるようにする。menu_activated() は、ラベル label に書かれた文字列を変更している。

static void menu_activated(GtkMenuItem *menuitem, GtkWidget *label)
{
    const gchar *name = gtk_menu_item_get_label(menuitem);
    char message[100];
    snprintf(message, 100, "%sの注文を承りました。", name);

    gtk_label_set_text(GTK_LABEL(label), message);
}


(実行結果)

  • 初期状態


  • 「カレー」が選択された

実はサブメニューが表示されているときのキャプチャも取りたかったんだけど、サブメニューが表示されてる間はキャプチャできないのねorz

[GTK+] カスタムウィジェットの作り方

ということで自前のウィジェット作成してみた。「警告つきパスワード入力欄ウィジェット」とでも呼べばいいか。

ウェブ上にある会員制サイトに登録をしようとすると、たいていパスワードの登録を求められる。サイトによっては、パスワード欄に入力されたパスワードが適切なものかを判断し、脆弱なパスワードである間は警告を出すようになっている。そんな雰囲気のあるカスタムウィジェットを作ってみた。



キー入力があるたびにパスワードの妥当性をチェックし(今回は文字数4文字〜10文字という条件のみつけた)、もし妥当なパスワードであれば背景が緑に、妥当でなければ赤になる。上は初期状態で、パスワードが0文字なので妥当でない=背景が赤になっている。また下はパスワードを6文字入力した状態で、背景が緑になっている。


で、そんなウィジェットの作り方・・・なんてのを書いてみたかったのだが時間が…orz
とりあえず登録したばかりの git にあげといたぜ。

http://github.com/egawata/gtksample/tree/master/mypasswd/


解説はまた後日…

[GTK+] 描画する色を指定する

線を描画するの続き。


前回のプログラムでは、線の色として、そのウィジェットの前景色として指定されていたものをそのまま使っていた。しかし、描画する色を明示的に指定したい場面も多々ある。そこで今回は描画色を指定する方法について。

点を打つときに使う関数 gdk_draw_point() の仕様は以下のとおりだった。

void                gdk_draw_point        (GdkDrawable *drawable,
                                                         GdkGC *gc,
                                                         gint x,
                                                         gint y);

第2引数の gc はグラフィックコンテキストというもので、描画のスタイルについていろいろな指定ができるオブジェクト。前回は、GtkDrawingArea ウィジェットに設定されていた値を取り出してきて指定していたが、もちろん新たにGdkGCオブジェクトを生成して、そこで値を指定して gdk_draw_point に渡すこともできる。

前回から変更した関数のみ示す。

void draw_point(
    GtkWidget *area,
    gint x,
    gint y,
    guint16 red,
    guint16 green,
    guint16 blue ) 
{
    GdkGC  *gc;
    GdkColor color;

    color.red   = red;
    color.green = green;
    color.blue  = blue;

    gc = gdk_gc_new(area->window);
    gdk_gc_set_rgb_fg_color(gc, &color);

    gdk_draw_point(area->window,
                   gc,
                   x, y);

    g_object_unref(gc);
}

static gboolean
expose_event(GtkWidget *area, GdkEventExpose *event, gpointer data)
{
    gint x;

    for (x = 0; x < 200; x++)
        draw_point(area, x, x, x * 300, 0, 65535);

    return TRUE;
}
    GdkGC  *gc;
    ....
    gc = gdk_gc_new(area->window);

点を打つための関数 gdk_draw_point() の第2引数に渡す GdkGC オブジェクトを生成し、ポインタを gc に格納する。なお、順番が前後するが、生成した GdkGC オブジェクトは使用後に開放する必要があるため、関数の最後で

    g_object_unref(gc);

としている。

    GdkColor color;

    color.red   = red;
    color.green = green;
    color.blue  = blue;

    ....
    gdk_gc_set_rgb_fg_color(gc, &color);

GdkColor 構造体の各要素にRGB値を設定している。GdkColor 構造体は以下のような構造となっている。

typedef struct {
  guint32 pixel;
  guint16 red;
  guint16 green;
  guint16 blue;
} GdkColor;

red, green, blue に各RGB値の配合度合いを 0 〜 65535 の整数値で指定する。

そして、gc に対して前景色の設定を行うため、gdk_gc_set_rgb_fg_color() という関数を呼び出す。このとき、今生成した GdkColor 構造体のポインタを引数として渡す。

これで、独自のグラフィックコンテキストの準備が出来た。あとは gdk_draw_point() にこの gc を渡せば、指定した色で点を打つことができる。



ソース全体

#include <gtk/gtk.h>

static void destroy(GtkWidget *window, gpointer data)
{
    gtk_main_quit();
}

void draw_point(
    GtkWidget *area, 
    gint x, 
    gint y, 
    guint16 red, 
    guint16 green, 
    guint16 blue) 
{
    GdkGC  *gc;
    GdkColor color;

    color.red   = red;
    color.green = green;
    color.blue  = blue;

    gc = gdk_gc_new(area->window);
    gdk_gc_set_rgb_fg_color(gc, &color);

    gdk_draw_point(area->window, 
                   gc,
                   x, y);

    g_object_unref(gc);
}

static gboolean 
expose_event(GtkWidget *area, GdkEventExpose *event, gpointer data)
{
    gint x;
    
    for (x = 0; x < 200; x++)
        draw_point(area, x, x, x * 300, 0, 65535);

    return TRUE;
}

int main(int argc, char *argv[]) 
{
    GtkWidget *window, *area;
    
    gtk_init(&argc, &argv);

    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title(GTK_WINDOW(window), "Drawing Line");
    gtk_widget_set_size_request(window, 200, 200);
    g_signal_connect(G_OBJECT(window), "destroy", 
                     G_CALLBACK(destroy), NULL);

    area = gtk_drawing_area_new();
    g_signal_connect(G_OBJECT(area), "expose_event", 
                     G_CALLBACK(expose_event), NULL);

    gtk_container_add(GTK_CONTAINER(window), area);
    gtk_widget_show_all(window);
    gtk_main();

    return 0;
}

パフォーマンスを最大化するために必要なこと

Time Power を引き続き読んでいる。

最大のパフォーマンスを得るための12個の実証済みの原則 (Twelve Proven Principles for Peak Performance) という項目があった。とりあえず気になったものをば。

時間を上手く管理するため、自分を「工場」に見立てる

工場の生産活動には3つのフェーズがある。

  • 資源投入(input)
  • 生産(production activities)
  • 成果物(output)

で、自分の活動を工場に見立てたあと、どこに意識の重点をおくかが運命の分かれ道となる。普通の人は、一番はじめの input に注目し、何を自分の中に取り入れたら良いかということばかり気にしてしまう。どんな知識や経験を身につけるか、とか。
しかし効率的に行動できる人は、一番最後の output に意識を集中する。つまり、何かを行うことで自分が何を生み出せるか、何を成し遂げられるかという点を重視し、それに向かって行動する。
逆に、output に意識を集中させれば、行動を効率化できる。

自分に与えられたすべての時間は「今」、この瞬間だけである。

今、この1分、この1時間、この1日…を上手く活用することを考える。
昔読んだ「アルケミスト」という小説に、「過去のことも未来のことも忘れてしまいなさい。今という時間を大切にしなさい」といった事が書いてあった(文章正確でなくてスマソ)。未来は自分の意思で変えられるってよく言われるけど、今という時間はその未来よりはるかに自由に変えられる。自分の思い通りにできる。だったら、未来のことをあれこれ考えるより、今を精一杯生きることが大切なんだと思う。
まぁ計画性は必要だけど。

[GTK+] 線を描画する (解説編)

さて昨日の線描画プログラムの解説をば。長いよ。w

 28     GtkWidget *window, *area;
...
 38     area = gtk_drawing_area_new();

GtkDrawingArea ウィジェットを生成する。
GtkDrawingArea というのは GtkWidget の派生オブジェクトなのだが、他の派生オブジェクト(ボタンやラベルなど)と違い、特に用途が決められているわけではない。このことは、 GtkDrawingArea が提供する関数がただ一つ、このオブジェクトを生成するための gtk_drawing_area_new() だけだということからも伺い知れる。リファレンスマニュアルの GtkDrawingArea に対する説明には「カスタムユーザインタフェースを生成するために使われる」とある。
今回はこのGtkDrawingArea ウィジェットをウィンドウの上に貼り付けて、その上に描画することにしたい。

 39     g_signal_connect(G_OBJECT(area), "expose_event",
 40                      G_CALLBACK(expose_event), NULL);

つい先ほど生成した GtkDrawingArea ウィジェット(=area)が expose_event シグナルを受け取ったときに、コールバック関数 expose_event (15-24行目) をコールするようにする。

expose_event シグナルとは何ぞや、ということになるが、その前にまず GTK+の描画モデルについて理解する必要があると思うので、はじめにそちらについて説明したい。


例えばウィンドウのサイズを変更したり、背面にあったウィンドウを最前面に持ってきたりしたときのことを考えてみよう。ウィンドウサイズを拡大した場合、その拡げられた部分にはこれまで何も描画されていないため、新たに描画を行う必要がある。また背面にあったウィンドウを前面に持ってくるケースでは、背面に隠れていた部分を新たに描画する必要がある。
これら新たに描画される部分を含めて、ウィンドウシステム側であらかじめ描画内容をメモリに格納しておき、必要なときに必要な箇所を自動的に再描画してくれたりしたら、プログラマ的には楽だろう。
が、実際にはそうなっていない。おそらく「どう再描画するか」については、アプリケーション制作者に委ねられるべきという考え方からだと思う。そこでウィンドウシステム側は、自動的に再描画はせずに、再描画が必要なウィジェットに対して expose_event というシグナルを送信し、再描画の必要があることをアプリケーション側に知らせるようになっている。

繰り返しになるが、上記の g_signal_connect では、ウィジェット area が expose_event シグナルを受け取ったときに実行される関数を登録している。

 42     gtk_container_add(GTK_CONTAINER(window), area);

そして、その area を、トップレベルウィンドウ(window)に追加している。


次に、このコールバック関数 expose_event の内容をば。

 15 static gboolean
 16 expose_event(GtkWidget *area, 
                                  GdkEventExpose *event, 
                                  gpointer data)
 17 {
 18     gint x;
 19 
 20     for (x = 0; x < 200; x++)
 21         draw_point(area, x, x);
 22 
 23     return TRUE;
 24 }

expose_event シグナルを受け取ったときのコールバック関数は以下の形式となる。(GTK+ Reference Manual の GtkWidget の Signals 参照)

gboolean user_function (GtkWidget *widget, 
                                               GdkEventExpose *event, 
                                               gpointer user_data)

で、ここでやっているのは、

  • (0, 0) から (200, 200) に向かって斜めの線を引く

これだけである。
ちなみに draw_point というのは以下のとおりで、

  8 void draw_point(GtkWidget *area, gint x, gint y)
  9 {
 10     gdk_draw_point(area->window,
 11                    area->style->fg_gc[ GTK_WIDGET_STATE(area) ],
 12                    x, y);
 13 }

gdk_draw_point という関数を使って、指定された座標に点を打っている。
gdk_draw_point の関数仕様は以下のようになっている。

void gdk_draw_point(GdkDrawable *drawable, 
                                        GdkGC *gc, 
                                        gint x, 
                                        gint y)


注:以下の説明文で GdkWindow と GtkWindow という非常に良く似ているが全く別物の名前が出てきますw


第1引数で指定するのは描画対象。ただし、ウィジェットそのものを指定するのではなく、GdkDrawable * 型のものを指定する。描画対象を GtkWidget 型で持っている場合は、その中に window というプロパティがある(型は GdkWindow*)ので、それを使って

widget->window

のように指定すればよい。

ちなみに window という言葉の意味がややこしいのだが、ここで言う window は GdkWindow *型であり、単に「画面上に存在する矩形の領域」を意味する。GtkWindow、つまり複数ウィジェットをまとめる最上位のウィジェットという意味のウィンドウとは別物だ。(一文字違いだから余計ややこしいw)

window はGtkWidget のプロパティの一つとして含まれているので、GtkWidget を継承したオブジェクト、たとえば GtkButton や GtkEntry などもすべてこの window を持っていることになる。
で、GdkWindow は GdkDrawable を継承したオブジェクトなので、gdk_draw_point の第1引数の描画対象として指定することが可能だ。


次に第2引数の描画スタイルの指定部分について。

 11                    area->style->fg_gc[ GTK_WIDGET_STATE(area) ],

内側の GTK_WIDGET_STATE(area) から見ていく。
GTK_WIDGET_STATE() はマクロで、以下のように定義されている。

#define GTK_WIDGET_STATE(wid)		  (GTK_WIDGET (wid)->state)

state というのは何かというと、特定ウィジェットの「状態」を表す値であって、GtkStateType で定義された5つの値のいずれかをとる。

typedef enum
{
  GTK_STATE_NORMAL,
  GTK_STATE_ACTIVE,
  GTK_STATE_PRELIGHT,
  GTK_STATE_SELECTED,
  GTK_STATE_INSENSITIVE
} GtkStateType;

これらの意味するところは Reference Manual を参照していただくとしてここでは割愛。ただ、これが GTK_WIDGET(area)->state の値であり、配列 fg_gc[] の添字である。

 11                    area->style->fg_gc[ GTK_WIDGET_STATE(area) ],

さて今度は area->style の意味を。
GtkWidget のプロパティの中に style という GtkStyle * 型の値がある。この GtkStyle 構造体は、ウィジェットの見せ方に関するさまざまな情報を格納している(色など)。

さらに GtkStyle の中を見ていく。

typedef struct {
  ...(中略)
  GdkGC *fg_gc[5];
  ...(中略)
} GtkStyle;

かなり前後を略してあるが、この中に fg_gc というGdkGC * 型の配列が定義されている。

GdkGC というのは GDK で使用される Graphic Context(GC)。描画を行うさいのさまざまな情報、たとえば色や線の太さなどを保持している。
で、fg_gc は、GdkGC へのポインタを5つ保持している。5つというのは、先に出てきた GtkStateType の数と同じであり、つまり、各 State における前景描画時の情報をそれぞれ保持しているということになる。


かなり話がややこしくなってきたが、結局

 11                    area->style->fg_gc[ GTK_WIDGET_STATE(area) ],

これは、

ウィジェットに定義されている、現在のウィジェットの状況における描画コンテクスト

を得ることになる。そして、

 10     gdk_draw_point(area->window,
 11                    area->style->fg_gc[ GTK_WIDGET_STATE(area) ],
 12                    x, y);

これは「ウィジェット area の window 領域に対して、現在の状況における描画コンテクストを使って、座標(x, y)に点を打つ」ということになる。


ふぅ、ずいぶん長くなってしまったww

(参考)