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