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