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