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