0bf7c5657f6a5be3781b29902ea048b935fb9e8e
[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 #include <pango/pangoft2.h> // for fontmap resolution control for GnomeCanvas
21 #include <pango/pangocairo.h> // for fontmap resolution control for GnomeCanvas
22
23 #include <cstdlib>
24 #include <cctype>
25 #include <fstream>
26 #include <sys/stat.h>
27 #include <libart_lgpl/art_misc.h>
28 #include <gtkmm/rc.h>
29 #include <gtkmm/window.h>
30 #include <gtkmm/combo.h>
31 #include <gtkmm/label.h>
32 #include <gtkmm/paned.h>
33 #include <gtk/gtkpaned.h>
34
35 #include "pbd/file_utils.h"
36
37 #include <gtkmm2ext/utils.h>
38 #include "ardour/configuration.h"
39 #include "ardour/rc_configuration.h"
40
41 #include "ardour/filesystem_paths.h"
42
43 #include "ardour_ui.h"
44 #include "public_editor.h"
45 #include "keyboard.h"
46 #include "utils.h"
47 #include "i18n.h"
48 #include "rgb_macros.h"
49 #include "canvas_impl.h"
50
51 using namespace std;
52 using namespace Gtk;
53 using namespace Glib;
54 using namespace PBD;
55 using Gtkmm2ext::Keyboard;
56
57 sigc::signal<void>  DPIReset;
58
59 int
60 pixel_width (const ustring& str, Pango::FontDescription& font)
61 {
62         Label foo;
63         Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout ("");
64
65         layout->set_font_description (font);
66         layout->set_text (str);
67
68         int width, height;
69         Gtkmm2ext::get_ink_pixel_size (layout, width, height);
70         return width;
71 }
72
73 ustring
74 fit_to_pixels (const ustring& str, int pixel_width, Pango::FontDescription& font, int& actual_width, bool with_ellipses)
75 {
76         Label foo;
77         Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout ("");
78         ustring::size_type shorter_by = 0;
79         ustring txt;
80
81         layout->set_font_description (font);
82
83         actual_width = 0;
84
85         ustring ustr = str;
86         ustring::iterator last = ustr.end();
87         --last; /* now points at final entry */
88
89         txt = ustr;
90
91         while (!ustr.empty()) {
92
93                 layout->set_text (txt);
94
95                 int width, height;
96                 Gtkmm2ext::get_ink_pixel_size (layout, width, height);
97
98                 if (width < pixel_width) {
99                         actual_width = width;
100                         break;
101                 }
102
103                 ustr.erase (last--);
104                 shorter_by++;
105
106                 if (with_ellipses && shorter_by > 3) {
107                         txt = ustr;
108                         txt += "...";
109                 } else {
110                         txt = ustr;
111                 }
112         }
113
114         return txt;
115 }
116
117 /** Try to fit a string into a given horizontal space by ellipsizing it.
118  *  @param cr Cairo context in which the text will be plotted.
119  *  @param name Text.
120  *  @param avail Available horizontal space.
121  *  @return (Text, possibly ellipsized) and (horizontal size of text)
122  */
123
124 std::pair<std::string, double>
125 fit_to_pixels (cairo_t* cr, std::string name, double avail)
126 {
127         /* XXX hopefully there exists a more efficient way of doing this */
128
129         bool abbreviated = false;
130         uint32_t width = 0;
131
132         while (1) {
133                 cairo_text_extents_t ext;
134                 cairo_text_extents (cr, name.c_str(), &ext);
135
136                 if (ext.width < avail || name.length() <= 4) {
137                         width = ext.width;
138                         break;
139                 }
140
141                 if (abbreviated) {
142                         name = name.substr (0, name.length() - 4) + "...";
143                 } else {
144                         name = name.substr (0, name.length() - 3) + "...";
145                         abbreviated = true;
146                 }
147         }
148
149         return std::make_pair (name, width);
150 }
151
152
153 /** Add an element to a menu, settings its sensitivity.
154  * @param m Menu to add to.
155  * @param e Element to add.
156  * @param s true to make sensitive, false to make insensitive
157  */
158 void
159 add_item_with_sensitivity (Menu_Helpers::MenuList& m, Menu_Helpers::MenuElem e, bool s)
160 {
161         m.push_back (e);
162         if (!s) {
163                 m.back().set_sensitive (false);
164         }
165 }
166
167
168 gint
169 just_hide_it (GdkEventAny */*ev*/, Gtk::Window *win)
170 {
171         win->hide ();
172         return 0;
173 }
174
175 /* xpm2rgb copied from nixieclock, which bore the legend:
176
177     nixieclock - a nixie desktop timepiece
178     Copyright (C) 2000 Greg Ercolano, erco@3dsite.com
179
180     and was released under the GPL.
181 */
182
183 unsigned char*
184 xpm2rgb (const char** xpm, uint32_t& w, uint32_t& h)
185 {
186         static long vals[256], val;
187         uint32_t t, x, y, colors, cpp;
188         unsigned char c;
189         unsigned char *savergb, *rgb;
190
191         // PARSE HEADER
192
193         if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
194                 error << string_compose (_("bad XPM header %1"), xpm[0])
195                       << endmsg;
196                 return 0;
197         }
198
199         savergb = rgb = (unsigned char*) malloc (h * w * 3);
200
201         // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
202         for (t = 0; t < colors; ++t) {
203                 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
204                 vals[c] = val;
205         }
206
207         // COLORMAP -> RGB CONVERSION
208         //    Get low 3 bytes from vals[]
209         //
210
211         const char *p;
212         for (y = h-1; y > 0; --y) {
213
214                 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 3) {
215                         val = vals[(int)*p++];
216                         *(rgb+2) = val & 0xff; val >>= 8;  // 2:B
217                         *(rgb+1) = val & 0xff; val >>= 8;  // 1:G
218                         *(rgb+0) = val & 0xff;             // 0:R
219                 }
220         }
221
222         return (savergb);
223 }
224
225 unsigned char*
226 xpm2rgba (const char** xpm, uint32_t& w, uint32_t& h)
227 {
228         static long vals[256], val;
229         uint32_t t, x, y, colors, cpp;
230         unsigned char c;
231         unsigned char *savergb, *rgb;
232         char transparent;
233
234         // PARSE HEADER
235
236         if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
237                 error << string_compose (_("bad XPM header %1"), xpm[0])
238                       << endmsg;
239                 return 0;
240         }
241
242         savergb = rgb = (unsigned char*) malloc (h * w * 4);
243
244         // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
245
246         if (strstr (xpm[1], "None")) {
247                 sscanf (xpm[1], "%c", &transparent);
248                 t = 1;
249         } else {
250                 transparent = 0;
251                 t = 0;
252         }
253
254         for (; t < colors; ++t) {
255                 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
256                 vals[c] = val;
257         }
258
259         // COLORMAP -> RGB CONVERSION
260         //    Get low 3 bytes from vals[]
261         //
262
263         const char *p;
264         for (y = h-1; y > 0; --y) {
265
266                 char alpha;
267
268                 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 4) {
269
270                         if (transparent && (*p++ == transparent)) {
271                                 alpha = 0;
272                                 val = 0;
273                         } else {
274                                 alpha = 255;
275                                 val = vals[(int)*p];
276                         }
277
278                         *(rgb+3) = alpha;                  // 3: alpha
279                         *(rgb+2) = val & 0xff; val >>= 8;  // 2:B
280                         *(rgb+1) = val & 0xff; val >>= 8;  // 1:G
281                         *(rgb+0) = val & 0xff;             // 0:R
282                 }
283         }
284
285         return (savergb);
286 }
287
288 ArdourCanvas::Points*
289 get_canvas_points (string /*who*/, uint32_t npoints)
290 {
291         // cerr << who << ": wants " << npoints << " canvas points" << endl;
292 #ifdef TRAP_EXCESSIVE_POINT_REQUESTS
293         if (npoints > (uint32_t) gdk_screen_width() + 4) {
294                 abort ();
295         }
296 #endif
297         return new ArdourCanvas::Points (npoints);
298 }
299
300 Pango::FontDescription*
301 get_font_for_style (string widgetname)
302 {
303         Gtk::Window window (WINDOW_TOPLEVEL);
304         Gtk::Label foobar;
305         Glib::RefPtr<Gtk::Style> style;
306
307         window.add (foobar);
308         foobar.set_name (widgetname);
309         foobar.ensure_style();
310
311         style = foobar.get_style ();
312
313         Glib::RefPtr<const Pango::Layout> layout = foobar.get_layout();
314
315         PangoFontDescription *pfd = (PangoFontDescription *)pango_layout_get_font_description((PangoLayout *)layout->gobj());
316
317         if (!pfd) {
318
319                 /* layout inherited its font description from a PangoContext */
320
321                 PangoContext* ctxt = (PangoContext*) pango_layout_get_context ((PangoLayout*) layout->gobj());
322                 pfd =  pango_context_get_font_description (ctxt);
323                 return new Pango::FontDescription (pfd, true); /* make a copy */
324         }
325
326         return new Pango::FontDescription (pfd, true); /* make a copy */
327 }
328
329 uint32_t
330 rgba_from_style (string style, uint32_t r, uint32_t g, uint32_t b, uint32_t a, string attr, int state, bool rgba)
331 {
332         /* In GTK+2, styles aren't set up correctly if the widget is not
333            attached to a toplevel window that has a screen pointer.
334         */
335
336         static Gtk::Window* window = 0;
337
338         if (window == 0) {
339                 window = new Window (WINDOW_TOPLEVEL);
340         }
341
342         Gtk::Label foo;
343
344         window->add (foo);
345
346         foo.set_name (style);
347         foo.ensure_style ();
348
349         GtkRcStyle* waverc = foo.get_style()->gobj()->rc_style;
350
351         if (waverc) {
352                 if (attr == "fg") {
353                         r = waverc->fg[state].red / 257;
354                         g = waverc->fg[state].green / 257;
355                         b = waverc->fg[state].blue / 257;
356
357                         /* what a hack ... "a" is for "active" */
358                         if (state == Gtk::STATE_NORMAL && rgba) {
359                                 a = waverc->fg[GTK_STATE_ACTIVE].red / 257;
360                         }
361                 } else if (attr == "bg") {
362                         r = g = b = 0;
363                         r = waverc->bg[state].red / 257;
364                         g = waverc->bg[state].green / 257;
365                         b = waverc->bg[state].blue / 257;
366                 } else if (attr == "base") {
367                         r = waverc->base[state].red / 257;
368                         g = waverc->base[state].green / 257;
369                         b = waverc->base[state].blue / 257;
370                 } else if (attr == "text") {
371                         r = waverc->text[state].red / 257;
372                         g = waverc->text[state].green / 257;
373                         b = waverc->text[state].blue / 257;
374                 }
375         } else {
376                 warning << string_compose (_("missing RGBA style for \"%1\""), style) << endl;
377         }
378
379         window->remove ();
380
381         if (state == Gtk::STATE_NORMAL && rgba) {
382                 return (uint32_t) RGBA_TO_UINT(r,g,b,a);
383         } else {
384                 return (uint32_t) RGB_TO_UINT(r,g,b);
385         }
386 }
387
388
389 Gdk::Color
390 color_from_style (string widget_style_name, int state, string attr)
391 {
392         GtkStyle* style;
393
394         style = gtk_rc_get_style_by_paths (gtk_settings_get_default(),
395                                            widget_style_name.c_str(),
396                                            0, G_TYPE_NONE);
397
398         if (!style) {
399                 error << string_compose (_("no style found for %1, using red"), style) << endmsg;
400                 return Gdk::Color ("red");
401         }
402
403         if (attr == "fg") {
404                 return Gdk::Color (&style->fg[state]);
405         }
406
407         if (attr == "bg") {
408                 return Gdk::Color (&style->bg[state]);
409         }
410
411         if (attr == "light") {
412                 return Gdk::Color (&style->light[state]);
413         }
414
415         if (attr == "dark") {
416                 return Gdk::Color (&style->dark[state]);
417         }
418
419         if (attr == "mid") {
420                 return Gdk::Color (&style->mid[state]);
421         }
422
423         if (attr == "text") {
424                 return Gdk::Color (&style->text[state]);
425         }
426
427         if (attr == "base") {
428                 return Gdk::Color (&style->base[state]);
429         }
430
431         if (attr == "text_aa") {
432                 return Gdk::Color (&style->text_aa[state]);
433         }
434
435         error << string_compose (_("unknown style attribute %1 requested for color; using \"red\""), attr) << endmsg;
436         return Gdk::Color ("red");
437 }
438
439 Glib::RefPtr<Gdk::GC>
440 gc_from_style (string widget_style_name, int state, string attr)
441 {
442         GtkStyle* style;
443
444         style = gtk_rc_get_style_by_paths (gtk_settings_get_default(),
445                                            widget_style_name.c_str(),
446                                            0, G_TYPE_NONE);
447
448         if (!style) {
449                 error << string_compose (_("no style found for %1, using red"), style) << endmsg;
450                 Glib::RefPtr<Gdk::GC> ret = Gdk::GC::create();
451                 ret->set_rgb_fg_color(Gdk::Color("red"));
452                 return ret;
453         }
454
455         if (attr == "fg") {
456                 return Glib::wrap(style->fg_gc[state]);
457         }
458
459         if (attr == "bg") {
460                 return Glib::wrap(style->bg_gc[state]);
461         }
462
463         if (attr == "light") {
464                 return Glib::wrap(style->light_gc[state]);
465         }
466
467         if (attr == "dark") {
468                 return Glib::wrap(style->dark_gc[state]);
469         }
470
471         if (attr == "mid") {
472                 return Glib::wrap(style->mid_gc[state]);
473         }
474
475         if (attr == "text") {
476                 return Glib::wrap(style->text_gc[state]);
477         }
478
479         if (attr == "base") {
480                 return Glib::wrap(style->base_gc[state]);
481         }
482
483         if (attr == "text_aa") {
484                 return Glib::wrap(style->text_aa_gc[state]);
485         }
486
487         error << string_compose (_("unknown style attribute %1 requested for color; using \"red\""), attr) << endmsg;
488         Glib::RefPtr<Gdk::GC> ret = Gdk::GC::create();
489         ret->set_rgb_fg_color(Gdk::Color("red"));
490         return ret;
491 }
492
493
494 bool
495 canvas_item_visible (ArdourCanvas::Item* item)
496 {
497         return (item->gobj()->object.flags & GNOME_CANVAS_ITEM_VISIBLE) ? true : false;
498 }
499
500 void
501 set_color (Gdk::Color& c, int rgb)
502 {
503         c.set_rgb((rgb >> 16)*256, ((rgb & 0xff00) >> 8)*256, (rgb & 0xff)*256);
504 }
505
506 #ifdef GTKOSX
507 extern "C" {
508         gboolean gdk_quartz_possibly_forward (GdkEvent*);
509 }
510 #endif
511
512 bool
513 relay_key_press (GdkEventKey* ev, Gtk::Window* win)
514 {
515         if (!key_press_focus_accelerator_handler (*win, ev)) {
516                 return PublicEditor::instance().on_key_press_event(ev);
517         } else {
518                 return true;
519         }
520 }
521
522 bool
523 forward_key_press (GdkEventKey* ev)
524 {
525         return PublicEditor::instance().on_key_press_event(ev);
526 }
527
528 bool
529 key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev)
530 {
531         GtkWindow* win = window.gobj();
532         GtkWidget* focus = gtk_window_get_focus (win);
533         bool special_handling_of_unmodified_accelerators = false;
534         bool allow_activating = true;
535
536 #undef DEBUG_ACCELERATOR_HANDLING
537 #ifdef  DEBUG_ACCELERATOR_HANDLING
538         //bool debug = (getenv ("ARDOUR_DEBUG_ACCELERATOR_HANDLING") != 0);
539         bool debug=true;
540 #endif
541         if (focus) {
542                 if (GTK_IS_ENTRY(focus) || Keyboard::some_magic_widget_has_focus()) {
543                         special_handling_of_unmodified_accelerators = true;
544                 }
545         }
546
547 #ifdef GTKOSX
548         /* should this be universally true? */
549         if (Keyboard::some_magic_widget_has_focus ()) {
550                 allow_activating = false;
551         }
552 #endif
553
554 #ifdef DEBUG_ACCELERATOR_HANDLING
555         if (debug) {
556                 cerr << "Win = " << win << " Key event: code = " << ev->keyval << " state = " << hex << ev->state << dec << " special handling ? "
557                      << special_handling_of_unmodified_accelerators
558                      << " magic widget focus ? "
559                      << Keyboard::some_magic_widget_has_focus()
560                      << " allow_activation ? "
561                      << allow_activating
562                      << endl;
563         }
564 #endif
565
566         /* This exists to allow us to override the way GTK handles
567            key events. The normal sequence is:
568
569            a) event is delivered to a GtkWindow
570            b) accelerators/mnemonics are activated
571            c) if (b) didn't handle the event, propagate to
572                the focus widget and/or focus chain
573
574            The problem with this is that if the accelerators include
575            keys without modifiers, such as the space bar or the
576            letter "e", then pressing the key while typing into
577            a text entry widget results in the accelerator being
578            activated, instead of the desired letter appearing
579            in the text entry.
580
581            There is no good way of fixing this, but this
582            represents a compromise. The idea is that
583            key events involving modifiers (not Shift)
584            get routed into the activation pathway first, then
585            get propagated to the focus widget if necessary.
586
587            If the key event doesn't involve modifiers,
588            we deliver to the focus widget first, thus allowing
589            it to get "normal text" without interference
590            from acceleration.
591
592            Of course, this can also be problematic: if there
593            is a widget with focus, then it will swallow
594            all "normal text" accelerators.
595         */
596
597
598         if (!special_handling_of_unmodified_accelerators) {
599
600                 /* pretend that certain key events that GTK does not allow
601                    to be used as accelerators are actually something that
602                    it does allow.
603                 */
604
605                 uint32_t fakekey = ev->keyval;
606
607                 if (possibly_translate_keyval_to_make_legal_accelerator (fakekey)) {
608                         if (allow_activating && gtk_accel_groups_activate(G_OBJECT(win), fakekey, GdkModifierType(ev->state))) {
609                                 return true;
610                         }
611
612 #ifdef GTKOSX
613                         if (allow_activating) {
614                                 int oldval = ev->keyval;
615                                 ev->keyval = fakekey;
616                                 if (gdk_quartz_possibly_forward ((GdkEvent*) ev)) {
617                                         return true;
618                                 }
619                                 ev->keyval = oldval;
620                         }
621 #endif
622                 }
623         }
624
625         /* consider all relevant modifiers but not LOCK or SHIFT */
626
627         guint mask = (Keyboard::RelevantModifierKeyMask & ~(Gdk::SHIFT_MASK|Gdk::LOCK_MASK));
628
629         if (!special_handling_of_unmodified_accelerators || (ev->state & mask)) {
630
631                 /* no special handling or there are modifiers in effect: accelerate first */
632
633 #ifdef DEBUG_ACCELERATOR_HANDLING
634                 if (debug) {
635                         cerr << "\tactivate, then propagate\n";
636                 }
637 #endif
638
639                 if (allow_activating) {
640 #ifdef GTKOSX
641                         if (gdk_quartz_possibly_forward ((GdkEvent*) ev)) {
642                                 return true;
643                         }
644 #endif
645                         if (gtk_window_activate_key (win, ev)) {
646                                 return true;
647                         }
648                 }
649
650 #ifdef DEBUG_ACCELERATOR_HANDLING
651                 if (debug) {
652                         cerr << "\tnot accelerated, now propagate\n";
653                 }
654 #endif
655                 return gtk_window_propagate_key_event (win, ev);
656         }
657
658         /* no modifiers, propagate first */
659
660 #ifdef DEBUG_ACCELERATOR_HANDLING
661         if (debug) {
662                 cerr << "\tpropagate, then activate\n";
663         }
664 #endif
665         if (!gtk_window_propagate_key_event (win, ev)) {
666 #ifdef DEBUG_ACCELERATOR_HANDLING
667                 if (debug) {
668                         cerr << "\tpropagation didn't handle, so activate\n";
669                 }
670 #endif
671
672                 if (allow_activating) {
673
674 #ifdef GTKOSX
675                         if (gdk_quartz_possibly_forward ((GdkEvent*) ev)) {
676                                 return true;
677                         }
678 #endif
679                         return gtk_window_activate_key (win, ev);
680                 }
681
682         } else {
683 #ifdef DEBUG_ACCELERATOR_HANDLING
684                 if (debug) {
685                         cerr << "\thandled by propagate\n";
686                 }
687 #endif
688                 return true;
689         }
690
691 #ifdef DEBUG_ACCELERATOR_HANDLING
692         if (debug) {
693                 cerr << "\tnot handled\n";
694         }
695 #endif
696         return true;
697 }
698
699 Glib::RefPtr<Gdk::Pixbuf>
700 get_xpm (std::string name)
701 {
702         if (!xpm_map[name]) {
703
704                 SearchPath spath(ARDOUR::ardour_search_path());
705                 spath += ARDOUR::system_data_search_path();
706
707                 spath.add_subdirectory_to_paths("pixmaps");
708
709                 sys::path data_file_path;
710
711                 if(!find_file_in_search_path (spath, name, data_file_path)) {
712                         fatal << string_compose (_("cannot find XPM file for %1"), name) << endmsg;
713                 }
714
715                 try {
716                         xpm_map[name] =  Gdk::Pixbuf::create_from_file (data_file_path.to_string());
717                 } catch(const Glib::Error& e)   {
718                         warning << "Caught Glib::Error: " << e.what() << endmsg;
719                 }
720         }
721
722         return xpm_map[name];
723 }
724
725 Glib::ustring
726 get_icon_path (const char* cname)
727 {
728         string name = cname;
729         name += X_(".png");
730
731         SearchPath spath(ARDOUR::ardour_search_path());
732         spath += ARDOUR::system_data_search_path();
733
734         spath.add_subdirectory_to_paths("icons");
735
736         sys::path data_file_path;
737
738         if (!find_file_in_search_path (spath, name, data_file_path)) {
739                 fatal << string_compose (_("cannot find icon image for %1"), name) << endmsg;
740         }
741
742         return data_file_path.to_string();
743 }
744
745 Glib::RefPtr<Gdk::Pixbuf>
746 get_icon (const char* cname)
747 {
748         Glib::RefPtr<Gdk::Pixbuf> img;
749         try {
750                 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname));
751         } catch (const Gdk::PixbufError &e) {
752                 cerr << "Caught PixbufError: " << e.what() << endl;
753         } catch (...) {
754                 g_message("Caught ... ");
755         }
756
757         return img;
758 }
759
760 string
761 longest (vector<string>& strings)
762 {
763         if (strings.empty()) {
764                 return string ("");
765         }
766
767         vector<string>::iterator longest = strings.begin();
768         string::size_type longest_length = (*longest).length();
769
770         vector<string>::iterator i = longest;
771         ++i;
772
773         while (i != strings.end()) {
774
775                 string::size_type len = (*i).length();
776
777                 if (len > longest_length) {
778                         longest = i;
779                         longest_length = len;
780                 }
781
782                 ++i;
783         }
784
785         return *longest;
786 }
787
788 bool
789 key_is_legal_for_numeric_entry (guint keyval)
790 {
791         switch (keyval) {
792         case GDK_minus:
793         case GDK_plus:
794         case GDK_period:
795         case GDK_comma:
796         case GDK_0:
797         case GDK_1:
798         case GDK_2:
799         case GDK_3:
800         case GDK_4:
801         case GDK_5:
802         case GDK_6:
803         case GDK_7:
804         case GDK_8:
805         case GDK_9:
806         case GDK_KP_Add:
807         case GDK_KP_Subtract:
808         case GDK_KP_Decimal:
809         case GDK_KP_0:
810         case GDK_KP_1:
811         case GDK_KP_2:
812         case GDK_KP_3:
813         case GDK_KP_4:
814         case GDK_KP_5:
815         case GDK_KP_6:
816         case GDK_KP_7:
817         case GDK_KP_8:
818         case GDK_KP_9:
819         case GDK_Return:
820         case GDK_BackSpace:
821         case GDK_Delete:
822         case GDK_KP_Enter:
823         case GDK_Home:
824         case GDK_End:
825         case GDK_Left:
826         case GDK_Right:
827                 return true;
828
829         default:
830                 break;
831         }
832
833         return false;
834 }
835 void
836 set_pango_fontsize ()
837 {
838         long val = ARDOUR::Config->get_font_scale();
839
840         /* FT2 rendering - used by GnomeCanvas, sigh */
841
842         pango_ft2_font_map_set_resolution ((PangoFT2FontMap*) pango_ft2_font_map_for_display(), val/1024, val/1024);
843
844         /* Cairo rendering, in case there is any */
845
846         pango_cairo_font_map_set_resolution ((PangoCairoFontMap*) pango_cairo_font_map_get_default(), val/1024);
847 }
848
849 void
850 reset_dpi ()
851 {
852         long val = ARDOUR::Config->get_font_scale();
853         set_pango_fontsize ();
854         /* Xft rendering */
855
856         gtk_settings_set_long_property (gtk_settings_get_default(),
857                                         "gtk-xft-dpi", val, "ardour");
858         DPIReset();//Emit Signal
859 }
860
861 bool
862 possibly_translate_keyval_to_make_legal_accelerator (uint32_t& keyval)
863 {
864         int fakekey = GDK_VoidSymbol;
865
866         switch (keyval) {
867         case GDK_Tab:
868         case GDK_ISO_Left_Tab:
869                 fakekey = GDK_nabla;
870                 break;
871
872         case GDK_Up:
873                 fakekey = GDK_uparrow;
874                 break;
875
876         case GDK_Down:
877                 fakekey = GDK_downarrow;
878                 break;
879
880         case GDK_Right:
881                 fakekey = GDK_rightarrow;
882                 break;
883
884         case GDK_Left:
885                 fakekey = GDK_leftarrow;
886                 break;
887
888         case GDK_Return:
889                 fakekey = GDK_3270_Enter;
890                 break;
891
892         case GDK_KP_Enter:
893                 fakekey = GDK_F35;
894                 break;
895
896         default:
897                 break;
898         }
899
900         if (fakekey != GDK_VoidSymbol) {
901                 keyval = fakekey;
902                 return true;
903         }
904
905         return false;
906 }
907
908 uint32_t
909 possibly_translate_legal_accelerator_to_real_key (uint32_t keyval)
910 {
911         switch (keyval) {
912         case GDK_nabla:
913                 return GDK_Tab;
914                 break;
915
916         case GDK_uparrow:
917                 return GDK_Up;
918                 break;
919
920         case GDK_downarrow:
921                 return GDK_Down;
922                 break;
923
924         case GDK_rightarrow:
925                 return GDK_Right;
926                 break;
927
928         case GDK_leftarrow:
929                 return GDK_Left;
930                 break;
931
932         case GDK_3270_Enter:
933                 return GDK_Return;
934
935         case GDK_F35:
936                 return GDK_KP_Enter;
937                 break;
938         }
939
940         return keyval;
941 }
942
943
944
945 inline guint8
946 convert_color_channel (guint8 src,
947                        guint8 alpha)
948 {
949         return alpha ? ((guint (src) << 8) - src) / alpha : 0;
950 }
951
952 void
953 convert_bgra_to_rgba (guint8 const* src,
954                       guint8*       dst,
955                       int           width,
956                       int           height)
957 {
958         guint8 const* src_pixel = src;
959         guint8*       dst_pixel = dst;
960
961         for (int y = 0; y < height; y++)
962                 for (int x = 0; x < width; x++)
963                 {
964                         dst_pixel[0] = convert_color_channel (src_pixel[2],
965                                                               src_pixel[3]);
966                         dst_pixel[1] = convert_color_channel (src_pixel[1],
967                                                               src_pixel[3]);
968                         dst_pixel[2] = convert_color_channel (src_pixel[0],
969                                                               src_pixel[3]);
970                         dst_pixel[3] = src_pixel[3];
971
972                         dst_pixel += 4;
973                         src_pixel += 4;
974                 }
975 }
976
977 void
978 resize_window_to_proportion_of_monitor (Gtk::Window* window, int max_width, int max_height)
979 {
980         Glib::RefPtr<Gdk::Screen> screen = window->get_screen ();
981         Gdk::Rectangle monitor_rect;
982         screen->get_monitor_geometry (0, monitor_rect);
983
984         int const w = std::min (monitor_rect.get_width(), max_width) * 0.8;
985         int const h = std::min (monitor_rect.get_height(), max_height) * 0.8;
986
987         window->resize (w, h);
988 }
989
990 Glib::RefPtr<Gdk::Pixbuf>
991 pixbuf_from_ustring(const ustring& name, Pango::FontDescription* font, int clip_width, int clip_height, Gdk::Color fg)
992 {
993         static Glib::RefPtr<Gdk::Pixbuf>* empty_pixbuf = 0;
994
995         if (name.empty()) {
996                 if (empty_pixbuf == 0) {
997                         empty_pixbuf = new Glib::RefPtr<Gdk::Pixbuf>;
998                         *empty_pixbuf = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB, true, 8, clip_width, clip_height);
999                 }
1000                 return *empty_pixbuf;
1001         }
1002
1003         Glib::RefPtr<Gdk::Pixbuf> buf = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB, true, 8, clip_width, clip_height);
1004
1005         cairo_surface_t* surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, clip_width, clip_height);
1006         cairo_t* cr = cairo_create (surface);
1007         cairo_text_extents_t te;
1008         
1009         cairo_set_source_rgba (cr, fg.get_red_p(), fg.get_green_p(), fg.get_blue_p(), 1.0);
1010         cairo_select_font_face (cr, font->get_family().c_str(),
1011                                 CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
1012         cairo_set_font_size (cr,  font->get_size() / Pango::SCALE);
1013         cairo_text_extents (cr, name.c_str(), &te);
1014         
1015         cairo_move_to (cr, 0.5, 0.5 - te.height / 2 - te.y_bearing + clip_height / 2);
1016         cairo_show_text (cr, name.c_str());
1017         
1018         convert_bgra_to_rgba(cairo_image_surface_get_data (surface), buf->get_pixels(), clip_width, clip_height);
1019
1020         cairo_destroy(cr);
1021         cairo_surface_destroy(surface);
1022
1023         return buf;
1024 }
1025
1026 /** Replace _ with __ in a string; for use with menu item text to make underscores displayed correctly */
1027 string
1028 escape_underscores (string const & s)
1029 {
1030         string o;
1031         string::size_type const N = s.length ();
1032
1033         for (string::size_type i = 0; i < N; ++i) {
1034                 if (s[i] == '_') {
1035                         o += "__";
1036                 } else {
1037                         o += s[i];
1038                 }
1039         }
1040
1041         return o;
1042 }