QGIS API Documentation 3.99.0-Master (7d2ca374f2d)
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
536 // If it is classification and we lack stats, lets use the full set of default categories
537 if ( isClassificationAttribute && providerCategories.isEmpty() )
538 {
539 for ( const QgsPointCloudCategory &c : defaultLayerCategories )
540 {
541 providerCategories.append( c.value() );
542 }
543 }
544
545 for ( const int &providerCategory : std::as_const( providerCategories ) )
546 {
547 // does this category already exist?
548 bool found = false;
549 for ( const QgsPointCloudCategory &c : currentCategories )
550 {
551 if ( c.value() == providerCategory )
552 {
553 found = true;
554 break;
555 }
556 }
557
558 if ( found )
559 continue;
560
561 QgsPointCloudCategory category;
562 if ( isClassificationAttribute )
563 {
564 for ( const QgsPointCloudCategory &c : defaultLayerCategories )
565 {
566 if ( c.value() == providerCategory )
567 category = c;
568 }
569 }
570 else
571 {
572 category = QgsPointCloudCategory( providerCategory, QgsApplication::colorSchemeRegistry()->fetchRandomStyleColor(), QString::number( providerCategory ) );
573 }
574 mModel->addCategory( category );
575 }
576 mBlockChangedSignal = false;
577 emitWidgetChanged();
578}
579
580void QgsPointCloudClassifiedRendererWidget::addCategory()
581{
582 if ( !mModel )
583 return;
584
585 const QgsPointCloudCategory cat( mModel->categories().size(), QgsApplication::colorSchemeRegistry()->fetchRandomStyleColor(), QString(), true );
586 mModel->addCategory( cat );
587}
588
589void QgsPointCloudClassifiedRendererWidget::deleteCategories()
590{
591 const QList<int> categoryIndexes = selectedCategories();
592 mModel->deleteRows( categoryIndexes );
593}
594
595void QgsPointCloudClassifiedRendererWidget::deleteAllCategories()
596{
597 mModel->removeAllRows();
598}
599
600void QgsPointCloudClassifiedRendererWidget::setFromRenderer( const QgsPointCloudRenderer *r )
601{
602 mBlockChangedSignal = true;
603 if ( const QgsPointCloudClassifiedRenderer *classifiedRenderer = dynamic_cast<const QgsPointCloudClassifiedRenderer *>( r ) )
604 {
605 mModel->setRendererCategories( classifiedRenderer->categories() );
606 mAttributeComboBox->setAttribute( classifiedRenderer->attribute() );
607 }
608 else
609 {
610 initialize();
611 }
612 mBlockChangedSignal = false;
613 emitWidgetChanged();
614}
615
616void QgsPointCloudClassifiedRendererWidget::setFromCategories( QgsPointCloudCategoryList categories, const QString &attribute )
617{
618 mBlockChangedSignal = true;
619 mModel->setRendererCategories( categories );
620 if ( !attribute.isEmpty() )
621 {
622 mAttributeComboBox->setAttribute( attribute );
623 }
624 else
625 {
626 initialize();
627 }
628 mBlockChangedSignal = false;
629 emitWidgetChanged();
630}
631
632void QgsPointCloudClassifiedRendererWidget::initialize()
633{
634 if ( mAttributeComboBox->findText( u"Classification"_s ) > -1 )
635 {
636 mAttributeComboBox->setAttribute( u"Classification"_s );
637 }
638 else
639 {
640 mAttributeComboBox->setCurrentIndex( mAttributeComboBox->count() > 1 ? 1 : 0 );
641 }
642 mModel->removeAllRows();
643 addCategories();
644}
645
646void QgsPointCloudClassifiedRendererWidget::changeCategoryColor()
647{
648 const QList<int> categoryList = selectedCategories();
649 if ( categoryList.isEmpty() )
650 {
651 return;
652 }
653
654 const QgsPointCloudCategory category = mModel->categories().value( categoryList.first() );
655
657 if ( panel && panel->dockMode() )
658 {
660 colorWidget->setPanelTitle( categoryList.count() == 1 ? category.label() : tr( "Select Color" ) );
661 colorWidget->setAllowOpacity( true );
662 colorWidget->setPreviousColor( category.color() );
663
664 connect( colorWidget, &QgsCompoundColorWidget::currentColorChanged, this, [this, categoryList]( const QColor &newColor ) {
665 for ( int row : categoryList )
666 {
667 mModel->setCategoryColor( row, newColor );
668 }
669 } );
670 panel->openPanel( colorWidget );
671 }
672 else
673 {
674 const QColor newColor = QgsColorDialog::getColor( category.color(), this, category.label(), true );
675 if ( newColor.isValid() )
676 {
677 for ( int row : categoryList )
678 {
679 mModel->setCategoryColor( row, newColor );
680 }
681 }
682 }
683}
684
685void QgsPointCloudClassifiedRendererWidget::changeCategoryOpacity()
686{
687 const QList<int> categoryList = selectedCategories();
688 if ( categoryList.isEmpty() )
689 {
690 return;
691 }
692
693 const double oldOpacity = mModel->categories().value( categoryList.first() ).color().alphaF() * 100.0;
694
695 bool ok;
696 const double opacity = QInputDialog::getDouble( this, tr( "Opacity" ), tr( "Change symbol opacity [%]" ), oldOpacity, 0.0, 100.0, 1, &ok );
697 if ( ok )
698 {
699 for ( int row : categoryList )
700 {
701 const QgsPointCloudCategory category = mModel->categories().value( row );
702 QColor color = category.color();
703 color.setAlphaF( opacity / 100.0 );
704 mModel->setCategoryColor( row, color );
705 }
706 }
707}
708
709void QgsPointCloudClassifiedRendererWidget::changeCategoryPointSize()
710{
711 const QList<int> categoryList = selectedCategories();
712 if ( categoryList.isEmpty() )
713 {
714 return;
715 }
716
717 const double oldSize = mModel->categories().value( categoryList.first() ).pointSize();
718
719 bool ok;
720 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 );
721 if ( ok )
722 {
723 for ( int row : categoryList )
724 {
725 mModel->setCategoryPointSize( row, size );
726 }
727 }
728}
729
730void QgsPointCloudClassifiedRendererWidget::rowsMoved()
731{
732 viewCategories->selectionModel()->clear();
733}
734
735QList<int> QgsPointCloudClassifiedRendererWidget::selectedCategories()
736{
737 QList<int> rows;
738 const QModelIndexList selectedRows = viewCategories->selectionModel()->selectedRows();
739 for ( const QModelIndex &r : selectedRows )
740 {
741 if ( r.isValid() )
742 {
743 rows.append( r.row() );
744 }
745 }
746 return rows;
747}
748
749int QgsPointCloudClassifiedRendererWidget::currentCategoryRow()
750{
751 const QModelIndex idx = viewCategories->selectionModel()->currentIndex();
752 if ( !idx.isValid() )
753 return -1;
754 return idx.row();
755}
756
757void QgsPointCloudClassifiedRendererWidget::updateCategoriesPercentages()
758{
759 QMap<int, float> percentages;
760
761 const QgsPointCloudStatistics stats = mLayer->statistics();
762 const QMap<int, int> classes = stats.availableClasses( attribute() );
763 const int pointCount = stats.sampledPointsCount();
764 const QgsPointCloudCategoryList currentCategories = mModel->categories();
765
766 // when the stats are 100% accurate, we are sure that missing classes have a 0% of points
767 const bool statsExact = stats.sampledPointsCount() == mLayer->pointCount();
768 for ( const QgsPointCloudCategory &category : currentCategories )
769 {
770 if ( classes.contains( category.value() ) || statsExact )
771 percentages.insert( category.value(), ( double ) classes.value( category.value() ) / pointCount * 100 );
772 }
773 mModel->updateCategoriesPercentages( percentages );
774}
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