QGIS API Documentation  2.2.0-Valmiera
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
qgsdatadefinedbutton.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsdatadefinedbutton.cpp - Data defined selector button
3  --------------------------------------
4  Date : 27-April-2013
5  Copyright : (C) 2013 by Larry Shaffer
6  Email : larrys at dakcarto dot com
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 "qgsdatadefinedbutton.h"
17 
18 #include <qgsapplication.h>
19 #include <qgsdatadefined.h>
21 #include <qgsexpression.h>
22 #include <qgsmessageviewer.h>
23 #include <qgsvectorlayer.h>
24 
25 #include <QClipboard>
26 #include <QMenu>
27 #include <QMouseEvent>
28 #include <QPointer>
29 
30 
37 
39  const QgsVectorLayer* vl,
40  const QgsDataDefined* datadefined,
41  DataTypes datatypes,
42  QString description )
43  : QToolButton( parent )
44 {
45  // set up static icons
46  if ( mIconDataDefine.isNull() )
47  {
48  mIconDataDefine = QgsApplication::getThemeIcon( "/mIconDataDefine.svg" );
49  mIconDataDefineOn = QgsApplication::getThemeIcon( "/mIconDataDefineOn.svg" );
50  mIconDataDefineError = QgsApplication::getThemeIcon( "/mIconDataDefineError.svg" );
51  mIconDataDefineExpression = QgsApplication::getThemeIcon( "/mIconDataDefineExpression.svg" );
52  mIconDataDefineExpressionOn = QgsApplication::getThemeIcon( "/mIconDataDefineExpressionOn.svg" );
53  mIconDataDefineExpressionError = QgsApplication::getThemeIcon( "/mIconDataDefineExpressionError.svg" );
54  }
55 
56  // set default tool button icon properties
57  setFixedSize( 28, 24 );
58  setStyleSheet( QString( "QToolButton{ background: none; border: none;}" ) );
59  setIconSize( QSize( 24, 24 ) );
60  setPopupMode( QToolButton::InstantPopup );
61 
62  mDefineMenu = new QMenu( this );
63  connect( mDefineMenu, SIGNAL( aboutToShow() ), this, SLOT( aboutToShowMenu() ) );
64  connect( mDefineMenu, SIGNAL( triggered( QAction* ) ), this, SLOT( menuActionTriggered( QAction* ) ) );
65  setMenu( mDefineMenu );
66 
67  mFieldsMenu = new QMenu( this );
68 
69  mActionDataTypes = new QAction( this );
70  // list fields and types in submenu, since there may be many
71  mActionDataTypes->setMenu( mFieldsMenu );
72 
73  mActionActive = new QAction( this );
74  QFont f = mActionActive->font();
75  f.setBold( true );
76  mActionActive->setFont( f );
77 
78  mActionDescription = new QAction( tr( "Description..." ), this );
79 
80  mActionExpDialog = new QAction( tr( "Edit..." ), this );
82  mActionPasteExpr = new QAction( tr( "Paste" ), this );
83  mActionCopyExpr = new QAction( tr( "Copy" ), this );
84  mActionClearExpr = new QAction( tr( "Clear" ), this );
85 
86  // set up sibling widget connections
87  connect( this, SIGNAL( dataDefinedActivated( bool ) ), this, SLOT( disableEnabledWidgets( bool ) ) );
88  connect( this, SIGNAL( dataDefinedActivated( bool ) ), this, SLOT( checkCheckedWidgets( bool ) ) );
89 
90  init( vl, datadefined, datatypes, description );
91 }
92 
94 {
95  mEnabledWidgets.clear();
96  mCheckedWidgets.clear();
97 }
98 
100  const QgsDataDefined* datadefined,
101  DataTypes datatypes,
102  QString description )
103 {
104  mVectorLayer = vl;
105  // construct default property if none or incorrect passed in
106  if ( !datadefined )
107  {
108  mProperty.insert( "active", "0" );
109  mProperty.insert( "useexpr", "0" );
110  mProperty.insert( "expression", "" );
111  mProperty.insert( "field", "" );
112  }
113  else
114  {
115  mProperty.insert( "active", datadefined->isActive() ? "1" : "0" );
116  mProperty.insert( "useexpr", datadefined->useExpression() ? "1" : "0" );
117  mProperty.insert( "expression", datadefined->expressionString() );
118  mProperty.insert( "field", datadefined->field() );
119  }
120 
121  mDataTypes = datatypes;
122  mFieldNameList.clear();
123  mFieldTypeList.clear();
124 
125  mInputDescription = description;
126  mFullDescription = QString( "" );
127  mUsageInfo = QString( "" );
128  mCurrentDefinition = QString( "" );
129 
130  // set up data types string
131  mDataTypesString = QString( "" );
132 
133  QStringList ts;
134  if ( mDataTypes.testFlag( AnyType ) || mDataTypes.testFlag( String ) )
135  {
136  ts << tr( "string" );
137  }
138  if ( mDataTypes.testFlag( AnyType ) || mDataTypes.testFlag( Int ) )
139  {
140  ts << tr( "int" );
141  }
142  if ( mDataTypes.testFlag( AnyType ) || mDataTypes.testFlag( Double ) )
143  {
144  ts << tr( "double" );
145  }
146 
147  if ( !ts.isEmpty() )
148  {
149  mDataTypesString = ts.join( ", " );
150  mActionDataTypes->setText( tr( "Field type: " ) + mDataTypesString );
151  }
152 
153  if ( mVectorLayer )
154  {
155  // store just a list of fields of unknown type or those that match the expected type
156  const QgsFields& fields = mVectorLayer->pendingFields();
157  for ( int i = 0; i < fields.count(); ++i )
158  {
159  const QgsField& f = fields.at( i );
160  bool fieldMatch = false;
161  // NOTE: these are the only QVariant enums supported at this time (see QgsField)
162  QString fieldType;
163  switch ( f.type() )
164  {
165  case QVariant::String:
166  fieldMatch = mDataTypes.testFlag( String );
167  fieldType = tr( "string" );
168  break;
169  case QVariant::Int:
170  fieldMatch = mDataTypes.testFlag( Int );
171  fieldType = tr( "integer" );
172  break;
173  case QVariant::Double:
174  fieldMatch = mDataTypes.testFlag( Double );
175  fieldType = tr( "double" );
176  break;
177  case QVariant::Invalid:
178  default:
179  fieldMatch = true; // field type is unknown
180  fieldType = tr( "unknown type" );
181  }
182  if ( fieldMatch || mDataTypes.testFlag( AnyType ) )
183  {
184  mFieldNameList << f.name();
185  mFieldTypeList << fieldType;
186  }
187  }
188  }
189 
190  updateGui();
191 }
192 
193 void QgsDataDefinedButton::mouseReleaseEvent( QMouseEvent *event )
194 {
195  // Ctrl-click to toggle activated state
196  if (( event->modifiers() & ( Qt::ControlModifier ) )
197  || event->button() == Qt::RightButton )
198  {
199  setActive( !isActive() );
200  updateGui();
201  event->ignore();
202  return;
203  }
204 
205  // pass to default behaviour
206  QToolButton::mousePressEvent( event );
207 }
208 
210 {
211  mDefineMenu->clear();
212 
213  bool hasExp = !getExpression().isEmpty();
214  bool hasField = !getField().isEmpty();
215  QString ddTitle = tr( "Data defined override" );
216 
217  QAction* ddTitleAct = mDefineMenu->addAction( ddTitle );
218  QFont titlefont = ddTitleAct->font();
219  titlefont.setItalic( true );
220  ddTitleAct->setFont( titlefont );
221  ddTitleAct->setEnabled( false );
222 
223  bool addActiveAction = false;
224  if ( useExpression() && hasExp )
225  {
226  QgsExpression exp( getExpression() );
227  // whether expression is parse-able
228  addActiveAction = !exp.hasParserError();
229  }
230  else if ( !useExpression() && hasField )
231  {
232  // whether field exists
233  addActiveAction = mFieldNameList.contains( getField() );
234  }
235 
236  if ( addActiveAction )
237  {
238  ddTitleAct->setText( ddTitle + " (" + ( useExpression() ? tr( "expression" ) : tr( "field" ) ) + ")" );
239  mDefineMenu->addAction( mActionActive );
240  mActionActive->setText( isActive() ? tr( "Deactivate" ) : tr( "Activate" ) );
241  mActionActive->setData( QVariant( isActive() ? false : true ) );
242  }
243 
244  if ( !mFullDescription.isEmpty() )
245  {
246  mDefineMenu->addAction( mActionDescription );
247  }
248 
249  mDefineMenu->addSeparator();
250 
251  if ( !mDataTypesString.isEmpty() )
252  {
253  QAction* fieldTitleAct = mDefineMenu->addAction( tr( "Attribute field" ) );
254  fieldTitleAct->setFont( titlefont );
255  fieldTitleAct->setEnabled( false );
256 
257  mDefineMenu->addAction( mActionDataTypes );
258 
259  mFieldsMenu->clear();
260 
261  if ( mFieldNameList.size() > 0 )
262  {
263 
264  for ( int j = 0; j < mFieldNameList.count(); ++j )
265  {
266  QString fldname = mFieldNameList.at( j );
267  QAction* act = mFieldsMenu->addAction( fldname + " (" + mFieldTypeList.at( j ) + ")" );
268  act->setData( QVariant( fldname ) );
269  if ( getField() == fldname )
270  {
271  act->setCheckable( true );
272  act->setChecked( !useExpression() );
273  }
274  }
275  }
276  else
277  {
278  QAction* act = mFieldsMenu->addAction( tr( "No matching field types found" ) );
279  act->setEnabled( false );
280  }
281 
282  mDefineMenu->addSeparator();
283  }
284 
285  QAction* exprTitleAct = mDefineMenu->addAction( tr( "Expression" ) );
286  exprTitleAct->setFont( titlefont );
287  exprTitleAct->setEnabled( false );
288 
289  if ( hasExp )
290  {
291  QString expString = getExpression();
292  if ( expString.length() > 35 )
293  {
294  expString.truncate( 35 );
295  expString.append( "..." );
296  }
297 
298  expString.prepend( tr( "Current: " ) );
299 
300  if ( !mActionExpression )
301  {
302  mActionExpression = new QAction( expString, this );
303  mActionExpression->setCheckable( true );
304  }
305  else
306  {
307  mActionExpression->setText( expString );
308  }
309  mDefineMenu->addAction( mActionExpression );
310  mActionExpression->setChecked( useExpression() );
311 
312  mDefineMenu->addAction( mActionExpDialog );
313  mDefineMenu->addAction( mActionCopyExpr );
314  mDefineMenu->addAction( mActionPasteExpr );
315  mDefineMenu->addAction( mActionClearExpr );
316  }
317  else
318  {
319  mDefineMenu->addAction( mActionExpDialog );
320  mDefineMenu->addAction( mActionPasteExpr );
321  }
322 
323 }
324 
326 {
327  if ( action == mActionActive )
328  {
329  setActive( mActionActive->data().toBool() );
330  updateGui();
331  }
332  else if ( action == mActionDescription )
333  {
335  }
336  else if ( action == mActionExpDialog )
337  {
339  }
340  else if ( action == mActionExpression )
341  {
342  setUseExpression( true );
343  setActive( true );
344  updateGui();
345  }
346  else if ( action == mActionCopyExpr )
347  {
348  QApplication::clipboard()->setText( getExpression() );
349  }
350  else if ( action == mActionPasteExpr )
351  {
352  QString exprString = QApplication::clipboard()->text();
353  if ( !exprString.isEmpty() )
354  {
355  setExpression( exprString );
356  setUseExpression( true );
357  setActive( true );
358  updateGui();
359  }
360  }
361  else if ( action == mActionClearExpr )
362  {
363  // only deactivate if defined expression is being used
364  if ( isActive() && useExpression() )
365  {
366  setUseExpression( false );
367  setActive( false );
368  }
369  setExpression( QString( "" ) );
370  updateGui();
371  }
372  else if ( mFieldsMenu->actions().contains( action ) ) // a field name clicked
373  {
374  if ( action->isEnabled() )
375  {
376  if ( getField() != action->text() )
377  {
378  setField( action->data().toString() );
379  }
380  setUseExpression( false );
381  setActive( true );
382  updateGui();
383  }
384  }
385 }
386 
388 {
389  QgsMessageViewer* mv = new QgsMessageViewer( this );
390  mv->setWindowTitle( tr( "Data definition description" ) );
392  mv->exec();
393 }
394 
396 {
397  QgsExpressionBuilderDialog d( const_cast<QgsVectorLayer*>( mVectorLayer ), getExpression() );
398  if ( d.exec() == QDialog::Accepted )
399  {
400  QString newExp = d.expressionText();
401  setExpression( d.expressionText().trimmed() );
402  bool hasExp = !newExp.isEmpty();
403 
404  setUseExpression( hasExp );
405  setActive( hasExp );
406  updateGui();
407  }
408  activateWindow(); // reset focus to parent window
409 }
410 
412 {
413  QString oldDef = mCurrentDefinition;
414  QString newDef( "" );
415  bool hasExp = !getExpression().isEmpty();
416  bool hasField = !getField().isEmpty();
417 
418  if ( useExpression() && !hasExp )
419  {
420  setActive( false );
421  setUseExpression( false );
422  }
423  else if ( !useExpression() && !hasField )
424  {
425  setActive( false );
426  }
427 
428  QIcon icon = mIconDataDefine;
429  QString deftip = tr( "undefined" );
430  if ( useExpression() && hasExp )
431  {
433  newDef = deftip = getExpression();
434 
435  QgsExpression exp( getExpression() );
436  if ( exp.hasParserError() )
437  {
438  setActive( false );
440  deftip = tr( "Parse error: %1" ).arg( exp.parserErrorString() );
441  newDef = "";
442  }
443  }
444  else if ( !useExpression() && hasField )
445  {
447  newDef = deftip = getField();
448 
449  if ( !mFieldNameList.contains( getField() ) )
450  {
451  setActive( false );
452  icon = mIconDataDefineError;
453  deftip = tr( "'%1' field missing" ).arg( getField() );
454  newDef = "";
455  }
456  }
457 
458  setIcon( icon );
459 
460  // update and emit current definition
461  if ( newDef != oldDef )
462  {
463  mCurrentDefinition = newDef;
465  }
466 
467  // build full description for tool tip and popup dialog
468  mFullDescription = tr( "<b><u>Data defined override</u></b><br>" );
469 
470  mFullDescription += tr( "<b>Active: </b>%1&nbsp;&nbsp;&nbsp;<i>(ctrl|right-click toggles)</i><br>" ).arg( isActive() ? tr( "yes" ) : tr( "no" ) );
471 
472  if ( !mUsageInfo.isEmpty() )
473  {
474  mFullDescription += tr( "<b>Usage:</b><br>%1<br>" ).arg( mUsageInfo );
475  }
476 
477  if ( !mInputDescription.isEmpty() )
478  {
479  mFullDescription += tr( "<b>Expected input:</b><br>%1<br>" ).arg( mInputDescription );
480  }
481 
482  if ( !mDataTypesString.isEmpty() )
483  {
484  mFullDescription += tr( "<b>Valid input types:</b><br>%1<br>" ).arg( mDataTypesString );
485  }
486 
487  QString deftype( "" );
488  if ( deftip != tr( "undefined" ) )
489  {
490  deftype = QString( " (%1)" ).arg( useExpression() ? tr( "expression" ) : tr( "field" ) );
491  }
492 
493  // truncate long expressions, or tool tip may be too wide for screen
494  if ( deftip.length() > 75 )
495  {
496  deftip.truncate( 75 );
497  deftip.append( "..." );
498  }
499 
500  mFullDescription += tr( "<b>Current definition %1:</b><br>%2" ).arg( deftype ).arg( deftip );
501 
502  setToolTip( mFullDescription );
503 
504 }
505 
507 {
508  if ( isActive() != active )
509  {
510  mProperty.insert( "active", active ? "1" : "0" );
511  emit dataDefinedActivated( active );
512  }
513 }
514 
515 void QgsDataDefinedButton::registerEnabledWidgets( QList<QWidget*> wdgts )
516 {
517  for ( int i = 0; i < wdgts.size(); ++i )
518  {
519  registerEnabledWidget( wdgts.at( i ) );
520  }
521 }
522 
524 {
525  QPointer<QWidget> wdgtP( wdgt );
526  if ( !mEnabledWidgets.contains( wdgtP ) )
527  {
528  mEnabledWidgets.append( wdgtP );
529  }
530 }
531 
533 {
534  QList<QWidget*> wdgtList;
535  for ( int i = 0; i < mEnabledWidgets.size(); ++i )
536  {
537  wdgtList << mEnabledWidgets.at( i );
538  }
539  return wdgtList;
540 }
541 
543 {
544  for ( int i = 0; i < mEnabledWidgets.size(); ++i )
545  {
546  mEnabledWidgets.at( i )->setDisabled( disable );
547  }
548 }
549 
550 void QgsDataDefinedButton::registerCheckedWidgets( QList<QWidget*> wdgts )
551 {
552  for ( int i = 0; i < wdgts.size(); ++i )
553  {
554  registerCheckedWidget( wdgts.at( i ) );
555  }
556 }
557 
559 {
560  QPointer<QWidget> wdgtP( wdgt );
561  if ( !mCheckedWidgets.contains( wdgtP ) )
562  {
563  mCheckedWidgets.append( wdgtP );
564  }
565 }
566 
568 {
569  QList<QWidget*> wdgtList;
570  for ( int i = 0; i < mCheckedWidgets.size(); ++i )
571  {
572  wdgtList << mCheckedWidgets.at( i );
573  }
574  return wdgtList;
575 }
576 
578 {
579  // don't uncheck, only set to checked
580  if ( !check )
581  {
582  return;
583  }
584  for ( int i = 0; i < mCheckedWidgets.size(); ++i )
585  {
586  QAbstractButton *btn = qobject_cast< QAbstractButton * >( mCheckedWidgets.at( i ) );
587  if ( btn && btn->isCheckable() )
588  {
589  btn->setChecked( true );
590  continue;
591  }
592  QGroupBox *grpbx = qobject_cast< QGroupBox * >( mCheckedWidgets.at( i ) );
593  if ( grpbx && grpbx->isCheckable() )
594  {
595  grpbx->setChecked( true );
596  }
597  }
598 }
599 
601 {
602  // just something to reduce translation redundancy
603  return tr( "string " );
604 }
605 
607 {
608  return tr( "bool [<b>1</b>=True|<b>0</b>=False]" );
609 }
610 
612 {
613  return tr( "string of variable length" );
614 }
615 
617 {
618  return tr( "int [&lt;= 0 =&gt;]" );
619 }
620 
622 {
623  return tr( "int [&gt;= 0]" );
624 }
625 
627 {
628  return tr( "int [&gt;= 1]" );
629 }
630 
632 {
633  return tr( "double [&lt;= 0.0 =&gt;]" );
634 }
635 
637 {
638  return tr( "double [&gt;= 0.0]" );
639 }
640 
642 {
643  return tr( "double coord [<b>X,Y</b>] as &lt;= 0.0 =&gt;" );
644 }
645 
647 {
648  return tr( "double [-180.0 - 180.0]" );
649 }
650 
652 {
653  return tr( "int [0-100]" );
654 }
655 
657 {
658  return trString() + "[<b>MM</b>|<b>MapUnit</b>]";
659 }
660 
662 {
663  return trString() + "[<b>MM</b>|<b>MapUnit</b>|<b>Percent</b>]";
664 }
665 
667 {
668  return tr( "string [<b>r,g,b</b>] as int 0-255" );
669 }
670 
672 {
673  return tr( "string [<b>r,g,b,a</b>] as int 0-255" );
674 }
675 
677 {
678  return trString() + "[<b>Left</b>|<b>Center</b>|<b>Right</b>]";
679 }
680 
682 {
683  return trString() + "[<b>Bottom</b>|<b>Middle</b>|<b>Top</b>]";
684 }
685 
687 {
688  return trString() + "[<b>Bevel</b>|<b>Miter</b>|<b>Round</b>]";
689 }
690 
692 {
693  return trString() + QString( "[<b>Normal</b>|<b>Lighten</b>|<b>Screen</b>|<b>Dodge</b>|<br>"
694  "<b>Addition</b>|<b>Darken</b>|<b>Multiply</b>|<b>Burn</b>|<b>Overlay</b>|<br>"
695  "<b>SoftLight</b>|<b>HardLight</b>|<b>Difference</b>|<b>Subtract</b>" );
696 }
697 
699 {
700  return trString() + QString( "[<b>filepath</b>] as<br>"
701  "<b>''</b>=empty|absolute|search-paths-relative|<br>"
702  "project-relative|URL" );
703 }