25 #include "qgsprocessingmodelcomponent.h"
26 #include "qgsprocessingmodelparameter.h"
27 #include "qgsprocessingmodelchildalgorithm.h"
29 #include "qgsprocessingmodelalgorithm.h"
30 #include <QDragEnterEvent>
32 #include <QApplication>
37 #define MIN_VIEW_SCALE 0.05
38 #define MAX_VIEW_SCALE 1000.0
40 QgsModelGraphicsView::QgsModelGraphicsView( QWidget *parent )
41 : QGraphicsView( parent )
43 setResizeAnchor( QGraphicsView::AnchorViewCenter );
44 setMouseTracking(
true );
45 viewport()->setMouseTracking(
true );
46 setAcceptDrops(
true );
52 mSnapper.setSnapToGrid(
true );
55 QgsModelGraphicsView::~QgsModelGraphicsView()
60 void QgsModelGraphicsView::dragEnterEvent( QDragEnterEvent *event )
62 if ( event->mimeData()->hasText() || event->mimeData()->hasFormat( QStringLiteral(
"application/x-vnd.qgis.qgis.algorithmid" ) ) )
63 event->acceptProposedAction();
68 void QgsModelGraphicsView::dropEvent( QDropEvent *event )
70 const QPointF dropPoint = mapToScene( event->pos() );
71 if ( event->mimeData()->hasFormat( QStringLiteral(
"application/x-vnd.qgis.qgis.algorithmid" ) ) )
73 QByteArray data =
event->mimeData()->data( QStringLiteral(
"application/x-vnd.qgis.qgis.algorithmid" ) );
74 QDataStream stream( &data, QIODevice::ReadOnly );
76 stream >> algorithmId;
78 QTimer::singleShot( 0,
this, [
this, dropPoint, algorithmId ]
80 emit algorithmDropped( algorithmId, dropPoint );
84 else if ( event->mimeData()->hasText() )
86 const QString itemId =
event->mimeData()->text();
87 QTimer::singleShot( 0,
this, [
this, dropPoint, itemId ]
89 emit inputDropped( itemId, dropPoint );
99 void QgsModelGraphicsView::dragMoveEvent( QDragMoveEvent *event )
101 if ( event->mimeData()->hasText() || event->mimeData()->hasFormat( QStringLiteral(
"application/x-vnd.qgis.qgis.algorithmid" ) ) )
102 event->acceptProposedAction();
107 void QgsModelGraphicsView::wheelEvent( QWheelEvent *event )
114 mTool->wheelEvent( event );
117 if ( !mTool || !event->isAccepted() )
124 void QgsModelGraphicsView::wheelZoom( QWheelEvent *event )
128 double zoomFactor = settings.
value( QStringLiteral(
"qgis/zoom_factor" ), 2 ).toDouble();
131 zoomFactor = 1.0 + ( zoomFactor - 1.0 ) / 120.0 * std::fabs( event->angleDelta().y() );
133 if ( event->modifiers() & Qt::ControlModifier )
136 zoomFactor = 1.0 + ( zoomFactor - 1.0 ) / 20.0;
140 bool zoomIn =
event->angleDelta().y() > 0;
141 double scaleFactor = ( zoomIn ? 1 / zoomFactor : zoomFactor );
144 QRect viewportRect( 0, 0, viewport()->width(), viewport()->height() );
148 QPointF scenePoint = mapToScene( event->pos() );
152 QgsPointXY newCenter( scenePoint.x() + ( ( oldCenter.x() - scenePoint.x() ) * scaleFactor ),
153 scenePoint.y() + ( ( oldCenter.y() - scenePoint.y() ) * scaleFactor ) );
154 centerOn( newCenter.x(), newCenter.y() );
159 scaleSafe( zoomFactor );
163 scaleSafe( 1 / zoomFactor );
167 void QgsModelGraphicsView::scaleSafe(
double scale )
169 double currentScale = transform().m11();
170 scale *= currentScale;
172 setTransform( QTransform::fromScale( scale, scale ) );
175 QPointF QgsModelGraphicsView::deltaForKeyEvent( QKeyEvent *event )
178 double increment = 1.0;
179 if ( event->modifiers() & Qt::ShiftModifier )
184 else if ( event->modifiers() & Qt::AltModifier )
187 double viewScale = transform().m11();
190 increment = 1 / viewScale;
196 switch ( event->key() )
214 return QPointF( deltaX, deltaY );
217 void QgsModelGraphicsView::mousePressEvent( QMouseEvent *event )
225 mTool->modelPressEvent( me.get() );
226 event->setAccepted( me->isAccepted() );
229 if ( !mTool || !event->isAccepted() )
231 if ( event->button() == Qt::MidButton )
234 setTool( mMidMouseButtonPanTool );
239 QGraphicsView::mousePressEvent( event );
244 void QgsModelGraphicsView::mouseReleaseEvent( QMouseEvent *event )
252 mTool->modelReleaseEvent( me.get() );
253 event->setAccepted( me->isAccepted() );
256 if ( !mTool || !event->isAccepted() )
257 QGraphicsView::mouseReleaseEvent( event );
260 void QgsModelGraphicsView::mouseMoveEvent( QMouseEvent *event )
265 mMouseCurrentXY =
event->pos();
267 QPointF cursorPos = mapToScene( mMouseCurrentXY );
278 if ( me->isSnapped() )
280 cursorPos = me->snappedPoint();
283 mSnapMarker->setPos( me->snappedPoint() );
284 mSnapMarker->setVisible(
true );
287 else if ( mSnapMarker )
289 mSnapMarker->setVisible(
false );
292 mTool->modelMoveEvent( me.get() );
293 event->setAccepted( me->isAccepted() );
296 if ( !mTool || !event->isAccepted() )
297 QGraphicsView::mouseMoveEvent( event );
300 void QgsModelGraphicsView::mouseDoubleClickEvent( QMouseEvent *event )
308 mTool->modelDoubleClickEvent( me.get() );
309 event->setAccepted( me->isAccepted() );
312 if ( !mTool || !event->isAccepted() )
313 QGraphicsView::mouseDoubleClickEvent( event );
316 void QgsModelGraphicsView::keyPressEvent( QKeyEvent *event )
323 mTool->keyPressEvent( event );
326 if ( mTool && event->isAccepted() )
329 if ( event->key() == Qt::Key_Space && ! event->isAutoRepeat() )
331 if ( !( event->modifiers() & Qt::ControlModifier ) )
334 setTool( mSpacePanTool );
339 setTool( mSpaceZoomTool );
343 else if ( event->key() == Qt::Key_Left
344 || event->key() == Qt::Key_Right
345 || event->key() == Qt::Key_Up
346 || event->key() == Qt::Key_Down )
348 QgsModelGraphicsScene *s = modelScene();
349 const QList<QgsModelComponentGraphicItem *> itemList = s->selectedComponentItems();
350 if ( !itemList.empty() )
352 QPointF delta = deltaForKeyEvent( event );
354 itemList.at( 0 )->aboutToChange( tr(
"Move Items" ) );
355 for ( QgsModelComponentGraphicItem *item : itemList )
357 item->moveComponentBy( delta.x(), delta.y() );
359 itemList.at( 0 )->changed();
365 void QgsModelGraphicsView::keyReleaseEvent( QKeyEvent *event )
372 mTool->keyReleaseEvent( event );
375 if ( !mTool || !event->isAccepted() )
376 QGraphicsView::keyReleaseEvent( event );
379 void QgsModelGraphicsView::setModelScene( QgsModelGraphicsScene *scene )
385 mSnapMarker =
new QgsModelViewSnapMarker();
387 scene->addItem( mSnapMarker );
390 QgsModelGraphicsScene *QgsModelGraphicsView::modelScene()
const
392 return qobject_cast< QgsModelGraphicsScene * >( QgsModelGraphicsView::scene() );
416 emit toolSet( mTool );
421 if ( mTool && mTool == tool )
424 emit toolSet(
nullptr );
425 setCursor( Qt::ArrowCursor );
434 void QgsModelGraphicsView::startMacroCommand(
const QString &text )
436 emit macroCommandStarted( text );
439 void QgsModelGraphicsView::endMacroCommand()
441 emit macroCommandEnded();
444 void QgsModelGraphicsView::snapSelected()
446 QgsModelGraphicsScene *s = modelScene();
447 const QList<QgsModelComponentGraphicItem *> itemList = s->selectedComponentItems();
448 startMacroCommand( tr(
"Snap Items" ) );
449 if ( !itemList.empty() )
451 bool prevSetting = mSnapper.snapToGrid();
452 mSnapper.setSnapToGrid(
true );
453 for ( QgsModelComponentGraphicItem *item : itemList )
455 bool wasSnapped =
false;
456 QRectF snapped = mSnapper.snapRectWithResize( item->mapRectToScene( item->itemRect( ) ), transform().m11(), wasSnapped );
459 item->setItemRect( snapped );
462 mSnapper.setSnapToGrid( prevSetting );
467 void QgsModelGraphicsView::copySelectedItems( QgsModelGraphicsView::ClipboardOperation operation )
469 copyItems( modelScene()->selectedComponentItems(), operation );
472 void QgsModelGraphicsView::copyItems(
const QList<QgsModelComponentGraphicItem *> &items, QgsModelGraphicsView::ClipboardOperation operation )
479 QDomElement documentElement = doc.createElement( QStringLiteral(
"ModelComponentClipboard" ) );
480 if ( operation == ClipboardCut )
482 emit macroCommandStarted( tr(
"Cut Items" ) );
483 emit beginCommand( QString() );
486 QList< QVariant > paramComponents;
487 QList< QVariant > groupBoxComponents;
488 QList< QVariant > algComponents;
490 QList< QgsModelComponentGraphicItem * > selectedCommentParents;
491 QList< QgsProcessingModelOutput > selectedOutputs;
492 QList< QgsProcessingModelOutput > selectedOutputsComments;
493 for ( QgsModelComponentGraphicItem *item : items )
495 if (
const QgsModelCommentGraphicItem *commentItem =
dynamic_cast< QgsModelCommentGraphicItem *
>( item ) )
497 selectedCommentParents << commentItem->parentComponentItem();
498 if (
const QgsModelOutputGraphicItem *outputItem =
dynamic_cast< QgsModelOutputGraphicItem *
>( commentItem->parentComponentItem() ) )
500 selectedOutputsComments << *( static_cast< const QgsProcessingModelOutput *>( outputItem->component() ) );
503 else if (
const QgsModelOutputGraphicItem *outputItem =
dynamic_cast< QgsModelOutputGraphicItem *
>( item ) )
505 selectedOutputs << *( static_cast< const QgsProcessingModelOutput *>( outputItem->component() ) );
509 for ( QgsModelComponentGraphicItem *item : items )
511 if (
const QgsProcessingModelParameter *param =
dynamic_cast< QgsProcessingModelParameter *
>( item->component() ) )
513 QgsProcessingModelParameter component = *param;
516 if ( !selectedCommentParents.contains( item ) )
519 component.comment()->setDescription( QString() );
522 QVariantMap paramDef;
523 paramDef.insert( QStringLiteral(
"component" ), component.toVariant() );
525 paramDef.insert( QStringLiteral(
"definition" ), def->
toVariantMap() );
527 paramComponents << paramDef;
529 else if ( QgsProcessingModelGroupBox *groupBox =
dynamic_cast< QgsProcessingModelGroupBox *
>( item->component() ) )
531 groupBoxComponents << groupBox->toVariant();
533 else if (
const QgsProcessingModelChildAlgorithm *alg =
dynamic_cast< QgsProcessingModelChildAlgorithm *
>( item->component() ) )
535 QgsProcessingModelChildAlgorithm childAlg = *alg;
538 if ( !selectedCommentParents.contains( item ) )
545 QMap<QString, QgsProcessingModelOutput> clipboardOutputs;
546 const QMap<QString, QgsProcessingModelOutput> existingOutputs = childAlg.modelOutputs();
547 for (
auto it = existingOutputs.constBegin(); it != existingOutputs.constEnd(); ++ it )
550 for (
const QgsProcessingModelOutput &candidate : selectedOutputs )
552 if ( candidate.childId() == childAlg.childId() && candidate.name() == it.value().name() && candidate.childOutputName() == it.value().childOutputName() )
561 bool commentFound =
false;
562 for (
const QgsProcessingModelOutput &candidate : selectedOutputsComments )
564 if ( candidate.childId() == childAlg.childId() && candidate.name() == it.value().name() && candidate.childOutputName() == it.value().childOutputName() )
571 QgsProcessingModelOutput output = it.value();
573 output.comment()->setDescription( QString() );
575 clipboardOutputs.insert( it.key(), output );
578 childAlg.setModelOutputs( clipboardOutputs );
580 algComponents << childAlg.toVariant();
583 QVariantMap components;
584 components.insert( QStringLiteral(
"parameters" ), paramComponents );
585 components.insert( QStringLiteral(
"groupboxes" ), groupBoxComponents );
586 components.insert( QStringLiteral(
"algs" ), algComponents );
588 if ( operation == ClipboardCut )
590 emit deleteSelectedItems();
592 emit macroCommandEnded();
595 QMimeData *mimeData =
new QMimeData;
596 mimeData->setData( QStringLiteral(
"text/xml" ), doc.toByteArray() );
597 mimeData->setText( doc.toByteArray() );
598 QClipboard *clipboard = QApplication::clipboard();
599 clipboard->setMimeData( mimeData );
602 void QgsModelGraphicsView::pasteItems( QgsModelGraphicsView::PasteMode mode )
607 QList< QgsModelComponentGraphicItem * > pastedItems;
609 QClipboard *clipboard = QApplication::clipboard();
610 if ( doc.setContent( clipboard->mimeData()->data( QStringLiteral(
"text/xml" ) ) ) )
612 QDomElement docElem = doc.documentElement();
615 if ( res.contains( QStringLiteral(
"parameters" ) ) && res.contains( QStringLiteral(
"algs" ) ) )
620 case PasteModeCursor:
621 case PasteModeInPlace:
624 pt = mapToScene( mapFromGlobal( QCursor::pos() ) );
627 case PasteModeCenter:
630 pt = mapToScene( viewport()->rect().center() );
635 emit beginCommand( tr(
"Paste Items" ) );
639 QList< QgsProcessingModelGroupBox > pastedGroups;
640 for (
const QVariant &v : res.value( QStringLiteral(
"groupboxes" ) ).toList() )
642 QgsProcessingModelGroupBox box;
644 box.loadVariant( v.toMap(),
true );
648 modelScene()->model()->addGroupBox( box );
650 if ( !pastedBounds.isValid( ) )
651 pastedBounds = QRectF( box.position() - QPointF( box.size().width() / 2.0, box.size().height() / 2.0 ), box.size() );
653 pastedBounds = pastedBounds.united( QRectF( box.position() - QPointF( box.size().width() / 2.0, box.size().height() / 2.0 ), box.size() ) );
656 QStringList pastedParameters;
657 for (
const QVariant &v : res.value( QStringLiteral(
"parameters" ) ).toList() )
659 QVariantMap param = v.toMap();
660 QVariantMap componentDef = param.value( QStringLiteral(
"component" ) ).toMap();
661 QVariantMap paramDef = param.value( QStringLiteral(
"definition" ) ).toMap();
665 QgsProcessingModelParameter p;
666 p.loadVariant( componentDef );
669 QString name = p.parameterName();
670 QString description = paramDefinition->description();
672 while ( modelScene()->model()->parameterDefinition( name ) )
675 name = QStringLiteral(
"%1 (%2)" ).arg( p.parameterName() ).arg( next );
676 description = QStringLiteral(
"%1 (%2)" ).arg( paramDefinition->description() ).arg( next );
678 paramDefinition->setName( name );
679 paramDefinition->setDescription( description );
680 p.setParameterName( name );
682 modelScene()->model()->addModelParameter( paramDefinition.release(), p );
683 pastedParameters << p.parameterName();
685 if ( !pastedBounds.isValid( ) )
686 pastedBounds = QRectF( p.position() - QPointF( p.size().width() / 2.0, p.size().height() / 2.0 ), p.size() );
688 pastedBounds = pastedBounds.united( QRectF( p.position() - QPointF( p.size().width() / 2.0, p.size().height() / 2.0 ), p.size() ) );
690 if ( !p.comment()->description().isEmpty() )
691 pastedBounds = pastedBounds.united( QRectF( p.comment()->position() - QPointF( p.comment()->size().width() / 2.0, p.comment()->size().height() / 2.0 ), p.comment()->size() ) );
694 QStringList pastedAlgorithms;
695 for (
const QVariant &v : res.value( QStringLiteral(
"algs" ) ).toList() )
697 QgsProcessingModelChildAlgorithm alg;
698 alg.loadVariant( v.toMap() );
701 alg.generateChildId( *modelScene()->model() );
704 pastedAlgorithms << alg.childId();
706 if ( !pastedBounds.isValid( ) )
707 pastedBounds = QRectF( alg.position() - QPointF( alg.size().width() / 2.0, alg.size().height() / 2.0 ), alg.size() );
709 pastedBounds = pastedBounds.united( QRectF( alg.position() - QPointF( alg.size().width() / 2.0, alg.size().height() / 2.0 ), alg.size() ) );
711 if ( !alg.comment()->description().isEmpty() )
712 pastedBounds = pastedBounds.united( QRectF( alg.comment()->position() - QPointF( alg.comment()->size().width() / 2.0, alg.comment()->size().height() / 2.0 ), alg.comment()->size() ) );
714 const QMap<QString, QgsProcessingModelChildAlgorithm> existingAlgs = modelScene()->model()->childAlgorithms();
716 const QMap<QString, QgsProcessingModelOutput> outputs = alg.modelOutputs();
717 QMap<QString, QgsProcessingModelOutput> pastedOutputs;
718 for (
auto it = outputs.constBegin(); it != outputs.constEnd(); ++it )
720 QString name = it.value().name();
726 for (
auto algIt = existingAlgs.constBegin(); algIt != existingAlgs.constEnd(); ++algIt )
728 const QMap<QString, QgsProcessingModelOutput> algOutputs = algIt->modelOutputs();
729 for (
auto outputIt = algOutputs.constBegin(); outputIt != algOutputs.constEnd(); ++outputIt )
731 if ( outputIt.value().name() == name )
743 name = QStringLiteral(
"%1 (%2)" ).arg( it.value().name() ).arg( next );
746 QgsProcessingModelOutput newOutput = it.value();
747 newOutput.setName( name );
748 newOutput.setDescription( name );
749 pastedOutputs.insert( name, newOutput );
751 pastedBounds = pastedBounds.united( QRectF( newOutput.position() - QPointF( newOutput.size().width() / 2.0, newOutput.size().height() / 2.0 ), newOutput.size() ) );
753 if ( !alg.comment()->description().isEmpty() )
754 pastedBounds = pastedBounds.united( QRectF( newOutput.comment()->position() - QPointF( newOutput.comment()->size().width() / 2.0, newOutput.comment()->size().height() / 2.0 ), newOutput.comment()->size() ) );
756 alg.setModelOutputs( pastedOutputs );
758 modelScene()->model()->addChildAlgorithm( alg );
761 QPointF offset( 0, 0 );
764 case PasteModeInPlace:
767 case PasteModeCursor:
768 case PasteModeCenter:
770 offset = pt - pastedBounds.topLeft();
775 if ( !offset.isNull() )
777 for ( QgsProcessingModelGroupBox pastedGroup : qgis::as_const( pastedGroups ) )
779 pastedGroup.setPosition( pastedGroup.position() + offset );
780 modelScene()->model()->addGroupBox( pastedGroup );
782 for (
const QString &pastedParam : qgis::as_const( pastedParameters ) )
784 modelScene()->model()->parameterComponent( pastedParam ).setPosition( modelScene()->model()->parameterComponent( pastedParam ).position() + offset );
785 modelScene()->model()->parameterComponent( pastedParam ).comment()->setPosition( modelScene()->model()->parameterComponent( pastedParam ).comment()->position() + offset );
787 for (
const QString &pastedAlg : qgis::as_const( pastedAlgorithms ) )
789 modelScene()->model()->childAlgorithm( pastedAlg ).setPosition( modelScene()->model()->childAlgorithm( pastedAlg ).position() + offset );
790 modelScene()->model()->childAlgorithm( pastedAlg ).comment()->setPosition( modelScene()->model()->childAlgorithm( pastedAlg ).comment()->position() + offset );
792 const QMap<QString, QgsProcessingModelOutput> outputs = modelScene()->model()->childAlgorithm( pastedAlg ).modelOutputs();
793 for (
auto it = outputs.begin(); it != outputs.end(); ++it )
795 modelScene()->model()->childAlgorithm( pastedAlg ).modelOutput( it.key() ).setPosition( modelScene()->model()->childAlgorithm( pastedAlg ).modelOutput( it.key() ).position() + offset );
796 modelScene()->model()->childAlgorithm( pastedAlg ).modelOutput( it.key() ).comment()->setPosition( modelScene()->model()->childAlgorithm( pastedAlg ).modelOutput( it.key() ).comment()->position() + offset );
805 modelScene()->rebuildRequired();
808 QgsModelViewSnapMarker::QgsModelViewSnapMarker()
809 : QGraphicsRectItem( QRectF( 0, 0, 0, 0 ) )
812 QFontMetrics fm( f );
813 #if QT_VERSION < QT_VERSION_CHECK(5, 11, 0)
814 mSize = fm.width( QStringLiteral(
"X" ) );
816 mSize = fm.horizontalAdvance(
'X' );
818 setPen( QPen( Qt::transparent, mSize ) );
820 setFlags( flags() | QGraphicsItem::ItemIgnoresTransformations );
821 setZValue( QgsModelGraphicsScene::ZSnapIndicator );
824 void QgsModelViewSnapMarker::paint( QPainter *p,
const QStyleOptionGraphicsItem *, QWidget * )
826 QPen pen( QColor( 255, 0, 0 ) );
829 p->setBrush( Qt::NoBrush );
831 double halfSize = mSize / 2.0;
832 p->drawLine( QLineF( -halfSize, -halfSize, halfSize, halfSize ) );
833 p->drawLine( QLineF( -halfSize, halfSize, halfSize, -halfSize ) );