QWidget *QgsProcessingCrsWidgetWrapper::createWidget()
{
  mProjectionSelectionWidget = new QgsProjectionSelectionWidget();
  mProjectionSelectionWidget->setToolTip( parameterDefinition()->toolTip() );

  if ( parameterDefinition()->flags() & QgsProcessingParameterDefinition::FlagOptional )
    mProjectionSelectionWidget->setOptionVisible( QgsProjectionSelectionWidget::CrsNotSet, true );
  else
    mProjectionSelectionWidget->setOptionVisible( QgsProjectionSelectionWidget::CrsNotSet, false );

  connect( mProjectionSelectionWidget, &QgsProjectionSelectionWidget::crsChanged, this, [ = ]
  {
    emit widgetValueHasChanged( this );
  } );

  switch ( type() )
  {
    case QgsProcessingGui::Standard:
    case QgsProcessingGui::Batch:
    {
      return mProjectionSelectionWidget;
    };

    case QgsProcessingGui::Modeler:
    {
      QWidget *w = new QWidget();
      w->setToolTip( parameterDefinition()->toolTip() );

      QVBoxLayout *vl = new QVBoxLayout();
      vl->setMargin( 0 );
      vl->setContentsMargins( 0, 0, 0, 0 );
      w->setLayout( vl );

      mUseProjectCrsCheckBox = new QCheckBox( tr( "Use project CRS" ) );
      mUseProjectCrsCheckBox->setToolTip( tr( "Always use the current project CRS when running the model" ) );
      vl->addWidget( mUseProjectCrsCheckBox );
      connect( mUseProjectCrsCheckBox, &QCheckBox::toggled, mProjectionSelectionWidget, &QgsProjectionSelectionWidget::setDisabled );
      connect( mUseProjectCrsCheckBox, &QCheckBox::toggled, this, [ = ]
      {
        emit widgetValueHasChanged( this );
      } );

      vl->addWidget( mProjectionSelectionWidget );

      return w;
    }
  }
  return nullptr;
}
void QgsProcessingStringWidgetWrapper::setWidgetValue( const QVariant &value, QgsProcessingContext &context )
{
  const QString v = QgsProcessingParameters::parameterAsString( parameterDefinition(), value, context );
  if ( mLineEdit )
    mLineEdit->setText( v );
  if ( mPlainTextEdit )
    mPlainTextEdit->setPlainText( v );
}
QWidget *QgsProcessingStringWidgetWrapper::createWidget()
{
  switch ( type() )
  {
    case QgsProcessingGui::Standard:
    case QgsProcessingGui::Modeler:
    {
      if ( static_cast< const QgsProcessingParameterString * >( parameterDefinition() )->multiLine() )
      {
        mPlainTextEdit = new QPlainTextEdit();
        mPlainTextEdit->setToolTip( parameterDefinition()->toolTip() );

        connect( mPlainTextEdit, &QPlainTextEdit::textChanged, this, [ = ]
        {
          emit widgetValueHasChanged( this );
        } );
        return mPlainTextEdit;
      }
      else
      {
        mLineEdit = new QLineEdit();
        mLineEdit->setToolTip( parameterDefinition()->toolTip() );

        connect( mLineEdit, &QLineEdit::textChanged, this, [ = ]
        {
          emit widgetValueHasChanged( this );
        } );
        return mLineEdit;
      }
    };

    case QgsProcessingGui::Batch:
    {
      mLineEdit = new QLineEdit();
      mLineEdit->setToolTip( parameterDefinition()->toolTip() );

      connect( mLineEdit, &QLineEdit::textChanged, this, [ = ]
      {
        emit widgetValueHasChanged( this );
      } );
      return mLineEdit;
    }
  }
  return nullptr;
}
void QgsProcessingAlgorithm::removeParameter( const QString &name )
{
  const QgsProcessingParameterDefinition *def = parameterDefinition( name );
  if ( def )
  {
    delete def;
    mParameters.removeAll( def );
  }
}
void QgsProcessingBooleanWidgetWrapper::setWidgetValue( const QVariant &value, QgsProcessingContext &context )
{
  switch ( type() )
  {
    case QgsProcessingGui::Standard:
    {
      const bool v = QgsProcessingParameters::parameterAsBool( parameterDefinition(), value, context );
      mCheckBox->setChecked( v );
      break;
    }

    case QgsProcessingGui::Batch:
    case QgsProcessingGui::Modeler:
    {
      const bool v = QgsProcessingParameters::parameterAsBool( parameterDefinition(), value, context );
      mComboBox->setCurrentIndex( mComboBox->findData( v ) );
      break;
    }
  }
}
QWidget *QgsProcessingBooleanWidgetWrapper::createWidget()
{
  switch ( type() )
  {
    case QgsProcessingGui::Standard:
    {
      QString description = parameterDefinition()->description();
      if ( parameterDefinition()->flags() & QgsProcessingParameterDefinition::FlagOptional )
        description = QObject::tr( "%1 [optional]" ).arg( description );

      mCheckBox = new QCheckBox( description );
      mCheckBox->setToolTip( parameterDefinition()->toolTip() );

      connect( mCheckBox, &QCheckBox::toggled, this, [ = ]
      {
        emit widgetValueHasChanged( this );
      } );
      return mCheckBox;
    };

    case QgsProcessingGui::Batch:
    case QgsProcessingGui::Modeler:
    {
      mComboBox = new QComboBox();
      mComboBox->addItem( tr( "Yes" ), true );
      mComboBox->addItem( tr( "No" ), false );
      mComboBox->setToolTip( parameterDefinition()->toolTip() );

      connect( mComboBox, qgis::overload< int>::of( &QComboBox::currentIndexChanged ), this, [ = ]
      {
        emit widgetValueHasChanged( this );
      } );

      return mComboBox;
    }
  }
  return nullptr;
}
void QgsProcessingNumericWidgetWrapper::setWidgetValue( const QVariant &value, QgsProcessingContext &context )
{
  if ( mDoubleSpinBox )
  {
    if ( mAllowingNull && !value.isValid() )
      mDoubleSpinBox->clear();
    else
    {
      const double v = QgsProcessingParameters::parameterAsDouble( parameterDefinition(), value, context );
      mDoubleSpinBox->setValue( v );
    }
  }
  else if ( mSpinBox )
  {
    if ( mAllowingNull && !value.isValid() )
      mSpinBox->clear();
    else
    {
      const int v = QgsProcessingParameters::parameterAsInt( parameterDefinition(), value, context );
      mSpinBox->setValue( v );
    }
  }
}
void QgsProcessingRangeWidgetWrapper::setWidgetValue( const QVariant &value, QgsProcessingContext &context )
{
  const QList< double > v = QgsProcessingParameters::parameterAsRange( parameterDefinition(), value, context );
  if ( v.empty() )
    return;

  mBlockChangedSignal++;
  mMinSpinBox->setValue( v.at( 0 ) );
  if ( v.count() >= 2 )
    mMaxSpinBox->setValue( v.at( 1 ) );
  mBlockChangedSignal--;

  if ( !mBlockChangedSignal )
    emit widgetValueHasChanged( this );
}
void QgsProcessingCrsWidgetWrapper::setWidgetValue( const QVariant &value, QgsProcessingContext &context )
{
  if ( mUseProjectCrsCheckBox )
  {
    if ( value.toString().compare( QLatin1String( "ProjectCrs" ), Qt::CaseInsensitive ) == 0 )
    {
      mUseProjectCrsCheckBox->setChecked( true );
      return;
    }
    else
    {
      mUseProjectCrsCheckBox->setChecked( false );
    }
  }

  const QgsCoordinateReferenceSystem v = QgsProcessingParameters::parameterAsCrs( parameterDefinition(), value, context );
  if ( mProjectionSelectionWidget )
    mProjectionSelectionWidget->setCrs( v );
}
QWidget *QgsProcessingAuthConfigWidgetWrapper::createWidget()
{
  switch ( type() )
  {
    case QgsProcessingGui::Standard:
    case QgsProcessingGui::Modeler:
    case QgsProcessingGui::Batch:
    {
      mAuthConfigSelect = new QgsAuthConfigSelect();
      mAuthConfigSelect->setToolTip( parameterDefinition()->toolTip() );

      connect( mAuthConfigSelect, &QgsAuthConfigSelect::selectedConfigIdChanged, this, [ = ]
      {
        emit widgetValueHasChanged( this );
      } );
      return mAuthConfigSelect;
    };
  }
  return nullptr;
}
void QgsProcessingDistanceWidgetWrapper::setUnitParameterValue( const QVariant &value )
{
  QgsUnitTypes::DistanceUnit units = QgsUnitTypes::DistanceUnknownUnit;

  // evaluate value to layer
  QgsProcessingContext *context = nullptr;
  std::unique_ptr< QgsProcessingContext > tmpContext;
  if ( mProcessingContextGenerator )
    context = mProcessingContextGenerator->processingContext();

  if ( !context )
  {
    tmpContext = qgis::make_unique< QgsProcessingContext >();
    context = tmpContext.get();
  }

  QgsCoordinateReferenceSystem crs = QgsProcessingParameters::parameterAsCrs( parameterDefinition(), value, *context );
  if ( crs.isValid() )
  {
    units = crs.mapUnits();
  }

  setUnits( units );
}
void QgsProcessingDistanceWidgetWrapper::postInitialize( const QList<QgsAbstractProcessingParameterWidgetWrapper *> &wrappers )
{
  QgsProcessingNumericWidgetWrapper::postInitialize( wrappers );
  switch ( type() )
  {
    case QgsProcessingGui::Standard:
    {
      for ( const QgsAbstractProcessingParameterWidgetWrapper *wrapper : wrappers )
      {
        if ( wrapper->parameterDefinition()->name() == static_cast< const QgsProcessingParameterDistance * >( parameterDefinition() )->parentParameterName() )
        {
          setUnitParameterValue( wrapper->parameterValue() );
          connect( wrapper, &QgsAbstractProcessingParameterWidgetWrapper::widgetValueHasChanged, this, [ = ]
          {
            setUnitParameterValue( wrapper->parameterValue() );
          } );
          break;
        }
      }
      break;
    }

    case QgsProcessingGui::Batch:
    case QgsProcessingGui::Modeler:
      break;
  }
}
QWidget *QgsProcessingDistanceWidgetWrapper::createWidget()
{
  const QgsProcessingParameterDistance *distanceDef = static_cast< const QgsProcessingParameterDistance * >( parameterDefinition() );

  QWidget *spin = QgsProcessingNumericWidgetWrapper::createWidget();
  switch ( type() )
  {
    case QgsProcessingGui::Standard:
    {
      mLabel = new QLabel();
      mUnitsCombo = new QComboBox();

      mUnitsCombo->addItem( QgsUnitTypes::toString( QgsUnitTypes::DistanceMeters ), QgsUnitTypes::DistanceMeters );
      mUnitsCombo->addItem( QgsUnitTypes::toString( QgsUnitTypes::DistanceKilometers ), QgsUnitTypes::DistanceKilometers );
      mUnitsCombo->addItem( QgsUnitTypes::toString( QgsUnitTypes::DistanceFeet ), QgsUnitTypes::DistanceFeet );
      mUnitsCombo->addItem( QgsUnitTypes::toString( QgsUnitTypes::DistanceMiles ), QgsUnitTypes::DistanceMiles );
      mUnitsCombo->addItem( QgsUnitTypes::toString( QgsUnitTypes::DistanceYards ), QgsUnitTypes::DistanceYards );

      const int labelMargin = static_cast< int >( std::round( mUnitsCombo->fontMetrics().width( 'X' ) ) );
      QHBoxLayout *layout = new QHBoxLayout();
      layout->addWidget( spin, 1 );
      layout->insertSpacing( 1, labelMargin / 2 );
      layout->insertWidget( 2, mLabel );
      layout->insertWidget( 3, mUnitsCombo );

      // bit of fiddlyness here -- we want the initial spacing to only be visible
      // when the warning label is shown, so it's embedded inside mWarningLabel
      // instead of outside it
      mWarningLabel = new QWidget();
      QHBoxLayout *warningLayout = new QHBoxLayout();
      warningLayout->setMargin( 0 );
      warningLayout->setContentsMargins( 0, 0, 0, 0 );
      QLabel *warning = new QLabel();
      QIcon icon = QgsApplication::getThemeIcon( QStringLiteral( "mIconWarning.svg" ) );
      const int size = static_cast< int >( std::max( 24.0, spin->minimumSize().height() * 0.5 ) );
      warning->setPixmap( icon.pixmap( icon.actualSize( QSize( size, size ) ) ) );
      warning->setToolTip( tr( "Distance is in geographic degrees. Consider reprojecting to a projected local coordinate system for accurate results." ) );
      warningLayout->insertSpacing( 0, labelMargin / 2 );
      warningLayout->insertWidget( 1, warning );
      mWarningLabel->setLayout( warningLayout );
      layout->insertWidget( 4, mWarningLabel );

      setUnits( distanceDef->defaultUnit() );

      QWidget *w = new QWidget();
      layout->setMargin( 0 );
      layout->setContentsMargins( 0, 0, 0, 0 );
      w->setLayout( layout );
      return w;
    }

    case QgsProcessingGui::Batch:
    case QgsProcessingGui::Modeler:
      return spin;

  }
  return nullptr;
}
QgsProcessingFeatureSource *QgsProcessingAlgorithm::parameterAsSource( const QVariantMap &parameters, const QString &name, QgsProcessingContext &context ) const
{
  return QgsProcessingParameters::parameterAsSource( parameterDefinition( name ), parameters, context );
}
QWidget *QgsProcessingNumericWidgetWrapper::createWidget()
{
  const QgsProcessingParameterNumber *numberDef = static_cast< const QgsProcessingParameterNumber * >( parameterDefinition() );
  const QVariantMap metadata = numberDef->metadata();
  const int decimals = metadata.value( QStringLiteral( "widget_wrapper" ) ).toMap().value( QStringLiteral( "decimals" ), 6 ).toInt();
  switch ( type() )
  {
    case QgsProcessingGui::Standard:
    case QgsProcessingGui::Modeler:
    case QgsProcessingGui::Batch:
    {
      // lots of duplicate code here -- but there's no common interface between QSpinBox/QDoubleSpinBox which would allow us to avoid this
      QAbstractSpinBox *spinBox = nullptr;
      switch ( numberDef->dataType() )
      {
        case QgsProcessingParameterNumber::Double:
          mDoubleSpinBox = new QgsDoubleSpinBox();
          mDoubleSpinBox->setExpressionsEnabled( true );
          mDoubleSpinBox->setDecimals( decimals );

          // guess reasonable step value for double spin boxes
          if ( !qgsDoubleNear( numberDef->maximum(), std::numeric_limits<double>::max() ) &&
               !qgsDoubleNear( numberDef->minimum(), std::numeric_limits<double>::lowest() + 1 ) )
          {
            double singleStep = calculateStep( numberDef->minimum(), numberDef->maximum() );
            singleStep = std::max( singleStep, std::pow( 10, -decimals ) );
            mDoubleSpinBox->setSingleStep( singleStep );
          }

          spinBox = mDoubleSpinBox;
          break;

        case QgsProcessingParameterNumber::Integer:
          mSpinBox = new QgsSpinBox();
          mSpinBox->setExpressionsEnabled( true );
          spinBox = mSpinBox;
          break;
      }
      spinBox->setToolTip( parameterDefinition()->toolTip() );

      double max = 999999999;
      if ( !qgsDoubleNear( numberDef->maximum(), std::numeric_limits<double>::max() ) )
      {
        max = numberDef->maximum();
      }
      double min = -999999999;
      if ( !qgsDoubleNear( numberDef->minimum(), std::numeric_limits<double>::lowest() ) )
      {
        min = numberDef->minimum();
      }
      if ( mDoubleSpinBox )
      {
        mDoubleSpinBox->setMinimum( min );
        mDoubleSpinBox->setMaximum( max );
      }
      else
      {
        mSpinBox->setMinimum( static_cast< int >( min ) );
        mSpinBox->setMaximum( static_cast< int >( max ) );
      }

      if ( numberDef->flags() & QgsProcessingParameterDefinition::FlagOptional )
      {
        mAllowingNull = true;
        if ( mDoubleSpinBox )
        {
          mDoubleSpinBox->setShowClearButton( true );
          const double min = mDoubleSpinBox->minimum() - 1;
          mDoubleSpinBox->setMinimum( min );
          mDoubleSpinBox->setValue( min );
        }
        else
        {
          mSpinBox->setShowClearButton( true );
          const int min = mSpinBox->minimum() - 1;
          mSpinBox->setMinimum( min );
          mSpinBox->setValue( min );
        }
        spinBox->setSpecialValueText( tr( "Not set" ) );
      }
      else
      {
        if ( numberDef->defaultValue().isValid() )
        {
          // if default value for parameter, we clear to that
          bool ok = false;
          if ( mDoubleSpinBox )
          {
            double defaultVal = numberDef->defaultValue().toDouble( &ok );
            if ( ok )
              mDoubleSpinBox->setClearValue( defaultVal );
          }
          else
          {
            int intVal = numberDef->defaultValue().toInt( &ok );
            if ( ok )
              mSpinBox->setClearValue( intVal );
          }
        }
        else if ( !qgsDoubleNear( numberDef->minimum(), std::numeric_limits<double>::lowest() ) )
        {
          // otherwise we clear to the minimum, if it's set
          if ( mDoubleSpinBox )
            mDoubleSpinBox->setClearValue( numberDef->minimum() );
          else
            mSpinBox->setClearValue( static_cast< int >( numberDef->minimum() ) );
        }
        else
        {
          // last resort, we clear to 0
          if ( mDoubleSpinBox )
          {
            mDoubleSpinBox->setValue( 0 );
            mDoubleSpinBox->setClearValue( 0 );
          }
          else
          {
            mSpinBox->setValue( 0 );
            mSpinBox->setClearValue( 0 );
          }
        }
      }

      if ( mDoubleSpinBox )
        connect( mDoubleSpinBox, qgis::overload<double>::of( &QgsDoubleSpinBox::valueChanged ), this, [ = ] { emit widgetValueHasChanged( this ); } );
      else if ( mSpinBox )
        connect( mSpinBox, qgis::overload<int>::of( &QgsSpinBox::valueChanged ), this, [ = ] { emit widgetValueHasChanged( this ); } );

      return spinBox;
    };
  }
  return nullptr;
}
void QgsProcessingAuthConfigWidgetWrapper::setWidgetValue( const QVariant &value, QgsProcessingContext &context )
{
  const QString v = QgsProcessingParameters::parameterAsString( parameterDefinition(), value, context );
  if ( mAuthConfigSelect )
    mAuthConfigSelect->setConfigId( v );
}
QStringList QgsProcessingAlgorithm::parameterAsFields( const QVariantMap &parameters, const QString &name, QgsProcessingContext &context ) const
{
  return QgsProcessingParameters::parameterAsFields( parameterDefinition( name ), parameters, context );
}
QgsFeatureSink *QgsProcessingAlgorithm::parameterAsSink( const QVariantMap &parameters, const QString &name, QgsProcessingContext &context, QString &destinationIdentifier, const QgsFields &fields, QgsWkbTypes::Type geometryType, const QgsCoordinateReferenceSystem &crs, QgsFeatureSink::SinkFlags sinkFlags ) const
{
  return QgsProcessingParameters::parameterAsSink( parameterDefinition( name ), parameters, fields, geometryType, crs, context, destinationIdentifier, sinkFlags );
}
QgsVectorLayer *QgsProcessingAlgorithm::parameterAsVectorLayer( const QVariantMap &parameters, const QString &name, QgsProcessingContext &context ) const
{
  return QgsProcessingParameters::parameterAsVectorLayer( parameterDefinition( name ), parameters, context );
}
QgsGeometry QgsProcessingAlgorithm::parameterAsExtentGeometry( const QVariantMap &parameters, const QString &name, QgsProcessingContext &context, const QgsCoordinateReferenceSystem &crs )
{
  return QgsProcessingParameters::parameterAsExtentGeometry( parameterDefinition( name ), parameters, context, crs );
}
QgsCoordinateReferenceSystem QgsProcessingAlgorithm::parameterAsPointCrs( const QVariantMap &parameters, const QString &name, QgsProcessingContext &context )
{
  return QgsProcessingParameters::parameterAsPointCrs( parameterDefinition( name ), parameters, context );
}
QList<QgsMapLayer *> QgsProcessingAlgorithm::parameterAsLayerList( const QVariantMap &parameters, const QString &name, QgsProcessingContext &context ) const
{
  return QgsProcessingParameters::parameterAsLayerList( parameterDefinition( name ), parameters, context );
}
Beispiel #23
0
QVariantMap QgsBufferAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) const
{
  std::unique_ptr< QgsFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
  if ( !source )
    return QVariantMap();

  QString dest;
  std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT_LAYER" ), context, source->fields(), QgsWkbTypes::Polygon, source->sourceCrs(), dest ) );
  if ( !sink )
    return QVariantMap();

  // fixed parameters
  bool dissolve = parameterAsBool( parameters, QStringLiteral( "DISSOLVE" ), context );
  int segments = parameterAsInt( parameters, QStringLiteral( "SEGMENTS" ), context );
  QgsGeometry::EndCapStyle endCapStyle = static_cast< QgsGeometry::EndCapStyle >( 1 + parameterAsInt( parameters, QStringLiteral( "END_CAP_STYLE" ), context ) );
  QgsGeometry::JoinStyle joinStyle = static_cast< QgsGeometry::JoinStyle>( 1 + parameterAsInt( parameters, QStringLiteral( "JOIN_STYLE" ), context ) );
  double miterLimit = parameterAsDouble( parameters, QStringLiteral( "MITRE_LIMIT" ), context );
  double bufferDistance = parameterAsDouble( parameters, QStringLiteral( "DISTANCE" ), context );
  bool dynamicBuffer = QgsProcessingParameters::isDynamic( parameters, QStringLiteral( "DISTANCE" ) );
  const QgsProcessingParameterDefinition *distanceParamDef = parameterDefinition( QStringLiteral( "DISTANCE" ) );

  long count = source->featureCount();
  if ( count <= 0 )
    return QVariantMap();

  QgsFeature f;
  QgsFeatureIterator it = source->getFeatures();

  double step = 100.0 / count;
  int current = 0;

  QList< QgsGeometry > bufferedGeometriesForDissolve;
  QgsAttributes dissolveAttrs;

  while ( it.nextFeature( f ) )
  {
    if ( feedback->isCanceled() )
    {
      break;
    }
    if ( dissolveAttrs.isEmpty() )
      dissolveAttrs = f.attributes();

    QgsFeature out = f;
    if ( out.hasGeometry() )
    {
      if ( dynamicBuffer )
      {
        context.expressionContext().setFeature( f );
        bufferDistance = QgsProcessingParameters::parameterAsDouble( distanceParamDef, parameters, context );
      }

      QgsGeometry outputGeometry = f.geometry().buffer( bufferDistance, segments, endCapStyle, joinStyle, miterLimit );
      if ( !outputGeometry )
      {
        QgsMessageLog::logMessage( QObject::tr( "Error calculating buffer for feature %1" ).arg( f.id() ), QObject::tr( "Processing" ), QgsMessageLog::WARNING );
      }
      if ( dissolve )
        bufferedGeometriesForDissolve << outputGeometry;
      else
        out.setGeometry( outputGeometry );
    }

    if ( !dissolve )
      sink->addFeature( out );

    feedback->setProgress( current * step );
    current++;
  }

  if ( dissolve )
  {
    QgsGeometry finalGeometry = QgsGeometry::unaryUnion( bufferedGeometriesForDissolve );
    QgsFeature f;
    f.setGeometry( finalGeometry );
    f.setAttributes( dissolveAttrs );
    sink->addFeature( f );
  }

  QVariantMap outputs;
  outputs.insert( QStringLiteral( "OUTPUT_LAYER" ), dest );
  return outputs;
}
QgsLayoutItem *QgsProcessingAlgorithm::parameterAsLayoutItem( const QVariantMap &parameters, const QString &name, QgsProcessingContext &context, QgsPrintLayout *layout )
{
  return QgsProcessingParameters::parameterAsLayoutItem( parameterDefinition( name ), parameters, context, layout );
}
QWidget *QgsProcessingRangeWidgetWrapper::createWidget()
{
  const QgsProcessingParameterRange *rangeDef = static_cast< const QgsProcessingParameterRange * >( parameterDefinition() );
  switch ( type() )
  {
    case QgsProcessingGui::Standard:
    case QgsProcessingGui::Modeler:
    case QgsProcessingGui::Batch:
    {
      QHBoxLayout *layout = new QHBoxLayout();

      mMinSpinBox = new QgsDoubleSpinBox();
      mMaxSpinBox = new QgsDoubleSpinBox();

      mMinSpinBox->setExpressionsEnabled( true );
      mMinSpinBox->setShowClearButton( false );
      mMaxSpinBox->setExpressionsEnabled( true );
      mMaxSpinBox->setShowClearButton( false );

      QLabel *minLabel = new QLabel( tr( "Min" ) );
      layout->addWidget( minLabel );
      layout->addWidget( mMinSpinBox, 1 );

      QLabel *maxLabel = new QLabel( tr( "Max" ) );
      layout->addWidget( maxLabel );
      layout->addWidget( mMaxSpinBox, 1 );

      QWidget *w = new QWidget();
      layout->setMargin( 0 );
      layout->setContentsMargins( 0, 0, 0, 0 );
      w->setLayout( layout );

      if ( rangeDef->dataType() == QgsProcessingParameterNumber::Double )
      {
        mMinSpinBox->setDecimals( 6 );
        mMaxSpinBox->setDecimals( 6 );
      }
      else
      {
        mMinSpinBox->setDecimals( 0 );
        mMaxSpinBox->setDecimals( 0 );
      }

      mMinSpinBox->setMinimum( -99999999.999999 );
      mMaxSpinBox->setMinimum( -99999999.999999 );
      mMinSpinBox->setMaximum( 99999999.999999 );
      mMaxSpinBox->setMaximum( 99999999.999999 );

      w->setToolTip( parameterDefinition()->toolTip() );

      connect( mMinSpinBox, qgis::overload<double>::of( &QgsDoubleSpinBox::valueChanged ), this, [ = ]( const double v )
      {
        mBlockChangedSignal++;
        if ( v > mMaxSpinBox->value() )
          mMaxSpinBox->setValue( v );
        mBlockChangedSignal--;

        if ( !mBlockChangedSignal )
          emit widgetValueHasChanged( this );
      } );
      connect( mMaxSpinBox, qgis::overload<double>::of( &QgsDoubleSpinBox::valueChanged ), this, [ = ]( const double v )
      {
        mBlockChangedSignal++;
        if ( v < mMinSpinBox->value() )
          mMinSpinBox->setValue( v );
        mBlockChangedSignal--;

        if ( !mBlockChangedSignal )
          emit widgetValueHasChanged( this );
      } );

      return w;
    };
  }
  return nullptr;
}
QString QgsProcessingAlgorithm::parameterAsCompatibleSourceLayerPath( const QVariantMap &parameters, const QString &name, QgsProcessingContext &context, const QStringList &compatibleFormats, const QString &preferredFormat, QgsProcessingFeedback *feedback )
{
  return QgsProcessingParameters::parameterAsCompatibleSourceLayerPath( parameterDefinition( name ), parameters, context, compatibleFormats, preferredFormat, feedback );
}
QString QgsProcessingModelAlgorithm::asPythonCode() const
{
  QStringList lines;
  lines << QStringLiteral( "##%1=name" ).arg( name() );

  QMap< QString, ModelParameter >::const_iterator paramIt = mParameterComponents.constBegin();
  for ( ; paramIt != mParameterComponents.constEnd(); ++paramIt )
  {
    QString name = paramIt.value().parameterName();
    if ( parameterDefinition( name ) )
    {
      lines << parameterDefinition( name )->asScriptCode();
    }
  }

  auto safeName = []( const QString & name )->QString
  {
    QString n = name.toLower().trimmed();
    QRegularExpression rx( "[^a-z_]" );
    n.replace( rx, QString() );
    return n;
  };

  QMap< QString, ChildAlgorithm >::const_iterator childIt = mChildAlgorithms.constBegin();
  for ( ; childIt != mChildAlgorithms.constEnd(); ++childIt )
  {
    if ( !childIt->isActive() || !childIt->algorithm() )
      continue;

    // look through all outputs for child
    QMap<QString, QgsProcessingModelAlgorithm::ModelOutput> outputs = childIt->modelOutputs();
    QMap<QString, QgsProcessingModelAlgorithm::ModelOutput>::const_iterator outputIt = outputs.constBegin();
    for ( ; outputIt != outputs.constEnd(); ++outputIt )
    {
      const QgsProcessingOutputDefinition *output = childIt->algorithm()->outputDefinition( outputIt->childOutputName() );
      lines << QStringLiteral( "##%1=output %2" ).arg( safeName( outputIt->name() ), output->type() );
    }
  }

  lines << QStringLiteral( "results={}" );

  QSet< QString > toExecute;
  childIt = mChildAlgorithms.constBegin();
  for ( ; childIt != mChildAlgorithms.constEnd(); ++childIt )
  {
    if ( childIt->isActive() && childIt->algorithm() )
      toExecute.insert( childIt->childId() );
  }

  QSet< QString > executed;
  bool executedAlg = true;
  while ( executedAlg && executed.count() < toExecute.count() )
  {
    executedAlg = false;
    Q_FOREACH ( const QString &childId, toExecute )
    {
      if ( executed.contains( childId ) )
        continue;

      bool canExecute = true;
      Q_FOREACH ( const QString &dependency, dependsOnChildAlgorithms( childId ) )
      {
        if ( !executed.contains( dependency ) )
        {
          canExecute = false;
          break;
        }
      }

      if ( !canExecute )
        continue;

      executedAlg = true;

      const ChildAlgorithm &child = mChildAlgorithms[ childId ];
      lines << child.asPythonCode();

      executed.insert( childId );
    }
  }

  lines << QStringLiteral( "return results" );

  return lines.join( '\n' );
}
QList<double> QgsProcessingAlgorithm::parameterAsRange( const QVariantMap &parameters, const QString &name, QgsProcessingContext &context ) const
{
  return QgsProcessingParameters::parameterAsRange( parameterDefinition( name ), parameters, context );
}