Ahmet Zeki EYMÜR's Hardware and Software Products

Products: AZEskop Sound Card Oscilloscope | C64TPC (Connect C64 to PC) | Programmable USB Footswitch | TimeSticker Software |
Articles(Turkish):
GTK+'da Fare Olaylarının Düşük Seviyeli Yönetimi | Suse Linux 10.1 Kurulum | Suse Linux 10.1 Kurulum Sonrası |

GTK+'da Fare Olaylarının Düşük Seviyeli Yönetimi

Yasal Uyarı
Telif Hakkı (c) Ahmet Zeki EYMÜR - Her hakkı yazarına aittir.
Bu belgede anlatılanların doğru ve uygulanabilir olduğuna karar vermek sizin sorumluluğunuzdadır. Anlatılanların uygulanmasından doğabilecek her türlü zarar ve kayıpların sorumluluğu uygulayana aittir. Bunlardan belge yazarı sorumlu tutulamaz.

GTK+ bileşenleri (Widget) özellikleriyle ilgili olarak bazı sinyalleri hazır olarak yayarlar. Çoğu durumda bu sinyaller yeterlidir. Hatta GTK+ hakkındaki belgeleri incelerken bu sinyallerden pek çoğuna hiç ihtiyaç duymayacağınızı dahi düşünebilirsiniz. Çoğu zaman yapmanız gereken size zaten hazır olarak sağlanmış sinyale fonksiyonunuzu basitçe bağlamaktır ama bir gün programınızda olmasını istediğiniz bir özelliği gerçekleştirebilmek için ihtiyaç duyduğunuz sinyalin ilgili bileşen tarafından hazır olarak yayılmadığını görürseniz. Artık tam kontrol için sizin de "event" sinyaliyle tanışma ve olayları ele alma zamanınız gelmiştir. Bu yazıda GdkEvent birliği (union GdkEvent) ve fareyle ilgili temel olay yapıları (event struct) açıklanmaya çalışılmıştır.

İmrenerek baktığımız profesyonel programlar artık kullanıcılarına pek çok içerik menüsü sağlıyorlar. Sağ tuşla tıkladığımız konuma göre içeriği özelleştirilmiş menüler, kullanıcıların programın ayar veya özelliklerine hızlıca erişmesini sağlıyor. Yine profesyonel programlarda, düşük önem seviyesine sahip bilgilendirme için durum çubuğunun yoğun olarak kullanıldığını görüyoruz. Mesela menülerde gezerken farenin üzerinde bulunduğu seçenekle ilgili birer ikişer cümlelik açıklamalar durum çubuğuna gelip gider. Kendi programlarınızda bunlar gibi özelliklerin bulunmasını isterseniz bileşenlerin sağladağı hazır sinyallerden fazlasına ihtiyacınız var demektir. Çünkü GTK+2'de bileşenler, farenin sağ tuşuyla üzerlerine tıklanması durumunda çağrılmasını istediğiniz fonksiyonunuzu bağlayabileceğiniz hazır bir sinyal sunmazlar. Benzer şekilde menü seçenekleri de fare imleci üzerlerinde dolaşırken buna özel bir sinyal yaymaz.

Sorunu bu kadar tanımladıktan sonra şimdi çözüme geçelim. Olay yönetim sistemiyle ilişkilendirilmiş olan bileşenler, kendileriyle ilgili herhangi bir olay meydana geldiğinde "event" sinyalini yayar.

(Bazı bileşenler olay yönetim sistemiyle ilişkilendirilmemiştir. Bunların bazıları sadece bir çizim, bir grafiktirler. Mesela GtkImage gibi. Bazılarıysa görünmez bile. Bu tür bileşenlerin üzerinde meydana gelecek olayları yakalamanız gerekiyorsa onları bir GtkEventBox içine koymanız gerekir.)

"event" sinyaline bağlamak istediğiniz fonksiyonun prototipi şu şekilde olmalıdır:


gboolean user_function (GtkWidget *widget,

                        GdkEvent  *event,

                        gpointer   user_data)

          

