Monday, April 15, 2013

GTK+中的通用构件(Custom GTK+ widget)

GTK+中的通用构件(Custom GTK+ widget)

在本章节,也就是本教程最后一章啦!我们将向大家战士如何去自己DIY一个通用的构件。在内容里我们还将用到Cairo 图形函数工具库

CPU widget

在接下来的示例中我们将一步一步地制作一个“CPU 构件”。
/* cpu.h */

#ifndef __CPU_H
#define __CPU_H

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

G_BEGIN_DECLS


#define GTK_CPU(obj) GTK_CHECK_CAST(obj, gtk_cpu_get_type (), GtkCpu)
#define GTK_CPU_CLASS(klass) GTK_CHECK_CLASS_CAST(klass, gtk_cpu_get_type(), GtkCpuClass)
#define GTK_IS_CPU(obj) GTK_CHECK_TYPE(obj, gtk_cpu_get_type())


typedef struct _GtkCpu GtkCpu;
typedef struct _GtkCpuClass GtkCpuClass;


struct _GtkCpu {
GtkWidget widget;

gint sel;
};

struct _GtkCpuClass {
GtkWidgetClass parent_class;
};


GtkType gtk_cpu_get_type(void);
void gtk_cpu_set_sel(GtkCpu *cpu, gint sel);
GtkWidget * gtk_cpu_new();


G_END_DECLS

#endif /* __CPU_H */
/* cpu.c */

#include "cpu.h"


static void gtk_cpu_class_init(GtkCpuClass *klass);
static void gtk_cpu_init(GtkCpu *cpu);
static void gtk_cpu_size_request(GtkWidget *widget,
GtkRequisition *requisition);
static void gtk_cpu_size_allocate(GtkWidget *widget,
GtkAllocation *allocation);
static void gtk_cpu_realize(GtkWidget *widget);
static gboolean gtk_cpu_expose(GtkWidget *widget,
GdkEventExpose *event);
static void gtk_cpu_paint(GtkWidget *widget);
static void gtk_cpu_destroy(GtkObject *object);


GtkType
gtk_cpu_get_type(void)
{
static GtkType gtk_cpu_type = 0;


if (!gtk_cpu_type) {
static const GtkTypeInfo gtk_cpu_info = {
"GtkCpu",
sizeof(GtkCpu),
sizeof(GtkCpuClass),
(GtkClassInitFunc) gtk_cpu_class_init,
(GtkObjectInitFunc) gtk_cpu_init,
NULL,
NULL,
(GtkClassInitFunc) NULL
};
gtk_cpu_type = gtk_type_unique(GTK_TYPE_WIDGET, &gtk_cpu_info);
}


return gtk_cpu_type;
}

void
gtk_cpu_set_state(GtkCpu *cpu, gint num)
{
cpu->sel = num;
gtk_cpu_paint(GTK_WIDGET(cpu));
}


GtkWidget * gtk_cpu_new()
{
return GTK_WIDGET(gtk_type_new(gtk_cpu_get_type()));
}


static void
gtk_cpu_class_init(GtkCpuClass *klass)
{
GtkWidgetClass *widget_class;
GtkObjectClass *object_class;


widget_class = (GtkWidgetClass *) klass;
object_class = (GtkObjectClass *) klass;

widget_class->realize = gtk_cpu_realize;
widget_class->size_request = gtk_cpu_size_request;
widget_class->size_allocate = gtk_cpu_size_allocate;
widget_class->expose_event = gtk_cpu_expose;

object_class->destroy = gtk_cpu_destroy;
}


static void
gtk_cpu_init(GtkCpu *cpu)
{
cpu->sel = 0;
}


static void
gtk_cpu_size_request(GtkWidget *widget,
GtkRequisition *requisition)
{
g_return_if_fail(widget != NULL);
g_return_if_fail(GTK_IS_CPU(widget));
g_return_if_fail(requisition != NULL);

requisition->width = 80;
requisition->height = 100;
}


static void
gtk_cpu_size_allocate(GtkWidget *widget,
GtkAllocation *allocation)
{
g_return_if_fail(widget != NULL);
g_return_if_fail(GTK_IS_CPU(widget));
g_return_if_fail(allocation != NULL);

widget->allocation = *allocation;

if (GTK_WIDGET_REALIZED(widget)) {
gdk_window_move_resize(
widget->window,
allocation->x, allocation->y,
allocation->width, allocation->height
);
}
}


static void
gtk_cpu_realize(GtkWidget *widget)
{
GdkWindowAttr attributes;
guint attributes_mask;

g_return_if_fail(widget != NULL);
g_return_if_fail(GTK_IS_CPU(widget));

GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED);

attributes.window_type = GDK_WINDOW_CHILD;
attributes.x = widget->allocation.x;
attributes.y = widget->allocation.y;
attributes.width = 80;
attributes.height = 100;

attributes.wclass = GDK_INPUT_OUTPUT;
attributes.event_mask = gtk_widget_get_events(widget) | GDK_EXPOSURE_MASK;

attributes_mask = GDK_WA_X | GDK_WA_Y;

widget->window = gdk_window_new(
gtk_widget_get_parent_window (widget),
& attributes, attributes_mask
);

gdk_window_set_user_data(widget->window, widget);

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


