# -*- coding: utf-8 -*-
### Copyright (C) 2008-2012 Antonio Valentino <a_valentino@users.sf.net>
### This file is part of GSDView.
### GSDView 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.
### GSDView is distributed in the hope that it will be useful,
### but WITHOUT ANY WARRANTY; without even the implied warranty of
### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
### GNU General Public License for more details.
### You should have received a copy of the GNU General Public License
### along with GSDView; if not, write to the Free Software
### Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
'''Overview pannel for GDAL raster bands.'''
import logging
from qt import QtCore, QtGui
from gsdview.qt4support import overrideCursor
from gsdview.gdalbackend import gdalsupport
__author__ = 'Antonio Valentino <a_valentino@users.sf.net>'
__date__ = '$Date$'
__revision__ = '$Revision$'
[docs]class NavigationGraphicsView(QtGui.QGraphicsView):
'''Graphics view for dataset navigation.
The view usually displays an auto-scalled low resolution overview
of the scene with a red box indicating the area currently displayed
in the high resolution view.
:SIGNALS:
* :attr:`mousePressed`
* :attr:`mouseMoved`
'''
BOXCOLOR = QtGui.QColor(QtCore.Qt.red)
#: SIGNAL: it is emitted when a mouse button is presses on the view
#:
#: :param point:
#: the scene position
#: :param mousebutton:
#: the ID of the pressed button
#: :param dragmode:
#: current darg mode
#:
#: :C++ signature: `void mousePressed(QPointF, Qt::MouseButtons,
#: QGraphicsView::DragMode)`
mousePressed = QtCore.Signal(QtCore.QPointF, QtCore.Qt.MouseButtons,
QtGui.QGraphicsView.DragMode)
#: SIGNAL: it is emitted when the mouse is moved on the view
#:
#: :param point:
#: the scene position
#: :param mousebutton:
#: the ID of the pressed button
#: :param dragmode:
#: current darg mode
#:
#: :C++ signature: `void mouseMoved(QPointF, Qt::MouseButtons,
#: QGraphicsView::DragMode)`
mouseMoved = QtCore.Signal(QtCore.QPointF, QtCore.Qt.MouseButtons,
QtGui.QGraphicsView.DragMode)
def __init__(self, parent=None, **kwargs):
super(NavigationGraphicsView, self).__init__(parent, **kwargs)
self._viewbox = None
self._autoscale = True
self.setMouseTracking(True)
[docs] def getbox(self):
return self._viewbox
[docs] def setbox(self, box):
assert isinstance(box, (QtCore.QRect, QtCore.QRectF))
self._viewbox = box
if self.isVisible():
# @WARNING: calling "update" on the scene causes a repaint of
# *all* attached views and for each view the entire
# exposedRect is updated.
# Using QGraphicsView.invalidateScene with the
# QtGui.QGraphicsScene.ForegroundLayer parameter
# should be faster and repaint only one layer of the
# current view.
# @TODO: check
#self.invalidateScene(self.sceneRect(),
# QtGui.QGraphicsScene.ForegroundLayer)
self.scene().update()
viewbox = property(getbox, setbox, doc='viewport box in scene coordinates')
[docs] def drawForeground(self, painter, rect):
if not self.viewbox:
return
pen = painter.pen()
try:
box = self.viewbox.intersected(self.sceneRect())
painter.setPen(self.BOXCOLOR)
painter.drawRect(box)
#painter.drawConvexPolygon(self.viewbox) #@TODO: check
finally:
painter.setPen(pen)
[docs] def fitInView(self, rect=None, aspectRatioMode=QtCore.Qt.KeepAspectRatio):
if not rect:
scene = self.scene()
if scene:
rect = scene.sceneRect()
else:
return
QtGui.QGraphicsView.fitInView(self, rect, aspectRatioMode)
def _getAutoscale(self):
return self._autoscale
def _setAutoscale(self, flag):
self._autoscale = bool(flag)
if self._autoscale:
self.fitInView()
else:
self.setMatrix(QtGui.QMatrix())
self.update()
autoscale = property(_getAutoscale, _setAutoscale)
[docs] def resizeEvent(self, event):
if self.autoscale:
self.fitInView()
return QtGui.QGraphicsView.resizeEvent(self, event)
# @TODO: use event filters
[docs] def mousePressEvent(self, event):
pos = self.mapToScene(event.pos())
self.mousePressed.emit(pos, event.buttons(), self.dragMode())
return QtGui.QGraphicsView.mousePressEvent(self, event)
[docs] def mouseMoveEvent(self, event):
pos = self.mapToScene(event.pos())
self.mouseMoved.emit(pos, event.buttons(), self.dragMode())
return QtGui.QGraphicsView.mouseMoveEvent(self, event)
[docs]class BandOverviewDock(QtGui.QDockWidget):
OVRMAXSIZE = 10 * 1024 ** 2 # 10MB
def __init__(self, app, flags=QtCore.Qt.WindowFlags(0), **kwargs):
#title = self.tr('Dataset Browser')
super(BandOverviewDock, self).__init__('Band Overview', app, flags,
**kwargs)
#self.setObjectName('datasetBroeserPanel') # @TODO: check
self.app = app # @TODO: check
self.graphicsview = NavigationGraphicsView(self)
self.setWidget(self.graphicsview)
# @TODO: understand why this doewn't work
#
# self.graphicsview.installEventFilter(self)
#
#def eventFilter(self, obj, event):
# if obj.scene():
# if event.type() in (QtCore.QEvent.MouseButtonPress,
# QtCore.QEvent.MouseMove):
# if event.buttons() & QtCore.Qt.LeftButton:
# pos = obj.mapToScene(event.pos())
# self.centerMainViewOn(pos)
# return obj.eventFilter(obj, event)
@overrideCursor
[docs] def setItem(self, item):
assert item.backend == 'gdalbackend'
self.graphicsview.setUpdatesEnabled(False)
try:
self.reset()
try:
level = gdalsupport.ovrLevelForSize(item, self.OVRMAXSIZE)
# @NOTE: use GREATER for overview level to ensure an overview
# size smaller than OVRMAXSIZE
ovrindex = gdalsupport.ovrBestIndex(item, level, 'GREATER')
except gdalsupport.MissingOvrError:
logging.info('no overview available or available overviews '
'are too large')
return
scene = item.scene
self.graphicsview.setScene(scene)
self.graphicsview.setSceneRect(scene.sceneRect())
if not self.graphicsview.autoscale:
ovrlevel = gdalsupport.ovrLevels(item)[ovrindex]
matrix = QtCore.QMatirx(ovrlevel, 0, 0, ovrlevel, 0, 0)
self.graphicsview.setMatrix(matrix)
else:
self.graphicsview.fitInView()
self.updateMainViewBox()
finally:
self.graphicsview.setUpdatesEnabled(True)
self.graphicsview.update()
[docs] def centerMainViewOn(self, scenepos):
view = self.app.currentGraphicsView()
if view:
if self.graphicsview.scene():
# @TODO: check
assert view.scene() == self.graphicsview.scene()
view.centerOn(scenepos)
@QtCore.Slot()
@QtCore.Slot(QtGui.QGraphicsView)
[docs] def updateMainViewBox(self, srcview=None):
if not self.graphicsview.scene():
return
if not srcview:
# @TODO: check API
srcview = self.app.currentGraphicsView()
elif srcview is not self.app.currentGraphicsView():
# current view not yet updated: do nothing
return
if srcview:
assert srcview.scene() == self.graphicsview.scene() # @TODO: check
hbar = srcview.horizontalScrollBar()
vbar = srcview.verticalScrollBar()
# @TODO: bug report: mapping to scene seems to introduce a
# spurious offset "x1 = 2*x0" and y1 = 2*y0;
# this doesn't happen for "w" and "h"
#polygon = srcview.mapToScene(hbar.value(), vbar.value(),
# hbar.pageStep(), vbar.pageStep())
#@TODO: in case of rotations it should be better keep using
# a polygon
#self.graphicsview.viewbox = polygon.boundingRect()
# @NOTE: this is a workaround; mapToScene should be used instead
rect = QtCore.QRectF(hbar.value(), vbar.value(),
hbar.pageStep(), vbar.pageStep())
transform = srcview.transform().inverted()[0]
self.graphicsview.viewbox = transform.mapRect(rect)
[docs] def reset(self):
self.graphicsview.setScene(None)
[docs]class OverviewController(QtCore.QObject):
def __init__(self, app, **kwargs):
super(OverviewController, self).__init__(app, **kwargs)
self.app = app
self.panel = BandOverviewDock(app)
self.panel.setObjectName('bandOverviewPanel') # @TODO: check
app.addDockWidget(QtCore.Qt.LeftDockWidgetArea, self.panel)
# Connect signals
app.mdiarea.subWindowActivated.connect(self.onSubWindowChanged)
app.subWindowClosed.connect(self.onWindowClosed)
app.datamodel.itemChanged.connect(self.onItemChanged)
app.monitor.scrolled.connect(self.panel.updateMainViewBox)
app.monitor.viewportResized.connect(self.panel.updateMainViewBox)
app.monitor.resized.connect(self.panel.updateMainViewBox)
self.panel.graphicsview.mousePressed.connect(self.onNewPos)
self.panel.graphicsview.mouseMoved.connect(self.onNewPos)
@QtCore.Slot()
@QtCore.Slot(QtGui.QMdiSubWindow)
[docs] def onSubWindowChanged(self, subwin=None):
if subwin is None:
subwin = self.app.mdiarea.activeSubWindow()
try:
item = subwin.item
except AttributeError:
self.panel.reset()
else:
self.panel.setItem(item)
@QtCore.Slot()
[docs] def onWindowClosed(self):
if len(self.app.mdiarea.subWindowList()) == 0:
self.panel.reset()
#@QtCore.Slot(QtGui.QStandardItem)
@QtCore.Slot('QStandardItem*') # @TODO:check
[docs] def onItemChanged(self, item):
if hasattr(item, 'scene'):
srcview = self.app.currentGraphicsView()
if (srcview and srcview.scene() is item.scene
and not self.panel.graphicsview.scene()):
self.panel.setItem(item)
# @TODO: translate into an event handler
@QtCore.Slot(QtCore.QPointF, QtCore.Qt.MouseButtons,
QtGui.QGraphicsView.DragMode)
[docs] def onNewPos(self, pos, buttons, dragmode):
if buttons & QtCore.Qt.LeftButton:
self.panel.centerMainViewOn(pos)