「りも」トップページへ
TIPS index へ

ポップアップメニューと画像表示

Sep.04.1999

GTK+ と GLADE の組合せでポップアップメニューを作る方法についての説明です。 さらにポップアップメニューで選択された結果を元にウィンドウ内に画像を 表示する手順を模索します。


・GLADE を扱うについてあれこれ

GLADE はインターフェースビルダーです。これでウィンドウの大きさや ボタンの位置と言ったアプリケーションデザインを行い、GTK+ を用いた 基本的なソースコードを吐き出してもらいます。プログラマーはその吐き出された コードに、各パーツの挙動をプログラミングすることによってアプリケーション を作成します。画面デザインやインターフェースの作成という案外冗長で 重たい作業を軽減するためのツールです。
作業手順を考えるとわかるのですが、アプリケーションを作っている最中に インターフェースを修正するといった作業は結構大変です。こればっかりは 他の環境に統合されていない限り難しいので、現在の GLADE の問題点と言った ところでしょうか。
ですから、なるべく GLADE が吐き出すソースは後で上書きしても良いように なるべくいじらないで新規にソースを起こし、そっちに処理を書くといった ことを心がける必要がありそうです。

取り敢えず、今回 目的となるインターフェースを作成します。
256x256Pixel サイズのメインウィンドウ一つに全面 Drawing Widget を 張り付けておきます。この Drawing Widget に画像を表示する予定です。 メインウィンドウと別にポップアップメニューを作成します (GTK+ Additional の方にあります)。 メニュー項目ひとつひとつにハンドラを指定しておいてください。

メインウィンドウとなるウィンドウに Destroy ハンドラを設定するのを 忘れないでください。Window Maneger によって Window が閉じられても アプリケーションは終了しませんから、この Destroy シグナルで Window が (ある意味強制的に)閉じられたことを検出する必要があるわけです。
ハンドラ側のコードは以下の通りになります。(callbacks.c)

void
on_window1_destroy (GtkObject  *object,
                    gpointer   user_data)
{
  gtk_main_quit();
}
毎回必要になる常套句です。

今回目的とするアプリケーションは

といった機能を有していますのでそれを実現するに必要なイベントとハンドラを 設定します。
具体的に「PopupMenuが表示されるきっかけのマウスボタンクリック」と 「ウィンドウ再描画が必要な場合、そのリクエスト」が必要になりますので exposure と button_click のイベントを拾います。
drawingarea1 にイベントを拾える様 イベントマスクの設定 イベントハンドラの設定を行います。
Main window でなく Drawing Area にイベントを設定しているのは、 メニューバーとかが存在していた場合その領域では反応しないように したかったからです。

以上でインターフェースの作成は終了。セーブし、ソースを生成します。 以上の作業をセーブしたファイルです→ popup.glade

GLADE にソースを吐き出させると src/ ディレクトリ下に以下のファイルが 生成されていることがわかるとおもいます。

callbacks.[h|c]
interface.[h|c]
support.[h|c]
main.c
この中でプログラマが手を加えることを許されているのは main.c と callbacks.c の様です。その他、特に interface.[c|h] は GLADE が ソースを生成する度に書き直されますので、ここにプログラマーが コードを書くのは避けてください。
main.c は最初の一回だけ生成されて以後重ね書きされる事はありません。 interface.c で生成された GtkWidget を受け取れる場面は主にここですので うまいこと利用しましょう。
callbacks.[h|c] にはイベントハンドラが記述されます。プログラマは このソースの中に、ハンドラのインプリメンテーションを記述します。 このソースはユーザーコードを尊重するため、上書きはされませんが 追記されていきます。GLADE でハンドラ周りをいじるとそのハンドラが ソースの後ろに追加されます。ですから新規作成でなく修正した場合 同じ関数が追加されるので注意してください。
main や callbacks に冗長なコードを書き込むのではなく、そこで処理したい ことを別ソースとして用意し、それをコールする形にしなるべく短いコード 追加にするのが理想かと思います。 もちろん、ソースコードを追加したらそれを有効にするために src/ 内の Makefile.am を更新して configure を行う必要がありますのでお忘れなく。

・ポップアップメニューの作成

GLADE で PopupMenu を作成しても、生成のためのコードが作られるだけで そのままではメニューは現れません。ポップアップメニューが現れるのは ボタンクリックだけとは限りませんから、どうやったらメニューが現れるかを インプリメントする必要があります。
今回はウィンドウ上でマウスの Button3 をクリックしたらポップアップメニューが 表示されるといった形で作成することにします。

ポップアップメニューの名称を "popupmenu" として GLADE でソース生成を すると、interface.c 内に create_popupmenu なる関数が生成されます。 これをコールすると Menu Widget が生成されますが、表示はされません。 その後に gtk_menu_popup を実行すると、目的とするポップアップメニューが 現れ、機能します。メニュー内のハンドラが正しく記述されていれば、選択項目に 応じてハンドラがコールされますので目的は達成されます。

ボタンクリックからポップアップメニューまでの具体的なコードは以下の様に なります。(大部分を Gtkリファレンスから引用)

