diff --git a/controller.py b/controller.py
index eb07bf1..017d798 100644
--- a/controller.py
+++ b/controller.py
@@ -6,7 +6,7 @@
from qgis.utils import iface
from .filters import FilterDefinition, Predicate, FilterManager
-from .helpers import getPostgisLayers, removeFilterFromLayer, addFilterToLayer, refreshLayerTree
+from .helpers import getPostgisLayers, removeFilterFromLayer, addFilterToLayer, refreshLayerTree, hasLayerException
from .settings import FILTER_COMMENT_START, FILTER_COMMENT_STOP
@@ -51,10 +51,10 @@ def onLayersAdded(self, layers: Iterable[QgsMapLayer]):
def updateLayerFilters(self, checked: bool):
for layer in getPostgisLayers(QgsProject.instance().mapLayers().values()):
- if not checked:
- removeFilterFromLayer(layer)
- else:
+ if checked and not hasLayerException(layer):
addFilterToLayer(layer, self.currentFilter)
+ else:
+ removeFilterFromLayer(layer)
refreshLayerTree()
def updateProjectLayers(self, checked):
diff --git a/helpers.py b/helpers.py
index 2ea1423..f722159 100644
--- a/helpers.py
+++ b/helpers.py
@@ -1,8 +1,8 @@
from typing import Any, List, Iterable
-from qgis.core import QgsSettings, QgsMapLayer, QgsMapLayerType, QgsVectorLayer, QgsMessageLog, Qgis
+from qgis.core import QgsExpressionContextUtils, QgsSettings, QgsMapLayer, QgsMapLayerType, QgsVectorLayer
-from .settings import GROUP, FILTER_COMMENT_START, FILTER_COMMENT_STOP
+from .settings import GROUP, FILTER_COMMENT_START, FILTER_COMMENT_STOP, LAYER_EXCEPTION_VARIABLE
def saveSettingsValue(key: str, value: Any):
@@ -72,6 +72,14 @@ def getLayerGeomName(layer: QgsVectorLayer):
return layer.dataProvider().uri().geometryColumn()
+def hasLayerException(layer: QgsVectorLayer) -> bool:
+ return QgsExpressionContextUtils.layerScope(layer).variable(LAYER_EXCEPTION_VARIABLE) == 'true'
+
+
+def setLayerException(layer: QgsVectorLayer, exception: bool) -> None:
+ QgsExpressionContextUtils.setLayerVariable(layer, LAYER_EXCEPTION_VARIABLE, exception)
+
+
def getTestFilterDefinition():
from .filters import Predicate, FilterDefinition
name = 'museumsinsel'
diff --git a/i18n/map_filter_de.qm b/i18n/map_filter_de.qm
index 072b2ac..3a6fcee 100644
Binary files a/i18n/map_filter_de.qm and b/i18n/map_filter_de.qm differ
diff --git a/i18n/map_filter_de.ts b/i18n/map_filter_de.ts
index c078a51..f072ae1 100644
--- a/i18n/map_filter_de.ts
+++ b/i18n/map_filter_de.ts
@@ -109,66 +109,84 @@
- Polygonlayer auswählen
+ Polygonlayer auswählen
- Keine Features gewählt
+ Keine Features gewählt
- Neuer Filter
+ Neuer Filter
ExtentDialog
-
+
Rechteckige Filtergeometrie setzen
+
+ FilterController
+
+
+
+ Neuer Filter
+
+
+
+
+ Polygonlayer auswählen
+
+
+
+
+ Keine Features gewählt
+
+
FilterManager
-
+
Aktuelle Filterdefinition ist ungültig
-
+
Bitte benennen Sie erst den Filter
-
+
Einstellungen überschreiben für Filter
-
+
Überschreiben?
-
+
Filter löschen
-
+
Löschen?
-
+
Aktuelle Einstellungen gehen verloren. Trotzdem anwenden?
-
+
Fortfahren?
@@ -176,12 +194,12 @@
FilterToolbar
-
+
Rechteckiger Filter
-
+
Filter aus gewählten Features
@@ -191,40 +209,71 @@
Filter ein/aus
-
+
Filter speichern
-
+
Filter verwalten
-
+
Filterwerkzeugleiste
-
+
Filtergeometrie anzeigen
-
+
Filter aktivieren
-
+
Filter deaktivieren
-
+
Keine Filtergeometrie gesetzt
+
+
+
+ Ausnahmen festlegen
+
+
+
+
+ Filtergeometrie verstecken
+
+
+
+ LayerExceptionsDialog
+
+
+
+
+
+
+
+ LayerModel
+
+
+
+ Layer hat keinen räumlichen Index
+
+
+
+
+ Layertyp wird nicht unterstützt
+
ManageFiltersDialog
@@ -264,12 +313,12 @@
Schließen
-
+
Namen ändern
-
+
Neuer Name:
@@ -285,7 +334,7 @@
PredicateButton
-
+
Räumlicher Operator
diff --git a/models.py b/models.py
index c1c26b8..1061bce 100644
--- a/models.py
+++ b/models.py
@@ -1,6 +1,12 @@
+from typing import Any
+
from PyQt5.QtCore import QAbstractListModel, Qt, QModelIndex
-from qgis.core import QgsMessageLog, Qgis
+from PyQt5.QtGui import QStandardItemModel, QStandardItem
+
+from qgis.core import QgsMessageLog, Qgis, QgsProject, QgsFeatureSource, QgsApplication
+from .helpers import hasLayerException
+from .settings import SUPPORTED_PROVIDERS
from .filters import FilterManager
@@ -29,3 +35,27 @@ def removeRows(self, row: int, count: int = 1, parent: QModelIndex = ...) -> boo
self.filters = self.filters[:row] + self.tableData[row + count:]
self.endRemoveRows()
return True
+
+
+class LayerModel(QStandardItemModel):
+ def __init__(self, parent=None):
+ super(LayerModel, self).__init__(parent)
+
+ for layer in [layerNode.layer() for layerNode in QgsProject.instance().layerTreeRoot().findLayers()]:
+ item = QStandardItem(layer.name())
+ item.setData(layer, role=DataRole)
+ item.setFlags(Qt.ItemIsUserCheckable)
+ if layer.providerType() in SUPPORTED_PROVIDERS:
+ item.setEnabled(True)
+ if layer.dataProvider().hasSpatialIndex() == QgsFeatureSource.SpatialIndexNotPresent:
+ item.setToolTip(self.tr('Layer has no spatial index'))
+ item.setIcon(QgsApplication.getThemeIcon('/mIconWarning.svg'))
+ else:
+ item.setEnabled(False)
+ item.setToolTip(self.tr('Layer type is not supported'))
+ if hasLayerException(layer):
+ item.setCheckState(Qt.Checked)
+ else:
+ item.setCheckState(Qt.Unchecked)
+ self.appendRow(item)
+
diff --git a/settings.py b/settings.py
index ceb0402..1bd2767 100644
--- a/settings.py
+++ b/settings.py
@@ -1,7 +1,11 @@
GROUP = 'MapFilter' # The section name for filter definitions stored in QSettings
+LAYER_EXCEPTION_VARIABLE = 'MapFilterException'
SPLIT_STRING = '#!#!#' # String used to split filter definition parameters in QSettings
# The filter string might contain user-specific parts so we surround *our* filter
# string with text markers
FILTER_COMMENT_START = '/* MapFilter Plugin Start */'
FILTER_COMMENT_STOP = '/* MapFilter Plugin Stop */'
+
+# The QGIS Provider Types that can be filtered by the plugin
+SUPPORTED_PROVIDERS = ['postgres']
diff --git a/widgets.py b/widgets.py
index 3b1c7c8..9439ae7 100644
--- a/widgets.py
+++ b/widgets.py
@@ -15,14 +15,23 @@
QDialog,
QVBoxLayout,
QSizePolicy,
- QDialogButtonBox, QListWidget, QMenu, QActionGroup, QLabel, QFrame, QInputDialog
+ QDialogButtonBox, QListWidget, QMenu, QActionGroup, QLabel, QFrame, QInputDialog, QTreeView
)
from qgis.gui import QgsExtentWidget, QgsRubberBand
-from qgis.core import QgsApplication, QgsGeometry, QgsProject, QgsCoordinateReferenceSystem, QgsCoordinateTransform, QgsWkbTypes
+from qgis.core import (
+ QgsApplication,
+ QgsGeometry,
+ QgsProject,
+ QgsCoordinateReferenceSystem,
+ QgsCoordinateTransform,
+ QgsVectorLayer,
+ QgsWkbTypes,
+)
from qgis.utils import iface
+from .helpers import removeFilterFromLayer, setLayerException, hasLayerException, addFilterToLayer
from .controller import FilterController
-from .models import FilterModel, DataRole
+from .models import FilterModel, LayerModel, DataRole
from .filters import Predicate, FilterManager, FilterDefinition
@@ -61,6 +70,49 @@ def accept(self) -> None:
super().accept()
+class LayerExceptionsDialog(QDialog):
+ def __init__(self, controller: FilterController, parent: Optional[QWidget] = None) -> None:
+ super().__init__(parent=parent)
+ self.controller = controller
+ self.setObjectName("mLayerExceptionsDialog")
+ self.setWindowTitle(self.tr("Exclude layers from filter"))
+ self.setupUi()
+ self.listView.setModel(LayerModel())
+ self.adjustSize()
+
+ def setupUi(self):
+ sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.sizePolicy().hasHeightForWidth())
+ self.setSizePolicy(sizePolicy)
+ self.verticalLayout = QVBoxLayout(self)
+ self.listView = QTreeView(self)
+ self.listView.header().hide()
+ self.verticalLayout.addWidget(self.listView)
+ self.buttonBox = QDialogButtonBox(self)
+ self.buttonBox.setOrientation(Qt.Horizontal)
+ self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel | QDialogButtonBox.Ok)
+ self.verticalLayout.addWidget(self.buttonBox)
+ self.buttonBox.accepted.connect(self.accept)
+ self.buttonBox.rejected.connect(self.reject)
+
+ def accept(self) -> None:
+ model = self.listView.model()
+ for index in range(model.rowCount()):
+ item = model.item(index)
+ layer = item.data()
+ self.setExceptionForLayer(layer, bool(item.checkState() == Qt.Checked))
+ super().accept()
+
+ def setExceptionForLayer(self, layer: QgsVectorLayer, exception: bool) -> None:
+ if exception:
+ removeFilterFromLayer(layer)
+ if not exception and hasLayerException(layer) and self.controller.toolbarIsActive:
+ addFilterToLayer(layer, self.controller.currentFilter)
+ setLayerException(layer, exception)
+
+
FORM_CLASS, _ = uic.loadUiType(os.path.join(os.path.dirname(__file__), 'ui', 'named_filters_dialog.ui'))
@@ -220,6 +272,11 @@ def setupUi(self):
self.predicateButton.setIconSize(self.iconSize())
self.addWidget(self.predicateButton)
+ self.layerExceptionsAction = QAction(self)
+ self.layerExceptionsAction.setIcon(QgsApplication.getThemeIcon('/mIconLayerTree.svg'))
+ self.layerExceptionsAction.setToolTip(self.tr('Exclude layers from filter'))
+ self.addAction(self.layerExceptionsAction)
+
self.saveCurrentFilterAction = QAction(self)
self.saveCurrentFilterAction.setIcon(QgsApplication.getThemeIcon('/mActionFileSave.svg'))
self.saveCurrentFilterAction.setToolTip(self.tr('Save current filter'))
@@ -233,6 +290,7 @@ def setupUi(self):
def setupConnections(self):
self.toggleFilterAction.toggled.connect(self.onToggled)
self.filterFromExtentAction.triggered.connect(self.startFilterFromExtentDialog)
+ self.layerExceptionsAction.triggered.connect(self.startLayerExceptionsDialog)
self.manageFiltersAction.triggered.connect(self.startManageFiltersDialog)
self.saveCurrentFilterAction.triggered.connect(self.controller.saveCurrentFilter)
self.predicateButton.predicateChanged.connect(self.controller.setFilterPredicate)
@@ -270,6 +328,10 @@ def startFilterFromExtentDialog(self):
dlg = ExtentDialog(self.controller, parent=self)
dlg.show()
+ def startLayerExceptionsDialog(self):
+ dlg = LayerExceptionsDialog(self.controller, parent=self)
+ dlg.exec()
+
def startManageFiltersDialog(self):
dlg = ManageFiltersDialog(self.controller, parent=self)
dlg.exec()