72a922046f3a64c2e91d78e4782e92d9f9c53efa
[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         if (!key_press_focus_accelerator_handler (*win, ev)) {
367                 return PublicEditor::instance().on_key_press_event(ev);
368         } else {
369                 return true;
370         }
371 }
372
373 bool
374 forward_key_press (GdkEventKey* ev)
375 {
376         return PublicEditor::instance().on_key_press_event(ev);
377 }
378
379 bool
380 emulate_key_event (Gtk::Widget* w, unsigned int keyval)
381 {
382         GdkDisplay  *display = gtk_widget_get_display (GTK_WIDGET(w->gobj()));
383         GdkKeymap   *keymap  = gdk_keymap_get_for_display (display);
384         GdkKeymapKey *keymapkey = NULL;
385         gint n_keys;
386
387         if (!gdk_keymap_get_entries_for_keyval(keymap, keyval, &keymapkey, &n_keys)) return false;
388         if (n_keys !=1) { g_free(keymapkey); return false;}
389
390         GdkEventKey ev;
391         ev.type = GDK_KEY_PRESS;
392         ev.window = gtk_widget_get_window(GTK_WIDGET(w->gobj()));
393         ev.send_event = FALSE;
394         ev.time = 0;
395         ev.state = 0;
396         ev.keyval = keyval;
397         ev.length = 0;
398         ev.string = (gchar*) "";
399         ev.hardware_keycode = keymapkey[0].keycode;
400         ev.group = keymapkey[0].group;
401         g_free(keymapkey);
402
403         forward_key_press(&ev);
404         ev.type = GDK_KEY_RELEASE;
405         return forward_key_press(&ev);
406 }
407
408 bool
409 key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev)
410 {
411         GtkWindow* win = window.gobj();
412         GtkWidget* focus = gtk_window_get_focus (win);
413         bool special_handling_of_unmodified_accelerators = false;
414         bool allow_activating = true;
415         /* consider all relevant modifiers but not LOCK or SHIFT */
416         const guint mask = (Keyboard::RelevantModifierKeyMask & ~(Gdk::SHIFT_MASK|Gdk::LOCK_MASK));
417
418         if (focus) {
419                 if (GTK_IS_ENTRY(focus) || Keyboard::some_magic_widget_has_focus()) {
420                         special_handling_of_unmodified_accelerators = true;
421                 }
422         }
423
424 #ifdef GTKOSX
425         /* at one time this appeared to be necessary. As of July 2012, it does not
426            appear to be. if it ever is necessar, figure out if it should apply
427            to all platforms.
428         */
429 #if 0 
430         if (Keyboard::some_magic_widget_has_focus ()) {
431                 allow_activating = false;
432         }
433 #endif
434 #endif
435
436
437         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",
438                                                           win,
439                                                           ev->keyval,
440                                                           ev->state,
441                                                           special_handling_of_unmodified_accelerators,
442                                                           Keyboard::some_magic_widget_has_focus(),
443                                                           allow_activating,
444                                                           focus));
445
446         /* This exists to allow us to override the way GTK handles
447            key events. The normal sequence is:
448
449            a) event is delivered to a GtkWindow
450            b) accelerators/mnemonics are activated
451            c) if (b) didn't handle the event, propagate to
452                the focus widget and/or focus chain
453
454            The problem with this is that if the accelerators include
455            keys without modifiers, such as the space bar or the
456            letter "e", then pressing the key while typing into
457            a text entry widget results in the accelerator being
458            activated, instead of the desired letter appearing
459            in the text entry.
460
461            There is no good way of fixing this, but this
462            represents a compromise. The idea is that
463            key events involving modifiers (not Shift)
464            get routed into the activation pathway first, then
465            get propagated to the focus widget if necessary.
466
467            If the key event doesn't involve modifiers,
468            we deliver to the focus widget first, thus allowing
469            it to get "normal text" without interference
470            from acceleration.
471
472            Of course, this can also be problematic: if there
473            is a widget with focus, then it will swallow
474            all "normal text" accelerators.
475         */
476
477         if (!special_handling_of_unmodified_accelerators) {
478
479                 /* XXX note that for a brief moment, the conditional above
480                  * included "|| (ev->state & mask)" so as to enforce the
481                  * implication of special_handling_of_UNMODIFIED_accelerators.
482                  * however, this forces any key that GTK doesn't allow and that
483                  * we have an alternative (see next comment) for to be
484                  * automatically sent through the accel groups activation
485                  * pathway, which prevents individual widgets & canvas items
486                  * from ever seeing it if is used by a key binding.
487                  * 
488                  * specifically, this hid Ctrl-down-arrow from MIDI region
489                  * views because it is also bound to an action.
490                  *
491                  * until we have a robust, clean binding system, this
492                  * quirk will have to remain in place.
493                  */
494
495                 /* pretend that certain key events that GTK does not allow
496                    to be used as accelerators are actually something that
497                    it does allow. but only where there are no modifiers.
498                 */
499
500                 uint32_t fakekey = ev->keyval;
501
502                 if (Gtkmm2ext::possibly_translate_keyval_to_make_legal_accelerator (fakekey)) {
503                         DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tactivate (was %1 now %2) without special hanlding of unmodified accels\n",
504                                                                           ev->keyval, fakekey));
505
506                         GdkModifierType mod = GdkModifierType (ev->state);
507
508                         mod = GdkModifierType (mod & gtk_accelerator_get_default_mod_mask());
509 #ifdef GTKOSX
510                         /* GTK on OS X is currently (February 2012) setting both
511                            the Meta and Mod2 bits in the event modifier state if 
512                            the Command key is down.
513
514                            gtk_accel_groups_activate() does not invoke any of the logic
515                            that gtk_window_activate_key() will that sorts out that stupid
516                            state of affairs, and as a result it fails to find a match
517                            for the key event and the current set of accelerators.
518
519                            to fix this, if the meta bit is set, remove the mod2 bit
520                            from the modifier. this assumes that our bindings use Primary
521                            which will have set the meta bit in the accelerator entry.
522                         */
523                         if (mod & GDK_META_MASK) {
524                                 mod = GdkModifierType (mod & ~GDK_MOD2_MASK);
525                         }
526 #endif
527
528                         if (allow_activating && gtk_accel_groups_activate(G_OBJECT(win), fakekey, mod)) {
529                                 DEBUG_TRACE (DEBUG::Accelerators, "\taccel group activated by fakekey\n");
530                                 return true;
531                         }
532                 }
533         }
534
535         if (!special_handling_of_unmodified_accelerators || (ev->state & mask)) {
536
537                 /* no special handling or there are modifiers in effect: accelerate first */
538
539                 DEBUG_TRACE (DEBUG::Accelerators, "\tactivate, then propagate\n");
540                 DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tevent send-event:%1 time:%2 length:%3 string:%4 hardware_keycode:%5 group:%6\n",
541                                         ev->send_event, ev->time, ev->length, ev->string, ev->hardware_keycode, ev->group));
542
543                 if (allow_activating) {
544                         DEBUG_TRACE (DEBUG::Accelerators, "\tsending to window\n");
545                         if (gtk_window_activate_key (win, ev)) {
546                                 DEBUG_TRACE (DEBUG::Accelerators, "\t\thandled\n");
547                                 return true;
548                         }
549                 } else {
550                         DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
551                 }
552
553                 DEBUG_TRACE (DEBUG::Accelerators, "\tnot accelerated, now propagate\n");
554
555                 return gtk_window_propagate_key_event (win, ev);
556         }
557
558         /* no modifiers, propagate first */
559
560         DEBUG_TRACE (DEBUG::Accelerators, "\tpropagate, then activate\n");
561
562         if (!gtk_window_propagate_key_event (win, ev)) {
563                 DEBUG_TRACE (DEBUG::Accelerators, "\tpropagation didn't handle, so activate\n");
564                 if (allow_activating) {
565                         return gtk_window_activate_key (win, ev);
566                 } else {
567                         DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
568                 }
569
570         } else {
571                 DEBUG_TRACE (DEBUG::Accelerators, "\thandled by propagate\n");
572                 return true;
573         }
574
575         DEBUG_TRACE (DEBUG::Accelerators, "\tnot handled\n");
576         return true;
577 }
578
579 Glib::RefPtr<Gdk::Pixbuf>
580 get_xpm (std::string name)
581 {
582         if (!xpm_map[name]) {
583
584                 SearchPath spath(ARDOUR::ardour_data_search_path());
585
586                 spath.add_subdirectory_to_paths("pixmaps");
587
588                 std::string data_file_path;
589
590                 if(!find_file_in_search_path (spath, name, data_file_path)) {
591                         fatal << string_compose (_("cannot find XPM file for %1"), name) << endmsg;
592                 }
593
594                 try {
595                         xpm_map[name] =  Gdk::Pixbuf::create_from_file (data_file_path);
596                 } catch(const Glib::Error& e)   {
597                         warning << "Caught Glib::Error: " << e.what() << endmsg;
598                 }
599         }
600
601         return xpm_map[name];
602 }
603
604 std::string
605 get_icon_path (const char* cname)
606 {
607         string name = cname;
608         name += X_(".png");
609
610         SearchPath spath(ARDOUR::ardour_data_search_path());
611
612         spath.add_subdirectory_to_paths("icons");
613
614         std::string data_file_path;
615
616         if (!find_file_in_search_path (spath, name, data_file_path)) {
617                 fatal << string_compose (_("cannot find icon image for %1 using %2"), name, spath.to_string()) << endmsg;
618         }
619
620         return data_file_path;
621 }
622
623 Glib::RefPtr<Gdk::Pixbuf>
624 get_icon (const char* cname)
625 {
626         Glib::RefPtr<Gdk::Pixbuf> img;
627         try {
628                 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname));
629         } catch (const Gdk::PixbufError &e) {
630                 cerr << "Caught PixbufError: " << e.what() << endl;
631         } catch (...) {
632                 error << string_compose (_("Caught exception while loading icon named %1"), cname) << endmsg;
633         }
634
635         return img;
636 }
637
638 string
639 longest (vector<string>& strings)
640 {
641         if (strings.empty()) {
642                 return string ("");
643         }
644
645         vector<string>::iterator longest = strings.begin();
646         string::size_type longest_length = (*longest).length();
647
648         vector<string>::iterator i = longest;
649         ++i;
650
651         while (i != strings.end()) {
652
653                 string::size_type len = (*i).length();
654
655                 if (len > longest_length) {
656                         longest = i;
657                         longest_length = len;
658                 }
659
660                 ++i;
661         }
662
663         return *longest;
664 }
665
666 bool
667 key_is_legal_for_numeric_entry (guint keyval)
668 {
669         /* we assume that this does not change over the life of the process 
670          */
671
672         static int comma_decimal = -1;
673
674         switch (keyval) {
675         case GDK_period:
676         case GDK_comma:
677                 if (comma_decimal < 0) {
678                         std::lconv* lc = std::localeconv();
679                         if (strchr (lc->decimal_point, ',') != 0) {
680                                 comma_decimal = 1;
681                         } else {
682                                 comma_decimal = 0;
683                         }
684                 }
685                 break;
686         default:
687                 break;
688         }
689
690         switch (keyval) {
691         case GDK_decimalpoint:
692         case GDK_KP_Separator:
693                 return true;
694
695         case GDK_period:
696                 if (comma_decimal) {
697                         return false;
698                 } else {
699                         return true;
700                 }
701                 break;
702         case GDK_comma:
703                 if (comma_decimal) {
704                         return true;
705                 } else {
706                         return false;
707                 }
708                 break;
709         case GDK_minus:
710         case GDK_plus:
711         case GDK_0:
712         case GDK_1:
713         case GDK_2:
714         case GDK_3:
715         case GDK_4:
716         case GDK_5:
717         case GDK_6:
718         case GDK_7:
719         case GDK_8:
720         case GDK_9:
721         case GDK_KP_Add:
722         case GDK_KP_Subtract:
723         case GDK_KP_Decimal:
724         case GDK_KP_0:
725         case GDK_KP_1:
726         case GDK_KP_2:
727         case GDK_KP_3:
728         case GDK_KP_4:
729         case GDK_KP_5:
730         case GDK_KP_6:
731         case GDK_KP_7:
732         case GDK_KP_8:
733         case GDK_KP_9:
734         case GDK_Return:
735         case GDK_BackSpace:
736         case GDK_Delete:
737         case GDK_KP_Enter:
738         case GDK_Home:
739         case GDK_End:
740         case GDK_Left:
741         case GDK_Right:
742                 return true;
743
744         default:
745                 break;
746         }
747
748         return false;
749 }
750 void
751 set_pango_fontsize ()
752 {
753         long val = ARDOUR::Config->get_font_scale();
754
755         /* FT2 rendering - used by GnomeCanvas, sigh */
756
757         pango_ft2_font_map_set_resolution ((PangoFT2FontMap*) pango_ft2_font_map_new(), val/1024, val/1024);
758
759         /* Cairo rendering, in case there is any */
760
761         pango_cairo_font_map_set_resolution ((PangoCairoFontMap*) pango_cairo_font_map_get_default(), val/1024);
762 }
763
764 void
765 reset_dpi ()
766 {
767         long val = ARDOUR::Config->get_font_scale();
768         set_pango_fontsize ();
769         /* Xft rendering */
770
771         gtk_settings_set_long_property (gtk_settings_get_default(),
772                                         "gtk-xft-dpi", val, "ardour");
773         DPIReset();//Emit Signal
774 }
775
776 void
777 resize_window_to_proportion_of_monitor (Gtk::Window* window, int max_width, int max_height)
778 {
779         Glib::RefPtr<Gdk::Screen> screen = window->get_screen ();
780         Gdk::Rectangle monitor_rect;
781         screen->get_monitor_geometry (0, monitor_rect);
782
783         int const w = std::min (int (monitor_rect.get_width() * 0.8), max_width);
784         int const h = std::min (int (monitor_rect.get_height() * 0.8), max_height);
785
786         window->resize (w, h);
787 }
788
789
790 /** Replace _ with __ in a string; for use with menu item text to make underscores displayed correctly */
791 string
792 escape_underscores (string const & s)
793 {
794         string o;
795         string::size_type const N = s.length ();
796
797         for (string::size_type i = 0; i < N; ++i) {
798                 if (s[i] == '_') {
799                         o += "__";
800                 } else {
801                         o += s[i];
802                 }
803         }
804
805         return o;
806 }
807
808 /** Replace < and > with &lt; and &gt; respectively to make < > display correctly in markup strings */
809 string
810 escape_angled_brackets (string const & s)
811 {
812         string o = s;
813         boost::replace_all (o, "<", "&lt;");
814         boost::replace_all (o, ">", "&gt;");
815         return o;
816 }
817
818 Gdk::Color
819 unique_random_color (list<Gdk::Color>& used_colors)
820 {
821         Gdk::Color newcolor;
822
823         while (1) {
824
825                 /* avoid neon/glowing tones by limiting them to the
826                    "inner section" (paler) of a color wheel/circle.
827                 */
828
829                 const int32_t max_saturation = 48000; // 65535 would open up the whole color wheel
830
831                 newcolor.set_red (random() % max_saturation);
832                 newcolor.set_blue (random() % max_saturation);
833                 newcolor.set_green (random() % max_saturation);
834
835                 if (used_colors.size() == 0) {
836                         used_colors.push_back (newcolor);
837                         return newcolor;
838                 }
839
840                 for (list<Gdk::Color>::iterator i = used_colors.begin(); i != used_colors.end(); ++i) {
841                   Gdk::Color c = *i;
842                         float rdelta, bdelta, gdelta;
843
844                         rdelta = newcolor.get_red() - c.get_red();
845                         bdelta = newcolor.get_blue() - c.get_blue();
846                         gdelta = newcolor.get_green() - c.get_green();
847
848                         if (sqrt (rdelta*rdelta + bdelta*bdelta + gdelta*gdelta) > 25.0) {
849                                 used_colors.push_back (newcolor);
850                                 return newcolor;
851                         }
852                 }
853
854                 /* XXX need throttle here to make sure we don't spin for ever */
855         }
856 }