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