-
Benjamin Jakimow authoredBenjamin Jakimow authored
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
mapcanvas.py 27.02 KiB
# -*- coding: utf-8 -*-
"""
/***************************************************************************
EO Time Series Viewer
-------------------
begin : 2015-08-20
git sha : $Format:%H$
copyright : (C) 2017 by HU-Berlin
email : benjamin.jakimow@geo.hu-berlin.de
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
"""
# noinspection PyPep8Naming
import os, time, types
from qgis.core import *
from qgis.gui import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtXml import QDomDocument
from . import SETTINGS
from .utils import *
from .timeseries import TimeSeriesDatum
from .crosshair import CrosshairDialog, CrosshairStyle
from .maptools import *
class MapCanvas(QgsMapCanvas):
saveFileDirectories = dict()
sigShowProfiles = pyqtSignal(SpatialPoint, str)
sigSpatialExtentChanged = pyqtSignal(SpatialExtent)
sigChangeDVRequest = pyqtSignal(QgsMapCanvas, str)
sigChangeMVRequest = pyqtSignal(QgsMapCanvas, str)
sigChangeSVRequest = pyqtSignal(QgsMapCanvas, QgsRasterRenderer)
sigMapRefreshed = pyqtSignal([float, float],[float])
sigCrosshairPositionChanged = pyqtSignal(SpatialPoint)
sigCrosshairVisibilityChanged = pyqtSignal(bool)
from .crosshair import CrosshairStyle
sigCrosshairStyleChanged = pyqtSignal(CrosshairStyle)
def __init__(self, parent=None):
super(MapCanvas, self).__init__(parent=parent)
self.mMapLayerStore = QgsProject.instance()
self.mLayerSources = []
self.mTSD = self.mMapView = None
#the canvas
self.mRasterLayersVisible = True
self.mVectorLayersVisible = True
self.mPluginLayerVisible = True
self.mIsRefreshing = False
self.mRenderingFinished = True
self.mRefreshStartTime = time.time()
self.mNeedsRefresh = False
self.mRendererRaster = None
self.mRendererVector = None
def onMapCanvasRefreshed(*args):
self.mIsRefreshing = False
self.mRenderingFinished = True
self.mIsRefreshing = False
t2 = time.time()
dt = t2 - self.mRefreshStartTime
self.sigMapRefreshed[float].emit(dt)
self.sigMapRefreshed[float, float].emit(self.mRefreshStartTime, t2)
self.mapCanvasRefreshed.connect(onMapCanvasRefreshed)
self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
self.setCanvasColor(SETTINGS.value('CANVAS_BACKGROUND_COLOR', QColor(0, 0, 0)))
self.setContextMenuPolicy(Qt.DefaultContextMenu)
#refreshTimer.timeout.connect(self.onTimerRefresh)
#self.extentsChanged.connect(lambda : self._setDataRefreshed())
self.extentsChanged.connect(lambda : self.sigSpatialExtentChanged.emit(self.spatialExtent()))
from timeseriesviewer.crosshair import CrosshairMapCanvasItem
self.mCrosshairItem = CrosshairMapCanvasItem(self)
def setMapLayerStore(self, store):
"""
Sets the QgsMapLayerStore or QgsProject instance that is used to register map layers
:param store: QgsMapLayerStore | QgsProject
"""
assert isinstance(store, (QgsMapLayerStore, QgsProject))
self.mMapLayerStore = store
def setLayerVisibility(self, cls, isVisible:bool):
"""
Sets if specific QgsMapLayer types are to be shown or not
:param cls: class-type or instance of QgsMapLayer
:param isVisible: bool
"""
assert isinstance(isVisible, bool)
if cls == QgsRasterLayer or isinstance(cls, QgsRasterLayer):
self.mRasterLayersVisible = isVisible
elif cls == QgsVectorLayer or isinstance(cls, QgsVectorLayer):
self.mVectorLayersVisible = isVisible
elif cls == QgsPluginLayer or isinstance(cls, QgsPluginLayer):
self.mPluginLayerVisible = isVisible
def renderingFinished(self)->bool:
"""
Returns whether the MapCanvas is processing a rendering task
:return: bool
"""
return self.mRenderingFinished
def mousePressEvent(self, event:QMouseEvent):
b = event.button() == Qt.LeftButton
if b and isinstance(self.mapTool(), QgsMapTool):
from timeseriesviewer.maptools import CursorLocationMapTool
b = isinstance(self.mapTool(), (QgsMapToolIdentify,
CursorLocationMapTool,
SpectralProfileMapTool, TemporalProfileMapTool))
super(MapCanvas, self).mousePressEvent(event)
if b:
ms = self.mapSettings()
pointXY = ms.mapToPixel().toMapCoordinates(event.x(), event.y())
spatialPoint = SpatialPoint(ms.destinationCrs(), pointXY)
self.setCrosshairPosition(spatialPoint)
def setMapView(self, mapView):
from timeseriesviewer.mapvisualization import MapView
assert isinstance(mapView, MapView)
self.mMapView = mapView
scope = self.expressionContextScope()
def setTSD(self, tsd):
from timeseriesviewer.timeseries import TimeSeriesDatum
assert isinstance(tsd, TimeSeriesDatum)
self.mTSD = tsd
scope = self.expressionContextScope()
scope.setVariable('map_date', str(tsd.date()), isStatic=True)
scope.setVariable('map_doy', tsd.doy(), isStatic=True)
scope.setVariable('map_sensor', tsd.sensor().name(), isStatic=False)
tsd.sensor().sigNameChanged.connect(lambda name : scope.setVariable('map_sensor', name))
def setFixedSize(self, size):
assert isinstance(size, QSize)
if self.size() != size:
super(MapCanvas, self).setFixedSize(size)
def setCrs(self, crs:QgsCoordinateReferenceSystem):
"""
Sets the
:param crs:
:return:
"""
assert isinstance(crs, QgsCoordinateReferenceSystem)
if self.crs() != crs:
self.setDestinationCrs(crs)
def crs(self)->QgsCoordinateReferenceSystem:
"""
Shortcut to return self.mapSettings().destinationCrs()
:return: QgsCoordinateReferenceSystem
"""
return self.mapSettings().destinationCrs()
def layerModel(self):
return self.mLayerSources
def setLayers(self, mapLayers):
"""
Set the map layers and, if necessary, registers the in a QgsMapLayerStore
:param mapLayers:
"""
self.mMapLayerStore.addMapLayers(mapLayers)
super(MapCanvas, self).setLayers(mapLayers)
def setLazyLayers(self, layerSources):
"""
Sets the list of map layers of map layer sources to be shown.
:param layerSources: [list-of-sources]
"""
assert isinstance(layerSources, (list, types.GeneratorType))
self.mLayerSources = layerSources
def isRefreshing(self)->bool:
return self.mIsRefreshing
def isVisibleToViewport(self)->bool:
return self.visibleRegion().boundingRect().isValid()
def visibleLayers(self)->list:
"""
Returns the QgsMpaLayers to be shown in the map
:return: [list-of-QgsMapLayers]
"""
for i in range(len(self.mLayerSources)):
if not isinstance(self.mLayerSources[i], QgsMapLayer):
lyr = toMapLayer(self.mLayerSources[i])
assert isinstance(lyr, QgsMapLayer), 'unable to convert {} into QgsMapLayer'.format(
self.mLayerSources[i])
self.mLayerSources[i] = lyr
if isinstance(lyr, QgsRasterLayer) and isinstance(self.mRendererRaster, QgsRasterRenderer):
lyr.setRenderer(self.mRendererRaster)
elif isinstance(lyr, QgsVectorLayer) and isinstance(self.mRendererVector, QgsFeatureRenderer):
lyr.setRenderer(self.mRendererVector)
visibleLayers = []
for l in self.mLayerSources:
if isinstance(l, QgsRasterLayer) and self.mRasterLayersVisible:
visibleLayers.append(l)
elif isinstance(l, QgsVectorLayer) and self.mVectorLayersVisible:
visibleLayers.append(l)
elif isinstance(l, QgsPluginLayer) and self.mPluginLayerVisible:
visibleLayers.append(l)
return visibleLayers
def timedRefresh(self):
"""
Call this to refresh all layers, visibility etc.
"""
visibleLayers = self.visibleLayers()
lastLayers = self.layers()
if self.mNeedsRefresh or visibleLayers != lastLayers:
self.mIsRefreshing = True
self.mRefreshStartTime = time.time()
self.setLayers(visibleLayers)
self.refresh()
self.mNeedsRefresh = False
def setCrosshairStyle(self, crosshairStyle:CrosshairStyle, emitSignal=True):
"""
Sets the CrosshairStyle
:param crosshairStyle: CrosshairStyle
:param emitSignal: Set to Fals to no emit a signal.
"""
from timeseriesviewer.crosshair import CrosshairStyle
if crosshairStyle is None:
self.mCrosshairItem.crosshairStyle.setShow(False)
self.mCrosshairItem.update()
else:
assert isinstance(crosshairStyle, CrosshairStyle)
self.mCrosshairItem.setCrosshairStyle(crosshairStyle)
if emitSignal:
self.sigCrosshairStyleChanged.emit(self.mCrosshairItem.crosshairStyle)
def crosshairStyle(self)->CrosshairStyle:
"""
Returns the style of the Crosshair.
:return: CrosshairStyle
"""
return self.mCrosshairItem.crosshairStyle
def setCrosshairPosition(self, spatialPoint:SpatialPoint, emitSignal=True):
"""
Sets the position of the Crosshair.
:param spatialPoint: SpatialPoint
:param emitSignal: True (default). Set False to avoid emitting sigCrosshairPositionChanged
:return:
"""
point = spatialPoint.toCrs(self.mapSettings().destinationCrs())
self.mCrosshairItem.setPosition(point)
if emitSignal:
self.sigCrosshairPositionChanged.emit(point)
def crosshairPosition(self)->SpatialPoint:
"""Returns the last crosshair position"""
return self.mCrosshairItem.mPosition
def setCrosshairVisibility(self, b:bool, emitSignal=True):
"""
Sets the Crosshair visbility
:param b: bool
"""
if b and self.mCrosshairItem.mPosition is None:
self.mCrosshairItem.setPosition(self.spatialCenter())
self.sigCrosshairPositionChanged.emit(self.spatialCenter())
if b != self.mCrosshairItem.visibility():
self.mCrosshairItem.setVisibility(b)
if emitSignal:
self.sigCrosshairVisibilityChanged.emit(b)
def layerPaths(self):
"""
:return: [list-of-str]
"""
return [str(l.source()) for l in self.layers()]
def pixmap(self):
"""
Returns the current map image as pixmap
:return: QPixmap
"""
return self.grab()
def contextMenu(self)->QMenu:
"""
Create the MapCanvas context menu
:return:
"""
menu = QMenu()
# add general options
menu.addSeparator()
m = menu.addMenu('Stretch to current extent...')
action = m.addAction('Linear')
action.triggered.connect(lambda : self.stretchToExtent(self.spatialExtent(), 'linear_minmax', p=0.0))
action = m.addAction('Linear 5%')
action.triggered.connect(lambda: self.stretchToExtent(self.spatialExtent(), 'linear_minmax', p=0.05))
action = m.addAction('Gaussian')
action.triggered.connect(lambda: self.stretchToExtent(self.spatialExtent(), 'gaussian', n=3))
action = menu.addAction('Zoom to Layer')
action.triggered.connect(lambda : self.setSpatialExtent(self.spatialExtentHint()))
action = menu.addAction('Refresh')
action.triggered.connect(lambda: self.refresh(True))
menu.addSeparator()
action = menu.addAction('Change crosshair style')
def onCrosshairChange(*args):
style = CrosshairDialog.getCrosshairStyle(parent=self,
mapCanvas=self,
crosshairStyle=self.mCrosshairItem.crosshairStyle)
if isinstance(style, CrosshairStyle):
self.setCrosshairStyle(style)
action.triggered.connect(onCrosshairChange)
if self.mCrosshairItem.visibility():
action = menu.addAction('Hide crosshair')
action.triggered.connect(lambda : self.setCrosshairVisibility(False))
else:
action = menu.addAction('Show crosshair')
action.triggered.connect(lambda: self.setCrosshairVisibility(True))
menu.addSeparator()
m = menu.addMenu('Copy...')
action = m.addAction('Date')
action.triggered.connect(lambda: self.sigChangeDVRequest.emit(self, 'copy_date'))
action = m.addAction('Sensor')
action.triggered.connect(lambda: self.sigChangeDVRequest.emit(self, 'copy_sensor'))
action = m.addAction('Path')
action.triggered.connect(lambda: self.sigChangeDVRequest.emit(self, 'copy_path'))
action = m.addAction('Map')
action.triggered.connect(lambda: QApplication.clipboard().setPixmap(self.pixmap()))
m = menu.addMenu('Map Coordinates...')
ext = self.spatialExtent()
center = self.spatialExtent().spatialCenter()
action = m.addAction('Extent (WKT Coordinates)')
action.triggered.connect(lambda: QApplication.clipboard().setText(ext.asWktCoordinates()))
action = m.addAction('Extent (WKT Polygon)')
action.triggered.connect(lambda: QApplication.clipboard().setText(ext.asWktPolygon()))
m.addSeparator()
action = m.addAction('Map Center (WKT)')
action.triggered.connect(lambda: QApplication.clipboard().setText(center.wellKnownText()))
action = m.addAction('Map Center')
action.triggered.connect(lambda: QApplication.clipboard().setText(center.toString()))
action = m.addAction('Map Extent (WKT)')
action.triggered.connect(lambda: QApplication.clipboard().setText(ext.wellKnownText()))
action = m.addAction('Map Extent')
action.triggered.connect(lambda: QApplication.clipboard().setText(ext.toString()))
m.addSeparator()
action = m.addAction('CRS (EPSG)')
action.triggered.connect(lambda: QApplication.clipboard().setText(self.crs().authid()))
action = m.addAction('CRS (WKT)')
action.triggered.connect(lambda: QApplication.clipboard().setText(self.crs().toWkt()))
action = m.addAction('CRS (Proj4)')
action.triggered.connect(lambda: QApplication.clipboard().setText(self.crs().toProj4()))
m = menu.addMenu('Save to...')
action = m.addAction('PNG')
action.triggered.connect(lambda : self.saveMapImageDialog('PNG'))
action = m.addAction('JPEG')
action.triggered.connect(lambda: self.saveMapImageDialog('JPG'))
menu.addSeparator()
from timeseriesviewer.utils import qgisInstance
actionAddRaster2QGIS = menu.addAction('Add raster layers(s) to QGIS')
actionAddRaster2QGIS.triggered.connect(lambda : self.addLayers2QGIS(
[l for l in self.layers() if isinstance(l, QgsRasterLayer)]
)
)
# QGIS 3: action.triggered.connect(lambda: QgsProject.instance().addMapLayers([l for l in self.layers() if isinstance(l, QgsRasterLayer)]))
actionAddVector2QGIS = menu.addAction('Add vector layer(s) to QGIS')
actionAddRaster2QGIS.triggered.connect(lambda : self.addLayers2QGIS(
#QgsProject.instance().addMapLayers(
[l for l in self.layers() if isinstance(l, QgsVectorLayer)]
)
)
# QGIS 3: action.triggered.connect(lambda: QgsProject.instance().addMapLayers([l for l in self.layers() if isinstance(l, QgsVectorLayer)]))
b = isinstance(qgisInstance(), QgisInterface)
for a in [actionAddRaster2QGIS, actionAddVector2QGIS]:
a.setEnabled(b)
menu.addSeparator()
action = menu.addAction('Hide date')
action.triggered.connect(lambda : self.sigChangeDVRequest.emit(self, 'hide_date'))
action = menu.addAction('Remove date')
action.triggered.connect(lambda: self.sigChangeDVRequest.emit(self, 'remove_date'))
menu.addSeparator()
action = menu.addAction('Hide map view')
action.triggered.connect(lambda: self.sigChangeMVRequest.emit(self, 'hide_mapview'))
action = menu.addAction('Remove map view')
action.triggered.connect(lambda: self.sigChangeMVRequest.emit(self, 'remove_mapview'))
return menu
def contextMenuEvent(self, event):
"""
Create and shows the MapCanvas context menu.
:param event: QEvent
"""
menu = self.contextMenu()
menu.exec_(event.globalPos())
def addLayers2QGIS(self, mapLayers):
from timeseriesviewer.utils import qgisInstance
iface = qgisInstance()
if isinstance(iface, QgisInterface):
grpNode= iface.layerTreeView().currentGroupNode()
assert isinstance(grpNode, QgsLayerTreeGroup)
for l in mapLayers:
if isinstance(l, QgsRasterLayer):
lqgis = iface.addRasterLayer(l.source(), l.name())
lqgis.setRenderer(l.renderer().clone())
if isinstance(l, QgsVectorLayer):
lqgis = iface.addVectorLayer(l.source(), l.name(), 'ogr')
lqgis.setRenderer(l.renderer().clone())
def stretchToCurrentExtent(self):
se = self.spatialExtent()
self.stretchToExtent(se)
def stretchToExtent(self, spatialExtent:SpatialExtent, stretchType='linear_minmax', **stretchArgs):
"""
:param spatialExtent: rectangle to get the image statistics for
:param stretchType: ['linear_minmax' (default), 'gaussian']
:param stretchArgs:
linear_minmax: 'p' percentage from min/max, e.g. +- 5 %
gaussian: 'n' mean +- n* standard deviations
:return:
"""
for l in self.layers():
if isinstance(l, QgsRasterLayer):
r = l.renderer()
dp = l.dataProvider()
newRenderer = None
extent = spatialExtent.toCrs(l.crs())
assert isinstance(dp, QgsRasterDataProvider)
def getCE(band):
stats = dp.bandStatistics(band, QgsRasterBandStats.All, extent, 500)
# hist = dp.histogram(band,100, stats.minimumValue, stats.maximumValue, extent, 500, False)
ce = QgsContrastEnhancement(dp.dataType(band))
d = (stats.maximumValue - stats.minimumValue)
if stretchType == 'linear_minmax':
ce.setContrastEnhancementAlgorithm(QgsContrastEnhancement.StretchToMinimumMaximum)
ce.setMinimumValue(stats.minimumValue + d * stretchArgs.get('p', 0))
ce.setMaximumValue(stats.maximumValue - d * stretchArgs.get('p', 0))
elif stretchType == 'gaussian':
ce.setContrastEnhancementAlgorithm(QgsContrastEnhancement.StretchToMinimumMaximum)
ce.setMinimumValue(stats.mean - stats.stdDev * stretchArgs.get('n', 3))
ce.setMaximumValue(stats.mean + stats.stdDev * stretchArgs.get('n', 3))
else:
# stretchType == 'linear_minmax':
ce.setContrastEnhancementAlgorithm(QgsContrastEnhancement.StretchToMinimumMaximum)
ce.setMinimumValue(stats.minimumValue)
ce.setMaximumValue(stats.maximumValue)
return ce
if isinstance(r, QgsMultiBandColorRenderer):
#newRenderer = QgsMultiBandColorRenderer(None, r.redBand(), r.greenBand(), r.blueBand())
newRenderer = cloneRenderer(r)
ceR = getCE(r.redBand())
ceG = getCE(r.greenBand())
ceB = getCE(r.blueBand())
newRenderer.setRedContrastEnhancement(ceR)
newRenderer.setGreenContrastEnhancement(ceG)
newRenderer.setBlueContrastEnhancement(ceB)
elif isinstance(r, QgsSingleBandPseudoColorRenderer):
newRenderer = cloneRenderer(r)
ce = getCE(newRenderer.band())
#stats = dp.bandStatistics(newRenderer.band(), QgsRasterBandStats.All, extent, 500)
shader = newRenderer.shader()
newRenderer.setClassificationMax(ce.maximumValue())
newRenderer.setClassificationMin(ce.minimumValue())
shader.setMaximumValue(ce.maximumValue())
shader.setMinimumValue(ce.minimumValue())
elif isinstance(r, QgsSingleBandGrayRenderer):
newRenderer = cloneRenderer(r)
s = ""
elif isinstance(r, QgsPalettedRasterRenderer):
s = ""
#newRenderer = cloneRenderer(r)
if newRenderer is not None:
self.sigChangeSVRequest.emit(self, newRenderer)
s = ""
def saveMapImageDialog(self, fileType):
lastDir = SETTINGS.value('CANVAS_SAVE_IMG_DIR', os.path.expanduser('~'))
from timeseriesviewer.utils import saveFilePath
from timeseriesviewer.mapvisualization import MapView
if isinstance(self.mTSD, TimeSeriesDatum) and isinstance(self.mMapView, MapView):
path = saveFilePath('{}.{}'.format(self.mTSD.date, self.mMapView.title()))
else:
path = 'mapcanvas'
path = jp(lastDir, '{}.{}'.format(path, fileType.lower()))
path, _ = QFileDialog.getSaveFileName(self, 'Save map as {}'.format(fileType), path)
if len(path) > 0:
self.saveAsImage(path, None, fileType)
SETTINGS.setValue('CANVAS_SAVE_IMG_DIR', os.path.dirname(path))
def setRenderer(self, renderer, refresh=True):
"""
Sets a QgsRasterRenderer or QgsFeatureRenderer which is applied or all QgsRasterLayers or QgsVectorLayer, respectively.
:param renderer: QgsRasterRenderer | QgsFeatureRenderer
:param refresh:
:return:
"""
if isinstance(renderer, QgsRasterRenderer):
self.mRendererRaster = renderer
for l in self.layers():
if isinstance(l, QgsRasterLayer):
l.setRenderer(renderer.clone())
if isinstance(renderer, QgsFeatureRenderer):
self.mRendererVector = renderer
for l in self.layers():
if isinstance(l, QgsVectorLayer):
l.setRenderer(renderer.clone())
def setSpatialExtent(self, spatialExtent:SpatialExtent):
"""
Sets the SpatialExtent to be shown.
:param spatialExtent: SpatialExtent
"""
assert isinstance(spatialExtent, SpatialExtent)
if self.spatialExtent() != spatialExtent:
spatialExtent = spatialExtent.toCrs(self.crs())
if spatialExtent:
ext = QgsRectangle(spatialExtent)
self.setCenter(ext.center())
self.setExtent(ext)
def spatialExtent(self)->SpatialExtent:
"""
Returns the map extent as SpatialExtent (extent + CRS)
:return: SpatialExtent
"""
return SpatialExtent.fromMapCanvas(self)
def spatialCenter(self)->SpatialPoint:
"""
Returns the map center as SpatialPoint (QgsPointXY + CRS)
:return: SpatialPoint
"""
return SpatialPoint.fromMapCanvasCenter(self)
def spatialExtentHint(self)->SpatialExtent:
"""
Returns a hint for a SpatialExtent, derived from the first raster layer in this
:return:
"""
crs = self.crs()
ext = SpatialExtent.world()
layers = self.layers() + self.visibleLayers()
for lyr in layers:
ext = SpatialExtent.fromLayer(lyr).toCrs(crs)
break
return ext
class CanvasBoundingBoxItem(QgsGeometryRubberBand):
def __init__(self, mapCanvas):
assert isinstance(mapCanvas, QgsMapCanvas)
super(CanvasBoundingBoxItem, self).__init__(mapCanvas)
self.canvas = mapCanvas
self.mCanvasExtents = dict()
self.mShow = True
self.mShowTitles = True
self.setIconType(QgsGeometryRubberBand.ICON_NONE)
def connectCanvas(self, canvas):
assert isinstance(canvas, QgsMapCanvas)
assert canvas != self.canvas
if canvas not in self.mCanvasExtents.keys():
self.mCanvasExtents[canvas] = None
canvas.extentsChanged.connect(lambda : self.onExtentsChanged(canvas))
canvas.destroyed.connect(lambda : self.disconnectCanvas(canvas))
self.onExtentsChanged(canvas)
def disconnectCanvas(self, canvas):
self.mCanvasExtents.pop(canvas)
def onExtentsChanged(self, canvas):
assert isinstance(canvas, QgsMapCanvas)
ext = SpatialExtent.fromMapCanvas(canvas)
ext = ext.toCrs(self.canvas.mapSettings().destinationCrs())
geom = QgsPolygon()
assert geom.fromWkt(ext.asWktPolygon())
self.mCanvasExtents[canvas] = (ext, geom)
self.refreshExtents()
def refreshExtents(self):
multi = QgsPolygon()
if self.mShow:
for canvas, t in self.mCanvasExtents.items():
ext, geom = t
multi.addGeometry(geom.clone())
self.setGeometry(multi)
def paint(self, painter, QStyleOptionGraphicsItem=None, QWidget_widget=None):
super(CanvasBoundingBoxItem, self).paint(painter)
if self.mShowTitles and self.mShow:
painter.setPen(Qt.blue);
painter.setFont(QFont("Arial", 30))
for canvas, t in self.mCanvasExtents.items():
ext, geom = t
ULpx = self.toCanvasCoordinates(ext.center())
txt = canvas.windowTitle()
painter.drawLine(0, 0, 200, 200);
painter.drawText(ULpx, txt)
def setShow(self, b):
assert isinstance(b, bool)
self.mShow = b
def setShowTitles(self, b):
assert isinstance(b, bool)
self.mShowTitles = b