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 #ifndef PLATFORM_WINDOWS
25 #include <pango/pangoft2.h> // for fontmap resolution control for GnomeCanvas
27 #include <pango/pangocairo.h> // for fontmap resolution control for GnomeCanvas
38 #include <gtkmm/window.h>
39 #include <gtkmm/combo.h>
40 #include <gtkmm/label.h>
41 #include <gtkmm/paned.h>
42 #include <gtk/gtkpaned.h>
43 #include <boost/algorithm/string.hpp>
45 #include "pbd/file_utils.h"
47 #include <gtkmm2ext/utils.h>
48 #include "ardour/rc_configuration.h"
49 #include "ardour/filesystem_paths.h"
51 #include "canvas/item.h"
52 #include "canvas/utils.h"
54 #include "ardour_ui.h"
56 #include "public_editor.h"
60 #include "rgb_macros.h"
61 #include "gui_thread.h"
67 using Gtkmm2ext::Keyboard;
69 namespace ARDOUR_UI_UTILS {
70 sigc::signal<void> DPIReset;
73 #ifdef PLATFORM_WINDOWS
74 #define random() rand()
78 /** Add an element to a menu, settings its sensitivity.
79 * @param m Menu to add to.
80 * @param e Element to add.
81 * @param s true to make sensitive, false to make insensitive
84 ARDOUR_UI_UTILS::add_item_with_sensitivity (Menu_Helpers::MenuList& m, Menu_Helpers::MenuElem e, bool s)
88 m.back().set_sensitive (false);
94 ARDOUR_UI_UTILS::just_hide_it (GdkEventAny */*ev*/, Gtk::Window *win)
100 /* xpm2rgb copied from nixieclock, which bore the legend:
102 nixieclock - a nixie desktop timepiece
103 Copyright (C) 2000 Greg Ercolano, erco@3dsite.com
105 and was released under the GPL.
109 ARDOUR_UI_UTILS::xpm2rgb (const char** xpm, uint32_t& w, uint32_t& h)
111 static long vals[256], val;
112 uint32_t t, x, y, colors, cpp;
114 unsigned char *savergb, *rgb;
118 if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
119 error << string_compose (_("bad XPM header %1"), xpm[0])
124 savergb = rgb = (unsigned char*) malloc (h * w * 3);
126 // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
127 for (t = 0; t < colors; ++t) {
128 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
132 // COLORMAP -> RGB CONVERSION
133 // Get low 3 bytes from vals[]
137 for (y = h-1; y > 0; --y) {
139 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 3) {
140 val = vals[(int)*p++];
141 *(rgb+2) = val & 0xff; val >>= 8; // 2:B
142 *(rgb+1) = val & 0xff; val >>= 8; // 1:G
143 *(rgb+0) = val & 0xff; // 0:R
151 ARDOUR_UI_UTILS::xpm2rgba (const char** xpm, uint32_t& w, uint32_t& h)
153 static long vals[256], val;
154 uint32_t t, x, y, colors, cpp;
156 unsigned char *savergb, *rgb;
161 if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
162 error << string_compose (_("bad XPM header %1"), xpm[0])
167 savergb = rgb = (unsigned char*) malloc (h * w * 4);
169 // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
171 if (strstr (xpm[1], "None")) {
172 sscanf (xpm[1], "%c", &transparent);
179 for (; t < colors; ++t) {
180 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
184 // COLORMAP -> RGB CONVERSION
185 // Get low 3 bytes from vals[]
189 for (y = h-1; y > 0; --y) {
193 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 4) {
195 if (transparent && (*p++ == transparent)) {
203 *(rgb+3) = alpha; // 3: alpha
204 *(rgb+2) = val & 0xff; val >>= 8; // 2:B
205 *(rgb+1) = val & 0xff; val >>= 8; // 1:G
206 *(rgb+0) = val & 0xff; // 0:R
213 /** Returns a Pango::FontDescription given a string describing the font.
215 * If the returned FontDescription does not specify a family, then
216 * the family is set to "Sans". This mirrors GTK's behaviour in
219 * Some environments will force Pango to specify the family
220 * even if it was not specified in the string describing the font.
221 * Such environments should be left unaffected by this function,
222 * since the font family will be left alone.
224 * There may be other similar font specification enforcement
225 * that we might add here later.
227 Pango::FontDescription
228 ARDOUR_UI_UTILS::sanitized_font (std::string const& name)
230 Pango::FontDescription fd (name);
232 if (fd.get_family().empty()) {
233 fd.set_family ("Sans");
239 Pango::FontDescription
240 ARDOUR_UI_UTILS::get_font_for_style (string widgetname)
242 Gtk::Window window (WINDOW_TOPLEVEL);
244 Glib::RefPtr<Gtk::Style> style;
247 foobar.set_name (widgetname);
248 foobar.ensure_style();
250 style = foobar.get_style ();
252 Glib::RefPtr<const Pango::Layout> layout = foobar.get_layout();
254 PangoFontDescription *pfd = const_cast<PangoFontDescription *> (pango_layout_get_font_description(const_cast<PangoLayout *>(layout->gobj())));
258 /* layout inherited its font description from a PangoContext */
260 PangoContext* ctxt = (PangoContext*) pango_layout_get_context (const_cast<PangoLayout*>(layout->gobj()));
261 pfd = pango_context_get_font_description (ctxt);
262 return Pango::FontDescription (pfd); /* make a copy */
265 return Pango::FontDescription (pfd); /* make a copy */
269 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)
271 /* In GTK+2, styles aren't set up correctly if the widget is not
272 attached to a toplevel window that has a screen pointer.
275 static Gtk::Window* window = 0;
278 window = new Window (WINDOW_TOPLEVEL);
285 foo.set_name (style);
288 GtkRcStyle* rc = foo.get_style()->gobj()->rc_style;
292 r = rc->fg[state].red / 257;
293 g = rc->fg[state].green / 257;
294 b = rc->fg[state].blue / 257;
296 /* what a hack ... "a" is for "active" */
297 if (state == Gtk::STATE_NORMAL && rgba) {
298 a = rc->fg[GTK_STATE_ACTIVE].red / 257;
300 } else if (attr == "bg") {
302 r = rc->bg[state].red / 257;
303 g = rc->bg[state].green / 257;
304 b = rc->bg[state].blue / 257;
305 } else if (attr == "base") {
306 r = rc->base[state].red / 257;
307 g = rc->base[state].green / 257;
308 b = rc->base[state].blue / 257;
309 } else if (attr == "text") {
310 r = rc->text[state].red / 257;
311 g = rc->text[state].green / 257;
312 b = rc->text[state].blue / 257;
315 warning << string_compose (_("missing RGBA style for \"%1\""), style) << endl;
320 if (state == Gtk::STATE_NORMAL && rgba) {
321 return (uint32_t) RGBA_TO_UINT(r,g,b,a);
323 return (uint32_t) RGBA_TO_UINT(r,g,b,255);
328 ARDOUR_UI_UTILS::rgba_p_from_style (string style, float *r, float *g, float *b, string attr, int state)
330 static Gtk::Window* window = 0;
331 assert (r && g && b);
334 window = new Window (WINDOW_TOPLEVEL);
341 foo.set_name (style);
344 GtkRcStyle* rc = foo.get_style()->gobj()->rc_style;
347 warning << string_compose (_("missing RGBA style for \"%1\""), style) << endl;
351 *r = rc->fg[state].red / 65535.0;
352 *g = rc->fg[state].green / 65535.0;
353 *b = rc->fg[state].blue / 65535.0;
354 } else if (attr == "bg") {
355 *r = rc->bg[state].red / 65535.0;
356 *g = rc->bg[state].green / 65535.0;
357 *b = rc->bg[state].blue / 65535.0;
358 } else if (attr == "base") {
359 *r = rc->base[state].red / 65535.0;
360 *g = rc->base[state].green / 65535.0;
361 *b = rc->base[state].blue / 65535.0;
362 } else if (attr == "text") {
363 *r = rc->text[state].red / 65535.0;
364 *g = rc->text[state].green / 65535.0;
365 *b = rc->text[state].blue / 65535.0;
375 ARDOUR_UI_UTILS::set_color_from_rgb (Gdk::Color& c, uint32_t rgb)
377 /* Gdk::Color color ranges are 16 bit, so scale from 8 bit by
380 c.set_rgb ((rgb >> 16)*256, ((rgb & 0xff00) >> 8)*256, (rgb & 0xff)*256);
384 ARDOUR_UI_UTILS::set_color_from_rgba (Gdk::Color& c, uint32_t rgba)
386 /* Gdk::Color color ranges are 16 bit, so scale from 8 bit by
389 c.set_rgb ((rgba >> 24)*256, ((rgba & 0xff0000) >> 16)*256, ((rgba & 0xff00) >> 8)*256);
393 ARDOUR_UI_UTILS::gdk_color_to_rgba (Gdk::Color const& c)
395 /* since alpha value is not available from a Gdk::Color, it is
396 hardcoded as 0xff (aka 255 or 1.0)
399 const uint32_t r = c.get_red_p () * 255.0;
400 const uint32_t g = c.get_green_p () * 255.0;
401 const uint32_t b = c.get_blue_p () * 255.0;
402 const uint32_t a = 0xff;
404 return RGBA_TO_UINT (r,g,b,a);
409 ARDOUR_UI_UTILS::relay_key_press (GdkEventKey* ev, Gtk::Window* win)
411 PublicEditor& ed (PublicEditor::instance());
413 if (!key_press_focus_accelerator_handler (*win, ev)) {
415 /* early key press in pre-main-window-dialogs, no editor yet */
418 return ed.on_key_press_event(ev);
425 ARDOUR_UI_UTILS::forward_key_press (GdkEventKey* ev)
427 return PublicEditor::instance().on_key_press_event(ev);
431 ARDOUR_UI_UTILS::emulate_key_event (Gtk::Widget* w, unsigned int keyval)
433 GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET(w->gobj()));
434 GdkKeymap *keymap = gdk_keymap_get_for_display (display);
435 GdkKeymapKey *keymapkey = NULL;
438 if (!gdk_keymap_get_entries_for_keyval(keymap, keyval, &keymapkey, &n_keys)) return false;
439 if (n_keys !=1) { g_free(keymapkey); return false;}
442 ev.type = GDK_KEY_PRESS;
443 ev.window = gtk_widget_get_window(GTK_WIDGET(w->gobj()));
444 ev.send_event = FALSE;
449 ev.string = const_cast<gchar*> ("");
450 ev.hardware_keycode = keymapkey[0].keycode;
451 ev.group = keymapkey[0].group;
454 forward_key_press(&ev);
455 ev.type = GDK_KEY_RELEASE;
456 return forward_key_press(&ev);
460 ARDOUR_UI_UTILS::key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev)
462 GtkWindow* win = window.gobj();
463 GtkWidget* focus = gtk_window_get_focus (win);
464 bool special_handling_of_unmodified_accelerators = false;
465 bool allow_activating = true;
466 /* consider all relevant modifiers but not LOCK or SHIFT */
467 const guint mask = (Keyboard::RelevantModifierKeyMask & ~(Gdk::SHIFT_MASK|Gdk::LOCK_MASK));
470 if (GTK_IS_ENTRY(focus) || Keyboard::some_magic_widget_has_focus()) {
471 special_handling_of_unmodified_accelerators = true;
476 /* at one time this appeared to be necessary. As of July 2012, it does not
477 appear to be. if it ever is necessar, figure out if it should apply
481 if (Keyboard::some_magic_widget_has_focus ()) {
482 allow_activating = false;
488 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",
492 special_handling_of_unmodified_accelerators,
493 Keyboard::some_magic_widget_has_focus(),
497 /* This exists to allow us to override the way GTK handles
498 key events. The normal sequence is:
500 a) event is delivered to a GtkWindow
501 b) accelerators/mnemonics are activated
502 c) if (b) didn't handle the event, propagate to
503 the focus widget and/or focus chain
505 The problem with this is that if the accelerators include
506 keys without modifiers, such as the space bar or the
507 letter "e", then pressing the key while typing into
508 a text entry widget results in the accelerator being
509 activated, instead of the desired letter appearing
512 There is no good way of fixing this, but this
513 represents a compromise. The idea is that
514 key events involving modifiers (not Shift)
515 get routed into the activation pathway first, then
516 get propagated to the focus widget if necessary.
518 If the key event doesn't involve modifiers,
519 we deliver to the focus widget first, thus allowing
520 it to get "normal text" without interference
523 Of course, this can also be problematic: if there
524 is a widget with focus, then it will swallow
525 all "normal text" accelerators.
528 if (!special_handling_of_unmodified_accelerators) {
530 /* XXX note that for a brief moment, the conditional above
531 * included "|| (ev->state & mask)" so as to enforce the
532 * implication of special_handling_of_UNMODIFIED_accelerators.
533 * however, this forces any key that GTK doesn't allow and that
534 * we have an alternative (see next comment) for to be
535 * automatically sent through the accel groups activation
536 * pathway, which prevents individual widgets & canvas items
537 * from ever seeing it if is used by a key binding.
539 * specifically, this hid Ctrl-down-arrow from MIDI region
540 * views because it is also bound to an action.
542 * until we have a robust, clean binding system, this
543 * quirk will have to remain in place.
546 /* pretend that certain key events that GTK does not allow
547 to be used as accelerators are actually something that
548 it does allow. but only where there are no modifiers.
551 uint32_t fakekey = ev->keyval;
553 if (Gtkmm2ext::possibly_translate_keyval_to_make_legal_accelerator (fakekey)) {
554 DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tactivate (was %1 now %2) without special hanlding of unmodified accels\n",
555 ev->keyval, fakekey));
557 GdkModifierType mod = GdkModifierType (ev->state);
559 mod = GdkModifierType (mod & gtk_accelerator_get_default_mod_mask());
561 /* GTK on OS X is currently (February 2012) setting both
562 the Meta and Mod2 bits in the event modifier state if
563 the Command key is down.
565 gtk_accel_groups_activate() does not invoke any of the logic
566 that gtk_window_activate_key() will that sorts out that stupid
567 state of affairs, and as a result it fails to find a match
568 for the key event and the current set of accelerators.
570 to fix this, if the meta bit is set, remove the mod2 bit
571 from the modifier. this assumes that our bindings use Primary
572 which will have set the meta bit in the accelerator entry.
574 if (mod & GDK_META_MASK) {
575 mod = GdkModifierType (mod & ~GDK_MOD2_MASK);
579 if (allow_activating && gtk_accel_groups_activate(G_OBJECT(win), fakekey, mod)) {
580 DEBUG_TRACE (DEBUG::Accelerators, "\taccel group activated by fakekey\n");
586 if (!special_handling_of_unmodified_accelerators || (ev->state & mask)) {
588 /* no special handling or there are modifiers in effect: accelerate first */
590 DEBUG_TRACE (DEBUG::Accelerators, "\tactivate, then propagate\n");
591 DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tevent send-event:%1 time:%2 length:%3 name %7 string:%4 hardware_keycode:%5 group:%6\n",
592 ev->send_event, ev->time, ev->length, ev->string, ev->hardware_keycode, ev->group, gdk_keyval_name (ev->keyval)));
594 if (allow_activating) {
595 DEBUG_TRACE (DEBUG::Accelerators, "\tsending to window\n");
596 if (gtk_window_activate_key (win, ev)) {
597 DEBUG_TRACE (DEBUG::Accelerators, "\t\thandled\n");
601 DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
604 DEBUG_TRACE (DEBUG::Accelerators, "\tnot accelerated, now propagate\n");
606 return gtk_window_propagate_key_event (win, ev);
609 /* no modifiers, propagate first */
611 DEBUG_TRACE (DEBUG::Accelerators, "\tpropagate, then activate\n");
613 if (!gtk_window_propagate_key_event (win, ev)) {
614 DEBUG_TRACE (DEBUG::Accelerators, "\tpropagation didn't handle, so activate\n");
615 if (allow_activating) {
616 return gtk_window_activate_key (win, ev);
618 DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
622 DEBUG_TRACE (DEBUG::Accelerators, "\thandled by propagate\n");
626 DEBUG_TRACE (DEBUG::Accelerators, "\tnot handled\n");
630 Glib::RefPtr<Gdk::Pixbuf>
631 ARDOUR_UI_UTILS::get_xpm (std::string name)
633 if (!xpm_map[name]) {
635 Searchpath spath(ARDOUR::ardour_data_search_path());
637 spath.add_subdirectory_to_paths("pixmaps");
639 std::string data_file_path;
641 if(!find_file (spath, name, data_file_path)) {
642 fatal << string_compose (_("cannot find XPM file for %1"), name) << endmsg;
646 xpm_map[name] = Gdk::Pixbuf::create_from_file (data_file_path);
647 } catch(const Glib::Error& e) {
648 warning << "Caught Glib::Error: " << e.what() << endmsg;
652 return xpm_map[name];
656 ARDOUR_UI_UTILS::get_icon_sets ()
658 Searchpath spath(ARDOUR::ardour_data_search_path());
659 spath.add_subdirectory_to_paths ("icons");
662 r.push_back (_("default"));
664 for (vector<string>::iterator s = spath.begin(); s != spath.end(); ++s) {
666 vector<string> entries;
668 get_paths (entries, *s, false, false);
670 for (vector<string>::iterator e = entries.begin(); e != entries.end(); ++e) {
671 if (Glib::file_test (*e, Glib::FILE_TEST_IS_DIR)) {
672 r.push_back (Glib::filename_to_utf8 (Glib::path_get_basename(*e)));
681 ARDOUR_UI_UTILS::get_icon_path (const char* cname, string icon_set, bool is_image)
683 std::string data_file_path;
690 Searchpath spath(ARDOUR::ardour_data_search_path());
692 if (!icon_set.empty() && icon_set != _("default")) {
694 /* add "icons/icon_set" but .. not allowed to add both of these at once */
695 spath.add_subdirectory_to_paths ("icons");
696 spath.add_subdirectory_to_paths (icon_set);
698 find_file (spath, name, data_file_path);
701 if (is_image && data_file_path.empty()) {
703 if (!icon_set.empty() && icon_set != _("default")) {
704 warning << string_compose (_("icon \"%1\" not found for icon set \"%2\", fallback to default"), cname, icon_set) << endmsg;
707 Searchpath def (ARDOUR::ardour_data_search_path());
708 def.add_subdirectory_to_paths ("icons");
710 if (!find_file (def, name, data_file_path)) {
711 fatal << string_compose (_("cannot find icon image for %1 using %2"), name, spath.to_string()) << endmsg;
716 return data_file_path;
719 Glib::RefPtr<Gdk::Pixbuf>
720 ARDOUR_UI_UTILS::get_icon (const char* cname, string icon_set)
722 Glib::RefPtr<Gdk::Pixbuf> img;
724 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname, icon_set));
725 } catch (const Gdk::PixbufError &e) {
726 cerr << "Caught PixbufError: " << e.what() << endl;
728 error << string_compose (_("Caught exception while loading icon named %1"), cname) << endmsg;
734 namespace ARDOUR_UI_UTILS {
735 Glib::RefPtr<Gdk::Pixbuf>
736 get_icon (const char* cname)
738 Glib::RefPtr<Gdk::Pixbuf> img;
740 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname));
741 } catch (const Gdk::PixbufError &e) {
742 cerr << "Caught PixbufError: " << e.what() << endl;
744 error << string_compose (_("Caught exception while loading icon named %1"), cname) << endmsg;
752 ARDOUR_UI_UTILS::longest (vector<string>& strings)
754 if (strings.empty()) {
758 vector<string>::iterator longest = strings.begin();
759 string::size_type longest_length = (*longest).length();
761 vector<string>::iterator i = longest;
764 while (i != strings.end()) {
766 string::size_type len = (*i).length();
768 if (len > longest_length) {
770 longest_length = len;
780 ARDOUR_UI_UTILS::key_is_legal_for_numeric_entry (guint keyval)
782 /* we assume that this does not change over the life of the process
785 static int comma_decimal = -1;
790 if (comma_decimal < 0) {
791 std::lconv* lc = std::localeconv();
792 if (strchr (lc->decimal_point, ',') != 0) {
804 case GDK_decimalpoint:
805 case GDK_KP_Separator:
835 case GDK_KP_Subtract:
865 ARDOUR_UI_UTILS::set_pango_fontsize ()
867 long val = ARDOUR::Config->get_font_scale();
869 /* FT2 rendering - used by GnomeCanvas, sigh */
871 #ifndef PLATFORM_WINDOWS
872 pango_ft2_font_map_set_resolution ((PangoFT2FontMap*) pango_ft2_font_map_new(), val/1024, val/1024);
875 /* Cairo rendering, in case there is any */
877 pango_cairo_font_map_set_resolution ((PangoCairoFontMap*) pango_cairo_font_map_get_default(), val/1024);
881 ARDOUR_UI_UTILS::reset_dpi ()
883 long val = ARDOUR::Config->get_font_scale();
884 set_pango_fontsize ();
887 gtk_settings_set_long_property (gtk_settings_get_default(),
888 "gtk-xft-dpi", val, "ardour");
889 DPIReset();//Emit Signal
893 ARDOUR_UI_UTILS::resize_window_to_proportion_of_monitor (Gtk::Window* window, int max_width, int max_height)
895 Glib::RefPtr<Gdk::Screen> screen = window->get_screen ();
896 Gdk::Rectangle monitor_rect;
897 screen->get_monitor_geometry (0, monitor_rect);
899 int const w = std::min (int (monitor_rect.get_width() * 0.8), max_width);
900 int const h = std::min (int (monitor_rect.get_height() * 0.8), max_height);
902 window->resize (w, h);
906 /** Replace _ with __ in a string; for use with menu item text to make underscores displayed correctly */
908 ARDOUR_UI_UTILS::escape_underscores (string const & s)
911 string::size_type const N = s.length ();
913 for (string::size_type i = 0; i < N; ++i) {
924 /** Replace < and > with < and > respectively to make < > display correctly in markup strings */
926 ARDOUR_UI_UTILS::escape_angled_brackets (string const & s)
929 boost::replace_all (o, "<", "<");
930 boost::replace_all (o, ">", ">");
935 ARDOUR_UI_UTILS::unique_random_color (list<Gdk::Color>& used_colors)
943 h = fmod (random(), 360.0);
944 s = (random() % 65535) / 65535.0;
945 v = (random() % 65535) / 65535.0;
947 s = min (0.5, s); /* not too saturated */
948 v = max (0.9, v); /* not too bright */
949 newcolor.set_hsv (h, s, v);
951 if (used_colors.size() == 0) {
952 used_colors.push_back (newcolor);
956 for (list<Gdk::Color>::iterator i = used_colors.begin(); i != used_colors.end(); ++i) {
958 float rdelta, bdelta, gdelta;
960 rdelta = newcolor.get_red() - c.get_red();
961 bdelta = newcolor.get_blue() - c.get_blue();
962 gdelta = newcolor.get_green() - c.get_green();
964 if (sqrt (rdelta*rdelta + bdelta*bdelta + gdelta*gdelta) > 25.0) {
965 /* different enough */
966 used_colors.push_back (newcolor);
971 /* XXX need throttle here to make sure we don't spin for ever */
976 ARDOUR_UI_UTILS::rate_as_string (float r)
979 if (fmod (r, 1000.0f)) {
980 snprintf (buf, sizeof (buf), "%.1f kHz", r/1000.0);
982 snprintf (buf, sizeof (buf), "%.0f kHz", r/1000.0);