البرمجة الرسومية GUI

باستعمال لغة Python وحزمة ‎GTK+‎

تمهيد

تعتمد لغة جافا في أغلب الجامعات كلغة تعليمية. كلا، لم أخطئ بالعنوان! هذه المقالة عن بايثون أسهل لغة والتي أخذت مكان جافا كلغة تعليمية وموجهة للكائنات OOP. ولأني أعاني من تحسس مرضي من لغة جافا (لم يخففه إعلان شركة صن عن إطلاق الإصدارات القادمة من جافا وفق رخصة التأميم العامة) يرجع هذا التحسس بسبب البطء الشديد في لغة جافا وإلى طبيعة المحاكاة الإلزامية فيها والطريقة التي تتوزع فيها الحزم ...إلخ. ولأني أحب أن تكون البرامج التي يكتبها أخوتي سريعة وموثوقة أقدم لهم البديل.

مقدمة سريعة عن لغة بايثون

إن اللغة البرمجية النصية scripting language الأفضل في العالم كانت دون منازع هي الجَمل بيرل Perl (نسبة للصورة على الكتاب) الذي لديه "أكثر من طريقة للقيام بأي شيء" حتى جاءت لغة بايثون Python والتي تعرف بأنها اللغة النصية الأخرى! (أي إلى جانب Perl) الثعبان الأسطوري Python ليس السبب وراء هذا الاسم بل جاء هذا الاسم نسبة لرواية إنجليزية كوميدية ساخرة تعود للسبعينات من القرن الماضي. لغة بايثون تتخطى كل عيوب لغة بيرل Perl التي توصف بأنها لغة قذرة Dirty Language ففي لغة بيرل من السهل كتابة برنامج لكن قد يكون شبه مستحيل قراته أو تعديله أو إصلاحه. بل إن هناك من عرف بعمل برامج تحل مشاكل معقدة بسطر واحد بلغة بيرل. أما لغة بايثون فإنها تحمي المبرمج من نفسه، إنها تلزمه بالوضوح. لكن هذا ينفر المبرمجين الجدد منها في البداية، كما أنه قد يذكر منتقديها بلغة فورتران سيئة الذكر. جاءت لغة بايثون من مراكز الأبحاث و "مطاحن الأعداد" بسبب كثرة الحزم الإضافية المتخصصة بالحوسبة الفائقة Super computing والحوسبة المتوازية Parallel computing ويظهر هذا واضحاً من خلال النظر في مالكي حقوق النسخ عليها وهم Stichting Mathematisch Centrum ثم Corporation for National Research Initiatives ثم BeOpen.com ثم Python Software Foundation وقد غدت هذه اللغة المفضلة عند علماء الرياضيات والمهندسين والباحثين وقد حلت محل لغة FORTRAN. انظر مقالات Pramode C.E. في Linux Gazette أو في موقعه http://pramode.net ويعرف مخترع هذه اللغة باختراعه لشيء آخر أقل أهمية مقارنة مع باثون وألا وهو آلة الزمن!! نعم، إذ يشاع بأنه قد اخترع آلة التحكم في الزمن، ذلك لأنه كان يجيب على الأسئلة التي تطلب المزيد من المزايا في هذه اللغة بقوله "لقد فعلت ذلك بالأمس!" (القليل من روح الدعابة لكسر الجمود)

من أشهر التطبيقات التي كتبت بهذه اللغة هي Anaconda (على اسم ثعبان حقيقي) وهو برنامج التركيب في توزيعة ريدهات وفيدورا وغيرها وبرامج الإعدادات في تلك التوزيعات. كما أن هناك الكثير من المواقع تستخدم نوع خاص من بايثون يسمى Mod_Python يعمل بشكل متكامل مع خادم الويب أباتشي بطريقة أسرع من CGI بحوالي 50 ضعف وهو المستخدم في غوغل!! عمل البرامج الرسومية بواسطة بايثون يحتاج لاختيار مكتبة أدوات رسومية toolkit الأكثر رواجاً مع بايثون هي Tkinter (التي جاءات من Tcl/Tk) لكنها لا تدعم العربية كما أنها دميمة!! المفضلة لدي هي ‎GTK+‎ (ويمسى هذا المزيد PyGTK) وهي المستخدمة في برامج التركيب والإعداد في توزيعة فيدورا وهي التي اخترتها لهذه المقالة. لكن للعلم هناك غيرها مثل Qt وغيرها.

بديهيات بايثون

نستطيع في بايثون كتابة برامج تنفيذية من سطر واحد كما في بيرل لكن هذا لا ينجح إلا في الأمثلة السخيفة مثل الأمر التالي الذي وضعته في مقالة الصوتيات (اكتب السطر المكتوب بخط غامق فقط في سطر الأوامر)

bash$ python -c 'for i in xrange(8): print i,(1<<(i+1))-1'
0 1
1 3
2 7
3 15
4 31
5 63
6 127
7 255
هذا البرنامج المكون من سطر واحد يعمل حلقة for يدور فيها المتغير i على المدى range من 0 إلى ما دون 8 وفي كل مرة يطبع i وبقربها i بعد إزاحتها عدة منازل ثنائية لليسار بمقدار i+1 ثم طرح واحد من الناتج.

