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