From e17a6ba381cefec1f7ea50b8e348cb986a7ea14c Mon Sep 17 00:00:00 2001
From: "benjamin.jakimow@geo.hu-berlin.de" <q8DTkxUg-BB>
Date: Mon, 3 Apr 2017 18:06:42 +0200
Subject: [PATCH] getTextColorWithContrast(QColor)
 ClassInfo(QObject).setLabel(label) ClassInfo(QObject).__str__() changes to
 ClassificationScheme(QObject) (clear, saveToRaster)
 ClassificationSchemeTableModel, ClassificationWidgetDelegates

---
 timeseriesviewer/classificationscheme.py | 313 ++++++++++++++++++++---
 1 file changed, 272 insertions(+), 41 deletions(-)

diff --git a/timeseriesviewer/classificationscheme.py b/timeseriesviewer/classificationscheme.py
index 5596e275..0359a007 100644
--- a/timeseriesviewer/classificationscheme.py
+++ b/timeseriesviewer/classificationscheme.py
@@ -14,27 +14,47 @@ from timeseriesviewer.ui.widgets import loadUIFormClass
 
 load = lambda p : loadUIFormClass(jp(DIR_UI,p))
 
-class ClassInfo(QObject):
 
-    def __init__(self, name=None, color=None):
-        self.mName = ''
+def getTextColorWithContrast(c):
+    assert isinstance(c, QColor)
+    if c.lightness() < 0.5:
+        return QColor('white')
+    else:
+        return QColor('black')
+
+
+class ClassInfo(QObject):
+    sigSettingsChanged = pyqtSignal()
+    def __init__(self, label=0, name='unclassified', color=None):
+        super(ClassInfo, self).__init__()
+        self.mName = name
+        self.mLabel = label
         self.mColor = QColor('black')
-        if name:
-            self.setName(name)
         if color:
             self.setColor(color)
 
+    def setLabel(self, label):
+        assert isinstance(label, int)
+        assert label >= 0
+        self.mLabel = label
+        self.sigSettingsChanged.emit()
+
     def setColor(self, color):
         assert isinstance(color, QColor)
         self.mColor = color
+        self.sigSettingsChanged.emit()
 
     def setName(self, name):
         assert isinstance(name, str)
         self.mName = name
+        self.sigSettingsChanged.emit()
 
     def clone(self):
         return ClassInfo(name=self.mName, color=self.mColor)
 
+    def __str__(self):
+        return '{} "{}"'.format(self.mLabel,self.mName)
+
 class ClassificationScheme(QObject):
     @staticmethod
     def fromRasterImage(path, bandIndex=None):
@@ -69,14 +89,30 @@ class ClassificationScheme(QObject):
     def fromVectorFile(self, path, fieldClassName='classname', fieldClassColor='classColor'):
         pass
 
-    def clear(self):
-        del self.classes[:]
+    sigClassRemoved = pyqtSignal(ClassInfo)
+    sigClassAdded = pyqtSignal(ClassInfo)
 
     def __init__(self):
         super(ClassificationScheme, self).__init__()
 
         self.classes = []
 
+    def clear(self):
+        removed = self.classes[:]
+        del self.classes[:]
+
+
+    def __getitem__(self, slice):
+        return self.classes[slice]
+
+    def __delitem__(self, slice):
+        classes = self[slice]
+        for c in classes:
+            self.removeClass(c)
+
+    def __contains__(self, item):
+        return item in self.classes
+
     def __len__(self):
         return len(self.classes)
 
@@ -85,36 +121,108 @@ class ClassificationScheme(QObject):
 
     def removeClass(self, c):
         assert c in self.classes
+        self.classes.remove(c)
+        self.sigClassRemoved.emit(c)
 
     def addClass(self, c, index=None):
         assert isinstance(c, ClassInfo)
         if index is None:
             index = len(self.classes)
+        c.setLabel(index)
         self.classes.insert(index, c)
