QGIS API Documentation  2.2.0-Valmiera
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
qgsattributedialog.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsattributedialog.cpp - description
3  -------------------
4  begin : October 2004
5  copyright : (C) 2004 by Marco Hugentobler
6  email : [email protected]
7  ***************************************************************************/
8 
9 /***************************************************************************
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  ***************************************************************************/
17 #include "qgsattributedialog.h"
18 #include "qgseditorwidgetwrapper.h"
19 #include "qgsfield.h"
20 #include "qgslogger.h"
21 #include "qgsmapcanvas.h"
22 #include "qgsproject.h"
23 #include "qgsrelationeditor.h"
24 #include "qgsvectordataprovider.h"
25 #include "qgsvectorlayer.h"
26 #include "qgsattributeeditor.h"
27 #include "qgshighlight.h"
28 #include "qgsexpression.h"
29 #include "qgspythonrunner.h"
30 
31 #include <QTableWidgetItem>
32 #include <QSettings>
33 #include <QLabel>
34 #include <QFrame>
35 #include <QScrollArea>
36 #include <QFile>
37 #include <QFileInfo>
38 #include <QDir>
39 #include <QDialogButtonBox>
40 #include <QUiLoader>
41 #include <QDialog>
42 #include <QVBoxLayout>
43 #include <QLineEdit>
44 #include <QWebView>
45 #include <QPushButton>
46 
48 QString QgsAttributeDialog::sSettingsPath = "/Windows/AttributeDialog/";
49 
50 QgsAttributeDialog::QgsAttributeDialog( QgsVectorLayer* vl, QgsFeature* thepFeature, bool featureOwner, QWidget* parent, bool showDialogButtons, QgsAttributeEditorContext context )
51  : QObject( parent )
52  , mDialog( 0 )
53  , mContext( context )
54  , mLayer( vl )
55  , mFeature( thepFeature )
56  , mFeatureOwner( featureOwner )
57  , mHighlight( 0 )
58  , mFormNr( sFormCounter++ )
59  , mShowDialogButtons( showDialogButtons )
60 {
62  init();
63 }
64 
65 QgsAttributeDialog::QgsAttributeDialog( QgsVectorLayer* vl, QgsFeature* thepFeature, bool featureOwner, QgsDistanceArea myDa, QWidget* parent, bool showDialogButtons )
66  : QObject( parent )
67  , mDialog( 0 )
68  , mContext( )
69  , mLayer( vl )
70  , mFeature( thepFeature )
71  , mFeatureOwner( featureOwner )
72  , mHighlight( 0 )
73  , mFormNr( sFormCounter++ )
74  , mShowDialogButtons( showDialogButtons )
75 {
76  mContext.setDistanceArea( myDa );
78  init();
79 }
80 
82 {
83  if ( !mFeature || !mLayer->dataProvider() )
84  return;
85 
86  const QgsFields &theFields = mLayer->pendingFields();
87  if ( theFields.isEmpty() )
88  return;
89 
90  QDialogButtonBox *buttonBox = NULL;
91 
92  if ( mLayer->editorLayout() == QgsVectorLayer::UiFileLayout && !mLayer->editForm().isEmpty() )
93  {
94  // UI-File defined layout
95  QFile file( mLayer->editForm() );
96 
97  if ( file.open( QFile::ReadOnly ) )
98  {
99  QUiLoader loader;
100 
101  QFileInfo fi( mLayer->editForm() );
102  loader.setWorkingDirectory( fi.dir() );
103  QWidget *myWidget = loader.load( &file, qobject_cast<QWidget*>( parent() ) );
104  file.close();
105 
106  mDialog = qobject_cast<QDialog*>( myWidget );
107  buttonBox = myWidget->findChild<QDialogButtonBox*>();
108  }
109  }
111  {
112  // Tab display
113  mDialog = new QDialog( qobject_cast<QWidget*>( parent() ) );
114 
115  QGridLayout *gridLayout;
116  QTabWidget *tabWidget;
117 
118  mDialog->resize( 447, 343 );
119  gridLayout = new QGridLayout( mDialog );
120  gridLayout->setObjectName( QString::fromUtf8( "gridLayout" ) );
121 
122  tabWidget = new QTabWidget( mDialog );
123  gridLayout->addWidget( tabWidget );
124 
125  foreach ( const QgsAttributeEditorElement *widgDef, mLayer->attributeEditorElements() )
126  {
127  QWidget* tabPage = new QWidget( tabWidget );
128 
129  tabWidget->addTab( tabPage, widgDef->name() );
130  QGridLayout *tabPageLayout = new QGridLayout( tabPage );
131 
133  {
134  QString dummy1;
135  bool dummy2;
136  tabPageLayout->addWidget( QgsAttributeEditor::createWidgetFromDef( widgDef, tabPage, mLayer, *mFeature, mContext, dummy1, dummy2 ) );
137  }
138  else
139  {
140  QgsDebugMsg( "No support for fields in attribute editor on top level" );
141  }
142  }
143 
144  buttonBox = new QDialogButtonBox( mDialog );
145  buttonBox->setObjectName( QString::fromUtf8( "buttonBox" ) );
146  gridLayout->addWidget( buttonBox );
147  }
148 
149  // Still no dialog: create the default generated dialog
150  if ( !mDialog )
151  {
152  mDialog = new QDialog( qobject_cast<QWidget*>( parent() ) );
153 
154  QGridLayout *gridLayout;
155  QFrame *mFrame;
156 
157  mDialog->resize( 447, 343 );
158  gridLayout = new QGridLayout( mDialog );
159  gridLayout->setSpacing( 6 );
160  gridLayout->setMargin( 2 );
161  gridLayout->setObjectName( QString::fromUtf8( "gridLayout" ) );
162  mFrame = new QFrame( mDialog );
163  mFrame->setObjectName( QString::fromUtf8( "mFrame" ) );
164  mFrame->setFrameShape( QFrame::NoFrame );
165  mFrame->setFrameShadow( QFrame::Plain );
166 
167  gridLayout->addWidget( mFrame, 0, 0, 1, 1 );
168 
169  buttonBox = new QDialogButtonBox( mDialog );
170  buttonBox->setObjectName( QString::fromUtf8( "buttonBox" ) );
171  gridLayout->addWidget( buttonBox, 2, 0, 1, 1 );
172 
173  //
174  //Set up dynamic inside a scroll box
175  //
176  QVBoxLayout *mypOuterLayout = new QVBoxLayout();
177  mypOuterLayout->setContentsMargins( 0, 0, 0, 0 );
178 
179  //transfers layout ownership so no need to call delete
180  mFrame->setLayout( mypOuterLayout );
181 
182  QScrollArea *mypScrollArea = new QScrollArea();
183  mypScrollArea->setFrameShape( QFrame::NoFrame );
184  mypScrollArea->setFrameShadow( QFrame::Plain );
185 
186  //transfers scroll area ownership so no need to call delete
187  mypOuterLayout->addWidget( mypScrollArea );
188 
189  QFrame *mypInnerFrame = new QFrame();
190  mypInnerFrame->setFrameShape( QFrame::NoFrame );
191  mypInnerFrame->setFrameShadow( QFrame::Plain );
192 
193  //transfers frame ownership so no need to call delete
194  mypScrollArea->setWidget( mypInnerFrame );
195 
196  mypScrollArea->setWidgetResizable( true );
197  QGridLayout *mypInnerLayout = new QGridLayout( mypInnerFrame );
198 
199  int index = 0;
200  for ( int fldIdx = 0; fldIdx < theFields.count(); ++fldIdx )
201  {
202  //show attribute alias if available
203  QString myFieldName = mLayer->attributeDisplayName( fldIdx );
204  // by default (until user defined alias) append date format
205  // (validator does not allow to enter a value in wrong format)
206  const QgsField &myField = theFields[fldIdx];
207  if ( myField.type() == QVariant::Date && mLayer->attributeAlias( fldIdx ).isEmpty() )
208  {
209  myFieldName += " (" + mLayer->dateFormat( fldIdx ) + ")";
210  }
211 
212  QWidget *myWidget = QgsAttributeEditor::createAttributeEditor( mDialog, 0, mLayer, fldIdx, mFeature->attribute( fldIdx ), mContext );
213  if ( !myWidget )
214  continue;
215 
216  QLabel *mypLabel = new QLabel( myFieldName, mypInnerFrame );
217 
218  if ( mLayer->editType( fldIdx ) != QgsVectorLayer::Immutable )
219  {
220  if ( mLayer->isEditable() && mLayer->fieldEditable( fldIdx ) )
221  {
222  myWidget->setEnabled( true );
223  }
224  else if ( mLayer->editType( fldIdx ) == QgsVectorLayer::Photo )
225  {
226  foreach ( QWidget *w, myWidget->findChildren<QWidget *>() )
227  {
228  w->setEnabled( qobject_cast<QLabel *>( w ) ? true : false );
229  }
230  }
231  else if ( mLayer->editType( fldIdx ) == QgsVectorLayer::WebView )
232  {
233  foreach ( QWidget *w, myWidget->findChildren<QWidget *>() )
234  {
235  if ( qobject_cast<QWebView *>( w ) )
236  w->setEnabled( true );
237  else if ( qobject_cast<QPushButton *>( w ) && w->objectName() == "openUrl" )
238  w->setEnabled( true );
239  else
240  w->setEnabled( false );
241  }
242  }
243  else if ( mLayer->editType( fldIdx ) == QgsVectorLayer::EditorWidgetV2 )
244  {
245  QgsEditorWidgetWrapper* ww = QgsEditorWidgetWrapper::fromWidget( myWidget );
246  if ( ww )
247  {
248  ww->setEnabled( false );
249  }
250  }
251  else
252  {
253  myWidget->setEnabled( false );
254  }
255  }
256 
257  if ( mLayer->labelOnTop( fldIdx ) )
258  {
259  mypInnerLayout->addWidget( mypLabel, index++, 0, 1, 2 );
260  mypInnerLayout->addWidget( myWidget, index++, 0, 1, 2 );
261  }
262  else
263  {
264  mypInnerLayout->addWidget( mypLabel, index, 0 );
265  mypInnerLayout->addWidget( myWidget, index, 1 );
266  ++index;
267  }
268  }
269 
270  QList<QgsRelation> relations = QgsProject::instance()->relationManager()->referencedRelations( mLayer );
271 
272  foreach ( const QgsRelation& relation, relations )
273  {
275  if ( !myWidget )
276  continue;
277 
278  myWidget->setProperty( "qgisRelation", relation.id() );
279  myWidget->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
280  mypInnerLayout->addWidget( myWidget, index, 0, 1, 2 );
281  mypInnerLayout->setRowStretch( index, 2 );
282  ++index;
283  }
284 
285  // Set focus to first widget in list, to help entering data without moving the mouse.
286  if ( mypInnerLayout->rowCount() > 0 )
287  {
288  QWidget* widget = mypInnerLayout->itemAtPosition( 0, 1 )->widget();
289  if ( widget )
290  widget->setFocus( Qt::OtherFocusReason );
291  }
292 
293  QSpacerItem *mypSpacer = new QSpacerItem( 0, 0, QSizePolicy::Fixed, QSizePolicy::MinimumExpanding );
294  mypInnerLayout->addItem( mypSpacer, mypInnerLayout->rowCount() + 1, 0 );
295  }
296  else
297  {
298 #if 0
299  QgsDistanceArea myDa;
300 
301  myDa.setSourceCrs( vl->crs().srsid() );
302  myDa.setEllipsoidalMode( QgisApp::instance()->mapCanvas()->mapRenderer()->hasCrsTransformEnabled() );
303  myDa.setEllipsoid( QgsProject::instance()->readEntry( "Measure", "/Ellipsoid", GEO_NONE ) );
304 #endif
305 
306  // Get all widgets on the dialog
307  QList<QWidget *> myWidgets = mDialog->findChildren<QWidget*>();
308  Q_FOREACH( QWidget* myWidget, myWidgets )
309  {
310  // Check the widget's properties for a relation definition
311  QVariant vRel = myWidget->property( "qgisRelation" );
312  if ( vRel.isValid() )
313  {
315  QgsRelation relation = relMgr->relation( vRel.toString() );
316  if ( relation.isValid() )
317  {
319  if ( !myWidget->layout() )
320  {
321  myWidget->setLayout( new QHBoxLayout() );
322  }
323  myWidget->layout()->addWidget( relWdg );
324  }
325  }
326  else
327  {
328  // No widget definition properties defined, check if the widget's
329  // objectName matches a field name
330  for ( int fldIdx = 0; fldIdx < theFields.count(); ++fldIdx )
331  {
332  if ( myWidget->objectName() == theFields[fldIdx].name() )
333  {
335 
336  if ( mLayer->editType( fldIdx ) != QgsVectorLayer::Immutable )
337  {
338  if ( mLayer->isEditable() && mLayer->fieldEditable( fldIdx ) )
339  {
340  myWidget->setEnabled( true );
341  }
342  else if ( mLayer->editType( fldIdx ) == QgsVectorLayer::Photo )
343  {
344  foreach ( QWidget *w, myWidget->findChildren<QWidget *>() )
345  {
346  w->setEnabled( qobject_cast<QLabel *>( w ) ? true : false );
347  }
348  }
349  else if ( mLayer->editType( fldIdx ) == QgsVectorLayer::WebView )
350  {
351  foreach ( QWidget *w, myWidget->findChildren<QWidget *>() )
352  {
353  w->setEnabled( qobject_cast<QWebView *>( w ) ? true : false );
354  }
355  }
356  else if ( mLayer->editType( fldIdx ) == QgsVectorLayer::EditorWidgetV2 )
357  {
358  QgsEditorWidgetWrapper* ww = QgsEditorWidgetWrapper::fromWidget( myWidget );
359  if ( ww )
360  {
361  ww->setEnabled( false );
362  }
363  }
364  else
365  {
366  myWidget->setEnabled( false );
367  }
368  }
369  }
370  }
371  }
372  }
373 
374  foreach ( QLineEdit *le, mDialog->findChildren<QLineEdit*>() )
375  {
376  if ( !le->objectName().startsWith( "expr_" ) )
377  continue;
378 
379  le->setReadOnly( true );
380  QString expr = le->text();
381  le->setText( tr( "Error" ) );
382 
383  QgsExpression exp( expr );
384  if ( exp.hasParserError() )
385  continue;
386 
387 
388  if ( !mFeature->geometry() && exp.needsGeometry() )
389  {
390  QgsFeature f;
391  if ( mLayer->getFeatures( QgsFeatureRequest().setFilterFid( mFeature->id() ).setSubsetOfAttributes( QgsAttributeList() ) ).nextFeature( f ) && f.geometry() )
392  {
393  mFeature->setGeometry( *f.geometry() );
394  }
395  }
396 
398 
399  QVariant value = exp.evaluate( mFeature, mLayer->pendingFields() );
400 
401  if ( !exp.hasEvalError() )
402  {
403  QString text;
404  switch ( value.type() )
405  {
406  case QVariant::Invalid: text = "NULL"; break;
407  case QVariant::Int: text = QString::number( value.toInt() ); break;
408  case QVariant::Double: text = QString::number( value.toDouble() ); break;
409  case QVariant::String:
410  default: text = value.toString();
411  }
412  le->setText( text );
413  }
414  else
415  {
416  le->setText( tr( "Error: %1" ).arg( exp.evalErrorString() ) );
417  }
418  }
419  }
420 
422 
423  if ( mDialog )
424  {
425  if ( mDialog->objectName().isEmpty() )
426  mDialog->setObjectName( "QgsAttributeDialogBase" );
427 
428  if ( mDialog->windowTitle().isEmpty() )
429  mDialog->setWindowTitle( tr( "Attributes - %1" ).arg( mLayer->name() ) );
430  }
431 
432  if ( mShowDialogButtons )
433  {
434  if ( buttonBox )
435  {
436  buttonBox->clear();
437 
438  if ( mLayer->isEditable() )
439  {
440  buttonBox->setStandardButtons( QDialogButtonBox::Ok | QDialogButtonBox::Cancel );
441  connect( buttonBox, SIGNAL( accepted() ), mDialog, SLOT( accept() ) );
442  connect( buttonBox, SIGNAL( accepted() ), this, SLOT( accept() ) );
443  }
444  else
445  {
446  buttonBox->setStandardButtons( QDialogButtonBox::Cancel );
447  }
448 
449  connect( buttonBox, SIGNAL( rejected() ), mDialog, SLOT( reject() ) );
450  }
451  }
452  else
453  {
454  if ( buttonBox )
455  {
456  // Add dummy buttons
457  if ( mLayer->isEditable() )
458  {
459  buttonBox->clear();
460 
461  buttonBox->setStandardButtons( QDialogButtonBox::Ok | QDialogButtonBox::Cancel );
462  connect( buttonBox, SIGNAL( accepted() ), mDialog, SLOT( accept() ) );
463  connect( buttonBox, SIGNAL( accepted() ), this, SLOT( accept() ) );
464  }
465 
466  buttonBox->setVisible( false );
467  }
468  }
469 
470  QMetaObject::connectSlotsByName( mDialog );
471 
472  connect( mDialog, SIGNAL( destroyed() ), this, SLOT( dialogDestroyed() ) );
473 
474  if ( !mLayer->editFormInit().isEmpty() )
475  {
476 #if 0
477  // would be nice if only PyQt's QVariant.toPyObject() wouldn't take ownership
478  vl->setProperty( "featureForm.dialog", QVariant::fromValue( qobject_cast<QObject*>( mDialog ) ) );
479  vl->setProperty( "featureForm.id", QVariant( mpFeature->id() ) );
480 #endif
481 
482  QString module = mLayer->editFormInit();
483  int pos = module.lastIndexOf( "." );
484  if ( pos >= 0 )
485  {
486  QgsPythonRunner::run( QString( "import %1" ).arg( module.left( pos ) ) );
487  }
488 
489  /* Reload the module if the DEBUGMODE switch has been set in the module.
490  If set to False you have to reload QGIS to reset it to True due to Python
491  module caching */
492  QString reload = QString( "if hasattr(%1,'DEBUGMODE') and %1.DEBUGMODE:"
493  " reload(%1)" ).arg( module.left( pos ) );
494 
495  QgsPythonRunner::run( reload );
496 
497  QString form = QString( "_qgis_featureform_%1 = sip.wrapinstance( %2, QtGui.QDialog )" )
498  .arg( mFormNr )
499  .arg(( unsigned long ) mDialog );
500 
501  QString layer = QString( "_qgis_layer_%1 = sip.wrapinstance( %2, qgis.core.QgsVectorLayer )" )
502  .arg( mLayer->id() )
503  .arg(( unsigned long ) mLayer );
504 
505  // Generate the unique ID of this feature. We used to use feature ID but some providers
506  // return a ID that is an invalid python variable when we have new unsaved features.
507  QDateTime dt = QDateTime::currentDateTime();
508  QString featurevarname = QString( "_qgis_feature_%1" ).arg( dt.toString( "yyyyMMddhhmmsszzz" ) );
509  QString feature = QString( "%1 = sip.wrapinstance( %2, qgis.core.QgsFeature )" )
510  .arg( featurevarname )
511  .arg(( unsigned long ) mFeature );
512 
513  QgsPythonRunner::run( form );
514  QgsPythonRunner::run( feature );
515  QgsPythonRunner::run( layer );
516 
517  mReturnvarname = QString( "_qgis_feature_form_%1" ).arg( dt.toString( "yyyyMMddhhmmsszzz" ) );
518  QString expr = QString( "%5 = %1(_qgis_featureform_%2, _qgis_layer_%3, %4)" )
519  .arg( mLayer->editFormInit() )
520  .arg( mFormNr )
521  .arg( mLayer->id() )
522  .arg( featurevarname )
523  .arg( mReturnvarname );
524 
525  QgsDebugMsg( QString( "running featureForm init: %1" ).arg( expr ) );
526  QgsPythonRunner::run( expr );
527  }
528 
529  // Only restore the geometry of the dialog if it's not a custom one.
531  {
532  restoreGeometry();
533  }
534 }
535 
536 
538 {
539  if ( mHighlight )
540  {
541  mHighlight->hide();
542  delete mHighlight;
543  }
544 
545  if ( mFeatureOwner )
546  {
547  delete mFeature;
548  }
549 
550  // Only save the geometry of the dialog if it's not a custom one.
552  {
553  saveGeometry();
554  }
555 
556  if ( mDialog )
557  {
558  delete mDialog;
559  }
560 }
561 
563 {
564  if ( !mLayer->isEditable() || !mFeature )
565  return;
566 
567  //write the new values back to the feature
568  const QgsFields& fields = mLayer->pendingFields();
569  for ( int idx = 0; idx < fields.count(); ++idx )
570  {
571  QVariant value;
572 
574  mFeature->setAttribute( idx, value );
575  }
576 }
577 
579 {
580  if ( mDialog )
581  {
582  return mDialog->exec();
583  }
584  else
585  {
586  QgsDebugMsg( "No dialog" );
587  return QDialog::Accepted;
588  }
589 }
590 
592 {
593  if ( mDialog )
594  {
595  mDialog->setAttribute( Qt::WA_DeleteOnClose );
596  mDialog->show();
597  mDialog->raise();
598  mDialog->activateWindow();
599  mDialog->installEventFilter( this );
600  }
601 }
602 
604 {
605  if ( mDialog )
606  {
607  QSettings settings;
608  settings.setValue( mSettingsPath + "geometry", mDialog->saveGeometry() );
609  }
610 }
611 
613 {
614  if ( mDialog )
615  {
616  QSettings settings;
617  mDialog->restoreGeometry( settings.value( mSettingsPath + "geometry" ).toByteArray() );
618  }
619 }
620 
622 {
623  if ( mHighlight )
624  {
625  delete mHighlight;
626  }
627 
628  mHighlight = h;
629 }
630 
631 
633 {
634 #if 0
635  mLayer->setProperty( "featureForm.dialog", QVariant() );
636  mLayer->setProperty( "featureForm.id", QVariant() );
637 #endif
638  if ( -1 < mFormNr )
639  {
640  QString expr = QString( "if locals().has_key('_qgis_featureform_%1'): del _qgis_featureform_%1\n" ).arg( mFormNr );
641  QgsPythonRunner::run( expr );
642  }
643 
644  if ( !mReturnvarname.isEmpty() )
645  {
646  QString expr = QString( "if locals().has_key('%1'): del %1\n" ).arg( mReturnvarname );
647  QgsPythonRunner::run( expr );
648  }
649 
650  mDialog = NULL;
651  deleteLater();
652 }
653 
654 bool QgsAttributeDialog::eventFilter( QObject *obj, QEvent *e )
655 {
656  if ( mHighlight && obj == mDialog )
657  {
658  switch ( e->type() )
659  {
660  case QEvent::WindowActivate:
661  mHighlight->show();
662  break;
663  case QEvent::WindowDeactivate:
664  mHighlight->hide();
665  break;
666  default:
667  break;
668  }
669  }
670 
671  return false;
672 }