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