الطريقة التقليدية في برمجة النصوص في UNIX هي عمل ملف نصي يبدأ بعلامة شابانغ ‎#!‎ متبوعاً بالمفسر لهذه اللغة ولغة بايثون ليست استثناءً (مفسر بايثون غالباً ما يكون ‎/usr/bin/python‎ ما لم تكن قد بنيته من المصدر على أي حال يمكن استخدام env كي يعرف ذلك تلقائياً) قم بفتح محرر النصوص المفضل لديك مثل gedit أو kate أو vim أو emacs ...إلخ ثم اكتب فيه

#! /usr/bin/env python
for i in xrange(8): print i,(1<<(i+1))-1
ثم احفظ الملف باسم معبر مثل "test1.py" وتأكد من السماح بتنفيذ هذا الملف من خلال النقر بالزر الأيمن على الملف ثم أخذ خصائص ثم permissions أو بالأمر "chmod +x test1.py". لتنفيذ البرنامج تستطيع الاستفادة من مزايا محررات النصوص الإضافية وعمل ذلك داخل محرر النصوص أو من خلال سطر الأوامر بكتابة "‎./test1.py‎"

اختيار بيئة التطوير وتجهيزها

يمكنك استخدام بيئات التطوير المتخصصة مثل Eclipse الظاهر في الصورة أدناه
لتفعيل PyDev في Eclipse عليك تحديد مسار مفسر لغة بايثون في التفضيلات Preferences من قائمة Window في بند python interpreter ثم New ثم حدد المسار ليكون ‎/usr/bin/python‎.

أو باستخدام محررات النصوص التقليدية أو المتقدمة (مثل Kate أو Gedit) خصوصاً بعد تفعيل الخصائص الإضافية plugins والصور التالية هي من kate
تأكد من تفعيل Python Browser الذي يظهر قائمة جانبية (بالضغط على F9) تحتوي الصنوف والوظائف والمتغيرات ...إلخ. أما في Gedit فعليك تفعيل إضافات Plugins مثل Python Console (تظهرها CTRL+F9) و Indent Lines و DevHelp (تظهرها F2) و Snippets (اختر Configure لإعدادها أو لمعرفة الكلمات والمفاتيح الموجودة) وغيرها. في أغلب البرامج CTRL+Space تعطي البدائل أو الإكمال أو تفعل Snippets.

برامج بسيطة

إليكم برنامج يجد مجموع الأرقام من 1 إلى 99

#! /usr/bin/env python
"""An example to find the summation of 1,2,3,..,99"""
sum=0
for i in xrange(1,100): sum+=i
print "the summation of 1,2,3,..,99 is ", sum
البرنامج التالي يطبع مربعات الأعداد من 1 حتى 5 ويطبع مجموعها
#! /usr/bin/env python
"""An example to find the summation of squares of 1,2,3,4,5"""
sum=0
for i in xrange(1,5):
  print "The square of ", i, " is ", i**2
  sum+=i**2
print "the summation of the squares of 1,2,3,4,5 is ", sum
في لغة بايثون العلامة ^ تعني علاقة "إما xor" (كما في لغة سي) وتوفر علاقة الرفع إلى قوة عبر العملية **. لاحظ من المثال السابق عدم استخدام الحاصرات { } ولا استخدام تعابير مثل start و end لحصر الجزء الذي تدور فيه الحلقة for وهذا ينطبق على جميع أنواع اللبنات blocks في لغة بايثون حيث يتم حصر اللبنة الواحدة بأن تكون لها نفس عدد المسافات البادئة ففي المثال السابقة كانت print وعملية الجمع على sum تبدأ بمسافة تزيد عما حوالها لذا فهما محصوران دون ما حولهما وذلك واضح جداً عن النظر إلى الكود. المثال التالي يجمع الأعداد دون المئة الفردية في المتغير o والزوجية في المتغير e
#! /usr/bin/env python
"""An example to find the summation of odd numbers then even numbers between 0-99"""
o,e = (0,0)
for i in xrange(100):
  if i%2:
    o+=i
  else:
    e+=i
print "the summation of 1+3+5+..+99 is ", o
print "the summation of 2+4+6+..+98 is ", e
لاحظ كيف وضعت القيمة صفر في المتغير o و e دفهة واحدة حيث كان يمكنني عمل ذلك بالطريقة التالية ‎o=0; e=0‎ حيث أن الفاصلة المنقوطة تنهي الجملة السابقة وتبدأ جملة جديدة. العملية % تعني باقي القسمة على الأعداد (فإن كان باقي القسمة على 2 لا يساوي صفر فإنه فردي) وعلى نص تكون عملية تهيئة format كما في printf في لغة سي (انظر المثال التالي)
#! /usr/bin/env python
"""An example to find the length of a vector"""
from math import sqrt # make square root function available
def vlength(x,y):
   """Return the length of the vector (x,y)"""
   return sqrt(x*x+y*y)
# here is the main program, an end-less loop
while(True):
   try:
      x=float(raw_input("Enter x (or Q to quit):")) # take input then convert it to a float number
      y=float(raw_input("Enter y (or Q to quit):")) # take input then convert it to a float number
      print "The length of (x,y) is %g" % vlength(x,y)
  except:
      break # end the end-less loop when something wrong
