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