QGIS API Documentation  2.8.2-Wien
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
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 
83  if ( mFeatureListPreviewButton->defaultAction() )
84  mFeatureList->setDisplayExpression( mDisplayExpression );
85  else
86  columnBoxInit();
87 
88  mFeatureList->setEditSelection( QgsFeatureIds() << mFeatureListModel->idxToFid( mFeatureListModel->index( 0, 0 ) ) );
89 }
90 
92 {
93  // load fields
94  QList<QgsField> fields = mLayerCache->layer()->pendingFields().toList();
95 
96  QString defaultField;
97 
98  // default expression: saved value
99  QString displayExpression = mLayerCache->layer()->displayExpression();
100 
101  // if no display expression is saved: use display field instead
102  if ( displayExpression == "" )
103  {
104  if ( mLayerCache->layer()->displayField() != "" )
105  {
106  defaultField = mLayerCache->layer()->displayField();
107  displayExpression = QString( "COALESCE(\"%1\", '<NULL>')" ).arg( defaultField );
108  }
109  }
110 
111  // if neither diaplay expression nor display field is saved...
112  if ( displayExpression == "" )
113  {
114  QgsAttributeList pkAttrs = mLayerCache->layer()->pendingPkAttributesList();
115 
116  if ( pkAttrs.size() > 0 )
117  {
118  if ( pkAttrs.size() == 1 )
119  defaultField = pkAttrs.at( 0 );
120 
121  // ... If there are primary key(s) defined
122  QStringList pkFields;
123 
124  Q_FOREACH ( int attr, pkAttrs )
125  {
126  pkFields.append( "COALESCE(\"" + fields[attr].name() + "\", '<NULL>')" );
127  }
128 
129  displayExpression = pkFields.join( "||', '||" );
130  }
131  else if ( fields.size() > 0 )
132  {
133  if ( fields.size() == 1 )
134  defaultField = fields.at( 0 ).name();
135 
136  // ... concat all fields
137  QStringList fieldNames;
138  foreach ( QgsField field, fields )
139  {
140  fieldNames.append( "COALESCE(\"" + field.name() + "\", '<NULL>')" );
141  }
142 
143  displayExpression = fieldNames.join( "||', '||" );
144  }
145  else
146  {
147  // ... there isn't really much to display
148  displayExpression = "'[Please define preview text]'";
149  }
150  }
151 
152  mFeatureListPreviewButton->addAction( mActionExpressionPreview );
153  mFeatureListPreviewButton->addAction( mActionPreviewColumnsMenu );
154 
155  Q_FOREACH ( const QgsField& field, fields )
156  {
157  int fieldIndex = mLayerCache->layer()->fieldNameIndex( field.name() );
158  if ( fieldIndex == -1 )
159  continue;
160 
161  if ( mLayerCache->layer()->editorWidgetV2( fieldIndex ) != "Hidden" )
162  {
163  QIcon icon = QgsApplication::getThemeIcon( "/mActionNewAttribute.png" );
164  QString text = field.name();
165 
166  // Generate action for the preview popup button of the feature list
167  QAction* previewAction = new QAction( icon, text, mFeatureListPreviewButton );
168  mPreviewActionMapper->setMapping( previewAction, previewAction );
169  connect( previewAction, SIGNAL( triggered() ), mPreviewActionMapper, SLOT( map() ) );
170  mPreviewColumnsMenu->addAction( previewAction );
171 
172  if ( text == defaultField )
173  {
174  mFeatureListPreviewButton->setDefaultAction( previewAction );
175  }
176  }
177  }
178 
179  // If there is no single field found as preview
180  if ( !mFeatureListPreviewButton->defaultAction() )
181  {
182  mFeatureList->setDisplayExpression( displayExpression );
183  mFeatureListPreviewButton->setDefaultAction( mActionExpressionPreview );
184  mDisplayExpression = mFeatureList->displayExpression();
185  }
186  else
187  {
188  mFeatureListPreviewButton->defaultAction()->trigger();
189  }
190 }
191 
193 {
194  setCurrentIndex( view );
195 }
196 
198 {
199  mFilterModel->setFilterMode( filterMode );
200  emit filterChanged();
201 }
202 
203 void QgsDualView::setSelectedOnTop( bool selectedOnTop )
204 {
205  mFilterModel->setSelectedOnTop( selectedOnTop );
206 }
207 
208 void QgsDualView::initLayerCache( QgsVectorLayer* layer, bool cacheGeometry )
209 {
210  // Initialize the cache
211  QSettings settings;
212  int cacheSize = settings.value( "/qgis/attributeTableRowCache", "10000" ).toInt();
213  mLayerCache = new QgsVectorLayerCache( layer, cacheSize, this );
214  mLayerCache->setCacheGeometry( cacheGeometry );
215  if ( 0 == cacheSize || 0 == ( QgsVectorDataProvider::SelectAtId & mLayerCache->layer()->dataProvider()->capabilities() ) )
216  {
217  connect( mLayerCache, SIGNAL( progress( int, bool & ) ), this, SLOT( progress( int, bool & ) ) );
218  connect( mLayerCache, SIGNAL( finished() ), this, SLOT( finished() ) );
219 
220  mLayerCache->setFullCache( true );
221  }
222 }
223 
224 void QgsDualView::initModels( QgsMapCanvas* mapCanvas, const QgsFeatureRequest& request )
225 {
226  delete mFeatureListModel;
227  delete mFilterModel;
228  delete mMasterModel;
229 
230  mMasterModel = new QgsAttributeTableModel( mLayerCache, this );
231  mMasterModel->setRequest( request );
232  mMasterModel->setEditorContext( mEditorContext );
233 
234  connect( mMasterModel, SIGNAL( progress( int, bool & ) ), this, SLOT( progress( int, bool & ) ) );
235  connect( mMasterModel, SIGNAL( finished() ), this, SLOT( finished() ) );
236 
237  mMasterModel->loadLayer();
238 
239  mFilterModel = new QgsAttributeTableFilterModel( mapCanvas, mMasterModel, mMasterModel );
240 
241  connect( mFeatureList, SIGNAL( displayExpressionChanged( QString ) ), this, SIGNAL( displayExpressionChanged( QString ) ) );
242 
243  mFeatureListModel = new QgsFeatureListModel( mFilterModel, mFilterModel );
244 }
245 
246 void QgsDualView::on_mFeatureList_aboutToChangeEditSelection( bool& ok )
247 {
248  if ( mLayerCache->layer()->isEditable() && !mAttributeForm->save() )
249  ok = false;
250 }
251 
252 void QgsDualView::on_mFeatureList_currentEditSelectionChanged( const QgsFeature &feat )
253 {
254  if ( !mLayerCache->layer()->isEditable() || mAttributeForm->save() )
255  {
256  mAttributeForm->setFeature( feat );
258  }
259  else
260  {
261  // Couldn't save feature
262  }
263 }
264 
266 {
267  mFeatureList->setCurrentFeatureEdited( false );
268  mFeatureList->setEditSelection( fids );
269 }
270 
272 {
273  return mAttributeForm->save();
274 }
275 
276 void QgsDualView::previewExpressionBuilder()
277 {
278  // Show expression builder
279  QgsExpressionBuilderDialog dlg( mLayerCache->layer(), mFeatureList->displayExpression(), this );
280  dlg.setWindowTitle( tr( "Expression based preview" ) );
281  dlg.setExpressionText( mFeatureList->displayExpression() );
282 
283  if ( dlg.exec() == QDialog::Accepted )
284  {
285  mFeatureList->setDisplayExpression( dlg.expressionText() );
286  mFeatureListPreviewButton->setDefaultAction( mActionExpressionPreview );
287  mFeatureListPreviewButton->setPopupMode( QToolButton::MenuButtonPopup );
288  }
289 
290  mDisplayExpression = mFeatureList->displayExpression();
291 }
292 
293 void QgsDualView::previewColumnChanged( QObject* action )
294 {
295  QAction* previewAction = qobject_cast< QAction* >( action );
296 
297  if ( previewAction )
298  {
299  if ( !mFeatureList->setDisplayExpression( QString( "COALESCE( \"%1\", '<NULL>' )" ).arg( previewAction->text() ) ) )
300  {
301  QMessageBox::warning( this,
302  tr( "Could not set preview column" ),
303  tr( "Could not set column '%1' as preview column.\nParser error:\n%2" )
304  .arg( previewAction->text() )
305  .arg( mFeatureList->parserErrorString() )
306  );
307  }
308  else
309  {
310  mFeatureListPreviewButton->setDefaultAction( previewAction );
311  mFeatureListPreviewButton->setPopupMode( QToolButton::InstantPopup );
312  }
313  }
314 
315  mDisplayExpression = mFeatureList->displayExpression();
316 
317  Q_ASSERT( previewAction );
318 }
319 
321 {
322  return mMasterModel->rowCount();
323 }
324 
326 {
327  return mFilterModel->rowCount();
328 }
329 
330 void QgsDualView::viewWillShowContextMenu( QMenu* menu, QModelIndex atIndex )
331 {
332  QModelIndex sourceIndex = mFilterModel->mapToSource( atIndex );
333 
334  //add user-defined actions to context menu
335  if ( mLayerCache->layer()->actions()->size() != 0 )
336  {
337 
338  QAction *a = menu->addAction( tr( "Run layer action" ) );
339  a->setEnabled( false );
340 
341  for ( int i = 0; i < mLayerCache->layer()->actions()->size(); i++ )
342  {
343  const QgsAction &action = mLayerCache->layer()->actions()->at( i );
344 
345  if ( !action.runable() )
346  continue;
347 
348  QgsAttributeTableAction *a = new QgsAttributeTableAction( action.name(), this, i, sourceIndex );
349  menu->addAction( action.name(), a, SLOT( execute() ) );
350  }
351  }
352 
353  //add actions from QgsMapLayerActionRegistry to context menu
354  QList<QgsMapLayerAction *> registeredActions = QgsMapLayerActionRegistry::instance()->mapLayerActions( mLayerCache->layer() );
355  if ( registeredActions.size() > 0 )
356  {
357  //add a separator between user defined and standard actions
358  menu->addSeparator();
359 
360  QList<QgsMapLayerAction*>::iterator actionIt;
361  for ( actionIt = registeredActions.begin(); actionIt != registeredActions.end(); ++actionIt )
362  {
363  QgsAttributeTableMapLayerAction *a = new QgsAttributeTableMapLayerAction(( *actionIt )->text(), this, ( *actionIt ), sourceIndex );
364  menu->addAction(( *actionIt )->text(), a, SLOT( execute() ) );
365  }
366  }
367 
368  menu->addSeparator();
369  QgsAttributeTableAction *a = new QgsAttributeTableAction( tr( "Open form" ), this, -1, sourceIndex );
370  menu->addAction( tr( "Open form" ), a, SLOT( featureForm() ) );
371 }
372 
373 void QgsDualView::previewExpressionChanged( const QString expression )
374 {
375  mLayerCache->layer()->setDisplayExpression( expression );
376 }
377 
378 void QgsDualView::featureFormAttributeChanged()
379 {
380  mFeatureList->setCurrentFeatureEdited( true );
381 }
382 
384 {
385  mFilterModel->setFilteredFeatures( filteredFeatures );
386 }
387 
389 {
390  mMasterModel->setRequest( request );
391 }
392 
394 {
395  mTableView->setFeatureSelectionManager( featureSelectionManager );
396  // mFeatureList->setFeatureSelectionManager( featureSelectionManager );
397 
398  if ( mFeatureSelectionManager && mFeatureSelectionManager->parent() == this )
399  delete mFeatureSelectionManager;
400 
401  mFeatureSelectionManager = featureSelectionManager;
402 }
403 
404 void QgsDualView::progress( int i, bool& cancel )
405 {
406  if ( !mProgressDlg )
407  {
408  mProgressDlg = new QProgressDialog( tr( "Loading features..." ), tr( "Abort" ), 0, 0, this );
409  mProgressDlg->setWindowTitle( tr( "Attribute table" ) );
410  mProgressDlg->setWindowModality( Qt::WindowModal );
411  mProgressDlg->show();
412  }
413 
414  mProgressDlg->setValue( i );
415  mProgressDlg->setLabelText( tr( "%1 features loaded." ).arg( i ) );
416 
417  QCoreApplication::processEvents();
418 
419  cancel = mProgressDlg->wasCanceled();
420 }
421 
422 void QgsDualView::finished()
423 {
424  delete mProgressDlg;
425  mProgressDlg = 0;
426 }
427 
428 /*
429  * QgsAttributeTableAction
430  */
431 
433 {
434  mDualView->masterModel()->executeAction( mAction, mFieldIdx );
435 }
436 
438 {
439  QgsFeatureIds editedIds;
440  editedIds << mDualView->masterModel()->rowToId( mFieldIdx.row() );
441  mDualView->setCurrentEditSelection( editedIds );
443 }
444 
445 /*
446  * QgsAttributeTableMapLayerAction
447  */
448 
450 {
451  mDualView->masterModel()->executeMapLayerAction( mAction, mFieldIdx );
452 }