bbce202ad1baa21827f45cc6f7111a1984937325
[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 <fstream>
32 #include <list>
33 #include <sys/stat.h>
34 #include <libart_lgpl/art_misc.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
48 #include "ardour/filesystem_paths.h"
49
50 #include "ardour_ui.h"
51 #include "debug.h"
52 #include "public_editor.h"
53 #include "keyboard.h"
54 #include "utils.h"
55 #include "i18n.h"
56 #include "rgb_macros.h"
57 #include "canvas_impl.h"
58 #include "gui_thread.h"
59
60 using namespace std;
61 using namespace Gtk;
62 using namespace Glib;
63 using namespace PBD;
64 using Gtkmm2ext::Keyboard;
65
66 sigc::signal<void>  DPIReset;
67
68
69 /** Add an element to a menu, settings its sensitivity.
70  * @param m Menu to add to.
71  * @param e Element to add.
72  * @param s true to make sensitive, false to make insensitive
73  */
74 void
75 add_item_with_sensitivity (Menu_Helpers::MenuList& m, Menu_Helpers::MenuElem e, bool s)
76 {
77         m.push_back (e);
78         if (!s) {
79                 m.back().set_sensitive (false);
80         }
81 }
82
83
84 gint
85 just_hide_it (GdkEventAny */*ev*/, Gtk::Window *win)
86 {
87         win->hide ();
88         return 0;
89 }
90
91 /* xpm2rgb copied from nixieclock, which bore the legend:
92
93     nixieclock - a nixie desktop timepiece
94     Copyright (C) 2000 Greg Ercolano, erco@3dsite.com
95
96     and was released under the GPL.
97 */
98
99 unsigned char*
100 xpm2rgb (const char** xpm, uint32_t& w, uint32_t& h)
101 {
102         static long vals[256], val;
103         uint32_t t, x, y, colors, cpp;
104         unsigned char c;
105         unsigned char *savergb, *rgb;
106
107         // PARSE HEADER
108
109         if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
110                 error << string_compose (_("bad XPM header %1"), xpm[0])
111                       << endmsg;
112                 return 0;
113         }
114
115         savergb = rgb = (unsigned char*) malloc (h * w * 3);
116
117         // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
118         for (t = 0; t < colors; ++t) {
119                 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
120                 vals[c] = val;
121         }
122
123         // COLORMAP -> RGB CONVERSION
124         //    Get low 3 bytes from vals[]
125         //
126
127         const char *p;
128         for (y = h-1; y > 0; --y) {
129
130                 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 3) {
131                         val = vals[(int)*p++];
132                         *(rgb+2) = val & 0xff; val >>= 8;  // 2:B
133                         *(rgb+1) = val & 0xff; val >>= 8;  // 1:G
134                         *(rgb+0) = val & 0xff;             // 0:R
135                 }
136         }
137
138         return (savergb);
139 }
140
141 unsigned char*
142 xpm2rgba (const char** xpm, uint32_t& w, uint32_t& h)
143 {
144         static long vals[256], val;
145         uint32_t t, x, y, colors, cpp;
146         unsigned char c;
147         unsigned char *savergb, *rgb;
148         char transparent;
149
150         // PARSE HEADER
151
152         if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
153                 error << string_compose (_("bad XPM header %1"), xpm[0])
154                       << endmsg;
155                 return 0;
156         }
157
158         savergb = rgb = (unsigned char*) malloc (h * w * 4);
159
160         // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
161
162         if (strstr (xpm[1], "None")) {
163                 sscanf (xpm[1], "%c", &transparent);
164                 t = 1;
165         } else {
166                 transparent = 0;
167                 t = 0;
168         }
169
170         for (; t < colors; ++t) {
171                 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
172                 vals[c] = val;
173         }
174
175         // COLORMAP -> RGB CONVERSION
176         //    Get low 3 bytes from vals[]
177         //
178
179         const char *p;
180         for (y = h-1; y > 0; --y) {
181
182                 char alpha;
183
184                 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 4) {
185
186                         if (transparent && (*p++ == transparent)) {
187                                 alpha = 0;
188                                 val = 0;
189                         } else {
190                                 alpha = 255;
191                                 val = vals[(int)*p];
192                         }
193
194                         *(rgb+3) = alpha;                  // 3: alpha
195                         *(rgb+2) = val & 0xff; val >>= 8;  // 2:B
196                         *(rgb+1) = val & 0xff; val >>= 8;  // 1:G
197                         *(rgb+0) = val & 0xff;             // 0:R
198                 }
199         }
200
201         return (savergb);
202 }
203
204 ArdourCanvas::Points*
205 get_canvas_points (string /*who*/, uint32_t npoints)
206 {
207         // cerr << who << ": wants " << npoints << " canvas points" << endl;
208 #ifdef TRAP_EXCESSIVE_POINT_REQUESTS
209         if (npoints > (uint32_t) gdk_screen_width() + 4) {
210                 abort ();
211         }
212 #endif
213         return new ArdourCanvas::Points (npoints);
214 }
215
216 Pango::FontDescription
217 get_font_for_style (string widgetname)
218 {
219         Gtk::Window window (WINDOW_TOPLEVEL);
220         Gtk::Label foobar;
221         Glib::RefPtr<Gtk::Style> style;
222
223         window.add (foobar);
224         foobar.set_name (widgetname);
225         foobar.ensure_style();
226
227         style = foobar.get_style ();
228
229         Glib::RefPtr<const Pango::Layout> layout = foobar.get_layout();
230
231         PangoFontDescription *pfd = const_cast<PangoFontDescription *> (pango_layout_get_font_description(const_cast<PangoLayout *>(layout->gobj())));
232
233         if (!pfd) {
234
235                 /* layout inherited its font description from a PangoContext */
236
237                 PangoContext* ctxt = (PangoContext*) pango_layout_get_context (const_cast<PangoLayout*>(layout->gobj()));
238                 pfd =  pango_context_get_font_description (ctxt);
239                 return Pango::FontDescription (pfd); /* make a copy */
240         }
241
242         return Pango::FontDescription (pfd); /* make a copy */
243 }
244
245 uint32_t
246 rgba_from_style (string style, uint32_t r, uint32_t g, uint32_t b, uint32_t a, string attr, int state, bool rgba)
247 {
248         /* In GTK+2, styles aren't set up correctly if the widget is not
249            attached to a toplevel window that has a screen pointer.
250         */
251
252         static Gtk::Window* window = 0;
253
254         if (window == 0) {
255                 window = new Window (WINDOW_TOPLEVEL);
256         }
257
258         Gtk::Label foo;
259
260         window->add (foo);
261
262         foo.set_name (style);
263         foo.ensure_style ();
264
265         GtkRcStyle* rc = foo.get_style()->gobj()->rc_style;
266
267         if (rc) {
268                 if (attr == "fg") {
269                         r = rc->fg[state].red / 257;
270                         g = rc->fg[state].green / 257;
271                         b = rc->fg[state].blue / 257;
272
273                         /* what a hack ... "a" is for "active" */
274                         if (state == Gtk::STATE_NORMAL && rgba) {
275                                 a = rc->fg[GTK_STATE_ACTIVE].red / 257;
276                         }
277                 } else if (attr == "bg") {
278                         r = g = b = 0;
279                         r = rc->bg[state].red / 257;
280                         g = rc->bg[state].green / 257;
281                         b = rc->bg[state].blue / 257;
282                 } else if (attr == "base") {
283                         r = rc->base[state].red / 257;
284                         g = rc->base[state].green / 257;
285                         b = rc->base[state].blue / 257;
286                 } else if (attr == "text") {
287                         r = rc->text[state].red / 257;
288                         g = rc->text[state].green / 257;
289                         b = rc->text[state].blue / 257;
290                 }
291         } else {
292                 warning << string_compose (_("missing RGBA style for \"%1\""), style) << endl;
293         }
294
295         window->remove ();
296
297         if (state == Gtk::STATE_NORMAL && rgba) {
298                 return (uint32_t) RGBA_TO_UINT(r,g,b,a);
299         } else {
300                 return (uint32_t) RGB_TO_UINT(r,g,b);
301         }
302 }
303
304 bool
305 rgba_p_from_style (string style, float *r, float *g, float *b, string attr, int state)
306 {
307         static Gtk::Window* window = 0;
308         assert (r && g && b);
309
310         if (window == 0) {
311                 window = new Window (WINDOW_TOPLEVEL);
312         }
313
314         Gtk::EventBox foo;
315
316         window->add (foo);
317
318         foo.set_name (style);
319         foo.ensure_style ();
320
321         GtkRcStyle* rc = foo.get_style()->gobj()->rc_style;
322
323         if (!rc) {
324                 warning << string_compose (_("missing RGBA style for \"%1\""), style) << endl;
325                 return false;
326         }
327         if (attr == "fg") {
328                 *r = rc->fg[state].red / 65535.0;
329                 *g = rc->fg[state].green / 65535.0;
330                 *b = rc->fg[state].blue / 65535.0;
331         } else if (attr == "bg") {
332                 *r = rc->bg[state].red / 65535.0;
333                 *g = rc->bg[state].green / 65535.0;
334                 *b = rc->bg[state].blue / 65535.0;
335         } else if (attr == "base") {
336                 *r = rc->base[state].red / 65535.0;
337                 *g = rc->base[state].green / 65535.0;
338                 *b = rc->base[state].blue / 65535.0;
339         } else if (attr == "text") {
340                 *r = rc->text[state].red / 65535.0;
341                 *g = rc->text[state].green / 65535.0;
342                 *b = rc->text[state].blue / 65535.0;
343         } else {
344                 return false;
345         }
346
347         window->remove ();
348         return true;
349 }
350
351 bool
352 canvas_item_visible (ArdourCanvas::Item* item)
353 {
354         return (item->gobj()->object.flags & GNOME_CANVAS_ITEM_VISIBLE) ? true : false;
355 }
356
357 void
358 set_color (Gdk::Color& c, int rgb)
359 {
360         c.set_rgb((rgb >> 16)*256, ((rgb & 0xff00) >> 8)*256, (rgb & 0xff)*256);
361 }
362
363 bool
364 relay_key_press (GdkEventKey* ev, Gtk::Window* win)
365 {
366         PublicEditor& ed (PublicEditor::instance());
367
368         if (!key_press_focus_accelerator_handler (*win, ev)) {
369                 if (&ed == 0) {
370                         /* early key press in pre-main-window-dialogs, no editor yet */
371                         return false;
372                 }
373                 return ed.on_key_press_event(ev);
374         } else {
375                 return true;
376         }
377 }
378
379 bool
380 forward_key_press (GdkEventKey* ev)
381 {
382         return PublicEditor::instance().on_key_press_event(ev);
383 }
384
385 bool
386 emulate_key_event (Gtk::Widget* w, unsigned int keyval)
387 {
388         GdkDisplay  *display = gtk_widget_get_display (GTK_WIDGET(w->gobj()));
389         GdkKeymap   *keymap  = gdk_keymap_get_for_display (display);
390         GdkKeymapKey *keymapkey = NULL;
391         gint n_keys;
392
393         if (!gdk_keymap_get_entries_for_keyval(keymap, keyval, &keymapkey, &n_keys)) return false;
394         if (n_keys !=1) { g_free(keymapkey); return false;}
395
396         GdkEventKey ev;
397         ev.type = GDK_KEY_PRESS;
398         ev.window = gtk_widget_get_window(GTK_WIDGET(w->gobj()));
399         ev.send_event = FALSE;
400         ev.time = 0;
401         ev.state = 0;
402         ev.keyval = keyval;
403         ev.length = 0;
404         ev.string = (const gchar*) "";
405         ev.hardware_keycode = keymapkey[0].keycode;
406         ev.group = keymapkey[0].group;
407         g_free(keymapkey);
408
409         forward_key_press(&ev);
410         ev.type = GDK_KEY_RELEASE;
411         return forward_key_press(&ev);
412 }
413
414 bool
415 key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev)
416 {
417         GtkWindow* win = window.gobj();
418         GtkWidget* focus = gtk_window_get_focus (win);
419         bool special_handling_of_unmodified_accelerators = false;
420         bool allow_activating = true;
421         /* consider all relevant modifiers but not LOCK or SHIFT */
422         const guint mask = (Keyboard::RelevantModifierKeyMask & ~(Gdk::SHIFT_MASK|Gdk::LOCK_MASK));
423
424         if (focus) {
425                 if (GTK_IS_ENTRY(focus) || Keyboard::some_magic_widget_has_focus()) {
426                         special_handling_of_unmodified_accelerators = true;
427                 }
428         }
429
430 #ifdef GTKOSX
431         /* at one time this appeared to be necessary. As of July 2012, it does not
432            appear to be. if it ever is necessar, figure out if it should apply
433            to all platforms.
434         */
435 #if 0 
436         if (Keyboard::some_magic_widget_has_focus ()) {
437                 allow_activating = false;
438         }
439 #endif
440 #endif
441
442
443         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",
444                                                           win,
445                                                           ev->keyval,
446                                                           ev->state,
447                                                           special_handling_of_unmodified_accelerators,
448                                                           Keyboard::some_magic_widget_has_focus(),
449                                                           allow_activating,
450                                                           focus));
451
452         /* This exists to allow us to override the way GTK handles
453            key events. The normal sequence is:
454
455            a) event is delivered to a GtkWindow
456            b) accelerators/mnemonics are activated
457            c) if (b) didn't handle the event, propagate to
458                the focus widget and/or focus chain
459
460            The problem with this is that if the accelerators include
461            keys without modifiers, such as the space bar or the
462            letter "e", then pressing the key while typing into
463            a text entry widget results in the accelerator being
464            activated, instead of the desired letter appearing
465            in the text entry.
466
467            There is no good way of fixing this, but this
468            represents a compromise. The idea is that
469            key events involving modifiers (not Shift)
470            get routed into the activation pathway first, then
471            get propagated to the focus widget if necessary.
472
473            If the key event doesn't involve modifiers,
474            we deliver to the focus widget first, thus allowing
475            it to get "normal text" without interference
476            from acceleration.
477
478            Of course, this can also be problematic: if there
479            is a widget with focus, then it will swallow
480            all "normal text" accelerators.
481         */
482
483         if (!special_handling_of_unmodified_accelerators) {
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                         GdkModifierType mod = GdkModifierType (ev->state);
513
514                         mod = GdkModifierType (mod & gtk_accelerator_get_default_mod_mask());
515 #ifdef GTKOSX
516                         /* GTK on OS X is currently (February 2012) setting both
517                            the Meta and Mod2 bits in the event modifier state if 
518                            the Command key is down.
519
520                            gtk_accel_groups_activate() does not invoke any of the logic
521                            that gtk_window_activate_key() will that sorts out that stupid
522                            state of affairs, and as a result it fails to find a match
523                            for the key event and the current set of accelerators.
524
525                            to fix this, if the meta bit is set, remove the mod2 bit
526                            from the modifier. this assumes that our bindings use Primary
527                            which will have set the meta bit in the accelerator entry.
528                         */
529                         if (mod & GDK_META_MASK) {
530                                 mod = GdkModifierType (mod & ~GDK_MOD2_MASK);
531                         }
532 #endif
533
534                         if (allow_activating && gtk_accel_groups_activate(G_OBJECT(win), fakekey, mod)) {
535                                 DEBUG_TRACE (DEBUG::Accelerators, "\taccel group activated by fakekey\n");
536                                 return true;
537                         }
538                 }
539         }
540
541         if (!special_handling_of_unmodified_accelerators || (ev->state & mask)) {
542
543                 /* no special handling or there are modifiers in effect: accelerate first */
544
545                 DEBUG_TRACE (DEBUG::Accelerators, "\tactivate, then propagate\n");
546                 DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tevent send-event:%1 time:%2 length:%3 string:%4 hardware_keycode:%5 group:%6\n",
547                                         ev->send_event, ev->time, ev->length, ev->string, ev->hardware_keycode, ev->group));
548
549                 if (allow_activating) {
550                         DEBUG_TRACE (DEBUG::Accelerators, "\tsending to window\n");
551                         if (gtk_window_activate_key (win, ev)) {
552                                 DEBUG_TRACE (DEBUG::Accelerators, "\t\thandled\n");
553                                 return true;
554                         }
555                 } else {
556                         DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
557                 }
558
559                 DEBUG_TRACE (DEBUG::Accelerators, "\tnot accelerated, now propagate\n");
560
561                 return gtk_window_propagate_key_event (win, ev);
562         }
563
564         /* no modifiers, propagate first */
565
566         DEBUG_TRACE (DEBUG::Accelerators, "\tpropagate, then activate\n");
567
568         if (!gtk_window_propagate_key_event (win, ev)) {
569                 DEBUG_TRACE (DEBUG::Accelerators, "\tpropagation didn't handle, so activate\n");
570                 if (allow_activating) {
571                         return gtk_window_activate_key (win, ev);
572                 } else {
573                         DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
574                 }
575
576         } else {
577                 DEBUG_TRACE (DEBUG::Accelerators, "\thandled by propagate\n");
578                 return true;
579         }
580
581         DEBUG_TRACE (DEBUG::Accelerators, "\tnot handled\n");
582         return true;
583 }
584
585 Glib::RefPtr<Gdk::Pixbuf>
586 get_xpm (std::string name)
587 {
588         if (!xpm_map[name]) {
589
590                 Searchpath spath(ARDOUR::ardour_data_search_path());
591
592                 spath.add_subdirectory_to_paths("pixmaps");
593
594                 std::string data_file_path;
595
596                 if(!find_file_in_search_path (spath, name, data_file_path)) {
597                         fatal << string_compose (_("cannot find XPM file for %1"), name) << endmsg;
598                 }
599
600                 try {
601                         xpm_map[name] =  Gdk::Pixbuf::create_from_file (data_file_path);
602                 } catch(const Glib::Error& e)   {
603                         warning << "Caught Glib::Error: " << e.what() << endmsg;
604                 }
605         }
606
607         return xpm_map[name];
608 }
609
610 std::string
611 get_icon_path (const char* cname)
612 {
613         string name = cname;
614         name += X_(".png");
615
616         Searchpath spath(ARDOUR::ardour_data_search_path());
617
618         spath.add_subdirectory_to_paths("icons");
619
620         std::string data_file_path;
621
622         if (!find_file_in_search_path (spath, name, data_file_path)) {
623                 fatal << string_compose (_("cannot find icon image for %1 using %2"), name, spath.to_string()) << endmsg;
624         }
625
626         return data_file_path;
627 }
628
629 Glib::RefPtr<Gdk::Pixbuf>
630 get_icon (const char* cname)
631 {
632         Glib::RefPtr<Gdk::Pixbuf> img;
633         try {
634                 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname));
635         } catch (const Gdk::PixbufError &e) {
636                 cerr << "Caught PixbufError: " << e.what() << endl;
637         } catch (...) {
638                 error << string_compose (_("Caught exception while loading icon named %1"), cname) << endmsg;
639         }
640
641         return img;
642 }
643
644 string
645 longest (vector<string>& strings)
646 {
647         if (strings.empty()) {
648                 return string ("");
649         }
650
651         vector<string>::iterator longest = strings.begin();
652         string::size_type longest_length = (*longest).length();
653
654         vector<string>::iterator i = longest;
655         ++i;
656
657         while (i != strings.end()) {
658
659                 string::size_type len = (*i).length();
660
661                 if (len > longest_length) {
662                         longest = i;
663                         longest_length = len;
664                 }
665
666                 ++i;
667         }
668
669         return *longest;
670 }
671
672 bool
673 key_is_legal_for_numeric_entry (guint keyval)
674 {
675         /* we assume that this does not change over the life of the process 
676          */
677
678         static int comma_decimal = -1;
679
680         switch (keyval) {
681         case GDK_period:
682         case GDK_comma:
683                 if (comma_decimal < 0) {
684                         std::lconv* lc = std::localeconv();
685                         if (strchr (lc->decimal_point, ',') != 0) {
686                                 comma_decimal = 1;
687                         } else {
688                                 comma_decimal = 0;
689                         }
690                 }
691                 break;
692         default:
693                 break;
694         }
695
696         switch (keyval) {
697         case GDK_decimalpoint:
698         case GDK_KP_Separator:
699                 return true;
700
701         case GDK_period:
702                 if (comma_decimal) {
703                         return false;
704                 } else {
705                         return true;
706                 }
707                 break;
708         case GDK_comma:
709                 if (comma_decimal) {
710                         return true;
711                 } else {
712                         return false;
713                 }
714                 break;
715         case GDK_minus:
716         case GDK_plus:
717         case GDK_0:
718         case GDK_1:
719         case GDK_2:
720         case GDK_3:
721         case GDK_4:
722         case GDK_5:
723         case GDK_6:
724         case GDK_7:
725         case GDK_8:
726         case GDK_9:
727         case GDK_KP_Add:
728         case GDK_KP_Subtract:
729         case GDK_KP_Decimal:
730         case GDK_KP_0:
731         case GDK_KP_1:
732         case GDK_KP_2:
733         case GDK_KP_3:
734         case GDK_KP_4:
735         case GDK_KP_5:
736         case GDK_KP_6:
737         case GDK_KP_7:
738         case GDK_KP_8:
739         case GDK_KP_9:
740         case GDK_Return:
741         case GDK_BackSpace:
742         case GDK_Delete:
743         case GDK_KP_Enter:
744         case GDK_Home:
745         case GDK_End:
746         case GDK_Left:
747         case GDK_Right:
748                 return true;
749
750         default:
751                 break;
752         }
753
754         return false;
755 }
756 void
757 set_pango_fontsize ()
758 {
759         long val = ARDOUR::Config->get_font_scale();
760
761         /* FT2 rendering - used by GnomeCanvas, sigh */
762
763 #ifndef WIN32
764         pango_ft2_font_map_set_resolution ((PangoFT2FontMap*) pango_ft2_font_map_new(), val/1024, val/1024);
765 #endif
766
767         /* Cairo rendering, in case there is any */
768
769         pango_cairo_font_map_set_resolution ((PangoCairoFontMap*) pango_cairo_font_map_get_default(), val/1024);
770 }
771
772 void
773 reset_dpi ()
774 {
775         long val = ARDOUR::Config->get_font_scale();
776         set_pango_fontsize ();
777         /* Xft rendering */
778
779         gtk_settings_set_long_property (gtk_settings_get_default(),
780                                         "gtk-xft-dpi", val, "ardour");
781         DPIReset();//Emit Signal
782 }
783
784 void
785 resize_window_to_proportion_of_monitor (Gtk::Window* window, int max_width, int max_height)
786 {
787         Glib::RefPtr<Gdk::Screen> screen = window->get_screen ();
788         Gdk::Rectangle monitor_rect;
789         screen->get_monitor_geometry (0, monitor_rect);
790
791         int const w = std::min (int (monitor_rect.get_width() * 0.8), max_width);
792         int const h = std::min (int (monitor_rect.get_height() * 0.8), max_height);
793
794         window->resize (w, h);
795 }
796
797
798 /** Replace _ with __ in a string; for use with menu item text to make underscores displayed correctly */
799 string
800 escape_underscores (string const & s)
801 {
802         string o;
803         string::size_type const N = s.length ();
804
805         for (string::size_type i = 0; i < N; ++i) {
806                 if (s[i] == '_') {
807                         o += "__";
808                 } else {
809                         o += s[i];
810                 }
811         }
812
813         return o;
814 }
815
816 /** Replace < and > with &lt; and &gt; respectively to make < > display correctly in markup strings */
817 string
818 escape_angled_brackets (string const & s)
819 {
820         string o = s;
821         boost::replace_all (o, "<", "&lt;");
822         boost::replace_all (o, ">", "&gt;");
823         return o;
824 }
825
826 Gdk::Color
827 unique_random_color (list<Gdk::Color>& used_colors)
828 {
829         Gdk::Color newcolor;
830
831         while (1) {
832
833                 /* avoid neon/glowing tones by limiting them to the
834                    "inner section" (paler) of a color wheel/circle.
835                 */
836
837                 const int32_t max_saturation = 48000; // 65535 would open up the whole color wheel
838
839                 newcolor.set_red (g_random_int() % max_saturation);
840                 newcolor.set_blue (g_random_int() % max_saturation);
841                 newcolor.set_green (g_random_int() % max_saturation);
842
843                 if (used_colors.size() == 0) {
844                         used_colors.push_back (newcolor);
845                         return newcolor;
846                 }
847
848                 for (list<Gdk::Color>::iterator i = used_colors.begin(); i != used_colors.end(); ++i) {
849                   Gdk::Color c = *i;
850                         float rdelta, bdelta, gdelta;
851
852                         rdelta = newcolor.get_red() - c.get_red();
853                         bdelta = newcolor.get_blue() - c.get_blue();
854                         gdelta = newcolor.get_green() - c.get_green();
855
856                         if (sqrt (rdelta*rdelta + bdelta*bdelta + gdelta*gdelta) > 25.0) {
857                                 used_colors.push_back (newcolor);
858                                 return newcolor;
859                         }
860                 }
861
862                 /* XXX need throttle here to make sure we don't spin for ever */
863         }
864 }