QGIS API Documentation  3.6.0-Noosa (5873452)
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.
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)
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
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.
bool isEnabledOnlyWhenEditable() const
Returns whether only enabled in editable mode.
Definition: qgsaction.h:173
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
QString shortTitle() const
The short title is used to label user interface elements like buttons.
Definition: qgsaction.h:127
void actionColumnItemPainted(const QModelIndex &index) const
Is 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()
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)
QIcon icon() const
The icon.
Definition: qgsaction.h:147
QString name() const
The name of the action. This may be a longer description.
Definition: qgsaction.h:124
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.
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.
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)
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