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