30 #include <QDragEnterEvent>
32 #include <QApplication>
39 #define MIN_VIEW_SCALE 0.05
40 #define MAX_VIEW_SCALE 1000.0
42 QgsModelGraphicsView::QgsModelGraphicsView( QWidget *parent )
43 : QGraphicsView( parent )
45 setResizeAnchor( QGraphicsView::AnchorViewCenter );
46 setMouseTracking(
true );
47 viewport()->setMouseTracking(
true );
48 setAcceptDrops(
true );
54 mSnapper.setSnapToGrid(
true );
57 QgsModelGraphicsView::~QgsModelGraphicsView()
62 void QgsModelGraphicsView::dragEnterEvent( QDragEnterEvent *event )
64 if ( event->mimeData()->hasText() || event->mimeData()->hasFormat( QStringLiteral(
"application/x-vnd.qgis.qgis.algorithmid" ) ) )
65 event->acceptProposedAction();
70 void QgsModelGraphicsView::dropEvent( QDropEvent *event )
72 const QPointF dropPoint = mapToScene( event->pos() );
73 if ( event->mimeData()->hasFormat( QStringLiteral(
"application/x-vnd.qgis.qgis.algorithmid" ) ) )
75 QByteArray data =
event->mimeData()->data( QStringLiteral(
"application/x-vnd.qgis.qgis.algorithmid" ) );
76 QDataStream stream( &data, QIODevice::ReadOnly );
78 stream >> algorithmId;
80 QTimer::singleShot( 0,
this, [
this, dropPoint, algorithmId ]
82 emit algorithmDropped( algorithmId, dropPoint );
86 else if ( event->mimeData()->hasText() )
88 const QString itemId =
event->mimeData()->text();
89 QTimer::singleShot( 0,
this, [
this, dropPoint, itemId ]
91 emit inputDropped( itemId, dropPoint );
101 void QgsModelGraphicsView::dragMoveEvent( QDragMoveEvent *event )
103 if ( event->mimeData()->hasText() || event->mimeData()->hasFormat( QStringLiteral(
"application/x-vnd.qgis.qgis.algorithmid" ) ) )
104 event->acceptProposedAction();
109 void QgsModelGraphicsView::wheelEvent( QWheelEvent *event )
116 mTool->wheelEvent( event );
119 if ( !mTool || !event->isAccepted() )
126 void QgsModelGraphicsView::wheelZoom( QWheelEvent *event )
130 double zoomFactor = settings.
value( QStringLiteral(
"qgis/zoom_factor" ), 2 ).toDouble();
133 zoomFactor = 1.0 + ( zoomFactor - 1.0 ) / 120.0 * std::fabs( event->angleDelta().y() );
135 if ( event->modifiers() & Qt::ControlModifier )
138 zoomFactor = 1.0 + ( zoomFactor - 1.0 ) / 20.0;
142 bool zoomIn =
event->angleDelta().y() > 0;
143 double scaleFactor = ( zoomIn ? 1 / zoomFactor : zoomFactor );
146 QRect viewportRect( 0, 0, viewport()->width(), viewport()->height() );
150 #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
151 QPointF scenePoint = mapToScene( event->pos().x(), event->pos().y() );
153 QPointF scenePoint = mapToScene( event->position().x(), event->position().y() );
158 QgsPointXY newCenter( scenePoint.x() + ( ( oldCenter.x() - scenePoint.x() ) * scaleFactor ),
159 scenePoint.y() + ( ( oldCenter.y() - scenePoint.y() ) * scaleFactor ) );
160 centerOn( newCenter.x(), newCenter.y() );
165 scaleSafe( zoomFactor );
169 scaleSafe( 1 / zoomFactor );
173 void QgsModelGraphicsView::scaleSafe(
double scale )
175 double currentScale = transform().m11();
176 scale *= currentScale;
178 setTransform( QTransform::fromScale( scale, scale ) );
181 QPointF QgsModelGraphicsView::deltaForKeyEvent( QKeyEvent *event )
184 double increment = 1.0;
185 if ( event->modifiers() & Qt::ShiftModifier )
190 else if ( event->modifiers() & Qt::AltModifier )
193 double viewScale = transform().m11();
196 increment = 1 / viewScale;
202 switch ( event->key() )
220 return QPointF( deltaX, deltaY );
223 void QgsModelGraphicsView::mousePressEvent( QMouseEvent *event )
231 mTool->modelPressEvent( me.get() );
232 event->setAccepted( me->isAccepted() );
235 if ( !mTool || !event->isAccepted() )
237 if ( event->button() == Qt::MiddleButton )
240 setTool( mMidMouseButtonPanTool );
245 QGraphicsView::mousePressEvent( event );
250 void QgsModelGraphicsView::mouseReleaseEvent( QMouseEvent *event )
258 mTool->modelReleaseEvent( me.get() );
259 event->setAccepted( me->isAccepted() );
262 if ( !mTool || !event->isAccepted() )
263 QGraphicsView::mouseReleaseEvent( event );
266 void QgsModelGraphicsView::mouseMoveEvent( QMouseEvent *event )
271 mMouseCurrentXY =
event->pos();
273 QPointF cursorPos = mapToScene( mMouseCurrentXY );
284 if ( me->isSnapped() )
286 cursorPos = me->snappedPoint();
289 mSnapMarker->setPos( me->snappedPoint() );
290 mSnapMarker->setVisible(
true );
293 else if ( mSnapMarker )
295 mSnapMarker->setVisible(
false );
298 mTool->modelMoveEvent( me.get() );
299 event->setAccepted( me->isAccepted() );
302 if ( !mTool || !event->isAccepted() )
303 QGraphicsView::mouseMoveEvent( event );
306 void QgsModelGraphicsView::mouseDoubleClickEvent( QMouseEvent *event )
314 mTool->modelDoubleClickEvent( me.get() );
315 event->setAccepted( me->isAccepted() );
318 if ( !mTool || !event->isAccepted() )
319 QGraphicsView::mouseDoubleClickEvent( event );
322 void QgsModelGraphicsView::keyPressEvent( QKeyEvent *event )
329 mTool->keyPressEvent( event );
332 if ( mTool && event->isAccepted() )
335 if ( event->key() == Qt::Key_Space && ! event->isAutoRepeat() )
337 if ( !( event->modifiers() & Qt::ControlModifier ) )
340 setTool( mSpacePanTool );
345 setTool( mSpaceZoomTool );
349 else if ( event->key() == Qt::Key_Left
350 || event->key() == Qt::Key_Right
351 || event->key() == Qt::Key_Up
352 || event->key() == Qt::Key_Down )
354 QgsModelGraphicsScene *s = modelScene();
355 const QList<QgsModelComponentGraphicItem *> itemList = s->selectedComponentItems();
356 if ( !itemList.empty() )
358 QPointF delta = deltaForKeyEvent( event );
360 itemList.at( 0 )->aboutToChange( tr(
"Move Items" ) );
361 for ( QgsModelComponentGraphicItem *item : itemList )
363 item->moveComponentBy( delta.x(), delta.y() );
365 itemList.at( 0 )->changed();
371 void QgsModelGraphicsView::keyReleaseEvent( QKeyEvent *event )
378 mTool->keyReleaseEvent( event );
381 if ( !mTool || !event->isAccepted() )
382 QGraphicsView::keyReleaseEvent( event );
385 void QgsModelGraphicsView::setModelScene( QgsModelGraphicsScene *scene )
391 mSnapMarker =
new QgsModelViewSnapMarker();
393 scene->addItem( mSnapMarker );
396 QgsModelGraphicsScene *QgsModelGraphicsView::modelScene()
const
398 return qobject_cast< QgsModelGraphicsScene * >( QgsModelGraphicsView::scene() );
422 emit toolSet( mTool );
427 if ( mTool && mTool == tool )
430 emit toolSet(
nullptr );
431 setCursor( Qt::ArrowCursor );
440 void QgsModelGraphicsView::startMacroCommand(
const QString &text )
442 emit macroCommandStarted( text );
445 void QgsModelGraphicsView::endMacroCommand()
447 emit macroCommandEnded();
450 void QgsModelGraphicsView::snapSelected()
452 QgsModelGraphicsScene *s = modelScene();
453 const QList<QgsModelComponentGraphicItem *> itemList = s->selectedComponentItems();
454 startMacroCommand( tr(
"Snap Items" ) );
455 if ( !itemList.empty() )
457 bool prevSetting = mSnapper.snapToGrid();
458 mSnapper.setSnapToGrid(
true );
459 for ( QgsModelComponentGraphicItem *item : itemList )
461 bool wasSnapped =
false;
462 QRectF snapped = mSnapper.snapRectWithResize( item->mapRectToScene( item->itemRect( ) ), transform().m11(), wasSnapped );
465 item->setItemRect( snapped );
468 mSnapper.setSnapToGrid( prevSetting );
473 void QgsModelGraphicsView::copySelectedItems( QgsModelGraphicsView::ClipboardOperation operation )
475 copyItems( modelScene()->selectedComponentItems(), operation );
478 void QgsModelGraphicsView::copyItems(
const QList<QgsModelComponentGraphicItem *> &items, QgsModelGraphicsView::ClipboardOperation operation )
485 QDomElement documentElement = doc.createElement( QStringLiteral(
"ModelComponentClipboard" ) );
486 if ( operation == ClipboardCut )
488 emit macroCommandStarted( tr(
"Cut Items" ) );
489 emit beginCommand( QString() );
492 QList< QVariant > paramComponents;
493 QList< QVariant > groupBoxComponents;
494 QList< QVariant > algComponents;
496 QList< QgsModelComponentGraphicItem * > selectedCommentParents;
497 QList< QgsProcessingModelOutput > selectedOutputs;
498 QList< QgsProcessingModelOutput > selectedOutputsComments;
499 for ( QgsModelComponentGraphicItem *item : items )
501 if (
const QgsModelCommentGraphicItem *commentItem =
dynamic_cast< QgsModelCommentGraphicItem *
>( item ) )
503 selectedCommentParents << commentItem->parentComponentItem();
504 if (
const QgsModelOutputGraphicItem *outputItem =
dynamic_cast< QgsModelOutputGraphicItem *
>( commentItem->parentComponentItem() ) )
506 selectedOutputsComments << *( static_cast< const QgsProcessingModelOutput *>( outputItem->component() ) );
509 else if (
const QgsModelOutputGraphicItem *outputItem =
dynamic_cast< QgsModelOutputGraphicItem *
>( item ) )
511 selectedOutputs << *( static_cast< const QgsProcessingModelOutput *>( outputItem->component() ) );
515 for ( QgsModelComponentGraphicItem *item : items )
517 if (
const QgsProcessingModelParameter *param =
dynamic_cast< QgsProcessingModelParameter *
>( item->component() ) )
519 QgsProcessingModelParameter component = *param;
522 if ( !selectedCommentParents.contains( item ) )
525 component.comment()->setDescription( QString() );
528 QVariantMap paramDef;
529 paramDef.insert( QStringLiteral(
"component" ), component.toVariant() );
531 paramDef.insert( QStringLiteral(
"definition" ), def->
toVariantMap() );
533 paramComponents << paramDef;
535 else if ( QgsProcessingModelGroupBox *groupBox =
dynamic_cast< QgsProcessingModelGroupBox *
>( item->component() ) )
537 groupBoxComponents << groupBox->toVariant();
539 else if (
const QgsProcessingModelChildAlgorithm *alg =
dynamic_cast< QgsProcessingModelChildAlgorithm *
>( item->component() ) )
541 QgsProcessingModelChildAlgorithm childAlg = *alg;
544 if ( !selectedCommentParents.contains( item ) )
551 QMap<QString, QgsProcessingModelOutput> clipboardOutputs;
552 const QMap<QString, QgsProcessingModelOutput> existingOutputs = childAlg.modelOutputs();
553 for (
auto it = existingOutputs.constBegin(); it != existingOutputs.constEnd(); ++ it )
556 for (
const QgsProcessingModelOutput &candidate : selectedOutputs )
558 if ( candidate.childId() == childAlg.childId() && candidate.name() == it.value().name() && candidate.childOutputName() == it.value().childOutputName() )
567 bool commentFound =
false;
568 for (
const QgsProcessingModelOutput &candidate : selectedOutputsComments )
570 if ( candidate.childId() == childAlg.childId() && candidate.name() == it.value().name() && candidate.childOutputName() == it.value().childOutputName() )
577 QgsProcessingModelOutput output = it.value();
579 output.comment()->setDescription( QString() );
581 clipboardOutputs.insert( it.key(), output );
584 childAlg.setModelOutputs( clipboardOutputs );
586 algComponents << childAlg.toVariant();
589 QVariantMap components;
590 components.insert( QStringLiteral(
"parameters" ), paramComponents );
591 components.insert( QStringLiteral(
"groupboxes" ), groupBoxComponents );
592 components.insert( QStringLiteral(
"algs" ), algComponents );
594 if ( operation == ClipboardCut )
596 emit deleteSelectedItems();
598 emit macroCommandEnded();
601 QMimeData *mimeData =
new QMimeData;
602 mimeData->setData( QStringLiteral(
"text/xml" ), doc.toByteArray() );
603 mimeData->setText( doc.toByteArray() );
604 QClipboard *clipboard = QApplication::clipboard();
605 clipboard->setMimeData( mimeData );
608 void QgsModelGraphicsView::pasteItems( QgsModelGraphicsView::PasteMode mode )
614 QClipboard *clipboard = QApplication::clipboard();
615 if ( doc.setContent( clipboard->mimeData()->data( QStringLiteral(
"text/xml" ) ) ) )
617 QDomElement docElem = doc.documentElement();
620 if ( res.contains( QStringLiteral(
"parameters" ) ) && res.contains( QStringLiteral(
"algs" ) ) )
625 case PasteModeCursor:
626 case PasteModeInPlace:
629 pt = mapToScene( mapFromGlobal( QCursor::pos() ) );
632 case PasteModeCenter:
635 pt = mapToScene( viewport()->rect().center() );
640 emit beginCommand( tr(
"Paste Items" ) );
644 QList< QgsProcessingModelGroupBox > pastedGroups;
645 for (
const QVariant &v : res.value( QStringLiteral(
"groupboxes" ) ).toList() )
647 QgsProcessingModelGroupBox box;
649 box.loadVariant( v.toMap(),
true );
653 modelScene()->model()->addGroupBox( box );
655 if ( !pastedBounds.isValid( ) )
656 pastedBounds = QRectF( box.position() - QPointF( box.size().width() / 2.0, box.size().height() / 2.0 ), box.size() );
658 pastedBounds = pastedBounds.united( QRectF( box.position() - QPointF( box.size().width() / 2.0, box.size().height() / 2.0 ), box.size() ) );
661 QStringList pastedParameters;
662 for (
const QVariant &v : res.value( QStringLiteral(
"parameters" ) ).toList() )
664 QVariantMap param = v.toMap();
665 QVariantMap componentDef = param.value( QStringLiteral(
"component" ) ).toMap();
666 QVariantMap paramDef = param.value( QStringLiteral(
"definition" ) ).toMap();
670 QgsProcessingModelParameter p;
671 p.loadVariant( componentDef );
674 QString name = p.parameterName();
675 QString description = paramDefinition->description();
677 while ( modelScene()->model()->parameterDefinition( name ) )
680 name = QStringLiteral(
"%1 (%2)" ).arg( p.parameterName() ).arg( next );
681 description = QStringLiteral(
"%1 (%2)" ).arg( paramDefinition->description() ).arg( next );
683 paramDefinition->setName( name );
684 paramDefinition->setDescription( description );
685 p.setParameterName( name );
687 modelScene()->model()->addModelParameter( paramDefinition.release(), p );
688 pastedParameters << p.parameterName();
690 if ( !pastedBounds.isValid( ) )
691 pastedBounds = QRectF( p.position() - QPointF( p.size().width() / 2.0, p.size().height() / 2.0 ), p.size() );
693 pastedBounds = pastedBounds.united( QRectF( p.position() - QPointF( p.size().width() / 2.0, p.size().height() / 2.0 ), p.size() ) );
695 if ( !p.comment()->description().isEmpty() )
696 pastedBounds = pastedBounds.united( QRectF( p.comment()->position() - QPointF( p.comment()->size().width() / 2.0, p.comment()->size().height() / 2.0 ), p.comment()->size() ) );
699 QStringList pastedAlgorithms;
700 for (
const QVariant &v : res.value( QStringLiteral(
"algs" ) ).toList() )
702 QgsProcessingModelChildAlgorithm alg;
703 alg.loadVariant( v.toMap() );
706 if ( modelScene()->model()->childAlgorithms().contains( alg.childId() ) )
708 alg.generateChildId( *modelScene()->model() );
712 pastedAlgorithms << alg.childId();
714 if ( !pastedBounds.isValid( ) )
715 pastedBounds = QRectF( alg.position() - QPointF( alg.size().width() / 2.0, alg.size().height() / 2.0 ), alg.size() );
717 pastedBounds = pastedBounds.united( QRectF( alg.position() - QPointF( alg.size().width() / 2.0, alg.size().height() / 2.0 ), alg.size() ) );
719 if ( !alg.comment()->description().isEmpty() )
720 pastedBounds = pastedBounds.united( QRectF( alg.comment()->position() - QPointF( alg.comment()->size().width() / 2.0, alg.comment()->size().height() / 2.0 ), alg.comment()->size() ) );
722 const QMap<QString, QgsProcessingModelChildAlgorithm> existingAlgs = modelScene()->model()->childAlgorithms();
724 const QMap<QString, QgsProcessingModelOutput> outputs = alg.modelOutputs();
725 QMap<QString, QgsProcessingModelOutput> pastedOutputs;
726 for (
auto it = outputs.constBegin(); it != outputs.constEnd(); ++it )
728 QString name = it.value().name();
734 for (
auto algIt = existingAlgs.constBegin(); algIt != existingAlgs.constEnd(); ++algIt )
736 const QMap<QString, QgsProcessingModelOutput> algOutputs = algIt->modelOutputs();
737 for (
auto outputIt = algOutputs.constBegin(); outputIt != algOutputs.constEnd(); ++outputIt )
739 if ( outputIt.value().name() == name )
751 name = QStringLiteral(
"%1 (%2)" ).arg( it.value().name() ).arg( next );
754 QgsProcessingModelOutput newOutput = it.value();
755 newOutput.setName( name );
756 newOutput.setDescription( name );
757 pastedOutputs.insert( name, newOutput );
759 pastedBounds = pastedBounds.united( QRectF( newOutput.position() - QPointF( newOutput.size().width() / 2.0, newOutput.size().height() / 2.0 ), newOutput.size() ) );
761 if ( !alg.comment()->description().isEmpty() )
762 pastedBounds = pastedBounds.united( QRectF( newOutput.comment()->position() - QPointF( newOutput.comment()->size().width() / 2.0, newOutput.comment()->size().height() / 2.0 ), newOutput.comment()->size() ) );
764 alg.setModelOutputs( pastedOutputs );
766 modelScene()->model()->addChildAlgorithm( alg );
769 QPointF offset( 0, 0 );
772 case PasteModeInPlace:
775 case PasteModeCursor:
776 case PasteModeCenter:
778 offset = pt - pastedBounds.topLeft();
783 if ( !offset.isNull() )
785 for ( QgsProcessingModelGroupBox pastedGroup : std::as_const( pastedGroups ) )
787 pastedGroup.setPosition( pastedGroup.position() + offset );
788 modelScene()->model()->addGroupBox( pastedGroup );
790 for (
const QString &pastedParam : std::as_const( pastedParameters ) )
792 modelScene()->model()->parameterComponent( pastedParam ).setPosition( modelScene()->model()->parameterComponent( pastedParam ).position() + offset );
793 modelScene()->model()->parameterComponent( pastedParam ).comment()->setPosition( modelScene()->model()->parameterComponent( pastedParam ).comment()->position() + offset );
795 for (
const QString &pastedAlg : std::as_const( pastedAlgorithms ) )
797 modelScene()->model()->childAlgorithm( pastedAlg ).setPosition( modelScene()->model()->childAlgorithm( pastedAlg ).position() + offset );
798 modelScene()->model()->childAlgorithm( pastedAlg ).comment()->setPosition( modelScene()->model()->childAlgorithm( pastedAlg ).comment()->position() + offset );
800 const QMap<QString, QgsProcessingModelOutput> outputs = modelScene()->model()->childAlgorithm( pastedAlg ).modelOutputs();
801 for (
auto it = outputs.begin(); it != outputs.end(); ++it )
803 modelScene()->model()->childAlgorithm( pastedAlg ).modelOutput( it.key() ).setPosition( modelScene()->model()->childAlgorithm( pastedAlg ).modelOutput( it.key() ).position() + offset );
804 modelScene()->model()->childAlgorithm( pastedAlg ).modelOutput( it.key() ).comment()->setPosition( modelScene()->model()->childAlgorithm( pastedAlg ).modelOutput( it.key() ).comment()->position() + offset );
813 modelScene()->rebuildRequired();
816 QgsModelViewSnapMarker::QgsModelViewSnapMarker()
817 : QGraphicsRectItem( QRectF( 0, 0, 0, 0 ) )
820 QFontMetrics fm( f );
821 mSize = fm.horizontalAdvance(
'X' );
822 setPen( QPen( Qt::transparent, mSize ) );
824 setFlags( flags() | QGraphicsItem::ItemIgnoresTransformations );
825 setZValue( QgsModelGraphicsScene::ZSnapIndicator );
828 void QgsModelViewSnapMarker::paint( QPainter *p,
const QStyleOptionGraphicsItem *, QWidget * )
830 QPen pen( QColor( 255, 0, 0 ) );
833 p->setBrush( Qt::NoBrush );
835 double halfSize = mSize / 2.0;
836 p->drawLine( QLineF( -halfSize, -halfSize, halfSize, halfSize ) );
837 p->drawLine( QLineF( -halfSize, halfSize, halfSize, -halfSize ) );