+        self.sigClassAdded.emit(c)
+
+    def saveToRaster(self, path, bandIndex=0):
+
+        ds = gdal.Open(path)
+        assert ds is not None
+        assert ds.RasterCount < bandIndex
+        band = ds.GetRasterBand(bandIndex+1)
+        ct = gdal.ColorTable()
+        cat = []
+        for i, classInfo in enumerate(self.classes):
+            c = classInfo.mColor
+            cat.append(classInfo.mName)
+            assert isinstance(c, QColor)
+            rgba = (c.red(), c.green(), c.blue(), c.alpha())
+            ct.SetColorEntry(i, *rgba)
+
+        band.SetColorTable(ct)
+        band.SetCategoryNames(cat)
+
+        ds = None
+
+
+    def toString(self, sep=';'):
+        lines = [sep.join(['class_value', 'class_name', 'R', 'G', 'B', 'A'])]
+        for classInfo in self.classes:
+            c = classInfo.mColor
+            info = [classInfo.mValue, classInfo.mName, c.red(), c.green(), c.blue(), c.alpha()]
+            info = ['{}'.format(v) for v in info]
+
+            lines.append(sep.join(info))
+        return '\n'.join(lines)
+
+    def saveToCsv(self, path, sep=';'):
+        lines = self.toString(sep=sep)
+        file = open(path, 'w')
+        file.write(lines)
+        file.close()
+
+
 
 
 class ClassificationSchemeTableModel(QAbstractTableModel):
-    columnNames = ['label', 'name', 'color']
 
-    def __init__(self, parent=None):
+
+    def __init__(self, scheme, parent=None):
+        self.cLABEL = 'Label'
+        self.cNAME = 'Name'
+        self.cCOLOR = 'Color'
+        self.columnNames = [self.cLABEL, self.cNAME, self.cCOLOR]
+        assert isinstance(scheme, ClassificationScheme)
         super(ClassificationSchemeTableModel, self).__init__(parent)
 
-        self.scheme = ClassificationScheme()
+        self.valLabel = QIntValidator(0, 99999)
 
-    def loadClassesFromImage(self, path, append=True):
+        self.scheme = scheme
+        #self.scheme.sigClassRemoved.connect(lambda : self.reset())
+        #self.scheme.sigClassAdded.connect(self.onClassAdded)
+
+        #self.modelReset.emit()
+
+        #idx = self.getIndexFromClassInfo(c)
+        #self.beginInsertRows(idx.parent(), idx.row(), 1)
+        #self.endInsertRows()
+
+    def removeClass(self, c):
+        idx = self.getIndexFromClassInfo(c)
+        if idx:
+            self.beginRemoveRows(idx.parent(), idx.row(), idx.row())
+            self.scheme.removeClass(c)
+            self.endRemoveRows()
 
-        if not append:
-            for c in self.classes:
-                self.removeClass(c)
+    def insertClass(self, c, i=None):
+        if i is None:
+            i = len(self.scheme)
+        self.beginInsertRows(QModelIndex(), i, i)
+        self.scheme.addClass(c,i)
+        self.endInsertRows()
+
+
+    def clear(self):
+        self.beginRemoveRows(QModelIndex(), 0, self.rowCount()-1)
+        self.scheme.clear()
+        self.endRemoveRows()
 
     def rowCount(self, QModelIndex_parent=None, *args, **kwargs):
         return len(self.scheme)
 
     def columnCount(self, parent = QModelIndex()):
-        return len(self.columNames)
+        return len(self.columnNames)
 
     def getIndexFromClassInfo(self, classInfo):
-        return self.createIndex(self.scheme.index(classInfo),0)
+        return self.createIndex(self.scheme.classes.index(classInfo),0)
 
     def getClassInfoFromIndex(self, index):
         if index.isValid():
@@ -127,43 +235,61 @@ class ClassificationSchemeTableModel(QAbstractTableModel):
         if role is None or not index.isValid():
             return None
 
-        columnName = self.columnames[index.column()]
+        columnName = self.columnNames[index.column()]
 
         classInfo = self.getClassInfoFromIndex(index)
         assert isinstance(classInfo, ClassInfo)
 
         value = None
         if role == Qt.DisplayRole:
-            if columnName == 'id':
-                value = index.row()
-            if columnName == 'name':
+            if columnName == self.cLABEL:
+                value = classInfo.mLabel
+            elif columnName == self.cNAME:
                 value = classInfo.mName
-            elif columnName == 'color':
-                value = str(classInfo.mColor)
+            elif columnName == self.cCOLOR:
+                value = classInfo.mColor
+            else:
+                s = ""
+        if role == Qt.BackgroundRole:
+            if columnName == self.cCOLOR:
+                return QBrush(classInfo.mColor)
+        if role == Qt.ForegroundRole:
+            if columnName == self.cCOLOR:
+                return getTextColorWithContrast(classInfo.mColor)
+
+
+        if role == Qt.UserRole:
+            return classInfo
         return value
 
     def setData(self, index, value, role=None):
         if role is None or not index.isValid():
             return None
 
-        columnName = self.columnames[index.column()]
+        columnName = self.columnNames[index.column()]
 
         classInfo = self.getClassInfoFromIndex(index)
         assert isinstance(classInfo, ClassInfo)
 
