###############################################################################
##
## Copyright (C) 2014-2015, New York University.
## Copyright (C) 2011-2014, NYU-Poly.
## Copyright (C) 2006-2011, University of Utah.
## All rights reserved.
## Contact: contact@vistrails.org
##
## This file is part of VisTrails.
##
## "Redistribution and use in source and binary forms, with or without
## modification, are permitted provided that the following conditions are met:
##
##  - Redistributions of source code must retain the above copyright notice,
##    this list of conditions and the following disclaimer.
##  - Redistributions in binary form must reproduce the above copyright
##    notice, this list of conditions and the following disclaimer in the
##    documentation and/or other materials provided with the distribution.
##  - Neither the name of the New York University nor the names of its
##    contributors may be used to endorse or promote products derived from
##    this software without specific prior written permission.
##
## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
## AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
## THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
## PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
## CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
## EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
## PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
## OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
## WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
## OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
## ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
##
###############################################################################
""" This containing a subclassed QGraphicsView that allows
zoom/pan/select inside the graphics view and supports PIP as well

QInteractiveGraphicsScene
QInteractiveGraphicsView
QPIPGraphicsView
"""
from __future__ import division

from vistrails.core import debug
from PyQt4 import QtCore, QtGui
from vistrails.gui.theme import CurrentTheme
from vistrails.core.configuration import get_vistrails_configuration
import vistrails.core.system
import math
from vistrails.gui.qt import qt_super
################################################################################

class QGraphicsItemInterface(object):
    """
    QGraphicsItem will override the default QGraphicsItem mouseRelease
    event to let it fires only 1 ItemSelectionChanged event. This is
    due to the clearSelection() in
    qgraphicsitem.cpp::QGrahpicsItem::mouseReleaseEvent
    
    """
    pass

class QGraphicsRubberBandItem(QtGui.QGraphicsRectItem):
    """
    QGraphicsRubberBandItem try to replace QRubberBand to have a
    unified look and feel on all platform. In the end, it mimics the
    Windows look. It is just a rectangle with a 50% transparency
    
    """
    def paint(self, painter, option, widget=None):
        """ paint(painter: QPainter, option: QStyleOptionGraphicsItem,
                  widget: QWidget) -> None
        Peform actual painting of the rubber band
        
        """
        painter.setOpacity(0.5)
        painter.fillRect(self.rect(),
                         CurrentTheme.SELECTION_BOX_BRUSH)
        painter.setOpacity(1.0)
        painter.setPen(CurrentTheme.SELECTION_BOX_PEN)
        painter.drawRect(self.rect())

