43 #include <QMessageBox> 44 #include <QStandardItemModel> 45 #include <QStandardItem> 48 #include <QFileDialog> 52 QgsCategorizedSymbolRendererModel::QgsCategorizedSymbolRendererModel( QObject *parent ) : QAbstractItemModel( parent )
53 , mMimeFormat( QStringLiteral(
"application/x-qgscategorizedsymbolrendererv2model" ) )
61 beginRemoveRows( QModelIndex(), 0, std::max( mRenderer->categories().size() - 1, 0 ) );
70 beginInsertRows( QModelIndex(), 0, renderer->
categories().size() - 1 );
78 if ( !mRenderer )
return;
79 int idx = mRenderer->categories().size();
80 beginInsertRows( QModelIndex(), idx, idx );
81 mRenderer->addCategory( cat );
92 int row = index.row();
93 if ( row >= catList.size() )
97 return catList.at( row );
101 Qt::ItemFlags QgsCategorizedSymbolRendererModel::flags(
const QModelIndex &index )
const 103 if ( !index.isValid() )
105 return Qt::ItemIsDropEnabled;
108 Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsUserCheckable;
109 if ( index.column() == 1 || index.column() == 2 )
111 flags |= Qt::ItemIsEditable;
116 Qt::DropActions QgsCategorizedSymbolRendererModel::supportedDropActions()
const 118 return Qt::MoveAction;
121 QVariant QgsCategorizedSymbolRendererModel::data(
const QModelIndex &index,
int role )
const 123 if ( !index.isValid() || !mRenderer )
128 if ( role == Qt::CheckStateRole && index.column() == 0 )
130 return category.
renderState() ? Qt::Checked : Qt::Unchecked;
132 else if ( role == Qt::DisplayRole || role == Qt::ToolTipRole )
134 switch ( index.column() )
137 return category.
value().toString();
139 return category.
label();
144 else if ( role == Qt::DecorationRole && index.column() == 0 && category.
symbol() )
148 else if ( role == Qt::TextAlignmentRole )
150 return ( index.column() == 0 ) ? Qt::AlignHCenter : Qt::AlignLeft;
152 else if ( role == Qt::EditRole )
154 switch ( index.column() )
157 return category.
value();
159 return category.
label();
168 bool QgsCategorizedSymbolRendererModel::setData(
const QModelIndex &index,
const QVariant &value,
int role )
170 if ( !index.isValid() )
173 if ( index.column() == 0 && role == Qt::CheckStateRole )
175 mRenderer->updateCategoryRenderState( index.row(), value == Qt::Checked );
176 emit dataChanged( index, index );
180 if ( role != Qt::EditRole )
183 switch ( index.column() )
189 switch ( mRenderer->categories().value( index.row() ).value().type() )
194 case QVariant::Double:
195 val = value.toDouble();
198 val = value.toString();
201 mRenderer->updateCategoryValue( index.row(), val );
205 mRenderer->updateCategoryLabel( index.row(), value.toString() );
211 emit dataChanged( index, index );
215 QVariant QgsCategorizedSymbolRendererModel::headerData(
int section, Qt::Orientation orientation,
int role )
const 217 if ( orientation == Qt::Horizontal && role == Qt::DisplayRole && section >= 0 && section < 3 )
220 lst << tr(
"Symbol" ) << tr(
"Value" ) << tr(
"Legend" );
221 return lst.value( section );
226 int QgsCategorizedSymbolRendererModel::rowCount(
const QModelIndex &parent )
const 228 if ( parent.isValid() || !mRenderer )
232 return mRenderer->categories().size();
235 int QgsCategorizedSymbolRendererModel::columnCount(
const QModelIndex &index )
const 241 QModelIndex QgsCategorizedSymbolRendererModel::index(
int row,
int column,
const QModelIndex &parent )
const 243 if ( hasIndex( row, column, parent ) )
245 return createIndex( row, column );
247 return QModelIndex();
250 QModelIndex QgsCategorizedSymbolRendererModel::parent(
const QModelIndex &index )
const 253 return QModelIndex();
256 QStringList QgsCategorizedSymbolRendererModel::mimeTypes()
const 259 types << mMimeFormat;
263 QMimeData *QgsCategorizedSymbolRendererModel::mimeData(
const QModelIndexList &indexes )
const 265 QMimeData *mimeData =
new QMimeData();
266 QByteArray encodedData;
268 QDataStream stream( &encodedData, QIODevice::WriteOnly );
271 Q_FOREACH (
const QModelIndex &index, indexes )
273 if ( !index.isValid() || index.column() != 0 )
276 stream << index.row();
278 mimeData->setData( mMimeFormat, encodedData );
282 bool QgsCategorizedSymbolRendererModel::dropMimeData(
const QMimeData *data, Qt::DropAction action,
int row,
int column,
const QModelIndex &parent )
286 if ( action != Qt::MoveAction )
return true;
288 if ( !data->hasFormat( mMimeFormat ) )
return false;
290 QByteArray encodedData = data->data( mMimeFormat );
291 QDataStream stream( &encodedData, QIODevice::ReadOnly );
294 while ( !stream.atEnd() )
301 int to = parent.row();
304 if ( to == -1 ) to = mRenderer->categories().size();
305 for (
int i = rows.size() - 1; i >= 0; i-- )
307 QgsDebugMsg( QStringLiteral(
"move %1 to %2" ).arg( rows[i] ).arg( to ) );
310 if ( rows[i] < t ) t--;
311 mRenderer->moveCategory( rows[i], t );
313 for (
int j = 0; j < i; j++ )
315 if ( to < rows[j] && rows[i] > rows[j] ) rows[j] += 1;
318 if ( rows[i] < to ) to--;
320 emit dataChanged( createIndex( 0, 0 ), createIndex( mRenderer->categories().size(), 0 ) );
325 void QgsCategorizedSymbolRendererModel::deleteRows( QList<int> rows )
327 std::sort( rows.begin(), rows.end() );
328 for (
int i = rows.size() - 1; i >= 0; i-- )
330 beginRemoveRows( QModelIndex(), rows[i], rows[i] );
331 mRenderer->deleteCategory( rows[i] );
336 void QgsCategorizedSymbolRendererModel::removeAllRows()
338 beginRemoveRows( QModelIndex(), 0, mRenderer->categories().size() - 1 );
339 mRenderer->deleteAllCategories();
343 void QgsCategorizedSymbolRendererModel::sort(
int column, Qt::SortOrder order )
351 mRenderer->sortByValue( order );
353 else if ( column == 2 )
355 mRenderer->sortByLabel( order );
357 emit dataChanged( createIndex( 0, 0 ), createIndex( mRenderer->categories().size(), 0 ) );
360 void QgsCategorizedSymbolRendererModel::updateSymbology()
362 emit dataChanged( createIndex( 0, 0 ), createIndex( mRenderer->categories().size(), 0 ) );
366 QgsCategorizedSymbolRendererViewStyle::QgsCategorizedSymbolRendererViewStyle( QWidget *parent )
370 void QgsCategorizedSymbolRendererViewStyle::drawPrimitive( PrimitiveElement element,
const QStyleOption *option, QPainter *painter,
const QWidget *widget )
const 372 if ( element == QStyle::PE_IndicatorItemViewItemDrop && !option->rect.isNull() )
374 QStyleOption opt( *option );
375 opt.rect.setLeft( 0 );
377 opt.rect.setHeight( 0 );
378 if ( widget ) opt.rect.setRight( widget->width() );
379 QProxyStyle::drawPrimitive( element, &opt, painter, widget );
382 QProxyStyle::drawPrimitive( element, option, painter, widget );
409 QString attrName =
mRenderer->classAttribute();
410 mOldClassificationAttribute = attrName;
414 this->layout()->setContentsMargins( 0, 0, 0, 0 );
416 mExpressionWidget->setLayer(
mLayer );
419 btnColorRamp->setShowRandomColorRamp(
true );
422 QString defaultColorRamp =
QgsProject::instance()->
readEntry( QStringLiteral(
"DefaultStyles" ), QStringLiteral(
"/ColorRamp" ), QLatin1String(
"" ) );
423 if ( !defaultColorRamp.isEmpty() )
425 btnColorRamp->setColorRampFromName( defaultColorRamp );
429 btnColorRamp->setRandomColorRamp();
434 mModel =
new QgsCategorizedSymbolRendererModel(
this );
440 viewCategories->setModel(
mModel );
441 viewCategories->resizeColumnToContents( 0 );
442 viewCategories->resizeColumnToContents( 1 );
443 viewCategories->resizeColumnToContents( 2 );
445 viewCategories->setStyle(
new QgsCategorizedSymbolRendererViewStyle( viewCategories ) );
464 QMenu *advMenu =
new QMenu;
468 advMenu->addAction( tr(
"Symbol Levels…" ),
this, SLOT(
showSymbolLevels() ) );
471 QAction *actionDdsLegend = advMenu->addAction( tr(
"Data-defined Size Legend…" ) );
473 connect( actionDdsLegend, &QAction::triggered,
this, &QgsCategorizedSymbolRendererWidget::dataDefinedSizeLegend );
476 btnAdvanced->setMenu( advMenu );
478 mExpressionWidget->registerExpressionContextGenerator(
this );
497 QString attrName =
mRenderer->classAttribute();
498 mExpressionWidget->setField( attrName );
510 btnColorRamp->setColorRamp(
mRenderer->sourceColorRamp() );
523 if ( !selectedCats.isEmpty() )
534 Q_FOREACH (
int idx, selectedCats )
540 mRenderer->updateCategorySymbol( idx, newCatSymbol );
562 if ( !dlg.exec() || !newSymbol )
579 btnChangeCategorizedSymbol->setIcon( icon );
594 if ( idx.isValid() && idx.column() == 0 )
602 std::unique_ptr< QgsSymbol > symbol;
626 if ( !dlg.exec() || !symbol )
641 int num = values.count();
643 for (
int i = 0; i < num; i++ )
645 QVariant value = values[i];
647 if ( ! value.isNull() )
661 QString attrName = mExpressionWidget->currentField();
663 QList<QVariant> unique_vals;
674 expression->
prepare( &context );
680 QVariant value = expression->
evaluate( &context );
681 if ( unique_vals.contains( value ) )
683 unique_vals << value;
692 if ( unique_vals.size() >= 1000 )
694 int res = QMessageBox::warning(
nullptr, tr(
"Classify Categories" ),
695 tr(
"High number of classes. Classification would yield %1 entries which might not be expected. Continue?" ).arg( unique_vals.size() ),
696 QMessageBox::Ok | QMessageBox::Cancel,
697 QMessageBox::Cancel );
698 if ( res == QMessageBox::Cancel )
705 DlgAddCategories dlg(
mStyle, createDefaultSymbol(), unique_vals,
this );
712 bool deleteExisting =
false;
714 if ( !mOldClassificationAttribute.isEmpty() &&
715 attrName != mOldClassificationAttribute &&
718 int res = QMessageBox::question(
this,
719 tr(
"Delete Classification" ),
720 tr(
"The classification field was changed from '%1' to '%2'.\n" 721 "Should the existing classes be deleted before classification?" )
722 .arg( mOldClassificationAttribute, attrName ),
723 QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel );
724 if ( res == QMessageBox::Cancel )
729 deleteExisting = ( res == QMessageBox::Yes );
733 bool keepExistingColors =
false;
734 if ( !deleteExisting )
737 keepExistingColors = !prevCats.isEmpty();
738 for (
int i = 0; i < cats.size(); ++i )
740 bool contains =
false;
741 QVariant value = cats.at( i ).value();
742 for (
int j = 0; j < prevCats.size() && !contains; ++j )
744 if ( prevCats.at( j ).value() == value )
752 prevCats.append( cats.at( i ) );
757 mOldClassificationAttribute = attrName;
774 std::unique_ptr< QgsCategorizedSymbolRenderer > r = qgis::make_unique< QgsCategorizedSymbolRenderer >( attrName, cats );
776 std::unique_ptr< QgsColorRamp > ramp( btnColorRamp->colorRamp() );
778 r->setSourceColorRamp( ramp->clone() );
782 mModel->setRenderer( r.get() );
785 if ( ! keepExistingColors && ramp )
792 if ( !btnColorRamp->isNull() )
794 mRenderer->updateColorRamp( btnColorRamp->colorRamp() );
796 mModel->updateSymbology();
801 QModelIndex idx = viewCategories->selectionModel()->currentIndex();
802 if ( !idx.isValid() )
810 QModelIndexList selectedRows = viewCategories->selectionModel()->selectedRows();
812 Q_FOREACH (
const QModelIndex &r, selectedRows )
816 rows.append( r.row() );
825 mModel->deleteRows( categoryIndexes );
840 mModel->addCategory( cat );
848 QItemSelectionModel *m = viewCategories->selectionModel();
849 QModelIndexList selectedIndexes = m->selectedRows( 1 );
851 if ( m && !selectedIndexes.isEmpty() )
854 QModelIndexList::const_iterator indexIt = selectedIndexes.constBegin();
855 for ( ; indexIt != selectedIndexes.constEnd(); ++indexIt )
857 int row = ( *indexIt ).row();
861 selectedSymbols.append( s );
872 QItemSelectionModel *m = viewCategories->selectionModel();
873 QModelIndexList selectedIndexes = m->selectedRows( 1 );
875 if ( m && !selectedIndexes.isEmpty() )
877 QModelIndexList::const_iterator indexIt = selectedIndexes.constBegin();
878 for ( ; indexIt != selectedIndexes.constEnd(); ++indexIt )
880 cl.append(
mModel->category( *indexIt ) );
899 viewCategories->selectionModel()->clear();
907 QMessageBox::information(
this, tr(
"Matched Symbols" ),
908 tr(
"Matched %1 categories to symbols." ).arg( matched ) );
912 QMessageBox::warning(
this, tr(
"Matched Symbols" ),
913 tr(
"No categories could be matched to symbols in library." ) );
923 for (
int catIdx = 0; catIdx <
mRenderer->categories().count(); ++catIdx )
925 QString val =
mRenderer->categories().at( catIdx ).value().toString();
936 mModel->updateSymbology();
943 QString openFileDir = settings.
value( QStringLiteral(
"UI/lastMatchToSymbolsDir" ), QDir::homePath() ).toString();
945 QString fileName = QFileDialog::getOpenFileName(
this, tr(
"Match to Symbols from File" ), openFileDir,
946 tr(
"XML files (*.xml *XML)" ) );
947 if ( fileName.isEmpty() )
952 QFileInfo openFileInfo( fileName );
953 settings.
setValue( QStringLiteral(
"UI/lastMatchToSymbolsDir" ), openFileInfo.absolutePath() );
956 if ( !importedStyle.
importXml( fileName ) )
958 QMessageBox::warning(
this, tr(
"Match to Symbols from File" ),
959 tr(
"An error occurred while reading file:\n%1" ).arg( importedStyle.
errorString() ) );
966 QMessageBox::information(
this, tr(
"Match to Symbols from File" ),
967 tr(
"Matched %1 categories to symbols from file." ).arg( matched ) );
971 QMessageBox::warning(
this, tr(
"Match to Symbols from File" ),
972 tr(
"No categories could be matched to symbols in file." ) );
976 void QgsCategorizedSymbolRendererWidget::cleanUpSymbolSelector(
QgsPanelWidget *container )
985 void QgsCategorizedSymbolRendererWidget::updateSymbolsFromWidget()
996 QItemSelectionModel *m = viewCategories->selectionModel();
997 QModelIndexList i = m->selectedRows();
999 if ( m && !i.isEmpty() )
1003 if ( !selectedCats.isEmpty() )
1005 Q_FOREACH (
int idx, selectedCats )
1008 if ( selectedCats.count() > 1 )
1013 mRenderer->updateCategorySymbol( idx, newCatSymbol );
1033 if ( event->key() == Qt::Key_C &&
event->modifiers() == Qt::ControlModifier )
1035 mCopyBuffer.clear();
1038 else if ( event->key() == Qt::Key_V &&
event->modifiers() == Qt::ControlModifier )
1040 QgsCategoryList::const_iterator rIt = mCopyBuffer.constBegin();
1041 for ( ; rIt != mCopyBuffer.constEnd(); ++rIt )
1043 mModel->addCategory( *rIt );
1077 void QgsCategorizedSymbolRendererWidget::dataDefinedSizeLegend()
int lookupField(const QString &fieldName) const
Look up field's index from the field name.
Class for parsing and evaluation of expressions (formerly called "search strings").
Wrapper for iterator of features from vector data provider or vector layer.
QString readEntry(const QString &scope, const QString &key, const QString &def=QString(), bool *ok=nullptr) const
This class is a composition of two QSettings instances:
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
QgsSymbol * symbol() const
QVariant evaluate()
Evaluate the feature and return the result.
QgsWkbTypes::GeometryType geometryType() const
Returns point, line or polygon.
QSet< QVariant > uniqueValues(int fieldIndex, int limit=-1) const override
Calculates a list of unique values contained within an attribute in the layer.
static QgsExpressionContextScope * projectScope(const QgsProject *project)
Creates a new scope which contains variables and functions relating to a QGIS project.
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
A QProxyStyle subclass which correctly sets the base style to match the QGIS application style...
static QgsStyle * defaultStyle()
Returns default application-wide style.
The QgsMapSettings class contains configuration for rendering of the map.
QList< QgsRendererCategory > QgsCategoryList
static QgsSymbol * defaultSymbol(QgsWkbTypes::GeometryType geomType)
Returns new default symbol for specified geometry type.
static QgsExpressionContextScope * globalScope()
Creates a new scope which contains variables and functions relating to the global QGIS context...
static QIcon symbolPreviewIcon(QgsSymbol *symbol, QSize size, int padding=0)
Returns an icon preview for a color ramp.
QgsFields fields() const override
Returns the list of fields of this layer.
bool importXml(const QString &filename)
Imports the symbols and colorramps into the default style database from the given XML file...
const QgsCategoryList & categories() const
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
Single scope for storing variables and functions for use within a QgsExpressionContext.
bool renderState() const
Returns true if the category is currently enabled and should be rendered.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const override
Query the layer for features specified in request.
const QgsMapSettings & mapSettings() const
Gets access to properties used for map rendering.
static void sortVariantList(QList< QVariant > &list, Qt::SortOrder order)
Sorts the passed list in requested order.
QgsMapCanvas * mapCanvas() const
Returns the map canvas associated with the widget.
void setContext(const QgsSymbolWidgetContext &context)
Sets the context in which the symbol widget is shown, e.g., the associated map canvas and expression ...
static QgsExpressionContextScope * atlasScope(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...
QgsExpressionContextScope & expressionContextScope()
Returns a reference to the expression context scope for the map canvas.
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
bool prepare(const QgsExpressionContext *context)
Gets the expression ready for evaluation - find out column indexes.
virtual QgsSymbol * clone() const =0
Gets a deep copy of this symbol.
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
static QgsProject * instance()
Returns the QgsProject singleton instance.
static QgsCategorizedSymbolRenderer * convertFromRenderer(const QgsFeatureRenderer *renderer)
creates a QgsCategorizedSymbolRenderer from an existing renderer.
QString errorString()
Returns last error from load/save operation.
QgsSymbol * symbol(const QString &name)
Returns a NEW copy of symbol.
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
bool nextFeature(QgsFeature &f)
Represents a vector layer which manages a vector based data sets.
QList< QgsExpressionContextScope > additionalExpressionContextScopes() const
Returns the list of additional expression context scopes to show as available within the layer...
void setColor(const QColor &color)