use lucida grande for clocks with all OS X versions, BUT BETTER
[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_decimalpoint:
614         case GDK_KP_Separator:
615                 return true;
616
617         case GDK_period:
618                 if (comma_decimal) {
619                         return false;
620                 } else {
621                         return true;
622                 }
623                 break;
624         case GDK_comma:
625                 if (comma_decimal) {
626                         return true;
627                 } else {
628                         return false;
629                 }
630                 break;
631         case GDK_minus:
632         case GDK_plus:
633         case GDK_0:
634         case GDK_1:
635         case GDK_2:
636         case GDK_3:
637         case GDK_4:
638         case GDK_5:
639         case GDK_6:
640         case GDK_7:
641         case GDK_8:
642         case GDK_9:
643         case GDK_KP_Add:
644         case GDK_KP_Subtract:
645         case GDK_KP_Decimal:
646         case GDK_KP_0:
647         case GDK_KP_1:
648         case GDK_KP_2:
649         case GDK_KP_3:
650         case GDK_KP_4:
651         case GDK_KP_5:
652         case GDK_KP_6:
653         case GDK_KP_7:
654         case GDK_KP_8:
655         case GDK_KP_9:
656         case GDK_Return:
657         case GDK_BackSpace:
658         case GDK_Delete:
659         case GDK_KP_Enter:
660         case GDK_Home:
661         case GDK_End:
662         case GDK_Left:
663         case GDK_Right:
664                 return true;
665
666         default:
667                 break;
668         }
669
670         return false;
671 }
672 void
673 set_pango_fontsize ()
674 {
675         long val = ARDOUR::Config->get_font_scale();
676
677         /* FT2 rendering - used by GnomeCanvas, sigh */
678
679         pango_ft2_font_map_set_resolution ((PangoFT2FontMap*) pango_ft2_font_map_for_display(), val/1024, val/1024);
680
681         /* Cairo rendering, in case there is any */
682
683         pango_cairo_font_map_set_resolution ((PangoCairoFontMap*) pango_cairo_font_map_get_default(), val/1024);
684 }
685
686 void
687 reset_dpi ()
688 {
689         long val = ARDOUR::Config->get_font_scale();
690         set_pango_fontsize ();
691         /* Xft rendering */
692
693         gtk_settings_set_long_property (gtk_settings_get_default(),
694                                         "gtk-xft-dpi", val, "ardour");
695         DPIReset();//Emit Signal
696 }
697
698 void
699 resize_window_to_proportion_of_monitor (Gtk::Window* window, int max_width, int max_height)
700 {
701         Glib::RefPtr<Gdk::Screen> screen = window->get_screen ();
702         Gdk::Rectangle monitor_rect;
703         screen->get_monitor_geometry (0, monitor_rect);
704
705         int const w = std::min (int (monitor_rect.get_width() * 0.8), max_width);
706         int const h = std::min (int (monitor_rect.get_height() * 0.8), max_height);
707
708         window->resize (w, h);
709 }
710
711
712 /** Replace _ with __ in a string; for use with menu item text to make underscores displayed correctly */
713 string
714 escape_underscores (string const & s)
715 {
716         string o;
717         string::size_type const N = s.length ();
718
719         for (string::size_type i = 0; i < N; ++i) {
720                 if (s[i] == '_') {
721                         o += "__";
722                 } else {
723                         o += s[i];
724                 }
725         }
726
727         return o;
728 }
729
730 /** Replace < and > with &lt; and &gt; respectively to make < > display correctly in markup strings */
731 string
732 escape_angled_brackets (string const & s)
733 {
734         string o = s;
735         boost::replace_all (o, "<", "&lt;");
736         boost::replace_all (o, ">", "&gt;");
737         return o;
738 }
739
740 Gdk::Color
741 unique_random_color (list<Gdk::Color>& used_colors)
742 {
743         Gdk::Color newcolor;
744
745         while (1) {
746
747                 /* avoid neon/glowing tones by limiting them to the
748                    "inner section" (paler) of a color wheel/circle.
749                 */
750
751                 const int32_t max_saturation = 48000; // 65535 would open up the whole color wheel
752
753                 newcolor.set_red (random() % max_saturation);
754                 newcolor.set_blue (random() % max_saturation);
755                 newcolor.set_green (random() % max_saturation);
756
757                 if (used_colors.size() == 0) {
758                         used_colors.push_back (newcolor);
759                         return newcolor;
760                 }
761
762                 for (list<Gdk::Color>::iterator i = used_colors.begin(); i != used_colors.end(); ++i) {
763                   Gdk::Color c = *i;
764                         float rdelta, bdelta, gdelta;
765
766                         rdelta = newcolor.get_red() - c.get_red();
767                         bdelta = newcolor.get_blue() - c.get_blue();
768                         gdelta = newcolor.get_green() - c.get_green();
769
770                         if (sqrt (rdelta*rdelta + bdelta*bdelta + gdelta*gdelta) > 25.0) {
771                                 used_colors.push_back (newcolor);
772                                 return newcolor;
773                         }
774                 }
775
776                 /* XXX need throttle here to make sure we don't spin for ever */
777         }
778 }