most of the 2.X->3.0 commit (up to rev 4299) except for gtk2_ardour/editor_canvas...
[ardour.git] / gtk2_ardour / utils.cc
1 /*
2     Copyright (C) 2003 Paul Davis 
3
4     This program is free software; you an 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         if (attr == "fg") {
352                 return Gdk::Color (&style->fg[state]);
353         }
354
355         if (attr == "bg") {
356                 return Gdk::Color (&style->bg[state]);
357         }
358
359         if (attr == "light") {
360                 return Gdk::Color (&style->light[state]);
361         }
362
363         if (attr == "dark") {
364                 return Gdk::Color (&style->dark[state]);
365         }
366
367         if (attr == "mid") {
368                 return Gdk::Color (&style->mid[state]);
369         }
370
371         if (attr == "text") {
372                 return Gdk::Color (&style->text[state]);
373         }
374
375         if (attr == "base") {
376                 return Gdk::Color (&style->base[state]);
377         }
378
379         if (attr == "text_aa") {
380                 return Gdk::Color (&style->text_aa[state]);
381         }
382
383         error << string_compose (_("unknown style attribute %1 requested for color; using \"red\""), attr) << endmsg;
384         return Gdk::Color ("red");
385 }
386
387 bool 
388 canvas_item_visible (ArdourCanvas::Item* item)
389 {
390         return (item->gobj()->object.flags & GNOME_CANVAS_ITEM_VISIBLE) ? true : false;
391 }
392
393 void
394 set_color (Gdk::Color& c, int rgb)
395 {
396         c.set_rgb((rgb >> 16)*256, ((rgb & 0xff00) >> 8)*256, (rgb & 0xff)*256);
397 }
398
399 #ifdef GTKOSX
400 extern "C" {
401         gboolean gdk_quartz_possibly_forward (GdkEvent*);
402 }
403 #endif
404
405 bool
406 key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev)
407 {
408         GtkWindow* win = window.gobj();
409         GtkWidget* focus = gtk_window_get_focus (win);
410         bool special_handling_of_unmodified_accelerators = false;
411         bool allow_activating = true;
412
413 #undef DEBUG_ACCELERATOR_HANDLING
414 #ifdef  DEBUG_ACCELERATOR_HANDLING
415         //bool debug = (getenv ("ARDOUR_DEBUG_ACCELERATOR_HANDLING") != 0);
416         bool debug=true;
417 #endif
418         if (focus) {
419                 if (GTK_IS_ENTRY(focus) || Keyboard::some_magic_widget_has_focus()) {
420                         special_handling_of_unmodified_accelerators = true;
421                 } 
422         } 
423
424 #ifdef GTKOSX
425         /* should this be universally true? */
426         if (Keyboard::some_magic_widget_has_focus ()) {
427                 allow_activating = false;
428         }
429 #endif
430
431 #ifdef DEBUG_ACCELERATOR_HANDLING
432         if (debug) {
433                 cerr << "Win = " << win << " Key event: code = " << ev->keyval << " state = " << hex << ev->state << dec << " special handling ? " 
434                      << special_handling_of_unmodified_accelerators
435                      << " magic widget focus ? "
436                      << Keyboard::some_magic_widget_has_focus()
437                      << " allow_activation ? "
438                      << allow_activating
439                      << endl;
440         }
441 #endif
442
443         /* This exists to allow us to override the way GTK handles
444            key events. The normal sequence is:
445
446            a) event is delivered to a GtkWindow
447            b) accelerators/mnemonics are activated
448            c) if (b) didn't handle the event, propagate to
449                the focus widget and/or focus chain
450
451            The problem with this is that if the accelerators include
452            keys without modifiers, such as the space bar or the 
453            letter "e", then pressing the key while typing into
454            a text entry widget results in the accelerator being
455            activated, instead of the desired letter appearing
456            in the text entry.
457
458            There is no good way of fixing this, but this
459            represents a compromise. The idea is that 
460            key events involving modifiers (not Shift)
461            get routed into the activation pathway first, then
462            get propagated to the focus widget if necessary.
463            
464            If the key event doesn't involve modifiers,
465            we deliver to the focus widget first, thus allowing
466            it to get "normal text" without interference
467            from acceleration.
468
469            Of course, this can also be problematic: if there
470            is a widget with focus, then it will swallow
471            all "normal text" accelerators.
472         */
473
474
475         if (!special_handling_of_unmodified_accelerators) {
476
477                 /* pretend that certain key events that GTK does not allow
478                    to be used as accelerators are actually something that
479                    it does allow.
480                 */
481
482                 uint32_t fakekey = ev->keyval;
483
484                 if (possibly_translate_keyval_to_make_legal_accelerator (fakekey)) {
485                         if (allow_activating && gtk_accel_groups_activate(G_OBJECT(win), fakekey, GdkModifierType(ev->state))) {
486                                 return true;
487                         }
488
489 #ifdef GTKOSX
490                         if (allow_activating) {
491                                 int oldval = ev->keyval;
492                                 ev->keyval = fakekey;
493                                 if (gdk_quartz_possibly_forward ((GdkEvent*) ev)) {
494                                         return true;
495                                 }
496                                 ev->keyval = oldval;
497                         }
498 #endif
499                 }
500         }
501
502         /* consider all relevant modifiers but not LOCK or SHIFT */
503
504         guint mask = (Keyboard::RelevantModifierKeyMask & ~(Gdk::SHIFT_MASK|Gdk::LOCK_MASK));
505
506         if (!special_handling_of_unmodified_accelerators || (ev->state & mask)) {
507
508                 /* no special handling or there are modifiers in effect: accelerate first */
509
510 #ifdef DEBUG_ACCELERATOR_HANDLING
511                 if (debug) {
512                         cerr << "\tactivate, then propagate\n";
513                 }
514 #endif
515
516                 if (allow_activating) {
517 #ifdef GTKOSX
518                         if (gdk_quartz_possibly_forward ((GdkEvent*) ev)) {
519                                 return true;
520                         }
521 #endif
522                         if (gtk_window_activate_key (win, ev)) {
523                                 return true;
524                         }
525                 }
526
527 #ifdef DEBUG_ACCELERATOR_HANDLING
528                 if (debug) {
529                         cerr << "\tnot accelerated, now propagate\n";
530                 }
531 #endif
532                 return gtk_window_propagate_key_event (win, ev);
533         }
534         
535         /* no modifiers, propagate first */
536         
537 #ifdef DEBUG_ACCELERATOR_HANDLING
538         if (debug) {
539                 cerr << "\tpropagate, then activate\n";
540         }
541 #endif
542         if (!gtk_window_propagate_key_event (win, ev)) {
543 #ifdef DEBUG_ACCELERATOR_HANDLING
544                 if (debug) {
545                         cerr << "\tpropagation didn't handle, so activate\n";
546                 }
547 #endif
548
549                 if (allow_activating) {
550                         
551 #ifdef GTKOSX
552                         if (gdk_quartz_possibly_forward ((GdkEvent*) ev)) {
553                                 return true;
554                         }
555 #endif
556                         return gtk_window_activate_key (win, ev);
557                 } 
558                         
559         } else {
560 #ifdef DEBUG_ACCELERATOR_HANDLING
561                 if (debug) {
562                         cerr << "\thandled by propagate\n";
563                 }
564 #endif
565                 return true;
566         }
567
568 #ifdef DEBUG_ACCELERATOR_HANDLING
569         if (debug) {
570                 cerr << "\tnot handled\n";
571         }
572 #endif
573         return true;
574 }
575
576 Glib::RefPtr<Gdk::Pixbuf>       
577 get_xpm (std::string name)
578 {
579         if (!xpm_map[name]) {
580
581                 SearchPath spath(ARDOUR::ardour_search_path());
582                 spath += ARDOUR::system_data_search_path();
583                 
584                 spath.add_subdirectory_to_paths("pixmaps");
585                 
586                 sys::path data_file_path;
587                 
588                 if(!find_file_in_search_path (spath, name, data_file_path)) {
589                         fatal << string_compose (_("cannot find XPM file for %1"), name) << endmsg;
590                 }
591                 
592                 try {
593                         xpm_map[name] =  Gdk::Pixbuf::create_from_file (data_file_path.to_string());
594                 } catch(const Glib::Error& e)   {
595                         warning << "Caught Glib::Error: " << e.what() << endmsg;
596                 }
597         }
598
599         return xpm_map[name];
600 }
601
602
603 Glib::RefPtr<Gdk::Pixbuf>       
604 get_icon (const char* cname)
605 {
606         string name = cname;
607         name += X_(".png");
608
609         SearchPath spath(ARDOUR::ardour_search_path());
610         spath += ARDOUR::system_data_search_path();
611
612         spath.add_subdirectory_to_paths("icons");
613
614         sys::path data_file_path;
615
616         if (!find_file_in_search_path (spath, name, data_file_path)) {
617                 fatal << string_compose (_("cannot find icon image for %1"), name) << endmsg;
618         }
619
620         Glib::RefPtr<Gdk::Pixbuf> img;
621         try {
622                 img = Gdk::Pixbuf::create_from_file (data_file_path.to_string());
623         } catch (const Gdk::PixbufError &e) {
624                 cerr << "Caught PixbufError: " << e.what() << endl;
625         } catch (...) {
626                 g_message("Caught ... ");
627         }
628
629         return img;
630 }
631
632 string
633 longest (vector<string>& strings)
634 {
635         if (strings.empty()) {
636                 return string ("");
637         }
638
639         vector<string>::iterator longest = strings.begin();
640         string::size_type longest_length = (*longest).length();
641         
642         vector<string>::iterator i = longest;
643         ++i;
644
645         while (i != strings.end()) {
646                 
647                 string::size_type len = (*i).length();
648                 
649                 if (len > longest_length) {
650                         longest = i;
651                         longest_length = len;
652                 } 
653                 
654                 ++i;
655         }
656         
657         return *longest;
658 }
659
660 bool
661 key_is_legal_for_numeric_entry (guint keyval)
662 {
663         switch (keyval) {
664         case GDK_minus:
665         case GDK_plus:
666         case GDK_period:
667         case GDK_comma:
668         case GDK_0:
669         case GDK_1:
670         case GDK_2:
671         case GDK_3:
672         case GDK_4:
673         case GDK_5:
674         case GDK_6:
675         case GDK_7:
676         case GDK_8:
677         case GDK_9:
678         case GDK_KP_Add:
679         case GDK_KP_Subtract:
680         case GDK_KP_Decimal:
681         case GDK_KP_0:
682         case GDK_KP_1:
683         case GDK_KP_2:
684         case GDK_KP_3:
685         case GDK_KP_4:
686         case GDK_KP_5:
687         case GDK_KP_6:
688         case GDK_KP_7:
689         case GDK_KP_8:
690         case GDK_KP_9:
691         case GDK_Return:
692         case GDK_BackSpace:
693         case GDK_Delete:
694         case GDK_KP_Enter:
695         case GDK_Home:
696         case GDK_End:
697         case GDK_Left:
698         case GDK_Right:
699                 return true;
700                 
701         default:
702                 break;
703         }
704
705         return false;
706 }
707 void
708 set_pango_fontsize ()
709 {
710         long val = ARDOUR::Config->get_font_scale();
711
712         /* FT2 rendering - used by GnomeCanvas, sigh */
713
714         pango_ft2_font_map_set_resolution ((PangoFT2FontMap*) pango_ft2_font_map_for_display(), val/1024, val/1024);
715
716         /* Cairo rendering, in case there is any */
717         
718         pango_cairo_font_map_set_resolution ((PangoCairoFontMap*) pango_cairo_font_map_get_default(), val/1024);
719 }
720
721 void
722 reset_dpi ()
723 {
724         long val = ARDOUR::Config->get_font_scale();
725         set_pango_fontsize ();
726         /* Xft rendering */
727
728         gtk_settings_set_long_property (gtk_settings_get_default(),
729                                         "gtk-xft-dpi", val, "ardour");
730         DPIReset();//Emit Signal
731 }
732
733 bool
734 possibly_translate_keyval_to_make_legal_accelerator (uint32_t& keyval)
735 {
736         int fakekey = GDK_VoidSymbol;
737
738         switch (keyval) {
739         case GDK_Tab:
740         case GDK_ISO_Left_Tab:
741                 fakekey = GDK_nabla;
742                 break;
743                 
744         case GDK_Up:
745                 fakekey = GDK_uparrow;
746                 break;
747                 
748         case GDK_Down:
749                 fakekey = GDK_downarrow;
750                 break;
751                 
752         case GDK_Right:
753                 fakekey = GDK_rightarrow;
754                 break;
755                 
756         case GDK_Left:
757                 fakekey = GDK_leftarrow;
758                 break;
759                 
760         default:
761                 break;
762         }
763         
764         if (fakekey != GDK_VoidSymbol) {
765                 keyval = fakekey;
766                 return true;
767         } 
768
769         return false;
770 }
771