class QInteractiveGraphicsScene(QtGui.QGraphicsScene):
    """
    QInteractiveGraphicsScene expands QGraphicsScene to allow panning
    freely over the view are
    
    """
    def __init__(self, parent=None):
        """ QInteractiveGraphicsScene(parent: QWidget)
                                      -> QInteractiveGraphicsScene
        Initialize the actual scene bounding rect
        """
        QtGui.QGraphicsScene.__init__(self, parent)
        self.sceneBoundingRect = QtCore.QRectF()
        self.multiSelecting = False
        
    def updateSceneBoundingRect(self, keep_square=False):
        """ updateSceneBoundingRect() -> None        
        Compute the actual bounding rect of all shapes, then update
        the scene rect to be much wider for panning
        
        """
        self.sceneBoundingRect = QtCore.QRectF()
        for item in self.items():
            rect = item.sceneBoundingRect()
            self.sceneBoundingRect = self.sceneBoundingRect.united(rect)

        # Keep a minimum size
        minWDiff = 0
        minHDiff = 0
        min = CurrentTheme.BOUNDING_RECT_MINIMUM
        if self.sceneBoundingRect.width() < min:
            minWDiff = min-self.sceneBoundingRect.width()
        if self.sceneBoundingRect.height() < min:
            minHDiff = min-self.sceneBoundingRect.height()
        self.sceneBoundingRect.adjust(-minWDiff/2, -minHDiff/2, 
                                       minWDiff/2, minHDiff/2)

        if keep_square:
            diff = abs(self.sceneBoundingRect.width()-
                       self.sceneBoundingRect.height())
            if self.sceneBoundingRect.width()<self.sceneBoundingRect.height():
                self.sceneBoundingRect.adjust(-diff/2, 0, diff/2, 0)
            else:
                self.sceneBoundingRect.adjust(0, -diff/2, 0, diff/2)
        panRect = self.sceneBoundingRect.adjusted(
            -self.sceneBoundingRect.width()*2,
            -self.sceneBoundingRect.height()*2,
            self.sceneBoundingRect.width()*2,
            self.sceneBoundingRect.height()*2)
        if panRect.width()<1e-6 and panRect.height()<1e-6:
            panRect = QtCore.QRectF(-1000,-1000,2000,2000)
        self.setSceneRect(panRect)

        # Reset cache
        for view in self.views():
            view.resetCachedContent()


    def fitToView(self, view, recompute_bounding_rect=False):
        """ fitToView(view: QGraphicsView,
                      recompute_bounding_rect=False) -> None
        Adjust view to fit and center the whole scene. If recompute_bounding_rect is
        False, does not recompute bounds, and instead uses previous one.
        
        """
        if recompute_bounding_rect:
            self.updateSceneBoundingRect()
        view.centerOn(self.sceneBoundingRect.center())
        view.fitInView(self.sceneBoundingRect, QtCore.Qt.KeepAspectRatio)
            
    def fitToAllViews(self, recompute_bounding_rect=False):
        """ fitToAllViews(recompute_bounding_rect=False) -> None
        Adjust all views using this scene to fit and center the whole scene

        if recompute_bounding_rect is False, uses previous bounding rect.
        
        """
        if recompute_bounding_rect:
            self.updateSceneBoundingRect()
        for view in self.views():
            self.fitToView(view, False)

    def clearItems(self):
        """ clearShapes() -> None
        Remove and delete all items belonging to this scene
        
        """
        self.removeItems(self.items())

    def removeItems(self, itemList):
        """ removeItems(itemList: sequence of [QGraphicsItem]) -> None
        Remove all items in itemList
        
        """
        for item in itemList:
            if item.scene():
                self.removeItem(item)

    def saveToPDF(self, filename):
        self.updateSceneBoundingRect(False)
        printer = QtGui.QPrinter()
        printer.setOutputFormat(QtGui.QPrinter.PdfFormat)
        printer.setOutputFileName(filename)
        b_rect = self.sceneBoundingRect
        debug.debug("%sx%s" % (b_rect.width(), b_rect.height()))
        printer.setPaperSize(QtCore.QSizeF(b_rect.width(), b_rect.height()),
                             QtGui.QPrinter.Point)
        painter = QtGui.QPainter(printer)
        brush = self.backgroundBrush()
        self.setBackgroundBrush(QtGui.QBrush(QtGui.QColor(255,255,255)))
        self.render(painter, QtCore.QRectF(), b_rect)
        painter.end()
        self.setBackgroundBrush(brush)
    
    def saveToPNG(self, filename, width=None):
        try:
            self.updateSceneBoundingRect(False)
            b_rect = QtCore.QRectF(self.sceneBoundingRect)
            b_rect.setWidth(math.floor(b_rect.width()))
            b_rect.setHeight(math.floor(b_rect.height()))
            debug.debug("PNG bounding box %sx%s" % (b_rect.width(), b_rect.height()))
            pixmap = QtGui.QPixmap(QtCore.QSize(int(math.floor(b_rect.width())),
                                                int(math.floor(b_rect.height()))))
            debug.debug("PNG pixmap size: %s"%str(pixmap.size()))
            painter = QtGui.QPainter(pixmap)
            painter.setRenderHint(QtGui.QPainter.Antialiasing)
            brush = self.backgroundBrush()
            self.setBackgroundBrush(QtGui.QBrush(QtGui.QColor(255,255,255)))
            self.render(painter, QtCore.QRectF(), b_rect)
            painter.end()
            if width is not None:
                pixmap = pixmap.scaledToWidth(width, QtCore.Qt.SmoothTransformation)
            pixmap.save(filename)
            self.setBackgroundBrush(brush)
        except Exception, e:
            debug.critical("Exception saving to PNG", e)

