QGIS API Documentation  2.18.21-Las Palmas (9fba24a)
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 
38  : QTableView( parent )
39  , mFilterModel( nullptr )
40  , mFeatureSelectionModel( nullptr )
41  , mFeatureSelectionManager( nullptr )
42  , mActionPopup( nullptr )
43  , mRowSectionAnchor( 0 )
44  , mCtrlDragSelectionFlag( QItemSelectionModel::Select )
45 {
46  QSettings settings;
47  restoreGeometry( settings.value( "/BetterAttributeTable/geometry" ).toByteArray() );
48 
49  //verticalHeader()->setDefaultSectionSize( 20 );
51 
52  // We need mouse move events to create the action button on hover
53  mTableDelegate = new QgsAttributeTableDelegate( this );
54  setItemDelegate( mTableDelegate );
55 
56  setEditTriggers( QAbstractItemView::AllEditTriggers );
57 
58  setSelectionBehavior( QAbstractItemView::SelectRows );
59  setSelectionMode( QAbstractItemView::ExtendedSelection );
60  setSortingEnabled( true ); // At this point no data is in the model yet, so actually nothing is sorted.
61  horizontalHeader()->setSortIndicatorShown( false ); // So hide the indicator to avoid confusion.
62 
64 
65  connect( verticalHeader(), SIGNAL( sectionPressed( int ) ), this, SLOT( selectRow( int ) ) );
66  connect( verticalHeader(), SIGNAL( sectionEntered( int ) ), this, SLOT( _q_selectRow( int ) ) );
67  connect( horizontalHeader(), SIGNAL( sectionResized( int, int, int ) ), this, SLOT( columnSizeChanged( int, int, int ) ) );
68  connect( horizontalHeader(), SIGNAL( sortIndicatorChanged( int, Qt::SortOrder ) ), this, SLOT( showHorizontalSortIndicator() ) );
69  connect( QgsMapLayerActionRegistry::instance(), SIGNAL( changed() ), this, SLOT( recreateActionWidgets() ) );
70 }
71 
73 {
74  if ( object == verticalHeader()->viewport() )
75  {
76  switch ( event->type() )
77  {
78  case QEvent::MouseButtonPress:
79  mFeatureSelectionModel->enableSync( false );
80  break;
81 
82  case QEvent::MouseButtonRelease:
83  mFeatureSelectionModel->enableSync( true );
84  break;
85 
86  default:
87  break;
88  }
89  }
90  return false;
91 }
92 
94 {
95  int i = 0;
96  Q_FOREACH ( const QgsAttributeTableConfig::ColumnConfig& columnConfig, config.columns() )
97  {
98  if ( columnConfig.hidden )
99  continue;
100 
101  if ( columnConfig.width >= 0 )
102  {
103  setColumnWidth( i, columnConfig.width );
104  }
105  else
106  {
107  setColumnWidth( i, horizontalHeader()->defaultSectionSize() );
108  }
109  i++;
110  }
111 }
112 
114 {
115  mFilterModel = filterModel;
116  QTableView::setModel( filterModel );
117 
118  if ( mFilterModel )
119  {
120  connect( mFilterModel, SIGNAL( destroyed() ), this, SLOT( modelDeleted() ) );
121  connect( mTableDelegate, SIGNAL( actionColumnItemPainted( QModelIndex ) ), this, SLOT( onActionColumnItemPainted( QModelIndex ) ) );
122  }
123 
124  delete mFeatureSelectionModel;
125  mFeatureSelectionModel = nullptr;
126 
127  if ( filterModel )
128  {
129  if ( !mFeatureSelectionManager )
130  {
131  mFeatureSelectionManager = new QgsVectorLayerSelectionManager( mFilterModel->layer(), mFilterModel );
132  }
133 
134  mFeatureSelectionModel = new QgsFeatureSelectionModel( mFilterModel, mFilterModel, mFeatureSelectionManager, mFilterModel );
135  setSelectionModel( mFeatureSelectionModel );
136  mTableDelegate->setFeatureSelectionModel( mFeatureSelectionModel );
137  connect( mFeatureSelectionModel, SIGNAL( requestRepaint( QModelIndexList ) ), this, SLOT( repaintRequested( QModelIndexList ) ) );
138  connect( mFeatureSelectionModel, SIGNAL( requestRepaint() ), this, SLOT( repaintRequested() ) );
139  }
140 }
141 
143 {
144  if ( mFeatureSelectionManager )
145  delete mFeatureSelectionManager;
146 
147  mFeatureSelectionManager = featureSelectionManager;
148 
149  if ( mFeatureSelectionModel )
150  mFeatureSelectionModel->setFeatureSelectionManager( mFeatureSelectionManager );
151 }
152 
153 QWidget* QgsAttributeTableView::createActionWidget( QgsFeatureId fid )
154 {
155  QgsAttributeTableConfig attributeTableConfig = mFilterModel->layer()->attributeTableConfig();
156 
157  QToolButton* toolButton = nullptr;
158  QWidget* container = nullptr;
159 
160  if ( attributeTableConfig.actionWidgetStyle() == QgsAttributeTableConfig::DropDown )
161  {
162  toolButton = new QToolButton();
163  toolButton->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
164  toolButton->setPopupMode( QToolButton::MenuButtonPopup );
165  container = toolButton;
166  }
167  else
168  {
169  container = new QWidget();
170  container->setLayout( new QHBoxLayout() );
171  container->layout()->setMargin( 0 );
172  }
173 
174  QList< QAction* > actionList;
175  QAction* defaultAction = nullptr;
176 
177  // first add user created layer actions
178  QgsActionManager* actions = mFilterModel->layer()->actions();
179  for ( int i = 0; i < actions->size(); ++i )
180  {
181  const QgsAction& action = actions->at( i );
182 
183  if ( !action.showInAttributeTable() )
184  continue;
185 
186  QString actionTitle = !action.shortTitle().isEmpty() ? action.shortTitle() : action.icon().isNull() ? action.name() : "";
187  QAction* act = new QAction( action.icon(), actionTitle, container );
188  act->setToolTip( action.name() );
189  act->setData( "user_action" );
190  act->setProperty( "action_id", i );
191  act->setProperty( "fid", fid );
192  connect( act, SIGNAL( triggered( bool ) ), this, SLOT( actionTriggered() ) );
193  actionList << act;
194 
195  if ( actions->defaultAction() == i )
196  defaultAction = act;
197  }
198 
199  // next add any registered actions for this layer
200  Q_FOREACH ( QgsMapLayerAction* mapLayerAction,
201  QgsMapLayerActionRegistry::instance()->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, SIGNAL( triggered() ), this, SLOT( actionTriggered() ) );
210  actionList << action;
211 
212  if ( !defaultAction &&
213  QgsMapLayerActionRegistry::instance()->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  if ( toolButton && !toolButton->actions().isEmpty() && actions->defaultAction() == -1 )
245  toolButton->setDefaultAction( toolButton->actions().at( 0 ) );
246 
247  return container;
248 }
249 
251 {
252  Q_UNUSED( e );
253  QSettings settings;
254  settings.setValue( "/BetterAttributeTable/geometry", QVariant( saveGeometry() ) );
255 }
256 
258 {
259  setSelectionMode( QAbstractItemView::NoSelection );
261  setSelectionMode( QAbstractItemView::ExtendedSelection );
262 }
263 
265 {
266  setSelectionMode( QAbstractItemView::NoSelection );
268  setSelectionMode( QAbstractItemView::ExtendedSelection );
269 }
270 
272 {
273  setSelectionMode( QAbstractItemView::NoSelection );
275  setSelectionMode( QAbstractItemView::ExtendedSelection );
276 }
277 
279 {
280  switch ( event->key() )
281  {
282 
283  // Default Qt behavior would be to change the selection.
284  // We don't make it that easy for the user to trash his selection.
285  case Qt::Key_Up:
286  case Qt::Key_Down:
287  case Qt::Key_Left:
288  case Qt::Key_Right:
289  setSelectionMode( QAbstractItemView::NoSelection );
290  QTableView::keyPressEvent( event );
291  setSelectionMode( QAbstractItemView::ExtendedSelection );
292  break;
293 
294  default:
295  QTableView::keyPressEvent( event );
296  break;
297  }
298 }
299 
300 void QgsAttributeTableView::repaintRequested( const QModelIndexList& indexes )
301 {
302  Q_FOREACH ( const QModelIndex& index, indexes )
303  {
304  update( index );
305  }
306 }
307 
309 {
310  setDirtyRegion( viewport()->rect() );
311 }
312 
314 {
315  QItemSelection selection;
316  selection.append( QItemSelectionRange( mFilterModel->index( 0, 0 ), mFilterModel->index( mFilterModel->rowCount() - 1, 0 ) ) );
317  mFeatureSelectionModel->selectFeatures( selection, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows );
318 }
319 
321 {
322  delete mActionPopup;
323  mActionPopup = nullptr;
324 
325  QModelIndex idx = indexAt( event->pos() );
326  if ( !idx.isValid() )
327  {
328  return;
329  }
330 
331  QgsVectorLayer *vlayer = mFilterModel->layer();
332  if ( !vlayer )
333  return;
334 
335  mActionPopup = new QMenu( this );
336 
337  mActionPopup->addAction( tr( "Select All" ), this, SLOT( selectAll() ), QKeySequence::SelectAll );
338 
339  // let some other parts of the application add some actions
340  emit willShowContextMenu( mActionPopup, idx );
341 
342  if ( !mActionPopup->actions().isEmpty() )
343  {
344  mActionPopup->popup( event->globalPos() );
345  }
346 }
347 
349 {
350  selectRow( row, true );
351 }
352 
354 {
355  selectRow( row, false );
356 }
357 
358 void QgsAttributeTableView::modelDeleted()
359 {
360  mFilterModel = nullptr;
361  mFeatureSelectionManager = nullptr;
362  mFeatureSelectionModel = nullptr;
363 }
364 
365 void QgsAttributeTableView::selectRow( int row, bool anchor )
366 {
367  if ( selectionBehavior() == QTableView::SelectColumns
368  || ( selectionMode() == QTableView::SingleSelection
369  && selectionBehavior() == QTableView::SelectItems ) )
370  return;
371 
372  if ( row >= 0 && row < model()->rowCount() )
373  {
374  int column = horizontalHeader()->logicalIndexAt( isRightToLeft() ? viewport()->width() : 0 );
375  QModelIndex index = model()->index( row, column );
377  selectionModel()->setCurrentIndex( index, QItemSelectionModel::NoUpdate );
378  if (( anchor && !( command & QItemSelectionModel::Current ) )
379  || ( selectionMode() == QTableView::SingleSelection ) )
380  mRowSectionAnchor = row;
381 
382  if ( selectionMode() != QTableView::SingleSelection
383  && command.testFlag( QItemSelectionModel::Toggle ) )
384  {
385  if ( anchor )
386  mCtrlDragSelectionFlag = mFeatureSelectionModel->isSelected( index )
387  ? QItemSelectionModel::Deselect : QItemSelectionModel::Select;
388  command &= ~QItemSelectionModel::Toggle;
389  command |= mCtrlDragSelectionFlag;
390  if ( !anchor )
391  command |= QItemSelectionModel::Current;
392  }
393 
394  QModelIndex tl = model()->index( qMin( mRowSectionAnchor, row ), 0 );
395  QModelIndex br = model()->index( qMax( mRowSectionAnchor, row ), model()->columnCount() - 1 );
396  if ( verticalHeader()->sectionsMoved() && tl.row() != br.row() )
397  setSelection( visualRect( tl ) | visualRect( br ), command );
398  else
399  mFeatureSelectionModel->selectFeatures( QItemSelection( tl, br ), command );
400  }
401 }
402 
403 void QgsAttributeTableView::showHorizontalSortIndicator()
404 {
406 }
407 
408 void QgsAttributeTableView::actionTriggered()
409 {
410  QAction* action = qobject_cast<QAction*>( sender() );
411  QgsFeatureId fid = action->property( "fid" ).toLongLong();
412 
413  QgsFeature f;
414  mFilterModel->layerCache()->getFeatures( QgsFeatureRequest( fid ) ).nextFeature( f );
415 
416  if ( action->data().toString() == "user_action" )
417  {
418  mFilterModel->layer()->actions()->doAction( action->property( "action_id" ).toInt(), f );
419  }
420  else if ( action->data().toString() == "map_layer_action" )
421  {
422  QObject* object = action->property( "action" ).value<QObject *>();
423  QgsMapLayerAction* layerAction = qobject_cast<QgsMapLayerAction *>( object );
424  if ( layerAction )
425  {
426  layerAction->triggerForFeature( mFilterModel->layer(), &f );
427  }
428  }
429 }
430 
431 void QgsAttributeTableView::columnSizeChanged( int index, int oldWidth, int newWidth )
432 {
433  Q_UNUSED( oldWidth )
434  emit columnResized( index, newWidth );
435 }
436 
437 void QgsAttributeTableView::onActionColumnItemPainted( const QModelIndex& index )
438 {
439  if ( !indexWidget( index ) )
440  {
441  QWidget* widget = createActionWidget( mFilterModel->data( index, QgsAttributeTableModel::FeatureIdRole ).toLongLong() );
442  mActionWidgets.insert( index, widget );
443  setIndexWidget( index, widget );
444  }
445 }
446 
447 void QgsAttributeTableView::recreateActionWidgets()
448 {
451  for ( ; it != mActionWidgets.constEnd(); ++it )
452  {
453  it.value()->deleteLater(); //?
454  QWidget* widget = createActionWidget( mFilterModel->data( it.key(), QgsAttributeTableModel::FeatureIdRole ).toLongLong() );
455  newWidgets.insert( it.key(), widget );
456  setIndexWidget( it.key(), widget );
457  }
458  mActionWidgets = newWidgets;
459 }
QLayout * layout() const
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const
qlonglong toLongLong(bool *ok) const
QgsActionManager * actions()
Get all layer actions defined on this layer.
QgsVectorLayer * layer() const
Returns the layer this filter acts on.
void setDirtyRegion(const QRegion &region)
virtual QVariant data(const QModelIndex &index, int role) const override
QByteArray toByteArray() const
static unsigned index
void setColumnWidth(int column, int width)
virtual bool isSelected(QgsFeatureId fid)
Returns the selection status of a given feature id.
Type type() const
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const=0
void willShowContextMenu(QMenu *menu, const QModelIndex &atIndex)
Is emitted, in order to provide a hook to add aditional menu entries to the context menu...
virtual void setSelection(const QRect &rect, QFlags< QItemSelectionModel::SelectionFlag > flags)
void setSelectionMode(QAbstractItemView::SelectionMode mode)
QWidget * indexWidget(const QModelIndex &index) const
QgsAttributeTableView(QWidget *parent=nullptr)
QItemSelectionModel * selectionModel() const
void addAction(QAction *action)
void setDefaultAction(QAction *action)
QObject * sender() const
QVariant data() const
int defaultAction() const
Returns the index of the default action, or -1 if no default action is available. ...
void setHighlightSections(bool highlight)
const_iterator constBegin() const
const T & at(int i) const
void addAction(QAction *action)
void setSortingEnabled(bool enable)
T value() const
QWidget * viewport() const
void setSortIndicatorShown(bool show)
void columnResized(int column, int width)
Emitted when a column in the view has been resized.
QHeaderView * verticalHeader() const
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:187
QString shortTitle() const
The short title is used to label user interface elements like buttons.
Definition: qgsaction.h:100
void setSelectionBehavior(QAbstractItemView::SelectionBehavior behavior)
virtual void selectAll() override
const QPoint & globalPos() const
QString tr(const char *sourceText, const char *disambiguation, int n)
void enableSync(bool enable)
Enables or disables synchronisation to the QgsVectorLayer When synchronisation is disabled...
virtual void mouseReleaseEvent(QMouseEvent *event)
void update()
const QgsAction & at(int idx) const
Get the action at the specified index.
virtual int rowCount(const QModelIndex &parent) const
void setFeatureSelectionModel(QgsFeatureSelectionModel *featureSelectionModel)
void setToolTip(const QString &tip)
void mouseReleaseEvent(QMouseEvent *event) override
Called for mouse release events on a table cell.
Get the feature id of the feature in this row.
int width() const
void setValue(const QString &key, const QVariant &value)
bool isValid() const
virtual QModelIndex indexAt(const QPoint &pos) const
ActionWidgetStyle actionWidgetStyle() const
Get the style of the action widget.
void append(const T &value)
virtual void setModel(QAbstractItemModel *model)
virtual void setSelectionModel(QItemSelectionModel *selectionModel)
QVariant property(const char *name) const
void setLayout(QLayout *layout)
void installEventFilter(QObject *filterObj)
void popup(const QPoint &p, QAction *atAction)
int toInt(bool *ok) const
const Key & key() const
bool restoreGeometry(const QByteArray &geometry)
Utility class that encapsulates an action based on vector attributes.
Definition: qgsaction.h:25
bool isEmpty() const
bool isEmpty() const
const_iterator constEnd() const
void setItemDelegate(QAbstractItemDelegate *delegate)
int row() const
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 setEditTriggers(QFlags< QAbstractItemView::EditTrigger > triggers)
Storage and management of actions associated with a layer.
const T & value() const
QIcon icon() const
The icon.
Definition: qgsaction.h:106
QString name() const
The name of the action. This may be a longer description.
Definition: qgsaction.h:97
bool showInAttributeTable() const
Whether this action should be shown on the attribute table.
Definition: qgsaction.h:118
void setMargin(int margin)
virtual void setModel(QgsAttributeTableFilterModel *filterModel)
void addWidget(QWidget *w)
QRect rect() const
void setData(const QVariant &userData)
int key() const
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.
virtual QRect visualRect(const QModelIndex &index) const=0
virtual void mouseMoveEvent(QMouseEvent *event)
virtual QItemSelectionModel::SelectionFlags selectionCommand(const QModelIndex &index, const QEvent *event) const
QgsAttributeTableConfig attributeTableConfig() const
Get the attribute table configuration object.
int logicalIndexAt(int position) const
QVariant value(const QString &key, const QVariant &defaultValue) const
virtual bool event(QEvent *event)
int width
Width of column, or -1 for default width.
virtual void selectFeatures(const QItemSelection &selection, const SelectionFlags &command)
Select features on this table.
const QPoint & pos() const
static QgsMapLayerActionRegistry * instance()
Returns the instance pointer, creating the object on the first call.
QByteArray saveGeometry() const
virtual void mousePressEvent(QMouseEvent *event)
A tool button with a dropdown to select the current action.
bool isNull() const
void setFeatureSelectionManager(QgsIFeatureSelectionManager *featureSelectionManager)
setFeatureSelectionManager
QWidget(QWidget *parent, QFlags< Qt::WindowType > f)
void setPopupMode(ToolButtonPopupMode mode)
void setIndexWidget(const QModelIndex &index, QWidget *widget)
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:31
virtual void selectRow(int row)
void setCurrentIndex(const QModelIndex &index, QFlags< QItemSelectionModel::SelectionFlag > command)
bool setProperty(const char *name, const QVariant &value)
iterator insert(const Key &key, const T &value)
virtual void keyPressEvent(QKeyEvent *event)
virtual void _q_selectRow(int row)
bool nextFeature(QgsFeature &f)
This is a container for configuration of the attribute table.
QAbstractItemModel * model() const
bool connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QList< QAction * > actions() const
Is an interface class to abstract feature selection handling.
void closeEvent(QCloseEvent *event) override
Saves geometry to the settings on close.
void setToolButtonStyle(Qt::ToolButtonStyle style)
Represents a vector layer which manages a vector based data sets.
int size() const
Get the number of actions managed by this.
QHeaderView * horizontalHeader() const
QgsFeatureIterator getFeatures(const QgsFeatureRequest &featureRequest=QgsFeatureRequest())
Query this VectorLayerCache for features.
QString toString() const
void doAction(int index, const QgsFeature &feat, int defaultValueIndex=0, const QgsExpressionContextScope &scope=QgsExpressionContextScope())
Does the given action.
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 destroyed(QObject *obj)
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.