remove debug output
[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::color_theme_changed, this));
108
109         ParameterChanged.connect (sigc::mem_fun (*this, &UIConfiguration::parameter_changed));
110
111         /* force loading of the GTK rc file */
112
113         parameter_changed ("ui-rc-file");
114 }
115
116 UIConfiguration::~UIConfiguration ()
117 {
118 }
119
120 void
121 UIConfiguration::color_theme_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         save_state ();
139 }
140
141 void
142 UIConfiguration::parameter_changed (string param)
143 {
144         _dirty = true;
145         
146         if (param == "ui-rc-file") {
147                 bool env_defined = false;
148                 string rcfile = Glib::getenv("ARDOUR3_UI_RC", env_defined);
149                 
150                 if (!env_defined) {
151                         rcfile = get_ui_rc_file();
152                 }
153
154                 load_rc_file (rcfile, true);
155         }
156 }
157
158 void
159 UIConfiguration::reset_gtk_theme ()
160 {
161         stringstream ss;
162
163         ss << "gtk_color_scheme = \"" << hex;
164         
165         for (ColorAliases::iterator g = color_aliases.begin(); g != color_aliases.end(); ++g) {
166                 
167                 if (g->first.find ("gtk_") == 0) {
168                         ColorAliases::const_iterator a = color_aliases.find (g->first);
169                         const string gtk_name = g->first.substr (4);
170                         ss << gtk_name << ":#" << std::setw (6) << setfill ('0') << (color (g->second) >> 8) << ';';
171                 }
172         }
173
174         ss << '"' << dec << endl;
175
176         /* reset GTK color scheme */
177
178         Gtk::Settings::get_default()->property_gtk_color_scheme() = ss.str();
179 }
180         
181 UIConfiguration::RelativeHSV
182 UIConfiguration::color_as_relative_hsv (Color c)
183 {
184         HSV variable (c);
185         HSV closest;
186         double shortest_distance = DBL_MAX;
187         string closest_name;
188
189         map<string,ColorVariable<Color>*>::iterator f;
190         std::map<std::string,HSV> palette;
191
192         for (f = configurable_colors.begin(); f != configurable_colors.end(); ++f) {
193                 palette.insert (make_pair (f->first, HSV (f->second->get())));
194         }
195
196         for (map<string,HSV>::iterator f = palette.begin(); f != palette.end(); ++f) {
197                 
198                 double d;
199                 HSV fixed (f->second);
200                 
201                 if (fixed.is_gray() || variable.is_gray()) {
202                         /* at least one is achromatic; HSV::distance() will do
203                          * the right thing
204                          */
205                         d = fixed.distance (variable);
206                 } else {
207                         /* chromatic: compare ONLY hue because our task is
208                            to pick the HUE closest and then compute
209                            a modifier. We want to keep the number of 
210                            hues low, and by computing perceptual distance 
211                            we end up finding colors that are to each
212                            other without necessarily be close in hue.
213                         */
214                         d = fabs (variable.h - fixed.h);
215                 }
216
217                 if (d < shortest_distance) {
218                         closest = fixed;
219                         closest_name = f->first;
220                         shortest_distance = d;
221                 }
222         }
223         
224         /* we now know the closest color of the fixed colors to 
225            this variable color. Compute the HSV diff and
226            use it to redefine the variable color in terms of the
227            fixed one.
228         */
229         
230         HSV delta = variable.delta (closest);
231
232         /* quantize hue delta so we don't end up with many subtle hues caused
233          * by original color choices
234          */
235
236         delta.h = hue_width * (round (delta.h/hue_width));
237
238         return RelativeHSV (closest_name, delta);
239 }
240
241 void
242 UIConfiguration::map_parameters (boost::function<void (std::string)>& functor)
243 {
244 #undef  UI_CONFIG_VARIABLE
245 #define UI_CONFIG_VARIABLE(Type,var,Name,value) functor (Name);
246 #include "ui_config_vars.h"
247 #undef  UI_CONFIG_VARIABLE
248 }
249
250 int
251 UIConfiguration::load_defaults ()
252 {
253         int found = 0;
254         std::string rcfile;
255
256         if (find_file (ardour_config_search_path(), default_ui_config_file_name, rcfile) ) {
257                 XMLTree tree;
258                 found = 1;
259
260                 info << string_compose (_("Loading default ui configuration file %1"), rcfile) << endmsg;
261
262                 if (!tree.read (rcfile.c_str())) {
263                         error << string_compose(_("cannot read default ui configuration file \"%1\""), rcfile) << endmsg;
264                         return -1;
265                 }
266
267                 if (set_state (*tree.root(), Stateful::loading_state_version)) {
268                         error << string_compose(_("default ui configuration file \"%1\" not loaded successfully."), rcfile) << endmsg;
269                         return -1;
270                 }
271                 
272                 _dirty = false;
273         }
274
275         ARDOUR_UI_UTILS::ColorsChanged ();
276
277         return found;
278 }
279
280 int
281 UIConfiguration::load_state ()
282 {
283         bool found = false;
284
285         std::string rcfile;
286
287         if (find_file (ardour_config_search_path(), default_ui_config_file_name, rcfile)) {
288                 XMLTree tree;
289                 found = true;
290
291                 info << string_compose (_("Loading default ui configuration file %1"), rcfile) << endmsg;
292
293                 if (!tree.read (rcfile.c_str())) {
294                         error << string_compose(_("cannot read default ui configuration file \"%1\""), rcfile) << endmsg;
295                         return -1;
296                 }
297
298                 if (set_state (*tree.root(), Stateful::loading_state_version)) {
299                         error << string_compose(_("default ui configuration file \"%1\" not loaded successfully."), rcfile) << endmsg;
300                         return -1;
301                 }
302
303                 /* make a copy */
304         }
305
306         if (find_file (ardour_config_search_path(), ui_config_file_name, rcfile)) {
307                 XMLTree tree;
308                 found = true;
309
310                 info << string_compose (_("Loading user ui configuration file %1"), rcfile) << endmsg;
311
312                 if (!tree.read (rcfile)) {
313                         error << string_compose(_("cannot read ui configuration file \"%1\""), rcfile) << endmsg;
314                         return -1;
315                 }
316
317                 if (set_state (*tree.root(), Stateful::loading_state_version)) {
318                         error << string_compose(_("user ui configuration file \"%1\" not loaded successfully."), rcfile) << endmsg;
319                         return -1;
320                 }
321
322                 _dirty = false;
323         }
324
325         if (!found) {
326                 error << _("could not find any ui configuration file, canvas will look broken.") << endmsg;
327         }
328
329         ARDOUR_UI_UTILS::ColorsChanged ();
330
331         return 0;
332 }
333
334 int
335 UIConfiguration::save_state()
336 {
337         XMLTree tree;
338
339         if (!dirty()) {
340                 return 0;
341         }
342         
343         std::string rcfile(user_config_directory());
344         rcfile = Glib::build_filename (rcfile, ui_config_file_name);
345
346         // this test seems bogus?
347         if (rcfile.length()) {
348                 tree.set_root (&get_state());
349                 if (!tree.write (rcfile.c_str())){
350                         error << string_compose (_("Config file %1 not saved"), rcfile) << endmsg;
351                         return -1;
352                 }
353         }
354
355         _dirty = false;
356
357         return 0;
358 }
359
360 XMLNode&
361 UIConfiguration::get_state ()
362 {
363         XMLNode* root;
364         LocaleGuard lg (X_("POSIX"));
365
366         root = new XMLNode("Ardour");
367
368         root->add_child_nocopy (get_variables ("UI"));
369         root->add_child_nocopy (get_variables ("Canvas"));
370
371         if (derived_modified) {
372
373         }
374
375         if (aliases_modified) {
376                 XMLNode* parent = new XMLNode (X_("ColorAliases"));
377                 for (ColorAliases::const_iterator i = color_aliases.begin(); i != color_aliases.end(); ++i) {
378                         XMLNode* node = new XMLNode (X_("ColorAlias"));
379                         node->add_property (X_("name"), i->first);
380                         node->add_property (X_("alias"), i->second);
381                         parent->add_child_nocopy (*node);
382                 }
383                 root->add_child_nocopy (*parent);
384         }
385         
386         if (_extra_xml) {
387                 root->add_child_copy (*_extra_xml);
388         }
389
390         return *root;
391 }
392
393 XMLNode&
394 UIConfiguration::get_variables (std::string which_node)
395 {
396         XMLNode* node;
397         LocaleGuard lg (X_("POSIX"));
398
399         node = new XMLNode (which_node);
400
401 #undef  UI_CONFIG_VARIABLE
402 #undef  CANVAS_FONT_VARIABLE
403 #define UI_CONFIG_VARIABLE(Type,var,Name,value) if (node->name() == "UI") { var.add_to_node (*node); }
404 #define CANVAS_FONT_VARIABLE(var,Name) if (node->name() == "Canvas") { var.add_to_node (*node); }
405 #include "ui_config_vars.h"
406 #include "canvas_vars.h"
407 #undef  UI_CONFIG_VARIABLE
408 #undef  CANVAS_FONT_VARIABLE
409
410         return *node;
411 }
412
413 int
414 UIConfiguration::set_state (const XMLNode& root, int /*version*/)
415 {
416         if (root.name() != "Ardour") {
417                 return -1;
418         }
419
420         Stateful::save_extra_xml (root);
421
422         XMLNodeList nlist = root.children();
423         XMLNodeConstIterator niter;
424         XMLNode *node;
425
426         for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
427
428                 node = *niter;
429
430                 if (node->name() == "Canvas" ||  node->name() == "UI") {
431                         set_variables (*node);
432
433                 }
434         }
435
436         XMLNode* relative = find_named_node (root, X_("RelativeColors"));
437         
438         if (relative) {
439                 // load_relative_colors (*relative);
440         }
441
442         
443         XMLNode* aliases = find_named_node (root, X_("ColorAliases"));
444
445         if (aliases) {
446                 load_color_aliases (*aliases);
447         }
448
449         return 0;
450 }
451
452 void
453 UIConfiguration::load_color_aliases (XMLNode const & node)
454 {
455         XMLNodeList const nlist = node.children();
456         XMLNodeConstIterator niter;
457         XMLProperty const *name;
458         XMLProperty const *alias;
459         
460         color_aliases.clear ();
461
462         for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
463                 if ((*niter)->name() != X_("ColorAlias")) {
464                         continue;
465                 }
466                 name = (*niter)->property (X_("name"));
467                 alias = (*niter)->property (X_("alias"));
468
469                 if (name && alias) {
470                         color_aliases.insert (make_pair (name->value(), alias->value()));
471                 }
472         }
473 }
474
475
476 #if 0
477 void
478 UIConfiguration::load_relative_colors (XMLNode const & node)
479 {
480         XMLNodeList const nlist = node.children();
481         XMLNodeConstIterator niter;
482         XMLProperty const *name;
483         XMLProperty const *alias;
484         
485         color_aliases.clear ();
486
487         for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
488                 if ((*niter)->name() != X_("RelativeColor")) {
489                         continue;
490                 }
491                 name = (*niter)->property (X_("name"));
492                 alias = (*niter)->property (X_("alias"));
493
494                 if (name && alias) {
495                         color_aliases.insert (make_pair (name->value(), alias->value()));
496                 }
497         }
498
499 }
500 #endif
501 void
502 UIConfiguration::set_variables (const XMLNode& node)
503 {
504 #undef  UI_CONFIG_VARIABLE
505 #define UI_CONFIG_VARIABLE(Type,var,name,val) \
506          if (var.set_from_node (node)) { \
507                  ParameterChanged (name); \
508                  }
509 #define CANVAS_FONT_VARIABLE(var,name)  \
510          if (var.set_from_node (node)) { \
511                  ParameterChanged (name); \
512                  }
513 #include "ui_config_vars.h"
514 #include "canvas_vars.h"
515 #undef  UI_CONFIG_VARIABLE
516 #undef  CANVAS_FONT_VARIABLE
517
518         /* Reset base colors */
519
520 #undef  CANVAS_BASE_COLOR
521 #define CANVAS_BASE_COLOR(var,name,val) \
522         var.set_from_node (node);
523 #include "base_colors.h"
524 #undef CANVAS_BASE_COLOR        
525
526 }
527
528 void
529 UIConfiguration::set_dirty ()
530 {
531         _dirty = true;
532 }
533
534 bool
535 UIConfiguration::dirty () const
536 {
537         return _dirty || aliases_modified || derived_modified;
538 }
539
540 ArdourCanvas::Color
541 UIConfiguration::base_color_by_name (const std::string& name) const
542 {
543         map<std::string,ColorVariable<Color>* >::const_iterator i = configurable_colors.find (name);
544
545         if (i != configurable_colors.end()) {
546                 return i->second->get();
547         }
548
549 #if 0 // yet unsed experimental style postfix
550         /* Idea: use identical colors but different font/sizes
551          * for variants of the same 'widget'.
552          *
553          * example:
554          *  set_name("mute button");  // in route_ui.cc
555          *  set_name("mute button small"); // in mixer_strip.cc
556          *
557          * ardour3_widget_list.rc:
558          *  widget "*mute button" style:highest "small_button"
559          *  widget "*mute button small" style:highest "very_small_text"
560          *
561          * both use color-schema of defined in
562          *   BUTTON_VARS(MuteButton, "mute button")
563          *
564          * (in this particular example the widgets should be packed
565          * vertically shinking the mixer strip ones are currently not)
566          */
567         const size_t name_len = name.size();
568         const size_t name_sep = name.find(':');
569         for (i = configurable_colors.begin(); i != configurable_colors.end(), name_sep != string::npos; ++i) {
570                 const size_t cmp_len = i->first.size();
571                 const size_t cmp_sep = i->first.find(':');
572                 if (cmp_len >= name_len || cmp_sep == string::npos) continue;
573                 if (name.substr(name_sep) != i->first.substr(cmp_sep)) continue;
574                 if (name.substr(0, cmp_sep) != i->first.substr(0, cmp_sep)) continue;
575                 return i->second->get();
576         }
577 #endif
578
579         cerr << string_compose (_("Base Color %1 not found"), name) << endl;
580         return RGBA_TO_UINT (g_random_int()%256,g_random_int()%256,g_random_int()%256,0xff);
581 }
582
583 ArdourCanvas::Color
584 UIConfiguration::color (const std::string& name) const
585 {
586         map<string,string>::const_iterator e = color_aliases.find (name);
587
588         if (e != color_aliases.end ()) {
589                 map<string,RelativeHSV>::const_iterator rc = relative_colors.find (e->second);
590                 if (rc != relative_colors.end()) {
591                         return rc->second.get();
592                 }
593         } else {
594                 /* not an alias, try directly */
595                 map<string,RelativeHSV>::const_iterator rc = relative_colors.find (name);
596                 if (rc != relative_colors.end()) {
597                         return rc->second.get();
598                 }
599         }
600         
601         cerr << string_compose (_("Color %1 not found"), name) << endl;
602         
603         return rgba_to_color ((g_random_int()%256)/255.0,
604                               (g_random_int()%256)/255.0,
605                               (g_random_int()%256)/255.0,
606                               0xff);
607 }
608
609 ArdourCanvas::HSV
610 UIConfiguration::RelativeHSV::get() const
611 {
612         HSV base (UIConfiguration::instance()->base_color_by_name (base_color));
613         
614         /* this operation is a little wierd. because of the way we originally
615          * computed the alpha specification for the modifiers used here
616          * we need to reset base's alpha to zero before adding the modifier.
617          */
618
619         HSV self (base + modifier);
620         
621         if (quantized_hue >= 0.0) {
622                 self.h = quantized_hue;
623         }
624         
625         return self;
626 }
627
628 Color
629 UIConfiguration::quantized (Color c) const
630 {
631         HSV hsv (c);
632         hsv.h = hue_width * (round (hsv.h/hue_width));
633         return hsv.color ();
634 }
635
636 void
637 UIConfiguration::reset_relative (const string& name, const RelativeHSV& rhsv)
638 {
639         RelativeColors::iterator i = relative_colors.find (name);
640
641         if (i == relative_colors.end()) {
642                 return;
643         }
644
645         i->second = rhsv;
646         derived_modified = true;
647
648         ARDOUR_UI_UTILS::ColorsChanged (); /* EMIT SIGNAL */
649 }
650
651 void
652 UIConfiguration::set_alias (string const & name, string const & alias)
653 {
654         ColorAliases::iterator i = color_aliases.find (name);
655         if (i == color_aliases.end()) {
656                 return;
657         }
658
659         i->second = alias;
660         aliases_modified = true;
661
662         ARDOUR_UI_UTILS::ColorsChanged (); /* EMIT SIGNAL */
663 }
664
665 void
666 UIConfiguration::load_rc_file (const string& filename, bool themechange)
667 {
668         std::string rc_file_path;
669
670         if (!find_file (ardour_config_search_path(), filename, rc_file_path)) {
671                 warning << string_compose (_("Unable to find UI style file %1 in search path %2. %3 will look strange"),
672                                            filename, ardour_config_search_path().to_string(), PROGRAM_NAME)
673                                 << endmsg;
674                 return;
675         }
676
677         info << "Loading ui configuration file " << rc_file_path << endmsg;
678
679         Gtkmm2ext::UI::instance()->load_rcfile (rc_file_path, themechange);
680 }