QGIS API Documentation  3.27.0-Master (0e23467727)
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 );
185 
186  if ( panel && panel->dockMode() )
187  {
188  QgsLabelingRulePropsWidget *widget = new QgsLabelingRulePropsWidget( rule, mLayer, this, mCanvas );
189  widget->setPanelTitle( tr( "Edit Rule" ) );
190  connect( widget, &QgsPanelWidget::panelAccepted, this, &QgsRuleBasedLabelingWidget::ruleWidgetPanelAccepted );
191  connect( widget, &QgsLabelingRulePropsWidget::widgetChanged, this, &QgsRuleBasedLabelingWidget::liveUpdateRuleFromPanel );
192  openPanel( widget );
193  return;
194  }
195 
196  QgsLabelingRulePropsDialog dlg( rule, mLayer, this, mCanvas );
197  if ( dlg.exec() )
198  {
199  mModel->updateRule( index.parent(), index.row() );
200  emit widgetChanged();
201  }
202 }
203 
204 void QgsRuleBasedLabelingWidget::removeRule()
205 {
206  const QItemSelection sel = viewRules->selectionModel()->selection();
207  const auto constSel = sel;
208  for ( const QItemSelectionRange &range : constSel )
209  {
210  if ( range.isValid() )
211  mModel->removeRows( range.top(), range.bottom() - range.top() + 1, range.parent() );
212  }
213  // make sure that the selection is gone
214  viewRules->selectionModel()->clear();
215 }
216 
217 void QgsRuleBasedLabelingWidget::copy()
218 {
219  const QModelIndexList indexlist = viewRules->selectionModel()->selectedRows();
220 
221  if ( indexlist.isEmpty() )
222  return;
223 
224  QMimeData *mime = mModel->mimeData( indexlist );
225  QApplication::clipboard()->setMimeData( mime );
226 }
227 
228 void QgsRuleBasedLabelingWidget::paste()
229 {
230  const QMimeData *mime = QApplication::clipboard()->mimeData();
231  QModelIndexList indexlist = viewRules->selectionModel()->selectedRows();
232  QModelIndex index;
233  if ( indexlist.isEmpty() )
234  index = mModel->index( mModel->rowCount(), 0 );
235  else
236  index = indexlist.first();
237  mModel->dropMimeData( mime, Qt::CopyAction, index.row(), index.column(), index.parent() );
238 }
239 
240 QgsRuleBasedLabeling::Rule *QgsRuleBasedLabelingWidget::currentRule()
241 {
242  QItemSelectionModel *sel = viewRules->selectionModel();
243  const QModelIndex idx = sel->currentIndex();
244  if ( !idx.isValid() )
245  return nullptr;
246  return mModel->ruleForIndex( idx );
247 }
248 
249 #include "qgsvscrollarea.h"
250 
252  : QDialog( parent )
253 {
254 
255 #ifdef Q_OS_MAC
256  setWindowModality( Qt::WindowModal );
257 #endif
258 
259  QVBoxLayout *layout = new QVBoxLayout( this );
260  QgsVScrollArea *scrollArea = new QgsVScrollArea( this );
261  scrollArea->setFrameShape( QFrame::NoFrame );
262  layout->addWidget( scrollArea );
263 
264  buttonBox = new QDialogButtonBox( QDialogButtonBox::Cancel | QDialogButtonBox::Help | QDialogButtonBox::Ok );
265  mPropsWidget = new QgsLabelingRulePropsWidget( rule, layer, this, mapCanvas );
266 
267  scrollArea->setWidget( mPropsWidget );
268  layout->addWidget( buttonBox );
269  this->setWindowTitle( "Edit Rule" );
271 
272  connect( buttonBox, &QDialogButtonBox::accepted, this, &QgsLabelingRulePropsDialog::accept );
273  connect( buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject );
274  connect( buttonBox, &QDialogButtonBox::helpRequested, this, &QgsLabelingRulePropsDialog::showHelp );
275 }
276 
278 {
279  mPropsWidget->testFilter();
280 }
281 
283 {
284  mPropsWidget->buildExpression();
285 }
286 
288 {
289  mPropsWidget->apply();
290  QDialog::accept();
291 }
292 
293 void QgsLabelingRulePropsDialog::showHelp()
294 {
295  QgsHelp::openHelp( QStringLiteral( "working_with_vector/vector_properties.html#rule-based-labeling" ) );
296 }
297 
299 
301  : QAbstractItemModel( parent )
302  , mRootRule( rootRule )
303 {
304 }
305 
306 Qt::ItemFlags QgsRuleBasedLabelingModel::flags( const QModelIndex &index ) const
307 {
308  if ( !index.isValid() )
309  return Qt::ItemIsDropEnabled;
310 
311  // allow drop only at first column
312  const Qt::ItemFlag drop = ( index.column() == 0 ? Qt::ItemIsDropEnabled : Qt::NoItemFlags );
313 
314  const Qt::ItemFlag checkable = ( index.column() == 0 ? Qt::ItemIsUserCheckable : Qt::NoItemFlags );
315 
316  return Qt::ItemIsEnabled | Qt::ItemIsSelectable |
317  Qt::ItemIsEditable | checkable |
318  Qt::ItemIsDragEnabled | drop;
319 }
320 
321 QVariant QgsRuleBasedLabelingModel::data( const QModelIndex &index, int role ) const
322 {
323  if ( !index.isValid() )
324  return QVariant();
325 
327 
328  if ( role == Qt::DisplayRole || role == Qt::ToolTipRole )
329  {
330  switch ( index.column() )
331  {
332  case 0:
333  return rule->description();
334  case 1:
335  if ( rule->isElse() )
336  {
337  return "ELSE";
338  }
339  else
340  {
341  return rule->filterExpression().isEmpty() ? tr( "(no filter)" ) : rule->filterExpression();
342  }
343  case 2:
344  return rule->dependsOnScale() ? QgsScaleComboBox::toString( rule->minimumScale() ) : QVariant();
345  case 3:
346  return rule->dependsOnScale() ? QgsScaleComboBox::toString( rule->maximumScale() ) : QVariant();
347  case 4:
348  return rule->settings() ? rule->settings()->fieldName : QVariant();
349  default:
350  return QVariant();
351  }
352  }
353  else if ( role == Qt::DecorationRole && index.column() == 0 && rule->settings() )
354  {
355  const int iconSize = QgsGuiUtils::scaleIconSize( 16 );
356  return QgsPalLayerSettings::labelSettingsPreviewPixmap( *rule->settings(), QSize( iconSize, iconSize ), QString(), static_cast< int >( iconSize * ICON_PADDING_FACTOR ) );
357  }
358  else if ( role == Qt::TextAlignmentRole )
359  {
360  return ( index.column() == 2 || index.column() == 3 ) ? static_cast<Qt::Alignment::Int>( Qt::AlignRight ) : static_cast<Qt::Alignment::Int>( Qt::AlignLeft );
361  }
362  else if ( role == Qt::FontRole && index.column() == 1 )
363  {
364  if ( rule->isElse() )
365  {
366  QFont italicFont;
367  italicFont.setItalic( true );
368  return italicFont;
369  }
370  return QVariant();
371  }
372  else if ( role == Qt::EditRole )
373  {
374  switch ( index.column() )
375  {
376  case 0:
377  return rule->description();
378  case 1:
379  return rule->filterExpression();
380  case 2:
381  return rule->minimumScale();
382  case 3:
383  return rule->maximumScale();
384  case 4:
385  return rule->settings() ? rule->settings()->fieldName : QVariant();
386  default:
387  return QVariant();
388  }
389  }
390  else if ( role == Qt::CheckStateRole )
391  {
392  if ( index.column() != 0 )
393  return QVariant();
394  return rule->active() ? Qt::Checked : Qt::Unchecked;
395  }
396  else
397  return QVariant();
398 }
399 
400 QVariant QgsRuleBasedLabelingModel::headerData( int section, Qt::Orientation orientation, int role ) const
401 {
402  if ( orientation == Qt::Horizontal && role == Qt::DisplayRole && section >= 0 && section < 5 )
403  {
404  QStringList lst;
405  lst << tr( "Label" ) << tr( "Rule" ) << tr( "Min. Scale" ) << tr( "Max. Scale" ) << tr( "Text" ); // << tr( "Count" ) << tr( "Duplicate Count" );
406  return lst[section];
407  }
408 
409  return QVariant();
410 }
411 
412 int QgsRuleBasedLabelingModel::rowCount( const QModelIndex &parent ) const
413 {
414  if ( parent.column() > 0 )
415  return 0;
416 
418 
419  return parentRule->children().count();
420 }
421 
422 int QgsRuleBasedLabelingModel::columnCount( const QModelIndex & ) const
423 {
424  return 5;
425 }
426 
427 QModelIndex QgsRuleBasedLabelingModel::index( int row, int column, const QModelIndex &parent ) const
428 {
429  if ( hasIndex( row, column, parent ) )
430  {
432  QgsRuleBasedLabeling::Rule *childRule = parentRule->children()[row];
433  return createIndex( row, column, childRule );
434  }
435  return QModelIndex();
436 }
437 
438 QModelIndex QgsRuleBasedLabelingModel::parent( const QModelIndex &index ) const
439 {
440  if ( !index.isValid() )
441  return QModelIndex();
442 
444  QgsRuleBasedLabeling::Rule *parentRule = childRule->parent();
445 
446  if ( parentRule == mRootRule )
447  return QModelIndex();
448 
449  // this is right: we need to know row number of our parent (in our grandparent)
450  const int row = parentRule->parent()->children().indexOf( parentRule );
451 
452  return createIndex( row, 0, parentRule );
453 }
454 
455 bool QgsRuleBasedLabelingModel::setData( const QModelIndex &index, const QVariant &value, int role )
456 {
457  if ( !index.isValid() )
458  return false;
459 
461 
462  if ( role == Qt::CheckStateRole )
463  {
464  rule->setActive( value.toInt() == Qt::Checked );
465  emit dataChanged( index, index );
466  return true;
467  }
468 
469  if ( role != Qt::EditRole )
470  return false;
471 
472  switch ( index.column() )
473  {
474  case 0: // description
475  rule->setDescription( value.toString() );
476  break;
477  case 1: // filter
478  rule->setFilterExpression( value.toString() );
479  break;
480  case 2: // scale min
481  rule->setMinimumScale( value.toDouble() );
482  break;
483  case 3: // scale max
484  rule->setMaximumScale( value.toDouble() );
485  break;
486  case 4: // label text
487  if ( !rule->settings() )
488  return false;
489  rule->settings()->fieldName = value.toString();
490  break;
491  default:
492  return false;
493  }
494 
495  emit dataChanged( index, index );
496  return true;
497 }
498 
500 {
501  return Qt::MoveAction; // | Qt::CopyAction
502 }
503 
505 {
506  QStringList types;
507  types << QStringLiteral( "application/vnd.text.list" );
508  return types;
509 }
510 
511 // manipulate DOM before dropping it so that rules are more useful
512 void _renderer2labelingRules( QDomElement &ruleElem )
513 {
514  // labeling rules recognize only "description"
515  if ( ruleElem.hasAttribute( QStringLiteral( "label" ) ) )
516  ruleElem.setAttribute( QStringLiteral( "description" ), ruleElem.attribute( QStringLiteral( "label" ) ) );
517 
518  // run recursively
519  QDomElement childRuleElem = ruleElem.firstChildElement( QStringLiteral( "rule" ) );
520  while ( !childRuleElem.isNull() )
521  {
522  _renderer2labelingRules( childRuleElem );
523  childRuleElem = childRuleElem.nextSiblingElement( QStringLiteral( "rule" ) );
524  }
525 }
526 
527 QMimeData *QgsRuleBasedLabelingModel::mimeData( const QModelIndexList &indexes ) const
528 {
529  QMimeData *mimeData = new QMimeData();
530  QByteArray encodedData;
531 
532  QDataStream stream( &encodedData, QIODevice::WriteOnly );
533 
534  const auto constIndexes = indexes;
535  for ( const QModelIndex &index : constIndexes )
536  {
537  // each item consists of several columns - let's add it with just first one
538  if ( !index.isValid() || index.column() != 0 )
539  continue;
540 
541  // we use a clone of the existing rule because it has a new unique rule key
542  // non-unique rule keys would confuse other components using them (e.g. legend)
544  QDomDocument doc;
545 
546  QDomElement rootElem = doc.createElement( QStringLiteral( "rule_mime" ) );
547  rootElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "labeling" ) ); // for determining whether rules are from renderer or labeling
548  const QDomElement rulesElem = rule->save( doc, QgsReadWriteContext() );
549  rootElem.appendChild( rulesElem );
550  doc.appendChild( rootElem );
551 
552  delete rule;
553 
554  stream << doc.toString( -1 );
555  }
556 
557  mimeData->setData( QStringLiteral( "application/vnd.text.list" ), encodedData );
558  return mimeData;
559 }
560 
561 bool QgsRuleBasedLabelingModel::dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent )
562 {
563  Q_UNUSED( column )
564 
565  if ( action == Qt::IgnoreAction )
566  return true;
567 
568  if ( !data->hasFormat( QStringLiteral( "application/vnd.text.list" ) ) )
569  return false;
570 
571  if ( parent.column() > 0 )
572  return false;
573 
574  QByteArray encodedData = data->data( QStringLiteral( "application/vnd.text.list" ) );
575  QDataStream stream( &encodedData, QIODevice::ReadOnly );
576  int rows = 0;
577 
578  if ( row == -1 )
579  {
580  // the item was dropped at a parent - we may decide where to put the items - let's append them
581  row = rowCount( parent );
582  }
583 
584  while ( !stream.atEnd() )
585  {
586  QString text;
587  stream >> text;
588 
589  QDomDocument doc;
590  if ( !doc.setContent( text ) )
591  continue;
592  const QDomElement rootElem = doc.documentElement();
593  if ( rootElem.tagName() != QLatin1String( "rule_mime" ) )
594  continue;
595  QDomElement ruleElem = rootElem.firstChildElement( QStringLiteral( "rule" ) );
596  if ( rootElem.attribute( QStringLiteral( "type" ) ) == QLatin1String( "renderer" ) )
597  _renderer2labelingRules( ruleElem ); // do some modifications so that we load the rules more nicely
599 
600  insertRule( parent, row + rows, rule );
601 
602  ++rows;
603  }
604  return true;
605 }
606 
607 bool QgsRuleBasedLabelingModel::removeRows( int row, int count, const QModelIndex &parent )
608 {
610 
611  if ( row < 0 || row >= parentRule->children().count() )
612  return false;
613 
614  beginRemoveRows( parent, row, row + count - 1 );
615 
616  for ( int i = 0; i < count; i++ )
617  {
618  if ( row < parentRule->children().count() )
619  {
620  parentRule->removeChildAt( row );
621  }
622  else
623  {
624  QgsDebugMsg( QStringLiteral( "trying to remove invalid index - this should not happen!" ) );
625  }
626  }
627 
628  endRemoveRows();
629 
630  return true;
631 }
632 
634 {
635  if ( index.isValid() )
636  return static_cast<QgsRuleBasedLabeling::Rule *>( index.internalPointer() );
637  return mRootRule;
638 }
639 
640 void QgsRuleBasedLabelingModel::insertRule( const QModelIndex &parent, int before, QgsRuleBasedLabeling::Rule *newrule )
641 {
642  beginInsertRows( parent, before, before );
643 
645  parentRule->insertChild( before, newrule );
646 
647  endInsertRows();
648 }
649 
650 void QgsRuleBasedLabelingModel::updateRule( const QModelIndex &parent, int row )
651 {
652  emit dataChanged( index( row, 0, parent ),
653  index( row, columnCount( parent ), parent ) );
654 }
655 
657 
659  : QgsPanelWidget( parent )
660  , mRule( rule )
661  , mLayer( layer )
662  , mSettings( nullptr )
663  , mMapCanvas( mapCanvas )
664 {
665  setupUi( this );
666 
667  QButtonGroup *radioGroup = new QButtonGroup( this );
668  radioGroup->addButton( mFilterRadio );
669  radioGroup->addButton( mElseRadio );
670 
671  mElseRadio->setChecked( mRule->isElse() );
672  mFilterRadio->setChecked( !mRule->isElse() );
673  editFilter->setText( mRule->filterExpression() );
674  editFilter->setToolTip( mRule->filterExpression() );
675  editDescription->setText( mRule->description() );
676  editDescription->setToolTip( mRule->description() );
677 
678  if ( mRule->dependsOnScale() )
679  {
680  groupScale->setChecked( true );
681  // caution: rule uses scale denom, scale widget uses true scales
682  mScaleRangeWidget->setScaleRange( std::max( rule->minimumScale(), 0.0 ),
683  std::max( rule->maximumScale(), 0.0 ) );
684  }
685  mScaleRangeWidget->setMapCanvas( mMapCanvas );
686 
687  if ( mRule->settings() )
688  {
689  groupSettings->setChecked( true );
690  mSettings = new QgsPalLayerSettings( *mRule->settings() ); // use a clone!
691  }
692  else
693  {
694  groupSettings->setChecked( false );
695  mSettings = new QgsPalLayerSettings;
696  }
697 
698  mLabelingGui = new QgsLabelingGui( nullptr, mMapCanvas, *mSettings, this );
699  mLabelingGui->layout()->setContentsMargins( 0, 0, 0, 0 );
700  QVBoxLayout *l = new QVBoxLayout;
701  l->addWidget( mLabelingGui );
702  groupSettings->setLayout( l );
703 
704  mLabelingGui->setLabelMode( QgsLabelingGui::Labels );
705  mLabelingGui->setLayer( mLayer );
706 
707  connect( btnExpressionBuilder, &QAbstractButton::clicked, this, &QgsLabelingRulePropsWidget::buildExpression );
708  connect( btnTestFilter, &QAbstractButton::clicked, this, &QgsLabelingRulePropsWidget::testFilter );
709  connect( editFilter, &QLineEdit::textEdited, this, &QgsLabelingRulePropsWidget::widgetChanged );
710  connect( editDescription, &QLineEdit::textChanged, this, &QgsLabelingRulePropsWidget::widgetChanged );
711  connect( groupScale, &QGroupBox::toggled, this, &QgsLabelingRulePropsWidget::widgetChanged );
713  connect( groupSettings, &QGroupBox::toggled, this, &QgsLabelingRulePropsWidget::widgetChanged );
715  connect( mFilterRadio, &QRadioButton::toggled, this, [ = ]( bool toggled ) { filterFrame->setEnabled( toggled ) ; } );
716  connect( mElseRadio, &QRadioButton::toggled, this, [ = ]( bool toggled ) { if ( toggled ) editFilter->setText( QStringLiteral( "ELSE" ) );} );
717 }
718 
720 {
721  delete mSettings;
722 }
723 
725 {
727  mLabelingGui->setDockMode( dockMode );
728 }
729 
731 {
732  if ( !mFilterRadio->isChecked() )
733  return;
734 
735  QgsExpression filter( editFilter->text() );
736  if ( filter.hasParserError() )
737  {
738  QMessageBox::critical( this, tr( "Test Filter" ), tr( "Filter expression parsing error:\n" ) + filter.parserErrorString() );
739  return;
740  }
741 
742  QgsExpressionContext context( _globalProjectAtlasMapLayerScopes( mMapCanvas, mLayer ) );
743 
744  if ( !filter.prepare( &context ) )
745  {
746  QMessageBox::critical( this, tr( "Test Filter" ), filter.evalErrorString() );
747  return;
748  }
749 
750  QApplication::setOverrideCursor( Qt::WaitCursor );
751 
752  QgsFeatureIterator fit = mLayer->getFeatures();
753 
754  int count = 0;
755  QgsFeature f;
756  while ( fit.nextFeature( f ) )
757  {
758  context.setFeature( f );
759 
760  const QVariant value = filter.evaluate( &context );
761  if ( value.toInt() != 0 )
762  count++;
763  if ( filter.hasEvalError() )
764  break;
765  }
766 
767  QApplication::restoreOverrideCursor();
768 
769  QMessageBox::information( this, tr( "Test Filter" ), tr( "Filter returned %n feature(s)", "number of filtered features", count ) );
770 }
771 
772 
774 {
775  const QgsExpressionContext context( _globalProjectAtlasMapLayerScopes( mMapCanvas, mLayer ) );
776 
777  QgsExpressionBuilderDialog dlg( mLayer, editFilter->text(), this, QStringLiteral( "generic" ), context );
778 
779  if ( dlg.exec() )
780  editFilter->setText( dlg.expressionText() );
781 }
782 
784 {
785  const QString filter = mElseRadio->isChecked() ? QStringLiteral( "ELSE" ) : editFilter->text().trimmed();
786  mRule->setFilterExpression( filter );
787  mRule->setDescription( editDescription->text() );
788  mRule->setMinimumScale( groupScale->isChecked() ? mScaleRangeWidget->minimumScale() : 0 );
789  mRule->setMaximumScale( groupScale->isChecked() ? mScaleRangeWidget->maximumScale() : 0 );
790  mRule->setSettings( groupSettings->isChecked() ? new QgsPalLayerSettings( mLabelingGui->layerSettings() ) : nullptr );
791 }
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...
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
Class for parsing and evaluation of expressions (formerly called "search strings").
bool prepare(const QgsExpressionContext *context)
Gets the expression ready for evaluation - find out column indexes.
bool hasParserError() const
Returns true if an error occurred when parsing the input expression.
QString evalErrorString() const
Returns evaluation error.
QString parserErrorString() const
Returns parser error.
bool hasEvalError() const
Returns true if an error occurred when evaluating last input.
QVariant evaluate()
Evaluate the feature and return the result.
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
static void enableAutoGeometryRestore(QWidget *widget, const QString &key=QString())
Register the widget to allow its position to be automatically saved and restored when open and closed...
Definition: qgsgui.cpp:180
static void openHelp(const QString &key)
Opens help topic for the given help key using default system web browser.
Definition: qgshelp.cpp:36
Dialog for editing labeling rule.
void buildExpression()
Open the expression builder widget.
QgsLabelingRulePropsDialog(QgsRuleBasedLabeling::Rule *rule, QgsVectorLayer *layer, QWidget *parent=nullptr, QgsMapCanvas *mapCanvas=nullptr)
Constructor for QgsLabelingRulePropsDialog.
void testFilter()
Test the filter that is set in the widget.
QgsRuleBasedLabeling::Rule * rule()
Returns the current set rule.
void accept() override
Apply any changes from the widget to the set rule.
Widget for editing a labeling rule.
void setDockMode(bool dockMode) override
Set the widget in dock mode.
void apply()
Apply any changes from the widget to the set rule.
void testFilter()
Test the filter that is set in the widget.
QgsRuleBasedLabeling::Rule * rule()
Returns the rule being edited.
void buildExpression()
Open the expression builder widget.
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:90
QgsExpressionContextScope & expressionContextScope()
Returns a reference to the expression context scope for the map canvas.
Definition: qgsmapcanvas.h:728
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.
static QgsPanelWidget * findParentPanel(QWidget *widget)
Traces through the parents of a widget to find if it is contained within a QgsPanelWidget widget.
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:479
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.
QgsVScrollArea is a QScrollArea subclass which only displays a vertical scrollbar and fits the width ...
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)