From 6c2579a1e1f16cd0d4cbe4283af108b9e82876e2 Mon Sep 17 00:00:00 2001
From: signedav
Date: Fri, 17 Nov 2023 13:36:11 +0100
Subject: [PATCH 01/30] move Checkbox Delegate to utils
---
.../import_data_configuration_page.py | 28 ++--------------
QgisModelBaker/utils/gui_utils.py | 32 ++++++++++++++++++-
2 files changed, 34 insertions(+), 26 deletions(-)
diff --git a/QgisModelBaker/gui/workflow_wizard/import_data_configuration_page.py b/QgisModelBaker/gui/workflow_wizard/import_data_configuration_page.py
index 9a4b2f7a1..00be44c6a 100644
--- a/QgisModelBaker/gui/workflow_wizard/import_data_configuration_page.py
+++ b/QgisModelBaker/gui/workflow_wizard/import_data_configuration_page.py
@@ -21,7 +21,7 @@
from PyQt5.QtWidgets import QApplication
from qgis.core import QgsApplication
-from qgis.PyQt.QtCore import QEvent, Qt
+from qgis.PyQt.QtCore import Qt
from qgis.PyQt.QtGui import QIcon
from qgis.PyQt.QtWidgets import (
QComboBox,
@@ -29,7 +29,6 @@
QHeaderView,
QStyle,
QStyledItemDelegate,
- QStyleOptionButton,
QStyleOptionComboBox,
QWizardPage,
)
@@ -42,7 +41,7 @@
MetaConfigCompleterDelegate,
)
from QgisModelBaker.utils.globals import CATALOGUE_DATASETNAME, DEFAULT_DATASETNAME
-from QgisModelBaker.utils.gui_utils import LogColor
+from QgisModelBaker.utils.gui_utils import CheckDelegate, LogColor
PAGE_UI = gui_utils.get_ui_class("workflow_wizard/import_data_configuration.ui")
@@ -90,27 +89,6 @@ def paint(self, painter, option, index):
QApplication.style().drawControl(QStyle.CE_ComboBoxLabel, opt, painter)
-class CatalogueCheckDelegate(QStyledItemDelegate):
- def __init__(self, parent):
- super().__init__(parent)
-
- def editorEvent(self, event, model, option, index):
- if event.type() == QEvent.MouseButtonRelease:
- value = index.data(int(gui_utils.SourceModel.Roles.IS_CATALOGUE)) or False
- model.setData(
- index, not value, int(gui_utils.SourceModel.Roles.IS_CATALOGUE)
- )
- return True
- return super().editorEvent(event, model, option, index)
-
- def paint(self, painter, option, index):
- opt = QStyleOptionButton()
- opt.rect = option.rect
- value = index.data(int(gui_utils.SourceModel.Roles.IS_CATALOGUE)) or False
- opt.state |= QStyle.State_On if value else QStyle.State_Off
- QApplication.style().drawControl(QStyle.CE_CheckBox, opt, painter)
-
-
class ImportDataConfigurationPage(QWizardPage, PAGE_UI):
def __init__(self, parent, title):
QWizardPage.__init__(self, parent)
@@ -269,7 +247,7 @@ def _set_basket_defaults(self):
self.file_table_view.setItemDelegateForColumn(
gui_utils.SourceModel.Columns.IS_CATALOGUE,
- CatalogueCheckDelegate(self),
+ CheckDelegate(self, gui_utils.SourceModel.Roles.IS_CATALOGUE),
)
self.file_table_view.setItemDelegateForColumn(
gui_utils.SourceModel.Columns.DATASET,
diff --git a/QgisModelBaker/utils/gui_utils.py b/QgisModelBaker/utils/gui_utils.py
index 6ac5b1d04..70b57476a 100644
--- a/QgisModelBaker/utils/gui_utils.py
+++ b/QgisModelBaker/utils/gui_utils.py
@@ -6,7 +6,9 @@
import xml.etree.ElementTree as CET
from enum import Enum, IntEnum
+from PyQt5.QtWidgets import QApplication
from qgis.PyQt.QtCore import (
+ QEvent,
QModelIndex,
QSortFilterProxyModel,
QStringListModel,
@@ -14,7 +16,14 @@
pyqtSignal,
)
from qgis.PyQt.QtGui import QIcon, QStandardItem, QStandardItemModel
-from qgis.PyQt.QtWidgets import QCheckBox, QLineEdit, QListView
+from qgis.PyQt.QtWidgets import (
+ QCheckBox,
+ QLineEdit,
+ QListView,
+ QStyle,
+ QStyledItemDelegate,
+ QStyleOptionButton,
+)
from qgis.PyQt.uic import loadUiType
from QgisModelBaker.libs.modelbaker.iliwrapper.ilicache import IliCache
@@ -174,6 +183,7 @@ class PageIds:
ExportDataConfiguration = 10
ExportDataExecution = 11
ProjectCreation = 12
+ TIDConfiguration = 13
class ToppingWizardPageIds:
@@ -1107,3 +1117,23 @@ def model_topics(self, schema_identificator):
for basket in self.schema_baskets[schema_identificator]:
model_topics.add(basket.get("topic", ""))
return list(model_topics)
+
+
+class CheckDelegate(QStyledItemDelegate):
+ def __init__(self, parent, role):
+ super().__init__(parent)
+ self.role = role
+
+ def editorEvent(self, event, model, option, index):
+ if event.type() == QEvent.MouseButtonRelease:
+ value = index.data(int(self.role)) or False
+ model.setData(index, not value, int(self.role))
+ return True
+ return super().editorEvent(event, model, option, index)
+
+ def paint(self, painter, option, index):
+ opt = QStyleOptionButton()
+ opt.rect = option.rect
+ value = index.data(int(self.role)) or False
+ opt.state |= QStyle.State_On if value else QStyle.State_Off
+ QApplication.style().drawControl(QStyle.CE_CheckBox, opt, painter)
From eadee7defba8ad42f34c2db56bbc1d3e8def5041 Mon Sep 17 00:00:00 2001
From: signedav
Date: Fri, 17 Nov 2023 13:50:31 +0100
Subject: [PATCH 02/30] Put checkbox in the middle
---
QgisModelBaker/utils/gui_utils.py | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/QgisModelBaker/utils/gui_utils.py b/QgisModelBaker/utils/gui_utils.py
index 70b57476a..a0f3399a4 100644
--- a/QgisModelBaker/utils/gui_utils.py
+++ b/QgisModelBaker/utils/gui_utils.py
@@ -10,6 +10,7 @@
from qgis.PyQt.QtCore import (
QEvent,
QModelIndex,
+ QRect,
QSortFilterProxyModel,
QStringListModel,
Qt,
@@ -1134,6 +1135,19 @@ def editorEvent(self, event, model, option, index):
def paint(self, painter, option, index):
opt = QStyleOptionButton()
opt.rect = option.rect
+ center_x = opt.rect.x() + opt.rect.width() / 2
+ center_y = opt.rect.y() + opt.rect.height() / 2
+
+ checkbox_width = QApplication.style().pixelMetric(QStyle.PM_IndicatorWidth)
+ checkbox_height = QApplication.style().pixelMetric(QStyle.PM_IndicatorHeight)
+ checkbox_rect = QRect(
+ int(center_x - checkbox_width / 2),
+ int(center_y - checkbox_height / 2),
+ checkbox_width,
+ checkbox_height,
+ )
+ opt.rect = checkbox_rect
+
value = index.data(int(self.role)) or False
opt.state |= QStyle.State_On if value else QStyle.State_Off
QApplication.style().drawControl(QStyle.CE_CheckBox, opt, painter)
From 383fdc4910caae21ac0e0883ab8dbeac54023b6e Mon Sep 17 00:00:00 2001
From: signedav
Date: Fri, 17 Nov 2023 14:18:26 +0100
Subject: [PATCH 03/30] Weird order of the setData parameters - marked with a
comment. One could fix this in future, since it's a little fault prone
---
QgisModelBaker/gui/topping_wizard/ili2dbsettings_page.py | 1 +
QgisModelBaker/gui/topping_wizard/layers_page.py | 1 +
QgisModelBaker/utils/gui_utils.py | 7 +++++--
3 files changed, 7 insertions(+), 2 deletions(-)
diff --git a/QgisModelBaker/gui/topping_wizard/ili2dbsettings_page.py b/QgisModelBaker/gui/topping_wizard/ili2dbsettings_page.py
index 2e2656e82..b4c485aa5 100644
--- a/QgisModelBaker/gui/topping_wizard/ili2dbsettings_page.py
+++ b/QgisModelBaker/gui/topping_wizard/ili2dbsettings_page.py
@@ -86,6 +86,7 @@ def data(self, index, role):
return self.parameters.get(key, "")
return None
+ # this is unusual that it's not first data and then role (could be changed)
def setData(self, index, role, data):
if role == int(Qt.EditRole):
if index.column() == ParametersModel.Columns.NAME:
diff --git a/QgisModelBaker/gui/topping_wizard/layers_page.py b/QgisModelBaker/gui/topping_wizard/layers_page.py
index ee075182a..4b57f51a5 100644
--- a/QgisModelBaker/gui/topping_wizard/layers_page.py
+++ b/QgisModelBaker/gui/topping_wizard/layers_page.py
@@ -207,6 +207,7 @@ def data(self, index, role):
return QgsLayerTreeModel.data(self, index, role)
+ # this is unusual that it's not first data and then role (could be changed)
def setData(self, index, role, data):
if role == Qt.CheckStateRole:
node = self.index2node(index)
diff --git a/QgisModelBaker/utils/gui_utils.py b/QgisModelBaker/utils/gui_utils.py
index a0f3399a4..ad818edcc 100644
--- a/QgisModelBaker/utils/gui_utils.py
+++ b/QgisModelBaker/utils/gui_utils.py
@@ -693,6 +693,7 @@ def data(self, index, role):
]
return SourceModel.data(self, index, role)
+ # this is unusual that it's not first data and then role (could be changed)
def setData(self, index, role, data):
if role == Qt.CheckStateRole:
self.beginResetModel()
@@ -838,11 +839,12 @@ def data(self, index, role):
else:
return QStringListModel.data(self, index, role)
+ # this is unusual that it's not first data and then role (could be changed)
def setData(self, index, role, data):
if role == Qt.CheckStateRole:
self._checked_entries[self.data(index, Qt.DisplayRole)] = data
else:
- QStringListModel.setData(self, index, role, data)
+ QStringListModel.setData(self, index, data, role)
def check(self, index):
if self.data(index, Qt.CheckStateRole) == Qt.Checked:
@@ -909,6 +911,7 @@ def data(self, index, role):
else:
return CheckEntriesModel.data(self, index, role)
+ # this is unusual that it's not first data and then role (could be changed)
def setData(self, index, role, data):
if role == int(SchemaModelsModel.Roles.PARENT_MODELS):
self._parent_models[self.data(index, Qt.DisplayRole)] = data
@@ -1126,7 +1129,7 @@ def __init__(self, parent, role):
self.role = role
def editorEvent(self, event, model, option, index):
- if event.type() == QEvent.MouseButtonRelease:
+ if event.type() == QEvent.MouseButtonPress:
value = index.data(int(self.role)) or False
model.setData(index, not value, int(self.role))
return True
From 8e2847fad89276bbf024c9af31fb852ad2235710 Mon Sep 17 00:00:00 2001
From: signedav
Date: Fri, 17 Nov 2023 14:19:59 +0100
Subject: [PATCH 04/30] Integration of TID configuration page WIP
---
.../gui/panel/set_sequence_panel.py | 37 ++++
QgisModelBaker/gui/panel/tid_config_panel.py | 167 ++++++++++++++++++
.../workflow_wizard/project_creation_page.py | 12 +-
.../workflow_wizard/tid_configuration_page.py | 58 ++++++
.../gui/workflow_wizard/workflow_wizard.py | 12 ++
QgisModelBaker/ui/set_sequence_panel.ui | 31 ++++
QgisModelBaker/ui/tid_config_panel.ui | 28 +++
.../ui/workflow_wizard/tid_configuration.ui | 92 ++++++++++
8 files changed, 436 insertions(+), 1 deletion(-)
create mode 100644 QgisModelBaker/gui/panel/set_sequence_panel.py
create mode 100644 QgisModelBaker/gui/panel/tid_config_panel.py
create mode 100644 QgisModelBaker/gui/workflow_wizard/tid_configuration_page.py
create mode 100644 QgisModelBaker/ui/set_sequence_panel.ui
create mode 100644 QgisModelBaker/ui/tid_config_panel.ui
create mode 100644 QgisModelBaker/ui/workflow_wizard/tid_configuration.ui
diff --git a/QgisModelBaker/gui/panel/set_sequence_panel.py b/QgisModelBaker/gui/panel/set_sequence_panel.py
new file mode 100644
index 000000000..a830b332c
--- /dev/null
+++ b/QgisModelBaker/gui/panel/set_sequence_panel.py
@@ -0,0 +1,37 @@
+"""
+/***************************************************************************
+ -------------------
+ begin : 17.11.2023
+ git sha : :%H$
+ copyright : (C) 2023 by Dave Signer
+ email : david at opengis ch
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ ***************************************************************************/
+"""
+
+
+from qgis.PyQt.QtWidgets import QWidget
+
+import QgisModelBaker.utils.gui_utils as gui_utils
+
+WIDGET_UI = gui_utils.get_ui_class("set_sequence_panel.ui")
+
+
+class SetSequencePanel(QWidget, WIDGET_UI):
+ def __init__(self, parent=None):
+ QWidget.__init__(self, parent)
+ self.setupUi(self)
+
+ def _load_sequence(self):
+ pass
+
+ def _save_sequence(self):
+ pass
diff --git a/QgisModelBaker/gui/panel/tid_config_panel.py b/QgisModelBaker/gui/panel/tid_config_panel.py
new file mode 100644
index 000000000..3eac4428b
--- /dev/null
+++ b/QgisModelBaker/gui/panel/tid_config_panel.py
@@ -0,0 +1,167 @@
+"""
+/***************************************************************************
+ -------------------
+ begin : 17.11.2023
+ git sha : :%H$
+ copyright : (C) 2023 by Dave Signer
+ email : david at opengis ch
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ ***************************************************************************/
+"""
+
+
+from enum import IntEnum
+
+from qgis.core import QgsProject
+from qgis.PyQt.QtCore import QAbstractTableModel, QModelIndex, Qt
+from qgis.PyQt.QtWidgets import QHeaderView, QWidget
+
+import QgisModelBaker.utils.gui_utils as gui_utils
+from QgisModelBaker.libs.modelbaker.utils.qgis_utils import QgisProjectUtils
+from QgisModelBaker.utils.gui_utils import CheckDelegate
+
+WIDGET_UI = gui_utils.get_ui_class("tid_config_panel.ui")
+
+
+class TIDModel(QAbstractTableModel):
+ """
+ ItemModel providing all TIDs and default values of the passed project
+
+ oid_settings is a dictionary like:
+ {
+ "Strasse":
+ {
+ "oid_domain": "STANDARDOID",
+ "interlis_topic" : "OIDMadness_V1",
+ "default_value_expression": "uuid()",
+ "in_form": True
+ }
+ [...]
+ }
+ """
+
+ class Columns(IntEnum):
+ NAME = 0
+ OID_DOMAIN = 1
+ DEFAULT_VALUE = 2
+ IN_FORM = 3
+
+ def __init__(self):
+ super().__init__()
+ self.oid_settings = {}
+
+ def columnCount(self, parent):
+ return len(TIDModel.Columns)
+
+ def rowCount(self, parent):
+ return len(self.oid_settings.keys())
+
+ def flags(self, index):
+ if index.column() == TIDModel.Columns.IN_FORM:
+ return Qt.ItemIsEnabled
+ if index.column() == TIDModel.Columns.DEFAULT_VALUE:
+ return Qt.ItemIsEditable | Qt.ItemIsEnabled
+ return Qt.ItemIsSelectable | Qt.ItemIsEnabled
+
+ def index(self, row: int, column: int, parent: QModelIndex = ...) -> QModelIndex:
+ """
+ default override
+ """
+ return super().createIndex(row, column, parent)
+
+ def parent(self, index):
+ """
+ default override
+ """
+ return index
+
+ def headerData(self, section, orientation, role):
+ if orientation == Qt.Horizontal and role == Qt.DisplayRole:
+ if section == TIDModel.Columns.NAME:
+ return self.tr("Layer")
+ if section == TIDModel.Columns.OID_DOMAIN:
+ return self.tr("TID (OID Type)")
+ if section == TIDModel.Columns.DEFAULT_VALUE:
+ return self.tr("Default Value Expression")
+ if section == TIDModel.Columns.IN_FORM:
+ return self.tr("Show")
+
+ def data(self, index, role):
+ if role == int(Qt.DisplayRole) or role == int(Qt.EditRole):
+ key = list(self.oid_settings.keys())[index.row()]
+ if index.column() == TIDModel.Columns.NAME:
+ return f"{key} ({self.oid_settings[key]['interlis_topic']})"
+ if index.column() == TIDModel.Columns.OID_DOMAIN:
+ return self.oid_settings[key]["oid_domain"]
+ if index.column() == TIDModel.Columns.DEFAULT_VALUE:
+ return self.oid_settings[key]["default_value_expression"]
+ if index.column() == TIDModel.Columns.IN_FORM:
+ return self.oid_settings[key]["in_form"]
+ return None
+
+ def setData(self, index, data, role):
+ if role == int(Qt.EditRole):
+ if index.column() == TIDModel.Columns.DEFAULT_VALUE:
+ key = list(self.oid_settings.keys())[index.row()]
+ self.oid_settings[key]["default_value_expression"] = data
+ self.dataChanged.emit(index, index)
+ if index.column() == TIDModel.Columns.IN_FORM:
+ key = list(self.oid_settings.keys())[index.row()]
+ self.oid_settings[key]["in_form"] = data
+ self.dataChanged.emit(index, index)
+
+ def load_tid_config(self, qgis_project=None):
+ self.beginResetModel()
+ self.oid_settings = QgisProjectUtils(qgis_project).get_oid_settings()
+ self.endResetModel()
+
+ def save_tid_config(self, qgis_project=None):
+ if qgis_project:
+ QgisProjectUtils(qgis_project.set_oid_settings(self.oid_settings))
+
+
+class TIDConfigPanel(QWidget, WIDGET_UI):
+ def __init__(self, qgis_project, parent=None):
+ QWidget.__init__(self, parent)
+ self.setupUi(self)
+ self.parent = parent
+ self.qgis_project = (
+ QgsProject.instance()
+ ) # test after that it should be passed with qgis_project
+ self.tid_model = TIDModel()
+ self.tid_config_view.setModel(self.tid_model)
+
+ self.tid_config_view.horizontalHeader().setSectionResizeMode(
+ TIDModel.Columns.NAME, QHeaderView.Stretch
+ )
+ self.tid_config_view.horizontalHeader().setSectionResizeMode(
+ TIDModel.Columns.OID_DOMAIN, QHeaderView.ResizeToContents
+ )
+ self.tid_config_view.horizontalHeader().setSectionResizeMode(
+ TIDModel.Columns.DEFAULT_VALUE, QHeaderView.ResizeToContents
+ )
+ self.tid_config_view.horizontalHeader().setSectionResizeMode(
+ TIDModel.Columns.IN_FORM, QHeaderView.ResizeToContents
+ )
+
+ # load data
+ self._load_tid_config()
+
+ self.tid_config_view.setItemDelegateForColumn(
+ TIDModel.Columns.IN_FORM,
+ CheckDelegate(self, Qt.EditRole),
+ )
+
+ def _load_tid_config(self):
+ self.tid_model.load_tid_config(self.qgis_project)
+
+ def _save_tid_config(self):
+ self.tid_model.save_tid_config(self.qgis_project)
diff --git a/QgisModelBaker/gui/workflow_wizard/project_creation_page.py b/QgisModelBaker/gui/workflow_wizard/project_creation_page.py
index 2e94ae094..abd006991 100644
--- a/QgisModelBaker/gui/workflow_wizard/project_creation_page.py
+++ b/QgisModelBaker/gui/workflow_wizard/project_creation_page.py
@@ -45,7 +45,6 @@ def __init__(self, parent, title):
self.workflow_wizard = parent
self.setupUi(self)
- self.setFinalPage(True)
self.setTitle(title)
self.setStyleSheet(gui_utils.DEFAULT_STYLE)
@@ -63,6 +62,16 @@ def __init__(self, parent, title):
self.create_project_button.clicked.connect(self._create_project)
+ self.is_complete = False
+
+ def isComplete(self):
+ return self.is_complete
+
+ def setComplete(self, complete):
+ self.is_complete = complete
+ self.create_project_button.setDisabled(complete)
+ self.completeChanged.emit()
+
def set_configuration(self, configuration):
self.configuration = configuration
@@ -349,6 +358,7 @@ def _create_project(self):
self.progress_bar.setValue(100)
self.setStyleSheet(gui_utils.SUCCESS_STYLE)
self.workflow_wizard.log_panel.print_info(self.tr("It's served!"))
+ self.setComplete(True)
def ilidata_path_resolver(self, base_path, path):
if "ilidata:" in path or "file:" in path:
diff --git a/QgisModelBaker/gui/workflow_wizard/tid_configuration_page.py b/QgisModelBaker/gui/workflow_wizard/tid_configuration_page.py
new file mode 100644
index 000000000..13210b8de
--- /dev/null
+++ b/QgisModelBaker/gui/workflow_wizard/tid_configuration_page.py
@@ -0,0 +1,58 @@
+"""
+/***************************************************************************
+ -------------------
+ begin : 17.11.2023
+ git sha : :%H$
+ copyright : (C) 2023 by Dave Signer
+ email : david at opengis ch
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ ***************************************************************************/
+"""
+
+
+from qgis.PyQt.QtWidgets import QWizardPage
+
+from QgisModelBaker.gui.panel.set_sequence_panel import SetSequencePanel
+from QgisModelBaker.gui.panel.tid_config_panel import TIDConfigPanel
+from QgisModelBaker.utils import gui_utils
+
+PAGE_UI = gui_utils.get_ui_class("workflow_wizard/tid_configuration.ui")
+
+
+class TIDConfigurationPage(QWizardPage, PAGE_UI):
+ def __init__(self, parent, title):
+ QWizardPage.__init__(self, parent)
+
+ self.workflow_wizard = parent
+
+ self.setupUi(self)
+ self.setFinalPage(True)
+ self.setTitle(title)
+ self.setStyleSheet(gui_utils.DEFAULT_STYLE)
+
+ self.tid_config_panel = TIDConfigPanel(self.workflow_wizard)
+ self.tid_config_layout.addWidget(self.tid_config_panel)
+
+ self.set_sequence_panel = SetSequencePanel(self.workflow_wizard)
+ self.set_sequence_layout.addWidget(self.set_sequence_panel)
+
+ self.set_tid_configuration_button.clicked.connect(self._set_tid_configuration)
+
+ def set_configuration(self, configuration):
+ self.configuration = configuration
+
+ def _set_tid_configuration(self):
+ self.progress_bar.setValue(0)
+ self.progress_bar.setValue(100)
+ self.setStyleSheet(gui_utils.SUCCESS_STYLE)
+ self.workflow_wizard.log_panel.print_info(
+ self.tr("Stored TID Configurations to current project")
+ )
diff --git a/QgisModelBaker/gui/workflow_wizard/workflow_wizard.py b/QgisModelBaker/gui/workflow_wizard/workflow_wizard.py
index 3db32c7d8..4fceefea1 100644
--- a/QgisModelBaker/gui/workflow_wizard/workflow_wizard.py
+++ b/QgisModelBaker/gui/workflow_wizard/workflow_wizard.py
@@ -44,6 +44,9 @@
)
from QgisModelBaker.gui.workflow_wizard.intro_page import IntroPage
from QgisModelBaker.gui.workflow_wizard.project_creation_page import ProjectCreationPage
+from QgisModelBaker.gui.workflow_wizard.tid_configuration_page import (
+ TIDConfigurationPage,
+)
from QgisModelBaker.libs.modelbaker.iliwrapper.ili2dbconfig import (
ExportConfiguration,
ImportDataConfiguration,
@@ -161,6 +164,9 @@ def __init__(self, iface, base_config, parent):
self.project_creation_page = ProjectCreationPage(
self, self._current_page_title(PageIds.ProjectCreation)
)
+ self.tid_configuration_page = TIDConfigurationPage(
+ self, self._current_page_title(PageIds.TIDConfiguration)
+ )
self.generate_database_selection_page = DatabaseSelectionPage(
self,
self._current_page_title(PageIds.GenerateDatabaseSelection),
@@ -189,6 +195,7 @@ def __init__(self, iface, base_config, parent):
self.setPage(PageIds.ImportDataConfiguration, self.data_configuration_page)
self.setPage(PageIds.ImportDataExecution, self.import_data_execution_page)
self.setPage(PageIds.ProjectCreation, self.project_creation_page)
+ self.setPage(PageIds.TIDConfiguration, self.tid_configuration_page)
self.setPage(
PageIds.GenerateDatabaseSelection, self.generate_database_selection_page
)
@@ -316,6 +323,9 @@ def next_id(self):
if self.current_id == PageIds.ExportDataConfiguration:
return PageIds.ExportDataExecution
+ if self.current_id == PageIds.ProjectCreation:
+ return PageIds.TIDConfiguration
+
return self.current_id
def id_changed(self, new_id):
@@ -453,6 +463,8 @@ def _current_page_title(self, id):
return self.tr("Data Export Sessions")
elif id == PageIds.ProjectCreation:
return self.tr("Generate a QGIS Project")
+ elif id == PageIds.TIDConfiguration:
+ return self.tr("Configure OID Generation")
else:
return self.tr("Model Baker - Workflow Wizard")
diff --git a/QgisModelBaker/ui/set_sequence_panel.ui b/QgisModelBaker/ui/set_sequence_panel.ui
new file mode 100644
index 000000000..fa73474f3
--- /dev/null
+++ b/QgisModelBaker/ui/set_sequence_panel.ui
@@ -0,0 +1,31 @@
+
+
+ set_sequence_layout
+
+
+
+ 0
+ 0
+ 674
+ 43
+
+
+
+ Select Files
+
+
+ -
+
+
+ -
+
+
+ T_Id (Sequence) Value
+
+
+
+
+
+
+
+
diff --git a/QgisModelBaker/ui/tid_config_panel.ui b/QgisModelBaker/ui/tid_config_panel.ui
new file mode 100644
index 000000000..8d5775330
--- /dev/null
+++ b/QgisModelBaker/ui/tid_config_panel.ui
@@ -0,0 +1,28 @@
+
+
+ tid_config_layout
+
+
+
+ 0
+ 0
+ 800
+ 600
+
+
+
+ Select Files
+
+
+ -
+
+
+ true
+
+
+
+
+
+
+
+
diff --git a/QgisModelBaker/ui/workflow_wizard/tid_configuration.ui b/QgisModelBaker/ui/workflow_wizard/tid_configuration.ui
new file mode 100644
index 000000000..30af5f5e4
--- /dev/null
+++ b/QgisModelBaker/ui/workflow_wizard/tid_configuration.ui
@@ -0,0 +1,92 @@
+
+
+ tid_configuration
+
+
+
+ 0
+ 0
+ 800
+ 600
+
+
+
+ Select Files
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 75
+ true
+
+
+
+ <html><head/><body><p>Configure the OIDs (<span style=" font-family:'monospace';">t_ili_tid</span>) default values of the layers in the generated project.</p></body></html>
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop
+
+
+ true
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ Set configuration
+
+
+
+ -
+
+
+ 0
+
+
+
+
+
+
+
+
From 3d6b6e0ff8fe28dad704e236a403694db56b4de6 Mon Sep 17 00:00:00 2001
From: signedav
Date: Fri, 17 Nov 2023 16:02:56 +0100
Subject: [PATCH 05/30] here we go. first version of the tid configurator -
needs finetuning
---
.../gui/panel/set_sequence_panel.py | 13 ++++---
QgisModelBaker/gui/panel/tid_config_panel.py | 19 ++++------
.../workflow_wizard/tid_configuration_page.py | 35 +++++++++++++++---
.../gui/workflow_wizard/workflow_wizard.py | 6 ++++
QgisModelBaker/ui/set_sequence_panel.ui | 10 ++++--
.../ui/workflow_wizard/tid_configuration.ui | 36 ++++++++++++++-----
6 files changed, 87 insertions(+), 32 deletions(-)
diff --git a/QgisModelBaker/gui/panel/set_sequence_panel.py b/QgisModelBaker/gui/panel/set_sequence_panel.py
index a830b332c..4948174c6 100644
--- a/QgisModelBaker/gui/panel/set_sequence_panel.py
+++ b/QgisModelBaker/gui/panel/set_sequence_panel.py
@@ -30,8 +30,11 @@ def __init__(self, parent=None):
QWidget.__init__(self, parent)
self.setupUi(self)
- def _load_sequence(self):
- pass
-
- def _save_sequence(self):
- pass
+ def load_sequence(self, db_connector=None):
+ if db_connector:
+ sequence_value = db_connector.get_ili2db_sequence_value()
+ self.sequence_value_edit.setValue(sequence_value)
+
+ def save_sequence(self, db_connector=None):
+ if db_connector:
+ db_connector.set_ili2db_sequence_value(self.sequence_value_edit.value())
diff --git a/QgisModelBaker/gui/panel/tid_config_panel.py b/QgisModelBaker/gui/panel/tid_config_panel.py
index 3eac4428b..8a590a00f 100644
--- a/QgisModelBaker/gui/panel/tid_config_panel.py
+++ b/QgisModelBaker/gui/panel/tid_config_panel.py
@@ -117,6 +117,7 @@ def setData(self, index, data, role):
key = list(self.oid_settings.keys())[index.row()]
self.oid_settings[key]["in_form"] = data
self.dataChanged.emit(index, index)
+ return True
def load_tid_config(self, qgis_project=None):
self.beginResetModel()
@@ -125,17 +126,14 @@ def load_tid_config(self, qgis_project=None):
def save_tid_config(self, qgis_project=None):
if qgis_project:
- QgisProjectUtils(qgis_project.set_oid_settings(self.oid_settings))
+ QgisProjectUtils(qgis_project).set_oid_settings(self.oid_settings)
class TIDConfigPanel(QWidget, WIDGET_UI):
- def __init__(self, qgis_project, parent=None):
+ def __init__(self, parent=None):
QWidget.__init__(self, parent)
self.setupUi(self)
self.parent = parent
- self.qgis_project = (
- QgsProject.instance()
- ) # test after that it should be passed with qgis_project
self.tid_model = TIDModel()
self.tid_config_view.setModel(self.tid_model)
@@ -152,16 +150,13 @@ def __init__(self, qgis_project, parent=None):
TIDModel.Columns.IN_FORM, QHeaderView.ResizeToContents
)
- # load data
- self._load_tid_config()
-
self.tid_config_view.setItemDelegateForColumn(
TIDModel.Columns.IN_FORM,
CheckDelegate(self, Qt.EditRole),
)
- def _load_tid_config(self):
- self.tid_model.load_tid_config(self.qgis_project)
+ def load_tid_config(self, qgis_project=QgsProject.instance()):
+ self.tid_model.load_tid_config(qgis_project)
- def _save_tid_config(self):
- self.tid_model.save_tid_config(self.qgis_project)
+ def save_tid_config(self, qgis_project=QgsProject.instance()):
+ self.tid_model.save_tid_config(qgis_project)
diff --git a/QgisModelBaker/gui/workflow_wizard/tid_configuration_page.py b/QgisModelBaker/gui/workflow_wizard/tid_configuration_page.py
index 13210b8de..9b6493ae9 100644
--- a/QgisModelBaker/gui/workflow_wizard/tid_configuration_page.py
+++ b/QgisModelBaker/gui/workflow_wizard/tid_configuration_page.py
@@ -18,8 +18,10 @@
"""
+from qgis.core import QgsProject
from qgis.PyQt.QtWidgets import QWizardPage
+import QgisModelBaker.libs.modelbaker.utils.db_utils as db_utils
from QgisModelBaker.gui.panel.set_sequence_panel import SetSequencePanel
from QgisModelBaker.gui.panel.tid_config_panel import TIDConfigPanel
from QgisModelBaker.utils import gui_utils
@@ -32,6 +34,7 @@ def __init__(self, parent, title):
QWizardPage.__init__(self, parent)
self.workflow_wizard = parent
+ self.qgis_project = QgsProject.instance()
self.setupUi(self)
self.setFinalPage(True)
@@ -44,15 +47,39 @@ def __init__(self, parent, title):
self.set_sequence_panel = SetSequencePanel(self.workflow_wizard)
self.set_sequence_layout.addWidget(self.set_sequence_panel)
- self.set_tid_configuration_button.clicked.connect(self._set_tid_configuration)
+ self.reset_tid_config_button.clicked.connect(self._reset_tid_configuration)
+ self.set_tid_config_and_sequence_button.clicked.connect(
+ self._set_tid_configuration
+ )
+
+ self.configuration = None
+ self.db_connector = None
def set_configuration(self, configuration):
self.configuration = configuration
+ def setup_dialog(self):
+ self.db_connector = db_utils.get_db_connector(self.configuration)
+ self._reset_tid_configuration()
+
+ def _reset_tid_configuration(self):
+ self.tid_config_panel.load_tid_config(self.qgis_project)
+ self.set_sequence_panel.load_sequence(self.db_connector)
+
def _set_tid_configuration(self):
self.progress_bar.setValue(0)
- self.progress_bar.setValue(100)
- self.setStyleSheet(gui_utils.SUCCESS_STYLE)
+
+ self.tid_config_panel.save_tid_config(self.qgis_project)
self.workflow_wizard.log_panel.print_info(
- self.tr("Stored TID Configurations to current project")
+ self.tr("Stored TID configurations to current project")
)
+
+ self.progress_bar.setValue(50)
+
+ self.set_sequence_panel.save_sequence(self.db_connector)
+ self.workflow_wizard.log_panel.print_info(
+ self.tr("Stored the sequence value to current database")
+ )
+
+ self.progress_bar.setValue(100)
+ self.setStyleSheet(gui_utils.SUCCESS_STYLE)
diff --git a/QgisModelBaker/gui/workflow_wizard/workflow_wizard.py b/QgisModelBaker/gui/workflow_wizard/workflow_wizard.py
index 4fceefea1..a4cf463dc 100644
--- a/QgisModelBaker/gui/workflow_wizard/workflow_wizard.py
+++ b/QgisModelBaker/gui/workflow_wizard/workflow_wizard.py
@@ -379,6 +379,12 @@ def id_changed(self, new_id):
self.import_schema_configuration
)
+ if self.current_id == PageIds.TIDConfiguration:
+ self.tid_configuration_page.set_configuration(
+ self.import_schema_configuration
+ )
+ self.tid_configuration_page.setup_dialog()
+
if self.current_id == PageIds.ImportDataConfiguration:
self.data_configuration_page.setup_dialog(
self._basket_handling(self.import_data_configuration)
diff --git a/QgisModelBaker/ui/set_sequence_panel.ui b/QgisModelBaker/ui/set_sequence_panel.ui
index fa73474f3..7101d2fe5 100644
--- a/QgisModelBaker/ui/set_sequence_panel.ui
+++ b/QgisModelBaker/ui/set_sequence_panel.ui
@@ -14,9 +14,6 @@
Select Files
- -
-
-
-
@@ -24,6 +21,13 @@
+ -
+
+
+ 2147483647
+
+
+
diff --git a/QgisModelBaker/ui/workflow_wizard/tid_configuration.ui b/QgisModelBaker/ui/workflow_wizard/tid_configuration.ui
index 30af5f5e4..f83ff353e 100644
--- a/QgisModelBaker/ui/workflow_wizard/tid_configuration.ui
+++ b/QgisModelBaker/ui/workflow_wizard/tid_configuration.ui
@@ -39,13 +39,13 @@
+ -
+
+
-
- -
-
-
- -
+
-
Qt::Vertical
@@ -58,7 +58,7 @@
- -
+
-
Qt::Horizontal
@@ -71,20 +71,40 @@
- -
-
+
-
+
Set configuration
- -
+
-
0
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ Reset Values
+
+
+
From bb5ead576eca731013ade9c002ba2e01972ccd70 Mon Sep 17 00:00:00 2001
From: signedav
Date: Fri, 17 Nov 2023 17:01:38 +0100
Subject: [PATCH 06/30] have now 1 a page then a tid_configuator and there the
two panels
---
...id_config_panel.py => layer_tids_panel.py} | 16 ++---
.../gui/panel/tid_configurator_panel.py | 58 +++++++++++++++++++
.../workflow_wizard/tid_configuration_page.py | 33 +++--------
.../gui/workflow_wizard/workflow_wizard.py | 1 -
...id_config_panel.ui => layer_tids_panel.ui} | 6 +-
QgisModelBaker/ui/set_sequence_panel.ui | 42 ++++++++++----
QgisModelBaker/ui/tid_configurator_panel.ui | 55 ++++++++++++++++++
.../ui/workflow_wizard/tid_configuration.ui | 43 ++++----------
8 files changed, 172 insertions(+), 82 deletions(-)
rename QgisModelBaker/gui/panel/{tid_config_panel.py => layer_tids_panel.py} (92%)
create mode 100644 QgisModelBaker/gui/panel/tid_configurator_panel.py
rename QgisModelBaker/ui/{tid_config_panel.ui => layer_tids_panel.ui} (78%)
create mode 100644 QgisModelBaker/ui/tid_configurator_panel.ui
diff --git a/QgisModelBaker/gui/panel/tid_config_panel.py b/QgisModelBaker/gui/panel/layer_tids_panel.py
similarity index 92%
rename from QgisModelBaker/gui/panel/tid_config_panel.py
rename to QgisModelBaker/gui/panel/layer_tids_panel.py
index 8a590a00f..0caa8bcd4 100644
--- a/QgisModelBaker/gui/panel/tid_config_panel.py
+++ b/QgisModelBaker/gui/panel/layer_tids_panel.py
@@ -28,7 +28,7 @@
from QgisModelBaker.libs.modelbaker.utils.qgis_utils import QgisProjectUtils
from QgisModelBaker.utils.gui_utils import CheckDelegate
-WIDGET_UI = gui_utils.get_ui_class("tid_config_panel.ui")
+WIDGET_UI = gui_utils.get_ui_class("layer_tids_panel.ui")
class TIDModel(QAbstractTableModel):
@@ -129,28 +129,28 @@ def save_tid_config(self, qgis_project=None):
QgisProjectUtils(qgis_project).set_oid_settings(self.oid_settings)
-class TIDConfigPanel(QWidget, WIDGET_UI):
+class LayerTIDsPanel(QWidget, WIDGET_UI):
def __init__(self, parent=None):
QWidget.__init__(self, parent)
self.setupUi(self)
self.parent = parent
self.tid_model = TIDModel()
- self.tid_config_view.setModel(self.tid_model)
+ self.layer_tids_view.setModel(self.tid_model)
- self.tid_config_view.horizontalHeader().setSectionResizeMode(
+ self.layer_tids_view.horizontalHeader().setSectionResizeMode(
TIDModel.Columns.NAME, QHeaderView.Stretch
)
- self.tid_config_view.horizontalHeader().setSectionResizeMode(
+ self.layer_tids_view.horizontalHeader().setSectionResizeMode(
TIDModel.Columns.OID_DOMAIN, QHeaderView.ResizeToContents
)
- self.tid_config_view.horizontalHeader().setSectionResizeMode(
+ self.layer_tids_view.horizontalHeader().setSectionResizeMode(
TIDModel.Columns.DEFAULT_VALUE, QHeaderView.ResizeToContents
)
- self.tid_config_view.horizontalHeader().setSectionResizeMode(
+ self.layer_tids_view.horizontalHeader().setSectionResizeMode(
TIDModel.Columns.IN_FORM, QHeaderView.ResizeToContents
)
- self.tid_config_view.setItemDelegateForColumn(
+ self.layer_tids_view.setItemDelegateForColumn(
TIDModel.Columns.IN_FORM,
CheckDelegate(self, Qt.EditRole),
)
diff --git a/QgisModelBaker/gui/panel/tid_configurator_panel.py b/QgisModelBaker/gui/panel/tid_configurator_panel.py
new file mode 100644
index 000000000..7daddb901
--- /dev/null
+++ b/QgisModelBaker/gui/panel/tid_configurator_panel.py
@@ -0,0 +1,58 @@
+"""
+/***************************************************************************
+ -------------------
+ begin : 17.11.2023
+ git sha : :%H$
+ copyright : (C) 2023 by Dave Signer
+ email : david at opengis ch
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ ***************************************************************************/
+"""
+
+
+from qgis.PyQt.QtWidgets import QWidget
+
+from QgisModelBaker.gui.panel.layer_tids_panel import LayerTIDsPanel
+from QgisModelBaker.gui.panel.set_sequence_panel import SetSequencePanel
+from QgisModelBaker.utils import gui_utils
+
+WIDGET_UI = gui_utils.get_ui_class("tid_configurator_panel.ui")
+
+
+class TIDConfiguratorPanel(QWidget, WIDGET_UI):
+ def __init__(self, parent=None):
+ QWidget.__init__(self, parent)
+ self.setupUi(self)
+ self.parent = parent
+
+ self.layer_tids_panel = LayerTIDsPanel(self.parent)
+ self.layer_tids_layout.addWidget(self.layer_tids_panel)
+
+ self.set_sequence_panel = SetSequencePanel(self.parent)
+ self.set_sequence_layout.addWidget(self.set_sequence_panel)
+
+ self.reset_layer_tids_button.clicked.connect(self._reset_tid_configuration)
+
+ self.qgis_project = None
+ self.db_connector = None
+
+ def setup_dialog(self, db_connector, qgis_project):
+ self.qgis_project = qgis_project
+ self.db_connector = db_connector
+ self._reset_tid_configuration()
+
+ def _reset_tid_configuration(self):
+ self.layer_tids_panel.load_tid_config(self.qgis_project)
+ self.set_sequence_panel.load_sequence(self.db_connector)
+
+ def set_tid_configuration(self):
+ self.layer_tids_panel.save_tid_config(self.qgis_project)
+ self.set_sequence_panel.save_sequence(self.db_connector)
diff --git a/QgisModelBaker/gui/workflow_wizard/tid_configuration_page.py b/QgisModelBaker/gui/workflow_wizard/tid_configuration_page.py
index 9b6493ae9..d08fff9ec 100644
--- a/QgisModelBaker/gui/workflow_wizard/tid_configuration_page.py
+++ b/QgisModelBaker/gui/workflow_wizard/tid_configuration_page.py
@@ -22,8 +22,7 @@
from qgis.PyQt.QtWidgets import QWizardPage
import QgisModelBaker.libs.modelbaker.utils.db_utils as db_utils
-from QgisModelBaker.gui.panel.set_sequence_panel import SetSequencePanel
-from QgisModelBaker.gui.panel.tid_config_panel import TIDConfigPanel
+from QgisModelBaker.gui.panel.tid_configurator_panel import TIDConfiguratorPanel
from QgisModelBaker.utils import gui_utils
PAGE_UI = gui_utils.get_ui_class("workflow_wizard/tid_configuration.ui")
@@ -34,49 +33,33 @@ def __init__(self, parent, title):
QWizardPage.__init__(self, parent)
self.workflow_wizard = parent
- self.qgis_project = QgsProject.instance()
self.setupUi(self)
self.setFinalPage(True)
self.setTitle(title)
self.setStyleSheet(gui_utils.DEFAULT_STYLE)
- self.tid_config_panel = TIDConfigPanel(self.workflow_wizard)
- self.tid_config_layout.addWidget(self.tid_config_panel)
+ self.tid_configurator_panel = TIDConfiguratorPanel(self.workflow_wizard)
+ self.tid_configurator_layout.addWidget(self.tid_configurator_panel)
- self.set_sequence_panel = SetSequencePanel(self.workflow_wizard)
- self.set_sequence_layout.addWidget(self.set_sequence_panel)
-
- self.reset_tid_config_button.clicked.connect(self._reset_tid_configuration)
- self.set_tid_config_and_sequence_button.clicked.connect(
+ self.set_layer_tids_and_sequence_button.clicked.connect(
self._set_tid_configuration
)
self.configuration = None
- self.db_connector = None
def set_configuration(self, configuration):
self.configuration = configuration
-
- def setup_dialog(self):
- self.db_connector = db_utils.get_db_connector(self.configuration)
- self._reset_tid_configuration()
-
- def _reset_tid_configuration(self):
- self.tid_config_panel.load_tid_config(self.qgis_project)
- self.set_sequence_panel.load_sequence(self.db_connector)
+ db_connector = db_utils.get_db_connector(self.configuration)
+ self.tid_configurator_panel.setup_dialog(db_connector, QgsProject.instance())
def _set_tid_configuration(self):
self.progress_bar.setValue(0)
-
- self.tid_config_panel.save_tid_config(self.qgis_project)
+ # we store the settings to project and db
+ self.tid_configurator_panel.set_tid_configuration()
self.workflow_wizard.log_panel.print_info(
self.tr("Stored TID configurations to current project")
)
-
- self.progress_bar.setValue(50)
-
- self.set_sequence_panel.save_sequence(self.db_connector)
self.workflow_wizard.log_panel.print_info(
self.tr("Stored the sequence value to current database")
)
diff --git a/QgisModelBaker/gui/workflow_wizard/workflow_wizard.py b/QgisModelBaker/gui/workflow_wizard/workflow_wizard.py
index a4cf463dc..b90da69ed 100644
--- a/QgisModelBaker/gui/workflow_wizard/workflow_wizard.py
+++ b/QgisModelBaker/gui/workflow_wizard/workflow_wizard.py
@@ -383,7 +383,6 @@ def id_changed(self, new_id):
self.tid_configuration_page.set_configuration(
self.import_schema_configuration
)
- self.tid_configuration_page.setup_dialog()
if self.current_id == PageIds.ImportDataConfiguration:
self.data_configuration_page.setup_dialog(
diff --git a/QgisModelBaker/ui/tid_config_panel.ui b/QgisModelBaker/ui/layer_tids_panel.ui
similarity index 78%
rename from QgisModelBaker/ui/tid_config_panel.ui
rename to QgisModelBaker/ui/layer_tids_panel.ui
index 8d5775330..7ee47f957 100644
--- a/QgisModelBaker/ui/tid_config_panel.ui
+++ b/QgisModelBaker/ui/layer_tids_panel.ui
@@ -1,7 +1,7 @@
- tid_config_layout
-
+ layer_tids_layout
+
0
@@ -15,7 +15,7 @@
-
-
+
true
diff --git a/QgisModelBaker/ui/set_sequence_panel.ui b/QgisModelBaker/ui/set_sequence_panel.ui
index 7101d2fe5..ec1ee3f99 100644
--- a/QgisModelBaker/ui/set_sequence_panel.ui
+++ b/QgisModelBaker/ui/set_sequence_panel.ui
@@ -6,26 +6,44 @@
0
0
- 674
- 43
+ 744
+ 88
Select Files
-
-
-
-
- T_Id (Sequence) Value
+
-
+
+
+ <html><head/><body><p>With this option you reset the technical sequence id on the database. </p><p>Do it only when you know what you are about to do.</p></body></html>
-
-
- -
-
-
- 2147483647
+
+ Reset the technical sequence used as t_id value
+
+
+ true
+
+
+ false
+
+
-
+
+
+ T_Id (Sequence) Value
+
+
+
+ -
+
+
+ 2147483647
+
+
+
+
diff --git a/QgisModelBaker/ui/tid_configurator_panel.ui b/QgisModelBaker/ui/tid_configurator_panel.ui
new file mode 100644
index 000000000..617e5a10c
--- /dev/null
+++ b/QgisModelBaker/ui/tid_configurator_panel.ui
@@ -0,0 +1,55 @@
+
+
+ tid_configuration
+
+
+
+ 0
+ 0
+ 800
+ 443
+
+
+
+ Select Files
+
+
+ -
+
+
+ QLayout::SetMaximumSize
+
+
+
+ -
+
+
+ QLayout::SetMinimumSize
+
+
+
+ -
+
+
+ Reset Values
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+
+
+
diff --git a/QgisModelBaker/ui/workflow_wizard/tid_configuration.ui b/QgisModelBaker/ui/workflow_wizard/tid_configuration.ui
index f83ff353e..d537e32c6 100644
--- a/QgisModelBaker/ui/workflow_wizard/tid_configuration.ui
+++ b/QgisModelBaker/ui/workflow_wizard/tid_configuration.ui
@@ -14,7 +14,7 @@
Select Files
- -
+
-
@@ -39,13 +39,10 @@
- -
-
+
-
+
- -
-
-
- -
+
-
Qt::Vertical
@@ -58,35 +55,15 @@
- -
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
- -
-
-
- Set configuration
-
-
-
- -
+
-
0
- -
-
+
-
+
Qt::Horizontal
@@ -98,10 +75,10 @@
- -
-
+
-
+
- Reset Values
+ Set configuration
From 5f5d2fb2493e1ac8e08f22a700bfa9a9d02de0c4 Mon Sep 17 00:00:00 2001
From: signedav
Date: Fri, 17 Nov 2023 22:19:55 +0100
Subject: [PATCH 07/30] nice expression dialog delegate, but it still appears
only on double click
---
QgisModelBaker/gui/panel/layer_tids_panel.py | 28 +++++++++++++++++++-
1 file changed, 27 insertions(+), 1 deletion(-)
diff --git a/QgisModelBaker/gui/panel/layer_tids_panel.py b/QgisModelBaker/gui/panel/layer_tids_panel.py
index 0caa8bcd4..ee1b5bd11 100644
--- a/QgisModelBaker/gui/panel/layer_tids_panel.py
+++ b/QgisModelBaker/gui/panel/layer_tids_panel.py
@@ -21,8 +21,9 @@
from enum import IntEnum
from qgis.core import QgsProject
+from qgis.gui import QgsFieldExpressionWidget
from qgis.PyQt.QtCore import QAbstractTableModel, QModelIndex, Qt
-from qgis.PyQt.QtWidgets import QHeaderView, QWidget
+from qgis.PyQt.QtWidgets import QHeaderView, QStyledItemDelegate, QWidget
import QgisModelBaker.utils.gui_utils as gui_utils
from QgisModelBaker.libs.modelbaker.utils.qgis_utils import QgisProjectUtils
@@ -129,6 +130,27 @@ def save_tid_config(self, qgis_project=None):
QgisProjectUtils(qgis_project).set_oid_settings(self.oid_settings)
+class FieldExpressionDelegate(QStyledItemDelegate):
+ def __init__(self, parent):
+ super().__init__(parent)
+ self.editor = None
+
+ def createEditor(self, parent, option, index):
+ self.editor = QgsFieldExpressionWidget(parent)
+ return self.editor
+
+ def setEditorData(self, editor, index):
+ value = index.data(int(Qt.DisplayRole))
+ self.editor.setExpression(value)
+
+ def setModelData(self, editor, model, index):
+ value = editor.expression()
+ model.setData(index, value, int(Qt.EditRole))
+
+ def updateEditorGeometry(self, editor, option, index):
+ self.editor.setGeometry(option.rect)
+
+
class LayerTIDsPanel(QWidget, WIDGET_UI):
def __init__(self, parent=None):
QWidget.__init__(self, parent)
@@ -154,6 +176,10 @@ def __init__(self, parent=None):
TIDModel.Columns.IN_FORM,
CheckDelegate(self, Qt.EditRole),
)
+ self.layer_tids_view.setItemDelegateForColumn(
+ TIDModel.Columns.DEFAULT_VALUE,
+ FieldExpressionDelegate(self),
+ )
def load_tid_config(self, qgis_project=QgsProject.instance()):
self.tid_model.load_tid_config(qgis_project)
From e43ea388dba25c439bf5402d87aa09e5eaef06d9 Mon Sep 17 00:00:00 2001
From: signedav
Date: Fri, 17 Nov 2023 22:26:49 +0100
Subject: [PATCH 08/30] at least with one click it opens the expression widget
---
QgisModelBaker/gui/panel/layer_tids_panel.py | 8 +++++++-
.../gui/workflow_wizard/import_data_configuration_page.py | 2 ++
2 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/QgisModelBaker/gui/panel/layer_tids_panel.py b/QgisModelBaker/gui/panel/layer_tids_panel.py
index ee1b5bd11..de7aafa06 100644
--- a/QgisModelBaker/gui/panel/layer_tids_panel.py
+++ b/QgisModelBaker/gui/panel/layer_tids_panel.py
@@ -23,7 +23,12 @@
from qgis.core import QgsProject
from qgis.gui import QgsFieldExpressionWidget
from qgis.PyQt.QtCore import QAbstractTableModel, QModelIndex, Qt
-from qgis.PyQt.QtWidgets import QHeaderView, QStyledItemDelegate, QWidget
+from qgis.PyQt.QtWidgets import (
+ QAbstractItemView,
+ QHeaderView,
+ QStyledItemDelegate,
+ QWidget,
+)
import QgisModelBaker.utils.gui_utils as gui_utils
from QgisModelBaker.libs.modelbaker.utils.qgis_utils import QgisProjectUtils
@@ -180,6 +185,7 @@ def __init__(self, parent=None):
TIDModel.Columns.DEFAULT_VALUE,
FieldExpressionDelegate(self),
)
+ self.layer_tids_view.setEditTriggers(QAbstractItemView.AllEditTriggers)
def load_tid_config(self, qgis_project=QgsProject.instance()):
self.tid_model.load_tid_config(qgis_project)
diff --git a/QgisModelBaker/gui/workflow_wizard/import_data_configuration_page.py b/QgisModelBaker/gui/workflow_wizard/import_data_configuration_page.py
index 00be44c6a..628d5e834 100644
--- a/QgisModelBaker/gui/workflow_wizard/import_data_configuration_page.py
+++ b/QgisModelBaker/gui/workflow_wizard/import_data_configuration_page.py
@@ -24,6 +24,7 @@
from qgis.PyQt.QtCore import Qt
from qgis.PyQt.QtGui import QIcon
from qgis.PyQt.QtWidgets import (
+ QAbstractItemView,
QComboBox,
QCompleter,
QHeaderView,
@@ -151,6 +152,7 @@ def __init__(self, parent, title):
gui_utils.SourceModel.Columns.DATASET, QHeaderView.ResizeToContents
)
+ self.file_table_view.setEditTriggers(QAbstractItemView.AllEditTriggers)
self.file_table_view.verticalHeader().setSectionsMovable(True)
self.file_table_view.verticalHeader().setDragEnabled(True)
self.file_table_view.verticalHeader().setDragDropMode(QHeaderView.InternalMove)
From 4f9071610848b47ad5338955de4895fde95dafe3 Mon Sep 17 00:00:00 2001
From: signedav
Date: Mon, 20 Nov 2023 09:31:22 +0100
Subject: [PATCH 09/30] Make delegates beautiful - means show them from
beginning. Pass the layer to the expression widget so we can use fields etc.
---
QgisModelBaker/gui/panel/layer_tids_panel.py | 29 ++++++++++++++++++-
.../import_data_configuration_page.py | 23 +++++++--------
2 files changed, 39 insertions(+), 13 deletions(-)
diff --git a/QgisModelBaker/gui/panel/layer_tids_panel.py b/QgisModelBaker/gui/panel/layer_tids_panel.py
index de7aafa06..20884cf38 100644
--- a/QgisModelBaker/gui/panel/layer_tids_panel.py
+++ b/QgisModelBaker/gui/panel/layer_tids_panel.py
@@ -18,11 +18,12 @@
"""
-from enum import IntEnum
+from enum import Enum, IntEnum
from qgis.core import QgsProject
from qgis.gui import QgsFieldExpressionWidget
from qgis.PyQt.QtCore import QAbstractTableModel, QModelIndex, Qt
+from qgis.PyQt.QtGui import QPixmap
from qgis.PyQt.QtWidgets import (
QAbstractItemView,
QHeaderView,
@@ -54,6 +55,12 @@ class TIDModel(QAbstractTableModel):
}
"""
+ class Roles(Enum):
+ LAYER = Qt.UserRole + 1
+
+ def __int__(self):
+ return self.value
+
class Columns(IntEnum):
NAME = 0
OID_DOMAIN = 1
@@ -111,6 +118,9 @@ def data(self, index, role):
return self.oid_settings[key]["default_value_expression"]
if index.column() == TIDModel.Columns.IN_FORM:
return self.oid_settings[key]["in_form"]
+ elif role == int(TIDModel.Roles.LAYER):
+ key = list(self.oid_settings.keys())[index.row()]
+ return self.oid_settings[key]["layer"]
return None
def setData(self, index, data, role):
@@ -138,10 +148,13 @@ def save_tid_config(self, qgis_project=None):
class FieldExpressionDelegate(QStyledItemDelegate):
def __init__(self, parent):
super().__init__(parent)
+ self.parent = parent
self.editor = None
def createEditor(self, parent, option, index):
self.editor = QgsFieldExpressionWidget(parent)
+ layer = index.data(int(TIDModel.Roles.LAYER))
+ self.editor.setLayer(layer)
return self.editor
def setEditorData(self, editor, index):
@@ -155,6 +168,17 @@ def setModelData(self, editor, model, index):
def updateEditorGeometry(self, editor, option, index):
self.editor.setGeometry(option.rect)
+ def paint(self, painter, option, index):
+ opt = self.createEditor(self.parent, option, index)
+ opt.editable = False
+ value = index.data(int(Qt.DisplayRole))
+ opt.setExpression(value)
+ opt.resize(option.rect.width(), option.rect.height())
+ pixmap = QPixmap(opt.width(), opt.height())
+ opt.render(pixmap)
+ painter.drawPixmap(option.rect, pixmap)
+ painter.restore()
+
class LayerTIDsPanel(QWidget, WIDGET_UI):
def __init__(self, parent=None):
@@ -187,6 +211,9 @@ def __init__(self, parent=None):
)
self.layer_tids_view.setEditTriggers(QAbstractItemView.AllEditTriggers)
+ # for row in range(0,self.tid_model.rowCount(QModelIndex())):
+ # self.layer_tids_view.openPersistentEditor(self.tid_model.index(row, TIDModel.Columns.DEFAULT_VALUE))
+
def load_tid_config(self, qgis_project=QgsProject.instance()):
self.tid_model.load_tid_config(qgis_project)
diff --git a/QgisModelBaker/gui/workflow_wizard/import_data_configuration_page.py b/QgisModelBaker/gui/workflow_wizard/import_data_configuration_page.py
index 628d5e834..79876c277 100644
--- a/QgisModelBaker/gui/workflow_wizard/import_data_configuration_page.py
+++ b/QgisModelBaker/gui/workflow_wizard/import_data_configuration_page.py
@@ -19,18 +19,15 @@
import os
-from PyQt5.QtWidgets import QApplication
from qgis.core import QgsApplication
from qgis.PyQt.QtCore import Qt
-from qgis.PyQt.QtGui import QIcon
+from qgis.PyQt.QtGui import QIcon, QPixmap
from qgis.PyQt.QtWidgets import (
QAbstractItemView,
QComboBox,
QCompleter,
QHeaderView,
- QStyle,
QStyledItemDelegate,
- QStyleOptionComboBox,
QWizardPage,
)
@@ -50,6 +47,7 @@
class DatasetComboDelegate(QStyledItemDelegate):
def __init__(self, parent, db_connector):
super().__init__(parent)
+ self.parent = parent
self.refresh_datasets(db_connector)
def refresh_datasets(self, db_connector):
@@ -79,15 +77,16 @@ def updateEditorGeometry(self, editor, option, index):
editor.setGeometry(option.rect)
def paint(self, painter, option, index):
- """
- Here it paints only the lable without a StyleItem for the ComboBox, because to edit it needs multiple clicks and the behavior gets confusing.
- """
- opt = QStyleOptionComboBox()
+ opt = self.createEditor(self.parent, option, index)
opt.editable = False
- opt.rect = option.rect
- value = index.data(int(Qt.DisplayRole))
- opt.currentText = value
- QApplication.style().drawControl(QStyle.CE_ComboBoxLabel, opt, painter)
+ value = index.data(int(gui_utils.SourceModel.Roles.DATASET_NAME))
+ num = self.items.index(value) if value in self.items else 0
+ opt.setCurrentIndex(num)
+ opt.resize(option.rect.width(), option.rect.height())
+ pixmap = QPixmap(opt.width(), opt.height())
+ opt.render(pixmap)
+ painter.drawPixmap(option.rect, pixmap)
+ painter.restore()
class ImportDataConfigurationPage(QWizardPage, PAGE_UI):
From a0d85cf0f7bd42d284766346c2da3c502c3f3b16 Mon Sep 17 00:00:00 2001
From: signedav
Date: Mon, 20 Nov 2023 11:51:07 +0100
Subject: [PATCH 10/30] TID Manager openable via menu
---
QgisModelBaker/gui/dataset_manager.py | 1 +
.../gui/panel/set_sequence_panel.py | 10 ++-
.../gui/panel/tid_configurator_panel.py | 31 ++++++-
QgisModelBaker/gui/tid_manager.py | 86 +++++++++++++++++++
.../workflow_wizard/tid_configuration_page.py | 26 +++---
QgisModelBaker/qgismodelbaker.py | 35 ++++++++
QgisModelBaker/ui/tid_manager.ui | 31 +++++++
7 files changed, 203 insertions(+), 17 deletions(-)
create mode 100644 QgisModelBaker/gui/tid_manager.py
create mode 100644 QgisModelBaker/ui/tid_manager.ui
diff --git a/QgisModelBaker/gui/dataset_manager.py b/QgisModelBaker/gui/dataset_manager.py
index 31c5cc4d5..74c9ee7bb 100644
--- a/QgisModelBaker/gui/dataset_manager.py
+++ b/QgisModelBaker/gui/dataset_manager.py
@@ -105,6 +105,7 @@ def _close_editing(self):
if layer.isEditable():
editable_layers.append(layer)
if editable_layers:
+ # in case it could not close it automatically
warning_box = QMessageBox(self)
warning_box.setIcon(QMessageBox.Warning)
warning_title = self.tr("Layers still editable")
diff --git a/QgisModelBaker/gui/panel/set_sequence_panel.py b/QgisModelBaker/gui/panel/set_sequence_panel.py
index 4948174c6..344891822 100644
--- a/QgisModelBaker/gui/panel/set_sequence_panel.py
+++ b/QgisModelBaker/gui/panel/set_sequence_panel.py
@@ -36,5 +36,11 @@ def load_sequence(self, db_connector=None):
self.sequence_value_edit.setValue(sequence_value)
def save_sequence(self, db_connector=None):
- if db_connector:
- db_connector.set_ili2db_sequence_value(self.sequence_value_edit.value())
+ if self.sequence_group.isChecked():
+ if db_connector:
+ return db_connector.set_ili2db_sequence_value(
+ self.sequence_value_edit.value()
+ )
+ return False, self.tr("Could not reset T_Id - no db_connector...")
+ else:
+ return True, self.tr("T_Id not set.")
diff --git a/QgisModelBaker/gui/panel/tid_configurator_panel.py b/QgisModelBaker/gui/panel/tid_configurator_panel.py
index 7daddb901..8926ab7cd 100644
--- a/QgisModelBaker/gui/panel/tid_configurator_panel.py
+++ b/QgisModelBaker/gui/panel/tid_configurator_panel.py
@@ -20,8 +20,12 @@
from qgis.PyQt.QtWidgets import QWidget
+import QgisModelBaker.libs.modelbaker.utils.db_utils as db_utils
from QgisModelBaker.gui.panel.layer_tids_panel import LayerTIDsPanel
from QgisModelBaker.gui.panel.set_sequence_panel import SetSequencePanel
+from QgisModelBaker.libs.modelbaker.iliwrapper.ili2dbconfig import (
+ Ili2DbCommandConfiguration,
+)
from QgisModelBaker.utils import gui_utils
WIDGET_UI = gui_utils.get_ui_class("tid_configurator_panel.ui")
@@ -44,9 +48,24 @@ def __init__(self, parent=None):
self.qgis_project = None
self.db_connector = None
- def setup_dialog(self, db_connector, qgis_project):
+ def setup_dialog(self, qgis_project, db_connector=None):
self.qgis_project = qgis_project
- self.db_connector = db_connector
+
+ if db_connector:
+ self.db_connector = db_connector
+ else:
+ # getting the data source of the first layer in the layer tree
+ first_tree_layer = qgis_project.layerTreeRoot().findLayers()[0]
+ if first_tree_layer:
+ configuration = Ili2DbCommandConfiguration()
+ source_provider = first_tree_layer.layer().dataProvider()
+ valid, mode = db_utils.get_configuration_from_sourceprovider(
+ source_provider, configuration
+ )
+ if valid:
+ configuration.tool = mode
+ self.db_connector = db_utils.get_db_connector(configuration)
+
self._reset_tid_configuration()
def _reset_tid_configuration(self):
@@ -54,5 +73,9 @@ def _reset_tid_configuration(self):
self.set_sequence_panel.load_sequence(self.db_connector)
def set_tid_configuration(self):
- self.layer_tids_panel.save_tid_config(self.qgis_project)
- self.set_sequence_panel.save_sequence(self.db_connector)
+ result, message = self.set_sequence_panel.save_sequence(self.db_connector)
+ # only set the project settings when the sequence part succeeds (or is not performed)
+ if result:
+ self.layer_tids_panel.save_tid_config(self.qgis_project)
+ return True, message
+ return False, message
diff --git a/QgisModelBaker/gui/tid_manager.py b/QgisModelBaker/gui/tid_manager.py
new file mode 100644
index 000000000..b889da5bb
--- /dev/null
+++ b/QgisModelBaker/gui/tid_manager.py
@@ -0,0 +1,86 @@
+"""
+/***************************************************************************
+ begin : 10.08.2021
+ git sha : :%H$
+ copyright : (C) 2021 by Dave Signer
+ email : david at opengis ch
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ ***************************************************************************/
+"""
+
+from qgis.core import QgsMapLayer, QgsProject
+from qgis.PyQt.QtWidgets import QDialog, QMessageBox
+
+import QgisModelBaker.libs.modelbaker.utils.db_utils as db_utils
+from QgisModelBaker.gui.panel.tid_configurator_panel import TIDConfiguratorPanel
+from QgisModelBaker.utils import gui_utils
+
+DIALOG_UI = gui_utils.get_ui_class("tid_manager.ui")
+
+
+class TIDManagerDialog(QDialog, DIALOG_UI):
+ def __init__(self, iface, parent=None):
+
+ QDialog.__init__(self, parent)
+ self.iface = iface
+ self._close_editing()
+ self.parent = parent
+
+ self.setupUi(self)
+
+ self.tid_configurator_panel = TIDConfiguratorPanel(self.parent)
+ self.tid_configurator_layout.addWidget(self.tid_configurator_panel)
+
+ self.buttonBox.accepted.connect(self._accepted)
+ self.buttonBox.rejected.connect(self._rejected)
+
+ self.setStyleSheet(gui_utils.DEFAULT_STYLE)
+
+ self.configuration = configuration
+ db_connector = db_utils.get_db_connector(self.configuration)
+ self.tid_configurator_panel.setup_dialog(db_connector, QgsProject.instance())
+
+ self.tid_configurator_panel.setup_dialog(QgsProject.instance())
+
+ def _close_editing(self):
+ editable_layers = []
+ for layer in QgsProject.instance().mapLayers().values():
+ if layer.type() == QgsMapLayer.VectorLayer:
+ self.iface.vectorLayerTools().stopEditing(layer)
+ if layer.isEditable():
+ editable_layers.append(layer)
+ if editable_layers:
+ # in case it could not close it automatically
+ warning_box = QMessageBox(self)
+ warning_box.setIcon(QMessageBox.Warning)
+ warning_title = self.tr("Layers still editable")
+ warning_box.setWindowTitle(warning_title)
+ warning_box.setText(
+ self.tr(
+ "You still have layers in edit mode.\nIn case you modify the sequence in the database of those layers, it could lead to database locks.\nEditable layers are:\n - {}"
+ ).format("\n - ".join([layer.name() for layer in editable_layers]))
+ )
+ warning_box.exec_()
+
+ def _accepted(self):
+ result, message = self.tid_configurator_panel.set_tid_configuration()
+ if not result:
+ error_box = QMessageBox(self)
+ error_box.setIcon(QMessageBox.Error)
+ warning_title = self.tr("Problems on setting T_Id sequence.")
+ error_box.setWindowTitle(warning_title)
+ error_box.setText(message)
+ error_box.exec_()
+ else:
+ self.close()
+
+ def _rejected(self):
+ self.close()
diff --git a/QgisModelBaker/gui/workflow_wizard/tid_configuration_page.py b/QgisModelBaker/gui/workflow_wizard/tid_configuration_page.py
index d08fff9ec..c1635addc 100644
--- a/QgisModelBaker/gui/workflow_wizard/tid_configuration_page.py
+++ b/QgisModelBaker/gui/workflow_wizard/tid_configuration_page.py
@@ -51,18 +51,22 @@ def __init__(self, parent, title):
def set_configuration(self, configuration):
self.configuration = configuration
db_connector = db_utils.get_db_connector(self.configuration)
- self.tid_configurator_panel.setup_dialog(db_connector, QgsProject.instance())
+ self.tid_configurator_panel.setup_dialog(QgsProject.instance(), db_connector)
def _set_tid_configuration(self):
self.progress_bar.setValue(0)
# we store the settings to project and db
- self.tid_configurator_panel.set_tid_configuration()
- self.workflow_wizard.log_panel.print_info(
- self.tr("Stored TID configurations to current project")
- )
- self.workflow_wizard.log_panel.print_info(
- self.tr("Stored the sequence value to current database")
- )
-
- self.progress_bar.setValue(100)
- self.setStyleSheet(gui_utils.SUCCESS_STYLE)
+ result, message = self.tid_configurator_panel.set_tid_configuration()
+ if result:
+ self.workflow_wizard.log_panel.print_info(
+ self.tr("Stored TID configurations to current project")
+ )
+ self.workflow_wizard.log_panel.print_info(
+ self.tr("Stored the sequence value to current database")
+ )
+ self.progress_bar.setValue(100)
+ self.setStyleSheet(gui_utils.SUCCESS_STYLE)
+ else:
+ self.workflow_wizard.log_panel.print_info(message)
+ self.progress_bar.setValue(0)
+ self.setStyleSheet(gui_utils.ERROR_STYLE)
diff --git a/QgisModelBaker/qgismodelbaker.py b/QgisModelBaker/qgismodelbaker.py
index cd74c2c06..d1d717012 100644
--- a/QgisModelBaker/qgismodelbaker.py
+++ b/QgisModelBaker/qgismodelbaker.py
@@ -48,6 +48,7 @@
from QgisModelBaker.gui.drop_message import DropMessageDialog
from QgisModelBaker.gui.options import OptionsDialog
from QgisModelBaker.gui.panel.dataset_selector import DatasetSelector
+from QgisModelBaker.gui.tid_manager import TIDManagerDialog
from QgisModelBaker.gui.topping_wizard.topping_wizard import ToppingWizardDialog
from QgisModelBaker.gui.validate import ValidateDock
from QgisModelBaker.gui.workflow_wizard.workflow_wizard import WorkflowWizardDialog
@@ -65,10 +66,12 @@ def __init__(self, iface):
self.workflow_wizard_dlg = None
self.datasetmanager_dlg = None
+ self.tidmanager_dlg = None
self.topping_wizard_dlg = None
self.__workflow_wizard_action = None
self.__datasetmanager_action = None
+ self.__tidmanager_action = None
self.__validate_action = None
self.__topping_wizard_action = None
self.__configure_action = None
@@ -131,6 +134,16 @@ def initGui(self):
self.tr("Dataset Manager"),
None,
)
+ self.__tidmanager_action = QAction(
+ QIcon(
+ os.path.join(
+ os.path.dirname(__file__),
+ "images/QgisModelBaker-tidmanager-icon.svg",
+ )
+ ),
+ self.tr("TID (OID) Manager"),
+ None,
+ )
self.__validate_action = QAction(
QIcon(
os.path.join(
@@ -172,11 +185,13 @@ def initGui(self):
# set these actions checkable to visualize that the dialog is open
self.__workflow_wizard_action.setCheckable(True)
self.__datasetmanager_action.setCheckable(True)
+ self.__tidmanager_action.setCheckable(True)
self.__validate_action.setCheckable(True)
self.__topping_wizard_action.setCheckable(True)
self.__configure_action.triggered.connect(self.show_options_dialog)
self.__datasetmanager_action.triggered.connect(self.show_datasetmanager_dialog)
+ self.__tidmanager_action.triggered.connect(self.show_tidmanager_dialog)
self.__validate_action.triggered.connect(self.show_validate_dock)
self.__workflow_wizard_action.triggered.connect(
self.show_workflow_wizard_dialog
@@ -198,6 +213,9 @@ def initGui(self):
self.iface.addPluginToDatabaseMenu(
self.tr("Model Baker"), self.__datasetmanager_action
)
+ self.iface.addPluginToDatabaseMenu(
+ self.tr("Model Baker"), self.__tidmanager_action
+ )
self.iface.addPluginToDatabaseMenu(
self.tr("Model Baker"), self.__topping_wizard_action
)
@@ -334,6 +352,23 @@ def datasetmanager_dialog_finished(self):
self.__datasetmanager_action.setChecked(False)
self.datasetmanager_dlg = None
+ def show_tidmanager_dialog(self):
+ if self.tidmanager_dlg:
+ self.tidmanager_dlg.reject()
+ else:
+ self.tidmanager_dlg = TIDManagerDialog(self.iface, self.iface.mainWindow())
+ self.tidmanager_dlg.setAttribute(Qt.WA_DeleteOnClose)
+ self.tidmanager_dlg.setWindowFlags(
+ self.tidmanager_dlg.windowFlags() | Qt.Tool
+ )
+ self.tidmanager_dlg.show()
+ self.tidmanager_dlg.finished.connect(self.tidmanager_dialog_finished)
+ self.__tidmanager_action.setChecked(True)
+
+ def tidmanager_dialog_finished(self):
+ self.__tidmanager_action.setChecked(False)
+ self.tidmanager_dlg = None
+
def show_validate_dock(self):
self.__validate_dock.setVisible(not self.__validate_dock.isVisible())
diff --git a/QgisModelBaker/ui/tid_manager.ui b/QgisModelBaker/ui/tid_manager.ui
new file mode 100644
index 000000000..2b482e284
--- /dev/null
+++ b/QgisModelBaker/ui/tid_manager.ui
@@ -0,0 +1,31 @@
+
+
+ tid_manager
+
+
+
+ 0
+ 0
+ 800
+ 705
+
+
+
+ TID (OID) Manager
+
+
+ -
+
+
+ -
+
+
+ QDialogButtonBox::Cancel|QDialogButtonBox::Ok
+
+
+
+
+
+
+
+
From 36a777214779cb048b74e8b011d5185ef914b191 Mon Sep 17 00:00:00 2001
From: signedav
Date: Mon, 20 Nov 2023 12:14:40 +0100
Subject: [PATCH 11/30] push changes to model on ok when still editing cell in
view
---
QgisModelBaker/gui/panel/layer_tids_panel.py | 7 ++++---
QgisModelBaker/gui/tid_manager.py | 5 -----
2 files changed, 4 insertions(+), 8 deletions(-)
diff --git a/QgisModelBaker/gui/panel/layer_tids_panel.py b/QgisModelBaker/gui/panel/layer_tids_panel.py
index 20884cf38..81417a630 100644
--- a/QgisModelBaker/gui/panel/layer_tids_panel.py
+++ b/QgisModelBaker/gui/panel/layer_tids_panel.py
@@ -163,6 +163,7 @@ def setEditorData(self, editor, index):
def setModelData(self, editor, model, index):
value = editor.expression()
+ print(f"new exp{value}")
model.setData(index, value, int(Qt.EditRole))
def updateEditorGeometry(self, editor, option, index):
@@ -211,11 +212,11 @@ def __init__(self, parent=None):
)
self.layer_tids_view.setEditTriggers(QAbstractItemView.AllEditTriggers)
- # for row in range(0,self.tid_model.rowCount(QModelIndex())):
- # self.layer_tids_view.openPersistentEditor(self.tid_model.index(row, TIDModel.Columns.DEFAULT_VALUE))
-
def load_tid_config(self, qgis_project=QgsProject.instance()):
self.tid_model.load_tid_config(qgis_project)
def save_tid_config(self, qgis_project=QgsProject.instance()):
+ # if a cell is still edited, we need to store it in model by force
+ index = self.layer_tids_view.currentIndex()
+ self.layer_tids_view.currentChanged(index, index)
self.tid_model.save_tid_config(qgis_project)
diff --git a/QgisModelBaker/gui/tid_manager.py b/QgisModelBaker/gui/tid_manager.py
index b889da5bb..5987c5f84 100644
--- a/QgisModelBaker/gui/tid_manager.py
+++ b/QgisModelBaker/gui/tid_manager.py
@@ -19,7 +19,6 @@
from qgis.core import QgsMapLayer, QgsProject
from qgis.PyQt.QtWidgets import QDialog, QMessageBox
-import QgisModelBaker.libs.modelbaker.utils.db_utils as db_utils
from QgisModelBaker.gui.panel.tid_configurator_panel import TIDConfiguratorPanel
from QgisModelBaker.utils import gui_utils
@@ -44,10 +43,6 @@ def __init__(self, iface, parent=None):
self.setStyleSheet(gui_utils.DEFAULT_STYLE)
- self.configuration = configuration
- db_connector = db_utils.get_db_connector(self.configuration)
- self.tid_configurator_panel.setup_dialog(db_connector, QgsProject.instance())
-
self.tid_configurator_panel.setup_dialog(QgsProject.instance())
def _close_editing(self):
From 1aac0c5dbe40bd64e6783d25690235b2e5aee389 Mon Sep 17 00:00:00 2001
From: signedav
Date: Mon, 20 Nov 2023 13:37:13 +0100
Subject: [PATCH 12/30] Descriptive text. Layout finetuning and tooltips.
---
QgisModelBaker/gui/panel/layer_tids_panel.py | 41 +++++++++++++++++++
QgisModelBaker/ui/layer_tids_panel.ui | 12 ++++++
QgisModelBaker/ui/set_sequence_panel.ui | 12 ++++++
QgisModelBaker/ui/tid_configurator_panel.ui | 14 ++++++-
QgisModelBaker/ui/tid_manager.ui | 22 ++++++++--
.../ui/workflow_wizard/tid_configuration.ui | 33 ++++++---------
6 files changed, 108 insertions(+), 26 deletions(-)
diff --git a/QgisModelBaker/gui/panel/layer_tids_panel.py b/QgisModelBaker/gui/panel/layer_tids_panel.py
index 81417a630..cd21e026c 100644
--- a/QgisModelBaker/gui/panel/layer_tids_panel.py
+++ b/QgisModelBaker/gui/panel/layer_tids_panel.py
@@ -118,6 +118,47 @@ def data(self, index, role):
return self.oid_settings[key]["default_value_expression"]
if index.column() == TIDModel.Columns.IN_FORM:
return self.oid_settings[key]["in_form"]
+ elif role == int(Qt.ToolTipRole):
+ key = list(self.oid_settings.keys())[index.row()]
+ if index.column() == TIDModel.Columns.NAME:
+ return f"{key} ({self.oid_settings[key]['interlis_topic']})"
+ if index.column() == TIDModel.Columns.OID_DOMAIN:
+ message = self.tr(
+ "The OID format is not defined, you can use whatever you want, but it should always start with an underscore _
or an alphanumeric value.
"
+ )
+ oid_domain = self.oid_settings[key].get("oid_domain", "")
+ if oid_domain[-7:] == "UUIDOID":
+ message = self.tr(
+ "The OID should be an Universally Unique Identifier (OID TEXT*36).
"
+ )
+ elif oid_domain[-11:] == "STANDARDOID":
+ message = self.tr(
+ """
+
+
+ The OID format requireds an 8 char prefix and 8 char postfix.
+
+ Prefix (2 + 6 chars): Country identifier + a 'global' identification part assigned once by the official authority.
+
Postfix (8 chars): Sequence (numeric or alphanumeric) of your system as 'local' identification part.
+
+
+ """
+ )
+ elif oid_domain[-6:] == "I32OID":
+ message = self.tr(
+ "The OID must be an integer value (OID 0 .. 2147483647).
"
+ )
+ elif oid_domain[-6:] == "ANYOID":
+ message = self.tr(
+ "The OID format could vary depending in what basket the object (entry) is located.
These objects could be in the following topics: {topics}".format(
+ topics=self.oid_settings[key]["interlis_topic"]
+ )
+ )
+ return message
+ if index.column() == TIDModel.Columns.DEFAULT_VALUE:
+ return self.oid_settings[key]["default_value_expression"]
+ if index.column() == TIDModel.Columns.IN_FORM:
+ return self.tr("Show t_ili_tid field (OID) in attribute form.")
elif role == int(TIDModel.Roles.LAYER):
key = list(self.oid_settings.keys())[index.row()]
return self.oid_settings[key]["layer"]
diff --git a/QgisModelBaker/ui/layer_tids_panel.ui b/QgisModelBaker/ui/layer_tids_panel.ui
index 7ee47f957..20e792118 100644
--- a/QgisModelBaker/ui/layer_tids_panel.ui
+++ b/QgisModelBaker/ui/layer_tids_panel.ui
@@ -14,6 +14,18 @@
Select Files
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
-
diff --git a/QgisModelBaker/ui/set_sequence_panel.ui b/QgisModelBaker/ui/set_sequence_panel.ui
index ec1ee3f99..d3f3dff18 100644
--- a/QgisModelBaker/ui/set_sequence_panel.ui
+++ b/QgisModelBaker/ui/set_sequence_panel.ui
@@ -14,6 +14,18 @@
Select Files
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
-
diff --git a/QgisModelBaker/ui/tid_configurator_panel.ui b/QgisModelBaker/ui/tid_configurator_panel.ui
index 617e5a10c..6bbe5c264 100644
--- a/QgisModelBaker/ui/tid_configurator_panel.ui
+++ b/QgisModelBaker/ui/tid_configurator_panel.ui
@@ -6,7 +6,7 @@
0
0
- 800
+ 797
443
@@ -14,6 +14,18 @@
Select Files
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
-
diff --git a/QgisModelBaker/ui/tid_manager.ui b/QgisModelBaker/ui/tid_manager.ui
index 2b482e284..1cc68744d 100644
--- a/QgisModelBaker/ui/tid_manager.ui
+++ b/QgisModelBaker/ui/tid_manager.ui
@@ -7,23 +7,37 @@
0
0
800
- 705
+ 604
TID (OID) Manager
-
-
-
+
-
+
+
+ 6
+
+
- -
+
-
QDialogButtonBox::Cancel|QDialogButtonBox::Ok
+ -
+
+
+ Define the default expression values used for the t_ili_tid field in the attribute forms according to the OID definition from the INTERLIS model.
+
+
+ true
+
+
+
diff --git a/QgisModelBaker/ui/workflow_wizard/tid_configuration.ui b/QgisModelBaker/ui/workflow_wizard/tid_configuration.ui
index d537e32c6..a0ec7bef8 100644
--- a/QgisModelBaker/ui/workflow_wizard/tid_configuration.ui
+++ b/QgisModelBaker/ui/workflow_wizard/tid_configuration.ui
@@ -14,7 +14,7 @@
Select Files
- -
+
-
@@ -29,7 +29,7 @@
- <html><head/><body><p>Configure the OIDs (<span style=" font-family:'monospace';">t_ili_tid</span>) default values of the layers in the generated project.</p></body></html>
+ <html><head/><body><p>Define the default expression values used for the t_ili_tid field in the attribute forms according to the OID definition from the INTERLIS model.</p></body></html>
Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop
@@ -39,30 +39,21 @@
- -
-
-
- -
-
-
- Qt::Vertical
-
-
-
- 20
- 40
-
-
-
-
- -
+
-
0
- -
+
-
+
+
+ QLayout::SetMaximumSize
+
+
+
+ -
Qt::Horizontal
@@ -75,7 +66,7 @@
- -
+
-
Set configuration
From a065b1ea1ab8393b47bebccaf93a134813150f39 Mon Sep 17 00:00:00 2001
From: signedav
Date: Thu, 23 Nov 2023 13:18:17 +0100
Subject: [PATCH 13/30] Documentation for TID Generation
---
docs/assets/oid_physical_data.png | Bin 0 -> 14680 bytes
docs/assets/oid_tid_manager.png | Bin 0 -> 177346 bytes
.../workflow_wizard_tid_generator_page.png | Bin 0 -> 235560 bytes
docs/background_info/oid_tid_generator.md | 145 ++++++++++++++++++
docs/user_guide/import_workflow.md | 25 ++-
mkdocs.yml | 3 +
6 files changed, 166 insertions(+), 7 deletions(-)
create mode 100644 docs/assets/oid_physical_data.png
create mode 100644 docs/assets/oid_tid_manager.png
create mode 100644 docs/assets/workflow_wizard_tid_generator_page.png
create mode 100644 docs/background_info/oid_tid_generator.md
diff --git a/docs/assets/oid_physical_data.png b/docs/assets/oid_physical_data.png
new file mode 100644
index 0000000000000000000000000000000000000000..9c0a80b15ab7a1a4f5338b237255768505df90ec
GIT binary patch
literal 14680
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
fKM+0*S$8QoMvcgrr^nd?9YenT+
literal 0
HcmV?d00001
diff --git a/docs/assets/oid_tid_manager.png b/docs/assets/oid_tid_manager.png
new file mode 100644
index 0000000000000000000000000000000000000000..fb077bcd900139053eafc4c00cd27f8c0699ea85
GIT binary patch
literal 177346
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
J6|qMP=4_
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