crossfade hack and slash. removed overlap checks, overlap mode, default length,...
[ardour.git] / gtk2_ardour / utils.cc
index 4c4bcf69ec5efef711b4714827568d469ae94436..aa68519560534977294548c432fc9735dab9ce18 100644 (file)
 
 */
 
+#ifdef WAF_BUILD
+#include "gtk2ardour-config.h"
+#endif
+
 #include <pango/pangoft2.h> // for fontmap resolution control for GnomeCanvas
 #include <pango/pangocairo.h> // for fontmap resolution control for GnomeCanvas
 
 #include <cstdlib>
+#include <clocale>
+#include <cstring>
 #include <cctype>
 #include <fstream>
+#include <list>
 #include <sys/stat.h>
 #include <libart_lgpl/art_misc.h>
 #include <gtkmm/rc.h>
 #include <gtkmm/label.h>
 #include <gtkmm/paned.h>
 #include <gtk/gtkpaned.h>
+#include <boost/algorithm/string.hpp>
 
 #include "pbd/file_utils.h"
 
 #include <gtkmm2ext/utils.h>
-#include "ardour/configuration.h"
 #include "ardour/rc_configuration.h"
 
 #include "ardour/filesystem_paths.h"
 
 #include "ardour_ui.h"
+#include "debug.h"
 #include "public_editor.h"
 #include "keyboard.h"
 #include "utils.h"
 #include "i18n.h"
 #include "rgb_macros.h"
 #include "canvas_impl.h"
+#include "gui_thread.h"
 
 using namespace std;
 using namespace Gtk;
-using namespace sigc;
 using namespace Glib;
 using namespace PBD;
+using Gtkmm2ext::Keyboard;
 
 sigc::signal<void>  DPIReset;
 
-int
-pixel_width (const ustring& str, Pango::FontDescription& font)
-{
-       Label foo;
-       Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout ("");
-
-       layout->set_font_description (font);
-       layout->set_text (str);
-
-       int width, height;
-       Gtkmm2ext::get_ink_pixel_size (layout, width, height);
-       return width;
-}
-
-ustring
-fit_to_pixels (const ustring& str, int pixel_width, Pango::FontDescription& font, int& actual_width, bool with_ellipses)
-{
-       Label foo;
-       Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout ("");
-       ustring::size_type shorter_by = 0;
-       ustring txt;
-
-       layout->set_font_description (font);
-
-       actual_width = 0;
-
-       ustring ustr = str;
-       ustring::iterator last = ustr.end();
-       --last; /* now points at final entry */
-
-       txt = ustr;
-
-       while (!ustr.empty()) {
-
-               layout->set_text (txt);
-
-               int width, height;
-               Gtkmm2ext::get_ink_pixel_size (layout, width, height);
-
-               if (width < pixel_width) {
-                       actual_width = width;
-                       break;
-               }
-
-               ustr.erase (last--);
-               shorter_by++;
-
-               if (with_ellipses && shorter_by > 3) {
-                       txt = ustr;
-                       txt += "...";
-               } else {
-                       txt = ustr;
-               }
-       }
-
-       return txt;
-}
-
-/** Try to fit a string into a given horizontal space by ellipsizing it.
- *  @param cr Cairo context in which the text will be plotted.
- *  @param name Text.
- *  @param avail Available horizontal space.
- *  @return (Text, possibly ellipsized) and (horizontal size of text)
- */
-
-std::pair<std::string, double>
-fit_to_pixels (cairo_t* cr, std::string name, double avail)
-{
-       /* XXX hopefully there exists a more efficient way of doing this */
-
-       bool abbreviated = false;
-       uint32_t width = 0;
-
-       while (1) {
-               cairo_text_extents_t ext;
-               cairo_text_extents (cr, name.c_str(), &ext);
-
-               if (ext.width < avail || name.length() <= 4) {
-                       width = ext.width;
-                       break;
-               }
-
-               if (abbreviated) {
-                       name = name.substr (0, name.length() - 4) + "...";
-               } else {
-                       name = name.substr (0, name.length() - 3) + "...";
-                       abbreviated = true;
-               }
-       }
-
-       return std::make_pair (name, width);
-}
-
 
 /** Add an element to a menu, settings its sensitivity.
  * @param m Menu to add to.
@@ -297,7 +213,7 @@ get_canvas_points (string /*who*/, uint32_t npoints)
        return new ArdourCanvas::Points (npoints);
 }
 
-Pango::FontDescription*
+Pango::FontDescription
 get_font_for_style (string widgetname)
 {
        Gtk::Window window (WINDOW_TOPLEVEL);
@@ -312,18 +228,18 @@ get_font_for_style (string widgetname)
 
        Glib::RefPtr<const Pango::Layout> layout = foobar.get_layout();
 
-       PangoFontDescription *pfd = (PangoFontDescription *)pango_layout_get_font_description((PangoLayout *)layout->gobj());
+       PangoFontDescription *pfd = const_cast<PangoFontDescription *> (pango_layout_get_font_description(const_cast<PangoLayout *>(layout->gobj())));
 
        if (!pfd) {
 
                /* layout inherited its font description from a PangoContext */
 
-               PangoContext* ctxt = (PangoContext*) pango_layout_get_context ((PangoLayout*) layout->gobj());
+               PangoContext* ctxt = (PangoContext*) pango_layout_get_context (const_cast<PangoLayout*>(layout->gobj()));
                pfd =  pango_context_get_font_description (ctxt);
-               return new Pango::FontDescription (pfd, true); /* make a copy */
+               return Pango::FontDescription (pfd); /* make a copy */
        }
 
-       return new Pango::FontDescription (pfd, true); /* make a copy */
+       return Pango::FontDescription (pfd); /* make a copy */
 }
 
 uint32_t
