QGIS API Documentation 4.1.0-Master (5bf3c20f3c9)
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 = 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 = new 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 = new QgsRuleBasedLabeling::Rule( nullptr );
109 }
110
111 mModel = new QgsRuleBasedLabelingModel( mRootRule );
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
123
125{
126 if ( dockMode )
127 {
128 // when in dock mode, these shortcuts conflict with the main window shortcuts and cannot be used
129 if ( mCopyAction )
130 mCopyAction->setShortcut( QKeySequence() );
131 if ( mPasteAction )
132 mPasteAction->setShortcut( QKeySequence() );
133 if ( mDeleteAction )
134 mDeleteAction->setShortcut( QKeySequence() );
135 }
137}
138
139void QgsRuleBasedLabelingWidget::addRule()
140{
142
143 QgsRuleBasedLabeling::Rule *current = currentRule();
144 if ( current )
145 {
146 // add after this rule
147 const QModelIndex currentIndex = viewRules->selectionModel()->currentIndex();
148 mModel->insertRule( currentIndex.parent(), currentIndex.row() + 1, newrule );
149 const QModelIndex newindex = mModel->index( currentIndex.row() + 1, 0, currentIndex.parent() );
150 viewRules->selectionModel()->setCurrentIndex( newindex, QItemSelectionModel::ClearAndSelect );
151 }
152 else
153 {
154 // append to root rule
155 const int rows = mModel->rowCount();
156 mModel->insertRule( QModelIndex(), rows, newrule );
157 const QModelIndex newindex = mModel->index( rows, 0 );
158 viewRules->selectionModel()->setCurrentIndex( newindex, QItemSelectionModel::ClearAndSelect );
159 }
160 editRule();
161}
162
163void QgsRuleBasedLabelingWidget::ruleWidgetPanelAccepted( QgsPanelWidget *panel )
164{
165 QgsLabelingRulePropsWidget *widget = qobject_cast<QgsLabelingRulePropsWidget *>( panel );
166 widget->apply();
167
168 const QModelIndex index = viewRules->selectionModel()->currentIndex();
169 mModel->updateRule( index.parent(), index.row() );
170}
171
172void QgsRuleBasedLabelingWidget::liveUpdateRuleFromPanel()
173{
174 ruleWidgetPanelAccepted( qobject_cast<QgsPanelWidget *>( sender() ) );
175}
176
177
178void QgsRuleBasedLabelingWidget::editRule()
179{
180 editRule( viewRules->selectionModel()->currentIndex() );
181}
182
183void QgsRuleBasedLabelingWidget::editRule( const QModelIndex &index )
184{
185 if ( !index.isValid() )
186 return;
187
188 QgsRuleBasedLabeling::Rule *rule = mModel->ruleForIndex( index );
190
191 if ( panel && panel->dockMode() )
192 {
193 QgsLabelingRulePropsWidget *widget = new QgsLabelingRulePropsWidget( rule, mLayer, this, mCanvas );
194 widget->setPanelTitle( tr( "Edit Rule" ) );
195 connect( widget, &QgsPanelWidget::panelAccepted, this, &QgsRuleBasedLabelingWidget::ruleWidgetPanelAccepted );
196 connect( widget, &QgsLabelingRulePropsWidget::widgetChanged, this, &QgsRuleBasedLabelingWidget::liveUpdateRuleFromPanel );
197 openPanel( widget );
198 return;
199 }
200
201 QgsLabelingRulePropsDialog dlg( rule, mLayer, this, mCanvas );
202 if ( dlg.exec() )
203 {
204 mModel->updateRule( index.parent(), index.row() );
205 emit widgetChanged();
206 }
207}
208
209void QgsRuleBasedLabelingWidget::removeRule()
210{
211 const QItemSelection sel = viewRules->selectionModel()->selection();
212 const auto constSel = sel;
213 for ( const QItemSelectionRange &range : constSel )
214 {
215 if ( range.isValid() )
216 mModel->removeRows( range.top(), range.bottom() - range.top() + 1, range.parent() );
217 }
218 // make sure that the selection is gone
219 viewRules->selectionModel()->clear();
220}
221
222void QgsRuleBasedLabelingWidget::copy()
223{
224 const QModelIndexList indexlist = viewRules->selectionModel()->selectedRows();
225
226 if ( indexlist.isEmpty() )
227 return;
228
229 QMimeData *mime = mModel->mimeData( indexlist );
230 QApplication::clipboard()->setMimeData( mime );
231}
232
233void QgsRuleBasedLabelingWidget::paste()
234{
235 const QMimeData *mime = QApplication::clipboard()->mimeData();
236 if ( !mime )
237 return;
238
239 QModelIndexList indexlist = viewRules->selectionModel()->selectedRows();
240 QModelIndex index;
241 if ( indexlist.isEmpty() )
242 index = mModel->index( mModel->rowCount(), 0 );
243 else
244 index = indexlist.first();
245 mModel->dropMimeData( mime, Qt::CopyAction, index.row(), index.column(), index.parent() );
246}
247
248QgsRuleBasedLabeling::Rule *QgsRuleBasedLabelingWidget::currentRule()
249{
250 QItemSelectionModel *sel = viewRules->selectionModel();
251 const QModelIndex idx = sel->currentIndex();
252 if ( !idx.isValid() )
253 return nullptr;
254 return mModel->ruleForIndex( idx );
255}
256
257#include "qgsvscrollarea.h"
258
260 : QDialog( parent )
261{
262#ifdef Q_OS_MAC
263 setWindowModality( Qt::WindowModal );
264#endif
265
266 QVBoxLayout *layout = new QVBoxLayout( this );
267 QgsVScrollArea *scrollArea = new QgsVScrollArea( this );
268 scrollArea->setFrameShape( QFrame::NoFrame );
269 layout->addWidget( scrollArea );
270
271 buttonBox = new QDialogButtonBox( QDialogButtonBox::Cancel | QDialogButtonBox::Help | QDialogButtonBox::Ok );
272 mPropsWidget = new QgsLabelingRulePropsWidget( rule, layer, this, mapCanvas );
273
274 scrollArea->setWidget( mPropsWidget );
275 layout->addWidget( buttonBox );
276 this->setWindowTitle( "Edit Rule" );
278
279 connect( buttonBox, &QDialogButtonBox::accepted, this, &QgsLabelingRulePropsDialog::accept );
280 connect( buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject );
281 connect( buttonBox, &QDialogButtonBox::helpRequested, this, &QgsLabelingRulePropsDialog::showHelp );
282}
283
285{
286 mPropsWidget->testFilter();
287}
288
290{
291 mPropsWidget->buildExpression();
292}
293
295{
296 mPropsWidget->apply();
297 QDialog::accept();
298}
299
300void QgsLabelingRulePropsDialog::showHelp()
301{
302 QgsHelp::openHelp( u"working_with_vector/vector_properties.html#rule-based-labeling"_s );
303}
304
306
308 : QAbstractItemModel( parent )
309 , mRootRule( rootRule )
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 ) {
718 if ( toggled )
719 editFilter->setText( u"ELSE"_s );
720 } );
721}
722
727
729{
731 mLabelingGui->setDockMode( dockMode );
732}
733
735{
736 if ( !mFilterRadio->isChecked() )
737 return;
738
739 QgsExpression filter( editFilter->text() );
740 if ( filter.hasParserError() )
741 {
742 QMessageBox::critical( this, tr( "Test Filter" ), tr( "Filter expression parsing error:\n" ) + filter.parserErrorString() );
743 return;
744 }
745
746 QgsExpressionContext context( createExpressionContext( mMapCanvas, mLayer ) );
747
748 if ( !filter.prepare( &context ) )
749 {
750 QMessageBox::critical( this, tr( "Test Filter" ), filter.evalErrorString() );
751 return;
752 }
753
754 QApplication::setOverrideCursor( Qt::WaitCursor );
755
756 QgsFeatureIterator fit = mLayer->getFeatures();
757
758 int count = 0;
759 QgsFeature f;
760 while ( fit.nextFeature( f ) )
761 {
762 context.setFeature( f );
763
764 const QVariant value = filter.evaluate( &context );
765 if ( value.toInt() != 0 )
766 count++;
767 if ( filter.hasEvalError() )
768 break;
769 }
770
771 QApplication::restoreOverrideCursor();
772
773 QMessageBox::information( this, tr( "Test Filter" ), tr( "Filter returned %n feature(s)", "number of filtered features", count ) );
774}
775
776
778{
779 const QgsExpressionContext context( createExpressionContext( mMapCanvas, mLayer ) );
780
781 QgsExpressionBuilderDialog dlg( mLayer, editFilter->text(), this, u"generic"_s, context );
782
783 if ( dlg.exec() )
784 editFilter->setText( dlg.expressionText() );
785}
786
788{
789 const QString filter = mElseRadio->isChecked() ? u"ELSE"_s : editFilter->text().trimmed();
790 mRule->setFilterExpression( filter );
791 mRule->setDescription( editDescription->text() );
792 mRule->setMinimumScale( groupScale->isChecked() ? mScaleRangeWidget->minimumScale() : 0 );
793 mRule->setMaximumScale( groupScale->isChecked() ? mScaleRangeWidget->maximumScale() : 0 );
794 mRule->setSettings( groupSettings->isChecked() ? new QgsPalLayerSettings( mLabelingGui->layerSettings() ) : nullptr );
795}
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