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