16.1. هيكلة إضافات Python

الخطوات الرئيسية لإنشاء إضافة هي:

  1. الفكرة: كوّن فكرة عما تريد فعله بإضافة QGIS الجديدة.

  2. الإعداد: أنشئ ملفات إضافتك. بحسب نوع الإضافة، بعضها إلزامي وبعضها اختياري

  3. التطوير: اكتب الشيفرة في الملفات المناسبة

  4. التوثيق: اكتب توثيق الإضافة

  5. اختياريًا: الترجمة: ترجم إضافتك إلى لغات مختلفة

  6. الاختبار: أعد تحميل إضافتك للتحقق من أن كل شيء على ما يرام

  7. النشر: انشر إضافتك في مستودع QGIS أو أنشئ مستودعك الخاص بوصفه "ترسانة" من "أسلحة GIS" الشخصية.

16.1.1. البدء

قبل البدء بكتابة إضافة جديدة، ألقِ نظرة على مستودع إضافات Python الرسمي. يمكن أن تساعدك الشيفرة المصدرية للإضافات الموجودة على تعلّم المزيد عن البرمجة. وقد تجد أيضًا أن إضافة مشابهة موجودة بالفعل، فتستطيع توسيعها أو على الأقل البناء عليها لتطوير إضافتك.

16.1.1.1. إعداد بنية ملفات الإضافة

للبدء بإضافة جديدة، علينا إعداد ملفات الإضافة اللازمة.

يوجد مصدران لقوالب الإضافات يمكن أن يساعداك على البدء:

  • للأغراض التعليمية أو كلما كان النهج المبسّط مطلوبًا، يوفر قالب الإضافة المبسّط الملفات الأساسية (الهيكل) اللازمة لإنشاء إضافة Python صالحة لـ QGIS.

  • للحصول على قالب إضافة أكثر اكتمالًا من حيث الميزات، يستطيع Plugin Builder إنشاء قوالب لأنواع متعددة من الإضافات، بما في ذلك ميزات مثل التوطين (الترجمة) والاختبار.

يتضمن مجلد الإضافة النموذجي الملفات التالية:

  • metadata.txt - إلزامي - يحتوي على معلومات عامة والإصدار والاسم وبعض البيانات الوصفية الأخرى التي يستخدمها موقع الإضافات والبنية التحتية للإضافات.

  • __init__.py - إلزامي - نقطة انطلاق الإضافة. يجب أن يحتوي على الدالة classFactory() ويمكن أن يحتوي على أي شيفرة تهيئة أخرى.

  • mainPlugin.py - الشيفرة الأساسية - شيفرة العمل الرئيسية للإضافة. يحتوي على جميع المعلومات حول إجراءات الإضافة والشيفرة الرئيسية.

  • form.ui - للإضافات ذات الواجهة الرسومية المخصصة - الواجهة الرسومية المُنشأة بواسطة Qt Designer.

  • form.py - واجهة رسومية مُترجَمة (compiled) - تحويل الملف form.ui الموصوف أعلاه إلى Python.

  • resources.qrc - اختياري - مستند .xml يُنشئه Qt Designer. يحتوي على مسارات نسبية للموارد المستخدمة في نماذج الواجهة الرسومية.

  • resources.py - موارد مُترجَمة (compiled)، اختياري - تحويل ملف .qrc الموصوف أعلاه إلى Python.

  • LICENSE - إلزامي إن كانت الإضافة ستُنشر أو تُحدَّث في دليل إضافات QGIS، وإلا فهو اختياري. يجب أن يكون ملفًا نصيًا عاديًا بلا امتداد في اسمه.

تحذير

إن كنت تخطط لرفع الإضافة إلى مستودع إضافات Python الرسمي فعليك التحقق من أن إضافتك تتبع بعض القواعد الإضافية المطلوبة من أجل التحقق.

16.1.2. كتابة شيفرة الإضافة

يوضح القسم التالي المحتوى الذي يجب إضافته في كل ملف من الملفات المذكورة أعلاه.

16.1.2.1. metadata.txt

