Практические советы по GTK+

  Все выпуски  

Практические советы по GTK+ (Создание нового виджета)


Создание нового виджета

Внимание! Все выпуски, включая этот, можно найти в одном файле gtk_book.zip

 

Попробуем создать свой собственный виджет, на примере виджета в виде текстовой ссылки  (вроде GtkLinkButton).

GtkLinkButton меня не устраивает по ряду причин, например обработчик нажатия общий для всех ссылок и при наведении на ссылку, появляются контуры кнопки. Поэтому пришлось сделать альтернативу.

Наш виджет назовём GtkLinkLabel и он будет выглядеть вот так: link1 При наведении на него мышкой, он будет менять цвет и курсор мыши будет меняться на другой (в виде руки).

Все функции нашего виджета GtkLinkLabel:

// Создать текстовую метку

GtkWidget* gtk_link_label_new (const gchar *str);

// сменить тестовую метку

void gtk_link_label_set_text(GtkLinkLabel *linklabel,const gchar *str);

// сменить размер шрифта

void gtk_link_label_set_size(GtkLinkLabel *linklabel,const gchar *str_size);

 

К виджету можно будет подключить обработчик нового сигнала "click_label", который посылается при нажатии на текстовую метку.

Это было полное описание нашего нового виджета. Рассмотрим пример его использования:

// создание виджета

GtkWidget *label_test = gtk_link_label_new(_("Ссылка1"));

// смена размера текста в виджета на более мелкий (не обязательно)

gtk_link_label_set_size(GTK_LINK_LABEL(label_test),"smaller");

// подключаем обработчик сигнала нажатия курсором мышки на наш виджет

g_signal_connect (G_OBJECT (label_test),"click_label",G_CALLBACK (click_label_test),NULL);

 

// обработчик сигнала нажатия курсором мышки на наш виджет

void  click_label_test(GtkLinkButton *button,gpointer user_data)

{

       printf("нажали на текстовую метку\n");

}

 

Теперь посмотрим исходный код, который состоит из двух файлов gtklinklabel.h и gtklinklabel.с. Виджет содержит совсем немного кода, в основном это стандартный каркас, который присутствует во всех виждетах.

Оба файла исходного кода виджета с подробными комментариями представлены ниже:

 

/*

* GtkLinkLabel widget for GTK+

*

* Файл gtklinklabel.h

*

*/

 

#ifndef __GTK_LINK_LABEL_H__

#define __GTK_LINK_LABEL_H__

 

#include <gdk/gdk.h>

#include <gtk/gtkhbox.h>

 

G_BEGIN_DECLS

 

// Стандартные макросы для класса

#define GTK_TYPE_LINK_LABEL            (gtk_link_label_get_type ())

#define GTK_LINK_LABEL(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_LINK_LABEL, GtkLinkLabel))

#define GTK_LINK_LABEL_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_LINK_LABEL, GtkLinkLabel))

#define GTK_IS_LINK_LABEL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_LINK_LABEL))

#define GTK_IS_LINK_LABEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_LINK_LABEL))

#define GTK_LINK_LABEL_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_LINK_LABEL, GtkLinkLabel))

 

 

typedef struct _GtkLinkLabel GtkLinkLabel;

typedef struct _GtkLinkLabelClass GtkLinkLabelClass;

 

// Для каждого экземпляра виджета создается структура объекта, предназначенаая для хранения индивидуальной информации этого экземпляра.

struct _GtkLinkLabel

{

 GtkEventBox linklabel;// родительский виджет

 

 GtkWidget *label;        // текстовая метка

 gint        state;                // состояние: 1 - курсор находится над текстовой меткой, 0 - нет

 gchar *size;                // размер текста:'smaller' или 'larger' и абсолютные значения: 'xx-small', 'x- small', 'small', 'medium', 'large', 'x-large', 'xx-large'

 gchar *str;                // непосредственно сам текст

};

 

struct _GtkLinkLabelClass

{

       GtkEventBoxClass parent_class;// родительский класс - текстовая метка

