GTK+ 2.0 教學-添加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之間的浮點數。xtiltytilt可以取-1到1之間的值,對應在每個方向的傾斜度數。sourcedeviceid用不同的方法指出發生事件的設備。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回復到預設條件。

然而這還沒有完,預設情況下,擴展設備是不允許的。我們需要一個機制讓用戶去允許和配置擴展設備。下面的程序處理一個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。這是必要的,因為函式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和視窗。通常,我們會從一個事件結構的deviceid欄位得到設備ID。當擴展事件不允許時,這個函式也會傳回合法的值。(這樣event->deviceidGDK_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繪製不同的顏色,依據壓力改變畫刷的大小。

/* 在螢幕上畫一個矩形,大小依據壓力,顏色依據設備的類型 */
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);

傳回值是一個GdkDeviceInfo結構的GList(GLib函式庫的一個鏈結串列型別)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。如果has_cursor是 FALSE,我們需要自己繪製游標。但因為我們已經指定了GDK_EXTENSION_EVENTS_CURSOR,所以我們不必關心這個。

函式print_button_press()簡單的重複,直到找到匹配,然後列印出設備名稱。

static void
print_button_press (guint32 deviceid)
{
  GList *tmp_list;

  /* gdk_input_list_devices傳回一個內部列表,因此我們後面不必釋放它。*/
  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_key()

(gdk_input_list_devices()傳回的列表不能直接修改。)在繪圖程式gsumi中可以發現它的用法。(http://www.msc.cornell.edu/~otaylor/gsumi/)其實做這個,最好使用所有應用程式標準的方法。這也許屬於比 GTK 稍進階的函式庫,也許在 GNOME 函式庫中。

另一個缺點是我們上面提到的,缺乏游標繪製。當前平台 XFree86 不允許同時用一個設備和主指示裝置在一個應用程式中。詳見XInput-HOWTO。更好的應用程式應該繪製自己的游標。

一個程式要繪製自己的游標,需要兩方面:確定當前設備是否需要繪製游標,確定當前設備是否”in proximity”。(如果當前設備是手寫板,最好在筆尖離開平板時不顯示游標。當設備是觸摸板時,那叫做”in proximity”。)首先要搜索設備列表,尋找設備名稱。其次是選擇 “proximity_out” 事件。它的用法見 GTK 發佈中的範例程式”testinput”.

感謝你看到這裡,很快就可以離開了,但最好的獎勵行動就是按一下幫我分享或留言,感恩喔~

點我分享到Facebook

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *