48 #include <QMessageBox> 
   49 #include <QStandardItemModel> 
   50 #include <QStandardItem> 
   53 #include <QFileDialog> 
   58 QgsCategorizedSymbolRendererModel::QgsCategorizedSymbolRendererModel( QObject *parent ) : QAbstractItemModel( parent )
 
   59   , mMimeFormat( QStringLiteral( 
"application/x-qgscategorizedsymbolrendererv2model" ) )
 
   67     beginRemoveRows( QModelIndex(), 0, std::max< int >( mRenderer->categories().size() - 1, 0 ) );
 
   76       beginInsertRows( QModelIndex(), 0, renderer->
categories().size() - 1 );
 
   84   if ( !mRenderer ) 
return;
 
   85   const int idx = mRenderer->categories().size();
 
   86   beginInsertRows( QModelIndex(), idx, idx );
 
   87   mRenderer->addCategory( cat );
 
   98   const int row = index.row();
 
   99   if ( row >= catList.size() )
 
  103   return catList.at( row );
 
  107 Qt::ItemFlags QgsCategorizedSymbolRendererModel::flags( 
const QModelIndex &index )
 const 
  109   if ( !index.isValid() || !mRenderer )
 
  111     return Qt::ItemIsDropEnabled;
 
  114   Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsUserCheckable;
 
  115   if ( index.column() == 1 )
 
  118     if ( category.
value().type() != QVariant::List )
 
  120       flags |= Qt::ItemIsEditable;
 
  123   else if ( index.column() == 2 )
 
  125     flags |= Qt::ItemIsEditable;
 
  130 Qt::DropActions QgsCategorizedSymbolRendererModel::supportedDropActions()
 const 
  132   return Qt::MoveAction;
 
  135 QVariant QgsCategorizedSymbolRendererModel::data( 
const QModelIndex &index, 
int role )
 const 
  137   if ( !index.isValid() || !mRenderer )
 
  144     case Qt::CheckStateRole:
 
  146       if ( index.column() == 0 )
 
  148         return category.
renderState() ? Qt::Checked : Qt::Unchecked;
 
  153     case Qt::DisplayRole:
 
  154     case Qt::ToolTipRole:
 
  156       switch ( index.column() )
 
  160           if ( category.
value().type() == QVariant::List )
 
  163             const QVariantList list = category.
value().toList();
 
  164             res.reserve( list.size() );
 
  165             for ( 
const QVariant &v : list )
 
  168             if ( role == Qt::DisplayRole )
 
  169               return res.join( 
';' );
 
  171               return res.join( 
'\n' );
 
  173           else if ( !category.
value().isValid() || category.
value().isNull() || category.
value().toString().isEmpty() )
 
  175             return tr( 
"all other values" );
 
  183           return category.
label();
 
  190       if ( index.column() == 1 && category.
value().type() != QVariant::List && ( !category.
value().isValid() || category.
value().isNull() || category.
value().toString().isEmpty() ) )
 
  193         italicFont.setItalic( 
true );
 
  199     case Qt::DecorationRole:
 
  201       if ( index.column() == 0 && category.
symbol() )
 
  209     case Qt::ForegroundRole:
 
  211       QBrush brush( qApp->palette().color( QPalette::Text ), Qt::SolidPattern );
 
  212       if ( index.column() == 1 && ( category.
value().type() == QVariant::List
 
  213                                     || !category.
value().isValid() || category.
value().isNull() || category.
value().toString().isEmpty() ) )
 
  215         QColor fadedTextColor = brush.color();
 
  216         fadedTextColor.setAlpha( 128 );
 
  217         brush.setColor( fadedTextColor );
 
  222     case Qt::TextAlignmentRole:
 
  224       return ( index.column() == 0 ) ? 
static_cast<Qt::Alignment::Int
>( Qt::AlignHCenter ) : 
static_cast<Qt::Alignment::Int
>( Qt::AlignLeft );
 
  229       switch ( index.column() )
 
  233           if ( category.
value().type() == QVariant::List )
 
  236             const QVariantList list = category.
value().toList();
 
  237             res.reserve( list.size() );
 
  238             for ( 
const QVariant &v : list )
 
  241             return res.join( 
';' );
 
  245             return category.
value();
 
  250           return category.
label();
 
  254     case QgsCategorizedSymbolRendererWidget::CustomRoles::ValueRole:
 
  256       if ( index.column() == 1 )
 
  257         return category.
