44#include <QInputDialog>
47#include <QProgressDialog>
52#include "moc_qgsdualview.cpp"
54using namespace Qt::StringLiterals;
56const std::unique_ptr<QgsSettingsEntryVariant> QgsDualView::conditionalFormattingSplitterState = std::make_unique<
58const std::unique_ptr<QgsSettingsEntryVariant> QgsDualView::attributeEditorSplitterState = std::make_unique<
62 : QStackedWidget( parent )
71 mTableView->horizontalHeader()->setContextMenuPolicy( Qt::CustomContextMenu );
72 connect( mTableView->horizontalHeader(), &QHeaderView::customContextMenuRequested,
this, &QgsDualView::showViewHeaderMenu );
75 mConditionalFormatWidgetStack->hide();
77 mConditionalFormatWidgetStack->setMainPanel( mConditionalFormatWidget );
78 mConditionalFormatWidget->setDockMode(
true );
82 conditionalFormattingSplitterState->copyValueFromKey( u
"/qgis/attributeTable/splitterState"_s,
true );
83 mConditionalSplitter->restoreState( conditionalFormattingSplitterState->value().toByteArray() );
84 mAttributeEditorViewSplitter->restoreState( attributeEditorSplitterState->value().toByteArray() );
86 mPreviewColumnsMenu =
new QMenu(
this );
87 mActionPreviewColumnsMenu->setMenu( mPreviewColumnsMenu );
93 connect( mActionExpressionPreview, &QAction::triggered,
this, &QgsDualView::previewExpressionBuilder );
102 auto createShortcuts = [
this](
const QString &objectName, void (
QgsFeatureListView::*slot )() ) {
107 connect( sc, &QShortcut::activated, mFeatureListView, slot );
114 QButtonGroup *buttonGroup =
new QButtonGroup(
this );
115 buttonGroup->setExclusive(
false );
119 QAbstractButton *bt = buttonGroup->button(
static_cast<int>( action ) );
121 bt->setChecked(
true );
122 connect( buttonGroup, qOverload<QAbstractButton *, bool>( &QButtonGroup::buttonToggled ),
this, &QgsDualView::panZoomGroupButtonToggled );
123 mFlashButton->setChecked(
QgsSettings().value( u
"/qgis/attributeTable/featureListHighlightFeature"_s,
true ).toBool() );
124 connect( mFlashButton, &QToolButton::clicked,
this, &QgsDualView::flashButtonClicked );
135 delete mAttributeForm;
136 mAttributeForm =
nullptr;
143 mEditorContext = context;
150 const bool needsGeometry = mLayer->conditionalStyles()->rulesNeedGeometry()
156 initLayerCache( needsGeometry );
157 initModels( mapCanvas, request, loadFeatures );
159 mConditionalFormatWidget->setLayer( mLayer );
161 mTableView->setModel( mFilterModel );
162 mFeatureListView->setModel( mFeatureListModel );
167 if ( mFeatureListPreviewButton->defaultAction() )
169 mFeatureListView->setDisplayExpression( mDisplayExpression );
179 if ( showFirstFeature && mFeatureListModel->rowCount() > 0 )
190void QgsDualView::initAttributeForm(
const QgsFeature &feature )
192 Q_ASSERT( !mAttributeForm );
198 mAttributeEditorScrollArea->setWidgetResizable(
true );
199 mAttributeEditor->layout()->addWidget( mAttributeEditorScrollArea );
200 mAttributeEditorScrollArea->setWidget( mAttributeForm );
204 mAttributeEditor->layout()->addWidget( mAttributeForm );
209 mAttributeForm->setMinimumWidth( 200 );
217 if ( QgsMapCanvas *canvas = mFilterModel->mapCanvas() )
223 if ( QgsMapCanvas *canvas = mFilterModel->mapCanvas() )
232void QgsDualView::columnBoxInit()
235 const QList<QgsField> fields = mLayer->fields().toList();
236 const QString displayExpression = mLayer->displayExpression();
238 mFeatureListPreviewButton->addAction( mActionExpressionPreview );
239 mFeatureListPreviewButton->addAction( mActionPreviewColumnsMenu );
241 QAction *defaultFieldAction =
nullptr;
242 const auto constFields = fields;
243 for (
const QgsField &field : constFields )
245 const int fieldIndex = mLayer->fields().lookupField( field.name() );
246 if ( fieldIndex == -1 )
249 const QString fieldName = field.name();
252 const QIcon icon = mLayer->fields().iconForField( fieldIndex );
253 const QString text = mLayer->attributeDisplayName( fieldIndex );
256 QAction *previewAction =
new QAction( icon, text, mFeatureListPreviewButton );
257 connect( previewAction, &QAction::triggered,
this, [
this, previewAction, fieldName] { previewColumnChanged( previewAction, fieldName ); } );
258 mPreviewColumnsMenu->addAction( previewAction );
260 if ( text == displayExpression || u
"COALESCE( \"%1\", '<NULL>' )"_s.arg( text ) == displayExpression || u
"\"%1\""_s.arg( text ) == displayExpression )
262 defaultFieldAction = previewAction;
267 QMenu *sortMenu =
new QMenu(
this );
269 sortMenuAction->setMenu( sortMenu );
271 QAction *sortByPreviewExpressionAsc =
new QAction(
QgsApplication::getThemeIcon( u
"sort.svg"_s ), tr(
"By Display Name (Ascending)" ),
this );
272 connect( sortByPreviewExpressionAsc, &QAction::triggered,
this, [
this]() { mFeatureListModel->setSortByDisplayExpression(
true, Qt::AscendingOrder ); } );
273 sortMenu->addAction( sortByPreviewExpressionAsc );
274 QAction *sortByPreviewExpressionDesc =
new QAction(
QgsApplication::getThemeIcon( u
"sort-reverse.svg"_s ), tr(
"By Display Name (Descending)" ),
this );
275 connect( sortByPreviewExpressionDesc, &QAction::triggered,
this, [
this]() { mFeatureListModel->setSortByDisplayExpression(
true, Qt::DescendingOrder ); } );
276 sortMenu->addAction( sortByPreviewExpressionDesc );
277 QAction *sortByPreviewExpressionCustom =
new QAction(
QgsApplication::getThemeIcon( u
"mIconExpressionPreview.svg"_s ), tr(
"By Custom Expression" ),
this );
278 connect( sortByPreviewExpressionCustom, &QAction::triggered,
this, [
this]() {
280 mFeatureListModel->setSortByDisplayExpression(
false );
282 sortMenu->addAction( sortByPreviewExpressionCustom );
284 mFeatureListPreviewButton->addAction( sortMenuAction );
286 QAction *separator =
new QAction( mFeatureListPreviewButton );
287 separator->setSeparator(
true );
288 mFeatureListPreviewButton->addAction( separator );
289 restoreRecentDisplayExpressions();
291 if ( defaultFieldAction )
293 mFeatureListPreviewButton->setDefaultAction( defaultFieldAction );
294 mFeatureListPreviewButton->defaultAction()->trigger();
298 mActionExpressionPreview->setText( displayExpression );
299 mFeatureListPreviewButton->setDefaultAction( mActionExpressionPreview );
301 mFeatureListView->setDisplayExpression( displayExpression );
302 setDisplayExpression( displayExpression.isEmpty() ? tr(
"'[Please define preview text]'" ) : displayExpression );
308 setCurrentIndex(
view );
319 switch ( mFilterModel->filterMode() )
343 mMasterModel->setShowValidityState(
false );
353 const bool requiresTableReload = ( request.
filterType() != Qgis::Qgis::FeatureRequestFilterType::NoFilter
356 || ( mMasterModel->rowCount() == 0 );
368 if ( mFilterModel->mapCanvas() )
370 const QgsRectangle rect = mFilterModel->mapCanvas()->mapSettings().mapToLayerCoordinates( mLayer, mFilterModel->mapCanvas()->extent() );
385 mMasterModel->setShowValidityState(
true );
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( u
"qgis/attributeTableRowCache"_s,
"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( u
"dualview/previewExpressions"_s ).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( u
"dualview/previewExpressions"_s, 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(
"COALESCE( \""_L1 ) && expression.endsWith(
", '<NULL>' )"_L1 ) )
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( u
"%1 / %2"_s.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]() { canvas->
panToFeatureIds( mLayer, featureset,
false ); } );
609 else if ( mAutoZoomButton->isChecked() )
610 QTimer::singleShot( 0,
this, [
this, featureset, canvas]() { canvas->
zoomToFeatureIds( mLayer, featureset ); } );
612 if ( mFlashButton->isChecked() )
613 QTimer::singleShot( 0,
this, [
this, featureset, canvas]() { canvas->
flashFeatureIds( mLayer, featureset ); } );
614 mLastFeatureSet = featureset;
618void QgsDualView::setBrowsingAutoPanScaleAllowed(
bool allowed )
620 if ( mBrowsingAutoPanScaleAllowed == allowed )
623 mBrowsingAutoPanScaleAllowed = allowed;
625 mAutoPanButton->setEnabled( allowed );
626 mAutoZoomButton->setEnabled( allowed );
628 const QString disabledHint = tr(
"(disabled when attribute table only shows features visible in the current map canvas extent)" );
630 mAutoPanButton->setToolTip( tr(
"Automatically pan to the current feature" ) + ( allowed ? QString() : QString(
' ' ) + disabledHint ) );
631 mAutoZoomButton->setToolTip( tr(
"Automatically zoom to the current feature" ) + ( allowed ? QString() : QString(
' ' ) + disabledHint ) );
634void QgsDualView::panZoomGroupButtonToggled( QAbstractButton *button,
bool checked )
636 if ( button == mAutoPanButton && checked )
638 QgsSettings().setEnumValue( u
"/qgis/attributeTable/featureListBrowsingAction"_s,
PanToFeature );
639 mAutoZoomButton->setChecked(
false );
641 else if ( button == mAutoZoomButton && checked )
643 QgsSettings().setEnumValue( u
"/qgis/attributeTable/featureListBrowsingAction"_s,
ZoomToFeature );
644 mAutoPanButton->setChecked(
false );
648 QgsSettings().setEnumValue( u
"/qgis/attributeTable/featureListBrowsingAction"_s,
NoAction );
651 if ( checked && mLayer->isSpatial() )
652 panOrZoomToFeature( mFeatureListView->currentEditSelection() );
655void QgsDualView::flashButtonClicked(
bool clicked )
657 QgsSettings().setValue( u
"/qgis/attributeTable/featureListHighlightFeature"_s, clicked );
661 QgsMapCanvas *canvas = mFilterModel->mapCanvas();
664 canvas->
flashFeatureIds( mLayer, mFeatureListView->currentEditSelection() );
667void QgsDualView::filterError(
const QString &errorMessage )
669 if ( mEditorContext.mainMessageBar() )
671 mEditorContext.mainMessageBar()->pushWarning( tr(
"An error occurred while filtering features" ), errorMessage );
675void QgsDualView::featureListAboutToChangeEditSelection(
bool &ok )
677 if ( !mAttributeForm )
680 if ( mLayer->isEditable() && !mAttributeForm->save() )
684void QgsDualView::featureListCurrentEditSelectionChanged(
const QgsFeature &feat )
686 if ( !mAttributeForm )
688 initAttributeForm( feat );
690 else if ( !mLayer->isEditable() || mAttributeForm->save() )
692 mAttributeForm->setFeature( feat );
694 featureset << feat.
id();
697 if ( mLayer->isSpatial() )
698 panOrZoomToFeature( featureset );
708 mFeatureListView->setCurrentFeatureEdited(
false );
709 mFeatureListView->setEditSelection( fids );
714 return mAttributeForm ? mAttributeForm->save() :
false;
719 mConditionalFormatWidgetStack->setVisible( !mConditionalFormatWidgetStack->isVisible() );
724 if ( !mAttributeForm )
729 mPreviousView =
view();
742 if ( !mAttributeForm )
749 mAttributeForm->setVisible(
true );
754 mAttributeForm->setVisible( mFilterModel->rowCount() > 0 );
758void QgsDualView::previewExpressionBuilder()
764 dlg.setWindowTitle( tr(
"Expression Based Preview" ) );
765 dlg.setExpressionText( mFeatureListView->displayExpression() );
767 if ( dlg.exec() == QDialog::Accepted )
769 mFeatureListView->setDisplayExpression( dlg.expressionText() );
770 mActionExpressionPreview->setText( dlg.expressionText() );
772 mFeatureListPreviewButton->setDefaultAction( mActionExpressionPreview );
773 mFeatureListPreviewButton->setPopupMode( QToolButton::MenuButtonPopup );
776 setDisplayExpression( mFeatureListView->displayExpression() );
779void QgsDualView::previewColumnChanged( QAction *previewAction,
const QString &expression )
781 if ( !mFeatureListView->setDisplayExpression( u
"COALESCE( \"%1\", '<NULL>' )"_s.arg( expression ) ) )
783 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() ) );
787 mActionExpressionPreview->setText( tr(
"Expression" ) );
788 mFeatureListPreviewButton->setText( previewAction->text() );
789 mFeatureListPreviewButton->setIcon( previewAction->icon() );
790 mFeatureListPreviewButton->setPopupMode( QToolButton::InstantPopup );
793 setDisplayExpression( mFeatureListView->displayExpression() );
798 return mMasterModel->rowCount();
803 return mFilterModel->rowCount();
808 const QModelIndex currentIndex = mTableView->currentIndex();
809 if ( !currentIndex.isValid() )
814 const QVariant var = mMasterModel->data( currentIndex, Qt::DisplayRole );
815 QApplication::clipboard()->setText( var.toString() );
821 mProgressDlg->cancel();
826 if ( mAttributeForm )
828 mAttributeForm->parentFormValueChanged( attribute, newValue );
835 saveRecentDisplayExpressions();
842 conditionalFormattingSplitterState->setValue( mConditionalSplitter->saveState() );
843 attributeEditorSplitterState->setValue( mAttributeEditorViewSplitter->saveState() );
846void QgsDualView::viewWillShowContextMenu( QMenu *menu,
const QModelIndex &masterIndex )
853 const QVariant displayValue = mMasterModel->data( masterIndex, Qt::DisplayRole );
855 if ( displayValue.isValid() )
858 QString previewDisplayValue = displayValue.toString();
859 if ( previewDisplayValue.length() > 12 )
861 previewDisplayValue.truncate( 12 );
862 previewDisplayValue.append( u
"…"_s );
866 QAction *copyContentAction = menu->addAction( tr(
"Copy Cell Content (%1)" ).arg( previewDisplayValue ) );
867 menu->addAction( copyContentAction );
868 connect( copyContentAction, &QAction::triggered,
this, [displayValue] { QApplication::clipboard()->setText( displayValue.toString() ); } );
871 const QVariant rawValue = mMasterModel->data( masterIndex, Qt::EditRole );
872 if ( rawValue.isValid() )
875 QString previewRawValue = rawValue.toString();
876 if ( previewRawValue.length() > 12 )
878 previewRawValue.truncate( 12 );
879 previewRawValue.append( u
"…"_s );
882 QAction *copyRawContentAction = menu->addAction( tr(
"Copy Raw Value (%1)" ).arg( previewRawValue ) );
883 menu->addAction( copyRawContentAction );
884 connect( copyRawContentAction, &QAction::triggered,
this, [rawValue] { QApplication::clipboard()->setText( rawValue.toString() ); } );
887 QgsVectorLayer *vl = mFilterModel->layer();
888 QgsMapCanvas *canvas = mFilterModel->mapCanvas();
891 QAction *zoomToFeatureAction = menu->addAction( tr(
"Zoom to Feature" ) );
892 connect( zoomToFeatureAction, &QAction::triggered,
this, &QgsDualView::zoomToCurrentFeature );
894 QAction *panToFeatureAction = menu->addAction( tr(
"Pan to Feature" ) );
895 connect( panToFeatureAction, &QAction::triggered,
this, &QgsDualView::panToCurrentFeature );
897 QAction *flashFeatureAction = menu->addAction( tr(
"Flash Feature" ) );
898 connect( flashFeatureAction, &QAction::triggered,
this, &QgsDualView::flashCurrentFeature );
902 const QList<QgsAction> actions = mLayer->actions()->actions( u
"Field"_s );
903 if ( !actions.isEmpty() )
905 QAction *a = menu->addAction( tr(
"Run Layer Action" ) );
906 a->setEnabled(
false );
908 for (
const QgsAction &action : actions )
910 if ( !action.runable() )
913 if ( vl && !vl->
isEditable() && action.isEnabledOnlyWhenEditable() )
916 QgsAttributeTableAction *a =
new QgsAttributeTableAction( action.name(),
this, action.id(), masterIndex );
920 const QModelIndex rowSourceIndex = mMasterModel->index( masterIndex.row(), 0 );
921 if ( !rowSourceIndex.isValid() )
927 QgsMapLayerActionContext context;
929 if ( !registeredActions.isEmpty() )
932 menu->addSeparator();
934 for ( QgsMapLayerAction *action : registeredActions )
936 QgsAttributeTableMapLayerAction *a =
new QgsAttributeTableMapLayerAction( action->text(),
this, action, rowSourceIndex );
943 const QgsFeatureId currentFid = mMasterModel->rowToId( masterIndex.row() );
944 if ( mLayer->selectedFeatureCount() > 1 && mLayer->selectedFeatureIds().contains( currentFid ) )
947 if ( !registeredActions.isEmpty() )
949 menu->addSeparator();
950 QAction *action = menu->addAction( tr(
"Actions on Selection (%1)" ).arg( mLayer->selectedFeatureCount() ) );
951 action->setEnabled(
false );
953 QgsMapLayerActionContext context;
954 for ( QgsMapLayerAction *action : registeredActions )
956 menu->addAction( action->text(), action, [
this, action, context]() {
957 Q_NOWARN_DEPRECATED_PUSH
958 action->triggerForFeatures( mLayer, mLayer->selectedFeatures() );
959 Q_NOWARN_DEPRECATED_POP
960 action->triggerForFeatures( mLayer, mLayer->selectedFeatures(), context );
966 menu->addSeparator();
967 QgsAttributeTableAction *a =
new QgsAttributeTableAction( tr(
"Open Form" ),
this, QUuid(), rowSourceIndex );
972void QgsDualView::widgetWillShowContextMenu(
QgsActionMenu *menu,
const QModelIndex &atIndex )
978void QgsDualView::showViewHeaderMenu( QPoint point )
980 const int col = mTableView->columnAt( point.x() );
982 delete mHorizontalHeaderMenu;
983 mHorizontalHeaderMenu =
new QMenu(
this );
985 QAction *hide =
new QAction( tr(
"&Hide Column" ), mHorizontalHeaderMenu );
986 connect( hide, &QAction::triggered,
this, &QgsDualView::hideColumn );
987 hide->setData( col );
988 mHorizontalHeaderMenu->addAction( hide );
989 QAction *setWidth =
new QAction( tr(
"&Set Width…" ), mHorizontalHeaderMenu );
990 connect( setWidth, &QAction::triggered,
this, &QgsDualView::resizeColumn );
991 setWidth->setData( col );
992 mHorizontalHeaderMenu->addAction( setWidth );
994 QAction *setWidthAllColumns =
new QAction( tr(
"&Set All Column Widths…" ), mHorizontalHeaderMenu );
995 connect( setWidthAllColumns, &QAction::triggered,
this, &QgsDualView::resizeAllColumns );
996 setWidthAllColumns->setData( col );
997 mHorizontalHeaderMenu->addAction( setWidthAllColumns );
999 QAction *optimizeWidth =
new QAction( tr(
"&Autosize" ), mHorizontalHeaderMenu );
1000 connect( optimizeWidth, &QAction::triggered,
this, &QgsDualView::autosizeColumn );
1001 optimizeWidth->setData( col );
1002 mHorizontalHeaderMenu->addAction( optimizeWidth );
1004 QAction *optimizeWidthAllColumns =
new QAction( tr(
"&Autosize All Columns" ), mHorizontalHeaderMenu );
1005 connect( optimizeWidthAllColumns, &QAction::triggered,
this, &QgsDualView::autosizeAllColumns );
1006 mHorizontalHeaderMenu->addAction( optimizeWidthAllColumns );
1009 mHorizontalHeaderMenu->addSeparator();
1010 QAction *organize =
new QAction( tr(
"&Organize Columns…" ), mHorizontalHeaderMenu );
1011 connect( organize, &QAction::triggered,
this, &QgsDualView::organizeColumns );
1012 mHorizontalHeaderMenu->addAction( organize );
1013 QAction *sort =
new QAction( tr(
"&Sort…" ), mHorizontalHeaderMenu );
1014 connect( sort, &QAction::triggered,
this, [
this]() { modifySort(); } );
1015 mHorizontalHeaderMenu->addAction( sort );
1017 mHorizontalHeaderMenu->popup( mTableView->horizontalHeader()->mapToGlobal( point ) );
1020void QgsDualView::organizeColumns()
1028 if ( dialog.exec() == QDialog::Accepted )
1030 const QgsAttributeTableConfig config = dialog.config();
1035void QgsDualView::tableColumnResized(
int column,
int width )
1037 QgsAttributeTableConfig config = mConfig;
1039 if ( sourceCol >= 0 && config.
columnWidth( sourceCol ) != width )
1046void QgsDualView::hideColumn()
1048 QAction *action = qobject_cast<QAction *>( sender() );
1049 const int col = action->data().toInt();
1050 QgsAttributeTableConfig config = mConfig;
1052 if ( sourceCol >= 0 )
1059void QgsDualView::resizeColumn()
1061 QAction *action = qobject_cast<QAction *>( sender() );
1062 const int col = action->data().toInt();
1066 QgsAttributeTableConfig config = mConfig;
1068 if ( sourceCol >= 0 )
1071 const int width = QInputDialog::getInt(
this, tr(
"Set column width" ), tr(
"Enter column width" ), mTableView->columnWidth( col ), 0, 1000, 10, &ok );
1080void QgsDualView::resizeAllColumns()
1082 QAction *action = qobject_cast<QAction *>( sender() );
1083 const int col = action->data().toInt();
1087 QgsAttributeTableConfig config = mConfig;
1090 const int width = QInputDialog::getInt(
this, tr(
"Set Column Width" ), tr(
"Enter column width" ), mTableView->columnWidth( col ), 1, 1000, 10, &ok );
1093 const int colCount = mTableView->model()->columnCount();
1096 for (
int i = 0; i < colCount; i++ )
1105void QgsDualView::autosizeColumn()
1107 QAction *action = qobject_cast<QAction *>( sender() );
1108 const int col = action->data().toInt();
1109 mTableView->resizeColumnToContents( col );
1112void QgsDualView::autosizeAllColumns()
1114 mTableView->resizeColumnsToContents();
1117bool QgsDualView::modifySort()
1122 QgsAttributeTableConfig config = mConfig;
1125 orderByDlg.setWindowTitle( tr(
"Configure Attribute Table Sort Order" ) );
1126 QDialogButtonBox *dialogButtonBox =
new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel );
1127 QGridLayout *layout =
new QGridLayout();
1128 connect( dialogButtonBox, &QDialogButtonBox::accepted, &orderByDlg, &QDialog::accept );
1129 connect( dialogButtonBox, &QDialogButtonBox::rejected, &orderByDlg, &QDialog::reject );
1130 orderByDlg.setLayout( layout );
1132 QGroupBox *sortingGroupBox =
new QGroupBox();
1133 sortingGroupBox->setTitle( tr(
"Defined sort order in attribute table" ) );
1134 sortingGroupBox->setCheckable(
true );
1136 layout->addWidget( sortingGroupBox );
1137 sortingGroupBox->setLayout(
new QGridLayout() );
1139 QgsExpressionBuilderWidget *expressionBuilder =
new QgsExpressionBuilderWidget();
1142 expressionBuilder->
initWithLayer( mLayer, context, u
"generic"_s );
1145 sortingGroupBox->layout()->addWidget( expressionBuilder );
1147 QCheckBox *cbxSortAscending =
new QCheckBox( tr(
"Sort ascending" ) );
1148 cbxSortAscending->setChecked( config.
sortOrder() == Qt::AscendingOrder );
1149 sortingGroupBox->layout()->addWidget( cbxSortAscending );
1151 layout->addWidget( dialogButtonBox );
1152 if ( orderByDlg.exec() )
1154 const Qt::SortOrder sortOrder = cbxSortAscending->isChecked() ? Qt::AscendingOrder : Qt::DescendingOrder;
1155 if ( sortingGroupBox->isChecked() )
1177 QSet<int> attributes;
1181 const QVector<QgsAttributeTableConfig::ColumnConfig> constColumnconfigs { config.
columns() };
1190 const QSet<int> colAttrs { attributes };
1191 for (
const int attrIdx : std::as_const( colAttrs ) )
1200 std::sort( attrs.begin(), attrs.end() );
1204void QgsDualView::zoomToCurrentFeature()
1206 const QModelIndex currentIndex = mTableView->currentIndex();
1207 if ( !currentIndex.isValid() )
1213 ids.insert( mFilterModel->rowToId( currentIndex ) );
1214 QgsMapCanvas *canvas = mFilterModel->mapCanvas();
1221void QgsDualView::panToCurrentFeature()
1223 const QModelIndex currentIndex = mTableView->currentIndex();
1224 if ( !currentIndex.isValid() )
1230 ids.insert( mFilterModel->rowToId( currentIndex ) );
1231 QgsMapCanvas *canvas = mFilterModel->mapCanvas();
1238void QgsDualView::flashCurrentFeature()
1240 const QModelIndex currentIndex = mTableView->currentIndex();
1241 if ( !currentIndex.isValid() )
1247 ids.insert( mFilterModel->rowToId( currentIndex ) );
1248 QgsMapCanvas *canvas = mFilterModel->mapCanvas();
1255void QgsDualView::rebuildFullLayerCache()
1260 mLayerCache->setFullCache(
true );
1263void QgsDualView::previewExpressionChanged(
const QString &expression )
1265 mLayer->setDisplayExpression( expression );
1268void QgsDualView::onSortColumnChanged()
1279void QgsDualView::updateSelectedFeatures()
1281 QgsFeatureRequest r = mMasterModel->request();
1286 mMasterModel->setRequest( r );
1287 mMasterModel->loadLayer();
1291void QgsDualView::updateEditedAddedFeatures()
1293 QgsFeatureRequest r = mMasterModel->request();
1298 mMasterModel->setRequest( r );
1299 mMasterModel->loadLayer();
1303void QgsDualView::extentChanged()
1305 QgsFeatureRequest r = mMasterModel->request();
1308 const QgsRectangle rect = mFilterModel->mapCanvas()->mapSettings().mapToLayerCoordinates( mLayer, mFilterModel->mapCanvas()->extent() );
1310 mMasterModel->setRequest( r );
1311 mMasterModel->loadLayer();
1316void QgsDualView::featureFormAttributeChanged(
const QString &attribute,
const QVariant &value,
bool attributeChanged )
1318 Q_UNUSED( attribute )
1320 if ( attributeChanged )
1322 mFeatureListView->setCurrentFeatureEdited(
true );
1323 mAttributeForm->save();
1334 mFilterModel->setFilterExpression( filterExpression, context );
1335 mFilterModel->filterFeatures();
1341 mMasterModel->setRequest( request );
1346 mTableView->setFeatureSelectionManager( featureSelectionManager );
1347 mFeatureListView->setFeatureSelectionManager( featureSelectionManager );
1349 if ( mFeatureSelectionManager && mFeatureSelectionManager->parent() ==
this )
1350 delete mFeatureSelectionManager;
1352 mFeatureSelectionManager = featureSelectionManager;
1358 mConfig.
update( mLayer->fields() );
1359 mLayer->setAttributeTableConfig( mConfig );
1360 mFilterModel->setAttributeTableConfig( mConfig );
1361 mTableView->setAttributeTableConfig( mConfig );
1367 mLayerCache->setCacheGeometry(
true );
1371 mMasterModel->setRequest( request );
1372 mLayerCache->setCacheSubsetOfAttributes( attributes );
1378 mFilterModel->sort( -1 );
1383 mConfig.setSortOrder( sortOrder );
1389 return mFilterModel->sortExpression();
1397void QgsDualView::progress(
int i,
bool &cancel )
1399 if ( !mProgressDlg )
1401 mProgressDlg =
new QProgressDialog( tr(
"Loading features…" ), tr(
"Abort" ), 0, 0,
this );
1402 mProgressDlg->setWindowTitle( tr(
"Attribute Table" ) );
1403 mProgressDlg->setWindowModality( Qt::WindowModal );
1404 mProgressDlg->show();
1407 mProgressDlg->setLabelText( tr(
"%L1 features loaded." ).arg( i ) );
1408 QCoreApplication::processEvents();
1410 cancel = mProgressDlg && mProgressDlg->wasCanceled();
1413void QgsDualView::finished()
1415 delete mProgressDlg;
1416 mProgressDlg =
nullptr;
1425 mDualView->masterModel()->executeAction( mAction, mFieldIdx );
1431 editedIds << mDualView->masterModel()->rowToId( mFieldIdx.row() );
1432 mDualView->setCurrentEditSelection( editedIds );
1443 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.
A variant settings entry.
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.