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