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