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