Skip to content

Commit

Permalink
[composer] Draw mouse handles and item bounds outside of item's frame…
Browse files Browse the repository at this point in the history
…s, so that snapping occurs to edge of item frame (fix #8943)
  • Loading branch information
nyalldawson committed Feb 4, 2014
1 parent 0fc0ffb commit 78ecef6
Show file tree
Hide file tree
Showing 10 changed files with 166 additions and 20 deletions.
33 changes: 32 additions & 1 deletion python/core/composer/qgscomposeritem.sip
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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();

};
8 changes: 8 additions & 0 deletions python/core/composer/qgscomposershape.sip
Expand Up @@ -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();
};
4 changes: 1 addition & 3 deletions src/app/composer/qgscomposeritemwidget.cpp
Expand Up @@ -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();
}

Expand Down
4 changes: 1 addition & 3 deletions src/app/composer/qgscomposerscalebarwidget.cpp
Expand Up @@ -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();
Expand Down
1 change: 1 addition & 0 deletions src/app/composer/qgscomposershapewidget.cpp
Expand Up @@ -123,6 +123,7 @@ void QgsComposerShapeWidget::updateShapeStyle()
{
if ( mComposerShape )
{
mComposerShape->refreshSymbol();
QIcon icon = QgsSymbolLayerV2Utils::symbolPreviewIcon( mComposerShape->shapeStyleSymbol(), mShapeStyleButton->iconSize() );
mShapeStyleButton->setIcon( icon );
}
Expand Down
59 changes: 58 additions & 1 deletion src/core/composer/qgscomposeritem.cpp
Expand Up @@ -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 )
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 ) );
}

Expand Down
34 changes: 32 additions & 2 deletions src/core/composer/qgscomposeritem.h
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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;
Expand Down
23 changes: 13 additions & 10 deletions src/core/composer/qgscomposermousehandles.cpp
Expand Up @@ -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() );
Expand All @@ -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 );
Expand All @@ -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 );
}
Expand All @@ -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 )
Expand All @@ -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 );
}
}
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -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 );
Expand Down
11 changes: 11 additions & 0 deletions src/core/composer/qgscomposershape.cpp
Expand Up @@ -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()
Expand Down Expand Up @@ -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
{
Expand Down
9 changes: 9 additions & 0 deletions src/core/composer/qgscomposershape.h
Expand Up @@ -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*/
Expand Down

0 comments on commit 78ecef6

Please sign in to comment.