static gboolean
gtk_cpu_expose(GtkWidget *widget,
GdkEventExpose *event)
{
g_return_val_if_fail(widget != NULL, FALSE);
g_return_val_if_fail(GTK_IS_CPU(widget), FALSE);
g_return_val_if_fail(event != NULL, FALSE);

gtk_cpu_paint(widget);

return FALSE;
}


static void
gtk_cpu_paint(GtkWidget *widget)
{
cairo_t *cr;

cr = gdk_cairo_create(widget->window);

cairo_translate(cr, 0, 7);

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

gint pos = GTK_CPU(widget)->sel;
gint rect = pos / 5;

cairo_set_source_rgb(cr, 0.2, 0.4, 0);

gint i;
for ( i = 1; i <= 20; i++) {
if (i > 20 - rect) {
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);
}


static void
gtk_cpu_destroy(GtkObject *object)
{
GtkCpu *cpu;
GtkCpuClass *klass;

g_return_if_fail(object != NULL);
g_return_if_fail(GTK_IS_CPU(object));

cpu = GTK_CPU(object);

klass = gtk_type_class(gtk_widget_get_type());

if (GTK_OBJECT_CLASS(klass)->destroy) {
(* GTK_OBJECT_CLASS(klass)->destroy) (object);
}
}
/* main.c */

#include "cpu.h"


static void set_value(GtkWidget * widget, gpointer data)
{
GdkRegion *region;

GtkRange *range = (GtkRange *) widget;
GtkWidget *cpu = (GtkWidget *) data;
GTK_CPU(cpu)->sel = gtk_range_get_value(range);

region = gdk_drawable_get_clip_region(cpu->window);
gdk_window_invalidate_region(cpu->window, region, TRUE);
gdk_window_process_updates(cpu->window, TRUE);
}


int main (int argc, char ** argv)
{
GtkWidget *window;
GtkWidget *cpu;
GtkWidget *fixed;
GtkWidget *scale;

gtk_init(&argc, &argv);


window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(window), "CPU widget");
gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
gtk_window_set_default_size(GTK_WINDOW(window), 200, 180);


g_signal_connect(G_OBJECT(window), "destroy",
G_CALLBACK(gtk_main_quit), NULL);

fixed = gtk_fixed_new();
gtk_container_add(GTK_CONTAINER(window), fixed);

cpu = gtk_cpu_new();
gtk_fixed_put(GTK_FIXED(fixed), cpu, 30, 40);


scale = gtk_vscale_new_with_range(0.0, 100.0, 1.0);
gtk_range_set_inverted(GTK_RANGE(scale), TRUE);
gtk_scale_set_value_pos(GTK_SCALE(scale), GTK_POS_TOP);
gtk_widget_set_size_request(scale, 50, 120);
gtk_fixed_put(GTK_FIXED(fixed), scale, 130, 20);

g_signal_connect(G_OBJECT(scale), "value_changed", G_CALLBACK(set_value), (gpointer) cpu);

gtk_widget_show(cpu);
gtk_widget_show(fixed);
gtk_widget_show_all(window);
gtk_main();

return 0;
}
你可以看到上面的完整代码,其实在这里我们所制作的 CPU 构件也是属于一种GtkWidget, 我们利用了Cairo API 来绘制我们所需要的效果。我们绘制了一个黑色的背景还有40个小的长方形。这个小的长方形被用两种颜色进行绘制:深绿色和翠绿色。 GtkVScale 构件用来控制翠绿色的长方形框在构件上绘制的个数。
这个示例第一眼看起来或许还满复杂的。-_-!!但是说实话,并不是想象中的那么复杂。大多数的代码都是样板代码,当我们要尝试去制作了一新的构件的时候,很多代码都是重复的。
绘制图形的工作都会在功能函数gtk_cpu_paint()中进行。
  cairo_t *cr;

cr = gdk_cairo_create(widget->window);

cairo_translate(cr, 0, 7);

cairo_set_source_rgb(cr, 0, 0, 0);
cairo_paint(cr);
按照惯例,我们生成了一个cairo context。我们还设置了大小。并在接下来还为这个构件设置了背景色——黑色。
 gint pos = GTK_CPU(widget)->sel;
gint rect = pos / 5;
这里我们用到了变量sel 中的数字。 她其实是从滑块构件(scale widget)中的当前位置获得的。那个滑块共有100个数字。我们通过滑块当前的位置所对应数字值换算出要有多少个翠绿色的小长方形,然后我们进行绘制操作。
 gint i;
for ( i = 1; i <= 20; i++) {
if (i > 20 - rect) {
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);
}
依据我们所需要的“跨度”数据。我们绘制了一共40个小长方形包括深绿色和翠绿色在内。值得醒一下的是,我们画长方形的顺序,在GTK+系统中是从上往下来的,所以换算时要小心。
 GtkRange *range = (GtkRange *) widget;
GtkWidget *cpu = (GtkWidget *) data;
GTK_CPU(cpu)->sel = gtk_range_get_value(range);
在函数 set_value() 中,我们把滑块的当前值传递给CPU 构件。
 GdkRegion *region;
...
region = gdk_drawable_get_clip_region(cpu->window);
gdk_window_invalidate_region(cpu->window, region, TRUE);
gdk_window_process_updates(cpu->window, TRUE);
上面的这段代码致使CPU所在的构件窗口失效,并进行自我刷新的操作;这样我们就可以实现动态的效果啦`~~

cpu widget
Figure: CPU widget

No comments:

Post a Comment