From 0273af2ea09b3f38a08950c18610954794e27423 Mon Sep 17 00:00:00 2001 From: Benjamin Jakimow <benjamin.jakimow@geo.hu-berlin.de> Date: Thu, 24 Jan 2019 06:52:50 +0100 Subject: [PATCH] layerproperties.py refactoring, imporvied models labeling.py refactoring, improved return values for QgsVectorFile editing --- tests/test_labeling.py | 41 ++---------- tests/test_layerproperties.py | 54 +++++++++++++--- timeseriesviewer/labeling.py | 65 +++++++++++-------- timeseriesviewer/layerproperties.py | 99 ++++++++++++++++++++--------- timeseriesviewer/ui/labelingdock.ui | 84 ++---------------------- 5 files changed, 163 insertions(+), 180 deletions(-) diff --git a/tests/test_labeling.py b/tests/test_labeling.py index b59afe1c..fea650b2 100644 --- a/tests/test_labeling.py +++ b/tests/test_labeling.py @@ -29,8 +29,9 @@ resourceDir = os.path.join(DIR_REPO, 'qgisresources') QGIS_APP = initQgisApplication(qgisResourceDir=resourceDir) SHOW_GUI = True - -QgsGui.editorWidgetRegistry().initEditors() +reg = QgsGui.editorWidgetRegistry() +if len(reg.factories()) == 0: + reg.initEditors() class testclassLabelingTest(unittest.TestCase): @@ -257,35 +258,6 @@ class testclassLabelingTest(unittest.TestCase): CONFKEY_CLASSIFICATIONSCHEME: classScheme2})) return classScheme1, classScheme2 - def test_FieldConfigEditorWidget(self): - - reg = QgsGui.editorWidgetRegistry() - if len(reg.factories()) == 0: - reg.initEditors() - - registerLabelShortcutEditorWidget() - - lyr = self.createVectorLayer() - - w = FieldConfigEditorWidget(None, lyr, 3) - w.show() - - conf1 = w.currentFieldConfig() - self.assertEqual(conf1.config(), w.mInitialConf) - self.assertEqual(conf1.factoryKey(), w.mInitialFactoryKey) - w.setFactory('CheckBox') - conf2 = w.currentFieldConfig() - self.assertTrue(conf1 != conf2) - self.assertTrue(w.hasChanged()) - w.setFactory(conf1.factoryKey()) - - conf3 = w.currentFieldConfig() - self.assertEqual(conf3.factoryKey(), conf1.factoryKey()) - - self.assertFalse(w.hasChanged()) - - if SHOW_GUI: - QGIS_APP.exec_() def test_LabelingDock(self): @@ -299,9 +271,7 @@ class testclassLabelingTest(unittest.TestCase): - reg = QgsGui.editorWidgetRegistry() - if len(reg.factories()) == 0: - reg.initEditors() + registerLabelShortcutEditorWidget() @@ -345,9 +315,6 @@ class testclassLabelingTest(unittest.TestCase): self.assertTrue(dock.mVectorLayerComboBox.currentLayer() == lyr) - - - if SHOW_GUI: dock.show() QGIS_APP.exec_() diff --git a/tests/test_layerproperties.py b/tests/test_layerproperties.py index e3ae7e4f..49b56a8b 100644 --- a/tests/test_layerproperties.py +++ b/tests/test_layerproperties.py @@ -1,12 +1,13 @@ # noinspection PyPep8Naming import os, sys, re -from timeseriesviewer.tests import initQgisApplication, testRasterFiles +from qgis.core import * +from qgis.gui import * +from timeseriesviewer.tests import initQgisApplication, testRasterFiles, TestObjects import unittest, tempfile from timeseriesviewer.layerproperties import * from timeseriesviewer import DIR_REPO -from timeseriesviewer.mapcanvas import MapCanvas -from timeseriesviewer.tests import TestObjects + resourceDir = os.path.join(DIR_REPO, 'qgisresources') QGIS_APP = initQgisApplication(qgisResourceDir=resourceDir) SHOW_GUI = True @@ -15,7 +16,7 @@ SHOW_GUI = True QgsGui.editorWidgetRegistry().initEditors() -class testclassLabelingTest(unittest.TestCase): +class TestLayerproperties(unittest.TestCase): def createVectorLayer(self) -> QgsVectorLayer: lyr = TestObjects.createVectorLayer() @@ -35,9 +36,7 @@ class testclassLabelingTest(unittest.TestCase): return lyr - - - def test_fieldModel(self): + def test_LabelFieldModel(self): lyr = self.createVectorLayer() @@ -45,6 +44,12 @@ class testclassLabelingTest(unittest.TestCase): fm = LabelFieldModel(w) fm.setLayer(lyr) + self.assertEqual(fm.layer(), lyr) + + newName = 'Layer Fields' + fm.setHeaderData(0, Qt.Horizontal, newName, role=Qt.EditRole) + self.assertEqual(newName, fm.headerData(0, Qt.Horizontal)) + w.setModel(fm) w.show() @@ -52,6 +57,33 @@ class testclassLabelingTest(unittest.TestCase): if SHOW_GUI: QGIS_APP.exec_() + def test_FieldConfigEditorWidget(self): + + + lyr = self.createVectorLayer() + + w = FieldConfigEditorWidget(None, lyr, 3) + self.assertIsInstance(w, FieldConfigEditorWidget) + w.show() + + conf1 = w.currentFieldConfig() + self.assertEqual(conf1.config(), w.mInitialConf) + self.assertEqual(conf1.factoryKey(), w.mInitialFactoryKey) + w.setFactory('CheckBox') + conf2 = w.currentFieldConfig() + + self.assertTrue(conf1 != conf2) + self.assertTrue(w.changed()) + w.setFactory(conf1.factoryKey()) + + conf3 = w.currentFieldConfig() + self.assertEqual(conf3.factoryKey(), conf1.factoryKey()) + + self.assertFalse(w.changed()) + + if SHOW_GUI: + QGIS_APP.exec_() + def test_LayerFieldConfigEditorWidget(self): @@ -64,8 +96,14 @@ class testclassLabelingTest(unittest.TestCase): self.assertTrue(w.layer() == None) w.setLayer(lyr) self.assertTrue(w.layer() == lyr) + w.setLayer(None) + self.assertTrue(w.layer() == None) if SHOW_GUI: - QGIS_APP.exec_() \ No newline at end of file + QGIS_APP.exec_() + +if __name__ == "__main__": + SHOW_GUI = False + unittest.main() diff --git a/timeseriesviewer/labeling.py b/timeseriesviewer/labeling.py index 4e4aeb90..9fd79406 100644 --- a/timeseriesviewer/labeling.py +++ b/timeseriesviewer/labeling.py @@ -12,7 +12,7 @@ from timeseriesviewer.classification.classificationscheme \ import ClassificationSchemeWidget, ClassificationScheme, ClassInfo, ClassificationSchemeComboBox from timeseriesviewer.timeseries import TimeSeriesDatum - +from timeseriesviewer.layerproperties import * #the QgsProject(s) and QgsMapLayerStore(s) to search for QgsVectorLayers MAP_LAYER_STORES = [QgsProject.instance()] @@ -68,7 +68,7 @@ def layerClassSchemes(layer:QgsVectorLayer)->list: setup = layer.editorWidgetSetup(i) assert isinstance(setup, QgsEditorWidgetSetup) if setup.type() == EDITOR_WIDGET_REGISTRY_KEY: - results.append(layer) + schemes.append(layer) break return schemes @@ -490,10 +490,6 @@ class LabelingDock(QgsDockWidget, loadUI('labelingdock.ui')): #self.mLabelAttributeModel = LabelAttributeTableModel() - self.mLayerFieldModel = LabelFieldModel(self) - self.configTableView.setModel(self.mLayerFieldModel) - self.configSelectionModel = self.configTableView.selectionModel() - self.configSelectionModel.currentRowChanged.connect(lambda idx: self.stackedFieldConfigs.setCurrentIndex(idx.row())) self.mVectorLayerComboBox.setAllowEmptyLayer(True) allowed = ['DB2', 'WFS', 'arcgisfeatureserver', 'delimitedtext', 'memory', 'mssql', 'ogr', 'oracle', 'ows', @@ -503,8 +499,7 @@ class LabelingDock(QgsDockWidget, loadUI('labelingdock.ui')): self.mVectorLayerComboBox.setExcludedProviders(excluded) self.mVectorLayerComboBox.currentIndexChanged.connect(self.onVectorLayerChanged) - self.mLastConf = {} - self.mCurrentConfigWidget = None + self.mDualView = None self.mCanvas = QgsMapCanvas(self) self.mCanvas.setVisible(False) @@ -585,22 +580,9 @@ class LabelingDock(QgsDockWidget, loadUI('labelingdock.ui')): def onVectorLayerChanged(self): lyr = self.currentVectorSource() - self.mLayerFieldModel.setLayer(lyr) - - # remove old config widget - while self.stackedFieldConfigs.count() > 0: - w = self.stackedFieldConfigs.widget(0) - self.stackedFieldConfigs.removeWidget(w) - w.setParent(None) - btnApply = self.buttonBoxEditorWidget.button(QDialogButtonBox.Apply) - btnApply.setEnabled(False) - + self.layerFieldConfigEditorWidget.setLayer(lyr) # add a config widget for each QgsField if isinstance(lyr, QgsVectorLayer): - for i in range(lyr.fields().count()): - w = FieldConfigEditorWidget(self.stackedFieldConfigs, lyr, i) - w.sigChanged.connect(self.onCheckApply) - self.stackedFieldConfigs.addWidget(w) lyr.editingStarted.connect(lambda : self.actionToggleEditing.setChecked(True)) lyr.editingStopped.connect(lambda: self.actionToggleEditing.setChecked(False)) @@ -682,9 +664,6 @@ class LabelingDock(QgsDockWidget, loadUI('labelingdock.ui')): self.btnToggleEditing.setDefaultAction(self.actionToggleEditing) self.btnAddOgrLayer.setDefaultAction(self.actionAddOgrLayer) - # QgsVectorLayer settings view buttons - self.buttonBoxEditorWidget.button(QDialogButtonBox.Apply).clicked.connect(self.onApply) - self.buttonBoxEditorWidget.button(QDialogButtonBox.Reset).clicked.connect(self.onReset) # bottom button bar self.btnAttributeView.setDefaultAction(self.actionSwitchToTableView) @@ -821,6 +800,11 @@ class LabelShortcutEditorWidgetWrapper(QgsEditorWidgetWrapper): return self.config(CONFKEY_CLASSIFICATIONSCHEME) def createWidget(self, parent: QWidget): + """ + Create the data input widget + :param parent: QWidget + :return: ClassificationSchemeComboBox | default widget + """ #log('createWidget') labelType = self.configLabelType() if labelType == LabelShortcutType.Classification: @@ -849,36 +833,58 @@ class LabelShortcutEditorWidgetWrapper(QgsEditorWidgetWrapper): s = "" def valid(self, *args, **kwargs)->bool: + """ + Returns True if a valid editor widget exists + :param args: + :param kwargs: + :return: bool + """ return isinstance(self.mEditor, (ClassificationSchemeComboBox, QLineEdit)) def value(self, *args, **kwargs): + """ + Reuturns the value + :param args: + :param kwargs: + :return: + """ typeCode = self.field().type() if isinstance(self.mEditor, ClassificationSchemeComboBox): classInfo = self.mEditor.currentClassInfo() + if isinstance(classInfo, ClassInfo): + if typeCode == QVariant.String: return classInfo.name() + if typeCode in [QVariant.Int, QVariant.Double]: return classInfo.label() + elif isinstance(self.mEditor, QLineEdit): txt = self.mEditor.text() + if len(txt) == '': return self.defaultValue() + if typeCode == QVariant.String: return txt try: + txt = txt.strip() + if typeCode == QVariant.Int: return int(txt) + if typeCode == QVariant.Double: return float(txt) + except Exception as e: - return self.defaultValue() + return txt return self.mLineEdit.text() - return None + return self.defaultValue() def setEnabled(self, enabled:bool): @@ -895,7 +901,10 @@ class LabelShortcutEditorWidgetWrapper(QgsEditorWidgetWrapper): i = cs.classIndexFromValue(value) self.mEditor.setCurrentIndex(max(i, 0)) elif isinstance(self.mEditor, QLineEdit): - self.mEditor.setText(str(value)) + if value in [QVariant(), None]: + self.mEditor.setText(None) + else: + self.mEditor.setText(str(value)) class LabelShortcutWidgetFactory(QgsEditorWidgetFactory): diff --git a/timeseriesviewer/layerproperties.py b/timeseriesviewer/layerproperties.py index 41621b5d..8134eb30 100644 --- a/timeseriesviewer/layerproperties.py +++ b/timeseriesviewer/layerproperties.py @@ -5,23 +5,31 @@ from qgis.gui import * from qgis.PyQt.QtCore import * from qgis.PyQt.QtGui import * from qgis.PyQt.QtWidgets import * -from osgeo import gdal -from timeseriesviewer.utils import loadUI, qgisInstance -from timeseriesviewer.classification.classificationscheme \ - import ClassificationSchemeWidget, ClassificationScheme, ClassInfo, ClassificationSchemeComboBox - -from timeseriesviewer.timeseries import TimeSeriesDatum +from timeseriesviewer.utils import loadUI class LabelFieldModel(QgsFieldModel): - + """ + A model to show the QgsFields of an QgsVectorLayer. + Inherits QgsFieldModel and allows to change the name of the 1st column. + """ def __init__(self, parent): + """ + Constructor + :param parent: + """ super(LabelFieldModel, self).__init__(parent) - self.mColumnNames = ['Fields', 'Type'] + self.mColumnNames = ['Fields'] - - def headerData(self, col, orientation, role): + def headerData(self, col, orientation, role=Qt.DisplayRole): + """ + Returns header data + :param col: int + :param orientation: Qt.Horizontal | Qt.Vertical + :param role: + :return: value + """ if Qt is None: return None if orientation == Qt.Horizontal and role == Qt.DisplayRole: @@ -30,7 +38,24 @@ class LabelFieldModel(QgsFieldModel): return col return None + def setHeaderData(self, col, orientation, value, role=Qt.EditRole): + """ + Sets the header data. + :param col: int + :param orientation: + :param value: any + :param role: + """ + result = False + + if role == Qt.EditRole: + if orientation == Qt.Horizontal and col < len(self.mColumnNames) and isinstance(value, str): + self.mColumnNames[col] = value + result = True + if result == True: + self.headerDataChanged.emit(orientation, col, col) + return result class FieldConfigEditorWidget(QWidget): @@ -251,20 +276,27 @@ class LayerFieldConfigEditorWidget(QWidget, loadUI('layerfieldconfigeditorwidget super(LayerFieldConfigEditorWidget, self).__init__(parent, *args, **kwds) self.setupUi(self) + self.scrollArea.resizeEvent = self.onScrollAreaResize self.mFieldModel = LabelFieldModel(self) self.treeView.setModel(self.mFieldModel) - self.treeView.selectionModel().currentRowChanged.connect(self.onSelectionChanged) + self.treeView.selectionModel().currentRowChanged.connect( + lambda current, _ : self.stackedWidget.setCurrentIndex(current.row()) + ) + self.btnApply = self.buttonBox.button(QDialogButtonBox.Apply) self.btnReset = self.buttonBox.button(QDialogButtonBox.Reset) self.btnApply.clicked.connect(self.onApply) self.btnReset.clicked.connect(self.onReset) - def onSelectionChanged(self, current:QModelIndex, previous:QModelIndex): - sw = self.stackedWidget - assert isinstance(sw, QStackedWidget) - sw.setCurrentWidget(sw.widget(current.row())) - s = "" - pass + + def onScrollAreaResize(self, resizeEvent:QResizeEvent): + """ + Forces the stackedWidget's width to fit into the scrollAreas viewport + :param resizeEvent: QResizeEvent + """ + assert isinstance(resizeEvent, QResizeEvent) + self.stackedWidget.setMaximumWidth(resizeEvent.size().width()) + s ="" def onReset(self): @@ -278,6 +310,10 @@ class LayerFieldConfigEditorWidget(QWidget, loadUI('layerfieldconfigeditorwidget self.onSettingsChanged() def onApply(self): + """ + Applies all changes to the QgsVectorLayer + :return: + """ sw = self.stackedWidget assert isinstance(sw, QStackedWidget) @@ -291,13 +327,24 @@ class LayerFieldConfigEditorWidget(QWidget, loadUI('layerfieldconfigeditorwidget def setLayer(self, layer:QgsVectorLayer): """ + Sets the QgsVectorLayer :param layer: """ self.mFieldModel.setLayer(layer) self.updateFieldWidgets() - def updateFieldWidgets(self): + def layer(self)->QgsVectorLayer: + """ + Returns the current QgsVectorLayer + :return: + """ + return self.mFieldModel.layer() + def updateFieldWidgets(self): + """ + Empties the stackedWidget and populates it with a FieldConfigEditor + for each QgsVectorLayer field. + """ sw = self.stackedWidget assert isinstance(sw, QStackedWidget) while sw.count() > 0: @@ -312,10 +359,11 @@ class LayerFieldConfigEditorWidget(QWidget, loadUI('layerfieldconfigeditorwidget self.onSettingsChanged() - - def onSettingsChanged(self): - + """ + Enables/disables buttons + :return: + """ b = False for i in range(self.stackedWidget.count()): w = self.stackedWidget.widget(i) @@ -324,15 +372,6 @@ class LayerFieldConfigEditorWidget(QWidget, loadUI('layerfieldconfigeditorwidget b = True break - self.btnReset.setEnabled(b) self.btnApply.setEnabled(b) - - def layer(self)->QgsVectorLayer: - """ - Returns the current QgsVectorLayer - :return: - """ - return self.mFieldModel.layer() - diff --git a/timeseriesviewer/ui/labelingdock.ui b/timeseriesviewer/ui/labelingdock.ui index d904396b..91c23813 100644 --- a/timeseriesviewer/ui/labelingdock.ui +++ b/timeseriesviewer/ui/labelingdock.ui @@ -227,88 +227,12 @@ <number>0</number> </property> <item> - <widget class="QTableView" name="configTableView"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="maximumSize"> - <size> - <width>150</width> - <height>16777215</height> - </size> - </property> - <property name="selectionMode"> - <enum>QAbstractItemView::SingleSelection</enum> - </property> - <property name="selectionBehavior"> - <enum>QAbstractItemView::SelectRows</enum> - </property> - </widget> - </item> - <item> - <widget class="QScrollArea" name="scrollAreaWidgetTypes"> - <property name="horizontalScrollBarPolicy"> - <enum>Qt::ScrollBarAsNeeded</enum> - </property> - <property name="sizeAdjustPolicy"> - <enum>QAbstractScrollArea::AdjustIgnored</enum> - </property> - <property name="widgetResizable"> - <bool>true</bool> - </property> - <widget class="QWidget" name="scrollAreaWidgetTypesContents"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>392</width> - <height>239</height> - </rect> - </property> - <layout class="QVBoxLayout" name="verticalLayout_3"> - <property name="spacing"> - <number>2</number> - </property> - <property name="leftMargin"> - <number>0</number> - </property> - <property name="topMargin"> - <number>0</number> - </property> - <property name="rightMargin"> - <number>0</number> - </property> - <property name="bottomMargin"> - <number>0</number> - </property> - <item> - <widget class="QStackedWidget" name="stackedFieldConfigs"> - <property name="autoFillBackground"> - <bool>false</bool> - </property> - </widget> - </item> - </layout> - </widget> - </widget> + <widget class="LayerFieldConfigEditorWidget" name="layerFieldConfigEditorWidget" native="true"/> </item> </layout> </widget> </widget> </item> - <item> - <widget class="QDialogButtonBox" name="buttonBoxEditorWidget"> - <property name="standardButtons"> - <set>QDialogButtonBox::Apply</set> - </property> - <property name="centerButtons"> - <bool>false</bool> - </property> - </widget> - </item> <item> <widget class="QWidget" name="btnBar2" native="true"> <layout class="QHBoxLayout" name="btnBarBottom"> @@ -481,6 +405,12 @@ <extends>QComboBox</extends> <header>qgsmaplayercombobox.h</header> </customwidget> + <customwidget> + <class>LayerFieldConfigEditorWidget</class> + <extends>QWidget</extends> + <header>timeseriesviewer.layerproperties</header> + <container>1</container> + </customwidget> </customwidgets> <resources> <include location="resources.qrc"/> -- GitLab