QGIS API Documentation 3.99.0-Master (c22de0620c0)
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 startMacroCommand( tr( "Move Items" ) );
381 for ( QgsModelComponentGraphicItem *item : itemList )
382 {
383 item->moveComponentBy( delta.x(), delta.y() );
384 }
385 itemList.at( 0 )->changed();
386 endMacroCommand();
387 }
388 event->accept();
389 }
390}
391
392void QgsModelGraphicsView::keyReleaseEvent( QKeyEvent *event )
393{
394 if ( !modelScene() )
395 return;
396
397 if ( mTool )
398 {
399 mTool->keyReleaseEvent( event );
400 }
401
402 if ( !mTool || !event->isAccepted() )
403 QGraphicsView::keyReleaseEvent( event );
404}
405
406void QgsModelGraphicsView::setModelScene( QgsModelGraphicsScene *scene )
407{
408 setScene( scene );
409
410 connect( scene, &QgsModelGraphicsScene::sceneRectChanged, this, &QgsModelGraphicsView::friendlySetSceneRect );
411
412 // IMPORTANT!
413 // previous snap markers, snap lines are owned by previous layout - so don't delete them here!
414 mSnapMarker = new QgsModelViewSnapMarker();
415 mSnapMarker->hide();
416 scene->addItem( mSnapMarker );
417}
418
419QgsModelGraphicsScene *QgsModelGraphicsView::modelScene() const
420{
421 return qobject_cast<QgsModelGraphicsScene *>( QgsModelGraphicsView::scene() );
422}
423
424QgsModelViewTool *QgsModelGraphicsView::tool()
425{
426 return mTool;
427}
428
429void QgsModelGraphicsView::setTool( QgsModelViewTool *tool )
430{
431 if ( !tool )
432 return;
433
434 if ( mTool )
435 {
436 mTool->deactivate();
437 disconnect( mTool, &QgsModelViewTool::itemFocused, this, &QgsModelGraphicsView::itemFocused );
438 }
439
440 // activate new tool before setting it - gives tools a chance
441 // to respond to whatever the current tool is
442 tool->activate();
443 mTool = tool;
444 connect( mTool, &QgsModelViewTool::itemFocused, this, &QgsModelGraphicsView::itemFocused );
445 emit toolSet( mTool );
446}
447
448void QgsModelGraphicsView::unsetTool( QgsModelViewTool *tool )
449{
450 if ( mTool && mTool == tool )
451 {
452 mTool->deactivate();
453 emit toolSet( nullptr );
454 setCursor( Qt::ArrowCursor );
455 }
456}
457
458QgsModelSnapper *QgsModelGraphicsView::snapper()
459{
460 return &mSnapper;
461}
462
463void QgsModelGraphicsView::startMacroCommand( const QString &text )
464{
465 emit macroCommandStarted( text );
466}
467
468void QgsModelGraphicsView::endMacroCommand()
469{
470 emit macroCommandEnded();
471}
472
473void QgsModelGraphicsView::beginCommand( const QString &text )
474{
475 emit commandBegun( text );
476}
477
478void QgsModelGraphicsView::endCommand()
479{
480 emit commandEnded();
481}
482
483void QgsModelGraphicsView::abortCommand()
484{
485 emit commandAborted();
486}
487
488void QgsModelGraphicsView::snapSelected()
489{
490 QgsModelGraphicsScene *s = modelScene();
491 const QList<QgsModelComponentGraphicItem *> itemList = s->selectedComponentItems();
492 startMacroCommand( tr( "Snap Items" ) );
493 if ( !itemList.empty() )
494 {
495 bool prevSetting = mSnapper.snapToGrid();
496 mSnapper.setSnapToGrid( true );
497 for ( QgsModelComponentGraphicItem *item : itemList )
498 {
499 bool wasSnapped = false;
500 QRectF snapped = mSnapper.snapRectWithResize( item->mapRectToScene( item->itemRect() ), transform().m11(), wasSnapped );
501 if ( wasSnapped )
502 {
503 item->setItemRect( snapped );
504 }
505 }
506 mSnapper.setSnapToGrid( prevSetting );
507 }
508 endMacroCommand();
509}
510
511void QgsModelGraphicsView::friendlySetSceneRect()
512{
513 if ( mBlockScrollbarSignals )
514 return;
515
516 const QRectF currentSceneRect = sceneRect();
517
518 const QRectF modelSceneRect = modelScene()->sceneRect();
519 const QRectF visibleRect = mapToScene( viewport()->rect() ).boundingRect();
520 QRectF newSceneRect;
521 newSceneRect.setLeft( std::min( modelSceneRect.left(), visibleRect.left() ) );
522 newSceneRect.setRight( std::max( modelSceneRect.right(), visibleRect.right() ) );
523 newSceneRect.setTop( std::min( modelSceneRect.top(), visibleRect.top() ) );
524 newSceneRect.setBottom( std::max( modelSceneRect.bottom(), visibleRect.bottom() ) );
525
526 // the above conversions may involve small rounding errors which stack up and could
527 // result in unwanted small shifts of the visible scene area => only update the
528 // scene rect if the visible area change is sufficiently large to warrant this:
529 constexpr int MIN_VIEW_SHIFT_THRESHOLD_PIXELS = 20;
530 if ( std::abs( newSceneRect.left() - currentSceneRect.left() ) > MIN_VIEW_SHIFT_THRESHOLD_PIXELS
531 || std::abs( newSceneRect.right() - currentSceneRect.right() ) > MIN_VIEW_SHIFT_THRESHOLD_PIXELS
532 || std::abs( newSceneRect.top() - currentSceneRect.top() ) > MIN_VIEW_SHIFT_THRESHOLD_PIXELS
533 || std::abs( newSceneRect.bottom() - currentSceneRect.bottom() ) > MIN_VIEW_SHIFT_THRESHOLD_PIXELS )
534 {
535 mBlockScrollbarSignals++;
536 setSceneRect( newSceneRect );
537 mBlockScrollbarSignals--;
538 }
539}
540
541void QgsModelGraphicsView::copySelectedItems( QgsModelGraphicsView::ClipboardOperation operation )
542{
543 copyItems( modelScene()->selectedComponentItems(), operation );
544}
545
546void QgsModelGraphicsView::copyItems( const QList<QgsModelComponentGraphicItem *> &items, QgsModelGraphicsView::ClipboardOperation operation )
547{
548 if ( !modelScene() )
549 return;
550
551 QgsReadWriteContext context;
552 QDomDocument doc;
553 QDomElement documentElement = doc.createElement( u"ModelComponentClipboard"_s );
554 if ( operation == ClipboardCut )
555 {
556 emit macroCommandStarted( tr( "Cut Items" ) );
557 emit commandBegun( QString() );
558 }
559
560 QList<QVariant> paramComponents;
561 QList<QVariant> groupBoxComponents;
562 QList<QVariant> algComponents;
563
564 QList<QgsModelComponentGraphicItem *> selectedCommentParents;
565 QList<QgsProcessingModelOutput> selectedOutputs;
566 QList<QgsProcessingModelOutput> selectedOutputsComments;
567 for ( QgsModelComponentGraphicItem *item : items )
568 {
569 if ( const QgsModelCommentGraphicItem *commentItem = dynamic_cast<QgsModelCommentGraphicItem *>( item ) )
570 {
571 selectedCommentParents << commentItem->parentComponentItem();
572 if ( const QgsModelOutputGraphicItem *outputItem = dynamic_cast<QgsModelOutputGraphicItem *>( commentItem->parentComponentItem() ) )
573 {
574 selectedOutputsComments << *( static_cast<const QgsProcessingModelOutput *>( outputItem->component() ) );
575 }
576 }
577 else if ( const QgsModelOutputGraphicItem *outputItem = dynamic_cast<QgsModelOutputGraphicItem *>( item ) )
578 {
579 selectedOutputs << *( static_cast<const QgsProcessingModelOutput *>( outputItem->component() ) );
580 }
581 }
582
583 for ( QgsModelComponentGraphicItem *item : items )
584 {
585 if ( const QgsProcessingModelParameter *param = dynamic_cast<QgsProcessingModelParameter *>( item->component() ) )
586 {
587 QgsProcessingModelParameter component = *param;
588
589 // was comment selected?
590 if ( !selectedCommentParents.contains( item ) )
591 {
592 // no, so drop comment
593 component.comment()->setDescription( QString() );
594 }
595
596 QVariantMap paramDef;
597 paramDef.insert( u"component"_s, component.toVariant() );
598 const QgsProcessingParameterDefinition *def = modelScene()->model()->parameterDefinition( component.parameterName() );
599 paramDef.insert( u"definition"_s, def->toVariantMap() );
600
601 paramComponents << paramDef;
602 }
603 else if ( QgsProcessingModelGroupBox *groupBox = dynamic_cast<QgsProcessingModelGroupBox *>( item->component() ) )
604 {
605 groupBoxComponents << groupBox->toVariant();
606 }
607 else if ( const QgsProcessingModelChildAlgorithm *alg = dynamic_cast<QgsProcessingModelChildAlgorithm *>( item->component() ) )
608 {
609 QgsProcessingModelChildAlgorithm childAlg = *alg;
610
611 // was comment selected?
612 if ( !selectedCommentParents.contains( item ) )
613 {
614 // no, so drop comment
615 childAlg.comment()->setDescription( QString() );
616 }
617
618 // don't copy outputs which weren't selected either
619 QMap<QString, QgsProcessingModelOutput> clipboardOutputs;
620 const QMap<QString, QgsProcessingModelOutput> existingOutputs = childAlg.modelOutputs();
621 for ( auto it = existingOutputs.constBegin(); it != existingOutputs.constEnd(); ++it )
622 {
623 bool found = false;
624 for ( const QgsProcessingModelOutput &candidate : selectedOutputs )
625 {
626 if ( candidate.childId() == childAlg.childId() && candidate.name() == it.value().name() && candidate.childOutputName() == it.value().childOutputName() )
627 {
628 found = true;
629 break;
630 }
631 }
632 if ( found )
633 {
634 // should we also copy the comment?
635 bool commentFound = false;
636 for ( const QgsProcessingModelOutput &candidate : selectedOutputsComments )
637 {
638 if ( candidate.childId() == childAlg.childId() && candidate.name() == it.value().name() && candidate.childOutputName() == it.value().childOutputName() )
639 {
640 commentFound = true;
641 break;
642 }
643 }
644
645 QgsProcessingModelOutput output = it.value();
646 if ( !commentFound )
647 output.comment()->setDescription( QString() );
648
649 clipboardOutputs.insert( it.key(), output );
650 }
651 }
652 childAlg.setModelOutputs( clipboardOutputs );
653
654 algComponents << childAlg.toVariant();
655 }
656 }
657 QVariantMap components;
658 components.insert( u"parameters"_s, paramComponents );
659 components.insert( u"groupboxes"_s, groupBoxComponents );
660 components.insert( u"algs"_s, algComponents );
661 doc.appendChild( QgsXmlUtils::writeVariant( components, doc ) );
662 if ( operation == ClipboardCut )
663 {
664 emit deleteSelectedItems();
665 emit commandEnded();
666 emit macroCommandEnded();
667 }
668
669 QMimeData *mimeData = new QMimeData;
670 mimeData->setData( u"text/xml"_s, doc.toByteArray() );
671 mimeData->setText( doc.toByteArray() );
672 QClipboard *clipboard = QApplication::clipboard();
673 clipboard->setMimeData( mimeData );
674}
675
676void QgsModelGraphicsView::pasteItems( QgsModelGraphicsView::PasteMode mode )
677{
678 if ( !modelScene() )
679 return;
680
681 QDomDocument doc;
682 QClipboard *clipboard = QApplication::clipboard();
683 const QMimeData *mimeData = clipboard->mimeData();
684 if ( !mimeData )
685 return;
686 if ( doc.setContent( mimeData->data( u"text/xml"_s ) ) )
687 {
688 QDomElement docElem = doc.documentElement();
689 QVariantMap res = QgsXmlUtils::readVariant( docElem ).toMap();
690
691 if ( res.contains( u"parameters"_s ) && res.contains( u"algs"_s ) )
692 {
693 QPointF pt;
694 switch ( mode )
695 {
696 case PasteModeCursor:
697 case PasteModeInPlace:
698 {
699 // place items at cursor position
700 pt = mapToScene( mapFromGlobal( QCursor::pos() ) );
701 break;
702 }
703 case PasteModeCenter:
704 {
705 // place items in center of viewport
706 pt = mapToScene( viewport()->rect().center() );
707 break;
708 }
709 }
710
711 beginCommand( tr( "Paste Items" ) );
712
713 QRectF pastedBounds;
714
715 QList<QgsProcessingModelGroupBox> pastedGroups;
716 for ( const QVariant &v : res.value( u"groupboxes"_s ).toList() )
717 {
718 QgsProcessingModelGroupBox box;
719 // don't restore the uuid -- we need them to be unique in the model
720 box.loadVariant( v.toMap(), true );
721
722 pastedGroups << box;
723
724 modelScene()->model()->addGroupBox( box );
725
726 if ( !pastedBounds.isValid() )
727 pastedBounds = QRectF( box.position() - QPointF( box.size().width() / 2.0, box.size().height() / 2.0 ), box.size() );
728 else
729 pastedBounds = pastedBounds.united( QRectF( box.position() - QPointF( box.size().width() / 2.0, box.size().height() / 2.0 ), box.size() ) );
730 }
731
732 QStringList pastedParameters;
733 for ( const QVariant &v : res.value( u"parameters"_s ).toList() )
734 {
735 QVariantMap param = v.toMap();
736 QVariantMap componentDef = param.value( u"component"_s ).toMap();
737 QVariantMap paramDef = param.value( u"definition"_s ).toMap();
738
739 std::unique_ptr<QgsProcessingParameterDefinition> paramDefinition( QgsProcessingParameters::parameterFromVariantMap( paramDef ) );
740
741 QgsProcessingModelParameter p;
742 p.loadVariant( componentDef );
743
744 // we need a unique name for the parameter
745 QString name = p.parameterName();
746 QString description = paramDefinition->description();
747 int next = 1;
748 while ( modelScene()->model()->parameterDefinition( name ) )
749 {
750 next++;
751 name = u"%1 (%2)"_s.arg( p.parameterName() ).arg( next );
752 description = u"%1 (%2)"_s.arg( paramDefinition->description() ).arg( next );
753 }
754 paramDefinition->setName( name );
755 paramDefinition->setDescription( description );
756 p.setParameterName( name );
757
758 modelScene()->model()->addModelParameter( paramDefinition.release(), p );
759 pastedParameters << p.parameterName();
760
761 if ( !pastedBounds.isValid() )
762 pastedBounds = QRectF( p.position() - QPointF( p.size().width() / 2.0, p.size().height() / 2.0 ), p.size() );
763 else
764 pastedBounds = pastedBounds.united( QRectF( p.position() - QPointF( p.size().width() / 2.0, p.size().height() / 2.0 ), p.size() ) );
765
766 if ( !p.comment()->description().isEmpty() )
767 pastedBounds = pastedBounds.united( QRectF( p.comment()->position() - QPointF( p.comment()->size().width() / 2.0, p.comment()->size().height() / 2.0 ), p.comment()->size() ) );
768 }
769
770 QStringList pastedAlgorithms;
771 for ( const QVariant &v : res.value( u"algs"_s ).toList() )
772 {
773 QgsProcessingModelChildAlgorithm alg;
774 alg.loadVariant( v.toMap() );
775
776 // ensure algorithm id is unique
777 if ( modelScene()->model()->childAlgorithms().contains( alg.childId() ) )
778 {
779 alg.generateChildId( *modelScene()->model() );
780 }
781 alg.reattach();
782
783 pastedAlgorithms << alg.childId();
784
785 if ( !pastedBounds.isValid() )
786 pastedBounds = QRectF( alg.position() - QPointF( alg.size().width() / 2.0, alg.size().height() / 2.0 ), alg.size() );
787 else
788 pastedBounds = pastedBounds.united( QRectF( alg.position() - QPointF( alg.size().width() / 2.0, alg.size().height() / 2.0 ), alg.size() ) );
789
790 if ( !alg.comment()->description().isEmpty() )
791 pastedBounds = pastedBounds.united( QRectF( alg.comment()->position() - QPointF( alg.comment()->size().width() / 2.0, alg.comment()->size().height() / 2.0 ), alg.comment()->size() ) );
792
793 const QMap<QString, QgsProcessingModelChildAlgorithm> existingAlgs = modelScene()->model()->childAlgorithms();
794
795 const QMap<QString, QgsProcessingModelOutput> outputs = alg.modelOutputs();
796 QMap<QString, QgsProcessingModelOutput> pastedOutputs;
797 for ( auto it = outputs.constBegin(); it != outputs.constEnd(); ++it )
798 {
799 QString name = it.value().name();
800 int next = 1;
801 bool unique = false;
802 while ( !unique )
803 {
804 unique = true;
805 for ( auto algIt = existingAlgs.constBegin(); algIt != existingAlgs.constEnd(); ++algIt )
806 {
807 const QMap<QString, QgsProcessingModelOutput> algOutputs = algIt->modelOutputs();
808 for ( auto outputIt = algOutputs.constBegin(); outputIt != algOutputs.constEnd(); ++outputIt )
809 {
810 if ( outputIt.value().name() == name )
811 {
812 unique = false;
813 break;
814 }
815 }
816 if ( !unique )
817 break;
818 }
819 if ( unique )
820 break;
821 next++;
822 name = u"%1 (%2)"_s.arg( it.value().name() ).arg( next );
823 }
824
825 QgsProcessingModelOutput newOutput = it.value();
826 newOutput.setName( name );
827 newOutput.setDescription( name );
828 pastedOutputs.insert( name, newOutput );
829
830 pastedBounds = pastedBounds.united( QRectF( newOutput.position() - QPointF( newOutput.size().width() / 2.0, newOutput.size().height() / 2.0 ), newOutput.size() ) );
831
832 if ( !alg.comment()->description().isEmpty() )
833 pastedBounds = pastedBounds.united( QRectF( newOutput.comment()->position() - QPointF( newOutput.comment()->size().width() / 2.0, newOutput.comment()->size().height() / 2.0 ), newOutput.comment()->size() ) );
834 }
835 alg.setModelOutputs( pastedOutputs );
836
837 modelScene()->model()->addChildAlgorithm( alg );
838 }
839
840 QPointF offset( 0, 0 );
841 switch ( mode )
842 {
843 case PasteModeInPlace:
844 break;
845
846 case PasteModeCursor:
847 case PasteModeCenter:
848 {
849 offset = pt - pastedBounds.topLeft();
850 break;
851 }
852 }
853
854 if ( !offset.isNull() )
855 {
856 for ( QgsProcessingModelGroupBox pastedGroup : std::as_const( pastedGroups ) )
857 {
858 pastedGroup.setPosition( pastedGroup.position() + offset );
859 modelScene()->model()->addGroupBox( pastedGroup );
860 }
861 for ( const QString &pastedParam : std::as_const( pastedParameters ) )
862 {
863 modelScene()->model()->parameterComponent( pastedParam ).setPosition( modelScene()->model()->parameterComponent( pastedParam ).position() + offset );
864 modelScene()->model()->parameterComponent( pastedParam ).comment()->setPosition( modelScene()->model()->parameterComponent( pastedParam ).comment()->position() + offset );
865 }
866 for ( const QString &pastedAlg : std::as_const( pastedAlgorithms ) )
867 {
868 modelScene()->model()->childAlgorithm( pastedAlg ).setPosition( modelScene()->model()->childAlgorithm( pastedAlg ).position() + offset );
869 modelScene()->model()->childAlgorithm( pastedAlg ).comment()->setPosition( modelScene()->model()->childAlgorithm( pastedAlg ).comment()->position() + offset );
870
871 const QMap<QString, QgsProcessingModelOutput> outputs = modelScene()->model()->childAlgorithm( pastedAlg ).modelOutputs();
872 for ( auto it = outputs.begin(); it != outputs.end(); ++it )
873 {
874 modelScene()->model()->childAlgorithm( pastedAlg ).modelOutput( it.key() ).setPosition( modelScene()->model()->childAlgorithm( pastedAlg ).modelOutput( it.key() ).position() + offset );
875 modelScene()->model()->childAlgorithm( pastedAlg ).modelOutput( it.key() ).comment()->setPosition( modelScene()->model()->childAlgorithm( pastedAlg ).modelOutput( it.key() ).comment()->position() + offset );
876 }
877 }
878 }
879
880 emit commandEnded();
881 }
882 }
883
884 modelScene()->rebuildRequired();
885}
886
887QgsModelViewSnapMarker::QgsModelViewSnapMarker()
888 : QGraphicsRectItem( QRectF( 0, 0, 0, 0 ) )
889{
890 QFont f;
891 QFontMetrics fm( f );
892 mSize = fm.horizontalAdvance( 'X' );
893 setPen( QPen( Qt::transparent, mSize ) );
894
895 setFlags( flags() | QGraphicsItem::ItemIgnoresTransformations );
896 setZValue( QgsModelGraphicsScene::ZSnapIndicator );
897}
898
899void QgsModelViewSnapMarker::paint( QPainter *p, const QStyleOptionGraphicsItem *, QWidget * )
900{
901 QPen pen( QColor( 255, 0, 0 ) );
902 pen.setWidth( 0 );
903 p->setPen( pen );
904 p->setBrush( Qt::NoBrush );
905
906 double halfSize = mSize / 2.0;
907 p->drawLine( QLineF( -halfSize, -halfSize, halfSize, halfSize ) );
908 p->drawLine( QLineF( -halfSize, halfSize, halfSize, -halfSize ) );
909}
910
911
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