Birinci parametre sinyali yayan bileşenin adresidir. İkinci parametreyse biraz sonra ayrıntılı olarak ele alacağımız GdkEvent birliğine işaret eder. Bu birlik bize meydana gelen olayla ilgili her türlü bilgiyi sağlar. Üçüncü parametreyse fonksiyonumuzu bağlarken bizim belirttiğimiz ve fonksiyona geçirilmesini istediğimiz herhangi bir verinin adresi olabilir. Geri dönüş değerine gelince, eğer FALSE döndürürsek sinyal yayılmaya devam eder. TRUE değeri döndürürsek sinyalin yayılımını durdururuz.

GdkEvent birliğinin bildirimi aşağıdaki gibidir :


union GdkEvent  {

  GdkEventType          type;

  GdkEventAny           any;

  GdkEventExpose        expose;

  GdkEventNoExpose      no_expose;

  GdkEventVisibility    visibility;

  GdkEventMotion        motion;

  GdkEventButton        button;

  GdkEventScroll        scroll;

  GdkEventKey           key;

  GdkEventCrossing      crossing;

  GdkEventFocus         focus_change;

  GdkEventConfigure     configure;

  GdkEventProperty      property;

  GdkEventSelection     selection;

  GdkEventOwnerChange   owner_change;

  GdkEventProximity     proximity;

  GdkEventClient        client;

  GdkEventDND           dnd;

  GdkEventWindowState   window_state;

  GdkEventSetting       setting;

  GdkEventGrabBroken    grab_broken;};

          

GdkEvent, birincisi hariç üyelerininin tamamı yapılardan oluşan bir birliktir. Bu yapılara "olay yapıları" (event struct) denir. Olaylar kendilerine özgü farklı veriler içerecekleri ve birlik aynı anda sadece bir olay için düzenlenmiş olacağından GdkEvent, değişik olay yapılarından oluşmuş bir birlik şeklinde tanımlanmıştır.

GdkEvent birliğinin birinci üyesi olan "type" (GdkEventType türünde) bir sayımlama sabitidir (enum) ve ne tür bir olayın meydana geldiğini belirtir. Birliği doğru bir şekilde kullanabilmemiz için hangi olayın meydana geldiğini bilmemiz önemlidir. Birlik üyelerinin hepsinin belleğin aynı adresinden itibaren yerleştirildiğini unutmayınız. Yani birliğe üye yapılardan sadece birine yapacağımız erişim anlamlı sonuçlar verecektir. Birliğin birinci üyesi olan GdkEventType sayımlama sabiti, birlik üyesi diğer yapıların da hepsinin ilk üyesidir. Böylece meydana gelen olayın tipi ne olursa olsun birliğin birinci üyesine doğrudan erişmemiz mümkün kılınmıştır. Aşağıdaki tabloda, GdkEventType'in bildiriminde bulunan fareyle ilgli temel olay türleri, açıklamaları ve hangi olay yapısıyla ilişkilendirildiği gösterilmektedir:

Temel Fare Olayları
Olay Türü Açıklama Olay Yapısı GdkEvent Üye Adı
GDK_MOTION_NOTIFY İmleç hareket ettirildi. GdkEventMotion motion
GDK_BUTTON_PRESS Bir fare düğmesine basıldı. GdkEventButton button
GDK_2BUTTON_PRESS Bir fare düğmesiyle çift tıklandı. GdkEventButton button
GDK_3BUTTON_PRESS Bir fare düğmesiyle üç kere tıklandı. GdkEventButton button
GDK_BUTTON_RELEASE Bir fare düğmesi bırakıldı. GdkEventButton button
GDK_ENTER_NOTIFY Fare imleci bileşenin üzerine geldi. GdkEventCrossing crossing
GDK_LEAVE_NOTIFY Fare imleci bileşenin üzerinden ayrıldı. GdkEventCrossing crossing
GDK_SCROLL Farenin kaydırma tekerleği çevrildi. GdkEventScroll scroll

Gördüğünüz gibi fareyle ilgili temel olayların ayrıntıları dört değişik yapıda bulunabiliyor. İmlecin hareket ettirilmesi GdkEventMotion yapısı, düğmelerle ilgili olaylar GdkEvenButton yapısı, kaydırma tekerleği ile ilgili olaylar GdkEventScroll yapısı, imlecin bileşenlerin üzerinde dolaşmasıyla ilgili olaylar da GdkEventCrossing yapısı kullanılarak fonksiyonumuza aktarılıyor. Şimdi bu yapıların içlerinde neler olduğuna bakalım. Dört yapının bildirimi ve alanların açıklamaları aşağıdaki gibidir:


/* GdkEventMotion */

typedef struct {

  GdkEventType type; /*Sadece GDK_MOTION_NOTIFY olabilir.*/

  GdkWindow *window; /*Sinyali yayan GTK+ bileşeni*/

  gint8 send_event;

  guint32 time;      /*milisaniye cinsinden olayın zamanı*/

  gdouble x;         /*farenin bileşene göre x koordinatı*/

  gdouble y;         /*farenin bileşene göre y koordinatı*/

  gdouble *axes;

  guint state;       /*Ctrl, Shift, Alt  ve fare düğmelerinin durumları

                         Ayrıntısı aşağıdadır.*/

  gint16 is_hint;

  GdkDevice *device;

  gdouble x_root, y_root; /*Farenin ekrana göre x ve y 

                              koordinatları*/

  } GdkEventMotion;

  

/* GdkEventButton */

typedef struct {

  GdkEventType type; /*GDK_BUTTON_PRESS, 

                       GDK_2BUTTON_PRESS, 

                       GDK_3BUTTON_PRESS, 

                       GDK_BUTTON_RELEASE olay türlerinden 

                       biri olabilir.*/

  GdkWindow *window; /*Sinyali yayan GTK+ bileşeni*/

  gint8 send_event;

  guint32 time;      /*milisaniye cinsinden olayın zamanı*/

  gdouble x;         /*farenin bileşene göre x koordinatı*/

  gdouble y;         /*farenin bileşene göre y koordinatı*/

  gdouble *axes;     /*Fare olayları için hep NULL olur.*/

  guint state;       /*Ctrl, Shift, Alt  ve fare düğmelerinin durumları

                       Ayrıntısı aşağıdadır.*/

  guint button;      /*Basılan ya da bırakılan fare düğmesi:

                       1 = Sol Düğme

                       2 = Orta Düğme

                       3 = Sağ Düğme*/

  GdkDevice *device; 

  gdouble x_root, y_root; /*Farenin ekrana göre x ve y 

                            koordinatları*/

  } GdkEventButton;

  

  

/* GdkEventCrossing */

typedef struct {

  GdkEventType type; /*GDK_ENTER_NOTIFY, 

                       GDK_LEAVE_NOTIFY olay türlerinden biri olabilir*/

  GdkWindow *window; /*Sinyali yayan GTK+ bileşeni*/

  gint8 send_event;

  GdkWindow *subwindow; /*İmlecin üzerine geldiği ya da 

                          ayrıldığı bileşen*/

  guint32 time;      /*milisaniye cinsinden olayın zamanı*/

  gdouble x;         /*farenin bileşene göre x koordinatı*/

  gdouble y;         /*farenin bileşene göre y koordinatı*/

  gdouble x_root;    /*Farenin ekrana göre x koordinatı*/

  gdouble y_root;    /*Farenin ekrana göre y koordinatı*/

  GdkCrossingMode mode;

  GdkNotifyType detail;

  gboolean focus;

  guint state;       /*Ctrl, Shift, Alt  ve fare düğmelerinin durumları

                      Ayrıntısı aşağıdadır.*/

  } GdkEventCrossing;

  

/* GdkEventScroll */

typedef struct {

  GdkEventType type;  /*Sadece GDK_SCROLL olabilir*/

  GdkWindow *window;  /*Sinyali yayan GTK+ bileşeni*/

  gint8 send_event;

  guint32 time;       /*milisaniye cinsinden olayın zamanı*/

  gdouble x;          /*farenin bileşene göre x koordinatı*/

  gdouble y;          /*Farenin ekrana göre y koordinatı*/

  guint state;        /*Ctrl, Shift, Alt  ve fare düğmelerinin durumları. 

                        Ayrıntısı aşağıdadır.*/

  GdkScrollDirection direction; /*Kaydırma tekerleğinin döndürüldüğü yön:

                                  GDK_SCROLL_UP,

                                  GDK_SCROLL_DOWN,

                                  GDK_SCROLL_LEFT,

                                  GDK_SCROLL_RIGHT değerlerini alabilir.*/

  GdkDevice *device;  

  gdouble x_root, y_root; /*Farenin ekrana göre x ve y 

                            koordinatları*/

  } GdkEventScroll;

          

