303c4a4c53c8114d3d8bbf797a073a8ce1ba35be
[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 #undef  DEBUG_ACCELERATOR_HANDLING
363 #ifdef  DEBUG_ACCELERATOR_HANDLING
364         bool debug = (getenv ("ARDOUR_DEBUG_ACCELERATOR_HANDLING") != 0);
365 #endif
366
367         if (focus) {
368                 if (GTK_IS_ENTRY(focus)) {
369                         special_handling_of_unmodified_accelerators = true;
370                 } 
371         } 
372
373 #ifdef DEBUG_ACCELERATOR_HANDLING
374         if (debug) {
375                 cerr << "Key event: code = " << ev->keyval << " state = " << hex << ev->state << dec << " focus is an entry ? " 
376                      << special_handling_of_unmodified_accelerators
377                      << endl;
378         }
379 #endif
380
381         /* This exists to allow us to override the way GTK handles
382            key events. The normal sequence is:
383
384            a) event is delivered to a GtkWindow
385            b) accelerators/mnemonics are activated
386            c) if (b) didn't handle the event, propagate to
387                the focus widget and/or focus chain
388
389            The problem with this is that if the accelerators include
390            keys without modifiers, such as the space bar or the 
391            letter "e", then pressing the key while typing into
392            a text entry widget results in the accelerator being
393            activated, instead of the desired letter appearing
394            in the text entry.
395
396            There is no good way of fixing this, but this
397            represents a compromise. The idea is that 
398            key events involving modifiers (not Shift)
399            get routed into the activation pathway first, then
400            get propagated to the focus widget if necessary.
401            
402            If the key event doesn't involve modifiers,
403            we deliver to the focus widget first, thus allowing
404            it to get "normal text" without interference
405            from acceleration.
406
407            Of course, this can also be problematic: if there
408            is a widget with focus, then it will swallow
409            all "normal text" accelerators.
410         */
411
412
413         if (!special_handling_of_unmodified_accelerators) {
414
415                 /* pretend that certain key events that GTK does not allow
416                    to be used as accelerators are actually something that
417                    it does allow.
418                 */
419
420                 int ret = false;
421
422                 switch (ev->keyval) {
423                 case GDK_Up:
424                         ret = gtk_accel_groups_activate(G_OBJECT(win), GDK_uparrow, GdkModifierType(ev->state));
425                         break;
426
427                 case GDK_Down:
428                         ret = gtk_accel_groups_activate(G_OBJECT(win), GDK_downarrow, GdkModifierType(ev->state));
429                         break;
430
431                 case GDK_Right:
432                         ret = gtk_accel_groups_activate(G_OBJECT(win), GDK_rightarrow, GdkModifierType(ev->state));
433                         break;
434
435                 case GDK_Left:
436                         ret = gtk_accel_groups_activate(G_OBJECT(win), GDK_leftarrow, GdkModifierType(ev->state));
437                         break;
438
439                 default:
440                         break;
441                 }
442
443                 if (ret) {
444                         return true;
445                 }
446         }
447                 
448         if (!special_handling_of_unmodified_accelerators ||
449             ev->state & (Gdk::MOD1_MASK|
450                          Gdk::MOD3_MASK|
451                          Gdk::MOD4_MASK|
452                          Gdk::MOD5_MASK|
453                          Gdk::CONTROL_MASK)) {
454
455                 /* no special handling or modifiers in effect: accelerate first */
456
457 #ifdef DEBUG_ACCELERATOR_HANDLING
458                 if (debug) {
459                         cerr << "\tactivate, then propagate\n";
460                 }
461 #endif
462                 if (!gtk_window_activate_key (win, ev)) {
463                         return gtk_window_propagate_key_event (win, ev);
464                 } else {
465 #ifdef DEBUG_ACCELERATOR_HANDLING
466                 if (debug) {
467                         cerr << "\tnot handled\n";
468                 }
469 #endif
470                         return true;
471                 } 
472         }
473         
474         /* no modifiers, propagate first */
475         
476 #ifdef DEBUG_ACCELERATOR_HANDLING
477                 if (debug) {
478                         cerr << "\tactivate, then propagate\n";
479                 }
480 #endif
481         if (!gtk_window_propagate_key_event (win, ev)) {
482                 return gtk_window_activate_key (win, ev);
483         } 
484
485
486 #ifdef DEBUG_ACCELERATOR_HANDLING
487         if (debug) {
488                 cerr << "\tnot handled\n";
489         }
490 #endif
491         return true;
492 }
493
494 Glib::RefPtr<Gdk::Pixbuf>       
495 get_xpm (std::string name)
496 {
497         if (!xpm_map[name]) {
498                 xpm_map[name] = Gdk::Pixbuf::create_from_file (ARDOUR::find_data_file(name, "pixmaps"));
499         }
500                 
501         return (xpm_map[name]);
502 }
503
504 Glib::RefPtr<Gdk::Pixbuf>       
505 get_icon (const char* cname)
506 {
507         string name = cname;
508         name += X_(".png");
509
510         string path = ARDOUR::find_data_file (name, "icons");
511
512         if (path.empty()) {
513                 fatal << string_compose (_("cannot find icon image for %1"), name) << endmsg;
514                 /*NOTREACHED*/
515         }
516
517         return Gdk::Pixbuf::create_from_file (path);
518 }
519
520 string
521 longest (vector<string>& strings)
522 {
523         if (strings.empty()) {
524                 return string ("");
525         }
526
527         vector<string>::iterator longest = strings.begin();
528         string::size_type longest_length = (*longest).length();
529         
530         vector<string>::iterator i = longest;
531         ++i;
532
533         while (i != strings.end()) {
534                 
535                 string::size_type len = (*i).length();
536                 
537                 if (len > longest_length) {
538                         longest = i;
539                         longest_length = len;
540                 } 
541                 
542                 ++i;
543         }
544         
545         return *longest;
546 }
547
548 bool
549 key_is_legal_for_numeric_entry (guint keyval)
550 {
551         switch (keyval) {
552         case GDK_minus:
553         case GDK_plus:
554         case GDK_period:
555         case GDK_comma:
556         case GDK_0:
557         case GDK_1:
558         case GDK_2:
559         case GDK_3:
560         case GDK_4:
561         case GDK_5:
562         case GDK_6:
563         case GDK_7:
564         case GDK_8:
565         case GDK_9:
566         case GDK_KP_Add:
567         case GDK_KP_Subtract:
568         case GDK_KP_Decimal:
569         case GDK_KP_0:
570         case GDK_KP_1:
571         case GDK_KP_2:
572         case GDK_KP_3:
573         case GDK_KP_4:
574         case GDK_KP_5:
575         case GDK_KP_6:
576         case GDK_KP_7:
577         case GDK_KP_8:
578         case GDK_KP_9:
579         case GDK_Return:
580         case GDK_BackSpace:
581         case GDK_Delete:
582         case GDK_KP_Enter:
583         case GDK_Home:
584         case GDK_End:
585         case GDK_Left:
586         case GDK_Right:
587                 return true;
588                 
589         default:
590                 break;
591         }
592
593         return false;
594 }
595