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