Logo Search packages:      
Sourcecode: veusz version File versions  Download package

controlgraph.py

#    Copyright (C) 2008 Jeremy S. Sanders
#    Email: Jeremy Sanders <jeremy@jeremysanders.net>
#
#    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.
#
#    This program 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 this program; if not, write to the Free Software Foundation, Inc.,
#    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
##############################################################################

# $Id: controlgraph.py 1411 2010-09-25 08:38:27Z jeremysanders $

"""
Classes for moving widgets around

Control items have a createGraphicsItem method which returns a graphics
item to control the object
"""

import math
import itertools

import veusz.qtall as qt4
import veusz.document as document
import veusz.setting as setting

##############################################################################

00037 class _ShapeCorner(qt4.QGraphicsRectItem):
    """Representing the corners of the rectangle."""
    def __init__(self, parent, rotator=False):
        qt4.QGraphicsRectItem.__init__(self, parent)
        if rotator:
            self.setBrush( qt4.QBrush(setting.settingdb.color('cntrlline')) )
            self.setRect(-3, -3, 6, 6)
        else:
            self.setBrush(qt4.QBrush(setting.settingdb.color('cntrlcorner')) )
            self.setRect(-5, -5, 10, 10)
        self.setPen(qt4.QPen(qt4.Qt.NoPen))
        self.setFlag(qt4.QGraphicsItem.ItemIsMovable)
        self.setZValue(3.)

00051     def mouseMoveEvent(self, event):
        """Notify parent on move."""
        qt4.QGraphicsRectItem.mouseMoveEvent(self, event)
        self.parentItem().updateFromCorner(self, event)

00056     def mouseReleaseEvent(self, event):
        """Notify parent on unclicking."""
        qt4.QGraphicsRectItem.mouseReleaseEvent(self, event)
        self.parentItem().updateWidget()

##############################################################################

def controlLinePen():
    """Get pen for lines around shapes."""
    return qt4.QPen(setting.settingdb.color('cntrlline'), 2, qt4.Qt.DotLine)

00067 class _EdgeLine(qt4.QGraphicsLineItem):
    """Line used for edges of resizing box."""
    def __init__(self, parent, ismovable = True):
        qt4.QGraphicsLineItem.__init__(self, parent)
        self.setPen(controlLinePen())
        self.setZValue(2.)
        if ismovable:
            self.setFlag(qt4.QGraphicsItem.ItemIsMovable)
            self.setCursor(qt4.Qt.SizeAllCursor)

00077     def mouseMoveEvent(self, event):
        """Notify parent on move."""
        qt4.QGraphicsLineItem.mouseMoveEvent(self, event)
        self.parentItem().updateFromLine(self, self.pos())

00082     def mouseReleaseEvent(self, event):
        """Notify parent on unclicking."""
        qt4.QGraphicsLineItem.mouseReleaseEvent(self, event)
        self.parentItem().updateWidget()

##############################################################################

00089 class ControlMarginBox(object):
00090     def __init__(self, widget, posn, maxposn, painter,
                 ismovable = True, isresizable = True):
        """Create control box item.

        widget: widget this is controllng
        posn: coordinates of box [x1, y1, x2, y2]
        maxposn: coordinates of biggest possibe box
        painter: painter to get scaling from
        ismovable: box can be moved
        isresizable: box can be resized
        """

        # save values
        self.posn = posn
        self.maxposn = maxposn
        self.widget = widget
        self.ismovable = ismovable
        self.isresizable = isresizable

        # we need these later to convert back to original units
        self.page_size = painter.veusz_page_size
        self.scaling = painter.veusz_scaling
        self.pixperpt = painter.veusz_pixperpt

    def createGraphicsItem(self):
        return _GraphMarginBox(self)

