2 Copyright (C) 1999-2014 Paul Davis
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.
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.
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.
24 #include <cstdio> /* for snprintf, grrr */
26 #include <glibmm/miscutils.h>
27 #include <glib/gstdio.h>
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"
35 #include "gtkmm2ext/rgb_macros.h"
36 #include "gtkmm2ext/gtk_ui.h"
38 #include "ardour/filesystem_paths.h"
40 #include "ardour_ui.h"
41 #include "global_signals.h"
42 #include "ui_config.h"
48 using namespace ARDOUR;
49 using namespace ArdourCanvas;
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;
55 static const double hue_width = 18.0;
57 UIConfiguration::UIConfiguration ()
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
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.
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
78 aliases_modified (false),
79 derived_modified (false),
80 _saved_state_node (""),
81 _saved_state_version (-1)
86 /* pack all base colors into the configurable color map so that
87 derived colors can use them.
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
96 #define CANVAS_COLOR(var,name,base,modifier) relative_colors.insert (make_pair (name, RelativeHSV (base,modifier)));
101 #define COLOR_ALIAS(var,name,alias) color_aliases.insert (make_pair (name,alias));
102 #include "color_aliases.h"
107 ARDOUR_UI_UTILS::ColorsChanged.connect (boost::bind (&UIConfiguration::color_theme_changed, this));
109 ParameterChanged.connect (sigc::mem_fun (*this, &UIConfiguration::parameter_changed));
111 /* force loading of the GTK rc file */
113 parameter_changed ("ui-rc-file");
116 UIConfiguration::~UIConfiguration ()
121 UIConfiguration::color_theme_changed ()
127 /* In theory, one of these ought to work:
129 gtk_rc_reparse_all_for_settings (gtk_settings_get_default(), true);
130 gtk_rc_reset_styles (gtk_settings_get_default());
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
136 parameter_changed ("ui-rc-file");
142 UIConfiguration::parameter_changed (string param)
146 if (param == "ui-rc-file") {
147 bool env_defined = false;
148 string rcfile = Glib::getenv("ARDOUR3_UI_RC", env_defined);
151 rcfile = get_ui_rc_file();
154 load_rc_file (rcfile, true);
159 UIConfiguration::reset_gtk_theme ()
163 ss << "gtk_color_scheme = \"" << hex;
165 for (ColorAliases::iterator g = color_aliases.begin(); g != color_aliases.end(); ++g) {
167 if (g->first.find ("gtk_") == 0) {
168 ColorAliases::const_iterator a = color_aliases.find (g->first);
169 const string gtk_name = g->first.substr (4);
170 ss << gtk_name << ":#" << std::setw (6) << setfill ('0') << (color (g->second) >> 8) << ';';
174 ss << '"' << dec << endl;
176 /* reset GTK color scheme */
178 Gtk::Settings::get_default()->property_gtk_color_scheme() = ss.str();
181 UIConfiguration::RelativeHSV
182 UIConfiguration::color_as_relative_hsv (Color c)
186 double shortest_distance = DBL_MAX;
189 map<string,ColorVariable<Color>*>::iterator f;
190 std::map<std::string,HSV> palette;
192 for (f = configurable_colors.begin(); f != configurable_colors.end(); ++f) {
193 palette.insert (make_pair (f->first, HSV (f->second->get())));
196 for (map<string,HSV>::iterator f = palette.begin(); f != palette.end(); ++f) {
199 HSV fixed (f->second);
201 if (fixed.is_gray() || variable.is_gray()) {
202 /* at least one is achromatic; HSV::distance() will do
205 d = fixed.distance (variable);
207 /* chromatic: compare ONLY hue because our task is
208 to pick the HUE closest and then compute
209 a modifier. We want to keep the number of
210 hues low, and by computing perceptual distance
211 we end up finding colors that are to each
212 other without necessarily be close in hue.
214 d = fabs (variable.h - fixed.h);
217 if (d < shortest_distance) {
219 closest_name = f->first;
220 shortest_distance = d;
224 /* we now know the closest color of the fixed colors to
225 this variable color. Compute the HSV diff and
226 use it to redefine the variable color in terms of the
230 HSV delta = variable.delta (closest);
232 /* quantize hue delta so we don't end up with many subtle hues caused
233 * by original color choices
236 delta.h = hue_width * (round (delta.h/hue_width));
238 return RelativeHSV (closest_name, delta);
242 UIConfiguration::map_parameters (boost::function<void (std::string)>& functor)
244 #undef UI_CONFIG_VARIABLE
245 #define UI_CONFIG_VARIABLE(Type,var,Name,value) functor (Name);
246 #include "ui_config_vars.h"
247 #undef UI_CONFIG_VARIABLE
251 UIConfiguration::load_defaults ()
256 if (find_file (ardour_config_search_path(), default_ui_config_file_name, rcfile) ) {
260 info << string_compose (_("Loading default ui configuration file %1"), rcfile) << endmsg;
262 if (!tree.read (rcfile.c_str())) {
263 error << string_compose(_("cannot read default ui configuration file \"%1\""), rcfile) << endmsg;
267 if (set_state (*tree.root(), Stateful::loading_state_version)) {
268 error << string_compose(_("default ui configuration file \"%1\" not loaded successfully."), rcfile) << endmsg;
275 ARDOUR_UI_UTILS::ColorsChanged ();
281 UIConfiguration::load_state ()
287 if (find_file (ardour_config_search_path(), default_ui_config_file_name, rcfile)) {
291 info << string_compose (_("Loading default ui configuration file %1"), rcfile) << endmsg;
293 if (!tree.read (rcfile.c_str())) {
294 error << string_compose(_("cannot read default ui configuration file \"%1\""), rcfile) << endmsg;
298 if (set_state (*tree.root(), Stateful::loading_state_version)) {
299 error << string_compose(_("default ui configuration file \"%1\" not loaded successfully."), rcfile) << endmsg;
306 if (find_file (ardour_config_search_path(), ui_config_file_name, rcfile)) {
310 info << string_compose (_("Loading user ui configuration file %1"), rcfile) << endmsg;
312 if (!tree.read (rcfile)) {
313 error << string_compose(_("cannot read ui configuration file \"%1\""), rcfile) << endmsg;
317 if (set_state (*tree.root(), Stateful::loading_state_version)) {
318 error << string_compose(_("user ui configuration file \"%1\" not loaded successfully."), rcfile) << endmsg;
326 error << _("could not find any ui configuration file, canvas will look broken.") << endmsg;
329 ARDOUR_UI_UTILS::ColorsChanged ();
335 UIConfiguration::save_state()
343 std::string rcfile(user_config_directory());
344 rcfile = Glib::build_filename (rcfile, ui_config_file_name);
346 // this test seems bogus?
347 if (rcfile.length()) {
348 tree.set_root (&get_state());
349 if (!tree.write (rcfile.c_str())){
350 error << string_compose (_("Config file %1 not saved"), rcfile) << endmsg;
361 UIConfiguration::get_state ()
364 LocaleGuard lg (X_("POSIX"));
366 root = new XMLNode("Ardour");
368 root->add_child_nocopy (get_variables ("UI"));
369 root->add_child_nocopy (get_variables ("Canvas"));
371 if (derived_modified) {
375 if (aliases_modified) {
376 XMLNode* parent = new XMLNode (X_("ColorAliases"));
377 for (ColorAliases::const_iterator i = color_aliases.begin(); i != color_aliases.end(); ++i) {
378 XMLNode* node = new XMLNode (X_("ColorAlias"));
379 node->add_property (X_("name"), i->first);
380 node->add_property (X_("alias"), i->second);
381 parent->add_child_nocopy (*node);
383 root->add_child_nocopy (*parent);
387 root->add_child_copy (*_extra_xml);
394 UIConfiguration::get_variables (std::string which_node)
397 LocaleGuard lg (X_("POSIX"));
399 node = new XMLNode (which_node);
401 #undef UI_CONFIG_VARIABLE
402 #undef CANVAS_FONT_VARIABLE
403 #define UI_CONFIG_VARIABLE(Type,var,Name,value) if (node->name() == "UI") { var.add_to_node (*node); }
404 #define CANVAS_FONT_VARIABLE(var,Name) if (node->name() == "Canvas") { var.add_to_node (*node); }
405 #include "ui_config_vars.h"
406 #include "canvas_vars.h"
407 #undef UI_CONFIG_VARIABLE
408 #undef CANVAS_FONT_VARIABLE
414 UIConfiguration::set_state (const XMLNode& root, int /*version*/)
416 if (root.name() != "Ardour") {
420 Stateful::save_extra_xml (root);
422 XMLNodeList nlist = root.children();
423 XMLNodeConstIterator niter;
426 for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
430 if (node->name() == "Canvas" || node->name() == "UI") {
431 set_variables (*node);
436 XMLNode* relative = find_named_node (root, X_("RelativeColors"));
439 // load_relative_colors (*relative);
443 XMLNode* aliases = find_named_node (root, X_("ColorAliases"));
446 load_color_aliases (*aliases);
453 UIConfiguration::load_color_aliases (XMLNode const & node)
455 XMLNodeList const nlist = node.children();
456 XMLNodeConstIterator niter;
457 XMLProperty const *name;
458 XMLProperty const *alias;
460 color_aliases.clear ();
462 for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
463 if ((*niter)->name() != X_("ColorAlias")) {
466 name = (*niter)->property (X_("name"));
467 alias = (*niter)->property (X_("alias"));
470 color_aliases.insert (make_pair (name->value(), alias->value()));
478 UIConfiguration::load_relative_colors (XMLNode const & node)
480 XMLNodeList const nlist = node.children();
481 XMLNodeConstIterator niter;
482 XMLProperty const *name;
483 XMLProperty const *alias;
485 color_aliases.clear ();
487 for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
488 if ((*niter)->name() != X_("RelativeColor")) {
491 name = (*niter)->property (X_("name"));
492 alias = (*niter)->property (X_("alias"));
495 color_aliases.insert (make_pair (name->value(), alias->value()));
502 UIConfiguration::set_variables (const XMLNode& node)
504 #undef UI_CONFIG_VARIABLE
505 #define UI_CONFIG_VARIABLE(Type,var,name,val) \
506 if (var.set_from_node (node)) { \
507 ParameterChanged (name); \
509 #define CANVAS_FONT_VARIABLE(var,name) \
510 if (var.set_from_node (node)) { \
511 ParameterChanged (name); \
513 #include "ui_config_vars.h"
514 #include "canvas_vars.h"
515 #undef UI_CONFIG_VARIABLE
516 #undef CANVAS_FONT_VARIABLE
518 /* Reset base colors */
520 #undef CANVAS_BASE_COLOR
521 #define CANVAS_BASE_COLOR(var,name,val) \
522 var.set_from_node (node);
523 #include "base_colors.h"
524 #undef CANVAS_BASE_COLOR
529 UIConfiguration::set_dirty ()
535 UIConfiguration::dirty () const
537 return _dirty || aliases_modified || derived_modified;
541 UIConfiguration::base_color_by_name (const std::string& name) const
543 map<std::string,ColorVariable<Color>* >::const_iterator i = configurable_colors.find (name);
545 if (i != configurable_colors.end()) {
546 return i->second->get();
549 #if 0 // yet unsed experimental style postfix
550 /* Idea: use identical colors but different font/sizes
551 * for variants of the same 'widget'.
554 * set_name("mute button"); // in route_ui.cc
555 * set_name("mute button small"); // in mixer_strip.cc
557 * ardour3_widget_list.rc:
558 * widget "*mute button" style:highest "small_button"
559 * widget "*mute button small" style:highest "very_small_text"
561 * both use color-schema of defined in
562 * BUTTON_VARS(MuteButton, "mute button")
564 * (in this particular example the widgets should be packed
565 * vertically shinking the mixer strip ones are currently not)
567 const size_t name_len = name.size();
568 const size_t name_sep = name.find(':');
569 for (i = configurable_colors.begin(); i != configurable_colors.end(), name_sep != string::npos; ++i) {
570 const size_t cmp_len = i->first.size();
571 const size_t cmp_sep = i->first.find(':');
572 if (cmp_len >= name_len || cmp_sep == string::npos) continue;
573 if (name.substr(name_sep) != i->first.substr(cmp_sep)) continue;
574 if (name.substr(0, cmp_sep) != i->first.substr(0, cmp_sep)) continue;
575 return i->second->get();
579 cerr << string_compose (_("Base Color %1 not found"), name) << endl;
580 return RGBA_TO_UINT (g_random_int()%256,g_random_int()%256,g_random_int()%256,0xff);
584 UIConfiguration::color (const std::string& name) const
586 map<string,string>::const_iterator e = color_aliases.find (name);
588 if (e != color_aliases.end ()) {
589 map<string,RelativeHSV>::const_iterator rc = relative_colors.find (e->second);
590 if (rc != relative_colors.end()) {
591 return rc->second.get();
594 /* not an alias, try directly */
595 map<string,RelativeHSV>::const_iterator rc = relative_colors.find (name);
596 if (rc != relative_colors.end()) {
597 return rc->second.get();
601 cerr << string_compose (_("Color %1 not found"), name) << endl;
603 return rgba_to_color ((g_random_int()%256)/255.0,
604 (g_random_int()%256)/255.0,
605 (g_random_int()%256)/255.0,
610 UIConfiguration::RelativeHSV::get() const
612 HSV base (UIConfiguration::instance()->base_color_by_name (base_color));
614 /* this operation is a little wierd. because of the way we originally
615 * computed the alpha specification for the modifiers used here
616 * we need to reset base's alpha to zero before adding the modifier.
619 HSV self (base + modifier);
621 if (quantized_hue >= 0.0) {
622 self.h = quantized_hue;
629 UIConfiguration::quantized (Color c) const
632 hsv.h = hue_width * (round (hsv.h/hue_width));
637 UIConfiguration::reset_relative (const string& name, const RelativeHSV& rhsv)
639 RelativeColors::iterator i = relative_colors.find (name);
641 if (i == relative_colors.end()) {
646 derived_modified = true;
648 ARDOUR_UI_UTILS::ColorsChanged (); /* EMIT SIGNAL */
652 UIConfiguration::set_alias (string const & name, string const & alias)
654 ColorAliases::iterator i = color_aliases.find (name);
655 if (i == color_aliases.end()) {
660 aliases_modified = true;
662 ARDOUR_UI_UTILS::ColorsChanged (); /* EMIT SIGNAL */
666 UIConfiguration::load_rc_file (const string& filename, bool themechange)
668 std::string rc_file_path;
670 if (!find_file (ardour_config_search_path(), filename, rc_file_path)) {
671 warning << string_compose (_("Unable to find UI style file %1 in search path %2. %3 will look strange"),
672 filename, ardour_config_search_path().to_string(), PROGRAM_NAME)
677 info << "Loading ui configuration file " << rc_file_path << endmsg;
679 Gtkmm2ext::UI::instance()->load_rcfile (rc_file_path, themechange);