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