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