fix up various issues with UIConfiguration, saving state, RC file loading etc.
[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         PBD::stacktrace (cerr, 20);
333         
334         if (!dirty()) {
335                 return 0;
336         }
337         
338         std::string rcfile(user_config_directory());
339         rcfile = Glib::build_filename (rcfile, ui_config_file_name);
340
341         // this test seems bogus?
342         if (rcfile.length()) {
343                 tree.set_root (&get_state());
344                 if (!tree.write (rcfile.c_str())){
345                         error << string_compose (_("Config file %1 not saved"), rcfile) << endmsg;
346                         return -1;
347                 }
348         }
349
350         _dirty = false;
351
352         return 0;
353 }
354
355 XMLNode&
356 UIConfiguration::get_state ()
357 {
358         XMLNode* root;
359         LocaleGuard lg (X_("POSIX"));
360
361         root = new XMLNode("Ardour");
362
363         root->add_child_nocopy (get_variables ("UI"));
364         root->add_child_nocopy (get_variables ("Canvas"));
365
366         if (derived_modified) {
367
368         }
369
370         if (aliases_modified) {
371                 XMLNode* parent = new XMLNode (X_("ColorAliases"));
372                 for (ColorAliases::const_iterator i = color_aliases.begin(); i != color_aliases.end(); ++i) {
373                         XMLNode* node = new XMLNode (X_("ColorAlias"));
374                         node->add_property (X_("name"), i->first);
375                         node->add_property (X_("alias"), i->second);
376                         parent->add_child_nocopy (*node);
377                 }
378                 root->add_child_nocopy (*parent);
379         }
380         
381         if (_extra_xml) {
382                 root->add_child_copy (*_extra_xml);
383         }
384
385         return *root;
386 }
387
388 XMLNode&
389 UIConfiguration::get_variables (std::string which_node)
390 {
391         XMLNode* node;
392         LocaleGuard lg (X_("POSIX"));
393
394         node = new XMLNode (which_node);
395
396 #undef  UI_CONFIG_VARIABLE
397 #undef  CANVAS_FONT_VARIABLE
398 #define UI_CONFIG_VARIABLE(Type,var,Name,value) if (node->name() == "UI") { var.add_to_node (*node); }
399 #define CANVAS_FONT_VARIABLE(var,Name) if (node->name() == "Canvas") { var.add_to_node (*node); }
400 #include "ui_config_vars.h"
401 #include "canvas_vars.h"
402 #undef  UI_CONFIG_VARIABLE
403 #undef  CANVAS_FONT_VARIABLE
404
405         return *node;
406 }
407
408 int
409 UIConfiguration::set_state (const XMLNode& root, int /*version*/)
410 {
411         if (root.name() != "Ardour") {
412                 return -1;
413         }
414
415         Stateful::save_extra_xml (root);
416
417         XMLNodeList nlist = root.children();
418         XMLNodeConstIterator niter;
419         XMLNode *node;
420
421         for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
422
423                 node = *niter;
424
425                 if (node->name() == "Canvas" ||  node->name() == "UI") {
426                         set_variables (*node);
427
428                 }
429         }
430
431         XMLNode* relative = find_named_node (root, X_("RelativeColors"));
432         
433         if (relative) {
434                 // load_relative_colors (*relative);
435         }
436
437         
438         XMLNode* aliases = find_named_node (root, X_("ColorAliases"));
439
440         if (aliases) {
441                 load_color_aliases (*aliases);
442         }
443
444         return 0;
445 }
446
447 void
448 UIConfiguration::load_color_aliases (XMLNode const & node)
449 {
450         XMLNodeList const nlist = node.children();
451         XMLNodeConstIterator niter;
452         XMLProperty const *name;
453         XMLProperty const *alias;
454         
455         color_aliases.clear ();
456
457         for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
458                 if ((*niter)->name() != X_("ColorAlias")) {
459                         continue;
460                 }
461                 name = (*niter)->property (X_("name"));
462                 alias = (*niter)->property (X_("alias"));
463
464                 if (name && alias) {
465                         color_aliases.insert (make_pair (name->value(), alias->value()));
466                 }
467         }
468 }
469
470
471 #if 0
472 void
473 UIConfiguration::load_relative_colors (XMLNode const & node)
474 {
475         XMLNodeList const nlist = node.children();
476         XMLNodeConstIterator niter;
477         XMLProperty const *name;
478         XMLProperty const *alias;
479         
480         color_aliases.clear ();
481
482         for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
483                 if ((*niter)->name() != X_("RelativeColor")) {
484                         continue;
485                 }
486                 name = (*niter)->property (X_("name"));
487                 alias = (*niter)->property (X_("alias"));
488
489                 if (name && alias) {
490                         color_aliases.insert (make_pair (name->value(), alias->value()));
491                 }
492         }
493
494 }
495 #endif
496 void
497 UIConfiguration::set_variables (const XMLNode& node)
498 {
499 #undef  UI_CONFIG_VARIABLE
500 #define UI_CONFIG_VARIABLE(Type,var,name,val) \
501          if (var.set_from_node (node)) { \
502                  ParameterChanged (name); \
503                  }
504 #define CANVAS_FONT_VARIABLE(var,name)  \
505          if (var.set_from_node (node)) { \
506                  ParameterChanged (name); \
507                  }
508 #include "ui_config_vars.h"
509 #include "canvas_vars.h"
510 #undef  UI_CONFIG_VARIABLE
511 #undef  CANVAS_FONT_VARIABLE
512
513         /* Reset base colors */
514
515 #undef  CANVAS_BASE_COLOR
516 #define CANVAS_BASE_COLOR(var,name,val) \
517         var.set_from_node (node);
518 #include "base_colors.h"
519 #undef CANVAS_BASE_COLOR        
520
521 }
522
523 void
524 UIConfiguration::set_dirty ()
525 {
526         _dirty = true;
527 }
528
529 bool
530 UIConfiguration::dirty () const
531 {
532         return _dirty || aliases_modified || derived_modified;
533 }
534
535 ArdourCanvas::Color
536 UIConfiguration::base_color_by_name (const std::string& name) const
537 {
538         map<std::string,ColorVariable<Color>* >::const_iterator i = configurable_colors.find (name);
539
540         if (i != configurable_colors.end()) {
541                 return i->second->get();
542         }
543
544 #if 0 // yet unsed experimental style postfix
545         /* Idea: use identical colors but different font/sizes
546          * for variants of the same 'widget'.
547          *
548          * example:
549          *  set_name("mute button");  // in route_ui.cc
550          *  set_name("mute button small"); // in mixer_strip.cc
551          *
552          * ardour3_widget_list.rc:
553          *  widget "*mute button" style:highest "small_button"
554          *  widget "*mute button small" style:highest "very_small_text"
555          *
556          * both use color-schema of defined in
557          *   BUTTON_VARS(MuteButton, "mute button")
558          *
559          * (in this particular example the widgets should be packed
560          * vertically shinking the mixer strip ones are currently not)
561          */
562         const size_t name_len = name.size();
563         const size_t name_sep = name.find(':');
564         for (i = configurable_colors.begin(); i != configurable_colors.end(), name_sep != string::npos; ++i) {
565                 const size_t cmp_len = i->first.size();
566                 const size_t cmp_sep = i->first.find(':');
567                 if (cmp_len >= name_len || cmp_sep == string::npos) continue;
568                 if (name.substr(name_sep) != i->first.substr(cmp_sep)) continue;
569                 if (name.substr(0, cmp_sep) != i->first.substr(0, cmp_sep)) continue;
570                 return i->second->get();
571         }
572 #endif
573
574         cerr << string_compose (_("Base Color %1 not found"), name) << endl;
575         return RGBA_TO_UINT (g_random_int()%256,g_random_int()%256,g_random_int()%256,0xff);
576 }
577
578 ArdourCanvas::Color
579 UIConfiguration::color (const std::string& name) const
580 {
581         map<string,string>::const_iterator e = color_aliases.find (name);
582
583         if (e != color_aliases.end ()) {
584                 map<string,RelativeHSV>::const_iterator rc = relative_colors.find (e->second);
585                 if (rc != relative_colors.end()) {
586                         return rc->second.get();
587                 }
588         } else {
589                 /* not an alias, try directly */
590                 map<string,RelativeHSV>::const_iterator rc = relative_colors.find (name);
591                 if (rc != relative_colors.end()) {
592                         return rc->second.get();
593                 }
594         }
595         
596         cerr << string_compose (_("Color %1 not found"), name) << endl;
597         
598         return rgba_to_color ((g_random_int()%256)/255.0,
599                               (g_random_int()%256)/255.0,
600                               (g_random_int()%256)/255.0,
601                               0xff);
602 }
603
604 ArdourCanvas::HSV
605 UIConfiguration::RelativeHSV::get() const
606 {
607         HSV base (UIConfiguration::instance()->base_color_by_name (base_color));
608         
609         /* this operation is a little wierd. because of the way we originally
610          * computed the alpha specification for the modifiers used here
611          * we need to reset base's alpha to zero before adding the modifier.
612          */
613
614         HSV self (base + modifier);
615         
616         if (quantized_hue >= 0.0) {
617                 self.h = quantized_hue;
618         }
619         
620         return self;
621 }
622
623 Color
624 UIConfiguration::quantized (Color c) const
625 {
626         HSV hsv (c);
627         hsv.h = hue_width * (round (hsv.h/hue_width));
628         return hsv.color ();
629 }
630
631 void
632 UIConfiguration::reset_relative (const string& name, const RelativeHSV& rhsv)
633 {
634         RelativeColors::iterator i = relative_colors.find (name);
635
636         if (i == relative_colors.end()) {
637                 return;
638         }
639
640         i->second = rhsv;
641         derived_modified = true;
642
643         ARDOUR_UI_UTILS::ColorsChanged (); /* EMIT SIGNAL */
644
645         save_state ();
646 }
647
648 void
649 UIConfiguration::set_alias (string const & name, string const & alias)
650 {
651         ColorAliases::iterator i = color_aliases.find (name);
652         if (i == color_aliases.end()) {
653                 return;
654         }
655
656         i->second = alias;
657         aliases_modified = true;
658
659         ARDOUR_UI_UTILS::ColorsChanged (); /* EMIT SIGNAL */
660
661         save_state ();
662 }
663
664 void
665 UIConfiguration::load_rc_file (const string& filename, bool themechange)
666 {
667         std::string rc_file_path;
668
669         if (!find_file (ardour_config_search_path(), filename, rc_file_path)) {
670                 warning << string_compose (_("Unable to find UI style file %1 in search path %2. %3 will look strange"),
671                                            filename, ardour_config_search_path().to_string(), PROGRAM_NAME)
672                                 << endmsg;
673                 return;
674         }
675
676         info << "Loading ui configuration file " << rc_file_path << endmsg;
677
678         Gtkmm2ext::UI::instance()->load_rcfile (rc_file_path, themechange);
679 }