QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
qgslayoutviewtoolselect.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgslayoutviewtoolselect.cpp
3 ---------------------------
4 Date : July 2017
5 Copyright : (C) 2017 Nyall Dawson
6 Email : nyall dot dawson at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
18#include "qgslayoutview.h"
19#include "qgslayout.h"
20#include "qgslayoutitempage.h"
22#include "qgslayoutitemgroup.h"
23
24
25const double QgsLayoutViewToolSelect::sSearchToleranceInMillimeters = 2.0;
26
27
29 : QgsLayoutViewTool( view, tr( "Select" ) )
30{
31 setCursor( Qt::ArrowCursor );
32
33 mRubberBand.reset( new QgsLayoutViewRectangularRubberBand( view ) );
34 mRubberBand->setBrush( QBrush( QColor( 224, 178, 76, 63 ) ) );
35 mRubberBand->setPen( QPen( QBrush( QColor( 254, 58, 29, 100 ) ), 0, Qt::DotLine ) );
36}
37
39{
40 if ( mMouseHandles )
41 {
42 // want to force them to be removed from the scene
43 if ( mMouseHandles->scene() )
44 mMouseHandles->scene()->removeItem( mMouseHandles );
45 mMouseHandles->deleteLater();
46 }
47}
48
50{
51 if ( mMouseHandles->shouldBlockEvent( event ) )
52 {
53 //swallow clicks while dragging/resizing items
54 return;
55 }
56 if ( mMouseHandles->isVisible() )
57 {
58 //selection handles are being shown, get mouse action for current cursor position
59 QgsLayoutMouseHandles::MouseAction mouseAction = mMouseHandles->mouseActionForScenePos( event->layoutPoint() );
60
61 if ( mouseAction != QgsLayoutMouseHandles::MoveItem
62 && mouseAction != QgsLayoutMouseHandles::NoAction
63 && mouseAction != QgsLayoutMouseHandles::SelectItem )
64 {
65 //mouse is over a resize handle, so propagate event onward
66 event->ignore();
67 return;
68 }
69 }
70
71 if ( event->button() != Qt::LeftButton )
72 {
73 if ( mIsSelecting )
74 {
75 mIsSelecting = false;
76 mRubberBand->finish();
77 }
78 event->ignore();
79 return;
80 }
81
82 QgsLayoutItem *selectedItem = nullptr;
83 QgsLayoutItem *previousSelectedItem = nullptr;
84
85 QList<QgsLayoutItem *> selectedItems = layout()->selectedLayoutItems();
86
87 if ( event->modifiers() & Qt::ControlModifier )
88 {
89 //CTRL modifier, so we are trying to select the next item below the current one
90 //first, find currently selected item
91 if ( !selectedItems.isEmpty() )
92 {
93 previousSelectedItem = selectedItems.at( 0 );
94 }
95 }
96
97 if ( previousSelectedItem )
98 {
99 //select highest item just below previously selected item at position of event
100 selectedItem = layout()->layoutItemAt( event->layoutPoint(), previousSelectedItem, true, searchToleranceInLayoutUnits() );
101
102 //if we didn't find a lower item we'll use the top-most as fall-back
103 //this duplicates mapinfo/illustrator/etc behavior where ctrl-clicks are "cyclic"
104 if ( !selectedItem )
105 {
106 selectedItem = layout()->layoutItemAt( event->layoutPoint(), true, searchToleranceInLayoutUnits() );
107 }
108 }
109 else
110 {
111 //select topmost item at position of event
112 selectedItem = layout()->layoutItemAt( event->layoutPoint(), true, searchToleranceInLayoutUnits() );
113 }
114
115 // if selected item is in a group, we actually get the top-level group it's part of
116 QgsLayoutItemGroup *group = selectedItem ? selectedItem->parentGroup() : nullptr;
117 while ( group && group->parentGroup() )
118 {
119 group = group->parentGroup();
120 }
121 if ( group )
122 selectedItem = group;
123
124 if ( !selectedItem )
125 {
126 //not clicking over an item, so start marquee selection
127 mIsSelecting = true;
128 mMousePressStartPos = event->pos();
129 mRubberBand->start( event->layoutPoint(), Qt::KeyboardModifiers() );
130 return;
131 }
132
133 if ( ( event->modifiers() & Qt::ShiftModifier ) && ( selectedItem->isSelected() ) )
134 {
135 //SHIFT-clicking a selected item deselects it
136 selectedItem->setSelected( false );
137
138 //Check if we have any remaining selected items, and if so, update the item panel
139 const QList<QgsLayoutItem *> selectedItems = layout()->selectedLayoutItems();
140 if ( !selectedItems.isEmpty() )
141 {
142 emit itemFocused( selectedItems.at( 0 ) );
143 }
144 else
145 {
146 emit itemFocused( nullptr );
147 }
148 }
149 else
150 {
151 if ( ( !selectedItem->isSelected() ) && //keep selection if an already selected item pressed
152 !( event->modifiers() & Qt::ShiftModifier ) ) //keep selection if shift key pressed
153 {
154 layout()->setSelectedItem( selectedItem ); // clears existing selection
155 }
156 else
157 {
158 selectedItem->setSelected( true );
159 }
160
161 // Due to the selection tolerance, items can be selected while the mouse is not over them,
162 // so we cannot just forward the mouse press event by calling event->ignore().
163 // We call startMove to simulate a mouse press event on the handles
164 mMouseHandles->startMove( view()->mapToScene( event->pos() ) );
165
166 emit itemFocused( selectedItem );
167 }
168}
169
171{
172 if ( mIsSelecting )
173 {
174 mRubberBand->update( event->layoutPoint(), Qt::KeyboardModifiers() );
175 }
176 else
177 {
178 if ( !mMouseHandles->isDragging() && !mMouseHandles->isResizing() )
179 {
180 if ( layout()->layoutItemAt( event->layoutPoint(), true, searchToleranceInLayoutUnits() ) )
181 {
182 view()->viewport()->setCursor( Qt::SizeAllCursor );
183 }
184 else
185 {
186 view()->viewport()->setCursor( Qt::ArrowCursor );
187 }
188 }
189 event->ignore();
190 }
191}
192
194{
195 if ( event->button() != Qt::LeftButton && mMouseHandles->shouldBlockEvent( event ) )
196 {
197 //swallow clicks while dragging/resizing items
198 return;
199 }
200
201 if ( !mIsSelecting || event->button() != Qt::LeftButton )
202 {
203 event->ignore();
204 return;
205 }
206
207 mIsSelecting = false;
208 bool wasClick = !isClickAndDrag( mMousePressStartPos, event->pos() );
209
210 // important -- we don't pass the event modifiers here, because we use them for a different meaning!
211 // (modifying how the selection interacts with the items, rather than modifying the selection shape)
212 QRectF rect = mRubberBand->finish( event->layoutPoint() );
213
214 bool subtractingSelection = false;
215 if ( event->modifiers() & Qt::ShiftModifier )
216 {
217 //shift modifier means adding to selection, nothing required here
218 }
219 else if ( event->modifiers() & Qt::ControlModifier )
220 {
221 //control modifier means subtract from current selection
222 subtractingSelection = true;
223 }
224 else
225 {
226 //not adding to or removing from selection, so clear current selection
227 whileBlocking( layout() )->deselectAll();
228 }
229
230 //determine item selection mode, default to intersection
231 Qt::ItemSelectionMode selectionMode = Qt::IntersectsItemShape;
232 if ( event->modifiers() & Qt::AltModifier )
233 {
234 //alt modifier switches to contains selection mode
235 selectionMode = Qt::ContainsItemShape;
236 }
237
238 //find all items in rect
239 QList<QGraphicsItem *> itemList;
240 if ( wasClick )
241 {
242 const double tolerance = searchToleranceInLayoutUnits();
243 itemList = layout()->items( QRectF( rect.center().x() - tolerance, rect.center().y() - tolerance, 2 * tolerance, 2 * tolerance ), selectionMode );
244 }
245 else
246 itemList = layout()->items( rect, selectionMode );
247
248 QgsLayoutItemPage *focusedPaperItem = nullptr;
249 for ( QGraphicsItem *item : std::as_const( itemList ) )
250 {
251 QgsLayoutItem *layoutItem = dynamic_cast<QgsLayoutItem *>( item );
252 QgsLayoutItemPage *paperItem = dynamic_cast<QgsLayoutItemPage *>( item );
253 if ( paperItem )
254 focusedPaperItem = paperItem;
255
256 if ( layoutItem && !paperItem )
257 {
258 if ( !layoutItem->isLocked() )
259 {
260 if ( subtractingSelection )
261 {
262 layoutItem->setSelected( false );
263 }
264 else
265 {
266 layoutItem->setSelected( true );
267 }
268 if ( wasClick )
269 {
270 // found an item, and only a click - nothing more to do
271 break;
272 }
273 }
274 }
275 }
276
277
278 //update item panel
279 const QList<QgsLayoutItem *> selectedItemList = layout()->selectedLayoutItems();
280 if ( !selectedItemList.isEmpty() )
281 {
282 emit itemFocused( selectedItemList.at( 0 ) );
283 }
284 else if ( focusedPaperItem )
285 {
286 emit itemFocused( focusedPaperItem );
287 }
288 else
289 {
290 emit itemFocused( nullptr );
291 }
292 mMouseHandles->selectionChanged();
293}
294
295void QgsLayoutViewToolSelect::wheelEvent( QWheelEvent *event )
296{
297 if ( mMouseHandles->shouldBlockEvent( event ) )
298 {
299 //ignore wheel events while dragging/resizing items
300 return;
301 }
302 else
303 {
304 event->ignore();
305 }
306}
307
309{
310 if ( mMouseHandles->isDragging() || mMouseHandles->isResizing() )
311 {
312 return;
313 }
314 else
315 {
316 event->ignore();
317 }
318}
319
321{
322 if ( mIsSelecting )
323 {
324 mRubberBand->finish();
325 mIsSelecting = false;
326 }
328}
329
331QgsLayoutMouseHandles *QgsLayoutViewToolSelect::mouseHandles()
332{
333 return mMouseHandles;
334}
335
337{
338 // existing handles are owned by previous layout
339 if ( mMouseHandles )
340 mMouseHandles->deleteLater();
341
342 //add mouse selection handles to layout, and initially hide
343 mMouseHandles = new QgsLayoutMouseHandles( layout, view() );
344 mMouseHandles->hide();
345 mMouseHandles->setZValue( QgsLayout::ZMouseHandles );
346 layout->addItem( mMouseHandles );
347}
349{
350 const double pixelsPerMm = view()->physicalDpiX() / 25.4;
351 return sSearchToleranceInMillimeters * pixelsPerMm / view()->transform().m11();
352}
A container for grouping several QgsLayoutItems.
Item representing the paper in a layout.
Base class for graphical items within a QgsLayout.
QgsLayoutItemGroup * parentGroup() const
Returns the item's parent group, if the item is part of a QgsLayoutItemGroup group.
virtual void setSelected(bool selected)
Sets whether the item should be selected.
bool isLocked() const
Returns true if the item is locked, and cannot be interacted with using the mouse.
A QgsLayoutViewMouseEvent is the result of a user interaction with the mouse on a QgsLayoutView.
QPointF layoutPoint() const
Returns the event point location in layout coordinates.
QgsLayoutViewRectangularRubberBand is rectangular rubber band for use within QgsLayoutView widgets.
void deactivate() override
Called when tool is deactivated.
void keyPressEvent(QKeyEvent *event) override
Key press event for overriding.
void wheelEvent(QWheelEvent *event) override
Mouse wheel event for overriding.
void layoutPressEvent(QgsLayoutViewMouseEvent *event) override
Mouse press event for overriding.
double searchToleranceInLayoutUnits()
Compute the search tolerance in layout units from the view current scale.
void layoutMoveEvent(QgsLayoutViewMouseEvent *event) override
Mouse move event for overriding.
void layoutReleaseEvent(QgsLayoutViewMouseEvent *event) override
Mouse release event for overriding.
QgsLayoutViewToolSelect(QgsLayoutView *view)
Constructor for QgsLayoutViewToolSelect.
void setLayout(QgsLayout *layout)
Sets the a layout.
Abstract base class for all layout view tools.
void setCursor(const QCursor &cursor)
Sets a user defined cursor for use when the tool is active.
QgsLayoutView * view() const
Returns the view associated with the tool.
virtual void deactivate()
Called when tool is deactivated.
bool isClickAndDrag(QPoint startViewPoint, QPoint endViewPoint) const
Returns true if a mouse press/release operation which started at startViewPoint and ended at endViewP...
QgsLayout * layout() const
Returns the layout associated with the tool.
void itemFocused(QgsLayoutItem *item)
Emitted when an item is "focused" by the tool, i.e.
A graphical widget to display and interact with QgsLayouts.
Definition: qgslayoutview.h:50
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition: qgslayout.h:49
QgsLayoutItem * layoutItemAt(QPointF position, bool ignoreLocked=false, double searchTolerance=0) const
Returns the topmost layout item at a specified position.
Definition: qgslayout.cpp:302
QList< QgsLayoutItem * > selectedLayoutItems(bool includeLockedItems=true)
Returns list of selected layout items.
Definition: qgslayout.cpp:151
void setSelectedItem(QgsLayoutItem *item)
Clears any selected items and sets item as the current selection.
Definition: qgslayout.cpp:168
@ ZMouseHandles
Z-value for mouse handles.
Definition: qgslayout.h:62
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition: qgis.h:5111