Many widgets, like buttons, do all their drawing themselves. You just tell them the label you want to see, and they figure out what font to use, draw the button outline and focus rectangle, etc. Sometimes, it is necessary to do some custom drawing. In that case, a Gtk_Drawing_Area might be the right widget to use. It offers a canvas on which you can draw by connecting to the "draw" signal.
The contents of a widget often need to be partially or fully redrawn, e.g. when another window is moved and uncovers part of the widget, or when tie window containing it is resized. It is also possible to explicitly cause part or all of the widget to be redrawn, by calling Gtk.Widget.Queue_Draw() or its variants. GtkAda takes care of most of the details by providing a ready-to-use cairo context to the ::draw signal handler.
The following example shows a ::draw signal handler. It is a bit more complicated than the previous examples, since it also demonstrates input event handling by means of ::button-press and ::motion-notify handlers.
src/draw.adbwith Gtk.Window; use Gtk.Window; with Gtk.Frame; use Gtk.Frame; with Gtk.Button; use Gtk.Button; with Gtk.Drawing_Area; use Gtk.Drawing_Area; with Gtkada.Handlers; use Gtkada.Handlers; with Gdk.Event; use Gdk.Event; with draw_cb; use draw_cb; with Gtk.Main; with Gtk.Enums; procedure Draw is Win : Gtk_Window; Frame : Gtk_Frame; Da : Gtk_Drawing_Area; begin -- Initialize GtkAda. Gtk.Main.Init; -- create a top level window Gtk_New (Win); Win.Set_Title ("Drawing Area"); -- set the border width of the window Win.Set_Border_Width (8); -- connect the "destroy" signal Win.On_Destroy (main_quit'Access); -- create a frame Gtk_New (Frame); Frame.Set_Shadow_Type (Gtk.Enums.Shadow_In); Win.Add (Frame); Gtk_New (Da); -- set a minimum size Da.Set_Size_Request (100, 100); Frame.Add (Da); -- Signals used to handle the backing surface Da.On_Draw (draw_cb.draw_cb'Access); Da.On_Configure_Event (configure_event_cb'Access); -- Event signals Da.On_Motion_Notify_Event (motion_notify_event_cb'Access); Da.On_Button_Press_Event (button_press_event_cb'Access); -- Ask to receive events the drawing area doesn't normally -- subscribe to. In particular, we need to ask for the -- button press and motion notify events that want to handle. Da.Set_Events (Da.Get_Events or Button_Press_Mask or Pointer_Motion_Mask); -- Now that we are done packing our widgets, we show them all -- in one go, by calling Win.Show_All. -- This call recursively calls Show on all widgets -- that are contained in the window, directly or indirectly. Win.Show_All; -- All GTK applications must have a Gtk.Main.Main. Control ends here -- and waits for an event to occur (like a key press or a mouse event), -- until Gtk.Main.Main_Quit is called. Gtk.Main.Main; end Draw;src/draw_cb.ads:
with Gtk.Widget; use Gtk.Widget; with Gtk.Button; use Gtk.Button; with Glib.Object; with Gdk.Event; with Cairo; package draw_cb is procedure main_quit (Self : access Gtk_Widget_Record'Class); function draw_cb (Self : access Gtk_Widget_Record'Class; Cr : Cairo.Cairo_Context) return Boolean; -- Create a new surface of the appropriate size to store our scribbles function configure_event_cb (Self : access Gtk_Widget_Record'Class; Event : Gdk.Event.Gdk_Event_Configure) return Boolean; -- Handle motion events by continuing to draw if button 1 is -- still held down. The ::motion-notify signal handler receives -- a GdkEventMotion struct which contains this information. function motion_notify_event_cb (Self : access Gtk_Widget_Record'Class; Event : Gdk.Event.Gdk_Event_Motion) return Boolean; -- Handle button press events by either drawing a rectangle -- or clearing the surface, depending on which button was pressed. -- The ::button-press signal handler receives a GdkEventButton -- struct which contains this information. function button_press_event_cb (Self : access Gtk_Widget_Record'Class; Event : Gdk.Event.Gdk_Event_Button) return Boolean; end draw_cb;src/draw_cb.adb:
with Glib; use Glib; with Gdk.Types; use Gdk.Types; with Gdk.Window; with Gtk.Main; package body draw_cb is -- Button defintions from gtk-3.0/gdk/gdkevents.h -- The primary button. This is typically the left button, or the -- right button in a left-handed setup. Gdk_Button_Primary : constant := 1; -- The secondary button. This is typically the right mouse button, or the -- left button in a left-handed setup. Gdk_Button_Secondary : constant := 3; surface : Cairo.Cairo_Surface; use type Cairo.Cairo_Surface; procedure main_quit (Self : access Gtk_Widget_Record'Class) is begin if surface /= Cairo.Null_Surface then Cairo.Surface_Destroy (surface); end if; Gtk.Main.Main_Quit; end main_quit; procedure Clear_Surface is Cr : Cairo.Cairo_Context; begin Cr := Cairo.Create (surface); Cairo.Set_Source_Rgb (Cr, 1.0, 1.0, 1.0); Cairo.Paint (Cr); Cairo.Destroy (Cr); end Clear_Surface; -- Redraw the screen from the surface. Note that the ::draw -- signal receives a ready-to-be-used cairo_t that is already -- clipped to only draw the exposed areas of the widget function draw_cb (Self : access Gtk_Widget_Record'Class; Cr : Cairo.Cairo_Context) return Boolean is begin Cairo.Set_Source_Surface (Cr, surface, 0.0, 0.0); Cairo.Paint (Cr); return False; end draw_cb; function configure_event_cb (Self : access Gtk_Widget_Record'Class; Event : Gdk.Event.Gdk_Event_Configure) return Boolean is begin if surface /= Cairo.Null_Surface then Cairo.Surface_Destroy (surface); end if; surface := Gdk.Window.Create_Similar_Surface (Self.Get_Window, Cairo.Cairo_Content_Color, Self.Get_Allocated_Width, Self.Get_Allocated_Height); -- Initialize the surface to white Clear_Surface; -- We've handled the configure event, no need for further processing. return True; end configure_event_cb; -- Draw a rectangle on the surface at the given position procedure draw_brush (Self : access Gtk_Widget_Record'Class; x : Gdouble; y : Gdouble) is Cr : Cairo.Cairo_Context; begin -- Paint to the surface, where we store our state Cr := Cairo.Create (surface); Cairo.Rectangle (Cr, x - 3.0, y - 3.0, 6.0, 6.0); Cairo.Fill (Cr); Cairo.Destroy (Cr); -- Now invalidate the affected region of the drawing area. Self.Queue_Draw_Area (Gint (x - 3.0), Gint (y - 3.0), 6, 6); end draw_brush; function motion_notify_event_cb (Self : access Gtk_Widget_Record'Class; Event : Gdk.Event.Gdk_Event_Motion) return Boolean is begin -- paranoia check, in case we haven't gotten a configure event if surface = Cairo.Null_Surface then return False; end if; if (Event.State and Gdk.Types.Button1_Mask) > 0 then draw_brush (Self, Event.X, Event.Y); end if; -- We've handled it, stop processing return True; end motion_notify_event_cb; function button_press_event_cb (Self : access Gtk_Widget_Record'Class; Event : Gdk.Event.Gdk_Event_Button) return Boolean is begin -- paranoia check, in case we haven't gotten a configure event if surface = Cairo.Null_Surface then return False; end if; if Event.Button = Gdk_Button_Primary then draw_brush (Self, Event.X, Event.Y); elsif Event.Button = Gdk_Button_Secondary then Clear_Surface; Self.Queue_Draw; end if; -- We've handled the event, stop processing return True; end button_press_event_cb; end draw_cb;draw.gpr:
with "gtkada"; project Draw is for Source_Dirs use ("src"); for Object_Dir use "obj"; for Main use ("draw.adb"); -- Enable Ada 2005. package Compiler is for Default_Switches ("ada") use ("-gnat05"); end Compiler; end Draw;To compile:
gprbuild -P draw
This program is more complicated and it shows a problem with the current GtkAda (3.8.3) binding.
There is no button definition for GDK_BUTTON_PRIMARY and GDK_BUTTON_SECONDARY, etc., which have to be defined in package draw_cb ourselves by taking the definition from the gtk-3.0/gdk/gdkevents.h. As of 2013-03-26, the SVN source tree has been updated after I reported the bug, it now contains the button definitions in gdk-event.ads as Button_Primary, Button_Secondary and Button_MIddle, if you are using the SVN source after the day, you may want to use that definition directly.
The cairo surface variable is defined in the draw_cb package body, it has the similar effect of the C static definition that only procedures and functions in the package body could see it.
We use use type Cairo.Cairo_Surface; in the draw_cb package body, so that /= and = could be used directly on the type without exposing other Cairo definitions.