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