@@ -346,31 +262,31 @@ rgba_from_style (string style, uint32_t r, uint32_t g, uint32_t b, uint32_t a, s
        foo.set_name (style);
        foo.ensure_style ();
 
-       GtkRcStyle* waverc = foo.get_style()->gobj()->rc_style;
+       GtkRcStyle* rc = foo.get_style()->gobj()->rc_style;
 
-       if (waverc) {
+       if (rc) {
                if (attr == "fg") {
-                       r = waverc->fg[state].red / 257;
-                       g = waverc->fg[state].green / 257;
-                       b = waverc->fg[state].blue / 257;
+                       r = rc->fg[state].red / 257;
+                       g = rc->fg[state].green / 257;
+                       b = rc->fg[state].blue / 257;
 
                        /* what a hack ... "a" is for "active" */
                        if (state == Gtk::STATE_NORMAL && rgba) {
-                               a = waverc->fg[GTK_STATE_ACTIVE].red / 257;
+                               a = rc->fg[GTK_STATE_ACTIVE].red / 257;
                        }
                } else if (attr == "bg") {
                        r = g = b = 0;
-                       r = waverc->bg[state].red / 257;
-                       g = waverc->bg[state].green / 257;
-                       b = waverc->bg[state].blue / 257;
+                       r = rc->bg[state].red / 257;
+                       g = rc->bg[state].green / 257;
+                       b = rc->bg[state].blue / 257;
                } else if (attr == "base") {
-                       r = waverc->base[state].red / 257;
-                       g = waverc->base[state].green / 257;
-                       b = waverc->base[state].blue / 257;
+                       r = rc->base[state].red / 257;
+                       g = rc->base[state].green / 257;
+                       b = rc->base[state].blue / 257;
                } else if (attr == "text") {
-                       r = waverc->text[state].red / 257;
-                       g = waverc->text[state].green / 257;
-                       b = waverc->text[state].blue / 257;
+                       r = rc->text[state].red / 257;
+                       g = rc->text[state].green / 257;
+                       b = rc->text[state].blue / 257;
                }
        } else {
                warning << string_compose (_("missing RGBA style for \"%1\""), style) << endl;
@@ -385,112 +301,6 @@ rgba_from_style (string style, uint32_t r, uint32_t g, uint32_t b, uint32_t a, s
        }
 }
 
-
-Gdk::Color
-color_from_style (string widget_style_name, int state, string attr)
-{
-       GtkStyle* style;
-
-       style = gtk_rc_get_style_by_paths (gtk_settings_get_default(),
-                                          widget_style_name.c_str(),
-                                          0, G_TYPE_NONE);
-
-       if (!style) {
-               error << string_compose (_("no style found for %1, using red"), style) << endmsg;
-               return Gdk::Color ("red");
-       }
-
-       if (attr == "fg") {
-               return Gdk::Color (&style->fg[state]);
-       }
-
-       if (attr == "bg") {
-               return Gdk::Color (&style->bg[state]);
-       }
-
-       if (attr == "light") {
-               return Gdk::Color (&style->light[state]);
-       }
-
-       if (attr == "dark") {
-               return Gdk::Color (&style->dark[state]);
-       }
-
-       if (attr == "mid") {
-               return Gdk::Color (&style->mid[state]);
-       }
-
-       if (attr == "text") {
-               return Gdk::Color (&style->text[state]);
-       }
-
-       if (attr == "base") {
-               return Gdk::Color (&style->base[state]);
-       }
-
-       if (attr == "text_aa") {
-               return Gdk::Color (&style->text_aa[state]);
-       }
-
-       error << string_compose (_("unknown style attribute %1 requested for color; using \"red\""), attr) << endmsg;
-       return Gdk::Color ("red");
-}
-
-Glib::RefPtr<Gdk::GC>
-gc_from_style (string widget_style_name, int state, string attr)
-{
-        GtkStyle* style;
-
-        style = gtk_rc_get_style_by_paths (gtk_settings_get_default(),
-                                           widget_style_name.c_str(),
-                                           0, G_TYPE_NONE);
-
-        if (!style) {
-                error << string_compose (_("no style found for %1, using red"), style) << endmsg;
-               Glib::RefPtr<Gdk::GC> ret = Gdk::GC::create();
-               ret->set_rgb_fg_color(Gdk::Color("red"));
-                return ret;
-        }
-
-        if (attr == "fg") {
-                return Glib::wrap(style->fg_gc[state]);
-        }
-
-        if (attr == "bg") {
-                return Glib::wrap(style->bg_gc[state]);
-        }
-
-        if (attr == "light") {
-                return Glib::wrap(style->light_gc[state]);
-        }
-
-        if (attr == "dark") {
-                return Glib::wrap(style->dark_gc[state]);
-        }
-
-        if (attr == "mid") {
-                return Glib::wrap(style->mid_gc[state]);
-        }
-
-        if (attr == "text") {
-                return Glib::wrap(style->text_gc[state]);
-        }
-
-        if (attr == "base") {
-                return Glib::wrap(style->base_gc[state]);
-        }
-
-        if (attr == "text_aa") {
-                return Glib::wrap(style->text_aa_gc[state]);
-        }
-
-        error << string_compose (_("unknown style attribute %1 requested for color; using \"red\""), attr) << endmsg;
-       Glib::RefPtr<Gdk::GC> ret = Gdk::GC::create();
-       ret->set_rgb_fg_color(Gdk::Color("red"));
-        return ret;
-}
-
-
 bool
 canvas_item_visible (ArdourCanvas::Item* item)
 {
@@ -503,12 +313,6 @@ set_color (Gdk::Color& c, int rgb)
        c.set_rgb((rgb >> 16)*256, ((rgb & 0xff00) >> 8)*256, (rgb & 0xff)*256);
 }
 
-#ifdef GTKOSX
-extern "C" {
-       gboolean gdk_quartz_possibly_forward (GdkEvent*);
-}
-#endif
-
 bool
 relay_key_press (GdkEventKey* ev, Gtk::Window* win)
 {
@@ -519,6 +323,12 @@ relay_key_press (GdkEventKey* ev, Gtk::Window* win)
        }
 }
 
+bool
+forward_key_press (GdkEventKey* ev)
+{
+        return PublicEditor::instance().on_key_press_event(ev);
+}
+
 bool
 key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev)
 {
@@ -526,12 +336,9 @@ key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev)
        GtkWidget* focus = gtk_window_get_focus (win);
        bool special_handling_of_unmodified_accelerators = false;
        bool allow_activating = true;
+       /* consider all relevant modifiers but not LOCK or SHIFT */
+       const guint mask = (Keyboard::RelevantModifierKeyMask & ~(Gdk::SHIFT_MASK|Gdk::LOCK_MASK));
 
-#undef DEBUG_ACCELERATOR_HANDLING
-#ifdef  DEBUG_ACCELERATOR_HANDLING
-       //bool debug = (getenv ("ARDOUR_DEBUG_ACCELERATOR_HANDLING") != 0);
-       bool debug=true;
-#endif
        if (focus) {
                if (GTK_IS_ENTRY(focus) || Keyboard::some_magic_widget_has_focus()) {
                        special_handling_of_unmodified_accelerators = true;
@@ -539,24 +346,27 @@ key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev)
        }
 
 #ifdef GTKOSX
-       /* should this be universally true? */
+        /* at one time this appeared to be necessary. As of July 2012, it does not
+           appear to be. if it ever is necessar, figure out if it should apply
+           to all platforms.
+        */
+#if 0 
        if (Keyboard::some_magic_widget_has_focus ()) {
-               allow_activating = false;
+                allow_activating = false;
        }
 #endif
-
-#ifdef DEBUG_ACCELERATOR_HANDLING
-       if (debug) {
-               cerr << "Win = " << win << " Key event: code = " << ev->keyval << " state = " << hex << ev->state << dec << " special handling ? "
-                    << special_handling_of_unmodified_accelerators
-                    << " magic widget focus ? "
-                    << Keyboard::some_magic_widget_has_focus()
-                    << " allow_activation ? "
-                    << allow_activating
-                    << endl;
-       }
 #endif
 
+
+        DEBUG_TRACE (DEBUG::Accelerators, string_compose ("Win = %1 focus = %7 Key event: code = %2  state = %3 special handling ? %4 magic widget focus ? %5 allow_activation ? %6\n",
+                                                          win,
+                                                          ev->keyval,
+                                                          ev->state,
+                                                          special_handling_of_unmodified_accelerators,
+                                                          Keyboard::some_magic_widget_has_focus(),
+                                                          allow_activating,
+                                                         focus));
+
        /* This exists to allow us to override the way GTK handles
           key events. The normal sequence is:
 
@@ -588,105 +398,103 @@ key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev)
           all "normal text" accelerators.
        */
 
-
        if (!special_handling_of_unmodified_accelerators) {
 
+               /* XXX note that for a brief moment, the conditional above
+                * included "|| (ev->state & mask)" so as to enforce the
+                * implication of special_handling_of_UNMODIFIED_accelerators.
+                * however, this forces any key that GTK doesn't allow and that
+                * we have an alternative (see next comment) for to be
+                * automatically sent through the accel groups activation
+                * pathway, which prevents individual widgets & canvas items
+                * from ever seeing it if is used by a key binding.
+                * 
+                * specifically, this hid Ctrl-down-arrow from MIDI region
+                * views because it is also bound to an action.
+                *
+                * until we have a robust, clean binding system, this
+                * quirk will have to remain in place.
+                */
+
                /* pretend that certain key events that GTK does not allow
                   to be used as accelerators are actually something that
-                  it does allow.
+                  it does allow. but only where there are no modifiers.
                */
 
                uint32_t fakekey = ev->keyval;
 
-               if (possibly_translate_keyval_to_make_legal_accelerator (fakekey)) {
-                       if (allow_activating && gtk_accel_groups_activate(G_OBJECT(win), fakekey, GdkModifierType(ev->state))) {
-                               return true;
-                       }
+               if (Gtkmm2ext::possibly_translate_keyval_to_make_legal_accelerator (fakekey)) {
+                       DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tactivate (was %1 now %2) without special hanlding of unmodified accels\n",
+                                                                         ev->keyval, fakekey));
+
+                       GdkModifierType mod = GdkModifierType (ev->state);
 
+                       mod = GdkModifierType (mod & gtk_accelerator_get_default_mod_mask());
 #ifdef GTKOSX
-                       if (allow_activating) {
-                               int oldval = ev->keyval;
-                               ev->keyval = fakekey;
-                               if (gdk_quartz_possibly_forward ((GdkEvent*) ev)) {
-                                       return true;
-                               }
-                               ev->keyval = oldval;
+                       /* GTK on OS X is currently (February 2012) setting both
+                          the Meta and Mod2 bits in the event modifier state if 
+                          the Command key is down.
+
+                          gtk_accel_groups_activate() does not invoke any of the logic
+                          that gtk_window_activate_key() will that sorts out that stupid
+                          state of affairs, and as a result it fails to find a match
+                          for the key event and the current set of accelerators.
+
+                          to fix this, if the meta bit is set, remove the mod2 bit
+                          from the modifier. this assumes that our bindings use Primary
+                          which will have set the meta bit in the accelerator entry.
+                       */
+                       if (mod & GDK_META_MASK) {
+                               mod = GdkModifierType (mod & ~GDK_MOD2_MASK);
                        }
 #endif
+
+                       if (allow_activating && gtk_accel_groups_activate(G_OBJECT(win), fakekey, mod)) {
+                               DEBUG_TRACE (DEBUG::Accelerators, "\taccel group activated by fakekey\n");
+                               return true;
+                       }
                }
        }
 
-       /* consider all relevant modifiers but not LOCK or SHIFT */
-
-       guint mask = (Keyboard::RelevantModifierKeyMask & ~(Gdk::SHIFT_MASK|Gdk::LOCK_MASK));
-
        if (!special_handling_of_unmodified_accelerators || (ev->state & mask)) {
 
                /* no special handling or there are modifiers in effect: accelerate first */
 
-#ifdef DEBUG_ACCELERATOR_HANDLING
-               if (debug) {
-                       cerr << "\tactivate, then propagate\n";
-               }
-#endif
+                DEBUG_TRACE (DEBUG::Accelerators, "\tactivate, then propagate\n");
 
                if (allow_activating) {
-#ifdef GTKOSX
-                       if (gdk_quartz_possibly_forward ((GdkEvent*) ev)) {
-                               return true;
-                       }
-#endif
+                       DEBUG_TRACE (DEBUG::Accelerators, "\tsending to window\n");
                        if (gtk_window_activate_key (win, ev)) {
+                               DEBUG_TRACE (DEBUG::Accelerators, "\t\thandled\n");
                                return true;
                        }
+               } else {
+                       DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
                }
 
-#ifdef DEBUG_ACCELERATOR_HANDLING
-               if (debug) {
-                       cerr << "\tnot accelerated, now propagate\n";
-               }
-#endif
+                DEBUG_TRACE (DEBUG::Accelerators, "\tnot accelerated, now propagate\n");
+
                return gtk_window_propagate_key_event (win, ev);
        }
 
        /* no modifiers, propagate first */
 
-#ifdef DEBUG_ACCELERATOR_HANDLING
-       if (debug) {
-               cerr << "\tpropagate, then activate\n";
-       }
-#endif
-       if (!gtk_window_propagate_key_event (win, ev)) {
-#ifdef DEBUG_ACCELERATOR_HANDLING
-               if (debug) {
-                       cerr << "\tpropagation didn't handle, so activate\n";
-               }
-#endif
+        DEBUG_TRACE (DEBUG::Accelerators, "\tpropagate, then activate\n");
 
+       if (!gtk_window_propagate_key_event (win, ev)) {
+                DEBUG_TRACE (DEBUG::Accelerators, "\tpropagation didn't handle, so activate\n");
                if (allow_activating) {
-
-#ifdef GTKOSX
-                       if (gdk_quartz_possibly_forward ((GdkEvent*) ev)) {
-                               return true;
-                       }
-#endif
                        return gtk_window_activate_key (win, ev);
+               } else {
+                       DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
                }
 
        } else {
-#ifdef DEBUG_ACCELERATOR_HANDLING
-               if (debug) {
-                       cerr << "\thandled by propagate\n";
-               }
-#endif
+                DEBUG_TRACE (DEBUG::Accelerators, "\thandled by propagate\n");
                return true;
        }
 
-#ifdef DEBUG_ACCELERATOR_HANDLING
-       if (debug) {
-               cerr << "\tnot handled\n";
-       }
-#endif
+        DEBUG_TRACE (DEBUG::Accelerators, "\tnot handled\n");
        return true;
 }
 