أولًا، يحتاج مدير الإضافات إلى استرجاع بعض المعلومات الأساسية عن الإضافة مثل اسمها ووصفها وغير ذلك. تُخزَّن هذه المعلومات في metadata.txt.

ملاحظة

يجب أن تكون جميع البيانات الوصفية بترميز UTF-8.

اسم البيان الوصفي

إلزامي

ملاحظات

name

True

سلسلة نصية قصيرة تحتوي على اسم الإضافة

qgisMinimumVersion

True

الحد الأدنى لإصدار QGIS بصيغة النقاط

qgisMaximumVersion

False

الحد الأقصى لإصدار QGIS بصيغة النقاط

description

True

نص قصير يصف الإضافة، ولا يُسمح باستخدام HTML

about

True

نص أطول يصف الإضافة بالتفصيل، ولا يُسمح باستخدام HTML

version

True

سلسلة نصية قصيرة تحمل رقم الإصدار بصيغة النقاط

author

True

اسم المؤلف

email

True

البريد الإلكتروني للمؤلف، يظهر على الموقع للمستخدمين المسجلين دخولهم فقط، لكنه مرئي في مدير الإضافات بعد تثبيت الإضافة

changelog

False

سلسلة نصية، يمكن أن تكون متعددة الأسطر، ولا يُسمح باستخدام HTML

experimental

False

علامة منطقية، True أو False - تكون True إن كان هذا الإصدار تجريبيًا

deprecated

False

علامة منطقية، True أو False، تنطبق على الإضافة كاملة وليس على الإصدار المرفوع فقط

tags

False

قائمة مفصولة بفواصل، ويُسمح بالمسافات داخل الوسوم المفردة

homepage

False

عنوان URL صالح يشير إلى الصفحة الرئيسية لإضافتك

repository

True

عنوان URL صالح لمستودع الشيفرة المصدرية

tracker

False

عنوان URL صالح للتذاكر وتقارير الأخطاء

icon

False

اسم ملف أو مسار نسبي (نسبةً إلى المجلد الأساسي في الحزمة المضغوطة للإضافة) لصورة مناسبة للويب (PNG، JPEG)

category

False

واحدة من Raster و Vector و Database و Mesh و Web

plugin_dependencies

False

قائمة مفصولة بفواصل على غرار PIP لإضافات أخرى يجب تثبيتها، استخدم أسماء الإضافات كما في حقل name في بياناتها الوصفية

server

False

علامة منطقية، True أو False، تحدد ما إذا كانت للإضافة واجهة خادم

hasProcessingProvider

False

علامة منطقية، True أو False، تحدد ما إذا كانت الإضافة توفر خوارزميات معالجة

افتراضيًا، توضع الإضافات في القائمة Plugins (سنرى في القسم التالي كيفية إضافة عنصر قائمة لإضافتك)، لكن يمكن أيضًا وضعها في القوائم Raster و Vector و Database و Mesh و Web.

يوجد بيان وصفي مقابل هو "category" لتحديد ذلك، كي تُصنَّف الإضافة وفقًا له. يُستخدم هذا البيان الوصفي تلميحًا للمستخدمين يخبرهم أين (في أي قائمة) يمكن العثور على الإضافة. القيم المسموح بها لـ "category" هي: Vector أو Raster أو Database أو Web. مثلًا، إن كانت إضافتك ستتوفر من القائمة Raster، فأضف ما يلي إلى metadata.txt

category=Raster

ملاحظة

إن كان qgisMaximumVersion فارغًا، فسيُعيَّن تلقائيًا إلى الإصدار الرئيسي مضافًا إليه .99 عند الرفع إلى مستودع إضافات Python الرسمي.

مثال على ملف metadata.txt هذا

; the next section is mandatory

[general]
name=HelloWorld
email=me@example.com
author=Just Me
qgisMinimumVersion=3.0
description=This is an example plugin for greeting the world.
    Multiline is allowed:
    lines starting with spaces belong to the same
    field, in this case to the "description" field.
    HTML formatting is not allowed.
