conf: throw on error
[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 magic value for certain parser functions to indicate that the
52  * parser has failed to recognize the string.
53  */
54 static constexpr short COLOR_ERROR = -3;
55
56 /**
57  * A non-standad magic value which means "inherit attributes from the
58  * parent style".
59  */
60 static constexpr attr_t A_INHERIT = ~attr_t(0);
61
62 struct StyleData {
63         /**
64          * A name which can be used to address this style from the
65          * configuration file.
66          */
67         const char *const name;
68
69 #ifdef ENABLE_COLORS
70         /**
71          * Inherit unspecified values from this style.  The special
72          * value #Style::DEFAULT means "don't inherit".
73          */
74         const Style inherit;
75
76         /**
77          * The foreground (text) color in "color" mode.
78          */
79         short fg_color;
80
81         /**
82          * The background (fill) color in "color" mode.
83          */
84         short bg_color;
85
86         /**
87          * The attributes in "color" mode.
88          */
89         attr_t attr;
90 #endif
91
92         /**
93          * The attributes in "mono" mode.
94          */
95         const attr_t mono;
96
97 #ifndef ENABLE_COLORS
98         constexpr StyleData(const char *_name, Style,
99                             short, short, attr_t, attr_t _mono)
100                 :name(_name), mono(_mono) {}
101 #endif
102 };
103
104 #ifndef ENABLE_COLORS
105 constexpr
106 #endif
107 static StyleData styles[size_t(Style::END)] = {
108         /* color pair = field name, color, mono */
109         {
110                 nullptr, Style::DEFAULT,
111                 COLOR_NONE, COLOR_NONE, A_NORMAL,
112                 A_NORMAL,
113         },
114         {
115                 "title", Style::BACKGROUND,
116                 COLOR_WHITE, COLOR_BLUE, A_NORMAL,
117                 A_NORMAL,
118         },
119         {
120                 "title-bold", Style::TITLE,
121                 COLOR_YELLOW, COLOR_INHERIT, A_BOLD,
122                 A_BOLD,
123         },
124         {
125                 "line", Style::TITLE,
126                 COLOR_WHITE, COLOR_INHERIT, A_NORMAL,
127                 A_NORMAL,
128         },
129         {
130                 "line-bold", Style::LINE,
131                 COLOR_INHERIT, COLOR_INHERIT, A_BOLD,
132                 A_BOLD,
133         },
134         {
135                 "line-flags", Style::LINE,
136                 COLOR_GREEN, COLOR_INHERIT, A_BOLD,
137                 A_NORMAL,
138         },
139         {
140                 "list", Style::BACKGROUND,
141                 COLOR_WHITE, COLOR_INHERIT, A_NORMAL,
142                 A_NORMAL,
143         },
144         {
145                 "list-bold", Style::LIST,
146                 COLOR_INHERIT, COLOR_INHERIT, A_BOLD,
147                 A_BOLD,
148         },
149         {
150                 "progressbar", Style::STATUS,
151                 COLOR_WHITE, COLOR_INHERIT, A_BOLD,
152                 A_NORMAL,
153         },
154         {
155                 "progressbar-background", Style::PROGRESSBAR,
156                 COLOR_BLACK, COLOR_INHERIT, A_BOLD,
157                 A_NORMAL,
158         },
159         {
160                 "status-song", Style::BACKGROUND,
161                 COLOR_WHITE, COLOR_BLUE, A_NORMAL,
162                 A_NORMAL,
163         },
164         {
165                 "status-state", Style::STATUS,
166                 COLOR_GREEN, COLOR_INHERIT, A_BOLD,
167                 A_BOLD,
168         },
169         {
170                 "status-time", Style::STATUS_BOLD,
171                 COLOR_INHERIT, COLOR_INHERIT, A_NORMAL,
172                 A_NORMAL,
173         },
174         {
175                 "alert", Style::STATUS,
176                 COLOR_RED, COLOR_INHERIT, A_BOLD,
177                 A_BOLD,
178         },
179         {
180                 "browser-directory", Style::LIST,
181                 COLOR_YELLOW, COLOR_INHERIT, A_INHERIT,
182                 A_NORMAL,
183         },
184         {
185                 "browser-playlist", Style::LIST,
186                 COLOR_RED, COLOR_INHERIT, A_INHERIT,
187                 A_NORMAL,
188         },
189         {
190                 "background", Style::DEFAULT,
191                 COLOR_NONE, COLOR_BLACK, A_NORMAL,
192                 A_NORMAL,
193         },
194 };
195
196 static constexpr auto &
197 GetStyle(Style style)
198 {
199         return styles[size_t(style)];
200 }
201
202 #ifdef ENABLE_COLORS
203
204 gcc_pure
205 static Style
206 StyleByName(const char *name)
207 {
208         for (size_t i = 1; i < size_t(Style::END); ++i)
209                 if (!strcasecmp(styles[i].name, name))
210                         return Style(i);
211
212         return Style::END;
213 }
214
215 static void
216 colors_update_pair(Style style)
217 {
218         auto &data = GetStyle(style);
219
220         int fg = data.fg_color;
221         for (Style i = style; fg == COLOR_INHERIT;) {
222                 i = GetStyle(i).inherit;
223                 assert(i != Style::DEFAULT);
224                 fg = GetStyle(i).fg_color;
225         }
226
227         int bg = data.bg_color;
228         for (Style i = style; bg == COLOR_INHERIT;) {
229                 i = GetStyle(i).inherit;
230                 assert(i != Style::DEFAULT);
231                 bg = GetStyle(i).bg_color;
232         }
233
234         /* apply A_INHERIT (modifies the "attr" value, which is
235            irreversible) */
236         for (Style i = style; data.attr == A_INHERIT;) {
237                 i = GetStyle(i).inherit;
238                 assert(i != Style::DEFAULT);
239                 data.attr = GetStyle(i).attr;
240         }
241
242         init_pair(short(style), fg, bg);
243 }
244
245 /**
246  * Throws on error.
247  */
248 static short
249 ParseBackgroundColor(const char *s)
250 {
251         short color = ParseColorNameOrNumber(s);
252         if (color >= 0)
253                 return color;
254
255         if (!strcasecmp(s, "none"))
256                 return COLOR_NONE;
257
258         throw FormatRuntimeError("%s: %s", _("Unknown color"), s);
259 }
260
261 /**
262  * Throws on error.
263  */
264 static void
265 ParseStyle(StyleData &d, const char *str)
266 {
267         std::string copy(str);
268
269         for (char *cur = strtok(&copy.front(), ","); cur != nullptr;
270              cur = strtok(nullptr, ",")) {
271                 cur = Strip(cur);
272                 char *slash = strchr(cur, '/');
273                 if (slash != nullptr) {
274                         const char *name = slash + 1;
275                         d.bg_color = ParseBackgroundColor(name);
276
277                         *slash = 0;
278
279                         if (*cur == 0)
280                                 continue;
281                 }
282
283                 /* Legacy colors (brightblue,etc) */
284                 if (!strncasecmp(cur, "bright", 6)) {
285                         d.attr |= A_BOLD;
286                         cur += 6;
287                 }
288
289                 /* Colors */
290                 short b = ParseColorNameOrNumber(cur);
291                 if (b >= 0) {
292                         d.fg_color = b;
293                         continue;
294                 }
295
296                 if (!strcasecmp(cur, "none"))
297                         d.fg_color = COLOR_NONE;
298                 else if (!strcasecmp(cur, "grey") ||
299                          !strcasecmp(cur, "gray")) {
300                         d.fg_color = COLOR_BLACK;
301                         d.attr |= A_BOLD;
302                 }
303
304                 /* Attributes */
305                 else if (!strcasecmp(cur, "standout"))
306                         d.attr |= A_STANDOUT;
307                 else if (!strcasecmp(cur, "underline"))
308                         d.attr |= A_UNDERLINE;
309                 else if (!strcasecmp(cur, "reverse"))
310                         d.attr |= A_REVERSE;
311                 else if (!strcasecmp(cur, "blink"))
312                         d.attr |= A_BLINK;
313                 else if (!strcasecmp(cur, "dim"))
314                         d.attr |= A_DIM;
315                 else if (!strcasecmp(cur, "bold"))
316                         d.attr |= A_BOLD;
317                 else
318                         throw FormatRuntimeError("%s: %s",
319                                                  _("Unknown color"), str);
320         }
321 }
322
323 void
324 ModifyStyle(const char *name, const char *value)
325 {
326         const auto style = StyleByName(name);
327         if (style == Style::END)
328                 throw FormatRuntimeError("%s: %s",
329                                          _("Unknown color field"), name);
330
331         auto &data = GetStyle(style);
332
333         if (style == Style::BACKGROUND) {
334                 /* "background" is a special style which all other
335                    styles inherit their background color from; if the
336                    user configures a color, it will be the background
337                    color, but no attributes */
338                 data.bg_color = ParseBackgroundColor(value);
339                 return;
340         }
341
342         return ParseStyle(data, value);
343 }
344
345 void
346 ApplyStyles()
347 {
348         if (has_colors()) {
349                 /* initialize color support */
350                 start_color();
351                 use_default_colors();
352                 /* define any custom colors defined in the configuration file */
353                 ApplyCustomColors();
354
355                 if (options.enable_colors) {
356                         for (size_t i = 1; i < size_t(Style::END); ++i)
357                                 /* update the color pairs */
358                                 colors_update_pair(Style(i));
359                 }
360         } else if (options.enable_colors) {
361                 fprintf(stderr, "%s\n",
362                         _("Terminal lacks color capabilities"));
363                 options.enable_colors = false;
364         }
365 }
366 #endif
367
368 void
369 SelectStyle(WINDOW *w, Style style)
370 {
371         const auto &data = GetStyle(style);
372
373 #ifdef ENABLE_COLORS
374         if (options.enable_colors) {
375                 /* color mode */
376                 wattr_set(w, data.attr, short(style), nullptr);
377         } else {
378 #endif
379                 /* mono mode */
380                 (void)wattrset(w, data.mono);
381 #ifdef ENABLE_COLORS
382         }
383 #endif
384 }