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") {
299 r = rc->bg[state].red / 257;
300 g = rc->bg[state].green / 257;
301 b = rc->bg[state].blue / 257;
302 } else if (attr == "base") {
303 r = rc->base[state].red / 257;
304 g = rc->base[state].green / 257;
305 b = rc->base[state].blue / 257;
306 } else if (attr == "text") {
307 r = rc->text[state].red / 257;
308 g = rc->text[state].green / 257;
309 b = rc->text[state].blue / 257;
312 warning << string_compose (_("missing RGBA style for \"%1\""), style) << endl;
317 if (state == Gtk::STATE_NORMAL && rgba) {
318 return (uint32_t) RGBA_TO_UINT(r,g,b,a);
320 return (uint32_t) RGBA_TO_UINT(r,g,b,255);
325 ARDOUR_UI_UTILS::rgba_p_from_style (string style, float *r, float *g, float *b, string attr, int state)
327 static Gtk::Window* window = 0;
328 assert (r && g && b);
331 window = new Window (WINDOW_TOPLEVEL);
338 foo.set_name (style);
341 GtkRcStyle* rc = foo.get_style()->gobj()->rc_style;
344 warning << string_compose (_("missing RGBA style for \"%1\""), style) << endl;
348 *r = rc->fg[state].red / 65535.0;
349 *g = rc->fg[state].green / 65535.0;
350 *b = rc->fg[state].blue / 65535.0;
351 } else if (attr == "bg") {
352 *r = rc->bg[state].red / 65535.0;
353 *g = rc->bg[state].green / 65535.0;
354 *b = rc->bg[state].blue / 65535.0;
355 } else if (attr == "base") {
356 *r = rc->base[state].red / 65535.0;
357 *g = rc->base[state].green / 65535.0;
358 *b = rc->base[state].blue / 65535.0;
359 } else if (attr == "text") {
360 *r = rc->text[state].red / 65535.0;
361 *g = rc->text[state].green / 65535.0;
362 *b = rc->text[state].blue / 65535.0;
372 ARDOUR_UI_UTILS::set_color_from_rgb (Gdk::Color& c, uint32_t rgb)
374 /* Gdk::Color color ranges are 16 bit, so scale from 8 bit by
377 c.set_rgb ((rgb >> 16)*256, ((rgb & 0xff00) >> 8)*256, (rgb & 0xff)*256);
381 ARDOUR_UI_UTILS::set_color_from_rgba (Gdk::Color& c, uint32_t rgba)
383 /* Gdk::Color color ranges are 16 bit, so scale from 8 bit by
386 c.set_rgb ((rgba >> 24)*256, ((rgba & 0xff0000) >> 16)*256, ((rgba & 0xff00) >> 8)*256);
390 ARDOUR_UI_UTILS::gdk_color_to_rgba (Gdk::Color const& c)
392 /* since alpha value is not available from a Gdk::Color, it is
393 hardcoded as 0xff (aka 255 or 1.0)
396 const uint32_t r = c.get_red_p () * 255.0;
397 const uint32_t g = c.get_green_p () * 255.0;
398 const uint32_t b = c.get_blue_p () * 255.0;
399 const uint32_t a = 0xff;
401 return RGBA_TO_UINT (r,g,b,a);
406 ARDOUR_UI_UTILS::relay_key_press (GdkEventKey* ev, Gtk::Window* win)
408 PublicEditor& ed (PublicEditor::instance());
410 if (!key_press_focus_accelerator_handler (*win, ev)) {
412 /* early key press in pre-main-window-dialogs, no editor yet */
415 return ed.on_key_press_event(ev);
422 ARDOUR_UI_UTILS::forward_key_press (GdkEventKey* ev)
424 return PublicEditor::instance().on_key_press_event(ev);
428 ARDOUR_UI_UTILS::emulate_key_event (Gtk::Widget* w, unsigned int keyval)
430 GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET(w->gobj()));
431 GdkKeymap *keymap = gdk_keymap_get_for_display (display);
432 GdkKeymapKey *keymapkey = NULL;
435 if (!gdk_keymap_get_entries_for_keyval(keymap, keyval, &keymapkey, &n_keys)) return false;
436 if (n_keys !=1) { g_free(keymapkey); return false;}
439 ev.type = GDK_KEY_PRESS;
440 ev.window = gtk_widget_get_window(GTK_WIDGET(w->gobj()));
441 ev.send_event = FALSE;
446 ev.string = const_cast<gchar*> ("");
447 ev.hardware_keycode = keymapkey[0].keycode;
448 ev.group = keymapkey[0].group;
451 forward_key_press(&ev);
452 ev.type = GDK_KEY_RELEASE;
453 return forward_key_press(&ev);
457 ARDOUR_UI_UTILS::key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev)
459 GtkWindow* win = window.gobj();
460 GtkWidget* focus = gtk_window_get_focus (win);
461 bool special_handling_of_unmodified_accelerators = false;
462 bool allow_activating = true;
463 /* consider all relevant modifiers but not LOCK or SHIFT */
464 const guint mask = (Keyboard::RelevantModifierKeyMask & ~(Gdk::SHIFT_MASK|Gdk::LOCK_MASK));
467 if (GTK_IS_ENTRY(focus) || Keyboard::some_magic_widget_has_focus()) {
468 special_handling_of_unmodified_accelerators = true;
473 /* at one time this appeared to be necessary. As of July 2012, it does not
474 appear to be. if it ever is necessar, figure out if it should apply
478 if (Keyboard::some_magic_widget_has_focus ()) {
479 allow_activating = false;
485 DEBUG_TRACE (DEBUG::Accelerators, string_compose ("Win = %1 focus = %7 (%8) Key event: code = %2 state = %3 special handling ? %4 magic widget focus ? %5 allow_activation ? %6\n",
489 special_handling_of_unmodified_accelerators,
490 Keyboard::some_magic_widget_has_focus(),
493 (focus ? gtk_widget_get_name (focus) : "no focus widget")));
495 /* This exists to allow us to override the way GTK handles
496 key events. The normal sequence is:
498 a) event is delivered to a GtkWindow
499 b) accelerators/mnemonics are activated
500 c) if (b) didn't handle the event, propagate to
501 the focus widget and/or focus chain
503 The problem with this is that if the accelerators include
504 keys without modifiers, such as the space bar or the
505 letter "e", then pressing the key while typing into
506 a text entry widget results in the accelerator being
507 activated, instead of the desired letter appearing
510 There is no good way of fixing this, but this
511 represents a compromise. The idea is that
512 key events involving modifiers (not Shift)
513 get routed into the activation pathway first, then
514 get propagated to the focus widget if necessary.
516 If the key event doesn't involve modifiers,
517 we deliver to the focus widget first, thus allowing
518 it to get "normal text" without interference
521 Of course, this can also be problematic: if there
522 is a widget with focus, then it will swallow
523 all "normal text" accelerators.
526 if (!special_handling_of_unmodified_accelerators) {
528 /* XXX note that for a brief moment, the conditional above
529 * included "|| (ev->state & mask)" so as to enforce the
530 * implication of special_handling_of_UNMODIFIED_accelerators.
531 * however, this forces any key that GTK doesn't allow and that
532 * we have an alternative (see next comment) for to be
533 * automatically sent through the accel groups activation
534 * pathway, which prevents individual widgets & canvas items
535 * from ever seeing it if is used by a key binding.
537 * specifically, this hid Ctrl-down-arrow from MIDI region
538 * views because it is also bound to an action.
540 * until we have a robust, clean binding system, this
541 * quirk will have to remain in place.
544 /* pretend that certain key events that GTK does not allow
545 to be used as accelerators are actually something that
546 it does allow. but only where there are no modifiers.
549 uint32_t fakekey = ev->keyval;
551 if (Gtkmm2ext::possibly_translate_keyval_to_make_legal_accelerator (fakekey)) {
552 DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tactivate (was %1 now %2) without special hanlding of unmodified accels\n",
553 ev->keyval, fakekey));
555 GdkModifierType mod = GdkModifierType (ev->state);
557 mod = GdkModifierType (mod & gtk_accelerator_get_default_mod_mask());
559 /* GTK on OS X is currently (February 2012) setting both
560 the Meta and Mod2 bits in the event modifier state if
561 the Command key is down.
563 gtk_accel_groups_activate() does not invoke any of the logic
564 that gtk_window_activate_key() will that sorts out that stupid
565 state of affairs, and as a result it fails to find a match
566 for the key event and the current set of accelerators.
568 to fix this, if the meta bit is set, remove the mod2 bit
569 from the modifier. this assumes that our bindings use Primary
570 which will have set the meta bit in the accelerator entry.
572 if (mod & GDK_META_MASK) {
573 mod = GdkModifierType (mod & ~GDK_MOD2_MASK);
577 if (allow_activating && gtk_accel_groups_activate(G_OBJECT(win), fakekey, mod)) {
578 DEBUG_TRACE (DEBUG::Accelerators, "\taccel group activated by fakekey\n");
584 if (!special_handling_of_unmodified_accelerators || (ev->state & mask)) {
586 /* no special handling or there are modifiers in effect: accelerate first */
588 DEBUG_TRACE (DEBUG::Accelerators, "\tactivate, then propagate\n");
589 DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tevent send-event:%1 time:%2 length:%3 name %7 string:%4 hardware_keycode:%5 group:%6\n",
590 ev->send_event, ev->time, ev->length, ev->string, ev->hardware_keycode, ev->group, gdk_keyval_name (ev->keyval)));
592 if (allow_activating) {
593 DEBUG_TRACE (DEBUG::Accelerators, "\tsending to window\n");
594 if (gtk_window_activate_key (win, ev)) {
595 DEBUG_TRACE (DEBUG::Accelerators, "\t\thandled\n");
599 DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
602 DEBUG_TRACE (DEBUG::Accelerators, "\tnot accelerated, now propagate\n");
604 return gtk_window_propagate_key_event (win, ev);
607 /* no modifiers, propagate first */
609 DEBUG_TRACE (DEBUG::Accelerators, "\tpropagate, then activate\n");
611 if (!gtk_window_propagate_key_event (win, ev)) {
612 DEBUG_TRACE (DEBUG::Accelerators, "\tpropagation didn't handle, so activate\n");
613 if (allow_activating) {
614 return gtk_window_activate_key (win, ev);
616 DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
620 DEBUG_TRACE (DEBUG::Accelerators, "\thandled by propagate\n");
624 DEBUG_TRACE (DEBUG::Accelerators, "\tnot handled\n");
628 Glib::RefPtr<Gdk::Pixbuf>
629 ARDOUR_UI_UTILS::get_xpm (std::string name)
631 if (!xpm_map[name]) {
633 Searchpath spath(ARDOUR::ardour_data_search_path());
635 spath.add_subdirectory_to_paths("pixmaps");
637 std::string data_file_path;
639 if(!find_file (spath, name, data_file_path)) {
640 fatal << string_compose (_("cannot find XPM file for %1"), name) << endmsg;
644 xpm_map[name] = Gdk::Pixbuf::create_from_file (data_file_path);
645 } catch(const Glib::Error& e) {
646 warning << "Caught Glib::Error: " << e.what() << endmsg;
650 return xpm_map[name];
654 ARDOUR_UI_UTILS::get_icon_sets ()
656 Searchpath spath(ARDOUR::ardour_data_search_path());
657 spath.add_subdirectory_to_paths ("icons");
660 r.push_back (_("default"));
662 for (vector<string>::iterator s = spath.begin(); s != spath.end(); ++s) {
664 vector<string> entries;
666 get_paths (entries, *s, false, false);
668 for (vector<string>::iterator e = entries.begin(); e != entries.end(); ++e) {
669 if (Glib::file_test (*e, Glib::FILE_TEST_IS_DIR)) {
670 r.push_back (Glib::filename_to_utf8 (Glib::path_get_basename(*e)));
679 ARDOUR_UI_UTILS::get_icon_path (const char* cname, string icon_set, bool is_image)
681 std::string data_file_path;
688 Searchpath spath(ARDOUR::ardour_data_search_path());
690 if (!icon_set.empty() && icon_set != _("default")) {
692 /* add "icons/icon_set" but .. not allowed to add both of these at once */
693 spath.add_subdirectory_to_paths ("icons");
694 spath.add_subdirectory_to_paths (icon_set);
696 find_file (spath, name, data_file_path);
698 spath.add_subdirectory_to_paths ("icons");
699 find_file (spath, name, data_file_path);
702 if (is_image && data_file_path.empty()) {
704 if (!icon_set.empty() && icon_set != _("default")) {
705 warning << string_compose (_("icon \"%1\" not found for icon set \"%2\", fallback to default"), cname, icon_set) << endmsg;
708 Searchpath def (ARDOUR::ardour_data_search_path());
709 def.add_subdirectory_to_paths ("icons");
711 if (!find_file (def, name, data_file_path)) {
712 fatal << string_compose (_("cannot find icon image for %1 using %2"), name, spath.to_string()) << endmsg;
713 abort(); /*NOTREACHED*/
717 return data_file_path;
720 Glib::RefPtr<Gdk::Pixbuf>
721 ARDOUR_UI_UTILS::get_icon (const char* cname, string icon_set)
723 Glib::RefPtr<Gdk::Pixbuf> img;
725 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname, icon_set));
726 } catch (const Gdk::PixbufError &e) {
727 cerr << "Caught PixbufError: " << e.what() << endl;
729 error << string_compose (_("Caught exception while loading icon named %1"), cname) << endmsg;
735 namespace ARDOUR_UI_UTILS {
736 Glib::RefPtr<Gdk::Pixbuf>
737 get_icon (const char* cname)
739 Glib::RefPtr<Gdk::Pixbuf> img;
741 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname));
742 } catch (const Gdk::PixbufError &e) {
743 cerr << "Caught PixbufError: " << e.what() << endl;
745 error << string_compose (_("Caught exception while loading icon named %1"), cname) << endmsg;
753 ARDOUR_UI_UTILS::longest (vector<string>& strings)
755 if (strings.empty()) {
759 vector<string>::iterator longest = strings.begin();
760 string::size_type longest_length = (*longest).length();
762 vector<string>::iterator i = longest;
765 while (i != strings.end()) {
767 string::size_type len = (*i).length();
769 if (len > longest_length) {
771 longest_length = len;
781 ARDOUR_UI_UTILS::key_is_legal_for_numeric_entry (guint keyval)
783 /* we assume that this does not change over the life of the process
786 static int comma_decimal = -1;
791 if (comma_decimal < 0) {
792 std::lconv* lc = std::localeconv();
793 if (strchr (lc->decimal_point, ',') != 0) {
805 case GDK_decimalpoint:
806 case GDK_KP_Separator:
836 case GDK_KP_Subtract:
866 ARDOUR_UI_UTILS::set_pango_fontsize ()
868 long val = ARDOUR::Config->get_font_scale();
870 /* FT2 rendering - used by GnomeCanvas, sigh */
872 #ifndef PLATFORM_WINDOWS
873 pango_ft2_font_map_set_resolution ((PangoFT2FontMap*) pango_ft2_font_map_new(), val/1024, val/1024);
876 /* Cairo rendering, in case there is any */
878 pango_cairo_font_map_set_resolution ((PangoCairoFontMap*) pango_cairo_font_map_get_default(), val/1024);
882 ARDOUR_UI_UTILS::reset_dpi ()
884 long val = ARDOUR::Config->get_font_scale();
885 set_pango_fontsize ();
888 gtk_settings_set_long_property (gtk_settings_get_default(),
889 "gtk-xft-dpi", val, "ardour");
890 DPIReset();//Emit Signal
894 ARDOUR_UI_UTILS::resize_window_to_proportion_of_monitor (Gtk::Window* window, int max_width, int max_height)
896 Glib::RefPtr<Gdk::Screen> screen = window->get_screen ();
897 Gdk::Rectangle monitor_rect;
898 screen->get_monitor_geometry (0, monitor_rect);
900 int const w = std::min (int (monitor_rect.get_width() * 0.8), max_width);
901 int const h = std::min (int (monitor_rect.get_height() * 0.8), max_height);
903 window->resize (w, h);
907 /** Replace _ with __ in a string; for use with menu item text to make underscores displayed correctly */
909 ARDOUR_UI_UTILS::escape_underscores (string const & s)
912 string::size_type const N = s.length ();
914 for (string::size_type i = 0; i < N; ++i) {
925 /** Replace < and > with < and > respectively to make < > display correctly in markup strings */
927 ARDOUR_UI_UTILS::escape_angled_brackets (string const & s)
930 boost::replace_all (o, "<", "<");
931 boost::replace_all (o, ">", ">");
936 ARDOUR_UI_UTILS::unique_random_color (list<Gdk::Color>& used_colors)
944 h = fmod (random(), 360.0);
945 s = (random() % 65535) / 65535.0;
946 v = (random() % 65535) / 65535.0;
948 s = min (0.5, s); /* not too saturated */
949 v = max (0.9, v); /* not too bright */
950 newcolor.set_hsv (h, s, v);
952 if (used_colors.size() == 0) {
953 used_colors.push_back (newcolor);
957 for (list<Gdk::Color>::iterator i = used_colors.begin(); i != used_colors.end(); ++i) {
959 float rdelta, bdelta, gdelta;
961 rdelta = newcolor.get_red() - c.get_red();
962 bdelta = newcolor.get_blue() - c.get_blue();
963 gdelta = newcolor.get_green() - c.get_green();
965 if (sqrt (rdelta*rdelta + bdelta*bdelta + gdelta*gdelta) > 25.0) {
966 /* different enough */
967 used_colors.push_back (newcolor);
972 /* XXX need throttle here to make sure we don't spin for ever */
977 ARDOUR_UI_UTILS::rate_as_string (float r)
980 if (fmod (r, 1000.0f)) {
981 snprintf (buf, sizeof (buf), "%.1f kHz", r/1000.0);
983 snprintf (buf, sizeof (buf), "%.0f kHz", r/1000.0);