Home  Contents

GTK+ events and signals

في هذا الجزء من دورة البرمجه في ال GTK+  سوف نتكلم عن نظام الاحداث في مكتبة ال GTK+ .

مكتبة ال GTk+  هي مكتبة حدثيه تنتظر وقوع الاحداث وهو مصطلح برمجي باسم (event driven)  . كل تطبيقات الواجه الرسوميه هي تطبيقات حدثيه . التطبيق يقوم ببدأ دواره اساسيه main loop , التي باستمرار تقوم بتفقد الاحداث الجديده . اذا لم يكن هناك اي حدث , فان التطبيق ينتظر ولا يفعل ايش شيئ. في مكتبة ال GTK+  الحدث (event) هو رساله من الخادم x  (x server) . عندما يصل الحدث الي widget معين كالنافذه مثلا , يمكن ان يتصرف الwidget ببعث اشاره. مبرمج ال GTK+  يستطيع ان يربط اشاره محدده بداله عكسيه . وهي داله تقوم بالتعامل والرد علي هذه الاشاره . وبدونها لا نستطيع التعامل مع الاشاره .



#include <gtk/gtk.h>

void button_clicked(GtkWidget *widget, gpointer data)
{
  g_print("clicked\n");
}

int main( int argc, char *argv[])
{
  GtkWidget *window;
  GtkWidget *fixed;
  GtkWidget *button;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title(GTK_WINDOW(window), "GtkButton");
  gtk_window_set_default_size(GTK_WINDOW(window), 230, 150);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);

  fixed = gtk_fixed_new();
  gtk_container_add(GTK_CONTAINER(window), fixed);

  button = gtk_button_new_with_label("Click");
  gtk_fixed_put(GTK_FIXED(fixed), button, 50, 50);
  gtk_widget_set_size_request(button, 80, 35);

  g_signal_connect(G_OBJECT(button), "clicked", 
      G_CALLBACK(button_clicked), NULL);

  g_signal_connect(G_OBJECT(window), "destroy", 
      G_CALLBACK(gtk_main_quit), NULL);

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}

في تطبيقنا لدينا اشارتين . اشرة النقر (clicked)  واشارة التدمير (destroy) .

 g_signal_connect(G_OBJECT(button), "clicked", 
     G_CALLBACK(button_clicked), NULL);

نقوم باستخدام الداله g_signal_connect() لكي نقوم بالربط بين الاشاره والداله العكسيه button_clicked() .

 void button_clicked(GtkWidget *widget, gpointer data)
 {
   g_print("clicked\n");
 }

هذه الداله العكسيه سوف تقوم بطبع كلمة "clicked"  علي شاشة الكونسول . ال parameter الاول في الداله العكسيه هو الكائن الذي يبعث الاشاره . في حالتنا هذا الكائن هو زرار click . الparameter الثاني اختياري . يمكننا ان نرسل بعض البيانات الي الداله العكسيه .  في حالتنا لم نقوم بارسال اي بيانات . قمنا باعطائه قيمة NULL  في الداله g_signal_connect() .



 g_signal_connect(G_OBJECT(window), "destroy", 
      G_CALLBACK(gtk_main_quit), NULL);

اذا قمنا بضغط العلامه x  الموجوده في الجزء اليميني الاعلي من النافذه او قمنا بضغط المفتاحين Alt+F4 , يتم ارسال اشارة تدمير (destroy) . ثم نستخدم الداله gtk_main_quit()  كداله عكسيه ونستدعيها , والتي تنهي التطبيق بشكل كامل وصحيح .

Moving window

في المثال القادم نعرض كيف يمكن ان تقوم بالرد علي اشارة تحريك النافذه.

#include <gtk/gtk.h>

void frame_callback(GtkWindow *window, 
      GdkEvent *event, gpointer data)
{
   int x, y;
   char buf[10];
   x = event->configure.x;
   y = event->configure.y;
   sprintf(buf, "%d, %d", x, y);
   gtk_window_set_title(window, buf);
}


int main(int argc, char *argv[])
{
  GtkWidget *window;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 230, 150);
  gtk_window_set_title(GTK_WINDOW(window), "Simple");
  gtk_widget_add_events(GTK_WIDGET(window), GDK_CONFIGURE);

  g_signal_connect_swapped(G_OBJECT(window), "destroy",
        G_CALLBACK(gtk_main_quit), G_OBJECT(window));

  g_signal_connect(G_OBJECT(window), "configure-event",
        G_CALLBACK(frame_callback), NULL);

  gtk_widget_show(window);
  gtk_main();

  return 0;
}

