QGIS API Documentation  3.21.0-Master (5b68dc587e)
qgslayoutmousehandles.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgslayoutmousehandles.cpp
3  ------------------------
4  begin : September 2017
5  copyright : (C) 2017 by Nyall Dawson
6  email : [email protected]
7  ***************************************************************************/
8 
9 /***************************************************************************
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  ***************************************************************************/
17 
18 #include "qgslayoutmousehandles.h"
19 #include "qgis.h"
20 #include "qgslogger.h"
21 #include "qgsproject.h"
22 #include "qgslayout.h"
23 #include "qgslayoutitem.h"
24 #include "qgslayoututils.h"
25 #include "qgslayoutview.h"
27 #include "qgslayoutsnapper.h"
28 #include "qgslayoutitemgroup.h"
29 #include "qgslayoutundostack.h"
30 #include <QGraphicsView>
31 #include <QGraphicsSceneHoverEvent>
32 #include <QPainter>
33 #include <QWidget>
34 #include <limits>
35 
37 
38 QgsLayoutMouseHandles::QgsLayoutMouseHandles( QgsLayout *layout, QgsLayoutView *view )
39  : QgsGraphicsViewMouseHandles( view )
40  , mLayout( layout )
41  , mView( view )
42 {
43  //listen for selection changes, and update handles accordingly
44  connect( mLayout, &QGraphicsScene::selectionChanged, this, &QgsLayoutMouseHandles::selectionChanged );
45 
46  mHorizontalSnapLine = mView->createSnapLine();
47  mHorizontalSnapLine->hide();
48  layout->addItem( mHorizontalSnapLine );
49  mVerticalSnapLine = mView->createSnapLine();
50  mVerticalSnapLine->hide();
51  layout->addItem( mVerticalSnapLine );
52 }
53 
54 void QgsLayoutMouseHandles::paint( QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget )
55 {
56  paintInternal( painter, mLayout->renderContext().isPreviewRender(),
57  mLayout->renderContext().boundingBoxesVisible(), true, option, widget );
58 }
59 
60 void QgsLayoutMouseHandles::selectionChanged()
61 {
62  //listen out for selected items' size and rotation changed signals
63  const QList<QGraphicsItem *> itemList = layout()->items();
64  for ( QGraphicsItem *graphicsItem : itemList )
65  {
66  QgsLayoutItem *item = dynamic_cast<QgsLayoutItem *>( graphicsItem );
67  if ( !item )
68  continue;
69 
70  if ( item->isSelected() )
71  {
72  connect( item, &QgsLayoutItem::sizePositionChanged, this, &QgsLayoutMouseHandles::selectedItemSizeChanged );
73  connect( item, &QgsLayoutItem::rotationChanged, this, &QgsLayoutMouseHandles::selectedItemRotationChanged );
74  connect( item, &QgsLayoutItem::frameChanged, this, &QgsLayoutMouseHandles::selectedItemSizeChanged );
75  connect( item, &QgsLayoutItem::lockChanged, this, &QgsLayoutMouseHandles::selectedItemSizeChanged );
76  }
77  else
78  {
79  disconnect( item, &QgsLayoutItem::sizePositionChanged, this, &QgsLayoutMouseHandles::selectedItemSizeChanged );
80  disconnect( item, &QgsLayoutItem::rotationChanged, this, &QgsLayoutMouseHandles::selectedItemRotationChanged );
81  disconnect( item, &QgsLayoutItem::frameChanged, this, &QgsLayoutMouseHandles::selectedItemSizeChanged );
82  disconnect( item, &QgsLayoutItem::lockChanged, this, &QgsLayoutMouseHandles::selectedItemSizeChanged );
83  }
84  }
85 
86  resetStatusBar();
87  updateHandles();
88 }
89 
90 void QgsLayoutMouseHandles::setViewportCursor( Qt::CursorShape cursor )
91 {
92  //workaround qt bug #3732 by setting cursor for QGraphicsView viewport,
93  //rather then setting it directly here
94 
95  if ( qobject_cast< QgsLayoutViewToolSelect *>( mView->tool() ) )
96  {
97  mView->viewport()->setCursor( cursor );
98  }
99 }
100 
101 QList<QGraphicsItem *> QgsLayoutMouseHandles::sceneItemsAtPoint( QPointF scenePoint )
102 {
103  QList< QGraphicsItem * > items = mLayout->items( scenePoint );
104  items.erase( std::remove_if( items.begin(), items.end(), []( QGraphicsItem * item )
105  {
106  return !dynamic_cast<QgsLayoutItem *>( item );
107  } ), items.end() );
108 
109  return items;
110 }
111 
112 QList<QGraphicsItem *> QgsLayoutMouseHandles::selectedSceneItems( bool includeLockedItems ) const
113 {
114  QList<QGraphicsItem *> res;
115  const QList<QgsLayoutItem *> layoutItems = mLayout->selectedLayoutItems( includeLockedItems );
116  res.reserve( layoutItems.size() );
117  for ( QgsLayoutItem *item : layoutItems )
118  res << item;
119  return res;
120 }
121 
122 bool QgsLayoutMouseHandles::itemIsLocked( QGraphicsItem *item )
123 {
124  if ( QgsLayoutItem *layoutItem = dynamic_cast<QgsLayoutItem *>( item ) )
125  return layoutItem->isLocked();
126  else
127  return false;
128 }
129 
130 bool QgsLayoutMouseHandles::itemIsGroupMember( QGraphicsItem *item )
131 {
132  if ( QgsLayoutItem *layoutItem = dynamic_cast<QgsLayoutItem *>( item ) )
133  return layoutItem->isGroupMember();
134  else
135  return false;
136 }
137 
138 QRectF QgsLayoutMouseHandles::itemRect( QGraphicsItem *item ) const
139 {
140  if ( QgsLayoutItem *layoutItem = dynamic_cast<QgsLayoutItem *>( item ) )
141  return layoutItem->rectWithFrame();
142  else
143  return QRectF();
144 }
145 
146 QPointF QgsLayoutMouseHandles::snapPoint( QPointF originalPoint, QgsLayoutMouseHandles::SnapGuideMode mode, bool snapHorizontal, bool snapVertical )
147 {
148  bool snapped = false;
149 
150  const QList< QGraphicsItem * > selectedItems = selectedSceneItems();
151  QList< QGraphicsItem * > itemsToExclude;
152  expandItemList( selectedItems, itemsToExclude );
153 
154  QList< QgsLayoutItem * > layoutItemsToExclude;
155  for ( QGraphicsItem *item : itemsToExclude )
156  layoutItemsToExclude << dynamic_cast< QgsLayoutItem * >( item );
157 
158  //depending on the mode, we either snap just the single point, or all the bounds of the selection
159  QPointF snappedPoint;
160  switch ( mode )
161  {
162  case Item:
163  snappedPoint = mLayout->snapper().snapRect( rect().translated( originalPoint ), mView->transform().m11(), snapped, snapHorizontal ? mHorizontalSnapLine : nullptr,
164  snapVertical ? mVerticalSnapLine : nullptr, &layoutItemsToExclude ).topLeft();
165  break;
166  case Point:
167  snappedPoint = mLayout->snapper().snapPoint( originalPoint, mView->transform().m11(), snapped, snapHorizontal ? mHorizontalSnapLine : nullptr,
168  snapVertical ? mVerticalSnapLine : nullptr, &layoutItemsToExclude );
169  break;
170  }
171 
172  return snapped ? snappedPoint : originalPoint;
173 }
174 
175 void QgsLayoutMouseHandles::createItemCommand( QGraphicsItem *item )
176 {
177  mItemCommand.reset( qgis::down_cast< QgsLayoutItem * >( item )->createCommand( QString(), 0 ) );
178  mItemCommand->saveBeforeState();
179 }
180 
181 void QgsLayoutMouseHandles::endItemCommand( QGraphicsItem * )
182 {
183  mItemCommand->saveAfterState();
184  mLayout->undoStack()->push( mItemCommand.release() );
185 }
186 
187 void QgsLayoutMouseHandles::startMacroCommand( const QString &text )
188 {
189  mLayout->undoStack()->beginMacro( text );
190 
191 }
192 
193 void QgsLayoutMouseHandles::endMacroCommand()
194 {
195  mLayout->undoStack()->endMacro();
196 }
197 
198 void QgsLayoutMouseHandles::hideAlignItems()
199 {
200  mHorizontalSnapLine->hide();
201  mVerticalSnapLine->hide();
202 }
203 
204 void QgsLayoutMouseHandles::expandItemList( const QList<QGraphicsItem *> &items, QList<QGraphicsItem *> &collected ) const
205 {
206  for ( QGraphicsItem *item : items )
207  {
208  if ( item->type() == QgsLayoutItemRegistry::LayoutGroup )
209  {
210  // if a group is selected, we don't draw the bounds of the group - instead we draw the bounds of the grouped items
211  const QList<QgsLayoutItem *> groupItems = static_cast< QgsLayoutItemGroup * >( item )->items();
212  collected.reserve( collected.size() + groupItems.size() );
213  for ( QgsLayoutItem *groupItem : groupItems )
214  collected.append( groupItem );
215  }
216  else
217  {
218  collected << item;
219  }
220  }
221 }
222 
223 void QgsLayoutMouseHandles::moveItem( QGraphicsItem *item, double deltaX, double deltaY )
224 {
225  qgis::down_cast< QgsLayoutItem * >( item )->attemptMoveBy( deltaX, deltaY );
226 }
227 
228 void QgsLayoutMouseHandles::setItemRect( QGraphicsItem *item, QRectF rect )
229 {
230  QgsLayoutItem *layoutItem = dynamic_cast< QgsLayoutItem * >( item );
231  layoutItem->attemptSetSceneRect( rect, true );
232 }
233 
234 void QgsLayoutMouseHandles::showStatusMessage( const QString &message )
235 {
236  if ( !mView )
237  return;
238 
239  mView->pushStatusMessage( message );
240 }
241 
242 
A container for grouping several QgsLayoutItems.
Base class for graphical items within a QgsLayout.
void rotationChanged(double newRotation)
Emitted on item rotation change.
bool isLocked() const
Returns true if the item is locked, and cannot be interacted with using the mouse.
void sizePositionChanged()
Emitted when the item's size or position changes.
void lockChanged()
Emitted if the item's lock status changes.
void frameChanged()
Emitted if the item's frame style changes.
void attemptMoveBy(double deltaX, double deltaY)
Attempts to shift the item's position by a specified deltaX and deltaY, in layout units.
void attemptSetSceneRect(const QRectF &rect, bool includesFrame=false)
Attempts to update the item's position and size to match the passed rect in layout coordinates.
A graphical widget to display and interact with QgsLayouts.
Definition: qgslayoutview.h:50
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition: qgslayout.h:51