1 /***************************************************************************
2                              qgscolorscheme.cpp
3                              -------------------
4     begin                : July 2014
5     copyright            : (C) 2014 by Nyall Dawson
6     email                : nyall dot dawson at gmail dot com
7  ***************************************************************************/
8 
9 /***************************************************************************
10  *                                                                         *
11  *   This program is free software; you can redistribute it and/or modify  *
12  *   it under the terms of the GNU General Public License as published by  *
13  *   the Free Software Foundation; either version 2 of the License, or     *
14  *   (at your option) any later version.                                   *
15  *                                                                         *
16  ***************************************************************************/
17 
18 #include "qgscolorscheme.h"
19 #include "qgscolorschemeregistry.h"
20 
21 #include "qgsproject.h"
22 #include "qgssymbollayerutils.h"
23 #include "qgsapplication.h"
24 #include "qgssettings.h"
25 
26 #include <QDir>
27 #include <QRegularExpression>
28 #include <QTextStream>
29 
setColors(const QgsNamedColorList & colors,const QString & context,const QColor & baseColor)30 bool QgsColorScheme::setColors( const QgsNamedColorList &colors, const QString &context, const QColor &baseColor )
31 {
32   //base implementation does nothing
33   Q_UNUSED( colors )
34   Q_UNUSED( context )
35   Q_UNUSED( baseColor )
36   return false;
37 }
38 
39 
40 //
41 // QgsRecentColorScheme
42 //
43 
fetchColors(const QString & context,const QColor & baseColor)44 QgsNamedColorList QgsRecentColorScheme::fetchColors( const QString &context, const QColor &baseColor )
45 {
46   Q_UNUSED( context )
47   Q_UNUSED( baseColor )
48 
49   //fetch recent colors
50   const QgsSettings settings;
51   const QList< QVariant > recentColorVariants = settings.value( QStringLiteral( "colors/recent" ) ).toList();
52 
53   //generate list from recent colors
54   QgsNamedColorList colorList;
55   const auto constRecentColorVariants = recentColorVariants;
56   for ( const QVariant &color : constRecentColorVariants )
57   {
58     colorList.append( qMakePair( color.value<QColor>(), QgsSymbolLayerUtils::colorToName( color.value<QColor>() ) ) );
59   }
60   return colorList;
61 }
62 
clone() const63 QgsRecentColorScheme *QgsRecentColorScheme::clone() const
64 {
65   return new QgsRecentColorScheme();
66 }
67 
addRecentColor(const QColor & color)68 void QgsRecentColorScheme::addRecentColor( const QColor &color )
69 {
70   if ( !color.isValid() )
71   {
72     return;
73   }
74 
75   //strip alpha from color
76   QColor opaqueColor = color;
77   opaqueColor.setAlpha( 255 );
78 
79   QgsSettings settings;
80   QList< QVariant > recentColorVariants = settings.value( QStringLiteral( "colors/recent" ) ).toList();
81 
82   //remove colors by name
83   for ( int colorIdx = recentColorVariants.length() - 1; colorIdx >= 0; --colorIdx )
84   {
85     if ( ( recentColorVariants.at( colorIdx ).value<QColor>() ).name() == opaqueColor.name() )
86     {
87       recentColorVariants.removeAt( colorIdx );
88     }
89   }
90 
91   //add color
92   const QVariant colorVariant = QVariant( opaqueColor );
93   recentColorVariants.prepend( colorVariant );
94 
95   //trim to 20 colors
96   while ( recentColorVariants.count() > 20 )
97   {
98     recentColorVariants.pop_back();
99   }
100 
101   settings.setValue( QStringLiteral( "colors/recent" ), recentColorVariants );
102 }
103 
lastUsedColor()104 QColor QgsRecentColorScheme::lastUsedColor()
105 {
106   //fetch recent colors
107   const QgsSettings settings;
108   const QList< QVariant > recentColorVariants = settings.value( QStringLiteral( "colors/recent" ) ).toList();
109 
110   if ( recentColorVariants.isEmpty() )
111     return QColor();
112 
113   return recentColorVariants.at( 0 ).value<QColor>();
114 }
115 
fetchColors(const QString & context,const QColor & baseColor)116 QgsNamedColorList QgsCustomColorScheme::fetchColors( const QString &context, const QColor &baseColor )
117 {
118   Q_UNUSED( context )
119   Q_UNUSED( baseColor )
120 
121   //fetch predefined custom colors
122   QgsNamedColorList colorList;
123   const QgsSettings settings;
124 
125   //check if settings contains custom palette
126   if ( !settings.contains( QStringLiteral( "/colors/palettecolors" ) ) )
127   {
128     //no custom palette, return default colors
129     colorList.append( qMakePair( QColor( 0, 0, 0 ), QString() ) );
130     colorList.append( qMakePair( QColor( 255, 255, 255 ), QString() ) );
131     colorList.append( qMakePair( QColor( 166, 206, 227 ), QString() ) );
132     colorList.append( qMakePair( QColor( 31, 120, 180 ), QString() ) );
133     colorList.append( qMakePair( QColor( 178, 223, 138 ), QString() ) );
134     colorList.append( qMakePair( QColor( 51, 160, 44 ), QString() ) );
135     colorList.append( qMakePair( QColor( 251, 154, 153 ), QString() ) );
136     colorList.append( qMakePair( QColor( 227, 26, 28 ), QString() ) );
137     colorList.append( qMakePair( QColor( 253, 191, 111 ), QString() ) );
138     colorList.append( qMakePair( QColor( 255, 127, 0 ), QString() ) );
139 
140     return colorList;
141   }
142 
143   QList< QVariant > customColorVariants = settings.value( QStringLiteral( "colors/palettecolors" ) ).toList();
144   const QList< QVariant > customColorLabels = settings.value( QStringLiteral( "colors/palettelabels" ) ).toList();
145 
146   //generate list from custom colors
147   int colorIndex = 0;
148   for ( QList< QVariant >::iterator it = customColorVariants.begin();
149         it != customColorVariants.end(); ++it )
150   {
151     const QColor color = ( *it ).value<QColor>();
152     QString label;
153     if ( customColorLabels.length() > colorIndex )
154     {
155       label = customColorLabels.at( colorIndex ).toString();
156     }
157 
158     colorList.append( qMakePair( color, label ) );
159     colorIndex++;
160   }
161 
162   return colorList;
163 }
164 
setColors(const QgsNamedColorList & colors,const QString & context,const QColor & baseColor)165 bool QgsCustomColorScheme::setColors( const QgsNamedColorList &colors, const QString &context, const QColor &baseColor )
166 {
167   Q_UNUSED( context )
168   Q_UNUSED( baseColor )
169 
170   // save colors to settings
171   QgsSettings settings;
172   QList< QVariant > customColors;
173   QList< QVariant > customColorLabels;
174 
175   QgsNamedColorList::const_iterator colorIt = colors.constBegin();
176   for ( ; colorIt != colors.constEnd(); ++colorIt )
177   {
178     const QVariant color = ( *colorIt ).first;
179     const QVariant label = ( *colorIt ).second;
180     customColors.append( color );
181     customColorLabels.append( label );
182   }
183   settings.setValue( QStringLiteral( "colors/palettecolors" ), customColors );
184   settings.setValue( QStringLiteral( "colors/palettelabels" ), customColorLabels );
185   return true;
186 }
187 
clone() const188 QgsCustomColorScheme *QgsCustomColorScheme::clone() const
189 {
190   return new QgsCustomColorScheme();
191 }
192 
193 
fetchColors(const QString & context,const QColor & baseColor)194 QgsNamedColorList QgsProjectColorScheme::fetchColors( const QString &context, const QColor &baseColor )
195 {
196   Q_UNUSED( context )
197   Q_UNUSED( baseColor )
198 
199   QgsNamedColorList colorList;
200 
201   QStringList colorStrings = QgsProject::instance()->readListEntry( QStringLiteral( "Palette" ), QStringLiteral( "/Colors" ) );
202   const QStringList colorLabels = QgsProject::instance()->readListEntry( QStringLiteral( "Palette" ), QStringLiteral( "/Labels" ) );
203 
204   //generate list from custom colors
205   int colorIndex = 0;
206   for ( QStringList::iterator it = colorStrings.begin();
207         it != colorStrings.end(); ++it )
208   {
209     const QColor color = QgsSymbolLayerUtils::decodeColor( *it );
210     QString label;
211     if ( colorLabels.length() > colorIndex )
212     {
213       label = colorLabels.at( colorIndex );
214     }
215 
216     colorList.append( qMakePair( color, label ) );
217     colorIndex++;
218   }
219 
220   return colorList;
221 }
222 
setColors(const QgsNamedColorList & colors,const QString & context,const QColor & baseColor)223 bool QgsProjectColorScheme::setColors( const QgsNamedColorList &colors, const QString &context, const QColor &baseColor )
224 {
225   Q_UNUSED( context )
226   Q_UNUSED( baseColor )
227   QgsProject::instance()->setProjectColors( colors );
228   return true;
229 }
230 
clone() const231 QgsProjectColorScheme *QgsProjectColorScheme::clone() const
232 {
233   return new QgsProjectColorScheme();
234 }
235 
236 
237 //
238 // QgsGplColorScheme
239 //
240 
fetchColors(const QString & context,const QColor & baseColor)241 QgsNamedColorList QgsGplColorScheme::fetchColors( const QString &context, const QColor &baseColor )
242 {
243   Q_UNUSED( context )
244   Q_UNUSED( baseColor )
245 
246   const QString sourceFilePath = gplFilePath();
247   if ( sourceFilePath.isEmpty() )
248   {
249     QgsNamedColorList noColors;
250     return noColors;
251   }
252 
253   bool ok;
254   QString name;
255   QFile sourceFile( sourceFilePath );
256   return QgsSymbolLayerUtils::importColorsFromGpl( sourceFile, ok, name );
257 }
258 
setColors(const QgsNamedColorList & colors,const QString & context,const QColor & baseColor)259 bool QgsGplColorScheme::setColors( const QgsNamedColorList &colors, const QString &context, const QColor &baseColor )
260 {
261   Q_UNUSED( context )
262   Q_UNUSED( baseColor )
263 
264   const QString destFilePath = gplFilePath();
265   if ( destFilePath.isEmpty() )
266   {
267     return false;
268   }
269 
270   QFile destFile( destFilePath );
271   if ( QgsSymbolLayerUtils::saveColorsToGpl( destFile, schemeName(), colors ) )
272   {
273     if ( QgsApplication::colorSchemeRegistry()->randomStyleColorScheme() == this )
274     {
275       // force a re-generation of the random style color list, since the color list has changed
276       QgsApplication::colorSchemeRegistry()->setRandomStyleColorScheme( this );
277     }
278     return true;
279   }
280   else
281   {
282     return false;
283   }
284 }
285 
286 
287 //
288 // QgsUserColorScheme
289 //
290 
QgsUserColorScheme(const QString & filename)291 QgsUserColorScheme::QgsUserColorScheme( const QString &filename )
292   : mFilename( filename )
293 {
294   QFile sourceFile( gplFilePath() );
295 
296   //read in name
297   if ( sourceFile.open( QIODevice::ReadOnly ) )
298   {
299     QTextStream in( &sourceFile );
300 
301     //find name line
302     QString line;
303     while ( !in.atEnd() && !line.startsWith( QLatin1String( "Name:" ) ) )
304     {
305       line = in.readLine();
306     }
307     if ( !in.atEnd() )
308     {
309       const QRegularExpression rx( "Name:\\s*(\\S.*)$" );
310       const QRegularExpressionMatch match = rx.match( line );
311       if ( match.hasMatch() )
312       {
313         mName = match.captured( 1 );
314       }
315     }
316   }
317   if ( mName.isEmpty() )
318   {
319     mName = mFilename;
320   }
321 
322   // we consider this scheme writable if the user has permission, OR
323   // if it DOESN'T already exist (since new schemes are only created when
324   // first written to)
325   const QFileInfo sourceFileInfo( gplFilePath() );
326   mEditable = !sourceFileInfo.exists() || sourceFileInfo.isWritable();
327 }
328 
schemeName() const329 QString QgsUserColorScheme::schemeName() const
330 {
331   return mName;
332 }
333 
clone() const334 QgsUserColorScheme *QgsUserColorScheme::clone() const
335 {
336   return new QgsUserColorScheme( mFilename );
337 }
338 
flags() const339 QgsColorScheme::SchemeFlags QgsUserColorScheme::flags() const
340 {
341   QgsColorScheme::SchemeFlags f = QgsGplColorScheme::flags();
342 
343   const QgsSettings s;
344   const QStringList showInMenuSchemes = s.value( QStringLiteral( "/colors/showInMenuList" ) ).toStringList();
345 
346   if ( showInMenuSchemes.contains( mName ) )
347   {
348     f |= QgsColorScheme::ShowInColorButtonMenu;
349   }
350 
351   return f;
352 }
353 
erase()354 bool QgsUserColorScheme::erase()
355 {
356   const QString filePath = gplFilePath();
357   if ( filePath.isEmpty() )
358   {
359     return false;
360   }
361 
362   // if file does not exist, nothing to do on the disk, so we can consider erasing done
363   if ( ! QFile::exists( filePath ) )
364   {
365     return true;
366   }
367 
368   //try to erase gpl file
369   return QFile::remove( filePath );
370 }
371 
setShowSchemeInMenu(bool show)372 void QgsUserColorScheme::setShowSchemeInMenu( bool show )
373 {
374   QgsSettings s;
375   QStringList showInMenuSchemes = s.value( QStringLiteral( "/colors/showInMenuList" ) ).toStringList();
376 
377   if ( show && !showInMenuSchemes.contains( mName ) )
378   {
379     showInMenuSchemes << mName;
380   }
381   else if ( !show && showInMenuSchemes.contains( mName ) )
382   {
383     showInMenuSchemes.removeAll( mName );
384   }
385 
386   s.setValue( QStringLiteral( "/colors/showInMenuList" ), showInMenuSchemes );
387 }
388 
gplFilePath()389 QString QgsUserColorScheme::gplFilePath()
390 {
391   const QString palettesDir = QgsApplication::qgisSettingsDirPath() + "palettes";
392 
393   const QDir localDir;
394   if ( !localDir.mkpath( palettesDir ) )
395   {
396     return QString();
397   }
398 
399   return QDir( palettesDir ).filePath( mFilename );
400 }
401