save and restore color aliases
[ardour.git] / gtk2_ardour / ui_config.cc
index 5c98766819a8f0ee5a2d2f4b486a332c46157f7c..7b4d91adfa3caeefaf4e3b9d7157c8bd3c85e096 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 1999-2006 Paul Davis
+    Copyright (C) 1999-2014 Paul Davis
 
     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
 #include "pbd/xml++.h"
 #include "pbd/file_utils.h"
 #include "pbd/error.h"
+#include "pbd/stacktrace.h"
 
 #include "gtkmm2ext/rgb_macros.h"
 
 #include "ardour/filesystem_paths.h"
 
+#include "ardour_ui.h"
+#include "global_signals.h"
 #include "ui_config.h"
 
 #include "i18n.h"
 using namespace std;
 using namespace PBD;
 using namespace ARDOUR;
+using namespace ArdourCanvas;
 
 static const char* ui_config_file_name = "ui_config";
 static const char* default_ui_config_file_name = "default_ui_config";
+UIConfiguration* UIConfiguration::_instance = 0;
+
+static const double hue_width = 18.0;
 
 UIConfiguration::UIConfiguration ()
        :
 #undef  UI_CONFIG_VARIABLE
-#undef  CANVAS_VARIABLE
 #define UI_CONFIG_VARIABLE(Type,var,name,val) var (name,val),
-#define CANVAS_VARIABLE(var,name) var (name),
-#define CANVAS_STRING_VARIABLE(var,name) var (name),
 #define CANVAS_FONT_VARIABLE(var,name) var (name),
 #include "ui_config_vars.h"
 #include "canvas_vars.h"
 #undef  UI_CONFIG_VARIABLE
-#undef  CANVAS_VARIABLE
-#undef  CANVAS_STRING_VARIABLE
 #undef  CANVAS_FONT_VARIABLE
-       _dirty (false)
+
+       /* initialize all the base colors using default
+          colors for now. these will be reset when/if
+          we load the UI config file.
+       */
+
+#undef CANVAS_BASE_COLOR
+#define CANVAS_BASE_COLOR(var,name,val) var (name,quantized (val)),
+#include "base_colors.h"
+#undef CANVAS_BASE_COLOR
+
+       _dirty (false),
+       aliases_modified (false),
+       derived_modified (false)
+       
 {
+       _instance = this;
+
+       /* pack all base colors into the configurable color map so that
+          derived colors can use them.
+       */
+         
+#undef CANVAS_BASE_COLOR
+#define CANVAS_BASE_COLOR(var,name,color) configurable_colors.insert (make_pair (name,&var));
+#include "base_colors.h"
+#undef CANVAS_BASE_COLOR
+
+#undef CANVAS_COLOR
+#define CANVAS_COLOR(var,name,base,modifier) relative_colors.insert (make_pair (name, RelativeHSV (base,modifier)));
+#include "colors.h"
+#undef CANVAS_COLOR
+       
+#undef COLOR_ALIAS
+#define COLOR_ALIAS(var,name,alias) color_aliases.insert (make_pair (name,alias));
+#include "color_aliases.h"
+#undef CANVAS_COLOR
+
        load_state();
+
+       ARDOUR_UI_UTILS::ColorsChanged.connect (boost::bind (&UIConfiguration::color_theme_changed, this));
 }
 
 UIConfiguration::~UIConfiguration ()
 {
 }
 
