[composer] Draw mouse handles and item bounds outside of item's frames, so that snapping occurs to edge of item frame (fix #8943)

This commit is contained in:
Nyall Dawson 2014-02-04 18:34:47 +11:00
parent 0fc0ffbb8d
commit 78ecef6f2a
10 changed files with 166 additions and 20 deletions

View File

@ -183,8 +183,9 @@ class QgsComposerItem : QObject, QGraphicsRectItem
void setItemPosition( double x, double y, ItemPositionMode itemPoint = UpperLeft );
/**Sets item position and width / height in one go
* @param posIncludesFrame set to true if the position and size arguments include the item's frame border
@note: this method was added in version 1.6*/
void setItemPosition( double x, double y, double width, double height, ItemPositionMode itemPoint = UpperLeft );
void setItemPosition( double x, double y, double width, double height, ItemPositionMode itemPoint = UpperLeft, bool posIncludesFrame = false );
/**Returns item's last used position mode.
@note: This property has no effect on actual's item position, which is always the top-left corner.
@ -228,6 +229,31 @@ class QgsComposerItem : QObject, QGraphicsRectItem
*/
void setFrameEnabled( bool drawFrame );
/** Sets frame outline width
* @param outlineWidth new width for outline frame
* @returns nothing
* @note introduced in 2.2
* @see setFrameEnabled
*/
virtual void setFrameOutlineWidth( double outlineWidth );
/** Returns the estimated amount the item's frame bleeds outside the item's
* actual rectangle. For instance, if the item has a 2mm frame outline, then
* 1mm of this frame is drawn outside the item's rect. In this case the
* return value will be 1.0
* @note introduced in 2.2
*/
virtual double estimatedFrameBleed() const;
/** Returns the item's rectangular bounds, including any bleed caused by the item's frame.
* The bounds are returned in the item's coordinate system (see Qt's QGraphicsItem docs for
* more details about QGraphicsItem coordinate systems). The results differ from Qt's rect()
* function, as rect() makes no allowances for the portion of outlines which are drawn
* outside of the item.
* @note introduced in 2.2
* @see estimatedFrameBleed
*/
virtual QRectF rectWithFrame() const;
/** Whether this item has a Background or not.
* @returns true if there is a Background around this item, otherwise false.
@ -466,4 +492,9 @@ class QgsComposerItem : QObject, QGraphicsRectItem
void itemChanged();
/**Emitted if the rectangle changes*/
void sizeChanged();
/**Emitted if the item's frame style changes
* @note: this function was introduced in version 2.2
*/
void frameChanged();
};

View File

@ -61,4 +61,12 @@ class QgsComposerShape: QgsComposerItem
virtual void drawFrame( QPainter* p );
/* reimplement drawBackground, since it's not a rect, but a custom shape */
virtual void drawBackground( QPainter* p );
/**reimplement estimatedFrameBleed, since frames on shapes are drawn using symbology
* rather than the item's pen */
virtual double estimatedFrameBleed() const;
public slots:
/**Should be called after the shape's symbol is changed. Redraws the shape and recalculates
* its selection bounds.
* Note: added in version 2.1*/
void refreshSymbol();
};

View File

@ -196,9 +196,7 @@ void QgsComposerItemWidget::on_mOutlineWidthSpinBox_valueChanged( double d )
}
mItem->beginCommand( tr( "Item outline width" ), QgsComposerMergeCommand::ItemOutlineWidth );
QPen itemPen = mItem->pen();
itemPen.setWidthF( d );
mItem->setPen( itemPen );
mItem->setFrameOutlineWidth( d );
mItem->endCommand();
}

View File

@ -206,9 +206,7 @@ void QgsComposerScaleBarWidget::on_mLineWidthSpinBox_valueChanged( double d )
mComposerScaleBar->beginCommand( tr( "Scalebar line width" ), QgsComposerMergeCommand::ScaleBarLineWidth );
disconnectUpdateSignal();
QPen newPen( mComposerScaleBar->pen().color() );
newPen.setWidthF( d );
mComposerScaleBar->setPen( newPen );
mComposerScaleBar->setFrameOutlineWidth( d );
mComposerScaleBar->update();
connectUpdateSignal();
mComposerScaleBar->endCommand();

