QGIS API Documentation 3.41.0-Master (3440c17df1d)
Loading...
Searching...
No Matches
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
17#include "moc_qgslayoutviewtoolselect.cpp"
19#include "qgslayoutview.h"
20#include "qgslayout.h"
21#include "qgslayoutitempage.h"
23#include "qgslayoutitemgroup.h"
24
25
26const double QgsLayoutViewToolSelect::sSearchToleranceInMillimeters = 2.0;
27
28
30 : QgsLayoutViewTool( view, tr( "Select" ) )
31{
32 setCursor( Qt::ArrowCursor );
33
34 mRubberBand.reset( new QgsLayoutViewRectangularRubberBand( view ) );
35 mRubberBand->setBrush( QBrush( QColor( 224, 178, 76, 63 ) ) );
36 mRubberBand->setPen( QPen( QBrush( QColor( 254, 58, 29, 100 ) ), 0, Qt::DotLine ) );
37}
38
40{
41 if ( mMouseHandles )
42 {
43 // want to force them to be removed from the scene
44 if ( mMouseHandles->scene() )
45 mMouseHandles->scene()->removeItem( mMouseHandles );
46 mMouseHandles->deleteLater();
47 }
48}
49
51{
52 if ( mMouseHandles->shouldBlockEvent( event ) )
53 {
54 //swallow clicks while dragging/resizing items
55 return;
56 }
57 if ( mMouseHandles->isVisible() )
58 {
59 //selection handles are being shown, get mouse action for current cursor position
60 QgsLayoutMouseHandles::MouseAction mouseAction = mMouseHandles->mouseActionForScenePos( event->layoutPoint() );
61
62 if ( mouseAction != QgsLayoutMouseHandles::MoveItem
63 && mouseAction != QgsLayoutMouseHandles::NoAction
64 && mouseAction != QgsLayoutMouseHandles::SelectItem )
65 {
66 //mouse is over a resize handle, so propagate event onward
67 event->ignore();
68 return;
69 }
70 }
71
72 if ( event->button() != Qt::LeftButton )
73 {
74 if ( mIsSelecting )
75 {
76 mIsSelecting = false;
77 mRubberBand->finish();
78 }
79 event->ignore();
80 return;
81 }
82
83 QgsLayoutItem *selectedItem = nullptr;
84 QgsLayoutItem *previousSelectedItem = nullptr;
85
86 QList<QgsLayoutItem *> selectedItems = layout()->selectedLayoutItems();
87
88 if ( event->modifiers() & Qt::ControlModifier )
89 {
90 //CTRL modifier, so we are trying to select the next item below the current one
91 //first, find currently selected item
92 if ( !selectedItems.isEmpty() )
93 {
94 previousSelectedItem = selectedItems.at( 0 );
95 }
96 }
97
98 if ( previousSelectedItem )
99 {
100 //select highest item just below previously selected item at position of event
101 selectedItem = layout()->layoutItemAt( event->layoutPoint(), previousSelectedItem, true, searchToleranceInLayoutUnits() );
102
103 //if we didn't find a lower item we'll use the top-most as fall-back
104 //this duplicates mapinfo/illustrator/etc behavior where ctrl-clicks are "cyclic"
105 if ( !selectedItem )
106 {
107 selectedItem = layout()->layoutItemAt( event->layoutPoint(), true, searchToleranceInLayoutUnits() );
108 }
109 }
110 else
111 {
112 //select topmost item at position of event
113 selectedItem = layout()->layoutItemAt( event->layoutPoint(), true, searchToleranceInLayoutUnits() );
114 }
115
116 // if selected item is in a group, we actually get the top-level group it's part of
117 QgsLayoutItemGroup *group = selectedItem ? selectedItem->parentGroup() : nullptr;
118 while ( group && group->parentGroup() )
119 {
120 group = group->parentGroup();
121 }
122 if ( group )
123 selectedItem = group;
124
125 if ( !selectedItem )
126 {
127 //not clicking over an item, so start marquee selection
128 mIsSelecting = true;
129 mMousePressStartPos = event->pos();
130 mRubberBand->start( event->layoutPoint(), Qt::KeyboardModifiers() );
131 return;
132 }
133
134 if ( ( event->modifiers() & Qt::ShiftModifier ) && ( selectedItem->isSelected() ) )
135 {
136 //SHIFT-clicking a selected item deselects it
137 selectedItem->setSelected( false );
138
139 //Check if we have any remaining selected items, and if so, update the item panel
140 const QList<QgsLayoutItem *> selectedItems = layout()->selectedLayoutItems();
141 if ( !selectedItems.isEmpty() )
142 {
143 emit itemFocused( selectedItems.at( 0 ) );
144 }
145 else
146 {
147 emit itemFocused( nullptr );
148 }
149 }
150 else
151 {
152 if ( ( !selectedItem->isSelected() ) && //keep selection if an already selected item pressed
153 !( event->modifiers() & Qt::ShiftModifier ) ) //keep selection if shift key pressed
154 {
155 layout()->setSelectedItem( selectedItem ); // clears existing selection
156 }
157 else
158 {
159 selectedItem->setSelected( true );
160 }
161
162 // Due to the selection tolerance, items can be selected while the mouse is not over them,
163 // so we cannot just forward the mouse press event by calling event->ignore().
164 // We call startMove to simulate a mouse press event on the handles
165 mMouseHandles->startMove( view()->mapToScene( event->pos() ) );
166
167 emit itemFocused( selectedItem );
168 }
169}
170
172{
173 if ( mIsSelecting )
174 {
175 mRubberBand->update( event->layoutPoint(), Qt::KeyboardModifiers() );
176 }
177 else
178 {
179 if ( !mMouseHandles->isDragging() && !mMouseHandles->isResizing() )
180 {
181 if ( layout()->layoutItemAt( event->layoutPoint(), true, searchToleranceInLayoutUnits() ) )
182 {
183 view()->viewport()->setCursor( Qt::SizeAllCursor );
184 }
185 else
186 {
187 view()->viewport()->setCursor( Qt::ArrowCursor );
188 }
189 }
190 event->ignore();
191 }
192}
193
195{
196 if ( event->button() != Qt::LeftButton && mMouseHandles->shouldBlockEvent( event ) )
197 {
198 //swallow clicks while dragging/resizing items
199 return;
200 }
201
202 if ( !mIsSelecting || event->button() != Qt::LeftButton )
203 {
204 event->ignore();
205 return;
206 }
207
208 mIsSelecting = false;
209 bool wasClick = !isClickAndDrag( mMousePressStartPos, event->pos() );
210
211 // important -- we don't pass the event modifiers here, because we use them for a different meaning!
212 // (modifying how the selection interacts with the items, rather than modifying the selection shape)
213 QRectF rect = mRubberBand->finish( event->layoutPoint() );
214
215 bool subtractingSelection = false;
216 if ( event->modifiers() & Qt::ShiftModifier )
217 {
218 //shift modifier means adding to selection, nothing required here
219 }
220 else if ( event->modifiers() & Qt::ControlModifier )
221 {
222 //control modifier means subtract from current selection
223 subtractingSelection = true;
224 }
225 else
226 {
227 //not adding to or removing from selection, so clear current selection
228 whileBlocking( layout() )->deselectAll();
229 }
230
231 //determine item selection mode, default to intersection
232 Qt::ItemSelectionMode selectionMode = Qt::IntersectsItemShape;
233 if ( event->modifiers() & Qt::AltModifier )
234 {
235 //alt modifier switches to contains selection mode
236 selectionMode = Qt::ContainsItemShape;
237 }
238
239 //find all items in rect
240 QList<QGraphicsItem *> itemList;
241 if ( wasClick )
242 {
243 const double tolerance = searchToleranceInLayoutUnits();
244 itemList = layout()->items( QRectF( rect.center().x() - tolerance, rect.center().y() - tolerance, 2 * tolerance, 2 * tolerance ), selectionMode );
245 }
246 else
247 itemList = layout()->items( rect, selectionMode );
248
249 QgsLayoutItemPage *focusedPaperItem = nullptr;
250 for ( QGraphicsItem *item : std::as_const( itemList ) )
251 {
252 QgsLayoutItem *layoutItem = dynamic_cast<QgsLayoutItem *>( item );
253 QgsLayoutItemPage *paperItem = dynamic_cast<QgsLayoutItemPage *>( item );
254 if ( paperItem )
255 focusedPaperItem = paperItem;
256
257 if ( layoutItem && !paperItem )
258 {
259 if ( !layoutItem->isLocked() )
260 {
261 if ( subtractingSelection )
262 {
263 layoutItem->setSelected( false );
264 }
265 else
266 {
267 layoutItem->setSelected( true );
268 }
269 if ( wasClick )
270 {
271 // found an item, and only a click - nothing more to do
272 break;
273 }
274 }
275 }
276 }
277
278
279 //update item panel
280 const QList<QgsLayoutItem *> selectedItemList = layout()->selectedLayoutItems();
281 if ( !selectedItemList.isEmpty() )
282 {
283 emit itemFocused( selectedItemList.at( 0 ) );
284 }
285 else if ( focusedPaperItem )
286 {
287 emit itemFocused( focusedPaperItem );
288 }
289 else
290 {
291 emit itemFocused( nullptr );
292 }
293 mMouseHandles->selectionChanged();
294}
295
296void QgsLayoutViewToolSelect::wheelEvent( QWheelEvent *event )
297{
298 if ( mMouseHandles->shouldBlockEvent( event ) )
299 {
300 //ignore wheel events while dragging/resizing items
301 return;
302 }
303 else
304 {
305 event->ignore();
306 }
307}
308
310{
311 if ( mMouseHandles->isDragging() || mMouseHandles->isResizing() )
312 {
313 return;
314 }
315 else
316 {
317 event->ignore();
318 }
319}
320
322{
323 if ( mIsSelecting )
324 {
325 mRubberBand->finish();
326 mIsSelecting = false;
327 }
329}
330
332QgsLayoutMouseHandles *QgsLayoutViewToolSelect::mouseHandles()
333{
334 return mMouseHandles;
335}
336
338{
339 // existing handles are owned by previous layout
340 if ( mMouseHandles )
341 mMouseHandles->deleteLater();
342
343 //add mouse selection handles to layout, and initially hide
344 mMouseHandles = new QgsLayoutMouseHandles( layout, view() );
345 mMouseHandles->hide();
346 mMouseHandles->setZValue( QgsLayout::ZMouseHandles );
347 layout->addItem( mMouseHandles );
348}
350{
351 const double pixelsPerMm = view()->physicalDpiX() / 25.4;
352 return sSearchToleranceInMillimeters * pixelsPerMm / view()->transform().m11();
353}
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.
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.
QList< QgsLayoutItem * > selectedLayoutItems(bool includeLockedItems=true)
Returns list of selected layout items.
void setSelectedItem(QgsLayoutItem *item)
Clears any selected items and sets item as the current selection.
@ 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:5862