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