value();
 
  265 bool QgsCategorizedSymbolRendererModel::setData( 
const QModelIndex &index, 
const QVariant &value, 
int role )
 
  267   if ( !index.isValid() )
 
  270   if ( index.column() == 0 && role == Qt::CheckStateRole )
 
  272     mRenderer->updateCategoryRenderState( index.row(), value == Qt::Checked );
 
  273     emit dataChanged( index, index );
 
  277   if ( role != Qt::EditRole )
 
  280   switch ( index.column() )
 
  285       QVariant val = value;
 
  286       const QVariant previousValue = mRenderer->categories().value( index.row() ).value();
 
  287       if ( previousValue.type() != QVariant::String && ! previousValue.toString().isEmpty() )
 
  289         switch ( previousValue.type() )
 
  294           case QVariant::Double:
 
  295             val = value.toDouble();
 
  299             const QStringList parts = value.toString().split( 
';' );
 
  301             list.reserve( parts.count() );
 
  302             for ( 
const QString &p : parts )
 
  305             if ( list.count() == 1 )
 
  312             val = value.toString();
 
  316       mRenderer->updateCategoryValue( index.row(), val );
 
  320       mRenderer->updateCategoryLabel( index.row(), value.toString() );
 
  326   emit dataChanged( index, index );
 
  330 QVariant QgsCategorizedSymbolRendererModel::headerData( 
int section, Qt::Orientation orientation, 
int role )
 const 
  332   if ( orientation == Qt::Horizontal && role == Qt::DisplayRole && section >= 0 && section < 3 )
 
  335     lst << tr( 
"Symbol" ) << tr( 
"Value" ) << tr( 
"Legend" );
 
  336     return lst.value( section );
 
  341 int QgsCategorizedSymbolRendererModel::rowCount( 
const QModelIndex &parent )
 const 
  343   if ( parent.isValid() || !mRenderer )
 
  347   return mRenderer->categories().size();
 
  350 int QgsCategorizedSymbolRendererModel::columnCount( 
const QModelIndex &index )
 const 
  356 QModelIndex QgsCategorizedSymbolRendererModel::index( 
int row, 
int column, 
const QModelIndex &parent )
 const 
  358   if ( hasIndex( row, column, parent ) )
 
  360     return createIndex( row, column );
 
  362   return QModelIndex();
 
  365 QModelIndex QgsCategorizedSymbolRendererModel::parent( 
const QModelIndex &index )
 const 
  368   return QModelIndex();
 
  371 QStringList QgsCategorizedSymbolRendererModel::mimeTypes()
 const 
  374   types << mMimeFormat;
 
  378 QMimeData *QgsCategorizedSymbolRendererModel::mimeData( 
const QModelIndexList &indexes )
 const 
  380   QMimeData *mimeData = 
new QMimeData();
 
  381   QByteArray encodedData;
 
  383   QDataStream stream( &encodedData, QIODevice::WriteOnly );
 
  386   const auto constIndexes = indexes;
 
  387   for ( 
const QModelIndex &index : constIndexes )
 
  389     if ( !index.isValid() || index.column() != 0 )
 
  392     stream << index.row();
 
  394   mimeData->setData( mMimeFormat, encodedData );
 
  398 bool QgsCategorizedSymbolRendererModel::dropMimeData( 
const QMimeData *data, Qt::DropAction action, 
int row, 
int column, 
const QModelIndex &parent )
 
  402   if ( action != Qt::MoveAction ) 
return true;
 
  404   if ( !data->hasFormat( mMimeFormat ) ) 