Ctrl, Alt, Shift ve fare düğmelerinin durumlarını barındıran guint türündeki "state" üyesi, GdkModifierType sayımlama sabitinin üyeleriyle mantıksal işlemlere sokularak kullanılır. Bildirimi şöyledir:


typedef enum  {

  GDK_SHIFT_MASK    = 1 << 0,  /* Shift Tuşu     */

  GDK_LOCK_MASK     = 1 << 1,  /* Caps Lock Tuşu */

  GDK_CONTROL_MASK  = 1 << 2,  /* Control Tuşu   */

  GDK_MOD1_MASK     = 1 << 3,  /* Alt Tuşu       */

  GDK_MOD2_MASK     = 1 << 4,  

  GDK_MOD3_MASK     = 1 << 5,

  GDK_MOD4_MASK     = 1 << 6,

  GDK_MOD5_MASK     = 1 << 7,

  GDK_BUTTON1_MASK  = 1 << 8,  /* Sol Fare Düğmesi  */

  GDK_BUTTON2_MASK  = 1 << 9,  /* Orta Fare Düğmesi */

  GDK_BUTTON3_MASK  = 1 << 10, /* Sağ Fare Düğmesi  */

  GDK_BUTTON4_MASK  = 1 << 11, /* 4ncü Fare Düğmesi */

  GDK_BUTTON5_MASK  = 1 << 12, /* 5nci Fare Düğmesi */

  /* The next few modifiers are used by XKB, so we skip to the end.

   * Bits 16 - 28 are currently unused, but will eventually

   * be used for "virtual modifiers". Bit 29 is used internally.

   */

  GDK_RELEASE_MASK  = 1 << 30,

  GDK_MODIFIER_MASK = GDK_RELEASE_MASK | 0x1fff

  } GdkModifierType;

          

Önce GdkEvent birliğinin üyelerine nasıl erişebileceğimize bakalım. Daha önce bahsettiğimiz gibi özellikli bir durumu olan GdkEventType türündeki "type" üyesine doğrudan erişebiliyoruz:


GdkEvent *event;  

GdkEventType type;

  

type = event->type;

          

Diğer üyelere erişim için iki ayrı yol kullanabiliriz. Bir örnek üzerinde hareket edelim. Herhangi bir bileşenin üzerine farenin hangi düğmesiyle tıklandığını öğrenmek istediğimizi farzedelim. Temel fare olaylarının verildiği yukarıdaki tabloya bakın. Bir butona tıklandığında, GDK_BUTTON_PRESS olayının gerçekleştiğini ve olayla ilgili ayrıntıları GdkEvent birliğininin, GdkEventButton türünde tanımlı olan "button" üyesi aracılığıyla almamız gerektiğini görürüz. GdkEventButton yapısının bildiriminini verirken basılan tuş bilgisinin "button" üyesinde saklandığını belirtmiştik. Şimdi GdkEvent birliğinin ilgili üyesi aracalığıyla istediğimiz bilgiye erişelim:


GdkEvent *event;  

guint btn;



btn = event->button.button;

          

Aynı sonuca bilinçli tür dönüşümü yaparak aşağıdaki şekilde de ulaşabiliriz:


GdkEvent *event;  

guint btn;

  

btn = ((GdkEventButton*)event)->button;

          

İnceleyeceğiniz örnek kodlarda her iki yöntemin de kullanıldığını görebilirsiniz. Bana birinci yöntem daha doğal geliyor.

Artık yazının başında bahsettiğimiz özelliklere sahip örnek bir program yazabiliriz. Örnek programımız bir ana menü, bir araç çubuğu, bir durum çubuğu ve bir içerik menüsünden oluşmaktadır. Kullanıcı menüde dolaşırken durum çubuğunda, üzerinde bulunulan menü seçeneği hakkında açıklayıcı bir metin görüntüler. Menü seçeneklerinin tamamı araç çubuğunda da vardır. Ancak araç çubuğunda boş bir yere sağ tuşla tıklanırsa içerik menüsü aktifleşir ve kullanıcı araç çubuğundan istemediği düğmeleri bu menü yardımıyla kaldırabilir.


