QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
qgspointcloudclassifiedrendererwidget.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgspointcloudclassifiedrendererwidget.cpp
3 ---------------------
4 begin : November 2020
5 copyright : (C) 2020 by Nyall Dawson
6 email : nyall dot dawson at gmail dot com
7 ***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
20#include "qgspointcloudlayer.h"
22#include "qgsdoublevalidator.h"
23#include "qgsstyle.h"
24#include "qgsguiutils.h"
26#include "qgscolordialog.h"
27#include "qgsapplication.h"
30
31#include <QMimeData>
32#include <QInputDialog>
33
35
36QgsPointCloudClassifiedRendererModel::QgsPointCloudClassifiedRendererModel( QObject *parent )
37 : QAbstractItemModel( parent )
38 , mMimeFormat( QStringLiteral( "application/x-qgspointcloudclassifiedrenderermodel" ) )
39{
40}
41
42void QgsPointCloudClassifiedRendererModel::setRendererCategories( const QgsPointCloudCategoryList &categories )
43{
44 if ( !mCategories.empty() )
45 {
46 beginRemoveRows( QModelIndex(), 0, std::max< int >( mCategories.size() - 1, 0 ) );
47 mCategories.clear();
48 endRemoveRows();
49 }
50 if ( categories.size() > 0 )
51 {
52 beginInsertRows( QModelIndex(), 0, categories.size() - 1 );
53 mCategories = categories;
54 endInsertRows();
55 }
56}
57
58void QgsPointCloudClassifiedRendererModel::addCategory( const QgsPointCloudCategory &cat )
59{
60 const int idx = mCategories.size();
61 beginInsertRows( QModelIndex(), idx, idx );
62 mCategories.append( cat );
63 endInsertRows();
64
65 emit categoriesChanged();
66}
67
68QgsPointCloudCategory QgsPointCloudClassifiedRendererModel::category( const QModelIndex &index )
69{
70 const int row = index.row();
71 if ( row >= mCategories.size() )
72 {
73 return QgsPointCloudCategory();
74 }
75 return mCategories.at( row );
76}
77
78Qt::ItemFlags QgsPointCloudClassifiedRendererModel::flags( const QModelIndex &index ) const
79{
80 if ( !index.isValid() || mCategories.empty() )
81 {
82 return Qt::ItemIsDropEnabled;
83 }
84
85 Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsUserCheckable;
86 if ( index.column() == 1 ||
87 index.column() == 2 ||
88 index.column() == 3 )
89 {
90 flags |= Qt::ItemIsEditable;
91 }
92 return flags;
93}
94
95Qt::DropActions QgsPointCloudClassifiedRendererModel::supportedDropActions() const
96{
97 return Qt::MoveAction;
98}
99
100QVariant QgsPointCloudClassifiedRendererModel::data( const QModelIndex &index, int role ) const
101{
102 if ( !index.isValid() || mCategories.empty() )
103 return QVariant();
104
105 const QgsPointCloudCategory category = mCategories.value( index.row() );
106
107 switch ( role )
108 {
109 case Qt::CheckStateRole:
110 {
111 if ( index.column() == 0 )
112 {
113 return category.renderState() ? Qt::Checked : Qt::Unchecked;
114 }
115 break;
116 }
117
118 case Qt::DisplayRole:
119 case Qt::ToolTipRole:
120 {
121 switch ( index.column() )
122 {
123 case 1:
124 return category.pointSize() > 0 ? QString::number( category.pointSize() ) : QString();
125 case 2:
126 return QString::number( category.value() );
127 case 3:
128 return category.label();
129 case 4:
130 const float value = mPercentages.value( category.value(), -1 );
131 QString str;
132 if ( value < 0 )
133 str = tr( "N/A" );
134 else if ( value != 0 && std::round( value * 10 ) < 1 )
135 str = QStringLiteral( "< " ) + QLocale().toString( 0.1, 'f', 1 );
136 else
137 str = QLocale().toString( mPercentages.value( category.value() ), 'f', 1 );
138 return str;
139 }
140 break;
141 }
142
143 case Qt::DecorationRole:
144 {
145 if ( index.column() == 0 )
146 {
147 const int iconSize = QgsGuiUtils::scaleIconSize( 16 );
148 QPixmap pix( iconSize, iconSize );
149 pix.fill( category.color() );
150 return QIcon( pix );
151 }
152 break;
153 }
154
155 case Qt::TextAlignmentRole:
156 {
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 );
162 }
163
164 case Qt::EditRole:
165 {
166 switch ( index.column() )
167 {
168 case 1:
169 return category.pointSize() > 0 ? QString::number( category.pointSize() ) : QString();
170 case 2:
171 return QString::number( category.value() );
172 case 3:
173 return category.label();
174 }
175 break;
176 }
177 }
178
179 return QVariant();
180}
181
182bool QgsPointCloudClassifiedRendererModel::setData( const QModelIndex &index, const QVariant &value, int role )
183{
184 if ( !index.isValid() )
185 return false;
186
187 if ( index.column() == 0 && role == Qt::CheckStateRole )
188 {
189 mCategories[ index.row() ].setRenderState( value == Qt::Checked );
190 emit dataChanged( index, index );
191 emit categoriesChanged();
192 return true;
193 }
194
195 if ( role != Qt::EditRole )
196 return false;
197
198 switch ( index.column() )
199 {
200 case 1: // point size
201 {
202 const double size = value.toDouble();
203 mCategories[ index.row() ].setPointSize( size );
204 break;
205 }
206 case 2: // value
207 {
208 const int val = value.toInt();
209 mCategories[ index.row() ].setValue( val );
210 break;
211 }
212 case 3: // label
213 {
214 mCategories[ index.row() ].setLabel( value.toString() );
215 break;
216 }
217 default:
218 return false;
219 }
220
221 emit dataChanged( index, index );
222 emit categoriesChanged();
223 return true;
224}
225
226QVariant QgsPointCloudClassifiedRendererModel::headerData( int section, Qt::Orientation orientation, int role ) const
227{
228 if ( orientation == Qt::Horizontal && role == Qt::DisplayRole && section >= 0 && section < 5 )
229 {
230 QStringList lst;
231 lst << tr( "Color" ) << tr( "Size" ) << tr( "Value" ) << tr( "Legend" ) << tr( "Percentage" );
232 return lst.value( section );
233 }
234 return QVariant();
235}
236
237int QgsPointCloudClassifiedRendererModel::rowCount( const QModelIndex &parent ) const
238{
239 if ( parent.isValid() )
240 {
241 return 0;
242 }
243 return mCategories.size();
244}
245
246int QgsPointCloudClassifiedRendererModel::columnCount( const QModelIndex &index ) const
247{
248 Q_UNUSED( index )
249 return 5;
250}
251
252QModelIndex QgsPointCloudClassifiedRendererModel::index( int row, int column, const QModelIndex &parent ) const
253{
254 if ( hasIndex( row, column, parent ) )
255 {
256 return createIndex( row, column );
257 }
258 return QModelIndex();
259}
260
261QModelIndex QgsPointCloudClassifiedRendererModel::parent( const QModelIndex &index ) const
262{
263 Q_UNUSED( index )
264 return QModelIndex();
265}
266
267QStringList QgsPointCloudClassifiedRendererModel::mimeTypes() const
268{
269 QStringList types;
270 types << mMimeFormat;
271 return types;
272}
273
274QMimeData *QgsPointCloudClassifiedRendererModel::mimeData( const QModelIndexList &indexes ) const
275{
276 QMimeData *mimeData = new QMimeData();
277 QByteArray encodedData;
278
279 QDataStream stream( &encodedData, QIODevice::WriteOnly );
280
281 // Create list of rows
282 const auto constIndexes = indexes;
283 for ( const QModelIndex &index : constIndexes )
284 {
285 if ( !index.isValid() || index.column() != 0 )
286 continue;
287
288 stream << index.row();
289 }
290 mimeData->setData( mMimeFormat, encodedData );
291 return mimeData;
292}
293
294bool QgsPointCloudClassifiedRendererModel::dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent )
295{
296 Q_UNUSED( row )
297 Q_UNUSED( column )
298 if ( action != Qt::MoveAction )
299 return true;
300
301 if ( !data->hasFormat( mMimeFormat ) )
302 return false;
303
304 QByteArray encodedData = data->data( mMimeFormat );
305 QDataStream stream( &encodedData, QIODevice::ReadOnly );
306
307 QVector<int> rows;
308 while ( !stream.atEnd() )
309 {
310 int r;
311 stream >> r;
312 rows.append( r );
313 }
314
315 int to = parent.row();
316 // to is -1 if dragged outside items, i.e. below any item,
317 // then move to the last position
318 if ( to == -1 )
319 to = mCategories.size(); // out of rang ok, will be decreased
320 for ( int i = rows.size() - 1; i >= 0; i-- )
321 {
322 int t = to;
323 if ( rows[i] < t )
324 t--;
325
326 if ( !( rows[i] < 0 || rows[i] >= mCategories.size() || t < 0 || t >= mCategories.size() ) )
327 {
328 mCategories.move( rows[i], t );
329 }
330
331 // current moved under another, shift its index up
332 for ( int j = 0; j < i; j++ )
333 {
334 if ( to < rows[j] && rows[i] > rows[j] )
335 rows[j] += 1;
336 }
337 // removed under 'to' so the target shifted down
338 if ( rows[i] < to )
339 to--;
340 }
341 emit dataChanged( createIndex( 0, 0 ), createIndex( mCategories.size(), 0 ) );
342 emit categoriesChanged();
343 return false;
344}
345
346void QgsPointCloudClassifiedRendererModel::deleteRows( QList<int> rows )
347{
348 std::sort( rows.begin(), rows.end() ); // list might be unsorted, depending on how the user selected the rows
349 for ( int i = rows.size() - 1; i >= 0; i-- )
350 {
351 beginRemoveRows( QModelIndex(), rows[i], rows[i] );
352 mCategories.removeAt( rows[i] );
353 endRemoveRows();
354 }
355 emit categoriesChanged();
356}
357
358void QgsPointCloudClassifiedRendererModel::removeAllRows()
359{
360 beginRemoveRows( QModelIndex(), 0, mCategories.size() - 1 );
361 mCategories.clear();
362 endRemoveRows();
363 emit categoriesChanged();
364}
365
366void QgsPointCloudClassifiedRendererModel::setCategoryColor( int row, const QColor &color )
367{
368 mCategories[row].setColor( color );
369 emit dataChanged( createIndex( row, 0 ), createIndex( row, 0 ) );
370 emit categoriesChanged();
371}
372
373void QgsPointCloudClassifiedRendererModel::setCategoryPointSize( int row, double size )
374{
375 mCategories[row].setPointSize( size );
376 emit dataChanged( createIndex( row, 0 ), createIndex( row, 0 ) );
377 emit categoriesChanged();
378}
379
380// ------------------------------ View style --------------------------------
381QgsPointCloudClassifiedRendererViewStyle::QgsPointCloudClassifiedRendererViewStyle( QWidget *parent )
382 : QgsProxyStyle( parent )
383{}
384
385void QgsPointCloudClassifiedRendererViewStyle::drawPrimitive( PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget ) const
386{
387 if ( element == QStyle::PE_IndicatorItemViewItemDrop && !option->rect.isNull() )
388 {
389 QStyleOption opt( *option );
390 opt.rect.setLeft( 0 );
391 // draw always as line above, because we move item to that index
392 opt.rect.setHeight( 0 );
393 if ( widget )
394 opt.rect.setRight( widget->width() );
395 QProxyStyle::drawPrimitive( element, &opt, painter, widget );
396 return;
397 }
398 QProxyStyle::drawPrimitive( element, option, painter, widget );
399}
400
401
402QgsPointCloudClassifiedRendererWidget::QgsPointCloudClassifiedRendererWidget( QgsPointCloudLayer *layer, QgsStyle *style )
403 : QgsPointCloudRendererWidget( layer, style )
404{
405 setupUi( this );
406
407 mAttributeComboBox->setAllowEmptyAttributeName( true );
409
410 mModel = new QgsPointCloudClassifiedRendererModel( this );
411
412 if ( layer )
413 {
414 mAttributeComboBox->setLayer( layer );
415
416 setFromRenderer( layer->renderer() );
417 }
418
419 viewCategories->setModel( mModel );
420 viewCategories->resizeColumnToContents( 0 );
421 viewCategories->resizeColumnToContents( 1 );
422 viewCategories->resizeColumnToContents( 2 );
423 viewCategories->resizeColumnToContents( 3 );
424
425 viewCategories->setStyle( new QgsPointCloudClassifiedRendererViewStyle( viewCategories ) );
426
427 connect( mAttributeComboBox, &QgsPointCloudAttributeComboBox::attributeChanged,
428 this, &QgsPointCloudClassifiedRendererWidget::attributeChanged );
429 connect( mModel, &QgsPointCloudClassifiedRendererModel::categoriesChanged, this, &QgsPointCloudClassifiedRendererWidget::emitWidgetChanged );
430
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 );
436
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 );
441
442 viewCategories->setContextMenuPolicy( Qt::CustomContextMenu );
443 viewCategories->setSelectionMode( QAbstractItemView::ExtendedSelection );
444 connect( viewCategories, &QTreeView::customContextMenuRequested, this, [ = ]( QPoint ) { contextMenu->exec( QCursor::pos() ); } );
445}
446
447QgsPointCloudRendererWidget *QgsPointCloudClassifiedRendererWidget::create( QgsPointCloudLayer *layer, QgsStyle *style, QgsPointCloudRenderer * )
448{
449 return new QgsPointCloudClassifiedRendererWidget( layer, style );
450}
451
452QgsPointCloudRenderer *QgsPointCloudClassifiedRendererWidget::renderer()
453{
454 if ( !mLayer )
455 {
456 return nullptr;
457 }
458
459 std::unique_ptr< QgsPointCloudClassifiedRenderer > renderer = std::make_unique< QgsPointCloudClassifiedRenderer >();
460 renderer->setAttribute( mAttributeComboBox->currentAttribute() );
461 renderer->setCategories( mModel->categories() );
462
463 return renderer.release();
464}
465
466QgsPointCloudCategoryList QgsPointCloudClassifiedRendererWidget::categoriesList()
467{
468 return mModel->categories();
469}
470
471QString QgsPointCloudClassifiedRendererWidget::attribute()
472{
473 return mAttributeComboBox->currentAttribute();
474}
475
476void QgsPointCloudClassifiedRendererWidget::attributeChanged()
477{
478 if ( mBlockChangedSignal )
479 return;
480
481 mBlockChangedSignal = true;
482 mModel->removeAllRows();
483 mBlockChangedSignal = false;
484 addCategories();
485}
486
487void QgsPointCloudClassifiedRendererWidget::emitWidgetChanged()
488{
489 if ( mBlockChangedSignal )
490 return;
491
492 updateCategoriesPercentages();
493 emit widgetChanged();
494}
495
496void QgsPointCloudClassifiedRendererWidget::categoriesDoubleClicked( const QModelIndex &idx )
497{
498 if ( idx.isValid() && idx.column() == 0 )
499 changeCategoryColor();
500}
501
502void QgsPointCloudClassifiedRendererWidget::addCategories()
503{
504 if ( !mLayer || !mLayer->dataProvider() )
505 return;
506
507
508 const QString currentAttribute = mAttributeComboBox->currentAttribute();
509 const QgsPointCloudStatistics stats = mLayer->statistics();
510
511 const QgsPointCloudCategoryList currentCategories = mModel->categories();
512
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 ) );
518
519 QList<int> providerCategories = stats.classesOf( currentAttribute );
520
521 // for 0/1 attributes we should always show both 0 and 1 categories, unless we have full stats
522 // so we can show categories that are actually available
523 if ( isBooleanAttribute &&
524 ( providerCategories.isEmpty() || stats.sampledPointsCount() < mLayer->pointCount() ) )
525 providerCategories = { 0, 1 };
526
527 const QgsPointCloudCategoryList defaultLayerCategories = isClassificationAttribute ? QgsPointCloudRendererRegistry::classificationAttributeCategories( mLayer ) : QgsPointCloudCategoryList();
528
529 mBlockChangedSignal = true;
530 for ( const int &providerCategory : std::as_const( providerCategories ) )
531 {
532 // does this category already exist?
533 bool found = false;
534 for ( const QgsPointCloudCategory &c : currentCategories )
535 {
536 if ( c.value() == providerCategory )
537 {
538 found = true;
539 break;
540 }
541 }
542
543 if ( found )
544 continue;
545
546 QgsPointCloudCategory category;
547 if ( isClassificationAttribute )
548 {
549 for ( const QgsPointCloudCategory &c : defaultLayerCategories )
550 {
551 if ( c.value() == providerCategory )
552 category = c;
553 }
554 }
555 else
556 {
557 category = QgsPointCloudCategory( providerCategory, QgsApplication::colorSchemeRegistry()->fetchRandomStyleColor(), QString::number( providerCategory ) );
558 }
559 mModel->addCategory( category );
560 }
561 mBlockChangedSignal = false;
562 emitWidgetChanged();
563}
564
565void QgsPointCloudClassifiedRendererWidget::addCategory()
566{
567 if ( !mModel )
568 return;
569
570 const QgsPointCloudCategory cat( mModel->categories().size(), QgsApplication::colorSchemeRegistry()->fetchRandomStyleColor(), QString(), true );
571 mModel->addCategory( cat );
572}
573
574void QgsPointCloudClassifiedRendererWidget::deleteCategories()
575{
576 const QList<int> categoryIndexes = selectedCategories();
577 mModel->deleteRows( categoryIndexes );
578}
579
580void QgsPointCloudClassifiedRendererWidget::deleteAllCategories()
581{
582 mModel->removeAllRows();
583}
584
585void QgsPointCloudClassifiedRendererWidget::setFromRenderer( const QgsPointCloudRenderer *r )
586{
587 mBlockChangedSignal = true;
588 if ( const QgsPointCloudClassifiedRenderer *classifiedRenderer = dynamic_cast< const QgsPointCloudClassifiedRenderer *>( r ) )
589 {
590 mModel->setRendererCategories( classifiedRenderer->categories() );
591 mAttributeComboBox->setAttribute( classifiedRenderer->attribute() );
592 }
593 else
594 {
595 initialize();
596 }
597 mBlockChangedSignal = false;
598 emitWidgetChanged();
599}
600
601void QgsPointCloudClassifiedRendererWidget::setFromCategories( QgsPointCloudCategoryList categories, const QString &attribute )
602{
603 mBlockChangedSignal = true;
604 mModel->setRendererCategories( categories );
605 if ( !attribute.isEmpty() )
606 {
607 mAttributeComboBox->setAttribute( attribute );
608 }
609 else
610 {
611 initialize();
612 }
613 mBlockChangedSignal = false;
614 emitWidgetChanged();
615}
616
617void QgsPointCloudClassifiedRendererWidget::initialize()
618{
619 if ( mAttributeComboBox->findText( QStringLiteral( "Classification" ) ) > -1 )
620 {
621 mAttributeComboBox->setAttribute( QStringLiteral( "Classification" ) );
622 }
623 else
624 {
625 mAttributeComboBox->setCurrentIndex( mAttributeComboBox->count() > 1 ? 1 : 0 );
626 }
627 mModel->removeAllRows();
628 addCategories();
629}
630
631void QgsPointCloudClassifiedRendererWidget::changeCategoryColor()
632{
633 const QList<int> categoryList = selectedCategories();
634 if ( categoryList.isEmpty() )
635 {
636 return;
637 }
638
639 const QgsPointCloudCategory category = mModel->categories().value( categoryList.first() );
640
642 if ( panel && panel->dockMode() )
643 {
645 colorWidget->setPanelTitle( categoryList.count() == 1 ? category.label() : tr( "Select Color" ) );
646 colorWidget->setAllowOpacity( true );
647 colorWidget->setPreviousColor( category.color() );
648
649 connect( colorWidget, &QgsCompoundColorWidget::currentColorChanged, this, [ = ]( const QColor & newColor )
650 {
651 for ( int row : categoryList )
652 {
653 mModel->setCategoryColor( row, newColor );
654 }
655 } );
656 panel->openPanel( colorWidget );
657 }
658 else
659 {
660 const QColor newColor = QgsColorDialog::getColor( category.color(), this, category.label(), true );
661 if ( newColor.isValid() )
662 {
663 for ( int row : categoryList )
664 {
665 mModel->setCategoryColor( row, newColor );
666 }
667 }
668 }
669}
670
671void QgsPointCloudClassifiedRendererWidget::changeCategoryOpacity()
672{
673 const QList<int> categoryList = selectedCategories();
674 if ( categoryList.isEmpty() )
675 {
676 return;
677 }
678
679 const double oldOpacity = mModel->categories().value( categoryList.first() ).color().alphaF() * 100.0;
680
681 bool ok;
682 const double opacity = QInputDialog::getDouble( this, tr( "Opacity" ), tr( "Change symbol opacity [%]" ), oldOpacity, 0.0, 100.0, 1, &ok );
683 if ( ok )
684 {
685 for ( int row : categoryList )
686 {
687 const QgsPointCloudCategory category = mModel->categories().value( row );
688 QColor color = category.color();
689 color.setAlphaF( opacity / 100.0 );
690 mModel->setCategoryColor( row, color );
691 }
692 }
693}
694
695void QgsPointCloudClassifiedRendererWidget::changeCategoryPointSize()
696{
697 const QList<int> categoryList = selectedCategories();
698 if ( categoryList.isEmpty() )
699 {
700 return;
701 }
702
703 const double oldSize = mModel->categories().value( categoryList.first() ).pointSize();
704
705 bool ok;
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 );
707 if ( ok )
708 {
709 for ( int row : categoryList )
710 {
711 mModel->setCategoryPointSize( row, size );
712 }
713 }
714}
715
716QList<int> QgsPointCloudClassifiedRendererWidget::selectedCategories()
717{
718 QList<int> rows;
719 const QModelIndexList selectedRows = viewCategories->selectionModel()->selectedRows();
720 for ( const QModelIndex &r : selectedRows )
721 {
722 if ( r.isValid() )
723 {
724 rows.append( r.row() );
725 }
726 }
727 return rows;
728}
729
730int QgsPointCloudClassifiedRendererWidget::currentCategoryRow()
731{
732 const QModelIndex idx = viewCategories->selectionModel()->currentIndex();
733 if ( !idx.isValid() )
734 return -1;
735 return idx.row();
736}
737
738void QgsPointCloudClassifiedRendererWidget::updateCategoriesPercentages()
739{
740 QMap < int, float > percentages;
741
742 const QgsPointCloudStatistics stats = mLayer->statistics();
743 const QMap<int, int> classes = stats.availableClasses( attribute() );
744 const int pointCount = stats.sampledPointsCount();
745 const QgsPointCloudCategoryList currentCategories = mModel->categories();
746
747 // when the stats are 100% accurate, we are sure that missing classes have a 0% of points
748 const bool statsExact = stats.sampledPointsCount() == mLayer->pointCount();
749 for ( const QgsPointCloudCategory &category : currentCategories )
750 {
751 if ( classes.contains( category.value() ) || statsExact )
752 percentages.insert( category.value(), ( double ) classes.value( category.value() ) / pointCount * 100 );
753 }
754 mModel->updateCategoriesPercentages( percentages );
755}
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.
A custom QGIS widget for selecting a color, including options for selecting colors via hue wheel,...
@ LayoutVertical
Use a narrower, vertically stacked layout.
void currentColorChanged(const QColor &color)
Emitted when the dialog's color changes.
void setPreviousColor(const QColor &color)
Sets the color to show in an optional "previous color" section.
void setAllowOpacity(bool allowOpacity)
Sets whether opacity modification (transparency) is permitted for the color dialog.
Base class for any widget that can be shown as a inline panel.
void openPanel(QgsPanelWidget *panel)
Open a panel or dialog depending on dock mode setting If dock mode is true this method will emit the ...
static QgsPanelWidget * findParentPanel(QWidget *widget)
Traces through the parents of a widget to find if it is contained within a QgsPanelWidget widget.
void setPanelTitle(const QString &panelTitle)
Set the title of the panel when shown in the interface.
bool dockMode()
Returns the dock mode state.
void attributeChanged(const QString &name)
Emitted when the currently selected attribute changes.
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,...
Base class for point cloud 2D renderer settings widgets.
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,...
Definition: qgsproxystyle.h:31
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
#define str(x)
Definition: qgis.cpp:38
QList< QgsPointCloudCategory > QgsPointCloudCategoryList