return false;
 
  406   QByteArray encodedData = data->data( mMimeFormat );
 
  407   QDataStream stream( &encodedData, QIODevice::ReadOnly );
 
  410   while ( !stream.atEnd() )
 
  417   int to = parent.row();
 
  420   if ( to == -1 ) to = mRenderer->categories().size(); 
 
  421   for ( 
int i = rows.size() - 1; i >= 0; i-- )
 
  423     QgsDebugMsg( QStringLiteral( 
"move %1 to %2" ).arg( rows[i] ).arg( to ) );
 
  426     if ( rows[i] < t ) t--;
 
  427     mRenderer->moveCategory( rows[i], t );
 
  429     for ( 
int j = 0; j < i; j++ )
 
  431       if ( to < rows[j] && rows[i] > rows[j] ) rows[j] += 1;
 
  434     if ( rows[i] < to ) to--;
 
  436   emit dataChanged( createIndex( 0, 0 ), createIndex( mRenderer->categories().size(), 0 ) );
 
  441 void QgsCategorizedSymbolRendererModel::deleteRows( QList<int> rows )
 
  443   std::sort( rows.begin(), rows.end() ); 
 
  444   for ( 
int i = rows.size() - 1; i >= 0; i-- )
 
  446     beginRemoveRows( QModelIndex(), rows[i], rows[i] );
 
  447     mRenderer->deleteCategory( rows[i] );
 
  452 void QgsCategorizedSymbolRendererModel::removeAllRows()
 
  454   beginRemoveRows( QModelIndex(), 0, mRenderer->categories().size() - 1 );
 
  455   mRenderer->deleteAllCategories();
 
  459 void QgsCategorizedSymbolRendererModel::sort( 
int column, Qt::SortOrder order )
 
  467     mRenderer->sortByValue( order );
 
  469   else if ( column == 2 )
 
  471     mRenderer->sortByLabel( order );
 
  473   emit dataChanged( createIndex( 0, 0 ), createIndex( mRenderer->categories().size(), 0 ) );
 
  476 void QgsCategorizedSymbolRendererModel::updateSymbology()
 
  478   emit dataChanged( createIndex( 0, 0 ), createIndex( mRenderer->categories().size(), 0 ) );
 
  482 QgsCategorizedSymbolRendererViewStyle::QgsCategorizedSymbolRendererViewStyle( QWidget *parent )
 
  486 void QgsCategorizedSymbolRendererViewStyle::drawPrimitive( PrimitiveElement element, 
const QStyleOption *option, QPainter *painter, 
const QWidget *widget )
 const 
  488   if ( element == QStyle::PE_IndicatorItemViewItemDrop && !option->rect.isNull() )
 
  490     QStyleOption opt( *option );
 
  491     opt.rect.setLeft( 0 );
 
  493     opt.rect.setHeight( 0 );
 
  494     if ( widget ) opt.rect.setRight( widget->width() );
 
  495     QProxyStyle::drawPrimitive( element, &opt, painter, widget );
 
  498   QProxyStyle::drawPrimitive( element, option, painter, widget );
 
  502 QgsCategorizedRendererViewItemDelegate::QgsCategorizedRendererViewItemDelegate( 
QgsFieldExpressionWidget *expressionWidget, QObject *parent )
 
  503   : QStyledItemDelegate( parent )
 
  504   , mFieldExpressionWidget( expressionWidget )
 
  508 QWidget *QgsCategorizedRendererViewItemDelegate::createEditor( QWidget *parent, 
const QStyleOptionViewItem &option, 
const QModelIndex &index )
 const 
  510   QVariant::Type userType { index.data( QgsCategorizedSymbolRendererWidget::CustomRoles::ValueRole ).type() };
 
  513   if ( userType == QVariant::String && index.data( QgsCategorizedSymbolRendererWidget::CustomRoles::ValueRole ).isNull() )
 
  517     const QString fieldName { mFieldExpressionWidget->currentField( &isExpression, &isValid ) };
 
  518     if ( ! fieldName.isEmpty() && mFieldExpressionWidget->layer() && mFieldExpressionWidget->layer()->fields().lookupField( fieldName ) != -1 )
 
  520       userType = mFieldExpressionWidget->layer()->fields().field( fieldName ).type();
 
  522     else if ( isExpression && isValid )
 
  526       if ( mFieldExpressionWidget->layer()->getFeatures().nextFeature( feat ) )
 
  531         expressionContext.
