a1e317abd45c344543d1ae3e70b2dd3bbfc89fac
[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 "utils.h"
34 #include "i18n.h"
35 #include "rgb_macros.h"
36
37 using namespace std;
38 using namespace Gtk;
39
40 string
41 short_version (string orig, string::size_type target_length)
42 {
43         /* this tries to create a recognizable abbreviation
44            of "orig" by removing characters until we meet
45            a certain target length.
46
47            note that we deliberately leave digits in the result
48            without modification.
49         */
50
51
52         string::size_type pos;
53
54         /* remove white-space and punctuation, starting at end */
55
56         while (orig.length() > target_length) {
57                 if ((pos = orig.find_last_of (_("\"\n\t ,<.>/?:;'[{}]~`!@#$%^&*()_-+="))) == string::npos) {
58                         break;
59                 }
60                 orig.replace (pos, 1, "");
61         }
62
63         /* remove lower-case vowels, starting at end */
64
65         while (orig.length() > target_length) {
66                 if ((pos = orig.find_last_of (_("aeiou"))) == string::npos) {
67                         break;
68                 }
69                 orig.replace (pos, 1, "");
70         }
71
72         /* remove upper-case vowels, starting at end */
73
74         while (orig.length() > target_length) {
75                 if ((pos = orig.find_last_of (_("AEIOU"))) == string::npos) {
76                         break;
77                 }
78                 orig.replace (pos, 1, "");
79         }
80
81         /* remove lower-case consonants, starting at end */
82
83         while (orig.length() > target_length) {
84                 if ((pos = orig.find_last_of (_("bcdfghjklmnpqrtvwxyz"))) == string::npos) {
85                         break;
86                 }
87                 orig.replace (pos, 1, "");
88         }
89
90         /* remove upper-case consonants, starting at end */
91
92         while (orig.length() > target_length) {
93                 if ((pos = orig.find_last_of (_("BCDFGHJKLMNPQRTVWXYZ"))) == string::npos) {
94                         break;
95                 }
96                 orig.replace (pos, 1, "");
97         }
98
99         /* whatever the length is now, use it */
100         
101         return orig;
102 }
103
104 string
105 fit_to_pixels (string str, int32_t pixel_width, Gdk_Font& font)
106 {
107         gint width;
108         gint lbearing;
109         gint rbearing;
110         gint ascent;
111         gint descent;
112
113         int namelen = str.length();
114         char cstr[namelen+1];
115         strcpy (cstr, str.c_str());
116         
117         while (namelen) {
118                 
119                 gdk_string_extents (font,
120                                     cstr,
121                                     &lbearing,
122                                     &rbearing,
123                                     &width,
124                                     &ascent,
125                                     &descent);
126
127                 if (width < (pixel_width)) {
128                         break;
129                 }
130
131                 --namelen;
132                 cstr[namelen] = '\0';
133
134         }
135
136         return cstr;
137 }
138
139 int
140 atoi (const string& s)
141 {
142         return atoi (s.c_str());
143 }
144
145 double
146 atof (const string& s)
147 {
148         return atof (s.c_str());
149 }
150
151 void
152 strip_whitespace_edges (string& str)
153 {
154         string::size_type i;
155         string::size_type len;
156         string::size_type s;
157
158         len = str.length();
159
160         for (i = 0; i < len; ++i) {
161                 if (isgraph (str[i])) {
162                         break;
163                 }
164         }
165
166         s = i;
167
168         for (i = len - 1; i >= 0; --i) {
169                 if (isgraph (str[i])) {
170                         break;
171                 }
172         }
173
174         str = str.substr (s, (i - s) + 1);
175 }
176
177 vector<string>
178 internationalize (const char **array)
179 {
180         vector<string> v;
181
182         for (uint32_t i = 0; array[i]; ++i) {
183                 v.push_back (_(array[i]));
184         }
185
186         return v;
187 }
188
189 gint
190 just_hide_it (GdkEventAny *ev, Gtk::Window *win)
191 {
192         ARDOUR_UI::instance()->allow_focus (false);
193         win->hide_all ();
194         return TRUE;
195 }
196
197 void
198 allow_keyboard_focus (bool yn)
199 {
200         ARDOUR_UI::instance()->allow_focus (yn);
201 }
202
203 /* xpm2rgb copied from nixieclock, which bore the legend:
204
205     nixieclock - a nixie desktop timepiece
206     Copyright (C) 2000 Greg Ercolano, erco@3dsite.com
207
208     and was released under the GPL.
209 */
210
211 unsigned char*
212 xpm2rgb (const char** xpm, uint32_t& w, uint32_t& h)
213 {
214         static long vals[256], val;
215         uint32_t t, x, y, colors, cpp;
216         unsigned char c;
217         unsigned char *savergb, *rgb;
218         
219         // PARSE HEADER
220         
221         if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
222                 error << compose (_("bad XPM header %1"), xpm[0])
223                       << endmsg;
224                 return 0;
225         }
226
227         savergb = rgb = (unsigned char*)art_alloc (h * w * 3);
228         
229         // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
230         for (t = 0; t < colors; ++t) {
231                 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
232                 vals[c] = val;
233         }
234         
235         // COLORMAP -> RGB CONVERSION
236         //    Get low 3 bytes from vals[]
237         //
238
239         const char *p;
240         for (y = h-1; y > 0; --y) {
241
242                 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 3) {
243                         val = vals[(int)*p++];
244                         *(rgb+2) = val & 0xff; val >>= 8;  // 2:B
245                         *(rgb+1) = val & 0xff; val >>= 8;  // 1:G
246                         *(rgb+0) = val & 0xff;             // 0:R
247                 }
248         }
249
250         return (savergb);
251 }
252
253 unsigned char*
254 xpm2rgba (const char** xpm, uint32_t& w, uint32_t& h)
255 {
256         static long vals[256], val;
257         uint32_t t, x, y, colors, cpp;
258         unsigned char c;
259         unsigned char *savergb, *rgb;
260         char transparent;
261
262         // PARSE HEADER
263
264         if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
265                 error << compose (_("bad XPM header %1"), xpm[0])
266                       << endmsg;
267                 return 0;
268         }
269
270         savergb = rgb = (unsigned char*)art_alloc (h * w * 4);
271         
272         // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
273
274         if (strstr (xpm[1], "None")) {
275                 sscanf (xpm[1], "%c", &transparent);
276                 t = 1;
277         } else {
278                 transparent = 0;
279                 t = 0;
280         }
281
282         for (; t < colors; ++t) {
283                 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
284                 vals[c] = val;
285         }
286         
287         // COLORMAP -> RGB CONVERSION
288         //    Get low 3 bytes from vals[]
289         //
290
291         const char *p;
292         for (y = h-1; y > 0; --y) {
293
294                 char alpha;
295
296                 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 4) {
297
298                         if (transparent && (*p++ == transparent)) {
299                                 alpha = 0;
300                                 val = 0;
301                         } else {
302                                 alpha = 255;
303                                 val = vals[(int)*p];
304                         }
305
306                         *(rgb+3) = alpha;                  // 3: alpha
307                         *(rgb+2) = val & 0xff; val >>= 8;  // 2:B
308                         *(rgb+1) = val & 0xff; val >>= 8;  // 1:G
309                         *(rgb+0) = val & 0xff;             // 0:R
310                 }
311         }
312
313         return (savergb);
314 }
315
316 GtkCanvasPoints*
317 get_canvas_points (string who, uint32_t npoints)
318 {
319         // cerr << who << ": wants " << npoints << " canvas points" << endl;
320 #ifdef TRAP_EXCESSIVE_POINT_REQUESTS
321         if (npoints > (uint32_t) gdk_screen_width() + 4) {
322                 abort ();
323         }
324 #endif
325         return gtk_canvas_points_new (npoints);
326 }
327
328 int
329 channel_combo_get_channel_count (Gtk::Combo& combo)
330 {
331         string str = combo.get_entry()->get_text();
332         int chns;
333
334         if (str == _("mono")) {
335                 return 1;
336         } else if (str == _("stereo")) {
337                 return 2;
338         } else if ((chns = atoi (str)) != 0) {
339                 return chns;
340         } else {
341                 return 0;
342         }
343 }
344
345 static int32_t 
346 int_from_hex (char hic, char loc) 
347 {
348         int hi;         /* hi byte */
349         int lo;         /* low byte */
350
351         hi = (int) hic;
352
353         if( ('0'<=hi) && (hi<='9') ) {
354                 hi -= '0';
355         } else if( ('a'<= hi) && (hi<= 'f') ) {
356                 hi -= ('a'-10);
357         } else if( ('A'<=hi) && (hi<='F') ) {
358                 hi -= ('A'-10);
359         }
360         
361         lo = (int) loc;
362         
363         if( ('0'<=lo) && (lo<='9') ) {
364                 lo -= '0';
365         } else if( ('a'<=lo) && (lo<='f') ) {
366                 lo -= ('a'-10);
367         } else if( ('A'<=lo) && (lo<='F') ) {
368                 lo -= ('A'-10);
369         }
370
371         return lo + (16 * hi);
372 }
373
374 void
375 url_decode (string& url)
376 {
377         string::iterator last;
378         string::iterator next;
379
380         for (string::iterator i = url.begin(); i != url.end(); ++i) {
381                 if ((*i) == '+') {
382                         *i = ' ';
383                 }
384         }
385
386         if (url.length() <= 3) {
387                 return;
388         }
389
390         last = url.end();
391
392         --last; /* points at last char */
393         --last; /* points at last char - 1 */
394
395         for (string::iterator i = url.begin(); i != last; ) {
396
397                 if (*i == '%') {
398
399                         next = i;
400
401                         url.erase (i);
402                         
403                         i = next;
404                         ++next;
405                         
406                         if (isxdigit (*i) && isxdigit (*next)) {
407                                 /* replace first digit with char */
408                                 *i = int_from_hex (*i,*next);
409                                 ++i; /* points at 2nd of 2 digits */
410                                 url.erase (i);
411                         }
412                 } else {
413                         ++i;
414                 }
415         }
416 }
417
418 string
419 get_font_for_style (string widgetname)
420 {
421         Gtk::Label foobar;
422
423         foobar.set_name (widgetname);
424         foobar.ensure_style();
425
426         if (foobar.get_style() == 0 || foobar.get_style()->gobj()->rc_style == 0 || foobar.get_style()->gobj()->rc_style->font_name == 0) {
427                 return "fixed";
428         }
429
430         string str = foobar.get_style()->gobj()->rc_style->font_name;
431
432         if (str.empty()) {
433                 return "fixed"; // standard X Window fallback font
434         } else {
435                 return str;
436         }
437 }
438 gint
439 pane_handler (GdkEventButton* ev, Gtk::Paned* pane)
440 {
441         if (ev->window != Gtkmm2ext::get_paned_handle (*pane)) {
442                 return FALSE;
443         }
444
445         if (Keyboard::is_delete_event (ev)) {
446
447                 gint pos;
448                 gint cmp;
449
450                 pos = Gtkmm2ext::gtk_paned_get_position (pane->gobj());
451
452                 if (dynamic_cast<VPaned*>(pane)) {
453                         cmp = pane->height();
454                 } else {
455                         cmp = pane->width();
456                 }
457
458                 /* we have to use approximations here because we can't predict the
459                    exact position or sizes of the pane (themes, etc)
460                 */
461
462                 if (pos < 10 || abs (pos - cmp) < 10) {
463
464                         /* already collapsed: restore it (note that this is cast from a pointer value to int, which is tricky on 64bit */
465                         
466                         pane->set_position ((gint64) pane->get_data ("rpos"));
467
468                 } else {        
469
470                         int collapse_direction;
471
472                         /* store the current position */
473
474                         pane->set_data ("rpos", (gpointer) pos);
475
476                         /* collapse to show the relevant child in full */
477                         
478                         collapse_direction = (gint64) pane->get_data ("collapse-direction");
479
480                         if (collapse_direction) {
481                                 pane->set_position (1);
482                         } else {
483                                 if (dynamic_cast<VPaned*>(pane)) {
484                                         pane->set_position (pane->height());
485                                 } else {
486                                         pane->set_position (pane->width());
487                                 }
488                         }
489                 }
490
491                 return TRUE;
492         } 
493
494         return FALSE;
495 }
496 uint32_t
497 rgba_from_style (string style, uint32_t r, uint32_t g, uint32_t b, uint32_t a)
498 {
499         Gtk::Label foo;
500         
501         foo.set_name (style);
502         foo.ensure_style ();
503         
504         GtkRcStyle* waverc = foo.get_style()->gobj()->rc_style;
505
506         if (waverc) {
507                 r = waverc->fg[Gtk::STATE_NORMAL].red / 257;
508                 g = waverc->fg[Gtk::STATE_NORMAL].green / 257;
509                 b = waverc->fg[Gtk::STATE_NORMAL].blue / 257;
510
511                 /* what a hack ... "a" is for "active" */
512
513                 a = waverc->fg[GTK_STATE_ACTIVE].red / 257; 
514
515         } else {
516                 warning << compose (_("missing RGBA style for \"%1\""), style) << endl;
517         }
518         
519         return (uint32_t) RGBA_TO_UINT(r,g,b,a);
520 }