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