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