[FEATURE] Allow multi item drag and resize in composer (fix #7918)

[FEATURE] Always draw selection handles on top of composition, add dashed border for selected items (fix #7793)
Move responsibility for drawing selection mouse handles and mouse interaction with selection to new class
This commit is contained in:
Nyall Dawson 2013-09-17 21:09:00 +10:00 committed by Marco Hugentobler
parent 7bc72d4f6d
commit 24d110606c
10 changed files with 1132 additions and 701 deletions

View File

@ -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 );

View File

@ -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)*/

View File

@ -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

View File

@ -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<double>::min() && qAbs( diffY ) < std::numeric_limits<double>::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<QgsComposerItem *>( 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<const QgsComposerItem*>( 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<const QgsComposerItem*>( 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 )

View File

@ -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 );

View File

@ -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 <QPainter>
#include <QWidget>
#include <limits>
#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<QgsComposerItem*> 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<QgsComposerItem*>::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<QGraphicsItem *> itemList = composition()->items();
QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
for ( ; itemIt != itemList.end(); ++itemIt )
{
QgsComposerItem* item = dynamic_cast<QgsComposerItem *>( *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<QgsComposerItem*> 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<QgsComposerItem*> selectedItems = mComposition->selectedComposerItems();
QList<QgsComposerItem*>::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<QGraphicsView*> 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<QGraphicsItem *> itemsAtCursorPos = mComposition->items( scenePoint );
if ( itemsAtCursorPos.size() == 0 )
{
//no items at cursor position
return QgsComposerMouseHandles::SelectItem;
}
QList<QGraphicsItem*>::iterator itemIter = itemsAtCursorPos.begin();
for ( ; itemIter != itemsAtCursorPos.end(); ++itemIter )
{
QgsComposerItem* item = dynamic_cast<QgsComposerItem *>(( *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<double>::min() && qAbs( diffY ) < std::numeric_limits<double>::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<QgsComposerItem*> selectedItems = mComposition->selectedComposerItems();
QList<QgsComposerItem*>::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<QgsComposerItem*> selectedItems = mComposition->selectedComposerItems();
QList<QgsComposerItem*>::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<double, const QgsComposerItem* > xAlignCoordinates;
QMap<double, const QgsComposerItem* > 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<double, const QgsComposerItem* > xAlignCoordinates;
QMap<double, const QgsComposerItem* > 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<QGraphicsItem *> itemList = mComposition->items();
QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
for ( ; itemIt != itemList.end(); ++itemIt )
{
const QgsComposerItem* currentItem = dynamic_cast<const QgsComposerItem *>( *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;
}
}
}

View File

@ -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 <QGraphicsRectItem>
#include <QObject>
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

View File

@ -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<double, const QgsComposerItem* > xAlignCoordinates;
QMap<double, const QgsComposerItem* > 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<double, const QgsComposerItem* > xAlignCoordinates;
QMap<double, const QgsComposerItem* > 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<QGraphicsItem *> itemList = items();
QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
for ( ; itemIt != itemList.end(); ++itemIt )
{
const QgsComposerItem* currentItem = dynamic_cast<const QgsComposerItem *>( *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
{
//

View File

@ -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();

View File

@ -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<QgsComposerItem *>( itemAt( e->pos() ) );
if ( item )
//get a list of items at clicked position
QList<QGraphicsItem *> 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<QGraphicsItem*>::iterator itemIter = itemsAtCursorPos.begin();
for ( ; itemIter != itemsAtCursorPos.end(); ++itemIter )
{
QgsComposerItem* item = dynamic_cast<QgsComposerItem *>(( *itemIter ) );
if ( item )
{
//we've found the highest QgsComposerItem
mMoveContentStartPos = scenePoint;
mMoveContentItem = item;
break;
}
}
//no QgsComposerItem at clicked position
return;
}
case AddArrow: