QGIS/src/gui/layout/qgslayoutview.cpp
2021-03-22 05:34:10 +10:00

1288 lines
35 KiB
C++

/***************************************************************************
qgslayoutview.cpp
-----------------
Date : July 2017
Copyright : (C) 2017 Nyall Dawson
Email : nyall dot dawson at gmail dot com
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgslayoutview.h"
#include "qgslayout.h"
#include "qgslayoutframe.h"
#include "qgslayoutmultiframe.h"
#include "qgslayoutviewtool.h"
#include "qgslayoutviewmouseevent.h"
#include "qgslayoutviewtooltemporarykeypan.h"
#include "qgslayoutviewtooltemporarykeyzoom.h"
#include "qgslayoutviewtooltemporarymousepan.h"
#include "qgslayoutmousehandles.h"
#include "qgslayoutruler.h"
#include "qgslayoutmodel.h"
#include "qgssettings.h"
#include "qgsrectangle.h"
#include "qgsapplication.h"
#include "qgslayoutitemundocommand.h"
#include "qgsproject.h"
#include "qgslayoutitemgroup.h"
#include "qgslayoutpagecollection.h"
#include "qgslayoutundostack.h"
#include "qgslayoutreportsectionlabel.h"
#include "qgsreadwritecontext.h"
#include <memory>
#include <QDesktopWidget>
#include <QMenu>
#include <QClipboard>
#include <QMimeData>
#define MIN_VIEW_SCALE 0.05
#define MAX_VIEW_SCALE 1000.0
QgsLayoutView::QgsLayoutView( QWidget *parent )
: QGraphicsView( parent )
{
setResizeAnchor( QGraphicsView::AnchorViewCenter );
setMouseTracking( true );
viewport()->setMouseTracking( true );
// set the "scene" background on the view using stylesheets
// we don't want to use QGraphicsScene::setBackgroundBrush because we want to keep
// a transparent background for exports, and it's only a cosmetic thing for the view only
// ALSO - only set it on the viewport - we don't want scrollbars/etc affected by this
viewport()->setStyleSheet( QStringLiteral( "background-color:#d7d7d7;" ) );
mSpacePanTool = new QgsLayoutViewToolTemporaryKeyPan( this );
mMidMouseButtonPanTool = new QgsLayoutViewToolTemporaryMousePan( this );
mSpaceZoomTool = new QgsLayoutViewToolTemporaryKeyZoom( this );
mPreviewEffect = new QgsPreviewEffect( this );
viewport()->setGraphicsEffect( mPreviewEffect );
connect( this, &QgsLayoutView::zoomLevelChanged, this, &QgsLayoutView::invalidateCachedRenders );
}
QgsLayoutView::~QgsLayoutView()
{
emit willBeDeleted();
}
QgsLayout *QgsLayoutView::currentLayout()
{
return qobject_cast<QgsLayout *>( scene() );
}
const QgsLayout *QgsLayoutView::currentLayout() const
{
return qobject_cast<const QgsLayout *>( scene() );
}
void QgsLayoutView::setCurrentLayout( QgsLayout *layout )
{
setScene( layout );
connect( layout->pageCollection(), &QgsLayoutPageCollection::changed, this, &QgsLayoutView::viewChanged );
connect( layout, &QgsLayout::selectedItemChanged, this, &QgsLayoutView::itemFocused );
viewChanged();
// IMPORTANT!
// previous snap markers, snap lines are owned by previous layout - so don't delete them here!
mSnapMarker = new QgsLayoutViewSnapMarker();
mSnapMarker->hide();
layout->addItem( mSnapMarker );
mHorizontalSnapLine = createSnapLine();
mHorizontalSnapLine->hide();
layout->addItem( mHorizontalSnapLine );
mVerticalSnapLine = createSnapLine();
mVerticalSnapLine->hide();
layout->addItem( mVerticalSnapLine );
mSectionLabel = nullptr;
if ( mHorizontalRuler )
{
connect( &layout->guides(), &QAbstractItemModel::dataChanged, mHorizontalRuler, [ = ] { mHorizontalRuler->update(); } );
connect( &layout->guides(), &QAbstractItemModel::rowsInserted, mHorizontalRuler, [ = ] { mHorizontalRuler->update(); } );
connect( &layout->guides(), &QAbstractItemModel::rowsRemoved, mHorizontalRuler, [ = ] { mHorizontalRuler->update(); } );
connect( &layout->guides(), &QAbstractItemModel::modelReset, mHorizontalRuler, [ = ] { mHorizontalRuler->update(); } );
}
if ( mVerticalRuler )
{
connect( &layout->guides(), &QAbstractItemModel::dataChanged, mVerticalRuler, [ = ] { mVerticalRuler->update(); } );
connect( &layout->guides(), &QAbstractItemModel::rowsInserted, mVerticalRuler, [ = ] { mVerticalRuler->update(); } );
connect( &layout->guides(), &QAbstractItemModel::rowsRemoved, mVerticalRuler, [ = ] { mVerticalRuler->update(); } );
connect( &layout->guides(), &QAbstractItemModel::modelReset, mVerticalRuler, [ = ] { mVerticalRuler->update(); } );
}
//emit layoutSet, so that designer dialogs can update for the new layout
emit layoutSet( layout );
}
QgsLayoutViewTool *QgsLayoutView::tool()
{
return mTool;
}
void QgsLayoutView::setTool( QgsLayoutViewTool *tool )
{
if ( !tool )
return;
if ( mTool )
{
mTool->deactivate();
disconnect( mTool, &QgsLayoutViewTool::itemFocused, this, &QgsLayoutView::itemFocused );
}
if ( mSnapMarker )
mSnapMarker->hide();
if ( mHorizontalSnapLine )
mHorizontalSnapLine->hide();
if ( mVerticalSnapLine )
mVerticalSnapLine->hide();
// activate new tool before setting it - gives tools a chance
// to respond to whatever the current tool is
tool->activate();
mTool = tool;
connect( mTool, &QgsLayoutViewTool::itemFocused, this, &QgsLayoutView::itemFocused );
emit toolSet( mTool );
}
void QgsLayoutView::unsetTool( QgsLayoutViewTool *tool )
{
if ( mTool && mTool == tool )
{
mTool->deactivate();
emit toolSet( nullptr );
setCursor( Qt::ArrowCursor );
}
}
void QgsLayoutView::setPreviewModeEnabled( bool enabled )
{
mPreviewEffect->setEnabled( enabled );
}
bool QgsLayoutView::previewModeEnabled() const
{
return mPreviewEffect->isEnabled();
}
void QgsLayoutView::setPreviewMode( QgsPreviewEffect::PreviewMode mode )
{
mPreviewEffect->setMode( mode );
}
QgsPreviewEffect::PreviewMode QgsLayoutView::previewMode() const
{
return mPreviewEffect->mode();
}
void QgsLayoutView::scaleSafe( double scale )
{
double currentScale = transform().m11();
scale *= currentScale;
scale = std::clamp( scale, MIN_VIEW_SCALE, MAX_VIEW_SCALE );
setTransform( QTransform::fromScale( scale, scale ) );
emit zoomLevelChanged();
viewChanged();
}
void QgsLayoutView::setZoomLevel( double level )
{
if ( !currentLayout() )
return;
if ( currentLayout()->units() == QgsUnitTypes::LayoutPixels )
{
setTransform( QTransform::fromScale( level, level ) );
}
else
{
double dpi = QgsApplication::desktop()->logicalDpiX();
//monitor dpi is not always correct - so make sure the value is sane
if ( ( dpi < 60 ) || ( dpi > 1200 ) )
dpi = 72;
//desired pixel width for 1mm on screen
level = std::clamp( level, MIN_VIEW_SCALE, MAX_VIEW_SCALE );
double mmLevel = currentLayout()->convertFromLayoutUnits( level, QgsUnitTypes::LayoutMillimeters ).length() * dpi / 25.4;
setTransform( QTransform::fromScale( mmLevel, mmLevel ) );
}
emit zoomLevelChanged();
viewChanged();
}
void QgsLayoutView::setHorizontalRuler( QgsLayoutRuler *ruler )
{
mHorizontalRuler = ruler;
ruler->setLayoutView( this );
if ( QgsLayout *layout = currentLayout() )
{
connect( &layout->guides(), &QAbstractItemModel::dataChanged, ruler, [ = ] { mHorizontalRuler->update(); } );
connect( &layout->guides(), &QAbstractItemModel::rowsInserted, ruler, [ = ] { mHorizontalRuler->update(); } );
connect( &layout->guides(), &QAbstractItemModel::rowsRemoved, ruler, [ = ] { mHorizontalRuler->update(); } );
connect( &layout->guides(), &QAbstractItemModel::modelReset, ruler, [ = ] { mHorizontalRuler->update(); } );
}
viewChanged();
}
void QgsLayoutView::setVerticalRuler( QgsLayoutRuler *ruler )
{
mVerticalRuler = ruler;
ruler->setLayoutView( this );
if ( QgsLayout *layout = currentLayout() )
{
connect( &layout->guides(), &QAbstractItemModel::dataChanged, ruler, [ = ] { mVerticalRuler->update(); } );
connect( &layout->guides(), &QAbstractItemModel::rowsInserted, ruler, [ = ] { mVerticalRuler->update(); } );
connect( &layout->guides(), &QAbstractItemModel::rowsRemoved, ruler, [ = ] { mVerticalRuler->update(); } );
connect( &layout->guides(), &QAbstractItemModel::modelReset, ruler, [ = ] { mVerticalRuler->update(); } );
}
viewChanged();
}
void QgsLayoutView::setMenuProvider( QgsLayoutViewMenuProvider *provider )
{
mMenuProvider.reset( provider );
}
QgsLayoutViewMenuProvider *QgsLayoutView::menuProvider() const
{
return mMenuProvider.get();
}
QList<QgsLayoutItemPage *> QgsLayoutView::visiblePages() const
{
if ( !currentLayout() )
return QList< QgsLayoutItemPage *>();
//get current visible part of scene
QRect viewportRect( 0, 0, viewport()->width(), viewport()->height() );
QRectF visibleRect = mapToScene( viewportRect ).boundingRect();
return currentLayout()->pageCollection()->visiblePages( visibleRect );
}
QList<int> QgsLayoutView::visiblePageNumbers() const
{
if ( !currentLayout() )
return QList< int >();
//get current visible part of scene
QRect viewportRect( 0, 0, viewport()->width(), viewport()->height() );
QRectF visibleRect = mapToScene( viewportRect ).boundingRect();
return currentLayout()->pageCollection()->visiblePageNumbers( visibleRect );
}
void QgsLayoutView::alignSelectedItems( QgsLayoutAligner::Alignment alignment )
{
if ( !currentLayout() )
return;
const QList<QgsLayoutItem *> selectedItems = currentLayout()->selectedLayoutItems();
QgsLayoutAligner::alignItems( currentLayout(), selectedItems, alignment );
}
void QgsLayoutView::distributeSelectedItems( QgsLayoutAligner::Distribution distribution )
{
if ( !currentLayout() )
return;
const QList<QgsLayoutItem *> selectedItems = currentLayout()->selectedLayoutItems();
QgsLayoutAligner::distributeItems( currentLayout(), selectedItems, distribution );
}
void QgsLayoutView::resizeSelectedItems( QgsLayoutAligner::Resize resize )
{
if ( !currentLayout() )
return;
const QList<QgsLayoutItem *> selectedItems = currentLayout()->selectedLayoutItems();
QgsLayoutAligner::resizeItems( currentLayout(), selectedItems, resize );
}
void QgsLayoutView::copySelectedItems( QgsLayoutView::ClipboardOperation operation )
{
copyItems( currentLayout()->selectedLayoutItems(), operation );
}
void QgsLayoutView::copyItems( const QList<QgsLayoutItem *> &items, QgsLayoutView::ClipboardOperation operation )
{
if ( !currentLayout() )
return;
QgsReadWriteContext context;
QDomDocument doc;
QDomElement documentElement = doc.createElement( QStringLiteral( "LayoutItemClipboard" ) );
if ( operation == ClipboardCut )
currentLayout()->undoStack()->beginMacro( tr( "Cut Items" ) );
QSet< QgsLayoutMultiFrame * > copiedMultiFrames;
for ( QgsLayoutItem *item : items )
{
// copy every child from a group
if ( QgsLayoutItemGroup *itemGroup = qobject_cast<QgsLayoutItemGroup *>( item ) )
{
const QList<QgsLayoutItem *> groupedItems = itemGroup->items();
for ( const QgsLayoutItem *groupedItem : groupedItems )
{
groupedItem->writeXml( documentElement, doc, context );
}
}
else if ( QgsLayoutFrame *frame = qobject_cast<QgsLayoutFrame *>( item ) )
{
// copy multiframe too
if ( frame->multiFrame() && !copiedMultiFrames.contains( frame->multiFrame() ) )
{
frame->multiFrame()->writeXml( documentElement, doc, context );
copiedMultiFrames.insert( frame->multiFrame() );
}
}
item->writeXml( documentElement, doc, context );
if ( operation == ClipboardCut )
currentLayout()->removeLayoutItem( item );
}
doc.appendChild( documentElement );
if ( operation == ClipboardCut )
{
currentLayout()->undoStack()->endMacro();
currentLayout()->update();
}
//remove the UUIDs since we don't want any duplicate UUID
QDomNodeList itemsNodes = doc.elementsByTagName( QStringLiteral( "LayoutItem" ) );
for ( int i = 0; i < itemsNodes.count(); ++i )
{
QDomNode itemNode = itemsNodes.at( i );
if ( itemNode.isElement() )
{
itemNode.toElement().removeAttribute( QStringLiteral( "uuid" ) );
itemNode.toElement().removeAttribute( QStringLiteral( "groupUuid" ) );
}
}
QDomNodeList multiFrameNodes = doc.elementsByTagName( QStringLiteral( "LayoutMultiFrame" ) );
for ( int i = 0; i < multiFrameNodes.count(); ++i )
{
QDomNode multiFrameNode = multiFrameNodes.at( i );
if ( multiFrameNode.isElement() )
{
multiFrameNode.toElement().removeAttribute( QStringLiteral( "uuid" ) );
QDomNodeList frameNodes = multiFrameNode.toElement().elementsByTagName( QStringLiteral( "childFrame" ) );
for ( int j = 0; j < frameNodes.count(); ++j )
{
QDomNode itemNode = frameNodes.at( j );
if ( itemNode.isElement() )
{
itemNode.toElement().removeAttribute( QStringLiteral( "uuid" ) );
}
}
}
}
QMimeData *mimeData = new QMimeData;
mimeData->setData( QStringLiteral( "text/xml" ), doc.toByteArray() );
QClipboard *clipboard = QApplication::clipboard();
clipboard->setMimeData( mimeData );
}
QList< QgsLayoutItem * > QgsLayoutView::pasteItems( QgsLayoutView::PasteMode mode )
{
if ( !currentLayout() )
return QList< QgsLayoutItem * >();
QList< QgsLayoutItem * > pastedItems;
QDomDocument doc;
QClipboard *clipboard = QApplication::clipboard();
if ( doc.setContent( clipboard->mimeData()->data( QStringLiteral( "text/xml" ) ) ) )
{
QDomElement docElem = doc.documentElement();
if ( docElem.tagName() == QLatin1String( "LayoutItemClipboard" ) )
{
QPointF pt;
switch ( mode )
{
case PasteModeCursor:
case PasteModeInPlace:
{
// place items at cursor position
pt = mapToScene( mapFromGlobal( QCursor::pos() ) );
break;
}
case PasteModeCenter:
{
// place items in center of viewport
pt = mapToScene( viewport()->rect().center() );
break;
}
}
bool pasteInPlace = ( mode == PasteModeInPlace );
currentLayout()->undoStack()->beginMacro( tr( "Paste Items" ) );
currentLayout()->undoStack()->beginCommand( currentLayout(), tr( "Paste Items" ) );
pastedItems = currentLayout()->addItemsFromXml( docElem, doc, QgsReadWriteContext(), &pt, pasteInPlace );
currentLayout()->undoStack()->endCommand();
currentLayout()->undoStack()->endMacro();
}
}
return pastedItems;
}
QList<QgsLayoutItem *> QgsLayoutView::pasteItems( QPointF layoutPoint )
{
if ( !currentLayout() )
return QList<QgsLayoutItem *>();
QList< QgsLayoutItem * > pastedItems;
QDomDocument doc;
QClipboard *clipboard = QApplication::clipboard();
if ( doc.setContent( clipboard->mimeData()->data( QStringLiteral( "text/xml" ) ) ) )
{
QDomElement docElem = doc.documentElement();
if ( docElem.tagName() == QLatin1String( "LayoutItemClipboard" ) )
{
currentLayout()->undoStack()->beginMacro( tr( "Paste Items" ) );
currentLayout()->undoStack()->beginCommand( currentLayout(), tr( "Paste Items" ) );
pastedItems = currentLayout()->addItemsFromXml( docElem, doc, QgsReadWriteContext(), &layoutPoint, false );
currentLayout()->undoStack()->endCommand();
currentLayout()->undoStack()->endMacro();
}
}
return pastedItems;
}
bool QgsLayoutView::hasItemsInClipboard() const
{
QDomDocument doc;
QClipboard *clipboard = QApplication::clipboard();
if ( doc.setContent( clipboard->mimeData()->data( QStringLiteral( "text/xml" ) ) ) )
{
QDomElement docElem = doc.documentElement();
if ( docElem.tagName() == QLatin1String( "LayoutItemClipboard" ) )
return true;
}
return false;
}
QPointF QgsLayoutView::deltaForKeyEvent( QKeyEvent *event )
{
// increment used for cursor key item movement
double increment = 1.0;
if ( event->modifiers() & Qt::ShiftModifier )
{
//holding shift while pressing cursor keys results in a big step
increment = 10.0;
}
else if ( event->modifiers() & Qt::AltModifier )
{
//holding alt while pressing cursor keys results in a 1 pixel step
double viewScale = transform().m11();
if ( viewScale > 0 )
{
increment = 1 / viewScale;
}
}
double deltaX = 0;
double deltaY = 0;
switch ( event->key() )
{
case Qt::Key_Left:
deltaX = -increment;
break;
case Qt::Key_Right:
deltaX = increment;
break;
case Qt::Key_Up:
deltaY = -increment;
break;
case Qt::Key_Down:
deltaY = increment;
break;
default:
break;
}
return QPointF( deltaX, deltaY );
}
void QgsLayoutView::setPaintingEnabled( bool enabled )
{
mPaintingEnabled = enabled;
if ( enabled )
update();
}
void QgsLayoutView::setSectionLabel( const QString &label )
{
if ( !currentLayout() )
return;
if ( !mSectionLabel )
{
mSectionLabel = new QgsLayoutReportSectionLabel( currentLayout(), this );
currentLayout()->addItem( mSectionLabel );
mSectionLabel->setRect( 0, -200, 1000, 200 );
mSectionLabel->setZValue( -1 );
}
mSectionLabel->setLabel( label );
}
void QgsLayoutView::zoomFull()
{
if ( !scene() )
return;
fitInView( scene()->sceneRect(), Qt::KeepAspectRatio );
viewChanged();
emit zoomLevelChanged();
}
void QgsLayoutView::zoomWidth()
{
if ( !scene() )
return;
//get current visible part of scene
QRect viewportRect( 0, 0, viewport()->width(), viewport()->height() );
QRectF visibleRect = mapToScene( viewportRect ).boundingRect();
double verticalCenter = ( visibleRect.top() + visibleRect.bottom() ) / 2.0;
// expand out visible rect to include left/right edges of scene
// centered on current visible vertical center
// note that we can't have a 0 height rect - fitInView doesn't handle that
// so we just set a very small height instead.
const double tinyHeight = 0.01;
QRectF targetRect( scene()->sceneRect().left(),
verticalCenter - tinyHeight,
scene()->sceneRect().width(),
tinyHeight * 2 );
fitInView( targetRect, Qt::KeepAspectRatio );
emit zoomLevelChanged();
viewChanged();
}
void QgsLayoutView::zoomIn()
{
scaleSafe( 2 );
}
void QgsLayoutView::zoomOut()
{
scaleSafe( 0.5 );
}
void QgsLayoutView::zoomActual()
{
setZoomLevel( 1.0 );
}
void QgsLayoutView::emitZoomLevelChanged()
{
emit zoomLevelChanged();
}
void QgsLayoutView::selectAll()
{
if ( !currentLayout() )
{
return;
}
//select all items in layout
QgsLayoutItem *focusedItem = nullptr;
const QList<QGraphicsItem *> itemList = currentLayout()->items();
for ( QGraphicsItem *graphicsItem : itemList )
{
QgsLayoutItem *item = dynamic_cast<QgsLayoutItem *>( graphicsItem );
QgsLayoutItemPage *paperItem = dynamic_cast<QgsLayoutItemPage *>( graphicsItem );
if ( item && !paperItem )
{
if ( !item->isLocked() )
{
item->setSelected( true );
if ( !focusedItem )
focusedItem = item;
}
else
{
//deselect all locked items
item->setSelected( false );
}
}
}
emit itemFocused( focusedItem );
}
void QgsLayoutView::deselectAll()
{
if ( !currentLayout() )
{
return;
}
currentLayout()->deselectAll();
}
void QgsLayoutView::invertSelection()
{
if ( !currentLayout() )
{
return;
}
QgsLayoutItem *focusedItem = nullptr;
//check all items in layout
const QList<QGraphicsItem *> itemList = currentLayout()->items();
for ( QGraphicsItem *graphicsItem : itemList )
{
QgsLayoutItem *item = dynamic_cast<QgsLayoutItem *>( graphicsItem );
QgsLayoutItemPage *paperItem = dynamic_cast<QgsLayoutItemPage *>( graphicsItem );
if ( item && !paperItem )
{
//flip selected state for items (and deselect any locked items)
if ( item->isSelected() || item->isLocked() )
{
item->setSelected( false );
}
else
{
item->setSelected( true );
if ( !focusedItem )
focusedItem = item;
}
}
}
if ( focusedItem )
emit itemFocused( focusedItem );
}
void selectNextByZOrder( QgsLayout *layout, bool above )
{
if ( !layout )
return;
QgsLayoutItem *previousSelectedItem = nullptr;
const QList<QgsLayoutItem *> selectedItems = layout->selectedLayoutItems();
if ( !selectedItems.isEmpty() )
{
previousSelectedItem = selectedItems.at( 0 );
}
if ( !previousSelectedItem )
{
return;
}
//select item with target z value
QgsLayoutItem *selectedItem = nullptr;
if ( !above )
selectedItem = layout->itemsModel()->findItemBelow( previousSelectedItem );
else
selectedItem = layout->itemsModel()->findItemAbove( previousSelectedItem );
if ( !selectedItem )
{
return;
}
//OK, found a good target item
layout->setSelectedItem( selectedItem );
}
void QgsLayoutView::selectNextItemAbove()
{
selectNextByZOrder( currentLayout(), true );
}
void QgsLayoutView::selectNextItemBelow()
{
selectNextByZOrder( currentLayout(), false );
}
void QgsLayoutView::raiseSelectedItems()
{
if ( !currentLayout() )
return;
const QList<QgsLayoutItem *> selectedItems = currentLayout()->selectedLayoutItems();
bool itemsRaised = false;
for ( QgsLayoutItem *item : selectedItems )
{
itemsRaised = itemsRaised | currentLayout()->raiseItem( item, true );
}
if ( !itemsRaised )
{
//no change
return;
}
//update all positions
currentLayout()->updateZValues();
currentLayout()->update();
}
void QgsLayoutView::lowerSelectedItems()
{
if ( !currentLayout() )
return;
const QList<QgsLayoutItem *> selectedItems = currentLayout()->selectedLayoutItems();
bool itemsLowered = false;
for ( QgsLayoutItem *item : selectedItems )
{
itemsLowered = itemsLowered | currentLayout()->lowerItem( item, true );
}
if ( !itemsLowered )
{
//no change
return;
}
//update all positions
currentLayout()->updateZValues();
currentLayout()->update();
}
void QgsLayoutView::moveSelectedItemsToTop()
{
if ( !currentLayout() )
return;
const QList<QgsLayoutItem *> selectedItems = currentLayout()->selectedLayoutItems();
bool itemsRaised = false;
for ( QgsLayoutItem *item : selectedItems )
{
itemsRaised = itemsRaised | currentLayout()->moveItemToTop( item, true );
}
if ( !itemsRaised )
{
//no change
return;
}
//update all positions
currentLayout()->updateZValues();
currentLayout()->update();
}
void QgsLayoutView::moveSelectedItemsToBottom()
{
if ( !currentLayout() )
return;
const QList<QgsLayoutItem *> selectedItems = currentLayout()->selectedLayoutItems();
bool itemsLowered = false;
for ( QgsLayoutItem *item : selectedItems )
{
itemsLowered = itemsLowered | currentLayout()->moveItemToBottom( item, true );
}
if ( !itemsLowered )
{
//no change
return;
}
//update all positions
currentLayout()->updateZValues();
currentLayout()->update();
}
void QgsLayoutView::lockSelectedItems()
{
if ( !currentLayout() )
return;
currentLayout()->undoStack()->beginMacro( tr( "Lock Items" ) );
const QList<QgsLayoutItem *> selectionList = currentLayout()->selectedLayoutItems();
for ( QgsLayoutItem *item : selectionList )
{
item->setLocked( true );
}
currentLayout()->deselectAll();
currentLayout()->undoStack()->endMacro();
}
void QgsLayoutView::unlockAllItems()
{
if ( !currentLayout() )
return;
//unlock all items in layout
currentLayout()->undoStack()->beginMacro( tr( "Unlock Items" ) );
//first, clear the selection
currentLayout()->deselectAll();
QgsLayoutItem *focusItem = nullptr;
const QList<QGraphicsItem *> itemList = currentLayout()->items();
for ( QGraphicsItem *graphicItem : itemList )
{
QgsLayoutItem *item = dynamic_cast<QgsLayoutItem *>( graphicItem );
if ( item && item->isLocked() )
{
focusItem = item;
item->setLocked( false );
//select unlocked items, same behavior as illustrator
item->setSelected( true );
}
}
currentLayout()->undoStack()->endMacro();
emit itemFocused( focusItem );
}
void QgsLayoutView::deleteSelectedItems()
{
if ( !currentLayout() )
return;
deleteItems( currentLayout()->selectedLayoutItems() );
}
void QgsLayoutView::deleteItems( const QList<QgsLayoutItem *> &items )
{
if ( !currentLayout() )
return;
if ( items.empty() )
return;
currentLayout()->undoStack()->beginMacro( tr( "Delete Items" ) );
//delete selected items
for ( QgsLayoutItem *item : items )
{
currentLayout()->removeLayoutItem( item );
}
currentLayout()->undoStack()->endMacro();
currentLayout()->project()->setDirty( true );
}
void QgsLayoutView::groupSelectedItems()
{
if ( !currentLayout() )
{
return;
}
//group selected items
const QList<QgsLayoutItem *> selectionList = currentLayout()->selectedLayoutItems();
QgsLayoutItemGroup *itemGroup = currentLayout()->groupItems( selectionList );
if ( !itemGroup )
{
//group could not be created
return;
}
for ( QgsLayoutItem *item : selectionList )
{
item->setSelected( false );
}
currentLayout()->setSelectedItem( itemGroup );
}
void QgsLayoutView::ungroupSelectedItems()
{
if ( !currentLayout() )
{
return;
}
QList< QgsLayoutItem * > ungroupedItems;
//hunt through selection for any groups, and ungroup them
const QList<QgsLayoutItem *> selectionList = currentLayout()->selectedLayoutItems();
for ( QgsLayoutItem *item : selectionList )
{
if ( item->type() == QgsLayoutItemRegistry::LayoutGroup )
{
QgsLayoutItemGroup *itemGroup = static_cast<QgsLayoutItemGroup *>( item );
ungroupedItems.append( currentLayout()->ungroupItems( itemGroup ) );
}
}
if ( !ungroupedItems.empty() )
{
for ( QgsLayoutItem *item : std::as_const( ungroupedItems ) )
{
item->setSelected( true );
}
emit itemFocused( ungroupedItems.at( 0 ) );
}
}
void QgsLayoutView::mousePressEvent( QMouseEvent *event )
{
if ( !currentLayout() )
return;
if ( mSnapMarker )
mSnapMarker->setVisible( false );
if ( mTool )
{
std::unique_ptr<QgsLayoutViewMouseEvent> me( new QgsLayoutViewMouseEvent( this, event, mTool->flags() & QgsLayoutViewTool::FlagSnaps ) );
mTool->layoutPressEvent( me.get() );
event->setAccepted( me->isAccepted() );
}
if ( !mTool || !event->isAccepted() )
{
if ( event->button() == Qt::MiddleButton )
{
// Pan layout with middle mouse button
setTool( mMidMouseButtonPanTool );
event->accept();
}
else if ( event->button() == Qt::RightButton && mMenuProvider )
{
QMenu *menu = mMenuProvider->createContextMenu( this, currentLayout(), mapToScene( event->pos() ) );
if ( menu )
{
menu->exec( event->globalPos() );
delete menu;
}
}
else
{
QGraphicsView::mousePressEvent( event );
}
}
}
void QgsLayoutView::mouseReleaseEvent( QMouseEvent *event )
{
if ( !currentLayout() )
return;
if ( mTool )
{
std::unique_ptr<QgsLayoutViewMouseEvent> me( new QgsLayoutViewMouseEvent( this, event, mTool->flags() & QgsLayoutViewTool::FlagSnaps ) );
mTool->layoutReleaseEvent( me.get() );
event->setAccepted( me->isAccepted() );
}
if ( !mTool || !event->isAccepted() )
QGraphicsView::mouseReleaseEvent( event );
}
void QgsLayoutView::mouseMoveEvent( QMouseEvent *event )
{
if ( !currentLayout() )
return;
mMouseCurrentXY = event->pos();
QPointF cursorPos = mapToScene( mMouseCurrentXY );
if ( mTool )
{
std::unique_ptr<QgsLayoutViewMouseEvent> me( new QgsLayoutViewMouseEvent( this, event, false ) );
if ( mTool->flags() & QgsLayoutViewTool::FlagSnaps )
{
me->snapPoint( mHorizontalSnapLine, mVerticalSnapLine, mTool->ignoredSnapItems() );
}
if ( mTool->flags() & QgsLayoutViewTool::FlagSnaps )
{
//draw snapping point indicator
if ( me->isSnapped() )
{
cursorPos = me->snappedPoint();
if ( mSnapMarker )
{
mSnapMarker->setPos( me->snappedPoint() );
mSnapMarker->setVisible( true );
}
}
else if ( mSnapMarker )
{
mSnapMarker->setVisible( false );
}
}
mTool->layoutMoveEvent( me.get() );
event->setAccepted( me->isAccepted() );
}
//update cursor position in status bar
emit cursorPosChanged( cursorPos );
if ( !mTool || !event->isAccepted() )
QGraphicsView::mouseMoveEvent( event );
}
void QgsLayoutView::mouseDoubleClickEvent( QMouseEvent *event )
{
if ( !currentLayout() )
return;
if ( mTool )
{
std::unique_ptr<QgsLayoutViewMouseEvent> me( new QgsLayoutViewMouseEvent( this, event, mTool->flags() & QgsLayoutViewTool::FlagSnaps ) );
mTool->layoutDoubleClickEvent( me.get() );
event->setAccepted( me->isAccepted() );
}
if ( !mTool || !event->isAccepted() )
QGraphicsView::mouseDoubleClickEvent( event );
}
void QgsLayoutView::wheelEvent( QWheelEvent *event )
{
if ( !currentLayout() )
return;
if ( mTool )
{
mTool->wheelEvent( event );
}
if ( !mTool || !event->isAccepted() )
{
event->accept();
wheelZoom( event );
}
}
void QgsLayoutView::keyPressEvent( QKeyEvent *event )
{
if ( !currentLayout() )
return;
if ( mTool )
{
mTool->keyPressEvent( event );
}
if ( mTool && event->isAccepted() )
return;
if ( event->key() == Qt::Key_Space && ! event->isAutoRepeat() )
{
if ( !( event->modifiers() & Qt::ControlModifier ) )
{
// Pan layout with space bar
setTool( mSpacePanTool );
}
else
{
//ctrl+space pressed, so switch to temporary keyboard based zoom tool
setTool( mSpaceZoomTool );
}
event->accept();
}
else if ( event->key() == Qt::Key_Left
|| event->key() == Qt::Key_Right
|| event->key() == Qt::Key_Up
|| event->key() == Qt::Key_Down )
{
QgsLayout *l = currentLayout();
const QList<QgsLayoutItem *> layoutItemList = l->selectedLayoutItems();
QPointF delta = deltaForKeyEvent( event );
l->undoStack()->beginMacro( tr( "Move Item" ) );
for ( QgsLayoutItem *item : layoutItemList )
{
l->undoStack()->beginCommand( item, tr( "Move Item" ), QgsLayoutItem::UndoIncrementalMove );
item->attemptMoveBy( delta.x(), delta.y() );
l->undoStack()->endCommand();
}
l->undoStack()->endMacro();
event->accept();
}
}
void QgsLayoutView::keyReleaseEvent( QKeyEvent *event )
{
if ( !currentLayout() )
return;
if ( mTool )
{
mTool->keyReleaseEvent( event );
}
if ( !mTool || !event->isAccepted() )
QGraphicsView::keyReleaseEvent( event );
}
void QgsLayoutView::resizeEvent( QResizeEvent *event )
{
QGraphicsView::resizeEvent( event );
emit zoomLevelChanged();
viewChanged();
}
void QgsLayoutView::scrollContentsBy( int dx, int dy )
{
QGraphicsView::scrollContentsBy( dx, dy );
viewChanged();
}
void QgsLayoutView::dragEnterEvent( QDragEnterEvent *e )
{
// By default graphics view delegates the drag events to graphics items.
// But we do not want that and by ignoring the drag enter we let the
// parent (e.g. QgsLayoutDesignerDialog) to handle drops of files.
e->ignore();
}
void QgsLayoutView::paintEvent( QPaintEvent *event )
{
if ( mPaintingEnabled )
{
QGraphicsView::paintEvent( event );
event->accept();
}
else
{
event->ignore();
}
}
void QgsLayoutView::invalidateCachedRenders()
{
if ( !currentLayout() )
return;
//redraw cached map items
QList< QgsLayoutItem *> items;
currentLayout()->layoutItems( items );
for ( QgsLayoutItem *item : std::as_const( items ) )
{
item->invalidateCache();
}
}
void QgsLayoutView::viewChanged()
{
if ( mHorizontalRuler )
{
mHorizontalRuler->setSceneTransform( viewportTransform() );
}
if ( mVerticalRuler )
{
mVerticalRuler->setSceneTransform( viewportTransform() );
}
// determine page at center of view
QRect viewportRect( 0, 0, viewport()->width(), viewport()->height() );
QRectF visibleRect = mapToScene( viewportRect ).boundingRect();
QPointF centerVisible = visibleRect.center();
if ( currentLayout() && currentLayout()->pageCollection() )
{
int newPage = currentLayout()->pageCollection()->pageNumberForPoint( centerVisible );
if ( newPage != mCurrentPage )
{
mCurrentPage = newPage;
emit pageChanged( mCurrentPage );
}
}
}
void QgsLayoutView::pushStatusMessage( const QString &message )
{
emit statusMessage( message );
}
void QgsLayoutView::wheelZoom( QWheelEvent *event )
{
//get mouse wheel zoom behavior settings
QgsSettings settings;
double zoomFactor = settings.value( QStringLiteral( "qgis/zoom_factor" ), 2 ).toDouble();
// "Normal" mouse have an angle delta of 120, precision mouses provide data faster, in smaller steps
zoomFactor = 1.0 + ( zoomFactor - 1.0 ) / 120.0 * std::fabs( event->angleDelta().y() );
if ( event->modifiers() & Qt::ControlModifier )
{
//holding ctrl while wheel zooming results in a finer zoom
zoomFactor = 1.0 + ( zoomFactor - 1.0 ) / 20.0;
}
//calculate zoom scale factor
bool zoomIn = event->angleDelta().y() > 0;
double scaleFactor = ( zoomIn ? 1 / zoomFactor : zoomFactor );
//get current visible part of scene
QRect viewportRect( 0, 0, viewport()->width(), viewport()->height() );
QgsRectangle visibleRect = QgsRectangle( mapToScene( viewportRect ).boundingRect() );
//transform the mouse pos to scene coordinates
QPointF scenePoint = mapToScene( event->pos() );
//adjust view center
QgsPointXY oldCenter( visibleRect.center() );
QgsPointXY newCenter( scenePoint.x() + ( ( oldCenter.x() - scenePoint.x() ) * scaleFactor ),
scenePoint.y() + ( ( oldCenter.y() - scenePoint.y() ) * scaleFactor ) );
centerOn( newCenter.x(), newCenter.y() );
//zoom layout
if ( zoomIn )
{
scaleSafe( zoomFactor );
}
else
{
scaleSafe( 1 / zoomFactor );
}
}
QGraphicsLineItem *QgsLayoutView::createSnapLine() const
{
std::unique_ptr< QGraphicsLineItem> item( new QGraphicsLineItem( nullptr ) );
QPen pen = QPen( QColor( Qt::blue ) );
pen.setStyle( Qt::DotLine );
pen.setWidthF( 0.0 );
item->setPen( pen );
item->setZValue( QgsLayout::ZSmartGuide );
return item.release();
}
//
// QgsLayoutViewSnapMarker
//
///@cond PRIVATE
QgsLayoutViewSnapMarker::QgsLayoutViewSnapMarker()
: QGraphicsRectItem( QRectF( 0, 0, 0, 0 ) )
{
QFont f;
QFontMetrics fm( f );
mSize = fm.horizontalAdvance( 'X' );
setPen( QPen( Qt::transparent, mSize ) );
setFlags( flags() | QGraphicsItem::ItemIgnoresTransformations );
setZValue( QgsLayout::ZSnapIndicator );
}
void QgsLayoutViewSnapMarker::paint( QPainter *p, const QStyleOptionGraphicsItem *, QWidget * )
{
QPen pen( QColor( 255, 0, 0 ) );
pen.setWidth( 0 );
p->setPen( pen );
p->setBrush( Qt::NoBrush );
double halfSize = mSize / 2.0;
p->drawLine( QLineF( -halfSize, -halfSize, halfSize, halfSize ) );
p->drawLine( QLineF( -halfSize, halfSize, halfSize, -halfSize ) );
}
///@endcond