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