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