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 |