389d43d2f234061642630ed7fde3c425765d0e06
[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 uint32_t
228 rgba_from_style (string style, uint32_t r, uint32_t g, uint32_t b, uint32_t a, string attr, int state, bool rgba)
229 {
230         /* In GTK+2, styles aren't set up correctly if the widget is not
231            attached to a toplevel window that has a screen pointer.
232         */
233
234         static Gtk::Window* window = 0;
235
236         if (window == 0) {
237                 window = new Window (WINDOW_TOPLEVEL);
238         }
239
240         Gtk::Label foo;
241         
242         window->add (foo);
243
244         foo.set_name (style);
245         foo.ensure_style ();
246         
247         GtkRcStyle* waverc = foo.get_style()->gobj()->rc_style;
248
249         if (waverc) {
250                 if (attr == "fg") {
251                         r = waverc->fg[state].red / 257;
252                         g = waverc->fg[state].green / 257;
253                         b = waverc->fg[state].blue / 257;
254                         /* what a hack ... "a" is for "active" */
255                         if (state == Gtk::STATE_NORMAL && rgba) {
256                                 a = waverc->fg[GTK_STATE_ACTIVE].red / 257;
257                         }
258                 } else if (attr == "bg") {
259                         r = g = b = 0;
260                         r = waverc->bg[state].red / 257;
261                         g = waverc->bg[state].green / 257;
262                         b = waverc->bg[state].blue / 257;
263                 } else if (attr == "base") {
264                         r = waverc->base[state].red / 257;
265                         g = waverc->base[state].green / 257;
266                         b = waverc->base[state].blue / 257;
267                 } else if (attr == "text") {
268                         r = waverc->text[state].red / 257;
269                         g = waverc->text[state].green / 257;
270                         b = waverc->text[state].blue / 257;
271                 }
272         } else {
273                 warning << string_compose (_("missing RGBA style for \"%1\""), style) << endl;
274         }
275
276         window->remove ();
277         
278         if (state == Gtk::STATE_NORMAL && rgba) {
279                 return (uint32_t) RGBA_TO_UINT(r,g,b,a);
280         } else {
281                 return (uint32_t) RGB_TO_UINT(r,g,b);
282         }
283 }
284
285 bool 
286 canvas_item_visible (ArdourCanvas::Item* item)
287 {
288         return (item->gobj()->object.flags & GNOME_CANVAS_ITEM_VISIBLE) ? true : false;
289 }
290
291 void
292 set_color (Gdk::Color& c, int rgb)
293 {
294         c.set_rgb((rgb >> 16)*256, ((rgb & 0xff00) >> 8)*256, (rgb & 0xff)*256);
295 }
296
297 bool
298 key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev)
299 {
300         GtkWindow* win = window.gobj();
301         GtkWidget* focus = gtk_window_get_focus (win);
302         bool special_handling_of_unmodified_accelerators = false;
303
304 #undef  DEBUG_ACCELERATOR_HANDLING
305 #ifdef  DEBUG_ACCELERATOR_HANDLING
306         bool debug = (getenv ("ARDOUR_DEBUG_ACCELERATOR_HANDLING") != 0);
307 #endif
308
309         if (focus) {
310                 if (GTK_IS_ENTRY(focus)) {
311                         special_handling_of_unmodified_accelerators = true;
312                 } 
313         } 
314
315 #ifdef DEBUG_ACCELERATOR_HANDLING
316         if (debug) {
317                 cerr << "Key event: code = " << ev->keyval << " state = " << hex << ev->state << dec << " focus is an entry ? " 
318                      << special_handling_of_unmodified_accelerators
319                      << endl;
320         }
321 #endif
322
323         /* This exists to allow us to override the way GTK handles
324            key events. The normal sequence is:
325
326            a) event is delivered to a GtkWindow
327            b) accelerators/mnemonics are activated
328            c) if (b) didn't handle the event, propagate to
329                the focus widget and/or focus chain
330
331            The problem with this is that if the accelerators include
332            keys without modifiers, such as the space bar or the 
333            letter "e", then pressing the key while typing into
334            a text entry widget results in the accelerator being
335            activated, instead of the desired letter appearing
336            in the text entry.
337
338            There is no good way of fixing this, but this
339            represents a compromise. The idea is that 
340            key events involving modifiers (not Shift)
341            get routed into the activation pathway first, then
342            get propagated to the focus widget if necessary.
343            
344            If the key event doesn't involve modifiers,
345            we deliver to the focus widget first, thus allowing
346            it to get "normal text" without interference
347            from acceleration.
348
349            Of course, this can also be problematic: if there
350            is a widget with focus, then it will swallow
351            all "normal text" accelerators.
352         */
353
354
355         if (!special_handling_of_unmodified_accelerators) {
356
357                 /* pretend that certain key events that GTK does not allow
358                    to be used as accelerators are actually something that
359                    it does allow.
360                 */
361
362                 int ret = false;
363
364                 switch (ev->keyval) {
365                 case GDK_Up:
366                         ret = gtk_accel_groups_activate(G_OBJECT(win), GDK_uparrow, GdkModifierType(ev->state));
367                         break;
368
369                 case GDK_Down:
370                         ret = gtk_accel_groups_activate(G_OBJECT(win), GDK_downarrow, GdkModifierType(ev->state));
371                         break;
372
373                 case GDK_Right:
374                         ret = gtk_accel_groups_activate(G_OBJECT(win), GDK_rightarrow, GdkModifierType(ev->state));
375                         break;
376
377                 case GDK_Left:
378                         ret = gtk_accel_groups_activate(G_OBJECT(win), GDK_leftarrow, GdkModifierType(ev->state));
379                         break;
380
381                 default:
382                         break;
383                 }
384
385                 if (ret) {
386                         return true;
387                 }
388         }
389                 
390         if (!special_handling_of_unmodified_accelerators ||
391             ev->state & (Gdk::MOD1_MASK|
392                          Gdk::MOD3_MASK|
393                          Gdk::MOD4_MASK|
394                          Gdk::MOD5_MASK|
395                          Gdk::CONTROL_MASK)) {
396
397                 /* no special handling or modifiers in effect: accelerate first */
398
399 #ifdef DEBUG_ACCELERATOR_HANDLING
400                 if (debug) {
401                         cerr << "\tactivate, then propagate\n";
402                 }
403 #endif
404                 if (!gtk_window_activate_key (win, ev)) {
405                         return gtk_window_propagate_key_event (win, ev);
406                 } else {
407 #ifdef DEBUG_ACCELERATOR_HANDLING
408                 if (debug) {
409                         cerr << "\tnot handled\n";
410                 }
411 #endif
412                         return true;
413                 } 
414         }
415         
416         /* no modifiers, propagate first */
417         
418 #ifdef DEBUG_ACCELERATOR_HANDLING
419                 if (debug) {
420                         cerr << "\tactivate, then propagate\n";
421                 }
422 #endif
423         if (!gtk_window_propagate_key_event (win, ev)) {
424                 return gtk_window_activate_key (win, ev);
425         } 
426
427
428 #ifdef DEBUG_ACCELERATOR_HANDLING
429         if (debug) {
430                 cerr << "\tnot handled\n";
431         }
432 #endif
433         return true;
434 }
435
436 Glib::RefPtr<Gdk::Pixbuf>       
437 get_xpm (std::string name)
438 {
439         if (!xpm_map[name]) {
440                 xpm_map[name] = Gdk::Pixbuf::create_from_file (ARDOUR::find_data_file(name, "pixmaps"));
441         }
442                 
443         return (xpm_map[name]);
444 }
445
446 Glib::RefPtr<Gdk::Pixbuf>       
447 get_icon (const char* cname)
448 {
449         string name = cname;
450         name += X_(".png");
451
452         string path = ARDOUR::find_data_file (name, "icons");
453
454         if (path.empty()) {
455                 fatal << string_compose (_("cannot find icon image for %1"), name) << endmsg;
456                 /*NOTREACHED*/
457         }
458
459         return Gdk::Pixbuf::create_from_file (path);
460 }
461
462 string
463 longest (vector<string>& strings)
464 {
465         if (strings.empty()) {
466                 return string ("");
467         }
468
469         vector<string>::iterator longest = strings.begin();
470         string::size_type longest_length = (*longest).length();
471         
472         vector<string>::iterator i = longest;
473         ++i;
474
475         while (i != strings.end()) {
476                 
477                 string::size_type len = (*i).length();
478                 
479                 if (len > longest_length) {
480                         longest = i;
481                         longest_length = len;
482                 } 
483                 
484                 ++i;
485         }
486         
487         return *longest;
488 }
489
490 bool
491 key_is_legal_for_numeric_entry (guint keyval)
492 {
493         switch (keyval) {
494         case GDK_minus:
495         case GDK_plus:
496         case GDK_period:
497         case GDK_comma:
498         case GDK_0:
499         case GDK_1:
500         case GDK_2:
501         case GDK_3:
502         case GDK_4:
503         case GDK_5:
504         case GDK_6:
505         case GDK_7:
506         case GDK_8:
507         case GDK_9:
508         case GDK_KP_Add:
509         case GDK_KP_Subtract:
510         case GDK_KP_Decimal:
511         case GDK_KP_0:
512         case GDK_KP_1:
513         case GDK_KP_2:
514         case GDK_KP_3:
515         case GDK_KP_4:
516         case GDK_KP_5:
517         case GDK_KP_6:
518         case GDK_KP_7:
519         case GDK_KP_8:
520         case GDK_KP_9:
521         case GDK_Return:
522         case GDK_BackSpace:
523         case GDK_Delete:
524         case GDK_KP_Enter:
525         case GDK_Home:
526         case GDK_End:
527         case GDK_Left:
528         case GDK_Right:
529                 return true;
530                 
531         default:
532                 break;
533         }
534
535         return false;
536 }
537