32#include <QInputDialog> 
   36QgsPointCloudClassifiedRendererModel::QgsPointCloudClassifiedRendererModel( QObject *parent )
 
   37  : QAbstractItemModel( parent )
 
   38  , mMimeFormat( QStringLiteral( 
"application/x-qgspointcloudclassifiedrenderermodel" ) )
 
   44  if ( !mCategories.empty() )
 
   46    beginRemoveRows( QModelIndex(), 0, std::max< int >( mCategories.size() - 1, 0 ) );
 
   50  if ( categories.size() > 0 )
 
   52    beginInsertRows( QModelIndex(), 0, categories.size() - 1 );
 
   53    mCategories = categories;
 
   60  const int idx = mCategories.size();
 
   61  beginInsertRows( QModelIndex(), idx, idx );
 
   62  mCategories.append( cat );
 
   65  emit categoriesChanged();
 
   70  const int row = index.row();
 
   71  if ( row >= mCategories.size() )
 
   75  return mCategories.at( row );
 
   78Qt::ItemFlags QgsPointCloudClassifiedRendererModel::flags( 
const QModelIndex &index )
 const 
   80  if ( !index.isValid() || mCategories.empty() )
 
   82    return Qt::ItemIsDropEnabled;
 
   85  Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsUserCheckable;
 
   86  if ( index.column() == 1 ||
 
   87       index.column() == 2 ||
 
   90    flags |= Qt::ItemIsEditable;
 
   95Qt::DropActions QgsPointCloudClassifiedRendererModel::supportedDropActions()
 const 
   97  return Qt::MoveAction;
 
  100QVariant QgsPointCloudClassifiedRendererModel::data( 
const QModelIndex &index, 
int role )
 const 
  102  if ( !index.isValid() || mCategories.empty() )
 
  109    case Qt::CheckStateRole:
 
  111      if ( index.column() == 0 )
 
  113        return category.
renderState() ? Qt::Checked : Qt::Unchecked;
 
  118    case Qt::DisplayRole:
 
  119    case Qt::ToolTipRole:
 
  121      switch ( index.column() )
 
  124          return category.
pointSize() > 0 ? QString::number( category.
pointSize() ) : QString();
 
  126          return QString::number( category.
value() );
 
  128          return category.
label();
 
  130          const float value = mPercentages.value( category.
value(), -1 );
 
  134          else if ( value != 0 && std::round( value * 10 ) < 1 )
 
  135            str = QStringLiteral( 
"< " ) + QLocale().toString( 0.1, 
'f', 1 );
 
  137            str = QLocale().toString( mPercentages.value( category.
value() ), 
'f', 1 );
 
  143    case Qt::DecorationRole:
 
  145      if ( index.column() == 0 )
 
  148        QPixmap pix( iconSize, iconSize );
 
  149        pix.fill( category.
color() );
 
  155    case Qt::TextAlignmentRole:
 
  157      if ( index.column() == 0 )
 
  158        return static_cast<Qt::Alignment::Int
>( Qt::AlignHCenter );
 
  159      if ( index.column() == 4 )
 
  160        return static_cast<Qt::Alignment::Int
>( Qt::AlignRight );
 
  161      return static_cast<Qt::Alignment::Int
>( Qt::AlignLeft );
 
  166      switch ( index.column() )
 
  169          return category.
pointSize() > 0 ? QString::number( category.
pointSize() ) : QString();
 
  171          return QString::number( category.
value() );
 
  173          return category.
label();
 
  182bool QgsPointCloudClassifiedRendererModel::setData( 
const QModelIndex &index, 
const QVariant &value, 
int role )
 
  184  if ( !index.isValid() )
 
  187  if ( index.column() == 0 && role == Qt::CheckStateRole )
 
  189    mCategories[ index.row() ].setRenderState( value == Qt::Checked );
 
  190    emit dataChanged( index, index );
 
  191    emit categoriesChanged();
 
  195  if ( role != Qt::EditRole )
 
  198  switch ( index.column() )
 
  202      const double size = value.toDouble();
 
  203      mCategories[ index.row() ].setPointSize( size );
 
  208      const int val = value.toInt();
 
  209      mCategories[ index.row() ].setValue( val );
 
  214      mCategories[ index.row() ].setLabel( value.toString() );
 
  221  emit dataChanged( index, index );
 
  222  emit categoriesChanged();
 
  226QVariant QgsPointCloudClassifiedRendererModel::headerData( 
int section, Qt::Orientation orientation, 
int role )
 const 
  228  if ( orientation == Qt::Horizontal && role == Qt::DisplayRole && section >= 0 && section < 5 )
 
  231    lst << tr( 
"Color" ) << tr( 
"Size" ) << tr( 
"Value" ) << tr( 
"Legend" ) << tr( 
"Percentage" );
 
  232    return lst.value( section );
 
  237int QgsPointCloudClassifiedRendererModel::rowCount( 
const QModelIndex &parent )
 const 
  239  if ( parent.isValid() )
 
  243  return mCategories.size();
 
  246int QgsPointCloudClassifiedRendererModel::columnCount( 
const QModelIndex &index )
 const 
  252QModelIndex QgsPointCloudClassifiedRendererModel::index( 
int row, 
int column, 
const QModelIndex &parent )
 const 
  254  if ( hasIndex( row, column, parent ) )
 
  256    return createIndex( row, column );
 
  258  return QModelIndex();
 
  261QModelIndex QgsPointCloudClassifiedRendererModel::parent( 
const QModelIndex &index )
 const 
  264  return QModelIndex();
 
  267QStringList QgsPointCloudClassifiedRendererModel::mimeTypes()
 const 
  270  types << mMimeFormat;
 
  274QMimeData *QgsPointCloudClassifiedRendererModel::mimeData( 
const QModelIndexList &indexes )
 const 
  276  QMimeData *mimeData = 
new QMimeData();
 
  277  QByteArray encodedData;
 
  279  QDataStream stream( &encodedData, QIODevice::WriteOnly );
 
  282  const auto constIndexes = indexes;
 
  283  for ( 
const QModelIndex &index : constIndexes )
 
  285    if ( !index.isValid() || index.column() != 0 )
 
  288    stream << index.row();
 
  290  mimeData->setData( mMimeFormat, encodedData );
 
  294bool QgsPointCloudClassifiedRendererModel::dropMimeData( 
const QMimeData *data, Qt::DropAction action, 
int row, 
int column, 
const QModelIndex &parent )
 
  298  if ( action != Qt::MoveAction )
 
  301  if ( !data->hasFormat( mMimeFormat ) )
 
  304  QByteArray encodedData = data->data( mMimeFormat );
 
  305  QDataStream stream( &encodedData, QIODevice::ReadOnly );
 
  308  while ( !stream.atEnd() )
 
  315  int to = parent.row();
 
  319    to = mCategories.size(); 
 
  320  for ( 
int i = rows.size() - 1; i >= 0; i-- )
 
  326    if ( !( rows[i] < 0 ||  rows[i] >= mCategories.size() || t < 0 || t >= mCategories.size() ) )
 
  328      mCategories.move( rows[i], t );
 
  332    for ( 
int j = 0; j < i; j++ )
 
  334      if ( to < rows[j] && rows[i] > rows[j] )
 
  341  emit dataChanged( createIndex( 0, 0 ), createIndex( mCategories.size(), 0 ) );
 
  342  emit categoriesChanged();
 
  346void QgsPointCloudClassifiedRendererModel::deleteRows( QList<int> rows )
 
  348  std::sort( rows.begin(), rows.end() ); 
 
  349  for ( 
int i = rows.size() - 1; i >= 0; i-- )
 
  351    beginRemoveRows( QModelIndex(), rows[i], rows[i] );
 
  352    mCategories.removeAt( rows[i] );
 
  355  emit categoriesChanged();
 
  358void QgsPointCloudClassifiedRendererModel::removeAllRows()
 
  360  beginRemoveRows( QModelIndex(), 0, mCategories.size() - 1 );
 
  363  emit categoriesChanged();
 
  366void QgsPointCloudClassifiedRendererModel::setCategoryColor( 
int row, 
const QColor &color )
 
  368  mCategories[row].setColor( color );
 
  369  emit dataChanged( createIndex( row, 0 ), createIndex( row, 0 ) );
 
  370  emit categoriesChanged();
 
  373void QgsPointCloudClassifiedRendererModel::setCategoryPointSize( 
int row, 
double size )
 
  375  mCategories[row].setPointSize( size );
 
  376  emit dataChanged( createIndex( row, 0 ), createIndex( row, 0 ) );
 
  377  emit categoriesChanged();
 
  381QgsPointCloudClassifiedRendererViewStyle::QgsPointCloudClassifiedRendererViewStyle( QWidget *parent )
 
  385void QgsPointCloudClassifiedRendererViewStyle::drawPrimitive( PrimitiveElement element, 
const QStyleOption *option, QPainter *painter, 
const QWidget *widget )
 const 
  387  if ( element == QStyle::PE_IndicatorItemViewItemDrop && !option->rect.isNull() )
 
  389    QStyleOption opt( *option );
 
  390    opt.rect.setLeft( 0 );
 
  392    opt.rect.setHeight( 0 );
 
  394      opt.rect.setRight( widget->width() );
 
  395    QProxyStyle::drawPrimitive( element, &opt, painter, widget );
 
  398  QProxyStyle::drawPrimitive( element, option, painter, widget );
 
  407  mAttributeComboBox->setAllowEmptyAttributeName( 
true );
 
  410  mModel = 
new QgsPointCloudClassifiedRendererModel( 
this );
 
  414    mAttributeComboBox->setLayer( layer );
 
  416    setFromRenderer( layer->
renderer() );
 
  419  viewCategories->setModel( mModel );
 
  420  viewCategories->resizeColumnToContents( 0 );
 
  421  viewCategories->resizeColumnToContents( 1 );
 
  422  viewCategories->resizeColumnToContents( 2 );
 
  423  viewCategories->resizeColumnToContents( 3 );
 
  425  viewCategories->setStyle( 
new QgsPointCloudClassifiedRendererViewStyle( viewCategories ) );
 
  428           this, &QgsPointCloudClassifiedRendererWidget::attributeChanged );
 
  429  connect( mModel, &QgsPointCloudClassifiedRendererModel::categoriesChanged, 
this, &QgsPointCloudClassifiedRendererWidget::emitWidgetChanged );
 
  431  connect( viewCategories, &QAbstractItemView::doubleClicked, 
this, &QgsPointCloudClassifiedRendererWidget::categoriesDoubleClicked );
 
  432  connect( btnAddCategories, &QAbstractButton::clicked, 
this, &QgsPointCloudClassifiedRendererWidget::addCategories );
 
  433  connect( btnDeleteCategories, &QAbstractButton::clicked, 
this, &QgsPointCloudClassifiedRendererWidget::deleteCategories );
 
  434  connect( btnDeleteAllCategories, &QAbstractButton::clicked, 
this, &QgsPointCloudClassifiedRendererWidget::deleteAllCategories );
 
  435  connect( btnAddCategory, &QAbstractButton::clicked, 
this, &QgsPointCloudClassifiedRendererWidget::addCategory );
 
  437  contextMenu = 
new QMenu( tr( 
"Options" ), 
this );
 
  438  contextMenu->addAction( tr( 
"Change &Color…" ), 
this, &QgsPointCloudClassifiedRendererWidget::changeCategoryColor );
 
  439  contextMenu->addAction( tr( 
"Change &Opacity…" ), 
this, &QgsPointCloudClassifiedRendererWidget::changeCategoryOpacity );
 
  440  contextMenu->addAction( tr( 
"Change &Size…" ), 
this, &QgsPointCloudClassifiedRendererWidget::changeCategoryPointSize );
 
  442  viewCategories->setContextMenuPolicy( Qt::CustomContextMenu );
 
  443  viewCategories->setSelectionMode( QAbstractItemView::ExtendedSelection );
 
  444  connect( viewCategories, &QTreeView::customContextMenuRequested, 
this, [ = ]( QPoint ) { contextMenu->exec( QCursor::pos() ); } );
 
  449  return new QgsPointCloudClassifiedRendererWidget( layer, style );
 
  459  std::unique_ptr< QgsPointCloudClassifiedRenderer > renderer = std::make_unique< QgsPointCloudClassifiedRenderer >();
 
  460  renderer->setAttribute( mAttributeComboBox->currentAttribute() );
 
  461  renderer->setCategories( mModel->categories() );
 
  463  return renderer.release();
 
  468  return mModel->categories();
 
  471QString QgsPointCloudClassifiedRendererWidget::attribute()
 
  473  return mAttributeComboBox->currentAttribute();
 
  476void QgsPointCloudClassifiedRendererWidget::attributeChanged()
 
  478  if ( mBlockChangedSignal )
 
  481  mBlockChangedSignal = 
