new code-level defaults for audio peak meter colors
[ardour.git] / gtk2_ardour / ui_config.cc
1 /*
2     Copyright (C) 1999-2014 Paul Davis
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 #include <iostream>
21 #include <sstream>
22 #include <unistd.h>
23 #include <cstdlib>
24 #include <cstdio> /* for snprintf, grrr */
25
26 #include <glibmm/miscutils.h>
27 #include <glib/gstdio.h>
28
29 #include "pbd/failed_constructor.h"
30 #include "pbd/xml++.h"
31 #include "pbd/file_utils.h"
32 #include "pbd/error.h"
33 #include "pbd/stacktrace.h"
34
35 #include "gtkmm2ext/rgb_macros.h"
36 #include "gtkmm2ext/gtk_ui.h"
37
38 #include "ardour/filesystem_paths.h"
39
40 #include "ardour_ui.h"
41 #include "global_signals.h"
42 #include "ui_config.h"
43
44 #include "i18n.h"
45
46 using namespace std;
47 using namespace PBD;
48 using namespace ARDOUR;
49 using namespace ArdourCanvas;
50
51 static const char* ui_config_file_name = "ui_config";
52 static const char* default_ui_config_file_name = "default_ui_config";
53 UIConfiguration* UIConfiguration::_instance = 0;
54
55 static const double hue_width = 18.0;
56
57 UIConfiguration::UIConfiguration ()
58         :
59 #undef  UI_CONFIG_VARIABLE
60 #define UI_CONFIG_VARIABLE(Type,var,name,val) var (name,val),
61 #define CANVAS_FONT_VARIABLE(var,name) var (name),
62 #include "ui_config_vars.h"
63 #include "canvas_vars.h"
64 #undef  UI_CONFIG_VARIABLE
65 #undef  CANVAS_FONT_VARIABLE
66
67         /* initialize all the base colors using default
68            colors for now. these will be reset when/if
69            we load the UI config file.
70         */
71
72 #undef CANVAS_BASE_COLOR
73 #define CANVAS_BASE_COLOR(var,name,val) var (name,quantized (val)),
74 #include "base_colors.h"
75 #undef CANVAS_BASE_COLOR
76
77         _dirty (false),
78         aliases_modified (false),
79         derived_modified (false),
80         _saved_state_node (""),
81         _saved_state_version (-1)
82         
83 {
84         _instance = this;
85
86         /* pack all base colors into the configurable color map so that
87            derived colors can use them.
88         */
89           
90 #undef CANVAS_BASE_COLOR
91 #define CANVAS_BASE_COLOR(var,name,color) configurable_colors.insert (make_pair (name,&var));
92 #include "base_colors.h"
93 #undef CANVAS_BASE_COLOR
94
95 #undef CANVAS_COLOR
96 #define CANVAS_COLOR(var,name,base,modifier) relative_colors.insert (make_pair (name, RelativeHSV (base,modifier)));
97 #include "colors.h"
98 #undef CANVAS_COLOR
99         
100 #undef COLOR_ALIAS
101 #define COLOR_ALIAS(var,name,alias) color_aliases.insert (make_pair (name,alias));
102 #include "color_aliases.h"
103 #undef CANVAS_COLOR
104
105         load_state();
106
107         ARDOUR_UI_UTILS::ColorsChanged.connect (boost::bind (&UIConfiguration::colors_changed, this));
108
109         ParameterChanged.connect (sigc::mem_fun (*this, &UIConfiguration::parameter_changed));
110
111         /* force GTK theme setting, so that RC file will work */
112         
113         reset_gtk_theme ();
114 }
115
116 UIConfiguration::~UIConfiguration ()
117 {
118 }
119
120 void
121 UIConfiguration::colors_changed ()
122 {
123         _dirty = true;
124
125         reset_gtk_theme ();
126
127         /* In theory, one of these ought to work:
128
129            gtk_rc_reparse_all_for_settings (gtk_settings_get_default(), true);
130            gtk_rc_reset_styles (gtk_settings_get_default());
131
132            but in practice, neither of them do. So just reload the current
133            GTK RC file, which causes a reset of all styles and a redraw
134         */
135
136         parameter_changed ("ui-rc_file");
137 }
138
139 void
140 UIConfiguration::parameter_changed (string param)
141 {
142         _dirty = true;
143         
144         if (param == "ui-rc-file") {
145                 load_rc_file (get_ui_rc_file(), true);
146         }
147
148         save_state ();
149 }
150
151 void
152 UIConfiguration::reset_gtk_theme ()
153 {
154         stringstream ss;
155
156         ss << "gtk_color_scheme = \"" << hex;
157         
158         for (ColorAliases::iterator g = color_aliases.begin(); g != color_aliases.end(); ++g) {
159                 
160                 if (g->first.find ("gtk_") == 0) {
161                         ColorAliases::const_iterator a = color_aliases.find (g->first);
162                         const string gtk_name = g->first.substr (4);
163                         ss << gtk_name << ":#" << std::setw (6) << setfill ('0') << (color (g->second) >> 8) << ';';
164                 }
165         }
166
167         ss << '"' << dec << endl;
168
169         /* reset GTK color scheme */
170
171         Gtk::Settings::get_default()->property_gtk_color_scheme() = ss.str();
172 }
173         
174 UIConfiguration::RelativeHSV
175 UIConfiguration::color_as_relative_hsv (Color c)
176 {
177         HSV variable (c);
178         HSV closest;
179         double shortest_distance = DBL_MAX;
180         string closest_name;
181
182         map<string,ColorVariable<Color>*>::iterator f;
183         std::map<std::string,HSV> palette;
184
185         for (f = configurable_colors.begin(); f != configurable_colors.end(); ++f) {
186                 palette.insert (make_pair (f->first, HSV (f->second->get())));
187         }
188
189         for (map<string,HSV>::iterator f = palette.begin(); f != palette.end(); ++f) {
190                 
191                 double d;
192                 HSV fixed (f->second);
193                 
194                 if (fixed.is_gray() || variable.is_gray()) {
195                         /* at least one is achromatic; HSV::distance() will do
196                          * the right thing
197                          */
198                         d = fixed.distance (variable);
199                 } else {
200                         /* chromatic: compare ONLY hue because our task is
201                            to pick the HUE closest and then compute
202                            a modifier. We want to keep the number of 
203                            hues low, and by computing perceptual distance 
204                            we end up finding colors that are to each
205                            other without necessarily be close in hue.
206                         */
207                         d = fabs (variable.h - fixed.h);
208                 }
209
210                 if (d < shortest_distance) {
211                         closest = fixed;
212                         closest_name = f->first;
213                         shortest_distance = d;
214                 }
215         }
216         
217         /* we now know the closest color of the fixed colors to 
218            this variable color. Compute the HSV diff and
219            use it to redefine the variable color in terms of the
220            fixed one.
221         */
222         
223         HSV delta = variable.delta (closest);
224
225         /* quantize hue delta so we don't end up with many subtle hues caused
226          * by original color choices
227          */
228
229         delta.h = hue_width * (round (delta.h/hue_width));
230
231         return RelativeHSV (closest_name, delta);
232 }
233
234 void
235 UIConfiguration::map_parameters (boost::function<void (std::string)>& functor)
236 {
237 #undef  UI_CONFIG_VARIABLE
238 #define UI_CONFIG_VARIABLE(Type,var,Name,value) functor (Name);
239 #include "ui_config_vars.h"
240 #undef  UI_CONFIG_VARIABLE
241 }
242
243 int
244 UIConfiguration::load_defaults ()
245 {
246         int found = 0;
247         std::string rcfile;
248
249         if (find_file (ardour_config_search_path(), default_ui_config_file_name, rcfile) ) {
250                 XMLTree tree;
251                 found = 1;
252
253                 info << string_compose (_("Loading default ui configuration file %1"), rcfile) << endmsg;
254
255                 if (!tree.read (rcfile.c_str())) {
256                         error << string_compose(_("cannot read default ui configuration file \"%1\""), rcfile) << endmsg;
257                         return -1;
258                 }
259
260                 if (set_state (*tree.root(), Stateful::loading_state_version)) {
261                         error << string_compose(_("default ui configuration file \"%1\" not loaded successfully."), rcfile) << endmsg;
262                         return -1;
263                 }
264                 
265                 _dirty = false;
266         }
267
268         ARDOUR_UI_UTILS::ColorsChanged ();
269
270         return found;
271 }
272
273 int
274 UIConfiguration::load_state ()
275 {
276         bool found = false;
277
278         std::string rcfile;
279
280         if (find_file (ardour_config_search_path(), default_ui_config_file_name, rcfile)) {
281                 XMLTree tree;
282                 found = true;
283
284                 info << string_compose (_("Loading default ui configuration file %1"), rcfile) << endmsg;
285
286                 if (!tree.read (rcfile.c_str())) {
287                         error << string_compose(_("cannot read default ui configuration file \"%1\""), rcfile) << endmsg;
288                         return -1;
289                 }
290
291                 if (set_state (*tree.root(), Stateful::loading_state_version)) {
292                         error << string_compose(_("default ui configuration file \"%1\" not loaded successfully."), rcfile) << endmsg;
293                         return -1;
294                 }
295
296                 /* make a copy */
297         }
298
299         if (find_file (ardour_config_search_path(), ui_config_file_name, rcfile)) {
300                 XMLTree tree;
301                 found = true;
302
303                 info << string_compose (_("Loading user ui configuration file %1"), rcfile) << endmsg;
304
305                 if (!tree.read (rcfile)) {
306                         error << string_compose(_("cannot read ui configuration file \"%1\""), rcfile) << endmsg;
307                         return -1;
308                 }
309
310                 if (set_state (*tree.root(), Stateful::loading_state_version)) {
311                         error << string_compose(_("user ui configuration file \"%1\" not loaded successfully."), rcfile) << endmsg;
312                         return -1;
313                 }
314
315                 _dirty = false;
316         }
317
318         if (!found) {
319                 error << _("could not find any ui configuration file, canvas will look broken.") << endmsg;
320         }
321
322         ARDOUR_UI_UTILS::ColorsChanged ();
323
324         return 0;
325 }
326
327 int
328 UIConfiguration::save_state()
329 {
330         XMLTree tree;
331
332         if (!dirty()) {
333                 return 0;
334         }
335         
336         std::string rcfile(user_config_directory());
337         rcfile = Glib::build_filename (rcfile, ui_config_file_name);
338
339         if (rcfile.length()) {
340                 tree.set_root (&get_state());
341                 if (!tree.write (rcfile.c_str())){
342                         error << string_compose (_("Config file %1 not saved"), rcfile) << endmsg;
343                         return -1;
344                 }
345         }
346
347         _dirty = false;
348
349         return 0;
350 }
351
352 XMLNode&
353 UIConfiguration::get_state ()
354 {
355         XMLNode* root;
356         LocaleGuard lg (X_("POSIX"));
357
358         root = new XMLNode("Ardour");
359
360         root->add_child_nocopy (get_variables ("UI"));
361         root->add_child_nocopy (get_variables ("Canvas"));
362
363         if (derived_modified) {
364
365         }
366
367         if (aliases_modified) {
368                 XMLNode* parent = new XMLNode (X_("ColorAliases"));
369                 for (ColorAliases::const_iterator i = color_aliases.begin(); i != color_aliases.end(); ++i) {
370                         XMLNode* node = new XMLNode (X_("ColorAlias"));
371                         node->add_property (X_("name"), i->first);
372                         node->add_property (X_("alias"), i->second);
373                         parent->add_child_nocopy (*node);
374                 }
375                 root->add_child_nocopy (*parent);
376         }
377         
378         if (_extra_xml) {
379                 root->add_child_copy (*_extra_xml);
380         }
381
382         return *root;
383 }
384
385 XMLNode&
386 UIConfiguration::get_variables (std::string which_node)
387 {
388         XMLNode* node;
389         LocaleGuard lg (X_("POSIX"));
390
391         node = new XMLNode (which_node);
392
393 #undef  UI_CONFIG_VARIABLE
394 #undef  CANVAS_FONT_VARIABLE
395 #define UI_CONFIG_VARIABLE(Type,var,Name,value) if (node->name() == "UI") { var.add_to_node (*node); }
396 #define CANVAS_FONT_VARIABLE(var,Name) if (node->name() == "Canvas") { var.add_to_node (*node); }
397 #include "ui_config_vars.h"
398 #include "canvas_vars.h"
399 #undef  UI_CONFIG_VARIABLE
400 #undef  CANVAS_FONT_VARIABLE
401
402         return *node;
403 }
404
405 int
406 UIConfiguration::set_state (const XMLNode& root, int /*version*/)
407 {
408         if (root.name() != "Ardour") {
409                 return -1;
410         }
411
412         Stateful::save_extra_xml (root);
413
414         XMLNodeList nlist = root.children();
415         XMLNodeConstIterator niter;
416         XMLNode *node;
417
418         for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
419
420                 node = *niter;
421
422                 if (node->name() == "Canvas" ||  node->name() == "UI") {
423                         set_variables (*node);
424
425                 }
426         }
427
428         XMLNode* relative = find_named_node (root, X_("RelativeColors"));
429         
430         if (relative) {
431                 // load_relative_colors (*relative);
432         }
433
434         
435         XMLNode* aliases = find_named_node (root, X_("ColorAliases"));
436
437         if (aliases) {
438                 load_color_aliases (*aliases);
439         }
440
441         return 0;
442 }
443
444 void
445 UIConfiguration::load_color_aliases (XMLNode const & node)
446 {
447         XMLNodeList const nlist = node.children();
448         XMLNodeConstIterator niter;
449         XMLProperty const *name;
450         XMLProperty const *alias;
451         
452         color_aliases.clear ();
453
454         for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
455                 if ((*niter)->name() != X_("ColorAlias")) {
456                         continue;
457                 }
458                 name = (*niter)->property (X_("name"));
459                 alias = (*niter)->property (X_("alias"));
460
461                 if (name && alias) {
462                         color_aliases.insert (make_pair (name->value(), alias->value()));
463                 }
464         }
465 }
466
467 void
468 UIConfiguration::load_relative_colors (XMLNode const & node)
469 {
470         XMLNodeList const nlist = node.children();
471         XMLNodeConstIterator niter;
472         XMLProperty const *name;
473         XMLProperty const *alias;
474         
475         color_aliases.clear ();
476
477         for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
478                 if ((*niter)->name() != X_("RelativeColor")) {
479                         continue;
480                 }
481                 name = (*niter)->property (X_("name"));
482                 alias = (*niter)->property (X_("alias"));
483
484                 if (name && alias) {
485                         color_aliases.insert (make_pair (name->value(), alias->value()));
486                 }
487         }
488
489 }
490 #endif
491 void
492 UIConfiguration::set_variables (const XMLNode& node)
493 {
494 #undef  UI_CONFIG_VARIABLE
495 #define UI_CONFIG_VARIABLE(Type,var,name,val) \
496          if (var.set_from_node (node)) { \
497                  ParameterChanged (name); \
498                  }
499 #define CANVAS_FONT_VARIABLE(var,name)  \
500          if (var.set_from_node (node)) { \
501                  ParameterChanged (name); \
502                  }
503 #include "ui_config_vars.h"
504 #include "canvas_vars.h"
505 #undef  UI_CONFIG_VARIABLE
506 #undef  CANVAS_FONT_VARIABLE
507
508         /* Reset base colors */
509
510 #undef  CANVAS_BASE_COLOR
511 #define CANVAS_BASE_COLOR(var,name,val) \
512         var.set_from_node (node);
513 #include "base_colors.h"
514 #undef CANVAS_BASE_COLOR        
515
516 }
517
518 void
519 UIConfiguration::set_dirty ()
520 {
521         _dirty = true;
522 }
523
524 bool
525 UIConfiguration::dirty () const
526 {
527         return _dirty || aliases_modified || derived_modified;
528 }
529
530 ArdourCanvas::Color
531 UIConfiguration::base_color_by_name (const std::string& name) const
532 {
533         map<std::string,ColorVariable<Color>* >::const_iterator i = configurable_colors.find (name);
534
535         if (i != configurable_colors.end()) {
536                 return i->second->get();
537         }
538
539 #if 0 // yet unsed experimental style postfix
540         /* Idea: use identical colors but different font/sizes
541          * for variants of the same 'widget'.
542          *
543          * example:
544          *  set_name("mute button");  // in route_ui.cc
545          *  set_name("mute button small"); // in mixer_strip.cc
546          *
547          * ardour3_widget_list.rc:
548          *  widget "*mute button" style:highest "small_button"
549          *  widget "*mute button small" style:highest "very_small_text"
550          *
551          * both use color-schema of defined in
552          *   BUTTON_VARS(MuteButton, "mute button")
553          *
554          * (in this particular example the widgets should be packed
555          * vertically shinking the mixer strip ones are currently not)
556          */
557         const size_t name_len = name.size();
558         const size_t name_sep = name.find(':');
559         for (i = configurable_colors.begin(); i != configurable_colors.end(), name_sep != string::npos; ++i) {
560                 const size_t cmp_len = i->first.size();
561                 const size_t cmp_sep = i->first.find(':');
562                 if (cmp_len >= name_len || cmp_sep == string::npos) continue;
563                 if (name.substr(name_sep) != i->first.substr(cmp_sep)) continue;
564                 if (name.substr(0, cmp_sep) != i->first.substr(0, cmp_sep)) continue;
565                 return i->second->get();
566         }
567 #endif
568
569         cerr << string_compose (_("Base Color %1 not found"), name) << endl;
570         return RGBA_TO_UINT (g_random_int()%256,g_random_int()%256,g_random_int()%256,0xff);
571 }
572
573 ArdourCanvas::Color
574 UIConfiguration::color (const std::string& name) const
575 {
576         map<string,string>::const_iterator e = color_aliases.find (name);
577
578         if (e != color_aliases.end ()) {
579                 map<string,RelativeHSV>::const_iterator rc = relative_colors.find (e->second);
580                 if (rc != relative_colors.end()) {
581                         return rc->second.get();
582                 }
583         } else {
584                 /* not an alias, try directly */
585                 map<string,RelativeHSV>::const_iterator rc = relative_colors.find (name);
586                 if (rc != relative_colors.end()) {
587                         return rc->second.get();
588                 }
589         }
590         
591         cerr << string_compose (_("Color %1 not found"), name) << endl;
592         
593         return rgba_to_color ((g_random_int()%256)/255.0,
594                               (g_random_int()%256)/255.0,
595                               (g_random_int()%256)/255.0,
596                               0xff);
597 }
598
599 ArdourCanvas::HSV
600 UIConfiguration::RelativeHSV::get() const
601 {
602         HSV base (UIConfiguration::instance()->base_color_by_name (base_color));
603         
604         /* this operation is a little wierd. because of the way we originally
605          * computed the alpha specification for the modifiers used here
606          * we need to reset base's alpha to zero before adding the modifier.
607          */
608
609         HSV self (base + modifier);
610         
611         if (quantized_hue >= 0.0) {
612                 self.h = quantized_hue;
613         }
614         
615         return self;
616 }
617
618 Color
619 UIConfiguration::quantized (Color c) const
620 {
621         HSV hsv (c);
622         hsv.h = hue_width * (round (hsv.h/hue_width));
623         return hsv.color ();
624 }
625
626 void
627 UIConfiguration::reset_relative (const string& name, const RelativeHSV& rhsv)
628 {
629         RelativeColors::iterator i = relative_colors.find (name);
630
631         if (i == relative_colors.end()) {
632                 return;
633         }
634
635         i->second = rhsv;
636         derived_modified = true;
637
638         ARDOUR_UI_UTILS::ColorsChanged (); /* EMIT SIGNAL */
639
640         save_state ();
641 }
642
643 void
644 UIConfiguration::set_alias (string const & name, string const & alias)
645 {
646         ColorAliases::iterator i = color_aliases.find (name);
647         if (i == color_aliases.end()) {
648                 return;
649         }
650
651         i->second = alias;
652         aliases_modified = true;
653
654         ARDOUR_UI_UTILS::ColorsChanged (); /* EMIT SIGNAL */
655
656         save_state ();
657 }
658
659 void
660 UIConfiguration::load_rc_file (const string& filename, bool themechange)
661 {
662         std::string rc_file_path;
663
664         if (!find_file (ardour_config_search_path(), filename, rc_file_path)) {
665                 warning << string_compose (_("Unable to find UI style file %1 in search path %2. %3 will look strange"),
666                                            filename, ardour_config_search_path().to_string(), PROGRAM_NAME)
667                                 << endmsg;
668                 return;
669         }
670
671         info << "Loading ui configuration file " << rc_file_path << endmsg;
672
673         Gtkmm2ext::UI::instance()->load_rcfile (rc_file_path, themechange);
674 }