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