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 ) )
75 this->layout()->setContentsMargins( 0, 0, 0, 0 );
78 #ifdef ENABLE_MODELTEST
79 new ModelTest(
mModel,
this );
81 viewRules->setModel(
mModel );
84 mDeleteAction->setShortcut( QKeySequence( QKeySequence::Delete ) );
90 mRefineMenu =
new QMenu( tr(
"Refine Current Rule" ), btnRefineRule );
103 connect( viewRules, &QWidget::customContextMenuRequested,
this, &QgsRuleBasedRendererWidget::showContextMenu );
137 qDeleteAll( mCopyBuffer );
152 mDeleteAction->setShortcut( QKeySequence() );
166 QModelIndex currentIndex = viewRules->selectionModel()->currentIndex();
167 mModel->insertRule( currentIndex.parent(), currentIndex.row() + 1, newrule );
168 QModelIndex newindex = mModel->index( currentIndex.row() + 1, 0, currentIndex.parent() );
169 viewRules->selectionModel()->setCurrentIndex( newindex, QItemSelectionModel::ClearAndSelect );
174 int rows = mModel->rowCount();
175 mModel->insertRule( QModelIndex(), rows, newrule );
176 QModelIndex newindex = mModel->index( rows, 0 );
177 viewRules->selectionModel()->setCurrentIndex( newindex, QItemSelectionModel::ClearAndSelect );
184 QItemSelectionModel *sel = viewRules->selectionModel();
185 QModelIndex idx = sel->currentIndex();
186 if ( !idx.isValid() )
188 return mModel->ruleForIndex( idx );
193 editRule( viewRules->selectionModel()->currentIndex() );
198 if ( !index.isValid() )
206 QgsRendererRulePropsWidget *widget =
new QgsRendererRulePropsWidget( rule, mLayer, mStyle,
this, mContext );
207 widget->setPanelTitle( tr(
"Edit Rule" ) );
217 mModel->updateRule( index.parent(), index.row() );
218 mModel->clearFeatureCounts();
219 emit widgetChanged();
225 QItemSelection sel = viewRules->selectionModel()->selection();
226 QgsDebugMsg( QStringLiteral(
"REMOVE RULES!!! ranges: %1" ).arg( sel.count() ) );
227 const auto constSel = sel;
228 for (
const QItemSelectionRange &range : constSel )
230 QgsDebugMsg( QStringLiteral(
"RANGE: r %1 - %2" ).arg( range.top() ).arg( range.bottom() ) );
231 if ( range.isValid() )
232 mModel->removeRows( range.top(), range.bottom() - range.top() + 1, range.parent() );
235 viewRules->selectionModel()->clear();
236 mModel->clearFeatureCounts();
242 btnEditRule->setEnabled( current.isValid() );
251 #include <QDialogButtonBox>
252 #include <QInputDialog>
253 #include <QClipboard>
257 QModelIndexList indexlist = viewRules->selectionModel()->selectedRows();
259 if ( indexlist.isEmpty() )
264 refineRuleCategoriesGui();
265 else if ( type == 1 )
266 refineRuleRangesGui();
268 refineRuleScalesGui( indexlist );
273 const auto constIndexlist = indexlist;
274 for (
const QModelIndex &index : constIndexlist )
275 viewRules->expand( index );
295 QgsCategorizedSymbolRendererWidget *w =
new QgsCategorizedSymbolRendererWidget( mLayer, mStyle,
nullptr );
296 w->setPanelTitle( tr(
"Add Categories to Rules" ) );
298 w->setContext( mContext );
304 QgsGraduatedSymbolRendererWidget *w =
new QgsGraduatedSymbolRendererWidget( mLayer, mStyle,
nullptr );
305 w->setPanelTitle( tr(
"Add Ranges to Rules" ) );
307 w->setContext( mContext );
313 for (
const QModelIndex &index : indexList )
318 if ( !initialRule->
symbol() )
320 QMessageBox::warning(
this, tr(
"Scale Refinement" ), tr(
"Parent rule %1 must have a symbol for this operation." ).arg( initialRule->
label() ) );
325 QString txt = QInputDialog::getText(
this,
326 tr(
"Scale Refinement" ),
327 tr(
"Please enter scale denominators at which will split the rule, separate them by commas (e.g. 1000,5000):" ) );
333 const auto constSplit = txt.split(
',' );
334 for (
const QString &item : constSplit )
336 int scale = item.toInt( &ok );
338 scales.append( scale );
340 QMessageBox::information(
this, tr(
"Scale Refinement" ), tr(
"\"%1\" is not valid scale denominator, ignoring it." ).arg( item ) );
343 for (
const QModelIndex &index : indexList )
346 mModel->willAddRules( index, scales.count() + 1 );
349 mModel->finishedAddingRules();
354 QList<QgsSymbol *> symbolList;
361 QItemSelection sel = viewRules->selectionModel()->selection();
362 const auto constSel = sel;
363 for (
const QItemSelectionRange &range : constSel )
365 QModelIndex parent = range.parent();
368 for (
int row = range.top(); row <= range.bottom(); row++ )
370 symbolList.append( children.at( row )->symbol() );
380 QItemSelection sel = viewRules->selectionModel()->selection();
381 const auto constSel = sel;
382 for (
const QItemSelectionRange &range : constSel )
384 QModelIndex parent = range.parent();
387 for (
int row = range.top(); row <= range.bottom(); row++ )
389 rl.append( children.at( row )->clone() );
404 emit widgetChanged();
414 if ( event->key() == Qt::Key_C && event->modifiers() == Qt::ControlModifier )
416 qDeleteAll( mCopyBuffer );
418 mCopyBuffer = selectedRules();
420 else if ( event->key() == Qt::Key_V && event->modifiers() == Qt::ControlModifier )
422 QgsRuleBasedRenderer::RuleList::const_iterator rIt = mCopyBuffer.constBegin();
423 for ( ; rIt != mCopyBuffer.constEnd(); ++rIt )
425 int rows = mModel->rowCount();
426 mModel->insertRule( QModelIndex(), rows, ( *rIt )->clone() );
451 emit widgetChanged();
462 QString path =
"/Windows/RuleBasedTree/sectionWidth/" + QString::number( section );
469 QString path = QStringLiteral(
"/Windows/RuleBasedTree/sectionWidth/" );
470 QHeaderView *head = viewRules->header();
471 head->resizeSection( 0, settings.
value( path + QString::number( 0 ), 150 ).toInt() );
472 head->resizeSection( 1, settings.
value( path + QString::number( 1 ), 150 ).toInt() );
473 head->resizeSection( 2, settings.
value( path + QString::number( 2 ), 80 ).toInt() );
474 head->resizeSection( 3, settings.
value( path + QString::number( 3 ), 80 ).toInt() );
475 head->resizeSection( 4, settings.
value( path + QString::number( 4 ), 50 ).toInt() );
476 head->resizeSection( 5, settings.
value( path + QString::number( 5 ), 50 ).toInt() );
481 QModelIndexList indexlist = viewRules->selectionModel()->selectedRows();
484 if ( indexlist.isEmpty() )
487 QMimeData *mime = mModel->mimeData( indexlist );
488 QApplication::clipboard()->setMimeData( mime );
493 const QMimeData *mime = QApplication::clipboard()->mimeData();
494 QModelIndexList indexlist = viewRules->selectionModel()->selectedRows();
496 if ( indexlist.isEmpty() )
497 index = mModel->index( mModel->rowCount(), 0 );
499 index = indexlist.first();
500 mModel->dropMimeData( mime, Qt::CopyAction, index.row(), index.column(), index.parent() );
509 const QModelIndexList indexList = viewRules->selectionModel()->selectedRows();
510 for (
const QModelIndex &index : indexList )
514 if ( !rule->symbol() || rule->symbol()->type() != tempSymbol->type() )
517 mModel->setSymbol( index, tempSymbol->clone() );
520 emit widgetChanged();
523 void QgsRuleBasedRendererWidget::refineRuleCategoriesAccepted(
QgsPanelWidget *panel )
525 QgsCategorizedSymbolRendererWidget *w = qobject_cast<QgsCategorizedSymbolRendererWidget *>( panel );
529 QModelIndexList indexList = viewRules->selectionModel()->selectedRows();
530 const auto constIndexList = indexList;
531 for (
const QModelIndex &index : constIndexList )
534 mModel->willAddRules( index, r->
categories().count() );
537 mModel->finishedAddingRules();
540 void QgsRuleBasedRendererWidget::refineRuleRangesAccepted(
QgsPanelWidget *panel )
542 QgsGraduatedSymbolRendererWidget *w = qobject_cast<QgsGraduatedSymbolRendererWidget *>( panel );
545 QModelIndexList indexList = viewRules->selectionModel()->selectedRows();
546 const auto constIndexList = indexList;
547 for (
const QModelIndex &index : constIndexList )
550 mModel->willAddRules( index, r->
ranges().count() );
553 mModel->finishedAddingRules();
556 void QgsRuleBasedRendererWidget::ruleWidgetPanelAccepted(
QgsPanelWidget *panel )
558 QgsRendererRulePropsWidget *widget = qobject_cast<QgsRendererRulePropsWidget *>( panel );
565 QModelIndex index = viewRules->selectionModel()->currentIndex();
566 mModel->updateRule( index.parent(), index.row() );
567 mModel->clearFeatureCounts();
570 void QgsRuleBasedRendererWidget::liveUpdateRuleFromPanel()
572 ruleWidgetPanelAccepted( qobject_cast<QgsPanelWidget *>( sender() ) );
575 void QgsRuleBasedRendererWidget::showContextMenu( QPoint )
577 mContextMenu->clear();
578 mContextMenu->addAction( mCopyAction );
579 mContextMenu->addAction( mPasteAction );
581 const QList< QAction * > actions = contextMenu->actions();
582 for ( QAction *act : actions )
584 mContextMenu->addAction( act );
587 mContextMenu->addMenu( mRefineMenu );
589 mContextMenu->exec( QCursor::pos() );
595 if ( !mLayer || !mRenderer || !mRenderer->rootRule() )
599 QHash<QgsRuleBasedRenderer::Rule *, QgsRuleBasedRendererCount> countMap;
603 const auto constRuleList = ruleList;
606 countMap[rule].count = 0;
607 countMap[rule].duplicateCount = 0;
616 const auto constAdditionalExpressionContextScopes = mContext.additionalExpressionContextScopes();
624 mRenderer->startRender( renderContext, mLayer->fields() );
631 int nFeatures = mLayer->featureCount();
632 QProgressDialog p( tr(
"Calculating feature count." ), tr(
"Abort" ), 0, nFeatures );
633 p.setWindowModality( Qt::WindowModal );
634 int featuresCounted = 0;
642 const auto constFeatureRuleList = featureRuleList;
645 countMap[rule].count++;
646 if ( featureRuleList.size() > 1 )
648 countMap[rule].duplicateCount++;
650 const auto constFeatureRuleList = featureRuleList;
653 if ( duplicateRule == rule )
continue;
654 countMap[rule].duplicateCountMap[duplicateRule] += 1;
658 if ( featuresCounted % 50 == 0 )
660 if ( featuresCounted > nFeatures )
664 p.setValue( featuresCounted );
665 if ( p.wasCanceled() )
671 p.setValue( nFeatures );
673 mRenderer->stopRender( renderContext );
676 const auto constKeys = countMap.keys();
679 QgsDebugMsg( QStringLiteral(
"rule: %1 count %2" ).arg( rule->label() ).arg( countMap[rule].count ) );
683 mModel->setFeatureCounts( countMap );
688 bool enabled = !viewRules->selectionModel()->selectedIndexes().isEmpty();
689 btnRefineRule->setEnabled( enabled );
690 btnRemoveRule->setEnabled( enabled );
699 , mContext( context )
702 layout()->setContentsMargins( 0, 0, 0, 0 );
714 groupScale->setChecked(
true );
722 groupSymbol->setChecked(
true );
727 groupSymbol->setChecked(
false );
736 QVBoxLayout *l =
new QVBoxLayout;
738 groupSymbol->setLayout( l );
748 connect( mFilterRadio, &QRadioButton::toggled,
this, [ = ](
bool toggled ) { filterFrame->setEnabled( toggled ) ; } );
749 connect( mElseRadio, &QRadioButton::toggled,
this, [ = ](
bool toggled ) {
if ( toggled ) editFilter->setText( QStringLiteral(
"ELSE" ) );} );
759 setWindowModality( Qt::WindowModal );
762 QVBoxLayout *layout =
new QVBoxLayout(
this );
764 scrollArea->setFrameShape( QFrame::NoFrame );
765 layout->addWidget( scrollArea );
767 buttonBox =
new QDialogButtonBox( QDialogButtonBox::Cancel | QDialogButtonBox::Help | QDialogButtonBox::Ok );
768 mPropsWidget =
new QgsRendererRulePropsWidget(
rule, layer, style,
this, context );
770 scrollArea->setWidget( mPropsWidget );
771 layout->addWidget( buttonBox );
772 this->setWindowTitle(
"Edit Rule" );
776 connect( buttonBox, &QDialogButtonBox::rejected,
this, &QDialog::reject );
777 connect( buttonBox, &QDialogButtonBox::helpRequested,
this, &QgsRendererRulePropsDialog::showHelp );
782 mPropsWidget->testFilter();
787 mPropsWidget->buildExpression();
792 mPropsWidget->apply();
796 void QgsRendererRulePropsDialog::showHelp()
798 QgsHelp::openHelp( QStringLiteral(
"working_with_vector/vector_properties.html#rule-based-rendering" ) );
807 const auto constAdditionalExpressionContextScopes = mContext.additionalExpressionContextScopes();
821 if ( !mFilterRadio->isChecked() )
827 QMessageBox::critical(
this, tr(
"Test Filter" ), tr(
"Filter expression parsing error:\n" ) + filter.
parserErrorString() );
834 const auto constAdditionalExpressionContextScopes = mContext.additionalExpressionContextScopes();
840 if ( !filter.
prepare( &context ) )
842 QMessageBox::critical(
this, tr(
"Test Filter" ), filter.
evalErrorString() );
846 QApplication::setOverrideCursor( Qt::WaitCursor );
862 QApplication::restoreOverrideCursor();
864 QMessageBox::information(
this, tr(
"Test Filter" ), tr(
"Filter returned %n feature(s)",
"number of filtered features", count ) );
869 QString filter = mElseRadio->isChecked() ? QStringLiteral(
"ELSE" ) : editFilter->text();
870 mRule->setFilterExpression( filter );
871 mRule->setLabel( editLabel->text() );
872 mRule->setDescription( editDescription->text() );
874 mRule->setMinimumScale( groupScale->isChecked() ? mScaleRangeWidget->minimumScale() : 0 );
875 mRule->setMaximumScale( groupScale->isChecked() ? mScaleRangeWidget->maximumScale() : 0 );
876 mRule->setSymbol( groupSymbol->isChecked() ? mSymbol->clone() :
nullptr );
882 mSymbolSelector->setDockMode( dockMode );
897 : QAbstractItemModel( parent )
904 if ( !
index.isValid() )
905 return Qt::ItemIsDropEnabled;
908 Qt::ItemFlag drop = (
index.column() == 0 ? Qt::ItemIsDropEnabled : Qt::NoItemFlags );
910 Qt::ItemFlag checkable = (
index.column() == 0 ? Qt::ItemIsUserCheckable : Qt::NoItemFlags );
912 return Qt::ItemIsEnabled | Qt::ItemIsSelectable |
913 Qt::ItemIsEditable | checkable |
914 Qt::ItemIsDragEnabled | drop;
919 if ( !
index.isValid() )
924 if ( role == Qt::DisplayRole || role == Qt::ToolTipRole )
926 switch (
index.column() )
929 return rule->
label();
952 if ( role == Qt::DisplayRole )
960 QString tip = QStringLiteral(
"<p style='margin:0px;'><ul>" );
962 for (
auto it = duplicateMap.constBegin(); it != duplicateMap.constEnd(); ++it )
964 QString label = it.key()->label().replace(
'&', QLatin1String(
"&" ) ).replace(
'>', QLatin1String(
">" ) ).replace(
'<', QLatin1String(
"<" ) );
965 tip += tr(
"<li><nobr>%1 features also in rule %2</nobr></li>" ).arg( it.value() ).arg( label );
967 tip += QLatin1String(
"</ul>" );
981 else if ( role == Qt::DecorationRole &&
index.column() == 0 && rule->
symbol() )
986 else if ( role == Qt::TextAlignmentRole )
988 return (
index.column() == 2 ||
index.column() == 3 ) ? Qt::AlignRight : Qt::AlignLeft;
990 else if ( role == Qt::FontRole &&
index.column() == 1 )
995 italicFont.setItalic(
true );
1000 else if ( role == Qt::EditRole )
1002 switch (
index.column() )
1005 return rule->
label();
1016 else if ( role == Qt::CheckStateRole )
1018 if (
index.column() != 0 )
1020 return rule->
active() ? Qt::Checked : Qt::Unchecked;
1028 if ( orientation == Qt::Horizontal && role == Qt::DisplayRole && section >= 0 && section < 7 )
1031 lst << tr(
"Label" ) << tr(
"Rule" ) << tr(
"Min. Scale" ) << tr(
"Max. Scale" ) << tr(
"Count" ) << tr(
"Duplicate Count" );
1032 return lst[section];
1034 else if ( orientation == Qt::Horizontal && role == Qt::ToolTipRole )
1038 return tr(
"Number of features in this rule." );
1040 else if ( section == 5 )
1042 return tr(
"Number of features in this rule which are also present in other rule(s)." );
1051 if (
parent.column() > 0 )
1056 return parentRule->
children().count();
1066 if ( hasIndex( row, column,
parent ) )
1070 return createIndex( row, column, childRule );
1072 return QModelIndex();
1077 if ( !
index.isValid() )
1078 return QModelIndex();
1084 return QModelIndex();
1087 int row = parentRule->
parent()->
children().indexOf( parentRule );
1089 return createIndex( row, 0, parentRule );
1094 if ( !
index.isValid() )
1099 if ( role == Qt::CheckStateRole )
1101 rule->
setActive( value.toInt() == Qt::Checked );
1106 if ( role != Qt::EditRole )
1109 switch (
index.column() )
1112 rule->
setLabel( value.toString() );
1133 return Qt::MoveAction;
1139 types << QStringLiteral(
"application/vnd.text.list" );
1145 QMimeData *
mimeData =
new QMimeData();
1146 QByteArray encodedData;
1148 QDataStream stream( &encodedData, QIODevice::WriteOnly );
1150 const auto constIndexes = indexes;
1151 for (
const QModelIndex &
index : constIndexes )
1154 if ( !
index.isValid() ||
index.column() != 0 )
1163 QDomElement rootElem = doc.createElement( QStringLiteral(
"rule_mime" ) );
1164 rootElem.setAttribute( QStringLiteral(
"type" ), QStringLiteral(
"renderer" ) );
1165 QDomElement rulesElem = rule->
save( doc, symbols );
1166 rootElem.appendChild( rulesElem );
1168 rootElem.appendChild( symbolsElem );
1169 doc.appendChild( rootElem );
1173 stream << doc.toString( -1 );
1176 mimeData->setData( QStringLiteral(
"application/vnd.text.list" ), encodedData );
1185 if ( ruleElem.hasAttribute( QStringLiteral(
"description" ) ) )
1186 ruleElem.setAttribute( QStringLiteral(
"label" ), ruleElem.attribute( QStringLiteral(
"description" ) ) );
1189 QDomElement childRuleElem = ruleElem.firstChildElement( QStringLiteral(
"rule" ) );
1190 while ( !childRuleElem.isNull() )
1193 childRuleElem = childRuleElem.nextSiblingElement( QStringLiteral(
"rule" ) );
1199 Qt::DropAction action,
int row,
int column,
const QModelIndex &parent )
1203 if ( action == Qt::IgnoreAction )
1206 if ( !
data->hasFormat( QStringLiteral(
"application/vnd.text.list" ) ) )
1209 if (
parent.column() > 0 )
1212 QByteArray encodedData =
data->data( QStringLiteral(
"application/vnd.text.list" ) );
1213 QDataStream stream( &encodedData, QIODevice::ReadOnly );
1222 while ( !stream.atEnd() )
1228 if ( !doc.setContent( text ) )
1230 QDomElement rootElem = doc.documentElement();
1231 if ( rootElem.tagName() != QLatin1String(
"rule_mime" ) )
1233 if ( rootElem.attribute( QStringLiteral(
"type" ) ) == QLatin1String(
"labeling" ) )
1234 rootElem.appendChild( doc.createElement( QStringLiteral(
"symbols" ) ) );
1235 QDomElement symbolsElem = rootElem.firstChildElement( QStringLiteral(
"symbols" ) );
1236 if ( symbolsElem.isNull() )
1239 QDomElement ruleElem = rootElem.firstChildElement( QStringLiteral(
"rule" ) );
1240 if ( rootElem.attribute( QStringLiteral(
"type" ) ) == QLatin1String(
"labeling" ) )
1253 if (
index.isValid() )
1262 if ( row < 0 || row >= parentRule->
children().count() )
1265 QgsDebugMsg( QStringLiteral(
"Called: row %1 count %2 parent ~~%3~~" ).arg( row ).arg( count ).arg( parentRule->
dump() ) );
1267 beginRemoveRows(
parent, row, row + count - 1 );
1269 for (
int i = 0; i < count; i++ )
1271 if ( row < parentRule->children().count() )
1279 QgsDebugMsg( QStringLiteral(
"trying to remove invalid index - this should not happen!" ) );
1291 beginInsertRows(
parent, before, before );
1293 QgsDebugMsg( QStringLiteral(
"insert before %1 rule: %2" ).arg( before ).arg( newrule->
dump() ) );
1309 emit dataChanged(
index( 0, 0, idx ),
1312 for (
int i = 0; i <
rowCount( idx ); i++ )
1321 if ( !
index.isValid() )
1342 beginInsertRows(
parent, row, row + count - 1 );