diff --git a/python/core/composer/qgscomposeritem.sip b/python/core/composer/qgscomposeritem.sip index dc919b2f1de..9cf39027361 100644 --- a/python/core/composer/qgscomposeritem.sip +++ b/python/core/composer/qgscomposeritem.sip @@ -332,10 +332,6 @@ class QgsComposerItem: QObject, QGraphicsRectItem @note this method was added in version 1.2*/ bool positionLock() const; - /**Update mouse cursor at (item) position - @note this method was added in version 1.2*/ - void updateCursor( const QPointF& itemPos ); - double rotation() const; /**Updates item, with the possibility to do custom update for subclasses*/ @@ -367,22 +363,6 @@ class QgsComposerItem: QObject, QGraphicsRectItem virtual void hoverMoveEvent( QGraphicsSceneHoverEvent * event ); - /**Finds out the appropriate cursor for the current mouse position in the widget (e.g. move in the middle, resize at border)*/ - Qt::CursorShape cursorForPosition( const QPointF& itemCoordPos ); - - /**Finds out which mouse move action to choose depending on the cursor position inside the widget*/ - QgsComposerItem::MouseMoveAction mouseMoveActionForPosition( const QPointF& itemCoordPos ); - - /**Changes the rectangle of an item depending on current mouse action (resize or move) - @param currentPosition current position of mouse cursor - @param mouseMoveStartPos cursor position at the start of the current mouse action - @param originalItem Item position at the start of the mouse action - @param dx x-Change of mouse cursor - @param dy y-Change of mouse cursor - @param changeItem Item to change size (can be the same as originalItem or a differen one) - */ - void changeItemRectangle( const QPointF& currentPosition, const QPointF& mouseMoveStartPos, const QGraphicsRectItem* originalItem, double dx, double dy, QGraphicsRectItem* changeItem ); - /**Draw selection boxes around item*/ virtual void drawSelectionBoxes( QPainter* p ); diff --git a/python/core/composer/qgscomposition.sip b/python/core/composer/qgscomposition.sip index b2a4eac8fec..630eaed3407 100644 --- a/python/core/composer/qgscomposition.sip +++ b/python/core/composer/qgscomposition.sip @@ -213,23 +213,6 @@ class QgsComposition : QGraphicsScene /**Snaps a scene coordinate point to grid*/ QPointF snapPointToGrid( const QPointF& scenePoint ) const; - /**Snaps item position to align with other items (left / middle / right or top / middle / bottom - @param item current item - @param alignX x-coordinate of align or -1 if not aligned to x - @param alignY y-coordinate of align or -1 if not aligned to y - @param dx item shift in x direction - @param dy item shift in y direction - @return new upper left point after the align*/ - QPointF alignItem( const QgsComposerItem* item, double& alignX, double& alignY, double dx = 0, double dy = 0 ); - - /**Snaps position to align with the boundaries of other items - @param pos position to snap - @param excludeItem item to exclude - @param alignX snapped x coordinate or -1 if not snapped - @param alignY snapped y coordinate or -1 if not snapped - @return snapped position or original position if no snap*/ - QPointF alignPos( const QPointF& pos, const QgsComposerItem* excludeItem, double& alignX, double& alignY ); - /**Add a custom snap line (can be horizontal or vertical)*/ QGraphicsLineItem* addSnapLine(); /**Remove custom snap line (and delete the object)*/ diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 08bf3b3618d..d31284cad39 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -133,6 +133,7 @@ SET(QGIS_CORE_SRCS composer/qgscomposermultiframecommand.cpp composer/qgscomposerarrow.cpp composer/qgscomposerframe.cpp + composer/qgscomposermousehandles.cpp composer/qgscomposeritem.cpp composer/qgscomposeritemcommand.cpp composer/qgscomposeritemgroup.cpp @@ -324,6 +325,7 @@ SET(QGIS_CORE_MOC_HDRS composer/qgscomposerscalebar.h composer/qgscomposeritem.h composer/qgscomposeritemgroup.h + composer/qgscomposermousehandles.h composer/qgscomposerlabel.h composer/qgscomposershape.h composer/qgscomposerattributetable.h diff --git a/src/core/composer/qgscomposeritem.cpp b/src/core/composer/qgscomposeritem.cpp index b8e4e65e652..67a3a6e1d58 100644 --- a/src/core/composer/qgscomposeritem.cpp +++ b/src/core/composer/qgscomposeritem.cpp @@ -94,7 +94,6 @@ QgsComposerItem::QgsComposerItem( qreal x, qreal y, qreal width, qreal height, Q void QgsComposerItem::init( bool manageZValue ) { setFlag( QGraphicsItem::ItemIsSelectable, true ); - setAcceptsHoverEvents( true ); //set default pen and brush setBrush( QBrush( QColor( 255, 255, 255, 255 ) ) ); QPen defaultPen( QColor( 0, 0, 0 ) ); @@ -369,390 +368,9 @@ void QgsComposerItem::cancelCommand() } } -void QgsComposerItem::mouseMoveEvent( QGraphicsSceneMouseEvent * event ) -{ - if ( mItemPositionLocked ) - { - return; - } - - if ( !isSelected() ) - { - return; - } - - if ( mBoundingResizeRectangle ) - { - double diffX = event->lastScenePos().x() - mLastMouseEventPos.x(); - double diffY = event->lastScenePos().y() - mLastMouseEventPos.y(); - - changeItemRectangle( event->lastScenePos(), mMouseMoveStartPos, this, diffX, diffY, mBoundingResizeRectangle ); - } - mLastMouseEventPos = event->lastScenePos(); -} - -void QgsComposerItem::mousePressEvent( QGraphicsSceneMouseEvent * event ) -{ - if ( mItemPositionLocked ) - { - return; - } - - if ( !isSelected() ) - { - return; - } - - //set current position and type of mouse move action - mMouseMoveStartPos = event->lastScenePos(); - mLastMouseEventPos = event->lastScenePos(); - mCurrentMouseMoveAction = mouseMoveActionForPosition( event->pos() ); - - //remove the old rubber band item if it is still there - if ( mBoundingResizeRectangle ) - { - scene()->removeItem( mBoundingResizeRectangle ); - delete mBoundingResizeRectangle; - mBoundingResizeRectangle = 0; - } - deleteAlignItems(); - - //create and show bounding rectangle - mBoundingResizeRectangle = new QGraphicsRectItem( 0 ); - scene()->addItem( mBoundingResizeRectangle ); - mBoundingResizeRectangle->setRect( QRectF( 0, 0, rect().width(), rect().height() ) ); - QTransform resizeTransform; - resizeTransform.translate( transform().dx(), transform().dy() ); - mBoundingResizeRectangle->setTransform( resizeTransform ); - - mBoundingResizeRectangle->setBrush( Qt::NoBrush ); - mBoundingResizeRectangle->setPen( QPen( QColor( 0, 0, 0 ), 0 ) ); - mBoundingResizeRectangle->setZValue( 90 ); - mBoundingResizeRectangle->show(); -} - -void QgsComposerItem::mouseReleaseEvent( QGraphicsSceneMouseEvent * event ) -{ - - if ( mItemPositionLocked ) - { - return; - } - - if ( !isSelected() ) - { - return; - } - - //delete frame rectangle - if ( mBoundingResizeRectangle ) - { - scene()->removeItem( mBoundingResizeRectangle ); - delete mBoundingResizeRectangle; - mBoundingResizeRectangle = 0; - } - - QPointF mouseMoveStopPoint = event->lastScenePos(); - double diffX = mouseMoveStopPoint.x() - mMouseMoveStartPos.x(); - double diffY = mouseMoveStopPoint.y() - mMouseMoveStartPos.y(); - - //it was only a click - if ( qAbs( diffX ) < std::numeric_limits::min() && qAbs( diffY ) < std::numeric_limits::min() ) - { - return; - } - - beginItemCommand( tr( "Change item position" ) ); - changeItemRectangle( mouseMoveStopPoint, mMouseMoveStartPos, this, diffX, diffY, this ); - endItemCommand(); - - deleteAlignItems(); - - //reset default action - mCurrentMouseMoveAction = QgsComposerItem::MoveItem; - setCursor( Qt::ArrowCursor ); -} - -Qt::CursorShape QgsComposerItem::cursorForPosition( const QPointF& itemCoordPos ) -{ - QgsComposerItem::MouseMoveAction mouseAction = mouseMoveActionForPosition( itemCoordPos ); - switch ( mouseAction ) - { - case NoAction: - return Qt::ForbiddenCursor; - case MoveItem: - return Qt::SizeAllCursor; - case ResizeUp: - case ResizeDown: - return Qt::SizeVerCursor; - case ResizeLeft: - case ResizeRight: - return Qt::SizeHorCursor; - case ResizeLeftUp: - case ResizeRightDown: - return Qt::SizeFDiagCursor; - case ResizeRightUp: - case ResizeLeftDown: - return Qt::SizeBDiagCursor; - default: - return Qt::ArrowCursor; - } -} - -QgsComposerItem::MouseMoveAction QgsComposerItem::mouseMoveActionForPosition( const QPointF& itemCoordPos ) -{ - - //no action at all if item position is locked for mouse - if ( mItemPositionLocked ) - { - return QgsComposerItem::NoAction; - } - - bool nearLeftBorder = false; - bool nearRightBorder = false; - bool nearLowerBorder = false; - bool nearUpperBorder = false; - - double borderTolerance = rectHandlerBorderTolerance(); - - if ( itemCoordPos.x() < borderTolerance ) - { - nearLeftBorder = true; - } - if ( itemCoordPos.y() < borderTolerance ) - { - nearUpperBorder = true; - } - if ( itemCoordPos.x() > ( rect().width() - borderTolerance ) ) - { - nearRightBorder = true; - } - if ( itemCoordPos.y() > ( rect().height() - borderTolerance ) ) - { - nearLowerBorder = true; - } - - if ( nearLeftBorder && nearUpperBorder ) - { - return QgsComposerItem::ResizeLeftUp; - } - else if ( nearLeftBorder && nearLowerBorder ) - { - return QgsComposerItem::ResizeLeftDown; - } - else if ( nearRightBorder && nearUpperBorder ) - { - return QgsComposerItem::ResizeRightUp; - } - else if ( nearRightBorder && nearLowerBorder ) - { - return QgsComposerItem::ResizeRightDown; - } - else if ( nearLeftBorder ) - { - return QgsComposerItem::ResizeLeft; - } - else if ( nearRightBorder ) - { - return QgsComposerItem::ResizeRight; - } - else if ( nearUpperBorder ) - { - return QgsComposerItem::ResizeUp; - } - else if ( nearLowerBorder ) - { - return QgsComposerItem::ResizeDown; - } - - return QgsComposerItem::MoveItem; //default -} - -void QgsComposerItem::changeItemRectangle( const QPointF& currentPosition, - const QPointF& mouseMoveStartPos, - const QGraphicsRectItem* originalItem, - double dx, double dy, - QGraphicsRectItem* changeItem ) -{ - Q_UNUSED( dx ); - Q_UNUSED( dy ); - if ( !changeItem || !originalItem || !mComposition ) - { - return; - } - - //test if change item is a composer item. If so, prefer call to setSceneRect() instead of setTransform() and setRect() - QgsComposerItem* changeComposerItem = dynamic_cast( changeItem ); - - double mx = 0.0, my = 0.0, rx = 0.0, ry = 0.0; - QPointF snappedPosition = mComposition->snapPointToGrid( currentPosition ); - - //snap to grid and align to other items - if ( mComposition->alignmentSnap() && mCurrentMouseMoveAction != QgsComposerItem::MoveItem ) - { - double alignX = 0; - double alignY = 0; - snappedPosition = mComposition->alignPos( snappedPosition, dynamic_cast( originalItem ), alignX, alignY ); - if ( alignX != -1 ) - { - QGraphicsLineItem* item = hAlignSnapItem(); - item->setLine( QLineF( alignX, 0, alignX, mComposition->paperHeight() ) ); - item->show(); - } - else - { - deleteHAlignSnapItem(); - } - - if ( alignY != -1 ) - { - QGraphicsLineItem* item = vAlignSnapItem(); - item->setLine( QLineF( 0, alignY, mComposition->paperWidth(), alignY ) ); - item->show(); - } - else - { - deleteVAlignSnapItem(); - } - } - - double diffX = 0; - double diffY = 0; - - switch ( mCurrentMouseMoveAction ) - { - //vertical resize - case QgsComposerItem::ResizeUp: - diffY = snappedPosition.y() - originalItem->transform().dy(); - mx = 0; my = diffY; rx = 0; ry = -diffY; - break; - - case QgsComposerItem::ResizeDown: - diffY = snappedPosition.y() - ( originalItem->transform().dy() + originalItem->rect().height() ); - mx = 0; my = 0; rx = 0; ry = diffY; - break; - - //horizontal resize - case QgsComposerItem::ResizeLeft: - diffX = snappedPosition.x() - originalItem->transform().dx(); - mx = diffX, my = 0; rx = -diffX; ry = 0; - break; - - case QgsComposerItem::ResizeRight: - diffX = snappedPosition.x() - ( originalItem->transform().dx() + originalItem->rect().width() ); - mx = 0; my = 0; rx = diffX, ry = 0; - break; - - //diagonal resize - case QgsComposerItem::ResizeLeftUp: - diffX = snappedPosition.x() - originalItem->transform().dx(); - diffY = snappedPosition.y() - originalItem->transform().dy(); - mx = diffX, my = diffY; rx = -diffX; ry = -diffY; - break; - - case QgsComposerItem::ResizeRightDown: - diffX = snappedPosition.x() - ( originalItem->transform().dx() + originalItem->rect().width() ); - diffY = snappedPosition.y() - ( originalItem->transform().dy() + originalItem->rect().height() ); - mx = 0; my = 0; rx = diffX, ry = diffY; - break; - - case QgsComposerItem::ResizeRightUp: - diffX = snappedPosition.x() - ( originalItem->transform().dx() + originalItem->rect().width() ); - diffY = snappedPosition.y() - originalItem->transform().dy(); - mx = 0; my = diffY, rx = diffX, ry = -diffY; - break; - - case QgsComposerItem::ResizeLeftDown: - diffX = snappedPosition.x() - originalItem->transform().dx(); - diffY = snappedPosition.y() - ( originalItem->transform().dy() + originalItem->rect().height() ); - mx = diffX, my = 0; rx = -diffX; ry = diffY; - break; - - case QgsComposerItem::MoveItem: - { - //calculate total move difference - double moveX = currentPosition.x() - mouseMoveStartPos.x(); - double moveY = currentPosition.y() - mouseMoveStartPos.y(); - - QPointF upperLeftPoint( originalItem->transform().dx() + moveX, originalItem->transform().dy() + moveY ); - QPointF snappedLeftPoint = mComposition->snapPointToGrid( upperLeftPoint ); - - if ( snappedLeftPoint != upperLeftPoint ) //don't do align snap if grid snap has been done - { - deleteAlignItems(); - } - else if ( mComposition->alignmentSnap() ) //align item - { - double alignX = 0; - double alignY = 0; - snappedLeftPoint = mComposition->alignItem( dynamic_cast( originalItem ), alignX, alignY, moveX, moveY ); - if ( alignX != -1 ) - { - QGraphicsLineItem* item = hAlignSnapItem(); - int numPages = mComposition->numPages(); - double yLineCoord = 300; //default in case there is no single page - if ( numPages > 0 ) - { - yLineCoord = mComposition->paperHeight() * numPages + mComposition->spaceBetweenPages() * ( numPages - 1 ); - } - item->setLine( QLineF( alignX, 0, alignX, yLineCoord ) ); - item->show(); - } - else - { - deleteHAlignSnapItem(); - } - if ( alignY != -1 ) - { - QGraphicsLineItem* item = vAlignSnapItem(); - item->setLine( QLineF( 0, alignY, mComposition->paperWidth(), alignY ) ); - item->show(); - } - else - { - deleteVAlignSnapItem(); - } - } - double moveRectX = snappedLeftPoint.x() - originalItem->transform().dx(); - double moveRectY = snappedLeftPoint.y() - originalItem->transform().dy(); - - if ( !changeComposerItem ) - { - QTransform moveTransform; - moveTransform.translate( originalItem->transform().dx() + moveRectX, originalItem->transform().dy() + moveRectY ); - changeItem->setTransform( moveTransform ); - } - else //for composer items, we prefer setSceneRect as subclasses can implement custom behaviour (e.g. item group) - { - changeComposerItem->setSceneRect( QRectF( originalItem->transform().dx() + moveRectX, - originalItem->transform().dy() + moveRectY, - originalItem->rect().width(), originalItem->rect().height() ) ); - changeComposerItem->updateItem(); - } - } - return; - case QgsComposerItem::NoAction: - break; - } - - if ( !changeComposerItem ) - { - QTransform itemTransform; - itemTransform.translate( originalItem->transform().dx() + mx, originalItem->transform().dy() + my ); - changeItem->setTransform( itemTransform ); - QRectF itemRect( 0, 0, originalItem->rect().width() + rx, originalItem->rect().height() + ry ); - changeItem->setRect( itemRect ); - } - else //for composer items, we prefer setSceneRect as subclasses can implement custom behaviour (e.g. item group) - { - changeComposerItem->setSceneRect( QRectF( originalItem->transform().dx() + mx, originalItem->transform().dy() + my, - originalItem->rect().width() + rx, originalItem->rect().height() + ry ) ); - changeComposerItem->updateItem(); - } -} - void QgsComposerItem::drawSelectionBoxes( QPainter* p ) { + if ( !mComposition ) { return; @@ -760,8 +378,6 @@ void QgsComposerItem::drawSelectionBoxes( QPainter* p ) if ( mComposition->plotStyle() == QgsComposition::Preview ) { - //size of symbol boxes depends on zoom level in composer view - double rectHandlerSize = rectHandlerBorderTolerance(); double sizeLockSymbol = lockSymbolSize(); if ( mItemPositionLocked ) @@ -779,15 +395,6 @@ void QgsComposerItem::drawSelectionBoxes( QPainter* p ) p->drawImage( QRectF( 0, 0, sizeLockSymbol, sizeLockSymbol ), lockImage, QRectF( 0, 0, lockImage.width(), lockImage.height() ) ); } } - else //draw blue squares - { - p->setPen( QColor( 50, 100, 120, 200 ) ); - p->setBrush( QColor( 200, 200, 210, 120 ) ); - p->drawRect( QRectF( 0, 0, rectHandlerSize, rectHandlerSize ) ); - p->drawRect( QRectF( rect().width() - rectHandlerSize, 0, rectHandlerSize, rectHandlerSize ) ); - p->drawRect( QRectF( rect().width() - rectHandlerSize, rect().height() - rectHandlerSize, rectHandlerSize, rectHandlerSize ) ); - p->drawRect( QRectF( 0, rect().height() - rectHandlerSize, rectHandlerSize, rectHandlerSize ) ); - } } } @@ -922,18 +529,6 @@ void QgsComposerItem::setEffectsEnabled( bool effectsEnabled ) mEffect->setEnabled( effectsEnabled ); } -void QgsComposerItem::hoverMoveEvent( QGraphicsSceneHoverEvent * event ) -{ - if ( isSelected() ) - { - setCursor( cursorForPosition( event->pos() ) ); - } - else - { - setCursor( Qt::ArrowCursor ); - } -} - void QgsComposerItem::drawText( QPainter* p, double x, double y, const QString& text, const QFont& font ) const { QFont textFont = scaledFontPixelSize( font ); @@ -1108,11 +703,6 @@ double QgsComposerItem::lockSymbolSize() const return lockSymbolSize; } -void QgsComposerItem::updateCursor( const QPointF& itemPos ) -{ - setCursor( cursorForPosition( itemPos ) ); -} - void QgsComposerItem::setRotation( double r ) { if ( r > 360 ) diff --git a/src/core/composer/qgscomposeritem.h b/src/core/composer/qgscomposeritem.h index 249c00b5fba..ef8cb93168b 100644 --- a/src/core/composer/qgscomposeritem.h +++ b/src/core/composer/qgscomposeritem.h @@ -108,7 +108,7 @@ class CORE_EXPORT QgsComposerItem: public QObject, public QGraphicsRectItem virtual void setSelected( bool s ); /** \brief Is selected */ - virtual bool selected() {return QGraphicsRectItem::isSelected();} + virtual bool selected() const {return QGraphicsRectItem::isSelected();}; /** stores state in project */ virtual bool writeSettings(); @@ -287,10 +287,6 @@ class CORE_EXPORT QgsComposerItem: public QObject, public QGraphicsRectItem @note this method was added in version 1.2*/ bool positionLock() const {return mItemPositionLocked;} - /**Update mouse cursor at (item) position - @note this method was added in version 1.2*/ - void updateCursor( const QPointF& itemPos ); - double rotation() const {return mRotation;} /**Updates item, with the possibility to do custom update for subclasses*/ @@ -357,29 +353,6 @@ class CORE_EXPORT QgsComposerItem: public QObject, public QGraphicsRectItem @note: this member was added in version 2.0*/ ItemPositionMode mLastUsedPositionMode; - //event handlers - virtual void mouseMoveEvent( QGraphicsSceneMouseEvent * event ); - virtual void mousePressEvent( QGraphicsSceneMouseEvent * event ); - virtual void mouseReleaseEvent( QGraphicsSceneMouseEvent * event ); - - virtual void hoverMoveEvent( QGraphicsSceneHoverEvent * event ); - - /**Finds out the appropriate cursor for the current mouse position in the widget (e.g. move in the middle, resize at border)*/ - Qt::CursorShape cursorForPosition( const QPointF& itemCoordPos ); - - /**Finds out which mouse move action to choose depending on the cursor position inside the widget*/ - QgsComposerItem::MouseMoveAction mouseMoveActionForPosition( const QPointF& itemCoordPos ); - - /**Changes the rectangle of an item depending on current mouse action (resize or move) - @param currentPosition current position of mouse cursor - @param mouseMoveStartPos cursor position at the start of the current mouse action - @param originalItem Item position at the start of the mouse action - @param dx x-Change of mouse cursor - @param dy y-Change of mouse cursor - @param changeItem Item to change size (can be the same as originalItem or a differen one) - */ - void changeItemRectangle( const QPointF& currentPosition, const QPointF& mouseMoveStartPos, const QGraphicsRectItem* originalItem, double dx, double dy, QGraphicsRectItem* changeItem ); - /**Draw selection boxes around item*/ virtual void drawSelectionBoxes( QPainter* p ); diff --git a/src/core/composer/qgscomposermousehandles.cpp b/src/core/composer/qgscomposermousehandles.cpp new file mode 100644 index 00000000000..15e5b467d46 --- /dev/null +++ b/src/core/composer/qgscomposermousehandles.cpp @@ -0,0 +1,893 @@ +/*************************************************************************** + qgscomposermousehandles.cpp + ------------------- + begin : September 2013 + copyright : (C) 2013 by Nyall Dawson, Radim Blazek + email : nyall.dawson@gmail.com + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ +#include +#include + +#include + +#include "qgscomposermousehandles.h" +#include "qgscomposeritem.h" +#include "qgscomposition.h" +#include "qgis.h" +#include "qgslogger.h" + +QgsComposerMouseHandles::QgsComposerMouseHandles( QgsComposition *composition ) : QObject( 0 ), + QGraphicsRectItem( 0 ), + mComposition( composition ), + mBeginHandleWidth( 0 ), + mBeginHandleHeight( 0 ), + mIsDragging( false ), + mIsResizing( false ), + mHAlignSnapItem( 0 ), + mVAlignSnapItem( 0 ) +{ + //listen for selection changes, and update handles accordingly + QObject::connect( mComposition, SIGNAL( selectionChanged() ), this, SLOT( selectionChanged() ) ); + + //accept hover events, required for changing cursor to resize cursors + setAcceptsHoverEvents( true ); +} + +QgsComposerMouseHandles::~QgsComposerMouseHandles() +{ + +} + +void QgsComposerMouseHandles::paint( QPainter* painter, const QStyleOptionGraphicsItem* itemStyle, QWidget* pWidget ) +{ + Q_UNUSED( itemStyle ); + Q_UNUSED( pWidget ); + + //draw resize handles around bounds of entire selection + double rectHandlerSize = rectHandlerBorderTolerance(); + drawHandles( painter, rectHandlerSize ); + + //draw dotted boxes around selected items + drawSelectedItemBounds( painter ); +} + +void QgsComposerMouseHandles::drawHandles( QPainter* painter, double rectHandlerSize ) +{ + //blue, zero width cosmetic pen for outline + QPen handlePen = QPen( QColor( 55, 140, 195, 255 ) ); + handlePen.setWidth( 0 ); + painter->setPen( handlePen ); + + //draw box around entire selection bounds + painter->setBrush( Qt::NoBrush ); + painter->drawRect( QRectF( 0, 0, rect().width(), rect().height() ) ); + + //draw resize handles, using a filled white box + painter->setBrush( QColor( 255, 255, 255, 255 ) ); + //top left + painter->drawRect( QRectF( 0, 0, rectHandlerSize, rectHandlerSize ) ); + //mid top + painter->drawRect( QRectF(( rect().width() - rectHandlerSize ) / 2, 0, rectHandlerSize, rectHandlerSize ) ); + //top right + painter->drawRect( QRectF( rect().width() - rectHandlerSize, 0, rectHandlerSize, rectHandlerSize ) ); + //mid left + painter->drawRect( QRectF( 0, ( rect().height() - rectHandlerSize ) / 2, rectHandlerSize, rectHandlerSize ) ); + //mid right + painter->drawRect( QRectF( rect().width() - rectHandlerSize, ( rect().height() - rectHandlerSize ) / 2, rectHandlerSize, rectHandlerSize ) ); + //bottom left + painter->drawRect( QRectF( 0, rect().height() - rectHandlerSize, rectHandlerSize, rectHandlerSize ) ); + //mid bottom + painter->drawRect( QRectF(( rect().width() - rectHandlerSize ) / 2, rect().height() - rectHandlerSize, rectHandlerSize, rectHandlerSize ) ); + //bottom right + painter->drawRect( QRectF( rect().width() - rectHandlerSize, rect().height() - rectHandlerSize, rectHandlerSize, rectHandlerSize ) ); +} + +void QgsComposerMouseHandles::drawSelectedItemBounds( QPainter* painter ) +{ + //draw dotted border around selected items to give visual feedback which items are selected + QList selectedItems = mComposition->selectedComposerItems(); + if ( selectedItems.size() == 0 ) + { + return; + } + + //use difference mode so that they are visible regardless of item colors + painter->save(); + painter->setCompositionMode( QPainter::CompositionMode_Difference ); + + // use a grey dashed pen - in difference mode this should always be visible + QPen selectedItemPen = QPen( QColor( 144, 144, 144, 255 ) ); + selectedItemPen.setStyle( Qt::DashLine ); + selectedItemPen.setWidth( 0 ); + painter->setPen( selectedItemPen ); + painter->setBrush( Qt::NoBrush ); + + QList::iterator itemIter = selectedItems.begin(); + for ( ; itemIter != selectedItems.end(); ++itemIter ) + { + //scene bounds of selected item + QRectF itemSceneBounds = ( *itemIter )->sceneBoundingRect(); + //convert scene bounds to handle item bounds + QRectF itemBounds; + if ( mIsDragging ) + { + //if currently dragging, draw selected item bounds relative to current mouse position + itemBounds = mapRectFromScene( itemSceneBounds ); + itemBounds.translate( transform().dx(), transform().dy() ); + } + else if ( mIsResizing ) + { + //if currently resizing, calculate relative resize of this item + itemBounds = itemSceneBounds; + relativeResizeRect( itemBounds, QRectF( mBeginHandlePos.x(), mBeginHandlePos.y(), mBeginHandleWidth, mBeginHandleHeight ), sceneBoundingRect() ); + itemBounds = mapRectFromScene( itemBounds ); + } + else + { + //not resizing or moving, so just map from scene bounds + itemBounds = mapRectFromScene( itemSceneBounds ); + } + painter->drawRect( itemBounds ); + } + painter->restore(); +} + +void QgsComposerMouseHandles::selectionChanged() +{ + //listen out for selected items' sizeChanged signal + QList itemList = composition()->items(); + QList::iterator itemIt = itemList.begin(); + for ( ; itemIt != itemList.end(); ++itemIt ) + { + QgsComposerItem* item = dynamic_cast( *itemIt ); + if ( item ) + { + if ( item->selected() ) + { + QObject::connect( item, SIGNAL( sizeChanged() ), this, SLOT( selectedItemSizeChanged() ) ); + } + else + { + QObject::disconnect( item, SIGNAL( sizeChanged() ), this, 0 ); + } + } + } + + updateHandles(); +} + +void QgsComposerMouseHandles::selectedItemSizeChanged() +{ + if ( !mIsDragging && !mIsResizing ) + { + //only required for non-mouse initiated size changes + updateHandles(); + } +} + +void QgsComposerMouseHandles::updateHandles() +{ + //recalculate size and position of handle item + + //first check to see if any items are selected + QList selectedItems = mComposition->selectedComposerItems(); + if ( selectedItems.size() > 0 ) + { + //one or more items are selected, get bounds of all selected items + QRectF newHandleBounds = selectionBounds(); + //update size and position of handle object + setRect( 0, 0, newHandleBounds.width(), newHandleBounds.height() ); + setPos( newHandleBounds.topLeft() ); + show(); + } + else + { + //no items selected, hide handles + hide(); + } + //force redraw + update(); +} + +QRectF QgsComposerMouseHandles::selectionBounds() const +{ + //calculate scene bounds of all currently selected items + QList selectedItems = mComposition->selectedComposerItems(); + QList::iterator itemIter = selectedItems.begin(); + + //start with scene bounds of first selected item + QRectF bounds = ( *itemIter )->sceneBoundingRect(); + + //iterate through remaining items, expanding the bounds as required + for ( ++itemIter; itemIter != selectedItems.end(); ++itemIter ) + { + bounds = bounds.united(( *itemIter )->sceneBoundingRect() ); + } + + return bounds; +} + +double QgsComposerMouseHandles::rectHandlerBorderTolerance() const +{ + //calculate size for resize handles + //get view scale factor + QList viewList = mComposition->views(); + QGraphicsView* currentView = viewList.at( 0 ); + double viewScaleFactor = currentView->transform().m11(); + + //size of handle boxes depends on zoom level in composer view + double rectHandlerSize = 10.0 / viewScaleFactor; + + //make sure the boxes don't get too large + if ( rectHandlerSize > ( rect().width() / 3 ) ) + { + rectHandlerSize = rect().width() / 3; + } + if ( rectHandlerSize > ( rect().height() / 3 ) ) + { + rectHandlerSize = rect().height() / 3; + } + return rectHandlerSize; +} + +Qt::CursorShape QgsComposerMouseHandles::cursorForPosition( const QPointF& itemCoordPos ) +{ + QgsComposerMouseHandles::MouseAction mouseAction = mouseActionForPosition( itemCoordPos ); + switch ( mouseAction ) + { + case NoAction: + return Qt::ForbiddenCursor; + case MoveItem: + return Qt::SizeAllCursor; + case ResizeUp: + case ResizeDown: + return Qt::SizeVerCursor; + case ResizeLeft: + case ResizeRight: + return Qt::SizeHorCursor; + case ResizeLeftUp: + case ResizeRightDown: + return Qt::SizeFDiagCursor; + case ResizeRightUp: + case ResizeLeftDown: + return Qt::SizeBDiagCursor; + case SelectItem: + default: + return Qt::ArrowCursor; + } +} + +QgsComposerMouseHandles::MouseAction QgsComposerMouseHandles::mouseActionForPosition( const QPointF& itemCoordPos ) +{ + bool nearLeftBorder = false; + bool nearRightBorder = false; + bool nearLowerBorder = false; + bool nearUpperBorder = false; + + double borderTolerance = rectHandlerBorderTolerance(); + + if ( itemCoordPos.x() >= 0 && itemCoordPos.x() < borderTolerance ) + { + nearLeftBorder = true; + } + if ( itemCoordPos.y() >= 0 && itemCoordPos.y() < borderTolerance ) + { + nearUpperBorder = true; + } + if ( itemCoordPos.x() <= rect().width() && itemCoordPos.x() > ( rect().width() - borderTolerance ) ) + { + nearRightBorder = true; + } + if ( itemCoordPos.y() <= rect().height() && itemCoordPos.y() > ( rect().height() - borderTolerance ) ) + { + nearLowerBorder = true; + } + + if ( nearLeftBorder && nearUpperBorder ) + { + return QgsComposerMouseHandles::ResizeLeftUp; + } + else if ( nearLeftBorder && nearLowerBorder ) + { + return QgsComposerMouseHandles::ResizeLeftDown; + } + else if ( nearRightBorder && nearUpperBorder ) + { + return QgsComposerMouseHandles::ResizeRightUp; + } + else if ( nearRightBorder && nearLowerBorder ) + { + return QgsComposerMouseHandles::ResizeRightDown; + } + else if ( nearLeftBorder ) + { + return QgsComposerMouseHandles::ResizeLeft; + } + else if ( nearRightBorder ) + { + return QgsComposerMouseHandles::ResizeRight; + } + else if ( nearUpperBorder ) + { + return QgsComposerMouseHandles::ResizeUp; + } + else if ( nearLowerBorder ) + { + return QgsComposerMouseHandles::ResizeDown; + } + + //find out if cursor position is over a selected item + QPointF scenePoint = mapToScene( itemCoordPos ); + QList itemsAtCursorPos = mComposition->items( scenePoint ); + if ( itemsAtCursorPos.size() == 0 ) + { + //no items at cursor position + return QgsComposerMouseHandles::SelectItem; + } + QList::iterator itemIter = itemsAtCursorPos.begin(); + for ( ; itemIter != itemsAtCursorPos.end(); ++itemIter ) + { + QgsComposerItem* item = dynamic_cast(( *itemIter ) ); + if ( item && item->selected() ) + { + //cursor is over a selected composer item + return QgsComposerMouseHandles::MoveItem; + } + } + + //default + return QgsComposerMouseHandles::SelectItem; +} + +QgsComposerMouseHandles::MouseAction QgsComposerMouseHandles::mouseActionForScenePos( const QPointF& sceneCoordPos ) +{ + // convert sceneCoordPos to item coordinates + QPointF itemPos = mapFromScene( sceneCoordPos ); + return mouseActionForPosition( itemPos ); +} + +void QgsComposerMouseHandles::hoverMoveEvent( QGraphicsSceneHoverEvent * event ) +{ + setCursor( cursorForPosition( event->pos() ) ); +} + +void QgsComposerMouseHandles::mouseMoveEvent( QGraphicsSceneMouseEvent* event ) +{ + if ( mIsDragging ) + { + //currently dragging a selection + dragMouseMove( event->lastScenePos() ); + } + else if ( mIsResizing ) + { + //currently resizing a selection + resizeMouseMove( event->lastScenePos() ); + } + + mLastMouseEventPos = event->lastScenePos(); +} + +void QgsComposerMouseHandles::mouseReleaseEvent( QGraphicsSceneMouseEvent* event ) +{ + QPointF mouseMoveStopPoint = event->lastScenePos(); + double diffX = mouseMoveStopPoint.x() - mMouseMoveStartPos.x(); + double diffY = mouseMoveStopPoint.y() - mMouseMoveStartPos.y(); + + //it was only a click + if ( qAbs( diffX ) < std::numeric_limits::min() && qAbs( diffY ) < std::numeric_limits::min() ) + { + mIsDragging = false; + mIsResizing = false; + return; + } + + if ( mCurrentMouseMoveAction == QgsComposerMouseHandles::MoveItem ) + { + //move selected items + QUndoCommand* parentCommand = new QUndoCommand( tr( "Change item position" ) ); + + QPointF mEndHandleMovePos = scenePos(); + + //move all selected items + QList selectedItems = mComposition->selectedComposerItems(); + QList::iterator itemIter = selectedItems.begin(); + for ( ; itemIter != selectedItems.end(); ++itemIter ) + { + QgsComposerItemCommand* subcommand = new QgsComposerItemCommand( *itemIter, "", parentCommand ); + subcommand->savePreviousState(); + ( *itemIter )->move( mEndHandleMovePos.x() - mBeginHandlePos.x(), mEndHandleMovePos.y() - mBeginHandlePos.y() ); + subcommand->saveAfterState(); + } + mComposition->undoStack()->push( parentCommand ); + + } + else if ( mCurrentMouseMoveAction != QgsComposerMouseHandles::NoAction ) + { + //resize selected items + QUndoCommand* parentCommand = new QUndoCommand( tr( "Change item size" ) ); + + //resize all selected items + QList selectedItems = mComposition->selectedComposerItems(); + QList::iterator itemIter = selectedItems.begin(); + for ( ; itemIter != selectedItems.end(); ++itemIter ) + { + QgsComposerItemCommand* subcommand = new QgsComposerItemCommand( *itemIter, "", parentCommand ); + subcommand->savePreviousState(); + QRectF itemBounds = ( *itemIter )->sceneBoundingRect(); + relativeResizeRect( itemBounds, QRectF( mBeginHandlePos.x(), mBeginHandlePos.y(), mBeginHandleWidth, mBeginHandleHeight ), sceneBoundingRect() ); + ( *itemIter )->setSceneRect( itemBounds ); + subcommand->saveAfterState(); + } + mComposition->undoStack()->push( parentCommand ); + } + + deleteAlignItems(); + + if ( mIsDragging ) + { + mIsDragging = false; + } + if ( mIsResizing ) + { + mIsResizing = false; + } + + //reset default action + mCurrentMouseMoveAction = QgsComposerMouseHandles::MoveItem; + setCursor( Qt::ArrowCursor ); + //redraw handles + resetTransform(); + updateHandles(); +} + +void QgsComposerMouseHandles::mousePressEvent( QGraphicsSceneMouseEvent* event ) +{ + //save current cursor position + mMouseMoveStartPos = event->lastScenePos(); + mLastMouseEventPos = event->lastScenePos(); + //save current item geometry + mBeginMouseEventPos = event->lastScenePos(); + mBeginHandlePos = scenePos(); + mBeginHandleWidth = rect().width(); + mBeginHandleHeight = rect().height(); + //type of mouse move action + mCurrentMouseMoveAction = mouseActionForPosition( event->pos() ); + + deleteAlignItems(); + + if ( mCurrentMouseMoveAction == QgsComposerMouseHandles::MoveItem ) + { + mIsDragging = true; + } + else if ( mCurrentMouseMoveAction != QgsComposerMouseHandles::SelectItem && + mCurrentMouseMoveAction != QgsComposerMouseHandles::NoAction ) + { + mIsResizing = true; + } + +} + +void QgsComposerMouseHandles::dragMouseMove( const QPointF& currentPosition ) +{ + if ( !mComposition ) + { + return; + } + + //calculate total amount of mouse movement since drag began + double moveX = currentPosition.x() - mBeginMouseEventPos.x(); + double moveY = currentPosition.y() - mBeginMouseEventPos.y(); + + //find target position before snapping (in scene coordinates) + QPointF upperLeftPoint( mBeginHandlePos.x() + moveX, mBeginHandlePos.y() + moveY ); + + //snap to grid and guides + QPointF snappedLeftPoint = snapPoint( upperLeftPoint, QgsComposerMouseHandles::Item ); + + //TODO: shift moving should lock to horizontal/vertical movement + + //calculate total shift for item from beginning of drag operation to current position + double moveRectX = snappedLeftPoint.x() - mBeginHandlePos.x(); + double moveRectY = snappedLeftPoint.y() - mBeginHandlePos.y(); + + //shift handle item to new position + QTransform moveTransform; + moveTransform.translate( moveRectX, moveRectY ); + setTransform( moveTransform ); +} + +void QgsComposerMouseHandles::resizeMouseMove( const QPointF& currentPosition ) +{ + + if ( !mComposition ) + { + return; + } + + double mx = 0.0, my = 0.0, rx = 0.0, ry = 0.0; + QPointF snappedPosition = snapPoint( currentPosition, QgsComposerMouseHandles::Point ); + + double diffX = 0; + double diffY = 0; + + //TODO: shift-resizing should lock aspect ratio + + //TODO: resizing eg from top handle to below bottom handle + switch ( mCurrentMouseMoveAction ) + { + //vertical resize + case QgsComposerMouseHandles::ResizeUp: + diffY = snappedPosition.y() - mBeginHandlePos.y(); + mx = 0; my = diffY; rx = 0; ry = -diffY; + break; + + case QgsComposerMouseHandles::ResizeDown: + diffY = snappedPosition.y() - ( mBeginHandlePos.y() + mBeginHandleHeight ); + mx = 0; my = 0; rx = 0; ry = diffY; + break; + + //horizontal resize + case QgsComposerMouseHandles::ResizeLeft: + diffX = snappedPosition.x() - mBeginHandlePos.x(); + mx = diffX, my = 0; rx = -diffX; ry = 0; + break; + + case QgsComposerMouseHandles::ResizeRight: + diffX = snappedPosition.x() - ( mBeginHandlePos.x() + mBeginHandleWidth ); + mx = 0; my = 0; rx = diffX, ry = 0; + break; + + //diagonal resize + case QgsComposerMouseHandles::ResizeLeftUp: + diffX = snappedPosition.x() - mBeginHandlePos.x(); + diffY = snappedPosition.y() - mBeginHandlePos.y(); + mx = diffX, my = diffY; rx = -diffX; ry = -diffY; + break; + + case QgsComposerMouseHandles::ResizeRightDown: + diffX = snappedPosition.x() - ( mBeginHandlePos.x() + mBeginHandleWidth ); + diffY = snappedPosition.y() - ( mBeginHandlePos.y() + mBeginHandleHeight ); + mx = 0; my = 0; rx = diffX, ry = diffY; + break; + + case QgsComposerMouseHandles::ResizeRightUp: + diffX = snappedPosition.x() - ( mBeginHandlePos.x() + mBeginHandleWidth ); + diffY = snappedPosition.y() - mBeginHandlePos.y(); + mx = 0; my = diffY, rx = diffX, ry = -diffY; + break; + + case QgsComposerMouseHandles::ResizeLeftDown: + diffX = snappedPosition.x() - mBeginHandlePos.x(); + diffY = snappedPosition.y() - ( mBeginHandlePos.y() + mBeginHandleHeight ); + mx = diffX, my = 0; rx = -diffX; ry = diffY; + break; + + case QgsComposerMouseHandles::MoveItem: + case QgsComposerMouseHandles::SelectItem: + case QgsComposerMouseHandles::NoAction: + break; + } + + //update selection handle rectangle + QTransform itemTransform; + itemTransform.translate( mx, my ); + setTransform( itemTransform ); + QRectF itemRect( 0, 0, mBeginHandleWidth + rx, mBeginHandleHeight + ry ); + setRect( itemRect ); +} + +void QgsComposerMouseHandles::relativeResizeRect( QRectF& rectToResize, const QRectF& boundsBefore, const QRectF& boundsAfter ) +{ + //linearly scale rectToResize relative to the scaling from boundsBefore to boundsAfter + double left = relativePosition( rectToResize.left(), boundsBefore.left(), boundsBefore.right(), boundsAfter.left(), boundsAfter.right() ); + double right = relativePosition( rectToResize.right(), boundsBefore.left(), boundsBefore.right(), boundsAfter.left(), boundsAfter.right() ); + double top = relativePosition( rectToResize.top(), boundsBefore.top(), boundsBefore.bottom(), boundsAfter.top(), boundsAfter.bottom() ); + double bottom = relativePosition( rectToResize.bottom(), boundsBefore.top(), boundsBefore.bottom(), boundsAfter.top(), boundsAfter.bottom() ); + + rectToResize.setRect( left, top, right - left, bottom - top ); +} + +double QgsComposerMouseHandles::relativePosition( double position, double beforeMin, double beforeMax, double afterMin, double afterMax ) +{ + //calculate parameters for linear scale between before and after ranges + double m = ( afterMax - afterMin ) / ( beforeMax - beforeMin ); + double c = afterMin - ( beforeMin * m ); + + //return linearly scaled position + return m * position + c; +} + +QPointF QgsComposerMouseHandles::snapPoint( const QPointF& point, QgsComposerMouseHandles::SnapGuideMode mode ) +{ + //snap to grid + QPointF snappedPoint = mComposition->snapPointToGrid( point ); + + if ( snappedPoint != point ) //don't do align snap if grid snap has been done + { + deleteAlignItems(); + return snappedPoint; + } + + //align item + if ( !mComposition->alignmentSnap() ) + { + return point; + } + + double alignX = 0; + double alignY = 0; + + //depending on the mode, we either snap just the single point, or all the bounds of the selection + switch ( mode ) + { + case QgsComposerMouseHandles::Item: + snappedPoint = alignItem( alignX, alignY, point.x(), point.y() ); + break; + case QgsComposerMouseHandles::Point: + snappedPoint = alignPos( point, alignX, alignY ); + break; + } + + if ( alignX != -1 ) + { + QGraphicsLineItem* item = hAlignSnapItem(); + int numPages = mComposition->numPages(); + double yLineCoord = 300; //default in case there is no single page + if ( numPages > 0 ) + { + yLineCoord = mComposition->paperHeight() * numPages + mComposition->spaceBetweenPages() * ( numPages - 1 ); + } + item->setLine( QLineF( alignX, 0, alignX, yLineCoord ) ); + item->show(); + } + else + { + deleteHAlignSnapItem(); + } + if ( alignY != -1 ) + { + QGraphicsLineItem* item = vAlignSnapItem(); + item->setLine( QLineF( 0, alignY, mComposition->paperWidth(), alignY ) ); + item->show(); + } + else + { + deleteVAlignSnapItem(); + } + return snappedPoint; +} + +QGraphicsLineItem* QgsComposerMouseHandles::hAlignSnapItem() +{ + if ( !mHAlignSnapItem ) + { + mHAlignSnapItem = new QGraphicsLineItem( 0 ); + mHAlignSnapItem->setPen( QPen( QColor( Qt::red ) ) ); + scene()->addItem( mHAlignSnapItem ); + mHAlignSnapItem->setZValue( 90 ); + } + return mHAlignSnapItem; +} + +QGraphicsLineItem* QgsComposerMouseHandles::vAlignSnapItem() +{ + if ( !mVAlignSnapItem ) + { + mVAlignSnapItem = new QGraphicsLineItem( 0 ); + mVAlignSnapItem->setPen( QPen( QColor( Qt::red ) ) ); + scene()->addItem( mVAlignSnapItem ); + mVAlignSnapItem->setZValue( 90 ); + } + return mVAlignSnapItem; +} + +void QgsComposerMouseHandles::deleteHAlignSnapItem() +{ + if ( mHAlignSnapItem ) + { + scene()->removeItem( mHAlignSnapItem ); + delete mHAlignSnapItem; + mHAlignSnapItem = 0; + } +} + +void QgsComposerMouseHandles::deleteVAlignSnapItem() +{ + if ( mVAlignSnapItem ) + { + scene()->removeItem( mVAlignSnapItem ); + delete mVAlignSnapItem; + mVAlignSnapItem = 0; + } +} + +void QgsComposerMouseHandles::deleteAlignItems() +{ + deleteHAlignSnapItem(); + deleteVAlignSnapItem(); +} + +QPointF QgsComposerMouseHandles::alignItem( double& alignX, double& alignY, double unalignedX, double unalignedY ) +{ + double left = unalignedX; + double right = left + rect().width(); + double midH = ( left + right ) / 2.0; + double top = unalignedY; + double bottom = top + rect().height(); + double midV = ( top + bottom ) / 2.0; + + QMap xAlignCoordinates; + QMap yAlignCoordinates; + collectAlignCoordinates( xAlignCoordinates, yAlignCoordinates ); + + //find nearest matches x + double xItemLeft = left; //new left coordinate of the item + double xAlignCoord = 0; + double smallestDiffX = DBL_MAX; + + checkNearestItem( left, xAlignCoordinates, smallestDiffX, 0, xItemLeft, xAlignCoord ); + checkNearestItem( midH, xAlignCoordinates, smallestDiffX, ( left - right ) / 2.0, xItemLeft, xAlignCoord ); + checkNearestItem( right, xAlignCoordinates, smallestDiffX, left - right, xItemLeft, xAlignCoord ); + + //find nearest matches y + double yItemTop = top; //new top coordinate of the item + double yAlignCoord = 0; + double smallestDiffY = DBL_MAX; + + checkNearestItem( top, yAlignCoordinates, smallestDiffY, 0, yItemTop, yAlignCoord ); + checkNearestItem( midV, yAlignCoordinates, smallestDiffY, ( top - bottom ) / 2.0, yItemTop, yAlignCoord ); + checkNearestItem( bottom, yAlignCoordinates, smallestDiffY, top - bottom, yItemTop, yAlignCoord ); + + double xCoord = ( smallestDiffX < 5 ) ? xItemLeft : unalignedX; + alignX = ( smallestDiffX < 5 ) ? xAlignCoord : -1; + double yCoord = ( smallestDiffY < 5 ) ? yItemTop : unalignedY; + alignY = ( smallestDiffY < 5 ) ? yAlignCoord : -1; + return QPointF( xCoord, yCoord ); +} + +QPointF QgsComposerMouseHandles::alignPos( const QPointF& pos, double& alignX, double& alignY ) +{ + QMap xAlignCoordinates; + QMap yAlignCoordinates; + collectAlignCoordinates( xAlignCoordinates, yAlignCoordinates ); + + double nearestX = pos.x(); + double nearestY = pos.y(); + if ( !nearestItem( xAlignCoordinates, pos.x(), nearestX ) + || !nearestItem( yAlignCoordinates, pos.y(), nearestY ) ) + { + alignX = -1; + alignY = -1; + return pos; + } + + QPointF result( pos.x(), pos.y() ); + if ( abs( nearestX - pos.x() ) < mComposition->alignmentSnapTolerance() ) + { + result.setX( nearestX ); + alignX = nearestX; + } + else + { + alignX = -1; + } + + if ( abs( nearestY - pos.y() ) < mComposition->alignmentSnapTolerance() ) + { + result.setY( nearestY ); + alignY = nearestY; + } + else + { + alignY = -1; + } + return result; +} + +void QgsComposerMouseHandles::collectAlignCoordinates( QMap< double, const QgsComposerItem* >& alignCoordsX, QMap< double, const QgsComposerItem* >& alignCoordsY ) +{ + alignCoordsX.clear(); + alignCoordsY.clear(); + + QList itemList = mComposition->items(); + QList::iterator itemIt = itemList.begin(); + for ( ; itemIt != itemList.end(); ++itemIt ) + { + const QgsComposerItem* currentItem = dynamic_cast( *itemIt ); + //don't snap to selected items, since they're the ones that will be snapping to something else + if ( !currentItem || currentItem->selected() ) + { + continue; + } + alignCoordsX.insert( currentItem->transform().dx(), currentItem ); + alignCoordsX.insert( currentItem->transform().dx() + currentItem->rect().width(), currentItem ); + alignCoordsX.insert( currentItem->transform().dx() + currentItem->rect().center().x(), currentItem ); + alignCoordsY.insert( currentItem->transform().dy() + currentItem->rect().top(), currentItem ); + alignCoordsY.insert( currentItem->transform().dy() + currentItem->rect().center().y(), currentItem ); + alignCoordsY.insert( currentItem->transform().dy() + currentItem->rect().bottom(), currentItem ); + + } + + //arbitrary snap lines + QList< QGraphicsLineItem* >::const_iterator sIt = mComposition->snapLines()->constBegin(); + for ( ; sIt != mComposition->snapLines()->constEnd(); ++sIt ) + { + double x = ( *sIt )->line().x1(); + double y = ( *sIt )->line().y1(); + if ( qgsDoubleNear( y, 0.0 ) ) + { + alignCoordsX.insert( x, 0 ); + } + else + { + alignCoordsY.insert( y, 0 ); + } + } +} + +void QgsComposerMouseHandles::checkNearestItem( double checkCoord, const QMap< double, const QgsComposerItem* >& alignCoords, double& smallestDiff, double itemCoordOffset, double& itemCoord, double& alignCoord ) const +{ + double currentCoord = 0; + if ( !nearestItem( alignCoords, checkCoord, currentCoord ) ) + { + return; + } + + double currentDiff = abs( checkCoord - currentCoord ); + if ( currentDiff < mComposition->alignmentSnapTolerance() ) + { + itemCoord = currentCoord + itemCoordOffset; + alignCoord = currentCoord; + smallestDiff = currentDiff; + } +} + +bool QgsComposerMouseHandles::nearestItem( const QMap< double, const QgsComposerItem* >& coords, double value, double& nearestValue ) const +{ + if ( coords.size() < 1 ) + { + return false; + } + + QMap< double, const QgsComposerItem* >::const_iterator it = coords.lowerBound( value ); + if ( it == coords.constBegin() ) //value smaller than first map value + { + nearestValue = it.key(); + return true; + } + else if ( it == coords.constEnd() ) //value larger than last map value + { + --it; + nearestValue = it.key(); + return true; + } + else + { + //get smaller value and larger value and return the closer one + double upperVal = it.key(); + --it; + double lowerVal = it.key(); + + double lowerDiff = value - lowerVal; + double upperDiff = upperVal - value; + if ( lowerDiff < upperDiff ) + { + nearestValue = lowerVal; + return true; + } + else + { + nearestValue = upperVal; + return true; + } + } +} diff --git a/src/core/composer/qgscomposermousehandles.h b/src/core/composer/qgscomposermousehandles.h new file mode 100644 index 00000000000..58b41c985aa --- /dev/null +++ b/src/core/composer/qgscomposermousehandles.h @@ -0,0 +1,174 @@ +/*************************************************************************** + qgscomposermousehandles.h + ------------------- + begin : September 2013 + copyright : (C) 2013 by Nyall Dawson, Radim Blazek + email : nyall.dawson@gmail.com + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ +#ifndef QGSCOMPOSERMOUSEHANDLES_H +#define QGSCOMPOSERMOUSEHANDLES_H + +#include +#include + +class QgsComposition; +class QgsComposerItem; + +/** \ingroup MapComposer + * Handles drawing of selection outlines and mouse handles. Responsible for mouse + * interactions such as resizing and moving selected items. + * */ +class CORE_EXPORT QgsComposerMouseHandles: public QObject, public QGraphicsRectItem +{ + Q_OBJECT + public: + + /**Describes the action (move or resize in different directon) to be done during mouse move*/ + enum MouseAction + { + MoveItem, + ResizeUp, + ResizeDown, + ResizeLeft, + ResizeRight, + ResizeLeftUp, + ResizeRightUp, + ResizeLeftDown, + ResizeRightDown, + SelectItem, + NoAction + }; + + enum ItemPositionMode + { + UpperLeft, + UpperMiddle, + UpperRight, + MiddleLeft, + Middle, + MiddleRight, + LowerLeft, + LowerMiddle, + LowerRight + }; + + enum SnapGuideMode + { + Item, + Point + }; + + QgsComposerMouseHandles( QgsComposition *composition ); + virtual ~QgsComposerMouseHandles(); + + void setComposition( QgsComposition* c ) { mComposition = c; } + QgsComposition* composition() { return mComposition; } + + void paint( QPainter* painter, const QStyleOptionGraphicsItem* itemStyle, QWidget* pWidget ); + + /**Finds out which mouse move action to choose depending on the scene cursor position*/ + QgsComposerMouseHandles::MouseAction mouseActionForScenePos( const QPointF& sceneCoordPos ); + + protected: + + void mouseMoveEvent( QGraphicsSceneMouseEvent* event ); + void mouseReleaseEvent( QGraphicsSceneMouseEvent* event ); + void mousePressEvent( QGraphicsSceneMouseEvent* event ); + void hoverMoveEvent( QGraphicsSceneHoverEvent * event ); + + public slots: + + /**Sets up listeners to sizeChanged signal for all selected items*/ + void selectionChanged(); + + /**Redraws handles when selected item size changes*/ + void selectedItemSizeChanged(); + + private: + + QgsComposition* mComposition; //reference to composition + + QgsComposerMouseHandles::MouseAction mCurrentMouseMoveAction; + /**Start point of the last mouse move action (in scene coordinates)*/ + QPointF mMouseMoveStartPos; + /**Position of the last mouse move event (in scene coordinates)*/ + QPointF mLastMouseEventPos; + /**Position of the mouse at beginning of move/resize (in scene coordinates)*/ + QPointF mBeginMouseEventPos; + /**Position of composer handles at beginning of move/resize (in scene coordinates)*/ + QPointF mBeginHandlePos; + /**Width and height of composer handles at beginning of resize*/ + double mBeginHandleWidth; + double mBeginHandleHeight; + + /**True if user is currently dragging items*/ + bool mIsDragging; + /**True is user is currently resizing items*/ + bool mIsResizing; + + /**Align snap lines*/ + QGraphicsLineItem* mHAlignSnapItem; + QGraphicsLineItem* mVAlignSnapItem; + + /**Returns the scene bounds of current selection*/ + QRectF selectionBounds() const; + + /**Redraws or hides the handles based on the current selection*/ + void updateHandles(); + /**Draws the handles*/ + void drawHandles( QPainter* painter, double rectHandlerSize ); + /**Draw outlines for selected items*/ + void drawSelectedItemBounds( QPainter* painter ); + + /**Returns the current (zoom level dependent) tolerance to decide if mouse position is close enough to the + item border for resizing*/ + double rectHandlerBorderTolerance() const; + + /**Finds out the appropriate cursor for the current mouse position in the widget (e.g. move in the middle, resize at border)*/ + Qt::CursorShape cursorForPosition( const QPointF& itemCoordPos ); + + /**Finds out which mouse move action to choose depending on the cursor position inside the widget*/ + QgsComposerMouseHandles::MouseAction mouseActionForPosition( const QPointF& itemCoordPos ); + + /**Handles dragging of items during mouse move*/ + void dragMouseMove( const QPointF& currentPosition ); + /**Handles resizing of items during mouse move*/ + void resizeMouseMove( const QPointF& currentPosition ); + + /**Resizes a QRectF relative to the change from boundsBefore to boundsAfter*/ + void relativeResizeRect( QRectF& rectToResize, const QRectF& boundsBefore, const QRectF& boundsAfter ); + /**Returns a scaled position given a before and after range*/ + double relativePosition( double position, double beforeMin, double beforeMax, double afterMin, double afterMax ); + + /**Return horizontal align snap item. Creates a new graphics line if 0*/ + QGraphicsLineItem* hAlignSnapItem(); + void deleteHAlignSnapItem(); + /**Return vertical align snap item. Creates a new graphics line if 0*/ + QGraphicsLineItem* vAlignSnapItem(); + void deleteVAlignSnapItem(); + void deleteAlignItems(); + + /**Snaps an item or point (depending on mode) originating at originalPoint to the grid or align rulers*/ + QPointF snapPoint( const QPointF& originalPoint, QgsComposerMouseHandles::SnapGuideMode mode ); + /**Snaps an item originating at unalignedX, unalignedY to the grid or align rulers*/ + QPointF alignItem( double& alignX, double& alignY, double unalignedX, double unalignedY ); + /**Snaps a point to to the grid or align rulers*/ + QPointF alignPos( const QPointF& pos, double& alignX, double& alignY ); + + //helper functions for item align + void collectAlignCoordinates( QMap< double, const QgsComposerItem* >& alignCoordsX, QMap< double, const QgsComposerItem* >& alignCoordsY ); + bool nearestItem( const QMap< double, const QgsComposerItem* >& coords, double value, double& nearestValue ) const; + void checkNearestItem( double checkCoord, const QMap< double, const QgsComposerItem* >& alignCoords, double& smallestDiff, double itemCoordOffset, double& itemCoord, double& alignCoord ) const; + +}; + +#endif // QGSCOMPOSERMOUSEHANDLES_H diff --git a/src/core/composer/qgscomposition.cpp b/src/core/composer/qgscomposition.cpp index a0f7133cfa6..640f7fd9ebc 100644 --- a/src/core/composer/qgscomposition.cpp +++ b/src/core/composer/qgscomposition.cpp @@ -21,6 +21,7 @@ #include "qgscomposerlabel.h" #include "qgscomposerlegend.h" #include "qgscomposermap.h" +#include "qgscomposermousehandles.h" #include "qgscomposeritemgroup.h" #include "qgscomposerpicture.h" #include "qgscomposerscalebar.h" @@ -64,6 +65,7 @@ QgsComposition::QgsComposition( QgsMapRenderer* mapRenderer ) , mSnapGridOffsetY( 0.0 ) , mAlignmentSnap( true ) , mAlignmentSnapTolerance( 2 ) + , mSelectionHandles( 0 ) , mActiveItemCommand( 0 ) , mActiveMultiFrameCommand( 0 ) , mAtlasComposition( this ) @@ -71,6 +73,11 @@ QgsComposition::QgsComposition( QgsMapRenderer* mapRenderer ) setBackgroundBrush( Qt::gray ); addPaperItem(); + mSelectionHandles = new QgsComposerMouseHandles( this ); + addItem( mSelectionHandles ); + mSelectionHandles->setRect( 30, 30, 100, 100 ); + mSelectionHandles->setZValue( 200 ); + mPrintResolution = 300; //hardcoded default loadSettings(); } @@ -93,6 +100,7 @@ QgsComposition::QgsComposition() mSnapGridOffsetY( 0.0 ), mAlignmentSnap( true ), mAlignmentSnapTolerance( 2 ), + mSelectionHandles( 0 ), mActiveItemCommand( 0 ), mActiveMultiFrameCommand( 0 ), mAtlasComposition( this ) @@ -1350,88 +1358,6 @@ QPointF QgsComposition::snapPointToGrid( const QPointF& scenePoint ) const return QPointF( xRatio * mSnapGridResolution + mSnapGridOffsetX, yRatio * mSnapGridResolution + mSnapGridOffsetY + yOffset ); } -QPointF QgsComposition::alignItem( const QgsComposerItem* item, double& alignX, double& alignY, double dx, double dy ) -{ - if ( !item ) - { - return QPointF(); - } - - double left = item->transform().dx() + dx; - double right = left + item->rect().width(); - double midH = ( left + right ) / 2.0; - double top = item->transform().dy() + dy; - double bottom = top + item->rect().height(); - double midV = ( top + bottom ) / 2.0; - - QMap xAlignCoordinates; - QMap yAlignCoordinates; - collectAlignCoordinates( xAlignCoordinates, yAlignCoordinates, item ); - - //find nearest matches x - double xItemLeft = left; //new left coordinate of the item - double xAlignCoord = 0; - double smallestDiffX = DBL_MAX; - - checkNearestItem( left, xAlignCoordinates, smallestDiffX, 0, xItemLeft, xAlignCoord ); - checkNearestItem( midH, xAlignCoordinates, smallestDiffX, ( left - right ) / 2.0, xItemLeft, xAlignCoord ); - checkNearestItem( right, xAlignCoordinates, smallestDiffX, left - right, xItemLeft, xAlignCoord ); - - //find nearest matches y - double yItemTop = top; //new top coordinate of the item - double yAlignCoord = 0; - double smallestDiffY = DBL_MAX; - - checkNearestItem( top, yAlignCoordinates, smallestDiffY, 0, yItemTop, yAlignCoord ); - checkNearestItem( midV, yAlignCoordinates, smallestDiffY, ( top - bottom ) / 2.0, yItemTop, yAlignCoord ); - checkNearestItem( bottom, yAlignCoordinates, smallestDiffY, top - bottom, yItemTop, yAlignCoord ); - - double xCoord = ( smallestDiffX < 5 ) ? xItemLeft : item->transform().dx() + dx; - alignX = ( smallestDiffX < 5 ) ? xAlignCoord : -1; - double yCoord = ( smallestDiffY < 5 ) ? yItemTop : item->transform().dy() + dy; - alignY = ( smallestDiffY < 5 ) ? yAlignCoord : -1; - return QPointF( xCoord, yCoord ); -} - -QPointF QgsComposition::alignPos( const QPointF& pos, const QgsComposerItem* excludeItem, double& alignX, double& alignY ) -{ - QMap xAlignCoordinates; - QMap yAlignCoordinates; - collectAlignCoordinates( xAlignCoordinates, yAlignCoordinates, excludeItem ); - - double nearestX = pos.x(); - double nearestY = pos.y(); - if ( !nearestItem( xAlignCoordinates, pos.x(), nearestX ) - || !nearestItem( yAlignCoordinates, pos.y(), nearestY ) ) - { - alignX = -1; - alignY = -1; - return pos; - } - - QPointF result( pos.x(), pos.y() ); - if ( abs( nearestX - pos.x() ) < mAlignmentSnapTolerance ) - { - result.setX( nearestX ); - alignX = nearestX; - } - else - { - alignX = -1; - } - - if ( abs( nearestY - pos.y() ) < mAlignmentSnapTolerance ) - { - result.setY( nearestY ); - alignY = nearestY; - } - else - { - alignY = -1; - } - return result; -} - QGraphicsLineItem* QgsComposition::addSnapLine() { QGraphicsLineItem* item = new QGraphicsLineItem(); @@ -2218,108 +2144,6 @@ QString QgsComposition::encodeStringForXML( const QString& str ) return modifiedStr; } -void QgsComposition::collectAlignCoordinates( QMap< double, const QgsComposerItem* >& alignCoordsX, QMap< double, const QgsComposerItem* >& alignCoordsY, - const QgsComposerItem* excludeItem ) -{ - alignCoordsX.clear(); - alignCoordsY.clear(); - - QList itemList = items(); - QList::iterator itemIt = itemList.begin(); - for ( ; itemIt != itemList.end(); ++itemIt ) - { - const QgsComposerItem* currentItem = dynamic_cast( *itemIt ); - if ( excludeItem ) - { - if ( !currentItem || currentItem == excludeItem ) - { - continue; - } - alignCoordsX.insert( currentItem->transform().dx(), currentItem ); - alignCoordsX.insert( currentItem->transform().dx() + currentItem->rect().width(), currentItem ); - alignCoordsX.insert( currentItem->transform().dx() + currentItem->rect().center().x(), currentItem ); - alignCoordsY.insert( currentItem->transform().dy() + currentItem->rect().top(), currentItem ); - alignCoordsY.insert( currentItem->transform().dy() + currentItem->rect().center().y(), currentItem ); - alignCoordsY.insert( currentItem->transform().dy() + currentItem->rect().bottom(), currentItem ); - } - } - - //arbitrary snap lines - QList< QGraphicsLineItem* >::const_iterator sIt = mSnapLines.constBegin(); - for ( ; sIt != mSnapLines.constEnd(); ++sIt ) - { - double x = ( *sIt )->line().x1(); - double y = ( *sIt )->line().y1(); - if ( qgsDoubleNear( y, 0.0 ) ) - { - alignCoordsX.insert( x, 0 ); - } - else - { - alignCoordsY.insert( y, 0 ); - } - } -} - -void QgsComposition::checkNearestItem( double checkCoord, const QMap< double, const QgsComposerItem* >& alignCoords, double& smallestDiff, - double itemCoordOffset, double& itemCoord, double& alignCoord ) const -{ - double currentCoord = 0; - if ( !nearestItem( alignCoords, checkCoord, currentCoord ) ) - { - return; - } - - double currentDiff = abs( checkCoord - currentCoord ); - if ( currentDiff < mAlignmentSnapTolerance ) - { - itemCoord = currentCoord + itemCoordOffset; - alignCoord = currentCoord; - smallestDiff = currentDiff; - } -} - -bool QgsComposition::nearestItem( const QMap< double, const QgsComposerItem* >& coords, double value, double& nearestValue ) -{ - if ( coords.size() < 1 ) - { - return false; - } - - QMap< double, const QgsComposerItem* >::const_iterator it = coords.lowerBound( value ); - if ( it == coords.constBegin() ) //value smaller than first map value - { - nearestValue = it.key(); - return true; - } - else if ( it == coords.constEnd() ) //value larger than last map value - { - --it; - nearestValue = it.key(); - return true; - } - else - { - //get smaller value and larger value and return the closer one - double upperVal = it.key(); - --it; - double lowerVal = it.key(); - - double lowerDiff = value - lowerVal; - double upperDiff = upperVal - value; - if ( lowerDiff < upperDiff ) - { - nearestValue = lowerVal; - return true; - } - else - { - nearestValue = upperVal; - return true; - } - } -} - void QgsComposition::computeWorldFileParameters( double& a, double& b, double& c, double& d, double& e, double& f ) const { // diff --git a/src/core/composer/qgscomposition.h b/src/core/composer/qgscomposition.h index b4f9e449d28..e37bbe07187 100644 --- a/src/core/composer/qgscomposition.h +++ b/src/core/composer/qgscomposition.h @@ -41,6 +41,7 @@ class QGraphicsRectItem; class QgsMapRenderer; class QDomElement; class QgsComposerArrow; +class QgsComposerMouseHandles; class QgsComposerHtml; class QgsComposerItem; class QgsComposerLabel; @@ -289,22 +290,11 @@ class CORE_EXPORT QgsComposition : public QGraphicsScene /**Snaps a scene coordinate point to grid*/ QPointF snapPointToGrid( const QPointF& scenePoint ) const; - /**Snaps item position to align with other items (left / middle / right or top / middle / bottom - @param item current item - @param alignX x-coordinate of align or -1 if not aligned to x - @param alignY y-coordinate of align or -1 if not aligned to y - @param dx item shift in x direction - @param dy item shift in y direction - @return new upper left point after the align*/ - QPointF alignItem( const QgsComposerItem* item, double& alignX, double& alignY, double dx = 0, double dy = 0 ); + /**Returns pointer to snap lines collection*/ + QList< QGraphicsLineItem* >* snapLines() {return &mSnapLines;}; - /**Snaps position to align with the boundaries of other items - @param pos position to snap - @param excludeItem item to exclude - @param alignX snapped x coordinate or -1 if not snapped - @param alignY snapped y coordinate or -1 if not snapped - @return snapped position or original position if no snap*/ - QPointF alignPos( const QPointF& pos, const QgsComposerItem* excludeItem, double& alignX, double& alignY ); + /**Returns pointer to selection handles*/ + QgsComposerMouseHandles* selectionHandles() {return mSelectionHandles;}; /**Add a custom snap line (can be horizontal or vertical)*/ QGraphicsLineItem* addSnapLine(); @@ -441,6 +431,8 @@ class CORE_EXPORT QgsComposition : public QGraphicsScene /**Arbitraty snap lines (horizontal and vertical)*/ QList< QGraphicsLineItem* > mSnapLines; + QgsComposerMouseHandles* mSelectionHandles; + QUndoStack mUndoStack; QgsComposerItemCommand* mActiveItemCommand; @@ -470,17 +462,6 @@ class CORE_EXPORT QgsComposition : public QGraphicsScene static QString encodeStringForXML( const QString& str ); - //helper functions for item align - void collectAlignCoordinates( QMap< double, const QgsComposerItem* >& alignCoordsX, - QMap< double, const QgsComposerItem* >& alignCoordsY, const QgsComposerItem* excludeItem ); - - void checkNearestItem( double checkCoord, const QMap< double, const QgsComposerItem* >& alignCoords, double& smallestDiff, - double itemCoordOffset, double& itemCoord, double& alignCoord ) const; - - /**Find nearest item in coordinate map to a double. - @return true if item found, false if coords is empty*/ - static bool nearestItem( const QMap< double, const QgsComposerItem* >& coords, double value, double& nearestValue ); - signals: void paperSizeChanged(); void nPagesChanged(); diff --git a/src/gui/qgscomposerview.cpp b/src/gui/qgscomposerview.cpp index a07f7891545..0a236109651 100644 --- a/src/gui/qgscomposerview.cpp +++ b/src/gui/qgscomposerview.cpp @@ -30,6 +30,7 @@ #include "qgscomposerlabel.h" #include "qgscomposerlegend.h" #include "qgscomposermap.h" +#include "qgscomposermousehandles.h" #include "qgscomposeritemgroup.h" #include "qgscomposerpicture.h" #include "qgscomposerruler.h" @@ -77,9 +78,6 @@ void QgsComposerView::mousePressEvent( QMouseEvent* e ) bool lock = selectedItem->positionLock() ? false : true; selectedItem->setPositionLock( lock ); selectedItem->update(); - //make sure the new cursor is correct - QPointF itemPoint = selectedItem->mapFromScene( scenePoint ); - selectedItem->updateCursor( itemPoint ); } return; } @@ -89,7 +87,21 @@ void QgsComposerView::mousePressEvent( QMouseEvent* e ) //select/deselect items and pass mouse event further case Select: { - QgsComposerItem* selectedItem; + //check if we are clicking on a selection handle + if ( composition()->selectionHandles()->isVisible() ) + { + //selection handles are being shown, get mouse action for current cursor position + QgsComposerMouseHandles::MouseAction mouseAction = composition()->selectionHandles()->mouseActionForScenePos( scenePoint ); + + if ( mouseAction != QgsComposerMouseHandles::MoveItem && mouseAction != QgsComposerMouseHandles::NoAction && mouseAction != QgsComposerMouseHandles::SelectItem ) + { + //mouse is over a resize handle, so propagate event onward + QGraphicsView::mousePressEvent( e ); + return; + } + } + + QgsComposerItem* selectedItem = 0; QgsComposerItem* previousSelectedItem = 0; if ( e->modifiers() & Qt::ControlModifier ) @@ -103,11 +115,6 @@ void QgsComposerView::mousePressEvent( QMouseEvent* e ) } } - if ( !( e->modifiers() & Qt::ShiftModifier ) ) //keep selection if shift key pressed - { - composition()->clearSelection(); - } - if ( previousSelectedItem ) { //select highest item just below previously selected item at position of event @@ -128,9 +135,16 @@ void QgsComposerView::mousePressEvent( QMouseEvent* e ) if ( !selectedItem ) { + composition()->clearSelection(); break; } + if (( !selectedItem->selected() ) && //keep selection if an already selected item pressed + !( e->modifiers() & Qt::ShiftModifier ) ) //keep selection if shift key pressed + { + composition()->clearSelection(); + } + if (( e->modifiers() & Qt::ShiftModifier ) && ( selectedItem->selected() ) ) { //SHIFT-clicking a selected item deselects it @@ -154,14 +168,31 @@ void QgsComposerView::mousePressEvent( QMouseEvent* e ) case MoveItemContent: { - //store item as member if it is selected and cursor is over item - QgsComposerItem* item = dynamic_cast( itemAt( e->pos() ) ); - if ( item ) + //get a list of items at clicked position + QList itemsAtCursorPos = items( e->pos() ); + if ( itemsAtCursorPos.size() == 0 ) { - mMoveContentStartPos = scenePoint; + //no items at clicked position + return; } - mMoveContentItem = item; - break; + + //find highest QgsComposerItem at clicked position + //(other graphics items may be higher, eg selection handles) + QList::iterator itemIter = itemsAtCursorPos.begin(); + for ( ; itemIter != itemsAtCursorPos.end(); ++itemIter ) + { + QgsComposerItem* item = dynamic_cast(( *itemIter ) ); + if ( item ) + { + //we've found the highest QgsComposerItem + mMoveContentStartPos = scenePoint; + mMoveContentItem = item; + break; + } + } + + //no QgsComposerItem at clicked position + return; } case AddArrow: