d574ece427f4827d807671e22e0ad70b67ebe29c
[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) || Keyboard::some_magic_widget_has_focus()) {
351                         special_handling_of_unmodified_accelerators = true;
352                 } 
353         } 
354
355 #ifdef DEBUG_ACCELERATOR_HANDLING
356         if (debug) {
357                 cerr << "Win = " << win << " Key event: code = " << ev->keyval << " state = " << hex << ev->state << dec << " special handling ? " 
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 << "\tpropagate, then activate\n";
461         }
462 #endif
463         if (!gtk_window_propagate_key_event (win, ev)) {
464 #ifdef DEBUG_ACCELERATOR_HANDLING
465                 if (debug) {
466                         cerr << "\tpropagation didn't handle, so activate\n";
467                 }
468 #endif
469                 return gtk_window_activate_key (win, ev);
470         } else {
471 #ifdef DEBUG_ACCELERATOR_HANDLING
472                 if (debug) {
473                         cerr << "\thandled by propagate\n";
474                 }
475 #endif
476                 return true;
477         }
478
479 #ifdef DEBUG_ACCELERATOR_HANDLING
480         if (debug) {
481                 cerr << "\tnot handled\n";
482         }
483 #endif
484         return true;
485 }
486
487 Glib::RefPtr<Gdk::Pixbuf>       
488 get_xpm (std::string name)
489 {
490         if (!xpm_map[name]) {
491                 xpm_map[name] = Gdk::Pixbuf::create_from_file (ARDOUR::find_data_file(name, "pixmaps"));
492         }
493                 
494         return (xpm_map[name]);
495 }
496
497 Glib::RefPtr<Gdk::Pixbuf>       
498 get_icon (const char* cname)
499 {
500         string name = cname;
501         name += X_(".png");
502
503         string path = ARDOUR::find_data_file (name, "icons");
504
505         if (path.empty()) {
506                 fatal << string_compose (_("cannot find icon image for %1"), name) << endmsg;
507                 /*NOTREACHED*/
508         }
509
510         return Gdk::Pixbuf::create_from_file (path);
511 }
512
513 string
514 longest (vector<string>& strings)
515 {
516         if (strings.empty()) {
517                 return string ("");
518         }
519
520         vector<string>::iterator longest = strings.begin();
521         string::size_type longest_length = (*longest).length();
522         
523         vector<string>::iterator i = longest;
524         ++i;
525
526         while (i != strings.end()) {
527                 
528                 string::size_type len = (*i).length();
529                 
530                 if (len > longest_length) {
531                         longest = i;
532                         longest_length = len;
533                 } 
534                 
535                 ++i;
536         }
537         
538         return *longest;
539 }
540
541 bool
542 key_is_legal_for_numeric_entry (guint keyval)
543 {
544         switch (keyval) {
545         case GDK_minus:
546         case GDK_plus:
547         case GDK_period:
548         case GDK_comma:
549         case GDK_0:
550         case GDK_1:
551         case GDK_2:
552         case GDK_3:
553         case GDK_4:
554         case GDK_5:
555         case GDK_6:
556         case GDK_7:
557         case GDK_8:
558         case GDK_9:
559         case GDK_KP_Add:
560         case GDK_KP_Subtract:
561         case GDK_KP_Decimal:
562         case GDK_KP_0:
563         case GDK_KP_1:
564         case GDK_KP_2:
565         case GDK_KP_3:
566         case GDK_KP_4:
567         case GDK_KP_5:
568         case GDK_KP_6:
569         case GDK_KP_7:
570         case GDK_KP_8:
571         case GDK_KP_9:
572         case GDK_Return:
573         case GDK_BackSpace:
574         case GDK_Delete:
575         case GDK_KP_Enter:
576         case GDK_Home:
577         case GDK_End:
578         case GDK_Left:
579         case GDK_Right:
580                 return true;
581                 
582         default:
583                 break;
584         }
585
586         return false;
587 }
588
589
590