00117     def setWidgetMargins(self):
        """A helpful routine for setting widget margins after
        moving or resizing.

        This is called by the widget after receiving
        updateControlItem
        """
        s = self.widget.settings

        # get margins in pixels
        left = self.posn[0] - self.maxposn[0]
        right = self.maxposn[2] - self.posn[2]
        top = self.posn[1] - self.maxposn[1]
        bottom = self.maxposn[3] - self.posn[3]

        # set up fake painter containing veusz scalings
        fakepainter = qt4.QPainter()
        fakepainter.veusz_page_size = self.page_size
        fakepainter.veusz_scaling = self.scaling
        fakepainter.veusz_pixperpt = self.pixperpt

        # convert to physical units
        left = s.get('leftMargin').convertInverse(left, fakepainter)
        right = s.get('rightMargin').convertInverse(right, fakepainter)
        top = s.get('topMargin').convertInverse(top, fakepainter)
        bottom = s.get('bottomMargin').convertInverse(bottom, fakepainter)

        # modify widget margins
        operations = (
            document.OperationSettingSet(s.get('leftMargin'), left),
            document.OperationSettingSet(s.get('rightMargin'), right),
            document.OperationSettingSet(s.get('topMargin'), top),
            document.OperationSettingSet(s.get('bottomMargin'), bottom)
            )
        self.widget.document.applyOperation(
            document.OperationMultiple(operations, descr='resize margins'))

00154 class _GraphMarginBox(qt4.QGraphicsItem):
    """A box which can be moved or resized.

    Can automatically set margins or widget
    """

    # posn coords of each corner
    mapcornertoposn = ( (0, 1), (2, 1), (0, 3), (2, 3) )

00163     def __init__(self, params):
        """Create control box item."""

        qt4.QGraphicsItem.__init__(self)
        self.params = params

        self.setZValue(2.)

        # create corners of box
        self.corners = [_ShapeCorner(self)
                        for i in xrange(4)]

        # lines connecting corners
        self.lines = [_EdgeLine(self, ismovable=params.ismovable)
                      for i in xrange(4)]

        # hide corners if box is not resizable
        if not params.isresizable:
            for c in self.corners:
                c.hide()

        self.updateCornerPosns()

00186     def updateCornerPosns(self):
        """Update all corners from updated box."""

        par = self.params
        pos = par.posn
        # update cursors
        self.corners[0].setCursor(qt4.Qt.SizeFDiagCursor)
        self.corners[1].setCursor(qt4.Qt.SizeBDiagCursor)
        self.corners[2].setCursor(qt4.Qt.SizeBDiagCursor)
        self.corners[3].setCursor(qt4.Qt.SizeFDiagCursor)

        # trim box to maximum size
        pos[0] = max(pos[0], par.maxposn[0])
        pos[1] = max(pos[1], par.maxposn[1])
        pos[2] = min(pos[2], par.maxposn[2])
        pos[3] = min(pos[3], par.maxposn[3])

        # move corners
        for corner, (xindex, yindex) in itertools.izip(self.corners,
                                                       self.mapcornertoposn):
            corner.setPos( qt4.QPointF( pos[xindex], pos[yindex] ) )

        # move lines
        w, h = pos[2]-pos[0], pos[3]-pos[1]
        self.lines[0].setPos(pos[0], pos[1])
        self.lines[0].setLine(0, 0,  w,  0)
        self.lines[1].setPos(pos[2], pos[1])
        self.lines[1].setLine(0, 0,  0,  h)
        self.lines[2].setPos(pos[2], pos[3])
        self.lines[2].setLine(0, 0, -w,  0)
        self.lines[3].setPos(pos[0], pos[3])
        self.lines[3].setLine(0, 0,  0, -h)

00219     def updateFromLine(self, line, thispos):
        """Edge line of box was moved - update bounding box."""

        par = self.params
        # need old coordinate to work out how far line has moved
        try:
            li = self.lines.index(line)
        except ValueError:
            return
        ox = par.posn[ (0, 2, 2, 0)[li] ]
        oy = par.posn[ (1, 1, 3, 3)[li] ]

        # add on deltas to box coordinates
        dx, dy = thispos.x()-ox, thispos.y()-oy

        # make sure box can't be moved outside the allowed region
        if dx > 0:
            dx = min(dx, par.maxposn[2]-par.posn[2])
        else:
            dx = -min(abs(dx), abs(par.maxposn[0]-par.posn[0]))
        if dy > 0:
            dy = min(dy, par.maxposn[3]-par.posn[3])
        else:
            dy = -min(abs(dy), abs(par.maxposn[1]-par.posn[1]))

        # move the box
        par.posn[0] += dx
        par.posn[1] += dy
        par.posn[2] += dx
        par.posn[3] += dy

        # update corner coords and other line coordinates
        self.updateCornerPosns()

