QGIS API Documentation  3.26.3-Buenos Aires (65e4edfdad)
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 #include "qgslayoutitemgroup.h"
23 
24 
26  : QgsLayoutViewTool( view, tr( "Select" ) )
27 {
28  setCursor( Qt::ArrowCursor );
29 
30  mRubberBand.reset( new QgsLayoutViewRectangularRubberBand( 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  mMouseHandles->deleteLater();
43  }
44 }
45 
47 {
48  if ( mMouseHandles->shouldBlockEvent( event ) )
49  {
50  //swallow clicks while dragging/resizing items
51  return;
52  }
53  if ( mMouseHandles->isVisible() )
54  {
55  //selection handles are being shown, get mouse action for current cursor position
56  QgsLayoutMouseHandles::MouseAction mouseAction = mMouseHandles->mouseActionForScenePos( event->layoutPoint() );
57 
58  if ( mouseAction != QgsLayoutMouseHandles::MoveItem
59  && mouseAction != QgsLayoutMouseHandles::NoAction
60  && mouseAction != QgsLayoutMouseHandles::SelectItem )
61  {
62  //mouse is over a resize handle, so propagate event onward
63  event->ignore();
64  return;
65  }
66  }
67 
68  if ( event->button() != Qt::LeftButton )
69  {
70  if ( mIsSelecting )
71  {
72  mIsSelecting = false;
73  mRubberBand->finish();
74  }
75  event->ignore();
76  return;
77  }
78 
79  QgsLayoutItem *selectedItem = nullptr;
80  QgsLayoutItem *previousSelectedItem = nullptr;
81 
82  QList<QgsLayoutItem *> selectedItems = layout()->selectedLayoutItems();
83 
84  if ( event->modifiers() & Qt::ControlModifier )
85  {
86  //CTRL modifier, so we are trying to select the next item below the current one
87  //first, find currently selected item
88  if ( !selectedItems.isEmpty() )
89  {
90  previousSelectedItem = selectedItems.at( 0 );
91  }
92  }
93 
94  if ( previousSelectedItem )
95  {
96  //select highest item just below previously selected item at position of event
97  selectedItem = layout()->layoutItemAt( event->layoutPoint(), previousSelectedItem, true );
98 
99  //if we didn't find a lower item we'll use the top-most as fall-back
100  //this duplicates mapinfo/illustrator/etc behavior where ctrl-clicks are "cyclic"
101  if ( !selectedItem )
102  {
103  selectedItem = layout()->layoutItemAt( event->layoutPoint(), true );
104  }
105  }
106  else
107  {
108  //select topmost item at position of event
109  selectedItem = layout()->layoutItemAt( event->layoutPoint(), true );
110  }
111 
112  // if selected item is in a group, we actually get the top-level group it's part of
113  QgsLayoutItemGroup *group = selectedItem ? selectedItem->parentGroup() : nullptr;
114  while ( group && group->parentGroup() )
115  {
116  group = group->parentGroup();
117  }
118  if ( group )
119  selectedItem = group;
120 
121  if ( !selectedItem )
122  {
123  //not clicking over an item, so start marquee selection
124  mIsSelecting = true;
125  mMousePressStartPos = event->pos();
126  mRubberBand->start( event->layoutPoint(), Qt::KeyboardModifiers() );
127  return;
128  }
129 
130  if ( ( event->modifiers() & Qt::ShiftModifier ) && ( selectedItem->isSelected() ) )
131  {
132  //SHIFT-clicking a selected item deselects it
133  selectedItem->setSelected( false );
134 
135  //Check if we have any remaining selected items, and if so, update the item panel
136  const QList<QgsLayoutItem *> selectedItems = layout()->selectedLayoutItems();
137  if ( !selectedItems.isEmpty() )
138  {
139  emit itemFocused( selectedItems.at( 0 ) );
140  }
141  else
142  {
143  emit itemFocused( nullptr );
144  }
145  }
146  else
147  {
148  if ( ( !selectedItem->isSelected() ) && //keep selection if an already selected item pressed
149  !( event->modifiers() & Qt::ShiftModifier ) ) //keep selection if shift key pressed
150  {
151  layout()->setSelectedItem( selectedItem ); // clears existing selection
152  }
153  else
154  {
155  selectedItem->setSelected( true );
156  }
157  event->ignore();
158  emit itemFocused( selectedItem );
159  }
160 }
161 
163 {
164  if ( mIsSelecting )
165  {
166  mRubberBand->update( event->layoutPoint(), Qt::KeyboardModifiers() );
167  }
168  else
169  {
170  event->ignore();
171  }
172 }
173 
175 {
176  if ( event->button() != Qt::LeftButton && mMouseHandles->shouldBlockEvent( event ) )
177  {
178  //swallow clicks while dragging/resizing items
179  return;
180  }
181 
182  if ( !mIsSelecting || event->button() != Qt::LeftButton )
183  {
184  event->ignore();
185  return;
186  }
187 
188  mIsSelecting = false;
189  bool wasClick = !isClickAndDrag( mMousePressStartPos, event->pos() );
190 
191  // important -- we don't pass the event modifiers here, because we use them for a different meaning!
192  // (modifying how the selection interacts with the items, rather than modifying the selection shape)
193  QRectF rect = mRubberBand->finish( event->layoutPoint() );
194 
195  bool subtractingSelection = false;
196  if ( event->modifiers() & Qt::ShiftModifier )
197  {
198  //shift modifier means adding to selection, nothing required here
199  }
200  else if ( event->modifiers() & Qt::ControlModifier )
201  {
202  //control modifier means subtract from current selection
203  subtractingSelection = true;
204  }
205  else
206  {
207  //not adding to or removing from selection, so clear current selection
208  whileBlocking( layout() )->deselectAll();
209  }
210 
211  //determine item selection mode, default to intersection
212  Qt::ItemSelectionMode selectionMode = Qt::IntersectsItemShape;
213  if ( event->modifiers() & Qt::AltModifier )
214  {
215  //alt modifier switches to contains selection mode
216  selectionMode = Qt::ContainsItemShape;
217  }
218 
219  //find all items in rect
220  QList<QGraphicsItem *> itemList;
221  if ( wasClick )
222  itemList = layout()->items( rect.center(), selectionMode );
223  else
224  itemList = layout()->items( rect, selectionMode );
225 
226  QgsLayoutItemPage *focusedPaperItem = nullptr;
227  for ( QGraphicsItem *item : std::as_const( itemList ) )
228  {
229  QgsLayoutItem *layoutItem = dynamic_cast<QgsLayoutItem *>( item );
230  QgsLayoutItemPage *paperItem = dynamic_cast<QgsLayoutItemPage *>( item );
231  if ( paperItem )
232  focusedPaperItem = paperItem;
233 
234  if ( layoutItem && !paperItem )
235  {
236  if ( !layoutItem->isLocked() )
237  {
238  if ( subtractingSelection )
239  {
240  layoutItem->setSelected( false );
241  }
242  else
243  {
244  layoutItem->setSelected( true );
245  }
246  if ( wasClick )
247  {
248  // found an item, and only a click - nothing more to do
249  break;
250  }
251  }
252  }
253  }
254 
255 
256  //update item panel
257  const QList<QgsLayoutItem *> selectedItemList = layout()->selectedLayoutItems();
258  if ( !selectedItemList.isEmpty() )
259  {
260  emit itemFocused( selectedItemList.at( 0 ) );
261  }
262  else if ( focusedPaperItem )
263  {
264  emit itemFocused( focusedPaperItem );
265  }
266  else
267  {
268  emit itemFocused( nullptr );
269  }
270  mMouseHandles->selectionChanged();
271 }
272 
273 void QgsLayoutViewToolSelect::wheelEvent( QWheelEvent *event )
274 {
275  if ( mMouseHandles->shouldBlockEvent( event ) )
276  {
277  //ignore wheel events while dragging/resizing items
278  return;
279  }
280  else
281  {
282  event->ignore();
283  }
284 }
285 
287 {
288  if ( mMouseHandles->isDragging() || mMouseHandles->isResizing() )
289  {
290  return;
291  }
292  else
293  {
294  event->ignore();
295  }
296 }
297 
299 {
300  if ( mIsSelecting )
301  {
302  mRubberBand->finish();
303  mIsSelecting = false;
304  }
306 }
307 
309 QgsLayoutMouseHandles *QgsLayoutViewToolSelect::mouseHandles()
310 {
311  return mMouseHandles;
312 }
313 
315 {
316  // existing handles are owned by previous layout
317  if ( mMouseHandles )
318  mMouseHandles->deleteLater();
319 
320  //add mouse selection handles to layout, and initially hide
321  mMouseHandles = new QgsLayoutMouseHandles( layout, view() );
322  mMouseHandles->hide();
323  mMouseHandles->setZValue( QgsLayout::ZMouseHandles );
324  layout->addItem( mMouseHandles );
325 }
QgsLayoutItem::parentGroup
QgsLayoutItemGroup * parentGroup() const
Returns the item's parent group, if the item is part of a QgsLayoutItemGroup group.
Definition: qgslayoutitem.cpp:230
qgslayoutitemgroup.h
QgsLayoutItemPage
Item representing the paper in a layout.
Definition: qgslayoutitempage.h:58
QgsLayout::setSelectedItem
void setSelectedItem(QgsLayoutItem *item)
Clears any selected items and sets item as the current selection.
Definition: qgslayout.cpp:159
QgsLayout::selectedLayoutItems
QList< QgsLayoutItem * > selectedLayoutItems(bool includeLockedItems=true)
Returns list of selected layout items.
Definition: qgslayout.cpp:142
qgslayoutview.h
QgsLayoutViewRectangularRubberBand
QgsLayoutViewRectangularRubberBand is rectangular rubber band for use within QgsLayoutView widgets.
Definition: qgslayoutviewrubberband.h:159
QgsLayoutViewToolSelect::layoutMoveEvent
void layoutMoveEvent(QgsLayoutViewMouseEvent *event) override
Mouse move event for overriding.
Definition: qgslayoutviewtoolselect.cpp:162
qgslayoutviewmouseevent.h
QgsLayoutViewTool::deactivate
virtual void deactivate()
Called when tool is deactivated.
Definition: qgslayoutviewtool.cpp:130
QgsLayoutViewToolSelect::layoutPressEvent
void layoutPressEvent(QgsLayoutViewMouseEvent *event) override
Mouse press event for overriding.
Definition: qgslayoutviewtoolselect.cpp:46
QgsLayoutItem::setSelected
virtual void setSelected(bool selected)
Sets whether the item should be selected.
Definition: qgslayoutitem.cpp:160
QgsLayout::layoutItemAt
QgsLayoutItem * layoutItemAt(QPointF position, bool ignoreLocked=false) const
Returns the topmost layout item at a specified position.
Definition: qgslayout.cpp:293
QgsLayoutViewToolSelect::setLayout
void setLayout(QgsLayout *layout)
Sets the a layout.
qgslayoutviewtoolselect.h
whileBlocking
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition: qgis.h:2191
QgsLayoutViewToolSelect::keyPressEvent
void keyPressEvent(QKeyEvent *event) override
Key press event for overriding.
Definition: qgslayoutviewtoolselect.cpp:286
QgsLayoutViewTool::view
QgsLayoutView * view() const
Returns the view associated with the tool.
Definition: qgslayoutviewtool.cpp:38
QgsLayoutViewTool::itemFocused
void itemFocused(QgsLayoutItem *item)
Emitted when an item is "focused" by the tool, i.e.
QgsLayoutItem
Base class for graphical items within a QgsLayout.
Definition: qgslayoutitem.h:112
QgsLayoutViewTool::layout
QgsLayout * layout() const
Returns the layout associated with the tool.
Definition: qgslayoutviewtool.cpp:43
QgsLayoutViewToolSelect::~QgsLayoutViewToolSelect
~QgsLayoutViewToolSelect() override
Definition: qgslayoutviewtoolselect.cpp:35
qgslayout.h
QgsLayoutItemGroup
A container for grouping several QgsLayoutItems.
Definition: qgslayoutitemgroup.h:28
QgsLayout::ZMouseHandles
@ ZMouseHandles
Z-value for mouse handles.
Definition: qgslayout.h:64
qgslayoutitempage.h
QgsLayoutViewTool::isClickAndDrag
bool isClickAndDrag(QPoint startViewPoint, QPoint endViewPoint) const
Returns true if a mouse press/release operation which started at startViewPoint and ended at endViewP...
Definition: qgslayoutviewtool.cpp:31
QgsLayout
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition: qgslayout.h:50
QgsLayoutViewTool
Abstract base class for all layout view tools. Layout view tools are user interactive tools for manip...
Definition: qgslayoutviewtool.h:46
QgsLayoutView
A graphical widget to display and interact with QgsLayouts.
Definition: qgslayoutview.h:49
QgsLayoutViewMouseEvent::layoutPoint
QPointF layoutPoint() const
Returns the event point location in layout coordinates.
Definition: qgslayoutviewmouseevent.cpp:44
QgsLayoutViewToolSelect::deactivate
void deactivate() override
Called when tool is deactivated.
Definition: qgslayoutviewtoolselect.cpp:298
qgslayoutmousehandles.h
QgsLayoutViewToolSelect::layoutReleaseEvent
void layoutReleaseEvent(QgsLayoutViewMouseEvent *event) override
Mouse release event for overriding.
Definition: qgslayoutviewtoolselect.cpp:174
QgsLayoutViewTool::setCursor
void setCursor(const QCursor &cursor)
Sets a user defined cursor for use when the tool is active.
Definition: qgslayoutviewtool.cpp:115
QgsLayoutViewToolSelect::wheelEvent
void wheelEvent(QWheelEvent *event) override
Mouse wheel event for overriding.
Definition: qgslayoutviewtoolselect.cpp:273
QgsLayoutViewMouseEvent
A QgsLayoutViewMouseEvent is the result of a user interaction with the mouse on a QgsLayoutView.
Definition: qgslayoutviewmouseevent.h:35
QgsLayoutViewToolSelect::QgsLayoutViewToolSelect
QgsLayoutViewToolSelect(QgsLayoutView *view)
Constructor for QgsLayoutViewToolSelect.
Definition: qgslayoutviewtoolselect.cpp:25
QgsLayoutItem::isLocked
bool isLocked() const
Returns true if the item is locked, and cannot be interacted with using the mouse.
Definition: qgslayoutitem.h:402