19 #include <QMessageBox>
20 #include <QProgressDialog>
22 #include <QInputDialog>
50 : QStackedWidget( parent )
59 mTableView->horizontalHeader()->setContextMenuPolicy( Qt::CustomContextMenu );
60 connect( mTableView->horizontalHeader(), &QHeaderView::customContextMenuRequested,
this, &QgsDualView::showViewHeaderMenu );
63 mConditionalFormatWidgetStack->hide();
65 mConditionalFormatWidgetStack->setMainPanel( mConditionalFormatWidget );
69 mConditionalSplitter->restoreState( settings.
value( QStringLiteral(
"/qgis/attributeTable/splitterState" ), QByteArray() ).toByteArray() );
71 mPreviewColumnsMenu =
new QMenu(
this );
72 mActionPreviewColumnsMenu->setMenu( mPreviewColumnsMenu );
78 connect( mActionExpressionPreview, &QAction::triggered,
this, &QgsDualView::previewExpressionBuilder );
87 auto createShortcuts = [ = ](
const QString & objectName, void (
QgsFeatureListView::* slot )() )
93 connect( sc, &QShortcut::activated, mFeatureListView, slot );
100 QButtonGroup *buttonGroup =
new QButtonGroup(
this );
101 buttonGroup->setExclusive(
false );
105 QAbstractButton *bt = buttonGroup->button(
static_cast<int>( action ) );
107 bt->setChecked(
true );
108 connect( buttonGroup, qgis::overload< QAbstractButton *, bool >::of( &QButtonGroup::buttonToggled ),
this, &QgsDualView::panZoomGroupButtonToggled );
109 mFlashButton->setChecked(
QgsSettings().value( QStringLiteral(
"/qgis/attributeTable/featureListHighlightFeature" ),
true ).toBool() );
110 connect( mFlashButton, &QToolButton::clicked,
this, &QgsDualView::flashButtonClicked );
116 settings.
setValue( QStringLiteral(
"/qgis/attributeTable/splitterState" ), mConditionalSplitter->saveState() );
126 mEditorContext = context;
129 initModels( mapCanvas, request, loadFeatures );
131 mConditionalFormatWidget->
setLayer( mLayer );
133 mTableView->setModel( mFilterModel );
134 mFeatureListView->setModel( mFeatureListModel );
135 delete mAttributeForm;
136 mAttributeForm =
new QgsAttributeForm( mLayer, mTempAttributeFormFeature, mEditorContext );
141 mAttributeEditorScrollArea->setWidgetResizable(
true );
142 mAttributeEditor->layout()->addWidget( mAttributeEditorScrollArea );
143 mAttributeEditorScrollArea->setWidget( mAttributeForm );
147 mAttributeEditor->layout()->addWidget( mAttributeForm );
157 QgsMapCanvasUtils::flashMatchingFeatures( canvas, mLayer, filter );
164 QgsMapCanvasUtils::zoomToMatchingFeatures( canvas, mLayer, filter );
171 if ( mFeatureListPreviewButton->defaultAction() )
172 mFeatureListView->setDisplayExpression( mDisplayExpression );
179 if ( mFeatureListModel->
rowCount( ) > 0 )
180 mFeatureListView->setEditSelection(
QgsFeatureIds() << mFeatureListModel->
data( mFeatureListModel->index( 0, 0 ), QgsFeatureListModel::Role::FeatureRole ).value<
QgsFeature>().
id() );
184 void QgsDualView::columnBoxInit()
187 QList<QgsField> fields = mLayer->fields().toList();
189 QString defaultField;
192 QString displayExpression = mLayer->displayExpression();
194 if ( displayExpression.isEmpty() )
197 displayExpression = QStringLiteral(
"'[Please define preview text]'" );
200 mFeatureListPreviewButton->addAction( mActionExpressionPreview );
201 mFeatureListPreviewButton->addAction( mActionPreviewColumnsMenu );
203 const auto constFields = fields;
206 int fieldIndex = mLayer->fields().lookupField(
field.
name() );
207 if ( fieldIndex == -1 )
213 QIcon icon = mLayer->fields().iconForField( fieldIndex );
214 QString text = mLayer->attributeDisplayName( fieldIndex );
217 QAction *previewAction =
new QAction( icon, text, mFeatureListPreviewButton );
218 connect( previewAction, &QAction::triggered,
this, [ = ] { previewColumnChanged( previewAction, fieldName ); } );
219 mPreviewColumnsMenu->addAction( previewAction );
221 if ( text == defaultField )
223 mFeatureListPreviewButton->setDefaultAction( previewAction );
228 QMenu *sortMenu =
new QMenu(
this );
230 sortMenuAction->setMenu( sortMenu );
232 QAction *sortByPreviewExpressionAsc =
new QAction(
QgsApplication::getThemeIcon( QStringLiteral(
"sort.svg" ) ), tr(
"By Preview Expression (ascending)" ),
this );
233 connect( sortByPreviewExpressionAsc, &QAction::triggered,
this, [ = ]()
237 sortMenu->addAction( sortByPreviewExpressionAsc );
238 QAction *sortByPreviewExpressionDesc =
new QAction(
QgsApplication::getThemeIcon( QStringLiteral(
"sort-reverse.svg" ) ), tr(
"By Preview Expression (descending)" ),
this );
239 connect( sortByPreviewExpressionDesc, &QAction::triggered,
this, [ = ]()
243 sortMenu->addAction( sortByPreviewExpressionDesc );
244 QAction *sortByPreviewExpressionCustom =
new QAction(
QgsApplication::getThemeIcon( QStringLiteral(
"mIconExpressionPreview.svg" ) ), tr(
"By Custom Expression" ),
this );
245 connect( sortByPreviewExpressionCustom, &QAction::triggered,
this, [ = ]()
250 sortMenu->addAction( sortByPreviewExpressionCustom );
252 mFeatureListPreviewButton->addAction( sortMenuAction );
254 QAction *separator =
new QAction( mFeatureListPreviewButton );
255 separator->setSeparator(
true );
256 mFeatureListPreviewButton->addAction( separator );
257 restoreRecentDisplayExpressions();
260 if ( !mFeatureListPreviewButton->defaultAction() )
262 mFeatureListView->setDisplayExpression( displayExpression );
263 mFeatureListPreviewButton->setDefaultAction( mActionExpressionPreview );
264 setDisplayExpression( mFeatureListView->displayExpression() );
268 mFeatureListPreviewButton->defaultAction()->trigger();
274 setCurrentIndex(
view );
308 || ( mMasterModel->
rowCount() == 0 );
310 if ( !needsGeometry )
347 setBrowsingAutoPanScaleAllowed(
false );
354 setBrowsingAutoPanScaleAllowed(
true );
358 if ( requiresTableReload )
364 whileBlocking( mLayerCache )->setCacheGeometry( needsGeometry );
378 void QgsDualView::initLayerCache(
bool cacheGeometry )
382 int cacheSize = settings.
value( QStringLiteral(
"qgis/attributeTableRowCache" ),
"10000" ).toInt();
388 rebuildFullLayerCache();
394 delete mFeatureListModel;
415 connect( mMasterModel, &QgsAttributeTableModel::rowsRemoved, mFilterModel, &QgsAttributeTableFilterModel::invalidate );
416 connect( mMasterModel, &QgsAttributeTableModel::rowsInserted, mFilterModel, &QgsAttributeTableFilterModel::invalidate );
424 void QgsDualView::restoreRecentDisplayExpressions()
426 const QVariantList previewExpressions = mLayer->customProperty( QStringLiteral(
"dualview/previewExpressions" ) ).toList();
428 for (
const QVariant &previewExpression : previewExpressions )
429 insertRecentlyUsedDisplayExpression( previewExpression.toString() );
432 void QgsDualView::saveRecentDisplayExpressions()
const
438 QList<QAction *> actions = mFeatureListPreviewButton->actions();
441 int index = actions.indexOf( mLastDisplayExpressionAction );
444 QVariantList previewExpressions;
445 for ( ; index < actions.length(); ++index )
447 QAction *action = actions.at( index );
448 previewExpressions << action->property(
"previewExpression" );
451 mLayer->setCustomProperty( QStringLiteral(
"dualview/previewExpressions" ), previewExpressions );
455 void QgsDualView::setDisplayExpression(
const QString &expression )
457 mDisplayExpression = expression;
458 insertRecentlyUsedDisplayExpression( expression );
461 void QgsDualView::insertRecentlyUsedDisplayExpression(
const QString &expression )
463 QList<QAction *> actions = mFeatureListPreviewButton->actions();
466 int index = actions.indexOf( mLastDisplayExpressionAction );
469 for (
int i = 0; index + i < actions.length(); ++i )
471 QAction *action = actions.at( index );
472 if ( action->text() == expression || i >= 9 )
474 if ( action == mLastDisplayExpressionAction )
475 mLastDisplayExpressionAction =
nullptr;
476 mFeatureListPreviewButton->removeAction( action );
480 if ( !mLastDisplayExpressionAction )
481 mLastDisplayExpressionAction = action;
486 QString name = expression;
488 if ( expression.startsWith( QLatin1String(
"COALESCE( \"" ) ) && expression.endsWith( QLatin1String(
", '<NULL>' )" ) ) )
490 name = expression.mid( 11, expression.length() - 24 );
492 int fieldIndex = mLayer->fields().indexOf( name );
493 if ( fieldIndex != -1 )
495 name = mLayer->attributeDisplayName( fieldIndex );
496 icon = mLayer->fields().iconForField( fieldIndex );
504 QAction *previewAction =
new QAction( icon, name, mFeatureListPreviewButton );
505 previewAction->setProperty(
"previewExpression", expression );
506 connect( previewAction, &QAction::triggered,
this, [expression,
this](
bool )
508 setDisplayExpression( expression );
509 mFeatureListPreviewButton->setText( expression );
513 mFeatureListPreviewButton->insertAction( mLastDisplayExpressionAction, previewAction );
514 mLastDisplayExpressionAction = previewAction;
517 void QgsDualView::updateEditSelectionProgress(
int progress,
int count )
519 mProgressCount->setText( QStringLiteral(
"%1 / %2" ).arg( progress + 1 ).arg( count ) );
520 mPreviousFeatureButton->setEnabled( progress > 0 );
521 mNextFeatureButton->setEnabled( progress + 1 < count );
522 mFirstFeatureButton->setEnabled( progress > 0 );
523 mLastFeatureButton->setEnabled( progress + 1 < count );
526 void QgsDualView::panOrZoomToFeature(
const QgsFeatureIds &featureset )
531 if ( mBrowsingAutoPanScaleAllowed )
533 if ( mAutoPanButton->isChecked() )
534 QTimer::singleShot( 0,
this, [ = ]()
538 else if ( mAutoZoomButton->isChecked() )
539 QTimer::singleShot( 0,
this, [ = ]()
544 if ( mFlashButton->isChecked() )
545 QTimer::singleShot( 0,
this, [ = ]()
549 mLastFeatureSet = featureset;
553 void QgsDualView::setBrowsingAutoPanScaleAllowed(
bool allowed )
555 if ( mBrowsingAutoPanScaleAllowed == allowed )
558 mBrowsingAutoPanScaleAllowed = allowed;
560 mAutoPanButton->setEnabled( allowed );
561 mAutoZoomButton->setEnabled( allowed );
563 QString disabledHint = tr(
"(disabled when attribute table only shows features visible in the current map canvas extent)" );
565 mAutoPanButton->setToolTip( tr(
"Automatically pan to the current feature" ) + ( allowed ? QString() : QString(
' ' ) + disabledHint ) );
566 mAutoZoomButton->setToolTip( tr(
"Automatically zoom to the current feature" ) + ( allowed ? QString() : QString(
' ' ) + disabledHint ) );
569 void QgsDualView::panZoomGroupButtonToggled( QAbstractButton *button,
bool checked )
571 if ( button == mAutoPanButton && checked )
574 mAutoZoomButton->setChecked(
false );
576 else if ( button == mAutoZoomButton && checked )
579 mAutoPanButton->setChecked(
false );
586 if ( checked && mLayer->isSpatial() )
587 panOrZoomToFeature( mFeatureListView->currentEditSelection() );
590 void QgsDualView::flashButtonClicked(
bool clicked )
592 QgsSettings().
setValue( QStringLiteral(
"/qgis/attributeTable/featureListHighlightFeature" ), clicked );
599 canvas->
flashFeatureIds( mLayer, mFeatureListView->currentEditSelection() );
602 void QgsDualView::featureListAboutToChangeEditSelection(
bool &ok )
604 if ( mLayer->isEditable() && !mAttributeForm->
save() )
608 void QgsDualView::featureListCurrentEditSelectionChanged(
const QgsFeature &feat )
610 if ( !mAttributeForm )
612 mTempAttributeFormFeature = feat;
614 else if ( !mLayer->isEditable() || mAttributeForm->
save() )
618 featureset << feat.
id();
621 if ( mLayer->isSpatial() )
622 panOrZoomToFeature( featureset );
633 mFeatureListView->setCurrentFeatureEdited(
false );
634 mFeatureListView->setEditSelection( fids );
639 return mAttributeForm->
save();
644 mConditionalFormatWidgetStack->setVisible( !mConditionalFormatWidgetStack->isVisible() );
651 mPreviousView =
view();
673 void QgsDualView::previewExpressionBuilder()
679 dlg.setWindowTitle( tr(
"Expression Based Preview" ) );
680 dlg.setExpressionText( mFeatureListView->displayExpression() );
682 if ( dlg.exec() == QDialog::Accepted )
684 mFeatureListView->setDisplayExpression( dlg.expressionText() );
685 mFeatureListPreviewButton->setDefaultAction( mActionExpressionPreview );
686 mFeatureListPreviewButton->setPopupMode( QToolButton::MenuButtonPopup );
689 setDisplayExpression( mFeatureListView->displayExpression() );
692 void QgsDualView::previewColumnChanged( QAction *previewAction,
const QString &expression )
694 if ( !mFeatureListView->setDisplayExpression( QStringLiteral(
"COALESCE( \"%1\", '<NULL>' )" ).arg( expression ) ) )
696 QMessageBox::warning(
this,
697 tr(
"Column Preview" ),
698 tr(
"Could not set column '%1' as preview column.\nParser error:\n%2" )
699 .arg( previewAction->text(), mFeatureListView->parserErrorString() )
704 mFeatureListPreviewButton->setText( previewAction->text() );
705 mFeatureListPreviewButton->setIcon( previewAction->icon() );
706 mFeatureListPreviewButton->setPopupMode( QToolButton::InstantPopup );
709 setDisplayExpression( mFeatureListView->displayExpression() );
719 return mFilterModel->rowCount();
724 const QModelIndex currentIndex = mTableView->currentIndex();
725 if ( !currentIndex.isValid() )
730 QVariant var = mMasterModel->
data( currentIndex, Qt::DisplayRole );
731 QApplication::clipboard()->setText( var.toString() );
737 mProgressDlg->cancel();
742 if ( mAttributeForm )
751 saveRecentDisplayExpressions();
754 void QgsDualView::viewWillShowContextMenu( QMenu *menu,
const QModelIndex &masterIndex )
761 QAction *copyContentAction = menu->addAction( tr(
"Copy Cell Content" ) );
762 menu->addAction( copyContentAction );
763 connect( copyContentAction, &QAction::triggered,
this, [masterIndex,
this]
765 QVariant var = mMasterModel->
data( masterIndex, Qt::DisplayRole );
766 QApplication::clipboard()->setText( var.toString() );
773 QAction *zoomToFeatureAction = menu->addAction( tr(
"Zoom to Feature" ) );
774 connect( zoomToFeatureAction, &QAction::triggered,
this, &QgsDualView::zoomToCurrentFeature );
776 QAction *panToFeatureAction = menu->addAction( tr(
"Pan to Feature" ) );
777 connect( panToFeatureAction, &QAction::triggered,
this, &QgsDualView::panToCurrentFeature );
779 QAction *flashFeatureAction = menu->addAction( tr(
"Flash Feature" ) );
780 connect( flashFeatureAction, &QAction::triggered,
this, &QgsDualView::flashCurrentFeature );
784 const QList<QgsAction> actions = mLayer->actions()->actions( QStringLiteral(
"Field" ) );
785 if ( !actions.isEmpty() )
787 QAction *a = menu->addAction( tr(
"Run Layer Action" ) );
788 a->setEnabled(
false );
790 for (
const QgsAction &action : actions )
792 if ( !action.runable() )
795 if ( vl && !vl->isEditable() && action.isEnabledOnlyWhenEditable() )
802 QModelIndex rowSourceIndex = mMasterModel->index( masterIndex.row(), 0 );
803 if ( ! rowSourceIndex.isValid() )
810 if ( !registeredActions.isEmpty() )
813 menu->addSeparator();
825 if ( mLayer->selectedFeatureCount() > 1 && mLayer->selectedFeatureIds().contains( currentFid ) )
828 if ( !registeredActions.isEmpty() )
830 menu->addSeparator();
831 QAction *action = menu->addAction( tr(
"Actions on Selection (%1)" ).arg( mLayer->selectedFeatureCount() ) );
832 action->setEnabled(
false );
836 menu->addAction( action->text(), action, [ = ]() {action->triggerForFeatures( mLayer, mLayer->selectedFeatures() );} );
841 menu->addSeparator();
847 void QgsDualView::widgetWillShowContextMenu(
QgsActionMenu *menu,
const QModelIndex &atIndex )
853 void QgsDualView::showViewHeaderMenu( QPoint point )
855 int col = mTableView->columnAt( point.x() );
857 delete mHorizontalHeaderMenu;
858 mHorizontalHeaderMenu =
new QMenu(
this );
860 QAction *hide =
new QAction( tr(
"&Hide Column" ), mHorizontalHeaderMenu );
861 connect( hide, &QAction::triggered,
this, &QgsDualView::hideColumn );
862 hide->setData( col );
863 mHorizontalHeaderMenu->addAction( hide );
864 QAction *setWidth =
new QAction( tr(
"&Set Width…" ), mHorizontalHeaderMenu );
865 connect( setWidth, &QAction::triggered,
this, &QgsDualView::resizeColumn );
866 setWidth->setData( col );
867 mHorizontalHeaderMenu->addAction( setWidth );
868 QAction *optimizeWidth =
new QAction( tr(
"&Autosize" ), mHorizontalHeaderMenu );
869 connect( optimizeWidth, &QAction::triggered,
this, &QgsDualView::autosizeColumn );
870 optimizeWidth->setData( col );
871 mHorizontalHeaderMenu->addAction( optimizeWidth );
873 mHorizontalHeaderMenu->addSeparator();
874 QAction *organize =
new QAction( tr(
"&Organize Columns…" ), mHorizontalHeaderMenu );
875 connect( organize, &QAction::triggered,
this, &QgsDualView::organizeColumns );
876 mHorizontalHeaderMenu->addAction( organize );
877 QAction *sort =
new QAction( tr(
"&Sort…" ), mHorizontalHeaderMenu );
878 connect( sort, &QAction::triggered,
this, [ = ]() {modifySort();} );
879 mHorizontalHeaderMenu->addAction( sort );
881 mHorizontalHeaderMenu->popup( mTableView->horizontalHeader()->mapToGlobal( point ) );
884 void QgsDualView::organizeColumns()
892 if ( dialog.exec() == QDialog::Accepted )
899 void QgsDualView::tableColumnResized(
int column,
int width )
903 if ( sourceCol >= 0 && config.
columnWidth( sourceCol ) != width )
910 void QgsDualView::hideColumn()
912 QAction *action = qobject_cast<QAction *>( sender() );
913 int col = action->data().toInt();
916 if ( sourceCol >= 0 )
923 void QgsDualView::resizeColumn()
925 QAction *action = qobject_cast<QAction *>( sender() );
926 int col = action->data().toInt();
932 if ( sourceCol >= 0 )
935 int width = QInputDialog::getInt(
this, tr(
"Set column width" ), tr(
"Enter column width" ),
936 mTableView->columnWidth( col ),
946 void QgsDualView::autosizeColumn()
948 QAction *action = qobject_cast<QAction *>( sender() );
949 int col = action->data().toInt();
950 mTableView->resizeColumnToContents( col );
953 bool QgsDualView::modifySort()
961 orderByDlg.setWindowTitle( tr(
"Configure Attribute Table Sort Order" ) );
962 QDialogButtonBox *dialogButtonBox =
new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel );
963 QGridLayout *layout =
new QGridLayout();
964 connect( dialogButtonBox, &QDialogButtonBox::accepted, &orderByDlg, &QDialog::accept );
965 connect( dialogButtonBox, &QDialogButtonBox::rejected, &orderByDlg, &QDialog::reject );
966 orderByDlg.setLayout( layout );
968 QGroupBox *sortingGroupBox =
new QGroupBox();
969 sortingGroupBox->setTitle( tr(
"Defined sort order in attribute table" ) );
970 sortingGroupBox->setCheckable(
true );
972 layout->addWidget( sortingGroupBox );
973 sortingGroupBox->setLayout(
new QGridLayout() );
978 expressionBuilder->
initWithLayer( mLayer, context, QStringLiteral(
"generic" ) );
981 sortingGroupBox->layout()->addWidget( expressionBuilder );
983 QCheckBox *cbxSortAscending =
new QCheckBox( tr(
"Sort ascending" ) );
984 cbxSortAscending->setChecked( config.
sortOrder() == Qt::AscendingOrder );
985 sortingGroupBox->layout()->addWidget( cbxSortAscending );
987 layout->addWidget( dialogButtonBox );
988 if ( orderByDlg.exec() )
990 Qt::SortOrder sortOrder = cbxSortAscending->isChecked() ? Qt::AscendingOrder : Qt::DescendingOrder;
991 if ( sortingGroupBox->isChecked() )
1013 void QgsDualView::zoomToCurrentFeature()
1015 QModelIndex currentIndex = mTableView->currentIndex();
1016 if ( !currentIndex.isValid() )
1022 ids.insert( mFilterModel->
rowToId( currentIndex ) );
1030 void QgsDualView::panToCurrentFeature()
1032 QModelIndex currentIndex = mTableView->currentIndex();
1033 if ( !currentIndex.isValid() )
1039 ids.insert( mFilterModel->
rowToId( currentIndex ) );
1047 void QgsDualView::flashCurrentFeature()
1049 QModelIndex currentIndex = mTableView->currentIndex();
1050 if ( !currentIndex.isValid() )
1056 ids.insert( mFilterModel->
rowToId( currentIndex ) );
1064 void QgsDualView::rebuildFullLayerCache()
1072 void QgsDualView::previewExpressionChanged(
const QString &expression )
1074 mLayer->setDisplayExpression( expression );
1077 void QgsDualView::onSortColumnChanged()
1081 cfg.
sortOrder() != mFilterModel->sortOrder() )
1089 void QgsDualView::updateSelectedFeatures()
1101 void QgsDualView::extentChanged()
1114 void QgsDualView::featureFormAttributeChanged(
const QString &attribute,
const QVariant &value,
bool attributeChanged )
1116 Q_UNUSED( attribute )
1118 if ( attributeChanged )
1119 mFeatureListView->setCurrentFeatureEdited(
true );
1141 mTableView->setFeatureSelectionManager( featureSelectionManager );
1142 mFeatureListView->setFeatureSelectionManager( featureSelectionManager );
1144 if ( mFeatureSelectionManager && mFeatureSelectionManager->parent() ==
this )
1145 delete mFeatureSelectionManager;
1147 mFeatureSelectionManager = featureSelectionManager;
1153 mConfig.
update( mLayer->fields() );
1154 mLayer->setAttributeTableConfig( mConfig );
1156 mTableView->setAttributeTableConfig( mConfig );
1162 mFilterModel->
sort( -1 );
1181 void QgsDualView::progress(
int i,
bool &cancel )
1183 if ( !mProgressDlg )
1185 mProgressDlg =
new QProgressDialog( tr(
"Loading features…" ), tr(
"Abort" ), 0, 0,
this );
1186 mProgressDlg->setWindowTitle( tr(
"Attribute Table" ) );
1187 mProgressDlg->setWindowModality( Qt::WindowModal );
1188 mProgressDlg->show();
1191 mProgressDlg->setLabelText( tr(
"%1 features loaded." ).arg( i ) );
1192 QCoreApplication::processEvents();
1194 cancel = mProgressDlg && mProgressDlg->wasCanceled();
1197 void QgsDualView::finished()
1199 delete mProgressDlg;
1200 mProgressDlg =
nullptr;