start handling plugin window keyboard focus (in-progress)
[ardour.git] / gtk2_ardour / utils.cc
1 /*
2     Copyright (C) 2003 Paul Davis 
3
4     This program is free software; you can 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 <gtkmm2ext/utils.h>
36 #include <ardour/ardour.h>
37 #include <ardour/configuration.h>
38
39 #include "ardour_ui.h"
40 #include "keyboard.h"
41 #include "utils.h"
42 #include "i18n.h"
43 #include "rgb_macros.h"
44 #include "canvas_impl.h"
45
46 using namespace std;
47 using namespace Gtk;
48 using namespace sigc;
49 using namespace Glib;
50 using namespace PBD;
51
52 sigc::signal<void>  DPIReset;
53
54 int
55 pixel_width (const ustring& str, Pango::FontDescription& font)
56 {
57         Label foo;
58         Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout ("");
59
60         layout->set_font_description (font);
61         layout->set_text (str);
62
63         int width, height;
64         Gtkmm2ext::get_ink_pixel_size (layout, width, height);
65         return width;
66 }
67
68 ustring
69 fit_to_pixels (const ustring& str, int pixel_width, Pango::FontDescription& font, int& actual_width, bool with_ellipses)
70 {
71         Label foo;
72         Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout ("");
73         ustring::size_type shorter_by = 0;
74         ustring txt;
75
76         layout->set_font_description (font);
77
78         actual_width = 0;
79
80         ustring ustr = str;
81         ustring::iterator last = ustr.end();
82         --last; /* now points at final entry */
83
84         txt = ustr;
85
86         while (!ustr.empty()) {
87
88                 layout->set_text (txt);
89
90                 int width, height;
91                 Gtkmm2ext::get_ink_pixel_size (layout, width, height);
92
93                 if (width < pixel_width) {
94                         actual_width = width;
95                         break;
96                 }
97                 
98                 ustr.erase (last--);
99                 shorter_by++;
100
101                 if (with_ellipses && shorter_by > 3) {
102                         txt = ustr;
103                         txt += "...";
104                 } else {
105                         txt = ustr;
106                 }
107         }
108
109         return txt;
110 }
111
112 gint
113 just_hide_it (GdkEventAny *ev, Gtk::Window *win)
114 {
115         win->hide ();
116         return TRUE;
117 }
118
119 /* xpm2rgb copied from nixieclock, which bore the legend:
120
121     nixieclock - a nixie desktop timepiece
122     Copyright (C) 2000 Greg Ercolano, erco@3dsite.com
123
124     and was released under the GPL.
125 */
126
127 unsigned char*
128 xpm2rgb (const char** xpm, uint32_t& w, uint32_t& h)
129 {
130         static long vals[256], val;
131         uint32_t t, x, y, colors, cpp;
132         unsigned char c;
133         unsigned char *savergb, *rgb;
134         
135         // PARSE HEADER
136         
137         if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
138                 error << string_compose (_("bad XPM header %1"), xpm[0])
139                       << endmsg;
140                 return 0;
141         }
142
143         savergb = rgb = (unsigned char*) malloc (h * w * 3);
144         
145         // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
146         for (t = 0; t < colors; ++t) {
147                 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
148                 vals[c] = val;
149         }
150         
151         // COLORMAP -> RGB CONVERSION
152         //    Get low 3 bytes from vals[]
153         //
154
155         const char *p;
156         for (y = h-1; y > 0; --y) {
157
158                 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 3) {
159                         val = vals[(int)*p++];
160                         *(rgb+2) = val & 0xff; val >>= 8;  // 2:B
161                         *(rgb+1) = val & 0xff; val >>= 8;  // 1:G
162                         *(rgb+0) = val & 0xff;             // 0:R
163                 }
164         }
165
166         return (savergb);
167 }
168
169 unsigned char*
170 xpm2rgba (const char** xpm, uint32_t& w, uint32_t& h)
171 {
172         static long vals[256], val;
173         uint32_t t, x, y, colors, cpp;
174         unsigned char c;
175         unsigned char *savergb, *rgb;
176         char transparent;
177
178         // PARSE HEADER
179
180         if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
181                 error << string_compose (_("bad XPM header %1"), xpm[0])
182                       << endmsg;
183                 return 0;
184         }
185
186         savergb = rgb = (unsigned char*) malloc (h * w * 4);
187         
188         // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
189
190         if (strstr (xpm[1], "None")) {
191                 sscanf (xpm[1], "%c", &transparent);
192                 t = 1;
193         } else {
194                 transparent = 0;
195                 t = 0;
196         }
197
198         for (; t < colors; ++t) {
199                 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
200                 vals[c] = val;
201         }
202         
203         // COLORMAP -> RGB CONVERSION
204         //    Get low 3 bytes from vals[]
205         //
206
207         const char *p;
208         for (y = h-1; y > 0; --y) {
209
210                 char alpha;
211
212                 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 4) {
213
214                         if (transparent && (*p++ == transparent)) {
215                                 alpha = 0;
216                                 val = 0;
217                         } else {
218                                 alpha = 255;
219                                 val = vals[(int)*p];
220                         }
221
222                         *(rgb+3) = alpha;                  // 3: alpha
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 ArdourCanvas::Points*
233 get_canvas_points (string who, uint32_t npoints)
234 {
235         // cerr << who << ": wants " << npoints << " canvas points" << endl;
236 #ifdef TRAP_EXCESSIVE_POINT_REQUESTS
237         if (npoints > (uint32_t) gdk_screen_width() + 4) {
238                 abort ();
239         }
240 #endif
241         return new ArdourCanvas::Points (npoints);
242 }
243
244 Pango::FontDescription*
245 get_font_for_style (string widgetname)
246 {
247         Gtk::Window window (WINDOW_TOPLEVEL);
248         Gtk::Label foobar;
249         Glib::RefPtr<Gtk::Style> style;
250
251         window.add (foobar);
252         foobar.set_name (widgetname);
253         foobar.ensure_style();
254
255         style = foobar.get_style ();
256
257         Glib::RefPtr<const Pango::Layout> layout = foobar.get_layout();
258         
259         PangoFontDescription *pfd = (PangoFontDescription *)pango_layout_get_font_description((PangoLayout *)layout->gobj());
260         
261         if (!pfd) {
262                 
263                 /* layout inherited its font description from a PangoContext */
264
265                 PangoContext* ctxt = (PangoContext*) pango_layout_get_context ((PangoLayout*) layout->gobj());
266                 pfd =  pango_context_get_font_description (ctxt);
267                 return new Pango::FontDescription (pfd, true); /* make a copy */
268         } 
269
270         return new Pango::FontDescription (pfd, true); /* make a copy */
271 }
272
273 uint32_t
274 rgba_from_style (string style, uint32_t r, uint32_t g, uint32_t b, uint32_t a, string attr, int state, bool rgba)
275 {
276         /* In GTK+2, styles aren't set up correctly if the widget is not
277            attached to a toplevel window that has a screen pointer.
278         */
279
280         static Gtk::Window* window = 0;
281
282         if (window == 0) {
283                 window = new Window (WINDOW_TOPLEVEL);
284         }
285
286         Gtk::Label foo;
287         
288         window->add (foo);
289
290         foo.set_name (style);
291         foo.ensure_style ();
292         
293         GtkRcStyle* waverc = foo.get_style()->gobj()->rc_style;
294
295         if (waverc) {
296                 if (attr == "fg") {
297                         r = waverc->fg[state].red / 257;
298                         g = waverc->fg[state].green / 257;
299                         b = waverc->fg[state].blue / 257;
300  
301                         /* what a hack ... "a" is for "active" */
302                         if (state == Gtk::STATE_NORMAL && rgba) {
303                                 a = waverc->fg[GTK_STATE_ACTIVE].red / 257;
304                         }
305                 } else if (attr == "bg") {
306                         r = g = b = 0;
307                         r = waverc->bg[state].red / 257;
308                         g = waverc->bg[state].green / 257;
309                         b = waverc->bg[state].blue / 257;
310                 } else if (attr == "base") {
311                         r = waverc->base[state].red / 257;
312                         g = waverc->base[state].green / 257;
313                         b = waverc->base[state].blue / 257;
314                 } else if (attr == "text") {
315                         r = waverc->text[state].red / 257;
316                         g = waverc->text[state].green / 257;
317                         b = waverc->text[state].blue / 257;
318                 }
319         } else {
320                 warning << string_compose (_("missing RGBA style for \"%1\""), style) << endl;
321         }
322
323         window->remove ();
324         
325         if (state == Gtk::STATE_NORMAL && rgba) {
326                 return (uint32_t) RGBA_TO_UINT(r,g,b,a);
327         } else {
328                 return (uint32_t) RGB_TO_UINT(r,g,b);
329         }
330 }
331
332
333 Gdk::Color
334 color_from_style (string widget_style_name, int state, string attr)
335 {
336         GtkStyle* style;
337
338         style = gtk_rc_get_style_by_paths (gtk_settings_get_default(),
339                                            widget_style_name.c_str(),
340                                            0, G_TYPE_NONE);
341
342         if (!style) {
343                 error << string_compose (_("no style found for %1, using red"), style) << endmsg;
344                 return Gdk::Color ("red");
345         }
346
347         cerr << "got style for " << widget_style_name << endl;
348
349         if (attr == "fg") {
350                 return Gdk::Color (&style->fg[state]);
351         }
352
353         if (attr == "bg") {
354                 cerr << "returning color from bg\n";
355                 return Gdk::Color (&style->bg[state]);
356         }
357
358         if (attr == "light") {
359                 return Gdk::Color (&style->light[state]);
360         }
361
362         if (attr == "dark") {
363                 return Gdk::Color (&style->dark[state]);
364         }
365
366         if (attr == "mid") {
367                 return Gdk::Color (&style->mid[state]);
368         }
369
370         if (attr == "text") {
371                 return Gdk::Color (&style->text[state]);
372         }
373
374         if (attr == "base") {
375                 return Gdk::Color (&style->base[state]);
376         }
377
378         if (attr == "text_aa") {
379                 return Gdk::Color (&style->text_aa[state]);
380         }
381
382         error << string_compose (_("unknown style attribute %1 requested for color; using \"red\""), attr) << endmsg;
383         return Gdk::Color ("red");
384 }
385
386
387 bool 
388 canvas_item_visible (ArdourCanvas::Item* item)
389 {
390         return (item->gobj()->object.flags & GNOME_CANVAS_ITEM_VISIBLE) ? true : false;
391 }
392
393 void
394 set_color (Gdk::Color& c, int rgb)
395 {
396         c.set_rgb((rgb >> 16)*256, ((rgb & 0xff00) >> 8)*256, (rgb & 0xff)*256);
397 }
398
399 #ifdef GTKOSX
400 extern "C" {
401         gboolean gdk_quartz_possibly_forward (GdkEvent*);
402 }
403 #endif
404
405 bool
406 key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev)
407 {
408         GtkWindow* win = window.gobj();
409         GtkWidget* focus = gtk_window_get_focus (win);
410         bool special_handling_of_unmodified_accelerators = false;
411 #ifdef GTKOSX
412         bool allow_forwarding = true;
413 #endif
414
415 #undef DEBUG_ACCELERATOR_HANDLING
416 #ifdef  DEBUG_ACCELERATOR_HANDLING
417         bool debug = (getenv ("ARDOUR_DEBUG_ACCELERATOR_HANDLING") != 0);
418 #endif
419         if (focus) {
420                 if (GTK_IS_ENTRY(focus) || Keyboard::some_magic_widget_has_focus()) {
421                         special_handling_of_unmodified_accelerators = true;
422                 } 
423         } 
424
425 #ifdef GTKOSX
426         if (Keyboard::some_magic_widget_has_focus ()) {
427                 allow_forwarding = false;
428         }
429 #endif
430
431 #ifdef DEBUG_ACCELERATOR_HANDLING
432         if (debug) {
433                 cerr << "Win = " << win << " Key event: code = " << ev->keyval << " state = " << hex << ev->state << dec << " special handling ? " 
434                      << special_handling_of_unmodified_accelerators
435                      << endl;
436         }
437 #endif
438
439         /* This exists to allow us to override the way GTK handles
440            key events. The normal sequence is:
441
442            a) event is delivered to a GtkWindow
443            b) accelerators/mnemonics are activated
444            c) if (b) didn't handle the event, propagate to
445                the focus widget and/or focus chain
446
447            The problem with this is that if the accelerators include
448            keys without modifiers, such as the space bar or the 
449            letter "e", then pressing the key while typing into
450            a text entry widget results in the accelerator being
451            activated, instead of the desired letter appearing
452            in the text entry.
453
454            There is no good way of fixing this, but this
455            represents a compromise. The idea is that 
456            key events involving modifiers (not Shift)
457            get routed into the activation pathway first, then
458            get propagated to the focus widget if necessary.
459            
460            If the key event doesn't involve modifiers,
461            we deliver to the focus widget first, thus allowing
462            it to get "normal text" without interference
463            from acceleration.
464
465            Of course, this can also be problematic: if there
466            is a widget with focus, then it will swallow
467            all "normal text" accelerators.
468         */
469
470
471         if (!special_handling_of_unmodified_accelerators) {
472
473                 /* pretend that certain key events that GTK does not allow
474                    to be used as accelerators are actually something that
475                    it does allow.
476                 */
477
478                 uint32_t fakekey = ev->keyval;
479
480                 if (possibly_translate_keyval_to_make_legal_accelerator (fakekey)) {
481                         if (gtk_accel_groups_activate(G_OBJECT(win), fakekey, GdkModifierType(ev->state))) {
482                                 return true;
483                         }
484
485 #ifdef GTKOSX
486                         int oldval = ev->keyval;
487                         ev->keyval = fakekey;
488                         if (gdk_quartz_possibly_forward ((GdkEvent*) ev)) {
489                                 return true;
490                         }
491                         ev->keyval = oldval;
492 #endif
493                 }
494         }
495
496         /* consider all relevant modifiers but not LOCK or SHIFT */
497
498         guint mask = (Keyboard::RelevantModifierKeyMask & ~(Gdk::SHIFT_MASK|Gdk::LOCK_MASK));
499
500         if (!special_handling_of_unmodified_accelerators || (ev->state & mask)) {
501
502                 /* no special handling or there are modifiers in effect: accelerate first */
503
504 #ifdef DEBUG_ACCELERATOR_HANDLING
505                 if (debug) {
506                         cerr << "\tactivate, then propagate\n";
507                 }
508 #endif
509
510 #ifdef GTKOSX
511                 if (allow_forwarding && gdk_quartz_possibly_forward ((GdkEvent*) ev)) {
512                         return true;
513                 }
514 #endif
515                 if (!gtk_window_activate_key (win, ev)) {
516 #ifdef DEBUG_ACCELERATOR_HANDLING
517                         if (debug) {
518                                 cerr << "\tnot accelerated, now propagate\n";
519                         }
520 #endif
521                         return gtk_window_propagate_key_event (win, ev);
522                 } else {
523 #ifdef DEBUG_ACCELERATOR_HANDLING
524                         if (debug) {
525                                 cerr << "\taccelerated - done.\n";
526                         }
527 #endif
528                         return true;
529                 } 
530         }
531         
532         /* no modifiers, propagate first */
533         
534 #ifdef DEBUG_ACCELERATOR_HANDLING
535         if (debug) {
536                 cerr << "\tpropagate, then activate\n";
537         }
538 #endif
539         if (!gtk_window_propagate_key_event (win, ev)) {
540 #ifdef DEBUG_ACCELERATOR_HANDLING
541                 if (debug) {
542                         cerr << "\tpropagation didn't handle, so activate\n";
543                 }
544 #endif
545 #ifdef GTKOSX
546                 if (gdk_quartz_possibly_forward ((GdkEvent*) ev)) {
547                         return true;
548                 }
549 #endif
550                 return gtk_window_activate_key (win, ev);
551         } else {
552 #ifdef DEBUG_ACCELERATOR_HANDLING
553                 if (debug) {
554                         cerr << "\thandled by propagate\n";
555                 }
556 #endif
557                 return true;
558         }
559
560 #ifdef DEBUG_ACCELERATOR_HANDLING
561         if (debug) {
562                 cerr << "\tnot handled\n";
563         }
564 #endif
565         return true;
566 }
567
568 Glib::RefPtr<Gdk::Pixbuf>       
569 get_xpm (std::string name)
570 {
571         //cerr << "xpm path = " << ARDOUR::find_data_file(name, "pixmaps") << endl;//DEBUG
572         if (!xpm_map[name]) {
573                 try {
574                         xpm_map[name] = Gdk::Pixbuf::create_from_file (ARDOUR::find_data_file(name, "pixmaps"));
575                 }
576                 catch(const Glib::Error& e)     {
577                 warning << "Caught Glib::Error: " << e.what() << endmsg;
578                 }
579         }
580                 
581         return (xpm_map[name]);
582 }
583
584 Glib::RefPtr<Gdk::Pixbuf>       
585 get_icon (const char* cname)
586 {
587         string name = cname;
588         name += X_(".png");
589
590         string path = ARDOUR::find_data_file (name, "icons");
591
592         if (path.empty()) {
593                 fatal << string_compose (_("cannot find icon image for %1"), name) << endmsg;
594                 /*NOTREACHED*/
595         }
596
597         Glib::RefPtr<Gdk::Pixbuf> img;
598         try {
599                 img = Gdk::Pixbuf::create_from_file (path);
600         }
601         catch (const Gdk::PixbufError &e)
602     {
603         cerr << "Caught PixbufError: " << e.what() << endl;
604     }
605     catch (...)
606     {
607         g_message("Caught ... ");
608     }
609
610         return img;
611 }
612
613 string
614 longest (vector<string>& strings)
615 {
616         if (strings.empty()) {
617                 return string ("");
618         }
619
620         vector<string>::iterator longest = strings.begin();
621         string::size_type longest_length = (*longest).length();
622         
623         vector<string>::iterator i = longest;
624         ++i;
625
626         while (i != strings.end()) {
627                 
628                 string::size_type len = (*i).length();
629                 
630                 if (len > longest_length) {
631                         longest = i;
632                         longest_length = len;
633                 } 
634                 
635                 ++i;
636         }
637         
638         return *longest;
639 }
640
641 bool
642 key_is_legal_for_numeric_entry (guint keyval)
643 {
644         switch (keyval) {
645         case GDK_minus:
646         case GDK_plus:
647         case GDK_period:
648         case GDK_comma:
649         case GDK_0:
650         case GDK_1:
651         case GDK_2:
652         case GDK_3:
653         case GDK_4:
654         case GDK_5:
655         case GDK_6:
656         case GDK_7:
657         case GDK_8:
658         case GDK_9:
659         case GDK_KP_Add:
660         case GDK_KP_Subtract:
661         case GDK_KP_Decimal:
662         case GDK_KP_0:
663         case GDK_KP_1:
664         case GDK_KP_2:
665         case GDK_KP_3:
666         case GDK_KP_4:
667         case GDK_KP_5:
668         case GDK_KP_6:
669         case GDK_KP_7:
670         case GDK_KP_8:
671         case GDK_KP_9:
672         case GDK_Return:
673         case GDK_BackSpace:
674         case GDK_Delete:
675         case GDK_KP_Enter:
676         case GDK_Home:
677         case GDK_End:
678         case GDK_Left:
679         case GDK_Right:
680                 return true;
681                 
682         default:
683                 break;
684         }
685
686         return false;
687 }
688 void
689 set_pango_fontsize ()
690 {
691         long val = ARDOUR::Config->get_font_scale();
692
693         /* FT2 rendering - used by GnomeCanvas, sigh */
694
695         pango_ft2_font_map_set_resolution ((PangoFT2FontMap*) pango_ft2_font_map_for_display(), val/1024, val/1024);
696
697         /* Cairo rendering, in case there is any */
698         
699         // pango_cairo_font_map_set_resolution ((PangoCairoFontMap*) pango_cairo_font_map_get_default(), val/1024);
700 }
701
702 void
703 reset_dpi ()
704 {
705         long val = ARDOUR::Config->get_font_scale();
706         set_pango_fontsize ();
707         /* Xft rendering */
708
709         gtk_settings_set_long_property (gtk_settings_get_default(),
710                                         "gtk-xft-dpi", val, "ardour");
711         DPIReset();//Emit Signal
712 }
713
714 bool
715 possibly_translate_keyval_to_make_legal_accelerator (uint32_t& keyval)
716 {
717         int fakekey = GDK_VoidSymbol;
718
719         switch (keyval) {
720         case GDK_Tab:
721         case GDK_ISO_Left_Tab:
722                 fakekey = GDK_nabla;
723                 break;
724                 
725         case GDK_Up:
726                 fakekey = GDK_uparrow;
727                 break;
728                 
729         case GDK_Down:
730                 fakekey = GDK_downarrow;
731                 break;
732                 
733         case GDK_Right:
734                 fakekey = GDK_rightarrow;
735                 break;
736                 
737         case GDK_Left:
738                 fakekey = GDK_leftarrow;
739                 break;
740                 
741         default:
742                 break;
743         }
744         
745         if (fakekey != GDK_VoidSymbol) {
746                 keyval = fakekey;
747                 return true;
748         } 
749
750         return false;
751 }
752