15cf01d85a51306419aa80ca4222c520f31b74b5
[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  
267                         /* what a hack ... "a" is for "active" */
268                         if (state == Gtk::STATE_NORMAL && rgba) {
269                                 a = waverc->fg[GTK_STATE_ACTIVE].red / 257;
270                         }
271                 } else if (attr == "bg") {
272                         r = g = b = 0;
273                         r = waverc->bg[state].red / 257;
274                         g = waverc->bg[state].green / 257;
275                         b = waverc->bg[state].blue / 257;
276                 } else if (attr == "base") {
277                         r = waverc->base[state].red / 257;
278                         g = waverc->base[state].green / 257;
279                         b = waverc->base[state].blue / 257;
280                 } else if (attr == "text") {
281                         r = waverc->text[state].red / 257;
282                         g = waverc->text[state].green / 257;
283                         b = waverc->text[state].blue / 257;
284                 }
285         } else {
286                 warning << string_compose (_("missing RGBA style for \"%1\""), style) << endl;
287         }
288
289         window->remove ();
290         
291         if (state == Gtk::STATE_NORMAL && rgba) {
292                 return (uint32_t) RGBA_TO_UINT(r,g,b,a);
293         } else {
294                 return (uint32_t) RGB_TO_UINT(r,g,b);
295         }
296 }
297
298 bool 
299 canvas_item_visible (ArdourCanvas::Item* item)
300 {
301         return (item->gobj()->object.flags & GNOME_CANVAS_ITEM_VISIBLE) ? true : false;
302 }
303
304 void
305 set_color (Gdk::Color& c, int rgb)
306 {
307         c.set_rgb((rgb >> 16)*256, ((rgb & 0xff00) >> 8)*256, (rgb & 0xff)*256);
308 }
309
310 bool
311 key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev)
312 {
313         GtkWindow* win = window.gobj();
314         GtkWidget* focus = gtk_window_get_focus (win);
315         bool special_handling_of_unmodified_accelerators = false;
316
317 #undef  DEBUG_ACCELERATOR_HANDLING
318 #ifdef  DEBUG_ACCELERATOR_HANDLING
319         bool debug = (getenv ("ARDOUR_DEBUG_ACCELERATOR_HANDLING") != 0);
320 #endif
321
322         if (focus) {
323                 if (GTK_IS_ENTRY(focus)) {
324                         special_handling_of_unmodified_accelerators = true;
325                 } 
326         } 
327
328 #ifdef DEBUG_ACCELERATOR_HANDLING
329         if (debug) {
330                 cerr << "Key event: code = " << ev->keyval << " state = " << hex << ev->state << dec << " focus is an entry ? " 
331                      << special_handling_of_unmodified_accelerators
332                      << endl;
333         }
334 #endif
335
336         /* This exists to allow us to override the way GTK handles
337            key events. The normal sequence is:
338
339            a) event is delivered to a GtkWindow
340            b) accelerators/mnemonics are activated
341            c) if (b) didn't handle the event, propagate to
342                the focus widget and/or focus chain
343
344            The problem with this is that if the accelerators include
345            keys without modifiers, such as the space bar or the 
346            letter "e", then pressing the key while typing into
347            a text entry widget results in the accelerator being
348            activated, instead of the desired letter appearing
349            in the text entry.
350
351            There is no good way of fixing this, but this
352            represents a compromise. The idea is that 
353            key events involving modifiers (not Shift)
354            get routed into the activation pathway first, then
355            get propagated to the focus widget if necessary.
356            
357            If the key event doesn't involve modifiers,
358            we deliver to the focus widget first, thus allowing
359            it to get "normal text" without interference
360            from acceleration.
361
362            Of course, this can also be problematic: if there
363            is a widget with focus, then it will swallow
364            all "normal text" accelerators.
365         */
366
367
368         if (!special_handling_of_unmodified_accelerators) {
369
370                 /* pretend that certain key events that GTK does not allow
371                    to be used as accelerators are actually something that
372                    it does allow.
373                 */
374
375                 int ret = false;
376
377                 switch (ev->keyval) {
378                 case GDK_Up:
379                         ret = gtk_accel_groups_activate(G_OBJECT(win), GDK_uparrow, GdkModifierType(ev->state));
380                         break;
381
382                 case GDK_Down:
383                         ret = gtk_accel_groups_activate(G_OBJECT(win), GDK_downarrow, GdkModifierType(ev->state));
384                         break;
385
386                 case GDK_Right:
387                         ret = gtk_accel_groups_activate(G_OBJECT(win), GDK_rightarrow, GdkModifierType(ev->state));
388                         break;
389
390                 case GDK_Left:
391                         ret = gtk_accel_groups_activate(G_OBJECT(win), GDK_leftarrow, GdkModifierType(ev->state));
392                         break;
393
394                 default:
395                         break;
396                 }
397
398                 if (ret) {
399                         return true;
400                 }
401         }
402                 
403         if (!special_handling_of_unmodified_accelerators ||
404             ev->state & (Gdk::MOD1_MASK|
405                          Gdk::MOD3_MASK|
406                          Gdk::MOD4_MASK|
407                          Gdk::MOD5_MASK|
408                          Gdk::CONTROL_MASK)) {
409
410                 /* no special handling or modifiers in effect: accelerate first */
411
412 #ifdef DEBUG_ACCELERATOR_HANDLING
413                 if (debug) {
414                         cerr << "\tactivate, then propagate\n";
415                 }
416 #endif
417                 if (!gtk_window_activate_key (win, ev)) {
418                         return gtk_window_propagate_key_event (win, ev);
419                 } else {
420 #ifdef DEBUG_ACCELERATOR_HANDLING
421                 if (debug) {
422                         cerr << "\tnot handled\n";
423                 }
424 #endif
425                         return true;
426                 } 
427         }
428         
429         /* no modifiers, propagate first */
430         
431 #ifdef DEBUG_ACCELERATOR_HANDLING
432                 if (debug) {
433                         cerr << "\tactivate, then propagate\n";
434                 }
435 #endif
436         if (!gtk_window_propagate_key_event (win, ev)) {
437                 return gtk_window_activate_key (win, ev);
438         } 
439
440
441 #ifdef DEBUG_ACCELERATOR_HANDLING
442         if (debug) {
443                 cerr << "\tnot handled\n";
444         }
445 #endif
446         return true;
447 }
448
449 Glib::RefPtr<Gdk::Pixbuf>       
450 get_xpm (std::string name)
451 {
452         if (!xpm_map[name]) {
453                 xpm_map[name] = Gdk::Pixbuf::create_from_file (ARDOUR::find_data_file(name, "pixmaps"));
454         }
455                 
456         return (xpm_map[name]);
457 }
458
459 Glib::RefPtr<Gdk::Pixbuf>       
460 get_icon (const char* cname)
461 {
462         string name = cname;
463         name += X_(".png");
464
465         string path = ARDOUR::find_data_file (name, "icons");
466
467         if (path.empty()) {
468                 fatal << string_compose (_("cannot find icon image for %1"), name) << endmsg;
469                 /*NOTREACHED*/
470         }
471
472         return Gdk::Pixbuf::create_from_file (path);
473 }
474
475 string
476 longest (vector<string>& strings)
477 {
478         if (strings.empty()) {
479                 return string ("");
480         }
481
482         vector<string>::iterator longest = strings.begin();
483         string::size_type longest_length = (*longest).length();
484         
485         vector<string>::iterator i = longest;
486         ++i;
487
488         while (i != strings.end()) {
489                 
490                 string::size_type len = (*i).length();
491                 
492                 if (len > longest_length) {
493                         longest = i;
494                         longest_length = len;
495                 } 
496                 
497                 ++i;
498         }
499         
500         return *longest;
501 }
502
503 bool
504 key_is_legal_for_numeric_entry (guint keyval)
505 {
506         switch (keyval) {
507         case GDK_minus:
508         case GDK_plus:
509         case GDK_period:
510         case GDK_comma:
511         case GDK_0:
512         case GDK_1:
513         case GDK_2:
514         case GDK_3:
515         case GDK_4:
516         case GDK_5:
517         case GDK_6:
518         case GDK_7:
519         case GDK_8:
520         case GDK_9:
521         case GDK_KP_Add:
522         case GDK_KP_Subtract:
523         case GDK_KP_Decimal:
524         case GDK_KP_0:
525         case GDK_KP_1:
526         case GDK_KP_2:
527         case GDK_KP_3:
528         case GDK_KP_4:
529         case GDK_KP_5:
530         case GDK_KP_6:
531         case GDK_KP_7:
532         case GDK_KP_8:
533         case GDK_KP_9:
534         case GDK_Return:
535         case GDK_BackSpace:
536         case GDK_Delete:
537         case GDK_KP_Enter:
538         case GDK_Home:
539         case GDK_End:
540         case GDK_Left:
541         case GDK_Right:
542                 return true;
543                 
544         default:
545                 break;
546         }
547
548         return false;
549 }
550
551
552 ustring
553 short_path (ustring path, uint32_t target_characters)
554 {
555         ustring::size_type last_sep;
556         ustring::size_type len = path.length();
557         const char separator = '/';
558
559         if (len <= target_characters) {
560                 return path;
561         }
562
563         if ((last_sep = path.find_last_of (separator)) == ustring::npos) {
564
565                 /* just a filename, but its too long anyway */
566
567                 if (target_characters > 3) {
568                         return path.substr (0, target_characters - 3) + ustring ("...");
569                 } else {
570                         /* stupid caller, just hand back the whole thing */
571                         return path;
572                 }
573         }
574
575         if (len - last_sep >= target_characters) {
576
577                 /* even the filename itself is too long */
578
579                 if (target_characters > 3) {
580                         return path.substr (last_sep+1, target_characters - 3) + ustring ("...");
581                 } else {
582                         /* stupid caller, just hand back the whole thing */
583                         return path;
584                 }
585         }
586         
587         uint32_t so_far = (len - last_sep);
588         uint32_t space_for = target_characters - so_far;
589
590         if (space_for >= 3) {
591                 ustring res = "...";
592                 res += path.substr (last_sep - space_for);
593                 return res;
594         } else {
595                 /* remove part of the end */
596                 ustring res = "...";
597                 res += path.substr (last_sep - space_for, len - last_sep + space_for - 3);
598                 res += "...";
599                 return res;
600                 
601         }
602 }
603