QGIS API Documentation 3.99.0-Master (2fe06baccd8)
Loading...
Searching...
No Matches
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"
20#include "qgsfeatureiterator.h"
21#include "qgslabelinggui.h"
22#include "qgslogger.h"
23#include "qgsmapcanvas.h"
24#include "qgsproject.h"
25#include "qgsreadwritecontext.h"
27#include "qgsvectorlayer.h"
29
30#include <QAction>
31#include <QClipboard>
32#include <QMessageBox>
33
34#include "moc_qgsrulebasedlabelingwidget.cpp"
35
36const double ICON_PADDING_FACTOR = 0.16;
37
38QgsExpressionContext QgsLabelingRulePropsWidget::createExpressionContext( QgsMapCanvas *mapCanvas, const QgsMapLayer *layer )
39{
41 if ( mapCanvas )
42 {
43 context = mapCanvas->createExpressionContext();
44 }
45 else
46 {
51 }
52 context << QgsExpressionContextUtils::layerScope( layer );
53 return context;
54}
55
56
58 : QgsPanelWidget( parent )
59 , mLayer( layer )
60 , mCanvas( canvas )
61
62{
63 setupUi( this );
64
65 btnAddRule->setIcon( QIcon( QgsApplication::iconPath( "symbologyAdd.svg" ) ) );
66 btnEditRule->setIcon( QIcon( QgsApplication::iconPath( "symbologyEdit.svg" ) ) );
67 btnRemoveRule->setIcon( QIcon( QgsApplication::iconPath( "symbologyRemove.svg" ) ) );
68
69 mCopyAction = new QAction( tr( "Copy" ), this );
70 mCopyAction->setShortcut( QKeySequence( QKeySequence::Copy ) );
71 mPasteAction = new QAction( tr( "Paste" ), this );
72 mPasteAction->setShortcut( QKeySequence( QKeySequence::Paste ) );
73 mDeleteAction = new QAction( tr( "Remove Rule" ), this );
74 mDeleteAction->setShortcut( QKeySequence( QKeySequence::Delete ) );
75
76 viewRules->addAction( mCopyAction );
77 viewRules->addAction( mPasteAction );
78 viewRules->addAction( mDeleteAction );
79
80 connect( viewRules, &QAbstractItemView::doubleClicked, this, static_cast<void ( QgsRuleBasedLabelingWidget::* )( const QModelIndex & )>( &QgsRuleBasedLabelingWidget::editRule ) );
81
82 connect( btnAddRule, &QAbstractButton::clicked, this, &QgsRuleBasedLabelingWidget::addRule );
83 connect( btnEditRule, &QAbstractButton::clicked, this, static_cast<void ( QgsRuleBasedLabelingWidget::* )()>( &QgsRuleBasedLabelingWidget::editRule ) );
84 connect( btnRemoveRule, &QAbstractButton::clicked, this, &QgsRuleBasedLabelingWidget::removeRule );
85 connect( mCopyAction, &QAction::triggered, this, &QgsRuleBasedLabelingWidget::copy );
86 connect( mPasteAction, &QAction::triggered, this, &QgsRuleBasedLabelingWidget::paste );
87 connect( mDeleteAction, &QAction::triggered, this, &QgsRuleBasedLabelingWidget::removeRule );
88
89 if ( mLayer->labeling() && mLayer->labeling()->type() == QLatin1String( "rule-based" ) )
90 {
91 const QgsRuleBasedLabeling *rl = static_cast<const QgsRuleBasedLabeling *>( mLayer->labeling() );
92 mRootRule = rl->rootRule()->clone( false );
93 }
94 else if ( mLayer->labeling() && mLayer->labeling()->type() == QLatin1String( "simple" ) )
95 {
96 // copy simple label settings to first rule
97 mRootRule = new QgsRuleBasedLabeling::Rule( nullptr );
98 auto newSettings = std::make_unique<QgsPalLayerSettings>( mLayer->labeling()->settings() );
99 newSettings->drawLabels = true; // otherwise we may be trying to copy a "blocking" setting to a rule - which is confusing for users!
100 mRootRule->appendChild( new QgsRuleBasedLabeling::Rule( newSettings.release() ) );
101 }
102 else
103 {
104 mRootRule = new QgsRuleBasedLabeling::Rule( nullptr );
105 }
106
107 mModel = new QgsRuleBasedLabelingModel( mRootRule );
108 viewRules->setModel( mModel );
109
110 connect( mModel, &QAbstractItemModel::dataChanged, this, &QgsRuleBasedLabelingWidget::widgetChanged );
111 connect( mModel, &QAbstractItemModel::rowsInserted, this, &QgsRuleBasedLabelingWidget::widgetChanged );
112 connect( mModel, &QAbstractItemModel::rowsRemoved, this, &QgsRuleBasedLabelingWidget::widgetChanged );
113}
114
119
121{
122 if ( dockMode )
123 {
124 // when in dock mode, these shortcuts conflict with the main window shortcuts and cannot be used
125 if ( mCopyAction )
126 mCopyAction->setShortcut( QKeySequence() );
127 if ( mPasteAction )
128 mPasteAction->setShortcut( QKeySequence() );
129 if ( mDeleteAction )
130 mDeleteAction->setShortcut( QKeySequence() );
131 }
133}
134
135void QgsRuleBasedLabelingWidget::addRule()
136{
138
139 QgsRuleBasedLabeling::Rule *current = currentRule();
140 if ( current )
141 {
142 // add after this rule
143 const QModelIndex currentIndex = viewRules->selectionModel()->currentIndex();
144 mModel->insertRule( currentIndex.parent(), currentIndex.row() + 1, newrule );
145 const 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 const int rows = mModel->rowCount();
152 mModel->insertRule( QModelIndex(), rows, newrule );
153 const QModelIndex newindex = mModel->index( rows, 0 );
154 viewRules->selectionModel()->setCurrentIndex( newindex, QItemSelectionModel::ClearAndSelect );
155 }
156 editRule();
157}
158
159void QgsRuleBasedLabelingWidget::ruleWidgetPanelAccepted( QgsPanelWidget *panel )
160{
161 QgsLabelingRulePropsWidget *widget = qobject_cast<QgsLabelingRulePropsWidget *>( panel );
162 widget->apply();
163
164 const QModelIndex index = viewRules->selectionModel()->currentIndex();
165 mModel->updateRule( index.parent(), index.row() );
166}
167
168void QgsRuleBasedLabelingWidget::liveUpdateRuleFromPanel()
169{
170 ruleWidgetPanelAccepted( qobject_cast<QgsPanelWidget *>( sender() ) );
171}
172
173
174void QgsRuleBasedLabelingWidget::editRule()
175{
176 editRule( viewRules->selectionModel()->currentIndex() );
177}
178
179void QgsRuleBasedLabelingWidget::editRule( const QModelIndex &index )
180{
181 if ( !index.isValid() )
182 return;
183
184 QgsRuleBasedLabeling::Rule *rule = mModel->ruleForIndex( index );
186
187 if ( panel && panel->dockMode() )
188 {
189 QgsLabelingRulePropsWidget *widget = new QgsLabelingRulePropsWidget( rule, mLayer, this, mCanvas );
190 widget->setPanelTitle( tr( "Edit Rule" ) );
191 connect( widget, &QgsPanelWidget::panelAccepted, this, &QgsRuleBasedLabelingWidget::ruleWidgetPanelAccepted );
192 connect( widget, &QgsLabelingRulePropsWidget::widgetChanged, this, &QgsRuleBasedLabelingWidget::liveUpdateRuleFromPanel );
193 openPanel( widget );
194 return;
195 }
196
197 QgsLabelingRulePropsDialog dlg( rule, mLayer, this, mCanvas );
198 if ( dlg.exec() )
199 {
200 mModel->updateRule( index.parent(), index.row() );
201 emit widgetChanged();
202 }
203}
204
205void QgsRuleBasedLabelingWidget::removeRule()
206{
207 const QItemSelection sel = viewRules->selectionModel()->selection();
208 const auto constSel = sel;
209 for ( const QItemSelectionRange &range : constSel )
210 {
211 if ( range.isValid() )
212 mModel->removeRows( range.top(), range.bottom() - range.top() + 1, range.parent() );
213 }
214 // make sure that the selection is gone
215 viewRules->selectionModel()->clear();
216}
217
218void QgsRuleBasedLabelingWidget::copy()
219{
220 const QModelIndexList indexlist = viewRules->selectionModel()->selectedRows();
221
222 if ( indexlist.isEmpty() )
223 return;
224
225 QMimeData *mime = mModel->mimeData( indexlist );
226 QApplication::clipboard()->setMimeData( mime );
227}
228
229void QgsRuleBasedLabelingWidget::paste()
230{
231 const QMimeData *mime = QApplication::clipboard()->mimeData();
232 if ( !mime )
233 return;
234
235 QModelIndexList indexlist = viewRules->selectionModel()->selectedRows();
236 QModelIndex index;
237 if ( indexlist.isEmpty() )
238 index = mModel->index( mModel->rowCount(), 0 );
239 else
240 index = indexlist.first();
241 mModel->dropMimeData( mime, Qt::CopyAction, index.row(), index.column(), index.parent() );
242}
243
244QgsRuleBasedLabeling::Rule *QgsRuleBasedLabelingWidget::currentRule()
245{
246 QItemSelectionModel *sel = viewRules->selectionModel();
247 const QModelIndex idx = sel->currentIndex();
248 if ( !idx.isValid() )
249 return nullptr;
250 return mModel->ruleForIndex( idx );
251}
252
253#include "qgsvscrollarea.h"
254
256 : QDialog( parent )
257{
258#ifdef Q_OS_MAC
259 setWindowModality( Qt::WindowModal );
260#endif
261
262 QVBoxLayout *layout = new QVBoxLayout( this );
263 QgsVScrollArea *scrollArea = new QgsVScrollArea( this );
264 scrollArea->setFrameShape( QFrame::NoFrame );
265 layout->addWidget( scrollArea );
266
267 buttonBox = new QDialogButtonBox( QDialogButtonBox::Cancel | QDialogButtonBox::Help | QDialogButtonBox::Ok );
268 mPropsWidget = new QgsLabelingRulePropsWidget( rule, layer, this, mapCanvas );
269
270 scrollArea->setWidget( mPropsWidget );
271 layout->addWidget( buttonBox );
272 this->setWindowTitle( "Edit Rule" );
274
275 connect( buttonBox, &QDialogButtonBox::accepted, this, &QgsLabelingRulePropsDialog::accept );
276 connect( buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject );
277 connect( buttonBox, &QDialogButtonBox::helpRequested, this, &QgsLabelingRulePropsDialog::showHelp );
278}
279
281{
282 mPropsWidget->testFilter();
283}
284
286{
287 mPropsWidget->buildExpression();
288}
289
291{
292 mPropsWidget->apply();
293 QDialog::accept();
294}
295
296void QgsLabelingRulePropsDialog::showHelp()
297{
298 QgsHelp::openHelp( QStringLiteral( "working_with_vector/vector_properties.html#rule-based-labeling" ) );
299}
300
302
304 : QAbstractItemModel( parent )
305 , mRootRule( rootRule )
306{
307}
308
309Qt::ItemFlags QgsRuleBasedLabelingModel::flags( const QModelIndex &index ) const
310{
311 if ( !index.isValid() )
312 return Qt::ItemIsDropEnabled;
313
314 // allow drop only at first column
315 const Qt::ItemFlag drop = ( index.column() == 0 ? Qt::ItemIsDropEnabled : Qt::NoItemFlags );
316
317 const Qt::ItemFlag checkable = ( index.column() == 0 ? Qt::ItemIsUserCheckable : Qt::NoItemFlags );
318
319 return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable | checkable | Qt::ItemIsDragEnabled | drop;
320}
321
322QVariant QgsRuleBasedLabelingModel::data( const QModelIndex &index, int role ) const
323{
324 if ( !index.isValid() )
325 return QVariant();
326
328
329 if ( role == Qt::DisplayRole || role == Qt::ToolTipRole )
330 {
331 switch ( index.column() )
332 {
333 case 0:
334 return rule->description();
335 case 1:
336 if ( rule->isElse() )
337 {
338 return "ELSE";
339 }
340 else
341 {
342 return rule->filterExpression().isEmpty() ? tr( "(no filter)" ) : rule->filterExpression();
343 }
344 case 2:
345 return rule->dependsOnScale() ? QgsScaleComboBox::toString( rule->minimumScale() ) : QVariant();
346 case 3:
347 return rule->dependsOnScale() ? QgsScaleComboBox::toString( rule->maximumScale() ) : QVariant();
348 case 4:
349 return rule->settings() ? rule->settings()->fieldName : QVariant();
350 default:
351 return QVariant();
352 }
353 }
354 else if ( role == Qt::DecorationRole && index.column() == 0 && rule->settings() )
355 {
356 const int iconSize = QgsGuiUtils::scaleIconSize( 16 );
357 return QgsPalLayerSettings::labelSettingsPreviewPixmap( *rule->settings(), QSize( iconSize, iconSize ), QString(), static_cast<int>( iconSize * ICON_PADDING_FACTOR ) );
358 }
359 else if ( role == Qt::TextAlignmentRole )
360 {
361 return ( index.column() == 2 || index.column() == 3 ) ? static_cast<Qt::Alignment::Int>( Qt::AlignRight ) : static_cast<Qt::Alignment::Int>( Qt::AlignLeft );
362 }
363 else if ( role == Qt::FontRole && index.column() == 1 )
364 {
365 if ( rule->isElse() )
366 {
367 QFont italicFont;
368 italicFont.setItalic( true );
369 return italicFont;
370 }
371 return QVariant();
372 }
373 else if ( role == Qt::EditRole )
374 {
375 switch ( index.column() )
376 {
377 case 0:
378 return rule->description();
379 case 1:
380 return rule->filterExpression();
381 case 2:
382 return rule->minimumScale();
383 case 3:
384 return rule->maximumScale();
385 case 4:
386 return rule->settings() ? rule->settings()->fieldName : QVariant();
387 default:
388 return QVariant();
389 }
390 }
391 else if ( role == Qt::CheckStateRole )
392 {
393 if ( index.column() != 0 )
394 return QVariant();
395 return rule->active() ? Qt::Checked : Qt::Unchecked;
396 }
397 else
398 return QVariant();
399}
400
401QVariant QgsRuleBasedLabelingModel::headerData( int section, Qt::Orientation orientation, int role ) const
402{
403 if ( orientation == Qt::Horizontal && role == Qt::DisplayRole && section >= 0 && section < 5 )
404 {
405 QStringList lst;
406 lst << tr( "Label" ) << tr( "Rule" ) << tr( "Min. Scale" ) << tr( "Max. Scale" ) << tr( "Text" ); // << tr( "Count" ) << tr( "Duplicate Count" );
407 return lst[section];
408 }
409
410 return QVariant();
411}
412
413int QgsRuleBasedLabelingModel::rowCount( const QModelIndex &parent ) const
414{
415 if ( parent.column() > 0 )
416 return 0;
417
419
420 return parentRule->children().count();
421}
422
423int QgsRuleBasedLabelingModel::columnCount( const QModelIndex & ) const
424{
425 return 5;
426}
427
428QModelIndex QgsRuleBasedLabelingModel::index( int row, int column, const QModelIndex &parent ) const
429{
430 if ( hasIndex( row, column, parent ) )
431 {
433 QgsRuleBasedLabeling::Rule *childRule = parentRule->children()[row];
434 return createIndex( row, column, childRule );
435 }
436 return QModelIndex();
437}
438
439QModelIndex QgsRuleBasedLabelingModel::parent( const QModelIndex &index ) const
440{
441 if ( !index.isValid() )
442 return QModelIndex();
443
445 QgsRuleBasedLabeling::Rule *parentRule = childRule->parent();
446
447 if ( parentRule == mRootRule )
448 return QModelIndex();
449
450 // this is right: we need to know row number of our parent (in our grandparent)
451 const int row = parentRule->parent()->children().indexOf( parentRule );
452
453 return createIndex( row, 0, parentRule );
454}
455
456bool QgsRuleBasedLabelingModel::setData( const QModelIndex &index, const QVariant &value, int role )
457{
458 if ( !index.isValid() )
459 return false;
460
462
463 if ( role == Qt::CheckStateRole )
464 {
465 rule->setActive( value.toInt() == Qt::Checked );
466 emit dataChanged( index, index );
467 return true;
468 }
469
470 if ( role != Qt::EditRole )
471 return false;
472
473 switch ( index.column() )
474 {
475 case 0: // description
476 rule->setDescription( value.toString() );
477 break;
478 case 1: // filter
479 rule->setFilterExpression( value.toString() );
480 break;
481 case 2: // scale min
482 rule->setMinimumScale( value.toDouble() );
483 break;
484 case 3: // scale max
485 rule->setMaximumScale( value.toDouble() );
486 break;
487 case 4: // label text
488 if ( !rule->settings() )
489 return false;
490 rule->settings()->fieldName = value.toString();
491 break;
492 default:
493 return false;
494 }
495
496 emit dataChanged( index, index );
497 return true;
498}
499
501{
502 return Qt::MoveAction; // | Qt::CopyAction
503}
504
506{
507 QStringList types;
508 types << QStringLiteral( "application/vnd.text.list" );
509 return types;
510}
511
512// manipulate DOM before dropping it so that rules are more useful
513void _renderer2labelingRules( QDomElement &ruleElem )
514{
515 // labeling rules recognize only "description"
516 if ( ruleElem.hasAttribute( QStringLiteral( "label" ) ) )
517 ruleElem.setAttribute( QStringLiteral( "description" ), ruleElem.attribute( QStringLiteral( "label" ) ) );
518
519 // run recursively
520 QDomElement childRuleElem = ruleElem.firstChildElement( QStringLiteral( "rule" ) );
521 while ( !childRuleElem.isNull() )
522 {
523 _renderer2labelingRules( childRuleElem );
524 childRuleElem = childRuleElem.nextSiblingElement( QStringLiteral( "rule" ) );
525 }
526}
527
528QMimeData *QgsRuleBasedLabelingModel::mimeData( const QModelIndexList &indexes ) const
529{
530 QMimeData *mimeData = new QMimeData();
531 QByteArray encodedData;
532
533 QDataStream stream( &encodedData, QIODevice::WriteOnly );
534
535 const auto constIndexes = indexes;
536 for ( const QModelIndex &index : constIndexes )
537 {
538 // each item consists of several columns - let's add it with just first one
539 if ( !index.isValid() || index.column() != 0 )
540 continue;
541
542 // we use a clone of the existing rule because it has a new unique rule key
543 // non-unique rule keys would confuse other components using them (e.g. legend)
545 QDomDocument doc;
546
547 QDomElement rootElem = doc.createElement( QStringLiteral( "rule_mime" ) );
548 rootElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "labeling" ) ); // for determining whether rules are from renderer or labeling
549 const QDomElement rulesElem = rule->save( doc, QgsReadWriteContext() );
550 rootElem.appendChild( rulesElem );
551 doc.appendChild( rootElem );
552
553 delete rule;
554
555 stream << doc.toString( -1 );
556 }
557
558 mimeData->setData( QStringLiteral( "application/vnd.text.list" ), encodedData );
559 return mimeData;
560}
561
562bool QgsRuleBasedLabelingModel::dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent )
563{
564 Q_UNUSED( column )
565
566 if ( action == Qt::IgnoreAction )
567 return true;
568
569 if ( !data->hasFormat( QStringLiteral( "application/vnd.text.list" ) ) )
570 return false;
571
572 if ( parent.column() > 0 )
573 return false;
574
575 QByteArray encodedData = data->data( QStringLiteral( "application/vnd.text.list" ) );
576 QDataStream stream( &encodedData, QIODevice::ReadOnly );
577 int rows = 0;
578
579 if ( row == -1 )
580 {
581 // the item was dropped at a parent - we may decide where to put the items - let's append them
582 row = rowCount( parent );
583 }
584
585 while ( !stream.atEnd() )
586 {
587 QString text;
588 stream >> text;
589
590 QDomDocument doc;
591 if ( !doc.setContent( text ) )
592 continue;
593 const QDomElement rootElem = doc.documentElement();
594 if ( rootElem.tagName() != QLatin1String( "rule_mime" ) )
595 continue;
596 QDomElement ruleElem = rootElem.firstChildElement( QStringLiteral( "rule" ) );
597 if ( rootElem.attribute( QStringLiteral( "type" ) ) == QLatin1String( "renderer" ) )
598 _renderer2labelingRules( ruleElem ); // do some modifications so that we load the rules more nicely
600
601 insertRule( parent, row + rows, rule );
602
603 ++rows;
604 }
605 return true;
606}
607
608bool QgsRuleBasedLabelingModel::removeRows( int row, int count, const QModelIndex &parent )
609{
611
612 if ( row < 0 || row >= parentRule->children().count() )
613 return false;
614
615 beginRemoveRows( parent, row, row + count - 1 );
616
617 for ( int i = 0; i < count; i++ )
618 {
619 if ( row < parentRule->children().count() )
620 {
621 parentRule->removeChildAt( row );
622 }
623 else
624 {
625 QgsDebugError( QStringLiteral( "trying to remove invalid index - this should not happen!" ) );
626 }
627 }
628
629 endRemoveRows();
630
631 return true;
632}
633
635{
636 if ( index.isValid() )
637 return static_cast<QgsRuleBasedLabeling::Rule *>( index.internalPointer() );
638 return mRootRule;
639}
640
641void QgsRuleBasedLabelingModel::insertRule( const QModelIndex &parent, int before, QgsRuleBasedLabeling::Rule *newrule )
642{
643 beginInsertRows( parent, before, before );
644
646 parentRule->insertChild( before, newrule );
647
648 endInsertRows();
649}
650
651void QgsRuleBasedLabelingModel::updateRule( const QModelIndex &parent, int row )
652{
653 emit dataChanged( index( row, 0, parent ), index( row, columnCount( parent ), parent ) );
654}
655
657
659 : QgsPanelWidget( parent )
660 , mRule( rule )
661 , mLayer( layer )
662 , mMapCanvas( mapCanvas )
663{
664 setupUi( this );
665
666 QButtonGroup *radioGroup = new QButtonGroup( this );
667 radioGroup->addButton( mFilterRadio );
668 radioGroup->addButton( mElseRadio );
669
670 mElseRadio->setChecked( mRule->isElse() );
671 mFilterRadio->setChecked( !mRule->isElse() );
672 editFilter->setText( mRule->filterExpression() );
673 editFilter->setToolTip( mRule->filterExpression() );
674 editDescription->setText( mRule->description() );
675 editDescription->setToolTip( mRule->description() );
676
677 if ( mRule->dependsOnScale() )
678 {
679 groupScale->setChecked( true );
680 // caution: rule uses scale denom, scale widget uses true scales
681 mScaleRangeWidget->setScaleRange( std::max( rule->minimumScale(), 0.0 ), std::max( rule->maximumScale(), 0.0 ) );
682 }
683 mScaleRangeWidget->setMapCanvas( mMapCanvas );
684
685 if ( mRule->settings() )
686 {
687 groupSettings->setChecked( true );
688 mSettings = new QgsPalLayerSettings( *mRule->settings() ); // use a clone!
689 }
690 else
691 {
692 groupSettings->setChecked( false );
693 mSettings = new QgsPalLayerSettings;
694 }
695
696 mLabelingGui = new QgsLabelingGui( mMapCanvas, *mSettings, this );
697 mLabelingGui->layout()->setContentsMargins( 0, 0, 0, 0 );
698 QVBoxLayout *l = new QVBoxLayout;
699 l->addWidget( mLabelingGui );
700 groupSettings->setLayout( l );
701
702 mLabelingGui->setLabelMode( QgsLabelingGui::Labels );
703 mLabelingGui->setLayer( mLayer );
704
705 connect( btnExpressionBuilder, &QAbstractButton::clicked, this, &QgsLabelingRulePropsWidget::buildExpression );
706 connect( btnTestFilter, &QAbstractButton::clicked, this, &QgsLabelingRulePropsWidget::testFilter );
707 connect( editFilter, &QLineEdit::textEdited, this, &QgsLabelingRulePropsWidget::widgetChanged );
708 connect( editDescription, &QLineEdit::textChanged, this, &QgsLabelingRulePropsWidget::widgetChanged );
709 connect( groupScale, &QGroupBox::toggled, this, &QgsLabelingRulePropsWidget::widgetChanged );
711 connect( groupSettings, &QGroupBox::toggled, this, &QgsLabelingRulePropsWidget::widgetChanged );
713 connect( mFilterRadio, &QRadioButton::toggled, this, [this]( bool toggled ) { filterFrame->setEnabled( toggled ); } );
714 connect( mElseRadio, &QRadioButton::toggled, this, [this]( bool toggled ) { if ( toggled ) editFilter->setText( QStringLiteral( "ELSE" ) ); } );
715}
716
721
723{
725 mLabelingGui->setDockMode( dockMode );
726}
727
729{
730 if ( !mFilterRadio->isChecked() )
731 return;
732
733 QgsExpression filter( editFilter->text() );
734 if ( filter.hasParserError() )
735 {
736 QMessageBox::critical( this, tr( "Test Filter" ), tr( "Filter expression parsing error:\n" ) + filter.parserErrorString() );
737 return;
738 }
739
740 QgsExpressionContext context( createExpressionContext( mMapCanvas, mLayer ) );
741
742 if ( !filter.prepare( &context ) )
743 {
744 QMessageBox::critical( this, tr( "Test Filter" ), filter.evalErrorString() );
745 return;
746 }
747
748 QApplication::setOverrideCursor( Qt::WaitCursor );
749
750 QgsFeatureIterator fit = mLayer->getFeatures();
751
752 int count = 0;
753 QgsFeature f;
754 while ( fit.nextFeature( f ) )
755 {
756 context.setFeature( f );
757
758 const QVariant value = filter.evaluate( &context );
759 if ( value.toInt() != 0 )
760 count++;
761 if ( filter.hasEvalError() )
762 break;
763 }
764
765 QApplication::restoreOverrideCursor();
766
767 QMessageBox::information( this, tr( "Test Filter" ), tr( "Filter returned %n feature(s)", "number of filtered features", count ) );
768}
769
770
772{
773 const QgsExpressionContext context( createExpressionContext( mMapCanvas, mLayer ) );
774
775 QgsExpressionBuilderDialog dlg( mLayer, editFilter->text(), this, QStringLiteral( "generic" ), context );
776
777 if ( dlg.exec() )
778 editFilter->setText( dlg.expressionText() );
779}
780
782{
783 const QString filter = mElseRadio->isChecked() ? QStringLiteral( "ELSE" ) : editFilter->text().trimmed();
784 mRule->setFilterExpression( filter );
785 mRule->setDescription( editDescription->text() );
786 mRule->setMinimumScale( groupScale->isChecked() ? mScaleRangeWidget->minimumScale() : 0 );
787 mRule->setMaximumScale( groupScale->isChecked() ? mScaleRangeWidget->maximumScale() : 0 );
788 mRule->setSettings( groupSettings->isChecked() ? new QgsPalLayerSettings( mLabelingGui->layerSettings() ) : nullptr );
789}
static QgsPalLayerSettings defaultSettingsForLayer(const QgsVectorLayer *layer)
Returns the default layer settings to use for the specified vector layer.
static QString iconPath(const QString &iconFile)
Returns path to the desired icon file.
A generic dialog for building expression strings.
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.
Handles 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)
Fetch next feature and stores in f, returns true on success.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:58
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:221
static void openHelp(const QString &key)
Opens help topic for the given help key using default system web browser.
Definition qgshelp.cpp:38
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.
QgsRuleBasedLabeling::Rule * rule()
Returns the rule being edited.
void apply()
Apply any changes from the widget to the set rule.
void testFilter()
Test the filter that is set in the widget.
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.
QgsExpressionContext createExpressionContext() const override
This method needs to be reimplemented in all classes which implement this interface and return an exp...
Base class for all map layer types.
Definition qgsmaplayer.h:80
Contains configuration for rendering maps.
Contains settings for how a map layer will be labeled.
QString fieldName
Name of field (or an expression) to use for label text.
static QPixmap labelSettingsPreviewPixmap(const QgsPalLayerSettings &settings, QSize size, const QString &previewText=QString(), int padding=0, const QgsScreenProperties &screen=QgsScreenProperties())
Returns a pixmap preview for label settings.
Base class for any widget that can be shown as an 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 ...
bool dockMode() const
Returns the dock mode state.
void panelAccepted(QgsPanelWidget *panel)
Emitted when the panel is accepted by the user.
QgsPanelWidget(QWidget *parent=nullptr)
Base class for any widget that can be shown as an inline panel.
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.
static QgsProject * instance()
Returns the QgsProject singleton instance.
A container for the context for various read/write operations on 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
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.
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.
const QgsRuleBasedLabeling::RuleList & children() const
Returns all children rules of this rule.
QString filterExpression() const
A filter that will check if this rule applies.
const QgsRuleBasedLabeling::Rule * parent() const
The parent rule.
bool active() const
Returns if this rule is active.
QgsPalLayerSettings * settings() const
Returns the labeling settings.
void removeChildAt(int i)
delete child rule
void setActive(bool state)
Sets if this rule is active.
static QgsRuleBasedLabeling::Rule * create(const QDomElement &ruleElem, const QgsReadWriteContext &context, bool reuseId=true)
Create a rule from an XML definition.
QDomElement save(QDomDocument &doc, const QgsReadWriteContext &context) const
store labeling info to XML element
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.
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.
QgsRuleBasedLabeling::Rule * clone(bool resetRuleKey=true) const
clone this rule
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, QgsScaleComboBox::RatioMode mode=QgsScaleComboBox::RatioMode::ForceUnitNumerator)
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.
A QScrollArea subclass which only displays a vertical scrollbar and fits the width to the contents.
Represents a vector layer which manages a vector based dataset.
int scaleIconSize(int standardSize)
Scales an icon size to compensate for display pixel density, making the icon size hi-dpi friendly,...
#define QgsDebugError(str)
Definition qgslogger.h:57
void _renderer2labelingRules(QDomElement &ruleElem)
const double ICON_PADDING_FACTOR