Next >>> | ||
Tutorial Availability |
GTK+ 2.0 Tutorial |
||
---|---|---|
Поскольку GTK - объектно ориентированный набор виджетов, то он имеет иерархию наследования. Этот механизм наследования применяет сигналы. Поэтому, вы должны обращаться к дереву иерархии виджета, используя сигналы, перечисленные в этом разделе.
void GtkObject::destroy (GtkObject *, gpointer); |
Tutorial Copyright and Permissions Notice |
|
GtkWidget |
GTK+ 2.0 Tutorial |
||
---|---|---|
Следующие типы данных передают в обработчики событий GTK+. Для каждого перечисленного типа данных, перечислены сигналы, которые используют этот тип данных.
Тип данных GdkEventType - специальный тип данных, который используется всеми другими типами данных как индикатор типа данных, который передают в обработчик сигнала. Как вы увидите ниже, каждая из структур данных события имеет члена этого типа. Это определено как перечисление типов следующим образом:
typedef enum { GDK_NOTHING = -1, GDK_DELETE = 0, GDK_DESTROY = 1, GDK_EXPOSE = 2, GDK_MOTION_NOTIFY = 3, GDK_BUTTON_PRESS = 4, GDK_2BUTTON_PRESS = 5, GDK_3BUTTON_PRESS = 6, GDK_BUTTON_RELEASE = 7, GDK_KEY_PRESS = 8, GDK_KEY_RELEASE = 9, GDK_ENTER_NOTIFY = 10, GDK_LEAVE_NOTIFY = 11, GDK_FOCUS_CHANGE = 12, GDK_CONFIGURE = 13, GDK_MAP = 14, GDK_UNMAP = 15, GDK_PROPERTY_NOTIFY = 16, GDK_SELECTION_CLEAR = 17, GDK_SELECTION_REQUEST = 18, GDK_SELECTION_NOTIFY = 19, GDK_PROXIMITY_IN = 20, GDK_PROXIMITY_OUT = 21, GDK_DRAG_BEGIN = 22, GDK_DRAG_REQUEST = 23, GDK_DROP_ENTER = 24, GDK_DROP_LEAVE = 25, GDK_DROP_DATA_AVAIL = 26, GDK_CLIENT_EVENT = 27, GDK_VISIBILITY_NOTIFY = 28, GDK_NO_EXPOSE = 29, GDK_OTHER_EVENT = 9999 /* Не рекомендуемый, используйте вместо этого фильтры */ } GdkEventType; |
Другой тип события отличается от этих - GdkEvent. Это объединение всех других типов данных, которое позволяет быть приведенным к определенному типу данных события в пределах обработчика сигнала.
Типы данных события определены следующим образом:
struct _GdkEventAny { GdkEventType type; GdkWindow *window; gint8 send_event; }; struct _GdkEventExpose { GdkEventType type; GdkWindow *window; gint8 send_event; GdkRectangle area; gint count; /* Если ненулевое, следует больше событий. */ }; struct _GdkEventNoExpose { GdkEventType type; GdkWindow *window; gint8 send_event; /* XXX: любой нуждается в X major_code или minor_code областях? */ }; struct _GdkEventVisibility { GdkEventType type; GdkWindow *window; gint8 send_event; GdkVisibilityState state; }; struct _GdkEventMotion { GdkEventType type; GdkWindow *window; gint8 send_event; guint32 time; gdouble x; gdouble y; gdouble pressure; gdouble xtilt; gdouble ytilt; guint state; gint16 is_hint; GdkInputSource source; guint32 deviceid; gdouble x_root, y_root; }; struct _GdkEventButton { GdkEventType type; GdkWindow *window; gint8 send_event; guint32 time; gdouble x; gdouble y; gdouble pressure; gdouble xtilt; gdouble ytilt; guint state; guint button; GdkInputSource source; guint32 deviceid; gdouble x_root, y_root; }; struct _GdkEventKey { GdkEventType type; GdkWindow *window; gint8 send_event; guint32 time; guint state; guint keyval; gint length; gchar *string; }; struct _GdkEventCrossing { GdkEventType type; GdkWindow *window; gint8 send_event; GdkWindow *subwindow; GdkNotifyType detail; }; struct _GdkEventFocus { GdkEventType type; GdkWindow *window; gint8 send_event; gint16 in; }; struct _GdkEventConfigure { GdkEventType type; GdkWindow *window; gint8 send_event; gint16 x, y; gint16 width; gint16 height; }; struct _GdkEventProperty { GdkEventType type; GdkWindow *window; gint8 send_event; GdkAtom atom; guint32 time; guint state; }; struct _GdkEventSelection { GdkEventType type; GdkWindow *window; gint8 send_event; GdkAtom selection; GdkAtom target; GdkAtom property; guint32 requestor; guint32 time; }; /* Этот тип события используется довольно редко. Это важно только для XInput програм которые рисуют свой курсор */ struct _GdkEventProximity { GdkEventType type; GdkWindow *window; gint8 send_event; guint32 time; GdkInputSource source; guint32 deviceid; }; struct _GdkEventDragRequest { GdkEventType type; GdkWindow *window; gint8 send_event; guint32 requestor; union { struct { guint protocol_version:4; guint sendreply:1; guint willaccept:1; guint delete_data:1; /* Do *not* delete if link is sent, only if data is sent */ guint senddata:1; guint reserved:22; } flags; glong allflags; } u; guint8 isdrop; /* This gdk event can be generated by a couple of X events - this lets the app know whether the drop really occurred or we just set the data */ GdkPoint drop_coords; gchar *data_type; guint32 timestamp; }; struct _GdkEventDragBegin { GdkEventType type; GdkWindow *window; gint8 send_event; union { struct { guint protocol_version:4; guint reserved:28; } flags; glong allflags; } u; }; struct _GdkEventDropEnter { GdkEventType type; GdkWindow *window; gint8 send_event; guint32 requestor; union { struct { guint protocol_version:4; guint sendreply:1; guint extended_typelist:1; guint reserved:26; } flags; glong allflags; } u; }; struct _GdkEventDropLeave { GdkEventType type; GdkWindow *window; gint8 send_event; guint32 requestor; union { struct { guint protocol_version:4; guint reserved:28; } flags; glong allflags; } u; }; struct _GdkEventDropDataAvailable { GdkEventType type; GdkWindow *window; gint8 send_event; guint32 requestor; union { struct { guint protocol_version:4; guint isdrop:1; guint reserved:25; } flags; glong allflags; } u; gchar *data_type; /* MIME type */ gulong data_numbytes; gpointer data; guint32 timestamp; GdkPoint coords; }; struct _GdkEventClient { GdkEventType type; GdkWindow *window; gint8 send_event; GdkAtom message_type; gushort data_format; union { char b[20]; short s[10]; long l[5]; } data; }; struct _GdkEventOther { GdkEventType type; GdkWindow *window; gint8 send_event; GdkXEvent *xevent; }; |
GtkAdjustment |
|
Code Examples |
GTK+ 2.0 Tutorial |
||
---|---|---|
Ниже приведены примеры кода использованные в данном руководстве, которые не были полностью включены в другом месте.
/* GTK - The GIMP Toolkit * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #ifndef __TICTACTOE_H__ #define __TICTACTOE_H__ #include <gdk/gdk.h> #include <gtk/gtkvbox.h> #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ #define TICTACTOE(obj) GTK_CHECK_CAST (obj, tictactoe_get_type (), Tictactoe) #define TICTACTOE_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, tictactoe_get_type (), TictactoeClass) #define IS_TICTACTOE(obj) GTK_CHECK_TYPE (obj, tictactoe_get_type ()) typedef struct _Tictactoe Tictactoe; typedef struct _TictactoeClass TictactoeClass; struct _Tictactoe { GtkVBox vbox; GtkWidget *buttons[3][3]; }; struct _TictactoeClass { GtkVBoxClass parent_class; void (* tictactoe) (Tictactoe *ttt); }; GtkType tictactoe_get_type (void); GtkWidget* tictactoe_new (void); void tictactoe_clear (Tictactoe *ttt); #ifdef __cplusplus } #endif /* __cplusplus */ #endif /* __TICTACTOE_H__ */ |
/* GTK - The GIMP Toolkit * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include "gtk/gtksignal.h" #include "gtk/gtktable.h" #include "gtk/gtktogglebutton.h" #include "tictactoe.h" enum { TICTACTOE_SIGNAL, LAST_SIGNAL }; static void tictactoe_class_init (TictactoeClass *klass); static void tictactoe_init (Tictactoe *ttt); static void tictactoe_toggle (GtkWidget *widget, Tictactoe *ttt); static gint tictactoe_signals[LAST_SIGNAL] = { 0 }; GType tictactoe_get_type () { static GType ttt_type = 0; if (!ttt_type) { static const GTypeInfo ttt_info = { sizeof (TictactoeClass), NULL, NULL, (GClassInitFunc) tictactoe_class_init, NULL, NULL, sizeof (Tictactoe), 0, (GInstanceInitFunc) tictactoe_init, }; ttt_type = g_type_register_static (GTK_TYPE_VBOX, "Tictactoe", &ttt_info, 0); } return ttt_type; } static void tictactoe_class_init (TictactoeClass *class) { GtkObjectClass *object_class; object_class = (GtkObjectClass*) class; tictactoe_signals[TICTACTOE_SIGNAL] = g_signal_new ("tictactoe", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_FIRST, 0, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0, NULL); class->tictactoe = NULL; } static void tictactoe_init (Tictactoe *ttt) { GtkWidget *table; gint i,j; table = gtk_table_new (3, 3, TRUE); gtk_container_add (GTK_CONTAINER (ttt), table); gtk_widget_show (table); for (i = 0; i < 3; i++) for (j = 0; j < 3; j++) { ttt->buttons[i][j] = gtk_toggle_button_new (); i, i+1, j, j+1); g_signal_connect (G_OBJECT (ttt->buttons[i][j]), "toggled", G_CALLBACK (tictactoe_toggle), (gpointer) ttt); gtk_widget_set_size_request (ttt->buttons[i][j], 20, 20); gtk_widget_show (ttt->buttons[i][j]); } } GtkWidget* tictactoe_new () { return GTK_WIDGET (g_object_new (tictactoe_get_type (), NULL)); } tictactoe_clear (Tictactoe *ttt) { int i,j; for (i = 0; i < 3; i++) for (j = 0; j < 3; j++) { NULL, ttt); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ttt->buttons[i][j]), FALSE); NULL, ttt); } } static void tictactoe_toggle (GtkWidget *widget, Tictactoe *ttt) { int i,k; static int rwins[8][3] = { { 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 }, { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 } }; static int cwins[8][3] = { { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 }, { 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 }, { 0, 1, 2 }, { 2, 1, 0 } }; int success, found; for (k = 0; k < 8; k++) { success = TRUE; found = FALSE; for (i = 0; i < 3; i++) { GTK_TOGGLE_BUTTON (ttt->buttons[rwins[k][i]][cwins[k][i]])->active; found = found || ttt->buttons[rwins[k][i]][cwins[k][i]] == widget; } if (success && found) { tictactoe_signals[TICTACTOE_SIGNAL], 0); break; } } } |
#include <stdlib.h> #include <gtk/gtk.h> #include "tictactoe.h" void win( GtkWidget *widget, gpointer data ) { g_print ("Yay!\n"); tictactoe_clear (TICTACTOE (widget)); } int main( int argc, char *argv[] ) { GtkWidget *window; GtkWidget *ttt; gtk_init (&argc, &argv); window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_window_set_title (GTK_WINDOW (window), "Aspect Frame"); g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (exit), NULL); gtk_container_set_border_width (GTK_CONTAINER (window), 10); ttt = tictactoe_new (); gtk_container_add (GTK_CONTAINER (window), ttt); gtk_widget_show (ttt); g_signal_connect (G_OBJECT (ttt), "tictactoe", G_CALLBACK (win), NULL); gtk_widget_show (window); gtk_main (); return 0; } |
GDK Event Types |
|
GtkDial |
|
|
|
|
|
Tutorial Availability |
GTK+ 2.0 Tutorial |
||
---|---|---|
Некоторые виджеты GTK не имеют собственных окон (X windows), поэтому используют родительские. Из-за этого, они не могут получать события и в результате могут иметь неправильный размер и т.д. Если вы хотите большего от этих виджетов, EventBox - для вас.
На первый взгляд виджет EventBox кажется бесполезным. Он не создает ничего на экране и не отвечает ни на какие события. Однако, он выполняет функцию обеспечения дочернего виджета окном. Это важно потому, что многие виджеты GTK не имеют ассоциированных окон (X window). Отсутствие окна экономит память и улучшает работу, но и имеет некоторые недостатки. Виджет без окна X не может получить события и не имеет привязки к его содержимому. Хотя название EventBox подчеркивает его функцию обработки событий (event-handling), его можно также использовать для прерывания (подробнее смотрите пример ниже).
Создание нового виджета EventBox:
GtkWidget *gtk_event_box_new( void ); |
Добавляем в виджет EventBox дочерний виджет:
gtk_container_add (GTK_CONTAINER (event_box), child_widget); |
Следующий пример демонстрирует обе возможности EventBox - в нём создаётся длинный ярлык прикрепленный к маленькому контейнеру, при нажатии кнопкой мыши на ярлыке происходит выход из программы. Изменение размера окна раскрывает полное содержимое ярлыка.
#include <stdlib.h> #include <gtk/gtk.h> int main( int argc, char *argv[] ) { GtkWidget *window; GtkWidget *event_box; GtkWidget *label; gtk_init (&argc, &argv); window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_window_set_title (GTK_WINDOW (window), "Event Box"); g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (exit), NULL); gtk_container_set_border_width (GTK_CONTAINER (window), 10); /* Создаём контейнер события и добавляем его в основное окно */ event_box = gtk_event_box_new (); gtk_container_add (GTK_CONTAINER (window), event_box); gtk_widget_show (event_box); /* Создаём длинный ярлык */ label = gtk_label_new ("Click here to quit, quit, quit, quit, quit"); gtk_container_add (GTK_CONTAINER (event_box), label); gtk_widget_show (label); /* Делаем усечение */ gtk_widget_set_size_request (label, 110, 20); /* И связываем с этим действие */ gtk_widget_set_events (event_box, GDK_BUTTON_PRESS_MASK); g_signal_connect (G_OBJECT (event_box), "button_press_event", G_CALLBACK (exit), NULL); gtk_widget_realize (event_box); gdk_window_set_cursor (event_box->window, gdk_cursor_new (GDK_HAND1)); gtk_widget_show (window); gtk_main (); return 0; } |
File Selections |
|
The Alignment widget |
GTK+ 2.0 Tutorial |
||
---|---|---|
Есть два способа создать меню - легкий и сложный. Оба способа могут использоваться, но обычно используется Itemfactory (лёгкий путь). "Сложный" путь состоит в том, чтобы создать все меню, используя вызовы непосредственно. "Лёгкий" путь заключается в том, что используется вызов gtk_item_factory. В каждом подходе есть свои преимущества и недостатки.
Itemfactory («фабрика» пунктов меню) простой способ создания и добавления пунктов в меню, при создании нескольких функций оболочек для создания меню. Используя ручной метод можно пойти длинным путём для удобства и простоты использования. С Itemfactory, не возможно добавить изображения или символ '/' в меню.
Сначала мы рассмотрим сложный путь создания меню.
Есть три виджета, которые входят в создание строки меню (menubar) и суб меню (submenus):
Всё немного усложнено тем фактом, что виджеты пункта меню используются для двух различных целей. Они являются виджетами упакованными в меню и виджетами упакованными в строку меню, которая при выборе активизирует встроенное меню.
Рассмотрим функции для создания меню и строк меню. Первая функция используется для создания нового меню.
GtkWidget *gtk_menu_bar_new( void ); |
Можно использовать gtk_container_add() для упаковки в окно, или box_pack функцию для упаковки в контейнер - как некоторые кнопки.
GtkWidget *gtk_menu_new( void ); |
Эта функция возвращает указатель на вновь созданное меню; оно никогда не показывается (gtk_widget_show()), а служит контейнером для пунктов меню. Я надеюсь что это прояснит ниже приведённый пример.
Следующие три запроса используются, чтобы создать пункты меню, которые упакованы в меню и строки меню.
GtkWidget *gtk_menu_item_new( void ); GtkWidget *gtk_menu_item_new_with_label( const char *label ); GtkWidget *gtk_menu_item_new_with_mnemnonic( const char *label ); |
Эти вызовы функций используются для создания пунктов меню, которые должны быть показаны. Помните о различиях между "меню" созданного с использованием функции gtk_menu_new() и "пунктом меню" созданным с использованием функции gtk_menu_item_new(). Пункт меню является фактически кнопкой со связанным действием, а меню фактически является контейнером для пунктов меню.
Функции gtk_menu_item_new_with_label() и gtk_menu_item_new() создают ярлык для пункта меню и новый пункт меню соответственно, причём ярлык естественно упаковывается в пункт меню.
Как только пункт меню создан, его необходимо поместить в меню. Это делается с помощью функции gtk_menu_shelll_append. Для перехвата события "выбор пользователя", нужно пункт меню соединить с сигналом activate. Вот пример создания меню "файл" со стандартными пунктами открыть (Open), сохранить (Save), и выход (Quit):
file_menu = gtk_menu_new (); /* меню не нужно отображать */ /* Создаём новые пункты меню */ open_item = gtk_menu_item_new_with_label ("Open"); save_item = gtk_menu_item_new_with_label ("Save"); quit_item = gtk_menu_item_new_with_label ("Quit"); /* Добавляем их в само меню */ gtk_menu_shell_append (GTK_MENU_SHELL (file_menu), open_item); gtk_menu_shell_append (GTK_MENU_SHELL (file_menu), save_item); gtk_menu_shell_append (GTK_MENU_SHELL (file_menu), quit_item); /* присоединяем функцию к сигналу выбора */ g_signal_connect_swapped (G_OBJECT (open_item), "activate", G_CALLBACK (menuitem_response), (gpointer) "file.open"); g_signal_connect_swapped (G_OBJECT (save_item), "activate", G_CALLBACK (menuitem_response), (gpointer) "file.save"); /* прикрепляем пункт меню Quit к функции выхода */ g_signal_connect_swapped (G_OBJECT (quit_item), "activate", G_CALLBACK (destroy), (gpointer) "file.quit"); /* отображаем пункты меню */ gtk_widget_show (open_item); gtk_widget_show (save_item); gtk_widget_show (quit_item); |
Это наше меню. Теперь мы должны создать строку меню и пункт меню для входа «файл», к которому мы добавляем наше меню. Код примерно такой:
menu_bar = gtk_menu_bar_new (); gtk_container_add (GTK_CONTAINER (window), menu_bar); gtk_widget_show (menu_bar); file_item = gtk_menu_item_new_with_label ("File"); gtk_widget_show (file_item); |
Теперь нужно ассоциировать меню с file_item (пунктом «файл»). Это делает функция:
void gtk_menu_item_set_submenu( GtkMenuItem *menu_item, GtkWidget *submenu ); |
gtk_menu_item_set_submenu (GTK_MENU_ITEM (file_item), file_menu); |
void gtk_menu_bar_append( GtkMenuBar *menu_bar, GtkWidget *menu_item ); |
gtk_menu_bar_append (GTK_MENU_BAR (menu_bar), file_item); |
Если нам нужно меню справа, такое например как "помощь", то мы можем использовать следующую функцию (как и в текущем примере file_item ) после прикрепления к панели меню.
void gtk_menu_item_right_justify( GtkMenuItem *menu_item ); |
Вот список шагов для создания панели меню с прикрепленным пунктом панели меню.
Многократный вызов функции gtk_menu_item_new() для создания пунктов нового меню и использование gtk_menu_shell_append() для помещения пунктов в само меню.
Создание пункта корневого меню используя gtk_menu_item_new() размещенного в виде текста непосредственно на панели меню.
Используем gtk_menu_item_set_submenu() для прикрепления меню к корневому пункту меню (созданного шагом выше).
Создаём новую панель меню используя gtk_menu_bar_new. Этот шаг необходимо выполнить один раз для создания ряда меню на одной панели меню.
Используем gtk_menu_bar_append() для размещения корневого меню на панели меню.
Создание всплывающего меню - почти то же самое. Различие заключается в то, что меню не размещается автоматически ("automatically") в панели меню (menubar), а явно с помощью вызова функции gtk_menu_popup() при событии "нажатая кнопка". Вот шаги выполнения:
Создаём функцию обработки события. Она должна иметь прототип
static gint handler (GtkWidget *widget, GdkEvent *event); |
и использоваться для появления меню в момент возникновения события.
В обработчике события, если событие это - нажатие кнопки мыши, трактуется как (button event (which it is)) и используется как показано в образце кода для передачи информации в gtk_menu_popup().
Связываем обработчик события с виджетом
g_signal_connect_swapped (G_OBJECT (widget), "event", G_CALLBACK (handler), G_OBJECT (menu)); |
где widget это связываемый с обработчиком события виджет, handler - это обработчик события, а menu - меню созданное с помощью gtk_menu_new(). Это также может быть меню, которое размещено на панели меню, как показано в типовом коде.
Notebooks |
|
Manual Menu Example |
GTK+ 2.0 Tutorial |
||
---|---|---|
Копия этого руководства распространяется с исходными текстами GTK+ в формате SGML и HTML. В случае бинарных поставок консультируйтесь у поставщиков. А также копия руководства доступна в интернет по адресу www.gtk.org/tutorial.
Версия данного руководства в разных форматах находится в упакованном виде на ftp://ftp.gtk.org/pub/gtk/tutorial. Это наилучший вариант для тех кто желает иметь локальную копию руководства или распечатать его на бумаге.
GTK+ 2.0 Tutorial |
|
Introduction |
GTK+ 2.0 Tutorial |
||
---|---|---|
Требуются авторы для описания работы всех виджетов перечисленных в этом разделе! :) Пожалуйста окажите помощь в дополнении и расширении данного руководства.
Если вам нужно использовать какиенибудь из недокументированных виджетов, то обратитесь к заголовочным файлам поставляемым вместе с дистрибутивом GTK. Имена функций GTK's очень описательные. Как работает виджет можно понять по его декларации и используя примеры похожих виджетов.
Когда вы хорошо разберётесь в работе недокументированного виджета и его функциях, рассмотрите возможность написать раздел в данное руководство, так вы окажете помощь другим людям и сэкономите их время.
Item Factory Example |
|
Option Menu |
GTK+ 2.0 Tutorial |
||
---|---|---|
Здесь описаны функции используемые в работе с виджетами. Они могут использоваться, чтобы установить стиль, дополнение, размер и т.д.
(Возможно, нужно сделать отдельный раздел по акселераторам)
void gtk_widget_activate( GtkWidget *widget ); void gtk_widget_set_name( GtkWidget *widget, gchar *name ); gchar *gtk_widget_get_name( GtkWidget *widget ); void gtk_widget_set_sensitive( GtkWidget *widget, gboolean sensitive ); void gtk_widget_set_style( GtkWidget *widget, GtkStyle *style ); GtkStyle *gtk_widget_get_style( GtkWidget *widget ); GtkStyle *gtk_widget_get_default_style( void ); void gtk_widget_set_size_request ( GtkWidget *widget, gint width, gint height ); void gtk_widget_grab_focus( GtkWidget *widget ); void gtk_widget_show( GtkWidget *widget ); void gtk_widget_hide( GtkWidget *widget ); |
Text View |
|
Timeouts, IO and Idle Functions |
GTK+ 2.0 Tutorial |
||
---|---|---|
Вас наверное интересует как GTK выполняет работу в gtk_main. Для этого есть несколько опций. Используя следующую функцию Вы можете создать функцию времени ожидания, которая будет вызываться каждую миле секунду (milliseconds).
gint gtk_timeout_add( guint32 interval, GtkFunction function, gpointer data ); |
Первый аргумент кол-во миле секунд между вызовами вашей функции. Второй аргумент - ваша функция которую вы хотите вызывать, а третий - данные передаваемые в функцию обратного вызова. Возвращаемое значение - целочисленное "tag", которое может использоваться, чтобы остановить время ожидания, вызвав:
void gtk_timeout_remove( gint tag ); |
Вы так же можете остановить время ожидания вернув 0 (zero) или FALSE из вашей функции обратного вызова. Очевидно, что для продолжения нужно вернуть не ноль (non-zero), т.е. TRUE.
Декларация вашего обратного вызова:
gint timeout_callback( gpointer data ); |
Setting Widget Attributes |
|
Monitoring IO |
GTK+ 2.0 Tutorial | ||
---|---|---|
<<< Previous | Next >>> |
gulong g_signal_connect( GObject *object, |
void g_signal_handler_block( GObject *object, |
void g_signal_emit( GObject *object, |
<<< Previous | Home | Next >>> |
Idle Functions | Signal Emission and Propagation |
GTK+ 2.0 Tutorial |
||
---|---|---|
Один тип межпроцессорной коммуникации поддерживается X и GTK это "выделение" (selections). Выделение (selection) идентифицирует порцию данных, например часть текста, выбранных пользователем каким либо способом, например с помощью мыши. Только одно приложение в конкретный момент времени может быть владельцем (the owner) индивидуального выделения (particular selection), предыдущий приложение-владелец должен сообщить пользователю о том что выделение было оставлено. Другие приложения могут просить содержание выделения в различных формах, вызовом targets. Может быть любое число выделений, но большинство приложений X обрабатывает только одно - primary selection.
В большинстве случаев для приложения GTK не обязательно иметь дело с выделениями самостоятельно. Стандартные виджеты, типа виджета Ввода, уже имеют возможность требовать выделения (например, когда пользователь перемещается по тексту) и отыскивать содержимое выделения, принадлежащего другому виджету или другому приложению (например, когда пользователь щелкает второй кнопкой мыши). Однако, могут быть случаи, в которых вы хотите дать другим виджетам способность предоставлять выделение, или вы желаете отыскать адресатов, не поддерживаемых по умолчанию.
Фундаментальное понятие обработки выделений (selection) - atom. Атом - целое число, которое однозначно определяет строку (на определенном дисплее). Определенные атомы предопределены сервером X, и в некоторых случаях есть константы в gtk.h, соответствующие этим атомам. Например константа GDK_PRIMARY_SELECTION соответствует строке "PRIMARY". В других случаях, вы должны использовать функции gdk_atom_intern() - получить атом соответствующей строки и gdk_atom_name() - получить название атома. И selections и targets идентифицируются атомами (atoms).
Signal Emission and Propagation |
|
Retrieving the selection |
GTK+ 2.0 Tutorial |
||
---|---|---|
GTK+ имеет установки функций высокого уровня для межпроцессорных коммуникаций посредством системы drag-and-drop. GTK+ может выполнять drag-and-drop поверх низкоуровневых Xdnd и Motif drag-and-drop протоколов.
Приложения поддерживающие GTK+ drag-and-drop, сначала определяют и устанавливают виджеты GTK+ для drag-and-drop. Каждый виджет может быть предметом и/или местом назначения для drag-and-drop. Заметьте, что эти GTK+ виджеты должны иметь связанное с ними окно (X Window), проверьте используя GTK_WIDGET_NO_WINDOW(widget)).
Виджеты являющиеся предметом перетаскивания могут отправлять данные, позволяя пользователю перетаскивать их в другое место, а виджеты которые являются целью назначения для перетаскивания могут принимать эти данные. Drag-and-drop может ограничивать то от кого получаются данные, например то же самое приложение или любое приложение (включая себя).
Посылка и получение данных использует сигналы GTK+. Проброс элемента к виджету назначения требует и запрашиваемых данных (для виджета назначения) и посылаемых данных обработчика сигнала (для исходящего виджета). Дополнительный сигнал обработчиков используется если вы хотите знать когда начинается перетаскивание, когда заканчивается проброс завершая всю операцию (успешно или нет).
Ваше приложение должно будет обеспечить данные для исходных виджетов когда требуется, это вовлекает, имеющий данные проброса, обработчик сигнала. Для виджета назначения они будут нуждаться в данных посылаемых обработчиком сигнала.
Итак, типичный цикл drag-and-drop выглядит так:
Запрос данных проброса ( может быть на том же самом или другом приложении).
Удаление перетаскиваемых данных ( если перетаскивание было перемещением).
Есть несколько не значительных шагов, которые будут обсуждаться позже.
Supplying the selection |
|
Properties |
GTK+ 2.0 Tutorial |
||
---|---|---|
GLib - библиотека низшего уровня, которая обеспечивает много полезных определений и функций, доступных для использования при создании GDK и GTK приложений. Она включает в себя определение основных типов и их пределов, стандартные макросы, преобразования типов, последовательность байт, выделение памяти, предупреждения и утверждения, регистрацию сообщений, таймеры, строковые утилиты, методы функций, лексический сканер, динамическая загрузка модулей и автоматическое дополнение строк. А также множество структур данных (связанные с ними операции), включая участки памяти, списки двойной связи, списки одиночной связи, хэш таблицы, строки (имеющие динамический рост), части строк (группы строк), массивы (способные увеличиваться в размере по мере добавления элементов), сбалансированные двоичные деревья, N-ary деревья, кварки (двухсторонняя ассоциация строки и уникального целочисленного идентификатора), ключевые списки данных (список элементов данных, доступных строкой или целочисленным id), связи и группы связанных данных (таблицы данных, которые могут быть индексированы на любом числе областей), кэши.
Не каждая функция, структура данных, или операция GLib перечислена здесь. Для более полной информации о библиотеке GLib смотрите документацию. Один из источников документации о GLib http://www.gtk.org/.
Если вы используете другой язык программирования (отличный от C), вам необходимо посмотреть документацию по привязкам к вашему языку (binding documentation). В некоторых случаях ваш язык может иметь эквивалентные встроенные функциональные возможности, в то время как в других случаях это может быть не возможно.
Определения для экстремальных значений многих из стандартных типов:
G_MINFLOAT G_MAXFLOAT G_MINDOUBLE G_MAXDOUBLE G_MINSHORT G_MAXSHORT G_MININT G_MAXINT G_MINLONG G_MAXLONG |
Кроме того определены следующие типы. Некоторые зависят от архитектуры процессора. Помните о разных размерах указателей если вам нужна портируемость. Например указатель на Alpha равен 8 bytes, а на Intel 80x86 family CPUs равен 4.
char gchar; short gshort; long glong; int gint; char gboolean; unsigned char guchar; unsigned short gushort; unsigned long gulong; unsigned int guint; float gfloat; double gdouble; long double gldouble; void* gpointer; gint8 guint8 gint16 guint16 gint32 guint32 |
Functions |
|
Doubly Linked Lists |
GTK+ 2.0 Tutorial |
||
---|---|---|
GTK использует для настроек программ специальные файлы (rc files). Например они могут использоваться для установки цветовой схемы виджетов.
Во время старта вашего приложения вы должны включить вызов:
void gtk_rc_parse( char *filename ); |
Аргумент filename - ваш файл настроек (rc file). Это заставит GTK использовать настройки указанные в вашем файле для отрисовки стиля виджетов.
Если вы хотите использовать специальные настройки для отдельных виджетов, используйте функцию:
void gtk_widget_set_name( GtkWidget *widget, gchar *name ); |
В качестве первого аргумента передаётся ваш новый виджет, а в качестве второго его имя. Это позволит вам изменять настройки виджета, через файл настройки (rc file), используя его имя.
button = gtk_button_new_with_label ("Special Button"); gtk_widget_set_name (button, "special button"); |
Тогда кнопке присваивается имя "special button" и вы можете обращаться к ней через файл настройки (rc file), как "special button.GtkButton". [<--- Verify ME!]
В файле примере устанавливаются свойства главного окна и позволяется всем дочерним виджетам наследовать стиль "main button". Вот код используемый приложением:
window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_widget_set_name (window, "main window"); |
И затем стиль определяется в файле настроек (rc file):
widget "main window.*GtkButton*" style "main_button" |
Поэтому все виджеты кнопок, находящихся в "main window", будут иметь стиль как у "main_buttons" определенный в файле настроек (rc file).
Как видите это очень мощный и гибкий инструмент настроек. Используйте своё воображение для более лучшего его применения.
Utility and Error Functions |
|
GTK's rc File Format |
GTK+ 2.0 Tutorial |
||
---|---|---|
Хотя в состав GTK входит достаточное количество виджетов для решения большинства стандартных задач программирования, может возникнуть ситуация когда вам понадобится создать свой собственный виджет. Зачастую для создания своего собственного виджета достаточно взять уже существующий и изменить или добавить несколько строк кода. Но перед тем как делать свой собственный виджет, лучше поискать, возможно ктото уже сделал то что вам нужно. Это предотвратит дублирование виджетов и поможет держать код различных программ последовательным. После создания своего собственного виджета было бы неплохо сообщить об этом другим разработчикам, так вы поможете многим людям сэкономить время. Лучше всего размещать эти сообщения в gtk-list.
Полные исходные тексты примеров виджетов доступны по следующему адресу:
http://www.gtk.org/~otaylor/gtk/tutorial/
Example rc file |
|
The Anatomy Of A Widget |
GTK+ 2.0 Tutorial |
||
---|---|---|
Данный набор библиотек лицензируется по LGPL, таким образом вы можете разрабатывать свободное программное обеспечение, программное обеспечение с открытым кодом или даже закрытые коммерческие программы не беспокоясь о лицензионных отчислениях или каких нибудь ограничениях в использовании GTK+. GTK (GIMP Toolkit) набор библиотек для построения графического интерфейса пользователя (GUI).
Первоначально библиотека разрабатывалась в рамках проекта GNU Image Manipulation Program (GIMP) по созданию программы редактирования изображений, но в данный момент написано огромное колличество программ с использованием GTK, включая GNOME (GNU Network Object Model Environment - Сетевая Объектная Среда GNU). GTK разрабатывается на основе GDK (GIMP Drawing Kit) которая в свою очередь является оболочкой для функций низкого уровня оконной среды (Xlib в случае X window system), и gdk-pixbuf, библиотека манипулирования изображением на стороне клиента.
Peter Mattis petm@xcf.berkeley.edu
Spencer Kimball spencer@xcf.berkeley.edu
Josh MacDonald jmacd@xcf.berkeley.edu
GTK в настоящее время поддерживается:
Owen Taylor otaylor@redhat.com
Tim Janik timj@gtk.org
GTK - по существу объектно ориентированный программный интерфейс приложения (API). Хотя написан полностью на языке программирования C, в нем заложены идеи классов и функций обратного вызова (сallback functions).
Еще есть третий компонент под названием GLib предназначенный для замены некоторых стандартных вызовов, а также содержит некоторые дополнительные функции для работы со связанными списками и т.д. Замененные функции предназначены для повышения мобильности GTK's, так некоторые функции реализованные здесь недоступны в некоторых системах (Unixes), например g_strerror(). Также внесены дополнения для libc, такие как g_malloc() для улучшения утилит отладки.
В версии 2.0, GLib собрала систему типов которая формирует фонд для иерархии классов GTK's, система сигналов которая используется повсюду в GTK, интерфейс библиотек, предоставляемый программам при компиляции (API), которые резюмируют различия родных нитей APIs разных платформ и средства для загрузки модулей.
И как последний компонент, GTK использует библиотеку Pango для интернационализации текстового вывода.
Есть привязки GTK (GTK bindings) для многих других языков программирования: C++, Guile, Perl, Python, TOM, Ada95, Objective C, Free Pascal, Eiffel, Java и C#. Это руководство описывает С интерфейс GTK. Если вы хотите использовать GTK с другим языком программирования, то вам следует в первую очередь ознакомится с документацией для привязки (GTK bindings) к этому языку. В ней может находится важная информация которая потребуется перед тем как изучать данное руководство. Есть кроссплатформенные APIs (такие как wxWindows и V) использующие GTK как основу, они также требуют изучения дополнительной документации.
Если вы разрабатываете ваше GTK приложение на языке С++, вам могут понадобится некоторые дополнения. Для С++ существует привязка GTK--, она предоставляет лучший C++-like интерфейс для GTK; вам лучше изучить её чем это руководство. Если вам не понравится этот подход то существует две альтернативы использования GTK. Во-первых вы можете применять для связи с GTK только C как подмножество C++ и затем использовать интерфейс С как описано в данном руководстве. Во-вторых, вы можете использовать GTK совместно с C++ объявляя функции обратного вызова (callbacks) как статические в C++ классах, и вызывать GTK используя C интерфейс. Если вы выберете последний вариант, то сможете использовать в качестве данных, передаваемых callback-функции, указатель на объект, с которым вы работаете (значение "this"). Выбор между тремя этими способами вопрос вкуса, в любом случае вы получаете C++ и GTK. Не один из этих вариантов не требует специального препроцессора и вы в любом случае получаете стандартный C++ для GTK.
Это руководство, попытка создать документацию по GTK, но оно не является полным. Это руководство для хорошего понимания языка программирования С и создания программ на нём. Для читателя было бы хорошо знать основы программирования в X среде, но это не является обязательным. Если вы изучаете GTK как первый свой инструмент для создания оконных интерфейсов, то прокомментируйте пожалуйста с какими трудностями вы столкнулись изучая данное руководство.
Этот документ находится в постоянной разработке. Вы можете посмотреть последние обновления на http://www.gtk.org/. Замечания по поводу перевода можно отправлять sergeyvp@gmail.com.
Если вы сообщите о проблемах при изучении GTK по данному документу, то вы примете участие в улучшении данного руководства. Пожалуйста посмотрите секцию Содействие для получения дополнительной информации.
Tutorial Availability |
|
Getting Started |
GTK+ 2.0 Tutorial |
||
---|---|---|
В этом разделе, мы будем строить простую программу для рисования. В процессе мы исследуем как обрабатывать события при нажатии мышки, как рисовать в окне и как улучшить рисунок при использовании фоновой карты пикселей (backing pixmap). После создания простой программы для рисования, мы расширим её, добавляя поддержку устройств XInput, таких как планшет для рисования (drawing tablets). GTK обеспечивает поддержку подпрограмм, которые делают получение расширенной информации, типа давления и наклона, от таких устройств весьма простой задачей.
Learning More |
|
Event Handling |
GTK+ 2.0 Tutorial |
||
---|---|---|
Этот раздел содержит простые советы по созданию хороших GTK программ. В данный момент этот раздел очень короткий, но я надеюсь в будущем он будет дополнен и расширен вместе с данным руководством.
Используйте GNU autoconf и automake! Они - ваши друзья :) Automake исследует файлы C, определяет, как они зависят друг от друга, и производит Makefile, таким образом файлы могут быть собраны в правильном порядке. Autoconf позволяет автоматическую конфигурацию установки программного обеспечения, обращаясь с большим количеством специальных систем, увеличивая мобильность. Здесь планируется сделать короткий обзор этих утилит.
При создании кода на C, используйте только С комментарии (начинается как "/*" и заканчивается на "*/"). Хотя многие компиляторы С поддерживают С++ подобный стиль комментариев ("//"), стандарт ANSI C не поддерживает C++-стиль комментариев.
Adding XInput support |
|
Contributing |
GTK+ 2.0 Tutorial |
||
---|---|---|
Данное руководство, как и многое другое замечательное программное обеспечение было создано добровольцами. Если вы хорошо осведомлены о каких нибудь возможностях или свойствах GTK не упомянутых в данном руководстве, пожалуйста помогите дополнить и улучшить его.
Если вы действительно захотите сделать ваш вклад в развитие руководства, то вышлите ваш текст по почте Tony Gale, gale@gtk.org. Также вам необходимо знать что данное руководство является свободным, поэтому все ваши дополнения тоже должны быть таковыми. Тоесть все люди могут копировать, распространять, использовать примеры этого руководства в своих программах без всяких ограничений.
Спасибо вам за ваше участие!
Tips For Writing GTK Applications |
|
Credits |
GTK+ 2.0 Tutorial |
||
---|---|---|
Мы хотели бы поблагодарить следующих людей за вклад в создание этого текста.
Bawer Dagdeviren, chamele0n@geocities.com for the menus tutorial.
Raph Levien, raph@acm.org for hello world ala GTK, widget packing, and general all around wisdom. He's also generously donated a home for this tutorial.
and the ability to make it :)
Werner Koch werner.koch@guug.de for converting the original plain text to SGML, and the widget class hierarchy.
Mark Crichton crichton@expert.cc.purdue.edu for the menu factory code, and the table packing tutorial.
Owen Taylor owt1@cornell.edu for the EventBox widget section (and the patch to the distro). He's also responsible for the selections code and tutorial, as well as the sections on writing your own GTK widgets, and the example application. Thanks a lot Owen for all you help!
Mark VanderBoom mvboom42@calvin.edu for his wonderful work on the Notebook, Progress Bar, Dialogs, and File selection widgets. Thanks a lot Mark! You've been a great help.
Tim Janik timj@gtk.org for his great job on the Lists Widget. His excellent work on automatically extracting the widget tree and signal information from GTK. Thanks Tim :)
Rajat Datta rajat@ix.netcom.com for the excellent job on the Pixmap tutorial.
Michael K. Johnson johnsonm@redhat.com for info and code for popup menus.
David Huggins-Daines bn711@freenet.carleton.ca for the Range Widgets and Tree Widget sections.
Stefan Mars mars@lysator.liu.se for the CList section.
David A. Wheeler dwheeler@ida.org for portions of the text on GLib and various tutorial fixups and improvements. The GLib text was in turn based on material developed by Damon Chaplin DAChaplin@msn.com
David King for style checking the entire document.
И всем вам, кто помогал редактировать данный документ.
Спасибо!
Contributing |
|
Tutorial Copyright and Permissions Notice |
GTK+ 2.0 Tutorial |
||
---|---|---|
The GTK Tutorial is Copyright (C) 1997 Ian Main.
Copyright (C) 1998-2002 Tony Gale.
Разрешается делать и распространять дословные копии этого руководства, если объявление об авторском праве и это уведомление о разрешении сохраняются на всех копиях.
Разрешается копировать и распространять измененные версии этого документа при условиях для дословного копирования, при условии, что это объявление об авторском праве включено точно как в оригинале, и что полная версия работы распространена в соответствии с уведомлением о разрешении, идентичным этому.
Разрешается распространять копии трансляции этого документа на другие языки, при вышеупомянутых условиях для измененных версий.
Если вы намереваетесь включить этот документ в издаваемую вами работу, пожалуйста войдите в контакт с мантейнером (maintainer) и мы постараемся гарантировать, что вы имеете в наличии самую свежую информацию.
Нет никакой гарантии, что этот документ соответствует его намеченной цели. Этот документ существует как бесплатный информационный ресурс. Также, авторы и мантейнеры не дают никакой гарантии, что информация является точной.
Credits |
|
GTK Signals |
GTK+ 2.0 Tutorial |
||
---|---|---|
Вы наверное заметили некоторые моменты в предыдущих примерах, требующие объяснения. В системе GLib существуют собственные типы данных такие, как gint, gchar и т.д. эти типы являются аналогами типов в языке программирования С. Это сделано для преодоления зависимости от платформы при выполнении расчетов.
Хорошим примером является "gint32" который определяет тип целого числа размером 32 bit на любой платформе, например 64 bit alpha, или 32 bit i386. Определения типов являются интуитивными и прямолинейными. Они все определены в glib/glib.h (который включен в gtk.h).
Вы также заметили использование GtkWidget когда функция обращается к GtkObject. GTK имеет объектно ориентированный дизайн с использованием виджетов (widget) как объектов (object).
Stepping Through Hello World |
|
More on Signal Handlers |
GTK+ 2.0 Tutorial |
||
---|---|---|
При создании графических интерфейсов необходимо размещать большое кол-во виджетов внутри диалоговых окон. Первый пример helloworld имел только один виджет, поэтому мы могли использовать вызов gtk_container_add() для размещения его внутри окна. Когда вам понадобится размещать в окне больше одного виджета, нужно будет каким-то образом контролировать их расположение. Для этого и нужны упаковочные контейнеры.
Существует два вида упаковочных невидимых контейнеров для размещения виджетов, горизонтальный и вертикальный. Когда используется горизонтальный контейнер, объекты внутри него могут располагаться с права налево, или наоборот в зависимости от использованных вызовов. В вертикальном контейнере виджеты располагаются сверху вниз или снизу вверх. Вы можете использовать любые комбинации расположения контейнеров (рядом, внутри), для достижения желаемого результата.
Для создания нового горизонтального контейнера используется вызов gtk_hbox_new(), а для вертикального gtk_vbox_new(). Функции gtk_box_pack_start() и gtk_box_pack_end() используются для размещения объектов внутри этих контейнеров. Функция gtk_box_pack_start() размещает объекты сверху вниз в вертикальных контейнерах и слева на право в горизонтальных, а функция. gtk_box_pack_end() соответственно снизу вверх и справа на лево. Эти функции позволяют нам использовать правое или левое, а также верхнее или нижнее выравнивание виджетов и даже смешанное для достижения необходимого результата. В большинстве примеров мы используем gtk_box_pack_start(). Любой объект или виджет может выступать в роли контейнера. Кнопка фактически тоже является контейнером, но мы размещаем в ней только надпись для обозначения.
При использовании этих вызовов, GTK узнаёт где размещаются ваши виджеты и может автоматически регулировать размер, а также выполнять другие полезные действия. Есть и другие методы упаковки ваших виджетов, если вы думаете что данный метод даёт слишком мало гибкости при создании и размещении виджетов.
An Upgraded Hello World |
|
Details of Boxes |
GTK+ 2.0 Tutorial |
||
---|---|---|
Основные действия для создания GTK виджетов:
gtk_*_new() - одна из функций для создания виджетов. Все они детализированы в этой секции.
Соединение всех сигналов и событий, которые мы собираемся использовать, с обработчиками.
Упаковка виджетов в контейнер, используя соответствующий запрос, типа gtk_container_add() или gtk_box_pack_start().
gtk_widget_show() позволяет GTK выводить виджеты согласно установленным атрибутам. Для скрытия виджетов можно использовать gtk_widget_hide. Порядок вывода виджетов на экран не важен, но лучше выводить основное окно последним чтобы все виджеты в окне отображались одновременно, а не появлялись по мере их формирования. Дочерние виджеты не отображаются до тех пор, пока не показано основное для них окно с помощью функции gtk_widget_show().
GTK использует систему преобразования типов. Она всегда использует макросы для проверки преобразования, перед тем как выполнить его. Вот некоторые из них:
G_OBJECT (object) GTK_WIDGET (widget) GTK_OBJECT (object) GTK_SIGNAL_FUNC (function) GTK_CONTAINER (container) GTK_WINDOW (window) GTK_BOX (box) |
Все они используются как аргументы функций. Вы видели их в примерах при простом использовании в декларациях функций.
Как вы можете увидеть ниже в иерархии классов, все GtkWidgets образованы из базового класса GObject. Это значит, что в любом месте где функция запрашивает виджет типа объект, вы можете использовать макрос G_OBJECT().
g_signal_connect( G_OBJECT (button), "clicked", G_CALLBACK (callback_function), callback_data); |
Здесь кнопка преобразуется в объект, и выполняется преобразование указателя функции в обратный вызов.
Многие виджеты являются контейнерами. Если вы посмотрите в иерархии классов ниже, то заметите, что много виджетов происходят из контейнерного класса. Любой из этих виджетов может использоваться с макросом GTK_CONTAINER, чтобы передать их в функции, которые запрашивают контейнеры.
К сожалению невозможно рассказать о всех макросах в данном руководстве, поэтому я рекомендую вам изучить заголовочные файлы GTK (header files) или GTK API reference manual. Это может быть очень познавательно. Фактически можно понять как работают виджеты изучая декларации функций.
Table Packing Example |
|
Widget Hierarchy |
GTK+ 2.0 Tutorial |
||
---|---|---|
Вы уже много раз видели процесс создания виджетов. Это довольно просто. Есть несколько способов создавать кнопки. Вы можете использовать gtk_button_new_with_label() или gtk_button_new_with_mnemonic() чтобы создать кнопку с ярлыком, или используйте gtk_button_new_from_stock (), чтобы создать кнопку, содержащую изображение и текст. Или gtk_button_new(), чтобы создать чистую кнопку, в которую вы сможете упаковать ярлык или pixmap(картинку). Для этого нужно создать контейнер, упаковать в этот контейнер необходимый объект используя gtk_box_pack_start(), и с помощью gtk_container_add() упаковать контейнер в кнопку.
Вот пример мспользования gtk_button_new(), для создания кнопки с изображением и текстом внутри. Я отделил код создания контейнера от остального, вы можете поступать также в своих программах. Дальше в этом руководстве будут приведены ещё примеры использования изображений.
#include <stdlib.h> #include <gtk/gtk.h> /* Создаём новый контейнер hbox содержащий изображение и текст * и возвращаем контейнер. */ GtkWidget *xpm_label_box( gchar *xpm_filename, gchar *label_text ) { GtkWidget *box; GtkWidget *label; GtkWidget *image; /* Создаём контейнер для изображения и текста */ box = gtk_hbox_new (FALSE, 0); gtk_container_set_border_width (GTK_CONTAINER (box), 2); /* Определяем файл с изображением */ image = gtk_image_new_from_file (xpm_filename); /* Создаём ярлык для кнопки */ label = gtk_label_new (label_text); /* Упаковываем рисунок и текст в контейнер */ gtk_box_pack_start (GTK_BOX (box), image, FALSE, FALSE, 3); gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 3); gtk_widget_show (image); gtk_widget_show (label); return box; } /* Обычная функция вызова */ void callback( GtkWidget *widget, gpointer data ) { g_print ("Hello again - %s was pressed\n", (char *) data); } int main( int argc, char *argv[] ) { /* GtkWidget является типом хранения для виджетов */ GtkWidget *window; GtkWidget *button; GtkWidget *box; gtk_init (&argc, &argv); /* Создаём новое окно */ window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_window_set_title (GTK_WINDOW (window), "Pixmap'd Buttons!"); /* Хорошая идея сделать это для всех окон. */ g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (gtk_main_quit), NULL); g_signal_connect (G_OBJECT (window), "delete_event", G_CALLBACK (gtk_main_quit), NULL); /* Устанавливаем окантовку для окна. */ gtk_container_set_border_width (GTK_CONTAINER (window), 10); /* Создаём новую кнопку */ button = gtk_button_new (); /* Соединяем сигнал нажатия "clicked" кнопки с обратным вызовом */ g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (callback), (gpointer) "cool button"); /* Вызов функции для создания контейнера */ box = xpm_label_box ("info.xpm", "cool button"); /* Упаковываем и отображаем все виджеты */ gtk_widget_show (box); gtk_container_add (GTK_CONTAINER (button), box); gtk_widget_show (button); gtk_container_add (GTK_CONTAINER (window), button); gtk_widget_show (window); /* Переходим в состояние ожидания событий! */ gtk_main (); return 0; } |
Функция xpm_label_box() может быть использована для упаковки изображений и надписей в любой виджет способный выступать в роли контейнера.
Виджет кнопка, имеет следующие сигналы:
pressed - посылается, когда кнопка нажата в пределах её размеров
released - посылается, когда кнопка отжата в пределах её размеров
clicked - когда кнопка нажата и отпущена в пределах размеров виджета
leave - когда курсор выходит из области, где нарисована кнопка
Widgets Without Windows |
|
Toggle Buttons |
GTK+ 2.0 Tutorial |
||
---|---|---|
Первое что нужно сделать, это конечно скачать и установить GTK. Вы всегда можете найти последнюю версию на ftp.gtk.org. Вы также можете посмотреть информацию об исходном коде GTK на http://www.gtk.org/. GTK использует GNU autoconf для конфигурации. Для просмотра справки наберите ./configure --help.
Исходный код GTK содержит все примеры приведенные в данном руководстве, а также Makefiles для упрощенной компиляции.
Для начала мы рассмотрим пример самой простой программы на GTK. Эта программа создает окно размером 200x200 pixel и не совершает никаких действий кроме как выход после ввода соответствующей команды shell.
#include <gtk/gtk.h> int main( int argc, char *argv[] ) { GtkWidget *window; gtk_init (&argc, &argv); window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_widget_show (window); gtk_main (); return 0; } |
Вы можете скомпилировать программу используя компилятор gcc:
gcc base.c -o base `pkg-config --cflags --libs gtk+-2.0`
О возможных вариантах компиляции объясняется ниже в Компиляция Hello World.
Все программы будут конечно включать заголовочный файл gtk/gtk.h, который объявляет переменные, функции, структуры, и т.д. использующиеся в вашей GTK программе.
Следующая строка:
gtk_init(&argc, &argv); |
вызов функции gtk_init(gint *argc, gchar ***argv) которая используется во всех GTK программах. Она создает установки визуальной и цветовой карты, а также запускает процесс вызова функции gdk_init(gint *argc, gchar ***argv). Эта функция инициализирует библиотеки для использования, устанавливает по умолчанию обработчики сигналов, и проверяет аргументы командной строки вашей программы, производя поиск одного из следующих:
--gtk-module
--g-fatal-warnings
--gtk-debug
--gtk-no-debug
--gdk-debug
--gdk-no-debug
--display
--sync
--name
--class
Проходя по списку происходит поиск объявленных аргументов вашей программы для дальнейшего разбора или игнорирования. Так создаётся ряд стандартных аргументов для всех GTK программ.
Следующие две строчки кода выводят на дисплей окно программы.
window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_widget_show (window); |
Аргумент GTK_WINDOW_TOPLEVEL определяет специфику расположения и оформления окна в менеджере окон. Чтобы не создавалось окно размером 0x0, по умолчанию принят размер 200x200 для всех дочерних окон, вы также можете манипулировать этими значениями.
Функция gtk_widget_show() позволяет GTK понять какие параметры мы установили для виджетов и правильно отобразить их.
Последняя строка вводит в главный цикл GTK процесса (main processing loop).
gtk_main (); |
gtk_main() ещё один вызов который используется в любой программе GTK. Когда программа доходит до этого вызова, GTK начинает ожидать X события (такого как нажатие кнопки мыши или клавиатуры), прерывания, или файлового ввода вывода. Однако в нашем простом примере никаких событий не ожидается.
Еще одна программа в виде окна с кнопкой. Это классический "hello world" с использованием GTK.
#include <gtk/gtk.h> /* Это функция обратного вызова. Аргументы в данном * примере отсутствуют. * Подробней об обратных вызовах будет сказано ниже. */ void hello( GtkWidget *widget, gpointer data ) { g_print ("Hello World\n"); } gint delete_event( GtkWidget *widget, GdkEvent *event, gpointer data ) { /* Если вы вернете FALSE в "delete_event" обработчик сигналов, * GTK создаст "destroy" разрушающий сигнал. Возвращенный TRUE говорит * о нежелании закрывать окно. * Это полезно при всплывающих типах диалогов * 'вы уверены что хотите выйти?'*/ g_print ("delete event occurred\n"); /* Измените TRUE на FALSE и главное окно будет закрыто * с "delete_event". */ return TRUE; } /* Другой вызов */ void destroy( GtkWidget *widget, gpointer data ) { gtk_main_quit (); } int main( int argc, char *argv[] ) { /* GtkWidget является типом хранения для виджетов */ GtkWidget *window; GtkWidget *button; /* Этот вызов используется во всех GTK программах. Аргументы из командной строки * разбираются и возвращаются в приложение. */ gtk_init (&argc, &argv); /* создается новое окно */ window = gtk_window_new (GTK_WINDOW_TOPLEVEL); /* Когда окну поступает "delete_event" сигнал (это делает * менеджер окон, обычно "close" опция в верхнем заголовке окна), * задаётся вопрос delete_event() функции, как обсуждалось выше. * Данные поступают в функцию обратного вызова являясь * NULL и игнорируются. */ g_signal_connect (G_OBJECT (window), "delete_event", G_CALLBACK (delete_event), NULL); /* Здесь мы соединяем "destroy" событие с обработчиком сигнала. * Это делается вызовом gtk_widget_destroy() в окне, * или если возвращаем FALSE в обратном вызове "delete_event". */ g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (destroy), NULL); /* Устанавливаем ширину окантовки окна */ gtk_container_set_border_width (GTK_CONTAINER (window), 10); /* Создаем новую кнопку с ярлыком "Hello World". */ button = gtk_button_new_with_label ("Hello World"); /* Когда кнопка получит "clicked" сигнал, то вызовет * функцию hello() с NULL аргументом. Функция hello() * определена выше. */ g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (hello), NULL); /* Это закроет окно вызвав * gtk_widget_destroy(window) когда поступит "clicked". Снова сигнал * закрытия мог поступить от сюда, или от менеджера окон. */ g_signal_connect_swapped (G_OBJECT (button), "clicked", G_CALLBACK (gtk_widget_destroy), G_OBJECT (window)); /* Помещаем кнопку внутрь окна */ gtk_container_add (GTK_CONTAINER (window), button); /* Последний этап, вывод кнопки на экран. */ gtk_widget_show (button); /* Вывод на экран окна. */ gtk_widget_show (window); /* Все GTK приложения содержат gtk_main(). Здесь заканчивается контроль * и происходит ожидание(нажатие клавиши или мышки) */ gtk_main (); return 0; } |
Introduction |
|
Compiling Hello World |
GTK+ 2.0 Tutorial |
||
---|---|---|
GTK имеет различные виджеты для регулировки диапазона с использованием мыши или клавиатуры, они обсуждаются в секции Range Widgets. Есть также несколько виджетов для регулирования текстовых или информационных данных выходящих за рамки окна отображения, для вывода данных определёнными порциями.
Очевидно, что приложение должно уметь реагировать на действия пользователя связанные с виджетами регулировки. Один из способов добиться этого, заставить виджет создавать сигнал определенного типа в момент изменения регулировок, перехватывая этот сигнал с помощью обработчика или контролировать состояние значения в структуре данных виджета. Вы также можете соединить несколько регуляторов вместе, чтобы регулировки одного отражались на других. Самый очевидный пример такого соединения, это регулировочная планка (scrollbar) в области просмотра (viewport) или прокрутка области текста. Если каждый виджет имеет собственное значение регулировки, то программисту потребуется написать для каждого свой собственный обработчик сигнала, для того, чтобы различать сигнал выхода одного от сигнала входа другого.
GTK решает эту проблему используя регулировочный объект (Adjustment object), это не виджет а метод передачи и хранения информации о регулировании в абстрактной и гибкой форме. Самое очевидное использование регулировочного объекта (Adjustment), это сохранение конфигурации параметров значения виджетов регулирования диапазона, таких как планка прокрутки (scrollbars) и скользящий регулятор (scale controls). Однако, так как Регуляторы (Adjustments) получены из объектов (Object), они имеют некоторые специальные возможности в отличие от нормальных структурных данных. Самое главное, они могут создавать сигналы как обычные виджеты и эти сигналы могут не только использоваться вашей программой для реакции на ваш ввод или вывод, но и прозрачно распространяться между регулировочными виджетами.
Вы можете посмотреть сходство регуляторов с другими виджетами: Progress Bars, Viewports, Scrolled Windows, и т.д.
Многие виджеты используют объекты регулирования созданные автоматически, но в некоторых случаях, показанных позже, требуется самостоятельное создание виджетов. Пример создания:
GtkObject *gtk_adjustment_new( gdouble value, gdouble lower, gdouble upper, gdouble step_increment, gdouble page_increment, gdouble page_size ); |
Аргумент value используется для установки по умолчанию значения положения регулятора, обычно это позиция "самый верхний" или "крайний левый". Аргумент lower определяет самое нижнее значение регулятора. Step_increment аргумент определяет "меньшие" из двух приращений, которыми пользователь может изменить значение, в то время как page_increment - "больший". Аргумент page_size обычно устанавливает видимую площадь для панорамного виджета. Аргумент upper используется для предоставления самых нижних или крайних справа координат дочернему панорамному виджету. Это не всегда самое меньшее значение value так как в этих виджетах page_size обычно не равно нулю.
Radio Buttons |
|
Using Adjustments the Easy Way |
GTK+ 2.0 Tutorial |
||
---|---|---|
Категория виджетов диапазона включает вездесущий scrollbar (полоса прокрутки) и менее используемый scale widget (виджет масштабирования). Хотя эти два виджета используются для абсолютно разных целей, функционально они очень похожи. Все виджеты диапазона разделены на ряд графических элементов, каждый из которых имеет собственное окно и собственное событие оконной системы. Они все содержат "колею" ("trough") и "ползунок" ("slider") (их иногда называют "координатный манипулятор" "thumbwheel" в других средах разработки GUI). Перемещение "ползунка" указателем мыши допустимо в пределах колеи, щелчок мыши в любом месте колеи заставляет "ползунок" перемещаться в направлении щелчка на заранее определенное растояние.
Как упомянуто в главе о Регуляторах выше, все виджеты диапазона связаны с объектом регулирования, от которого они получают длину "ползунка" и его положение в пределах "колеи". Когда пользователь манипулирует ползунком, виджет диапазона изменяет значение регулировки.
Полоса прокрутки чаще всего используется с такими виджетами как списки (list), текстовые контейнеры (text box), или область просмотра (viewport) (и в стандартных оконных виджетах лучше всего использовать "прокручивание"). Для других целей лучше использовать виджеты масштабирования, они дружественны и более функциональны (featureful).
Есть отдельные типы для горизонтального и вертикального прокручивания (scrollbars). Они создаются при помощи отдельных функций:
GtkWidget *gtk_hscrollbar_new( GtkAdjustment *adjustment ); GtkWidget *gtk_vscrollbar_new( GtkAdjustment *adjustment ); |
Аргумент adjustment может быть указателем на Регулятор, или NULL, в зависимости от вашего выбора. Пустое значение используется в тех случаях, когда вновь созданный регулятор проходит через конструктор функции другого виджета который сконфигурирует его за вас, например текстовый виджет.
Adjustment Internals |
|
Scale Widgets |
GTK+ 2.0 Tutorial |
||
---|---|---|
Разные виджеты (Miscellaneous Widgets) |
Ярлыки в GTK используются часто и они достаточно просты. Ярлыки не создают никаких сигналов ассоциированных с X window. Если вы хотите перехватить сигнал, то поместите ярлык в EventBox widget или Button widget.
Для создания нового ярлыка воспользуйтесь:
GtkWidget *gtk_label_new( const char *str ); GtkWidget *gtk_label_new_with_mnemonic( const char *str ); |
Единственным параметром является строка для отображения.
Для изменения текста ярлыка после создания, воспользуйтесь функцией:
void gtk_label_set_text( GtkLabel *label, const char *str ); |
Первый аргумент - предварительно созданный ярлык (приведение с использованием макрокоманды GTK_LABEL()), а второй - новая строка.
Пространство, необходимое для новой строки будет автоматически откорректировано если нужно. Вы можете использовать многострочные ярлыки, помещая в конец каждой строки знак перевода строки.
Для нахождения текущей строки, используйте:
const gchar* gtk_label_get_text( GtkLabel *label ); |
Не делайте пустой возвращённую строку, это внутреннее использование GTK.
Текст ярлыка выравнивается с помощью:
void gtk_label_set_justify( GtkLabel *label, GtkJustification jtype ); |
Значение jtype:
GTK_JUSTIFY_LEFT GTK_JUSTIFY_RIGHT GTK_JUSTIFY_CENTER (the default) GTK_JUSTIFY_FILL |
Виджет ярлык может автоматически создавать оболочку для линии текста. Это активизируется так:
void gtk_label_set_line_wrap (GtkLabel *label, gboolean wrap); |
Аргумент wrap принимает значение TRUE или FALSE.
Если вы хотите подчеркнуть ваш ярлык, то вы можете установить образец:
void gtk_label_set_pattern (GtkLabel *label, const gchar *pattern); |
Аргумент pattern указывает как должно выглядеть подчеркивание. Оно может состоять из символов подчеркивания и пробелов. Каждый символ подчеркивается согласно образцу. Например, строка "__ __" подчеркнула бы первые два символа и последние.
Ниже дан короткий пример для иллюстрации этих функций. Этот пример использует виджеты рамки для лучшего стиля оформления ярлыков. Сейчас вы можете проигнорировать их, но позже к ним прийдется вернуться в секции Frame.
В GTK + 2.0, тексты ярлыков могут содержать разметку для шрифта и другие атрибуты изменения текста, а также текст ярлыка может быть выделен (для копирования и вставки). Эти расширенные особенности здесь объясняться не будут.
Example |
|
Arrows |
GTK+ 2.0 Tutorial |
||
---|---|---|
Miscellaneous Widgets |
Комбинированный ввод - ещё один довольно простой виджет, который реально является комбинацией двух других виджетов. Для пользователя этот виджет выглядит как поле для ввода текста и выпадающее меню содержащее возможные варианты ввода. Альтернативно, пользователь может вводить в список выбора дополнительные варианты.
В следующей структуре представлены компоненты комбинированного ввода:
struct _GtkCombo { GtkHBox hbox; GtkWidget *entry; GtkWidget *button; GtkWidget *popup; GtkWidget *popwin; GtkWidget *list; ... }; |
Комбинированный ввод имеет две основные части: entry и list.
GtkWidget *gtk_combo_new( void ); |
Теперь, при непосредственном манипулировании entry, создаём список доступных значений для ввода, выпадающий из строки ввода:
gtk_entry_set_text (GTK_ENTRY (GTK_COMBO (combo)->entry), "My String."); |
Задаём значение для каждой строки выпадающего списка:
void gtk_combo_set_popdown_strings( GtkCombo *combo, GList *strings ); |
Прежде всего вы должны собрать строки в GList. GList - связанное выполнение списка, входит в состав GLib и поддерживается библиотекой GTK. При беглом рассмотрении это выглядит так, вы создаёте указатель на GList равный NULL, затем прикладываете перечень:
GList *g_list_append( GList *glist, gpointer data ); |
Важно инициализировать указатель GList равным NULL. Значение возвращённое из функции g_list_append() должно использоваться как новый указатель на GList.
Пример создания списка вариантов:
GList *glist = NULL; glist = g_list_append (glist, "String 1"); glist = g_list_append (glist, "String 2"); glist = g_list_append (glist, "String 3"); glist = g_list_append (glist, "String 4"); gtk_combo_set_popdown_strings (GTK_COMBO (combo), glist); /* glist освобождается, combo забирает копии */ |
Виджет combo создаёт копии строк переданных в структуру glist. Как результат, вы можете освободить память занимаемую списком если ваше приложение нуждается в этом.
В этом пункте рассматривается работающий комбинированный ввод. Есть несколько аспектов которые вы можете изменять. Это делается функциями:
void gtk_combo_set_use_arrows( GtkCombo *combo, gboolean val ); void gtk_combo_set_use_arrows_always( GtkCombo *combo, gboolean val ); void gtk_combo_set_case_sensitive( GtkCombo *combo, gboolean val ); |
gtk_combo_set_use_arrows() позволяет пользователю изменять значение ввода используя клавиши стрелки up/down. Сам список не поднимается, а скорее заменяет текущее значение ввода на следующее по списку (вверх или вниз, как указывает выбор вашей клавиши). Обычно в окне ввода клавиши стрелки используются для смещения фокуса (вы можете также использовать TAB). Заметьте, что при достижении последнего значения в списке и нажатии кнопки вниз происходит перемещение фокуса на самый верх для последующего продвижения, тоже самое происходит и в обратном направлении.
Если текущее значение во входе не находится в списке, то функция gtk_combo_set_use_arrows() - отключена.
gtk_combo_set_use_arrows_always() - также позволяет циклически проходить по списку выбора вверх или вниз используя клавиши стрелки, за исключением того, что полностью отключает использование клавиш up и down для смещения фокуса.
gtk_combo_set_case_sensitive() - переключает GTK на поиск чувствительный или нет к регистру. Это используется когда нужно найти текущее значение строки ввода в списке значений. Окончание может быть выполнено или чувствительным к регистру или нечувствительным способом, в зависимости от использования этой функции. Combo widget может просто прекратить текущий ввод используя комбинацию клавиш MOD-1 и "Tab". MOD-1 тоже самое что и клавиша "Alt". Отметьте однако, что некоторые оконные менеджеры, также используют эту ключевую комбинацию, которая отменит ее использование в пределах GTK.
Теперь, когда мы имеем поле со списком, приспособленное для просмотра и действий, как мы этого хотим, все, что остается, это получить данные для поля со списком. В большинстве случаев, всё что вам нужно для получения данных - это ввод. К вводу обращаются просто GTK_ENTRY (GTK_COMBO (combo)->entry). Два основных действия, которые вы захотите выполнять - соединяются с активизирующимся сигналом, который указывает, что пользователь нажал Return или ENTER клавишу и прочёл текст.
Первое делается так:
g_signal_connect (G_OBJECT (GTK_COMBO (combo)->entry), "activate", G_CALLBACK (my_callback_function), (gpointer) my_data); |
Получение текста в любое произвольное время достигается, просто функцией ввода:
gchar *gtk_entry_get_text( GtkEntry *entry ); |
Как нибудь так:
gchar *string; string = gtk_entry_get_text (GTK_ENTRY (GTK_COMBO (combo)->entry)); |
Есть ещё функция:
void gtk_combo_disable_activate( GtkCombo *combo ); |
она отключает сигнал активизации в поле ввода в виджете комбинированного ввода, непонятно при каких обстоятельствах это может пригодится, но тем не менее эта функция существует.
Spin Buttons |
Calendar |
GTK+ 2.0 Tutorial |
||
---|---|---|
Разные виджеты (Miscellaneous Widgets) |
Виджет Calendar - эффективный способ работы с датами в помесячном формате информации. Это очень легкий в создании и работе виджет.
GtkWidget *gtk_calendar_new( void ); |
Следующие функции позволяют вносить многократные изменения в виджет Календарь производя незаметные для пользователя обновления.
void gtk_calendar_freeze( GtkCalendar *Calendar ); void gtk_calendar_thaw( GtkCalendar *Calendar ); |
Они работают точно также как и функции freeze/thaw в других виджетах.
Виджет Календарь имеет несколько опций позволяющих вам менять внешний вид виджета. Они используются в следующей функции:
void gtk_calendar_display_options( GtkCalendar *calendar, GtkCalendarDisplayOptions flags ); |
Аргумент flags может быть скомбинирован из пяти значений, используя операцию логическое ИЛИ (|):
Следующие функции используются, чтобы установить дату отображаемую в настоящее время:
gint gtk_calendar_select_month( GtkCalendar *calendar, guint month, guint year ); void gtk_calendar_select_day( GtkCalendar *calendar, guint day ); |
Возвращаемое значение из gtk_calendar_select_month() - булево значение, указывающее, был ли выбор успешен. С помощью gtk_calendar_select_day() выбирается указанное число дней в течение текущего месяца, если это возможно. Значение дня 0 снимет любое текущее выделение.
В дополнение к выбору дня, любой день может быть помечен "marked". Помеченный день подсвечивается в пределах календарного месяца. Следующая функция позволяет манипулировать с маркировками дней:
gint gtk_calendar_mark_day( GtkCalendar *calendar, guint day); gint gtk_calendar_unmark_day( GtkCalendar *calendar, guint day); void gtk_calendar_clear_marks( GtkCalendar *calendar); |
В настоящее время отмечаемые дни сохраняются в массив в пределах структуры GtkCalendar. Этот массив имеет длину в 31 элемент, чтобы проверить отмечен день или нет, нужно обратиться к соответствующему элементу массива (не забывайте что в C нумерация элементов массива начинается с 0 до n-1).
Пример:
GtkCalendar *calendar; calendar = gtk_calendar_new (); ... /* 7 день отмечен? */ if (calendar->marked_date[7-1]) /* день отмечен */ |
Заметьте, что эти отметки постоянны с изменением месяца и года.
Последняя функция виджета Календарь используется, чтобы возвращать выделенную текущую дату, месяц и/или год.
void gtk_calendar_get_date( GtkCalendar *calendar, guint *year, guint *month, guint *day ); |
Эта функция требует, чтобы Вы передали адреса guint переменных, в которые будет помещен результат. Переданный NULL как значение, вернет соответственный результат.
Календарный виджет может генерировать множество сигналов, указывающих выбор даты и изменение. Названия этих сигналов говорят сами за себя:
Ниже приведен код который включает почти всё выше сказанное о виджете Календарь.
/* * Copyright (C) 1998 Cesar Miquel, Shawn T. Amundson, Mattias GrЖnlund * Copyright (C) 2000 Tony Gale * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include <gtk/gtk.h> #include <stdio.h> #include <string.h> #include <time.h> #define DEF_PAD 10 #define DEF_PAD_SMALL 5 #define TM_YEAR_BASE 1900 typedef struct _CalendarData { GtkWidget *flag_checkboxes[5]; gboolean settings[5]; gchar *font; GtkWidget *font_dialog; GtkWidget *window; GtkWidget *prev2_sig; GtkWidget *prev_sig; GtkWidget *last_sig; GtkWidget *month; } CalendarData; enum { calendar_show_header, calendar_show_days, calendar_month_change, calendar_show_week, calendar_monday_first }; /* * GtkCalendar */ void calendar_date_to_string( CalendarData *data, char *buffer, gint buff_len ) { struct tm tm; time_t time; memset (&tm, 0, sizeof (tm)); gtk_calendar_get_date (GTK_CALENDAR (data->window), &tm.tm_year, &tm.tm_mon, &tm.tm_mday); tm.tm_year -= TM_YEAR_BASE; time = mktime (&tm); strftime (buffer, buff_len-1, "%x", gmtime (&time)); } void calendar_set_signal_strings( char *sig_str, CalendarData *data) { const gchar *prev_sig; prev_sig = gtk_label_get_text (GTK_LABEL (data->prev_sig)); gtk_label_set_text (GTK_LABEL (data->prev2_sig), prev_sig); prev_sig = gtk_label_get_text (GTK_LABEL (data->last_sig)); gtk_label_set_text (GTK_LABEL (data->prev_sig), prev_sig); gtk_label_set_text (GTK_LABEL (data->last_sig), sig_str); } void calendar_month_changed( GtkWidget *widget, CalendarData *data ) { char buffer[256] = "month_changed: "; calendar_date_to_string (data, buffer+15, 256-15); calendar_set_signal_strings (buffer, data); } void calendar_day_selected( GtkWidget *widget, CalendarData *data ) { char buffer[256] = "day_selected: "; calendar_date_to_string (data, buffer+14, 256-14); calendar_set_signal_strings (buffer, data); } void calendar_day_selected_double_click( GtkWidget *widget, CalendarData *data ) { struct tm tm; char buffer[256] = "day_selected_double_click: "; calendar_date_to_string (data, buffer+27, 256-27); calendar_set_signal_strings (buffer, data); memset (&tm, 0, sizeof (tm)); gtk_calendar_get_date (GTK_CALENDAR (data->window), &tm.tm_year, &tm.tm_mon, &tm.tm_mday); tm.tm_year -= TM_YEAR_BASE; if (GTK_CALENDAR (data->window)->marked_date[tm.tm_mday-1] == 0) { gtk_calendar_mark_day (GTK_CALENDAR (data->window), tm.tm_mday); } else { gtk_calendar_unmark_day (GTK_CALENDAR (data->window), tm.tm_mday); } } void calendar_prev_month( GtkWidget *widget, CalendarData *data ) { char buffer[256] = "prev_month: "; calendar_date_to_string (data, buffer+12, 256-12); calendar_set_signal_strings (buffer, data); } void calendar_next_month( GtkWidget *widget, CalendarData *data ) { char buffer[256] = "next_month: "; calendar_date_to_string (data, buffer+12, 256-12); calendar_set_signal_strings (buffer, data); } void calendar_prev_year( GtkWidget *widget, CalendarData *data ) { char buffer[256] = "prev_year: "; calendar_date_to_string (data, buffer+11, 256-11); calendar_set_signal_strings (buffer, data); } void calendar_next_year( GtkWidget *widget, CalendarData *data ) { char buffer[256] = "next_year: "; calendar_date_to_string (data, buffer+11, 256-11); calendar_set_signal_strings (buffer, data); } void calendar_set_flags( CalendarData *calendar ) { gint i; gint options = 0; for (i = 0; i < 5; i++) if (calendar->settings[i]) { options=options + (1<<i); } if (calendar->window) gtk_calendar_display_options (GTK_CALENDAR (calendar->window), options); } void calendar_toggle_flag( GtkWidget *toggle, CalendarData *calendar ) { gint i; gint j; j = 0; for (i = 0; i < 5; i++) if (calendar->flag_checkboxes[i] == toggle) j = i; calendar->settings[j] = !calendar->settings[j]; calendar_set_flags (calendar); } void calendar_font_selection_ok( GtkWidget *button, CalendarData *calendar ) { GtkStyle *style; PangoFontDescription *font_desc; calendar->font = gtk_font_selection_dialog_get_font_name ( GTK_FONT_SELECTION_DIALOG (calendar->font_dialog)); if (calendar->window) { font_desc = pango_font_description_from_string (calendar->font); if (font_desc) { style = gtk_style_copy (gtk_widget_get_style (calendar->window)); style->font_desc = font_desc; gtk_widget_set_style (calendar->window, style); } } } void calendar_select_font( GtkWidget *button, CalendarData *calendar ) { GtkWidget *window; if (!calendar->font_dialog) { window = gtk_font_selection_dialog_new ("Font Selection Dialog"); g_return_if_fail (GTK_IS_FONT_SELECTION_DIALOG (window)); calendar->font_dialog = window; gtk_window_set_position (GTK_WINDOW (window), GTK_WIN_POS_MOUSE); g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (gtk_widget_destroyed), (gpointer) &calendar->font_dialog); g_signal_connect (G_OBJECT (GTK_FONT_SELECTION_DIALOG (window)->ok_button), "clicked", G_CALLBACK (calendar_font_selection_ok), (gpointer) calendar); g_signal_connect_swapped (G_OBJECT (GTK_FONT_SELECTION_DIALOG (window)->cancel_button), "clicked", G_CALLBACK (gtk_widget_destroy), G_OBJECT (calendar->font_dialog)); } window=calendar->font_dialog; if (!GTK_WIDGET_VISIBLE (window)) gtk_widget_show (window); else gtk_widget_destroy (window); } void create_calendar() { GtkWidget *window; GtkWidget *vbox, *vbox2, *vbox3; GtkWidget *hbox; GtkWidget *hbbox; GtkWidget *calendar; GtkWidget *toggle; GtkWidget *button; GtkWidget *frame; GtkWidget *separator; GtkWidget *label; GtkWidget *bbox; static CalendarData calendar_data; gint i; struct { char *label; } flags[] = { { "Show Heading" }, { "Show Day Names" }, { "No Month Change" }, { "Show Week Numbers" }, { "Week Start Monday" } }; calendar_data.window = NULL; calendar_data.font = NULL; calendar_data.font_dialog = NULL; for (i = 0; i < 5; i++) { calendar_data.settings[i] = 0; } window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_window_set_title (GTK_WINDOW (window), "GtkCalendar Example"); gtk_container_set_border_width (GTK_CONTAINER (window), 5); g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (gtk_main_quit), NULL); g_signal_connect (G_OBJECT (window), "delete-event", G_CALLBACK (gtk_false), NULL); gtk_window_set_resizable (GTK_WINDOW (window), FALSE); vbox = gtk_vbox_new (FALSE, DEF_PAD); gtk_container_add (GTK_CONTAINER (window), vbox); /* * Главная часть окна, Календарь, переключатели и выбор шрифта. */ hbox = gtk_hbox_new (FALSE, DEF_PAD); gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, DEF_PAD); hbbox = gtk_hbutton_box_new (); gtk_box_pack_start (GTK_BOX (hbox), hbbox, FALSE, FALSE, DEF_PAD); gtk_button_box_set_layout (GTK_BUTTON_BOX(hbbox), GTK_BUTTONBOX_SPREAD); gtk_box_set_spacing (GTK_BOX (hbbox), 5); /* Calendar widget */ frame = gtk_frame_new ("Calendar"); gtk_box_pack_start (GTK_BOX (hbbox), frame, FALSE, TRUE, DEF_PAD); calendar=gtk_calendar_new (); calendar_data.window = calendar; calendar_set_flags (&calendar_data); gtk_calendar_mark_day (GTK_CALENDAR (calendar), 19); gtk_container_add( GTK_CONTAINER (frame), calendar); g_signal_connect (G_OBJECT (calendar), "month_changed", G_CALLBACK (calendar_month_changed), (gpointer) &calendar_data); g_signal_connect (G_OBJECT (calendar), "day_selected", G_CALLBACK (calendar_day_selected), (gpointer) &calendar_data); g_signal_connect (G_OBJECT (calendar), "day_selected_double_click", G_CALLBACK (calendar_day_selected_double_click), (gpointer) &calendar_data); g_signal_connect (G_OBJECT (calendar), "prev_month", G_CALLBACK (calendar_prev_month), (gpointer) &calendar_data); g_signal_connect (G_OBJECT (calendar), "next_month", G_CALLBACK (calendar_next_month), (gpointer) &calendar_data); g_signal_connect (G_OBJECT (calendar), "prev_year", G_CALLBACK (calendar_prev_year), (gpointer) &calendar_data); g_signal_connect (G_OBJECT (calendar), "next_year", G_CALLBACK (calendar_next_year), (gpointer) &calendar_data); separator = gtk_vseparator_new (); gtk_box_pack_start (GTK_BOX (hbox), separator, FALSE, TRUE, 0); vbox2 = gtk_vbox_new (FALSE, DEF_PAD); gtk_box_pack_start (GTK_BOX (hbox), vbox2, FALSE, FALSE, DEF_PAD); /* Создаём рамку справа для размещения в ней переключателей */ frame = gtk_frame_new ("Flags"); gtk_box_pack_start (GTK_BOX (vbox2), frame, TRUE, TRUE, DEF_PAD); vbox3 = gtk_vbox_new (TRUE, DEF_PAD_SMALL); gtk_container_add (GTK_CONTAINER (frame), vbox3); for (i = 0; i < 5; i++) { toggle = gtk_check_button_new_with_label (flags[i].label); g_signal_connect (G_OBJECT (toggle), "toggled", G_CALLBACK (calendar_toggle_flag), (gpointer) &calendar_data); gtk_box_pack_start (GTK_BOX (vbox3), toggle, TRUE, TRUE, 0); calendar_data.flag_checkboxes[i] = toggle; } /* Создаём правую кнопку выбора шрифта (font-button) */ button = gtk_button_new_with_label ("Font..."); g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (calendar_select_font), (gpointer) &calendar_data); gtk_box_pack_start (GTK_BOX (vbox2), button, FALSE, FALSE, 0); /* * Создаём сигналы и события (Build the Signal-event part). */ frame = gtk_frame_new ("Signal events"); gtk_box_pack_start (GTK_BOX (vbox), frame, TRUE, TRUE, DEF_PAD); vbox2 = gtk_vbox_new (TRUE, DEF_PAD_SMALL); gtk_container_add (GTK_CONTAINER (frame), vbox2); hbox = gtk_hbox_new (FALSE, 3); gtk_box_pack_start (GTK_BOX (vbox2), hbox, FALSE, TRUE, 0); label = gtk_label_new ("Signal:"); gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, TRUE, 0); calendar_data.last_sig = gtk_label_new (""); gtk_box_pack_start (GTK_BOX (hbox), calendar_data.last_sig, FALSE, TRUE, 0); hbox = gtk_hbox_new (FALSE, 3); gtk_box_pack_start (GTK_BOX (vbox2), hbox, FALSE, TRUE, 0); label = gtk_label_new ("Previous signal:"); gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, TRUE, 0); calendar_data.prev_sig = gtk_label_new (""); gtk_box_pack_start (GTK_BOX (hbox), calendar_data.prev_sig, FALSE, TRUE, 0); hbox = gtk_hbox_new (FALSE, 3); gtk_box_pack_start (GTK_BOX (vbox2), hbox, FALSE, TRUE, 0); label = gtk_label_new ("Second previous signal:"); gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, TRUE, 0); calendar_data.prev2_sig = gtk_label_new (""); gtk_box_pack_start (GTK_BOX (hbox), calendar_data.prev2_sig, FALSE, TRUE, 0); bbox = gtk_hbutton_box_new (); gtk_box_pack_start (GTK_BOX (vbox), bbox, FALSE, FALSE, 0); gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), GTK_BUTTONBOX_END); button = gtk_button_new_with_label ("Close"); g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (gtk_main_quit), NULL); gtk_container_add (GTK_CONTAINER (bbox), button); GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT); gtk_widget_grab_default (button); gtk_widget_show_all (window); } int main(int argc, char *argv[] ) { gtk_init (&argc, &argv); create_calendar (); gtk_main (); return 0; } |
Combo Box |
Color Selection |
GTK+ 2.0 Tutorial |
||
---|---|---|
Miscellaneous Widgets |
Виджет выбора цвета является достаточно сложным и позволяет выбирать цвет манипулируя треугольником в цветовой схеме RGB (Red(красный), Green(зелёный), Blue(синий)) и HSV (Hue(оттенок), Saturation(насыщенность), Value(значение)). Цвет можно выбирать используя простые слайдеры или вращая треугольную область насыщенности и оттенка вокруг цветовой шкалы. Опционально может быть установлена прозрачность.
Виджет выбор цвета создаёт только один сигнал, "color_changed", он производится когда происходит изменение цвета пользователем, или через функцию gtk_color_selection_set_color().
Виджет имеет две особенности: GtkColorSelection и GtkColorSelectionDialog.
GtkWidget *gtk_color_selection_new( void ); |
Вам не прийдется использовать этот конструктор непосредственно. Это создаёт шаблонный виджет ColorSelection который вы сделаете основным самостоятельно. Виджет ColorSelection наследует от виджета VBox.
GtkWidget *gtk_color_selection_dialog_new( const gchar *title ); |
Самый основной конструктор color selection. Это создаёт диалог ColorSelectionDialog. Он состоит из рамки содержащей виджет ColorSelection, HSeparator и HBox с тремя кнопками, "Ok", "Cancel" и "Help". Вы можете получить доступ к этим кнопкам через виджеты "ok_button", "cancel_button" и "help_button" в структуре ColorSelectionDialog, (т,е, GTK_COLOR_SELECTION_DIALOG (colorseldialog)->ok_button)).
void gtk_color_selection_set_has_opacity_control( GtkColorSelection *colorsel, gboolean has_opacity ); |
Виджет выбор цвета поддерживает регулировку полупрозрачности (известную также как alpha channel), которая отключена по умолчанию. Вызов этой функции со значением аргумента has_opacity установленным в TRUE включает полупрозрачность. Соответственно значение FALSE отключает полупрозрачность.
void gtk_color_selection_set_current_color( GtkColorSelection *colorsel, GdkColor *color ); void gtk_color_selection_set_current_alpha( GtkColorSelection *colorsel, guint16 alpha ); |
Вы можете установить текущий цвет явно, вызвав gtk_color_selection_set_current_color() с указателем на GdkColor. Установка полупрозрачности (alpha channel) делается с помощью gtk_color_selection_set_current_alpha(). Значение alpha должно быть между 0 (полностью прозрачно) и 65636 (полностью непрозрачно).
void gtk_color_selection_get_current_color( GtkColorSelection *colorsel, GdkColor *color ); void gtk_color_selection_get_current_alpha( GtkColorSelection *colorsel, guint16 *alpha ); |
Эти функции используются когда вам нужно получить текущее значение цвета при получении сигнала "color_changed".
Вот - простой пример, демонстрирующий использование ColorSelectionDialog. Программа отображает окно, содержащее область рисунка. Нажатие на кнопку открывает диалог выбора цвета и изменение цвета в диалоге выбора цвета, изменяет цвет фона самой программы.
Calendar |
File Selections |
GTK+ 2.0 Tutorial |
||
---|---|---|
Разные виджеты (Miscellaneous Widgets) |
Виджет выбора файла - быстрый и простой способ показать диалоговое окно File. Окно полностью укомплектовано всем необходимым включая кнопки Ok и Cancel что позволяет сэкономить время при программировании.
Диалог выбора файлов создаётся функцией:
GtkWidget *gtk_file_selection_new( const gchar *title ); |
Установить имя файла для примера в определённом каталоге или получить имя файла по умолчанию:
void gtk_file_selection_set_filename( GtkFileSelection *filesel, const gchar *filename ); |
Для захвата текста введенного пользователем используется эта функция:
gchar *gtk_file_selection_get_filename( GtkFileSelection *filesel ); |
Есть также указатели на виджеты, находящиеся в пределах виджета выбора файла:
dir_list file_list selection_entry selection_text main_vbox ok_button cancel_button help_button |
Наиболее используемые указатели ok_button, cancel_button и help_button.
Модифицированный пример из testgtk.c.
#include <gtk/gtk.h> /* Получаем выбранное имя файла и выводим на консоль */ void file_ok_sel( GtkWidget *w, GtkFileSelection *fs ) { g_print ("%s\n", gtk_file_selection_get_filename (GTK_FILE_SELECTION (fs))); } int main( int argc, char *argv[] ) { GtkWidget *filew; gtk_init (&argc, &argv); /* Создаём новый виджет выбора файлов */ filew = gtk_file_selection_new ("File selection"); g_signal_connect (G_OBJECT (filew), "destroy", G_CALLBACK (gtk_main_quit), NULL); /* Соединяем ok_button с file_ok_sel функцией */ g_signal_connect (G_OBJECT (GTK_FILE_SELECTION (filew)->ok_button), "clicked", G_CALLBACK (file_ok_sel), (gpointer) filew); /* Соединяем cancel_button с закрытием виджета */ g_signal_connect_swapped (G_OBJECT (GTK_FILE_SELECTION (filew)->cancel_button), "clicked", G_CALLBACK (gtk_widget_destroy), G_OBJECT (filew)); /* Устанавливаем имя файла по умолчанию */ "penguin.png"); gtk_widget_show (filew); gtk_main (); return 0; } |
Color Selection |
Container Widgets |
GTK+ 2.0 Tutorial |
||
---|---|---|
Container Widgets |
Виджет выравнивания позволяет вам помещать виджет в пределах его окна в позиции и размере относительно размера виджета Выравнивания непосредственно. Например, это может быть очень полезным чтобы сосредоточить виджет в пределах окна.
Есть только две функции, связанные с виджетом Выравнивания:
GtkWidget* gtk_alignment_new( gfloat xalign, gfloat yalign, gfloat xscale, gfloat yscale ); void gtk_alignment_set( GtkAlignment *alignment, gfloat xalign, gfloat yalign, gfloat xscale, gfloat yscale ); |
Первая создаёт виджет выравнивания со специфическими параметрами. Вторая функция позволяет изменять эти параметры.
Все четыре параметра выравнивания десятичные числа, которые могут расположиться от 0.0 до 1.0. Параметры xalign и yalign создают позицию виджета, помещенного в пределах виджета Выравнивания. Параметры xscale и yscale определяют свободное пространство выделенное для виджета.
Дочерний виджет помещается в виджет выравнивания используя:
gtk_container_add (GTK_CONTAINER (alignment), child_widget); |
Пример виджета выравнивания можно посмотреть в Progress Bar widget.
Container Widgets |
Fixed Container |
GTK+ 2.0 Tutorial |
||
---|---|---|
Контейнерные виджеты (Container Widgets) |
Неподвижный контейнер позволяет помещать виджеты, неподвижно в пределах окна, в позиции относительно верхнего левого угла. Позиция виджетов может быть изменена динамически.
Немногие функции связаны с неподвижным виджетом:
GtkWidget* gtk_fixed_new( void ); void gtk_fixed_put( GtkFixed *fixed, GtkWidget *widget, gint x, gint y ); void gtk_fixed_move( GtkFixed *fixed, GtkWidget *widget, gint x, gint y ); |
Функция gtk_fixed_new() - создаёт неподвижный контейнер.
gtk_fixed_put() - помещает дочерний виджет в неподвижный контейнер, в позицию относительно x и y.
gtk_fixed_move() - позволяет перемещать виджет в новую позицию.
void gtk_fixed_set_has_window( GtkFixed *fixed, gboolean has_window ); gboolean gtk_fixed_get_has_window( GtkFixed *fixed ); |
Обычно, Неподвижные виджеты не имеют собственного окна X. Так как это отличается от поведения Неподвижных виджетов в более ранних выпусках GTK, функция gtk_fixed_set_has_window() позволяет создание Неподвижных виджетов с их собственным окном.
Следующий пример демонстрирует использование неподвижных контейнеров (Fixed Container).
The Alignment widget |
Layout Container |
GTK+ 2.0 Tutorial |
||
---|---|---|
Container Widgets |
Контейнер Размещения подобен Неподвижному контейнеру за исключением того, что он имеет большое количество (где бесконечность - меньше чем 2^32) прокручиваемой области. Система X window имеет ограничения, окна не могут превышать ширину или длину в 32767 pixels. Контейнер Размещения обходит это ограничение создавая экзотическое окно использования материала так, чтобы вы могли плавно прокручивать область даже когда в ней находится много дочерних виджетов.
Контейнер размещения создаётся так:
GtkWidget *gtk_layout_new( GtkAdjustment *hadjustment, GtkAdjustment *vadjustment ); |
Как вы можете заметить, Контейнер Размещения использует специальные объекты регулирования для прокручивания области размещения.
Вы можете добавлять или удалять виджеты из контейнера размещения при помощи функций:
void gtk_layout_put( GtkLayout *layout, GtkWidget *widget, gint x, gint y ); void gtk_layout_move( GtkLayout *layout, GtkWidget *widget, gint x, gint y ); |
Функция для установки размера контейнера:
void gtk_layout_set_size( GtkLayout *layout, guint width, guint height ); |
Последние функции предназначены для манипулирования, по горизонтали или вертикали, виджетами размещенными в контейнере:
GtkAdjustment* gtk_layout_get_hadjustment( GtkLayout *layout ); GtkAdjustment* gtk_layout_get_vadjustment( GtkLayout *layout ); void gtk_layout_set_hadjustment( GtkLayout *layout, GtkAdjustment *adjustment ); void gtk_layout_set_vadjustment( GtkLayout *layout, GtkAdjustment *adjustment); |
Fixed Container |
Frames |
GTK+ 2.0 Tutorial |
||
---|---|---|
Getting Started |
Для компиляции воспользуйтесь командой:
gcc -Wall -g
helloworld.c -o helloworld `pkg-config --cflags gtk+-2.0`
\
`pkg-config --libs gtk+-2.0`
Здесь используется
программа pkg-config, которую
можно получить на www.freedesktop.org.
Эта программа читает .pc
которым комплектуется GTK для определения
ключей компилятора необходимых для
сборки. pkg-config --cflags gtk+-2.0
создаёт поток вывода в виде списка
содержимого каталога компилятора, а
pkg-config --libs gtk+-2.0 вывод
списка найденных библиотек необходимых
для связи (link) при компиляции. В выше
приведенном примере команды могли быть
объединены в одну, например
`pkg-config
--cflags --libs gtk+-2.0`.
Обратите внимание что данный тип команды существенен для компиляции.
Библиотеки которые обычно нужны для связки (linked):
GTK library (-lgtk) - библиотека виджетов, основана на GDK.
GDK library (-lgdk) - оболочка для Xlib.
gdk-pixbuf library (-lgdk_pixbuf) - библиотека манипулирования изображениями.
Pango - библиотека (-lpango) для интернационализации текстовой информации.
gobject library (-lgobject) - содержит системные типы на которых базируется GTK.
gmodule library (-lgmodule) - используется для загрузки модулей и расширений.
GLib library (-lglib) - содержит разные функции; только g_print() используется в специфических примерах. GTK основан на GLib поэтому всегда зависит от этих библиотек. Подробности смотрите в секции GLib.
Xlib library (-lX11) используется GDK.
Xext library (-lXext) - содержит код для совместного использования памяти pixmaps и других расширений X.
math library (-lm) - математическая библиотека для различных целей GTK.
Getting Started |
Theory of Signals and Callbacks |
GTK+ 2.0 Tutorial |
||
---|---|---|
Container Widgets |
Рамки могут использоваться для ограничения одного или группы виджетов с установкой произвольно маркированного поля. Позиция и стиль поля маркировки может изменяться.
Рамка создаётся так:
GtkWidget *gtk_frame_new( const gchar *label ); |
Поле маркировки по умолчанию располагается вверху слева. Значение NULL в качестве аргумента label, отключает отображение маркировки. Текст маркировки может быть изменён с помощью следующей функции.
void gtk_frame_set_label( GtkFrame *frame, const gchar *label ); |
Позиция поля маркировки изменяется так:
void gtk_frame_set_label_align( GtkFrame *frame, gfloat xalign, gfloat yalign ); |
xalign и yalign могут иметь значения между 0.0 и 1.0. Аргумент xalign указывает позицию по горизонтали на вершине рамки. yalign в настоящее время не используется. Значение по умолчанию xalign - 0.0, это помещает метку в левом конце рамки.
Следующая функция изменяет стиль поля, которое используется, чтобы выделить рамку.
void gtk_frame_set_shadow_type( GtkFrame *frame, GtkShadowType type); |
Аргумент type может принимать одно из следующих значений:
GTK_SHADOW_NONE GTK_SHADOW_IN GTK_SHADOW_OUT GTK_SHADOW_ETCHED_IN (по умолчанию) GTK_SHADOW_ETCHED_OUT |
Следующий пример кода демонстрирует использование рамки.
#include <gtk/gtk.h> int main( int argc, char *argv[] ) { /* GtkWidget содержит типы виджетов */ GtkWidget *window; GtkWidget *frame; /* Инициализируем GTK */ gtk_init (&argc, &argv); /* Создаём новое окно */ window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_window_set_title (GTK_WINDOW (window), "Frame Example"); g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (gtk_main_quit), NULL); gtk_widget_set_size_request (window, 300, 300); /* Устанавливаем окантовку окна. */ gtk_container_set_border_width (GTK_CONTAINER (window), 10); /* Создаём рамку */ frame = gtk_frame_new (NULL); gtk_container_add (GTK_CONTAINER (window), frame); /* Устанавливаем маркировку рамки */ gtk_frame_set_label (GTK_FRAME (frame), "GTK Frame Widget"); /* Выравниваем маркировку справа */ gtk_frame_set_label_align (GTK_FRAME (frame), 1.0, 0.0); /* Устанавливаем стиль рамки */ gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_OUT); gtk_widget_show (frame); /* Отображаем окно */ gtk_widget_show (window); /* Входим в цикл */ gtk_main (); return 0; } |
Layout Container |
Aspect Frames |
GTK+ 2.0 Tutorial |
||
---|---|---|
Container Widgets |
Пропорциональная рамка очень похожа на виджет рамка, за исключением того, что стороны виджета имеют определенную пропорцию соотношения ширины и высоты рамки. Это может пригодится при просмотре изображений. Размер предварительного просмотра должен измениться, когда пользователь изменяет размеры окна, но соотношение сторон должно всегда соответствовать оригинальному изображению.
GtkWidget *gtk_aspect_frame_new( const gchar *label, gfloat xalign, gfloat yalign, gfloat ratio, gboolean obey_child); |
xalign и yalign определяют выравнивание как в Alignment widgets. Если obey_child равен TRUE, то пропорции дочернего виджета будут соответствовать коэффициенту сжатия идеального размера. Иначе будет использовано отношение сторон.
Установки можно изменить функцией:
void gtk_aspect_frame_set( GtkAspectFrame *aspect_frame, gfloat xalign, gfloat yalign, gfloat ratio, gboolean obey_child); |
В следующем примере приведен код аспект рамки, имеющей соотношение сторон 2х1, при изменении размера окна меняется размер рамки, но не меняется соотношение сторон.
#include <gtk/gtk.h> int main( int argc, char *argv[] ) { GtkWidget *window; GtkWidget *aspect_frame; GtkWidget *drawing_area; gtk_init (&argc, &argv); window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_window_set_title (GTK_WINDOW (window), "Aspect Frame"); g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (gtk_main_quit), NULL); gtk_container_set_border_width (GTK_CONTAINER (window), 10); /* Создаём аспект рамку и помещаем в основное окно */ aspect_frame = gtk_aspect_frame_new ("2x1", /* label */ 0.5, /* center x */ 0.5, /* center y */ 2, /* xsize/ysize = 2 */ FALSE /* ignore child's aspect */); gtk_container_add (GTK_CONTAINER (window), aspect_frame); gtk_widget_show (aspect_frame); /* Добавляем дочерний виджет в аспект рамку */ drawing_area = gtk_drawing_area_new (); /* При размере она в 200x200, рамка имеет размер 200x100 * потому, что соотношение установлено в 2х1 */ gtk_widget_set_size_request (drawing_area, 200, 200); gtk_container_add (GTK_CONTAINER (aspect_frame), drawing_area); gtk_widget_show (drawing_area); gtk_widget_show (window); gtk_main (); return 0; } |
Frames |
Paned Window Widgets |
GTK+ 2.0 Tutorial |
||
---|---|---|
Container Widgets |
Разделяемые окна используются когда нужно создать две части, относительного размера одного окна, контролируемые пользователем. Между частями существует специальный маркер для изменения размера частей при его захвате и перетаскивании. Разделение может быть как горизонтальным (HPaned), так и вертикальным (VPaned).
Создание нового разделённого окна:
GtkWidget *gtk_hpaned_new (void); GtkWidget *gtk_vpaned_new (void); |
После создания двух разделённых частей может потребоваться размещение в них дочерних виджетов. Это делается так:
void gtk_paned_add1 (GtkPaned *paned, GtkWidget *child); void gtk_paned_add2 (GtkPaned *paned, GtkWidget *child); |
gtk_paned_add1() добавляет дочерний виджет в левую или верхнюю часть разделённого окна. gtk_paned_add2() добавляет дочерний виджет в правую или нижнюю часть разделённого окна.
Как пример, мы создадим часть пользовательского интерфейса воображаемой почтовой программы. Окно разделено на две части вертикально, с главной частью, являющейся списком почтовых сообщений и части основания - текст почтового сообщения.
Большая часть программы достаточно проста. В ней есть несколько строк для информации в которые не может быть добавлен текст. Это могло быть выполнено вызовом функции gtk_widget_realize(), но как демонстрация дополнительной методики, мы подключаем обработчик с сигналом "realize" - добавить текст. Кроме того, мы должны добавить опцию GTK_SHRINK к некоторым из элементов в таблице, содержащей текстовое окно и его полосы прокрутки, чтобы, когда часть основания уменьшается, сжались правильные части виджета, вместо того, чтобы размешаться в основании окна.
Aspect Frames |
Viewports |
GTK+ 2.0 Tutorial |
||
---|---|---|
Виджеты контейнеры (Container Widgets) |
Маловероятно, что вам когда либо прийдется использовать окно просмотра непосредственно, скорее всего вам следует использовать виджет Scrolled Window, который включает в себя виджет «окно просмотра».
Окно просмотра позволяет поместить больший виджет внутри себя, чтобы вы могли просматривать его по частям. Виджет использует Adjustments, чтобы определить область, которая является в настоящее время в поле зрения.
Окно просмотра создаётся с помощью функции:
GtkWidget *gtk_viewport_new( GtkAdjustment *hadjustment, GtkAdjustment *vadjustment ); |
Как вы можете видеть допускаются как вертикальные так и горизонтальные регулировки.
Вы можете получить и установить регуляторы, после того как виджет был создан, используя следующие четыре функции:
GtkAdjustment *gtk_viewport_get_hadjustment (GtkViewport *viewport ); GtkAdjustment *gtk_viewport_get_vadjustment (GtkViewport *viewport ); void gtk_viewport_set_hadjustment( GtkViewport *viewport, GtkAdjustment *adjustment ); void gtk_viewport_set_vadjustment( GtkViewport *viewport, GtkAdjustment *adjustment ); |
Единственная функция области просмотра используется, чтобы изменить ее внешность:
void gtk_viewport_set_shadow_type( GtkViewport *viewport, GtkShadowType type ); |
Возможные значения параметра type :
GTK_SHADOW_NONE, GTK_SHADOW_IN, GTK_SHADOW_OUT, GTK_SHADOW_ETCHED_IN, GTK_SHADOW_ETCHED_OUT |
Paned Window Widgets |
Scrolled Windows |
GTK+ 2.0 Tutorial |
||
---|---|---|
Container Widgets |
Прокручиваемое окно (Scrolled windows) используется для создания большой области содержащей другие виджеты с возможность постепенного просмотра содержимого. Вы можете вставлять в прокручиваемое окно любой тип виджетов и содержимое будет доступно несмотря на размер, благодаря scrollbars.
Функция для создания прокручиваемого окна:
GtkWidget *gtk_scrolled_window_new( GtkAdjustment *hadjustment, GtkAdjustment *vadjustment ); |
Первый аргумент отвечает за горизонтальное регулирование, второй за вертикальное. Все значения могут быть равны NULL.
void gtk_scrolled_window_set_policy( GtkScrolledWindow *scrolled_window, GtkPolicyType hscrollbar_policy, GtkPolicyType vscrollbar_policy ); |
Эта функция устанавливает политику использования полосы прокручивания. Первый аргумент - окно прокручивания которое вы хотите настроить. Второй - политика горизонтального прокручивания и третий - политика вертикального прокручивания.
Политика может быть двух типов, GTK_POLICY_AUTOMATIC или GTK_POLICY_ALWAYS. GTK_POLICY_AUTOMATIC автоматически определяет необходимость появления полосы прокручивания, а GTK_POLICY_ALWAYS всегда показывает полосу прокручивания.
Вы можете размещать объекты в окне прокручивания с помощью функции:
void gtk_scrolled_window_add_with_viewport( GtkScrolledWindow *scrolled_window, GtkWidget *child); |
В этом примере в окно прокручивания помещено 100 кнопок переключателей. В исходном коде прокомментированны только плохо знакомые вам части.
Попробуйте по изменять размеры окна, чтобы увидеть реакцию полос прокручивания. Вы можете использовать функцию gtk_widget_set_size_request() для установки размеров окна или других виджетов по умолчанию.
Viewports |
Button Boxes |
GTK+ 2.0 Tutorial |
||
---|---|---|
Виджеты контейнеры (Container Widgets) |
Контейнеры кнопок позволяют быстро размещать группы кнопок с горизонтальным или вертикальным положением. Для создания контейнеров кнопок используются функции:
GtkWidget *gtk_hbutton_box_new( void ); GtkWidget *gtk_vbutton_box_new( void ); |
Для добавления в контейнер кнопок применяется:
gtk_container_add (GTK_CONTAINER (button_box), child_widget); |
Пример демонстрирует разные настройки контейнеров кнопок
#include <gtk/gtk.h> /* Создаём контейнер кнопок со специфическими параметрами */ GtkWidget *create_bbox( gint horizontal, char *title, gint spacing, gint child_w, gint child_h, gint layout ) { GtkWidget *frame; GtkWidget *bbox; GtkWidget *button; frame = gtk_frame_new (title); if (horizontal) bbox = gtk_hbutton_box_new (); else bbox = gtk_vbutton_box_new (); gtk_container_set_border_width (GTK_CONTAINER (bbox), 5); gtk_container_add (GTK_CONTAINER (frame), bbox); /* Установка внешнего вида контейнера */ gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), layout); gtk_box_set_spacing (GTK_BOX (bbox), spacing); /*gtk_button_box_set_child_size (GTK_BUTTON_BOX (bbox), child_w, child_h);*/ button = gtk_button_new_from_stock (GTK_STOCK_OK); gtk_container_add (GTK_CONTAINER (bbox), button); button = gtk_button_new_from_stock (GTK_STOCK_CANCEL); gtk_container_add (GTK_CONTAINER (bbox), button); button = gtk_button_new_from_stock (GTK_STOCK_HELP); gtk_container_add (GTK_CONTAINER (bbox), button); return frame; } int main( int argc, char *argv[] ) { static GtkWidget* window = NULL; GtkWidget *main_vbox; GtkWidget *vbox; GtkWidget *hbox; GtkWidget *frame_horz; GtkWidget *frame_vert; /* Инициализируем GTK */ gtk_init (&argc, &argv); window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_window_set_title (GTK_WINDOW (window), "Button Boxes"); g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (gtk_main_quit), NULL); gtk_container_set_border_width (GTK_CONTAINER (window), 10); main_vbox = gtk_vbox_new (FALSE, 0); gtk_container_add (GTK_CONTAINER (window), main_vbox); frame_horz = gtk_frame_new ("Горизонтальный контейнер кнопок"); gtk_box_pack_start (GTK_BOX (main_vbox), frame_horz, TRUE, TRUE, 10); vbox = gtk_vbox_new (FALSE, 0); gtk_container_set_border_width (GTK_CONTAINER (vbox), 10); gtk_container_add (GTK_CONTAINER (frame_horz), vbox); gtk_box_pack_start (GTK_BOX (vbox), create_bbox (TRUE, "Spread (spacing 40)", 40, 85, 20, GTK_BUTTONBOX_SPREAD), TRUE, TRUE, 0); gtk_box_pack_start (GTK_BOX (vbox), create_bbox (TRUE, "Edge (spacing 30)", 30, 85, 20, GTK_BUTTONBOX_EDGE), TRUE, TRUE, 5); gtk_box_pack_start (GTK_BOX (vbox), create_bbox (TRUE, "Start (spacing 20)", 20, 85, 20, GTK_BUTTONBOX_START), TRUE, TRUE, 5); gtk_box_pack_start (GTK_BOX (vbox), create_bbox (TRUE, "End (spacing 10)", 10, 85, 20, GTK_BUTTONBOX_END), TRUE, TRUE, 5); frame_vert = gtk_frame_new ("Вертикальный контейнер кнопок"); gtk_box_pack_start (GTK_BOX (main_vbox), frame_vert, TRUE, TRUE, 10); hbox = gtk_hbox_new (FALSE, 0); gtk_container_set_border_width (GTK_CONTAINER (hbox), 10); gtk_container_add (GTK_CONTAINER (frame_vert), hbox); gtk_box_pack_start (GTK_BOX (hbox), create_bbox (FALSE, "Spread (spacing 5)", 5, 85, 20, GTK_BUTTONBOX_SPREAD), TRUE, TRUE, 0); gtk_box_pack_start (GTK_BOX (hbox), create_bbox (FALSE, "Edge (spacing 30)", 30, 85, 20, GTK_BUTTONBOX_EDGE), TRUE, TRUE, 5); gtk_box_pack_start (GTK_BOX (hbox), create_bbox (FALSE, "Start (spacing 20)", 20, 85, 20, GTK_BUTTONBOX_START), TRUE, TRUE, 5); gtk_box_pack_start (GTK_BOX (hbox), create_bbox (FALSE, "End (spacing 20)", 20, 85, 20, GTK_BUTTONBOX_END), TRUE, TRUE, 5); gtk_widget_show_all (window); /* Входим в цикл ожидания событий */ gtk_main (); return 0; } |
Scrolled Windows |
Toolbar |
GTK+ 2.0 Tutorial |
||
---|---|---|
Контейнерные виджеты (Container Widgets) |
Панели инструментов обычно используются для группировки некоторого числа виджетов, чтобы упростить настройку их вида и расположения. Обычно панель состоит из кнопок с изображениями и текстовыми ярлыками, но можно добавить и любой другой виджет на панель. Объекты на панели могут выравниваться горизонтально или вертикально, а кнопки могут отображаться с пиктограммами, текстовыми ярлыками или с тем и другим сразу.
GtkWidget *gtk_toolbar_new( void ); |
После создания панели в неё можно добавлять пункты (короткие текстовые строки) или элементы (любые виджеты). Чтобы описать пункт, нужен текстовый ярлык, пиктограмма, текст подсказки, текст расширенной подсказки и функция обратного вызова. Для прикрепления к панели пункта можно использовать функции:
GtkWidget *gtk_toolbar_append_item( GtkToolbar *toolbar, const char *text, const char *tooltip_text, const char *tooltip_private_text, GtkWidget *icon, GtkSignalFunc callback, gpointer user_data ); GtkWidget *gtk_toolbar_prepend_item( GtkToolbar *toolbar, const char *text, const char *tooltip_text, const char *tooltip_private_text, GtkWidget *icon, GtkSignalFunc callback, gpointer user_data ); |
Если Вы хотите использовать gtk_toolbar_insert_item(), единственный дополнительный параметр который должен быть определен - положение в которое пункт должен быть вставлен, выглядит это таким образом:
GtkWidget *gtk_toolbar_insert_item( GtkToolbar *toolbar, const char *text, const char *tooltip_text, const char *tooltip_private_text, GtkWidget *icon, GtkSignalFunc callback, gpointer user_data, gint position ); |
Чтобы упростить добавку места между пунктами панели инструментов, вы можете использовать следующие функции:
void gtk_toolbar_append_space( GtkToolbar *toolbar ); void gtk_toolbar_prepend_space( GtkToolbar *toolbar ); void gtk_toolbar_insert_space( GtkToolbar *toolbar, gint position ); |
Если требуется, ориентация панели инструментов и ее стиля может быть изменена "на лету" используя следующие функции:
void gtk_toolbar_set_orientation( GtkToolbar *toolbar, GtkOrientation orientation ); void gtk_toolbar_set_style( GtkToolbar *toolbar, GtkToolbarStyle style ); void gtk_toolbar_set_tooltips( GtkToolbar *toolbar, gint enable ); |
Где аргумент orientation одно из двух значений GTK_ORIENTATION_HORIZONTAL или GTK_ORIENTATION_VERTICAL. Аргумент style используется для установки одного из возможных вариантов отображения пунктов на панели GTK_TOOLBAR_ICONS, GTK_TOOLBAR_TEXT или GTK_TOOLBAR_BOTH.
Чтобы показать некоторые дополнительные действия, которые могут быть сделаны с панелью инструментов, давайте рассмотрим следующую программу (мы прервем пример с некоторыми дополнительными объяснениями):
#include <gtk/gtk.h> /* Эта функция подключается к кнопке Close или * закрытию окна с помощью WM */ gint delete_event (GtkWidget *widget, GdkEvent *event, gpointer data) { gtk_main_quit (); return FALSE; } |
Вышеупомянутое начало кажется наверняка знакомым, если это не первая ваша программа GTK. Дополнительно мы включили изображение (XPM picture) для всех кнопок.
GtkWidget* close_button; /* Эта кнопка создаёт сигнал для * закрытия приложения */ GtkWidget* tooltips_button; /* включить/выключить (enable/disable) подсказки */ GtkWidget* text_button, * icon_button, * both_button; /* кнопка переключения стиля панели */ GtkWidget* entry; /* ввод текста для упаковки в какой нибудь виджет при отображении на панели */ |
Фактически не все вышеупомянутые виджеты необходимы здесь, но для более ясного понимания мы разместили их все вместе.
/* при переключении кнопки мы проверяем какой стиль был активным * и делаем соответствующие установки * ВНИМАНИЕ: панель передаётся как данные для обратного вызова! */ void radio_event (GtkWidget *widget, gpointer data) { if (GTK_TOGGLE_BUTTON (text_button)->active) gtk_toolbar_set_style (GTK_TOOLBAR (data), GTK_TOOLBAR_TEXT); else if (GTK_TOGGLE_BUTTON (icon_button)->active) gtk_toolbar_set_style (GTK_TOOLBAR (data), GTK_TOOLBAR_ICONS); else if (GTK_TOGGLE_BUTTON (both_button)->active) gtk_toolbar_set_style (GTK_TOOLBAR (data), GTK_TOOLBAR_BOTH); } /* проверяем переключатель для включения/выключения подсказок */ void toggle_event (GtkWidget *widget, gpointer data) { gtk_toolbar_set_tooltips (GTK_TOOLBAR (data), GTK_TOGGLE_BUTTON (widget)->active ); } |
В зависимости от того какая кнопка нажата, будет вызвана одна из двух функций. Вам знакомо это, если вы уже использовали переключатели и кнопки выбора (radio buttons).
Из всего вышеперечисленного в объяснении нуждается только «удобный контейнер» (handle box). «Удобный контейнер» используется для упаковки виджетов, но отличается от обычных контейнеров. Различие заключается в том, что «удобный контейнер» может быть отсоединен от основного окна для произвольного позиционирования виджетов размещенных в нем (в пределах родительского окна). Обычно он используется для создания съёмных панелей инструментов.
/* панель будет размещена горизонтально с пиктограммами и текстом, * между пунктами будет промежуток в 5pxl * и всё это будет упаковано в handlebox */ toolbar = gtk_toolbar_new (); gtk_toolbar_set_orientation (GTK_TOOLBAR (toolbar), GTK_ORIENTATION_HORIZONTAL); gtk_toolbar_set_style (GTK_TOOLBAR (toolbar), GTK_TOOLBAR_BOTH); gtk_container_set_border_width (GTK_CONTAINER (toolbar), 5); gtk_toolbar_set_space_size (GTK_TOOLBAR (toolbar), 5); gtk_container_add (GTK_CONTAINER (handlebox), toolbar); |
То, что сделано выше, является прямой инициализацией виджета панели инструментов.
/* наш первый пункт кнопка <close> */ iconw = gtk_image_new_from_file ("gtk.xpm"); /* icon widget */ close_button = gtk_toolbar_append_item (GTK_TOOLBAR (toolbar), /* наша панель */ "Close", /* ярлык кнопки */ "Closes this app", /* подсказка кнопки */ "Private", /* подробная подсказка */ iconw, /* icon widget */ GTK_SIGNAL_FUNC (delete_event), /* сигнал */ NULL); gtk_toolbar_append_space (GTK_TOOLBAR (toolbar)); /* пробел после пункта */ |
В вышеупомянутом коде вы видите самый простой способ добавления кнопки на панель инструментов. Как раз перед добавлением нового пункта, мы должны сконструировать виджет изображения (image widget), чтобы служить изображением для этого пункта; этот шаг должен будет быть повторен для каждого нового пункта. Также мы добавляем пробел после пункта для избежания касания с ним других пунктов. Как вы видите, функция gtk_toolbar_append_item() возвращает указатель на вновь созданную кнопку, вы можете работать с ним как обычно.
/* создаём группу кнопок выбора */ iconw = gtk_image_new_from_file ("gtk.xpm"); icon_button = gtk_toolbar_append_element ( GTK_TOOLBAR (toolbar), GTK_TOOLBAR_CHILD_RADIOBUTTON, /* тип элемента */ NULL, /* указатель на виджет */ "Icon", /* ярлык */ "Only icons in toolbar", /* подсказка */ "Private", /* расширенная подсказка */ iconw, /* пиктограмма */ GTK_SIGNAL_FUNC (radio_event), /* сигнал */ toolbar); /* данные сигнала */ gtk_toolbar_append_space (GTK_TOOLBAR (toolbar)); |
Для создания группы переключателей мы использовали gtk_toolbar_append_element. Фактически используя эту функцию можно легко добавлять пункты или пробелы (type = GTK_TOOLBAR_CHILD_SPACE или +GTK_TOOLBAR_CHILD_BUTTON). В создании других радио-кнопок для этой группы требуется указатель на предыдущую кнопку в группе, чтобы легко построить список кнопок (см. секцию Radio Buttons).
/* following radio buttons refer to previous ones */ iconw = gtk_image_new_from_file ("gtk.xpm"); text_button = gtk_toolbar_append_element (GTK_TOOLBAR (toolbar), GTK_TOOLBAR_CHILD_RADIOBUTTON, icon_button, "Text", "Only texts in toolbar", "Private", iconw, GTK_SIGNAL_FUNC (radio_event), toolbar); gtk_toolbar_append_space (GTK_TOOLBAR (toolbar)); iconw = gtk_image_new_from_file ("gtk.xpm"); both_button = gtk_toolbar_append_element (GTK_TOOLBAR (toolbar), GTK_TOOLBAR_CHILD_RADIOBUTTON, text_button, "Both", "Icons and text in toolbar", "Private", iconw, GTK_SIGNAL_FUNC (radio_event), toolbar); gtk_toolbar_append_space (GTK_TOOLBAR (toolbar)); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (both_button), TRUE); |
В конце мы должны установить статус одной из кнопок вручную (иначе, они все остаются в активном статусе, препятствуя нам переключаться между ними).
/* простая кнопка переключения */ iconw = gtk_image_new_from_file ("gtk.xpm"); tooltips_button = gtk_toolbar_append_element (GTK_TOOLBAR (toolbar), GTK_TOOLBAR_CHILD_TOGGLEBUTTON, NULL, "Tooltips", "Toolbar with or without tips", "Private", iconw, GTK_SIGNAL_FUNC (toggle_event), toolbar); gtk_toolbar_append_space (GTK_TOOLBAR (toolbar)); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (tooltips_button), TRUE); |
Переключатель создаётся очевидным способом (если вы уже знаете как создать кнопку выбора).
/* упаковываем виджет в панель и прикрепляем соответствующую подсказку */ entry = gtk_entry_new (); gtk_toolbar_append_widget (GTK_TOOLBAR (toolbar), entry, "This is just an entry", "Private"); /* отображаем созданное */ gtk_widget_show (entry); |
Как вы видите, добавить любой виджет к панели инструментов достаточно просто. Однако вы должны помнить, что этот виджет нужно показать вручную (вопреки другим пунктам, которые будут показаны вместе с панелью инструментов).
/* выводим на экран всё сразу. */ gtk_widget_show (toolbar); gtk_widget_show (handlebox); gtk_widget_show (dialog); /* переходим в цикл ожидания событий */ gtk_main (); return 0; } |
Наша пиктограмма (XPM icon):
/* XPM */ static char * gtk_xpm[] = { "32 39 5 1", ". c none", "+ c black", "@ c #3070E0", "# c #F05050", "$ c #35E035", "................+...............", "..............+++++.............", "............+++++@@++...........", "..........+++++@@@@@@++.........", "........++++@@@@@@@@@@++........", "......++++@@++++++++@@@++.......", ".....+++@@@+++++++++++@@@++.....", "...+++@@@@+++@@@@@@++++@@@@+....", "..+++@@@@+++@@@@@@@@+++@@@@@++..", ".++@@@@@@+++@@@@@@@@@@@@@@@@@@++", ".+#+@@@@@@++@@@@+++@@@@@@@@@@@@+", ".+##++@@@@+++@@@+++++@@@@@@@@$@.", ".+###++@@@@+++@@@+++@@@@@++$$$@.", ".+####+++@@@+++++++@@@@@+@$$$$@.", ".+#####+++@@@@+++@@@@++@$$$$$$+.", ".+######++++@@@@@@@++@$$$$$$$$+.", ".+#######+##+@@@@+++$$$$$$@@$$+.", ".+###+++##+##+@@++@$$$$$$++$$$+.", ".+###++++##+##+@@$$$$$$$@+@$$@+.", ".+###++++++#+++@$$@+@$$@++$$$@+.", ".+####+++++++#++$$@+@$$++$$$$+..", ".++####++++++#++$$@+@$++@$$$$+..", ".+#####+++++##++$$++@+++$$$$$+..", ".++####+++##+#++$$+++++@$$$$$+..", ".++####+++####++$$++++++@$$$@+..", ".+#####++#####++$$+++@++++@$@+..", ".+#####++#####++$$++@$$@+++$@@..", ".++####++#####++$$++$$$$$+@$@++.", ".++####++#####++$$++$$$$$$$$+++.", ".+++####+#####++$$++$$$$$$$@+++.", "..+++#########+@$$+@$$$$$$+++...", "...+++########+@$$$$$$$$@+++....", ".....+++######+@$$$$$$$+++......", "......+++#####+@$$$$$@++........", ".......+++####+@$$$$+++.........", ".........++###+$$$@++...........", "..........++##+$@+++............", "...........+++++++..............", ".............++++..............."}; |
Button Boxes |
Notebooks |
GTK+ 2.0 Tutorial |
||
---|---|---|
Container Widgets |
Виджет вкладки (NoteBook) - это набор страниц которые размещаются друг над другом и несут в себе различную информацию, только одна страница из набора может быть видима при просмотре. Этот виджет в последнее время стал очень популярным в GUI programming, с его помощью очень удобно показывать блоки однотипной информации разделяя их соответствующим образом.
GtkWidget *gtk_notebook_new( void ); |
Существует достаточно много функций для работы с этим виджетом. Рассмотрим каждую в отдельности ниже.
Сначала рассмотрим индикаторы страниц или "tabs". Индикаторы могут размещаться вверху, внизу, слева или справа.
void gtk_notebook_set_tab_pos( GtkNotebook *notebook, GtkPositionType pos ); |
Аргумент GtkPositionType имеет интуитивно понятные значения:
GTK_POS_LEFT GTK_POS_RIGHT GTK_POS_TOP GTK_POS_BOTTOM |
По умолчанию используется GTK_POS_TOP.
Следующим рассмотрим добавление страниц к набору. Есть три способа сделать это. Первые два рассмотрим вместе, потому что они очень похожи.
void gtk_notebook_append_page( GtkNotebook *notebook, GtkWidget *child, GtkWidget *tab_label ); void gtk_notebook_prepend_page( GtkNotebook *notebook, GtkWidget *child, GtkWidget *tab_label ); |
Эти функции добавляют страницы к набору в конец (append), или в начало (prepend). child - это виджет который размещен в пределах добавляемой страницы, а tab_label - это ярлык добавленной страницы. Дочерний виджет должен быть создан отдельно и может располагаться в другом виджете контейнере, например в таблице.
Третья функция добавления страницы имеет теже параметры что и предыдущие две, а также позволяет определять расположение добавляемой страницы.
void gtk_notebook_insert_page( GtkNotebook *notebook, GtkWidget *child, GtkWidget *tab_label, gint position ); |
Параметр position определяет позицию добавляемой страницы относительно первой из набора, которая имеет позицию ноль (zero).
Зная как можно добавлять страницы, рассмотрим как их можно удалять:
void gtk_notebook_remove_page( GtkNotebook *notebook, gint page_num ); |
Эта функция удаляет страницу определённую аргументом page_num из набора notebook.
Функция определяющая текущую страницу в наборе:
gint gtk_notebook_get_current_page( GtkNotebook *notebook ); |
Следующие две функции перемещают страницы вперед или назад. Просто укажите функциям набор страниц с которым хотите работать. Учтите: когда набор страниц находится на последней странице и вызвана функция gtk_notebook_next_page(), он переходит на первую. Аналогично, если набор на первой странице и вызвана функция gtk_notebook_prev_page(), он переходит на последнюю.
void gtk_notebook_next_page( GtkNoteBook *notebook ); void gtk_notebook_prev_page( GtkNoteBook *notebook ); |
Следующая функция определяет активную ("active") страницу. Если вы захотите например открыть какую нибудь страницу, то вам нужна будет эта функция. По умолчанию всегда открывается первая страница.
void gtk_notebook_set_current_page( GtkNotebook *notebook, gint page_num ); |
Следующие две функции добавляют или удаляют из набора вкладки и границы набора соответственно.
void gtk_notebook_set_show_tabs( GtkNotebook *notebook, gboolean show_tabs ); void gtk_notebook_set_show_border( GtkNotebook *notebook, gboolean show_border ); |
Следующая функция позволяет пролистывать вкладки с помощью специальных стрелок, когда имеется слишком большое кол-во страниц и вкладки не могут сжаться до соответствующего уровня.
void gtk_notebook_set_scrollable( GtkNotebook *notebook, gboolean scrollable ); |
show_tabs, show_border и scrollable могут иметь значение TRUE или FALSE.
Рассмотрим усовершенствованный пример из estgtk.c, который поставляется вместе с кодом GTK. Эта небольшая программа состоит из набора страниц и шести кнопок. Набор содержит 11 страниц вставленных разными способами, наложением, вставкой, и по выбору. Кнопки позволяют добавлять/удалять вкладки и границы, удалять страницы, изменять положение страниц в разные стороны и выходить из программы.
#include <stdio.h> #include <gtk/gtk.h> /* Функция перемещает позиции вкладок */ void rotate_book( GtkButton *button, GtkNotebook *notebook ) { gtk_notebook_set_tab_pos (notebook, (notebook->tab_pos + 1) % 4); } /* Добавить/удалить вкладку и границу */ void tabsborder_book( GtkButton *button, GtkNotebook *notebook ) { gint tval = FALSE; gint bval = FALSE; if (notebook->show_tabs == 0) if (notebook->show_border == 0) bval = TRUE; gtk_notebook_set_show_tabs (notebook, tval); gtk_notebook_set_show_border (notebook, bval); } /* Удалить страницу из набора */ void remove_book( GtkButton *button, GtkNotebook *notebook ) { gint page; page = gtk_notebook_get_current_page (notebook); gtk_notebook_remove_page (notebook, page); This forces the widget to redraw itself. */ gtk_widget_queue_draw (GTK_WIDGET (notebook)); } gint delete( GtkWidget *widget, GtkWidget *event, gpointer data ) { gtk_main_quit (); return FALSE; } int main( int argc, char *argv[] ) { GtkWidget *window; GtkWidget *button; GtkWidget *table; GtkWidget *notebook; GtkWidget *frame; GtkWidget *label; GtkWidget *checkbutton; int i; char bufferf[32]; char bufferl[32]; gtk_init (&argc, &argv); window = gtk_window_new (GTK_WINDOW_TOPLEVEL); g_signal_connect (G_OBJECT (window), "delete_event", G_CALLBACK (delete), NULL); gtk_container_set_border_width (GTK_CONTAINER (window), 10); table = gtk_table_new (3, 6, FALSE); gtk_container_add (GTK_CONTAINER (window), table); /* Создаём новый набор, указываем позицию вкладок */ notebook = gtk_notebook_new (); gtk_notebook_set_tab_pos (GTK_NOTEBOOK (notebook), GTK_POS_TOP); gtk_table_attach_defaults (GTK_TABLE (table), notebook, 0, 6, 0, 1); gtk_widget_show (notebook); /* прикладываем страницы к набору */ for (i = 0; i < 5; i++) { sprintf(bufferf, "Append Frame %d", i + 1); sprintf(bufferl, "Page %d", i + 1); frame = gtk_frame_new (bufferf); gtk_container_set_border_width (GTK_CONTAINER (frame), 10); gtk_widget_set_size_request (frame, 100, 75); gtk_widget_show (frame); label = gtk_label_new (bufferf); gtk_container_add (GTK_CONTAINER (frame), label); gtk_widget_show (label); label = gtk_label_new (bufferl); gtk_notebook_append_page (GTK_NOTEBOOK (notebook), frame, label); } /* добавляем страницу специальной меткой */ checkbutton = gtk_check_button_new_with_label ("Check me please!"); gtk_widget_set_size_request (checkbutton, 100, 75); gtk_widget_show (checkbutton); label = gtk_label_new ("Add page"); gtk_notebook_insert_page (GTK_NOTEBOOK (notebook), checkbutton, label, 2); /* вставляем страницы в набор */ for (i = 0; i < 5; i++) { sprintf (bufferf, "Prepend Frame %d", i + 1); sprintf (bufferl, "PPage %d", i + 1); frame = gtk_frame_new (bufferf); gtk_container_set_border_width (GTK_CONTAINER (frame), 10); gtk_widget_set_size_request (frame, 100, 75); gtk_widget_show (frame); label = gtk_label_new (bufferf); gtk_container_add (GTK_CONTAINER (frame), label); gtk_widget_show (label); label = gtk_label_new (bufferl); gtk_notebook_prepend_page (GTK_NOTEBOOK (notebook), frame, label); } /* с какой страницы открывать набор (page 4) */ gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), 3); /* Создаём кнопки */ button = gtk_button_new_with_label ("close"); g_signal_connect_swapped (G_OBJECT (button), "clicked", G_CALLBACK (delete), NULL); gtk_table_attach_defaults (GTK_TABLE (table), button, 0, 1, 1, 2); gtk_widget_show (button); button = gtk_button_new_with_label ("next page"); g_signal_connect_swapped (G_OBJECT (button), "clicked", G_CALLBACK (gtk_notebook_next_page), G_OBJECT (notebook)); gtk_table_attach_defaults (GTK_TABLE (table), button, 1, 2, 1, 2); gtk_widget_show (button); button = gtk_button_new_with_label ("prev page"); g_signal_connect_swapped (G_OBJECT (button), "clicked", G_CALLBACK (gtk_notebook_prev_page), G_OBJECT (notebook)); gtk_table_attach_defaults (GTK_TABLE (table), button, 2, 3, 1, 2); gtk_widget_show (button); button = gtk_button_new_with_label ("tab position"); g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (rotate_book), (gpointer) notebook); gtk_table_attach_defaults (GTK_TABLE (table), button, 3, 4, 1, 2); gtk_widget_show (button); button = gtk_button_new_with_label ("tabs/border on/off"); g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (tabsborder_book), (gpointer) notebook); gtk_table_attach_defaults (GTK_TABLE (table), button, 4, 5, 1, 2); gtk_widget_show (button); button = gtk_button_new_with_label ("remove page"); g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (remove_book), (gpointer) notebook); gtk_table_attach_defaults (GTK_TABLE (table), button, 5, 6, 1, 2); gtk_widget_show (button); gtk_widget_show (table); gtk_widget_show (window); gtk_main (); return 0; } |
Toolbar |
Menu Widget |
GTK+ 2.0 Tutorial |
||
---|---|---|
Menu Widget |
Также можно сделать пункты меню привязанные к определённым клавишам.
Menu Widget |
Using ItemFactory |
GTK+ 2.0 Tutorial |
||
---|---|---|
Menu Widget |
После рассмотрения сложного пути создания меню, рассмотрим как это же можно сделать при помощи вызова функции gtk_item_factory.
ItemFactory создаёт меню из массива записей ItemFactory. Это означает, что вы можете определить ваше меню в его самой простой форме и затем создать виджеты (меню/строки) меню с минимумом запросов функции.
В ядре ItemFactory находится ItemFactoryEntry. Эта структура определяет один пункт меню и когда массив этих записей определен, целое меню сформировано. Структура записей ItemFactory выглядит так:
struct _GtkItemFactoryEntry { gchar *path; gchar *accelerator; GtkItemFactoryCallback callback; guint callback_action; gchar *item_type; }; |
Каждая строка определяет часть пункта меню.
*path - определяет путь и название пункта меню, например: "/File/Open" было бы именем пункта меню который подходит для записи ItemFactory с путём "/File". Однако пункт "/File/Open" был бы показан в меню File как "Open", потому что первые "/" используются для определения пути и не могут присутствовать в имени пункта. Символ, которому предшествует символ подчеркивания, указывает акселератор (сокращенная клавиша) в открытом меню.
*accelerator является строкой, которая указывает ключевую комбинацию клавиш и используется как ярлык к пункту меню. Строка может состоять из единичного символа или комбинации модификаторов и символов.
Модификаторами клавиш могут быть:
"<ALT> - alt "<CTL>" или "<CTRL>" или "<CONTROL>" - control "<MOD1>" до "<MOD5>" - modn "<SHFT>" или "<SHIFT>" - shift |
"<ConTroL>a" "<SHFT><ALT><CONTROL>X" |
callback - функция вызываемая при создании пунктом меню сигнала "activate". Форма обратного вызова описана в разделе Callback Description.
Значение callback_action помещается в функцию обратного вызова. Прототип функции смотрите в разделе Callback Description.
item_type - является строкой, которая определяет тип виджета упакованного в контейнер пунктов меню. Возможные значения:
Отметьте, что <LastBranch> полезен только для одного подменю строки меню.
Обратный вызов для ItemFactory entry может иметь две формы. Если callback_action равен 0 (zero), он имеет следующую форму:
void callback(void) |
другая форма:
void callback(gpointer callback_data, guint callback_action, GtkWidget *widget) |
callback_data - является указателем на произвольную часть данных и установлен вызовом gtk_item_factory_create_items().
callback_action - некоторое значение callback_action в ItemFactory записях.
*widget - указатель на виджет пункта меню (подробности в Manual Menu Creation).
Создаём простой пункт меню:
GtkItemFactoryEntry entry = {"/_File/_Open...", "<CTRL>O", print_hello, 0, "<Item>"}; |
Здесь создаётся простой пункт меню "/File/Open" (отображается как "Open"), внутри строки меню "/File". Он имеет акселератор (shortcut) control+'O' при нажатии которого вызывается функция print_hello(). print_hello() имеет форму void print_hello(void) так как callback_action имеет значение zero. Если 'O' в "Open" подчеркнута, то при открытом меню и нажатой клавише 'O' пункт будет активизирован. Заметьте, что "File/_Open" может быть использован как путь вместо "/_File/_Open".
Создание входа с более сложным обратным вызовом (callback):
GtkItemFactoryEntry entry = {"/_View/Display _FPS", NULL, print_state, 7,"<CheckItem>"}; |
Это определяет новый пункт меню отображаемый как "Display FPS" в вышестоящем пункте меню "View". При нажатии вызывается функция print_state(). Так как callback_action не равно zero print_state() имеет форму:
void print_state(gpointer callback_data, guint callback_action, GtkWidget *widget) |
с callback_action равным 7.
Создаём установку кнопки выбора:
GtkItemFactoryEntry entry1 = {"/_View/_Low Resolution", NULL, change_resolution, 1, "<RadioButton>"}; GtkItemFactoryEntry entry2 = {"/_View/_High Resolution", NULL, change_resolution, 2, "/View/Low Resolution"}; |
entry1 определяет одиночную кнопку выбора которая переключает вызов функции change_resolution() с параметром callback_action равным 1. change_resolution() имеет форму:
void change_resolution(gpointer callback_data, guint callback_action, GtkWidget *widget) |
entry2 определяет кнопку выбора которая принадлежит группе в которой находится entry1. Она вызывает туже самую функцию при переключении, но параметр callback_action равен 2. Обратите внимание на отсутствие акселераторов. Если бы потребовалась ещё одна кнопка выбора для переключения, то её можно было бы создать таким же образом в этой же группе с item_type равным "/View/Low Resolution".
Массив записей для определения меню. Ниже приведен пример объявления массива:
static GtkItemFactoryEntry entries[] = { { "/_File", NULL, NULL, 0, "<Branch>" }, { "/File/tear1", NULL, NULL, 0, "<Tearoff>" }, { "/File/_New", "<CTRL>N", new_file, 1, "<Item>" }, { "/File/_Open...", "<CTRL>O", open_file, 1, "<Item>" }, { "/File/sep1", NULL, NULL, 0, "<Seperator>" }, { "/File/_Quit", "<CTRL>Q", quit_program, 0, "<Item>"} }; |
Массив GtkItemFactoryEntry пунктов определяет меню. Функция выглядит так:
GtkItemFactory* gtk_item_factory_new( GtkType container_type, const gchar *path, GtkAccelGroup *accel_group ); |
container_type может иметь следующие значения:
GTK_TYPE_MENU GTK_TYPE_MENU_BAR GTK_TYPE_OPTION_MENU |
container_type определяет тип нужного вам меню, например - всплывающее меню, панель меню или меню опций (как поле со списком но с выпавшим меню).
path определяет путь к основному меню. В основном уникальное имя основного меню должно быть заключено в треугольные скобки "<>". Это важно для обозначения акселераторов. Оно должно быть уникальным и для каждого меню и для каждой программы. Например программа с именем 'foo', может иметь главное меню "<FooMain>", а всплывающее меню "<FooImagePopUp>", или нечто похожее. Главное чтобы они были уникальны.
accel_group указатель на gtk_accel_group. Таблицы акселераторов устанавливаются в то время как генерируется меню. Новая группа акселераторов создаётся функцией gtk_accel_group_new().
Но это - только первый шаг. Чтобы преобразовывать массив информации GtkItemFactoryEntry в виджеты, используется следующая функция :
void gtk_item_factory_create_items( GtkItemFactory *ifactory, guint n_entries, GtkItemFactoryEntry *entries, gpointer callback_data ); |
*ifactory - указатель на вышеупомянутое созданное производство пунктов.
n_entries - является числом входов в массив GtkItemFactoryEntry..
*entries - указатель на массив GtkItemFactoryEntry.
callback_data - является тем, что передают во все функции обратного вызова для всех входов с callback_action != 0.
Группа акселераторов создана, теперь нужно прикрепить меню к окну:
void gtk_window_add_accel_group( GtkWindow *window, GtkAccelGroup *accel_group); |
Следующая функция извлекает виджеты из ItemFactory:
GtkWidget* gtk_item_factory_get_widget( GtkItemFactory *ifactory, const gchar *path ); |
Например, если ItemFactory имеет две записи "/File", и "/file/new", используя дорожку "/File" извлечётся виджет menu из ItemFactory. Используя путь "/File/New" извлечётся виджет menu item. Это позволяет установить начальное состояние пунктов меню. Например для установки по умолчанию кнопки выбора в путь "/Shape/Oval", код будет выглядеть так:
gtk_check_menu_item_set_active( GTK_CHECK_MENU_ITEM (gtk_item_factory_get_item (item_factory, "/Shape/Oval")), TRUE); |
Для извлечения основного меню используется функция gtk_item_factory_get_item() с путем "<main>" (в независимости от использованного пути в gtk_item_factory_new()). В случае ItemFactory, создаваемого с типом GTK_TYPE_MENU_BAR, возвращается виджет панели меню. Если тип GTK_TYPE_MENU возвращается виджет меню. Если тип GTK_TYPE_OPTION_MENU возвращается виджет меню опций.
Помните: вход определенный путем "/_File" фактически путь "/File".
Теперь вы имеете строку меню или меню и можете манипулировать ими как обсуждалось в разделе Manual Menu Creation.
Manual Menu Example |
Item Factory Example |
GTK+ 2.0 Tutorial |
||
---|---|---|
Menu Widget |
Пример использования производства пунктов (item factory) GTK.
/* Начало примера меню itemfactory.c */ #include <gtk/gtk.h> #include <strings.h> /* Обязательный основной обратный вызов */ static void print_hello( GtkWidget *w, gpointer data ) { g_message ("Hello, World!\n"); } /* Для контрольной кнопки */ static void print_toggle(gpointer callback_data, guint callback_action, GtkWidget *menu_item) { g_message ("Состояние контрольной кнопки - %d\n", GTK_CHECK_MENU_ITEM(menu_item)->active); } /* Для кнопки выбора */ static void print_selected(gpointer callback_data, guint callback_action, GtkWidget *menu_item) { if(GTK_CHECK_MENU_ITEM(menu_item)->active) g_message("Кнопка выбора %d выбрана\n", callback_action); } /* Наше меню, массив GtkItemFactoryEntry структур которые определяют каждый пункт меню */ static GtkItemFactoryEntry menu_items[] = { { "/_File", NULL, NULL, 0, "<Branch>" }, { "/File/_New", "<control>N", print_hello, 0, "<Item>" }, { "/File/_Open", "<control>O", print_hello, 0, "<Item>" }, { "/File/_Save", "<control>S", print_hello, 0, "<Item>" }, { "/File/Save _As", NULL, NULL, 0, "<Item>" }, { "/File/sep1", NULL, NULL, 0, "<Separator>" }, { "/File/Quit", "<control>Q", gtk_main_quit, 0, "<Item>" }, { "/_Options", NULL, NULL, 0, "<Branch>" }, { "/Options/tear", NULL, NULL, 0, "<Tearoff>" }, { "/Options/Check", NULL, print_toggle, 1, "<CheckItem>" }, { "/Options/sep", NULL, NULL, 0, "<Separator>" }, { "/Options/Rad1", NULL, print_selected, 1, "<RadioItem>" }, { "/Options/Rad2", NULL, print_selected, 2, "/Options/Rad1" }, { "/Options/Rad3", NULL, print_selected, 3, "/Options/Rad1" }, { "/_Help", NULL, NULL, 0, "<LastBranch>" }, { "/_Help/About", NULL, NULL, 0, "<Item>" }, }; static gint nmenu_items = sizeof (menu_items) / sizeof (menu_items[0]); /* Возвращает виджет панель меню, сделанный из вышеупомянутого меню */ GtkWidget *get_menubar_menu( GtkWidget *window) { GtkItemFactory *item_factory; GtkAccelGroup *accel_group; /* Создаём группу акселераторов (shortcut keys) */ accel_group = gtk_accel_group_new (); /* Создаём ItemFactory (строку меню) */ item_factory = gtk_item_factory_new (GTK_TYPE_MENU_BAR, "<main>", accel_group); /* Эта функция генерирует пункты меню. Передает item factory, номер пункта в массиве, массив непосредственно, а также любые данные обратных вызовов для пунктов меню. */ gtk_item_factory_create_items (item_factory, nmenu_items, menu_items, NULL); /* Прикрепляем новую группу акселераторов к окну. */ gtk_window_add_accel_group (GTK_WINDOW (window), accel_group); /* Возвращаем фактически созданную производством пунктов (item factory) панель меню. */ return gtk_item_factory_get_widget (item_factory, "<main>"); } /* Всплывающее меню при нажатии на всплывающую кнопку */ static gint popup_cb(GtkWidget *widget, GdkEvent *event, GtkWidget *menu) { GdkEventButton *bevent = (GdkEventButton *)event; /* только нажатие кнопки */ if(event->type != GDK_BUTTON_PRESS) return FALSE; /* Отображаем меню */ gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, bevent->button, bevent->time); return TRUE; } /* Тоже что и get_menubar_menu(), но возвращает кнопку с сигналом для всплывающего меню */ GtkWidget *get_popup_menu(void) { GtkItemFactory *item_factory; GtkWidget *button, *menu; /* Тоже что и выше, но без акселераторов */ item_factory = gtk_item_factory_new (GTK_TYPE_MENU, "<main>", NULL); gtk_item_factory_create_items (item_factory, nmenu_items, menu_items, NULL); menu = gtk_item_factory_get_widget(item_factory, "<main>"); /* Кнопка для активизации всплывающего меню */ button = gtk_button_new_with_label("Popup"); /* Создаём всплывающее меню при щелчке */ g_signal_connect(G_OBJECT(button), "event", G_CALLBACK(popup_cb), (gpointer) menu); return button; } /* То же самое, но возвращается меню опций */ GtkWidget *get_option_menu(void) { GtkItemFactory *item_factory; GtkWidget *option_menu; /* Ещё раз, но без акселераторов */ item_factory = gtk_item_factory_new (GTK_TYPE_OPTION_MENU, "<main>", NULL); gtk_item_factory_create_items (item_factory, nmenu_items, menu_items, NULL); option_menu = gtk_item_factory_get_widget(item_factory, "<main>"); return option_menu; } /* Начальная функция */ int main( int argc, char *argv[] ) { GtkWidget *window; GtkWidget *main_vbox; GtkWidget *menubar, *option_menu, *popup_button; /* Инициализация GTK */ gtk_init (&argc, &argv); /* Создаём окно */ window = gtk_window_new (GTK_WINDOW_TOPLEVEL); g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (gtk_main_quit), NULL); gtk_window_set_title (GTK_WINDOW(window), "Item Factory"); gtk_widget_set_size_request (GTK_WIDGET(window), 300, 200); /* Создаём vbox для размещения меню */ main_vbox = gtk_vbox_new (FALSE, 1); gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 1); gtk_container_add (GTK_CONTAINER (window), main_vbox); /* получаем три типа меню */ /* Помните: все три меню созданы отдельно друг от друга */ menubar = get_menubar_menu (window); popup_button = get_popup_menu(); option_menu = get_option_menu(); /* Упаковываем всё вместе */ gtk_box_pack_start (GTK_BOX (main_vbox), menubar, FALSE, TRUE, 0); gtk_box_pack_end (GTK_BOX (main_vbox), popup_button, FALSE, TRUE, 0); gtk_box_pack_end (GTK_BOX (main_vbox), option_menu, FALSE, TRUE, 0); /* Отображаем виджет */ gtk_widget_show_all (window); /* КОНЕЦ! */ gtk_main (); return(0); } /* Конец примера */ |
Using ItemFactory |
Undocumented Widgets |
GTK+ 2.0 Tutorial | ||
---|---|---|
<<< Previous | Undocumented Widgets | Next >>> |
<<< Previous | Home | Next >>> |
Undocumented Widgets | Up | Menu Items |
GTK+ 2.0 Tutorial | ||
---|---|---|
<<< Previous | Undocumented Widgets | Next >>> |
<<< Previous | Home | Next >>> |
Option Menu | Up | Curves |
GTK+ 2.0 Tutorial | ||
---|---|---|
<<< Previous | Undocumented Widgets | Next >>> |
<<< Previous | Home | Next >>> |
Menu Items | Up | Drawing Area |
GTK+ 2.0 Tutorial | ||
---|---|---|
<<< Previous | Undocumented Widgets | Next >>> |
<<< Previous | Home | Next >>> |
Curves | Up | Font Selection Dialog |
GTK+ 2.0 Tutorial | ||
---|---|---|
<<< Previous | Undocumented Widgets | Next >>> |
<<< Previous | Home | Next >>> |
Drawing Area | Up | Message Dialog |
GTK+ 2.0 Tutorial | ||
---|---|---|
<<< Previous | Undocumented Widgets | Next >>> |
<<< Previous | Home | Next >>> |
Font Selection Dialog | Up | Gamma Curve |
GTK+ 2.0 Tutorial | ||
---|---|---|
<<< Previous | Undocumented Widgets | Next >>> |
<<< Previous | Home | Next >>> |
Message Dialog | Up | Image |
GTK+ 2.0 Tutorial | ||
---|---|---|
<<< Previous | Undocumented Widgets | Next >>> |
<<< Previous | Home | Next >>> |
Gamma Curve | Up | Plugs and Sockets |
GTK+ 2.0 Tutorial | ||
---|---|---|
<<< Previous | Undocumented Widgets | Next >>> |
<<< Previous | Home | Next >>> |
Image | Up | Tree View |
GTK+ 2.0 Tutorial | ||
---|---|---|
<<< Previous | Undocumented Widgets | Next >>> |
<<< Previous | Home | Next >>> |
Plugs and Sockets | Up | Text View |
GTK+ 2.0 Tutorial | ||
---|---|---|
<<< Previous | Undocumented Widgets | Next >>> |
<<< Previous | Home | Next >>> |
Tree View | Up | Setting Widget Attributes |
GTK+ 2.0 Tutorial |
||
---|---|---|
Getting Started |
|
В версии 2.0, система сигналов перемещена из GTK в GLib, поэтому функции и типы в этой секции имеют приставку "g_" а не "gtk_". Сдесь не говорится о расширениях системы сигналов GLib 2.0 по сравнению с системой сигналов GTK 1.2. |
Перед подробным рассмотрением helloworld, мы обсудим сигналы (signals) и обратные вызовы (callbacks). GTK - набор инструментов управляемых событиями, это значит что процесс находясь в функции gtk_main() ждет пока не произойдет событие позволяющее передать управление соответствующей функции.
Такой контроль выполняется с использованием идеи сигналов (idea of "signals"). (Учтите что эта система сигналов не тоже самое что система сигналов Unix, хотя используется идентичная терминология). Когда происходит событие, такое как нажатие кнопки мыши, виджет создаёт ("emitted") сигнал соответствующий нажатию. Так GTK выполняет большинство своих действий. Есть сигналы которые наследуют все виджеты, например сигнал закрытия ("destroy"), а есть специфические сигналы, например сигнал кнопки переключения ("toggled").
Чтобы заставить кнопку работать, нужно
настроить обработчик сигналов, который
при возникновении события запустит
функцию.
Это делается так:
gulong g_signal_connect( gpointer *object, const gchar *name, GCallback func, gpointer func_data ); |
первый аргумент - виджет который создаёт сигнал, второй - имя сигнала, третий - функция которая должна быть вызвана этим сигналом и четвертый - данные которые передаются этой функции.
Функция определенная в третьем аргументе должна иметь общую форму:
void callback_func( GtkWidget *widget, gpointer callback_data ); |
где первым аргументом будет указатель на виджет, который создал сигнал, а второй - указатель на данные как последний аргумент функции g_signal_connect() как показано выше.
Выше описанная форма функции обратного вызова имеет общий вид, но некоторые виджеты имеют специфические параметры и сигналы.
Другой вызов использованный в примере helloworld:
gulong g_signal_connect_swapped( gpointer *object, const gchar *name, GCallback func, gpointer *slot_object ); |
g_signal_connect_swapped() тоже самое, что g_signal_connect() только в качестве единственного аргумента используется указатель на GTK объект. Для использования этой функции вызова сигнала, обратный вызов должен иметь форму
void callback_func( GtkObject *object ); |
где объектом обычно является виджет. Обычно обратный вызов не устанавливается для g_signal_connect_swapped(). Они обычно используются в вызовах GTK функций которые принимают как единственный аргумент сигнал виджета или объект, что и показано в примере helloworld.
Наличие двух функций вызова сигналов объясняется необходимостью обратным вызовам иметь разное количество аргументов. Многие функции в библиотеке GTK принимают только указатель GtkWidget как единственный аргумент, таким образом используя g_signal_connect_swapped() в ваших функциях, вам могут понадобится расширенные данные обратных вызовов.
Compiling Hello World |
Events |
GTK+ 2.0 Tutorial |
||
---|---|---|
Timeouts, IO and Idle Functions |
Изящная особенность GDK (библиотека, которая лежит в основе GTK), является способность сделать так, чтобы проверить данные относительно дескриптора файла (как возвращено open(2) или socket(2)). Это особенно полезно для сетевых приложений. Функция:
gint gdk_input_add( gint source, GdkInputCondition condition, GdkInputFunction function, gpointer data ); |
Первый аргумент - дескриптор файла который вы хотите посмотреть, а второй - то, что вы хотите чтобы искал GDK. Возможные значения:
GDK_INPUT_READ - вызывает вашу функцию когда есть данные готовые для чтения в дескрипторе файла.
>GDK_INPUT_WRITE - вызывает вашу функцию когда дескриптор файла готов к записи.
Третим аргументом как вы понимаете является ваша функция которую вы хотите вызывать, а четвертый - данные которые посылаются в эту функцию.
Возвращаемое значение это тег который может быть использован для остановки контроля дескриптора файла используя функцию:
void gdk_input_remove( gint tag ); |
Функция отзыва должна быть объявлена как:
void input_callback( gpointer data, gint source, GdkInputCondition condition ); |
Где source и condition тоже что и выше.
Timeouts, IO and Idle Functions |
Idle Functions |
GTK+ 2.0 Tutorial |
||
---|---|---|
Timeouts, IO and Idle Functions |
А что, если нужна функция которая используется, когда ничего не происходит?
gint gtk_idle_add( GtkFunction function, gpointer data ); |
Это заставляет GTK вызывать указанную функцию когда ничего не происходит.
void gtk_idle_remove( gint tag ); |
Функция, на которую указывает первый параметр gtk_idle_add будет вызываться всякий раз, когда появляется возможность. Как и в других случаях, возвращённый FALSE остановит вызов функции.
Monitoring IO |
Advanced Event and Signal Handling |
GTK+ 2.0 Tutorial |
||
---|---|---|
Advanced Event and Signal Handling |
Signal Emission - процесс, посредством которого GTK выполняет все обработчики для определенного объекта и сигнала.
Для начала отметьте, что возвращаемое значение от эмиссии сигнала (Signal Emission) - возвращаемое значение последнего выполняемого обработчика. Так как события сигналов все имеют тип GTK_RUN_LAST, он же будет установлен (самим GTK) по умолчанию обработчиком, если вы не вызовите gtk_signal_connect_after().
Событием обработчика (говорят "button_press_event") может быть:
Воспроизвести основной сигнал "event". Если этот обработчик вернул значение TRUE, остановить весь процесс.
В другом случае воспроизвести специфический сигнал "button_press_event". Если возвращено TRUE, остановить весь процесс.
Иначе, вернутся к родителю виджета, и повторить вышеупомянутые два шага.
Продолжать пока обработчик сигнала не вернул TRUE, или пока виджет верхнего уровня не достиг предела.
Некоторые последствия вышеупомянутого:
Возвращаемое значение вашего обработчика не будет иметь никакого эффекта, если это обработчик по умолчанию, или если вы не вызываете gtk_signal_connect_after().
Чтобы обработчик значения по умолчанию не выполнился, вы должны вызвать gtk_signal_connect() и использовать gtk_signal_emit_stop_by_name() - возвращаемое значение влияет на размножение сигнала, но не на текущую эмиссию.
Advanced Event and Signal Handling |
Managing Selections |
GTK+ 2.0 Tutorial |
||
---|---|---|
Managing Selections |
Поиск выделения является асинхронным процессом, который запускается вызовом функции:
gboolean gtk_selection_convert( GtkWidget *widget, GdkAtom selection, GdkAtom target, guint32 time ); |
Это преобразует (converts) выделение в форму определённую target. Если вообще возможно, то аргумент time должен быть временем события которое вызвало выделение (selection). Это помогает событиям удостовериться в том, что пользователь их попросил. Однако, если это не доступно (например, если преобразование было вызвано "clicked" сигналом), то вы можете использовать постоянный GDK_CURRENT_TIME.
Когда хозяин выделения отвечает на запрос, вашему приложению посылается сигнал "selection_received". Обработчик для этого сигнала получает указатель на структуру GtkSelectionData, которая определена как:
struct _GtkSelectionData { GdkAtom selection; GdkAtom target; GdkAtom type; gint format; guchar *data; gint length; }; |
selection и target значения которые вы дали в вашем вызове gtk_selection_convert(). type является атомом, который идентифицирует тип данных, возвращенных владельцем выбора. Некоторые возможные значения : "STRING" - строка из символов latin-1, "ATOM" - ряд атомов, "INTEGER" - целое число, и т.д.. Большинство targets могут вернуть только один тип. format даёт длину единиц (например букв) в битах. Обычно вам не нужно заботится об этом получая данные. data - указатель на возвращенные данные, а length - возвращает длину данных в байтах. Если длина отрицательная, то произошла ошибка и выделение не может быть восстановлено. Это могло случится, если у выделения небыло приложения владельца, или запрашиваемая вами цель не поддерживается приложением. Буфер всегда гарантированно больше length на один байт; лишний байт всегда 0 (zero), таким образом нет необходимости копировать строку, только чтобы закончить её (nul-terminate them).
В следующем примере мы восстанавливаем специальную цель "TARGETS", которая является списком всех целей в которые может быть преобразовано выделение.
Managing Selections |
Supplying the selection |
GTK+ 2.0 Tutorial |
||
---|---|---|
Управление выделениями (Managing Selections) |
Замещать выделение немного сложнее. Вы должны зарегистрировать обработчики которые будут вызваны по требованию выделения. Для каждой пары selection/target делается вызов:
void gtk_selection_add_target (GtkWidget *widget, GdkAtom selection, GdkAtom target, guint info); |
виджет selection и target идентифицируют запросы, которыми этот обработчик будет управлять. Когда получен сигнал о выделении, производится сигнал "selection_get". info может использоваться как нумератор, чтобы идентифицировать определенную цель в пределах функции обратного вызова.
Функция обратного вызова имеет сигнатуру:
void "selection_get" (GtkWidget *widget, GtkSelectionData *selection_data, guint info, guint time); |
GtkSelectionData - то же самое как выше, но на сей раз, мы ответственны за то, чем заполнить поля type, format, data и length. ( Фактически важна здесь область формата - сервер X использует это, чтобы выяснить, должны ли данные меняться байтом или нет. Обычно это будет 8 - то есть символ - или 32 - то есть целое число.) Это выполняется вызовом функции:
void gtk_selection_data_set( GtkSelectionData *selection_data, GdkAtom type, gint format, guchar *data, gint length ); |
Эта функция заботится о надлежащем создании копии данных, так что вам ненужно заботиться об этом. (Вы не должны в ручную заполнять структуру GtkSelectionData.)
При запросе пользователя, требуется монопольное использование выделения, вызовом:
gboolean gtk_selection_owner_set( GtkWidget *widget, GdkAtom selection, guint32 time ); |
Если другое приложение будет требовать монопольного использования выделения, то вы получите "selection_clear_event".
Как пример замещения выделения, следующая программа добавляет функциональные возможности выделения к выключателю. Когда кнопка выключателя вдавлена, программа требует первичного выделения. Поддерживаются только цели "STRING" (кроме определенных целей как "TARGETS" подаваемые непосредственно GTK). Когда цель этого требует, возвращается строка представления времени.
Retrieving the selection |
Drag-and-drop (DND) |
GTK+ 2.0 Tutorial |
||
---|---|---|
Drag-and-drop (DND) |
Перетаскиваемые данные имеют следующие свойства:
Тип выполняемого перетаскивания (GDK_ACTION_COPY, GDK_ACTION_MOVE).
Клиент определил произвольный тип перетаскивания(имя и номер пары).
Действия перетаскивания весьма очевидны, они определяют может ли виджет переместиться (после перетаскивания виджет удаляется в исходном местоположении) или он должен быть скопирован (виджет после перетаскивания находится и в новом и в старом месте прибывания), например GDK_ACTION_COPY и/или GDK_ACTION_MOVE. Есть дополнительное действие перетаскивания GDK_ACTION_LINK, которое вы изучите при достижении более высокого уровня понимания системы drag-and-drop.
Клиент определяет произвольный тип drag-and-drop более гибким, потому что ваше приложение будет определять и проверять это более точно. Вы должны установить ваши виджеты назначения, чтобы получить точные типы drag-and-drop, определённые именем и/или номером. Более надежно использовать имя, так-как другое пиложение может использовать тот же номер для совершенно другого значения.
Посланный и полученные типы формата данных (selection target) входят в игру только в вашем запросе и полученных функциях обработчика данных. Термин selection target немного вводит в заблуждение. Этот термин адаптирован для GTK+ выделения (вырезать/копировать и вставить). То, что фактически подразумевает selection target - тип формата данных (то есть. GdkAtom, целое число, или строка) посылаемый или полученный. Ваш запрос функции обработки данных должен определить тип (selection target) отправляемых данных и ваш обработчик данных должен обработать тип (selection target) получаемых данных.
Drag-and-drop (DND) |
Functions |
GTK+ 2.0 Tutorial |
||
---|---|---|
Drag-and-drop (DND) |
Функция gtk_drag_source_set() определяет ряд целевых типов операции перетаскивания на виджете.
void gtk_drag_source_set( GtkWidget *widget, GdkModifierType start_button_mask, const GtkTargetEntry *targets, gint n_targets, GdkDragAction actions ); |
Параметры показывают следующее:
start_button_mask - определяет bitmask кнопок которые могут начать перетаскивание (например GDK_BUTTON1_MASK)
targets - определяет таблицу целевых данных типов поддерживающих перетаскивание
actions - определяет bitmask возможных действий перетаскивания из этого окна
Параметр targets - массив следующей структуры:
struct GtkTargetEntry { gchar *target; guint flags; guint info; }; |
Поля определяют строку представляющую тип перетаскивания, дополнительные флаги и приложение заданного целочисленного идентификатора.
Если виджет больше не обязан действовать как источник для операций перетаскивания, функция gtk_drag_source_unset() может использоваться, чтобы удалить ряд типов адресата перетаскивания.
void gtk_drag_source_unset( GtkWidget *widget ); |
Исходному виджету посылают следующие сигналы в течение операции перетаскивания.
Таблица 1. Сигналы исходных виджетов
drag_begin |
void (*drag_begin)(GtkWidget *widget, GdkDragContext *dc, gpointer data) |
drag_motion |
gboolean (*drag_motion)(GtkWidget *widget, GdkDragContext *dc, gint x, gint y, guint t, gpointer data) |
drag_data_get |
void (*drag_data_get)(GtkWidget *widget, GdkDragContext *dc, GtkSelectionData *selection_data, guint info, guint t, gpointer data) |
drag_data_delete |
void (*drag_data_delete)(GtkWidget *widget, GdkDragContext *dc, gpointer data) |
drag_drop |
gboolean (*drag_drop)(GtkWidget *widget, GdkDragContext *dc, gint x, gint y, guint t, gpointer data) |
drag_end |
void (*drag_end)(GtkWidget *widget, GdkDragContext *dc, gpointer data) |
gtk_drag_dest_set() - определяет виджет который может принять перетаскиваемый объект и определяет типы приёма.
gtk_drag_dest_unset() - определяет, что виджет больше не может осуществлять приём.
void gtk_drag_dest_set( GtkWidget *widget, GtkDestDefaults flags, const GtkTargetEntry *targets, gint n_targets, GdkDragAction actions ); void gtk_drag_dest_unset( GtkWidget *widget ); |
Виджету назначения посылают следующие сигналы в течение операции перетаскивания.
Таблица 2. Сигналы виджета назначения
drag_data_received |
void (*drag_data_received)(GtkWidget *widget, GdkDragContext *dc, gint x, gint y, GtkSelectionData *selection_data, guint info, guint t, gpointer data) |
Properties |
GLib |
GTK+ 2.0 Tutorial |
||
---|---|---|
Getting Started |
В дополнение к механизму сигналов, описанному выше, есть ряд событий, которые отражают механизм событий X. Обратные вызовы также могут быть связаны с событиями. Вот эти события:
Чтобы ассоциировать функцию обратного вызова с одним из этих событий нужно использовать функцию g_signal_connect(), как описано выше, в качестве параметра нужно использовать название одного из событий. Формат функции обратного вызова для событий имеет отличия от аналогичных для сигналов:
gint callback_func( GtkWidget *widget, GdkEvent *event, gpointer callback_data ); |
GdkEvent это C объединение(union) структур, тип которых зависит от выбранного события. Для того чтобы понять какое событие произошло каждая из возможных альтернатив имеет свой тип, который отражает произошедшее событие. Многие компоненты структуры событий зависят от типа события. Возможные варианты типов событий:
GDK_NOTHING GDK_DELETE GDK_DESTROY GDK_EXPOSE GDK_MOTION_NOTIFY GDK_BUTTON_PRESS GDK_2BUTTON_PRESS GDK_3BUTTON_PRESS GDK_BUTTON_RELEASE GDK_KEY_PRESS GDK_KEY_RELEASE GDK_ENTER_NOTIFY GDK_LEAVE_NOTIFY GDK_FOCUS_CHANGE GDK_CONFIGURE GDK_MAP GDK_UNMAP GDK_PROPERTY_NOTIFY GDK_SELECTION_CLEAR GDK_SELECTION_REQUEST GDK_SELECTION_NOTIFY GDK_PROXIMITY_IN GDK_PROXIMITY_OUT GDK_DRAG_ENTER GDK_DRAG_LEAVE GDK_DRAG_MOTION GDK_DRAG_STATUS GDK_DROP_START GDK_DROP_FINISHED GDK_CLIENT_EVENT GDK_VISIBILITY_NOTIFY GDK_NO_EXPOSE GDK_SCROLL GDK_WINDOW_STATE GDK_SETTING |
Так, для ассоциации функции обратного вызова с событием можно использовать следующее:
g_signal_connect (G_OBJECT (button), "button_press_event", G_CALLBACK (button_press_callback), NULL); |
Это предполагает что кнопка(button) это кнопка виджета (Button widget). Когда курсор находится на кнопке и кнопка мыши нажата, то вызывается функция button_press_callback(). Эта функция может быть объявлена так:
static gint button_press_callback( GtkWidget *widget, GdkEventButton *event, gpointer data ); |
Обратите внимание, что мы можем объявить второй аргумент как тип GdkEventButton, поскольку мы знаем, какое событие произойдет для той функции, которая будет вызвана.
Значение возвращенное функцией указывает механизму обработки событий GTK о дальнейших действиях. Возвращенное TRUE указывает на прекращение дальнейшего выполнения события. Возвращенное FALSE продолжает нормальное выполнение события. Для более подробной информации смотрите секцию Advanced Event and Signal Handling.
Детально типы данных GdkEvent, рассматриваются в GDK Event Types.
GDK selection и drag-and-drop APIs также создают множество событий которые отражены сигналами GTK. Смотрите Signals on the source widget и Signals on the destination widget для детального изучения функций обратного вызова сигналов:
selection_received
selection_get
drag_begin_event
drag_end_event
drag_data_delete
drag_motion
drag_drop
drag_data_get
drag_data_received
Theory of Signals and Callbacks |
Stepping Through Hello World |
GTK+ 2.0 Tutorial |
||
---|---|---|
GLib |
Следующие функции используются для создания, управления и уничтожения стандартных списков двойной связи. Каждый элемент в списке содержит часть данных, вместе с указателями, которые связываются с предыдущими и следующими элементами в списке. Это позволяет легко передвигаться по списку в любом направлении. Элемент данных имеет тип "gpointer", что означает, что данные могут быть указателем на ваши реальные данные или (через приведение) числовое значение (но не думайте, что int и gpointer имеют одинаковый размер!). Эти подпрограммы внутренне распределяют элементы списка в блоках, которые более эффективны чем распределение элементов индивидуально.
Нет функции чтобы определенно создать список. Вместо этого просто создайте переменную типа GList* и установите его значение в NULL; NULL, как предполагается, является пустым списком.
Для добавления элементов в список используйте g_list_append(), g_list_prepend(), g_list_insert(), или g_list_insert_sorted(). Во всех случаях они принимают указатель на начало списка, и возвращают (возможно измененный) указатель на начало списка. Таким образом, для всех операций, которые добавляют или удаляют элементы, убедитесь, что сохранили возвращенное значение!
GList *g_list_append( GList *list, gpointer data ); |
Это добавляет новый элемент (со значением data) в конец списка.
GList *g_list_prepend( GList *list, gpointer data ); |
Это добавляет новый элемент (со значением data) в начало списка.
GList *g_list_insert( GList *list, gpointer data, gint position ); |
Это вставляет новый элемент (со значением data) в список в данной позиции. Если позиция - 0, то результат аналогичен g_list_prepend(); если позиция - меньше чем 0, то результат аналогичен g_list_append().
GList *g_list_remove( GList *list, gpointer data ); |
Это удаляет из списка элемент со значением data; если элемент не существует, список не изменяется.
void g_list_free( GList *list ); |
Это освобождает всю память используемую GList. Если элементы списка обращаются к динамически-распределенной памяти, то они должны быть сначала освобождены.
Есть много других функций GLib которые поддерживают списки двойной связи; для более подробной информации смотрите документацию glib. Вот несколько из сигнатур некоторых полезных функций:
GList *g_list_remove_link( GList *list, GList *link ); GList *g_list_reverse( GList *list ); GList *g_list_nth( GList *list, gint n ); GList *g_list_find( GList *list, gpointer data ); GList *g_list_last( GList *list ); GList *g_list_first( GList *list ); gint g_list_length( GList *list ); void g_list_foreach( GList *list, GFunc func, gpointer user_data ); |
GLib |
Singly Linked Lists |
GTK+ 2.0 Tutorial |
||
---|---|---|
GLib |
Многие из вышеупомянутых функций идентичны для односвязных списков. Вот список некоторых операций:
GSList *g_slist_append( GSList *list, gpointer data ); GSList *g_slist_prepend( GSList *list, gpointer data ); GSList *g_slist_insert( GSList *list, gpointer data, gint position ); GSList *g_slist_remove( GSList *list, gpointer data ); GSList *g_slist_remove_link( GSList *list, GSList *link ); GSList *g_slist_reverse( GSList *list ); GSList *g_slist_nth( GSList *list, gint n ); GSList *g_slist_find( GSList *list, gpointer data ); GSList *g_slist_last( GSList *list ); gint g_slist_length( GSList *list ); void g_slist_foreach( GSList *list, GFunc func, gpointer user_data ); |
Doubly Linked Lists |
Memory Management |
GTK+ 2.0 Tutorial |
||
---|---|---|
GLib |
gpointer g_malloc( gulong size ); |
Эта функция - замена malloc(). Вы не должны проверять возвращаемое событие, это выполняет за вас функция. Если распределение памяти закончилось неудачей, по любой причине, приложение будет закрыто.
gpointer g_malloc0( gulong size ); |
То же самое как выше, но обнуляет память перед возвращением указателя.
gpointer g_realloc( gpointer mem, gulong size ); |
Перемещает "size" байты памяти, начинающиеся в "mem". Очевидно, что память должна была быть предварительно распределена.
void g_free( gpointer mem ); |
Облегчённый вариант предыдущего примера освобождения памяти. Если mem равно NULL, то просто возвращается NULL.
void g_mem_profile( void ); |
Формирует дамп конфигурации используемой памяти, но требует, чтобы вы добавили #define MEM_PROFILE к заголовку glib/gmem.c и произвели re-make и make install.
void g_mem_check( gpointer mem ); |
Проверки правильности местоположения памяти. Требует, чтобы вы добавили #define MEM_CHECK к заголовку gmem.c и выполнили re-make и make install.
Singly Linked Lists |
Timers |
GTK+ 2.0 Tutorial |
||
---|---|---|
GLib |
Функции таймера могут использоваться в операциях времени (например, чтобы определять, сколько времени прошло). Вопервых вы создаёте новый таймер используя функцию g_timer_new(). После чего вы можете использовать g_timer_start() - чтобы начать рассчитывать операцию, g_timer_stop() - чтобы прекратить рассчитывать операцию и g_timer_elapsed() - чтобы определить прошедшее время.
GTimer *g_timer_new( void ); void g_timer_destroy( GTimer *timer ); void g_timer_start( GTimer *timer ); void g_timer_stop( GTimer *timer ); void g_timer_reset( GTimer *timer ); gdouble g_timer_elapsed( GTimer *timer, gulong *microseconds ); |
Memory Management |
String Handling |
GTK+ 2.0 Tutorial |
||
---|---|---|
GLib |
GLib определяет новый тип, названный GString, который является подобным стандартному C string, но который увеличивается автоматически. Это строковые данные с нулевым символом в конце, что дает вам - защиту от буферного переполнения, программируя ошибки в пределах вашей программы. Это - очень важная особенность, и следовательно я рекомендую, чтобы вы использовали GStrings. Сам GString имеет простое общее определение:
struct GString { gchar *str; /* Points to the string's current \0-terminated value. */ gint len; /* Current length */ }; |
Есть множество операций, которые вы можете сделать с GString.
GString *g_string_new( gchar *init ); |
Эта конструкция GString, копирует строковое значение из init в GString и возвращает указатель на него. NULL можно дать как параметр для первоначально пустого GString.
void g_string_free( GString *string, gint free_segment ); |
Освобождаем память для данного GString. Если free_segment TRUE, то также освобождаются его символьные данные.
GString *g_string_assign( GString *lval, const gchar *rval ); |
Это копирует символы из rval в lval, удаляя предыдущее содержание lval. Отметьте, что lval будет удлинен по мере необходимости, чтобы вместить содержание строки, в отличие от стандартной функции strcpy().
Остальная часть этих функций должна быть относительно очевидной ("_c" версии принимают символ вместо строки):
GString *g_string_truncate( GString *string, gint len ); GString *g_string_append( GString *string, gchar *val ); GString *g_string_append_c( GString *string, gchar c ); GString *g_string_prepend( GString *string, gchar *val ); GString *g_string_prepend_c( GString *string, gchar c ); void g_string_sprintf( GString *string, gchar *fmt, ...); void g_string_sprintfa ( GString *string, gchar *fmt, ... ); |
Timers |
Utility and Error Functions |
GTK+ 2.0 Tutorial |
||
---|---|---|
GLib |
gchar *g_strdup( const gchar *str ); |
Функция замены strdup. Копирует оригинальное содержимое строк в недавно выделенную память и возвращает указатель на неё.
gchar *g_strerror( gint errnum ); |
Я рекомендую использовать это для всех сообщений об ошибках. Это намного лучше и более портативно чем perror() или другие функции. Вывод имеет обычно форму:
program name:function that failed:file or further description:strerror |
Вот - пример одного такого вызова, используемого в нашей программе hello_world:
g_print("hello_world:open:%s:%s\n", filename, g_strerror(errno)); |
void g_error( gchar *format, ... ); |
Печатает сообщение об ошибках. Формат такой же как у printf, но добавляет "** ERROR **: " к вашему сообщению и выходит из программы. Используйте только для неустранимых ошибок.
void g_warning( gchar *format, ... ); |
Тоже что и выше но перед сообщением выводит "** WARNING **: " и не выходит из программы.
void g_message( gchar *format, ... ); |
Печатает "message: " перед помещенной строкой.
void g_print( gchar *format, ... ); |
gchar *g_strsignal( gint signum ); |
Распечатывает название сигнала системы Unix, получая номер сигнала. Полезен в основном функциям обрабатывающим сигналы.
Всё вышеописанное взято из glib.h и это лишь небольшая часть. Если вы захотите документировать какую нибудь функцию сообщите мне об этом!
String Handling |
GTK's rc Files |
GTK+ 2.0 Tutorial |
||
---|---|---|
GTK's rc Files |
Формат файла GTK иллюстрирован в примере ниже. Это файл testgtkrc из пакета поставки GTK, но с некоторыми дополнениями и комментариями. Вы можете включить эти комментарии в своё приложение, чтобы пользователь смог более точно настраивать его самостоятельно.
Есть несколько директив для изменения признаков виджета.
В дополнение к этому существует несколько состояний виджетов, и вы можете устанавливать разные цвета, pixmaps и шрифты в этих состояниях. Вот эти состояния:
NORMAL - Нормальное состояние виджета, когда курсор не находится на нём и не происходит никаких действий мышки связанных с этим виджетом.
PRELIGHT - Когда курсор мыши находится на виджете, задействуются цвета определённые для этого состояния.
ACTIVE - Когда произошло нажатие или щелчок на виджете, задействуются соответствующие для этого состояния настройки виджета.
INSENSITIVE - Когда виджет находится в неактивном состоянии и неможет быть активирован.
Для установки цветов виджета используйте ключевые слова "fg" и "bg" в следующем формате:
fg[<STATE>] = { Red, Green, Blue } |
Где STATE одно из вышеперечисленных состояний (PRELIGHT, ACTIVE, и т.д.), а Red, Green и Blue значения в диапазоне 0 - 1.0, например { 1.0, 1.0, 1.0 } даёт белый цвет. Значения должны быть десятичными или 0, таким образом значение "1" не будет работать, его нужно выставлять как "1.0". Неопределённые значения устанавливаются равными 0.
bg_pixmap подобен выше указанному, но вместо цветов указывается имя файла.
pixmap_path список путей разделённых ":". По этим путям будет производится поиск pixmap которые вы определите.
font = "<font name>" |
Единственная сложность заключается в выяснении строки шрифта. Использование утилиты xfontsel может в этом помочь.
"Widget_class" устанавливает стиль класса виджетов. Эти классы перечислены в кратком обзоре виджета в иерархии класса.
Директива "widget" устанавливает стиль для определенного названия виджетов, полностью игнорируя любой стиль определенный для класса виджетов. Эти виджеты зарегистрированы в приложении с помощью вызова gtk_widget_set_name(). Это позволяет вам определять атрибуты основного виджета, вместо установки атрибутов виджета для всего класса. Пожалуйста документируйте эти специальные виджеты, чтобы пользователи могли настраивать их.
Когда ключевое слово parent используется как признак, виджет берет атрибуты своего родителя в приложении.
Определяя стиль вы можете назначить предварительно определённые атрибуты.
style "main_button" = "button" { font = "-adobe-helvetica-medium-r-normal--*-100-*-*-*-*-*-*" bg[PRELIGHT] = { 0.75, 0, 0 } } |
В этом примере берётся стиль "button" и создаётся новый "main_button" стиль, просто изменяя шрифт и цвет.
Конечно многие атрибуты неприменимы ко всем виджетам, это просто вопрос здравого смысла, что именно может быть применено.
GTK's rc Files |
Example rc file |
GTK+ 2.0 Tutorial |
||
---|---|---|
GTK's rc Files |
GTK's rc File Format |
Writing Your Own Widgets |
GTK+ 2.0 Tutorial |
||
---|---|---|
Создание собственных виджетов (Writing Your Own Widgets) |
Перед тем как создавать собственный виджет, нужно иметь представление как GTK работает с объектами. Здесь приведен только краткий обзор, для более полной информации обратитесь к документации.
GTK виджеты выполнены в объектно ориентированном стиле. Однако написаны полностью на стандартном C. Это очень улучшает мобильность и стабильность по использованию текущего поколения C++ компиляторов; однако означает, что автор виджета должен обратить внимание на некоторые из деталей выполнения. Информация, обычная для всех случаев одного класса виджетов (например, для всех виджетов Кнопки) сохраняется в структуре класса (class structure). Есть только одна копия, в которой сохранена информация о сигналах класса (которые действуют как действительные функции в C). Чтобы поддерживать наследование, первая область в структуре класса должна быть копией структуры класса родителя. Декларация структуры класса GtkButtton:
struct _GtkButtonClass { GtkContainerClass parent_class; void (* pressed) (GtkButton *button); void (* released) (GtkButton *button); void (* clicked) (GtkButton *button); void (* enter) (GtkButton *button); void (* leave) (GtkButton *button); }; |
Когда кнопка трактуется как контейнер (например когда изменяется размер), его структура класса может быть приведена к GtkContainerClass, а важные области использованы обработчиками сигналов.
Есть также структура для каждого виджета, который создан на примере основного. Эта структура имеет области, чтобы хранить информацию, которая является отличной для каждого виджета. Мы назовем эту структуру object structure. Для класса Кнопки, это выглядит так:
struct _GtkButton { GtkContainer container; GtkWidget *child; guint in_button : 1; guint button_down : 1; }; |
Отметьте, что как и в структуре класса первая область в структуре объекта должна быть копией родителя так, чтобы структура могла быть приведена к структуре объекта родительского класса как необходимо.
Writing Your Own Widgets |
Creating a Composite widget |
GTK+ 2.0 Tutorial |
||
---|---|---|
Writing Your Own Widgets |
Один тип виджета который может вас заинтересовать, создаёт виджет на базе других виджетов GTK. Этот тип виджета не делает ничего, что не могло бы быть сделано, не создавая новые виджеты, но обеспечивает удобный способ упаковать элементы пользовательского интерфейса для многократного использования. Виджеты FileSelection и ColorSelection являются примерами такого типа виджетов.
Пример виджета который мы рассмотрим в этом разделе - Tictactoe widget, массив 3x3 кнопок переключателей который создаёт сигнал когда задействованы три переключателя в ряд по горизонтали или вертикали.
Родительский класс для сложного виджета - типичный контейнерный класс, который содержит все элементы сложного виджета. Например родительский класс виджета FileSelection - Диалоговый класс (Dialog class). Так как наши кнопки будут упорядочены в таблице, могло бы показаться естественным сделать наш родительский класс классом Таблицы (Table class). Но к сожалению, этот вариант не работает. Создание виджета разделено на две функции - функция WIDGETNAME_new() пользовательских вызовов, и функция WIDGETNAME_init() которая выполняет основную работу инициализации виджета, который не зависит от аргументов переданных в функцию the _new(). Дочерние виджеты только вызывают функцию _init их родительского виджета. Но это разделение не работает для таблиц, когда необходимо перед созданием знать число колонок и строк. Если мы не хотим дублировать большинство функциональных возможностей gtk_table_new() в нашем виджете Tictactoe, лучше всего избежать использование таблицы. Поэтому мы создаём таблицу из Vbox и прикрепляем её внутри VBox.
Каждый класс виджетов имеет заголовочные файлы в которых объявляются объекты и структуры классов для виджета, наряду с общими функциями. Есть некоторые особенности о которых стоит упомянуть. Чтобы предотвратить двойные определения, мы заключаем весь заголовочный файл в:
#ifndef __TICTACTOE_H__ #define __TICTACTOE_H__ . . . #endif /* __TICTACTOE_H__ */ |
А для поддержки C++ программ помещаем заголовочный файл в оболочку:
#ifdef __cplusplus extern "C" { #endif /* __cplusplus */ . . . #ifdef __cplusplus } #endif /* __cplusplus */ |
Наряду с функциями и структурами, мы объявляем три стандартных макроопределения в нашем заголовочном файле, TICTACTOE (obj), TICTACTOE_CLASS (klass), и IS_TICTACTOE (obj), которые помещают указатель в указатель на объект или структуру класса и проверяют если объект - виджет Tictactoe соответственно.
/* GTK - The GIMP Toolkit * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #ifndef __TICTACTOE_H__ #define __TICTACTOE_H__ #include <gdk/gdk.h> #include <gtk/gtkvbox.h> #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ #define TICTACTOE(obj) GTK_CHECK_CAST (obj, tictactoe_get_type (), Tictactoe) #define TICTACTOE_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, tictactoe_get_type (), TictactoeClass) #define IS_TICTACTOE(obj) GTK_CHECK_TYPE (obj, tictactoe_get_type ()) typedef struct _Tictactoe Tictactoe; typedef struct _TictactoeClass TictactoeClass; struct _Tictactoe { GtkVBox vbox; GtkWidget *buttons[3][3]; }; struct _TictactoeClass { GtkVBoxClass parent_class; void (* tictactoe) (Tictactoe *ttt); }; GtkType tictactoe_get_type (void); GtkWidget* tictactoe_new (void); void tictactoe_clear (Tictactoe *ttt); #ifdef __cplusplus } #endif /* __cplusplus */ #endif /* __TICTACTOE_H__ */ |
Продолжаем создание нашего виджета. Основная функция для каждого виджета - WIDGETNAME_get_type(). Когда вызывается эта функция, сначала она сообщает GTK о классе виджета, а потом идентифицирует класс виджета уникальным ID. В последующие вызовы она просто возвращает ID.
GtkType tictactoe_get_type () { static guint ttt_type = 0; if (!ttt_type) { GtkTypeInfo ttt_info = { "Tictactoe", sizeof (Tictactoe), sizeof (TictactoeClass), (GtkClassInitFunc) tictactoe_class_init, (GtkObjectInitFunc) tictactoe_init, (GtkArgSetFunc) NULL, (GtkArgGetFunc) NULL }; ttt_type = gtk_type_unique (gtk_vbox_get_type (), &ttt_info); } return ttt_type; } |
Структура GtkTypeInfo имеет следующее определение:
struct _GtkTypeInfo { gchar *type_name; guint object_size; guint class_size; GtkClassInitFunc class_init_func; GtkObjectInitFunc object_init_func; GtkArgSetFunc arg_set_func; GtkArgGetFunc arg_get_func; }; |
Значение полей этой структуры достаточно очевидно. Мы будем игнорировать arg_set_func и arg_get_func поля: они имеют важную, но пока мало используемую роль, позволяющую устанавливать варианты виджета интерпретируемых языков. Как только GTK получает корректно заполненную копию этой структуры, можно создавать объекты виджета специфического типа.
Функция WIDGETNAME_class_init() инициализирует поля структуры класса виджета и настраивает любые сигналы для класса. Для нашего виджета Tictactoe это выглядит так:
enum { TICTACTOE_SIGNAL, LAST_SIGNAL }; static gint tictactoe_signals[LAST_SIGNAL] = { 0 }; static void tictactoe_class_init (TictactoeClass *class) { GtkObjectClass *object_class; object_class = (GtkObjectClass*) class; tictactoe_signals[TICTACTOE_SIGNAL] = gtk_signal_new ("tictactoe", GTK_RUN_FIRST, object_class->type, GTK_SIGNAL_OFFSET (TictactoeClass, tictactoe), gtk_signal_default_marshaller, GTK_TYPE_NONE, 0); gtk_object_class_add_signals (object_class, tictactoe_signals, LAST_SIGNAL); class->tictactoe = NULL; } |
Наш виджет имеет только один сигнал, сигнал tictactoe, который производится когда ряд, колонка, или диагональ полностью заполнены. Не каждый сложный виджет нуждается в сигналах. Если вы новичок в программировании, вы можете перейти к следующему разделу, поскольку далее будет более сложный материал.
gint gtk_signal_new( const gchar *name, GtkSignalRunType run_type, GtkType object_type, gint function_offset, GtkSignalMarshaller marshaller, GtkType return_val, guint nparams, ...); |
Создаёт новый сигнал. Значения параметров:
run_type - выполняется ли по умолчанию, после или перед пользовательским обработчиком. Как правило это будет GTK_RUN_FIRST, или GTK_RUN_LAST, но могут быть и другие варианты.
object_type - ID из объекта, к которому этот сигнал обращается. (Это также применимо к объектам потомкам.)
function_offset - Смещение в пределах структуры класса указателя на обработчик значения по умолчанию.
Marshaller - Функция, которая используется, чтобы вызвать обработчик сигнала. Для обработчиков сигнала, которые не имеют никаких параметров кроме объекта, который произвёл сигнал и пользовательские данные, мы можем использовать pre-supplied marshaller функцию gtk_signal_default_marshaller.
Nparams - Число параметров обработчика сигнала (кроме двух упомянутых выше значений по умолчанию)
Определяя типы, используется перечисление GtkType:
gtk_signal_new() возвращает уникальный целочисленный идентификатор для сигнала, который мы храним в массиве tictactoe_signals, который мы индексируем используя перечисления.
После создания нашего сигнала, нужно чтобы GTK ассоциировал наш сигнал с Tictactoe class. Это делается вызовом функции gtk_object_class_add_signals(). Устанавливая указатель, который указывает на обработчик значения по умолчанию для сигнала "tictactoe" в NULL, указываем, что нет никакого действия по умолчанию.
Каждый класс виджета также нуждается в функции, чтобы инициализировать структуру объекта. Обычно, эта функция имеет справедливо ограниченную роль установки полей структуры к значениям по умолчанию. Для сложных виджетов, однако, эта функция также создает составные виджеты.
static void tictactoe_init (Tictactoe *ttt) { GtkWidget *table; gint i,j; table = gtk_table_new (3, 3, TRUE); gtk_container_add (GTK_CONTAINER(ttt), table); gtk_widget_show (table); for (i=0;i<3; i++) for (j=0;j<3; j++) { ttt->buttons[i][j] = gtk_toggle_button_new (); gtk_table_attach_defaults (GTK_TABLE(table), ttt->buttons[i][j], i, i+1, j, j+1); gtk_signal_connect (GTK_OBJECT (ttt->buttons[i][j]), "toggled", GTK_SIGNAL_FUNC (tictactoe_toggle), ttt); gtk_widget_set_size_request (ttt->buttons[i][j], 20, 20); gtk_widget_show (ttt->buttons[i][j]); } } |
Есть еще одна функция, которую каждый виджет ( за исключением основных типов виджета как Bin, который не может быть проиллюстрирован) должен иметь это функция, которую пользователь вызывает, чтобы создать объект определенного типа. Это традиционный вызов WIDGETNAME_new(). В некоторых виджетах, хотя не для виджета Tictactoe, эта функция берет параметры, и делает некоторые установки, основанные на параметрах. Другие две функции являются специальными для виджета Tictactoe.
tictactoe_clear() является общей функцией, которая сбрасывает все кнопки в виджете в позицию выключено (up). Обратите внимание на использование gtk_signal_handler_block_by_data(), чтобы оградить наш обработчик сигнала для кнопок переключателей от излишних вызовов.
tictactoe_toggle() является обработчиком сигнала, который вызывается, когда пользователь нажимает на кнопку. Выясняется наличие необходимой комбинации нажатых кнопок, если комбинация существует, производится сигнал "tictactoe".
GtkWidget* tictactoe_new () { return GTK_WIDGET ( gtk_type_new (tictactoe_get_type ())); } void tictactoe_clear (Tictactoe *ttt) { int i,j; for (i=0;i<3;i++) for (j=0;j<3;j++) { gtk_signal_handler_block_by_data (GTK_OBJECT(ttt->buttons[i][j]), ttt); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ttt->buttons[i][j]), FALSE); gtk_signal_handler_unblock_by_data (GTK_OBJECT(ttt->buttons[i][j]), ttt); } } static void tictactoe_toggle (GtkWidget *widget, Tictactoe *ttt) { int i,k; static int rwins[8][3] = { { 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 }, { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 } }; static int cwins[8][3] = { { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 }, { 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 }, { 0, 1, 2 }, { 2, 1, 0 } }; int success, found; for (k=0; k<8; k++) { success = TRUE; found = FALSE; for (i=0;i<3;i++) { success = success && GTK_TOGGLE_BUTTON(ttt->buttons[rwins[k][i]][cwins[k][i]])->active; found = found || ttt->buttons[rwins[k][i]][cwins[k][i]] == widget; } if (success && found) { gtk_signal_emit (GTK_OBJECT (ttt), tictactoe_signals[TICTACTOE_SIGNAL]); break; } } } |
И наконец, пример программы, использующей наш виджет Tictactoe:
#include <gtk/gtk.h> #include "tictactoe.h" /* Вызывается когда строка, столбец, или диагональ заполнены */ void win (GtkWidget *widget, gpointer data) { g_print ("Yay!\n"); tictactoe_clear (TICTACTOE (widget)); } int main (int argc, char *argv[]) { GtkWidget *window; GtkWidget *ttt; gtk_init (&argc, &argv); window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_window_set_title (GTK_WINDOW (window), "Aspect Frame"); gtk_signal_connect (GTK_OBJECT (window), "destroy", GTK_SIGNAL_FUNC (gtk_exit), NULL); gtk_container_set_border_width (GTK_CONTAINER (window), 10); /* Создаём новый Tictactoe виджет */ ttt = tictactoe_new (); gtk_container_add (GTK_CONTAINER (window), ttt); gtk_widget_show (ttt); /* И присоединяем "tictactoe" сигнал */ gtk_signal_connect (GTK_OBJECT (ttt), "tictactoe", GTK_SIGNAL_FUNC (win), NULL); gtk_widget_show (window); gtk_main (); return 0; } |
The Anatomy Of A Widget |
Creating a widget from scratch |
GTK+ 2.0 Tutorial |
||
---|---|---|
Writing Your Own Widgets |
В этом разделе большое внимание будет уделено самостоятельному отображению виджетов и взаимодействию с событиями. Для примера мы создадим аналог циферблата, чтобы пользователь при помощи перемещения стрелки мог устанавливать необходимое значение.
Отображение виджета на экране проходит в несколько шагов. После создания виджета функцией WIDGETNAME_new(), необходимо задействовать ещё несколько функций:
WIDGETNAME_realize() отвечает за создание X window для виджета, если это необходимо.
WIDGETNAME_map() вызывается после того, как пользователь вызвал gtk_widget_show(). Чтобы удостоверится в том, что виджет отобразился на экране (mapped). Для контейнерного класса, необходим вызов функции map()> для любых дочерних виджетов.
WIDGETNAME_draw() вызывается, когда вызвана функция gtk_widget_draw()для виджета или одного из предков. Это фактически вызов для отрисовки виджета на экране. Для контейнерных виджетов эта функция должна вызвать gtk_widget_draw() для всех дочерних вызовов.
WIDGETNAME_expose() является обработчиком событий виджета. Производит необходимые вызовы функций прорисовки для доступной части экрана. Для контейнерных виджетов, эта функция генерирует отображающие события для их дочерних виджетов неимеющих собственных окон. (Если они имеют собственные окна, то необходимые события генерирует X.)
Как вы наверное заметили последние две функции очень похожи - каждая отвечает за отрисовку виджета на экране. Большинство типов виджетов не беспокоят различия между двумя этими функциями. По умолчанию функция draw() в виджет классе просто генерирует событие для перерисовки площади. Однако, некоторые типы виджетов могут сохранить работу, различая две функции. Например, если виджет имеет многоуровневые окна X, то экспозиционное событие идентифицируют окно и может перерисовать только затронутое окно, которое не доступно для вызова draw().
Контейнерные виджеты, даже если они не беспокоятся о различии сами, не могут просто использовать по умолчанию draw() функцию, потому что их дочерние виджеты могут зависеть от этих различий. Однако, было бы расточительно дублировать код отрисовки между двумя функциями. Поэтому виджеты вызывают функцию WIDGETNAME_paint() которая выполняет основную работу по прорисовке виджета, которая в свою очередь вызывает функции draw() и expose().
В нашем примере, циферблат является не контейнерным виджетом и имеет единственное окно, поэтому мы можем по умолчанию использовать функцию draw() и только обеспечивать выполнение функции expose().
Большинство виджетов GTK берут своё начало с уже существующих виджетов. Хотя раздел называется "Создание виджета с нуля", на самом деле виджет циферблата создаётся на основе существующего виджета регулировок (Range widget). Это сделано потому, что наш виджет циферблата будет иметь интерфейс виджета масштабирования, который является специализированным потомком виджета регулировок (Range widget). Хотя код представленный ниже имеет законченную форму, не нужно думать, что это было написано ab initio способом. Кроме того не мешало бы просмотреть методы работы виджетов масштабирования.
Затем, после включения заголовочного файла, мы получаем некоторые функции для обеспечения информации о виджете и его инициализации:
#include <math.h> #include <stdio.h> #include <gtk/gtkmain.h> #include <gtk/gtksignal.h> #include "gtkdial.h" #define SCROLL_DELAY_LENGTH 300 #define DIAL_DEFAULT_SIZE 100 /* Forward declarations */ [ omitted to save space ] /* Локальные данные */ static GtkWidgetClass *parent_class = NULL; GtkType gtk_dial_get_type () { static GtkType dial_type = 0; if (!dial_type) { static const GtkTypeInfo dial_info = { "GtkDial", sizeof (GtkDial), sizeof (GtkDialClass), (GtkClassInitFunc) gtk_dial_class_init, (GtkObjectInitFunc) gtk_dial_init, /* reserved_1 */ NULL, /* reserved_1 */ NULL, (GtkClassInitFunc) NULL }; dial_type = gtk_type_unique (GTK_TYPE_WIDGET, &dial_info); } return dial_type; } static void gtk_dial_class_init (GtkDialClass *class) { GtkObjectClass *object_class; GtkWidgetClass *widget_class; object_class = (GtkObjectClass*) class; widget_class = (GtkWidgetClass*) class; parent_class = gtk_type_class (gtk_widget_get_type ()); object_class->destroy = gtk_dial_destroy; widget_class->realize = gtk_dial_realize; widget_class->expose_event = gtk_dial_expose; widget_class->size_request = gtk_dial_size_request; widget_class->size_allocate = gtk_dial_size_allocate; widget_class->button_press_event = gtk_dial_button_press; widget_class->button_release_event = gtk_dial_button_release; widget_class->motion_notify_event = gtk_dial_motion_notify; } static void gtk_dial_init (GtkDial *dial) { dial->button = 0; dial->policy = GTK_UPDATE_CONTINUOUS; dial->timer = 0; dial->radius = 0; dial->pointer_width = 0; dial->angle = 0.0; dial->old_value = 0.0; dial->old_lower = 0.0; dial->old_upper = 0.0; dial->adjustment = NULL; } GtkWidget* gtk_dial_new (GtkAdjustment *adjustment) { GtkDial *dial; dial = gtk_type_new (gtk_dial_get_type ()); if (!adjustment) adjustment = (GtkAdjustment*) gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0); gtk_dial_set_adjustment (dial, adjustment); return GTK_WIDGET (dial); } static void gtk_dial_destroy (GtkObject *object) { GtkDial *dial; g_return_if_fail (object != NULL); g_return_if_fail (GTK_IS_DIAL (object)); dial = GTK_DIAL (object); if (dial->adjustment) gtk_object_unref (GTK_OBJECT (dial->adjustment)); if (GTK_OBJECT_CLASS (parent_class)->destroy) (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); } |
Заметьте, что функция init() делает меньше чем в виджете Tictactoe, так как это не сложный виджет, а функция new()делает больше, так как имеет аргумент. Кроме того, заметьте что указатель на объект регулирования мы увеличиваем подсчетом ссылок, (и соответственно уменьшаем когда больше не используем).
Кроме того, есть несколько функций, чтобы управлять опциями виджета:
GtkAdjustment* gtk_dial_get_adjustment (GtkDial *dial) { g_return_val_if_fail (dial != NULL, NULL); g_return_val_if_fail (GTK_IS_DIAL (dial), NULL); return dial->adjustment; } void gtk_dial_set_update_policy (GtkDial *dial, GtkUpdateType policy) { g_return_if_fail (dial != NULL); g_return_if_fail (GTK_IS_DIAL (dial)); dial->policy = policy; } void gtk_dial_set_adjustment (GtkDial *dial, GtkAdjustment *adjustment) { g_return_if_fail (dial != NULL); g_return_if_fail (GTK_IS_DIAL (dial)); if (dial->adjustment) { gtk_signal_disconnect_by_data (GTK_OBJECT (dial->adjustment), (gpointer) dial); gtk_object_unref (GTK_OBJECT (dial->adjustment)); } dial->adjustment = adjustment; gtk_object_ref (GTK_OBJECT (dial->adjustment)); gtk_signal_connect (GTK_OBJECT (adjustment), "changed", (GtkSignalFunc) gtk_dial_adjustment_changed, (gpointer) dial); gtk_signal_connect (GTK_OBJECT (adjustment), "value_changed", (GtkSignalFunc) gtk_dial_adjustment_value_changed, (gpointer) dial); dial->old_value = adjustment->value; dial->old_lower = adjustment->lower; dial->old_upper = adjustment->upper; gtk_dial_update (dial); } |
Теперь мы дошли до нового типа функций. Первая функция выполняет работу по созданию X window (X окна). Обратите внимание, что маску передают в функции gdk_window_new(), которая определяет, какие поля структуры GdkWindowAttr фактически имеют данные в них (оставшимся полям будут присвоены значения по умолчанию). Также ценность, отмечающая - способ, которым маска события виджета создана. Мы вызываем gtk_widget_get_events(), чтобы отыскать маску события которую пользователь определил для этого виджета (с помощью gtk_widget_set_events()), и добавляем событие которое нас интересует непосредственно.
После создания окна, мы устанавливаем его стиль и фон, и помещаем указатель на виджет в пользовательской области данных GdkWindow. Это позволяет GTK посылать события окна правильному виджету.
static void gtk_dial_realize (GtkWidget *widget) { GtkDial *dial; GdkWindowAttr attributes; gint attributes_mask; g_return_if_fail (widget != NULL); g_return_if_fail (GTK_IS_DIAL (widget)); GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED); dial = GTK_DIAL (widget); attributes.x = widget->allocation.x; attributes.y = widget->allocation.y; attributes.width = widget->allocation.width; attributes.height = widget->allocation.height; attributes.wclass = GDK_INPUT_OUTPUT; attributes.window_type = GDK_WINDOW_CHILD; attributes.event_mask = gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK; attributes.visual = gtk_widget_get_visual (widget); attributes.colormap = gtk_widget_get_colormap (widget); attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP; widget->window = gdk_window_new (widget->parent->window, &attributes, attributes_mask); widget->style = gtk_style_attach (widget->style, widget->window); gdk_window_set_user_data (widget->window, widget); gtk_style_set_background (widget->style, widget->window, GTK_STATE_ACTIVE); } |
Когда окно содержащее виджет отображается первый раз и каждый раз когда изменяется его расположение, GTK запрашивает дочерние виджеты о желаемом размере. Этот запрос обрабатывается функцией gtk_dial_size_request (). Так как наш виджет не контейнерный и не имеет никаких реальных ограничений на его размер, мы только возвращаем разумное значение по умолчанию.
static void gtk_dial_size_request (GtkWidget *widget, GtkRequisition *requisition) { requisition->width = DIAL_DEFAULT_SIZE; requisition->height = DIAL_DEFAULT_SIZE; } |
После того, как все виджеты запросили идеальный размер, размещение окна вычислено, и каждый дочерний виджет зарегистрирован относительно его натуральной величины. Как правило это будет необходимый размер, но если пользователь изменит размеры окна, то размер виджета может оказаться меньше требуемого. Уведомление о размере обрабатывается функцией gtk_dial_size_allocate(). Обратите внимание, что так же как вычисление размеров некоторых составляющих частей для будущего использования, эта подпрограмма также выполняет работу сворачивания и перемещения X окна виджета в новую позицию или размер.
static void gtk_dial_size_allocate (GtkWidget *widget, GtkAllocation *allocation) { GtkDial *dial; g_return_if_fail (widget != NULL); g_return_if_fail (GTK_IS_DIAL (widget)); g_return_if_fail (allocation != NULL); widget->allocation = *allocation; if (GTK_WIDGET_REALIZED (widget)) { dial = GTK_DIAL (widget); gdk_window_move_resize (widget->window, allocation->x, allocation->y, allocation->width, allocation->height); dial->radius = MAX(allocation->width,allocation->height) * 0.45; dial->pointer_width = dial->radius / 5; } } |
Как упомянуто выше, прорисовка данного виджета выполнена обработчиком для событий экспозиции (expose events). Здесь изменений не много, только функция gtk_draw_polygon для отрисовки указателя с трехмерным оттенением согласно цветам сохраненным в стиле виджета.
static gint gtk_dial_expose (GtkWidget *widget, GdkEventExpose *event) { GtkDial *dial; GdkPoint points[3]; gdouble s,c; gdouble theta; gint xc, yc; gint tick_length; gint i; g_return_val_if_fail (widget != NULL, FALSE); g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE); if (event->count > 0) return FALSE; dial = GTK_DIAL (widget); gdk_window_clear_area (widget->window, 0, 0, widget->allocation.width, widget->allocation.height); xc = widget->allocation.width/2; yc = widget->allocation.height/2; /* отрисовка подсказок */ for (i=0; i<25; i++) { theta = (i*M_PI/18. - M_PI/6.); s = sin(theta); c = cos(theta); tick_length = (i%6 == 0) ? dial->pointer_width : dial->pointer_width/2; gdk_draw_line (widget->window, widget->style->fg_gc[widget->state], xc + c*(dial->radius - tick_length), yc - s*(dial->radius - tick_length), xc + c*dial->radius, yc - s*dial->radius); } /* отрисовка указателя */ s = sin(dial->angle); c = cos(dial->angle); points[0].x = xc + s*dial->pointer_width/2; points[0].y = yc + c*dial->pointer_width/2; points[1].x = xc + c*dial->radius; points[1].y = yc - s*dial->radius; points[2].x = xc - s*dial->pointer_width/2; points[2].y = yc - c*dial->pointer_width/2; gtk_draw_polygon (widget->style, widget->window, GTK_STATE_NORMAL, GTK_SHADOW_OUT, points, 3, TRUE); return FALSE; } |
Остальная часть кода виджета обрабатывает различные типы событий, и не слишком отличается от того, что могло бы быть найдено во многих приложениях GTK. Два типа событий могут произойти - или пользователь может нажать на виджет мышкой и переместиться, чтобы переместить стрелку, или значение объекта настройки (Adjustment object) может измениться из-за некоторого внешнего обстоятельства.
Если пользователь нажал на виджет, мы выясняем был ли щелчок возле стрелки, ели это так, то заносим кнопку которую нажал пользователь в поле "кнопка" структуры виджета и захватываем все события связанные с мышкой с помощью вызова gtk_grab_add(). Последующее движение мыши заставляет повторно вычислить значение управления (функцией gtk_dial_update_mouse). В зависимости от политики, которая была установлена, событие "value_changed" генерируется немедленно (GTK_UPDATE_CONTINUOUS), после задержки таймера, добавленного gtk_timeout_add() (GTK_UPDATE_DELAYED), или только когда кнопка отпущена (GTK_UPDATE_DISCONTINUOUS).
static gint gtk_dial_button_press (GtkWidget *widget, GdkEventButton *event) { GtkDial *dial; gint dx, dy; double s, c; double d_parallel; double d_perpendicular; g_return_val_if_fail (widget != NULL, FALSE); g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE); dial = GTK_DIAL (widget); /* Определяем нажата ли кнопка в области стрелки - мы выполнем это определяя параллельное и перпендикулярное положение точки нажатия от линии проходящей через стрелку */ dx = event->x - widget->allocation.width / 2; dy = widget->allocation.height / 2 - event->y; s = sin(dial->angle); c = cos(dial->angle); d_parallel = s*dy + c*dx; d_perpendicular = fabs(s*dx - c*dy); if (!dial->button && (d_perpendicular < dial->pointer_width/2) && (d_parallel > - dial->pointer_width)) { gtk_grab_add (widget); dial->button = event->button; gtk_dial_update_mouse (dial, event->x, event->y); } return FALSE; } static gint gtk_dial_button_release (GtkWidget *widget, GdkEventButton *event) { GtkDial *dial; g_return_val_if_fail (widget != NULL, FALSE); g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE); dial = GTK_DIAL (widget); if (dial->button == event->button) { gtk_grab_remove (widget); dial->button = 0; if (dial->policy == GTK_UPDATE_DELAYED) gtk_timeout_remove (dial->timer); if ((dial->policy != GTK_UPDATE_CONTINUOUS) && (dial->old_value != dial->adjustment->value)) gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed"); } return FALSE; } static gint gtk_dial_motion_notify (GtkWidget *widget, GdkEventMotion *event) { GtkDial *dial; GdkModifierType mods; gint x, y, mask; g_return_val_if_fail (widget != NULL, FALSE); g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE); dial = GTK_DIAL (widget); if (dial->button != 0) { x = event->x; y = event->y; if (event->is_hint || (event->window != widget->window)) gdk_window_get_pointer (widget->window, &x, &y, &mods); switch (dial->button) { case 1: mask = GDK_BUTTON1_MASK; break; case 2: mask = GDK_BUTTON2_MASK; break; case 3: mask = GDK_BUTTON3_MASK; break; default: mask = 0; break; } if (mods & mask) gtk_dial_update_mouse (dial, x,y); } return FALSE; } static gint gtk_dial_timer (GtkDial *dial) { g_return_val_if_fail (dial != NULL, FALSE); g_return_val_if_fail (GTK_IS_DIAL (dial), FALSE); if (dial->policy == GTK_UPDATE_DELAYED) gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed"); return FALSE; } static void gtk_dial_update_mouse (GtkDial *dial, gint x, gint y) { gint xc, yc; gfloat old_value; g_return_if_fail (dial != NULL); g_return_if_fail (GTK_IS_DIAL (dial)); xc = GTK_WIDGET(dial)->allocation.width / 2; yc = GTK_WIDGET(dial)->allocation.height / 2; old_value = dial->adjustment->value; dial->angle = atan2(yc-y, x-xc); if (dial->angle < -M_PI/2.) dial->angle += 2*M_PI; if (dial->angle < -M_PI/6) dial->angle = -M_PI/6; if (dial->angle > 7.*M_PI/6.) dial->angle = 7.*M_PI/6.; dial->adjustment->value = dial->adjustment->lower + (7.*M_PI/6 - dial->angle) * (dial->adjustment->upper - dial->adjustment->lower) / (4.*M_PI/3.); if (dial->adjustment->value != old_value) { if (dial->policy == GTK_UPDATE_CONTINUOUS) { gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed"); } else { gtk_widget_draw (GTK_WIDGET(dial), NULL); if (dial->policy == GTK_UPDATE_DELAYED) { if (dial->timer) gtk_timeout_remove (dial->timer); dial->timer = gtk_timeout_add (SCROLL_DELAY_LENGTH, (GtkFunction) gtk_dial_timer, (gpointer) dial); } } } } |
Изменения настройки (Adjustment) нашего виджета внешними средствами происходит с помощью сигналов "changed" и "value_changed". Обработчики для этих функций вызывает gtk_dial_update(), чтобы утвердить параметры, вычислить новый угол указателя и перерисовать виджет (вызвав gtk_widget_draw()).
static void gtk_dial_update (GtkDial *dial) { gfloat new_value; g_return_if_fail (dial != NULL); g_return_if_fail (GTK_IS_DIAL (dial)); new_value = dial->adjustment->value; if (new_value < dial->adjustment->lower) new_value = dial->adjustment->lower; if (new_value > dial->adjustment->upper) new_value = dial->adjustment->upper; if (new_value != dial->adjustment->value) { dial->adjustment->value = new_value; gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed"); } dial->angle = 7.*M_PI/6. - (new_value - dial->adjustment->lower) * 4.*M_PI/3. / (dial->adjustment->upper - dial->adjustment->lower); gtk_widget_draw (GTK_WIDGET(dial), NULL); } static void gtk_dial_adjustment_changed (GtkAdjustment *adjustment, gpointer data) { GtkDial *dial; g_return_if_fail (adjustment != NULL); g_return_if_fail (data != NULL); dial = GTK_DIAL (data); if ((dial->old_value != adjustment->value) || (dial->old_lower != adjustment->lower) || (dial->old_upper != adjustment->upper)) { gtk_dial_update (dial); dial->old_value = adjustment->value; dial->old_lower = adjustment->lower; dial->old_upper = adjustment->upper; } } static void gtk_dial_adjustment_value_changed (GtkAdjustment *adjustment, gpointer data) { GtkDial *dial; g_return_if_fail (adjustment != NULL); g_return_if_fail (data != NULL); dial = GTK_DIAL (data); if (dial->old_value != adjustment->value) { gtk_dial_update (dial); dial->old_value = adjustment->value; } } |
Виджет циферблата (Dial widget) который мы описали имеет 670 строк кода. Однако, есть еще довольно много расширений, которые можно добавить этому виджету:
Если вы попробуете использовать этот виджет, вы заметите некоторое мерцание при вращении указателя. Это происходит потому, что каждый раз при изменении указателя перерисовывается весь виджет. Лучшим решением этой проблемы будет закадровая отрисовка pixmap, затем копирование финального результата на экран за один шаг. (Виджет ProgressBar отрисовывается таким образом.)
Пользователь должен иметь возможность использовать клавиши up и down для изменения значений указателя.
Было бы хорошо иметь кнопки регулирующие размер шага увеличения или уменьшения значения, при удержании которых происходит автоматическое изменение, также как это сделано в виджете полосы прокрутки (scrollbar). Большая часть кода, для осуществления этого типа поведения может быть найдена в виджете регулирования (Range widget).
Виджет циферблата мог быть превращен в контейнерный виджет с единственным дочерним виджетом, позиционированным в основание между упомянутыми выше кнопками.
Creating a Composite widget |
Learning More |
GTK+ 2.0 Tutorial |
||
---|---|---|
Writing Your Own Widgets |
Только небольшая часть некоторых деталей вовлеченных в создание виджета была затронута выше. Если вы хотите создать свой виджет, то лучшим способом для изучения данного процесса, являются непосредственно исходные тексты GTK. Задайте себе несколько вопросов о том, какой виджет вы хотите создать: Это будет контейнерный виджет? Он будет иметь собственное окно? Это модификация существующего виджета? Если да, то найдите похожий виджет и начинайте вносить изменения. Удачи!
Creating a widget from scratch |
Scribble, A Simple Example Drawing Program |
GTK+ 2.0 Tutorial |
||
---|---|---|
Scribble, A Simple Example Drawing Program |
Сигналы GTK для действий высшего уровня (high-level) мы уже обсудили, такие как выбор пункта меню. Но иногда полезно использовать сигналы низкого уровня (lower-level), такие как передвижение курсора мышки, или нажатие клавиши. Обработчики для этих сигналов имеют дополнительный параметр, который является указателем на структуру, содержащую информацию о событии. Например, обработчики события движения передают указатель на структуру GdkEventMotion, которая частично выглядит так:
struct _GdkEventMotion { GdkEventType type; GdkWindow *window; guint32 time; gdouble x; gdouble y; ... guint state; ... }; |
type - устанавливает тип события, в этом случае GDK_MOTION_NOTIFY, window - окно в котором произошло событие. x и y - координаты произошедшего события. state - определяет состояние модификатора, когда произошло событие ( то есть определяет, какие модифицирующие клавиши и кнопки мыши были нажаты). Это - поразрядное ИЛИ, как например:
GDK_SHIFT_MASK GDK_LOCK_MASK GDK_CONTROL_MASK GDK_MOD1_MASK GDK_MOD2_MASK GDK_MOD3_MASK GDK_MOD4_MASK GDK_MOD5_MASK GDK_BUTTON1_MASK GDK_BUTTON2_MASK GDK_BUTTON3_MASK GDK_BUTTON4_MASK GDK_BUTTON5_MASK |
Для других сигналов, чтобы определить, какое событие произошло, мы вызываем gtk_signal_connect(). Но мы также должны сообщить GTK о каких событиях нас следует уведомлять. Это делается функцией:
void gtk_widget_set_events (GtkWidget *widget, gint events); |
Вторая часть определяет события, которыми мы интересуемся. Это - поразрядное ИЛИ констант, которые определяют различные типы событий. Типы события:
GDK_EXPOSURE_MASK GDK_POINTER_MOTION_MASK GDK_POINTER_MOTION_HINT_MASK GDK_BUTTON_MOTION_MASK GDK_BUTTON1_MOTION_MASK GDK_BUTTON2_MOTION_MASK GDK_BUTTON3_MOTION_MASK GDK_BUTTON_PRESS_MASK GDK_BUTTON_RELEASE_MASK GDK_KEY_PRESS_MASK GDK_KEY_RELEASE_MASK GDK_ENTER_NOTIFY_MASK GDK_LEAVE_NOTIFY_MASK GDK_FOCUS_CHANGE_MASK GDK_STRUCTURE_MASK GDK_PROPERTY_CHANGE_MASK GDK_PROXIMITY_IN_MASK GDK_PROXIMITY_OUT_MASK |
Есть несколько нюансов которые нужно соблюсти вызывая gtk_widget_set_events(). Первое - нужно вызвать эту функцию перед тем как будет создано окно X для виджета GTK. Практически вы должны вызвать функцию сразу после создания виджета. Во вторых - виджет должен иметь ассоциированное X окно(X window). Для эффективности, многие типы виджетов не имеют собственного окна, а отображаются в окне их родителя. Вот эти виджеты:
GtkAlignment GtkArrow GtkBin GtkBox GtkImage GtkItem GtkLabel GtkPixmap GtkScrolledWindow GtkSeparator GtkTable GtkAspectFrame GtkFrame GtkVBox GtkHBox GtkVSeparator GtkHSeparator |
Чтобы перехватывать события для этих виджетов, вы должны использовать виджет EventBox. См. раздел EventBox.
Для нашей программы рисования, мы должны знать, когда кнопка мыши нажата и мышь перемещена, таким образом мы определяем GDK_POINTER_MOTION_MASK и GDK_BUTTON_PRESS_MASK. Мы также должны знать, когда перерисовать наше окно, таким образом мы определяем GDK_EXPOSURE_MASK. Хотя нам необходимо уведомление об изменении размера нашего окна через конфигурацию события, мы не должны определять передачу флага GDK_STRUCTURE_MASK, потому что это автоматически определено для всех окон.
Однако, как оказывается есть проблема с определением GDK_POINTER_MOTION_MASK. Это заставит сервер добавлять новое событие передвижения в очередь событий каждый раз, когда пользователь перемещает мышь. Вообразите, что нам требуется 0.1 секунды, чтобы обработать событие движения, но в очередь X сервера поступает новое событие передвижения каждые 0.05 секунды. Если пользователь рисовал в течении 5 секунд, нам потребуется ещё 5 секунд для перехвата после освобождения кнопки мышки! Но нам нужно получить только одно событие движения для каждого случая, который мы обрабатываем. Это можно сделать определив GDK_POINTER_MOTION_HINT_MASK.
Мы определяем GDK_POINTER_MOTION_HINT_MASK, когда сервер в первый раз сообщает нам о передвижении указателя после входа в наше окно, или после нажатия или освобождения кнопки. Последующие события движения будут подавлены, пока мы явно не спросим положение указателя, используя функцию:
GdkWindow* gdk_window_get_pointer (GdkWindow *window, gint *x, gint *y, GdkModifierType *mask); |
( Есть другая функция gtk_widget_get_pointer(), которая имеет более простой интерфейс, но оказывается, не очень полезной, так как только восстанавливает положение мышки в состояние не нажатых кнопок.)
Код установки событий для нашего окна выглядит так:
gtk_signal_connect (GTK_OBJECT (drawing_area), "expose_event", (GtkSignalFunc) expose_event, NULL); gtk_signal_connect (GTK_OBJECT(drawing_area),"configure_event", (GtkSignalFunc) configure_event, NULL); gtk_signal_connect (GTK_OBJECT (drawing_area), "motion_notify_event", (GtkSignalFunc) motion_notify_event, NULL); gtk_signal_connect (GTK_OBJECT (drawing_area), "button_press_event", (GtkSignalFunc) button_press_event, NULL); gtk_widget_set_events (drawing_area, GDK_EXPOSURE_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK); |
Мы сохраним "expose_event" и "configure_event" обработчики позже. Обработчики "motion_notify_event" и "button_press_event" довольно просты:
static gint button_press_event (GtkWidget *widget, GdkEventButton *event) { if (event->button == 1 && pixmap != NULL) draw_brush (widget, event->x, event->y); return TRUE; } static gint motion_notify_event (GtkWidget *widget, GdkEventMotion *event) { int x, y; GdkModifierType state; if (event->is_hint) gdk_window_get_pointer (event->window, &x, &y, &state); else { x = event->x; y = event->y; state = event->state; } if (state & GDK_BUTTON1_MASK && pixmap != NULL) draw_brush (widget, x, y); return TRUE; } |
Scribble, A Simple Example Drawing Program |
The DrawingArea Widget, And Drawing |
GTK+ 2.0 Tutorial |
||
---|---|---|
Scribble, A Simple Example Drawing Program |
Теперь мы приступаем к процессу рисования на экране. Виджет, который мы используем для этого, называется DrawingArea (область рисования). Виджет области рисования - по существу простое окно X. Это чистый холст в котором мы можем рисовать то, что мы хотим. Область рисования создаётся вызовом:
GtkWidget* gtk_drawing_area_new (void); |
Размер виджета по умолчанию может быть определён вызовом:
void gtk_drawing_area_size (GtkDrawingArea *darea, gint width, gint height); |
Этот размер может быть изменён вызовом функции gtk_widget_set_size_request(), а размер определённый с помощью этой функции может быть изменен пользователем с помощью простого увеличения или уменьшения окна содержащего область рисования.
Должно быть отмечено, что, когда мы создаем виджет DrawingArea, мы полностью ответственны за отрисовку содержимого. Если наше окно скрыто, то при открытии мы получаем событие экспозиции и должны перерисовать то, что было предварительно скрыто.
Необходимо помнить все, что было нарисовано на экране, чтобы можно было должным образом перерисовать. Кроме того, может быть визуально неприятно, если части окна очищаются и перерисовываются шаг за шагом. Решение этой проблемы заключается в использовании закадрового backing pixmap. Вместо того, чтобы рисовать непосредственно на экране, мы рисуем на изображении сохраненном в памяти сервера, но не отображенном на экране, затем когда изменения изображения или новые части изображения отображены, мы копируем необходимые части на экран.
Создание закадрового pixmap, выполняется функцией:
GdkPixmap* gdk_pixmap_new (GdkWindow *window, gint width, gint height, gint depth); |
Параметр window определяет окно GDK, от которого этот pixmap берет некоторые из его свойств. width и height определяет размер pixmap. depth определяет глубину цвета (color depth) - число битов в пикселах, для нового окна. Если глубина будет определена как -1, то будет соответствовать глубине window.
Мы создаем pixmap в нашем "configure_event" обработчике. Это событие производится всякий раз, когда окно изменяет размер, включая первоначальное создание.
/* Резервирование pixmap для области рисования */ static GdkPixmap *pixmap = NULL; /* Создание нового backing pixmap соответствующего размера */ static gint configure_event (GtkWidget *widget, GdkEventConfigure *event) { if (pixmap) gdk_pixmap_unref(pixmap); pixmap = gdk_pixmap_new(widget->window, widget->allocation.width, widget->allocation.height, -1); gdk_draw_rectangle (pixmap, widget->style->white_gc, TRUE, 0, 0, widget->allocation.width, widget->allocation.height); return TRUE; } |
Функция gdk_draw_rectangle() очищает pixmap инициализируя белым. Подробней об этом чуть ниже.
Наш обработчик события экспонирования просто копирует необходимую часть pixmap на дисплей (мы определяем область, которую должны перерисовать при использовании события->площадь области для события экспонирования):
/* Перерисовываем экран используя backing pixmap */ static gint expose_event (GtkWidget *widget, GdkEventExpose *event) { gdk_draw_pixmap(widget->window, widget->style->fg_gc[GTK_WIDGET_STATE (widget)], pixmap, event->area.x, event->area.y, event->area.x, event->area.y, event->area.width, event->area.height); return FALSE; } |
Теперь мы знаем как применить обновление экрана используя нашу карту пикселей (pixmap), но как фактически рисовать необходимый нам материал на нашей карте (pixmap)? Есть большое количество вызовов в библиотеках GTK's GDK для рисования drawables. drawable - это просто что то, что может быть нарисовано. Это может быть окно, карта пикселей, или точечный рисунок (черно-белое изображение). Мы уже видели два таких вызова выше gdk_draw_rectangle() и gdk_draw_pixmap(). Вот полный список:
gdk_draw_line () gdk_draw_rectangle () gdk_draw_arc () gdk_draw_polygon () gdk_draw_string () gdk_draw_text () gdk_draw_pixmap () gdk_draw_bitmap () gdk_draw_image () gdk_draw_points () gdk_draw_segments () |
См. справочную документацию или файл заголовка <gdk/gdk.h> для получения детальной информации относительно этих функций. Все эти функции имеют те же самые два аргумента. Первый параметр - drawable, второй параметр - graphics context (GC).
Графический контекст инкапсулирует информацию о цвете переднего и заднего фона, а также о толщине линии. GDK имеет полный набор функций для того, чтобы создавать и изменять графические контексты , но сохраняя простоту, мы будем использовать только предопределенные графические контексты. Каждый виджет имеет связанный стиль. (Который может быть изменен в gtkrc файле, см. раздел GTK's rc file.) Он, между прочим, хранит множество графического контекста. Некоторые примеры доступа к этим графическим контекстам:
widget->style->white_gc widget->style->black_gc widget->style->fg_gc[GTK_STATE_NORMAL] widget->style->bg_gc[GTK_WIDGET_STATE(widget)] |
Поля fg_gc, bg_gc, dark_gc, и light_gc индексированы параметром типа GtkStateType, который может принимать значения:
GTK_STATE_NORMAL, GTK_STATE_ACTIVE, GTK_STATE_PRELIGHT, GTK_STATE_SELECTED, GTK_STATE_INSENSITIVE |
Например, для GTK_STATE_SELECTED цвет символа имеет значение по умолчанию - белый, а цвет фона - темно-синий.
Наша функция draw_brush(), для фактической отрисовки на дисплей:
/* Рисуем прямоугольник на экране */ static void draw_brush (GtkWidget *widget, gdouble x, gdouble y) { GdkRectangle update_rect; update_rect.x = x - 5; update_rect.y = y - 5; update_rect.width = 10; update_rect.height = 10; gdk_draw_rectangle (pixmap, widget->style->black_gc, TRUE, update_rect.x, update_rect.y, update_rect.width, update_rect.height); gtk_widget_draw (widget, &update_rect); } |
После того, как мы нарисовали прямоугольник перенесенный на pixmap, вызываем функцию:
void gtk_widget_draw(GtkWidget *widget, GdkRectangle *area); |
которая сообщает X, что область полученная параметром area должна быть обновлена. X в конечном счете генерирует событие экспонирования (возможно объединение областей, которые передаются в нескольких вызовах gtk_widget_draw()) которое вызовет наш обработчик событий экспонирования, чтобы скопировать необходимые участки на экран.
Вот мы и рассмотрели всю программу для рисования, за исключением простых деталей, таких как создание основного окна.
Event Handling |
Adding XInput support |
GTK+ 2.0 Tutorial |
||
---|---|---|
Scribble, A Simple Example Drawing Program |
Устройства ввода (например, графические планшеты), позволяющие рисовать удобнее и проще, чем мышью, последнее время стали намного дешевле . Самый простой метод использования подобных устройств - замена мыши, но следует заметить, что также существуют другие преимущества :
Меж точечное расположение
Для детальной информации о дополнительных возможностях XInput, смотрите XInput HOWTO.
Анализируя, например, структуру GdkEventMotion мы видим, что она имеет поля для хранения информации об устройствах с дополнительными возможностями.
struct _GdkEventMotion { GdkEventType type; GdkWindow *window; guint32 time; gdouble x; gdouble y; gdouble pressure; gdouble xtilt; gdouble ytilt; guint state; gint16 is_hint; GdkInputSource source; guint32 deviceid; }; |
pressure определяет давление, задаваемое числом между 0 и 1. xtilt и ytilt могут иметь значения между -1 и 1, соответствующие углу наклона в каждом из направлений. source и deviceid указывают на устройство, связанное с событием. source предоставляет простую информацию об устройстве. Она может иметь такие значения:
GDK_SOURCE_MOUSE GDK_SOURCE_PEN GDK_SOURCE_ERASER GDK_SOURCE_CURSOR |
deviceid - уникальный ID устройства. Оно может быть использовано для выяснения дальнейшей информации об устройстве с помощью вызова gdk_input_list_devices() (см. ниже). Специальное значение GDK_CORE_POINTER чаще всего указывает простую мышь.
Для того, чтобы дать GTK понять о нашем желании использовать дополнительную информацию устройства ввода, нужно всего-то добавить одну строку в программу:
gtk_widget_set_extension_events (drawing_area, GDK_EXTENSION_EVENTS_CURSOR); |
Используя значение GDK_EXTENSION_EVENTS_CURSOR мы указываем, что мы заинтересованы в дополнительных событиях устройства, но только если мы сами не должны рисовать свой собственный курсор. См. Дальнейшие исследования для выяснения более подробной информации о рисовании курсора. Также можно использовать GDK_EXTENSION_EVENTS_ALL при желании рисовать свои курсоры или GDK_EXTENSION_EVENTS_NONE для возврата к настройкам по умолчанию.
Но это ещё не всё. По умолчанию дополнительные возможности устройств не включены: мы должны пользователям предоставить некий механизм для конфигурирования устройств. Для автоматизации этого процесса GTK предоставляет виджет InputDialog. Предоставленный код демонстрирует работу с InputDialog:
void input_dialog_destroy (GtkWidget *w, gpointer data) { *((GtkWidget **)data) = NULL; } void create_input_dialog () { static GtkWidget *inputd = NULL; if (!inputd) { inputd = gtk_input_dialog_new(); gtk_signal_connect (GTK_OBJECT(inputd), "destroy", (GtkSignalFunc)input_dialog_destroy, &inputd); gtk_signal_connect_object (GTK_OBJECT(GTK_INPUT_DIALOG(inputd)->close_button), "clicked", (GtkSignalFunc)gtk_widget_hide, GTK_OBJECT(inputd)); gtk_widget_hide ( GTK_INPUT_DIALOG(inputd)->save_button); gtk_widget_show (inputd); } else { if (!GTK_WIDGET_MAPPED(inputd)) gtk_widget_show(inputd); else gdk_window_raise(inputd->window); } } |
(Следует заметить, что после уничтожения диалога, мы не храним указатель на него. Это гарантирует отсутствие ошибки сегментации.)
InputDialog имеет две кнопки: "Закрыть" и "Сохранить", которые ничего в принципе не делают: "Закрыть" прячет диалог, "Сохранить" - спрятана.
Устройство включено - можно начинать пользоваться дополнительными возможностями. В принципе, использование этой информации - вполне безопасное действие, т.к. полученные данные будут вполне вменяемы, даже если устройство не было включено.
Следует использовать gdk_input_window_get_pointer(), а не gdk_window_get_pointer, т.к. последний вызов не возвращает дополнительную информацию устройства.
void gdk_input_window_get_pointer( GdkWindow *window, guint32 deviceid, gdouble *x, gdouble *y, gdouble *pressure, gdouble *xtilt, gdouble *ytilt, GdkModifierType *mask) |
При вызове функции следует указать ID устройства и окно. Обычно ID берётся из поля deviceid структуры события. Опять же, даже при наличии обычного устройства ввода (мышь) полученные данные будут корректными. Просто event->deviceid будет иметь значение GDK_CORE_POINTER.
Обработчики событий нажатия кнопки или движения особо не меняются - лишь добавляется обработка дополнительной информации.
static gint button_press_event (GtkWidget *widget, GdkEventButton *event) { print_button_press (event->deviceid); if (event->button == 1 && pixmap != NULL) draw_brush (widget, event->source, event->x, event->y, event->pressure); return TRUE; } static gint motion_notify_event (GtkWidget *widget, GdkEventMotion *event) { gdouble x, y; gdouble pressure; GdkModifierType state; if (event->is_hint) gdk_input_window_get_pointer (event->window, event->deviceid, &x, &y, &pressure, NULL, NULL, &state); else { x = event->x; y = event->y; pressure = event->pressure; state = event->state; } if (state & GDK_BUTTON1_MASK && pixmap != NULL) draw_brush (widget, event->source, x, y, pressure); return TRUE; |
Так же следует как-нибудь использовать полученную информацию. Скажем, функция draw_brush() рисует разными цветами в зависимости от event->source и меняет размер кисти в зависимости от давления.
/* Draw a rectangle on the screen, size depending on pressure, and color on the type of device */ static void draw_brush (GtkWidget *widget, GdkInputSource source, gdouble x, gdouble y, gdouble pressure) { GdkGC *gc; GdkRectangle update_rect; switch (source) { case GDK_SOURCE_MOUSE: gc = widget->style->dark_gc[GTK_WIDGET_STATE (widget)]; break; case GDK_SOURCE_PEN: gc = widget->style->black_gc; break; case GDK_SOURCE_ERASER: gc = widget->style->white_gc; break; default: gc = widget->style->light_gc[GTK_WIDGET_STATE (widget)]; } update_rect.x = x - 10 * pressure; update_rect.y = y - 10 * pressure; update_rect.width = 20 * pressure; update_rect.height = 20 * pressure; gdk_draw_rectangle (pixmap, gc, TRUE, update_rect.x, update_rect.y, update_rect.width, update_rect.height); gtk_widget_draw (widget, &update_rect); } |
В качестве примера приведём код, который показывает имя устройства при нажатии на кнопку. Для выяснения имени используется функция
GList *gdk_input_list_devices (void); |
, которая возвращает список (GList из библиотеки GLib) структур GdkDeviceInfo. GdkDeviceInfo определена как:
struct _GdkDeviceInfo { guint32 deviceid; gchar *name; GdkInputSource source; GdkInputMode mode; gint has_cursor; gint num_axes; GdkAxisUse *axes; gint num_keys; GdkDeviceKey *keys; }; |
Скорее всего большенство полей этой структуры будет Вами проигнорировано до тех пор, пока Вам не нужно сохранение конфигурации XInput. В данный момент нас интересует поле name, которое представляет из себя имя, присвоенное устройству X'ми. В свою очередь, если has_cursor равен false, нам следует рисовать курсор самим, но т.к. мы указали GDK_EXTENSION_EVENTS_CURSOR нам не следует об этом беспокоится.
Функция print_button_press() просто итерирует по возвращённому списку до тех пор пока не найдёт совпадение.
static void print_button_press (guint32 deviceid) { GList *tmp_list; /* gdk_input_list_devices returns an internal list, so we shouldn't free it afterwards */ tmp_list = gdk_input_list_devices(); while (tmp_list) { GdkDeviceInfo *info = (GdkDeviceInfo *)tmp_list->data; if (info->deviceid == deviceid) { printf("Button press on device '%s'\n", info->name); return; } tmp_list = tmp_list->next; } |
Описанное выше - последний шаг включения поддержки "XInput" в нашей программе.
Не смотря на то, что наша программа достаточно не плохо поддерживает XInput, не хватает того, что мы бы желали видеть в полноценной программе. Во-первых, пользователь скорее всего не захочет конфигурировать устройство ввода при каждом запуске программы - мы должны позволить сохранить конфигурацию. Это достигается итерированием по результату gdk_input_list_devices() и записью результата в файл.
Для восстановления состояния при загрузке программы GDK предоставляет следующие функции:
gdk_input_set_extension_events() gdk_input_set_source() gdk_input_set_mode() gdk_input_set_axes() gdk_input_set_ke |
(Список, возвращённый gdk_input_list_devices() не должен изменяться на прямую.) Пример подобной программы - gsumi (доступна:http://www.msc.cornell.edu/~otaylor/gsumi/) Конечно, было бы прекрасно иметь стандартный метод выполнения подобной процедуры, но, наверное, это задача библиотек более высокого уровня, скажем GNOME.
Другой не малый недостаток - отсутствие курсоров. Платформы, отличные от XFree86, на данный момент не позволяют одновременное использование устройства ввода как простую мышь и специальное устройство, используемое напрямую из приложения. Подробнее: XInput-HOWTO. Это означает, что если автор приложения желает сделать своё приложение более универсальным, нужно курсоры рисовать самому.
Приложение, само желающее рисовать курсоры, должно сделать 2 вещи: определить требует ли устройство ввода прорисовки курсора и состояние устройства ввода (ведь приложение должно вести себя натурально: курсор пропадает, если стилус не дотрагивается до планшета, и появляется при контакте с планшетом). Первое достигается поиском устройства в списке по имени. Второе - используя событие "proximity_out". Пример прорисовки собственных курсоров может быть найден в программе "testinput" из поставки GTK.
The DrawingArea Widget, And Drawing |
Tips For Writing GTK Applications |
GTK+ 2.0 Tutorial | ||
---|---|---|
<<< Previous | GTK Signals | Next >>> |
void GtkWidget::show (GtkWidget *, gpointer); void GtkWidget::hide (GtkWidget *, gpointer); void GtkWidget::map (GtkWidget *, gpointer); void GtkWidget::unmap (GtkWidget *, gpointer); void GtkWidget::realize (GtkWidget *, gpointer); void GtkWidget::unrealize (GtkWidget *, gpointer); void GtkWidget::draw (GtkWidget *, ggpointer, gpointer); void GtkWidget::draw-focus (GtkWidget *, gpointer); void GtkWidget::draw-default (GtkWidget *, gpointer); void GtkWidget::size-request (GtkWidget *, ggpointer, gpointer); void GtkWidget::size-allocate (GtkWidget *, ggpointer, gpointer); void GtkWidget::state-changed (GtkWidget *, GtkStateType, gpointer); void GtkWidget::parent-set (GtkWidget *, GtkObject *, gpointer); void GtkWidget::style-set (GtkWidget *, GtkStyle *, gpointer); void GtkWidget::add-accelerator (GtkWidget *, gguint, GtkAccelGroup *, gguint, GdkModifierType, GtkAccelFlags, gpointer); void GtkWidget::remove-accelerator (GtkWidget *, GtkAccelGroup *, gguint, GdkModifierType, gpointer); gboolean GtkWidget::event (GtkWidget *, GdkEvent *, gpointer); gboolean GtkWidget::button-press-event (GtkWidget *, GdkEvent *, gpointer); gboolean GtkWidget::button-release-event (GtkWidget *, GdkEvent *, gpointer); gboolean GtkWidget::motion-notify-event (GtkWidget *, GdkEvent *, gpointer); gboolean GtkWidget::delete-event (GtkWidget *, GdkEvent *, gpointer); gboolean GtkWidget::destroy-event (GtkWidget *, GdkEvent *, gpointer); gboolean GtkWidget::expose-event (GtkWidget *, GdkEvent *, gpointer); gboolean GtkWidget::key-press-event (GtkWidget *, GdkEvent *, gpointer); gboolean GtkWidget::key-release-event (GtkWidget *, GdkEvent *, gpointer); gboolean GtkWidget::enter-notify-event (GtkWidget *, GdkEvent *, gpointer); gboolean GtkWidget::leave-notify-event (GtkWidget *, GdkEvent *, gpointer); gboolean GtkWidget::configure-event (GtkWidget *, GdkEvent *, gpointer); gboolean GtkWidget::focus-in-event (GtkWidget *, GdkEvent *, gpointer); gboolean GtkWidget::focus-out-event (GtkWidget *, GdkEvent *, gpointer); gboolean GtkWidget::map-event (GtkWidget *, GdkEvent *, gpointer); gboolean GtkWidget::unmap-event (GtkWidget *, GdkEvent *, gpointer); gboolean GtkWidget::property-notify-event (GtkWidget *, GdkEvent *, gpointer); gboolean GtkWidget::selection-clear-event (GtkWidget *, GdkEvent *, gpointer); gboolean GtkWidget::selection-request-event (GtkWidget *, GdkEvent *, gpointer); gboolean GtkWidget::selection-notify-event (GtkWidget *, GdkEvent *, gpointer); void GtkWidget::selection-get (GtkWidget *, GtkSelectionData *, gguint, gpointer); void GtkWidget::selection-received (GtkWidget *, GtkSelectionData *, gguint, gpointer); gboolean GtkWidget::proximity-in-event (GtkWidget *, GdkEvent *, gpointer); gboolean GtkWidget::proximity-out-event (GtkWidget *, GdkEvent *, gpointer); void GtkWidget::drag-begin (GtkWidget *, GdkDragContext *, gpointer); void GtkWidget::drag-end (GtkWidget *, GdkDragContext *, gpointer); void GtkWidget::drag-data-delete (GtkWidget *, GdkDragContext *, gpointer); void GtkWidget::drag-leave (GtkWidget *, GdkDragContext *, gguint, gpointer); gboolean GtkWidget::drag-motion (GtkWidget *, GdkDragContext *, ggint, ggint, gguint, gpointer); gboolean GtkWidget::drag-drop (GtkWidget *, GdkDragContext *, ggint, ggint, gguint, gpointer); void GtkWidget::drag-data-get (GtkWidget *, GdkDragContext *, GtkSelectionData *, gguint, gguint, gpointer); void GtkWidget::drag-data-received (GtkWidget *, GdkDragContext *, ggint, ggint, GtkSelectionData *, gguint, gguint, gpointer); gboolean GtkWidget::client-event (GtkWidget *, GdkEvent *, gpointer); gboolean GtkWidget::no-expose-event (GtkWidget *, GdkEvent *, gpointer); gboolean GtkWidget::visibility-notify-event (GtkWidget *, GdkEvent *, gpointer); void GtkWidget::debug-msg (GtkWidget *, GtkString *, gpointer); |
<<< Previous | Home | Next >>> |
GTK Signals | Up | GtkData |
GTK+ 2.0 Tutorial | ||
---|---|---|
<<< Previous | GTK Signals | Next >>> |
void GtkData::disconnect (GtkData *, gpointer); |
void GtkContainer::add (GtkContainer *, GtkWidget *, gpointer); void GtkContainer::remove (GtkContainer *, GtkWidget *, gpointer); void GtkContainer::check-resize (GtkContainer *, gpointer); GtkDirectionType GtkContainer::focus (GtkContainer *, GtkDirectionType, gpointer); void GtkContainer::set-focus-child (GtkContainer *, GtkWidget *, gpointer); |
void GtkCalendar::month-changed (GtkCalendar *, gpointer); void GtkCalendar::day-selected (GtkCalendar *, gpointer); void GtkCalendar::day-selected-double-click (GtkCalendar *, gpointer); void GtkCalendar::prev-month (GtkCalendar *, gpointer); void GtkCalendar::next-month (GtkCalendar *, gpointer); void GtkCalendar::prev-year (GtkCalendar *, gpointer); void GtkCalendar::next-year (GtkCalendar *, gpointer); |
void GtkEditable::changed (GtkEditable *, gpointer); void GtkEditable::insert-text (GtkEditable *, GtkString *, ggint, ggpointer, gpointer); void GtkEditable::delete-text (GtkEditable *, ggint, ggint, gpointer); void GtkEditable::activate (GtkEditable *, gpointer); void GtkEditable::set-editable (GtkEditable *, gboolean, gpointer); void GtkEditable::move-cursor (GtkEditable *, ggint, ggint, gpointer); void GtkEditable::move-word (GtkEditable *, ggint, gpointer); void GtkEditable::move-page (GtkEditable *, ggint, ggint, gpointer); void GtkEditable::move-to-row (GtkEditable *, ggint, gpointer); void GtkEditable::move-to-column (GtkEditable *, ggint, gpointer); void GtkEditable::kill-char (GtkEditable *, ggint, gpointer); void GtkEditable::kill-word (GtkEditable *, ggint, gpointer); void GtkEditable::kill-line (GtkEditable *, ggint, gpointer); void GtkEditable::cut-clipboard (GtkEditable *, gpointer); void GtkEditable::copy-clipboard (GtkEditable *, gpointer); void GtkEditable::paste-clipboard (GtkEditable *, gpointer); |
void GtkNotebook::switch-page (GtkNotebook *, ggpointer, gguint, gpointer); |
void GtkList::selection-changed (GtkList *, gpointer); void GtkList::select-child (GtkList *, GtkWidget *, gpointer); void GtkList::unselect-child (GtkList *, GtkWidget *, gpointer); |
void GtkMenuShell::deactivate (GtkMenuShell *, gpointer); void GtkMenuShell::selection-done (GtkMenuShell *, gpointer); void GtkMenuShell::move-current (GtkMenuShell *, GtkMenuDirectionType, gpointer); void GtkMenuShell::activate-current (GtkMenuShell *, gboolean, gpointer); void GtkMenuShell::cancel (GtkMenuShell *, gpointer); |
void GtkToolbar::orientation-changed (GtkToolbar *, ggint, gpointer); void GtkToolbar::style-changed (GtkToolbar *, ggint, gpointer); |
void GtkButton::pressed (GtkButton *, gpointer); void GtkButton::released (GtkButton *, gpointer); void GtkButton::clicked (GtkButton *, gpointer); void GtkButton::enter (GtkButton *, gpointer); void GtkButton::leave (GtkButton *, gpointer); |
void GtkItem::select (GtkItem *, gpointer); void GtkItem::deselect (GtkItem *, gpointer); void GtkItem::toggle (GtkItem *, gpointer); |
void GtkWindow::set-focus (GtkWindow *, ggpointer, gpointer); |
void GtkHandleBox::child-attached (GtkHandleBox *, GtkWidget *, gpointer); void GtkHandleBox::child-detached (GtkHandleBox *, GtkWidget *, gpointer); |
void GtkToggleButton::toggled (GtkToggleButton *, gpointer); |
void GtkMenuItem::activate (GtkMenuItem *, gpointer); void GtkMenuItem::activate-item (GtkMenuItem *, gpointer); |
<<< Previous | Home | Next >>> |
GtkToggleButton | Up | GtkCheckMenuItem |
GTK+ 2.0 Tutorial | ||
---|---|---|
<<< Previous | GTK Signals | Next >>> |
void GtkCheckMenuItem::toggled (GtkCheckMenuItem *, gpointer); |
<<< Previous | Home | Next >>> |
GtkMenuItem | Up | GtkInputDialog |
GTK+ 2.0 Tutorial | ||
---|---|---|
<<< Previous | GTK Signals | Next >>> |
void GtkInputDialog::enable-device (GtkInputDialog *, ggint, gpointer); void GtkInputDialog::disable-device (GtkInputDialog *, ggint, gpointer); |
<<< Previous | Home | Next >>> |
GtkCheckMenuItem | Up | GtkColorSelection |
GTK+ 2.0 Tutorial | ||
---|---|---|
<<< Previous | GTK Signals | Next >>> |
void GtkColorSelection::color-changed (GtkColorSelection *, gpointer); |
<<< Previous | Home | Next >>> |
GtkInputDialog | Up | GtkStatusBar |
GTK+ 2.0 Tutorial | ||
---|---|---|
<<< Previous | GTK Signals | Next >>> |
void GtkStatusbar::text-pushed (GtkStatusbar *, gguint, GtkString *, gpointer); void GtkStatusbar::text-popped (GtkStatusbar *, gguint, GtkString *, gpointer); |
<<< Previous | Home | Next >>> |
GtkColorSelection | Up | GtkCurve |
GTK+ 2.0 Tutorial | ||
---|---|---|
<<< Previous | GTK Signals | Next >>> |
void GtkCurve::curve-type-changed (GtkCurve *, gpointer); |
<<< Previous | Home | Next >>> |
GtkStatusBar | Up | GtkAdjustment |
GTK+ 2.0 Tutorial | ||
---|---|---|
<<< Previous | GTK Signals | Next >>> |
void GtkAdjustment::changed (GtkAdjustment *, gpointer); void GtkAdjustment::value-changed (GtkAdjustment *, gpointer); |
<<< Previous | Home | Next >>> |
GtkCurve | Up | GDK Event Types |
GTK+ 2.0 Tutorial | ||
---|---|---|
<<< Previous | Code Examples | Next >>> |
/* GTK - The GIMP Toolkit * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #ifndef __GTK_DIAL_H__ #define __GTK_DIAL_H__ #include <gdk/gdk.h> #include <gtk/gtkadjustment.h> #include <gtk/gtkwidget.h> #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ #define GTK_DIAL(obj) GTK_CHECK_CAST (obj, gtk_dial_get_type (), GtkDial) #define GTK_DIAL_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, gtk_dial_get_type (), GtkDialClass) #define GTK_IS_DIAL(obj) GTK_CHECK_TYPE (obj, gtk_dial_get_type ()) typedef struct _GtkDial GtkDial; typedef struct _GtkDialClass GtkDialClass; struct _GtkDial { GtkWidget widget; /* update policy (GTK_UPDATE_[CONTINUOUS/DELAYED/DISCONTINUOUS]) */ guint policy : 2; /* Button currently pressed or 0 if none */ guint8 button; /* Dimensions of dial components */ gint radius; gint pointer_width; /* ID of update timer, or 0 if none */ guint32 timer; /* Current angle */ gfloat angle; gfloat last_angle; /* Old values from adjustment stored so we know when something changes */ gfloat old_value; gfloat old_lower; gfloat old_upper; /* The adjustment object that stores the data for this dial */ GtkAdjustment *adjustment; }; struct _GtkDialClass { GtkWidgetClass parent_class; }; GtkWidget* gtk_dial_new (GtkAdjustment *adjustment); GtkType gtk_dial_get_type (void); GtkAdjustment* gtk_dial_get_adjustment (GtkDial *dial); void gtk_dial_set_update_policy (GtkDial *dial, GtkUpdateType policy); void gtk_dial_set_adjustment (GtkDial *dial, GtkAdjustment *adjustment); #ifdef __cplusplus } #endif /* __cplusplus */ #endif /* __GTK_DIAL_H__ */ |
/* GTK - The GIMP Toolkit * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include <math.h> #include <stdio.h> #include <gtk/gtkmain.h> #include <gtk/gtksignal.h> #include "gtkdial.h" #define SCROLL_DELAY_LENGTH 300 #define DIAL_DEFAULT_SIZE 100 /* Forward declarations */ static void gtk_dial_class_init (GtkDialClass *klass); static void gtk_dial_init (GtkDial *dial); static void gtk_dial_destroy (GtkObject *object); static void gtk_dial_realize (GtkWidget *widget); static void gtk_dial_size_request (GtkWidget *widget, GtkRequisition *requisition); static void gtk_dial_size_allocate (GtkWidget *widget, GtkAllocation *allocation); static gint gtk_dial_expose (GtkWidget *widget, GdkEventExpose *event); static gint gtk_dial_button_press (GtkWidget *widget, GdkEventButton *event); static gint gtk_dial_button_release (GtkWidget *widget, GdkEventButton *event); static gint gtk_dial_motion_notify (GtkWidget *widget, GdkEventMotion *event); static gint gtk_dial_timer (GtkDial *dial); static void gtk_dial_update_mouse (GtkDial *dial, gint x, gint y); static void gtk_dial_update (GtkDial *dial); static void gtk_dial_adjustment_changed (GtkAdjustment *adjustment, gpointer data); static void gtk_dial_adjustment_value_changed (GtkAdjustment *adjustment, gpointer data); /* Local data */ static GtkWidgetClass *parent_class = NULL; GType gtk_dial_get_type () { static GType dial_type = 0; if (!dial_type) { static const GTypeInfo dial_info = { sizeof (GtkDialClass), NULL, NULL, (GClassInitFunc) gtk_dial_class_init, NULL, NULL, sizeof (GtkDial), 0, (GInstanceInitFunc) gtk_dial_init, }; dial_type = g_type_register_static (GTK_TYPE_WIDGET, "GtkDial", &dial_info, 0); } return dial_type; } static void gtk_dial_class_init (GtkDialClass *class) { GtkObjectClass *object_class; GtkWidgetClass *widget_class; object_class = (GtkObjectClass*) class; widget_class = (GtkWidgetClass*) class; parent_class = gtk_type_class (gtk_widget_get_type ()); object_class->destroy = gtk_dial_destroy; widget_class->realize = gtk_dial_realize; widget_class->expose_event = gtk_dial_expose; widget_class->size_request = gtk_dial_size_request; widget_class->size_allocate = gtk_dial_size_allocate; widget_class->button_press_event = gtk_dial_button_press; widget_class->button_release_event = gtk_dial_button_release; widget_class->motion_notify_event = gtk_dial_motion_notify; } static void gtk_dial_init (GtkDial *dial) { dial->button = 0; dial->policy = GTK_UPDATE_CONTINUOUS; dial->timer = 0; dial->radius = 0; dial->pointer_width = 0; dial->angle = 0.0; dial->old_value = 0.0; dial->old_lower = 0.0; dial->old_upper = 0.0; dial->adjustment = NULL; } GtkWidget* gtk_dial_new (GtkAdjustment *adjustment) { GtkDial *dial; dial = g_object_new (gtk_dial_get_type (), NULL); if (!adjustment) adjustment = (GtkAdjustment*) gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0); gtk_dial_set_adjustment (dial, adjustment); return GTK_WIDGET (dial); } static void gtk_dial_destroy (GtkObject *object) { GtkDial *dial; g_return_if_fail (object != NULL); g_return_if_fail (GTK_IS_DIAL (object)); dial = GTK_DIAL (object); if (dial->adjustment) { g_object_unref (GTK_OBJECT (dial->adjustment)); dial->adjustment = NULL; } if (GTK_OBJECT_CLASS (parent_class)->destroy) (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); } GtkAdjustment* gtk_dial_get_adjustment (GtkDial *dial) { g_return_val_if_fail (dial != NULL, NULL); g_return_val_if_fail (GTK_IS_DIAL (dial), NULL); return dial->adjustment; } void gtk_dial_set_update_policy (GtkDial *dial, GtkUpdateType policy) { g_return_if_fail (dial != NULL); g_return_if_fail (GTK_IS_DIAL (dial)); dial->policy = policy; } void gtk_dial_set_adjustment (GtkDial *dial, GtkAdjustment *adjustment) { g_return_if_fail (dial != NULL); g_return_if_fail (GTK_IS_DIAL (dial)); if (dial->adjustment) { g_signal_handlers_disconnect_by_func (GTK_OBJECT (dial->adjustment), NULL, (gpointer) dial); g_object_unref (GTK_OBJECT (dial->adjustment)); } dial->adjustment = adjustment; g_object_ref (GTK_OBJECT (dial->adjustment)); g_signal_connect (GTK_OBJECT (adjustment), "changed", GTK_SIGNAL_FUNC (gtk_dial_adjustment_changed), (gpointer) dial); g_signal_connect (GTK_OBJECT (adjustment), "value_changed", GTK_SIGNAL_FUNC (gtk_dial_adjustment_value_changed), (gpointer) dial); dial->old_value = adjustment->value; dial->old_lower = adjustment->lower; dial->old_upper = adjustment->upper; gtk_dial_update (dial); } static void gtk_dial_realize (GtkWidget *widget) { GtkDial *dial; GdkWindowAttr attributes; gint attributes_mask; g_return_if_fail (widget != NULL); g_return_if_fail (GTK_IS_DIAL (widget)); GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED); dial = GTK_DIAL (widget); attributes.x = widget->allocation.x; attributes.y = widget->allocation.y; attributes.width = widget->allocation.width; attributes.height = widget->allocation.height; attributes.wclass = GDK_INPUT_OUTPUT; attributes.window_type = GDK_WINDOW_CHILD; GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK; attributes.visual = gtk_widget_get_visual (widget); attributes.colormap = gtk_widget_get_colormap (widget); attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP; widget->window = gdk_window_new (widget->parent->window, &attributes, attributes_mask); widget->style = gtk_style_attach (widget->style, widget->window); gdk_window_set_user_data (widget->window, widget); gtk_style_set_background (widget->style, widget->window, GTK_STATE_ACTIVE); } gtk_dial_size_request (GtkWidget *widget, GtkRequisition *requisition) { requisition->width = DIAL_DEFAULT_SIZE; requisition->height = DIAL_DEFAULT_SIZE; } static void gtk_dial_size_allocate (GtkWidget *widget, GtkAllocation *allocation) { GtkDial *dial; g_return_if_fail (widget != NULL); g_return_if_fail (GTK_IS_DIAL (widget)); g_return_if_fail (allocation != NULL); widget->allocation = *allocation; dial = GTK_DIAL (widget); if (GTK_WIDGET_REALIZED (widget)) { gdk_window_move_resize (widget->window, allocation->x, allocation->y, allocation->width, allocation->height); } dial->radius = MIN (allocation->width, allocation->height) * 0.45; dial->pointer_width = dial->radius / 5; } static gint gtk_dial_expose (GtkWidget *widget, GdkEventExpose *event) { GtkDial *dial; GdkPoint points[6]; gdouble s,c; gdouble theta, last, increment; GtkStyle *blankstyle; gint xc, yc; gint upper, lower; gint tick_length; gint i, inc; g_return_val_if_fail (widget != NULL, FALSE); g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE); if (event->count > 0) return FALSE; dial = GTK_DIAL (widget); /* gdk_window_clear_area (widget->window, 0, 0, widget->allocation.width, widget->allocation.height); */ xc = widget->allocation.width / 2; yc = widget->allocation.height / 2; upper = dial->adjustment->upper; lower = dial->adjustment->lower; /* Erase old pointer */ s = sin (dial->last_angle); c = cos (dial->last_angle); dial->last_angle = dial->angle; points[0].x = xc + s*dial->pointer_width/2; points[0].y = yc + c*dial->pointer_width/2; points[1].x = xc + c*dial->radius; points[1].y = yc - s*dial->radius; points[2].x = xc - s*dial->pointer_width/2; points[2].y = yc - c*dial->pointer_width/2; points[3].x = xc - c*dial->radius/10; points[3].y = yc + s*dial->radius/10; points[4].x = points[0].x; points[4].y = points[0].y; blankstyle = gtk_style_new (); blankstyle->bg_gc[GTK_STATE_NORMAL] = widget->style->bg_gc[GTK_STATE_NORMAL]; blankstyle->dark_gc[GTK_STATE_NORMAL] = widget->style->bg_gc[GTK_STATE_NORMAL]; blankstyle->light_gc[GTK_STATE_NORMAL] = widget->style->bg_gc[GTK_STATE_NORMAL]; blankstyle->black_gc = widget->style->bg_gc[GTK_STATE_NORMAL]; gtk_paint_polygon (blankstyle, widget->window, GTK_STATE_NORMAL, GTK_SHADOW_OUT, NULL, widget, NULL, points, 5, FALSE); g_object_unref (blankstyle); /* Draw ticks */ if ((upper - lower) == 0) return FALSE; increment = (100*M_PI) / (dial->radius*dial->radius); inc = (upper - lower); while (inc < 100) inc *= 10; while (inc >= 1000) inc /= 10; last = -1; for (i = 0; i <= inc; i++) { theta = ((gfloat)i*M_PI / (18*inc/24.) - M_PI/6.); if ((theta - last) < (increment)) last = theta; s = sin (theta); c = cos (theta); tick_length = (i%(inc/10) == 0) ? dial->pointer_width : dial->pointer_width / 2; gdk_draw_line (widget->window, widget->style->fg_gc[widget->state], xc + c*(dial->radius - tick_length), yc - s*(dial->radius - tick_length), xc + c*dial->radius, yc - s*dial->radius); } /* Draw pointer */ s = sin (dial->angle); c = cos (dial->angle); dial->last_angle = dial->angle; points[0].x = xc + s*dial->pointer_width/2; points[0].y = yc + c*dial->pointer_width/2; points[1].x = xc + c*dial->radius; points[1].y = yc - s*dial->radius; points[2].x = xc - s*dial->pointer_width/2; points[2].y = yc - c*dial->pointer_width/2; points[3].x = xc - c*dial->radius/10; points[3].y = yc + s*dial->radius/10; points[4].x = points[0].x; points[4].y = points[0].y; gtk_paint_polygon (widget->style, widget->window, GTK_STATE_NORMAL, GTK_SHADOW_OUT, NULL, widget, NULL, points, 5, TRUE); return FALSE; } static gint gtk_dial_button_press (GtkWidget *widget, GdkEventButton *event) { GtkDial *dial; gint dx, dy; double s, c; double d_parallel; double d_perpendicular; g_return_val_if_fail (widget != NULL, FALSE); g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE); dial = GTK_DIAL (widget); do this by computing the parallel and perpendicular distance of the point where the mouse was pressed from the line passing through the pointer */ dx = event->x - widget->allocation.width / 2; dy = widget->allocation.height / 2 - event->y; s = sin (dial->angle); c = cos (dial->angle); d_parallel = s*dy + c*dx; d_perpendicular = fabs (s*dx - c*dy); if (!dial->button && (d_perpendicular < dial->pointer_width/2) && (d_parallel > - dial->pointer_width)) { gtk_grab_add (widget); dial->button = event->button; gtk_dial_update_mouse (dial, event->x, event->y); } return FALSE; } static gint gtk_dial_button_release (GtkWidget *widget, GdkEventButton *event) { GtkDial *dial; g_return_val_if_fail (widget != NULL, FALSE); g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE); dial = GTK_DIAL (widget); if (dial->button == event->button) { gtk_grab_remove (widget); dial->button = 0; if (dial->policy == GTK_UPDATE_DELAYED) gtk_timeout_remove (dial->timer); if ((dial->policy != GTK_UPDATE_CONTINUOUS) && (dial->old_value != dial->adjustment->value)) g_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed"); } return FALSE; } static gint gtk_dial_motion_notify (GtkWidget *widget, GdkEventMotion *event) { GtkDial *dial; GdkModifierType mods; gint x, y, mask; g_return_val_if_fail (widget != NULL, FALSE); g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE); dial = GTK_DIAL (widget); if (dial->button != 0) { x = event->x; y = event->y; if (event->is_hint || (event->window != widget->window)) gdk_window_get_pointer (widget->window, &x, &y, &mods); switch (dial->button) { case 1: mask = GDK_BUTTON1_MASK; break; case 2: mask = GDK_BUTTON2_MASK; break; case 3: mask = GDK_BUTTON3_MASK; break; default: mask = 0; break; } if (mods & mask) gtk_dial_update_mouse (dial, x,y); } return FALSE; } static gint gtk_dial_timer (GtkDial *dial) { g_return_val_if_fail (dial != NULL, FALSE); g_return_val_if_fail (GTK_IS_DIAL (dial), FALSE); if (dial->policy == GTK_UPDATE_DELAYED) g_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed"); return FALSE; } static void gtk_dial_update_mouse (GtkDial *dial, gint x, gint y) { gint xc, yc; gfloat old_value; g_return_if_fail (dial != NULL); g_return_if_fail (GTK_IS_DIAL (dial)); xc = GTK_WIDGET(dial)->allocation.width / 2; yc = GTK_WIDGET(dial)->allocation.height / 2; old_value = dial->adjustment->value; dial->angle = atan2(yc-y, x-xc); if (dial->angle < -M_PI/2.) dial->angle += 2*M_PI; if (dial->angle < -M_PI/6) dial->angle = -M_PI/6; if (dial->angle > 7.*M_PI/6.) dial->angle = 7.*M_PI/6.; dial->adjustment->value = dial->adjustment->lower + (7.*M_PI/6 - dial->angle) * (dial->adjustment->upper - dial->adjustment->lower) / (4.*M_PI/3.); if (dial->adjustment->value != old_value) { if (dial->policy == GTK_UPDATE_CONTINUOUS) { g_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed"); } else { gtk_widget_queue_draw (GTK_WIDGET (dial)); if (dial->policy == GTK_UPDATE_DELAYED) { if (dial->timer) gtk_timeout_remove (dial->timer); dial->timer = gtk_timeout_add (SCROLL_DELAY_LENGTH, (GtkFunction) gtk_dial_timer, (gpointer) dial); } } } } static void gtk_dial_update (GtkDial *dial) { gfloat new_value; g_return_if_fail (dial != NULL); g_return_if_fail (GTK_IS_DIAL (dial)); new_value = dial->adjustment->value; if (new_value < dial->adjustment->lower) new_value = dial->adjustment->lower; if (new_value > dial->adjustment->upper) new_value = dial->adjustment->upper; if (new_value != dial->adjustment->value) { dial->adjustment->value = new_value; g_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed"); } dial->angle = 7.*M_PI/6. - (new_value - dial->adjustment->lower) * 4.*M_PI/3. / (dial->adjustment->upper - dial->adjustment->lower); gtk_widget_queue_draw (GTK_WIDGET (dial)); } static void gtk_dial_adjustment_changed (GtkAdjustment *adjustment, gpointer data) { GtkDial *dial; g_return_if_fail (adjustment != NULL); g_return_if_fail (data != NULL); dial = GTK_DIAL (data); if ((dial->old_value != adjustment->value) || (dial->old_lower != adjustment->lower) || (dial->old_upper != adjustment->upper)) { gtk_dial_update (dial); dial->old_value = adjustment->value; dial->old_lower = adjustment->lower; dial->old_upper = adjustment->upper; } } static void gtk_dial_adjustment_value_changed (GtkAdjustment *adjustment, gpointer data) { GtkDial *dial; g_return_if_fail (adjustment != NULL); g_return_if_fail (data != NULL); dial = GTK_DIAL (data); if (dial->old_value != adjustment->value) { gtk_dial_update (dial); dial->old_value = adjustment->value; } } |
#include <stdio.h> #include <stdlib.h> #include <gtk/gtk.h> #include "gtkdial.h" void value_changed( GtkAdjustment *adjustment, GtkWidget *label ) { char buffer[16]; sprintf(buffer,"%4.2f",adjustment->value); gtk_label_set_text (GTK_LABEL (label), buffer); } int main( int argc, char *argv[]) { GtkWidget *window; GtkAdjustment *adjustment; GtkWidget *dial; GtkWidget *frame; GtkWidget *vbox; GtkWidget *label; gtk_init (&argc, &argv); window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_window_set_title (GTK_WINDOW (window), "Dial"); g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (exit), NULL); gtk_container_set_border_width (GTK_CONTAINER (window), 10); vbox = gtk_vbox_new (FALSE, 5); gtk_container_add (GTK_CONTAINER (window), vbox); gtk_widget_show (vbox); frame = gtk_frame_new (NULL); gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); gtk_container_add (GTK_CONTAINER (vbox), frame); adjustment = GTK_ADJUSTMENT (gtk_adjustment_new (0, 0, 100, 0.01, 0.1, 0)); dial = gtk_dial_new (adjustment); gtk_dial_set_update_policy (GTK_DIAL (dial), GTK_UPDATE_DELAYED); /* gtk_widget_set_size_request (dial, 100, 100); */ gtk_container_add (GTK_CONTAINER (frame), dial); gtk_widget_show (dial); label = gtk_label_new ("0.00"); gtk_box_pack_end (GTK_BOX (vbox), label, 0, 0, 0); gtk_widget_show (label); g_signal_connect (G_OBJECT (adjustment), "value_changed", G_CALLBACK (value_changed), (gpointer) label); gtk_widget_show (window); gtk_main (); return 0; } |
/* GTK - The GIMP Toolkit * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include <stdlib.h> #include <gtk/gtk.h> /* Backing pixmap for drawing area */ static GdkPixmap *pixmap = NULL; /* Create a new backing pixmap of the appropriate size */ static gint configure_event( GtkWidget *widget, GdkEventConfigure *event ) { if (pixmap) g_object_unref (pixmap); pixmap = gdk_pixmap_new (widget->window, widget->allocation.width, widget->allocation.height, -1); gdk_draw_rectangle (pixmap, widget->style->white_gc, TRUE, 0, 0, widget->allocation.width, widget->allocation.height); return TRUE; } /* Redraw the screen from the backing pixmap */ static gint expose_event( GtkWidget *widget, GdkEventExpose *event ) { gdk_draw_drawable (widget->window, widget->style->fg_gc[GTK_WIDGET_STATE (widget)], pixmap, event->area.x, event->area.y, event->area.x, event->area.y, event->area.width, event->area.height); return FALSE; } /* Draw a rectangle on the screen */ static void draw_brush( GtkWidget *widget, gdouble x, gdouble y) { GdkRectangle update_rect; update_rect.x = x - 5; update_rect.y = y - 5; update_rect.width = 10; update_rect.height = 10; gdk_draw_rectangle (pixmap, widget->style->black_gc, TRUE, update_rect.x, update_rect.y, update_rect.width, update_rect.height); update_rect.x, update_rect.y, update_rect.width, update_rect.height); } static gint button_press_event( GtkWidget *widget, GdkEventButton *event ) { if (event->button == 1 && pixmap != NULL) draw_brush (widget, event->x, event->y); return TRUE; } static gint motion_notify_event( GtkWidget *widget, GdkEventMotion *event ) { int x, y; GdkModifierType state; if (event->is_hint) gdk_window_get_pointer (event->window, &x, &y, &state); else { x = event->x; y = event->y; state = event->state; } if (state & GDK_BUTTON1_MASK && pixmap != NULL) draw_brush (widget, x, y); return TRUE; } void quit () { exit (0); } char *argv[] ) { GtkWidget *window; GtkWidget *drawing_area; GtkWidget *vbox; GtkWidget *button; gtk_init (&argc, &argv); window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_widget_set_name (window, "Test Input"); vbox = gtk_vbox_new (FALSE, 0); gtk_container_add (GTK_CONTAINER (window), vbox); gtk_widget_show (vbox); g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (quit), NULL); /* Create the drawing area */ drawing_area = gtk_drawing_area_new (); gtk_widget_set_size_request (GTK_WIDGET (drawing_area), 200, 200); gtk_box_pack_start (GTK_BOX (vbox), drawing_area, TRUE, TRUE, 0); gtk_widget_show (drawing_area); /* Signals used to handle backing pixmap */ g_signal_connect (G_OBJECT (drawing_area), "expose_event", G_CALLBACK (expose_event), NULL); g_signal_connect (G_OBJECT (drawing_area),"configure_event", G_CALLBACK (configure_event), NULL); /* Event signals */ g_signal_connect (G_OBJECT (drawing_area), "motion_notify_event", G_CALLBACK (motion_notify_event), NULL); g_signal_connect (G_OBJECT (drawing_area), "button_press_event", G_CALLBACK (button_press_event), NULL); gtk_widget_set_events (drawing_area, GDK_EXPOSURE_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK); /* .. And a quit button */ button = gtk_button_new_with_label ("Quit"); gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0); g_signal_connect_swapped (G_OBJECT (button), "clicked", G_CALLBACK (gtk_widget_destroy), G_OBJECT (window)); gtk_widget_show (button); gtk_widget_show (window); gtk_main (); return 0; } |
/* GTK - The GIMP Toolkit * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include <gtk/gtk.h> /* Backing pixmap for drawing area */ static GdkPixmap *pixmap = NULL; /* Create a new backing pixmap of the appropriate size */ static gint configure_event (GtkWidget *widget, GdkEventConfigure *event) { if (pixmap) g_object_unref (pixmap); pixmap = gdk_pixmap_new (widget->window, widget->allocation.width, widget->allocation.height, -1); gdk_draw_rectangle (pixmap, widget->style->white_gc, TRUE, 0, 0, widget->allocation.width, widget->allocation.height); return TRUE; } /* Redraw the screen from the backing pixmap */ static gint expose_event (GtkWidget *widget, GdkEventExpose *event) { gdk_draw_drawable (widget->window, widget->style->fg_gc[GTK_WIDGET_STATE (widget)], pixmap, event->area.x, event->area.y, event->area.x, event->area.y, event->area.width, event->area.height); return FALSE; } /* Draw a rectangle on the screen, size depending on pressure, and color on the type of device */ static void draw_brush (GtkWidget *widget, GdkInputSource source, gdouble x, gdouble y, gdouble pressure) { GdkGC *gc; GdkRectangle update_rect; switch (source) { case GDK_SOURCE_MOUSE: gc = widget->style->dark_gc[GTK_WIDGET_STATE (widget)]; break; case GDK_SOURCE_PEN: gc = widget->style->black_gc; break; case GDK_SOURCE_ERASER: gc = widget->style->white_gc; break; default: gc = widget->style->light_gc[GTK_WIDGET_STATE (widget)]; } update_rect.x = x - 10 * pressure; update_rect.y = y - 10 * pressure; update_rect.width = 20 * pressure; update_rect.height = 20 * pressure; gdk_draw_rectangle (pixmap, gc, TRUE, update_rect.x, update_rect.y, update_rect.width, update_rect.height); update_rect.x, update_rect.y, update_rect.width, update_rect.height); } static void print_button_press (GdkDevice *device) { g_print ("Button press on device '%s'\n", device->name); } static gint button_press_event (GtkWidget *widget, GdkEventButton *event) { print_button_press (event->device); if (event->button == 1 && pixmap != NULL) { gdouble pressure; gdk_event_get_axis ((GdkEvent *)event, GDK_AXIS_PRESSURE, &pressure); draw_brush (widget, event->device->source, event->x, event->y, pressure); } return TRUE; } static gint motion_notify_event (GtkWidget *widget, GdkEventMotion *event) { gdouble x, y; gdouble pressure; GdkModifierType state; { gdk_device_get_state (event->device, event->window, NULL, &state); gdk_event_get_axis ((GdkEvent *)event, GDK_AXIS_X, &x); gdk_event_get_axis ((GdkEvent *)event, GDK_AXIS_Y, &y); gdk_event_get_axis ((GdkEvent *)event, GDK_AXIS_PRESSURE, &pressure); } else { x = event->x; y = event->y; gdk_event_get_axis ((GdkEvent *)event, GDK_AXIS_PRESSURE, &pressure); state = event->state; } if (state & GDK_BUTTON1_MASK && pixmap != NULL) draw_brush (widget, event->device->source, x, y, pressure); return TRUE; } void input_dialog_destroy (GtkWidget *w, gpointer data) { *((GtkWidget **)data) = NULL; } void create_input_dialog () { static GtkWidget *inputd = NULL; if (!inputd) { inputd = gtk_input_dialog_new(); g_signal_connect (G_OBJECT (inputd), "destroy", G_CALLBACK (input_dialog_destroy), (gpointer) &inputd); g_signal_connect_swapped (G_OBJECT (GTK_INPUT_DIALOG (inputd)->close_button), "clicked", G_CALLBACK (gtk_widget_hide), G_OBJECT (inputd)); gtk_widget_hide (GTK_INPUT_DIALOG (inputd)->save_button); gtk_widget_show (inputd); } else { if (!GTK_WIDGET_MAPPED (inputd)) gtk_widget_show (inputd); else gdk_window_raise (inputd->window); } } void quit () { exit (0); } int main (int argc, char *argv[]) { GtkWidget *window; GtkWidget *drawing_area; GtkWidget *vbox; GtkWidget *button; gtk_init (&argc, &argv); window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_widget_set_name (window, "Test Input"); vbox = gtk_vbox_new (FALSE, 0); gtk_container_add (GTK_CONTAINER (window), vbox); gtk_widget_show (vbox); g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (quit), NULL); /* Create the drawing area */ drawing_area = gtk_drawing_area_new (); gtk_widget_set_size_request (GTK_WIDGET (drawing_area), 200, 200); gtk_box_pack_start (GTK_BOX (vbox), drawing_area, TRUE, TRUE, 0); gtk_widget_show (drawing_area); /* Signals used to handle backing pixmap */ g_signal_connect (G_OBJECT (drawing_area), "expose_event", G_CALLBACK (expose_event), NULL); g_signal_connect (G_OBJECT(drawing_area),"configure_event", G_CALLBACK (configure_event), NULL); /* Event signals */ g_signal_connect (G_OBJECT (drawing_area), "motion_notify_event", G_CALLBACK (motion_notify_event), NULL); g_signal_connect (G_OBJECT (drawing_area), "button_press_event", G_CALLBACK (button_press_event), NULL); gtk_widget_set_events (drawing_area, GDK_EXPOSURE_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK); /* The following call enables tracking and processing of extension events for the drawing area */ gtk_widget_set_extension_events (drawing_area, GDK_EXTENSION_EVENTS_CURSOR); /* .. And some buttons */ button = gtk_button_new_with_label ("Input Dialog"); gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0); g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (create_input_dialog), NULL); gtk_widget_show (button); button = gtk_button_new_with_label ("Quit"); gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0); g_signal_connect_swapped (G_OBJECT (button), "clicked", G_CALLBACK (gtk_widget_destroy), G_OBJECT (window)); gtk_widget_show (button); gtk_widget_show (window); gtk_main (); return 0; } |
<<< Previous | Home | |
GtkDial | Up |
GTK+ 2.0 Tutorial |
||
---|---|---|
Getting Started |
Теперь рассмотрим теорию на примере программы helloworld.
Начнем с функции обратного вызова которая вызывается при нажатии на кнопку ("clicked"). Мы игнорируем и виджеты и данные в этом примере, но не трудно понять что они делают. В следующем примере мы будем использовать данные аргументов для определения того, какая кнопка была нажата.
void hello( GtkWidget *widget, gpointer data ) { g_print ("Hello World\n"); } |
Следующий обратный вызов (callback) является специальным. "delete_event" происходит когда оконный менеджер сообщает об этом событии программе. Мы можем выбирать что сделать с этим событием. Проигнорировать его, создавая своего рода ответ, или просто выйти из программы.
Значение которое вы возвращаете в данном обратном вызове (callback) сообщает GTK какое действие выполнить. Возвращая TRUE, мы сообщаем что не хотим создать сигнал "закрыть" ("destroy"), продолжая выполнение нашей программы. Возвращая FALSE, мы создаем сигнал "закрыть" ("destroy"), который будет выполнен обработчиком сигналов.
gint delete_event( GtkWidget *widget, GdkEvent *event, gpointer data ) { g_print ("delete event occurred\n"); return TRUE; } |
Вот другая функция обратного вызова которая сообщает программе о выходе calling gtk_main_quit(). Когда контроль переходит к ней, она сообщает GTK о том, что нужно прекратить выполнение gtk_main.
void destroy( GtkWidget *widget, gpointer data ) { gtk_main_quit (); } |
Как вы знаете, все программы имеют функцию main(), GTK программы тоже не являются исключением.
int main( int argc, char *argv[] ) { } |
Следующие строки объявляют указатели на структуры имеющих тип GtkWidget. Они используются для создания окна и кнопки.
GtkWidget *window; GtkWidget *button; |
И снова наш gtk_init(), как и прежде инициализирует набор инструментов, и разбирает найденные аргументы командной строки. Любой аргумент не входящий в список допустимых для gtk_init() передаётся для дальнейшего разбора вашей программой.
gtk_init (&argc, &argv); |
Создание нового окна, достаточно прямолинейно. Память распределяется для указателя GtkWidget *window таким образом указатель становится структурой. Окно создается, но оно не будет показано на дисплее пока мы не вызовем gtk_widget_show(window) функцию в нашей программе.
window = gtk_window_new (GTK_WINDOW_TOPLEVEL); |
Вот два примера вызова обработчика сигналов для объекта, в данном случае окна. Здесь перехватываются сигнал "delete_event" и "destroy". Первый создается когда мы ипользуем менеджер окон для закрытия окна (window), или когда мы используем в окне объект который вызывает gtk_widget_destroy(). Второй создается когда, в обработчик "delete_event", возвращается FALSE. Макросы G_OBJECT и G_CALLBACK проводят преобразование типов, а также делают код более удобочитаемым.
g_signal_connect (G_OBJECT (window), "delete_event", G_CALLBACK (delete_event), NULL); g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (destroy), NULL); |
Следующая функция используется для установки атрибутов объекта контейнер. Это окно имеет внутренний контур в 10 pixels в который не входит не один виджет. Есть и другие похожие функции которые будут рассмотрены в секции Setting Widget Attributes
И снова макрос GTK_CONTAINER для преобразования типа.
gtk_container_set_border_width (GTK_CONTAINER (window), 10); |
Этот вызов создаёт новую кнопку. Выделяет память для новой структуры GtkWidget, инициализирует и создаёт указатель на кнопку. Она будет иметь ярлык "Hello World".
button = gtk_button_new_with_label ("Hello World"); |
Теперь мы попробуем заставить кнопку выполнить что-нибудь полезное. Когда обработчик сигналов получает сигнал "clicked", то вызывается функция hello(). Данные игнорируются, таким образом обратным вызовом функции hello() будет NULL. Очевидно, что сигнал "clicked" возникает при нажатии кнопки мыши.
g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (hello), NULL); |
Можно также использовать эту кнопку для выхода из программы. Это иллюстрирует что сигнал "destroy" может быть вызван, как менеджером окон, так и нашей программой.
Вы можете иметь сколько нужно функций обратного вызова (callback functions), все они будут выполнены после вызова. Поскольку функция gtk_widget_destroy() принимает только указатель GtkWidget *widget как аргумент, мы используем здесь функцию g_signal_connect_swapped() вместо straight g_signal_connect().
g_signal_connect_swapped (G_OBJECT (button), "clicked", G_CALLBACK (gtk_widget_destroy), G_OBJECT (window)); |
Упаковочные вызовы обсуждаются позже в Packing Widgets. Здесь просто сообщается GTK, что кнопка должна размещаться внутри окна в момент отображения. Заметьте, что контейнер GTK может содержать только один виджет. Есть другие виджеты, которые обсуждаются позже, разработанные для многократного размещения виджетов.
gtk_container_add (GTK_CONTAINER (window), button); |
Теперь мы настроили всё необходимое. Нужно чтобы GTK мог показать все окно, кнопки с прикрепленными обработчиками сигналов и другие виджеты. Для того, чтобы всё окно отображалось сразу со всеми виджетами, его нужно вызывать для отображения последним, иначе при сложных интерфейсах может возникнуть эффект прорисовки виджетов на фоне пустого окна.
gtk_widget_show (button); gtk_widget_show (window); |
И конечно мы запускаем функцию gtk_main() которая ожидает событий X server и при возникновении таковых запускает соответствующие виджеты.
gtk_main (); |
И заключительный вызов после gtk_quit().
return 0; |
Теперь когда мы нажимаем на кнопку мыши, виджет GTK button создаёт сигнал "clicked". Эту информацию можно использовать для настройки обработчика сигнала таким образом, чтобы он запускал функции соответственно вашего выбора. В нашем примере при создании сигнала "clicked", запускается функция hello() с пустым аргументом NULL, а затем вызывает следующего обработчика сигналов, который запускает функцию gtk_widget_destroy() с аргументами для закрытия основного окна программы. Она создаёт сигнал "destroy", который вызывает функцию обратного вызова destroy() для простого выхода из GTK программы.
Другой вариант, использовать менеджер окон для закрытия программы при помощи события "delete_event". Если в обработчик события "delete_event", возвратить TRUE, то ничего не произойдет и окно останется открытым. Возвращенное FALSE заставит GTK создать сигнал "destroy" и выйти из GTK программы.
Events |
Moving On |
GTK+ 2.0 Tutorial |
||
---|---|---|
Углубленное изучение (Moving On) |
Вот вариант объявления функции g_signal_connect().
gulong g_signal_connect( gpointer object, const gchar *name, GCallback func, gpointer func_data ); |
Обратите внимание, возвращаемое значение имеет тип gulong? Это ярлык идентификации вашей функции обратного вызова. Как упоминалось выше, вы можете вызывать любое количество сигналов на объект и все они будут созданы в порядке очереди прикрепления.
Данный ярлык, позволяет вам удалять сигнал при использовании:
void g_signal_handler_disconnect( gpointer object, gulong id ); |
При прохождении виджета вы можете отключить обработчик, вернув в функцию (signal_connect functions) пустое значение.
Вы также можете временно отключать обработчики сигналов из родственных функций g_signal_handler_block() и g_signal_handler_unblock().
void g_signal_handler_block( gpointer object, gulong id ); void g_signal_handlers_block_by_func( gpointer object, GCallback func, gpointer data ); void g_signal_handler_unblock( gpointer object, gulong id ); void g_signal_handlers_unblock_by_func( gpointer object, GCallback func, gpointer data ); |
Moving On |
An Upgraded Hello World |
GTK+ 2.0 Tutorial |
||
---|---|---|
Moving On |
Давайте рассмотрим helloworld с улучшенными примерами обратных вызовов. Это приблизит нас к следующей теме, упаковочные виджеты (packing widgets).
#include <gtk/gtk.h> /* Новый обратный вызов. Данные переданные этой функции выводятся на stdout */ void callback( GtkWidget *widget, gpointer data ) { g_print ("Hello again - %s was pressed\n", (gchar *) data); } /* другой обратный вызов */ gint delete_event( GtkWidget *widget, GdkEvent *event, gpointer data ) { gtk_main_quit (); return FALSE; } int main( int argc, char *argv[] ) { /* Тип указателей на виджеты - GtkWidget */ GtkWidget *window; GtkWidget *button; GtkWidget *box1; /* Этот вызов присутствует во всех программах GTK. Разбираются аргументы командной строки и возвращаются программе */ gtk_init (&argc, &argv); /* Создаём основное окно программы */ window = gtk_window_new (GTK_WINDOW_TOPLEVEL); /* Новый вызов который устанавливает заголовок * окна "Hello Buttons!" */ gtk_window_set_title (GTK_WINDOW (window), "Hello Buttons!"); /* Определяем обработчика сигналов delete_event * для выхода из GTK. */ g_signal_connect (G_OBJECT (window), "delete_event", G_CALLBACK (delete_event), NULL); /* Устанавливаем ширину окантовки окна программы */ gtk_container_set_border_width (GTK_CONTAINER (window), 10); /* Создаём контейнер для упаковки виджетов. Реально контейнер не отображается, он используется как инструмент для размещения виджетов */ box1 = gtk_hbox_new (FALSE, 0); /* Помещаем контейнер в основное окно. */ gtk_container_add (GTK_CONTAINER (window), box1); /* Создаём кнопку с надписью "Button 1". */ button = gtk_button_new_with_label ("Button 1"); /* Размещаем кнопку "button 1" в качестве аргумента для функции обратного вызова, для того, чтобы вызывать эту функцию при нажатии на кнопку.*/ g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (callback), (gpointer) "button 1"); /* Вместо gtk_container_add, мы упаковываем кнопку * в невидимый контейнер который был помещен в основное окно. */ gtk_box_pack_start (GTK_BOX(box1), button, TRUE, TRUE, 0); /* Обязательное действие для отображения сформированной кнопки */ gtk_widget_show (button); /* Повторяем теже самые действия для создания второй кнопки */ button = gtk_button_new_with_label ("Button 2"); /* Вызываем туже функцию но в качестве аргумента передаём вторую кнопку. */ g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (callback), (gpointer) "button 2"); gtk_box_pack_start(GTK_BOX (box1), button, TRUE, TRUE, 0); /* Порядок отображения виджетов неважен, но рекомендуется последним вызывать отображение основного окна. */ gtk_widget_show (button); gtk_widget_show (box1); gtk_widget_show (window); /* Входим в gtk_main и ждем событий X сервера для начала выполнения действий! */ gtk_main (); return 0; } |
Скомпилируйте эту программу используя команду приведенную в первом примере. Вы заметите, что нет способа выйти из программы кроме как использование менеджера окон, или уничтожение процесса из командной строки. Было-бы хорошо, если читатель сам вставил в программу третью кнопку для выхода. Вы также сможете поиграть с опциями gtk_box_pack_start() после прочтения следующей секции. Попробуйте по изменять размеры окна и посмотреть, что из этого получится.
More on Signal Handlers |
Packing Widgets |
GTK+ 2.0 Tutorial |
||
---|---|---|
Packing Widgets |
Поначалу из за гибкости упаковок в GTK можно запутаться. Существующие варианты не всегда являются очевидными. Однако в общей сложности существует пять различных стилей упаковки, в которых нам нужно разобраться.
Каждая линия содержит горизонтальный контейнер (hbox) который содержит кнопки. Вызов gtk_box_pack упаковывает кнопки в контейнер hbox. Каждая кнопка упакованная в hbox имеет одинаковые аргументы для функции gtk_box_pack_start().
Вот объявление gtk_box_pack_start() функции.
void gtk_box_pack_start( GtkBox *box, GtkWidget *child, gboolean expand, gboolean fill, guint padding ); |
Первый аргумент - это контейнер в который упаковывается объект обозначенный вторым аргументом. Все кнопки будут объектами, которые мы упаковываем в контейнеры.
Расширяющие аргументы в gtk_box_pack_start() и gtk_box_pack_end() контролируют размер пространства которое занимает контейнер или виджет в контейнере, если (TRUE) то заполняется всё доступное пространство; или (FALSE), тогда контейнер сжимается до уровня необходимого для вмещения виджетов. Установка расширения в FALSE оправдана при правом и левом выравнивании виджетов. Иначе они растянутся на всю ширину контейнера, а такой же эффект можно получить при использовании gtk_box_pack_start() или gtk_box_pack_end().
Аргумент заполнения в функции gtk_box_pack контролирует, выделено ли дополнительное место для расширения объектов (TRUE), или как дополнительное пространство для объекта (FALSE). Это имеет смысл, только если расширяющий аргумент равен TRUE.
Новый контейнер создаётся примерно так:
GtkWidget *gtk_hbox_new ( gboolean homogeneous, gint spacing ); |
Аргумент homogeneous в gtk_hbox_new() (и некоторые для gtk_vbox_new()) контролирует равноценность размеров объектов в контейнерах (т.е., одинаковую ширину в hbox, или одинаковую высоту в vbox). Если он установлен, то расширяющий аргумент всегда включен в шаблон функции gtk_box_pack().
Свободное пространство добавляется перед объектами и вокруг них. Следующий пример объясняет это:
Далее приведён код создающий вышеприведенное изображение. Надеюсь у вас не возникнет проблем после того, как вы скомпилируете код и немного "поиграете" с параметрами, для лучшего понимания этой темы.
Packing Widgets |
Packing Demonstration Program |
GTK+ 2.0 Tutorial |
||
---|---|---|
Packing Widgets |
#include <stdio.h> #include <stdlib.h> #include "gtk/gtk.h" gint delete_event( GtkWidget *widget, GdkEvent *event, gpointer data ) { gtk_main_quit (); return FALSE; } /* Создаём контейнер hbox заполненный кнопками с ярлыками. * Передаём функции интересующие аргументы. * Отображается только содержимое контейнера. */ GtkWidget *make_box( gboolean homogeneous, gint spacing, gboolean expand, gboolean fill, guint padding ) { GtkWidget *box; GtkWidget *button; char padstr[80]; /* Создаём новый контейнер hbox соответствующий homogeneous * и установкам интервала */ box = gtk_hbox_new (homogeneous, spacing); /* Создаём ряд кнопок с соответствующими параметрами */ button = gtk_button_new_with_label ("gtk_box_pack"); gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding); gtk_widget_show (button); button = gtk_button_new_with_label ("(box,"); gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding); gtk_widget_show (button); button = gtk_button_new_with_label ("button,"); gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding); gtk_widget_show (button); /* Создаём кнопку с надписью в зависимости от расширения */ if (expand == TRUE) button = gtk_button_new_with_label ("TRUE,"); else button = gtk_button_new_with_label ("FALSE,"); gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding); gtk_widget_show (button); /* Тоже, что и выше, но в другой форме */ button = gtk_button_new_with_label (fill ? "TRUE," : "FALSE,"); gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding); gtk_widget_show (button); sprintf (padstr, "%d);", padding); button = gtk_button_new_with_label (padstr); gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding); gtk_widget_show (button); return box; } int main( int argc, char *argv[]) { GtkWidget *window; GtkWidget *button; GtkWidget *box1; GtkWidget *box2; GtkWidget *separator; GtkWidget *label; GtkWidget *quitbox; int which; /* Обязательный init, не забывайте об этом! :) */ gtk_init (&argc, &argv); if (argc != 2) { fprintf (stderr, "usage: packbox num, where num is 1, 2, or 3.\n"); /* Очищаем поток выхода GTK и выходим со статусом 1. */ exit (1); } which = atoi (argv[1]); /* Создаём основное окно */ window = gtk_window_new (GTK_WINDOW_TOPLEVEL); /* Нужно всегда подключать сигнал delete_event к основному окну * Это необходимо для интуитивного поведения */ g_signal_connect (G_OBJECT (window), "delete_event", G_CALLBACK (delete_event), NULL); gtk_container_set_border_width (GTK_CONTAINER (window), 10); /* Создаём вертикальный контейнер (vbox) для упаковки в него горизонтальных контейнеров * Это позволяет располагать горизонтальные контейнеры с кнопками один над другим. */ box1 = gtk_vbox_new (FALSE, 0); /* Выбор отображения примеров */ switch (which) { case 1: /* создаём новый ярлык. */ label = gtk_label_new ("gtk_hbox_new (FALSE, 0);"); /* Выравниваем ярлык по левому краю. Эту функцию мы обсудим в секции Widget Attributes. */ gtk_misc_set_alignment (GTK_MISC (label), 0, 0); /* Упаковываем ярлык в вертикальный контейнер (vbox box1). * Помните, что виджеты упакованные в vbox располагаются один над другим. */ gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0); /* Показываем ярлык */ gtk_widget_show (label); /* Вызов функции контейнера - homogeneous = FALSE, spacing = 0, * expand = FALSE, fill = FALSE, padding = 0 */ box2 = make_box (FALSE, 0, FALSE, FALSE, 0); gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); gtk_widget_show (box2); /* Вызов функции контейнера - homogeneous = FALSE, spacing = 0, * expand = TRUE, fill = FALSE, padding = 0 */ box2 = make_box (FALSE, 0, TRUE, FALSE, 0); gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); gtk_widget_show (box2); /* Аргументы: homogeneous, spacing, expand, fill, padding */ box2 = make_box (FALSE, 0, TRUE, TRUE, 0); gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); gtk_widget_show (box2); /* Создаём сепаратор, они достаточно просты, мы обсудим их позже. */ separator = gtk_hseparator_new (); /* Помещаем сепаратор в vbox.*/ gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5); gtk_widget_show (separator); /* Создаём другой ярлык и отображаем его. */ label = gtk_label_new ("gtk_hbox_new (TRUE, 0);"); gtk_misc_set_alignment (GTK_MISC (label), 0, 0); gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0); gtk_widget_show (label); /* Аргументы: homogeneous, spacing, expand, fill, padding */ box2 = make_box (TRUE, 0, TRUE, FALSE, 0); gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); gtk_widget_show (box2); /* Аргументы: homogeneous, spacing, expand, fill, padding */ box2 = make_box (TRUE, 0, TRUE, TRUE, 0); gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); gtk_widget_show (box2); /* Ещё один сепаратор. */ separator = gtk_hseparator_new (); /* Последние три аргумента gtk_box_pack_start: * expand, fill, padding. */ gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5); gtk_widget_show (separator); break; case 2: /* Создаём новый ярлык, помните box1 это vbox созданный * в начале main() */ label = gtk_label_new ("gtk_hbox_new (FALSE, 10);"); gtk_misc_set_alignment (GTK_MISC (label), 0, 0); gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0); gtk_widget_show (label); /* Аргументы: homogeneous, spacing, expand, fill, padding */ box2 = make_box (FALSE, 10, TRUE, FALSE, 0); gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); gtk_widget_show (box2); separator = gtk_hseparator_new (); /* Последние три аргумента gtk_box_pack_start: * expand, fill, padding. */ gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5); gtk_widget_show (separator); label = gtk_label_new ("gtk_hbox_new (FALSE, 0);"); gtk_misc_set_alignment (GTK_MISC (label), 0, 0); gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0); gtk_widget_show (label); case 3: /* Демонстрация использования gtk_box_pack_end() для правого выравнивания виджетов. * Сначала создаём контейнер как и прежде*/ box2 = make_box (FALSE, 0, FALSE, FALSE, 0); /* Создаём ярлык, который будет размещён в конце */ label = gtk_label_new ("end"); /* Упаковываем используя gtk_box_pack_end(), размещая таким образом * hbox созданный вызовом make_box() по правой стороне. */ gtk_box_pack_end (GTK_BOX (box2), label, FALSE, FALSE, 0); /* Показываем ярлык */ gtk_widget_show (label); /* Упаковываем box2 в box1 (vbox помните ? :) */ gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); gtk_widget_show (box2); /* Сепаратор для основания. */ separator = gtk_hseparator_new (); /* Явно определяем ширину сепаратора в 400 pixels и высоту в 5 pixels. * hbox который мы создали также будет 400 pixels в ширину, * а ярлык "end" будет отделен от других ярлыков в hbox. * Иначе все виджеты в hbox будут расположены вплотную друг к другу */ gtk_widget_set_size_request (separator, 400, 5); /* упаковываем сепаратор в hbox (box1) созданный в начале main() */ gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5); gtk_widget_show (separator); } /* Создаём новый hbox. Помните, их может быть столько - сколько нужно! */ quitbox = gtk_hbox_new (FALSE, 0); /* Кнопка выхода. */ button = gtk_button_new_with_label ("Quit"); /* Устанавливаем сигнал для закрытия программы при нажатии кнопки */ g_signal_connect_swapped (G_OBJECT (button), "clicked", G_CALLBACK (gtk_main_quit), G_OBJECT (window)); /* Упаковываем кнопку в quitbox. * Последние три аргумента gtk_box_pack_start are: * expand, fill, padding. */ gtk_box_pack_start (GTK_BOX (quitbox), button, TRUE, FALSE, 0); /* Упаковываем quitbox в vbox (box1) */ gtk_box_pack_start (GTK_BOX (box1), quitbox, FALSE, FALSE, 0); /* Упаковываем vbox (box1), который теперь содержит все наши виджеты, в главное окно. */ gtk_container_add (GTK_CONTAINER (window), box1); /* Показываем всё остальное */ gtk_widget_show (button); gtk_widget_show (quitbox); gtk_widget_show (box1); /* Последним выводим для отображения главное окно, таким образом всё отображается одновременно */ gtk_widget_show (window); /* И конечно главная функция */ gtk_main (); /* Сюда контроль попадает после вызова gtk_main_quit(),но не после использования exit(). */ return 0; } |
Details of Boxes |
Packing Using Tables |
GTK+ 2.0 Tutorial |
||
---|---|---|
Packing Widgets |
Следующий рассматриваемый способ упаковки - Таблицы. Они могут быть чрезвычайно полезными в определенных ситуациях.
Используя таблицы, мы создаем сетку, в которую можем поместить виджеты. Виджеты могут занимать так много места, сколько мы определим.
Первое, что мы рассмотрим будет функция gtk_table_new():
GtkWidget *gtk_table_new( guint rows, guint columns, gboolean homogeneous ); |
Первый аргумент задаёт кол-во строк в таблице, а второй соответственно кол-во столбцов.
Аргумент homogeneous выполняет тоже, что и в контейнерах - задаёт размер. Если homogeneous равно TRUE, ячейки таблицы расширяются до значения самого большого виджета. Если homogeneous равно FALSE, размер ячейки продиктован самой большой по высоте в этом ряду и самой широкой в этом столбце.
Столбцы и строки располагаются от 0 до n, где n определено в вызове gtk_table_new. Если вы определили rows = 2 и columns = 2, то таблица будет выглядеть примерно так:
0 1 2 0+----------+----------+ | | | 1+----------+----------+ | | | 2+----------+----------+ |
Заметьте, система координат начинается в верхнем левом углу.
Для размещения виджета в ячейке используйте следующую функцию:
void gtk_table_attach( GtkTable *table,
GtkWidget *child,
guint left_attach,
guint right_attach,
guint top_attach,
guint bottom_attach,
GtkAttachOptions xoptions,
GtkAttachOptions yoptions,
guint xpadding,
guint ypadding );
|
Первый аргумент таблица ("table") которую вы создали, а второй виджет ("child") который вы хотите разместить в таблице.
Остальные аргументы определяют место расположения виджета в ячейке и то, сколько ячеек использовать. Если вы хотите разместить кнопку в правом нижнем углу в выше указанной таблице 2х2 и чтобы она занимала все пространство, то left_attach would be = 1, right_attach = 2, top_attach = 1, bottom_attach = 2.
Если понадобится расположить кнопку в верхней строчки на обе ячейки, то нужно использовать следующие аргументы left_attach = 0, right_attach = 2, top_attach = 0, bottom_attach = 1.
Аргументы xoptions и yoptions используются для специфических опций упаковки.
Аргументы xpadding и ypadding используются для выделения свободного пространства вокруг виджетов в пикселях (pixels).
gtk_table_attach() имеет много вариантов. Например сокращенный:
void gtk_table_attach_defaults( GtkTable *table, GtkWidget *widget, guint left_attach, guint right_attach, guint top_attach, guint bottom_attach ); |
Опции X и Y в данном случае установлены по умолчанию в GTK_FILL | GTK_EXPAND, а xpadding и ypadding равны 0. Остальная часть аргументов идентична предыдущей функции.
Также есть функции gtk_table_set_row_spacing() и gtk_table_set_col_spacing(), устанавливающие интервал между рядами или столбцами.
void gtk_table_set_row_spacing( GtkTable *table, guint row, guint spacing ); |
void gtk_table_set_col_spacing ( GtkTable *table, guint column, guint spacing ); |
Обратите внимание на то, что промежуток устанавливается справа для столбцов и снизу для рядов.
Вы можете также установить последовательный интервал всех рядов и/или колонок:
void gtk_table_set_row_spacings( GtkTable *table, guint spacing ); |
void gtk_table_set_col_spacings( GtkTable *table, guint spacing ); |
Заметьте, что последний ряд и последний столбец не получают никакого интервала.
Packing Demonstration Program |
Table Packing Example |
GTK+ 2.0 Tutorial |
||
---|---|---|
Упаковочные ввиджеты (Packing Widgets) |
Создаём окно с тремя кнопками в таблице 2x2. Первые две кнопки будут помещены в верхний ряд. А третья, кнопка выхода, будет помещена в нижний ряд заняв оба столбца. Выглядеть это будет примерно так:
#include <gtk/gtk.h> /* Обратный вызов. * Данные направленные в эту функцию будут выведены на stdout */ void callback( GtkWidget *widget, gpointer data ) { g_print ("Hello again - %s was pressed\n", (char *) data); } /* Обратный вызов выхода из программы */ gint delete_event( GtkWidget *widget, GdkEvent *event, gpointer data ) { gtk_main_quit (); return FALSE; } int main( int argc, char *argv[] ) { GtkWidget *window; GtkWidget *button; GtkWidget *table; gtk_init (&argc, &argv); /* Создаём новое окно */ window = gtk_window_new (GTK_WINDOW_TOPLEVEL); /* Устанавливаем заголовок окна */ gtk_window_set_title (GTK_WINDOW (window), "Table"); /* Определяем обработчик сигналов для delete_event, чтобы выходить из GTK */ g_signal_connect (G_OBJECT (window), "delete_event", G_CALLBACK (delete_event), NULL); /* Устанавливаем окантовку окна. */ gtk_container_set_border_width (GTK_CONTAINER (window), 20); /* Создаём таблицу 2x2 */ table = gtk_table_new (2, 2, TRUE); /* Вставляем таблицу в главное окно */ gtk_container_add (GTK_CONTAINER (window), table); /* Создаём первую кнопку */ button = gtk_button_new_with_label ("button 1"); /* Когда нажимается кнопка, происходит вызов функции * с указателем на кнопку "button 1" в качестве аргумента */ g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (callback), (gpointer) "button 1"); /* Вставляем первую кнопку в верхний левый угол таблицы */ gtk_table_attach_defaults (GTK_TABLE (table), button, 0, 1, 0, 1); gtk_widget_show (button); /* Создаём вторую кнопку */ button = gtk_button_new_with_label ("button 2"); /* Когда нажимается кнопка, происходит вызов функции * с указателем на кнопку "button 2" в качестве аргумента */ g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (callback), (gpointer) "button 2"); /* Вставляем кнопку в верхний правый угол таблицы */ gtk_table_attach_defaults (GTK_TABLE (table), button, 1, 2, 0, 1); gtk_widget_show (button); /* Создаём кнопку выхода "Quit" */ button = gtk_button_new_with_label ("Quit"); /* При нажатии на кнопку происходит вызов функции delete_event и выход из программы */ g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (delete_event), NULL); /* Вставляем кнопку выхода в обе нижние ячейки */ gtk_table_attach_defaults (GTK_TABLE (table), button, 0, 2, 1, 2); gtk_widget_show (button); gtk_widget_show (table); gtk_widget_show (window); gtk_main (); return 0; } |
Packing Using Tables |
Widget Overview |
GTK+ 2.0 Tutorial | ||
---|---|---|
<<< Previous | Widget Overview | Next >>> |
Иерархическое дерево виджетов. (Вспомогательные классы и неподдерживаемые виджеты были пропущены.)
GObject |
<<< Previous | Home | Next >>> |
Widget Overview | Up | Widgets Without Windows |
GTK+ 2.0 Tutorial |
||
---|---|---|
Widget Overview |
Виджеты не асоциированные с окнами. Когда вы хотите перехватить событие, вы должны использовать виджет EventBox. Подробности в разделе EventBox widget.
GtkAlignment GtkArrow GtkBin GtkBox GtkButton GtkCheckButton GtkFixed GtkImage GtkLabel GtkMenuItem GtkNotebook GtkPaned GtkRadioButton GtkRange GtkScrolledWindow GtkSeparator GtkTable GtkToolbar GtkAspectFrame GtkFrame GtkVBox GtkHBox GtkVSeparator GtkHSeparator |
При дальнейшем изучении GTK, мы на простых примерах создания функций, рассмотрим каждый в отдельности. Другой хороший пример - testgtk, программа поставляемая с GTK. Вы найдете её в tests/testgtk.c.
Widget Hierarchy |
The Button Widget |
GTK+ 2.0 Tutorial |
||
---|---|---|
The Button Widget |
Переключатели - это кнопки всегда находящиеся в одном из двух положений, включено или выключено. При каждом нажатии на кнопку вы переключаете её положение.
Переключатели являются основой для контроль-кнопок (check buttons) и для радио-кнопок (radio buttons), поэтому многие вызовы используемые для переключателей действительны и для них. Об этом чуть позже.
GtkWidget *gtk_toggle_button_new( void ); GtkWidget *gtk_toggle_button_new_with_label( const gchar *label ); GtkWidget *gtk_toggle_button_new_with_mnemonic( const gchar *label ); |
Как вы понимаете, они создаются тождественно нормальным кнопкам. В первой строке создается переключатель а в следующих двух, надпись(ярлык) которая в него упаковывается.
Для восстановления статуса переключателя, радио-кнопки или контроль-кнопки используем конструкцию приведенную в примере ниже. Получая доступ к активной области структуры переключателя, осуществляем контроль его состояния, после первого использования макроса GTK_TOGGLE_BUTTON определяется указатель на переключатель. Нас интересует сигнал создаваемый переключателем (the toggle button, check button, and radio button widgets) - это "toggled" сигнал. Для получения доступа к структуре контроля состояния, необходимо настроить обработчик сигналов для сигнала "toggled". Это выглядит примерно так:
void toggle_button_callback (GtkWidget *widget, gpointer data) { if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) { /* Если выполнение останавливается здесь, кнопка переключателя нажата */ } else { /* Если выполнение останавливается здесь, кнопка переключателя не нажата */ } } |
Изменить состояние переключателя и его дочерних виджетов контроль-кнопки или радио-кнопки можно используя функцию:
void gtk_toggle_button_set_active( GtkToggleButton *toggle_button, gboolean is_active ); |
При выполнении в качестве первого аргумента создаётся кнопка, а вторым аргументом определяется состояние, TRUE или FALSE - нажата (depressed) или нет (released) соответственно. По умолчанию не нажата, тоесть FALSE.
Заметьте, при использовании функции gtk_toggle_button_set_active(), состояние изменяется и кнопка производит сигналы "clicked" и "toggled".
gboolean gtk_toggle_button_get_active (GtkToggleButton *toggle_button); |
Это возвращает текущее состояние кнопки переключателя как значение (boolean) TRUE/FALSE.
The Button Widget |
Check Buttons |
GTK+ 2.0 Tutorial |
||
---|---|---|
The Button Widget |
Контроль-кнопки наследуют большинство функций и свойств переключателей, но имеют немного другой вид. Они имеют вид небольшой области для отметки с текстом справа. Они чаще всего используются для включения или выключения опций программы.
Для создания используется функция подобная созданию переключателя:
GtkWidget *gtk_check_button_new( void ); GtkWidget *gtk_check_button_new_with_label ( const gchar *label ); GtkWidget *gtk_check_button_new_with_mnemonic ( const gchar *label ); |
Функция gtk_check_button_new_with_label() создаёт контроль-кнопку с расположенным рядом текстовым ярлыком.
Проверка состояния идентична проверки состояния переключателя.
Toggle Buttons |
Radio Buttons |
GTK+ 2.0 Tutorial |
||
---|---|---|
Виджеты кнопки (The Button Widget) |
Кнопки выбора подобны контроль-кнопкам за исключением того, что они создаются группами и в отдельно взятый момент времени может быть выбрана только одна из них. Это удобно там где нужно выбирать из нескольких возможных взаимоисключающих вариантов.
Создание кнопок выбора выполняется одним из этих вызовов:
GtkWidget *gtk_radio_button_new( GSList *group ); GtkWidget *gtk_radio_button_new_from_widget( GtkRadioButton *group ); GtkWidget *gtk_radio_button_new_with_label( GSList *group, const gchar *label ); GtkWidget* gtk_radio_button_new_with_label_from_widget( GtkRadioButton *group, const gchar *label ); GtkWidget *gtk_radio_button_new_with_mnemonic( GSList *group, const gchar *label ); GtkWidget *gtk_radio_button_new_with_mnemonic_from_widget( GtkRadioButton *group, const gchar *label ); |
Вы заметили наверное, что функции содержат дополнительные аргументы. Это нужно для выполнения группой кнопок выбора их обязанности должным образом. Если первый вызов gtk_radio_button_new() или gtk_radio_button_new_with_label() должен содержать NULL в качестве первого аргумента, то используйте:
GSList *gtk_radio_button_get_group( GtkRadioButton *radio_button ); |
Важно помнить, что gtk_radio_button_get_group() вызывается каждый раз, когда нужно добавить кнопку выбора в группу, используя в качестве аргумента предыдущую кнопку выбора. Результат передаётся в следующий вызов gtk_radio_button_new() или gtk_radio_button_new_with_label(). Это позволяет связать цепь кнопок выбора. Пример ниже проесняет это.
Вы можете немного сократить синтаксис, удалив переменную для содержания списка кнопок выбора:
button2 = gtk_radio_button_new_with_label( gtk_radio_button_get_group (GTK_RADIO_BUTTON (button1)), "button2"); |
Варианты с _from_widget() позволяют создавать сокращенные функции в будущем, опуская вызов gtk_radio_button_get_group(). Эта форма используется для создания третьей кнопки в следующем примере:
button2 = gtk_radio_button_new_with_label_from_widget( GTK_RADIO_BUTTON (button1), "button2"); |
Также хорошей идеей может быть установка по умолчанию определяющая выбранную кнопку:
void gtk_toggle_button_set_active( GtkToggleButton *toggle_button, gboolean state ); |
Это описано в секции о переключателях и работает таким же образом. Как только кнопки выбора сгруппированы, только одна из них может быть выбрана. Если пользователь нажмет сначала на одну кнопку выбора, а затем на другую, то первая создаст сигнал "toggled" (сообщит о переходе в неактивное состояние), а вторая создаст "toggled" сигнал сообщая об активизации.
Пример создания группы из трёх кнопок выбора.
#include <glib.h> #include <gtk/gtk.h> gint close_application( GtkWidget *widget, GdkEvent *event, gpointer data ) { gtk_main_quit (); return FALSE; } int main( int argc, char *argv[] ) { GtkWidget *window = NULL; GtkWidget *box1; GtkWidget *box2; GtkWidget *button; GtkWidget *separator; GSList *group; window = gtk_window_new (GTK_WINDOW_TOPLEVEL); g_signal_connect (G_OBJECT (window), "delete_event", G_CALLBACK (close_application), NULL); gtk_window_set_title (GTK_WINDOW (window), "radio buttons"); gtk_container_set_border_width (GTK_CONTAINER (window), 0); box1 = gtk_vbox_new (FALSE, 0); gtk_container_add (GTK_CONTAINER (window), box1); gtk_widget_show (box1); box2 = gtk_vbox_new (FALSE, 10); gtk_container_set_border_width (GTK_CONTAINER (box2), 10); gtk_box_pack_start (GTK_BOX (box1), box2, TRUE, TRUE, 0); gtk_widget_show (box2); button = gtk_radio_button_new_with_label (NULL, "button1"); gtk_box_pack_start (GTK_BOX (box2), button, TRUE, TRUE, 0); gtk_widget_show (button); group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (button)); button = gtk_radio_button_new_with_label (group, "button2"); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE); gtk_box_pack_start (GTK_BOX (box2), button, TRUE, TRUE, 0); gtk_widget_show (button); button = gtk_radio_button_new_with_label_from_widget (GTK_RADIO_BUTTON (button), "button3"); gtk_box_pack_start (GTK_BOX (box2), button, TRUE, TRUE, 0); gtk_widget_show (button); separator = gtk_hseparator_new (); gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 0); gtk_widget_show (separator); box2 = gtk_vbox_new (FALSE, 10); gtk_container_set_border_width (GTK_CONTAINER (box2), 10); gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, TRUE, 0); gtk_widget_show (box2); button = gtk_button_new_with_label ("close"); g_signal_connect_swapped (G_OBJECT (button), "clicked", G_CALLBACK (close_application), G_OBJECT (window)); gtk_box_pack_start (GTK_BOX (box2), button, TRUE, TRUE, 0); GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT); gtk_widget_grab_default (button); gtk_widget_show (button); gtk_widget_show (window); gtk_main (); return 0; } |
Check Buttons |
Adjustments |
GTK+ 2.0 Tutorial |
||
---|---|---|
Adjustments |
Регуляторы можно условно разделить на те которые используют и требуют в своих значениях специальные единицы, и те которые используют произвольные числа. Группа регуляторов которые используют произвольные числа - scrollbars и scales, progress bar widget и spin button widget. Все эти виджеты обычно регулируются пользователем при помощи клавиатуры или мышки. Они рассматривают самый низкий (lower) и высокий (upper) диапазон значения в рамках которого пользователь может манипулировать регулировками значений (value). По умолчанию изменяется только значение регулятора.
Другая группа включает text widget, viewport widget, compound list widget и scrolled window widget. Все эти виджеты используют значения в пикселах (pixel values) для своих регулировок. Также все эти виджеты косвенно используют типовые регуляторы ("adjusted") scrollbars. Все виджеты использующие регуляторы имеют стандартные наборы параметров, которые наследуются и могут быть изменены, а также вы можете создавать собственные специфические регуляторы на базе существующих (способы создания виджетов и их параметров могут отличаться друг от друга поэтому вам необходимо изучить исходный код).
Теперь, вы вероятно думаете, что так как виджеты текста и viewports позволяют устанавливать все, кроме значения их регуляторов, в то время как scrollbars наоборот позволяет регулировать только значения диапазона, то разделив объект регулирования между scrollbar и виджетом текста, манипулирование scrollbar автоматически отрегулирует viewport виджет? Именно так всё и произойдет, как показано ниже:
/* Создаём собственный регулятор */ viewport = gtk_viewport_new (NULL, NULL); /* используем вновь созданный регулятор для scrollbar */ vscrollbar = gtk_vscrollbar_new (gtk_viewport_get_vadjustment (viewport)); |
Adjustments |
Adjustment Internals |
GTK+ 2.0 Tutorial |
||
---|---|---|
Adjustments |
Хорошо поняв вышесказанное, возникает вопрос: как можно создать собственный обработчик сигнала для перехвата сигналов регулирования, при использовании регуляторов пользователем, и как устанавливать значение диапазона регулировок в таких обработчиках?
Чтобы ответить на этот вопрос и последующие, для начала рассмотрим структуру struct _GtkAdjustment:
struct _GtkAdjustment { GtkObject parent_instance; gdouble lower; gdouble upper; gdouble value; gdouble step_increment; gdouble page_increment; gdouble page_size; }; |
(real C programmer), вы можете использовать другой способ доступа к изменению значений регулирования:
gdouble gtk_adjustment_get_value( GtkAdjustment *adjustment); |
Когда вы установите значения регулировок, вы захотите чтобы все виджеты использовали эти значения как основные, GTK позволяет сделать это с помощью функции, например:
void gtk_adjustment_set_value( GtkAdjustment *adjustment, gdouble value ); |
Как упоминалось ранее, Регуляторы это подкласс объектов как и все виджеты, поэтому они могут производить сигналы. Обновления случаются автоматически, когда вы совместно используете объект настройки между scrollbar и другим регулировочным виджетом; все регулировочные виджеты подключают обработчики сигнала с сигналом value_changed для их настройки. Вот - определение этого сигнала в struct _GtkAdjustmentClass:
void (* value_changed) (GtkAdjustment *adjustment); |
Различные виджеты использующие объекты регулирования, создают этот сигнал каждый раз, когда происходит изменение значения регулятора. Это происходит и тогда, когда пользователь изменяет положение регулятора, и когда программа явно изменяет значение используя gtk_adjustment_set_value(). Так например при использовании регулятора масштаба изображения, нужно чтобы вид изображения менялся всякий раз когда меняется значение регулятора, для этого создаётся обратный вызов:
void cb_rotate_picture (GtkAdjustment *adj, GtkWidget *picture) { set_picture_rotation (picture, gtk_adjustment_get_value (adj)); ... |
и подключается регулятор масштаба:
g_signal_connect (G_OBJECT (adj), "value_changed", G_CALLBACK (cb_rotate_picture), (gpointer) picture); |
А что если пользователь добавит информацию в виджет, тем самым расширит область регулировки? В этом случае производится изменённый сигнал:
void (* changed) (GtkAdjustment *adjustment); |
Обычно виджеты регулировки диапазона подключают к этому сигналу обработчик, который изменяет их внешний вид - например размер ползунка в полосе прокрутки уменьшается пропорционально увеличению области прокручивания.
Вам наверное никогда не понадобится к этому сигналу подключать обработчик, за исключением случаев создания новых типов виджетов диапазона. Однако, если вы непосредственно изменяете значения регулировок, вам потребуется создавать этот сигнал для перенастройки любого виджета примерно так:
g_signal_emit_by_name (G_OBJECT (adjustment), "changed"); |
Using Adjustments the Easy Way |
Range Widgets |
GTK+ 2.0 Tutorial |
||
---|---|---|
Range Widgets |
Виджеты масштабирования используются, чтобы позволить пользователю визуально выбирать и управлять значениями в пределах определенного диапазона. Вы можете использовать виджет масштабирования например при увеличении или уменьшении отображаемой области просмотра изображения, или для регулирования всевозможных параметров (цвет, яркость, контрастность и т.д.), а также для выбора промежутка времени через которое заставка (screensaver) закроет экран.
Как и для scrollbars, есть два типа виджетов масштабирования, горизонтальные и вертикальные. (большинство программистов предпочитают горизонтальные) Так как они работают одинаково, нет никакого смысла рассматривать их по отдельности. Следующие функции создают горизонтальные и вертикальные виджеты масштабирования соответственно:
GtkWidget *gtk_vscale_new( GtkAdjustment *adjustment ); GtkWidget *gtk_vscale_new_with_range( gdouble min, gdouble max, gdouble step ); GtkWidget *gtk_hscale_new( GtkAdjustment *adjustment ); GtkWidget *gtk_hscale_new_with_range( gdouble min, gdouble max, gdouble step ); |
Аргумент adjustment может быть любым регулятором созданным gtk_adjustment_new(), или NULL, в случае создания анонимного регулятора все значения устанавливаются Чтобы избежать путаницы, вы наверное захотите установить регулятор с page_size 0.0, так чтобы его верхнее положение соответствовало наибольшему значению которое может выбрать пользователь. О подходящем регулировании заботится _new_with_range(). (Если вы уже полностью запутались, прочитайте секцию снова, для того чтобы понять как создавать регуляторы и манипулировать ими.)
Виджеты масштабирования могут показывать своё текущее значение рядом со своей "колеёй" в виде чисел. По умолчанию значение отображается, но вы можете изменить это поведение с помощью функции:
void gtk_scale_set_draw_value( GtkScale *scale, gboolean draw_value ); |
Аргумент draw_value выставляется в значение TRUE или FALSE, в зависимости от желаемого результата.
Значение виджета масштабирования по умолчанию округляется до одного знака после запятой. Вы можете изменить это поведение:
void gtk_scale_set_digits( GtkScale *scale, gint digits ); |
Аргумент digits содержит кол-во знаков которые будут отображены после запятой. Вы можете установить любое число digits, но на экран будет выведено не более 13 знаков после запятой.
Наконец, значение может быть отодвинуто на любую позицию относительно колеи:
void gtk_scale_set_value_pos( GtkScale *scale, GtkPositionType pos ); |
Аргумент pos имеет тип GtkPositionType и может принимать одно из значений:
GTK_POS_LEFT GTK_POS_RIGHT GTK_POS_TOP GTK_POS_BOTTOM |
Если позиция со стороны колеи (т.е. сверху или снизу горизонтального виджета масштабирования), то значение будет передвигаться вдоль колеи за ползунком.
Все предыдущие функции определены в <gtk/gtkscale.h>. Заголовочные файлы GTK widgets автоматически включаются после включения <gtk/gtk.h>. Вы можете просмотреть заголовочные файлы всех виджетов, которые вас интересуют, чтобы больше узнать об их функциях и особенностях.
Range Widgets |
Common Range Functions |
GTK+ 2.0 Tutorial |
||
---|---|---|
Range Widgets |
Внутренне виджеты диапазона справедливо усложнены, также как и все виджеты базового класса ("base class"), но все эти сложности достаточно интересны для изучения, если вы хотите по-настоящему разобраться в этом. Кроме того, все функции и сигналы которые здесь определены, действительно реально используются при создании виджетов. Есть, однако, несколько полезных функций, которые определены в <gtk/gtkrange.h> и будут работать со всеми виджетами диапазона.
"Политика обновления" ("update policy") для виджетов диапазона определяет на сколько изменять значение в результате регулировок пользователя и создаёт "value_changed" сигнал для этого регулятора."Политика обновления", определена в <gtk/gtkenums.h> как тип enum GtkUpdateType:
"Политика обновления" может быть установлена размещением макроса GTK_RANGE(widget) в функцию:
void gtk_range_set_update_policy( GtkRange *range, GtkUpdateType policy); |
Получение и установка регуляторов для виджетов диапазона налету ("on the fly") делается так:
GtkAdjustment* gtk_range_get_adjustment( GtkRange *range ); void gtk_range_set_adjustment( GtkRange *range, GtkAdjustment *adjustment ); |
gtk_range_get_adjustment() возвращает указатель на регулятор с которым связан range.
gtk_range_set_adjustment () не делает абсолютно ничего, если вы передаете регулятор, диапазон которого уже используется, независимо от того, изменили ли вы любую из его областей или нет. Если вы передаете новый регулятор, он может уничтожить старый если таковой существовал (possibly destroying it), соединит соответствующие сигналы с новым и вызовет отдельно функцию gtk_range_adjustment_changed(), которая будет (или по крайней мере должна), повторно вычислять размер и/или положение ползунка и изменять в случае необходимости. Как упоминалось в секции о регуляторах, если вы хотите использовать тот же регулятор при непосредственном изменении значений, вы должны создать сигнал "changed":
g_signal_emit_by_name (G_OBJECT (adjustment), "changed"); |
Scale Widgets |
Key and Mouse bindings |
GTK+ 2.0 Tutorial |
||
---|---|---|
Range Widgets |
Все виджеты библиотеки GTK так или иначе реагируют на щелчки мышки. Щелчок кнопки -1 в колее регулятора заставит соответственно увеличить значение диапазона page_increment и передвинуть ползунок. Щелчок кнопки -2 в колее передвинет на это место ползунок. Щелчок кнопкой -3 в колее диапазона или любой кнопки на стрелках полосы прокрутки одновременно заставит значение ее настройки изменяться step_increment.
Полосы прокрутки не находятся в фокусе (focusable), таким образом не имеют никаких привязок клавиш. Привязки клавиш для других виджетов диапазона (которые активны только когда виджет находится в фокусе) - не дифференцируются между горизонтальными и вертикальными виджетами диапазона.
Все виджеты могут управляться клавишами "стрелки" - вправо, влево, вверх, вниз, а также клавишами Page Up и Page Down. Стрелки вверх и вниз перемещают пошаговый диапазон (step_increment), а клавиши Page Up и Page Down перемещают постранично (page_increment).
С помощью клавиш END и HOME, пользователь может перемещать ползунок сразу, либо в конец колеи, либо в начало соответственно.
Common Range Functions |
Example |
GTK+ 2.0 Tutorial |
||
---|---|---|
Range Widgets |
Этот пример - несколько измененная версия "теста" регуляторов диапазона из testgtk.c. Основное окно с тремя виджетами диапазона, все связаны с настройкой и несколькими регуляторами для того, чтобы регулировать некоторые из упомянутых выше в разделе по регуляторам параметров, таким образом вы можете видеть, как выполняется работа виджетов для пользователя.
Обратите внимание, что программа не вызывает g_signal_connect() для "delete_event", а только для "destroy" сигнала. Это выполняет желательную функцию потому, что не обработанный сигнал "delete_event" в результате приведет к сигналу "destroy" для окна.
Key and Mouse bindings |
Miscellaneous Widgets |
GTK+ 2.0 Tutorial |
||
---|---|---|
Разные виджеты (Miscellaneous Widgets) |
Виджет стрелка используется во множестве всевозможных указателей и в разных стилях. Она может быть очень полезна, когда размещена в кнопке, во всевозможных приложениях. Как и виджет ярлык стрелки не создают сигналы.
Есть только две функции для манипулирования виджетом стрелка:
GtkWidget *gtk_arrow_new( GtkArrowType arrow_type, GtkShadowType shadow_type ); void gtk_arrow_set( GtkArrow *arrow, GtkArrowType arrow_type, GtkShadowType shadow_type ); |
Первая создаёт новый виджет стрелка с определенным типом и внешним видом. Вторая позволяет этим значениям быть измененными ретроспективно. Аргумент arrow_type может иметь следующие значения:
GTK_ARROW_UP GTK_ARROW_DOWN GTK_ARROW_LEFT GTK_ARROW_RIGHT |
Очевидно что эти значения явным образом указывают направление стрелки.
Аргумент shadow_type может принимать следующие значения:
GTK_SHADOW_IN GTK_SHADOW_OUT (the default) GTK_SHADOW_ETCHED_IN GTK_SHADOW_ETCHED_OUT |
Вот короткий пример для иллюстрации их использования.
#include <gtk/gtk.h> /* Создаём виджет стрелка со специфическими параметрами * и упаковываем его в виджет кнопка */ GtkWidget *create_arrow_button( GtkArrowType arrow_type, GtkShadowType shadow_type ) { GtkWidget *button; GtkWidget *arrow; button = gtk_button_new (); arrow = gtk_arrow_new (arrow_type, shadow_type); gtk_container_add (GTK_CONTAINER (button), arrow); gtk_widget_show (button); gtk_widget_show (arrow); return button; } int main( int argc, char *argv[] ) { /* GtkWidget хранит типы для виджетов */ GtkWidget *window; GtkWidget *button; GtkWidget *box; /* инициализируем набор инструментов GTK */ gtk_init (&argc, &argv); /* Создаём новое окно */ window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_window_set_title (GTK_WINDOW (window), "Arrow Buttons"); /* Хорошая идея выполнить это для всех окон */ g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (gtk_main_quit), NULL); /* Устанавливаем окантовку для окна. */ gtk_container_set_border_width (GTK_CONTAINER (window), 10); /* Создаём контейнер для кнопок/стрелок (arrows/buttons) */ box = gtk_hbox_new (FALSE, 0); gtk_container_set_border_width (GTK_CONTAINER (box), 2); gtk_container_add (GTK_CONTAINER (window), box); /* Упаковываем и отображаем все виджеты */ gtk_widget_show (box); button = create_arrow_button (GTK_ARROW_UP, GTK_SHADOW_IN); gtk_box_pack_start (GTK_BOX (box), button, FALSE, FALSE, 3); button = create_arrow_button (GTK_ARROW_DOWN, GTK_SHADOW_OUT); gtk_box_pack_start (GTK_BOX (box), button, FALSE, FALSE, 3); button = create_arrow_button (GTK_ARROW_LEFT, GTK_SHADOW_ETCHED_IN); gtk_box_pack_start (GTK_BOX (box), button, FALSE, FALSE, 3); button = create_arrow_button (GTK_ARROW_RIGHT, GTK_SHADOW_ETCHED_OUT); gtk_box_pack_start (GTK_BOX (box), button, FALSE, FALSE, 3); gtk_widget_show (window); /* Переходим в gtk_main и ожидаем событий для дальнейших действий */ gtk_main (); return 0; } |
Miscellaneous Widgets |
The Tooltips Object |
GTK+ 2.0 Tutorial |
||
---|---|---|
Разные виджеты (Miscellaneous Widgets) |
Это небольшие строчки текста, которые появляются рядом с указателем мыши при задержке курсора на каком нибудь из виджетов в течении короткого промежутка времени. Они очень удобны в использовании. Здесь не приводится пример их использования, но вы если хотите можете посмотреть код программы testgtk.c поставляемой с дистрибутивом GTK.
Виджеты, которые не получают события (не имеют собственного окна) не будут работать с tooltips.
Первый вызов который вы будете использовать для создания нового tooltip. Вам нужно тоько один раз установить tooltips как GtkTooltips object для многократного использования этой функции при создании подсказок.
GtkTooltips *gtk_tooltips_new( void ); |
Для использования новой подсказки на виджете нужно установить следующий сигнал:
void gtk_tooltips_set_tip( GtkTooltips *tooltips, GtkWidget *widget, const gchar *tip_text, const gchar *tip_private ); |
Первый аргумент - это подсказка которую вы создали, второй - виджет на котором она будет появляться, третий - текст который будет в подсказке. Последний аргумент - строка текста, которая может быть использована как идентификатор GtkTipsQuery, для вызова контекста подробной подсказки. Вы можете установить последний аргумент в значение NULL.
GtkTooltips *tooltips; GtkWidget *button; . . . tooltips = gtk_tooltips_new (); button = gtk_button_new_with_label ("button 1"); . . . gtk_tooltips_set_tip (tooltips, button, "This is button 1", NULL); |
Есть другие вызовы которые могут использоваться с подсказками. Они перечислены здесь только с коротким описанием.
void gtk_tooltips_enable( GtkTooltips *tooltips ); |
Включает выключенную подсказку.
void gtk_tooltips_disable( GtkTooltips *tooltips ); |
Отключает включенную подсказку.
Это все функции которые связаны с всплывающими подсказками. Намного больше чем вам нужно знать :-)
Arrows |
Progress Bars |
GTK+ 2.0 Tutorial |
||
---|---|---|
Разные виджеты (Miscellaneous Widgets) |
Шкала выполнения используется для индикации статуса операции. Они довольно удобны в использовании, вы увидите это в коде приведенном ниже. Но сначала создадим новую шкалу статуса выполнения:
GtkWidget *gtk_progress_bar_new( void ); |
Теперь созданную шкалу можно использовать:
void gtk_progress_bar_set_fraction ( GtkProgressBar *pbar, gdouble fraction ); |
Первый аргумент - шкала выполнения, второй - выполнение "completed" означает, что шкала была заполнена от 0 до 100%. Это передаётся в функцию как реальное число от 0 до 1.
С версии GTK v1.2 к шкале выполнения были добавлены некоторые функциональные возможности, позволяющие по разному отображать состояние выполнения операции и сообщать пользователю значение диапазона.
Шкала прогресса выполнения может принимать разную ориентацию используя функцию:
void gtk_progress_bar_set_orientation( GtkProgressBar *pbar, GtkProgressBarOrientation orientation ); |
Аргумент orientation может принимать одно из возможных значений для отображения перемещения шкалы выполнения:
GTK_PROGRESS_LEFT_TO_RIGHT GTK_PROGRESS_RIGHT_TO_LEFT GTK_PROGRESS_BOTTOM_TO_TOP GTK_PROGRESS_TOP_TO_BOTTOM |
Шкала выполнения может как отображать значение прогресса выполнения, так и просто выступать в роли индикатора некоторой активности операции. Это может быть полезным в ситуациях, где продвижение не может быть измерено против значения диапазона. Следующая функция указывает на некоторое продвижение шкалы:
void gtk_progress_bar_pulse ( GtkProgressBar *progress ); |
Размер индикации шага активности устанавливается с помощью функции:
void gtk_progress_bar_set_pulse_step( GtkProgressBar *pbar, gdouble fraction ); |
В неактивном состоянии, конфигурационная строка текста может отображаться в колее шкалы.
void gtk_progress_bar_set_text( GtkProgressBar *progress, const gchar *text ); |
|
Обратите внимание, что gtk_progress_set_text() не поддерживает printf(), как в подобном форматировании GTK + 1.2 Progressbar. |
Вы можете выключить отображение строки, вызвав повторно calling gtk_progess_bar_set_text() с NULL в качестве второго параметра.
Текущие установки текста в шкале могут быть выполнены с помощью следующей функции. Не возвращайте пустую строку.
const gchar *gtk_progress_bar_get_text( GtkProgressBar *pbar ); |
Шкала выполнения обычно используется совместно с функциями перерыва (timeouts) или им подобными (смотрите секцию Timeouts, I/O and Idle Functions), для создания иллюзии многопоточности управления (multitasking). Используйте gtk_progress_bar_set_fraction() или gtk_progress_bar_pulse() функции всё в той же самой манере.
Вот пример использования
шкалы выполнения, обновляющейся в
перерывах.
Этот код, также демонстрирует
перезагрузку шкалы.
#include <gtk/gtk.h> typedef struct _ProgressData { GtkWidget *window; GtkWidget *pbar; int timer; gboolean activity_mode; } ProgressData; /* Обновляем значение шкалы выполнения * для отображения некоторых действий */ gint progress_timeout( gpointer data ) { ProgressData *pdata = (ProgressData *)data; gdouble new_val; if (pdata->activity_mode) gtk_progress_bar_pulse (GTK_PROGRESS_BAR (pdata->pbar)); else { /* Вычисляем значение шкалы выполнения используя * установленные значения диапазона в объекте регулирования (adjustment object) */ new_val = gtk_progress_bar_get_fraction (GTK_PROGRESS_BAR (pdata->pbar)) + 0.01; if (new_val > 1.0) new_val = 0.0; /* Устанавливаем новые значения */ gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (pdata->pbar), new_val); } /* Для продолжения вызова функции перерыва * (timeout function) необходимо вернуть значение TRUE */ return TRUE; } /* Обратный вызов переключателя для отображения текста в колее шкалы выполнения */ void toggle_show_text( GtkWidget *widget, ProgressData *pdata ) { const gchar *text; text = gtk_progress_bar_get_text (GTK_PROGRESS_BAR (pdata->pbar)); if (text && *text) gtk_progress_bar_set_text (GTK_PROGRESS_BAR (pdata->pbar), ""); else gtk_progress_bar_set_text (GTK_PROGRESS_BAR (pdata->pbar), "любой текст"); } /* Переключатель активного состояния шкалы выполнения */ void toggle_activity_mode( GtkWidget *widget, ProgressData *pdata ) { pdata->activity_mode = !pdata->activity_mode; if (pdata->activity_mode) gtk_progress_bar_pulse (GTK_PROGRESS_BAR (pdata->pbar)); else gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (pdata->pbar), 0.0); } /* Переключатель ориентации шкалы выполнения */ void toggle_orientation( GtkWidget *widget, ProgressData *pdata ) { switch (gtk_progress_bar_get_orientation (GTK_PROGRESS_BAR (pdata->pbar))) { case GTK_PROGRESS_LEFT_TO_RIGHT: gtk_progress_bar_set_orientation (GTK_PROGRESS_BAR (pdata->pbar), GTK_PROGRESS_RIGHT_TO_LEFT); break; case GTK_PROGRESS_RIGHT_TO_LEFT: gtk_progress_bar_set_orientation (GTK_PROGRESS_BAR (pdata->pbar), GTK_PROGRESS_LEFT_TO_RIGHT); break; } } /* Очистка выделенной памяти и удаление таймера */ void destroy_progress( GtkWidget *widget, ProgressData *pdata) { gtk_timeout_remove (pdata->timer); pdata->timer = 0; pdata->window = NULL; g_free (pdata); gtk_main_quit (); } int main( int argc, char *argv[]) { ProgressData *pdata; GtkWidget *align; GtkWidget *separator; GtkWidget *table; GtkWidget *button; GtkWidget *check; GtkWidget *vbox; gtk_init (&argc, &argv); /* Выделение памяти для данных размещенных в обратных вызовах */ pdata = g_malloc (sizeof (ProgressData)); pdata->window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_window_set_resizable (GTK_WINDOW (pdata->window), TRUE); g_signal_connect (G_OBJECT (pdata->window), "destroy", G_CALLBACK (destroy_progress), (gpointer) pdata); gtk_window_set_title (GTK_WINDOW (pdata->window), "GtkProgressBar"); gtk_container_set_border_width (GTK_CONTAINER (pdata->window), 0); vbox = gtk_vbox_new (FALSE, 5); gtk_container_set_border_width (GTK_CONTAINER (vbox), 10); gtk_container_add (GTK_CONTAINER (pdata->window), vbox); gtk_widget_show (vbox); /* Создаём объект выравнивания */ align = gtk_alignment_new (0.5, 0.5, 0, 0); gtk_box_pack_start (GTK_BOX (vbox), align, FALSE, FALSE, 5); gtk_widget_show (align); /* Создаём GtkProgressBar */ pdata->pbar = gtk_progress_bar_new (); gtk_container_add (GTK_CONTAINER (align), pdata->pbar); gtk_widget_show (pdata->pbar); /* Добавляем обратный вызов таймера для обновления значения шкалы выполнения */ pdata->timer = gtk_timeout_add (100, progress_timeout, pdata); separator = gtk_hseparator_new (); gtk_box_pack_start (GTK_BOX (vbox), separator, FALSE, FALSE, 0); gtk_widget_show (separator); /* ряды, колонки, homogeneous */ table = gtk_table_new (2, 3, FALSE); gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, TRUE, 0); gtk_widget_show (table); /* Добавляем кнопку контроля для отображения текста в колее */ check = gtk_check_button_new_with_label ("Показать текст"); gtk_table_attach (GTK_TABLE (table), check, 0, 1, 0, 1, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 5, 5); g_signal_connect (G_OBJECT (check), "clicked", G_CALLBACK (toggle_show_text), (gpointer) pdata); gtk_widget_show (check); /* Добавляем контроль-кнопку активного режима */ check = gtk_check_button_new_with_label ("Активный режим"); gtk_table_attach (GTK_TABLE (table), check, 0, 1, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 5, 5); g_signal_connect (G_OBJECT (check), "clicked", G_CALLBACK (toggle_activity_mode), (gpointer) pdata); gtk_widget_show (check); /* Добавляем контроль-кнопку переключателя активного режима */ check = gtk_check_button_new_with_label ("Справа на лево"); gtk_table_attach (GTK_TABLE (table), check, 0, 1, 2, 3, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 5, 5); g_signal_connect (G_OBJECT (check), "clicked", G_CALLBACK (toggle_orientation), (gpointer) pdata); gtk_widget_show (check); /* Добавляем кнопку выхода из программы */ button = gtk_button_new_with_label ("закрыть"); g_signal_connect_swapped (G_OBJECT (button), "clicked", G_CALLBACK (gtk_widget_destroy), G_OBJECT (pdata->window)); gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0); /* Создаем активную кнопку по умолчанию. */ GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT); /* Устанавливаем перехват для кнопки по умолчанию. * Простое нажатие клавиши "Enter" активизирует кнопку. */ gtk_widget_grab_default (button); gtk_widget_show (button); gtk_widget_show (pdata->window); gtk_main (); return 0; } |
The Tooltips Object |
Dialogs |
GTK+ 2.0 Tutorial |
||
---|---|---|
Разные виджеты (Miscellaneous Widgets) |
Виджет диалога очень простой, фактически это просто окно с пред упакованными виджетами. Структура диалога такова:
struct GtkDialog { GtkWindow window; GtkWidget *vbox; GtkWidget *action_area; }; |
Вы видите, что создаётся обычное окно в которое в верху упаковывается vbox, содержащий сепаратор и затем hbox вызываемый "action_area".
Виджет диалога может использоваться в качестве всплывающих сообщений для пользователя и других подобных задач. Существует две функции для создания новых диалогов:
GtkWidget *gtk_dialog_new( void ); GtkWidget *gtk_dialog_new_with_buttons( const gchar *title, GtkWindow *parent, GtkDialogFlags flags, const gchar *first_button_text, ... ); |
Первая функция создаёт пустой диалог для дальнейшего использования. Вы можете упаковать кнопку action_area примерно так:
button = ... gtk_box_pack_start (GTK_BOX (GTK_DIALOG (window)->action_area), button, TRUE, TRUE, 0); gtk_widget_show (button); |
Вы также можете добавить область упаковки vbox для ярлыка:
label = gtk_label_new ("Dialogs are groovy"); gtk_box_pack_start (GTK_BOX (GTK_DIALOG (window)->vbox), label, TRUE, TRUE, 0); gtk_widget_show (label); |
Как пример использования можно привести окно с ярлыком содержащим некий текст и двумя кнопками для того, чтобы пользователь смог сделать выбор между "OK" и "Сancel", создав тем самым определённый сигнал для выполнения дальнейших действий.
Если простые функциональные возможности, обеспеченные вертикальными и горизонтальными контейнерами по умолчанию в этих двух областях не дают вам достаточного контроля для вашего приложения, то вы можете просто упаковать другой виджет расположения в предоставленные контейнеры. Например вы можете поместить таблицу в вертикальный контейнер.
Более сложный вариант complicated _new_with_buttons() позволяет установить один или более флагов.
Progress Bars |
Rulers |
GTK+ 2.0 Tutorial |
||
---|---|---|
Разные виджеты (Miscellaneous Widgets) |
Виджеты линейки используются для индикации координат места положения курсора мыши в данном окне. Существуют горизонтальные линейки и вертикальные, располагающиеся в окне соответственно. Точное положение курсора отображается на линейки с помощью специального треугольника, выполняющего роль стрелки.
Горизонтальные и вертикальные линейки создаются при помощи функций:
GtkWidget *gtk_hruler_new( void ); /* горизонтальная линейка */ GtkWidget *gtk_vruler_new( void ); /* вертикальная линейка */ |
После создания линейки, можно определить значение единиц измерения. Единицы измерения линеек могут быть GTK_PIXELS, GTK_INCHES или GTK_CENTIMETERS:
void gtk_ruler_set_metric( GtkRuler *ruler, GtkMetricType metric ); |
По умолчанию единицы измерения установлены в GTK_PIXELS.
gtk_ruler_set_metric( GTK_RULER(ruler), GTK_PIXELS ); |
Другими важными параметрами линеек является изначальное расположение индикатора положения и как нанесена разметка линейки. Это устанавливается с помощью:
void gtk_ruler_set_range( GtkRuler *ruler, gdouble lower, gdouble upper, gdouble position, gdouble max_size ); |
Аргументы lower и upper определяют размер линейки, а max_size наибольшее число для отображения. Аргумент position определяет начальное расположение индикатора линейки.
Вертикальная линейка в 800 pixel, может быть определена таким образом:
gtk_ruler_set_range( GTK_RULER(vruler), 0, 800, 0, 800); |
Линейка будет промаркирована от 0 до 800, с отображением цифр каждые 100 pixels. Чтобы установить размер линейки от 7 до 16 нужно ввести следующее:
gtk_ruler_set_range( GTK_RULER(vruler), 7, 16, 0, 20); |
Если линейка используется для следования за указателем мыши, то необходимо к линейке подключить сигнал motion_notify_event соответствующим методом. Для следования за всеми движениями курсора мыши в окне нужно использовать:
#define EVENT_METHOD(i, x) GTK_WIDGET_GET_CLASS(i)->x g_signal_connect_swapped (G_OBJECT (area), "motion_notify_event", G_CALLBACK (EVENT_METHOD (ruler, motion_notify_event)), G_OBJECT (ruler)); |
В следующем примере создаётся окно с горизонтальной линейкой вверху и вертикальной слева. Размер полотна определен в 600 пикселей ширины и 400 высоты. Горизонтальная линейка охватывает от 7 до 13 единиц, с маркировкой через каждые 10 pixels, а вертикальная линейка охватывает от 0 до 400 единиц, с маркировкой через каждые 100 pixels. Размещение полотна и линеек в окне выполнено с использованием таблицы.
#include <gtk/gtk.h> #define EVENT_METHOD(i, x) GTK_WIDGET_GET_CLASS(i)->x #define XSIZE 600 #define YSIZE 400 /* Стандартная операция выхода из программы при нажатии на кнопку выход */ gint close_application( GtkWidget *widget, GdkEvent *event, gpointer data ) { gtk_main_quit (); return FALSE; } /* Стандартная функция main */ int main( int argc, char *argv[] ) { GtkWidget *window, *table, *area, *hrule, *vrule; /* Инициализируем GTK и создаём новое основное окно */ gtk_init (&argc, &argv); window = gtk_window_new (GTK_WINDOW_TOPLEVEL); g_signal_connect (G_OBJECT (window), "delete_event", G_CALLBACK (close_application), NULL); gtk_container_set_border_width (GTK_CONTAINER (window), 10); /* Создаём таблицу для размещения полотна и линеек */ table = gtk_table_new (3, 2, FALSE); gtk_container_add (GTK_CONTAINER (window), table); area = gtk_drawing_area_new (); gtk_widget_set_size_request (GTK_WIDGET (area), XSIZE, YSIZE); gtk_table_attach (GTK_TABLE (table), area, 1, 2, 1, 2, GTK_EXPAND|GTK_FILL, GTK_FILL, 0, 0); gtk_widget_set_events (area, GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK); /* Горизонтальная линейка располагается сверху. При передвижении указателя мыши * по полотну, сигнал motion_notify_event посылается обработчику сигналов для линейки. */ hrule = gtk_hruler_new (); gtk_ruler_set_metric (GTK_RULER (hrule), GTK_PIXELS); gtk_ruler_set_range (GTK_RULER (hrule), 7, 13, 0, 20); g_signal_connect_swapped (G_OBJECT (area), "motion_notify_event", G_CALLBACK (EVENT_METHOD (hrule, motion_notify_event)), G_OBJECT (hrule)); gtk_table_attach (GTK_TABLE (table), hrule, 1, 2, 0, 1, GTK_EXPAND|GTK_SHRINK|GTK_FILL, GTK_FILL, 0, 0); /* Вертикальная линейка располагается слева. При передвижении указателя мыши * по полотну, сигнал motion_notify_event посылается обработчику сигналов для линейки. */ vrule = gtk_vruler_new (); gtk_ruler_set_metric (GTK_RULER (vrule), GTK_PIXELS); gtk_ruler_set_range (GTK_RULER (vrule), 0, YSIZE, 10, YSIZE ); g_signal_connect_swapped (G_OBJECT (area), "motion_notify_event", G_CALLBACK (EVENT_METHOD (vrule, motion_notify_event)), G_OBJECT (vrule)); gtk_table_attach (GTK_TABLE (table), vrule, 0, 1, 1, 2, GTK_FILL, GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0); /* Выводим всё на экран */ gtk_widget_show (area); gtk_widget_show (hrule); gtk_widget_show (vrule); gtk_widget_show (table); gtk_widget_show (window); gtk_main (); return 0; } |
Dialogs |
Statusbars |
GTK+ 2.0 Tutorial |
||
---|---|---|
Разные виджеты (Miscellaneous Widgets) |
Строка состояния, это простой виджет для отображения текстовых сообщений. Она содержит сообщения в стеке, таким образом каждое сообщение может быть выведено повторно после перекрытия новым сообщением.
Чтобы позволить различным частям приложения использовать туже самую строку состояния (statusbar), для отображения сообщений, statusbar виджет использует Идентификаторы Контекста, которые используются, чтобы идентифицировать различных "пользователей". На экран выводится сообщение находящееся в данный момент сверху в стеке, не зависимо от контекста. Сообщения помещаются в стек по принципу - "последний вошёл первый вышел", независимо от контекста.
Строка состояния создаётся вызовом:
GtkWidget *gtk_statusbar_new( void ); |
Новый контекстный идентификатор (Context Identifier) создаётся используя функцию с коротким текстовым описанием контекста:
guint gtk_statusbar_get_context_id( GtkStatusbar *statusbar, const gchar *context_description ); |
Существует три функции для работы со statusbars:
guint gtk_statusbar_push( GtkStatusbar *statusbar, guint context_id, const gchar *text ); void gtk_statusbar_pop( GtkStatusbar *statusbar) guint context_id ); void gtk_statusbar_remove( GtkStatusbar *statusbar, guint context_id, guint message_id ); |
Первая, gtk_statusbar_push(), используется для добавления сообщений в строку состояния. Она возвращает идентификатор сообщения (Message Identifier), который может быть использован функцией gtk_statusbar_remove для удаления данного сообщения из стека строки состояния.
Функция gtk_statusbar_pop() перемещает на вершину стека сообщение с определенным идентификатором (Context Identifier).
В дополнение к текстовым сообщениям может отображаться небольшая область для изменения размера основного окна при захвате курсором мыши. Следующие функции предназначены для отображения области контроля размера окна в строке состояния.
void gtk_statusbar_set_has_resize_grip( GtkStatusbar *statusbar, gboolean setting ); gboolean gtk_statusbar_get_has_resize_grip( GtkStatusbar *statusbar ); |
В следующем примере в окне расположена строка состояния и две кнопки, первая размещает дополнительный пункт в стеке, а вторая выводит предыдущий пункт.
#include <stdlib.h> #include <gtk/gtk.h> #include <glib.h> GtkWidget *status_bar; void push_item( GtkWidget *widget, gpointer data ) { static int count = 1; char buff[20]; g_snprintf (buff, 20, "Item %d", count++); gtk_statusbar_push (GTK_STATUSBAR (status_bar), GPOINTER_TO_INT (data), buff); return; } void pop_item( GtkWidget *widget, gpointer data ) { gtk_statusbar_pop (GTK_STATUSBAR (status_bar), GPOINTER_TO_INT (data)); return; } int main( int argc, char *argv[] ) { GtkWidget *window; GtkWidget *vbox; GtkWidget *button; gint context_id; gtk_init (&argc, &argv); /* создаём новое окно */ window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_widget_set_size_request (GTK_WIDGET (window), 200, 100); gtk_window_set_title (GTK_WINDOW (window), "GTK statusbar"); g_signal_connect (G_OBJECT (window), "delete_event", G_CALLBACK (exit), NULL); vbox = gtk_vbox_new (FALSE, 1); gtk_container_add (GTK_CONTAINER (window), vbox); gtk_widget_show (vbox); status_bar = gtk_statusbar_new (); gtk_box_pack_start (GTK_BOX (vbox), status_bar, TRUE, TRUE, 0); gtk_widget_show (status_bar); context_id = gtk_statusbar_get_context_id( GTK_STATUSBAR (status_bar), "Statusbar example"); button = gtk_button_new_with_label ("push item"); g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (push_item), GINT_TO_POINTER (context_id)); gtk_box_pack_start (GTK_BOX (vbox), button, TRUE, TRUE, 2); gtk_widget_show (button); button = gtk_button_new_with_label ("pop last item"); g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (pop_item), GINT_TO_POINTER (context_id)); gtk_box_pack_start (GTK_BOX (vbox), button, TRUE, TRUE, 2); gtk_widget_show (button); /* выводим на экран всё содержимое окна */ gtk_widget_show (window); gtk_main (); return 0; } |
Rulers |
Text Entries |
GTK+ 2.0 Tutorial |
||
---|---|---|
Разные виджеты (Miscellaneous Widgets) |
Виджет ввода позволяет вводить текст и отображать его в специальном текстовом окне. Текст может быть установлен вызовом функции позволяющей заменять предустановленный текст на новый, или может использоваться текущий текстовый ввод.
Функция для создания виджетов ввода текста:
GtkWidget *gtk_entry_new( void ); |
Следующая функция меняет текст, находящийся в данный момент в виджете ввода:
void gtk_entry_set_text( GtkEntry *entry, const gchar *text ); |
Функция gtk_entry_set_text() устанавливает новое содержимое виджета ввода, перезаписывая текущее содержимое. Заметьте, класс ввода содержит Editable interface (да, gobject поддерживают джава-подобный интерфейс (Java-like interfaces)) который содержит много функций для манипуляций содержимым.
Содержимое ввода может быть восстановлено при помощи следующей функции. Это полезно для функций обратного вызова описанных ниже.
const gchar *gtk_entry_get_text( GtkEntry *entry ); |
Значение возвращенное этой функцией применяется для внутреннего использования и не должно высвобождаться с помощью использования free() или g_free().
Если мы не хотим чтобы что-либо изменяло содержимое ввода, то мы можем изменить статус редактирования:
void gtk_editable_set_editable( GtkEditable *entry, gboolean editable ); |
Эта функция позволяет переключать состояние редактирования для виджета ввода, устанавливая в качестве аргумента editable значение TRUE или FALSE.
Если вы не хотите чтобы в момент ввода текста он отображался в виджете ввода, например при вводе пароля, то вы можете воспользоваться следующей функцией установив соответствующий флаг.
void gtk_entry_set_visibility( GtkEntry *entry, gboolean visible ); |
Установки области текста могут быть определены следующей функцией. Это чаще всего используется для установки текста по умолчанию, который удаляется после ввода пользователя.
void gtk_editable_select_region( GtkEditable *entry, gint start, gint end ); |
Если нужно перехватить момент, когда пользователь производит ввод, можно подключить сигнал activate или changed. Сигнал Activate производится, когда пользователь нажал клавишу в области виджета ввода. Сигнал Changed производится, когда происходит любое изменение текста вообще, например когда вводится или удаляется символ.
Ниже приведен пример кода, для создания виджета ввода текста.
#include <stdio.h> #include <stdlib.h> #include <gtk/gtk.h> void enter_callback( GtkWidget *widget, GtkWidget *entry ) { const gchar *entry_text; entry_text = gtk_entry_get_text (GTK_ENTRY (entry)); printf("Entry contents: %s\n", entry_text); } void entry_toggle_editable( GtkWidget *checkbutton, GtkWidget *entry ) { gtk_editable_set_editable (GTK_EDITABLE (entry), GTK_TOGGLE_BUTTON (checkbutton)->active); } void entry_toggle_visibility( GtkWidget *checkbutton, GtkWidget *entry ) { gtk_entry_set_visibility (GTK_ENTRY (entry), GTK_TOGGLE_BUTTON (checkbutton)->active); } int main( int argc, char *argv[] ) { GtkWidget *window; GtkWidget *vbox, *hbox; GtkWidget *entry; GtkWidget *button; GtkWidget *check; gint tmp_pos; gtk_init (&argc, &argv); /* создаём новое окно */ window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_widget_set_size_request (GTK_WIDGET (window), 200, 100); gtk_window_set_title (GTK_WINDOW (window), "GTK Entry"); g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (gtk_main_quit), NULL); g_signal_connect_swapped (G_OBJECT (window), "delete_event", G_CALLBACK (gtk_widget_destroy), G_OBJECT (window)); vbox = gtk_vbox_new (FALSE, 0); gtk_container_add (GTK_CONTAINER (window), vbox); gtk_widget_show (vbox); entry = gtk_entry_new (); gtk_entry_set_max_length (GTK_ENTRY (entry), 50); g_signal_connect (G_OBJECT (entry), "activate", G_CALLBACK (enter_callback), (gpointer) entry); gtk_entry_set_text (GTK_ENTRY (entry), "hello"); tmp_pos = GTK_ENTRY (entry)->text_length; gtk_editable_insert_text (GTK_EDITABLE (entry), " world", -1, &tmp_pos); gtk_editable_select_region (GTK_EDITABLE (entry), 0, GTK_ENTRY (entry)->text_length); gtk_box_pack_start (GTK_BOX (vbox), entry, TRUE, TRUE, 0); gtk_widget_show (entry); hbox = gtk_hbox_new (FALSE, 0); gtk_container_add (GTK_CONTAINER (vbox), hbox); gtk_widget_show (hbox); check = gtk_check_button_new_with_label ("Editable"); gtk_box_pack_start (GTK_BOX (hbox), check, TRUE, TRUE, 0); g_signal_connect (G_OBJECT (check), "toggled", G_CALLBACK (entry_toggle_editable), (gpointer) entry); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check), TRUE); gtk_widget_show (check); check = gtk_check_button_new_with_label ("Visible"); gtk_box_pack_start (GTK_BOX (hbox), check, TRUE, TRUE, 0); g_signal_connect (G_OBJECT (check), "toggled", G_CALLBACK (entry_toggle_visibility), (gpointer) entry); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check), TRUE); gtk_widget_show (check); button = gtk_button_new_from_stock (GTK_STOCK_CLOSE); g_signal_connect_swapped (G_OBJECT (button), "clicked", G_CALLBACK (gtk_widget_destroy), G_OBJECT (window)); gtk_box_pack_start (GTK_BOX (vbox), button, TRUE, TRUE, 0); GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT); gtk_widget_grab_default (button); gtk_widget_show (button); gtk_widget_show (window); gtk_main(); return 0; } |
Statusbars |
Spin Buttons |
GTK+ 2.0 Tutorial |
||
---|---|---|
Miscellaneous Widgets |
Кнопки "карусели" используются для пошагового изменения определенного значения в любом направлении, это похоже на прокручивание. Этот виджет выглядит как две кнопки со стрелками расположенные друг над другом, обычно он располагается сбоку в виджете ввода текста. Нажатие на одну из этих кнопок приводит к прокручиванию значений в ту или в другую сторону. При этом строка ввода может уже содержать определенное значение.
Кнопка "карусель" может иметь изначально нулевое или десятичное значение и выполнять пошаговое увеличение или уменьшение. Удержание одной из кнопок в нажатом состоянии приводит к ускоренному изменению значения, в зависимости от времени удержания.
Кнопки "карусели" используют Adjustment объекты для содержания информации о возможных значениях регулирования. Это делает данный виджет более мощным в использовании.
Вспомните что adjustment widget создаются с помощью функции которая содержит следующую информацию:
GtkObject *gtk_adjustment_new( gdouble value, gdouble lower, gdouble upper, gdouble step_increment, gdouble page_increment, gdouble page_size ); |
Эти атрибуты регулирования используются кнопками "карусель" следующим образом:
value: инициализирует значение кнопки "карусель"
lower: меньшее значение регулировки
upper: высшее значение регулировки
step_increment: значение на которое происходит увеличение или уменьшение (increment/decrement) при нажатии на кнопку-1 мыши
page_increment: значение increment/decrement при нажатии на кнопку-2
page_size: не используется
Дополнительно можно использовать кнопку-3 мыши для перехода сразу к высшему или низшему значению регулировок, в зависимости от выбранной кнопки. Создание новой кнопки "карусель":
GtkWidget *gtk_spin_button_new( GtkAdjustment *adjustment, gdouble climb_rate, guint digits ); |
Аргумент climb_rate должен иметь значение между 0.0 и 1.0 и показывает степень ускорения прокручивания. Аргумент digits определяет кол-во цифр после запятой в десятичных значениях.
Кнопку "карусель" можно переконфигурировать после создания с помощью следующей функции:
void gtk_spin_button_configure( GtkSpinButton *spin_button, GtkAdjustment *adjustment, gdouble climb_rate, guint digits ); |
Аргумент spin_button определяет что кнопка "карусель" должна быть переконфигурирована. Другие аргументы несут новые значения.
Регулировки могут быть установлены с помощью двух функций:
void gtk_spin_button_set_adjustment( GtkSpinButton *spin_button, GtkAdjustment *adjustment ); GtkAdjustment *gtk_spin_button_get_adjustment( GtkSpinButton *spin_button ); |
Кол-во цифр после запятой в десятичных значениях тоже может быть изменено:
void gtk_spin_button_set_digits( GtkSpinButton *spin_button, guint digits) ; |
Текущее значение можно изменять с помощью:
void gtk_spin_button_set_value( GtkSpinButton *spin_button, gdouble value ); |
Определить текущее значение, десятичное или целое, можно при помощи следующих двух функций:
gdouble gtk_spin_button_get_value ( GtkSpinButton *spin_button ); gint gtk_spin_button_get_value_as_int( GtkSpinButton *spin_button ); |
Если вы хотите изменить значение кнопки по отношению к текущему значению, то используйте следующую функцию:
void gtk_spin_button_spin( GtkSpinButton *spin_button, GtkSpinType direction, gdouble increment ); |
Параметр direction может принимать одно из этих значений:
GTK_SPIN_STEP_FORWARD GTK_SPIN_STEP_BACKWARD GTK_SPIN_PAGE_FORWARD GTK_SPIN_PAGE_BACKWARD GTK_SPIN_HOME GTK_SPIN_END GTK_SPIN_USER_DEFINED |
Эта функция упаковывается в значительное количество выполняемых функций, которые я попытаюсь объяснить. Многие настройки используемые объектом регулировки (Adjustment object) связаны с кнопкой "карусель".
GTK_SPIN_STEP_FORWARD и GTK_SPIN_STEP_BACKWARD изменяет значение Кнопки "карусель" количеством, определенным приращением, если приращение не равно 0, когда значение изменено значением step_increment в Adjustment.
GTK_SPIN_PAGE_FORWARD и GTK_SPIN_PAGE_BACKWARD просто изменяет значение кнопки "карусель" приращением (increment).
GTK_SPIN_HOME устанавливает значение в основание регулятора диапазона.
GTK_SPIN_END устанавливает значение к вершине регулятора диапазона.
GTK_SPIN_USER_DEFINED просто изменяет значение указанным количеством.
Мы закончили с рассмотрением функций отвечающих за установки и определения признаков кнопок "карусель" и переходим к функциям непосредственного создания виджета кнопка "карусель".
Первая функция ограничивает поле кнопки "карусель" так, чтобы оно могло содержать только числовое значение. Это не позволяет пользователю печатать ничего кроме цифр в данном поле:
void gtk_spin_button_set_numeric( GtkSpinButton *spin_button, gboolean numeric ); |
Следующая функция определяет возможность кругового изменения значения, когда по достижению верхнего или нижнего значения, изменение продолжается с противоположного конца:
void gtk_spin_button_set_wrap( GtkSpinButton *spin_button, gboolean wrap ); |
Вы можете установить приблизительное значение к самому близкому step_increment, который установлен в пределах объекта регулирования, используемого с Кнопкой "карусель":
void gtk_spin_button_set_snap_to_ticks( GtkSpinButton *spin_button, gboolean snap_to_ticks ); |
Политика обновления для кнопки "карусель" (Spin Button) может быть определена с помощью функции:
void gtk_spin_button_set_update_policy( GtkSpinButton *spin_button, GtkSpinButtonUpdatePolicy policy ); |
Возможные значения аргумента policy GTK_UPDATE_ALWAYS или GTK_UPDATE_IF_VALID.
Эта политика затрагивает поведение Кнопки "карусель", анализируя вставленный текст и синхронизируя его значение со значениями Настройки (Adjustment).
В случае GTK_UPDATE_IF_VALID если текстовый ввод изменен - числовое значение находится в диапазоне регулирования. Иначе текст сбрасывается к текущему значению.
В случае GTK_UPDATE_ALWAYS мы игнорируем ошибки, преобразовывая текст в числовое значение.
Наконец вы можете непосредственно обновлять кнопку "карусель":
void gtk_spin_button_update( GtkSpinButton *spin_button ); |
Пример:
#include <stdio.h> #include <gtk/gtk.h> static GtkWidget *spinner1; void toggle_snap( GtkWidget *widget, GtkSpinButton *spin ) { gtk_spin_button_set_snap_to_ticks (spin, GTK_TOGGLE_BUTTON (widget)->active); } void toggle_numeric( GtkWidget *widget, GtkSpinButton *spin ) { gtk_spin_button_set_numeric (spin, GTK_TOGGLE_BUTTON (widget)->active); } void change_digits( GtkWidget *widget, GtkSpinButton *spin ) { gtk_spin_button_set_digits (GTK_SPIN_BUTTON (spinner1), gtk_spin_button_get_value_as_int (spin)); } void get_value( GtkWidget *widget, gpointer data ) { gchar buf[32]; GtkLabel *label; GtkSpinButton *spin; spin = GTK_SPIN_BUTTON (spinner1); label = GTK_LABEL (g_object_get_data (G_OBJECT (widget), "user_data")); if (GPOINTER_TO_INT (data) == 1) sprintf (buf, "%d", gtk_spin_button_get_value_as_int (spin)); else sprintf (buf, "%0.*f", spin->digits, gtk_spin_button_get_value (spin)); gtk_label_set_text (label, buf); } int main( int argc, char *argv[] ) { GtkWidget *window; GtkWidget *frame; GtkWidget *hbox; GtkWidget *main_vbox; GtkWidget *vbox; GtkWidget *vbox2; GtkWidget *spinner2; GtkWidget *spinner; GtkWidget *button; GtkWidget *label; GtkWidget *val_label; GtkAdjustment *adj; /* Инициализируем GTK */ gtk_init (&argc, &argv); window = gtk_window_new (GTK_WINDOW_TOPLEVEL); g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (gtk_main_quit), NULL); gtk_window_set_title (GTK_WINDOW (window), "Spin Button"); main_vbox = gtk_vbox_new (FALSE, 5); gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 10); gtk_container_add (GTK_CONTAINER (window), main_vbox); frame = gtk_frame_new ("Not accelerated"); gtk_box_pack_start (GTK_BOX (main_vbox), frame, TRUE, TRUE, 0); vbox = gtk_vbox_new (FALSE, 0); gtk_container_set_border_width (GTK_CONTAINER (vbox), 5); gtk_container_add (GTK_CONTAINER (frame), vbox); /* Прокручивание - день, месяц, год */ hbox = gtk_hbox_new (FALSE, 0); gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 5); vbox2 = gtk_vbox_new (FALSE, 0); gtk_box_pack_start (GTK_BOX (hbox), vbox2, TRUE, TRUE, 5); label = gtk_label_new ("Day :"); gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5); gtk_box_pack_start (GTK_BOX (vbox2), label, FALSE, TRUE, 0); adj = (GtkAdjustment *) gtk_adjustment_new (1.0, 1.0, 31.0, 1.0, 5.0, 0.0); spinner = gtk_spin_button_new (adj, 0, 0); gtk_spin_button_set_wrap (GTK_SPIN_BUTTON (spinner), TRUE); gtk_box_pack_start (GTK_BOX (vbox2), spinner, FALSE, TRUE, 0); vbox2 = gtk_vbox_new (FALSE, 0); gtk_box_pack_start (GTK_BOX (hbox), vbox2, TRUE, TRUE, 5); label = gtk_label_new ("Month :"); gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5); gtk_box_pack_start (GTK_BOX (vbox2), label, FALSE, TRUE, 0); adj = (GtkAdjustment *) gtk_adjustment_new (1.0, 1.0, 12.0, 1.0, 5.0, 0.0); spinner = gtk_spin_button_new (adj, 0, 0); gtk_spin_button_set_wrap (GTK_SPIN_BUTTON (spinner), TRUE); gtk_box_pack_start (GTK_BOX (vbox2), spinner, FALSE, TRUE, 0); vbox2 = gtk_vbox_new (FALSE, 0); gtk_box_pack_start (GTK_BOX (hbox), vbox2, TRUE, TRUE, 5); label = gtk_label_new ("Year :"); gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5); gtk_box_pack_start (GTK_BOX (vbox2), label, FALSE, TRUE, 0); adj = (GtkAdjustment *) gtk_adjustment_new (1998.0, 0.0, 2100.0, 1.0, 100.0, 0.0); spinner = gtk_spin_button_new (adj, 0, 0); gtk_spin_button_set_wrap (GTK_SPIN_BUTTON (spinner), FALSE); gtk_widget_set_size_request (spinner, 55, -1); gtk_box_pack_start (GTK_BOX (vbox2), spinner, FALSE, TRUE, 0); frame = gtk_frame_new ("Accelerated"); gtk_box_pack_start (GTK_BOX (main_vbox), frame, TRUE, TRUE, 0); vbox = gtk_vbox_new (FALSE, 0); gtk_container_set_border_width (GTK_CONTAINER (vbox), 5); gtk_container_add (GTK_CONTAINER (frame), vbox); hbox = gtk_hbox_new (FALSE, 0); gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, TRUE, 5); vbox2 = gtk_vbox_new (FALSE, 0); gtk_box_pack_start (GTK_BOX (hbox), vbox2, TRUE, TRUE, 5); label = gtk_label_new ("Value :"); gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5); gtk_box_pack_start (GTK_BOX (vbox2), label, FALSE, TRUE, 0); adj = (GtkAdjustment *) gtk_adjustment_new (0.0, -10000.0, 10000.0, 0.5, 100.0, 0.0); spinner1 = gtk_spin_button_new (adj, 1.0, 2); gtk_spin_button_set_wrap (GTK_SPIN_BUTTON (spinner1), TRUE); gtk_widget_set_size_request (spinner1, 100, -1); gtk_box_pack_start (GTK_BOX (vbox2), spinner1, FALSE, TRUE, 0); vbox2 = gtk_vbox_new (FALSE, 0); gtk_box_pack_start (GTK_BOX (hbox), vbox2, TRUE, TRUE, 5); label = gtk_label_new ("Digits :"); gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5); gtk_box_pack_start (GTK_BOX (vbox2), label, FALSE, TRUE, 0); adj = (GtkAdjustment *) gtk_adjustment_new (2, 1, 5, 1, 1, 0); spinner2 = gtk_spin_button_new (adj, 0.0, 0); gtk_spin_button_set_wrap (GTK_SPIN_BUTTON (spinner2), TRUE); g_signal_connect (G_OBJECT (adj), "value_changed", G_CALLBACK (change_digits), (gpointer) spinner2); gtk_box_pack_start (GTK_BOX (vbox2), spinner2, FALSE, TRUE, 0); hbox = gtk_hbox_new (FALSE, 0); gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, TRUE, 5); button = gtk_check_button_new_with_label ("Snap to 0.5-ticks"); g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (toggle_snap), (gpointer) spinner1); gtk_box_pack_start (GTK_BOX (vbox), button, TRUE, TRUE, 0); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE); button = gtk_check_button_new_with_label ("Numeric only input mode"); g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (toggle_numeric), (gpointer) spinner1); gtk_box_pack_start (GTK_BOX (vbox), button, TRUE, TRUE, 0); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE); val_label = gtk_label_new (""); hbox = gtk_hbox_new (FALSE, 0); gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, TRUE, 5); button = gtk_button_new_with_label ("Value as Int"); g_object_set_data (G_OBJECT (button), "user_data", val_label); g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (get_value), GINT_TO_POINTER (1)); gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 5); button = gtk_button_new_with_label ("Value as Float"); g_object_set_data (G_OBJECT (button), "user_data", val_label); g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (get_value), GINT_TO_POINTER (2)); gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 5); gtk_box_pack_start (GTK_BOX (vbox), val_label, TRUE, TRUE, 0); gtk_label_set_text (GTK_LABEL (val_label), "0"); hbox = gtk_hbox_new (FALSE, 0); gtk_box_pack_start (GTK_BOX (main_vbox), hbox, FALSE, TRUE, 0); button = gtk_button_new_with_label ("Close"); g_signal_connect_swapped (G_OBJECT (button), "clicked", G_CALLBACK (gtk_widget_destroy), G_OBJECT (window)); gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 5); gtk_widget_show_all (window); /* Входим в завершающий цикл */ gtk_main (); return 0; } |
Text Entries |
Combo Box |