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<QgsSettingsEntryVariant>( u
"attribute-table-splitter-state"_s,
QgsSettingsTree::sTreeWindowState,
QgsVariantUtils::createNullVariant( QMetaType::Type::QByteArray ), u
"State of conditional formatting splitter's layout so it could be restored when opening attribute table view."_s );
57const std::unique_ptr<QgsSettingsEntryVariant> QgsDualView::attributeEditorSplitterState = std::make_unique<QgsSettingsEntryVariant>( u
"attribute-editor-splitter-state"_s,
QgsSettingsTree::sTreeWindowState,
QgsVariantUtils::createNullVariant( QMetaType::Type::QByteArray ), u
"State of attribute editor splitter's layout so it could be restored when opening attribute editor view."_s );
60 : QStackedWidget( parent )
69 mTableView->horizontalHeader()->setContextMenuPolicy( Qt::CustomContextMenu );
70 connect( mTableView->horizontalHeader(), &QHeaderView::customContextMenuRequested,
this, &QgsDualView::showViewHeaderMenu );
73 mConditionalFormatWidgetStack->hide();
75 mConditionalFormatWidgetStack->setMainPanel( mConditionalFormatWidget );
76 mConditionalFormatWidget->setDockMode(
true );
80 conditionalFormattingSplitterState->copyValueFromKey( u
"/qgis/attributeTable/splitterState"_s,
true );
81 mConditionalSplitter->restoreState( conditionalFormattingSplitterState->value().toByteArray() );
82 mAttributeEditorViewSplitter->restoreState( attributeEditorSplitterState->value().toByteArray() );
84 mPreviewColumnsMenu =
new QMenu(
this );
85 mActionPreviewColumnsMenu->setMenu( mPreviewColumnsMenu );
91 connect( mActionExpressionPreview, &QAction::triggered,
this, &QgsDualView::previewExpressionBuilder );
100 auto createShortcuts = [
this](
const QString &objectName, void (
QgsFeatureListView::*slot )() ) {
105 connect( sc, &QShortcut::activated, mFeatureListView, slot );
112 QButtonGroup *buttonGroup =
new QButtonGroup(
this );
113 buttonGroup->setExclusive(
false );
117 QAbstractButton *bt = buttonGroup->button(
static_cast<int>( action ) );
119 bt->setChecked(
true );
120 connect( buttonGroup, qOverload<QAbstractButton *, bool>( &QButtonGroup::buttonToggled ),
this, &QgsDualView::panZoomGroupButtonToggled );
121 mFlashButton->setChecked(
QgsSettings().value( u
"/qgis/attributeTable/featureListHighlightFeature"_s,
true ).toBool() );
122 connect( mFlashButton, &QToolButton::clicked,
this, &QgsDualView::flashButtonClicked );
134 delete mAttributeForm;
135 mAttributeForm =
nullptr;
144 mEditorContext = context;
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]() {
273 mFeatureListModel->setSortByDisplayExpression(
true, Qt::AscendingOrder );
275 sortMenu->addAction( sortByPreviewExpressionAsc );
276 QAction *sortByPreviewExpressionDesc =
new QAction(
QgsApplication::getThemeIcon( u
"sort-reverse.svg"_s ), tr(
"By Display Name (Descending)" ),
this );
277 connect( sortByPreviewExpressionDesc, &QAction::triggered,
this, [
this]() {
278 mFeatureListModel->setSortByDisplayExpression(
true, Qt::DescendingOrder );
280 sortMenu->addAction( sortByPreviewExpressionDesc );
281 QAction *sortByPreviewExpressionCustom =
new QAction(
QgsApplication::getThemeIcon( u
"mIconExpressionPreview.svg"_s ), tr(
"By Custom Expression" ),
this );
282 connect( sortByPreviewExpressionCustom, &QAction::triggered,
this, [
this]() {
284 mFeatureListModel->setSortByDisplayExpression(
false );
286 sortMenu->addAction( sortByPreviewExpressionCustom );
288 mFeatureListPreviewButton->addAction( sortMenuAction );
290 QAction *separator =
new QAction( mFeatureListPreviewButton );
291 separator->setSeparator(
true );
292 mFeatureListPreviewButton->addAction( separator );
293 restoreRecentDisplayExpressions();
295 if ( defaultFieldAction )
297 mFeatureListPreviewButton->setDefaultAction( defaultFieldAction );
298 mFeatureListPreviewButton->defaultAction()->trigger();
302 mActionExpressionPreview->setText( displayExpression );
303 mFeatureListPreviewButton->setDefaultAction( mActionExpressionPreview );
305 mFeatureListView->setDisplayExpression( displayExpression );
306 setDisplayExpression( displayExpression.isEmpty() ? tr(
"'[Please define preview text]'" ) : displayExpression );
312 setCurrentIndex(
view );
323 switch ( mFilterModel->filterMode() )
347 mMasterModel->setShowValidityState(
false );
359 || ( mMasterModel->rowCount() == 0 );
371 if ( mFilterModel->mapCanvas() )
373 const QgsRectangle rect = mFilterModel->mapCanvas()->mapSettings().mapToLayerCoordinates( mLayer, mFilterModel->mapCanvas()->extent() );
388 mMasterModel->setShowValidityState(
true );
400 if ( !filterExpression.isEmpty() )
417 setBrowsingAutoPanScaleAllowed(
false );
425 setBrowsingAutoPanScaleAllowed(
true );
429 if ( requiresTableReload )
432 mFilterModel->disconnectFilterModeConnections();
434 mMasterModel->setRequest( request );
435 whileBlocking( mLayerCache )->setCacheGeometry( needsGeometry );
436 mMasterModel->loadLayer();
446 mFilterModel->setSelectedOnTop( selectedOnTop );
449void QgsDualView::initLayerCache(
bool cacheGeometry )
453 const int cacheSize = settings.
value( u
"qgis/attributeTableRowCache"_s,
"10000" ).toInt();
460 rebuildFullLayerCache();
466 delete mFeatureListModel;
470 mMasterModel =
new QgsAttributeTableModel( mLayerCache,
this );
471 mMasterModel->setRequest( request );
472 mMasterModel->setEditorContext( mEditorContext );
473 mMasterModel->setExtraColumns( 1 );
481 mMasterModel->loadLayer();
483 mFilterModel =
new QgsAttributeTableFilterModel( mapCanvas, mMasterModel, mMasterModel );
487 connect( mMasterModel, &QgsAttributeTableModel::rowsRemoved, mFilterModel, &QgsAttributeTableFilterModel::invalidate );
488 connect( mMasterModel, &QgsAttributeTableModel::rowsInserted, mFilterModel, &QgsAttributeTableFilterModel::invalidate );
492 mFeatureListModel =
new QgsFeatureListModel( mFilterModel, mFilterModel );
495void QgsDualView::restoreRecentDisplayExpressions()
497 const QVariantList previewExpressions = mLayer->customProperty( u
"dualview/previewExpressions"_s ).toList();
499 for (
const QVariant &previewExpression : previewExpressions )
500 insertRecentlyUsedDisplayExpression( previewExpression.toString() );
503void QgsDualView::saveRecentDisplayExpressions()
const
509 const QList<QAction *> actions = mFeatureListPreviewButton->actions();
512 int index = actions.indexOf( mLastDisplayExpressionAction );
515 QVariantList previewExpressions;
516 for ( ; index < actions.length(); ++index )
518 QAction *action = actions.at( index );
519 previewExpressions << action->property(
"previewExpression" );
522 mLayer->setCustomProperty( u
"dualview/previewExpressions"_s, previewExpressions );
526void QgsDualView::setDisplayExpression(
const QString &expression )
528 mDisplayExpression = expression;
529 insertRecentlyUsedDisplayExpression( expression );
532void QgsDualView::insertRecentlyUsedDisplayExpression(
const QString &expression )
534 const QList<QAction *> actions = mFeatureListPreviewButton->actions();
537 const int index = actions.indexOf( mLastDisplayExpressionAction );
540 for (
int i = 0; index + i < actions.length(); ++i )
542 QAction *action = actions.at( index );
543 if ( action->text() == expression || i >= 9 )
545 if ( action == mLastDisplayExpressionAction )
547 mLastDisplayExpressionAction =
nullptr;
549 mFeatureListPreviewButton->removeAction( action );
553 if ( !mLastDisplayExpressionAction )
555 mLastDisplayExpressionAction = action;
561 QString name = expression;
563 if ( expression.startsWith(
"COALESCE( \""_L1 ) && expression.endsWith(
", '<NULL>' )"_L1 ) )
565 name = expression.mid( 11, expression.length() - 24 );
567 const int fieldIndex = mLayer->fields().indexOf( name );
568 if ( fieldIndex != -1 )
570 name = mLayer->attributeDisplayName( fieldIndex );
571 icon = mLayer->fields().iconForField( fieldIndex );
579 QAction *previewAction =
new QAction( icon, name, mFeatureListPreviewButton );
580 previewAction->setProperty(
"previewExpression", expression );
581 connect( previewAction, &QAction::triggered,
this, [expression,
this](
bool ) {
582 setDisplayExpression( expression );
583 mFeatureListPreviewButton->setText( expression );
586 mFeatureListPreviewButton->insertAction( mLastDisplayExpressionAction, previewAction );
587 mLastDisplayExpressionAction = previewAction;
590void QgsDualView::updateEditSelectionProgress(
int progress,
int count )
592 mProgressCount->setText( u
"%1 / %2"_s.arg( progress + 1 ).arg( count ) );
593 mPreviousFeatureButton->setEnabled( progress > 0 );
594 mNextFeatureButton->setEnabled( progress + 1 < count );
595 mFirstFeatureButton->setEnabled( progress > 0 );
596 mLastFeatureButton->setEnabled( progress + 1 < count );
597 if ( mAttributeForm )
599 mAttributeForm->setVisible( count > 0 );
603void QgsDualView::panOrZoomToFeature(
const QgsFeatureIds &featureset )
605 QgsMapCanvas *canvas = mFilterModel->mapCanvas();
608 if ( mBrowsingAutoPanScaleAllowed )
610 if ( mAutoPanButton->isChecked() )
611 QTimer::singleShot( 0,
this, [
this, featureset, canvas]() {
614 else if ( mAutoZoomButton->isChecked() )
615 QTimer::singleShot( 0,
this, [
this, featureset, canvas]() {
619 if ( mFlashButton->isChecked() )
620 QTimer::singleShot( 0,
this, [
this, featureset, canvas]() {
623 mLastFeatureSet = featureset;
627void QgsDualView::setBrowsingAutoPanScaleAllowed(
bool allowed )
629 if ( mBrowsingAutoPanScaleAllowed == allowed )
632 mBrowsingAutoPanScaleAllowed = allowed;
634 mAutoPanButton->setEnabled( allowed );
635 mAutoZoomButton->setEnabled( allowed );
637 const QString disabledHint = tr(
"(disabled when attribute table only shows features visible in the current map canvas extent)" );
639 mAutoPanButton->setToolTip( tr(
"Automatically pan to the current feature" ) + ( allowed ? QString() : QString(
' ' ) + disabledHint ) );
640 mAutoZoomButton->setToolTip( tr(
"Automatically zoom to the current feature" ) + ( allowed ? QString() : QString(
' ' ) + disabledHint ) );
643void QgsDualView::panZoomGroupButtonToggled( QAbstractButton *button,
bool checked )
645 if ( button == mAutoPanButton && checked )
647 QgsSettings().setEnumValue( u
"/qgis/attributeTable/featureListBrowsingAction"_s,
PanToFeature );
648 mAutoZoomButton->setChecked(
false );
650 else if ( button == mAutoZoomButton && checked )
652 QgsSettings().setEnumValue( u
"/qgis/attributeTable/featureListBrowsingAction"_s,
ZoomToFeature );
653 mAutoPanButton->setChecked(
false );
657 QgsSettings().setEnumValue( u
"/qgis/attributeTable/featureListBrowsingAction"_s,
NoAction );
660 if ( checked && mLayer->isSpatial() )
661 panOrZoomToFeature( mFeatureListView->currentEditSelection() );
664void QgsDualView::flashButtonClicked(
bool clicked )
666 QgsSettings().setValue( u
"/qgis/attributeTable/featureListHighlightFeature"_s, clicked );
670 QgsMapCanvas *canvas = mFilterModel->mapCanvas();
673 canvas->
flashFeatureIds( mLayer, mFeatureListView->currentEditSelection() );
676void QgsDualView::filterError(
const QString &errorMessage )
678 if ( mEditorContext.mainMessageBar() )
680 mEditorContext.mainMessageBar()->pushWarning( tr(
"An error occurred while filtering features" ), errorMessage );
684void QgsDualView::featureListAboutToChangeEditSelection(
bool &ok )
686 if ( !mAttributeForm )
689 if ( mLayer->isEditable() && !mAttributeForm->save() )
693void QgsDualView::featureListCurrentEditSelectionChanged(
const QgsFeature &feat )
695 if ( !mAttributeForm )
697 initAttributeForm( feat );
699 else if ( !mLayer->isEditable() || mAttributeForm->save() )
701 mAttributeForm->setFeature( feat );
703 featureset << feat.
id();
706 if ( mLayer->isSpatial() )
707 panOrZoomToFeature( featureset );
717 mFeatureListView->setCurrentFeatureEdited(
false );
718 mFeatureListView->setEditSelection( fids );
723 return mAttributeForm ? mAttributeForm->save() :
false;
728 mConditionalFormatWidgetStack->setVisible( !mConditionalFormatWidgetStack->isVisible() );
733 if ( !mAttributeForm )
738 mPreviousView =
view();
751 if ( !mAttributeForm )
758 mAttributeForm->setVisible(
true );
763 mAttributeForm->setVisible( mFilterModel->rowCount() > 0 );
767void QgsDualView::previewExpressionBuilder()
773 dlg.setWindowTitle( tr(
"Expression Based Preview" ) );
774 dlg.setExpressionText( mFeatureListView->displayExpression() );
776 if ( dlg.exec() == QDialog::Accepted )
778 mFeatureListView->setDisplayExpression( dlg.expressionText() );
779 mActionExpressionPreview->setText( dlg.expressionText() );
781 mFeatureListPreviewButton->setDefaultAction( mActionExpressionPreview );
782 mFeatureListPreviewButton->setPopupMode( QToolButton::MenuButtonPopup );
785 setDisplayExpression( mFeatureListView->displayExpression() );
788void QgsDualView::previewColumnChanged( QAction *previewAction,
const QString &expression )
790 if ( !mFeatureListView->setDisplayExpression( u
"COALESCE( \"%1\", '<NULL>' )"_s.arg( expression ) ) )
792 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() ) );
796 mActionExpressionPreview->setText( tr(
"Expression" ) );
797 mFeatureListPreviewButton->setText( previewAction->text() );
798 mFeatureListPreviewButton->setIcon( previewAction->icon() );
799 mFeatureListPreviewButton->setPopupMode( QToolButton::InstantPopup );
802 setDisplayExpression( mFeatureListView->displayExpression() );
807 return mMasterModel->rowCount();
812 return mFilterModel->rowCount();
817 const QModelIndex currentIndex = mTableView->currentIndex();
818 if ( !currentIndex.isValid() )
823 const QVariant var = mMasterModel->data( currentIndex, Qt::DisplayRole );
824 QApplication::clipboard()->setText( var.toString() );
830 mProgressDlg->cancel();
835 if ( mAttributeForm )
837 mAttributeForm->parentFormValueChanged( attribute, newValue );
844 saveRecentDisplayExpressions();
851 conditionalFormattingSplitterState->setValue( mConditionalSplitter->saveState() );
852 attributeEditorSplitterState->setValue( mAttributeEditorViewSplitter->saveState() );
855void QgsDualView::viewWillShowContextMenu( QMenu *menu,
const QModelIndex &masterIndex )
862 const QVariant displayValue = mMasterModel->data( masterIndex, Qt::DisplayRole );
864 if ( displayValue.isValid() )
867 QString previewDisplayValue = displayValue.toString();
868 if ( previewDisplayValue.length() > 12 )
870 previewDisplayValue.truncate( 12 );
871 previewDisplayValue.append( u
"…"_s );
875 QAction *copyContentAction = menu->addAction( tr(
"Copy Cell Content (%1)" ).arg( previewDisplayValue ) );
876 menu->addAction( copyContentAction );
877 connect( copyContentAction, &QAction::triggered,
this, [displayValue] {
878 QApplication::clipboard()->setText( displayValue.toString() );
882 const QVariant rawValue = mMasterModel->data( masterIndex, Qt::EditRole );
883 if ( rawValue.isValid() )
886 QString previewRawValue = rawValue.toString();
887 if ( previewRawValue.length() > 12 )
889 previewRawValue.truncate( 12 );
890 previewRawValue.append( u
"…"_s );
893 QAction *copyRawContentAction = menu->addAction( tr(
"Copy Raw Value (%1)" ).arg( previewRawValue ) );
894 menu->addAction( copyRawContentAction );
895 connect( copyRawContentAction, &QAction::triggered,
this, [rawValue] {
896 QApplication::clipboard()->setText( rawValue.toString() );
900 QgsVectorLayer *vl = mFilterModel->layer();
901 QgsMapCanvas *canvas = mFilterModel->mapCanvas();
904 QAction *zoomToFeatureAction = menu->addAction( tr(
"Zoom to Feature" ) );
905 connect( zoomToFeatureAction, &QAction::triggered,
this, &QgsDualView::zoomToCurrentFeature );
907 QAction *panToFeatureAction = menu->addAction( tr(
"Pan to Feature" ) );
908 connect( panToFeatureAction, &QAction::triggered,
this, &QgsDualView::panToCurrentFeature );
910 QAction *flashFeatureAction = menu->addAction( tr(
"Flash Feature" ) );
911 connect( flashFeatureAction, &QAction::triggered,
this, &QgsDualView::flashCurrentFeature );
915 const QList<QgsAction> actions = mLayer->actions()->actions( u
"Field"_s );
916 if ( !actions.isEmpty() )
918 QAction *a = menu->addAction( tr(
"Run Layer Action" ) );
919 a->setEnabled(
false );
921 for (
const QgsAction &action : actions )
923 if ( !action.runable() )
926 if ( vl && !vl->
isEditable() && action.isEnabledOnlyWhenEditable() )
929 QgsAttributeTableAction *a =
new QgsAttributeTableAction( action.name(),
this, action.id(), masterIndex );
933 const QModelIndex rowSourceIndex = mMasterModel->index( masterIndex.row(), 0 );
934 if ( !rowSourceIndex.isValid() )
940 QgsMapLayerActionContext context;
942 if ( !registeredActions.isEmpty() )
945 menu->addSeparator();
947 for ( QgsMapLayerAction *action : registeredActions )
949 QgsAttributeTableMapLayerAction *a =
new QgsAttributeTableMapLayerAction( action->text(),
this, action, rowSourceIndex );
956 const QgsFeatureId currentFid = mMasterModel->rowToId( masterIndex.row() );
957 if ( mLayer->selectedFeatureCount() > 1 && mLayer->selectedFeatureIds().contains( currentFid ) )
960 if ( !registeredActions.isEmpty() )
962 menu->addSeparator();
963 QAction *action = menu->addAction( tr(
"Actions on Selection (%1)" ).arg( mLayer->selectedFeatureCount() ) );
964 action->setEnabled(
false );
966 QgsMapLayerActionContext context;
967 for ( QgsMapLayerAction *action : registeredActions )
969 menu->addAction( action->text(), action, [
this, action, context]() {
970 Q_NOWARN_DEPRECATED_PUSH
971 action->triggerForFeatures( mLayer, mLayer->selectedFeatures() );
972 Q_NOWARN_DEPRECATED_POP
973 action->triggerForFeatures( mLayer, mLayer->selectedFeatures(), context );
979 menu->addSeparator();
980 QgsAttributeTableAction *a =
new QgsAttributeTableAction( tr(
"Open Form" ),
this, QUuid(), rowSourceIndex );
985void QgsDualView::widgetWillShowContextMenu(
QgsActionMenu *menu,
const QModelIndex &atIndex )
991void QgsDualView::showViewHeaderMenu( QPoint point )
993 const int col = mTableView->columnAt( point.x() );
995 delete mHorizontalHeaderMenu;
996 mHorizontalHeaderMenu =
new QMenu(
this );
998 QAction *hide =
new QAction( tr(
"&Hide Column" ), mHorizontalHeaderMenu );
999 connect( hide, &QAction::triggered,
this, &QgsDualView::hideColumn );
1000 hide->setData( col );
1001 mHorizontalHeaderMenu->addAction( hide );
1002 QAction *setWidth =
new QAction( tr(
"&Set Width…" ), mHorizontalHeaderMenu );
1003 connect( setWidth, &QAction::triggered,
this, &QgsDualView::resizeColumn );
1004 setWidth->setData( col );
1005 mHorizontalHeaderMenu->addAction( setWidth );
1007 QAction *setWidthAllColumns =
new QAction( tr(
"&Set All Column Widths…" ), mHorizontalHeaderMenu );
1008 connect( setWidthAllColumns, &QAction::triggered,
this, &QgsDualView::resizeAllColumns );
1009 setWidthAllColumns->setData( col );
1010 mHorizontalHeaderMenu->addAction( setWidthAllColumns );
1012 QAction *optimizeWidth =
new QAction( tr(
"&Autosize" ), mHorizontalHeaderMenu );
1013 connect( optimizeWidth, &QAction::triggered,
this, &QgsDualView::autosizeColumn );
1014 optimizeWidth->setData( col );
1015 mHorizontalHeaderMenu->addAction( optimizeWidth );
1017 QAction *optimizeWidthAllColumns =
new QAction( tr(
"&Autosize All Columns" ), mHorizontalHeaderMenu );
1018 connect( optimizeWidthAllColumns, &QAction::triggered,
this, &QgsDualView::autosizeAllColumns );
1019 mHorizontalHeaderMenu->addAction( optimizeWidthAllColumns );
1022 mHorizontalHeaderMenu->addSeparator();
1023 QAction *organize =
new QAction( tr(
"&Organize Columns…" ), mHorizontalHeaderMenu );
1024 connect( organize, &QAction::triggered,
this, &QgsDualView::organizeColumns );
1025 mHorizontalHeaderMenu->addAction( organize );
1026 QAction *sort =
new QAction( tr(
"&Sort…" ), mHorizontalHeaderMenu );
1027 connect( sort, &QAction::triggered,
this, [
this]() { modifySort(); } );
1028 mHorizontalHeaderMenu->addAction( sort );
1030 mHorizontalHeaderMenu->popup( mTableView->horizontalHeader()->mapToGlobal( point ) );
1033void QgsDualView::organizeColumns()
1041 if ( dialog.exec() == QDialog::Accepted )
1043 const QgsAttributeTableConfig config = dialog.config();
1048void QgsDualView::tableColumnResized(
int column,
int width )
1050 QgsAttributeTableConfig config = mConfig;
1052 if ( sourceCol >= 0 && config.
columnWidth( sourceCol ) != width )
1059void QgsDualView::hideColumn()
1061 QAction *action = qobject_cast<QAction *>( sender() );
1062 const int col = action->data().toInt();
1063 QgsAttributeTableConfig config = mConfig;
1065 if ( sourceCol >= 0 )
1072void QgsDualView::resizeColumn()
1074 QAction *action = qobject_cast<QAction *>( sender() );
1075 const int col = action->data().toInt();
1079 QgsAttributeTableConfig config = mConfig;
1081 if ( sourceCol >= 0 )
1084 const int width = QInputDialog::getInt(
this, tr(
"Set column width" ), tr(
"Enter column width" ), mTableView->columnWidth( col ), 0, 1000, 10, &ok );
1093void QgsDualView::resizeAllColumns()
1095 QAction *action = qobject_cast<QAction *>( sender() );
1096 const int col = action->data().toInt();
1100 QgsAttributeTableConfig config = mConfig;
1103 const int width = QInputDialog::getInt(
this, tr(
"Set Column Width" ), tr(
"Enter column width" ), mTableView->columnWidth( col ), 1, 1000, 10, &ok );
1106 const int colCount = mTableView->model()->columnCount();
1109 for (
int i = 0; i < colCount; i++ )
1118void QgsDualView::autosizeColumn()
1120 QAction *action = qobject_cast<QAction *>( sender() );
1121 const int col = action->data().toInt();
1122 mTableView->resizeColumnToContents( col );
1125void QgsDualView::autosizeAllColumns()
1127 mTableView->resizeColumnsToContents();
1130bool QgsDualView::modifySort()
1135 QgsAttributeTableConfig config = mConfig;
1138 orderByDlg.setWindowTitle( tr(
"Configure Attribute Table Sort Order" ) );
1139 QDialogButtonBox *dialogButtonBox =
new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel );
1140 QGridLayout *layout =
new QGridLayout();
1141 connect( dialogButtonBox, &QDialogButtonBox::accepted, &orderByDlg, &QDialog::accept );
1142 connect( dialogButtonBox, &QDialogButtonBox::rejected, &orderByDlg, &QDialog::reject );
1143 orderByDlg.setLayout( layout );
1145 QGroupBox *sortingGroupBox =
new QGroupBox();
1146 sortingGroupBox->setTitle( tr(
"Defined sort order in attribute table" ) );
1147 sortingGroupBox->setCheckable(
true );
1149 layout->addWidget( sortingGroupBox );
1150 sortingGroupBox->setLayout(
new QGridLayout() );
1152 QgsExpressionBuilderWidget *expressionBuilder =
new QgsExpressionBuilderWidget();
1155 expressionBuilder->
initWithLayer( mLayer, context, u
"generic"_s );
1158 sortingGroupBox->layout()->addWidget( expressionBuilder );
1160 QCheckBox *cbxSortAscending =
new QCheckBox( tr(
"Sort ascending" ) );
1161 cbxSortAscending->setChecked( config.
sortOrder() == Qt::AscendingOrder );
1162 sortingGroupBox->layout()->addWidget( cbxSortAscending );
1164 layout->addWidget( dialogButtonBox );
1165 if ( orderByDlg.exec() )
1167 const Qt::SortOrder sortOrder = cbxSortAscending->isChecked() ? Qt::AscendingOrder : Qt::DescendingOrder;
1168 if ( sortingGroupBox->isChecked() )
1190 QSet<int> attributes;
1194 const QVector<QgsAttributeTableConfig::ColumnConfig> constColumnconfigs { config.
columns() };
1203 const QSet<int> colAttrs { attributes };
1204 for (
const int attrIdx : std::as_const( colAttrs ) )
1213 std::sort( attrs.begin(), attrs.end() );
1217void QgsDualView::zoomToCurrentFeature()
1219 const QModelIndex currentIndex = mTableView->currentIndex();
1220 if ( !currentIndex.isValid() )
1226 ids.insert( mFilterModel->rowToId( currentIndex ) );
1227 QgsMapCanvas *canvas = mFilterModel->mapCanvas();
1234void QgsDualView::panToCurrentFeature()
1236 const QModelIndex currentIndex = mTableView->currentIndex();
1237 if ( !currentIndex.isValid() )
1243 ids.insert( mFilterModel->rowToId( currentIndex ) );
1244 QgsMapCanvas *canvas = mFilterModel->mapCanvas();
1251void QgsDualView::flashCurrentFeature()
1253 const QModelIndex currentIndex = mTableView->currentIndex();
1254 if ( !currentIndex.isValid() )
1260 ids.insert( mFilterModel->rowToId( currentIndex ) );
1261 QgsMapCanvas *canvas = mFilterModel->mapCanvas();
1268void QgsDualView::rebuildFullLayerCache()
1273 mLayerCache->setFullCache(
true );
1276void QgsDualView::previewExpressionChanged(
const QString &expression )
1278 mLayer->setDisplayExpression( expression );
1281void QgsDualView::onSortColumnChanged()
1292void QgsDualView::updateSelectedFeatures()
1294 QgsFeatureRequest r = mMasterModel->request();
1299 mMasterModel->setRequest( r );
1300 mMasterModel->loadLayer();
1304void QgsDualView::updateEditedAddedFeatures()
1306 QgsFeatureRequest r = mMasterModel->request();
1311 mMasterModel->setRequest( r );
1312 mMasterModel->loadLayer();
1316void QgsDualView::extentChanged()
1318 QgsFeatureRequest r = mMasterModel->request();
1321 const QgsRectangle rect = mFilterModel->mapCanvas()->mapSettings().mapToLayerCoordinates( mLayer, mFilterModel->mapCanvas()->extent() );
1323 mMasterModel->setRequest( r );
1324 mMasterModel->loadLayer();
1329void QgsDualView::featureFormAttributeChanged(
const QString &attribute,
const QVariant &value,
bool attributeChanged )
1331 Q_UNUSED( attribute )
1333 if ( attributeChanged )
1335 mFeatureListView->setCurrentFeatureEdited(
true );
1336 mAttributeForm->save();
1347 mFilterModel->setFilterExpression( filterExpression, context );
1348 mFilterModel->filterFeatures();
1354 mMasterModel->setRequest( request );
1359 mTableView->setFeatureSelectionManager( featureSelectionManager );
1360 mFeatureListView->setFeatureSelectionManager( featureSelectionManager );
1362 if ( mFeatureSelectionManager && mFeatureSelectionManager->parent() ==
this )
1363 delete mFeatureSelectionManager;
1365 mFeatureSelectionManager = featureSelectionManager;
1371 mConfig.
update( mLayer->fields() );
1372 mLayer->setAttributeTableConfig( mConfig );
1373 mFilterModel->setAttributeTableConfig( mConfig );
1374 mTableView->setAttributeTableConfig( mConfig );
1380 mLayerCache->setCacheGeometry(
true );
1384 mMasterModel->setRequest( request );
1385 mLayerCache->setCacheSubsetOfAttributes( attributes );
1391 mFilterModel->sort( -1 );
1396 mConfig.setSortOrder( sortOrder );
1402 return mFilterModel->sortExpression();
1410void QgsDualView::progress(
int i,
bool &cancel )
1412 if ( !mProgressDlg )
1414 mProgressDlg =
new QProgressDialog( tr(
"Loading features…" ), tr(
"Abort" ), 0, 0,
this );
1415 mProgressDlg->setWindowTitle( tr(
"Attribute Table" ) );
1416 mProgressDlg->setWindowModality( Qt::WindowModal );
1417 mProgressDlg->show();
1420 mProgressDlg->setLabelText( tr(
"%L1 features loaded." ).arg( i ) );
1421 QCoreApplication::processEvents();
1423 cancel = mProgressDlg && mProgressDlg->wasCanceled();
1426void QgsDualView::finished()
1428 delete mProgressDlg;
1429 mProgressDlg =
nullptr;
1438 mDualView->masterModel()->executeAction( mAction, mFieldIdx );
1444 editedIds << mDualView->masterModel()->rowToId( mFieldIdx.row() );
1445 mDualView->setCurrentEditSelection( editedIds );
1456 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.