+UIConfiguration::RelativeHSV
+UIConfiguration::color_as_relative_hsv (Color c)
+{
+       HSV variable (c);
+       HSV closest;
+       double shortest_distance = DBL_MAX;
+       string closest_name;
+
+       map<string,ColorVariable<Color>*>::iterator f;
+       std::map<std::string,HSV> palette;
+
+       for (f = configurable_colors.begin(); f != configurable_colors.end(); ++f) {
+               palette.insert (make_pair (f->first, HSV (f->second->get())));
+       }
+
+       for (map<string,HSV>::iterator f = palette.begin(); f != palette.end(); ++f) {
+               
+               double d;
+               HSV fixed (f->second);
+               
+               if (fixed.is_gray() || variable.is_gray()) {
+                       /* at least one is achromatic; HSV::distance() will do
+                        * the right thing
+                        */
+                       d = fixed.distance (variable);
+               } else {
+                       /* chromatic: compare ONLY hue because our task is
+                          to pick the HUE closest and then compute
+                          a modifier. We want to keep the number of 
+                          hues low, and by computing perceptual distance 
+                          we end up finding colors that are to each
+                          other without necessarily be close in hue.
+                       */
+                       d = fabs (variable.h - fixed.h);
+               }
+
+               if (d < shortest_distance) {
+                       closest = fixed;
+                       closest_name = f->first;
+                       shortest_distance = d;
+               }
+       }
+       
+       /* we now know the closest color of the fixed colors to 
+          this variable color. Compute the HSV diff and
+          use it to redefine the variable color in terms of the
+          fixed one.
+       */
+       
+       HSV delta = variable.delta (closest);
+
+       /* quantize hue delta so we don't end up with many subtle hues caused
+        * by original color choices
+        */
+
+       delta.h = hue_width * (round (delta.h/hue_width));
+
+       return RelativeHSV (closest_name, delta);
+}
+
+void
+UIConfiguration::color_theme_changed ()
+{
+       return;
+       
+       map<std::string,RelativeHSV>::iterator current_color;
+
+       /* we need to reset the quantized hues before we start, because
+        * otherwise when we call RelativeHSV::get() in color_compute()
+        * we don't get an answer based on the new base colors, but instead
+        * based on any existing hue quantization.
+        */
+
+       for (current_color = relative_colors.begin(); current_color != relative_colors.end(); ++current_color) {
+               current_color->second.quantized_hue = -1;
+       }
+}
+
 void
 UIConfiguration::map_parameters (boost::function<void (std::string)>& functor)
 {
@@ -87,7 +204,7 @@ UIConfiguration::load_defaults ()
 
                info << string_compose (_("Loading default ui configuration file %1"), rcfile) << endl;
 
-               if (!tree.read (default_ui_config_file_name)) {
+               if (!tree.read (rcfile.c_str())) {
                        error << string_compose(_("cannot read default ui configuration file \"%1\""), rcfile) << endmsg;
                        return -1;
                }
@@ -110,11 +227,11 @@ UIConfiguration::load_state ()
 
        std::string rcfile;
 
-       if ( find_file (ardour_config_search_path(), default_ui_config_file_name, rcfile)) {
+       if (find_file (ardour_config_search_path(), default_ui_config_file_name, rcfile)) {
                XMLTree tree;
                found = true;
 
-               info << string_compose (_("Loading default ui configuration file %1"), rcfile) << endl;
+               info << string_compose (_("Loading default ui configuration file %1"), rcfile) << endmsg;
 
                if (!tree.read (rcfile.c_str())) {
                        error << string_compose(_("cannot read default ui configuration file \"%1\""), rcfile) << endmsg;
@@ -146,10 +263,9 @@ UIConfiguration::load_state ()
                _dirty = false;
        }
 
-       if (!found)
+       if (!found) {
                error << _("could not find any ui configuration file, canvas will look broken.") << endmsg;
-
-       pack_canvasvars();
+       }
 
        return 0;
 }
@@ -187,6 +303,21 @@ UIConfiguration::get_state ()
        root->add_child_nocopy (get_variables ("UI"));
        root->add_child_nocopy (get_variables ("Canvas"));
 
+       if (derived_modified) {
+
+       }
+
+       if (aliases_modified) {
+               XMLNode* parent = new XMLNode (X_("ColorAliases"));
+               for (ColorAliases::const_iterator i = color_aliases.begin(); i != color_aliases.end(); ++i) {
+                       XMLNode* node = new XMLNode (X_("ColorAlias"));
+                       node->add_property (X_("name"), i->first);
+                       node->add_property (X_("alias"), i->second);
+                       parent->add_child_nocopy (*node);
+               }
+               root->add_child_nocopy (*parent);
+       }
+       
        if (_extra_xml) {
                root->add_child_copy (*_extra_xml);
        }
@@ -200,19 +331,15 @@ UIConfiguration::get_variables (std::string which_node)
        XMLNode* node;
        LocaleGuard lg (X_("POSIX"));
 
-       node = new XMLNode(which_node);
+       node = new XMLNode (which_node);
 
 #undef  UI_CONFIG_VARIABLE
-#undef  CANVAS_VARIABLE
+#undef  CANVAS_FONT_VARIABLE
 #define UI_CONFIG_VARIABLE(Type,var,Name,value) if (node->name() == "UI") { var.add_to_node (*node); }
-#define CANVAS_VARIABLE(var,Name) if (node->name() == "Canvas") { var.add_to_node (*node); }
-#define CANVAS_STRING_VARIABLE(var,Name) if (node->name() == "Canvas") { var.add_to_node (*node); }
 #define CANVAS_FONT_VARIABLE(var,Name) if (node->name() == "Canvas") { var.add_to_node (*node); }
 #include "ui_config_vars.h"
 #include "canvas_vars.h"
 #undef  UI_CONFIG_VARIABLE
-#undef  CANVAS_VARIABLE
-#undef  CANVAS_STRING_VARIABLE
 #undef  CANVAS_FONT_VARIABLE
 
        return *node;
@@ -241,26 +368,46 @@ UIConfiguration::set_state (const XMLNode& root, int /*version*/)
                }
        }
 
+       XMLNode* aliases = find_named_node (root, X_("ColorAliases"));
+
+       if (aliases) {
+               load_color_aliases (*aliases);
+       }
+       
        return 0;
 }
 
+void
+UIConfiguration::load_color_aliases (XMLNode const & node)
+{
+       XMLNodeList const nlist = node.children();
+       XMLNodeConstIterator niter;
+       XMLProperty const *name;
+       XMLProperty const *alias;
+       
+       color_aliases.clear ();
+
+       for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
+               if ((*niter)->name() != X_("ColorAlias")) {
+                       continue;
+               }
+               name = (*niter)->property (X_("name"));
+               alias = (*niter)->property (X_("alias"));
+
+               if (name && alias) {
+                       color_aliases.insert (make_pair (name->value(), alias->value()));
+               }
+       }
+}
+
 void
 UIConfiguration::set_variables (const XMLNode& node)
 {
 #undef  UI_CONFIG_VARIABLE
-#undef  CANVAS_VARIABLE
 #define UI_CONFIG_VARIABLE(Type,var,name,val) \
          if (var.set_from_node (node)) { \
                 ParameterChanged (name); \
                 }
-#define CANVAS_VARIABLE(var,name) \
-         if (var.set_from_node (node)) { \
-                ParameterChanged (name); \
-                }
-#define CANVAS_STRING_VARIABLE(var,name)       \
-         if (var.set_from_node (node)) { \
-                ParameterChanged (name); \
-                }
 #define CANVAS_FONT_VARIABLE(var,name) \
          if (var.set_from_node (node)) { \
                 ParameterChanged (name); \
@@ -268,45 +415,152 @@ UIConfiguration::set_variables (const XMLNode& node)
 #include "ui_config_vars.h"
 #include "canvas_vars.h"
 #undef  UI_CONFIG_VARIABLE
-#undef  CANVAS_VARIABLE
-#undef  CANVAS_STRING_VARIABLE
 #undef  CANVAS_FONT_VARIABLE
+
+       /* Reset base colors */
+
+#undef  CANVAS_BASE_COLOR
+#define CANVAS_BASE_COLOR(var,name,val) \
+       var.set_from_node (node);
+#include "base_colors.h"
+#undef CANVAS_BASE_COLOR       
+
 }
 
 void
-UIConfiguration::pack_canvasvars ()
+UIConfiguration::set_dirty ()
 {
-#undef  CANVAS_VARIABLE
-#define CANVAS_VARIABLE(var,name) canvas_colors.insert (std::pair<std::string,ColorVariable<uint32_t>* >(name,&var));
-#define CANVAS_STRING_VARIABLE(var,name) 
-#define CANVAS_FONT_VARIABLE(var,name) 
-#include "canvas_vars.h"
-#undef  CANVAS_VARIABLE
-#undef  CANVAS_STRING_VARIABLE
-#undef  CANVAS_FONT_VARIABLE
+       _dirty = true;
+}
+
+bool
+UIConfiguration::dirty () const
+{
+       return _dirty;
 }
 
-uint32_t
-UIConfiguration::color_by_name (const std::string& name)
+ArdourCanvas::Color
+UIConfiguration::base_color_by_name (const std::string& name) const
 {
-       map<std::string,ColorVariable<uint32_t>* >::iterator i = canvas_colors.find (name);
+       map<std::string,ColorVariable<Color>* >::const_iterator i = configurable_colors.find (name);
+
+       if (i != configurable_colors.end()) {
+               return i->second->get();
+       }
 
-       if (i != canvas_colors.end()) {
+#if 0 // yet unsed experimental style postfix
+       /* Idea: use identical colors but different font/sizes
+        * for variants of the same 'widget'.
+        *
+        * example:
+        *  set_name("mute button");  // in route_ui.cc
+        *  set_name("mute button small"); // in mixer_strip.cc
+        *
+        * ardour3_widget_list.rc:
+        *  widget "*mute button" style:highest "small_button"
+        *  widget "*mute button small" style:highest "very_small_text"
+        *
+        * both use color-schema of defined in
+        *   BUTTON_VARS(MuteButton, "mute button")
+        *
+        * (in this particular example the widgets should be packed
+        * vertically shinking the mixer strip ones are currently not)
+        */
+       const size_t name_len = name.size();
+       const size_t name_sep = name.find(':');
+       for (i = configurable_colors.begin(); i != configurable_colors.end(), name_sep != string::npos; ++i) {
+               const size_t cmp_len = i->first.size();
+               const size_t cmp_sep = i->first.find(':');
+               if (cmp_len >= name_len || cmp_sep == string::npos) continue;
+               if (name.substr(name_sep) != i->first.substr(cmp_sep)) continue;
+               if (name.substr(0, cmp_sep) != i->first.substr(0, cmp_sep)) continue;
                return i->second->get();
        }
+#endif
 
-       // cerr << string_compose (_("Color %1 not found"), name) << endl;
+       cerr << string_compose (_("Base Color %1 not found"), name) << endl;
        return RGBA_TO_UINT (g_random_int()%256,g_random_int()%256,g_random_int()%256,0xff);
 }
 
+ArdourCanvas::Color
+UIConfiguration::color (const std::string& name) const
+{
+       map<string,string>::const_iterator e = color_aliases.find (name);
+
+       if (e != color_aliases.end ()) {
+               map<string,RelativeHSV>::const_iterator rc = relative_colors.find (e->second);
+               if (rc != relative_colors.end()) {
+                       return rc->second.get();
+               }
+       } else {
+               /* not an alias, try directly */
+               map<string,RelativeHSV>::const_iterator rc = relative_colors.find (name);
+               if (rc != relative_colors.end()) {
+                       return rc->second.get();
+               }
+       }
+       
+       cerr << string_compose (_("Color %1 not found"), name) << endl;
+       
+       return rgba_to_color ((g_random_int()%256)/255.0,
+                             (g_random_int()%256)/255.0,
+                             (g_random_int()%256)/255.0,
+                             0xff);
+}
+
+ArdourCanvas::HSV
+UIConfiguration::RelativeHSV::get() const
+{
+       HSV base (UIConfiguration::instance()->base_color_by_name (base_color));
+       
+       /* this operation is a little wierd. because of the way we originally
+        * computed the alpha specification for the modifiers used here
+        * we need to reset base's alpha to zero before adding the modifier.
+        */
+
+       HSV self (base + modifier);
+       
+       if (quantized_hue >= 0.0) {
+               self.h = quantized_hue;
+       }
+       
+       return self;
+}
+
+Color
+UIConfiguration::quantized (Color c) const
+{
+       HSV hsv (c);
+       hsv.h = hue_width * (round (hsv.h/hue_width));
+       return hsv.color ();
+}
+
 void
-UIConfiguration::set_dirty ()
+UIConfiguration::reset_relative (const string& name, const RelativeHSV& rhsv)
 {
-       _dirty = true;
+       RelativeColors::iterator i = relative_colors.find (name);
+
+       if (i == relative_colors.end()) {
+               return;
+       }
+
+       i->second = rhsv;
+       derived_modified = true;
+
+       ARDOUR_UI_UTILS::ColorsChanged (); /* EMIT SIGNAL */
 }
 
-bool
-UIConfiguration::dirty () const
+void
+UIConfiguration::set_alias (string const & name, string const & alias)
 {
-       return _dirty;
+       ColorAliases::iterator i = color_aliases.find (name);
+       if (i == color_aliases.end()) {
+               return;
+       }
+
+       i->second = alias;
+       aliases_modified = true;
+
+       ARDOUR_UI_UTILS::ColorsChanged (); /* EMIT SIGNAL */
 }
+