مكتبة GTK وبرنامج glade كتاب لينكس الشامل >>

مكتبة GTK وبرنامج glade

8.3.1 مقدمة

تعتبر مكتبة gtk أي Gimp tool kit من أقوى المكتبات لعمل برامج ذات واجهة رسومية وتفاعلية. نعم هي التي صمم gimp و كل برامج غنوم عليها وهي سهلة ومتوفرة لأكثر من نظام تشغيل منها لينكس و MacOS و ويندوز ولكن اصدار لينكس هو الأشهر والأنشط وهو الإصدار المركزي لها ، أما اصدار ويندوز فهو أقل نشاطاً وتوفراً ودعماً لكنه موجود. وبدأً من الإصدار الثاني كانت هذه المكتبة تدعم الكثير من اللغات ومنها اللغة العربية وتوفر الكثير من المزايا التي لا ينافسها فيها إلا مكتبة QT الخاصة ب KDE والفرق الأهم بين gtk و QT هو الرخصة فالأولى مرخصة وفقاً ل lgpl أي يمكنك استعمالها في عمل برامج تجارية أو مجانية أما QT فهي مجانية إذا عملت برامج مفتوحة و حرة وتجارية وعليك شراء رخصة إذا عملت برامج تجارية.

تتوفر مكتبة gtk في أكثر من لغة أهمها C/C++ وهناك perl و python و php بالنسبة ل php حتى الإصدار الرابع توفر gtk1 التي لا تدعم العربية وعليك الحصول على php 5 التي توفر gtk2 ويمكن الحصول عليها على كل CVS وتركيبها من المصدر تجد المزيد من التفاصيل على موقع www.gtk.org/bindings سنشرح كل شيء بلغة سي ثم نتحدث عن اللغات الأخرى ولا داعي أن أذكر أن perl و غيرها أسهل مليون مرة من لغة سي لذا يمكنك أن تبدأ بلغة perl أو python أو غيرهما ، أعرف أن لغة سي تشكل صدمة للجدد ولكنها اللغة المركزية ومنها يحول إلى اللغات الأخرى.

ومكتبة gtk هي تركيب من أكثر من مكتبة فهي بنيت فوق gdk أي gimp drawing kit للتعامل مع الرسم ومع مكتبة pango بدءاً من الإصدار الثاني ل gtk من أجل العالمية ودعم اللغات المختلفة مثل لغتنا العربية لذا عليك التعامل مع تلك المكتبات بين الحين والآخر

8.3.2 تعلم gtk

المرجع الأساسي يأتي مع حزمة تطوير gtk ويتم تركيبه في مجلد /usr/share/gtk-doc/ حيث تجد مجلد باسم html يحتوي مجلدات فرعية عن gtk وغيرها وهو مكان يحتوي على معلومات مفصلة تفصيلاً مملاً يكون مزعجا في البداية لكثرة التفصيل والمعلومات الدقيقة والمتشابكة ولكنه سيكون المكان الأفضل كمرجع ، أهم صفحة فيه هي Object Hierarchy التي توضح الخريطة الوراثية لكائنات gtk! في البداية ربما تحب أن تلقي نظرة على tutotial الموجود على موقع www.gtk.org/tutorial وهناك الكثير من الوثائق على موقع http://developer.gnome.org/doc من أهم الوثائق التي تهمنا كعرب وهي http://developer.gnome.org/dotplan/proting التي تتحدث عن كيفية تحويل البرامج من gtk1 التي لا تدعم العربية إلى gtk2 التي تدعم العربية ويمكنك تشغيل برنامج gtk-demo

قد تعتبر هذا غريباً بعض الشيء ولكن يمكنك تعلم gtk من ملفات headers أو من الملف المصدري ل gtk وطبعا هذا هو المكان الآخير ، ويمكن تعلمها من قراءة الملفات المصدرية للكثير من البرامج المفتوحة ومن الملفات المولدة بواسطة glade

8.3.3 كيف تكتب برنامج على gtk ؟

يمكنك استعمال برنامج مثل glade و glade2 لتوليد البرنامج وتصميمه باللغة التي تريد ،أي أنهما يكتبان البرنامج عنك ، ويقومان بترتيبه بحث يتم استعمال أدوات مثل auto-config ويتم وضع ملفات المصدر في مجلد src والملف الوحيد الذي عليك التعديل فيه هو ملف ال callbacks في حالة لغة C هو callbacks.c الذي يحتوي على وظائف تتبع الأحداث للتفاعل مع المستخدم ولتصنيف البرنامج كل ما عليك هو تنفيذ النص البرمجي autogen.sh (على ما أذكر!!) وأكثر من ذلك يمكنك تحميل ملف ال glade وتشغيله مباشرة دون أن تصنفه مثلاً هذا الكود بلغة السي يقوم بتحميل ملف اسمه filename.glade بعد تصنيف البرنامج (انظر الطريقة أدناه) قم بتصميم ملف على glade وخزنه باسم filename.glade ثم شغل برنامجنا الآن أغلقه وعدل التصميم ثم شغله مرة أخرى وانظر النتيجة

#include <gtk/gtk.h>
#include <glade/glade.h>

void some_handler(GtkWidget *widget) {
    /* a handler referenced by the glade file.  Must not be static
     * so that it appears in the global symbol table. */
}

int main(int argc, char **argv) {
    GladeXML *xml;
    GtkWidget *widget;

    gtk_init(&argc, &argv);
    xml = glade_xml_new("filename.glade", NULL, NULL);

    /* get a widget (useful if you want to change something) */
    widget = glade_xml_get_widget(xml, "widgetname");

    /* connect signal handlers */
    glade_xml_signal_autoconnect(xml);

    gtk_main();
    return 0;
}