00253     def updateFromCorner(self, corner, event):
        """Move corner of box to new position."""
        try:
            index = self.corners.index(corner)
        except ValueError:
            return

        pos = self.params.posn
        pos[ self.mapcornertoposn[index][0] ] = corner.x()
        pos[ self.mapcornertoposn[index][1] ] = corner.y()

        # this is needed if the corners move past each other
        if pos[0] > pos[2]:
            # swap x
            pos[0], pos[2] = pos[2], pos[0]
            self.corners[0], self.corners[1] = self.corners[1], self.corners[0]
            self.corners[2], self.corners[3] = self.corners[3], self.corners[2]
        if pos[1] > pos[3]:
            # swap y
            pos[1], pos[3] = pos[3], pos[1]
            self.corners[0], self.corners[2] = self.corners[2], self.corners[0]
            self.corners[1], self.corners[3] = self.corners[3], self.corners[1]

        self.updateCornerPosns()
        
    def boundingRect(self):
        return qt4.QRectF(0, 0, 0, 0)

00281     def paint(self, painter, option, widget):
        """Intentionally empty painter."""

00284     def updateWidget(self):
        """Update widget margins."""
        self.params.widget.updateControlItem(self.params)


##############################################################################

00291 class ControlResizableBox(object):
    """Control a resizable box.
    Item resizes centred around a position
    """

00296     def __init__(self, widget, posn, dims, angle, allowrotate=False):
        """Initialise with widget and boxbounds shape.
        Rotation is allowed if allowrotate is set
        """
        self.widget = widget
        self.posn = posn
        self.dims = dims
        self.angle = angle
        self.allowrotate = allowrotate

    def createGraphicsItem(self):
        return _GraphResizableBox(self)

00309 class _GraphResizableBox(qt4.QGraphicsRectItem):
    """Control a resizable box.
    Item resizes centred around a position
    """

00314     def __init__(self, params):
        """Initialise with widget and boxbounds shape.
        Rotation is allowed if allowrotate is set
        """

        qt4.QGraphicsRectItem.__init__(self,
                                       params.posn[0], params.posn[1],
                                       params.dims[0], params.dims[1])
        self.params = params
        self.rotate(params.angle)

        # initial setup
        self.setCursor(qt4.Qt.SizeAllCursor)
        self.setZValue(1.)
        self.setFlag(qt4.QGraphicsItem.ItemIsMovable)
        self.setPen(controlLinePen())
        self.setBrush( qt4.QBrush() )

        # create child graphicsitem for each corner
        self.corners = [_ShapeCorner(self) for i in xrange(4)]
        self.corners[0].setCursor(qt4.Qt.SizeFDiagCursor)
        self.corners[1].setCursor(qt4.Qt.SizeBDiagCursor)
        self.corners[2].setCursor(qt4.Qt.SizeBDiagCursor)
        self.corners[3].setCursor(qt4.Qt.SizeFDiagCursor)

        # whether box is allowed to be rotated
        self.rotator = None
        if params.allowrotate:
            self.rotator = _ShapeCorner(self, rotator=True)
            self.rotator.setCursor(qt4.Qt.CrossCursor)

        self.updateCorners()
        self.rotator.setPos( 0, -abs(params.dims[1]*0.5) )

00348     def updateFromCorner(self, corner, event):
        """Take position and update corners."""

        par = self.params
        if corner in self.corners:
            # compute size from corner position
            par.dims[0] = abs(corner.pos().x()*2)
            par.dims[1] = abs(corner.pos().y()*2)
        elif corner == self.rotator:
            # work out angle relative to centre of widget
            delta = event.scenePos() - self.scenePos()
            angle = math.atan2( delta.y(), delta.x() )
            # change to degrees from correct direction
            par.angle = (angle*(180/math.pi) + 90.) % 360

            # apply rotation
            selfpt = self.pos()
            self.resetTransform()
            self.setPos(selfpt)
            self.rotate(par.angle)

        self.updateCorners()

00371     def updateCorners(self):
        """Update corners on size."""
        size = 5
        par = self.params

        # update position and size
        self.setPos( par.posn[0], par.posn[1] )
        self.setRect( -par.dims[0]*0.5, -par.dims[1]*0.5,
                       par.dims[0], par.dims[1] )

        # update corners
        self.corners[0].setPos(-par.dims[0]*0.5, -par.dims[1]*0.5)
        self.corners[1].setPos( par.dims[0]*0.5, -par.dims[1]*0.5)
        self.corners[2].setPos(-par.dims[0]*0.5,  par.dims[1]*0.5)
        self.corners[3].setPos( par.dims[0]*0.5,  par.dims[1]*0.5)

        if self.rotator:
            # set rotator position (constant distance)
            self.rotator.setPos( 0, -abs(par.dims[1]*0.5) )

