In this article Davyd Madeley continues his tutorial on writing a clock widget using GTK and Cairo.
Thinking back to last issue, we used Cairo to build the face of a clock as part of a GtkWidget we called EggClockFace. We covered the basics of writing a GObject and drawing in the expose hander with Cairo, but what about making the clock run?
Step 3. Making it Tick
Making the clock run is as simple as starting a timer that calls a callback. However, we might also want to be able to set a different time on our clock, so we’ll store the time for the clock inside the widget. We don’t want to let people change the time directly, as then we won’t know that the time has been changed. Instead, we want them to have to use the public API we provide to change the time on the clock. In object-orientation speak, we want to make the time variable private.
We can add a structure of private data to the top of our C file. We add it to the C file rather than a header since it is private to this class (which is contained within this C file) and we don’t want to clutter our namespace with it (or let it be accessed from outside our class).
Add this to the top of your clock.c:
typedef struct _EggClockFacePrivate EggClockFacePrivate; struct _EggClockFacePrivate { struct tm time; /* the time on the clock face */ };
#define EGG_CLOCK_FACE_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), EGG_TYPE_CLOCK_FACE, gClockFacePrivate))
g_type_class_add_private (obj_class, sizeof (EggClockFacePrivate));
static void egg_clock_face_example_function (EggClockFace *clock) { EggClockFacePrivate *priv; priv = EGG_CLOCK_FACE_GET_PRIVATE (clock); }
localtime_r (&timet, &priv->time);
static gboolean egg_clock_face_update (gpointer data) { EggClockFace *clock; EggClockFacePrivate *priv; time_t timet; clock = EGG_CLOCK_FACE (data); priv = EGG_CLOCK_FACE_GET_PRIVATE (clock); /* update the time */ time (&timet); localtime_r (&timet, &priv->time); egg_clock_face_redraw_canvas (clock); return TRUE; /* keep running this event */ }
static void egg_clock_face_redraw_canvas (EggClockFace *clock) { GtkWidget *widget; GdkRegion *region; widget = GTK_WIDGET (clock); if (!widget->window) return; region = gdk_drawable_get_clip_region (widget->window); /* redraw the cairo canvas completely by exposing it */ gdk_window_invalidate_region (widget->window, region, TRUE); gdk_window_process_updates (widget->window, TRUE); gdk_region_destroy (region); }
So to draw the hour hand, we might do something like:
cairo_save (cr); cairo_set_line_width (cr, 2.5 * cairo_get_line_width (cr)); cairo_move_to (cr, x, y); cairo_line_to (cr, x + radius / 2 * sin (M_PI / 6 * hours + M_PI / 360 * minutes), y + radius / 2 * -cos (M_PI / 6 * hours + M_PI / 360 * minutes)); cairo_stroke (cr); cairo_restore (cr);
cairo_move_to (cr, x, y); cairo_line_to (cr, x + radius * 0.75 * sin (M_PI / 30 * minutes), y + radius * 0.75 * -cos (M_PI / 30 * minutes)); cairo_stroke (cr);
static void egg_clock_face_init (EggClockFace *clock) { egg_clock_face_update (clock); /* update the clock once a second */ g_timeout_add (1000, egg_clock_face_update, clock); }
gcc -o clock `pkg-config --cflags --libs gtk+-2.0` clock-ex4.c main.c
Extra: Making the Picture Tick
The animated GIF of the clock ticking was done with a tool called byzanz. I simply recorded 60 seconds of the clock. In order to find out the window location for byzanz-record, I added this to my main.c after gtk_widget_show_all():
{ GdkRectangle rect; gdk_window_get_frame_extents (window->window, &rect); g_print ("-x %i -y %i -w %i -h %in", rect.x, rect.y, rect.width, rect.height); }
$ ./byzanz-record -d 60 $GEOMETRY -l clock.gif
Firstly we need to decide on what our signal is going to send and add this to our clock.h. We will implement a “time-changed” signal that along with the object also gives the time in hours and minutes that the clock has now been set to. If we were connecting this signal, our callback would look something like this:
static void time_changed_cb (EggClockFace *clock, int hours, int minutes, gpointer user_data) { ... }
void (* time_changed) (EggClockFace *clock, int hours, int minutes);
enum { TIME_CHANGED, LAST_SIGNAL };
static guint egg_clock_face_signals[LAST_SIGNAL] = { 0 };
egg_clock_face_class_signals[TIME_CHANGED] = g_signal_new ( "time-changed", G_OBJECT_CLASS_TYPE (obj_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (EggClockFaceClass, time_changed), NULL, NULL, clock_marshal_VOID_INT_INT, G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_INT);
This is actually very easy to do, we simply define it in a file clock-marshallers.list, which contains a list of the marshallers we wish to generate. For example:
# these marshallers are generated with glib-genmarshal(1) VOID:INT,INT
all: clock clock: clock.c clock.h main.c clock-marshallers.c clock-marshallers.h gcc -g -o clock clock.c main.c clock-marshallers.c `pkg-config --libs --cflags gtk+-2.0` clock-marshallers.c: clock-marshallers.list glib-genmarshal --prefix _clock_marshal --body $< > $@ clock-marshallers.h: clock-marshallers.list glib-genmarshal --prefix _clock_marshal --header $< > $@
This kind of code autogeneration is designed to make things easier for programmers without requiring them to write repetitive marshaller code over and over again. This is especialy useful if you have lots of signals. Of course, there is nothing wrong with either writing the marshallers yourself, or simply using a generated marshallers list, but why would you? Doing so only makes it harder to add or edit signals later on.
Next we have to implement a button-press-event handler so that we can determine when someone has actually clicked on a hand. We can override the signals for button-press-event, button-release-event and motion-notify-handler at the same time as replacing the expose-event. In egg_clock_face_class_init():
widget_class->button_press_event = egg_clock_face_button_press; widget_class->button_release_event = egg_clock_face_button_release; widget_class->motion_notify_event = egg_clock_face_motion_notify;
gtk_widget_add_events (GTK_WIDGET (clock), GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK);
We know that (sin ?, cos ?) is a point on the unit circle, that is it has magnitude 1. Thus a vector from the origin to (sin ?, cos ?) will be a unit vector, we will name it l. We will also take a vector p which is the vector from the origin to the point where the user clicked.
This would give vector components equal to:
px = event->x - clock->allocation.width / 2; py = clock->allocation.height / 2 - event->y; lx = sin (M_PI / 30 * minutes); ly = cos (M_PI / 30 * minutes);
We can project p onto l using the dot product such that u = p.l. The dot product can be done mathematically like so:
u = lx * px + ly * py;
If u comes out to be a negative number we’ll ignore it, this means that the user clicked on the opposite side of the clock to where the hand is. Finally, the magnitude of the distance can be found. If the magnitude of the distance (squared) is less then 5 pixels (squared) we can set a private property dragging to be true (we used squared values here because we have no need to do a slow sqrt()).
if (u < 0) return FALSE; d2 = pow (px - u * lx, 2) + pow (py - u * ly, 2); if (d2 < 25) /* 5 pixels away from the line */ { priv->dragging = TRUE; }
static emit_time_changed_signal (EggClockFace *clock, int x, int y) { EggClockFacePrivate *priv; double phi; int hour, minute; priv = EGG_CLOCK_FACE_GET_PRIVATE (clock); /* decode the minute hand */ /* normalise the coordinates around the origin */ x -= GTK_WIDGET (clock)->allocation.width / 2; y -= GTK_WIDGET (clock)->allocation.height / 2; /* phi is a bearing from north clockwise, use the same geometry as we * did to position the minute hand originally */ phi = atan2 (x, -y); if (phi < 0) phi += M_PI * 2; hour = priv->time.tm_hour; minute = phi * 30 / M_PI; /* update the offset */ priv->minute_offset = minute - priv->time.tm_min; egg_clock_face_redraw_canvas (clock); g_signal_emit (clock, egg_clock_face_signals[TIME_CHANGED], 0, hour, minute); }
After all of this our two signals handlers from above are simply:
static gboolean egg_clock_face_motion_notify (GtkWidget *clock, GdkEventMotion *event) { EggClockFacePrivate *priv; int x, y; priv = EGG_CLOCK_FACE_GET_PRIVATE (clock); if (priv->dragging) { emit_time_changed_signal (EGG_CLOCK_FACE (clock), event->x, event->y); } } static gboolean egg_clock_face_button_release (GtkWidget *clock, GdkEventButton *event) { EggClockFacePrivate *priv; priv = EGG_CLOCK_FACE_GET_PRIVATE (clock); if (priv->dragging) { priv = EGG_CLOCK_FACE_GET_PRIVATE (clock); priv->dragging = FALSE; emit_time_changed_signal (EGG_CLOCK_FACE (clock), event->x, event->y); } return FALSE; }
g_signal_connect (clock, "time-changed<a href=":time-changed">, G_CALLBACK (time_changed_cb), NULL); static void time_changed_cb (EggClockFace *clock, int hours, int minutes, gpointer data) { g_print (</a> - %02i:%02in", hours, minutes); }
That’s it! You now know how to implement a GObject, draw things inside that GObject, add private data, add signals and animate the object. This forms pretty much everything you need to write your own GtkWidget.
Источник: http://www.gnomejournal.org/article/36/writing-a-widget-using-cairo-and-gtk28-part-2