2 Copyright (C) 2003 Paul Davis
4 This program is free software; you an 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.
21 #include "gtk2ardour-config.h"
24 #include <pango/pangoft2.h> // for fontmap resolution control for GnomeCanvas
25 #include <pango/pangocairo.h> // for fontmap resolution control for GnomeCanvas
36 #include <gtkmm/window.h>
37 #include <gtkmm/combo.h>
38 #include <gtkmm/label.h>
39 #include <gtkmm/paned.h>
40 #include <gtk/gtkpaned.h>
41 #include <boost/algorithm/string.hpp>
43 #include "pbd/file_utils.h"
45 #include <gtkmm2ext/utils.h>
46 #include "ardour/rc_configuration.h"
47 #include "ardour/filesystem_paths.h"
49 #include "canvas/item.h"
50 #include "canvas/utils.h"
52 #include "ardour_ui.h"
54 #include "public_editor.h"
58 #include "rgb_macros.h"
59 #include "gui_thread.h"
65 using Gtkmm2ext::Keyboard;
67 namespace ARDOUR_UI_UTILS {
68 sigc::signal<void> DPIReset;
71 #ifdef PLATFORM_WINDOWS
72 #define random() rand()
76 /** Add an element to a menu, settings its sensitivity.
77 * @param m Menu to add to.
78 * @param e Element to add.
79 * @param s true to make sensitive, false to make insensitive
82 ARDOUR_UI_UTILS::add_item_with_sensitivity (Menu_Helpers::MenuList& m, Menu_Helpers::MenuElem e, bool s)
86 m.back().set_sensitive (false);
92 ARDOUR_UI_UTILS::just_hide_it (GdkEventAny */*ev*/, Gtk::Window *win)
98 /* xpm2rgb copied from nixieclock, which bore the legend:
100 nixieclock - a nixie desktop timepiece
101 Copyright (C) 2000 Greg Ercolano, erco@3dsite.com
103 and was released under the GPL.
107 ARDOUR_UI_UTILS::xpm2rgb (const char** xpm, uint32_t& w, uint32_t& h)
109 static long vals[256], val;
110 uint32_t t, x, y, colors, cpp;
112 unsigned char *savergb, *rgb;
116 if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
117 error << string_compose (_("bad XPM header %1"), xpm[0])
122 savergb = rgb = (unsigned char*) malloc (h * w * 3);
124 // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
125 for (t = 0; t < colors; ++t) {
126 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
130 // COLORMAP -> RGB CONVERSION
131 // Get low 3 bytes from vals[]
135 for (y = h-1; y > 0; --y) {
137 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 3) {
138 val = vals[(int)*p++];
139 *(rgb+2) = val & 0xff; val >>= 8; // 2:B
140 *(rgb+1) = val & 0xff; val >>= 8; // 1:G
141 *(rgb+0) = val & 0xff; // 0:R
149 ARDOUR_UI_UTILS::xpm2rgba (const char** xpm, uint32_t& w, uint32_t& h)
151 static long vals[256], val;
152 uint32_t t, x, y, colors, cpp;
154 unsigned char *savergb, *rgb;
159 if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
160 error << string_compose (_("bad XPM header %1"), xpm[0])
165 savergb = rgb = (unsigned char*) malloc (h * w * 4);
167 // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
169 if (strstr (xpm[1], "None")) {
170 sscanf (xpm[1], "%c", &transparent);
177 for (; t < colors; ++t) {
178 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
182 // COLORMAP -> RGB CONVERSION
183 // Get low 3 bytes from vals[]
187 for (y = h-1; y > 0; --y) {
191 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 4) {
193 if (transparent && (*p++ == transparent)) {
201 *(rgb+3) = alpha; // 3: alpha
202 *(rgb+2) = val & 0xff; val >>= 8; // 2:B
203 *(rgb+1) = val & 0xff; val >>= 8; // 1:G
204 *(rgb+0) = val & 0xff; // 0:R
211 /** Returns a Pango::FontDescription given a string describing the font.
213 * If the returned FontDescription does not specify a family, then
214 * the family is set to "Sans". This mirrors GTK's behaviour in
217 * Some environments will force Pango to specify the family
218 * even if it was not specified in the string describing the font.
219 * Such environments should be left unaffected by this function,
220 * since the font family will be left alone.
222 * There may be other similar font specification enforcement
223 * that we might add here later.
225 Pango::FontDescription
226 ARDOUR_UI_UTILS::sanitized_font (std::string const& name)
228 Pango::FontDescription fd (name);
230 if (fd.get_family().empty()) {
231 fd.set_family ("Sans");
237 Pango::FontDescription
238 ARDOUR_UI_UTILS::get_font_for_style (string widgetname)
240 Gtk::Window window (WINDOW_TOPLEVEL);
242 Glib::RefPtr<Gtk::Style> style;
245 foobar.set_name (widgetname);
246 foobar.ensure_style();
248 style = foobar.get_style ();
250 Glib::RefPtr<const Pango::Layout> layout = foobar.get_layout();
252 PangoFontDescription *pfd = const_cast<PangoFontDescription *> (pango_layout_get_font_description(const_cast<PangoLayout *>(layout->gobj())));
256 /* layout inherited its font description from a PangoContext */
258 PangoContext* ctxt = (PangoContext*) pango_layout_get_context (const_cast<PangoLayout*>(layout->gobj()));
259 pfd = pango_context_get_font_description (ctxt);
260 return Pango::FontDescription (pfd); /* make a copy */
263 return Pango::FontDescription (pfd); /* make a copy */
267 ARDOUR_UI_UTILS::rgba_from_style (string style, uint32_t r, uint32_t g, uint32_t b, uint32_t a, string attr, int state, bool rgba)
269 /* In GTK+2, styles aren't set up correctly if the widget is not
270 attached to a toplevel window that has a screen pointer.
273 static Gtk::Window* window = 0;
276 window = new Window (WINDOW_TOPLEVEL);
283 foo.set_name (style);
286 GtkRcStyle* rc = foo.get_style()->gobj()->rc_style;
290 r = rc->fg[state].red / 257;
291 g = rc->fg[state].green / 257;
292 b = rc->fg[state].blue / 257;
294 /* what a hack ... "a" is for "active" */
295 if (state == Gtk::STATE_NORMAL && rgba) {
296 a = rc->fg[GTK_STATE_ACTIVE].red / 257;
298 } else if (attr == "bg") {
300 r = rc->bg[state].red / 257;
301 g = rc->bg[state].green / 257;
302 b = rc->bg[state].blue / 257;
303 } else if (attr == "base") {
304 r = rc->base[state].red / 257;
305 g = rc->base[state].green / 257;
306 b = rc->base[state].blue / 257;
307 } else if (attr == "text") {
308 r = rc->text[state].red / 257;
309 g = rc->text[state].green / 257;
310 b = rc->text[state].blue / 257;
313 warning << string_compose (_("missing RGBA style for \"%1\""), style) << endl;
318 if (state == Gtk::STATE_NORMAL && rgba) {
319 return (uint32_t) RGBA_TO_UINT(r,g,b,a);
321 return (uint32_t) RGBA_TO_UINT(r,g,b,255);
326 ARDOUR_UI_UTILS::rgba_p_from_style (string style, float *r, float *g, float *b, string attr, int state)
328 static Gtk::Window* window = 0;
329 assert (r && g && b);
332 window = new Window (WINDOW_TOPLEVEL);
339 foo.set_name (style);
342 GtkRcStyle* rc = foo.get_style()->gobj()->rc_style;
345 warning << string_compose (_("missing RGBA style for \"%1\""), style) << endl;
349 *r = rc->fg[state].red / 65535.0;
350 *g = rc->fg[state].green / 65535.0;
351 *b = rc->fg[state].blue / 65535.0;
352 } else if (attr == "bg") {
353 *r = rc->bg[state].red / 65535.0;
354 *g = rc->bg[state].green / 65535.0;
355 *b = rc->bg[state].blue / 65535.0;
356 } else if (attr == "base") {
357 *r = rc->base[state].red / 65535.0;
358 *g = rc->base[state].green / 65535.0;
359 *b = rc->base[state].blue / 65535.0;
360 } else if (attr == "text") {
361 *r = rc->text[state].red / 65535.0;
362 *g = rc->text[state].green / 65535.0;
363 *b = rc->text[state].blue / 65535.0;
373 ARDOUR_UI_UTILS::set_color_from_rgb (Gdk::Color& c, uint32_t rgb)
375 /* Gdk::Color color ranges are 16 bit, so scale from 8 bit by
378 c.set_rgb ((rgb >> 16)*256, ((rgb & 0xff00) >> 8)*256, (rgb & 0xff)*256);
382 ARDOUR_UI_UTILS::set_color_from_rgba (Gdk::Color& c, uint32_t rgba)
384 /* Gdk::Color color ranges are 16 bit, so scale from 8 bit by
387 c.set_rgb ((rgba >> 24)*256, ((rgba & 0xff0000) >> 16)*256, ((rgba & 0xff00) >> 8)*256);
391 ARDOUR_UI_UTILS::gdk_color_to_rgba (Gdk::Color const& c)
393 /* since alpha value is not available from a Gdk::Color, it is
394 hardcoded as 0xff (aka 255 or 1.0)
397 const uint32_t r = c.get_red_p () * 255.0;
398 const uint32_t g = c.get_green_p () * 255.0;
399 const uint32_t b = c.get_blue_p () * 255.0;
400 const uint32_t a = 0xff;
402 return RGBA_TO_UINT (r,g,b,a);
406 ARDOUR_UI_UTILS::contrasting_text_color (uint32_t c)
409 ArdourCanvas::color_to_rgba (c, r, g, b, a);
411 const double black_r = 0.0;
412 const double black_g = 0.0;
413 const double black_b = 0.0;
415 const double white_r = 1.0;
416 const double white_g = 1.0;
417 const double white_b = 1.0;
419 /* Use W3C contrast guideline calculation */
421 double white_contrast = (max (r, white_r) - min (r, white_r)) +
422 (max (g, white_g) - min (g, white_g)) +
423 (max (b, white_b) - min (b, white_b));
425 double black_contrast = (max (r, black_r) - min (r, black_r)) +
426 (max (g, black_g) - min (g, black_g)) +
427 (max (b, black_b) - min (b, black_b));
429 if (white_contrast > black_contrast) {
431 return ArdourCanvas::rgba_to_color (1.0, 1.0, 1.0, 1.0);
434 return ArdourCanvas::rgba_to_color (0.0, 0.0, 0.0, 1.0);
439 ARDOUR_UI_UTILS::relay_key_press (GdkEventKey* ev, Gtk::Window* win)
441 PublicEditor& ed (PublicEditor::instance());
443 if (!key_press_focus_accelerator_handler (*win, ev)) {
445 /* early key press in pre-main-window-dialogs, no editor yet */
448 return ed.on_key_press_event(ev);
455 ARDOUR_UI_UTILS::forward_key_press (GdkEventKey* ev)
457 return PublicEditor::instance().on_key_press_event(ev);
461 ARDOUR_UI_UTILS::emulate_key_event (Gtk::Widget* w, unsigned int keyval)
463 GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET(w->gobj()));
464 GdkKeymap *keymap = gdk_keymap_get_for_display (display);
465 GdkKeymapKey *keymapkey = NULL;
468 if (!gdk_keymap_get_entries_for_keyval(keymap, keyval, &keymapkey, &n_keys)) return false;
469 if (n_keys !=1) { g_free(keymapkey); return false;}
472 ev.type = GDK_KEY_PRESS;
473 ev.window = gtk_widget_get_window(GTK_WIDGET(w->gobj()));
474 ev.send_event = FALSE;
479 ev.string = const_cast<gchar*> ("");
480 ev.hardware_keycode = keymapkey[0].keycode;
481 ev.group = keymapkey[0].group;
484 forward_key_press(&ev);
485 ev.type = GDK_KEY_RELEASE;
486 return forward_key_press(&ev);
490 ARDOUR_UI_UTILS::key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev)
492 GtkWindow* win = window.gobj();
493 GtkWidget* focus = gtk_window_get_focus (win);
494 bool special_handling_of_unmodified_accelerators = false;
495 bool allow_activating = true;
496 /* consider all relevant modifiers but not LOCK or SHIFT */
497 const guint mask = (Keyboard::RelevantModifierKeyMask & ~(Gdk::SHIFT_MASK|Gdk::LOCK_MASK));
500 if (GTK_IS_ENTRY(focus) || Keyboard::some_magic_widget_has_focus()) {
501 special_handling_of_unmodified_accelerators = true;
506 /* at one time this appeared to be necessary. As of July 2012, it does not
507 appear to be. if it ever is necessar, figure out if it should apply
511 if (Keyboard::some_magic_widget_has_focus ()) {
512 allow_activating = false;
518 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",
522 special_handling_of_unmodified_accelerators,
523 Keyboard::some_magic_widget_has_focus(),
527 /* This exists to allow us to override the way GTK handles
528 key events. The normal sequence is:
530 a) event is delivered to a GtkWindow
531 b) accelerators/mnemonics are activated
532 c) if (b) didn't handle the event, propagate to
533 the focus widget and/or focus chain
535 The problem with this is that if the accelerators include
536 keys without modifiers, such as the space bar or the
537 letter "e", then pressing the key while typing into
538 a text entry widget results in the accelerator being
539 activated, instead of the desired letter appearing
542 There is no good way of fixing this, but this
543 represents a compromise. The idea is that
544 key events involving modifiers (not Shift)
545 get routed into the activation pathway first, then
546 get propagated to the focus widget if necessary.
548 If the key event doesn't involve modifiers,
549 we deliver to the focus widget first, thus allowing
550 it to get "normal text" without interference
553 Of course, this can also be problematic: if there
554 is a widget with focus, then it will swallow
555 all "normal text" accelerators.
558 if (!special_handling_of_unmodified_accelerators) {
560 /* XXX note that for a brief moment, the conditional above
561 * included "|| (ev->state & mask)" so as to enforce the
562 * implication of special_handling_of_UNMODIFIED_accelerators.
563 * however, this forces any key that GTK doesn't allow and that
564 * we have an alternative (see next comment) for to be
565 * automatically sent through the accel groups activation
566 * pathway, which prevents individual widgets & canvas items
567 * from ever seeing it if is used by a key binding.
569 * specifically, this hid Ctrl-down-arrow from MIDI region
570 * views because it is also bound to an action.
572 * until we have a robust, clean binding system, this
573 * quirk will have to remain in place.
576 /* pretend that certain key events that GTK does not allow
577 to be used as accelerators are actually something that
578 it does allow. but only where there are no modifiers.
581 uint32_t fakekey = ev->keyval;
583 if (Gtkmm2ext::possibly_translate_keyval_to_make_legal_accelerator (fakekey)) {
584 DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tactivate (was %1 now %2) without special hanlding of unmodified accels\n",
585 ev->keyval, fakekey));
587 GdkModifierType mod = GdkModifierType (ev->state);
589 mod = GdkModifierType (mod & gtk_accelerator_get_default_mod_mask());
591 /* GTK on OS X is currently (February 2012) setting both
592 the Meta and Mod2 bits in the event modifier state if
593 the Command key is down.
595 gtk_accel_groups_activate() does not invoke any of the logic
596 that gtk_window_activate_key() will that sorts out that stupid
597 state of affairs, and as a result it fails to find a match
598 for the key event and the current set of accelerators.
600 to fix this, if the meta bit is set, remove the mod2 bit
601 from the modifier. this assumes that our bindings use Primary
602 which will have set the meta bit in the accelerator entry.
604 if (mod & GDK_META_MASK) {
605 mod = GdkModifierType (mod & ~GDK_MOD2_MASK);
609 if (allow_activating && gtk_accel_groups_activate(G_OBJECT(win), fakekey, mod)) {
610 DEBUG_TRACE (DEBUG::Accelerators, "\taccel group activated by fakekey\n");
616 if (!special_handling_of_unmodified_accelerators || (ev->state & mask)) {
618 /* no special handling or there are modifiers in effect: accelerate first */
620 DEBUG_TRACE (DEBUG::Accelerators, "\tactivate, then propagate\n");
621 DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tevent send-event:%1 time:%2 length:%3 string:%4 hardware_keycode:%5 group:%6\n",
622 ev->send_event, ev->time, ev->length, ev->string, ev->hardware_keycode, ev->group));
624 if (allow_activating) {
625 DEBUG_TRACE (DEBUG::Accelerators, "\tsending to window\n");
626 if (gtk_window_activate_key (win, ev)) {
627 DEBUG_TRACE (DEBUG::Accelerators, "\t\thandled\n");
631 DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
634 DEBUG_TRACE (DEBUG::Accelerators, "\tnot accelerated, now propagate\n");
636 return gtk_window_propagate_key_event (win, ev);
639 /* no modifiers, propagate first */
641 DEBUG_TRACE (DEBUG::Accelerators, "\tpropagate, then activate\n");
643 if (!gtk_window_propagate_key_event (win, ev)) {
644 DEBUG_TRACE (DEBUG::Accelerators, "\tpropagation didn't handle, so activate\n");
645 if (allow_activating) {
646 return gtk_window_activate_key (win, ev);
648 DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
652 DEBUG_TRACE (DEBUG::Accelerators, "\thandled by propagate\n");
656 DEBUG_TRACE (DEBUG::Accelerators, "\tnot handled\n");
660 Glib::RefPtr<Gdk::Pixbuf>
661 ARDOUR_UI_UTILS::get_xpm (std::string name)
663 if (!xpm_map[name]) {
665 Searchpath spath(ARDOUR::ardour_data_search_path());
667 spath.add_subdirectory_to_paths("pixmaps");
669 std::string data_file_path;
671 if(!find_file (spath, name, data_file_path)) {
672 fatal << string_compose (_("cannot find XPM file for %1"), name) << endmsg;
676 xpm_map[name] = Gdk::Pixbuf::create_from_file (data_file_path);
677 } catch(const Glib::Error& e) {
678 warning << "Caught Glib::Error: " << e.what() << endmsg;
682 return xpm_map[name];
686 ARDOUR_UI_UTILS::get_icon_sets ()
688 Searchpath spath(ARDOUR::ardour_data_search_path());
689 spath.add_subdirectory_to_paths ("icons");
692 r.push_back (_("default"));
694 for (vector<string>::iterator s = spath.begin(); s != spath.end(); ++s) {
696 vector<string> entries;
698 get_paths (entries, *s, false, false);
700 for (vector<string>::iterator e = entries.begin(); e != entries.end(); ++e) {
701 if (Glib::file_test (*e, Glib::FILE_TEST_IS_DIR)) {
702 r.push_back (Glib::filename_to_utf8 (Glib::path_get_basename(*e)));
711 ARDOUR_UI_UTILS::get_icon_path (const char* cname, string icon_set)
713 std::string data_file_path;
717 Searchpath spath(ARDOUR::ardour_data_search_path());
719 if (!icon_set.empty() && icon_set != _("default")) {
721 /* add "icons/icon_set" but .. not allowed to add both of these at once */
722 spath.add_subdirectory_to_paths ("icons");
723 spath.add_subdirectory_to_paths (icon_set);
725 find_file (spath, name, data_file_path);
728 if (data_file_path.empty()) {
730 if (!icon_set.empty() && icon_set != _("default")) {
731 warning << string_compose (_("icon \"%1\" not found for icon set \"%2\", fallback to default"), cname, icon_set) << endmsg;
734 Searchpath def (ARDOUR::ardour_data_search_path());
735 def.add_subdirectory_to_paths ("icons");
737 if (!find_file (def, name, data_file_path)) {
738 fatal << string_compose (_("cannot find icon image for %1 using %2"), name, spath.to_string()) << endmsg;
743 return data_file_path;
746 Glib::RefPtr<Gdk::Pixbuf>
747 ARDOUR_UI_UTILS::get_icon (const char* cname, string icon_set)
749 Glib::RefPtr<Gdk::Pixbuf> img;
751 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname, icon_set));
752 } catch (const Gdk::PixbufError &e) {
753 cerr << "Caught PixbufError: " << e.what() << endl;
755 error << string_compose (_("Caught exception while loading icon named %1"), cname) << endmsg;
761 namespace ARDOUR_UI_UTILS {
762 Glib::RefPtr<Gdk::Pixbuf>
763 get_icon (const char* cname)
765 Glib::RefPtr<Gdk::Pixbuf> img;
767 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname));
768 } catch (const Gdk::PixbufError &e) {
769 cerr << "Caught PixbufError: " << e.what() << endl;
771 error << string_compose (_("Caught exception while loading icon named %1"), cname) << endmsg;
779 ARDOUR_UI_UTILS::longest (vector<string>& strings)
781 if (strings.empty()) {
785 vector<string>::iterator longest = strings.begin();
786 string::size_type longest_length = (*longest).length();
788 vector<string>::iterator i = longest;
791 while (i != strings.end()) {
793 string::size_type len = (*i).length();
795 if (len > longest_length) {
797 longest_length = len;
807 ARDOUR_UI_UTILS::key_is_legal_for_numeric_entry (guint keyval)
809 /* we assume that this does not change over the life of the process
812 static int comma_decimal = -1;
817 if (comma_decimal < 0) {
818 std::lconv* lc = std::localeconv();
819 if (strchr (lc->decimal_point, ',') != 0) {
831 case GDK_decimalpoint:
832 case GDK_KP_Separator:
862 case GDK_KP_Subtract:
892 ARDOUR_UI_UTILS::set_pango_fontsize ()
894 long val = ARDOUR::Config->get_font_scale();
896 /* FT2 rendering - used by GnomeCanvas, sigh */
898 #ifndef PLATFORM_WINDOWS
899 pango_ft2_font_map_set_resolution ((PangoFT2FontMap*) pango_ft2_font_map_new(), val/1024, val/1024);
902 /* Cairo rendering, in case there is any */
904 pango_cairo_font_map_set_resolution ((PangoCairoFontMap*) pango_cairo_font_map_get_default(), val/1024);
908 ARDOUR_UI_UTILS::reset_dpi ()
910 long val = ARDOUR::Config->get_font_scale();
911 set_pango_fontsize ();
914 gtk_settings_set_long_property (gtk_settings_get_default(),
915 "gtk-xft-dpi", val, "ardour");
916 DPIReset();//Emit Signal
920 ARDOUR_UI_UTILS::resize_window_to_proportion_of_monitor (Gtk::Window* window, int max_width, int max_height)
922 Glib::RefPtr<Gdk::Screen> screen = window->get_screen ();
923 Gdk::Rectangle monitor_rect;
924 screen->get_monitor_geometry (0, monitor_rect);
926 int const w = std::min (int (monitor_rect.get_width() * 0.8), max_width);
927 int const h = std::min (int (monitor_rect.get_height() * 0.8), max_height);
929 window->resize (w, h);
933 /** Replace _ with __ in a string; for use with menu item text to make underscores displayed correctly */
935 ARDOUR_UI_UTILS::escape_underscores (string const & s)
938 string::size_type const N = s.length ();
940 for (string::size_type i = 0; i < N; ++i) {
951 /** Replace < and > with < and > respectively to make < > display correctly in markup strings */
953 ARDOUR_UI_UTILS::escape_angled_brackets (string const & s)
956 boost::replace_all (o, "<", "<");
957 boost::replace_all (o, ">", ">");
962 ARDOUR_UI_UTILS::unique_random_color (list<Gdk::Color>& used_colors)
970 h = fmod (random(), 360.0);
971 s = (random() % 65535) / 65535.0;
972 v = (random() % 65535) / 65535.0;
974 s = min (0.5, s); /* not too saturated */
975 v = max (0.9, v); /* not too bright */
976 newcolor.set_hsv (h, s, v);
978 if (used_colors.size() == 0) {
979 used_colors.push_back (newcolor);
983 for (list<Gdk::Color>::iterator i = used_colors.begin(); i != used_colors.end(); ++i) {
985 float rdelta, bdelta, gdelta;
987 rdelta = newcolor.get_red() - c.get_red();
988 bdelta = newcolor.get_blue() - c.get_blue();
989 gdelta = newcolor.get_green() - c.get_green();
991 if (sqrt (rdelta*rdelta + bdelta*bdelta + gdelta*gdelta) > 25.0) {
992 /* different enough */
993 used_colors.push_back (newcolor);
998 /* XXX need throttle here to make sure we don't spin for ever */
1003 ARDOUR_UI_UTILS::rate_as_string (float r)
1006 if (fmod (r, 1000.0f)) {
1007 snprintf (buf, sizeof (buf), "%.1f kHz", r/1000.0);
1009 snprintf (buf, sizeof (buf), "%.0f kHz", r/1000.0);
1016 ARDOUR_UI_UTILS::track_number_to_string (
1017 int64_t tracknumber,
1023 if (tracknumber > 0) {
1024 rv = "<span weight=\"bold\" font_family=\"ArdourMono, Mono\">";
1025 rv += PBD::to_string (tracknumber, std::dec);
1029 else if (tracknumber < 0) {
1030 rv = "<span weight=\"bold\" font_family=\"ArdourMono, Mono\">";
1031 rv += PBD::to_string (-tracknumber, std::dec);
1035 rv += Glib::Markup::escape_text(postfix);