00391     def mouseReleaseEvent(self, event):
        """If the item has been moved, do and update."""
        qt4.QGraphicsRectItem.mouseReleaseEvent(self, event)
        self.updateWidget()

00396     def mouseMoveEvent(self, event):
        """Keep track of movement."""
        qt4.QGraphicsRectItem.mouseMoveEvent(self, event)
        self.params.posn = [self.pos().x(), self.pos().y()]

00401     def updateWidget(self):
        """Tell the user the graphicsitem has been moved or resized."""
        self.params.widget.updateControlItem(self.params)

##############################################################################

00407 class ControlMovableBox(ControlMarginBox):
    """Item for user display for controlling widget.
    This is a dotted movable box with an optional "cross" where
    the real position of the widget is
    """

    def __init__(self, widget, posn, painter, crosspos=None):
        ControlMarginBox.__init__(self, widget, posn,
                                  [-10000, -10000, 10000, 10000],
                                  painter, isresizable=False)
        self.deltacrosspos = (crosspos[0] - self.posn[0],
                              crosspos[1] - self.posn[1])

    def createGraphicsItem(self):
        return _GraphMovableBox(self)

00423 class _GraphMovableBox(_GraphMarginBox):
00424     def __init__(self, params):
        _GraphMarginBox.__init__(self, params)
        self.cross = _ShapeCorner(self)
        self.cross.setCursor(qt4.Qt.SizeAllCursor)
        self.updateCornerPosns()

00430     def updateCornerPosns(self):
        _GraphMarginBox.updateCornerPosns(self)

        par = self.params
        if hasattr(self, 'cross'):
            # this fails if called before self.cross is initialised!
            self.cross.setPos( par.deltacrosspos[0] + par.posn[0],
                               par.deltacrosspos[1] + par.posn[1] )

00439     def updateFromCorner(self, corner, event):
        if corner == self.cross:
            # if cross moves, move whole box
            par = self.params
            cx, cy = self.cross.pos().x(), self.cross.pos().y()
            dx = cx - (par.deltacrosspos[0] + par.posn[0])
            dy = cy - (par.deltacrosspos[1] + par.posn[1])

            par.posn[0] += dx
            par.posn[1] += dy
            par.posn[2] += dx
            par.posn[3] += dy
            self.updateCornerPosns()
        else:
            _GraphMarginBox.updateFromCorner(self, corner, event)

##############################################################################

00457 class ControlLine(object):
    """For controlling the position and ends of a line."""
    def __init__(self, widget, x1, y1, x2, y2):
        self.widget = widget
        self.line = x1, y1, x2, y2
    def createGraphicsItem(self):
        return _GraphLine(self)

00465 class _GraphLine(qt4.QGraphicsLineItem):
    """Represents the line as a graphics item."""
    def __init__(self, params):
        qt4.QGraphicsLineItem.__init__(self, *params.line)
        self.params = params

        self.setCursor(qt4.Qt.SizeAllCursor)
        self.setFlag(qt4.QGraphicsItem.ItemIsMovable)
        self.setZValue(1.)
        self.setPen(controlLinePen())
        self.pts = [_ShapeCorner(self, rotator=True),
                    _ShapeCorner(self, rotator=True)]
        self.pts[0].setPos(params.line[0], params.line[1])
        self.pts[1].setPos(params.line[2], params.line[3])
        self.pts[0].setCursor(qt4.Qt.CrossCursor)
        self.pts[1].setCursor(qt4.Qt.CrossCursor)

00482     def updateFromCorner(self, corner, event):
        """Take position and update ends of line."""
        line = (self.pts[0].x(), self.pts[0].y(),
                self.pts[1].x(), self.pts[1].y())
        self.setLine(*line)

00488     def mouseReleaseEvent(self, event):
        """If widget has moved, tell it."""
        qt4.QGraphicsItem.mouseReleaseEvent(self, event)
        self.updateWidget()

00493     def updateWidget(self):
        """Update caller with position and line positions."""

        pt1 = ( self.pts[0].x() + self.pos().x(),
                self.pts[0].y() + self.pos().y() )
        pt2 = ( self.pts[1].x() + self.pos().x(),
                self.pts[1].y() + self.pos().y() )

        self.params.widget.updateControlItem(self.params, pt1, pt2)