في المثال , نقوم بعرض الموقع الحالي للجزء الاعلي اليساري من النافذه في شريط العنوان title bar .

 gtk_widget_add_events(GTK_WIDGET(window), GDK_CONFIGURE);

قناع الحدث الخاص بال widget  يحدد , اي نوع من الاحداث يستقبلها widget  معين . بعد الاحداث قد تم تهيئتها بالفعل من قبل , بعض الاحداث الاهري يجب علينا ان نضعها في قناع الاحداث (event mask) . الداله gtk_widget_add_event()  تضع الحدث GTK_CONFIGURE  الي القناع . هذا النوع من الاحداث التي قمنا باضافته يحسب كل المساحات , المواقع , والمكان في الكومه stack order من الاحداث .

 g_signal_connect(G_OBJECT(window), "configure-event",
     G_CALLBACK(frame_callback), NULL);

هذا الحدث يبعث في حالة اذا صدر حدث تغير حجم او مكان او ترتيب في الكومه .

 void frame_callback(GtkWindow *window, 
     GdkEvent *event, gpointer data)
 {
   int x, y;
   char buf[10];
   x = event->configure.x;
   y = event->configure.y;
   sprintf(buf, "%d, %d", x, y);
   gtk_window_set_title(window, buf);
 }

الداله العكسيه لديها ثلاث parameters . الكائن الذي بعث الاشاره , ونوع بيانات GdkEven  وبيانات اختياريه . ونقوم بحساب مكان النافذه بالمتغيرات x , y ونقوم بوضعها في شريط العناون .


Move event

Figure: Move event



The enter signal

في المثال القادم , سوف نعرض كيف يمكننا ان نريد علي اشارة دخول (enter signal) . هذه الاشاره تبعث عندما ندخل في مساحة widget معينه بمؤشر الفأره .

#include <gtk/gtk.h>


void enter_button(GtkWidget *widget, gpointer data) 
{ 
  GdkColor color;
  color.red = 27000;
  color.green = 30325;
  color.blue = 34181;
  gtk_widget_modify_bg(widget, GTK_STATE_PRELIGHT, &color);
}


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

  GtkWidget *window;
  GtkWidget *fixed;
  GtkWidget *button;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 230, 150);
  gtk_window_set_title(GTK_WINDOW(window), "enter signal");

  fixed = gtk_fixed_new();
  gtk_container_add(GTK_CONTAINER(window), fixed);

  button = gtk_button_new_with_label("Button");
  gtk_widget_set_size_request(button, 80, 35);
  gtk_fixed_put(GTK_FIXED(fixed), button, 50, 50);

  g_signal_connect(G_OBJECT(button), "enter", 
      G_CALLBACK(enter_button), NULL);

  g_signal_connect_swapped(G_OBJECT(window), "destroy",
      G_CALLBACK(gtk_main_quit), NULL);

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}

هنا نقوم بتغير لون خلفية زرار بمجرد ان يقوم مؤشر الفأره بالمرور عليها .

 g_signal_connect(G_OBJECT(button), "enter", 
    G_CALLBACK(enter_button), NULL);

نقوم باستدعاء الداله العكسيه عندما يتم بعث اشارة دخول.

 GdkColor color;
 color.red = 27000;
 color.green = 30325;
 color.blue = 34181;
 gtk_widget_modify_bg(widget, GTK_STATE_PRELIGHT, &color);

داخل الداله العكسيه قمنا بتغير لون الخلفيه بواسطة استدعاء الداله gtk_widget_modify_bg() بداخل الداله العكسيه .

Disconnecting a callback

يمكننا ان نفصل اتصال دالة عكسيه من اشاره معينه . هذا الكود يشرح هذه العمليه .

#include <gtk/gtk.h>


int handler_id;

void button_clicked(GtkWidget *widget, gpointer data) 
{ 
  g_print("clicked\n");
}

