QGIS API Documentation  3.24.2-Tisler (13c1a02865)
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"
21 #include "qgslayoutmousehandles.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 
273 void 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 
309 QgsLayoutMouseHandles *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:50
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:1517