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