gboolean
on_drawingarea1_button_press_event     (GtkWidget       *widget,
                                        GdkEventButton  *event,
                                        gpointer         user_data)
{
  GtkMenu *menu;
  GdkEventButton *event_button;

    menu = GTK_MENU (create_popupmenu());

    g_return_val_if_fail (widget != NULL, FALSE);
    g_return_val_if_fail (event != NULL, FALSE);

    /* The "widget" is the menu that was supplied when 
     * gtk_signal_connect_object was called.
     */

    if (event->type == GDK_BUTTON_PRESS)
      {
        event_button = (GdkEventButton *) event;
        if (event_button->button == 3)
          {
            gtk_menu_popup (menu, NULL, NULL, NULL, NULL, 
                            event_button->button, event_button->time);
            return TRUE;
          }
      }

  return FALSE;
}

・Pixmap の画像表示

ウィンドウ上にある Drawarea に Pixmap を表示する手法を解説します。
GLADE には Pixmap というパーツがあるのですが、特定の Pixmap を置いておく には簡単ですがダイナミックに描き変わるといった用途にはさほど向いていない ようなので、自前でメモリー上に Pixmap を用意しそれを Drawarea に描き込む 方法をとります。将来的にダブルバッファとかを用意するための基礎とも なりますので。

まずは XPM 形式のグラフィックファイルを「インクルード」して Pixmap を 生成する方法から。
ASCII形式の XPM ファイルはそのままテキストファイルとして覗けばわかるように C のデータ形式をしています。これを Gdk で利用するには「ファイルとしてデータ を置きそれを読み込む」方法と、「コードとしてインクルードしてバイナリに含めて しまう」といった方法の2つがあります。
GLADE ではなにかとファイル形式にしてしまいます。これはこれで良いのですが、 インストール先に pixmap/ というフォルダを作りそこにファイルをおさめると いった形になります。ほんのちょっとしたデータでもこれをやられるのはちょっと うざったいのでケースバイケースということで、今回はコードとして埋め込む 方式にしてみましょう。

XPM データはソース内で include してしまいます。

#include "../picture/pic1.xpm"
#include "../picture/pic2.xpm"
それぞれのデータポインタが pic1_xpm, pic2_xpm とした場合、これらデータ ポインタから pixmap を生成するのは gdk_pixmap_create_from_xpm_d という gdk関数を使います。
gdk_pixmap_create_from_xpm_d は XPM データから Pixmap データと表示領域 マスクとして用いられる Bitmap データの二つを生成します。Pixmap を表示する 際に Mask が必要な場面もありますので、どちらも拾っておきましょう。 具体的なコードは以下の様になります。mainwin ってのはメインウィンドウの GtkWidget* です。
{
  GdkPixmap *pixmap[2];
  GdkBitmap *mask[2];

  /* Pixmap の作成 */
  pixmap[0] = gdk_pixmap_create_from_xpm_d(mainwin->window,
					   &mask[0],
					   NULL,
					   pic1_xpm);
  pixmap[1] = gdk_pixmap_create_from_xpm_d(mainwin->window,
					   &mask[1],
					   NULL,
					   pic2_xpm);
}
そうやって作成した Pixmap と Mask があるとして、それらを Drawarea に 表示する手順は以下の様になります。(詳細省略)
{
  gdk_window_get_size(pixmap[n], &w, &h);
  gdk_gc_set_clip_mask(win_gc, mask[n]);
  gdk_gc_set_clip_origin(win_gc, 0, 0);
  gdk_draw_pixmap(canvas->window, win_gc, pixmap[n], 0, 0, 0, 0, w, h);
  gdk_gc_set_clip_mask(win_gc, NULL);
}
canvas は Drawarea の GtkWidget* を表します。win_gc は グラフィックコンテキストで
{
  /* GC の作成 */
  win_gc = gdk_gc_new(mainwin->window);
}
で生成されています。

細かい関数仕様などは Gtkリファレンスを参照したりしてください。

全体的なコード例 "paint.c"

#エラーチェックとか甘甘 :-P


・Make dist するために

GLADE でコードを生成すると autogen.sh なるスクリプトが作られます。 これを実行すると configure が生成された後、実行され Make の準備が 整います。その後 make したり make install したりします。
で、そのなかに make dist というものがあります。これは配布に必要な 一式をまとめ tarball 形式のソース配布形式を作成するものです。 (バージョンナンバーもファイルネームに含まれる)
自分でソースを追加した後とかだとそのままってわけにいきませんから この make dist ができるように configure.in や Makefile.am をちょっと 書き直しましょう。

ソースルート(README や configure があるフォルダ)になにかしら 新規フォルダを追加した場合それをソースルートの Makefile.am に記録します。 今回は GLADE が生成する intl, po という二つのソースディレクトリを 使いませんのでそれを削除し、src のみを残します。 tarball作成に picture というディレクトリの画像データが必要なので、 dist-hook: というターゲットを書き換えて picture に準じるようにします。 多分デフォルトだと pixmap を扱うために存在しているはずです。もし、 pixmap ディレクトリも必要ならコピーして併記にしましょう。 pixmap インストールに必要な install-data-local: は今回必要無いんで 削っちゃいます。
configure.in の中に、Makefile を実行する手順が AC_OUTPUT に書いてあります。 今回 intl/, po/ はいらないので削っておきます。それと、バージョンナンバー も正しく記載しておきましょう。
src/ 内にソースを追加している場合 src/Makefile.am の 〜_SOURCES にそれら を追加します。
それら変更が終ったら autogen.sh を実行して autoconf を起動、configure を 作成します。ここまで問題なくできたら make all, make install, make dist が 実行できると思います。

詳しいことは autoconf や automake の利用法を調べてみてください。


というわけで、上記の サンプルソース です。(Size = 130KB)

配布条件はソース/データ共に GPL に準ず。


rero2@yuumu.rim.or.jp