       // функция для обработчика сигнала "click_label"

       void (* click_label)  (GtkLinkLabel *linklabel);

};

 

 

GType gtk_link_label_get_type (void) G_GNUC_CONST;

 

 

// Создать текстовую метку

GtkWidget* gtk_link_label_new (const gchar *str);

// сменить тест

void gtk_link_label_set_text(GtkLinkLabel *linklabel,const gchar *str);

// сменить размер шрифта

// str_size - 'smaller' или 'larger' и абсолютные значения: 'xx-small', 'x- small', 'small', 'medium', 'large', 'x-large', 'xx-large'.

void gtk_link_label_set_size(GtkLinkLabel *linklabel,const gchar *str_size);

 

 

G_END_DECLS

 

#endif /* __GTK_LINK_LABEL_H__ */

 

 

 

 

 

 

/*

* GtkLinkLabel widget for GTK+

*

* Файл gtklinklabel.c

*

*/

#include <gtk/gtk.h>

#include "gtklinklabel.h"

 

static GtkWidgetClass *parent_class = NULL;

 

// инициализия структуры класса виджета и регистрация сигналов для этого класса

static void gtk_link_label_class_init (GtkLinkLabelClass *class);

// инициализация структуру объекта

static void gtk_link_label_init (GtkLinkLabel *linklabel);

// обработчик сигналов "enter-notify-event"(курсор мышки зашёл в область виджета) и "leave-notify-event"(курсор мышки вышёл из области виджета)

static void move_cursor(GtkWidget *widget,GdkEventCrossing *event,gpointer user_data);

// обработчик сигнала "button-press-event" - нажали мышкой на виджете

static void click_cursor(GtkWidget *widget,GdkEventButton *event,gpointer user_data);

// деструктор

static void gtk_link_label_unrealize(GtkWidget *object);

// обновить текстовую метку

static void gtk_link_label_update_text(GtkLinkLabel *linklabel);

 

 

// состояние текстовой метки - нормальное или выделенное

typedef enum

{

 GTK_LINK_LABEL_NORMAL  = 0,

 GTK_LINK_LABEL_ACTIVE  = 1

} GtkLinkLabelState;

 

// список сигналов виджета (только один сигнал)

enum {

 CLICK_LABEL_SIGNAL,

 LAST_SIGNAL

};

static guint link_label_signals[LAST_SIGNAL] = { 0 };

 

 

/*

Главной функцией для каждого объекта является функция xxx_get_type ().

При первом вызове она информирует библитеку о новом классе и получает

уникальный идентификатор созданного класса.

При всех последующих вызовах она возвращает только идентификатор ID.

*/

GType gtk_link_label_get_type (void)

{

       static GType date_type = 0;

 

       if (!date_type)

               {

                       static const GTypeInfo date_info =

                       {

                               sizeof (GtkLinkLabelClass), /* размер структуры класса */

                               NULL,                /* конструктор базового класса */

                               NULL,                /* деструктор базового класаа */

                               (GClassInitFunc) gtk_link_label_class_init,

                               NULL,        /* деструктор класса */

                               NULL,                /* данные конструкторов и деструктров класса */

                               sizeof (GtkLinkLabel),/* размер объекта класса */

                               0,                /* количество кешируемых в памяти объектов */

                               (GInstanceInitFunc) gtk_link_label_init,/* конструктор объекта */

                       };

 

                       date_type = g_type_register_static (GTK_TYPE_EVENT_BOX, "GtkLinkLabel", &date_info, 0);

               }

 

       return date_type;

}

 

// Функция xxx_class_init () инициализирует поля структуры класса виджета и регистирует сигналы для этого класса

static void gtk_link_label_class_init (GtkLinkLabelClass *class)

