QGIS API Documentation  3.10.0-A Coruña (6c816b4204)
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(), nullptr );
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(), nullptr );
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 }
void setCursor(const QCursor &cursor)
Sets a user defined cursor for use when the tool is active.
void setSelectedItem(QgsLayoutItem *item)
Clears any selected items and sets item as the current selection.
Definition: qgslayout.cpp:158
Base class for graphical items within a QgsLayout.
void layoutMoveEvent(QgsLayoutViewMouseEvent *event) override
Mouse move event for overriding.
A graphical widget to display and interact with QgsLayouts.
Definition: qgslayoutview.h:49
virtual void deactivate()
Called when tool is deactivated.
void layoutPressEvent(QgsLayoutViewMouseEvent *event) override
Mouse press event for overriding.
A container for grouping several QgsLayoutItems.
virtual void setSelected(bool selected)
Sets whether the item should be selected.
Z-value for mouse handles.
Definition: qgslayout.h:63
void itemFocused(QgsLayoutItem *item)
Emitted when an item is "focused" by the tool, i.e.
void keyPressEvent(QKeyEvent *event) override
Key press event for overriding.
QgsLayoutView * view() const
Returns the view associated with the tool.
QgsLayout * layout() const
Returns the layout associated with the tool.
bool isClickAndDrag(QPoint startViewPoint, QPoint endViewPoint) const
Returns true if a mouse press/release operation which started at startViewPoint and ended at endViewP...
QList< QgsLayoutItem * > selectedLayoutItems(bool includeLockedItems=true)
Returns list of selected layout items.
Definition: qgslayout.cpp:141
void deactivate() override
Called when tool is deactivated.
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition: qgslayout.h:49
void setLayout(QgsLayout *layout)
Sets the a layout.
A QgsLayoutViewMouseEvent is the result of a user interaction with the mouse on a QgsLayoutView...
void layoutReleaseEvent(QgsLayoutViewMouseEvent *event) override
Mouse release event for overriding.
void wheelEvent(QWheelEvent *event) override
Mouse wheel event for overriding.
QPointF layoutPoint() const
Returns the event point location in layout coordinates.
QgsLayoutViewRectangularRubberBand is rectangular rubber band for use within QgsLayoutView widgets...
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition: qgis.h:227
QgsLayoutViewToolSelect(QgsLayoutView *view)
Constructor for QgsLayoutViewToolSelect.
bool isLocked() const
Returns true if the item is locked, and cannot be interacted with using the mouse.
Abstract base class for all layout view tools.
QgsLayoutItem * layoutItemAt(QPointF position, bool ignoreLocked=false) const
Returns the topmost layout item at a specified position.
Definition: qgslayout.cpp:292
QgsLayoutItemGroup * parentGroup() const
Returns the item&#39;s parent group, if the item is part of a QgsLayoutItemGroup group.
Item representing the paper in a layout.