/*

 Derlemek İçin :

 gcc -o fare1 fare1.c `pkg-config --cflags --libs gtk+-2.0`

*/

#include "gtk/gtk.h"

GtkWidget *Win, *VBox, *MBar, *Lab, *Tbar,

          *Sbar, *Menu, *MItem;

GtkToolItem *TINew, *TIOpen, *TISave, *TIQuit;

guint SbarCI;



gboolean cb_win_delete_event (GtkWidget *Widget, GdkEvent Event, gpointer Data)

{

gtk_main_quit ();

return FALSE;

}

  

gboolean cb_menu_item_event (GtkWidget *Widget, GdkEvent *Event, gpointer Data)

{

if (Event->type == GDK_ENTER_NOTIFY)

  gtk_statusbar_push (GTK_STATUSBAR (Sbar), SbarCI, (gchar *) Data);

if (Event->type == GDK_LEAVE_NOTIFY)

  gtk_statusbar_pop (GTK_STATUSBAR (Sbar), SbarCI);

return FALSE;

}

  

gboolean cb_toolbar_event (GtkWidget *Widget, GdkEvent *Event, gpointer Data)

{

if (Event->type == GDK_BUTTON_PRESS)

  if (Event->button.button == 3)

    {

    gtk_menu_popup (GTK_MENU (Menu), 

                    NULL, NULL, NULL, NULL, 

                    Event->button.button, Event->button.time);

    return TRUE;

    }

return FALSE;

}



void cb_context_menu_toggled (GtkCheckMenuItem *CMItem, GtkWidget *Data)

{

if (gtk_check_menu_item_get_active (CMItem)) 

  gtk_widget_show (Data);

    else gtk_widget_hide (Data);

}



int main (int argc, char *argv[])

