44#include <QInputDialog>
47#include <QProgressDialog>
51#include "moc_qgsdualview.cpp"
53const std::unique_ptr<QgsSettingsEntryVariant> QgsDualView::conditionalFormattingSplitterState = std::make_unique<QgsSettingsEntryVariant>( QStringLiteral(
"attribute-table-splitter-state" ),
QgsSettingsTree::sTreeWindowState,
QgsVariantUtils::createNullVariant( QMetaType::Type::QByteArray ), QStringLiteral(
"State of conditional formatting splitter's layout so it could be restored when opening attribute table view." ) );
54const std::unique_ptr<QgsSettingsEntryVariant> QgsDualView::attributeEditorSplitterState = std::make_unique<QgsSettingsEntryVariant>( QStringLiteral(
"attribute-editor-splitter-state" ),
QgsSettingsTree::sTreeWindowState,
QgsVariantUtils::createNullVariant( QMetaType::Type::QByteArray ), QStringLiteral(
"State of attribute editor splitter's layout so it could be restored when opening attribute editor view." ) );
57 : QStackedWidget( parent )
66 mTableView->horizontalHeader()->setContextMenuPolicy( Qt::CustomContextMenu );
67 connect( mTableView->horizontalHeader(), &QHeaderView::customContextMenuRequested,
this, &QgsDualView::showViewHeaderMenu );
70 mConditionalFormatWidgetStack->hide();
72 mConditionalFormatWidgetStack->setMainPanel( mConditionalFormatWidget );
73 mConditionalFormatWidget->setDockMode(
true );
77 conditionalFormattingSplitterState->copyValueFromKey( QStringLiteral(
"/qgis/attributeTable/splitterState" ),
true );
78 mConditionalSplitter->restoreState( conditionalFormattingSplitterState->value().toByteArray() );
79 mAttributeEditorViewSplitter->restoreState( attributeEditorSplitterState->value().toByteArray() );
81 mPreviewColumnsMenu =
new QMenu(
this );
82 mActionPreviewColumnsMenu->setMenu( mPreviewColumnsMenu );
88 connect( mActionExpressionPreview, &QAction::triggered,
this, &QgsDualView::previewExpressionBuilder );
97 auto createShortcuts = [
this](
const QString &objectName, void (
QgsFeatureListView::*slot )() ) {
102 connect( sc, &QShortcut::activated, mFeatureListView, slot );
109 QButtonGroup *buttonGroup =
new QButtonGroup(
this );
110 buttonGroup->setExclusive(
false );
114 QAbstractButton *bt = buttonGroup->button(
static_cast<int>( action ) );
116 bt->setChecked(
true );
117 connect( buttonGroup, qOverload<QAbstractButton *, bool>( &QButtonGroup::buttonToggled ),
this, &QgsDualView::panZoomGroupButtonToggled );
118 mFlashButton->setChecked(
QgsSettings().value( QStringLiteral(
"/qgis/attributeTable/featureListHighlightFeature" ),
true ).toBool() );
119 connect( mFlashButton, &QToolButton::clicked,
this, &QgsDualView::flashButtonClicked );
131 delete mAttributeForm;
132 mAttributeForm =
nullptr;
141 mEditorContext = context;
153 initLayerCache( needsGeometry );
154 initModels( mapCanvas, request, loadFeatures );
156 mConditionalFormatWidget->setLayer( mLayer );
158 mTableView->setModel( mFilterModel );
159 mFeatureListView->setModel( mFeatureListModel );
164 if ( mFeatureListPreviewButton->defaultAction() )
166 mFeatureListView->setDisplayExpression( mDisplayExpression );
176 if ( showFirstFeature && mFeatureListModel->rowCount() > 0 )
187void QgsDualView::initAttributeForm(
const QgsFeature &feature )
189 Q_ASSERT( !mAttributeForm );
195 mAttributeEditorScrollArea->setWidgetResizable(
true );
196 mAttributeEditor->layout()->addWidget( mAttributeEditorScrollArea );
197 mAttributeEditorScrollArea->setWidget( mAttributeForm );
201 mAttributeEditor->layout()->addWidget( mAttributeForm );
206 mAttributeForm->setMinimumWidth( 200 );
214 if ( QgsMapCanvas *canvas = mFilterModel->mapCanvas() )
220 if ( QgsMapCanvas *canvas = mFilterModel->mapCanvas() )
229void QgsDualView::columnBoxInit()
232 const QList<QgsField> fields = mLayer->fields().toList();
233 const QString displayExpression = mLayer->displayExpression();
235 mFeatureListPreviewButton->addAction( mActionExpressionPreview );
236 mFeatureListPreviewButton->addAction( mActionPreviewColumnsMenu );
238 QAction *defaultFieldAction =
nullptr;
239 const auto constFields = fields;
240 for (
const QgsField &field : constFields )
242 const int fieldIndex = mLayer->fields().lookupField( field.name() );
243 if ( fieldIndex == -1 )
246 const QString fieldName = field.name();
249 const QIcon icon = mLayer->fields().iconForField( fieldIndex );
250 const QString text = mLayer->attributeDisplayName( fieldIndex );
253 QAction *previewAction =
new QAction( icon, text, mFeatureListPreviewButton );
254 connect( previewAction, &QAction::triggered,
this, [
this, previewAction, fieldName] { previewColumnChanged( previewAction, fieldName ); } );
255 mPreviewColumnsMenu->addAction( previewAction );
257 if ( text == displayExpression || QStringLiteral(
"COALESCE( \"%1\", '<NULL>' )" ).arg( text ) == displayExpression || QStringLiteral(
"\"%1\"" ).arg( text ) == displayExpression )
259 defaultFieldAction = previewAction;
264 QMenu *sortMenu =
new QMenu(
this );
266 sortMenuAction->setMenu( sortMenu );
268 QAction *sortByPreviewExpressionAsc =
new QAction(
QgsApplication::getThemeIcon( QStringLiteral(
"sort.svg" ) ), tr(
"By Display Name (Ascending)" ),
this );
269 connect( sortByPreviewExpressionAsc, &QAction::triggered,
this, [
this]() {
270 mFeatureListModel->setSortByDisplayExpression(
true, Qt::AscendingOrder );
272 sortMenu->addAction( sortByPreviewExpressionAsc );
273 QAction *sortByPreviewExpressionDesc =
new QAction(
QgsApplication::getThemeIcon( QStringLiteral(
"sort-reverse.svg" ) ), tr(
"By Display Name (Descending)" ),
this );
274 connect( sortByPreviewExpressionDesc, &QAction::triggered,
this, [
this]() {
275 mFeatureListModel->setSortByDisplayExpression(
true, Qt::DescendingOrder );
277 sortMenu->addAction( sortByPreviewExpressionDesc );
278 QAction *sortByPreviewExpressionCustom =
new QAction(
QgsApplication::getThemeIcon( QStringLiteral(
"mIconExpressionPreview.svg" ) ), tr(
"By Custom Expression" ),
this );
279 connect( sortByPreviewExpressionCustom, &QAction::triggered,
this, [
this]() {
281 mFeatureListModel->setSortByDisplayExpression(
false );
283 sortMenu->addAction( sortByPreviewExpressionCustom );
285 mFeatureListPreviewButton->addAction( sortMenuAction );
287 QAction *separator =
new QAction( mFeatureListPreviewButton );
288 separator->setSeparator(
true );
289 mFeatureListPreviewButton->addAction( separator );
290 restoreRecentDisplayExpressions();
292 if ( defaultFieldAction )
294 mFeatureListPreviewButton->setDefaultAction( defaultFieldAction );
295 mFeatureListPreviewButton->defaultAction()->trigger();
299 mActionExpressionPreview->setText( displayExpression );
300 mFeatureListPreviewButton->setDefaultAction( mActionExpressionPreview );
302 mFeatureListView->setDisplayExpression( displayExpression );
303 setDisplayExpression( displayExpression.isEmpty() ? tr(
"'[Please define preview text]'" ) : displayExpression );
309 setCurrentIndex(
view );
320 switch ( mFilterModel->filterMode() )
344 mMasterModel->setShowValidityState(
false );
356 || ( mMasterModel->rowCount() == 0 );
368 if ( mFilterModel->mapCanvas() )
370 const QgsRectangle rect = mFilterModel->mapCanvas()->mapSettings().mapToLayerCoordinates( mLayer, mFilterModel->mapCanvas()->extent() );
385 mMasterModel->setShowValidityState(
true );
387 filterFeatures( QStringLiteral(
"is_feature_valid() = false" ), context );
397 if ( !filterExpression.isEmpty() )
414 setBrowsingAutoPanScaleAllowed(
false );
422 setBrowsingAutoPanScaleAllowed(
true );
426 if ( requiresTableReload )
429 mFilterModel->disconnectFilterModeConnections();
431 mMasterModel->setRequest( request );
432 whileBlocking( mLayerCache )->setCacheGeometry( needsGeometry );
433 mMasterModel->loadLayer();
443 mFilterModel->setSelectedOnTop( selectedOnTop );
446void QgsDualView::initLayerCache(
bool cacheGeometry )
450 const int cacheSize = settings.
value( QStringLiteral(
"qgis/attributeTableRowCache" ),
"10000" ).toInt();
457 rebuildFullLayerCache();
463 delete mFeatureListModel;
467 mMasterModel =
new QgsAttributeTableModel( mLayerCache,
this );
468 mMasterModel->setRequest( request );
469 mMasterModel->setEditorContext( mEditorContext );
470 mMasterModel->setExtraColumns( 1 );
478 mMasterModel->loadLayer();
480 mFilterModel =
new QgsAttributeTableFilterModel( mapCanvas, mMasterModel, mMasterModel );
484 connect( mMasterModel, &QgsAttributeTableModel::rowsRemoved, mFilterModel, &QgsAttributeTableFilterModel::invalidate );
485 connect( mMasterModel, &QgsAttributeTableModel::rowsInserted, mFilterModel, &QgsAttributeTableFilterModel::invalidate );
489 mFeatureListModel =
new QgsFeatureListModel( mFilterModel, mFilterModel );
492void QgsDualView::restoreRecentDisplayExpressions()
494 const QVariantList previewExpressions = mLayer->customProperty( QStringLiteral(
"dualview/previewExpressions" ) ).toList();
496 for (
const QVariant &previewExpression : previewExpressions )
497 insertRecentlyUsedDisplayExpression( previewExpression.toString() );
500void QgsDualView::saveRecentDisplayExpressions()
const
506 const QList<QAction *> actions = mFeatureListPreviewButton->actions();
509 int index = actions.indexOf( mLastDisplayExpressionAction );
512 QVariantList previewExpressions;
513 for ( ; index < actions.length(); ++index )
515 QAction *action = actions.at( index );
516 previewExpressions << action->property(
"previewExpression" );
519 mLayer->setCustomProperty( QStringLiteral(
"dualview/previewExpressions" ), previewExpressions );
523void QgsDualView::setDisplayExpression(
const QString &expression )
525 mDisplayExpression = expression;
526 insertRecentlyUsedDisplayExpression( expression );
529void QgsDualView::insertRecentlyUsedDisplayExpression(
const QString &expression )
531 const QList<QAction *> actions = mFeatureListPreviewButton->actions();
534 const int index = actions.indexOf( mLastDisplayExpressionAction );
537 for (
int i = 0; index + i < actions.length(); ++i )
539 QAction *action = actions.at( index );
540 if ( action->text() == expression || i >= 9 )
542 if ( action == mLastDisplayExpressionAction )
544 mLastDisplayExpressionAction =
nullptr;
546 mFeatureListPreviewButton->removeAction( action );
550 if ( !mLastDisplayExpressionAction )
552 mLastDisplayExpressionAction = action;
558 QString name = expression;
560 if ( expression.startsWith( QLatin1String(
"COALESCE( \"" ) ) && expression.endsWith( QLatin1String(
", '<NULL>' )" ) ) )
562 name = expression.mid( 11, expression.length() - 24 );
564 const int fieldIndex = mLayer->fields().indexOf( name );
565 if ( fieldIndex != -1 )
567 name = mLayer->attributeDisplayName( fieldIndex );
568 icon = mLayer->fields().iconForField( fieldIndex );
576 QAction *previewAction =
new QAction( icon, name, mFeatureListPreviewButton );
577 previewAction->setProperty(
"previewExpression", expression );
578 connect( previewAction, &QAction::triggered,
this, [expression,
this](
bool ) {
579 setDisplayExpression( expression );
580 mFeatureListPreviewButton->setText( expression );
583 mFeatureListPreviewButton->insertAction( mLastDisplayExpressionAction, previewAction );
584 mLastDisplayExpressionAction = previewAction;
587void QgsDualView::updateEditSelectionProgress(
int progress,
int count )
589 mProgressCount->setText( QStringLiteral(
"%1 / %2" ).arg( progress + 1 ).arg( count ) );
590 mPreviousFeatureButton->setEnabled( progress > 0 );
591 mNextFeatureButton->setEnabled( progress + 1 < count );
592 mFirstFeatureButton->setEnabled( progress > 0 );
593 mLastFeatureButton->setEnabled( progress + 1 < count );
594 if ( mAttributeForm )
596 mAttributeForm->setVisible( count > 0 );
600void QgsDualView::panOrZoomToFeature(
const QgsFeatureIds &featureset )
602 QgsMapCanvas *canvas = mFilterModel->mapCanvas();
605 if ( mBrowsingAutoPanScaleAllowed )
607 if ( mAutoPanButton->isChecked() )
608 QTimer::singleShot( 0,
this, [
this, featureset, canvas]() {
611 else if ( mAutoZoomButton->isChecked() )
612 QTimer::singleShot( 0,
this, [
this, featureset, canvas]() {
616 if ( mFlashButton->isChecked() )
617 QTimer::singleShot( 0,
this, [
this, featureset, canvas]() {
620 mLastFeatureSet = featureset;
624void QgsDualView::setBrowsingAutoPanScaleAllowed(
bool allowed )
626 if ( mBrowsingAutoPanScaleAllowed == allowed )
629 mBrowsingAutoPanScaleAllowed = allowed;
631 mAutoPanButton->setEnabled( allowed );
632 mAutoZoomButton->setEnabled( allowed );
634 const QString disabledHint = tr(
"(disabled when attribute table only shows features visible in the current map canvas extent)" );
636 mAutoPanButton->setToolTip( tr(
"Automatically pan to the current feature" ) + ( allowed ? QString() : QString(
' ' ) + disabledHint ) );
637 mAutoZoomButton->setToolTip( tr(
"Automatically zoom to the current feature" ) + ( allowed ? QString() : QString(
' ' ) + disabledHint ) );
640void QgsDualView::panZoomGroupButtonToggled( QAbstractButton *button,
bool checked )
642 if ( button == mAutoPanButton && checked )
644 QgsSettings().setEnumValue( QStringLiteral(
"/qgis/attributeTable/featureListBrowsingAction" ),
PanToFeature );
645 mAutoZoomButton->setChecked(
false );
647 else if ( button == mAutoZoomButton && checked )
649 QgsSettings().setEnumValue( QStringLiteral(
"/qgis/attributeTable/featureListBrowsingAction" ),
ZoomToFeature );
650 mAutoPanButton->setChecked(
false );
654 QgsSettings().setEnumValue( QStringLiteral(
"/qgis/attributeTable/featureListBrowsingAction" ),
NoAction );
657 if ( checked && mLayer->isSpatial() )
658 panOrZoomToFeature( mFeatureListView->currentEditSelection() );
661void QgsDualView::flashButtonClicked(
bool clicked )
663 QgsSettings().setValue( QStringLiteral(
"/qgis/attributeTable/featureListHighlightFeature" ), clicked );
667 QgsMapCanvas *canvas = mFilterModel->mapCanvas();
670 canvas->
flashFeatureIds( mLayer, mFeatureListView->currentEditSelection() );
673void QgsDualView::filterError(
const QString &errorMessage )
675 if ( mEditorContext.mainMessageBar() )
677 mEditorContext.mainMessageBar()->pushWarning( tr(
"An error occurred while filtering features" ), errorMessage );
681void QgsDualView::featureListAboutToChangeEditSelection(
bool &ok )
683 if ( !mAttributeForm )
686 if ( mLayer->isEditable() && !mAttributeForm->save() )
690void QgsDualView::featureListCurrentEditSelectionChanged(
const QgsFeature &feat )
692 if ( !mAttributeForm )
694 initAttributeForm( feat );
696 else if ( !mLayer->isEditable() || mAttributeForm->save() )
698 mAttributeForm->setFeature( feat );
700 featureset << feat.
id();
703 if ( mLayer->isSpatial() )
704 panOrZoomToFeature( featureset );
714 mFeatureListView->setCurrentFeatureEdited(
false );
715 mFeatureListView->setEditSelection( fids );
720 return mAttributeForm ? mAttributeForm->save() :
false;
725 mConditionalFormatWidgetStack->setVisible( !mConditionalFormatWidgetStack->isVisible() );
730 if ( !mAttributeForm )
735 mPreviousView =
view();
748 if ( !mAttributeForm )
755 mAttributeForm->setVisible(
true );
760 mAttributeForm->setVisible( mFilterModel->rowCount() > 0 );
764void QgsDualView::previewExpressionBuilder()
770 dlg.setWindowTitle( tr(
"Expression Based Preview" ) );
771 dlg.setExpressionText( mFeatureListView->displayExpression() );
773 if ( dlg.exec() == QDialog::Accepted )
775 mFeatureListView->setDisplayExpression( dlg.expressionText() );
776 mActionExpressionPreview->setText( dlg.expressionText() );
778 mFeatureListPreviewButton->setDefaultAction( mActionExpressionPreview );
779 mFeatureListPreviewButton->setPopupMode( QToolButton::MenuButtonPopup );
782 setDisplayExpression( mFeatureListView->displayExpression() );
785void QgsDualView::previewColumnChanged( QAction *previewAction,
const QString &expression )
787 if ( !mFeatureListView->setDisplayExpression( QStringLiteral(
"COALESCE( \"%1\", '<NULL>' )" ).arg( expression ) ) )
789 QMessageBox::warning(
this, tr(
"Column Display Name" ), tr(
"Could not set column '%1' as display name.\nParser error:\n%2" ).arg( previewAction->text(), mFeatureListView->parserErrorString() ) );
793 mActionExpressionPreview->setText( tr(
"Expression" ) );
794 mFeatureListPreviewButton->setText( previewAction->text() );
795 mFeatureListPreviewButton->setIcon( previewAction->icon() );
796 mFeatureListPreviewButton->setPopupMode( QToolButton::InstantPopup );
799 setDisplayExpression( mFeatureListView->displayExpression() );
804 return mMasterModel->rowCount();
809 return mFilterModel->rowCount();
814 const QModelIndex currentIndex = mTableView->currentIndex();
815 if ( !currentIndex.isValid() )
820 const QVariant var = mMasterModel->data( currentIndex, Qt::DisplayRole );
821 QApplication::clipboard()->setText( var.toString() );
827 mProgressDlg->cancel();
832 if ( mAttributeForm )
834 mAttributeForm->parentFormValueChanged( attribute, newValue );
841 saveRecentDisplayExpressions();
848 conditionalFormattingSplitterState->setValue( mConditionalSplitter->saveState() );
849 attributeEditorSplitterState->setValue( mAttributeEditorViewSplitter->saveState() );
852void QgsDualView::viewWillShowContextMenu( QMenu *menu,
const QModelIndex &masterIndex )
859 const QVariant displayValue = mMasterModel->data( masterIndex, Qt::DisplayRole );
861 if ( displayValue.isValid() )
864 QString previewDisplayValue = displayValue.toString();
865 if ( previewDisplayValue.length() > 12 )
867 previewDisplayValue.truncate( 12 );
868 previewDisplayValue.append( QStringLiteral(
"…" ) );
872 QAction *copyContentAction = menu->addAction( tr(
"Copy Cell Content (%1)" ).arg( previewDisplayValue ) );
873 menu->addAction( copyContentAction );
874 connect( copyContentAction, &QAction::triggered,
this, [displayValue] {
875 QApplication::clipboard()->setText( displayValue.toString() );
879 const QVariant rawValue = mMasterModel->data( masterIndex, Qt::EditRole );
880 if ( rawValue.isValid() )
883 QString previewRawValue = rawValue.toString();
884 if ( previewRawValue.length() > 12 )
886 previewRawValue.truncate( 12 );
887 previewRawValue.append( QStringLiteral(
"…" ) );
890 QAction *copyRawContentAction = menu->addAction( tr(
"Copy Raw Value (%1)" ).arg( previewRawValue ) );
891 menu->addAction( copyRawContentAction );
892 connect( copyRawContentAction, &QAction::triggered,
this, [rawValue] {
893 QApplication::clipboard()->setText( rawValue.toString() );
897 QgsVectorLayer *vl = mFilterModel->layer();
898 QgsMapCanvas *canvas = mFilterModel->mapCanvas();
901 QAction *zoomToFeatureAction = menu->addAction( tr(
"Zoom to Feature" ) );
902 connect( zoomToFeatureAction, &QAction::triggered,
this, &QgsDualView::zoomToCurrentFeature );
904 QAction *panToFeatureAction = menu->addAction( tr(
"Pan to Feature" ) );
905 connect( panToFeatureAction, &QAction::triggered,
this, &QgsDualView::panToCurrentFeature );
907 QAction *flashFeatureAction = menu->addAction( tr(
"Flash Feature" ) );
908 connect( flashFeatureAction, &QAction::triggered,
this, &QgsDualView::flashCurrentFeature );
912 const QList<QgsAction> actions = mLayer->actions()->actions( QStringLiteral(
"Field" ) );
913 if ( !actions.isEmpty() )
915 QAction *a = menu->addAction( tr(
"Run Layer Action" ) );
916 a->setEnabled(
false );
918 for (
const QgsAction &action : actions )
920 if ( !action.runable() )
923 if ( vl && !vl->
isEditable() && action.isEnabledOnlyWhenEditable() )
926 QgsAttributeTableAction *a =
new QgsAttributeTableAction( action.name(),
this, action.id(), masterIndex );
930 const QModelIndex rowSourceIndex = mMasterModel->index( masterIndex.row(), 0 );
931 if ( !rowSourceIndex.isValid() )
937 QgsMapLayerActionContext context;
939 if ( !registeredActions.isEmpty() )
942 menu->addSeparator();
944 for ( QgsMapLayerAction *action : registeredActions )
946 QgsAttributeTableMapLayerAction *a =
new QgsAttributeTableMapLayerAction( action->text(),
this, action, rowSourceIndex );
953 const QgsFeatureId currentFid = mMasterModel->rowToId( masterIndex.row() );
954 if ( mLayer->selectedFeatureCount() > 1 && mLayer->selectedFeatureIds().contains( currentFid ) )
957 if ( !registeredActions.isEmpty() )
959 menu->addSeparator();
960 QAction *action = menu->addAction( tr(
"Actions on Selection (%1)" ).arg( mLayer->selectedFeatureCount() ) );
961 action->setEnabled(
false );
963 QgsMapLayerActionContext context;
964 for ( QgsMapLayerAction *action : registeredActions )
966 menu->addAction( action->text(), action, [
this, action, context]() {
967 Q_NOWARN_DEPRECATED_PUSH
968 action->triggerForFeatures( mLayer, mLayer->selectedFeatures() );
969 Q_NOWARN_DEPRECATED_POP
970 action->triggerForFeatures( mLayer, mLayer->selectedFeatures(), context );
976 menu->addSeparator();
977 QgsAttributeTableAction *a =
new QgsAttributeTableAction( tr(
"Open Form" ),
this, QUuid(), rowSourceIndex );
982void QgsDualView::widgetWillShowContextMenu(
QgsActionMenu *menu,
const QModelIndex &atIndex )
988void QgsDualView::showViewHeaderMenu( QPoint point )
990 const int col = mTableView->columnAt( point.x() );
992 delete mHorizontalHeaderMenu;
993 mHorizontalHeaderMenu =
new QMenu(
this );
995 QAction *hide =
new QAction( tr(
"&Hide Column" ), mHorizontalHeaderMenu );
996 connect( hide, &QAction::triggered,
this, &QgsDualView::hideColumn );
997 hide->setData( col );
998 mHorizontalHeaderMenu->addAction( hide );
999 QAction *setWidth =
new QAction( tr(
"&Set Width…" ), mHorizontalHeaderMenu );
1000 connect( setWidth, &QAction::triggered,
this, &QgsDualView::resizeColumn );
1001 setWidth->setData( col );
1002 mHorizontalHeaderMenu->addAction( setWidth );
1004 QAction *setWidthAllColumns =
new QAction( tr(
"&Set All Column Widths…" ), mHorizontalHeaderMenu );
1005 connect( setWidthAllColumns, &QAction::triggered,
this, &QgsDualView::resizeAllColumns );
1006 setWidthAllColumns->setData( col );
1007 mHorizontalHeaderMenu->addAction( setWidthAllColumns );
1009 QAction *optimizeWidth =
new QAction( tr(
"&Autosize" ), mHorizontalHeaderMenu );
1010 connect( optimizeWidth, &QAction::triggered,
this, &QgsDualView::autosizeColumn );
1011 optimizeWidth->setData( col );
1012 mHorizontalHeaderMenu->addAction( optimizeWidth );
1014 QAction *optimizeWidthAllColumns =
new QAction( tr(
"&Autosize All Columns" ), mHorizontalHeaderMenu );
1015 connect( optimizeWidthAllColumns, &QAction::triggered,
this, &QgsDualView::autosizeAllColumns );
1016 mHorizontalHeaderMenu->addAction( optimizeWidthAllColumns );
1019 mHorizontalHeaderMenu->addSeparator();
1020 QAction *organize =
new QAction( tr(
"&Organize Columns…" ), mHorizontalHeaderMenu );
1021 connect( organize, &QAction::triggered,
this, &QgsDualView::organizeColumns );
1022 mHorizontalHeaderMenu->addAction( organize );
1023 QAction *sort =
new QAction( tr(
"&Sort…" ), mHorizontalHeaderMenu );
1024 connect( sort, &QAction::triggered,
this, [
this]() { modifySort(); } );
1025 mHorizontalHeaderMenu->addAction( sort );
1027 mHorizontalHeaderMenu->popup( mTableView->horizontalHeader()->mapToGlobal( point ) );
1030void QgsDualView::organizeColumns()
1038 if ( dialog.exec() == QDialog::Accepted )
1040 const QgsAttributeTableConfig config = dialog.config();
1045void QgsDualView::tableColumnResized(
int column,
int width )
1047 QgsAttributeTableConfig config = mConfig;
1049 if ( sourceCol >= 0 && config.
columnWidth( sourceCol ) != width )
1056void QgsDualView::hideColumn()
1058 QAction *action = qobject_cast<QAction *>( sender() );
1059 const int col = action->data().toInt();
1060 QgsAttributeTableConfig config = mConfig;
1062 if ( sourceCol >= 0 )
1069void QgsDualView::resizeColumn()
1071 QAction *action = qobject_cast<QAction *>( sender() );
1072 const int col = action->data().toInt();
1076 QgsAttributeTableConfig config = mConfig;
1078 if ( sourceCol >= 0 )
1081 const int width = QInputDialog::getInt(
this, tr(
"Set column width" ), tr(
"Enter column width" ), mTableView->columnWidth( col ), 0, 1000, 10, &ok );
1090void QgsDualView::resizeAllColumns()
1092 QAction *action = qobject_cast<QAction *>( sender() );
1093 const int col = action->data().toInt();
1097 QgsAttributeTableConfig config = mConfig;
1100 const int width = QInputDialog::getInt(
this, tr(
"Set Column Width" ), tr(
"Enter column width" ), mTableView->columnWidth( col ), 1, 1000, 10, &ok );
1103 const int colCount = mTableView->model()->columnCount();
1106 for (
int i = 0; i < colCount; i++ )
1115void QgsDualView::autosizeColumn()
1117 QAction *action = qobject_cast<QAction *>( sender() );
1118 const int col = action->data().toInt();
1119 mTableView->resizeColumnToContents( col );
1122void QgsDualView::autosizeAllColumns()
1124 mTableView->resizeColumnsToContents();
1127bool QgsDualView::modifySort()
1132 QgsAttributeTableConfig config = mConfig;
1135 orderByDlg.setWindowTitle( tr(
"Configure Attribute Table Sort Order" ) );
1136 QDialogButtonBox *dialogButtonBox =
new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel );
1137 QGridLayout *layout =
new QGridLayout();
1138 connect( dialogButtonBox, &QDialogButtonBox::accepted, &orderByDlg, &QDialog::accept );
1139 connect( dialogButtonBox, &QDialogButtonBox::rejected, &orderByDlg, &QDialog::reject );
1140 orderByDlg.setLayout( layout );
1142 QGroupBox *sortingGroupBox =
new QGroupBox();
1143 sortingGroupBox->setTitle( tr(
"Defined sort order in attribute table" ) );
1144 sortingGroupBox->setCheckable(
true );
1146 layout->addWidget( sortingGroupBox );
1147 sortingGroupBox->setLayout(
new QGridLayout() );
1149 QgsExpressionBuilderWidget *expressionBuilder =
new QgsExpressionBuilderWidget();
1152 expressionBuilder->
initWithLayer( mLayer, context, QStringLiteral(
"generic" ) );
1155 sortingGroupBox->layout()->addWidget( expressionBuilder );
1157 QCheckBox *cbxSortAscending =
new QCheckBox( tr(
"Sort ascending" ) );
1158 cbxSortAscending->setChecked( config.
sortOrder() == Qt::AscendingOrder );
1159 sortingGroupBox->layout()->addWidget( cbxSortAscending );
1161 layout->addWidget( dialogButtonBox );
1162 if ( orderByDlg.exec() )
1164 const Qt::SortOrder sortOrder = cbxSortAscending->isChecked() ? Qt::AscendingOrder : Qt::DescendingOrder;
1165 if ( sortingGroupBox->isChecked() )
1187 QSet<int> attributes;
1191 const QVector<QgsAttributeTableConfig::ColumnConfig> constColumnconfigs { config.
columns() };
1200 const QSet<int> colAttrs { attributes };
1201 for (
const int attrIdx : std::as_const( colAttrs ) )
1210 std::sort( attrs.begin(), attrs.end() );
1214void QgsDualView::zoomToCurrentFeature()
1216 const QModelIndex currentIndex = mTableView->currentIndex();
1217 if ( !currentIndex.isValid() )
1223 ids.insert( mFilterModel->rowToId( currentIndex ) );
1224 QgsMapCanvas *canvas = mFilterModel->mapCanvas();
1231void QgsDualView::panToCurrentFeature()
1233 const QModelIndex currentIndex = mTableView->currentIndex();
1234 if ( !currentIndex.isValid() )
1240 ids.insert( mFilterModel->rowToId( currentIndex ) );
1241 QgsMapCanvas *canvas = mFilterModel->mapCanvas();
1248void QgsDualView::flashCurrentFeature()
1250 const QModelIndex currentIndex = mTableView->currentIndex();
1251 if ( !currentIndex.isValid() )
1257 ids.insert( mFilterModel->rowToId( currentIndex ) );
1258 QgsMapCanvas *canvas = mFilterModel->mapCanvas();
1265void QgsDualView::rebuildFullLayerCache()
1270 mLayerCache->setFullCache(
true );
1273void QgsDualView::previewExpressionChanged(
const QString &expression )
1275 mLayer->setDisplayExpression( expression );
1278void QgsDualView::onSortColumnChanged()
1289void QgsDualView::updateSelectedFeatures()
1291 QgsFeatureRequest r = mMasterModel->request();
1296 mMasterModel->setRequest( r );
1297 mMasterModel->loadLayer();
1301void QgsDualView::updateEditedAddedFeatures()
1303 QgsFeatureRequest r = mMasterModel->request();
1308 mMasterModel->setRequest( r );
1309 mMasterModel->loadLayer();
1313void QgsDualView::extentChanged()
1315 QgsFeatureRequest r = mMasterModel->request();
1318 const QgsRectangle rect = mFilterModel->mapCanvas()->mapSettings().mapToLayerCoordinates( mLayer, mFilterModel->mapCanvas()->extent() );
1320 mMasterModel->setRequest( r );
1321 mMasterModel->loadLayer();
1326void QgsDualView::featureFormAttributeChanged(
const QString &attribute,
const QVariant &value,
bool attributeChanged )
1328 Q_UNUSED( attribute )
1330 if ( attributeChanged )
1332 mFeatureListView->setCurrentFeatureEdited(
true );
1333 mAttributeForm->save();
1344 mFilterModel->setFilterExpression( filterExpression, context );
1345 mFilterModel->filterFeatures();
1351 mMasterModel->setRequest( request );
1356 mTableView->setFeatureSelectionManager( featureSelectionManager );
1357 mFeatureListView->setFeatureSelectionManager( featureSelectionManager );
1359 if ( mFeatureSelectionManager && mFeatureSelectionManager->parent() ==
this )
1360 delete mFeatureSelectionManager;
1362 mFeatureSelectionManager = featureSelectionManager;
1368 mConfig.
update( mLayer->fields() );
1369 mLayer->setAttributeTableConfig( mConfig );
1370 mFilterModel->setAttributeTableConfig( mConfig );
1371 mTableView->setAttributeTableConfig( mConfig );
1377 mLayerCache->setCacheGeometry(
true );
1381 mMasterModel->setRequest( request );
1382 mLayerCache->setCacheSubsetOfAttributes( attributes );
1388 mFilterModel->sort( -1 );
1393 mConfig.setSortOrder( sortOrder );
1399 return mFilterModel->sortExpression();
1407void QgsDualView::progress(
int i,
bool &cancel )
1409 if ( !mProgressDlg )
1411 mProgressDlg =
new QProgressDialog( tr(
"Loading features…" ), tr(
"Abort" ), 0, 0,
this );
1412 mProgressDlg->setWindowTitle( tr(
"Attribute Table" ) );
1413 mProgressDlg->setWindowModality( Qt::WindowModal );
1414 mProgressDlg->show();
1417 mProgressDlg->setLabelText( tr(
"%L1 features loaded." ).arg( i ) );
1418 QCoreApplication::processEvents();
1420 cancel = mProgressDlg && mProgressDlg->wasCanceled();
1423void QgsDualView::finished()
1425 delete mProgressDlg;
1426 mProgressDlg =
nullptr;
1435 mDualView->masterModel()->executeAction( mAction, mFieldIdx );
1441 editedIds << mDualView->masterModel()->rowToId( mFieldIdx.row() );
1442 mDualView->setCurrentEditSelection( editedIds );
1453 mDualView->masterModel()->executeMapLayerAction( mAction, mFieldIdx, context );
@ SelectAtId
Fast access to features using their ID.
@ NoFilter
No filter is applied.
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
@ NoFilter
No spatial filtering of features.
@ MultipleFeatures
Action targets multiple features from a layer.
@ Layer
Action targets a complete layer.
@ SingleFeature
Action targets a single feature from a layer.
@ Expression
Field is calculated from an expression.
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
Contains context information for attribute editor widgets.
@ SearchMode
Form values are used for searching/filtering the layer.
@ SingleEditMode
Single edit mode, for editing a single feature.
@ MultiEditMode
Multi edit mode, for editing fields of multiple features at once.
const QgsAttributeEditorContext * parentContext() const
A container for configuration of the attribute table.
void setSortExpression(const QString &sortExpression)
Set the sort expression used for sorting.
@ Field
This column represents a field.
Qt::SortOrder sortOrder() const
Gets the sort order.
QVector< QgsAttributeTableConfig::ColumnConfig > columns() const
Gets the list with all columns and their configuration.
int mapVisibleColumnToIndex(int visibleColumn) const
Maps a visible column index to its original column index.
void update(const QgsFields &fields)
Update the configuration with the given fields.
void setSortOrder(Qt::SortOrder sortOrder)
Set the sort order.
int columnWidth(int column) const
Returns the width of a column, or -1 if column should use default width.
void setColumnHidden(int column, bool hidden)
Sets whether the specified column should be hidden.
QString sortExpression() const
Gets the expression used for sorting.
void setColumnWidth(int column, int width)
Sets the width of a column.
FilterMode
The filter mode defines how the rows should be filtered.
@ ShowFilteredList
Show only features whose ids are on the filter list. {.
@ ShowVisible
Show only visible features (depends on the map canvas).
@ ShowSelected
Show only selected features.
@ ShowInvalid
Show only features not respecting constraints.
@ ShowEdited
Show only features which have unsaved changes.
@ ShowAll
Show all features.
void filterError(const QString &errorMessage)
Emitted when an error occurred while filtering features.
void featuresFiltered()
Emitted when the filtering of the features has been done.
void visibleReloaded()
Emitted when the the visible features on extend are reloaded (the list is created).
void sortColumnChanged(int column, Qt::SortOrder order)
Emitted whenever the sort column is changed.
void fieldConditionalStyleChanged(const QString &fieldName)
Handles updating the model when the conditional style for a field changes.
void modelChanged()
Emitted when the model has been changed.
void progress(int i, bool &cancel)
void finished()
Emitted when the model has completely loaded all features.
void willShowContextMenu(QMenu *menu, const QModelIndex &atIndex)
Emitted in order to provide a hook to add additional* menu entries to the context menu.
void columnResized(int column, int width)
Emitted when a column in the view has been resized.
void showContextMenuExternally(QgsActionMenu *menu, QgsFeatureId fid)
Emitted when selecting context menu on the feature list to create the context menu individually.
void copyCellContent() const
Copy the content of the selected cell in the clipboard.
ViewMode
The view modes, in which this widget can present information.
@ AttributeEditor
Show a list of the features, where one can be chosen and the according attribute dialog will be prese...
void setFeatureSelectionManager(QgsIFeatureSelectionManager *featureSelectionManager)
Set the feature selection model.
static QgsAttributeList requiredAttributes(const QgsVectorLayer *layer)
Returns the list of required attributes according to the attribute table configuration of the layer,...
QgsAttributeTableFilterModel::FilterMode filterMode()
Gets the filter mode.
void setMultiEditEnabled(bool enabled)
Sets whether multi edit mode is enabled.
QgsFeatureIds filteredFeatures()
Gets a list of currently visible feature ids.
void cancelProgress()
Cancel the progress dialog (if any).
void filterChanged()
Emitted whenever the filter changes.
QgsDualView(QWidget *parent=nullptr)
Constructor.
void setAttributeTableConfig(const QgsAttributeTableConfig &config)
Set the attribute table config which should be used to control the appearance of the attribute table.
ViewMode view() const
Returns the current view mode.
int featureCount()
Returns the number of features on the layer.
Q_DECL_DEPRECATED void setFilteredFeatures(const QgsFeatureIds &filteredFeatures)
Set a list of currently visible features.
void formModeChanged(QgsAttributeEditorContext::Mode mode)
Emitted when the form changes mode.
FeatureListBrowsingAction
Action on the map canvas when browsing the list of features.
@ NoAction
No action is done.
@ PanToFeature
The map is panned to the center of the feature bounding-box.
@ ZoomToFeature
The map is zoomed to contained the feature bounding-box.
void hideEvent(QHideEvent *event) override
QgsAttributeTableConfig attributeTableConfig() const
The config used for the attribute table.
void filterExpressionSet(const QString &expression, QgsAttributeForm::FilterType type)
Emitted when a filter expression is set using the view.
void init(QgsVectorLayer *layer, QgsMapCanvas *mapCanvas, const QgsFeatureRequest &request=QgsFeatureRequest(), const QgsAttributeEditorContext &context=QgsAttributeEditorContext(), bool loadFeatures=true, bool showFirstFeature=true)
Has to be called to initialize the dual view.
bool saveEditChanges()
saveEditChanges
void openConditionalStyles()
void toggleSearchMode(bool enabled)
Toggles whether search mode should be enabled in the form.
void displayExpressionChanged(const QString &expression)
Emitted whenever the display expression is successfully changed.
void setSortExpression(const QString &sortExpression, Qt::SortOrder sortOrder=Qt::AscendingOrder)
Set the expression used for sorting the table and feature list.
void setRequest(const QgsFeatureRequest &request)
Set the request.
void parentFormValueChanged(const QString &attribute, const QVariant &value)
Called in embedded forms when an attribute value in the parent form has changed.
QgsAttributeTableModel * masterModel() const
Returns the model which has the information about all features (not only filtered).
void setCurrentEditSelection(const QgsFeatureIds &fids)
Set the current edit selection in the AttributeEditor mode.
int filteredFeatureCount()
Returns the number of features which are currently visible, according to the filter restrictions.
QString sortExpression() const
Gets the expression used for sorting the table and feature list.
void setFilterMode(QgsAttributeTableFilterModel::FilterMode filterMode)
Set the filter mode.
void setView(ViewMode view)
Change the current view mode.
void setSelectedOnTop(bool selectedOnTop)
Toggle the selectedOnTop flag.
void filterFeatures(const QgsExpression &filterExpression, const QgsExpressionContext &context)
Sets the expression and Updates the filtered features in the filter model.
A generic dialog for building expression strings.
static QList< QgsExpressionContextScope * > globalProjectLayerScopes(const QgsMapLayer *layer)
Creates a list of three scopes: global, layer's project and layer.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
Handles parsing and evaluation of expressions (formerly called "search strings").
bool needsGeometry() const
Returns true if the expression uses feature geometry for some computation.
QSet< int > referencedAttributeIndexes(const QgsFields &fields) const
Returns a list of field name indexes obtained from the provided fields.
@ FeatureRole
Feature with all attributes and no geometry.
Shows a list of features and renders an edit button next to each feature.
void currentEditSelectionProgressChanged(int progress, int count)
Emitted whenever the current edit selection has been changed.
void editNextFeature()
editNextFeature will try to edit next feature of the list
void editLastFeature()
editLastFeature will try to edit the last feature of the list
void editFirstFeature()
editFirstFeature will try to edit the first feature of the list
void displayExpressionChanged(const QString &expression)
Emitted whenever the display expression is successfully changed.
void editPreviousFeature()
editPreviousFeature will try to edit previous feature of the list
void willShowContextMenu(QgsActionMenu *menu, const QModelIndex &atIndex)
Emitted when the context menu is created to add the specific actions to it.
void currentEditSelectionChanged(QgsFeature &feat)
Emitted whenever the current edit selection has been changed.
void aboutToChangeEditSelection(bool &ok)
Wraps a request for features to a vector layer (or directly its vector data provider).
QgsFeatureRequest & setFlags(Qgis::FeatureRequestFlags flags)
Sets flags that affect how features will be fetched.
QgsRectangle filterRect() const
Returns the rectangle from which features will be taken.
QgsFeatureRequest & setFilterFids(const QgsFeatureIds &fids)
Sets the feature IDs that should be fetched.
Qgis::FeatureRequestFilterType filterType() const
Returns the attribute/ID filter type which is currently set on this request.
Qgis::FeatureRequestFlags flags() const
Returns the flags which affect how features are fetched.
QgsFeatureRequest & disableFilter()
Disables any attribute/ID filtering.
QgsFeatureRequest & setSubsetOfAttributes(const QgsAttributeList &attrs)
Set a subset of attributes that will be fetched.
QgsFeatureRequest & setFilterExpression(const QString &expression)
Set the filter expression.
Qgis::SpatialFilterType spatialFilterType() const
Returns the spatial filter type which is currently set on this request.
QgsFeatureRequest & setFilterRect(const QgsRectangle &rectangle)
Sets the rectangle from which features will be taken.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Qgis::FieldOrigin fieldOrigin(int fieldIdx) const
Returns the field's origin (value from an enumeration).
Q_INVOKABLE int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
static QgsEditorWidgetRegistry * editorWidgetRegistry()
Returns the global editor widget registry, used for managing all known edit widget factories.
static QgsShortcutsManager * shortcutsManager()
Returns the global shortcuts manager, used for managing a QAction and QShortcut sequences.
static QgsMapLayerActionRegistry * mapLayerActionRegistry()
Returns the global map layer action registry, used for registering map layer actions.
Is an interface class to abstract feature selection handling.
static long zoomToMatchingFeatures(QgsMapCanvas *canvas, QgsVectorLayer *layer, const QString &filter)
Zooms a map canvas to features from the specified layer which match the given filter expression strin...
static long flashMatchingFeatures(QgsMapCanvas *canvas, QgsVectorLayer *layer, const QString &filter)
Flashes features from the specified layer which match the given filter expression string with a map c...
Map canvas is a class for displaying all GIS data types on a canvas.
void extentsChanged()
Emitted when the extents of the map change.
void panToFeatureIds(QgsVectorLayer *layer, const QgsFeatureIds &ids, bool alwaysRecenter=true)
Centers canvas extent to feature ids.
void flashFeatureIds(QgsVectorLayer *layer, const QgsFeatureIds &ids, const QColor &startColor=QColor(255, 0, 0, 255), const QColor &endColor=QColor(255, 0, 0, 0), int flashes=3, int duration=500)
Causes a set of features with matching ids from a vector layer to flash within the canvas.
void zoomToFeatureIds(QgsVectorLayer *layer, const QgsFeatureIds &ids)
Set canvas extent to the bounding box of a set of features.
Encapsulates the context in which a QgsMapLayerAction action is executed.
QList< QgsMapLayerAction * > mapLayerActions(QgsMapLayer *layer, Qgis::MapLayerActionTargets targets=Qgis::MapLayerActionTarget::AllActions, const QgsMapLayerActionContext &context=QgsMapLayerActionContext())
Returns the map layer actions which can run on the specified layer.
void layerModified()
Emitted when modifications has been done on layer.
A rectangle specified with double values.
static QgsSettingsTreeNode * sTreeWindowState
Stores settings for use within QGIS.
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
T enumValue(const QString &key, const T &defaultValue, const Section section=NoSection)
Returns the setting value for a setting based on an enum.
QShortcut * shortcutByName(const QString &name) const
Returns a shortcut by its name, or nullptr if nothing found.
static QVariant createNullVariant(QMetaType::Type metaType)
Helper method to properly create a null QVariant from a metaType Returns the created QVariant.
Caches features for a given QgsVectorLayer.
void finished()
When filling the cache, this signal gets emitted once the cache is fully initialized.
void invalidated()
The cache has been invalidated and cleared.
void setCacheSubsetOfAttributes(const QgsAttributeList &attributes)
Set the list (possibly a subset) of attributes to be cached.
void progress(int i, bool &cancel)
When filling the cache, this signal gets emitted periodically to notify about the progress and to be ...
void setCacheGeometry(bool cacheGeometry)
Enable or disable the caching of geometries.
Represents a vector layer which manages a vector based dataset.
bool isEditable() const final
Returns true if the provider is in editing mode.
bool isSpatial() const final
Returns true if this is a geometry layer and false in case of NoGeometry (table only) or UnknownGeome...
QString expressionField(int index) const
Returns the expression used for a given expression field.
void afterCommitChanges()
Emitted after changes are committed to the data provider.
QgsAttributeTableConfig attributeTableConfig() const
Returns the attribute table configuration object.
void selectionChanged(const QgsFeatureIds &selected, const QgsFeatureIds &deselected, bool clearAndSelect)
Emitted when selection was changed.
void updatedFields()
Emitted whenever the fields available from this layer have been changed.
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
QSet< QgsFeatureId > QgsFeatureIds
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
QList< int > QgsAttributeList
Defines the configuration of a column in the attribute table.