Skip to content
Snippets Groups Projects
layerproperties.py 11.8 KiB
Newer Older

import sys, os, re, enum
from qgis.core import *
from qgis.gui import *
from qgis.PyQt.QtCore import *
from qgis.PyQt.QtGui import *
from qgis.PyQt.QtWidgets import *

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']
    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:
            return self.mColumnNames[col]
        elif orientation == Qt.Vertical and role == Qt.DisplayRole:
            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):

    class ConfigInfo(QStandardItem):
        """
        Describes a QgsEditorWidgetFactory configuration.
        """
        def __init__(self, key:str, factory:QgsEditorWidgetFactory, configWidget:QgsEditorConfigWidget):
            super(FieldConfigEditorWidget.ConfigInfo, self).__init__()

            assert isinstance(key, str)
            assert isinstance(factory, QgsEditorWidgetFactory)
            assert isinstance(configWidget, QgsEditorConfigWidget)
            self.mKey = key
            self.mFactory = factory
            self.mConfigWidget = configWidget
            self.setText(factory.name())
            self.setToolTip(factory.name())
            self.mInitialConfig = dict(configWidget.config())


        def resetConfig(self):
            """
            Resets the widget to its initial values
            """
            self.mConfigWidget.setConfig(dict(self.mInitialConfig))

        def factoryKey(self)->str:
            """
            Returns the QgsEditorWidgetFactory key, e.g. "CheckBox"
            :return: str
            """
            return self.mKey

        def factoryName(self)->str:
            """
            Returns the QgsEditorWidgetFactory name, e.g. "Checkbox"
            :return: str
            """
            return self.factory().name()

        def config(self)->dict:
            """
            Returns the config dictionary
            :return: dict
            """
            return self.mConfigWidget.config()

        def configWidget(self)->QgsEditorConfigWidget:
            """
            Returns the QgsEditorConfigWidget
            :return: QgsEditorConfigWidget
            """
            return self.mConfigWidget

        def factory(self)->QgsEditorWidgetFactory:
            """
            Returns the QgsEditorWidgetFactory
            :return: QgsEditorWidgetFactory
            """
            return self.mFactory

        def editorWidgetSetup(self)->QgsEditorWidgetSetup:
            """
            Creates a QgsEditorWidgetSetup
            :return: QgsEditorWidgetSetup
            """
            return QgsEditorWidgetSetup(self.factoryKey(), self.config())


    sigChanged = pyqtSignal(object)

    def __init__(self, parent, layer:QgsVectorLayer, index:int):
        super(FieldConfigEditorWidget, self).__init__(parent)

        self.setLayout(QVBoxLayout())

        assert isinstance(layer, QgsVectorLayer)
        assert isinstance(index, int)

        self.mLayer = layer
        self.mField = layer.fields().at(index)
        assert isinstance(self.mField, QgsField)
        self.mFieldIndex = index

        self.mFieldNameLabel = QLabel(parent)
        self.mFieldNameLabel.setText(self.mField.name())

        self.layout().addWidget(self.mFieldNameLabel)

        self.gbWidgetType = QgsCollapsibleGroupBox(self)
        self.gbWidgetType.setTitle('Widget Type')
        self.gbWidgetType.setLayout(QVBoxLayout())
        self.cbWidgetType = QComboBox(self.gbWidgetType)

        self.stackedWidget = QStackedWidget(self.gbWidgetType)
        self.gbWidgetType.layout().addWidget(self.cbWidgetType)
        self.gbWidgetType.layout().addWidget(self.stackedWidget)




        currentSetup = self.mLayer.editorWidgetSetup(self.mFieldIndex)
        self.mInitialConf = currentSetup.config()
        refkey = currentSetup.type()
        if refkey == '':
            refkey = QgsGui.editorWidgetRegistry().findBest(self.mLayer, self.mField.name()).type()

        self.mItemModel = QStandardItemModel(parent=self.cbWidgetType)

        iCurrent = -1
        i = 0
        factories = QgsGui.editorWidgetRegistry().factories()
        for key, fac in factories.items():
            assert isinstance(key, str)
            assert isinstance(fac, QgsEditorWidgetFactory)
            score = fac.fieldScore(self.mLayer, self.mFieldIndex)
            configWidget = fac.configWidget(self.mLayer, self.mFieldIndex, self.stackedWidget)

            if isinstance(configWidget, QgsEditorConfigWidget):
                configWidget.changed.connect(lambda :self.sigChanged.emit(self))
                self.stackedWidget.addWidget(configWidget)
                confItem = FieldConfigEditorWidget.ConfigInfo(key, fac, configWidget)
                if key == refkey:
                    iCurrent = i
                confItem.setEnabled(score > 0)
                confItem.setData(self, role=Qt.UserRole)
                self.mItemModel.appendRow(confItem)

                i += 1

        self.cbWidgetType.setModel(self.mItemModel)
        self.cbWidgetType.currentIndexChanged.connect(self.updateConfigWidget)

        self.layout().addWidget(self.gbWidgetType)
        self.layout().addStretch()
        self.cbWidgetType.setCurrentIndex(iCurrent)


        conf = self.currentFieldConfig()
        self.mInitialFactoryKey = conf.factoryKey()
        self.mInitialConf = conf.config()



    def setFactory(self, factoryKey:str):
        """
        Shows the QgsEditorConfigWidget of QgsEditorWidgetFactory `factoryKey`
        :param factoryKey: str
        """
        for i in range(self.mItemModel.rowCount()):
            confItem = self.mItemModel.item(i)
            assert isinstance(confItem, FieldConfigEditorWidget.ConfigInfo)
            if confItem.factoryKey() == factoryKey:
                self.cbWidgetType.setCurrentIndex(i)
                break


    def changed(self)->bool:
        """
        Returns True if the QgsEditorWidgetFactory or its configuration has been changed
        :return: bool
        """
        w = self.currentEditorConfigWidget()
        assert isinstance(w, QgsEditorConfigWidget)

        recentConfigInfo = self.currentFieldConfig()

        if self.mInitialFactoryKey != recentConfigInfo.factoryKey():
            return True
        elif self.mInitialConf != recentConfigInfo.config():
            return True

        return False

    def apply(self):
        """
        Applies the
        :return:
        """
        if self.changed():
            configInfo = self.currentFieldConfig()
            self.mInitialConf = configInfo.config()
            self.mInitialFactoryKey = configInfo.factoryKey()
            setup = QgsEditorWidgetSetup(self.mInitialFactoryKey, self.mInitialConf)
            self.mLayer.setEditorWidgetSetup(self.mFieldIndex, setup)

    def reset(self):
        """
        Resets the widget to its initial status
        """
        if self.changed():

            self.setFactory(self.mInitialFactoryKey)
            self.currentEditorConfigWidget().setConfig(self.mInitialConf)

    def currentFieldConfig(self)->ConfigInfo:
        i = self.cbWidgetType.currentIndex()
        return self.mItemModel.item(i)

    def currentEditorConfigWidget(self)->QgsEditorConfigWidget:
        return self.currentFieldConfig().configWidget()


    def updateConfigWidget(self, index):
        self.stackedWidget.setCurrentIndex(index)
        fieldConfig = self.currentFieldConfig()
        assert isinstance(fieldConfig, FieldConfigEditorWidget.ConfigInfo)

        self.sigChanged.emit(self)