ولكن تصميم برنامج مباشرة ليس صعباً ولن تكون بحاجة لتحديد حجم وموقع كل زر و كل جسم بالبيكسل ورسمها ، في مكتبة gtk نستخدم نظام تكديس packing يقوم على وضع الأجسام في حاويات وترتيبها وكأنها في جدول ونحدد الحد الأدنى لحجمها إذا أردنا ونحدد فيما إذا كنا نريد أن تكبر وتصغر عند تحجيمها أم تبقى كما هي ويتغير الهامش وهكذا نكون حصلنا على التصميم الذي نريد

8.3.4 تصنيف البرنامج compiling

لتصنيف برنامج مكتوب على gtk عليك تحديد أسماء المجلدات التي تحتوي ملفات headers ذات الإمتداد h وأيضاً ربط برنامجك بمكتبة gtk أي ملفات ذات الإمتداد a اذا كنت تستعمل gcc فإليك الخيارات التي يجب أن تمررها له لمكتبة gtk2

-I/usr/include/gtk-2.0 -I/usr/lib/gtk-2.0/include -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include -I/usr/include/pango-1.0 -I/usr/X11R6/include -I/usr/include/freetype2 -I/usr/include/atk-1.0

-L/usr/lib -L/usr/X11R6/lib -lgtk-x11-2.0 -lgdk-x11-2.0 -lXi -lgdk_pixbuf-2.0 -lm -lpangox -lpangoxft -lXft -lXrender -lXext -lX11 -lfreetype -lpango -latk -lgobject-2.0 -lgmodule-2.0 -ldl -lglib-2.0

ويمكن أن تختلف المجلدات من توزيعة لأخرى وأيضا تختلف من اصدار gtk إلى آخر مثلاً في gtk1 هناك القليل من المكتبات الإضافية فهي لا تعتمد على pango وغيرها مما يجعل تصنيف البرمج مزعجاً ، ولكن لا تظن أنك يجب أن تعرف شيء عن هذه الطلاسم كل ما عليك هو استدعاء الأداة pkg-config وأخذ ناتج تنفيذها ، التي حلت مكان أداة gtk-config القديمة التي كانت تستعمل في gtk1

# the old method الطريقة القديمة
bash$ gcc myfile.c -o myfile `gtk-config --cflags` `gtk-config --libs`
# the new method الطريقة الحديثة
bash$ gcc myfile.c -o myfile  `pkg-config --cflags --libs gtk+-2.0`

8.3.5 البداية

برنامج gtk العادي يبدو مثل هذا

/* البداية التقليدية */
#include <gtk/gtk.h>

int main(int argc, char *argv[])
{
/* نعرف متغيرات تمثل النافذة والأزرار */
GtkWidget *win, *btn;
/* نقوم بالعمليات الإستهلالية */
gtk_init(&argc, &argv);

/* نبدأ برسم النوافذ والأزرار */
/* نحجز نافذة جديدة */
win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
/* نحجز زر جديد */
btn = gtk_button_new_with_label("Hello World !!");
/* نضع الزر في النافذة */
gtk_container_add (GTK_CONTAINER (win), btn);
/* نحدد حجم النافذة الإبتدائي */
gtk_window_set_default_size(GTK_WINDOW(win), 200, 200);

/* نحدد وظائف التفاعل وتتبع الأحداث */
/* مثلاُ نربط حدث إغلاق النافذة بالخروج */
g_signal_connect(G_OBJECT(win), "destroy", G_CALLBACK(gtk_main_quit), NULL);

/* نظهر النافذة ومحتوياتها */
gtk_widget_show_all(win);

/* النهاية التقليدية */
/*
	تبقي البرنامج عاملاً وتتبع الأحداث
	إلى أن يتم إغلاق البرنامج
*/
gtk_main();
return 0;
}

هذا البرنامج عبارة عن نافذة تحتوي زر اسمه btn مكتوب عليه Hello World عند النقر على زر الإغلاق من مدير النوافذ يتم استدعاء الوظيفة gtk_main_quit الموجودة ضمن مكتبة gtk والتي تقوم بإغلاق البرنامج والخروج ، أما الزر btn فإن النقر عليه لا يقوم بشيء لربط وظيفة معينة به
g_signal_connect(G_OBJECT(win), "clicked", G_CALLBACK(gtk_main_quit), NULL); ويمكن وضع أي وظيفة للقيام بها كرد على ضغط الزر وتكون صيغتها أنها تأخذ المعامل الأول هو مؤشر على الزر نفسه ويكون من نوع GtkWidget* والأخير من نوع gpointer * حيث يكون مؤشراً على بيانات اضافية يتم تمريرها له بدلاً من NULL المعامل الأخير في g_signal_connect ويمكن أن يأخذ معاملات أخرى بينهما قد تمثل مكان حدوث النقر أو النص الذي أضيف ... هذا يعتمد على صنف الكائن وتجدها مفصلة في مرجع gtk الذي تحدثنا عنه.

في الإصدارات السابقة من gtk ماقبل gtk2 استعمل gtk_signal_connect مكان g_signal_connect كما في المثالين

gtk_signal_connect(GTK_OBJECT(win),"destroy", GTK_SIGNAL_FUNC(gtk_main_quit),NULL);
gtk_signal_connect(GTK_OBJECT(win),"clicked", win->destroy(),NULL);
ولكن يجب أن لا تستعملها في البرامج الحديثة لأنها موجودة للتوافق مع gtk1 فقط.

