Custom GTK+ widget

In this part of the GTK+ tutorial, we create a custom CPU widget.

gui
Custom GTK+ widget

Contents Previous

Custom GTK+ widget

last modified October 18, 2023

In this part of the GTK+ programming tutorial, we create a custom GTK+ widget. We use the Cairo graphics library.

CPU widget

In the next example we create a custom CPU widget.

mycpu.h

#ifndef MY_CPU_H #define MY_CPU_H

#include <gtk/gtk.h> #include <cairo.h>

G_BEGIN_DECLS

/* Standart GObject macros */ #define MY_TYPE_CPU (my_cpu_get_type()) #define MY_CPU(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), MY_TYPE_CPU, MyCpu)) #define MY_CPU_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), MY_TYPE_CPU, MyCpuClass)) #define MY_IS_CPU(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), MY_TYPE_CPU)) #define MY_IS_CPU_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), MY_TYPE_CPU)) #define MY_CPU_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), MY_TYPE_CPU, MyCpuClass))

/* Type definition */ typedef struct _MyCpu MyCpu; typedef struct _MyCpuClass MyCpuClass; typedef struct _MyCpuPrivate MyCpuPrivate;

struct _MyCpu {

GtkWidget parent;

/< Private >/ MyCpuPrivate *priv; };

struct _MyCpuClass {

GtkWidgetClass parent_class; };

/* Public API */ GType my_cpu_get_type(void) G_GNUC_CONST; GtkWidget *my_cpu_new(void);

gdouble my_cpu_get_percent(MyCpu *cpu); void my_cpu_set_percent(MyCpu *cpu, gdouble sel);

G_END_DECLS

#endif /* MY_CPU_H */

In the mycpu.h file, we define types, macros, and functions for the custom widget.

mycpu.c

/* mycpu.c */

#include “mycpu.h”

/* Properties enum */ enum {

P_0, /* Padding */ P_PERCENT };

/* Private data structure */ struct _MyCpuPrivate {

gdouble percent; GdkWindow *window; };

const gint WIDTH = 80; const gint HEIGHT = 100;

/* Internal API */ static void my_cpu_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void my_cpu_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void my_cpu_size_request(GtkWidget *widget, GtkRequisition *requisition); static void my_cpu_size_allocate(GtkWidget *widget, GtkAllocation *allocation); static void my_cpu_realize(GtkWidget *widget); static gboolean my_cpu_expose(GtkWidget *widget, GdkEventExpose *event);

/* Define type */ G_DEFINE_TYPE(MyCpu, my_cpu, GTK_TYPE_WIDGET)

/* Initialization */ static void my_cpu_class_init(MyCpuClass *klass) {

GObjectClass *g_class; GtkWidgetClass *w_class; GParamSpec *pspec;

g_class = G_OBJECT_CLASS(klass); w_class = GTK_WIDGET_CLASS(klass);

/* Override widget class methods */ g_class->set_property = my_cpu_set_property; g_class->get_property = my_cpu_get_property;

w_class->realize = my_cpu_realize; w_class->size_request = my_cpu_size_request; w_class->size_allocate = my_cpu_size_allocate; w_class->expose_event = my_cpu_expose;

/* Install property */ pspec = g_param_spec_double(“percent”, “Percent”, “What CPU load should be displayed”, 0, 1, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

g_object_class_install_property(g_class, P_PERCENT, pspec);

/* Add private data */ g_type_class_add_private(g_class, sizeof(MyCpuPrivate)); }

static void my_cpu_init(MyCpu *cpu) {

MyCpuPrivate *priv;

priv = G_TYPE_INSTANCE_GET_PRIVATE(cpu, MY_TYPE_CPU, MyCpuPrivate);

gtk_widget_set_has_window(GTK_WIDGET(cpu), TRUE);

/* Set default values */ priv->percent = 0;

/* Create cache for faster access */ cpu->priv = priv; }

/* Overriden virtual methods */ static void my_cpu_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) {

MyCpu *cpu = MY_CPU(object);

switch(prop_id) {

  case P_PERCENT:
  
     my_cpu_set_percent(cpu, g_value_get_double(value));
     break;

  default:
  
     G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
     break;

} }

static void my_cpu_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) {

MyCpu *cpu = MY_CPU(object);

switch(prop_id) {

  case P_PERCENT:
     g_value_set_double(value, cpu-&gt;priv-&gt;percent);
     break;

  default:
     G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
     break;

} }

