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