#############################################################################

00505 class _AxisGraphicsLineItem(qt4.QGraphicsLineItem):
    def __init__(self, parent):
        qt4.QGraphicsLineItem.__init__(self, parent)
        self.parent = parent

        self.setPen(controlLinePen())
        self.setZValue(2.)
        self.setFlag(qt4.QGraphicsItem.ItemIsMovable)

00514     def mouseReleaseEvent(self, event):
        """Notify finished."""
        qt4.QGraphicsLineItem.mouseReleaseEvent(self, event)
        self.parent.updateWidget()

00519     def mouseMoveEvent(self, event):
        """Move the axis."""
        qt4.QGraphicsLineItem.mouseMoveEvent(self, event)
        self.parent.doLineUpdate()

00524 class ControlAxisLine(object):
    """Controlling position of an axis."""

    def __init__(self, widget, direction, minpos, maxpos, axispos,
                 maxposn):
        self.widget = widget
        self.direction = direction
        self.minpos = minpos
        self.maxpos = maxpos
        self.axispos = axispos
        self.maxposn = maxposn

    def createGraphicsItem(self):
        return _GraphAxisLine(self)

00539 class _GraphAxisLine(qt4.QGraphicsItem):

    curs = {True: qt4.Qt.SizeVerCursor,
            False: qt4.Qt.SizeHorCursor}

00544     def __init__(self, params):
        """Line is about to be shown."""
        qt4.QGraphicsItem.__init__(self)
        self.params = params
        self.pts = [ _ShapeCorner(self),
                     _ShapeCorner(self) ]
        self.line = _AxisGraphicsLineItem(self)

        # set correct coordinates
        self.horz = (params.direction == 'horizontal')
        endcurs = self.curs[not self.horz]
        self.pts[0].setCursor(endcurs)
        self.pts[1].setCursor(endcurs)
        self.line.setCursor( self.curs[self.horz] )
        self.setZValue(2.)

        self.updatePos()

00562     def updatePos(self):
        """Set ends of line and line positions from stored values."""
        par = self.params
        mxp = par.maxposn
        if self.horz:
            # clip to box bounds
            par.minpos = max(par.minpos, mxp[0])
            par.maxpos = min(par.maxpos, mxp[2])
            par.axispos = max(par.axispos, mxp[1])
            par.axispos = min(par.axispos, mxp[3])

            # set positions
            self.line.setPos(par.minpos, par.axispos)
            self.line.setLine(0, 0, par.maxpos-par.minpos, 0)
            self.pts[0].setPos(par.minpos, par.axispos)
            self.pts[1].setPos(par.maxpos, par.axispos)
        else:
            # clip to box bounds
            par.minpos = max(par.minpos, mxp[1])
            par.maxpos = min(par.maxpos, mxp[3])
            par.axispos = max(par.axispos, mxp[0])
            par.axispos = min(par.axispos, mxp[2])

            # set positions
            self.line.setPos(par.axispos, par.minpos)
            self.line.setLine(0, 0, 0, par.maxpos-par.minpos)
            self.pts[0].setPos(par.axispos, par.minpos)
            self.pts[1].setPos(par.axispos, par.maxpos)

00591     def updateFromCorner(self, corner, event):
        """Ends of axis have moved, so update values."""

        par = self.params
        # which end has moved?
        if corner is self.pts[0]:
            # horizonal or vertical axis?
            if self.horz:
                par.minpos = corner.x()
            else:
                par.minpos = corner.y()
        else:
            if self.horz:
                par.maxpos = corner.x()
            else:
                par.maxpos = corner.y()

        # swap round end points if min > max
        if par.minpos > par.maxpos:
            par.minpos, par.maxpos = par.maxpos, par.minpos
            self.pts[0], self.pts[1] = self.pts[1], self.pts[0]

        self.updatePos()

00615     def doLineUpdate(self):
        """Line has moved, so update position."""
        pos = self.line.pos()
        if self.horz:
            self.params.axispos = pos.y()
        else:
            self.params.axispos = pos.x()
        self.updatePos()

00624     def updateWidget(self):
        """Tell widget to update."""
        self.params.widget.updateControlItem(self.params)

00628     def boundingRect(self):
        """Intentionally zero bounding rect."""
        return qt4.QRectF(0, 0, 0, 0)

00632     def paint(self, painter, option, widget):
        """Intentionally empty painter."""


Generated by  Doxygen 1.6.0   Back to index