إذا كنت تريد استدعاء التحديث أثناء قيامك بعمليات معقدة وتأخذ وقتاً حتى لا يبدو برنامج متوقف عن الإستجابة ضع هذا السطر وسط تلك الحسابات

	/* ... */
 while(gtk_events_pendings()) gtk_main_iteration();
	/* ... */

8.3.6 أسماء الوظائف و الثوابت والصنوف الأساسية

أسماء الوظائف في gtk هي عبارة عن سلسلة تفصل بين مقاطعها شرطة تحتية "_" تبدأ باسم المكتبة مثل gtk و gdk ... إلخ ثم يأتي الصنف -إن وجد- مثل button أو window ثم الوظيفة مثل new فتصبح مثلاً gtk_button_new وأول المعاملات التي يأخذها يكون في الغالب مؤشر على الكائن الذي نريد تطبيق الوظيفة عليه مثلاً gtk_window_set_title((GtkWindow*)win,"The brand new title"); لاحظ أن وظائف حجز كائن جديد تعيد مؤشراً لكائن من نوع GtkWidget وتأخذ معظم وظائف تعديل خصائصها هذا النوع من المعاملات أي مؤشر إلى نوع GtkWidget أو نوع الكائن الذي تعود له هذه الصفة كما لاحظنا gtk_window_set_title تأخذ مؤشر إلى GtkWindow . لقد كانت وظائف الكائنات في الإصدارة الأولى تأخذ معاملاتها من نفس نوعها مثل GtkButton للأزرار وليس من نوع GtkWidget مما يوجب عليك القيام دائماً بالتحويل إما بالطريقة التقليدية (GtkButton*)btn1 أو بالإختصار/الوظيفة المناسبة GTK_BUTTON(btn1) ولكن في الإصدار الثانية من Gtk فقط عليك التحويل في حالات نادرة لها علاقة بالنوافذ والحاويات أما أغلب الوظائف تأخذ GtkWidget. على أي حال إذا لم تقم بالتحويل فإن أكثر ما يمكن أن تحصل عليه هو تنبيه warning ولا يشكل خطأ.

أما أنواع المتغيرات وأسماء الصنوف فهي تكون بأحرف استهلالية كبيرة ودون فاصل ، اسم المكتبة ثم النوع مثل GtkButton وكل تعاملاتنا تكون بمؤشرات ، أضف إلى ذلك ما قلناه عن أننا في الغالب سنستعمل فقط GtkWidget الثوابت والإختصارات تكون بأحرف كبيرة ويفصل بين مقاطعها "_" مثل GTK_WINDOW_TOPLEVEL

8.3.7 تصميم وترتيب الواجهة

بعد حجز نافذة يمكنك تحديد حجمها gtk_window_set_default_size(GTK_WINDOW(win), 600, 400); وعنوانها gtk_window_set_title(GTK_WINDOW(win),"My Title"); ثم نقسم النافذة إلى صفوف ونضع في الصف الأول القوائم ثم في الآخر سطر الأدوات والذي يليه ربما نقسمه إلى عدة أعمدة نضع فيها مثلاً شريطاً جانبياً قابلاً للتحجيم مع مساحة للنص وصف جديد يحتوي بعض الأزرار وسطر آخر يحتوي سطر الحالة إذا كنت تفكر لتصميم برنامجك بهذه الطريقة فتطبيق ذلك لا يحتاج أي احداثيات وطلاسم كل ما عليك هو استعمال GtkVBox و GtkHBox حيث V و H ترمز للعامودي والأفقي على الترتيب

vbox1=gtk_vbox_new(FALSE,3); تقوم بعمل صندوق لوضع الصفوف فيه بحيث لا تكون هذه الصناديق متجانسة ، إذا وضعنا TRUE مكان FALSE يصبح متجانس أي كل الصفوف بنفس الحجم والرقم ثلاثة يشير إلى عرض الخط الفاصل. بعد ذلك نضيفه إلى النافذة gtk_container_add(GTK_CONTIANER(win),vbox1); ثم نبدأ بإضافة الكائنات (الصفوف) في مثالنا نريد سطر القوائم جديد menubar1 = gtk_menu_bar_new(); ثم نضيفه على شكل صف في الصندوق gtk_box_pack_start(GTK_BOX(vbox1),menubar1, FALSE, FALSE,0); حيث ال FALSE هي أننا لا نريده أن يكبر عند تكبير النافذة والثانية أنه عند تصغيره يتم إخفاء جزء منه وليس تصغير حجمه على الترتيب. أما الصفر فهي الهامش. الوظيفة gtk_box_pack_start تضيف الكائنات من البداية إلى النهاية في مثالنا من فوق لتحت إذا أردت أن تعكس العملية استعمل gtk_box_pack_end بنفس المعاملات. ويمكنك ايضاً اضافتها باستعمال gtk_box_pack_start_defaults(GTK_BOX(vbox1),menubar1); أو حتى الطريقة الأولى لإضافة العناصر وهي gtk_container_add(GTK_BOX(vbox1),menubar1); ولأنهما تأخذان معاملين فقط فإنك لن تستطيع تحديد طريقة التكديس وسيتم استخدام التلقائية.

لعمل قائمة ملف مثلاً نحجزه هكذا filemenu=gtk_menu_item_new_with_label("File"); ثم نضيفه إلى سطر القوائم بالطريقة المعتادة gtk_container_add(GTK_CONTIANER(menubar1),filemenu); لنضيف خيار جديد إلى قائمة ملف نحجر newmenu=gtk_menu_item_new_with_label("new"); ثم نضيف gtk_container_add(GTK_CONTIANER(filemenu),newmenu); ولنفرض أننا نريد قائمة جديد أن تتفرع إلى قائمة بها اختياران مثلاً نافذة جديدة و ملف جديد نحجزها ب newwinmenu=gtk_menu_item_new_with_label("new window"); و newfilemenu=gtk_menu_item_new_with_label("new file"); ونضيفها لقائمة "جديد" ب gtk_container_add(GTK_CONTIANER(newmenu),newwinmenu); و gtk_container_add(GTK_CONTIANER(newmenu),newfilemenu);

