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