{

       GtkWidgetClass *widget_class;

 

       widget_class = (GtkWidgetClass*) class;

       parent_class = g_type_class_peek_parent (class);

 

       class->click_label = NULL;

       // задать деструктор класса (нужен только, чтобы удалить строку str)

       widget_class->unrealize = gtk_link_label_unrealize;

 

       // Наш виджет имеет только один сигнал - "click_label",

       // который посылается при нажатии на текстовую метку. Создание нового сигнала выполняется с помощью функции g_signal_new

       link_label_signals[CLICK_LABEL_SIGNAL] =

       // Функция g_signal_new() возвращает уникальный целочисленный идентификатор сигнала

       //который в нашем примере сохраняется в массиве link_label_signals

       g_signal_new("click_label",// название сигнала

                               // идентификатор типа объекта, распространяющего сигнал                        

                               G_OBJECT_CLASS_TYPE((GObjectClass*)class),                        

                               // флаг запуска     обработчика  сигнала,  устанавливает

                               // порядок   вызова   заданного   по   умолчанию   обработчика   сигнала

                               G_SIGNAL_RUN_FIRST,// обработчик сигнала по умолчанию выполняется до пользовательских обработчиков сигнала

                               // смещение в пределах структуры класса указателя на заданный по умолчанию обработчик

                               G_STRUCT_OFFSET (GtkLinkLabelClass, click_label),        

                               // accumulator — для большинства классов может быть установлен в NULL

                               NULL,

                               // accu_data - пользовательские данные, которые будут обрабатываться функцией accumulator

                               NULL,

                               // c_marshaller (сигнальщик) - функция вызова обработчика сигнала   по

                               // указателю с передачей ей аргументов.. Для обработчиков сигнала, аргументы которого

                               // представлены только объектом, породившем сигнал, и пользовательскими данными,

                               // МОЖНО использовать g_cclosure_marshal_VOID __ VOID;

                               g_cclosure_marshal_VOID__VOID,

                               // return_type - тип возвращаемого значения

                               G_TYPE_NONE,

                               // типы параметров

                               0);

}

 

/* Инициализация структуру объекта  - выполняется при создании каждого

экземпляра виджета GtkLinkLabel.

Обычно эта функция выполняет присваивание полям структуры объекта значений

по умолчанию. В нашем случае эта функция также создает компоненты виджета

*/

static void gtk_link_label_init (GtkLinkLabel *linklabel)

{

       // Параметры по умолчанию

       linklabel->str = NULL;// строка с текстовым полем, которое отображается в нашем виджете

       linklabel->size = NULL;// размер текста

       linklabel->state = GTK_LINK_LABEL_NORMAL;// текстовая метка не выделена

       // создаём единственный виджет внутри нашего нового виджета и упаковываем его в родительский виджет EventBox

       linklabel->label = gtk_label_new(NULL);

       gtk_container_add (GTK_CONTAINER (linklabel), linklabel->label);

       gtk_widget_show(linklabel->label);

       // разрешаем принимать нашему виджету требуемые сигналы

       gtk_widget_set_events (GTK_WIDGET(linklabel),GDK_ENTER_NOTIFY_MASK|GDK_LEAVE_NOTIFY_MASK);

       // подключение обработчиков сигналов

       g_signal_connect (G_OBJECT (linklabel),"button-press-event",G_CALLBACK (click_cursor),NULL);// нажали мышкой на виджете

       g_signal_connect (G_OBJECT (linklabel),"enter-notify-event",G_CALLBACK (move_cursor),NULL);// курсор мышки зашёл в область виджета

       g_signal_connect (G_OBJECT (linklabel),"leave-notify-event",G_CALLBACK (move_cursor),NULL);// курсор мышки вышёл из области виджета

}

 

// деструктор

static void gtk_link_label_unrealize(GtkWidget *object)

{

       GtkLinkLabel *linklabel;

       g_return_if_fail(object!=NULL);

       g_return_if_fail(GTK_IS_LINK_LABEL(object));

       linklabel = GTK_LINK_LABEL(object);

       // очистка выденной памяти при создании виджета

       if(linklabel->str)

               g_free(linklabel->str);

       if(linklabel->size)

               g_free(linklabel->size);

       // запуск деструктора родительского класса для очистки остатков виджета

       if(GTK_OBJECT_CLASS(parent_class)->destroy)

               (* GTK_OBJECT_CLASS(parent_class)->destroy)(GTK_OBJECT(object));

}

 