true;
 
  482  mModel->removeAllRows();
 
  483  mBlockChangedSignal = 
false;
 
  487void QgsPointCloudClassifiedRendererWidget::emitWidgetChanged()
 
  489  if ( mBlockChangedSignal )
 
  492  updateCategoriesPercentages();
 
  493  emit widgetChanged();
 
  496void QgsPointCloudClassifiedRendererWidget::categoriesDoubleClicked( 
const QModelIndex &idx )
 
  498  if ( idx.isValid() && idx.column() == 0 )
 
  499    changeCategoryColor();
 
  502void QgsPointCloudClassifiedRendererWidget::addCategories()
 
  504  if ( !mLayer || !mLayer->dataProvider() )
 
  508  const QString currentAttribute = mAttributeComboBox->currentAttribute();
 
  513  const bool isClassificationAttribute = ( 0 == currentAttribute.compare( QStringLiteral( 
"Classification" ), Qt::CaseInsensitive ) );
 
  514  const bool isBooleanAttribute = ( 0 == currentAttribute.compare( QStringLiteral( 
"Synthetic" ), Qt::CaseInsensitive ) ||
 
  515                                    0 == currentAttribute.compare( QStringLiteral( 
"KeyPoint" ), Qt::CaseInsensitive ) ||
 
  516                                    0 == currentAttribute.compare( QStringLiteral( 
"Withheld" ), Qt::CaseInsensitive ) ||
 
  517                                    0 == currentAttribute.compare( QStringLiteral( 
"Overlap" ), Qt::CaseInsensitive ) );
 
  519  QList<int> providerCategories = stats.
