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