Merged with trunk R992.
[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 <fstream>
24 #include <sys/stat.h>
25 #include <libart_lgpl/art_misc.h>
26 #include <gtkmm/window.h>
27 #include <gtkmm/combo.h>
28 #include <gtkmm/label.h>
29 #include <gtkmm/paned.h>
30 #include <gtk/gtkpaned.h>
31
32 #include <gtkmm2ext/utils.h>
33 #include <ardour/ardour.h>
34
35 #include "ardour_ui.h"
36 #include "keyboard.h"
37 #include "utils.h"
38 #include "i18n.h"
39 #include "rgb_macros.h"
40 #include "canvas_impl.h"
41
42 using namespace std;
43 using namespace Gtk;
44 using namespace sigc;
45 using namespace Glib;
46 using namespace PBD;
47
48 ustring
49 fit_to_pixels (const ustring& str, int pixel_width, Pango::FontDescription& font, int& actual_width)
50 {
51         Label foo;
52         Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout ("");
53         
54         layout->set_font_description (font);
55
56         actual_width = 0;
57
58         ustring ustr = str;
59         ustring::iterator last = ustr.end();
60         --last; /* now points at final entry */
61
62         while (!ustr.empty()) {
63
64                 layout->set_text (ustr);
65
66                 int width, height;
67                 Gtkmm2ext::get_ink_pixel_size (layout, width, height);
68
69                 if (width < pixel_width) {
70                         actual_width = width;
71                         break;
72                 }
73                 
74                 ustr.erase (last--);
75         }
76
77         return ustr;
78 }
79
80 gint
81 just_hide_it (GdkEventAny *ev, Gtk::Window *win)
82 {
83         win->hide_all ();
84         return TRUE;
85 }
86
87 /* xpm2rgb copied from nixieclock, which bore the legend:
88
89     nixieclock - a nixie desktop timepiece
90     Copyright (C) 2000 Greg Ercolano, erco@3dsite.com
91
92     and was released under the GPL.
93 */
94
95 unsigned char*
96 xpm2rgb (const char** xpm, uint32_t& w, uint32_t& h)
97 {
98         static long vals[256], val;
99         uint32_t t, x, y, colors, cpp;
100         unsigned char c;
101         unsigned char *savergb, *rgb;
102         
103         // PARSE HEADER
104         
105         if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
106                 error << string_compose (_("bad XPM header %1"), xpm[0])
107                       << endmsg;
108                 return 0;
109         }
110
111         savergb = rgb = (unsigned char*)art_alloc (h * w * 3);
112         
113         // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
114         for (t = 0; t < colors; ++t) {
115                 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
116                 vals[c] = val;
117         }
118         
119         // COLORMAP -> RGB CONVERSION
120         //    Get low 3 bytes from vals[]
121         //
122
123         const char *p;
124         for (y = h-1; y > 0; --y) {
125
126                 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 3) {
127                         val = vals[(int)*p++];
128                         *(rgb+2) = val & 0xff; val >>= 8;  // 2:B
129                         *(rgb+1) = val & 0xff; val >>= 8;  // 1:G
130                         *(rgb+0) = val & 0xff;             // 0:R
131                 }
132         }
133
134         return (savergb);
135 }
136
137 unsigned char*
138 xpm2rgba (const char** xpm, uint32_t& w, uint32_t& h)
139 {
140         static long vals[256], val;
141         uint32_t t, x, y, colors, cpp;
142         unsigned char c;
143         unsigned char *savergb, *rgb;
144         char transparent;
145
146         // PARSE HEADER
147
148         if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
149                 error << string_compose (_("bad XPM header %1"), xpm[0])
150                       << endmsg;
151                 return 0;
152         }
153
154         savergb = rgb = (unsigned char*)art_alloc (h * w * 4);
155         
156         // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
157
158         if (strstr (xpm[1], "None")) {
159                 sscanf (xpm[1], "%c", &transparent);
160                 t = 1;
161         } else {
162                 transparent = 0;
163                 t = 0;
164         }
165
166         for (; t < colors; ++t) {
167                 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
168                 vals[c] = val;
169         }
170         
171         // COLORMAP -> RGB CONVERSION
172         //    Get low 3 bytes from vals[]
173         //
174
175         const char *p;
176         for (y = h-1; y > 0; --y) {
177
178                 char alpha;
179
180                 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 4) {
181
182                         if (transparent && (*p++ == transparent)) {
183                                 alpha = 0;
184                                 val = 0;
185                         } else {
186                                 alpha = 255;
187                                 val = vals[(int)*p];
188                         }
189
190                         *(rgb+3) = alpha;                  // 3: alpha
191                         *(rgb+2) = val & 0xff; val >>= 8;  // 2:B
192                         *(rgb+1) = val & 0xff; val >>= 8;  // 1:G
193                         *(rgb+0) = val & 0xff;             // 0:R
194                 }
195         }
196
197         return (savergb);
198 }
199
200 ArdourCanvas::Points*
201 get_canvas_points (string who, uint32_t npoints)
202 {
203         // cerr << who << ": wants " << npoints << " canvas points" << endl;
204 #ifdef TRAP_EXCESSIVE_POINT_REQUESTS
205         if (npoints > (uint32_t) gdk_screen_width() + 4) {
206                 abort ();
207         }
208 #endif
209         return new ArdourCanvas::Points (npoints);
210 }
211
212 Pango::FontDescription
213 get_font_for_style (string widgetname)
214 {
215         Gtk::Window window (WINDOW_TOPLEVEL);
216         Gtk::Label foobar;
217         Glib::RefPtr<Gtk::Style> style;
218
219         window.add (foobar);
220         foobar.set_name (widgetname);
221         foobar.ensure_style();
222
223         style = foobar.get_style ();
224         return style->get_font();
225 }
226
227 gint
228 pane_handler (GdkEventButton* ev, Gtk::Paned* pane)
229 {
230         if (ev->window != Gtkmm2ext::get_paned_handle (*pane)) {
231                 return FALSE;
232         }
233
234         if (Keyboard::is_delete_event (ev)) {
235
236                 gint pos;
237                 gint cmp;
238                 
239                 pos = pane->get_position ();
240
241                 if (dynamic_cast<VPaned*>(pane)) {
242                         cmp = pane->get_height();
243                 } else {
244                         cmp = pane->get_width();
245                 }
246
247                 /* we have to use approximations here because we can't predict the
248                    exact position or sizes of the pane (themes, etc)
249                 */
250
251                 if (pos < 10 || abs (pos - cmp) < 10) {
252
253                         /* already collapsed: restore it (note that this is cast from a pointer value to int, which is tricky on 64bit */
254                         
255                         pane->set_position ((intptr_t) pane->get_data ("rpos"));
256
257                 } else {        
258
259                         int collapse_direction;
260
261                         /* store the current position */
262
263                         pane->set_data ("rpos", (gpointer) pos);
264
265                         /* collapse to show the relevant child in full */
266                         
267                         collapse_direction = (intptr_t) pane->get_data ("collapse-direction");
268
269                         if (collapse_direction) {
270                                 pane->set_position (1);
271                         } else {
272                                 if (dynamic_cast<VPaned*>(pane)) {
273                                         pane->set_position (pane->get_height());
274                                 } else {
275                                         pane->set_position (pane->get_width());
276                                 }
277                         }
278                 }
279
280                 return TRUE;
281         } 
282
283         return FALSE;
284 }
285 uint32_t
286 rgba_from_style (string style, uint32_t r, uint32_t g, uint32_t b, uint32_t a, string attr, int state, bool rgba)
287 {
288         /* In GTK+2, styles aren't set up correctly if the widget is not
289            attached to a toplevel window that has a screen pointer.
290         */
291
292         static Gtk::Window* window = 0;
293
294         if (window == 0) {
295                 window = new Window (WINDOW_TOPLEVEL);
296         }
297
298         Gtk::Label foo;
299         
300         window->add (foo);
301
302         foo.set_name (style);
303         foo.ensure_style ();
304         
305         GtkRcStyle* waverc = foo.get_style()->gobj()->rc_style;
306
307         if (waverc) {
308                 if (attr == "fg") {
309                         r = waverc->fg[state].red / 257;
310                         g = waverc->fg[state].green / 257;
311                         b = waverc->fg[state].blue / 257;
312                         /* what a hack ... "a" is for "active" */
313                         if (state == Gtk::STATE_NORMAL && rgba) {
314                                 a = waverc->fg[GTK_STATE_ACTIVE].red / 257;
315                         }
316                 } else if (attr == "bg") {
317                         r = g = b = 0;
318                         r = waverc->bg[state].red / 257;
319                         g = waverc->bg[state].green / 257;
320                         b = waverc->bg[state].blue / 257;
321                 } else if (attr == "base") {
322                         r = waverc->base[state].red / 257;
323                         g = waverc->base[state].green / 257;
324                         b = waverc->base[state].blue / 257;
325                 } else if (attr == "text") {
326                         r = waverc->text[state].red / 257;
327                         g = waverc->text[state].green / 257;
328                         b = waverc->text[state].blue / 257;
329                 }
330         } else {
331                 warning << string_compose (_("missing RGBA style for \"%1\""), style) << endl;
332         }
333
334         window->remove ();
335         
336         if (state == Gtk::STATE_NORMAL && rgba) {
337                 return (uint32_t) RGBA_TO_UINT(r,g,b,a);
338         } else {
339                 return (uint32_t) RGB_TO_UINT(r,g,b);
340         }
341 }
342
343 bool 
344 canvas_item_visible (ArdourCanvas::Item* item)
345 {
346         return (item->gobj()->object.flags & GNOME_CANVAS_ITEM_VISIBLE) ? true : false;
347 }
348
349 void
350 set_color (Gdk::Color& c, int rgb)
351 {
352         c.set_rgb((rgb >> 16)*256, ((rgb & 0xff00) >> 8)*256, (rgb & 0xff)*256);
353 }
354
355 bool
356 key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev)
357 {
358         GtkWindow* win = window.gobj();
359         GtkWidget* focus = gtk_window_get_focus (win);
360         bool special_handling_of_unmodified_accelerators = false;
361
362         if (focus) {
363                 if (GTK_IS_ENTRY(focus)) {
364                         special_handling_of_unmodified_accelerators = true;
365                 }
366         } 
367
368         /* This exists to allow us to override the way GTK handles
369            key events. The normal sequence is:
370
371            a) event is delivered to a GtkWindow
372            b) accelerators/mnemonics are activated
373            c) if (b) didn't handle the event, propagate to
374                the focus widget and/or focus chain
375
376            The problem with this is that if the accelerators include
377            keys without modifiers, such as the space bar or the 
378            letter "e", then pressing the key while typing into
379            a text entry widget results in the accelerator being
380            activated, instead of the desired letter appearing
381            in the text entry.
382
383            There is no good way of fixing this, but this
384            represents a compromise. The idea is that 
385            key events involving modifiers (not Shift)
386            get routed into the activation pathway first, then
387            get propagated to the focus widget if necessary.
388            
389            If the key event doesn't involve modifiers,
390            we deliver to the focus widget first, thus allowing
391            it to get "normal text" without interference
392            from acceleration.
393
394            Of course, this can also be problematic: if there
395            is a widget with focus, then it will swallow
396            all "normal text" accelerators.
397         */
398
399
400         if (!special_handling_of_unmodified_accelerators) {
401
402                 /* pretend that certain key events that GTK does not allow
403                    to be used as accelerators are actually something that
404                    it does allow.
405                 */
406
407                 int ret = false;
408
409                 switch (ev->keyval) {
410                 case GDK_Up:
411                         ret = gtk_accel_groups_activate(G_OBJECT(win), GDK_uparrow, GdkModifierType(ev->state));
412                         break;
413
414                 case GDK_Down:
415                         ret = gtk_accel_groups_activate(G_OBJECT(win), GDK_downarrow, GdkModifierType(ev->state));
416                         break;
417
418                 case GDK_Right:
419                         ret = gtk_accel_groups_activate(G_OBJECT(win), GDK_rightarrow, GdkModifierType(ev->state));
420                         break;
421
422                 case GDK_Left:
423                         ret = gtk_accel_groups_activate(G_OBJECT(win), GDK_leftarrow, GdkModifierType(ev->state));
424                         break;
425
426                 default:
427                         break;
428                 }
429
430                 if (ret) {
431                         return true;
432                 }
433         }
434                 
435         if (!special_handling_of_unmodified_accelerators ||
436             ev->state & (Gdk::MOD1_MASK|
437                          Gdk::MOD3_MASK|
438                          Gdk::MOD4_MASK|
439                          Gdk::MOD5_MASK|
440                          Gdk::CONTROL_MASK)) {
441
442                 /* no special handling or modifiers in effect: accelerate first */
443
444                 if (!gtk_window_activate_key (win, ev)) {
445                         return gtk_window_propagate_key_event (win, ev);
446                 } else {
447                         return true;
448                 } 
449         }
450         
451         /* no modifiers, propagate first */
452         
453         if (!gtk_window_propagate_key_event (win, ev)) {
454                 return gtk_window_activate_key (win, ev);
455         } 
456
457
458         return true;
459 }
460
461 Glib::RefPtr<Gdk::Pixbuf>       
462 get_xpm (std::string name)
463 {
464         if (!xpm_map[name]) {
465                 xpm_map[name] = Gdk::Pixbuf::create_from_file (ARDOUR::find_data_file(name, "pixmaps"));
466         }
467                 
468         return (xpm_map[name]);
469 }
470
471 Glib::RefPtr<Gdk::Pixbuf>       
472 get_icon (const char* cname)
473 {
474         string name = cname;
475         name += X_(".png");
476
477         string path = ARDOUR::find_data_file (name, "icons");
478
479         if (path.empty()) {
480                 fatal << string_compose (_("cannot find icon image for %1"), name) << endmsg;
481                 /*NOTREACHED*/
482         }
483
484         return Gdk::Pixbuf::create_from_file (path);
485 }
486
487 string
488 longest (vector<string>& strings)
489 {
490         if (strings.empty()) {
491                 return string ("");
492         }
493
494         vector<string>::iterator longest = strings.begin();
495         string::size_type longest_length = (*longest).length();
496         
497         vector<string>::iterator i = longest;
498         ++i;
499
500         while (i != strings.end()) {
501                 
502                 string::size_type len = (*i).length();
503                 
504                 if (len > longest_length) {
505                         longest = i;
506                         longest_length = len;
507                 } 
508                 
509                 ++i;
510         }
511         
512         return *longest;
513 }