Merge branch 'master' into cairocanvas
[ardour.git] / gtk2_ardour / utils.cc
1 /*
2     Copyright (C) 2003 Paul Davis
3
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.
8
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.
13
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.
17
18 */
19
20 #ifdef WAF_BUILD
21 #include "gtk2ardour-config.h"
22 #endif
23
24 #include <pango/pangoft2.h> // for fontmap resolution control for GnomeCanvas
25 #include <pango/pangocairo.h> // for fontmap resolution control for GnomeCanvas
26
27 #include <cstdlib>
28 #include <clocale>
29 #include <cstring>
30 #include <cctype>
31 #include <fstream>
32 #include <list>
33 #include <sys/stat.h>
34 #include <gtkmm/rc.h>
35 #include <gtkmm/window.h>
36 #include <gtkmm/combo.h>
37 #include <gtkmm/label.h>
38 #include <gtkmm/paned.h>
39 #include <gtk/gtkpaned.h>
40 #include <boost/algorithm/string.hpp>
41
42 #include "pbd/file_utils.h"
43
44 #include <gtkmm2ext/utils.h>
45 #include "ardour/rc_configuration.h"
46 #include "ardour/filesystem_paths.h"
47 #include "canvas/item.h"
48
49 #include "ardour_ui.h"
50 #include "debug.h"
51 #include "public_editor.h"
52 #include "keyboard.h"
53 #include "utils.h"
54 #include "i18n.h"
55 #include "rgb_macros.h"
56 #include "gui_thread.h"
57
58 using namespace std;
59 using namespace Gtk;
60 using namespace Glib;
61 using namespace PBD;
62 using Gtkmm2ext::Keyboard;
63
64 sigc::signal<void>  DPIReset;
65
66
67 /** Add an element to a menu, settings its sensitivity.
68  * @param m Menu to add to.
69  * @param e Element to add.
70  * @param s true to make sensitive, false to make insensitive
71  */
72 void
73 add_item_with_sensitivity (Menu_Helpers::MenuList& m, Menu_Helpers::MenuElem e, bool s)
74 {
75         m.push_back (e);
76         if (!s) {
77                 m.back().set_sensitive (false);
78         }
79 }
80
81
82 gint
83 just_hide_it (GdkEventAny */*ev*/, Gtk::Window *win)
84 {
85         win->hide ();
86         return 0;
87 }
88
89 /* xpm2rgb copied from nixieclock, which bore the legend:
90
91     nixieclock - a nixie desktop timepiece
92     Copyright (C) 2000 Greg Ercolano, erco@3dsite.com
93
94     and was released under the GPL.
95 */
96
97 unsigned char*
98 xpm2rgb (const char** xpm, uint32_t& w, uint32_t& h)
99 {
100         static long vals[256], val;
101         uint32_t t, x, y, colors, cpp;
102         unsigned char c;
103         unsigned char *savergb, *rgb;
104
105         // PARSE HEADER
106
107         if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
108                 error << string_compose (_("bad XPM header %1"), xpm[0])
109                       << endmsg;
110                 return 0;
111         }
112
113         savergb = rgb = (unsigned char*) malloc (h * w * 3);
114
115         // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
116         for (t = 0; t < colors; ++t) {
117                 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
118                 vals[c] = val;
119         }
120
121         // COLORMAP -> RGB CONVERSION
122         //    Get low 3 bytes from vals[]
123         //
124
125         const char *p;
126         for (y = h-1; y > 0; --y) {
127
128                 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 3) {
129                         val = vals[(int)*p++];
130                         *(rgb+2) = val & 0xff; val >>= 8;  // 2:B
131                         *(rgb+1) = val & 0xff; val >>= 8;  // 1:G
132                         *(rgb+0) = val & 0xff;             // 0:R
133                 }
134         }
135
136         return (savergb);
137 }
138
139 unsigned char*
140 xpm2rgba (const char** xpm, uint32_t& w, uint32_t& h)
141 {
142         static long vals[256], val;
143         uint32_t t, x, y, colors, cpp;
144         unsigned char c;
145         unsigned char *savergb, *rgb;
146         char transparent;
147
148         // PARSE HEADER
149
150         if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
151                 error << string_compose (_("bad XPM header %1"), xpm[0])
152                       << endmsg;
153                 return 0;
154         }
155
156         savergb = rgb = (unsigned char*) malloc (h * w * 4);
157
158         // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
159
160         if (strstr (xpm[1], "None")) {
161                 sscanf (xpm[1], "%c", &transparent);
162                 t = 1;
163         } else {
164                 transparent = 0;
165                 t = 0;
166         }
167
168         for (; t < colors; ++t) {
169                 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
170                 vals[c] = val;
171         }
172
173         // COLORMAP -> RGB CONVERSION
174         //    Get low 3 bytes from vals[]
175         //
176
177         const char *p;
178         for (y = h-1; y > 0; --y) {
179
180                 char alpha;
181
182                 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 4) {
183
184                         if (transparent && (*p++ == transparent)) {
185                                 alpha = 0;
186                                 val = 0;
187                         } else {
188                                 alpha = 255;
189                                 val = vals[(int)*p];
190                         }
191
192                         *(rgb+3) = alpha;                  // 3: alpha
193                         *(rgb+2) = val & 0xff; val >>= 8;  // 2:B
194                         *(rgb+1) = val & 0xff; val >>= 8;  // 1:G
195                         *(rgb+0) = val & 0xff;             // 0:R
196                 }
197         }
198
199         return (savergb);
200 }
201
202 ArdourCanvas::Points*
203 get_canvas_points (string /*who*/, uint32_t npoints)
204 {
205         // cerr << who << ": wants " << npoints << " canvas points" << endl;
206 #ifdef TRAP_EXCESSIVE_POINT_REQUESTS
207         if (npoints > (uint32_t) gdk_screen_width() + 4) {
208                 abort ();
209         }
210 #endif
211         return new ArdourCanvas::Points (npoints);
212 }
213
214 Pango::FontDescription
215 get_font_for_style (string widgetname)
216 {
217         Gtk::Window window (WINDOW_TOPLEVEL);
218         Gtk::Label foobar;
219         Glib::RefPtr<Gtk::Style> style;
220
221         window.add (foobar);
222         foobar.set_name (widgetname);
223         foobar.ensure_style();
224
225         style = foobar.get_style ();
226
227         Glib::RefPtr<const Pango::Layout> layout = foobar.get_layout();
228
229         PangoFontDescription *pfd = const_cast<PangoFontDescription *> (pango_layout_get_font_description(const_cast<PangoLayout *>(layout->gobj())));
230
231         if (!pfd) {
232
233                 /* layout inherited its font description from a PangoContext */
234
235                 PangoContext* ctxt = (PangoContext*) pango_layout_get_context (const_cast<PangoLayout*>(layout->gobj()));
236                 pfd =  pango_context_get_font_description (ctxt);
237                 return Pango::FontDescription (pfd); /* make a copy */
238         }
239
240         return Pango::FontDescription (pfd); /* make a copy */
241 }
242
243 uint32_t
244 rgba_from_style (string style, uint32_t r, uint32_t g, uint32_t b, uint32_t a, string attr, int state, bool rgba)
245 {
246         /* In GTK+2, styles aren't set up correctly if the widget is not
247            attached to a toplevel window that has a screen pointer.
248         */
249
250         static Gtk::Window* window = 0;
251
252         if (window == 0) {
253                 window = new Window (WINDOW_TOPLEVEL);
254         }
255
256         Gtk::Label foo;
257
258         window->add (foo);
259
260         foo.set_name (style);
261         foo.ensure_style ();
262
263         GtkRcStyle* rc = foo.get_style()->gobj()->rc_style;
264
265         if (rc) {
266                 if (attr == "fg") {
267                         r = rc->fg[state].red / 257;
268                         g = rc->fg[state].green / 257;
269                         b = rc->fg[state].blue / 257;
270
271                         /* what a hack ... "a" is for "active" */
272                         if (state == Gtk::STATE_NORMAL && rgba) {
273                                 a = rc->fg[GTK_STATE_ACTIVE].red / 257;
274                         }
275                 } else if (attr == "bg") {
276                         r = g = b = 0;
277                         r = rc->bg[state].red / 257;
278                         g = rc->bg[state].green / 257;
279                         b = rc->bg[state].blue / 257;
280                 } else if (attr == "base") {
281                         r = rc->base[state].red / 257;
282                         g = rc->base[state].green / 257;
283                         b = rc->base[state].blue / 257;
284                 } else if (attr == "text") {
285                         r = rc->text[state].red / 257;
286                         g = rc->text[state].green / 257;
287                         b = rc->text[state].blue / 257;
288                 }
289         } else {
290                 warning << string_compose (_("missing RGBA style for \"%1\""), style) << endl;
291         }
292
293         window->remove ();
294
295         if (state == Gtk::STATE_NORMAL && rgba) {
296                 return (uint32_t) RGBA_TO_UINT(r,g,b,a);
297         } else {
298                 return (uint32_t) RGB_TO_UINT(r,g,b);
299         }
300 }
301
302 void
303 set_color (Gdk::Color& c, int rgb)
304 {
305         c.set_rgb((rgb >> 16)*256, ((rgb & 0xff00) >> 8)*256, (rgb & 0xff)*256);
306 }
307
308 bool
309 relay_key_press (GdkEventKey* ev, Gtk::Window* win)
310 {
311         if (!key_press_focus_accelerator_handler (*win, ev)) {
312                 return PublicEditor::instance().on_key_press_event(ev);
313         } else {
314                 return true;
315         }
316 }
317
318 bool
319 forward_key_press (GdkEventKey* ev)
320 {
321         return PublicEditor::instance().on_key_press_event(ev);
322 }
323
324 bool
325 key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev)
326 {
327         GtkWindow* win = window.gobj();
328         GtkWidget* focus = gtk_window_get_focus (win);
329         bool special_handling_of_unmodified_accelerators = false;
330         bool allow_activating = true;
331         /* consider all relevant modifiers but not LOCK or SHIFT */
332         const guint mask = (Keyboard::RelevantModifierKeyMask & ~(Gdk::SHIFT_MASK|Gdk::LOCK_MASK));
333
334         if (focus) {
335                 if (GTK_IS_ENTRY(focus) || Keyboard::some_magic_widget_has_focus()) {
336                         special_handling_of_unmodified_accelerators = true;
337                 }
338         }
339
340 #ifdef GTKOSX
341         /* at one time this appeared to be necessary. As of July 2012, it does not
342            appear to be. if it ever is necessar, figure out if it should apply
343            to all platforms.
344         */
345 #if 0 
346         if (Keyboard::some_magic_widget_has_focus ()) {
347                 allow_activating = false;
348         }
349 #endif
350 #endif
351
352
353         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",
354                                                           win,
355                                                           ev->keyval,
356                                                           ev->state,
357                                                           special_handling_of_unmodified_accelerators,
358                                                           Keyboard::some_magic_widget_has_focus(),
359                                                           allow_activating,
360                                                           focus));
361
362         /* This exists to allow us to override the way GTK handles
363            key events. The normal sequence is:
364
365            a) event is delivered to a GtkWindow
366            b) accelerators/mnemonics are activated
367            c) if (b) didn't handle the event, propagate to
368                the focus widget and/or focus chain
369
370            The problem with this is that if the accelerators include
371            keys without modifiers, such as the space bar or the
372            letter "e", then pressing the key while typing into
373            a text entry widget results in the accelerator being
374            activated, instead of the desired letter appearing
375            in the text entry.
376
377            There is no good way of fixing this, but this
378            represents a compromise. The idea is that
379            key events involving modifiers (not Shift)
380            get routed into the activation pathway first, then
381            get propagated to the focus widget if necessary.
382
383            If the key event doesn't involve modifiers,
384            we deliver to the focus widget first, thus allowing
385            it to get "normal text" without interference
386            from acceleration.
387
388            Of course, this can also be problematic: if there
389            is a widget with focus, then it will swallow
390            all "normal text" accelerators.
391         */
392
393         if (!special_handling_of_unmodified_accelerators) {
394
395                 /* XXX note that for a brief moment, the conditional above
396                  * included "|| (ev->state & mask)" so as to enforce the
397                  * implication of special_handling_of_UNMODIFIED_accelerators.
398                  * however, this forces any key that GTK doesn't allow and that
399                  * we have an alternative (see next comment) for to be
400                  * automatically sent through the accel groups activation
401                  * pathway, which prevents individual widgets & canvas items
402                  * from ever seeing it if is used by a key binding.
403                  * 
404                  * specifically, this hid Ctrl-down-arrow from MIDI region
405                  * views because it is also bound to an action.
406                  *
407                  * until we have a robust, clean binding system, this
408                  * quirk will have to remain in place.
409                  */
410
411                 /* pretend that certain key events that GTK does not allow
412                    to be used as accelerators are actually something that
413                    it does allow. but only where there are no modifiers.
414                 */
415
416                 uint32_t fakekey = ev->keyval;
417
418                 if (Gtkmm2ext::possibly_translate_keyval_to_make_legal_accelerator (fakekey)) {
419                         DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tactivate (was %1 now %2) without special hanlding of unmodified accels\n",
420                                                                           ev->keyval, fakekey));
421
422                         GdkModifierType mod = GdkModifierType (ev->state);
423
424                         mod = GdkModifierType (mod & gtk_accelerator_get_default_mod_mask());
425 #ifdef GTKOSX
426                         /* GTK on OS X is currently (February 2012) setting both
427                            the Meta and Mod2 bits in the event modifier state if 
428                            the Command key is down.
429
430                            gtk_accel_groups_activate() does not invoke any of the logic
431                            that gtk_window_activate_key() will that sorts out that stupid
432                            state of affairs, and as a result it fails to find a match
433                            for the key event and the current set of accelerators.
434
435                            to fix this, if the meta bit is set, remove the mod2 bit
436                            from the modifier. this assumes that our bindings use Primary
437                            which will have set the meta bit in the accelerator entry.
438                         */
439                         if (mod & GDK_META_MASK) {
440                                 mod = GdkModifierType (mod & ~GDK_MOD2_MASK);
441                         }
442 #endif
443
444                         if (allow_activating && gtk_accel_groups_activate(G_OBJECT(win), fakekey, mod)) {
445                                 DEBUG_TRACE (DEBUG::Accelerators, "\taccel group activated by fakekey\n");
446                                 return true;
447                         }
448                 }
449         }
450
451         if (!special_handling_of_unmodified_accelerators || (ev->state & mask)) {
452
453                 /* no special handling or there are modifiers in effect: accelerate first */
454
455                 DEBUG_TRACE (DEBUG::Accelerators, "\tactivate, then propagate\n");
456
457                 if (allow_activating) {
458                         DEBUG_TRACE (DEBUG::Accelerators, "\tsending to window\n");
459                         if (gtk_window_activate_key (win, ev)) {
460                                 DEBUG_TRACE (DEBUG::Accelerators, "\t\thandled\n");
461                                 return true;
462                         }
463                 } else {
464                         DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
465                 }
466
467                 DEBUG_TRACE (DEBUG::Accelerators, "\tnot accelerated, now propagate\n");
468
469                 return gtk_window_propagate_key_event (win, ev);
470         }
471
472         /* no modifiers, propagate first */
473
474         DEBUG_TRACE (DEBUG::Accelerators, "\tpropagate, then activate\n");
475
476         if (!gtk_window_propagate_key_event (win, ev)) {
477                 DEBUG_TRACE (DEBUG::Accelerators, "\tpropagation didn't handle, so activate\n");
478                 if (allow_activating) {
479                         return gtk_window_activate_key (win, ev);
480                 } else {
481                         DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
482                 }
483
484         } else {
485                 DEBUG_TRACE (DEBUG::Accelerators, "\thandled by propagate\n");
486                 return true;
487         }
488
489         DEBUG_TRACE (DEBUG::Accelerators, "\tnot handled\n");
490         return true;
491 }
492
493 Glib::RefPtr<Gdk::Pixbuf>
494 get_xpm (std::string name)
495 {
496         if (!xpm_map[name]) {
497
498                 SearchPath spath(ARDOUR::ardour_data_search_path());
499
500                 spath.add_subdirectory_to_paths("pixmaps");
501
502                 std::string data_file_path;
503
504                 if(!find_file_in_search_path (spath, name, data_file_path)) {
505                         fatal << string_compose (_("cannot find XPM file for %1"), name) << endmsg;
506                 }
507
508                 try {
509                         xpm_map[name] =  Gdk::Pixbuf::create_from_file (data_file_path);
510                 } catch(const Glib::Error& e)   {
511                         warning << "Caught Glib::Error: " << e.what() << endmsg;
512                 }
513         }
514
515         return xpm_map[name];
516 }
517
518 std::string
519 get_icon_path (const char* cname)
520 {
521         string name = cname;
522         name += X_(".png");
523
524         SearchPath spath(ARDOUR::ardour_data_search_path());
525
526         spath.add_subdirectory_to_paths("icons");
527
528         std::string data_file_path;
529
530         if (!find_file_in_search_path (spath, name, data_file_path)) {
531                 fatal << string_compose (_("cannot find icon image for %1 using %2"), name, spath.to_string()) << endmsg;
532         }
533
534         return data_file_path;
535 }
536
537 Glib::RefPtr<Gdk::Pixbuf>
538 get_icon (const char* cname)
539 {
540         Glib::RefPtr<Gdk::Pixbuf> img;
541         try {
542                 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname));
543         } catch (const Gdk::PixbufError &e) {
544                 cerr << "Caught PixbufError: " << e.what() << endl;
545         } catch (...) {
546                 error << string_compose (_("Caught exception while loading icon named %1"), cname) << endmsg;
547         }
548
549         return img;
550 }
551
552 string
553 longest (vector<string>& strings)
554 {
555         if (strings.empty()) {
556                 return string ("");
557         }
558
559         vector<string>::iterator longest = strings.begin();
560         string::size_type longest_length = (*longest).length();
561
562         vector<string>::iterator i = longest;
563         ++i;
564
565         while (i != strings.end()) {
566
567                 string::size_type len = (*i).length();
568
569                 if (len > longest_length) {
570                         longest = i;
571                         longest_length = len;
572                 }
573
574                 ++i;
575         }
576
577         return *longest;
578 }
579
580 bool
581 key_is_legal_for_numeric_entry (guint keyval)
582 {
583         /* we assume that this does not change over the life of the process 
584          */
585
586         static int comma_decimal = -1;
587
588         switch (keyval) {
589         case GDK_period:
590         case GDK_comma:
591                 if (comma_decimal < 0) {
592                         std::lconv* lc = std::localeconv();
593                         if (strchr (lc->decimal_point, ',') != 0) {
594                                 comma_decimal = 1;
595                         } else {
596                                 comma_decimal = 0;
597                         }
598                 }
599                 break;
600         default:
601                 break;
602         }
603
604         switch (keyval) {
605         case GDK_decimalpoint:
606         case GDK_KP_Separator:
607                 return true;
608
609         case GDK_period:
610                 if (comma_decimal) {
611                         return false;
612                 } else {
613                         return true;
614                 }
615                 break;
616         case GDK_comma:
617                 if (comma_decimal) {
618                         return true;
619                 } else {
620                         return false;
621                 }
622                 break;
623         case GDK_minus:
624         case GDK_plus:
625         case GDK_0:
626         case GDK_1:
627         case GDK_2:
628         case GDK_3:
629         case GDK_4:
630         case GDK_5:
631         case GDK_6:
632         case GDK_7:
633         case GDK_8:
634         case GDK_9:
635         case GDK_KP_Add:
636         case GDK_KP_Subtract:
637         case GDK_KP_Decimal:
638         case GDK_KP_0:
639         case GDK_KP_1:
640         case GDK_KP_2:
641         case GDK_KP_3:
642         case GDK_KP_4:
643         case GDK_KP_5:
644         case GDK_KP_6:
645         case GDK_KP_7:
646         case GDK_KP_8:
647         case GDK_KP_9:
648         case GDK_Return:
649         case GDK_BackSpace:
650         case GDK_Delete:
651         case GDK_KP_Enter:
652         case GDK_Home:
653         case GDK_End:
654         case GDK_Left:
655         case GDK_Right:
656                 return true;
657
658         default:
659                 break;
660         }
661
662         return false;
663 }
664 void
665 set_pango_fontsize ()
666 {
667         long val = ARDOUR::Config->get_font_scale();
668
669         /* FT2 rendering - used by GnomeCanvas, sigh */
670
671         pango_ft2_font_map_set_resolution ((PangoFT2FontMap*) pango_ft2_font_map_new(), val/1024, val/1024);
672
673         /* Cairo rendering, in case there is any */
674
675         pango_cairo_font_map_set_resolution ((PangoCairoFontMap*) pango_cairo_font_map_get_default(), val/1024);
676 }
677
678 void
679 reset_dpi ()
680 {
681         long val = ARDOUR::Config->get_font_scale();
682         set_pango_fontsize ();
683         /* Xft rendering */
684
685         gtk_settings_set_long_property (gtk_settings_get_default(),
686                                         "gtk-xft-dpi", val, "ardour");
687         DPIReset();//Emit Signal
688 }
689
690 void
691 resize_window_to_proportion_of_monitor (Gtk::Window* window, int max_width, int max_height)
692 {
693         Glib::RefPtr<Gdk::Screen> screen = window->get_screen ();
694         Gdk::Rectangle monitor_rect;
695         screen->get_monitor_geometry (0, monitor_rect);
696
697         int const w = std::min (int (monitor_rect.get_width() * 0.8), max_width);
698         int const h = std::min (int (monitor_rect.get_height() * 0.8), max_height);
699
700         window->resize (w, h);
701 }
702
703
704 /** Replace _ with __ in a string; for use with menu item text to make underscores displayed correctly */
705 string
706 escape_underscores (string const & s)
707 {
708         string o;
709         string::size_type const N = s.length ();
710
711         for (string::size_type i = 0; i < N; ++i) {
712                 if (s[i] == '_') {
713                         o += "__";
714                 } else {
715                         o += s[i];
716                 }
717         }
718
719         return o;
720 }
721
722 /** Replace < and > with &lt; and &gt; respectively to make < > display correctly in markup strings */
723 string
724 escape_angled_brackets (string const & s)
725 {
726         string o = s;
727         boost::replace_all (o, "<", "&lt;");
728         boost::replace_all (o, ">", "&gt;");
729         return o;
730 }
731
732 Gdk::Color
733 unique_random_color (list<Gdk::Color>& used_colors)
734 {
735         Gdk::Color newcolor;
736
737         while (1) {
738
739                 /* avoid neon/glowing tones by limiting them to the
740                    "inner section" (paler) of a color wheel/circle.
741                 */
742
743                 const int32_t max_saturation = 48000; // 65535 would open up the whole color wheel
744
745                 newcolor.set_red (random() % max_saturation);
746                 newcolor.set_blue (random() % max_saturation);
747                 newcolor.set_green (random() % max_saturation);
748
749                 if (used_colors.size() == 0) {
750                         used_colors.push_back (newcolor);
751                         return newcolor;
752                 }
753
754                 for (list<Gdk::Color>::iterator i = used_colors.begin(); i != used_colors.end(); ++i) {
755                   Gdk::Color c = *i;
756                         float rdelta, bdelta, gdelta;
757
758                         rdelta = newcolor.get_red() - c.get_red();
759                         bdelta = newcolor.get_blue() - c.get_blue();
760                         gdelta = newcolor.get_green() - c.get_green();
761
762                         if (sqrt (rdelta*rdelta + bdelta*bdelta + gdelta*gdelta) > 25.0) {
763                                 used_colors.push_back (newcolor);
764                                 return newcolor;
765                         }
766                 }
767
768                 /* XXX need throttle here to make sure we don't spin for ever */
769         }
770 }