hide a lot of color debugging noise
[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         _dirty (false),
68         base_modified (false),
69         aliases_modified (false),
70         derived_modified (false),
71         block_save (0)
72 {
73         _instance = this;
74
75         /* pack all base colors into the configurable color map so that
76            derived colors can use them.
77         */
78           
79 #undef CANVAS_BASE_COLOR
80 #define CANVAS_BASE_COLOR(var,name,color) base_colors.insert (make_pair (name,color));
81 #include "base_colors.h"
82 #undef CANVAS_BASE_COLOR
83
84 #undef CANVAS_COLOR
85 #define CANVAS_COLOR(var,name,base,modifier) relative_colors.insert (make_pair (name, RelativeHSV (base,modifier)));
86 #include "colors.h"
87 #undef CANVAS_COLOR
88         
89 #undef COLOR_ALIAS
90 #define COLOR_ALIAS(var,name,alias) color_aliases.insert (make_pair (name,alias));
91 #include "color_aliases.h"
92 #undef CANVAS_COLOR
93
94         load_state();
95
96         ARDOUR_UI_UTILS::ColorsChanged.connect (boost::bind (&UIConfiguration::colors_changed, this));
97
98         ParameterChanged.connect (sigc::mem_fun (*this, &UIConfiguration::parameter_changed));
99
100         /* force GTK theme setting, so that loading an RC file will work */
101         
102         load_color_theme ();
103 }
104
105 UIConfiguration::~UIConfiguration ()
106 {
107 }
108
109 void
110 UIConfiguration::colors_changed ()
111 {
112         reset_gtk_theme ();
113
114         /* In theory, one of these ought to work:
115
116            gtk_rc_reparse_all_for_settings (gtk_settings_get_default(), true);
117            gtk_rc_reset_styles (gtk_settings_get_default());
118
119            but in practice, neither of them do. So just reload the current
120            GTK RC file, which causes a reset of all styles and a redraw
121         */
122
123         parameter_changed ("ui-rc-file");
124 }
125
126 void
127 UIConfiguration::parameter_changed (string param)
128 {
129         _dirty = true;
130         
131         if (param == "ui-rc-file") {
132                 load_rc_file (true);
133         } else if (param == "color-file") {
134                 load_color_theme ();
135         } else if (param == "base-color") { /* one of many */
136                 base_modified = true;
137                 ARDOUR_UI_UTILS::ColorsChanged (); /* EMIT SIGNAL */
138         }
139
140         save_state ();
141 }
142
143 void
144 UIConfiguration::reset_gtk_theme ()
145 {
146         stringstream ss;
147
148         ss << "gtk_color_scheme = \"" << hex;
149         
150         for (ColorAliases::iterator g = color_aliases.begin(); g != color_aliases.end(); ++g) {
151                 
152                 if (g->first.find ("gtk_") == 0) {
153                         ColorAliases::const_iterator a = color_aliases.find (g->first);
154                         const string gtk_name = g->first.substr (4);
155                         ss << gtk_name << ":#" << std::setw (6) << setfill ('0') << (color (g->second) >> 8) << ';';
156                 }
157         }
158
159         ss << '"' << dec << endl;
160
161         /* reset GTK color scheme */
162
163         Gtk::Settings::get_default()->property_gtk_color_scheme() = ss.str();
164 }
165         
166 UIConfiguration::RelativeHSV
167 UIConfiguration::color_as_relative_hsv (Color c)
168 {
169         HSV variable (c);
170         HSV closest;
171         double shortest_distance = DBL_MAX;
172         string closest_name;
173
174         BaseColors::iterator f;
175         std::map<std::string,HSV> palette;
176
177         for (f = base_colors.begin(); f != base_colors.end(); ++f) {
178                 /* Do not include any specialized base colors in the palette
179                    we use to do comparisons (e.g. meter colors)
180                 */
181
182                 if (f->first.find ("color") == 0) {
183                         palette.insert (make_pair (f->first, HSV (f->second)));
184                 }
185         }
186
187         for (map<string,HSV>::iterator f = palette.begin(); f != palette.end(); ++f) {
188                 
189                 double d;
190                 HSV fixed (f->second);
191                 
192                 if (fixed.is_gray() || variable.is_gray()) {
193                         /* at least one is achromatic; HSV::distance() will do
194                          * the right thing
195                          */
196                         d = fixed.distance (variable);
197                 } else {
198                         /* chromatic: compare ONLY hue because our task is
199                            to pick the HUE closest and then compute
200                            a modifier. We want to keep the number of 
201                            hues low, and by computing perceptual distance 
202                            we end up finding colors that are to each
203                            other without necessarily be close in hue.
204                         */
205                         d = fabs (variable.h - fixed.h);
206                 }
207
208                 if (d < shortest_distance) {
209                         closest = fixed;
210                         closest_name = f->first;
211                         shortest_distance = d;
212                 }
213         }
214         
215         /* we now know the closest color of the fixed colors to 
216            this variable color. Compute the HSV diff and
217            use it to redefine the variable color in terms of the
218            fixed one.
219         */
220         
221         HSV delta = variable.delta (closest);
222
223         /* quantize hue delta so we don't end up with many subtle hues caused
224          * by original color choices
225          */
226
227         delta.h = hue_width * (round (delta.h/hue_width));
228
229         return RelativeHSV (closest_name, delta);
230 }
231
232 string
233 UIConfiguration::color_as_alias (Color c)
234 {
235         string closest;
236         double shortest_distance = DBL_MAX;
237         HSV target (c);
238         
239         for (RelativeColors::const_iterator a = relative_colors.begin(); a != relative_colors.end(); ++a) {
240                 HSV hsv (a->second.get());
241                 double d = hsv.distance (target);
242                 if (d < shortest_distance) {
243                         shortest_distance = d;
244                         closest = a->first;
245                 }
246         }
247         return closest;
248 }               
249 void
250 UIConfiguration::map_parameters (boost::function<void (std::string)>& functor)
251 {
252 #undef  UI_CONFIG_VARIABLE
253 #define UI_CONFIG_VARIABLE(Type,var,Name,value) functor (Name);
254 #include "ui_config_vars.h"
255 #undef  UI_CONFIG_VARIABLE
256 }
257
258 int
259 UIConfiguration::load_defaults ()
260 {
261         std::string rcfile;
262         int ret = -1;
263         
264         if (find_file (ardour_config_search_path(), default_ui_config_file_name, rcfile) ) {
265                 XMLTree tree;
266
267                 info << string_compose (_("Loading default ui configuration file %1"), rcfile) << endmsg;
268
269                 if (!tree.read (rcfile.c_str())) {
270                         error << string_compose(_("cannot read default ui configuration file \"%1\""), rcfile) << endmsg;
271                 } else {
272                         if (set_state (*tree.root(), Stateful::loading_state_version)) {
273                                 error << string_compose(_("default ui configuration file \"%1\" not loaded successfully."), rcfile) << endmsg;
274                         } else {
275                                 _dirty = false;
276                                 ret = 0;
277                         }
278                 }
279
280         } else {
281                 warning << string_compose (_("Could not find default UI configuration file %1"), default_ui_config_file_name) << endmsg;
282         }
283
284         if (ret == 0) {
285                 /* reload color theme */
286                 load_color_theme (false);
287                 ARDOUR_UI_UTILS::ColorsChanged (); /* EMIT SIGNAL */
288         }
289
290         return 0;
291 }
292
293 int
294 UIConfiguration::load_color_theme (bool allow_own)
295 {
296         std::string cfile;
297         string basename;
298         bool found = false;
299
300         if (allow_own) {
301                 basename = "my-";
302                 basename += color_file.get();
303                 basename += ".colors";
304
305                 if (find_file (ardour_config_search_path(), basename, cfile)) {
306                         found = true;
307                 }
308         }
309
310         if (!found) {
311                 basename = color_file.get();
312                 basename += ".colors";
313         
314                 if (find_file (ardour_config_search_path(), basename, cfile)) {
315                         found = true;
316                 }
317         }
318
319         if (found) {
320
321                 XMLTree tree;
322                 
323                 info << string_compose (_("Loading color file %1"), cfile) << endmsg;
324
325                 if (!tree.read (cfile.c_str())) {
326                         error << string_compose(_("cannot read color file \"%1\""), cfile) << endmsg;
327                         return -1;
328                 }
329
330                 if (set_state (*tree.root(), Stateful::loading_state_version)) {
331                         error << string_compose(_("color file \"%1\" not loaded successfully."), cfile) << endmsg;
332                         return -1;
333                 }
334
335                 ARDOUR_UI_UTILS::ColorsChanged ();
336         } else {
337                 warning << string_compose (_("Color file %1 not found"), basename) << endmsg;
338         }
339
340         return 0;
341 }
342
343 int
344 UIConfiguration::store_color_theme ()
345 {
346         XMLNode* root;
347         LocaleGuard lg (X_("POSIX"));
348
349         root = new XMLNode("Ardour");
350
351         XMLNode* parent = new XMLNode (X_("RelativeColors"));
352         for (RelativeColors::const_iterator i = relative_colors.begin(); i != relative_colors.end(); ++i) {
353                 XMLNode* node = new XMLNode (X_("RelativeColor"));
354                 node->add_property (X_("name"), i->first);
355                 node->add_property (X_("base"), i->second.base_color);
356                 node->add_property (X_("modifier"), i->second.modifier.to_string());
357                 parent->add_child_nocopy (*node);
358         }
359         root->add_child_nocopy (*parent);
360         
361         
362         parent = new XMLNode (X_("ColorAliases"));
363         for (ColorAliases::const_iterator i = color_aliases.begin(); i != color_aliases.end(); ++i) {
364                 XMLNode* node = new XMLNode (X_("ColorAlias"));
365                 node->add_property (X_("name"), i->first);
366                 node->add_property (X_("alias"), i->second);
367                 parent->add_child_nocopy (*node);
368         }
369         root->add_child_nocopy (*parent);
370         
371         XMLTree tree;
372         std::string colorfile = Glib::build_filename (user_config_directory(), (string ("my-") + color_file.get() + ".colors"));
373         
374         tree.set_root (root);
375
376         if (!tree.write (colorfile.c_str())){
377                 error << string_compose (_("Color file %1 not saved"), colorfile) << endmsg;
378                 return -1;
379         }
380
381         return 0;
382 }
383
384 int
385 UIConfiguration::load_state ()
386 {
387         bool found = false;
388
389         std::string rcfile;
390
391         if (find_file (ardour_config_search_path(), default_ui_config_file_name, rcfile)) {
392                 XMLTree tree;
393                 found = true;
394
395                 info << string_compose (_("Loading default ui configuration file %1"), rcfile) << endmsg;
396
397                 if (!tree.read (rcfile.c_str())) {
398                         error << string_compose(_("cannot read default ui configuration file \"%1\""), rcfile) << endmsg;
399                         return -1;
400                 }
401
402                 if (set_state (*tree.root(), Stateful::loading_state_version)) {
403                         error << string_compose(_("default ui configuration file \"%1\" not loaded successfully."), rcfile) << endmsg;
404                         return -1;
405                 }
406         }
407
408         if (find_file (ardour_config_search_path(), ui_config_file_name, rcfile)) {
409                 XMLTree tree;
410                 found = true;
411
412                 info << string_compose (_("Loading user ui configuration file %1"), rcfile) << endmsg;
413
414                 if (!tree.read (rcfile)) {
415                         error << string_compose(_("cannot read ui configuration file \"%1\""), rcfile) << endmsg;
416                         return -1;
417                 }
418
419                 if (set_state (*tree.root(), Stateful::loading_state_version)) {
420                         error << string_compose(_("user ui configuration file \"%1\" not loaded successfully."), rcfile) << endmsg;
421                         return -1;
422                 }
423
424                 _dirty = false;
425         }
426
427         if (!found) {
428                 error << _("could not find any ui configuration file, canvas will look broken.") << endmsg;
429         }
430
431         return 0;
432 }
433
434 int
435 UIConfiguration::save_state()
436 {
437
438         if (_dirty) {
439                 std::string rcfile = Glib::build_filename (user_config_directory(), ui_config_file_name);
440                 
441                 XMLTree tree;
442
443                 tree.set_root (&get_state());
444
445                 if (!tree.write (rcfile.c_str())){
446                         error << string_compose (_("Config file %1 not saved"), rcfile) << endmsg;
447                         return -1;
448                 }
449
450                 _dirty = false;
451         }
452
453         if (base_modified || aliases_modified || derived_modified) {
454
455                 if (store_color_theme ()) {
456                         error << string_compose (_("Color file %1 not saved"), color_file.get()) << endmsg;
457                         return -1;
458                 }
459
460                 base_modified = false;
461                 aliases_modified = false;
462                 derived_modified = false;
463         }
464         
465
466         return 0;
467 }
468
469 XMLNode&
470 UIConfiguration::get_state ()
471 {
472         XMLNode* root;
473         LocaleGuard lg (X_("POSIX"));
474
475         root = new XMLNode("Ardour");
476
477         root->add_child_nocopy (get_variables ("UI"));
478         root->add_child_nocopy (get_variables ("Canvas"));
479
480         if (_extra_xml) {
481                 root->add_child_copy (*_extra_xml);
482         }
483
484         return *root;
485 }
486
487 XMLNode&
488 UIConfiguration::get_variables (std::string which_node)
489 {
490         XMLNode* node;
491         LocaleGuard lg (X_("POSIX"));
492
493         node = new XMLNode (which_node);
494
495 #undef  UI_CONFIG_VARIABLE
496 #undef  CANVAS_FONT_VARIABLE
497 #define UI_CONFIG_VARIABLE(Type,var,Name,value) if (node->name() == "UI") { var.add_to_node (*node); }
498 #define CANVAS_FONT_VARIABLE(var,Name) if (node->name() == "Canvas") { var.add_to_node (*node); }
499 #include "ui_config_vars.h"
500 #include "canvas_vars.h"
501 #undef  UI_CONFIG_VARIABLE
502 #undef  CANVAS_FONT_VARIABLE
503
504         return *node;
505 }
506
507 int
508 UIConfiguration::set_state (const XMLNode& root, int /*version*/)
509 {
510         /* this can load a generic UI configuration file or a colors file */
511
512         if (root.name() != "Ardour") {
513                 return -1;
514         }
515
516         Stateful::save_extra_xml (root);
517
518         XMLNodeList nlist = root.children();
519         XMLNodeConstIterator niter;
520         XMLNode *node;
521
522         for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
523
524                 node = *niter;
525
526                 if (node->name() == "Canvas" ||  node->name() == "UI") {
527                         set_variables (*node);
528
529                 }
530         }
531
532         XMLNode* base = find_named_node (root, X_("BaseColors"));
533
534         if (base) {
535                 load_base_colors (*base);
536         }
537
538         
539         XMLNode* relative = find_named_node (root, X_("RelativeColors"));
540         
541         if (relative) {
542                 load_relative_colors (*relative);
543         }
544
545         
546         XMLNode* aliases = find_named_node (root, X_("ColorAliases"));
547
548         if (aliases) {
549                 load_color_aliases (*aliases);
550         }
551
552         return 0;
553 }
554
555 void
556 UIConfiguration::load_base_colors (XMLNode const &)
557 {
558
559 }
560
561 void
562 UIConfiguration::load_color_aliases (XMLNode const & node)
563 {
564         XMLNodeList const nlist = node.children();
565         XMLNodeConstIterator niter;
566         XMLProperty const *name;
567         XMLProperty const *alias;
568         
569         color_aliases.clear ();
570
571         for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
572                 if ((*niter)->name() != X_("ColorAlias")) {
573                         continue;
574                 }
575                 name = (*niter)->property (X_("name"));
576                 alias = (*niter)->property (X_("alias"));
577
578                 if (name && alias) {
579                         color_aliases.insert (make_pair (name->value(), alias->value()));
580                 }
581         }
582 }
583
584 void
585 UIConfiguration::load_relative_colors (XMLNode const & node)
586 {
587         XMLNodeList const nlist = node.children();
588         XMLNodeConstIterator niter;
589         XMLProperty const *name;
590         XMLProperty const *base;
591         XMLProperty const *modifier;
592         
593         relative_colors.clear ();
594
595         for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
596                 if ((*niter)->name() != X_("RelativeColor")) {
597                         continue;
598                 }
599                 name = (*niter)->property (X_("name"));
600                 base = (*niter)->property (X_("base"));
601                 modifier = (*niter)->property (X_("modifier"));
602
603                 if (name && base && modifier) {
604                         RelativeHSV rhsv (base->value(), HSV (modifier->value()));
605                         relative_colors.insert (make_pair (name->value(), rhsv));
606                 }
607         }
608
609 }
610
611 void
612 UIConfiguration::set_variables (const XMLNode& node)
613 {
614 #undef  UI_CONFIG_VARIABLE
615 #define UI_CONFIG_VARIABLE(Type,var,name,val) if (var.set_from_node (node)) { ParameterChanged (name); }
616 #define CANVAS_FONT_VARIABLE(var,name)        if (var.set_from_node (node)) { ParameterChanged (name); }
617 #include "ui_config_vars.h"
618 #include "canvas_vars.h"
619 #undef  UI_CONFIG_VARIABLE
620 #undef  CANVAS_FONT_VARIABLE
621 }
622
623 ArdourCanvas::Color
624 UIConfiguration::base_color_by_name (const std::string& name) const
625 {
626         BaseColors::const_iterator i = base_colors.find (name);
627
628         if (i != base_colors.end()) {
629                 return i->second;
630         }
631
632         cerr << string_compose (_("Base Color %1 not found"), name) << endl;
633         return RGBA_TO_UINT (g_random_int()%256,g_random_int()%256,g_random_int()%256,0xff);
634 }
635
636 ArdourCanvas::Color
637 UIConfiguration::color (const std::string& name, bool* failed) const
638 {
639         map<string,string>::const_iterator e = color_aliases.find (name);
640
641         if (failed) {
642                 *failed = false;
643         }
644         
645         if (e != color_aliases.end ()) {
646                 map<string,RelativeHSV>::const_iterator rc = relative_colors.find (e->second);
647                 if (rc != relative_colors.end()) {
648                         return rc->second.get();
649                 }
650         } else {
651                 /* not an alias, try directly */
652                 map<string,RelativeHSV>::const_iterator rc = relative_colors.find (name);
653                 if (rc != relative_colors.end()) {
654                         return rc->second.get();
655                 }
656         }
657         
658         if (!failed) {
659                 /* only show this message if the caller wasn't interested in
660                    the fail status.
661                 */
662                 cerr << string_compose (_("Color %1 not found"), name) << endl;
663         }
664
665         if (failed) {
666                 *failed = true;
667         }
668         
669         return rgba_to_color ((g_random_int()%256)/255.0,
670                               (g_random_int()%256)/255.0,
671                               (g_random_int()%256)/255.0,
672                               0xff);
673 }
674
675 ArdourCanvas::HSV
676 UIConfiguration::RelativeHSV::get() const
677 {
678         HSV base (UIConfiguration::instance()->base_color_by_name (base_color));
679         
680         /* this operation is a little wierd. because of the way we originally
681          * computed the alpha specification for the modifiers used here
682          * we need to reset base's alpha to zero before adding the modifier.
683          */
684
685         HSV self (base + modifier);
686         
687         if (quantized_hue >= 0.0) {
688                 self.h = quantized_hue;
689         }
690         
691         return self;
692 }
693
694 Color
695 UIConfiguration::quantized (Color c) const
696 {
697         HSV hsv (c);
698         hsv.h = hue_width * (round (hsv.h/hue_width));
699         return hsv.color ();
700 }
701
702 void
703 UIConfiguration::set_base (string const& name, ArdourCanvas::Color color)
704 {
705         BaseColors::iterator i = base_colors.find (name);
706         if (i == base_colors.end()) {
707                 return;
708         }
709         i->second = color;
710         base_modified = true;
711
712         ARDOUR_UI_UTILS::ColorsChanged (); /* EMIT SIGNAL */
713 }
714
715 void
716 UIConfiguration::set_relative (const string& name, const RelativeHSV& rhsv)
717 {
718         RelativeColors::iterator i = relative_colors.find (name);
719
720         if (i == relative_colors.end()) {
721                 return;
722         }
723
724         i->second = rhsv;
725         derived_modified = true;
726
727         ARDOUR_UI_UTILS::ColorsChanged (); /* EMIT SIGNAL */
728 }
729
730 void
731 UIConfiguration::set_alias (string const & name, string const & alias)
732 {
733         ColorAliases::iterator i = color_aliases.find (name);
734         if (i == color_aliases.end()) {
735                 return;
736         }
737
738         i->second = alias;
739         aliases_modified = true;
740
741         ARDOUR_UI_UTILS::ColorsChanged (); /* EMIT SIGNAL */
742 }
743
744 void
745 UIConfiguration::load_rc_file (bool themechange, bool allow_own)
746 {
747         string basename = ui_rc_file.get();
748         std::string rc_file_path;
749
750         if (!find_file (ardour_config_search_path(), basename, rc_file_path)) {
751                 warning << string_compose (_("Unable to find UI style file %1 in search path %2. %3 will look strange"),
752                                            basename, ardour_config_search_path().to_string(), PROGRAM_NAME)
753                                 << endmsg;
754                 return;
755         }
756
757         info << "Loading ui configuration file " << rc_file_path << endmsg;
758
759         Gtkmm2ext::UI::instance()->load_rcfile (rc_file_path, themechange);
760 }
761
762 std::ostream& operator<< (std::ostream& o, const UIConfiguration::RelativeHSV& rhsv)
763 {
764         return o << rhsv.base_color << " + HSV(" << rhsv.modifier << ")";
765 }
766