appendScope( mFieldExpressionWidget->layer()->createExpressionContextScope() );
 
  534         const QVariant value = exp.evaluate( &expressionContext );
 
  535         if ( !exp.hasEvalError() )
 
  537           userType = value.type();
 
  546     case QVariant::Type::Double:
 
  550       const QVariant value = index.data( QgsCategorizedSymbolRendererWidget::CustomRoles::ValueRole );
 
  552       if ( value.toDouble( &ok ); ok )
 
  554         const QString strVal { value.toString() };
 
  555         const int dotPosition( strVal.indexOf( 
'.' ) );
 
  556         if ( dotPosition >= 0 )
 
  558           decimals = std::max<int>( 2, strVal.length() - dotPosition - 1 );
 
  561       editor->setDecimals( decimals );
 
  563       editor->setMaximum( std::numeric_limits<double>::max() );
 
  564       editor->setMinimum( std::numeric_limits<double>::lowest() );
 
  567     case QVariant::Type::Int:
 
  570       editor->setDecimals( 0 );
 
  572       editor->setMaximum( std::numeric_limits<int>::max() );
 
  573       editor->setMinimum( std::numeric_limits<int>::min() );
 
  576     case QVariant::Type::Char:
 
  579       editor->setDecimals( 0 );
 
  581       editor->setMaximum( std::numeric_limits<char>::max() );
 
  582       editor->setMinimum( std::numeric_limits<char>::min() );
 
  585     case QVariant::Type::UInt:
 
  588       editor->setDecimals( 0 );
 
  590       editor->setMaximum( std::numeric_limits<unsigned int>::max() );
 
  591       editor->setMinimum( 0 );
 
  594     case QVariant::Type::LongLong:
 
  597       editor->setDecimals( 0 );
 
  599       editor->setMaximum( 
static_cast<double>( std::numeric_limits<qlonglong>::max() ) );
 
  600       editor->setMinimum( std::numeric_limits<qlonglong>::min() );
 
  603     case QVariant::Type::ULongLong:
 
  606       editor->setDecimals( 0 );
 
  608       editor->setMaximum( 
static_cast<double>( std::numeric_limits<unsigned long long>::max() ) );
 
  609       editor->setMinimum( 0 );
 
  615   return editor ? editor : QStyledItemDelegate::createEditor( parent, option, index );
 
  623   return new QgsCategorizedSymbolRendererWidget( layer, style, renderer );
 
  628   , mContextMenu( new QMenu( this ) )
 
  644   const QString attrName = 
mRenderer->classAttribute();
 
  645   mOldClassificationAttribute = attrName;
 
  649   layout()->setContentsMargins( 0, 0, 0, 0 );
 
  651   mExpressionWidget->setLayer( 
mLayer );
 
  652   btnChangeCategorizedSymbol->setLayer( 
mLayer );
 
  653   btnChangeCategorizedSymbol->registerExpressionContextGenerator( 
this );
 
  656   btnColorRamp->setShowRandomColorRamp( 
true );
 
  659   std::unique_ptr< QgsColorRamp > colorRamp( 
QgsProject::instance()->styleSettings()->defaultColorRamp() );
 
  662     btnColorRamp->setColorRamp( colorRamp.get() );
 
  666     btnColorRamp->setRandomColorRamp();
 
  676   mModel = 
new QgsCategorizedSymbolRendererModel( 
this );
 
  682   viewCategories->setModel( 
mModel );
 
  683   viewCategories->resizeColumnToContents( 0 );
 
  684   viewCategories->resizeColumnToContents( 1 );
 
  685   viewCategories->resizeColumnToContents( 2 );
 
  686   viewCategories->setItemDelegateForColumn( 1, 
new QgsCategorizedRendererViewItemDelegate( mExpressionWidget, viewCategories ) );
 
  688   viewCategories->setStyle( 
new QgsCategorizedSymbolRendererViewStyle( viewCategories ) );
 
  689   connect( viewCategories->selectionModel(), &QItemSelectionModel::selectionChanged, 
this, &QgsCategorizedSymbolRendererWidget::selectionChanged );
 
  697   connect( viewCategories, &QTreeView::customContextMenuRequested, 
this, &QgsCategorizedSymbolRendererWidget::showContextMenu );
 
  699   connect( btnChangeCategorizedSymbol, &
QgsSymbolButton::changed, 
this, &QgsCategorizedSymbolRendererWidget::updateSymbolsFromButton );
 
  709   QMenu *advMenu = 
new QMenu;
 
  716     QAction *actionDdsLegend = advMenu->addAction( tr( 
"Data-defined Size Legend…" ) );
 
  718     connect( actionDdsLegend, &QAction::triggered, 
this, &QgsCategorizedSymbolRendererWidget::dataDefinedSizeLegend );
 
  721   btnAdvanced->setMenu( advMenu );
 
  723   mExpressionWidget->registerExpressionContextGenerator( 
this );
 
  725   mMergeCategoriesAction = 
new QAction( tr( 
"Merge Categories" ), 
this );
 
  726   connect( mMergeCategoriesAction, &QAction::triggered, 
this, &QgsCategorizedSymbolRendererWidget::mergeSelectedCategories );
 
  727   mUnmergeCategoriesAction = 
