QGIS API Documentation 3.99.0-Master (26c88405ac0)
Loading...
Searching...
No Matches
qgsmodelviewtoollink.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsmodelviewtoollink.cpp
3 ------------------------------------
4 Date : January 2024
5 Copyright : (C) 2024 Valentin Buira
6 Email : valentin dot buira 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
18#include <memory>
19
20#include "qgsmodelgraphicitem.h"
30
31#include "moc_qgsmodelviewtoollink.cpp"
32
34 : QgsModelViewTool( view, tr( "Link Tool" ) )
35{
36 setCursor( Qt::PointingHandCursor );
37 mBezierRubberBand = std::make_unique<QgsModelViewBezierRubberBand>( view );
38
39 mBezierRubberBand->setBrush( QBrush( QColor( 0, 0, 0, 63 ) ) );
40 mBezierRubberBand->setPen( QPen( QBrush( QColor( 0, 0, 0, 100 ) ), 0, Qt::SolidLine ) );
41}
42
44{
45 mBezierRubberBand->update( event->modelPoint(), Qt::KeyboardModifiers() );
46
47 // we need to manually pass this event down to items we want it to go to -- QGraphicsScene doesn't propagate
48 const QList<QGraphicsItem *> items = scene()->items( event->modelPoint() );
49
50 QgsModelDesignerSocketGraphicItem *socket = nullptr;
51 for ( QGraphicsItem *item : items )
52 {
53 socket = dynamic_cast<QgsModelDesignerSocketGraphicItem *>( item );
54 if ( !socket || mFromSocket == socket || mFromSocket->edge() == socket->edge() || mFromSocket->component() == socket->component() )
55 continue;
56
57 // snap
58 socket->modelHoverEnterEvent( event );
59 const QPointF rubberEndPos = socket->mapToScene( socket->position() );
60 mBezierRubberBand->update( rubberEndPos, Qt::KeyboardModifiers() );
61 break;
62 }
63
64 if ( mLastHoveredSocket && socket != mLastHoveredSocket )
65 {
66 mLastHoveredSocket->modelHoverLeaveEvent( event );
67 mLastHoveredSocket = nullptr;
68 }
69
70 if ( socket && socket != mLastHoveredSocket )
71 {
72 mLastHoveredSocket = socket;
73 }
74}
75
77{
78 if ( event->button() != Qt::LeftButton )
79 {
80 return;
81 }
82 mBezierRubberBand->finish( event->modelPoint() );
83 if ( mLastHoveredSocket )
84 {
85 mLastHoveredSocket->modelHoverLeaveEvent( nullptr );
86 mLastHoveredSocket = nullptr;
87 }
88
89 view()->setTool( mPreviousViewTool );
90
91 // we need to manually pass this event down to items we want it to go to -- QGraphicsScene doesn't propagate
92 const QList<QGraphicsItem *> items = scene()->items( event->modelPoint() );
93
94 mToSocket = nullptr;
95
96 for ( QGraphicsItem *item : items )
97 {
98 if ( QgsModelDesignerSocketGraphicItem *socket = dynamic_cast<QgsModelDesignerSocketGraphicItem *>( item ) )
99 {
100 // Skip if sockets are both input or both output or both from the same algorithm
101 if ( mFromSocket->edge() == socket->edge() || mFromSocket->component() == socket->component() )
102 continue;
103
104 mToSocket = socket;
105 break;
106 }
107 }
108
109 // Do nothing if cursor didn't land on another socket
110 if ( !mToSocket )
111 {
112 // but it might have been an unlink, so we properly end the command
113 view()->endCommand();
114 return;
115 }
116
117 // and we abort any pending unlink command to not litter the undo buffer
118 view()->abortCommand();
119
120 // Do nothing if from socket and to socket are both input or both output
121 if ( mFromSocket->edge() == mToSocket->edge() )
122 {
123 return;
124 }
125
135 if ( !mToSocket->isInput() )
136 {
137 std::swap( mFromSocket, mToSocket );
138 }
139
140 QgsProcessingModelComponent *outputComponent = mFromSocket->component();
141 QgsProcessingModelChildAlgorithm *inputChildAlgorithm = dynamic_cast<QgsProcessingModelChildAlgorithm *>( mToSocket->component() );
142 if ( !inputChildAlgorithm )
143 {
144 // Should not happen, but checking is cheap!
145 QgsDebugError( QStringLiteral( "Input is not a QgsProcessingModelChildAlgorithm" ) );
146 return;
147 }
148
149 QgsProcessingModelChildParameterSource newInputParamSource;
150
151 QString outParamDescription;
152 if ( const QgsProcessingModelChildAlgorithm *outputChildAlgorithm = dynamic_cast<QgsProcessingModelChildAlgorithm *>( outputComponent ) )
153 {
154 const QString outParamName = outputChildAlgorithm->algorithm()->outputDefinitions().at( mFromSocket->index() )->name();
155 newInputParamSource = QgsProcessingModelChildParameterSource::fromChildOutput( outputChildAlgorithm->childId(), outParamName );
156 outParamDescription = outputChildAlgorithm->algorithm()->outputDefinitions().at( mFromSocket->index() )->description();
157 }
158 else if ( const QgsProcessingModelParameter *paramFrom = dynamic_cast<QgsProcessingModelParameter *>( outputComponent ) )
159 {
160 newInputParamSource = QgsProcessingModelChildParameterSource::fromModelParameter( paramFrom->parameterName() );
161 outParamDescription = paramFrom->description();
162 }
163
164 const QgsProcessingParameterDefinition *inputParam = inputChildAlgorithm->algorithm()->parameterDefinitions().at( mToSocket->index() );
165 const QList<QgsProcessingModelChildParameterSource> compatibleInputParamSources = scene()->model()->availableSourcesForChild( inputChildAlgorithm->childId(), inputParam );
166 if ( !compatibleInputParamSources.contains( newInputParamSource ) )
167 {
168 // Types are incompatible
169 const QString title = tr( "Sockets cannot be connected" );
170 const QString message = tr( "Either the sockets are incompatible or there is a circular dependency" );
171 scene()->showWarning( message, title, message );
172
173 // If the output was previously connected to an input, let's restore it.
174 if ( mPreviousInputSocketNumber != -1 )
175 {
176 QgsProcessingModelChildAlgorithm previousChildAlgorithm = scene()->model()->childAlgorithm( mPreviousInputChildId );
177 const QgsProcessingParameterDefinition *previousInputParam = previousChildAlgorithm.algorithm()->parameterDefinitions().at( mPreviousInputSocketNumber );
178 previousChildAlgorithm.addParameterSources( previousInputParam->name(), { newInputParamSource } );
179 scene()->model()->setChildAlgorithm( previousChildAlgorithm );
180 scene()->requestRebuildRequired();
181 }
182 return;
183 }
184
185 view()->beginCommand( tr( "Link %1: %2 to %3: %4" ).arg( outputComponent->description(), outParamDescription, inputChildAlgorithm->description(), inputParam->description() ) );
186
187 inputChildAlgorithm->addParameterSources( inputParam->name(), { newInputParamSource } );
188
189 //We need to pass the update child algorithm to the model
190 scene()->model()->setChildAlgorithm( *inputChildAlgorithm );
191
192 if ( inputChildAlgorithm->childId() == mPreviousInputChildId && mToSocket->index() == mPreviousInputSocketNumber )
193 {
194 // This is the input socket that was originally connected, let's keep the undo buffer clean since practically nothing changed.
195 view()->abortCommand();
196 }
197 else
198 {
199 view()->endCommand();
200 }
201
202 // Redraw
203 scene()->requestRebuildRequired();
204}
205
207{
208 return true;
209}
210
212{
213 QgsModelViewTool *tool = view()->tool();
214 // Make sure we always return to the select tool and not a temporary tool.
215 if ( dynamic_cast<QgsModelViewToolSelect *>( tool ) )
216 {
217 mPreviousViewTool = tool;
218 }
219
220 const QPointF rubberStartPos = mFromSocket->mapToScene( mFromSocket->position() );
221 mBezierRubberBand->start( rubberStartPos, Qt::KeyboardModifiers() );
222
224}
225
227{
228 mBezierRubberBand->finish();
230}
231
232void QgsModelViewToolLink::setFromSocket( QgsModelDesignerSocketGraphicItem *socket )
233{
234 mFromSocket = socket;
235 mPreviousInputChildId.clear();
236 mPreviousInputSocketNumber = -1;
237
238 // If it's an input socket and it's already connected, we want 'From' to be the output at the other end of the connection
239 if ( mFromSocket->isInput() )
240 {
241 QgsProcessingModelChildAlgorithm *childFrom = dynamic_cast<QgsProcessingModelChildAlgorithm *>( mFromSocket->component() );
242 if ( !childFrom )
243 return;
244
245 mPreviousInputSocketNumber = mFromSocket->index();
246 const QgsProcessingParameterDefinition *param = childFrom->algorithm()->parameterDefinitions().at( mPreviousInputSocketNumber );
247 const QList<QgsProcessingModelChildParameterSource> currentSources = childFrom->parameterSources().value( param->name() );
248 mPreviousInputChildId = childFrom->childId();
249
250 for ( const QgsProcessingModelChildParameterSource &source : currentSources )
251 {
252 // Was not connected, nothing to do
253 if ( ( source.source() == Qgis::ProcessingModelChildParameterSource::ChildOutput && source.outputChildId().isEmpty() ) || ( source.source() == Qgis::ProcessingModelChildParameterSource::ModelParameter && source.parameterName().isEmpty() ) )
254 continue;
255
256 switch ( source.source() )
257 {
260 {
261 view()->beginCommand( tr( "Unlink %1: %2", "Unlink Algorithm: Input" ).arg( childFrom->description(), param->description() ) );
262
263 // reset to default value.
264 QList<QgsProcessingModelChildParameterSource> newSources;
266 {
267 // Layers/feature sources default to an empty model input parameter
268 // This is the same default that a newly added algorithm uses. It's not the best, since when opening the algorithm's
269 // dialog it will be automatically assigned to the first available input, however it is more consistent behavior.
270 // If we defaulted to an empty static value, then it would be populated by the first available project layer which
271 // would be more confusing.
272 newSources << QgsProcessingModelChildParameterSource::fromModelParameter( QString() );
273 }
274 else
275 {
276 // Other parameters default to static value
277 newSources << QgsProcessingModelChildParameterSource::fromStaticValue( param->defaultValue() );
278 }
279
280 childFrom->addParameterSources( param->name(), newSources );
281 //We need to pass the update child algorithm to the model
282 scene()->model()->setChildAlgorithm( *childFrom );
283 // Redraw
284 scene()->requestRebuildRequired();
285
286 //Get socket from initial source alg / source parameter
287 QgsModelComponentGraphicItem *item = nullptr;
288 int socketIndex = -1;
290 {
291 item = scene()->childAlgorithmItem( source.outputChildId() );
292 auto algSource = dynamic_cast<QgsProcessingModelChildAlgorithm *>( item->component() );
293 if ( !algSource )
294 {
295 QgsDebugError( QStringLiteral( "algSource not set, aborting!" ) );
296 return;
297 }
298 socketIndex = QgsProcessingUtils::outputDefinitionIndex( algSource->algorithm(), source.outputName() );
299 }
301 {
302 item = scene()->parameterItem( source.parameterName() );
303 socketIndex = 0;
304 }
305
306 if ( !item )
307 {
308 QgsDebugError( QStringLiteral( "item not set, aborting!" ) );
309 return;
310 }
311
312 mFromSocket = item->outSocketAt( socketIndex );
313 }
314 break;
315
320 continue;
321 }
322
323 // Stop on first iteration to get only one link at a time
324 break;
325 }
326 }
327};
@ ExpressionText
Parameter value is taken from a text with expressions, evaluated just before the algorithm runs.
Definition qgis.h:3852
@ ModelOutput
Parameter value is linked to an output parameter for the model.
Definition qgis.h:3853
@ ChildOutput
Parameter value is taken from an output generated by a child algorithm.
Definition qgis.h:3849
@ ModelParameter
Parameter value is taken from a parent model parameter.
Definition qgis.h:3848
@ StaticValue
Parameter value is a static value.
Definition qgis.h:3850
@ Expression
Parameter value is taken from an expression, evaluated just before the algorithm runs.
Definition qgis.h:3851
A mouse event which is the result of a user interaction with a QgsModelGraphicsView.
QPointF modelPoint() const
Returns the event point location in model coordinates.
Model designer view tool for selecting items in the model.
QgsModelGraphicsView * view() const
Returns the view associated with the tool.
void setCursor(const QCursor &cursor)
Sets a user defined cursor for use when the tool is active.
virtual void deactivate()
Called when tool is deactivated.
QgsModelViewTool(QgsModelGraphicsView *view, const QString &name)
Constructor for QgsModelViewTool, taking a model view and tool name as parameters.
virtual void activate()
Called when tool is set as the currently active model tool.
QgsModelGraphicsScene * scene() const
Returns the scene associated with the tool.
QgsProcessingParameterDefinitions parameterDefinitions() const
Returns an ordered list of parameter definitions utilized by the algorithm.
Base class for the definition of processing parameters.
QVariant defaultValue() const
Returns the default value for the parameter.
QgsProcessingAlgorithm * algorithm() const
Returns a pointer to the algorithm which owns this parameter.
QString description() const
Returns the description for the parameter.
virtual QString type() const =0
Unique parameter type name.
QString name() const
Returns the name of the parameter.
static QString typeName()
Returns the type name for the parameter class.
static QString typeName()
Returns the type name for the parameter class.
static QString typeName()
Returns the type name for the parameter class.
static QString typeName()
Returns the type name for the parameter class.
static QString typeName()
Returns the type name for the parameter class.
static QString typeName()
Returns the type name for the parameter class.
static int outputDefinitionIndex(const QgsProcessingAlgorithm *algorithm, const QString &name)
Returns the index of the output matching name for a specified algorithm.
#define QgsDebugError(str)
Definition qgslogger.h:57