QGIS API Documentation 3.99.0-Master (a26b91b364d)
qgslayoutmousehandles.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgslayoutmousehandles.cpp
3 ------------------------
4 begin : September 2017
5 copyright : (C) 2017 by Nyall Dawson
6 email : nyall.dawson@gmail.com
7 ***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
19#include "moc_qgslayoutmousehandles.cpp"
20#include "qgis.h"
21#include "qgsgui.h"
22#include "qgslayout.h"
23#include "qgslayoutitem.h"
24#include "qgslayoututils.h"
25#include "qgslayoutview.h"
28#include "qgslayoutsnapper.h"
29#include "qgslayoutitemgroup.h"
30#include "qgslayoutmultiframe.h"
31#include "qgslayoutframe.h"
32#include "qgslayoutundostack.h"
34#include <QGraphicsView>
35#include <QGraphicsSceneHoverEvent>
36#include <QPainter>
37#include <QWidget>
38#include <limits>
39
41
42QgsLayoutMouseHandles::QgsLayoutMouseHandles( QgsLayout *layout, QgsLayoutView *view )
43 : QgsGraphicsViewMouseHandles( view )
44 , mLayout( layout )
45 , mView( view )
46{
47 setRotationEnabled( true );
48
49 //listen for selection changes, and update handles accordingly
50 connect( mLayout, &QGraphicsScene::selectionChanged, this, &QgsLayoutMouseHandles::selectionChanged );
51
52 mHorizontalSnapLine = mView->createSnapLine();
53 mHorizontalSnapLine->hide();
54 layout->addItem( mHorizontalSnapLine );
55 mVerticalSnapLine = mView->createSnapLine();
56 mVerticalSnapLine->hide();
57 layout->addItem( mVerticalSnapLine );
58}
59
60void QgsLayoutMouseHandles::paint( QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget )
61{
62 paintInternal( painter, mLayout->renderContext().isPreviewRender(), mLayout->renderContext().boundingBoxesVisible(), true, option, widget );
63}
64
65void QgsLayoutMouseHandles::selectionChanged()
66{
67 //listen out for selected items' size and rotation changed signals
68 const QList<QGraphicsItem *> itemList = layout()->items();
69 for ( QGraphicsItem *graphicsItem : itemList )
70 {
71 QgsLayoutItem *item = dynamic_cast<QgsLayoutItem *>( graphicsItem );
72 if ( !item )
73 continue;
74
75 if ( item->isSelected() )
76 {
77 connect( item, &QgsLayoutItem::sizePositionChanged, this, &QgsLayoutMouseHandles::selectedItemSizeChanged );
78 connect( item, &QgsLayoutItem::rotationChanged, this, &QgsLayoutMouseHandles::selectedItemRotationChanged );
79 connect( item, &QgsLayoutItem::frameChanged, this, &QgsLayoutMouseHandles::selectedItemSizeChanged );
80 connect( item, &QgsLayoutItem::lockChanged, this, &QgsLayoutMouseHandles::selectedItemSizeChanged );
81 }
82 else
83 {
84 disconnect( item, &QgsLayoutItem::sizePositionChanged, this, &QgsLayoutMouseHandles::selectedItemSizeChanged );
85 disconnect( item, &QgsLayoutItem::rotationChanged, this, &QgsLayoutMouseHandles::selectedItemRotationChanged );
86 disconnect( item, &QgsLayoutItem::frameChanged, this, &QgsLayoutMouseHandles::selectedItemSizeChanged );
87 disconnect( item, &QgsLayoutItem::lockChanged, this, &QgsLayoutMouseHandles::selectedItemSizeChanged );
88 }
89 }
90
91 resetStatusBar();
92 updateHandles();
93}
94
95void QgsLayoutMouseHandles::setViewportCursor( Qt::CursorShape cursor )
96{
97 //workaround qt bug #3732 by setting cursor for QGraphicsView viewport,
98 //rather then setting it directly here
99
100 if ( qobject_cast<QgsLayoutViewToolSelect *>( mView->tool() ) )
101 {
102 mView->viewport()->setCursor( cursor );
103 }
104}
105
106QList<QGraphicsItem *> QgsLayoutMouseHandles::sceneItemsAtPoint( QPointF scenePoint )
107{
108 QList<QGraphicsItem *> items;
109 if ( QgsLayoutViewToolSelect *tool = qobject_cast<QgsLayoutViewToolSelect *>( mView->tool() ) )
110 {
111 const double searchTolerance = tool->searchToleranceInLayoutUnits();
112 const QRectF area( scenePoint.x() - searchTolerance, scenePoint.y() - searchTolerance, 2 * searchTolerance, 2 * searchTolerance );
113 items = mLayout->items( area );
114 }
115 else
116 {
117 items = mLayout->items( scenePoint );
118 }
119 items.erase( std::remove_if( items.begin(), items.end(), []( QGraphicsItem *item ) {
120 return !dynamic_cast<QgsLayoutItem *>( item );
121 } ),
122 items.end() );
123
124 return items;
125}
126
127QList<QGraphicsItem *> QgsLayoutMouseHandles::selectedSceneItems( bool includeLockedItems ) const
128{
129 QList<QGraphicsItem *> res;
130 const QList<QgsLayoutItem *> layoutItems = mLayout->selectedLayoutItems( includeLockedItems );
131 res.reserve( layoutItems.size() );
132 for ( QgsLayoutItem *item : layoutItems )
133 res << item;
134 return res;
135}
136
137bool QgsLayoutMouseHandles::itemIsLocked( QGraphicsItem *item )
138{
139 if ( QgsLayoutItem *layoutItem = dynamic_cast<QgsLayoutItem *>( item ) )
140 return layoutItem->isLocked();
141 else
142 return false;
143}
144
145bool QgsLayoutMouseHandles::itemIsGroupMember( QGraphicsItem *item )
146{
147 if ( QgsLayoutItem *layoutItem = dynamic_cast<QgsLayoutItem *>( item ) )
148 return layoutItem->isGroupMember();
149 else
150 return false;
151}
152
153QRectF QgsLayoutMouseHandles::itemRect( QGraphicsItem *item ) const
154{
155 if ( QgsLayoutItem *layoutItem = dynamic_cast<QgsLayoutItem *>( item ) )
156 return layoutItem->rectWithFrame();
157 else
158 return QRectF();
159}
160
161QPointF QgsLayoutMouseHandles::snapPoint( QPointF originalPoint, QgsLayoutMouseHandles::SnapGuideMode mode, bool snapHorizontal, bool snapVertical )
162{
163 bool snapped = false;
164
165 const QList<QGraphicsItem *> selectedItems = selectedSceneItems();
166 QList<QGraphicsItem *> itemsToExclude;
167 expandItemList( selectedItems, itemsToExclude );
168
169 QList<QgsLayoutItem *> layoutItemsToExclude;
170 for ( QGraphicsItem *item : itemsToExclude )
171 layoutItemsToExclude << dynamic_cast<QgsLayoutItem *>( item );
172
173 //depending on the mode, we either snap just the single point, or all the bounds of the selection
174 QPointF snappedPoint;
175 switch ( mode )
176 {
177 case Item:
178 snappedPoint = mLayout->snapper().snapRect( rect().translated( originalPoint ), mView->transform().m11(), snapped, snapHorizontal ? mHorizontalSnapLine : nullptr, snapVertical ? mVerticalSnapLine : nullptr, &layoutItemsToExclude ).topLeft();
179 break;
180 case Point:
181 snappedPoint = mLayout->snapper().snapPoint( originalPoint, mView->transform().m11(), snapped, snapHorizontal ? mHorizontalSnapLine : nullptr, snapVertical ? mVerticalSnapLine : nullptr, &layoutItemsToExclude );
182 break;
183 }
184
185 return snapped ? snappedPoint : originalPoint;
186}
187
188void QgsLayoutMouseHandles::createItemCommand( QGraphicsItem *item )
189{
190 mItemCommand.reset( qgis::down_cast<QgsLayoutItem *>( item )->createCommand( QString(), 0 ) );
191 mItemCommand->saveBeforeState();
192}
193
194void QgsLayoutMouseHandles::endItemCommand( QGraphicsItem * )
195{
196 mItemCommand->saveAfterState();
197 mLayout->undoStack()->push( mItemCommand.release() );
198}
199
200void QgsLayoutMouseHandles::startMacroCommand( const QString &text )
201{
202 mLayout->undoStack()->beginMacro( text );
203}
204
205void QgsLayoutMouseHandles::endMacroCommand()
206{
207 mLayout->undoStack()->endMacro();
208}
209
210void QgsLayoutMouseHandles::hideAlignItems()
211{
212 mHorizontalSnapLine->hide();
213 mVerticalSnapLine->hide();
214}
215
216void QgsLayoutMouseHandles::expandItemList( const QList<QGraphicsItem *> &items, QList<QGraphicsItem *> &collected ) const
217{
218 for ( QGraphicsItem *item : items )
219 {
220 if ( item->type() == QgsLayoutItemRegistry::LayoutGroup )
221 {
222 // if a group is selected, we don't draw the bounds of the group - instead we draw the bounds of the grouped items
223 const QList<QgsLayoutItem *> groupItems = static_cast<QgsLayoutItemGroup *>( item )->items();
224 expandItemList( groupItems, collected );
225 }
226 else
227 {
228 collected << item;
229 }
230 }
231}
232
233
234void QgsLayoutMouseHandles::expandItemList( const QList<QgsLayoutItem *> &items, QList<QGraphicsItem *> &collected ) const
235{
236 for ( QGraphicsItem *item : items )
237 {
238 if ( item->type() == QgsLayoutItemRegistry::LayoutGroup )
239 {
240 // if a group is selected, we don't draw the bounds of the group - instead we draw the bounds of the grouped items
241 const QList<QgsLayoutItem *> groupItems = static_cast<QgsLayoutItemGroup *>( item )->items();
242 expandItemList( groupItems, collected );
243 }
244 else
245 {
246 collected << item;
247 }
248 }
249}
250
251void QgsLayoutMouseHandles::moveItem( QGraphicsItem *item, double deltaX, double deltaY )
252{
253 qgis::down_cast<QgsLayoutItem *>( item )->attemptMoveBy( deltaX, deltaY );
254}
255
256void QgsLayoutMouseHandles::rotateItem( QGraphicsItem *item, double deltaDegree, double deltaCenterX, double deltaCenterY )
257{
258 QgsLayoutItem *itm = qgis::down_cast<QgsLayoutItem *>( item );
259 QgsLayoutItem::ReferencePoint previousReferencePoint = itm->referencePoint();
261 itm->attemptMoveBy( deltaCenterX, deltaCenterY );
262 itm->setItemRotation( itm->itemRotation() + deltaDegree, true );
263 itm->setReferencePoint( previousReferencePoint );
264}
265
266void QgsLayoutMouseHandles::setItemRect( QGraphicsItem *item, QRectF rect )
267{
268 QgsLayoutItem *layoutItem = dynamic_cast<QgsLayoutItem *>( item );
269 layoutItem->attemptSetSceneRect( rect, true );
270}
271
272void QgsLayoutMouseHandles::showStatusMessage( const QString &message )
273{
274 if ( !mView )
275 return;
276
277 mView->pushStatusMessage( message );
278}
279
280
281void QgsLayoutMouseHandles::mouseDoubleClickEvent( QGraphicsSceneMouseEvent *event )
282{
283 QgsGraphicsViewMouseHandles::mouseDoubleClickEvent( event );
284
285 QList<QGraphicsItem *> items = selectedSceneItems();
286 if ( items.isEmpty() )
287 return;
288
289 QgsLayoutItem *item = dynamic_cast<QgsLayoutItem *>( items.first() );
290 if ( item == nullptr )
291 return;
292
293 // If item is a frame, use the multiFrame type
294 int itemtype = item->type();
295 if ( QgsLayoutFrame *frame = dynamic_cast<QgsLayoutFrame *>( item ) )
296 if ( QgsLayoutMultiFrame *multiFrame = frame->multiFrame() )
297 itemtype = multiFrame->type();
298
299 int metadataId = QgsGui::layoutItemGuiRegistry()->metadataIdForItemType( itemtype );
300 if ( metadataId == -1 )
301 {
302 return;
303 }
304 QgsGui::layoutItemGuiRegistry()->itemMetadata( metadataId )->handleDoubleClick( item, mouseActionForScenePos( event->scenePos() ) );
305}
306
static QgsLayoutItemGuiRegistry * layoutItemGuiRegistry()
Returns the global layout item GUI registry, used for registering the GUI behavior of layout items.
Definition qgsgui.cpp:140
Base class for frame items, which form a layout multiframe item.
virtual void handleDoubleClick(QgsLayoutItem *item, Qgis::MouseHandlesAction action)
Called when a layout item is double-clicked.
A container for grouping several QgsLayoutItems.
int metadataIdForItemType(int type) const
Returns the GUI item metadata ID which corresponds to the specified layout item type.
QgsLayoutItemAbstractGuiMetadata * itemMetadata(int metadataId) const
Returns the metadata for the specified item metadataId.
Base class for graphical items within a QgsLayout.
double itemRotation() const
Returns the current rotation for the item, in degrees clockwise.
virtual void setItemRotation(double rotation, bool adjustPosition=true)
Sets the layout item's rotation, in degrees clockwise.
void rotationChanged(double newRotation)
Emitted on item rotation change.
bool isLocked() const
Returns true if the item is locked, and cannot be interacted with using the mouse.
ReferencePoint referencePoint() const
Returns the reference point for positioning of the layout item.
ReferencePoint
Fixed position reference point.
@ Middle
Center of item.
int type() const override
Returns a unique graphics item type identifier.
void sizePositionChanged()
Emitted when the item's size or position changes.
void lockChanged()
Emitted if the item's lock status changes.
void frameChanged()
Emitted if the item's frame style changes.
void attemptMoveBy(double deltaX, double deltaY)
Attempts to shift the item's position by a specified deltaX and deltaY, in layout units.
void setReferencePoint(ReferencePoint point)
Sets the reference point for positioning of the layout item.
void attemptSetSceneRect(const QRectF &rect, bool includesFrame=false)
Attempts to update the item's position and size to match the passed rect in layout coordinates.
Abstract base class for layout items with the ability to distribute the content to several frames (Qg...
Layout view tool for selecting items in the layout.
A graphical widget to display and interact with QgsLayouts.
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition qgslayout.h:49