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