32#include <QInputDialog>
36#include "moc_qgspointcloudclassifiedrendererwidget.cpp"
38using namespace Qt::StringLiterals;
42QgsPointCloudClassifiedRendererModel::QgsPointCloudClassifiedRendererModel( QObject *parent )
43 : QAbstractItemModel( parent )
44 , mMimeFormat( u
"application/x-qgspointcloudclassifiedrenderermodel"_s )
49 if ( !mCategories.empty() )
51 beginRemoveRows( QModelIndex(), 0, std::max<int>( mCategories.size() - 1, 0 ) );
55 if ( categories.size() > 0 )
57 beginInsertRows( QModelIndex(), 0, categories.size() - 1 );
58 mCategories = categories;
65 const int idx = mCategories.size();
66 beginInsertRows( QModelIndex(), idx, idx );
67 mCategories.append( cat );
70 emit categoriesChanged();
75 const int row = index.row();
76 if ( row >= mCategories.size() )
80 return mCategories.at( row );
83Qt::ItemFlags QgsPointCloudClassifiedRendererModel::flags(
const QModelIndex &index )
const
86 if ( !index.isValid() || mCategories.empty() )
88 return Qt::ItemIsDropEnabled;
91 Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsUserCheckable;
92 if ( index.column() == 1 || index.column() == 2 || index.column() == 3 )
94 flags |= Qt::ItemIsEditable;
99Qt::DropActions QgsPointCloudClassifiedRendererModel::supportedDropActions()
const
101 return Qt::MoveAction;
104QVariant QgsPointCloudClassifiedRendererModel::data(
const QModelIndex &index,
int role )
const
106 if ( !index.isValid() || mCategories.empty() )
113 case Qt::CheckStateRole:
115 if ( index.column() == 0 )
117 return category.
renderState() ? Qt::Checked : Qt::Unchecked;
122 case Qt::DisplayRole:
123 case Qt::ToolTipRole:
125 switch ( index.column() )
128 return category.
pointSize() > 0 ? QString::number( category.
pointSize() ) : QString();
130 return QString::number( category.
value() );
132 return category.
label();
134 const float value = mPercentages.value( category.
value(), -1 );
138 else if ( value != 0 && std::round( value * 10 ) < 1 )
139 str = u
"< "_s + QLocale().toString( 0.1,
'f', 1 );
141 str = QLocale().toString( mPercentages.value( category.
value() ),
'f', 1 );
147 case Qt::DecorationRole:
149 if ( index.column() == 0 )
152 QPixmap pix( iconSize, iconSize );
153 pix.fill( category.
color() );
159 case Qt::TextAlignmentRole:
161 if ( index.column() == 0 )
162 return static_cast<Qt::Alignment::Int
>( Qt::AlignHCenter );
163 if ( index.column() == 4 )
164 return static_cast<Qt::Alignment::Int
>( Qt::AlignRight );
165 return static_cast<Qt::Alignment::Int
>( Qt::AlignLeft );
170 switch ( index.column() )
173 return category.
pointSize() > 0 ? QString::number( category.
pointSize() ) : QString();
175 return QString::number( category.
value() );
177 return category.
label();
186bool QgsPointCloudClassifiedRendererModel::setData(
const QModelIndex &index,
const QVariant &value,
int role )
188 if ( !index.isValid() )
191 if ( index.column() == 0 && role == Qt::CheckStateRole )
193 mCategories[index.row()].setRenderState( value == Qt::Checked );
194 emit dataChanged( index, index );
195 emit categoriesChanged();
199 if ( role != Qt::EditRole )
202 switch ( index.column() )
206 const double size = value.toDouble();
207 mCategories[index.row()].setPointSize( size );
212 const int val = value.toInt();
213 mCategories[index.row()].setValue( val );
218 mCategories[index.row()].setLabel( value.toString() );
225 emit dataChanged( index, index );
226 emit categoriesChanged();
230QVariant QgsPointCloudClassifiedRendererModel::headerData(
int section, Qt::Orientation orientation,
int role )
const
232 if ( orientation == Qt::Horizontal && role == Qt::DisplayRole && section >= 0 && section < 5 )
235 lst << tr(
"Color" ) << tr(
"Size" ) << tr(
"Value" ) << tr(
"Legend" ) << tr(
"Percentage" );
236 return lst.value( section );
241int QgsPointCloudClassifiedRendererModel::rowCount(
const QModelIndex &parent )
const
243 if ( parent.isValid() )
247 return mCategories.size();
250int QgsPointCloudClassifiedRendererModel::columnCount(
const QModelIndex &index )
const
256QModelIndex QgsPointCloudClassifiedRendererModel::index(
int row,
int column,
const QModelIndex &parent )
const
258 if ( hasIndex( row, column, parent ) )
260 return createIndex( row, column );
262 return QModelIndex();
265QModelIndex QgsPointCloudClassifiedRendererModel::parent(
const QModelIndex &index )
const
268 return QModelIndex();
271QStringList QgsPointCloudClassifiedRendererModel::mimeTypes()
const
274 types << mMimeFormat;
278QMimeData *QgsPointCloudClassifiedRendererModel::mimeData(
const QModelIndexList &indexes )
const
280 QMimeData *mimeData =
new QMimeData();
281 QByteArray encodedData;
283 QDataStream stream( &encodedData, QIODevice::WriteOnly );
286 const auto constIndexes = indexes;
287 for (
const QModelIndex &index : constIndexes )
289 if ( !index.isValid() || index.column() != 0 )
292 stream << index.row();
294 mimeData->setData( mMimeFormat, encodedData );
298bool QgsPointCloudClassifiedRendererModel::dropMimeData(
const QMimeData *data, Qt::DropAction action,
int row,
int column,
const QModelIndex &parent )
302 if ( action != Qt::MoveAction )
305 if ( !data->hasFormat( mMimeFormat ) )
308 QByteArray encodedData = data->data( mMimeFormat );
309 QDataStream stream( &encodedData, QIODevice::ReadOnly );
312 while ( !stream.atEnd() )
320 std::sort( rows.begin(), rows.end() );
326 to = mCategories.size();
327 for (
int i = rows.size() - 1; i >= 0; i-- )
333 if ( !( rows[i] < 0 || rows[i] >= mCategories.size() || t < 0 || t >= mCategories.size() ) )
335 mCategories.move( rows[i], t );
339 for (
int j = 0; j < i; j++ )
341 if ( to < rows[j] && rows[i] > rows[j] )
348 emit dataChanged( createIndex( 0, 0 ), createIndex( mCategories.size(), 0 ) );
349 emit categoriesChanged();
354void QgsPointCloudClassifiedRendererModel::deleteRows( QList<int> rows )
356 std::sort( rows.begin(), rows.end() );
357 for (
int i = rows.size() - 1; i >= 0; i-- )
359 beginRemoveRows( QModelIndex(), rows[i], rows[i] );
360 mCategories.removeAt( rows[i] );
363 emit categoriesChanged();
366void QgsPointCloudClassifiedRendererModel::removeAllRows()
368 beginRemoveRows( QModelIndex(), 0, mCategories.size() - 1 );
371 emit categoriesChanged();
374void QgsPointCloudClassifiedRendererModel::setCategoryColor(
int row,
const QColor &color )
376 mCategories[row].setColor( color );
377 emit dataChanged( createIndex( row, 0 ), createIndex( row, 0 ) );
378 emit categoriesChanged();
381void QgsPointCloudClassifiedRendererModel::setCategoryPointSize(
int row,
double size )
383 mCategories[row].setPointSize( size );
384 emit dataChanged( createIndex( row, 0 ), createIndex( row, 0 ) );
385 emit categoriesChanged();
389QgsPointCloudClassifiedRendererViewStyle::QgsPointCloudClassifiedRendererViewStyle( QWidget *parent )
393void QgsPointCloudClassifiedRendererViewStyle::drawPrimitive( PrimitiveElement element,
const QStyleOption *option, QPainter *painter,
const QWidget *widget )
const
395 if ( element == QStyle::PE_IndicatorItemViewItemDrop && !option->rect.isNull() )
397 QStyleOption opt( *option );
398 opt.rect.setLeft( 0 );
400 opt.rect.setHeight( 0 );
402 opt.rect.setRight( widget->width() );
403 QProxyStyle::drawPrimitive( element, &opt, painter, widget );
406 QProxyStyle::drawPrimitive( element, option, painter, widget );
415 mAttributeComboBox->setAllowEmptyAttributeName(
true );
418 mModel =
new QgsPointCloudClassifiedRendererModel(
this );
422 mAttributeComboBox->setLayer( layer );
424 setFromRenderer( layer->
renderer() );
427 viewCategories->setModel( mModel );
428 viewCategories->resizeColumnToContents( 0 );
429 viewCategories->resizeColumnToContents( 1 );
430 viewCategories->resizeColumnToContents( 2 );
431 viewCategories->resizeColumnToContents( 3 );
433 viewCategories->setStyle(
new QgsPointCloudClassifiedRendererViewStyle( viewCategories ) );
436 connect( mModel, &QgsPointCloudClassifiedRendererModel::categoriesChanged,
this, &QgsPointCloudClassifiedRendererWidget::emitWidgetChanged );
437 connect( mModel, &QgsPointCloudClassifiedRendererModel::rowsMoved,
this, &QgsPointCloudClassifiedRendererWidget::rowsMoved );
439 connect( viewCategories, &QAbstractItemView::doubleClicked,
this, &QgsPointCloudClassifiedRendererWidget::categoriesDoubleClicked );
440 connect( btnAddCategories, &QAbstractButton::clicked,
this, &QgsPointCloudClassifiedRendererWidget::addCategories );
441 connect( btnDeleteCategories, &QAbstractButton::clicked,
this, &QgsPointCloudClassifiedRendererWidget::deleteCategories );
442 connect( btnDeleteAllCategories, &QAbstractButton::clicked,
this, &QgsPointCloudClassifiedRendererWidget::deleteAllCategories );
443 connect( btnAddCategory, &QAbstractButton::clicked,
this, &QgsPointCloudClassifiedRendererWidget::addCategory );
445 contextMenu =
new QMenu( tr(
"Options" ),
this );
446 contextMenu->addAction( tr(
"Change &Color…" ),
this, &QgsPointCloudClassifiedRendererWidget::changeCategoryColor );
447 contextMenu->addAction( tr(
"Change &Opacity…" ),
this, &QgsPointCloudClassifiedRendererWidget::changeCategoryOpacity );
448 contextMenu->addAction( tr(
"Change &Size…" ),
this, &QgsPointCloudClassifiedRendererWidget::changeCategoryPointSize );
450 viewCategories->setContextMenuPolicy( Qt::CustomContextMenu );
451 viewCategories->setSelectionMode( QAbstractItemView::ExtendedSelection );
452 connect( viewCategories, &QTreeView::customContextMenuRequested,
this, [
this]( QPoint ) { contextMenu->exec( QCursor::pos() ); } );
457 return new QgsPointCloudClassifiedRendererWidget( layer, style );
467 auto renderer = std::make_unique<QgsPointCloudClassifiedRenderer>();
468 renderer->setAttribute( mAttributeComboBox->currentAttribute() );
469 renderer->setCategories( mModel->categories() );
471 return renderer.release();
476 return mModel->categories();
479QString QgsPointCloudClassifiedRendererWidget::attribute()
const
481 return mAttributeComboBox->currentAttribute();
484void QgsPointCloudClassifiedRendererWidget::attributeChanged()
486 if ( mBlockChangedSignal )
489 mBlockChangedSignal =
true;
490 mModel->removeAllRows();
491 mBlockChangedSignal =
false;
495void QgsPointCloudClassifiedRendererWidget::emitWidgetChanged()
497 if ( mBlockChangedSignal )
500 updateCategoriesPercentages();
501 emit widgetChanged();
504void QgsPointCloudClassifiedRendererWidget::categoriesDoubleClicked(
const QModelIndex &idx )
506 if ( idx.isValid() && idx.column() == 0 )
507 changeCategoryColor();
510void QgsPointCloudClassifiedRendererWidget::addCategories()
512 if ( !mLayer || !mLayer->dataProvider() )
516 const QString currentAttribute = mAttributeComboBox->currentAttribute();
521 const bool isClassificationAttribute = ( 0 == currentAttribute.compare( u
"Classification"_s, Qt::CaseInsensitive ) );
522 const bool isBooleanAttribute = ( 0 == currentAttribute.compare( u
"Synthetic"_s, Qt::CaseInsensitive ) || 0 == currentAttribute.compare( u
"KeyPoint"_s, Qt::CaseInsensitive ) || 0 == currentAttribute.compare( u
"Withheld"_s, Qt::CaseInsensitive ) || 0 == currentAttribute.compare( u
"Overlap"_s, Qt::CaseInsensitive ) );
524 QList<int> providerCategories = stats.
classesOf( currentAttribute );
528 if ( isBooleanAttribute && ( providerCategories.isEmpty() || stats.
sampledPointsCount() < mLayer->pointCount() ) )
529 providerCategories = { 0, 1 };
533 mBlockChangedSignal =
true;
536 if ( isClassificationAttribute && providerCategories.isEmpty() )
538 for ( const QgsPointCloudCategory &c : defaultLayerCategories )
540 providerCategories.append( c.value() );
544 for (
const int &providerCategory : std::as_const( providerCategories ) )
550 if (
c.value() == providerCategory )
561 if ( isClassificationAttribute )
565 if (
c.value() == providerCategory )
573 mModel->addCategory( category );
575 mBlockChangedSignal =
false;
579void QgsPointCloudClassifiedRendererWidget::addCategory()
585 mModel->addCategory( cat );
588void QgsPointCloudClassifiedRendererWidget::deleteCategories()
590 const QList<int> categoryIndexes = selectedCategories();
591 mModel->deleteRows( categoryIndexes );
594void QgsPointCloudClassifiedRendererWidget::deleteAllCategories()
596 mModel->removeAllRows();
601 mBlockChangedSignal =
true;
604 mModel->setRendererCategories( classifiedRenderer->categories() );
605 mAttributeComboBox->setAttribute( classifiedRenderer->attribute() );
611 mBlockChangedSignal =
false;
615void QgsPointCloudClassifiedRendererWidget::setFromCategories(
QgsPointCloudCategoryList categories,
const QString &attribute )
617 mBlockChangedSignal =
true;
618 mModel->setRendererCategories( categories );
619 if ( !attribute.isEmpty() )
621 mAttributeComboBox->setAttribute( attribute );
627 mBlockChangedSignal =
false;
631void QgsPointCloudClassifiedRendererWidget::initialize()
633 if ( mAttributeComboBox->findText( u
"Classification"_s ) > -1 )
635 mAttributeComboBox->setAttribute( u
"Classification"_s );
639 mAttributeComboBox->setCurrentIndex( mAttributeComboBox->count() > 1 ? 1 : 0 );
641 mModel->removeAllRows();
645void QgsPointCloudClassifiedRendererWidget::changeCategoryColor()
647 const QList<int> categoryList = selectedCategories();
648 if ( categoryList.isEmpty() )
659 colorWidget->
setPanelTitle( categoryList.count() == 1 ? category.
label() : tr(
"Select Color" ) );
664 for (
int row : categoryList )
666 mModel->setCategoryColor( row, newColor );
674 if ( newColor.isValid() )
676 for (
int row : categoryList )
678 mModel->setCategoryColor( row, newColor );
684void QgsPointCloudClassifiedRendererWidget::changeCategoryOpacity()
686 const QList<int> categoryList = selectedCategories();
687 if ( categoryList.isEmpty() )
692 const double oldOpacity = mModel->categories().value( categoryList.first() ).color().alphaF() * 100.0;
695 const double opacity = QInputDialog::getDouble(
this, tr(
"Opacity" ), tr(
"Change symbol opacity [%]" ), oldOpacity, 0.0, 100.0, 1, &ok );
698 for (
int row : categoryList )
701 QColor color = category.
color();
702 color.setAlphaF( opacity / 100.0 );
703 mModel->setCategoryColor( row, color );
708void QgsPointCloudClassifiedRendererWidget::changeCategoryPointSize()
710 const QList<int> categoryList = selectedCategories();
711 if ( categoryList.isEmpty() )
716 const double oldSize = mModel->categories().value( categoryList.first() ).pointSize();
719 const double size = QInputDialog::getDouble(
this, tr(
"Point Size" ), tr(
"Change point size (set to 0 to reset to default point size)" ), oldSize, 0.0, 42.0, 1, &ok );
722 for (
int row : categoryList )
724 mModel->setCategoryPointSize( row, size );
729void QgsPointCloudClassifiedRendererWidget::rowsMoved()
731 viewCategories->selectionModel()->clear();
734QList<int> QgsPointCloudClassifiedRendererWidget::selectedCategories()
737 const QModelIndexList selectedRows = viewCategories->selectionModel()->selectedRows();
738 for (
const QModelIndex &r : selectedRows )
742 rows.append( r.row() );
748int QgsPointCloudClassifiedRendererWidget::currentCategoryRow()
750 const QModelIndex idx = viewCategories->selectionModel()->currentIndex();
751 if ( !idx.isValid() )
756void QgsPointCloudClassifiedRendererWidget::updateCategoriesPercentages()
758 QMap<int, float> percentages;
769 if ( classes.contains( category.
value() ) || statsExact )
770 percentages.insert( category.
value(), (
double ) classes.value( category.
value() ) / pointCount * 100 );
772 mModel->updateCategoriesPercentages( percentages );
static QgsColorSchemeRegistry * colorSchemeRegistry()
Returns the application's color scheme registry, used for managing color schemes.
static QColor getColor(const QColor &initialColor, QWidget *parent, const QString &title=QString(), bool allowOpacity=false)
Returns a color selection from a color dialog.
void attributeChanged(const QString &name)
Emitted when the currently selected attribute changes.
@ Char
Character attributes.
Represents an individual category (class) from a QgsPointCloudClassifiedRenderer.
int value() const
Returns the value corresponding to this category.
bool renderState() const
Returns true if the category is currently enabled and should be rendered.
QColor color() const
Returns the color which will be used to render this category.
double pointSize() const
Returns the point size for this category.
QString label() const
Returns the label for this category, which is used to represent the category within legends and the l...
Renders point clouds by a classification attribute.
Represents a map layer supporting display of point clouds.
QgsPointCloudRenderer * renderer()
Returns the 2D renderer for the point cloud.
static QgsPointCloudCategoryList classificationAttributeCategories(const QgsPointCloudLayer *layer)
Returns a list of categories using the available Classification classes of a specified layer,...
Abstract base class for 2d point cloud renderers.
Used to store statistics of a point cloud dataset.
QMap< int, int > availableClasses(const QString &attribute) const
Returns a map containing the count of each class of the attribute attribute If no matching statistic ...
QList< int > classesOf(const QString &attribute) const
Returns a list of existing classes which are present for the specified attribute.
int sampledPointsCount() const
Returns the number of points used to calculate the statistics.
A QProxyStyle subclass which correctly sets the base style to match the QGIS application style,...
A database of saved style entities, including symbols, color ramps, text formats and others.
QSize iconSize(bool dockableToolbar)
Returns the user-preferred size of a window's toolbar icons.
int scaleIconSize(int standardSize)
Scales an icon size to compensate for display pixel density, making the icon size hi-dpi friendly,...
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
QList< QgsPointCloudCategory > QgsPointCloudCategoryList