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