QGIS API Documentation  2.8.6-Wien
qgsdualview.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsdualview.cpp
3  --------------------------------------
4  Date : 10.2.2013
5  Copyright : (C) 2013 Matthias Kuhn
6  Email : matthias dot kuhn at gmx dot ch
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 "qgsapplication.h"
17 #include "qgsattributeaction.h"
18 #include "qgsattributeform.h"
19 #include "qgsattributetablemodel.h"
20 #include "qgsdualview.h"
22 #include "qgsfeaturelistmodel.h"
24 #include "qgsmapcanvas.h"
26 #include "qgsmessagelog.h"
27 #include "qgsvectordataprovider.h"
28 #include "qgsvectordataprovider.h"
29 #include "qgsvectorlayercache.h"
30 
31 #include <QDialog>
32 #include <QMenu>
33 #include <QMessageBox>
34 #include <QProgressDialog>
35 
36 QgsDualView::QgsDualView( QWidget* parent )
37  : QStackedWidget( parent )
38  , mEditorContext()
39  , mMasterModel( 0 )
40  , mFilterModel( 0 )
41  , mFeatureListModel( 0 )
42  , mAttributeForm( 0 )
43  , mLayerCache( 0 )
44  , mProgressDlg( 0 )
45  , mFeatureSelectionManager( 0 )
46 {
47  setupUi( this );
48 
49  mPreviewActionMapper = new QSignalMapper( this );
50 
51  mPreviewColumnsMenu = new QMenu( this );
52  mActionPreviewColumnsMenu->setMenu( mPreviewColumnsMenu );
53 
54  // Set preview icon
55  mActionExpressionPreview->setIcon( QgsApplication::getThemeIcon( "/mIconExpressionPreview.svg" ) );
56 
57  // Connect layer list preview signals
58  connect( mActionExpressionPreview, SIGNAL( triggered() ), SLOT( previewExpressionBuilder() ) );
59  connect( mPreviewActionMapper, SIGNAL( mapped( QObject* ) ), SLOT( previewColumnChanged( QObject* ) ) );
60  connect( mFeatureList, SIGNAL( displayExpressionChanged( QString ) ), this, SLOT( previewExpressionChanged( QString ) ) );
61 }
62 
63 void QgsDualView::init( QgsVectorLayer* layer, QgsMapCanvas* mapCanvas, const QgsFeatureRequest &request, const QgsAttributeEditorContext &context )
64 {
65  mEditorContext = context;
66 
67  connect( mTableView, SIGNAL( willShowContextMenu( QMenu*, QModelIndex ) ), this, SLOT( viewWillShowContextMenu( QMenu*, QModelIndex ) ) );
68 
69  initLayerCache( layer, request.filterType() == QgsFeatureRequest::FilterRect );
70  initModels( mapCanvas, request );
71 
72  mTableView->setModel( mFilterModel );
73  mFeatureList->setModel( mFeatureListModel );
74  mAttributeForm = new QgsAttributeForm( layer, QgsFeature(), mEditorContext );
75  mAttributeEditorScrollArea->setLayout( new QGridLayout() );
76  mAttributeEditorScrollArea->layout()->addWidget( mAttributeForm );
77  mAttributeEditorScrollArea->setWidget( mAttributeForm );
78 
79  mAttributeForm->hideButtonBox();
80 
81  connect( mAttributeForm, SIGNAL( attributeChanged( QString, QVariant ) ), this, SLOT( featureFormAttributeChanged() ) );
82  connect( mMasterModel, SIGNAL( modelChanged() ), mAttributeForm, SLOT( refreshFeature() ) );
83 
84  if ( mFeatureListPreviewButton->defaultAction() )
85  mFeatureList->setDisplayExpression( mDisplayExpression );
86  else
87  columnBoxInit();
88 
89  mFeatureList->setEditSelection( QgsFeatureIds() << mFeatureListModel->idxToFid( mFeatureListModel->index( 0, 0 ) ) );
90 }
91 
93 {
94  // load fields
95  QList<QgsField> fields = mLayerCache->layer()->pendingFields().toList();
96 
97  QString defaultField;
98 
99  // default expression: saved value
100  QString displayExpression = mLayerCache->layer()->displayExpression();
101 
102  // if no display expression is saved: use display field instead
103  if ( displayExpression == "" )
104  {
105  if ( mLayerCache->layer()->displayField() != "" )
106  {
107  defaultField = mLayerCache->layer()->displayField();
108  displayExpression = QString( "COALESCE(\"%1\", '<NULL>')" ).arg( defaultField );
109  }
110  }
111 
112  // if neither diaplay expression nor display field is saved...
113  if ( displayExpression == "" )
114  {
115  QgsAttributeList pkAttrs = mLayerCache->layer()->pendingPkAttributesList();
116 
117  if ( pkAttrs.size() > 0 )
118  {
119  if ( pkAttrs.size() == 1 )
120  defaultField = pkAttrs.at( 0 );
121 
122  // ... If there are primary key(s) defined
123  QStringList pkFields;
124 
125  Q_FOREACH ( int attr, pkAttrs )
126  {
127  pkFields.append( "COALESCE(\"" + fields[attr].name() + "\", '<NULL>')" );
128  }
129 
130  displayExpression = pkFields.join( "||', '||" );
131  }
132  else if ( fields.size() > 0 )
133  {
134  if ( fields.size() == 1 )
135  defaultField = fields.at( 0 ).name();
136 
137  // ... concat all fields
138  QStringList fieldNames;
139  foreach ( QgsField field, fields )
140  {
141  fieldNames.append( "COALESCE(\"" + field.name() + "\", '<NULL>')" );
142  }
143 
144  displayExpression = fieldNames.join( "||', '||" );
145  }
146  else
147  {
148  // ... there isn't really much to display
149  displayExpression = "'[Please define preview text]'";
150  }
151  }
152 
153  mFeatureListPreviewButton->addAction( mActionExpressionPreview );
154  mFeatureListPreviewButton->addAction( mActionPreviewColumnsMenu );
155 
156  Q_FOREACH ( const QgsField& field, fields )
157  {
158  int fieldIndex = mLayerCache->layer()->fieldNameIndex( field.name() );
159  if ( fieldIndex == -1 )
160  continue;
161 
162  if ( mLayerCache->layer()->editorWidgetV2( fieldIndex ) != "Hidden" )
163  {
164  QIcon icon = QgsApplication::getThemeIcon( "/mActionNewAttribute.png" );
165  QString text = field.name();
166 
167  // Generate action for the preview popup button of the feature list
168  QAction* previewAction = new QAction( icon, text, mFeatureListPreviewButton );
169  mPreviewActionMapper->setMapping( previewAction, previewAction );
170  connect( previewAction, SIGNAL( triggered() ), mPreviewActionMapper, SLOT( map() ) );
171  mPreviewColumnsMenu->addAction( previewAction );
172 
173  if ( text == defaultField )
174  {
175  mFeatureListPreviewButton->setDefaultAction( previewAction );
176  }
177  }
178  }
179 
180  // If there is no single field found as preview
181  if ( !mFeatureListPreviewButton->defaultAction() )
182  {
183  mFeatureList->setDisplayExpression( displayExpression );
184  mFeatureListPreviewButton->setDefaultAction( mActionExpressionPreview );
185  mDisplayExpression = mFeatureList->displayExpression();
186  }
187  else
188  {
189  mFeatureListPreviewButton->defaultAction()->trigger();
190  }
191 }
192 
194 {
195  setCurrentIndex( view );
196 }
197 
199 {
200  mFilterModel->setFilterMode( filterMode );
201  emit filterChanged();
202 }
203 
204 void QgsDualView::setSelectedOnTop( bool selectedOnTop )
205 {
206  mFilterModel->setSelectedOnTop( selectedOnTop );
207 }
208 
209 void QgsDualView::initLayerCache( QgsVectorLayer* layer, bool cacheGeometry )
210 {
211  // Initialize the cache
212  QSettings settings;
213  int cacheSize = settings.value( "/qgis/attributeTableRowCache", "10000" ).toInt();
214  mLayerCache = new QgsVectorLayerCache( layer, cacheSize, this );
215  mLayerCache->setCacheGeometry( cacheGeometry );
216  if ( 0 == cacheSize || 0 == ( QgsVectorDataProvider::SelectAtId & mLayerCache->layer()->dataProvider()->capabilities() ) )
217  {
218  connect( mLayerCache, SIGNAL( progress( int, bool & ) ), this, SLOT( progress( int, bool & ) ) );
219  connect( mLayerCache, SIGNAL( finished() ), this, SLOT( finished() ) );
220 
221  mLayerCache->setFullCache( true );
222  }
223 }
224 
225 void QgsDualView::initModels( QgsMapCanvas* mapCanvas, const QgsFeatureRequest& request )
226 {
227  delete mFeatureListModel;
228  delete mFilterModel;
229  delete mMasterModel;
230 
231  mMasterModel = new QgsAttributeTableModel( mLayerCache, this );
232  mMasterModel->setRequest( request );
233  mMasterModel->setEditorContext( mEditorContext );
234 
235  connect( mMasterModel, SIGNAL( progress( int, bool & ) ), this, SLOT( progress( int, bool & ) ) );
236  connect( mMasterModel, SIGNAL( finished() ), this, SLOT( finished() ) );
237 
238  mMasterModel->loadLayer();
239 
240  mFilterModel = new QgsAttributeTableFilterModel( mapCanvas, mMasterModel, mMasterModel );
241 
242  connect( mFeatureList, SIGNAL( displayExpressionChanged( QString ) ), this, SIGNAL( displayExpressionChanged( QString ) ) );
243 
244  mFeatureListModel = new QgsFeatureListModel( mFilterModel, mFilterModel );
245 }
246 
247 void QgsDualView::on_mFeatureList_aboutToChangeEditSelection( bool& ok )
248 {
249  if ( mLayerCache->layer()->isEditable() && !mAttributeForm->save() )
250  ok = false;
251 }
252 
253 void QgsDualView::on_mFeatureList_currentEditSelectionChanged( const QgsFeature &feat )
254 {
255  if ( !mLayerCache->layer()->isEditable() || mAttributeForm->save() )
256  {
257  mAttributeForm->setFeature( feat );
259  }
260  else
261  {
262  // Couldn't save feature
263  }
264 }
265 
267 {
268  mFeatureList->setCurrentFeatureEdited( false );
269  mFeatureList->setEditSelection( fids );
270 }
271 
273 {
274  return mAttributeForm->save();
275 }
276 
277 void QgsDualView::previewExpressionBuilder()
278 {
279  // Show expression builder
280  QgsExpressionBuilderDialog dlg( mLayerCache->layer(), mFeatureList->displayExpression(), this );
281  dlg.setWindowTitle( tr( "Expression based preview" ) );
282  dlg.setExpressionText( mFeatureList->displayExpression() );
283 
284  if ( dlg.exec() == QDialog::Accepted )
285  {
286  mFeatureList->setDisplayExpression( dlg.expressionText() );
287  mFeatureListPreviewButton->setDefaultAction( mActionExpressionPreview );
288  mFeatureListPreviewButton->setPopupMode( QToolButton::MenuButtonPopup );
289  }
290 
291  mDisplayExpression = mFeatureList->displayExpression();
292 }
293 
294 void QgsDualView::previewColumnChanged( QObject* action )
295 {
296  QAction* previewAction = qobject_cast< QAction* >( action );
297 
298  if ( previewAction )
299  {
300  if ( !mFeatureList->setDisplayExpression( QString( "COALESCE( \"%1\", '<NULL>' )" ).arg( previewAction->text() ) ) )
301  {
302  QMessageBox::warning( this,
303  tr( "Could not set preview column" ),
304  tr( "Could not set column '%1' as preview column.\nParser error:\n%2" )
305  .arg( previewAction->text() )
306  .arg( mFeatureList->parserErrorString() )
307  );
308  }
309  else
310  {
311  mFeatureListPreviewButton->setDefaultAction( previewAction );
312  mFeatureListPreviewButton->setPopupMode( QToolButton::InstantPopup );
313  }
314  }
315 
316  mDisplayExpression = mFeatureList->displayExpression();
317 
318  Q_ASSERT( previewAction );
319 }
320 
322 {
323  return mMasterModel->rowCount();
324 }
325 
327 {
328  return mFilterModel->rowCount();
329 }
330 
331 void QgsDualView::viewWillShowContextMenu( QMenu* menu, QModelIndex atIndex )
332 {
333  QModelIndex sourceIndex = mFilterModel->mapToSource( atIndex );
334 
335  //add user-defined actions to context menu
336  if ( mLayerCache->layer()->actions()->size() != 0 )
337  {
338 
339  QAction *a = menu->addAction( tr( "Run layer action" ) );
340  a->setEnabled( false );
341 
342  for ( int i = 0; i < mLayerCache->layer()->actions()->size(); i++ )
343  {
344  const QgsAction &action = mLayerCache->layer()->actions()->at( i );
345 
346  if ( !action.runable() )
347  continue;
348 
349  QgsAttributeTableAction *a = new QgsAttributeTableAction( action.name(), this, i, sourceIndex );
350  menu->addAction( action.name(), a, SLOT( execute() ) );
351  }
352  }
353 
354  //add actions from QgsMapLayerActionRegistry to context menu
355  QList<QgsMapLayerAction *> registeredActions = QgsMapLayerActionRegistry::instance()->mapLayerActions( mLayerCache->layer() );
356  if ( registeredActions.size() > 0 )
357  {
358  //add a separator between user defined and standard actions
359  menu->addSeparator();
360 
361  QList<QgsMapLayerAction*>::iterator actionIt;
362  for ( actionIt = registeredActions.begin(); actionIt != registeredActions.end(); ++actionIt )
363  {
364  QgsAttributeTableMapLayerAction *a = new QgsAttributeTableMapLayerAction(( *actionIt )->text(), this, ( *actionIt ), sourceIndex );
365  menu->addAction(( *actionIt )->text(), a, SLOT( execute() ) );
366  }
367  }
368 
369  menu->addSeparator();
370  QgsAttributeTableAction *a = new QgsAttributeTableAction( tr( "Open form" ), this, -1, sourceIndex );
371  menu->addAction( tr( "Open form" ), a, SLOT( featureForm() ) );
372 }
373 
374 void QgsDualView::previewExpressionChanged( const QString expression )
375 {
376  mLayerCache->layer()->setDisplayExpression( expression );
377 }
378 
379 void QgsDualView::featureFormAttributeChanged()
380 {
381  mFeatureList->setCurrentFeatureEdited( true );
382 }
383 
385 {
386  mFilterModel->setFilteredFeatures( filteredFeatures );
387 }
388 
390 {
391  mMasterModel->setRequest( request );
392 }
393 
395 {
396  mTableView->setFeatureSelectionManager( featureSelectionManager );
397  // mFeatureList->setFeatureSelectionManager( featureSelectionManager );
398 
399  if ( mFeatureSelectionManager && mFeatureSelectionManager->parent() == this )
400  delete mFeatureSelectionManager;
401 
402  mFeatureSelectionManager = featureSelectionManager;
403 }
404 
405 void QgsDualView::progress( int i, bool& cancel )
406 {
407  if ( !mProgressDlg )
408  {
409  mProgressDlg = new QProgressDialog( tr( "Loading features..." ), tr( "Abort" ), 0, 0, this );
410  mProgressDlg->setWindowTitle( tr( "Attribute table" ) );
411  mProgressDlg->setWindowModality( Qt::WindowModal );
412  mProgressDlg->show();
413  }
414 
415  mProgressDlg->setValue( i );
416  mProgressDlg->setLabelText( tr( "%1 features loaded." ).arg( i ) );
417 
418  QCoreApplication::processEvents();
419 
420  cancel = mProgressDlg->wasCanceled();
421 }
422 
423 void QgsDualView::finished()
424 {
425  delete mProgressDlg;
426  mProgressDlg = 0;
427 }
428 
429 /*
430  * QgsAttributeTableAction
431  */
432 
434 {
435  mDualView->masterModel()->executeAction( mAction, mFieldIdx );
436 }
437 
439 {
440  QgsFeatureIds editedIds;
441  editedIds << mDualView->masterModel()->rowToId( mFieldIdx.row() );
442  mDualView->setCurrentEditSelection( editedIds );
443  mDualView->setView( QgsDualView::AttributeEditor );
444 }
445 
446 /*
447  * QgsAttributeTableMapLayerAction
448  */
449 
451 {
452  mDualView->masterModel()->executeMapLayerAction( mAction, mFieldIdx );
453 }
QList< QgsField > toList() const
Utility function to return a list of QgsField instances.
Definition: qgsfield.cpp:255
QgsFeatureId id() const
Get the feature id for this feature.
Definition: qgsfeature.cpp:100
void setRequest(const QgsFeatureRequest &request)
Set a request that will be used to fill this attribute table model.
const QString & name() const
Gets the name of the field.
Definition: qgsfield.cpp:60
void setFilterMode(QgsAttributeTableFilterModel::FilterMode filterMode)
Set the filter mode.
virtual void loadLayer()
Loads the layer into the model Preferably to be called, before basing any other models on this model...
void setExpressionText(const QString &text)
void setSelectedOnTop(bool selectedOnTop)
Changes the sort order of the features.
void setFilterMode(FilterMode filterMode)
Set the filter mode the filter will use.
QgsAttributeAction * actions()
void setFeatureSelectionManager(QgsIFeatureSelectionManager *featureSelectionManager)
void hideButtonBox()
Hides the button box (Ok/Cancel) and enables auto-commit.
QSet< QgsFeatureId > QgsFeatureIds
Definition: qgsfeature.h:360
This class contains context information for attribute editor widgets.
static QIcon getThemeIcon(const QString &theName)
Helper to get a theme icon.
bool save()
Save all the values from the editors to the layer.
ViewMode
The view modes, in which this widget can present information.
Definition: qgsdualview.h:52
QgsVectorLayer * layer()
Returns the layer to which this cache belongs.
int filteredFeatureCount()
Returns the number of features which are currently visible, according to the filter restrictions...
QgsDualView(QWidget *parent=0)
Constructor.
Definition: qgsdualview.cpp:36
void setDisplayExpression(const QString &displayExpression)
Set the preview expression, used to create a human readable preview string.
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:156
A model backed by a QgsVectorLayerCache which is able to provide feature/attribute information to a Q...
void setCurrentEditSelection(const QgsFeatureIds &fids)
Set the current edit selection in the AttributeEditor mode.
const QString displayExpression()
Get the preview expression, used to create a human readable preview string.
virtual bool isEditable() const override
Returns true if the provider is in editing mode.
Map canvas is a class for displaying all GIS data types on a canvas.
Definition: qgsmapcanvas.h:105
void setView(ViewMode view)
Change the current view mode.
Show a list of the features, where one can be chosen and the according attribute dialog will be prese...
Definition: qgsdualview.h:63
virtual QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const override
QString name() const
The name of the action.
QList< QgsMapLayerAction * > mapLayerActions(QgsMapLayer *layer, QgsMapLayerAction::Targets targets=QgsMapLayerAction::AllActions)
Returns the map layer actions which can run on the specified layer.
void setFeature(const QgsFeature &feature)
Update all editors to correspond to a different feature.
virtual int capabilities() const
Returns a bitmask containing the supported capabilities Note, some capabilities may change depending ...
void setEditorContext(const QgsAttributeEditorContext &context)
Sets the context in which this table is shown.
const QString editorWidgetV2(int fieldIdx) const
Get the id for the editor widget used to represent the field at the given index.
Utility class that encapsulates an action based on vector attributes.
void setFilteredFeatures(QgsFeatureIds filteredFeatures)
Set a list of currently visible features.
void displayExpressionChanged(const QString expression)
Is emitted, whenever the display expression is successfully changed.
This class wraps a request for features to a vector layer (or directly its vector data provider)...
QList< int > QgsAttributeList
void init(QgsVectorLayer *layer, QgsMapCanvas *mapCanvas, const QgsFeatureRequest &request=QgsFeatureRequest(), const QgsAttributeEditorContext &context=QgsAttributeEditorContext())
Has to be called to initialize the dual view.
Definition: qgsdualview.cpp:63
QgsFeatureIds filteredFeatures()
Definition: qgsdualview.h:130
const QString displayField() const
Returns the primary display field name used in the identify results dialog.
Filter using a rectangle, no need to set NoGeometry.
void filterChanged()
Is emitted, whenever the filter changes.
FilterType filterType() const
QgsAction & at(int idx)
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:33
fast access to features using their ID
This class caches features of a given QgsVectorLayer.
bool saveEditChanges()
saveEditChanges
void setRequest(const QgsFeatureRequest &request)
QgsAttributeTableFilterModel::FilterMode filterMode()
Definition: qgsdualview.h:97
void setSelectedOnTop(bool selectedOnTop)
Toggle the selectedOnTop flag.
static QgsMapLayerActionRegistry * instance()
Returns the instance pointer, creating the object on the first call.
virtual int rowCount(const QModelIndex &parent=QModelIndex()) const override
Returns the number of rows.
void setCacheGeometry(bool cacheGeometry)
Enable or disable the caching of geometries.
void setFullCache(bool fullCache)
This enables or disables full caching.
QgsFeatureId idxToFid(const QModelIndex &index) const
bool runable() const
Whether the action is runable on the current platform.
const QgsFields & pendingFields() const
returns field list in the to-be-committed state
QgsVectorDataProvider * dataProvider()
Returns the data provider.
QgsAttributeList pendingPkAttributesList()
returns list of attribute making up the primary key
Is an interface class to abstract feature selection handling.
Represents a vector layer which manages a vector based data sets.
int fieldNameIndex(const QString &fieldName) const
Returns the index of a field name or -1 if the field does not exist.
virtual void setFilteredFeatures(QgsFeatureIds ids)
Specify a list of features, which the filter will accept.
A generic dialog for building expression strings.
void columnBoxInit()
Initializes widgets which depend on the attributes of this layer.
Definition: qgsdualview.cpp:92
int featureCount()
Returns the number of features on the layer.
#define tr(sourceText)