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