mirror of
https://github.com/ARUP-CAS/aiscr-qgis-amcr-viewer.git
synced 2026-06-17 11:22:53 +02:00
Fixed compatibility issues with QGIS 4 (#24)
* used PyQt6 compatible objects * metadata update
This commit is contained in:
@@ -210,3 +210,4 @@ __marimo__/
|
||||
|
||||
README_files/
|
||||
README.html
|
||||
amcr_viewer.zip
|
||||
|
||||
+60
-62
@@ -13,6 +13,7 @@ from .amcr_codelists import (OBDOBI, TYP_AKCE, KRAJE, AREAL, ORGANIZACE,
|
||||
class FilterableSelectionDialog(QDialog):
|
||||
"""
|
||||
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):
|
||||
super().__init__(parent)
|
||||
@@ -37,7 +38,9 @@ class FilterableSelectionDialog(QDialog):
|
||||
layout.addWidget(self.list_widget)
|
||||
|
||||
# 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.rejected.connect(self.reject)
|
||||
layout.addWidget(buttons)
|
||||
@@ -52,16 +55,16 @@ class FilterableSelectionDialog(QDialog):
|
||||
item = QListWidgetItem(name)
|
||||
|
||||
# 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)
|
||||
item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
|
||||
item.setFlags(item.flags() | Qt.ItemFlag.ItemIsUserCheckable)
|
||||
|
||||
# Restore previous selection state
|
||||
if code in self.preselected:
|
||||
item.setCheckState(Qt.Checked)
|
||||
item.setCheckState(Qt.CheckState.Checked)
|
||||
else:
|
||||
item.setCheckState(Qt.Unchecked)
|
||||
item.setCheckState(Qt.CheckState.Unchecked)
|
||||
|
||||
self.list_widget.addItem(item)
|
||||
|
||||
@@ -70,10 +73,7 @@ class FilterableSelectionDialog(QDialog):
|
||||
search_text = text.lower()
|
||||
for i in range(self.list_widget.count()):
|
||||
item = self.list_widget.item(i)
|
||||
if search_text not in item.text().lower():
|
||||
item.setHidden(True)
|
||||
else:
|
||||
item.setHidden(False)
|
||||
item.setHidden(search_text not in item.text().lower())
|
||||
|
||||
def get_selected_codes(self):
|
||||
"""Returns the hidden codes and display labels of all checked items."""
|
||||
@@ -81,8 +81,8 @@ class FilterableSelectionDialog(QDialog):
|
||||
labels = []
|
||||
for i in range(self.list_widget.count()):
|
||||
item = self.list_widget.item(i)
|
||||
if item.checkState() == Qt.Checked:
|
||||
codes.append(item.data(Qt.UserRole))
|
||||
if item.checkState() == Qt.CheckState.Checked:
|
||||
codes.append(item.data(Qt.ItemDataRole.UserRole))
|
||||
labels.append(item.text())
|
||||
return codes, labels
|
||||
|
||||
@@ -93,7 +93,7 @@ class AmcrFilterDialog(QDialog):
|
||||
The main filtering UI where users set criteria before downloading data.
|
||||
"""
|
||||
def __init__(self, typ_dat, parent=None):
|
||||
super(AmcrFilterDialog, self).__init__(parent)
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle("Filtr AMČR")
|
||||
self.resize(500, 750)
|
||||
|
||||
@@ -189,7 +189,9 @@ class AmcrFilterDialog(QDialog):
|
||||
layout.addStretch(1)
|
||||
|
||||
# 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.rejected.connect(self.reject)
|
||||
layout.addWidget(buttons)
|
||||
@@ -197,60 +199,56 @@ class AmcrFilterDialog(QDialog):
|
||||
self.setLayout(layout)
|
||||
|
||||
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
|
||||
text field showing selected items, and a button to open the selection dialog.
|
||||
"""
|
||||
row_widget = QGroupBox(label_text)
|
||||
# row_widget.setFlat(True)
|
||||
|
||||
row_layout = QHBoxLayout()
|
||||
row_layout.setContentsMargins(5, 5, 5, 5)
|
||||
|
||||
# Read-only field displaying the names of selected items
|
||||
display_field = QLineEdit()
|
||||
display_field.setReadOnly(True)
|
||||
display_field.setPlaceholderText("Nic nevybráno (vše)")
|
||||
display_field.setStyleSheet("background-color: #f0f0f0; color: #333;")
|
||||
|
||||
btn = QPushButton("Vybrat...")
|
||||
btn.setFixedWidth(80)
|
||||
|
||||
# Nested function that handles opening the dialog and saving results
|
||||
def open_dialog():
|
||||
dlg = FilterableSelectionDialog(label_text, data_source, self.selection_cache[cache_key], self)
|
||||
if dlg.exec() == QDialog.Accepted:
|
||||
codes, labels = dlg.get_selected_codes()
|
||||
|
||||
# Update local cache with selected IDs
|
||||
self.selection_cache[cache_key] = codes
|
||||
|
||||
# Update the UI text field with selected names
|
||||
if labels:
|
||||
display_field.setText(", ".join(labels))
|
||||
else:
|
||||
display_field.clear()
|
||||
|
||||
# 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']
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
row_widget = QGroupBox(label_text)
|
||||
row_layout = QHBoxLayout()
|
||||
row_layout.setContentsMargins(5, 5, 5, 5)
|
||||
|
||||
# Read-only field displaying the names of selected items
|
||||
display_field = QLineEdit()
|
||||
display_field.setReadOnly(True)
|
||||
display_field.setPlaceholderText("Nic nevybráno (vše)")
|
||||
display_field.setStyleSheet("background-color: #f0f0f0; color: #333;")
|
||||
|
||||
btn = QPushButton("Vybrat...")
|
||||
btn.setFixedWidth(80)
|
||||
|
||||
# Nested function that handles opening the dialog and saving results
|
||||
def open_dialog():
|
||||
dlg = FilterableSelectionDialog(label_text, data_source, self.selection_cache[cache_key], self)
|
||||
if dlg.exec() == QDialog.DialogCode.Accepted: # PyQt6: DialogCode
|
||||
codes, labels = dlg.get_selected_codes()
|
||||
# Update local cache with selected IDs
|
||||
self.selection_cache[cache_key] = codes
|
||||
# Update the UI text field with selected names
|
||||
if labels:
|
||||
display_field.setText(", ".join(labels))
|
||||
else:
|
||||
display_field.clear()
|
||||
|
||||
# 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_layout.addWidget(btn)
|
||||
|
||||
# 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
|
||||
row_widget.setLayout(row_layout)
|
||||
return row_widget
|
||||
|
||||
def action_update_vedouci(self):
|
||||
# Change cursor to loading state to indicate background task
|
||||
QApplication.setOverrideCursor(Qt.WaitCursor)
|
||||
QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor)
|
||||
try:
|
||||
success, msg = download_vedouci()
|
||||
if success:
|
||||
|
||||
+43
-42
@@ -2,10 +2,11 @@
|
||||
from qgis.gui import QgsMapToolIdentifyFeature
|
||||
from qgis.core import (QgsProject, QgsVectorLayer, QgsFeature, QgsGeometry,
|
||||
QgsField, QgsCoordinateReferenceSystem, QgsCoordinateTransform,
|
||||
QgsWkbTypes, QgsRelation, QgsEditorWidgetSetup)
|
||||
QgsWkbTypes, QgsRelation, QgsEditorWidgetSetup, Qgis)
|
||||
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.QtGui import QCursor
|
||||
import requests
|
||||
import json
|
||||
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"
|
||||
|
||||
iface.messageBar().pushMessage("AMCR", "Hledám záznamy...", level=1)
|
||||
QApplication.setOverrideCursor(Qt.WaitCursor)
|
||||
iface.messageBar().pushMessage("AMCR", "Hledám záznamy...", level=Qgis.MessageLevel.Info)
|
||||
QApplication.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor))
|
||||
|
||||
try:
|
||||
# ==========================================
|
||||
@@ -128,7 +129,7 @@ def load_amcr_data(canvas, bb, filters=None, typ_dat="akce", komponenty="false")
|
||||
if len(docs) >= num_found:
|
||||
break
|
||||
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
|
||||
|
||||
current_page += 1
|
||||
@@ -139,7 +140,7 @@ def load_amcr_data(canvas, bb, filters=None, typ_dat="akce", komponenty="false")
|
||||
break
|
||||
|
||||
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
|
||||
|
||||
# ==========================================
|
||||
@@ -268,7 +269,7 @@ def load_amcr_data(canvas, bb, filters=None, typ_dat="akce", komponenty="false")
|
||||
feats_k.append(feat)
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -280,7 +281,7 @@ def load_amcr_data(canvas, bb, filters=None, typ_dat="akce", komponenty="false")
|
||||
docs_pian = []
|
||||
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"]
|
||||
|
||||
@@ -318,43 +319,43 @@ def load_amcr_data(canvas, bb, filters=None, typ_dat="akce", komponenty="false")
|
||||
|
||||
# Define attribute table structure
|
||||
cols = [
|
||||
QgsField("PIAN", QVariant.String),
|
||||
QgsField("Přesnost", QVariant.String),
|
||||
QgsField("PIAN – typ", QVariant.String),
|
||||
QgsField("Dokumentační jednotka", QVariant.String),
|
||||
QgsField("Typ dokumentační jednotky", QVariant.String),
|
||||
QgsField("Definiční bod(y) (WGS-84)", QVariant.String),
|
||||
QgsField(archeologicky_zaznam, QVariant.String),
|
||||
QgsField("Odkaz do Digitálního archivu AMČR", QVariant.String),
|
||||
QgsField("Okres", QVariant.String),
|
||||
QgsField("Katastr", QVariant.String),
|
||||
QgsField("Další katastry", QVariant.String)
|
||||
QgsField("PIAN", QMetaType.Type.QString),
|
||||
QgsField("Přesnost", QMetaType.Type.QString),
|
||||
QgsField("PIAN – typ", QMetaType.Type.QString),
|
||||
QgsField("Dokumentační jednotka", QMetaType.Type.QString),
|
||||
QgsField("Typ dokumentační jednotky", QMetaType.Type.QString),
|
||||
QgsField("Definiční bod(y) (WGS-84)", QMetaType.Type.QString),
|
||||
QgsField(archeologicky_zaznam, QMetaType.Type.QString),
|
||||
QgsField("Odkaz do Digitálního archivu AMČR", QMetaType.Type.QString),
|
||||
QgsField("Okres", QMetaType.Type.QString),
|
||||
QgsField("Katastr", QMetaType.Type.QString),
|
||||
QgsField("Další katastry", QMetaType.Type.QString)
|
||||
]
|
||||
|
||||
# Extend table based on data type
|
||||
if typ_dat == "akce":
|
||||
cols += [
|
||||
QgsField("Akce – lokalizace", QVariant.String),
|
||||
QgsField("Vedoucí akce", QVariant.String),
|
||||
QgsField("Organizace", QVariant.String),
|
||||
QgsField("Specifikace data", QVariant.String),
|
||||
QgsField("Datum zahájeni", QVariant.String),
|
||||
QgsField("Datum ukončení", QVariant.String),
|
||||
QgsField("Hlavní typ", QVariant.String),
|
||||
QgsField("Vedlejší typ", QVariant.String),
|
||||
QgsField("Zjištění", QVariant.String),
|
||||
QgsField("Akce – nahrazuje NZ", QVariant.String),
|
||||
QgsField("Akce – lokalizace", QMetaType.Type.QString),
|
||||
QgsField("Vedoucí akce", QMetaType.Type.QString),
|
||||
QgsField("Organizace", QMetaType.Type.QString),
|
||||
QgsField("Specifikace data", QMetaType.Type.QString),
|
||||
QgsField("Datum zahájeni", QMetaType.Type.QString),
|
||||
QgsField("Datum ukončení", QMetaType.Type.QString),
|
||||
QgsField("Hlavní typ", QMetaType.Type.QString),
|
||||
QgsField("Vedlejší typ", QMetaType.Type.QString),
|
||||
QgsField("Zjištění", QMetaType.Type.QString),
|
||||
QgsField("Akce – nahrazuje NZ", QMetaType.Type.QString),
|
||||
]
|
||||
elif typ_dat == "lokalita":
|
||||
cols += [
|
||||
QgsField("nazev_lokality", QVariant.String),
|
||||
QgsField("popis_lokality", QVariant.String),
|
||||
QgsField("typ_lokality", QVariant.String),
|
||||
QgsField("druh_lokality", QVariant.String),
|
||||
QgsField("zachovalost", QVariant.String)
|
||||
QgsField("nazev_lokality", QMetaType.Type.QString),
|
||||
QgsField("popis_lokality", QMetaType.Type.QString),
|
||||
QgsField("typ_lokality", QMetaType.Type.QString),
|
||||
QgsField("druh_lokality", QMetaType.Type.QString),
|
||||
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
|
||||
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")
|
||||
pr = vl_komponenty.dataProvider()
|
||||
komponenty_cols = [
|
||||
QgsField("komponenta", QVariant.String),
|
||||
QgsField("dj_id", QVariant.String),
|
||||
QgsField("komponenta_areal", QVariant.String),
|
||||
QgsField("komponenta_obdobi", QVariant.String)
|
||||
QgsField("komponenta", QMetaType.Type.QString),
|
||||
QgsField("dj_id", QMetaType.Type.QString),
|
||||
QgsField("komponenta_areal", QMetaType.Type.QString),
|
||||
QgsField("komponenta_obdobi", QMetaType.Type.QString)
|
||||
]
|
||||
pr.addAttributes(komponenty_cols)
|
||||
vl_komponenty.updateFields()
|
||||
@@ -493,7 +494,7 @@ def load_amcr_data(canvas, bb, filters=None, typ_dat="akce", komponenty="false")
|
||||
added += len(f)
|
||||
|
||||
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 ---
|
||||
# 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í!")
|
||||
|
||||
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:
|
||||
iface.messageBar().pushMessage("Chyba", str(e), level=2)
|
||||
iface.messageBar().pushMessage("Chyba", str(e), level=Qgis.MessageLevel.Critical)
|
||||
finally:
|
||||
# Always restore cursor, even after failure
|
||||
QApplication.restoreOverrideCursor()
|
||||
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from qgis.PyQt.QtCore import QSettings, QTranslator, QCoreApplication
|
||||
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_dialog import AmcrFilterDialog
|
||||
@@ -120,7 +120,7 @@ class AmcrViewer:
|
||||
self.tool_button = QToolButton()
|
||||
self.tool_button.setMenu(self.plugin_menu)
|
||||
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
|
||||
self.toolbar_action = self.iface.addToolBarWidget(self.tool_button)
|
||||
@@ -161,7 +161,7 @@ class AmcrViewer:
|
||||
result = dlg.exec()
|
||||
|
||||
# If user confirmed the dialog (OK button), gather filters and load data
|
||||
if result == 1:
|
||||
if result == QDialog.DialogCode.Accepted:
|
||||
filters = dlg.get_filters()
|
||||
bbox = dlg.get_bbox()
|
||||
komponenty = dlg.get_komponenty()
|
||||
|
||||
@@ -5,9 +5,10 @@
|
||||
|
||||
[general]
|
||||
name=AMČR Viewer
|
||||
qgisMinimumVersion=3.4
|
||||
qgisMinimumVersion=3.4.0
|
||||
qgisMaximumVersion=4.9.9
|
||||
description=Viewing and downloading the AMČR data.
|
||||
version=1.2.0
|
||||
version=1.3.0
|
||||
author=David Spáčil
|
||||
email=spacil@arub.cz
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
from PyQt5 import QtCore
|
||||
from qgis.PyQt import QtCore
|
||||
|
||||
qt_resource_data = b"\
|
||||
\x00\x00\x04\x0a\
|
||||
|
||||
Reference in New Issue
Block a user