QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
qgsrulebasedlabelingwidget.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsrulebasedlabelingwidget.cpp
3  ---------------------
4  begin : September 2015
5  copyright : (C) 2015 by Martin Dobias
6  email : wonder dot sk at gmail 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  ***************************************************************************/
16 
17 #include "qgsapplication.h"
19 #include "qgsfeatureiterator.h"
20 #include "qgslabelinggui.h"
21 #include "qgsmapcanvas.h"
22 #include "qgsproject.h"
23 #include "qgsreadwritecontext.h"
24 #include "qgsrulebasedlabeling.h"
25 #include "qgsvectorlayer.h"
26 #include "qgsvectorlayerlabeling.h"
27 #include "qgslogger.h"
29 
30 #include <QAction>
31 #include <QClipboard>
32 #include <QMessageBox>
33 
34 const double ICON_PADDING_FACTOR = 0.16;
35 
36 static QList<QgsExpressionContextScope *> _globalProjectAtlasMapLayerScopes( QgsMapCanvas *mapCanvas, const QgsMapLayer *layer )
37 {
38  QList<QgsExpressionContextScope *> scopes;
42  if ( mapCanvas )
43  {
46  }
47  else
48  {
50  }
51  scopes << QgsExpressionContextUtils::layerScope( layer );
52  return scopes;
53 }
54 
55 
57  : QgsPanelWidget( parent )
58  , mLayer( layer )
59  , mCanvas( canvas )
60 
61 {
62  setupUi( this );
63 
64  btnAddRule->setIcon( QIcon( QgsApplication::iconPath( "symbologyAdd.svg" ) ) );
65  btnEditRule->setIcon( QIcon( QgsApplication::iconPath( "symbologyEdit.svg" ) ) );
66  btnRemoveRule->setIcon( QIcon( QgsApplication::iconPath( "symbologyRemove.svg" ) ) );
67 
68  mCopyAction = new QAction( tr( "Copy" ), this );
69  mCopyAction->setShortcut( QKeySequence( QKeySequence::Copy ) );
70  mPasteAction = new QAction( tr( "Paste" ), this );
71  mPasteAction->setShortcut( QKeySequence( QKeySequence::Paste ) );
72  mDeleteAction = new QAction( tr( "Remove Rule" ), this );
73  mDeleteAction->setShortcut( QKeySequence( QKeySequence::Delete ) );
74 
75  viewRules->addAction( mCopyAction );
76  viewRules->addAction( mPasteAction );
77  viewRules->addAction( mDeleteAction );
78 
79  connect( viewRules, &QAbstractItemView::doubleClicked, this, static_cast<void ( QgsRuleBasedLabelingWidget::* )( const QModelIndex & )>( &QgsRuleBasedLabelingWidget::editRule ) );
80 
81  connect( btnAddRule, &QAbstractButton::clicked, this, &QgsRuleBasedLabelingWidget::addRule );
82  connect( btnEditRule, &QAbstractButton::clicked, this, static_cast<void ( QgsRuleBasedLabelingWidget::* )()>( &QgsRuleBasedLabelingWidget::editRule ) );
83  connect( btnRemoveRule, &QAbstractButton::clicked, this, &QgsRuleBasedLabelingWidget::removeRule );
84  connect( mCopyAction, &QAction::triggered, this, &QgsRuleBasedLabelingWidget::copy );
85  connect( mPasteAction, &QAction::triggered, this, &QgsRuleBasedLabelingWidget::paste );
86  connect( mDeleteAction, &QAction::triggered, this, &QgsRuleBasedLabelingWidget::removeRule );
87 
88  if ( mLayer->labeling() && mLayer->labeling()->type() == QLatin1String( "rule-based" ) )
89  {
90  const QgsRuleBasedLabeling *rl = static_cast<const QgsRuleBasedLabeling *>( mLayer->labeling() );
91  mRootRule = rl->rootRule()->clone();
92  }
93  else if ( mLayer->labeling() && mLayer->labeling()->type() == QLatin1String( "simple" ) )
94  {
95  // copy simple label settings to first rule
96  mRootRule = new QgsRuleBasedLabeling::Rule( nullptr );
97  std::unique_ptr< QgsPalLayerSettings > newSettings = qgis::make_unique< QgsPalLayerSettings >( mLayer->labeling()->settings() );
98  newSettings->drawLabels = true; // otherwise we may be trying to copy a "blocking" setting to a rule - which is confusing for users!
99  mRootRule->appendChild( new QgsRuleBasedLabeling::Rule( newSettings.release() ) );
100  }
101  else
102  {
103  mRootRule = new QgsRuleBasedLabeling::Rule( nullptr );
104  }
105 
106  mModel = new QgsRuleBasedLabelingModel( mRootRule );
107  viewRules->setModel( mModel );
108 
109  connect( mModel, &QAbstractItemModel::dataChanged, this, &QgsRuleBasedLabelingWidget::widgetChanged );
110  connect( mModel, &QAbstractItemModel::rowsInserted, this, &QgsRuleBasedLabelingWidget::widgetChanged );
111  connect( mModel, &QAbstractItemModel::rowsRemoved, this, &QgsRuleBasedLabelingWidget::widgetChanged );
112 }
113 
115 {
116  delete mRootRule;
117 }
118 
120 {
121  if ( dockMode )
122  {
123  // when in dock mode, these shortcuts conflict with the main window shortcuts and cannot be used
124  if ( mCopyAction )
125  mCopyAction->setShortcut( QKeySequence() );
126  if ( mPasteAction )
127  mPasteAction->setShortcut( QKeySequence() );
128  if ( mDeleteAction )
129  mDeleteAction->setShortcut( QKeySequence() );
130  }
132 }
133 
134 void QgsRuleBasedLabelingWidget::addRule()
135 {
136 
138 
139  QgsRuleBasedLabeling::Rule *current = currentRule();
140  if ( current )
141  {
142  // add after this rule
143  QModelIndex currentIndex = viewRules->selectionModel()->currentIndex();
144  mModel->insertRule( currentIndex.parent(), currentIndex.row() + 1, newrule );
145  QModelIndex newindex = mModel->index( currentIndex.row() + 1, 0, currentIndex.parent() );
146  viewRules->selectionModel()->setCurrentIndex( newindex, QItemSelectionModel::ClearAndSelect );
147  }
148  else
149  {
150  // append to root rule
151  int rows = mModel->rowCount();
152  mModel->insertRule( QModelIndex(), rows, newrule );
153  QModelIndex newindex = mModel->index( rows, 0 );
154  viewRules->selectionModel()->setCurrentIndex( newindex, QItemSelectionModel::ClearAndSelect );
155  }
156  editRule();
157 }
158 
159 void QgsRuleBasedLabelingWidget::ruleWidgetPanelAccepted( QgsPanelWidget *panel )
160 {
161  QgsLabelingRulePropsWidget *widget = qobject_cast<QgsLabelingRulePropsWidget *>( panel );
162  widget->apply();
163 
164  QModelIndex index = viewRules->selectionModel()->currentIndex();
165  mModel->updateRule( index.parent(), index.row() );
166 }
167 
168 void QgsRuleBasedLabelingWidget::liveUpdateRuleFromPanel()
169 {
170  ruleWidgetPanelAccepted( qobject_cast<QgsPanelWidget *>( sender() ) );
171 }
172 
173 
174 void QgsRuleBasedLabelingWidget::editRule()
175 {
176  editRule( viewRules->selectionModel()->currentIndex() );
177 }
178 
179 void QgsRuleBasedLabelingWidget::editRule( const QModelIndex &index )
180 {
181  if ( !index.isValid() )
182  return;
183 
184  QgsRuleBasedLabeling::Rule *rule = mModel->ruleForIndex( index );
185 
186  QgsLabelingRulePropsWidget *widget = new QgsLabelingRulePropsWidget( rule, mLayer, this, mCanvas );
187  widget->setPanelTitle( tr( "Edit Rule" ) );
188  connect( widget, &QgsPanelWidget::panelAccepted, this, &QgsRuleBasedLabelingWidget::ruleWidgetPanelAccepted );
189  connect( widget, &QgsLabelingRulePropsWidget::widgetChanged, this, &QgsRuleBasedLabelingWidget::liveUpdateRuleFromPanel );
190  openPanel( widget );
191 }
192 
193 void QgsRuleBasedLabelingWidget::removeRule()
194 {
195  QItemSelection sel = viewRules->selectionModel()->selection();
196  const auto constSel = sel;
197  for ( const QItemSelectionRange &range : constSel )
198  {
199  if ( range.isValid() )
200  mModel->removeRows( range.top(), range.bottom() - range.top() + 1, range.parent() );
201  }
202  // make sure that the selection is gone
203  viewRules->selectionModel()->clear();
204 }
205 
206 void QgsRuleBasedLabelingWidget::copy()
207 {
208  QModelIndexList indexlist = viewRules->selectionModel()->selectedRows();
209 
210  if ( indexlist.isEmpty() )
211  return;
212 
213  QMimeData *mime = mModel->mimeData( indexlist );
214  QApplication::clipboard()->setMimeData( mime );
215 }
216 
217 void QgsRuleBasedLabelingWidget::paste()
218 {
219  const QMimeData *mime = QApplication::clipboard()->mimeData();
220  QModelIndexList indexlist = viewRules->selectionModel()->selectedRows();
221  QModelIndex index;
222  if ( indexlist.isEmpty() )
223  index = mModel->index( mModel->rowCount(), 0 );
224  else
225  index = indexlist.first();
226  mModel->dropMimeData( mime, Qt::CopyAction, index.row(), index.column(), index.parent() );
227 }
228 
229 QgsRuleBasedLabeling::Rule *QgsRuleBasedLabelingWidget::currentRule()
230 {
231  QItemSelectionModel *sel = viewRules->selectionModel();
232  QModelIndex idx = sel->currentIndex();
233  if ( !idx.isValid() )
234  return nullptr;
235  return mModel->ruleForIndex( idx );
236 }
237 
239 
241  : QAbstractItemModel( parent )
242  , mRootRule( rootRule )
243 {
244 }
245 
246 Qt::ItemFlags QgsRuleBasedLabelingModel::flags( const QModelIndex &index ) const
247 {
248  if ( !index.isValid() )
249  return Qt::ItemIsDropEnabled;
250 
251  // allow drop only at first column
252  Qt::ItemFlag drop = ( index.column() == 0 ? Qt::ItemIsDropEnabled : Qt::NoItemFlags );
253 
254  Qt::ItemFlag checkable = ( index.column() == 0 ? Qt::ItemIsUserCheckable : Qt::NoItemFlags );
255 
256  return Qt::ItemIsEnabled | Qt::ItemIsSelectable |
257  Qt::ItemIsEditable | checkable |
258  Qt::ItemIsDragEnabled | drop;
259 }
260 
261 QVariant QgsRuleBasedLabelingModel::data( const QModelIndex &index, int role ) const
262 {
263  if ( !index.isValid() )
264  return QVariant();
265 
267 
268  if ( role == Qt::DisplayRole || role == Qt::ToolTipRole )
269  {
270  switch ( index.column() )
271  {
272  case 0:
273  return rule->description();
274  case 1:
275  if ( rule->isElse() )
276  {
277  return "ELSE";
278  }
279  else
280  {
281  return rule->filterExpression().isEmpty() ? tr( "(no filter)" ) : rule->filterExpression();
282  }
283  case 2:
284  return rule->dependsOnScale() ? QgsScaleComboBox::toString( rule->minimumScale() ) : QVariant();
285  case 3:
286  return rule->dependsOnScale() ? QgsScaleComboBox::toString( rule->maximumScale() ) : QVariant();
287  case 4:
288  return rule->settings() ? rule->settings()->fieldName : QVariant();
289  default:
290  return QVariant();
291  }
292  }
293  else if ( role == Qt::DecorationRole && index.column() == 0 && rule->settings() )
294  {
295  const int iconSize = QgsGuiUtils::scaleIconSize( 16 );
296  return QgsPalLayerSettings::labelSettingsPreviewPixmap( *rule->settings(), QSize( iconSize, iconSize ), QString(), static_cast< int >( iconSize * ICON_PADDING_FACTOR ) );
297  }
298  else if ( role == Qt::TextAlignmentRole )
299  {
300  return ( index.column() == 2 || index.column() == 3 ) ? Qt::AlignRight : Qt::AlignLeft;
301  }
302  else if ( role == Qt::FontRole && index.column() == 1 )
303  {
304  if ( rule->isElse() )
305  {
306  QFont italicFont;
307  italicFont.setItalic( true );
308  return italicFont;
309  }
310  return QVariant();
311  }
312  else if ( role == Qt::EditRole )
313  {
314  switch ( index.column() )
315  {
316  case 0:
317  return rule->description();
318  case 1:
319  return rule->filterExpression();
320  case 2:
321  return rule->minimumScale();
322  case 3:
323  return rule->maximumScale();
324  case 4:
325  return rule->settings() ? rule->settings()->fieldName : QVariant();
326  default:
327  return QVariant();
328  }
329  }
330  else if ( role == Qt::CheckStateRole )
331  {
332  if ( index.column() != 0 )
333  return QVariant();
334  return rule->active() ? Qt::Checked : Qt::Unchecked;
335  }
336  else
337  return QVariant();
338 }
339 
340 QVariant QgsRuleBasedLabelingModel::headerData( int section, Qt::Orientation orientation, int role ) const
341 {
342  if ( orientation == Qt::Horizontal && role == Qt::DisplayRole && section >= 0 && section < 5 )
343  {
344  QStringList lst;
345  lst << tr( "Label" ) << tr( "Rule" ) << tr( "Min. Scale" ) << tr( "Max. Scale" ) << tr( "Text" ); // << tr( "Count" ) << tr( "Duplicate Count" );
346  return lst[section];
347  }
348 
349  return QVariant();
350 }
351 
352 int QgsRuleBasedLabelingModel::rowCount( const QModelIndex &parent ) const
353 {
354  if ( parent.column() > 0 )
355  return 0;
356 
358 
359  return parentRule->children().count();
360 }
361 
362 int QgsRuleBasedLabelingModel::columnCount( const QModelIndex & ) const
363 {
364  return 5;
365 }
366 
367 QModelIndex QgsRuleBasedLabelingModel::index( int row, int column, const QModelIndex &parent ) const
368 {
369  if ( hasIndex( row, column, parent ) )
370  {
372  QgsRuleBasedLabeling::Rule *childRule = parentRule->children()[row];
373  return createIndex( row, column, childRule );
374  }
375  return QModelIndex();
376 }
377 
378 QModelIndex QgsRuleBasedLabelingModel::parent( const QModelIndex &index ) const
379 {
380  if ( !index.isValid() )
381  return QModelIndex();
382 
384  QgsRuleBasedLabeling::Rule *parentRule = childRule->parent();
385 
386  if ( parentRule == mRootRule )
387  return QModelIndex();
388 
389  // this is right: we need to know row number of our parent (in our grandparent)
390  int row = parentRule->parent()->children().indexOf( parentRule );
391 
392  return createIndex( row, 0, parentRule );
393 }
394 
395 bool QgsRuleBasedLabelingModel::setData( const QModelIndex &index, const QVariant &value, int role )
396 {
397  if ( !index.isValid() )
398  return false;
399 
401 
402  if ( role == Qt::CheckStateRole )
403  {
404  rule->setActive( value.toInt() == Qt::Checked );
405  emit dataChanged( index, index );
406  return true;
407  }
408 
409  if ( role != Qt::EditRole )
410  return false;
411 
412  switch ( index.column() )
413  {
414  case 0: // description
415  rule->setDescription( value.toString() );
416  break;
417  case 1: // filter
418  rule->setFilterExpression( value.toString() );
419  break;
420  case 2: // scale min
421  rule->setMinimumScale( value.toDouble() );
422  break;
423  case 3: // scale max
424  rule->setMaximumScale( value.toDouble() );
425  break;
426  case 4: // label text
427  if ( !rule->settings() )
428  return false;
429  rule->settings()->fieldName = value.toString();
430  break;
431  default:
432  return false;
433  }
434 
435  emit dataChanged( index, index );
436  return true;
437 }
438 
440 {
441  return Qt::MoveAction; // | Qt::CopyAction
442 }
443 
445 {
446  QStringList types;
447  types << QStringLiteral( "application/vnd.text.list" );
448  return types;
449 }
450 
451 // manipulate DOM before dropping it so that rules are more useful
452 void _renderer2labelingRules( QDomElement &ruleElem )
453 {
454  // labeling rules recognize only "description"
455  if ( ruleElem.hasAttribute( QStringLiteral( "label" ) ) )
456  ruleElem.setAttribute( QStringLiteral( "description" ), ruleElem.attribute( QStringLiteral( "label" ) ) );
457 
458  // run recursively
459  QDomElement childRuleElem = ruleElem.firstChildElement( QStringLiteral( "rule" ) );
460  while ( !childRuleElem.isNull() )
461  {
462  _renderer2labelingRules( childRuleElem );
463  childRuleElem = childRuleElem.nextSiblingElement( QStringLiteral( "rule" ) );
464  }
465 }
466 
467 QMimeData *QgsRuleBasedLabelingModel::mimeData( const QModelIndexList &indexes ) const
468 {
469  QMimeData *mimeData = new QMimeData();
470  QByteArray encodedData;
471 
472  QDataStream stream( &encodedData, QIODevice::WriteOnly );
473 
474  const auto constIndexes = indexes;
475  for ( const QModelIndex &index : constIndexes )
476  {
477  // each item consists of several columns - let's add it with just first one
478  if ( !index.isValid() || index.column() != 0 )
479  continue;
480 
481  // we use a clone of the existing rule because it has a new unique rule key
482  // non-unique rule keys would confuse other components using them (e.g. legend)
484  QDomDocument doc;
485 
486  QDomElement rootElem = doc.createElement( QStringLiteral( "rule_mime" ) );
487  rootElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "labeling" ) ); // for determining whether rules are from renderer or labeling
488  QDomElement rulesElem = rule->save( doc, QgsReadWriteContext() );
489  rootElem.appendChild( rulesElem );
490  doc.appendChild( rootElem );
491 
492  delete rule;
493 
494  stream << doc.toString( -1 );
495  }
496 
497  mimeData->setData( QStringLiteral( "application/vnd.text.list" ), encodedData );
498  return mimeData;
499 }
500 
501 bool QgsRuleBasedLabelingModel::dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent )
502 {
503  Q_UNUSED( column )
504 
505  if ( action == Qt::IgnoreAction )
506  return true;
507 
508  if ( !data->hasFormat( QStringLiteral( "application/vnd.text.list" ) ) )
509  return false;
510 
511  if ( parent.column() > 0 )
512  return false;
513 
514  QByteArray encodedData = data->data( QStringLiteral( "application/vnd.text.list" ) );
515  QDataStream stream( &encodedData, QIODevice::ReadOnly );
516  int rows = 0;
517 
518  if ( row == -1 )
519  {
520  // the item was dropped at a parent - we may decide where to put the items - let's append them
521  row = rowCount( parent );
522  }
523 
524  while ( !stream.atEnd() )
525  {
526  QString text;
527  stream >> text;
528 
529  QDomDocument doc;
530  if ( !doc.setContent( text ) )
531  continue;
532  QDomElement rootElem = doc.documentElement();
533  if ( rootElem.tagName() != QLatin1String( "rule_mime" ) )
534  continue;
535  QDomElement ruleElem = rootElem.firstChildElement( QStringLiteral( "rule" ) );
536  if ( rootElem.attribute( QStringLiteral( "type" ) ) == QLatin1String( "renderer" ) )
537  _renderer2labelingRules( ruleElem ); // do some modifications so that we load the rules more nicely
539 
540  insertRule( parent, row + rows, rule );
541 
542  ++rows;
543  }
544  return true;
545 }
546 
547 bool QgsRuleBasedLabelingModel::removeRows( int row, int count, const QModelIndex &parent )
548 {
550 
551  if ( row < 0 || row >= parentRule->children().count() )
552  return false;
553 
554  beginRemoveRows( parent, row, row + count - 1 );
555 
556  for ( int i = 0; i < count; i++ )
557  {
558  if ( row < parentRule->children().count() )
559  {
560  parentRule->removeChildAt( row );
561  }
562  else
563  {
564  QgsDebugMsg( QStringLiteral( "trying to remove invalid index - this should not happen!" ) );
565  }
566  }
567 
568  endRemoveRows();
569 
570  return true;
571 }
572 
574 {
575  if ( index.isValid() )
576  return static_cast<QgsRuleBasedLabeling::Rule *>( index.internalPointer() );
577  return mRootRule;
578 }
579 
580 void QgsRuleBasedLabelingModel::insertRule( const QModelIndex &parent, int before, QgsRuleBasedLabeling::Rule *newrule )
581 {
582  beginInsertRows( parent, before, before );
583 
585  parentRule->insertChild( before, newrule );
586 
587  endInsertRows();
588 }
589 
590 void QgsRuleBasedLabelingModel::updateRule( const QModelIndex &parent, int row )
591 {
592  emit dataChanged( index( row, 0, parent ),
593  index( row, columnCount( parent ), parent ) );
594 }
595 
597 
599  : QgsPanelWidget( parent )
600  , mRule( rule )
601  , mLayer( layer )
602  , mSettings( nullptr )
603  , mMapCanvas( mapCanvas )
604 {
605  setupUi( this );
606 
607  mElseRadio->setChecked( mRule->isElse() );
608  mFilterRadio->setChecked( !mRule->isElse() );
609  editFilter->setText( mRule->filterExpression() );
610  editFilter->setToolTip( mRule->filterExpression() );
611  editDescription->setText( mRule->description() );
612  editDescription->setToolTip( mRule->description() );
613 
614  if ( mRule->dependsOnScale() )
615  {
616  groupScale->setChecked( true );
617  // caution: rule uses scale denom, scale widget uses true scales
618  mScaleRangeWidget->setScaleRange( std::max( rule->minimumScale(), 0.0 ),
619  std::max( rule->maximumScale(), 0.0 ) );
620  }
621  mScaleRangeWidget->setMapCanvas( mMapCanvas );
622 
623  if ( mRule->settings() )
624  {
625  groupSettings->setChecked( true );
626  mSettings = new QgsPalLayerSettings( *mRule->settings() ); // use a clone!
627  }
628  else
629  {
630  groupSettings->setChecked( false );
631  mSettings = new QgsPalLayerSettings;
632  }
633 
634  mLabelingGui = new QgsLabelingGui( nullptr, mMapCanvas, *mSettings, this );
635  mLabelingGui->layout()->setContentsMargins( 0, 0, 0, 0 );
636  QVBoxLayout *l = new QVBoxLayout;
637  l->addWidget( mLabelingGui );
638  groupSettings->setLayout( l );
639 
640  mLabelingGui->setLabelMode( QgsLabelingGui::Labels );
641  mLabelingGui->setLayer( mLayer );
642 
643  connect( btnExpressionBuilder, &QAbstractButton::clicked, this, &QgsLabelingRulePropsWidget::buildExpression );
644  connect( btnTestFilter, &QAbstractButton::clicked, this, &QgsLabelingRulePropsWidget::testFilter );
645  connect( editFilter, &QLineEdit::textEdited, this, &QgsLabelingRulePropsWidget::widgetChanged );
646  connect( editDescription, &QLineEdit::textChanged, this, &QgsLabelingRulePropsWidget::widgetChanged );
647  connect( groupScale, &QGroupBox::toggled, this, &QgsLabelingRulePropsWidget::widgetChanged );
649  connect( groupSettings, &QGroupBox::toggled, this, &QgsLabelingRulePropsWidget::widgetChanged );
651  connect( mFilterRadio, &QRadioButton::toggled, this, [ = ]( bool toggled ) { filterFrame->setEnabled( toggled ) ; } );
652  connect( mElseRadio, &QRadioButton::toggled, this, [ = ]( bool toggled ) { if ( toggled ) editFilter->setText( QStringLiteral( "ELSE" ) );} );
653 }
654 
656 {
657  delete mSettings;
658 }
659 
661 {
663  mLabelingGui->setDockMode( dockMode );
664 }
665 
666 void QgsLabelingRulePropsWidget::testFilter()
667 {
668  if ( !mFilterRadio->isChecked() )
669  return;
670 
671  QgsExpression filter( editFilter->text() );
672  if ( filter.hasParserError() )
673  {
674  QMessageBox::critical( this, tr( "Test Filter" ), tr( "Filter expression parsing error:\n" ) + filter.parserErrorString() );
675  return;
676  }
677 
678  QgsExpressionContext context( _globalProjectAtlasMapLayerScopes( mMapCanvas, mLayer ) );
679 
680  if ( !filter.prepare( &context ) )
681  {
682  QMessageBox::critical( this, tr( "Test Filter" ), filter.evalErrorString() );
683  return;
684  }
685 
686  QApplication::setOverrideCursor( Qt::WaitCursor );
687 
688  QgsFeatureIterator fit = mLayer->getFeatures();
689 
690  int count = 0;
691  QgsFeature f;
692  while ( fit.nextFeature( f ) )
693  {
694  context.setFeature( f );
695 
696  QVariant value = filter.evaluate( &context );
697  if ( value.toInt() != 0 )
698  count++;
699  if ( filter.hasEvalError() )
700  break;
701  }
702 
703  QApplication::restoreOverrideCursor();
704 
705  QMessageBox::information( this, tr( "Test Filter" ), tr( "Filter returned %n feature(s)", "number of filtered features", count ) );
706 }
707 
708 
709 void QgsLabelingRulePropsWidget::buildExpression()
710 {
711  QgsExpressionContext context( _globalProjectAtlasMapLayerScopes( mMapCanvas, mLayer ) );
712 
713  QgsExpressionBuilderDialog dlg( mLayer, editFilter->text(), this, QStringLiteral( "generic" ), context );
714 
715  if ( dlg.exec() )
716  editFilter->setText( dlg.expressionText() );
717 }
718 
720 {
721  QString filter = mElseRadio->isChecked() ? QStringLiteral( "ELSE" ) : editFilter->text();
722  mRule->setFilterExpression( filter );
723  mRule->setDescription( editDescription->text() );
724  mRule->setMinimumScale( groupScale->isChecked() ? mScaleRangeWidget->minimumScale() : 0 );
725  mRule->setMaximumScale( groupScale->isChecked() ? mScaleRangeWidget->maximumScale() : 0 );
726  mRule->setSettings( groupSettings->isChecked() ? new QgsPalLayerSettings( mLabelingGui->layerSettings() ) : nullptr );
727 }
virtual QgsPalLayerSettings settings(const QString &providerId=QString()) const =0
Gets associated label settings.
virtual QString type() const =0
Unique type string of the labeling configuration implementation.
static QString iconPath(const QString &iconFile)
Returns path to the desired icon file.
A generic dialog for building expression strings.
Single scope for storing variables and functions for use within a QgsExpressionContext.
static QgsExpressionContextScope * projectScope(const QgsProject *project)
Creates a new scope which contains variables and functions relating to a QGIS project.
static QgsExpressionContextScope * atlasScope(const QgsLayoutAtlas *atlas)
Creates a new scope which contains variables and functions relating to a QgsLayoutAtlas.
static QgsExpressionContextScope * mapSettingsScope(const QgsMapSettings &mapSettings)
Creates a new scope which contains variables and functions relating to a QgsMapSettings object.
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
static QgsExpressionContextScope * globalScope()
Creates a new scope which contains variables and functions relating to the global QGIS context.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
Class for parsing and evaluation of expressions (formerly called "search strings").
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:56
Widget for editing a labeling rule.
void setDockMode(bool dockMode) override
Set the widget in dock mode which tells the widget to emit panel widgets and not open dialogs.
void apply()
Apply any changes from the widget to the set rule.
QgsRuleBasedLabeling::Rule * rule()
Returns the rule being edited.
QgsLabelingRulePropsWidget(QgsRuleBasedLabeling::Rule *rule, QgsVectorLayer *layer, QWidget *parent=nullptr, QgsMapCanvas *mapCanvas=nullptr)
constructor
Map canvas is a class for displaying all GIS data types on a canvas.
Definition: qgsmapcanvas.h:86
QgsExpressionContextScope & expressionContextScope()
Returns a reference to the expression context scope for the map canvas.
Definition: qgsmapcanvas.h:652
const QgsMapSettings & mapSettings() const
Gets access to properties used for map rendering.
Base class for all map layer types.
Definition: qgsmaplayer.h:85
The QgsMapSettings class contains configuration for rendering of the map.
Contains settings for how a map layer will be labeled.
static QPixmap labelSettingsPreviewPixmap(const QgsPalLayerSettings &settings, QSize size, const QString &previewText=QString(), int padding=0)
Returns a pixmap preview for label settings.
QString fieldName
Name of field (or an expression) to use for label text.
Base class for any widget that can be shown as a inline panel.
void openPanel(QgsPanelWidget *panel)
Open a panel or dialog depending on dock mode setting If dock mode is true this method will emit the ...
void panelAccepted(QgsPanelWidget *panel)
Emitted when the panel is accepted by the user.
void widgetChanged()
Emitted when the widget state changes.
void setPanelTitle(const QString &panelTitle)
Set the title of the panel when shown in the interface.
virtual void setDockMode(bool dockMode)
Set the widget in dock mode which tells the widget to emit panel widgets and not open dialogs.
bool dockMode()
Returns the dock mode state.
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:501
The class is used as a container of context for various read/write operations on other objects.
Model for rule based rendering rules view.
QgsRuleBasedLabelingModel(QgsRuleBasedLabeling::Rule *rootRule, QObject *parent=nullptr)
constructor
bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override
QStringList mimeTypes() const override
QgsRuleBasedLabeling::Rule * mRootRule
Qt::DropActions supportedDropActions() const override
void insertRule(const QModelIndex &parent, int before, QgsRuleBasedLabeling::Rule *newrule)
Inserts a new rule at the specified position.
QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const override
provide model index for parent's child item
QgsRuleBasedLabeling::Rule * ruleForIndex(const QModelIndex &index) const
Returns the rule at the specified index.
void updateRule(const QModelIndex &parent, int row)
Updates the rule at the specified position.
QMimeData * mimeData(const QModelIndexList &indexes) const override
QVariant headerData(int section, Qt::Orientation orientation, int role=Qt::DisplayRole) const override
bool removeRows(int row, int count, const QModelIndex &parent=QModelIndex()) override
int rowCount(const QModelIndex &parent=QModelIndex()) const override
bool setData(const QModelIndex &index, const QVariant &value, int role=Qt::EditRole) override
QModelIndex parent(const QModelIndex &index) const override
provide parent model index
Qt::ItemFlags flags(const QModelIndex &index) const override
int columnCount(const QModelIndex &=QModelIndex()) const override
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override
Widget for configuring rule based labeling.
void setDockMode(bool dockMode) override
Set the widget in dock mode which tells the widget to emit panel widgets and not open dialogs.
QgsRuleBasedLabelingWidget(QgsVectorLayer *layer, QgsMapCanvas *canvas, QWidget *parent=nullptr)
constructor
A child rule for QgsRuleBasedLabeling.
const QgsRuleBasedLabeling::RuleList & children() const
Returns all children rules of this rule.
QgsRuleBasedLabeling::Rule * clone() const
clone this rule, return new instance
void setMaximumScale(double scale)
Sets the maximum map scale (i.e.
double maximumScale() const
Returns the maximum map scale (i.e.
void setDescription(const QString &description)
Set a human readable description for this rule.
bool dependsOnScale() const
Determines if scale based labeling is active.
QgsPalLayerSettings * settings() const
Returns the labeling settings.
QString filterExpression() const
A filter that will check if this rule applies.
bool active() const
Returns if this rule is active.
static QgsRuleBasedLabeling::Rule * create(const QDomElement &ruleElem, const QgsReadWriteContext &context)
Create a rule from an XML definition.
void removeChildAt(int i)
delete child rule
void setActive(bool state)
Sets if this rule is active.
QDomElement save(QDomDocument &doc, const QgsReadWriteContext &context) const
store labeling info to XML element
void setSettings(QgsPalLayerSettings *settings)
Sets new settings (or nullptr). Deletes old settings if any.
bool isElse() const
Check if this rule is an ELSE rule.
void insertChild(int i, QgsRuleBasedLabeling::Rule *rule)
add child rule, take ownership, sets this as parent
void setMinimumScale(double scale)
Sets the minimum map scale (i.e.
const QgsRuleBasedLabeling::Rule * parent() const
The parent rule.
void setFilterExpression(const QString &filterExp)
Set the expression used to check if a given feature shall be rendered with this rule.
double minimumScale() const
Returns the minimum map scale (i.e.
void appendChild(QgsRuleBasedLabeling::Rule *rule)
add child rule, take ownership, sets this as parent
QString description() const
A human readable description for this rule.
Rule based labeling for a vector layer.
QgsRuleBasedLabeling::Rule * rootRule()
static QString toString(double scale)
Helper function to convert a scale double to scale string.
void rangeChanged(double min, double max)
Emitted when the scale range set in the widget is changed.
void widgetChanged()
Emitted when the text format defined by the widget changes.
Represents a vector layer which manages a vector based data sets.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Queries the layer for features specified in request.
const QgsAbstractVectorLayerLabeling * labeling() const
Access to const labeling configuration.
QSize iconSize(bool dockableToolbar)
Returns the user-preferred size of a window's toolbar icons.
int scaleIconSize(int standardSize)
Scales an icon size to compensate for display pixel density, making the icon size hi-dpi friendly,...
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
const double ICON_PADDING_FACTOR
void _renderer2labelingRules(QDomElement &ruleElem)