View File

@ -123,6 +123,7 @@ void QgsComposerShapeWidget::updateShapeStyle()
{
if ( mComposerShape )
{
mComposerShape->refreshSymbol();
QIcon icon = QgsSymbolLayerV2Utils::symbolPreviewIcon( mComposerShape->shapeStyleSymbol(), mShapeStyleButton->iconSize() );
mShapeStyleButton->setIcon( icon );
}

View File

@ -349,6 +349,41 @@ bool QgsComposerItem::_readXML( const QDomElement& itemElem, const QDomDocument&
return true;
}
void QgsComposerItem::setFrameEnabled( bool drawFrame )
{
mFrame = drawFrame;
emit frameChanged();
}
void QgsComposerItem::setFrameOutlineWidth( double outlineWidth )
{
QPen itemPen = pen();
if ( itemPen.widthF() == outlineWidth )
{
//no change
return;
}
itemPen.setWidthF( outlineWidth );
setPen( itemPen );
emit frameChanged();
}
double QgsComposerItem::estimatedFrameBleed() const
{
if ( !hasFrame() )
{
return 0;
}
return pen().widthF() / 2.0;
}
QRectF QgsComposerItem::rectWithFrame() const
{
double frameBleed = estimatedFrameBleed();
return rect().adjusted( -frameBleed, -frameBleed, frameBleed, frameBleed );
}
void QgsComposerItem::beginCommand( const QString& commandText, QgsComposerMergeCommand::Context c )
{
if ( mComposition )
@ -432,7 +467,7 @@ void QgsComposerItem::setItemPosition( double x, double y, ItemPositionMode item
setItemPosition( x, y, width, height, itemPoint );
}
void QgsComposerItem::setItemPosition( double x, double y, double width, double height, ItemPositionMode itemPoint )
void QgsComposerItem::setItemPosition( double x, double y, double width, double height, ItemPositionMode itemPoint, bool posIncludesFrame )
{
double upperLeftX = x;
double upperLeftY = y;
@ -460,6 +495,28 @@ void QgsComposerItem::setItemPosition( double x, double y, double width, double
upperLeftY -= height;
}
if ( posIncludesFrame )
{
//adjust position to account for frame size
if ( mItemRotation == 0 )
{
upperLeftX += estimatedFrameBleed();
upperLeftY += estimatedFrameBleed();
}
else
{
//adjust position for item rotation
QLineF lineToItemOrigin = QLineF( 0, 0, estimatedFrameBleed(), estimatedFrameBleed() );
lineToItemOrigin.setAngle( -45 - mItemRotation );
upperLeftX += lineToItemOrigin.x2();
upperLeftY += lineToItemOrigin.y2();
}
width -= 2 * estimatedFrameBleed();
height -= 2 * estimatedFrameBleed();
}
setSceneRect( QRectF( upperLeftX, upperLeftY, width, height ) );
}

View File

@ -137,8 +137,9 @@ class CORE_EXPORT QgsComposerItem: public QObject, public QGraphicsRectItem
void setItemPosition( double x, double y, ItemPositionMode itemPoint = UpperLeft );
/**Sets item position and width / height in one go
*@param posIncludesFrame set to true if the position and size arguments include the item's frame border
@note: this method was added in version 1.6*/
void setItemPosition( double x, double y, double width, double height, ItemPositionMode itemPoint = UpperLeft );
void setItemPosition( double x, double y, double width, double height, ItemPositionMode itemPoint = UpperLeft, bool posIncludesFrame = false );
/**Returns item's last used position mode.
@note: This property has no effect on actual's item position, which is always the top-left corner.
@ -180,8 +181,33 @@ class CORE_EXPORT QgsComposerItem: public QObject, public QGraphicsRectItem
* @note introduced in 1.8
* @see hasFrame
*/
void setFrameEnabled( bool drawFrame ) {mFrame = drawFrame;}
void setFrameEnabled( bool drawFrame );
/** Sets frame outline width
* @param outlineWidth new width for outline frame
* @returns nothing
* @note introduced in 2.2
* @see setFrameEnabled
*/
virtual void setFrameOutlineWidth( double outlineWidth );
/** Returns the estimated amount the item's frame bleeds outside the item's
* actual rectangle. For instance, if the item has a 2mm frame outline, then
* 1mm of this frame is drawn outside the item's rect. In this case the
* return value will be 1.0
* @note introduced in 2.2
*/
virtual double estimatedFrameBleed() const;
/** Returns the item's rectangular bounds, including any bleed caused by the item's frame.
* The bounds are returned in the item's coordinate system (see Qt's QGraphicsItem docs for
* more details about QGraphicsItem coordinate systems). The results differ from Qt's rect()
* function, as rect() makes no allowances for the portion of outlines which are drawn
* outside of the item.
* @note introduced in 2.2
* @see estimatedFrameBleed
*/
virtual QRectF rectWithFrame() const;
/** Whether this item has a Background or not.
* @returns true if there is a Background around this item, otherwise false.
@ -456,6 +482,10 @@ class CORE_EXPORT QgsComposerItem: public QObject, public QGraphicsRectItem
void itemChanged();
/**Emitted if the rectangle changes*/
void sizeChanged();
/**Emitted if the item's frame style changes
* @note: this function was introduced in version 2.2
*/
void frameChanged();
private:
// id (not unique)
QString mId;

View File

@ -153,7 +153,7 @@ void QgsComposerMouseHandles::drawSelectedItemBounds( QPainter* painter )
{
//if currently dragging, draw selected item bounds relative to current mouse position
//first, get bounds of current item in scene coordinates
QPolygonF itemSceneBounds = ( *itemIter )->mapToScene(( *itemIter )->rect() );
QPolygonF itemSceneBounds = ( *itemIter )->mapToScene(( *itemIter )->rectWithFrame() );
//now, translate it by the current movement amount
//IMPORTANT - this is done in scene coordinates, since we don't want any rotation/non-translation transforms to affect the movement
itemSceneBounds.translate( transform().dx(), transform().dy() );
@ -166,7 +166,7 @@ void QgsComposerMouseHandles::drawSelectedItemBounds( QPainter* painter )
if ( selectedItems.size() > 1 )
{
//get item bounds in mouse handle item's coordinate system
QRectF itemRect = mapRectFromItem(( *itemIter ), ( *itemIter )->rect() );
QRectF itemRect = mapRectFromItem(( *itemIter ), ( *itemIter )->rectWithFrame() );
//now, resize it relative to the current resized dimensions of the mouse handles
QgsComposition::relativeResizeRect( itemRect, QRectF( -mResizeMoveX, -mResizeMoveY, mBeginHandleWidth, mBeginHandleHeight ), mResizeRect );
itemBounds = QPolygonF( itemRect );
@ -180,7 +180,7 @@ void QgsComposerMouseHandles::drawSelectedItemBounds( QPainter* painter )
else
{
//not resizing or moving, so just map from scene bounds
itemBounds = mapRectFromItem(( *itemIter ), ( *itemIter )->rect() );
itemBounds = mapRectFromItem(( *itemIter ), ( *itemIter )->rectWithFrame() );
}
painter->drawPolygon( itemBounds );
}
@ -189,7 +189,7 @@ void QgsComposerMouseHandles::drawSelectedItemBounds( QPainter* painter )
void QgsComposerMouseHandles::selectionChanged()
{
//listen out for selected items' sizeChanged signal
//listen out for selected items' size and rotation changed signals
QList<QGraphicsItem *> itemList = composition()->items();
QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
for ( ; itemIt != itemList.end(); ++itemIt )
@ -201,10 +201,13 @@ void QgsComposerMouseHandles::selectionChanged()
{
QObject::connect( item, SIGNAL( sizeChanged() ), this, SLOT( selectedItemSizeChanged() ) );
QObject::connect( item, SIGNAL( itemRotationChanged( double ) ), this, SLOT( selectedItemRotationChanged() ) );
QObject::connect( item, SIGNAL( frameChanged( ) ), this, SLOT( selectedItemSizeChanged() ) );
}
else
{
QObject::disconnect( item, SIGNAL( sizeChanged() ), this, 0 );
QObject::disconnect( item, SIGNAL( itemRotationChanged( double ) ), this, 0 );
QObject::disconnect( item, SIGNAL( frameChanged( ) ), this, 0 );
}
}
}
@ -279,12 +282,12 @@ QRectF QgsComposerMouseHandles::selectionBounds() const
QList<QgsComposerItem*>::iterator itemIter = selectedItems.begin();
//start with handle bounds of first selected item
QRectF bounds = mapFromItem(( *itemIter ), ( *itemIter )->rect() ).boundingRect();
QRectF bounds = mapFromItem(( *itemIter ), ( *itemIter )->rectWithFrame() ).boundingRect();
//iterate through remaining items, expanding the bounds as required
for ( ++itemIter; itemIter != selectedItems.end(); ++itemIter )
{
bounds = bounds.united( mapFromItem(( *itemIter ), ( *itemIter )->rect() ).boundingRect() );
bounds = bounds.united( mapFromItem(( *itemIter ), ( *itemIter )->rectWithFrame() ).boundingRect() );
}
return bounds;
@ -627,19 +630,19 @@ void QgsComposerMouseHandles::mouseReleaseEvent( QGraphicsSceneMouseEvent* event
QRectF itemRect;
if ( selectedItems.size() == 1 )
{
//only a single item is selected, so set it's size to the final resized mouse handle size
//only a single item is selected, so set its size to the final resized mouse handle size
itemRect = mResizeRect;
}
else
{
//multiple items selected, so each needs to be scaled relatively to the final size of the mouse handles
itemRect = mapRectFromItem(( *itemIter ), ( *itemIter )->rect() );
itemRect = mapRectFromItem(( *itemIter ), ( *itemIter )->rectWithFrame() );
QgsComposition::relativeResizeRect( itemRect, QRectF( -mResizeMoveX, -mResizeMoveY, mBeginHandleWidth, mBeginHandleHeight ), mResizeRect );
}
itemRect = itemRect.normalized();
QPointF newPos = mapToScene( itemRect.topLeft() );
( *itemIter )->setItemPosition( newPos.x(), newPos.y(), itemRect.width(), itemRect.height() );
( *itemIter )->setItemPosition( newPos.x(), newPos.y(), itemRect.width(), itemRect.height(), QgsComposerItem::UpperLeft, true );
subcommand->saveAfterState();
}
@ -1255,7 +1258,7 @@ void QgsComposerMouseHandles::collectAlignCoordinates( QMap< double, const QgsCo
}
else
{
itemRect = currentItem->sceneBoundingRect();
itemRect = currentItem->mapRectToScene( currentItem->rectWithFrame() );
}
alignCoordsX.insert( itemRect.left(), currentItem );
alignCoordsX.insert( itemRect.right(), currentItem );

View File

@ -59,6 +59,13 @@ void QgsComposerShape::setShapeStyleSymbol( QgsFillSymbolV2* symbol )
delete mShapeStyleSymbol;
mShapeStyleSymbol = symbol;
update();
emit frameChanged();
}
void QgsComposerShape::refreshSymbol()
{
update();
emit frameChanged();
}
void QgsComposerShape::createDefaultShapeStyleSymbol()
@ -242,6 +249,10 @@ void QgsComposerShape::drawBackground( QPainter* p )
}
}
double QgsComposerShape::estimatedFrameBleed() const
{
return QgsSymbolLayerV2Utils::estimateMaxSymbolBleed( mShapeStyleSymbol );
}
bool QgsComposerShape::writeXML( QDomElement& elem, QDomDocument & doc ) const
{

View File

@ -85,6 +85,15 @@ class CORE_EXPORT QgsComposerShape: public QgsComposerItem
virtual void drawFrame( QPainter* p );
/* reimplement drawBackground, since it's not a rect, but a custom shape */
virtual void drawBackground( QPainter* p );
/**reimplement estimatedFrameBleed, since frames on shapes are drawn using symbology
* rather than the item's pen */
virtual double estimatedFrameBleed() const;
public slots:
/**Should be called after the shape's symbol is changed. Redraws the shape and recalculates
* its selection bounds.
* Note: added in version 2.1*/
void refreshSymbol();
private:
/**Ellipse, rectangle or triangle*/