QGIS API Documentation 3.43.0-Master (3ee7834ace6)
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
19#include "moc_qgspointcloudclassifiedrendererwidget.cpp"
21#include "qgspointcloudlayer.h"
23#include "qgsdoublevalidator.h"
24#include "qgsstyle.h"
25#include "qgsguiutils.h"
27#include "qgscolordialog.h"
28#include "qgsapplication.h"
31
32#include <QMimeData>
33#include <QInputDialog>
34
36
37QgsPointCloudClassifiedRendererModel::QgsPointCloudClassifiedRendererModel( QObject *parent )
38 : QAbstractItemModel( parent )
39 , mMimeFormat( QStringLiteral( "application/x-qgspointcloudclassifiedrenderermodel" ) )
40{
41}
42
43void QgsPointCloudClassifiedRendererModel::setRendererCategories( const QgsPointCloudCategoryList &categories )
44{
45 if ( !mCategories.empty() )
46 {
47 beginRemoveRows( QModelIndex(), 0, std::max<int>( mCategories.size() - 1, 0 ) );
48 mCategories.clear();
49 endRemoveRows();
50 }
51 if ( categories.size() > 0 )
52 {
53 beginInsertRows( QModelIndex(), 0, categories.size() - 1 );
54 mCategories = categories;
55 endInsertRows();
56 }
57}
58
59void QgsPointCloudClassifiedRendererModel::addCategory( const QgsPointCloudCategory &cat )
60{
61 const int idx = mCategories.size();
62 beginInsertRows( QModelIndex(), idx, idx );
63 mCategories.append( cat );
64 endInsertRows();
65
66 emit categoriesChanged();
67}
68
69QgsPointCloudCategory QgsPointCloudClassifiedRendererModel::category( const QModelIndex &index )
70{
71 const int row = index.row();
72 if ( row >= mCategories.size() )
73 {
74 return QgsPointCloudCategory();
75 }
76 return mCategories.at( row );
77}
78
79Qt::ItemFlags QgsPointCloudClassifiedRendererModel::flags( const QModelIndex &index ) const
80{
81 // Flat list, to ease drop handling valid indexes are not dropEnabled
82 if ( !index.isValid() || mCategories.empty() )
83 {
84 return Qt::ItemIsDropEnabled;
85 }
86
87 Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsUserCheckable;
88 if ( index.column() == 1 || index.column() == 2 || 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( column )
297 Q_UNUSED( parent ) // Unused because only invalid indexes have Qt::ItemIsDropEnabled
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 // Items may come unsorted depending on selecion order
316 std::sort( rows.begin(), rows.end() );
317
318 int to = row;
319 // to is -1 if dragged outside items, i.e. below any item,
320 // then move to the last position
321 if ( to == -1 )
322 to = mCategories.size(); // out of rang ok, will be decreased
323 for ( int i = rows.size() - 1; i >= 0; i-- )
324 {
325 int t = to;
326 if ( rows[i] < t )
327 t--;
328
329 if ( !( rows[i] < 0 || rows[i] >= mCategories.size() || t < 0 || t >= mCategories.size() ) )
330 {
331 mCategories.move( rows[i], t );
332 }
333
334 // current moved under another, shift its index up
335 for ( int j = 0; j < i; j++ )
336 {
337 if ( to < rows[j] && rows[i] > rows[j] )
338 rows[j] += 1;
339 }
340 // removed under 'to' so the target shifted down
341 if ( rows[i] < to )
342 to--;
343 }
344 emit dataChanged( createIndex( 0, 0 ), createIndex( mCategories.size(), 0 ) );
345 emit categoriesChanged();
346 emit rowsMoved();
347 return false;
348}
349
350void QgsPointCloudClassifiedRendererModel::deleteRows( QList<int> rows )
351{
352 std::sort( rows.begin(), rows.end() ); // list might be unsorted, depending on how the user selected the rows
353 for ( int i = rows.size() - 1; i >= 0; i-- )
354 {
355 beginRemoveRows( QModelIndex(), rows[i], rows[i] );
356 mCategories.removeAt( rows[i] );
357 endRemoveRows();
358 }
359 emit categoriesChanged();
360}
361
362void QgsPointCloudClassifiedRendererModel::removeAllRows()
363{
364 beginRemoveRows( QModelIndex(), 0, mCategories.size() - 1 );
365 mCategories.clear();
366 endRemoveRows();
367 emit categoriesChanged();
368}
369
370void QgsPointCloudClassifiedRendererModel::setCategoryColor( int row, const QColor &color )
371{
372 mCategories[row].setColor( color );
373 emit dataChanged( createIndex( row, 0 ), createIndex( row, 0 ) );
374 emit categoriesChanged();
375}
376
377void QgsPointCloudClassifiedRendererModel::setCategoryPointSize( int row, double size )
378{
379 mCategories[row].setPointSize( size );
380 emit dataChanged( createIndex( row, 0 ), createIndex( row, 0 ) );
381 emit categoriesChanged();
382}
383
384// ------------------------------ View style --------------------------------
385QgsPointCloudClassifiedRendererViewStyle::QgsPointCloudClassifiedRendererViewStyle( QWidget *parent )
386 : QgsProxyStyle( parent )
387{}
388
389void QgsPointCloudClassifiedRendererViewStyle::drawPrimitive( PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget ) const
390{
391 if ( element == QStyle::PE_IndicatorItemViewItemDrop && !option->rect.isNull() )
392 {
393 QStyleOption opt( *option );
394 opt.rect.setLeft( 0 );
395 // draw always as line above, because we move item to that index
396 opt.rect.setHeight( 0 );
397 if ( widget )
398 opt.rect.setRight( widget->width() );
399 QProxyStyle::drawPrimitive( element, &opt, painter, widget );
400 return;
401 }
402 QProxyStyle::drawPrimitive( element, option, painter, widget );
403}
404
405
406QgsPointCloudClassifiedRendererWidget::QgsPointCloudClassifiedRendererWidget( QgsPointCloudLayer *layer, QgsStyle *style )
407 : QgsPointCloudRendererWidget( layer, style )
408{
409 setupUi( this );
410
411 mAttributeComboBox->setAllowEmptyAttributeName( true );
413
414 mModel = new QgsPointCloudClassifiedRendererModel( this );
415
416 if ( layer )
417 {
418 mAttributeComboBox->setLayer( layer );
419
420 setFromRenderer( layer->renderer() );
421 }
422
423 viewCategories->setModel( mModel );
424 viewCategories->resizeColumnToContents( 0 );
425 viewCategories->resizeColumnToContents( 1 );
426 viewCategories->resizeColumnToContents( 2 );
427 viewCategories->resizeColumnToContents( 3 );
428
429 viewCategories->setStyle( new QgsPointCloudClassifiedRendererViewStyle( viewCategories ) );
430
431 connect( mAttributeComboBox, &QgsPointCloudAttributeComboBox::attributeChanged, this, &QgsPointCloudClassifiedRendererWidget::attributeChanged );
432 connect( mModel, &QgsPointCloudClassifiedRendererModel::categoriesChanged, this, &QgsPointCloudClassifiedRendererWidget::emitWidgetChanged );
433 connect( mModel, &QgsPointCloudClassifiedRendererModel::rowsMoved, this, &QgsPointCloudClassifiedRendererWidget::rowsMoved );
434
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 );
440
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 );
445
446 viewCategories->setContextMenuPolicy( Qt::CustomContextMenu );
447 viewCategories->setSelectionMode( QAbstractItemView::ExtendedSelection );
448 connect( viewCategories, &QTreeView::customContextMenuRequested, this, [=]( QPoint ) { contextMenu->exec( QCursor::pos() ); } );
449}
450
451QgsPointCloudRendererWidget *QgsPointCloudClassifiedRendererWidget::create( QgsPointCloudLayer *layer, QgsStyle *style, QgsPointCloudRenderer * )
452{
453 return new QgsPointCloudClassifiedRendererWidget( layer, style );
454}
455
456QgsPointCloudRenderer *QgsPointCloudClassifiedRendererWidget::renderer()
457{
458 if ( !mLayer )
459 {
460 return nullptr;
461 }
462
463 auto renderer = std::make_unique<QgsPointCloudClassifiedRenderer>();
464 renderer->setAttribute( mAttributeComboBox->currentAttribute() );
465 renderer->setCategories( mModel->categories() );
466
467 return renderer.release();
468}
469
470QgsPointCloudCategoryList QgsPointCloudClassifiedRendererWidget::categoriesList()
471{
472 return mModel->categories();
473}
474
475QString QgsPointCloudClassifiedRendererWidget::attribute()
476{
477 return mAttributeComboBox->currentAttribute();
478}
479
480void QgsPointCloudClassifiedRendererWidget::attributeChanged()
481{
482 if ( mBlockChangedSignal )
483 return;
484
485 mBlockChangedSignal = true;
486 mModel->removeAllRows();
487 mBlockChangedSignal = false;
488 addCategories();
489}
490
491void QgsPointCloudClassifiedRendererWidget::emitWidgetChanged()
492{
493 if ( mBlockChangedSignal )
494 return;
495
496 updateCategoriesPercentages();
497 emit widgetChanged();
498}
499
500void QgsPointCloudClassifiedRendererWidget::categoriesDoubleClicked( const QModelIndex &idx )
501{
502 if ( idx.isValid() && idx.column() == 0 )
503 changeCategoryColor();
504}
505
506void QgsPointCloudClassifiedRendererWidget::addCategories()
507{
508 if ( !mLayer || !mLayer->dataProvider() )
509 return;
510
511
512 const QString currentAttribute = mAttributeComboBox->currentAttribute();
513 const QgsPointCloudStatistics stats = mLayer->statistics();
514
515 const QgsPointCloudCategoryList currentCategories = mModel->categories();
516
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 ) );
519
520 QList<int> providerCategories = stats.classesOf( currentAttribute );
521
522 // for 0/1 attributes we should always show both 0 and 1 categories, unless we have full stats
523 // so we can show categories that are actually available
524 if ( isBooleanAttribute && ( 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 for ( int row : categoryList )
651 {
652 mModel->setCategoryColor( row, newColor );
653 }
654 } );
655 panel->openPanel( colorWidget );
656 }
657 else
658 {
659 const QColor newColor = QgsColorDialog::getColor( category.color(), this, category.label(), true );
660 if ( newColor.isValid() )
661 {
662 for ( int row : categoryList )
663 {
664 mModel->setCategoryColor( row, newColor );
665 }
666 }
667 }
668}
669
670void QgsPointCloudClassifiedRendererWidget::changeCategoryOpacity()
671{
672 const QList<int> categoryList = selectedCategories();
673 if ( categoryList.isEmpty() )
674 {
675 return;
676 }
677
678 const double oldOpacity = mModel->categories().value( categoryList.first() ).color().alphaF() * 100.0;
679
680 bool ok;
681 const double opacity = QInputDialog::getDouble( this, tr( "Opacity" ), tr( "Change symbol opacity [%]" ), oldOpacity, 0.0, 100.0, 1, &ok );
682 if ( ok )
683 {
684 for ( int row : categoryList )
685 {
686 const QgsPointCloudCategory category = mModel->categories().value( row );
687 QColor color = category.color();
688 color.setAlphaF( opacity / 100.0 );
689 mModel->setCategoryColor( row, color );
690 }
691 }
692}
693
694void QgsPointCloudClassifiedRendererWidget::changeCategoryPointSize()
695{
696 const QList<int> categoryList = selectedCategories();
697 if ( categoryList.isEmpty() )
698 {
699 return;
700 }
701
702 const double oldSize = mModel->categories().value( categoryList.first() ).pointSize();
703
704 bool ok;
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 );
706 if ( ok )
707 {
708 for ( int row : categoryList )
709 {
710 mModel->setCategoryPointSize( row, size );
711 }
712 }
713}
714
715void QgsPointCloudClassifiedRendererWidget::rowsMoved()
716{
717 viewCategories->selectionModel()->clear();
718}
719
720QList<int> QgsPointCloudClassifiedRendererWidget::selectedCategories()
721{
722 QList<int> rows;
723 const QModelIndexList selectedRows = viewCategories->selectionModel()->selectedRows();
724 for ( const QModelIndex &r : selectedRows )
725 {
726 if ( r.isValid() )
727 {
728 rows.append( r.row() );
729 }
730 }
731 return rows;
732}
733
734int QgsPointCloudClassifiedRendererWidget::currentCategoryRow()
735{
736 const QModelIndex idx = viewCategories->selectionModel()->currentIndex();
737 if ( !idx.isValid() )
738 return -1;
739 return idx.row();
740}
741
742void QgsPointCloudClassifiedRendererWidget::updateCategoriesPercentages()
743{
744 QMap<int, float> percentages;
745
746 const QgsPointCloudStatistics stats = mLayer->statistics();
747 const QMap<int, int> classes = stats.availableClasses( attribute() );
748 const int pointCount = stats.sampledPointsCount();
749 const QgsPointCloudCategoryList currentCategories = mModel->categories();
750
751 // when the stats are 100% accurate, we are sure that missing classes have a 0% of points
752 const bool statsExact = stats.sampledPointsCount() == mLayer->pointCount();
753 for ( const QgsPointCloudCategory &category : currentCategories )
754 {
755 if ( classes.contains( category.value() ) || statsExact )
756 percentages.insert( category.value(), ( double ) classes.value( category.value() ) / pointCount * 100 );
757 }
758 mModel->updateCategoriesPercentages( percentages );
759}
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,...
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