Since version 2.8, GTK+ renders many of its interface widgets with Cairo, a powerful vector graphics library. Davyd Madeley explains how you can implement your own GTK+ widget using Cairo for the actual drawing.
Cairo is a powerful 2-dimensional graphics library designed to support a number of modern graphics techniques including stroking, alpha blending and antialiasing. It supports multiple output formats, which allows developers to use the same code to display graphics on the screen, print them to the printer or accellerate them with OpenGL.
As of GTK+ 2.8, GTK+ has been integrated with the Cairo 1.0 rendering library, giving developers access to this flexible graphics API.
Chapter 1 of this article looks at the more mundane work required to implement a complete GTK+ widget that uses Cairo. Chapter 2 looks at using Cairo to do actual drawing. If you simply want to draw inside an existing widget or simple GtkDrawingArea with Cairo, you can skip straight to Chapter 2. Later chapters will cover emitting signals from your widget and some of the features in the Cairo drawing API.
Шаг 1. Writing a GObject
While it is possible to simply start using Cairo drawing to draw inside a drawing area, many times you will want to be able to write your own custom widget that you can use over and over again. Much of this chapter can be used for the development of many widgets, not just ones using Cairo.
The first step to writing your own custom widget is creating a new GObject to use with that widget. GObjects can get quite complicated, but we’re going to look at the basics for writing our widget.
GTK+ is an object-oriented environment. This means we have two data structures that we need to worry about: the class and the instance. The instance is the data structure that we are most familar with, it is the GtkWidget struct that we pass around inside our program.
Classes also inherit from other classes. Since we are writing a widget that will be drawn on with Cairo, it will be easiest to inherit GtkDrawingArea, which inherits GtkWidget, GtkObject and finally GObject. GtkDrawingArea already implements a lot of functions we need for our widget and will save us from writing a lot of code.
The class is initialised only once, whereas an instance is initialised every time you create a new copy of the widget. If this is confusing or you are not incredibly familiar with object-orientated programming, don’t worry; we will go into depth with what you can do here in a later chapter.
In clock.c you’ll want to define our new object type:
G_DEFINE_TYPE (EggClockFace, egg_clock_face, GTK_TYPE_DRAWING_AREA);
We will call our object EggClockFace and preface all of our function calls with egg_clock_face. We are inheriting the type of GtkDrawingArea.
We now need to define structs for our new class and for instances of that class. In our example these structs are quite simple. If we wanted any publicly accessibly variables we could add them here. Private variables are defined in another struct (we’ll get to that). So, in clock.h:
typedef struct _EggClockFace EggClockFace; typedef struct _EggClockFaceClass EggClockFaceClass; struct _EggClockFace { GtkDrawingArea parent; /* private */ }; struct _EggClockFaceClass { GtkDrawingAreaClass parent_class; };
#define EGG_TYPE_CLOCK_FACE (egg_clock_face_get_type ()) #define EGG_CLOCK_FACE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_CLOCK_FACE, EggClockFace)) #define EGG_CLOCK_FACE_CLASS(obj) (G_TYPE_CHECK_CLASS_CAST ((obj), EGG_CLOCK_FACE, EggClockFaceClass)) #define EGG_IS_CLOCK_FACE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_CLOCK_FACE)) #define EGG_IS_CLOCK_FACE_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE ((obj), EGG_TYPE_CLOCK_FACE)) #define EGG_CLOCK_FACE_GET_CLASS (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_CLOCK_FACE, EggClockFaceClass))
static void egg_clock_face_class_init (EggClockFaceClass *class) { GtkWidgetClass *widget_class; widget_class = GTK_WIDGET_CLASS (class); widget_class->expose_event = egg_clock_face_expose; }
When someone creates a new EggClockFace, egg_clock_face_init() will be called. We don’t need to worry about constructing the GtkWidget or allocating any memory, that has already been handled for us. For the moment, let’s just keep the function empty.
static void egg_clock_face_init (EggClockFace *clock) { }
We do need a way to create an object though, so let’s add egg_clock_face_new().
GtkWidget * egg_clock_face_new (void) { return g_object_new (EGG_TYPE_CLOCK_FACE, NULL); }
Finally, to make it compile, we’re going to need to define our expose handler:
static gboolean egg_clock_face_expose (GtkWidget *clock, GdkEventExpose *event) { return FALSE; }
int main (int argc, char **argv) { GtkWidget *window; GtkWidget *clock; gtk_init (&argc, &argv); window = gtk_window_new (GTK_WINDOW_TOPLEVEL); clock = egg_clock_face_new (); gtk_container_add (GTK_CONTAINER (window), clock); g_signal_connect (window, "destroy", G_CALLBACK (gtk_main_quit), NULL); gtk_widget_show_all (window); gtk_main (); }
You can compile it with the simple line:
gcc -o clock `pkg-config --cflags --libs gtk+-2.0` clock.h clock-ex1.c main.c
And it should look something like this:
Pretty neat, huh? Well… maybe not yet. Now we need to draw something in there!
Шаг 2. Рисование с использованием Cairo.
When things need drawing in GTK+ an “expose-event” will be emitted. As you’ll recall from Step 1, we put the stub for an expose handler in our code. When an expose event occurs, GTK+ will also give us other information, including the area of the widget that we need to redraw. All of this information is contained within the GdkEventExpose struct.
In order to do drawing with Cairo we need a Cairo context (called a cairo_t). We can get a cairo_t for a GdkWindow (this is what you’re drawing into). You should be aware that a GdkWindow is not like a GtkWindow and that all sorts of widgets have one or more GdkWindows inside them for doing drawing. If you can’t keep track of all the names, don’t worry too much, you’ll get the hang of it eventually.
You can access the GdkWindow of most widgets (such as a GtkDrawingArea) by accessing the window member of the widget struct, eg. widget->window. So to get our cairo_t we can extend our expose stub to look like this:
static gboolean egg_clock_face_expose (GtkWidget *clock, GdkEventExpose *event) { cairo_t *cr; /* get a cairo_t */ cr = gdk_cairo_create (clock->window); draw (clock, cr); cairo_destroy (cr); return FALSE; }
static gboolean egg_clock_face_expose (GtkWidget *clock, GdkEventExpose *event) { cairo_t *cr; /* get a cairo_t */ cr = gdk_cairo_create (clock->window); /* set a clip region for the expose event */ cairo_rectangle (cr, event->area.x, event->area.y, event->area.width, event->area.height); cairo_clip (cr); draw (clock, cr); cairo_destroy (cr); return FALSE; }
Firstly, we’re trying to draw a clock face, so we need to do a little bit of simple geometry. The size of our canvas is stored in the widget struct as the member ‘allocation’. Therefore, we can find out the center of our canvas (x, y) pretty easily:
double x, y; x = clock->allocation.x + clock->allocation.width / 2; y = clock->allocation.y + clock->allocation.height / 2;
double radius; radius = MIN (clock->allocation.width / 2, clock->allocation.height / 2) - 5;
cairo_arc (cr, x, y, radius, 0, 2 * M_PI);
cairo_set_source_rgb (cr, 1, 1, 1); cairo_fill_preserve (cr); cairo_set_source_rgb (cr, 0, 0, 0); cairo_stroke (cr);
We can also add a marker for each hour on the clock face. Using a bit of geometry again, we know that we need to divide 2? into 12 pieces, which means that each line is ?/6 radians apart.
We want to draw a line from just inside the circle to the edge of the circle. We can define the path for a line using cairo_move_to() and cairo_line_to().
for (i = 0; i < 12; i++) { int inset; inset = 0.1 * radius; cairo_move_to (cr, x + (radius - inset) * cos (i * M_PI / 6), y + (radius - inset) * sin (i * M_PI / 6)); cairo_line_to (cr, x + radius * cos (i * M_PI / 6), y + radius * sin (i * M_PI / 6)); cairo_stroke (cr); }
for (i = 0; i < 12; i++) { int inset; cairo_save (cr); /* save pen size to stack */ if (i % 3 == 0) { inset = 0.2 * radius; } else { inset = 0.1 * radius; cairo_set_line_width (cr, 0.5 * cairo_get_line_width (cr)); } cairo_move_to (cr, x + (radius - inset) * cos (i * M_PI / 6), y + (radius - inset) * sin (i * M_PI / 6)); cairo_line_to (cr, x + radius * cos (i * M_PI / 6), y + radius * sin (i * M_PI / 6)); cairo_stroke (cr); cairo_restore (cr); /* recover pen size from stack */ }
Your C file should look like clock-ex3.c with the new drawing instructions.
If you compile it with
gcc -o clock `pkg-config --cflags --libs gtk+-2.0` clock.h clock-ex3.c main.c
you will get a blank clock face
So we’ve covered the basics of drawing something on your canvas and making sure that GTK+ redraws it when it is exposed. Next time we’ll look at how to extend your widget with an API so that we can actually start drawing content on it and implementing signals so that we can receive updated information.
If you want to learn more about Cairo and the Cairo drawing API, see their website: Источник: http://www.cairographics.org/
Источник: http://www.gnomejournal.org/article/34/writing-a-widget-using-cairo-and-gtk28