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