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"
19 #include "qgsprocessingmodelalgorithm.h"
20 #include "qgsmodelgraphicsscene.h"
21 #include "qgsmodelcomponentgraphicitem.h"
22 #include "qgsmodelviewmousehandles.h"
23 #include "qgsmodelgraphicitem.h"
24
QgsModelViewToolSelect(QgsModelGraphicsView * view)25 QgsModelViewToolSelect::QgsModelViewToolSelect( QgsModelGraphicsView *view )
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
~QgsModelViewToolSelect()35 QgsModelViewToolSelect::~QgsModelViewToolSelect()
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
modelPressEvent(QgsModelViewMouseEvent * event)46 void QgsModelViewToolSelect::modelPressEvent( QgsModelViewMouseEvent *event )
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 QList<QgsModelComponentGraphicItem *> selectedItems = scene()->selectedComponentItems();
79
80 //select topmost item at position of event
81 selectedItem = scene()->componentItemAt( event->modelPoint() );
82
83 if ( !selectedItem )
84 {
85 //not clicking over an item, so start marquee selection
86 mIsSelecting = true;
87 mMousePressStartPos = event->pos();
88 mRubberBand->start( event->modelPoint(), Qt::KeyboardModifiers() );
89 return;
90 }
91
92 if ( ( event->modifiers() & Qt::ShiftModifier ) && ( selectedItem->isSelected() ) )
93 {
94 //SHIFT-clicking a selected item deselects it
95 selectedItem->setSelected( false );
96
97 //Check if we have any remaining selected items, and if so, update the item panel
98 const QList<QgsModelComponentGraphicItem *> selectedItems = scene()->selectedComponentItems();
99 if ( !selectedItems.isEmpty() )
100 {
101 emit itemFocused( selectedItems.at( 0 ) );
102 }
103 else
104 {
105 emit itemFocused( nullptr );
106 }
107 }
108 else
109 {
110 if ( ( !selectedItem->isSelected() ) && //keep selection if an already selected item pressed
111 !( event->modifiers() & Qt::ShiftModifier ) ) //keep selection if shift key pressed
112 {
113 scene()->setSelectedItem( selectedItem ); // clears existing selection
114 }
115 else
116 {
117 selectedItem->setSelected( true );
118 }
119 event->ignore();
120 emit itemFocused( selectedItem );
121
122 if ( !( event->modifiers() & Qt::ShiftModifier ) )
123 {
124 // we need to manually pass this event down to items we want it to go to -- QGraphicsScene doesn't propagate events
125 // to multiple items
126 QList< QGraphicsItem * > items = scene()->items( event->modelPoint() );
127 for ( QGraphicsItem *item : items )
128 {
129 if ( QgsModelDesignerFlatButtonGraphicItem *button = dynamic_cast< QgsModelDesignerFlatButtonGraphicItem * >( item ) )
130 {
131 // arghhh - if the event happens outside the mouse handles bounding rect, then it's ALREADY passed on!
132 if ( mMouseHandles->sceneBoundingRect().contains( event->modelPoint() ) )
133 {
134 button->modelPressEvent( event );
135 event->accept();
136 return;
137 }
138 }
139 }
140 }
141 }
142
143 event->ignore();
144 }
145
modelMoveEvent(QgsModelViewMouseEvent * event)146 void QgsModelViewToolSelect::modelMoveEvent( QgsModelViewMouseEvent *event )
147 {
148 if ( mIsSelecting )
149 {
150 mRubberBand->update( event->modelPoint(), Qt::KeyboardModifiers() );
151 }
152 else
153 {
154 // we need to manually pass this event down to items we want it to go to -- QGraphicsScene doesn't propagate events
155 // to multiple items
156 QList< QGraphicsItem * > items = scene()->items( event->modelPoint() );
157 for ( QGraphicsItem *item : items )
158 {
159 if ( mHoverEnteredItems.contains( item ) )
160 {
161 if ( QgsModelComponentGraphicItem *component = dynamic_cast< QgsModelComponentGraphicItem * >( item ) )
162 {
163 component->modelHoverMoveEvent( event );
164 }
165 }
166 else
167 {
168 mHoverEnteredItems.append( item );
169 if ( QgsModelComponentGraphicItem *component = dynamic_cast< QgsModelComponentGraphicItem * >( item ) )
170 {
171 component->modelHoverEnterEvent( event );
172 }
173 else if ( QgsModelDesignerFlatButtonGraphicItem *button = dynamic_cast<QgsModelDesignerFlatButtonGraphicItem *>( item ) )
174 {
175 // arghhh - if the event happens outside the mouse handles bounding rect, then it's ALREADY passed on!
176 if ( mMouseHandles->sceneBoundingRect().contains( event->modelPoint() ) )
177 button->modelHoverEnterEvent( event );
178 }
179 }
180 }
181 const QList< QGraphicsItem * > prevHovered = mHoverEnteredItems;
182 for ( QGraphicsItem *item : prevHovered )
183 {
184 if ( ! items.contains( item ) )
185 {
186 mHoverEnteredItems.removeAll( item );
187 if ( QgsModelComponentGraphicItem *component = dynamic_cast< QgsModelComponentGraphicItem * >( item ) )
188 {
189 component->modelHoverLeaveEvent( event );
190 }
191 else if ( QgsModelDesignerFlatButtonGraphicItem *button = dynamic_cast<QgsModelDesignerFlatButtonGraphicItem *>( item ) )
192 {
193 // arghhh - if the event happens outside the mouse handles bounding rect, then it's ALREADY passed on!
194 if ( mMouseHandles->sceneBoundingRect().contains( event->modelPoint() ) )
195 button->modelHoverLeaveEvent( event );
196 }
197 }
198 }
199
200 event->ignore();
201 }
202 }
203
modelDoubleClickEvent(QgsModelViewMouseEvent * event)204 void QgsModelViewToolSelect::modelDoubleClickEvent( QgsModelViewMouseEvent *event )
205 {
206 if ( !mIsSelecting )
207 {
208 // we need to manually pass this event down to items we want it to go to -- QGraphicsScene doesn't propagate events
209 // to multiple items
210 QList< QGraphicsItem * > items = scene()->items( event->modelPoint() );
211 for ( QGraphicsItem *item : items )
212 {
213 if ( QgsModelComponentGraphicItem *component = dynamic_cast< QgsModelComponentGraphicItem * >( item ) )
214 {
215 scene()->setSelectedItem( component ); // clears existing selection
216 component->modelDoubleClickEvent( event );
217 break;
218 }
219 }
220 }
221 }
222
modelReleaseEvent(QgsModelViewMouseEvent * event)223 void QgsModelViewToolSelect::modelReleaseEvent( QgsModelViewMouseEvent *event )
224 {
225 if ( event->button() != Qt::LeftButton && mMouseHandles->shouldBlockEvent( event ) )
226 {
227 //swallow clicks while dragging/resizing items
228 return;
229 }
230
231 if ( !mIsSelecting || event->button() != Qt::LeftButton )
232 {
233 event->ignore();
234 return;
235 }
236
237 mIsSelecting = false;
238 bool wasClick = !isClickAndDrag( mMousePressStartPos, event->pos() );
239
240 // important -- we don't pass the event modifiers here, because we use them for a different meaning!
241 // (modifying how the selection interacts with the items, rather than modifying the selection shape)
242 QRectF rect = mRubberBand->finish( event->modelPoint() );
243
244 bool subtractingSelection = false;
245 if ( event->modifiers() & Qt::ShiftModifier )
246 {
247 //shift modifier means adding to selection, nothing required here
248 }
249 else if ( event->modifiers() & Qt::ControlModifier )
250 {
251 //control modifier means subtract from current selection
252 subtractingSelection = true;
253 }
254 else
255 {
256 //not adding to or removing from selection, so clear current selection
257 whileBlocking( scene() )->deselectAll();
258 }
259
260 //determine item selection mode, default to intersection
261 Qt::ItemSelectionMode selectionMode = Qt::IntersectsItemShape;
262 if ( event->modifiers() & Qt::AltModifier )
263 {
264 //alt modifier switches to contains selection mode
265 selectionMode = Qt::ContainsItemShape;
266 }
267
268 //find all items in rect
269 QList<QGraphicsItem *> itemList;
270 if ( wasClick )
271 itemList = scene()->items( rect.center(), selectionMode );
272 else
273 itemList = scene()->items( rect, selectionMode );
274 for ( QGraphicsItem *item : qgis::as_const( itemList ) )
275 {
276 if ( QgsModelComponentGraphicItem *componentItem = dynamic_cast<QgsModelComponentGraphicItem *>( item ) )
277 {
278 if ( subtractingSelection )
279 {
280 componentItem->setSelected( false );
281 }
282 else
283 {
284 componentItem->setSelected( true );
285 }
286 if ( wasClick )
287 {
288 // found an item, and only a click - nothing more to do
289 break;
290 }
291 }
292 }
293
294 //update item panel
295 const QList<QgsModelComponentGraphicItem *> selectedItemList = scene()->selectedComponentItems();
296 if ( !selectedItemList.isEmpty() )
297 {
298 emit itemFocused( selectedItemList.at( 0 ) );
299 }
300 else
301 {
302 emit itemFocused( nullptr );
303 }
304 mMouseHandles->selectionChanged();
305 }
306
wheelEvent(QWheelEvent * event)307 void QgsModelViewToolSelect::wheelEvent( QWheelEvent *event )
308 {
309 if ( mMouseHandles->shouldBlockEvent( event ) )
310 {
311 //ignore wheel events while dragging/resizing items
312 return;
313 }
314 else
315 {
316 event->ignore();
317 }
318 }
319
keyPressEvent(QKeyEvent * event)320 void QgsModelViewToolSelect::keyPressEvent( QKeyEvent *event )
321 {
322 if ( mMouseHandles->isDragging() || mMouseHandles->isResizing() )
323 {
324 return;
325 }
326 else
327 {
328 event->ignore();
329 }
330 }
331
deactivate()332 void QgsModelViewToolSelect::deactivate()
333 {
334 if ( mIsSelecting )
335 {
336 mRubberBand->finish();
337 mIsSelecting = false;
338 }
339 QgsModelViewTool::deactivate();
340 }
341
allowItemInteraction()342 bool QgsModelViewToolSelect::allowItemInteraction()
343 {
344 return !mIsSelecting;
345 }
346
mouseHandles()347 QgsModelViewMouseHandles *QgsModelViewToolSelect::mouseHandles()
348 {
349 return mMouseHandles;
350 }
351
setScene(QgsModelGraphicsScene * scene)352 void QgsModelViewToolSelect::setScene( QgsModelGraphicsScene *scene )
353 {
354 // existing handles are owned by previous layout
355 if ( mMouseHandles )
356 mMouseHandles->deleteLater();
357
358 //add mouse selection handles to scene, and initially hide
359 mMouseHandles = new QgsModelViewMouseHandles( view() );
360 mMouseHandles->hide();
361 mMouseHandles->setZValue( QgsModelGraphicsScene::MouseHandles );
362 scene->addItem( mMouseHandles );
363 }
364
resetCache()365 void QgsModelViewToolSelect::resetCache()
366 {
367 mHoverEnteredItems.clear();
368 }
369 ///@endcond
370