void toogle_signal(GtkWidget *widget, gpointer window)
{
  if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) {
     handler_id = g_signal_connect(G_OBJECT(window), "clicked", 
           G_CALLBACK(button_clicked), NULL);
  } else {
     g_signal_handler_disconnect(window, handler_id);
  }
}


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

  GtkWidget *window;
  GtkWidget *fixed;
  GtkWidget *button;
  GtkWidget *check;


  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 250, 150);
  gtk_window_set_title(GTK_WINDOW(window), "Disconnect");

  fixed = gtk_fixed_new();
  gtk_container_add(GTK_CONTAINER(window), fixed);

  button = gtk_button_new_with_label("Click");
  gtk_widget_set_size_request(button, 80, 30);
  gtk_fixed_put(GTK_FIXED(fixed), button, 30, 50);

  check = gtk_check_button_new_with_label("Connect");
  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), TRUE);
  gtk_fixed_put(GTK_FIXED(fixed), check, 130, 50);

  handler_id = g_signal_connect(G_OBJECT(button), "clicked", 
        G_CALLBACK(button_clicked), NULL);

  g_signal_connect(G_OBJECT(check), "clicked",
        G_CALLBACK(toogle_signal), (gpointer) button);

  g_signal_connect_swapped(G_OBJECT(window), "destroy",
        G_CALLBACK(gtk_main_quit), NULL);

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}

في مثالنا ,لدنيا صندوق تحقق (check box) , علي حسب علامة الصندوق يفصل او يربط صنددوق التحقق الداله العكسيه من الربط او الاتصال بالاشاره.

 handler_id = g_signal_connect(G_OBJECT(button), "clicked", 
     G_CALLBACK(button_clicked), NULL);

الداله g_signal_connect()  ترجع قيمه عدديه , والتي تميز الداله العكسيه التي تم ربطها.

 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) {
    handler_id = g_signal_connect(G_OBJECT(window), "clicked", 
          G_CALLBACK(button_clicked), NULL);
 } else {
    g_signal_handler_disconnect(window, handler_id);
 }

هذا الكود يقوم بتحديد حالة صندوق التحقق . تقوم بربط الداله العكسيه بالاشاره اذا كان الصندوق معلم . غير ذلك تقوم بفصل الداله العكسيه من الاشاره.


Disconnect

Figure: Disconnect



Drag and Drop example

في مثالنا القادم , نعرض خاصيه مثيره للاهتمام. سوف نقوم بعرض كل النوافذ التي لا تحتوي علي عدود ونتعلم كيف يمكننا ان نقوم بسحب ووضع مثل هذه النافذه.

#include <gtk/gtk.h>

gboolean on_button_press (GtkWidget* widget,
  GdkEventButton * event, GdkWindowEdge edge)
{
  if (event->type == GDK_BUTTON_PRESS)
  {
    if (event->button == 1) {
      gtk_window_begin_move_drag(GTK_WINDOW(gtk_widget_get_toplevel(widget)),
          event->button,
          event->x_root,
          event->y_root,
          event->time);
    }
  }

  return FALSE;
}


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

  GtkWidget *window;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 230, 150);
  gtk_window_set_title(GTK_WINDOW(window), "Drag & drop");
  gtk_window_set_decorated(GTK_WINDOW (window), FALSE);
  gtk_widget_add_events(window, GDK_BUTTON_PRESS_MASK);

  g_signal_connect(G_OBJECT(window), "button-press-event",
      G_CALLBACK(on_button_press), NULL);

  g_signal_connect_swapped(G_OBJECT(window), "destroy",
        G_CALLBACK(gtk_main_quit), G_OBJECT(window));

  gtk_widget_show(window);

  gtk_main();

  return 0;
}

هذا المثال يشرح سحب ووضع نافذه بغير حدود .

 gtk_window_set_decorated(GTK_WINDOW (window), FALSE);

نقوم بنزع التنسيقات من النافذه . هذا يعني ان هذه النافذه لن تحتوي علي شريط عنوان او حدود .

  g_signal_connect(G_OBJECT(window), "button-press-event",
      G_CALLBACK(on_button_press), NULL);

نقوم بربط النافذه بالحدث button-press-event  بالداله العكسيه .

gboolean on_button_press (GtkWidget* widget,
  GdkEventButton * event, GdkWindowEdge edge)
{
  if (event->type == GDK_BUTTON_PRESS)
  {
    if (event->button == 1) {
      gtk_window_begin_move_drag(GTK_WINDOW(gtk_widget_get_toplevel(widget)),
          event->button,
          event->x_root,
          event->y_root,
          event->time);
    }
  }

  return FALSE;
}

