QGIS API Documentation 3.99.0-Master (357b655ed83)
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 <QString>
32
33#include "moc_qgsmodelviewtoollink.cpp"
34
35using namespace Qt::StringLiterals;
36
38 : QgsModelViewTool( view, tr( "Link Tool" ) )
39{
40 setCursor( Qt::PointingHandCursor );
41 mBezierRubberBand = std::make_unique<QgsModelViewBezierRubberBand>( view );
42
43 mBezierRubberBand->setBrush( QBrush( QColor( 0, 0, 0, 63 ) ) );
44 mBezierRubberBand->setPen( QPen( QBrush( QColor( 0, 0, 0, 100 ) ), 0, Qt::SolidLine ) );
45}
46
48{
49 mBezierRubberBand->update( event->modelPoint(), Qt::KeyboardModifiers() );
50
51 // we need to manually pass this event down to items we want it to go to -- QGraphicsScene doesn't propagate
52 const QList<QGraphicsItem *> items = scene()->items( event->modelPoint() );
53
54 QgsModelDesignerSocketGraphicItem *socket = nullptr;
55 for ( QGraphicsItem *item : items )
56 {
57 socket = dynamic_cast<QgsModelDesignerSocketGraphicItem *>( item );
58 if ( !socket || mFromSocket == socket || mFromSocket->edge() == socket->edge() || mFromSocket->component() == socket->component() )
59 continue;
60
61 // snap
62 socket->modelHoverEnterEvent( event );
63 const QPointF rubberEndPos = socket->mapToScene( socket->position() );
64 mBezierRubberBand->update( rubberEndPos, Qt::KeyboardModifiers() );
65 break;
66 }
67
68 if ( mLastHoveredSocket && socket != mLastHoveredSocket )
69 {
70 mLastHoveredSocket->modelHoverLeaveEvent( event );
71 mLastHoveredSocket = nullptr;
72 }
73
74 if ( socket && socket != mLastHoveredSocket )
75 {
76 mLastHoveredSocket = socket;
77 }
78}
79
81{
82 if ( event->button() != Qt::LeftButton )
83 {
84 return;
85 }
86 mBezierRubberBand->finish( event->modelPoint() );
87 if ( mLastHoveredSocket )
88 {
89 mLastHoveredSocket->modelHoverLeaveEvent( nullptr );
90 mLastHoveredSocket = nullptr;
91 }
92
93 view()->setTool( mPreviousViewTool );
94
95 // we need to manually pass this event down to items we want it to go to -- QGraphicsScene doesn't propagate
96 const QList<QGraphicsItem *> items = scene()->items( event->modelPoint() );
97
98 mToSocket = nullptr;
99
100 for ( QGraphicsItem *item : items )
101 {
102 if ( QgsModelDesignerSocketGraphicItem *socket = dynamic_cast<QgsModelDesignerSocketGraphicItem *>( item ) )
103 {
104 // Skip if sockets are both input or both output or both from the same algorithm
105 if ( mFromSocket->edge() == socket->edge() || mFromSocket->component() == socket->component() )
106 continue;
107
108 mToSocket = socket;
109 break;
110 }
111 }
112
113 // Do nothing if cursor didn't land on another socket
114 if ( !mToSocket )
115 {
116 // but it might have been an unlink, so we properly end the command
117 view()->endCommand();
118 return;
119 }
120
121 // and we abort any pending unlink command to not litter the undo buffer
122 view()->abortCommand();
123
124 // Do nothing if from socket and to socket are both input or both output
125 if ( mFromSocket->edge() == mToSocket->edge() )
126 {
127 return;
128 }
129
139 if ( !mToSocket->isInput() )
140 {
141 std::swap( mFromSocket, mToSocket );
142 }
143
144 QgsProcessingModelComponent *outputComponent = mFromSocket->component();
145 QgsProcessingModelChildAlgorithm *inputChildAlgorithm = dynamic_cast<QgsProcessingModelChildAlgorithm *>( mToSocket->component() );
146 if ( !inputChildAlgorithm )
147 {
148 // Should not happen, but checking is cheap!
149 QgsDebugError( u"Input is not a QgsProcessingModelChildAlgorithm"_s );
150 return;
151 }
152
153 QgsProcessingModelChildParameterSource newInputParamSource;
154
155 QString outParamDescription;
156 if ( const QgsProcessingModelChildAlgorithm *outputChildAlgorithm = dynamic_cast<QgsProcessingModelChildAlgorithm *>( outputComponent ) )
157 {
158 const QString outParamName = outputChildAlgorithm->algorithm()->outputDefinitions().at( mFromSocket->index() )->name();
159 newInputParamSource = QgsProcessingModelChildParameterSource::fromChildOutput( outputChildAlgorithm->childId(), outParamName );
160 outParamDescription = outputChildAlgorithm->algorithm()->outputDefinitions().at( mFromSocket->index() )->description();
161 }
162 else if ( const QgsProcessingModelParameter *paramFrom = dynamic_cast<QgsProcessingModelParameter *>( outputComponent ) )
163 {
164 newInputParamSource = QgsProcessingModelChildParameterSource::fromModelParameter( paramFrom->parameterName() );
165 outParamDescription = paramFrom->description();
166 }
167
168 const QgsProcessingParameterDefinition *inputParam = inputChildAlgorithm->algorithm()->parameterDefinitions().at( mToSocket->index() );
169 const QList<QgsProcessingModelChildParameterSource> compatibleInputParamSources = scene()->model()->availableSourcesForChild( inputChildAlgorithm->childId(), inputParam );
170 if ( !compatibleInputParamSources.contains( newInputParamSource ) )
171 {
172 // Types are incompatible
173 const QString title = tr( "Sockets cannot be connected" );
174 const QString message = tr( "Either the sockets are incompatible or there is a circular dependency" );
175 scene()->showWarning( message, title, message );
176
177 // If the output was previously connected to an input, let's restore it.
178 if ( mPreviousInputSocketNumber != -1 )
179 {
180 QgsProcessingModelChildAlgorithm previousChildAlgorithm = scene()->model()->childAlgorithm( mPreviousInputChildId );
181 const QgsProcessingParameterDefinition *previousInputParam = previousChildAlgorithm.algorithm()->parameterDefinitions().at( mPreviousInputSocketNumber );
182 previousChildAlgorithm.addParameterSources( previousInputParam->name(), { newInputParamSource } );
183 scene()->model()->setChildAlgorithm( previousChildAlgorithm );
184 scene()->requestRebuildRequired();
185 }
186 return;
187 }
188
189 view()->beginCommand( tr( "Link %1: %2 to %3: %4" ).arg( outputComponent->description(), outParamDescription, inputChildAlgorithm->description(), inputParam->description() ) );
190
191 inputChildAlgorithm->addParameterSources( inputParam->name(), { newInputParamSource } );
192
193 //We need to pass the update child algorithm to the model
194 scene()->model()->setChildAlgorithm( *inputChildAlgorithm );
195
196 if ( inputChildAlgorithm->childId() == mPreviousInputChildId && mToSocket->index() == mPreviousInputSocketNumber )
197 {
198 // This is the input socket that was originally connected, let's keep the undo buffer clean since practically nothing changed.
199 view()->abortCommand();
200 }
201 else
202 {
203 view()->endCommand();
204 }
205
206 // Redraw
207 scene()->requestRebuildRequired();
208}
209
211{
212 return true;
213}
214
216{
217 QgsModelViewTool *tool = view()->tool();
218 // Make sure we always return to the select tool and not a temporary tool.
219 if ( dynamic_cast<QgsModelViewToolSelect *>( tool ) )
220 {
221 mPreviousViewTool = tool;
222 }
223
224 const QPointF rubberStartPos = mFromSocket->mapToScene( mFromSocket->position() );
225 mBezierRubberBand->start( rubberStartPos, Qt::KeyboardModifiers() );
226
228}
229
231{
232 mBezierRubberBand->finish();
234}
235
236void QgsModelViewToolLink::setFromSocket( QgsModelDesignerSocketGraphicItem *socket )
237{
238 mFromSocket = socket;
239 mPreviousInputChildId.clear();
240 mPreviousInputSocketNumber = -1;
241
242 // 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
243 if ( mFromSocket->isInput() )
244 {
245 QgsProcessingModelChildAlgorithm *childFrom = dynamic_cast<QgsProcessingModelChildAlgorithm *>( mFromSocket->component() );
246 if ( !childFrom )
247 return;
248
249 mPreviousInputSocketNumber = mFromSocket->index();
250 const QgsProcessingParameterDefinition *param = childFrom->algorithm()->parameterDefinitions().at( mPreviousInputSocketNumber );
251 const QList<QgsProcessingModelChildParameterSource> currentSources = childFrom->parameterSources().value( param->name() );
252 mPreviousInputChildId = childFrom->childId();
253
254 for ( const QgsProcessingModelChildParameterSource &source : currentSources )
255 {
256 // Was not connected, nothing to do
257 if ( ( source.source() == Qgis::ProcessingModelChildParameterSource::ChildOutput && source.outputChildId().isEmpty() ) || ( source.source() == Qgis::ProcessingModelChildParameterSource::ModelParameter && source.parameterName().isEmpty() ) )
258 continue;
259
260 switch ( source.source() )
261 {
264 {
265 view()->beginCommand( tr( "Unlink %1: %2", "Unlink Algorithm: Input" ).arg( childFrom->description(), param->description() ) );
266
267 // reset to default value.
268 QList<QgsProcessingModelChildParameterSource> newSources;
270 {
271 // Layers/feature sources default to an empty model input parameter
272 // This is the same default that a newly added algorithm uses. It's not the best, since when opening the algorithm's
273 // dialog it will be automatically assigned to the first available input, however it is more consistent behavior.
274 // If we defaulted to an empty static value, then it would be populated by the first available project layer which
275 // would be more confusing.
276 newSources << QgsProcessingModelChildParameterSource::fromModelParameter( QString() );
277 }
278 else
279 {
280 // Other parameters default to static value
281 newSources << QgsProcessingModelChildParameterSource::fromStaticValue( param->defaultValue() );
282 }
283
284 childFrom->addParameterSources( param->name(), newSources );
285 //We need to pass the update child algorithm to the model
286 scene()->model()->setChildAlgorithm( *childFrom );
287 // Redraw
288 scene()->requestRebuildRequired();
289
290 //Get socket from initial source alg / source parameter
291 QgsModelComponentGraphicItem *item = nullptr;
292 int socketIndex = -1;
294 {
295 item = scene()->childAlgorithmItem( source.outputChildId() );
296 auto algSource = dynamic_cast<QgsProcessingModelChildAlgorithm *>( item->component() );
297 if ( !algSource )
298 {
299 QgsDebugError( u"algSource not set, aborting!"_s );
300 return;
301 }
302 socketIndex = QgsProcessingUtils::outputDefinitionIndex( algSource->algorithm(), source.outputName() );
303 }
305 {
306 item = scene()->parameterItem( source.parameterName() );
307 socketIndex = 0;
308 }
309
310 if ( !item )
311 {
312 QgsDebugError( u"item not set, aborting!"_s );
313 return;
314 }
315
316 mFromSocket = item->outSocketAt( socketIndex );
317 }
318 break;
319
324 continue;
325 }
326
327 // Stop on first iteration to get only one link at a time
328 break;
329 }
330 }
331};
@ ExpressionText
Parameter value is taken from a text with expressions, evaluated just before the algorithm runs.
Definition qgis.h:3923
@ ModelOutput
Parameter value is linked to an output parameter for the model.
Definition qgis.h:3924
@ ChildOutput
Parameter value is taken from an output generated by a child algorithm.
Definition qgis.h:3920
@ ModelParameter
Parameter value is taken from a parent model parameter.
Definition qgis.h:3919
@ StaticValue
Parameter value is a static value.
Definition qgis.h:3921
@ Expression
Parameter value is taken from an expression, evaluated just before the algorithm runs.
Definition qgis.h:3922
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:59