initial pass to replace all UIConfiguration::get_XXXXXX() calls with UIConfiguration...
[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         cerr << "Color alias table contains " << color_aliases.size() << endl;
584 }
585
586 void
587 UIConfiguration::load_relative_colors (XMLNode const & node)
588 {
589         XMLNodeList const nlist = node.children();
590         XMLNodeConstIterator niter;
591         XMLProperty const *name;
592         XMLProperty const *base;
593         XMLProperty const *modifier;
594         
595         relative_colors.clear ();
596
597         for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
598                 if ((*niter)->name() != X_("RelativeColor")) {
599                         continue;
600                 }
601                 name = (*niter)->property (X_("name"));
602                 base = (*niter)->property (X_("base"));
603                 modifier = (*niter)->property (X_("modifier"));
604
605                 if (name && base && modifier) {
606                         RelativeHSV rhsv (base->value(), HSV (modifier->value()));
607                         relative_colors.insert (make_pair (name->value(), rhsv));
608                 }
609         }
610
611 }
612
613 void
614 UIConfiguration::set_variables (const XMLNode& node)
615 {
616 #undef  UI_CONFIG_VARIABLE
617 #define UI_CONFIG_VARIABLE(Type,var,name,val) if (var.set_from_node (node)) { ParameterChanged (name); }
618 #define CANVAS_FONT_VARIABLE(var,name)        if (var.set_from_node (node)) { ParameterChanged (name); }
619 #include "ui_config_vars.h"
620 #include "canvas_vars.h"
621 #undef  UI_CONFIG_VARIABLE
622 #undef  CANVAS_FONT_VARIABLE
623 }
624
625 ArdourCanvas::Color
626 UIConfiguration::base_color_by_name (const std::string& name) const
627 {
628         BaseColors::const_iterator i = base_colors.find (name);
629
630         if (i != base_colors.end()) {
631                 return i->second;
632         }
633
634         cerr << string_compose (_("Base Color %1 not found"), name) << endl;
635         return RGBA_TO_UINT (g_random_int()%256,g_random_int()%256,g_random_int()%256,0xff);
636 }
637
638 ArdourCanvas::Color
639 UIConfiguration::color (const std::string& name, bool* failed) const
640 {
641         map<string,string>::const_iterator e = color_aliases.find (name);
642
643         if (failed) {
644                 *failed = false;
645         }
646         
647         if (e != color_aliases.end ()) {
648                 map<string,RelativeHSV>::const_iterator rc = relative_colors.find (e->second);
649                 if (rc != relative_colors.end()) {
650                         return rc->second.get();
651                 }
652         } else {
653                 /* not an alias, try directly */
654                 map<string,RelativeHSV>::const_iterator rc = relative_colors.find (name);
655                 if (rc != relative_colors.end()) {
656                         return rc->second.get();
657                 }
658         }
659         
660         if (!failed) {
661                 /* only show this message if the caller wasn't interested in
662                    the fail status.
663                 */
664                 cerr << string_compose (_("Color %1 not found"), name) << endl;
665         }
666
667         if (failed) {
668                 *failed = true;
669         }
670         
671         return rgba_to_color ((g_random_int()%256)/255.0,
672                               (g_random_int()%256)/255.0,
673                               (g_random_int()%256)/255.0,
674                               0xff);
675 }
676
677 ArdourCanvas::HSV
678 UIConfiguration::RelativeHSV::get() const
679 {
680         HSV base (UIConfiguration::instance()->base_color_by_name (base_color));
681         
682         /* this operation is a little wierd. because of the way we originally
683          * computed the alpha specification for the modifiers used here
684          * we need to reset base's alpha to zero before adding the modifier.
685          */
686
687         HSV self (base + modifier);
688         
689         if (quantized_hue >= 0.0) {
690                 self.h = quantized_hue;
691         }
692         
693         return self;
694 }
695
696 Color
697 UIConfiguration::quantized (Color c) const
698 {
699         HSV hsv (c);
700         hsv.h = hue_width * (round (hsv.h/hue_width));
701         return hsv.color ();
702 }
703
704 void
705 UIConfiguration::set_base (string const& name, ArdourCanvas::Color color)
706 {
707         BaseColors::iterator i = base_colors.find (name);
708         if (i == base_colors.end()) {
709                 return;
710         }
711         i->second = color;
712         base_modified = true;
713
714         ARDOUR_UI_UTILS::ColorsChanged (); /* EMIT SIGNAL */
715 }
716
717 void
718 UIConfiguration::set_relative (const string& name, const RelativeHSV& rhsv)
719 {
720         RelativeColors::iterator i = relative_colors.find (name);
721
722         if (i == relative_colors.end()) {
723                 return;
724         }
725
726         i->second = rhsv;
727         derived_modified = true;
728
729         ARDOUR_UI_UTILS::ColorsChanged (); /* EMIT SIGNAL */
730 }
731
732 void
733 UIConfiguration::set_alias (string const & name, string const & alias)
734 {
735         ColorAliases::iterator i = color_aliases.find (name);
736         if (i == color_aliases.end()) {
737                 return;
738         }
739
740         i->second = alias;
741         aliases_modified = true;
742
743         ARDOUR_UI_UTILS::ColorsChanged (); /* EMIT SIGNAL */
744 }
745
746 void
747 UIConfiguration::load_rc_file (bool themechange, bool allow_own)
748 {
749         string basename = ui_rc_file.get();
750         std::string rc_file_path;
751
752         if (!find_file (ardour_config_search_path(), basename, rc_file_path)) {
753                 warning << string_compose (_("Unable to find UI style file %1 in search path %2. %3 will look strange"),
754                                            basename, ardour_config_search_path().to_string(), PROGRAM_NAME)
755                                 << endmsg;
756                 return;
757         }
758
759         info << "Loading ui configuration file " << rc_file_path << endmsg;
760
761         Gtkmm2ext::UI::instance()->load_rcfile (rc_file_path, themechange);
762 }
763
764 std::ostream& operator<< (std::ostream& o, const UIConfiguration::RelativeHSV& rhsv)
765 {
766         return o << rhsv.base_color << " + HSV(" << rhsv.modifier << ")";
767 }
768