Merge branch 'cairocanvas'
[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, bool is_image)
680 {
681         std::string data_file_path;
682         string name = cname;
683
684         if (is_image) {
685                 name += X_(".png");
686         }
687
688         Searchpath spath(ARDOUR::ardour_data_search_path());
689
690         if (!icon_set.empty() && icon_set != _("default")) {
691
692                 /* add "icons/icon_set" but .. not allowed to add both of these at once */
693                 spath.add_subdirectory_to_paths ("icons");
694                 spath.add_subdirectory_to_paths (icon_set);
695                 
696                 find_file (spath, name, data_file_path);
697         }
698         
699         if (is_image && data_file_path.empty()) {
700                 
701                 if (!icon_set.empty() && icon_set != _("default")) {
702                         warning << string_compose (_("icon \"%1\" not found for icon set \"%2\", fallback to default"), cname, icon_set) << endmsg;
703                 }
704                 
705                 Searchpath def (ARDOUR::ardour_data_search_path());
706                 def.add_subdirectory_to_paths ("icons");
707         
708                 if (!find_file (def, name, data_file_path)) {
709                         fatal << string_compose (_("cannot find icon image for %1 using %2"), name, spath.to_string()) << endmsg;
710                         /*NOTREACHED*/
711                 }
712         }
713
714         return data_file_path;
715 }
716
717 Glib::RefPtr<Gdk::Pixbuf>
718 ARDOUR_UI_UTILS::get_icon (const char* cname, string icon_set)
719 {
720         Glib::RefPtr<Gdk::Pixbuf> img;
721         try {
722                 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname, icon_set));
723         } catch (const Gdk::PixbufError &e) {
724                 cerr << "Caught PixbufError: " << e.what() << endl;
725         } catch (...) {
726                 error << string_compose (_("Caught exception while loading icon named %1"), cname) << endmsg;
727         }
728
729         return img;
730 }
731
732 namespace ARDOUR_UI_UTILS {
733 Glib::RefPtr<Gdk::Pixbuf>
734 get_icon (const char* cname)
735 {
736         Glib::RefPtr<Gdk::Pixbuf> img;
737         try {
738                 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname));
739         } catch (const Gdk::PixbufError &e) {
740                 cerr << "Caught PixbufError: " << e.what() << endl;
741         } catch (...) {
742                 error << string_compose (_("Caught exception while loading icon named %1"), cname) << endmsg;
743         }
744
745         return img;
746 }
747 }
748
749 string
750 ARDOUR_UI_UTILS::longest (vector<string>& strings)
751 {
752         if (strings.empty()) {
753                 return string ("");
754         }
755
756         vector<string>::iterator longest = strings.begin();
757         string::size_type longest_length = (*longest).length();
758
759         vector<string>::iterator i = longest;
760         ++i;
761
762         while (i != strings.end()) {
763
764                 string::size_type len = (*i).length();
765
766                 if (len > longest_length) {
767                         longest = i;
768                         longest_length = len;
769                 }
770
771                 ++i;
772         }
773
774         return *longest;
775 }
776
777 bool
778 ARDOUR_UI_UTILS::key_is_legal_for_numeric_entry (guint keyval)
779 {
780         /* we assume that this does not change over the life of the process 
781          */
782
783         static int comma_decimal = -1;
784
785         switch (keyval) {
786         case GDK_period:
787         case GDK_comma:
788                 if (comma_decimal < 0) {
789                         std::lconv* lc = std::localeconv();
790                         if (strchr (lc->decimal_point, ',') != 0) {
791                                 comma_decimal = 1;
792                         } else {
793                                 comma_decimal = 0;
794                         }
795                 }
796                 break;
797         default:
798                 break;
799         }
800
801         switch (keyval) {
802         case GDK_decimalpoint:
803         case GDK_KP_Separator:
804                 return true;
805
806         case GDK_period:
807                 if (comma_decimal) {
808                         return false;
809                 } else {
810                         return true;
811                 }
812                 break;
813         case GDK_comma:
814                 if (comma_decimal) {
815                         return true;
816                 } else {
817                         return false;
818                 }
819                 break;
820         case GDK_minus:
821         case GDK_plus:
822         case GDK_0:
823         case GDK_1:
824         case GDK_2:
825         case GDK_3:
826         case GDK_4:
827         case GDK_5:
828         case GDK_6:
829         case GDK_7:
830         case GDK_8:
831         case GDK_9:
832         case GDK_KP_Add:
833         case GDK_KP_Subtract:
834         case GDK_KP_Decimal:
835         case GDK_KP_0:
836         case GDK_KP_1:
837         case GDK_KP_2:
838         case GDK_KP_3:
839         case GDK_KP_4:
840         case GDK_KP_5:
841         case GDK_KP_6:
842         case GDK_KP_7:
843         case GDK_KP_8:
844         case GDK_KP_9:
845         case GDK_Return:
846         case GDK_BackSpace:
847         case GDK_Delete:
848         case GDK_KP_Enter:
849         case GDK_Home:
850         case GDK_End:
851         case GDK_Left:
852         case GDK_Right:
853                 return true;
854
855         default:
856                 break;
857         }
858
859         return false;
860 }
861
862 void
863 ARDOUR_UI_UTILS::set_pango_fontsize ()
864 {
865         long val = ARDOUR::Config->get_font_scale();
866
867         /* FT2 rendering - used by GnomeCanvas, sigh */
868
869 #ifndef PLATFORM_WINDOWS
870         pango_ft2_font_map_set_resolution ((PangoFT2FontMap*) pango_ft2_font_map_new(), val/1024, val/1024);
871 #endif
872
873         /* Cairo rendering, in case there is any */
874
875         pango_cairo_font_map_set_resolution ((PangoCairoFontMap*) pango_cairo_font_map_get_default(), val/1024);
876 }
877
878 void
879 ARDOUR_UI_UTILS::reset_dpi ()
880 {
881         long val = ARDOUR::Config->get_font_scale();
882         set_pango_fontsize ();
883         /* Xft rendering */
884
885         gtk_settings_set_long_property (gtk_settings_get_default(),
886                                         "gtk-xft-dpi", val, "ardour");
887         DPIReset();//Emit Signal
888 }
889
890 void
891 ARDOUR_UI_UTILS::resize_window_to_proportion_of_monitor (Gtk::Window* window, int max_width, int max_height)
892 {
893         Glib::RefPtr<Gdk::Screen> screen = window->get_screen ();
894         Gdk::Rectangle monitor_rect;
895         screen->get_monitor_geometry (0, monitor_rect);
896
897         int const w = std::min (int (monitor_rect.get_width() * 0.8), max_width);
898         int const h = std::min (int (monitor_rect.get_height() * 0.8), max_height);
899
900         window->resize (w, h);
901 }
902
903
904 /** Replace _ with __ in a string; for use with menu item text to make underscores displayed correctly */
905 string
906 ARDOUR_UI_UTILS::escape_underscores (string const & s)
907 {
908         string o;
909         string::size_type const N = s.length ();
910
911         for (string::size_type i = 0; i < N; ++i) {
912                 if (s[i] == '_') {
913                         o += "__";
914                 } else {
915                         o += s[i];
916                 }
917         }
918
919         return o;
920 }
921
922 /** Replace < and > with &lt; and &gt; respectively to make < > display correctly in markup strings */
923 string
924 ARDOUR_UI_UTILS::escape_angled_brackets (string const & s)
925 {
926         string o = s;
927         boost::replace_all (o, "<", "&lt;");
928         boost::replace_all (o, ">", "&gt;");
929         return o;
930 }
931
932 Gdk::Color
933 ARDOUR_UI_UTILS::unique_random_color (list<Gdk::Color>& used_colors)
934 {
935         Gdk::Color newcolor;
936
937         while (1) {
938
939                 double h, s, v;
940
941                 h = fmod (random(), 360.0);
942                 s = (random() % 65535) / 65535.0;
943                 v = (random() % 65535) / 65535.0;
944
945                 s = min (0.5, s); /* not too saturated */
946                 v = max (0.9, v);  /* not too bright */
947                 newcolor.set_hsv (h, s, v);
948
949                 if (used_colors.size() == 0) {
950                         used_colors.push_back (newcolor);
951                         return newcolor;
952                 }
953
954                 for (list<Gdk::Color>::iterator i = used_colors.begin(); i != used_colors.end(); ++i) {
955                   Gdk::Color c = *i;
956                         float rdelta, bdelta, gdelta;
957
958                         rdelta = newcolor.get_red() - c.get_red();
959                         bdelta = newcolor.get_blue() - c.get_blue();
960                         gdelta = newcolor.get_green() - c.get_green();
961
962                         if (sqrt (rdelta*rdelta + bdelta*bdelta + gdelta*gdelta) > 25.0) {
963                                 /* different enough */
964                                 used_colors.push_back (newcolor);
965                                 return newcolor;
966                         }
967                 }
968
969                 /* XXX need throttle here to make sure we don't spin for ever */
970         }
971 }
972
973 string 
974 ARDOUR_UI_UTILS::rate_as_string (float r)
975 {
976         char buf[32];
977         if (fmod (r, 1000.0f)) {
978                 snprintf (buf, sizeof (buf), "%.1f kHz", r/1000.0);
979         } else {
980                 snprintf (buf, sizeof (buf), "%.0f kHz", r/1000.0);
981         }
982         return buf;
983 }