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