about=This paragraph can contain a detailed description
    of the plugin. Multiline is allowed, HTML is not.
version=version 1.2
tracker=http://bugs.itopen.it
repository=http://www.itopen.it/repo
; end of mandatory metadata

; start of optional metadata
category=Raster
changelog=The changelog lists the plugin versions
    and their changes as in the example below:
    1.0 - First stable release
    0.9 - All features implemented
    0.8 - First testing release

; Tags are in comma separated value format, spaces are allowed within the
; tag name.
; Tags should be in English language. Please also check for existing tags and
; synonyms before creating a new one.
tags=wkt,raster,hello world

; these metadata can be empty, they will eventually become mandatory.
homepage=https://www.itopen.it
icon=icon.png

; experimental flag (applies to the single version)
experimental=True

; deprecated flag (applies to the whole plugin and not only to the uploaded version)
deprecated=False

; if empty, it will be automatically set to major version + .99
qgisMaximumVersion=3.99

; Since QGIS 3.8, a comma separated list of plugins to be installed
; (or upgraded) can be specified.
; The example below will try to install (or upgrade) "MyOtherPlugin" version 1.12
; and any version of "YetAnotherPlugin".
; Both "MyOtherPlugin" and "YetAnotherPlugin" names come from their own metadata's
; name field
plugin_dependencies=MyOtherPlugin==1.12,YetAnotherPlugin

16.1.2.2. __init__.py

هذا الملف مطلوب لنظام الاستيراد في Python. كما يشترط QGIS أن يحتوي هذا الملف على الدالة classFactory() التي تُستدعى عند تحميل الإضافة في QGIS. تستقبل هذه الدالة مرجعًا إلى نسخة من QgisInterface ويجب أن تُعيد كائنًا من صنف إضافتك الموجود في mainplugin.py --- وفي حالتنا اسمه TestPlugin (انظر أدناه). هكذا يجب أن يبدو __init__.py

def classFactory(iface):
  from .mainPlugin import TestPlugin
  return TestPlugin(iface)

# any other initialisation needed

16.1.2.3. mainPlugin.py

هنا يحدث السحر، وهكذا يبدو السحر: (مثلًا mainPlugin.py)

from qgis.PyQt.QtGui import *
from qgis.PyQt.QtWidgets import *

# initialize Qt resources from file resources.py
from . import resources

class TestPlugin:

  def __init__(self, iface):
    # save reference to the QGIS interface
    self.iface = iface

  def initGui(self):
    # create action that will start plugin configuration
    self.action = QAction(QIcon("testplug:icon.png"),
                          "Test plugin",
                          self.iface.mainWindow())
    self.action.setObjectName("testAction")
    self.action.setWhatsThis("Configuration for test plugin")
    self.action.setStatusTip("This is status tip")
    self.action.triggered.connect(self.run)

    # add toolbar button and menu item
    self.iface.addToolBarIcon(self.action)
    self.iface.addPluginToMenu("&Test plugins", self.action)

    # connect to signal renderComplete which is emitted when canvas
    # rendering is done
    self.iface.mapCanvas().renderComplete.connect(self.renderTest)

  def unload(self):
    # remove the plugin menu item and icon
    self.iface.removePluginMenu("&Test plugins", self.action)
    self.iface.removeToolBarIcon(self.action)

    # disconnect form signal of the canvas
    self.iface.mapCanvas().renderComplete.disconnect(self.renderTest)

  def run(self):
    # create and show a configuration dialog or something similar
    print("TestPlugin: run called!")

  def renderTest(self, painter):
    # use painter for drawing to map canvas
    print("TestPlugin: renderTest called!")

الدوال الوحيدة التي يجب أن توجد في ملف المصدر الرئيسي للإضافة (مثلًا mainPlugin.py) هي:

  • __init__ التي تتيح الوصول إلى واجهة QGIS

  • initGui() تُستدعى عند تحميل الإضافة

  • unload() تُستدعى عند إلغاء تحميل الإضافة

