From aba592ab0127964697ae2ed70525303d38a2ad4c Mon Sep 17 00:00:00 2001
From: "benjamin.jakimow" <benjamin.jakimow@geo.hu-berlin.de>
Date: Thu, 4 Jul 2019 08:04:03 +0200
Subject: [PATCH] MapCanvas context menu lists layers under cursor position
 only refactoring Loading Progress dialog and TimeSeriesTreeView context menu
 increased version to 1.5

---
 CHANGELOG                                 |  3 +
 eotimeseriesviewer/__init__.py            |  2 +-
 eotimeseriesviewer/main.py                | 56 +++++--------
 eotimeseriesviewer/mapcanvas.py           | 10 ++-
 eotimeseriesviewer/mapviewscrollarea.py   |  6 ++
 eotimeseriesviewer/timeseries.py          | 98 ++++++++++++++---------
 eotimeseriesviewer/ui/timeseriesdock.ui   |  4 +-
 eotimeseriesviewer/ui/timeseriesviewer.ui | 14 ++--
 8 files changed, 104 insertions(+), 89 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG
index a718c9f6..fd3d1614 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,9 @@
 ==============
 Changelog
 ==============
+2019-07-04 (version 1.5):
+    *
+
 2019-07-02 (version 1.4):
     * adding vector layers with sublayers will add all sublayers
     * map canvas context menu "Focus on Spatial Extent" will hide maps without time series data for the current spatial extent
diff --git a/eotimeseriesviewer/__init__.py b/eotimeseriesviewer/__init__.py
index dc91c11e..92e93bd1 100644
--- a/eotimeseriesviewer/__init__.py
+++ b/eotimeseriesviewer/__init__.py
@@ -21,7 +21,7 @@
 # noinspection PyPep8Naming
 
 
-__version__ = '1.4'  # sub-subversion number is added automatically
+__version__ = '1.5'  # sub-subversion number is added automatically
 LICENSE = 'GNU GPL-3'
 TITLE = 'EO Time Series Viewer'
 DESCRIPTION = 'Visualization of multi-sensor Earth observation time series data.'
