add remote control menu item to editor audio time axis views
[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     $Id$
19 */
20
21 #include <cstdlib>
22 #include <cctype>
23 #include <fstream>
24 #include <sys/stat.h>
25 #include <libart_lgpl/art_misc.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
47 string
48 short_version (string orig, string::size_type target_length)
49 {
50         /* this tries to create a recognizable abbreviation
51            of "orig" by removing characters until we meet
52            a certain target length.
53
54            note that we deliberately leave digits in the result
55            without modification.
56         */
57
58
59         string::size_type pos;
60
61         /* remove white-space and punctuation, starting at end */
62
63         while (orig.length() > target_length) {
64                 if ((pos = orig.find_last_of (_("\"\n\t ,<.>/?:;'[{}]~`!@#$%^&*()_-+="))) == string::npos) {
65                         break;
66                 }
67                 orig.replace (pos, 1, "");
68         }
69
70         /* remove lower-case vowels, starting at end */
71
72         while (orig.length() > target_length) {
73                 if ((pos = orig.find_last_of (_("aeiou"))) == string::npos) {
74                         break;
75                 }
76                 orig.replace (pos, 1, "");
77         }
78
79         /* remove upper-case vowels, starting at end */
80
81         while (orig.length() > target_length) {
82                 if ((pos = orig.find_last_of (_("AEIOU"))) == string::npos) {
83                         break;
84                 }
85                 orig.replace (pos, 1, "");
86         }
87
88         /* remove lower-case consonants, starting at end */
89
90         while (orig.length() > target_length) {
91                 if ((pos = orig.find_last_of (_("bcdfghjklmnpqrtvwxyz"))) == string::npos) {
92                         break;
93                 }
94                 orig.replace (pos, 1, "");
95         }
96
97         /* remove upper-case consonants, starting at end */
98
99         while (orig.length() > target_length) {
100                 if ((pos = orig.find_last_of (_("BCDFGHJKLMNPQRTVWXYZ"))) == string::npos) {
101                         break;
102                 }
103                 orig.replace (pos, 1, "");
104         }
105
106         /* whatever the length is now, use it */
107         
108         return orig;
109 }
110
111 string
112 fit_to_pixels (const string & str, int pixel_width, const string & font)
113 {
114         Label foo;
115         int width;
116         int height;
117         Pango::FontDescription fontdesc (font);
118         
119         int namelen = str.length();
120         char cstr[namelen+1];
121         strcpy (cstr, str.c_str());
122         
123         while (namelen) {
124                 Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout (cstr);
125
126                 layout->set_font_description (fontdesc);
127                 layout->get_pixel_size (width, height);
128
129                 if (width < (pixel_width)) {
130                         break;
131                 }
132
133                 --namelen;
134                 cstr[namelen] = '\0';
135
136         }
137
138         return cstr;
139 }
140
141 int
142 atoi (const string& s)
143 {
144         return atoi (s.c_str());
145 }
146
147 double
148 atof (const string& s)
149 {
150         return atof (s.c_str());
151 }
152
153 vector<string>
154 internationalize (const char **array)
155 {
156         vector<string> v;
157
158         for (uint32_t i = 0; array[i]; ++i) {
159                 v.push_back (_(array[i]));
160         }
161
162         return v;
163 }
164
165 gint
166 just_hide_it (GdkEventAny *ev, Gtk::Window *win)
167 {
168         win->hide_all ();
169         return TRUE;
170 }
171
172 /* xpm2rgb copied from nixieclock, which bore the legend:
173
174     nixieclock - a nixie desktop timepiece
175     Copyright (C) 2000 Greg Ercolano, erco@3dsite.com
176
177     and was released under the GPL.
178 */
179
180 unsigned char*
181 xpm2rgb (const char** xpm, uint32_t& w, uint32_t& h)
182 {
183         static long vals[256], val;
184         uint32_t t, x, y, colors, cpp;
185         unsigned char c;
186         unsigned char *savergb, *rgb;
187         
188         // PARSE HEADER
189         
190         if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
191                 error << string_compose (_("bad XPM header %1"), xpm[0])
192                       << endmsg;
193                 return 0;
194         }
195
196         savergb = rgb = (unsigned char*)art_alloc (h * w * 3);
197         
198         // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
199         for (t = 0; t < colors; ++t) {
200                 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
201                 vals[c] = val;
202         }
203         
204         // COLORMAP -> RGB CONVERSION
205         //    Get low 3 bytes from vals[]
206         //
207
208         const char *p;
209         for (y = h-1; y > 0; --y) {
210
211                 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 3) {
212                         val = vals[(int)*p++];
213                         *(rgb+2) = val & 0xff; val >>= 8;  // 2:B
214                         *(rgb+1) = val & 0xff; val >>= 8;  // 1:G
215                         *(rgb+0) = val & 0xff;             // 0:R
216                 }
217         }
218
219         return (savergb);
220 }
221
222 unsigned char*
223 xpm2rgba (const char** xpm, uint32_t& w, uint32_t& h)
224 {
225         static long vals[256], val;
226         uint32_t t, x, y, colors, cpp;
227         unsigned char c;
228         unsigned char *savergb, *rgb;
229         char transparent;
230
231         // PARSE HEADER
232
233         if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
234                 error << string_compose (_("bad XPM header %1"), xpm[0])
235                       << endmsg;
236                 return 0;
237         }
238
239         savergb = rgb = (unsigned char*)art_alloc (h * w * 4);
240         
241         // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
242
243         if (strstr (xpm[1], "None")) {
244                 sscanf (xpm[1], "%c", &transparent);
245                 t = 1;
246         } else {
247                 transparent = 0;
248                 t = 0;
249         }
250
251         for (; t < colors; ++t) {
252                 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
253                 vals[c] = val;
254         }
255         
256         // COLORMAP -> RGB CONVERSION
257         //    Get low 3 bytes from vals[]
258         //
259
260         const char *p;
261         for (y = h-1; y > 0; --y) {
262
263                 char alpha;
264
265                 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 4) {
266
267                         if (transparent && (*p++ == transparent)) {
268                                 alpha = 0;
269                                 val = 0;
270                         } else {
271                                 alpha = 255;
272                                 val = vals[(int)*p];
273                         }
274
275                         *(rgb+3) = alpha;                  // 3: alpha
276                         *(rgb+2) = val & 0xff; val >>= 8;  // 2:B
277                         *(rgb+1) = val & 0xff; val >>= 8;  // 1:G
278                         *(rgb+0) = val & 0xff;             // 0:R
279                 }
280         }
281
282         return (savergb);
283 }
284
285 ArdourCanvas::Points*
286 get_canvas_points (string who, uint32_t npoints)
287 {
288         // cerr << who << ": wants " << npoints << " canvas points" << endl;
289 #ifdef TRAP_EXCESSIVE_POINT_REQUESTS
290         if (npoints > (uint32_t) gdk_screen_width() + 4) {
291                 abort ();
292         }
293 #endif
294         return new ArdourCanvas::Points (npoints);
295 }
296
297 static int32_t 
298 int_from_hex (char hic, char loc) 
299 {
300         int hi;         /* hi byte */
301         int lo;         /* low byte */
302
303         hi = (int) hic;
304
305         if( ('0'<=hi) && (hi<='9') ) {
306                 hi -= '0';
307         } else if( ('a'<= hi) && (hi<= 'f') ) {
308                 hi -= ('a'-10);
309         } else if( ('A'<=hi) && (hi<='F') ) {
310                 hi -= ('A'-10);
311         }
312         
313         lo = (int) loc;
314         
315         if( ('0'<=lo) && (lo<='9') ) {
316                 lo -= '0';
317         } else if( ('a'<=lo) && (lo<='f') ) {
318                 lo -= ('a'-10);
319         } else if( ('A'<=lo) && (lo<='F') ) {
320                 lo -= ('A'-10);
321         }
322
323         return lo + (16 * hi);
324 }
325
326 void
327 url_decode (string& url)
328 {
329         string::iterator last;
330         string::iterator next;
331
332         for (string::iterator i = url.begin(); i != url.end(); ++i) {
333                 if ((*i) == '+') {
334                         *i = ' ';
335                 }
336         }
337
338         if (url.length() <= 3) {
339                 return;
340         }
341
342         last = url.end();
343
344         --last; /* points at last char */
345         --last; /* points at last char - 1 */
346
347         for (string::iterator i = url.begin(); i != last; ) {
348
349                 if (*i == '%') {
350
351                         next = i;
352
353                         url.erase (i);
354                         
355                         i = next;
356                         ++next;
357                         
358                         if (isxdigit (*i) && isxdigit (*next)) {
359                                 /* replace first digit with char */
360                                 *i = int_from_hex (*i,*next);
361                                 ++i; /* points at 2nd of 2 digits */
362                                 url.erase (i);
363                         }
364                 } else {
365                         ++i;
366                 }
367         }
368 }
369
370 Pango::FontDescription
371 get_font_for_style (string widgetname)
372 {
373         Gtk::Window window (WINDOW_TOPLEVEL);
374         Gtk::Label foobar;
375         Glib::RefPtr<Style> style;
376
377         window.add (foobar);
378         foobar.set_name (widgetname);
379         foobar.ensure_style();
380
381         style = foobar.get_style ();
382         return style->get_font();
383 }
384
385 gint
386 pane_handler (GdkEventButton* ev, Gtk::Paned* pane)
387 {
388         if (ev->window != Gtkmm2ext::get_paned_handle (*pane)) {
389                 return FALSE;
390         }
391
392         if (Keyboard::is_delete_event (ev)) {
393
394                 gint pos;
395                 gint cmp;
396                 
397                 pos = pane->get_position ();
398
399                 if (dynamic_cast<VPaned*>(pane)) {
400                         cmp = pane->get_height();
401                 } else {
402                         cmp = pane->get_width();
403                 }
404
405                 /* we have to use approximations here because we can't predict the
406                    exact position or sizes of the pane (themes, etc)
407                 */
408
409                 if (pos < 10 || abs (pos - cmp) < 10) {
410
411                         /* already collapsed: restore it (note that this is cast from a pointer value to int, which is tricky on 64bit */
412                         
413                         pane->set_position ((intptr_t) pane->get_data ("rpos"));
414
415                 } else {        
416
417                         int collapse_direction;
418
419                         /* store the current position */
420
421                         pane->set_data ("rpos", (gpointer) pos);
422
423                         /* collapse to show the relevant child in full */
424                         
425                         collapse_direction = (intptr_t) pane->get_data ("collapse-direction");
426
427                         if (collapse_direction) {
428                                 pane->set_position (1);
429                         } else {
430                                 if (dynamic_cast<VPaned*>(pane)) {
431                                         pane->set_position (pane->get_height());
432                                 } else {
433                                         pane->set_position (pane->get_width());
434                                 }
435                         }
436                 }
437
438                 return TRUE;
439         } 
440
441         return FALSE;
442 }
443 uint32_t
444 rgba_from_style (string style, uint32_t r, uint32_t g, uint32_t b, uint32_t a, string attr, int state, bool rgba)
445 {
446         /* In GTK+2, styles aren't set up correctly if the widget is not
447            attached to a toplevel window that has a screen pointer.
448         */
449
450         static Gtk::Window* window = 0;
451
452         if (window == 0) {
453                 window = new Window (WINDOW_TOPLEVEL);
454         }
455
456         Gtk::Label foo;
457         
458         window->add (foo);
459
460         foo.set_name (style);
461         foo.ensure_style ();
462         
463         GtkRcStyle* waverc = foo.get_style()->gobj()->rc_style;
464
465         if (waverc) {
466                 if (attr == "fg") {
467                         r = waverc->fg[state].red / 257;
468                         g = waverc->fg[state].green / 257;
469                         b = waverc->fg[state].blue / 257;
470                         /* what a hack ... "a" is for "active" */
471                         if (state == Gtk::STATE_NORMAL && rgba) {
472                                 a = waverc->fg[GTK_STATE_ACTIVE].red / 257;
473                         }
474                 } else if (attr == "bg") {
475                         r = g = b = 0;
476                         r = waverc->bg[state].red / 257;
477                         g = waverc->bg[state].green / 257;
478                         b = waverc->bg[state].blue / 257;
479                 } else if (attr == "base") {
480                         r = waverc->base[state].red / 257;
481                         g = waverc->base[state].green / 257;
482                         b = waverc->base[state].blue / 257;
483                 } else if (attr == "text") {
484                         r = waverc->text[state].red / 257;
485                         g = waverc->text[state].green / 257;
486                         b = waverc->text[state].blue / 257;
487                 }
488         } else {
489                 warning << string_compose (_("missing RGBA style for \"%1\""), style) << endl;
490         }
491
492         window->remove ();
493         
494         if (state == Gtk::STATE_NORMAL && rgba) {
495                 return (uint32_t) RGBA_TO_UINT(r,g,b,a);
496         } else {
497                 return (uint32_t) RGB_TO_UINT(r,g,b);
498         }
499 }
500
501 bool 
502 canvas_item_visible (ArdourCanvas::Item* item)
503 {
504         return (item->gobj()->object.flags & GNOME_CANVAS_ITEM_VISIBLE) ? true : false;
505 }
506
507 void
508 set_color (Gdk::Color& c, int rgb)
509 {
510         c.set_rgb((rgb >> 16)*256, ((rgb & 0xff00) >> 8)*256, (rgb & 0xff)*256);
511 }
512
513 bool
514 key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev)
515 {
516         GtkWindow* win = window.gobj();
517         GtkWidget* focus = gtk_window_get_focus (win);
518         bool special_handling_of_unmodified_accelerators = false;
519
520         if (focus) {
521                 if (GTK_IS_ENTRY(focus)) {
522                         special_handling_of_unmodified_accelerators = true;
523                 }
524         } 
525
526         /* This exists to allow us to override the way GTK handles
527            key events. The normal sequence is:
528
529            a) event is delivered to a GtkWindow
530            b) accelerators/mnemonics are activated
531            c) if (b) didn't handle the event, propagate to
532                the focus widget and/or focus chain
533
534            The problem with this is that if the accelerators include
535            keys without modifiers, such as the space bar or the 
536            letter "e", then pressing the key while typing into
537            a text entry widget results in the accelerator being
538            activated, instead of the desired letter appearing
539            in the text entry.
540
541            There is no good way of fixing this, but this
542            represents a compromise. The idea is that 
543            key events involving modifiers (not Shift)
544            get routed into the activation pathway first, then
545            get propagated to the focus widget if necessary.
546            
547            If the key event doesn't involve modifiers,
548            we deliver to the focus widget first, thus allowing
549            it to get "normal text" without interference
550            from acceleration.
551
552            Of course, this can also be problematic: if there
553            is a widget with focus, then it will swallow
554            all "normal text" accelerators.
555         */
556
557         if (!special_handling_of_unmodified_accelerators ||
558             ev->state & (Gdk::MOD1_MASK|
559                          Gdk::MOD2_MASK|
560                          Gdk::MOD3_MASK|
561                          Gdk::MOD4_MASK|
562                          Gdk::MOD5_MASK|
563                          Gdk::CONTROL_MASK|
564                          Gdk::LOCK_MASK)) {
565
566                 /* no special handling or modifiers in effect: accelerate first */
567
568                 if (!gtk_window_activate_key (win, ev)) {
569                         return gtk_window_propagate_key_event (win, ev);
570                 } else {
571                         return true;
572                 } 
573         }
574         
575         /* no modifiers, propagate first */
576         
577         if (!gtk_window_propagate_key_event (win, ev)) {
578                 return gtk_window_activate_key (win, ev);
579         } 
580
581
582         return true;
583 }
584
585 Glib::RefPtr<Gdk::Pixbuf>       
586 get_xpm (std::string name)
587 {
588         if (!xpm_map[name]) {
589                 xpm_map[name] = Gdk::Pixbuf::create_from_file (ARDOUR::find_data_file(name, "pixmaps"));
590         }
591                 
592         return (xpm_map[name]);
593 }
594