diff --git a/AGENTS.md b/AGENTS.md index 251cf42..b6107b8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -83,6 +83,58 @@ Další pravidla: definice pole (`QgsField`), naplnění hodnoty z dokumentu, překlad hlavičky sloupce a export atributů. +### Kompatibilita s Qt6 / QGIS 4 + +Plugin cílí na QGIS 3.44 i na QGIS 4 (`qgisMaximumVersion=4.99.0`), tedy na +Qt5 i Qt6 zároveň. **Tohle se drží rigorózně** – ne až před releasem, ale při +každé změně kódu. Chování obou větví se liší tiše: pod Qt5 projde i to, co +QGIS 4 odmítne, takže lokální „funguje mi to“ nic nedokazuje. + +Závazná pravidla: + +- **Nikdy neimportuj přímo z `PyQt5` ani z `PyQt6`.** Vždy přes shim + `qgis.PyQt.*`. Ten mimo jiné pod Qt6 přetahuje `QAction`, `QActionGroup` + a `QShortcut` z `QtGui`, takže import z `qgis.PyQt.QtWidgets` je správně. +- **Enumy vždy plně kvalifikované (scoped).** `Qgis.MessageLevel.Info`, ne + `Qgis.Info`; `QgsTask.Flag.CanCancel`, ne `QgsTask.CanCancel`; + `QgsWkbTypes.GeometryType.PointGeometry`, ne `QgsWkbTypes.PointGeometry`. + Totéž pro Qt: `Qt.CheckState.Checked`, `QDialogButtonBox.StandardButton.Ok`. + Zkrácené tvary sice v QGIS 4.2 zatím fungují, ale oficiální kontrola je + hlásí a do budoucna mizí. +- **Zdrojové `.py` soubory ukládej bez BOM.** Kontrolní skript čte soubor + jako UTF-8 bez `utf-8-sig` a na BOM spadne s + `SyntaxError: invalid non-printable character U+FEFF`, takže se takový + soubor **vůbec nezkontroluje**. (`codelists/heslar.csv` BOM mít smí, tam je + kvůli Excelu.) +- Nepoužívej API zrušená v Qt6: `exec_()`, `QRegExp`, `QDesktopWidget`, + `QApplication.desktop()`, `Qt.MidButton`, `QFontMetrics.width()`, + `setResizeMode`, atributy `AA_EnableHighDpiScaling` / `AA_UseHighDpiPixmaps`. +- `supportsQt6=True` v `metadata.txt` **nepatří** – bylo zrušeno; o zařazení + mezi „QGIS 4 Ready“ rozhoduje rozsah `qgisMinimumVersion` až + `qgisMaximumVersion`. + +Ověření před PR, který mění Python kód: + +```sh +# oficiální kontrola, kterou pouští i plugins.qgis.org (pyqgis4-checker) +docker run --rm --pull always --user $(id -u):$(id -g) \ + --workdir /workspace/ -v "$(pwd):/workspace/" \ + ghcr.io/qgis/pyqgis4-checker:main-ubuntu \ + pyqt5_to_pyqt6.py --dry_run --logfile /workspace/pyqt6_checker.log . +``` + +Prázdný log = čisté. Kontrola je na plugins.qgis.org informativní +(neblokuje schválení), ale nález znamená, že plugin v QGIS 4 dříve nebo +později přestane fungovat. + +Když je po ruce QGIS 4 (např. flatpak `org.qgis.qgis`), ověř navíc, že se +plugin pod Qt6 opravdu načte: + +```sh +flatpak run --command=sh org.qgis.qgis -c \ + 'PYTHONPATH=/app/share/qgis/python python3 -c "import qgis.core"' +``` + ## Verzování a release - Verze pluginu žije v **`amcr_viewer/metadata.txt`** (`version=`). diff --git a/amcr_viewer/amcr_codelists.py b/amcr_viewer/amcr_codelists.py index 962b928..d537f81 100644 --- a/amcr_viewer/amcr_codelists.py +++ b/amcr_viewer/amcr_codelists.py @@ -1,4 +1,4 @@ -# -*- coding: utf-8 -*- +# -*- coding: utf-8 -*- import os import csv import requests @@ -90,7 +90,7 @@ def parse_codelist_file(filename, target_dict=None): except Exception as e: QgsMessageLog.logMessage( f"AMČR Codelist Read Error for {filename}: {e}", - "AMČR", Qgis.Critical) + "AMČR", Qgis.MessageLevel.Critical) return target_dict @@ -216,7 +216,7 @@ def fetch_set(base_url, internal_name, api_set, task=None): except Exception as e: QgsMessageLog.logMessage( f"Chyba u setu {api_set}: {e}", - "AMČR", Qgis.Warning) + "AMČR", Qgis.MessageLevel.Warning) break return dataset @@ -240,7 +240,7 @@ def download_heslare(task=None): QgsMessageLog.logMessage( f"Zpracovávám kategorii: {interni}...", - "AMČR", Qgis.Info) + "AMČR", Qgis.MessageLevel.Info) # Pass the task correctly to the updated fetch function data = fetch_set(base_url, interni, api_nazev, task=task) diff --git a/amcr_viewer/amcr_dialog.py b/amcr_viewer/amcr_dialog.py index 645a64c..ba6401a 100644 --- a/amcr_viewer/amcr_dialog.py +++ b/amcr_viewer/amcr_dialog.py @@ -1,4 +1,4 @@ -# -*- coding: utf-8 -*- +# -*- coding: utf-8 -*- from qgis.PyQt.QtWidgets import (QDialog, QVBoxLayout, QLineEdit, QDialogButtonBox, QCheckBox, QGroupBox, QPushButton, @@ -38,7 +38,7 @@ _ACTIVE_TASKS = [] class UpdateCodelistsTask(QgsTask): def __init__(self, description): - super().__init__(description, QgsTask.CanCancel) + super().__init__(description, QgsTask.Flag.CanCancel) self.success = False self.exception = None @@ -59,18 +59,18 @@ class UpdateCodelistsTask(QgsTask): refresh_globals() QgsMessageLog.logMessage( "Hesláře AMČR byly úspěšně aktualizovány.", - "AMČR", Qgis.Info + "AMČR", Qgis.MessageLevel.Info ) else: if self.isCanceled(): QgsMessageLog.logMessage( "Aktualizace heslářů byla zrušena.", - "AMČR", Qgis.Warning + "AMČR", Qgis.MessageLevel.Warning ) else: QgsMessageLog.logMessage( f"Chyba aktualizace: {self.exception}", - "AMČR", Qgis.Critical + "AMČR", Qgis.MessageLevel.Critical ) diff --git a/amcr_viewer/amcr_tools.py b/amcr_viewer/amcr_tools.py index d0d55ac..cb6a51c 100644 --- a/amcr_viewer/amcr_tools.py +++ b/amcr_viewer/amcr_tools.py @@ -1,4 +1,4 @@ -# -*- coding: utf-8 -*- +# -*- coding: utf-8 -*- from qgis.core import (QgsProject, QgsVectorLayer, QgsFeature, QgsGeometry, QgsField, QgsCoordinateReferenceSystem, QgsCoordinateTransform, QgsWkbTypes, Qgis, @@ -1138,11 +1138,11 @@ def load_amcr_data(canvas, bb, filters=None, if geom.isGeosValid(): t = geom.type() target_list = None - if t == QgsWkbTypes.PolygonGeometry: + if t == QgsWkbTypes.GeometryType.PolygonGeometry: target_list = feats_p - elif t == QgsWkbTypes.LineGeometry: + elif t == QgsWkbTypes.GeometryType.LineGeometry: target_list = feats_l - elif t == QgsWkbTypes.PointGeometry: + elif t == QgsWkbTypes.GeometryType.PointGeometry: target_list = feats_pt if target_list is None: @@ -1227,11 +1227,11 @@ def load_amcr_data(canvas, bb, filters=None, if geom.isGeosValid(): t = geom.type() target_list = None - if t == QgsWkbTypes.PolygonGeometry: + if t == QgsWkbTypes.GeometryType.PolygonGeometry: target_list = feats_p - elif t == QgsWkbTypes.LineGeometry: + elif t == QgsWkbTypes.GeometryType.LineGeometry: target_list = feats_l - elif t == QgsWkbTypes.PointGeometry: + elif t == QgsWkbTypes.GeometryType.PointGeometry: target_list = feats_pt if target_list is None: