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