في الصف التالي نريد إضافة عمودين الثاني مكان لتحرير النص والأول فارغ ربما للاستخدامات المستقبلية سنضع فيه نص ثابت مكتوب عليه قريباً soon نقوم بحجز صندوق يحتويها ب hbox1=gtk_hbox_new(FALSE,3); ونضيفه إلى الصفوف gtk_box_pack_start(GTK_BOX(vbox1),hbox1, TRUE, FALSE,0); الآن لكي نضيف العامود الأول وهو عبارة عن نص من سطر واحد نحجزه أولاً entry1=gtk_entry_new(); ثم نضيفه gtk_box_pack_start(GTK_BOX(hbox1),entry1, TRUE, FALSE,0); ونكتب فيه كلمة soon gtk_entry_set_text("soon!"); ولأننا لا نريد أن تكون هذه للإدخال بل نص ثابت gtk_editable_set_editable(entry1,FALSE); ثم نضيف العامود الثاني الذي يحتوي على مكان لتحرير نص متعدد الأسطر text1=gtk_text_view_new(); ثم اضافته بعد حجزه gtk_box_pack_start(GTK_BOX(hbox1),text1, TRUE, FALSE,0); وهو ليس لعرض النص كما قد يخطر ببالك فقط بل يمكن استخدامه لتحرير النص وذلك بتغير الخاصية "editble" إلى TRUE وذلك باستدعاء gtk_text_view_set_editable(text1,TRUE); وأيضاً يمكن استخدامه ليس فقط لتحرير النصوص العادية بل وأيضاً التي تحتوي على ألوان وخصائص مختلفة بل وأيضا صور مدرجة وروابط و... الكثير من الأشياء التي يمكنك أن تفكر فيها.

ولأن منطقة تحرير النص هي الكائن الأساسي نقوم بإعطائها حجم كبير نسبياً بل ونجعله الحد الأدنى للحجم (أي يمكن أن يزيد عنه ولكن لا يجوز أن يقل عنه ) وذلك باستدعاء gtk_widget_set_size_request(text1,400,400); ولا تستخدم الطريقة القديمة gtk_widget_set_usize(text1,400,400); فهي موجودة فقط للتوافق مع الإصدارات القديمة فقط.

نتابع إضافة سطر (صندوق أفقي) يحتوي بعض الأزرار hbox2=gtk_hbox_new(TRUE,3); نحجز زرين بـ btn1=gtk_button_new_with_label("OK"); و btn2=gtk_button_new_with_label("Clear"); ثم نضيفهما بـ gtk_box_pack_start(GTK_BOX(hbox2),btn1, FALSE, FALSE,0); و gtk_box_pack_start(GTK_BOX(hbox2),btn2, FALSE, FALSE,0);

وفي النهاية نضيف الصندوق الكبير الذي يحتوي كل شيء vbox1 إلى النافذة gtk_container_add(GTK_CONTIANER(win),vbox1); ونظهر النافذة وما فيها gtk_widget_show_all(win);

tipتلميح

يفترض أن تحدد أنك تريد اظهار الكائن ب gtk_widget_show(my_widget_name); لكل الكائنات في النافذة ولكن هذا سيكون مملاً الأجدى من ذلك أن تستعمل gtk_widget_show_all(my_window_name); لإظهار النافذة وكل شيء داخلها

سنحصل من البرنامج على ما نريد غير أن الحاجز الذي يفصل بين منطقة تحرير النص وصندوق soon لا يمكن تحريكه فإذا أردناه أن يتحرك نستبدل الصندوق الأفقي hbox1 ب hpaned وذلك باستعمال hpaned1=gtk_hpaned_new(); وهو كائن له ولدين يفصل بينهما حاجز متحرك يمكن جره ويكون ذلك بوضع الكود التالي

/* ... */
hpaned=gtk_hpaned_new();
gtk_box_pack_start(GTK_BOX(vbox1),hpaned1, TRUE, FALSE,0);
gtk_container_set_border_width(GTK_CONTAINER(hpaned1),3);
entry1=gtk_entry_new();
gtk_paned_pack1(GTK_PANED(hpaned1),entry1, TRUE,TRUE);
gtk_entry_set_text("soon!");
gtk_editable_set_editable(entry1,FALSE);
text1=gtk_text_view_new();
gtk_paned_pack2(GTK_PANED(hpaned1),text1, TRUE,TRUE);
gtk_text_view_set_editable(text1,TRUE);
/* ... */
مكان الكود القديم التالي
/* ... */
hbox1=gtk_hbox_new(FALSE,3);
gtk_box_pack_start(GTK_BOX(vbox1),hbox1, TRUE, FALSE,0);
entry1=gtk_entry_new();
gtk_box_pack_start(GTK_BOX(hbox1),entry1, TRUE, FALSE,0);
gtk_entry_set_text("soon!");
gtk_editable_set_editable(entry1,FALSE);
text1=gtk_text_view_new();
gtk_box_pack_start(GTK_BOX(hbox1),text1, TRUE, FALSE,0);
gtk_text_view_set_editable(text1,TRUE);
/* ... */
لاحظ أننا استعملنا gtk_paned_pack1 لإضافة الإين الأول و gtk_paned_pack2 الأول سيظهر على اليسار والثاني على اليمين إذا كانت اللغة الإنجليزية هي لغة النظام أما إذا كانت العربية فيكون الأول على اليمين .
tipتلميح

