QGIS API Documentation  3.4.15-Madeira (e83d02e274)
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  Q_FOREACH ( const QgsAttributeTableConfig::ColumnConfig &columnConfig, config.columns() )
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  mFeatureSelectionManager = new QgsVectorLayerSelectionManager( mFilterModel->layer(), mFilterModel );
152  }
153 
154  mFeatureSelectionModel = new QgsFeatureSelectionModel( mFilterModel, mFilterModel, mFeatureSelectionManager, mFilterModel );
155  setSelectionModel( mFeatureSelectionModel );
156  mTableDelegate->setFeatureSelectionModel( mFeatureSelectionModel );
157  connect( mFeatureSelectionModel, static_cast<void ( QgsFeatureSelectionModel::* )( const QModelIndexList &indexes )>( &QgsFeatureSelectionModel::requestRepaint ),
158  this, static_cast<void ( QgsAttributeTableView::* )( const QModelIndexList &indexes )>( &QgsAttributeTableView::repaintRequested ) );
159  connect( mFeatureSelectionModel, static_cast<void ( QgsFeatureSelectionModel::* )()>( &QgsFeatureSelectionModel::requestRepaint ),
160  this, static_cast<void ( QgsAttributeTableView::* )()>( &QgsAttributeTableView::repaintRequested ) );
161 
162  connect( mFilterModel->layer(), &QgsVectorLayer::editingStarted, this, &QgsAttributeTableView::recreateActionWidgets );
163  connect( mFilterModel->layer(), &QgsVectorLayer::editingStopped, this, &QgsAttributeTableView::recreateActionWidgets );
164  connect( mFilterModel->layer(), &QgsVectorLayer::readOnlyChanged, this, &QgsAttributeTableView::recreateActionWidgets );
165  }
166 }
167 
169 {
170  delete mFeatureSelectionManager;
171 
172  mFeatureSelectionManager = featureSelectionManager;
173 
174  if ( mFeatureSelectionModel )
175  mFeatureSelectionModel->setFeatureSelectionManager( mFeatureSelectionManager );
176 }
177 
178 QWidget *QgsAttributeTableView::createActionWidget( QgsFeatureId fid )
179 {
180  QgsAttributeTableConfig attributeTableConfig = mConfig;
181 
182  QToolButton *toolButton = nullptr;
183  QWidget *container = nullptr;
184 
185  if ( attributeTableConfig.actionWidgetStyle() == QgsAttributeTableConfig::DropDown )
186  {
187  toolButton = new QToolButton();
188  toolButton->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
189  toolButton->setPopupMode( QToolButton::MenuButtonPopup );
190  container = toolButton;
191  }
192  else
193  {
194  container = new QWidget();
195  container->setLayout( new QHBoxLayout() );
196  container->layout()->setMargin( 0 );
197  }
198 
199  QList< QAction * > actionList;
200  QAction *defaultAction = nullptr;
201 
202  // first add user created layer actions
203  QList<QgsAction> actions = mFilterModel->layer()->actions()->actions( QStringLiteral( "Feature" ) );
204  Q_FOREACH ( const QgsAction &action, actions )
205  {
206  if ( !mFilterModel->layer()->isEditable() && action.isEnabledOnlyWhenEditable() )
207  continue;
208 
209  QString actionTitle = !action.shortTitle().isEmpty() ? action.shortTitle() : action.icon().isNull() ? action.name() : QString();
210  QAction *act = new QAction( action.icon(), actionTitle, container );
211  act->setToolTip( action.name() );
212  act->setData( "user_action" );
213  act->setProperty( "fid", fid );
214  act->setProperty( "action_id", action.id() );
215  connect( act, &QAction::triggered, this, &QgsAttributeTableView::actionTriggered );
216  actionList << act;
217 
218  if ( mFilterModel->layer()->actions()->defaultAction( QStringLiteral( "Feature" ) ).id() == action.id() )
219  defaultAction = act;
220  }
221 
222  // next add any registered actions for this layer
223  Q_FOREACH ( QgsMapLayerAction *mapLayerAction,
224  QgsGui::mapLayerActionRegistry()->mapLayerActions( mFilterModel->layer(),
226  {
227  QAction *action = new QAction( mapLayerAction->icon(), mapLayerAction->text(), container );
228  action->setData( "map_layer_action" );
229  action->setToolTip( mapLayerAction->text() );
230  action->setProperty( "fid", fid );
231  action->setProperty( "action", qVariantFromValue( qobject_cast<QObject *>( mapLayerAction ) ) );
232  connect( action, &QAction::triggered, this, &QgsAttributeTableView::actionTriggered );
233  actionList << action;
234 
235  if ( !defaultAction &&
236  QgsGui::mapLayerActionRegistry()->defaultActionForLayer( mFilterModel->layer() ) == mapLayerAction )
237  defaultAction = action;
238  }
239 
240  if ( !defaultAction && !actionList.isEmpty() )
241  defaultAction = actionList.at( 0 );
242 
243  Q_FOREACH ( QAction *act, actionList )
244  {
245  if ( attributeTableConfig.actionWidgetStyle() == QgsAttributeTableConfig::DropDown )
246  {
247  toolButton->addAction( act );
248 
249  if ( act == defaultAction )
250  toolButton->setDefaultAction( act );
251 
252  container = toolButton;
253  }
254  else
255  {
256  QToolButton *btn = new QToolButton;
257  btn->setDefaultAction( act );
258  container->layout()->addWidget( btn );
259  }
260  }
261 
262  if ( attributeTableConfig.actionWidgetStyle() == QgsAttributeTableConfig::ButtonList )
263  {
264  static_cast< QHBoxLayout * >( container->layout() )->addStretch();
265  }
266 
267  // TODO: Rethink default actions
268 #if 0
269  if ( toolButton && !toolButton->actions().isEmpty() && actions->defaultAction() == -1 )
270  toolButton->setDefaultAction( toolButton->actions().at( 0 ) );
271 #endif
272 
273  return container;
274 }
275 
276 void QgsAttributeTableView::closeEvent( QCloseEvent *e )
277 {
278  Q_UNUSED( e );
279  QgsSettings settings;
280  settings.setValue( QStringLiteral( "BetterAttributeTable/geometry" ), QVariant( saveGeometry() ) );
281 }
282 
283 void QgsAttributeTableView::mousePressEvent( QMouseEvent *event )
284 {
285  setSelectionMode( QAbstractItemView::NoSelection );
286  QTableView::mousePressEvent( event );
287  setSelectionMode( QAbstractItemView::ExtendedSelection );
288 }
289 
291 {
292  setSelectionMode( QAbstractItemView::NoSelection );
293  QTableView::mouseReleaseEvent( event );
294  setSelectionMode( QAbstractItemView::ExtendedSelection );
295 }
296 
297 void QgsAttributeTableView::mouseMoveEvent( QMouseEvent *event )
298 {
299  setSelectionMode( QAbstractItemView::NoSelection );
300  QTableView::mouseMoveEvent( event );
301  setSelectionMode( QAbstractItemView::ExtendedSelection );
302 }
303 
304 void QgsAttributeTableView::keyPressEvent( QKeyEvent *event )
305 {
306  switch ( event->key() )
307  {
308 
309  // Default Qt behavior would be to change the selection.
310  // We don't make it that easy for the user to trash his selection.
311  case Qt::Key_Up:
312  case Qt::Key_Down:
313  case Qt::Key_Left:
314  case Qt::Key_Right:
315  setSelectionMode( QAbstractItemView::NoSelection );
316  QTableView::keyPressEvent( event );
317  setSelectionMode( QAbstractItemView::ExtendedSelection );
318  break;
319 
320  default:
321  QTableView::keyPressEvent( event );
322  break;
323  }
324 }
325 
326 void QgsAttributeTableView::repaintRequested( const QModelIndexList &indexes )
327 {
328  Q_FOREACH ( const QModelIndex &index, indexes )
329  {
330  update( index );
331  }
332 }
333 
335 {
336  setDirtyRegion( viewport()->rect() );
337 }
338 
340 {
341  QItemSelection selection;
342  selection.append( QItemSelectionRange( mFilterModel->index( 0, 0 ), mFilterModel->index( mFilterModel->rowCount() - 1, 0 ) ) );
343  mFeatureSelectionModel->selectFeatures( selection, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows );
344 }
345 
346 void QgsAttributeTableView::contextMenuEvent( QContextMenuEvent *event )
347 {
348  delete mActionPopup;
349  mActionPopup = nullptr;
350 
351  QModelIndex idx = indexAt( event->pos() );
352  if ( !idx.isValid() )
353  {
354  return;
355  }
356 
357  QgsVectorLayer *vlayer = mFilterModel->layer();
358  if ( !vlayer )
359  return;
360 
361  mActionPopup = new QMenu( this );
362 
363  mActionPopup->addAction( tr( "Select All" ), this, SLOT( selectAll() ), QKeySequence::SelectAll );
364 
365  // let some other parts of the application add some actions
366  emit willShowContextMenu( mActionPopup, idx );
367 
368  if ( !mActionPopup->actions().isEmpty() )
369  {
370  mActionPopup->popup( event->globalPos() );
371  }
372 }
373 
375 {
376  selectRow( row, true );
377 }
378 
380 {
381  selectRow( row, false );
382 }
383 
384 void QgsAttributeTableView::modelDeleted()
385 {
386  mFilterModel = nullptr;
387  mFeatureSelectionManager = nullptr;
388  mFeatureSelectionModel = nullptr;
389 }
390 
391 void QgsAttributeTableView::selectRow( int row, bool anchor )
392 {
393  if ( selectionBehavior() == QTableView::SelectColumns
394  || ( selectionMode() == QTableView::SingleSelection
395  && selectionBehavior() == QTableView::SelectItems ) )
396  return;
397 
398  if ( row >= 0 && row < model()->rowCount() )
399  {
400  int column = horizontalHeader()->logicalIndexAt( isRightToLeft() ? viewport()->width() : 0 );
401  QModelIndex index = model()->index( row, column );
402  QItemSelectionModel::SelectionFlags command = selectionCommand( index );
403  selectionModel()->setCurrentIndex( index, QItemSelectionModel::NoUpdate );
404  if ( ( anchor && !( command & QItemSelectionModel::Current ) )
405  || ( selectionMode() == QTableView::SingleSelection ) )
406  mRowSectionAnchor = row;
407 
408  if ( selectionMode() != QTableView::SingleSelection
409  && command.testFlag( QItemSelectionModel::Toggle ) )
410  {
411  if ( anchor )
412  mCtrlDragSelectionFlag = mFeatureSelectionModel->isSelected( index )
413  ? QItemSelectionModel::Deselect : QItemSelectionModel::Select;
414  command &= ~QItemSelectionModel::Toggle;
415  command |= mCtrlDragSelectionFlag;
416  if ( !anchor )
417  command |= QItemSelectionModel::Current;
418  }
419 
420  QModelIndex tl = model()->index( std::min( mRowSectionAnchor, row ), 0 );
421  QModelIndex br = model()->index( std::max( mRowSectionAnchor, row ), model()->columnCount() - 1 );
422  if ( verticalHeader()->sectionsMoved() && tl.row() != br.row() )
423  setSelection( visualRect( tl ) | visualRect( br ), command );
424  else
425  mFeatureSelectionModel->selectFeatures( QItemSelection( tl, br ), command );
426  }
427 }
428 
429 void QgsAttributeTableView::showHorizontalSortIndicator()
430 {
431  horizontalHeader()->setSortIndicatorShown( true );
432 }
433 
434 void QgsAttributeTableView::actionTriggered()
435 {
436  QAction *action = qobject_cast<QAction *>( sender() );
437  QgsFeatureId fid = action->property( "fid" ).toLongLong();
438 
439  QgsFeature f;
440  mFilterModel->layerCache()->getFeatures( QgsFeatureRequest( fid ) ).nextFeature( f );
441 
442  if ( action->data().toString() == QLatin1String( "user_action" ) )
443  {
444  mFilterModel->layer()->actions()->doAction( action->property( "action_id" ).toString(), f );
445  }
446  else if ( action->data().toString() == QLatin1String( "map_layer_action" ) )
447  {
448  QObject *object = action->property( "action" ).value<QObject *>();
449  QgsMapLayerAction *layerAction = qobject_cast<QgsMapLayerAction *>( object );
450  if ( layerAction )
451  {
452  layerAction->triggerForFeature( mFilterModel->layer(), &f );
453  }
454  }
455 }
456 
457 void QgsAttributeTableView::columnSizeChanged( int index, int oldWidth, int newWidth )
458 {
459  Q_UNUSED( oldWidth )
460  emit columnResized( index, newWidth );
461 }
462 
463 void QgsAttributeTableView::onActionColumnItemPainted( const QModelIndex &index )
464 {
465  if ( !indexWidget( index ) )
466  {
467  QWidget *widget = createActionWidget( mFilterModel->data( index, QgsAttributeTableModel::FeatureIdRole ).toLongLong() );
468  mActionWidgets.insert( index, widget );
469  setIndexWidget( index, widget );
470  }
471 }
472 
473 void QgsAttributeTableView::recreateActionWidgets()
474 {
475  QMap< QModelIndex, QWidget * >::const_iterator it = mActionWidgets.constBegin();
476  for ( ; it != mActionWidgets.constEnd(); ++it )
477  {
478  // ownership of widget was transferred by initial call to setIndexWidget - clearing
479  // the index widget will delete the old widget safely
480  // they should then be recreated by onActionColumnItemPainted
481  setIndexWidget( it.key(), nullptr );
482  }
483  mActionWidgets.clear();
484 }
QgsActionManager * actions()
Returns all layer actions defined on this layer.
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)
Is 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
void readOnlyChanged()
Emitted when the read only state of this layer is changed.
qint64 QgsFeatureId
Definition: qgsfeatureid.h:25
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
virtual void selectFeatures(const QItemSelection &selection, QItemSelectionModel::SelectionFlags command)
Select features on this table.
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
ActionWidgetStyle actionWidgetStyle() const
Gets the style of the action widget.
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.
QgsVectorLayer * layer() const
Returns the layer this filter acts on.
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.
QString name() const
The name of the action. This may be a longer description.
Definition: qgsaction.h:124
void requestRepaint()
Request a repaint of the visible items of connected views.
QgsVectorLayerCache * layerCache() const
Returns the layerCache this filter acts on.
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()
Is emitted, when edited changes successfully have been written to the data provider.
void mousePressEvent(QMouseEvent *event) override
Called for mouse press events on a table cell.
bool hidden
Flag that controls if the column is hidden.
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()
Is emitted, when editing on this layer has started.
QUuid id() const
Returns a unique id for this action.
Definition: qgsaction.h:134
QList< QgsFeatureId > selectedFeaturesIds() const
Returns the selected features in the attribute table in table sorted order.
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.
int width
Width of column, or -1 for default width.
QString shortTitle() const
The short title is used to label user interface elements like buttons.
Definition: qgsaction.h:127
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...
Defines the configuration of a column in the attribute table.
virtual void selectRow(int row)
void actionColumnItemPainted(const QModelIndex &index) const
Is emitted when an action column item is painted.
virtual void _q_selectRow(int row)
bool nextFeature(QgsFeature &f)
QList< QgsAction > actions(const QString &actionScope=QString()) const
Returns a list of actions that are available in the given action scope.
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:78
void triggerForFeature(QgsMapLayer *layer, const QgsFeature *feature)
Triggers the action with the specified layer and feature.
bool isEnabledOnlyWhenEditable() const
Returns whether only enabled in editable mode.
Definition: qgsaction.h:173
An action which can run on map layers.
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.
QIcon icon() const
The icon.
Definition: qgsaction.h:147
QModelIndex fidToIndex(QgsFeatureId fid) override