Commit ca103faa authored by Benjamin Jakimow's avatar Benjamin Jakimow
Browse files

- refactoring


- saving / loading a QgsProject saves / reloads the EOTSV time series (resolves #8)
- fixed several issues related to QgsMapLayerStyle handling
Signed-off-by: Benjamin Jakimow's avatarBenjamin Jakimow <benjamin.jakimow@geo.hu-berlin.de>
parent caf79b61
import enum
from qgis.core import *
from qgis.core import QgsMapLayer, QgsRasterLayer, QgsVectorLayer, QgsField, QgsFields, \
QgsEditorWidgetSetup, QgsFeature, QgsVectorLayerTools, \
......@@ -8,13 +10,12 @@ from qgis.gui import QgsDockWidget, QgsSpinBox, QgsDoubleSpinBox, \
QgsGui, QgsEditorWidgetRegistry, QgsDateTimeEdit, QgsDateEdit, QgsTimeEdit
from eotimeseriesviewer.externals.qps.layerproperties import *
from eotimeseriesviewer.timeseries import TimeSeriesDate, TimeSeriesSource
from qgis.PyQt.QtCore import *
from qgis.PyQt.QtGui import *
from qgis.PyQt.QtWidgets import *
from .externals.qps.layerproperties import AttributeTableWidget
from .externals.qps.utils import datetime64
from .externals.qps.classification.classificationscheme import ClassInfo
# the QgsProject(s) and QgsMapLayerStore(s) to search for QgsVectorLayers
MAP_LAYER_STORES = [QgsProject.instance()]
......
......@@ -610,6 +610,10 @@ class EOTimeSeriesViewer(QgisInterface, QObject):
self.ui.mActionLayerProperties.triggered.connect(self.onShowLayerProperties)
from qgis.utils import iface
self.ui.actionLoadProject.triggered.connect(iface.actionOpenProject().trigger)
self.ui.actionSaveProject.triggered.connect(iface.actionSaveProject().trigger)
self.profileDock.actionLoadProfileRequest.triggered.connect(self.activateIdentifyTemporalProfileMapTool)
self.ui.dockSpectralLibrary.SLW.actionSelectProfilesFromMap.triggered.connect(
self.activateIdentifySpectralProfileMapTool)
......@@ -659,6 +663,33 @@ class EOTimeSeriesViewer(QgisInterface, QObject):
self.ui.dockTimeSeries.setFloating(True)
self.ui.dockTimeSeries.setFloating(False)
QgsProject.instance().writeProject.connect(self.onWriteProject)
QgsProject.instance().readProject.connect(self.onReadProject)
def onWriteProject(self, dom: QDomDocument):
node = dom.createElement('EOTSV')
root = dom.documentElement()
# save time series
self.timeSeries().writeXml(node, dom)
# save map views
self.mapWidget().writeXml(node, dom)
root.appendChild(node)
def onReadProject(self, dom: QDomDocument):
if dom is None:
s = ""
root = dom.documentElement()
node = root.firstChildElement('EOTSV')
if node.nodeName() == 'EOTSV':
self.timeSeries().readXml(node)
self.mapWidget().readXml(node)
def lockCentralWidgetSize(self, b: bool):
"""
Locks or release the current central widget size
......
This diff is collapsed.
......@@ -28,12 +28,12 @@ import collections
import copy
import traceback
import bisect
import json
from eotimeseriesviewer import DIR_UI
from qgis.core import *
from qgis.core import QgsContrastEnhancement, QgsRasterShader, QgsColorRampShader, QgsProject, \
QgsCoordinateReferenceSystem, QgsVector, QgsTextFormat, \
QgsRectangle, QgsRasterRenderer, QgsMapLayerStore, \
QgsRectangle, QgsRasterRenderer, QgsMapLayerStore, QgsMapLayerStyle, \
QgsLayerTreeModel, QgsLayerTreeGroup, \
QgsLayerTree, QgsLayerTreeLayer, \
QgsRasterLayer, QgsVectorLayer, QgsMapLayer, QgsMapLayerProxyModel, QgsColorRamp, QgsSingleBandPseudoColorRenderer
......@@ -57,11 +57,6 @@ from .externals.qps.crosshair.crosshair import getCrosshairStyle, CrosshairStyle
from .externals.qps.layerproperties import showLayerPropertiesDialog
from .externals.qps.maptools import *
# assert os.path.isfile(dummyPath)
# lyr = QgsRasterLayer(dummyPath)
# assert lyr.isValid()
DUMMY_RASTERINTERFACE = QgsSingleBandGrayRenderer(None, 0)
KEY_LOCKED_LAYER = 'eotsv/locked'
KEY_SENSOR_GROUP = 'eotsv/sensorgroup'
KEY_SENSOR_LAYER = 'eotsv/sensorlayer'
......@@ -103,7 +98,7 @@ class MapView(QFrame):
self.mMapTextFormat = DEFAULT_VALUES[Keys.MapTextFormat]
self.mMapWidget = None
self.mInitialStretch: typing.Dict[SensorInstrument, bool] = dict()
self.mLayerStyleInitialized: typing.Dict[SensorInstrument, bool] = dict()
self.mTimeSeries = None
self.mSensorLayerList = list()
......@@ -422,6 +417,7 @@ class MapView(QFrame):
else:
return self.tbName.text().strip()
def setCrosshairStyle(self, crosshairStyle: CrosshairStyle) -> CrosshairStyle:
"""
Seths the CrosshairStyle of this MapView
......@@ -520,12 +516,23 @@ class MapView(QFrame):
return l
return None
def sensors(self) -> list:
def sensorLayers(self, sensor: SensorInstrument) -> typing.List[SensorProxyLayer]:
"""
:param sensor:
:return:
"""
layers = []
for c in self.mapCanvases():
for l in c.layers():
if isinstance(l, SensorProxyLayer) and l.sensor() == sensor:
layers.append(l)
return layers
def sensors(self) -> typing.List[SensorInstrument]:
"""
Returns a list of SensorsInstruments
:return: [list-of-SensorInstruments]
"""
return [t[0] for t in self.mSensorLayerList]
def addSensor(self, sensor: SensorInstrument):
......@@ -537,24 +544,30 @@ class MapView(QFrame):
assert isinstance(sensor, SensorInstrument)
if sensor not in self.sensors():
sensor.sigNameChanged.connect(self.sigCanvasAppearanceChanged)
dummyLayer = sensor.proxyRasterLayer()
assert isinstance(dummyLayer.renderer(), QgsRasterRenderer)
dummyLayer.rendererChanged.connect(lambda sensor=sensor: self.onSensorRendererChanged(sensor))
# QgsProject.instance().addMapLayer(dummyLayer)
layerTreeLayer = self.mLayerTreeSensorNode.addLayer(dummyLayer)
assert isinstance(layerTreeLayer, QgsLayerTreeLayer)
masterLayer: SensorProxyLayer = sensor.proxyRasterLayer()
assert isinstance(masterLayer.renderer(), QgsRasterRenderer)
self.mSensorLayerList.append((sensor, masterLayer))
masterLayer.styleChanged.connect(lambda *args, v=self, l=masterLayer: self.onMasterStyleChanged(l))
layerTreeLayer: QgsLayerTreeLayer = self.mLayerTreeSensorNode.addLayer(masterLayer)
layerTreeLayer.setCustomProperty(KEY_LOCKED_LAYER, True)
layerTreeLayer.setCustomProperty(KEY_SENSOR_LAYER, True)
self.mSensorLayerList.append((sensor, dummyLayer))
self.mInitialStretch[sensor] = False
def onSensorRendererChanged(self, sensor: SensorInstrument):
self.mLayerStyleInitialized[sensor] = False
def onMasterStyleChanged(self, masterLayer: SensorProxyLayer):
sensor: SensorInstrument = masterLayer.sensor()
style: QgsMapLayerStyle = masterLayer.mapLayerStyle()
#print('### MASTER-STYLE-CHANGED')
#print(style.xmlData())
for lyr in self.sensorLayers(sensor):
lyr.setMapLayerStyle(style)
for c in self.sensorCanvases(sensor):
assert isinstance(c, MapCanvas)
c.addToRefreshPipeLine(MapCanvas.Command.RefreshRenderer)
self.mInitialStretch[sensor] = True
self.mLayerStyleInitialized[sensor] = True
def sensorCanvases(self, sensor: SensorInstrument) -> list:
"""
......@@ -587,8 +600,8 @@ class MapView(QFrame):
:param sensor:
:return:
"""
if sensor in self.mInitialStretch.keys():
self.mInitialStretch.pop(sensor)
if sensor in self.mLayerStyleInitialized.keys():
self.mLayerStyleInitialized.pop(sensor)
toRemove = []
for t in self.mSensorLayerList:
......@@ -1132,14 +1145,14 @@ class MapWidget(QFrame):
for mapView in self.mapViews():
# test for initial raster stretches
for sensor in self.timeSeries().sensors():
if not mapView.mInitialStretch.get(sensor):
if not mapView.mLayerStyleInitialized.get(sensor):
for c in self.mapViewCanvases(mapView):
# find the first map canvas that contains layer data of this sensor
# in its extent
if not isinstance(c.tsd(), TimeSeriesDate):
continue
if c.tsd().sensor() == sensor and c.stretchToCurrentExtent():
mapView.mInitialStretch[sensor] = True
mapView.mLayerStyleInitialized[sensor] = True
break
def currentLayer(self) -> QgsMapLayer:
......@@ -1177,6 +1190,71 @@ class MapWidget(QFrame):
self.mCurrentMapView = mapView
self.sigCurrentMapViewChanged.emit(mapView)
def writeXml(self, node: QDomElement, doc: QDomDocument) -> bool:
"""
Writes the MapWidget settings to a QDomNode
:param node:
:param doc:
:return:
"""
mwNode = doc.createElement('EOTSV_MAPWIDGET')
context = QgsReadWriteContext()
for mapView in self.mapViews():
mvNode = doc.createElement('MAP_VIEW')
mvNode.setAttribute('name', mapView.name())
for sensor in mapView.sensors():
lyr = mapView.sensorProxyLayer(sensor)
if isinstance(lyr, SensorProxyLayer):
sensorNode = doc.createElement('MAP_VIEW_PROXY_LAYER')
sensorNode.setAttribute('sensor_id', sensor.id())
style: QgsMapLayerStyle = lyr.mapLayerStyle()
styleNode = doc.createElement('STYLE')
style.writeXml(styleNode)
sensorNode.appendChild(styleNode)
mvNode.appendChild(sensorNode)
mwNode.appendChild(mvNode)
node.appendChild(mwNode)
return True
def readXml(self, node: QDomNode):
if not node.nodeName() == 'EOTSV_MAPWIDGET':
node = node.firstChildElement('EOTSV_MAPWIDGET')
if node.isNull():
return None
context = QgsReadWriteContext()
mvNode = node.firstChildElement('MAP_VIEW')
while not mvNode.isNull():
mvName = mvNode.attribute('name')
mapView: MapView = None
for mv in self.mapViews():
if mv.name() == mvName:
mapView = mv
if not isinstance(mapView, MapView):
mapView = MapView()
mapView.setName(mvName)
self.addMapView(mapView)
sensorNode = mvNode.firstChildElement('MAP_VIEW_PROXY_LAYER').toElement()
while not sensorNode.isNull():
sid = sensorNode.attribute('sensor_id')
sensor = self.timeSeries().findMatchingSensor(sid)
if sensor:
lyr = mapView.sensorProxyLayer(sensor)
if isinstance(lyr, SensorProxyLayer):
styleNode = sensorNode.firstChildElement('STYLE')
if styleNode.nodeName() == 'STYLE':
style = QgsMapLayerStyle()
style.readXml(styleNode)
lyr.setMapLayerStyle(style)
s = ""
sensorNode = sensorNode.nextSibling().toElement()
mvNode = mvNode.nextSibling()
s = ""
def usedLayers(self):
layers = set()
for c in self.mapCanvases():
......@@ -1702,17 +1780,17 @@ class MapWidget(QFrame):
w.layout().update()
w.update()
def _updateLayerCache(self) -> list:
def _updateLayerCache(self) -> typing.List[MapCanvas]:
canvases = self.findChildren(MapCanvas)
for c in canvases:
assert isinstance(c, MapCanvas)
self.mMapLayerCache[self._layerListKey(c)] = c.layers()
return canvases
def _layerListKey(self, canvas: MapCanvas):
def _layerListKey(self, canvas: MapCanvas) -> typing.Tuple[MapView, TimeSeriesDate]:
return (canvas.mapView(), canvas.tsd())
def _updateCanvasDates(self, updateLayerCache=True):
def _updateCanvasDates(self, updateLayerCache:bool = True):
visibleBefore = self.visibleTSDs()
bTSDChanged = False
......@@ -1793,7 +1871,7 @@ class MapWidget(QFrame):
item.setVisibility(style.mShow)
assert isinstance(item, CrosshairMapCanvasItem)
item.setCrosshairStyle(style)
canvas.addToRefreshPipeLine(MapCanvas.Command.UpdateMapItems)
#canvas.addToRefreshPipeLine(MapCanvas.Command.UpdateMapItems)
def _updateCanvasAppearance(self, mapView=None):
......@@ -1845,7 +1923,7 @@ class MapWidget(QFrame):
info.setUpperCenter(uc)
info.setLowerCenter(lc)
canvas.addToRefreshPipeLine(MapCanvas.Command.UpdateMapItems)
#canvas.addToRefreshPipeLine(MapCanvas.Command.UpdateMapItems)
class MapViewDock(QgsDockWidget):
......
......@@ -39,9 +39,9 @@ from qgis import *
from qgis.PyQt.QtCore import *
from qgis.PyQt.QtGui import *
from qgis.PyQt.QtWidgets import *
from qgis.PyQt.QtXml import QDomDocument
from qgis.PyQt.QtXml import QDomDocument, QDomElement, QDomNode
from qgis.core import QgsRasterLayer, QgsCoordinateReferenceSystem, \
Qgis, QgsDateTimeRange, \
Qgis, QgsDateTimeRange, QgsMapLayerStyle, \
QgsProject, QgsGeometry, QgsApplication, QgsTask, QgsRasterBandStats, QgsRectangle, QgsRasterDataProvider, \
QgsTaskManager, QgsPoint, QgsPointXY, \
QgsRasterLayerTemporalProperties, QgsMimeDataUtils, QgsCoordinateTransform
......@@ -349,6 +349,7 @@ class SensorProxyLayer(QgsRasterLayer):
super(SensorProxyLayer, self).__init__(*args, **kwds)
self.mSensor: SensorInstrument = sensor
self.mTSS: TimeSeriesSource = None
self.mStyleXml: str = None
def sensor(self) -> SensorInstrument:
"""
......@@ -357,6 +358,23 @@ class SensorProxyLayer(QgsRasterLayer):
"""
return self.mSensor
def setMapLayerStyle(self, style: QgsMapLayerStyle):
if self.mStyleXml is None:
self.mStyleXml = self.mapLayerStyle().xmlData()
xml = style.xmlData()
if self.mStyleXml != xml:
style.writeToLayer(self)
self.mStyleXml = xml
self.styleChanged.emit()
#xml2 = self.mapLayerStyle().xmlData()
#assert xml2 == xml
#assert self.mStyleXml == xml
def mapLayerStyle(self) -> QgsMapLayerStyle:
style = QgsMapLayerStyle()
style.readFromLayer(self)
return style
def verifyInputImage(datasource):
"""
......@@ -2107,6 +2125,37 @@ class TimeSeries(QAbstractItemModel):
return None
def writeXml(self, node: QDomElement, doc: QDomDocument) -> bool:
"""
Writes the TimeSeires to a QDomNode
:param node:
:param doc:
:return:
"""
tsNode = doc.createElement('EOTSV_TIMESERIES')
for tss in self.sources():
tssNode = doc.createElement('TIME_SERIES_SOURCE')
tssNode.appendChild(doc.createTextNode((tss.uri())))
tsNode.appendChild(tssNode)
node.appendChild(tsNode)
return True
def readXml(self, node: QDomNode):
if not node.nodeName() == 'EOTSV_TIMESERIES':
node = node.firstChildElement('EOTSV_TIMESERIES')
if node.isNull():
return None
tssNode = node.firstChildElement('TIME_SERIES_SOURCE')
to_add = []
while not tssNode.isNull():
uri = tssNode.firstChild().nodeValue()
to_add.append(uri)
tssNode = tssNode.nextSibling()
if len(to_add) > 0:
self.addSources(to_add, runAsync=False)
def data(self, index, role):
"""
:param index: QModelIndex
......
......@@ -105,6 +105,9 @@
<addaction name="actionSaveTS"/>
<addaction name="actionClearTS"/>
<addaction name="separator"/>
<addaction name="actionSaveProject"/>
<addaction name="actionLoadProject"/>
<addaction name="separator"/>
<addaction name="actionAddTSExample"/>
</widget>
<widget class="QMenu" name="menuAbout">
......@@ -384,7 +387,7 @@
<bool>true</bool>
</property>
<property name="icon">
<iconset>
<iconset resource="../../../../cpp/QGIS/images/images.qrc">
<normaloff>:/images/themes/default/mActionZoomIn.svg</normaloff>:/images/themes/default/mActionZoomIn.svg</iconset>
</property>
<property name="text">
......@@ -399,7 +402,7 @@
<bool>true</bool>
</property>
<property name="icon">
<iconset>
<iconset resource="../../../../cpp/QGIS/images/images.qrc">
<normaloff>:/images/themes/default/mActionZoomOut.svg</normaloff>:/images/themes/default/mActionZoomOut.svg</iconset>
</property>
<property name="text">
......@@ -414,7 +417,7 @@
<bool>true</bool>
</property>
<property name="icon">
<iconset>
<iconset resource="../../../../cpp/QGIS/images/images.qrc">
<normaloff>:/images/themes/default/mActionZoomFullExtent.svg</normaloff>:/images/themes/default/mActionZoomFullExtent.svg</iconset>
</property>
<property name="text">
......@@ -429,7 +432,7 @@
<bool>true</bool>
</property>
<property name="icon">
<iconset>
<iconset resource="../../../../cpp/QGIS/images/images.qrc">
<normaloff>:/images/themes/default/mActionZoomActual.svg</normaloff>:/images/themes/default/mActionZoomActual.svg</iconset>
</property>
<property name="text">
......@@ -444,7 +447,7 @@
<bool>true</bool>
</property>
<property name="icon">
<iconset>
<iconset resource="../../../../cpp/QGIS/images/images.qrc">
<normaloff>:/images/themes/default/mActionPan.svg</normaloff>:/images/themes/default/mActionPan.svg</iconset>
</property>
<property name="text">
......@@ -518,7 +521,7 @@
</action>
<action name="actionRefresh">
<property name="icon">
<iconset>
<iconset resource="../../../../cpp/QGIS/images/images.qrc">
<normaloff>:/images/themes/default/mActionReload.svg</normaloff>:/images/themes/default/mActionReload.svg</iconset>
</property>
<property name="text">
......@@ -596,7 +599,7 @@
</action>
<action name="actionAddVectorData">
<property name="icon">
<iconset>
<iconset resource="../../../../cpp/QGIS/images/images.qrc">
<normaloff>:/images/themes/default/mActionAddOgrLayer.svg</normaloff>:/images/themes/default/mActionAddOgrLayer.svg</iconset>
</property>
<property name="text">
......@@ -649,7 +652,7 @@
<bool>false</bool>
</property>
<property name="icon">
<iconset>
<iconset resource="../../../../cpp/QGIS/images/images.qrc">
<normaloff>:/images/themes/default/mActionPropertiesWidget.svg</normaloff>:/images/themes/default/mActionPropertiesWidget.svg</iconset>
</property>
<property name="text">
......@@ -724,7 +727,7 @@
<bool>true</bool>
</property>
<property name="icon">
<iconset>
<iconset resource="../../../../cpp/QGIS/images/images.qrc">
<normaloff>:/images/themes/default/mActionSelectRectangle.svg</normaloff>:/images/themes/default/mActionSelectRectangle.svg</iconset>
</property>
<property name="text">
......@@ -739,7 +742,7 @@
<bool>true</bool>
</property>
<property name="icon">
<iconset>
<iconset resource="../../../../cpp/QGIS/images/images.qrc">
<normaloff>:/images/themes/default/mActionSelectPolygon.svg</normaloff>:/images/themes/default/mActionSelectPolygon.svg</iconset>
</property>
<property name="text">
......@@ -754,7 +757,7 @@
<bool>true</bool>
</property>
<property name="icon">
<iconset>
<iconset resource="../../../../cpp/QGIS/images/images.qrc">
<normaloff>:/images/themes/default/mActionSelectFreehand.svg</normaloff>:/images/themes/default/mActionSelectFreehand.svg</iconset>
</property>
<property name="text">
......@@ -766,7 +769,7 @@
<bool>true</bool>
</property>
<property name="icon">
<iconset>
<iconset resource="../../../../cpp/QGIS/images/images.qrc">
<normaloff>:/images/themes/default/mActionSelectRadius.svg</normaloff>:/images/themes/default/mActionSelectRadius.svg</iconset>
</property>
<property name="text">
......@@ -775,7 +778,7 @@
</action>
<action name="actionExportMapsToImages">
<property name="icon">
<iconset>
<iconset resource="../../../../cpp/QGIS/images/images.qrc">
<normaloff>:/images/themes/default/mActionSaveMapAsImage.svg</normaloff>:/images/themes/default/mActionSaveMapAsImage.svg</iconset>
</property>
<property name="text">
......@@ -795,7 +798,7 @@
<bool>true</bool>
</property>
<property name="icon">
<iconset>
<iconset resource="../../../../cpp/QGIS/images/images.qrc">
<normaloff>:/images/themes/default/lockedGray.svg</normaloff>:/images/themes/default/lockedGray.svg</iconset>
</property>
<property name="text">
......@@ -823,7 +826,7 @@
</action>
<action name="mActionSaveEdits">
<property name="icon">
<iconset>
<iconset resource="../../../../cpp/QGIS/images/images.qrc">
<normaloff>:/images/themes/default/mActionSaveEdits.svg</normaloff>:/images/themes/default/mActionSaveEdits.svg</iconset>
</property>
<property name="text">
......@@ -838,7 +841,7 @@
<bool>true</bool>
</property>
<property name="icon">
<iconset>
<iconset resource="../../../../cpp/QGIS/images/images.qrc">
<normaloff>:/images/themes/default/mActionCapturePolygon.svg</normaloff>:/images/themes/default/mActionCapturePolygon.svg</iconset>
</property>
<property name="text">
......@@ -853,7 +856,7 @@
<bool>true</bool>
</property>
<property name="icon">
<iconset>
<iconset resource="../../../../cpp/QGIS/images/images.qrc">
<normaloff>:/images/themes/default/mActionToggleEditing.svg</normaloff>:/images/themes/default/mActionToggleEditing.svg</iconset>
</property>
<property name="text">
......@@ -865,7 +868,7 @@
<bool>true</bool>
</property>
<property name="icon">
<iconset>
<iconset resource="../../../../cpp/QGIS/images/images.qrc">
<normaloff>:/images/themes/default/mActionSelectRectangle.svg</normaloff>:/images/themes/default/mActionSelectRectangle.svg</iconset>
</property>
<property name="text">
......@@ -880,7 +883,7 @@
</action>
<action name="mActionDeselectFeatures">
<property name="icon">
<iconset>
<iconset resource="../../../../cpp/QGIS/images/images.qrc">
<normaloff>:/images/themes/default/mActionDeselectAll.svg</normaloff>:/images/themes/default/mActionDeselectAll.svg</iconset>
</property>
<property name="text">
......@@ -897,7 +900,7 @@
</action>
<action name="mActionOpenTable">
<property name="icon">
<iconset>
<iconset resource="../../../../cpp/QGIS/images/images.qrc">
<normaloff>:/images/themes/default/mActionOpenTable.svg</normaloff>:/images/themes/default/mActionOpenTable.svg</iconset>
</property>
<property name="text">
......@@ -909,7 +912,7 @@
</action>
<action name="mActionZoomToLayer">
<property name="icon">
<iconset>
<iconset resource="../../../../cpp/QGIS/images/images.qrc">
<normaloff>:/images/themes/default/mActionZoomToLayer.svg</normaloff>:/images/themes/default/mActionZoomToLayer.svg</iconset>
</property>
<property name="text">
......@@ -926,6 +929,24 @@
<string>Properties...</string>
</property>
</action>
<action name="actionSaveProject">
<property name="icon">
<iconset resource="../../../../cpp/QGIS/images/images.qrc">
<normaloff>:/images/themes/default/mActionFileSave.svg</normaloff>:/images/themes/default/mActionFileSave.svg</iconset>
</property>
<property name="text">
<string>Save to Project</string>
</property>
</action>
<action name="actionLoadProject">
<property name="icon">
<iconset resource="../../../../cpp/QGIS/images/images.qrc">
<normaloff>:/images/themes/default/mActionFileOpen.svg</normaloff>:/images/themes/default/mActionFileOpen.svg</iconset>
</property>
<property name="text">
<string>Load from Project</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>
......@@ -937,8 +958,8 @@
</customwidgets>
<resources>