1 /***************************************************************************
2                              qgslayoutmousehandles.cpp
3                              ------------------------
4     begin                : September 2017
5     copyright            : (C) 2017 by Nyall Dawson
6     email                : nyall.dawson@gmail.com
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"
26 #include "qgslayoutviewtoolselect.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 
36 ///@cond PRIVATE
37 
QgsLayoutMouseHandles(QgsLayout * layout,QgsLayoutView * view)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 
paint(QPainter * painter,const QStyleOptionGraphicsItem * option,QWidget * widget)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 
selectionChanged()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 
setViewportCursor(Qt::CursorShape cursor)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 
sceneItemsAtPoint(QPointF scenePoint)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 
selectedSceneItems(bool includeLockedItems) const112 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 
itemIsLocked(QGraphicsItem * item)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 
itemIsGroupMember(QGraphicsItem * item)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 
itemRect(QGraphicsItem * item) const138 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 
snapPoint(QPointF originalPoint,QgsLayoutMouseHandles::SnapGuideMode mode,bool snapHorizontal,bool snapVertical)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 
createItemCommand(QGraphicsItem * item)175 void QgsLayoutMouseHandles::createItemCommand( QGraphicsItem *item )
176 {
177   mItemCommand.reset( dynamic_cast< QgsLayoutItem * >( item )->createCommand( QString(), 0 ) );
178   mItemCommand->saveBeforeState();
179 }
180 
endItemCommand(QGraphicsItem *)181 void QgsLayoutMouseHandles::endItemCommand( QGraphicsItem * )
182 {
183   mItemCommand->saveAfterState();
184   mLayout->undoStack()->push( mItemCommand.release() );
185 }
186 
startMacroCommand(const QString & text)187 void QgsLayoutMouseHandles::startMacroCommand( const QString &text )
188 {
189   mLayout->undoStack()->beginMacro( text );
190 
191 }
192 
endMacroCommand()193 void QgsLayoutMouseHandles::endMacroCommand()
194 {
195   mLayout->undoStack()->endMacro();
196 }
197 
hideAlignItems()198 void QgsLayoutMouseHandles::hideAlignItems()
199 {
200   mHorizontalSnapLine->hide();
201   mVerticalSnapLine->hide();
202 }
203 
expandItemList(const QList<QGraphicsItem * > & items,QList<QGraphicsItem * > & collected) const204 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 
moveItem(QGraphicsItem * item,double deltaX,double deltaY)223 void QgsLayoutMouseHandles::moveItem( QGraphicsItem *item, double deltaX, double deltaY )
224 {
225   dynamic_cast< QgsLayoutItem * >( item )->attemptMoveBy( deltaX, deltaY );
226 }
227 
setItemRect(QGraphicsItem * item,QRectF rect)228 void QgsLayoutMouseHandles::setItemRect( QGraphicsItem *item, QRectF rect )
229 {
230   QgsLayoutItem *layoutItem = dynamic_cast< QgsLayoutItem * >( item );
231   layoutItem->attemptSetSceneRect( rect, true );
232 }
233 
showStatusMessage(const QString & message)234 void QgsLayoutMouseHandles::showStatusMessage( const QString &message )
235 {
236   if ( !mView )
237     return;
238 
239   mView->pushStatusMessage( message );
240 }
241 
242 
243 ///@endcond PRIVATE
244