QGIS API Documentation 4.0.0-Norrköping (1ddcee3d0e4)
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 // Workaround for Qt default behavior where during the scroll the visible scene rect would be also updated on the axis that is not being scrolled.
62 // With ScrollBarAlwaysOn, we ensure that the visible scene rect is stable during scroll.
63 // See https://github.com/qgis/QGIS/pull/64605#issuecomment-3771638032
64 setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOn );
65 setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOn );
66
67 connect( horizontalScrollBar(), &QScrollBar::valueChanged, this, &QgsModelGraphicsView::friendlySetSceneRect );
68 connect( verticalScrollBar(), &QScrollBar::valueChanged, this, &QgsModelGraphicsView::friendlySetSceneRect );
69
70 mSnapper.setSnapToGrid( true );
71}
72
73QgsModelGraphicsView::~QgsModelGraphicsView()
74{
75 emit willBeDeleted();
76}
77
78void QgsModelGraphicsView::dragEnterEvent( QDragEnterEvent *event )
79{
80 if ( event->mimeData()->hasFormat( u"application/x-vnd.qgis.qgis.algorithmid"_s ) || event->mimeData()->hasFormat( u"application/x-vnd.qgis.qgis.parametertypeid"_s ) || event->mimeData()->hasText() )
81 event->acceptProposedAction();
82 else
83 event->ignore();
84}
85
86void QgsModelGraphicsView::dropEvent( QDropEvent *event )
87{
88 const QPointF dropPoint = mapToScene( event->pos() );
89 if ( event->mimeData()->hasFormat( u"application/x-vnd.qgis.qgis.algorithmid"_s ) )
90 {
91 QByteArray data = event->mimeData()->data( u"application/x-vnd.qgis.qgis.algorithmid"_s );
92 QDataStream stream( &data, QIODevice::ReadOnly );
93 QString algorithmId;
94 stream >> algorithmId;
95
96 QTimer::singleShot( 0, this, [this, dropPoint, algorithmId] { emit algorithmDropped( algorithmId, dropPoint ); } );
97 event->accept();
98 }
99 else if ( event->mimeData()->hasFormat( u"application/x-vnd.qgis.qgis.parametertypeid"_s ) )
100 {
101 QByteArray data = event->mimeData()->data( u"application/x-vnd.qgis.qgis.parametertypeid"_s );
102 QDataStream stream( &data, QIODevice::ReadOnly );
103 QString paramTypeId;
104 stream >> paramTypeId;
105
106 QTimer::singleShot( 0, this, [this, dropPoint, paramTypeId] { emit inputDropped( paramTypeId, dropPoint ); } );
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] { emit inputDropped( itemId, dropPoint ); } );
113 event->accept();
114 }
115 else
116 {
117 event->ignore();
118 }
119}
120
121void QgsModelGraphicsView::dragMoveEvent( QDragMoveEvent *event )
122{
123 if ( event->mimeData()->hasFormat( u"application/x-vnd.qgis.qgis.algorithmid"_s ) || event->mimeData()->hasFormat( u"application/x-vnd.qgis.qgis.parametertypeid"_s ) || event->mimeData()->hasText() )
124 event->acceptProposedAction();
125 else
126 event->ignore();
127}
128
129void QgsModelGraphicsView::wheelEvent( QWheelEvent *event )
130{
131 if ( !scene() )
132 return;
133
134 if ( mTool )
135 {
136 mTool->wheelEvent( event );
137 }
138
139 if ( !mTool || !event->isAccepted() )
140 {
141 event->accept();
142 wheelZoom( event );
143 }
144}
145
146void QgsModelGraphicsView::wheelZoom( QWheelEvent *event )
147{
148 //get mouse wheel zoom behavior settings
149 QgsSettings settings;
150 double zoomFactor = settings.value( u"qgis/zoom_factor"_s, 2 ).toDouble();
151 bool reverseZoom = settings.value( u"qgis/reverse_wheel_zoom"_s, false ).toBool();
152 bool zoomIn = reverseZoom ? event->angleDelta().y() < 0 : event->angleDelta().y() > 0;
153
154 // "Normal" mouse have an angle delta of 120, precision mouses provide data faster, in smaller steps
155 zoomFactor = 1.0 + ( zoomFactor - 1.0 ) / 120.0 * std::fabs( event->angleDelta().y() );
156
157 if ( event->modifiers() & Qt::ControlModifier )
158 {
159 //holding ctrl while wheel zooming results in a finer zoom
160 zoomFactor = 1.0 + ( zoomFactor - 1.0 ) / 20.0;
161 }
162
163 //calculate zoom scale factor
164 double scaleFactor = ( zoomIn ? 1 / zoomFactor : zoomFactor );
165
166 //get current visible part of scene
167 QRect viewportRect( 0, 0, viewport()->width(), viewport()->height() );
168 QgsRectangle visibleRect = QgsRectangle( mapToScene( viewportRect ).boundingRect() );
169
170 //transform the mouse pos to scene coordinates
171 QPointF scenePoint = mapToScene( event->position().x(), event->position().y() );
172
173 //adjust view center
174 QgsPointXY oldCenter( visibleRect.center() );
175 QgsPointXY newCenter( scenePoint.x() + ( ( oldCenter.x() - scenePoint.x() ) * scaleFactor ), scenePoint.y() + ( ( oldCenter.y() - scenePoint.y() ) * scaleFactor ) );
176 centerOn( newCenter.x(), newCenter.y() );
177
178 //zoom layout
179 if ( zoomIn )
180 {
181 scaleSafe( zoomFactor );
182 }
183 else
184 {
185 scaleSafe( 1 / zoomFactor );
186 }
187}
188
189void QgsModelGraphicsView::scaleSafe( double scale )
190{
191 double currentScale = transform().m11();
192 scale *= currentScale;
193 scale = std::clamp( scale, MIN_VIEW_SCALE, MAX_VIEW_SCALE );
194 setTransform( QTransform::fromScale( scale, scale ) );
195}
196
197QPointF QgsModelGraphicsView::deltaForKeyEvent( QKeyEvent *event )
198{
199 // increment used for cursor key item movement
200 double increment = 1.0;
201 if ( event->modifiers() & Qt::ShiftModifier )
202 {
203 //holding shift while pressing cursor keys results in a big step
204 increment = 10.0;
205 }
206 else if ( event->modifiers() & Qt::AltModifier )
207 {
208 //holding alt while pressing cursor keys results in a 1 pixel step
209 double viewScale = transform().m11();
210 if ( viewScale > 0 )
211 {
212 increment = 1 / viewScale;
213 }
214 }
215
216 double deltaX = 0;
217 double deltaY = 0;
218 switch ( event->key() )
219 {
220 case Qt::Key_Left:
221 deltaX = -increment;
222 break;
223 case Qt::Key_Right:
224 deltaX = increment;
225 break;
226 case Qt::Key_Up:
227 deltaY = -increment;
228 break;
229 case Qt::Key_Down:
230 deltaY = increment;
231 break;
232 default:
233 break;
234 }
235
236 return QPointF( deltaX, deltaY );
237}
238
239void QgsModelGraphicsView::mousePressEvent( QMouseEvent *event )
240{
241 if ( !modelScene() )
242 return;
243
244 if ( mTool )
245 {
246 auto me = std::make_unique<QgsModelViewMouseEvent>( this, event, mTool->flags() & QgsModelViewTool::FlagSnaps );
247 mTool->modelPressEvent( me.get() );
248 event->setAccepted( me->isAccepted() );
249 }
250
251 if ( !mTool || !event->isAccepted() )
252 {
253 if ( event->button() == Qt::MiddleButton && mTool != mSpacePanTool && mTool != mSpaceZoomTool )
254 {
255 // Pan layout with middle mouse button
256 setTool( mMidMouseButtonPanTool );
257 event->accept();
258 }
259 else
260 {
261 QGraphicsView::mousePressEvent( event );
262 }
263 }
264}
265
266void QgsModelGraphicsView::mouseReleaseEvent( QMouseEvent *event )
267{
268 if ( !modelScene() )
269 return;
270
271 if ( mTool )
272 {
273 auto me = std::make_unique<QgsModelViewMouseEvent>( this, event, mTool->flags() & QgsModelViewTool::FlagSnaps );
274 mTool->modelReleaseEvent( me.get() );
275 event->setAccepted( me->isAccepted() );
276 }
277
278 if ( !mTool || !event->isAccepted() )
279 QGraphicsView::mouseReleaseEvent( event );
280}
281
282void QgsModelGraphicsView::mouseMoveEvent( QMouseEvent *event )
283{
284 if ( !modelScene() )
285 return;
286
287 mMouseCurrentXY = event->pos();
288
289 QPointF cursorPos = mapToScene( mMouseCurrentXY );
290 if ( mTool )
291 {
292 auto me = std::make_unique<QgsModelViewMouseEvent>( this, event, false );
293 if ( mTool->flags() & QgsModelViewTool::FlagSnaps )
294 {
295 me->snapPoint();
296 }
297 if ( mTool->flags() & QgsModelViewTool::FlagSnaps )
298 {
299 //draw snapping point indicator
300 if ( me->isSnapped() )
301 {
302 cursorPos = me->snappedPoint();
303 if ( mSnapMarker )
304 {
305 mSnapMarker->setPos( me->snappedPoint() );
306 mSnapMarker->setVisible( true );
307 }
308 }
309 else if ( mSnapMarker )
310 {
311 mSnapMarker->setVisible( false );
312 }
313 }
314 mTool->modelMoveEvent( me.get() );
315 event->setAccepted( me->isAccepted() );
316 }
317
318 if ( !mTool || !event->isAccepted() )
319 QGraphicsView::mouseMoveEvent( event );
320}
321
322void QgsModelGraphicsView::mouseDoubleClickEvent( QMouseEvent *event )
323{
324 if ( !modelScene() )
325 return;
326
327 if ( mTool )
328 {
329 auto me = std::make_unique<QgsModelViewMouseEvent>( this, event, mTool->flags() & QgsModelViewTool::FlagSnaps );
330 mTool->modelDoubleClickEvent( me.get() );
331 event->setAccepted( me->isAccepted() );
332 }
333
334 if ( !mTool || !event->isAccepted() )
335 QGraphicsView::mouseDoubleClickEvent( event );
336}
337
338void QgsModelGraphicsView::keyPressEvent( QKeyEvent *event )
339{
340 if ( !modelScene() )
341 return;
342
343 if ( mTool )
344 {
345 mTool->keyPressEvent( event );
346 }
347
348 if ( mTool && event->isAccepted() )
349 return;
350
351 if ( event->key() == Qt::Key_Space && !event->isAutoRepeat() && mTool != mMidMouseButtonPanTool )
352 {
353 if ( !( event->modifiers() & Qt::ControlModifier ) )
354 {
355 // Pan layout with space bar
356 setTool( mSpacePanTool );
357 }
358 else
359 {
360 //ctrl+space pressed, so switch to temporary keyboard based zoom tool
361 setTool( mSpaceZoomTool );
362 }
363 event->accept();
364 }
365 else if ( event->key() == Qt::Key_Left || event->key() == Qt::Key_Right || event->key() == Qt::Key_Up || event->key() == Qt::Key_Down )
366 {
367 QgsModelGraphicsScene *s = modelScene();
368 const QList<QgsModelComponentGraphicItem *> itemList = s->selectedComponentItems();
369 if ( !itemList.empty() )
370 {
371 QPointF delta = deltaForKeyEvent( event );
372
373 startMacroCommand( tr( "Move Items" ) );
374 for ( QgsModelComponentGraphicItem *item : itemList )
375 {
376 item->moveComponentBy( delta.x(), delta.y() );
377 }
378 itemList.at( 0 )->changed();
379 endMacroCommand();
380 }
381 event->accept();
382 }
383}
384
385void QgsModelGraphicsView::keyReleaseEvent( QKeyEvent *event )
386{
387 if ( !modelScene() )
388 return;
389
390 if ( mTool )
391 {
392 mTool->keyReleaseEvent( event );
393 }
394
395 if ( !mTool || !event->isAccepted() )
396 QGraphicsView::keyReleaseEvent( event );
397}
398
399void QgsModelGraphicsView::setModelScene( QgsModelGraphicsScene *scene )
400{
401 setScene( scene );
402
403 connect( scene, &QgsModelGraphicsScene::sceneRectChanged, this, &QgsModelGraphicsView::friendlySetSceneRect );
404
405 // IMPORTANT!
406 // previous snap markers, snap lines are owned by previous layout - so don't delete them here!
407 mSnapMarker = new QgsModelViewSnapMarker();
408 mSnapMarker->hide();
409 scene->addItem( mSnapMarker );
410}
411
412QgsModelGraphicsScene *QgsModelGraphicsView::modelScene() const
413{
414 return qobject_cast<QgsModelGraphicsScene *>( QgsModelGraphicsView::scene() );
415}
416
417QgsModelViewTool *QgsModelGraphicsView::tool()
418{
419 return mTool;
420}
421
422void QgsModelGraphicsView::setTool( QgsModelViewTool *tool )
423{
424 if ( !tool )
425 return;
426
427 if ( mTool )
428 {
429 mTool->deactivate();
430 disconnect( mTool, &QgsModelViewTool::itemFocused, this, &QgsModelGraphicsView::itemFocused );
431 }
432
433 // activate new tool before setting it - gives tools a chance
434 // to respond to whatever the current tool is
435 tool->activate();
436 mTool = tool;
437 connect( mTool, &QgsModelViewTool::itemFocused, this, &QgsModelGraphicsView::itemFocused );
438 emit toolSet( mTool );
439}
440
441void QgsModelGraphicsView::unsetTool( QgsModelViewTool *tool )
442{
443 if ( mTool && mTool == tool )
444 {
445 mTool->deactivate();
446 emit toolSet( nullptr );
447 setCursor( Qt::ArrowCursor );
448 }
449}
450
451QgsModelSnapper *QgsModelGraphicsView::snapper()
452{
453 return &mSnapper;
454}
455
456void QgsModelGraphicsView::startMacroCommand( const QString &text )
457{
458 emit macroCommandStarted( text );
459}
460
461void QgsModelGraphicsView::endMacroCommand()
462{
463 emit macroCommandEnded();
464}
465
466void QgsModelGraphicsView::beginCommand( const QString &text )
467{
468 emit commandBegun( text );
469}
470
471void QgsModelGraphicsView::endCommand()
472{
473 emit commandEnded();
474}
475
476void QgsModelGraphicsView::abortCommand()
477{
478 emit commandAborted();
479}
480
481void QgsModelGraphicsView::snapSelected()
482{
483 QgsModelGraphicsScene *s = modelScene();
484 const QList<QgsModelComponentGraphicItem *> itemList = s->selectedComponentItems();
485 startMacroCommand( tr( "Snap Items" ) );
486 if ( !itemList.empty() )
487 {
488 bool prevSetting = mSnapper.snapToGrid();
489 mSnapper.setSnapToGrid( true );
490 for ( QgsModelComponentGraphicItem *item : itemList )
491 {
492 bool wasSnapped = false;
493 QRectF snapped = mSnapper.snapRectWithResize( item->mapRectToScene( item->itemRect() ), transform().m11(), wasSnapped );
494 if ( wasSnapped )
495 {
496 item->setItemRect( snapped );
497 }
498 }
499 mSnapper.setSnapToGrid( prevSetting );
500 }
501 endMacroCommand();
502}
503
504void QgsModelGraphicsView::friendlySetSceneRect()
505{
506 if ( mBlockScrollbarSignals )
507 return;
508
509 const QRectF currentSceneRect = sceneRect();
510
511 const QRectF modelSceneRect = modelScene()->sceneRect();
512 const QRectF visibleRect = mapToScene( viewport()->rect() ).boundingRect();
513 QRectF newSceneRect;
514 newSceneRect.setLeft( std::min( modelSceneRect.left(), visibleRect.left() ) );
515 newSceneRect.setRight( std::max( modelSceneRect.right(), visibleRect.right() ) );
516 newSceneRect.setTop( std::min( modelSceneRect.top(), visibleRect.top() ) );
517 newSceneRect.setBottom( std::max( modelSceneRect.bottom(), visibleRect.bottom() ) );
518
519 // Qt scrollbar range are dealt in integer, so we round it ourselves to avoid a small "jump"
520 newSceneRect.setLeft( std::floor( newSceneRect.left() ) );
521 newSceneRect.setTop( std::floor( newSceneRect.top() ) );
522 newSceneRect.setRight( std::ceil( newSceneRect.right() ) );
523 newSceneRect.setBottom( std::ceil( newSceneRect.bottom() ) );
524
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(
834 QRectF( newOutput.comment()->position() - QPointF( newOutput.comment()->size().width() / 2.0, newOutput.comment()->size().height() / 2.0 ), newOutput.comment()->size() )
835 );
836 }
837 alg.setModelOutputs( pastedOutputs );
838
839 modelScene()->model()->addChildAlgorithm( alg );
840 }
841
842 QPointF offset( 0, 0 );
843 switch ( mode )
844 {
845 case PasteModeInPlace:
846 break;
847
848 case PasteModeCursor:
849 case PasteModeCenter:
850 {
851 offset = pt - pastedBounds.topLeft();
852 break;
853 }
854 }
855
856 if ( !offset.isNull() )
857 {
858 for ( QgsProcessingModelGroupBox pastedGroup : std::as_const( pastedGroups ) )
859 {
860 pastedGroup.setPosition( pastedGroup.position() + offset );
861 modelScene()->model()->addGroupBox( pastedGroup );
862 }
863 for ( const QString &pastedParam : std::as_const( pastedParameters ) )
864 {
865 modelScene()->model()->parameterComponent( pastedParam ).setPosition( modelScene()->model()->parameterComponent( pastedParam ).position() + offset );
866 modelScene()->model()->parameterComponent( pastedParam ).comment()->setPosition( modelScene()->model()->parameterComponent( pastedParam ).comment()->position() + offset );
867 }
868 for ( const QString &pastedAlg : std::as_const( pastedAlgorithms ) )
869 {
870 modelScene()->model()->childAlgorithm( pastedAlg ).setPosition( modelScene()->model()->childAlgorithm( pastedAlg ).position() + offset );
871 modelScene()->model()->childAlgorithm( pastedAlg ).comment()->setPosition( modelScene()->model()->childAlgorithm( pastedAlg ).comment()->position() + offset );
872
873 const QMap<QString, QgsProcessingModelOutput> outputs = modelScene()->model()->childAlgorithm( pastedAlg ).modelOutputs();
874 for ( auto it = outputs.begin(); it != outputs.end(); ++it )
875 {
876 modelScene()->model()->childAlgorithm( pastedAlg ).modelOutput( it.key() ).setPosition( modelScene()->model()->childAlgorithm( pastedAlg ).modelOutput( it.key() ).position() + offset );
877 modelScene()
878 ->model()
879 ->childAlgorithm( pastedAlg )
880 .modelOutput( it.key() )
881 .comment()
882 ->setPosition( modelScene()->model()->childAlgorithm( pastedAlg ).modelOutput( it.key() ).comment()->position() + offset );
883 }
884 }
885 }
886
887 emit commandEnded();
888 }
889 }
890
891 modelScene()->rebuildRequired();
892}
893
894QgsModelViewSnapMarker::QgsModelViewSnapMarker()
895 : QGraphicsRectItem( QRectF( 0, 0, 0, 0 ) )
896{
897 QFont f;
898 QFontMetrics fm( f );
899 mSize = fm.horizontalAdvance( 'X' );
900 setPen( QPen( Qt::transparent, mSize ) );
901
902 setFlags( flags() | QGraphicsItem::ItemIgnoresTransformations );
903 setZValue( QgsModelGraphicsScene::ZSnapIndicator );
904}
905
906void QgsModelViewSnapMarker::paint( QPainter *p, const QStyleOptionGraphicsItem *, QWidget * )
907{
908 QPen pen( QColor( 255, 0, 0 ) );
909 pen.setWidth( 0 );
910 p->setPen( pen );
911 p->setBrush( Qt::NoBrush );
912
913 double halfSize = mSize / 2.0;
914 p->drawLine( QLineF( -halfSize, -halfSize, halfSize, halfSize ) );
915 p->drawLine( QLineF( -halfSize, halfSize, halfSize, -halfSize ) );
916}
917
918
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