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