class QInteractiveGraphicsView(QtGui.QGraphicsView):
    """
    QInteractiveGraphicsView is QGraphicsView with abilities to
    zoom/span with right/mid click
    
    """
    def __init__(self, parent=None):
        """ QInteractiveGraphicsView(parent: QWidget)
                                     -> QInteractiveGraphicsView
        Initialize the graphics view with interactive options
        
        """
        QtGui.QGraphicsView.__init__(self, parent)
        self.setInteractive(True)
#        self.setCacheMode(QtGui.QGraphicsView.CacheBackground)
        self.setResizeAnchor(QtGui.QGraphicsView.AnchorViewCenter)
        self.setRenderHints (QtGui.QPainter.Antialiasing |
                             QtGui.QPainter.TextAntialiasing |
                             QtGui.QPainter.SmoothPixmapTransform)
        self.scaleMax = 2000
        self.scaleRatio = self.scaleMax/10
        self.scaleOffset = 700
        self.currentScale = self.scaleMax/2
        self.startScroll = (0,0)
        self.lastPos = QtCore.QPoint(0,0)
        self.pipScene = None
        self.pipFrame = None
        self.resetButton = None
        self.selectionBox = QGraphicsRubberBandItem(None)
        self.startSelectingPos = None
        self.setProperty('captureModifiers', 1)
        self.defaultCursorState = 0
        self.setCursorState(self.defaultCursorState)
        self.canSelectBackground = True
        self.canSelectRectangle = True
        
        if QtCore.QT_VERSION >= 0x40600:
            self.viewport().grabGesture(QtCore.Qt.PinchGesture)
        self.gestureStartScale = None

        conf = get_vistrails_configuration()
        conf.subscribe('showScrollbars', self.setScrollbarPolicy)
        self.setScrollbarPolicy('showScrollbars', conf.showScrollbars)

    def setScrollbarPolicy(self, field, value):
        if value:
            self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
            self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
        else:
            self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
            self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)

    def viewportEvent(self, event):
        if QtCore.QT_VERSION >= 0x40600 and event.type() == QtCore.QEvent.Gesture:
            pinch = event.gesture(QtCore.Qt.PinchGesture)
            if pinch:
                changeFlags = pinch.changeFlags()
                if changeFlags & QtGui.QPinchGesture.ScaleFactorChanged:
                    if self.gestureStartScale is None:
                        self.computeScale()
                        self.gestureStartScale = self.currentScale
                    newScale = self.gestureStartScale + self.scaleMax * \
                        math.log(pinch.property("scaleFactor"))/2
                    # Clamp the scale
                    if newScale<0: newScale = 0
                    if newScale>self.scaleMax: newScale = self.scaleMax
                    self.currentScale = newScale
                    self.updateMatrix()
                if pinch.state() == QtCore.Qt.GestureFinished:
                    self.gestureStartScale = None
                return True
        return QtGui.QGraphicsView.viewportEvent(self, event)

    def modifiersPressed(self, modifiers):
        """ modifiersPressed(modifiers: QtCore.Qt.KeyboardModifiers) -> None
        Notification when one of the modifier keys has been pressed
        
        """
        self.validateCursorState(modifiers)
        
    def modifiersReleased(self):
        """ modifiersReleased() -> None
        Notification when one of the modifier keys has been released
        
        """
        self.validateCursorState()

    def findCursorState(self, modifiers=None):
        """ findCursorState(modifiers: QtCore.Qt.KeyboardModifiers) -> None
        Check the keyboard modifiers and return the cursor state

        """
        if not self.isActiveWindow():
            return self.defaultCursorState
        if modifiers==None:
            modifiers = QtGui.QApplication.keyboardModifiers()
        shift = modifiers & QtCore.Qt.ShiftModifier
        alt = modifiers & QtCore.Qt.AltModifier
        meta = modifiers & QtCore.Qt.MetaModifier or (alt and shift)
        ctrl = modifiers & QtCore.Qt.ControlModifier
        if shift and (not alt) and (not ctrl) and (not meta):
            return 1
        elif meta and (not ctrl):
            return 2
        else:
            return self.defaultCursorState

    def validateCursorState(self, modifiers=None):
        """ validateCursorState(modifiers: QtCore.Qt.KeyboardModifiers) -> None
        Check the keyboard modifiers to change the cursor shape correspondingly
        
        """        
        self.setCursorState(self.findCursorState(modifiers))

    def enterEvent(self, event):
        """ enterEvent(event: QEnterEvent) -> None        
        Check the modifiers state when the mouse enter the
        canvas. Then update the mouse functionality appropriately
        
        """
        self.validateCursorState()
        return QtGui.QGraphicsView.enterEvent(self, event)
        # super(QInteractiveGraphicsView, self).enterEvent(event)

    def setCursorState(self, state):
        """ setCursorState(state: int) -> None        
        Update the cursor shape

        Keyword arguments:
        state - 0: selecting (default)
                1: pan
                2: zoom
                3: panning
        
        """
        if state==0:
            self.viewport().setCursor(CurrentTheme.SELECT_CURSOR)
        elif state==1:
            self.viewport().setCursor(CurrentTheme.OPEN_HAND_CURSOR)
        elif state==2:
            self.viewport().setCursor(CurrentTheme.ZOOM_CURSOR)
        elif state==3:
            self.viewport().setCursor(CurrentTheme.CLOSE_HAND_CURSOR)
        
    def setDefaultCursorState(self, state):
        """ setDefaultCursorState(state: int) -> None
        Set the default cursor state when no modifier key is pressed
        
        """
        self.defaultCursorState = state
        self.validateCursorState()

    def translateButton(self, event):
        """ translateButton(event: QInputEvent) -> None
        Translate mouse button and modifiers into a virtual mouse button
        
        """
        if event.buttons() & QtCore.Qt.LeftButton:
            state = self.findCursorState(event.modifiers())
            state2Button = {0: QtCore.Qt.LeftButton,
                            1: QtCore.Qt.MidButton,
                            2: QtCore.Qt.RightButton}
            if state2Button.has_key(state):
                return state2Button[state]
        return event.buttons()

    def mousePressEvent(self, e):
        """ mousePressEvent(e: QMouseEvent) -> None        
        Handle mouse click event, use Qt rubber band for left-click
        selection and prepare for zoom/pan on right/mid click
        
        """
        scenePos = self.mapToScene(e.pos())
        item = self.scene().itemAt(scenePos)
        buttons = self.translateButton(e)
        if buttons == QtCore.Qt.LeftButton:
            if item is None:
                if self.scene():
                    self.scene().multiSelecting = True
                    self.scene().addItem(self.selectionBox)
                    self.selectionBox.setZValue(1000)
                    
                    self.startSelectingPos = self.mapToScene(e.pos())
                    rect = QtCore.QRectF(self.startSelectingPos,
                                         QtCore.QSizeF(0,0))
                    self.selectionBox.setRect(rect)
                    self.selectionBox.setVisible(self.canSelectRectangle)
            else:
                QtGui.QGraphicsView.mousePressEvent(self, e)
                # super(QInteractiveGraphicsView, self).mousePressEvent(e)
        else:
            if buttons & QtCore.Qt.RightButton:
                self.computeScale()
                if item is None:
                    self.setCursorState(2)
                else:
                    QtGui.QGraphicsView.mousePressEvent(self, e)
            elif buttons & QtCore.Qt.MidButton:
                self.setCursorState(3)
                self.computeScale()
                self.startScroll = (self.horizontalScrollBar().value(),
                                    self.verticalScrollBar().value())
            self.lastPos = QtCore.QPoint(QtGui.QCursor.pos())
            self.setDragMode(QtGui.QGraphicsView.NoDrag)

    def mouseMoveEvent(self, e):
        """ mouseMoveEvent(e: QMouseEvent) -> None        
        Handle right click (zoom) and mid click (pan). This function
        uses QCursor globalPos instead of e.globalX() and e.globalY()
        because of their flaky values during transformation
        
        """
        self.setUpdatesEnabled(False)
        buttons = self.translateButton(e)
        if buttons == QtCore.Qt.LeftButton:
            if self.startSelectingPos:
                dis = self.mapToScene(e.pos())-self.startSelectingPos
                rect = QtCore.QRectF(self.startSelectingPos,
                                 QtCore.QSizeF(dis.x(), dis.y()))
                self.selectionBox.prepareGeometryChange()
                self.selectionBox.setRect(rect)
                self.selectModules()
            else:
                QtGui.QGraphicsView.mouseMoveEvent(self, e)
                # super(QInteractiveGraphicsView, self).mousePressEvent(e)
        elif self.lastPos:
            if buttons == QtCore.Qt.RightButton:
                globalPos = QtGui.QCursor.pos()
            
                # Set up new scale based on pixels moved
                newScale = self.currentScale + globalPos.y() - self.lastPos.y()

                # Clamp the scale
                if newScale<0: newScale = 0
                if newScale>self.scaleMax: newScale = self.scaleMax

                # Update the scale and transformation matrix
                self.currentScale = newScale
                self.updateMatrix()

                # Need to update last position
                self.lastPos = QtCore.QPoint(globalPos)
                
            elif buttons == QtCore.Qt.MidButton:
                globalPos = QtGui.QCursor.pos()
                
                # Just need to pan the scroll bar
                self.horizontalScrollBar().setValue(self.startScroll[0] -
                                                    globalPos.x() +
                                                    self.lastPos.x())
                self.verticalScrollBar().setValue(self.startScroll[1] -
                                                  globalPos.y() +
                                                  self.lastPos.y())
        else:
            self.validateCursorState(e.modifiers())
            QtGui.QGraphicsView.mouseMoveEvent(self, e)
        self.setUpdatesEnabled(True)

    def mouseReleaseEvent(self, e):
        """ mouseReleaseEvent(self, e: QMouseEvent) -> None
        Mark box selecting as off
        """
        if self.startSelectingPos:
            self.selectModules()
            self.startSelectingPos = None
            self.selectionBox.setVisible(False)
            self.scene().removeItem(self.selectionBox)
            self.scene().multiSelecting = False
        self.lastPos = None
        self.validateCursorState(e.modifiers())
        self.setUpdatesEnabled(True)
        QtGui.QGraphicsView.mouseReleaseEvent(self, e)
        # super(QInteractiveGraphicsView, self).mouseReleaseEvent(e)

    def mouseDoubleClickEvent(self, e):
        """ mouseDoubleClickEvent(self, e: QMouseEvent) -> None
        Try to avoid unselect if double-click on the background        
        """
        if not self.canSelectBackground:
            return
        else:
            qt_super(QInteractiveGraphicsView, self).mouseDoubleClickEvent(e)

    def selectModules(self):
        """ selectModules() -> None
        Select all modules inside the self.selectionBox
        
        """
        if self.canSelectRectangle:
            br = self.selectionBox.sceneBoundingRect()
        else:
            br = QtCore.QRectF(self.startSelectingPos,
                              self.startSelectingPos)
        if not self.canSelectBackground:
            items = self.scene().items(br) 
            if len(items)==0 or items==[self.selectionBox]:
                return
        path = QtGui.QPainterPath()
        path.addRect(br)
        self.scene().setSelectionArea(path)
        
    def updateMatrix(self):
        """ updateMatrix() -> None
        Update the view matrix with the current scale
        
        """
        matrix = QtGui.QMatrix()
        power = float(self.currentScale - self.scaleMax/2 - self.scaleOffset
                      )/self.scaleRatio
        scale = pow(2.0, power)
        matrix.scale(scale, scale)
        self.setMatrix(matrix)

    def computeScale(self):
        """ computeScale() -> None
        Compute the current scale based on the view matrix
        
        """
        self.currentScale = (math.log(self.matrix().m11(), 2.0)*
                             self.scaleRatio + self.scaleMax/2 +
                             self.scaleOffset)

    def setPIPScene(self, scene):
        """ setPIPScene(scene: QGraphicsScene) -> None        
        Set the Picture-In-Picture scene fo the current GraphicsView
        to 'scene'
        
        """
        self.pipScene = scene

    def setPIPEnabled(self, enabled=True):
        """ setPIPEnabled(enabled: boolean) -> None        
        Enable/Disable PIP view
        
        """
        if self.pipScene:
            if enabled:
                if self.pipFrame==None:
                    self.pipFrame = QPIPGraphicsView(self)
                    self.pipFrame.graphicsView.setScene(self.pipScene)
                    self.pipFrame.move(self.width(), 0)
                self.pipFrame.show()
                self.pipFrame.updateGeometry()
            elif self.pipFrame!=None:
                self.pipFrame.hide()

    def setQueryEnabled(self, enabled=True):
        """ setQueryEnabled(enabled: bool) -> None
        Enable/Disable the query reset button

        """
        if enabled:
            if not self.resetButton:
                self.resetButton = QResetQueryButton(self)
                self.connect(self.resetButton,
                             QtCore.SIGNAL('resetQuery()'),
                             self.resetQuery)
            self.resetButton.show()
            self.resetButton.updateGeometry()
        else:
            if self.resetButton:
                self.resetButton.hide()
            self.scene().update()

    def resetQuery(self):
        """ resetQuery() -> None
        pass the signal along
        
        """
        self.emit(QtCore.SIGNAL('resetQuery()'))

    def resizeEvent(self, event):
        """ resizeEvent(event: QResizeEvent) -> None
        Make sure the pip frame is inside the graphics view
        """
        if self.pipFrame:
            self.pipFrame.updateGeometry()
        if self.resetButton:
            self.resetButton.updateGeometry()
        return QtGui.QGraphicsView.resizeEvent(self, event)
        # super(QInteractiveGraphicsView, self).resizeEvent(event)

    def zoomToFit(self):
        self.scene().fitToView(self, True)
        self.computeScale()

    def zoomIn(self):
        self.setUpdatesEnabled(False)
        newScale = self.currentScale + 100.0

        # Clamp the scale
        if newScale<0: newScale = 0
        if newScale>self.scaleMax: newScale = self.scaleMax
        
        # Update the scale and transformation matrix
        self.currentScale = newScale
        self.updateMatrix()
        self.setUpdatesEnabled(True)

    def zoomOut(self):
        self.setUpdatesEnabled(False)
        newScale = self.currentScale - 100.0

        # Clamp the scale
        if newScale<0: newScale = 0
        if newScale>self.scaleMax: newScale = self.scaleMax
        
        # Update the scale and transformation matrix
        self.currentScale = newScale
        self.updateMatrix()
        self.setUpdatesEnabled(True)

    def sizeHint(self):
        """ sizeHint(self) -> QSize
        Return recommended size of the widget
        
        """
        return QtCore.QSize(512, 512)

    def save_pdf(self, filename=None):
        if filename is None:
            fileName = QtGui.QFileDialog.getSaveFileName(self.window(),
                "Save PDF...",
                vistrails.core.system.vistrails_file_directory(),
                "PDF files (*.pdf)")

            if not fileName:
                return None
            f = str(fileName)
        else:
            f = str(filename)
            
        self.scene().saveToPDF(f)

    # Workaround for border aliasing on OSX
    # However, it breaks things on Linux, because it
    # makes zooming _extremely_ slow, so we check it
    # before we run.
    if vistrails.core.system.systemType == 'Darwin':
        def setScene(self, scene):
            """ setScene(scene: QGraphicsScene) -> None
            Make sure the viewport background brush the same as the scene
            one. This is only neccessary on the Mac to work around Qt/Mac
            bug. We can remove this if any future release of Qt fixes
            this.

            """
            QtGui.QGraphicsView.setScene(self, scene)
            if self.scene():
                palette = QtGui.QPalette(self.viewport().palette())
                palette.setBrush(QtGui.QPalette.Base, 
                                 self.scene().backgroundBrush())
                self.viewport().setPalette(palette)