في المثال أعلاه استُخدمت addPluginToMenu(). ستضيف هذه الدالة إجراء القائمة المقابل إلى القائمة Plugins. توجد دوال بديلة لإضافة الإجراء إلى قائمة أخرى، وإليك قائمة بها:

  • addPluginToRasterMenu()

  • addPluginToVectorMenu()

  • addPluginToDatabaseMenu()

  • addPluginToWebMenu()

جميعها لها الصياغة نفسها التي للدالة addPluginToMenu().

يُنصح بإضافة قائمة إضافتك إلى إحدى هذه الدوال المعرّفة مسبقًا للحفاظ على الاتساق في تنظيم عناصر الإضافات. ومع ذلك، يمكنك إضافة مجموعة القوائم المخصصة الخاصة بك مباشرة إلى شريط القوائم، كما يوضح المثال التالي:

def initGui(self):
    self.menu = QMenu(self.iface.mainWindow())
    self.menu.setObjectName("testMenu")
    self.menu.setTitle("MyMenu")

    self.action = QAction(QIcon("testplug:icon.png"),
                          "Test plugin",
                          self.iface.mainWindow())
    self.action.setObjectName("testAction")
    self.action.setWhatsThis("Configuration for test plugin")
    self.action.setStatusTip("This is status tip")
    self.action.triggered.connect(self.run)
    self.menu.addAction(self.action)

    menuBar = self.iface.mainWindow().menuBar()
    menuBar.insertMenu(self.iface.firstRightStandardMenu().menuAction(),
                       self.menu)

def unload(self):
    self.menu.deleteLater()

لا تنسَ تعيين objectName لكل من QAction و QMenu إلى اسم خاص بإضافتك حتى يمكن تخصيصهما.

مع أنه يمكن إضافة إجراءي المساعدة وحول (about) إلى قائمتك المخصصة أيضًا، فإن المكان المناسب لإتاحتهما هو قائمة QGIS الرئيسية Help ► Plugins. ويتم ذلك باستخدام الدالة pluginHelpMenu().

def initGui(self):

    self.help_action = QAction(
        QIcon("testplug:icon.png"),
        self.tr("Test Plugin..."),
        self.iface.mainWindow()
    )
    # Add the action to the Help menu
    self.iface.pluginHelpMenu().addAction(self.help_action)

    self.help_action.triggered.connect(self.show_help)

@staticmethod
def show_help():
    """ Open the online help. """
    QDesktopServices.openUrl(QUrl('https://docs.qgis.org'))

def unload(self):

    self.iface.pluginHelpMenu().removeAction(self.help_action)
    del self.help_action

عند العمل على إضافة حقيقية، من الحكمة كتابة الإضافة في مجلد (عمل) آخر وإنشاء ملف makefile يولّد ملفات الواجهة والموارد ويثبّت الإضافة في تثبيت QGIS لديك.

16.1.3. توثيق الإضافات

يمكن كتابة توثيق الإضافة على شكل ملفات مساعدة HTML. توفر الوحدة qgis.utils الدالة showPluginHelp() التي تفتح متصفح ملفات المساعدة بالطريقة نفسها التي تعمل بها مساعدة QGIS الأخرى.

تبحث الدالة showPluginHelp() عن ملفات المساعدة في المجلد نفسه الذي توجد فيه الوحدة المستدعية. وتبحث بالترتيب عن index-ll_cc.html و index-ll.html و index-en.html و index-en_us.html و index.html، وتعرض أول ملف تجده. هنا ll_cc هي إعدادات اللغة المحلية في QGIS. يتيح هذا تضمين عدة ترجمات للتوثيق مع الإضافة.

يمكن للدالة showPluginHelp() أيضًا أن تأخذ المعاملات packageName الذي يحدد إضافة معينة ستُعرض مساعدتها، و filename الذي يمكن أن يحل محل "index" في أسماء الملفات المبحوث عنها، و section وهو اسم وسم مرساة html في المستند الذي سيتموضع عنده المتصفح.

16.1.4. ترجمة الإضافات