@@ -695,19 +503,18 @@ get_xpm (std::string name)
 {
        if (!xpm_map[name]) {
 
-               SearchPath spath(ARDOUR::ardour_search_path());
-               spath += ARDOUR::system_data_search_path();
+               SearchPath spath(ARDOUR::ardour_data_search_path());
 
                spath.add_subdirectory_to_paths("pixmaps");
 
-               sys::path data_file_path;
+               std::string data_file_path;
 
                if(!find_file_in_search_path (spath, name, data_file_path)) {
                        fatal << string_compose (_("cannot find XPM file for %1"), name) << endmsg;
                }
 
                try {
-                       xpm_map[name] =  Gdk::Pixbuf::create_from_file (data_file_path.to_string());
+                       xpm_map[name] =  Gdk::Pixbuf::create_from_file (data_file_path);
                } catch(const Glib::Error& e)   {
                        warning << "Caught Glib::Error: " << e.what() << endmsg;
                }
@@ -716,24 +523,23 @@ get_xpm (std::string name)
        return xpm_map[name];
 }
 
-Glib::ustring
+std::string
 get_icon_path (const char* cname)
 {
        string name = cname;
        name += X_(".png");
 
-       SearchPath spath(ARDOUR::ardour_search_path());
-       spath += ARDOUR::system_data_search_path();
+       SearchPath spath(ARDOUR::ardour_data_search_path());
 
        spath.add_subdirectory_to_paths("icons");
 
-       sys::path data_file_path;
+       std::string data_file_path;
 
        if (!find_file_in_search_path (spath, name, data_file_path)) {
-               fatal << string_compose (_("cannot find icon image for %1"), name) << endmsg;
+               fatal << string_compose (_("cannot find icon image for %1 using %2"), name, spath.to_string()) << endmsg;
        }
 
-       return data_file_path.to_string();
+       return data_file_path;
 }
 
 Glib::RefPtr<Gdk::Pixbuf>
@@ -745,7 +551,7 @@ get_icon (const char* cname)
        } catch (const Gdk::PixbufError &e) {
                cerr << "Caught PixbufError: " << e.what() << endl;
        } catch (...) {
-               g_message("Caught ... ");
+               error << string_compose (_("Caught exception while loading icon named %1"), cname) << endmsg;
        }
 
        return img;