diff --git a/eotimeseriesviewer/main.py b/eotimeseriesviewer/main.py
index 0241097a..74433728 100644
--- a/eotimeseriesviewer/main.py
+++ b/eotimeseriesviewer/main.py
@@ -140,12 +140,13 @@ class TimeSeriesViewerUI(QMainWindow,
 
         area = None
 
-        def addDockWidget(dock):
+        def addDockWidget(dock:QDockWidget):
             """
             shortcut to add a created dock and return it
             :param dock:
             :return:
             """
+            dock.setParent(self)
             self.addDockWidget(area, dock)
             return dock
 
@@ -179,8 +180,8 @@ class TimeSeriesViewerUI(QMainWindow,
 
 
         area = Qt.BottomDockWidgetArea
-        panel = SpectralLibraryPanel(None)
-        panel.setParent(self)
+        panel = SpectralLibraryPanel(self)
+
         self.dockSpectralLibrary = addDockWidget(panel)
 
         self.tabifyDockWidget(self.dockTimeSeries, self.dockSpectralLibrary)
@@ -415,7 +416,7 @@ class TimeSeriesViewer(QgisInterface, QObject):
         self.ui.actionLoadTS.triggered.connect(self.loadTimeSeriesDefinition)
         self.ui.actionClearTS.triggered.connect(self.clearTimeSeries)
         self.ui.actionSaveTS.triggered.connect(self.saveTimeSeriesDefinition)
-        self.ui.actionAddTSExample.triggered.connect(self.loadExampleTimeSeries)
+        self.ui.actionAddTSExample.triggered.connect(lambda : self.loadExampleTimeSeries(loadAsync=False))
         self.ui.actionLoadTimeSeriesStack.triggered.connect(self.loadTimeSeriesStack)
         self.ui.actionShowCrosshair.toggled.connect(self.spatialTemporalVis.setCrosshairVisibility)
         self.ui.actionExportMapsToImages.triggered.connect(lambda: self.exportMapsToImages())
@@ -896,25 +897,8 @@ class TimeSeriesViewer(QgisInterface, QObject):
         """
         return self.ui.messageBar
 
-    def loadImageFiles(self, files:list):
-        """
-        Loads image files to the time series.
-        :param files: [list-of-file-paths]
-        """
-        assert isinstance(files, list)
-
-        progressDialog = QProgressDialog(parent=self.ui)
-        progressDialog.setWindowTitle('Load data')
-        progressDialog.setWindowFlag(Qt.WindowContextHelpButtonHint, False)
-        progressDialog.show()
-        QApplication.processEvents()
-        self.mTimeSeries.addSources(files, progressDialog=progressDialog)
-
-        progressDialog.hide()
-        progressDialog.setParent(None)
-
 
-    def loadTimeSeriesDefinition(self, path:str=None, n_max:int=None, progressDialog:QProgressDialog=None):
+    def loadTimeSeriesDefinition(self, path:str=None, n_max:int=None):
         """
         Loads a time series definition file
         :param path:
@@ -936,21 +920,12 @@ class TimeSeriesViewer(QgisInterface, QObject):
 
         if path is not None and os.path.exists(path):
             s.setValue('file_ts_definition', path)
-
-            b = isinstance(progressDialog, QProgressDialog)
-
-            if not b:
-                progressDialog = QProgressDialog(parent=self.ui)
-                progressDialog.setWindowTitle('Load data')
-                progressDialog.setWindowFlag(Qt.WindowContextHelpButtonHint, False)
-                progressDialog.show()
-
             self.clearTimeSeries()
+            progressDialog = QProgressDialog(self.ui)
+            progressDialog.setWindowTitle('Image Loading')
+            progressDialog.setMinimumDuration(2000)
             self.mTimeSeries.loadFromFile(path, n_max=n_max, progressDialog=progressDialog)
 
-            if not b:
-                progressDialog.hide()
-                progressDialog.setParent(None)
 
     def createMapView(self, name:str=None):
         """
@@ -1192,14 +1167,19 @@ class TimeSeriesViewer(QgisInterface, QObject):
                 dn = os.path.dirname(files[0])
                 s.setValue('dir_datasources', dn)
 
-
         if files:
+            progressDialog = QProgressDialog('Images Loading', 'Cancel', 0, len(files), parent=self.ui)
+            progressDialog.setLabelText('Start loading {} images....'.format(len(files)))
+            progressDialog.setMinimumDuration(2000)
+            progressDialog.setValue(0)
+
             if loadAsync:
-                self.mTimeSeries.addSourcesAsync(files)
+                self.mTimeSeries.addSourcesAsync(files, progressDialog=progressDialog)
             else:
-                self.mTimeSeries.addSources(files)
+                self.mTimeSeries.addSources(files, progressDialog=progressDialog)
+
 
-            QCoreApplication.processEvents()
+            #QCoreApplication.processEvents()
             #self.mTimeSeries.addSources(files)
 
     def clearTimeSeries(self):
diff --git a/eotimeseriesviewer/mapcanvas.py b/eotimeseriesviewer/mapcanvas.py
index 5d72a19f..aba98887 100644
--- a/eotimeseriesviewer/mapcanvas.py
+++ b/eotimeseriesviewer/mapcanvas.py
@@ -603,6 +603,8 @@ class MapCanvas(QgsMapCanvas):
                                 and SpatialExtent.fromLayer(l).toCrs(self.crs()).contains(pointGeo)]
         viewPortSensorLayers = [l for l in viewPortRasterLayers if isinstance(l, SensorProxyLayer)]
 
+        viewPortVectorLayers = [l for l in self.layers() if isinstance(l, QgsVectorLayer)]
+
         refSensorLayer = None
         refRasterLayer = None
 
@@ -678,8 +680,12 @@ class MapCanvas(QgsMapCanvas):
         menu.addSeparator()
 
         m = menu.addMenu('Layers...')
-        for mapLayer in self.layers():
-            sub = m.addMenu(mapLayer.name())
+        visibleLayers = viewPortRasterLayers + viewPortVectorLayers
+
+
+        for mapLayer in visibleLayers:
+            #sub = m.addMenu(mapLayer.name())
+            sub = m.addMenu(os.path.basename(mapLayer.source()))
 
             if isinstance(mapLayer, SensorProxyLayer):
                 sub.setIcon(QIcon(':/timeseriesviewer/icons/icon.svg'))
diff --git a/eotimeseriesviewer/mapviewscrollarea.py b/eotimeseriesviewer/mapviewscrollarea.py
index 0eeaca69..e3f5e7df 100644
--- a/eotimeseriesviewer/mapviewscrollarea.py
+++ b/eotimeseriesviewer/mapviewscrollarea.py
@@ -45,3 +45,9 @@ class MapViewScrollArea(QScrollArea):
 
         diff = centerInParent - centerViewPort
         return diff.manhattanLength()
+
+    def sizeHint(self):
+        parent = self.parent()
+        hint = super(MapViewScrollArea, self).sizeHint()
+
+        return hint
diff --git a/eotimeseriesviewer/timeseries.py b/eotimeseriesviewer/timeseries.py
index 2eda86b1..9ed15d65 100644
--- a/eotimeseriesviewer/timeseries.py
+++ b/eotimeseriesviewer/timeseries.py
@@ -861,6 +861,12 @@ class TimeSeriesTreeView(QTreeView):
         tsd = self.model().data(idx, role=Qt.UserRole)
 
         menu = QMenu(self)
+        if isinstance(tsd, TimeSeriesDate):
+            a = menu.addAction('Show map for {}'.format(tsd.date()))
+            a.setToolTip('Shows the map related to this time series date.')
+            a.triggered.connect(lambda _, tsd=tsd: self.sigMoveToDateRequest.emit(tsd))
+            menu.addSeparator()
+
         a = menu.addAction('Copy value(s)')
         a.triggered.connect(lambda: self.onCopyValues())
         a = menu.addAction('Hide date(s)')
@@ -869,10 +875,7 @@ class TimeSeriesTreeView(QTreeView):
         a = menu.addAction('Show date(s)')
         a.setToolTip('Shows the selected time series dates.')
         a.triggered.connect(lambda: self.onSetCheckState(Qt.Unchecked))
-        if isinstance(tsd, TimeSeriesDate):
-            a = menu.addAction('Show {} in map.'.format(tsd.date()))
-            a.setToolTip('Shows the map related to this time series date.')
-            a.triggered.connect(lambda _, tsd=tsd: self.sigMoveToDateRequest.emit(tsd))
+
 
         menu.popup(QCursor.pos())
 
@@ -950,7 +953,7 @@ class TimeSeries(QAbstractItemModel):
 
     sigTimeSeriesDatesAdded = pyqtSignal(list)
     sigTimeSeriesDatesRemoved = pyqtSignal(list)
-    sigLoadingProgress = pyqtSignal(int, int, str)
+    #sigLoadingProgress = pyqtSignal(int, int, str)
 
 
     sigSensorAdded = pyqtSignal(SensorInstrument)
@@ -970,6 +973,8 @@ class TimeSeries(QAbstractItemModel):
         self.mShape = None
         self.mDateTimePrecision = DateTimePrecision.Original
 
+        self.mLoadingProgressDialog = None
+
         self.mCurrentDates = []
         self.mCurrentSpatialExtent = None
 
@@ -1080,12 +1085,19 @@ class TimeSeries(QAbstractItemModel):
                 if len(parts) > 1:
                     masks.append(parts[1])
 
-
         if n_max:
             n_max = min([len(images), n_max])
-            self.addSourcesAsync(images[0:n_max], progressDialog=progressDialog)
-        else:
-            self.addSourcesAsync(images, progressDialog=progressDialog)
+            images = images[0:n_max]
+
+        if isinstance(progressDialog, QProgressDialog):
+            progressDialog.setMaximum(len(images))
+            progressDialog.setMinimum(0)
+            progressDialog.setValue(0)
+            progressDialog.setLabelText('Start loading {} images....'.format(len(images)))
+
+        self.addSourcesAsync(images, progressDialog=progressDialog)
+
+
         #self.addMasks(masks)
 
 
@@ -1215,7 +1227,14 @@ class TimeSeries(QAbstractItemModel):
 
         removed = list()
         for tsd in tsds:
+
             assert isinstance(tsd, TimeSeriesDate)
+
+            tsd.sigSourcesRemoved.disconnect()
+            tsd.sigSourcesAdded.disconnect()
+            tsd.sigVisibilityChanged.disconnect()
+            tsd.sigRemoveMe.disconnect()
+
             row = self.mTSDs.index(tsd)
             self.beginRemoveRows(self.mRootIndex, row, row)
             self.mTSDs.remove(tsd)
@@ -1302,6 +1321,8 @@ class TimeSeries(QAbstractItemModel):
         assert isinstance(tm, QgsTaskManager)
         assert isinstance(nWorkers, int) and nWorkers >= 1
 
+        self.mLoadingProgressDialog = progressDialog
+
 
         # see https://stackoverflow.com/questions/312443/how-do-you-split-a-list-into-evenly-sized-chunks
         def chunks(l, n):
@@ -1311,10 +1332,9 @@ class TimeSeries(QAbstractItemModel):
 
         n = int(len(sources) / nWorkers)
         for subset in chunks(sources, 50):
-        #for source in sources:
-            #subset = [source]
+
             dump = pickle.dumps(subset)
-            #taskDescription = 'Load EOTSV {} sources {}'.format(len(subset), uuid.uuid4())
+
             taskDescription = 'Load {} images'.format(len(subset))
             qgsTask = QgsTask.fromFunction(taskDescription, doLoadTimeSeriesSourcesTask, dump, on_finished=self.onAddSourcesAsyncFinished)
             tid = id(qgsTask)
@@ -1322,7 +1342,7 @@ class TimeSeries(QAbstractItemModel):
             qgsTask.taskCompleted.connect(lambda *args, tid=tid: self.onRemoveTask(tid))
             qgsTask.taskTerminated.connect(lambda *args, tid=tid: self.onRemoveTask(tid))
 
-            if False: # for debugging
+            if False: # for debugging only
                 resultDump = doLoadTimeSeriesSourcesTask(qgsTask, dump)
                 self.onAddSourcesAsyncFinished(None, resultDump)
             else:
@@ -1340,6 +1360,9 @@ class TimeSeries(QAbstractItemModel):
                 dump = args[1]
                 sources = pickle.loads(dump)
                 for source in sources:
+                    if isinstance(self.mLoadingProgressDialog, QProgressDialog):
+                        self.increaseProgressBar()
+
                     newTSD = self._addSource(source)
                     if isinstance(newTSD, TimeSeriesDate):
                         addedDates.append(newTSD)
@@ -1347,22 +1370,25 @@ class TimeSeries(QAbstractItemModel):
                 if len(addedDates) > 0:
                     self.sigTimeSeriesDatesAdded.emit(addedDates)
 
-
             except Exception as ex:
                 s = ""
         else:
             s = ""
-        #self._cleanTasks()
 
-    def _cleanTasks(self):
-        toRemove = []
-        for task in self.mTasks:
-            if isinstance(task, QgsTask):
-                if task.status() in [QgsTask.Complete, QgsTask.Terminated]:
-                    toRemove.append(task)
+        if isinstance(self.mLoadingProgressDialog, QProgressDialog):
+            if self.mLoadingProgressDialog.wasCanceled() or self.mLoadingProgressDialog.value() == -1:
+                self.mLoadingProgressDialog = None
+
+    def increaseProgressBar(self):
+        if isinstance(self.mLoadingProgressDialog, QProgressDialog):
+            v = self.mLoadingProgressDialog.value() + 1
+            self.mLoadingProgressDialog.setValue(v)
+            self.mLoadingProgressDialog.setLabelText('{}/{}'.format(v, self.mLoadingProgressDialog.maximum()))
+
+            if v == 1 or v % 25 == 0:
+                QApplication.processEvents()
+
 
-        for t in toRemove:
-            self.mTasks.remove(t)
 
     def addSources(self, sources:list, progressDialog:QProgressDialog=None):
         """
@@ -1370,15 +1396,8 @@ class TimeSeries(QAbstractItemModel):
         :param sources: [list-of-TimeSeriesSources]
         """
         assert isinstance(sources, list)
-
+        self.mLoadingProgressDialog = progressDialog
         nMax = len(sources)
-        #self.sigTimeSeriesSourcesAboutToBeChanged.emit()
-
-        self.sigLoadingProgress.emit(0, nMax, 'Start loading {} sources...'.format(nMax))
-        
-        if isinstance(progressDialog, QProgressDialog):
-            progressDialog.setRange(0, nMax)
-            progressDialog.setLabelText('Load rasters...'.format(nMax))
         # 1. read sources
         # this could be excluded into a parallel process
         addedDates = []
@@ -1394,17 +1413,16 @@ class TimeSeries(QAbstractItemModel):
                     msg = 'Unable to add: {}\n{}'.format(str(source), str(ex))
                     print(msg, file=sys.stderr)
 
-            if isinstance(progressDialog, QProgressDialog):
-                if progressDialog.wasCanceled():
+            if isinstance(self.mLoadingProgressDialog, QProgressDialog):
+                if self.mLoadingProgressDialog.wasCanceled():
                     break
-                progressDialog.setValue(i)
-                progressDialog.setLabelText('{}/{}'.format(i+1, nMax))
+                self.increaseProgressBar()
 
-            if (i+1) % 10 == 0:
-                self.sigLoadingProgress.emit(i+1, nMax, msg)
+            #if (i+1) % 10 == 0:
+            #    self.sigLoadingProgress.emit(i+1, nMax, msg)
 
-            if (i+1) % 50 == 0:
-                QGuiApplication.processEvents()
+            #if (i+1) % 50 == 0:
+            #    QGuiApplication.processEvents()
 
             if isinstance(newTSD, TimeSeriesDate):
                 addedDates.append(newTSD)
@@ -1416,6 +1434,8 @@ class TimeSeries(QAbstractItemModel):
 
         if len(addedDates) > 0:
             self.sigTimeSeriesDatesAdded.emit(addedDates)
+        self.mLoadingProgressDialog = None
+
 
     def _addSource(self, source:TimeSeriesSource)->TimeSeriesDate:
         """
diff --git a/eotimeseriesviewer/ui/timeseriesdock.ui b/eotimeseriesviewer/ui/timeseriesdock.ui
index a0856d09..b2635172 100644
--- a/eotimeseriesviewer/ui/timeseriesdock.ui
+++ b/eotimeseriesviewer/ui/timeseriesdock.ui
@@ -18,8 +18,8 @@
   </property>
   <property name="minimumSize">
    <size>
-    <width>152</width>
-    <height>139</height>
+    <width>0</width>
+    <height>0</height>
    </size>
   </property>
   <property name="windowTitle">
diff --git a/eotimeseriesviewer/ui/timeseriesviewer.ui b/eotimeseriesviewer/ui/timeseriesviewer.ui
index 615c5981..f79f1b3c 100644
--- a/eotimeseriesviewer/ui/timeseriesviewer.ui
+++ b/eotimeseriesviewer/ui/timeseriesviewer.ui
@@ -35,8 +35,8 @@
   <widget class="QWidget" name="mCentralWidget">
    <property name="sizePolicy">
     <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
-     <horstretch>1</horstretch>
-     <verstretch>1</verstretch>
+     <horstretch>3</horstretch>
+     <verstretch>3</verstretch>
     </sizepolicy>
    </property>
    <property name="minimumSize">
@@ -74,9 +74,9 @@
     <item>
      <widget class="MapViewScrollArea" name="scrollAreaSubsets">
       <property name="sizePolicy">
-       <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
-        <horstretch>1</horstretch>
-        <verstretch>1</verstretch>
+       <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
+        <horstretch>3</horstretch>
+        <verstretch>3</verstretch>
        </sizepolicy>
       </property>
       <property name="minimumSize">
@@ -108,8 +108,8 @@
        </property>
        <property name="sizePolicy">
         <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
-         <horstretch>0</horstretch>
-         <verstretch>0</verstretch>
+         <horstretch>2</horstretch>
+         <verstretch>2</verstretch>
         </sizepolicy>
        </property>
        <property name="minimumSize">
-- 
GitLab