static void my_cpu_realize(GtkWidget *widget) {

MyCpuPrivate *priv = MY_CPU(widget)->priv; GtkAllocation alloc; GdkWindowAttr attrs; guint attrs_mask;

gtk_widget_set_realized(widget, TRUE);

gtk_widget_get_allocation(widget, &alloc);

attrs.x = alloc.x; attrs.y = alloc.y; attrs.width = alloc.width; attrs.height = alloc.height; attrs.window_type = GDK_WINDOW_CHILD; attrs.wclass = GDK_INPUT_OUTPUT; attrs.event_mask = gtk_widget_get_events(widget) | GDK_EXPOSURE_MASK;

attrs_mask = GDK_WA_X | GDK_WA_Y;

priv->window = gdk_window_new(gtk_widget_get_parent_window(widget), &attrs, attrs_mask); gdk_window_set_user_data(priv->window, widget); gtk_widget_set_window(widget, priv->window);

widget->style = gtk_style_attach(gtk_widget_get_style( widget ), priv->window); gtk_style_set_background(widget->style, priv->window, GTK_STATE_NORMAL); }

static void my_cpu_size_request(GtkWidget *widget, GtkRequisition *requisition) {

requisition->width = WIDTH; requisition->height = HEIGHT; }

static void my_cpu_size_allocate(GtkWidget *widget, GtkAllocation *allocation) {

MyCpuPrivate *priv;

priv = MY_CPU(widget)->priv;

gtk_widget_set_allocation(widget, allocation);

if (gtk_widget_get_realized(widget)) {

  gdk_window_move_resize(priv-&gt;window, allocation-&gt;x, allocation-&gt;y,
      WIDTH, HEIGHT);

} }

static gboolean my_cpu_expose(GtkWidget *widget, GdkEventExpose *event) {

MyCpuPrivate *priv = MY_CPU(widget)->priv; cairo_t *cr; gint limit; gint i;

cr = gdk_cairo_create(event->window);

cairo_translate(cr, 0, 7);

cairo_set_source_rgb(cr, 0, 0, 0); cairo_paint(cr);

limit = 20 - priv->percent / 5;

for (i = 1; i <= 20; i++) {

  if (i &gt; limit) {
     cairo_set_source_rgb(cr, 0.6, 1.0, 0);
  } else {
     cairo_set_source_rgb(cr, 0.2, 0.4, 0);
  }

  cairo_rectangle(cr, 8,  i * 4, 30, 3);
  cairo_rectangle(cr, 42, i * 4, 30, 3);
  cairo_fill(cr);

}

cairo_destroy(cr);

return TRUE; }

/* Public API */ GtkWidget *my_cpu_new(void) {

return(g_object_new(MY_TYPE_CPU, NULL)); }

gdouble my_cpu_get_percent(MyCpu *cpu) {

g_return_val_if_fail(MY_IS_CPU(cpu), 0);

return(cpu->priv->percent); }

void my_cpu_set_percent(MyCpu *cpu, gdouble sel) {

g_return_if_fail(MY_IS_CPU(cpu));

cpu->priv->percent = sel; gtk_widget_queue_draw(GTK_WIDGET(cpu)); }

The mycpu.c is the implementation of the CPU widget. The CPU widget is a GtkWidget on which we draw with Cairo API. We draw a black background and 40 small rectangles. The rectangles are drawn in two colours: dark green and bright green colour. The GtkVScale widget controls the number of the bright green rectangles drawn on the widget.

main.c

#include “mycpu.h”

void cb_changed(GtkRange *range, GtkWidget *cpu) {

my_cpu_set_percent(MY_CPU(cpu), gtk_range_get_value(range)); }

int main(int argc, char *argv[]) {

GtkWidget *window; GtkWidget *hbox; GtkWidget *vbox; GtkWidget *cpu; GtkWidget *scale;

gtk_init(&argc, &argv);

window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_container_set_border_width(GTK_CONTAINER(window), 15); gtk_window_set_title(GTK_WINDOW(window), “CPU widget”);

vbox = gtk_vbox_new(FALSE, 0); hbox = gtk_hbox_new(FALSE, 25);

cpu = my_cpu_new(); gtk_box_pack_start(GTK_BOX(hbox), cpu, FALSE, FALSE, 0);

scale = gtk_vscale_new_with_range(0, 100, 1); gtk_scale_set_draw_value(GTK_SCALE(scale), FALSE); gtk_range_set_inverted(GTK_RANGE(scale), TRUE); gtk_box_pack_start(GTK_BOX(hbox), scale, FALSE, FALSE, 0);

gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); gtk_container_add(GTK_CONTAINER(window), vbox);

g_signal_connect(scale, “value-changed”, G_CALLBACK(cb_changed), cpu); g_signal_connect(window, “destroy”, G_CALLBACK(gtk_main_quit), NULL);

gtk_widget_show_all(window);

gtk_main();

return 0; }

This is the main file. We create the custom CPU widget and make it work with the GtkVScale widget.

gcc -Wall $(pkg-config –cflags gtk+-2.0) -o mycpu.o -c mycpu.c gcc -Wall $(pkg-config –cflags gtk+-2.0) -o main.o -c main.c gcc -o test_cpu main.o mycpu.o $(pkg-config –libs gtk+-2.0)

With these commands, we build the example.

customwidget.png

Figure: CPU widget

In this chapter we have created a custom GTK+ widget.

Contents Previous

ad ad