@@ -782,11 +588,48 @@ longest (vector<string>& strings)
 bool
 key_is_legal_for_numeric_entry (guint keyval)
 {
+       /* we assume that this does not change over the life of the process 
+        */
+
+       static int comma_decimal = -1;
+
        switch (keyval) {
-       case GDK_minus:
-       case GDK_plus:
        case GDK_period:
        case GDK_comma:
+               if (comma_decimal < 0) {
+                       std::lconv* lc = std::localeconv();
+                       if (strchr (lc->decimal_point, ',') != 0) {
+                               comma_decimal = 1;
+                       } else {
+                               comma_decimal = 0;
+                       }
+               }
+               break;
+       default:
+               break;
+       }
+
+       switch (keyval) {
+       case GDK_decimalpoint:
+       case GDK_KP_Separator:
+               return true;
+
+       case GDK_period:
+               if (comma_decimal) {
+                       return false;
+               } else {
+                       return true;
+               }
+               break;
+       case GDK_comma:
+               if (comma_decimal) {
+                       return true;
+               } else {
+                       return false;
+               }
+               break;
+       case GDK_minus:
+       case GDK_plus:
        case GDK_0:
        case GDK_1:
        case GDK_2:
@@ -852,87 +695,84 @@ reset_dpi ()
        DPIReset();//Emit Signal
 }
 
-bool
-possibly_translate_keyval_to_make_legal_accelerator (uint32_t& keyval)
+void
+resize_window_to_proportion_of_monitor (Gtk::Window* window, int max_width, int max_height)
 {
-       int fakekey = GDK_VoidSymbol;
-
-       switch (keyval) {
-       case GDK_Tab:
-       case GDK_ISO_Left_Tab:
-               fakekey = GDK_nabla;
-               break;
-
-       case GDK_Up:
-               fakekey = GDK_uparrow;
-               break;
+       Glib::RefPtr<Gdk::Screen> screen = window->get_screen ();
+       Gdk::Rectangle monitor_rect;
+       screen->get_monitor_geometry (0, monitor_rect);
 
-       case GDK_Down:
-               fakekey = GDK_downarrow;
-               break;
+       int const w = std::min (int (monitor_rect.get_width() * 0.8), max_width);
+       int const h = std::min (int (monitor_rect.get_height() * 0.8), max_height);
 
-       case GDK_Right:
-               fakekey = GDK_rightarrow;
-               break;
+       window->resize (w, h);
+}
 
-       case GDK_Left:
-               fakekey = GDK_leftarrow;
-               break;
 
-       default:
-               break;
-       }
+/** Replace _ with __ in a string; for use with menu item text to make underscores displayed correctly */
+string
+escape_underscores (string const & s)
+{
+       string o;
+       string::size_type const N = s.length ();
 
-       if (fakekey != GDK_VoidSymbol) {
-               keyval = fakekey;
-               return true;
+       for (string::size_type i = 0; i < N; ++i) {
+               if (s[i] == '_') {
+                       o += "__";
+               } else {
+                       o += s[i];
+               }
        }
 
-       return false;
+       return o;
 }
 
-
-inline guint8
-convert_color_channel (guint8 src,
-                      guint8 alpha)
+/** Replace < and > with &lt; and &gt; respectively to make < > display correctly in markup strings */
+string
+escape_angled_brackets (string const & s)
 {
-       return alpha ? ((guint (src) << 8) - src) / alpha : 0;
+       string o = s;
+       boost::replace_all (o, "<", "&lt;");
+       boost::replace_all (o, ">", "&gt;");
+       return o;
 }
 
-void
-convert_bgra_to_rgba (guint8 const* src,
-                     guint8*       dst,
-                     int           width,
-                     int           height)
+Gdk::Color
+unique_random_color (list<Gdk::Color>& used_colors)
 {
-       guint8 const* src_pixel = src;
-       guint8*       dst_pixel = dst;
-
-       for (int y = 0; y < height; y++)
-               for (int x = 0; x < width; x++)
-               {
-                       dst_pixel[0] = convert_color_channel (src_pixel[2],
-                                                             src_pixel[3]);
-                       dst_pixel[1] = convert_color_channel (src_pixel[1],
-                                                             src_pixel[3]);
-                       dst_pixel[2] = convert_color_channel (src_pixel[0],
-                                                             src_pixel[3]);
-                       dst_pixel[3] = src_pixel[3];
-
-                       dst_pixel += 4;
-                       src_pixel += 4;
+       Gdk::Color newcolor;
+
+       while (1) {
+
+               /* avoid neon/glowing tones by limiting them to the
+                  "inner section" (paler) of a color wheel/circle.
+               */
+
+               const int32_t max_saturation = 48000; // 65535 would open up the whole color wheel
+
+               newcolor.set_red (random() % max_saturation);
+               newcolor.set_blue (random() % max_saturation);
+               newcolor.set_green (random() % max_saturation);
+
+               if (used_colors.size() == 0) {
+                       used_colors.push_back (newcolor);
+                       return newcolor;
                }
-}
 
-void
-resize_window_to_proportion_of_monitor (Gtk::Window* window, int max_width, int max_height)
-{
-       Glib::RefPtr<Gdk::Screen> screen = window->get_screen ();
-       Gdk::Rectangle monitor_rect;
-       screen->get_monitor_geometry (0, monitor_rect);
+               for (list<Gdk::Color>::iterator i = used_colors.begin(); i != used_colors.end(); ++i) {
+                 Gdk::Color c = *i;
+                       float rdelta, bdelta, gdelta;
 
-       int const w = std::min (monitor_rect.get_width(), max_width) * 0.8;
-       int const h = std::min (monitor_rect.get_height(), max_height) * 0.8;
+                       rdelta = newcolor.get_red() - c.get_red();
+                       bdelta = newcolor.get_blue() - c.get_blue();
+                       gdelta = newcolor.get_green() - c.get_green();
 
-       window->resize (w, h);
+                       if (sqrt (rdelta*rdelta + bdelta*bdelta + gdelta*gdelta) > 25.0) {
+                               used_colors.push_back (newcolor);
+                               return newcolor;
+                       }
+               }
+
+               /* XXX need throttle here to make sure we don't spin for ever */
+       }
 }