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 )
50 if ( !mCategories.empty() )
52 beginRemoveRows( QModelIndex(), 0, std::max<int>( mCategories.size() - 1, 0 ) );
56 if ( categories.size() > 0 )
58 beginInsertRows( QModelIndex(), 0, categories.size() - 1 );
59 mCategories = categories;
66 const int idx = mCategories.size();
67 beginInsertRows( QModelIndex(), idx, idx );
68 mCategories.append( cat );
71 emit categoriesChanged();
76 const int row = index.row();
77 if ( row >= mCategories.size() )
81 return mCategories.at( row );
84Qt::ItemFlags QgsPointCloudClassifiedRendererModel::flags(
const QModelIndex &index )
const
87 if ( !index.isValid() || mCategories.empty() )
89 return Qt::ItemIsDropEnabled;
92 Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsUserCheckable;
93 if ( index.column() == 1 || index.column() == 2 || index.column() == 3 )
95 flags |= Qt::ItemIsEditable;
100Qt::DropActions QgsPointCloudClassifiedRendererModel::supportedDropActions()
const
102 return Qt::MoveAction;
105QVariant QgsPointCloudClassifiedRendererModel::data(
const QModelIndex &index,
int role )
const
107 if ( !index.isValid() || mCategories.empty() )
114 case Qt::CheckStateRole:
116 if ( index.column() == 0 )
118 return category.
renderState() ? Qt::Checked : Qt::Unchecked;
123 case Qt::DisplayRole:
124 case Qt::ToolTipRole:
126 switch ( index.column() )
129 return category.
pointSize() > 0 ? QString::number( category.
pointSize() ) : QString();
131 return QString::number( category.
value() );
133 return category.
label();
135 const float value = mPercentages.value( category.
value(), -1 );
139 else if ( value != 0 && std::round( value * 10 ) < 1 )
140 str = u
"< "_s + QLocale().toString( 0.1,
'f', 1 );
142 str = QLocale().toString( mPercentages.value( category.
value() ),
'f', 1 );
148 case Qt::DecorationRole:
150 if ( index.column() == 0 )
153 QPixmap pix( iconSize, iconSize );
154 pix.fill( category.
color() );
160 case Qt::TextAlignmentRole:
162 if ( index.column() == 0 )
163 return static_cast<Qt::Alignment::Int
>( Qt::AlignHCenter );
164 if ( index.column() == 4 )
165 return static_cast<Qt::Alignment::Int
>( Qt::AlignRight );
166 return static_cast<Qt::Alignment::Int
>( Qt::AlignLeft );
171 switch ( index.column() )
174 return category.
pointSize() > 0 ? QString::number( category.
pointSize() ) : QString();
176 return QString::number( category.
value() );
178 return category.
label();
187bool QgsPointCloudClassifiedRendererModel::setData(
const QModelIndex &index,
const QVariant &value,
int role )
189 if ( !index.isValid() )
192 if ( index.column() == 0 && role == Qt::CheckStateRole )
194 mCategories[index.row()].setRenderState( value == Qt::Checked );
195 emit dataChanged( index, index );
196 emit categoriesChanged();
200 if ( role != Qt::EditRole )
203 switch ( index.column() )
207 const double size = value.toDouble();
208 mCategories[index.row()].setPointSize( size );
213 const int val = value.toInt();
214 mCategories[index.row()].setValue( val );
219 mCategories[index.row()].setLabel( value.toString() );
226 emit dataChanged( index, index );
227 emit categoriesChanged();
231QVariant QgsPointCloudClassifiedRendererModel::headerData(
int section, Qt::Orientation orientation,
int role )
const
233 if ( orientation == Qt::Horizontal && role == Qt::DisplayRole && section >= 0 && section < 5 )
236 lst << tr(
"Color" ) << tr(
"Size" ) << tr(
"Value" ) << tr(
"Legend" ) << tr(
"Percentage" );
237 return lst.value( section );
242int QgsPointCloudClassifiedRendererModel::rowCount(
const QModelIndex &parent )
const
244 if ( parent.isValid() )
248 return mCategories.size();
251int QgsPointCloudClassifiedRendererModel::columnCount(
const QModelIndex &index )
const
257QModelIndex QgsPointCloudClassifiedRendererModel::index(
int row,
int column,
const QModelIndex &parent )
const
259 if ( hasIndex( row, column, parent ) )
261 return createIndex( row, column );
263 return QModelIndex();
266QModelIndex QgsPointCloudClassifiedRendererModel::parent(
const QModelIndex &index )
const
269 return QModelIndex();
272QStringList QgsPointCloudClassifiedRendererModel::mimeTypes()
const
275 types << mMimeFormat;
279QMimeData *QgsPointCloudClassifiedRendererModel::mimeData(
const QModelIndexList &indexes )
const
281 QMimeData *mimeData =
new QMimeData();
282 QByteArray encodedData;
284 QDataStream stream( &encodedData, QIODevice::WriteOnly );
287 const auto constIndexes = indexes;
288 for (
const QModelIndex &index : constIndexes )
290 if ( !index.isValid() || index.column() != 0 )
293 stream << index.row();
295 mimeData->setData( mMimeFormat, encodedData );
299bool QgsPointCloudClassifiedRendererModel::dropMimeData(
const QMimeData *data, Qt::DropAction action,
int row,
int column,
const QModelIndex &parent )
303 if ( action != Qt::MoveAction )
306 if ( !data->hasFormat( mMimeFormat ) )
309 QByteArray encodedData = data->data( mMimeFormat );
310 QDataStream stream( &encodedData, QIODevice::ReadOnly );
313 while ( !stream.atEnd() )
321 std::sort( rows.begin(), rows.end() );
327 to = mCategories.size();
328 for (
int i = rows.size() - 1; i >= 0; i-- )
334 if ( !( rows[i] < 0 || rows[i] >= mCategories.size() || t < 0 || t >= mCategories.size() ) )
336 mCategories.move( rows[i], t );
340 for (
int j = 0; j < i; j++ )
342 if ( to < rows[j] && rows[i] > rows[j] )
349 emit dataChanged( createIndex( 0, 0 ), createIndex( mCategories.size(), 0 ) );
350 emit categoriesChanged();
355void QgsPointCloudClassifiedRendererModel::deleteRows( QList<int> rows )
357 std::sort( rows.begin(), rows.end() );
358 for (
int i = rows.size() - 1; i >= 0; i-- )
360 beginRemoveRows( QModelIndex(), rows[i], rows[i] );
361 mCategories.removeAt( rows[i] );
364 emit categoriesChanged();
367void QgsPointCloudClassifiedRendererModel::removeAllRows()
369 beginRemoveRows( QModelIndex(), 0, mCategories.size() - 1 );
372 emit categoriesChanged();
375void QgsPointCloudClassifiedRendererModel::setCategoryColor(
int row,
const QColor &color )
377 mCategories[row].setColor( color );
378 emit dataChanged( createIndex( row, 0 ), createIndex( row, 0 ) );
379 emit categoriesChanged();
382void QgsPointCloudClassifiedRendererModel::setCategoryPointSize(
int row,
double size )
384 mCategories[row].setPointSize( size );
385 emit dataChanged( createIndex( row, 0 ), createIndex( row, 0 ) );
386 emit categoriesChanged();
390QgsPointCloudClassifiedRendererViewStyle::QgsPointCloudClassifiedRendererViewStyle( QWidget *parent )
394void QgsPointCloudClassifiedRendererViewStyle::drawPrimitive( PrimitiveElement element,
const QStyleOption *option, QPainter *painter,
const QWidget *widget )
const
396 if ( element == QStyle::PE_IndicatorItemViewItemDrop && !option->rect.isNull() )
398 QStyleOption opt( *option );
399 opt.rect.setLeft( 0 );
401 opt.rect.setHeight( 0 );
403 opt.rect.setRight( widget->width() );
404 QProxyStyle::drawPrimitive( element, &opt, painter, widget );
407 QProxyStyle::drawPrimitive( element, option, painter, widget );
416 mAttributeComboBox->setAllowEmptyAttributeName(
true );
419 mModel =
new QgsPointCloudClassifiedRendererModel(
this );
423 mAttributeComboBox->setLayer( layer );
425 setFromRenderer( layer->
renderer() );
428 viewCategories->setModel( mModel );
429 viewCategories->resizeColumnToContents( 0 );
430 viewCategories->resizeColumnToContents( 1 );
431 viewCategories->resizeColumnToContents( 2 );
432 viewCategories->resizeColumnToContents( 3 );
434 viewCategories->setStyle(
new QgsPointCloudClassifiedRendererViewStyle( viewCategories ) );
437 connect( mModel, &QgsPointCloudClassifiedRendererModel::categoriesChanged,
this, &QgsPointCloudClassifiedRendererWidget::emitWidgetChanged );
438 connect( mModel, &QgsPointCloudClassifiedRendererModel::rowsMoved,
this, &QgsPointCloudClassifiedRendererWidget::rowsMoved );
440 connect( viewCategories, &QAbstractItemView::doubleClicked,
this, &QgsPointCloudClassifiedRendererWidget::categoriesDoubleClicked );
441 connect( btnAddCategories, &QAbstractButton::clicked,
this, &QgsPointCloudClassifiedRendererWidget::addCategories );
442 connect( btnDeleteCategories, &QAbstractButton::clicked,
this, &QgsPointCloudClassifiedRendererWidget::deleteCategories );
443 connect( btnDeleteAllCategories, &QAbstractButton::clicked,
this, &QgsPointCloudClassifiedRendererWidget::deleteAllCategories );
444 connect( btnAddCategory, &QAbstractButton::clicked,
this, &QgsPointCloudClassifiedRendererWidget::addCategory );
446 contextMenu =
new QMenu( tr(
"Options" ),
this );
447 contextMenu->addAction( tr(
"Change &Color…" ),
this, &QgsPointCloudClassifiedRendererWidget::changeCategoryColor );
448 contextMenu->addAction( tr(
"Change &Opacity…" ),
this, &QgsPointCloudClassifiedRendererWidget::changeCategoryOpacity );
449 contextMenu->addAction( tr(
"Change &Size…" ),
this, &QgsPointCloudClassifiedRendererWidget::changeCategoryPointSize );
451 viewCategories->setContextMenuPolicy( Qt::CustomContextMenu );
452 viewCategories->setSelectionMode( QAbstractItemView::ExtendedSelection );
453 connect( viewCategories, &QTreeView::customContextMenuRequested,
this, [
this]( QPoint ) { contextMenu->exec( QCursor::pos() ); } );
458 return new QgsPointCloudClassifiedRendererWidget( layer, style );
468 auto renderer = std::make_unique<QgsPointCloudClassifiedRenderer>();
469 renderer->setAttribute( mAttributeComboBox->currentAttribute() );
470 renderer->setCategories( mModel->categories() );
472 return renderer.release();
477 return mModel->categories();
480QString QgsPointCloudClassifiedRendererWidget::attribute()
const
482 return mAttributeComboBox->currentAttribute();
485void QgsPointCloudClassifiedRendererWidget::attributeChanged()
487 if ( mBlockChangedSignal )
490 mBlockChangedSignal =
true;
491 mModel->removeAllRows();
492 mBlockChangedSignal =
false;
496void QgsPointCloudClassifiedRendererWidget::emitWidgetChanged()
498 if ( mBlockChangedSignal )
501 updateCategoriesPercentages();
502 emit widgetChanged();
505void QgsPointCloudClassifiedRendererWidget::categoriesDoubleClicked(
const QModelIndex &idx )
507 if ( idx.isValid() && idx.column() == 0 )
508 changeCategoryColor();
511void QgsPointCloudClassifiedRendererWidget::addCategories()
513 if ( !mLayer || !mLayer->dataProvider() )
517 const QString currentAttribute = mAttributeComboBox->currentAttribute();
522 const bool isClassificationAttribute = ( 0 == currentAttribute.compare( u
"Classification"_s, Qt::CaseInsensitive ) );
523 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 ) );
525 QList<int> providerCategories = stats.
classesOf( currentAttribute );
529 if ( isBooleanAttribute && ( providerCategories.isEmpty() || stats.
sampledPointsCount() < mLayer->pointCount() ) )
530 providerCategories = { 0, 1 };
534 mBlockChangedSignal =
true;
535 for (
const int &providerCategory : std::as_const( providerCategories ) )
541 if (
c.value() == providerCategory )
552 if ( isClassificationAttribute )
556 if (
c.value() == providerCategory )
564 mModel->addCategory( category );
566 mBlockChangedSignal =
false;
570void QgsPointCloudClassifiedRendererWidget::addCategory()
576 mModel->addCategory( cat );
579void QgsPointCloudClassifiedRendererWidget::deleteCategories()
581 const QList<int> categoryIndexes = selectedCategories();
582 mModel->deleteRows( categoryIndexes );
585void QgsPointCloudClassifiedRendererWidget::deleteAllCategories()
587 mModel->removeAllRows();
592 mBlockChangedSignal =
true;
595 mModel->setRendererCategories( classifiedRenderer->categories() );
596 mAttributeComboBox->setAttribute( classifiedRenderer->attribute() );
602 mBlockChangedSignal =
false;
606void QgsPointCloudClassifiedRendererWidget::setFromCategories(
QgsPointCloudCategoryList categories,
const QString &attribute )
608 mBlockChangedSignal =
true;
609 mModel->setRendererCategories( categories );
610 if ( !attribute.isEmpty() )
612 mAttributeComboBox->setAttribute( attribute );
618 mBlockChangedSignal =
false;
622void QgsPointCloudClassifiedRendererWidget::initialize()
624 if ( mAttributeComboBox->findText( u
"Classification"_s ) > -1 )
626 mAttributeComboBox->setAttribute( u
"Classification"_s );
630 mAttributeComboBox->setCurrentIndex( mAttributeComboBox->count() > 1 ? 1 : 0 );
632 mModel->removeAllRows();
636void QgsPointCloudClassifiedRendererWidget::changeCategoryColor()
638 const QList<int> categoryList = selectedCategories();
639 if ( categoryList.isEmpty() )
650 colorWidget->
setPanelTitle( categoryList.count() == 1 ? category.
label() : tr(
"Select Color" ) );
655 for (
int row : categoryList )
657 mModel->setCategoryColor( row, newColor );
665 if ( newColor.isValid() )
667 for (
int row : categoryList )
669 mModel->setCategoryColor( row, newColor );
675void QgsPointCloudClassifiedRendererWidget::changeCategoryOpacity()
677 const QList<int> categoryList = selectedCategories();
678 if ( categoryList.isEmpty() )
683 const double oldOpacity = mModel->categories().value( categoryList.first() ).color().alphaF() * 100.0;
686 const double opacity = QInputDialog::getDouble(
this, tr(
"Opacity" ), tr(
"Change symbol opacity [%]" ), oldOpacity, 0.0, 100.0, 1, &ok );
689 for (
int row : categoryList )
692 QColor color = category.
color();
693 color.setAlphaF( opacity / 100.0 );
694 mModel->setCategoryColor( row, color );
699void QgsPointCloudClassifiedRendererWidget::changeCategoryPointSize()
701 const QList<int> categoryList = selectedCategories();
702 if ( categoryList.isEmpty() )
707 const double oldSize = mModel->categories().value( categoryList.first() ).pointSize();
710 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 );
713 for (
int row : categoryList )
715 mModel->setCategoryPointSize( row, size );
720void QgsPointCloudClassifiedRendererWidget::rowsMoved()
722 viewCategories->selectionModel()->clear();
725QList<int> QgsPointCloudClassifiedRendererWidget::selectedCategories()
728 const QModelIndexList selectedRows = viewCategories->selectionModel()->selectedRows();
729 for (
const QModelIndex &r : selectedRows )
733 rows.append( r.row() );
739int QgsPointCloudClassifiedRendererWidget::currentCategoryRow()
741 const QModelIndex idx = viewCategories->selectionModel()->currentIndex();
742 if ( !idx.isValid() )
747void QgsPointCloudClassifiedRendererWidget::updateCategoriesPercentages()
749 QMap<int, float> percentages;
760 if ( classes.contains( category.
value() ) || statsExact )
761 percentages.insert( category.
value(), (
double ) classes.value( category.
value() ) / pointCount * 100 );
763 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