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