QGIS API Documentation  2.2.0-Valmiera
 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 "qgsvectordataprovider.h"
19 #include "qgsmessagelog.h"
20 #include "qgsattributedialog.h"
21 #include "qgsattributetablemodel.h"
22 #include "qgsdualview.h"
24 #include "qgsfeaturelistmodel.h"
26 #include "qgsmapcanvas.h"
27 #include "qgsvectordataprovider.h"
28 #include "qgsvectorlayercache.h"
30 
31 #include <QDialog>
32 #include <QMenu>
33 #include <QProgressDialog>
34 #include <QMessageBox>
35 
36 QgsDualView::QgsDualView( QWidget* parent )
37  : QStackedWidget( parent )
38  , mEditorContext()
39  , mMasterModel( NULL )
40  , mAttributeDialog( NULL )
41  , mLayerCache( NULL )
42  , mProgressDlg( NULL )
43  , mFeatureSelectionManager( NULL )
44 {
45  setupUi( this );
46 
47  mPreviewActionMapper = new QSignalMapper( this );
48 
49  mPreviewColumnsMenu = new QMenu( this );
50  mActionPreviewColumnsMenu->setMenu( mPreviewColumnsMenu );
51 
52  // Set preview icon
53  mActionExpressionPreview->setIcon( QgsApplication::getThemeIcon( "/mIconExpressionPreview.svg" ) );
54 
55  // Connect layer list preview signals
56  connect( mActionExpressionPreview, SIGNAL( triggered() ), SLOT( previewExpressionBuilder() ) );
57  connect( mPreviewActionMapper, SIGNAL( mapped( QObject* ) ), SLOT( previewColumnChanged( QObject* ) ) );
58  connect( mFeatureList, SIGNAL( displayExpressionChanged( QString ) ), this, SLOT( previewExpressionChanged( QString ) ) );
59  connect( this, SIGNAL( currentChanged( int ) ), this, SLOT( saveEditChanges() ) );
60 }
61 
63 {
64  delete mAttributeDialog;
65 }
66 
68 {
69  mEditorContext = context;
70 
71  connect( mTableView, SIGNAL( willShowContextMenu( QMenu*, QModelIndex ) ), this, SLOT( viewWillShowContextMenu( QMenu*, QModelIndex ) ) );
72 
73  initLayerCache( layer );
74  initModels( mapCanvas, request );
75 
76  mTableView->setModel( mFilterModel );
77  mFeatureList->setModel( mFeatureListModel );
78 
79  mAttributeDialog = new QgsAttributeDialog( layer, NULL, false, NULL, true, mEditorContext );
80  if ( mAttributeDialog->dialog() )
81  mAttributeEditorLayout->addWidget( mAttributeDialog->dialog() );
82 
83  columnBoxInit();
84 }
85 
87 {
88  // load fields
89  QList<QgsField> fields = mLayerCache->layer()->pendingFields().toList();
90 
91  QString defaultField;
92 
93  // default expression: saved value
94  QString displayExpression = mLayerCache->layer()->displayExpression();
95 
96  // if no display expression is saved: use display field instead
97  if ( displayExpression == "" )
98  {
99  if ( mLayerCache->layer()->displayField() != "" )
100  {
101  defaultField = mLayerCache->layer()->displayField();
102  displayExpression = QString( "COALESCE(\"%1\", '<NULL>')" ).arg( defaultField );
103  }
104  }
105 
106  // if neither diaplay expression nor display field is saved...
107  if ( displayExpression == "" )
108  {
110 
111  if ( pkAttrs.size() > 0 )
112  {
113  if ( pkAttrs.size() == 1 )
114  defaultField = pkAttrs.at( 0 );
115 
116  // ... If there are primary key(s) defined
117  QStringList pkFields;
118 
119  Q_FOREACH( int attr, pkAttrs )
120  {
121  pkFields.append( "COALESCE(\"" + fields[attr].name() + "\", '<NULL>')" );
122  }
123 
124  displayExpression = pkFields.join( "||', '||" );
125  }
126  else if ( fields.size() > 0 )
127  {
128  if ( fields.size() == 1 )
129  defaultField = fields.at( 0 ).name();
130 
131  // ... concat all fields
132  QStringList fieldNames;
133  foreach ( QgsField field, fields )
134  {
135  fieldNames.append( "COALESCE(\"" + field.name() + "\", '<NULL>')" );
136  }
137 
138  displayExpression = fieldNames.join( "||', '||" );
139  }
140  else
141  {
142  // ... there isn't really much to display
143  displayExpression = "'[Please define preview text]'";
144  }
145  }
146 
147  // now initialise the menu
148  QList< QAction* > previewActions = mFeatureListPreviewButton->actions();
149  foreach ( QAction* a, previewActions )
150  {
151  if ( a != mActionExpressionPreview )
152  {
153  mPreviewActionMapper->removeMappings( a );
154  delete a;
155  }
156  }
157 
158  mFeatureListPreviewButton->addAction( mActionExpressionPreview );
159  mFeatureListPreviewButton->addAction( mActionPreviewColumnsMenu );
160 
161  foreach ( const QgsField field, fields )
162  {
164  {
165  QIcon icon = QgsApplication::getThemeIcon( "/mActionNewAttribute.png" );
166  QString text = field.name();
167 
168  // Generate action for the preview popup button of the feature list
169  QAction* previewAction = new QAction( icon, text, mFeatureListPreviewButton );
170  mPreviewActionMapper->setMapping( previewAction, previewAction );
171  connect( previewAction, SIGNAL( triggered() ), mPreviewActionMapper, SLOT( map() ) );
172  mPreviewColumnsMenu->addAction( previewAction );
173 
174  if ( text == defaultField )
175  {
176  mFeatureListPreviewButton->setDefaultAction( previewAction );
177  }
178  }
179  }
180 
181  // If there is no single field found as preview
182  if ( !mFeatureListPreviewButton->defaultAction() )
183  {
184  mFeatureList->setDisplayExpression( displayExpression );
185  mFeatureListPreviewButton->setDefaultAction( mActionExpressionPreview );
186  }
187  else
188  {
189  mFeatureListPreviewButton->defaultAction()->trigger();
190  }
191 }
192 
193 void QgsDualView::hideEvent( QHideEvent* event )
194 {
195  if ( saveEditChanges() )
196  QStackedWidget::hideEvent( event );
197 }
198 
199 void QgsDualView::focusOutEvent( QFocusEvent* event )
200 {
201  if ( saveEditChanges() )
203 }
204 
206 {
207  setCurrentIndex( view );
208 }
209 
211 {
212  mFilterModel->setFilterMode( filterMode );
213 }
214 
215 void QgsDualView::setSelectedOnTop( bool selectedOnTop )
216 {
217  mFilterModel->setSelectedOnTop( selectedOnTop );
218 }
219 
221 {
222  // Initialize the cache
223  QSettings settings;
224  int cacheSize = qMax( 1, settings.value( "/qgis/attributeTableRowCache", "10000" ).toInt() );
225  mLayerCache = new QgsVectorLayerCache( layer, cacheSize, this );
226  mLayerCache->setCacheGeometry( false );
227  if ( 0 == ( QgsVectorDataProvider::SelectAtId & mLayerCache->layer()->dataProvider()->capabilities() ) )
228  {
229  connect( mLayerCache, SIGNAL( progress( int, bool & ) ), this, SLOT( progress( int, bool & ) ) );
230  connect( mLayerCache, SIGNAL( finished() ), this, SLOT( finished() ) );
231 
232  mLayerCache->setFullCache( true );
233  }
234 
235  connect( layer, SIGNAL( editingStarted() ), this, SLOT( editingToggled() ) );
236  connect( layer, SIGNAL( beforeCommitChanges() ), this, SLOT( editingToggled() ) );
237  connect( layer, SIGNAL( editingStopped() ), this, SLOT( editingToggled() ) );
238  connect( layer, SIGNAL( attributeAdded( int ) ), this, SLOT( attributeAdded( int ) ) );
239  connect( layer, SIGNAL( attributeDeleted( int ) ), this, SLOT( attributeDeleted( int ) ) );
240  connect( layer, SIGNAL( featureDeleted( QgsFeatureId ) ), this, SLOT( featureDeleted( QgsFeatureId ) ) );
241 }
242 
243 void QgsDualView::initModels( QgsMapCanvas* mapCanvas, const QgsFeatureRequest& request )
244 {
246  mMasterModel->setRequest( request );
247 
248  connect( mMasterModel, SIGNAL( progress( int, bool & ) ), this, SLOT( progress( int, bool & ) ) );
249  connect( mMasterModel, SIGNAL( finished() ), this, SLOT( finished() ) );
250 
252 
254 
255  connect( mFeatureList, SIGNAL( displayExpressionChanged( QString ) ), this, SIGNAL( displayExpressionChanged( QString ) ) );
256 
258 }
259 
261 {
262  // Invalid feature? Strange: bail out
263  if ( !feat.isValid() )
264  return;
265 
266  // We already show the feature in question: bail out
268  && mAttributeDialog->feature()->id() == feat.id() )
269  return;
270 
271  bool dontChange = false;
272 
273  // Backup old dialog and delete only after creating the new dialog, so we can "hot-swap" the contained QgsFeature
275 
277  {
278  if ( saveEditChanges() )
279  mAttributeEditorLayout->removeWidget( mAttributeDialog->dialog() );
280  else
281  dontChange = true;
282  }
283 
284  if ( !dontChange )
285  {
286  mAttributeDialog = new QgsAttributeDialog( mLayerCache->layer(), new QgsFeature( feat ), true, this, false, mEditorContext );
287  mAttributeEditorLayout->addWidget( mAttributeDialog->dialog() );
288  mAttributeDialog->dialog()->setVisible( true );
289 
290  delete oldDialog;
291  }
292  else
293  {
294  setCurrentEditSelection( QgsFeatureIds() << oldDialog->feature()->id() );
295  }
296 }
297 
299 {
300  mFeatureList->setEditSelection( fids );
301 }
302 
304 {
306  {
307  if ( mAttributeDialog->editable() )
308  {
309  // Get the current (unedited) feature
310  QgsFeature srcFeat;
312  mLayerCache->featureAtId( fid, srcFeat );
313  QgsAttributes src = srcFeat.attributes();
314 
315  // Let the dialog write the edited widget values to it's feature
316  QDialogButtonBox* buttonBox = mAttributeDialog->dialog()->findChild<QDialogButtonBox*>();
317  if ( buttonBox && buttonBox->button( QDialogButtonBox::Ok ) )
318  {
319  QPushButton* okBtn = buttonBox->button( QDialogButtonBox::Ok );
320  okBtn->click();
321  }
322  else
323  {
325  }
326 
327  if ( mAttributeDialog->dialog()->result() == QDialog::Accepted )
328  {
329  // Get the edited feature
331 
332  if ( src.count() != dst.count() )
333  {
334  // bail out
335  return false;
336  }
337 
338  // avoid empty command
339  int i = 0;
340  for ( ; i < dst.count() && dst[i] == src[i]; ++i )
341  ;
342 
343  if ( i == dst.count() )
344  return true;
345 
346  mLayerCache->layer()->beginEditCommand( tr( "Attributes changed" ) );
347 
348  for ( ; i < dst.count(); ++i )
349  {
350  if ( dst[i] == src[i] )
351  continue;
352 
353  mLayerCache->layer()->changeAttributeValue( fid, i, dst[i], src[i] );
354  }
355 
357  }
358  else
359  {
360  return false;
361  }
362  }
363  }
364  return true;
365 }
366 
368 {
369  // Show expression builder
370  QgsExpressionBuilderDialog dlg( mLayerCache->layer(), mFeatureList->displayExpression() , this );
371  dlg.setWindowTitle( tr( "Expression based preview" ) );
372  dlg.setExpressionText( mFeatureList->displayExpression() );
373 
374  if ( dlg.exec() == QDialog::Accepted )
375  {
376  mFeatureList->setDisplayExpression( dlg.expressionText() );
377  mFeatureListPreviewButton->setDefaultAction( mActionExpressionPreview );
378  mFeatureListPreviewButton->setPopupMode( QToolButton::MenuButtonPopup );
379  }
380 }
381 
382 void QgsDualView::previewColumnChanged( QObject* action )
383 {
384  QAction* previewAction = qobject_cast< QAction* >( action );
385 
386  if ( previewAction )
387  {
388  if ( !mFeatureList->setDisplayExpression( QString( "COALESCE( \"%1\", '<NULL>' )" ).arg( previewAction->text() ) ) )
389  {
390  QMessageBox::warning( this
391  , tr( "Could not set preview column" )
392  , tr( "Could not set column '%1' as preview column.\nParser error:\n%2" )
393  .arg( previewAction->text() )
394  .arg( mFeatureList->parserErrorString() )
395  );
396  }
397  else
398  {
399  mFeatureListPreviewButton->setDefaultAction( previewAction );
400  mFeatureListPreviewButton->setPopupMode( QToolButton::InstantPopup );
401  }
402  }
403 
404  Q_ASSERT( previewAction );
405 }
406 
408 {
409  // Reload the attribute dialog widget and commit changes if any
411  {
413  }
414 }
415 
417 {
418  return mMasterModel->rowCount();
419 }
420 
422 {
423  return mFilterModel->rowCount();
424 }
425 
426 void QgsDualView::viewWillShowContextMenu( QMenu* menu, QModelIndex atIndex )
427 {
428  QModelIndex sourceIndex = mFilterModel->mapToSource( atIndex );
429 
430  //add user-defined actions to context menu
431  if ( mLayerCache->layer()->actions()->size() != 0 )
432  {
433 
434  QAction *a = menu->addAction( tr( "Run layer action" ) );
435  a->setEnabled( false );
436 
437  for ( int i = 0; i < mLayerCache->layer()->actions()->size(); i++ )
438  {
439  const QgsAction &action = mLayerCache->layer()->actions()->at( i );
440 
441  if ( !action.runable() )
442  continue;
443 
444  QgsAttributeTableAction *a = new QgsAttributeTableAction( action.name(), this, i, sourceIndex );
445  menu->addAction( action.name(), a, SLOT( execute() ) );
446  }
447  }
448 
449  //add actions from QgsMapLayerActionRegistry to context menu
450  QList<QgsMapLayerAction *> registeredActions = QgsMapLayerActionRegistry::instance()->mapLayerActions( mLayerCache->layer() );
451  if ( registeredActions.size() > 0 )
452  {
453  //add a seperator between user defined and standard actions
454  menu->addSeparator();
455 
456  QList<QgsMapLayerAction*>::iterator actionIt;
457  for ( actionIt = registeredActions.begin(); actionIt != registeredActions.end(); ++actionIt )
458  {
459  QgsAttributeTableMapLayerAction *a = new QgsAttributeTableMapLayerAction(( *actionIt )->text(), this, ( *actionIt ), sourceIndex );
460  menu->addAction(( *actionIt )->text(), a, SLOT( execute() ) );
461  }
462  }
463 
464  menu->addSeparator();
465  QgsAttributeTableAction *a = new QgsAttributeTableAction( tr( "Open form" ), this, -1, sourceIndex );
466  menu->addAction( tr( "Open form" ), a, SLOT( featureForm() ) );
467 }
468 
469 void QgsDualView::previewExpressionChanged( const QString expression )
470 {
471  mLayerCache->layer()->setDisplayExpression( expression );
472 }
473 
474 void QgsDualView::attributeDeleted( int attribute )
475 {
477  {
478  // Let the dialog write the edited widget values to it's feature
480  // Get the edited feature
482  feat->deleteAttribute( attribute );
483 
484  // Backup old dialog and delete only after creating the new dialog, so we can "hot-swap" the contained QgsFeature
486 
487  mAttributeEditorLayout->removeWidget( mAttributeDialog->dialog() );
488 
489  mAttributeDialog = new QgsAttributeDialog( mLayerCache->layer(), new QgsFeature( *feat ), true, this, false );
490  mAttributeEditorLayout->addWidget( mAttributeDialog->dialog() );
491 
492  delete oldDialog;
493  }
494 }
495 
496 void QgsDualView::attributeAdded( int attribute )
497 {
499  {
500  // Let the dialog write the edited widget values to it's feature
502  // Get the edited feature
504 
505  // Get the feature including the newly added attribute
506  QgsFeature newFeat;
507  mLayerCache->featureAtId( feat->id(), newFeat );
508 
509  int offset = 0;
510  for ( int idx = 0; idx < newFeat.attributes().count(); ++idx )
511  {
512  if ( idx == attribute )
513  {
514  offset = 1;
515  }
516  else
517  {
518  newFeat.setAttribute( idx, feat->attribute( idx - offset ) );
519  }
520  }
521 
522  *feat = newFeat;
523 
524  // Backup old dialog and delete only after creating the new dialog, so we can "hot-swap" the contained QgsFeature
526 
527  mAttributeEditorLayout->removeWidget( mAttributeDialog->dialog() );
528 
529  mAttributeDialog = new QgsAttributeDialog( mLayerCache->layer(), new QgsFeature( *feat ), true, this, false );
530  mAttributeEditorLayout->addWidget( mAttributeDialog->dialog() );
531 
532  delete oldDialog;
533  }
534 }
535 
537 {
539  {
541  if ( feat )
542  {
543  if ( fid == feat->id() )
544  {
545  delete( mAttributeDialog );
546  mAttributeDialog = NULL;
547  }
548  }
549  }
550 }
551 
552 void QgsDualView::reloadAttribute( const int& idx )
553 {
555  {
556  // Let the dialog write the edited widget values to it's feature
558  // Get the edited feature
560 
561  // Get the feature including the changed attribute
562  QgsFeature newFeat;
563  mLayerCache->featureAtId( feat->id(), newFeat );
564 
565  // Update the attribute
566  feat->setAttribute( idx, newFeat.attribute( idx ) );
567 
568  // Backup old dialog and delete only after creating the new dialog, so we can "hot-swap" the contained QgsFeature
570 
571  mAttributeEditorLayout->removeWidget( mAttributeDialog->dialog() );
572 
573  mAttributeDialog = new QgsAttributeDialog( mLayerCache->layer(), new QgsFeature( *feat ), true, this, false );
574  mAttributeEditorLayout->addWidget( mAttributeDialog->dialog() );
575 
576  delete oldDialog;
577  }
578 }
579 
581 {
582  mFilterModel->setFilteredFeatures( filteredFeatures );
583 }
584 
586 {
587  mMasterModel->setRequest( request );
588 }
589 
591 {
592  mTableView->setFeatureSelectionManager( featureSelectionManager );
593  // mFeatureList->setFeatureSelectionManager( featureSelectionManager );
594 
595  if ( mFeatureSelectionManager && mFeatureSelectionManager->parent() == this )
597 
598  mFeatureSelectionManager = featureSelectionManager;
599 }
600 
601 void QgsDualView::progress( int i, bool& cancel )
602 {
603  if ( !mProgressDlg )
604  {
605  mProgressDlg = new QProgressDialog( tr( "Loading features..." ), tr( "Abort" ), 0, 0, this );
606  mProgressDlg->setWindowTitle( tr( "Attribute table" ) );
607  mProgressDlg->setWindowModality( Qt::WindowModal );
608  mProgressDlg->show();
609  }
610 
611  mProgressDlg->setValue( i );
612  mProgressDlg->setLabelText( tr( "%1 features loaded." ).arg( i ) );
613 
614  QCoreApplication::processEvents();
615 
616  cancel = mProgressDlg->wasCanceled();
617 }
618 
620 {
621  delete mProgressDlg;
622  mProgressDlg = 0;
623 }
624 
625 /*
626  * QgsAttributeTableAction
627  */
628 
630 {
632 }
633 
635 {
636  QgsFeatureIds editedIds;
637  editedIds << mDualView->masterModel()->rowToId( mFieldIdx.row() );
638  mDualView->setCurrentEditSelection( editedIds );
640 }
641 
642 /*
643  * QgsAttributeTableMapLayerAction
644  */
645 
647 {
649 }