{

gtk_init (&argc, &argv);

Win = gtk_window_new (GTK_WINDOW_TOPLEVEL);

gtk_window_set_title (GTK_WINDOW (Win), "Fare Olayları Örneği 1");

gtk_widget_set_size_request (Win, 300, 250);

g_signal_connect (G_OBJECT (Win), "delete_event", 

                  G_CALLBACK (cb_win_delete_event), NULL);

VBox = gtk_vbox_new (FALSE, 0);

gtk_container_add (GTK_CONTAINER (Win), VBox);

MBar = gtk_menu_bar_new ();

gtk_box_pack_start (GTK_BOX (VBox), MBar, FALSE, FALSE, 0);

Tbar = gtk_toolbar_new ();

gtk_box_pack_start (GTK_BOX (VBox), Tbar, FALSE, FALSE, 0);

g_signal_connect (G_OBJECT (Tbar), "event", 

                  G_CALLBACK (cb_toolbar_event), NULL);

Lab = gtk_label_new (NULL);

gtk_label_set_markup (GTK_LABEL (Lab), 

"Fare Olayları Örneği\n\

\n\

Araç çubuğunun üzerinde boş bir yere sağ\n\

tıklayarak içerik menüsüne ulaşabilirsiniz.\n\

\n\

Ana menu seçenekleri hakkında bilgilendirme\n\

durum çubuğunda yapılmaktadır.\n");

gtk_box_pack_start (GTK_BOX (VBox), Lab, TRUE, TRUE, 20);

Sbar = gtk_statusbar_new ();

gtk_box_pack_start (GTK_BOX (VBox), Sbar, FALSE, FALSE, 0);

SbarCI = gtk_statusbar_get_context_id (GTK_STATUSBAR (Sbar), "a");

gtk_statusbar_push (GTK_STATUSBAR (Sbar), SbarCI, "Hazır.");



MItem = gtk_menu_item_new_with_mnemonic ("_Dosya");

gtk_menu_shell_append (GTK_MENU_SHELL (MBar), MItem);

Menu = gtk_menu_new ();

gtk_menu_item_set_submenu (GTK_MENU_ITEM (MItem), Menu);



/* Dosya Menüsü */

MItem = gtk_image_menu_item_new_from_stock (GTK_STOCK_NEW, NULL);

gtk_menu_shell_append (GTK_MENU_SHELL (Menu), MItem);

g_signal_connect (G_OBJECT (MItem), "event", 

                  G_CALLBACK (cb_menu_item_event), "Yeni dosya oluştur.");

MItem = gtk_image_menu_item_new_from_stock (GTK_STOCK_OPEN, NULL);

gtk_menu_shell_append (GTK_MENU_SHELL (Menu), MItem);

g_signal_connect (G_OBJECT (MItem), "event", 

                  G_CALLBACK (cb_menu_item_event), "Mevcut bir dosyayı aç.");

MItem = gtk_image_menu_item_new_from_stock (GTK_STOCK_SAVE, NULL);

gtk_menu_shell_append (GTK_MENU_SHELL (Menu), MItem);

g_signal_connect (G_OBJECT (MItem), "event", 

                  G_CALLBACK (cb_menu_item_event), "Dosyayı Kaydet.");

MItem = gtk_image_menu_item_new_from_stock (GTK_STOCK_QUIT, NULL);

gtk_menu_shell_append (GTK_MENU_SHELL (Menu), MItem);

g_signal_connect (G_OBJECT (MItem), "event", 

                  G_CALLBACK (cb_menu_item_event), "Programdan Çık.");

g_signal_connect (G_OBJECT (MItem), "activate", 

                  G_CALLBACK (cb_win_delete_event), NULL);

/* Araç Çubuğu */

TINew = gtk_tool_button_new_from_stock (GTK_STOCK_NEW);

gtk_toolbar_insert (GTK_TOOLBAR (Tbar), TINew, -1);

TIOpen = gtk_tool_button_new_from_stock (GTK_STOCK_OPEN);

gtk_toolbar_insert (GTK_TOOLBAR (Tbar), TIOpen, -1);

TISave = gtk_tool_button_new_from_stock (GTK_STOCK_SAVE);

gtk_toolbar_insert (GTK_TOOLBAR (Tbar), TISave, -1);

TIQuit = gtk_tool_button_new_from_stock (GTK_STOCK_QUIT);

gtk_toolbar_insert (GTK_TOOLBAR (Tbar), TIQuit, -1);

g_signal_connect (G_OBJECT (TIQuit), "clicked", 

                  G_CALLBACK (cb_win_delete_event), NULL);

/* İçerik Menüsü */

Menu = gtk_menu_new ();

MItem = gtk_menu_item_new_with_label ("Araç Çubuğunda Göster");

gtk_menu_shell_append (GTK_MENU_SHELL (Menu), MItem);

gtk_widget_set_sensitive (MItem, FALSE);

gtk_widget_show (MItem);

MItem = gtk_check_menu_item_new_with_label ("Yeni");

gtk_menu_shell_append (GTK_MENU_SHELL (Menu), MItem);

gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (MItem), TRUE);

g_signal_connect (G_OBJECT (MItem), "toggled", 

                  G_CALLBACK (cb_context_menu_toggled), TINew);

gtk_widget_show (MItem);

MItem = gtk_check_menu_item_new_with_label ("Aç");

gtk_menu_shell_append (GTK_MENU_SHELL (Menu), MItem);

gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (MItem), TRUE);

g_signal_connect (G_OBJECT (MItem), "toggled", 

                  G_CALLBACK (cb_context_menu_toggled), TIOpen);

gtk_widget_show (MItem);

MItem = gtk_check_menu_item_new_with_label ("Kaydet");

gtk_menu_shell_append (GTK_MENU_SHELL (Menu), MItem);

gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (MItem), TRUE);

g_signal_connect (G_OBJECT (MItem), "toggled", 

                  G_CALLBACK (cb_context_menu_toggled), TISave);

gtk_widget_show (MItem);

MItem = gtk_check_menu_item_new_with_label ("Çık");

gtk_menu_shell_append (GTK_MENU_SHELL (Menu), MItem);

gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (MItem), TRUE);

g_signal_connect (G_OBJECT (MItem), "toggled", 

                  G_CALLBACK (cb_context_menu_toggled), TIQuit);

gtk_widget_show (MItem);



gtk_widget_show_all (Win);

gtk_main ();

return 0;

}

          

Telif Hakkı (c) 2007,2008,2009 Ahmet Zeki EYMÜR