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