1 /***************************************************************************
2   qgsnewvectortabledialog.cpp - QgsNewVectorTableDialog
3 
4  ---------------------
5  begin                : 12.7.2020
6  copyright            : (C) 2020 by Alessandro Pasotti
7  email                : elpaso at itopen dot it
8  ***************************************************************************
9  *                                                                         *
10  *   This program is free software; you can redistribute it and/or modify  *
11  *   it under the terms of the GNU General Public License as published by  *
12  *   the Free Software Foundation; either version 2 of the License, or     *
13  *   (at your option) any later version.                                   *
14  *                                                                         *
15  ***************************************************************************/
16 #include "qgsnewvectortabledialog.h"
17 #include "qgsvectorlayer.h"
18 #include "qgslogger.h"
19 #include "qgsgui.h"
20 #include "qgsapplication.h"
21 #include "qgsiconutils.h"
22 #include <QSpinBox>
23 #include <QMessageBox>
24 #include <QTimer>
25 
QgsNewVectorTableDialog(QgsAbstractDatabaseProviderConnection * conn,QWidget * parent)26 QgsNewVectorTableDialog::QgsNewVectorTableDialog( QgsAbstractDatabaseProviderConnection *conn, QWidget *parent )
27   : QDialog( parent )
28   , mConnection( conn )
29 {
30 
31   setupUi( this );
32 
33   // This is a precondition for the dialog to work correctly
34   try
35   {
36     mFieldModel = new QgsNewVectorTableFieldModel( mConnection->nativeTypes(), this );
37   }
38   catch ( QgsProviderConnectionException &ex )
39   {
40     QMessageBox::critical( nullptr, tr( "Cannot Create New Tables" ), tr( "Error retrieving native types from the data provider: creation of new tables is not possible.\n"
41                            "Error message: %1" ).arg( ex.what() ) );
42     QTimer::singleShot( 0, [ = ] { reject(); } );
43     return;
44   }
45 
46   Q_ASSERT( ! mFieldModel->nativeTypes().isEmpty() );
47 
48   QgsGui::enableAutoGeometryRestore( this );
49   setWindowTitle( tr( "New Table" ) );
50 
51   auto updateTableNames = [ = ]( const QString &schema = QString( ) )
52   {
53     mTableNames.clear();
54     try
55     {
56       const auto constTables { conn->tables( schema ) };
57       for ( const auto &tp : constTables )
58       {
59         mTableNames.push_back( tp.tableName() );
60       }
61       validate();
62     }
63     catch ( QgsProviderConnectionException &ex )
64     {
65       // This should never happen but it's not critical, we can safely continue.
66       QgsDebugMsg( QStringLiteral( "Error retrieving tables from connection: %1" ).arg( ex.what() ) );
67     }
68   };
69 
70   // Validate on data changed
71   connect( mFieldModel, &QgsNewVectorTableFieldModel::modelReset, this, [ = ]()
72   {
73     validate();
74   } );
75 
76   mTableName->setText( QStringLiteral( "new_table_name" ) );
77   mFieldsTableView->setModel( mFieldModel );
78   QgsNewVectorTableDialogFieldsDelegate *delegate { new QgsNewVectorTableDialogFieldsDelegate( mConnection->nativeTypes(), this )};
79   mFieldsTableView->setItemDelegate( delegate );
80   mFieldsTableView->setSelectionBehavior( QAbstractItemView::SelectionBehavior::SelectRows );
81   mFieldsTableView->setSelectionMode( QAbstractItemView::SelectionMode::SingleSelection );
82   mFieldsTableView->setVerticalHeader( nullptr );
83 
84   // Cosmetics
85   mFieldsTableView->horizontalHeader()->setStretchLastSection( true );
86   mFieldsTableView->setColumnWidth( QgsNewVectorTableFieldModel::ColumnHeaders::Type, 300 );
87 
88   // Schema is not supported by all providers
89   if ( mConnection->capabilities().testFlag( QgsAbstractDatabaseProviderConnection::Capability::Schemas ) )
90   {
91     mSchemaCbo->addItems( mConnection->schemas() );
92     connect( mSchemaCbo, &QComboBox::currentTextChanged, this, [ = ]( const QString & schema )
93     {
94       updateTableNames( schema );
95     } );
96   }
97   else
98   {
99     mSchemaCbo->hide();
100     mSchemaLabel->hide();
101   }
102 
103   if ( ! mConnection->capabilities().testFlag( QgsAbstractDatabaseProviderConnection::Capability::CreateSpatialIndex ) )
104   {
105     mSpatialIndexChk->setChecked( false );
106     mSpatialIndexChk->hide();
107     mSpatialIndexLabel->hide();
108   }
109 
110   // Initial load of table names
111   updateTableNames( mSchemaCbo->currentText() );
112 
113   // Validators
114   connect( mTableName, &QLineEdit::textChanged, this, [ = ]( const QString & )
115   {
116     validate();
117   } );
118 
119   connect( mGeomColumn, &QLineEdit::textChanged, this, [ = ]( const QString & )
120   {
121     validate();
122   } );
123 
124   // Enable/disable geometry options and call validate
125   connect( mGeomTypeCbo, qOverload<int>( &QComboBox::currentIndexChanged ), this, [ = ]( int index )
126   {
127     const bool hasGeom { index != 0 };
128     mGeomColumn->setEnabled( hasGeom );
129     mGeomColumnLabel->setEnabled( hasGeom );
130     mSpatialIndexChk->setEnabled( hasGeom );
131     mSpatialIndexLabel->setEnabled( hasGeom );
132     mCrs->setEnabled( hasGeom );
133     mCrsLabel->setEnabled( hasGeom );
134     mDimensionsLabel->setEnabled( hasGeom );
135     mHasMChk->setEnabled( hasGeom );
136     mHasZChk->setEnabled( hasGeom );
137     validate();
138   } );
139 
140   mCrs->setShowAccuracyWarnings( true );
141 
142   // geometry types
143   const bool hasSinglePart { conn->geometryColumnCapabilities().testFlag( QgsAbstractDatabaseProviderConnection::GeometryColumnCapability::SinglePart ) };
144 
145   const auto addGeomItem = [this]( QgsWkbTypes::Type type )
146   {
147     mGeomTypeCbo->addItem( QgsIconUtils::iconForWkbType( type ), QgsWkbTypes::translatedDisplayString( type ), type );
148   };
149 
150   mGeomTypeCbo->addItem( QgsApplication::getThemeIcon( QStringLiteral( "mIconTableLayer.svg" ) ), tr( "No Geometry" ), QgsWkbTypes::Type::NoGeometry );
151   if ( hasSinglePart )
152     addGeomItem( QgsWkbTypes::Type::Point );
153   addGeomItem( QgsWkbTypes::Type::MultiPoint );
154   if ( hasSinglePart )
155     addGeomItem( QgsWkbTypes::Type::LineString );
156   addGeomItem( QgsWkbTypes::Type::MultiLineString );
157   if ( hasSinglePart )
158     addGeomItem( QgsWkbTypes::Type::Polygon );
159   addGeomItem( QgsWkbTypes::Type::MultiPolygon );
160 
161   if ( conn->geometryColumnCapabilities().testFlag( QgsAbstractDatabaseProviderConnection::GeometryColumnCapability::Curves ) )
162   {
163     addGeomItem( QgsWkbTypes::Type::CompoundCurve );
164     addGeomItem( QgsWkbTypes::Type::CurvePolygon );
165     addGeomItem( QgsWkbTypes::Type::MultiCurve );
166     addGeomItem( QgsWkbTypes::Type::MultiSurface );
167   }
168 
169   mGeomTypeCbo->setCurrentIndex( 0 );
170 
171   const bool hasZ { conn->geometryColumnCapabilities().testFlag( QgsAbstractDatabaseProviderConnection::GeometryColumnCapability::Z ) };
172   const bool hasM { conn->geometryColumnCapabilities().testFlag( QgsAbstractDatabaseProviderConnection::GeometryColumnCapability::M ) };
173   if ( ! hasM )
174   {
175     mHasMChk->setEnabled( false );
176     mHasMChk->setChecked( false );
177   }
178   if ( ! hasZ )
179   {
180     mHasZChk->setEnabled( false );
181     mHasZChk->setChecked( false );
182   }
183   if ( ! hasM && ! hasM )
184   {
185     mHasZChk->setVisible( false );
186     mHasMChk->setVisible( false );
187     mDimensionsLabel->setVisible( false );
188   }
189 
190   connect( mFieldsTableView->selectionModel(), &QItemSelectionModel::selectionChanged, mFieldsTableView, [ = ]( const QItemSelection & selected, const QItemSelection & )
191   {
192     if ( ! selected.isEmpty() )
193     {
194       mCurrentRow = selected.indexes().first().row();
195     }
196     updateButtons();
197   } );
198 
199   // Get a default type for new fields
200   const QVariant::Type defaultFieldType { mFieldModel->nativeTypes().first().mType };
201   const QString defaultFieldTypeName { mFieldModel->nativeTypes().first().mTypeName };
202 
203   // Actions
204   connect( mAddFieldBtn, &QPushButton::clicked, this, [ = ]
205   {
206     QgsFields fieldList { fields() };
207     QgsField newField { QStringLiteral( "new_field_name" ), defaultFieldType, defaultFieldTypeName };
208     fieldList.append( newField );
209     setFields( fieldList );
210     selectRow( fieldList.count() - 1 );
211   } );
212 
213   connect( mDeleteFieldBtn, &QPushButton::clicked, this, [ = ]
214   {
215     QgsFields fieldList { fields() };
216     if ( fieldList.exists( mCurrentRow ) )
217     {
218       fieldList.remove( mCurrentRow );
219       setFields( fieldList );
220       mCurrentRow = -1;
221     }
222   } );
223 
224   connect( mFieldUpBtn, &QPushButton::clicked, this, [ = ]
225   {
226     if ( fields().exists( mCurrentRow ) && fields().exists( mCurrentRow - 1 ) )
227     {
228       QgsFields fieldList;
229       for ( int i = 0; i < fields().count(); ++i )
230       {
231         if ( i == mCurrentRow - 1 )
232         {
233           fieldList.append( fields().at( mCurrentRow ) );
234           fieldList.append( fields().at( mCurrentRow - 1 ) );
235         }
236         else if ( i != mCurrentRow )
237         {
238           fieldList.append( fields().at( i ) );
239         }
240       }
241       setFields( fieldList );
242       selectRow( mCurrentRow - 1 );
243     }
244   } );
245 
246   connect( mFieldDownBtn, &QPushButton::clicked, this, [ = ]
247   {
248     if ( fields().exists( mCurrentRow ) && fields().exists( mCurrentRow + 1 ) )
249     {
250       QgsFields fieldList;
251       for ( int i = 0; i < fields().count(); ++i )
252       {
253         if ( i == mCurrentRow )
254         {
255           fieldList.append( fields().at( mCurrentRow + 1 ) );
256           fieldList.append( fields().at( mCurrentRow ) );
257         }
258         else if ( i != mCurrentRow + 1 )
259         {
260           fieldList.append( fields().at( i ) );
261         }
262       }
263       setFields( fieldList );
264       selectRow( mCurrentRow + 1 );
265     }
266   } );
267 
268   updateButtons();
269   validate();
270 }
271 
setSchemaName(const QString & name)272 void QgsNewVectorTableDialog::setSchemaName( const QString &name )
273 {
274   mSchemaCbo->setCurrentText( name );
275 }
276 
setTableName(const QString & name)277 void QgsNewVectorTableDialog::setTableName( const QString &name )
278 {
279   mTableName->setText( name );
280 }
281 
setGeometryType(QgsWkbTypes::Type type)282 void QgsNewVectorTableDialog::setGeometryType( QgsWkbTypes::Type type )
283 {
284   mGeomTypeCbo->setCurrentIndex( mGeomTypeCbo->findData( type ) );
285 }
286 
setCrs(const QgsCoordinateReferenceSystem & crs)287 void QgsNewVectorTableDialog::setCrs( const QgsCoordinateReferenceSystem &crs )
288 {
289   mCrs->setCrs( crs );
290 }
291 
crs() const292 QgsCoordinateReferenceSystem QgsNewVectorTableDialog::crs() const
293 {
294   return mCrs->crs( );
295 }
296 
tableName() const297 QString QgsNewVectorTableDialog::tableName() const
298 {
299   return mTableName->text();
300 }
301 
schemaName() const302 QString QgsNewVectorTableDialog::schemaName() const
303 {
304   return mSchemaCbo->currentText();
305 }
306 
geometryColumnName() const307 QString QgsNewVectorTableDialog::geometryColumnName() const
308 {
309   return mGeomColumn->text();
310 }
311 
fields() const312 QgsFields QgsNewVectorTableDialog::fields() const
313 {
314   return mFieldModel ? mFieldModel->fields() : QgsFields();
315 }
316 
geometryType() const317 QgsWkbTypes::Type QgsNewVectorTableDialog::geometryType() const
318 {
319   QgsWkbTypes::Type type { static_cast<QgsWkbTypes::Type>( mGeomTypeCbo->currentData( ).toInt() ) };
320   if ( mHasMChk->isChecked() )
321   {
322     type = QgsWkbTypes::addM( type );
323   }
324   if ( mHasZChk->isChecked() )
325   {
326     type = QgsWkbTypes::addZ( type );
327   }
328   return type;
329 }
330 
331 
setFields(const QgsFields & fields)332 void QgsNewVectorTableDialog::setFields( const QgsFields &fields )
333 {
334   if ( mFieldModel )
335   {
336     mFieldModel->setFields( fields );
337   }
338 }
339 
createSpatialIndex()340 bool QgsNewVectorTableDialog::createSpatialIndex()
341 {
342   return mSpatialIndexChk->isChecked();
343 }
344 
validationErrors() const345 QStringList QgsNewVectorTableDialog::validationErrors() const
346 {
347   return mValidationErrors;
348 }
349 
updateButtons()350 void QgsNewVectorTableDialog::updateButtons()
351 {
352   mDeleteFieldBtn->setEnabled( mCurrentRow != -1 );
353   mFieldUpBtn->setEnabled( mCurrentRow != -1 && mCurrentRow != 0 );
354   mFieldDownBtn->setEnabled( mCurrentRow != -1 && mCurrentRow != fields().count() - 1 );
355 }
356 
selectRow(int row)357 void QgsNewVectorTableDialog::selectRow( int row )
358 {
359   QModelIndex index { mFieldsTableView->model()->index( row, 0 ) };
360   mFieldsTableView->setCurrentIndex( index );
361   QItemSelectionModel::SelectionFlags flags { QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Current };
362   mFieldsTableView->selectionModel()->select( index, flags );
363   mFieldsTableView->scrollTo( index );
364 }
365 
validate()366 void QgsNewVectorTableDialog::validate()
367 {
368   mValidationErrors.clear();
369 
370   const bool isSpatial { mGeomTypeCbo->currentIndex() > 0 };
371   if ( mTableNames.contains( mTableName->text(), Qt::CaseSensitivity::CaseInsensitive ) )
372   {
373     mValidationErrors.push_back( tr( "Table <b>%1</b> already exists!" ).arg( mTableName->text() ) );
374   }
375   // Check for field names and geom col name
376   if ( isSpatial && fields().names().contains( mGeomColumn->text(), Qt::CaseSensitivity::CaseInsensitive ) )
377   {
378     mValidationErrors.push_back( tr( "Geometry column name <b>%1</b> cannot be equal to an existing field name!" ).arg( mGeomColumn->text() ) );
379   }
380   // No geometry and no fields? No party!
381   if ( ! isSpatial && fields().count() == 0 )
382   {
383     mValidationErrors.push_back( tr( "The table has no geometry column and no fields!" ) );
384   }
385   // Check if precision is <= length
386   const auto cFields { fields() };
387   for ( const auto &f : cFields )
388   {
389     if ( f.isNumeric() && f.length() >= 0 && f.precision() >= 0 && f.precision() > f.length() )
390     {
391       mValidationErrors.push_back( tr( "Field <b>%1</b>: precision cannot be greater than length!" ).arg( f.name() ) );
392     }
393   }
394 
395   const bool isValid { mValidationErrors.isEmpty() };
396   if ( ! isValid )
397   {
398     mValidationResults->setText( mValidationErrors.join( QLatin1String( "<br>" ) ) );
399   }
400 
401   mValidationFrame->setVisible( ! isValid );
402   mButtonBox->button( QDialogButtonBox::StandardButton::Ok )->setEnabled( isValid );
403 }
404 
showEvent(QShowEvent * event)405 void QgsNewVectorTableDialog::showEvent( QShowEvent *event )
406 {
407   QDialog::showEvent( event );
408   mTableName->setFocus();
409   mTableName->selectAll();
410 }
411 
412 
413 /// @cond private
414 
415 
QgsNewVectorTableDialogFieldsDelegate(const QList<QgsVectorDataProvider::NativeType> & typeList,QObject * parent)416 QgsNewVectorTableDialogFieldsDelegate::QgsNewVectorTableDialogFieldsDelegate( const QList<QgsVectorDataProvider::NativeType> &typeList, QObject *parent )
417   : QStyledItemDelegate( parent )
418   , mTypeList( typeList )
419 {
420 
421 }
422 
createEditor(QWidget * parent,const QStyleOptionViewItem & option,const QModelIndex & index) const423 QWidget *QgsNewVectorTableDialogFieldsDelegate::createEditor( QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index ) const
424 {
425   switch ( index.column() )
426   {
427     case QgsNewVectorTableFieldModel::ColumnHeaders::Type:
428     {
429       QComboBox *cbo = new QComboBox { parent };
430       cbo->setEditable( false );
431       cbo->setFrame( false );
432       connect( cbo, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsNewVectorTableDialogFieldsDelegate::onFieldTypeChanged );
433       for ( const auto &f : std::as_const( mTypeList ) )
434       {
435         cbo->addItem( f.mTypeDesc, f.mTypeName );
436       }
437       return cbo;
438     }
439     case QgsNewVectorTableFieldModel::ColumnHeaders::Precision:
440     {
441       QSpinBox *sp { new QSpinBox { parent } };
442       const QgsNewVectorTableFieldModel *model { static_cast<const QgsNewVectorTableFieldModel *>( index.model() )};
443       if ( model )
444       {
445         const QgsVectorDataProvider::NativeType nt { model->nativeType( index.row() ) };
446         sp->setRange( nt.mMinPrec, std::min<int>( nt.mMaxPrec, index.model()->data( index.model()->index( index.row(), index.column() - 1 ) ).toInt() ) );
447       }
448       return sp;
449     }
450     case QgsNewVectorTableFieldModel::ColumnHeaders::Length:
451     {
452       QSpinBox *sp { new QSpinBox { parent } };
453       const QgsNewVectorTableFieldModel *model { static_cast<const QgsNewVectorTableFieldModel *>( index.model() )};
454       if ( model )
455       {
456         const QgsVectorDataProvider::NativeType nt { model->nativeType( index.row() ) };
457         sp->setRange( std::max<int>( nt.mMinLen, index.model()->data( index.model()->index( index.row(), index.column() + 1 ) ).toInt() ), nt.mMaxLen );
458       }
459       return sp;
460     }
461     default:
462     {
463       return QStyledItemDelegate::createEditor( parent, option, index );
464     }
465   }
466 }
467 
setEditorData(QWidget * editor,const QModelIndex & index) const468 void QgsNewVectorTableDialogFieldsDelegate::setEditorData( QWidget *editor, const QModelIndex &index ) const
469 {
470   const auto m { index.model() };
471   switch ( index.column() )
472   {
473     case QgsNewVectorTableFieldModel::ColumnHeaders::Type:
474     {
475       const QString txt = m->data( index, Qt::DisplayRole ).toString();
476       QComboBox *cbo{ qobject_cast<QComboBox *>( editor ) };
477       if ( cbo )
478       {
479         cbo->setCurrentIndex( cbo->findText( txt ) );
480       }
481       break;
482     }
483     case QgsNewVectorTableFieldModel::ColumnHeaders::Precision:
484     case QgsNewVectorTableFieldModel::ColumnHeaders::Length:
485     {
486       const int value = m->data( index, Qt::DisplayRole ).toInt();
487       QSpinBox *sp{ qobject_cast<QSpinBox *>( editor ) };
488       if ( sp )
489       {
490         sp->setValue( value );
491       }
492       break;
493     }
494     default:
495     {
496       QStyledItemDelegate::setEditorData( editor, index );
497     }
498   }
499 }
500 
setModelData(QWidget * editor,QAbstractItemModel * model,const QModelIndex & index) const501 void QgsNewVectorTableDialogFieldsDelegate::setModelData( QWidget *editor, QAbstractItemModel *model, const QModelIndex &index ) const
502 {
503   switch ( index.column() )
504   {
505     case QgsNewVectorTableFieldModel::ColumnHeaders::Type:
506     {
507       QComboBox *cbo { qobject_cast<QComboBox *>( editor ) };
508       if ( cbo )
509       {
510         model->setData( index, cbo->currentData() );
511       }
512       break;
513     }
514     default:
515     {
516       QStyledItemDelegate::setModelData( editor, model, index );
517     }
518   }
519 }
520 
onFieldTypeChanged(int index)521 void QgsNewVectorTableDialogFieldsDelegate::onFieldTypeChanged( int index )
522 {
523   Q_UNUSED( index )
524   QComboBox *cb = static_cast<QComboBox *>( sender() );
525   if ( cb )
526   {
527     emit commitData( cb );
528   }
529 }
530 
QgsNewVectorTableFieldModel(const QList<QgsVectorDataProvider::NativeType> & typeList,QObject * parent)531 QgsNewVectorTableFieldModel::QgsNewVectorTableFieldModel( const QList<QgsVectorDataProvider::NativeType> &typeList, QObject *parent )
532   : QgsFieldModel( parent )
533   , mNativeTypes( typeList )
534 {
535 
536 }
537 
columnCount(const QModelIndex &) const538 int QgsNewVectorTableFieldModel::columnCount( const QModelIndex & ) const
539 {
540   return 6;
541 }
542 
data(const QModelIndex & index,int role) const543 QVariant QgsNewVectorTableFieldModel::data( const QModelIndex &index, int role ) const
544 {
545   if ( mFields.exists( index.row() ) )
546   {
547     const QgsField field { mFields.at( index.row() ) };
548     switch ( role )
549     {
550       case Qt::ItemDataRole::DisplayRole:
551       {
552         switch ( static_cast<ColumnHeaders>( index.column() ) )
553         {
554           case ColumnHeaders::Name:
555           {
556             return QgsFieldModel::data( index, role );
557           }
558           case ColumnHeaders::Type:
559           {
560             return typeDesc( field.typeName() );
561           }
562           case ColumnHeaders::ProviderType:
563           {
564             return field.typeName();
565           }
566           case ColumnHeaders::Comment:
567           {
568             return field.comment();
569           }
570           case ColumnHeaders::Precision:
571           {
572             return field.precision();
573           }
574           case ColumnHeaders::Length:
575           {
576             return field.length();
577           }
578           default:
579             break;
580         }
581         return QgsFieldModel::data( index, role );
582       }
583       case Qt::ItemDataRole::TextAlignmentRole:
584       {
585         switch ( static_cast<ColumnHeaders>( index.column() ) )
586         {
587           case ColumnHeaders::Precision:
588           case ColumnHeaders::Length:
589           {
590             return static_cast<Qt::Alignment::Int>( Qt::AlignmentFlag::AlignVCenter | Qt::AlignmentFlag::AlignHCenter );
591           }
592           default:
593             break;
594         }
595         return QgsFieldModel::data( index, role );
596       }
597       default:
598       {
599         if ( static_cast<ColumnHeaders>( index.column() ) == ColumnHeaders::Name )
600         {
601           return QgsFieldModel::data( index, role );
602         }
603       }
604     }
605   }
606   return QVariant();
607 }
608 
headerData(int section,Qt::Orientation orientation,int role) const609 QVariant QgsNewVectorTableFieldModel::headerData( int section, Qt::Orientation orientation, int role ) const
610 {
611   if ( orientation == Qt::Orientation::Horizontal )
612   {
613     switch ( role )
614     {
615       case Qt::ItemDataRole::DisplayRole:
616       {
617         switch ( static_cast<ColumnHeaders>( section ) )
618         {
619           case ColumnHeaders::Name:
620           {
621             return tr( "Name" );
622           }
623           case ColumnHeaders::Type:
624           {
625             return tr( "Type" );
626           }
627           case ColumnHeaders::Comment:
628           {
629             return tr( "Comment" );
630           }
631           case ColumnHeaders::ProviderType:
632           {
633             return tr( "Provider type" );
634           }
635           case ColumnHeaders::Length:
636           {
637             return tr( "Length" );
638           }
639           case ColumnHeaders::Precision:
640           {
641             return tr( "Precision" );
642           }
643           default:
644             return QVariant();
645         }
646         break;
647       }
648       case Qt::ItemDataRole::TextAlignmentRole:
649       {
650         switch ( static_cast<ColumnHeaders>( section ) )
651         {
652           case ColumnHeaders::Name:
653           case ColumnHeaders::Comment:
654           case ColumnHeaders::Type:
655           case ColumnHeaders::ProviderType:
656           {
657             return static_cast<Qt::Alignment::Int>( Qt::AlignmentFlag::AlignVCenter | Qt::AlignmentFlag::AlignLeft );
658           }
659           default:
660           {
661             return static_cast<Qt::Alignment::Int>( Qt::AlignmentFlag::AlignVCenter | Qt::AlignmentFlag::AlignHCenter );
662           }
663         }
664         break;
665       }
666       default:
667       {
668         QgsFieldModel::headerData( section, orientation, role );
669       }
670     }
671   }
672   return QVariant();
673 }
674 
flags(const QModelIndex & index) const675 Qt::ItemFlags QgsNewVectorTableFieldModel::flags( const QModelIndex &index ) const
676 {
677   switch ( static_cast<ColumnHeaders>( index.column() ) )
678   {
679     case ColumnHeaders::Name:
680     case ColumnHeaders::Comment:
681     case ColumnHeaders::Type:
682     {
683       return QgsFieldModel::flags( index ) | Qt::ItemIsEditable;
684     }
685     case ColumnHeaders::Length:
686     {
687       if ( mFields.exists( index.row( ) ) )
688       {
689         const QgsVectorDataProvider::NativeType nt { nativeType( mFields.at( index.row( ) ).typeName() ) };
690         if ( nt.mMinLen < nt.mMaxLen )
691         {
692           return QgsFieldModel::flags( index ) | Qt::ItemIsEditable;
693         }
694       }
695       break;
696     }
697     case ColumnHeaders::Precision:
698     {
699       if ( mFields.exists( index.row( ) ) )
700       {
701         const QgsVectorDataProvider::NativeType nt { nativeType( mFields.at( index.row( ) ).typeName() ) };
702         if ( nt.mMinPrec < nt.mMaxPrec )
703         {
704           return QgsFieldModel::flags( index ) | Qt::ItemIsEditable;
705         }
706       }
707       break;
708     }
709     case ColumnHeaders::ProviderType:
710     {
711       return QgsFieldModel::flags( index );
712     }
713   }
714   return QgsFieldModel::flags( index );
715 }
716 
nativeTypes() const717 QList<QgsVectorDataProvider::NativeType> QgsNewVectorTableFieldModel::nativeTypes() const
718 {
719   return mNativeTypes;
720 }
721 
typeDesc(const QString & typeName) const722 QString QgsNewVectorTableFieldModel::typeDesc( const QString &typeName ) const
723 {
724   for ( const auto &t : std::as_const( mNativeTypes ) )
725   {
726     if ( t.mTypeName.compare( typeName, Qt::CaseSensitivity::CaseInsensitive ) == 0 )
727     {
728       return t.mTypeDesc;
729     }
730   }
731   return typeName;
732 }
733 
type(const QString & typeName) const734 QVariant::Type QgsNewVectorTableFieldModel::type( const QString &typeName ) const
735 {
736   return nativeType( typeName ).mType;
737 }
738 
nativeType(const QString & typeName) const739 QgsVectorDataProvider::NativeType QgsNewVectorTableFieldModel::nativeType( const QString &typeName ) const
740 {
741   for ( const auto &t : std::as_const( mNativeTypes ) )
742   {
743     if ( t.mTypeName.compare( typeName, Qt::CaseSensitivity::CaseInsensitive ) == 0 )
744     {
745       return t;
746     }
747   }
748   // This should never happen!
749   QgsDebugMsg( QStringLiteral( "Cannot get field native type for: %1" ).arg( typeName ) );
750   return mNativeTypes.first();
751 }
752 
nativeType(int row) const753 QgsVectorDataProvider::NativeType QgsNewVectorTableFieldModel::nativeType( int row ) const
754 {
755   if ( mFields.exists( row ) )
756   {
757     return nativeType( mFields.at( row ).typeName() );
758   }
759   // This should never happen!
760   QgsDebugMsg( QStringLiteral( "Cannot get field for row: %1" ).arg( row ) );
761   return mNativeTypes.first();
762 }
763 
setData(const QModelIndex & index,const QVariant & value,int role)764 bool QgsNewVectorTableFieldModel::setData( const QModelIndex &index, const QVariant &value, int role )
765 {
766   if ( role == Qt::ItemDataRole::EditRole && mFields.exists( index.row() ) && index.column() < 6 )
767   {
768     const int fieldIdx { index.row() };
769     QgsField field {mFields.at( fieldIdx )};
770     switch ( static_cast<ColumnHeaders>( index.column() ) )
771     {
772       case ColumnHeaders::Name:
773       {
774         field.setName( value.toString() );
775         break;
776       }
777       case ColumnHeaders::Type:
778       {
779         field.setTypeName( value.toString() );
780         const auto tp { nativeType( value.toString() ) };
781         field.setType( tp.mType );
782         field.setLength( std::max( std::min<int>( field.length(), tp.mMaxLen ), tp.mMinLen ) );
783         field.setPrecision( std::max( std::min<int>( field.precision(), tp.mMaxPrec ), tp.mMinPrec ) );
784         break;
785       }
786       case ColumnHeaders::Comment:
787       {
788         field.setComment( value.toString() );
789         break;
790       }
791       case ColumnHeaders::ProviderType:
792       {
793         field.setTypeName( value.toString() );
794         break;
795       }
796       case ColumnHeaders::Length:
797       {
798         field.setLength( value.toInt() );
799         break;
800       }
801       case ColumnHeaders::Precision:
802       {
803         field.setPrecision( value.toInt() );
804         break;
805       }
806     }
807 
808     QgsFields fields;
809     for ( int i = 0; i < mFields.count(); ++i )
810     {
811       if ( i == fieldIdx )
812       {
813         fields.append( field );
814       }
815       else
816       {
817         fields.append( mFields.at( i ) );
818       }
819     }
820     setFields( fields );
821   }
822   return QgsFieldModel::setData( index, value, role );
823 }
824 
825 /// @endcond
826