ببضع خطوات يمكنك إعداد البيئة لتوطين الإضافة بحيث تُحمَّل الإضافة بلغات مختلفة بحسب إعدادات اللغة المحلية في حاسوبك.

16.1.4.1. متطلبات البرمجيات

أسهل طريقة لإنشاء جميع ملفات الترجمة وإدارتها هي تثبيت Qt Linguist. في بيئة GNU/Linux مبنية على Debian يمكنك تثبيته بكتابة:

sudo apt install qttools5-dev-tools

16.1.4.2. الملفات والمجلد

عند إنشاء الإضافة ستجد المجلد i18n داخل المجلد الرئيسي للإضافة.

يجب أن تكون جميع ملفات الترجمة داخل هذا المجلد.

16.1.4.2.1. ملف .pro

عليك أولًا إنشاء ملف .pro، وهو ملف مشروع يمكن إدارته بواسطة Qt Linguist.

في ملف .pro هذا عليك تحديد جميع الملفات والنماذج التي تريد ترجمتها. يُستخدم هذا الملف لإعداد ملفات التوطين ومتغيراته. إليك ملف مشروع ممكنًا يطابق بنية الإضافة المثال:

FORMS = ../form.ui
SOURCES = ../your_plugin.py
TRANSLATIONS = your_plugin_it.ts

قد تتبع إضافتك بنية أكثر تعقيدًا، وقد تكون موزعة على عدة ملفات. في هذه الحالة، ضع في حسبانك أن pylupdate5، وهو البرنامج الذي نستخدمه لقراءة ملف .pro وتحديث السلاسل القابلة للترجمة، لا يوسّع أحرف البدل (wild card)، لذا عليك وضع كل ملف صراحةً في ملف .pro. وقد يبدو ملف مشروعك حينها على النحو التالي:

FORMS = ../ui/about.ui ../ui/feedback.ui \
        ../ui/main_dialog.ui
SOURCES = ../your_plugin.py ../computation.py \
          ../utils.py

علاوة على ذلك، فإن الملف your_plugin.py هو الملف الذي يستدعي جميع القوائم والقوائم الفرعية لإضافتك في شريط أدوات QGIS، وتريد ترجمتها كلها.

أخيرًا، يمكنك باستخدام المتغير TRANSLATIONS تحديد لغات الترجمة التي تريدها.

تحذير

احرص على تسمية ملف ts على النحو your_plugin_ + language + .ts وإلا فسيفشل تحميل اللغة! استخدم الاختصار المكوّن من حرفين للغة (it للإيطالية، de للألمانية، إلخ...)

16.1.4.2.2. ملف .ts

بعد إنشاء ملف .pro تكون جاهزًا لتوليد ملف (أو ملفات) .ts للغة (أو لغات) إضافتك.

افتح الطرفية، وانتقل إلى المجلد your_plugin/i18n واكتب:

pylupdate5 your_plugin.pro

يُفترض أن ترى ملف (أو ملفات) your_plugin_language.ts.

افتح ملف .ts باستخدام Qt Linguist وابدأ الترجمة.

16.1.4.2.3. ملف .qm

عند انتهائك من ترجمة إضافتك (إن لم تكتمل بعض السلاسل فستُستخدم لغة المصدر لها)، عليك إنشاء ملف .qm (وهو ملف .ts المُترجَم (compiled) الذي سيستخدمه QGIS).

افتح الطرفية فقط، وانتقل بالأمر cd إلى المجلد your_plugin/i18n واكتب:

lrelease your_plugin.ts

الآن سترى ملف (أو ملفات) your_plugin.qm في المجلد i18n.

16.1.4.3. الترجمة باستخدام Makefile

بدلًا من ذلك، يمكنك استخدام ملف makefile لاستخراج الرسائل من شيفرة Python ونوافذ حوار Qt، إن كنت قد أنشأت إضافتك باستخدام Plugin Builder. في بداية ملف Makefile يوجد المتغير LOCALES:

LOCALES = en

أضف اختصار اللغة إلى هذا المتغير، مثلًا للغة الهنغارية:

LOCALES = en hu

