QGIS API Documentation  3.21.0-Master (5b68dc587e)
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 = std::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 {
137 
138  QgsRuleBasedLabeling::Rule *current = currentRule();
139  if ( current )
140  {
141  // add after this rule
142  const QModelIndex currentIndex = viewRules->selectionModel()->currentIndex();
143  mModel->insertRule( currentIndex.parent(), currentIndex.row() + 1, newrule );
144  const QModelIndex newindex = mModel->index( currentIndex.row() + 1, 0, currentIndex.parent() );
145  viewRules->selectionModel()->setCurrentIndex( newindex, QItemSelectionModel::ClearAndSelect );
146  }
147  else
148  {
149  // append to root rule
150  const int rows = mModel->rowCount();
151  mModel->insertRule( QModelIndex(), rows, newrule );
152  const QModelIndex newindex = mModel->index( rows, 0 );
153  viewRules->selectionModel()->setCurrentIndex( newindex, QItemSelectionModel::ClearAndSelect );
154  }
155  editRule();
156 }
157 
158 void QgsRuleBasedLabelingWidget::ruleWidgetPanelAccepted( QgsPanelWidget *panel )
159 {
160  QgsLabelingRulePropsWidget *widget = qobject_cast<QgsLabelingRulePropsWidget *>( panel );
161  widget->apply();
162 
163  const QModelIndex index = viewRules->selectionModel()->currentIndex();
164  mModel->updateRule( index.parent(), index.row() );
165 }
166 
167 void QgsRuleBasedLabelingWidget::liveUpdateRuleFromPanel()
168 {
169  ruleWidgetPanelAccepted( qobject_cast<QgsPanelWidget *>( sender() ) );
170 }
171 
172 
173 void QgsRuleBasedLabelingWidget::editRule()
174 {
175  editRule( viewRules->selectionModel()->currentIndex() );
176 }
177 
178 void QgsRuleBasedLabelingWidget::editRule( const QModelIndex &index )
179 {
180  if ( !index.isValid() )
181  return;
182 
183  QgsRuleBasedLabeling::Rule *rule = mModel->ruleForIndex( index );
184 
185  QgsLabelingRulePropsWidget *widget = new QgsLabelingRulePropsWidget( rule, mLayer, this, mCanvas );
186  widget->setPanelTitle( tr( "Edit Rule" ) );
187  connect( widget, &QgsPanelWidget::panelAccepted, this, &QgsRuleBasedLabelingWidget::ruleWidgetPanelAccepted );
188  connect( widget, &QgsLabelingRulePropsWidget::widgetChanged, this, &QgsRuleBasedLabelingWidget::liveUpdateRuleFromPanel );
189  openPanel( widget );
190 }
191 
192 void QgsRuleBasedLabelingWidget::removeRule()
193 {
194  const QItemSelection sel = viewRules->selectionModel()->selection();
195  const auto constSel = sel;
196  for ( const QItemSelectionRange &range : constSel )
197  {
198  if ( range.isValid() )
199  mModel->removeRows( range.top(), range.bottom() - range.top() + 1, range.parent() );
200  }
201  // make sure that the selection is gone
202  viewRules->selectionModel()->clear();
203 }
204 
205 void QgsRuleBasedLabelingWidget::copy()
206 {
207  const QModelIndexList indexlist = viewRules->selectionModel()->selectedRows();
208 
209  if ( indexlist.isEmpty() )
210  return;
211 
212  QMimeData *mime = mModel->mimeData( indexlist );
213  QApplication::clipboard()->setMimeData( mime );
214 }
215 
216 void QgsRuleBasedLabelingWidget::paste()
217 {
218  const QMimeData *mime = QApplication::clipboard()->mimeData();
219  QModelIndexList indexlist = viewRules->selectionModel()->selectedRows();
220  QModelIndex index;
221  if ( indexlist.isEmpty() )
222  index = mModel->index( mModel->rowCount(), 0 );
223  else
224  index = indexlist.first();
225  mModel->dropMimeData( mime, Qt::CopyAction, index.row(), index.column(), index.parent() );
226 }
227 
228 QgsRuleBasedLabeling::Rule *QgsRuleBasedLabelingWidget::currentRule()
229 {
230  QItemSelectionModel *sel = viewRules->selectionModel();
231  const QModelIndex idx = sel->currentIndex();
232  if ( !idx.isValid() )
233  return nullptr;
234  return mModel->ruleForIndex( idx );
235 }
236 
238 
240  : QAbstractItemModel( parent )
241  , mRootRule( rootRule )
242 {
243 }
244 
245 Qt::ItemFlags QgsRuleBasedLabelingModel::flags( const QModelIndex &index ) const
246 {
247  if ( !index.isValid() )
248  return Qt::ItemIsDropEnabled;
249 
250  // allow drop only at first column
251  const Qt::ItemFlag drop = ( index.column() == 0 ? Qt::ItemIsDropEnabled : Qt::NoItemFlags );
252 
253  const Qt::ItemFlag checkable = ( index.column() == 0 ? Qt::ItemIsUserCheckable : Qt::NoItemFlags );
254 
255  return Qt::ItemIsEnabled | Qt::ItemIsSelectable |
256  Qt::ItemIsEditable | checkable |
257  Qt::ItemIsDragEnabled | drop;
258 }
259 
260 QVariant QgsRuleBasedLabelingModel::data( const QModelIndex &index, int role ) const
261 {
262  if ( !index.isValid() )
263  return QVariant();
264 
266 
267  if ( role == Qt::DisplayRole || role == Qt::ToolTipRole )
268  {
269  switch ( index.column() )
270  {
271  case 0:
272  return rule->description();
273  case 1:
274  if ( rule->isElse() )
275  {
276  return "ELSE";
277  }
278  else
279  {
280  return rule->filterExpression().isEmpty() ? tr( "(no filter)" ) : rule->filterExpression();
281  }
282  case 2:
283  return rule->dependsOnScale() ? QgsScaleComboBox::toString( rule->minimumScale() ) : QVariant();
284  case 3:
285  return rule->dependsOnScale() ? QgsScaleComboBox::toString( rule->maximumScale() ) : QVariant();
286  case 4:
287  return rule->settings() ? rule->settings()->fieldName : QVariant();
288  default:
289  return QVariant();
290  }
291  }
292  else if ( role == Qt::DecorationRole && index.column() == 0 && rule->settings() )
293  {
294  const int iconSize = QgsGuiUtils::scaleIconSize( 16 );
295  return QgsPalLayerSettings::labelSettingsPreviewPixmap( *rule->settings(), QSize( iconSize, iconSize ), QString(), static_cast< int >( iconSize * ICON_PADDING_FACTOR ) );
296  }
297  else if ( role == Qt::TextAlignmentRole )
298  {
299  return ( index.column() == 2 || index.column() == 3 ) ? Qt::AlignRight : Qt::AlignLeft;
300  }
301  else if ( role == Qt::FontRole && index.column() == 1 )
302  {
303  if ( rule->isElse() )
304  {
305  QFont italicFont;
306  italicFont.setItalic( true );
307  return italicFont;
308  }
309  return QVariant();
310  }
311  else if ( role == Qt::EditRole )
312  {
313  switch ( index.column() )
314  {
315  case 0:
316  return rule->description();
317  case 1:
318  return rule->filterExpression();
319  case 2:
320  return rule->minimumScale();
321  case 3:
322  return rule->maximumScale();
323  case 4:
324  return rule->settings() ? rule->settings()->fieldName : QVariant();
325  default:
326  return QVariant();
327  }
328  }
329  else if ( role == Qt::CheckStateRole )
330  {
331  if ( index.column() != 0 )
332  return QVariant();
333  return rule->active() ? Qt::Checked : Qt::Unchecked;
334  }
335  else
336  return QVariant();
337 }
338 
339 QVariant QgsRuleBasedLabelingModel::headerData( int section, Qt::Orientation orientation, int role ) const
340 {
341  if ( orientation == Qt::Horizontal && role == Qt::DisplayRole && section >= 0 && section < 5 )
342  {
343  QStringList lst;
344  lst << tr( "Label" ) << tr( "Rule" ) << tr( "Min. Scale" ) << tr( "Max. Scale" ) << tr( "Text" ); // << tr( "Count" ) << tr( "Duplicate Count" );
345  return lst[section];
346  }
347 
348  return QVariant();
349 }
350 
351 int QgsRuleBasedLabelingModel::rowCount( const QModelIndex &parent ) const
352 {
353  if ( parent.column() > 0 )
354  return 0;
355 
357 
358  return parentRule->children().count();
359 }
360 
361 int QgsRuleBasedLabelingModel::columnCount( const QModelIndex & ) const
362 {
363  return 5;
364 }
365 
366 QModelIndex QgsRuleBasedLabelingModel::index( int row, int column, const QModelIndex &parent ) const
367 {
368  if ( hasIndex( row, column, parent ) )
369  {
371  QgsRuleBasedLabeling::Rule *childRule = parentRule->children()[row];
372  return createIndex( row, column, childRule );
373  }
374  return QModelIndex();
375 }
376 
377 QModelIndex QgsRuleBasedLabelingModel::parent( const QModelIndex &index ) const
378 {
379  if ( !index.isValid() )
380  return QModelIndex();
381 
383  QgsRuleBasedLabeling::Rule *parentRule = childRule->parent();
384 
385  if ( parentRule == mRootRule )
386  return QModelIndex();
387 
388  // this is right: we need to know row number of our parent (in our grandparent)
389  const int row = parentRule->parent()->children().indexOf( parentRule );
390 
391  return createIndex( row, 0, parentRule );
392 }
393 
394 bool QgsRuleBasedLabelingModel::setData( const QModelIndex &index, const QVariant &value, int role )
395 {
396  if ( !index.isValid() )
397  return false;
398 
400 
401  if ( role == Qt::CheckStateRole )
402  {
403  rule->setActive( value.toInt() == Qt::Checked );
404  emit dataChanged( index, index );
405  return true;
406  }
407 
408  if ( role != Qt::EditRole )
409  return false;
410 
411  switch ( index.column() )
412  {
413  case 0: // description
414  rule->setDescription( value.toString() );
415  break;
416  case 1: // filter
417  rule->setFilterExpression( value.toString() );
418  break;
419  case 2: // scale min
420  rule->setMinimumScale( value.toDouble() );
421  break;
422  case 3: // scale max
423  rule->setMaximumScale( value.toDouble() );
424  break;
425  case 4: // label text
426  if ( !rule->settings() )
427  return false;
428  rule->settings()->fieldName = value.toString();
429  break;
430  default:
431  return false;
432  }
433 
434  emit dataChanged( index, index );
435  return true;
436 }
437 
439 {
440  return Qt::MoveAction; // | Qt::CopyAction
441 }
442 
444 {
445  QStringList types;
446  types << QStringLiteral( "application/vnd.text.list" );
447  return types;
448 }
449 
450 // manipulate DOM before dropping it so that rules are more useful
451 void _renderer2labelingRules( QDomElement &ruleElem )
452 {
453  // labeling rules recognize only "description"
454  if ( ruleElem.hasAttribute( QStringLiteral( "label" ) ) )
455  ruleElem.setAttribute( QStringLiteral( "description" ), ruleElem.attribute( QStringLiteral( "label" ) ) );
456 
457  // run recursively
458  QDomElement childRuleElem = ruleElem.firstChildElement( QStringLiteral( "rule" ) );
459  while ( !childRuleElem.isNull() )
460  {
461  _renderer2labelingRules( childRuleElem );
462  childRuleElem = childRuleElem.nextSiblingElement( QStringLiteral( "rule" ) );
463  }
464 }
465 
466 QMimeData *QgsRuleBasedLabelingModel::mimeData( const QModelIndexList &indexes ) const
467 {
468  QMimeData *mimeData = new QMimeData();
469  QByteArray encodedData;
470 
471  QDataStream stream( &encodedData, QIODevice::WriteOnly );
472 
473  const auto constIndexes = indexes;
474  for ( const QModelIndex &index : constIndexes )
475  {
476  // each item consists of several columns - let's add it with just first one
477  if ( !index.isValid() || index.column() != 0 )
478  continue;
479 
480  // we use a clone of the existing rule because it has a new unique rule key
481  // non-unique rule keys would confuse other components using them (e.g. legend)
483  QDomDocument doc;
484 
485  QDomElement rootElem = doc.createElement( QStringLiteral( "rule_mime" ) );
486  rootElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "labeling" ) ); // for determining whether rules are from renderer or labeling
487  const QDomElement rulesElem = rule->save( doc, QgsReadWriteContext() );
488  rootElem.appendChild( rulesElem );
489  doc.appendChild( rootElem );
490 
491  delete rule;
492 
493  stream << doc.toString( -1 );
494  }
495 
496  mimeData->setData( QStringLiteral( "application/vnd.text.list" ), encodedData );
497  return mimeData;
498 }
499 
500 bool QgsRuleBasedLabelingModel::dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent )
501 {
502  Q_UNUSED( column )
503 
504  if ( action == Qt::IgnoreAction )
505  return true;
506 
507  if ( !data->hasFormat( QStringLiteral( "application/vnd.text.list" ) ) )
508  return false;
509 
510  if ( parent.column() > 0 )
511  return false;
512 
513  QByteArray encodedData = data->data( QStringLiteral( "application/vnd.text.list" ) );
514  QDataStream stream( &encodedData, QIODevice::ReadOnly );
515  int rows = 0;
516 
517  if ( row == -1 )
518  {
519  // the item was dropped at a parent - we may decide where to put the items - let's append them
520  row = rowCount( parent );
521  }
522 
523  while ( !stream.atEnd() )
524  {
525  QString text;
526  stream >> text;
527 
528  QDomDocument doc;
529  if ( !doc.setContent( text ) )
530  continue;
531  const QDomElement rootElem = doc.documentElement();
532  if ( rootElem.tagName() != QLatin1String( "rule_mime" ) )
533  continue;
534  QDomElement ruleElem = rootElem.firstChildElement( QStringLiteral( "rule" ) );
535  if ( rootElem.attribute( QStringLiteral( "type" ) ) == QLatin1String( "renderer" ) )
536  _renderer2labelingRules( ruleElem ); // do some modifications so that we load the rules more nicely
538 
539  insertRule( parent, row + rows, rule );
540 
541  ++rows;
542  }
543  return true;
544 }
545 
546 bool QgsRuleBasedLabelingModel::removeRows( int row, int count, const QModelIndex &parent )
547 {
549 
550  if ( row < 0 || row >= parentRule->children().count() )
551  return false;
552 
553  beginRemoveRows( parent, row, row + count - 1 );
554 
555  for ( int i = 0; i < count; i++ )
556  {
557  if ( row < parentRule->children().count() )
558  {
559  parentRule->removeChildAt( row );
560  }
561  else
562  {
563  QgsDebugMsg( QStringLiteral( "trying to remove invalid index - this should not happen!" ) );
564  }
565  }
566 
567  endRemoveRows();
568 
569  return true;
570 }
571 
573 {
574  if ( index.isValid() )
575  return static_cast<QgsRuleBasedLabeling::Rule *>( index.internalPointer() );
576  return mRootRule;
577 }
578 
579 void QgsRuleBasedLabelingModel::insertRule( const QModelIndex &parent, int before, QgsRuleBasedLabeling::Rule *newrule )
580 {
581  beginInsertRows( parent, before, before );
582 
584  parentRule->insertChild( before, newrule );
585 
586  endInsertRows();
587 }
588 
589 void QgsRuleBasedLabelingModel::updateRule( const QModelIndex &parent, int row )
590 {
591  emit dataChanged( index( row, 0, parent ),
592  index( row, columnCount( parent ), parent ) );
593 }
594 
596 
598  : QgsPanelWidget( parent )
599  , mRule( rule )
600  , mLayer( layer )
601  , mSettings( nullptr )
602  , mMapCanvas( mapCanvas )
603 {
604  setupUi( this );
605 
606  QButtonGroup *radioGroup = new QButtonGroup( this );
607  radioGroup->addButton( mFilterRadio );
608  radioGroup->addButton( mElseRadio );
609 
610  mElseRadio->setChecked( mRule->isElse() );
611  mFilterRadio->setChecked( !mRule->isElse() );
612  editFilter->setText( mRule->filterExpression() );
613  editFilter->setToolTip( mRule->filterExpression() );
614  editDescription->setText( mRule->description() );
615  editDescription->setToolTip( mRule->description() );
616 
617  if ( mRule->dependsOnScale() )
618  {
619  groupScale->setChecked( true );
620  // caution: rule uses scale denom, scale widget uses true scales
621  mScaleRangeWidget->setScaleRange( std::max( rule->minimumScale(), 0.0 ),
622  std::max( rule->maximumScale(), 0.0 ) );
623  }
624  mScaleRangeWidget->setMapCanvas( mMapCanvas );
625 
626  if ( mRule->settings() )
627  {
628  groupSettings->setChecked( true );
629  mSettings = new QgsPalLayerSettings( *mRule->settings() ); // use a clone!
630  }
631  else
632  {
633  groupSettings->setChecked( false );
634  mSettings = new QgsPalLayerSettings;
635  }
636 
637  mLabelingGui = new QgsLabelingGui( nullptr, mMapCanvas, *mSettings, this );
638  mLabelingGui->layout()->setContentsMargins( 0, 0, 0, 0 );
639  QVBoxLayout *l = new QVBoxLayout;
640  l->addWidget( mLabelingGui );
641  groupSettings->setLayout( l );
642 
643  mLabelingGui->setLabelMode( QgsLabelingGui::Labels );
644  mLabelingGui->setLayer( mLayer );
645 
646  connect( btnExpressionBuilder, &QAbstractButton::clicked, this, &QgsLabelingRulePropsWidget::buildExpression );
647  connect( btnTestFilter, &QAbstractButton::clicked, this, &QgsLabelingRulePropsWidget::testFilter );
648  connect( editFilter, &QLineEdit::textEdited, this, &QgsLabelingRulePropsWidget::widgetChanged );
649  connect( editDescription, &QLineEdit::textChanged, this, &QgsLabelingRulePropsWidget::widgetChanged );
650  connect( groupScale, &QGroupBox::toggled, this, &QgsLabelingRulePropsWidget::widgetChanged );
652  connect( groupSettings, &QGroupBox::toggled, this, &QgsLabelingRulePropsWidget::widgetChanged );
654  connect( mFilterRadio, &QRadioButton::toggled, this, [ = ]( bool toggled ) { filterFrame->setEnabled( toggled ) ; } );
655  connect( mElseRadio, &QRadioButton::toggled, this, [ = ]( bool toggled ) { if ( toggled ) editFilter->setText( QStringLiteral( "ELSE" ) );} );
656 }
657 
659 {
660  delete mSettings;
661 }
662 
664 {
666  mLabelingGui->setDockMode( dockMode );
667 }
668 
669 void QgsLabelingRulePropsWidget::testFilter()
670 {
671  if ( !mFilterRadio->isChecked() )
672  return;
673 
674  QgsExpression filter( editFilter->text() );
675  if ( filter.hasParserError() )
676  {
677  QMessageBox::critical( this, tr( "Test Filter" ), tr( "Filter expression parsing error:\n" ) + filter.parserErrorString() );
678  return;
679  }
680 
681  QgsExpressionContext context( _globalProjectAtlasMapLayerScopes( mMapCanvas, mLayer ) );
682 
683  if ( !filter.prepare( &context ) )
684  {
685  QMessageBox::critical( this, tr( "Test Filter" ), filter.evalErrorString() );
686  return;
687  }
688 
689  QApplication::setOverrideCursor( Qt::WaitCursor );
690 
691  QgsFeatureIterator fit = mLayer->getFeatures();
692 
693  int count = 0;
694  QgsFeature f;
695  while ( fit.nextFeature( f ) )
696  {
697  context.setFeature( f );
698 
699  const QVariant value = filter.evaluate( &context );
700  if ( value.toInt() != 0 )
701  count++;
702  if ( filter.hasEvalError() )
703  break;
704  }
705 
706  QApplication::restoreOverrideCursor();
707 
708  QMessageBox::information( this, tr( "Test Filter" ), tr( "Filter returned %n feature(s)", "number of filtered features", count ) );
709 }
710 
711 
712 void QgsLabelingRulePropsWidget::buildExpression()
713 {
714  const QgsExpressionContext context( _globalProjectAtlasMapLayerScopes( mMapCanvas, mLayer ) );
715 
716  QgsExpressionBuilderDialog dlg( mLayer, editFilter->text(), this, QStringLiteral( "generic" ), context );
717 
718  if ( dlg.exec() )
719  editFilter->setText( dlg.expressionText() );
720 }
721 
723 {
724  const QString filter = mElseRadio->isChecked() ? QStringLiteral( "ELSE" ) : editFilter->text();
725  mRule->setFilterExpression( filter );
726  mRule->setDescription( editDescription->text() );
727  mRule->setMinimumScale( groupScale->isChecked() ? mScaleRangeWidget->minimumScale() : 0 );
728  mRule->setMaximumScale( groupScale->isChecked() ? mScaleRangeWidget->maximumScale() : 0 );
729  mRule->setSettings( groupSettings->isChecked() ? new QgsPalLayerSettings( mLabelingGui->layerSettings() ) : nullptr );
730 }
virtual QgsPalLayerSettings settings(const QString &providerId=QString()) const =0
Gets associated label settings.
static QgsPalLayerSettings defaultSettingsForLayer(const QgsVectorLayer *layer)
Returns the default layer settings to use for the specified vector layer.
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 unique ID, geometry and a list of field...
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:88
QgsExpressionContextScope & expressionContextScope()
Returns a reference to the expression context scope for the map canvas.
Definition: qgsmapcanvas.h:708
const QgsMapSettings & mapSettings() const
Gets access to properties used for map rendering.
Base class for all map layer types.
Definition: qgsmaplayer.h:73
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:467
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)