move VCA assignment up to Route level
[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 #if !defined USE_CAIRO_IMAGE_SURFACE && !defined NDEBUG
21 #define OPTIONAL_CAIRO_IMAGE_SURFACE
22 #endif
23
24 #include <iostream>
25 #include <sstream>
26 #include <unistd.h>
27 #include <cstdlib>
28 #include <cstdio> /* for snprintf, grrr */
29
30 #include <cairo/cairo.h>
31
32 #include <pango/pangoft2.h> // for fontmap resolution control for GnomeCanvas
33 #include <pango/pangocairo.h> // for fontmap resolution control for GnomeCanvas
34
35 #include "pbd/gstdio_compat.h"
36 #include "pbd/unwind.h"
37 #include <glibmm/miscutils.h>
38
39 #include <gtkmm/settings.h>
40
41 #include "pbd/convert.h"
42 #include "pbd/failed_constructor.h"
43 #include "pbd/xml++.h"
44 #include "pbd/file_utils.h"
45 #include "pbd/locale_guard.h"
46 #include "pbd/error.h"
47 #include "pbd/stacktrace.h"
48
49 #include "gtkmm2ext/rgb_macros.h"
50 #include "gtkmm2ext/gtk_ui.h"
51
52 #include "ardour/filesystem_paths.h"
53 #include "ardour/utils.h"
54
55 #include "ui_config.h"
56
57 #include "i18n.h"
58
59 using namespace std;
60 using namespace PBD;
61 using namespace ARDOUR;
62 using namespace ArdourCanvas;
63
64 static const char* ui_config_file_name = "ui_config";
65 static const char* default_ui_config_file_name = "default_ui_config";
66
67 static const double hue_width = 18.0;
68
69 UIConfiguration&
70 UIConfiguration::instance ()
71 {
72         static UIConfiguration s_instance;
73         return s_instance;
74 }
75
76 UIConfiguration::UIConfiguration ()
77         :
78 #undef  UI_CONFIG_VARIABLE
79 #define UI_CONFIG_VARIABLE(Type,var,name,val) var (name,val),
80 #define CANVAS_FONT_VARIABLE(var,name) var (name),
81 #include "ui_config_vars.h"
82 #include "canvas_vars.h"
83 #undef  UI_CONFIG_VARIABLE
84 #undef  CANVAS_FONT_VARIABLE
85
86         _dirty (false),
87         aliases_modified (false),
88         colors_modified (false),
89         modifiers_modified (false),
90         block_save (0)
91 {
92         load_state();
93
94         ColorsChanged.connect (boost::bind (&UIConfiguration::colors_changed, this));
95
96         ParameterChanged.connect (sigc::mem_fun (*this, &UIConfiguration::parameter_changed));
97 }
98
99 UIConfiguration::~UIConfiguration ()
100 {
101 }
102
103 void
104 UIConfiguration::colors_changed ()
105 {
106         reset_gtk_theme ();
107
108         /* In theory, one of these ought to work:
109
110            gtk_rc_reparse_all_for_settings (gtk_settings_get_default(), true);
111            gtk_rc_reset_styles (gtk_settings_get_default());
112
113            but in practice, neither of them do. So just reload the current
114            GTK RC file, which causes a reset of all styles and a redraw
115         */
116
117         parameter_changed ("ui-rc-file");
118 }
119
120 void
121 UIConfiguration::parameter_changed (string param)
122 {
123         _dirty = true;
124
125         if (param == "ui-rc-file") {
126                 load_rc_file (true);
127         } else if (param == "color-file") {
128                 load_color_theme ();
129         }
130
131         save_state ();
132 }
133
134 void
135 UIConfiguration::reset_gtk_theme ()
136 {
137         stringstream ss;
138
139         ss << "gtk_color_scheme = \"" << hex;
140
141         for (ColorAliases::iterator g = color_aliases.begin(); g != color_aliases.end(); ++g) {
142
143                 if (g->first.find ("gtk_") == 0) {
144                         const string gtk_name = g->first.substr (4);
145                         ss << gtk_name << ":#" << std::setw (6) << setfill ('0') << (color (g->second) >> 8) << ';';
146                 }
147         }
148
149         ss << '"' << dec << endl;
150
151         /* reset GTK color scheme */
152
153         Gtk::Settings::get_default()->property_gtk_color_scheme() = ss.str();
154 }
155
156 void
157 UIConfiguration::reset_dpi ()
158 {
159         long val = get_font_scale();
160         set_pango_fontsize ();
161         /* Xft rendering */
162
163         gtk_settings_set_long_property (gtk_settings_get_default(),
164                                         "gtk-xft-dpi", val, "ardour");
165         DPIReset(); //Emit Signal
166 }
167
168 void
169 UIConfiguration::set_pango_fontsize ()
170 {
171         long val = get_font_scale();
172
173         /* FT2 rendering - used by GnomeCanvas, sigh */
174
175 #ifndef PLATFORM_WINDOWS
176         pango_ft2_font_map_set_resolution ((PangoFT2FontMap*) pango_ft2_font_map_new(), val/1024, val/1024);
177 #endif
178
179         /* Cairo rendering, in case there is any */
180
181         pango_cairo_font_map_set_resolution ((PangoCairoFontMap*) pango_cairo_font_map_get_default(), val/1024);
182 }
183
184 float
185 UIConfiguration::get_ui_scale ()
186 {
187         return get_font_scale () / 102400.;
188 }
189
190 void
191 UIConfiguration::map_parameters (boost::function<void (std::string)>& functor)
192 {
193 #undef  UI_CONFIG_VARIABLE
194 #define UI_CONFIG_VARIABLE(Type,var,Name,value) functor (Name);
195 #include "ui_config_vars.h"
196 #undef  UI_CONFIG_VARIABLE
197 }
198
199 int
200 UIConfiguration::pre_gui_init ()
201 {
202 #ifdef CAIRO_SUPPORTS_FORCE_BUGGY_GRADIENTS_ENVIRONMENT_VARIABLE
203         if (get_buggy_gradients()) {
204                 g_setenv ("FORCE_BUGGY_GRADIENTS", "1", 1);
205         }
206 #endif
207 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
208         if (get_cairo_image_surface()) {
209                 g_setenv ("ARDOUR_IMAGE_SURFACE", "1", 1);
210         }
211 #endif
212         return 0;
213 }
214
215 UIConfiguration*
216 UIConfiguration::post_gui_init ()
217 {
218         load_color_theme ();
219         return this;
220 }
221
222 int
223 UIConfiguration::load_defaults ()
224 {
225         std::string rcfile;
226         int ret = -1;
227
228         if (find_file (ardour_config_search_path(), default_ui_config_file_name, rcfile) ) {
229                 XMLTree tree;
230
231                 info << string_compose (_("Loading default ui configuration file %1"), rcfile) << endmsg;
232
233                 if (!tree.read (rcfile.c_str())) {
234                         error << string_compose(_("cannot read default ui configuration file \"%1\""), rcfile) << endmsg;
235                 } else {
236                         if (set_state (*tree.root(), Stateful::loading_state_version)) {
237                                 error << string_compose(_("default ui configuration file \"%1\" not loaded successfully."), rcfile) << endmsg;
238                         } else {
239                                 _dirty = false;
240                                 ret = 0;
241                         }
242                 }
243
244         } else {
245                 warning << string_compose (_("Could not find default UI configuration file %1"), default_ui_config_file_name) << endmsg;
246         }
247
248
249         if (ret == 0) {
250                 /* reload color theme */
251                 load_color_theme (false);
252         }
253
254         return ret;
255 }
256
257 int
258 UIConfiguration::load_color_theme (bool allow_own)
259 {
260         std::string cfile;
261         string basename;
262         bool found = false;
263         /* ColorsChanged() will trigger a  parameter_changed () which
264          * in turn calls save_state()
265          */
266         PBD::Unwinder<uint32_t> uw (block_save, block_save + 1);
267
268         if (allow_own) {
269                 basename = "my-";
270                 basename += color_file.get();
271                 basename += ".colors";
272
273                 if (find_file (ardour_config_search_path(), basename, cfile)) {
274                         found = true;
275                 }
276         }
277
278         if (!found) {
279                 basename = color_file.get();
280                 basename += ".colors";
281
282                 if (find_file (ardour_config_search_path(), basename, cfile)) {
283                         found = true;
284                 }
285         }
286
287         if (found) {
288
289                 XMLTree tree;
290
291                 info << string_compose (_("Loading color file %1"), cfile) << endmsg;
292
293                 if (!tree.read (cfile.c_str())) {
294                         error << string_compose(_("cannot read color file \"%1\""), cfile) << endmsg;
295                         return -1;
296                 }
297
298                 if (set_state (*tree.root(), Stateful::loading_state_version)) {
299                         error << string_compose(_("color file \"%1\" not loaded successfully."), cfile) << endmsg;
300                         return -1;
301                 }
302
303                 ColorsChanged ();
304         } else {
305                 warning << string_compose (_("Color file %1 not found"), basename) << endmsg;
306         }
307
308         return 0;
309 }
310
311 int
312 UIConfiguration::store_color_theme ()
313 {
314         XMLNode* root;
315         LocaleGuard lg;
316
317         root = new XMLNode("Ardour");
318
319         XMLNode* parent = new XMLNode (X_("Colors"));
320         for (Colors::const_iterator i = colors.begin(); i != colors.end(); ++i) {
321                 XMLNode* node = new XMLNode (X_("Color"));
322                 node->add_property (X_("name"), i->first);
323                 stringstream ss;
324                 ss << "0x" << setw (8) << setfill ('0') << hex << i->second;
325                 node->add_property (X_("value"), ss.str());
326                 parent->add_child_nocopy (*node);
327         }
328         root->add_child_nocopy (*parent);
329
330         parent = new XMLNode (X_("ColorAliases"));
331         for (ColorAliases::const_iterator i = color_aliases.begin(); i != color_aliases.end(); ++i) {
332                 XMLNode* node = new XMLNode (X_("ColorAlias"));
333                 node->add_property (X_("name"), i->first);
334                 node->add_property (X_("alias"), i->second);
335                 parent->add_child_nocopy (*node);
336         }
337         root->add_child_nocopy (*parent);
338
339         parent = new XMLNode (X_("Modifiers"));
340         for (Modifiers::const_iterator i = modifiers.begin(); i != modifiers.end(); ++i) {
341                 XMLNode* node = new XMLNode (X_("Modifier"));
342                 node->add_property (X_("name"), i->first);
343                 node->add_property (X_("modifier"), i->second.to_string());
344                 parent->add_child_nocopy (*node);
345         }
346         root->add_child_nocopy (*parent);
347
348         XMLTree tree;
349         std::string colorfile = Glib::build_filename (user_config_directory(), (string ("my-") + color_file.get() + ".colors"));
350
351         tree.set_root (root);
352
353         if (!tree.write (colorfile.c_str())){
354                 error << string_compose (_("Color file %1 not saved"), colorfile) << endmsg;
355                 return -1;
356         }
357
358         return 0;
359 }
360
361 int
362 UIConfiguration::load_state ()
363 {
364         LocaleGuard lg; // a single guard for all 3 configs
365         bool found = false;
366
367         std::string rcfile;
368
369         if (find_file (ardour_config_search_path(), default_ui_config_file_name, rcfile)) {
370                 XMLTree tree;
371                 found = true;
372
373                 info << string_compose (_("Loading default ui configuration file %1"), rcfile) << endmsg;
374
375                 if (!tree.read (rcfile.c_str())) {
376                         error << string_compose(_("cannot read default ui configuration file \"%1\""), rcfile) << endmsg;
377                         return -1;
378                 }
379
380                 if (set_state (*tree.root(), Stateful::loading_state_version)) {
381                         error << string_compose(_("default ui configuration file \"%1\" not loaded successfully."), rcfile) << endmsg;
382                         return -1;
383                 }
384         }
385
386         if (find_file (ardour_config_search_path(), ui_config_file_name, rcfile)) {
387                 XMLTree tree;
388                 found = true;
389
390                 info << string_compose (_("Loading user ui configuration file %1"), rcfile) << endmsg;
391
392                 if (!tree.read (rcfile)) {
393                         error << string_compose(_("cannot read ui configuration file \"%1\""), rcfile) << endmsg;
394                         return -1;
395                 }
396
397                 if (set_state (*tree.root(), Stateful::loading_state_version)) {
398                         error << string_compose(_("user ui configuration file \"%1\" not loaded successfully."), rcfile) << endmsg;
399                         return -1;
400                 }
401
402                 _dirty = false;
403         }
404
405         if (!found) {
406                 error << _("could not find any ui configuration file, canvas will look broken.") << endmsg;
407         }
408
409         return 0;
410 }
411
412 int
413 UIConfiguration::save_state()
414 {
415         if (block_save != 0) {
416                 return -1;
417         }
418
419         if (_dirty) {
420                 std::string rcfile = Glib::build_filename (user_config_directory(), ui_config_file_name);
421
422                 XMLTree tree;
423
424                 tree.set_root (&get_state());
425
426                 if (!tree.write (rcfile.c_str())){
427                         error << string_compose (_("Config file %1 not saved"), rcfile) << endmsg;
428                         return -1;
429                 }
430
431                 _dirty = false;
432         }
433
434         if (aliases_modified || colors_modified || modifiers_modified) {
435
436                 if (store_color_theme ()) {
437                         error << string_compose (_("Color file %1 not saved"), color_file.get()) << endmsg;
438                         return -1;
439                 }
440
441                 aliases_modified = false;
442                 colors_modified = false;
443                 modifiers_modified = false;
444         }
445
446
447         return 0;
448 }
449
450 XMLNode&
451 UIConfiguration::get_state ()
452 {
453         XMLNode* root;
454         LocaleGuard lg;
455
456         root = new XMLNode("Ardour");
457
458         root->add_child_nocopy (get_variables ("UI"));
459         root->add_child_nocopy (get_variables ("Canvas"));
460
461         if (_extra_xml) {
462                 root->add_child_copy (*_extra_xml);
463         }
464
465         return *root;
466 }
467
468 XMLNode&
469 UIConfiguration::get_variables (std::string which_node)
470 {
471         XMLNode* node;
472         LocaleGuard lg;
473
474         node = new XMLNode (which_node);
475
476 #undef  UI_CONFIG_VARIABLE
477 #undef  CANVAS_FONT_VARIABLE
478 #define UI_CONFIG_VARIABLE(Type,var,Name,value) if (node->name() == "UI") { var.add_to_node (*node); }
479 #define CANVAS_FONT_VARIABLE(var,Name) if (node->name() == "Canvas") { var.add_to_node (*node); }
480 #include "ui_config_vars.h"
481 #include "canvas_vars.h"
482 #undef  UI_CONFIG_VARIABLE
483 #undef  CANVAS_FONT_VARIABLE
484
485         return *node;
486 }
487
488 int
489 UIConfiguration::set_state (const XMLNode& root, int /*version*/)
490 {
491         LocaleGuard lg;
492         /* this can load a generic UI configuration file or a colors file */
493
494         if (root.name() != "Ardour") {
495                 return -1;
496         }
497
498         Stateful::save_extra_xml (root);
499
500         XMLNodeList nlist = root.children();
501         XMLNodeConstIterator niter;
502         XMLNode *node;
503
504         for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
505
506                 node = *niter;
507
508                 if (node->name() == "Canvas" ||  node->name() == "UI") {
509                         set_variables (*node);
510
511                 }
512         }
513
514         XMLNode* colors = find_named_node (root, X_("Colors"));
515
516         if (colors) {
517                 load_colors (*colors);
518         }
519
520         XMLNode* aliases = find_named_node (root, X_("ColorAliases"));
521
522         if (aliases) {
523                 load_color_aliases (*aliases);
524         }
525
526         XMLNode* modifiers = find_named_node (root, X_("Modifiers"));
527
528         if (modifiers) {
529                 load_modifiers (*modifiers);
530         }
531
532         return 0;
533 }
534
535 void
536 UIConfiguration::load_color_aliases (XMLNode const & node)
537 {
538         XMLNodeList const nlist = node.children();
539         XMLNodeConstIterator niter;
540         XMLProperty const *name;
541         XMLProperty const *alias;
542
543         color_aliases.clear ();
544
545         for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
546                 XMLNode const * child = *niter;
547                 if (child->name() != X_("ColorAlias")) {
548                         continue;
549                 }
550                 name = child->property (X_("name"));
551                 alias = child->property (X_("alias"));
552
553                 if (name && alias) {
554                         color_aliases.insert (make_pair (name->value(), alias->value()));
555                 }
556         }
557 }
558
559 void
560 UIConfiguration::load_colors (XMLNode const & node)
561 {
562         XMLNodeList const nlist = node.children();
563         XMLNodeConstIterator niter;
564         XMLProperty const *name;
565         XMLProperty const *color;
566
567         colors.clear ();
568
569         for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
570                 XMLNode const * child = *niter;
571                 if (child->name() != X_("Color")) {
572                         continue;
573                 }
574                 name = child->property (X_("name"));
575                 color = child->property (X_("value"));
576
577                 if (name && color) {
578                         ArdourCanvas::Color c;
579                         c = strtoul (color->value().c_str(), 0, 16);
580                         colors.insert (make_pair (name->value(), c));
581                 }
582         }
583 }
584
585 void
586 UIConfiguration::load_modifiers (XMLNode const & node)
587 {
588         PBD::LocaleGuard lg;
589         XMLNodeList const nlist = node.children();
590         XMLNodeConstIterator niter;
591         XMLProperty const *name;
592         XMLProperty const *mod;
593
594         modifiers.clear ();
595
596         for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
597                 XMLNode const * child = *niter;
598                 if (child->name() != X_("Modifier")) {
599                         continue;
600                 }
601
602                 name = child->property (X_("name"));
603                 mod = child->property (X_("modifier"));
604
605                 if (name && mod) {
606                         SVAModifier svam (mod->value());
607                         modifiers.insert (make_pair (name->value(), svam));
608                 }
609         }
610 }
611
612 void
613 UIConfiguration::set_variables (const XMLNode& node)
614 {
615 #undef  UI_CONFIG_VARIABLE
616 #define UI_CONFIG_VARIABLE(Type,var,name,val) if (var.set_from_node (node)) { ParameterChanged (name); }
617 #define CANVAS_FONT_VARIABLE(var,name)        if (var.set_from_node (node)) { ParameterChanged (name); }
618 #include "ui_config_vars.h"
619 #include "canvas_vars.h"
620 #undef  UI_CONFIG_VARIABLE
621 #undef  CANVAS_FONT_VARIABLE
622 }
623
624 ArdourCanvas::SVAModifier
625 UIConfiguration::modifier (string const & name) const
626 {
627         Modifiers::const_iterator m = modifiers.find (name);
628         if (m != modifiers.end()) {
629                 return m->second;
630         }
631         return SVAModifier ();
632 }
633
634 ArdourCanvas::Color
635 UIConfiguration::color_mod (std::string const & colorname, std::string const & modifiername) const
636 {
637         return HSV (color (colorname)).mod (modifier (modifiername)).color ();
638 }
639
640 ArdourCanvas::Color
641 UIConfiguration::color_mod (const ArdourCanvas::Color& color, std::string const & modifiername) const
642 {
643         return HSV (color).mod (modifier (modifiername)).color ();
644 }
645
646 ArdourCanvas::Color
647 UIConfiguration::color (const std::string& name, bool* failed) const
648 {
649         ColorAliases::const_iterator e = color_aliases.find (name);
650
651         if (failed) {
652                 *failed = false;
653         }
654
655         if (e != color_aliases.end ()) {
656                 Colors::const_iterator rc = colors.find (e->second);
657                 if (rc != colors.end()) {
658                         return rc->second;
659                 }
660         } else {
661                 /* not an alias, try directly */
662                 Colors::const_iterator rc = colors.find (name);
663                 if (rc != colors.end()) {
664                         return rc->second;
665                 }
666         }
667
668         if (!failed) {
669                 /* only show this message if the caller wasn't interested in
670                    the fail status.
671                 */
672                 cerr << string_compose (_("Color %1 not found"), name) << endl;
673         }
674
675         if (failed) {
676                 *failed = true;
677         }
678
679         return rgba_to_color ((g_random_int()%256)/255.0,
680                               (g_random_int()%256)/255.0,
681                               (g_random_int()%256)/255.0,
682                               0xff);
683 }
684
685 Color
686 UIConfiguration::quantized (Color c) const
687 {
688         HSV hsv (c);
689         hsv.h = hue_width * (round (hsv.h/hue_width));
690         return hsv.color ();
691 }
692
693 void
694 UIConfiguration::set_color (string const& name, ArdourCanvas::Color color)
695 {
696         Colors::iterator i = colors.find (name);
697         if (i == colors.end()) {
698                 return;
699         }
700         i->second = color;
701         colors_modified = true;
702
703         ColorsChanged (); /* EMIT SIGNAL */
704 }
705
706 void
707 UIConfiguration::set_alias (string const & name, string const & alias)
708 {
709         ColorAliases::iterator i = color_aliases.find (name);
710         if (i == color_aliases.end()) {
711                 return;
712         }
713
714         i->second = alias;
715         aliases_modified = true;
716
717         ColorsChanged (); /* EMIT SIGNAL */
718 }
719
720 void
721 UIConfiguration::set_modifier (string const & name, SVAModifier svam)
722 {
723         Modifiers::iterator m = modifiers.find (name);
724
725         if (m == modifiers.end()) {
726                 return;
727         }
728
729         m->second = svam;
730         modifiers_modified = true;
731
732         ColorsChanged (); /* EMIT SIGNAL */
733 }
734
735 void
736 UIConfiguration::load_rc_file (bool themechange, bool allow_own)
737 {
738         string basename = ui_rc_file.get();
739         std::string rc_file_path;
740
741         if (!find_file (ardour_config_search_path(), basename, rc_file_path)) {
742                 warning << string_compose (_("Unable to find UI style file %1 in search path %2. %3 will look strange"),
743                                            basename, ardour_config_search_path().to_string(), PROGRAM_NAME)
744                                 << endmsg;
745                 return;
746         }
747
748         info << "Loading ui configuration file " << rc_file_path << endmsg;
749
750         Gtkmm2ext::UI::instance()->load_rcfile (rc_file_path, themechange);
751 }
752
753