QGIS API Documentation 3.99.0-Master (357b655ed83)
Loading...
Searching...
No Matches
qgsmodelgraphicsview.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsmodelgraphicsview.cpp
3 ----------------------------------
4 Date : March 2020
5 Copyright : (C) 2020 Nyall Dawson
6 Email : nyall dot dawson at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
17
21#include "qgsmodelviewtool.h"
29#include "qgssettings.h"
30#include "qgsxmlutils.h"
31
32#include <QApplication>
33#include <QClipboard>
34#include <QDragEnterEvent>
35#include <QMimeData>
36#include <QScrollBar>
37#include <QString>
38#include <QTimer>
39
40#include "moc_qgsmodelgraphicsview.cpp"
41
42using namespace Qt::StringLiterals;
43
45
46#define MIN_VIEW_SCALE 0.05
47#define MAX_VIEW_SCALE 1000.0
48
49QgsModelGraphicsView::QgsModelGraphicsView( QWidget *parent )
50 : QGraphicsView( parent )
51{
52 setResizeAnchor( QGraphicsView::AnchorViewCenter );
53 setMouseTracking( true );
54 viewport()->setMouseTracking( true );
55 setAcceptDrops( true );
56
57 mSpacePanTool = new QgsModelViewToolTemporaryKeyPan( this );
58 mMidMouseButtonPanTool = new QgsModelViewToolTemporaryMousePan( this );
59 mSpaceZoomTool = new QgsModelViewToolTemporaryKeyZoom( this );
60
61 connect( horizontalScrollBar(), &QScrollBar::valueChanged, this, &QgsModelGraphicsView::friendlySetSceneRect );
62 connect( verticalScrollBar(), &QScrollBar::valueChanged, this, &QgsModelGraphicsView::friendlySetSceneRect );
63
64 mSnapper.setSnapToGrid( true );
65}
66
67QgsModelGraphicsView::~QgsModelGraphicsView()
68{
69 emit willBeDeleted();
70}
71
72void QgsModelGraphicsView::dragEnterEvent( QDragEnterEvent *event )
73{
74 if ( event->mimeData()->hasFormat( u"application/x-vnd.qgis.qgis.algorithmid"_s )
75 || event->mimeData()->hasFormat( u"application/x-vnd.qgis.qgis.parametertypeid"_s )
76 || event->mimeData()->hasText() )
77 event->acceptProposedAction();
78 else
79 event->ignore();
80}
81
82void QgsModelGraphicsView::dropEvent( QDropEvent *event )
83{
84 const QPointF dropPoint = mapToScene( event->pos() );
85 if ( event->mimeData()->hasFormat( u"application/x-vnd.qgis.qgis.algorithmid"_s ) )
86 {
87 QByteArray data = event->mimeData()->data( u"application/x-vnd.qgis.qgis.algorithmid"_s );
88 QDataStream stream( &data, QIODevice::ReadOnly );
89 QString algorithmId;
90 stream >> algorithmId;
91
92 QTimer::singleShot( 0, this, [this, dropPoint, algorithmId] {
93 emit algorithmDropped( algorithmId, dropPoint );
94 } );
95 event->accept();
96 }
97 else if ( event->mimeData()->hasFormat( u"application/x-vnd.qgis.qgis.parametertypeid"_s ) )
98 {
99 QByteArray data = event->mimeData()->data( u"application/x-vnd.qgis.qgis.parametertypeid"_s );
100 QDataStream stream( &data, QIODevice::ReadOnly );
101 QString paramTypeId;
102 stream >> paramTypeId;
103
104 QTimer::singleShot( 0, this, [this, dropPoint, paramTypeId] {
105 emit inputDropped( paramTypeId, dropPoint );
106 } );
107 event->accept();
108 }
109 else if ( event->mimeData()->hasText() )
110 {
111 const QString itemId = event->mimeData()->text();
112 QTimer::singleShot( 0, this, [this, dropPoint, itemId] {
113 emit inputDropped( itemId, dropPoint );
114 } );
115 event->accept();
116 }
117 else
118 {
119 event->ignore();
120 }
121}
122
123void QgsModelGraphicsView::dragMoveEvent( QDragMoveEvent *event )
124{
125 if ( event->mimeData()->hasFormat( u"application/x-vnd.qgis.qgis.algorithmid"_s )
126 || event->mimeData()->hasFormat( u"application/x-vnd.qgis.qgis.parametertypeid"_s )
127 || event->mimeData()->hasText() )
128 event->acceptProposedAction();
129 else
130 event->ignore();
131}
132
133void QgsModelGraphicsView::wheelEvent( QWheelEvent *event )
134{
135 if ( !scene() )
136 return;
137
138 if ( mTool )
139 {
140 mTool->wheelEvent( event );
141 }
142
143 if ( !mTool || !event->isAccepted() )
144 {
145 event->accept();
146 wheelZoom( event );
147 }
148}
149
150void QgsModelGraphicsView::wheelZoom( QWheelEvent *event )
151{
152 //get mouse wheel zoom behavior settings
153 QgsSettings settings;
154 double zoomFactor = settings.value( u"qgis/zoom_factor"_s, 2 ).toDouble();
155 bool reverseZoom = settings.value( u"qgis/reverse_wheel_zoom"_s, false ).toBool();
156 bool zoomIn = reverseZoom ? event->angleDelta().y() < 0 : event->angleDelta().y() > 0;
157
158 // "Normal" mouse have an angle delta of 120, precision mouses provide data faster, in smaller steps
159 zoomFactor = 1.0 + ( zoomFactor - 1.0 ) / 120.0 * std::fabs( event->angleDelta().y() );
160
161 if ( event->modifiers() & Qt::ControlModifier )
162 {
163 //holding ctrl while wheel zooming results in a finer zoom
164 zoomFactor = 1.0 + ( zoomFactor - 1.0 ) / 20.0;
165 }
166
167 //calculate zoom scale factor
168 double scaleFactor = ( zoomIn ? 1 / zoomFactor : zoomFactor );
169
170 //get current visible part of scene
171 QRect viewportRect( 0, 0, viewport()->width(), viewport()->height() );
172 QgsRectangle visibleRect = QgsRectangle( mapToScene( viewportRect ).boundingRect() );
173
174 //transform the mouse pos to scene coordinates
175 QPointF scenePoint = mapToScene( event->position().x(), event->position().y() );
176
177 //adjust view center
178 QgsPointXY oldCenter( visibleRect.center() );
179 QgsPointXY newCenter( scenePoint.x() + ( ( oldCenter.x() - scenePoint.x() ) * scaleFactor ), scenePoint.y() + ( ( oldCenter.y() - scenePoint.y() ) * scaleFactor ) );
180 centerOn( newCenter.x(), newCenter.y() );
181
182 //zoom layout
183 if ( zoomIn )
184 {
185 scaleSafe( zoomFactor );
186 }
187 else
188 {
189 scaleSafe( 1 / zoomFactor );
190 }
191}
192
193void QgsModelGraphicsView::scaleSafe( double scale )
194{
195 double currentScale = transform().m11();
196 scale *= currentScale;
197 scale = std::clamp( scale, MIN_VIEW_SCALE, MAX_VIEW_SCALE );
198 setTransform( QTransform::fromScale( scale, scale ) );
199}
200
201QPointF QgsModelGraphicsView::deltaForKeyEvent( QKeyEvent *event )
202{
203 // increment used for cursor key item movement
204 double increment = 1.0;
205 if ( event->modifiers() & Qt::ShiftModifier )
206 {
207 //holding shift while pressing cursor keys results in a big step
208 increment = 10.0;
209 }
210 else if ( event->modifiers() & Qt::AltModifier )
211 {
212 //holding alt while pressing cursor keys results in a 1 pixel step
213 double viewScale = transform().m11();
214 if ( viewScale > 0 )
215 {
216 increment = 1 / viewScale;
217 }
218 }
219
220 double deltaX = 0;
221 double deltaY = 0;
222 switch ( event->key() )
223 {
224 case Qt::Key_Left:
225 deltaX = -increment;
226 break;
227 case Qt::Key_Right:
228 deltaX = increment;
229 break;
230 case Qt::Key_Up:
231 deltaY = -increment;
232 break;
233 case Qt::Key_Down:
234 deltaY = increment;
235 break;
236 default:
237 break;
238 }
239
240 return QPointF( deltaX, deltaY );
241}
242
243void QgsModelGraphicsView::mousePressEvent( QMouseEvent *event )
244{
245 if ( !modelScene() )
246 return;
247
248 if ( mTool )
249 {
250 auto me = std::make_unique<QgsModelViewMouseEvent>( this, event, mTool->flags() & QgsModelViewTool::FlagSnaps );
251 mTool->modelPressEvent( me.get() );
252 event->setAccepted( me->isAccepted() );
253 }
254
255 if ( !mTool || !event->isAccepted() )
256 {
257 if ( event->button() == Qt::MiddleButton && mTool != mSpacePanTool && mTool != mSpaceZoomTool )
258 {
259 // Pan layout with middle mouse button
260 setTool( mMidMouseButtonPanTool );
261 event->accept();
262 }
263 else
264 {
265 QGraphicsView::mousePressEvent( event );
266 }
267 }
268}
269
270void QgsModelGraphicsView::mouseReleaseEvent( QMouseEvent *event )
271{
272 if ( !modelScene() )
273 return;
274
275 if ( mTool )
276 {
277 auto me = std::make_unique<QgsModelViewMouseEvent>( this, event, mTool->flags() & QgsModelViewTool::FlagSnaps );
278 mTool->modelReleaseEvent( me.get() );
279 event->setAccepted( me->isAccepted() );
280 }
281
282 if ( !mTool || !event->isAccepted() )
283 QGraphicsView::mouseReleaseEvent( event );
284}
285
286void QgsModelGraphicsView::mouseMoveEvent( QMouseEvent *event )
287{
288 if ( !modelScene() )
289 return;
290
291 mMouseCurrentXY = event->pos();
292
293 QPointF cursorPos = mapToScene( mMouseCurrentXY );
294 if ( mTool )
295 {
296 auto me = std::make_unique<QgsModelViewMouseEvent>( this, event, false );
297 if ( mTool->flags() & QgsModelViewTool::FlagSnaps )
298 {
299 me->snapPoint();
300 }
301 if ( mTool->flags() & QgsModelViewTool::FlagSnaps )
302 {
303 //draw snapping point indicator
304 if ( me->isSnapped() )
305 {
306 cursorPos = me->snappedPoint();
307 if ( mSnapMarker )
308 {
309 mSnapMarker->setPos( me->snappedPoint() );
310 mSnapMarker->setVisible( true );
311 }
312 }
313 else if ( mSnapMarker )
314 {
315 mSnapMarker->setVisible( false );
316 }
317 }
318 mTool->modelMoveEvent( me.get() );
319 event->setAccepted( me->isAccepted() );
320 }
321
322 if ( !mTool || !event->isAccepted() )
323 QGraphicsView::mouseMoveEvent( event );
324}
325
326void QgsModelGraphicsView::mouseDoubleClickEvent( QMouseEvent *event )
327{
328 if ( !modelScene() )
329 return;
330
331 if ( mTool )
332 {
333 auto me = std::make_unique<QgsModelViewMouseEvent>( this, event, mTool->flags() & QgsModelViewTool::FlagSnaps );
334 mTool->modelDoubleClickEvent( me.get() );
335 event->setAccepted( me->isAccepted() );
336 }
337
338 if ( !mTool || !event->isAccepted() )
339 QGraphicsView::mouseDoubleClickEvent( event );
340}
341
342void QgsModelGraphicsView::keyPressEvent( QKeyEvent *event )
343{
344 if ( !modelScene() )
345 return;
346
347 if ( mTool )
348 {
349 mTool->keyPressEvent( event );
350 }
351
352 if ( mTool && event->isAccepted() )
353 return;
354
355 if ( event->key() == Qt::Key_Space && !event->isAutoRepeat() && mTool != mMidMouseButtonPanTool )
356 {
357 if ( !( event->modifiers() & Qt::ControlModifier ) )
358 {
359 // Pan layout with space bar
360 setTool( mSpacePanTool );
361 }
362 else
363 {
364 //ctrl+space pressed, so switch to temporary keyboard based zoom tool
365 setTool( mSpaceZoomTool );
366 }
367 event->accept();
368 }
369 else if ( event->key() == Qt::Key_Left
370 || event->key() == Qt::Key_Right
371 || event->key() == Qt::Key_Up
372 || event->key() == Qt::Key_Down )
373 {
374 QgsModelGraphicsScene *s = modelScene();
375 const QList<QgsModelComponentGraphicItem *> itemList = s->selectedComponentItems();
376 if ( !itemList.empty() )
377 {
378 QPointF delta = deltaForKeyEvent( event );
379
380 itemList.at( 0 )->aboutToChange( tr( "Move Items" ) );
381 for ( QgsModelComponentGraphicItem *item : itemList )
382 {
383 item->moveComponentBy( delta.x(), delta.y() );
384 }
385 itemList.at( 0 )->changed();
386 }
387 event->accept();
388 }
389}
390
391void QgsModelGraphicsView::keyReleaseEvent( QKeyEvent *event )
392{
393 if ( !modelScene() )
394 return;
395
396 if ( mTool )
397 {
398 mTool->keyReleaseEvent( event );
399 }
400
401 if ( !mTool || !event->isAccepted() )
402 QGraphicsView::keyReleaseEvent( event );
403}
404
405void QgsModelGraphicsView::setModelScene( QgsModelGraphicsScene *scene )
406{
407 setScene( scene );
408
409 connect( scene, &QgsModelGraphicsScene::sceneRectChanged, this, &QgsModelGraphicsView::friendlySetSceneRect );
410
411 // IMPORTANT!
412 // previous snap markers, snap lines are owned by previous layout - so don't delete them here!
413 mSnapMarker = new QgsModelViewSnapMarker();
414 mSnapMarker->hide();
415 scene->addItem( mSnapMarker );
416}
417
418QgsModelGraphicsScene *QgsModelGraphicsView::modelScene() const
419{
420 return qobject_cast<QgsModelGraphicsScene *>( QgsModelGraphicsView::scene() );
421}
422
423QgsModelViewTool *QgsModelGraphicsView::tool()
424{
425 return mTool;
426}
427
428void QgsModelGraphicsView::setTool( QgsModelViewTool *tool )
429{
430 if ( !tool )
431 return;
432
433 if ( mTool )
434 {
435 mTool->deactivate();
436 disconnect( mTool, &QgsModelViewTool::itemFocused, this, &QgsModelGraphicsView::itemFocused );
437 }
438
439 // activate new tool before setting it - gives tools a chance
440 // to respond to whatever the current tool is
441 tool->activate();
442 mTool = tool;
443 connect( mTool, &QgsModelViewTool::itemFocused, this, &QgsModelGraphicsView::itemFocused );
444 emit toolSet( mTool );
445}
446
447void QgsModelGraphicsView::unsetTool( QgsModelViewTool *tool )
448{
449 if ( mTool && mTool == tool )
450 {
451 mTool->deactivate();
452 emit toolSet( nullptr );
453 setCursor( Qt::ArrowCursor );
454 }
455}
456
457QgsModelSnapper *QgsModelGraphicsView::snapper()
458{
459 return &mSnapper;
460}
461
462void QgsModelGraphicsView::startMacroCommand( const QString &text )
463{
464 emit macroCommandStarted( text );
465}
466
467void QgsModelGraphicsView::endMacroCommand()
468{
469 emit macroCommandEnded();
470}
471
472void QgsModelGraphicsView::beginCommand( const QString &text )
473{
474 emit commandBegun( text );
475}
476
477void QgsModelGraphicsView::endCommand()
478{
479 emit commandEnded();
480}
481
482void QgsModelGraphicsView::abortCommand()
483{
484 emit commandAborted();
485}
486
487void QgsModelGraphicsView::snapSelected()
488{
489 QgsModelGraphicsScene *s = modelScene();
490 const QList<QgsModelComponentGraphicItem *> itemList = s->selectedComponentItems();
491 startMacroCommand( tr( "Snap Items" ) );
492 if ( !itemList.empty() )
493 {
494 bool prevSetting = mSnapper.snapToGrid();
495 mSnapper.setSnapToGrid( true );
496 for ( QgsModelComponentGraphicItem *item : itemList )
497 {
498 bool wasSnapped = false;
499 QRectF snapped = mSnapper.snapRectWithResize( item->mapRectToScene( item->itemRect() ), transform().m11(), wasSnapped );
500 if ( wasSnapped )
501 {
502 item->setItemRect( snapped );
503 }
504 }
505 mSnapper.setSnapToGrid( prevSetting );
506 }
507 endMacroCommand();
508}
509
510void QgsModelGraphicsView::friendlySetSceneRect()
511{
512 if ( mBlockScrollbarSignals )
513 return;
514
515 const QRectF currentSceneRect = sceneRect();
516
517 const QRectF modelSceneRect = modelScene()->sceneRect();
518 const QRectF visibleRect = mapToScene( viewport()->rect() ).boundingRect();
519 QRectF newSceneRect;
520 newSceneRect.setLeft( std::min( modelSceneRect.left(), visibleRect.left() ) );
521 newSceneRect.setRight( std::max( modelSceneRect.right(), visibleRect.right() ) );
522 newSceneRect.setTop( std::min( modelSceneRect.top(), visibleRect.top() ) );
523 newSceneRect.setBottom( std::max( modelSceneRect.bottom(), visibleRect.bottom() ) );
524
525 // the above conversions may involve small rounding errors which stack up and could
526 // result in unwanted small shifts of the visible scene area => only update the
527 // scene rect if the visible area change is sufficiently large to warrant this:
528 constexpr int MIN_VIEW_SHIFT_THRESHOLD_PIXELS = 20;
529 if ( std::abs( newSceneRect.left() - currentSceneRect.left() ) > MIN_VIEW_SHIFT_THRESHOLD_PIXELS
530 || std::abs( newSceneRect.right() - currentSceneRect.right() ) > MIN_VIEW_SHIFT_THRESHOLD_PIXELS
531 || std::abs( newSceneRect.top() - currentSceneRect.top() ) > MIN_VIEW_SHIFT_THRESHOLD_PIXELS
532 || std::abs( newSceneRect.bottom() - currentSceneRect.bottom() ) > MIN_VIEW_SHIFT_THRESHOLD_PIXELS )
533 {
534 mBlockScrollbarSignals++;
535 setSceneRect( newSceneRect );
536 mBlockScrollbarSignals--;
537 }
538}
539
540void QgsModelGraphicsView::copySelectedItems( QgsModelGraphicsView::ClipboardOperation operation )
541{
542 copyItems( modelScene()->selectedComponentItems(), operation );
543}
544
545void QgsModelGraphicsView::copyItems( const QList<QgsModelComponentGraphicItem *> &items, QgsModelGraphicsView::ClipboardOperation operation )
546{
547 if ( !modelScene() )
548 return;
549
550 QgsReadWriteContext context;
551 QDomDocument doc;
552 QDomElement documentElement = doc.createElement( u"ModelComponentClipboard"_s );
553 if ( operation == ClipboardCut )
554 {
555 emit macroCommandStarted( tr( "Cut Items" ) );
556 emit commandBegun( QString() );
557 }
558
559 QList<QVariant> paramComponents;
560 QList<QVariant> groupBoxComponents;
561 QList<QVariant> algComponents;
562
563 QList<QgsModelComponentGraphicItem *> selectedCommentParents;
564 QList<QgsProcessingModelOutput> selectedOutputs;
565 QList<QgsProcessingModelOutput> selectedOutputsComments;
566 for ( QgsModelComponentGraphicItem *item : items )
567 {
568 if ( const QgsModelCommentGraphicItem *commentItem = dynamic_cast<QgsModelCommentGraphicItem *>( item ) )
569 {
570 selectedCommentParents << commentItem->parentComponentItem();
571 if ( const QgsModelOutputGraphicItem *outputItem = dynamic_cast<QgsModelOutputGraphicItem *>( commentItem->parentComponentItem() ) )
572 {
573 selectedOutputsComments << *( static_cast<const QgsProcessingModelOutput *>( outputItem->component() ) );
574 }
575 }
576 else if ( const QgsModelOutputGraphicItem *outputItem = dynamic_cast<QgsModelOutputGraphicItem *>( item ) )
577 {
578 selectedOutputs << *( static_cast<const QgsProcessingModelOutput *>( outputItem->component() ) );
579 }
580 }
581
582 for ( QgsModelComponentGraphicItem *item : items )
583 {
584 if ( const QgsProcessingModelParameter *param = dynamic_cast<QgsProcessingModelParameter *>( item->component() ) )
585 {
586 QgsProcessingModelParameter component = *param;
587
588 // was comment selected?
589 if ( !selectedCommentParents.contains( item ) )
590 {
591 // no, so drop comment
592 component.comment()->setDescription( QString() );
593 }
594
595 QVariantMap paramDef;
596 paramDef.insert( u"component"_s, component.toVariant() );
597 const QgsProcessingParameterDefinition *def = modelScene()->model()->parameterDefinition( component.parameterName() );
598 paramDef.insert( u"definition"_s, def->toVariantMap() );
599
600 paramComponents << paramDef;
601 }
602 else if ( QgsProcessingModelGroupBox *groupBox = dynamic_cast<QgsProcessingModelGroupBox *>( item->component() ) )
603 {
604 groupBoxComponents << groupBox->toVariant();
605 }
606 else if ( const QgsProcessingModelChildAlgorithm *alg = dynamic_cast<QgsProcessingModelChildAlgorithm *>( item->component() ) )
607 {
608 QgsProcessingModelChildAlgorithm childAlg = *alg;
609
610 // was comment selected?
611 if ( !selectedCommentParents.contains( item ) )
612 {
613 // no, so drop comment
614 childAlg.comment()->setDescription( QString() );
615 }
616
617 // don't copy outputs which weren't selected either
618 QMap<QString, QgsProcessingModelOutput> clipboardOutputs;
619 const QMap<QString, QgsProcessingModelOutput> existingOutputs = childAlg.modelOutputs();
620 for ( auto it = existingOutputs.constBegin(); it != existingOutputs.constEnd(); ++it )
621 {
622 bool found = false;
623 for ( const QgsProcessingModelOutput &candidate : selectedOutputs )
624 {
625 if ( candidate.childId() == childAlg.childId() && candidate.name() == it.value().name() && candidate.childOutputName() == it.value().childOutputName() )
626 {
627 found = true;
628 break;
629 }
630 }
631 if ( found )
632 {
633 // should we also copy the comment?
634 bool commentFound = false;
635 for ( const QgsProcessingModelOutput &candidate : selectedOutputsComments )
636 {
637 if ( candidate.childId() == childAlg.childId() && candidate.name() == it.value().name() && candidate.childOutputName() == it.value().childOutputName() )
638 {
639 commentFound = true;
640 break;
641 }
642 }
643
644 QgsProcessingModelOutput output = it.value();
645 if ( !commentFound )
646 output.comment()->setDescription( QString() );
647
648 clipboardOutputs.insert( it.key(), output );
649 }
650 }
651 childAlg.setModelOutputs( clipboardOutputs );
652
653 algComponents << childAlg.toVariant();
654 }
655 }
656 QVariantMap components;
657 components.insert( u"parameters"_s, paramComponents );
658 components.insert( u"groupboxes"_s, groupBoxComponents );
659 components.insert( u"algs"_s, algComponents );
660 doc.appendChild( QgsXmlUtils::writeVariant( components, doc ) );
661 if ( operation == ClipboardCut )
662 {
663 emit deleteSelectedItems();
664 emit commandEnded();
665 emit macroCommandEnded();
666 }
667
668 QMimeData *mimeData = new QMimeData;
669 mimeData->setData( u"text/xml"_s, doc.toByteArray() );
670 mimeData->setText( doc.toByteArray() );
671 QClipboard *clipboard = QApplication::clipboard();
672 clipboard->setMimeData( mimeData );
673}
674
675void QgsModelGraphicsView::pasteItems( QgsModelGraphicsView::PasteMode mode )
676{
677 if ( !modelScene() )
678 return;
679
680 QDomDocument doc;
681 QClipboard *clipboard = QApplication::clipboard();
682 const QMimeData *mimeData = clipboard->mimeData();
683 if ( !mimeData )
684 return;
685 if ( doc.setContent( mimeData->data( u"text/xml"_s ) ) )
686 {
687 QDomElement docElem = doc.documentElement();
688 QVariantMap res = QgsXmlUtils::readVariant( docElem ).toMap();
689
690 if ( res.contains( u"parameters"_s ) && res.contains( u"algs"_s ) )
691 {
692 QPointF pt;
693 switch ( mode )
694 {
695 case PasteModeCursor:
696 case PasteModeInPlace:
697 {
698 // place items at cursor position
699 pt = mapToScene( mapFromGlobal( QCursor::pos() ) );
700 break;
701 }
702 case PasteModeCenter:
703 {
704 // place items in center of viewport
705 pt = mapToScene( viewport()->rect().center() );
706 break;
707 }
708 }
709
710 beginCommand( tr( "Paste Items" ) );
711
712 QRectF pastedBounds;
713
714 QList<QgsProcessingModelGroupBox> pastedGroups;
715 for ( const QVariant &v : res.value( u"groupboxes"_s ).toList() )
716 {
717 QgsProcessingModelGroupBox box;
718 // don't restore the uuid -- we need them to be unique in the model
719 box.loadVariant( v.toMap(), true );
720
721 pastedGroups << box;
722
723 modelScene()->model()->addGroupBox( box );
724
725 if ( !pastedBounds.isValid() )
726 pastedBounds = QRectF( box.position() - QPointF( box.size().width() / 2.0, box.size().height() / 2.0 ), box.size() );
727 else
728 pastedBounds = pastedBounds.united( QRectF( box.position() - QPointF( box.size().width() / 2.0, box.size().height() / 2.0 ), box.size() ) );
729 }
730
731 QStringList pastedParameters;
732 for ( const QVariant &v : res.value( u"parameters"_s ).toList() )
733 {
734 QVariantMap param = v.toMap();
735 QVariantMap componentDef = param.value( u"component"_s ).toMap();
736 QVariantMap paramDef = param.value( u"definition"_s ).toMap();
737
738 std::unique_ptr<QgsProcessingParameterDefinition> paramDefinition( QgsProcessingParameters::parameterFromVariantMap( paramDef ) );
739
740 QgsProcessingModelParameter p;
741 p.loadVariant( componentDef );
742
743 // we need a unique name for the parameter
744 QString name = p.parameterName();
745 QString description = paramDefinition->description();
746 int next = 1;
747 while ( modelScene()->model()->parameterDefinition( name ) )
748 {
749 next++;
750 name = u"%1 (%2)"_s.arg( p.parameterName() ).arg( next );
751 description = u"%1 (%2)"_s.arg( paramDefinition->description() ).arg( next );
752 }
753 paramDefinition->setName( name );
754 paramDefinition->setDescription( description );
755 p.setParameterName( name );
756
757 modelScene()->model()->addModelParameter( paramDefinition.release(), p );
758 pastedParameters << p.parameterName();
759
760 if ( !pastedBounds.isValid() )
761 pastedBounds = QRectF( p.position() - QPointF( p.size().width() / 2.0, p.size().height() / 2.0 ), p.size() );
762 else
763 pastedBounds = pastedBounds.united( QRectF( p.position() - QPointF( p.size().width() / 2.0, p.size().height() / 2.0 ), p.size() ) );
764
765 if ( !p.comment()->description().isEmpty() )
766 pastedBounds = pastedBounds.united( QRectF( p.comment()->position() - QPointF( p.comment()->size().width() / 2.0, p.comment()->size().height() / 2.0 ), p.comment()->size() ) );
767 }
768
769 QStringList pastedAlgorithms;
770 for ( const QVariant &v : res.value( u"algs"_s ).toList() )
771 {
772 QgsProcessingModelChildAlgorithm alg;
773 alg.loadVariant( v.toMap() );
774
775 // ensure algorithm id is unique
776 if ( modelScene()->model()->childAlgorithms().contains( alg.childId() ) )
777 {
778 alg.generateChildId( *modelScene()->model() );
779 }
780 alg.reattach();
781
782 pastedAlgorithms << alg.childId();
783
784 if ( !pastedBounds.isValid() )
785 pastedBounds = QRectF( alg.position() - QPointF( alg.size().width() / 2.0, alg.size().height() / 2.0 ), alg.size() );
786 else
787 pastedBounds = pastedBounds.united( QRectF( alg.position() - QPointF( alg.size().width() / 2.0, alg.size().height() / 2.0 ), alg.size() ) );
788
789 if ( !alg.comment()->description().isEmpty() )
790 pastedBounds = pastedBounds.united( QRectF( alg.comment()->position() - QPointF( alg.comment()->size().width() / 2.0, alg.comment()->size().height() / 2.0 ), alg.comment()->size() ) );
791
792 const QMap<QString, QgsProcessingModelChildAlgorithm> existingAlgs = modelScene()->model()->childAlgorithms();
793
794 const QMap<QString, QgsProcessingModelOutput> outputs = alg.modelOutputs();
795 QMap<QString, QgsProcessingModelOutput> pastedOutputs;
796 for ( auto it = outputs.constBegin(); it != outputs.constEnd(); ++it )
797 {
798 QString name = it.value().name();
799 int next = 1;
800 bool unique = false;
801 while ( !unique )
802 {
803 unique = true;
804 for ( auto algIt = existingAlgs.constBegin(); algIt != existingAlgs.constEnd(); ++algIt )
805 {
806 const QMap<QString, QgsProcessingModelOutput> algOutputs = algIt->modelOutputs();
807 for ( auto outputIt = algOutputs.constBegin(); outputIt != algOutputs.constEnd(); ++outputIt )
808 {
809 if ( outputIt.value().name() == name )
810 {
811 unique = false;
812 break;
813 }
814 }
815 if ( !unique )
816 break;
817 }
818 if ( unique )
819 break;
820 next++;
821 name = u"%1 (%2)"_s.arg( it.value().name() ).arg( next );
822 }
823
824 QgsProcessingModelOutput newOutput = it.value();
825 newOutput.setName( name );
826 newOutput.setDescription( name );
827 pastedOutputs.insert( name, newOutput );
828
829 pastedBounds = pastedBounds.united( QRectF( newOutput.position() - QPointF( newOutput.size().width() / 2.0, newOutput.size().height() / 2.0 ), newOutput.size() ) );
830
831 if ( !alg.comment()->description().isEmpty() )
832 pastedBounds = pastedBounds.united( QRectF( newOutput.comment()->position() - QPointF( newOutput.comment()->size().width() / 2.0, newOutput.comment()->size().height() / 2.0 ), newOutput.comment()->size() ) );
833 }
834 alg.setModelOutputs( pastedOutputs );
835
836 modelScene()->model()->addChildAlgorithm( alg );
837 }
838
839 QPointF offset( 0, 0 );
840 switch ( mode )
841 {
842 case PasteModeInPlace:
843 break;
844
845 case PasteModeCursor:
846 case PasteModeCenter:
847 {
848 offset = pt - pastedBounds.topLeft();
849 break;
850 }
851 }
852
853 if ( !offset.isNull() )
854 {
855 for ( QgsProcessingModelGroupBox pastedGroup : std::as_const( pastedGroups ) )
856 {
857 pastedGroup.setPosition( pastedGroup.position() + offset );
858 modelScene()->model()->addGroupBox( pastedGroup );
859 }
860 for ( const QString &pastedParam : std::as_const( pastedParameters ) )
861 {
862 modelScene()->model()->parameterComponent( pastedParam ).setPosition( modelScene()->model()->parameterComponent( pastedParam ).position() + offset );
863 modelScene()->model()->parameterComponent( pastedParam ).comment()->setPosition( modelScene()->model()->parameterComponent( pastedParam ).comment()->position() + offset );
864 }
865 for ( const QString &pastedAlg : std::as_const( pastedAlgorithms ) )
866 {
867 modelScene()->model()->childAlgorithm( pastedAlg ).setPosition( modelScene()->model()->childAlgorithm( pastedAlg ).position() + offset );
868 modelScene()->model()->childAlgorithm( pastedAlg ).comment()->setPosition( modelScene()->model()->childAlgorithm( pastedAlg ).comment()->position() + offset );
869
870 const QMap<QString, QgsProcessingModelOutput> outputs = modelScene()->model()->childAlgorithm( pastedAlg ).modelOutputs();
871 for ( auto it = outputs.begin(); it != outputs.end(); ++it )
872 {
873 modelScene()->model()->childAlgorithm( pastedAlg ).modelOutput( it.key() ).setPosition( modelScene()->model()->childAlgorithm( pastedAlg ).modelOutput( it.key() ).position() + offset );
874 modelScene()->model()->childAlgorithm( pastedAlg ).modelOutput( it.key() ).comment()->setPosition( modelScene()->model()->childAlgorithm( pastedAlg ).modelOutput( it.key() ).comment()->position() + offset );
875 }
876 }
877 }
878
879 emit commandEnded();
880 }
881 }
882
883 modelScene()->rebuildRequired();
884}
885
886QgsModelViewSnapMarker::QgsModelViewSnapMarker()
887 : QGraphicsRectItem( QRectF( 0, 0, 0, 0 ) )
888{
889 QFont f;
890 QFontMetrics fm( f );
891 mSize = fm.horizontalAdvance( 'X' );
892 setPen( QPen( Qt::transparent, mSize ) );
893
894 setFlags( flags() | QGraphicsItem::ItemIgnoresTransformations );
895 setZValue( QgsModelGraphicsScene::ZSnapIndicator );
896}
897
898void QgsModelViewSnapMarker::paint( QPainter *p, const QStyleOptionGraphicsItem *, QWidget * )
899{
900 QPen pen( QColor( 255, 0, 0 ) );
901 pen.setWidth( 0 );
902 p->setPen( pen );
903 p->setBrush( Qt::NoBrush );
904
905 double halfSize = mSize / 2.0;
906 p->drawLine( QLineF( -halfSize, -halfSize, halfSize, halfSize ) );
907 p->drawLine( QLineF( -halfSize, halfSize, halfSize, -halfSize ) );
908}
909
910
Manages snapping grids and preset snap lines in a layout, and handles snapping points to the nearest ...
Model designer view tool for temporarily panning a layout while a key is depressed.
Model view tool for temporarily zooming a model while a key is depressed.
Model view tool for temporarily panning a model while a mouse button is depressed.
Abstract base class for all model designer view tools.
@ FlagSnaps
Tool utilizes snapped coordinates.
void itemFocused(QgsModelComponentGraphicItem *item)
Emitted when an item is "focused" by the tool, i.e.
virtual void activate()
Called when tool is set as the currently active model tool.
Represents a 2D point.
Definition qgspointxy.h:62
Base class for the definition of processing parameters.
virtual QVariantMap toVariantMap() const
Saves this parameter to a QVariantMap.
static QgsProcessingParameterDefinition * parameterFromVariantMap(const QVariantMap &map)
Creates a new QgsProcessingParameterDefinition using the configuration from a supplied variant map.
A container for the context for various read/write operations on objects.
A rectangle specified with double values.
QgsPointXY center
Stores settings for use within QGIS.
Definition qgssettings.h:68
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
static QDomElement writeVariant(const QVariant &value, QDomDocument &doc)
Write a QVariant to a QDomElement.
static QVariant readVariant(const QDomElement &element)
Read a QVariant from a QDomElement.
#define MAX_VIEW_SCALE
#define MIN_VIEW_SCALE