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