أدوات المستخدم

أدوات الموقع


docs:pyqt4

اختلافات

عرض الاختلافات بين النسخة المختارة و النسخة الحالية من الصفحة.

رابط إلى هذه المقارنة

جانبي المراجعة السابقة المراجعة السابقة
المراجعة التالية
المراجعة السابقة
docs:pyqt4 [2009/09/23 11:48]
alsadi
docs:pyqt4 [2015/04/23 00:20] (حالي)
سطر 1: سطر 1:
 +{{tag>​مقالات مترجمة برمجة بايثون Qt PyQt4}}
 +====== تعليم البرمجة الرسومية في PyQt4  ======
 +  * مقالة من تأليف Jan Bodnar
 +  * المصدر http://​zetcode.com/​tutorials/​pyqt4/​
 +  * ترجمة أحمد يوسف
 +  * قيد المراجعة اللغوية
 +  * تمت المراجعة حتى فصل [[#​صناديق الحوار فى PyQt4|صناديق الحوار]]
 + ​\\ ​
 +هذا مدخل لطقم PyQt4. وهو مناسب للمبرمجين المبتدئين والمتوسطين.
 +بعد قراءة هذا المدحل، ستتمكن من عمل تطبيقات PyQt4 غير سطحية.
 +قد ترغب في إلقاء نظرة على مقالة [[http://​zetcode.com/​tutorials/​pythontutorial|تعلم بايثون]] أيضا.
 +
 +===== مقدمة في طقم أدوات PyQt4 =====
 +
 +==== حول هذه المقالة ====
 +
 +هذه مقدمة عن PyQt4. الهدف منها هو وضع قدمك على أول الطريق مع طقم أدوات PtQt4 تم كتابة المقال وتجربته على لينكس.
 +
 +==== حول PyQt ====
 +
 +تعتبر PyQt طقم أدوات لعمل تطبيقات رسومية GUI. وهي عبارة عن دمج بين لغة البرمجة بايثون ومكتبة Qt الناجحة وهذه الأخيرة من أقوى المكتبات على وجه البسيطة. الموقع الرسمي لها هو 
 +The official home site for PyQt is on [[http://​www.riverbankcomputing.co.uk|www.riverbankcomputing.co.uk]]
 +ويطورها فيل ثومسون Phil Thompson.
 +
 +تم تنفيذ PyQt على شكل طقم من عدة وحدات بايثون Python modules.
 +وهي تحتوي أكثر من 300 صنف class ((الترجمة الشائعة لكلمة class هي صف كما في مصطلحات نظرية التطور في الأحياء لكنه تعريب غير موفق لأن السياق هنا يحتوي على صفوف rows)) و 6000 وظيفة/​طريقة function/​method.
 +وهي متعددة المنصات وتعمل على كل الأنظمة الرئيسية بما في ذلك يونكس وويندوز وماك.
 +تخضع PyQt لرخصة مزدوجة حيث يمكن للمطورين الاختيار بين GPL والرخصة تجارية.
 +سابقا كانت رخصة GPL متوفرة فقط على يونكس لكن بدأ من الإصدار الرابعة أصبحت GPL متوفرة على كل المنصات المدعومة.
 +
 +وحيث أن هناك الكثير من الصنوف Classes المتوفرة تم تقسيمها إلى عدة وحدات Modules.
 +
 +
 +{{ :​docs:​qt-modules.png |الوحدات المكونة ل PyQt4}}
 +
 +الوحدة QtCore تحتوي الوظائف المركزية غير الرسومية. تستخدم هذه الوحدة في التعامل مع الوقت والملفات والأدلة (المجلدات) وأنواع البيانات المختلفة والسيالات streams وروابط URL وأنواع الملفات MIME والعمليات processes and threads.
 +
 +الوحدة QtGui فتحتوي على المكونات الرسومية وما يتعلق يها من صنوف. وتشتمل على سبيل المثال على الأزرار والنوافذ وأشرطة الأدوات والحالة والزلاقات والصور النقصية والألوان والخطوط ...إلخ.
 +
 +الوحدة QtSVG بها الصنوف اللازمة لعرض محتويات ملفات SVG. وهي لغة وصفية للرسومات المتجهية ثنائية الأبعاد والتطبيقات الرسومية ب XML
 +
 +الوحدة QtOpenGL تستخدم لتوليد رسومات ثلاثة الأبعاد أو ثنائية باستخدام مكتبة OpenGL. مما يمكننا من دمج Qt مع OpenGL دون عناء.
 +
 +الوحدة QtNetwork تحتوي على الصنوف اللازمة لبرمجة الشبكات. هذه الصنوف تسمح بكتابة برامج خوادم severs أو مخدومات clients لبروتوكولات TCP/IP و UDP. مما يجعل برمجية الشبكات أسهل وقابل للنقل لمنصات أخرى.
 +
 +الصنوف في الوحدة QtXml للعمل على ملفات xml. هذه الوحدة تقدم تطبيق ل SAX و DOM.
 +
 +الوحدة QtSql تقدم صنوف التعامل مع قواعد البيانات.
 +
 +
 +==== بايثون ====
 +بايثون لغة نصية ناجحة. بدأ تطويرها Guido van Rossum. وقد اطلقت لاول مرة في عام 1991.
 +بايثون استلهمت من ABC و Haskell. وهي لغة برمجة تفسيرية عالية المستوى وعامة الأغراض ومتعددة المنصات. والبعض يحب أن يسميها اللغة الديناميكية. إنها لغة سهلة التعلم. وهي لغة مصغّرة minimalistic. وأحد أوضح مزاياها أنها لا تستعمل الفاصلة المنقوطة ولا الحاصرات. بل تستخدم الإزاحة (المسافة البادئة indentation) الإصدار الأحدث وقت كتابة المقالة هو 2.5 وقد أطلق في أيلول/​سبتمبر 2006. ويقوم اليوم عليها مجموعة كبيرة من المتطوعين حول العالم.
 +
 +مجتمع مبرمجي [[http://​www.tiobe.com/​tpci.htm|TIOBE]] يقدم ترتيب نظري لمستوى استخدام لغات البرمجة المختلفة. جافا تتربع على عرشها بعد أن خلعت سي++ لكن سي++ ستظل حاضرة بثقلها خلال العقود القادمة ​ ولا يوجد ما يهددها.
 +ونرى تخصص لغات البرمجة فلغة جافا للأعمال enterprise وسهولة النقل portability .
 +وسي هي ملكة برمجة النظم (نظم التشغيل وتعريفات وادوات النظام الصغيرة). ولغة PHP تسيطر على مواقع الويب الصغيرة والمتوسطة وجافاسكربت تستخدم في تطبيقات الويب عند المخدوم client
 +
 +^  ترتيب اللغات في 2008 TIOBE  ^^^
 +^  المرتبة ​ ^  اللغة ​ ^  التقييم ​ ^
 +|  1  |  Java  |  21.571% |
 +|  2   ​| ​ C  |  16.178% ​ |
 +|  3   ​| ​ (Visual) Basic  |  10.857% ​ |
 +|  4   ​| ​ C++  |  10.057% ​ |
 +|  5   ​| ​ PHP  |  9.349% ​ |
 +|  6   ​| ​ Python ​ |  4.975% ​ |
 +|  7   ​| ​ Perl  |  4.694% ​ |
 +|  8   ​| ​ C#  |  3.697% ​ |
 +|  9   ​| ​ Ruby  |  2.920% ​ |
 +|  10   ​| ​ JavaScript ​ |  2.892% ​ |
 +
 +ونلاحظ أن بايثون تحتل المركز السادس (متقدمة مرتبتين على سي#) وقد كانت قبل عام في المركز الثامن.
 +
 +==== الأدوات الرسومية في بايثون ====
 +
 +لعمل تطبيقات رسومية يختار مبرمجوا بايثون بين ثلاث خيارات محترمة. وهي PyGTK و wxPython و PyQt، وأيها عليك أن تختار يعتمد على الظروف.
 +وهناك خيار آخر اسمه TkInter تجنبه.
 +
 +
 +===== أول البرامج على PyQt4 =====
 +
 +في هذا الجزء من "​تعليم PyQt4" سنتعلم بعض الوظائف الأساسية. وسنطيل الشرح كما لو اننا نتحدث إلى طفل. وكما تكون خطوات الطفل الأولى خرقاء كذلك تكون محاولات المبتدئ في البرمجة.
 +وتذكر، لا يوجد أغبياء لكن يوجد كسالى غير مثابرين.
 +
 +==== مثال بسيط ====
 +
 +هذا الكود مبسط جدا. إنه يظهر نافذة صغيرة. نستطيع القيام بالكثير من الأشياء على هذه النافذة، نحجمها أو نكبرها أو نصغرها وهذا يتطلب كتابة كود، لكن هناك شخص ما قد قام بذلك عنا. ولأن هذا يتكرر في كل التطبيقات لا داع لكتابته مرة بعد أخرى فقد تم إبعاد هذا الجزء من الكود عن ناظر المبرمج. مكتبة أدوات PyQt هي مكتبة عليا. فلو كنا نبرمج في مكتبة دنيا لكان الكود التالي مكونا من عشرات الأسطر.
 +
 +<code python>
 +#​!/​usr/​bin/​python
 +
 +# simple.py
 +
 +import sys
 +from PyQt4 import QtGui
 +
 +app = QtGui.QApplication(sys.argv)
 +
 +widget = QtGui.QWidget()
 +widget.resize(250,​ 150)
 +widget.setWindowTitle('​simple'​)
 +widget.show()
 +
 +sys.exit(app.exec_())
 +</​code>​
 +وإليكم شرح الكود
 +
 +  import sys
 +  from PyQt4 import QtGui
 +
 +هنا قمنا بعملية الاستيراد import اللازمة. فالودجات ((الودجة هي الكائن الرسومي لأنها وسيلة وجاء في لسان العرب "​ووَدَجَ بين القوم وَدْجاً أَصلح وفلانٌ وَدَجِي إِلى فلان أَي وسيلتي"​ انظر http://​wiki.arabeyes.org/​techdict_talk:​Widget )) الرسومية الأساسية موجودة في وحدة QtGui.
 +
 +  app = QtGui.QApplication(sys.argv)
 +
 +كل تطبيق على PyQt4 يجب أن ينشئ كائن من نوع تطبيق QApplication وهو موجود في وحدة QtGui
 +المعامل sys.argv هي قائمة بالمعاملات التي تمرر للتطبيق عبر سطر الاوامر.
 +وكما نعلم يمكن تنفيذ نصوص بايثون من الصدفة (سطر الأوامر) فهي طريقة نتحكم فيها ببداية البرنامج.
 +
 +  widget = QtGui.QWidget()
 +
 +الصنف QWidget هو أساس صنوف base class كل الودجات (الكائنات الرسومية) في PyQt4.
 +وهنا قدمنا على استدعاء المشيد constructor الخاص به.
 +والسلوك التلقائي أن لا يحتاج إلى والد. الودجة التي لا والد لها تكون نافذة.
 +
 +  widget.resize(250,​ 150)
 +
 +الطريقة resize تحجّم الكائن. فسيكون عرضه 250 وارتفاعه 150 (بيكسل).
 +
 +   ​widget.setWindowTitle('​simple'​)
 +
 +وهنا وضعنا عنوانا لنافذتنا. هذا العنوان يظهر في شريط العنوان.
 +
 +   ​widget.show()
 +الطريقة show تظهر الكائن على الشاشة
 +
 +  sys.exit(app.exec_())
 +
 +وأخيرا ندخل في الحلقة الرئيسية mainloop للتطبيق. وتبدأ معالجة الأحداث بعد هذه النقطة. الحلقة الرئيسية تستلم الأحداث من نظام النوافذ وترسلها لودجات التطبيق.
 +وتنتهي الحلقة الرئيسية عندما نستدع exit أو عند تدمير الودجة الرئيسية. وقد قمنا باستدعاء sys.exit للتأكد من إنهاء التطبيق بطريقة نظيفة.
 +
 +وإذا كنت تتساءل لماذا لحقت _ بالطريقة exec ؟ كل شيء له معنى. ببساطة لان exec عبارة عن كلمة مفتاحية في لغة بايثون.
 +
 +
 +{{ pyqt4-simple.jpg |البرنامج البسيط Simple}}
 +
 +==== أيقونة للتطبيق ====
 +
 +أيقونة التطبيق (البرنامج) هي صورة صغيرة تعرض في زاوية شريط العنوان. وفي المثال التالي سنريكم طريقة القيام بذلك في PyQt4
 +سنقدم بعض الطرق الجديدة.
 +
 +<code python>
 +#​!/​usr/​bin/​python
 +
 +# icon.py
 +
 +import sys
 +from PyQt4 import QtGui
 +
 +
 +class WinWithIcon(QtGui.QWidget):​
 +    def __init__(self,​ parent=None):​
 +        QtGui.QWidget.__init__(self,​ parent)
 +
 +        self.setGeometry(300,​ 300, 250, 150)
 +        self.setWindowTitle('​Icon'​)
 +        self.setWindowIcon(QtGui.QIcon('​icons/​web.png'​))
 +
 +
 +app = QtGui.QApplication(sys.argv)
 +icon = WinWithIcon()
 +icon.show()
 +sys.exit(app.exec_())
 +
 +</​code>​
 +
 +المثال السابق كتب بطريقة إجرائية. وبايثون تدعم الطريقة الإجرائية Procedural والموجهة للكائنات OOP. البرمجة مع PyQt4 تعني السير بأسلوب OOP.
 +
 +<code python>
 +class WinWithIcon(QtGui.QWidget):​
 +   def __init__(self,​ parent=None):​
 +      QtGui.QWidget.__init__(self,​ parent)
 +</​code>​
 +
 +أهم 3 عناصر في البرمجة الموجهة للكائنات هي الصنوف classes والبيانات data والطرق methods. وهنا قمنا بعمل صنف جديد اسمه WinWithIcon يرث الصنف QtGui.QWidget وهذا يعني أننا نستدعي الإنشاءين واحد للصنف WinWithIcon والثاني للصنف الذي ورثه.
 +
 +<code python>
 +self.setGeometry(300,​ 300, 250, 150)
 +self.setWindowTitle('​Icon'​)
 +self.setWindowIcon(QtGui.QIcon('​icons/​web.png'​))
 +</​code>​
 +
 +كل الصنوف الثلاثة ورثت من QtGui.QWidget.
 +الطريقة //​setGeometry//​ تقوم بشيئين. تموضع النافذة في مكانها وتحدد حجمها. المعاملان الأولان هما الإحداثيان x و y للنافذة. الثالث هو العرض والرابع هو الارتفاع.
 +الطريقة الأخيرة (أي الدالة الأخيرة) تحدد الأيقونة. لعمل ذلك قمنا بعمل كائن QIcon وهو يحمّل الأيقونة من المسار المعطى.
 +
 +{{ :​docs:​pyqt4-icon.jpg |برنامج له أيقونة}}
 +
 +==== عرض تلميح ====
 +
 +نستطيع ان نعرض بالونات المساعدة((التلميحات كانت تسمى بالونات المساعدة في نظام ماكنتوش)) للودجات الخاصة بنا ​
 +
 +<code python>
 +#​!/​usr/​bin/​python
 +
 +# tooltip.py
 +
 +import sys
 +from PyQt4 import QtGui
 +
 +class Tooltip(QtGui.QWidget):​
 +    def __init__(self,​ parent=None):​
 +        QtGui.QWidget.__init__(self,​ parent)
 +
 +        self.setGeometry(300,​ 300, 250, 150)
 +        self.setWindowTitle('​Tooltip'​)
 +
 +        self.setToolTip('​This is a <​b>​QWidget</​b>​ widget'​)
 +        QtGui.QToolTip.setFont(QtGui.QFont('​OldEnglish',​ 10))
 +
 +app = QtGui.QApplication(sys.argv)
 +tooltip = Tooltip()
 +tooltip.show()
 +app.exec_()
 +</​code>​
 +
 +فى هذا المثال سنقوم بعرض نص تلميج على ودجة //QWidget//
 +
 +<code python>
 +self.setToolTip('​This is a <​b>​QWidget</​b>​ widget'​)
 +</​code>​
 +
 +ننشئ التحديد باستدعاء الطريقة //​setToolTip//​. نستطيع عمل نص غني بالتنسيق Rich Text
 +
 +<code python>
 +QtGui.QToolTip.setFont(QtGui.QFont('​OldEnglish',​ 10))
 +</​code>​
 +
 +ولأن الخط التلقائي في //​QToolTip//​ قبيح نغيّره باستدعاء //setFont// ((لا أنصح بتغير الخط التلقائي فمكان ذلك في إعدادات النظام والتطبيق يجب أن ينصاع لتفضيلات المستخدم --السعدي))
 +
 +
 +{{ pyqt4tooltip.jpg |تلميح Tooltip}}
 +
 +==== غلق نافذة ====
 +
 +الطريقة الواضحة لإغلاق نافذة هي عن طريق الضغط على زر إكس x الموجود بشريط العنوان، سنريك فى المثال التالي كيف تقوم بإغلاق نافذتنا برمجيا. ​
 +
 +اخترنا المشيد التالي لصنف //​QPushButton//​ فى مثالنا((يمكنك الرجوع لصفحة [[http://​www.riverbankcomputing.co.uk/​static/​Docs/​PyQt4/​html/​classes.html|صنوف PyQT]]))
 +<code python>
 +QPushButton(string text, QWidget parent = None)
 +</​code>​
 +
 +تمثل فيه text النص الظاهر وتمثل parent والد الزر parent الذي فيه سيظر الزر وفي مثالنا هو QWidget.
 +
 +<code python>
 +#​!/​usr/​bin/​python
 +
 +# quitbutton.py
 +
 +import sys
 +from PyQt4 import QtGui, QtCore
 +
 +
 +class QuitButton(QtGui.QWidget):​
 +    def __init__(self,​ parent=None):​
 +        QtGui.QWidget.__init__(self,​ parent)
 +
 +        self.setGeometry(300,​ 300, 250, 150)
 +        self.setWindowTitle('​QuitButton'​)
 +
 +        quit = QtGui.QPushButton('​Close',​ self)
 +        quit.setGeometry(10,​ 10, 60, 35)
 +
 +        self.connect(quit,​ QtCore.SIGNAL('​clicked()'​),​
 +            QtGui.qApp, QtCore.SLOT('​quit()'​))
 +
 +
 +app = QtGui.QApplication(sys.argv)
 +qb = QuitButton()
 +qb.show()
 +sys.exit(app.exec_())
 +</​code>​
 +
 +<code python>
 +quit = QtGui.QPushButton('​Close',​ self)
 +quit.setGeometry(10,​ 10, 60, 35)
 +</​code>​
 +
 +انشأنا زر وحددنا موضعه بإستخدام الطريقة //​setGeometry//​ ((يفترض بالتطبيقات عدم استخدم الاحداثيات في توضيب أماكن الودجات بقيم ثابتة لأن الأماكن يجب أن تعكس في بعض اللغات مثل العربية كما أن اختلاف الخطوط قد يجعل النص يفيض أو يقص إن كان الخط كبيرا لذا يجب استخدام أساليب متقدمة في الرصف انظر [[#​إدارة المخطط فى PyQt4|إدارة المخطط]] --السعدي)) ، مثلما موضعنا النافذة على الشاشة.
 +
 +<code python>
 +self.connect(quit,​ QtCore.SIGNAL('​clicked()'​),​
 +     ​QtGui.qApp,​ QtCore.SLOT('​quit()'​))
 +</​code>​
 +معالجة الأحداث فى PyQt4 مبنية على آلية الإشارة والتّلم((التَّلَمُ مشَقُّ الكِراب في الأَرض بلغة أَهل اليمن وأَهل الغَوْر وقيل كل أُخْدُودٍ من أَخاديد الأَرض والجمع أَتْلامٌ وهو التِّلامُ والجمع تُلُم وقيل التِّلامُ أَثَرُ اللُّومَةِ في الأَرض وجمعها التُّلُم --لسان العرب)) signal/slot إذا ضغطنا على زر تُبث emitted إشارة ‎ //​clicked()//​ ‎ والتّلم slot الذي تمرر له هي أي تلم مُعرّف في PtQt أو أي كائن قابل للاستدعاء في بايثون python callable.
 +الطريقة ‎ //​QtCore.QObject.connect()//​ ‎ 
 +تربط الإشارة بالتلم. في حالتنا التلم معرف مسبقا في PyQt وهو ‎ //quit()// ‎ 
 +الإتصال سيتم بين كائنين الأول المرسل (وهو الزر) والمستقبل (وهو كائن التطبيق application object)
 +
 +{{ pyqt4quitbutton.jpg |زر الخروج}}
 +
 +==== صندوق الرسالة ====
 +
 +عادة، عند الضغط على علامة إكس x فى شريط العنوان يتم غلق الودجة، أحيانا نرغب في تعديل هذا السلوك. على سبيل المثال، إن كان لدينا ملف مفتوح فى محرر نصوص قمنا ببعض التعديلات عليه؛ عندها علينا عرض رسالة لتأكيد عملية الإغلاق
 +
 +<code python>
 +#​!/​usr/​bin/​python
 +
 +# messagebox.py
 +
 +import sys
 +from PyQt4 import QtGui
 +
 +
 +class MessageBox(QtGui.QWidget):​
 +    def __init__(self,​ parent=None):​
 +        QtGui.QWidget.__init__(self,​ parent)
 +
 +        self.setGeometry(300,​ 300, 250, 150)
 +        self.setWindowTitle('​message box')
 +
 +
 +    def closeEvent(self,​ event):
 +        reply = QtGui.QMessageBox.question(self,​ '​Message',​
 +            "Are you sure to quit?",​ QtGui.QMessageBox.Yes,​ QtGui.QMessageBox.No)
 +
 +        if reply == QtGui.QMessageBox.Yes:​
 +            event.accept()
 +        else:
 +            event.ignore()
 +
 +app = QtGui.QApplication(sys.argv)
 +qb = MessageBox()
 +qb.show()
 +sys.exit(app.exec_())
 +</​code>​
 +
 +عند غلق الودجة يتم توليد الحدث //​QcloseEvent//​. لتعديل سلوك الودجة يجب علينا إعادة تعريف معالج حدث ‎ //​()closeEvent//​ ‎ 
 +
 +
 +<code python>
 +reply = QtGui.QMessageBox.question(self,​ '​Message',​
 +     "​Are you sure to quit?",​ QtGui.QMessageBox.Yes,​ QtGui.QMessageBox.No)
 +</​code>​
 +نعرض "​صندوف رسالة"​ له زران "​نعم"​ و "​لا"​ وأول معامل هو السلسلة النصية التي تظهر على شريط العنوان والثاني هو محتوى رسالة صندوق الحوار. الرد يوضع ​ في المتغير reply
 +
 +<code python>
 + if reply == QtGui.QMessageBox.Yes:​
 +     ​event.accept()
 + else:
 +    event.ignore()
 +</​code>​
 +
 +هنا نختبر ​ قيمة الرد فإن كان المستخدم قد اختار
 +"​نعم"​ يتم الموافقة على الحدث مما يؤدي لإغلاق الودجة وإلا يتم تجاهل الحدث.
 +
 +{{ pyqt4messagebox.jpg |صندوق Message Box}}
 +
 +==== وضع النافذة فى منتصف الشاشة ====
 +
 +السكربت التالي يبين طريقة توسيط النافذة على سطح المكتب
 +
 +<code python>
 +#​!/​usr/​bin/​python
 +
 +# center.py
 +
 +import sys
 +from PyQt4 import QtGui
 +
 +
 +class Center(QtGui.QWidget):​
 +    def __init__(self,​ parent=None):​
 +        QtGui.QWidget.__init__(self,​ parent)
 +
 +        self.setWindowTitle('​center'​)
 +        self.resize(250,​ 150)
 +        self.center()
 +
 +    def center(self):​
 +        screen = QtGui.QDesktopWidget().screenGeometry()
 +        size =  self.geometry()
 +        self.move((screen.width()-size.width())/​2,​ (screen.height()-size.height())/​2)
 +
 +
 +app = QtGui.QApplication(sys.argv)
 +qb = Center()
 +qb.show()
 +sys.exit(app.exec_())
 +</​code>​
 +وشرحه:
 +<code python>
 +self.resize(250,​ 150)
 +</​code>​
 +
 +هنا نقوم بتحجيم الودجة QWidget إلى 250 بكسل عرض و 150 بكسل ارتفاع
 +
 +<code python>
 +screen = QtGui.QDesktopWidget().screenGeometry()
 +</​code>​
 +
 +نحصل على الاستبانة الخاصة بالشاشة
 +
 +<code python>
 +size =  self.geometry()
 +</​code>​
 +
 +نحصل على الحجم الخاص بالودجة
 +
 +<code python>
 +self.move((screen.width()-size.width())/​2,​ (screen.height()-size.height())/​2)
 +</​code>​
 +نقوم بنقل النافذة الى منتصف الشاشة بإستخدام الطريقة move
 +
 +===== القوائم وشريط الأدوات فى PyQt4 =====
 +
 +==== النافذة الرئيسية ====
 +
 +الصنف QMainWindow يقدم نافذة التطبيق الأساسية. وهذا يتيح إنشاء هيكل التطبيق بشرط حالة statusbar وشريط الأدوات وشريط القوائم
 +
 +==== شريط الحالة ====
 +
 +شريط الحالة هو ودجة تستخدم فى عرض معلومات عن حالة البرنامج (مثل "تم حفظ الملف"​)
 +
 +<code python>
 +#​!/​usr/​bin/​python
 +
 +# statusbar.py ​
 +
 +import sys
 +from PyQt4 import QtGui
 +
 +class MainWindow(QtGui.QMainWindow):​
 +    def __init__(self):​
 +        QtGui.QMainWindow.__init__(self)
 +
 +        self.resize(250,​ 150)
 +        self.setWindowTitle('​statusbar'​)
 +
 +        self.statusBar().showMessage('​Ready'​)
 +
 +
 +app = QtGui.QApplication(sys.argv)
 +main = MainWindow()
 +main.show()
 +sys.exit(app.exec_())
 +</​code>​
 +الشرح:
 +  self.statusBar().showMessage('​Ready'​)
 +
 +للحصول على شريط الحالة نقوم بإستدعاء الطريقة //​statusBar//​ ونقوم بإستدعاء الطريقة //​showMessage//​ لعرض رسالة ما على شريط الحالة ​
 +
 +==== شريط القوائم ====
 +شريط القوائم أحد أوضح الأجزاء فى واجهة التطبيق، هو مجموعة من الأوامر commands موزعةً على القوائم. كان لزاما عليك تذكر الأوامر في تطبيقات المحاورة console أما هنا فلا تحتاج لتذكر الأوامر إذ تجمعت إلى أجزاء منطقية.
 +وهناك معايير معتمدة لتقليل الوقت اللازم لتعلم التطبيقات الجديدة.
 +
 +<code python>
 +#​!/​usr/​bin/​python
 +
 +# menubar.py ​
 +
 +import sys
 +from PyQt4 import QtGui, QtCore
 +
 +class MainWindow(QtGui.QMainWindow):​
 +    def __init__(self):​
 +        QtGui.QMainWindow.__init__(self)
 +
 +        self.resize(250,​ 150)
 +        self.setWindowTitle('​menubar'​)
 +
 +        exit = QtGui.QAction(QtGui.QIcon('​icons/​exit.png'​),​ '​Exit',​ self)
 +        exit.setShortcut('​Ctrl+Q'​)
 +        exit.setStatusTip('​Exit application'​)
 +        self.connect(exit,​ QtCore.SIGNAL('​triggered()'​),​ QtCore.SLOT('​close()'​))
 +
 +        self.statusBar()
 +
 +        menubar = self.menuBar()
 +        file = menubar.addMenu('&​File'​)
 +        file.addAction(exit)
 +
 +app = QtGui.QApplication(sys.argv)
 +main = MainWindow()
 +main.show()
 +sys.exit(app.exec_())
 +</​code>​
 +الشرح:
 +  menubar = self.menuBar()
 +  file = menubar.addMenu('&​File'​)
 +  file.addAction(exit)
 +أولا أنشأنا شريط القائمة بالطريقة //menuBar// من الصنف //​QMainWindow//​. ثم أضفنا قائمة له بالطريقة //​AddMenu//​. وفي النهاية وصلنا كائن العمل action object لقائمة ملف.
 +
 +
 +==== شريط الأدوات ====
 +
 +في حين يقوم شريط القوائم بتجميع كل الأوامر المستخدمه فى التطبيق، يتفرد شريط الأدوات بتقديم وصول سريع لأكثرها استخداما.
 +
 +<code python>
 +#​!/​usr/​bin/​python
 +
 +# toolbar.py ​
 +
 +import sys
 +from PyQt4 import QtGui, QtCore
 +
 +class MainWindow(QtGui.QMainWindow):​
 +    def __init__(self):​
 +        QtGui.QMainWindow.__init__(self)
 +
 +        self.resize(250,​ 150)
 +        self.setWindowTitle('​toolbar'​)
 +
 +        self.exit = QtGui.QAction(QtGui.QIcon('​icons/​exit.png'​),​ '​Exit',​ self)
 +        self.exit.setShortcut('​Ctrl+Q'​)
 +        self.connect(self.exit,​ QtCore.SIGNAL('​triggered()'​),​ QtCore.SLOT('​close()'​))
 +
 +        self.toolbar = self.addToolBar('​Exit'​)
 +        self.toolbar.addAction(self.exit)
 +
 +
 +app = QtGui.QApplication(sys.argv)
 +main = MainWindow()
 +main.show()
 +sys.exit(app.exec_())
 +</​code>​
 +الشرح:
 +
 +  self.exit = QtGui.QAction(QtGui.QIcon('​icons/​exit.png'​),​ '​Exit',​ self)
 +  self.exit.setShortcut('​Ctrl+Q'​)
 +
 +يُتحكم بتطبيقات الواجهة الرسومية GUI  بأوامر، تطلق هذه الأوامر من شريط القوائم أو من القوائم السياقية (بالزر الأيمن مثلا) أو شريط الأدوات أو مفتاح مختصر (CTRL+Q مثلا).
 +تسهّل PyQt التطوير عبر تقديم مفهوم **الأعمل actions**.
 +العمل قد يكون له نص ليظهر في القائمة وأيقونة ومفتاح مختصر ونص الحالة ونص "ما هذا ؟" ونص التلميح.
 +في مثالنا عرّفنا عمل له أيقونة وتلميح واختصار.
 +
 +<code python
 +  self.connect(self.exit,​ QtCore.SIGNAL('​triggered()'​),​ QtCore.SLOT('​close()'​))
 +</​code>​
 +هنا قمنا بربط الإشارة //​triggered//​ الخاصة بالعمل action إلى التلم المعرف مسبقا //close//.
 +
 +<code python>
 +  self.toolbar = self.addToolBar('​Exit'​)
 +  self.toolbar.addAction(self.exit)
 +</​code>​
 +
 +وهنا أنشأنا الشرط ووصنا العمل إليه.
 +
 +{{ pyqt4toolbar.jpg }}
 +
 +==== ضم كل ذلك ====
 +في آخر امثلة هذا الفصل سنقوم بإنشاء شريط قوائم وشريط ادوات وشريط حالة وودجة مركزية.
 +
 +<code python>
 +#​!/​usr/​bin/​python
 +
 +# mainwindow.py ​
 +
 +import sys
 +from PyQt4 import QtGui, QtCore
 +
 +class MainWindow(QtGui.QMainWindow):​
 +    def __init__(self):​
 +        QtGui.QMainWindow.__init__(self)
 +
 +        self.resize(350,​ 250)
 +        self.setWindowTitle('​mainwindow'​)
 +
 +        textEdit = QtGui.QTextEdit()
 +        self.setCentralWidget(textEdit)
 +
 +        exit = QtGui.QAction(QtGui.QIcon('​icons/​exit.png'​),​ '​Exit',​ self)
 +        exit.setShortcut('​Ctrl+Q'​)
 +        exit.setStatusTip('​Exit application'​)
 +        self.connect(exit,​ QtCore.SIGNAL('​triggered()'​),​ QtCore.SLOT('​close()'​))
 +
 +        self.statusBar()
 +
 +        menubar = self.menuBar()
 +        file = menubar.addMenu('&​File'​)
 +        file.addAction(exit)
 +
 +        toolbar = self.addToolBar('​Exit'​)
 +        toolbar.addAction(exit)
 +
 +
 +app = QtGui.QApplication(sys.argv)
 +main = MainWindow()
 +main.show()
 +sys.exit(app.exec_())
 +</​code>​
 +الشرح:
 +<code python>
 +  textEdit = QtGui.QTextEdit()
 +  self.setCentralWidget(textEdit)
 +</​code>​
 +
 +
 +هنا نقوم بإضافة ودجة تحرير النصوص ونجعله الودجة المركزية للنافذة //​QMainWindow//​، الودجة المركزى سيقوم بشغل كل المساحة المتبقية.
 +
 +{{ pyqt4mainwindow.jpg }}
 +
 +===== إدارة المخطط فى PyQt4 =====
 +إن إدارة المخطط (توزيع المساحات في واجهة البرنامج)
 +أمر مهم، فعن طريقها نقوم بتحديد طريقة توزيع الودجات على النافذة (مكانا وحجما).
 +
 +نستطيع إدارتها بطريقين:​ الموقع المطلق أو استخدام احد صنوف التخطيط layout classes
 +
 +==== الموقع المطلق ====
 +
 +وهي أن يحدد المبرمج مكان وحجم كل ودجة بوحدة بكسل، عند استخدامك للتحديد المطلق يجب ان تعلم عدة اشياء:
 +
 +    * عند اعادة تحجيم النافذة ، لا يغيّر مكان وحجم الودجة تبعا لذلك
 +    * التطبيق قد يختلف شكله على المنصات المختلفة
 +    * تغيّر الخطوط قد يفسد تصميم المخطط
 +    * إذا قررت تغيّر المخطط، فإنك ستحتاج الى إعادة تنظيمه بالكامل وهو أمر ممل ويأخذ وقتا
 +
 +
 +<code python>
 +#​!/​usr/​bin/​python
 +
 +# absolute.py
 +
 +import sys
 +from PyQt4 import QtGui
 +
 +
 +class Absolute(QtGui.QWidget):​
 +    def __init__(self,​ parent=None):​
 +        QtGui.QWidget.__init__(self,​ parent)
 +
 +        self.setWindowTitle('​absolute'​)
 +
 +        label = QtGui.QLabel('​ABC',​ self)
 +        label.move(15,​ 10)
 +
 +        label = QtGui.QLabel('​DEF',​ self)
 +        label.move(35,​ 40)
 +
 +        label = QtGui.QLabel('​GHI',​ self)
 +        label.move(55,​ 65)
 +
 +        label = QtGui.QLabel('​JKL',​ self)
 +        label.move(115,​ 65)
 +
 +        label = QtGui.QLabel('​MNO',​ self)
 +        label.move(135,​ 45)
 +
 +        label = QtGui.QLabel('​PQR',​ self)
 +        label.move(115,​ 25)
 +
 +        label = QtGui.QLabel('​STUV',​ self)
 +        label.move(145,​ 10)
 +
 +        label = QtGui.QLabel('​WXYZ',​ self)
 +        label.move(215,​ 10)
 +
 +        self.resize(250,​ 150)
 +
 +app = QtGui.QApplication(sys.argv)
 +qb = Absolute()
 +qb.show()
 +sys.exit(app.exec_())
 +</​code>​
 +
 +ببساطة، نقوم بإستدعاء الطريقة //move// لتحديد موقع كل ودجة.
 +
 +نحدد الموقع بالإحاثي السيني x والصادي y.  نقطة الأصل في نظام الإحداثيات هي الزاوية العليا اليسرى
 +وتزداد فيه قيم س من اليسار إلى اليمين وقيم ص تزداد من أعلى لأسفل.
 +
 +{{ pyqt4absolute.png }}
 +
 +==== المخطط الصندوقي ====
 +
 +إدارة المخطط بصفنوف المخططات أكثر مرونة وعملية. وهى الطريقة المفضلة لوضع ودجات على نافذة.
 +
 +الصفنوف الأساسية ​ هى **QHBoxLayout** و **QVBoxLayout** وهما يرصفان الودجات أفقيا أو عاموديا (على الترتيب).
 +
 +تخيل أننا نريد وضع زرين فى أسفل يمين النافذة. لعمل مثل هذا المخطط سنستخدم صندوق افقى وصندوق رأسى ولإنشاء المساحة الباقية سنستخدم معامل التمدد **stretch factor**.
 +
 +<code python>
 +#​!/​usr/​bin/​python
 +
 +# boxlayout.py
 +
 +import sys
 +from PyQt4 import QtGui
 +
 +
 +class BoxLayout(QtGui.QWidget):​
 +    def __init__(self,​ parent=None):​
 +        QtGui.QWidget.__init__(self,​ parent)
 +
 +        self.setWindowTitle('​box layout'​)
 +
 +        ok = QtGui.QPushButton("​OK"​)
 +        cancel = QtGui.QPushButton("​Cancel"​)
 +
 +        hbox = QtGui.QHBoxLayout()
 +        hbox.addStretch(1)
 +        hbox.addWidget(ok)
 +        hbox.addWidget(cancel)
 +
 +        vbox = QtGui.QVBoxLayout()
 +        vbox.addStretch(1)
 +        vbox.addLayout(hbox)
 +
 +        self.setLayout(vbox)
 +
 +        self.resize(300,​ 150)
 +
 +app = QtGui.QApplication(sys.argv)
 +qb = BoxLayout()
 +qb.show()
 +sys.exit(app.exec_())
 +</​code>​
 +الشرح:
 +  ok = QtGui.QPushButton("​OK"​)
 +  cancel = QtGui.QPushButton("​Cancel"​)
 +
 +هنا نقوم بإنشاء الزرين من الصنف QPushButton ونقوم بتحدد النص الظاهر عليهم كمعاملات للمشيد.
 +
 +  hbox = QtGui.QHBoxLayout()
 +  hbox.addStretch(1)
 +  hbox.addWidget(ok)
 +  hbox.addWidget(cancel)
 +
 +هنا ننشئ صندوق أفقى، ونضيف معامل التمدد بإستخدام الطريقة addStretch ونقوم بإضافة الودجات بإستخدام الطريقة addWidget
 +
 +  vbox = QtGui.QVBoxLayout()
 +  vbox.addStretch(1)
 +  vbox.addLayout(hbox)
 +
 +لإنشاء المخطط النهائي المطلوب، نقوم بإنشاء صندوق عمودي ونضع الأفقي فيه.
 +
 +  self.setLayout(vbox)
 +
 +أخيرا نقوم بتحديد المخطط الرئيسى للنافذة بإستخدام الطريقة setLayout  ​
 +
 +{{ pyqt4boxlayout.jpg }}
 +
 +==== مخطط الشبكة ====
 +
 +اكثر مخطط استخداما هو صنف مخطط الشبكة أو الجدول Grid وفيه يتم تقسيم المساحة إلى صفوف وأعمدة. ونستخدم الصنف QGridLayout لتنفيذ ذلك.
 +
 +<code python>
 +#​!/​usr/​bin/​python
 +
 +# gridlayout.py
 +
 +import sys
 +from PyQt4 import QtGui
 +
 +
 +class GridLayout(QtGui.QWidget):​
 +    def __init__(self,​ parent=None):​
 +        QtGui.QWidget.__init__(self,​ parent)
 +
 +        self.setWindowTitle('​grid layout'​)
 +
 +        names = ['​Cls',​ '​Bck',​ '',​ '​Close',​ '​7',​ '​8',​ '​9',​ '/',​
 +            '​4',​ '​5',​ '​6',​ '​*',​ '​1',​ '​2',​ '​3',​ '​-',​
 +            '​0',​ '​.',​ '​=',​ '​+'​]
 +
 +        grid = QtGui.QGridLayout()
 +
 +        j = 0
 +        pos = [(0, 0), (0, 1), (0, 2), (0, 3),
 +                (1, 0), (1, 1), (1, 2), (1, 3),
 +                (2, 0), (2, 1), (2, 2), (2, 3),
 +                (3, 0), (3, 1), (3, 2), (3, 3 ),
 +                (4, 0), (4, 1), (4, 2), (4, 3)]
 +
 +        for i in names:
 +            button = QtGui.QPushButton(i)
 +            if j == 2:
 +                grid.addWidget(QtGui.QLabel(''​),​ 0, 2)
 +            else: grid.addWidget(button,​ pos[j][0], pos[j][1])
 +            j = j + 1
 +
 +        self.setLayout(grid)
 +
 +
 +
 +app = QtGui.QApplication(sys.argv)
 +qb = GridLayout()
 +qb.show()
 +sys.exit(app.exec_())
 +</​code>​
 +
 +فى مثالنا سننشئ شبكة من الأزرار. وأحد الأماكن سنحتاج فيها لترك فراغ((كان بإمكانه استعمال pass عوضا عن عمل كائن QLabel)) بالودجة QLabel ​
 +
 +  grid = QtGui.QGridLayout()
 +
 +هنا ننشئ كائن مخطط الشبكة باسم grid
 +
 +<code python>
 + if j == 2:
 +     ​grid.addWidget(QtGui.QLabel(''​),​ 0, 2)
 + else: grid.addWidget(button,​ pos[j][0], pos[j][1])
 +</​code>​
 +
 +
 +نقوم بإضافة الودجات بإستخدام الطريقة addWidget ومعاملاتها هنا هم (الودجة، رقم الصف، رقم العمود)
 +
 +{{ pyqt4gridlayout.jpg }}
 +
 +الودجات تستطيع شغل أكثر من عمود أو صف في الشبكة، كما فى المثال التالي
 +
 +<code python>
 +#​!/​usr/​bin/​python
 +
 +# gridlayout2.py
 +
 +import sys
 +from PyQt4 import QtGui
 +
 +
 +class GridLayout2(QtGui.QWidget):​
 +    def __init__(self,​ parent=None):​
 +        QtGui.QWidget.__init__(self,​ parent)
 +
 +        self.setWindowTitle('​grid layout'​)
 +
 +        title = QtGui.QLabel('​Title'​)
 +        author = QtGui.QLabel('​Author'​)
 +        review = QtGui.QLabel('​Review'​)
 +
 +        titleEdit = QtGui.QLineEdit()
 +        authorEdit = QtGui.QLineEdit()
 +        reviewEdit = QtGui.QTextEdit()
 +
 +        grid = QtGui.QGridLayout()
 +        grid.setSpacing(10)
 +
 +        grid.addWidget(title,​ 1, 0)
 +        grid.addWidget(titleEdit,​ 1, 1)
 +
 +        grid.addWidget(author,​ 2, 0)
 +        grid.addWidget(authorEdit,​ 2, 1)
 +
 +        grid.addWidget(review,​ 3, 0)
 +        grid.addWidget(reviewEdit,​ 3, 1, 5, 1)
 +
 +
 +        self.setLayout(grid)
 +        self.resize(350,​ 300)
 +
 +
 +app = QtGui.QApplication(sys.argv)
 +qb = GridLayout2()
 +qb.show()
 +sys.exit(app.exec_())
 +</​code>​
 +الشرح
 +  grid = QtGui.QGridLayout()
 +  grid.setSpacing(10)
 +
 +قمنا بإنشاء grid و بتحديد المسافة بين الودجات بإستخدام الطريقة setSpacing ​
 +
 +
 +  grid.addWidget(reviewEdit,​ 3, 1, 5, 1)
 +
 +إذا قمنا بإضافة ودجة إلى شبكة، نستطيع ان نقوم بتحديد التمدد على الصفوف أو الأعمدة. فى حالتنا هذه قمنا بمد الودجة reviewEdit إلى 5 صفوف
 +
 +
 +{{ pyqt4gridlayout2.jpg }}
 +
 +
 +===== الأحداث والإشارات فى PyQt4 =====
 +
 +
 +فى هذا الجزء سنستكشف للأحداث والإشارات اللتي تحدث فى التطبيقات.
 +
 +==== الأحداث ====
 +
 +
 +الأحداث هى جزء هام في أي تطبيق رسومى، وهى تحدث من المستخدم او من النظام. عندما نستدعى طريقة التطبيق ‎ //exec_()// ‎ 
 +يدخل التطبيق فى الحلقة الأساسية main loop. وهي بدورها تستخرج الأحداث وترسلها إلى الكائنات. قدمت Trolltech (الآن جزء من Nokia) بتقديم آلية فريدة هي الإشارات والأتلام
 +
 +==== الإشارات والأتلام Signals & Slots ====
 +
 +
 +الإشارات تحدث عندما يضغط مستخدم على زر او يحرك منزلق .. إلخ، وقد تحدث من البيئة (على سبيل المثال دقات الساعة)
 +
 +التلم هو طريقة method (الطريقة مصطلح يكافئ الوظيفة أو الدالة في البرمجة الإجرائية) تتفاعل ردا على إشارة. ويمكن أن يكون التلم أي كائن قابل للاستدعاء في بايثون.
 +
 +
 +<code python>
 +#​!/​usr/​bin/​python
 +
 +# sigslot.py
 +
 +import sys
 +from PyQt4 import QtGui, QtCore
 +
 +
 +class SigSlot(QtGui.QWidget):​
 +    def __init__(self,​ parent=None):​
 +        QtGui.QWidget.__init__(self,​ parent)
 +
 +        self.setWindowTitle('​signal & slot')
 +
 +        lcd = QtGui.QLCDNumber(self)
 +        slider = QtGui.QSlider(QtCore.Qt.Horizontal,​ self)
 +
 +        vbox = QtGui.QVBoxLayout()
 +        vbox.addWidget(lcd)
 +        vbox.addWidget(slider)
 +
 +        self.setLayout(vbox)
 +        self.connect(slider, ​ QtCore.SIGNAL('​valueChanged(int)'​),​ lcd, 
 + QtCore.SLOT('​display(int)'​) )
 +
 +        self.resize(250,​ 150)
 +
 +
 +app = QtGui.QApplication(sys.argv)
 +qb = SigSlot()
 +qb.show()
 +sys.exit(app.exec_())
 +</​code>​
 +
 +فى مثالنا، قمنا بعرض رقم LCD مع منزلق. ونقوم بتغيير الرقم بناء على سحب المنزلق ​
 +
 +  self.connect(slider, ​ QtCore.SIGNAL('​valueChanged(int)'​),​ lcd, QtCore.SLOT('​display(int)'​) )
 +
 +هنا قمنا بربط الإشارة valueChanged الخاصة بالمنزلق بالتلم display الخاصة بال LCD
 +للربط يوجد 4 معاملات المرسل وهو الكائن اللذي أرسل الإشارة ثم الإشارة ثم المستقبل للإشارة ثم أخيرا التلم وهى التي ستتفاعل مع الإشارة.
 +
 +
 +{{ pyqt4signalslot.jpg }}
 +
 +==== تعديل سلوك معالج الحدث ====
 +
 +الأحداث في PyQt يتم معالجتها غالبا بإعادة تعريف "​معالج الحدث"​ event handlers (وهي طريقة يتم تنفيذها لمعالجة حدث ما)
 +
 +<code python>
 +#​!/​usr/​bin/​python
 +
 +# escape.py
 +
 +import sys
 +from PyQt4 import QtGui, QtCore
 +
 +class Escape(QtGui.QWidget):​
 +    def __init__(self,​ parent=None):​
 +        QtGui.QWidget.__init__(self,​ parent)
 +
 +        self.setWindowTitle('​escape'​)
 +        self.resize(250,​ 150)
 +
 +    def keyPressEvent(self,​ event):
 +        if event.key() == QtCore.Qt.Key_Escape:​
 +            self.close()
 +
 +app = QtGui.QApplication(sys.argv)
 +qb = Escape()
 +qb.show()
 +sys.exit(app.exec_())
 +</​code>​
 +
 +فى مثالنا، ثمنا بإعادة تعريف معالج الحدث //​keyPressEvent// ​
 +
 +
 +<code python>
 + def keyPressEvent(self,​ event):
 +     if event.key() == QtCore.Qt.Key_Escape:​
 +         ​self.close()
 +</​code>​
 +
 +إذا تم الضغط على زر escape فيتم الخروج من التطبيق
 +
 +==== بث الإشارات ====
 +
 +الكائنات المنشئة من الصنف QtCore.QObject (أو أي صنف يرثها) تستطيع بت إشارات، اذا ضغطنا على زر ما فيتم توليد الإشارة //​clicked//​. في المثال التالي سنرى كيف يتم إرسال الإشارات
 +
 +<code python>
 +#​!/​usr/​bin/​python
 +
 +# emit.py
 +
 +import sys
 +from PyQt4 import QtGui, QtCore
 +
 +
 +class Emit(QtGui.QWidget):​
 +    def __init__(self,​ parent=None):​
 +        QtGui.QWidget.__init__(self,​ parent)
 +
 +        self.setWindowTitle('​emit'​)
 +        self.resize(250,​ 150)
 +        self.connect(self,​ QtCore.SIGNAL('​closeEmitApp()'​),​ QtCore.SLOT('​close()'​) )
 +
 +    def mousePressEvent(self,​ event):
 +        self.emit(QtCore.SIGNAL('​closeEmitApp()'​))
 +
 +app = QtGui.QApplication(sys.argv)
 +qb = Emit()
 +qb.show()
 +sys.exit(app.exec_())
 +</​code>​
 +
 +
 +انشأنا إشارة باسم //​closeEmitApp//​ وسيتم بثها عند الضغط بالفأرة
 +
 +
 +  def mousePressEvent(self,​ event): self.emit(QtCore.SIGNAL('​closeEmitApp()'​))
 +
 +
 +نقوم ببث الإشارة عبر الطريقة emit وهى تأخذ تعريف الإشارة كمعامل لها
 +
 +  self.connect(self,​ QtCore.SIGNAL('​closeEmitApp()'​),​ QtCore.SLOT('​close()'​) )
 +
 +هنا قمنا بربط الإشارة //​closeEmitApp//​ بالتلم //close//
 +
 +===== صناديق الحوار فى PyQt4 =====
 +
 +نوافذ الحوار Dialog مكون حيوي في التطبيقات الرسومية، الحوار هو محادثة بين شخصين أو أكثر، في التطبيقات الحاسوب فإن صندوق الحوار نافذة من خلالها نتحدث مع التطبيق. يمكن استخدامها فى إدخال بيانات ​ وتعديلها أو التحكم فى إعدادات البرنامج.. الخ. صناديق الحوار وسيلة مهمة في التواصل بين المستخدم والحاسوب.
 +
 +هناك نوعان منها: المعرفة مسبقا أو المخصصة.
 +
 +==== صناديق حوار معرفة مسبقا ====
 +
 +==== الصنف QInputDialog ====
 +
 +QinputDialog يقدم طريقة مريحة للحصول على قيمة من المستخدم (سلسلة نصية ، رقم ، عنصر من قائمة)
 +
 +<code python>
 +#​!/​usr/​bin/​python
 +
 +# inputdialog.py
 +
 +import sys
 +from PyQt4 import QtGui
 +from PyQt4 import QtCore
 +
 +class InputDialog(QtGui.QWidget):​
 +    def __init__(self,​ parent=None):​
 +        QtGui.QWidget.__init__(self,​ parent)
 +
 +        self.setGeometry(300,​ 300, 350, 80)
 +        self.setWindowTitle('​InputDialog'​)
 +
 +        self.button = QtGui.QPushButton('​Dialog',​ self)
 +        self.button.setFocusPolicy(QtCore.Qt.NoFocus)
 +
 +        self.button.move(20,​ 20)
 +        self.connect(self.button,​ QtCore.SIGNAL('​clicked()'​),​ self.showDialog)
 +        self.setFocus()
 +
 +        self.label = QtGui.QLineEdit(self)
 +        self.label.move(130,​ 22)
 +
 +
 +    def showDialog(self):​
 +        text, ok = QtGui.QInputDialog.getText(self,​ 'Input Dialog',​ 'Enter your name:'​)
 +
 +        if ok:
 +            self.label.setText(unicode(text))
 +
 +
 +app = QtGui.QApplication(sys.argv)
 +icon = InputDialog()
 +icon.show()
 +app.exec_()
 +</​code>​
 +
 +هذا المثال يشمل زر و line edit. الزر سيقوم بعرض InputDialog للحصول على قيمة، ووضعها فى ال line edit
 +
 +  text, ok = QtGui.QInputDialog.getText(self,​ 'Input Dialog',​ 'Enter your name:'​)
 +
 +هذا السطر يعرض ال input dialog. ,وذلك بإستخدام الطريقة getText ومعاملاته كالتالى:​ الوالد، العنوان، الرسالة .. يعيد هذا ال dialog ​
 +النص المدخل وقيمة منطقية (مرتبطة بالزر الذى ضغطه المستخدم اذا كان زر OK فإن القيمة ستكون True وإلا فإنها False.
 +
 +{{ pyqt4inputdialog.jpg }}
 +
 +==== الصنف QColorDialog ====
 +
 +الصنف QColorDialog يتيح لنا تحديد خطوط ما (اختيار خط)
 +
 +<code python>
 +#​!/​usr/​bin/​python
 +
 +# colordialog.py
 +
 +import sys
 +from PyQt4 import QtGui
 +from PyQt4 import QtCore
 +
 +
 +class ColorDialog(QtGui.QWidget):​
 +    def __init__(self,​ parent=None):​
 +        QtGui.QWidget.__init__(self,​ parent)
 +
 +        color = QtGui.QColor(0,​ 0, 0)
 +
 +        self.setGeometry(300,​ 300, 250, 180)
 +        self.setWindowTitle('​ColorDialog'​)
 +
 +        self.button = QtGui.QPushButton('​Dialog',​ self)
 +        self.button.setFocusPolicy(QtCore.Qt.NoFocus)
 +        self.button.move(20,​ 20)
 +
 +        self.connect(self.button,​ QtCore.SIGNAL('​clicked()'​),​ self.showDialog)
 +        self.setFocus()
 +
 +        self.widget = QtGui.QWidget(self)
 +        self.widget.setStyleSheet("​QWidget { background-color:​ %s }"
 +            % color.name())
 +        self.widget.setGeometry(130,​ 22, 100, 100)
 +
 +
 +    def showDialog(self):​
 +        color = QtGui.QColorDialog.getColor()
 +
 +        self.widget.setStyleSheet("​QWidget { background-color:​ %s }"
 +            % color.name())
 +
 +app = QtGui.QApplication(sys.argv)
 +cd = ColorDialog()
 +cd.show()
 +app.exec_()
 +
 +</​code>​
 +
 +T
 +هذا المثال يعرض زر وودجة ذو خلفية سوداء. سنقوم بتحديد الخلفية عن طريق الناتج من استدعاء الطريقة QColorDialog.getColor
 +
 +  color = QtGui.QColorDialog.getColor()
 +
 +هذا السطر سيقوم بعرض ال QColorDialog وذلك بإستدعاء الطريقة getColor للحصول على لون ما من المستخدم لتغيير لون خلفية الودجة self.widget إليه وذلك بإستخدام الطريقة setStyleSheet ​
 +
 +  self.widget.setStyleSheet("​QWidget { background-color:​ %s }" % color.name())
 +
 +We change the background color using stylesheets.
 +
 +{{ pyqt4colordialog.jpg }}
 +
 +==== الصنف QFontDialog ====
 +
 +
 +QFontDialog هو صف لإختيار الخطوط
 +
 +<code python>
 +#​!/​usr/​bin/​python
 +
 +# fontdialog.py
 +
 +import sys
 +from PyQt4 import QtGui
 +from PyQt4 import QtCore
 +
 +
 +class FontDialog(QtGui.QWidget):​
 +    def __init__(self,​ parent=None):​
 +        QtGui.QWidget.__init__(self,​ parent)
 +
 +        hbox = QtGui.QHBoxLayout()
 +
 +        self.setGeometry(300,​ 300, 250, 110)
 +        self.setWindowTitle('​FontDialog'​)
 +
 +        button = QtGui.QPushButton('​Dialog',​ self)
 +        button.setFocusPolicy(QtCore.Qt.NoFocus)
 +        button.move(20,​ 20)
 +
 +        hbox.addWidget(button)
 +
 +        self.connect(button,​ QtCore.SIGNAL('​clicked()'​),​ self.showDialog)
 +
 +        self.label = QtGui.QLabel('​Knowledge only matters',​ self)
 +        self.label.move(130,​ 20)
 +
 +        hbox.addWidget(self.label,​ 1)
 +        self.setLayout(hbox)
 +
 +
 +    def showDialog(self):​
 +        font, ok = QtGui.QFontDialog.getFont()
 +        if ok:
 +            self.label.setFont(font)
 +
 +
 +app = QtGui.QApplication(sys.argv)
 +cd = FontDialog()
 +cd.show()
 +app.exec_()
 +
 +</​code>​
 +
 +فى مثالنا لدينا زر ونص الساكن ( label) وبإستخدام الصنف QFontDialog نقوم بتغيير الخط على ذلك النص الثابت
 +
 +  hbox.addWidget(self.label,​ 1)
 +
 +هنا نجعل ال self.label قابل للتحجيم، وذلك لمرونة التعامل مع الخطوط وإلا قد لايظهر النص على ال self.label بصورة كاملة ​
 +
 +  font, ok = QtGui.QFontDialog.getFont()
 +
 +نستخدم الطريقة getFont لعرض الdialog للحصول على الخط ​
 +
 +  if ok: self.label.setFont(font)
 +
 +اذا قام المستخدم بضغط الزر OK على الdialog سيتم تغيير الخط على ال self.label “النص الساكن"​
 +
 +{{ pyqt4fontdialog.jpg }}
 +
 +==== الصنف QFileDialog ====
 +
 +الصنف QFileDialog يتيح لنا اختيار ملفات او مجلدات (سواء للفتح او للحفظ)
 +
 +<code python>
 +#​!/​usr/​bin/​python
 +
 +# openfiledialog.py
 +
 +import sys
 +from PyQt4 import QtGui
 +from PyQt4 import QtCore
 +
 +
 +class OpenFile(QtGui.QMainWindow):​
 +    def __init__(self,​ parent=None):​
 +        QtGui.QMainWindow.__init__(self,​ parent)
 +
 +        self.setGeometry(300,​ 300, 350, 300)
 +        self.setWindowTitle('​OpenFile'​)
 +
 +        self.textEdit = QtGui.QTextEdit()
 +        self.setCentralWidget(self.textEdit)
 +        self.statusBar()
 +        self.setFocus()
 +
 +        exit = QtGui.QAction(QtGui.QIcon('​open.png'​),​ '​Open',​ self)
 +        exit.setShortcut('​Ctrl+O'​)
 +        exit.setStatusTip('​Open new File')
 +        self.connect(exit,​ QtCore.SIGNAL('​triggered()'​),​ self.showDialog)
 +
 +        menubar = self.menuBar()
 +        file = menubar.addMenu('&​File'​)
 +        file.addAction(exit)
 +
 +    def showDialog(self):​
 +        filename = QtGui.QFileDialog.getOpenFileName(self,​ 'Open file',
 +                    '/​home'​)
 +        file=open(filename)
 +        data = file.read()
 +        self.textEdit.setText(data)
 +
 +app = QtGui.QApplication(sys.argv)
 +cd = OpenFile()
 +cd.show()
 +app.exec_()
 +
 +</​code>​
 +
 +Th
 +هذا المثال يعرض شريط وقوائم وودجة مركزى TextEdit "​ودجة النصوص"​ وشريط الحالة.
 +
 +عنصر القائمة يقوم بعرض QFileDialog لإختيار ملف ما، وعرض محتواه على ودجة النصوص
 +
 +
 +<code python>
 +class OpenFile(QtGui.QMainWindow):​
 +...
 +        self.textEdit = QtGui.QTextEdit()
 +        self.setCentralWidget(self.textEdit)
 +</​code>​
 +
 +
 +هذا المثال مبنى على الصنف QMainWidget وذلك لتحديد الودجة المركزى وذلك يتم بسهولة مع الصنف QMainWindow ​
 +
 +  filename = QtGui.QFileDialog.getOpenFileName(self,​ 'Open file', '/​home'​)
 +
 +
 +هنا نقوم بعرض ال QFileDialog للحصول على ملف ما للفتح بإستخدام الطريقة getOpenFileName ولها معاملات(الأب ، العنوان، ومسار البداية)، والفلتر المستخدم افتراضيا هو كل الملفات (*)
 +
 +  file=open(filename)
 +  data = file.read()
 +  self.textEdit.setText(data)
 +
 +يتم قراءة الملف بإستخدام الطريقة read الخاصة بال file object ويتم عرض المحتوى بإستخدام الطريقة setText الخاصة بالودجة textEdit
 +
 +{{ pyqt4filedialog.jpg }}
 +
 +===== الودجات =====
 +
 +الودجات هى وحدات البناء للتطبيق، وتقدم لنا PyQt عدد كبير من الودجات المختلفة كالأزرار والمنزلقات والصناديق المنطقية والقوائم.. الخ وكل مايحتاجه المبرمج لعمله. سنقوم بالتعرض لبعض الودجات المفيدة فى هذه الجزئية
 +
 +==== الصنف QCheckBox ====
 +
 +
 +QCheckBox هو ودجة بحالتين on “منشط"​ او off “غير منشط"​ ويشمل نص للوصف.عند اى تغيير فى الحالة يتم ارسال الإشارة statChanged()
 +
 +<code python>
 +#​!/​usr/​bin/​python
 +
 +# checkbox.py
 +
 +import sys
 +from PyQt4 import QtGui
 +from PyQt4 import QtCore
 +
 +
 +class CheckBox(QtGui.QWidget):​
 +    def __init__(self,​ parent=None):​
 +        QtGui.QWidget.__init__(self,​ parent)
 +
 +        self.setGeometry(300,​ 300, 250, 150)
 +        self.setWindowTitle('​Checkbox'​)
 +
 +        self.cb = QtGui.QCheckBox('​Show title',​ self)
 +        self.cb.setFocusPolicy(QtCore.Qt.NoFocus)
 +        self.cb.move(10,​ 10)
 +        self.cb.toggle();​
 +        self.connect(self.cb,​ QtCore.SIGNAL('​stateChanged(int)'​),​ self.changeTitle)
 +
 +    def changeTitle(self,​ value):
 +        if self.cb.isChecked():​
 +            self.setWindowTitle('​Checkbox'​)
 +        else:
 +            self.setWindowTitle(''​)
 +
 +app = QtGui.QApplication(sys.argv)
 +icon = CheckBox()
 +icon.show()
 +app.exec_()
 +</​code>​
 +
 +
 +فى مثالنا سننشئ اوبجكت من ال CheckBox ليتحكم فى عرض عنوان النافذة.
 +
 +
 +  self.cb = QtGui.QCheckBox('​Show title',​ self)
 +
 +هذا هو المشيد الخاص بال QCheckBox وفيه النص المرفق معه والأب
 +
 +  self.cb.setFocusPolicy(QtCore.Qt.NoFocus)
 +
 +الصنف QCheckBox بيقبل حدوث تركيز عليه. قمت هنا بمنع ذلك لمنع ظهور ذلك المستطيل البشع!
 +
 +نربط السلوت "​الطريقة اللتى سنقوم بتعريفها changeTitle” بإشارة تغيير الحالة stateChanged ​
 +
 +  self.connect(self.cb,​ QtCore.SIGNAL('​stateChanged(int)'​),​ self.changeTitle)
 +
 +<code python>
 +    def changeTitle(self,​ value):
 +        if self.cb.isChecked():​
 +            self.setWindowTitle('​Checkbox'​)
 +        else:
 +            self.setWindowTitle(''​)
 +
 +</​code>​
 +
 +قمنا بعكس الحالة الخاصة بال checkbox بإستخدام الطريقة toggle()، افتراضيا عنوان النافذة غير ظاهر وال checkbox غير منشط
 +
 +  self.cb.toggle();​
 +
 +We set the window title, so we must also check the checkbox. By default, the window title is not set and the
 +check box is unchecked.
 +
 +
 +{{ pyqt4checkbox.jpg }}
 +
 +==== الصنف ToggleButton ====
 +
 +PyQt لاتشمل ToggleButton “زر بحالتين مشابه لل CheckBox”، لذا لإنشاءه بنستخدم الصنف QPushButton بطريقة خاصة.
 +
 +
 +<code python>
 +#​!/​usr/​bin/​python
 +
 +# togglebutton.py
 +
 +import sys
 +from PyQt4 import QtGui
 +from PyQt4 import QtCore
 +
 +
 +class ToggleButton(QtGui.QWidget):​
 +    def __init__(self,​ parent=None):​
 +        QtGui.QWidget.__init__(self,​ parent)
 +
 +        self.color = QtGui.QColor(0,​ 0, 0) 
 +
 +        self.setGeometry(300,​ 300, 280, 170)
 +        self.setWindowTitle('​ToggleButton'​)
 +
 +        self.red = QtGui.QPushButton('​Red',​ self)
 +        self.red.setCheckable(True)
 +        self.red.move(10,​ 10)
 +
 +        self.connect(self.red,​ QtCore.SIGNAL('​clicked()'​),​ self.setRed)
 +
 +        self.green = QtGui.QPushButton('​Green',​ self)
 +        self.green.setCheckable(True)
 +        self.green.move(10,​ 60)
 +
 +        self.connect(self.green,​ QtCore.SIGNAL('​clicked()'​),​ self.setGreen)
 +
 +        self.blue = QtGui.QPushButton('​Blue',​ self)
 +        self.blue.setCheckable(True)
 +        self.blue.move(10,​ 110)
 +
 +        self.connect(self.blue,​ QtCore.SIGNAL('​clicked()'​),​ self.setBlue)
 +
 +        self.square = QtGui.QWidget(self)
 +        self.square.setGeometry(150,​ 20, 100, 100)
 +        self.square.setStyleSheet("​QWidget { background-color:​ %s }" % self.color.name())
 +
 +        QtGui.QApplication.setStyle(QtGui.QStyleFactory.create('​cleanlooks'​))
 +
 +    def setRed(self):​
 +        if self.red.isChecked():​
 +            self.color.setRed(255)
 +        else: self.color.setRed(0)
 +
 +        self.square.setStyleSheet("​QWidget { background-color:​ %s }" % self.color.name())
 +
 +    def setGreen(self):​
 +        if self.green.isChecked():​
 +            self.color.setGreen(255)
 +        else: self.color.setGreen(0)
 +
 +        self.square.setStyleSheet("​QWidget { background-color:​ %s }" % self.color.name())
 +
 +    def setBlue(self):​
 +        if self.blue.isChecked():​
 +            self.color.setBlue(255)
 +        else: self.color.setBlue(0)
 +
 +        self.square.setStyleSheet("​QWidget { background-color:​ %s }" % self.color.name())
 +
 +
 +
 +app = QtGui.QApplication(sys.argv)
 +tb = ToggleButton()
 +tb.show()
 +app.exec_()
 +</​code>​
 +
 +
 +فى مثالنا قمنا بإنشاء ثلاثة ToggleButtons وانشئنا ودجة وحددنا خلفيته باللون الأسود وال3 ازرار سيقومو بعكس الحالة بين الألوان الأحمر والأخضر والأزرق
 +
 +
 +  self.color = QtGui.QColor(0,​ 0, 0)
 +
 +Tهذا هو اللون الأساسى "​اسود"​ "لا أحمر ولا أخضر ولاأزرق" ​
 +
 +  self.red = QtGui.QPushButton('​Red',​ self)
 +  self.red.setCheckable(True)
 +
 +لإنشاء Toggle Button نقوم بإنشاء زر عادى من الصنف QPushButton ولكن نقوم بتحديد قابليته للتنشيط بإستخدام الطريقة setCheckable ​
 +
 +
 +  self.connect(self.red,​ QtCore.SIGNAL('​clicked()'​),​ self.setRed)
 +ونقوم بربط الإشارة clicked بطريقتنا المعرفة setRed
 +
 +  QtGui.QApplication.setStyle(QtGui.QStyleFactory.create('​cleanlooks'​))
 +
 +هنا قمنا بتحديد الإستايل الخاص ل cleanlooks وذلك بإستخدام الطريقة setStyle بالتطبيق بسبب عدم وضوع شكل ال Toggle Button على plastique
 +
 +  if self.red.isChecked():​ self.color.setRed(255)
 +  else: self.color.setRed(0)
 +
 +هنا نختبر اذا كان الزر الخاص باللون الأحمر منشط او لا بإستخدام الطريقة isChecked ونقوم بتحديد اللون ​
 +  self.square.setStyleSheet("​QWidget { background-color:​ %s }" % self.color.name())
 +
 +قمنا بتغيير لون خلفية الودجة بإستخدام الطريقة setStyleSheet مع تحديد ​  ​background-color كخاصية ليتم تعديلها
 +
 +وهكذا مع باقى الأزرار
 +
 +{{ pyqt4togglebutton.jpg }}
 +
 +==== الصنوف QSlider, QLabel ====
 +
 +المنزلق هو ودجة مع مقبض بسيط يمكن سحبه للأمام او للخلف، وذلك لإختيار قيمة ما. قد يكون استخدامه فى بعض الأحيان طبيعيا اكثر من ادخال رقم او استخدام spin box “ودجة مع اسهم لأعلى واسفل لزيادة القيمة وتقليلها"​
 +
 +ال QLabel بيستخدم فى عرض نص ساكن او ربما صورة.
 +
 +فى مثالنا سنعرض منزلق مع label “نعرض عليه صورة"​ وسيتم التحكم فيها بإستخدام المنزلق
 +
 +
 +<code python>
 +#​!/​usr/​bin/​python
 +
 +# slider-label.py
 +
 +import sys
 +from PyQt4 import QtGui
 +from PyQt4 import QtCore
 +
 +class SliderLabel(QtGui.QWidget):​
 +    def __init__(self,​ parent=None):​
 +        QtGui.QWidget.__init__(self,​ parent)
 +
 +        self.setGeometry(300,​ 300, 250, 150)
 +        self.setWindowTitle('​SliderLabel'​)
 +
 +        self.slider = QtGui.QSlider(QtCore.Qt.Horizontal,​ self)
 +        self.slider.setFocusPolicy(QtCore.Qt.NoFocus)
 +        self.slider.setGeometry(30,​ 40, 100, 30)
 +        self.connect(self.slider,​ QtCore.SIGNAL('​valueChanged(int)'​),​ self.changeValue)
 +
 +
 +        self.label = QtGui.QLabel(self)
 +        self.label.setPixmap(QtGui.QPixmap('​mute.png'​))
 +        self.label.setGeometry(160,​ 40, 80, 30)
 +
 +
 +    def changeValue(self,​ value):
 +        pos = self.slider.value()
 +
 +        if pos == 0:
 +            self.label.setPixmap(QtGui.QPixmap('​mute.png'​))
 +        elif pos > 0 and pos <= 30:
 +            self.label.setPixmap(QtGui.QPixmap('​min.png'​))
 +        elif pos > 30 and pos < 80:
 +            self.label.setPixmap(QtGui.QPixmap('​med.png'​))
 +        else:
 +            self.label.setPixmap(QtGui.QPixmap('​max.png'​))
 +
 +app = QtGui.QApplication(sys.argv)
 +icon = SliderLabel()
 +icon.show()
 +app.exec_()
 +
 +</​code>​
 +
 +فى مثالنا نحاكى اداة التحكم فى الصوت بجذب المقبض الخاص بالمنزل فيتم تغيير الصورة
 +
 +
 +  self.slider = QtGui.QSlider(QtCore.Qt.Horizontal,​ self)
 +
 +قمنا بإنشاء منزلق افقى
 +
 +
 +  self.label = QtGui.QLabel(self)
 +  self.label.setPixmap(QtGui.QPixmap('​mute.png'​))
 +
 +
 +انشأنا Label وحددنا الصورة الظاهرة "​بصوت صامت"​ بإستخدام الطريقة setPixmap التى تأخذ اوبجكت من الصنف QPixmap “المشيد له يحوى مسار الصورة"​
 +
 +  self.connect(self.slider,​ QtCore.SIGNAL('​valueChanged(int)'​),​ self.changeValue)
 +
 +هنا قمنا بربط اشارة التغيير فى حالة المنزلق valueChanged الى الطريقة اللتى عرفناها changeValue
 +<code python>
 +    def changeValue(self,​ value):
 +        pos = self.slider.value()
 +
 +        if pos == 0:
 +            self.label.setPixmap(QtGui.QPixmap('​mute.png'​))
 +        elif pos > 0 and pos <= 30:
 +            self.label.setPixmap(QtGui.QPixmap('​min.png'​))
 +        elif pos > 30 and pos < 80:
 +            self.label.setPixmap(QtGui.QPixmap('​med.png'​))
 +        else:
 +            self.label.setPixmap(QtGui.QPixmap('​max.png'​))
 +</​code>​
 +
 +  pos = self.slider.value()
 +
 +هنا نحصل على القيمة الحالية للمنزلق بإستخدام الطريقة value ونغير الصورة بناء عليها.
 +{{ pyqt4sliderlabel.jpg }}
 +
 +==== الصنف QProgressBar ====
 +
 +الصنف QProgressBar هو ودجة يستخدم فى تتبع مدى انتهاء المهمات (مفيدة للمستخدم)، قد يكون افقى او رأسى. يتم تقسيم المهمة الى خطوات يقوم المبرمج بتحديد اقل قيمة واعلى قيمة (الإفتراضى هو 0و 99)
 +
 +
 +<code python>
 +#​!/​usr/​bin/​python
 +
 +# progressbar.py
 +
 +import sys
 +from PyQt4 import QtGui
 +from PyQt4 import QtCore
 +
 +
 +class ProgressBar(QtGui.QWidget):​
 +    def __init__(self,​ parent=None):​
 +        QtGui.QWidget.__init__(self,​ parent)
 +
 +        self.setGeometry(300,​ 300, 250, 150)
 +        self.setWindowTitle('​ProgressBar'​)
 +
 +        self.pbar = QtGui.QProgressBar(self)
 +        self.pbar.setGeometry(30,​ 40, 200, 25)
 +
 +        self.button = QtGui.QPushButton('​Start',​ self)
 +        self.button.setFocusPolicy(QtCore.Qt.NoFocus)
 +        self.button.move(40,​ 80)
 +
 +        self.connect(self.button,​ QtCore.SIGNAL('​clicked()'​),​ self.onStart)
 +
 +        self.timer = QtCore.QBasicTimer()
 +        self.step = 0;
 +
 +
 +    def timerEvent(self,​ event):
 +        if self.step >= 100:
 +            self.timer.stop()
 +            return
 +        self.step = self.step + 1
 +        self.pbar.setValue(self.step)
 +
 +    def onStart(self):​
 +        if self.timer.isActive():​
 +            self.timer.stop()
 +            self.button.setText('​Start'​)
 +        else:
 +            self.timer.start(100,​ self)
 +            self.button.setText('​Stop'​)
 +
 +
 +app = QtGui.QApplication(sys.argv)
 +icon = ProgressBar()
 +icon.show()
 +app.exec_()
 +
 +</​code>​
 +
 +فى مثالنا لدينا progress bar افقى مع زر يتحكم فىه "​ايقاف واستمرار"​
 +
 +  self.pbar = QtGui.QProgressBar(self)
 +
 +المشيد الخاص بالQProgressBar
 +
 +  self.timer = QtCore.QBasicTimer()
 +
 +لتنشيط ال progressbar نستخدم مؤقت
 +
 +  self.timer.start(100,​ self)
 +
 +هنا نقوم بتحديد ال timeout والمستقبل للحدث بإستخدام الطريقة start
 +
 +<code python>
 + def timerEvent(self,​ event):
 +     if self.step >= 100:
 +         ​self.timer.stop()
 +         ​return
 +     ​self.step = self.step + 1
 +     ​self.pbar.setValue(self.step)
 +</​code>​
 +
 +
 +لكل مايرث QObject له معالج حدث timerEvent وللتعامل مع ذلك الحدث نقوم بإعادة تعريفه
 +
 +{{ pyqt4progressbar.jpg }}
 +
 +==== الصنف QCalendarWidget ====
 +QCalendarWidget تستخدم لتوفير نتيجة شهرية، تتيح للمستخدم اختيار تاريخ ما
 +
 +<code python>
 +
 +#​!/​usr/​bin/​python
 +
 +# calendar.py
 +
 +import sys
 +from PyQt4 import QtGui
 +from PyQt4 import QtCore
 +
 +
 +class Calendar(QtGui.QWidget):​
 +    def __init__(self,​ parent=None):​
 +        QtGui.QWidget.__init__(self,​ parent)
 +
 +        self.setGeometry(300,​ 300, 350, 300)
 +        self.setWindowTitle('​Calendar'​)
 +
 +        self.cal = QtGui.QCalendarWidget(self)
 +        self.cal.setGridVisible(True)
 +        self.cal.move(20,​ 20)
 +        self.connect(self.cal,​ QtCore.SIGNAL('​selectionChanged()'​),​ self.showDate)
 +
 +
 +        self.label = QtGui.QLabel(self)
 +        date = self.cal.selectedDate()
 +        self.label.setText(str(date.toPyDate()))
 +        self.label.move(130,​ 260)
 +
 +
 +    def showDate(self):​
 +        date = self.cal.selectedDate()
 +        self.label.setText(str(date.toPyDate()))
 +
 +
 +app = QtGui.QApplication(sys.argv)
 +icon = Calendar()
 +icon.show()
 +app.exec_()
 +
 +</​code>​
 +
 +هذا المثال يشمل نتيجة وlabel والتاريخ المختار يتم عرضه على ذلك ال label
 +
 +  self.cal = QtGui.QCalendarWidget(self)
 +
 +ننشئ اوبجكت من QCalendarWidget
 +
 +  self.connect(self.cal,​ QtCore.SIGNAL('​selectionChanged()'​),​ self.showDate)
 +
 +إذا قمنا بإختيار تاريخ ما فسيتم ارسال الإشارة selectionChanged ، لذا سنقوم بربطها بطريقة showData التى سنعرفها لعرض التاريخ على ال label
 +
 +<code python>
 + def showDate(self):​
 +     date = self.cal.selectedDate()
 +     ​self.label.setText(str(date.toPyDate()))
 +</​code>​
 +
 +هنا حصلنا على التاريخ بإستخدام الطريقة selectedDate وقمنا بتحويله إلى سلسلة نصية بإستخدام str 
 +{{ pyqt4calendar.jpg }}
 +
 +===== السحب والإفلات =====
 +
 +فى هذه الجزئية سنتحدث عن السحب والإفلات
 +
 +
 +In computer graphical user interfaces, drag-and-drop is the action of (or support for the action of) clicking on a virtual object and dragging it to a different location or onto another virtual object. In general, it can be used to invoke many kinds of actions, or create various types of associations between two abstract objects. (Wikipedia)
 +
 +السحب والإفلات احد اوضح جوانب الواجهات الرسومية. عمليات السحب والإفلات تتيح عمل اشياء معقدة ​
 +
 +عادة نستطيع ان نسحب ونفلت نوعين بيانات او بعض الأوبجكتس الرسومية. اذا سحبنا صورة من تطبيق لآخر فإننا نقوم بسحب وإفلات بيانات binary اذا قمنا بسحب نافذة فى متصفح فايرفوكس من مكان لآخر فإننا نقوم بسحب مكون رسومى
 +
 +==== سحب وإفلات بسيط ====
 +
 +فى هذا المثال لدينا QLineEdit و QPushButton اوبجكتس. سنقوم بحسب النص على ودجة النص وإفلاته على الزر
 +
 +In the first example, we will have a **QLineEdit** and a **QPushButton**. We will drag plain text from 
 +the line edit widget and drop it onto the button widget. ​
 +
 +<code python>
 +#​!/​usr/​bin/​python
 +
 +# dragdrop.py
 +
 +import sys
 +from PyQt4 import QtGui
 +
 +class Button(QtGui.QPushButton):​
 +    def __init__(self,​ title, parent):
 +        QtGui.QPushButton.__init__(self,​ title, parent)
 +        self.setAcceptDrops(True)
 +
 +    def dragEnterEvent(self,​ event):
 +        if event.mimeData().hasFormat('​text/​plain'​):​
 +            event.accept()
 +        else:
 +            event.ignore() ​
 +
 +    def dropEvent(self,​ event):
 +     self.setText(event.mimeData().text()) ​
 +
 +
 +class DragDrop(QtGui.QDialog):​
 +    def __init__(self,​ parent=None):​
 +        QtGui.QDialog.__init__(self,​ parent)
 +
 +        self.resize(280,​ 150)
 +        self.setWindowTitle('​Simple Drag & Drop')
 +
 +        edit = QtGui.QLineEdit('',​ self)
 + edit.setDragEnabled(True)
 +        edit.move(30,​ 65)
 +
 +        button = Button("​Button",​ self)
 +        button.move(170,​ 65)
 +
 +
 + screen = QtGui.QDesktopWidget().screenGeometry()
 +        size =  self.geometry()
 +        self.move((screen.width()-size.width())/​2, ​
 +            (screen.height()-size.height())/​2)
 +
 +app = QtGui.QApplication(sys.argv)
 +icon = DragDrop()
 +icon.show()
 +app.exec_()
 +</​code>​
 +
 +<code python>
 + class Button(QtGui.QPushButton):​
 +     def __init__(self,​ title, parent):
 +         ​QtGui.QPushButton.__init__(self,​ title, parent)
 +</​code>​
 +
 +لإتاحة الإفلات على الزر يجب علينا اعادة تعريف بعض الطرق فى الصنف الخاص بنا الذى سيرث من الصنف QPushButton
 +
 +  self.setAcceptDrops(True)
 +
 +هنا نتيج امكانيه احداث الإفلات على للصف ​
 +
 +<code python>
 + def dragEnterEvent(self,​ event):
 +     if event.mimeData().hasFormat('​text/​plain'​):​
 +         ​event.accept()
 +     else:
 +         ​event.ignore() ​
 +</​code>​
 +
 +
 +اولا نعيد تعريف معالج الحدث dragEnterEvent() لنحدد البيانات اللتى سنقبلها، فى حالتنا هى مجرد نص عادى.
 +
 +
 +<code python>
 + def dropEvent(self,​ event):
 +     ​self.setText(event.mimeData().text()) ​
 +</​code>​
 +
 +
 +بإعادة تعريف معالج الحدث dropEvent() سنقوم بتعريف ماذا سنفعل اثناء الإفلات. هنا سنقوم بتغير النص الظاهر على الزر.
 +
 +<code python>
 + edit = QtGui.QLineEdit('',​ self)
 + ​edit.setDragEnabled(True)
 +</​code>​
 +
 +الصنف QLineEdit لديه دعم داخلى لعمليات السحب. كل ماعلينا هو اتاحتها بإستخدام الطريقة setDragEnabled
 +
 +{{ pyqt4simpledd.png }}
 +
 +==== سحب وإفلات زر ====
 +
 +فى المثال التالى سنتعرض لكيفية سحب وإفلات زر
 +
 +
 +<code python>
 +#​!/​usr/​bin/​python
 +
 +# dragbutton.py
 +
 +import sys
 +from PyQt4 import QtGui
 +from PyQt4 import QtCore
 +
 +class Button(QtGui.QPushButton):​
 +    def __init__(self,​ title, parent):
 +        QtGui.QPushButton.__init__(self,​ title, parent)
 +
 +    def mouseMoveEvent(self,​ event):
 +
 +        if event.buttons() != QtCore.Qt.RightButton:​
 +     return
 +
 +        mimeData = QtCore.QMimeData()
 +
 +        drag = QtGui.QDrag(self)
 +        drag.setMimeData(mimeData)
 +        drag.setHotSpot(event.pos() - self.rect().topLeft())
 +
 +        dropAction = drag.start(QtCore.Qt.MoveAction)
 +
 +        if dropAction == QtCore.Qt.MoveAction:​
 +            self.close()
 +
 +    def mousePressEvent(self,​ event):
 +        QtGui.QPushButton.mousePressEvent(self,​ event)
 +        if event.button() == QtCore.Qt.LeftButton:​
 +     print '​press'​
 +
 +
 +
 +class DragButton(QtGui.QDialog):​
 +    def __init__(self,​ parent=None):​
 +        QtGui.QDialog.__init__(self,​ parent)
 +
 +        self.resize(280,​ 150)
 +        self.setWindowTitle('​Click or Move')
 + self.setAcceptDrops(True)
 +
 +        self.button = Button('​Close',​ self)
 +        self.button.move(100,​ 65)
 +
 +
 + screen = QtGui.QDesktopWidget().screenGeometry()
 +        size =  self.geometry()
 +        self.move((screen.width()-size.width())/​2, ​
 +            (screen.height()-size.height())/​2)
 +
 +
 +    def dragEnterEvent(self,​ event):
 + event.accept()
 +
 +    def dropEvent(self,​ event):
 +
 +        position = event.pos()
 +        button = Button('​Close',​ self)
 +        button.move(position)
 +        button.show()
 +
 +        event.setDropAction(QtCore.Qt.MoveAction)
 +        event.accept()
 +
 +
 +app = QtGui.QApplication(sys.argv)
 +db = DragButton()
 +db.show()
 +app.exec_()
 +</​code>​
 +
 +فى كود المثال، لدينا زر من الصنف QPushButton على النافذة اذا تم الضغط عليه بالزر الأيسر للفأرة سيطبع كلمة press على الكونسول. وفى حال الضغط بالزر الأيمن والتحريك سنقوم بعمل عملية سحب وإفلات على الزر.
 +
 +
 +<code python>
 + class Button(QtGui.QPushButton):​
 +     def __init__(self,​ title, parent):
 +         ​QtGui.QPushButton.__init__(self,​ title, parent)
 +
 +</​code>​
 +
 +
 +هنا انشأنا صف Button يرث QPushButton ​
 +سنقوم ايضا بإعادة تعريف معالجى الأحداث mouseMoveEvent() و mousePressEvent()
 +حيث mouseMoveEvent هى المكان الذى بدأت عنده عملية السحب والإفلات.
 +
 +  if event.buttons() != QtCore.Qt.RightButton:​ return
 +
 +هنا قررنا، اننا سنقوم بعملية السحب والإفلات عن طريق الزر الأيمن للفأرة. الزر الأيسر للفأرة محجوز للضغط على الزر
 +
 +<code python>
 + ​mimeData = QtCore.QMimeData()
 +
 + drag = QtGui.QDrag(self)
 + ​drag.setMimeData(mimeData)
 + ​drag.setHotSpot(event.pos() - self.rect().topLeft())
 +</​code>​
 +
 +هنا ننشئ اوبجكت من الصنف QDrag  ​
 +
 +<code python>
 + ​dropAction = drag.start(QtCore.Qt.MoveAction)
 +
 + if dropAction == QtCore.Qt.MoveAction:​
 +     ​self.close()
 +</​code>​
 +
 +الطريقة start() تبدأ عملية السحب والإفلات. عند سحب الزر فإننا نقوم بحذف الودجة من المكان الأول ونعيد انشاءه بالمكان الجديد
 +
 +
 +<code python>
 + def mousePressEvent(self,​ event):
 +     ​QtGui.QPushButton.mousePressEvent(self,​ event)
 +     if event.button() == QtCore.Qt.LeftButton:​
 +         print '​press'​
 +</​code>​
 +
 +
 +عند الضغط على الزر الأيسر يتم طباعة كلمة press 
 +لاحظ استدعائنا لمعالج الحدث mousePressEvent الخاص بالأب ايضا وإلا لن نرى الزر يتم الضغط عليه.
 +
 +
 +<code python>
 + ​position = event.pos()
 + ​button = Button('​Close',​ self)
 + ​button.move(position)
 + ​button.show()
 +</​code>​
 +
 +
 +فى معالج الحدث dropEvent نكتب ماسيحدث بعد افلات الفأرة، نقل الزر واظهاره.
 +فى مثالنا، قمنا بإعادة انشاء الزر فى الموقع الجديد عن مؤشر الفأرة
 +
 +<code python>
 + ​event.setDropAction(QtCore.Qt.MoveAction)
 + ​event.accept()
 +</​code>​
 +
 +نحدد نوع الإفلات، فى حالتنا هذه هو Move
 +
 +
 +
 +===== الرسم فى PyQt4 =====
 +
 +نستخدم الرسم عندما نريد تعديل او تحسين زر موجود او حتى انشاءه من الصفر.
 +للقيام بالرسم، نستخدم واجهة التطبيق البرمجة API التى تقدمها لكن PyQt4 
 +يتم الرسم داخل الطريقة paintEvent ويوضع كود الرسم بين الطريقتين begin()و end() وهما طريقتين خاصين بال QPainter اوبجكت
 +
 +
 +==== رسم نص ====
 +
 +نبدأ برسم بعض النصوص على النافذة
 +We begin with drawing some unicode text onto the window client area.
 +
 +<code python>
 +#​!/​usr/​bin/​python
 +
 +# drawtext.py
 +
 +import sys
 +from PyQt4 import QtGui, QtCore
 +
 +
 +class DrawText(QtGui.QWidget):​
 +    def __init__(self,​ parent=None):​
 +        QtGui.QWidget.__init__(self,​ parent)
 +
 +        self.setGeometry(300,​ 300, 250, 150)
 +        self.setWindowTitle('​Draw Text')
 +
 +       ​self.text = u'​\u041b\u0435\u0432 \u041d\u0438\u043a\u043e\u043b\u0430\
 +\u0435\u0432\u0438\u0447 \u0422\u043e\u043b\u0441\u0442\u043e\u0439:​ \n\
 +\u0410\u043d\u043d\u0430 \u041a\u0430\u0440\u0435\u043d\u0438\u043d\u0430'​
 +
 +
 +
 +    def paintEvent(self,​ event):
 +        paint = QtGui.QPainter()
 +        paint.begin(self)
 +        paint.setPen(QtGui.QColor(168,​ 34, 3))
 +        paint.setFont(QtGui.QFont('​Decorative',​ 10))
 +        paint.drawText(event.rect(),​ QtCore.Qt.AlignCenter,​ self.text)
 +        paint.end()
 +
 +
 +app = QtGui.QApplication(sys.argv)
 +dt = DrawText()
 +dt.show()
 +app.exec_()
 +</​code>​
 +
 +فى مثالنا نكتب بعض النصوص بazbuka. ذلك النص محاذى رأسيا وافقيا
 +In our example, we draw some text in azbuka. The text is vertically and horizontally aligned.
 +
 +  def paintEvent(self,​ event):
 +
 +الرسم يتم فى معالج الحدث paintEvent
 +
 +  paint = QtGui.QPainter()
 +  paint.begin(self)
 +  ...
 +  paint.end()
 +
 +
 +الصنف QPainter مسئول عن كل عمليات الرسم المنخفضة. كل عمليات الرسم تكون بين الطرق begin() و end()
 +
 +  paint.setPen(QtGui.QColor(168,​ 34, 3))
 +  paint.setFont(QtGui.QFont('​Decorative',​ 10))
 +
 +هنا نحدد قلما وخطا، لنستخدمهم فى رسم النص
 +
 +
 +  paint.drawText(event.rect(),​ QtCore.Qt.AlignCenter,​ self.text)
 +
 +الطريقة drawText() تقوم برسم النص على النافذة
 +
 +
 +{{ pyqt4drawtext.jpg }}
 +
 +==== رسم نقاط ====
 +
 +النقطة هى ابسط كائن رسومى، مجرد بقعة صغيرة على النافذة.
 +
 +<code python>
 +#​!/​usr/​bin/​python
 +
 +# points.py
 +
 +import sys, random
 +from PyQt4 import QtGui, QtCore
 +
 +
 +class Points(QtGui.QWidget):​
 +    def __init__(self,​ parent=None):​
 +        QtGui.QWidget.__init__(self,​ parent)
 +
 +        self.setGeometry(300,​ 300, 250, 150)
 +        self.setWindowTitle('​Points'​)
 +
 +    def paintEvent(self,​ event):
 +        paint = QtGui.QPainter()
 +        paint.begin(self)
 +        paint.setPen(QtCore.Qt.red)
 +        size = self.size()
 +        for i in range(1000):​
 +            x = random.randint(1,​ size.width()-1)
 +            y = random.randint(1,​ size.height()-1)
 +            paint.drawPoint(x,​ y)
 +        paint.end()
 +
 +app = QtGui.QApplication(sys.argv)
 +dt = Points()
 +dt.show()
 +app.exec_()
 +</​code>​
 +
 +فى مثالنا نقوم برسم 1000 نقطة حمراء على مساحة العميل
 +
 +  paint.setPen(QtCore.Qt.red)
 +
 +نقوم بتحديد اللون للقلم وذلك بإستخدام ثابت معرف سابقا red
 +
 +  size = self.size()
 +
 +عند كل عملية اعادة تحجيم، يتم انشاء الحدث paint 
 +نحصل على المساحة الحالية بإستخدام الطريقة size()
 +
 +  paint.drawPoint(x,​ y)
 +
 +نقوم برسم النقطة بإستخدام الطريقة drawPoint()
 +
 +{{ pyqt4points.jpg }}
 +
 +==== الألوان ====
 +
 +اللون هو كائن بيعبر عن خليط من الأحمر والأخضر والأزرق RGB بقيم تتراوح بين 0 ل 255 ، نستطيع تعريف اللون بعدة طرق واشهرهاقيم RGB decimal or Hexadicemal ..نستطيع ايضا استخدام RGBA "​اختصارا ل Red, Green, Blue, Alpha لنضيف بعض المعلومات الإضافية opacity, transparency
 +
 +
 +
 +<code python>
 +#​!/​usr/​bin/​python
 +
 +# colors.py
 +
 +import sys, random
 +from PyQt4 import QtGui, QtCore
 +
 +
 +class Colors(QtGui.QWidget):​
 +    def __init__(self,​ parent=None):​
 +        QtGui.QWidget.__init__(self,​ parent)
 +
 +        self.setGeometry(300,​ 300, 350, 280)
 +        self.setWindowTitle('​Colors'​)
 +
 +    def paintEvent(self,​ event):
 +        paint = QtGui.QPainter()
 +        paint.begin(self)
 +
 +        color = QtGui.QColor(0,​ 0, 0)
 +        color.setNamedColor('#​d4d4d4'​)
 +        paint.setPen(color)
 +
 +        paint.setBrush(QtGui.QColor(255,​ 0, 0, 80))
 +        paint.drawRect(10,​ 15, 90, 60)
 +
 +        paint.setBrush(QtGui.QColor(255,​ 0, 0, 160))
 +        paint.drawRect(130,​ 15, 90, 60)
 +
 +        paint.setBrush(QtGui.QColor(255,​ 0, 0, 255))
 +        paint.drawRect(250,​ 15, 90, 60)
 +
 +        paint.setBrush(QtGui.QColor(10,​ 163, 2, 55))
 +        paint.drawRect(10,​ 105, 90, 60)
 +
 +        paint.setBrush(QtGui.QColor(160,​ 100, 0, 255))
 +        paint.drawRect(130,​ 105, 90, 60)
 +
 +        paint.setBrush(QtGui.QColor(60,​ 100, 60, 255))
 +        paint.drawRect(250,​ 105, 90, 60)
 +
 +        paint.setBrush(QtGui.QColor(50,​ 50, 50, 255))
 +        paint.drawRect(10,​ 195, 90, 60)
 +
 +        paint.setBrush(QtGui.QColor(50,​ 150, 50, 255))
 +        paint.drawRect(130,​ 195, 90, 60)
 +
 +        paint.setBrush(QtGui.QColor(223,​ 135, 19, 255))
 +        paint.drawRect(250,​ 195, 90, 60)
 +
 +        paint.end()
 +
 +app = QtGui.QApplication(sys.argv)
 +dt = Colors()
 +dt.show()
 +app.exec_()
 +</​code>​
 +
 +فى مثالنا قمنا بإنشاء 9 مستطيلات ملونة. الصنف الأول مكون من مستطيلات حمراء مع قيم مختلفة لل alpha 
 +
 +  color = QtGui.QColor(0,​ 0, 0)
 +  color.setNamedColor('#​d4d4d4'​)
 +
 +هنا قمنا بتعريف الرقم بإستخدام hexadecimal
 +
 +  paint.setBrush(QtGui.QColor(255,​ 0, 0, 80));
 +  paint.drawRect(10,​ 15, 90, 60)
 +
 +هنا قمنا بتعريف فرشاة، لرسم خلفية الشكل.
 +الطريقة drawRect() الخاصة برسم المستطيل تأخذ 4 معاملات x, y للإحداثيات و العرض والإرتفاع للمستطيل.وذلك بإستخدام القلم والفرشاة الحاليين
 +
 +{{ pyqt4colors.jpg }}
 +
 +==== الصنف QPen ====
 +
 +الصنف QPen القلم كائن رسومى يستخدم فى رسم الخطوط، المنحنيات، محددات المستطيلات .. الخ
 +
 +<code python>
 +
 +#​!/​usr/​bin/​python
 +
 +# penstyles.py
 +
 +import sys
 +from PyQt4 import QtGui, QtCore
 +
 +
 +class PenStyles(QtGui.QWidget):​
 +    def __init__(self,​ parent=None):​
 +        QtGui.QWidget.__init__(self,​ parent)
 +
 +        self.setGeometry(300,​ 300, 280, 270)
 +        self.setWindowTitle('​penstyles'​)
 +
 +    def paintEvent(self,​ event):
 +        paint = QtGui.QPainter()
 +
 +        paint.begin(self)
 +
 +        pen = QtGui.QPen(QtCore.Qt.black,​ 2, QtCore.Qt.SolidLine)
 +
 +        paint.setPen(pen)
 +        paint.drawLine(20,​ 40, 250, 40)
 +
 +        pen.setStyle(QtCore.Qt.DashLine)
 +        paint.setPen(pen)
 +        paint.drawLine(20,​ 80, 250, 80)
 +
 +        pen.setStyle(QtCore.Qt.DashDotLine)
 +        paint.setPen(pen)
 +        paint.drawLine(20,​ 120, 250, 120)
 +
 +        pen.setStyle(QtCore.Qt.DotLine)
 +        paint.setPen(pen)
 +        paint.drawLine(20,​ 160, 250, 160)
 +
 +        pen.setStyle(QtCore.Qt.DashDotDotLine)
 +        paint.setPen(pen)
 +        paint.drawLine(20,​ 200, 250, 200)
 +
 +        pen.setStyle(QtCore.Qt.CustomDashLine)
 +        pen.setDashPattern([1,​ 4, 5, 4])
 +        paint.setPen(pen)
 +        paint.drawLine(20,​ 240, 250, 240)
 +
 +        paint.end()
 +
 +app = QtGui.QApplication(sys.argv)
 +dt = PenStyles()
 +dt.show()
 +app.exec_()
 +
 +</​code>​
 +
 +فى مثالنا، نقوم برسم 6 خطوط بإستخدام 6 انواع من الأقلام. هناك 5 معرفين مسبقا وواحد قمنا بتعريفه.
 +
 +  pen = QtGui.QPen(QtCore.Qt.black,​ 2, QtCore.Qt.SolidLine)
 +
 +هنا قمنا بإنشاء قلم من الصنف QPen وقمنا بتحديد اللون والسمك "​بالبكسلز"​ واستخدمنا النوع SolidLine كنوع للقلم
 +
 +  pen.setStyle(QtCore.Qt.CustomDashLine)
 +  pen.setDashPattern([1,​ 4, 5, 4])
 +  paint.setPen(pen)
 +
 +هنا قمنا بتعريف نوع جديد من الأقلام بإستخدام setStyle وقمنا بإستدعاء الطريقة setDashPattern() واللتى تأخذ list كمعامل. ​
 +يجب ان يكون هناك رقم زوجى، الأرقام الفردية بتحدد الdash والزوجية تحدد المسافة.
 +
 +نمطنا هو 1بكسل داش 4بكسل مسافة 5بكسل داش 4بكسل مسافة.. الخ
 +
 +{{ pyqt4penstyles.jpg }}
 +
 +==== الصنف QBrush ====
 +
 +
 +هى كائن رسومى، بيستخدم فى رسم خلفية الأشكال مثل المضلعات وغيرها.
 +ممكن تكون الفرشاة من 3 انواع، نوع معرف سابقا، gradien or a texture pattern
 +
 +<code python>
 +
 +#​!/​usr/​bin/​python
 +
 +# brushes.py
 +
 +import sys
 +from PyQt4 import QtGui, QtCore
 +
 +
 +class Brushes(QtGui.QWidget):​
 +    def __init__(self,​ parent=None):​
 +        QtGui.QWidget.__init__(self,​ parent)
 +
 +        self.setGeometry(300,​ 300, 355, 280)
 +        self.setWindowTitle('​Brushes'​)
 +
 +    def paintEvent(self,​ event):
 +        paint = QtGui.QPainter()
 +
 +        paint.begin(self)
 +
 +        brush = QtGui.QBrush(QtCore.Qt.SolidPattern)
 +        paint.setBrush(brush)
 +        paint.drawRect(10,​ 15, 90, 60)
 +
 +        brush.setStyle(QtCore.Qt.Dense1Pattern)
 +        paint.setBrush(brush)
 +        paint.drawRect(130,​ 15, 90, 60)
 +
 +        brush.setStyle(QtCore.Qt.Dense2Pattern)
 +        paint.setBrush(brush)
 +        paint.drawRect(250,​ 15, 90, 60)
 +
 +        brush.setStyle(QtCore.Qt.Dense3Pattern)
 +        paint.setBrush(brush)
 +        paint.drawRect(10,​ 105, 90, 60)
 +
 +        brush.setStyle(QtCore.Qt.DiagCrossPattern)
 +        paint.setBrush(brush)
 +        paint.drawRect(10,​ 105, 90, 60)
 +
 +        brush.setStyle(QtCore.Qt.Dense5Pattern)
 +        paint.setBrush(brush)
 +        paint.drawRect(130,​ 105, 90, 60)
 +
 +        brush.setStyle(QtCore.Qt.Dense6Pattern)
 +        paint.setBrush(brush)
 +        paint.drawRect(250,​ 105, 90, 60)
 +
 +        brush.setStyle(QtCore.Qt.Dense7Pattern)
 +        paint.setBrush(brush)
 +        paint.drawRect(250,​ 105, 90, 60)
 +
 +        brush.setStyle(QtCore.Qt.HorPattern)
 +        paint.setBrush(brush)
 +        paint.drawRect(10,​ 195, 90, 60)
 +
 +        brush.setStyle(QtCore.Qt.VerPattern)
 +        paint.setBrush(brush)
 +        paint.drawRect(130,​ 195, 90, 60)
 +
 +        brush.setStyle(QtCore.Qt.BDiagPattern)
 +        paint.setBrush(brush)
 +        paint.drawRect(250,​ 195, 90, 60)
 +
 +        paint.end()
 +
 +app = QtGui.QApplication(sys.argv)
 +dt = Brushes()
 +dt.show()
 +app.exec_()
 +
 +</​code>​
 +
 +فى مثالنا نقوم برسم 6 انواع من المستطيلات.
 +  brush = QtGui.QBrush(QtCore.Qt.SolidPattern)
 +  paint.setBrush(brush)
 +  paint.drawRect(10,​ 15, 90, 60)
 +
 +قمنا بتعريف كائن من الصنف QBrush ليتم استخدامه فى عملية الرسم بإستخدام الطريقة drawRect
 +We define a brush object. Set it to the painter object. And draw the rectangle calling the //​drawRect()//​ method.
 +
 +{{ pyqt4brushes.jpg }}
 +
 +===== Custom Widgets in PyQt4 =====
 +
 +هل تسألت كيف تم تنفيذ عنصر ما من الواجهة فى تطبيق ما؟ غالبا كل من يريد ان يصبح مبرمجا فعل، ثم عند النظر لقائمة الودجات اللتى تقدمها لك مكتبتك الرسومية المفضلة، ولكن لاتجدها؟
 +ادوات تطوير التطبيقات بتقدملك اهم الودجات مثل الأزرار، ويدجات النصوص، المنزلقات،.. الخ ولكن لايوجد من يقدم لك كل شئ.
 +
 +هناك نوعين من ادوات تطوير التطبيقات، سبارطية (مثل FLTK لاتقدم لك الا الأساسيات ليقوم المبرمج بإنشاء مايريده بنفسه) ​ وثقيلة (مثل PyQt4 مع العديد من الودجات ولكنها ايضا لاتوفر لك كل شئ مثلا ودجة لحرق اسطوانات؟ (مثل النيرو او k3B وغيرهم). وايضا ادوات فى الغالب لاتحتوى اداة تطوير التطبيقات على ال charts
 +
 +لذيا يجب ان ينشئ المبرمجين هذه الودجات بأنفسهم، وذلك بإستخدام ادوات الرسم المتوفرة فى اداة التطوير.هنالك احتمالين ​
 +تعديل وتحسين ودجة موجود بالفعل، او انشاء واحد من الصفر
 +
 +
 +==== ودجة الحرق ====
 +
 +شبيه بما تراه فى Nero, K3B او غيرهم من برامج حرق الإسطوانات
 +
 +<code python>
 +#​!/​usr/​bin/​python
 +
 +# burning.py
 +
 +import sys
 +from PyQt4 import QtGui, QtCore
 +
 +
 +class Widget(QtGui.QLabel):​
 +    def __init__(self,​ parent):
 +        QtGui.QLabel.__init__(self,​ parent)
 +        self.setMinimumSize(1,​ 30)
 +        self.parent = parent
 +        self.num = [75, 150, 225, 300, 375, 450, 525, 600, 675]
 +
 +    def paintEvent(self,​ event):
 +        paint = QtGui.QPainter()
 +        paint.begin(self)
 +
 +        font = QtGui.QFont('​Serif',​ 7, QtGui.QFont.Light)
 +        paint.setFont(font)
 +
 +        size = self.size()
 +        w = size.width()
 +        h = size.height()
 +        cw = self.parent.cw
 +        step = int(round(w / 10.0))
 +
 +
 +        till = int(((w / 750.0) * cw))
 +        full = int(((w / 750.0) * 700))
 +
 +        if cw >= 700:
 +            paint.setPen(QtGui.QColor(255,​ 255, 255))
 +            paint.setBrush(QtGui.QColor(255,​ 255, 184))
 +            paint.drawRect(0,​ 0, full, h)
 +            paint.setPen(QtGui.QColor(255,​ 175, 175))
 +            paint.setBrush(QtGui.QColor(255,​ 175, 175))
 +            paint.drawRect(full,​ 0, till-full, h)
 +        else:
 +            paint.setPen(QtGui.QColor(255,​ 255, 255))
 +            paint.setBrush(QtGui.QColor(255,​ 255, 184))
 +            paint.drawRect(0,​ 0, till, h)
 +
 +
 +        pen = QtGui.QPen(QtGui.QColor(20,​ 20, 20), 1, QtCore.Qt.SolidLine)
 +        paint.setPen(pen)
 +        paint.setBrush(QtCore.Qt.NoBrush)
 +        paint.drawRect(0,​ 0, w-1, h-1)
 +
 + j = 0
 +
 +        for i in range(step, 10*step, step):
 +            paint.drawLine(i,​ 0, i, 5)
 +            metrics = paint.fontMetrics()
 +            fw = metrics.width(str(self.num[j]))
 +            paint.drawText(i-fw/​2,​ h/2, str(self.num[j]))
 +            j = j + 1
 +
 +        paint.end()
 +
 +class Burning(QtGui.QWidget):​
 +    def __init__(self,​ parent=None):​
 +        QtGui.QWidget.__init__(self,​ parent)
 +
 +        self.cw = 75
 +
 +        self.slider = QtGui.QSlider(QtCore.Qt.Horizontal,​ self)
 +        self.slider.setFocusPolicy(QtCore.Qt.NoFocus)
 +        self.slider.setRange(1,​ 750)
 +        self.slider.setValue(75)
 +        self.slider.setGeometry(30,​ 40, 150, 30)
 +
 +        self.wid = Widget(self)
 +
 +        self.connect(self.slider,​ QtCore.SIGNAL('​valueChanged(int)'​),​ self.changeValue)
 +        hbox = QtGui.QHBoxLayout()
 +        hbox.addWidget(self.wid)
 +        vbox = QtGui.QVBoxLayout()
 +        vbox.addStretch(1)
 +        vbox.addLayout(hbox)
 +        self.setLayout(vbox)
 +
 +        self.setGeometry(300,​ 300, 300, 220)
 +        self.setWindowTitle('​Burning'​)
 +
 +    def changeValue(self,​ event):
 +        self.cw = self.slider.value()
 +        self.wid.repaint()
 +
 +
 +app = QtGui.QApplication(sys.argv)
 +dt = Burning()
 +dt.show()
 +app.exec_()
 +</​code>​
 +
 +فى مثالنا، لدينا منزلق و ودجة قمنا بإنشاءه خصيصا ويتم التحكم فيه عن طريق المنزلق.هذا الودجة يعرض السعة لوسيط تخزين والمساحة الفارغة فيه لنا.
 +اقل قيمة مقبولة هى 1 واكبر قيمة هى 750 وحينما يتم تعدى القيمة 700 نقوم بوضع الرسم بلون احمر "​ليعبر عن الحرق الزائد"​
 +
 +ودجة الحرق موضوع بأسفل النافذة وذلك عن طريق مخطط افقى QHBoxLayout و QVBoxLayout
 +
 +<code python>
 + class Widget(QtGui.QLabel):​
 +     def __init__(self,​ parent):
 +         ​QtGui.QLabel.__init__(self,​ parent)
 +</​code>​
 +
 +ودجة الحرق يرث ال QLabel
 +
 +  self.setMinimumSize(1,​ 30)
 +
 +نقوم بتعديل اقل مساحة (ارتفاع) الودجة لأن القيمة الأساسية صغيرة بالنسبة لنا
 +
 +
 +  font = QtGui.QFont('​Serif',​ 7, QtGui.QFont.Light)
 +  paint.setFont(font)
 +
 +نستخدم خط اصغر من الإفتراضى ليناسبنا اكثر.
 +
 +
 +<code python>
 + size = self.size()
 + w = size.width()
 + h = size.height()
 + cw = self.parent.cw
 + step = int(round(w / 10.0))
 +
 + till = int(((w / 750.0) * cw))
 + full = int(((w / 750.0) * 700))
 +</​code>​
 +
 +نرسم الودجة بصورة ديناميكية، الأكبر النافذة، الأكبر ودجة الحرق والعكس. لذا نحسب المساحة للودجة، المعامل till بيحدد المساحة الكلية للرسم، هذه القيمة تأتى من المنزلق وهى النسبة من المساحة الكلية. المعامل full يحدد النقطة اللتى سنبدأ عندها برسم اللون الأحمر "​حرق زائد"​. لاحظ استخدام الأعداد الحقيقة لضمان اكبر دقة.
 +
 +عملية الرسم تضمن 3 خطوات، رسم الأصفر او الأحمر، والمستطيل الأصفر ثم رسم الخطوط الرأسية لتقسم الودجة لعدة اقسام، واخيرا نقوم برسم الأرقام لتعبر عن سعة وسيط التخزين.
 +<code python>
 + ​metrics = paint.fontMetrics()
 + fw = metrics.width(str(self.num[j]))
 + ​paint.drawText(i-fw/​2,​ h/2, str(self.num[j]))
 +</​code>​
 +
 +نستخدم ال font metrics لرسم النص. يجب ان نعلم عرض النص لنقوم بوضعه فى المنتصف حول الخط الأرسى
 +
 +{{ pyqt4burning.jpg }}
 +
 +===== لعبة Tetris بإستخدام PyQt4 =====
 +
 +عمل لعبة كمبيوتر شئ غير سهل، وعاجلا او اجلا سيريد المبرمج عمل لعبة، فى الواقع العديد من الناس اصبحو مهتمين بالبرمجة رغبتهم فى انشاء العاب خاصة بهم.
 +على كل حال، كتابة لعبة سيفيدك فى تحسين مهاراتك البرمجية ​
 +
 +==== Tetris ====
 +
 +واحدة من اشهر الألعاب على الإطلاق. اللعبة الأصلية صممت وبرمجت بواسطة Alexey Pajitnov فى عام 1985، ومنذ ذلك الحين اصبحت موجودة تقريبا على معظم الأجهزة، حتى الخلوى
 +
 +تسمى ايضا احجية القطع المتساقطة. فى هذه اللعبة لدينا 7 انواع من القطع مسماه tetominoes وهى S-shape, Z-shape, T-shape, L-shape, Line-shape, MirroredL-shape and a Square-shape.
 +
 +كل من هذه الأشكال مكون من 4 مربعات. وتتساقط القطع على لوحة. ممكن ازاحة او تدوير القطع ليجعلهم مناسبين بأقصى قدر. ​
 +اذا كونا صف فيتم حذفه وإضافته للنتيجة النهائية، تظل اللعبة حتى يصل عدد الصفوف للأعلى
 +
 +{{ pyqt4tetrominoes.png }}
 +
 +اداة التطوير PyQt4 مخصصة لتطوير التطبيقات مع انه يمكن تطوير العاب بها كما سنرى، فى حين هناك بعض المكتبات موجهة لتطوير العاب الحاسوب.
 +
 +
 +المثال التالى هو اصدار معدل من لعبة tetris المتاحة مع ملفات تنصيب PyQt4
 +
 +==== التطوير ====
 +
 +ليس لدينا صور للعبة، لذا سنقوم برسمها بإستخدام ال APIs الخاصة بالرسم فى PyQt4 ، اى لعبة عبارة عن تمثيل رياضى وكذلك tetris
 +
 +
 +
 +بعض الأفكار وراء اللعبة
 +
 +*استخدام QtCore.QBasicTimer() لإدارة اللعبة
 +*رسم القطع
 +*القطع تتحرك بالمربع وليس بالبيكسل
 +*رياضيا، اللوحة ماهى الا مجموعة بسيطة من الأرقام
 +Some ideas behind the game. 
 +<code python>
 +#​!/​usr/​bin/​python
 +
 +# tetris.py
 +
 +import sys
 +import random
 +from PyQt4 import QtCore, QtGui
 +
 +
 +class Tetris(QtGui.QMainWindow):​
 +    def __init__(self):​
 +        QtGui.QMainWindow.__init__(self)
 +
 +        self.setGeometry(300,​ 300, 180, 380)
 +        self.setWindowTitle('​Tetris'​)
 + self.tetrisboard = Board(self)
 +
 + self.setCentralWidget(self.tetrisboard)
 +
 + self.statusbar = self.statusBar()
 + self.connect(self.tetrisboard,​ QtCore.SIGNAL("​messageToStatusbar(QString)"​), ​
 +     self.statusbar,​ QtCore.SLOT("​showMessage(QString)"​))
 +
 + self.tetrisboard.start()
 +        self.center()
 +
 +    def center(self):​
 +        screen = QtGui.QDesktopWidget().screenGeometry()
 +        size =  self.geometry()
 +        self.move((screen.width()-size.width())/​2, ​
 +     (screen.height()-size.height())/​2)
 +
 +class Board(QtGui.QFrame):​
 +    BoardWidth = 10
 +    BoardHeight = 22
 +    Speed = 300
 +
 +    def __init__(self,​ parent):
 +        QtGui.QFrame.__init__(self,​ parent)
 +
 +        self.timer = QtCore.QBasicTimer()
 +        self.isWaitingAfterLine = False
 +        self.curPiece = Shape()
 +        self.nextPiece = Shape()
 +        self.curX = 0
 +        self.curY = 0
 +        self.numLinesRemoved = 0
 +        self.board = []
 +
 +        self.setFocusPolicy(QtCore.Qt.StrongFocus)
 +        self.isStarted = False
 +        self.isPaused = False
 +        self.clearBoard()
 +
 +        self.nextPiece.setRandomShape()
 +
 +    def shapeAt(self,​ x, y):
 +        return self.board[(y * Board.BoardWidth) + x]
 +
 +    def setShapeAt(self,​ x, y, shape):
 +        self.board[(y * Board.BoardWidth) + x] = shape
 +
 +    def squareWidth(self):​
 +        return self.contentsRect().width() / Board.BoardWidth
 +
 +    def squareHeight(self):​
 +        return self.contentsRect().height() / Board.BoardHeight
 +
 +    def start(self):​
 +        if self.isPaused:​
 +            return
 +
 +        self.isStarted = True
 +        self.isWaitingAfterLine = False
 +        self.numLinesRemoved = 0
 +        self.clearBoard()
 +
 +        self.emit(QtCore.SIGNAL("​messageToStatusbar(QString)"​), ​
 +     str(self.numLinesRemoved))
 +
 +        self.newPiece()
 +        self.timer.start(Board.Speed,​ self)
 +
 +    def pause(self):​
 +        if not self.isStarted:​
 +            return
 +
 +        self.isPaused = not self.isPaused
 +        if self.isPaused:​
 +            self.timer.stop()
 +            self.emit(QtCore.SIGNAL("​messageToStatusbar(QString)"​),​ "​paused"​)
 +        else:
 +            self.timer.start(Board.Speed,​ self)
 +            self.emit(QtCore.SIGNAL("​messageToStatusbar(QString)"​), ​
 +         str(self.numLinesRemoved))
 +
 +        self.update()
 +
 +    def paintEvent(self,​ event):
 +        painter = QtGui.QPainter(self)
 +        rect = self.contentsRect()
 +
 +        boardTop = rect.bottom() - Board.BoardHeight * self.squareHeight()
 +
 +        for i in range(Board.BoardHeight):​
 +            for j in range(Board.BoardWidth):​
 +                shape = self.shapeAt(j,​ Board.BoardHeight - i - 1)
 +                if shape != Tetrominoes.NoShape:​
 +                    self.drawSquare(painter,​
 +                        rect.left() + j * self.squareWidth(),​
 +                        boardTop + i * self.squareHeight(),​ shape)
 +
 +        if self.curPiece.shape() != Tetrominoes.NoShape:​
 +            for i in range(4):
 +                x = self.curX + self.curPiece.x(i)
 +                y = self.curY - self.curPiece.y(i)
 +                self.drawSquare(painter,​ rect.left() + x * self.squareWidth(),​
 +                    boardTop + (Board.BoardHeight - y - 1) * self.squareHeight(),​
 +                    self.curPiece.shape())
 +
 +    def keyPressEvent(self,​ event):
 +        if not self.isStarted or self.curPiece.shape() == Tetrominoes.NoShape:​
 +            QtGui.QWidget.keyPressEvent(self,​ event)
 +            return
 +
 +        key = event.key()
 + if key == QtCore.Qt.Key_P:​
 +     self.pause()
 +            return
 + if self.isPaused:​
 +            return
 +        elif key == QtCore.Qt.Key_Left:​
 +            self.tryMove(self.curPiece,​ self.curX - 1, self.curY)
 +        elif key == QtCore.Qt.Key_Right:​
 +            self.tryMove(self.curPiece,​ self.curX + 1, self.curY)
 +        elif key == QtCore.Qt.Key_Down:​
 +            self.tryMove(self.curPiece.rotatedRight(),​ self.curX, self.curY)
 +        elif key == QtCore.Qt.Key_Up:​
 +            self.tryMove(self.curPiece.rotatedLeft(),​ self.curX, self.curY)
 +        elif key == QtCore.Qt.Key_Space:​
 +            self.dropDown()
 +        elif key == QtCore.Qt.Key_D:​
 +            self.oneLineDown()
 +        else:
 +            QtGui.QWidget.keyPressEvent(self,​ event)
 +
 +    def timerEvent(self,​ event):
 +        if event.timerId() == self.timer.timerId():​
 +            if self.isWaitingAfterLine:​
 +                self.isWaitingAfterLine = False
 +                self.newPiece()
 +            else:
 +                self.oneLineDown()
 +        else:
 +            QtGui.QFrame.timerEvent(self,​ event)
 +
 +    def clearBoard(self):​
 +        for i in range(Board.BoardHeight * Board.BoardWidth):​
 +     self.board.append(Tetrominoes.NoShape)
 +
 +    def dropDown(self):​
 +        newY = self.curY
 +        while newY > 0:
 +            if not self.tryMove(self.curPiece,​ self.curX, newY - 1):
 +                break
 +            newY -= 1
 +
 +        self.pieceDropped()
 +
 +    def oneLineDown(self):​
 +        if not self.tryMove(self.curPiece,​ self.curX, self.curY - 1):
 +            self.pieceDropped()
 +
 +    def pieceDropped(self):​
 +        for i in range(4):
 +            x = self.curX + self.curPiece.x(i)
 +            y = self.curY - self.curPiece.y(i)
 +            self.setShapeAt(x,​ y, self.curPiece.shape())
 +
 +        self.removeFullLines()
 +
 +        if not self.isWaitingAfterLine:​
 +            self.newPiece()
 +
 +    def removeFullLines(self):​
 +        numFullLines = 0
 +
 + rowsToRemove = []
 +
 + for i in range(Board.BoardHeight):​
 +     n = 0
 +            for j in range(Board.BoardWidth):​
 +                if not self.shapeAt(j,​ i) == Tetrominoes.NoShape:​
 +                    n = n + 1
 +
 +     if n == 10:
 + rowsToRemove.append(i)
 +
 + rowsToRemove.reverse()
 +
 + for m in rowsToRemove:​
 +     for k in range(m, Board.BoardHeight):​
 +         for l in range(Board.BoardWidth):​
 +                    self.setShapeAt(l,​ k, self.shapeAt(l,​ k + 1))
 +
 +        numFullLines = numFullLines + len(rowsToRemove)
 +
 +        if numFullLines > 0:
 +            self.numLinesRemoved = self.numLinesRemoved + numFullLines
 +            self.emit(QtCore.SIGNAL("​messageToStatusbar(QString)"​), ​
 + str(self.numLinesRemoved))
 +            self.isWaitingAfterLine = True
 +            self.curPiece.setShape(Tetrominoes.NoShape)
 +            self.update()
 +
 +    def newPiece(self):​
 +        self.curPiece = self.nextPiece
 +        self.nextPiece.setRandomShape()
 +        self.curX = Board.BoardWidth / 2 + 1
 +        self.curY = Board.BoardHeight - 1 + self.curPiece.minY()
 +
 +        if not self.tryMove(self.curPiece,​ self.curX, self.curY):
 +            self.curPiece.setShape(Tetrominoes.NoShape)
 +            self.timer.stop()
 +            self.isStarted = False
 +            self.emit(QtCore.SIGNAL("​messageToStatusbar(QString)"​),​ "Game over")
 +
 +
 +
 +    def tryMove(self,​ newPiece, newX, newY):
 +        for i in range(4):
 +            x = newX + newPiece.x(i)
 +            y = newY - newPiece.y(i)
 +            if x < 0 or x >= Board.BoardWidth or y < 0 or y >= Board.BoardHeight:​
 +                return False
 +            if self.shapeAt(x,​ y) != Tetrominoes.NoShape:​
 +                return False
 +
 +        self.curPiece = newPiece
 +        self.curX = newX
 +        self.curY = newY
 +        self.update()
 +        return True
 +
 +    def drawSquare(self,​ painter, x, y, shape):
 +        colorTable = [0x000000, 0xCC6666, 0x66CC66, 0x6666CC,
 +                      0xCCCC66, 0xCC66CC, 0x66CCCC, 0xDAAA00]
 +
 +        color = QtGui.QColor(colorTable[shape])
 +        painter.fillRect(x + 1, y + 1, self.squareWidth() - 2, 
 +     self.squareHeight() - 2, color)
 +
 +        painter.setPen(color.light())
 +        painter.drawLine(x,​ y + self.squareHeight() - 1, x, y)
 +        painter.drawLine(x,​ y, x + self.squareWidth() - 1, y)
 +
 +        painter.setPen(color.dark())
 +        painter.drawLine(x + 1, y + self.squareHeight() - 1,
 +            x + self.squareWidth() - 1, y + self.squareHeight() - 1)
 +        painter.drawLine(x + self.squareWidth() - 1, 
 +     y + self.squareHeight() - 1, x + self.squareWidth() - 1, y + 1)
 +
 +
 +class Tetrominoes(object):​
 +    NoShape = 0
 +    ZShape = 1
 +    SShape = 2
 +    LineShape = 3
 +    TShape = 4
 +    SquareShape = 5
 +    LShape = 6
 +    MirroredLShape = 7
 +
 +
 +class Shape(object):​
 +    coordsTable = (
 +        ((0, 0),     (0, 0),     (0, 0),     (0, 0)),
 +        ((0, -1),    (0, 0),     (-1, 0),    (-1, 1)),
 +        ((0, -1),    (0, 0),     (1, 0),     (1, 1)),
 +        ((0, -1),    (0, 0),     (0, 1),     (0, 2)),
 +        ((-1, 0),    (0, 0),     (1, 0),     (0, 1)),
 +        ((0, 0),     (1, 0),     (0, 1),     (1, 1)),
 +        ((-1, -1),   (0, -1),    (0, 0),     (0, 1)),
 +        ((1, -1),    (0, -1),    (0, 0),     (0, 1))
 +    )
 +
 +    def __init__(self):​
 +        self.coords = [[0,0] for i in range(4)]
 +        self.pieceShape = Tetrominoes.NoShape
 +
 +        self.setShape(Tetrominoes.NoShape)
 +
 +    def shape(self):​
 +        return self.pieceShape
 +
 +    def setShape(self,​ shape):
 +        table = Shape.coordsTable[shape]
 +        for i in range(4):
 +            for j in range(2):
 +                self.coords[i][j] = table[i][j]
 +
 +        self.pieceShape = shape
 +
 +    def setRandomShape(self):​
 +        self.setShape(random.randint(1,​ 7))
 +
 +    def x(self, index):
 +        return self.coords[index][0]
 +
 +    def y(self, index):
 +        return self.coords[index][1]
 +
 +    def setX(self, index, x):
 +        self.coords[index][0] = x
 +
 +    def setY(self, index, y):
 +        self.coords[index][1] = y
 +
 +    def minX(self):
 +        m = self.coords[0][0]
 +        for i in range(4):
 +            m = min(m, self.coords[i][0])
 +
 +        return m
 +
 +    def maxX(self):
 +        m = self.coords[0][0]
 +        for i in range(4):
 +            m = max(m, self.coords[i][0])
 +
 +        return m
 +
 +    def minY(self):
 +        m = self.coords[0][1]
 +        for i in range(4):
 +            m = min(m, self.coords[i][1])
 +
 +        return m
 +
 +    def maxY(self):
 +        m = self.coords[0][1]
 +        for i in range(4):
 +            m = max(m, self.coords[i][1])
 +
 +        return m
 +
 +    def rotatedLeft(self):​
 +        if self.pieceShape == Tetrominoes.SquareShape:​
 +            return self
 +
 +        result = Shape()
 +        result.pieceShape = self.pieceShape
 +        for i in range(4):
 +            result.setX(i,​ self.y(i))
 +            result.setY(i,​ -self.x(i))
 +
 +        return result
 +
 +    def rotatedRight(self):​
 +        if self.pieceShape == Tetrominoes.SquareShape:​
 +            return self
 +
 +        result = Shape()
 +        result.pieceShape = self.pieceShape
 +        for i in range(4):
 +            result.setX(i,​ -self.y(i))
 +            result.setY(i,​ self.x(i))
 +
 +        return result
 +
 +
 +app = QtGui.QApplication(sys.argv)
 +tetris = Tetris()
 +tetris.show()
 +sys.exit(app.exec_())
 +</​code>​
 +
 +قمت بتبسيط اللعبة قليلا لتصبح اسهل للفهم. تبدأ اللعبة مباشرة بعد تشغيلها، نستطيع ايقافها بالضغط على الزر p وزر المسافة سيقوم بإنزال القطعة فورا الى الأسفل. السرعة ثابتة فى اللعبة، لم يتم اضافة اى تسريع. يتم حساب النتيجة بعدد الأسطر اللتى تم حذفها
 +
 +<code python>
 + ​self.statusbar = self.statusBar()
 + ​self.connect(self.tetrisboard,​ QtCore.SIGNAL("​messageToStatusbar(QString)"​), ​
 +     ​self.statusbar,​ QtCore.SLOT("​showMessage(QString)"​))
 +
 +</​code>​
 +
 +بننشئ شريط حالة، لنعرض الرسائل(عدد الأسطر المنتهية، اللعبة متوقفة، انتهاء اللعبة)
 +
 +<code python>
 + ...
 + ​self.curX = 0
 + ​self.curY = 0
 + ​self.numLinesRemoved = 0
 + ​self.board = []
 + ...
 +</​code>​
 +
 +قبل ان نبدأ اللعبة، يجب ان نجهز بعض المتغيرات الهامة. مثل اللوحة وهى عبارة عن مجموعة الأرقام من 0 الى 7 تمثل المركز للقطع والبقايا. وعدد الأسطر/​الصفوف المحذوفه.. الخ
 +
 +<code python>
 + for j in range(Board.BoardWidth):​
 +     shape = self.shapeAt(j,​ Board.BoardHeight - i - 1)
 +     if shape != Tetrominoes.NoShape:​
 +         ​self.drawSquare(painter,​
 +             ​rect.left() + j * self.squareWidth(),​
 +             ​boardTop + i * self.squareHeight(),​ shape)
 +</​code>​
 +
 +عملية الرسم مقسمه لخطوتين:​
 +الخطوة الأولى:​ رسم كل القطع او البقايا اللتى سقطت بأسفل اللوحة، كل المربعات يتم تذكرها فى اللوحة self.board ونصل إليها بإستخدام الطريقة shapeAt()
 +
 +<code python>
 + if self.curPiece.shape() != Tetrominoes.NoShape:​
 +     for i in range(4):
 +         x = self.curX + self.curPiece.x(i)
 +         y = self.curY - self.curPiece.y(i)
 +         ​self.drawSquare(painter,​ rect.left() + x * self.squareWidth(),​
 +             ​boardTop + (Board.BoardHeight - y - 1) * self.squareHeight(),​
 +             ​self.curPiece.shape())
 +
 +</​code>​
 +
 +الخطوة الثانية:​ رسم القطعة الساقطة
 +The next step is drawing of the actual piece, that is falling down.
 +
 +<code python>
 + elif key == QtCore.Qt.Key_Left:​
 +     ​self.tryMove(self.curPiece,​ self.curX - 1, self.curY)
 + elif key == QtCore.Qt.Key_Right:​
 +     ​self.tryMove(self.curPiece,​ self.curX + 1, self.curY)
 +</​code>​
 +
 +فى معالج الحدث keyPressEvent نختبر الأزار اللتى يتم الضغط عليها، اذا تم ضغط السهم الأيمن فإننا نحاول تحريك القطعة الى اليمين(نحاول لأن قد لانستطيع تحريكها!)
 +
 +<code python>
 + def tryMove(self,​ newPiece, newX, newY):
 +     for i in range(4):
 +         x = newX + newPiece.x(i)
 +         y = newY - newPiece.y(i)
 +         if x < 0 or x >= Board.BoardWidth or y < 0 or y >= Board.BoardHeight:​
 +             ​return False
 +         if self.shapeAt(x,​ y) != Tetrominoes.NoShape:​
 +             ​return False
 +
 +     ​self.curPiece = newPiece
 +     ​self.curX = newX
 +     ​self.curY = newY
 +     ​self.update()
 +     ​return True
 +</​code>​
 +
 +فى الطريقة tryMove() نحاول تحريك القطع، فى حال وجود القطعة عند الحافة او المجاور فإننا نعيد قيمة False وإلا نقوم بوضع القطعة فى الموقع الجديد
 +
 +
 +<code python>
 + def timerEvent(self,​ event):
 +     if event.timerId() == self.timer.timerId():​
 +         if self.isWaitingAfterLine:​
 +             ​self.isWaitingAfterLine = False
 +             ​self.newPiece()
 +         else:
 +             ​self.oneLineDown()
 +     else:
 +         ​QtGui.QFrame.timerEvent(self,​ event)
 +</​code>​
 +
 +فى معالج الحدث timerEvent ​
 +اما ننشئ قطعة جديدة تلى اللتى تم اسقاطها، او تحريك تلك القطع للأسفل
 +
 +<code python>
 + def removeFullLines(self):​
 +     ​numFullLines = 0
 +
 +     ​rowsToRemove = []
 +
 +     for i in range(Board.BoardHeight):​
 +         n = 0
 +         for j in range(Board.BoardWidth):​
 +             if not self.shapeAt(j,​ i) == Tetrominoes.NoShape:​
 +                 n = n + 1
 +
 +         if n == 10:
 +             ​rowsToRemove.append(i)
 +
 +      rowsToRemove.reverse()
 +
 +      for m in rowsToRemove:​
 +          for k in range(m, Board.BoardHeight):​
 +              for l in range(Board.BoardWidth):​
 +                  self.setShapeAt(l,​ k, self.shapeAt(l,​ k + 1))
 + ...
 +
 +</​code>​
 +
 +عندما تصطدم القطعة بالأسفل نستدعى الطريقة removeFullLines() لحذف الصفوف المكتملة، ويتم ذلك بنقل كل الصفوف فوق الصف المكتمل. لاحظ عكس ترتيب الأسطر المحذوفة وإلا لن تعمل بصورة سليمة. فى حالتنا هذه نستخدم naive gravity مما يعنى ان القطع ربما تطفو فوق فراغات.
 +
 +
 +<code python>
 + def newPiece(self):​
 +     ​self.curPiece = self.nextPiece
 +     ​self.nextPiece.setRandomShape()
 +     ​self.curX = Board.BoardWidth / 2 + 1
 +     ​self.curY = Board.BoardHeight - 1 + self.curPiece.minY()
 +
 +     if not self.tryMove(self.curPiece,​ self.curX, self.curY):
 +         ​self.curPiece.setShape(Tetrominoes.NoShape)
 +         ​self.timer.stop()
 +         ​self.isStarted = False
 +         ​self.emit(QtCore.SIGNAL("​messageToStatusbar(QString)"​),​ "Game over")
 +</​code>​
 +
 +الطريقة newPiece() تنشئ قطعة عشوائية. اذا لم تستطع هذه القطعة التحرك فإن اللعبة انتهت.
 +
 +الصف Shape يقوم بتخزين المعلومات عن القطعة
 +
 +
 +<code python>
 + ​self.coords = [[0,0] for i in range(4)]
 +</​code>​
 +
 +عند الإنشاء قمنا بعمل قائمة من الأحداثيات لتخزين احداثيات شكل القطعة.
 +
 +{{ pyqt4coordinates.png }}
 +
 +عندما نرسم القطعة الساقطة نرسمها عند ال self.curX, self.curY وبعد ذلك ننظر الى جدول الأحداثيات لنرسم ال 4 مربعات.
 +
 +{{ pyqt4tetris.png }}
  
docs/pyqt4.txt · آخر تعديل: 2015/04/23 00:20 (تحرير خارجي)