0f61656033dd05bd593e86e7628e74b86328a053
[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 (%8) 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                                                           (focus ? gtk_widget_get_name (focus) : "no focus widget")));
495
496         /* This exists to allow us to override the way GTK handles
497            key events. The normal sequence is:
498
499            a) event is delivered to a GtkWindow
500            b) accelerators/mnemonics are activated
501            c) if (b) didn't handle the event, propagate to
502                the focus widget and/or focus chain
503
504            The problem with this is that if the accelerators include
505            keys without modifiers, such as the space bar or the
506            letter "e", then pressing the key while typing into
507            a text entry widget results in the accelerator being
508            activated, instead of the desired letter appearing
509            in the text entry.
510
511            There is no good way of fixing this, but this
512            represents a compromise. The idea is that
513            key events involving modifiers (not Shift)
514            get routed into the activation pathway first, then
515            get propagated to the focus widget if necessary.
516
517            If the key event doesn't involve modifiers,
518            we deliver to the focus widget first, thus allowing
519            it to get "normal text" without interference
520            from acceleration.
521
522            Of course, this can also be problematic: if there
523            is a widget with focus, then it will swallow
524            all "normal text" accelerators.
525         */
526
527         if (!special_handling_of_unmodified_accelerators) {
528
529                 /* XXX note that for a brief moment, the conditional above
530                  * included "|| (ev->state & mask)" so as to enforce the
531                  * implication of special_handling_of_UNMODIFIED_accelerators.
532                  * however, this forces any key that GTK doesn't allow and that
533                  * we have an alternative (see next comment) for to be
534                  * automatically sent through the accel groups activation
535                  * pathway, which prevents individual widgets & canvas items
536                  * from ever seeing it if is used by a key binding.
537                  * 
538                  * specifically, this hid Ctrl-down-arrow from MIDI region
539                  * views because it is also bound to an action.
540                  *
541                  * until we have a robust, clean binding system, this
542                  * quirk will have to remain in place.
543                  */
544
545                 /* pretend that certain key events that GTK does not allow
546                    to be used as accelerators are actually something that
547                    it does allow. but only where there are no modifiers.
548                 */
549
550                 uint32_t fakekey = ev->keyval;
551
552                 if (Gtkmm2ext::possibly_translate_keyval_to_make_legal_accelerator (fakekey)) {
553                         DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tactivate (was %1 now %2) without special hanlding of unmodified accels\n",
554                                                                           ev->keyval, fakekey));
555
556                         GdkModifierType mod = GdkModifierType (ev->state);
557
558                         mod = GdkModifierType (mod & gtk_accelerator_get_default_mod_mask());
559 #ifdef GTKOSX
560                         /* GTK on OS X is currently (February 2012) setting both
561                            the Meta and Mod2 bits in the event modifier state if 
562                            the Command key is down.
563
564                            gtk_accel_groups_activate() does not invoke any of the logic
565                            that gtk_window_activate_key() will that sorts out that stupid
566                            state of affairs, and as a result it fails to find a match
567                            for the key event and the current set of accelerators.
568
569                            to fix this, if the meta bit is set, remove the mod2 bit
570                            from the modifier. this assumes that our bindings use Primary
571                            which will have set the meta bit in the accelerator entry.
572                         */
573                         if (mod & GDK_META_MASK) {
574                                 mod = GdkModifierType (mod & ~GDK_MOD2_MASK);
575                         }
576 #endif
577
578                         if (allow_activating && gtk_accel_groups_activate(G_OBJECT(win), fakekey, mod)) {
579                                 DEBUG_TRACE (DEBUG::Accelerators, "\taccel group activated by fakekey\n");
580                                 return true;
581                         }
582                 }
583         }
584
585         if (!special_handling_of_unmodified_accelerators || (ev->state & mask)) {
586
587                 /* no special handling or there are modifiers in effect: accelerate first */
588
589                 DEBUG_TRACE (DEBUG::Accelerators, "\tactivate, then propagate\n");
590                 DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tevent send-event:%1 time:%2 length:%3 name %7 string:%4 hardware_keycode:%5 group:%6\n",
591                                                                   ev->send_event, ev->time, ev->length, ev->string, ev->hardware_keycode, ev->group, gdk_keyval_name (ev->keyval)));
592
593                 if (allow_activating) {
594                         DEBUG_TRACE (DEBUG::Accelerators, "\tsending to window\n");
595                         if (gtk_window_activate_key (win, ev)) {
596                                 DEBUG_TRACE (DEBUG::Accelerators, "\t\thandled\n");
597                                 return true;
598                         }
599                 } else {
600                         DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
601                 }
602
603                 DEBUG_TRACE (DEBUG::Accelerators, "\tnot accelerated, now propagate\n");
604
605                 return gtk_window_propagate_key_event (win, ev);
606         }
607
608         /* no modifiers, propagate first */
609
610         DEBUG_TRACE (DEBUG::Accelerators, "\tpropagate, then activate\n");
611
612         if (!gtk_window_propagate_key_event (win, ev)) {
613                 DEBUG_TRACE (DEBUG::Accelerators, "\tpropagation didn't handle, so activate\n");
614                 if (allow_activating) {
615                         return gtk_window_activate_key (win, ev);
616                 } else {
617                         DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
618                 }
619
620         } else {
621                 DEBUG_TRACE (DEBUG::Accelerators, "\thandled by propagate\n");
622                 return true;
623         }
624
625         DEBUG_TRACE (DEBUG::Accelerators, "\tnot handled\n");
626         return true;
627 }
628
629 Glib::RefPtr<Gdk::Pixbuf>
630 ARDOUR_UI_UTILS::get_xpm (std::string name)
631 {
632         if (!xpm_map[name]) {
633
634                 Searchpath spath(ARDOUR::ardour_data_search_path());
635
636                 spath.add_subdirectory_to_paths("pixmaps");
637
638                 std::string data_file_path;
639
640                 if(!find_file (spath, name, data_file_path)) {
641                         fatal << string_compose (_("cannot find XPM file for %1"), name) << endmsg;
642                 }
643
644                 try {
645                         xpm_map[name] =  Gdk::Pixbuf::create_from_file (data_file_path);
646                 } catch(const Glib::Error& e)   {
647                         warning << "Caught Glib::Error: " << e.what() << endmsg;
648                 }
649         }
650
651         return xpm_map[name];
652 }
653
654 vector<string>
655 ARDOUR_UI_UTILS::get_icon_sets ()
656 {
657         Searchpath spath(ARDOUR::ardour_data_search_path());
658         spath.add_subdirectory_to_paths ("icons");
659         vector<string> r;
660         
661         r.push_back (_("default"));
662
663         for (vector<string>::iterator s = spath.begin(); s != spath.end(); ++s) {
664
665                 vector<string> entries;
666
667                 get_paths (entries, *s, false, false);
668
669                 for (vector<string>::iterator e = entries.begin(); e != entries.end(); ++e) {
670                         if (Glib::file_test (*e, Glib::FILE_TEST_IS_DIR)) {
671                                 r.push_back (Glib::filename_to_utf8 (Glib::path_get_basename(*e)));
672                         }
673                 }
674         }
675
676         return r;
677 }
678
679 std::string
680 ARDOUR_UI_UTILS::get_icon_path (const char* cname, string icon_set, bool is_image)
681 {
682         std::string data_file_path;
683         string name = cname;
684
685         if (is_image) {
686                 name += X_(".png");
687         }
688
689         Searchpath spath(ARDOUR::ardour_data_search_path());
690
691         if (!icon_set.empty() && icon_set != _("default")) {
692
693                 /* add "icons/icon_set" but .. not allowed to add both of these at once */
694                 spath.add_subdirectory_to_paths ("icons");
695                 spath.add_subdirectory_to_paths (icon_set);
696                 
697                 find_file (spath, name, data_file_path);
698         }
699         
700         if (is_image && data_file_path.empty()) {
701                 
702                 if (!icon_set.empty() && icon_set != _("default")) {
703                         warning << string_compose (_("icon \"%1\" not found for icon set \"%2\", fallback to default"), cname, icon_set) << endmsg;
704                 }
705                 
706                 Searchpath def (ARDOUR::ardour_data_search_path());
707                 def.add_subdirectory_to_paths ("icons");
708         
709                 if (!find_file (def, name, data_file_path)) {
710                         fatal << string_compose (_("cannot find icon image for %1 using %2"), name, spath.to_string()) << endmsg;
711                         abort(); /*NOTREACHED*/
712                 }
713         }
714
715         return data_file_path;
716 }
717
718 Glib::RefPtr<Gdk::Pixbuf>
719 ARDOUR_UI_UTILS::get_icon (const char* cname, string icon_set)
720 {
721         Glib::RefPtr<Gdk::Pixbuf> img;
722         try {
723                 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname, icon_set));
724         } catch (const Gdk::PixbufError &e) {
725                 cerr << "Caught PixbufError: " << e.what() << endl;
726         } catch (...) {
727                 error << string_compose (_("Caught exception while loading icon named %1"), cname) << endmsg;
728         }
729
730         return img;
731 }
732
733 namespace ARDOUR_UI_UTILS {
734 Glib::RefPtr<Gdk::Pixbuf>
735 get_icon (const char* cname)
736 {
737         Glib::RefPtr<Gdk::Pixbuf> img;
738         try {
739                 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname));
740         } catch (const Gdk::PixbufError &e) {
741                 cerr << "Caught PixbufError: " << e.what() << endl;
742         } catch (...) {
743                 error << string_compose (_("Caught exception while loading icon named %1"), cname) << endmsg;
744         }
745
746         return img;
747 }
748 }
749
750 string
751 ARDOUR_UI_UTILS::longest (vector<string>& strings)
752 {
753         if (strings.empty()) {
754                 return string ("");
755         }
756
757         vector<string>::iterator longest = strings.begin();
758         string::size_type longest_length = (*longest).length();
759
760         vector<string>::iterator i = longest;
761         ++i;
762
763         while (i != strings.end()) {
764
765                 string::size_type len = (*i).length();
766
767                 if (len > longest_length) {
768                         longest = i;
769                         longest_length = len;
770                 }
771
772                 ++i;
773         }
774
775         return *longest;
776 }
777
778 bool
779 ARDOUR_UI_UTILS::key_is_legal_for_numeric_entry (guint keyval)
780 {
781         /* we assume that this does not change over the life of the process 
782          */
783
784         static int comma_decimal = -1;
785
786         switch (keyval) {
787         case GDK_period:
788         case GDK_comma:
789                 if (comma_decimal < 0) {
790                         std::lconv* lc = std::localeconv();
791                         if (strchr (lc->decimal_point, ',') != 0) {
792                                 comma_decimal = 1;
793                         } else {
794                                 comma_decimal = 0;
795                         }
796                 }
797                 break;
798         default:
799                 break;
800         }
801
802         switch (keyval) {
803         case GDK_decimalpoint:
804         case GDK_KP_Separator:
805                 return true;
806
807         case GDK_period:
808                 if (comma_decimal) {
809                         return false;
810                 } else {
811                         return true;
812                 }
813                 break;
814         case GDK_comma:
815                 if (comma_decimal) {
816                         return true;
817                 } else {
818                         return false;
819                 }
820                 break;
821         case GDK_minus:
822         case GDK_plus:
823         case GDK_0:
824         case GDK_1:
825         case GDK_2:
826         case GDK_3:
827         case GDK_4:
828         case GDK_5:
829         case GDK_6:
830         case GDK_7:
831         case GDK_8:
832         case GDK_9:
833         case GDK_KP_Add:
834         case GDK_KP_Subtract:
835         case GDK_KP_Decimal:
836         case GDK_KP_0:
837         case GDK_KP_1:
838         case GDK_KP_2:
839         case GDK_KP_3:
840         case GDK_KP_4:
841         case GDK_KP_5:
842         case GDK_KP_6:
843         case GDK_KP_7:
844         case GDK_KP_8:
845         case GDK_KP_9:
846         case GDK_Return:
847         case GDK_BackSpace:
848         case GDK_Delete:
849         case GDK_KP_Enter:
850         case GDK_Home:
851         case GDK_End:
852         case GDK_Left:
853         case GDK_Right:
854                 return true;
855
856         default:
857                 break;
858         }
859
860         return false;
861 }
862
863 void
864 ARDOUR_UI_UTILS::set_pango_fontsize ()
865 {
866         long val = ARDOUR::Config->get_font_scale();
867
868         /* FT2 rendering - used by GnomeCanvas, sigh */
869
870 #ifndef PLATFORM_WINDOWS
871         pango_ft2_font_map_set_resolution ((PangoFT2FontMap*) pango_ft2_font_map_new(), val/1024, val/1024);
872 #endif
873
874         /* Cairo rendering, in case there is any */
875
876         pango_cairo_font_map_set_resolution ((PangoCairoFontMap*) pango_cairo_font_map_get_default(), val/1024);
877 }
878
879 void
880 ARDOUR_UI_UTILS::reset_dpi ()
881 {
882         long val = ARDOUR::Config->get_font_scale();
883         set_pango_fontsize ();
884         /* Xft rendering */
885
886         gtk_settings_set_long_property (gtk_settings_get_default(),
887                                         "gtk-xft-dpi", val, "ardour");
888         DPIReset();//Emit Signal
889 }
890
891 void
892 ARDOUR_UI_UTILS::resize_window_to_proportion_of_monitor (Gtk::Window* window, int max_width, int max_height)
893 {
894         Glib::RefPtr<Gdk::Screen> screen = window->get_screen ();
895         Gdk::Rectangle monitor_rect;
896         screen->get_monitor_geometry (0, monitor_rect);
897
898         int const w = std::min (int (monitor_rect.get_width() * 0.8), max_width);
899         int const h = std::min (int (monitor_rect.get_height() * 0.8), max_height);
900
901         window->resize (w, h);
902 }
903
904
905 /** Replace _ with __ in a string; for use with menu item text to make underscores displayed correctly */
906 string
907 ARDOUR_UI_UTILS::escape_underscores (string const & s)
908 {
909         string o;
910         string::size_type const N = s.length ();
911
912         for (string::size_type i = 0; i < N; ++i) {
913                 if (s[i] == '_') {
914                         o += "__";
915                 } else {
916                         o += s[i];
917                 }
918         }
919
920         return o;
921 }
922
923 /** Replace < and > with &lt; and &gt; respectively to make < > display correctly in markup strings */
924 string
925 ARDOUR_UI_UTILS::escape_angled_brackets (string const & s)
926 {
927         string o = s;
928         boost::replace_all (o, "<", "&lt;");
929         boost::replace_all (o, ">", "&gt;");
930         return o;
931 }
932
933 Gdk::Color
934 ARDOUR_UI_UTILS::unique_random_color (list<Gdk::Color>& used_colors)
935 {
936         Gdk::Color newcolor;
937
938         while (1) {
939
940                 double h, s, v;
941
942                 h = fmod (random(), 360.0);
943                 s = (random() % 65535) / 65535.0;
944                 v = (random() % 65535) / 65535.0;
945
946                 s = min (0.5, s); /* not too saturated */
947                 v = max (0.9, v);  /* not too bright */
948                 newcolor.set_hsv (h, s, v);
949
950                 if (used_colors.size() == 0) {
951                         used_colors.push_back (newcolor);
952                         return newcolor;
953                 }
954
955                 for (list<Gdk::Color>::iterator i = used_colors.begin(); i != used_colors.end(); ++i) {
956                   Gdk::Color c = *i;
957                         float rdelta, bdelta, gdelta;
958
959                         rdelta = newcolor.get_red() - c.get_red();
960                         bdelta = newcolor.get_blue() - c.get_blue();
961                         gdelta = newcolor.get_green() - c.get_green();
962
963                         if (sqrt (rdelta*rdelta + bdelta*bdelta + gdelta*gdelta) > 25.0) {
964                                 /* different enough */
965                                 used_colors.push_back (newcolor);
966                                 return newcolor;
967                         }
968                 }
969
970                 /* XXX need throttle here to make sure we don't spin for ever */
971         }
972 }
973
974 string 
975 ARDOUR_UI_UTILS::rate_as_string (float r)
976 {
977         char buf[32];
978         if (fmod (r, 1000.0f)) {
979                 snprintf (buf, sizeof (buf), "%.1f kHz", r/1000.0);
980         } else {
981                 snprintf (buf, sizeof (buf), "%.0f kHz", r/1000.0);
982         }
983         return buf;
984 }