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