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