بداخل الداله العكسيه on_button_press نقوم بتأدية وظيفة السحب والوضع . نقوم بتفقد اذا ماكان زرار الفأرة الشمال قم تم نقره .ثم نستدعي  الداله gtk_window_being_move_drag() .

A timer example

في المثال القادم نقوم بتوضيح مثال علي مؤقت . نقوم باستخدام المؤقتات عندما يكون لدينا وظيفة نقوم بعملها بشكل متكرر . يمكنها ان تكون ساعه عداد تنازلي , او حتي مؤثرات بصريه .

#include <cairo.h>
#include <gtk/gtk.h>
#include <time.h>


static char buffer[256];


static gboolean
on_expose_event(GtkWidget *widget,
    GdkEventExpose *event,
    gpointer data)
{
  cairo_t *cr;

  cr = gdk_cairo_create(widget->window);

  cairo_move_to(cr, 30, 30);
  cairo_show_text(cr, buffer);

  cairo_destroy(cr);

  return FALSE;
}

static gboolean
time_handler(GtkWidget *widget)
{
  if (widget->window == NULL) return FALSE;

  time_t curtime;
  struct tm *loctime;

  curtime = time(NULL);
  loctime = localtime(&curtime);
  strftime(buffer, 256, "%T", loctime);

  gtk_widget_queue_draw(widget);
  return TRUE;
}

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

  GtkWidget *window;
  GtkWidget *darea;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

  darea = gtk_drawing_area_new();
  gtk_container_add(GTK_CONTAINER (window), darea);

  g_signal_connect(darea, "expose-event",
      G_CALLBACK(on_expose_event), NULL);
  g_signal_connect(window, "destroy",
      G_CALLBACK(gtk_main_quit), NULL);

  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 170, 100);

  gtk_window_set_title(GTK_WINDOW(window), "timer");
  g_timeout_add(1000, (GSourceFunc) time_handler, (gpointer) window);
  gtk_widget_show_all(window);
  time_handler(window);

  gtk_main();

  return 0;
}

نقوم بعرض الوقت الحالي , ونستخدم المكتبة االثنائية البعد cairo 2d .

 g_signal_connect(darea, "expose-event",
     G_CALLBACK(on_expose_event), NULL);

سوف نقوم برسم الوقت بداخل الداله العكسيه on_expose_event() . هذه الداله العكسيه مربوطه بالاشارة expose-event . هذه الاشاره تبعث في حالة ان النافذه سوف يتم اعادة رسمها .

 g_timeout_add(1000, (GSourceFunc) time_handler, (gpointer) window);

هذه الداله تقوم بتسجيل المؤقت . الداله time_handler()  تستدعي بشكل متكرر بنمط ثابت ومعتاد . في حالتنا في كل ثانيه . يتم استدعاء الدالة الي ان ترجع قيمة FALSE .

 time_handler(window);

هذه تقوم باستدعاء دالة المؤقت فورا , والا سوف يكون لدينا تأخير بثانيه .

 cairo_t *cr;

 cr = gdk_cairo_create(widget->window);

 cairo_move_to(cr, 30, 30);
 cairo_show_text(cr, buffer);

 cairo_destroy(cr);

هذا الكود يقوم برسم الوقت علي النافذه . لمزيد من المعلومات حول المكتبة الثنائيه للرسم Cairo 2D  , انظر دورة Zetcode الخاصه بها هنا.



 if (widget->window == NULL) return FALSE;

عندما يتم تدمير النافذه , قد يتم في نفس الوقت استدعاء دالة الموقت . هذا السطر سوف يمنع ان تقوم دالة الموقت بالعمل علي widget  قد تم تدميره بالفعل .

 time_t curtime;
 struct tm *loctime;

 curtime = time(NULL);
 loctime = localtime(&curtime);
 strftime(buffer, 256, "%T", loctime);

هذا السطر يقوم بتحديد الوقت المحلي الحالي.

 gtk_widget_queue_draw(widget);

هذا سوف يقوم بابطال عمل النافذه , الامر الذي سوف يبعث باشارة expose-event .



Home ‡ Contents ‡ Top of Page