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