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