class QPIPGraphicsView(QtGui.QWidget):
    """
    QPIPGraphicsView is a tool window contain a
    QInteractiveGraphicsView for PIP display

    """
    def __init__(self, parent=None):
        """ QPIPGraphicsView(parent: QWidget) -> QPIPGraphicsView
        Initialize a layout with some margin and a central widget
        """
        QtGui.QWidget.__init__(self, parent, QtCore.Qt.FramelessWindowHint)
        self.setMouseTracking(True)
        self.setAutoFillBackground(True)
        self.palette().setColor(QtGui.QPalette.Base,
                                CurrentTheme.PIP_FRAME_COLOR)
        self.setLayout(QtGui.QHBoxLayout(self))
        self.layout().setMargin(CurrentTheme.PIP_OUT_FRAME_WIDTH)
        self.graphicsView = QInteractiveGraphicsView()
        self.layout().addWidget(self.graphicsView)
        self.firstShow = True
        self.anchorCorner = [QtCore.Qt.AlignRight, QtCore.Qt.AlignTop]

    def sizeHint(self):
        """ sizeHint(self) -> QSize
        Return recommended size of the widget
        
        """        
        return QtCore.QSize(*CurrentTheme.PIP_DEFAULT_SIZE)
        
    def mousePressEvent(self, e):
        """ mousePressEvent(e: QMouseEvent) -> None
        Capture mouse press event on the frame to move the widget
        
        """
        if e.buttons() & QtCore.Qt.LeftButton:
            self.resizing = False
            self.lastPos = QtCore.QPoint(e.globalX(), e.globalY())
            gvRect = self.graphicsView.geometry()
            if ((e.x()<gvRect.left() or e.x()>gvRect.right()) and
                (e.y()<gvRect.top() or e.y()>gvRect.bottom())):
                self.direction = [1,1]
                if e.x()<gvRect.left():
                    self.direction[0] = -1
                if e.y()<gvRect.top():
                    self.direction[1] = -1
                self.resizing = True

    def mouseMoveEvent(self, e):
        """ mouseMoveEvent(e: QMouseEvent) -> None
        Move widget as the mouse moving
        
        """
        if e.buttons() & QtCore.Qt.LeftButton:
            (dx, dy) = (e.globalX()-self.lastPos.x(),
                        e.globalY()-self.lastPos.y())
            parentGeometry = self.parent().geometry()
            newGeometry = QtCore.QRect(self.geometry())
            if self.resizing:
                if self.direction[0]==1:
                    if newGeometry.right()+dx>parentGeometry.width()-1:
                        dx = parentGeometry.width()-1-newGeometry.right()
                    newGeometry.adjust(0, 0, dx, 0)
                else:
                    if newGeometry.left()+dx<0:
                        dx = -newGeometry.left()
                    newGeometry.adjust(dx, 0, 0, 0)
                    
                if self.direction[1]==1:
                    if newGeometry.bottom()+dy>parentGeometry.height()-1:
                        dy = parentGeometry.height()-1-newGeometry.bottom()
                    newGeometry.adjust(0, 0, 0, dy)
                else:
                    if newGeometry.top()+dy<0:
                        dy = -newGeometry.top()
                    newGeometry.adjust(0, dy, 0, 0)
            else:
                newGeometry.translate(dx, dy)
            if (newGeometry.left()>0 and
                newGeometry.right()<parentGeometry.width()-1):
                self.anchorCorner[0] = None
            if (newGeometry.top()>0 and
                newGeometry.bottom()<parentGeometry.height()-1):
                self.anchorCorner[1] = None
            self.updateGeometry(newGeometry)
            self.lastPos = QtCore.QPoint(e.globalX(), e.globalY())

    def updateGeometry(self, newGeometry=None):
        """ updateGeometry() -> None
        Make sure the widget is inside the parent graphics view
        
        """
        parentGeometry = self.parent().geometry()
        if newGeometry==None:
            newGeometry = QtCore.QRect(self.geometry())
            
        if self.anchorCorner[0]==None:
            if newGeometry.left()<0:
                newGeometry.moveLeft(0)
            if newGeometry.right()>=parentGeometry.width():
                newGeometry.moveRight(parentGeometry.width()-1)
            if newGeometry.left()==0:
                self.anchorCorner[0] = QtCore.Qt.AlignLeft
            if newGeometry.right()==parentGeometry.width()-1:
                self.anchorCorner[0] = QtCore.Qt.AlignRight
        else:
            if self.anchorCorner[0]==QtCore.Qt.AlignLeft:
                newGeometry.moveLeft(0)
            if self.anchorCorner[0]==QtCore.Qt.AlignRight:
                newGeometry.moveRight(parentGeometry.width()-1)
                
        if self.anchorCorner[1]==None:
            if newGeometry.top()<0:
                newGeometry.moveTop(0)
            if newGeometry.bottom()>=parentGeometry.height():
                newGeometry.moveBottom(parentGeometry.height()-1)
            if newGeometry.top()==0:
                self.anchorCorner[1] = QtCore.Qt.AlignTop
            if newGeometry.bottom()==parentGeometry.height()-1:
                self.anchorCorner[1] = QtCore.Qt.AlignBottom
        else:
            if self.anchorCorner[1]==QtCore.Qt.AlignTop:
                newGeometry.moveTop(0)
            if self.anchorCorner[1]==QtCore.Qt.AlignBottom:
                newGeometry.moveBottom(parentGeometry.height()-1)
                
        self.setGeometry(newGeometry)

    def showEvent(self, event):
        """ showEvent(event: QShowEvent) -> None
        Fit the scene to view for the show event only
        
        """
        if self.firstShow:
            self.firstShow = False
            self.graphicsView.scene().fitToView(self.graphicsView, True)
        return QtGui.QWidget.showEvent(self, event)        
        # super(QPIPGraphicsView, self).showEvent(event)

    def enterEvent(self, event):
        """ enterEvent(event: QEnterEvent) -> None        
        Show a larger frame when the mouse enter to facilitate
        resizing
        
        """
        self.layout().setMargin(CurrentTheme.PIP_IN_FRAME_WIDTH)

    def leaveEvent(self, event):
        """ leaveEvent(event: QLeaveEvent) -> None        
        Show a smaller frame when the mouse exit the widget to get
        more view
        
        """
        self.layout().setMargin(CurrentTheme.PIP_OUT_FRAME_WIDTH)


class QResetQueryButton(QtGui.QLabel):
    """
    
    """
    def __init__(self, parent=None):
        QtGui.QLabel.__init__(self, parent)

        self.setText('Reset Query')
        self.setFrameStyle(QtGui.QFrame.StyledPanel)
        self.setFrameShadow(QtGui.QFrame.Raised)
        self.marginPad = 10

    def mousePressEvent(self, e):
        """ mousePressEvent(e: QMouseEvent) -> None
        Capture mouse press event on the frame to move the widget
        
        """
        if e.buttons() & QtCore.Qt.LeftButton:
            self.setFrameShadow(QtGui.QFrame.Sunken)
    
    def mouseReleaseEvent(self, e):
        self.setFrameShadow(QtGui.QFrame.Raised)
        self.emit(QtCore.SIGNAL('resetQuery()'))

    def updateGeometry(self):
        parentGeometry = self.parent().geometry()
        self.move(self.marginPad, 
                  parentGeometry.height()-self.height()-self.marginPad)


