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