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