إن النص العائم ليس تعليقاً comment بل هو توثيق Documentation Strings فإذا طلبت مساعدة help عن الوظيفة vlength فإنه سيكتب لك
vlength(x, y)
    Return the length of the vector (x,y)
أما التعليقات فهي ما وراء علامة # وهو ما يتم تجاهله. لاحظ طريقة تعريف الوظيفة def ثم اسم الوظيفة ثم المعاملات ثم : وهكذا لاحظ استخدام try وهي تعني حاول القيام باللبنة المحصورة فإن حدث أي فشل انتقل إلى قسم الاستثناء except وفي هذا المثال إذا قم المستخدم بإدخال قيمة خطأ لا يمكن تحويلها إلى عدد كأن يكتب Q فإنه سينتقل إلى فصل except والذي يحتوي الأمر break أي الخروج من الحلقة while.

بايثون التفاعلي

يمكن استعمال بايثون بطريقة تفاعلية وذلك بكتابة python في سطر الأوامر لتحصل على ما يشبه

bash$ python
Python 2.5.1 (r251:54863, Oct  5 2007, 13:36:32) 
[GCC 4.1.3 20070929 (prerelease) (Ubuntu 4.1.2-16ubuntu2)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> a="%04d)\tHello %s" % (1,"Ali")
>>> a
'0001)\tHello Ali'
>>> print a
0001)   Hello Ali
>>> for i in xrange(1,5):
...   print i, "**2 =", i**2
... 
1 **2 = 1
2 **2 = 4
3 **2 = 9
4 **2 = 16
5 **2 = 25
>>>
وهنا يمكنك تجريب كل شيء (حتى عمل الوظائف function والصنوف classes). العلامة ‎>>>‎ هي المحث الذي تكتب بعده أوامر بلغة بايثون أما النقاط الثلاثة ... فتظهر عندما قد يحتاج الأمر إلى سطر آخر (اضغط ENTER لإنهاء تلك الحالة) تستطيع طلب مساعدة بواسطة help. مثلاً اكتب ‎help(str.join)‎ اضغط q للخروج. يمكنك إضافة ميزة الإكمال التلقائي إلى بايثون التفاعلي بواسطة الأوامرين
>>> import readline, rlcompleter
>>> readline.parse_and_bind("tab: complete")
جرب الآن أن تكتب re ثم تضغط TAB TAB لتحصل على البدائل التي تبدأ بالأحرف re وهكذا. لإنهاء بايثون التفاعلي اضغط CTRL+D منفردة على سطر. يمكنك جعل الإكمال هو السلوك التلقائي في بايثون التفاعلي من خلال الأوامر (في bash وليس python)
bash$ echo 'import readline, rlcompleter; readline.parse_and_bind("tab: complete")' >> ~/.pythonrc.py
bash$ echo 'export PYTHONSTARTUP=~/.pythonrc.py' >> .bashrc 
وأي نافذة سطر أوامر جديدة تفتحها منذ الآن ستخبر بايثون بذلك.

السلاسل النصية strings والمرتبات tubles والقوائم lists والقواميس dictionaries

المرونة العالية من أفضل ما في بايثون. السلاسل النصية strings يمكن أن تحصل بعلامة التنصيص المفردة كما في ‎a='this is not a joke'‎ والأسهل استعمال المزدوجة كما هي العادة ‎a="this isn't a joke"‎ للتخلص من مشكلة انهاء القتباس بالخطأ عند ' في isn't وإذا كنت بحاجة إلى كليهما استعمل الإقتباس المثلث (المزدوج أو المفرد) ‎a='''he said "this isn't a joke"'''‎ حيث لا ينتهي الاقتباس إلا بثلاث علامات متتالية (إما مزدوجة أو مفردة) وهذا الاقتباس يحتوي بعض التعويض حيث أن علامة \ متبوعة ب n مثلاً تصبح سطراً جديداً. فإذا كنت تريد علامة \ نفسها دون تعويض عليك وضع \\ كما في المثال: ‎file="C:\\folder\\file"‎ أو يمكنك استعمال العلامة r تلغي هذا النوع من التعويض فتصبح ‎file=r"C:\folder\file"‎ ويمكنك جمع السلاسل (إضافتها إلى النهاية) وضربها (تكرارها) جرب ‎print ("1"+"0")*5‎

المرتبات وعلامتها الأقواس الهلالية ( ) هي عناصر مرتبة ثابتة لا يمكن الإضافة إليها أو الحذف منها أو تعديلها كما في الأزواج المرتبة (1,2) في حيث أن القوائم وعلامتها الأقواس المربعة [ ] هي عناصر مرتبة يمكن الإضافة إليها والحذف منها.. إلخ. جرب ما يلي:

