QGIS API Documentation 3.34.0-Prizren (ffbdd678812)
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
20#include "qgspointcloudlayer.h"
22#include "qgsdoublevalidator.h"
23#include "qgsstyle.h"
24#include "qgsguiutils.h"
26#include "qgscolordialog.h"
27#include "qgsapplication.h"
30
31#include <QMimeData>
32
34
35QgsPointCloudClassifiedRendererModel::QgsPointCloudClassifiedRendererModel( QObject *parent )
36 : QAbstractItemModel( parent )
37 , mMimeFormat( QStringLiteral( "application/x-qgspointcloudclassifiedrenderermodel" ) )
38{
39}
40
41void QgsPointCloudClassifiedRendererModel::setRendererCategories( const QgsPointCloudCategoryList &categories )
42{
43 if ( !mCategories.empty() )
44 {
45 beginRemoveRows( QModelIndex(), 0, std::max< int >( mCategories.size() - 1, 0 ) );
46 mCategories.clear();
47 endRemoveRows();
48 }
49 if ( categories.size() > 0 )
50 {
51 beginInsertRows( QModelIndex(), 0, categories.size() - 1 );
52 mCategories = categories;
53 endInsertRows();
54 }
55}
56
57void QgsPointCloudClassifiedRendererModel::addCategory( const QgsPointCloudCategory &cat )
58{
59 const int idx = mCategories.size();
60 beginInsertRows( QModelIndex(), idx, idx );
61 mCategories.append( cat );
62 endInsertRows();
63
64 emit categoriesChanged();
65}
66
67QgsPointCloudCategory QgsPointCloudClassifiedRendererModel::category( const QModelIndex &index )
68{
69 const int row = index.row();
70 if ( row >= mCategories.size() )
71 {
72 return QgsPointCloudCategory();
73 }
74 return mCategories.at( row );
75}
76
77Qt::ItemFlags QgsPointCloudClassifiedRendererModel::flags( const QModelIndex &index ) const
78{
79 if ( !index.isValid() || mCategories.empty() )
80 {
81 return Qt::ItemIsDropEnabled;
82 }
83
84 Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsUserCheckable;
85 if ( index.column() == 1 )
86 {
87 flags |= Qt::ItemIsEditable;
88 }
89 else if ( index.column() == 2 )
90 {
91 flags |= Qt::ItemIsEditable;
92 }
93 return flags;
94}
95
96Qt::DropActions QgsPointCloudClassifiedRendererModel::supportedDropActions() const
97{
98 return Qt::MoveAction;
99}
100
101QVariant QgsPointCloudClassifiedRendererModel::data( const QModelIndex &index, int role ) const
102{
103 if ( !index.isValid() || mCategories.empty() )
104 return QVariant();
105
106 const QgsPointCloudCategory category = mCategories.value( index.row() );
107
108 switch ( role )
109 {
110 case Qt::CheckStateRole:
111 {
112 if ( index.column() == 0 )
113 {
114 return category.renderState() ? Qt::Checked : Qt::Unchecked;
115 }
116 break;
117 }
118
119 case Qt::DisplayRole:
120 case Qt::ToolTipRole:
121 {
122 switch ( index.column() )
123 {
124 case 1:
125 {
126 return QString::number( category.value() );
127 }
128 case 2:
129 return category.label();
130 case 3:
131 const float value = mPercentages.value( category.value(), -1 );
132 QString str;
133 if ( value < 0 )
134 str = tr( "N/A" );
135 else if ( value != 0 && std::round( value * 10 ) < 1 )
136 str = QStringLiteral( "< " ) + QLocale().toString( 0.1, 'f', 1 );
137 else
138 str = QLocale().toString( mPercentages.value( category.value() ), 'f', 1 );
139 return str;
140 }
141 break;
142 }
143
144 case Qt::DecorationRole:
145 {
146 if ( index.column() == 0 )
147 {
148 const int iconSize = QgsGuiUtils::scaleIconSize( 16 );
149 QPixmap pix( iconSize, iconSize );
150 pix.fill( category.color() );
151 return QIcon( pix );
152 }
153 break;
154 }
155
156 case Qt::TextAlignmentRole:
157 {
158 if ( index.column() == 0 )
159 return static_cast<Qt::Alignment::Int>( Qt::AlignHCenter );
160 if ( index.column() == 3 )
161 return static_cast<Qt::Alignment::Int>( Qt::AlignRight );
162 return static_cast<Qt::Alignment::Int>( Qt::AlignLeft );
163 }
164
165 case Qt::EditRole:
166 {
167 switch ( index.column() )
168 {
169 case 1:
170 {
171 return QString::number( category.value() );
172 }
173
174 case 2:
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: // value
203 {
204 const int val = value.toInt();
205 mCategories[ index.row() ].setValue( val );
206 break;
207 }
208 case 2: // label
209 {
210 mCategories[ index.row() ].setLabel( value.toString() );
211 break;
212 }
213 default:
214 return false;
215 }
216
217 emit dataChanged( index, index );
218 emit categoriesChanged();
219 return true;
220}
221
222QVariant QgsPointCloudClassifiedRendererModel::headerData( int section, Qt::Orientation orientation, int role ) const
223{
224 if ( orientation == Qt::Horizontal && role == Qt::DisplayRole && section >= 0 && section < 4 )
225 {
226 QStringList lst;
227 lst << tr( "Color" ) << tr( "Value" ) << tr( "Legend" ) << tr( "Percentage" );
228 return lst.value( section );
229 }
230 return QVariant();
231}
232
233int QgsPointCloudClassifiedRendererModel::rowCount( const QModelIndex &parent ) const
234{
235 if ( parent.isValid() )
236 {
237 return 0;
238 }
239 return mCategories.size();
240}
241
242int QgsPointCloudClassifiedRendererModel::columnCount( const QModelIndex &index ) const
243{
244 Q_UNUSED( index )
245 return 4;
246}
247
248QModelIndex QgsPointCloudClassifiedRendererModel::index( int row, int column, const QModelIndex &parent ) const
249{
250 if ( hasIndex( row, column, parent ) )
251 {
252 return createIndex( row, column );
253 }
254 return QModelIndex();
255}
256
257QModelIndex QgsPointCloudClassifiedRendererModel::parent( const QModelIndex &index ) const
258{
259 Q_UNUSED( index )
260 return QModelIndex();
261}
262
263QStringList QgsPointCloudClassifiedRendererModel::mimeTypes() const
264{
265 QStringList types;
266 types << mMimeFormat;
267 return types;
268}
269
270QMimeData *QgsPointCloudClassifiedRendererModel::mimeData( const QModelIndexList &indexes ) const
271{
272 QMimeData *mimeData = new QMimeData();
273 QByteArray encodedData;
274
275 QDataStream stream( &encodedData, QIODevice::WriteOnly );
276
277 // Create list of rows
278 const auto constIndexes = indexes;
279 for ( const QModelIndex &index : constIndexes )
280 {
281 if ( !index.isValid() || index.column() != 0 )
282 continue;
283
284 stream << index.row();
285 }
286 mimeData->setData( mMimeFormat, encodedData );
287 return mimeData;
288}
289
290bool QgsPointCloudClassifiedRendererModel::dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent )
291{
292 Q_UNUSED( row )
293 Q_UNUSED( column )
294 if ( action != Qt::MoveAction )
295 return true;
296
297 if ( !data->hasFormat( mMimeFormat ) )
298 return false;
299
300 QByteArray encodedData = data->data( mMimeFormat );
301 QDataStream stream( &encodedData, QIODevice::ReadOnly );
302
303 QVector<int> rows;
304 while ( !stream.atEnd() )
305 {
306 int r;
307 stream >> r;
308 rows.append( r );
309 }
310
311 int to = parent.row();
312 // to is -1 if dragged outside items, i.e. below any item,
313 // then move to the last position
314 if ( to == -1 )
315 to = mCategories.size(); // out of rang ok, will be decreased
316 for ( int i = rows.size() - 1; i >= 0; i-- )
317 {
318 int t = to;
319 if ( rows[i] < t )
320 t--;
321
322 if ( !( rows[i] < 0 || rows[i] >= mCategories.size() || t < 0 || t >= mCategories.size() ) )
323 {
324 mCategories.move( rows[i], t );
325 }
326
327 // current moved under another, shift its index up
328 for ( int j = 0; j < i; j++ )
329 {
330 if ( to < rows[j] && rows[i] > rows[j] )
331 rows[j] += 1;
332 }
333 // removed under 'to' so the target shifted down
334 if ( rows[i] < to )
335 to--;
336 }
337 emit dataChanged( createIndex( 0, 0 ), createIndex( mCategories.size(), 0 ) );
338 emit categoriesChanged();
339 return false;
340}
341
342void QgsPointCloudClassifiedRendererModel::deleteRows( QList<int> rows )
343{
344 std::sort( rows.begin(), rows.end() ); // list might be unsorted, depending on how the user selected the rows
345 for ( int i = rows.size() - 1; i >= 0; i-- )
346 {
347 beginRemoveRows( QModelIndex(), rows[i], rows[i] );
348 mCategories.removeAt( rows[i] );
349 endRemoveRows();
350 }
351 emit categoriesChanged();
352}
353
354void QgsPointCloudClassifiedRendererModel::removeAllRows()
355{
356 beginRemoveRows( QModelIndex(), 0, mCategories.size() - 1 );
357 mCategories.clear();
358 endRemoveRows();
359 emit categoriesChanged();
360}
361
362void QgsPointCloudClassifiedRendererModel::setCategoryColor( int row, const QColor &color )
363{
364 mCategories[row].setColor( color );
365 emit dataChanged( createIndex( row, 0 ), createIndex( row, 0 ) );
366 emit categoriesChanged();
367}
368
369// ------------------------------ View style --------------------------------
370QgsPointCloudClassifiedRendererViewStyle::QgsPointCloudClassifiedRendererViewStyle( QWidget *parent )
371 : QgsProxyStyle( parent )
372{}
373
374void QgsPointCloudClassifiedRendererViewStyle::drawPrimitive( PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget ) const
375{
376 if ( element == QStyle::PE_IndicatorItemViewItemDrop && !option->rect.isNull() )
377 {
378 QStyleOption opt( *option );
379 opt.rect.setLeft( 0 );
380 // draw always as line above, because we move item to that index
381 opt.rect.setHeight( 0 );
382 if ( widget )
383 opt.rect.setRight( widget->width() );
384 QProxyStyle::drawPrimitive( element, &opt, painter, widget );
385 return;
386 }
387 QProxyStyle::drawPrimitive( element, option, painter, widget );
388}
389
390
391QgsPointCloudClassifiedRendererWidget::QgsPointCloudClassifiedRendererWidget( QgsPointCloudLayer *layer, QgsStyle *style )
392 : QgsPointCloudRendererWidget( layer, style )
393{
394 setupUi( this );
395
396 mAttributeComboBox->setAllowEmptyAttributeName( true );
398
399 mModel = new QgsPointCloudClassifiedRendererModel( this );
400
401 if ( layer )
402 {
403 mAttributeComboBox->setLayer( layer );
404
405 setFromRenderer( layer->renderer() );
406 }
407
408 viewCategories->setModel( mModel );
409 viewCategories->resizeColumnToContents( 0 );
410 viewCategories->resizeColumnToContents( 1 );
411 viewCategories->resizeColumnToContents( 2 );
412
413 viewCategories->setStyle( new QgsPointCloudClassifiedRendererViewStyle( viewCategories ) );
414
415 connect( mAttributeComboBox, &QgsPointCloudAttributeComboBox::attributeChanged,
416 this, &QgsPointCloudClassifiedRendererWidget::attributeChanged );
417 connect( mModel, &QgsPointCloudClassifiedRendererModel::categoriesChanged, this, &QgsPointCloudClassifiedRendererWidget::emitWidgetChanged );
418
419 connect( viewCategories, &QAbstractItemView::doubleClicked, this, &QgsPointCloudClassifiedRendererWidget::categoriesDoubleClicked );
420 connect( btnAddCategories, &QAbstractButton::clicked, this, &QgsPointCloudClassifiedRendererWidget::addCategories );
421 connect( btnDeleteCategories, &QAbstractButton::clicked, this, &QgsPointCloudClassifiedRendererWidget::deleteCategories );
422 connect( btnDeleteAllCategories, &QAbstractButton::clicked, this, &QgsPointCloudClassifiedRendererWidget::deleteAllCategories );
423 connect( btnAddCategory, &QAbstractButton::clicked, this, &QgsPointCloudClassifiedRendererWidget::addCategory );
424
425}
426
427QgsPointCloudRendererWidget *QgsPointCloudClassifiedRendererWidget::create( QgsPointCloudLayer *layer, QgsStyle *style, QgsPointCloudRenderer * )
428{
429 return new QgsPointCloudClassifiedRendererWidget( layer, style );
430}
431
432QgsPointCloudRenderer *QgsPointCloudClassifiedRendererWidget::renderer()
433{
434 if ( !mLayer )
435 {
436 return nullptr;
437 }
438
439 std::unique_ptr< QgsPointCloudClassifiedRenderer > renderer = std::make_unique< QgsPointCloudClassifiedRenderer >();
440 renderer->setAttribute( mAttributeComboBox->currentAttribute() );
441 renderer->setCategories( mModel->categories() );
442
443 return renderer.release();
444}
445
446QgsPointCloudCategoryList QgsPointCloudClassifiedRendererWidget::categoriesList()
447{
448 return mModel->categories();
449}
450
451QString QgsPointCloudClassifiedRendererWidget::attribute()
452{
453 return mAttributeComboBox->currentAttribute();
454}
455
456void QgsPointCloudClassifiedRendererWidget::attributeChanged()
457{
458 if ( mBlockChangedSignal )
459 return;
460
461 mBlockChangedSignal = true;
462 mModel->removeAllRows();
463 mBlockChangedSignal = false;
464 addCategories();
465}
466
467void QgsPointCloudClassifiedRendererWidget::emitWidgetChanged()
468{
469 if ( mBlockChangedSignal )
470 return;
471
472 updateCategoriesPercentages();
473 emit widgetChanged();
474}
475
476void QgsPointCloudClassifiedRendererWidget::categoriesDoubleClicked( const QModelIndex &idx )
477{
478 if ( idx.isValid() && idx.column() == 0 )
479 changeCategorySymbol();
480}
481
482void QgsPointCloudClassifiedRendererWidget::addCategories()
483{
484 if ( !mLayer || !mLayer->dataProvider() )
485 return;
486
487
488 const QString currentAttribute = mAttributeComboBox->currentAttribute();
489 const QgsPointCloudStatistics stats = mLayer->statistics();
490 const QList<int> providerCategories = stats.classesOf( currentAttribute );
491
492 const QgsPointCloudCategoryList currentCategories = mModel->categories();
493
494 const bool isClassificationAttribute = ! currentAttribute.compare( QStringLiteral( "Classification" ), Qt::CaseInsensitive );
495 const QgsPointCloudCategoryList defaultLayerCategories = isClassificationAttribute ? QgsPointCloudRendererRegistry::classificationAttributeCategories( mLayer ) : QgsPointCloudCategoryList();
496
497 mBlockChangedSignal = true;
498 for ( const int &providerCategory : providerCategories )
499 {
500 // does this category already exist?
501 bool found = false;
502 for ( const QgsPointCloudCategory &c : currentCategories )
503 {
504 if ( c.value() == providerCategory )
505 {
506 found = true;
507 break;
508 }
509 }
510
511 if ( found )
512 continue;
513
514 QgsPointCloudCategory category;
515 if ( isClassificationAttribute )
516 {
517 for ( const QgsPointCloudCategory &c : defaultLayerCategories )
518 {
519 if ( c.value() == providerCategory )
520 category = c;
521 }
522 }
523 else
524 {
525 category = QgsPointCloudCategory( providerCategory, QgsApplication::colorSchemeRegistry()->fetchRandomStyleColor(), QString::number( providerCategory ) );
526 }
527 mModel->addCategory( category );
528 }
529 mBlockChangedSignal = false;
530 emitWidgetChanged();
531}
532
533void QgsPointCloudClassifiedRendererWidget::addCategory()
534{
535 if ( !mModel )
536 return;
537
538 const QgsPointCloudCategory cat( mModel->categories().size(), QgsApplication::colorSchemeRegistry()->fetchRandomStyleColor(), QString(), true );
539 mModel->addCategory( cat );
540}
541
542void QgsPointCloudClassifiedRendererWidget::deleteCategories()
543{
544 const QList<int> categoryIndexes = selectedCategories();
545 mModel->deleteRows( categoryIndexes );
546}
547
548void QgsPointCloudClassifiedRendererWidget::deleteAllCategories()
549{
550 mModel->removeAllRows();
551}
552
553void QgsPointCloudClassifiedRendererWidget::setFromRenderer( const QgsPointCloudRenderer *r )
554{
555 mBlockChangedSignal = true;
556 if ( const QgsPointCloudClassifiedRenderer *classifiedRenderer = dynamic_cast< const QgsPointCloudClassifiedRenderer *>( r ) )
557 {
558 mModel->setRendererCategories( classifiedRenderer->categories() );
559 mAttributeComboBox->setAttribute( classifiedRenderer->attribute() );
560 }
561 else
562 {
563 initialize();
564 }
565 mBlockChangedSignal = false;
566 emitWidgetChanged();
567}
568
569void QgsPointCloudClassifiedRendererWidget::setFromCategories( QgsPointCloudCategoryList categories, const QString &attribute )
570{
571 mBlockChangedSignal = true;
572 mModel->setRendererCategories( categories );
573 if ( !attribute.isEmpty() )
574 {
575 mAttributeComboBox->setAttribute( attribute );
576 }
577 else
578 {
579 initialize();
580 }
581 mBlockChangedSignal = false;
582 emitWidgetChanged();
583}
584
585void QgsPointCloudClassifiedRendererWidget::initialize()
586{
587 if ( mAttributeComboBox->findText( QStringLiteral( "Classification" ) ) > -1 )
588 {
589 mAttributeComboBox->setAttribute( QStringLiteral( "Classification" ) );
590 }
591 else
592 {
593 mAttributeComboBox->setCurrentIndex( mAttributeComboBox->count() > 1 ? 1 : 0 );
594 }
595 mModel->removeAllRows();
596 addCategories();
597}
598
599void QgsPointCloudClassifiedRendererWidget::changeCategorySymbol()
600{
601 const int row = currentCategoryRow();
602 if ( row < 0 )
603 return;
604
605 const QgsPointCloudCategory category = mModel->categories().value( row );
606
608 if ( panel && panel->dockMode() )
609 {
611 colorWidget->setPanelTitle( category.label() );
612 colorWidget->setAllowOpacity( true );
613 colorWidget->setPreviousColor( category.color() );
614
615 connect( colorWidget, &QgsCompoundColorWidget::currentColorChanged, this, [ = ]( const QColor & newColor )
616 {
617 mModel->setCategoryColor( row, newColor );
618 } );
619 panel->openPanel( colorWidget );
620 }
621 else
622 {
623 const QColor newColor = QgsColorDialog::getColor( category.color(), this, category.label(), true );
624 if ( newColor.isValid() )
625 {
626 mModel->setCategoryColor( row, newColor );
627 }
628 }
629}
630
631QList<int> QgsPointCloudClassifiedRendererWidget::selectedCategories()
632{
633 QList<int> rows;
634 const QModelIndexList selectedRows = viewCategories->selectionModel()->selectedRows();
635 for ( const QModelIndex &r : selectedRows )
636 {
637 if ( r.isValid() )
638 {
639 rows.append( r.row() );
640 }
641 }
642 return rows;
643}
644
645int QgsPointCloudClassifiedRendererWidget::currentCategoryRow()
646{
647 const QModelIndex idx = viewCategories->selectionModel()->currentIndex();
648 if ( !idx.isValid() )
649 return -1;
650 return idx.row();
651}
652
653void QgsPointCloudClassifiedRendererWidget::updateCategoriesPercentages()
654{
655 QMap < int, float > percentages;
656
657 const QgsPointCloudStatistics stats = mLayer->statistics();
658 const QMap<int, int> classes = stats.availableClasses( attribute() );
659 const int pointCount = stats.sampledPointsCount();
660 const QgsPointCloudCategoryList currentCategories = mModel->categories();
661
662 // when the stats are 100% accurate, we are sure that missing classes have a 0% of points
663 const bool statsExact = stats.sampledPointsCount() == mLayer->pointCount();
664 for ( const QgsPointCloudCategory &category : currentCategories )
665 {
666 if ( classes.contains( category.value() ) || statsExact )
667 percentages.insert( category.value(), ( double ) classes.value( category.value() ) / pointCount * 100 );
668 }
669 mModel->updateCategoriesPercentages( percentages );
670}
static QgsColorSchemeRegistry * colorSchemeRegistry()
Returns the application's color scheme registry, used for managing color schemes.
static QColor getColor(const QColor &initialColor, QWidget *parent, const QString &title=QString(), bool allowOpacity=false)
Returns a color selection from a color dialog.
QColor fetchRandomStyleColor() const
Returns a random color for use with a new symbol style (e.g.
A custom QGIS widget for selecting a color, including options for selecting colors via hue wheel,...
@ LayoutVertical
Use a narrower, vertically stacked layout.
void currentColorChanged(const QColor &color)
Emitted when the dialog's color changes.
void setPreviousColor(const QColor &color)
Sets the color to show in an optional "previous color" section.
void setAllowOpacity(bool allowOpacity)
Sets whether opacity modification (transparency) is permitted for the color dialog.
Base class for any widget that can be shown as a inline panel.
void openPanel(QgsPanelWidget *panel)
Open a panel or dialog depending on dock mode setting If dock mode is true this method will emit the ...
static QgsPanelWidget * findParentPanel(QWidget *widget)
Traces through the parents of a widget to find if it is contained within a QgsPanelWidget widget.
void setPanelTitle(const QString &panelTitle)
Set the title of the panel when shown in the interface.
bool dockMode()
Returns the dock mode state.
void attributeChanged(const QString &name)
Emitted when the currently selected attribute changes.
Represents an individual category (class) from a QgsPointCloudClassifiedRenderer.
int value() const
Returns the value corresponding to this category.
bool renderState() const
Returns true if the category is currently enabled and should be rendered.
QColor color() const
Returns the color which will be used to render this category.
QString label() const
Returns the label for this category, which is used to represent the category within legends and the l...
Renders point clouds by a classification attribute.
Represents a map layer supporting display of point clouds.
QgsPointCloudRenderer * renderer()
Returns the 2D renderer for the point cloud.
static QgsPointCloudCategoryList classificationAttributeCategories(const QgsPointCloudLayer *layer)
Returns a list of categories using the available Classification classes of a specified layer,...
Base class for point cloud 2D renderer settings widgets.
Abstract base class for 2d point cloud renderers.
Class used to store statistics of a point cloud dataset.
QMap< int, int > availableClasses(const QString &attribute) const
Returns a map containing the count of each class of the attribute attribute If no matching statistic ...
QList< int > classesOf(const QString &attribute) const
Returns a list of existing classes which are present for the specified attribute.
int sampledPointsCount() const
Returns the number of points used to calculate the statistics.
A QProxyStyle subclass which correctly sets the base style to match the QGIS application style,...
QSize iconSize(bool dockableToolbar)
Returns the user-preferred size of a window's toolbar icons.
int scaleIconSize(int standardSize)
Scales an icon size to compensate for display pixel density, making the icon size hi-dpi friendly,...
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
#define str(x)
Definition qgis.cpp:38
QList< QgsPointCloudCategory > QgsPointCloudCategoryList