Add gyk-osx.patch to repo, remove #ifdefs made obsolete by the patch.
[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 extern "C" {
400         gboolean gdk_quartz_possibly_forward (GdkEvent*);
401 }
402
403 bool
404 key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev)
405 {
406         GtkWindow* win = window.gobj();
407         GtkWidget* focus = gtk_window_get_focus (win);
408         bool special_handling_of_unmodified_accelerators = false;
409
410 #undef DEBUG_ACCELERATOR_HANDLING
411 #ifdef  DEBUG_ACCELERATOR_HANDLING
412         bool debug = (getenv ("ARDOUR_DEBUG_ACCELERATOR_HANDLING") != 0);
413 #endif
414         if (focus) {
415                 if (GTK_IS_ENTRY(focus) || Keyboard::some_magic_widget_has_focus()) {
416                         special_handling_of_unmodified_accelerators = true;
417                 } 
418         } 
419
420 #ifdef DEBUG_ACCELERATOR_HANDLING
421         if (debug) {
422                 cerr << "Win = " << win << " Key event: code = " << ev->keyval << " state = " << hex << ev->state << dec << " special handling ? " 
423                      << special_handling_of_unmodified_accelerators
424                      << endl;
425         }
426 #endif
427
428         /* This exists to allow us to override the way GTK handles
429            key events. The normal sequence is:
430
431            a) event is delivered to a GtkWindow
432            b) accelerators/mnemonics are activated
433            c) if (b) didn't handle the event, propagate to
434                the focus widget and/or focus chain
435
436            The problem with this is that if the accelerators include
437            keys without modifiers, such as the space bar or the 
438            letter "e", then pressing the key while typing into
439            a text entry widget results in the accelerator being
440            activated, instead of the desired letter appearing
441            in the text entry.
442
443            There is no good way of fixing this, but this
444            represents a compromise. The idea is that 
445            key events involving modifiers (not Shift)
446            get routed into the activation pathway first, then
447            get propagated to the focus widget if necessary.
448            
449            If the key event doesn't involve modifiers,
450            we deliver to the focus widget first, thus allowing
451            it to get "normal text" without interference
452            from acceleration.
453
454            Of course, this can also be problematic: if there
455            is a widget with focus, then it will swallow
456            all "normal text" accelerators.
457         */
458
459
460         if (!special_handling_of_unmodified_accelerators) {
461
462                 /* pretend that certain key events that GTK does not allow
463                    to be used as accelerators are actually something that
464                    it does allow.
465                 */
466
467                 uint32_t fakekey = ev->keyval;
468
469                 if (possibly_translate_keyval_to_make_legal_accelerator (fakekey)) {
470                         if (gtk_accel_groups_activate(G_OBJECT(win), fakekey, GdkModifierType(ev->state))) {
471                                 return true;
472                         }
473
474                         int oldval = ev->keyval;
475                         ev->keyval = fakekey;
476                         if (gdk_quartz_possibly_forward ((GdkEvent*) ev)) {
477                                 return true;
478                         }
479                         ev->keyval = oldval;
480                 }
481         }
482
483         /* consider all relevant modifiers but not LOCK or SHIFT */
484
485         guint mask = (Keyboard::RelevantModifierKeyMask & ~(Gdk::SHIFT_MASK|Gdk::LOCK_MASK));
486
487         if (!special_handling_of_unmodified_accelerators || (ev->state & mask)) {
488
489                 /* no special handling or there are modifiers in effect: accelerate first */
490
491 #ifdef DEBUG_ACCELERATOR_HANDLING
492                 if (debug) {
493                         cerr << "\tactivate, then propagate\n";
494                 }
495 #endif
496                 if (gdk_quartz_possibly_forward ((GdkEvent*) ev)) {
497                         return true;
498                 }
499                 if (!gtk_window_activate_key (win, ev)) {
500 #ifdef DEBUG_ACCELERATOR_HANDLING
501                         if (debug) {
502                                 cerr << "\tnot accelerated, now propagate\n";
503                         }
504 #endif
505                         return gtk_window_propagate_key_event (win, ev);
506                 } else {
507 #ifdef DEBUG_ACCELERATOR_HANDLING
508                         if (debug) {
509                                 cerr << "\taccelerated - done.\n";
510                         }
511 #endif
512                         return true;
513                 } 
514         }
515         
516         /* no modifiers, propagate first */
517         
518 #ifdef DEBUG_ACCELERATOR_HANDLING
519         if (debug) {
520                 cerr << "\tpropagate, then activate\n";
521         }
522 #endif
523         if (!gtk_window_propagate_key_event (win, ev)) {
524 #ifdef DEBUG_ACCELERATOR_HANDLING
525                 if (debug) {
526                         cerr << "\tpropagation didn't handle, so activate\n";
527                 }
528 #endif
529                 if (gdk_quartz_possibly_forward ((GdkEvent*) ev)) {
530                         return true;
531                 }
532                 return gtk_window_activate_key (win, ev);
533         } else {
534 #ifdef DEBUG_ACCELERATOR_HANDLING
535                 if (debug) {
536                         cerr << "\thandled by propagate\n";
537                 }
538 #endif
539                 return true;
540         }
541
542 #ifdef DEBUG_ACCELERATOR_HANDLING
543         if (debug) {
544                 cerr << "\tnot handled\n";
545         }
546 #endif
547         return true;
548 }
549
550 Glib::RefPtr<Gdk::Pixbuf>       
551 get_xpm (std::string name)
552 {
553         //cerr << "xpm path = " << ARDOUR::find_data_file(name, "pixmaps") << endl;//DEBUG
554         if (!xpm_map[name]) {
555                 try {
556                         xpm_map[name] = Gdk::Pixbuf::create_from_file (ARDOUR::find_data_file(name, "pixmaps"));
557                 }
558                 catch(const Glib::Error& e)     {
559                 warning << "Caught Glib::Error: " << e.what() << endmsg;
560                 }
561         }
562                 
563         return (xpm_map[name]);
564 }
565
566 Glib::RefPtr<Gdk::Pixbuf>       
567 get_icon (const char* cname)
568 {
569         string name = cname;
570         name += X_(".png");
571
572         string path = ARDOUR::find_data_file (name, "icons");
573
574         if (path.empty()) {
575                 fatal << string_compose (_("cannot find icon image for %1"), name) << endmsg;
576                 /*NOTREACHED*/
577         }
578
579         Glib::RefPtr<Gdk::Pixbuf> img;
580         try {
581                 img = Gdk::Pixbuf::create_from_file (path);
582         }
583         catch (const Gdk::PixbufError &e)
584     {
585         cerr << "Caught PixbufError: " << e.what() << endl;
586     }
587     catch (...)
588     {
589         g_message("Caught ... ");
590     }
591
592         return img;
593 }
594
595 string
596 longest (vector<string>& strings)
597 {
598         if (strings.empty()) {
599                 return string ("");
600         }
601
602         vector<string>::iterator longest = strings.begin();
603         string::size_type longest_length = (*longest).length();
604         
605         vector<string>::iterator i = longest;
606         ++i;
607
608         while (i != strings.end()) {
609                 
610                 string::size_type len = (*i).length();
611                 
612                 if (len > longest_length) {
613                         longest = i;
614                         longest_length = len;
615                 } 
616                 
617                 ++i;
618         }
619         
620         return *longest;
621 }
622
623 bool
624 key_is_legal_for_numeric_entry (guint keyval)
625 {
626         switch (keyval) {
627         case GDK_minus:
628         case GDK_plus:
629         case GDK_period:
630         case GDK_comma:
631         case GDK_0:
632         case GDK_1:
633         case GDK_2:
634         case GDK_3:
635         case GDK_4:
636         case GDK_5:
637         case GDK_6:
638         case GDK_7:
639         case GDK_8:
640         case GDK_9:
641         case GDK_KP_Add:
642         case GDK_KP_Subtract:
643         case GDK_KP_Decimal:
644         case GDK_KP_0:
645         case GDK_KP_1:
646         case GDK_KP_2:
647         case GDK_KP_3:
648         case GDK_KP_4:
649         case GDK_KP_5:
650         case GDK_KP_6:
651         case GDK_KP_7:
652         case GDK_KP_8:
653         case GDK_KP_9:
654         case GDK_Return:
655         case GDK_BackSpace:
656         case GDK_Delete:
657         case GDK_KP_Enter:
658         case GDK_Home:
659         case GDK_End:
660         case GDK_Left:
661         case GDK_Right:
662                 return true;
663                 
664         default:
665                 break;
666         }
667
668         return false;
669 }
670 void
671 set_pango_fontsize ()
672 {
673         long val = ARDOUR::Config->get_font_scale();
674
675         /* FT2 rendering - used by GnomeCanvas, sigh */
676
677         pango_ft2_font_map_set_resolution ((PangoFT2FontMap*) pango_ft2_font_map_for_display(), val/1024, val/1024);
678
679         /* Cairo rendering, in case there is any */
680         
681         // pango_cairo_font_map_set_resolution ((PangoCairoFontMap*) pango_cairo_font_map_get_default(), val/1024);
682 }
683
684 void
685 reset_dpi ()
686 {
687         long val = ARDOUR::Config->get_font_scale();
688         set_pango_fontsize ();
689         /* Xft rendering */
690
691         gtk_settings_set_long_property (gtk_settings_get_default(),
692                                         "gtk-xft-dpi", val, "ardour");
693         DPIReset();//Emit Signal
694 }
695
696 bool
697 possibly_translate_keyval_to_make_legal_accelerator (uint32_t& keyval)
698 {
699         int fakekey = GDK_VoidSymbol;
700
701         switch (keyval) {
702         case GDK_Tab:
703         case GDK_ISO_Left_Tab:
704                 fakekey = GDK_nabla;
705                 break;
706                 
707         case GDK_Up:
708                 fakekey = GDK_uparrow;
709                 break;
710                 
711         case GDK_Down:
712                 fakekey = GDK_downarrow;
713                 break;
714                 
715         case GDK_Right:
716                 fakekey = GDK_rightarrow;
717                 break;
718                 
719         case GDK_Left:
720                 fakekey = GDK_leftarrow;
721                 break;
722                 
723         default:
724                 break;
725         }
726         
727         if (fakekey != GDK_VoidSymbol) {
728                 keyval = fakekey;
729                 return true;
730         } 
731
732         return false;
733 }
734