-        if role == Qt.EditRole and columnName == 'name':
-            if len(value) == 0:  # do not accept empty strings
-                return False
-            classInfo.setName(str(value))
-            return True
-
+        if role == Qt.EditRole:
+            if columnName == self.cNAME and len(value) > 0:
+                # do not accept empty strings
+                classInfo.setName(str(value))
+                return True
+            if columnName == self.cCOLOR and isinstance(value, QColor):
+                classInfo.setColor(value)
+                return True
+            if columnName == self.cLABEL and \
+               self.valLabel.validate(value,0)[0] == QValidator.Acceptable:
+                classInfo.setLabel(int(value))
+                return True
         return False
 
     def flags(self, index):
         if index.isValid():
-            columnName = self.columnames[index.column()]
+            columnName = self.columnNames[index.column()]
             flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable
-            if columnName in ['name']:  # allow check state
+            if columnName in [self.cLABEL, self.cNAME]:  # allow check state
                 flags = flags | Qt.ItemIsUserCheckable | Qt.ItemIsEditable
             return flags
             # return item.qt_flags(index.column())
@@ -173,31 +299,132 @@ class ClassificationSchemeTableModel(QAbstractTableModel):
         if Qt is None:
             return None
         if orientation == Qt.Horizontal and role == Qt.DisplayRole:
-            return self.columnames[col]
+            return self.columnNames[col]
         elif orientation == Qt.Vertical and role == Qt.DisplayRole:
             return col
         return None
 
+
+class ClassificationWidgetDelegates(QStyledItemDelegate):
+
+    def __init__(self, tableView, parent=None):
+        assert isinstance(tableView, QTableView)
+        super(ClassificationWidgetDelegates, self).__init__(parent=parent)
+        self.tableView = tableView
+        self.tableView.doubleClicked.connect(self.onDoubleClick)
+        #self.tableView.model().rowsInserted.connect(self.onRowsInserted)
+
+    def onDoubleClick(self, idx):
+        model = self.tableView.model()
+        classInfo = model.getClassInfoFromIndex(idx)
+        if idx.column() == model.columnNames.index(model.cCOLOR):
+
+            w1 = QColorDialog(classInfo.mColor, self.tableView)
+            w1.exec_()
+            if w1.result() == QDialog.Accepted:
+                c = w1.getColor()
+                model.setData(idx, c, role=Qt.EditRole)
+
+
+
+    def getColumnName(self, index):
+        assert index.isValid()
+        assert isinstance(index.model(), ClassificationSchemeTableModel)
+        return index.model().columnNames[index.column()]
+
+    def createEditor(self, parent, option, index):
+        cname = self.getColumnName(index)
+        model = index.model()
+        assert isinstance(model, ClassificationSchemeTableModel)
+        w = None
+        if False and cname == model.cCOLOR:
+            classInfo = model.getClassInfoFromIndex(index)
+            w = QgsColorButton(parent, 'Class {}'.format(classInfo.mName))
+            w.setColor(QColor(index.data()))
+            w.colorChanged.connect(lambda: self.commitData.emit(w))
+        return w
+
+    def setEditorData(self, editor, index):
+        cname = self.getColumnName(index)
+        model = index.model()
+        assert isinstance(model, ClassificationSchemeTableModel)
+
+        classInfo = model.getClassInfoFromIndex(index)
+        assert isinstance(classInfo, ClassInfo)
+        if False and cname == model.cCOLOR:
+            lastColor = classInfo.mColor
+            assert isinstance(editor, QgsColorButton)
+            assert isinstance(lastColor, QColor)
+            editor.setColor(QColor(lastColor))
+            editor.setText('{},{},{}'.format(lastColor.red(), lastColor.green(), lastColor.blue()))
+
+    def setModelData(self, w, model, index):
+        cname = self.getColumnName(index)
+        model = index.model()
+        assert isinstance(model, ClassificationSchemeTableModel)
+
+        if False and cname == model.cCOLOR:
+            assert isinstance(w, QgsColorButton)
+            if index.data() != w.color():
+                model.setData(index, w.color(), Qt.EditRole)
+
 class ClassificationSchemeWidget(QWidget, load('classificationscheme.ui')):
 
+
+
     def __init__(self, parent=None, classificationScheme=None):
         super(ClassificationSchemeWidget, self).__init__(parent)
         self.setupUi(self)
 
         self.mScheme = ClassificationScheme()
+
         if classificationScheme is not None:
             self.setClassificationScheme(classificationScheme)
