19#include "moc_qgspointcloudclassifiedrendererwidget.cpp" 
   33#include <QInputDialog> 
   37QgsPointCloudClassifiedRendererModel::QgsPointCloudClassifiedRendererModel( QObject *parent )
 
   38  : QAbstractItemModel( parent )
 
   39  , mMimeFormat( QStringLiteral( 
"application/x-qgspointcloudclassifiedrenderermodel" ) )
 
   45  if ( !mCategories.empty() )
 
   47    beginRemoveRows( QModelIndex(), 0, std::max<int>( mCategories.size() - 1, 0 ) );
 
   51  if ( categories.size() > 0 )
 
   53    beginInsertRows( QModelIndex(), 0, categories.size() - 1 );
 
   54    mCategories = categories;
 
   61  const int idx = mCategories.size();
 
   62  beginInsertRows( QModelIndex(), idx, idx );
 
   63  mCategories.append( cat );
 
   66  emit categoriesChanged();
 
   71  const int row = index.row();
 
   72  if ( row >= mCategories.size() )
 
   76  return mCategories.at( row );
 
   79Qt::ItemFlags QgsPointCloudClassifiedRendererModel::flags( 
const QModelIndex &index )
 const 
   82  if ( !index.isValid() || mCategories.empty() )
 
   84    return Qt::ItemIsDropEnabled;
 
   87  Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsUserCheckable;
 
   88  if ( index.column() == 1 || index.column() == 2 || index.column() == 3 )
 
   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() )
 
  316  std::sort( rows.begin(), rows.end() );
 
  322    to = mCategories.size(); 
 
  323  for ( 
int i = rows.size() - 1; i >= 0; i-- )
 
  329    if ( !( rows[i] < 0 || rows[i] >= mCategories.size() || t < 0 || t >= mCategories.size() ) )
 
  331      mCategories.move( rows[i], t );
 
  335    for ( 
int j = 0; j < i; j++ )
 
  337      if ( to < rows[j] && rows[i] > rows[j] )
 
  344  emit dataChanged( createIndex( 0, 0 ), createIndex( mCategories.size(), 0 ) );
 
  345  emit categoriesChanged();
 
  350void QgsPointCloudClassifiedRendererModel::deleteRows( QList<int> rows )
 
  352  std::sort( rows.begin(), rows.end() ); 
 
  353  for ( 
int i = rows.size() - 1; i >= 0; i-- )
 
  355    beginRemoveRows( QModelIndex(), rows[i], rows[i] );
 
  356    mCategories.removeAt( rows[i] );
 
  359  emit categoriesChanged();
 
  362void QgsPointCloudClassifiedRendererModel::removeAllRows()
 
  364  beginRemoveRows( QModelIndex(), 0, mCategories.size() - 1 );
 
  367  emit categoriesChanged();
 
  370void QgsPointCloudClassifiedRendererModel::setCategoryColor( 
int row, 
const QColor &color )
 
  372  mCategories[row].setColor( color );
 
  373  emit dataChanged( createIndex( row, 0 ), createIndex( row, 0 ) );
 
  374  emit categoriesChanged();
 
  377void QgsPointCloudClassifiedRendererModel::setCategoryPointSize( 
int row, 
double size )
 
  379  mCategories[row].setPointSize( size );
 
  380  emit dataChanged( createIndex( row, 0 ), createIndex( row, 0 ) );
 
  381  emit categoriesChanged();
 
  385QgsPointCloudClassifiedRendererViewStyle::QgsPointCloudClassifiedRendererViewStyle( QWidget *parent )
 
  389void QgsPointCloudClassifiedRendererViewStyle::drawPrimitive( PrimitiveElement element, 
const QStyleOption *option, QPainter *painter, 
const QWidget *widget )
 const 
  391  if ( element == QStyle::PE_IndicatorItemViewItemDrop && !option->rect.isNull() )
 
  393    QStyleOption opt( *option );
 
  394    opt.rect.setLeft( 0 );
 
  396    opt.rect.setHeight( 0 );
 
  398      opt.rect.setRight( widget->width() );
 
  399    QProxyStyle::drawPrimitive( element, &opt, painter, widget );
 
  402  QProxyStyle::drawPrimitive( element, option, painter, widget );
 
  411  mAttributeComboBox->setAllowEmptyAttributeName( 
true );
 
  414  mModel = 
new QgsPointCloudClassifiedRendererModel( 
this );
 
  418    mAttributeComboBox->setLayer( layer );
 
  420    setFromRenderer( layer->
renderer() );
 
  423  viewCategories->setModel( mModel );
 
  424  viewCategories->resizeColumnToContents( 0 );
 
  425  viewCategories->resizeColumnToContents( 1 );
 
  426  viewCategories->resizeColumnToContents( 2 );
 
  427  viewCategories->resizeColumnToContents( 3 );
 
  429  viewCategories->setStyle( 
new QgsPointCloudClassifiedRendererViewStyle( viewCategories ) );
 
  432  connect( mModel, &QgsPointCloudClassifiedRendererModel::categoriesChanged, 
this, &QgsPointCloudClassifiedRendererWidget::emitWidgetChanged );
 
  433  connect( mModel, &QgsPointCloudClassifiedRendererModel::rowsMoved, 
this, &QgsPointCloudClassifiedRendererWidget::rowsMoved );
 
  435  connect( viewCategories, &QAbstractItemView::doubleClicked, 
this, &QgsPointCloudClassifiedRendererWidget::categoriesDoubleClicked );
 
  436  connect( btnAddCategories, &QAbstractButton::clicked, 
this, &QgsPointCloudClassifiedRendererWidget::addCategories );
 
  437  connect( btnDeleteCategories, &QAbstractButton::clicked, 
this, &QgsPointCloudClassifiedRendererWidget::deleteCategories );
 
  438  connect( btnDeleteAllCategories, &QAbstractButton::clicked, 
this, &QgsPointCloudClassifiedRendererWidget::deleteAllCategories );
 
  439  connect( btnAddCategory, &QAbstractButton::clicked, 
this, &QgsPointCloudClassifiedRendererWidget::addCategory );
 
  441  contextMenu = 