classesOf( currentAttribute );
 
  523  if ( isBooleanAttribute &&
 
  524       ( providerCategories.isEmpty() || stats.
sampledPointsCount() < mLayer->pointCount() ) )
 
  525    providerCategories = { 0, 1 };
 
  529  mBlockChangedSignal = 
true;
 
  530  for ( 
const int &providerCategory : std::as_const( providerCategories ) )
 
  536      if ( 
c.value() == providerCategory )
 
  547    if ( isClassificationAttribute )
 
  551        if ( 
c.value() == providerCategory )
 
  559    mModel->addCategory( category );
 
  561  mBlockChangedSignal = 
false;
 
  565void QgsPointCloudClassifiedRendererWidget::addCategory()
 
  571  mModel->addCategory( cat );
 
  574void QgsPointCloudClassifiedRendererWidget::deleteCategories()
 
  576  const QList<int> categoryIndexes = selectedCategories();
 
  577  mModel->deleteRows( categoryIndexes );
 
  580void QgsPointCloudClassifiedRendererWidget::deleteAllCategories()
 
  582  mModel->removeAllRows();
 
  587  mBlockChangedSignal = 
true;
 
  590    mModel->setRendererCategories( classifiedRenderer->categories() );
 
  591    mAttributeComboBox->setAttribute( classifiedRenderer->attribute() );
 
  597  mBlockChangedSignal = 