bash$ python
>>> t=("One", 2, 3, 4)
>>> print t
>>> print t[0]		# first element (as counting is from 0)
>>> print t[1]		# second element
>>> print t[2]		# third element 
>>> print t[1:3]	# from t[1] to but not including t[3]
>>> print t[1:]		# from t[1] to last one
>>> print t[-1]		# the last element
>>> print t[-3:]	# the last three elements
>>> t[1]="Two"		# error, it's fixed
>>> l=["One",2,3,4] 	# same as l=tuple(t), same operations work
>>> l[1]="Two"		# set the second element
>>> l.append("five")	# add element (push) to the end of l
>>> l.insert(1,1.5)	# insert before element l[1]
>>> print l
>>> for i in l: print i	# iteration loop
...
>>> print l.pop()	# get last element and remove it
>>> print l		# notice that it was removed
>>> print l.index("five") # find the index of element
هناك الكثير من العمليات التي يمكن عملها على القوائم مثل ترتيبها sort أو قلبها (دون عمل قائمة جديدة)
>>> l=range(1,6)
>>> print l
[1, 2, 3, 4, 5]
>>> l.reverse()
>>> print l
[5, 4, 3, 2, 1]
>>> l.sort()
>>> print l
[1, 2, 3, 4, 5]
>>> print min(l), max(l)
1 5
يمكنك فصل قائمة بواسطة split أو وصلها بواسطة join كما في
>>> s='Abu Bakir and Omar and Othman and Ali'
>>> l=s.split(' and ')
>>> print l
['Abu Bakir', 'Omar', 'Othman', 'Ali']
>>> print ', '.join(l)
Abu Bakir, Omar, Othman, Ali
>>> print ', '.join(l[:-1])+' and '+l[-1]
Abu Bakir, Omar, Othman and Ali

أما القوة الحقيقية فتكمن باستعمال وظائف lambda مع العمليات على القوائم مثلفي filter و map و reduce. تكمن فكرة lambda بعمل وظيفة "على الطاير" وفق الصيغة lambda ثم قائمة بالمعاملات ثم : ثم القيمة الناتجة هكذا ‎lambda ARGS: RETURN_EXPR المثال التالي يعرف وظيفة vlength التي تحسب طول متجه

