display key name when debugging keyboard/accel stuff
[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 = g = b = 0;
300                         r = rc->bg[state].red / 257;
301                         g = rc->bg[state].green / 257;
302                         b = rc->bg[state].blue / 257;
303                 } else if (attr == "base") {
304                         r = rc->base[state].red / 257;
305                         g = rc->base[state].green / 257;
306                         b = rc->base[state].blue / 257;
307                 } else if (attr == "text") {
308                         r = rc->text[state].red / 257;
309                         g = rc->text[state].green / 257;
310                         b = rc->text[state].blue / 257;
311                 }
312         } else {
313                 warning << string_compose (_("missing RGBA style for \"%1\""), style) << endl;
314         }
315
316         window->remove ();
317
318         if (state == Gtk::STATE_NORMAL && rgba) {
319                 return (uint32_t) RGBA_TO_UINT(r,g,b,a);
320         } else {
321                 return (uint32_t) RGBA_TO_UINT(r,g,b,255);
322         }
323 }
324
325 bool
326 ARDOUR_UI_UTILS::rgba_p_from_style (string style, float *r, float *g, float *b, string attr, int state)
327 {
328         static Gtk::Window* window = 0;
329         assert (r && g && b);
330
331         if (window == 0) {
332                 window = new Window (WINDOW_TOPLEVEL);
333         }
334
335         Gtk::EventBox foo;
336
337         window->add (foo);
338
339         foo.set_name (style);
340         foo.ensure_style ();
341
342         GtkRcStyle* rc = foo.get_style()->gobj()->rc_style;
343
344         if (!rc) {
345                 warning << string_compose (_("missing RGBA style for \"%1\""), style) << endl;
346                 return false;
347         }
348         if (attr == "fg") {
349                 *r = rc->fg[state].red / 65535.0;
350                 *g = rc->fg[state].green / 65535.0;
351                 *b = rc->fg[state].blue / 65535.0;
352         } else if (attr == "bg") {
353                 *r = rc->bg[state].red / 65535.0;
354                 *g = rc->bg[state].green / 65535.0;
355                 *b = rc->bg[state].blue / 65535.0;
356         } else if (attr == "base") {
357                 *r = rc->base[state].red / 65535.0;
358                 *g = rc->base[state].green / 65535.0;
359                 *b = rc->base[state].blue / 65535.0;
360         } else if (attr == "text") {
361                 *r = rc->text[state].red / 65535.0;
362                 *g = rc->text[state].green / 65535.0;
363                 *b = rc->text[state].blue / 65535.0;
364         } else {
365                 return false;
366         }
367
368         window->remove ();
369         return true;
370 }
371
372 void
373 ARDOUR_UI_UTILS::set_color_from_rgb (Gdk::Color& c, uint32_t rgb)
374 {
375         /* Gdk::Color color ranges are 16 bit, so scale from 8 bit by
376            multiplying by 256.
377         */
378         c.set_rgb ((rgb >> 16)*256, ((rgb & 0xff00) >> 8)*256, (rgb & 0xff)*256);
379 }
380
381 void
382 ARDOUR_UI_UTILS::set_color_from_rgba (Gdk::Color& c, uint32_t rgba)
383 {
384         /* Gdk::Color color ranges are 16 bit, so scale from 8 bit by
385            multiplying by 256.
386         */
387         c.set_rgb ((rgba >> 24)*256, ((rgba & 0xff0000) >> 16)*256, ((rgba & 0xff00) >> 8)*256);
388 }
389
390 uint32_t
391 ARDOUR_UI_UTILS::gdk_color_to_rgba (Gdk::Color const& c)
392 {
393         /* since alpha value is not available from a Gdk::Color, it is
394            hardcoded as 0xff (aka 255 or 1.0)
395         */
396
397         const uint32_t r = c.get_red_p () * 255.0;
398         const uint32_t g = c.get_green_p () * 255.0;
399         const uint32_t b = c.get_blue_p () * 255.0;
400         const uint32_t a = 0xff;
401
402         return RGBA_TO_UINT (r,g,b,a);
403 }
404
405
406 bool
407 ARDOUR_UI_UTILS::relay_key_press (GdkEventKey* ev, Gtk::Window* win)
408 {
409         PublicEditor& ed (PublicEditor::instance());
410
411         if (!key_press_focus_accelerator_handler (*win, ev)) {
412                 if (&ed == 0) {
413                         /* early key press in pre-main-window-dialogs, no editor yet */
414                         return false;
415                 }
416                 return ed.on_key_press_event(ev);
417         } else {
418                 return true;
419         }
420 }
421
422 bool
423 ARDOUR_UI_UTILS::forward_key_press (GdkEventKey* ev)
424 {
425         return PublicEditor::instance().on_key_press_event(ev);
426 }
427
428 bool
429 ARDOUR_UI_UTILS::emulate_key_event (Gtk::Widget* w, unsigned int keyval)
430 {
431         GdkDisplay  *display = gtk_widget_get_display (GTK_WIDGET(w->gobj()));
432         GdkKeymap   *keymap  = gdk_keymap_get_for_display (display);
433         GdkKeymapKey *keymapkey = NULL;
434         gint n_keys;
435
436         if (!gdk_keymap_get_entries_for_keyval(keymap, keyval, &keymapkey, &n_keys)) return false;
437         if (n_keys !=1) { g_free(keymapkey); return false;}
438
439         GdkEventKey ev;
440         ev.type = GDK_KEY_PRESS;
441         ev.window = gtk_widget_get_window(GTK_WIDGET(w->gobj()));
442         ev.send_event = FALSE;
443         ev.time = 0;
444         ev.state = 0;
445         ev.keyval = keyval;
446         ev.length = 0;
447         ev.string = const_cast<gchar*> ("");
448         ev.hardware_keycode = keymapkey[0].keycode;
449         ev.group = keymapkey[0].group;
450         g_free(keymapkey);
451
452         forward_key_press(&ev);
453         ev.type = GDK_KEY_RELEASE;
454         return forward_key_press(&ev);
455 }
456
457 bool
458 ARDOUR_UI_UTILS::key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev)
459 {
460         GtkWindow* win = window.gobj();
461         GtkWidget* focus = gtk_window_get_focus (win);
462         bool special_handling_of_unmodified_accelerators = false;
463         bool allow_activating = true;
464         /* consider all relevant modifiers but not LOCK or SHIFT */
465         const guint mask = (Keyboard::RelevantModifierKeyMask & ~(Gdk::SHIFT_MASK|Gdk::LOCK_MASK));
466
467         if (focus) {
468                 if (GTK_IS_ENTRY(focus) || Keyboard::some_magic_widget_has_focus()) {
469                         special_handling_of_unmodified_accelerators = true;
470                 }
471         }
472
473 #ifdef GTKOSX
474         /* at one time this appeared to be necessary. As of July 2012, it does not
475            appear to be. if it ever is necessar, figure out if it should apply
476            to all platforms.
477         */
478 #if 0 
479         if (Keyboard::some_magic_widget_has_focus ()) {
480                 allow_activating = false;
481         }
482 #endif
483 #endif
484
485
486         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",
487                                                           win,
488                                                           ev->keyval,
489                                                           ev->state,
490                                                           special_handling_of_unmodified_accelerators,
491                                                           Keyboard::some_magic_widget_has_focus(),
492                                                           allow_activating,
493                                                           focus));
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)
680 {
681         std::string data_file_path;
682         string name = cname;
683         name += X_(".png");
684
685         Searchpath spath(ARDOUR::ardour_data_search_path());
686
687         if (!icon_set.empty() && icon_set != _("default")) {
688
689                 /* add "icons/icon_set" but .. not allowed to add both of these at once */
690                 spath.add_subdirectory_to_paths ("icons");
691                 spath.add_subdirectory_to_paths (icon_set);
692                 
693                 find_file (spath, name, data_file_path);
694         }
695         
696         if (data_file_path.empty()) {
697                 
698                 if (!icon_set.empty() && icon_set != _("default")) {
699                         warning << string_compose (_("icon \"%1\" not found for icon set \"%2\", fallback to default"), cname, icon_set) << endmsg;
700                 }
701                 
702                 Searchpath def (ARDOUR::ardour_data_search_path());
703                 def.add_subdirectory_to_paths ("icons");
704         
705                 if (!find_file (def, name, data_file_path)) {
706                         fatal << string_compose (_("cannot find icon image for %1 using %2"), name, spath.to_string()) << endmsg;
707                         /*NOTREACHED*/
708                 }
709         }
710
711         return data_file_path;
712 }
713
714 Glib::RefPtr<Gdk::Pixbuf>
715 ARDOUR_UI_UTILS::get_icon (const char* cname, string icon_set)
716 {
717         Glib::RefPtr<Gdk::Pixbuf> img;
718         try {
719                 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname, icon_set));
720         } catch (const Gdk::PixbufError &e) {
721                 cerr << "Caught PixbufError: " << e.what() << endl;
722         } catch (...) {
723                 error << string_compose (_("Caught exception while loading icon named %1"), cname) << endmsg;
724         }
725
726         return img;
727 }
728
729 namespace ARDOUR_UI_UTILS {
730 Glib::RefPtr<Gdk::Pixbuf>
731 get_icon (const char* cname)
732 {
733         Glib::RefPtr<Gdk::Pixbuf> img;
734         try {
735                 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname));
736         } catch (const Gdk::PixbufError &e) {
737                 cerr << "Caught PixbufError: " << e.what() << endl;
738         } catch (...) {
739                 error << string_compose (_("Caught exception while loading icon named %1"), cname) << endmsg;
740         }
741
742         return img;
743 }
744 }
745
746 string
747 ARDOUR_UI_UTILS::longest (vector<string>& strings)
748 {
749         if (strings.empty()) {
750                 return string ("");
751         }
752
753         vector<string>::iterator longest = strings.begin();
754         string::size_type longest_length = (*longest).length();
755
756         vector<string>::iterator i = longest;
757         ++i;
758
759         while (i != strings.end()) {
760
761                 string::size_type len = (*i).length();
762
763                 if (len > longest_length) {
764                         longest = i;
765                         longest_length = len;
766                 }
767
768                 ++i;
769         }
770
771         return *longest;
772 }
773
774 bool
775 ARDOUR_UI_UTILS::key_is_legal_for_numeric_entry (guint keyval)
776 {
777         /* we assume that this does not change over the life of the process 
778          */
779
780         static int comma_decimal = -1;
781
782         switch (keyval) {
783         case GDK_period:
784         case GDK_comma:
785                 if (comma_decimal < 0) {
786                         std::lconv* lc = std::localeconv();
787                         if (strchr (lc->decimal_point, ',') != 0) {
788                                 comma_decimal = 1;
789                         } else {
790                                 comma_decimal = 0;
791                         }
792                 }
793                 break;
794         default:
795                 break;
796         }
797
798         switch (keyval) {
799         case GDK_decimalpoint:
800         case GDK_KP_Separator:
801                 return true;
802
803         case GDK_period:
804                 if (comma_decimal) {
805                         return false;
806                 } else {
807                         return true;
808                 }
809                 break;
810         case GDK_comma:
811                 if (comma_decimal) {
812                         return true;
813                 } else {
814                         return false;
815                 }
816                 break;
817         case GDK_minus:
818         case GDK_plus:
819         case GDK_0:
820         case GDK_1:
821         case GDK_2:
822         case GDK_3:
823         case GDK_4:
824         case GDK_5:
825         case GDK_6:
826         case GDK_7:
827         case GDK_8:
828         case GDK_9:
829         case GDK_KP_Add:
830         case GDK_KP_Subtract:
831         case GDK_KP_Decimal:
832         case GDK_KP_0:
833         case GDK_KP_1:
834         case GDK_KP_2:
835         case GDK_KP_3:
836         case GDK_KP_4:
837         case GDK_KP_5:
838         case GDK_KP_6:
839         case GDK_KP_7:
840         case GDK_KP_8:
841         case GDK_KP_9:
842         case GDK_Return:
843         case GDK_BackSpace:
844         case GDK_Delete:
845         case GDK_KP_Enter:
846         case GDK_Home:
847         case GDK_End:
848         case GDK_Left:
849         case GDK_Right:
850                 return true;
851
852         default:
853                 break;
854         }
855
856         return false;
857 }
858
859 void
860 ARDOUR_UI_UTILS::set_pango_fontsize ()
861 {
862         long val = ARDOUR::Config->get_font_scale();
863
864         /* FT2 rendering - used by GnomeCanvas, sigh */
865
866 #ifndef PLATFORM_WINDOWS
867         pango_ft2_font_map_set_resolution ((PangoFT2FontMap*) pango_ft2_font_map_new(), val/1024, val/1024);
868 #endif
869
870         /* Cairo rendering, in case there is any */
871
872         pango_cairo_font_map_set_resolution ((PangoCairoFontMap*) pango_cairo_font_map_get_default(), val/1024);
873 }
874
875 void
876 ARDOUR_UI_UTILS::reset_dpi ()
877 {
878         long val = ARDOUR::Config->get_font_scale();
879         set_pango_fontsize ();
880         /* Xft rendering */
881
882         gtk_settings_set_long_property (gtk_settings_get_default(),
883                                         "gtk-xft-dpi", val, "ardour");
884         DPIReset();//Emit Signal
885 }
886
887 void
888 ARDOUR_UI_UTILS::resize_window_to_proportion_of_monitor (Gtk::Window* window, int max_width, int max_height)
889 {
890         Glib::RefPtr<Gdk::Screen> screen = window->get_screen ();
891         Gdk::Rectangle monitor_rect;
892         screen->get_monitor_geometry (0, monitor_rect);
893
894         int const w = std::min (int (monitor_rect.get_width() * 0.8), max_width);
895         int const h = std::min (int (monitor_rect.get_height() * 0.8), max_height);
896
897         window->resize (w, h);
898 }
899
900
901 /** Replace _ with __ in a string; for use with menu item text to make underscores displayed correctly */
902 string
903 ARDOUR_UI_UTILS::escape_underscores (string const & s)
904 {
905         string o;
906         string::size_type const N = s.length ();
907
908         for (string::size_type i = 0; i < N; ++i) {
909                 if (s[i] == '_') {
910                         o += "__";
911                 } else {
912                         o += s[i];
913                 }
914         }
915
916         return o;
917 }
918
919 /** Replace < and > with &lt; and &gt; respectively to make < > display correctly in markup strings */
920 string
921 ARDOUR_UI_UTILS::escape_angled_brackets (string const & s)
922 {
923         string o = s;
924         boost::replace_all (o, "<", "&lt;");
925         boost::replace_all (o, ">", "&gt;");
926         return o;
927 }
928
929 Gdk::Color
930 ARDOUR_UI_UTILS::unique_random_color (list<Gdk::Color>& used_colors)
931 {
932         Gdk::Color newcolor;
933
934         while (1) {
935
936                 double h, s, v;
937
938                 h = fmod (random(), 360.0);
939                 s = (random() % 65535) / 65535.0;
940                 v = (random() % 65535) / 65535.0;
941
942                 s = min (0.5, s); /* not too saturated */
943                 v = max (0.9, v);  /* not too bright */
944                 newcolor.set_hsv (h, s, v);
945
946                 if (used_colors.size() == 0) {
947                         used_colors.push_back (newcolor);
948                         return newcolor;
949                 }
950
951                 for (list<Gdk::Color>::iterator i = used_colors.begin(); i != used_colors.end(); ++i) {
952                   Gdk::Color c = *i;
953                         float rdelta, bdelta, gdelta;
954
955                         rdelta = newcolor.get_red() - c.get_red();
956                         bdelta = newcolor.get_blue() - c.get_blue();
957                         gdelta = newcolor.get_green() - c.get_green();
958
959                         if (sqrt (rdelta*rdelta + bdelta*bdelta + gdelta*gdelta) > 25.0) {
960                                 /* different enough */
961                                 used_colors.push_back (newcolor);
962                                 return newcolor;
963                         }
964                 }
965
966                 /* XXX need throttle here to make sure we don't spin for ever */
967         }
968 }
969
970 string 
971 ARDOUR_UI_UTILS::rate_as_string (float r)
972 {
973         char buf[32];
974         if (fmod (r, 1000.0f)) {
975                 snprintf (buf, sizeof (buf), "%.1f kHz", r/1000.0);
976         } else {
977                 snprintf (buf, sizeof (buf), "%.0f kHz", r/1000.0);
978         }
979         return buf;
980 }
981
982
983 string
984 ARDOUR_UI_UTILS::track_number_to_string (
985                 int64_t tracknumber,
986                 std::string sep,
987                 std::string postfix
988                 )
989 {
990         string rv;
991         if (tracknumber > 0) {
992                 rv = "<span weight=\"bold\" font_family=\"ArdourMono, Mono\">";
993                 rv += PBD::to_string (tracknumber, std::dec);
994                 rv += "</span>";
995                 rv += sep;
996         }
997         else if (tracknumber < 0) {
998                 rv = "<span weight=\"bold\" font_family=\"ArdourMono, Mono\">";
999                 rv += PBD::to_string (-tracknumber, std::dec);
1000                 rv += "</span>";
1001                 rv += sep;
1002         }
1003         rv += Glib::Markup::escape_text(postfix);
1004         return rv;
1005 }