new QMenu( tr( 
"Options" ), 
this );
 
  442  contextMenu->addAction( tr( 
"Change &Color…" ), 
this, &QgsPointCloudClassifiedRendererWidget::changeCategoryColor );
 
  443  contextMenu->addAction( tr( 
"Change &Opacity…" ), 
this, &QgsPointCloudClassifiedRendererWidget::changeCategoryOpacity );
 
  444  contextMenu->addAction( tr( 
"Change &Size…" ), 
this, &QgsPointCloudClassifiedRendererWidget::changeCategoryPointSize );
 
  446  viewCategories->setContextMenuPolicy( Qt::CustomContextMenu );
 
  447  viewCategories->setSelectionMode( QAbstractItemView::ExtendedSelection );
 
  448  connect( viewCategories, &QTreeView::customContextMenuRequested, 
this, [
this]( QPoint ) { contextMenu->exec( QCursor::pos() ); } );
 
  453  return new QgsPointCloudClassifiedRendererWidget( layer, style );
 
  463  auto renderer = std::make_unique<QgsPointCloudClassifiedRenderer>();
 
  464  renderer->setAttribute( mAttributeComboBox->currentAttribute() );
 
  465  renderer->setCategories( mModel->categories() );
 
  467  return renderer.release();
 
  472  return mModel->categories();
 
  475QString QgsPointCloudClassifiedRendererWidget::attribute()
 
  477  return mAttributeComboBox->currentAttribute();
 
  480void QgsPointCloudClassifiedRendererWidget::attributeChanged()
 
  482  if ( mBlockChangedSignal )
 
  485  mBlockChangedSignal = 
true;
 
  486  mModel->removeAllRows();
 
  487  mBlockChangedSignal = 
false;
 
  491void QgsPointCloudClassifiedRendererWidget::emitWidgetChanged()
 
  493  if ( mBlockChangedSignal )
 
  496  updateCategoriesPercentages();
 
  497  emit widgetChanged();
 
  500void QgsPointCloudClassifiedRendererWidget::categoriesDoubleClicked( 
const QModelIndex &idx )
 
  502  if ( idx.isValid() && idx.column() == 0 )
 
  503    changeCategoryColor();
 
  506void QgsPointCloudClassifiedRendererWidget::addCategories()
 
  508  if ( !mLayer || !mLayer->dataProvider() )
 
  512  const QString currentAttribute = mAttributeComboBox->currentAttribute();
 
  517  const bool isClassificationAttribute = ( 0 == currentAttribute.compare( QStringLiteral( 
"Classification" ), Qt::CaseInsensitive ) );
 
  518  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 ) );
 
  520  QList<int> providerCategories = stats.
classesOf( currentAttribute );
 
  524  if ( isBooleanAttribute && ( 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" ) );
 
  650      for ( 
int row : categoryList )
 
  652        mModel->setCategoryColor( row, newColor );
 
  660    if ( newColor.isValid() )
 
  662      for ( 
int row : categoryList )
 
  664        mModel->setCategoryColor( row, newColor );
 
  670void QgsPointCloudClassifiedRendererWidget::changeCategoryOpacity()
 
  672  const QList<int> categoryList = selectedCategories();
 
  673  if ( categoryList.isEmpty() )
 
  678  const double oldOpacity = mModel->categories().value( categoryList.first() ).color().alphaF() * 100.0;
 
  681  const double opacity = QInputDialog::getDouble( 
this, tr( 
"Opacity" ), tr( 
"Change symbol opacity [%]" ), oldOpacity, 0.0, 100.0, 1, &ok );
 
  684    for ( 
int row : categoryList )
 
  687      QColor color = category.
color();
 
  688      color.setAlphaF( opacity / 100.0 );
 
  689      mModel->setCategoryColor( row, color );
 
  694void QgsPointCloudClassifiedRendererWidget::changeCategoryPointSize()
 
  696  const QList<int> categoryList = selectedCategories();
 
  697  if ( categoryList.isEmpty() )
 
  702  const double oldSize = mModel->categories().value( categoryList.first() ).pointSize();
 
  705  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 );
 
  708    for ( 
int row : categoryList )
 
  710      mModel->setCategoryPointSize( row, size );
 
  715void QgsPointCloudClassifiedRendererWidget::rowsMoved()
 
  717  viewCategories->selectionModel()->clear();
 
  720QList<int> QgsPointCloudClassifiedRendererWidget::selectedCategories()
 
  723  const QModelIndexList selectedRows = viewCategories->selectionModel()->selectedRows();
 
  724  for ( 
const QModelIndex &r : selectedRows )
 
  728      rows.append( r.row() );
 
  734int QgsPointCloudClassifiedRendererWidget::currentCategoryRow()
 
  736  const QModelIndex idx = viewCategories->selectionModel()->currentIndex();
 
  737  if ( !idx.isValid() )
 
  742void QgsPointCloudClassifiedRendererWidget::updateCategoriesPercentages()
 
  744  QMap<int, float> percentages;
 
  755    if ( classes.contains( category.
value() ) || statsExact )
 
  756      percentages.insert( category.
value(), ( 
double ) classes.value( category.
value() ) / pointCount * 100 );
 
  758  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.
 
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