QGIS API Documentation  3.4.15-Madeira (e83d02e274)
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:156
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.
QgsLayout * layout() const
Returns the layout associated with the tool.
QgsLayoutView * view() const
Returns the view associated with the tool.
QList< QgsLayoutItem * > selectedLayoutItems(bool includeLockedItems=true)
Returns list of selected layout items.
Definition: qgslayout.cpp:139
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.
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:225
bool isClickAndDrag(QPoint startViewPoint, QPoint endViewPoint) const
Returns true if a mouse press/release operation which started at startViewPoint and ended at endViewP...
QgsLayoutViewToolSelect(QgsLayoutView *view)
Constructor for QgsLayoutViewToolSelect.
QPointF layoutPoint() const
Returns the event point location in layout coordinates.
Abstract base class for all layout view tools.
bool isLocked() const
Returns true if the item is locked, and cannot be interacted with using the mouse.
QgsLayoutItem * layoutItemAt(QPointF position, bool ignoreLocked=false) const
Returns the topmost layout item at a specified position.
Definition: qgslayout.cpp:290
Item representing the paper in a layout.