provide sensible default step and page sizes for AutomationControllers based on param...
[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 focus = %7 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                                                           focus));
561
562         /* This exists to allow us to override the way GTK handles
563            key events. The normal sequence is:
564
565            a) event is delivered to a GtkWindow
566            b) accelerators/mnemonics are activated
567            c) if (b) didn't handle the event, propagate to
568                the focus widget and/or focus chain
569
570            The problem with this is that if the accelerators include
571            keys without modifiers, such as the space bar or the
572            letter "e", then pressing the key while typing into
573            a text entry widget results in the accelerator being
574            activated, instead of the desired letter appearing
575            in the text entry.
576
577            There is no good way of fixing this, but this
578            represents a compromise. The idea is that
579            key events involving modifiers (not Shift)
580            get routed into the activation pathway first, then
581            get propagated to the focus widget if necessary.
582
583            If the key event doesn't involve modifiers,
584            we deliver to the focus widget first, thus allowing
585            it to get "normal text" without interference
586            from acceleration.
587
588            Of course, this can also be problematic: if there
589            is a widget with focus, then it will swallow
590            all "normal text" accelerators.
591         */
592
593         if (!special_handling_of_unmodified_accelerators) {
594
595                 /* XXX note that for a brief moment, the conditional above
596                  * included "|| (ev->state & mask)" so as to enforce the
597                  * implication of special_handling_of_UNMODIFIED_accelerators.
598                  * however, this forces any key that GTK doesn't allow and that
599                  * we have an alternative (see next comment) for to be
600                  * automatically sent through the accel groups activation
601                  * pathway, which prevents individual widgets & canvas items
602                  * from ever seeing it if is used by a key binding.
603                  * 
604                  * specifically, this hid Ctrl-down-arrow from MIDI region
605                  * views because it is also bound to an action.
606                  *
607                  * until we have a robust, clean binding system, this
608                  * quirk will have to remain in place.
609                  */
610
611                 /* pretend that certain key events that GTK does not allow
612                    to be used as accelerators are actually something that
613                    it does allow. but only where there are no modifiers.
614                 */
615
616                 uint32_t fakekey = ev->keyval;
617
618                 if (Gtkmm2ext::possibly_translate_keyval_to_make_legal_accelerator (fakekey)) {
619                         if (allow_activating && gtk_accel_groups_activate(G_OBJECT(win), fakekey, GdkModifierType(ev->state))) {
620                                 DEBUG_TRACE (DEBUG::Accelerators, "\taccel group activated by fakekey\n");
621                                 return true;
622                         }
623                 }
624         }
625
626         if (!special_handling_of_unmodified_accelerators || (ev->state & mask)) {
627
628                 /* no special handling or there are modifiers in effect: accelerate first */
629
630                 DEBUG_TRACE (DEBUG::Accelerators, "\tactivate, then propagate\n");
631
632                 if (allow_activating) {
633                         if (gtk_window_activate_key (win, ev)) {
634                                 return true;
635                         }
636                 } else {
637                         DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
638                 }
639
640                 DEBUG_TRACE (DEBUG::Accelerators, "\tnot accelerated, now propagate\n");
641
642                 return gtk_window_propagate_key_event (win, ev);
643         }
644
645         /* no modifiers, propagate first */
646
647         DEBUG_TRACE (DEBUG::Accelerators, "\tpropagate, then activate\n");
648
649         if (!gtk_window_propagate_key_event (win, ev)) {
650                 DEBUG_TRACE (DEBUG::Accelerators, "\tpropagation didn't handle, so activate\n");
651                 if (allow_activating) {
652                         return gtk_window_activate_key (win, ev);
653                 } else {
654                         DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
655                 }
656
657         } else {
658                 DEBUG_TRACE (DEBUG::Accelerators, "\thandled by propagate\n");
659                 return true;
660         }
661
662         DEBUG_TRACE (DEBUG::Accelerators, "\tnot handled\n");
663         return true;
664 }
665
666 Glib::RefPtr<Gdk::Pixbuf>
667 get_xpm (std::string name)
668 {
669         if (!xpm_map[name]) {
670
671                 SearchPath spath(ARDOUR::ardour_search_path());
672                 spath += ARDOUR::system_data_search_path();
673
674                 spath.add_subdirectory_to_paths("pixmaps");
675
676                 sys::path data_file_path;
677
678                 if(!find_file_in_search_path (spath, name, data_file_path)) {
679                         fatal << string_compose (_("cannot find XPM file for %1"), name) << endmsg;
680                 }
681
682                 try {
683                         xpm_map[name] =  Gdk::Pixbuf::create_from_file (data_file_path.to_string());
684                 } catch(const Glib::Error& e)   {
685                         warning << "Caught Glib::Error: " << e.what() << endmsg;
686                 }
687         }
688
689         return xpm_map[name];
690 }
691
692 std::string
693 get_icon_path (const char* cname)
694 {
695         string name = cname;
696         name += X_(".png");
697
698         SearchPath spath(ARDOUR::ardour_search_path());
699         spath += ARDOUR::system_data_search_path();
700
701         spath.add_subdirectory_to_paths("icons");
702
703         sys::path data_file_path;
704
705         if (!find_file_in_search_path (spath, name, data_file_path)) {
706                 fatal << string_compose (_("cannot find icon image for %1"), name) << endmsg;
707         }
708
709         return data_file_path.to_string();
710 }
711
712 Glib::RefPtr<Gdk::Pixbuf>
713 get_icon (const char* cname)
714 {
715         Glib::RefPtr<Gdk::Pixbuf> img;
716         try {
717                 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname));
718         } catch (const Gdk::PixbufError &e) {
719                 cerr << "Caught PixbufError: " << e.what() << endl;
720         } catch (...) {
721                 g_message("Caught ... ");
722         }
723
724         return img;
725 }
726
727 string
728 longest (vector<string>& strings)
729 {
730         if (strings.empty()) {
731                 return string ("");
732         }
733
734         vector<string>::iterator longest = strings.begin();
735         string::size_type longest_length = (*longest).length();
736
737         vector<string>::iterator i = longest;
738         ++i;
739
740         while (i != strings.end()) {
741
742                 string::size_type len = (*i).length();
743
744                 if (len > longest_length) {
745                         longest = i;
746                         longest_length = len;
747                 }
748
749                 ++i;
750         }
751
752         return *longest;
753 }
754
755 bool
756 key_is_legal_for_numeric_entry (guint keyval)
757 {
758         switch (keyval) {
759         case GDK_minus:
760         case GDK_plus:
761         case GDK_period:
762         case GDK_comma:
763         case GDK_0:
764         case GDK_1:
765         case GDK_2:
766         case GDK_3:
767         case GDK_4:
768         case GDK_5:
769         case GDK_6:
770         case GDK_7:
771         case GDK_8:
772         case GDK_9:
773         case GDK_KP_Add:
774         case GDK_KP_Subtract:
775         case GDK_KP_Decimal:
776         case GDK_KP_0:
777         case GDK_KP_1:
778         case GDK_KP_2:
779         case GDK_KP_3:
780         case GDK_KP_4:
781         case GDK_KP_5:
782         case GDK_KP_6:
783         case GDK_KP_7:
784         case GDK_KP_8:
785         case GDK_KP_9:
786         case GDK_Return:
787         case GDK_BackSpace:
788         case GDK_Delete:
789         case GDK_KP_Enter:
790         case GDK_Home:
791         case GDK_End:
792         case GDK_Left:
793         case GDK_Right:
794                 return true;
795
796         default:
797                 break;
798         }
799
800         return false;
801 }
802 void
803 set_pango_fontsize ()
804 {
805         long val = ARDOUR::Config->get_font_scale();
806
807         /* FT2 rendering - used by GnomeCanvas, sigh */
808
809         pango_ft2_font_map_set_resolution ((PangoFT2FontMap*) pango_ft2_font_map_for_display(), val/1024, val/1024);
810
811         /* Cairo rendering, in case there is any */
812
813         pango_cairo_font_map_set_resolution ((PangoCairoFontMap*) pango_cairo_font_map_get_default(), val/1024);
814 }
815
816 void
817 reset_dpi ()
818 {
819         long val = ARDOUR::Config->get_font_scale();
820         set_pango_fontsize ();
821         /* Xft rendering */
822
823         gtk_settings_set_long_property (gtk_settings_get_default(),
824                                         "gtk-xft-dpi", val, "ardour");
825         DPIReset();//Emit Signal
826 }
827
828 void
829 resize_window_to_proportion_of_monitor (Gtk::Window* window, int max_width, int max_height)
830 {
831         Glib::RefPtr<Gdk::Screen> screen = window->get_screen ();
832         Gdk::Rectangle monitor_rect;
833         screen->get_monitor_geometry (0, monitor_rect);
834
835         int const w = std::min (int (monitor_rect.get_width() * 0.8), max_width);
836         int const h = std::min (int (monitor_rect.get_height() * 0.8), max_height);
837
838         window->resize (w, h);
839 }
840
841
842 /** Replace _ with __ in a string; for use with menu item text to make underscores displayed correctly */
843 string
844 escape_underscores (string const & s)
845 {
846         string o;
847         string::size_type const N = s.length ();
848
849         for (string::size_type i = 0; i < N; ++i) {
850                 if (s[i] == '_') {
851                         o += "__";
852                 } else {
853                         o += s[i];
854                 }
855         }
856
857         return o;
858 }
859
860 Gdk::Color
861 unique_random_color (list<Gdk::Color>& used_colors)
862 {
863         Gdk::Color newcolor;
864
865         while (1) {
866
867                 /* avoid neon/glowing tones by limiting them to the
868                    "inner section" (paler) of a color wheel/circle.
869                 */
870
871                 const int32_t max_saturation = 48000; // 65535 would open up the whole color wheel
872
873                 newcolor.set_red (random() % max_saturation);
874                 newcolor.set_blue (random() % max_saturation);
875                 newcolor.set_green (random() % max_saturation);
876
877                 if (used_colors.size() == 0) {
878                         used_colors.push_back (newcolor);
879                         return newcolor;
880                 }
881
882                 for (list<Gdk::Color>::iterator i = used_colors.begin(); i != used_colors.end(); ++i) {
883                   Gdk::Color c = *i;
884                         float rdelta, bdelta, gdelta;
885
886                         rdelta = newcolor.get_red() - c.get_red();
887                         bdelta = newcolor.get_blue() - c.get_blue();
888                         gdelta = newcolor.get_green() - c.get_green();
889
890                         if (sqrt (rdelta*rdelta + bdelta*bdelta + gdelta*gdelta) > 25.0) {
891                                 used_colors.push_back (newcolor);
892                                 return newcolor;
893                         }
894                 }
895
896                 /* XXX need throttle here to make sure we don't spin for ever */
897         }
898 }