396940284b3c273c04a71c592bda634ed328e6bc
[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 ustring
48 fit_to_pixels (const ustring& str, int pixel_width, Pango::FontDescription& font, int& actual_width, bool with_ellipses)
49 {
50         Label foo;
51         Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout ("");
52         ustring::size_type shorter_by = 0;
53         ustring txt;
54
55         layout->set_font_description (font);
56
57         actual_width = 0;
58
59         ustring ustr = str;
60         ustring::iterator last = ustr.end();
61         --last; /* now points at final entry */
62
63         txt = ustr;
64
65         while (!ustr.empty()) {
66
67                 layout->set_text (txt);
68
69                 int width, height;
70                 Gtkmm2ext::get_ink_pixel_size (layout, width, height);
71
72                 if (width < pixel_width) {
73                         actual_width = width;
74                         break;
75                 }
76                 
77                 ustr.erase (last--);
78                 shorter_by++;
79
80                 if (with_ellipses && shorter_by > 3) {
81                         txt = ustr;
82                         txt += "...";
83                 } else {
84                         txt = ustr;
85                 }
86         }
87
88         return txt;
89 }
90
91 gint
92 just_hide_it (GdkEventAny *ev, Gtk::Window *win)
93 {
94         win->hide_all ();
95         return TRUE;
96 }
97
98 /* xpm2rgb copied from nixieclock, which bore the legend:
99
100     nixieclock - a nixie desktop timepiece
101     Copyright (C) 2000 Greg Ercolano, erco@3dsite.com
102
103     and was released under the GPL.
104 */
105
106 unsigned char*
107 xpm2rgb (const char** xpm, uint32_t& w, uint32_t& h)
108 {
109         static long vals[256], val;
110         uint32_t t, x, y, colors, cpp;
111         unsigned char c;
112         unsigned char *savergb, *rgb;
113         
114         // PARSE HEADER
115         
116         if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
117                 error << string_compose (_("bad XPM header %1"), xpm[0])
118                       << endmsg;
119                 return 0;
120         }
121
122         savergb = rgb = (unsigned char*) malloc (h * w * 3);
123         
124         // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
125         for (t = 0; t < colors; ++t) {
126                 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
127                 vals[c] = val;
128         }
129         
130         // COLORMAP -> RGB CONVERSION
131         //    Get low 3 bytes from vals[]
132         //
133
134         const char *p;
135         for (y = h-1; y > 0; --y) {
136
137                 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 3) {
138                         val = vals[(int)*p++];
139                         *(rgb+2) = val & 0xff; val >>= 8;  // 2:B
140                         *(rgb+1) = val & 0xff; val >>= 8;  // 1:G
141                         *(rgb+0) = val & 0xff;             // 0:R
142                 }
143         }
144
145         return (savergb);
146 }
147
148 unsigned char*
149 xpm2rgba (const char** xpm, uint32_t& w, uint32_t& h)
150 {
151         static long vals[256], val;
152         uint32_t t, x, y, colors, cpp;
153         unsigned char c;
154         unsigned char *savergb, *rgb;
155         char transparent;
156
157         // PARSE HEADER
158
159         if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
160                 error << string_compose (_("bad XPM header %1"), xpm[0])
161                       << endmsg;
162                 return 0;
163         }
164
165         savergb = rgb = (unsigned char*) malloc (h * w * 4);
166         
167         // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
168
169         if (strstr (xpm[1], "None")) {
170                 sscanf (xpm[1], "%c", &transparent);
171                 t = 1;
172         } else {
173                 transparent = 0;
174                 t = 0;
175         }
176
177         for (; t < colors; ++t) {
178                 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
179                 vals[c] = val;
180         }
181         
182         // COLORMAP -> RGB CONVERSION
183         //    Get low 3 bytes from vals[]
184         //
185
186         const char *p;
187         for (y = h-1; y > 0; --y) {
188
189                 char alpha;
190
191                 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 4) {
192
193                         if (transparent && (*p++ == transparent)) {
194                                 alpha = 0;
195                                 val = 0;
196                         } else {
197                                 alpha = 255;
198                                 val = vals[(int)*p];
199                         }
200
201                         *(rgb+3) = alpha;                  // 3: alpha
202                         *(rgb+2) = val & 0xff; val >>= 8;  // 2:B
203                         *(rgb+1) = val & 0xff; val >>= 8;  // 1:G
204                         *(rgb+0) = val & 0xff;             // 0:R
205                 }
206         }
207
208         return (savergb);
209 }
210
211 ArdourCanvas::Points*
212 get_canvas_points (string who, uint32_t npoints)
213 {
214         // cerr << who << ": wants " << npoints << " canvas points" << endl;
215 #ifdef TRAP_EXCESSIVE_POINT_REQUESTS
216         if (npoints > (uint32_t) gdk_screen_width() + 4) {
217                 abort ();
218         }
219 #endif
220         return new ArdourCanvas::Points (npoints);
221 }
222
223 Pango::FontDescription
224 get_font_for_style (string widgetname)
225 {
226         Gtk::Window window (WINDOW_TOPLEVEL);
227         Gtk::Label foobar;
228         Glib::RefPtr<Gtk::Style> style;
229
230         window.add (foobar);
231         foobar.set_name (widgetname);
232         foobar.ensure_style();
233
234         style = foobar.get_style ();
235         return style->get_font();
236 }
237
238 uint32_t
239 rgba_from_style (string style, uint32_t r, uint32_t g, uint32_t b, uint32_t a, string attr, int state, bool rgba)
240 {
241         /* In GTK+2, styles aren't set up correctly if the widget is not
242            attached to a toplevel window that has a screen pointer.
243         */
244
245         static Gtk::Window* window = 0;
246
247         if (window == 0) {
248                 window = new Window (WINDOW_TOPLEVEL);
249         }
250
251         Gtk::Label foo;
252         
253         window->add (foo);
254
255         foo.set_name (style);
256         foo.ensure_style ();
257         
258         GtkRcStyle* waverc = foo.get_style()->gobj()->rc_style;
259
260         if (waverc) {
261                 if (attr == "fg") {
262                         r = waverc->fg[state].red / 257;
263                         g = waverc->fg[state].green / 257;
264                         b = waverc->fg[state].blue / 257;
265  
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
550
551 ustring
552 short_path (ustring path, uint32_t target_characters)
553 {
554         ustring::size_type last_sep;
555         ustring::size_type len = path.length();
556         const char separator = '/';
557
558         if (len <= target_characters) {
559                 return path;
560         }
561
562         if ((last_sep = path.find_last_of (separator)) == ustring::npos) {
563
564                 /* just a filename, but its too long anyway */
565
566                 if (target_characters > 3) {
567                         return path.substr (0, target_characters - 3) + ustring ("...");
568                 } else {
569                         /* stupid caller, just hand back the whole thing */
570                         return path;
571                 }
572         }
573
574         if (len - last_sep >= target_characters) {
575
576                 /* even the filename itself is too long */
577
578                 if (target_characters > 3) {
579                         return path.substr (last_sep+1, target_characters - 3) + ustring ("...");
580                 } else {
581                         /* stupid caller, just hand back the whole thing */
582                         return path;
583                 }
584         }
585         
586         uint32_t so_far = (len - last_sep);
587         uint32_t space_for = target_characters - so_far;
588
589         if (space_for >= 3) {
590                 ustring res = "...";
591                 res += path.substr (last_sep - space_for);
592                 return res;
593         } else {
594                 /* remove part of the end */
595                 ustring res = "...";
596                 res += path.substr (last_sep - space_for, len - last_sep + space_for - 3);
597                 res += "...";
598                 return res;
599                 
600         }
601 }
602