1123ff8a10804199beb952b2332061f110dab382
[ncmpc-debian.git] / src / Styles.cxx
1 /* ncmpc (Ncurses MPD Client)
2  * (c) 2004-2019 The Music Player Daemon Project
3  * Project homepage: http://musicpd.org
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18  */
19
20 #include "Styles.hxx"
21 #include "BasicColors.hxx"
22 #include "CustomColors.hxx"
23 #include "i18n.h"
24 #include "util/RuntimeError.hxx"
25 #include "util/StringStrip.hxx"
26 #include "util/Compiler.h"
27
28 #ifdef ENABLE_COLORS
29 #include "Options.hxx"
30 #endif
31
32 #include <assert.h>
33 #include <stdio.h>
34 #include <strings.h>
35 #include <string.h>
36
37 /**
38  * Use the terminal's default color.
39  *
40  * @see init_pair(3ncurses)
41  */
42 static constexpr short COLOR_NONE = -1;
43
44 /**
45  * A non-standad magic value which means "inherit this color from the
46  * parent style".
47  */
48 static constexpr short COLOR_INHERIT = -2;
49
50 /**
51  * A non-standad magic value which means "inherit attributes from the
52  * parent style".
53  */
54 static constexpr attr_t A_INHERIT = ~attr_t(0);
55
56 struct StyleData {
57         /**
58          * A name which can be used to address this style from the
59          * configuration file.
60          */
61         const char *const name;
62
63 #ifdef ENABLE_COLORS
64         /**
65          * Inherit unspecified values from this style.  The special
66          * value #Style::DEFAULT means "don't inherit".
67          */
68         const Style inherit;
69
70         /**
71          * The foreground (text) color in "color" mode.
72          */
73         short fg_color;
74
75         /**
76          * The background (fill) color in "color" mode.
77          */
78         short bg_color;
79
80         /**
81          * The attributes in "color" mode.
82          */
83         attr_t attr;
84 #endif
85
86         /**
87          * The attributes in "mono" mode.
88          */
89         const attr_t mono;
90
91 #ifndef ENABLE_COLORS
92         constexpr StyleData(const char *_name, Style,
93                             short, short, attr_t, attr_t _mono)
94                 :name(_name), mono(_mono) {}
95 #endif
96 };
97
98 #ifndef ENABLE_COLORS
99 constexpr
100 #endif
101 static StyleData styles[size_t(Style::END)] = {
102         /* color pair = field name, color, mono */
103         {
104                 nullptr, Style::DEFAULT,
105                 COLOR_NONE, COLOR_NONE, A_NORMAL,
106                 A_NORMAL,
107         },
108         {
109                 "title", Style::BACKGROUND,
110                 COLOR_WHITE, COLOR_BLUE, A_NORMAL,
111                 A_NORMAL,
112         },
113         {
114                 "title-bold", Style::TITLE,
115                 COLOR_YELLOW, COLOR_INHERIT, A_BOLD,
116                 A_BOLD,
117         },
118         {
119                 "line", Style::TITLE,
120                 COLOR_WHITE, COLOR_INHERIT, A_NORMAL,
121                 A_NORMAL,
122         },
123         {
124                 "line-bold", Style::LINE,
125                 COLOR_INHERIT, COLOR_INHERIT, A_BOLD,
126                 A_BOLD,
127         },
128         {
129                 "line-flags", Style::LINE,
130                 COLOR_GREEN, COLOR_INHERIT, A_BOLD,
131                 A_NORMAL,
132         },
133         {
134                 "list", Style::BACKGROUND,
135                 COLOR_WHITE, COLOR_INHERIT, A_NORMAL,
136                 A_NORMAL,
137         },
138         {
139                 "list-bold", Style::LIST,
140                 COLOR_INHERIT, COLOR_INHERIT, A_BOLD,
141                 A_BOLD,
142         },
143         {
144                 "progressbar", Style::STATUS,
145                 COLOR_WHITE, COLOR_INHERIT, A_BOLD,
146                 A_NORMAL,
147         },
148         {
149                 "progressbar-background", Style::PROGRESSBAR,
150                 COLOR_BLACK, COLOR_INHERIT, A_BOLD,
151                 A_NORMAL,
152         },
153         {
154                 "status-song", Style::BACKGROUND,
155                 COLOR_WHITE, COLOR_BLUE, A_NORMAL,
156                 A_NORMAL,
157         },
158         {
159                 "status-state", Style::STATUS,
160                 COLOR_GREEN, COLOR_INHERIT, A_BOLD,
161                 A_BOLD,
162         },
163         {
164                 "status-time", Style::STATUS_BOLD,
165                 COLOR_INHERIT, COLOR_INHERIT, A_NORMAL,
166                 A_NORMAL,
167         },
168         {
169                 "alert", Style::STATUS,
170                 COLOR_RED, COLOR_INHERIT, A_BOLD,
171                 A_BOLD,
172         },
173         {
174                 "browser-directory", Style::LIST,
175                 COLOR_YELLOW, COLOR_INHERIT, A_INHERIT,
176                 A_NORMAL,
177         },
178         {
179                 "browser-playlist", Style::LIST,
180                 COLOR_RED, COLOR_INHERIT, A_INHERIT,
181                 A_NORMAL,
182         },
183         {
184                 "background", Style::DEFAULT,
185                 COLOR_NONE, COLOR_BLACK, A_NORMAL,
186                 A_NORMAL,
187         },
188 };
189
190 static constexpr auto &
191 GetStyle(Style style)
192 {
193         return styles[size_t(style)];
194 }
195
196 #ifdef ENABLE_COLORS
197
198 gcc_pure
199 static Style
200 StyleByName(const char *name)
201 {
202         for (size_t i = 1; i < size_t(Style::END); ++i)
203                 if (!strcasecmp(styles[i].name, name))
204                         return Style(i);
205
206         return Style::END;
207 }
208
209 static void
210 colors_update_pair(Style style)
211 {
212         auto &data = GetStyle(style);
213
214         int fg = data.fg_color;
215         for (Style i = style; fg == COLOR_INHERIT;) {
216                 i = GetStyle(i).inherit;
217                 assert(i != Style::DEFAULT);
218                 fg = GetStyle(i).fg_color;
219         }
220
221         int bg = data.bg_color;
222         for (Style i = style; bg == COLOR_INHERIT;) {
223                 i = GetStyle(i).inherit;
224                 assert(i != Style::DEFAULT);
225                 bg = GetStyle(i).bg_color;
226         }
227
228         /* apply A_INHERIT (modifies the "attr" value, which is
229            irreversible) */
230         for (Style i = style; data.attr == A_INHERIT;) {
231                 i = GetStyle(i).inherit;
232                 assert(i != Style::DEFAULT);
233                 data.attr = GetStyle(i).attr;
234         }
235
236         init_pair(short(style), fg, bg);
237 }
238
239 /**
240  * Throws on error.
241  */
242 static short
243 ParseBackgroundColor(const char *s)
244 {
245         short color = ParseColorNameOrNumber(s);
246         if (color >= 0)
247                 return color;
248
249         if (!strcasecmp(s, "none"))
250                 return COLOR_NONE;
251
252         throw FormatRuntimeError("%s: %s", _("Unknown color"), s);
253 }
254
255 /**
256  * Throws on error.
257  */
258 static void
259 ParseStyle(StyleData &d, const char *str)
260 {
261         std::string copy(str);
262
263         for (char *cur = strtok(&copy.front(), ","); cur != nullptr;
264              cur = strtok(nullptr, ",")) {
265                 cur = Strip(cur);
266                 char *slash = strchr(cur, '/');
267                 if (slash != nullptr) {
268                         const char *name = slash + 1;
269                         d.bg_color = ParseBackgroundColor(name);
270
271                         *slash = 0;
272
273                         if (*cur == 0)
274                                 continue;
275                 }
276
277                 /* Legacy colors (brightblue,etc) */
278                 if (!strncasecmp(cur, "bright", 6)) {
279                         d.attr |= A_BOLD;
280                         cur += 6;
281                 }
282
283                 /* Colors */
284                 short b = ParseColorNameOrNumber(cur);
285                 if (b >= 0) {
286                         d.fg_color = b;
287                         continue;
288                 }
289
290                 if (!strcasecmp(cur, "none"))
291                         d.fg_color = COLOR_NONE;
292                 else if (!strcasecmp(cur, "grey") ||
293                          !strcasecmp(cur, "gray")) {
294                         d.fg_color = COLOR_BLACK;
295                         d.attr |= A_BOLD;
296                 }
297
298                 /* Attributes */
299                 else if (!strcasecmp(cur, "standout"))
300                         d.attr |= A_STANDOUT;
301                 else if (!strcasecmp(cur, "underline"))
302                         d.attr |= A_UNDERLINE;
303                 else if (!strcasecmp(cur, "reverse"))
304                         d.attr |= A_REVERSE;
305                 else if (!strcasecmp(cur, "blink"))
306                         d.attr |= A_BLINK;
307                 else if (!strcasecmp(cur, "dim"))
308                         d.attr |= A_DIM;
309                 else if (!strcasecmp(cur, "bold"))
310                         d.attr |= A_BOLD;
311                 else
312                         throw FormatRuntimeError("%s: %s",
313                                                  _("Unknown color"), str);
314         }
315 }
316
317 void
318 ModifyStyle(const char *name, const char *value)
319 {
320         const auto style = StyleByName(name);
321         if (style == Style::END)
322                 throw FormatRuntimeError("%s: %s",
323                                          _("Unknown color field"), name);
324
325         auto &data = GetStyle(style);
326
327         if (style == Style::BACKGROUND) {
328                 /* "background" is a special style which all other
329                    styles inherit their background color from; if the
330                    user configures a color, it will be the background
331                    color, but no attributes */
332                 data.bg_color = ParseBackgroundColor(value);
333                 return;
334         }
335
336         return ParseStyle(data, value);
337 }
338
339 void
340 ApplyStyles()
341 {
342         if (has_colors()) {
343                 /* initialize color support */
344                 start_color();
345                 use_default_colors();
346                 /* define any custom colors defined in the configuration file */
347                 ApplyCustomColors();
348
349                 if (options.enable_colors) {
350                         for (size_t i = 1; i < size_t(Style::END); ++i)
351                                 /* update the color pairs */
352                                 colors_update_pair(Style(i));
353                 }
354         } else if (options.enable_colors) {
355                 fprintf(stderr, "%s\n",
356                         _("Terminal lacks color capabilities"));
357                 options.enable_colors = false;
358         }
359 }
360 #endif
361
362 void
363 SelectStyle(WINDOW *w, Style style)
364 {
365         const auto &data = GetStyle(style);
366
367 #ifdef ENABLE_COLORS
368         if (options.enable_colors) {
369                 /* color mode */
370                 wattr_set(w, data.attr, short(style), nullptr);
371         } else {
372 #endif
373                 /* mono mode */
374                 (void)wattrset(w, data.mono);
375 #ifdef ENABLE_COLORS
376         }
377 #endif
378 }