[戻る]

GtkDrawingAreaに図形を表示する

マウスボタンの押下イベントでランダムに図形を表示します。

GtkDrawingAreaでのシグナルハンドリングとサンプルコード

GtkDrawingAreaでマウスボタンやキーボードのシグナルを扱うためにはgtk_widget_set_events()で扱うシグナルに対応したマスク値を登録する必要があります。
このgtk_widget_set_events()はウィジェットがリアライズする前に呼び出す必要があります。
リアライズは普段はあまり意識することは無いと思いますが、gtk_widget_show()するとリアライズされるようです。本当はもっと細かい条件があるんですが、ドキュメント読んでも、ソースコード眺めても理解できませんでした。実用上はこの程度の理解でも大丈夫みたいです。
リアライズ後に受け取るイベントを追加するにはgtk_widget_add_events()を使用します。
また、キーボードイベントを発生させるにはGTK_CAN_FOCUSフラグをウィジェットにセットする必要があります。でないとフォーカスされないのでkey-press-eventシグナルハンドラを設定してもそれが呼び出される事はありません。

描画を行うにはグラフィックコンテキストを得る必要があるのですが、こちらはリアライズ後、つまりGDKウィンドウが割り当てられた後でないとgdk_gc_new(widget->window)としても上手くいきません。
初期化はなるべく纏めて行いたいと思うのですが、シグナルハンドラの設定とグラフィックコンテキストの取得はgtk_widget_show()の前後に分ける必要があります。

サンプルです。gtkdrawingarea_event.c
ゴチャゴチャと判りにくくなってしまいました。
ランダムな画像をマウスクリックした場所に描画します。また、キーボードの0〜9で図形の大きさを変更します。
[TOP]

イベントを受け取れるようにする

main()gtk_widget_show_all()の前で使用しています。
void gtk_widget_set_events ( GtkWidget *widget,
                             gint       events );
eventsに使用するイベントの種類をenum GdkEventMaskの論理和で設定します。設定する値と対応するシグナルは下表のとおりです。
空いている所はわかりませんでした。漏れているシグナルもあると思います。
gtk_drag_source_set()の内部ではGDK_BUTTON_PRESS_MASKGDK_BUTTON_RELEASE_MASKGDK_BUTTON_MOTION_MASKを追加したりと、GTK+が自動的にセットしてくれるものもあるみたいです。
フラグ シグナル
GDK_EXPOSURE_MASK expose-event
GDK_POINTER_MOTION_MASK motion-notify-event
GDK_POINTER_MOTION_HINT_MASK
GDK_BUTTON_MOTION_MASK マウスボタン押下中のmotion-notify-event?
GDK_BUTTON1_MOTION_MASK
GDK_BUTTON2_MOTION_MASK
GDK_BUTTON3_MOTION_MASK
GDK_BUTTON_PRESS_MASK button-press-event
scroll-event
GDK_BUTTON_RELEASE_MASK button-release-event
GDK_KEY_PRESS_MASK key-press-event
GDK_KEY_RELEASE_MASK key-release-event
GDK_ENTER_NOTIFY_MASK enter-notify-event
GDK_LEAVE_NOTIFY_MASK leave-notify-event
GDK_FOCUS_CHANGE_MASK focus-in-event
focus-out-event
GDK_STRUCTURE_MASK destroy-event
configure-event
map-event
unmap-event
window-state-event
GDK_PROPERTY_CHANGE_MASK property-notify-event
GDK_VISIVILITY_NOTIFY_MASK visibility-notify-event
GDK_PROXIMITY_IN_MASK proximity-in-event
GDK_PROXIMITY_OUT_MASK proximity-out-event
GDK_SUBSTUCTURE_MASK
GDK_SCROLL_MASK
GDK_ALL_EVENTS_MASK 全イベント

gtk_widget_show()後に受け取るイベントを追加する場合はgtk_widget_add_events()を使用します。
void gtk_widget_add_events ( GtkWidget *widget,
                             gint       events );
eventsで使用する値はgtk_widget_set_events()と同じです。

GtkDrawingAreakey-press-eventを受け取るにはGTK_CAN_FOCUSフラグをセットします。
GTK_WIDGET_SET_FLAGS(widget, GTK_CAN_FOCUS);
とすればOKです。can-focusプロパティをTRUEにしても良いみたいですがわざわざそんな事をする必要もないでしょう。
[TOP]

グラフィックコンテキストを取得して描画する