بدء من الإصدار الثاني ل gtk فهي تدعم العربية بشكل ممتاز بسبب pango لهذا ليس عليك أن تقلق أو تغير في حساباتك اكتب البرنامج وكأنه إنجليزية وعند اختيار العربية سترى برنامجك وقد أصبحت قوائمه من اليمين وتغير ترتيب الأشياء لتصبح من اليمين لليسار لا داعي لإعادة تصميم البرنامج. وأكثر من ذلك فهي تدعم اللغات التي تكتب من فوق إلى تحت ومن اليمين إلى اليسار و التي تكتب من فوق إلى تحت ومن اليسار إلى اليمين

يمكن تغيير خصائص أي كان من خلال الوظيفة g_object_set_property ومعرفة قيمتها بالوظيفة g_object_get_property هاتان الوظيفتان تأخذان 3 معاملات هي مؤشر للكائن من نوع GObject وسلسلة نصية اسم الخاصية مثلاً "editable" أو "font" وأخيراً مؤشر من نوع GValue وهو يمثل القيمة.
/* I'm not sure if it works or correct */
GValue v;
g_value_init(&v,G_TYPE_STRING);
g_value_set_instance(&v,"monospace 10");
g_object_set_property((GObject*)entry1,"font",&v);

8.3.8 أول تطبيق

أول تطبيق سنصممه هو عبارة عن برنامج بسيط يأخذ رقمين ويجمعهما شكل البرنامج عبارة عن ثلاث حقول لإدخال رقمي يقابلها عناوينها التي هي First Number و Second Number و Sum موضوعة في ثلاث صفوف بشكل عامودي فوق بعضها البعض ، يأتي تحتها زر OK. إليكم نص البرنامج:

/* البداية التقليدية */
#include <stdio.h> /* من أجل sprintf */
#include <stdlib.h> /* للمستقبل */
#include <math.h> /* للمستقبل */
#include <gtk/gtk.h>

/* نعرف متغيرات تمثل النافذة والأزرار */
GtkWidget *win, *btn1;
GtkWidget *vbox1,*hbox1,*hbox2,*hbox3,*hbox4;
GtkWidget *label1,*label2,*label3;
GtkWidget *entry1,*entry2,*entry3;

/* نخبر سي عن نموذج الوظيفة */
void calc_result(GtkButton *btn,gpointer *ptr);

int main(int argc, char *argv[])
{
gtk_init(&argc, &argv);
/* نحجز الكائنات */
win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
vbox1 = gtk_vbox_new(TRUE,3);
hbox1 = gtk_hbox_new(FALSE,3); hbox2 = gtk_hbox_new(FALSE,3);
hbox3 = gtk_hbox_new(FALSE,3); hbox4 = gtk_hbox_new(FALSE,3);
label1 = gtk_label_new("First number:");
label2 = gtk_label_new("Second number:");
label3 = gtk_label_new("Sum :");
entry1 = gtk_entry_new();
entry2 = gtk_entry_new();
entry3 = gtk_entry_new(); /* الثالث يمكن أن يكون label */
btn1 = gtk_button_new_from_stock(GTK_STOCK_OK);
/* نضع كل واحد في مكانه  */
gtk_container_add (GTK_CONTAINER(win), vbox1);
gtk_container_add (GTK_CONTAINER(vbox1), hbox1);
gtk_container_add (GTK_CONTAINER(vbox1), hbox2);
gtk_container_add (GTK_CONTAINER(vbox1), hbox3);
gtk_container_add (GTK_CONTAINER(vbox1), hbox4);
gtk_container_add (GTK_CONTAINER(hbox1), label1); gtk_container_add (GTK_CONTAINER(hbox1), entry1);
gtk_container_add (GTK_CONTAINER(hbox2), label2); gtk_container_add (GTK_CONTAINER(hbox2), entry2);
gtk_container_add (GTK_CONTAINER(hbox3), label3); gtk_container_add (GTK_CONTAINER(hbox3), entry3);
gtk_container_add (GTK_CONTAINER(hbox4), btn1);
/* نحدد بعض خصائص الكائنات */
gtk_editable_set_editable(GTK_EDITABLE(entry1),TRUE);
gtk_editable_set_editable(GTK_EDITABLE(entry2),TRUE);
gtk_editable_set_editable(GTK_EDITABLE(entry3),FALSE);
/* نحدد وظائف التفاعل وتتبع الأحداث */
g_signal_connect(G_OBJECT(win), "destroy", G_CALLBACK(gtk_main_quit), NULL);
g_signal_connect(G_OBJECT(btn1), "clicked", G_CALLBACK(calc_result), NULL);
/* نظهر النافذة ومحتوياتها */
gtk_widget_show_all(win);
/* النهاية التقليدية */
gtk_main();
return 0;
}
/* الوظيفة التي ينفذها عند نقر OK */
void calc_result(GtkButton *btn,gpointer *ptr) {
	const char *str1,*str2;
	char str3[40];
	float n1,n2,r;
	/* هنا نأخذ السلسلة النصية التي تمثل الرقم */
	str1=gtk_entry_get_text(GTK_ENTRY(entry1));
	str2=gtk_entry_get_text(GTK_ENTRY(entry2));
	/* هنا نحول من سلسلة نصية إلى رقم نسبي */
	n1=atof(str1); n2=atof(str2); 
	r=n1+n2;/* هنا نحسب */
	sprintf(str3,"%g",r); /*نحول إلى سلسلة نصية*/
	/* يمكن استخدام ftoa(r);
	 * ولكني أفضل sprintf
	 * لأنني أتحكم في الصيغة
	 */
	/* نكتبها في خانة النتيجة */
	gtk_entry_set_text(GTK_ENTRY(entry3),str3);
	return TRUE;
}

