Merge branch 'windows' of git://git.ardour.org/ardour/ardour into windows
[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 emulate_key_event (Gtk::Widget* w, unsigned int keyval)
334 {
335         GdkDisplay  *display = gtk_widget_get_display (GTK_WIDGET(w->gobj()));
336         GdkKeymap   *keymap  = gdk_keymap_get_for_display (display);
337         GdkKeymapKey *keymapkey = NULL;
338         gint n_keys;
339
340         if (!gdk_keymap_get_entries_for_keyval(keymap, keyval, &keymapkey, &n_keys)) return false;
341         if (n_keys !=1) { g_free(keymapkey); return false;}
342
343         GdkEventKey ev;
344         ev.type = GDK_KEY_PRESS;
345         ev.window = gtk_widget_get_window(GTK_WIDGET(w->gobj()));
346         ev.send_event = FALSE;
347         ev.time = 0;
348         ev.state = 0;
349         ev.keyval = keyval;
350         ev.length = 0;
351         ev.string = (gchar*) "";
352         ev.hardware_keycode = keymapkey[0].keycode;
353         ev.group = keymapkey[0].group;
354         g_free(keymapkey);
355
356         forward_key_press(&ev);
357         ev.type = GDK_KEY_RELEASE;
358         return forward_key_press(&ev);
359 }
360
361 bool
362 key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev)
363 {
364         GtkWindow* win = window.gobj();
365         GtkWidget* focus = gtk_window_get_focus (win);
366         bool special_handling_of_unmodified_accelerators = false;
367         bool allow_activating = true;
368         /* consider all relevant modifiers but not LOCK or SHIFT */
369         const guint mask = (Keyboard::RelevantModifierKeyMask & ~(Gdk::SHIFT_MASK|Gdk::LOCK_MASK));
370
371         if (focus) {
372                 if (GTK_IS_ENTRY(focus) || Keyboard::some_magic_widget_has_focus()) {
373                         special_handling_of_unmodified_accelerators = true;
374                 }
375         }
376
377 #ifdef GTKOSX
378         /* at one time this appeared to be necessary. As of July 2012, it does not
379            appear to be. if it ever is necessar, figure out if it should apply
380            to all platforms.
381         */
382 #if 0 
383         if (Keyboard::some_magic_widget_has_focus ()) {
384                 allow_activating = false;
385         }
386 #endif
387 #endif
388
389
390         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",
391                                                           win,
392                                                           ev->keyval,
393                                                           ev->state,
394                                                           special_handling_of_unmodified_accelerators,
395                                                           Keyboard::some_magic_widget_has_focus(),
396                                                           allow_activating,
397                                                           focus));
398
399         /* This exists to allow us to override the way GTK handles
400            key events. The normal sequence is:
401
402            a) event is delivered to a GtkWindow
403            b) accelerators/mnemonics are activated
404            c) if (b) didn't handle the event, propagate to
405                the focus widget and/or focus chain
406
407            The problem with this is that if the accelerators include
408            keys without modifiers, such as the space bar or the
409            letter "e", then pressing the key while typing into
410            a text entry widget results in the accelerator being
411            activated, instead of the desired letter appearing
412            in the text entry.
413
414            There is no good way of fixing this, but this
415            represents a compromise. The idea is that
416            key events involving modifiers (not Shift)
417            get routed into the activation pathway first, then
418            get propagated to the focus widget if necessary.
419
420            If the key event doesn't involve modifiers,
421            we deliver to the focus widget first, thus allowing
422            it to get "normal text" without interference
423            from acceleration.
424
425            Of course, this can also be problematic: if there
426            is a widget with focus, then it will swallow
427            all "normal text" accelerators.
428         */
429
430         if (!special_handling_of_unmodified_accelerators) {
431
432                 /* XXX note that for a brief moment, the conditional above
433                  * included "|| (ev->state & mask)" so as to enforce the
434                  * implication of special_handling_of_UNMODIFIED_accelerators.
435                  * however, this forces any key that GTK doesn't allow and that
436                  * we have an alternative (see next comment) for to be
437                  * automatically sent through the accel groups activation
438                  * pathway, which prevents individual widgets & canvas items
439                  * from ever seeing it if is used by a key binding.
440                  * 
441                  * specifically, this hid Ctrl-down-arrow from MIDI region
442                  * views because it is also bound to an action.
443                  *
444                  * until we have a robust, clean binding system, this
445                  * quirk will have to remain in place.
446                  */
447
448                 /* pretend that certain key events that GTK does not allow
449                    to be used as accelerators are actually something that
450                    it does allow. but only where there are no modifiers.
451                 */
452
453                 uint32_t fakekey = ev->keyval;
454
455                 if (Gtkmm2ext::possibly_translate_keyval_to_make_legal_accelerator (fakekey)) {
456                         DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tactivate (was %1 now %2) without special hanlding of unmodified accels\n",
457                                                                           ev->keyval, fakekey));
458
459                         GdkModifierType mod = GdkModifierType (ev->state);
460
461                         mod = GdkModifierType (mod & gtk_accelerator_get_default_mod_mask());
462 #ifdef GTKOSX
463                         /* GTK on OS X is currently (February 2012) setting both
464                            the Meta and Mod2 bits in the event modifier state if 
465                            the Command key is down.
466
467                            gtk_accel_groups_activate() does not invoke any of the logic
468                            that gtk_window_activate_key() will that sorts out that stupid
469                            state of affairs, and as a result it fails to find a match
470                            for the key event and the current set of accelerators.
471
472                            to fix this, if the meta bit is set, remove the mod2 bit
473                            from the modifier. this assumes that our bindings use Primary
474                            which will have set the meta bit in the accelerator entry.
475                         */
476                         if (mod & GDK_META_MASK) {
477                                 mod = GdkModifierType (mod & ~GDK_MOD2_MASK);
478                         }
479 #endif
480
481                         if (allow_activating && gtk_accel_groups_activate(G_OBJECT(win), fakekey, mod)) {
482                                 DEBUG_TRACE (DEBUG::Accelerators, "\taccel group activated by fakekey\n");
483                                 return true;
484                         }
485                 }
486         }
487
488         if (!special_handling_of_unmodified_accelerators || (ev->state & mask)) {
489
490                 /* no special handling or there are modifiers in effect: accelerate first */
491
492                 DEBUG_TRACE (DEBUG::Accelerators, "\tactivate, then propagate\n");
493                 DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tevent send-event:%1 time:%2 length:%3 string:%4 hardware_keycode:%5 group:%6\n",
494                                         ev->send_event, ev->time, ev->length, ev->string, ev->hardware_keycode, ev->group));
495
496                 if (allow_activating) {
497                         DEBUG_TRACE (DEBUG::Accelerators, "\tsending to window\n");
498                         if (gtk_window_activate_key (win, ev)) {
499                                 DEBUG_TRACE (DEBUG::Accelerators, "\t\thandled\n");
500                                 return true;
501                         }
502                 } else {
503                         DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
504                 }
505
506                 DEBUG_TRACE (DEBUG::Accelerators, "\tnot accelerated, now propagate\n");
507
508                 return gtk_window_propagate_key_event (win, ev);
509         }
510
511         /* no modifiers, propagate first */
512
513         DEBUG_TRACE (DEBUG::Accelerators, "\tpropagate, then activate\n");
514
515         if (!gtk_window_propagate_key_event (win, ev)) {
516                 DEBUG_TRACE (DEBUG::Accelerators, "\tpropagation didn't handle, so activate\n");
517                 if (allow_activating) {
518                         return gtk_window_activate_key (win, ev);
519                 } else {
520                         DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
521                 }
522
523         } else {
524                 DEBUG_TRACE (DEBUG::Accelerators, "\thandled by propagate\n");
525                 return true;
526         }
527
528         DEBUG_TRACE (DEBUG::Accelerators, "\tnot handled\n");
529         return true;
530 }
531
532 Glib::RefPtr<Gdk::Pixbuf>
533 get_xpm (std::string name)
534 {
535         if (!xpm_map[name]) {
536
537                 SearchPath spath(ARDOUR::ardour_data_search_path());
538
539                 spath.add_subdirectory_to_paths("pixmaps");
540
541                 std::string data_file_path;
542
543                 if(!find_file_in_search_path (spath, name, data_file_path)) {
544                         fatal << string_compose (_("cannot find XPM file for %1"), name) << endmsg;
545                 }
546
547                 try {
548                         xpm_map[name] =  Gdk::Pixbuf::create_from_file (data_file_path);
549                 } catch(const Glib::Error& e)   {
550                         warning << "Caught Glib::Error: " << e.what() << endmsg;
551                 }
552         }
553
554         return xpm_map[name];
555 }
556
557 std::string
558 get_icon_path (const char* cname)
559 {
560         string name = cname;
561         name += X_(".png");
562
563         SearchPath spath(ARDOUR::ardour_data_search_path());
564
565         spath.add_subdirectory_to_paths("icons");
566
567         std::string data_file_path;
568
569         if (!find_file_in_search_path (spath, name, data_file_path)) {
570                 fatal << string_compose (_("cannot find icon image for %1 using %2"), name, spath.to_string()) << endmsg;
571         }
572
573         return data_file_path;
574 }
575
576 Glib::RefPtr<Gdk::Pixbuf>
577 get_icon (const char* cname)
578 {
579         Glib::RefPtr<Gdk::Pixbuf> img;
580         try {
581                 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname));
582         } catch (const Gdk::PixbufError &e) {
583                 cerr << "Caught PixbufError: " << e.what() << endl;
584         } catch (...) {
585                 error << string_compose (_("Caught exception while loading icon named %1"), cname) << endmsg;
586         }
587
588         return img;
589 }
590
591 string
592 longest (vector<string>& strings)
593 {
594         if (strings.empty()) {
595                 return string ("");
596         }
597
598         vector<string>::iterator longest = strings.begin();
599         string::size_type longest_length = (*longest).length();
600
601         vector<string>::iterator i = longest;
602         ++i;
603
604         while (i != strings.end()) {
605
606                 string::size_type len = (*i).length();
607
608                 if (len > longest_length) {
609                         longest = i;
610                         longest_length = len;
611                 }
612
613                 ++i;
614         }
615
616         return *longest;
617 }
618
619 bool
620 key_is_legal_for_numeric_entry (guint keyval)
621 {
622         /* we assume that this does not change over the life of the process 
623          */
624
625         static int comma_decimal = -1;
626
627         switch (keyval) {
628         case GDK_period:
629         case GDK_comma:
630                 if (comma_decimal < 0) {
631                         std::lconv* lc = std::localeconv();
632                         if (strchr (lc->decimal_point, ',') != 0) {
633                                 comma_decimal = 1;
634                         } else {
635                                 comma_decimal = 0;
636                         }
637                 }
638                 break;
639         default:
640                 break;
641         }
642
643         switch (keyval) {
644         case GDK_decimalpoint:
645         case GDK_KP_Separator:
646                 return true;
647
648         case GDK_period:
649                 if (comma_decimal) {
650                         return false;
651                 } else {
652                         return true;
653                 }
654                 break;
655         case GDK_comma:
656                 if (comma_decimal) {
657                         return true;
658                 } else {
659                         return false;
660                 }
661                 break;
662         case GDK_minus:
663         case GDK_plus:
664         case GDK_0:
665         case GDK_1:
666         case GDK_2:
667         case GDK_3:
668         case GDK_4:
669         case GDK_5:
670         case GDK_6:
671         case GDK_7:
672         case GDK_8:
673         case GDK_9:
674         case GDK_KP_Add:
675         case GDK_KP_Subtract:
676         case GDK_KP_Decimal:
677         case GDK_KP_0:
678         case GDK_KP_1:
679         case GDK_KP_2:
680         case GDK_KP_3:
681         case GDK_KP_4:
682         case GDK_KP_5:
683         case GDK_KP_6:
684         case GDK_KP_7:
685         case GDK_KP_8:
686         case GDK_KP_9:
687         case GDK_Return:
688         case GDK_BackSpace:
689         case GDK_Delete:
690         case GDK_KP_Enter:
691         case GDK_Home:
692         case GDK_End:
693         case GDK_Left:
694         case GDK_Right:
695                 return true;
696
697         default:
698                 break;
699         }
700
701         return false;
702 }
703 void
704 set_pango_fontsize ()
705 {
706         long val = ARDOUR::Config->get_font_scale();
707
708         /* FT2 rendering - used by GnomeCanvas, sigh */
709
710 #ifndef WIN32
711         pango_ft2_font_map_set_resolution ((PangoFT2FontMap*) pango_ft2_font_map_new(), val/1024, val/1024);
712 #endif
713
714         /* Cairo rendering, in case there is any */
715
716         pango_cairo_font_map_set_resolution ((PangoCairoFontMap*) pango_cairo_font_map_get_default(), val/1024);
717 }
718
719 void
720 reset_dpi ()
721 {
722         long val = ARDOUR::Config->get_font_scale();
723         set_pango_fontsize ();
724         /* Xft rendering */
725
726         gtk_settings_set_long_property (gtk_settings_get_default(),
727                                         "gtk-xft-dpi", val, "ardour");
728         DPIReset();//Emit Signal
729 }
730
731 void
732 resize_window_to_proportion_of_monitor (Gtk::Window* window, int max_width, int max_height)
733 {
734         Glib::RefPtr<Gdk::Screen> screen = window->get_screen ();
735         Gdk::Rectangle monitor_rect;
736         screen->get_monitor_geometry (0, monitor_rect);
737
738         int const w = std::min (int (monitor_rect.get_width() * 0.8), max_width);
739         int const h = std::min (int (monitor_rect.get_height() * 0.8), max_height);
740
741         window->resize (w, h);
742 }
743
744
745 /** Replace _ with __ in a string; for use with menu item text to make underscores displayed correctly */
746 string
747 escape_underscores (string const & s)
748 {
749         string o;
750         string::size_type const N = s.length ();
751
752         for (string::size_type i = 0; i < N; ++i) {
753                 if (s[i] == '_') {
754                         o += "__";
755                 } else {
756                         o += s[i];
757                 }
758         }
759
760         return o;
761 }
762
763 /** Replace < and > with &lt; and &gt; respectively to make < > display correctly in markup strings */
764 string
765 escape_angled_brackets (string const & s)
766 {
767         string o = s;
768         boost::replace_all (o, "<", "&lt;");
769         boost::replace_all (o, ">", "&gt;");
770         return o;
771 }
772
773 Gdk::Color
774 unique_random_color (list<Gdk::Color>& used_colors)
775 {
776         Gdk::Color newcolor;
777
778         while (1) {
779
780                 /* avoid neon/glowing tones by limiting them to the
781                    "inner section" (paler) of a color wheel/circle.
782                 */
783
784                 const int32_t max_saturation = 48000; // 65535 would open up the whole color wheel
785
786                 newcolor.set_red (g_random_int() % max_saturation);
787                 newcolor.set_blue (g_random_int() % max_saturation);
788                 newcolor.set_green (g_random_int() % max_saturation);
789
790                 if (used_colors.size() == 0) {
791                         used_colors.push_back (newcolor);
792                         return newcolor;
793                 }
794
795                 for (list<Gdk::Color>::iterator i = used_colors.begin(); i != used_colors.end(); ++i) {
796                   Gdk::Color c = *i;
797                         float rdelta, bdelta, gdelta;
798
799                         rdelta = newcolor.get_red() - c.get_red();
800                         bdelta = newcolor.get_blue() - c.get_blue();
801                         gdelta = newcolor.get_green() - c.get_green();
802
803                         if (sqrt (rdelta*rdelta + bdelta*bdelta + gdelta*gdelta) > 25.0) {
804                                 used_colors.push_back (newcolor);
805                                 return newcolor;
806                         }
807                 }
808
809                 /* XXX need throttle here to make sure we don't spin for ever */
810         }
811 }