// обновить текстовую метку

static void gtk_link_label_update_text(GtkLinkLabel *linklabel)

{

       char *str;

       if(!linklabel->str)        

               return;

       // underline - подчёркивание (single-одной чертой)

       // foreground - цвет текста (в зависимости от linklabel->state)

       // size - размер шрифта текста

       str = g_strdup_printf("<span underline=\"single\" foreground=\"%s\" size=\"%s\">%s</span>",

               (linklabel->state==GTK_LINK_LABEL_ACTIVE)?"#b737ff":"#4737ff",// цвет метки в зависимости от нахождения над ней курсора

               (linklabel->size)?linklabel->size:"medium",// если размер шрифта не задан, то средний

               linklabel->str);

       gtk_label_set_markup(GTK_LABEL(linklabel->label),str);

       g_free(str);

}

 

// обработчик сигналов "enter-notify-event"(курсор мышки зашёл в область виджета) и "leave-notify-event"(курсор мышки вышёл из области виджета)

static void move_cursor(GtkWidget *widget,GdkEventCrossing *event,gpointer user_data)

{

       // зашли в поле виджета

       if(event->type==GDK_ENTER_NOTIFY)

       {

               static GdkCursor *hand_cursor = NULL;

               // создать курсор и применить - только один раз

               if(!hand_cursor)

               {        

                       hand_cursor = gdk_cursor_new (GDK_HAND2);

                       gdk_window_set_cursor(widget->window,hand_cursor);

               }

 

               GTK_LINK_LABEL(widget)->state = GTK_LINK_LABEL_ACTIVE;

       }

       // вышли из поля виджета

       if(event->type==GDK_LEAVE_NOTIFY)

               GTK_LINK_LABEL(widget)->state = GTK_LINK_LABEL_NORMAL;

       // обновить текстовую метку

       gtk_link_label_update_text(GTK_LINK_LABEL(widget));

}

 

// обработчик сигнала "button-press-event" - нажали мышкой на виджете

static void click_cursor(GtkWidget *widget,GdkEventButton *event,gpointer user_data)

{

       // послать сигнал CLICK_LABEL_SIGNAL

       if(event->type==GDK_BUTTON_PRESS)

               g_signal_emit (GTK_LINK_LABEL(widget), link_label_signals[CLICK_LABEL_SIGNAL], 0);

}

 

// создать новый виджет

// str - новая текстовая строка

GtkWidget* gtk_link_label_new(const gchar *str)

{

       // создать экземпляр нашего виджета

       GtkWidget *w = GTK_WIDGET (g_object_new (GTK_TYPE_LINK_LABEL, NULL));

       // запоминаем текстовую метку в структуре виджета

       if(str)

               GTK_LINK_LABEL(w)->str = g_strdup(str);

       // обновить текстовую метку

       gtk_link_label_update_text(GTK_LINK_LABEL(w));// обновить текстовую метку

       return w;

}

 

// сменить размер шрифта

// 'smaller' или 'larger' и абсолютные значения: 'xx-small', 'x- small', 'small', 'medium', 'large', 'x-large', 'xx-large'.

void gtk_link_label_set_size(GtkLinkLabel *linklabel,const gchar *str_size)

{

       // удаляем старый размер шрифта

       if(linklabel->size)

               free(linklabel->size);

       // запоминаем новый размер шрифта в структуре виджета

       linklabel->size = g_strdup(str_size);

       gtk_link_label_update_text(linklabel);

}

 

// сменить тестовую метку

void gtk_link_label_set_text(GtkLinkLabel *linklabel,const gchar *str)

{

       // удаляем старую текстовую метку

       if(linklabel->str)

               g_free(linklabel->str);

       // запоминаем новую текстовую метку в структуре виджета

       linklabel->str = g_strdup(str);

       gtk_link_label_update_text(linklabel);

}

 


В избранное