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