false;
 
  601void QgsPointCloudClassifiedRendererWidget::setFromCategories( 
QgsPointCloudCategoryList categories, 
const QString &attribute )
 
  603  mBlockChangedSignal = 
true;
 
  604  mModel->setRendererCategories( categories );
 
  605  if ( !attribute.isEmpty() )
 
  607    mAttributeComboBox->setAttribute( attribute );
 
  613  mBlockChangedSignal = 
false;
 
  617void QgsPointCloudClassifiedRendererWidget::initialize()
 
  619  if ( mAttributeComboBox->findText( QStringLiteral( 
"Classification" ) ) > -1 )
 
  621    mAttributeComboBox->setAttribute( QStringLiteral( 
"Classification" ) );
 
  625    mAttributeComboBox->setCurrentIndex( mAttributeComboBox->count() > 1 ? 1 : 0 );
 
  627  mModel->removeAllRows();
 
  631void QgsPointCloudClassifiedRendererWidget::changeCategoryColor()
 
  633  const QList<int> categoryList = selectedCategories();
 
  634  if ( categoryList.isEmpty() )
 
  645    colorWidget->
setPanelTitle( categoryList.count() == 1 ? category.
label() : tr( 
"Select Color" ) );
 
  651      for ( 
int row : categoryList )
 
  653        mModel->setCategoryColor( row, newColor );
 
  661    if ( newColor.isValid() )
 
  663      for ( 
int row : categoryList )
 
  665        mModel->setCategoryColor( row, newColor );
 
  671void QgsPointCloudClassifiedRendererWidget::changeCategoryOpacity()
 
  673  const QList<int> categoryList = selectedCategories();
 
  674  if ( categoryList.isEmpty() )
 
  679  const double oldOpacity = mModel->categories().value( categoryList.first() ).color().alphaF() * 100.0;
 
  682  const double opacity = QInputDialog::getDouble( 
this, tr( 
"Opacity" ), tr( 
"Change symbol opacity [%]" ), oldOpacity, 0.0, 100.0, 1, &ok );
 
  685    for ( 
int row : categoryList )
 
  688      QColor color = category.
color();
 
  689      color.setAlphaF( opacity / 100.0 );
 
  690      mModel->setCategoryColor( row, color );
 
  695void QgsPointCloudClassifiedRendererWidget::changeCategoryPointSize()
 
  697  const QList<int> categoryList = selectedCategories();
 
  698  if ( categoryList.isEmpty() )
 
  703  const double oldSize = mModel->categories().value( categoryList.first() ).pointSize();
 
  706  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 );
 
  709    for ( 
int row : categoryList )
 
  711      mModel->setCategoryPointSize( row, size );
 
  716QList<int> QgsPointCloudClassifiedRendererWidget::selectedCategories()
 
  719  const QModelIndexList selectedRows = viewCategories->selectionModel()->selectedRows();
 
  720  for ( 
const QModelIndex &r : selectedRows )
 
  724      rows.append( r.row() );
 
  730int QgsPointCloudClassifiedRendererWidget::currentCategoryRow()
 
  732  const QModelIndex idx = viewCategories->selectionModel()->currentIndex();
 
  733  if ( !idx.isValid() )
 
  738void QgsPointCloudClassifiedRendererWidget::updateCategoriesPercentages()
 
  740  QMap < int, float > percentages;
 
  751    if ( classes.contains( category.
value() ) || statsExact )
 
  752      percentages.insert( category.
value(), ( 
double ) classes.value( category.
value() ) / pointCount * 100 );
 
  754  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.
 
QColor fetchRandomStyleColor() const
Returns a random color for use with a new symbol style (e.g.
 
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.
 
Class 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,...
 
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