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