-        self.tableViewModel = ClassificationSchemeTableModel(self)
-        self.tableClassificationScheme.setModel(self.tableViewModel)
+        self.schemeModel = ClassificationSchemeTableModel(self.mScheme, self)
+
+        self.tableClassificationScheme.verticalHeader().setMovable(True)
+        self.tableClassificationScheme.verticalHeader().setDragEnabled(True)
+        self.tableClassificationScheme.verticalHeader().setDragDropMode(QAbstractItemView.InternalMove)
+        self.tableClassificationScheme.horizontalHeader().setResizeMode(QHeaderView.ResizeToContents)
+        self.tableClassificationScheme.setModel(self.schemeModel)
+        self.tableClassificationScheme.doubleClicked.connect(self.onTableDoubleClick)
+        self.selectionModel = QItemSelectionModel(self.schemeModel)
+        self.selectionModel.selectionChanged.connect(self.onSelectionChanged)
+        self.onSelectionChanged() #enable/disabel widgets depending on a selection
+        self.tableClassificationScheme.setSelectionModel(self.selectionModel)
+
+        #self.delegate = ClassificationWidgetDelegates(self.tableClassificationScheme)
+        #self.tableClassificationScheme.setItemDelegateForColumn(2, self.delegate)
+
 
         self.btnLoadClasses.clicked.connect(self.loadClasses)
         self.btnRemoveClasses.clicked.connect(self.removeSelectedClasses)
-        self.btnAddClasses.clicked.connect(self.addClasses)
+        self.btnAddClasses.clicked.connect(lambda:self.createClasses(1))
+
+    def onTableDoubleClick(self, idx):
+        model = self.tableClassificationScheme.model()
+        classInfo = model.getClassInfoFromIndex(idx)
+        if idx.column() == model.columnNames.index(model.cCOLOR):
 
-    def addClasses(self, n):
+            c = QColorDialog.getColor(classInfo.mColor, self.tableClassificationScheme, \
+                                      'Set class color')
+            model.setData(idx, c, role=Qt.EditRole)
+    def onSelectionChanged(self, *args):
+        self.btnRemoveClasses.setEnabled(self.selectionModel is not None and
+                                         len(self.selectionModel.selectedRows()) > 0)
+
+    def createClasses(self, n):
         for i in range(n):
             c = ClassInfo(name = '<empty>', color = QColor('red'))
-            self.mScheme.addClass(c)
+            self.schemeModel.insertClass(c)
+
+
+    def removeSelectedClasses(self):
+        model = self.tableClassificationScheme.model()
+        indices = reversed(self.selectionModel.selectedRows())
+        classes = [self.schemeModel.getClassInfoFromIndex(idx) for idx in indices]
+        for c in classes:
+            self.schemeModel.removeClass(c)
 
 
     def loadClasses(self, *args):
@@ -211,14 +438,17 @@ class ClassificationSchemeWidget(QWidget, load('classificationscheme.ui')):
     def appendClassificationScheme(self, classificationScheme):
         assert isinstance(classificationScheme, ClassificationScheme)
         for c in classificationScheme:
-            self.mScheme.addClass(c)
+            self.schemeModel.insertClass(c.clone())
 
 
     def setClassificationScheme(self, classificationScheme):
         assert isinstance(classificationScheme, ClassificationScheme)
-        self.mScheme.classes[:]
+        self.schemeModel.clear()
         self.appendClassificationScheme(classificationScheme)
 
+    def classificationScheme(self):
+        return self.mScheme
+
 
 class ClassificationSchemeDialog(QgsDialog):
 
@@ -234,7 +464,7 @@ class ClassificationSchemeDialog(QgsDialog):
         d.exec_()
 
         if d.result() == QDialog.Accepted:
-            return d.classificationSheme()
+            return d.classificationScheme()
         else:
             return None
 
@@ -258,7 +488,7 @@ class ClassificationSchemeDialog(QgsDialog):
         s = ""
 
     def classificationScheme(self):
-        return self.w.crosshairStyle()
+        return self.w.classificationScheme()
 
     def setClassificationScheme(self, classificationScheme):
         assert isinstance(classificationScheme, ClassificationScheme)
@@ -275,6 +505,7 @@ if __name__ == '__main__':
     pathClassImg = r'D:\Repositories\QGIS_Plugins\enmap-box\enmapbox\testdata\HymapBerlinA\HymapBerlinA_test.img'
     pathShp = r''
 
+
     w = ClassificationSchemeWidget()
     w.setClassificationScheme(ClassificationScheme.fromRasterImage(pathClassImg))
     w.show()
-- 
GitLab