1 /*
2     SPDX-FileCopyrightText: 2002 Jason Harris <kstars@30doradus.org>
3 
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "colorscheme.h"
8 
9 #include "kspaths.h"
10 #include "ksutils.h"
11 #include "Options.h"
12 #include "auxiliary/ksnotification.h"
13 #include "skyobjects/starobject.h"
14 #ifdef KSTARS_LITE
15 #include "skymaplite.h"
16 #else
17 #include "skyqpainter.h"
18 #endif
19 
ColorScheme()20 ColorScheme::ColorScheme() : FileName()
21 {
22     //Each color has two names associated with it.  The KeyName is its
23     //identification in the QMap, the *.colors file, and the config file.
24     //The Name is what appears in the ViewOpsDialog ListBox.
25     //In addition, we define default RGB strings for each item.
26     //To add another color to the Palette, just add an entry for KeyName,
27     //Name and Default here.
28 
29     appendItem("SkyColor", i18n("Sky"), "#000000");
30     appendItem("DSOColor", i18n("Messier Object"), "#008f00");
31     appendItem("HSTColor", i18nc("Object with extra attached URLs", "Object w/ Links"), "#930000");
32     appendItem("SNameColor", i18n("Star Name"), "#577d7d");
33     appendItem("DSNameColor", i18n("Deep Sky Object Name"), "#75759c");
34     appendItem("PNameColor", i18n("Planet Name"), "#ac9800");
35     appendItem("CNameColor", i18nc("Constellation Name", "Constell. Name"), "#718488");
36     appendItem("CLineColor", i18nc("Constellation Line", "Constell. Line"), "#3d3d3d");
37     appendItem("CBoundColor", i18nc("Constellation Boundary", "Constell. Boundary"), "#222f2f");
38     appendItem("CBoundHighColor", i18nc("Highlighted Constellation Boundary", "Constell. Boundary Highlight"),
39                "#445555");
40     appendItem("MWColor", i18nc("refers to the band of stars in the sky due to the Galactic plane", "Milky Way"),
41                "#0d1115");
42     appendItem("EqColor", i18n("Equator"), "#909090");
43     appendItem("EclColor", i18n("Ecliptic"), "#613d12");
44     appendItem("HorzColor", i18n("Horizon"), "#091f14");
45     appendItem("LocalMeridianColor", i18n("Local Meridian"), "#0059b3");
46     appendItem("CompassColor", i18n("Compass Labels"), "#909055");
47     appendItem("EquatorialGridColor", i18n("Equatorial Coordinate Grid"), "#445566");
48     appendItem("HorizontalGridColor", i18n("Horizontal Coordinate Grid"), "#091f14");
49     appendItem("BoxTextColor", i18n("Info Box Text"), "#d2dbef");
50     appendItem("BoxGrabColor", i18n("Info Box Selected"), "#900000");
51     appendItem("BoxBGColor", i18n("Info Box Background"), "#000000");
52     appendItem("TargetColor", i18n("Target Indicator"), "#DD0000");
53     appendItem("UserLabelColor", i18n("User Labels"), "#AAAAAA");
54     appendItem("PlanetTrailColor", i18n("Planet Trails"), "#993311");
55     appendItem("AngularRuler", i18n("Angular Distance Ruler"), "#445566");
56     appendItem("ObsListColor", i18n("Observing List Label"), "#FF0000");
57     appendItem("StarHopRouteColor", i18n("Star-Hop Route"), "#00FFFF");
58     appendItem("VisibleSatColor", i18n("Visible Satellites"), "#00FF00");
59     appendItem("SatColor", i18n("Satellites"), "#FF0000");
60     appendItem("SatLabelColor", i18n("Satellites Labels"), "#640000");
61     appendItem("SupernovaColor", i18n("Supernovae"), "#FFA500");
62     appendItem("ArtificialHorizonColor", i18n("Artificial Horizon"), "#C82828");
63     appendItem("RAGuideError", i18n("RA Guide Error"), "#00FF00");
64     appendItem("DEGuideError", i18n("DEC Guide Error"), "#00A5FF");
65     appendItem("SolverFOVColor", i18n("Solver FOV"), "#FFFFFF");
66     appendItem("SensorFOVColor", i18n("Sensor FOV"), "#FFAA00");
67     appendItem("HIPSGridColor", i18n("HiPS Grid"), "#FFFFFF");
68     appendItem("FITSObjectLabelColor", i18n("FITS Image Object Label"), "#00FF00");
69 
70     //Load colors from config object
71     loadFromConfig();
72 
73     //Default values for integer variables:
74     StarColorMode      = 0;
75     StarColorIntensity = 4;
76     DarkPalette        = 0;
77 }
78 
appendItem(const QString & key,const QString & name,const QString & def)79 void ColorScheme::appendItem(const QString &key, const QString &name, const QString &def)
80 {
81     KeyName.append(key);
82     Name.append(name);
83     Default.append(def);
84 }
85 
colorNamed(const QString & name) const86 QColor ColorScheme::colorNamed(const QString &name) const
87 {
88     if (!hasColorNamed(name))
89     {
90         qWarning() << "No color named" << name << "found in color scheme.";
91         // Return white if no color found
92         return QColor(Qt::white);
93     }
94     return QColor(Palette[name]);
95 }
96 
colorAt(int i) const97 QColor ColorScheme::colorAt(int i) const
98 {
99     return QColor(Palette[KeyName.at(i)]);
100 }
101 
nameAt(int i) const102 QString ColorScheme::nameAt(int i) const
103 {
104     return Name.at(i);
105 }
106 
keyAt(int i) const107 QString ColorScheme::keyAt(int i) const
108 {
109     return KeyName.at(i);
110 }
111 
nameFromKey(const QString & key) const112 QString ColorScheme::nameFromKey(const QString &key) const
113 {
114     return nameAt(KeyName.indexOf(key));
115 }
116 
setColor(const QString & key,const QString & color)117 void ColorScheme::setColor(const QString &key, const QString &color)
118 {
119     //We can blindly insert() the new value; if the key exists, the old value is replaced
120     Palette.insert(key, color);
121 
122     KConfigGroup cg = KSharedConfig::openConfig()->group("Colors");
123     cg.writeEntry(key, color);
124 }
125 
load(const QString & name)126 bool ColorScheme::load(const QString &name)
127 {
128     QString filename = name.toLower().trimmed();
129     QFile file;
130     int inew = 0, iold = 0;
131     bool ok = false;
132 
133     //Parse default names which don't follow the regular file-naming scheme
134     if (name == i18nc("use default color scheme", "Default Colors"))
135         filename = "classic.colors";
136     if (name == i18nc("use 'star chart' color scheme", "Star Chart"))
137         filename = "chart.colors";
138     if (name == i18nc("use 'night vision' color scheme", "Night Vision"))
139         filename = "night.colors";
140 
141     //Try the filename if it ends with ".colors"
142     if (filename.endsWith(QLatin1String(".colors")))
143         ok = KSUtils::openDataFile(file, filename);
144 
145     //If that didn't work, try assuming that 'name' is the color scheme name
146     //convert it to a filename exactly as ColorScheme::save() does
147     if (!ok)
148     {
149         if (!filename.isEmpty())
150         {
151             filename.replace(' ', '-').append(".colors");
152             ok = KSUtils::openDataFile(file, filename);
153         }
154 
155         if (!ok)
156         {
157             qDebug() << QString("Unable to load color scheme named %1. Also tried %2.").arg(name, filename);
158             return false;
159         }
160     }
161 
162     //If we reach here, the file should have been successfully opened
163     QTextStream stream(&file);
164 
165     //first line is the star-color mode and star color intensity and dark palette
166     QString line      = stream.readLine();
167     QStringList modes = line.split(':');
168 
169     // Star Color Mode
170     if (modes.count() > 0)
171     {
172         int newmode = modes[0].toInt(&ok);
173         if (ok)
174             setStarColorMode(newmode);
175     }
176 
177     // Star Intensity
178     if (modes.count() > 1)
179     {
180         int newintens = modes[1].toInt(&ok);
181         if (ok)
182             setStarColorIntensity(newintens);
183     }
184 
185     // Dark Palette
186 #if 0
187     if (modes.count() > 2)
188     {
189         int newintens = modes[2].toInt(&ok);
190         if (ok)
191             setDarkPalette(newintens == 1);
192     }
193 #endif
194 
195     //More flexible method for reading in color values.  Any order is acceptable, and
196     //missing entries are ignored.
197     while (!stream.atEnd())
198     {
199         line = stream.readLine();
200 
201         if (line.count(':') ==
202                 1) //the new color preset format contains a ":" in each line, followed by the name of the color
203         {
204             ++inew;
205             if (iold)
206                 return false; //we read at least one line without a colon...file is corrupted.
207 
208             //If this line has a valid Key, set the color.
209             QString tkey  = line.mid(line.indexOf(':') + 1).trimmed();
210             QString tname = line.left(line.indexOf(':') - 1);
211 
212             if (KeyName.contains(tkey))
213             {
214                 setColor(tkey, tname);
215             }
216             else //attempt to translate from old color ID
217             {
218                 QString k(line.mid(5).trimmed() + "Color");
219                 if (KeyName.contains(k))
220                 {
221                     setColor(k, tname);
222                 }
223                 else
224                 {
225                     qWarning() << "Could not use the key \"" << tkey << "\" from the color scheme file \"" << filename
226                                << "\".  I also tried \"" << k << "\".";
227                 }
228             }
229         }
230         else // no ':' seen in the line, so we must assume the old format
231         {
232             ++iold;
233             if (inew)
234                 return false; //a previous line had a colon, this line doesn't.  File is corrupted.
235 
236             //Assuming the old *.colors format.  Loop through the KeyName list,
237             //and assign each color.  Note that order matters here, but only here
238             //(so if you don't use the old format, then order doesn't ever matter)
239             foreach (const QString &key, KeyName)
240                 setColor(key, line.left(7));
241         }
242     }
243 
244     FileName = filename;
245     return true;
246 }
247 
save(const QString & name)248 bool ColorScheme::save(const QString &name)
249 {
250     QFile file;
251 
252     //Construct a file name from the scheme name.  Make lowercase, replace spaces with "-",
253     //and append ".colors".
254     QString filename = name.toLower().trimmed();
255     if (!filename.isEmpty())
256     {
257         for (int i = 0; i < filename.length(); ++i)
258             if (filename.at(i) == ' ')
259                 filename.replace(i, 1, "-");
260 
261         filename = filename.append(".colors");
262         //determine filename in local user KDE directory tree.
263         file.setFileName(QDir(KSPaths::writableLocation(QStandardPaths::AppDataLocation)).filePath(filename));
264 
265         //if (file.exists() || !file.open(QIODevice::ReadWrite | QIODevice::Append))
266         if (!file.open(QIODevice::ReadWrite))
267         {
268             KSNotification::sorry(i18n("Local color scheme file could not be opened.\nScheme cannot be recorded."));
269             return false;
270         }
271         else
272         {
273             QTextStream stream(&file);
274             stream << StarColorMode << ":" << StarColorIntensity << ":" << DarkPalette << '\n';
275 
276             foreach (const QString &key, KeyName)
277                 stream << Palette[key] << " :" << key << '\n';
278             file.close();
279         }
280 
281         //determine filename in local user KDE directory tree.
282         file.setFileName(QDir(KSPaths::writableLocation(QStandardPaths::AppDataLocation)).filePath("colors.dat"));
283 
284         if (!file.open(QIODevice::ReadWrite))
285         {
286             KSNotification::sorry(i18n("Local color scheme index file could not be opened.\nScheme cannot be recorded."));
287         }
288         else
289         {
290             bool found = false;
291             QString schemeLine = name + ':' + filename;
292 
293             // Check if the scheme line is in the colors.dat file
294             // If not, then we add it
295             QTextStream stream(&file);
296             while (stream.atEnd() == false)
297             {
298                 QString line = stream.readLine();
299                 if (line == schemeLine)
300                 {
301                     found = true;
302                     break;
303                 }
304             }
305 
306             if (found == false)
307             {
308                 stream << schemeLine << '\n';
309                 file.close();
310             }
311         }
312     }
313     else
314     {
315         KSNotification::sorry("Invalid filename requested.\nScheme cannot be recorded.");
316         return false;
317     }
318 
319     FileName = filename;
320     saveToConfig();
321     return true;
322 }
323 
loadFromConfig()324 void ColorScheme::loadFromConfig()
325 {
326     KConfigGroup cg = KSharedConfig::openConfig()->group("Colors");
327 
328     for (int i = 0; i < KeyName.size(); ++i)
329         setColor(KeyName.at(i), cg.readEntry(KeyName.at(i).toUtf8().constData(), Default.at(i)));
330 
331     setStarColorModeIntensity(cg.readEntry("StarColorMode", 0), cg.readEntry("StarColorIntensity", 5));
332     //setDarkPalette(cg.readEntry("DarkAppColors", false));
333 
334     FileName = cg.readEntry("ColorSchemeFile", "moonless-night.colors");
335 }
336 
saveToConfig()337 void ColorScheme::saveToConfig()
338 {
339     KConfigGroup cg = KSharedConfig::openConfig()->group("Colors");
340     for (int i = 0; i < KeyName.size(); ++i)
341     {
342         QString c = colorNamed(KeyName.at(i)).name();
343         cg.writeEntry(KeyName.at(i), c);
344     }
345 
346     cg.writeEntry("StarColorMode", starColorMode());
347     cg.writeEntry("StarColorIntensity", starColorIntensity());
348     cg.writeEntry("ColorSchemeFile", FileName);
349     cg.writeEntry("DarkAppColors", useDarkPalette());
350 }
351 
setStarColorMode(int mode)352 void ColorScheme::setStarColorMode(int mode)
353 {
354     StarColorMode = mode;
355     Options::setStarColorMode(mode);
356 #ifndef KSTARS_LITE
357     SkyQPainter::initStarImages();
358 #endif
359 }
360 
361 #if 0
362 void ColorScheme::setDarkPalette(bool enable)
363 {
364     DarkPalette = enable ? 1 : 0;
365     Options::setDarkAppColors(enable);
366 #ifndef KSTARS_LITE
367     SkyQPainter::initStarImages();
368 #endif
369 }
370 #endif
371 
setStarColorIntensity(int intens)372 void ColorScheme::setStarColorIntensity(int intens)
373 {
374     StarColorIntensity = intens;
375     Options::setStarColorIntensity(intens);
376 #ifndef KSTARS_LITE
377     SkyQPainter::initStarImages();
378 #endif
379 }
380 
setStarColorModeIntensity(int mode,int intens)381 void ColorScheme::setStarColorModeIntensity(int mode, int intens)
382 {
383     StarColorMode      = mode;
384     StarColorIntensity = intens;
385     Options::setStarColorMode(mode);
386     Options::setStarColorIntensity(intens);
387 #ifndef KSTARS_LITE
388     SkyQPainter::initStarImages();
389 #endif
390 }
391