create xpm files from hard coded pixmaps, added function get_xpm() to make sure any...
[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 #include <ardour/ardour.h>
32
33 #include "ardour_ui.h"
34 #include "keyboard.h"
35 #include "utils.h"
36 #include "i18n.h"
37 #include "rgb_macros.h"
38 #include "canvas_impl.h"
39
40 using namespace std;
41 using namespace Gtk;
42 using namespace sigc;
43 using namespace Glib;
44
45 string
46 short_version (string orig, string::size_type target_length)
47 {
48         /* this tries to create a recognizable abbreviation
49            of "orig" by removing characters until we meet
50            a certain target length.
51
52            note that we deliberately leave digits in the result
53            without modification.
54         */
55
56
57         string::size_type pos;
58
59         /* remove white-space and punctuation, starting at end */
60
61         while (orig.length() > target_length) {
62                 if ((pos = orig.find_last_of (_("\"\n\t ,<.>/?:;'[{}]~`!@#$%^&*()_-+="))) == string::npos) {
63                         break;
64                 }
65                 orig.replace (pos, 1, "");
66         }
67
68         /* remove lower-case vowels, starting at end */
69
70         while (orig.length() > target_length) {
71                 if ((pos = orig.find_last_of (_("aeiou"))) == string::npos) {
72                         break;
73                 }
74                 orig.replace (pos, 1, "");
75         }
76
77         /* remove upper-case vowels, starting at end */
78
79         while (orig.length() > target_length) {
80                 if ((pos = orig.find_last_of (_("AEIOU"))) == string::npos) {
81                         break;
82                 }
83                 orig.replace (pos, 1, "");
84         }
85
86         /* remove lower-case consonants, starting at end */
87
88         while (orig.length() > target_length) {
89                 if ((pos = orig.find_last_of (_("bcdfghjklmnpqrtvwxyz"))) == string::npos) {
90                         break;
91                 }
92                 orig.replace (pos, 1, "");
93         }
94
95         /* remove upper-case consonants, starting at end */
96
97         while (orig.length() > target_length) {
98                 if ((pos = orig.find_last_of (_("BCDFGHJKLMNPQRTVWXYZ"))) == string::npos) {
99                         break;
100                 }
101                 orig.replace (pos, 1, "");
102         }
103
104         /* whatever the length is now, use it */
105         
106         return orig;
107 }
108
109 string
110 fit_to_pixels (const string & str, int pixel_width, const string & font)
111 {
112         Label foo;
113         int width;
114         int height;
115         Pango::FontDescription fontdesc (font);
116         
117         int namelen = str.length();
118         char cstr[namelen+1];
119         strcpy (cstr, str.c_str());
120         
121         while (namelen) {
122                 Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout (cstr);
123
124                 layout->set_font_description (fontdesc);
125                 layout->get_pixel_size (width, height);
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 vector<string>
152 internationalize (const char **array)
153 {
154         vector<string> v;
155
156         for (uint32_t i = 0; array[i]; ++i) {
157                 v.push_back (_(array[i]));
158         }
159
160         return v;
161 }
162
163 gint
164 just_hide_it (GdkEventAny *ev, Gtk::Window *win)
165 {
166         win->hide_all ();
167         return TRUE;
168 }
169
170 /* xpm2rgb copied from nixieclock, which bore the legend:
171
172     nixieclock - a nixie desktop timepiece
173     Copyright (C) 2000 Greg Ercolano, erco@3dsite.com
174
175     and was released under the GPL.
176 */
177
178 unsigned char*
179 xpm2rgb (const char** xpm, uint32_t& w, uint32_t& h)
180 {
181         static long vals[256], val;
182         uint32_t t, x, y, colors, cpp;
183         unsigned char c;
184         unsigned char *savergb, *rgb;
185         
186         // PARSE HEADER
187         
188         if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
189                 error << string_compose (_("bad XPM header %1"), xpm[0])
190                       << endmsg;
191                 return 0;
192         }
193
194         savergb = rgb = (unsigned char*)art_alloc (h * w * 3);
195         
196         // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
197         for (t = 0; t < colors; ++t) {
198                 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
199                 vals[c] = val;
200         }
201         
202         // COLORMAP -> RGB CONVERSION
203         //    Get low 3 bytes from vals[]
204         //
205
206         const char *p;
207         for (y = h-1; y > 0; --y) {
208
209                 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 3) {
210                         val = vals[(int)*p++];
211                         *(rgb+2) = val & 0xff; val >>= 8;  // 2:B
212                         *(rgb+1) = val & 0xff; val >>= 8;  // 1:G
213                         *(rgb+0) = val & 0xff;             // 0:R
214                 }
215         }
216
217         return (savergb);
218 }
219
220 unsigned char*
221 xpm2rgba (const char** xpm, uint32_t& w, uint32_t& h)
222 {
223         static long vals[256], val;
224         uint32_t t, x, y, colors, cpp;
225         unsigned char c;
226         unsigned char *savergb, *rgb;
227         char transparent;
228
229         // PARSE HEADER
230
231         if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
232                 error << string_compose (_("bad XPM header %1"), xpm[0])
233                       << endmsg;
234                 return 0;
235         }
236
237         savergb = rgb = (unsigned char*)art_alloc (h * w * 4);
238         
239         // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
240
241         if (strstr (xpm[1], "None")) {
242                 sscanf (xpm[1], "%c", &transparent);
243                 t = 1;
244         } else {
245                 transparent = 0;
246                 t = 0;
247         }
248
249         for (; t < colors; ++t) {
250                 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
251                 vals[c] = val;
252         }
253         
254         // COLORMAP -> RGB CONVERSION
255         //    Get low 3 bytes from vals[]
256         //
257
258         const char *p;
259         for (y = h-1; y > 0; --y) {
260
261                 char alpha;
262
263                 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 4) {
264
265                         if (transparent && (*p++ == transparent)) {
266                                 alpha = 0;
267                                 val = 0;
268                         } else {
269                                 alpha = 255;
270                                 val = vals[(int)*p];
271                         }
272
273                         *(rgb+3) = alpha;                  // 3: alpha
274                         *(rgb+2) = val & 0xff; val >>= 8;  // 2:B
275                         *(rgb+1) = val & 0xff; val >>= 8;  // 1:G
276                         *(rgb+0) = val & 0xff;             // 0:R
277                 }
278         }
279
280         return (savergb);
281 }
282
283 ArdourCanvas::Points*
284 get_canvas_points (string who, uint32_t npoints)
285 {
286         // cerr << who << ": wants " << npoints << " canvas points" << endl;
287 #ifdef TRAP_EXCESSIVE_POINT_REQUESTS
288         if (npoints > (uint32_t) gdk_screen_width() + 4) {
289                 abort ();
290         }
291 #endif
292         return new ArdourCanvas::Points (npoints);
293 }
294
295 static int32_t 
296 int_from_hex (char hic, char loc) 
297 {
298         int hi;         /* hi byte */
299         int lo;         /* low byte */
300
301         hi = (int) hic;
302
303         if( ('0'<=hi) && (hi<='9') ) {
304                 hi -= '0';
305         } else if( ('a'<= hi) && (hi<= 'f') ) {
306                 hi -= ('a'-10);
307         } else if( ('A'<=hi) && (hi<='F') ) {
308                 hi -= ('A'-10);
309         }
310         
311         lo = (int) loc;
312         
313         if( ('0'<=lo) && (lo<='9') ) {
314                 lo -= '0';
315         } else if( ('a'<=lo) && (lo<='f') ) {
316                 lo -= ('a'-10);
317         } else if( ('A'<=lo) && (lo<='F') ) {
318                 lo -= ('A'-10);
319         }
320
321         return lo + (16 * hi);
322 }
323
324 void
325 url_decode (string& url)
326 {
327         string::iterator last;
328         string::iterator next;
329
330         for (string::iterator i = url.begin(); i != url.end(); ++i) {
331                 if ((*i) == '+') {
332                         *i = ' ';
333                 }
334         }
335
336         if (url.length() <= 3) {
337                 return;
338         }
339
340         last = url.end();
341
342         --last; /* points at last char */
343         --last; /* points at last char - 1 */
344
345         for (string::iterator i = url.begin(); i != last; ) {
346
347                 if (*i == '%') {
348
349                         next = i;
350
351                         url.erase (i);
352                         
353                         i = next;
354                         ++next;
355                         
356                         if (isxdigit (*i) && isxdigit (*next)) {
357                                 /* replace first digit with char */
358                                 *i = int_from_hex (*i,*next);
359                                 ++i; /* points at 2nd of 2 digits */
360                                 url.erase (i);
361                         }
362                 } else {
363                         ++i;
364                 }
365         }
366 }
367
368 Pango::FontDescription
369 get_font_for_style (string widgetname)
370 {
371         Gtk::Window window (WINDOW_TOPLEVEL);
372         Gtk::Label foobar;
373         Glib::RefPtr<Style> style;
374
375         window.add (foobar);
376         foobar.set_name (widgetname);
377         foobar.ensure_style();
378
379         style = foobar.get_style ();
380         return style->get_font();
381 }
382
383 gint
384 pane_handler (GdkEventButton* ev, Gtk::Paned* pane)
385 {
386         if (ev->window != Gtkmm2ext::get_paned_handle (*pane)) {
387                 return FALSE;
388         }
389
390         if (Keyboard::is_delete_event (ev)) {
391
392                 gint pos;
393                 gint cmp;
394                 
395                 pos = pane->get_position ();
396
397                 if (dynamic_cast<VPaned*>(pane)) {
398                         cmp = pane->get_height();
399                 } else {
400                         cmp = pane->get_width();
401                 }
402
403                 /* we have to use approximations here because we can't predict the
404                    exact position or sizes of the pane (themes, etc)
405                 */
406
407                 if (pos < 10 || abs (pos - cmp) < 10) {
408
409                         /* already collapsed: restore it (note that this is cast from a pointer value to int, which is tricky on 64bit */
410                         
411                         pane->set_position ((intptr_t) pane->get_data ("rpos"));
412
413                 } else {        
414
415                         int collapse_direction;
416
417                         /* store the current position */
418
419                         pane->set_data ("rpos", (gpointer) pos);
420
421                         /* collapse to show the relevant child in full */
422                         
423                         collapse_direction = (intptr_t) pane->get_data ("collapse-direction");
424
425                         if (collapse_direction) {
426                                 pane->set_position (1);
427                         } else {
428                                 if (dynamic_cast<VPaned*>(pane)) {
429                                         pane->set_position (pane->get_height());
430                                 } else {
431                                         pane->set_position (pane->get_width());
432                                 }
433                         }
434                 }
435
436                 return TRUE;
437         } 
438
439         return FALSE;
440 }
441 uint32_t
442 rgba_from_style (string style, uint32_t r, uint32_t g, uint32_t b, uint32_t a, string attr, int state, bool rgba)
443 {
444         /* In GTK+2, styles aren't set up correctly if the widget is not
445            attached to a toplevel window that has a screen pointer.
446         */
447
448         static Gtk::Window* window = 0;
449
450         if (window == 0) {
451                 window = new Window (WINDOW_TOPLEVEL);
452         }
453
454         Gtk::Label foo;
455         
456         window->add (foo);
457
458         foo.set_name (style);
459         foo.ensure_style ();
460         
461         GtkRcStyle* waverc = foo.get_style()->gobj()->rc_style;
462
463         if (waverc) {
464                 if (attr == "fg") {
465                         r = waverc->fg[state].red / 257;
466                         g = waverc->fg[state].green / 257;
467                         b = waverc->fg[state].blue / 257;
468                         /* what a hack ... "a" is for "active" */
469                         if (state == Gtk::STATE_NORMAL && rgba) {
470                                 a = waverc->fg[GTK_STATE_ACTIVE].red / 257;
471                         }
472                 } else if (attr == "bg") {
473                         r = g = b = 0;
474                         r = waverc->bg[state].red / 257;
475                         g = waverc->bg[state].green / 257;
476                         b = waverc->bg[state].blue / 257;
477                 } else if (attr == "base") {
478                         r = waverc->base[state].red / 257;
479                         g = waverc->base[state].green / 257;
480                         b = waverc->base[state].blue / 257;
481                 } else if (attr == "text") {
482                         r = waverc->text[state].red / 257;
483                         g = waverc->text[state].green / 257;
484                         b = waverc->text[state].blue / 257;
485                 }
486         } else {
487                 warning << string_compose (_("missing RGBA style for \"%1\""), style) << endl;
488         }
489
490         window->remove ();
491         
492         if (state == Gtk::STATE_NORMAL && rgba) {
493                 return (uint32_t) RGBA_TO_UINT(r,g,b,a);
494         } else {
495                 return (uint32_t) RGB_TO_UINT(r,g,b);
496         }
497 }
498
499 bool 
500 canvas_item_visible (ArdourCanvas::Item* item)
501 {
502         return (item->gobj()->object.flags & GNOME_CANVAS_ITEM_VISIBLE) ? true : false;
503 }
504
505 void
506 set_color (Gdk::Color& c, int rgb)
507 {
508         c.set_rgb((rgb >> 16)*256, ((rgb & 0xff00) >> 8)*256, (rgb & 0xff)*256);
509 }
510
511 bool
512 key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev)
513 {
514         GtkWindow* win = window.gobj();
515
516         /* This exists to allow us to override the way GTK handles
517            key events. The normal sequence is:
518
519            a) event is delivered to a GtkWindow
520            b) accelerators/mnemonics are activated
521            c) if (b) didn't handle the event, propagate to
522                the focus widget and/or focus chain
523
524            The problem with this is that if the accelerators include
525            keys without modifiers, such as the space bar or the 
526            letter "e", then pressing the key while typing into
527            a text entry widget results in the accelerator being
528            activated, instead of the desired letter appearing
529            in the text entry.
530
531            There is no good way of fixing this, but this
532            represents a compromise. The idea is that 
533            key events involving modifiers (not Shift)
534            get routed into the activation pathway first, then
535            get propagated to the focus widget if necessary.
536            
537            If the key event doesn't involve modifiers,
538            we deliver to the focus widget first, thus allowing
539            it to get "normal text" without interference
540            from acceleration.
541
542            Of course, this can also be problematic: if there
543            is a widget with focus, then it will swallow
544            all "normal text" accelerators.
545         */
546
547         if (ev->state & ~Gdk::SHIFT_MASK) {
548                 
549                 /* modifiers in effect, accelerate first */
550                 if (!gtk_window_activate_key (win, ev)) {
551                         return gtk_window_propagate_key_event (win, ev);
552                 } else {
553                         return true;
554                 } 
555         }
556         
557         /* no modifiers, propagate first */
558
559         if (!gtk_window_propagate_key_event (win, ev)) {
560                 return gtk_window_activate_key (win, ev);
561         } 
562
563
564         return true;
565 }
566
567 Glib::RefPtr<Gdk::Pixbuf>       
568 get_xpm (std::string name)
569 {
570         if (!xpm_map[name]) {
571                 xpm_map[name] = Gdk::Pixbuf::create_from_file (ARDOUR::find_data_file(name, "pixmaps"));
572         }
573                 
574         return (xpm_map[name]);
575 }