remove unused fstream includes
[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 <cstdlib>
25 #include <clocale>
26 #include <cstring>
27 #include <cctype>
28 #include <cmath>
29 #include <list>
30 #include <sys/stat.h>
31 #include <gtkmm/rc.h>
32 #include <gtkmm/window.h>
33 #include <gtkmm/combo.h>
34 #include <gtkmm/label.h>
35 #include <gtkmm/paned.h>
36 #include <gtk/gtkpaned.h>
37 #include <boost/algorithm/string.hpp>
38
39 #include "pbd/file_utils.h"
40
41 #include <gtkmm2ext/utils.h>
42
43 #include "ardour/filesystem_paths.h"
44
45 #include "canvas/item.h"
46 #include "canvas/utils.h"
47
48 #include "debug.h"
49 #include "public_editor.h"
50 #include "keyboard.h"
51 #include "utils.h"
52 #include "i18n.h"
53 #include "rgb_macros.h"
54 #include "gui_thread.h"
55 #include "ui_config.h"
56
57 using namespace std;
58 using namespace Gtk;
59 using namespace Glib;
60 using namespace PBD;
61 using Gtkmm2ext::Keyboard;
62
63 namespace ARDOUR_UI_UTILS {
64         sigc::signal<void>  DPIReset;
65 }
66
67 #ifdef PLATFORM_WINDOWS
68 #define random() rand()
69 #endif
70
71
72 /** Add an element to a menu, settings its sensitivity.
73  * @param m Menu to add to.
74  * @param e Element to add.
75  * @param s true to make sensitive, false to make insensitive
76  */
77 void
78 ARDOUR_UI_UTILS::add_item_with_sensitivity (Menu_Helpers::MenuList& m, Menu_Helpers::MenuElem e, bool s)
79 {
80         m.push_back (e);
81         if (!s) {
82                 m.back().set_sensitive (false);
83         }
84 }
85
86
87 gint
88 ARDOUR_UI_UTILS::just_hide_it (GdkEventAny */*ev*/, Gtk::Window *win)
89 {
90         win->hide ();
91         return 0;
92 }
93
94 /* xpm2rgb copied from nixieclock, which bore the legend:
95
96     nixieclock - a nixie desktop timepiece
97     Copyright (C) 2000 Greg Ercolano, erco@3dsite.com
98
99     and was released under the GPL.
100 */
101
102 unsigned char*
103 ARDOUR_UI_UTILS::xpm2rgb (const char** xpm, uint32_t& w, uint32_t& h)
104 {
105         static long vals[256], val;
106         uint32_t t, x, y, colors, cpp;
107         unsigned char c;
108         unsigned char *savergb, *rgb;
109
110         // PARSE HEADER
111
112         if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
113                 error << string_compose (_("bad XPM header %1"), xpm[0])
114                       << endmsg;
115                 return 0;
116         }
117
118         savergb = rgb = (unsigned char*) malloc (h * w * 3);
119
120         // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
121         for (t = 0; t < colors; ++t) {
122                 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
123                 vals[c] = val;
124         }
125
126         // COLORMAP -> RGB CONVERSION
127         //    Get low 3 bytes from vals[]
128         //
129
130         const char *p;
131         for (y = h-1; y > 0; --y) {
132
133                 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 3) {
134                         val = vals[(int)*p++];
135                         *(rgb+2) = val & 0xff; val >>= 8;  // 2:B
136                         *(rgb+1) = val & 0xff; val >>= 8;  // 1:G
137                         *(rgb+0) = val & 0xff;             // 0:R
138                 }
139         }
140
141         return (savergb);
142 }
143
144 unsigned char*
145 ARDOUR_UI_UTILS::xpm2rgba (const char** xpm, uint32_t& w, uint32_t& h)
146 {
147         static long vals[256], val;
148         uint32_t t, x, y, colors, cpp;
149         unsigned char c;
150         unsigned char *savergb, *rgb;
151         char transparent;
152
153         // PARSE HEADER
154
155         if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
156                 error << string_compose (_("bad XPM header %1"), xpm[0])
157                       << endmsg;
158                 return 0;
159         }
160
161         savergb = rgb = (unsigned char*) malloc (h * w * 4);
162
163         // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
164
165         if (strstr (xpm[1], "None")) {
166                 sscanf (xpm[1], "%c", &transparent);
167                 t = 1;
168         } else {
169                 transparent = 0;
170                 t = 0;
171         }
172
173         for (; t < colors; ++t) {
174                 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
175                 vals[c] = val;
176         }
177
178         // COLORMAP -> RGB CONVERSION
179         //    Get low 3 bytes from vals[]
180         //
181
182         const char *p;
183         for (y = h-1; y > 0; --y) {
184
185                 char alpha;
186
187                 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 4) {
188
189                         if (transparent && (*p++ == transparent)) {
190                                 alpha = 0;
191                                 val = 0;
192                         } else {
193                                 alpha = 255;
194                                 val = vals[(int)*p];
195                         }
196
197                         *(rgb+3) = alpha;                  // 3: alpha
198                         *(rgb+2) = val & 0xff; val >>= 8;  // 2:B
199                         *(rgb+1) = val & 0xff; val >>= 8;  // 1:G
200                         *(rgb+0) = val & 0xff;             // 0:R
201                 }
202         }
203
204         return (savergb);
205 }
206
207 /** Returns a Pango::FontDescription given a string describing the font.
208  *
209  * If the returned FontDescription does not specify a family, then
210  * the family is set to "Sans". This mirrors GTK's behaviour in
211  * gtkstyle.c.
212  *
213  * Some environments will force Pango to specify the family
214  * even if it was not specified in the string describing the font.
215  * Such environments should be left unaffected by this function,
216  * since the font family will be left alone.
217  *
218  * There may be other similar font specification enforcement
219  * that we might add here later.
220  */
221 Pango::FontDescription
222 ARDOUR_UI_UTILS::sanitized_font (std::string const& name)
223 {
224         Pango::FontDescription fd (name);
225
226         if (fd.get_family().empty()) {
227                 fd.set_family ("Sans");
228         }
229
230         return fd;
231 }
232
233 Pango::FontDescription
234 ARDOUR_UI_UTILS::get_font_for_style (string widgetname)
235 {
236         Gtk::Window window (WINDOW_TOPLEVEL);
237         Gtk::Label foobar;
238         Glib::RefPtr<Gtk::Style> style;
239
240         window.add (foobar);
241         foobar.set_name (widgetname);
242         foobar.ensure_style();
243
244         style = foobar.get_style ();
245
246         Glib::RefPtr<const Pango::Layout> layout = foobar.get_layout();
247
248         PangoFontDescription *pfd = const_cast<PangoFontDescription *> (pango_layout_get_font_description(const_cast<PangoLayout *>(layout->gobj())));
249
250         if (!pfd) {
251
252                 /* layout inherited its font description from a PangoContext */
253
254                 PangoContext* ctxt = (PangoContext*) pango_layout_get_context (const_cast<PangoLayout*>(layout->gobj()));
255                 pfd =  pango_context_get_font_description (ctxt);
256                 return Pango::FontDescription (pfd); /* make a copy */
257         }
258
259         return Pango::FontDescription (pfd); /* make a copy */
260 }
261
262 void
263 ARDOUR_UI_UTILS::set_color_from_rgb (Gdk::Color& c, uint32_t rgb)
264 {
265         /* Gdk::Color color ranges are 16 bit, so scale from 8 bit by
266            multiplying by 256.
267         */
268         c.set_rgb ((rgb >> 16)*256, ((rgb & 0xff00) >> 8)*256, (rgb & 0xff)*256);
269 }
270
271 void
272 ARDOUR_UI_UTILS::set_color_from_rgba (Gdk::Color& c, uint32_t rgba)
273 {
274         /* Gdk::Color color ranges are 16 bit, so scale from 8 bit by
275            multiplying by 256.
276         */
277         c.set_rgb ((rgba >> 24)*256, ((rgba & 0xff0000) >> 16)*256, ((rgba & 0xff00) >> 8)*256);
278 }
279
280 uint32_t
281 ARDOUR_UI_UTILS::gdk_color_to_rgba (Gdk::Color const& c)
282 {
283         /* since alpha value is not available from a Gdk::Color, it is
284            hardcoded as 0xff (aka 255 or 1.0)
285         */
286
287         const uint32_t r = c.get_red_p () * 255.0;
288         const uint32_t g = c.get_green_p () * 255.0;
289         const uint32_t b = c.get_blue_p () * 255.0;
290         const uint32_t a = 0xff;
291
292         return RGBA_TO_UINT (r,g,b,a);
293 }
294
295
296 bool
297 ARDOUR_UI_UTILS::relay_key_press (GdkEventKey* ev, Gtk::Window* win)
298 {
299
300         if (!key_press_focus_accelerator_handler (*win, ev)) {
301                 if (!PublicEditor::_instance) {
302                         /* early key press in pre-main-window-dialogs, no editor yet */
303                         return false;
304                 }
305                 PublicEditor& ed (PublicEditor::instance());
306                 return ed.on_key_press_event(ev);
307         } else {
308                 return true;
309         }
310 }
311
312 bool
313 ARDOUR_UI_UTILS::forward_key_press (GdkEventKey* ev)
314 {
315         return PublicEditor::instance().on_key_press_event(ev);
316 }
317
318 bool
319 ARDOUR_UI_UTILS::emulate_key_event (Gtk::Widget* w, unsigned int keyval)
320 {
321         GdkDisplay  *display = gtk_widget_get_display (GTK_WIDGET(w->gobj()));
322         GdkKeymap   *keymap  = gdk_keymap_get_for_display (display);
323         GdkKeymapKey *keymapkey = NULL;
324         gint n_keys;
325
326         if (!gdk_keymap_get_entries_for_keyval(keymap, keyval, &keymapkey, &n_keys)) return false;
327         if (n_keys !=1) { g_free(keymapkey); return false;}
328
329         GdkEventKey ev;
330         ev.type = GDK_KEY_PRESS;
331         ev.window = gtk_widget_get_window(GTK_WIDGET(w->gobj()));
332         ev.send_event = FALSE;
333         ev.time = 0;
334         ev.state = 0;
335         ev.keyval = keyval;
336         ev.length = 0;
337         ev.string = const_cast<gchar*> ("");
338         ev.hardware_keycode = keymapkey[0].keycode;
339         ev.group = keymapkey[0].group;
340         g_free(keymapkey);
341
342         forward_key_press(&ev);
343         ev.type = GDK_KEY_RELEASE;
344         return forward_key_press(&ev);
345 }
346
347 static string
348 show_gdk_event_state (int state)
349 {
350         string s;
351         if (state & GDK_SHIFT_MASK) {
352                 s += "+SHIFT";
353         }
354         if (state & GDK_LOCK_MASK) {
355                 s += "+LOCK";
356         }
357         if (state & GDK_CONTROL_MASK) {
358                 s += "+CONTROL";
359         }
360         if (state & GDK_MOD1_MASK) {
361                 s += "+MOD1";
362         }
363         if (state & GDK_MOD2_MASK) {
364                 s += "+MOD2";
365         }
366         if (state & GDK_MOD3_MASK) {
367                 s += "+MOD3";
368         }
369         if (state & GDK_MOD4_MASK) {
370                 s += "+MOD4";
371         }
372         if (state & GDK_MOD5_MASK) {
373                 s += "+MOD5";
374         }
375         if (state & GDK_BUTTON1_MASK) {
376                 s += "+BUTTON1";
377         }
378         if (state & GDK_BUTTON2_MASK) {
379                 s += "+BUTTON2";
380         }
381         if (state & GDK_BUTTON3_MASK) {
382                 s += "+BUTTON3";
383         }
384         if (state & GDK_BUTTON4_MASK) {
385                 s += "+BUTTON4";
386         }
387         if (state & GDK_BUTTON5_MASK) {
388                 s += "+BUTTON5";
389         }
390         if (state & GDK_SUPER_MASK) {
391                 s += "+SUPER";
392         }
393         if (state & GDK_HYPER_MASK) {
394                 s += "+HYPER";
395         }
396         if (state & GDK_META_MASK) {
397                 s += "+META";
398         }
399         if (state & GDK_RELEASE_MASK) {
400                 s += "+RELEASE";
401         }
402
403         return s;
404 }
405 bool
406 ARDOUR_UI_UTILS::key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev)
407 {
408         GtkWindow* win = window.gobj();
409         GtkWidget* focus = gtk_window_get_focus (win);
410         bool special_handling_of_unmodified_accelerators = false;
411         bool allow_activating = true;
412         /* consider all relevant modifiers but not LOCK or SHIFT */
413         const guint mask = (Keyboard::RelevantModifierKeyMask & ~(Gdk::SHIFT_MASK|Gdk::LOCK_MASK));
414         GdkModifierType modifier = GdkModifierType (ev->state);
415         modifier = GdkModifierType (modifier & gtk_accelerator_get_default_mod_mask());
416         Gtkmm2ext::possibly_translate_mod_to_make_legal_accelerator(modifier);
417
418         if (focus) {
419                 if (GTK_IS_ENTRY(focus) || Keyboard::some_magic_widget_has_focus()) {
420                         special_handling_of_unmodified_accelerators = true;
421                 }
422         }
423
424 #ifdef GTKOSX
425         /* at one time this appeared to be necessary. As of July 2012, it does not
426            appear to be. if it ever is necessar, figure out if it should apply
427            to all platforms.
428         */
429 #if 0
430         if (Keyboard::some_magic_widget_has_focus ()) {
431                 allow_activating = false;
432         }
433 #endif
434 #endif
435
436
437         DEBUG_TRACE (DEBUG::Accelerators, string_compose ("Win = %1 focus = %7 (%8) Key event: code = %2  state = %3 special handling ? %4 magic widget focus ? %5 allow_activation ? %6\n",
438                                                           win,
439                                                           ev->keyval,
440                                                           show_gdk_event_state (ev->state),
441                                                           special_handling_of_unmodified_accelerators,
442                                                           Keyboard::some_magic_widget_has_focus(),
443                                                           allow_activating,
444                                                           focus,
445                                                           (focus ? gtk_widget_get_name (focus) : "no focus widget")));
446
447         /* This exists to allow us to override the way GTK handles
448            key events. The normal sequence is:
449
450            a) event is delivered to a GtkWindow
451            b) accelerators/mnemonics are activated
452            c) if (b) didn't handle the event, propagate to
453                the focus widget and/or focus chain
454
455            The problem with this is that if the accelerators include
456            keys without modifiers, such as the space bar or the
457            letter "e", then pressing the key while typing into
458            a text entry widget results in the accelerator being
459            activated, instead of the desired letter appearing
460            in the text entry.
461
462            There is no good way of fixing this, but this
463            represents a compromise. The idea is that
464            key events involving modifiers (not Shift)
465            get routed into the activation pathway first, then
466            get propagated to the focus widget if necessary.
467
468            If the key event doesn't involve modifiers,
469            we deliver to the focus widget first, thus allowing
470            it to get "normal text" without interference
471            from acceleration.
472
473            Of course, this can also be problematic: if there
474            is a widget with focus, then it will swallow
475            all "normal text" accelerators.
476         */
477
478         if (!special_handling_of_unmodified_accelerators) {
479
480
481                 /* XXX note that for a brief moment, the conditional above
482                  * included "|| (ev->state & mask)" so as to enforce the
483                  * implication of special_handling_of_UNMODIFIED_accelerators.
484                  * however, this forces any key that GTK doesn't allow and that
485                  * we have an alternative (see next comment) for to be
486                  * automatically sent through the accel groups activation
487                  * pathway, which prevents individual widgets & canvas items
488                  * from ever seeing it if is used by a key binding.
489                  *
490                  * specifically, this hid Ctrl-down-arrow from MIDI region
491                  * views because it is also bound to an action.
492                  *
493                  * until we have a robust, clean binding system, this
494                  * quirk will have to remain in place.
495                  */
496
497                 /* pretend that certain key events that GTK does not allow
498                    to be used as accelerators are actually something that
499                    it does allow. but only where there are no modifiers.
500                 */
501
502                 uint32_t fakekey = ev->keyval;
503
504                 if (Gtkmm2ext::possibly_translate_keyval_to_make_legal_accelerator (fakekey)) {
505                         DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tactivate (was %1 now %2) without special hanlding of unmodified accels\n",
506                                                                           ev->keyval, fakekey));
507
508                         DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tmodified modifier was %1\n", show_gdk_event_state (modifier)));
509
510                         if (allow_activating && gtk_accel_groups_activate(G_OBJECT(win), fakekey, modifier)) {
511                                 DEBUG_TRACE (DEBUG::Accelerators, "\taccel group activated by fakekey\n");
512                                 return true;
513                         }
514                 }
515         }
516
517         if (!special_handling_of_unmodified_accelerators || (ev->state & mask)) {
518
519                 /* no special handling or there are modifiers in effect: accelerate first */
520
521                 DEBUG_TRACE (DEBUG::Accelerators, "\tactivate, then propagate\n");
522                 DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tevent send-event:%1 time:%2 length:%3 name %7 string:%4 hardware_keycode:%5 group:%6\n",
523                                                                   ev->send_event, ev->time, ev->length, ev->string, ev->hardware_keycode, ev->group, gdk_keyval_name (ev->keyval)));
524
525                 if (allow_activating) {
526                         DEBUG_TRACE (DEBUG::Accelerators, "\tsending to window\n");
527                         if (gtk_accel_groups_activate (G_OBJECT(win), ev->keyval, modifier)) {
528                                 DEBUG_TRACE (DEBUG::Accelerators, "\t\thandled\n");
529                                 return true;
530                         }
531                 } else {
532                         DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
533                 }
534
535                 DEBUG_TRACE (DEBUG::Accelerators, "\tnot accelerated, now propagate\n");
536
537                 return gtk_window_propagate_key_event (win, ev);
538         }
539
540         /* no modifiers, propagate first */
541
542         DEBUG_TRACE (DEBUG::Accelerators, "\tpropagate, then activate\n");
543
544         if (!gtk_window_propagate_key_event (win, ev)) {
545                 DEBUG_TRACE (DEBUG::Accelerators, "\tpropagation didn't handle, so activate\n");
546                 if (allow_activating) {
547                         return gtk_accel_groups_activate (G_OBJECT(win), ev->keyval, modifier);
548                 } else {
549                         DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
550                 }
551
552         } else {
553                 DEBUG_TRACE (DEBUG::Accelerators, "\thandled by propagate\n");
554                 return true;
555         }
556
557         DEBUG_TRACE (DEBUG::Accelerators, "\tnot handled\n");
558         return true;
559 }
560
561 Glib::RefPtr<Gdk::Pixbuf>
562 ARDOUR_UI_UTILS::get_xpm (std::string name)
563 {
564         if (!xpm_map[name]) {
565
566                 Searchpath spath(ARDOUR::ardour_data_search_path());
567
568                 spath.add_subdirectory_to_paths("pixmaps");
569
570                 std::string data_file_path;
571
572                 if(!find_file (spath, name, data_file_path)) {
573                         fatal << string_compose (_("cannot find XPM file for %1"), name) << endmsg;
574                 }
575
576                 try {
577                         xpm_map[name] =  Gdk::Pixbuf::create_from_file (data_file_path);
578                 } catch(const Glib::Error& e)   {
579                         warning << "Caught Glib::Error: " << e.what() << endmsg;
580                 }
581         }
582
583         return xpm_map[name];
584 }
585
586 vector<string>
587 ARDOUR_UI_UTILS::get_icon_sets ()
588 {
589         Searchpath spath(ARDOUR::ardour_data_search_path());
590         spath.add_subdirectory_to_paths ("icons");
591         vector<string> r;
592
593         r.push_back (_("default"));
594
595         for (vector<string>::iterator s = spath.begin(); s != spath.end(); ++s) {
596
597                 vector<string> entries;
598
599                 get_paths (entries, *s, false, false);
600
601                 for (vector<string>::iterator e = entries.begin(); e != entries.end(); ++e) {
602                         if (Glib::file_test (*e, Glib::FILE_TEST_IS_DIR)) {
603                                 r.push_back (Glib::filename_to_utf8 (Glib::path_get_basename(*e)));
604                         }
605                 }
606         }
607
608         return r;
609 }
610
611 std::string
612 ARDOUR_UI_UTILS::get_icon_path (const char* cname, string icon_set, bool is_image)
613 {
614         std::string data_file_path;
615         string name = cname;
616
617         if (is_image) {
618                 name += X_(".png");
619         }
620
621         Searchpath spath(ARDOUR::ardour_data_search_path());
622
623         if (!icon_set.empty() && icon_set != _("default")) {
624
625                 /* add "icons/icon_set" but .. not allowed to add both of these at once */
626                 spath.add_subdirectory_to_paths ("icons");
627                 spath.add_subdirectory_to_paths (icon_set);
628
629                 find_file (spath, name, data_file_path);
630         } else {
631                 spath.add_subdirectory_to_paths ("icons");
632                 find_file (spath, name, data_file_path);
633         }
634
635         if (is_image && data_file_path.empty()) {
636
637                 if (!icon_set.empty() && icon_set != _("default")) {
638                         warning << string_compose (_("icon \"%1\" not found for icon set \"%2\", fallback to default"), cname, icon_set) << endmsg;
639                 }
640
641                 Searchpath def (ARDOUR::ardour_data_search_path());
642                 def.add_subdirectory_to_paths ("icons");
643
644                 if (!find_file (def, name, data_file_path)) {
645                         fatal << string_compose (_("cannot find icon image for %1 using %2"), name, spath.to_string()) << endmsg;
646                         abort(); /*NOTREACHED*/
647                 }
648         }
649
650         return data_file_path;
651 }
652
653 Glib::RefPtr<Gdk::Pixbuf>
654 ARDOUR_UI_UTILS::get_icon (const char* cname, string icon_set)
655 {
656         Glib::RefPtr<Gdk::Pixbuf> img;
657         try {
658                 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname, icon_set));
659         } catch (const Gdk::PixbufError &e) {
660                 cerr << "Caught PixbufError: " << e.what() << endl;
661         } catch (...) {
662                 error << string_compose (_("Caught exception while loading icon named %1"), cname) << endmsg;
663         }
664
665         return img;
666 }
667
668 namespace ARDOUR_UI_UTILS {
669 Glib::RefPtr<Gdk::Pixbuf>
670 get_icon (const char* cname)
671 {
672         Glib::RefPtr<Gdk::Pixbuf> img;
673         try {
674                 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname));
675         } catch (const Gdk::PixbufError &e) {
676                 cerr << "Caught PixbufError: " << e.what() << endl;
677         } catch (...) {
678                 error << string_compose (_("Caught exception while loading icon named %1"), cname) << endmsg;
679         }
680
681         return img;
682 }
683 }
684
685 string
686 ARDOUR_UI_UTILS::longest (vector<string>& strings)
687 {
688         if (strings.empty()) {
689                 return string ("");
690         }
691
692         vector<string>::iterator longest = strings.begin();
693         string::size_type longest_length = (*longest).length();
694
695         vector<string>::iterator i = longest;
696         ++i;
697
698         while (i != strings.end()) {
699
700                 string::size_type len = (*i).length();
701
702                 if (len > longest_length) {
703                         longest = i;
704                         longest_length = len;
705                 }
706
707                 ++i;
708         }
709
710         return *longest;
711 }
712
713 bool
714 ARDOUR_UI_UTILS::key_is_legal_for_numeric_entry (guint keyval)
715 {
716         /* we assume that this does not change over the life of the process
717          */
718
719         static int comma_decimal = -1;
720
721         switch (keyval) {
722         case GDK_period:
723         case GDK_comma:
724                 if (comma_decimal < 0) {
725                         std::lconv* lc = std::localeconv();
726                         if (strchr (lc->decimal_point, ',') != 0) {
727                                 comma_decimal = 1;
728                         } else {
729                                 comma_decimal = 0;
730                         }
731                 }
732                 break;
733         default:
734                 break;
735         }
736
737         switch (keyval) {
738         case GDK_decimalpoint:
739         case GDK_KP_Separator:
740                 return true;
741
742         case GDK_period:
743                 if (comma_decimal) {
744                         return false;
745                 } else {
746                         return true;
747                 }
748                 break;
749         case GDK_comma:
750                 if (comma_decimal) {
751                         return true;
752                 } else {
753                         return false;
754                 }
755                 break;
756         case GDK_minus:
757         case GDK_plus:
758         case GDK_0:
759         case GDK_1:
760         case GDK_2:
761         case GDK_3:
762         case GDK_4:
763         case GDK_5:
764         case GDK_6:
765         case GDK_7:
766         case GDK_8:
767         case GDK_9:
768         case GDK_KP_Add:
769         case GDK_KP_Subtract:
770         case GDK_KP_Decimal:
771         case GDK_KP_0:
772         case GDK_KP_1:
773         case GDK_KP_2:
774         case GDK_KP_3:
775         case GDK_KP_4:
776         case GDK_KP_5:
777         case GDK_KP_6:
778         case GDK_KP_7:
779         case GDK_KP_8:
780         case GDK_KP_9:
781         case GDK_Return:
782         case GDK_BackSpace:
783         case GDK_Delete:
784         case GDK_KP_Enter:
785         case GDK_Home:
786         case GDK_End:
787         case GDK_Left:
788         case GDK_Right:
789                 return true;
790
791         default:
792                 break;
793         }
794
795         return false;
796 }
797
798 void
799 ARDOUR_UI_UTILS::resize_window_to_proportion_of_monitor (Gtk::Window* window, int max_width, int max_height)
800 {
801         Glib::RefPtr<Gdk::Screen> screen = window->get_screen ();
802         Gdk::Rectangle monitor_rect;
803         screen->get_monitor_geometry (0, monitor_rect);
804
805         int const w = std::min (int (monitor_rect.get_width() * 0.8), max_width);
806         int const h = std::min (int (monitor_rect.get_height() * 0.8), max_height);
807
808         window->resize (w, h);
809 }
810
811
812 /** Replace _ with __ in a string; for use with menu item text to make underscores displayed correctly */
813 string
814 ARDOUR_UI_UTILS::escape_underscores (string const & s)
815 {
816         string o;
817         string::size_type const N = s.length ();
818
819         for (string::size_type i = 0; i < N; ++i) {
820                 if (s[i] == '_') {
821                         o += "__";
822                 } else {
823                         o += s[i];
824                 }
825         }
826
827         return o;
828 }
829
830 /** Replace < and > with &lt; and &gt; respectively to make < > display correctly in markup strings */
831 string
832 ARDOUR_UI_UTILS::escape_angled_brackets (string const & s)
833 {
834         string o = s;
835         boost::replace_all (o, "<", "&lt;");
836         boost::replace_all (o, ">", "&gt;");
837         return o;
838 }
839
840 Gdk::Color
841 ARDOUR_UI_UTILS::unique_random_color (list<Gdk::Color>& used_colors)
842 {
843         Gdk::Color newcolor;
844
845         while (1) {
846
847                 double h, s, v;
848
849                 h = fmod (random(), 360.0);
850                 s = (random() % 65535) / 65535.0;
851                 v = (random() % 65535) / 65535.0;
852
853                 s = min (0.5, s); /* not too saturated */
854                 v = max (0.9, v);  /* not too bright */
855                 newcolor.set_hsv (h, s, v);
856
857                 if (used_colors.size() == 0) {
858                         used_colors.push_back (newcolor);
859                         return newcolor;
860                 }
861
862                 for (list<Gdk::Color>::iterator i = used_colors.begin(); i != used_colors.end(); ++i) {
863                   Gdk::Color c = *i;
864                         float rdelta, bdelta, gdelta;
865
866                         rdelta = newcolor.get_red() - c.get_red();
867                         bdelta = newcolor.get_blue() - c.get_blue();
868                         gdelta = newcolor.get_green() - c.get_green();
869
870                         if (sqrt (rdelta*rdelta + bdelta*bdelta + gdelta*gdelta) > 25.0) {
871                                 /* different enough */
872                                 used_colors.push_back (newcolor);
873                                 return newcolor;
874                         }
875                 }
876
877                 /* XXX need throttle here to make sure we don't spin for ever */
878         }
879 }
880
881 string
882 ARDOUR_UI_UTILS::rate_as_string (float r)
883 {
884         char buf[32];
885         if (fmod (r, 1000.0f)) {
886                 snprintf (buf, sizeof (buf), "%.1f kHz", r/1000.0);
887         } else {
888                 snprintf (buf, sizeof (buf), "%.0f kHz", r/1000.0);
889         }
890         return buf;
891 }
892
893 bool
894 ARDOUR_UI_UTILS::windows_overlap (Gtk::Window *a, Gtk::Window *b)
895 {
896
897         if (!a || !b) {
898                 return false;
899         }
900         if (a->get_screen() == b->get_screen()) {
901                 gint ex, ey, ew, eh;
902                 gint mx, my, mw, mh;
903
904                 a->get_position (ex, ey);
905                 a->get_size (ew, eh);
906                 b->get_position (mx, my);
907                 b->get_size (mw, mh);
908
909                 GdkRectangle e;
910                 GdkRectangle m;
911                 GdkRectangle r;
912
913                 e.x = ex;
914                 e.y = ey;
915                 e.width = ew;
916                 e.height = eh;
917
918                 m.x = mx;
919                 m.y = my;
920                 m.width = mw;
921                 m.height = mh;
922
923                 if (gdk_rectangle_intersect (&e, &m, &r)) {
924                         return true;
925                 }
926         }
927         return false;
928 }