الآن يمكنك توليد الملف hu.ts أو تحديثه (والملف en.ts أيضًا) من المصادر عبر:

make transup

بعد ذلك تكون قد حدّثت ملف .ts لجميع اللغات المعيّنة في المتغير LOCALES. استخدم Qt Linguist لترجمة رسائل البرنامج. وبعد إنهاء الترجمة يمكن إنشاء ملفات .qm عبر الأمر transcompile:

make transcompile

عليك توزيع ملفات .ts مع إضافتك.

16.1.4.4. تحميل الإضافة

لكي ترى ترجمة إضافتك، افتح QGIS وغيّر اللغة (Settings ► Options ► General) ثم أعد تشغيل QGIS.

يُفترض أن ترى إضافتك باللغة الصحيحة.

تحذير

إن غيّرت شيئًا في إضافتك (واجهات جديدة، قائمة جديدة، إلخ...) فعليك إعادة توليد النسخة المحدّثة من ملفي .ts و .qm، لذا شغّل الأمر السابق مرة أخرى.

16.1.5. مشاركة إضافتك

يستضيف QGIS مئات الإضافات في مستودع الإضافات. فكّر في مشاركة إضافتك! فهي ستوسّع إمكانات QGIS وسيتمكن الآخرون من التعلم من شيفرتك. يمكن العثور على جميع الإضافات المستضافة وتثبيتها من داخل QGIS باستخدام مدير الإضافات.

المعلومات والمتطلبات موجودة هنا: plugins.qgis.org.

16.1.6. نصائح وحيل

16.1.6.1. Plugin Reloader

خلال تطوير إضافتك ستحتاج كثيرًا إلى إعادة تحميلها في QGIS لاختبارها. وهذا سهل جدًا باستخدام الإضافة Plugin Reloader. يمكنك العثور عليها عبر مدير الإضافات.

16.1.6.2. أتمتة التحزيم والإصدار والترجمة باستخدام qgis-plugin-ci

توفر qgis-plugin-ci واجهة سطر أوامر لإجراء التحزيم والنشر الآلي لإضافات QGIS على حاسوبك، أو باستخدام التكامل المستمر مثل GitHub workflows أو Gitlab-CI، إضافة إلى Transifex للترجمة.

تتيح إصدار الإضافة أو ترجمتها أو نشرها أو توليد ملف XML لمستودع الإضافات عبر سطر الأوامر (CLI) أو ضمن إجراءات التكامل المستمر (CI).

16.1.6.3. الوصول إلى الإضافات

يمكنك الوصول إلى جميع أصناف الإضافات المثبتة من داخل QGIS باستخدام Python، وهو أمر مفيد لأغراض التنقيح.

my_plugin = qgis.utils.plugins['My Plugin']

16.1.6.4. رسائل السجل

للإضافات تبويب خاص بها ضمن سجل لوحة الرسائل.

16.1.6.5. ملف الموارد

تستخدم بعض الإضافات ملفات موارد، مثل resources.qrc الذي يعرّف موارد الواجهة الرسومية كالأيقونات:

<RCC>
  <qresource prefix="/plugins/testplug" >
    <file>icon.png</file>
  </qresource>
</RCC>

من الجيد استخدام بادئة لا تتعارض مع الإضافات الأخرى أو مع أي أجزاء من QGIS، وإلا فقد تحصل على موارد لم تكن تريدها. الآن تحتاج فقط إلى توليد ملف Python يحتوي على الموارد. ويتم ذلك بالأمر pyrcc5:

pyrcc5 -o resources.py resources.qrc

ملاحظة

في بيئات Windows، ستؤدي محاولة تشغيل pyrcc5 من موجه الأوامر أو Powershell غالبًا إلى الخطأ "Windows cannot access the specified device, path, or file [...]". أسهل حل على الأرجح هو استخدام OSGeo4W Shell، لكن إن كنت معتادًا على تعديل متغير البيئة PATH أو تحديد مسار الملف التنفيذي صراحةً، فيُفترض أن تجده في <Your QGIS Install Directory>\bin\pyrcc5.exe.