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