35 QgsPointCloudClassifiedRendererModel::QgsPointCloudClassifiedRendererModel( QObject *parent )
 
   36   : QAbstractItemModel( parent )
 
   37   , mMimeFormat( QStringLiteral( 
"application/x-qgspointcloudclassifiedrenderermodel" ) )
 
   43   if ( !mCategories.empty() )
 
   45     beginRemoveRows( QModelIndex(), 0, std::max< int >( mCategories.size() - 1, 0 ) );
 
   49   if ( categories.size() > 0 )
 
   51     beginInsertRows( QModelIndex(), 0, categories.size() - 1 );
 
   52     mCategories = categories;
 
   59   const int idx = mCategories.size();
 
   60   beginInsertRows( QModelIndex(), idx, idx );
 
   61   mCategories.append( cat );
 
   64   emit categoriesChanged();
 
   69   const int row = index.row();
 
   70   if ( row >= mCategories.size() )
 
   74   return mCategories.at( row );
 
   77 Qt::ItemFlags QgsPointCloudClassifiedRendererModel::flags( 
const QModelIndex &index )
 const 
   79   if ( !index.isValid() || mCategories.empty() )
 
   81     return Qt::ItemIsDropEnabled;
 
   84   Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsUserCheckable;
 
   85   if ( index.column() == 1 )
 
   87     flags |= Qt::ItemIsEditable;
 
   89   else if ( index.column() == 2 )
 
   91     flags |= Qt::ItemIsEditable;
 
   96 Qt::DropActions QgsPointCloudClassifiedRendererModel::supportedDropActions()
 const 
   98   return Qt::MoveAction;
 
  101 QVariant QgsPointCloudClassifiedRendererModel::data( 
const QModelIndex &index, 
int role )
 const 
  103   if ( !index.isValid() || mCategories.empty() )
 
  110     case Qt::CheckStateRole:
 
  112       if ( index.column() == 0 )
 
  114         return category.
renderState() ? Qt::Checked : Qt::Unchecked;
 
  119     case Qt::DisplayRole:
 
  120     case Qt::ToolTipRole:
 
  122       switch ( index.column() )
 
  126           return QString::number( category.
value() );
 
  129           return category.
label();
 
  131           const float value = mPercentages.value( category.
value(), -1 );
 
  135           else if ( value != 0 && std::round( value * 10 ) < 1 )
 
  136             str = QStringLiteral( 
"< " ) + QLocale().toString( 0.1, 
'f', 1 );
 
  138             str = QLocale().toString( mPercentages.value( category.
value() ), 
'f', 1 );
 
  144     case Qt::DecorationRole:
 
  146       if ( index.column() == 0 )
 
  150         pix.fill( category.
color() );
 
  156     case Qt::TextAlignmentRole:
 
  158       if ( index.column() == 0 )
 
  159         return static_cast<Qt::Alignment::Int
>( Qt::AlignHCenter );
 
  160       if ( index.column() == 3 )
 
  161         return static_cast<Qt::Alignment::Int
>( Qt::AlignRight );
 
  162       return static_cast<Qt::Alignment::Int
>( Qt::AlignLeft );
 
  167       switch ( index.column() )
 
  171           return QString::number( category.
value() );
 
  175           return category.
label();
 
  184 bool 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 int val = value.toInt();
 
  205       mCategories[ index.row() ].setValue( val );
 
  210       mCategories[ index.row() ].setLabel( value.toString() );
 
  217   emit dataChanged( index, index );
 
  218   emit categoriesChanged();
 
  222 QVariant QgsPointCloudClassifiedRendererModel::headerData( 
int section, Qt::Orientation orientation, 
int role )
 const 
  224   if ( orientation == Qt::Horizontal && role == Qt::DisplayRole && section >= 0 && section < 4 )
 
  227     lst << tr( 
"Color" ) << tr( 
"Value" ) << tr( 
"Legend" ) << tr( 
"Percentage" );
 
  228     return lst.value( section );
 
  233 int QgsPointCloudClassifiedRendererModel::rowCount( 
const QModelIndex &parent )
 const 
  235   if ( parent.isValid() )
 
  239   return mCategories.size();
 
  242 int QgsPointCloudClassifiedRendererModel::columnCount( 
const QModelIndex &index )
 const 
  248 QModelIndex QgsPointCloudClassifiedRendererModel::index( 
int row, 
int column, 
const QModelIndex &parent )
 const 
  250   if ( hasIndex( row, column, parent ) )
 
  252     return createIndex( row, column );
 
  254   return QModelIndex();
 
  257 QModelIndex QgsPointCloudClassifiedRendererModel::parent( 
const QModelIndex &index )
 const 
  260   return QModelIndex();
 
  263 QStringList QgsPointCloudClassifiedRendererModel::mimeTypes()
 const 
  266   types << mMimeFormat;
 
  270 QMimeData *QgsPointCloudClassifiedRendererModel::mimeData( 
const QModelIndexList &indexes )
 const 
  272   QMimeData *mimeData = 
new QMimeData();
 
  273   QByteArray encodedData;
 
  275   QDataStream stream( &encodedData, QIODevice::WriteOnly );
 
  278   const auto constIndexes = indexes;
 
  279   for ( 
const QModelIndex &index : constIndexes )
 
  281     if ( !index.isValid() || index.column() != 0 )
 
  284     stream << index.row();
 
  286   mimeData->setData( mMimeFormat, encodedData );
 
  290 bool QgsPointCloudClassifiedRendererModel::dropMimeData( 
const QMimeData *data, Qt::DropAction action, 
int row, 
int column, 
const QModelIndex &parent )
 
  294   if ( action != Qt::MoveAction )
 
  297   if ( !data->hasFormat( mMimeFormat ) )
 
  300   QByteArray encodedData = data->data( mMimeFormat );
 
  301   QDataStream stream( &encodedData, QIODevice::ReadOnly );
 
  304   while ( !stream.atEnd() )
 
  311   int to = parent.row();
 
  315     to = mCategories.size(); 
 
  316   for ( 
int i = rows.size() - 1; i >= 0; i-- )
 
  322     if ( !( rows[i] < 0 ||  rows[i] >= mCategories.size() || t < 0 || t >= mCategories.size() ) )
 
  324       mCategories.move( rows[i], t );
 
  328     for ( 
int j = 0; j < i; j++ )
 
  330       if ( to < rows[j] && rows[i] > rows[j] )
 
  337   emit dataChanged( createIndex( 0, 0 ), createIndex( mCategories.size(), 0 ) );
 
  338   emit categoriesChanged();
 
  342 void QgsPointCloudClassifiedRendererModel::deleteRows( QList<int> rows )
 
  344   std::sort( rows.begin(), rows.end() ); 
 
  345   for ( 
int i = rows.size() - 1; i >= 0; i-- )
 
  347     beginRemoveRows( QModelIndex(), rows[i], rows[i] );
 
  348     mCategories.removeAt( rows[i] );
 
  351   emit categoriesChanged();
 
  354 void QgsPointCloudClassifiedRendererModel::removeAllRows()
 
  356   beginRemoveRows( QModelIndex(), 0, mCategories.size() - 1 );
 
  359   emit categoriesChanged();
 
  362 void QgsPointCloudClassifiedRendererModel::setCategoryColor( 
int row, 
const QColor &color )
 
  364   mCategories[row].setColor( color );
 
  365   emit dataChanged( createIndex( row, 0 ), createIndex( row, 0 ) );
 
  366   emit categoriesChanged();
 
  370 QgsPointCloudClassifiedRendererViewStyle::QgsPointCloudClassifiedRendererViewStyle( QWidget *parent )
 
  374 void QgsPointCloudClassifiedRendererViewStyle::drawPrimitive( PrimitiveElement element, 
const QStyleOption *option, QPainter *painter, 
const QWidget *widget )
 const 
  376   if ( element == QStyle::PE_IndicatorItemViewItemDrop && !option->rect.isNull() )
 
  378     QStyleOption opt( *option );
 
  379     opt.rect.setLeft( 0 );
 
  381     opt.rect.setHeight( 0 );
 
  383       opt.rect.setRight( widget->width() );
 
  384     QProxyStyle::drawPrimitive( element, &opt, painter, widget );
 
  387   QProxyStyle::drawPrimitive( element, option, painter, widget );
 
  396   mAttributeComboBox->setAllowEmptyAttributeName( 
true );
 
  399   mModel = 
new QgsPointCloudClassifiedRendererModel( 
this );
 
  403     mAttributeComboBox->setLayer( layer );
 
  405     setFromRenderer( layer->
renderer() );
 
  408   viewCategories->setModel( mModel );
 
  409   viewCategories->resizeColumnToContents( 0 );
 
  410   viewCategories->resizeColumnToContents( 1 );
 
  411   viewCategories->resizeColumnToContents( 2 );
 
  413   viewCategories->setStyle( 
new QgsPointCloudClassifiedRendererViewStyle( viewCategories ) );
 
  416            this, &QgsPointCloudClassifiedRendererWidget::attributeChanged );
 
  417   connect( mModel, &QgsPointCloudClassifiedRendererModel::categoriesChanged, 
this, &QgsPointCloudClassifiedRendererWidget::emitWidgetChanged );
 
  419   connect( viewCategories, &QAbstractItemView::doubleClicked, 
this, &QgsPointCloudClassifiedRendererWidget::categoriesDoubleClicked );
 
  420   connect( btnAddCategories, &QAbstractButton::clicked, 
this, &QgsPointCloudClassifiedRendererWidget::addCategories );
 
  421   connect( btnDeleteCategories, &QAbstractButton::clicked, 
this, &QgsPointCloudClassifiedRendererWidget::deleteCategories );
 
  422   connect( btnDeleteAllCategories, &QAbstractButton::clicked, 
this, &QgsPointCloudClassifiedRendererWidget::deleteAllCategories );
 
  423   connect( btnAddCategory, &QAbstractButton::clicked, 
this, &QgsPointCloudClassifiedRendererWidget::addCategory );
 
  429   return new QgsPointCloudClassifiedRendererWidget( layer, style );
 
  439   std::unique_ptr< QgsPointCloudClassifiedRenderer > renderer = std::make_unique< QgsPointCloudClassifiedRenderer >();
 
  440   renderer->setAttribute( mAttributeComboBox->currentAttribute() );
 
  441   renderer->setCategories( mModel->categories() );
 
  443   return renderer.release();
 
  448   return mModel->categories();
 
  451 QString QgsPointCloudClassifiedRendererWidget::attribute()
 
  453   return mAttributeComboBox->currentAttribute();
 
  456 void QgsPointCloudClassifiedRendererWidget::attributeChanged()
 
  458   if ( mBlockChangedSignal )
 
  461   mBlockChangedSignal = 
true;
 
  462   mModel->removeAllRows();
 
  463   mBlockChangedSignal = 
false;
 
  467 void QgsPointCloudClassifiedRendererWidget::emitWidgetChanged()
 
  469   if ( mBlockChangedSignal )
 
  472   updateCategoriesPercentages();
 
  473   emit widgetChanged();
 
  476 void QgsPointCloudClassifiedRendererWidget::categoriesDoubleClicked( 
const QModelIndex &idx )
 
  478   if ( idx.isValid() && idx.column() == 0 )
 
  479     changeCategorySymbol();
 
  482 void QgsPointCloudClassifiedRendererWidget::addCategories()
 
  484   if ( !mLayer || !mLayer->dataProvider() )
 
  488   const QString currentAttribute = mAttributeComboBox->currentAttribute();
 
  490   const QList<int> providerCategories = stats.
classesOf( currentAttribute );
 
  494   const bool isClassificationAttribute = ! currentAttribute.compare( QStringLiteral( 
"Classification" ), Qt::CaseInsensitive );
 
  497   mBlockChangedSignal = 
true;
 
  498   for ( 
const int &providerCategory : providerCategories )
 
  504       if ( 
c.value() == providerCategory )
 
  515     if ( isClassificationAttribute )
 
  519         if ( 
c.value() == providerCategory )
 
  527     mModel->addCategory( category );
 
  529   mBlockChangedSignal = 
false;
 
  533 void QgsPointCloudClassifiedRendererWidget::addCategory()
 
  539   mModel->addCategory( cat );
 
  542 void QgsPointCloudClassifiedRendererWidget::deleteCategories()
 
  544   const QList<int> categoryIndexes = selectedCategories();
 
  545   mModel->deleteRows( categoryIndexes );
 
  548 void QgsPointCloudClassifiedRendererWidget::deleteAllCategories()
 
  550   mModel->removeAllRows();
 
  555   mBlockChangedSignal = 
true;
 
  558     mModel->setRendererCategories( classifiedRenderer->categories() );
 
  559     mAttributeComboBox->setAttribute( classifiedRenderer->attribute() );
 
  565   mBlockChangedSignal = 
false;
 
  569 void QgsPointCloudClassifiedRendererWidget::setFromCategories( 
QgsPointCloudCategoryList categories, 
const QString &attribute )
 
  571   mBlockChangedSignal = 
true;
 
  572   mModel->setRendererCategories( categories );
 
  573   if ( !attribute.isEmpty() )
 
  575     mAttributeComboBox->setAttribute( attribute );
 
  581   mBlockChangedSignal = 
false;
 
  585 void QgsPointCloudClassifiedRendererWidget::initialize()
 
  587   if ( mAttributeComboBox->findText( QStringLiteral( 
"Classification" ) ) > -1 )
 
  589     mAttributeComboBox->setAttribute( QStringLiteral( 
"Classification" ) );
 
  593     mAttributeComboBox->setCurrentIndex( mAttributeComboBox->count() > 1 ? 1 : 0 );
 
  595   mModel->removeAllRows();
 
  599 void QgsPointCloudClassifiedRendererWidget::changeCategorySymbol()
 
  601   const int row = currentCategoryRow();
 
  617       mModel->setCategoryColor( row, newColor );
 
  624     if ( newColor.isValid() )
 
  626       mModel->setCategoryColor( row, newColor );
 
  631 QList<int> QgsPointCloudClassifiedRendererWidget::selectedCategories()
 
  634   const QModelIndexList selectedRows = viewCategories->selectionModel()->selectedRows();
 
  635   for ( 
const QModelIndex &r : selectedRows )
 
  639       rows.append( r.row() );
 
  645 int QgsPointCloudClassifiedRendererWidget::currentCategoryRow()
 
  647   const QModelIndex idx = viewCategories->selectionModel()->currentIndex();
 
  648   if ( !idx.isValid() )
 
  653 void QgsPointCloudClassifiedRendererWidget::updateCategoriesPercentages()
 
  655   QMap < int, float > percentages;
 
  666     if ( classes.contains( category.
value() ) || statsExact )
 
  667       percentages.insert( category.
value(), ( 
double ) classes.value( category.
value() ) / pointCount * 100 );
 
  669   mModel->updateCategoriesPercentages( percentages );