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