グラフィックコンテキストを得るにはgtk_widget_show()した後にgdk_gc_new()を呼び出します。
GdkGC* gdk_gc_new ( GdkDrawable *drawable );
描画したいウィジェットのwindowメンバを上記関数に渡します。
GdkGC *gc = gdk_gc_new(widget->widnow);
ウィジェットの中にはGtkLabelのように親ウィジェットに直接描画して自身ではウィンドウを持たないものもあります。 そういったウィジェットのグラフィックコンテキストは取得できません。

グラフィックコンテキストを取得したら描画します。
サンプルではbutton-press-eventシグナルハンドラからgdk_draw_arc()gdk_draw_rectangle()gdk_draw_polygon()を呼んでいます。
詳細はAPIマニュアルを参照してください。他にもいろいろあります。

描画する際に使用する色はgdk_colormap_alloc_color()gdk_gc_set_foreground()で指定しています。
GdkColormap* gdk_colormap_get_system ( void );

gboolean gdk_colormap_alloc_color ( GdkColormap *colormap,
                                    GdkColor    *color,
                                    gboolean     writeable,
                                    gboolean     best_match );

void gdk_gc_set_foreground ( GdkGC          *gc,
                             const GdkColor *color );
gdk_colormap_get_system()で取得したシステムカラーマップにgdk_colormap_alloc_color()で使用する色を割り当てています。
gdk_colormap_alloc_color()writeableTRUEにすると後でその色を変更することができます。best_matchTRUEにすると指定した色が割り当てられないとき似た色を使用します。
colorredgreenblueメンバのみ設定し、gdk_colormap_alloc_color()が割り当て結果をpixelメンバに格納してくれます。
typedef struct {
    guint32  pixel;
    guint16  red;
    guint16  green;
    guint16  blue;
} GdkColor;
そして、gdk_gc_set_foreground()color->pixelを描画色として設定しています。
つまりred,green,blue←→pixelの変換テーブルを作成し、描画のときにはpixelから実際の色を探すという事をしています。

線の太さはgdk_gc_set_line_attributes()で設定します。
void gdk_gc_set_line_attributes ( GdkGC        *gc,
                                  gint          line_width,
                                  GdkLineStyle  line_style,
                                  GdkCapStyle   cap_style,
                                  GdkJoinStyle  join_style );
line_widthで太さを指定します。
line_styleで線、cap_styleで線を描画したときの終端、join_styleで線の曲がり角の描画方法を指定します。
typedef enum {
    GDK_LINE_SOLID,                     実線
    GDK_LINE_ON_OFF_DASH,               点線
    GDK_LINE_DOUBLE_DASH                描画色と背景色を交互に繰り返す点線
} GdkLineStyle;

typedef enum {
    GDK_CAP_NOT_LAST,                   線の長さが0でなければGDK_CAP_BUTTと同じ。長さが0のときは最後の点を描画しない。
    GDK_CAP_BUTT,                       線の終端を直線的に描画する。線の太さ×長さの長方形になる。
    GDK_CAP_ROUND,                      線の終端を丸く描画する。丸い部分が終端の座標からはみ出す。
    GDK_CAP_PROJECTING                  線の終端を直線的に描画する。太さの半分だけ終端の座標からはみ出す。
} GdkCapStyle;

typedef enum {
    GDK_JOIN_MITER,                     線の曲がり角が尖った形になる。
    GDK_JOIN_ROUND,                     線の曲がり角が丸く描画される。
    GDK_JOIN_BEVEL                      線の曲がり角が面取りされ平に削られたように描画される。
} GdkJoinStyle;
サンプルではGDK_LINE_SOLIDGDK_CAP_BUTTGDK_JOIN_MITERを使用しています。
[TOP]

expose-event シグナルによる再描画

再描画が必要になるとexpose-eventシグナルが発行されます。
GTK+は何が描画されているかは関知していないので、必ずシグナルハンドラを設定し自分で再描画する必要があります。
gboolean expose_event ( GtkWidget      *widget,
                        GdkEventExpose *event,
                        gpointer        user_data );
再描画が必要な範囲はevent->areaおよびevent->regionにセットされていますが、サンプルでは手を抜いてevent->countが0でなければ無視し、0になったら全て再描画しています。
event->countにはこの後つづけて何回expose-eventが発行されるかがセットされています。
何か描画したらビットマップ等で保存しておいてevent->areaの範囲を再描画するのがまっとうな方法だと思います。
[TOP]
[戻る]