1 /***************************************************************************
2 qgspalettedrendererwidget.cpp
3 -----------------------------
4 begin : February 2012
5 copyright : (C) 2012 by Marco Hugentobler
6 email : marco at sourcepole dot ch
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
18 #include "qgspalettedrendererwidget.h"
19 #include "qgspalettedrasterrenderer.h"
20 #include "qgsrasterdataprovider.h"
21 #include "qgsrasterlayer.h"
22 #include "qgscolordialog.h"
23 #include "qgssettings.h"
24 #include "qgsproject.h"
25 #include "qgscolorrampshaderwidget.h"
26 #include "qgslocaleawarenumericlineeditdelegate.h"
27
28 #include <QColorDialog>
29 #include <QInputDialog>
30 #include <QFileDialog>
31 #include <QMessageBox>
32 #include <QMenu>
33 #include <QMimeData>
34 #include <QTextStream>
35
36 #ifdef ENABLE_MODELTEST
37 #include "modeltest.h"
38 #endif
39
40
QgsPalettedRendererWidget(QgsRasterLayer * layer,const QgsRectangle & extent)41 QgsPalettedRendererWidget::QgsPalettedRendererWidget( QgsRasterLayer *layer, const QgsRectangle &extent ): QgsRasterRendererWidget( layer, extent )
42 {
43 setupUi( this );
44
45 mCalculatingProgressBar->hide();
46 mCancelButton->hide();
47
48 mContextMenu = new QMenu( tr( "Options" ), this );
49 mContextMenu->addAction( tr( "Change Color…" ), this, SLOT( changeColor() ) );
50 mContextMenu->addAction( tr( "Change Opacity…" ), this, SLOT( changeOpacity() ) );
51 mContextMenu->addAction( tr( "Change Label…" ), this, SLOT( changeLabel() ) );
52
53 mAdvancedMenu = new QMenu( tr( "Advanced Options" ), this );
54 QAction *mLoadFromLayerAction = mAdvancedMenu->addAction( tr( "Load Classes from Layer" ) );
55 connect( mLoadFromLayerAction, &QAction::triggered, this, &QgsPalettedRendererWidget::loadFromLayer );
56 QAction *loadFromFile = mAdvancedMenu->addAction( tr( "Load Color Map from File…" ) );
57 connect( loadFromFile, &QAction::triggered, this, &QgsPalettedRendererWidget::loadColorTable );
58 QAction *exportToFile = mAdvancedMenu->addAction( tr( "Export Color Map to File…" ) );
59 connect( exportToFile, &QAction::triggered, this, &QgsPalettedRendererWidget::saveColorTable );
60
61
62 mButtonAdvanced->setMenu( mAdvancedMenu );
63
64 mModel = new QgsPalettedRendererModel( this );
65 mProxyModel = new QgsPalettedRendererProxyModel( this );
66 mProxyModel->setSourceModel( mModel );
67 mTreeView->setSortingEnabled( false );
68 mTreeView->setModel( mProxyModel );
69
70 connect( this, &QgsPalettedRendererWidget::widgetChanged, this, [ = ]
71 {
72 mProxyModel->sort( QgsPalettedRendererModel::Column::ValueColumn );
73 } );
74
75 #ifdef ENABLE_MODELTEST
76 new ModelTest( mModel, this );
77 #endif
78
79 mTreeView->setItemDelegateForColumn( QgsPalettedRendererModel::ColorColumn, new QgsColorSwatchDelegate( this ) );
80 mValueDelegate = new QgsLocaleAwareNumericLineEditDelegate( Qgis::DataType::UnknownDataType, this );
81 mTreeView->setItemDelegateForColumn( QgsPalettedRendererModel::ValueColumn, mValueDelegate );
82
83 mTreeView->setColumnWidth( QgsPalettedRendererModel::ColorColumn, Qgis::UI_SCALE_FACTOR * fontMetrics().horizontalAdvance( 'X' ) * 6.6 );
84 mTreeView->setContextMenuPolicy( Qt::CustomContextMenu );
85 mTreeView->setSelectionMode( QAbstractItemView::ExtendedSelection );
86 mTreeView->setDragEnabled( true );
87 mTreeView->setAcceptDrops( true );
88 mTreeView->setDropIndicatorShown( true );
89 mTreeView->setDragDropMode( QAbstractItemView::InternalMove );
90 mTreeView->setSelectionBehavior( QAbstractItemView::SelectRows );
91 mTreeView->setDefaultDropAction( Qt::MoveAction );
92
93 connect( mTreeView, &QTreeView::customContextMenuRequested, this, [ = ]( QPoint ) { mContextMenu->exec( QCursor::pos() ); } );
94
95 btnColorRamp->setShowRandomColorRamp( true );
96
97 connect( btnColorRamp, &QgsColorRampButton::colorRampChanged, this, &QgsPalettedRendererWidget::applyColorRamp );
98
99 mBandComboBox->setLayer( mRasterLayer );
100
101 if ( mRasterLayer )
102 {
103 QgsRasterDataProvider *provider = mRasterLayer->dataProvider();
104 if ( !provider )
105 {
106 return;
107 }
108 setFromRenderer( mRasterLayer->renderer() );
109 }
110
111 connect( mBandComboBox, &QgsRasterBandComboBox::bandChanged, this, &QgsRasterRendererWidget::widgetChanged );
112 connect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
113 connect( mDeleteEntryButton, &QPushButton::clicked, this, &QgsPalettedRendererWidget::deleteEntry );
114 connect( mButtonDeleteAll, &QPushButton::clicked, mModel, &QgsPalettedRendererModel::deleteAll );
115 connect( mAddEntryButton, &QPushButton::clicked, this, &QgsPalettedRendererWidget::addEntry );
116 connect( mClassifyButton, &QPushButton::clicked, this, &QgsPalettedRendererWidget::classify );
117
118 if ( mRasterLayer && mRasterLayer->dataProvider() )
119 {
120 mLoadFromLayerAction->setEnabled( !mRasterLayer->dataProvider()->colorTable( mBandComboBox->currentBand() ).isEmpty() );
121 }
122 else
123 {
124 mLoadFromLayerAction->setEnabled( false );
125 }
126
127 connect( QgsProject::instance(), static_cast < void ( QgsProject::* )( QgsMapLayer * ) >( &QgsProject::layerWillBeRemoved ), this, &QgsPalettedRendererWidget::layerWillBeRemoved );
128 connect( mBandComboBox, &QgsRasterBandComboBox::bandChanged, this, &QgsPalettedRendererWidget::bandChanged );
129 }
130
~QgsPalettedRendererWidget()131 QgsPalettedRendererWidget::~QgsPalettedRendererWidget()
132 {
133 if ( mGatherer )
134 {
135 mGatherer->stop();
136 mGatherer->wait(); // mGatherer is deleted when wait completes
137 }
138 }
139
renderer()140 QgsRasterRenderer *QgsPalettedRendererWidget::renderer()
141 {
142 QgsPalettedRasterRenderer::ClassData classes = mProxyModel->classData();
143 int bandNumber = mBandComboBox->currentBand();
144
145 QgsPalettedRasterRenderer *r = new QgsPalettedRasterRenderer( mRasterLayer->dataProvider(), bandNumber, classes );
146 if ( !btnColorRamp->isNull() )
147 {
148 r->setSourceColorRamp( btnColorRamp->colorRamp() );
149 }
150 return r;
151 }
152
setFromRenderer(const QgsRasterRenderer * r)153 void QgsPalettedRendererWidget::setFromRenderer( const QgsRasterRenderer *r )
154 {
155 const QgsPalettedRasterRenderer *pr = dynamic_cast<const QgsPalettedRasterRenderer *>( r );
156 if ( pr )
157 {
158 mBand = pr->band();
159 whileBlocking( mBandComboBox )->setBand( mBand );
160
161 //read values and colors and fill into tree widget
162 mModel->setClassData( pr->classes() );
163
164 if ( pr->sourceColorRamp() )
165 {
166 whileBlocking( btnColorRamp )->setColorRamp( pr->sourceColorRamp() );
167 }
168 else
169 {
170 std::unique_ptr< QgsColorRamp > ramp( new QgsRandomColorRamp() );
171 whileBlocking( btnColorRamp )->setColorRamp( ramp.get() );
172 }
173 }
174 else
175 {
176 loadFromLayer();
177 std::unique_ptr< QgsColorRamp > ramp( new QgsRandomColorRamp() );
178 whileBlocking( btnColorRamp )->setColorRamp( ramp.get() );
179 }
180
181 if ( mRasterLayer && mRasterLayer->dataProvider() )
182 {
183 mValueDelegate->setDataType( mRasterLayer->dataProvider()->dataType( mBand ) );
184 }
185 }
186
setSelectionColor(const QItemSelection & selection,const QColor & color)187 void QgsPalettedRendererWidget::setSelectionColor( const QItemSelection &selection, const QColor &color )
188 {
189 // don't want to emit widgetChanged multiple times
190 disconnect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
191
192 QModelIndex colorIndex;
193 const auto constSelection = selection;
194 for ( const QItemSelectionRange &range : constSelection )
195 {
196 const auto constIndexes = range.indexes();
197 for ( const QModelIndex &index : constIndexes )
198 {
199 colorIndex = mModel->index( index.row(), QgsPalettedRendererModel::ColorColumn );
200 mModel->setData( colorIndex, color, Qt::EditRole );
201 }
202 }
203 connect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
204
205 emit widgetChanged();
206 }
207
deleteEntry()208 void QgsPalettedRendererWidget::deleteEntry()
209 {
210 // don't want to emit widgetChanged multiple times
211 disconnect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
212
213 QItemSelection sel = mProxyModel->mapSelectionToSource( mTreeView->selectionModel()->selection() );
214 const auto constSel = sel;
215 for ( const QItemSelectionRange &range : constSel )
216 {
217 if ( range.isValid() )
218 mModel->removeRows( range.top(), range.bottom() - range.top() + 1, range.parent() );
219 }
220
221 connect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
222
223 emit widgetChanged();
224 }
225
addEntry()226 void QgsPalettedRendererWidget::addEntry()
227 {
228 disconnect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
229
230 QColor color( 150, 150, 150 );
231 std::unique_ptr< QgsColorRamp > ramp( btnColorRamp->colorRamp() );
232 if ( ramp )
233 {
234 color = ramp->color( 1.0 );
235 }
236 QModelIndex newEntry = mModel->addEntry( color );
237 mTreeView->scrollTo( newEntry );
238 mTreeView->selectionModel()->select( mProxyModel->mapFromSource( newEntry ), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows );
239 connect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
240 emit widgetChanged();
241 }
242
changeColor()243 void QgsPalettedRendererWidget::changeColor()
244 {
245 QItemSelection sel = mProxyModel->mapSelectionToSource( mTreeView->selectionModel()->selection() );
246 QModelIndex colorIndex = mModel->index( sel.first().top(), QgsPalettedRendererModel::ColorColumn );
247 QColor currentColor = mModel->data( colorIndex, Qt::DisplayRole ).value<QColor>();
248
249 QgsPanelWidget *panel = QgsPanelWidget::findParentPanel( qobject_cast< QWidget * >( parent() ) );
250 if ( panel && panel->dockMode() )
251 {
252 QgsCompoundColorWidget *colorWidget = new QgsCompoundColorWidget( panel, currentColor, QgsCompoundColorWidget::LayoutVertical );
253 colorWidget->setPanelTitle( tr( "Select Color" ) );
254 colorWidget->setAllowOpacity( true );
255 connect( colorWidget, &QgsCompoundColorWidget::currentColorChanged, this, [ = ]( const QColor & color ) { setSelectionColor( sel, color ); } );
256 panel->openPanel( colorWidget );
257 }
258 else
259 {
260 // modal dialog version... yuck
261 QColor newColor = QgsColorDialog::getColor( currentColor, this, QStringLiteral( "Change color" ), true );
262 if ( newColor.isValid() )
263 {
264 setSelectionColor( sel, newColor );
265 }
266 }
267 }
268
changeOpacity()269 void QgsPalettedRendererWidget::changeOpacity()
270 {
271 QItemSelection sel = mProxyModel->mapSelectionToSource( mTreeView->selectionModel()->selection() );
272 QModelIndex colorIndex = mModel->index( sel.first().top(), QgsPalettedRendererModel::ColorColumn );
273 QColor currentColor = mModel->data( colorIndex, Qt::DisplayRole ).value<QColor>();
274
275 bool ok;
276 double oldOpacity = ( currentColor.alpha() / 255.0 ) * 100.0;
277 double opacity = QInputDialog::getDouble( this, tr( "Opacity" ), tr( "Change color opacity [%]" ), oldOpacity, 0.0, 100.0, 0, &ok );
278 if ( ok )
279 {
280 int newOpacity = opacity / 100 * 255;
281
282 // don't want to emit widgetChanged multiple times
283 disconnect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
284
285 const auto constSel = sel;
286 for ( const QItemSelectionRange &range : constSel )
287 {
288 const auto constIndexes = range.indexes();
289 for ( const QModelIndex &index : constIndexes )
290 {
291 colorIndex = mModel->index( index.row(), QgsPalettedRendererModel::ColorColumn );
292
293 QColor newColor = mModel->data( colorIndex, Qt::DisplayRole ).value<QColor>();
294 newColor.setAlpha( newOpacity );
295 mModel->setData( colorIndex, newColor, Qt::EditRole );
296 }
297 }
298 connect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
299
300 emit widgetChanged();
301 }
302 }
303
changeLabel()304 void QgsPalettedRendererWidget::changeLabel()
305 {
306 QItemSelection sel = mProxyModel->mapSelectionToSource( mTreeView->selectionModel()->selection() );
307 QModelIndex labelIndex = mModel->index( sel.first().top(), QgsPalettedRendererModel::LabelColumn );
308 QString currentLabel = mModel->data( labelIndex, Qt::DisplayRole ).toString();
309
310 bool ok;
311 QString newLabel = QInputDialog::getText( this, tr( "Label" ), tr( "Change label" ), QLineEdit::Normal, currentLabel, &ok );
312 if ( ok )
313 {
314 // don't want to emit widgetChanged multiple times
315 disconnect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
316
317 const auto constSel = sel;
318 for ( const QItemSelectionRange &range : constSel )
319 {
320 const auto constIndexes = range.indexes();
321 for ( const QModelIndex &index : constIndexes )
322 {
323 labelIndex = mModel->index( index.row(), QgsPalettedRendererModel::LabelColumn );
324 mModel->setData( labelIndex, newLabel, Qt::EditRole );
325 }
326 }
327 connect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
328
329 emit widgetChanged();
330 }
331 }
332
applyColorRamp()333 void QgsPalettedRendererWidget::applyColorRamp()
334 {
335 std::unique_ptr< QgsColorRamp > ramp( btnColorRamp->colorRamp() );
336 if ( !ramp )
337 {
338 return;
339 }
340
341 disconnect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
342
343 QgsPalettedRasterRenderer::ClassData data = mProxyModel->classData();
344 QgsPalettedRasterRenderer::ClassData::iterator cIt = data.begin();
345
346 double numberOfEntries = data.count();
347 int i = 0;
348
349 if ( QgsRandomColorRamp *randomRamp = dynamic_cast<QgsRandomColorRamp *>( ramp.get() ) )
350 {
351 //ramp is a random colors ramp, so inform it of the total number of required colors
352 //this allows the ramp to pregenerate a set of visually distinctive colors
353 randomRamp->setTotalColorCount( numberOfEntries );
354 }
355
356 if ( numberOfEntries > 1 )
357 numberOfEntries -= 1; //avoid duplicate first color
358
359 for ( ; cIt != data.end(); ++cIt )
360 {
361 cIt->color = ramp->color( i / numberOfEntries );
362 i++;
363 }
364 mModel->setClassData( data );
365
366 connect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
367 emit widgetChanged();
368 }
369
loadColorTable()370 void QgsPalettedRendererWidget::loadColorTable()
371 {
372 QgsSettings settings;
373 QString lastDir = settings.value( QStringLiteral( "lastColorMapDir" ), QDir::homePath() ).toString();
374 QString fileName = QFileDialog::getOpenFileName( this, tr( "Load Color Table from File" ), lastDir );
375 if ( !fileName.isEmpty() )
376 {
377 QgsPalettedRasterRenderer::ClassData classes = QgsPalettedRasterRenderer::classDataFromFile( fileName );
378 if ( !classes.isEmpty() )
379 {
380 disconnect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
381 mModel->setClassData( classes );
382 emit widgetChanged();
383 connect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
384 }
385 else
386 {
387 QMessageBox::critical( nullptr, tr( "Load Color Table" ), tr( "Could not interpret file as a raster color table." ) );
388 }
389 }
390 }
391
saveColorTable()392 void QgsPalettedRendererWidget::saveColorTable()
393 {
394 QgsSettings settings;
395 QString lastDir = settings.value( QStringLiteral( "lastColorMapDir" ), QDir::homePath() ).toString();
396 QString fileName = QFileDialog::getSaveFileName( this, tr( "Save Color Table as File" ), lastDir, tr( "Text (*.clr)" ) );
397 if ( !fileName.isEmpty() )
398 {
399 if ( !fileName.endsWith( QLatin1String( ".clr" ), Qt::CaseInsensitive ) )
400 {
401 fileName = fileName + ".clr";
402 }
403
404 QFile outputFile( fileName );
405 if ( outputFile.open( QFile::WriteOnly | QIODevice::Truncate ) )
406 {
407 QTextStream outputStream( &outputFile );
408 outputStream << QgsPalettedRasterRenderer::classDataToString( mProxyModel->classData() );
409 outputStream.flush();
410 outputFile.close();
411
412 QFileInfo fileInfo( fileName );
413 settings.setValue( QStringLiteral( "lastColorMapDir" ), fileInfo.absoluteDir().absolutePath() );
414 }
415 else
416 {
417 QMessageBox::warning( this, tr( "Save Color Table as File" ), tr( "Write access denied. Adjust the file permissions and try again.\n\n" ) );
418 }
419 }
420 }
421
classify()422 void QgsPalettedRendererWidget::classify()
423 {
424 if ( mRasterLayer )
425 {
426 QgsRasterDataProvider *provider = mRasterLayer->dataProvider();
427 if ( !provider )
428 {
429 return;
430 }
431
432 if ( mGatherer )
433 {
434 mGatherer->stop();
435 return;
436 }
437
438 mGatherer = new QgsPalettedRendererClassGatherer( mRasterLayer, mBandComboBox->currentBand(), mModel->classData(), btnColorRamp->colorRamp() );
439
440 connect( mGatherer, &QgsPalettedRendererClassGatherer::progressChanged, mCalculatingProgressBar, [ = ]( int progress )
441 {
442 mCalculatingProgressBar->setValue( progress );
443 } );
444
445 mCalculatingProgressBar->show();
446 mCancelButton->show();
447 connect( mCancelButton, &QPushButton::clicked, mGatherer, &QgsPalettedRendererClassGatherer::stop );
448
449 connect( mGatherer, &QgsPalettedRendererClassGatherer::collectedClasses, this, &QgsPalettedRendererWidget::gatheredClasses );
450 connect( mGatherer, &QgsPalettedRendererClassGatherer::finished, this, &QgsPalettedRendererWidget::gathererThreadFinished );
451 mClassifyButton->setText( tr( "Calculating…" ) );
452 mClassifyButton->setEnabled( false );
453 mGatherer->start();
454 }
455 }
456
loadFromLayer()457 void QgsPalettedRendererWidget::loadFromLayer()
458 {
459 //read default palette settings from layer
460 QgsRasterDataProvider *provider = mRasterLayer->dataProvider();
461 if ( provider )
462 {
463 QList<QgsColorRampShader::ColorRampItem> table = provider->colorTable( mBandComboBox->currentBand() );
464 if ( !table.isEmpty() )
465 {
466 QgsPalettedRasterRenderer::ClassData classes = QgsPalettedRasterRenderer::colorTableToClassData( provider->colorTable( mBandComboBox->currentBand() ) );
467 mModel->setClassData( classes );
468 emit widgetChanged();
469 }
470 }
471 }
472
bandChanged(int band)473 void QgsPalettedRendererWidget::bandChanged( int band )
474 {
475 if ( band == mBand )
476 return;
477
478 if ( mRasterLayer && mRasterLayer->dataProvider( ) )
479 {
480 mValueDelegate->setDataType( mRasterLayer->dataProvider( )->dataType( mBand ) );
481 }
482
483 bool deleteExisting = false;
484 if ( !mModel->classData().isEmpty() )
485 {
486 int res = QMessageBox::question( this,
487 tr( "Delete Classification" ),
488 tr( "The classification band was changed from %1 to %2.\n"
489 "Should the existing classes be deleted?" ).arg( mBand ).arg( band ),
490 QMessageBox::Yes | QMessageBox::No );
491
492 deleteExisting = ( res == QMessageBox::Yes );
493 }
494
495 mBand = band;
496 mModel->blockSignals( true );
497 if ( deleteExisting )
498 mModel->deleteAll();
499
500 mModel->blockSignals( false );
501 emit widgetChanged();
502 }
503
gatheredClasses()504 void QgsPalettedRendererWidget::gatheredClasses()
505 {
506 if ( !mGatherer || mGatherer->wasCanceled() )
507 return;
508
509 mModel->setClassData( mGatherer->classes() );
510 emit widgetChanged();
511 }
512
gathererThreadFinished()513 void QgsPalettedRendererWidget::gathererThreadFinished()
514 {
515 mGatherer->deleteLater();
516 mGatherer = nullptr;
517 mClassifyButton->setText( tr( "Classify" ) );
518 mClassifyButton->setEnabled( true );
519 mCalculatingProgressBar->hide();
520 mCancelButton->hide();
521 }
522
layerWillBeRemoved(QgsMapLayer * layer)523 void QgsPalettedRendererWidget::layerWillBeRemoved( QgsMapLayer *layer )
524 {
525 if ( mGatherer && mRasterLayer == layer )
526 {
527 mGatherer->stop();
528 mGatherer->wait();
529 }
530 }
531
532 //
533 // QgsPalettedRendererModel
534 //
535
536 ///@cond PRIVATE
QgsPalettedRendererModel(QObject * parent)537 QgsPalettedRendererModel::QgsPalettedRendererModel( QObject *parent )
538 : QAbstractItemModel( parent )
539 {
540
541 }
542
setClassData(const QgsPalettedRasterRenderer::ClassData & data)543 void QgsPalettedRendererModel::setClassData( const QgsPalettedRasterRenderer::ClassData &data )
544 {
545 beginResetModel();
546 mData = data;
547 endResetModel();
548 }
549
index(int row,int column,const QModelIndex & parent) const550 QModelIndex QgsPalettedRendererModel::index( int row, int column, const QModelIndex &parent ) const
551 {
552 if ( column < 0 || column >= columnCount() )
553 {
554 //column out of bounds
555 return QModelIndex();
556 }
557
558 if ( !parent.isValid() && row >= 0 && row < mData.size() )
559 {
560 //return an index for the item at this position
561 return createIndex( row, column );
562 }
563
564 //only top level supported
565 return QModelIndex();
566 }
567
parent(const QModelIndex & index) const568 QModelIndex QgsPalettedRendererModel::parent( const QModelIndex &index ) const
569 {
570 Q_UNUSED( index )
571
572 //all items are top level
573 return QModelIndex();
574 }
575
columnCount(const QModelIndex & parent) const576 int QgsPalettedRendererModel::columnCount( const QModelIndex &parent ) const
577 {
578 if ( parent.isValid() )
579 return 0;
580
581 return 3;
582 }
583
rowCount(const QModelIndex & parent) const584 int QgsPalettedRendererModel::rowCount( const QModelIndex &parent ) const
585 {
586 if ( parent.isValid() )
587 return 0;
588
589 return mData.count();
590 }
591
data(const QModelIndex & index,int role) const592 QVariant QgsPalettedRendererModel::data( const QModelIndex &index, int role ) const
593 {
594 if ( !index.isValid() )
595 return QVariant();
596
597 switch ( role )
598 {
599 case Qt::DisplayRole:
600 case Qt::EditRole:
601 {
602 switch ( index.column() )
603 {
604 case ValueColumn:
605 return mData.at( index.row() ).value;
606
607 case ColorColumn:
608 return mData.at( index.row() ).color;
609
610 case LabelColumn:
611 return mData.at( index.row() ).label;
612 }
613 }
614
615 default:
616 break;
617 }
618
619 return QVariant();
620 }
621
headerData(int section,Qt::Orientation orientation,int role) const622 QVariant QgsPalettedRendererModel::headerData( int section, Qt::Orientation orientation, int role ) const
623 {
624 switch ( orientation )
625 {
626 case Qt::Vertical:
627 return QVariant();
628
629 case Qt::Horizontal:
630 {
631 switch ( role )
632 {
633 case Qt::DisplayRole:
634 {
635 switch ( section )
636 {
637 case ValueColumn:
638 return tr( "Value" );
639
640 case ColorColumn:
641 return tr( "Color" );
642
643 case LabelColumn:
644 return tr( "Label" );
645 }
646 }
647
648 }
649 break;
650 }
651
652 default:
653 return QAbstractItemModel::headerData( section, orientation, role );
654 }
655 return QAbstractItemModel::headerData( section, orientation, role );
656 }
657
setData(const QModelIndex & index,const QVariant & value,int)658 bool QgsPalettedRendererModel::setData( const QModelIndex &index, const QVariant &value, int )
659 {
660 if ( !index.isValid() )
661 return false;
662 if ( index.row() >= mData.length() )
663 return false;
664
665 switch ( index.column() )
666 {
667 case ValueColumn:
668 {
669 bool ok = false;
670 double newValue = value.toDouble( &ok );
671 if ( !ok )
672 return false;
673
674 mData[ index.row() ].value = newValue;
675 emit dataChanged( index, index );
676 emit classesChanged();
677 return true;
678 }
679
680 case ColorColumn:
681 {
682 mData[ index.row() ].color = value.value<QColor>();
683 emit dataChanged( index, index );
684 emit classesChanged();
685 return true;
686 }
687
688 case LabelColumn:
689 {
690 mData[ index.row() ].label = value.toString();
691 emit dataChanged( index, index );
692 emit classesChanged();
693 return true;
694 }
695 }
696
697 return false;
698 }
699
flags(const QModelIndex & index) const700 Qt::ItemFlags QgsPalettedRendererModel::flags( const QModelIndex &index ) const
701 {
702 if ( !index.isValid() )
703 return QAbstractItemModel::flags( index ) | Qt::ItemIsDropEnabled;
704
705 Qt::ItemFlags f = QAbstractItemModel::flags( index );
706 switch ( index.column() )
707 {
708 case ValueColumn:
709 case LabelColumn:
710 case ColorColumn:
711 f = f | Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsDragEnabled;
712 break;
713 }
714 return f | Qt::ItemIsEnabled | Qt::ItemIsSelectable;
715 }
716
removeRows(int row,int count,const QModelIndex & parent)717 bool QgsPalettedRendererModel::removeRows( int row, int count, const QModelIndex &parent )
718 {
719 if ( row < 0 || row >= mData.count() )
720 return false;
721 if ( parent.isValid() )
722 return false;
723
724 for ( int i = row + count - 1; i >= row; --i )
725 {
726 beginRemoveRows( parent, i, i );
727 mData.removeAt( i );
728 endRemoveRows();
729 }
730 emit classesChanged();
731 return true;
732 }
733
insertRows(int row,int count,const QModelIndex &)734 bool QgsPalettedRendererModel::insertRows( int row, int count, const QModelIndex & )
735 {
736 QgsPalettedRasterRenderer::ClassData::const_iterator cIt = mData.constBegin();
737 int currentMaxValue = -std::numeric_limits<int>::max();
738 for ( ; cIt != mData.constEnd(); ++cIt )
739 {
740 int value = cIt->value;
741 currentMaxValue = std::max( value, currentMaxValue );
742 }
743 int nextValue = std::max( 0, currentMaxValue + 1 );
744
745 beginInsertRows( QModelIndex(), row, row + count - 1 );
746 for ( int i = row; i < row + count; ++i, ++nextValue )
747 {
748 mData.insert( i, QgsPalettedRasterRenderer::Class( nextValue, QColor( 200, 200, 200 ), QLocale().toString( nextValue ) ) );
749 }
750 endInsertRows();
751 emit classesChanged();
752 return true;
753 }
754
supportedDropActions() const755 Qt::DropActions QgsPalettedRendererModel::supportedDropActions() const
756 {
757 return Qt::MoveAction;
758 }
759
mimeTypes() const760 QStringList QgsPalettedRendererModel::mimeTypes() const
761 {
762 QStringList types;
763 types << QStringLiteral( "application/x-qgspalettedrenderermodel" );
764 return types;
765 }
766
mimeData(const QModelIndexList & indexes) const767 QMimeData *QgsPalettedRendererModel::mimeData( const QModelIndexList &indexes ) const
768 {
769 QMimeData *mimeData = new QMimeData();
770 QByteArray encodedData;
771
772 QDataStream stream( &encodedData, QIODevice::WriteOnly );
773
774 // Create list of rows
775 const auto constIndexes = indexes;
776 for ( const QModelIndex &index : constIndexes )
777 {
778 if ( !index.isValid() || index.column() != 0 )
779 continue;
780
781 stream << index.row();
782 }
783 mimeData->setData( QStringLiteral( "application/x-qgspalettedrenderermodel" ), encodedData );
784 return mimeData;
785 }
786
dropMimeData(const QMimeData * data,Qt::DropAction action,int row,int column,const QModelIndex &)787 bool QgsPalettedRendererModel::dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex & )
788 {
789 Q_UNUSED( column )
790 if ( action != Qt::MoveAction ) return true;
791
792 if ( !data->hasFormat( QStringLiteral( "application/x-qgspalettedrenderermodel" ) ) )
793 return false;
794
795 QByteArray encodedData = data->data( QStringLiteral( "application/x-qgspalettedrenderermodel" ) );
796 QDataStream stream( &encodedData, QIODevice::ReadOnly );
797
798 QVector<int> rows;
799 while ( !stream.atEnd() )
800 {
801 int r;
802 stream >> r;
803 rows.append( r );
804 }
805
806 QgsPalettedRasterRenderer::ClassData newData;
807 for ( int i = 0; i < rows.count(); ++i )
808 newData << mData.at( rows.at( i ) );
809
810 if ( row < 0 )
811 row = mData.count();
812
813 beginInsertRows( QModelIndex(), row, row + rows.count() - 1 );
814 for ( int i = 0; i < rows.count(); ++i )
815 mData.insert( row + i, newData.at( i ) );
816 endInsertRows();
817 emit classesChanged();
818 return true;
819 }
820
addEntry(const QColor & color)821 QModelIndex QgsPalettedRendererModel::addEntry( const QColor &color )
822 {
823 insertRow( rowCount() );
824 QModelIndex newRow = index( mData.count() - 1, 1 );
825 setData( newRow, color );
826 return newRow;
827 }
828
deleteAll()829 void QgsPalettedRendererModel::deleteAll()
830 {
831 beginResetModel();
832 mData.clear();
833 endResetModel();
834 emit classesChanged();
835 }
836
run()837 void QgsPalettedRendererClassGatherer::run()
838 {
839 mWasCanceled = false;
840
841 // allow responsive cancellation
842 mFeedback = new QgsRasterBlockFeedback();
843 connect( mFeedback, &QgsRasterBlockFeedback::progressChanged, this, &QgsPalettedRendererClassGatherer::progressChanged );
844
845 QgsPalettedRasterRenderer::ClassData newClasses = QgsPalettedRasterRenderer::classDataFromRaster( mLayer->dataProvider(), mBandNumber, mRamp.get(), mFeedback );
846
847 // combine existing classes with new classes
848 QgsPalettedRasterRenderer::ClassData::iterator classIt = newClasses.begin();
849 emit progressChanged( 0 );
850 qlonglong i = 0;
851 for ( ; classIt != newClasses.end(); ++classIt )
852 {
853 // check if existing classes contains this same class
854 for ( const QgsPalettedRasterRenderer::Class &existingClass : std::as_const( mClasses ) )
855 {
856 if ( existingClass.value == classIt->value )
857 {
858 classIt->color = existingClass.color;
859 classIt->label = existingClass.label;
860 break;
861 }
862 }
863 i ++;
864 emit progressChanged( 100 * ( i / static_cast<float>( newClasses.count() ) ) );
865 }
866 mClasses = newClasses;
867
868 // be overly cautious - it's *possible* stop() might be called between deleting mFeedback and nulling it
869 mFeedbackMutex.lock();
870 delete mFeedback;
871 mFeedback = nullptr;
872 mFeedbackMutex.unlock();
873
874 emit collectedClasses();
875 }
876
877
classData() const878 QgsPalettedRasterRenderer::ClassData QgsPalettedRendererProxyModel::classData() const
879 {
880 QgsPalettedRasterRenderer::ClassData data;
881 for ( int i = 0; i < rowCount( ); ++i )
882 {
883 data.push_back( qobject_cast<QgsPalettedRendererModel *>( sourceModel() )->classAtIndex( mapToSource( index( i, 0 ) ) ) );
884 }
885 return data;
886 }
887
888
889 ///@endcond PRIVATE
890