نص البرنامج واضح ولا يحتاج إلى الكثير من الشرح، حجزنا صندوق عامودي ووضعنا فيه كل شيء حجزنا أربع صناديق أفقية ووضعنا في أول ثلاث عنوان ونص وفي الرابع وضعنا زر وعلى عكس ما هو متوقع لم أستخدم gtk_button_new_with_label("OK"); لعمل زر موافق بل استخدمت gtk_button_new_from_stock(GTK_STOCK_OK); لأن هناك زر جاهز (وليس تفصيل) في مخازن Gtk2 وهي محلات عريقة! تحتوي على الكثير من الأزرار والقوائم الجاهزة والتي تملك صور معبرة وأيضا مترجمة لعدة لغات منها العربية (أي أنك توفر وقت الترجمة ووقت تصميم الأيقونة). وهناك الكثير منها يمكنك أن تراها بتنفيذ gtk-demo هناك ستجد قائمة طويلة بها وهي أيضا موجودة في المرجع. ويمكن استخدام طريقة مماثلة لعمل القوائم مثلاً gtk_image_menu_item_new_from_stock(GTK_STOCK_OPEN,NULL);

الجزء الذي يمكن أن تراه أكثر تعقيداً هو calc_result الذي يحتوي على الكثير من الغموض. لاحظ أنني لم أحجز str1 و str2 والسبب أن gtk_entry_get_text تقوم بحجز المساحة ويجب أن لا تقوم بتحريرها أو التعديل عليها ولهذا كانت ثابتة const وهذه قاعدة عامة

warningتحذير

وظيفة gtk التي تعيد متغير ثابت const يجب أن لا تحرره ب g_free(my_ptr); كما يفترض أن لا تكون قد حجزته وأعطيته قيمة بل تتركه NULL حتى تضع الوظيفة فيه القيمة المناسبة لأن هذه القيمة قد تكون داخلية. أما الوظائف التي تعيد مؤشرات ليست ثابتة const فإنه يجوز لك تحريرها متى شئت باستعمال g_free(my_ptr); من الأمثلة عليها gtk_something_new();

أما str3 فحجزت له 40 بايت - وأظنها كافية - لأنه يمرر ل sprintf التي لا تحجز ذاكرة. أما إذا كنت تتسائل عن سبب أخذي للمتغيرات على شكل سلسلة نصية ثم تحويلها لرقم ثم جمعها ثم إعادتها على كل سلسلة مرة أخرى وذلك لأن قيمة GtkEntry عبارة عن سلسلة نصية لكي نقوم بالحسابات علينا أن نحولها لرقم وبعد أن نقوم بالحسابات ولكي نظهر النتيجة في entry3 يجب أن تكون نص فنحولها مرة أخرى إلى نص

8.3.9 ساعة ايقاف

البرنامج التالي عبارة عن ساعة ايقاف أي نافذة بثلاث أزرار واحدة لبدء العد والآخر لإيقاف العد والأخير لتصفير الساعة. يتم إضهار الثواني في صندوق نصي GtkEntry لايسمح بالكتابة فيه.

/*
 * stopwatch2.c: A stopwatch with Gtk+2
 *
 * by Moayyad Al-Sadi<alsadi[at]gmail.com>
 * released under the terms of the GNU General Public License
 * visit www.fsf.org/www.gnu.org
 */
#include <glib.h>
#include <gtk/gtk.h>
/* Public variables */
	GTimer *timer1;
	GtkWidget *win, *vbox1,*hbox1;
	GtkWidget *label1;
	GtkWidget *timer, *stopbtn,*startbtn,*resetbtn;
	gchar str[100]; double oldt=-1.0,newt=0.0;
/* idle function to update timer */
gint update_timer(gpointer ptr) {
	g_ascii_formatd(str,99,"%29.3f",newt=g_timer_elapsed(timer1,NULL));
	if (oldt!=newt)
		gtk_entry_set_text(GTK_ENTRY (timer),str);
	oldt=newt;
}
/* handlers for click the 3 buttons */
void start_event() {
	g_timer_start(timer1);
	update_timer(NULL);
}
void stop_event() {
	g_timer_stop(timer1);
	update_timer(NULL);
}
void reset_event() {
	g_timer_reset(timer1);
	g_timer_stop(timer1);
	update_timer(NULL);
}

int main(int argc, char *argv[]) {
	/* create the timer and put it on 0.0 */
	timer1=g_timer_new();
	g_timer_reset(timer1);
	g_timer_stop(timer1);
	
	gtk_init(&argc, &argv);
	/* Build the gui */
	win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
	vbox1 = gtk_vbox_new(FALSE,3); /* homo,spacing */
	label1=gtk_label_new("Moayyad al-Sadi Stopwatch");
	timer=gtk_entry_new();
	hbox1 = gtk_hbox_new(TRUE,3);
	startbtn=gtk_button_new_with_label("start");
	stopbtn=gtk_button_new_with_label("stop");
	resetbtn=gtk_button_new_with_label("reset");
	/* add each widget to it parent */
	gtk_container_add(GTK_CONTAINER (win),vbox1);
	gtk_box_pack_start(GTK_BOX(vbox1),label1,	FALSE, FALSE,0); /* resize=shrink=false */
	gtk_box_pack_start(GTK_BOX(vbox1),timer,	FALSE, FALSE,0);
	gtk_box_pack_start(GTK_BOX(vbox1),hbox1,	TRUE, FALSE,0);
	gtk_box_pack_start(GTK_BOX(hbox1),startbtn,	TRUE, FALSE,0);
	gtk_box_pack_start(GTK_BOX(hbox1),stopbtn,	TRUE, FALSE,0);
	gtk_box_pack_start(GTK_BOX(hbox1),resetbtn,	TRUE, FALSE,0);
	/* set some properties */
	 /* gtk_window_set_default_size(GTK_WINDOW(win), 300, 300); */
	gtk_window_set_title(GTK_WINDOW(win),"Moayyad Stopwatch");
	gtk_editable_set_editable(GTK_EDITABLE(timer),FALSE);
	/* connect events */
	g_signal_connect(G_OBJECT(win),"destroy",gtk_main_quit,NULL);
	g_signal_connect(G_OBJECT(startbtn),"clicked", start_event,NULL);
	g_signal_connect(G_OBJECT(stopbtn),"clicked", stop_event,NULL);
	g_signal_connect(G_OBJECT(resetbtn),"clicked", reset_event,NULL);
	gtk_idle_add(update_timer,NULL);
	/* show all and wait to exit */
	gtk_widget_show_all(win);
	gtk_main();
	return 0;
}                                                                                                                             
		

