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