new QAction( tr( 
"Unmerge Categories" ), 
this );
 
  728   connect( mUnmergeCategoriesAction, &QAction::triggered, 
this, &QgsCategorizedSymbolRendererWidget::unmergeSelectedCategories );
 
  730   connect( mContextMenu, &QMenu::aboutToShow, 
this, [ = ]
 
  751   const QString attrName = mRenderer->classAttribute();
 
  752   mExpressionWidget->setField( attrName );
 
  755   if ( mRenderer->sourceSymbol() )
 
  757     mCategorizedSymbol.reset( mRenderer->sourceSymbol()->clone() );
 
  758     whileBlocking( btnChangeCategorizedSymbol )->setSymbol( mCategorizedSymbol->clone() );
 
  762   if ( mRenderer->sourceColorRamp() )
 
  764     btnColorRamp->setColorRamp( mRenderer->sourceColorRamp() );
 
  770   return mRenderer.get();
 
  776   btnChangeCategorizedSymbol->setMapCanvas( context.
mapCanvas() );
 
  777   btnChangeCategorizedSymbol->setMessageBar( context.
messageBar() );
 
  782   delete mActionLevels;
 
  783   mActionLevels = 
nullptr;
 
  788   const QList<int> selectedCats = selectedCategories();
 
  790   if ( !selectedCats.isEmpty() )
 
  801     const auto constSelectedCats = selectedCats;
 
  802     for ( 
const int idx : constSelectedCats )
 
  807       newCatSymbol->
setColor( mRenderer->categories()[idx].symbol()->color() );
 
  808       mRenderer->updateCategorySymbol( idx, newCatSymbol );
 
  816   std::unique_ptr<QgsSymbol> newSymbol( mCategorizedSymbol->clone() );
 
  831     if ( !dlg.exec() || !newSymbol )
 
  836     mCategorizedSymbol = std::move( newSymbol );
 
  837     applyChangeToSymbol();
 
  848   mRenderer->setClassAttribute( 
field );
 
  849   emit widgetChanged();
 
  854   if ( idx.isValid() && idx.column() == 0 )
 
  855     changeCategorySymbol();
 
  862   std::unique_ptr< QgsSymbol > symbol;
 
  864   if ( 
auto *lSymbol = category.
symbol() )
 
  866     symbol.reset( lSymbol->clone() );
 
  887     if ( !dlg.exec() || !symbol )
 
  892     mCategorizedSymbol = std::move( symbol );
 
  893     applyChangeToSymbol();
 
  900   const QString attrName = mExpressionWidget->currentField();
 
  901   const int idx = mLayer->fields().lookupField( attrName );
 
  902   QList<QVariant> uniqueValues;
 
  913     expression->
prepare( &context );
 
  919       const QVariant value = expression->
evaluate( &context );
 
  920       if ( uniqueValues.contains( value ) )
 
  922       uniqueValues << value;
 
  927     uniqueValues = qgis::setToList( mLayer->uniqueValues( idx ) );
 
  931   if ( uniqueValues.size() >= 1000 )
 
  933     const int res = QMessageBox::warning( 
nullptr, tr( 
"Classify Categories" ),
 
  934                                           tr( 
"High number of classes. Classification would yield %n entries which might not be expected. Continue?", 
nullptr, uniqueValues.size() ),
 
  935                                           QMessageBox::Ok | QMessageBox::Cancel,
 
  936                                           QMessageBox::Cancel );
 
  937     if ( res == QMessageBox::Cancel )
 
  944   DlgAddCategories dlg( mStyle, createDefaultSymbol(), unique_vals, 
this );
 
  950   bool deleteExisting = 
false;
 
  952   if ( !mOldClassificationAttribute.isEmpty() &&
 
  953        attrName != mOldClassificationAttribute &&
 
  954        !mRenderer->categories().isEmpty() )
 
  956     const int res = QMessageBox::question( 
this,
 
  957                                            tr( 
"Delete Classification" ),
 
  958                                            tr( 
"The classification field was changed from '%1' to '%2'.\n" 
  959                                                "Should the existing classes be deleted before classification?" )
 
  960                                            .arg( mOldClassificationAttribute, attrName ),
 
  961                                            QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel );
 
  962     if ( res == QMessageBox::Cancel )
 
  967     deleteExisting = ( res == QMessageBox::Yes );
 
  971   bool keepExistingColors = 
false;
 
  972   if ( !deleteExisting )
 
  975     keepExistingColors = !prevCats.isEmpty();
 
  977     if ( keepExistingColors && btnColorRamp->isRandomColorRamp() )
 
  979     for ( 
int i = 0; i < cats.size(); ++i )
 
  981       bool contains = 
false;
 
  982       const QVariant value = cats.at( i ).value();
 
  983       for ( 
int j = 0; j < prevCats.size() && !contains; ++j )
 
  985         const QVariant prevCatValue = prevCats.at( j ).value();
 
  986         if ( prevCatValue.type() == QVariant::List )
 
  988           const QVariantList list = prevCatValue.toList();
 
  989           for ( 
const QVariant &v : list )
 
 1000           if ( prevCats.at( j ).value() == value )
 
 1011         if ( keepExistingColors && btnColorRamp->isRandomColorRamp() )
 
 1014           cats.at( i ).symbol()->setColor( randomColors.
color( i ) );
 
 1016         prevCats.append( cats.at( i ) );
 
 1022   mOldClassificationAttribute = attrName;
 
 1039   std::unique_ptr< QgsCategorizedSymbolRenderer > r = std::make_unique< QgsCategorizedSymbolRenderer >( attrName, cats );
 
 1040   r->setSourceSymbol( mCategorizedSymbol->clone() );
 
 1041   std::unique_ptr< QgsColorRamp > ramp( btnColorRamp->colorRamp() );
 
 1043     r->setSourceColorRamp( ramp->clone() );
 
 1047     mModel->setRenderer( r.get() );
 
 1049   mRenderer = std::move( r );
 
 1050   if ( ! keepExistingColors && ramp )
 
 1052   emit widgetChanged();
 
 1057   if ( !btnColorRamp->isNull() )
 
 1059     mRenderer->updateColorRamp( btnColorRamp->colorRamp() );
 
 1061   mModel->updateSymbology();
 
 1066   const QModelIndex idx = viewCategories->selectionModel()->currentIndex();
 
 1067   if ( !idx.isValid() )
 
 1075   const QModelIndexList selectedRows = viewCategories->selectionModel()->selectedRows();
 
 1077   const auto constSelectedRows = selectedRows;
 
 1078   for ( 
const QModelIndex &r : constSelectedRows )
 
 1082       rows.append( r.row() );
 
 1090   const QList<int> categoryIndexes = selectedCategories();
 
 1091   mModel->deleteRows( categoryIndexes );
 
 1092   emit widgetChanged();
 
 1097   mModel->removeAllRows();
 
 1098   emit widgetChanged();
 
 1103   if ( !mModel ) 
return;
 
 1106   mModel->addCategory( cat );
 
 1107   emit widgetChanged();
 
 1112   QList<QgsSymbol *> selectedSymbols;
 
 1114   QItemSelectionModel *m = viewCategories->selectionModel();
 
 1115   const QModelIndexList selectedIndexes = m->selectedRows( 1 );
 
 1117   if ( !selectedIndexes.isEmpty() )
 
 1120     QModelIndexList::const_iterator indexIt = selectedIndexes.constBegin();
 
 1121     for ( ; indexIt != selectedIndexes.constEnd(); ++indexIt )
 
 1123       const int row = ( *indexIt ).row();
 
 1124       QgsSymbol *s = categories[row].symbol();
 
 1127         selectedSymbols.append( s );
 
 1131   return selectedSymbols;
 
 1138   QItemSelectionModel *m = viewCategories->selectionModel();
 
 1139   const QModelIndexList selectedIndexes = m->selectedRows( 1 );
 
 1141   if ( !selectedIndexes.isEmpty() )
 
 1143     QModelIndexList::const_iterator indexIt = selectedIndexes.constBegin();
 
 1144     for ( ; indexIt != selectedIndexes.constEnd(); ++indexIt )
 
 1146       cl.append( mModel->category( *indexIt ) );
 
 1154   populateCategories();
 
 1155   emit widgetChanged();
 
 1160   showSymbolLevelsDialog( mRenderer.get() );
 
 1165   viewCategories->selectionModel()->clear();
 
 1173     QMessageBox::information( 
this, tr( 
"Matched Symbols" ),
 
 1174                               tr( 
"Matched %n categories to symbols.", 
nullptr, matched ) );
 
 1178     QMessageBox::warning( 
this, tr( 
"Matched Symbols" ),
 
 1179                           tr( 
"No categories could be matched to symbols in library." ) );
 
 1185   if ( !mLayer || !style )
 
 1192   QVariantList unmatchedCategories;
 
 1193   QStringList unmatchedSymbols;
 
 1194   const int matched = mRenderer->matchToSymbols( style, type, unmatchedCategories, unmatchedSymbols );
 
 1196   mModel->updateSymbology();
 
 1203   const QString openFileDir = settings.
value( QStringLiteral( 
"UI/lastMatchToSymbolsDir" ), QDir::homePath() ).toString();
 
 1205   const QString fileName = QFileDialog::getOpenFileName( 
this, tr( 
"Match to Symbols from File" ), openFileDir,
 
 1206                            tr( 
"XML files (*.xml *.XML)" ) );
 
 1207   if ( fileName.isEmpty() )
 
 1212   const QFileInfo openFileInfo( fileName );
 
 1213   settings.
setValue( QStringLiteral( 
"UI/lastMatchToSymbolsDir" ), openFileInfo.absolutePath() );
 
 1216   if ( !importedStyle.
importXml( fileName ) )
 
 1218     QMessageBox::warning( 
this, tr( 
"Match to Symbols from File" ),
 
 1219                           tr( 
"An error occurred while reading file:\n%1" ).arg( importedStyle.
errorString() ) );
 
 1223   const int matched = matchToSymbols( &importedStyle );
 
 1226     QMessageBox::information( 
this, tr( 
"Match to Symbols from File" ),
 
 1227                               tr( 
"Matched %n categories to symbols from file.", 
nullptr, matched ) );
 
 1231     QMessageBox::warning( 
this, tr( 
"Match to Symbols from File" ),
 
 1232                           tr( 
"No categories could be matched to symbols in file." ) );
 
 1243       mRenderer->setLegendSymbolItem( legendSymbol.ruleKey(), sym->
clone() );
 
 1246   mRenderer->setUsingSymbolLevels( enabled );
 
 1247   mModel->updateSymbology();
 
 1248   emit widgetChanged();
 
 1257   const QList<int> selectedCats = selectedCategories();
 
 1258   if ( !selectedCats.isEmpty() )
 
 1260     for ( 
const int idx : selectedCats )
 
 1262       if ( mRenderer->categories().at( idx ).symbol()->type() != tempSymbol->type() )
 
 1265       std::unique_ptr< QgsSymbol > newCatSymbol( tempSymbol->clone() );
 
 1266       if ( selectedCats.count() > 1 )
 
 1269         newCatSymbol->setColor( mRenderer->categories().at( idx ).symbol()->color() );
 
 1271       mRenderer->updateCategorySymbol( idx, newCatSymbol.release() );
 
 1273     emit widgetChanged();
 
 1277 void QgsCategorizedSymbolRendererWidget::cleanUpSymbolSelector( 
QgsPanelWidget *container )
 
 1286 void QgsCategorizedSymbolRendererWidget::updateSymbolsFromWidget()
 
 1289   mCategorizedSymbol.reset( dlg->
symbol()->
clone() );
 
 1291   applyChangeToSymbol();
 
 1294 void QgsCategorizedSymbolRendererWidget::updateSymbolsFromButton()
 
 1296   mCategorizedSymbol.reset( btnChangeCategorizedSymbol->symbol()->clone() );
 
 1298   applyChangeToSymbol();
 
 1304   QItemSelectionModel *m = viewCategories->selectionModel();
 
 1305   const QModelIndexList i = m->selectedRows();
 
 1309     const QList<int> selectedCats = selectedCategories();
 
 1311     if ( !selectedCats.isEmpty() )
 
 1313       const auto constSelectedCats = selectedCats;
 
 1314       for ( 
const int idx : constSelectedCats )
 
 1317         if ( selectedCats.count() > 1 )
 
 1320           newCatSymbol->
setColor( mRenderer->categories().at( idx ).symbol()->color() );
 
 1322         mRenderer->updateCategorySymbol( idx, newCatSymbol );
 
 1328     mRenderer->updateSymbols( mCategorizedSymbol.get() );
 
 1331   mModel->updateSymbology();
 
 1332   emit widgetChanged();
 
 1342   if ( event->key() == Qt::Key_C && event->modifiers() == Qt::ControlModifier )
 
 1344     mCopyBuffer.clear();
 
 1345     mCopyBuffer = selectedCategoryList();
 
 1347   else if ( event->key() == Qt::Key_V && event->modifiers() == Qt::ControlModifier )
 
 1349     QgsCategoryList::const_iterator rIt = mCopyBuffer.constBegin();
 
 1350     for ( ; rIt != mCopyBuffer.constEnd(); ++rIt )
 
 1352       mModel->addCategory( *rIt );
 
 1364   if ( 
auto *lMapCanvas = mContext.mapCanvas() )
 
 1370       expContext << generator->createExpressionContextScope();
 
 1378   if ( 
auto *lVectorLayer = vectorLayer() )
 
 1382   const auto constAdditionalExpressionContextScopes = mContext.additionalExpressionContextScopes();
 
 1391 void QgsCategorizedSymbolRendererWidget::dataDefinedSizeLegend()
 
 1394   QgsDataDefinedSizeLegendWidget *panel = createDataDefinedSizeLegendWidget( s, mRenderer->dataDefinedSizeLegend() );
 
 1399       mRenderer->setDataDefinedSizeLegend( panel->dataDefinedSizeLegend() );
 
 1400       emit widgetChanged();
 
 1406 void QgsCategorizedSymbolRendererWidget::mergeSelectedCategories()
 
 1410   const QList<int> selectedCategoryIndexes = selectedCategories();
 
 1411   QList< int > categoryIndexes;
 
 1414   for ( 
const int i : selectedCategoryIndexes )
 
 1416     const QVariant v = categories.at( i ).value();
 
 1418     if ( !v.isValid() || v == 
"" )
 
 1423     categoryIndexes.append( i );
 
 1426   if ( categoryIndexes.count() < 2 )
 
 1430   QVariantList values;
 
 1431   values.reserve( categoryIndexes.count() );
 
 1432   labels.reserve( categoryIndexes.count() );
 
 1433   for ( 
const int i : categoryIndexes )
 
 1435     const QVariant v = categories.at( i ).value();
 
 1437     if ( v.type() == QVariant::List )
 
 1439       values.append( v.toList() );
 
 1444     labels << categories.at( i ).label();
 
 1448   mRenderer->updateCategoryLabel( categoryIndexes.at( 0 ), labels.join( 
',' ) );
 
 1449   mRenderer->updateCategoryValue( categoryIndexes.at( 0 ), values );
 
 1451   categoryIndexes.pop_front();
 
 1452   mModel->deleteRows( categoryIndexes );
 
 1454   emit widgetChanged();
 
 1457 void QgsCategorizedSymbolRendererWidget::unmergeSelectedCategories()
 
 1459   const QList<int> categoryIndexes = selectedCategories();
 
 1460   if ( categoryIndexes.isEmpty() )
 
 1464   for ( 
const int i : categoryIndexes )
 
 1466     const QVariant v = categories.at( i ).value();
 
 1467     if ( v.type() != QVariant::List )
 
 1470     const QVariantList list = v.toList();
 
 1471     for ( 
int j = 1; j < list.count(); ++j )
 
 1473       mModel->addCategory( 
QgsRendererCategory( list.at( j ), categories.at( i ).symbol()->clone(), list.at( j ).toString(), categories.at( i ).renderState() ) );
 
 1475     mRenderer->updateCategoryValue( i, list.at( 0 ) );
 
 1476     mRenderer->updateCategoryLabel( i, list.at( 0 ).toString() );
 
 1479   emit widgetChanged();
 
 1482 void QgsCategorizedSymbolRendererWidget::showContextMenu( QPoint )
 
 1484   mContextMenu->clear();
 
 1485   const QList< QAction * > actions = contextMenu->actions();
 
 1486   for ( QAction *act : actions )
 
 1488     mContextMenu->addAction( act );
 
 1491   mContextMenu->addSeparator();
 
 1493   if ( viewCategories->selectionModel()->selectedRows().count() > 1 )
 
 1495     mContextMenu->addAction( mMergeCategoriesAction );
 
 1497   if ( viewCategories->selectionModel()->selectedRows().count() == 1 )
 
 1499     const QList<int> categoryIndexes = selectedCategories();
 
 1501     const QVariant v = categories.at( categoryIndexes.at( 0 ) ).value();
 
 1502     if ( v.type() == QVariant::List )
 
 1503       mContextMenu->addAction( mUnmergeCategoriesAction );
 
 1505   else if ( viewCategories->selectionModel()->selectedRows().count() > 1 )
 
 1507     mContextMenu->addAction( mUnmergeCategoriesAction );
 
 1510   mContextMenu->exec( QCursor::pos() );
 
 1513 void QgsCategorizedSymbolRendererWidget::selectionChanged( 
const QItemSelection &, 
const QItemSelection & )
 
 1515   const QList<int> selectedCats = selectedCategories();
 
 1516   if ( !selectedCats.isEmpty() )
 
 1518     whileBlocking( btnChangeCategorizedSymbol )->setSymbol( mRenderer->categories().at( selectedCats.at( 0 ) ).symbol()->clone() );
 
 1520   else if ( mRenderer->sourceSymbol() )
 
 1522     whileBlocking( btnChangeCategorizedSymbol )->setSymbol( mRenderer->sourceSymbol()->clone() );
 
 1524   btnChangeCategorizedSymbol->setDialogTitle( selectedCats.size() == 1 ? mRenderer->categories().at( selectedCats.at( 0 ) ).label() : tr( 
"Symbol Settings" ) );