void QgsGeometryCheckerFixDialog::setupNextError()
{
  mProgressBar->setValue( mProgressBar->maximum() - mErrors.size() );
  mNextBtn->setVisible( false );
  mFixBtn->setVisible( true );
  mFixBtn->setFocus();
  mSkipBtn->setVisible( true );
  mStatusLabel->setText( "" );
  mResolutionsBox->setEnabled( true );

  QgsGeometryCheckError* error = mErrors.first();
  emit currentErrorChanged( error );

  mResolutionsBox->setTitle( tr( "Select how to fix error \"%1\":" ).arg( error->description() ) );

  delete mRadioGroup;
  mRadioGroup = new QButtonGroup( this );

  delete mResolutionsBox->layout();
  qDeleteAll( mResolutionsBox->children() );
  mResolutionsBox->setLayout( new QVBoxLayout() );
  mResolutionsBox->layout()->setContentsMargins( 0, 0, 0, 4 );

  int id = 0;
  int checkedid = QSettings().value( QgsGeometryCheckerResultTab::sSettingsGroup + error->check()->errorName(), QVariant::fromValue<int>( 0 ) ).toInt();
  foreach ( const QString& method, error->check()->getResolutionMethods() )
  {
    QRadioButton* radio = new QRadioButton( method );
    radio->setChecked( checkedid == id );
    mResolutionsBox->layout()->addWidget( radio );
    mRadioGroup->addButton( radio, id++ );
  }
  adjustSize();
}
bool QgsGeometryCheckerResultTab::exportErrorsDo( const QString& file )
{
  QList< QPair<QString, QString> > attributes;
  attributes.append( qMakePair( QString( "FeatureID" ), QString( "String;10;" ) ) );
  attributes.append( qMakePair( QString( "ErrorDesc" ), QString( "String;80;" ) ) );

  QLibrary ogrLib( QgsProviderRegistry::instance()->library( "ogr" ) );
  if ( !ogrLib.load() )
  {
    return false;
  }
  typedef bool ( *createEmptyDataSourceProc )( const QString&, const QString&, const QString&, QGis::WkbType, const QList< QPair<QString, QString> >&, const QgsCoordinateReferenceSystem * );
  createEmptyDataSourceProc createEmptyDataSource = ( createEmptyDataSourceProc ) cast_to_fptr( ogrLib.resolve( "createEmptyDataSource" ) );
  if ( !createEmptyDataSource )
  {
    return false;
  }
  if ( !createEmptyDataSource( file, "ESRI Shapefile", mFeaturePool->getLayer()->dataProvider()->encoding(), QGis::WKBPoint, attributes, &mFeaturePool->getLayer()->crs() ) )
  {
    return false;
  }
  QgsVectorLayer* layer = new QgsVectorLayer( file, QFileInfo( file ).baseName(), "ogr" );
  if ( !layer->isValid() )
  {
    delete layer;
    return false;
  }

  int fieldFeatureId = layer->fieldNameIndex( "FeatureID" );
  int fieldErrDesc = layer->fieldNameIndex( "ErrorDesc" );
  for ( int row = 0, nRows = ui.tableWidgetErrors->rowCount(); row < nRows; ++row )
  {
    QgsGeometryCheckError* error = ui.tableWidgetErrors->item( row, 0 )->data( Qt::UserRole ).value<QgsGeometryCheckError*>();

    QgsFeature f( layer->pendingFields() );
    f.setAttribute( fieldFeatureId, error->featureId() );
    f.setAttribute( fieldErrDesc, error->description() );
    f.setGeometry( new QgsGeometry( error->location().clone() ) );
    layer->dataProvider()->addFeatures( QgsFeatureList() << f );
  }

  // Remove existing layer with same uri
  QStringList toRemove;
  foreach ( QgsMapLayer* maplayer, QgsMapLayerRegistry::instance()->mapLayers() )
  {
    if ( dynamic_cast<QgsVectorLayer*>( maplayer ) &&
         static_cast<QgsVectorLayer*>( maplayer )->dataProvider()->dataSourceUri() == layer->dataProvider()->dataSourceUri() )
    {
      toRemove.append( maplayer->id() );
    }
  }
  if ( !toRemove.isEmpty() )
  {
    QgsMapLayerRegistry::instance()->removeMapLayers( toRemove );
  }

  QgsMapLayerRegistry::instance()->addMapLayers( QList<QgsMapLayer*>() << layer );
  return true;
}
void QgsGeometryCheckerFixDialog::fixError()
{
  mResolutionsBox->setEnabled( false );
  mFixBtn->setVisible( false );
  mSkipBtn->setVisible( false );

  setCursor( Qt::WaitCursor );

  QgsGeometryCheckError* error = mErrors.first();
  mChecker->fixError( error, mRadioGroup->checkedId() );

  unsetCursor();

  mStatusLabel->setText( error->resolutionMessage() );
  if ( error->status() == QgsGeometryCheckError::StatusFixed )
  {
    mStatusLabel->setText( tr( "<b>Fixed:</b> %1" ).arg( error->resolutionMessage() ) );
  }
  else if ( error->status() == QgsGeometryCheckError::StatusFixFailed )
  {
    mStatusLabel->setText( tr( "<span color=\"red\"><b>Fixed failed:</b> %1</span>" ).arg( error->resolutionMessage() ) );
  }
  else if ( error->status() == QgsGeometryCheckError::StatusObsolete )
  {
    mStatusLabel->setText( tr( "<b>Error is obsolete</b>" ) );
  }
  mErrors.removeFirst();

  while ( !mErrors.isEmpty() && mErrors.first()->status() >= QgsGeometryCheckError::StatusFixed )
  {
    mErrors.removeFirst();
  }

  mProgressBar->setValue( mProgressBar->maximum() - mErrors.size() );

  if ( mErrors.isEmpty() )
  {
    mButtonBox->addButton( QDialogButtonBox::Close );
    mNextBtn->setVisible( false );
    mFixBtn->setVisible( false );
    mSkipBtn->setVisible( false );
    mButtonBox->button( QDialogButtonBox::Abort )->setVisible( false );
  }
  else
  {
    mNextBtn->setVisible( true );
    mNextBtn->setFocus();
  }
  adjustSize();
  emit currentErrorChanged( error );
  mIface->mapCanvas()->refresh();
}
void QgsGeometryCheckerResultTab::highlightErrors( bool current )
{
  qDeleteAll( mCurrentRubberBands );
  mCurrentRubberBands.clear();

  QList<QTableWidgetItem*> items;
  QList<QgsPoint> errorPositions;
  QgsRectangle totextent;

  if ( current )
  {
    items.append( ui.tableWidgetErrors->currentItem() );
  }
  else
  {
    items.append( ui.tableWidgetErrors->selectedItems() );
  }
  foreach ( QTableWidgetItem* item, items )
  {
    QgsGeometryCheckError* error = ui.tableWidgetErrors->item( item->row(), 0 )->data( Qt::UserRole ).value<QgsGeometryCheckError*>();

    QgsAbstractGeometryV2* geometry = error->geometry();
    if ( ui.checkBoxHighlight->isChecked() && geometry )
    {
      QgsRubberBand* featureRubberBand = new QgsRubberBand( mIface->mapCanvas() );
      QgsGeometry geom( geometry );
      featureRubberBand->addGeometry( &geom, mFeaturePool->getLayer() );
      featureRubberBand->setWidth( 5 );
      featureRubberBand->setColor( Qt::yellow );
      mCurrentRubberBands.append( featureRubberBand );
    }
    else
    {
      // QgsGeometry above takes ownership of geometry and deletes it when it goes out of scope
      delete geometry;
      geometry = 0;
    }

    if ( ui.radioButtonError->isChecked() || current || error->status() == QgsGeometryCheckError::StatusFixed )
    {
      QgsRubberBand* pointRubberBand = new QgsRubberBand( mIface->mapCanvas(), QGis::Point );
      QgsPoint pos = mIface->mapCanvas()->mapSettings().layerToMapCoordinates( mFeaturePool->getLayer(), QgsPoint( error->location().x(), error->location().y() ) );
      pointRubberBand->addPoint( pos );
      pointRubberBand->setWidth( 20 );
      pointRubberBand->setColor( Qt::red );
      mCurrentRubberBands.append( pointRubberBand );
      errorPositions.append( pos );
    }
    else if ( ui.radioButtonFeature->isChecked() && geometry )
    {
      QgsRectangle geomextent = mIface->mapCanvas()->mapSettings().layerExtentToOutputExtent( mFeaturePool->getLayer(), geometry->boundingBox() );
      if ( totextent.isEmpty() )
      {
        totextent = geomextent;
      }
      else
      {
        totextent.unionRect( geomextent );
      }
    }
  }
void QgsGeometryCheckerResultTab::highlightErrors( bool current )
{
  qDeleteAll( mCurrentRubberBands );
  mCurrentRubberBands.clear();

  QList<QTableWidgetItem *> items;
  QVector<QgsPointXY> errorPositions;
  QgsRectangle totextent;

  if ( current )
  {
    items.append( ui.tableWidgetErrors->currentItem() );
  }
  else
  {
    items.append( ui.tableWidgetErrors->selectedItems() );
  }
  for ( QTableWidgetItem *item : qgis::as_const( items ) )
  {
    QgsGeometryCheckError *error = ui.tableWidgetErrors->item( item->row(), 0 )->data( Qt::UserRole ).value<QgsGeometryCheckError *>();

    const QgsGeometry geom = error->geometry();
    if ( ui.checkBoxHighlight->isChecked() && !geom.isNull() )
    {
      QgsRubberBand *featureRubberBand = new QgsRubberBand( mIface->mapCanvas() );
      featureRubberBand->addGeometry( geom, nullptr );
      featureRubberBand->setWidth( 5 );
      featureRubberBand->setColor( Qt::yellow );
      mCurrentRubberBands.append( featureRubberBand );
    }

    if ( ui.radioButtonError->isChecked() || current || error->status() == QgsGeometryCheckError::StatusFixed )
    {
      QgsRubberBand *pointRubberBand = new QgsRubberBand( mIface->mapCanvas(), QgsWkbTypes::PointGeometry );
      pointRubberBand->addPoint( error->location() );
      pointRubberBand->setWidth( 20 );
      pointRubberBand->setColor( Qt::red );
      mCurrentRubberBands.append( pointRubberBand );
      errorPositions.append( error->location() );
    }
    else if ( ui.radioButtonFeature->isChecked() )
    {
      QgsRectangle geomextent = error->affectedAreaBBox();
      if ( totextent.isEmpty() )
      {
        totextent = geomextent;
      }
      else
      {
        totextent.combineExtentWith( geomextent );
      }
    }
  }

  // If error positions positions are marked, pan to the center of all positions,
  // and zoom out if necessary to make all points fit.
  if ( !errorPositions.isEmpty() )
  {
    double cx = 0., cy = 0.;
    QgsRectangle pointExtent( errorPositions.first(), errorPositions.first() );
    Q_FOREACH ( const QgsPointXY &p, errorPositions )
    {
      cx += p.x();
      cy += p.y();
      pointExtent.include( p );
    }
bool QgsGeometryCheckerResultTab::exportErrorsDo( const QString &file )
{
  QList< QPair<QString, QString> > attributes;
  attributes.append( qMakePair( QStringLiteral( "Layer" ), QStringLiteral( "String;30;" ) ) );
  attributes.append( qMakePair( QStringLiteral( "FeatureID" ), QStringLiteral( "String;10;" ) ) );
  attributes.append( qMakePair( QStringLiteral( "ErrorDesc" ), QStringLiteral( "String;80;" ) ) );

  QFileInfo fi( file );
  QString ext = fi.suffix();
  QString driver = QgsVectorFileWriter::driverForExtension( ext );

  QLibrary ogrLib( QgsProviderRegistry::instance()->library( QStringLiteral( "ogr" ) ) );
  if ( !ogrLib.load() )
  {
    return false;
  }
  typedef bool ( *createEmptyDataSourceProc )( const QString &, const QString &, const QString &, QgsWkbTypes::Type, const QList< QPair<QString, QString> > &, const QgsCoordinateReferenceSystem & );
  createEmptyDataSourceProc createEmptyDataSource = ( createEmptyDataSourceProc ) cast_to_fptr( ogrLib.resolve( "createEmptyDataSource" ) );
  if ( !createEmptyDataSource )
  {
    return false;
  }
  if ( !createEmptyDataSource( file, driver, "UTF-8", QgsWkbTypes::Point, attributes, QgsProject::instance()->crs() ) )
  {
    return false;
  }
  QgsVectorLayer *layer = new QgsVectorLayer( file, QFileInfo( file ).baseName(), QStringLiteral( "ogr" ) );
  if ( !layer->isValid() )
  {
    delete layer;
    return false;
  }

  int fieldLayer = layer->fields().lookupField( QStringLiteral( "Layer" ) );
  int fieldFeatureId = layer->fields().lookupField( QStringLiteral( "FeatureID" ) );
  int fieldErrDesc = layer->fields().lookupField( QStringLiteral( "ErrorDesc" ) );
  for ( int row = 0, nRows = ui.tableWidgetErrors->rowCount(); row < nRows; ++row )
  {
    QgsGeometryCheckError *error = ui.tableWidgetErrors->item( row, 0 )->data( Qt::UserRole ).value<QgsGeometryCheckError *>();
    QgsVectorLayer *srcLayer = mChecker->featurePools()[error->layerId()]->layer();
    QgsFeature f( layer->fields() );
    f.setAttribute( fieldLayer, srcLayer->name() );
    f.setAttribute( fieldFeatureId, error->featureId() );
    f.setAttribute( fieldErrDesc, error->description() );
    QgsGeometry geom( new QgsPoint( error->location() ) );
    f.setGeometry( geom );
    layer->dataProvider()->addFeatures( QgsFeatureList() << f );
  }

  // Remove existing layer with same uri
  QStringList toRemove;
  for ( QgsMapLayer *maplayer : QgsProject::instance()->mapLayers() )
  {
    if ( dynamic_cast<QgsVectorLayer *>( maplayer ) &&
         static_cast<QgsVectorLayer *>( maplayer )->dataProvider()->dataSourceUri() == layer->dataProvider()->dataSourceUri() )
    {
      toRemove.append( maplayer->id() );
    }
  }
  if ( !toRemove.isEmpty() )
  {
    QgsProject::instance()->removeMapLayers( toRemove );
  }

  QgsProject::instance()->addMapLayers( QList<QgsMapLayer *>() << layer );
  return true;
}