Fixed overflow issue. Code originally meant to truncate the 64 bit integer did not...
[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_ARDOUR_EVENTS_PATCH
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_ARDOUR_EVENTS_PATCH
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_ARDOUR_EVENTS_PATCH
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_ARDOUR_EVENTS_PATCH
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         //cerr << "xpm path = " << ARDOUR::find_data_file(name, "pixmaps") << endl;//DEBUG
562         if (!xpm_map[name]) {
563                 try {
564                         xpm_map[name] = Gdk::Pixbuf::create_from_file (ARDOUR::find_data_file(name, "pixmaps"));
565                 }
566                 catch(const Glib::Error& e)     {
567                 warning << "Caught Glib::Error: " << e.what() << endmsg;
568                 }
569         }
570                 
571         return (xpm_map[name]);
572 }
573
574 Glib::RefPtr<Gdk::Pixbuf>       
575 get_icon (const char* cname)
576 {
577         string name = cname;
578         name += X_(".png");
579
580         string path = ARDOUR::find_data_file (name, "icons");
581
582         if (path.empty()) {
583                 fatal << string_compose (_("cannot find icon image for %1"), name) << endmsg;
584                 /*NOTREACHED*/
585         }
586
587         Glib::RefPtr<Gdk::Pixbuf> img;
588         try {
589                 img = Gdk::Pixbuf::create_from_file (path);
590         }
591         catch (const Gdk::PixbufError &e)
592     {
593         cerr << "Caught PixbufError: " << e.what() << endl;
594     }
595     catch (...)
596     {
597         g_message("Caught ... ");
598     }
599
600         return img;
601 }
602
603 string
604 longest (vector<string>& strings)
605 {
606         if (strings.empty()) {
607                 return string ("");
608         }
609
610         vector<string>::iterator longest = strings.begin();
611         string::size_type longest_length = (*longest).length();
612         
613         vector<string>::iterator i = longest;
614         ++i;
615
616         while (i != strings.end()) {
617                 
618                 string::size_type len = (*i).length();
619                 
620                 if (len > longest_length) {
621                         longest = i;
622                         longest_length = len;
623                 } 
624                 
625                 ++i;
626         }
627         
628         return *longest;
629 }
630
631 bool
632 key_is_legal_for_numeric_entry (guint keyval)
633 {
634         switch (keyval) {
635         case GDK_minus:
636         case GDK_plus:
637         case GDK_period:
638         case GDK_comma:
639         case GDK_0:
640         case GDK_1:
641         case GDK_2:
642         case GDK_3:
643         case GDK_4:
644         case GDK_5:
645         case GDK_6:
646         case GDK_7:
647         case GDK_8:
648         case GDK_9:
649         case GDK_KP_Add:
650         case GDK_KP_Subtract:
651         case GDK_KP_Decimal:
652         case GDK_KP_0:
653         case GDK_KP_1:
654         case GDK_KP_2:
655         case GDK_KP_3:
656         case GDK_KP_4:
657         case GDK_KP_5:
658         case GDK_KP_6:
659         case GDK_KP_7:
660         case GDK_KP_8:
661         case GDK_KP_9:
662         case GDK_Return:
663         case GDK_BackSpace:
664         case GDK_Delete:
665         case GDK_KP_Enter:
666         case GDK_Home:
667         case GDK_End:
668         case GDK_Left:
669         case GDK_Right:
670                 return true;
671                 
672         default:
673                 break;
674         }
675
676         return false;
677 }
678 void
679 set_pango_fontsize ()
680 {
681         long val = ARDOUR::Config->get_font_scale();
682
683         /* FT2 rendering - used by GnomeCanvas, sigh */
684
685         pango_ft2_font_map_set_resolution ((PangoFT2FontMap*) pango_ft2_font_map_for_display(), val/1024, val/1024);
686
687         /* Cairo rendering, in case there is any */
688         
689         // pango_cairo_font_map_set_resolution ((PangoCairoFontMap*) pango_cairo_font_map_get_default(), val/1024);
690 }
691
692 void
693 reset_dpi ()
694 {
695         long val = ARDOUR::Config->get_font_scale();
696         set_pango_fontsize ();
697         /* Xft rendering */
698
699         gtk_settings_set_long_property (gtk_settings_get_default(),
700                                         "gtk-xft-dpi", val, "ardour");
701         DPIReset();//Emit Signal
702 }
703
704 bool
705 possibly_translate_keyval_to_make_legal_accelerator (uint32_t& keyval)
706 {
707         int fakekey = GDK_VoidSymbol;
708
709         switch (keyval) {
710         case GDK_Tab:
711         case GDK_ISO_Left_Tab:
712                 fakekey = GDK_nabla;
713                 break;
714                 
715         case GDK_Up:
716                 fakekey = GDK_uparrow;
717                 break;
718                 
719         case GDK_Down:
720                 fakekey = GDK_downarrow;
721                 break;
722                 
723         case GDK_Right:
724                 fakekey = GDK_rightarrow;
725                 break;
726                 
727         case GDK_Left:
728                 fakekey = GDK_leftarrow;
729                 break;
730                 
731         default:
732                 break;
733         }
734         
735         if (fakekey != GDK_VoidSymbol) {
736                 keyval = fakekey;
737                 return true;
738         } 
739
740         return false;
741 }
742