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