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