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