لتصنيف البرنامج في Gtk1 يجب تعديل بعض الأسطر كما يلي

/* ... */
/* in main(...) */
	gtk_signal_connect(GTK_OBJECT(win),"destroy", GTK_SIGNAL_FUNC(gtk_main_quit),NULL);
	gtk_signal_connect(GTK_OBJECT(startbtn),"clicked", GTK_SIGNAL_FUNC(start_event),NULL);
	gtk_signal_connect(GTK_OBJECT(stopbtn),"clicked", GTK_SIGNAL_FUNC(stop_event),NULL);
	gtk_signal_connect(GTK_OBJECT(resetbtn),"clicked", GTK_SIGNAL_FUNC(reset_event),NULL);
/* ... */
		

لاحظ استعمال الوظيفة gtk_idle_add والتي تعمل على استدعاء الوظيفة التي تحددها له بشكل متكرر عندما لا يقوم البرنامج بعمل أي شيء (أي أن يكون في وضع التوقف بانتظار حدث معين) تعيد هذه الوظيفة رقم يمكن تمريره إلى gtk_idle_remove لإيقاف تنفيذ تلك الوظيفة. وظيفة idle هذه تنفذ بشكل متكرر في فترات غير محددة في المقابل توجد الوظيفة gtk_timeout_add التي تمكنك من تحديد الفترات الزمنية بين استدعاءات المتكررة للوظيفة التي تحددها. يمكن تركيب حجم النافذة التي ستظهر باستعمال gtk_window_set_resizable(GTK_WINDOW(win),FALSE); ليبدو البرنامج منطقياً فلا يمكن تغير حجم النافذة.

8.3.10 صناديق الحوار

تحتوي gtk على الكثير من صناديق الحوار الجاهزة منها صندوق حوار الرسالة ، إذا كنت تريد أن تظهر رسالة بسرعة ودون الكثير من العناء يمكنك ذلك باستعمال GtkMessageDialog فهو ببساطة printf هذه مثال يوضح العملية

/* ... */
GtkWidget *dialog;
dialog=gtk_message_dialog_new(
	win1,GTK_DIALOG_MODAL,
	GTK_MESSAGE_ERROR,GTK_BUTTONS_CLOSE,
	"ERROR: you can't devide %d by zero",i
	);
gtk_run_dialog( GTK_DIALOG(dialog) );
gtk_widget_destroy(dialog);
/* ... */
هنا يظهر رسالة عليها رسمة توضح أنها رسالة خطأ تحتوي على زر اغلاق تظهر هذه الرسالة فوق نافذة اسمها win1 وتكون الرسالة من نوع MODAL أي تجمد النافذة الأب وتبقى فوقها ويبقى ينتظر منك أن تغلقها . الصيغة العامة لها هي
gtk_message_dialog_new(WINDOW,FLAGS, MESSAGE_TYPE,BUTTONS, "a message or a format,just like printf",...); حيث WINDOW هي النافذة التي يتبع لها يمكن أن تكون NULL ، FLAGS هي خصائص الصندوق وهي موضحة في الجدول أدناه، MESSAGE_TYPE هي نوع الرسالة وهي تحدد الأيقونة التي ترافق الرسالة الأنواع هي كما في الجدول أدناه ، أما BUTTONS فهي تحدد الأزرار الموجودة في مثالنا زر إغلاق واطقم الأزرار موضحة في الجدول أدناه

خيارات صندوق الحوار FLAGS
GTK_DIALOG_MODAL فوق النافذة الأب ويجمدها
GTK_DIALOG_DESTROY_WITH_PARENT يغلق النافذة الأب عند إغلاقه
GTK_DIALOG_NO_SEPARATOR لإزالة الخط الفصل بين المنطقة العلوية ومنطقة الأزرار
نوع الرسالة MESSAGE_TYPE
GTK_MESSAGE_INFO معلومات
GTK_MESSAGE_WARNING تحذير
GTK_MESSAGE_QUESTION سؤال
GTK_MESSAGE_ERROR خطأ
أطقم الأزرار BUTTONS
GTK_BUTTONS_NONE دون أي زر
GTK_BUTTONS_OK زر موافق
GTK_BUTTONS_CLOSE مع زر إغلاق
GTK_BUTTONS_CANCEL مع زر إلغاء
GTK_BUTTONS_YES_NO مع زرين هما "نعم" و"لا"
GTK_BUTTONS_OK_CANCEL مع زرين هما "موافق" و"إلغاء"

