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