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