QGIS API Documentation  3.12.1-BucureČ™ti (121cc00ff0)
qgsattributetableview.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  QgsAttributeTableView.cpp
3  --------------------------------------
4  Date : Feb 2009
5  Copyright : (C) 2009 Vita Cizek
6  Email : weetya (at) gmail.com
7  ***************************************************************************
8  * *
9  * This program is free software; you can redistribute it and/or modify *
10  * it under the terms of the GNU General Public License as published by *
11  * the Free Software Foundation; either version 2 of the License, or *
12  * (at your option) any later version. *
13  * *
14  ***************************************************************************/
15 
16 #include <QKeyEvent>
17 #include <QHeaderView>
18 #include <QMenu>
19 #include <QToolButton>
20 #include <QHBoxLayout>
21 
22 #include "qgssettings.h"
23 #include "qgsactionmanager.h"
24 #include "qgsattributetableview.h"
25 #include "qgsattributetablemodel.h"
28 #include "qgsvectorlayer.h"
29 #include "qgsvectorlayercache.h"
31 #include "qgsvectordataprovider.h"
32 #include "qgslogger.h"
33 #include "qgsmapcanvas.h"
36 #include "qgsfeatureiterator.h"
37 #include "qgsgui.h"
38 
40  : QTableView( parent )
41 {
43 
44  //verticalHeader()->setDefaultSectionSize( 20 );
45  horizontalHeader()->setHighlightSections( false );
46 
47  // We need mouse move events to create the action button on hover
48  mTableDelegate = new QgsAttributeTableDelegate( this );
49  setItemDelegate( mTableDelegate );
50 
51  setEditTriggers( QAbstractItemView::AllEditTriggers );
52 
53  setSelectionBehavior( QAbstractItemView::SelectRows );
54  setSelectionMode( QAbstractItemView::ExtendedSelection );
55  setSortingEnabled( true ); // At this point no data is in the model yet, so actually nothing is sorted.
56  horizontalHeader()->setSortIndicatorShown( false ); // So hide the indicator to avoid confusion.
57 
58  verticalHeader()->viewport()->installEventFilter( this );
59 
60  connect( verticalHeader(), &QHeaderView::sectionPressed, this, [ = ]( int row ) { selectRow( row, true ); } );
61  connect( verticalHeader(), &QHeaderView::sectionEntered, this, &QgsAttributeTableView::_q_selectRow );
62  connect( horizontalHeader(), &QHeaderView::sectionResized, this, &QgsAttributeTableView::columnSizeChanged );
63  connect( horizontalHeader(), &QHeaderView::sortIndicatorChanged, this, &QgsAttributeTableView::showHorizontalSortIndicator );
64  connect( QgsGui::mapLayerActionRegistry(), &QgsMapLayerActionRegistry::changed, this, &QgsAttributeTableView::recreateActionWidgets );
65 }
66 
67 bool QgsAttributeTableView::eventFilter( QObject *object, QEvent *event )
68 {
69  if ( object == verticalHeader()->viewport() )
70  {
71  switch ( event->type() )
72  {
73  case QEvent::MouseButtonPress:
74  mFeatureSelectionModel->enableSync( false );
75  break;
76 
77  case QEvent::MouseButtonRelease:
78  mFeatureSelectionModel->enableSync( true );
79  break;
80 
81  default:
82  break;
83  }
84  }
85  return QTableView::eventFilter( object, event );
86 }
87 
89 {
90  int i = 0;
91  const auto constColumns = config.columns();
92  for ( const QgsAttributeTableConfig::ColumnConfig &columnConfig : constColumns )
93  {
94  if ( columnConfig.hidden )
95  continue;
96 
97  if ( columnConfig.width >= 0 )
98  {
99  setColumnWidth( i, columnConfig.width );
100  }
101  else
102  {
103  setColumnWidth( i, horizontalHeader()->defaultSectionSize() );
104  }
105  i++;
106  }
107  mConfig = config;
108 }
109 
111 {
112  // In order to get the ids in the right sorted order based on the view we have to get the feature ids first
113  // from the selection manager which is in the order the user selected them when clicking
114  // then get the model index, sort that, and finally return the new sorted features ids.
115  const QgsFeatureIds featureIds = mFeatureSelectionManager->selectedFeatureIds();
116  QModelIndexList indexList;
117  for ( const QgsFeatureId &id : featureIds )
118  {
119  QModelIndex index = mFilterModel->fidToIndex( id );
120  indexList << index;
121  }
122 
123  std::sort( indexList.begin(), indexList.end() );
124  QList<QgsFeatureId> ids;
125  for ( const QModelIndex &index : indexList )
126  {
127  QgsFeatureId id = mFilterModel->data( index, QgsAttributeTableModel::FeatureIdRole ).toLongLong();
128  ids.append( id );
129  }
130  return ids;
131 }
132 
134 {
135  mFilterModel = filterModel;
136  QTableView::setModel( mFilterModel );
137 
138  if ( mFilterModel )
139  {
140  connect( mFilterModel, &QObject::destroyed, this, &QgsAttributeTableView::modelDeleted );
141  connect( mTableDelegate, &QgsAttributeTableDelegate::actionColumnItemPainted, this, &QgsAttributeTableView::onActionColumnItemPainted );
142  }
143 
144  delete mFeatureSelectionModel;
145  mFeatureSelectionModel = nullptr;
146 
147  if ( mFilterModel )
148  {
149  if ( !mFeatureSelectionManager )
150  {
151  mOwnedFeatureSelectionManager = new QgsVectorLayerSelectionManager( mFilterModel->layer(), this );
152  mFeatureSelectionManager = mOwnedFeatureSelectionManager;
153  }
154 
155  mFeatureSelectionModel = new QgsFeatureSelectionModel( mFilterModel, mFilterModel, mFeatureSelectionManager, mFilterModel );
156  setSelectionModel( mFeatureSelectionModel );
157  mTableDelegate->setFeatureSelectionModel( mFeatureSelectionModel );
158  connect( mFeatureSelectionModel, static_cast<void ( QgsFeatureSelectionModel::* )( const QModelIndexList &indexes )>( &QgsFeatureSelectionModel::requestRepaint ),
159  this, static_cast<void ( QgsAttributeTableView::* )( const QModelIndexList &indexes )>( &QgsAttributeTableView::repaintRequested ) );
160  connect( mFeatureSelectionModel, static_cast<void ( QgsFeatureSelectionModel::* )()>( &QgsFeatureSelectionModel::requestRepaint ),
161  this, static_cast<void ( QgsAttributeTableView::* )()>( &QgsAttributeTableView::repaintRequested ) );
162 
163  connect( mFilterModel->layer(), &QgsVectorLayer::editingStarted, this, &QgsAttributeTableView::recreateActionWidgets );
164  connect( mFilterModel->layer(), &QgsVectorLayer::editingStopped, this, &QgsAttributeTableView::recreateActionWidgets );
165  connect( mFilterModel->layer(), &QgsVectorLayer::readOnlyChanged, this, &QgsAttributeTableView::recreateActionWidgets );
166  }
167 }
168 
170 {
171  mFeatureSelectionManager = featureSelectionManager;
172 
173  if ( mFeatureSelectionModel )
174  mFeatureSelectionModel->setFeatureSelectionManager( mFeatureSelectionManager );
175 
176  // only delete the owner selection manager and not one created from outside
177  if ( mOwnedFeatureSelectionManager )
178  {
179  mOwnedFeatureSelectionManager->deleteLater();
180  mOwnedFeatureSelectionManager = nullptr;
181  }
182 }
183 
184 QWidget *QgsAttributeTableView::createActionWidget( QgsFeatureId fid )
185 {
186  QgsAttributeTableConfig attributeTableConfig = mConfig;
187 
188  QToolButton *toolButton = nullptr;
189  QWidget *container = nullptr;
190 
191  if ( attributeTableConfig.actionWidgetStyle() == QgsAttributeTableConfig::DropDown )
192  {
193  toolButton = new QToolButton();
194  toolButton->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
195  toolButton->setPopupMode( QToolButton::MenuButtonPopup );
196  container = toolButton;
197  }
198  else
199  {
200  container = new QWidget();
201  container->setLayout( new QHBoxLayout() );
202  container->layout()->setMargin( 0 );
203  }
204 
205  QList< QAction * > actionList;
206  QAction *defaultAction = nullptr;
207 
208  // first add user created layer actions
209  QList<QgsAction> actions = mFilterModel->layer()->actions()->actions( QStringLiteral( "Feature" ) );
210  const auto constActions = actions;
211  for ( const QgsAction &action : constActions )
212  {
213  if ( !mFilterModel->layer()->isEditable() && action.isEnabledOnlyWhenEditable() )
214  continue;
215 
216  QString actionTitle = !action.shortTitle().isEmpty() ? action.shortTitle() : action.icon().isNull() ? action.name() : QString();
217  QAction *act = new QAction( action.icon(), actionTitle, container );
218  act->setToolTip( action.name() );
219  act->setData( "user_action" );
220  act->setProperty( "fid", fid );
221  act->setProperty( "action_id", action.id() );
222  connect( act, &QAction::triggered, this, &QgsAttributeTableView::actionTriggered );
223  actionList << act;
224 
225  if ( mFilterModel->layer()->actions()->defaultAction( QStringLiteral( "Feature" ) ).id() == action.id() )
226  defaultAction = act;
227  }
228 
229  const auto mapLayerActions {QgsGui::mapLayerActionRegistry()->mapLayerActions( mFilterModel->layer(), QgsMapLayerAction::SingleFeature ) };
230  // next add any registered actions for this layer
231  for ( QgsMapLayerAction *mapLayerAction : mapLayerActions )
232  {
233  QAction *action = new QAction( mapLayerAction->icon(), mapLayerAction->text(), container );
234  action->setData( "map_layer_action" );
235  action->setToolTip( mapLayerAction->text() );
236  action->setProperty( "fid", fid );
237  action->setProperty( "action", qVariantFromValue( qobject_cast<QObject *>( mapLayerAction ) ) );
238  connect( action, &QAction::triggered, this, &QgsAttributeTableView::actionTriggered );
239  actionList << action;
240 
241  if ( !defaultAction &&
242  QgsGui::mapLayerActionRegistry()->defaultActionForLayer( mFilterModel->layer() ) == mapLayerAction )
243  defaultAction = action;
244  }
245 
246  if ( !defaultAction && !actionList.isEmpty() )
247  defaultAction = actionList.at( 0 );
248 
249  const auto constActionList = actionList;
250  for ( QAction *act : constActionList )
251  {
252  if ( attributeTableConfig.actionWidgetStyle() == QgsAttributeTableConfig::DropDown )
253  {
254  toolButton->addAction( act );
255 
256  if ( act == defaultAction )
257  toolButton->setDefaultAction( act );
258 
259  container = toolButton;
260  }
261  else
262  {
263  QToolButton *btn = new QToolButton;
264  btn->setDefaultAction( act );
265  container->layout()->addWidget( btn );
266  }
267  }
268 
269  if ( attributeTableConfig.actionWidgetStyle() == QgsAttributeTableConfig::ButtonList )
270  {
271  static_cast< QHBoxLayout * >( container->layout() )->addStretch();
272  }
273 
274  // TODO: Rethink default actions
275 #if 0
276  if ( toolButton && !toolButton->actions().isEmpty() && actions->defaultAction() == -1 )
277  toolButton->setDefaultAction( toolButton->actions().at( 0 ) );
278 #endif
279 
280  return container;
281 }
282 
283 void QgsAttributeTableView::closeEvent( QCloseEvent *e )
284 {
285  Q_UNUSED( e )
286 }
287 
288 void QgsAttributeTableView::mousePressEvent( QMouseEvent *event )
289 {
290  setSelectionMode( QAbstractItemView::NoSelection );
291  QTableView::mousePressEvent( event );
292  setSelectionMode( QAbstractItemView::ExtendedSelection );
293 }
294 
296 {
297  setSelectionMode( QAbstractItemView::NoSelection );
298  QTableView::mouseReleaseEvent( event );
299  setSelectionMode( QAbstractItemView::ExtendedSelection );
300 }
301 
302 void QgsAttributeTableView::mouseMoveEvent( QMouseEvent *event )
303 {
304  setSelectionMode( QAbstractItemView::NoSelection );
305  QTableView::mouseMoveEvent( event );
306  setSelectionMode( QAbstractItemView::ExtendedSelection );
307 }
308 
309 void QgsAttributeTableView::keyPressEvent( QKeyEvent *event )
310 {
311  switch ( event->key() )
312  {
313 
314  // Default Qt behavior would be to change the selection.
315  // We don't make it that easy for the user to trash his selection.
316  case Qt::Key_Up:
317  case Qt::Key_Down:
318  case Qt::Key_Left:
319  case Qt::Key_Right:
320  setSelectionMode( QAbstractItemView::NoSelection );
321  QTableView::keyPressEvent( event );
322  setSelectionMode( QAbstractItemView::ExtendedSelection );
323  break;
324 
325  default:
326  QTableView::keyPressEvent( event );
327  break;
328  }
329 }
330 
331 void QgsAttributeTableView::repaintRequested( const QModelIndexList &indexes )
332 {
333  const auto constIndexes = indexes;
334  for ( const QModelIndex &index : constIndexes )
335  {
336  update( index );
337  }
338 }
339 
341 {
342  setDirtyRegion( viewport()->rect() );
343 }
344 
346 {
347  QItemSelection selection;
348  selection.append( QItemSelectionRange( mFilterModel->index( 0, 0 ), mFilterModel->index( mFilterModel->rowCount() - 1, 0 ) ) );
349  mFeatureSelectionModel->selectFeatures( selection, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows );
350 }
351 
352 void QgsAttributeTableView::contextMenuEvent( QContextMenuEvent *event )
353 {
354  delete mActionPopup;
355  mActionPopup = nullptr;
356 
357  QModelIndex idx = indexAt( event->pos() );
358  if ( !idx.isValid() )
359  {
360  return;
361  }
362 
363  QgsVectorLayer *vlayer = mFilterModel->layer();
364  if ( !vlayer )
365  return;
366 
367  mActionPopup = new QMenu( this );
368 
369  mActionPopup->addAction( tr( "Select All" ), this, SLOT( selectAll() ), QKeySequence::SelectAll );
370 
371  // let some other parts of the application add some actions
372  emit willShowContextMenu( mActionPopup, idx );
373 
374  if ( !mActionPopup->actions().isEmpty() )
375  {
376  mActionPopup->popup( event->globalPos() );
377  }
378 }
379 
381 {
382  selectRow( row, true );
383 }
384 
386 {
387  selectRow( row, false );
388 }
389 
390 void QgsAttributeTableView::modelDeleted()
391 {
392  mFilterModel = nullptr;
393  mFeatureSelectionManager = nullptr;
394  mFeatureSelectionModel = nullptr;
395 }
396 
397 void QgsAttributeTableView::selectRow( int row, bool anchor )
398 {
399  if ( selectionBehavior() == QTableView::SelectColumns
400  || ( selectionMode() == QTableView::SingleSelection
401  && selectionBehavior() == QTableView::SelectItems ) )
402  return;
403 
404  if ( row >= 0 && row < model()->rowCount() )
405  {
406  int column = horizontalHeader()->logicalIndexAt( isRightToLeft() ? viewport()->width() : 0 );
407  QModelIndex index = model()->index( row, column );
408  QItemSelectionModel::SelectionFlags command = selectionCommand( index );
409  selectionModel()->setCurrentIndex( index, QItemSelectionModel::NoUpdate );
410  if ( ( anchor && !( command & QItemSelectionModel::Current ) )
411  || ( selectionMode() == QTableView::SingleSelection ) )
412  mRowSectionAnchor = row;
413 
414  if ( selectionMode() != QTableView::SingleSelection
415  && command.testFlag( QItemSelectionModel::Toggle ) )
416  {
417  if ( anchor )
418  mCtrlDragSelectionFlag = mFeatureSelectionModel->isSelected( index )
419  ? QItemSelectionModel::Deselect : QItemSelectionModel::Select;
420  command &= ~QItemSelectionModel::Toggle;
421  command |= mCtrlDragSelectionFlag;
422  if ( !anchor )
423  command |= QItemSelectionModel::Current;
424  }
425 
426  QModelIndex tl = model()->index( std::min( mRowSectionAnchor, row ), 0 );
427  QModelIndex br = model()->index( std::max( mRowSectionAnchor, row ), model()->columnCount() - 1 );
428  if ( verticalHeader()->sectionsMoved() && tl.row() != br.row() )
429  setSelection( visualRect( tl ) | visualRect( br ), command );
430  else
431  mFeatureSelectionModel->selectFeatures( QItemSelection( tl, br ), command );
432  }
433 }
434 
435 void QgsAttributeTableView::showHorizontalSortIndicator()
436 {
437  horizontalHeader()->setSortIndicatorShown( true );
438 }
439 
440 void QgsAttributeTableView::actionTriggered()
441 {
442  QAction *action = qobject_cast<QAction *>( sender() );
443  QgsFeatureId fid = action->property( "fid" ).toLongLong();
444 
445  QgsFeature f;
446  mFilterModel->layerCache()->getFeatures( QgsFeatureRequest( fid ) ).nextFeature( f );
447 
448  if ( action->data().toString() == QLatin1String( "user_action" ) )
449  {
450  mFilterModel->layer()->actions()->doAction( action->property( "action_id" ).toString(), f );
451  }
452  else if ( action->data().toString() == QLatin1String( "map_layer_action" ) )
453  {
454  QObject *object = action->property( "action" ).value<QObject *>();
455  QgsMapLayerAction *layerAction = qobject_cast<QgsMapLayerAction *>( object );
456  if ( layerAction )
457  {
458  layerAction->triggerForFeature( mFilterModel->layer(), f );
459  }
460  }
461 }
462 
463 void QgsAttributeTableView::columnSizeChanged( int index, int oldWidth, int newWidth )
464 {
465  Q_UNUSED( oldWidth )
466  emit columnResized( index, newWidth );
467 }
468 
469 void QgsAttributeTableView::onActionColumnItemPainted( const QModelIndex &index )
470 {
471  if ( !indexWidget( index ) )
472  {
473  QWidget *widget = createActionWidget( mFilterModel->data( index, QgsAttributeTableModel::FeatureIdRole ).toLongLong() );
474  mActionWidgets.insert( index, widget );
475  setIndexWidget( index, widget );
476  }
477 }
478 
479 void QgsAttributeTableView::recreateActionWidgets()
480 {
481  QMap< QModelIndex, QWidget * >::const_iterator it = mActionWidgets.constBegin();
482  for ( ; it != mActionWidgets.constEnd(); ++it )
483  {
484  // ownership of widget was transferred by initial call to setIndexWidget - clearing
485  // the index widget will delete the old widget safely
486  // they should then be recreated by onActionColumnItemPainted
487  setIndexWidget( it.key(), nullptr );
488  }
489  mActionWidgets.clear();
490 }
QgsActionManager * actions()
Returns all layer actions defined on this layer.
QgsVectorLayer * layer() const
Returns the layer this filter acts on.
QVariant data(const QModelIndex &index, int role) const override
Provides a table view of features of a QgsVectorLayer.
virtual bool isSelected(QgsFeatureId fid)
Returns the selection status of a given feature id.
void doAction(QUuid actionId, const QgsFeature &feature, int defaultValueIndex=0, const QgsExpressionContextScope &scope=QgsExpressionContextScope())
Does the given action.
void triggerForFeature(QgsMapLayer *layer, const QgsFeature &feature)
Triggers the action with the specified layer and feature.
QSet< QgsFeatureId > QgsFeatureIds
Definition: qgsfeatureid.h:34
void willShowContextMenu(QMenu *menu, const QModelIndex &atIndex)
Emitted in order to provide a hook to add additional* menu entries to the context menu...
QgsAttributeTableView(QWidget *parent=nullptr)
Constructor for QgsAttributeTableView.
void readOnlyChanged()
Emitted when the read only state of this layer is changed.
qint64 QgsFeatureId
Definition: qgsfeatureid.h:25
virtual void selectFeatures(const QItemSelection &selection, QItemSelectionModel::SelectionFlags command)
Select features on this table.
QList< QgsAction > actions(const QString &actionScope=QString()) const
Returns a list of actions that are available in the given action scope.
void columnResized(int column, int width)
Emitted when a column in the view has been resized.
bool eventFilter(QObject *object, QEvent *event) override
This event filter is installed on the verticalHeader to intercept mouse press and release events...
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:55
static QgsGui * instance()
Returns a pointer to the singleton instance.
Definition: qgsgui.cpp:62
void actionColumnItemPainted(const QModelIndex &index) const
Emitted when an action column item is painted.
void enableSync(bool enable)
Enables or disables synchronisation to the QgsVectorLayer When synchronisation is disabled...
void setFeatureSelectionModel(QgsFeatureSelectionModel *featureSelectionModel)
void mouseReleaseEvent(QMouseEvent *event) override
Called for mouse release events on a table cell.
Get the feature id of the feature in this row.
bool isEditable() const FINAL
Returns true if the provider is in editing mode.
ActionWidgetStyle actionWidgetStyle() const
Gets the style of the action widget.
void requestRepaint()
Request a repaint of the visible items of connected views.
Utility class that encapsulates an action based on vector attributes.
Definition: qgsaction.h:35
virtual const QgsFeatureIds & selectedFeatureIds() const =0
Returns reference to identifiers of selected features.
void editingStopped()
Emitted when edited changes have been successfully written to the data provider.
void mousePressEvent(QMouseEvent *event) override
Called for mouse press events on a table cell.
This class wraps a request for features to a vector layer (or directly its vector data provider)...
virtual void setFeatureSelectionManager(QgsIFeatureSelectionManager *featureSelectionManager)
void changed()
Triggered when an action is added or removed from the registry.
QgsAction defaultAction(const QString &actionScope)
Each scope can have a default action.
virtual void setModel(QgsAttributeTableFilterModel *filterModel)
void editingStarted()
Emitted when editing on this layer has started.
void mouseMoveEvent(QMouseEvent *event) override
Called for mouse move events on a table cell.
QVector< QgsAttributeTableConfig::ColumnConfig > columns() const
Gets the list with all columns and their configuration.
A tool button with a drop-down to select the current action.
void setFeatureSelectionManager(QgsIFeatureSelectionManager *featureSelectionManager)
setFeatureSelectionManager
A delegate item class for QgsAttributeTable (see Qt documentation for QItemDelegate).
void setAttributeTableConfig(const QgsAttributeTableConfig &config)
Set the attribute table config which should be used to control the appearance of the attribute table...
static void enableAutoGeometryRestore(QWidget *widget, const QString &key=QString())
Register the widget to allow its position to be automatically saved and restored when open and closed...
Definition: qgsgui.cpp:133
QList< QgsMapLayerAction * > mapLayerActions(QgsMapLayer *layer, QgsMapLayerAction::Targets targets=QgsMapLayerAction::AllActions)
Returns the map layer actions which can run on the specified layer.
Defines the configuration of a column in the attribute table.
virtual void selectRow(int row)
QUuid id() const
Returns a unique id for this action.
Definition: qgsaction.h:134
virtual void _q_selectRow(int row)
bool nextFeature(QgsFeature &f)
This is a container for configuration of the attribute table.
Is an interface class to abstract feature selection handling.
void closeEvent(QCloseEvent *event) override
Saves geometry to the settings on close.
Represents a vector layer which manages a vector based data sets.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &featureRequest=QgsFeatureRequest())
Query this VectorLayerCache for features.
static QgsMapLayerActionRegistry * mapLayerActionRegistry()
Returns the global map layer action registry, used for registering map layer actions.
Definition: qgsgui.cpp:93
An action which can run on map layers.
QList< QgsFeatureId > selectedFeaturesIds() const
Returns the selected features in the attribute table in table sorted order.
void keyPressEvent(QKeyEvent *event) override
Called for key press events Disables selection change by only pressing an arrow key.
void contextMenuEvent(QContextMenuEvent *event) override
Is called when the context menu will be shown.
QgsVectorLayerCache * layerCache() const
Returns the layerCache this filter acts on.
QModelIndex fidToIndex(QgsFeatureId fid) override