QGIS API Documentation  3.27.0-Master (bef583a8ef)
qgsmodelviewtoolselect.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsmodelviewtoolselect.cpp
3  ---------------------------
4  Date : March 2020
5  Copyright : (C) 2020 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 
16 #include "qgsmodelviewtoolselect.h"
17 #include "qgsmodelviewmouseevent.h"
18 #include "qgsmodelgraphicsview.h"
20 #include "qgsmodelgraphicsscene.h"
23 #include "qgsmodelgraphicitem.h"
24 
26  : QgsModelViewTool( view, tr( "Select" ) )
27 {
28  setCursor( Qt::ArrowCursor );
29 
30  mRubberBand.reset( new QgsModelViewRectangularRubberBand( 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  delete mMouseHandles;
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  QgsGraphicsViewMouseHandles::MouseAction mouseAction = mMouseHandles->mouseActionForScenePos( event->modelPoint() );
58 
59  if ( mouseAction != QgsGraphicsViewMouseHandles::MoveItem
60  && mouseAction != QgsGraphicsViewMouseHandles::NoAction
61  && mouseAction != QgsGraphicsViewMouseHandles::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 
76  QgsModelComponentGraphicItem *selectedItem = nullptr;
77 
78  //select topmost item at position of event
79  selectedItem = scene()->componentItemAt( event->modelPoint() );
80 
81  if ( !selectedItem )
82  {
83  //not clicking over an item, so start marquee selection
84  mIsSelecting = true;
85  mMousePressStartPos = event->pos();
86  mRubberBand->start( event->modelPoint(), Qt::KeyboardModifiers() );
87  return;
88  }
89 
90  if ( ( event->modifiers() & Qt::ShiftModifier ) && ( selectedItem->isSelected() ) )
91  {
92  //SHIFT-clicking a selected item deselects it
93  selectedItem->setSelected( false );
94 
95  //Check if we have any remaining selected items, and if so, update the item panel
96  const QList<QgsModelComponentGraphicItem *> selectedItems = scene()->selectedComponentItems();
97  if ( !selectedItems.isEmpty() )
98  {
99  emit itemFocused( selectedItems.at( 0 ) );
100  }
101  else
102  {
103  emit itemFocused( nullptr );
104  }
105  }
106  else
107  {
108  if ( ( !selectedItem->isSelected() ) && //keep selection if an already selected item pressed
109  !( event->modifiers() & Qt::ShiftModifier ) ) //keep selection if shift key pressed
110  {
111  scene()->setSelectedItem( selectedItem ); // clears existing selection
112  }
113  else
114  {
115  selectedItem->setSelected( true );
116  }
117  event->ignore();
118  emit itemFocused( selectedItem );
119 
120  if ( !( event->modifiers() & Qt::ShiftModifier ) )
121  {
122  // we need to manually pass this event down to items we want it to go to -- QGraphicsScene doesn't propagate events
123  // to multiple items
124  QList< QGraphicsItem * > items = scene()->items( event->modelPoint() );
125  for ( QGraphicsItem *item : items )
126  {
127  if ( QgsModelDesignerFlatButtonGraphicItem *button = dynamic_cast< QgsModelDesignerFlatButtonGraphicItem * >( item ) )
128  {
129  // arghhh - if the event happens outside the mouse handles bounding rect, then it's ALREADY passed on!
130  if ( mMouseHandles->sceneBoundingRect().contains( event->modelPoint() ) )
131  {
132  button->modelPressEvent( event );
133  event->accept();
134  return;
135  }
136  }
137  }
138  }
139  }
140 
141  event->ignore();
142 }
143 
145 {
146  if ( mIsSelecting )
147  {
148  mRubberBand->update( event->modelPoint(), Qt::KeyboardModifiers() );
149  }
150  else
151  {
152  // we need to manually pass this event down to items we want it to go to -- QGraphicsScene doesn't propagate events
153  // to multiple items
154  QList< QGraphicsItem * > items = scene()->items( event->modelPoint() );
155  for ( QGraphicsItem *item : items )
156  {
157  if ( mHoverEnteredItems.contains( item ) )
158  {
159  if ( QgsModelComponentGraphicItem *component = dynamic_cast< QgsModelComponentGraphicItem * >( item ) )
160  {
161  component->modelHoverMoveEvent( event );
162  }
163  }
164  else
165  {
166  mHoverEnteredItems.append( item );
167  if ( QgsModelComponentGraphicItem *component = dynamic_cast< QgsModelComponentGraphicItem * >( item ) )
168  {
169  component->modelHoverEnterEvent( event );
170  }
171  else if ( QgsModelDesignerFlatButtonGraphicItem *button = dynamic_cast<QgsModelDesignerFlatButtonGraphicItem *>( item ) )
172  {
173  // arghhh - if the event happens outside the mouse handles bounding rect, then it's ALREADY passed on!
174  if ( mMouseHandles->sceneBoundingRect().contains( event->modelPoint() ) )
175  button->modelHoverEnterEvent( event );
176  }
177  }
178  }
179  const QList< QGraphicsItem * > prevHovered = mHoverEnteredItems;
180  for ( QGraphicsItem *item : prevHovered )
181  {
182  if ( ! items.contains( item ) )
183  {
184  mHoverEnteredItems.removeAll( item );
185  if ( QgsModelComponentGraphicItem *component = dynamic_cast< QgsModelComponentGraphicItem * >( item ) )
186  {
187  component->modelHoverLeaveEvent( event );
188  }
189  else if ( QgsModelDesignerFlatButtonGraphicItem *button = dynamic_cast<QgsModelDesignerFlatButtonGraphicItem *>( item ) )
190  {
191  // arghhh - if the event happens outside the mouse handles bounding rect, then it's ALREADY passed on!
192  if ( mMouseHandles->sceneBoundingRect().contains( event->modelPoint() ) )
193  button->modelHoverLeaveEvent( event );
194  }
195  }
196  }
197 
198  event->ignore();
199  }
200 }
201 
203 {
204  if ( !mIsSelecting )
205  {
206  // we need to manually pass this event down to items we want it to go to -- QGraphicsScene doesn't propagate events
207  // to multiple items
208  QList< QGraphicsItem * > items = scene()->items( event->modelPoint() );
209  for ( QGraphicsItem *item : items )
210  {
211  if ( QgsModelComponentGraphicItem *component = dynamic_cast< QgsModelComponentGraphicItem * >( item ) )
212  {
213  scene()->setSelectedItem( component ); // clears existing selection
214  component->modelDoubleClickEvent( event );
215  break;
216  }
217  }
218  }
219 }
220 
222 {
223  if ( event->button() != Qt::LeftButton && mMouseHandles->shouldBlockEvent( event ) )
224  {
225  //swallow clicks while dragging/resizing items
226  return;
227  }
228 
229  if ( !mIsSelecting || event->button() != Qt::LeftButton )
230  {
231  event->ignore();
232  return;
233  }
234 
235  mIsSelecting = false;
236  bool wasClick = !isClickAndDrag( mMousePressStartPos, event->pos() );
237 
238  // important -- we don't pass the event modifiers here, because we use them for a different meaning!
239  // (modifying how the selection interacts with the items, rather than modifying the selection shape)
240  QRectF rect = mRubberBand->finish( event->modelPoint() );
241 
242  bool subtractingSelection = false;
243  if ( event->modifiers() & Qt::ShiftModifier )
244  {
245  //shift modifier means adding to selection, nothing required here
246  }
247  else if ( event->modifiers() & Qt::ControlModifier )
248  {
249  //control modifier means subtract from current selection
250  subtractingSelection = true;
251  }
252  else
253  {
254  //not adding to or removing from selection, so clear current selection
255  whileBlocking( scene() )->deselectAll();
256  }
257 
258  //determine item selection mode, default to intersection
259  Qt::ItemSelectionMode selectionMode = Qt::IntersectsItemShape;
260  if ( event->modifiers() & Qt::AltModifier )
261  {
262  //alt modifier switches to contains selection mode
263  selectionMode = Qt::ContainsItemShape;
264  }
265 
266  //find all items in rect
267  QList<QGraphicsItem *> itemList;
268  if ( wasClick )
269  itemList = scene()->items( rect.center(), selectionMode );
270  else
271  itemList = scene()->items( rect, selectionMode );
272  for ( QGraphicsItem *item : std::as_const( itemList ) )
273  {
274  if ( QgsModelComponentGraphicItem *componentItem = dynamic_cast<QgsModelComponentGraphicItem *>( item ) )
275  {
276  if ( subtractingSelection )
277  {
278  componentItem->setSelected( false );
279  }
280  else
281  {
282  componentItem->setSelected( true );
283  }
284  if ( wasClick )
285  {
286  // found an item, and only a click - nothing more to do
287  break;
288  }
289  }
290  }
291 
292  //update item panel
293  const QList<QgsModelComponentGraphicItem *> selectedItemList = scene()->selectedComponentItems();
294  if ( !selectedItemList.isEmpty() )
295  {
296  emit itemFocused( selectedItemList.at( 0 ) );
297  }
298  else
299  {
300  emit itemFocused( nullptr );
301  }
302  mMouseHandles->selectionChanged();
303 }
304 
305 void QgsModelViewToolSelect::wheelEvent( QWheelEvent *event )
306 {
307  if ( mMouseHandles->shouldBlockEvent( event ) )
308  {
309  //ignore wheel events while dragging/resizing items
310  return;
311  }
312  else
313  {
314  event->ignore();
315  }
316 }
317 
319 {
320  if ( mMouseHandles->isDragging() || mMouseHandles->isResizing() )
321  {
322  return;
323  }
324  else
325  {
326  event->ignore();
327  }
328 }
329 
331 {
332  if ( mIsSelecting )
333  {
334  mRubberBand->finish();
335  mIsSelecting = false;
336  }
338 }
339 
341 {
342  return !mIsSelecting;
343 }
344 
345 QgsModelViewMouseHandles *QgsModelViewToolSelect::mouseHandles()
346 {
347  return mMouseHandles;
348 }
349 
350 void QgsModelViewToolSelect::setScene( QgsModelGraphicsScene *scene )
351 {
352  // existing handles are owned by previous layout
353  if ( mMouseHandles )
354  mMouseHandles->deleteLater();
355 
356  //add mouse selection handles to scene, and initially hide
357  mMouseHandles = new QgsModelViewMouseHandles( view() );
358  mMouseHandles->hide();
359  mMouseHandles->setZValue( QgsModelGraphicsScene::MouseHandles );
360  scene->addItem( mMouseHandles );
361 }
362 
364 {
365  mHoverEnteredItems.clear();
366 }
A QgsModelViewMouseEvent is the result of a user interaction with the mouse on a QgsModelGraphicsView...
QPointF modelPoint() const
Returns the event point location in model coordinates.
QgsModelViewRectangularRubberBand is rectangular rubber band for use within QgsModelGraphicsView widg...
void modelReleaseEvent(QgsModelViewMouseEvent *event) override
Mouse release event for overriding.
void setScene(QgsModelGraphicsScene *scene)
Sets the a scene.
void modelPressEvent(QgsModelViewMouseEvent *event) override
Mouse press event for overriding.
void wheelEvent(QWheelEvent *event) override
Mouse wheel event for overriding.
void deactivate() override
Called when tool is deactivated.
void modelDoubleClickEvent(QgsModelViewMouseEvent *event) override
Mouse double-click event for overriding.
void modelMoveEvent(QgsModelViewMouseEvent *event) override
Mouse move event for overriding.
void resetCache()
Resets the internal cache following a scene change.
QgsModelViewMouseHandles * mouseHandles()
Returns the view's mouse handles.
void keyPressEvent(QKeyEvent *event) override
Key press event for overriding.
bool allowItemInteraction() override
Returns true if the tool allows interaction with component graphic items.
QgsModelViewToolSelect(QgsModelGraphicsView *view)
Constructor for QgsModelViewToolSelect.
Abstract base class for all model designer view tools.
QgsModelGraphicsView * view() const
Returns the view associated with the tool.
void itemFocused(QgsModelComponentGraphicItem *item)
Emitted when an item is "focused" by the tool, i.e.
bool isClickAndDrag(QPoint startViewPoint, QPoint endViewPoint) const
Returns true if a mouse press/release operation which started at startViewPoint and ended at endViewP...
void setCursor(const QCursor &cursor)
Sets a user defined cursor for use when the tool is active.
virtual void deactivate()
Called when tool is deactivated.
QgsModelGraphicsScene * scene() const
Returns the scene associated with the tool.
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition: qgis.h:2186