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