Fixed compatibility issues with QGIS 4 (#24)

* used PyQt6 compatible objects

* metadata update
This commit is contained in:
2026-03-13 10:48:12 +01:00
committed by GitHub
parent 8825ac3272
commit 5a951edec7
6 changed files with 111 additions and 110 deletions
+1
View File
@@ -210,3 +210,4 @@ __marimo__/
README_files/ README_files/
README.html README.html
amcr_viewer.zip
+60 -62
View File
@@ -13,6 +13,7 @@ from .amcr_codelists import (OBDOBI, TYP_AKCE, KRAJE, AREAL, ORGANIZACE,
class FilterableSelectionDialog(QDialog): class FilterableSelectionDialog(QDialog):
""" """
A custom dialog for selecting multiple items from a list with a search filter. A custom dialog for selecting multiple items from a list with a search filter.
Updated for PyQt6/Qt6 compatibility.
""" """
def __init__(self, title, data_dict, preselected_codes, parent=None): def __init__(self, title, data_dict, preselected_codes, parent=None):
super().__init__(parent) super().__init__(parent)
@@ -37,7 +38,9 @@ class FilterableSelectionDialog(QDialog):
layout.addWidget(self.list_widget) layout.addWidget(self.list_widget)
# Standard OK/Cancel dialog buttons # Standard OK/Cancel dialog buttons
buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) buttons = QDialogButtonBox(
QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
)
buttons.accepted.connect(self.accept) buttons.accepted.connect(self.accept)
buttons.rejected.connect(self.reject) buttons.rejected.connect(self.reject)
layout.addWidget(buttons) layout.addWidget(buttons)
@@ -52,16 +55,16 @@ class FilterableSelectionDialog(QDialog):
item = QListWidgetItem(name) item = QListWidgetItem(name)
# Store the actual code (ID) hidden in the UserRole # Store the actual code (ID) hidden in the UserRole
item.setData(Qt.UserRole, code) item.setData(Qt.ItemDataRole.UserRole, code)
# Make the item checkable (adds a checkbox) # Make the item checkable (adds a checkbox)
item.setFlags(item.flags() | Qt.ItemIsUserCheckable) item.setFlags(item.flags() | Qt.ItemFlag.ItemIsUserCheckable)
# Restore previous selection state # Restore previous selection state
if code in self.preselected: if code in self.preselected:
item.setCheckState(Qt.Checked) item.setCheckState(Qt.CheckState.Checked)
else: else:
item.setCheckState(Qt.Unchecked) item.setCheckState(Qt.CheckState.Unchecked)
self.list_widget.addItem(item) self.list_widget.addItem(item)
@@ -70,10 +73,7 @@ class FilterableSelectionDialog(QDialog):
search_text = text.lower() search_text = text.lower()
for i in range(self.list_widget.count()): for i in range(self.list_widget.count()):
item = self.list_widget.item(i) item = self.list_widget.item(i)
if search_text not in item.text().lower(): item.setHidden(search_text not in item.text().lower())
item.setHidden(True)
else:
item.setHidden(False)
def get_selected_codes(self): def get_selected_codes(self):
"""Returns the hidden codes and display labels of all checked items.""" """Returns the hidden codes and display labels of all checked items."""
@@ -81,8 +81,8 @@ class FilterableSelectionDialog(QDialog):
labels = [] labels = []
for i in range(self.list_widget.count()): for i in range(self.list_widget.count()):
item = self.list_widget.item(i) item = self.list_widget.item(i)
if item.checkState() == Qt.Checked: if item.checkState() == Qt.CheckState.Checked:
codes.append(item.data(Qt.UserRole)) codes.append(item.data(Qt.ItemDataRole.UserRole))
labels.append(item.text()) labels.append(item.text())
return codes, labels return codes, labels
@@ -93,7 +93,7 @@ class AmcrFilterDialog(QDialog):
The main filtering UI where users set criteria before downloading data. The main filtering UI where users set criteria before downloading data.
""" """
def __init__(self, typ_dat, parent=None): def __init__(self, typ_dat, parent=None):
super(AmcrFilterDialog, self).__init__(parent) super().__init__(parent)
self.setWindowTitle("Filtr AMČR") self.setWindowTitle("Filtr AMČR")
self.resize(500, 750) self.resize(500, 750)
@@ -189,7 +189,9 @@ class AmcrFilterDialog(QDialog):
layout.addStretch(1) layout.addStretch(1)
# Main dialog OK/Cancel buttons # Main dialog OK/Cancel buttons
buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) buttons = QDialogButtonBox(
QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
)
buttons.accepted.connect(self.accept) buttons.accepted.connect(self.accept)
buttons.rejected.connect(self.reject) buttons.rejected.connect(self.reject)
layout.addWidget(buttons) layout.addWidget(buttons)
@@ -197,60 +199,56 @@ class AmcrFilterDialog(QDialog):
self.setLayout(layout) self.setLayout(layout)
def setup_picker(self, label_text, cache_key, data_source, extra_btn=None): def setup_picker(self, label_text, cache_key, data_source, extra_btn=None):
""" """
Creates a reusable UI component consisting of a label, a read-only Creates a reusable UI component consisting of a label, a read-only
text field showing selected items, and a button to open the selection dialog. text field showing selected items, and a button to open the selection dialog.
""" """
row_widget = QGroupBox(label_text) row_widget = QGroupBox(label_text)
# row_widget.setFlat(True) row_layout = QHBoxLayout()
row_layout.setContentsMargins(5, 5, 5, 5)
row_layout = QHBoxLayout()
row_layout.setContentsMargins(5, 5, 5, 5) # Read-only field displaying the names of selected items
display_field = QLineEdit()
# Read-only field displaying the names of selected items display_field.setReadOnly(True)
display_field = QLineEdit() display_field.setPlaceholderText("Nic nevybráno (vše)")
display_field.setReadOnly(True) display_field.setStyleSheet("background-color: #f0f0f0; color: #333;")
display_field.setPlaceholderText("Nic nevybráno (vše)")
display_field.setStyleSheet("background-color: #f0f0f0; color: #333;") btn = QPushButton("Vybrat...")
btn.setFixedWidth(80)
btn = QPushButton("Vybrat...")
btn.setFixedWidth(80) # Nested function that handles opening the dialog and saving results
def open_dialog():
# Nested function that handles opening the dialog and saving results dlg = FilterableSelectionDialog(label_text, data_source, self.selection_cache[cache_key], self)
def open_dialog(): if dlg.exec() == QDialog.DialogCode.Accepted: # PyQt6: DialogCode
dlg = FilterableSelectionDialog(label_text, data_source, self.selection_cache[cache_key], self) codes, labels = dlg.get_selected_codes()
if dlg.exec() == QDialog.Accepted: # Update local cache with selected IDs
codes, labels = dlg.get_selected_codes() self.selection_cache[cache_key] = codes
# Update the UI text field with selected names
# Update local cache with selected IDs if labels:
self.selection_cache[cache_key] = codes display_field.setText(", ".join(labels))
else:
# Update the UI text field with selected names display_field.clear()
if labels:
display_field.setText(", ".join(labels)) # Special case: Pre-fill specific accuracy levels by default
else: if cache_key == 'pian_presnost':
display_field.clear() display_field.setText("odchylka jednotky metrů, odchylka desítky metrů, odchylka stovky metrů")
self.selection_cache[cache_key] = ['HES-000861', 'HES-000862', 'HES-000863']
# Special case: Pre-fill specific accuracy levels by default
if cache_key == 'pian_presnost':
display_field.setText("odchylka jednotky metrů, odchylka desítky metrů, odchylka stovky metrů")
self.selection_cache[cache_key] = ['HES-000861', 'HES-000862', 'HES-000863']
btn.clicked.connect(open_dialog) btn.clicked.connect(open_dialog)
row_layout.addWidget(display_field)
row_layout.addWidget(btn)
# Add an optional extra button (e.g., the refresh button for leaders)
if extra_btn:
row_layout.addWidget(extra_btn)
row_layout.addWidget(display_field) row_widget.setLayout(row_layout)
row_layout.addWidget(btn) return row_widget
# Add an optional extra button (e.g., the refresh button for leaders)
if extra_btn:
row_layout.addWidget(extra_btn)
row_widget.setLayout(row_layout)
return row_widget
def action_update_vedouci(self): def action_update_vedouci(self):
# Change cursor to loading state to indicate background task # Change cursor to loading state to indicate background task
QApplication.setOverrideCursor(Qt.WaitCursor) QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor)
try: try:
success, msg = download_vedouci() success, msg = download_vedouci()
if success: if success:
+43 -42
View File
@@ -2,10 +2,11 @@
from qgis.gui import QgsMapToolIdentifyFeature from qgis.gui import QgsMapToolIdentifyFeature
from qgis.core import (QgsProject, QgsVectorLayer, QgsFeature, QgsGeometry, from qgis.core import (QgsProject, QgsVectorLayer, QgsFeature, QgsGeometry,
QgsField, QgsCoordinateReferenceSystem, QgsCoordinateTransform, QgsField, QgsCoordinateReferenceSystem, QgsCoordinateTransform,
QgsWkbTypes, QgsRelation, QgsEditorWidgetSetup) QgsWkbTypes, QgsRelation, QgsEditorWidgetSetup, Qgis)
from qgis.utils import iface from qgis.utils import iface
from qgis.PyQt.QtCore import QVariant, Qt from qgis.PyQt.QtCore import Qt, QMetaType
from qgis.PyQt.QtWidgets import QMessageBox, QApplication from qgis.PyQt.QtWidgets import QMessageBox, QApplication
from qgis.PyQt.QtGui import QCursor
import requests import requests
import json import json
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
@@ -56,8 +57,8 @@ def load_amcr_data(canvas, bb, filters=None, typ_dat="akce", komponenty="false")
url = "https://digiarchiv.aiscr.cz/api/search/query" url = "https://digiarchiv.aiscr.cz/api/search/query"
iface.messageBar().pushMessage("AMCR", "Hledám záznamy...", level=1) iface.messageBar().pushMessage("AMCR", "Hledám záznamy...", level=Qgis.MessageLevel.Info)
QApplication.setOverrideCursor(Qt.WaitCursor) QApplication.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor))
try: try:
# ========================================== # ==========================================
@@ -128,7 +129,7 @@ def load_amcr_data(canvas, bb, filters=None, typ_dat="akce", komponenty="false")
if len(docs) >= num_found: if len(docs) >= num_found:
break break
if len(docs) >= MAX_LIMIT: if len(docs) >= MAX_LIMIT:
iface.messageBar().pushMessage("AMCR", f"Limit {MAX_LIMIT} záznamů dosažen.", level=1) iface.messageBar().pushMessage("AMCR", f"Limit {MAX_LIMIT} záznamů dosažen.", level=Qgis.MessageLevel.Warning)
break break
current_page += 1 current_page += 1
@@ -139,7 +140,7 @@ def load_amcr_data(canvas, bb, filters=None, typ_dat="akce", komponenty="false")
break break
if not docs: if not docs:
iface.messageBar().pushMessage("AMCR", "Žádné záznamy nenalezeny.", level=1) iface.messageBar().pushMessage("AMCR", "Žádné záznamy nenalezeny.", level=Qgis.MessageLevel.Warning)
return return
# ========================================== # ==========================================
@@ -268,7 +269,7 @@ def load_amcr_data(canvas, bb, filters=None, typ_dat="akce", komponenty="false")
feats_k.append(feat) feats_k.append(feat)
if not target_pian_ids: if not target_pian_ids:
iface.messageBar().pushMessage("AMCR", f"Nalezeno {len(docs)} záznamů, ale žádný nemá geometrii.", level=1) iface.messageBar().pushMessage("AMCR", f"Nalezeno {len(docs)} záznamů, ale žádný nemá geometrii.", level=Qgis.MessageLevel.Warning)
return return
@@ -280,7 +281,7 @@ def load_amcr_data(canvas, bb, filters=None, typ_dat="akce", komponenty="false")
docs_pian = [] docs_pian = []
BATCH_PIAN = 200 # Geometry requests are batch-processed to stay under URL length limits BATCH_PIAN = 200 # Geometry requests are batch-processed to stay under URL length limits
iface.messageBar().pushMessage("AMCR", f"Záznamů: {len(docs)} (z toho {actions_with_geom} s mapou). Stahuji {total_pians} unikátních geometrií, vykresluji {target_pian_ids_count} geometrií...", level=1) iface.messageBar().pushMessage("AMCR", f"Záznamů: {len(docs)} (z toho {actions_with_geom} s mapou). Stahuji {total_pians} unikátních geometrií, vykresluji {target_pian_ids_count} geometrií...", level=Qgis.MessageLevel.Info)
fl_pian = ["ident_cely", "pian_typ", "pian_chranene_udaje", "pian_presnost"] fl_pian = ["ident_cely", "pian_typ", "pian_chranene_udaje", "pian_presnost"]
@@ -318,43 +319,43 @@ def load_amcr_data(canvas, bb, filters=None, typ_dat="akce", komponenty="false")
# Define attribute table structure # Define attribute table structure
cols = [ cols = [
QgsField("PIAN", QVariant.String), QgsField("PIAN", QMetaType.Type.QString),
QgsField("Přesnost", QVariant.String), QgsField("Přesnost", QMetaType.Type.QString),
QgsField("PIAN typ", QVariant.String), QgsField("PIAN typ", QMetaType.Type.QString),
QgsField("Dokumentační jednotka", QVariant.String), QgsField("Dokumentační jednotka", QMetaType.Type.QString),
QgsField("Typ dokumentační jednotky", QVariant.String), QgsField("Typ dokumentační jednotky", QMetaType.Type.QString),
QgsField("Definiční bod(y) (WGS-84)", QVariant.String), QgsField("Definiční bod(y) (WGS-84)", QMetaType.Type.QString),
QgsField(archeologicky_zaznam, QVariant.String), QgsField(archeologicky_zaznam, QMetaType.Type.QString),
QgsField("Odkaz do Digitálního archivu AMČR", QVariant.String), QgsField("Odkaz do Digitálního archivu AMČR", QMetaType.Type.QString),
QgsField("Okres", QVariant.String), QgsField("Okres", QMetaType.Type.QString),
QgsField("Katastr", QVariant.String), QgsField("Katastr", QMetaType.Type.QString),
QgsField("Další katastry", QVariant.String) QgsField("Další katastry", QMetaType.Type.QString)
] ]
# Extend table based on data type # Extend table based on data type
if typ_dat == "akce": if typ_dat == "akce":
cols += [ cols += [
QgsField("Akce lokalizace", QVariant.String), QgsField("Akce lokalizace", QMetaType.Type.QString),
QgsField("Vedoucí akce", QVariant.String), QgsField("Vedoucí akce", QMetaType.Type.QString),
QgsField("Organizace", QVariant.String), QgsField("Organizace", QMetaType.Type.QString),
QgsField("Specifikace data", QVariant.String), QgsField("Specifikace data", QMetaType.Type.QString),
QgsField("Datum zahájeni", QVariant.String), QgsField("Datum zahájeni", QMetaType.Type.QString),
QgsField("Datum ukončení", QVariant.String), QgsField("Datum ukončení", QMetaType.Type.QString),
QgsField("Hlavní typ", QVariant.String), QgsField("Hlavní typ", QMetaType.Type.QString),
QgsField("Vedlejší typ", QVariant.String), QgsField("Vedlejší typ", QMetaType.Type.QString),
QgsField("Zjištění", QVariant.String), QgsField("Zjištění", QMetaType.Type.QString),
QgsField("Akce nahrazuje NZ", QVariant.String), QgsField("Akce nahrazuje NZ", QMetaType.Type.QString),
] ]
elif typ_dat == "lokalita": elif typ_dat == "lokalita":
cols += [ cols += [
QgsField("nazev_lokality", QVariant.String), QgsField("nazev_lokality", QMetaType.Type.QString),
QgsField("popis_lokality", QVariant.String), QgsField("popis_lokality", QMetaType.Type.QString),
QgsField("typ_lokality", QVariant.String), QgsField("typ_lokality", QMetaType.Type.QString),
QgsField("druh_lokality", QVariant.String), QgsField("druh_lokality", QMetaType.Type.QString),
QgsField("zachovalost", QVariant.String) QgsField("zachovalost", QMetaType.Type.QString)
] ]
cols.append(QgsField("Přístupnost", QVariant.String)) cols.append(QgsField("Přístupnost", QMetaType.Type.QString))
# Use aliases for technical field names # Use aliases for technical field names
alias_map = { alias_map = {
@@ -370,10 +371,10 @@ def load_amcr_data(canvas, bb, filters=None, typ_dat="akce", komponenty="false")
vl_komponenty = QgsVectorLayer("None", "AMCR Komponenty", "memory") vl_komponenty = QgsVectorLayer("None", "AMCR Komponenty", "memory")
pr = vl_komponenty.dataProvider() pr = vl_komponenty.dataProvider()
komponenty_cols = [ komponenty_cols = [
QgsField("komponenta", QVariant.String), QgsField("komponenta", QMetaType.Type.QString),
QgsField("dj_id", QVariant.String), QgsField("dj_id", QMetaType.Type.QString),
QgsField("komponenta_areal", QVariant.String), QgsField("komponenta_areal", QMetaType.Type.QString),
QgsField("komponenta_obdobi", QVariant.String) QgsField("komponenta_obdobi", QMetaType.Type.QString)
] ]
pr.addAttributes(komponenty_cols) pr.addAttributes(komponenty_cols)
vl_komponenty.updateFields() vl_komponenty.updateFields()
@@ -493,7 +494,7 @@ def load_amcr_data(canvas, bb, filters=None, typ_dat="akce", komponenty="false")
added += len(f) added += len(f)
if added > 0: if added > 0:
iface.messageBar().pushMessage("AMCR", f"Hotovo. Záznamů: {len(docs)} (s geom: {actions_with_geom}). Vykresleno: {added} prvků.", level=0) iface.messageBar().pushMessage("AMCR", f"Hotovo. Záznamů: {len(docs)} (s geom: {actions_with_geom}). Vykresleno: {added} prvků.", level=Qgis.MessageLevel.Success)
# --- RELATIONSHIP MANAGEMENT --- # --- RELATIONSHIP MANAGEMENT ---
# Set up automatic links between spatial layers and the component table # Set up automatic links between spatial layers and the component table
@@ -518,10 +519,10 @@ def load_amcr_data(canvas, bb, filters=None, typ_dat="akce", komponenty="false")
print(f"Relace pro {label} není validní!") print(f"Relace pro {label} není validní!")
else: else:
iface.messageBar().pushMessage("AMCR", "Žádná data k zobrazení.", level=1) iface.messageBar().pushMessage("AMCR", "Žádná data k zobrazení.", level=Qgis.MessageLevel.Info)
except Exception as e: except Exception as e:
iface.messageBar().pushMessage("Chyba", str(e), level=2) iface.messageBar().pushMessage("Chyba", str(e), level=Qgis.MessageLevel.Critical)
finally: finally:
# Always restore cursor, even after failure # Always restore cursor, even after failure
QApplication.restoreOverrideCursor() QApplication.restoreOverrideCursor()
+3 -3
View File
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from qgis.PyQt.QtCore import QSettings, QTranslator, QCoreApplication from qgis.PyQt.QtCore import QSettings, QTranslator, QCoreApplication
from qgis.PyQt.QtGui import QIcon from qgis.PyQt.QtGui import QIcon
from qgis.PyQt.QtWidgets import QMenu, QAction, QToolButton from qgis.PyQt.QtWidgets import QMenu, QAction, QToolButton, QDialog
from .amcr_tools import load_amcr_data from .amcr_tools import load_amcr_data
from .amcr_dialog import AmcrFilterDialog from .amcr_dialog import AmcrFilterDialog
@@ -120,7 +120,7 @@ class AmcrViewer:
self.tool_button = QToolButton() self.tool_button = QToolButton()
self.tool_button.setMenu(self.plugin_menu) self.tool_button.setMenu(self.plugin_menu)
self.tool_button.setDefaultAction(self.action_download_akce) self.tool_button.setDefaultAction(self.action_download_akce)
self.tool_button.setPopupMode(QToolButton.MenuButtonPopup) self.tool_button.setPopupMode(QToolButton.ToolButtonPopupMode.MenuButtonPopup)
# Add the widget directly to the toolbar and store the reference for cleanup # Add the widget directly to the toolbar and store the reference for cleanup
self.toolbar_action = self.iface.addToolBarWidget(self.tool_button) self.toolbar_action = self.iface.addToolBarWidget(self.tool_button)
@@ -161,7 +161,7 @@ class AmcrViewer:
result = dlg.exec() result = dlg.exec()
# If user confirmed the dialog (OK button), gather filters and load data # If user confirmed the dialog (OK button), gather filters and load data
if result == 1: if result == QDialog.DialogCode.Accepted:
filters = dlg.get_filters() filters = dlg.get_filters()
bbox = dlg.get_bbox() bbox = dlg.get_bbox()
komponenty = dlg.get_komponenty() komponenty = dlg.get_komponenty()
+3 -2
View File
@@ -5,9 +5,10 @@
[general] [general]
name=AMČR Viewer name=AMČR Viewer
qgisMinimumVersion=3.4 qgisMinimumVersion=3.4.0
qgisMaximumVersion=4.9.9
description=Viewing and downloading the AMČR data. description=Viewing and downloading the AMČR data.
version=1.2.0 version=1.3.0
author=David Spáčil author=David Spáčil
email=spacil@arub.cz email=spacil@arub.cz
+1 -1
View File
@@ -6,7 +6,7 @@
# #
# WARNING! All changes made in this file will be lost! # WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore from qgis.PyQt import QtCore
qt_resource_data = b"\ qt_resource_data = b"\
\x00\x00\x04\x0a\ \x00\x00\x04\x0a\