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