vlength=lambda x,y: sqrt(x*x+y*y)
وتفيد هذه كثيراً في filter و map و reduce التي تأخذ وظيفة وسلسلة (قائمة مثلاً) ثم تعيد filter قائمة أخرى بالعناصر التي تنتقيها الوظيفة بأن تعيد قيمة صحيحة True انظر المثال
>>> l=["One",2,"Three",4]
>>> filter(lambda a: type(a)==str, l)
['One', 'Three']
>>> filter(lambda a: type(a)==int, l)
[2, 4]
>>> filter(lambda i: i%3==0, xrange(1,21))
[3, 6, 9, 12, 15, 18]
الأول أعاد العناصر التي نوعها str أي سلاسل نصية والثاني أعاد الأعداد الصحيحة بين عناصر القائمة l أما الأمر الأخير أعاد قائمة بالأعداد بين 1 و 20 التي تقبل القسمة على 3 دون باق (الباقي كان صفر). أما map فهي تعيد قائمة عناصرها نواتج عناصر القائمة الأصلية في الوظيفة/الدالة.
>>> map(lambda a: a*a, xrange(10))
[1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> l=['abu bakir','omar','othman','ali']
>>> map(lambda a: a.upper(), l)
['ABU BAKIR', 'OMAR', 'OTHMAN', 'ALI']
>>> map(lambda a: a.title()+" (ra)", l)
['Abu Bakir (ra)', 'Omar (ra)', 'Othman (ra)', 'Ali (ra)']
>>> map(len, l)
[9, 4, 6, 3]
الأمر الأول يظهر مربعات الأعداد من 1 حتى 9. أما الأوامر اللاحقة له فهي تعمل على القائمة التي تحتوي على أسماء الخلفاء الأربعة، قمنا أولاً بجعل أسمائهم بأحرف كبيرة ثم بأحرف كبيرة إستهلالية (أول حرف من كل كلمة) وإضافة علامة (ra) أي رضي الله عنه بعد الاسم أوخيراً عملنا قائمة بطول السلسلة (عدد المحارف مع المسافة) لكل عنصر في القائمة الأصلية. أما reduce فهي تعيد قيمة واحدة ناتجة من تطبيق الوظيفة على القيمة الابتدائية ثم العنصر الأول ثم الناتج مع العنصر الثاني وهكذا
>>> reduce(lambda a, b: a+b, xrange(10), 0)
45
>>> 0+0+1+2+3+4+5+6+7+8+9
45
>>> l=['Abu Bakir','Omar','Othman','Ali']
>>> reduce(lambda a, b: a+len(b), l, 0)
22
>>> 0+len('Abu Bakir')+len('Omar')+len('Othman')+len('Ali')
22
المثال الأول أوجدنا به مجموع الأعداد من 1 إلى 9 بأمر واحد (وهذا الأمر سريع حيث أننا لم نخزن في الذاكرة قائمة بالأعداد لأن هذا هو الفرق بين range و xrange) المثال الذي يليه كان يجمع عدد المحارف في أسماء الخلفاء.

أما القواميس dictionaries فهي تسمى أيضاً منظومات مترابطة associative arrays أو مقاطع hashs وعلامتها الحاصرات { } وهي عبارة عن مفتاح بحث ثم : ثم محتوى مقابل له. والفرق بينه وبين القوائم (أو المنظومات) هي أن المفتاح ليس بالضرورة أن يكون عدداً فقد يكون اسماً والمحتوى المقابل هو رقم هاتفه كما في المثال

>>> tel={'Ahmad': '6635410', 'Fadi': '+962 6 6352112'}
>>> tel['Musa']='+44 3 6635410'
>>> print tel['Fadi']
يمكننا إضافة عناصر جديدة بكل سهولة بعملة إحلال = بسيطة. لاحظ أننا عرفنا رقم هاتف فادي دون بحث (حتى لو كان هناك الكثير من العناصر) هذا برنامج كامل
#! /usr/bin/env python
"""A sample phonebook"""
tel={} # empty dictionary
print "Welcome, give me data"
# here is the main program, an end-less loop to get input
while(True):
   i=raw_input("Enter Name (blank to exit):")
   if not i: break;
   j=raw_input("Enter Phone number:")
   tel[i]=j;
print "\n\nNow let's look for data"
# another loop to lookup what you have entered
i=True
while(i):
   i=raw_input("Enter Name (blank to exit):")
   try: print "Name=[%s] Tel=[%s]" % (i,tel[i])
   except: print "blank entry or entry with name=[%s] not found" % i
print "Done"

مقدمة عن ‎GTK+‎

GTK هي المكتبة العظيمة وراء بيئة غنوم GNOME فكل تطبيقات غنوم مثل برنامج جمب GIMP مكتوب بهذه المكتبة. وأسهل طريقة لتعلم هذه المكتبة هي عبر الوثائق التي تأتي مع حزمة التطوير الخاصة بها -devel أو في حزمة الوثائق -doc بحسب التوزيعة وتجد هذه الوثائق في المجلد ‎ /usr/share/gtk-doc/html/ ‎ فهناك المرجع GTK+ Reference Manual، يمكنك البدء بإلقاء نظرة على معرض العناصر Widget Gallery (الأزرار والأشرطة والمدخلات ...إلخ) في الباب الثاني من المرجع. وهناك كتاب آخر وهو الكتاب التعليمي Tutorial وهما للغة سي C وهناك إثنان آخران مقابلان لهما للغة بايثون Pyhton. لكن ما هو أفضل من ذلك هو استعمال برنامج devhelp لقراءة هذه الكتب حيث تختار الكتاب من القائمة الجانبية بل وتستخدم search للبحث عن وظيفة معينة.
أنصحك بقراءة التعليمي Tutorial مرة واحدة فقط مع تجريب بعض الأمثلة عملياً ثم نسيانه والرجوع إلى المرجع Reference لأنه تفصيلي. إن التحويل بين سي وبايثون ليس صعباً الفكرة هي أن الوظائف في لغة سي تبدأ دائماً باسم المكتبة gtk ثم اسم الصنف مثلاً window ثم الوظيفة مثل new أو set_title وتفصل أجزاء الاسم الشرطة التحتية _ مثل gtk_window_new أو gtk_window_set_title لكن في باثون الأمر أسهل لأنها موجهة للكائنات فإن كان الكائن جديداً نستدع وظيفة الإنشاء الضمنية دون new مثل gtk.Window أما إن كان لدينا كائن مثل w فإننا نذكر الوظيفة مباشرة مثل w.set_title (لاحظ أن اسم الصنف مثل Window يبدأ بحرف كبير)

افتح بايثون التفاعلي واكتب

>>> import gtk
>>> help(gtk) # press Q to exit help
>>> win=gtk.Window(gtk.WINDOW_TOPLEVEL) # nothing happens
>>> win.set_title("My first GTK+ trial")
>>> l=gtk.Label(); l.set_markup("Hello, <u>World!!</u>")
>>> win.add(l)
>>> win.show_all() # nothing yet happens
>>> win.connect("destroy", gtk.main_quit)
>>> gtk.main() # it works
الأمر الأول import gtk يستورد حزمة gtk. الثاني يعرض مساعدة (اضغط q للخروج). الأمر الثالث يعرف كائن من نوع gtk.Window ويسميه win (إذا كنت قد فعّلت الإكمال التلقائي كما أخبرناك يمكنك كتابة gtk.Wi ثم الضغط على TAB وهكذا للتسهيل) أما gtk.WINDOW_TOPLEVEL فهو ثابت معرّف في حزمة gtk يعني نافذة عادية بعكس gtk.WINDOW_POPUP التي لا تحاط بإطار وقد لا تظهر على شريط المهام. الأمر التالي واضح جداً إنه يغيّر عنوان النافذة. الأمران التاليان يعرفان لصاقة عنونة/كتابة Label باسم l ويجعل نصها كلمة Hello, world!! مع خط تحتي للكلمة الثانية (عبر PangoMarkup وهي تشبه HTML للمزيد عنها انظر ‎ /usr/share/gtk-doc/html/pango/PangoMarkupFormat.html ‎ ) ثم يضعها في النافذة win. والأمر الذي يليه connect يربط حادث معين وهو إغلاق النافذة destroy بوظيفة gtk.main_quit التي تنهي حلقة gtk.main غير المنتهية بالعربي تنهي البرنامج الرسومي. الأمر الذي يليه يظهر النافذة وكل محتوياتها (السلوك التلقائي أن تكون مخفية) ومع ذلك هي لا تظهر لأن حلقة gtk اللانهائية لم تبدأ بعد. الأمر الأخير يبدأ البرنامج الرسومي بحلقة gtk غير المنتهية. وهنا يتوقف باثون التفاعلي عن العمل (لأن التحكم مع حلقة gtk.main إلى أن تغلق النافذة) وتظهر نافذة صغيرة.

اكتب الملف التالي بمحرر النصوص المفضل لديك

#! /usr/bin/env python
# -*- coding: utf-8 -*-
"""pygtk-ex01-clickme.py
ClickMe, A GTK+ tutorial"""
import gtk
win=None
def main():
  global win
  win=gtk.Window(gtk.WINDOW_TOPLEVEL)
  win.set_title("A GTK+ example")
  l=gtk.Label("""Welcome to GTK+ world.
In this example we see a button below""")
  b=gtk.Button("Click Me, Here!!")
  b.connect("clicked", b_clicked)
  q=gtk.Button(stock=gtk.STOCK_QUIT)
  q.connect("clicked", q_clicked)

  box=gtk.VBox(False,0)
  win.add(box)
  box.add(l)
  box.add(b)
  box.add(q)

  win.show_all()
  win.connect("destroy", gtk.main_quit)
  gtk.main()

# callbacks
def q_clicked(*args):
  gtk.main_quit()
def b_clicked(*args):
  global win
  dlg = gtk.MessageDialog (win, 
gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, 
gtk.MESSAGE_INFO, gtk.BUTTONS_CLOSE, 
"The button was clicked!!")
  dlg.run()
  dlg.destroy()
# call main function (in the top of this file)
main()
هذا المثال عبارة عن نافذة بها لصاقة عنونة Label يحتوي جملة من سطرين، تحتها زر مكتوب عليه ‎Click Me, Here!!‎ وتحته زر الخروج التقليدي (جاهز من المستودع stock وهو الذي تشاهده في برامج غنوم مع صورة الباب مثلاً) وعند النقر على الزر الأول يظهر صندوق حوار dialog يخبرنا بأن أحدهم نقر الزر وإذا نفر زر الخروج فإن البرنامج ينتهي أول سطر من البرنامج (بعد تحديد المفسر) يحدد الترميز بأنه UTF-8 (وليس Unicode) مما يسمح لك بكتابة كلمات عربية بشكل طبيعي على أن تحفظ الملف بترميز UTF-8 (وإلا تكون الطريقة التلقائية في بايثون وهي استخدام ‎ u"\u0627\u0644\u0633\u0639\u062F\u064A" ‎ ) جرب ذلك الآن، عد للبرنامج السابق وعدّل الرسائل النصية لتصبح بالعربية.

السطر ‎ win=None‎ يعرف متغير عام (على مستوى الملف) باسم win دون قيمة (سنعطيع قيمة لاحقاً). ثم عملنا وظيفة اسمها main (الاسم من عندي) وهو الذي سنستدعيه في آخر الملف وذلك لأشعر بالألفة (لأني مبرمج سي أصلاً) ثم أعلنا أن win متغير عام global لأن python إذا رأى = بعد اسم متغير في وظيفة أو صنف اعتبره محلياً بشكل تلقائي (لتوفير الذاكرة). ثم عملنا نافذة ولصاقة عنوان وزرين. ربطنا حادة النقر "clicked" على أحدهما بالوظيفة b_clicked والآخر بالوظيفة q_clicked. أما VBox والأسطر الثالثة التالية له فإنها تضع كل شيء في مكانه على النافذة. نفذ البرنامج ولا تغلقع ثم عد للبرنامج وغيّر VBox لتصبح HBox احفظ ثم نفذه مرة وقارن النتيجة.

طرق رص العناصر Widgets

مبرمجي Visual Basic يستخدمون طريقة رص غبية يقابلها gtk.Fixed حيث نحدد احداثيات وأبعاد كل عنصر (زر أو عنوان...) وفي هذه الحالة نستخدم الوظيفة put وليس add متبوعة بالعنصر ثم الإحداثي x ثم الإحداثي y. بهذه الطريقة يبدو البرنامج غريب الشكل عند تغيير حجم النافذة أو عند تغير حجم أو نوع الخط ...إلخ.

تقدم GTK عدة طرقة أخرى أفضل لوصف طريقة رص العناصر. منها طريقة VBox حيث تتكدس العناصر عمودياً في طبقات فوق بعضها وأختها HBox التي ترص أبناءها أفقياً في طبقات بجانب بعضهم وكل منهما تأخذ معاملان هما True أو False (هل تجعل كل الطبقات متساوية الطول أو العرض أم لا) وعرض الفاصل بين كل طبقتين وتضاف العناصر إليها بالوظيفة add أو إذا أردت تحديد المزيد من الخيارات استخدم pack_start وتأخذ العنصر ثم هل يخصص للعنصر مساحة إضافة عند التكبير ثم هل يكبر العنصر عندما تكبر المساحة المخصصة له (وإلا يكبر الهامش فقط) ثم عرض الهامش padding. وهذان كافيان لعمل أكثر الواجهات تعقيداً بطريقة أنيقة حيث يجوز أن نضع VBox أو HBox داخل بعضها البعض. إذا نظرت لأي برنامج مثل متصفح الإنترنت مثلاً تجد أنه عبارة عن VBox يتكدس فيه شريط القوائم وشريط الأدوات ومساحة العمل ثم شريط الحالة فوق بعضها وهنا كل هذه العناصر لا تكبر عند تكبير النافذة باستثناء مساحة العمل التي تبتلع كل المساحة الإضافية. مساحة العمل بدورها قد تكون عبارة عن صندوق أفقي HBox يتكدس فيه الصندوق الجانبي (مثلاً لعرض History) جنباً إلى جنب مع العنصر الذي يعرض نص الصفحة.

الحاويتان HPaned و VPaned تشبهان الصندوقات السابقان (واحدة بالطول والأخرى بالعرض) لكنهما لا يتسعان إلا لعنرصين لكن ما يميّزهما أنهما يسمحان بتوسيع وتضييق المساحة بواسطة المستخدم (مفيدة لعمل القائمة الجانبية). وهنا نضيف العناصر عبر add1 و add2 أو pack1 و pack2 لكل من العنصر الأيمن أو الأيسر.

الطريقة التالية هي استخدام جدول لوضع العناصر بين أركانه، انظر لنموذج برنامج الآلة الحاسبة أدناه وإلى خلايا الجدول الذي استخدم في تصميمه
لاحظ أنه يتكون من أربعة أعمدة و ستة صفوف. هذه الحاوية هي gtk.Table وهي تأخذ عدد الصفوف (6 في المثال) ثم عدد الأعمدة (4 في المثال) ثم هل هذه الصفوف أو الأعمدة متناسقة (متساوية في الطول أو العرض) وإلا فإنها تكون مستقلة بحسب الحاجة. يتم إضافة العناصر عبر الوظيفة attach ثم العنصر ثم نقطة البداية الأفقية ثم النهاية الأفقية (الصف) ثم البداية العمودية ومن ثم نهايته (العمود) وبقية المعاملات اختيارية وهي طريقة اعطاء المساحة افقياً ثم عمودياً ثم عرض الفاصل الأفقي ثم عرض الفاصل العمودي. الزر الذي يحمل الرقم 5 (انظر الصورة) موضوع أفقياً بين خطوط الأعمدة 1 و 2 وموضوع عمودياً بين خطوط الصفوف 3 و 4. أما زر الجمع "+" الكبير فإنه فهو يمتد بين خطوط الأعمدة 3 و 4 ويمتد عمودياً بين خطوط الصفوف 2 و 4 (أي يمتد على صفين الثالث والرابع أو رقماً على الصف رقم 2 والصف رقم 3 عند العد من صفر). أما عنصر الإدخال أو "الشاشة" فهي تمتد أفقياً بين خط العمود رقم 0 و 4 وتقع على الصف رقم صفر (بين خط رقم 0 و 1). إليك مقطع من الكود (المثال كاملاً موجود في الملف)

# pygtk-ex02-calc.py
# ...
win = gtk.Window() # create the window
table = gtk.Table(6,4,True) # create table
# create entry and buttons
e = gtk.Entry()
b_add = gtk.Button("+")
b5 = gtk.Button("0")
# ...
# place them
win.add(table)
table.attach(e,0,4,0,1,gtk.FILL | gtk.EXPAND,gtk.FILL | gtk.EXPAND,1,1)
table.attach(b_add,3,4,2,4)
table.attach(b5,1,2,3,4)
# let's put a space the entry (LCD) and next row (buttons)
table.set_row_spacing(0, 5)
# ...

الحاوية Notebook يحتوي على عدة مساحات (تسمى الواحدة صفحة) لكن لا تظهر منها إلا واحدة ويتم اختيار أيها يظهر عبر ألسنة تبويب (ظاهرة أو مخفية) كل صفحة عبارة عن حاوية مشتقة من Bin تضع فيها ما تشاء ويكون ذلك عبر append_page. الحاويات المشتقة من نوع Bin تحتوي عنصر واحد فقط وتهدف لتوفير ميزة معينة مثلاً كأن تحتوي عناصر كبيرة في مساحة صغيرة عبر ScrolledWindow أو EventBox كي تستطيع مراقبة الأحداث على عنصر لا يراقب الأحداث ...إلخ. الزر Button عبارة عن Bin أي يمكننا أن نضع فيه أي عنصر آخر (تلقائياً هو يحتوي على لصاقة عنوان Label) بل ويمكن وضع عدة عناصر بوضعها داخل HBox ثم وضعها داخل الزر.

أهم العناصر

لقد مر بنا عدة عناصر حتى الآن (الزر Button و لصاقة العنوان Label والإدخال من سطر واحد Entry) وهي تشكل أغلب البرامج. وهناك أنواع مشتقة من الأزرار كالأزرار القلابة ToggleButton أي التي يكون لها حالتين on/off ونفس الوظيفة يمكن تحصيلها عبر الزر الذي بقربه علامة صح CheckButton وهناك أزرار الاختيار من متعدد RadioButton (تكون واحدة على الأكثر فعالة من بين مجموعة) يتم تحديد المجموعة عبر تمرير None كمعامل أول (قبل نص العنوان) لأول زر وفي الأزرار الأخرى التالية يتم تمرير أي من الأزرار السابقة في المجموعة كمعامل أول (حتى يعرف المجموعة). وطبعاً هناك Image التي تعرض صورة من ملف أو من مخزن GTK وذلك عبر set_from_file و set_from_stock وهذه الأخيرة تأخذ معامل ثان هو الحجم الذي يمكن أن يكون gtk.ICON_SIZE_MENU أو ICON_SIZE_BUTTON ..إلخ. وتدعم GTK الصورة المتحركة (GIF مثلاً وربما MNG). إليك هذا المثال البسيط (مجرد مقطع)

# pygtk-ex04-widgets.py
# ...
win = gtk.Window() # create the window
vb = gtk.VBox(False,0); win.add(vb)
img=gtk.Image()
img.set_from_stock(gtk.STOCK_SAVE, gtk.ICON_SIZE_BUTTON)
# or img.set_from_file('/folder/pic.jpg')
vb.add(img)

hb = gtk.VBox(False,0); vb.add(hb)
# create entry and buttons
b = gtk.ToggleButton("on/off")
hb.pack_start(b, False, False, 0)
rb = gtk.RadioButton(None,"Choice one")
hb.pack_start(rb, False, False, 0)
rb = gtk.RadioButton(rb,"Choice Two") # we pass rb which is a ref "Choice One"
hb.pack_start(rb, False, False, 0)
rb = gtk.RadioButton(rb,"Choice Three") # we pass rb which is a ref "Choice Two"
hb.pack_start(rb, False, False, 0)

win.show_all()
# ...

ومن أكثر العناصر قوة TextView وهو محرر نصوص متعدد الأسطر (بعكس Entry) كما أنه يدعم عمل كتابة متعددة الألوان والخطوط والتنسيقات بل وحتى يمكن إدراج أي عنصر widget كالزر أو الصورة. أما كلمة View والتي تعني عرض فسببها أن العمليات على النص تحدث في الصنف TextBuffer أما TextView فهو فقط يظهرها. بشكل عام تستطيع التعامل مع TextView ببديهية فالوظائف set_editable و set_cursor_visible و set_wrap_mode و set_justification تعبر عن نفسها وليست بحاجة لشرح. أما للتعامل مع النص فعليك أخذ مرجع reference عن كائن TextBuffer الخاص به ‎ txt=gtk.TextView(); buf=txt.get_buffer() ‎ ثم التعامل مع ذلك الكائن. حيث set_text تجعل النص هو معاملها (تلغي النص السابق) و insert_at_cursor تقوم بعمل مشابه لكنها لا تحذف النص السابق بل تحشر النص الجديد عند المؤشر. هناك 3 صنوف لتحديد الموقع في النص: السيار iterator والعلامة mark والمرساة anchor وهذه الأخيرة خاصة بالعارض لإضافة عنصر widget (صورة مثلاً) كأنه حرف inline. الأولى وهي الأبسط والأكثر استعمالاً لكل العمليات على النص فهي تمثل ما يشبه بعد المحرف (الحرف/الرمز) عن بداية النص. لكنه لا يبقى صالحاً بعد أي تعديل على النص لهذا بعد أن تحصل عليه عليك استعماله فوراً وعدم استعماله مجدداً بل توليده مرة أخرى. الوظيفة get_bounds في الصنف TextBuffer تعطينا سيارا البداية والنهاية. أما العلامة في لا تتأثر بأي تعديل يحدث على النص بل تتحرك مع فإذا كان هناك علامة "هنا" وثم إضافة كلمة قبلها فإن تتحرك لتظل تشير إلى بداية كلمة هنا السابقة. هناك علامتان في أي نص هما "insert" و "selection_bound" الأولى تخبرنا عن مكان المؤشر والأخرى عن نهاية التحديد (فإن تساويا لا يكون هناك تحديد/تظليل للنص)

التنسيقات المتقدمة يتم عملها عبر عمل راقمة tag بها خصائص النص ثم تطبيقها على جزء معين من النص (تحدد البداية والنهاية عبر السيار) كما يمكن معرفة الرواقم المطبقة على جزء معين من النص وإذا كنت على بداية يمكنك معرفة النهاية وهكذا. كما يمكن تطبيق الراقمة الواحدة على أكثر من جزء من النص.

المحتويات
حقوق النسخ والملكية الفكرة
ما هو لينكس؟
كيف تركب نظام لينكس؟
حقوق النسخ المرفوعة
وثائق التخويف

عرب-آيز
موسوعة ثواب
موسوعة ويكيبيديا
تعريف البرمجيات الحرة
تعريف مفتوحة المصدر
LinuxToday
Linux.org
Linux.com
SlashDot
FreshMeat
LWN.net

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

 

ننصح باستخدام متصفحات الوب الحرة، جرب ثعلب النار الآن

يمكنك الحصول على الكثير من البرامج الحرة عالية الجودة من هنا مجاناً
proud to be 100% Microsoft FREE GNU FDL
التدخين حرام

كن كحامل المسك ولا تكن كنافخ الكير

Generously Hosted by www.JadMadi.net

Copyright © 2007, Muayyad Saleh AlSadi