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