add -Wpointer-arith -Wcast-qual -Wcast-align and others to compile flags, and fix...
[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 #ifdef WAF_BUILD
21 #include "gtk2ardour-config.h"
22 #endif
23
24 #include <pango/pangoft2.h> // for fontmap resolution control for GnomeCanvas
25 #include <pango/pangocairo.h> // for fontmap resolution control for GnomeCanvas
26
27 #include <cstdlib>
28 #include <cctype>
29 #include <fstream>
30 #include <list>
31 #include <sys/stat.h>
32 #include <libart_lgpl/art_misc.h>
33 #include <gtkmm/rc.h>
34 #include <gtkmm/window.h>
35 #include <gtkmm/combo.h>
36 #include <gtkmm/label.h>
37 #include <gtkmm/paned.h>
38 #include <gtk/gtkpaned.h>
39 #include <boost/algorithm/string.hpp>
40
41 #include "pbd/file_utils.h"
42
43 #include <gtkmm2ext/utils.h>
44 #include "ardour/rc_configuration.h"
45
46 #include "ardour/filesystem_paths.h"
47
48 #include "ardour_ui.h"
49 #include "debug.h"
50 #include "public_editor.h"
51 #include "keyboard.h"
52 #include "utils.h"
53 #include "i18n.h"
54 #include "rgb_macros.h"
55 #include "canvas_impl.h"
56 #include "gui_thread.h"
57
58 using namespace std;
59 using namespace Gtk;
60 using namespace Glib;
61 using namespace PBD;
62 using Gtkmm2ext::Keyboard;
63
64 sigc::signal<void>  DPIReset;
65
66
67 /** Add an element to a menu, settings its sensitivity.
68  * @param m Menu to add to.
69  * @param e Element to add.
70  * @param s true to make sensitive, false to make insensitive
71  */
72 void
73 add_item_with_sensitivity (Menu_Helpers::MenuList& m, Menu_Helpers::MenuElem e, bool s)
74 {
75         m.push_back (e);
76         if (!s) {
77                 m.back().set_sensitive (false);
78         }
79 }
80
81
82 gint
83 just_hide_it (GdkEventAny */*ev*/, Gtk::Window *win)
84 {
85         win->hide ();
86         return 0;
87 }
88
89 /* xpm2rgb copied from nixieclock, which bore the legend:
90
91     nixieclock - a nixie desktop timepiece
92     Copyright (C) 2000 Greg Ercolano, erco@3dsite.com
93
94     and was released under the GPL.
95 */
96
97 unsigned char*
98 xpm2rgb (const char** xpm, uint32_t& w, uint32_t& h)
99 {
100         static long vals[256], val;
101         uint32_t t, x, y, colors, cpp;
102         unsigned char c;
103         unsigned char *savergb, *rgb;
104
105         // PARSE HEADER
106
107         if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
108                 error << string_compose (_("bad XPM header %1"), xpm[0])
109                       << endmsg;
110                 return 0;
111         }
112
113         savergb = rgb = (unsigned char*) malloc (h * w * 3);
114
115         // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
116         for (t = 0; t < colors; ++t) {
117                 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
118                 vals[c] = val;
119         }
120
121         // COLORMAP -> RGB CONVERSION
122         //    Get low 3 bytes from vals[]
123         //
124
125         const char *p;
126         for (y = h-1; y > 0; --y) {
127
128                 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 3) {
129                         val = vals[(int)*p++];
130                         *(rgb+2) = val & 0xff; val >>= 8;  // 2:B
131                         *(rgb+1) = val & 0xff; val >>= 8;  // 1:G
132                         *(rgb+0) = val & 0xff;             // 0:R
133                 }
134         }
135
136         return (savergb);
137 }
138
139 unsigned char*
140 xpm2rgba (const char** xpm, uint32_t& w, uint32_t& h)
141 {
142         static long vals[256], val;
143         uint32_t t, x, y, colors, cpp;
144         unsigned char c;
145         unsigned char *savergb, *rgb;
146         char transparent;
147
148         // PARSE HEADER
149
150         if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
151                 error << string_compose (_("bad XPM header %1"), xpm[0])
152                       << endmsg;
153                 return 0;
154         }
155
156         savergb = rgb = (unsigned char*) malloc (h * w * 4);
157
158         // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
159
160         if (strstr (xpm[1], "None")) {
161                 sscanf (xpm[1], "%c", &transparent);
162                 t = 1;
163         } else {
164                 transparent = 0;
165                 t = 0;
166         }
167
168         for (; t < colors; ++t) {
169                 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
170                 vals[c] = val;
171         }
172
173         // COLORMAP -> RGB CONVERSION
174         //    Get low 3 bytes from vals[]
175         //
176
177         const char *p;
178         for (y = h-1; y > 0; --y) {
179
180                 char alpha;
181
182                 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 4) {
183
184                         if (transparent && (*p++ == transparent)) {
185                                 alpha = 0;
186                                 val = 0;
187                         } else {
188                                 alpha = 255;
189                                 val = vals[(int)*p];
190                         }
191
192                         *(rgb+3) = alpha;                  // 3: alpha
193                         *(rgb+2) = val & 0xff; val >>= 8;  // 2:B
194                         *(rgb+1) = val & 0xff; val >>= 8;  // 1:G
195                         *(rgb+0) = val & 0xff;             // 0:R
196                 }
197         }
198
199         return (savergb);
200 }
201
202 ArdourCanvas::Points*
203 get_canvas_points (string /*who*/, uint32_t npoints)
204 {
205         // cerr << who << ": wants " << npoints << " canvas points" << endl;
206 #ifdef TRAP_EXCESSIVE_POINT_REQUESTS
207         if (npoints > (uint32_t) gdk_screen_width() + 4) {
208                 abort ();
209         }
210 #endif
211         return new ArdourCanvas::Points (npoints);
212 }
213
214 Pango::FontDescription
215 get_font_for_style (string widgetname)
216 {
217         Gtk::Window window (WINDOW_TOPLEVEL);
218         Gtk::Label foobar;
219         Glib::RefPtr<Gtk::Style> style;
220
221         window.add (foobar);
222         foobar.set_name (widgetname);
223         foobar.ensure_style();
224
225         style = foobar.get_style ();
226
227         Glib::RefPtr<const Pango::Layout> layout = foobar.get_layout();
228
229         PangoFontDescription *pfd = (PangoFontDescription *)pango_layout_get_font_description(const_cast<PangoLayout *>(layout->gobj()));
230
231         if (!pfd) {
232
233                 /* layout inherited its font description from a PangoContext */
234
235                 PangoContext* ctxt = (PangoContext*) pango_layout_get_context (const_cast<PangoLayout*>(layout->gobj()));
236                 pfd =  pango_context_get_font_description (ctxt);
237                 return Pango::FontDescription (pfd); /* make a copy */
238         }
239
240         return Pango::FontDescription (pfd); /* make a copy */
241 }
242
243 uint32_t
244 rgba_from_style (string style, uint32_t r, uint32_t g, uint32_t b, uint32_t a, string attr, int state, bool rgba)
245 {
246         /* In GTK+2, styles aren't set up correctly if the widget is not
247            attached to a toplevel window that has a screen pointer.
248         */
249
250         static Gtk::Window* window = 0;
251
252         if (window == 0) {
253                 window = new Window (WINDOW_TOPLEVEL);
254         }
255
256         Gtk::Label foo;
257
258         window->add (foo);
259
260         foo.set_name (style);
261         foo.ensure_style ();
262
263         GtkRcStyle* rc = foo.get_style()->gobj()->rc_style;
264
265         if (rc) {
266                 if (attr == "fg") {
267                         r = rc->fg[state].red / 257;
268                         g = rc->fg[state].green / 257;
269                         b = rc->fg[state].blue / 257;
270
271                         /* what a hack ... "a" is for "active" */
272                         if (state == Gtk::STATE_NORMAL && rgba) {
273                                 a = rc->fg[GTK_STATE_ACTIVE].red / 257;
274                         }
275                 } else if (attr == "bg") {
276                         r = g = b = 0;
277                         r = rc->bg[state].red / 257;
278                         g = rc->bg[state].green / 257;
279                         b = rc->bg[state].blue / 257;
280                 } else if (attr == "base") {
281                         r = rc->base[state].red / 257;
282                         g = rc->base[state].green / 257;
283                         b = rc->base[state].blue / 257;
284                 } else if (attr == "text") {
285                         r = rc->text[state].red / 257;
286                         g = rc->text[state].green / 257;
287                         b = rc->text[state].blue / 257;
288                 }
289         } else {
290                 warning << string_compose (_("missing RGBA style for \"%1\""), style) << endl;
291         }
292
293         window->remove ();
294
295         if (state == Gtk::STATE_NORMAL && rgba) {
296                 return (uint32_t) RGBA_TO_UINT(r,g,b,a);
297         } else {
298                 return (uint32_t) RGB_TO_UINT(r,g,b);
299         }
300 }
301
302 bool
303 canvas_item_visible (ArdourCanvas::Item* item)
304 {
305         return (item->gobj()->object.flags & GNOME_CANVAS_ITEM_VISIBLE) ? true : false;
306 }
307
308 void
309 set_color (Gdk::Color& c, int rgb)
310 {
311         c.set_rgb((rgb >> 16)*256, ((rgb & 0xff00) >> 8)*256, (rgb & 0xff)*256);
312 }
313
314 bool
315 relay_key_press (GdkEventKey* ev, Gtk::Window* win)
316 {
317         if (!key_press_focus_accelerator_handler (*win, ev)) {
318                 return PublicEditor::instance().on_key_press_event(ev);
319         } else {
320                 return true;
321         }
322 }
323
324 bool
325 forward_key_press (GdkEventKey* ev)
326 {
327         return PublicEditor::instance().on_key_press_event(ev);
328 }
329
330 bool
331 key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev)
332 {
333         GtkWindow* win = window.gobj();
334         GtkWidget* focus = gtk_window_get_focus (win);
335         bool special_handling_of_unmodified_accelerators = false;
336         bool allow_activating = true;
337         /* consider all relevant modifiers but not LOCK or SHIFT */
338         const guint mask = (Keyboard::RelevantModifierKeyMask & ~(Gdk::SHIFT_MASK|Gdk::LOCK_MASK));
339
340         if (focus) {
341                 if (GTK_IS_ENTRY(focus) || Keyboard::some_magic_widget_has_focus()) {
342                         special_handling_of_unmodified_accelerators = true;
343                 }
344         }
345
346 #ifdef GTKOSX
347         /* at one time this appeared to be necessary. As of July 2012, it does not
348            appear to be. if it ever is necessar, figure out if it should apply
349            to all platforms.
350         */
351 #if 0 
352         if (Keyboard::some_magic_widget_has_focus ()) {
353                 allow_activating = false;
354         }
355 #endif
356 #endif
357
358
359         DEBUG_TRACE (DEBUG::Accelerators, string_compose ("Win = %1 focus = %7 Key event: code = %2  state = %3 special handling ? %4 magic widget focus ? %5 allow_activation ? %6\n",
360                                                           win,
361                                                           ev->keyval,
362                                                           ev->state,
363                                                           special_handling_of_unmodified_accelerators,
364                                                           Keyboard::some_magic_widget_has_focus(),
365                                                           allow_activating,
366                                                           focus));
367
368         /* This exists to allow us to override the way GTK handles
369            key events. The normal sequence is:
370
371            a) event is delivered to a GtkWindow
372            b) accelerators/mnemonics are activated
373            c) if (b) didn't handle the event, propagate to
374                the focus widget and/or focus chain
375
376            The problem with this is that if the accelerators include
377            keys without modifiers, such as the space bar or the
378            letter "e", then pressing the key while typing into
379            a text entry widget results in the accelerator being
380            activated, instead of the desired letter appearing
381            in the text entry.
382
383            There is no good way of fixing this, but this
384            represents a compromise. The idea is that
385            key events involving modifiers (not Shift)
386            get routed into the activation pathway first, then
387            get propagated to the focus widget if necessary.
388
389            If the key event doesn't involve modifiers,
390            we deliver to the focus widget first, thus allowing
391            it to get "normal text" without interference
392            from acceleration.
393
394            Of course, this can also be problematic: if there
395            is a widget with focus, then it will swallow
396            all "normal text" accelerators.
397         */
398
399         if (!special_handling_of_unmodified_accelerators) {
400
401                 /* XXX note that for a brief moment, the conditional above
402                  * included "|| (ev->state & mask)" so as to enforce the
403                  * implication of special_handling_of_UNMODIFIED_accelerators.
404                  * however, this forces any key that GTK doesn't allow and that
405                  * we have an alternative (see next comment) for to be
406                  * automatically sent through the accel groups activation
407                  * pathway, which prevents individual widgets & canvas items
408                  * from ever seeing it if is used by a key binding.
409                  * 
410                  * specifically, this hid Ctrl-down-arrow from MIDI region
411                  * views because it is also bound to an action.
412                  *
413                  * until we have a robust, clean binding system, this
414                  * quirk will have to remain in place.
415                  */
416
417                 /* pretend that certain key events that GTK does not allow
418                    to be used as accelerators are actually something that
419                    it does allow. but only where there are no modifiers.
420                 */
421
422                 uint32_t fakekey = ev->keyval;
423
424                 if (Gtkmm2ext::possibly_translate_keyval_to_make_legal_accelerator (fakekey)) {
425                         DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tactivate (was %1 now %2) without special hanlding of unmodified accels\n",
426                                                                           ev->keyval, fakekey));
427
428                         GdkModifierType mod = GdkModifierType (ev->state);
429
430                         mod = GdkModifierType (mod & gtk_accelerator_get_default_mod_mask());
431 #ifdef GTKOSX
432                         /* GTK on OS X is currently (February 2012) setting both
433                            the Meta and Mod2 bits in the event modifier state if 
434                            the Command key is down.
435
436                            gtk_accel_groups_activate() does not invoke any of the logic
437                            that gtk_window_activate_key() will that sorts out that stupid
438                            state of affairs, and as a result it fails to find a match
439                            for the key event and the current set of accelerators.
440
441                            to fix this, if the meta bit is set, remove the mod2 bit
442                            from the modifier. this assumes that our bindings use Primary
443                            which will have set the meta bit in the accelerator entry.
444                         */
445                         if (mod & GDK_META_MASK) {
446                                 mod = GdkModifierType (mod & ~GDK_MOD2_MASK);
447                         }
448 #endif
449
450                         if (allow_activating && gtk_accel_groups_activate(G_OBJECT(win), fakekey, mod)) {
451                                 DEBUG_TRACE (DEBUG::Accelerators, "\taccel group activated by fakekey\n");
452                                 return true;
453                         }
454                 }
455         }
456
457         if (!special_handling_of_unmodified_accelerators || (ev->state & mask)) {
458
459                 /* no special handling or there are modifiers in effect: accelerate first */
460
461                 DEBUG_TRACE (DEBUG::Accelerators, "\tactivate, then propagate\n");
462
463                 if (allow_activating) {
464                         DEBUG_TRACE (DEBUG::Accelerators, "\tsending to window\n");
465                         if (gtk_window_activate_key (win, ev)) {
466                                 DEBUG_TRACE (DEBUG::Accelerators, "\t\thandled\n");
467                                 return true;
468                         }
469                 } else {
470                         DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
471                 }
472
473                 DEBUG_TRACE (DEBUG::Accelerators, "\tnot accelerated, now propagate\n");
474
475                 return gtk_window_propagate_key_event (win, ev);
476         }
477
478         /* no modifiers, propagate first */
479
480         DEBUG_TRACE (DEBUG::Accelerators, "\tpropagate, then activate\n");
481
482         if (!gtk_window_propagate_key_event (win, ev)) {
483                 DEBUG_TRACE (DEBUG::Accelerators, "\tpropagation didn't handle, so activate\n");
484                 if (allow_activating) {
485                         return gtk_window_activate_key (win, ev);
486                 } else {
487                         DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
488                 }
489
490         } else {
491                 DEBUG_TRACE (DEBUG::Accelerators, "\thandled by propagate\n");
492                 return true;
493         }
494
495         DEBUG_TRACE (DEBUG::Accelerators, "\tnot handled\n");
496         return true;
497 }
498
499 Glib::RefPtr<Gdk::Pixbuf>
500 get_xpm (std::string name)
501 {
502         if (!xpm_map[name]) {
503
504                 SearchPath spath(ARDOUR::ardour_data_search_path());
505
506                 spath.add_subdirectory_to_paths("pixmaps");
507
508                 std::string data_file_path;
509
510                 if(!find_file_in_search_path (spath, name, data_file_path)) {
511                         fatal << string_compose (_("cannot find XPM file for %1"), name) << endmsg;
512                 }
513
514                 try {
515                         xpm_map[name] =  Gdk::Pixbuf::create_from_file (data_file_path);
516                 } catch(const Glib::Error& e)   {
517                         warning << "Caught Glib::Error: " << e.what() << endmsg;
518                 }
519         }
520
521         return xpm_map[name];
522 }
523
524 std::string
525 get_icon_path (const char* cname)
526 {
527         string name = cname;
528         name += X_(".png");
529
530         SearchPath spath(ARDOUR::ardour_data_search_path());
531
532         spath.add_subdirectory_to_paths("icons");
533
534         std::string data_file_path;
535
536         if (!find_file_in_search_path (spath, name, data_file_path)) {
537                 fatal << string_compose (_("cannot find icon image for %1 using %2"), name, spath.to_string()) << endmsg;
538         }
539
540         return data_file_path;
541 }
542
543 Glib::RefPtr<Gdk::Pixbuf>
544 get_icon (const char* cname)
545 {
546         Glib::RefPtr<Gdk::Pixbuf> img;
547         try {
548                 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname));
549         } catch (const Gdk::PixbufError &e) {
550                 cerr << "Caught PixbufError: " << e.what() << endl;
551         } catch (...) {
552                 g_message("Caught ... ");
553         }
554
555         return img;
556 }
557
558 string
559 longest (vector<string>& strings)
560 {
561         if (strings.empty()) {
562                 return string ("");
563         }
564
565         vector<string>::iterator longest = strings.begin();
566         string::size_type longest_length = (*longest).length();
567
568         vector<string>::iterator i = longest;
569         ++i;
570
571         while (i != strings.end()) {
572
573                 string::size_type len = (*i).length();
574
575                 if (len > longest_length) {
576                         longest = i;
577                         longest_length = len;
578                 }
579
580                 ++i;
581         }
582
583         return *longest;
584 }
585
586 bool
587 key_is_legal_for_numeric_entry (guint keyval)
588 {
589         switch (keyval) {
590         case GDK_minus:
591         case GDK_plus:
592         case GDK_period:
593         case GDK_comma:
594         case GDK_0:
595         case GDK_1:
596         case GDK_2:
597         case GDK_3:
598         case GDK_4:
599         case GDK_5:
600         case GDK_6:
601         case GDK_7:
602         case GDK_8:
603         case GDK_9:
604         case GDK_KP_Add:
605         case GDK_KP_Subtract:
606         case GDK_KP_Decimal:
607         case GDK_KP_0:
608         case GDK_KP_1:
609         case GDK_KP_2:
610         case GDK_KP_3:
611         case GDK_KP_4:
612         case GDK_KP_5:
613         case GDK_KP_6:
614         case GDK_KP_7:
615         case GDK_KP_8:
616         case GDK_KP_9:
617         case GDK_Return:
618         case GDK_BackSpace:
619         case GDK_Delete:
620         case GDK_KP_Enter:
621         case GDK_Home:
622         case GDK_End:
623         case GDK_Left:
624         case GDK_Right:
625                 return true;
626
627         default:
628                 break;
629         }
630
631         return false;
632 }
633 void
634 set_pango_fontsize ()
635 {
636         long val = ARDOUR::Config->get_font_scale();
637
638         /* FT2 rendering - used by GnomeCanvas, sigh */
639
640         pango_ft2_font_map_set_resolution ((PangoFT2FontMap*) pango_ft2_font_map_for_display(), val/1024, val/1024);
641
642         /* Cairo rendering, in case there is any */
643
644         pango_cairo_font_map_set_resolution ((PangoCairoFontMap*) pango_cairo_font_map_get_default(), val/1024);
645 }
646
647 void
648 reset_dpi ()
649 {
650         long val = ARDOUR::Config->get_font_scale();
651         set_pango_fontsize ();
652         /* Xft rendering */
653
654         gtk_settings_set_long_property (gtk_settings_get_default(),
655                                         "gtk-xft-dpi", val, "ardour");
656         DPIReset();//Emit Signal
657 }
658
659 void
660 resize_window_to_proportion_of_monitor (Gtk::Window* window, int max_width, int max_height)
661 {
662         Glib::RefPtr<Gdk::Screen> screen = window->get_screen ();
663         Gdk::Rectangle monitor_rect;
664         screen->get_monitor_geometry (0, monitor_rect);
665
666         int const w = std::min (int (monitor_rect.get_width() * 0.8), max_width);
667         int const h = std::min (int (monitor_rect.get_height() * 0.8), max_height);
668
669         window->resize (w, h);
670 }
671
672
673 /** Replace _ with __ in a string; for use with menu item text to make underscores displayed correctly */
674 string
675 escape_underscores (string const & s)
676 {
677         string o;
678         string::size_type const N = s.length ();
679
680         for (string::size_type i = 0; i < N; ++i) {
681                 if (s[i] == '_') {
682                         o += "__";
683                 } else {
684                         o += s[i];
685                 }
686         }
687
688         return o;
689 }
690
691 /** Replace < and > with &lt; and &gt; respectively to make < > display correctly in markup strings */
692 string
693 escape_angled_brackets (string const & s)
694 {
695         string o = s;
696         boost::replace_all (o, "<", "&lt;");
697         boost::replace_all (o, ">", "&gt;");
698         return o;
699 }
700
701 Gdk::Color
702 unique_random_color (list<Gdk::Color>& used_colors)
703 {
704         Gdk::Color newcolor;
705
706         while (1) {
707
708                 /* avoid neon/glowing tones by limiting them to the
709                    "inner section" (paler) of a color wheel/circle.
710                 */
711
712                 const int32_t max_saturation = 48000; // 65535 would open up the whole color wheel
713
714                 newcolor.set_red (random() % max_saturation);
715                 newcolor.set_blue (random() % max_saturation);
716                 newcolor.set_green (random() % max_saturation);
717
718                 if (used_colors.size() == 0) {
719                         used_colors.push_back (newcolor);
720                         return newcolor;
721                 }
722
723                 for (list<Gdk::Color>::iterator i = used_colors.begin(); i != used_colors.end(); ++i) {
724                   Gdk::Color c = *i;
725                         float rdelta, bdelta, gdelta;
726
727                         rdelta = newcolor.get_red() - c.get_red();
728                         bdelta = newcolor.get_blue() - c.get_blue();
729                         gdelta = newcolor.get_green() - c.get_green();
730
731                         if (sqrt (rdelta*rdelta + bdelta*bdelta + gdelta*gdelta) > 25.0) {
732                                 used_colors.push_back (newcolor);
733                                 return newcolor;
734                         }
735                 }
736
737                 /* XXX need throttle here to make sure we don't spin for ever */
738         }
739 }