1 /***************************************************************************
2 qgscompoundcolorwidget.cpp
3 --------------------------
4 begin : April 2016
5 copyright : (C) 2016 by Nyall Dawson
6 email : nyall dot dawson at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16 #include "qgscompoundcolorwidget.h"
17 #include "qgscolorscheme.h"
18 #include "qgscolorschemeregistry.h"
19 #include "qgssymbollayerutils.h"
20 #include "qgsapplication.h"
21 #include "qgssettings.h"
22
23 #include <QHeaderView>
24 #include <QPushButton>
25 #include <QMenu>
26 #include <QToolButton>
27 #include <QFileDialog>
28 #include <QMessageBox>
29 #include <QDesktopWidget>
30 #include <QMouseEvent>
31 #include <QScreen>
32 #include <QInputDialog>
33 #include <QVBoxLayout>
34
QgsCompoundColorWidget(QWidget * parent,const QColor & color,Layout widgetLayout)35 QgsCompoundColorWidget::QgsCompoundColorWidget( QWidget *parent, const QColor &color, Layout widgetLayout )
36 : QgsPanelWidget( parent )
37 {
38 setupUi( this );
39 connect( mHueRadio, &QRadioButton::toggled, this, &QgsCompoundColorWidget::mHueRadio_toggled );
40 connect( mSaturationRadio, &QRadioButton::toggled, this, &QgsCompoundColorWidget::mSaturationRadio_toggled );
41 connect( mValueRadio, &QRadioButton::toggled, this, &QgsCompoundColorWidget::mValueRadio_toggled );
42 connect( mRedRadio, &QRadioButton::toggled, this, &QgsCompoundColorWidget::mRedRadio_toggled );
43 connect( mGreenRadio, &QRadioButton::toggled, this, &QgsCompoundColorWidget::mGreenRadio_toggled );
44 connect( mBlueRadio, &QRadioButton::toggled, this, &QgsCompoundColorWidget::mBlueRadio_toggled );
45 connect( mAddColorToSchemeButton, &QPushButton::clicked, this, &QgsCompoundColorWidget::mAddColorToSchemeButton_clicked );
46 connect( mAddCustomColorButton, &QPushButton::clicked, this, &QgsCompoundColorWidget::mAddCustomColorButton_clicked );
47 connect( mSampleButton, &QPushButton::clicked, this, &QgsCompoundColorWidget::mSampleButton_clicked );
48 connect( mTabWidget, &QTabWidget::currentChanged, this, &QgsCompoundColorWidget::mTabWidget_currentChanged );
49 connect( mActionShowInButtons, &QAction::toggled, this, &QgsCompoundColorWidget::mActionShowInButtons_toggled );
50
51 if ( widgetLayout == LayoutVertical )
52 {
53 // shuffle stuff around
54 QVBoxLayout *newLayout = new QVBoxLayout();
55 newLayout->setContentsMargins( 0, 0, 0, 0 );
56 newLayout->addWidget( mTabWidget );
57 newLayout->addWidget( mSlidersWidget );
58 newLayout->addWidget( mPreviewWidget );
59 newLayout->addWidget( mSwatchesWidget );
60 delete layout();
61 setLayout( newLayout );
62 }
63
64 QgsSettings settings;
65
66 mSchemeList->header()->hide();
67 #if QT_VERSION < QT_VERSION_CHECK(5, 11, 0)
68 mSchemeList->setColumnWidth( 0, static_cast< int >( Qgis::UI_SCALE_FACTOR * fontMetrics().width( 'X' ) * 6 ) );
69 #else
70 mSchemeList->setColumnWidth( 0, static_cast< int >( Qgis::UI_SCALE_FACTOR * fontMetrics().horizontalAdvance( 'X' ) * 6 ) );
71 #endif
72
73
74 //get schemes with ShowInColorDialog set
75 refreshSchemeComboBox();
76 QList<QgsColorScheme *> schemeList = QgsApplication::colorSchemeRegistry()->schemes( QgsColorScheme::ShowInColorDialog );
77
78 //choose a reasonable starting scheme
79 int activeScheme = settings.value( QStringLiteral( "Windows/ColorDialog/activeScheme" ), 0 ).toInt();
80 activeScheme = activeScheme >= mSchemeComboBox->count() ? 0 : activeScheme;
81
82 mSchemeList->setScheme( schemeList.at( activeScheme ) );
83
84 mSchemeComboBox->setCurrentIndex( activeScheme );
85 updateActionsForCurrentScheme();
86
87 //listen out for selection changes in list, so we can enable/disable the copy colors option
88 connect( mSchemeList->selectionModel(), &QItemSelectionModel::selectionChanged, this, &QgsCompoundColorWidget::listSelectionChanged );
89 //copy action defaults to disabled
90 mActionCopyColors->setEnabled( false );
91
92 connect( mActionCopyColors, &QAction::triggered, mSchemeList, &QgsColorSchemeList::copyColors );
93 connect( mActionPasteColors, &QAction::triggered, mSchemeList, &QgsColorSchemeList::pasteColors );
94 connect( mActionExportColors, &QAction::triggered, mSchemeList, &QgsColorSchemeList::showExportColorsDialog );
95 connect( mActionImportColors, &QAction::triggered, mSchemeList, &QgsColorSchemeList::showImportColorsDialog );
96 connect( mActionImportPalette, &QAction::triggered, this, &QgsCompoundColorWidget::importPalette );
97 connect( mActionRemovePalette, &QAction::triggered, this, &QgsCompoundColorWidget::removePalette );
98 connect( mActionNewPalette, &QAction::triggered, this, &QgsCompoundColorWidget::newPalette );
99 connect( mRemoveColorsFromSchemeButton, &QAbstractButton::clicked, mSchemeList, &QgsColorSchemeList::removeSelection );
100
101 QMenu *schemeMenu = new QMenu( mSchemeToolButton );
102 schemeMenu->addAction( mActionCopyColors );
103 schemeMenu->addAction( mActionPasteColors );
104 schemeMenu->addSeparator();
105 schemeMenu->addAction( mActionImportColors );
106 schemeMenu->addAction( mActionExportColors );
107 schemeMenu->addSeparator();
108 schemeMenu->addAction( mActionNewPalette );
109 schemeMenu->addAction( mActionImportPalette );
110 schemeMenu->addAction( mActionRemovePalette );
111 schemeMenu->addAction( mActionShowInButtons );
112 mSchemeToolButton->setMenu( schemeMenu );
113
114 connect( mSchemeComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsCompoundColorWidget::schemeIndexChanged );
115 connect( mSchemeList, &QgsColorSchemeList::colorSelected, this, &QgsCompoundColorWidget::setColor );
116
117 mOldColorLabel->hide();
118
119 mVerticalRamp->setOrientation( QgsColorRampWidget::Vertical );
120 mVerticalRamp->setInteriorMargin( 2 );
121 mVerticalRamp->setShowFrame( true );
122
123 mRedSlider->setComponent( QgsColorWidget::Red );
124 mGreenSlider->setComponent( QgsColorWidget::Green );
125 mBlueSlider->setComponent( QgsColorWidget::Blue );
126 mHueSlider->setComponent( QgsColorWidget::Hue );
127 mSaturationSlider->setComponent( QgsColorWidget::Saturation );
128 mValueSlider->setComponent( QgsColorWidget::Value );
129 mAlphaSlider->setComponent( QgsColorWidget::Alpha );
130
131 mSwatchButton1->setShowMenu( false );
132 mSwatchButton1->setBehavior( QgsColorButton::SignalOnly );
133 mSwatchButton2->setShowMenu( false );
134 mSwatchButton2->setBehavior( QgsColorButton::SignalOnly );
135 mSwatchButton3->setShowMenu( false );
136 mSwatchButton3->setBehavior( QgsColorButton::SignalOnly );
137 mSwatchButton4->setShowMenu( false );
138 mSwatchButton4->setBehavior( QgsColorButton::SignalOnly );
139 mSwatchButton5->setShowMenu( false );
140 mSwatchButton5->setBehavior( QgsColorButton::SignalOnly );
141 mSwatchButton6->setShowMenu( false );
142 mSwatchButton6->setBehavior( QgsColorButton::SignalOnly );
143 mSwatchButton7->setShowMenu( false );
144 mSwatchButton7->setBehavior( QgsColorButton::SignalOnly );
145 mSwatchButton8->setShowMenu( false );
146 mSwatchButton8->setBehavior( QgsColorButton::SignalOnly );
147 mSwatchButton9->setShowMenu( false );
148 mSwatchButton9->setBehavior( QgsColorButton::SignalOnly );
149 mSwatchButton10->setShowMenu( false );
150 mSwatchButton10->setBehavior( QgsColorButton::SignalOnly );
151 mSwatchButton11->setShowMenu( false );
152 mSwatchButton11->setBehavior( QgsColorButton::SignalOnly );
153 mSwatchButton12->setShowMenu( false );
154 mSwatchButton12->setBehavior( QgsColorButton::SignalOnly );
155 mSwatchButton13->setShowMenu( false );
156 mSwatchButton13->setBehavior( QgsColorButton::SignalOnly );
157 mSwatchButton14->setShowMenu( false );
158 mSwatchButton14->setBehavior( QgsColorButton::SignalOnly );
159 mSwatchButton15->setShowMenu( false );
160 mSwatchButton15->setBehavior( QgsColorButton::SignalOnly );
161 mSwatchButton16->setShowMenu( false );
162 mSwatchButton16->setBehavior( QgsColorButton::SignalOnly );
163 //restore custom colors
164 mSwatchButton1->setColor( settings.value( QStringLiteral( "Windows/ColorDialog/customColor1" ), QVariant( QColor() ) ).value<QColor>() );
165 mSwatchButton2->setColor( settings.value( QStringLiteral( "Windows/ColorDialog/customColor2" ), QVariant( QColor() ) ).value<QColor>() );
166 mSwatchButton3->setColor( settings.value( QStringLiteral( "Windows/ColorDialog/customColor3" ), QVariant( QColor() ) ).value<QColor>() );
167 mSwatchButton4->setColor( settings.value( QStringLiteral( "Windows/ColorDialog/customColor4" ), QVariant( QColor() ) ).value<QColor>() );
168 mSwatchButton5->setColor( settings.value( QStringLiteral( "Windows/ColorDialog/customColor5" ), QVariant( QColor() ) ).value<QColor>() );
169 mSwatchButton6->setColor( settings.value( QStringLiteral( "Windows/ColorDialog/customColor6" ), QVariant( QColor() ) ).value<QColor>() );
170 mSwatchButton7->setColor( settings.value( QStringLiteral( "Windows/ColorDialog/customColor7" ), QVariant( QColor() ) ).value<QColor>() );
171 mSwatchButton8->setColor( settings.value( QStringLiteral( "Windows/ColorDialog/customColor8" ), QVariant( QColor() ) ).value<QColor>() );
172 mSwatchButton9->setColor( settings.value( QStringLiteral( "Windows/ColorDialog/customColor9" ), QVariant( QColor() ) ).value<QColor>() );
173 mSwatchButton10->setColor( settings.value( QStringLiteral( "Windows/ColorDialog/customColor10" ), QVariant( QColor() ) ).value<QColor>() );
174 mSwatchButton11->setColor( settings.value( QStringLiteral( "Windows/ColorDialog/customColor11" ), QVariant( QColor() ) ).value<QColor>() );
175 mSwatchButton12->setColor( settings.value( QStringLiteral( "Windows/ColorDialog/customColor12" ), QVariant( QColor() ) ).value<QColor>() );
176 mSwatchButton13->setColor( settings.value( QStringLiteral( "Windows/ColorDialog/customColor13" ), QVariant( QColor() ) ).value<QColor>() );
177 mSwatchButton14->setColor( settings.value( QStringLiteral( "Windows/ColorDialog/customColor14" ), QVariant( QColor() ) ).value<QColor>() );
178 mSwatchButton15->setColor( settings.value( QStringLiteral( "Windows/ColorDialog/customColor15" ), QVariant( QColor() ) ).value<QColor>() );
179 mSwatchButton16->setColor( settings.value( QStringLiteral( "Windows/ColorDialog/customColor16" ), QVariant( QColor() ) ).value<QColor>() );
180
181 //restore sample radius
182 mSpinBoxRadius->setValue( settings.value( QStringLiteral( "Windows/ColorDialog/sampleRadius" ), 1 ).toInt() );
183 mSamplePreview->setColor( QColor() );
184
185 // hidpi friendly sizes
186 const int swatchWidth = static_cast< int >( std::round( std::max( Qgis::UI_SCALE_FACTOR * 1.9 * mSwatchButton1->fontMetrics().height(), 38.0 ) ) );
187 const int swatchHeight = static_cast< int >( std::round( std::max( Qgis::UI_SCALE_FACTOR * 1.5 * mSwatchButton1->fontMetrics().height(), 30.0 ) ) );
188 mSwatchButton1->setMinimumSize( swatchWidth, swatchHeight );
189 mSwatchButton1->setMaximumSize( swatchWidth, swatchHeight );
190 mSwatchButton2->setMinimumSize( swatchWidth, swatchHeight );
191 mSwatchButton2->setMaximumSize( swatchWidth, swatchHeight );
192 mSwatchButton3->setMinimumSize( swatchWidth, swatchHeight );
193 mSwatchButton3->setMaximumSize( swatchWidth, swatchHeight );
194 mSwatchButton4->setMinimumSize( swatchWidth, swatchHeight );
195 mSwatchButton4->setMaximumSize( swatchWidth, swatchHeight );
196 mSwatchButton5->setMinimumSize( swatchWidth, swatchHeight );
197 mSwatchButton5->setMaximumSize( swatchWidth, swatchHeight );
198 mSwatchButton6->setMinimumSize( swatchWidth, swatchHeight );
199 mSwatchButton6->setMaximumSize( swatchWidth, swatchHeight );
200 mSwatchButton7->setMinimumSize( swatchWidth, swatchHeight );
201 mSwatchButton7->setMaximumSize( swatchWidth, swatchHeight );
202 mSwatchButton8->setMinimumSize( swatchWidth, swatchHeight );
203 mSwatchButton8->setMaximumSize( swatchWidth, swatchHeight );
204 mSwatchButton9->setMinimumSize( swatchWidth, swatchHeight );
205 mSwatchButton9->setMaximumSize( swatchWidth, swatchHeight );
206 mSwatchButton10->setMinimumSize( swatchWidth, swatchHeight );
207 mSwatchButton10->setMaximumSize( swatchWidth, swatchHeight );
208 mSwatchButton11->setMinimumSize( swatchWidth, swatchHeight );
209 mSwatchButton11->setMaximumSize( swatchWidth, swatchHeight );
210 mSwatchButton12->setMinimumSize( swatchWidth, swatchHeight );
211 mSwatchButton12->setMaximumSize( swatchWidth, swatchHeight );
212 mSwatchButton13->setMinimumSize( swatchWidth, swatchHeight );
213 mSwatchButton13->setMaximumSize( swatchWidth, swatchHeight );
214 mSwatchButton14->setMinimumSize( swatchWidth, swatchHeight );
215 mSwatchButton14->setMaximumSize( swatchWidth, swatchHeight );
216 mSwatchButton15->setMinimumSize( swatchWidth, swatchHeight );
217 mSwatchButton15->setMaximumSize( swatchWidth, swatchHeight );
218 mSwatchButton16->setMinimumSize( swatchWidth, swatchHeight );
219 mSwatchButton16->setMaximumSize( swatchWidth, swatchHeight );
220 const int previewHeight = static_cast< int >( std::round( std::max( Qgis::UI_SCALE_FACTOR * 2.0 * mSwatchButton1->fontMetrics().height(), 40.0 ) ) );
221 mColorPreview->setMinimumSize( 0, previewHeight );
222 mPreviewWidget->setMaximumHeight( previewHeight * 2 );
223 const int swatchAddSize = static_cast< int >( std::round( std::max( Qgis::UI_SCALE_FACTOR * 1.4 * mSwatchButton1->fontMetrics().height(), 28.0 ) ) );
224 mAddCustomColorButton->setMinimumWidth( swatchAddSize );
225 mAddCustomColorButton->setMaximumWidth( swatchAddSize );
226
227 const int iconSize = QgsGuiUtils::scaleIconSize( 16 );
228 mTabWidget->setIconSize( QSize( iconSize, iconSize ) );
229
230 if ( color.isValid() )
231 {
232 setColor( color );
233 }
234
235 //restore active component radio button
236 int activeRadio = settings.value( QStringLiteral( "Windows/ColorDialog/activeComponent" ), 2 ).toInt();
237 switch ( activeRadio )
238 {
239 case 0:
240 mHueRadio->setChecked( true );
241 break;
242 case 1:
243 mSaturationRadio->setChecked( true );
244 break;
245 case 2:
246 mValueRadio->setChecked( true );
247 break;
248 case 3:
249 mRedRadio->setChecked( true );
250 break;
251 case 4:
252 mGreenRadio->setChecked( true );
253 break;
254 case 5:
255 mBlueRadio->setChecked( true );
256 break;
257 }
258 int currentTab = settings.value( QStringLiteral( "Windows/ColorDialog/activeTab" ), 0 ).toInt();
259 mTabWidget->setCurrentIndex( currentTab );
260
261 //setup connections
262 connect( mColorBox, &QgsColorWidget::colorChanged, this, &QgsCompoundColorWidget::setColor );
263 connect( mColorWheel, &QgsColorWidget::colorChanged, this, &QgsCompoundColorWidget::setColor );
264 connect( mColorText, &QgsColorWidget::colorChanged, this, &QgsCompoundColorWidget::setColor );
265 connect( mVerticalRamp, &QgsColorWidget::colorChanged, this, &QgsCompoundColorWidget::setColor );
266 connect( mRedSlider, &QgsColorWidget::colorChanged, this, &QgsCompoundColorWidget::setColor );
267 connect( mGreenSlider, &QgsColorWidget::colorChanged, this, &QgsCompoundColorWidget::setColor );
268 connect( mBlueSlider, &QgsColorWidget::colorChanged, this, &QgsCompoundColorWidget::setColor );
269 connect( mHueSlider, &QgsColorWidget::colorChanged, this, &QgsCompoundColorWidget::setColor );
270 connect( mValueSlider, &QgsColorWidget::colorChanged, this, &QgsCompoundColorWidget::setColor );
271 connect( mSaturationSlider, &QgsColorWidget::colorChanged, this, &QgsCompoundColorWidget::setColor );
272 connect( mAlphaSlider, &QgsColorWidget::colorChanged, this, &QgsCompoundColorWidget::setColor );
273 connect( mColorPreview, &QgsColorWidget::colorChanged, this, &QgsCompoundColorWidget::setColor );
274 connect( mSwatchButton1, &QgsColorButton::colorClicked, this, &QgsCompoundColorWidget::setColor );
275 connect( mSwatchButton2, &QgsColorButton::colorClicked, this, &QgsCompoundColorWidget::setColor );
276 connect( mSwatchButton3, &QgsColorButton::colorClicked, this, &QgsCompoundColorWidget::setColor );
277 connect( mSwatchButton4, &QgsColorButton::colorClicked, this, &QgsCompoundColorWidget::setColor );
278 connect( mSwatchButton5, &QgsColorButton::colorClicked, this, &QgsCompoundColorWidget::setColor );
279 connect( mSwatchButton6, &QgsColorButton::colorClicked, this, &QgsCompoundColorWidget::setColor );
280 connect( mSwatchButton7, &QgsColorButton::colorClicked, this, &QgsCompoundColorWidget::setColor );
281 connect( mSwatchButton8, &QgsColorButton::colorClicked, this, &QgsCompoundColorWidget::setColor );
282 connect( mSwatchButton9, &QgsColorButton::colorClicked, this, &QgsCompoundColorWidget::setColor );
283 connect( mSwatchButton10, &QgsColorButton::colorClicked, this, &QgsCompoundColorWidget::setColor );
284 connect( mSwatchButton11, &QgsColorButton::colorClicked, this, &QgsCompoundColorWidget::setColor );
285 connect( mSwatchButton12, &QgsColorButton::colorClicked, this, &QgsCompoundColorWidget::setColor );
286 connect( mSwatchButton13, &QgsColorButton::colorClicked, this, &QgsCompoundColorWidget::setColor );
287 connect( mSwatchButton14, &QgsColorButton::colorClicked, this, &QgsCompoundColorWidget::setColor );
288 connect( mSwatchButton15, &QgsColorButton::colorClicked, this, &QgsCompoundColorWidget::setColor );
289 connect( mSwatchButton16, &QgsColorButton::colorClicked, this, &QgsCompoundColorWidget::setColor );
290 }
291
~QgsCompoundColorWidget()292 QgsCompoundColorWidget::~QgsCompoundColorWidget()
293 {
294 if ( !mDiscarded )
295 {
296 QgsRecentColorScheme::addRecentColor( color() );
297 }
298 }
299
color() const300 QColor QgsCompoundColorWidget::color() const
301 {
302 //all widgets should have the same color, so it shouldn't matter
303 //which we fetch it from
304 return mColorPreview->color();
305 }
306
setAllowOpacity(const bool allowOpacity)307 void QgsCompoundColorWidget::setAllowOpacity( const bool allowOpacity )
308 {
309 mAllowAlpha = allowOpacity;
310 mAlphaLabel->setVisible( allowOpacity );
311 mAlphaSlider->setVisible( allowOpacity );
312 if ( !allowOpacity )
313 {
314 mAlphaLayout->setContentsMargins( 0, 0, 0, 0 );
315 mAlphaLayout->setSpacing( 0 );
316 }
317 }
318
refreshSchemeComboBox()319 void QgsCompoundColorWidget::refreshSchemeComboBox()
320 {
321 mSchemeComboBox->blockSignals( true );
322 mSchemeComboBox->clear();
323 QList<QgsColorScheme *> schemeList = QgsApplication::colorSchemeRegistry()->schemes( QgsColorScheme::ShowInColorDialog );
324 QList<QgsColorScheme *>::const_iterator schemeIt = schemeList.constBegin();
325 for ( ; schemeIt != schemeList.constEnd(); ++schemeIt )
326 {
327 mSchemeComboBox->addItem( ( *schemeIt )->schemeName() );
328 }
329 mSchemeComboBox->blockSignals( false );
330 }
331
332
importUserPaletteFromFile(QWidget * parent)333 QgsUserColorScheme *QgsCompoundColorWidget::importUserPaletteFromFile( QWidget *parent )
334 {
335 QgsSettings s;
336 QString lastDir = s.value( QStringLiteral( "/UI/lastGplPaletteDir" ), QDir::homePath() ).toString();
337 QString filePath = QFileDialog::getOpenFileName( parent, tr( "Select Palette File" ), lastDir, QStringLiteral( "GPL (*.gpl);;All files (*.*)" ) );
338 if ( parent )
339 parent->activateWindow();
340 if ( filePath.isEmpty() )
341 {
342 return nullptr;
343 }
344
345 //check if file exists
346 QFileInfo fileInfo( filePath );
347 if ( !fileInfo.exists() || !fileInfo.isReadable() )
348 {
349 QMessageBox::critical( nullptr, tr( "Import Color Palette" ), tr( "Error, file does not exist or is not readable." ) );
350 return nullptr;
351 }
352
353 s.setValue( QStringLiteral( "/UI/lastGplPaletteDir" ), fileInfo.absolutePath() );
354 QFile file( filePath );
355
356 QgsNamedColorList importedColors;
357 bool ok = false;
358 QString paletteName;
359 importedColors = QgsSymbolLayerUtils::importColorsFromGpl( file, ok, paletteName );
360 if ( !ok )
361 {
362 QMessageBox::critical( nullptr, tr( "Import Color Palette" ), tr( "Palette file is not readable." ) );
363 return nullptr;
364 }
365
366 if ( importedColors.length() == 0 )
367 {
368 //no imported colors
369 QMessageBox::critical( nullptr, tr( "Import Color Palette" ), tr( "No colors found in palette file." ) );
370 return nullptr;
371 }
372
373 //TODO - handle conflicting file names, name for new palette
374 QgsUserColorScheme *importedScheme = new QgsUserColorScheme( fileInfo.fileName() );
375 importedScheme->setName( paletteName );
376 importedScheme->setColors( importedColors );
377
378 QgsApplication::colorSchemeRegistry()->addColorScheme( importedScheme );
379 return importedScheme;
380 }
381
importPalette()382 void QgsCompoundColorWidget::importPalette()
383 {
384 if ( importUserPaletteFromFile( this ) )
385 {
386 //refresh combobox
387 refreshSchemeComboBox();
388 mSchemeComboBox->setCurrentIndex( mSchemeComboBox->count() - 1 );
389 }
390 }
391
392
removeUserPalette(QgsUserColorScheme * scheme,QWidget * parent)393 bool QgsCompoundColorWidget::removeUserPalette( QgsUserColorScheme *scheme, QWidget *parent )
394 {
395 if ( QMessageBox::question( parent, tr( "Remove Color Palette" ),
396 tr( "Are you sure you want to remove %1?" ).arg( scheme->schemeName() ),
397 QMessageBox::Yes | QMessageBox::No, QMessageBox::No ) != QMessageBox::Yes )
398 {
399 //user canceled
400 return false;
401 }
402
403 //remove palette and associated gpl file
404 if ( !scheme->erase() )
405 {
406 //something went wrong
407 return false;
408 }
409
410 //remove scheme from registry
411 QgsApplication::colorSchemeRegistry()->removeColorScheme( scheme );
412 return true;
413 }
414
removePalette()415 void QgsCompoundColorWidget::removePalette()
416 {
417 //get current scheme
418 QList<QgsColorScheme *> schemeList = QgsApplication::colorSchemeRegistry()->schemes( QgsColorScheme::ShowInColorDialog );
419 int prevIndex = mSchemeComboBox->currentIndex();
420 if ( prevIndex >= schemeList.length() )
421 {
422 return;
423 }
424
425 //make user scheme is a user removable scheme
426 QgsUserColorScheme *userScheme = dynamic_cast<QgsUserColorScheme *>( schemeList.at( prevIndex ) );
427 if ( !userScheme )
428 {
429 return;
430 }
431
432 if ( removeUserPalette( userScheme, this ) )
433 {
434 refreshSchemeComboBox();
435 prevIndex = std::max( std::min( prevIndex, mSchemeComboBox->count() - 1 ), 0 );
436 mSchemeComboBox->setCurrentIndex( prevIndex );
437 }
438 }
439
createNewUserPalette(QWidget * parent)440 QgsUserColorScheme *QgsCompoundColorWidget::createNewUserPalette( QWidget *parent )
441 {
442 bool ok = false;
443 QString name = QInputDialog::getText( parent, tr( "Create New Palette" ), tr( "Enter a name for the new palette:" ),
444 QLineEdit::Normal, tr( "New palette" ), &ok );
445
446 if ( !ok || name.isEmpty() )
447 {
448 //user canceled
449 return nullptr;
450 }
451
452 //generate file name for new palette
453 QDir palettePath( gplFilePath() );
454 QRegExp badChars( "[,^@={}\\[\\]~!?:&*\"|#%<>$\"'();`' /\\\\]" );
455 QString filename = name.simplified().toLower().replace( badChars, QStringLiteral( "_" ) );
456 if ( filename.isEmpty() )
457 {
458 filename = tr( "new_palette" );
459 }
460 QFileInfo destFileInfo( palettePath.filePath( filename + ".gpl" ) );
461 int fileNumber = 1;
462 while ( destFileInfo.exists() )
463 {
464 //try to generate a unique file name
465 destFileInfo = QFileInfo( palettePath.filePath( filename + QStringLiteral( "%1.gpl" ).arg( fileNumber ) ) );
466 fileNumber++;
467 }
468
469 QgsUserColorScheme *newScheme = new QgsUserColorScheme( destFileInfo.fileName() );
470 newScheme->setName( name );
471
472 QgsApplication::colorSchemeRegistry()->addColorScheme( newScheme );
473 return newScheme;
474 }
475
newPalette()476 void QgsCompoundColorWidget::newPalette()
477 {
478 if ( createNewUserPalette( this ) )
479 {
480 //refresh combobox and set new scheme as active
481 refreshSchemeComboBox();
482 mSchemeComboBox->setCurrentIndex( mSchemeComboBox->count() - 1 );
483 }
484 }
485
gplFilePath()486 QString QgsCompoundColorWidget::gplFilePath()
487 {
488 QString palettesDir = QgsApplication::qgisSettingsDirPath() + "palettes";
489
490 QDir localDir;
491 if ( !localDir.mkpath( palettesDir ) )
492 {
493 return QString();
494 }
495
496 return palettesDir;
497 }
498
schemeIndexChanged(int index)499 void QgsCompoundColorWidget::schemeIndexChanged( int index )
500 {
501 //save changes to scheme
502 if ( mSchemeList->isDirty() )
503 {
504 mSchemeList->saveColorsToScheme();
505 }
506
507 //get schemes with ShowInColorDialog set
508 QList<QgsColorScheme *> schemeList = QgsApplication::colorSchemeRegistry()->schemes( QgsColorScheme::ShowInColorDialog );
509 if ( index >= schemeList.length() )
510 {
511 return;
512 }
513
514 QgsColorScheme *scheme = schemeList.at( index );
515 mSchemeList->setScheme( scheme );
516
517 updateActionsForCurrentScheme();
518
519 //copy action defaults to disabled
520 mActionCopyColors->setEnabled( false );
521 }
522
listSelectionChanged(const QItemSelection & selected,const QItemSelection & deselected)523 void QgsCompoundColorWidget::listSelectionChanged( const QItemSelection &selected, const QItemSelection &deselected )
524 {
525 Q_UNUSED( deselected )
526 mActionCopyColors->setEnabled( selected.length() > 0 );
527 }
528
mAddCustomColorButton_clicked()529 void QgsCompoundColorWidget::mAddCustomColorButton_clicked()
530 {
531 switch ( mLastCustomColorIndex )
532 {
533 case 0:
534 mSwatchButton1->setColor( mColorPreview->color() );
535 break;
536 case 1:
537 mSwatchButton2->setColor( mColorPreview->color() );
538 break;
539 case 2:
540 mSwatchButton3->setColor( mColorPreview->color() );
541 break;
542 case 3:
543 mSwatchButton4->setColor( mColorPreview->color() );
544 break;
545 case 4:
546 mSwatchButton5->setColor( mColorPreview->color() );
547 break;
548 case 5:
549 mSwatchButton6->setColor( mColorPreview->color() );
550 break;
551 case 6:
552 mSwatchButton7->setColor( mColorPreview->color() );
553 break;
554 case 7:
555 mSwatchButton8->setColor( mColorPreview->color() );
556 break;
557 case 8:
558 mSwatchButton9->setColor( mColorPreview->color() );
559 break;
560 case 9:
561 mSwatchButton10->setColor( mColorPreview->color() );
562 break;
563 case 10:
564 mSwatchButton11->setColor( mColorPreview->color() );
565 break;
566 case 11:
567 mSwatchButton12->setColor( mColorPreview->color() );
568 break;
569 case 12:
570 mSwatchButton13->setColor( mColorPreview->color() );
571 break;
572 case 13:
573 mSwatchButton14->setColor( mColorPreview->color() );
574 break;
575 case 14:
576 mSwatchButton15->setColor( mColorPreview->color() );
577 break;
578 case 15:
579 mSwatchButton16->setColor( mColorPreview->color() );
580 break;
581 }
582 mLastCustomColorIndex++;
583 if ( mLastCustomColorIndex >= 16 )
584 {
585 mLastCustomColorIndex = 0;
586 }
587 }
588
mSampleButton_clicked()589 void QgsCompoundColorWidget::mSampleButton_clicked()
590 {
591 //activate picker color
592 setCursor( QgsApplication::getThemeCursor( QgsApplication::Cursor::Sampler ) );
593 grabMouse();
594 grabKeyboard();
595 mPickingColor = true;
596 setMouseTracking( true );
597 }
598
mTabWidget_currentChanged(int index)599 void QgsCompoundColorWidget::mTabWidget_currentChanged( int index )
600 {
601 //disable radio buttons if not using the first tab, as they have no meaning for other tabs
602 bool enabled = index == 0;
603 mRedRadio->setEnabled( enabled );
604 mBlueRadio->setEnabled( enabled );
605 mGreenRadio->setEnabled( enabled );
606 mHueRadio->setEnabled( enabled );
607 mSaturationRadio->setEnabled( enabled );
608 mValueRadio->setEnabled( enabled );
609 }
610
mActionShowInButtons_toggled(bool state)611 void QgsCompoundColorWidget::mActionShowInButtons_toggled( bool state )
612 {
613 QgsUserColorScheme *scheme = dynamic_cast< QgsUserColorScheme * >( mSchemeList->scheme() );
614 if ( scheme )
615 {
616 scheme->setShowSchemeInMenu( state );
617 }
618 }
619
findScreenAt(QPoint pos)620 QScreen *QgsCompoundColorWidget::findScreenAt( QPoint pos )
621 {
622 const QList< QScreen * > screens = QGuiApplication::screens();
623 for ( QScreen *screen : screens )
624 {
625 if ( screen->geometry().contains( pos ) )
626 {
627 return screen;
628 }
629 }
630 return nullptr;
631 }
632
saveSettings()633 void QgsCompoundColorWidget::saveSettings()
634 {
635 //save changes to scheme
636 if ( mSchemeList->isDirty() )
637 {
638 mSchemeList->saveColorsToScheme();
639 }
640
641 QgsSettings settings;
642
643 //record active component
644 int activeRadio = 0;
645 if ( mHueRadio->isChecked() )
646 activeRadio = 0;
647 if ( mSaturationRadio->isChecked() )
648 activeRadio = 1;
649 if ( mValueRadio->isChecked() )
650 activeRadio = 2;
651 if ( mRedRadio->isChecked() )
652 activeRadio = 3;
653 if ( mGreenRadio->isChecked() )
654 activeRadio = 4;
655 if ( mBlueRadio->isChecked() )
656 activeRadio = 5;
657 settings.setValue( QStringLiteral( "Windows/ColorDialog/activeComponent" ), activeRadio );
658
659 //record current scheme
660 settings.setValue( QStringLiteral( "Windows/ColorDialog/activeScheme" ), mSchemeComboBox->currentIndex() );
661
662 //record current tab
663 settings.setValue( QStringLiteral( "Windows/ColorDialog/activeTab" ), mTabWidget->currentIndex() );
664
665 //record custom colors
666 settings.setValue( QStringLiteral( "Windows/ColorDialog/customColor1" ), QVariant( mSwatchButton1->color() ) );
667 settings.setValue( QStringLiteral( "Windows/ColorDialog/customColor2" ), QVariant( mSwatchButton2->color() ) );
668 settings.setValue( QStringLiteral( "Windows/ColorDialog/customColor3" ), QVariant( mSwatchButton3->color() ) );
669 settings.setValue( QStringLiteral( "Windows/ColorDialog/customColor4" ), QVariant( mSwatchButton4->color() ) );
670 settings.setValue( QStringLiteral( "Windows/ColorDialog/customColor5" ), QVariant( mSwatchButton5->color() ) );
671 settings.setValue( QStringLiteral( "Windows/ColorDialog/customColor6" ), QVariant( mSwatchButton6->color() ) );
672 settings.setValue( QStringLiteral( "Windows/ColorDialog/customColor7" ), QVariant( mSwatchButton7->color() ) );
673 settings.setValue( QStringLiteral( "Windows/ColorDialog/customColor8" ), QVariant( mSwatchButton8->color() ) );
674 settings.setValue( QStringLiteral( "Windows/ColorDialog/customColor9" ), QVariant( mSwatchButton9->color() ) );
675 settings.setValue( QStringLiteral( "Windows/ColorDialog/customColor10" ), QVariant( mSwatchButton10->color() ) );
676 settings.setValue( QStringLiteral( "Windows/ColorDialog/customColor11" ), QVariant( mSwatchButton11->color() ) );
677 settings.setValue( QStringLiteral( "Windows/ColorDialog/customColor12" ), QVariant( mSwatchButton12->color() ) );
678 settings.setValue( QStringLiteral( "Windows/ColorDialog/customColor13" ), QVariant( mSwatchButton13->color() ) );
679 settings.setValue( QStringLiteral( "Windows/ColorDialog/customColor14" ), QVariant( mSwatchButton14->color() ) );
680 settings.setValue( QStringLiteral( "Windows/ColorDialog/customColor15" ), QVariant( mSwatchButton15->color() ) );
681 settings.setValue( QStringLiteral( "Windows/ColorDialog/customColor16" ), QVariant( mSwatchButton16->color() ) );
682
683 //sample radius
684 settings.setValue( QStringLiteral( "Windows/ColorDialog/sampleRadius" ), mSpinBoxRadius->value() );
685 }
686
stopPicking(QPoint eventPos,const bool takeSample)687 void QgsCompoundColorWidget::stopPicking( QPoint eventPos, const bool takeSample )
688 {
689 //release mouse and keyboard, and reset cursor
690 releaseMouse();
691 releaseKeyboard();
692 unsetCursor();
693 setMouseTracking( false );
694 mPickingColor = false;
695
696 if ( !takeSample )
697 {
698 //not sampling color, nothing more to do
699 return;
700 }
701
702 //grab snapshot of pixel under mouse cursor
703 QColor snappedColor = sampleColor( eventPos );
704 mSamplePreview->setColor( snappedColor );
705 mColorPreview->setColor( snappedColor, true );
706 }
707
setColor(const QColor & color)708 void QgsCompoundColorWidget::setColor( const QColor &color )
709 {
710 if ( !color.isValid() )
711 {
712 return;
713 }
714
715 QColor fixedColor = QColor( color );
716 if ( !mAllowAlpha )
717 {
718 //opacity disallowed, so don't permit transparent colors
719 fixedColor.setAlpha( 255 );
720 }
721 QList<QgsColorWidget *> colorWidgets = this->findChildren<QgsColorWidget *>();
722 const auto constColorWidgets = colorWidgets;
723 for ( QgsColorWidget *widget : constColorWidgets )
724 {
725 if ( widget == mSamplePreview )
726 {
727 continue;
728 }
729 widget->blockSignals( true );
730 widget->setColor( fixedColor );
731 widget->blockSignals( false );
732 }
733 emit currentColorChanged( fixedColor );
734 }
735
setPreviousColor(const QColor & color)736 void QgsCompoundColorWidget::setPreviousColor( const QColor &color )
737 {
738 mOldColorLabel->setVisible( color.isValid() );
739 mColorPreview->setColor2( color );
740 }
741
hideEvent(QHideEvent * e)742 void QgsCompoundColorWidget::hideEvent( QHideEvent *e )
743 {
744 saveSettings();
745 QWidget::hideEvent( e );
746 }
747
mousePressEvent(QMouseEvent * e)748 void QgsCompoundColorWidget::mousePressEvent( QMouseEvent *e )
749 {
750 if ( mPickingColor )
751 {
752 //don't show dialog if in color picker mode
753 e->accept();
754 return;
755 }
756
757 QWidget::mousePressEvent( e );
758 }
759
averageColor(const QImage & image) const760 QColor QgsCompoundColorWidget::averageColor( const QImage &image ) const
761 {
762 QRgb tmpRgb;
763 int colorCount = 0;
764 int sumRed = 0;
765 int sumBlue = 0;
766 int sumGreen = 0;
767 //scan through image and sum rgb components
768 for ( int heightIndex = 0; heightIndex < image.height(); ++heightIndex )
769 {
770 const QRgb *scanLine = reinterpret_cast< const QRgb * >( image.constScanLine( heightIndex ) );
771 for ( int widthIndex = 0; widthIndex < image.width(); ++widthIndex )
772 {
773 tmpRgb = scanLine[widthIndex];
774 sumRed += qRed( tmpRgb );
775 sumBlue += qBlue( tmpRgb );
776 sumGreen += qGreen( tmpRgb );
777 colorCount++;
778 }
779 }
780 //calculate average components as floats
781 double avgRed = static_cast<double>( sumRed ) / ( 255.0 * colorCount );
782 double avgGreen = static_cast<double>( sumGreen ) / ( 255.0 * colorCount );
783 double avgBlue = static_cast<double>( sumBlue ) / ( 255.0 * colorCount );
784
785 //create a new color representing the average
786 return QColor::fromRgbF( avgRed, avgGreen, avgBlue );
787 }
788
sampleColor(QPoint point) const789 QColor QgsCompoundColorWidget::sampleColor( QPoint point ) const
790 {
791 int sampleRadius = mSpinBoxRadius->value() - 1;
792 QScreen *screen = findScreenAt( point );
793 if ( ! screen )
794 {
795 return QColor();
796 }
797 QPixmap snappedPixmap = screen->grabWindow( QApplication::desktop()->winId(),
798 point.x() - sampleRadius,
799 point.y() - sampleRadius,
800 1 + sampleRadius * 2,
801 1 + sampleRadius * 2 );
802 QImage snappedImage = snappedPixmap.toImage();
803 //scan all pixels and take average color
804 return averageColor( snappedImage );
805 }
806
mouseMoveEvent(QMouseEvent * e)807 void QgsCompoundColorWidget::mouseMoveEvent( QMouseEvent *e )
808 {
809 if ( mPickingColor )
810 {
811 //currently in color picker mode
812 //sample color under cursor update preview widget to give feedback to user
813 QColor hoverColor = sampleColor( e->globalPos() );
814 mSamplePreview->setColor( hoverColor );
815
816 e->accept();
817 return;
818 }
819
820 QWidget::mouseMoveEvent( e );
821 }
822
mouseReleaseEvent(QMouseEvent * e)823 void QgsCompoundColorWidget::mouseReleaseEvent( QMouseEvent *e )
824 {
825 if ( mPickingColor )
826 {
827 //end color picking operation by sampling the color under cursor
828 stopPicking( e->globalPos() );
829 e->accept();
830 return;
831 }
832
833 QWidget::mouseReleaseEvent( e );
834 }
835
keyPressEvent(QKeyEvent * e)836 void QgsCompoundColorWidget::keyPressEvent( QKeyEvent *e )
837 {
838 if ( !mPickingColor )
839 {
840 //if not picking a color, use default tool button behavior
841 QgsPanelWidget::keyPressEvent( e );
842 return;
843 }
844
845 //cancel picking, sampling the color if space was pressed
846 stopPicking( QCursor::pos(), e->key() == Qt::Key_Space );
847 }
848
mHueRadio_toggled(bool checked)849 void QgsCompoundColorWidget::mHueRadio_toggled( bool checked )
850 {
851 if ( checked )
852 {
853 mColorBox->setComponent( QgsColorWidget::Hue );
854 mVerticalRamp->setComponent( QgsColorWidget::Hue );
855 }
856 }
857
mSaturationRadio_toggled(bool checked)858 void QgsCompoundColorWidget::mSaturationRadio_toggled( bool checked )
859 {
860 if ( checked )
861 {
862 mColorBox->setComponent( QgsColorWidget::Saturation );
863 mVerticalRamp->setComponent( QgsColorWidget::Saturation );
864 }
865 }
866
mValueRadio_toggled(bool checked)867 void QgsCompoundColorWidget::mValueRadio_toggled( bool checked )
868 {
869 if ( checked )
870 {
871 mColorBox->setComponent( QgsColorWidget::Value );
872 mVerticalRamp->setComponent( QgsColorWidget::Value );
873 }
874 }
875
mRedRadio_toggled(bool checked)876 void QgsCompoundColorWidget::mRedRadio_toggled( bool checked )
877 {
878 if ( checked )
879 {
880 mColorBox->setComponent( QgsColorWidget::Red );
881 mVerticalRamp->setComponent( QgsColorWidget::Red );
882 }
883 }
884
mGreenRadio_toggled(bool checked)885 void QgsCompoundColorWidget::mGreenRadio_toggled( bool checked )
886 {
887 if ( checked )
888 {
889 mColorBox->setComponent( QgsColorWidget::Green );
890 mVerticalRamp->setComponent( QgsColorWidget::Green );
891 }
892 }
893
mBlueRadio_toggled(bool checked)894 void QgsCompoundColorWidget::mBlueRadio_toggled( bool checked )
895 {
896 if ( checked )
897 {
898 mColorBox->setComponent( QgsColorWidget::Blue );
899 mVerticalRamp->setComponent( QgsColorWidget::Blue );
900 }
901 }
902
mAddColorToSchemeButton_clicked()903 void QgsCompoundColorWidget::mAddColorToSchemeButton_clicked()
904 {
905 mSchemeList->addColor( mColorPreview->color(), QgsSymbolLayerUtils::colorToName( mColorPreview->color() ) );
906 }
907
updateActionsForCurrentScheme()908 void QgsCompoundColorWidget::updateActionsForCurrentScheme()
909 {
910 QgsColorScheme *scheme = mSchemeList->scheme();
911
912 mActionImportColors->setEnabled( scheme->isEditable() );
913 mActionPasteColors->setEnabled( scheme->isEditable() );
914 mAddColorToSchemeButton->setEnabled( scheme->isEditable() );
915 mRemoveColorsFromSchemeButton->setEnabled( scheme->isEditable() );
916
917 QgsUserColorScheme *userScheme = dynamic_cast<QgsUserColorScheme *>( scheme );
918 mActionRemovePalette->setEnabled( static_cast< bool >( userScheme ) );
919 if ( userScheme )
920 {
921 mActionShowInButtons->setEnabled( true );
922 whileBlocking( mActionShowInButtons )->setChecked( userScheme->flags() & QgsColorScheme::ShowInColorButtonMenu );
923 }
924 else
925 {
926 whileBlocking( mActionShowInButtons )->setChecked( false );
927 mActionShowInButtons->setEnabled( false );
928 }
929 }
930