QGIS API Documentation  2.99.0-Master (314842d)
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 
39  : QTableView( parent )
40  , mFilterModel( nullptr )
41  , mFeatureSelectionModel( nullptr )
42  , mFeatureSelectionManager( nullptr )
43  , mActionPopup( nullptr )
44  , mRowSectionAnchor( 0 )
45  , mCtrlDragSelectionFlag( QItemSelectionModel::Select )
46 {
47  QgsSettings settings;
48  restoreGeometry( settings.value( QStringLiteral( "BetterAttributeTable/geometry" ) ).toByteArray() );
49 
50  //verticalHeader()->setDefaultSectionSize( 20 );
51  horizontalHeader()->setHighlightSections( false );
52 
53  // We need mouse move events to create the action button on hover
54  mTableDelegate = new QgsAttributeTableDelegate( this );
55  setItemDelegate( mTableDelegate );
56 
57  setEditTriggers( QAbstractItemView::AllEditTriggers );
58 
59  setSelectionBehavior( QAbstractItemView::SelectRows );
60  setSelectionMode( QAbstractItemView::ExtendedSelection );
61  setSortingEnabled( true ); // At this point no data is in the model yet, so actually nothing is sorted.
62  horizontalHeader()->setSortIndicatorShown( false ); // So hide the indicator to avoid confusion.
63 
64  verticalHeader()->viewport()->installEventFilter( this );
65 
66  connect( verticalHeader(), &QHeaderView::sectionPressed, this, [ = ]( int row ) { selectRow( row, false ); } );
67  connect( verticalHeader(), &QHeaderView::sectionEntered, this, &QgsAttributeTableView::_q_selectRow );
68  connect( horizontalHeader(), &QHeaderView::sectionResized, this, &QgsAttributeTableView::columnSizeChanged );
69  connect( horizontalHeader(), &QHeaderView::sortIndicatorChanged, this, &QgsAttributeTableView::showHorizontalSortIndicator );
70  connect( QgsMapLayerActionRegistry::instance(), &QgsMapLayerActionRegistry::changed, this, &QgsAttributeTableView::recreateActionWidgets );
71 }
72 
73 bool QgsAttributeTableView::eventFilter( QObject *object, QEvent *event )
74 {
75  if ( object == verticalHeader()->viewport() )
76  {
77  switch ( event->type() )
78  {
79  case QEvent::MouseButtonPress:
80  mFeatureSelectionModel->enableSync( false );
81  break;
82 
83  case QEvent::MouseButtonRelease:
84  mFeatureSelectionModel->enableSync( true );
85  break;
86 
87  default:
88  break;
89  }
90  }
91  return false;
92 }
93 
95 {
96  int i = 0;
97  Q_FOREACH ( const QgsAttributeTableConfig::ColumnConfig &columnConfig, config.columns() )
98  {
99  if ( columnConfig.hidden )
100  continue;
101 
102  if ( columnConfig.width >= 0 )
103  {
104  setColumnWidth( i, columnConfig.width );
105  }
106  else
107  {
108  setColumnWidth( i, horizontalHeader()->defaultSectionSize() );
109  }
110  i++;
111  }
112 }
113 
115 {
116  mFilterModel = filterModel;
117  QTableView::setModel( filterModel );
118 
119  if ( mFilterModel )
120  {
121  connect( mFilterModel, &QObject::destroyed, this, &QgsAttributeTableView::modelDeleted );
122  connect( mTableDelegate, &QgsAttributeTableDelegate::actionColumnItemPainted, this, &QgsAttributeTableView::onActionColumnItemPainted );
123  }
124 
125  delete mFeatureSelectionModel;
126  mFeatureSelectionModel = nullptr;
127 
128  if ( filterModel )
129  {
130  if ( !mFeatureSelectionManager )
131  {
132  mFeatureSelectionManager = new QgsVectorLayerSelectionManager( mFilterModel->layer(), mFilterModel );
133  }
134 
135  mFeatureSelectionModel = new QgsFeatureSelectionModel( mFilterModel, mFilterModel, mFeatureSelectionManager, mFilterModel );
136  setSelectionModel( mFeatureSelectionModel );
137  mTableDelegate->setFeatureSelectionModel( mFeatureSelectionModel );
138  connect( mFeatureSelectionModel, static_cast<void ( QgsFeatureSelectionModel::* )( const QModelIndexList &indexes )>( &QgsFeatureSelectionModel::requestRepaint ),
139  this, static_cast<void ( QgsAttributeTableView::* )( const QModelIndexList &indexes )>( &QgsAttributeTableView::repaintRequested ) );
140  connect( mFeatureSelectionModel, static_cast<void ( QgsFeatureSelectionModel::* )()>( &QgsFeatureSelectionModel::requestRepaint ),
141  this, static_cast<void ( QgsAttributeTableView::* )()>( &QgsAttributeTableView::repaintRequested ) );
142  }
143 }
144 
146 {
147  if ( mFeatureSelectionManager )
148  delete mFeatureSelectionManager;
149 
150  mFeatureSelectionManager = featureSelectionManager;
151 
152  if ( mFeatureSelectionModel )
153  mFeatureSelectionModel->setFeatureSelectionManager( mFeatureSelectionManager );
154 }
155 
156 QWidget *QgsAttributeTableView::createActionWidget( QgsFeatureId fid )
157 {
158  QgsAttributeTableConfig attributeTableConfig = mFilterModel->layer()->attributeTableConfig();
159 
160  QToolButton *toolButton = nullptr;
161  QWidget *container = nullptr;
162 
163  if ( attributeTableConfig.actionWidgetStyle() == QgsAttributeTableConfig::DropDown )
164  {
165  toolButton = new QToolButton();
166  toolButton->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
167  toolButton->setPopupMode( QToolButton::MenuButtonPopup );
168  container = toolButton;
169  }
170  else
171  {
172  container = new QWidget();
173  container->setLayout( new QHBoxLayout() );
174  container->layout()->setMargin( 0 );
175  }
176 
177  QList< QAction * > actionList;
178  QAction *defaultAction = nullptr;
179 
180  // first add user created layer actions
181  QList<QgsAction> actions = mFilterModel->layer()->actions()->actions( QStringLiteral( "Feature" ) );
182  Q_FOREACH ( const QgsAction &action, actions )
183  {
184  QString actionTitle = !action.shortTitle().isEmpty() ? action.shortTitle() : action.icon().isNull() ? action.name() : QLatin1String( "" );
185  QAction *act = new QAction( action.icon(), actionTitle, container );
186  act->setToolTip( action.name() );
187  act->setData( "user_action" );
188  act->setProperty( "fid", fid );
189  act->setProperty( "action_id", action.id() );
190  connect( act, &QAction::triggered, this, &QgsAttributeTableView::actionTriggered );
191  actionList << act;
192 
193  if ( mFilterModel->layer()->actions()->defaultAction( QStringLiteral( "AttributeTableRow" ) ).id() == action.id() )
194  defaultAction = act;
195  }
196 
197  // next add any registered actions for this layer
198  Q_FOREACH ( QgsMapLayerAction *mapLayerAction,
199  QgsMapLayerActionRegistry::instance()->mapLayerActions( mFilterModel->layer(),
201  {
202  QAction *action = new QAction( mapLayerAction->icon(), mapLayerAction->text(), container );
203  action->setData( "map_layer_action" );
204  action->setToolTip( mapLayerAction->text() );
205  action->setProperty( "fid", fid );
206  action->setProperty( "action", qVariantFromValue( qobject_cast<QObject *>( mapLayerAction ) ) );
207  connect( action, &QAction::triggered, this, &QgsAttributeTableView::actionTriggered );
208  actionList << action;
209 
210  if ( !defaultAction &&
211  QgsMapLayerActionRegistry::instance()->defaultActionForLayer( mFilterModel->layer() ) == mapLayerAction )
212  defaultAction = action;
213  }
214 
215  if ( !defaultAction && !actionList.isEmpty() )
216  defaultAction = actionList.at( 0 );
217 
218  Q_FOREACH ( QAction *act, actionList )
219  {
220  if ( attributeTableConfig.actionWidgetStyle() == QgsAttributeTableConfig::DropDown )
221  {
222  toolButton->addAction( act );
223 
224  if ( act == defaultAction )
225  toolButton->setDefaultAction( act );
226 
227  container = toolButton;
228  }
229  else
230  {
231  QToolButton *btn = new QToolButton;
232  btn->setDefaultAction( act );
233  container->layout()->addWidget( btn );
234  }
235  }
236 
237  if ( attributeTableConfig.actionWidgetStyle() == QgsAttributeTableConfig::ButtonList )
238  {
239  static_cast< QHBoxLayout * >( container->layout() )->addStretch();
240  }
241 
242  // TODO: Rethink default actions
243 #if 0
244  if ( toolButton && !toolButton->actions().isEmpty() && actions->defaultAction() == -1 )
245  toolButton->setDefaultAction( toolButton->actions().at( 0 ) );
246 #endif
247 
248  return container;
249 }
250 
251 void QgsAttributeTableView::closeEvent( QCloseEvent *e )
252 {
253  Q_UNUSED( e );
254  QgsSettings settings;
255  settings.setValue( QStringLiteral( "BetterAttributeTable/geometry" ), QVariant( saveGeometry() ) );
256 }
257 
258 void QgsAttributeTableView::mousePressEvent( QMouseEvent *event )
259 {
260  setSelectionMode( QAbstractItemView::NoSelection );
261  QTableView::mousePressEvent( event );
262  setSelectionMode( QAbstractItemView::ExtendedSelection );
263 }
264 
266 {
267  setSelectionMode( QAbstractItemView::NoSelection );
268  QTableView::mouseReleaseEvent( event );
269  setSelectionMode( QAbstractItemView::ExtendedSelection );
270 }
271 
272 void QgsAttributeTableView::mouseMoveEvent( QMouseEvent *event )
273 {
274  setSelectionMode( QAbstractItemView::NoSelection );
275  QTableView::mouseMoveEvent( event );
276  setSelectionMode( QAbstractItemView::ExtendedSelection );
277 }
278 
279 void QgsAttributeTableView::keyPressEvent( QKeyEvent *event )
280 {
281  switch ( event->key() )
282  {
283 
284  // Default Qt behavior would be to change the selection.
285  // We don't make it that easy for the user to trash his selection.
286  case Qt::Key_Up:
287  case Qt::Key_Down:
288  case Qt::Key_Left:
289  case Qt::Key_Right:
290  setSelectionMode( QAbstractItemView::NoSelection );
291  QTableView::keyPressEvent( event );
292  setSelectionMode( QAbstractItemView::ExtendedSelection );
293  break;
294 
295  default:
296  QTableView::keyPressEvent( event );
297  break;
298  }
299 }
300 
301 void QgsAttributeTableView::repaintRequested( const QModelIndexList &indexes )
302 {
303  Q_FOREACH ( const QModelIndex &index, indexes )
304  {
305  update( index );
306  }
307 }
308 
310 {
311  setDirtyRegion( viewport()->rect() );
312 }
313 
315 {
316  QItemSelection selection;
317  selection.append( QItemSelectionRange( mFilterModel->index( 0, 0 ), mFilterModel->index( mFilterModel->rowCount() - 1, 0 ) ) );
318  mFeatureSelectionModel->selectFeatures( selection, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows );
319 }
320 
321 void QgsAttributeTableView::contextMenuEvent( QContextMenuEvent *event )
322 {
323  delete mActionPopup;
324  mActionPopup = nullptr;
325 
326  QModelIndex idx = indexAt( event->pos() );
327  if ( !idx.isValid() )
328  {
329  return;
330  }
331 
332  QgsVectorLayer *vlayer = mFilterModel->layer();
333  if ( !vlayer )
334  return;
335 
336  mActionPopup = new QMenu( this );
337 
338  mActionPopup->addAction( tr( "Select All" ), this, SLOT( selectAll() ), QKeySequence::SelectAll );
339 
340  // let some other parts of the application add some actions
341  emit willShowContextMenu( mActionPopup, idx );
342 
343  if ( !mActionPopup->actions().isEmpty() )
344  {
345  mActionPopup->popup( event->globalPos() );
346  }
347 }
348 
350 {
351  selectRow( row, true );
352 }
353 
355 {
356  selectRow( row, false );
357 }
358 
359 void QgsAttributeTableView::modelDeleted()
360 {
361  mFilterModel = nullptr;
362  mFeatureSelectionManager = nullptr;
363  mFeatureSelectionModel = nullptr;
364 }
365 
366 void QgsAttributeTableView::selectRow( int row, bool anchor )
367 {
368  if ( selectionBehavior() == QTableView::SelectColumns
369  || ( selectionMode() == QTableView::SingleSelection
370  && selectionBehavior() == QTableView::SelectItems ) )
371  return;
372 
373  if ( row >= 0 && row < model()->rowCount() )
374  {
375  int column = horizontalHeader()->logicalIndexAt( isRightToLeft() ? viewport()->width() : 0 );
376  QModelIndex index = model()->index( row, column );
377  QItemSelectionModel::SelectionFlags command = selectionCommand( index );
378  selectionModel()->setCurrentIndex( index, QItemSelectionModel::NoUpdate );
379  if ( ( anchor && !( command & QItemSelectionModel::Current ) )
380  || ( selectionMode() == QTableView::SingleSelection ) )
381  mRowSectionAnchor = row;
382 
383  if ( selectionMode() != QTableView::SingleSelection
384  && command.testFlag( QItemSelectionModel::Toggle ) )
385  {
386  if ( anchor )
387  mCtrlDragSelectionFlag = mFeatureSelectionModel->isSelected( index )
388  ? QItemSelectionModel::Deselect : QItemSelectionModel::Select;
389  command &= ~QItemSelectionModel::Toggle;
390  command |= mCtrlDragSelectionFlag;
391  if ( !anchor )
392  command |= QItemSelectionModel::Current;
393  }
394 
395  QModelIndex tl = model()->index( qMin( mRowSectionAnchor, row ), 0 );
396  QModelIndex br = model()->index( qMax( mRowSectionAnchor, row ), model()->columnCount() - 1 );
397  if ( verticalHeader()->sectionsMoved() && tl.row() != br.row() )
398  setSelection( visualRect( tl ) | visualRect( br ), command );
399  else
400  mFeatureSelectionModel->selectFeatures( QItemSelection( tl, br ), command );
401  }
402 }
403 
404 void QgsAttributeTableView::showHorizontalSortIndicator()
405 {
406  horizontalHeader()->setSortIndicatorShown( true );
407 }
408 
409 void QgsAttributeTableView::actionTriggered()
410 {
411  QAction *action = qobject_cast<QAction *>( sender() );
412  QgsFeatureId fid = action->property( "fid" ).toLongLong();
413 
414  QgsFeature f;
415  mFilterModel->layerCache()->getFeatures( QgsFeatureRequest( fid ) ).nextFeature( f );
416 
417  if ( action->data().toString() == QLatin1String( "user_action" ) )
418  {
419  mFilterModel->layer()->actions()->doAction( action->property( "action_id" ).toString(), f );
420  }
421  else if ( action->data().toString() == QLatin1String( "map_layer_action" ) )
422  {
423  QObject *object = action->property( "action" ).value<QObject *>();
424  QgsMapLayerAction *layerAction = qobject_cast<QgsMapLayerAction *>( object );
425  if ( layerAction )
426  {
427  layerAction->triggerForFeature( mFilterModel->layer(), &f );
428  }
429  }
430 }
431 
432 void QgsAttributeTableView::columnSizeChanged( int index, int oldWidth, int newWidth )
433 {
434  Q_UNUSED( oldWidth )
435  emit columnResized( index, newWidth );
436 }
437 
438 void QgsAttributeTableView::onActionColumnItemPainted( const QModelIndex &index )
439 {
440  if ( !indexWidget( index ) )
441  {
442  QWidget *widget = createActionWidget( mFilterModel->data( index, QgsAttributeTableModel::FeatureIdRole ).toLongLong() );
443  mActionWidgets.insert( index, widget );
444  setIndexWidget( index, widget );
445  }
446 }
447 
448 void QgsAttributeTableView::recreateActionWidgets()
449 {
450  QMap< QModelIndex, QWidget * > newWidgets;
451  QMap< QModelIndex, QWidget * >::const_iterator it = mActionWidgets.constBegin();
452  for ( ; it != mActionWidgets.constEnd(); ++it )
453  {
454  it.value()->deleteLater(); //?
455  QWidget *widget = createActionWidget( mFilterModel->data( it.key(), QgsAttributeTableModel::FeatureIdRole ).toLongLong() );
456  newWidgets.insert( it.key(), widget );
457  setIndexWidget( it.key(), widget );
458  }
459  mActionWidgets = newWidgets;
460 }
QgsActionManager * actions()
Get all layer actions defined on this layer.
QgsVectorLayer * layer() const
Returns the layer this filter acts on.
virtual QVariant data(const QModelIndex &index, int role) const override
static unsigned index
Provides a table view of features of a QgsVectorLayer.
virtual bool isSelected(QgsFeatureId fid)
Returns the selection status of a given feature id.
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)
This class is a composition of two QSettings instances:
Definition: qgssettings.h:51
QList< QgsAction > actions(const QString &actionScope=QString()) const
Return 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.
virtual 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:136
QString shortTitle() const
The short title is used to label user interface elements like buttons.
Definition: qgsaction.h:96
void doAction(const QUuid &actionId, const QgsFeature &feature, int defaultValueIndex=0)
Does the given values.
virtual void selectAll() override
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.
Get the feature id of the feature in this row.
ActionWidgetStyle actionWidgetStyle() const
Get 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:34
virtual void selectFeatures(const QItemSelection &selection, SelectionFlags command)
Select features on this table.
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:116
QString name() const
The name of the action. This may be a longer description.
Definition: qgsaction.h:93
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 mouseMoveEvent(QMouseEvent *event) override
Called for mouse move events on a table cell.
QVector< ColumnConfig > columns() const
Get the list with all columns and their configuration.
QgsAttributeTableConfig attributeTableConfig() const
Get the attribute table configuration object.
int width
Width of column, or -1 for default width.
static QgsMapLayerActionRegistry * instance()
Returns the instance pointer, creating the object on the first call.
A tool button with a dropdown 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...
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), const Section section=Section::NoSection) const
Returns the value for setting key.
Defines the configuration of a column in the attribute table.
qint64 QgsFeatureId
Definition: qgsfeature.h:33
virtual void selectRow(int row)
QUuid id() const
Returns a unique id for this action.
Definition: qgsaction.h:103
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.
void setValue(const QString &key, const QVariant &value, const Section section=Section::NoSection)
Sets the value of setting key to value.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &featureRequest=QgsFeatureRequest())
Query this VectorLayerCache for features.
void triggerForFeature(QgsMapLayer *layer, const QgsFeature *feature)
Triggers the action with the specified layer and feature.
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.
QgsVectorLayerCache * layerCache() const
Returns the layerCache this filter acts on.