QGIS API Documentation  2.8.2-Wien
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
qgsrelationreferencewidget.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsrelationreferencewidget.cpp
3  --------------------------------------
4  Date : 20.4.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 
17 
18 #include <QPushButton>
19 #include <QDialog>
20 #include <QHBoxLayout>
21 #include <QTimer>
22 
23 #include "qgsattributeform.h"
24 #include "qgsattributedialog.h"
25 #include "qgsapplication.h"
26 #include "qgscollapsiblegroupbox.h"
27 #include "qgseditorwidgetfactory.h"
28 #include "qgsexpression.h"
29 #include "qgsfield.h"
30 #include "qgsgeometry.h"
31 #include "qgsmapcanvas.h"
32 #include "qgsmessagebar.h"
34 #include "qgsvectorlayer.h"
35 
38 {
39  switch ( p1.first.type() )
40  {
41  case QVariant::String:
42  return p1.first.toString() < p2.first.toString();
43  break;
44 
45  case QVariant::Double:
46  return p1.first.toDouble() < p2.first.toDouble();
47  break;
48 
49  default:
50  return p1.first.toInt() < p2.first.toInt();
51  break;
52  }
53 }
54 
56  : QWidget( parent )
57  , mEditorContext( QgsAttributeEditorContext() )
58  , mCanvas( NULL )
59  , mMessageBar( NULL )
60  , mForeignKey( QVariant() )
61  , mFeatureId( QgsFeatureId() )
62  , mFkeyFieldIdx( -1 )
63  , mAllowNull( true )
64  , mHighlight( NULL )
65  , mMapTool( NULL )
66  , mMessageBarItem( NULL )
67  , mRelationName( "" )
68  , mReferencedAttributeForm( NULL )
69  , mReferencedLayer( NULL )
70  , mReferencingLayer( NULL )
71  , mWindowWidget( NULL )
72  , mShown( false )
73  , mIsEditable( true )
74  , mEmbedForm( false )
75  , mReadOnlySelector( false )
76  , mAllowMapIdentification( false )
77  , mOrderByValue( false )
78  , mOpenFormButtonVisible( true )
79 {
80  mTopLayout = new QVBoxLayout( this );
81  mTopLayout->setContentsMargins( 0, 0, 0, 0 );
82  mTopLayout->setAlignment( Qt::AlignTop );
83  setLayout( mTopLayout );
84 
85  QHBoxLayout* editLayout = new QHBoxLayout();
86  editLayout->setContentsMargins( 0, 0, 0, 0 );
87  editLayout->setSpacing( 2 );
88 
89  // combobox (for non-geometric relation)
90  mComboBox = new QComboBox( this );
91  editLayout->addWidget( mComboBox );
92 
93  // read-only line edit
94  mLineEdit = new QLineEdit( this );
95  mLineEdit->setReadOnly( true );
96  editLayout->addWidget( mLineEdit );
97 
98  // open form button
99  mOpenFormButton = new QToolButton( this );
100  mOpenFormButton->setIcon( QgsApplication::getThemeIcon( "/mActionPropertyItem.png" ) );
101  mOpenFormButton->setText( tr( "Open related feature form" ) );
102  editLayout->addWidget( mOpenFormButton );
103 
104  // highlight button
105  mHighlightFeatureButton = new QToolButton( this );
106  mHighlightFeatureButton->setPopupMode( QToolButton::MenuButtonPopup );
107  mHighlightFeatureAction = new QAction( QgsApplication::getThemeIcon( "/mActionHighlightFeature.svg" ), tr( "Highlight feature" ), this );
108  mScaleHighlightFeatureAction = new QAction( QgsApplication::getThemeIcon( "/mActionScaleHighlightFeature.svg" ), tr( "Scale and highlight feature" ), this );
109  mPanHighlightFeatureAction = new QAction( QgsApplication::getThemeIcon( "/mActionPanHighlightFeature.svg" ), tr( "Pan and highlight feature" ), this );
110  mHighlightFeatureButton->addAction( mHighlightFeatureAction );
111  mHighlightFeatureButton->addAction( mScaleHighlightFeatureAction );
112  mHighlightFeatureButton->addAction( mPanHighlightFeatureAction );
113  mHighlightFeatureButton->setDefaultAction( mHighlightFeatureAction );
114  editLayout->addWidget( mHighlightFeatureButton );
115 
116  // map identification button
117  mMapIdentificationButton = new QToolButton( this );
118  mMapIdentificationButton->setIcon( QgsApplication::getThemeIcon( "/mActionMapIdentification.svg" ) );
119  mMapIdentificationButton->setText( tr( "Select on map" ) );
120  mMapIdentificationButton->setCheckable( true );
121  editLayout->addWidget( mMapIdentificationButton );
122 
123  // remove foreign key button
124  mRemoveFKButton = new QToolButton( this );
125  mRemoveFKButton->setIcon( QgsApplication::getThemeIcon( "/mActionRemove.svg" ) );
126  mRemoveFKButton->setText( tr( "No selection" ) );
127  editLayout->addWidget( mRemoveFKButton );
128 
129  // spacer
130  editLayout->addItem( new QSpacerItem( 0, 0, QSizePolicy::Expanding ) );
131 
132  // add line to top layout
133  mTopLayout->addLayout( editLayout );
134 
135  // embed form
136  mAttributeEditorFrame = new QgsCollapsibleGroupBox( this );
137  mAttributeEditorLayout = new QVBoxLayout( mAttributeEditorFrame );
138  mAttributeEditorFrame->setLayout( mAttributeEditorLayout );
139  mAttributeEditorFrame->setSizePolicy( mAttributeEditorFrame->sizePolicy().horizontalPolicy(), QSizePolicy::Expanding );
140  mTopLayout->addWidget( mAttributeEditorFrame );
141 
142  // invalid label
143  mInvalidLabel = new QLabel( tr( "The relation is not valid. Please make sure your relation definitions are ok." ) );
144  mInvalidLabel->setWordWrap( true );
145  QFont font = mInvalidLabel->font();
146  font.setItalic( true );
147  mInvalidLabel->setStyleSheet( "QLabel { color: red; } " );
148  mInvalidLabel->setFont( font );
149  mTopLayout->addWidget( mInvalidLabel );
150 
151  // default mode is combobox, no geometric relation and no embed form
152  mLineEdit->hide();
153  mMapIdentificationButton->hide();
154  mHighlightFeatureButton->hide();
155  mAttributeEditorFrame->hide();
156  mInvalidLabel->hide();
157 
158  // connect buttons
159  connect( mOpenFormButton, SIGNAL( clicked() ), this, SLOT( openForm() ) );
160  connect( mHighlightFeatureButton, SIGNAL( triggered( QAction* ) ), this, SLOT( highlightActionTriggered( QAction* ) ) );
161  connect( mMapIdentificationButton, SIGNAL( clicked() ), this, SLOT( mapIdentification() ) );
162  connect( mRemoveFKButton, SIGNAL( clicked() ), this, SLOT( deleteForeignKey() ) );
163 }
164 
166 {
167  deleteHighlight();
168  unsetMapTool();
169  if ( mMapTool )
170  delete mMapTool;
171 }
172 
173 void QgsRelationReferenceWidget::setRelation( QgsRelation relation, bool allowNullValue )
174 {
175  mAllowNull = allowNullValue;
176  mRemoveFKButton->setVisible( allowNullValue && mReadOnlySelector );
177 
178  if ( relation.isValid() )
179  {
180  mInvalidLabel->hide();
181 
182  mRelation = relation;
183  mReferencingLayer = relation.referencingLayer();
184  mRelationName = relation.name();
185  mReferencedLayer = relation.referencedLayer();
186  mFkeyFieldIdx = mReferencedLayer->fieldNameIndex( relation.fieldPairs().first().second );
187 
189 
190  if ( mEmbedForm )
191  {
192  mAttributeEditorFrame->setTitle( mReferencedLayer->name() );
193  mReferencedAttributeForm = new QgsAttributeForm( relation.referencedLayer(), QgsFeature(), context, this );
194  mReferencedAttributeForm->hideButtonBox();
195  mAttributeEditorLayout->addWidget( mReferencedAttributeForm );
196  }
197  }
198  else
199  {
200  mInvalidLabel->show();
201  }
202 
203  if ( mShown && isVisible() )
204  {
205  init();
206  }
207 }
208 
210 {
211  if ( !editable )
212  unsetMapTool();
213 
214  mComboBox->setEnabled( editable );
215  mMapIdentificationButton->setEnabled( editable );
216  mRemoveFKButton->setEnabled( editable );
217  mIsEditable = editable;
218 }
219 
220 void QgsRelationReferenceWidget::setForeignKey( const QVariant& value )
221 {
222  if ( !value.isValid() || value.isNull() )
223  {
225  return;
226  }
227 
228  QgsFeature f;
229  if ( !mReferencedLayer )
230  return;
231 
232  // TODO: Rewrite using expression
233  QgsFeatureIterator fit = mReferencedLayer->getFeatures( QgsFeatureRequest() );
234  while ( fit.nextFeature( f ) )
235  {
236  if ( f.attribute( mFkeyFieldIdx ) == value )
237  {
238  break;
239  }
240  }
241 
242  if ( !f.isValid() )
243  {
245  return;
246  }
247 
248  mForeignKey = f.attribute( mFkeyFieldIdx );
249 
250  if ( mReadOnlySelector )
251  {
252  QgsExpression expr( mReferencedLayer->displayExpression() );
253  QString title = expr.evaluate( &f ).toString();
254  if ( expr.hasEvalError() )
255  {
256  title = f.attribute( mFkeyFieldIdx ).toString();
257  }
258  mLineEdit->setText( title );
259  mFeatureId = f.id();
260  }
261  else
262  {
263  int i = mComboBox->findData( value );
264  if ( i == -1 && mAllowNull )
265  {
266  mComboBox->setCurrentIndex( 0 );
267  }
268  else
269  {
270  mComboBox->setCurrentIndex( i );
271  }
272  }
273 
274  mRemoveFKButton->setEnabled( mIsEditable );
275  highlightFeature( f );
276  updateAttributeEditorFrame( f );
277  emit foreignKeyChanged( foreignKey() );
278 }
279 
281 {
282  QVariant nullValue = QSettings().value( "qgis/nullValue", "NULL" );
283  if ( mReadOnlySelector )
284  {
285  QString nullText = "";
286  if ( mAllowNull )
287  {
288  nullText = tr( "%1 (no selection)" ).arg( nullValue.toString() );
289  }
290  mLineEdit->setText( nullText );
291  mForeignKey = QVariant();
292  mFeatureId = QgsFeatureId();
293  }
294  else
295  {
296  if ( mAllowNull )
297  {
298  mComboBox->setCurrentIndex( 0 );
299  }
300  else
301  {
302  mComboBox->setCurrentIndex( -1 );
303  }
304  }
305  mRemoveFKButton->setEnabled( false );
306  updateAttributeEditorFrame( QgsFeature() );
307  emit foreignKeyChanged( QVariant( QVariant::Int ) );
308 }
309 
311 {
312  QgsFeature f;
313  if ( mReferencedLayer )
314  {
315  QgsFeatureId fid;
316  if ( mReadOnlySelector )
317  {
318  fid = mFeatureId;
319  }
320  else
321  {
322  fid = mComboBox->itemData( mComboBox->currentIndex() ).value<QgsFeatureId>();
323  }
324  mReferencedLayer->getFeatures( QgsFeatureRequest().setFilterFid( fid ) ).nextFeature( f );
325  }
326  return f;
327 }
328 
330 {
331  if ( mReadOnlySelector )
332  {
333  return mForeignKey;
334  }
335  else
336  {
337  QVariant varFid = mComboBox->itemData( mComboBox->currentIndex() );
338  if ( varFid.isNull() )
339  {
340  return QVariant();
341  }
342  else
343  {
344  return mFidFkMap.value( varFid.value<QgsFeatureId>() );
345  }
346  }
347 }
348 
350 {
351  mEditorContext = context;
352  mCanvas = canvas;
353  mMessageBar = messageBar;
354 
355  if ( mMapTool )
356  delete mMapTool;
357  mMapTool = new QgsMapToolIdentifyFeature( mCanvas );
358  mMapTool->setAction( mMapIdentificationButton->defaultAction() );
359 }
360 
362 {
363  mAttributeEditorFrame->setVisible( display );
364  mEmbedForm = display;
365 }
366 
368 {
369  mComboBox->setHidden( readOnly );
370  mLineEdit->setVisible( readOnly );
371  mRemoveFKButton->setVisible( mAllowNull && readOnly );
372  mReadOnlySelector = readOnly;
373 }
374 
375 void QgsRelationReferenceWidget::setAllowMapIdentification( bool allowMapIdentification )
376 {
377  mHighlightFeatureButton->setVisible( allowMapIdentification );
378  mMapIdentificationButton->setVisible( allowMapIdentification );
379  mAllowMapIdentification = allowMapIdentification;
380 }
381 
383 {
384  mOrderByValue = orderByValue;
385 }
386 
388 {
389  mOpenFormButton->setVisible( openFormButtonVisible );
390  mOpenFormButtonVisible = openFormButtonVisible;
391 }
392 
394 {
395  Q_UNUSED( e )
396 
397  mShown = true;
398 
399  init();
400 }
401 
403 {
404  if ( !mReadOnlySelector && mComboBox->count() == 0 && mReferencedLayer )
405  {
406  QApplication::setOverrideCursor( Qt::WaitCursor );
407  if ( mAllowNull )
408  {
409  const QString nullValue = QSettings().value( "qgis/nullValue", "NULL" ).toString();
410 
411  mComboBox->addItem( tr( "%1 (no selection)" ).arg( nullValue ), QVariant( QVariant::Int ) );
412  mComboBox->setItemData( 0, QColor( Qt::gray ), Qt::ForegroundRole );
413  }
414 
415  QgsExpression exp( mReferencedLayer->displayExpression() );
416 
417  QStringList attrs = exp.referencedColumns();
418  attrs << mRelation.fieldPairs().first().second;
419 
420  QgsFeatureIterator fit = mReferencedLayer->getFeatures( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ).setSubsetOfAttributes( attrs, mReferencedLayer->pendingFields() ) );
421 
422  exp.prepare( mReferencedLayer->pendingFields() );
423 
424  QgsFeature f;
425  if ( mOrderByValue )
426  {
427  ValueRelationCache cache;
428 
429  QgsFeatureId currentSelection;
430 
431  while ( fit.nextFeature( f ) )
432  {
433  QVariant val = exp.evaluate( &f );
434  cache.append( qMakePair( val, f.id() ) );
435  mFidFkMap.insert( f.id(), f.attribute( mFkeyFieldIdx ) );
436  if ( f.attribute( mFkeyFieldIdx ) == mForeignKey )
437  currentSelection = f.id();
438  }
439 
440  qSort( cache.begin(), cache.end(), orderByLessThan );
441 
442  Q_FOREACH ( const ValueRelationItem& item, cache )
443  {
444  mComboBox->addItem( item.first.toString(), item.second );
445 
446  if ( currentSelection == item.second )
447  mComboBox->setCurrentIndex( mComboBox->count() - 1 );
448  }
449  }
450  else
451  {
452  while ( fit.nextFeature( f ) )
453  {
454  QString txt = exp.evaluate( &f ).toString();
455  mComboBox->addItem( txt, f.id() );
456 
457  if ( f.attribute( mFkeyFieldIdx ) == mForeignKey )
458  mComboBox->setCurrentIndex( mComboBox->count() - 1 );
459 
460  mFidFkMap.insert( f.id(), f.attribute( mFkeyFieldIdx ) );
461  }
462  }
463 
464  // Only connect after iterating, to have only one iterator on the referenced table at once
465  connect( mComboBox, SIGNAL( activated( int ) ), this, SLOT( comboReferenceChanged( int ) ) );
466  QApplication::restoreOverrideCursor();
467  }
468 }
469 
470 void QgsRelationReferenceWidget::highlightActionTriggered( QAction* action )
471 {
472  if ( action == mHighlightFeatureAction )
473  {
474  highlightFeature();
475  }
476  else if ( action == mScaleHighlightFeatureAction )
477  {
478  highlightFeature( QgsFeature(), Scale );
479  }
480  else if ( action == mPanHighlightFeatureAction )
481  {
482  highlightFeature( QgsFeature(), Pan );
483  }
484 }
485 
487 {
488  QgsFeature feat = referencedFeature();
489 
490  if ( !feat.isValid() )
491  return;
492 
494  QgsAttributeDialog attributeDialog( mReferencedLayer, new QgsFeature( feat ), true, this, true, context );
495  attributeDialog.exec();
496 }
497 
498 void QgsRelationReferenceWidget::highlightFeature( QgsFeature f, CanvasExtent canvasExtent )
499 {
500  if ( !mCanvas )
501  return;
502 
503  if ( !f.isValid() )
504  {
505  f = referencedFeature();
506  if ( !f.isValid() )
507  return;
508  }
509 
510  QgsGeometry* geom = f.geometry();
511  if ( !geom )
512  {
513  return;
514  }
515 
516  // scale or pan
517  if ( canvasExtent == Scale )
518  {
519  QgsRectangle featBBox = geom->boundingBox();
520  featBBox = mCanvas->mapSettings().layerToMapCoordinates( mReferencedLayer, featBBox );
521  QgsRectangle extent = mCanvas->extent();
522  if ( !extent.contains( featBBox ) )
523  {
524  extent.combineExtentWith( &featBBox );
525  extent.scale( 1.1 );
526  mCanvas->setExtent( extent );
527  mCanvas->refresh();
528  }
529  }
530  else if ( canvasExtent == Pan )
531  {
532  QgsGeometry* centroid = geom->centroid();
533  QgsPoint center = centroid->asPoint();
534  delete centroid;
535  center = mCanvas->mapSettings().layerToMapCoordinates( mReferencedLayer, center );
536  mCanvas->zoomByFactor( 1.0, &center ); // refresh is done in this method
537  }
538 
539  // highlight
540  deleteHighlight();
541  mHighlight = new QgsHighlight( mCanvas, f, mReferencedLayer );
542  QSettings settings;
543  QColor color = QColor( settings.value( "/Map/highlight/color", QGis::DEFAULT_HIGHLIGHT_COLOR.name() ).toString() );
544  int alpha = settings.value( "/Map/highlight/colorAlpha", QGis::DEFAULT_HIGHLIGHT_COLOR.alpha() ).toInt();
545  double buffer = settings.value( "/Map/highlight/buffer", QGis::DEFAULT_HIGHLIGHT_BUFFER_MM ).toDouble();
546  double minWidth = settings.value( "/Map/highlight/minWidth", QGis::DEFAULT_HIGHLIGHT_MIN_WIDTH_MM ).toDouble();
547 
548  mHighlight->setColor( color ); // sets also fill with default alpha
549  color.setAlpha( alpha );
550  mHighlight->setFillColor( color ); // sets fill with alpha
551  mHighlight->setBuffer( buffer );
552  mHighlight->setMinWidth( minWidth );
553  mHighlight->show();
554 
555  QTimer* timer = new QTimer( this );
556  timer->setSingleShot( true );
557  connect( timer, SIGNAL( timeout() ), this, SLOT( deleteHighlight() ) );
558  timer->start( 3000 );
559 }
560 
561 void QgsRelationReferenceWidget::deleteHighlight()
562 {
563  if ( mHighlight )
564  {
565  mHighlight->hide();
566  delete mHighlight;
567  }
568  mHighlight = NULL;
569 }
570 
572 {
573  if ( !mAllowMapIdentification || !mReferencedLayer )
574  return;
575 
576  const QgsVectorLayerTools* tools = mEditorContext.vectorLayerTools();
577  if ( !tools )
578  return;
579  if ( !mCanvas )
580  return;
581 
582  mMapTool->setLayer( mReferencedLayer );
583  mCanvas->setMapTool( mMapTool );
584 
585  mWindowWidget = window();
586 
587  mCanvas->window()->raise();
588  mCanvas->activateWindow();
589 
590  connect( mMapTool, SIGNAL( featureIdentified( QgsFeature ) ), this, SLOT( featureIdentified( const QgsFeature ) ) );
591  connect( mMapTool, SIGNAL( deactivated() ), this, SLOT( mapToolDeactivated() ) );
592 
593  if ( mMessageBar )
594  {
595  QString title = QString( "Relation %1 for %2." ).arg( mRelationName ).arg( mReferencingLayer->name() );
596  QString msg = tr( "Identify a feature of %1 to be associated. Press <ESC> to cancel." ).arg( mReferencedLayer->name() );
597  mMessageBarItem = QgsMessageBar::createMessage( title, msg, this );
598  mMessageBar->pushItem( mMessageBarItem );
599  }
600 }
601 
602 void QgsRelationReferenceWidget::comboReferenceChanged( int index )
603 {
604  QgsFeatureId fid = mComboBox->itemData( index ).value<QgsFeatureId>();
605  QgsFeature feat;
606  mReferencedLayer->getFeatures( QgsFeatureRequest().setFilterFid( fid ) ).nextFeature( feat );
607  highlightFeature( feat );
608  updateAttributeEditorFrame( feat );
609  emit foreignKeyChanged( mFidFkMap.value( fid ) );
610 }
611 
612 void QgsRelationReferenceWidget::updateAttributeEditorFrame( const QgsFeature feature )
613 {
614  // Check if we're running with an embedded frame we need to update
615  if ( mAttributeEditorFrame )
616  {
617  if ( mReferencedAttributeForm )
618  {
619  mReferencedAttributeForm->setFeature( feature );
620  }
621  }
622 }
623 
624 void QgsRelationReferenceWidget::featureIdentified( const QgsFeature& feature )
625 {
626  if ( mReadOnlySelector )
627  {
628  QgsExpression expr( mReferencedLayer->displayExpression() );
629  QString title = expr.evaluate( &feature ).toString();
630  if ( expr.hasEvalError() )
631  {
632  title = feature.attribute( mFkeyFieldIdx ).toString();
633  }
634  mLineEdit->setText( title );
635  mForeignKey = feature.attribute( mFkeyFieldIdx );
636  mFeatureId = feature.id();
637  }
638  else
639  {
640  mComboBox->setCurrentIndex( mComboBox->findData( feature.attribute( mFkeyFieldIdx ) ) );
641  }
642 
643  mRemoveFKButton->setEnabled( mIsEditable );
644  highlightFeature( feature );
645  updateAttributeEditorFrame( feature );
646  emit foreignKeyChanged( foreignKey() );
647 
648  unsetMapTool();
649 }
650 
651 void QgsRelationReferenceWidget::unsetMapTool()
652 {
653  // deactivate map tool if activated
654  if ( mCanvas && mMapTool )
655  {
656  /* this will call mapToolDeactivated */
657  mCanvas->unsetMapTool( mMapTool );
658  }
659 }
660 
661 void QgsRelationReferenceWidget::mapToolDeactivated()
662 {
663  if ( mWindowWidget )
664  {
665  mWindowWidget->raise();
666  mWindowWidget->activateWindow();
667  }
668 
669  if ( mMessageBar && mMessageBarItem )
670  {
671  mMessageBar->popWidget( mMessageBarItem );
672  }
673  mMessageBarItem = NULL;
674 }
675