QGIS API Documentation  2.0.1-Dufour
 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 "qgsdualview.h"
17 #include "qgsmapcanvas.h"
18 #include "qgsvectorlayercache.h"
19 #include "qgsattributetablemodel.h"
20 #include "qgsfeaturelistmodel.h"
21 #include "qgsattributedialog.h"
22 #include "qgsapplication.h"
24 #include "qgsattributeaction.h"
25 #include "qgsvectordataprovider.h"
26 #include "qgsmessagelog.h"
27 
28 #include <QDialog>
29 #include <QMenu>
30 #include <QProgressDialog>
31 #include <QMessageBox>
32 
33 QgsDualView::QgsDualView( QWidget* parent )
34  : QStackedWidget( parent )
35  , mAttributeDialog( NULL )
36  , mProgressDlg( NULL )
37 {
38  setupUi( this );
39 
40  mPreviewActionMapper = new QSignalMapper( this );
41 
42  mPreviewColumnsMenu = new QMenu( this );
43  mActionPreviewColumnsMenu->setMenu( mPreviewColumnsMenu );
44 
45  // Set preview icon
46  mActionExpressionPreview->setIcon( QgsApplication::getThemeIcon( "/mIconExpressionPreview.svg" ) );
47 
48  // Connect layer list preview signals
49  connect( mActionExpressionPreview, SIGNAL( triggered() ), SLOT( previewExpressionBuilder() ) );
50  connect( mPreviewActionMapper, SIGNAL( mapped( QObject* ) ), SLOT( previewColumnChanged( QObject* ) ) );
51  connect( mFeatureList, SIGNAL( displayExpressionChanged( QString ) ), this, SLOT( previewExpressionChanged( QString ) ) );
52  connect( this, SIGNAL( currentChanged( int ) ), this, SLOT( saveEditChanges() ) );
53 }
54 
56 {
57  delete mAttributeDialog;
58 }
59 
61 {
62  mDistanceArea = myDa;
63 
64  connect( mTableView, SIGNAL( willShowContextMenu( QMenu*, QModelIndex ) ), this, SLOT( viewWillShowContextMenu( QMenu*, QModelIndex ) ) );
65 
66  initLayerCache( layer );
67  initModels( mapCanvas );
68 
69  mTableView->setModel( mFilterModel );
70  mFeatureList->setModel( mFeatureListModel );
71 
72  mAttributeDialog = new QgsAttributeDialog( layer, 0, false, myDa );
73  if ( mAttributeDialog->dialog() )
74  mAttributeEditorLayout->addWidget( mAttributeDialog->dialog() );
75 
76  columnBoxInit();
77 }
78 
80 {
81  // load fields
82  QList<QgsField> fields = mLayerCache->layer()->pendingFields().toList();
83 
84  QString defaultField;
85 
86  // default expression: saved value
87  QString displayExpression = mLayerCache->layer()->displayExpression();
88 
89  // if no display expression is saved: use display field instead
90  if ( displayExpression == "" )
91  {
92  if ( mLayerCache->layer()->displayField() != "" )
93  {
94  defaultField = mLayerCache->layer()->displayField();
95  displayExpression = QString( "COALESCE(\"%1\", '<NULL>')" ).arg( defaultField );
96  }
97  }
98 
99  // if neither diaplay expression nor display field is saved...
100  if ( displayExpression == "" )
101  {
103 
104  if ( pkAttrs.size() > 0 )
105  {
106  if ( pkAttrs.size() == 1 )
107  defaultField = pkAttrs.at( 0 );
108 
109  // ... If there are primary key(s) defined
110  QStringList pkFields;
111 
112  Q_FOREACH( int attr, pkAttrs )
113  {
114  pkFields.append( "COALESCE(\"" + fields[attr].name() + "\", '<NULL>')" );
115  }
116 
117  displayExpression = pkFields.join( "||', '||" );
118  }
119  else if ( fields.size() > 0 )
120  {
121  if ( fields.size() == 1 )
122  defaultField = fields.at( 0 ).name();
123 
124  // ... concat all fields
125  QStringList fieldNames;
126  foreach ( QgsField field, fields )
127  {
128  fieldNames.append( "COALESCE(\"" + field.name() + "\", '<NULL>')" );
129  }
130 
131  displayExpression = fieldNames.join( "||', '||" );
132  }
133  else
134  {
135  // ... there isn't really much to display
136  displayExpression = "'[Please define preview text]'";
137  }
138  }
139 
140  // now initialise the menu
141  QList< QAction* > previewActions = mFeatureListPreviewButton->actions();
142  foreach ( QAction* a, previewActions )
143  {
144  if ( a != mActionExpressionPreview )
145  {
146  mPreviewActionMapper->removeMappings( a );
147  delete a;
148  }
149  }
150 
151  mFeatureListPreviewButton->addAction( mActionExpressionPreview );
152  mFeatureListPreviewButton->addAction( mActionPreviewColumnsMenu );
153 
154  foreach ( const QgsField field, fields )
155  {
157  {
158  QIcon icon = QgsApplication::getThemeIcon( "/mActionNewAttribute.png" );
159  QString text = field.name();
160 
161  // Generate action for the preview popup button of the feature list
162  QAction* previewAction = new QAction( icon, text, mFeatureListPreviewButton );
163  mPreviewActionMapper->setMapping( previewAction, previewAction );
164  connect( previewAction, SIGNAL( triggered() ), mPreviewActionMapper, SLOT( map() ) );
165  mPreviewColumnsMenu->addAction( previewAction );
166 
167  if ( text == defaultField )
168  {
169  mFeatureListPreviewButton->setDefaultAction( previewAction );
170  }
171  }
172  }
173 
174  // If there is no single field found as preview
175  if ( !mFeatureListPreviewButton->defaultAction() )
176  {
177  mFeatureList->setDisplayExpression( displayExpression );
178  mFeatureListPreviewButton->setDefaultAction( mActionExpressionPreview );
179  }
180  else
181  {
182  mFeatureListPreviewButton->defaultAction()->trigger();
183  }
184 }
185 
186 void QgsDualView::hideEvent( QHideEvent* event )
187 {
188  saveEditChanges();
189  QStackedWidget::hideEvent( event );
190 }
191 
192 void QgsDualView::focusOutEvent( QFocusEvent* event )
193 {
194  saveEditChanges();
196 }
197 
199 {
200  setCurrentIndex( view );
201 }
202 
204 {
205  mFilterModel->setFilterMode( filterMode );
206 }
207 
208 void QgsDualView::setSelectedOnTop( bool selectedOnTop )
209 {
210  mFilterModel->setSelectedOnTop( selectedOnTop );
211 }
212 
214 {
215  // Initialize the cache
216  QSettings settings;
217  int cacheSize = qMax( 1, settings.value( "/qgis/attributeTableRowCache", "10000" ).toInt() );
218  mLayerCache = new QgsVectorLayerCache( layer, cacheSize, this );
219  mLayerCache->setCacheGeometry( false );
220  if ( 0 == ( QgsVectorDataProvider::SelectAtId & mLayerCache->layer()->dataProvider()->capabilities() ) )
221  {
222  connect( mLayerCache, SIGNAL( progress( int, bool & ) ), this, SLOT( progress( int, bool & ) ) );
223  connect( mLayerCache, SIGNAL( finished() ), this, SLOT( finished() ) );
224 
225  mLayerCache->setFullCache( true );
226  }
227 
228  connect( layer, SIGNAL( editingStarted() ), this, SLOT( editingToggled() ) );
229  connect( layer, SIGNAL( beforeCommitChanges() ), this, SLOT( editingToggled() ) );
230  connect( layer, SIGNAL( editingStopped() ), this, SLOT( editingToggled() ) );
231  connect( layer, SIGNAL( attributeAdded( int ) ), this, SLOT( attributeAdded( int ) ) );
232  connect( layer, SIGNAL( attributeDeleted( int ) ), this, SLOT( attributeDeleted( int ) ) );
233  connect( layer, SIGNAL( featureDeleted( QgsFeatureId ) ), this, SLOT( featureDeleted( QgsFeatureId ) ) );
234 }
235 
237 {
239 
240  connect( mMasterModel, SIGNAL( progress( int, bool & ) ), this, SLOT( progress( int, bool & ) ) );
241  connect( mMasterModel, SIGNAL( finished() ), this, SLOT( finished() ) );
242 
244 
246 
247  connect( mFeatureList, SIGNAL( displayExpressionChanged( QString ) ), this, SIGNAL( displayExpressionChanged( QString ) ) );
248 
250 }
251 
253 {
254  if ( !feat.isValid() )
255  return;
256 
257  // Backup old dialog and delete only after creating the new dialog, so we can "hot-swap" the contained QgsFeature
259 
261  {
262  saveEditChanges();
263  mAttributeEditorLayout->removeWidget( mAttributeDialog->dialog() );
264  }
265 
266  mAttributeDialog = new QgsAttributeDialog( mLayerCache->layer(), new QgsFeature( feat ), true, mDistanceArea, this, false );
267  mAttributeEditorLayout->addWidget( mAttributeDialog->dialog() );
268  mAttributeDialog->dialog()->setVisible( true );
269 
270  delete oldDialog;
271 }
272 
274 {
275  mFeatureList->setEditSelection( fids );
276 }
277 
279 {
281  {
282  if ( mLayerCache->layer() && mLayerCache->layer()->isEditable() )
283  {
284  // Get the current (unedited) feature
285  QgsFeature srcFeat;
287  mLayerCache->featureAtId( fid, srcFeat );
288  QgsAttributes src = srcFeat.attributes();
289 
290  // Let the dialog write the edited widget values to it's feature
292  // Get the edited feature
294 
295  if ( src.count() != dst.count() )
296  {
297  // bail out
298  return;
299  }
300 
301  mLayerCache->layer()->beginEditCommand( tr( "Attributes changed" ) );
302 
303  for ( int i = 0; i < dst.count(); ++i )
304  {
305  if ( dst[i] != src[i] )
306  {
307  mLayerCache->layer()->changeAttributeValue( fid, i, dst[i] );
308  }
309  }
310 
312  }
313  }
314 }
315 
317 {
318  // Show expression builder
319  QgsExpressionBuilderDialog dlg( mLayerCache->layer(), mFeatureList->displayExpression() , this );
320  dlg.setWindowTitle( tr( "Expression based preview" ) );
321  dlg.setExpressionText( mFeatureList->displayExpression() );
322 
323  if ( dlg.exec() == QDialog::Accepted )
324  {
325  mFeatureList->setDisplayExpression( dlg.expressionText() );
326  mFeatureListPreviewButton->setDefaultAction( mActionExpressionPreview );
327  mFeatureListPreviewButton->setPopupMode( QToolButton::MenuButtonPopup );
328  }
329 }
330 
331 void QgsDualView::previewColumnChanged( QObject* action )
332 {
333  QAction* previewAction = qobject_cast< QAction* >( action );
334 
335  if ( previewAction )
336  {
337  if ( !mFeatureList->setDisplayExpression( QString( "COALESCE( \"%1\", '<NULL>' )" ).arg( previewAction->text() ) ) )
338  {
339  QMessageBox::warning( this
340  , tr( "Could not set preview column" )
341  , tr( "Could not set column '%1' as preview column.\nParser error:\n%2" )
342  .arg( previewAction->text() )
343  .arg( mFeatureList->parserErrorString() )
344  );
345  }
346  else
347  {
348  mFeatureListPreviewButton->setDefaultAction( previewAction );
349  mFeatureListPreviewButton->setPopupMode( QToolButton::InstantPopup );
350  }
351  }
352 
353  Q_ASSERT( previewAction );
354 }
355 
357 {
358  // Reload the attribute dialog widget and commit changes if any
360  {
362  }
363 }
364 
366 {
367  return mMasterModel->rowCount();
368 }
369 
371 {
372  return mFilterModel->rowCount();
373 }
374 
375 void QgsDualView::viewWillShowContextMenu( QMenu* menu, QModelIndex atIndex )
376 {
377  QModelIndex sourceIndex = mFilterModel->mapToSource( atIndex );
378 
379  if ( mLayerCache->layer()->actions()->size() != 0 )
380  {
381 
382  QAction *a = menu->addAction( tr( "Run action" ) );
383  a->setEnabled( false );
384 
385  for ( int i = 0; i < mLayerCache->layer()->actions()->size(); i++ )
386  {
387  const QgsAction &action = mLayerCache->layer()->actions()->at( i );
388 
389  if ( !action.runable() )
390  continue;
391 
392  QgsAttributeTableAction *a = new QgsAttributeTableAction( action.name(), this, i, sourceIndex );
393  menu->addAction( action.name(), a, SLOT( execute() ) );
394  }
395  }
396 
397  QgsAttributeTableAction *a = new QgsAttributeTableAction( tr( "Open form" ), this, -1, sourceIndex );
398  menu->addAction( tr( "Open form" ), a, SLOT( featureForm() ) );
399 }
400 
401 void QgsDualView::previewExpressionChanged( const QString expression )
402 {
403  mLayerCache->layer()->setDisplayExpression( expression );
404 }
405 
406 void QgsDualView::attributeDeleted( int attribute )
407 {
409  {
410  // Let the dialog write the edited widget values to it's feature
412  // Get the edited feature
414  feat->deleteAttribute( attribute );
415 
416  // Backup old dialog and delete only after creating the new dialog, so we can "hot-swap" the contained QgsFeature
418 
419  mAttributeEditorLayout->removeWidget( mAttributeDialog->dialog() );
420 
421  mAttributeDialog = new QgsAttributeDialog( mLayerCache->layer(), new QgsFeature( *feat ), true, mDistanceArea, this, false );
422  mAttributeEditorLayout->addWidget( mAttributeDialog->dialog() );
423 
424  delete oldDialog;
425  }
426 }
427 
428 void QgsDualView::attributeAdded( int attribute )
429 {
431  {
432  // Let the dialog write the edited widget values to it's feature
434  // Get the edited feature
436 
437  // Get the feature including the newly added attribute
438  QgsFeature newFeat;
439  mLayerCache->featureAtId( feat->id(), newFeat );
440 
441  int offset = 0;
442  for ( int idx = 0; idx < newFeat.attributes().count(); ++idx )
443  {
444  if ( idx == attribute )
445  {
446  offset = 1;
447  }
448  else
449  {
450  newFeat.setAttribute( idx, feat->attribute( idx - offset ) );
451  }
452  }
453 
454  *feat = newFeat;
455 
456  // Backup old dialog and delete only after creating the new dialog, so we can "hot-swap" the contained QgsFeature
458 
459  mAttributeEditorLayout->removeWidget( mAttributeDialog->dialog() );
460 
461  mAttributeDialog = new QgsAttributeDialog( mLayerCache->layer(), new QgsFeature( *feat ), true, mDistanceArea, this, false );
462  mAttributeEditorLayout->addWidget( mAttributeDialog->dialog() );
463 
464  delete oldDialog;
465  }
466 }
467 
469 {
471  {
473  if ( feat )
474  {
475  if ( fid == feat->id() )
476  {
477  delete( mAttributeDialog );
478  mAttributeDialog = NULL;
479  }
480  }
481  }
482 }
483 
484 void QgsDualView::reloadAttribute( const int& idx )
485 {
487  {
488  // Let the dialog write the edited widget values to it's feature
490  // Get the edited feature
492 
493  // Get the feature including the changed attribute
494  QgsFeature newFeat;
495  mLayerCache->featureAtId( feat->id(), newFeat );
496 
497  // Update the attribute
498  feat->setAttribute( idx, newFeat.attribute( idx ) );
499 
500  // Backup old dialog and delete only after creating the new dialog, so we can "hot-swap" the contained QgsFeature
502 
503  mAttributeEditorLayout->removeWidget( mAttributeDialog->dialog() );
504 
505  mAttributeDialog = new QgsAttributeDialog( mLayerCache->layer(), new QgsFeature( *feat ), true, mDistanceArea, this, false );
506  mAttributeEditorLayout->addWidget( mAttributeDialog->dialog() );
507 
508  delete oldDialog;
509  }
510 }
511 
513 {
514  mFilterModel->setFilteredFeatures( filteredFeatures );
515 }
516 
517 void QgsDualView::progress( int i, bool& cancel )
518 {
519  if ( !mProgressDlg )
520  {
521  mProgressDlg = new QProgressDialog( tr( "Loading features..." ), tr( "Abort" ), 0, 0, this );
522  mProgressDlg->setWindowTitle( tr( "Attribute table" ) );
523  mProgressDlg->setWindowModality( Qt::WindowModal );
524  mProgressDlg->show();
525  }
526 
527  mProgressDlg->setValue( i );
528  mProgressDlg->setLabelText( tr( "%1 features loaded." ).arg( i ) );
529 
530  QCoreApplication::processEvents();
531 
532  cancel = mProgressDlg->wasCanceled();
533 }
534 
536 {
537  delete mProgressDlg;
538  mProgressDlg = 0;
539 }
540 
541 /*
542  * QgsAttributeTableAction
543  */
544 
546 {
548 }
549 
551 {
552  QgsFeatureIds editedIds;
553  editedIds << mDualView->masterModel()->rowToId( mFieldIdx.row() );
554  mDualView->setCurrentEditSelection( editedIds );
556 }