GTK+ 2.0 Tutorial

<<< Previous

Scribble, A Simple Example Drawing Program

Next >>>


Добавление поддержки XInput

Устройства ввода (например, графические планшеты), позволяющие рисовать удобнее и проще, чем мышью, последнее время стали намного дешевле . Самый простой метод использования подобных устройств - замена мыши, но следует заметить, что также существуют другие преимущества :

Для детальной информации о дополнительных возможностях 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.


<<< Previous

Home

Next >>>

The DrawingArea Widget, And Drawing

Up

Tips For Writing GTK Applications