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