37 #include <QProgressDialog>
38 #include <QTreeWidgetItem>
39 #include <QVBoxLayout>
40 #include <QMessageBox>
43 #ifdef ENABLE_MODELTEST
44 #include "modeltest.h"
50 return new QgsRuleBasedRendererWidget( layer, style, renderer );
55 , mContextMenu( new QMenu( this ) )
71 mRenderer = std::make_unique< QgsRuleBasedRenderer >( symbol );
77 this->layout()->setContentsMargins( 0, 0, 0, 0 );
80 #ifdef ENABLE_MODELTEST
81 new ModelTest(
mModel,
this );
83 viewRules->setModel(
mModel );
86 mDeleteAction->setShortcut( QKeySequence( QKeySequence::Delete ) );
92 mRefineMenu =
new QMenu( tr(
"Refine Current Rule" ), btnRefineRule );
105 connect( viewRules, &QWidget::customContextMenuRequested,
this, &QgsRuleBasedRendererWidget::showContextMenu );
139 qDeleteAll( mCopyBuffer );
144 return mRenderer.get();
153 mDeleteAction->setShortcut( QKeySequence() );
167 QModelIndex currentIndex = viewRules->selectionModel()->currentIndex();
168 mModel->insertRule( currentIndex.parent(), currentIndex.row() + 1, newrule );
169 QModelIndex newindex = mModel->index( currentIndex.row() + 1, 0, currentIndex.parent() );
170 viewRules->selectionModel()->setCurrentIndex( newindex, QItemSelectionModel::ClearAndSelect );
175 int rows = mModel->rowCount();
176 mModel->insertRule( QModelIndex(), rows, newrule );
177 QModelIndex newindex = mModel->index( rows, 0 );
178 viewRules->selectionModel()->setCurrentIndex( newindex, QItemSelectionModel::ClearAndSelect );
185 QItemSelectionModel *sel = viewRules->selectionModel();
186 QModelIndex idx = sel->currentIndex();
187 if ( !idx.isValid() )
189 return mModel->ruleForIndex( idx );
194 editRule( viewRules->selectionModel()->currentIndex() );
199 if ( !index.isValid() )
207 QgsRendererRulePropsWidget *widget =
new QgsRendererRulePropsWidget( rule, mLayer, mStyle,
this, mContext );
208 widget->setPanelTitle( tr(
"Edit Rule" ) );
218 mModel->updateRule( index.parent(), index.row() );
219 mModel->clearFeatureCounts();
220 emit widgetChanged();
226 QItemSelection sel = viewRules->selectionModel()->selection();
227 QgsDebugMsg( QStringLiteral(
"REMOVE RULES!!! ranges: %1" ).arg( sel.count() ) );
228 const auto constSel = sel;
229 for (
const QItemSelectionRange &range : constSel )
231 QgsDebugMsg( QStringLiteral(
"RANGE: r %1 - %2" ).arg( range.top() ).arg( range.bottom() ) );
232 if ( range.isValid() )
233 mModel->removeRows( range.top(), range.bottom() - range.top() + 1, range.parent() );
236 viewRules->selectionModel()->clear();
237 mModel->clearFeatureCounts();
243 btnEditRule->setEnabled( current.isValid() );
252 #include <QDialogButtonBox>
253 #include <QInputDialog>
254 #include <QClipboard>
258 QModelIndexList indexlist = viewRules->selectionModel()->selectedRows();
260 if ( indexlist.isEmpty() )
265 refineRuleCategoriesGui();
266 else if ( type == 1 )
267 refineRuleRangesGui();
269 refineRuleScalesGui( indexlist );
274 const auto constIndexlist = indexlist;
275 for (
const QModelIndex &index : constIndexlist )
276 viewRules->expand( index );
296 QgsCategorizedSymbolRendererWidget *w =
new QgsCategorizedSymbolRendererWidget( mLayer, mStyle,
nullptr );
297 w->setPanelTitle( tr(
"Add Categories to Rules" ) );
299 w->setContext( mContext );
305 QgsGraduatedSymbolRendererWidget *w =
new QgsGraduatedSymbolRendererWidget( mLayer, mStyle,
nullptr );
306 w->setPanelTitle( tr(
"Add Ranges to Rules" ) );
308 w->setContext( mContext );
314 for (
const QModelIndex &index : indexList )
319 if ( !initialRule->
symbol() )
321 QMessageBox::warning(
this, tr(
"Scale Refinement" ), tr(
"Parent rule %1 must have a symbol for this operation." ).arg( initialRule->
label() ) );
326 QString txt = QInputDialog::getText(
this,
327 tr(
"Scale Refinement" ),
328 tr(
"Please enter scale denominators at which will split the rule, separate them by commas (e.g. 1000,5000):" ) );
334 const auto constSplit = txt.split(
',' );
335 for (
const QString &item : constSplit )
337 int scale = item.toInt( &ok );
339 scales.append( scale );
341 QMessageBox::information(
this, tr(
"Scale Refinement" ), tr(
"\"%1\" is not valid scale denominator, ignoring it." ).arg( item ) );
344 for (
const QModelIndex &index : indexList )
347 mModel->willAddRules( index, scales.count() + 1 );
350 mModel->finishedAddingRules();
363 mRenderer->setLegendSymbolItem( legendSymbol.ruleKey(), sym->
clone() );
367 emit widgetChanged();
372 QList<QgsSymbol *> symbolList;
379 QItemSelection sel = viewRules->selectionModel()->selection();
380 const auto constSel = sel;
381 for (
const QItemSelectionRange &range : constSel )
383 QModelIndex parent = range.parent();
386 for (
int row = range.top(); row <= range.bottom(); row++ )
388 symbolList.append( children.at( row )->symbol() );
398 QItemSelection sel = viewRules->selectionModel()->selection();
399 const auto constSel = sel;
400 for (
const QItemSelectionRange &range : constSel )
402 QModelIndex parent = range.parent();
405 for (
int row = range.top(); row <= range.bottom(); row++ )
407 rl.append( children.at( row )->clone() );
422 emit widgetChanged();
432 if ( event->key() == Qt::Key_C && event->modifiers() == Qt::ControlModifier )
434 qDeleteAll( mCopyBuffer );
436 mCopyBuffer = selectedRules();
438 else if ( event->key() == Qt::Key_V && event->modifiers() == Qt::ControlModifier )
440 QgsRuleBasedRenderer::RuleList::const_iterator rIt = mCopyBuffer.constBegin();
441 for ( ; rIt != mCopyBuffer.constEnd(); ++rIt )
443 int rows = mModel->rowCount();
444 mModel->insertRule( QModelIndex(), rows, ( *rIt )->clone() );
483 QString path =
"/Windows/RuleBasedTree/sectionWidth/" + QString::number( section );
490 QString path = QStringLiteral(
"/Windows/RuleBasedTree/sectionWidth/" );
491 QHeaderView *head = viewRules->header();
492 head->resizeSection( 0, settings.
value( path + QString::number( 0 ), 150 ).toInt() );
493 head->resizeSection( 1, settings.
value( path + QString::number( 1 ), 150 ).toInt() );
494 head->resizeSection( 2, settings.
value( path + QString::number( 2 ), 80 ).toInt() );
495 head->resizeSection( 3, settings.
value( path + QString::number( 3 ), 80 ).toInt() );
496 head->resizeSection( 4, settings.
value( path + QString::number( 4 ), 50 ).toInt() );
497 head->resizeSection( 5, settings.
value( path + QString::number( 5 ), 50 ).toInt() );
502 QModelIndexList indexlist = viewRules->selectionModel()->selectedRows();
505 if ( indexlist.isEmpty() )
508 QMimeData *mime = mModel->mimeData( indexlist );
509 QApplication::clipboard()->setMimeData( mime );
514 const QMimeData *mime = QApplication::clipboard()->mimeData();
515 QModelIndexList indexlist = viewRules->selectionModel()->selectedRows();
517 if ( indexlist.isEmpty() )
518 index = mModel->index( mModel->rowCount(), 0 );
520 index = indexlist.first();
521 mModel->dropMimeData( mime, Qt::CopyAction, index.row(), index.column(), index.parent() );
530 const QModelIndexList indexList = viewRules->selectionModel()->selectedRows();
531 for (
const QModelIndex &index : indexList )
535 if ( !rule->symbol() || rule->symbol()->type() != tempSymbol->type() )
538 mModel->setSymbol( index, tempSymbol->clone() );
541 emit widgetChanged();
544 void QgsRuleBasedRendererWidget::refineRuleCategoriesAccepted(
QgsPanelWidget *panel )
546 QgsCategorizedSymbolRendererWidget *w = qobject_cast<QgsCategorizedSymbolRendererWidget *>( panel );
550 QModelIndexList indexList = viewRules->selectionModel()->selectedRows();
551 const auto constIndexList = indexList;
552 for (
const QModelIndex &index : constIndexList )
555 mModel->willAddRules( index, r->
categories().count() );
558 mModel->finishedAddingRules();
561 void QgsRuleBasedRendererWidget::refineRuleRangesAccepted(
QgsPanelWidget *panel )
563 QgsGraduatedSymbolRendererWidget *w = qobject_cast<QgsGraduatedSymbolRendererWidget *>( panel );
566 QModelIndexList indexList = viewRules->selectionModel()->selectedRows();
567 const auto constIndexList = indexList;
568 for (
const QModelIndex &index : constIndexList )
571 mModel->willAddRules( index, r->
ranges().count() );
574 mModel->finishedAddingRules();
577 void QgsRuleBasedRendererWidget::ruleWidgetPanelAccepted(
QgsPanelWidget *panel )
579 QgsRendererRulePropsWidget *widget = qobject_cast<QgsRendererRulePropsWidget *>( panel );
586 QModelIndex index = viewRules->selectionModel()->currentIndex();
587 mModel->updateRule( index.parent(), index.row() );
588 mModel->clearFeatureCounts();
591 void QgsRuleBasedRendererWidget::liveUpdateRuleFromPanel()
593 ruleWidgetPanelAccepted( qobject_cast<QgsPanelWidget *>( sender() ) );
596 void QgsRuleBasedRendererWidget::showContextMenu( QPoint )
598 mContextMenu->clear();
599 mContextMenu->addAction( mCopyAction );
600 mContextMenu->addAction( mPasteAction );
602 const QList< QAction * > actions = contextMenu->actions();
603 for ( QAction *act : actions )
605 mContextMenu->addAction( act );
608 mContextMenu->addMenu( mRefineMenu );
610 mContextMenu->exec( QCursor::pos() );
616 if ( !mLayer || !mRenderer || !mRenderer->rootRule() )
620 QHash<QgsRuleBasedRenderer::Rule *, QgsRuleBasedRendererCount> countMap;
624 const auto constRuleList = ruleList;
627 countMap[rule].count = 0;
628 countMap[rule].duplicateCount = 0;
637 const auto constAdditionalExpressionContextScopes = mContext.additionalExpressionContextScopes();
645 mRenderer->startRender( renderContext, mLayer->fields() );
652 long long nFeatures = mLayer->featureCount();
653 QProgressDialog p( tr(
"Calculating feature count." ), tr(
"Abort" ), 0, 100 );
654 p.setWindowModality( Qt::WindowModal );
655 long long featuresCounted = 0;
663 const auto constFeatureRuleList = featureRuleList;
666 countMap[rule].count++;
667 if ( featureRuleList.size() > 1 )
669 countMap[rule].duplicateCount++;
671 const auto constFeatureRuleList = featureRuleList;
674 if ( duplicateRule == rule )
continue;
675 countMap[rule].duplicateCountMap[duplicateRule] += 1;
679 if ( featuresCounted % 50 == 0 )
681 if ( featuresCounted > nFeatures )
685 p.setValue(
static_cast< double >( featuresCounted ) / nFeatures * 100.0 );
686 if ( p.wasCanceled() )
692 p.setValue( nFeatures );
694 mRenderer->stopRender( renderContext );
697 const auto constKeys = countMap.keys();
700 QgsDebugMsg( QStringLiteral(
"rule: %1 count %2" ).arg( rule->label() ).arg( countMap[rule].count ) );
704 mModel->setFeatureCounts( countMap );
709 bool enabled = !viewRules->selectionModel()->selectedIndexes().isEmpty();
710 btnRefineRule->setEnabled( enabled );
711 btnRemoveRule->setEnabled( enabled );
720 , mContext( context )
723 layout()->setContentsMargins( 0, 0, 0, 0 );
735 groupScale->setChecked(
true );
743 groupSymbol->setChecked(
true );
748 groupSymbol->setChecked(
false );
757 QVBoxLayout *l =
new QVBoxLayout;
759 groupSymbol->setLayout( l );
769 connect( mFilterRadio, &QRadioButton::toggled,
this, [ = ](
bool toggled ) { filterFrame->setEnabled( toggled ) ; } );
770 connect( mElseRadio, &QRadioButton::toggled,
this, [ = ](
bool toggled ) {
if ( toggled ) editFilter->setText( QStringLiteral(
"ELSE" ) );} );
780 setWindowModality( Qt::WindowModal );
783 QVBoxLayout *layout =
new QVBoxLayout(
this );
785 scrollArea->setFrameShape( QFrame::NoFrame );
786 layout->addWidget( scrollArea );
788 buttonBox =
new QDialogButtonBox( QDialogButtonBox::Cancel | QDialogButtonBox::Help | QDialogButtonBox::Ok );
789 mPropsWidget =
new QgsRendererRulePropsWidget(
rule, layer, style,
this, context );
791 scrollArea->setWidget( mPropsWidget );
792 layout->addWidget( buttonBox );
793 this->setWindowTitle(
"Edit Rule" );
797 connect( buttonBox, &QDialogButtonBox::rejected,
this, &QDialog::reject );
798 connect( buttonBox, &QDialogButtonBox::helpRequested,
this, &QgsRendererRulePropsDialog::showHelp );
803 mPropsWidget->testFilter();
808 mPropsWidget->buildExpression();
813 mPropsWidget->apply();
817 void QgsRendererRulePropsDialog::showHelp()
819 QgsHelp::openHelp( QStringLiteral(
"working_with_vector/vector_properties.html#rule-based-rendering" ) );
828 const auto constAdditionalExpressionContextScopes = mContext.additionalExpressionContextScopes();
842 if ( !mFilterRadio->isChecked() )
848 QMessageBox::critical(
this, tr(
"Test Filter" ), tr(
"Filter expression parsing error:\n" ) + filter.
parserErrorString() );
855 const auto constAdditionalExpressionContextScopes = mContext.additionalExpressionContextScopes();
861 if ( !filter.
prepare( &context ) )
863 QMessageBox::critical(
this, tr(
"Test Filter" ), filter.
evalErrorString() );
867 QApplication::setOverrideCursor( Qt::WaitCursor );
883 QApplication::restoreOverrideCursor();
885 QMessageBox::information(
this, tr(
"Test Filter" ), tr(
"Filter returned %n feature(s)",
"number of filtered features", count ) );
890 QString filter = mElseRadio->isChecked() ? QStringLiteral(
"ELSE" ) : editFilter->text();
891 mRule->setFilterExpression( filter );
892 mRule->setLabel( editLabel->text() );
893 mRule->setDescription( editDescription->text() );
895 mRule->setMinimumScale( groupScale->isChecked() ? mScaleRangeWidget->minimumScale() : 0 );
896 mRule->setMaximumScale( groupScale->isChecked() ? mScaleRangeWidget->maximumScale() : 0 );
897 mRule->setSymbol( groupSymbol->isChecked() ? mSymbol->clone() :
nullptr );
903 mSymbolSelector->setDockMode( dockMode );
918 : QAbstractItemModel( parent )
925 if ( !
index.isValid() )
926 return Qt::ItemIsDropEnabled;
929 Qt::ItemFlag drop = (
index.column() == 0 ? Qt::ItemIsDropEnabled : Qt::NoItemFlags );
931 Qt::ItemFlag checkable = (
index.column() == 0 ? Qt::ItemIsUserCheckable : Qt::NoItemFlags );
933 return Qt::ItemIsEnabled | Qt::ItemIsSelectable |
934 Qt::ItemIsEditable | checkable |
935 Qt::ItemIsDragEnabled | drop;
940 if ( !
index.isValid() )
945 if ( role == Qt::DisplayRole || role == Qt::ToolTipRole )
947 switch (
index.column() )
950 return rule->
label();
973 if ( role == Qt::DisplayRole )
981 QString tip = QStringLiteral(
"<p style='margin:0px;'><ul>" );
983 for (
auto it = duplicateMap.constBegin(); it != duplicateMap.constEnd(); ++it )
985 QString label = it.key()->label().replace(
'&', QLatin1String(
"&" ) ).replace(
'>', QLatin1String(
">" ) ).replace(
'<', QLatin1String(
"<" ) );
986 tip += tr(
"<li><nobr>%1 features also in rule %2</nobr></li>" ).arg( it.value() ).arg( label );
988 tip += QLatin1String(
"</ul>" );
1002 else if ( role == Qt::DecorationRole &&
index.column() == 0 && rule->
symbol() )
1007 else if ( role == Qt::TextAlignmentRole )
1009 return (
index.column() == 2 ||
index.column() == 3 ) ?
static_cast<Qt::Alignment::Int
>( Qt::AlignRight ) :
static_cast<Qt::Alignment::Int
>( Qt::AlignLeft );
1011 else if ( role == Qt::FontRole &&
index.column() == 1 )
1016 italicFont.setItalic(
true );
1021 else if ( role == Qt::EditRole )
1023 switch (
index.column() )
1026 return rule->
label();
1037 else if ( role == Qt::CheckStateRole )
1039 if (
index.column() != 0 )
1041 return rule->
active() ? Qt::Checked : Qt::Unchecked;
1049 if ( orientation == Qt::Horizontal && role == Qt::DisplayRole && section >= 0 && section < 7 )
1052 lst << tr(
"Label" ) << tr(
"Rule" ) << tr(
"Min. Scale" ) << tr(
"Max. Scale" ) << tr(
"Count" ) << tr(
"Duplicate Count" );
1053 return lst[section];
1055 else if ( orientation == Qt::Horizontal && role == Qt::ToolTipRole )
1059 return tr(
"Number of features in this rule." );
1061 else if ( section == 5 )
1063 return tr(
"Number of features in this rule which are also present in other rule(s)." );
1072 if (
parent.column() > 0 )
1077 return parentRule->
children().count();
1087 if ( hasIndex( row, column,
parent ) )
1091 return createIndex( row, column, childRule );
1093 return QModelIndex();
1098 if ( !
index.isValid() )
1099 return QModelIndex();
1105 return QModelIndex();
1108 int row = parentRule->
parent()->
children().indexOf( parentRule );
1110 return createIndex( row, 0, parentRule );
1115 if ( !
index.isValid() )
1120 if ( role == Qt::CheckStateRole )
1122 rule->
setActive( value.toInt() == Qt::Checked );
1127 if ( role != Qt::EditRole )
1130 switch (
index.column() )
1133 rule->
setLabel( value.toString() );
1154 return Qt::MoveAction;
1160 types << QStringLiteral(
"application/vnd.text.list" );
1166 QMimeData *
mimeData =
new QMimeData();
1167 QByteArray encodedData;
1169 QDataStream stream( &encodedData, QIODevice::WriteOnly );
1171 const auto constIndexes = indexes;
1172 for (
const QModelIndex &
index : constIndexes )
1175 if ( !
index.isValid() ||
index.column() != 0 )
1184 QDomElement rootElem = doc.createElement( QStringLiteral(
"rule_mime" ) );
1185 rootElem.setAttribute( QStringLiteral(
"type" ), QStringLiteral(
"renderer" ) );
1186 QDomElement rulesElem = rule->
save( doc, symbols );
1187 rootElem.appendChild( rulesElem );
1189 rootElem.appendChild( symbolsElem );
1190 doc.appendChild( rootElem );
1194 stream << doc.toString( -1 );
1197 mimeData->setData( QStringLiteral(
"application/vnd.text.list" ), encodedData );
1206 if ( ruleElem.hasAttribute( QStringLiteral(
"description" ) ) )
1207 ruleElem.setAttribute( QStringLiteral(
"label" ), ruleElem.attribute( QStringLiteral(
"description" ) ) );
1210 QDomElement childRuleElem = ruleElem.firstChildElement( QStringLiteral(
"rule" ) );
1211 while ( !childRuleElem.isNull() )
1214 childRuleElem = childRuleElem.nextSiblingElement( QStringLiteral(
"rule" ) );
1220 Qt::DropAction action,
int row,
int column,
const QModelIndex &parent )
1224 if ( action == Qt::IgnoreAction )
1227 if ( !
data->hasFormat( QStringLiteral(
"application/vnd.text.list" ) ) )
1230 if (
parent.column() > 0 )
1233 QByteArray encodedData =
data->data( QStringLiteral(
"application/vnd.text.list" ) );
1234 QDataStream stream( &encodedData, QIODevice::ReadOnly );
1243 while ( !stream.atEnd() )
1249 if ( !doc.setContent( text ) )
1251 QDomElement rootElem = doc.documentElement();
1252 if ( rootElem.tagName() != QLatin1String(
"rule_mime" ) )
1254 if ( rootElem.attribute( QStringLiteral(
"type" ) ) == QLatin1String(
"labeling" ) )
1255 rootElem.appendChild( doc.createElement( QStringLiteral(
"symbols" ) ) );
1256 QDomElement symbolsElem = rootElem.firstChildElement( QStringLiteral(
"symbols" ) );
1257 if ( symbolsElem.isNull() )
1260 QDomElement ruleElem = rootElem.firstChildElement( QStringLiteral(
"rule" ) );
1261 if ( rootElem.attribute( QStringLiteral(
"type" ) ) == QLatin1String(
"labeling" ) )
1274 if (
index.isValid() )
1283 if ( row < 0 || row >= parentRule->
children().count() )
1286 QgsDebugMsg( QStringLiteral(
"Called: row %1 count %2 parent ~~%3~~" ).arg( row ).arg( count ).arg( parentRule->
dump() ) );
1288 beginRemoveRows(
parent, row, row + count - 1 );
1290 for (
int i = 0; i < count; i++ )
1292 if ( row < parentRule->children().count() )
1300 QgsDebugMsg( QStringLiteral(
"trying to remove invalid index - this should not happen!" ) );
1312 beginInsertRows(
parent, before, before );
1314 QgsDebugMsg( QStringLiteral(
"insert before %1 rule: %2" ).arg( before ).arg( newrule->
dump() ) );
1330 emit dataChanged(
index( 0, 0, idx ),
1333 for (
int i = 0; i <
rowCount( idx ); i++ )
1342 if ( !
index.isValid() )
1363 beginInsertRows(
parent, row, row + count - 1 );