implement relative clock editing (terminated by + or - keys, which adjust the clock...
[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 <cctype>
29 #include <fstream>
30 #include <list>
31 #include <sys/stat.h>
32 #include <libart_lgpl/art_misc.h>
33 #include <gtkmm/rc.h>
34 #include <gtkmm/window.h>
35 #include <gtkmm/combo.h>
36 #include <gtkmm/label.h>
37 #include <gtkmm/paned.h>
38 #include <gtk/gtkpaned.h>
39
40 #include "pbd/file_utils.h"
41
42 #include <gtkmm2ext/utils.h>
43 #include "ardour/configuration.h"
44 #include "ardour/rc_configuration.h"
45
46 #include "ardour/filesystem_paths.h"
47
48 #include "ardour_ui.h"
49 #include "debug.h"
50 #include "public_editor.h"
51 #include "keyboard.h"
52 #include "utils.h"
53 #include "i18n.h"
54 #include "rgb_macros.h"
55 #include "canvas_impl.h"
56 #include "gui_thread.h"
57
58 using namespace std;
59 using namespace Gtk;
60 using namespace Glib;
61 using namespace PBD;
62 using Gtkmm2ext::Keyboard;
63
64 sigc::signal<void>  DPIReset;
65
66 int
67 pixel_width (const string& str, Pango::FontDescription& font)
68 {
69         Label foo;
70         Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout ("");
71
72         layout->set_font_description (font);
73         layout->set_text (str);
74
75         int width, height;
76         Gtkmm2ext::get_ink_pixel_size (layout, width, height);
77         return width;
78 }
79
80 string
81 fit_to_pixels (const string& str, int pixel_width, Pango::FontDescription& font, int& actual_width, bool with_ellipses)
82 {
83         Label foo;
84         Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout ("");
85         string::size_type shorter_by = 0;
86         string txt;
87
88         layout->set_font_description (font);
89
90         actual_width = 0;
91
92         string ustr = str;
93         string::iterator last = ustr.end();
94         --last; /* now points at final entry */
95
96         txt = ustr;
97
98         while (!ustr.empty()) {
99
100                 layout->set_text (txt);
101
102                 int width, height;
103                 Gtkmm2ext::get_ink_pixel_size (layout, width, height);
104
105                 if (width < pixel_width) {
106                         actual_width = width;
107                         break;
108                 }
109
110                 ustr.erase (last--);
111                 shorter_by++;
112
113                 if (with_ellipses && shorter_by > 3) {
114                         txt = ustr;
115                         txt += "...";
116                 } else {
117                         txt = ustr;
118                 }
119         }
120
121         return txt;
122 }
123
124 /** Try to fit a string into a given horizontal space by ellipsizing it.
125  *  @param cr Cairo context in which the text will be plotted.
126  *  @param name Text.
127  *  @param avail Available horizontal space.
128  *  @return (Text, possibly ellipsized) and (horizontal size of text)
129  */
130
131 std::pair<std::string, double>
132 fit_to_pixels (cairo_t* cr, std::string name, double avail)
133 {
134         /* XXX hopefully there exists a more efficient way of doing this */
135
136         bool abbreviated = false;
137         uint32_t width = 0;
138
139         while (1) {
140                 cairo_text_extents_t ext;
141                 cairo_text_extents (cr, name.c_str(), &ext);
142
143                 if (ext.width < avail || name.length() <= 4) {
144                         width = ext.width;
145                         break;
146                 }
147
148                 if (abbreviated) {
149                         name = name.substr (0, name.length() - 4) + "...";
150                 } else {
151                         name = name.substr (0, name.length() - 3) + "...";
152                         abbreviated = true;
153                 }
154         }
155
156         return std::make_pair (name, width);
157 }
158
159
160 /** Add an element to a menu, settings its sensitivity.
161  * @param m Menu to add to.
162  * @param e Element to add.
163  * @param s true to make sensitive, false to make insensitive
164  */
165 void
166 add_item_with_sensitivity (Menu_Helpers::MenuList& m, Menu_Helpers::MenuElem e, bool s)
167 {
168         m.push_back (e);
169         if (!s) {
170                 m.back().set_sensitive (false);
171         }
172 }
173
174
175 gint
176 just_hide_it (GdkEventAny */*ev*/, Gtk::Window *win)
177 {
178         win->hide ();
179         return 0;
180 }
181
182 /* xpm2rgb copied from nixieclock, which bore the legend:
183
184     nixieclock - a nixie desktop timepiece
185     Copyright (C) 2000 Greg Ercolano, erco@3dsite.com
186
187     and was released under the GPL.
188 */
189
190 unsigned char*
191 xpm2rgb (const char** xpm, uint32_t& w, uint32_t& h)
192 {
193         static long vals[256], val;
194         uint32_t t, x, y, colors, cpp;
195         unsigned char c;
196         unsigned char *savergb, *rgb;
197
198         // PARSE HEADER
199
200         if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
201                 error << string_compose (_("bad XPM header %1"), xpm[0])
202                       << endmsg;
203                 return 0;
204         }
205
206         savergb = rgb = (unsigned char*) malloc (h * w * 3);
207
208         // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
209         for (t = 0; t < colors; ++t) {
210                 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
211                 vals[c] = val;
212         }
213
214         // COLORMAP -> RGB CONVERSION
215         //    Get low 3 bytes from vals[]
216         //
217
218         const char *p;
219         for (y = h-1; y > 0; --y) {
220
221                 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 3) {
222                         val = vals[(int)*p++];
223                         *(rgb+2) = val & 0xff; val >>= 8;  // 2:B
224                         *(rgb+1) = val & 0xff; val >>= 8;  // 1:G
225                         *(rgb+0) = val & 0xff;             // 0:R
226                 }
227         }
228
229         return (savergb);
230 }
231
232 unsigned char*
233 xpm2rgba (const char** xpm, uint32_t& w, uint32_t& h)
234 {
235         static long vals[256], val;
236         uint32_t t, x, y, colors, cpp;
237         unsigned char c;
238         unsigned char *savergb, *rgb;
239         char transparent;
240
241         // PARSE HEADER
242
243         if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
244                 error << string_compose (_("bad XPM header %1"), xpm[0])
245                       << endmsg;
246                 return 0;
247         }
248
249         savergb = rgb = (unsigned char*) malloc (h * w * 4);
250
251         // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
252
253         if (strstr (xpm[1], "None")) {
254                 sscanf (xpm[1], "%c", &transparent);
255                 t = 1;
256         } else {
257                 transparent = 0;
258                 t = 0;
259         }
260
261         for (; t < colors; ++t) {
262                 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
263                 vals[c] = val;
264         }
265
266         // COLORMAP -> RGB CONVERSION
267         //    Get low 3 bytes from vals[]
268         //
269
270         const char *p;
271         for (y = h-1; y > 0; --y) {
272
273                 char alpha;
274
275                 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 4) {
276
277                         if (transparent && (*p++ == transparent)) {
278                                 alpha = 0;
279                                 val = 0;
280                         } else {
281                                 alpha = 255;
282                                 val = vals[(int)*p];
283                         }
284
285                         *(rgb+3) = alpha;                  // 3: alpha
286                         *(rgb+2) = val & 0xff; val >>= 8;  // 2:B
287                         *(rgb+1) = val & 0xff; val >>= 8;  // 1:G
288                         *(rgb+0) = val & 0xff;             // 0:R
289                 }
290         }
291
292         return (savergb);
293 }
294
295 ArdourCanvas::Points*
296 get_canvas_points (string /*who*/, uint32_t npoints)
297 {
298         // cerr << who << ": wants " << npoints << " canvas points" << endl;
299 #ifdef TRAP_EXCESSIVE_POINT_REQUESTS
300         if (npoints > (uint32_t) gdk_screen_width() + 4) {
301                 abort ();
302         }
303 #endif
304         return new ArdourCanvas::Points (npoints);
305 }
306
307 Pango::FontDescription
308 get_font_for_style (string widgetname)
309 {
310         Gtk::Window window (WINDOW_TOPLEVEL);
311         Gtk::Label foobar;
312         Glib::RefPtr<Gtk::Style> style;
313
314         window.add (foobar);
315         foobar.set_name (widgetname);
316         foobar.ensure_style();
317
318         style = foobar.get_style ();
319
320         Glib::RefPtr<const Pango::Layout> layout = foobar.get_layout();
321
322         PangoFontDescription *pfd = (PangoFontDescription *)pango_layout_get_font_description((PangoLayout *)layout->gobj());
323
324         if (!pfd) {
325
326                 /* layout inherited its font description from a PangoContext */
327
328                 PangoContext* ctxt = (PangoContext*) pango_layout_get_context ((PangoLayout*) layout->gobj());
329                 pfd =  pango_context_get_font_description (ctxt);
330                 return Pango::FontDescription (pfd); /* make a copy */
331         }
332
333         return Pango::FontDescription (pfd); /* make a copy */
334 }
335
336 uint32_t
337 rgba_from_style (string style, uint32_t r, uint32_t g, uint32_t b, uint32_t a, string attr, int state, bool rgba)
338 {
339         /* In GTK+2, styles aren't set up correctly if the widget is not
340            attached to a toplevel window that has a screen pointer.
341         */
342
343         static Gtk::Window* window = 0;
344
345         if (window == 0) {
346                 window = new Window (WINDOW_TOPLEVEL);
347         }
348
349         Gtk::Label foo;
350
351         window->add (foo);
352
353         foo.set_name (style);
354         foo.ensure_style ();
355
356         GtkRcStyle* rc = foo.get_style()->gobj()->rc_style;
357
358         if (rc) {
359                 if (attr == "fg") {
360                         r = rc->fg[state].red / 257;
361                         g = rc->fg[state].green / 257;
362                         b = rc->fg[state].blue / 257;
363
364                         /* what a hack ... "a" is for "active" */
365                         if (state == Gtk::STATE_NORMAL && rgba) {
366                                 a = rc->fg[GTK_STATE_ACTIVE].red / 257;
367                         }
368                 } else if (attr == "bg") {
369                         r = g = b = 0;
370                         r = rc->bg[state].red / 257;
371                         g = rc->bg[state].green / 257;
372                         b = rc->bg[state].blue / 257;
373                 } else if (attr == "base") {
374                         r = rc->base[state].red / 257;
375                         g = rc->base[state].green / 257;
376                         b = rc->base[state].blue / 257;
377                 } else if (attr == "text") {
378                         r = rc->text[state].red / 257;
379                         g = rc->text[state].green / 257;
380                         b = rc->text[state].blue / 257;
381                 }
382         } else {
383                 warning << string_compose (_("missing RGBA style for \"%1\""), style) << endl;
384         }
385
386         window->remove ();
387
388         if (state == Gtk::STATE_NORMAL && rgba) {
389                 return (uint32_t) RGBA_TO_UINT(r,g,b,a);
390         } else {
391                 return (uint32_t) RGB_TO_UINT(r,g,b);
392         }
393 }
394
395
396 Gdk::Color
397 color_from_style (string widget_style_name, int state, string attr)
398 {
399         GtkStyle* style;
400
401         style = gtk_rc_get_style_by_paths (gtk_settings_get_default(),
402                                            widget_style_name.c_str(),
403                                            0, G_TYPE_NONE);
404
405         if (!style) {
406                 error << string_compose (_("no style found for %1, using red"), style) << endmsg;
407                 return Gdk::Color ("red");
408         }
409
410         if (attr == "fg") {
411                 return Gdk::Color (&style->fg[state]);
412         }
413
414         if (attr == "bg") {
415                 return Gdk::Color (&style->bg[state]);
416         }
417
418         if (attr == "light") {
419                 return Gdk::Color (&style->light[state]);
420         }
421
422         if (attr == "dark") {
423                 return Gdk::Color (&style->dark[state]);
424         }
425
426         if (attr == "mid") {
427                 return Gdk::Color (&style->mid[state]);
428         }
429
430         if (attr == "text") {
431                 return Gdk::Color (&style->text[state]);
432         }
433
434         if (attr == "base") {
435                 return Gdk::Color (&style->base[state]);
436         }
437
438         if (attr == "text_aa") {
439                 return Gdk::Color (&style->text_aa[state]);
440         }
441
442         error << string_compose (_("unknown style attribute %1 requested for color; using \"red\""), attr) << endmsg;
443         return Gdk::Color ("red");
444 }
445
446 Glib::RefPtr<Gdk::GC>
447 gc_from_style (string widget_style_name, int state, string attr)
448 {
449         GtkStyle* style;
450
451         style = gtk_rc_get_style_by_paths (gtk_settings_get_default(),
452                                            widget_style_name.c_str(),
453                                            0, G_TYPE_NONE);
454
455         if (!style) {
456                 error << string_compose (_("no style found for %1, using red"), style) << endmsg;
457                 Glib::RefPtr<Gdk::GC> ret = Gdk::GC::create();
458                 ret->set_rgb_fg_color(Gdk::Color("red"));
459                 return ret;
460         }
461
462         if (attr == "fg") {
463                 return Glib::wrap(style->fg_gc[state]);
464         }
465
466         if (attr == "bg") {
467                 return Glib::wrap(style->bg_gc[state]);
468         }
469
470         if (attr == "light") {
471                 return Glib::wrap(style->light_gc[state]);
472         }
473
474         if (attr == "dark") {
475                 return Glib::wrap(style->dark_gc[state]);
476         }
477
478         if (attr == "mid") {
479                 return Glib::wrap(style->mid_gc[state]);
480         }
481
482         if (attr == "text") {
483                 return Glib::wrap(style->text_gc[state]);
484         }
485
486         if (attr == "base") {
487                 return Glib::wrap(style->base_gc[state]);
488         }
489
490         if (attr == "text_aa") {
491                 return Glib::wrap(style->text_aa_gc[state]);
492         }
493
494         error << string_compose (_("unknown style attribute %1 requested for color; using \"red\""), attr) << endmsg;
495         Glib::RefPtr<Gdk::GC> ret = Gdk::GC::create();
496         ret->set_rgb_fg_color(Gdk::Color("red"));
497         return ret;
498 }
499
500
501 bool
502 canvas_item_visible (ArdourCanvas::Item* item)
503 {
504         return (item->gobj()->object.flags & GNOME_CANVAS_ITEM_VISIBLE) ? true : false;
505 }
506
507 void
508 set_color (Gdk::Color& c, int rgb)
509 {
510         c.set_rgb((rgb >> 16)*256, ((rgb & 0xff00) >> 8)*256, (rgb & 0xff)*256);
511 }
512
513 bool
514 relay_key_press (GdkEventKey* ev, Gtk::Window* win)
515 {
516         if (!key_press_focus_accelerator_handler (*win, ev)) {
517                 return PublicEditor::instance().on_key_press_event(ev);
518         } else {
519                 return true;
520         }
521 }
522
523 bool
524 forward_key_press (GdkEventKey* ev)
525 {
526         return PublicEditor::instance().on_key_press_event(ev);
527 }
528
529 bool
530 key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev)
531 {
532         GtkWindow* win = window.gobj();
533         GtkWidget* focus = gtk_window_get_focus (win);
534         bool special_handling_of_unmodified_accelerators = false;
535         bool allow_activating = true;
536         /* consider all relevant modifiers but not LOCK or SHIFT */
537         const guint mask = (Keyboard::RelevantModifierKeyMask & ~(Gdk::SHIFT_MASK|Gdk::LOCK_MASK));
538
539         if (focus) {
540                 if (GTK_IS_ENTRY(focus) || Keyboard::some_magic_widget_has_focus()) {
541                         special_handling_of_unmodified_accelerators = true;
542                 }
543         }
544
545 #ifdef GTKOSX
546         /* should this be universally true? */
547         if (Keyboard::some_magic_widget_has_focus ()) {
548                 allow_activating = false;
549         }
550 #endif
551
552
553         DEBUG_TRACE (DEBUG::Accelerators, string_compose ("Win = %1 Key event: code = %2  state = %3 special handling ? %4 magic widget focus ? %5 allow_activation ? %6\n",
554                                                           win,
555                                                           ev->keyval,
556                                                           ev->state,
557                                                           special_handling_of_unmodified_accelerators,
558                                                           Keyboard::some_magic_widget_has_focus(),
559                                                           allow_activating));
560
561         /* This exists to allow us to override the way GTK handles
562            key events. The normal sequence is:
563
564            a) event is delivered to a GtkWindow
565            b) accelerators/mnemonics are activated
566            c) if (b) didn't handle the event, propagate to
567                the focus widget and/or focus chain
568
569            The problem with this is that if the accelerators include
570            keys without modifiers, such as the space bar or the
571            letter "e", then pressing the key while typing into
572            a text entry widget results in the accelerator being
573            activated, instead of the desired letter appearing
574            in the text entry.
575
576            There is no good way of fixing this, but this
577            represents a compromise. The idea is that
578            key events involving modifiers (not Shift)
579            get routed into the activation pathway first, then
580            get propagated to the focus widget if necessary.
581
582            If the key event doesn't involve modifiers,
583            we deliver to the focus widget first, thus allowing
584            it to get "normal text" without interference
585            from acceleration.
586
587            Of course, this can also be problematic: if there
588            is a widget with focus, then it will swallow
589            all "normal text" accelerators.
590         */
591
592         if (!special_handling_of_unmodified_accelerators) {
593
594                 /* XXX note that for a brief moment, the conditional above
595                  * included "|| (ev->state & mask)" so as to enforce the
596                  * implication of special_handling_of_UNMODIFIED_accelerators.
597                  * however, this forces any key that GTK doesn't allow and that
598                  * we have an alternative (see next comment) for to be
599                  * automatically sent through the accel groups activation
600                  * pathway, which prevents individual widgets & canvas items
601                  * from ever seeing it if is used by a key binding.
602                  * 
603                  * specifically, this hid Ctrl-down-arrow from MIDI region
604                  * views because it is also bound to an action.
605                  *
606                  * until we have a robust, clean binding system, this
607                  * quirk will have to remain in place.
608                  */
609
610                 /* pretend that certain key events that GTK does not allow
611                    to be used as accelerators are actually something that
612                    it does allow. but only where there are no modifiers.
613                 */
614
615                 uint32_t fakekey = ev->keyval;
616
617                 if (Gtkmm2ext::possibly_translate_keyval_to_make_legal_accelerator (fakekey)) {
618                         if (allow_activating && gtk_accel_groups_activate(G_OBJECT(win), fakekey, GdkModifierType(ev->state))) {
619                                 DEBUG_TRACE (DEBUG::Accelerators, "\taccel group activated by fakekey\n");
620                                 return true;
621                         }
622                 }
623         }
624
625         if (!special_handling_of_unmodified_accelerators || (ev->state & mask)) {
626
627                 /* no special handling or there are modifiers in effect: accelerate first */
628
629                 DEBUG_TRACE (DEBUG::Accelerators, "\tactivate, then propagate\n");
630
631                 if (allow_activating) {
632                         if (gtk_window_activate_key (win, ev)) {
633                                 return true;
634                         }
635                 } else {
636                         DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
637                 }
638
639                 DEBUG_TRACE (DEBUG::Accelerators, "\tnot accelerated, now propagate\n");
640
641                 return gtk_window_propagate_key_event (win, ev);
642         }
643
644         /* no modifiers, propagate first */
645
646         DEBUG_TRACE (DEBUG::Accelerators, "\tpropagate, then activate\n");
647
648         if (!gtk_window_propagate_key_event (win, ev)) {
649                 DEBUG_TRACE (DEBUG::Accelerators, "\tpropagation didn't handle, so activate\n");
650                 if (allow_activating) {
651                         return gtk_window_activate_key (win, ev);
652                 } else {
653                         DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
654                 }
655
656         } else {
657                 DEBUG_TRACE (DEBUG::Accelerators, "\thandled by propagate\n");
658                 return true;
659         }
660
661         DEBUG_TRACE (DEBUG::Accelerators, "\tnot handled\n");
662         return true;
663 }
664
665 Glib::RefPtr<Gdk::Pixbuf>
666 get_xpm (std::string name)
667 {
668         if (!xpm_map[name]) {
669
670                 SearchPath spath(ARDOUR::ardour_search_path());
671                 spath += ARDOUR::system_data_search_path();
672
673                 spath.add_subdirectory_to_paths("pixmaps");
674
675                 sys::path data_file_path;
676
677                 if(!find_file_in_search_path (spath, name, data_file_path)) {
678                         fatal << string_compose (_("cannot find XPM file for %1"), name) << endmsg;
679                 }
680
681                 try {
682                         xpm_map[name] =  Gdk::Pixbuf::create_from_file (data_file_path.to_string());
683                 } catch(const Glib::Error& e)   {
684                         warning << "Caught Glib::Error: " << e.what() << endmsg;
685                 }
686         }
687
688         return xpm_map[name];
689 }
690
691 std::string
692 get_icon_path (const char* cname)
693 {
694         string name = cname;
695         name += X_(".png");
696
697         SearchPath spath(ARDOUR::ardour_search_path());
698         spath += ARDOUR::system_data_search_path();
699
700         spath.add_subdirectory_to_paths("icons");
701
702         sys::path data_file_path;
703
704         if (!find_file_in_search_path (spath, name, data_file_path)) {
705                 fatal << string_compose (_("cannot find icon image for %1"), name) << endmsg;
706         }
707
708         return data_file_path.to_string();
709 }
710
711 Glib::RefPtr<Gdk::Pixbuf>
712 get_icon (const char* cname)
713 {
714         Glib::RefPtr<Gdk::Pixbuf> img;
715         try {
716                 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname));
717         } catch (const Gdk::PixbufError &e) {
718                 cerr << "Caught PixbufError: " << e.what() << endl;
719         } catch (...) {
720                 g_message("Caught ... ");
721         }
722
723         return img;
724 }
725
726 string
727 longest (vector<string>& strings)
728 {
729         if (strings.empty()) {
730                 return string ("");
731         }
732
733         vector<string>::iterator longest = strings.begin();
734         string::size_type longest_length = (*longest).length();
735
736         vector<string>::iterator i = longest;
737         ++i;
738
739         while (i != strings.end()) {
740
741                 string::size_type len = (*i).length();
742
743                 if (len > longest_length) {
744                         longest = i;
745                         longest_length = len;
746                 }
747
748                 ++i;
749         }
750
751         return *longest;
752 }
753
754 bool
755 key_is_legal_for_numeric_entry (guint keyval)
756 {
757         switch (keyval) {
758         case GDK_minus:
759         case GDK_plus:
760         case GDK_period:
761         case GDK_comma:
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 void
802 set_pango_fontsize ()
803 {
804         long val = ARDOUR::Config->get_font_scale();
805
806         /* FT2 rendering - used by GnomeCanvas, sigh */
807
808         pango_ft2_font_map_set_resolution ((PangoFT2FontMap*) pango_ft2_font_map_for_display(), val/1024, val/1024);
809
810         /* Cairo rendering, in case there is any */
811
812         pango_cairo_font_map_set_resolution ((PangoCairoFontMap*) pango_cairo_font_map_get_default(), val/1024);
813 }
814
815 void
816 reset_dpi ()
817 {
818         long val = ARDOUR::Config->get_font_scale();
819         set_pango_fontsize ();
820         /* Xft rendering */
821
822         gtk_settings_set_long_property (gtk_settings_get_default(),
823                                         "gtk-xft-dpi", val, "ardour");
824         DPIReset();//Emit Signal
825 }
826
827 void
828 resize_window_to_proportion_of_monitor (Gtk::Window* window, int max_width, int max_height)
829 {
830         Glib::RefPtr<Gdk::Screen> screen = window->get_screen ();
831         Gdk::Rectangle monitor_rect;
832         screen->get_monitor_geometry (0, monitor_rect);
833
834         int const w = std::min (int (monitor_rect.get_width() * 0.8), max_width);
835         int const h = std::min (int (monitor_rect.get_height() * 0.8), max_height);
836
837         window->resize (w, h);
838 }
839
840
841 /** Replace _ with __ in a string; for use with menu item text to make underscores displayed correctly */
842 string
843 escape_underscores (string const & s)
844 {
845         string o;
846         string::size_type const N = s.length ();
847
848         for (string::size_type i = 0; i < N; ++i) {
849                 if (s[i] == '_') {
850                         o += "__";
851                 } else {
852                         o += s[i];
853                 }
854         }
855
856         return o;
857 }
858
859 Gdk::Color
860 unique_random_color (list<Gdk::Color>& used_colors)
861 {
862         Gdk::Color newcolor;
863
864         while (1) {
865
866                 /* avoid neon/glowing tones by limiting them to the
867                    "inner section" (paler) of a color wheel/circle.
868                 */
869
870                 const int32_t max_saturation = 48000; // 65535 would open up the whole color wheel
871
872                 newcolor.set_red (random() % max_saturation);
873                 newcolor.set_blue (random() % max_saturation);
874                 newcolor.set_green (random() % max_saturation);
875
876                 if (used_colors.size() == 0) {
877                         used_colors.push_back (newcolor);
878                         return newcolor;
879                 }
880
881                 for (list<Gdk::Color>::iterator i = used_colors.begin(); i != used_colors.end(); ++i) {
882                   Gdk::Color c = *i;
883                         float rdelta, bdelta, gdelta;
884
885                         rdelta = newcolor.get_red() - c.get_red();
886                         bdelta = newcolor.get_blue() - c.get_blue();
887                         gdelta = newcolor.get_green() - c.get_green();
888
889                         if (sqrt (rdelta*rdelta + bdelta*bdelta + gdelta*gdelta) > 25.0) {
890                                 used_colors.push_back (newcolor);
891                                 return newcolor;
892                         }
893                 }
894
895                 /* XXX need throttle here to make sure we don't spin for ever */
896         }
897 }