32#include <QInputDialog>
35#include "moc_qgspointcloudclassifiedrendererwidget.cpp"
39QgsPointCloudClassifiedRendererModel::QgsPointCloudClassifiedRendererModel( QObject *parent )
40 : QAbstractItemModel( parent )
41 , mMimeFormat( QStringLiteral(
"application/x-qgspointcloudclassifiedrenderermodel" ) )
47 if ( !mCategories.empty() )
49 beginRemoveRows( QModelIndex(), 0, std::max<int>( mCategories.size() - 1, 0 ) );
53 if ( categories.size() > 0 )
55 beginInsertRows( QModelIndex(), 0, categories.size() - 1 );
56 mCategories = categories;
63 const int idx = mCategories.size();
64 beginInsertRows( QModelIndex(), idx, idx );
65 mCategories.append( cat );
68 emit categoriesChanged();
73 const int row = index.row();
74 if ( row >= mCategories.size() )
78 return mCategories.at( row );
81Qt::ItemFlags QgsPointCloudClassifiedRendererModel::flags(
const QModelIndex &index )
const
84 if ( !index.isValid() || mCategories.empty() )
86 return Qt::ItemIsDropEnabled;
89 Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsUserCheckable;
90 if ( index.column() == 1 || index.column() == 2 || index.column() == 3 )
92 flags |= Qt::ItemIsEditable;
97Qt::DropActions QgsPointCloudClassifiedRendererModel::supportedDropActions()
const
99 return Qt::MoveAction;
102QVariant QgsPointCloudClassifiedRendererModel::data(
const QModelIndex &index,
int role )
const
104 if ( !index.isValid() || mCategories.empty() )
111 case Qt::CheckStateRole:
113 if ( index.column() == 0 )
115 return category.
renderState() ? Qt::Checked : Qt::Unchecked;
120 case Qt::DisplayRole:
121 case Qt::ToolTipRole:
123 switch ( index.column() )
126 return category.
pointSize() > 0 ? QString::number( category.
pointSize() ) : QString();
128 return QString::number( category.
value() );
130 return category.
label();
132 const float value = mPercentages.value( category.
value(), -1 );
136 else if ( value != 0 && std::round( value * 10 ) < 1 )
137 str = QStringLiteral(
"< " ) + QLocale().toString( 0.1,
'f', 1 );
139 str = QLocale().toString( mPercentages.value( category.
value() ),
'f', 1 );
145 case Qt::DecorationRole:
147 if ( index.column() == 0 )
150 QPixmap pix( iconSize, iconSize );
151 pix.fill( category.
color() );
157 case Qt::TextAlignmentRole:
159 if ( index.column() == 0 )
160 return static_cast<Qt::Alignment::Int
>( Qt::AlignHCenter );
161 if ( index.column() == 4 )
162 return static_cast<Qt::Alignment::Int
>( Qt::AlignRight );
163 return static_cast<Qt::Alignment::Int
>( Qt::AlignLeft );
168 switch ( index.column() )
171 return category.
pointSize() > 0 ? QString::number( category.
pointSize() ) : QString();
173 return QString::number( category.
value() );
175 return category.
label();
184bool QgsPointCloudClassifiedRendererModel::setData(
const QModelIndex &index,
const QVariant &value,
int role )
186 if ( !index.isValid() )
189 if ( index.column() == 0 && role == Qt::CheckStateRole )
191 mCategories[index.row()].setRenderState( value == Qt::Checked );
192 emit dataChanged( index, index );
193 emit categoriesChanged();
197 if ( role != Qt::EditRole )
200 switch ( index.column() )
204 const double size = value.toDouble();
205 mCategories[index.row()].setPointSize( size );
210 const int val = value.toInt();
211 mCategories[index.row()].setValue( val );
216 mCategories[index.row()].setLabel( value.toString() );
223 emit dataChanged( index, index );
224 emit categoriesChanged();
228QVariant QgsPointCloudClassifiedRendererModel::headerData(
int section, Qt::Orientation orientation,
int role )
const
230 if ( orientation == Qt::Horizontal && role == Qt::DisplayRole && section >= 0 && section < 5 )
233 lst << tr(
"Color" ) << tr(
"Size" ) << tr(
"Value" ) << tr(
"Legend" ) << tr(
"Percentage" );
234 return lst.value( section );
239int QgsPointCloudClassifiedRendererModel::rowCount(
const QModelIndex &parent )
const
241 if ( parent.isValid() )
245 return mCategories.size();
248int QgsPointCloudClassifiedRendererModel::columnCount(
const QModelIndex &index )
const
254QModelIndex QgsPointCloudClassifiedRendererModel::index(
int row,
int column,
const QModelIndex &parent )
const
256 if ( hasIndex( row, column, parent ) )
258 return createIndex( row, column );
260 return QModelIndex();
263QModelIndex QgsPointCloudClassifiedRendererModel::parent(
const QModelIndex &index )
const
266 return QModelIndex();
269QStringList QgsPointCloudClassifiedRendererModel::mimeTypes()
const
272 types << mMimeFormat;
276QMimeData *QgsPointCloudClassifiedRendererModel::mimeData(
const QModelIndexList &indexes )
const
278 QMimeData *mimeData =
new QMimeData();
279 QByteArray encodedData;
281 QDataStream stream( &encodedData, QIODevice::WriteOnly );
284 const auto constIndexes = indexes;
285 for (
const QModelIndex &index : constIndexes )
287 if ( !index.isValid() || index.column() != 0 )
290 stream << index.row();
292 mimeData->setData( mMimeFormat, encodedData );
296bool QgsPointCloudClassifiedRendererModel::dropMimeData(
const QMimeData *data, Qt::DropAction action,
int row,
int column,
const QModelIndex &parent )
300 if ( action != Qt::MoveAction )
303 if ( !data->hasFormat( mMimeFormat ) )
306 QByteArray encodedData = data->data( mMimeFormat );
307 QDataStream stream( &encodedData, QIODevice::ReadOnly );
310 while ( !stream.atEnd() )
318 std::sort( rows.begin(), rows.end() );
324 to = mCategories.size();
325 for (
int i = rows.size() - 1; i >= 0; i-- )
331 if ( !( rows[i] < 0 || rows[i] >= mCategories.size() || t < 0 || t >= mCategories.size() ) )
333 mCategories.move( rows[i], t );
337 for (
int j = 0; j < i; j++ )
339 if ( to < rows[j] && rows[i] > rows[j] )
346 emit dataChanged( createIndex( 0, 0 ), createIndex( mCategories.size(), 0 ) );
347 emit categoriesChanged();
352void QgsPointCloudClassifiedRendererModel::deleteRows( QList<int> rows )
354 std::sort( rows.begin(), rows.end() );
355 for (
int i = rows.size() - 1; i >= 0; i-- )
357 beginRemoveRows( QModelIndex(), rows[i], rows[i] );
358 mCategories.removeAt( rows[i] );
361 emit categoriesChanged();
364void QgsPointCloudClassifiedRendererModel::removeAllRows()
366 beginRemoveRows( QModelIndex(), 0, mCategories.size() - 1 );
369 emit categoriesChanged();
372void QgsPointCloudClassifiedRendererModel::setCategoryColor(
int row,
const QColor &color )
374 mCategories[row].setColor( color );
375 emit dataChanged( createIndex( row, 0 ), createIndex( row, 0 ) );
376 emit categoriesChanged();
379void QgsPointCloudClassifiedRendererModel::setCategoryPointSize(
int row,
double size )
381 mCategories[row].setPointSize( size );
382 emit dataChanged( createIndex( row, 0 ), createIndex( row, 0 ) );
383 emit categoriesChanged();
387QgsPointCloudClassifiedRendererViewStyle::QgsPointCloudClassifiedRendererViewStyle( QWidget *parent )
391void QgsPointCloudClassifiedRendererViewStyle::drawPrimitive( PrimitiveElement element,
const QStyleOption *option, QPainter *painter,
const QWidget *widget )
const
393 if ( element == QStyle::PE_IndicatorItemViewItemDrop && !option->rect.isNull() )
395 QStyleOption opt( *option );
396 opt.rect.setLeft( 0 );
398 opt.rect.setHeight( 0 );
400 opt.rect.setRight( widget->width() );
401 QProxyStyle::drawPrimitive( element, &opt, painter, widget );
404 QProxyStyle::drawPrimitive( element, option, painter, widget );
413 mAttributeComboBox->setAllowEmptyAttributeName(
true );
416 mModel =
new QgsPointCloudClassifiedRendererModel(
this );
420 mAttributeComboBox->setLayer( layer );
422 setFromRenderer( layer->
renderer() );
425 viewCategories->setModel( mModel );
426 viewCategories->resizeColumnToContents( 0 );
427 viewCategories->resizeColumnToContents( 1 );
428 viewCategories->resizeColumnToContents( 2 );
429 viewCategories->resizeColumnToContents( 3 );
431 viewCategories->setStyle(
new QgsPointCloudClassifiedRendererViewStyle( viewCategories ) );
434 connect( mModel, &QgsPointCloudClassifiedRendererModel::categoriesChanged,
this, &QgsPointCloudClassifiedRendererWidget::emitWidgetChanged );
435 connect( mModel, &QgsPointCloudClassifiedRendererModel::rowsMoved,
this, &QgsPointCloudClassifiedRendererWidget::rowsMoved );
437 connect( viewCategories, &QAbstractItemView::doubleClicked,
this, &QgsPointCloudClassifiedRendererWidget::categoriesDoubleClicked );
438 connect( btnAddCategories, &QAbstractButton::clicked,
this, &QgsPointCloudClassifiedRendererWidget::addCategories );
439 connect( btnDeleteCategories, &QAbstractButton::clicked,
this, &QgsPointCloudClassifiedRendererWidget::deleteCategories );
440 connect( btnDeleteAllCategories, &QAbstractButton::clicked,
this, &QgsPointCloudClassifiedRendererWidget::deleteAllCategories );
441 connect( btnAddCategory, &QAbstractButton::clicked,
this, &QgsPointCloudClassifiedRendererWidget::addCategory );
443 contextMenu =
new QMenu( tr(
"Options" ),
this );
444 contextMenu->addAction( tr(
"Change &Color…" ),
this, &QgsPointCloudClassifiedRendererWidget::changeCategoryColor );
445 contextMenu->addAction( tr(
"Change &Opacity…" ),
this, &QgsPointCloudClassifiedRendererWidget::changeCategoryOpacity );
446 contextMenu->addAction( tr(
"Change &Size…" ),
this, &QgsPointCloudClassifiedRendererWidget::changeCategoryPointSize );
448 viewCategories->setContextMenuPolicy( Qt::CustomContextMenu );
449 viewCategories->setSelectionMode( QAbstractItemView::ExtendedSelection );
450 connect( viewCategories, &QTreeView::customContextMenuRequested,
this, [
this]( QPoint ) { contextMenu->exec( QCursor::pos() ); } );
455 return new QgsPointCloudClassifiedRendererWidget( layer, style );
465 auto renderer = std::make_unique<QgsPointCloudClassifiedRenderer>();
466 renderer->setAttribute( mAttributeComboBox->currentAttribute() );
467 renderer->setCategories( mModel->categories() );
469 return renderer.release();
474 return mModel->categories();
477QString QgsPointCloudClassifiedRendererWidget::attribute()
const
479 return mAttributeComboBox->currentAttribute();
482void QgsPointCloudClassifiedRendererWidget::attributeChanged()
484 if ( mBlockChangedSignal )
487 mBlockChangedSignal =
true;
488 mModel->removeAllRows();
489 mBlockChangedSignal =
false;
493void QgsPointCloudClassifiedRendererWidget::emitWidgetChanged()
495 if ( mBlockChangedSignal )
498 updateCategoriesPercentages();
499 emit widgetChanged();
502void QgsPointCloudClassifiedRendererWidget::categoriesDoubleClicked(
const QModelIndex &idx )
504 if ( idx.isValid() && idx.column() == 0 )
505 changeCategoryColor();
508void QgsPointCloudClassifiedRendererWidget::addCategories()
510 if ( !mLayer || !mLayer->dataProvider() )
514 const QString currentAttribute = mAttributeComboBox->currentAttribute();
519 const bool isClassificationAttribute = ( 0 == currentAttribute.compare( QStringLiteral(
"Classification" ), Qt::CaseInsensitive ) );
520 const bool isBooleanAttribute = ( 0 == currentAttribute.compare( QStringLiteral(
"Synthetic" ), Qt::CaseInsensitive ) || 0 == currentAttribute.compare( QStringLiteral(
"KeyPoint" ), Qt::CaseInsensitive ) || 0 == currentAttribute.compare( QStringLiteral(
"Withheld" ), Qt::CaseInsensitive ) || 0 == currentAttribute.compare( QStringLiteral(
"Overlap" ), Qt::CaseInsensitive ) );
522 QList<int> providerCategories = stats.
classesOf( currentAttribute );
526 if ( isBooleanAttribute && ( providerCategories.isEmpty() || stats.
sampledPointsCount() < mLayer->pointCount() ) )
527 providerCategories = { 0, 1 };
531 mBlockChangedSignal =
true;
532 for (
const int &providerCategory : std::as_const( providerCategories ) )
538 if (
c.value() == providerCategory )
549 if ( isClassificationAttribute )
553 if (
c.value() == providerCategory )
561 mModel->addCategory( category );
563 mBlockChangedSignal =
false;
567void QgsPointCloudClassifiedRendererWidget::addCategory()
573 mModel->addCategory( cat );
576void QgsPointCloudClassifiedRendererWidget::deleteCategories()
578 const QList<int> categoryIndexes = selectedCategories();
579 mModel->deleteRows( categoryIndexes );
582void QgsPointCloudClassifiedRendererWidget::deleteAllCategories()
584 mModel->removeAllRows();
589 mBlockChangedSignal =
true;
592 mModel->setRendererCategories( classifiedRenderer->categories() );
593 mAttributeComboBox->setAttribute( classifiedRenderer->attribute() );
599 mBlockChangedSignal =
false;
603void QgsPointCloudClassifiedRendererWidget::setFromCategories(
QgsPointCloudCategoryList categories,
const QString &attribute )
605 mBlockChangedSignal =
true;
606 mModel->setRendererCategories( categories );
607 if ( !attribute.isEmpty() )
609 mAttributeComboBox->setAttribute( attribute );
615 mBlockChangedSignal =
false;
619void QgsPointCloudClassifiedRendererWidget::initialize()
621 if ( mAttributeComboBox->findText( QStringLiteral(
"Classification" ) ) > -1 )
623 mAttributeComboBox->setAttribute( QStringLiteral(
"Classification" ) );
627 mAttributeComboBox->setCurrentIndex( mAttributeComboBox->count() > 1 ? 1 : 0 );
629 mModel->removeAllRows();
633void QgsPointCloudClassifiedRendererWidget::changeCategoryColor()
635 const QList<int> categoryList = selectedCategories();
636 if ( categoryList.isEmpty() )
647 colorWidget->
setPanelTitle( categoryList.count() == 1 ? category.
label() : tr(
"Select Color" ) );
652 for (
int row : categoryList )
654 mModel->setCategoryColor( row, newColor );
662 if ( newColor.isValid() )
664 for (
int row : categoryList )
666 mModel->setCategoryColor( row, newColor );
672void QgsPointCloudClassifiedRendererWidget::changeCategoryOpacity()
674 const QList<int> categoryList = selectedCategories();
675 if ( categoryList.isEmpty() )
680 const double oldOpacity = mModel->categories().value( categoryList.first() ).color().alphaF() * 100.0;
683 const double opacity = QInputDialog::getDouble(
this, tr(
"Opacity" ), tr(
"Change symbol opacity [%]" ), oldOpacity, 0.0, 100.0, 1, &ok );
686 for (
int row : categoryList )
689 QColor color = category.
color();
690 color.setAlphaF( opacity / 100.0 );
691 mModel->setCategoryColor( row, color );
696void QgsPointCloudClassifiedRendererWidget::changeCategoryPointSize()
698 const QList<int> categoryList = selectedCategories();
699 if ( categoryList.isEmpty() )
704 const double oldSize = mModel->categories().value( categoryList.first() ).pointSize();
707 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 );
710 for (
int row : categoryList )
712 mModel->setCategoryPointSize( row, size );
717void QgsPointCloudClassifiedRendererWidget::rowsMoved()
719 viewCategories->selectionModel()->clear();
722QList<int> QgsPointCloudClassifiedRendererWidget::selectedCategories()
725 const QModelIndexList selectedRows = viewCategories->selectionModel()->selectedRows();
726 for (
const QModelIndex &r : selectedRows )
730 rows.append( r.row() );
736int QgsPointCloudClassifiedRendererWidget::currentCategoryRow()
738 const QModelIndex idx = viewCategories->selectionModel()->currentIndex();
739 if ( !idx.isValid() )
744void QgsPointCloudClassifiedRendererWidget::updateCategoriesPercentages()
746 QMap<int, float> percentages;
757 if ( classes.contains( category.
value() ) || statsExact )
758 percentages.insert( category.
value(), (
double ) classes.value( category.
value() ) / pointCount * 100 );
760 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