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;
204 for (
const QgsField &field : constFields )
206 int fieldIndex = mLayer->fields().lookupField( field.name() );
207 if ( fieldIndex == -1 )
210 QString fieldName = field.name();
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 );
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 panOrZoomToFeature( featureset );
632 mFeatureListView->setCurrentFeatureEdited(
false );
633 mFeatureListView->setEditSelection( fids );
638 return mAttributeForm->
save();
643 mConditionalFormatWidgetStack->setVisible( !mConditionalFormatWidgetStack->isVisible() );
650 mPreviousView =
view();
672 void QgsDualView::previewExpressionBuilder()
678 dlg.setWindowTitle( tr(
"Expression Based Preview" ) );
679 dlg.setExpressionText( mFeatureListView->displayExpression() );
681 if ( dlg.exec() == QDialog::Accepted )
683 mFeatureListView->setDisplayExpression( dlg.expressionText() );
684 mFeatureListPreviewButton->setDefaultAction( mActionExpressionPreview );
685 mFeatureListPreviewButton->setPopupMode( QToolButton::MenuButtonPopup );
688 setDisplayExpression( mFeatureListView->displayExpression() );
691 void QgsDualView::previewColumnChanged( QAction *previewAction,
const QString &expression )
693 if ( !mFeatureListView->setDisplayExpression( QStringLiteral(
"COALESCE( \"%1\", '<NULL>' )" ).arg( expression ) ) )
695 QMessageBox::warning(
this,
696 tr(
"Column Preview" ),
697 tr(
"Could not set column '%1' as preview column.\nParser error:\n%2" )
698 .arg( previewAction->text(), mFeatureListView->parserErrorString() )
703 mFeatureListPreviewButton->setText( previewAction->text() );
704 mFeatureListPreviewButton->setIcon( previewAction->icon() );
705 mFeatureListPreviewButton->setPopupMode( QToolButton::InstantPopup );
708 setDisplayExpression( mFeatureListView->displayExpression() );
718 return mFilterModel->rowCount();
723 const QModelIndex currentIndex = mTableView->currentIndex();
724 if ( !currentIndex.isValid() )
729 QVariant var = mMasterModel->
data( currentIndex, Qt::DisplayRole );
730 QApplication::clipboard()->setText( var.toString() );
736 mProgressDlg->cancel();
741 if ( mAttributeForm )
750 saveRecentDisplayExpressions();
753 void QgsDualView::viewWillShowContextMenu( QMenu *menu,
const QModelIndex &masterIndex )
760 QAction *copyContentAction = menu->addAction( tr(
"Copy Cell Content" ) );
761 menu->addAction( copyContentAction );
762 connect( copyContentAction, &QAction::triggered,
this, [masterIndex,
this]
764 QVariant var = mMasterModel->
data( masterIndex, Qt::DisplayRole );
765 QApplication::clipboard()->setText( var.toString() );
772 QAction *zoomToFeatureAction = menu->addAction( tr(
"Zoom to Feature" ) );
773 connect( zoomToFeatureAction, &QAction::triggered,
this, &QgsDualView::zoomToCurrentFeature );
775 QAction *panToFeatureAction = menu->addAction( tr(
"Pan to Feature" ) );
776 connect( panToFeatureAction, &QAction::triggered,
this, &QgsDualView::panToCurrentFeature );
778 QAction *flashFeatureAction = menu->addAction( tr(
"Flash Feature" ) );
779 connect( flashFeatureAction, &QAction::triggered,
this, &QgsDualView::flashCurrentFeature );
783 const QList<QgsAction> actions = mLayer->actions()->actions( QStringLiteral(
"Field" ) );
784 if ( !actions.isEmpty() )
786 QAction *a = menu->addAction( tr(
"Run Layer Action" ) );
787 a->setEnabled(
false );
789 for (
const QgsAction &action : actions )
791 if ( !action.runable() )
794 if ( vl && !vl->isEditable() && action.isEnabledOnlyWhenEditable() )
801 QModelIndex rowSourceIndex = mMasterModel->index( masterIndex.row(), 0 );
802 if ( ! rowSourceIndex.isValid() )
809 if ( !registeredActions.isEmpty() )
812 menu->addSeparator();
824 if ( mLayer->selectedFeatureCount() > 1 && mLayer->selectedFeatureIds().contains( currentFid ) )
827 if ( !registeredActions.isEmpty() )
829 menu->addSeparator();
830 QAction *action = menu->addAction( tr(
"Actions on Selection (%1)" ).arg( mLayer->selectedFeatureCount() ) );
831 action->setEnabled(
false );
835 menu->addAction( action->text(), action, [ = ]() {action->triggerForFeatures( mLayer, mLayer->selectedFeatures() );} );
840 menu->addSeparator();
846 void QgsDualView::widgetWillShowContextMenu(
QgsActionMenu *menu,
const QModelIndex &atIndex )
852 void QgsDualView::showViewHeaderMenu( QPoint point )
854 int col = mTableView->columnAt( point.x() );
856 delete mHorizontalHeaderMenu;
857 mHorizontalHeaderMenu =
new QMenu(
this );
859 QAction *hide =
new QAction( tr(
"&Hide Column" ), mHorizontalHeaderMenu );
860 connect( hide, &QAction::triggered,
this, &QgsDualView::hideColumn );
861 hide->setData( col );
862 mHorizontalHeaderMenu->addAction( hide );
863 QAction *setWidth =
new QAction( tr(
"&Set Width…" ), mHorizontalHeaderMenu );
864 connect( setWidth, &QAction::triggered,
this, &QgsDualView::resizeColumn );
865 setWidth->setData( col );
866 mHorizontalHeaderMenu->addAction( setWidth );
867 QAction *optimizeWidth =
new QAction( tr(
"&Autosize" ), mHorizontalHeaderMenu );
868 connect( optimizeWidth, &QAction::triggered,
this, &QgsDualView::autosizeColumn );
869 optimizeWidth->setData( col );
870 mHorizontalHeaderMenu->addAction( optimizeWidth );
872 mHorizontalHeaderMenu->addSeparator();
873 QAction *organize =
new QAction( tr(
"&Organize Columns…" ), mHorizontalHeaderMenu );
874 connect( organize, &QAction::triggered,
this, &QgsDualView::organizeColumns );
875 mHorizontalHeaderMenu->addAction( organize );
876 QAction *sort =
new QAction( tr(
"&Sort…" ), mHorizontalHeaderMenu );
877 connect( sort, &QAction::triggered,
this, [ = ]() {modifySort();} );
878 mHorizontalHeaderMenu->addAction( sort );
880 mHorizontalHeaderMenu->popup( mTableView->horizontalHeader()->mapToGlobal( point ) );
883 void QgsDualView::organizeColumns()
891 if ( dialog.exec() == QDialog::Accepted )
898 void QgsDualView::tableColumnResized(
int column,
int width )
902 if ( sourceCol >= 0 && config.
columnWidth( sourceCol ) != width )
909 void QgsDualView::hideColumn()
911 QAction *action = qobject_cast<QAction *>( sender() );
912 int col = action->data().toInt();
915 if ( sourceCol >= 0 )
922 void QgsDualView::resizeColumn()
924 QAction *action = qobject_cast<QAction *>( sender() );
925 int col = action->data().toInt();
931 if ( sourceCol >= 0 )
934 int width = QInputDialog::getInt(
this, tr(
"Set column width" ), tr(
"Enter column width" ),
935 mTableView->columnWidth( col ),
945 void QgsDualView::autosizeColumn()
947 QAction *action = qobject_cast<QAction *>( sender() );
948 int col = action->data().toInt();
949 mTableView->resizeColumnToContents( col );
952 bool QgsDualView::modifySort()
960 orderByDlg.setWindowTitle( tr(
"Configure Attribute Table Sort Order" ) );
961 QDialogButtonBox *dialogButtonBox =
new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel );
962 QGridLayout *layout =
new QGridLayout();
963 connect( dialogButtonBox, &QDialogButtonBox::accepted, &orderByDlg, &QDialog::accept );
964 connect( dialogButtonBox, &QDialogButtonBox::rejected, &orderByDlg, &QDialog::reject );
965 orderByDlg.setLayout( layout );
967 QGroupBox *sortingGroupBox =
new QGroupBox();
968 sortingGroupBox->setTitle( tr(
"Defined sort order in attribute table" ) );
969 sortingGroupBox->setCheckable(
true );
971 layout->addWidget( sortingGroupBox );
972 sortingGroupBox->setLayout(
new QGridLayout() );
977 expressionBuilder->
initWithLayer( mLayer, context, QStringLiteral(
"generic" ) );
980 sortingGroupBox->layout()->addWidget( expressionBuilder );
982 QCheckBox *cbxSortAscending =
new QCheckBox( tr(
"Sort ascending" ) );
983 cbxSortAscending->setChecked( config.
sortOrder() == Qt::AscendingOrder );
984 sortingGroupBox->layout()->addWidget( cbxSortAscending );
986 layout->addWidget( dialogButtonBox );
987 if ( orderByDlg.exec() )
989 Qt::SortOrder sortOrder = cbxSortAscending->isChecked() ? Qt::AscendingOrder : Qt::DescendingOrder;
990 if ( sortingGroupBox->isChecked() )
1012 void QgsDualView::zoomToCurrentFeature()
1014 QModelIndex currentIndex = mTableView->currentIndex();
1015 if ( !currentIndex.isValid() )
1021 ids.insert( mFilterModel->
rowToId( currentIndex ) );
1029 void QgsDualView::panToCurrentFeature()
1031 QModelIndex currentIndex = mTableView->currentIndex();
1032 if ( !currentIndex.isValid() )
1038 ids.insert( mFilterModel->
rowToId( currentIndex ) );
1046 void QgsDualView::flashCurrentFeature()
1048 QModelIndex currentIndex = mTableView->currentIndex();
1049 if ( !currentIndex.isValid() )
1055 ids.insert( mFilterModel->
rowToId( currentIndex ) );
1063 void QgsDualView::rebuildFullLayerCache()
1071 void QgsDualView::previewExpressionChanged(
const QString &expression )
1073 mLayer->setDisplayExpression( expression );
1076 void QgsDualView::onSortColumnChanged()
1080 cfg.
sortOrder() != mFilterModel->sortOrder() )
1088 void QgsDualView::updateSelectedFeatures()
1100 void QgsDualView::extentChanged()
1113 void QgsDualView::featureFormAttributeChanged(
const QString &attribute,
const QVariant &value,
bool attributeChanged )
1115 Q_UNUSED( attribute )
1117 if ( attributeChanged )
1118 mFeatureListView->setCurrentFeatureEdited(
true );
1140 mTableView->setFeatureSelectionManager( featureSelectionManager );
1141 mFeatureListView->setFeatureSelectionManager( featureSelectionManager );
1143 if ( mFeatureSelectionManager && mFeatureSelectionManager->parent() ==
this )
1144 delete mFeatureSelectionManager;
1146 mFeatureSelectionManager = featureSelectionManager;
1152 mConfig.
update( mLayer->fields() );
1153 mLayer->setAttributeTableConfig( mConfig );
1155 mTableView->setAttributeTableConfig( mConfig );
1161 mFilterModel->
sort( -1 );
1180 void QgsDualView::progress(
int i,
bool &cancel )
1182 if ( !mProgressDlg )
1184 mProgressDlg =
new QProgressDialog( tr(
"Loading features…" ), tr(
"Abort" ), 0, 0,
this );
1185 mProgressDlg->setWindowTitle( tr(
"Attribute Table" ) );
1186 mProgressDlg->setWindowModality( Qt::WindowModal );
1187 mProgressDlg->show();
1190 mProgressDlg->setLabelText( tr(
"%1 features loaded." ).arg( i ) );
1191 QCoreApplication::processEvents();
1193 cancel = mProgressDlg && mProgressDlg->wasCanceled();
1196 void QgsDualView::finished()
1198 delete mProgressDlg;
1199 mProgressDlg =
nullptr;