QGIS API Documentation  3.8.0-Zanzibar (11aff65)
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 {
42  QgsSettings settings;
43  restoreGeometry( settings.value( QStringLiteral( "BetterAttributeTable/geometry" ) ).toByteArray() );
44 
45  //verticalHeader()->setDefaultSectionSize( 20 );
46  horizontalHeader()->setHighlightSections( false );
47 
48  // We need mouse move events to create the action button on hover
49  mTableDelegate = new QgsAttributeTableDelegate( this );
50  setItemDelegate( mTableDelegate );
51 
52  setEditTriggers( QAbstractItemView::AllEditTriggers );
53 
54  setSelectionBehavior( QAbstractItemView::SelectRows );
55  setSelectionMode( QAbstractItemView::ExtendedSelection );
56  setSortingEnabled( true ); // At this point no data is in the model yet, so actually nothing is sorted.
57  horizontalHeader()->setSortIndicatorShown( false ); // So hide the indicator to avoid confusion.
58 
59  verticalHeader()->viewport()->installEventFilter( this );
60 
61  connect( verticalHeader(), &QHeaderView::sectionPressed, this, [ = ]( int row ) { selectRow( row, true ); } );
62  connect( verticalHeader(), &QHeaderView::sectionEntered, this, &QgsAttributeTableView::_q_selectRow );
63  connect( horizontalHeader(), &QHeaderView::sectionResized, this, &QgsAttributeTableView::columnSizeChanged );
64  connect( horizontalHeader(), &QHeaderView::sortIndicatorChanged, this, &QgsAttributeTableView::showHorizontalSortIndicator );
65  connect( QgsGui::mapLayerActionRegistry(), &QgsMapLayerActionRegistry::changed, this, &QgsAttributeTableView::recreateActionWidgets );
66 }
67 
68 bool QgsAttributeTableView::eventFilter( QObject *object, QEvent *event )
69 {
70  if ( object == verticalHeader()->viewport() )
71  {
72  switch ( event->type() )
73  {
74  case QEvent::MouseButtonPress:
75  mFeatureSelectionModel->enableSync( false );
76  break;
77 
78  case QEvent::MouseButtonRelease:
79  mFeatureSelectionModel->enableSync( true );
80  break;
81 
82  default:
83  break;
84  }
85  }
86  return QTableView::eventFilter( object, event );
87 }
88 
90 {
91  int i = 0;
92  const auto constColumns = config.columns();
93  for ( const QgsAttributeTableConfig::ColumnConfig &columnConfig : constColumns )
94  {
95  if ( columnConfig.hidden )
96  continue;
97 
98  if ( columnConfig.width >= 0 )
99  {
100  setColumnWidth( i, columnConfig.width );
101  }
102  else
103  {
104  setColumnWidth( i, horizontalHeader()->defaultSectionSize() );
105  }
106  i++;
107  }
108  mConfig = config;
109 }
110 
112 {
113  // In order to get the ids in the right sorted order based on the view we have to get the feature ids first
114  // from the selection manager which is in the order the user selected them when clicking
115  // then get the model index, sort that, and finally return the new sorted features ids.
116  const QgsFeatureIds featureIds = mFeatureSelectionManager->selectedFeatureIds();
117  QModelIndexList indexList;
118  for ( const QgsFeatureId &id : featureIds )
119  {
120  QModelIndex index = mFilterModel->fidToIndex( id );
121  indexList << index;
122  }
123 
124  std::sort( indexList.begin(), indexList.end() );
125  QList<QgsFeatureId> ids;
126  for ( const QModelIndex &index : indexList )
127  {
128  QgsFeatureId id = mFilterModel->data( index, QgsAttributeTableModel::FeatureIdRole ).toLongLong();
129  ids.append( id );
130  }
131  return ids;
132 }
133 
135 {
136  mFilterModel = filterModel;
137  QTableView::setModel( mFilterModel );
138 
139  if ( mFilterModel )
140  {
141  connect( mFilterModel, &QObject::destroyed, this, &QgsAttributeTableView::modelDeleted );
142  connect( mTableDelegate, &QgsAttributeTableDelegate::actionColumnItemPainted, this, &QgsAttributeTableView::onActionColumnItemPainted );
143  }
144 
145  delete mFeatureSelectionModel;
146  mFeatureSelectionModel = nullptr;
147 
148  if ( mFilterModel )
149  {
150  if ( !mFeatureSelectionManager )
151  {
152  mFeatureSelectionManager = new QgsVectorLayerSelectionManager( mFilterModel->layer(), mFilterModel );
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  delete mFeatureSelectionManager;
172 
173  mFeatureSelectionManager = featureSelectionManager;
174 
175  if ( mFeatureSelectionModel )
176  mFeatureSelectionModel->setFeatureSelectionManager( mFeatureSelectionManager );
177 }
178 
179 QWidget *QgsAttributeTableView::createActionWidget( QgsFeatureId fid )
180 {
181  QgsAttributeTableConfig attributeTableConfig = mConfig;
182 
183  QToolButton *toolButton = nullptr;
184  QWidget *container = nullptr;
185 
186  if ( attributeTableConfig.actionWidgetStyle() == QgsAttributeTableConfig::DropDown )
187  {
188  toolButton = new QToolButton();
189  toolButton->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
190  toolButton->setPopupMode( QToolButton::MenuButtonPopup );
191  container = toolButton;
192  }
193  else
194  {
195  container = new QWidget();
196  container->setLayout( new QHBoxLayout() );
197  container->layout()->setMargin( 0 );
198  }
199 
200  QList< QAction * > actionList;
201  QAction *defaultAction = nullptr;
202 
203  // first add user created layer actions
204  QList<QgsAction> actions = mFilterModel->layer()->actions()->actions( QStringLiteral( "Feature" ) );
205  const auto constActions = actions;
206  for ( const QgsAction &action : constActions )
207  {
208  if ( !mFilterModel->layer()->isEditable() && action.isEnabledOnlyWhenEditable() )
209  continue;
210 
211  QString actionTitle = !action.shortTitle().isEmpty() ? action.shortTitle() : action.icon().isNull() ? action.name() : QString();
212  QAction *act = new QAction( action.icon(), actionTitle, container );
213  act->setToolTip( action.name() );
214  act->setData( "user_action" );
215  act->setProperty( "fid", fid );
216  act->setProperty( "action_id", action.id() );
217  connect( act, &QAction::triggered, this, &QgsAttributeTableView::actionTriggered );
218  actionList << act;
219 
220  if ( mFilterModel->layer()->actions()->defaultAction( QStringLiteral( "Feature" ) ).id() == action.id() )
221  defaultAction = act;
222  }
223 
224  const auto mapLayerActions {QgsGui::mapLayerActionRegistry()->mapLayerActions( mFilterModel->layer(), QgsMapLayerAction::SingleFeature ) };
225  // next add any registered actions for this layer
226  for ( QgsMapLayerAction *mapLayerAction : mapLayerActions )
227  {
228  QAction *action = new QAction( mapLayerAction->icon(), mapLayerAction->text(), container );
229  action->setData( "map_layer_action" );
230  action->setToolTip( mapLayerAction->text() );
231  action->setProperty( "fid", fid );
232  action->setProperty( "action", qVariantFromValue( qobject_cast<QObject *>( mapLayerAction ) ) );
233  connect( action, &QAction::triggered, this, &QgsAttributeTableView::actionTriggered );
234  actionList << action;
235 
236  if ( !defaultAction &&
237  QgsGui::mapLayerActionRegistry()->defaultActionForLayer( mFilterModel->layer() ) == mapLayerAction )
238  defaultAction = action;
239  }
240 
241  if ( !defaultAction && !actionList.isEmpty() )
242  defaultAction = actionList.at( 0 );
243 
244  const auto constActionList = actionList;
245  for ( QAction *act : constActionList )
246  {
247  if ( attributeTableConfig.actionWidgetStyle() == QgsAttributeTableConfig::DropDown )
248  {
249  toolButton->addAction( act );
250 
251  if ( act == defaultAction )
252  toolButton->setDefaultAction( act );
253 
254  container = toolButton;
255  }
256  else
257  {
258  QToolButton *btn = new QToolButton;
259  btn->setDefaultAction( act );
260  container->layout()->addWidget( btn );
261  }
262  }
263 
264  if ( attributeTableConfig.actionWidgetStyle() == QgsAttributeTableConfig::ButtonList )
265  {
266  static_cast< QHBoxLayout * >( container->layout() )->addStretch();
267  }
268 
269  // TODO: Rethink default actions
270 #if 0
271  if ( toolButton && !toolButton->actions().isEmpty() && actions->defaultAction() == -1 )
272  toolButton->setDefaultAction( toolButton->actions().at( 0 ) );
273 #endif
274 
275  return container;
276 }
277 
278 void QgsAttributeTableView::closeEvent( QCloseEvent *e )
279 {
280  Q_UNUSED( e )
281  QgsSettings settings;
282  settings.setValue( QStringLiteral( "BetterAttributeTable/geometry" ), QVariant( saveGeometry() ) );
283 }
284 
285 void QgsAttributeTableView::mousePressEvent( QMouseEvent *event )
286 {
287  setSelectionMode( QAbstractItemView::NoSelection );
288  QTableView::mousePressEvent( event );
289  setSelectionMode( QAbstractItemView::ExtendedSelection );
290 }
291 
293 {
294  setSelectionMode( QAbstractItemView::NoSelection );
295  QTableView::mouseReleaseEvent( event );
296  setSelectionMode( QAbstractItemView::ExtendedSelection );
297 }
298 
299 void QgsAttributeTableView::mouseMoveEvent( QMouseEvent *event )
300 {
301  setSelectionMode( QAbstractItemView::NoSelection );
302  QTableView::mouseMoveEvent( event );
303  setSelectionMode( QAbstractItemView::ExtendedSelection );
304 }
305 
306 void QgsAttributeTableView::keyPressEvent( QKeyEvent *event )
307 {
308  switch ( event->key() )
309  {
310 
311  // Default Qt behavior would be to change the selection.
312  // We don't make it that easy for the user to trash his selection.
313  case Qt::Key_Up:
314  case Qt::Key_Down:
315  case Qt::Key_Left:
316  case Qt::Key_Right:
317  setSelectionMode( QAbstractItemView::NoSelection );
318  QTableView::keyPressEvent( event );
319  setSelectionMode( QAbstractItemView::ExtendedSelection );
320  break;
321 
322  default:
323  QTableView::keyPressEvent( event );
324  break;
325  }
326 }
327 
328 void QgsAttributeTableView::repaintRequested( const QModelIndexList &indexes )
329 {
330  const auto constIndexes = indexes;
331  for ( const QModelIndex &index : constIndexes )
332  {
333  update( index );
334  }
335 }
336 
338 {
339  setDirtyRegion( viewport()->rect() );
340 }
341 
343 {
344  QItemSelection selection;
345  selection.append( QItemSelectionRange( mFilterModel->index( 0, 0 ), mFilterModel->index( mFilterModel->rowCount() - 1, 0 ) ) );
346  mFeatureSelectionModel->selectFeatures( selection, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows );
347 }
348 
349 void QgsAttributeTableView::contextMenuEvent( QContextMenuEvent *event )
350 {
351  delete mActionPopup;
352  mActionPopup = nullptr;
353 
354  QModelIndex idx = indexAt( event->pos() );
355  if ( !idx.isValid() )
356  {
357  return;
358  }
359 
360  QgsVectorLayer *vlayer = mFilterModel->layer();
361  if ( !vlayer )
362  return;
363 
364  mActionPopup = new QMenu( this );
365 
366  mActionPopup->addAction( tr( "Select All" ), this, SLOT( selectAll() ), QKeySequence::SelectAll );
367 
368  // let some other parts of the application add some actions
369  emit willShowContextMenu( mActionPopup, idx );
370 
371  if ( !mActionPopup->actions().isEmpty() )
372  {
373  mActionPopup->popup( event->globalPos() );
374  }
375 }
376 
378 {
379  selectRow( row, true );
380 }
381 
383 {
384  selectRow( row, false );
385 }
386 
387 void QgsAttributeTableView::modelDeleted()
388 {
389  mFilterModel = nullptr;
390  mFeatureSelectionManager = nullptr;
391  mFeatureSelectionModel = nullptr;
392 }
393 
394 void QgsAttributeTableView::selectRow( int row, bool anchor )
395 {
396  if ( selectionBehavior() == QTableView::SelectColumns
397  || ( selectionMode() == QTableView::SingleSelection
398  && selectionBehavior() == QTableView::SelectItems ) )
399  return;
400 
401  if ( row >= 0 && row < model()->rowCount() )
402  {
403  int column = horizontalHeader()->logicalIndexAt( isRightToLeft() ? viewport()->width() : 0 );
404  QModelIndex index = model()->index( row, column );
405  QItemSelectionModel::SelectionFlags command = selectionCommand( index );
406  selectionModel()->setCurrentIndex( index, QItemSelectionModel::NoUpdate );
407  if ( ( anchor && !( command & QItemSelectionModel::Current ) )
408  || ( selectionMode() == QTableView::SingleSelection ) )
409  mRowSectionAnchor = row;
410 
411  if ( selectionMode() != QTableView::SingleSelection
412  && command.testFlag( QItemSelectionModel::Toggle ) )
413  {
414  if ( anchor )
415  mCtrlDragSelectionFlag = mFeatureSelectionModel->isSelected( index )
416  ? QItemSelectionModel::Deselect : QItemSelectionModel::Select;
417  command &= ~QItemSelectionModel::Toggle;
418  command |= mCtrlDragSelectionFlag;
419  if ( !anchor )
420  command |= QItemSelectionModel::Current;
421  }
422 
423  QModelIndex tl = model()->index( std::min( mRowSectionAnchor, row ), 0 );
424  QModelIndex br = model()->index( std::max( mRowSectionAnchor, row ), model()->columnCount() - 1 );
425  if ( verticalHeader()->sectionsMoved() && tl.row() != br.row() )
426  setSelection( visualRect( tl ) | visualRect( br ), command );
427  else
428  mFeatureSelectionModel->selectFeatures( QItemSelection( tl, br ), command );
429  }
430 }
431 
432 void QgsAttributeTableView::showHorizontalSortIndicator()
433 {
434  horizontalHeader()->setSortIndicatorShown( true );
435 }
436 
437 void QgsAttributeTableView::actionTriggered()
438 {
439  QAction *action = qobject_cast<QAction *>( sender() );
440  QgsFeatureId fid = action->property( "fid" ).toLongLong();
441 
442  QgsFeature f;
443  mFilterModel->layerCache()->getFeatures( QgsFeatureRequest( fid ) ).nextFeature( f );
444 
445  if ( action->data().toString() == QLatin1String( "user_action" ) )
446  {
447  mFilterModel->layer()->actions()->doAction( action->property( "action_id" ).toString(), f );
448  }
449  else if ( action->data().toString() == QLatin1String( "map_layer_action" ) )
450  {
451  QObject *object = action->property( "action" ).value<QObject *>();
452  QgsMapLayerAction *layerAction = qobject_cast<QgsMapLayerAction *>( object );
453  if ( layerAction )
454  {
455  layerAction->triggerForFeature( mFilterModel->layer(), &f );
456  }
457  }
458 }
459 
460 void QgsAttributeTableView::columnSizeChanged( int index, int oldWidth, int newWidth )
461 {
462  Q_UNUSED( oldWidth )
463  emit columnResized( index, newWidth );
464 }
465 
466 void QgsAttributeTableView::onActionColumnItemPainted( const QModelIndex &index )
467 {
468  if ( !indexWidget( index ) )
469  {
470  QWidget *widget = createActionWidget( mFilterModel->data( index, QgsAttributeTableModel::FeatureIdRole ).toLongLong() );
471  mActionWidgets.insert( index, widget );
472  setIndexWidget( index, widget );
473  }
474 }
475 
476 void QgsAttributeTableView::recreateActionWidgets()
477 {
478  QMap< QModelIndex, QWidget * >::const_iterator it = mActionWidgets.constBegin();
479  for ( ; it != mActionWidgets.constEnd(); ++it )
480  {
481  // ownership of widget was transferred by initial call to setIndexWidget - clearing
482  // the index widget will delete the old widget safely
483  // they should then be recreated by onActionColumnItemPainted
484  setIndexWidget( it.key(), nullptr );
485  }
486  mActionWidgets.clear();
487 }
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.
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.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:58
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
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
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.
void saveGeometry(QWidget *widget, const QString &keyName)
Save the wigget geometry into settings.
Get the feature id of the feature in this row.
bool isEditable() const FINAL
Returns true if the provider is in editing mode.
bool restoreGeometry(QWidget *widget, const QString &keyName)
Restore the wigget geometry from settings.
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.
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
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...
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:79
void triggerForFeature(QgsMapLayer *layer, const QgsFeature *feature)
Triggers the action with the specified layer and feature.
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