Reinstate command line option to hide splash screen.
[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 <cctype>
29 #include <fstream>
30 #include <list>
31 #include <sys/stat.h>
32 #include <libart_lgpl/art_misc.h>
33 #include <gtkmm/rc.h>
34 #include <gtkmm/window.h>
35 #include <gtkmm/combo.h>
36 #include <gtkmm/label.h>
37 #include <gtkmm/paned.h>
38 #include <gtk/gtkpaned.h>
39
40 #include "pbd/file_utils.h"
41
42 #include <gtkmm2ext/utils.h>
43 #include "ardour/configuration.h"
44 #include "ardour/rc_configuration.h"
45
46 #include "ardour/filesystem_paths.h"
47
48 #include "ardour_ui.h"
49 #include "debug.h"
50 #include "public_editor.h"
51 #include "keyboard.h"
52 #include "utils.h"
53 #include "i18n.h"
54 #include "rgb_macros.h"
55 #include "canvas_impl.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 = (PangoFontDescription *)pango_layout_get_font_description((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 ((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
303 Gdk::Color
304 color_from_style (string widget_style_name, int state, string attr)
305 {
306         GtkStyle* style;
307
308         style = gtk_rc_get_style_by_paths (gtk_settings_get_default(),
309                                            widget_style_name.c_str(),
310                                            0, G_TYPE_NONE);
311
312         if (!style) {
313                 error << string_compose (_("no style found for %1, using red"), style) << endmsg;
314                 return Gdk::Color ("red");
315         }
316
317         if (attr == "fg") {
318                 return Gdk::Color (&style->fg[state]);
319         }
320
321         if (attr == "bg") {
322                 return Gdk::Color (&style->bg[state]);
323         }
324
325         if (attr == "light") {
326                 return Gdk::Color (&style->light[state]);
327         }
328
329         if (attr == "dark") {
330                 return Gdk::Color (&style->dark[state]);
331         }
332
333         if (attr == "mid") {
334                 return Gdk::Color (&style->mid[state]);
335         }
336
337         if (attr == "text") {
338                 return Gdk::Color (&style->text[state]);
339         }
340
341         if (attr == "base") {
342                 return Gdk::Color (&style->base[state]);
343         }
344
345         if (attr == "text_aa") {
346                 return Gdk::Color (&style->text_aa[state]);
347         }
348
349         error << string_compose (_("unknown style attribute %1 requested for color; using \"red\""), attr) << endmsg;
350         return Gdk::Color ("red");
351 }
352
353 Glib::RefPtr<Gdk::GC>
354 gc_from_style (string widget_style_name, int state, string attr)
355 {
356         GtkStyle* style;
357
358         style = gtk_rc_get_style_by_paths (gtk_settings_get_default(),
359                                            widget_style_name.c_str(),
360                                            0, G_TYPE_NONE);
361
362         if (!style) {
363                 error << string_compose (_("no style found for %1, using red"), style) << endmsg;
364                 Glib::RefPtr<Gdk::GC> ret = Gdk::GC::create();
365                 ret->set_rgb_fg_color(Gdk::Color("red"));
366                 return ret;
367         }
368
369         if (attr == "fg") {
370                 return Glib::wrap(style->fg_gc[state]);
371         }
372
373         if (attr == "bg") {
374                 return Glib::wrap(style->bg_gc[state]);
375         }
376
377         if (attr == "light") {
378                 return Glib::wrap(style->light_gc[state]);
379         }
380
381         if (attr == "dark") {
382                 return Glib::wrap(style->dark_gc[state]);
383         }
384
385         if (attr == "mid") {
386                 return Glib::wrap(style->mid_gc[state]);
387         }
388
389         if (attr == "text") {
390                 return Glib::wrap(style->text_gc[state]);
391         }
392
393         if (attr == "base") {
394                 return Glib::wrap(style->base_gc[state]);
395         }
396
397         if (attr == "text_aa") {
398                 return Glib::wrap(style->text_aa_gc[state]);
399         }
400
401         error << string_compose (_("unknown style attribute %1 requested for color; using \"red\""), attr) << endmsg;
402         Glib::RefPtr<Gdk::GC> ret = Gdk::GC::create();
403         ret->set_rgb_fg_color(Gdk::Color("red"));
404         return ret;
405 }
406
407
408 bool
409 canvas_item_visible (ArdourCanvas::Item* item)
410 {
411         return (item->gobj()->object.flags & GNOME_CANVAS_ITEM_VISIBLE) ? true : false;
412 }
413
414 void
415 set_color (Gdk::Color& c, int rgb)
416 {
417         c.set_rgb((rgb >> 16)*256, ((rgb & 0xff00) >> 8)*256, (rgb & 0xff)*256);
418 }
419
420 bool
421 relay_key_press (GdkEventKey* ev, Gtk::Window* win)
422 {
423         if (!key_press_focus_accelerator_handler (*win, ev)) {
424                 return PublicEditor::instance().on_key_press_event(ev);
425         } else {
426                 return true;
427         }
428 }
429
430 bool
431 forward_key_press (GdkEventKey* ev)
432 {
433         return PublicEditor::instance().on_key_press_event(ev);
434 }
435
436 bool
437 key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev)
438 {
439         GtkWindow* win = window.gobj();
440         GtkWidget* focus = gtk_window_get_focus (win);
441         bool special_handling_of_unmodified_accelerators = false;
442         bool allow_activating = true;
443         /* consider all relevant modifiers but not LOCK or SHIFT */
444         const guint mask = (Keyboard::RelevantModifierKeyMask & ~(Gdk::SHIFT_MASK|Gdk::LOCK_MASK));
445
446         if (focus) {
447                 if (GTK_IS_ENTRY(focus) || Keyboard::some_magic_widget_has_focus()) {
448                         special_handling_of_unmodified_accelerators = true;
449                 }
450         }
451
452 #ifdef GTKOSX
453         /* should this be universally true? */
454         if (Keyboard::some_magic_widget_has_focus ()) {
455                 allow_activating = false;
456         }
457 #endif
458
459
460         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",
461                                                           win,
462                                                           ev->keyval,
463                                                           ev->state,
464                                                           special_handling_of_unmodified_accelerators,
465                                                           Keyboard::some_magic_widget_has_focus(),
466                                                           allow_activating,
467                                                           focus));
468
469         /* This exists to allow us to override the way GTK handles
470            key events. The normal sequence is:
471
472            a) event is delivered to a GtkWindow
473            b) accelerators/mnemonics are activated
474            c) if (b) didn't handle the event, propagate to
475                the focus widget and/or focus chain
476
477            The problem with this is that if the accelerators include
478            keys without modifiers, such as the space bar or the
479            letter "e", then pressing the key while typing into
480            a text entry widget results in the accelerator being
481            activated, instead of the desired letter appearing
482            in the text entry.
483
484            There is no good way of fixing this, but this
485            represents a compromise. The idea is that
486            key events involving modifiers (not Shift)
487            get routed into the activation pathway first, then
488            get propagated to the focus widget if necessary.
489
490            If the key event doesn't involve modifiers,
491            we deliver to the focus widget first, thus allowing
492            it to get "normal text" without interference
493            from acceleration.
494
495            Of course, this can also be problematic: if there
496            is a widget with focus, then it will swallow
497            all "normal text" accelerators.
498         */
499
500         if (!special_handling_of_unmodified_accelerators) {
501
502                 /* XXX note that for a brief moment, the conditional above
503                  * included "|| (ev->state & mask)" so as to enforce the
504                  * implication of special_handling_of_UNMODIFIED_accelerators.
505                  * however, this forces any key that GTK doesn't allow and that
506                  * we have an alternative (see next comment) for to be
507                  * automatically sent through the accel groups activation
508                  * pathway, which prevents individual widgets & canvas items
509                  * from ever seeing it if is used by a key binding.
510                  * 
511                  * specifically, this hid Ctrl-down-arrow from MIDI region
512                  * views because it is also bound to an action.
513                  *
514                  * until we have a robust, clean binding system, this
515                  * quirk will have to remain in place.
516                  */
517
518                 /* pretend that certain key events that GTK does not allow
519                    to be used as accelerators are actually something that
520                    it does allow. but only where there are no modifiers.
521                 */
522
523                 uint32_t fakekey = ev->keyval;
524
525                 if (Gtkmm2ext::possibly_translate_keyval_to_make_legal_accelerator (fakekey)) {
526                         if (allow_activating && gtk_accel_groups_activate(G_OBJECT(win), fakekey, GdkModifierType(ev->state))) {
527                                 DEBUG_TRACE (DEBUG::Accelerators, "\taccel group activated by fakekey\n");
528                                 return true;
529                         }
530                 }
531         }
532
533         if (!special_handling_of_unmodified_accelerators || (ev->state & mask)) {
534
535                 /* no special handling or there are modifiers in effect: accelerate first */
536
537                 DEBUG_TRACE (DEBUG::Accelerators, "\tactivate, then propagate\n");
538
539                 if (allow_activating) {
540                         if (gtk_window_activate_key (win, ev)) {
541                                 return true;
542                         }
543                 } else {
544                         DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
545                 }
546
547                 DEBUG_TRACE (DEBUG::Accelerators, "\tnot accelerated, now propagate\n");
548
549                 return gtk_window_propagate_key_event (win, ev);
550         }
551
552         /* no modifiers, propagate first */
553
554         DEBUG_TRACE (DEBUG::Accelerators, "\tpropagate, then activate\n");
555
556         if (!gtk_window_propagate_key_event (win, ev)) {
557                 DEBUG_TRACE (DEBUG::Accelerators, "\tpropagation didn't handle, so activate\n");
558                 if (allow_activating) {
559                         return gtk_window_activate_key (win, ev);
560                 } else {
561                         DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
562                 }
563
564         } else {
565                 DEBUG_TRACE (DEBUG::Accelerators, "\thandled by propagate\n");
566                 return true;
567         }
568
569         DEBUG_TRACE (DEBUG::Accelerators, "\tnot handled\n");
570         return true;
571 }
572
573 Glib::RefPtr<Gdk::Pixbuf>
574 get_xpm (std::string name)
575 {
576         if (!xpm_map[name]) {
577
578                 SearchPath spath(ARDOUR::ardour_search_path());
579                 spath += ARDOUR::system_data_search_path();
580
581                 spath.add_subdirectory_to_paths("pixmaps");
582
583                 sys::path data_file_path;
584
585                 if(!find_file_in_search_path (spath, name, data_file_path)) {
586                         fatal << string_compose (_("cannot find XPM file for %1"), name) << endmsg;
587                 }
588
589                 try {
590                         xpm_map[name] =  Gdk::Pixbuf::create_from_file (data_file_path.to_string());
591                 } catch(const Glib::Error& e)   {
592                         warning << "Caught Glib::Error: " << e.what() << endmsg;
593                 }
594         }
595
596         return xpm_map[name];
597 }
598
599 std::string
600 get_icon_path (const char* cname)
601 {
602         string name = cname;
603         name += X_(".png");
604
605         SearchPath spath(ARDOUR::ardour_search_path());
606         spath += ARDOUR::system_data_search_path();
607
608         spath.add_subdirectory_to_paths("icons");
609
610         sys::path data_file_path;
611
612         if (!find_file_in_search_path (spath, name, data_file_path)) {
613                 fatal << string_compose (_("cannot find icon image for %1"), name) << endmsg;
614         }
615
616         return data_file_path.to_string();
617 }
618
619 Glib::RefPtr<Gdk::Pixbuf>
620 get_icon (const char* cname)
621 {
622         Glib::RefPtr<Gdk::Pixbuf> img;
623         try {
624                 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname));
625         } catch (const Gdk::PixbufError &e) {
626                 cerr << "Caught PixbufError: " << e.what() << endl;
627         } catch (...) {
628                 g_message("Caught ... ");
629         }
630
631         return img;
632 }
633
634 string
635 longest (vector<string>& strings)
636 {
637         if (strings.empty()) {
638                 return string ("");
639         }
640
641         vector<string>::iterator longest = strings.begin();
642         string::size_type longest_length = (*longest).length();
643
644         vector<string>::iterator i = longest;
645         ++i;
646
647         while (i != strings.end()) {
648
649                 string::size_type len = (*i).length();
650
651                 if (len > longest_length) {
652                         longest = i;
653                         longest_length = len;
654                 }
655
656                 ++i;
657         }
658
659         return *longest;
660 }
661
662 bool
663 key_is_legal_for_numeric_entry (guint keyval)
664 {
665         switch (keyval) {
666         case GDK_minus:
667         case GDK_plus:
668         case GDK_period:
669         case GDK_comma:
670         case GDK_0:
671         case GDK_1:
672         case GDK_2:
673         case GDK_3:
674         case GDK_4:
675         case GDK_5:
676         case GDK_6:
677         case GDK_7:
678         case GDK_8:
679         case GDK_9:
680         case GDK_KP_Add:
681         case GDK_KP_Subtract:
682         case GDK_KP_Decimal:
683         case GDK_KP_0:
684         case GDK_KP_1:
685         case GDK_KP_2:
686         case GDK_KP_3:
687         case GDK_KP_4:
688         case GDK_KP_5:
689         case GDK_KP_6:
690         case GDK_KP_7:
691         case GDK_KP_8:
692         case GDK_KP_9:
693         case GDK_Return:
694         case GDK_BackSpace:
695         case GDK_Delete:
696         case GDK_KP_Enter:
697         case GDK_Home:
698         case GDK_End:
699         case GDK_Left:
700         case GDK_Right:
701                 return true;
702
703         default:
704                 break;
705         }
706
707         return false;
708 }
709 void
710 set_pango_fontsize ()
711 {
712         long val = ARDOUR::Config->get_font_scale();
713
714         /* FT2 rendering - used by GnomeCanvas, sigh */
715
716         pango_ft2_font_map_set_resolution ((PangoFT2FontMap*) pango_ft2_font_map_for_display(), val/1024, val/1024);
717
718         /* Cairo rendering, in case there is any */
719
720         pango_cairo_font_map_set_resolution ((PangoCairoFontMap*) pango_cairo_font_map_get_default(), val/1024);
721 }
722
723 void
724 reset_dpi ()
725 {
726         long val = ARDOUR::Config->get_font_scale();
727         set_pango_fontsize ();
728         /* Xft rendering */
729
730         gtk_settings_set_long_property (gtk_settings_get_default(),
731                                         "gtk-xft-dpi", val, "ardour");
732         DPIReset();//Emit Signal
733 }
734
735 void
736 resize_window_to_proportion_of_monitor (Gtk::Window* window, int max_width, int max_height)
737 {
738         Glib::RefPtr<Gdk::Screen> screen = window->get_screen ();
739         Gdk::Rectangle monitor_rect;
740         screen->get_monitor_geometry (0, monitor_rect);
741
742         int const w = std::min (int (monitor_rect.get_width() * 0.8), max_width);
743         int const h = std::min (int (monitor_rect.get_height() * 0.8), max_height);
744
745         window->resize (w, h);
746 }
747
748
749 /** Replace _ with __ in a string; for use with menu item text to make underscores displayed correctly */
750 string
751 escape_underscores (string const & s)
752 {
753         string o;
754         string::size_type const N = s.length ();
755
756         for (string::size_type i = 0; i < N; ++i) {
757                 if (s[i] == '_') {
758                         o += "__";
759                 } else {
760                         o += s[i];
761                 }
762         }
763
764         return o;
765 }
766
767 Gdk::Color
768 unique_random_color (list<Gdk::Color>& used_colors)
769 {
770         Gdk::Color newcolor;
771
772         while (1) {
773
774                 /* avoid neon/glowing tones by limiting them to the
775                    "inner section" (paler) of a color wheel/circle.
776                 */
777
778                 const int32_t max_saturation = 48000; // 65535 would open up the whole color wheel
779
780                 newcolor.set_red (random() % max_saturation);
781                 newcolor.set_blue (random() % max_saturation);
782                 newcolor.set_green (random() % max_saturation);
783
784                 if (used_colors.size() == 0) {
785                         used_colors.push_back (newcolor);
786                         return newcolor;
787                 }
788
789                 for (list<Gdk::Color>::iterator i = used_colors.begin(); i != used_colors.end(); ++i) {
790                   Gdk::Color c = *i;
791                         float rdelta, bdelta, gdelta;
792
793                         rdelta = newcolor.get_red() - c.get_red();
794                         bdelta = newcolor.get_blue() - c.get_blue();
795                         gdelta = newcolor.get_green() - c.get_green();
796
797                         if (sqrt (rdelta*rdelta + bdelta*bdelta + gdelta*gdelta) > 25.0) {
798                                 used_colors.push_back (newcolor);
799                                 return newcolor;
800                         }
801                 }
802
803                 /* XXX need throttle here to make sure we don't spin for ever */
804         }
805 }