use gtk_accel_groups_active() in preference to gtk_window_activate_key() since the...
[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 void
267 ARDOUR_UI_UTILS::set_color_from_rgb (Gdk::Color& c, uint32_t rgb)
268 {
269         /* Gdk::Color color ranges are 16 bit, so scale from 8 bit by
270            multiplying by 256.
271         */
272         c.set_rgb ((rgb >> 16)*256, ((rgb & 0xff00) >> 8)*256, (rgb & 0xff)*256);
273 }
274
275 void
276 ARDOUR_UI_UTILS::set_color_from_rgba (Gdk::Color& c, uint32_t rgba)
277 {
278         /* Gdk::Color color ranges are 16 bit, so scale from 8 bit by
279            multiplying by 256.
280         */
281         c.set_rgb ((rgba >> 24)*256, ((rgba & 0xff0000) >> 16)*256, ((rgba & 0xff00) >> 8)*256);
282 }
283
284 uint32_t
285 ARDOUR_UI_UTILS::gdk_color_to_rgba (Gdk::Color const& c)
286 {
287         /* since alpha value is not available from a Gdk::Color, it is
288            hardcoded as 0xff (aka 255 or 1.0)
289         */
290
291         const uint32_t r = c.get_red_p () * 255.0;
292         const uint32_t g = c.get_green_p () * 255.0;
293         const uint32_t b = c.get_blue_p () * 255.0;
294         const uint32_t a = 0xff;
295
296         return RGBA_TO_UINT (r,g,b,a);
297 }
298
299
300 bool
301 ARDOUR_UI_UTILS::relay_key_press (GdkEventKey* ev, Gtk::Window* win)
302 {
303         PublicEditor& ed (PublicEditor::instance());
304
305         if (!key_press_focus_accelerator_handler (*win, ev)) {
306                 if (&ed == 0) {
307                         /* early key press in pre-main-window-dialogs, no editor yet */
308                         return false;
309                 }
310                 return ed.on_key_press_event(ev);
311         } else {
312                 return true;
313         }
314 }
315
316 bool
317 ARDOUR_UI_UTILS::forward_key_press (GdkEventKey* ev)
318 {
319         return PublicEditor::instance().on_key_press_event(ev);
320 }
321
322 bool
323 ARDOUR_UI_UTILS::emulate_key_event (Gtk::Widget* w, unsigned int keyval)
324 {
325         GdkDisplay  *display = gtk_widget_get_display (GTK_WIDGET(w->gobj()));
326         GdkKeymap   *keymap  = gdk_keymap_get_for_display (display);
327         GdkKeymapKey *keymapkey = NULL;
328         gint n_keys;
329
330         if (!gdk_keymap_get_entries_for_keyval(keymap, keyval, &keymapkey, &n_keys)) return false;
331         if (n_keys !=1) { g_free(keymapkey); return false;}
332
333         GdkEventKey ev;
334         ev.type = GDK_KEY_PRESS;
335         ev.window = gtk_widget_get_window(GTK_WIDGET(w->gobj()));
336         ev.send_event = FALSE;
337         ev.time = 0;
338         ev.state = 0;
339         ev.keyval = keyval;
340         ev.length = 0;
341         ev.string = const_cast<gchar*> ("");
342         ev.hardware_keycode = keymapkey[0].keycode;
343         ev.group = keymapkey[0].group;
344         g_free(keymapkey);
345
346         forward_key_press(&ev);
347         ev.type = GDK_KEY_RELEASE;
348         return forward_key_press(&ev);
349 }
350
351 static string
352 show_gdk_event_state (int state)
353 {
354         string s;
355         if (state & GDK_SHIFT_MASK) {
356                 s += "+SHIFT";
357         }
358         if (state & GDK_LOCK_MASK) {
359                 s += "+LOCK";
360         }
361         if (state & GDK_CONTROL_MASK) {
362                 s += "+CONTROL";
363         }
364         if (state & GDK_MOD1_MASK) {
365                 s += "+MOD1";
366         }
367         if (state & GDK_MOD2_MASK) {
368                 s += "+MOD2";
369         }
370         if (state & GDK_MOD3_MASK) {
371                 s += "+MOD3";
372         }
373         if (state & GDK_MOD4_MASK) {
374                 s += "+MOD4";
375         }
376         if (state & GDK_MOD5_MASK) {
377                 s += "+MOD5";
378         }
379         if (state & GDK_BUTTON1_MASK) {
380                 s += "+BUTTON1";
381         }
382         if (state & GDK_BUTTON2_MASK) {
383                 s += "+BUTTON2";
384         }
385         if (state & GDK_BUTTON3_MASK) {
386                 s += "+BUTTON3";
387         }
388         if (state & GDK_BUTTON4_MASK) {
389                 s += "+BUTTON4";
390         }
391         if (state & GDK_BUTTON5_MASK) {
392                 s += "+BUTTON5";
393         }
394         if (state & GDK_SUPER_MASK) {
395                 s += "+SUPER";
396         }
397         if (state & GDK_HYPER_MASK) {
398                 s += "+HYPER";
399         }
400         if (state & GDK_META_MASK) {
401                 s += "+META";
402         }
403         if (state & GDK_RELEASE_MASK) {
404                 s += "+RELEASE";
405         }
406
407         return s;
408 }
409 bool
410 ARDOUR_UI_UTILS::key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev)
411 {
412         GtkWindow* win = window.gobj();
413         GtkWidget* focus = gtk_window_get_focus (win);
414         bool special_handling_of_unmodified_accelerators = false;
415         bool allow_activating = true;
416         /* consider all relevant modifiers but not LOCK or SHIFT */
417         const guint mask = (Keyboard::RelevantModifierKeyMask & ~(Gdk::SHIFT_MASK|Gdk::LOCK_MASK));
418
419         if (focus) {
420                 if (GTK_IS_ENTRY(focus) || Keyboard::some_magic_widget_has_focus()) {
421                         special_handling_of_unmodified_accelerators = true;
422                 }
423         }
424
425 #ifdef GTKOSX
426         /* at one time this appeared to be necessary. As of July 2012, it does not
427            appear to be. if it ever is necessar, figure out if it should apply
428            to all platforms.
429         */
430 #if 0 
431         if (Keyboard::some_magic_widget_has_focus ()) {
432                 allow_activating = false;
433         }
434 #endif
435 #endif
436
437
438         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",
439                                                           win,
440                                                           ev->keyval,
441                                                           show_gdk_event_state (ev->state),
442                                                           special_handling_of_unmodified_accelerators,
443                                                           Keyboard::some_magic_widget_has_focus(),
444                                                           allow_activating,
445                                                           focus,
446                                                           (focus ? gtk_widget_get_name (focus) : "no focus widget")));
447
448         /* This exists to allow us to override the way GTK handles
449            key events. The normal sequence is:
450
451            a) event is delivered to a GtkWindow
452            b) accelerators/mnemonics are activated
453            c) if (b) didn't handle the event, propagate to
454                the focus widget and/or focus chain
455
456            The problem with this is that if the accelerators include
457            keys without modifiers, such as the space bar or the
458            letter "e", then pressing the key while typing into
459            a text entry widget results in the accelerator being
460            activated, instead of the desired letter appearing
461            in the text entry.
462
463            There is no good way of fixing this, but this
464            represents a compromise. The idea is that
465            key events involving modifiers (not Shift)
466            get routed into the activation pathway first, then
467            get propagated to the focus widget if necessary.
468
469            If the key event doesn't involve modifiers,
470            we deliver to the focus widget first, thus allowing
471            it to get "normal text" without interference
472            from acceleration.
473
474            Of course, this can also be problematic: if there
475            is a widget with focus, then it will swallow
476            all "normal text" accelerators.
477         */
478
479         if (!special_handling_of_unmodified_accelerators) {
480
481                 /* XXX note that for a brief moment, the conditional above
482                  * included "|| (ev->state & mask)" so as to enforce the
483                  * implication of special_handling_of_UNMODIFIED_accelerators.
484                  * however, this forces any key that GTK doesn't allow and that
485                  * we have an alternative (see next comment) for to be
486                  * automatically sent through the accel groups activation
487                  * pathway, which prevents individual widgets & canvas items
488                  * from ever seeing it if is used by a key binding.
489                  * 
490                  * specifically, this hid Ctrl-down-arrow from MIDI region
491                  * views because it is also bound to an action.
492                  *
493                  * until we have a robust, clean binding system, this
494                  * quirk will have to remain in place.
495                  */
496
497                 /* pretend that certain key events that GTK does not allow
498                    to be used as accelerators are actually something that
499                    it does allow. but only where there are no modifiers.
500                 */
501
502                 uint32_t fakekey = ev->keyval;
503
504                 if (Gtkmm2ext::possibly_translate_keyval_to_make_legal_accelerator (fakekey)) {
505                         DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tactivate (was %1 now %2) without special hanlding of unmodified accels\n",
506                                                                           ev->keyval, fakekey));
507
508                         GdkModifierType mod = GdkModifierType (ev->state);
509
510                         mod = GdkModifierType (mod & gtk_accelerator_get_default_mod_mask());
511                         Gtkmm2ext::possibly_translate_mod_to_make_legal_accelerator(mod);
512
513                         DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tmodified modifier was %1\n", show_gdk_event_state (mod)));
514                         
515                         if (allow_activating && gtk_accel_groups_activate(G_OBJECT(win), fakekey, mod)) {
516                                 DEBUG_TRACE (DEBUG::Accelerators, "\taccel group activated by fakekey\n");
517                                 return true;
518                         }
519                 }
520         }
521
522         if (!special_handling_of_unmodified_accelerators || (ev->state & mask)) {
523
524                 /* no special handling or there are modifiers in effect: accelerate first */
525
526                 DEBUG_TRACE (DEBUG::Accelerators, "\tactivate, then propagate\n");
527                 DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tevent send-event:%1 time:%2 length:%3 name %7 string:%4 hardware_keycode:%5 group:%6\n",
528                                                                   ev->send_event, ev->time, ev->length, ev->string, ev->hardware_keycode, ev->group, gdk_keyval_name (ev->keyval)));
529
530                 if (allow_activating) {
531                         DEBUG_TRACE (DEBUG::Accelerators, "\tsending to window\n");
532                         if (gtk_accel_groups_activate (G_OBJECT(win), ev->keyval, GdkModifierType (ev->state & mask))) {
533                                 DEBUG_TRACE (DEBUG::Accelerators, "\t\thandled\n");
534                                 return true;
535                         }
536                 } else {
537                         DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
538                 }
539
540                 DEBUG_TRACE (DEBUG::Accelerators, "\tnot accelerated, now propagate\n");
541
542                 return gtk_window_propagate_key_event (win, ev);
543         }
544
545         /* no modifiers, propagate first */
546
547         DEBUG_TRACE (DEBUG::Accelerators, "\tpropagate, then activate\n");
548
549         if (!gtk_window_propagate_key_event (win, ev)) {
550                 DEBUG_TRACE (DEBUG::Accelerators, "\tpropagation didn't handle, so activate\n");
551                 if (allow_activating) {
552                         return gtk_accel_groups_activate (G_OBJECT(win), ev->keyval, GdkModifierType (ev->state & mask));
553                 } else {
554                         DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
555                 }
556
557         } else {
558                 DEBUG_TRACE (DEBUG::Accelerators, "\thandled by propagate\n");
559                 return true;
560         }
561
562         DEBUG_TRACE (DEBUG::Accelerators, "\tnot handled\n");
563         return true;
564 }
565
566 Glib::RefPtr<Gdk::Pixbuf>
567 ARDOUR_UI_UTILS::get_xpm (std::string name)
568 {
569         if (!xpm_map[name]) {
570
571                 Searchpath spath(ARDOUR::ardour_data_search_path());
572
573                 spath.add_subdirectory_to_paths("pixmaps");
574
575                 std::string data_file_path;
576
577                 if(!find_file (spath, name, data_file_path)) {
578                         fatal << string_compose (_("cannot find XPM file for %1"), name) << endmsg;
579                 }
580
581                 try {
582                         xpm_map[name] =  Gdk::Pixbuf::create_from_file (data_file_path);
583                 } catch(const Glib::Error& e)   {
584                         warning << "Caught Glib::Error: " << e.what() << endmsg;
585                 }
586         }
587
588         return xpm_map[name];
589 }
590
591 vector<string>
592 ARDOUR_UI_UTILS::get_icon_sets ()
593 {
594         Searchpath spath(ARDOUR::ardour_data_search_path());
595         spath.add_subdirectory_to_paths ("icons");
596         vector<string> r;
597         
598         r.push_back (_("default"));
599
600         for (vector<string>::iterator s = spath.begin(); s != spath.end(); ++s) {
601
602                 vector<string> entries;
603
604                 get_paths (entries, *s, false, false);
605
606                 for (vector<string>::iterator e = entries.begin(); e != entries.end(); ++e) {
607                         if (Glib::file_test (*e, Glib::FILE_TEST_IS_DIR)) {
608                                 r.push_back (Glib::filename_to_utf8 (Glib::path_get_basename(*e)));
609                         }
610                 }
611         }
612
613         return r;
614 }
615
616 std::string
617 ARDOUR_UI_UTILS::get_icon_path (const char* cname, string icon_set, bool is_image)
618 {
619         std::string data_file_path;
620         string name = cname;
621
622         if (is_image) {
623                 name += X_(".png");
624         }
625
626         Searchpath spath(ARDOUR::ardour_data_search_path());
627
628         if (!icon_set.empty() && icon_set != _("default")) {
629
630                 /* add "icons/icon_set" but .. not allowed to add both of these at once */
631                 spath.add_subdirectory_to_paths ("icons");
632                 spath.add_subdirectory_to_paths (icon_set);
633                 
634                 find_file (spath, name, data_file_path);
635         } else {
636                 spath.add_subdirectory_to_paths ("icons");
637                 find_file (spath, name, data_file_path);
638         }
639         
640         if (is_image && data_file_path.empty()) {
641                 
642                 if (!icon_set.empty() && icon_set != _("default")) {
643                         warning << string_compose (_("icon \"%1\" not found for icon set \"%2\", fallback to default"), cname, icon_set) << endmsg;
644                 }
645                 
646                 Searchpath def (ARDOUR::ardour_data_search_path());
647                 def.add_subdirectory_to_paths ("icons");
648         
649                 if (!find_file (def, name, data_file_path)) {
650                         fatal << string_compose (_("cannot find icon image for %1 using %2"), name, spath.to_string()) << endmsg;
651                         abort(); /*NOTREACHED*/
652                 }
653         }
654
655         return data_file_path;
656 }
657
658 Glib::RefPtr<Gdk::Pixbuf>
659 ARDOUR_UI_UTILS::get_icon (const char* cname, string icon_set)
660 {
661         Glib::RefPtr<Gdk::Pixbuf> img;
662         try {
663                 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname, icon_set));
664         } catch (const Gdk::PixbufError &e) {
665                 cerr << "Caught PixbufError: " << e.what() << endl;
666         } catch (...) {
667                 error << string_compose (_("Caught exception while loading icon named %1"), cname) << endmsg;
668         }
669
670         return img;
671 }
672
673 namespace ARDOUR_UI_UTILS {
674 Glib::RefPtr<Gdk::Pixbuf>
675 get_icon (const char* cname)
676 {
677         Glib::RefPtr<Gdk::Pixbuf> img;
678         try {
679                 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname));
680         } catch (const Gdk::PixbufError &e) {
681                 cerr << "Caught PixbufError: " << e.what() << endl;
682         } catch (...) {
683                 error << string_compose (_("Caught exception while loading icon named %1"), cname) << endmsg;
684         }
685
686         return img;
687 }
688 }
689
690 string
691 ARDOUR_UI_UTILS::longest (vector<string>& strings)
692 {
693         if (strings.empty()) {
694                 return string ("");
695         }
696
697         vector<string>::iterator longest = strings.begin();
698         string::size_type longest_length = (*longest).length();
699
700         vector<string>::iterator i = longest;
701         ++i;
702
703         while (i != strings.end()) {
704
705                 string::size_type len = (*i).length();
706
707                 if (len > longest_length) {
708                         longest = i;
709                         longest_length = len;
710                 }
711
712                 ++i;
713         }
714
715         return *longest;
716 }
717
718 bool
719 ARDOUR_UI_UTILS::key_is_legal_for_numeric_entry (guint keyval)
720 {
721         /* we assume that this does not change over the life of the process 
722          */
723
724         static int comma_decimal = -1;
725
726         switch (keyval) {
727         case GDK_period:
728         case GDK_comma:
729                 if (comma_decimal < 0) {
730                         std::lconv* lc = std::localeconv();
731                         if (strchr (lc->decimal_point, ',') != 0) {
732                                 comma_decimal = 1;
733                         } else {
734                                 comma_decimal = 0;
735                         }
736                 }
737                 break;
738         default:
739                 break;
740         }
741
742         switch (keyval) {
743         case GDK_decimalpoint:
744         case GDK_KP_Separator:
745                 return true;
746
747         case GDK_period:
748                 if (comma_decimal) {
749                         return false;
750                 } else {
751                         return true;
752                 }
753                 break;
754         case GDK_comma:
755                 if (comma_decimal) {
756                         return true;
757                 } else {
758                         return false;
759                 }
760                 break;
761         case GDK_minus:
762         case GDK_plus:
763         case GDK_0:
764         case GDK_1:
765         case GDK_2:
766         case GDK_3:
767         case GDK_4:
768         case GDK_5:
769         case GDK_6:
770         case GDK_7:
771         case GDK_8:
772         case GDK_9:
773         case GDK_KP_Add:
774         case GDK_KP_Subtract:
775         case GDK_KP_Decimal:
776         case GDK_KP_0:
777         case GDK_KP_1:
778         case GDK_KP_2:
779         case GDK_KP_3:
780         case GDK_KP_4:
781         case GDK_KP_5:
782         case GDK_KP_6:
783         case GDK_KP_7:
784         case GDK_KP_8:
785         case GDK_KP_9:
786         case GDK_Return:
787         case GDK_BackSpace:
788         case GDK_Delete:
789         case GDK_KP_Enter:
790         case GDK_Home:
791         case GDK_End:
792         case GDK_Left:
793         case GDK_Right:
794                 return true;
795
796         default:
797                 break;
798         }
799
800         return false;
801 }
802
803 void
804 ARDOUR_UI_UTILS::set_pango_fontsize ()
805 {
806         long val = ARDOUR_UI::config()->get_font_scale();
807
808         /* FT2 rendering - used by GnomeCanvas, sigh */
809
810 #ifndef PLATFORM_WINDOWS
811         pango_ft2_font_map_set_resolution ((PangoFT2FontMap*) pango_ft2_font_map_new(), val/1024, val/1024);
812 #endif
813
814         /* Cairo rendering, in case there is any */
815
816         pango_cairo_font_map_set_resolution ((PangoCairoFontMap*) pango_cairo_font_map_get_default(), val/1024);
817 }
818
819 void
820 ARDOUR_UI_UTILS::reset_dpi ()
821 {
822         long val = ARDOUR_UI::config()->get_font_scale();
823         set_pango_fontsize ();
824         /* Xft rendering */
825
826         gtk_settings_set_long_property (gtk_settings_get_default(),
827                                         "gtk-xft-dpi", val, "ardour");
828         DPIReset();//Emit Signal
829 }
830
831 void
832 ARDOUR_UI_UTILS::resize_window_to_proportion_of_monitor (Gtk::Window* window, int max_width, int max_height)
833 {
834         Glib::RefPtr<Gdk::Screen> screen = window->get_screen ();
835         Gdk::Rectangle monitor_rect;
836         screen->get_monitor_geometry (0, monitor_rect);
837
838         int const w = std::min (int (monitor_rect.get_width() * 0.8), max_width);
839         int const h = std::min (int (monitor_rect.get_height() * 0.8), max_height);
840
841         window->resize (w, h);
842 }
843
844
845 /** Replace _ with __ in a string; for use with menu item text to make underscores displayed correctly */
846 string
847 ARDOUR_UI_UTILS::escape_underscores (string const & s)
848 {
849         string o;
850         string::size_type const N = s.length ();
851
852         for (string::size_type i = 0; i < N; ++i) {
853                 if (s[i] == '_') {
854                         o += "__";
855                 } else {
856                         o += s[i];
857                 }
858         }
859
860         return o;
861 }
862
863 /** Replace < and > with &lt; and &gt; respectively to make < > display correctly in markup strings */
864 string
865 ARDOUR_UI_UTILS::escape_angled_brackets (string const & s)
866 {
867         string o = s;
868         boost::replace_all (o, "<", "&lt;");
869         boost::replace_all (o, ">", "&gt;");
870         return o;
871 }
872
873 Gdk::Color
874 ARDOUR_UI_UTILS::unique_random_color (list<Gdk::Color>& used_colors)
875 {
876         Gdk::Color newcolor;
877
878         while (1) {
879
880                 double h, s, v;
881
882                 h = fmod (random(), 360.0);
883                 s = (random() % 65535) / 65535.0;
884                 v = (random() % 65535) / 65535.0;
885
886                 s = min (0.5, s); /* not too saturated */
887                 v = max (0.9, v);  /* not too bright */
888                 newcolor.set_hsv (h, s, v);
889
890                 if (used_colors.size() == 0) {
891                         used_colors.push_back (newcolor);
892                         return newcolor;
893                 }
894
895                 for (list<Gdk::Color>::iterator i = used_colors.begin(); i != used_colors.end(); ++i) {
896                   Gdk::Color c = *i;
897                         float rdelta, bdelta, gdelta;
898
899                         rdelta = newcolor.get_red() - c.get_red();
900                         bdelta = newcolor.get_blue() - c.get_blue();
901                         gdelta = newcolor.get_green() - c.get_green();
902
903                         if (sqrt (rdelta*rdelta + bdelta*bdelta + gdelta*gdelta) > 25.0) {
904                                 /* different enough */
905                                 used_colors.push_back (newcolor);
906                                 return newcolor;
907                         }
908                 }
909
910                 /* XXX need throttle here to make sure we don't spin for ever */
911         }
912 }
913
914 string 
915 ARDOUR_UI_UTILS::rate_as_string (float r)
916 {
917         char buf[32];
918         if (fmod (r, 1000.0f)) {
919                 snprintf (buf, sizeof (buf), "%.1f kHz", r/1000.0);
920         } else {
921                 snprintf (buf, sizeof (buf), "%.0f kHz", r/1000.0);
922         }
923         return buf;
924 }