كل صناديق الحوار يمكنك أن تضيف عليها أزرار كما تريد، ربما تفضل ذلك مع GTK_BUTTONS_NONE الذي يكون بلا أزرار ، يمكن اضافته كما في المثال gtk_dialog_add_button(GTK_DIALOG(dialog),"Click me",1); حيث Click me هو النص الموجود على الزر و الرقم هو أي رقم (موجب لما تضيفه أنت وسالب للمسجل في gtk ) ليمكنك أن تستعمله لتعرف أي زر تم الضغط عليه ويمكنك أن تستعمل مخازن gtk التي تحتوي على الكثير من الأزرار الجاهزرة مثلاً gtk_dialog_add_button(GTK_DIALOG(dialog),GTK_STOCK_APPLY,GTK_RESPONSE_APPLY); ويمكنك اضافتها بطريقة أخرى نتحدث عنها لاحقاً.

بعد حجز الصندوق يمكن عرضه على أنه نافذة عادية أو ببساطة يمكن استدعاء gtk_run_dialog( GTK_DIALOG(dialog) ); هنا تحول هذه الوظيفة الصندوق إلى modal وتعرضه ثم تنتظر أن تستجيب له وتعيد رقم معيناً يمثل الزر الذي تم ضغطه ويمكن أن يكون من الجدول أدناه ،ويمكن أن يكون من الأزرار التي أضفتها

القيم التي يعيدها gtk_run_dialog
GTK_RESPONSE_OK عند اختيار "موافق"
GTK_RESPONSE_CANCEL عند اختيار "إلغاء"
GTK_RESPONSE_CLOSE عند اختيار "إغلاق"
GTK_RESPONSE_YES عند اختيار "نعم"
GTK_RESPONSE_NO عند اختيار "لا"
GTK_RESPONSE_APPLY عند اختيار "تطبيق"
GTK_RESPONSE_HELP عند اختيار "مساعدة"

لعمل صندوق حوار عام يمكن استخدام GtkDialog واضافة الكائنات فيه أو يمكن استعمال الصناديق الجاهزة منها GtkMessageDialog الذي تحدثثنا عنه ومنها أيضا GtkColorSelectionDialog و GtkFileSelection و GtkFontSelectionDialog. كل ما تحدثنا عنه في GtkMessageDialog ينطبق على باقي صناديق الحوار في gtk

في أي صندوق حوار فإنه يقسم لمنطقتين الأولى هي vbox والثانية action_area تحتوي هذه الأخيرة الأزرار لنفرض أن لديك صندوق حوار اسمه dialog يمكنك أن تصل إليهما بالطريقة التالية (GtkDialog *)dialog->vbox و (GtkDialog *)dialog->action_area أو بالإختصار GTK_DIALOG(dialog)->vbox و GTK_DIALOG(dialog)->action_area مثلاً لإضافة GtkEntry *entry1 إلى المنطقة العلوية استعمل gtk_container_add( (GtkContainer *)((GtkDialog *)dialog->vbox),entry1); أو بكلمات أخرى gtk_container_add(Gtk_CONTAINER(GTK_DIALOG(dialog)->vbox),entry1);

8.3.11 المزيد من الكائنات

هناك الكثير من الكائنات يمكنها أن تقوم بمعظم الأشيء التي قد تخطر ببالك مثلاً GtkImage التي تعرض الصور والرسوم المتحركة بمختلف أنواعها ،ببساطة استعمل image1=gtk_new_image_from_file("path/to/image.png"); ثم ضعها في النافذة وأظهرها. لدينا GtkCalendar لعرض التقويم ، ولدينا GtkCombo و GtkOptionMenu لعرض قائمة إختيارات منسدلة تظهر عند نقر سهم موجود عليها ، وكما لدينا الزر العادي GtkButton لدينا زر GtkToggleButton وهو زر متقلب بين وضعين ولدينا GtkCheckButton الذي يعرض مربع تنقر عليه فيضع صح (يمكن أن يكون أي شيء آخر حسب المؤثر المستخدم) وهناك GtkRadioButton وهو عبارة عن زر يمكنك من اختيار واحد من مجموعة منها بحيث يكون واحد مضاء فقط ، يمكن أن تحديد المجموعة ب GtkVBox أو GtkHBox . و يمكنك أن نستخدم GtkFrame لتحيط بهم لإعطاء شكل أنيق لهم ، ونستعمل GtkSpinButton وهو عبارة عن مكان لإدخال رقم مع سهمين متعادين لزيادة الرقم وانقاصه. يمكن استخدام GtkHandleBox ثم وضع حاوية بها مثل GtkToolBar لعمل جزء طافٍ من النافذة يمكن تحريكه. نستخدم GtkHSeparator أو GtkVSeparator لرسم خط يفصل بين الكائنات. ونستخدم GtkProgressBar لتبيان سير العملية على شكل نسبة مئوية أو مجرد حركة. لديك مسطرة عامودية وأفقية GtkVRuler و GtkHRuler. ولإختيار رقم بجر صندوق دحرجة GtkVScrollbar و GtkHScrollbar أو بتحريك مربع التدرج GtkHScale و GtkVScale . بل وحتى تحديد منحى مثل الموجود في gimp باستعمال GtkCurve. لعمل قائمة list أو شجرة tree نستعمل GtkTreeView لأن القائمة هي شجرة دون أفرع . ويمكن تلقي ومعالجة أحداث من كائنات لا تملك أحداث بوضعها داخل GtkEventBox

هناك المزيد من الكائنات ويمكنك أن تعمل كائنات خاصة بك أو استعمال كائنات عملها غيرك مثل GtkOpenGlArea


<< السابق كتاب لينكس الشامل التالي >>