QGIS API Documentation  3.2.0-Bonn (bc43194)
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 }
108 
110 {
111  mFilterModel = filterModel;
112  QTableView::setModel( mFilterModel );
113 
114  if ( mFilterModel )
115  {
116  connect( mFilterModel, &QObject::destroyed, this, &QgsAttributeTableView::modelDeleted );
117  connect( mTableDelegate, &QgsAttributeTableDelegate::actionColumnItemPainted, this, &QgsAttributeTableView::onActionColumnItemPainted );
118  }
119 
120  delete mFeatureSelectionModel;
121  mFeatureSelectionModel = nullptr;
122 
123  if ( mFilterModel )
124  {
125  if ( !mFeatureSelectionManager )
126  {
127  mFeatureSelectionManager = new QgsVectorLayerSelectionManager( mFilterModel->layer(), mFilterModel );
128  }
129 
130  mFeatureSelectionModel = new QgsFeatureSelectionModel( mFilterModel, mFilterModel, mFeatureSelectionManager, mFilterModel );
131  setSelectionModel( mFeatureSelectionModel );
132  mTableDelegate->setFeatureSelectionModel( mFeatureSelectionModel );
133  connect( mFeatureSelectionModel, static_cast<void ( QgsFeatureSelectionModel::* )( const QModelIndexList &indexes )>( &QgsFeatureSelectionModel::requestRepaint ),
134  this, static_cast<void ( QgsAttributeTableView::* )( const QModelIndexList &indexes )>( &QgsAttributeTableView::repaintRequested ) );
135  connect( mFeatureSelectionModel, static_cast<void ( QgsFeatureSelectionModel::* )()>( &QgsFeatureSelectionModel::requestRepaint ),
136  this, static_cast<void ( QgsAttributeTableView::* )()>( &QgsAttributeTableView::repaintRequested ) );
137 
138  connect( mFilterModel->layer(), &QgsVectorLayer::editingStarted, this, &QgsAttributeTableView::recreateActionWidgets );
139  connect( mFilterModel->layer(), &QgsVectorLayer::editingStopped, this, &QgsAttributeTableView::recreateActionWidgets );
140  connect( mFilterModel->layer(), &QgsVectorLayer::readOnlyChanged, this, &QgsAttributeTableView::recreateActionWidgets );
141  }
142 }
143 
145 {
146  if ( mFeatureSelectionManager )
147  delete mFeatureSelectionManager;
148 
149  mFeatureSelectionManager = featureSelectionManager;
150 
151  if ( mFeatureSelectionModel )
152  mFeatureSelectionModel->setFeatureSelectionManager( mFeatureSelectionManager );
153 }
154 
155 QWidget *QgsAttributeTableView::createActionWidget( QgsFeatureId fid )
156 {
157  QgsAttributeTableConfig attributeTableConfig = mFilterModel->layer()->attributeTableConfig();
158 
159  QToolButton *toolButton = nullptr;
160  QWidget *container = nullptr;
161 
162  if ( attributeTableConfig.actionWidgetStyle() == QgsAttributeTableConfig::DropDown )
163  {
164  toolButton = new QToolButton();
165  toolButton->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
166  toolButton->setPopupMode( QToolButton::MenuButtonPopup );
167  container = toolButton;
168  }
169  else
170  {
171  container = new QWidget();
172  container->setLayout( new QHBoxLayout() );
173  container->layout()->setMargin( 0 );
174  }
175 
176  QList< QAction * > actionList;
177  QAction *defaultAction = nullptr;
178 
179  // first add user created layer actions
180  QList<QgsAction> actions = mFilterModel->layer()->actions()->actions( QStringLiteral( "Feature" ) );
181  Q_FOREACH ( const QgsAction &action, actions )
182  {
183  if ( !mFilterModel->layer()->isEditable() && action.isEnabledOnlyWhenEditable() )
184  continue;
185 
186  QString actionTitle = !action.shortTitle().isEmpty() ? action.shortTitle() : action.icon().isNull() ? action.name() : QLatin1String( "" );
187  QAction *act = new QAction( action.icon(), actionTitle, container );
188  act->setToolTip( action.name() );
189  act->setData( "user_action" );
190  act->setProperty( "fid", fid );
191  act->setProperty( "action_id", action.id() );
192  connect( act, &QAction::triggered, this, &QgsAttributeTableView::actionTriggered );
193  actionList << act;
194 
195  if ( mFilterModel->layer()->actions()->defaultAction( QStringLiteral( "Feature" ) ).id() == action.id() )
196  defaultAction = act;
197  }
198 
199  // next add any registered actions for this layer
200  Q_FOREACH ( QgsMapLayerAction *mapLayerAction,
201  QgsGui::mapLayerActionRegistry()->mapLayerActions( mFilterModel->layer(),
203  {
204  QAction *action = new QAction( mapLayerAction->icon(), mapLayerAction->text(), container );
205  action->setData( "map_layer_action" );
206  action->setToolTip( mapLayerAction->text() );
207  action->setProperty( "fid", fid );
208  action->setProperty( "action", qVariantFromValue( qobject_cast<QObject *>( mapLayerAction ) ) );
209  connect( action, &QAction::triggered, this, &QgsAttributeTableView::actionTriggered );
210  actionList << action;
211 
212  if ( !defaultAction &&
213  QgsGui::mapLayerActionRegistry()->defaultActionForLayer( mFilterModel->layer() ) == mapLayerAction )
214  defaultAction = action;
215  }
216 
217  if ( !defaultAction && !actionList.isEmpty() )
218  defaultAction = actionList.at( 0 );
219 
220  Q_FOREACH ( QAction *act, actionList )
221  {
222  if ( attributeTableConfig.actionWidgetStyle() == QgsAttributeTableConfig::DropDown )
223  {
224  toolButton->addAction( act );
225 
226  if ( act == defaultAction )
227  toolButton->setDefaultAction( act );
228 
229  container = toolButton;
230  }
231  else
232  {
233  QToolButton *btn = new QToolButton;
234  btn->setDefaultAction( act );
235  container->layout()->addWidget( btn );
236  }
237  }
238 
239  if ( attributeTableConfig.actionWidgetStyle() == QgsAttributeTableConfig::ButtonList )
240  {
241  static_cast< QHBoxLayout * >( container->layout() )->addStretch();
242  }
243 
244  // TODO: Rethink default actions
245 #if 0
246  if ( toolButton && !toolButton->actions().isEmpty() && actions->defaultAction() == -1 )
247  toolButton->setDefaultAction( toolButton->actions().at( 0 ) );
248 #endif
249 
250  return container;
251 }
252 
253 void QgsAttributeTableView::closeEvent( QCloseEvent *e )
254 {
255  Q_UNUSED( e );
256  QgsSettings settings;
257  settings.setValue( QStringLiteral( "BetterAttributeTable/geometry" ), QVariant( saveGeometry() ) );
258 }
259 
260 void QgsAttributeTableView::mousePressEvent( QMouseEvent *event )
261 {
262  setSelectionMode( QAbstractItemView::NoSelection );
263  QTableView::mousePressEvent( event );
264  setSelectionMode( QAbstractItemView::ExtendedSelection );
265 }
266 
268 {
269  setSelectionMode( QAbstractItemView::NoSelection );
270  QTableView::mouseReleaseEvent( event );
271  setSelectionMode( QAbstractItemView::ExtendedSelection );
272 }
273 
274 void QgsAttributeTableView::mouseMoveEvent( QMouseEvent *event )
275 {
276  setSelectionMode( QAbstractItemView::NoSelection );
277  QTableView::mouseMoveEvent( event );
278  setSelectionMode( QAbstractItemView::ExtendedSelection );
279 }
280 
281 void QgsAttributeTableView::keyPressEvent( QKeyEvent *event )
282 {
283  switch ( event->key() )
284  {
285 
286  // Default Qt behavior would be to change the selection.
287  // We don't make it that easy for the user to trash his selection.
288  case Qt::Key_Up:
289  case Qt::Key_Down:
290  case Qt::Key_Left:
291  case Qt::Key_Right:
292  setSelectionMode( QAbstractItemView::NoSelection );
293  QTableView::keyPressEvent( event );
294  setSelectionMode( QAbstractItemView::ExtendedSelection );
295  break;
296 
297  default:
298  QTableView::keyPressEvent( event );
299  break;
300  }
301 }
302 
303 void QgsAttributeTableView::repaintRequested( const QModelIndexList &indexes )
304 {
305  Q_FOREACH ( const QModelIndex &index, indexes )
306  {
307  update( index );
308  }
309 }
310 
312 {
313  setDirtyRegion( viewport()->rect() );
314 }
315 
317 {
318  QItemSelection selection;
319  selection.append( QItemSelectionRange( mFilterModel->index( 0, 0 ), mFilterModel->index( mFilterModel->rowCount() - 1, 0 ) ) );
320  mFeatureSelectionModel->selectFeatures( selection, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows );
321 }
322 
323 void QgsAttributeTableView::contextMenuEvent( QContextMenuEvent *event )
324 {
325  delete mActionPopup;
326  mActionPopup = nullptr;
327 
328  QModelIndex idx = indexAt( event->pos() );
329  if ( !idx.isValid() )
330  {
331  return;
332  }
333 
334  QgsVectorLayer *vlayer = mFilterModel->layer();
335  if ( !vlayer )
336  return;
337 
338  mActionPopup = new QMenu( this );
339 
340  mActionPopup->addAction( tr( "Select All" ), this, SLOT( selectAll() ), QKeySequence::SelectAll );
341 
342  // let some other parts of the application add some actions
343  emit willShowContextMenu( mActionPopup, idx );
344 
345  if ( !mActionPopup->actions().isEmpty() )
346  {
347  mActionPopup->popup( event->globalPos() );
348  }
349 }
350 
352 {
353  selectRow( row, true );
354 }
355 
357 {
358  selectRow( row, false );
359 }
360 
361 void QgsAttributeTableView::modelDeleted()
362 {
363  mFilterModel = nullptr;
364  mFeatureSelectionManager = nullptr;
365  mFeatureSelectionModel = nullptr;
366 }
367 
368 void QgsAttributeTableView::selectRow( int row, bool anchor )
369 {
370  if ( selectionBehavior() == QTableView::SelectColumns
371  || ( selectionMode() == QTableView::SingleSelection
372  && selectionBehavior() == QTableView::SelectItems ) )
373  return;
374 
375  if ( row >= 0 && row < model()->rowCount() )
376  {
377  int column = horizontalHeader()->logicalIndexAt( isRightToLeft() ? viewport()->width() : 0 );
378  QModelIndex index = model()->index( row, column );
379  QItemSelectionModel::SelectionFlags command = selectionCommand( index );
380  selectionModel()->setCurrentIndex( index, QItemSelectionModel::NoUpdate );
381  if ( ( anchor && !( command & QItemSelectionModel::Current ) )
382  || ( selectionMode() == QTableView::SingleSelection ) )
383  mRowSectionAnchor = row;
384 
385  if ( selectionMode() != QTableView::SingleSelection
386  && command.testFlag( QItemSelectionModel::Toggle ) )
387  {
388  if ( anchor )
389  mCtrlDragSelectionFlag = mFeatureSelectionModel->isSelected( index )
390  ? QItemSelectionModel::Deselect : QItemSelectionModel::Select;
391  command &= ~QItemSelectionModel::Toggle;
392  command |= mCtrlDragSelectionFlag;
393  if ( !anchor )
394  command |= QItemSelectionModel::Current;
395  }
396 
397  QModelIndex tl = model()->index( std::min( mRowSectionAnchor, row ), 0 );
398  QModelIndex br = model()->index( std::max( mRowSectionAnchor, row ), model()->columnCount() - 1 );
399  if ( verticalHeader()->sectionsMoved() && tl.row() != br.row() )
400  setSelection( visualRect( tl ) | visualRect( br ), command );
401  else
402  mFeatureSelectionModel->selectFeatures( QItemSelection( tl, br ), command );
403  }
404 }
405 
406 void QgsAttributeTableView::showHorizontalSortIndicator()
407 {
408  horizontalHeader()->setSortIndicatorShown( true );
409 }
410 
411 void QgsAttributeTableView::actionTriggered()
412 {
413  QAction *action = qobject_cast<QAction *>( sender() );
414  QgsFeatureId fid = action->property( "fid" ).toLongLong();
415 
416  QgsFeature f;
417  mFilterModel->layerCache()->getFeatures( QgsFeatureRequest( fid ) ).nextFeature( f );
418 
419  if ( action->data().toString() == QLatin1String( "user_action" ) )
420  {
421  mFilterModel->layer()->actions()->doAction( action->property( "action_id" ).toString(), f );
422  }
423  else if ( action->data().toString() == QLatin1String( "map_layer_action" ) )
424  {
425  QObject *object = action->property( "action" ).value<QObject *>();
426  QgsMapLayerAction *layerAction = qobject_cast<QgsMapLayerAction *>( object );
427  if ( layerAction )
428  {
429  layerAction->triggerForFeature( mFilterModel->layer(), &f );
430  }
431  }
432 }
433 
434 void QgsAttributeTableView::columnSizeChanged( int index, int oldWidth, int newWidth )
435 {
436  Q_UNUSED( oldWidth )
437  emit columnResized( index, newWidth );
438 }
439 
440 void QgsAttributeTableView::onActionColumnItemPainted( const QModelIndex &index )
441 {
442  if ( !indexWidget( index ) )
443  {
444  QWidget *widget = createActionWidget( mFilterModel->data( index, QgsAttributeTableModel::FeatureIdRole ).toLongLong() );
445  mActionWidgets.insert( index, widget );
446  setIndexWidget( index, widget );
447  }
448 }
449 
450 void QgsAttributeTableView::recreateActionWidgets()
451 {
452  QMap< QModelIndex, QWidget * >::const_iterator it = mActionWidgets.constBegin();
453  for ( ; it != mActionWidgets.constEnd(); ++it )
454  {
455  // ownership of widget was transferred by initial call to setIndexWidget - clearing
456  // the index widget will delete the old widget safely
457  // they should then be recreated by onActionColumnItemPainted
458  setIndexWidget( it.key(), nullptr );
459  }
460  mActionWidgets.clear();
461 }
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 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
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:62
QString shortTitle() const
The short title is used to label user interface elements like buttons.
Definition: qgsaction.h:127
bool isEditable() const override
Returns true if the provider is in editing mode.
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 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
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.
QgsAttributeTableConfig attributeTableConfig() const
Returns the attribute table configuration object.
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.
qint64 QgsFeatureId
Definition: qgsfeature.h:37
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:67
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.