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 sigc::signal<void> DPIReset;
69 #ifdef PLATFORM_WINDOWS
70 #define random() rand()
74 /** Add an element to a menu, settings its sensitivity.
75 * @param m Menu to add to.
76 * @param e Element to add.
77 * @param s true to make sensitive, false to make insensitive
80 add_item_with_sensitivity (Menu_Helpers::MenuList& m, Menu_Helpers::MenuElem e, bool s)
84 m.back().set_sensitive (false);
90 just_hide_it (GdkEventAny */*ev*/, Gtk::Window *win)
96 /* xpm2rgb copied from nixieclock, which bore the legend:
98 nixieclock - a nixie desktop timepiece
99 Copyright (C) 2000 Greg Ercolano, erco@3dsite.com
101 and was released under the GPL.
105 xpm2rgb (const char** xpm, uint32_t& w, uint32_t& h)
107 static long vals[256], val;
108 uint32_t t, x, y, colors, cpp;
110 unsigned char *savergb, *rgb;
114 if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
115 error << string_compose (_("bad XPM header %1"), xpm[0])
120 savergb = rgb = (unsigned char*) malloc (h * w * 3);
122 // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
123 for (t = 0; t < colors; ++t) {
124 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
128 // COLORMAP -> RGB CONVERSION
129 // Get low 3 bytes from vals[]
133 for (y = h-1; y > 0; --y) {
135 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 3) {
136 val = vals[(int)*p++];
137 *(rgb+2) = val & 0xff; val >>= 8; // 2:B
138 *(rgb+1) = val & 0xff; val >>= 8; // 1:G
139 *(rgb+0) = val & 0xff; // 0:R
147 xpm2rgba (const char** xpm, uint32_t& w, uint32_t& h)
149 static long vals[256], val;
150 uint32_t t, x, y, colors, cpp;
152 unsigned char *savergb, *rgb;
157 if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
158 error << string_compose (_("bad XPM header %1"), xpm[0])
163 savergb = rgb = (unsigned char*) malloc (h * w * 4);
165 // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
167 if (strstr (xpm[1], "None")) {
168 sscanf (xpm[1], "%c", &transparent);
175 for (; t < colors; ++t) {
176 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
180 // COLORMAP -> RGB CONVERSION
181 // Get low 3 bytes from vals[]
185 for (y = h-1; y > 0; --y) {
189 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 4) {
191 if (transparent && (*p++ == transparent)) {
199 *(rgb+3) = alpha; // 3: alpha
200 *(rgb+2) = val & 0xff; val >>= 8; // 2:B
201 *(rgb+1) = val & 0xff; val >>= 8; // 1:G
202 *(rgb+0) = val & 0xff; // 0:R
209 /** Returns a Pango::FontDescription given a string describing the font.
211 * If the returned FontDescription does not specify a family, then
212 * the family is set to "Sans". This mirrors GTK's behaviour in
215 * Some environments will force Pango to specify the family
216 * even if it was not specified in the string describing the font.
217 * Such environments should be left unaffected by this function,
218 * since the font family will be left alone.
220 * There may be other similar font specification enforcement
221 * that we might add here later.
223 Pango::FontDescription
224 sanitized_font (std::string const& name)
226 Pango::FontDescription fd (name);
228 if (fd.get_family().empty()) {
229 fd.set_family ("Sans");
235 Pango::FontDescription
236 get_font_for_style (string widgetname)
238 Gtk::Window window (WINDOW_TOPLEVEL);
240 Glib::RefPtr<Gtk::Style> style;
243 foobar.set_name (widgetname);
244 foobar.ensure_style();
246 style = foobar.get_style ();
248 Glib::RefPtr<const Pango::Layout> layout = foobar.get_layout();
250 PangoFontDescription *pfd = const_cast<PangoFontDescription *> (pango_layout_get_font_description(const_cast<PangoLayout *>(layout->gobj())));
254 /* layout inherited its font description from a PangoContext */
256 PangoContext* ctxt = (PangoContext*) pango_layout_get_context (const_cast<PangoLayout*>(layout->gobj()));
257 pfd = pango_context_get_font_description (ctxt);
258 return Pango::FontDescription (pfd); /* make a copy */
261 return Pango::FontDescription (pfd); /* make a copy */
265 rgba_from_style (string style, uint32_t r, uint32_t g, uint32_t b, uint32_t a, string attr, int state, bool rgba)
267 /* In GTK+2, styles aren't set up correctly if the widget is not
268 attached to a toplevel window that has a screen pointer.
271 static Gtk::Window* window = 0;
274 window = new Window (WINDOW_TOPLEVEL);
281 foo.set_name (style);
284 GtkRcStyle* rc = foo.get_style()->gobj()->rc_style;
288 r = rc->fg[state].red / 257;
289 g = rc->fg[state].green / 257;
290 b = rc->fg[state].blue / 257;
292 /* what a hack ... "a" is for "active" */
293 if (state == Gtk::STATE_NORMAL && rgba) {
294 a = rc->fg[GTK_STATE_ACTIVE].red / 257;
296 } else if (attr == "bg") {
298 r = rc->bg[state].red / 257;
299 g = rc->bg[state].green / 257;
300 b = rc->bg[state].blue / 257;
301 } else if (attr == "base") {
302 r = rc->base[state].red / 257;
303 g = rc->base[state].green / 257;
304 b = rc->base[state].blue / 257;
305 } else if (attr == "text") {
306 r = rc->text[state].red / 257;
307 g = rc->text[state].green / 257;
308 b = rc->text[state].blue / 257;
311 warning << string_compose (_("missing RGBA style for \"%1\""), style) << endl;
316 if (state == Gtk::STATE_NORMAL && rgba) {
317 return (uint32_t) RGBA_TO_UINT(r,g,b,a);
319 return (uint32_t) RGBA_TO_UINT(r,g,b,255);
324 rgba_p_from_style (string style, float *r, float *g, float *b, string attr, int state)
326 static Gtk::Window* window = 0;
327 assert (r && g && b);
330 window = new Window (WINDOW_TOPLEVEL);
337 foo.set_name (style);
340 GtkRcStyle* rc = foo.get_style()->gobj()->rc_style;
343 warning << string_compose (_("missing RGBA style for \"%1\""), style) << endl;
347 *r = rc->fg[state].red / 65535.0;
348 *g = rc->fg[state].green / 65535.0;
349 *b = rc->fg[state].blue / 65535.0;
350 } else if (attr == "bg") {
351 *r = rc->bg[state].red / 65535.0;
352 *g = rc->bg[state].green / 65535.0;
353 *b = rc->bg[state].blue / 65535.0;
354 } else if (attr == "base") {
355 *r = rc->base[state].red / 65535.0;
356 *g = rc->base[state].green / 65535.0;
357 *b = rc->base[state].blue / 65535.0;
358 } else if (attr == "text") {
359 *r = rc->text[state].red / 65535.0;
360 *g = rc->text[state].green / 65535.0;
361 *b = rc->text[state].blue / 65535.0;
371 set_color_from_rgb (Gdk::Color& c, uint32_t rgb)
373 /* Gdk::Color color ranges are 16 bit, so scale from 8 bit by
376 c.set_rgb ((rgb >> 16)*256, ((rgb & 0xff00) >> 8)*256, (rgb & 0xff)*256);
380 set_color_from_rgba (Gdk::Color& c, uint32_t rgba)
382 /* Gdk::Color color ranges are 16 bit, so scale from 8 bit by
385 c.set_rgb ((rgba >> 24)*256, ((rgba & 0xff0000) >> 16)*256, ((rgba & 0xff00) >> 8)*256);
389 gdk_color_to_rgba (Gdk::Color const& c)
391 /* since alpha value is not available from a Gdk::Color, it is
392 hardcoded as 0xff (aka 255 or 1.0)
395 const uint32_t r = c.get_red_p () * 255.0;
396 const uint32_t g = c.get_green_p () * 255.0;
397 const uint32_t b = c.get_blue_p () * 255.0;
398 const uint32_t a = 0xff;
400 return RGBA_TO_UINT (r,g,b,a);
404 contrasting_text_color (uint32_t c)
407 ArdourCanvas::color_to_rgba (c, r, g, b, a);
409 const double black_r = 0.0;
410 const double black_g = 0.0;
411 const double black_b = 0.0;
413 const double white_r = 1.0;
414 const double white_g = 1.0;
415 const double white_b = 1.0;
417 /* Use W3C contrast guideline calculation */
419 double white_contrast = (max (r, white_r) - min (r, white_r)) +
420 (max (g, white_g) - min (g, white_g)) +
421 (max (b, white_b) - min (b, white_b));
423 double black_contrast = (max (r, black_r) - min (r, black_r)) +
424 (max (g, black_g) - min (g, black_g)) +
425 (max (b, black_b) - min (b, black_b));
427 if (white_contrast > black_contrast) {
429 return ArdourCanvas::rgba_to_color (1.0, 1.0, 1.0, 1.0);
432 return ArdourCanvas::rgba_to_color (0.0, 0.0, 0.0, 1.0);
437 relay_key_press (GdkEventKey* ev, Gtk::Window* win)
439 PublicEditor& ed (PublicEditor::instance());
441 if (!key_press_focus_accelerator_handler (*win, ev)) {
443 /* early key press in pre-main-window-dialogs, no editor yet */
446 return ed.on_key_press_event(ev);
453 forward_key_press (GdkEventKey* ev)
455 return PublicEditor::instance().on_key_press_event(ev);
459 emulate_key_event (Gtk::Widget* w, unsigned int keyval)
461 GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET(w->gobj()));
462 GdkKeymap *keymap = gdk_keymap_get_for_display (display);
463 GdkKeymapKey *keymapkey = NULL;
466 if (!gdk_keymap_get_entries_for_keyval(keymap, keyval, &keymapkey, &n_keys)) return false;
467 if (n_keys !=1) { g_free(keymapkey); return false;}
470 ev.type = GDK_KEY_PRESS;
471 ev.window = gtk_widget_get_window(GTK_WIDGET(w->gobj()));
472 ev.send_event = FALSE;
477 ev.string = const_cast<gchar*> ("");
478 ev.hardware_keycode = keymapkey[0].keycode;
479 ev.group = keymapkey[0].group;
482 forward_key_press(&ev);
483 ev.type = GDK_KEY_RELEASE;
484 return forward_key_press(&ev);
488 key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev)
490 GtkWindow* win = window.gobj();
491 GtkWidget* focus = gtk_window_get_focus (win);
492 bool special_handling_of_unmodified_accelerators = false;
493 bool allow_activating = true;
494 /* consider all relevant modifiers but not LOCK or SHIFT */
495 const guint mask = (Keyboard::RelevantModifierKeyMask & ~(Gdk::SHIFT_MASK|Gdk::LOCK_MASK));
498 if (GTK_IS_ENTRY(focus) || Keyboard::some_magic_widget_has_focus()) {
499 special_handling_of_unmodified_accelerators = true;
504 /* at one time this appeared to be necessary. As of July 2012, it does not
505 appear to be. if it ever is necessar, figure out if it should apply
509 if (Keyboard::some_magic_widget_has_focus ()) {
510 allow_activating = false;
516 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",
520 special_handling_of_unmodified_accelerators,
521 Keyboard::some_magic_widget_has_focus(),
525 /* This exists to allow us to override the way GTK handles
526 key events. The normal sequence is:
528 a) event is delivered to a GtkWindow
529 b) accelerators/mnemonics are activated
530 c) if (b) didn't handle the event, propagate to
531 the focus widget and/or focus chain
533 The problem with this is that if the accelerators include
534 keys without modifiers, such as the space bar or the
535 letter "e", then pressing the key while typing into
536 a text entry widget results in the accelerator being
537 activated, instead of the desired letter appearing
540 There is no good way of fixing this, but this
541 represents a compromise. The idea is that
542 key events involving modifiers (not Shift)
543 get routed into the activation pathway first, then
544 get propagated to the focus widget if necessary.
546 If the key event doesn't involve modifiers,
547 we deliver to the focus widget first, thus allowing
548 it to get "normal text" without interference
551 Of course, this can also be problematic: if there
552 is a widget with focus, then it will swallow
553 all "normal text" accelerators.
556 if (!special_handling_of_unmodified_accelerators) {
558 /* XXX note that for a brief moment, the conditional above
559 * included "|| (ev->state & mask)" so as to enforce the
560 * implication of special_handling_of_UNMODIFIED_accelerators.
561 * however, this forces any key that GTK doesn't allow and that
562 * we have an alternative (see next comment) for to be
563 * automatically sent through the accel groups activation
564 * pathway, which prevents individual widgets & canvas items
565 * from ever seeing it if is used by a key binding.
567 * specifically, this hid Ctrl-down-arrow from MIDI region
568 * views because it is also bound to an action.
570 * until we have a robust, clean binding system, this
571 * quirk will have to remain in place.
574 /* pretend that certain key events that GTK does not allow
575 to be used as accelerators are actually something that
576 it does allow. but only where there are no modifiers.
579 uint32_t fakekey = ev->keyval;
581 if (Gtkmm2ext::possibly_translate_keyval_to_make_legal_accelerator (fakekey)) {
582 DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tactivate (was %1 now %2) without special hanlding of unmodified accels\n",
583 ev->keyval, fakekey));
585 GdkModifierType mod = GdkModifierType (ev->state);
587 mod = GdkModifierType (mod & gtk_accelerator_get_default_mod_mask());
589 /* GTK on OS X is currently (February 2012) setting both
590 the Meta and Mod2 bits in the event modifier state if
591 the Command key is down.
593 gtk_accel_groups_activate() does not invoke any of the logic
594 that gtk_window_activate_key() will that sorts out that stupid
595 state of affairs, and as a result it fails to find a match
596 for the key event and the current set of accelerators.
598 to fix this, if the meta bit is set, remove the mod2 bit
599 from the modifier. this assumes that our bindings use Primary
600 which will have set the meta bit in the accelerator entry.
602 if (mod & GDK_META_MASK) {
603 mod = GdkModifierType (mod & ~GDK_MOD2_MASK);
607 if (allow_activating && gtk_accel_groups_activate(G_OBJECT(win), fakekey, mod)) {
608 DEBUG_TRACE (DEBUG::Accelerators, "\taccel group activated by fakekey\n");
614 if (!special_handling_of_unmodified_accelerators || (ev->state & mask)) {
616 /* no special handling or there are modifiers in effect: accelerate first */
618 DEBUG_TRACE (DEBUG::Accelerators, "\tactivate, then propagate\n");
619 DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tevent send-event:%1 time:%2 length:%3 string:%4 hardware_keycode:%5 group:%6\n",
620 ev->send_event, ev->time, ev->length, ev->string, ev->hardware_keycode, ev->group));
622 if (allow_activating) {
623 DEBUG_TRACE (DEBUG::Accelerators, "\tsending to window\n");
624 if (gtk_window_activate_key (win, ev)) {
625 DEBUG_TRACE (DEBUG::Accelerators, "\t\thandled\n");
629 DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
632 DEBUG_TRACE (DEBUG::Accelerators, "\tnot accelerated, now propagate\n");
634 return gtk_window_propagate_key_event (win, ev);
637 /* no modifiers, propagate first */
639 DEBUG_TRACE (DEBUG::Accelerators, "\tpropagate, then activate\n");
641 if (!gtk_window_propagate_key_event (win, ev)) {
642 DEBUG_TRACE (DEBUG::Accelerators, "\tpropagation didn't handle, so activate\n");
643 if (allow_activating) {
644 return gtk_window_activate_key (win, ev);
646 DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
650 DEBUG_TRACE (DEBUG::Accelerators, "\thandled by propagate\n");
654 DEBUG_TRACE (DEBUG::Accelerators, "\tnot handled\n");
658 Glib::RefPtr<Gdk::Pixbuf>
659 get_xpm (std::string name)
661 if (!xpm_map[name]) {
663 Searchpath spath(ARDOUR::ardour_data_search_path());
665 spath.add_subdirectory_to_paths("pixmaps");
667 std::string data_file_path;
669 if(!find_file_in_search_path (spath, name, data_file_path)) {
670 fatal << string_compose (_("cannot find XPM file for %1"), name) << endmsg;
674 xpm_map[name] = Gdk::Pixbuf::create_from_file (data_file_path);
675 } catch(const Glib::Error& e) {
676 warning << "Caught Glib::Error: " << e.what() << endmsg;
680 return xpm_map[name];
686 Searchpath spath(ARDOUR::ardour_data_search_path());
687 spath.add_subdirectory_to_paths ("icons");
690 r.push_back (_("default"));
692 for (vector<string>::iterator s = spath.begin(); s != spath.end(); ++s) {
694 vector<string> entries;
696 get_files_in_directory (*s, entries);
698 for (vector<string>::iterator e = entries.begin(); e != entries.end(); ++e) {
702 /* ignore dotfiles, . and .. */
704 if (d.empty() || d[0] == '.') {
708 Glib::ustring path = Glib::build_filename (*s, *e);
710 if (Glib::file_test (path, Glib::FILE_TEST_IS_DIR)) {
711 r.push_back (Glib::filename_to_utf8 (*e));
720 get_icon_path (const char* cname, string icon_set)
722 std::string data_file_path;
726 Searchpath spath(ARDOUR::ardour_data_search_path());
728 if (!icon_set.empty() && icon_set != _("default")) {
730 /* add "icons/icon_set" but .. not allowed to add both of these at once */
731 spath.add_subdirectory_to_paths ("icons");
732 spath.add_subdirectory_to_paths (icon_set);
734 find_file_in_search_path (spath, name, data_file_path);
737 if (data_file_path.empty()) {
739 if (!icon_set.empty() && icon_set != _("default")) {
740 warning << string_compose (_("icon \"%1\" not found for icon set \"%2\", fallback to default"), cname, icon_set) << endmsg;
743 Searchpath def (ARDOUR::ardour_data_search_path());
744 def.add_subdirectory_to_paths ("icons");
746 if (!find_file_in_search_path (def, name, data_file_path)) {
747 fatal << string_compose (_("cannot find icon image for %1 using %2"), name, spath.to_string()) << endmsg;
752 return data_file_path;
755 Glib::RefPtr<Gdk::Pixbuf>
756 get_icon (const char* cname, string icon_set)
758 Glib::RefPtr<Gdk::Pixbuf> img;
760 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname, icon_set));
761 } catch (const Gdk::PixbufError &e) {
762 cerr << "Caught PixbufError: " << e.what() << endl;
764 error << string_compose (_("Caught exception while loading icon named %1"), cname) << endmsg;
770 Glib::RefPtr<Gdk::Pixbuf>
771 get_icon (const char* cname)
773 Glib::RefPtr<Gdk::Pixbuf> img;
775 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname));
776 } catch (const Gdk::PixbufError &e) {
777 cerr << "Caught PixbufError: " << e.what() << endl;
779 error << string_compose (_("Caught exception while loading icon named %1"), cname) << endmsg;
786 longest (vector<string>& strings)
788 if (strings.empty()) {
792 vector<string>::iterator longest = strings.begin();
793 string::size_type longest_length = (*longest).length();
795 vector<string>::iterator i = longest;
798 while (i != strings.end()) {
800 string::size_type len = (*i).length();
802 if (len > longest_length) {
804 longest_length = len;
814 key_is_legal_for_numeric_entry (guint keyval)
816 /* we assume that this does not change over the life of the process
819 static int comma_decimal = -1;
824 if (comma_decimal < 0) {
825 std::lconv* lc = std::localeconv();
826 if (strchr (lc->decimal_point, ',') != 0) {
838 case GDK_decimalpoint:
839 case GDK_KP_Separator:
869 case GDK_KP_Subtract:
898 set_pango_fontsize ()
900 long val = ARDOUR::Config->get_font_scale();
902 /* FT2 rendering - used by GnomeCanvas, sigh */
904 #ifndef PLATFORM_WINDOWS
905 pango_ft2_font_map_set_resolution ((PangoFT2FontMap*) pango_ft2_font_map_new(), val/1024, val/1024);
908 /* Cairo rendering, in case there is any */
910 pango_cairo_font_map_set_resolution ((PangoCairoFontMap*) pango_cairo_font_map_get_default(), val/1024);
916 long val = ARDOUR::Config->get_font_scale();
917 set_pango_fontsize ();
920 gtk_settings_set_long_property (gtk_settings_get_default(),
921 "gtk-xft-dpi", val, "ardour");
922 DPIReset();//Emit Signal
926 resize_window_to_proportion_of_monitor (Gtk::Window* window, int max_width, int max_height)
928 Glib::RefPtr<Gdk::Screen> screen = window->get_screen ();
929 Gdk::Rectangle monitor_rect;
930 screen->get_monitor_geometry (0, monitor_rect);
932 int const w = std::min (int (monitor_rect.get_width() * 0.8), max_width);
933 int const h = std::min (int (monitor_rect.get_height() * 0.8), max_height);
935 window->resize (w, h);
939 /** Replace _ with __ in a string; for use with menu item text to make underscores displayed correctly */
941 escape_underscores (string const & s)
944 string::size_type const N = s.length ();
946 for (string::size_type i = 0; i < N; ++i) {
957 /** Replace < and > with < and > respectively to make < > display correctly in markup strings */
959 escape_angled_brackets (string const & s)
962 boost::replace_all (o, "<", "<");
963 boost::replace_all (o, ">", ">");
968 unique_random_color (list<Gdk::Color>& used_colors)
976 h = fmod (random(), 360.0);
977 s = (random() % 65535) / 65535.0;
978 v = (random() % 65535) / 65535.0;
980 s = min (0.5, s); /* not too saturated */
981 v = max (0.9, v); /* not too bright */
982 newcolor.set_hsv (h, s, v);
984 if (used_colors.size() == 0) {
985 used_colors.push_back (newcolor);
989 for (list<Gdk::Color>::iterator i = used_colors.begin(); i != used_colors.end(); ++i) {
991 float rdelta, bdelta, gdelta;
993 rdelta = newcolor.get_red() - c.get_red();
994 bdelta = newcolor.get_blue() - c.get_blue();
995 gdelta = newcolor.get_green() - c.get_green();
997 if (sqrt (rdelta*rdelta + bdelta*bdelta + gdelta*gdelta) > 25.0) {
998 /* different enough */
999 used_colors.push_back (newcolor);
1004 /* XXX need throttle here to make sure we don't spin for ever */
1009 rate_as_string (float r)
1012 if (fmod (r, 1000.0f)) {
1013 snprintf (buf, sizeof (buf), "%.1f kHz", r/1000.0);
1015 snprintf (buf, sizeof (buf), "%.0f kHz", r/1000.0);