remove stacktrace
[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/failed_constructor.h"
30 #include "pbd/xml++.h"
31 #include "pbd/file_utils.h"
32 #include "pbd/error.h"
33 #include "pbd/stacktrace.h"
34
35 #include "gtkmm2ext/rgb_macros.h"
36 #include "gtkmm2ext/gtk_ui.h"
37
38 #include "ardour/filesystem_paths.h"
39
40 #include "ardour_ui.h"
41 #include "global_signals.h"
42 #include "ui_config.h"
43
44 #include "i18n.h"
45
46 using namespace std;
47 using namespace PBD;
48 using namespace ARDOUR;
49 using namespace ArdourCanvas;
50
51 static const char* ui_config_file_name = "ui_config";
52 static const char* default_ui_config_file_name = "default_ui_config";
53 UIConfiguration* UIConfiguration::_instance = 0;
54
55 static const double hue_width = 18.0;
56
57 UIConfiguration::UIConfiguration ()
58         :
59 #undef  UI_CONFIG_VARIABLE
60 #define UI_CONFIG_VARIABLE(Type,var,name,val) var (name,val),
61 #define CANVAS_FONT_VARIABLE(var,name) var (name),
62 #include "ui_config_vars.h"
63 #include "canvas_vars.h"
64 #undef  UI_CONFIG_VARIABLE
65 #undef  CANVAS_FONT_VARIABLE
66
67         /* initialize all the base colors using default
68            colors for now. these will be reset when/if
69            we load the UI config file.
70         */
71
72 #undef CANVAS_BASE_COLOR
73 #define CANVAS_BASE_COLOR(var,name,val) var (name,quantized (val)),
74 #include "base_colors.h"
75 #undef CANVAS_BASE_COLOR
76
77         _dirty (false),
78         aliases_modified (false),
79         derived_modified (false),
80         _saved_state_node (""),
81         _saved_state_version (-1)
82         
83 {
84         _instance = this;
85
86         /* pack all base colors into the configurable color map so that
87            derived colors can use them.
88         */
89           
90 #undef CANVAS_BASE_COLOR
91 #define CANVAS_BASE_COLOR(var,name,color) configurable_colors.insert (make_pair (name,&var));
92 #include "base_colors.h"
93 #undef CANVAS_BASE_COLOR
94
95 #undef CANVAS_COLOR
96 #define CANVAS_COLOR(var,name,base,modifier) relative_colors.insert (make_pair (name, RelativeHSV (base,modifier)));
97 #include "colors.h"
98 #undef CANVAS_COLOR
99         
100 #undef COLOR_ALIAS
101 #define COLOR_ALIAS(var,name,alias) color_aliases.insert (make_pair (name,alias));
102 #include "color_aliases.h"
103 #undef CANVAS_COLOR
104
105         load_state();
106
107         ARDOUR_UI_UTILS::ColorsChanged.connect (boost::bind (&UIConfiguration::colors_changed, this));
108
109         ParameterChanged.connect (sigc::mem_fun (*this, &UIConfiguration::parameter_changed));
110
111         /* force GTK theme setting, so that RC file will work */
112         
113         reset_gtk_theme ();
114 }
115
116 UIConfiguration::~UIConfiguration ()
117 {
118 }
119
120 void
121 UIConfiguration::colors_changed ()
122 {
123         _dirty = true;
124
125         reset_gtk_theme ();
126
127         /* In theory, one of these ought to work:
128
129            gtk_rc_reparse_all_for_settings (gtk_settings_get_default(), true);
130            gtk_rc_reset_styles (gtk_settings_get_default());
131
132            but in practice, neither of them do. So just reload the current
133            GTK RC file, which causes a reset of all styles and a redraw
134         */
135
136         parameter_changed ("ui-rc_file");
137 }
138
139 void
140 UIConfiguration::parameter_changed (string param)
141 {
142         _dirty = true;
143         
144         if (param == "ui-rc-file") {
145                 load_rc_file (get_ui_rc_file(), true);
146         }
147
148         save_state ();
149 }
150
151 void
152 UIConfiguration::reset_gtk_theme ()
153 {
154         stringstream ss;
155
156         ss << "gtk_color_scheme = \"" << hex;
157         
158         for (ColorAliases::iterator g = color_aliases.begin(); g != color_aliases.end(); ++g) {
159                 
160                 if (g->first.find ("gtk_") == 0) {
161                         ColorAliases::const_iterator a = color_aliases.find (g->first);
162                         const string gtk_name = g->first.substr (4);
163                         ss << gtk_name << ":#" << std::setw (6) << setfill ('0') << (color (g->second) >> 8) << ';';
164                 }
165         }
166
167         ss << '"' << dec << endl;
168
169         /* reset GTK color scheme */
170
171         Gtk::Settings::get_default()->property_gtk_color_scheme() = ss.str();
172 }
173         
174 UIConfiguration::RelativeHSV
175 UIConfiguration::color_as_relative_hsv (Color c)
176 {
177         HSV variable (c);
178         HSV closest;
179         double shortest_distance = DBL_MAX;
180         string closest_name;
181
182         map<string,ColorVariable<Color>*>::iterator f;
183         std::map<std::string,HSV> palette;
184
185         for (f = configurable_colors.begin(); f != configurable_colors.end(); ++f) {
186                 palette.insert (make_pair (f->first, HSV (f->second->get())));
187         }
188
189         for (map<string,HSV>::iterator f = palette.begin(); f != palette.end(); ++f) {
190                 
191                 double d;
192                 HSV fixed (f->second);
193                 
194                 if (fixed.is_gray() || variable.is_gray()) {
195                         /* at least one is achromatic; HSV::distance() will do
196                          * the right thing
197                          */
198                         d = fixed.distance (variable);
199                 } else {
200                         /* chromatic: compare ONLY hue because our task is
201                            to pick the HUE closest and then compute
202                            a modifier. We want to keep the number of 
203                            hues low, and by computing perceptual distance 
204                            we end up finding colors that are to each
205                            other without necessarily be close in hue.
206                         */
207                         d = fabs (variable.h - fixed.h);
208                 }
209
210                 if (d < shortest_distance) {
211                         closest = fixed;
212                         closest_name = f->first;
213                         shortest_distance = d;
214                 }
215         }
216         
217         /* we now know the closest color of the fixed colors to 
218            this variable color. Compute the HSV diff and
219            use it to redefine the variable color in terms of the
220            fixed one.
221         */
222         
223         HSV delta = variable.delta (closest);
224
225         /* quantize hue delta so we don't end up with many subtle hues caused
226          * by original color choices
227          */
228
229         delta.h = hue_width * (round (delta.h/hue_width));
230
231         return RelativeHSV (closest_name, delta);
232 }
233
234 void
235 UIConfiguration::map_parameters (boost::function<void (std::string)>& functor)
236 {
237 #undef  UI_CONFIG_VARIABLE
238 #define UI_CONFIG_VARIABLE(Type,var,Name,value) functor (Name);
239 #include "ui_config_vars.h"
240 #undef  UI_CONFIG_VARIABLE
241 }
242
243 int
244 UIConfiguration::load_defaults ()
245 {
246         int found = 0;
247         std::string rcfile;
248
249         if (find_file (ardour_config_search_path(), default_ui_config_file_name, rcfile) ) {
250                 XMLTree tree;
251                 found = 1;
252
253                 info << string_compose (_("Loading default ui configuration file %1"), rcfile) << endmsg;
254
255                 if (!tree.read (rcfile.c_str())) {
256                         error << string_compose(_("cannot read default ui configuration file \"%1\""), rcfile) << endmsg;
257                         return -1;
258                 }
259
260                 if (set_state (*tree.root(), Stateful::loading_state_version)) {
261                         error << string_compose(_("default ui configuration file \"%1\" not loaded successfully."), rcfile) << endmsg;
262                         return -1;
263                 }
264                 
265                 _dirty = false;
266         }
267
268         ARDOUR_UI_UTILS::ColorsChanged ();
269
270         return found;
271 }
272
273 int
274 UIConfiguration::load_state ()
275 {
276         bool found = false;
277
278         std::string rcfile;
279
280         if (find_file (ardour_config_search_path(), default_ui_config_file_name, rcfile)) {
281                 XMLTree tree;
282                 found = true;
283
284                 info << string_compose (_("Loading default ui configuration file %1"), rcfile) << endmsg;
285
286                 if (!tree.read (rcfile.c_str())) {
287                         error << string_compose(_("cannot read default ui configuration file \"%1\""), rcfile) << endmsg;
288                         return -1;
289                 }
290
291                 if (set_state (*tree.root(), Stateful::loading_state_version)) {
292                         error << string_compose(_("default ui configuration file \"%1\" not loaded successfully."), rcfile) << endmsg;
293                         return -1;
294                 }
295
296                 /* make a copy */
297         }
298
299         if (find_file (ardour_config_search_path(), ui_config_file_name, rcfile)) {
300                 XMLTree tree;
301                 found = true;
302
303                 info << string_compose (_("Loading user ui configuration file %1"), rcfile) << endmsg;
304
305                 if (!tree.read (rcfile)) {
306                         error << string_compose(_("cannot read ui configuration file \"%1\""), rcfile) << endmsg;
307                         return -1;
308                 }
309
310                 if (set_state (*tree.root(), Stateful::loading_state_version)) {
311                         error << string_compose(_("user ui configuration file \"%1\" not loaded successfully."), rcfile) << endmsg;
312                         return -1;
313                 }
314
315                 _dirty = false;
316         }
317
318         if (!found) {
319                 error << _("could not find any ui configuration file, canvas will look broken.") << endmsg;
320         }
321
322         ARDOUR_UI_UTILS::ColorsChanged ();
323
324         return 0;
325 }
326
327 int
328 UIConfiguration::save_state()
329 {
330         XMLTree tree;
331
332         if (!dirty()) {
333                 return 0;
334         }
335         
336         std::string rcfile(user_config_directory());
337         rcfile = Glib::build_filename (rcfile, ui_config_file_name);
338
339         if (rcfile.length()) {
340                 tree.set_root (&get_state());
341                 if (!tree.write (rcfile.c_str())){
342                         error << string_compose (_("Config file %1 not saved"), rcfile) << endmsg;
343                         return -1;
344                 }
345         }
346
347         _dirty = false;
348
349         return 0;
350 }
351
352 XMLNode&
353 UIConfiguration::get_state ()
354 {
355         XMLNode* root;
356         LocaleGuard lg (X_("POSIX"));
357
358         root = new XMLNode("Ardour");
359
360         root->add_child_nocopy (get_variables ("UI"));
361         root->add_child_nocopy (get_variables ("Canvas"));
362
363         if (derived_modified) {
364
365         }
366
367         if (aliases_modified) {
368                 XMLNode* parent = new XMLNode (X_("ColorAliases"));
369                 for (ColorAliases::const_iterator i = color_aliases.begin(); i != color_aliases.end(); ++i) {
370                         XMLNode* node = new XMLNode (X_("ColorAlias"));
371                         node->add_property (X_("name"), i->first);
372                         node->add_property (X_("alias"), i->second);
373                         parent->add_child_nocopy (*node);
374                 }
375                 root->add_child_nocopy (*parent);
376         }
377         
378         if (_extra_xml) {
379                 root->add_child_copy (*_extra_xml);
380         }
381
382         return *root;
383 }
384
385 XMLNode&
386 UIConfiguration::get_variables (std::string which_node)
387 {
388         XMLNode* node;
389         LocaleGuard lg (X_("POSIX"));
390
391         node = new XMLNode (which_node);
392
393 #undef  UI_CONFIG_VARIABLE
394 #undef  CANVAS_FONT_VARIABLE
395 #define UI_CONFIG_VARIABLE(Type,var,Name,value) if (node->name() == "UI") { var.add_to_node (*node); }
396 #define CANVAS_FONT_VARIABLE(var,Name) if (node->name() == "Canvas") { var.add_to_node (*node); }
397 #include "ui_config_vars.h"
398 #include "canvas_vars.h"
399 #undef  UI_CONFIG_VARIABLE
400 #undef  CANVAS_FONT_VARIABLE
401
402         return *node;
403 }
404
405 int
406 UIConfiguration::set_state (const XMLNode& root, int /*version*/)
407 {
408         if (root.name() != "Ardour") {
409                 return -1;
410         }
411
412         Stateful::save_extra_xml (root);
413
414         XMLNodeList nlist = root.children();
415         XMLNodeConstIterator niter;
416         XMLNode *node;
417
418         for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
419
420                 node = *niter;
421
422                 if (node->name() == "Canvas" ||  node->name() == "UI") {
423                         set_variables (*node);
424
425                 }
426         }
427
428         XMLNode* relative = find_named_node (root, X_("RelativeColors"));
429         
430         if (relative) {
431                 // load_relative_colors (*relative);
432         }
433
434         
435         XMLNode* aliases = find_named_node (root, X_("ColorAliases"));
436
437         if (aliases) {
438                 load_color_aliases (*aliases);
439         }
440
441         return 0;
442 }
443
444 void
445 UIConfiguration::load_color_aliases (XMLNode const & node)
446 {
447         XMLNodeList const nlist = node.children();
448         XMLNodeConstIterator niter;
449         XMLProperty const *name;
450         XMLProperty const *alias;
451         
452         color_aliases.clear ();
453
454         for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
455                 if ((*niter)->name() != X_("ColorAlias")) {
456                         continue;
457                 }
458                 name = (*niter)->property (X_("name"));
459                 alias = (*niter)->property (X_("alias"));
460
461                 if (name && alias) {
462                         color_aliases.insert (make_pair (name->value(), alias->value()));
463                 }
464         }
465 }
466
467
468 #if 0
469 void
470 UIConfiguration::load_relative_colors (XMLNode const & node)
471 {
472         XMLNodeList const nlist = node.children();
473         XMLNodeConstIterator niter;
474         XMLProperty const *name;
475         XMLProperty const *alias;
476         
477         color_aliases.clear ();
478
479         for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
480                 if ((*niter)->name() != X_("RelativeColor")) {
481                         continue;
482                 }
483                 name = (*niter)->property (X_("name"));
484                 alias = (*niter)->property (X_("alias"));
485
486                 if (name && alias) {
487                         color_aliases.insert (make_pair (name->value(), alias->value()));
488                 }
489         }
490
491 }
492 #endif
493 void
494 UIConfiguration::set_variables (const XMLNode& node)
495 {
496 #undef  UI_CONFIG_VARIABLE
497 #define UI_CONFIG_VARIABLE(Type,var,name,val) \
498          if (var.set_from_node (node)) { \
499                  ParameterChanged (name); \
500                  }
501 #define CANVAS_FONT_VARIABLE(var,name)  \
502          if (var.set_from_node (node)) { \
503                  ParameterChanged (name); \
504                  }
505 #include "ui_config_vars.h"
506 #include "canvas_vars.h"
507 #undef  UI_CONFIG_VARIABLE
508 #undef  CANVAS_FONT_VARIABLE
509
510         /* Reset base colors */
511
512 #undef  CANVAS_BASE_COLOR
513 #define CANVAS_BASE_COLOR(var,name,val) \
514         var.set_from_node (node);
515 #include "base_colors.h"
516 #undef CANVAS_BASE_COLOR        
517
518 }
519
520 void
521 UIConfiguration::set_dirty ()
522 {
523         _dirty = true;
524 }
525
526 bool
527 UIConfiguration::dirty () const
528 {
529         return _dirty || aliases_modified || derived_modified;
530 }
531
532 ArdourCanvas::Color
533 UIConfiguration::base_color_by_name (const std::string& name) const
534 {
535         map<std::string,ColorVariable<Color>* >::const_iterator i = configurable_colors.find (name);
536
537         if (i != configurable_colors.end()) {
538                 return i->second->get();
539         }
540
541 #if 0 // yet unsed experimental style postfix
542         /* Idea: use identical colors but different font/sizes
543          * for variants of the same 'widget'.
544          *
545          * example:
546          *  set_name("mute button");  // in route_ui.cc
547          *  set_name("mute button small"); // in mixer_strip.cc
548          *
549          * ardour3_widget_list.rc:
550          *  widget "*mute button" style:highest "small_button"
551          *  widget "*mute button small" style:highest "very_small_text"
552          *
553          * both use color-schema of defined in
554          *   BUTTON_VARS(MuteButton, "mute button")
555          *
556          * (in this particular example the widgets should be packed
557          * vertically shinking the mixer strip ones are currently not)
558          */
559         const size_t name_len = name.size();
560         const size_t name_sep = name.find(':');
561         for (i = configurable_colors.begin(); i != configurable_colors.end(), name_sep != string::npos; ++i) {
562                 const size_t cmp_len = i->first.size();
563                 const size_t cmp_sep = i->first.find(':');
564                 if (cmp_len >= name_len || cmp_sep == string::npos) continue;
565                 if (name.substr(name_sep) != i->first.substr(cmp_sep)) continue;
566                 if (name.substr(0, cmp_sep) != i->first.substr(0, cmp_sep)) continue;
567                 return i->second->get();
568         }
569 #endif
570
571         cerr << string_compose (_("Base Color %1 not found"), name) << endl;
572         return RGBA_TO_UINT (g_random_int()%256,g_random_int()%256,g_random_int()%256,0xff);
573 }
574
575 ArdourCanvas::Color
576 UIConfiguration::color (const std::string& name) const
577 {
578         map<string,string>::const_iterator e = color_aliases.find (name);
579
580         if (e != color_aliases.end ()) {
581                 map<string,RelativeHSV>::const_iterator rc = relative_colors.find (e->second);
582                 if (rc != relative_colors.end()) {
583                         return rc->second.get();
584                 }
585         } else {
586                 /* not an alias, try directly */
587                 map<string,RelativeHSV>::const_iterator rc = relative_colors.find (name);
588                 if (rc != relative_colors.end()) {
589                         return rc->second.get();
590                 }
591         }
592         
593         cerr << string_compose (_("Color %1 not found"), name) << endl;
594         
595         return rgba_to_color ((g_random_int()%256)/255.0,
596                               (g_random_int()%256)/255.0,
597                               (g_random_int()%256)/255.0,
598                               0xff);
599 }
600
601 ArdourCanvas::HSV
602 UIConfiguration::RelativeHSV::get() const
603 {
604         HSV base (UIConfiguration::instance()->base_color_by_name (base_color));
605         
606         /* this operation is a little wierd. because of the way we originally
607          * computed the alpha specification for the modifiers used here
608          * we need to reset base's alpha to zero before adding the modifier.
609          */
610
611         HSV self (base + modifier);
612         
613         if (quantized_hue >= 0.0) {
614                 self.h = quantized_hue;
615         }
616         
617         return self;
618 }
619
620 Color
621 UIConfiguration::quantized (Color c) const
622 {
623         HSV hsv (c);
624         hsv.h = hue_width * (round (hsv.h/hue_width));
625         return hsv.color ();
626 }
627
628 void
629 UIConfiguration::reset_relative (const string& name, const RelativeHSV& rhsv)
630 {
631         RelativeColors::iterator i = relative_colors.find (name);
632
633         if (i == relative_colors.end()) {
634                 return;
635         }
636
637         i->second = rhsv;
638         derived_modified = true;
639
640         ARDOUR_UI_UTILS::ColorsChanged (); /* EMIT SIGNAL */
641
642         save_state ();
643 }
644
645 void
646 UIConfiguration::set_alias (string const & name, string const & alias)
647 {
648         ColorAliases::iterator i = color_aliases.find (name);
649         if (i == color_aliases.end()) {
650                 return;
651         }
652
653         i->second = alias;
654         aliases_modified = true;
655
656         ARDOUR_UI_UTILS::ColorsChanged (); /* EMIT SIGNAL */
657
658         save_state ();
659 }
660
661 void
662 UIConfiguration::load_rc_file (const string& filename, bool themechange)
663 {
664         std::string rc_file_path;
665
666         if (!find_file (ardour_config_search_path(), filename, rc_file_path)) {
667                 warning << string_compose (_("Unable to find UI style file %1 in search path %2. %3 will look strange"),
668                                            filename, ardour_config_search_path().to_string(), PROGRAM_NAME)
669                                 << endmsg;
670                 return;
671         }
672
673         info << "Loading ui configuration file " << rc_file_path << endmsg;
674
675         Gtkmm2ext::UI::instance()->load_rcfile (rc_file_path, themechange);
676 }