class LayerFieldConfigEditorWidget(QWidget, loadUI('layerfieldconfigeditorwidget.ui')):
    """
    A widget to set QgsVetorLayer field settings
    """
    def __init__(self, parent, *args, **kwds):
        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(
            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 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):

        sw = self.stackedWidget
        assert isinstance(sw, QStackedWidget)

        for i in range(sw.count()):
            w = sw.widget(i)
            assert isinstance(w, FieldConfigEditorWidget)
            w.reset()
        self.onSettingsChanged()

    def onApply(self):
        """
        Applies all changes to the QgsVectorLayer
        :return:
        """

        sw = self.stackedWidget
        assert isinstance(sw, QStackedWidget)

        for i in range(sw.count()):
            w = sw.widget(i)
            assert isinstance(w, FieldConfigEditorWidget)
            w.apply()
        self.onSettingsChanged()


    def setLayer(self, layer:QgsVectorLayer):
        """
        Sets the QgsVectorLayer
        :param layer:
        """
        self.mFieldModel.setLayer(layer)
        self.updateFieldWidgets()

    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:
            sw.removeWidget(sw.widget(0))

        lyr = self.layer()
        if isinstance(lyr, QgsVectorLayer):
            for i in range(lyr.fields().count()):
                w = FieldConfigEditorWidget(sw, lyr, i)
                w.sigChanged.connect(self.onSettingsChanged)
                sw.addWidget(w)

        self.onSettingsChanged()

    def onSettingsChanged(self):
        """
        Enables/disables buttons
        :return:
        """
        b = False
        for i in range(self.stackedWidget.count()):
            w = self.stackedWidget.widget(i)
            assert isinstance(w, FieldConfigEditorWidget)
            if w.changed():
                b = True
                break

        self.btnReset.setEnabled(b)
        self.btnApply.setEnabled(b)