fix a few things related to fit-to-tracks and toggle-visual-state (from 2.0)
[ardour.git] / gtk2_ardour / utils.cc
1 /*
2     Copyright (C) 2003 Paul Davis 
3
4     This program is free software; you an redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 #include <pango/pangoft2.h> // for fontmap resolution control for GnomeCanvas
21 #include <pango/pangocairo.h> // for fontmap resolution control for GnomeCanvas
22
23 #include <cstdlib>
24 #include <cctype>
25 #include <fstream>
26 #include <sys/stat.h>
27 #include <libart_lgpl/art_misc.h>
28 #include <gtkmm/rc.h>
29 #include <gtkmm/window.h>
30 #include <gtkmm/combo.h>
31 #include <gtkmm/label.h>
32 #include <gtkmm/paned.h>
33 #include <gtk/gtkpaned.h>
34
35 #include <pbd/file_utils.h>
36
37 #include <gtkmm2ext/utils.h>
38 #include <ardour/configuration.h>
39 #include <ardour/configuration.h>
40
41 #include <ardour/filesystem_paths.h>
42
43 #include "ardour_ui.h"
44 #include "keyboard.h"
45 #include "utils.h"
46 #include "i18n.h"
47 #include "rgb_macros.h"
48 #include "canvas_impl.h"
49
50 using namespace std;
51 using namespace Gtk;
52 using namespace sigc;
53 using namespace Glib;
54 using namespace PBD;
55
56 sigc::signal<void>  DPIReset;
57
58 int
59 pixel_width (const ustring& str, Pango::FontDescription& font)
60 {
61         Label foo;
62         Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout ("");
63
64         layout->set_font_description (font);
65         layout->set_text (str);
66
67         int width, height;
68         Gtkmm2ext::get_ink_pixel_size (layout, width, height);
69         return width;
70 }
71
72 ustring
73 fit_to_pixels (const ustring& str, int pixel_width, Pango::FontDescription& font, int& actual_width, bool with_ellipses)
74 {
75         Label foo;
76         Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout ("");
77         ustring::size_type shorter_by = 0;
78         ustring txt;
79
80         layout->set_font_description (font);
81
82         actual_width = 0;
83
84         ustring ustr = str;
85         ustring::iterator last = ustr.end();
86         --last; /* now points at final entry */
87
88         txt = ustr;
89
90         while (!ustr.empty()) {
91
92                 layout->set_text (txt);
93
94                 int width, height;
95                 Gtkmm2ext::get_ink_pixel_size (layout, width, height);
96
97                 if (width < pixel_width) {
98                         actual_width = width;
99                         break;
100                 }
101                 
102                 ustr.erase (last--);
103                 shorter_by++;
104
105                 if (with_ellipses && shorter_by > 3) {
106                         txt = ustr;
107                         txt += "...";
108                 } else {
109                         txt = ustr;
110                 }
111         }
112
113         return txt;
114 }
115
116 gint
117 just_hide_it (GdkEventAny *ev, Gtk::Window *win)
118 {
119         win->hide ();
120         return TRUE;
121 }
122
123 /* xpm2rgb copied from nixieclock, which bore the legend:
124
125     nixieclock - a nixie desktop timepiece
126     Copyright (C) 2000 Greg Ercolano, erco@3dsite.com
127
128     and was released under the GPL.
129 */
130
131 unsigned char*
132 xpm2rgb (const char** xpm, uint32_t& w, uint32_t& h)
133 {
134         static long vals[256], val;
135         uint32_t t, x, y, colors, cpp;
136         unsigned char c;
137         unsigned char *savergb, *rgb;
138         
139         // PARSE HEADER
140         
141         if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
142                 error << string_compose (_("bad XPM header %1"), xpm[0])
143                       << endmsg;
144                 return 0;
145         }
146
147         savergb = rgb = (unsigned char*) malloc (h * w * 3);
148         
149         // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
150         for (t = 0; t < colors; ++t) {
151                 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
152                 vals[c] = val;
153         }
154         
155         // COLORMAP -> RGB CONVERSION
156         //    Get low 3 bytes from vals[]
157         //
158
159         const char *p;
160         for (y = h-1; y > 0; --y) {
161
162                 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 3) {
163                         val = vals[(int)*p++];
164                         *(rgb+2) = val & 0xff; val >>= 8;  // 2:B
165                         *(rgb+1) = val & 0xff; val >>= 8;  // 1:G
166                         *(rgb+0) = val & 0xff;             // 0:R
167                 }
168         }
169
170         return (savergb);
171 }
172
173 unsigned char*
174 xpm2rgba (const char** xpm, uint32_t& w, uint32_t& h)
175 {
176         static long vals[256], val;
177         uint32_t t, x, y, colors, cpp;
178         unsigned char c;
179         unsigned char *savergb, *rgb;
180         char transparent;
181
182         // PARSE HEADER
183
184         if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
185                 error << string_compose (_("bad XPM header %1"), xpm[0])
186                       << endmsg;
187                 return 0;
188         }
189
190         savergb = rgb = (unsigned char*) malloc (h * w * 4);
191         
192         // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
193
194         if (strstr (xpm[1], "None")) {
195                 sscanf (xpm[1], "%c", &transparent);
196                 t = 1;
197         } else {
198                 transparent = 0;
199                 t = 0;
200         }
201
202         for (; t < colors; ++t) {
203                 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
204                 vals[c] = val;
205         }
206         
207         // COLORMAP -> RGB CONVERSION
208         //    Get low 3 bytes from vals[]
209         //
210
211         const char *p;
212         for (y = h-1; y > 0; --y) {
213
214                 char alpha;
215
216                 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 4) {
217
218                         if (transparent && (*p++ == transparent)) {
219                                 alpha = 0;
220                                 val = 0;
221                         } else {
222                                 alpha = 255;
223                                 val = vals[(int)*p];
224                         }
225
226                         *(rgb+3) = alpha;                  // 3: alpha
227                         *(rgb+2) = val & 0xff; val >>= 8;  // 2:B
228                         *(rgb+1) = val & 0xff; val >>= 8;  // 1:G
229                         *(rgb+0) = val & 0xff;             // 0:R
230                 }
231         }
232
233         return (savergb);
234 }
235
236 ArdourCanvas::Points*
237 get_canvas_points (string who, uint32_t npoints)
238 {
239         // cerr << who << ": wants " << npoints << " canvas points" << endl;
240 #ifdef TRAP_EXCESSIVE_POINT_REQUESTS
241         if (npoints > (uint32_t) gdk_screen_width() + 4) {
242                 abort ();
243         }
244 #endif
245         return new ArdourCanvas::Points (npoints);
246 }
247
248 Pango::FontDescription*
249 get_font_for_style (string widgetname)
250 {
251         Gtk::Window window (WINDOW_TOPLEVEL);
252         Gtk::Label foobar;
253         Glib::RefPtr<Gtk::Style> style;
254
255         window.add (foobar);
256         foobar.set_name (widgetname);
257         foobar.ensure_style();
258
259         style = foobar.get_style ();
260
261         Glib::RefPtr<const Pango::Layout> layout = foobar.get_layout();
262         
263         PangoFontDescription *pfd = (PangoFontDescription *)pango_layout_get_font_description((PangoLayout *)layout->gobj());
264         
265         if (!pfd) {
266                 
267                 /* layout inherited its font description from a PangoContext */
268
269                 PangoContext* ctxt = (PangoContext*) pango_layout_get_context ((PangoLayout*) layout->gobj());
270                 pfd =  pango_context_get_font_description (ctxt);
271                 return new Pango::FontDescription (pfd, true); /* make a copy */
272         } 
273
274         return new Pango::FontDescription (pfd, true); /* make a copy */
275 }
276
277 uint32_t
278 rgba_from_style (string style, uint32_t r, uint32_t g, uint32_t b, uint32_t a, string attr, int state, bool rgba)
279 {
280         /* In GTK+2, styles aren't set up correctly if the widget is not
281            attached to a toplevel window that has a screen pointer.
282         */
283
284         static Gtk::Window* window = 0;
285
286         if (window == 0) {
287                 window = new Window (WINDOW_TOPLEVEL);
288         }
289
290         Gtk::Label foo;
291         
292         window->add (foo);
293
294         foo.set_name (style);
295         foo.ensure_style ();
296         
297         GtkRcStyle* waverc = foo.get_style()->gobj()->rc_style;
298
299         if (waverc) {
300                 if (attr == "fg") {
301                         r = waverc->fg[state].red / 257;
302                         g = waverc->fg[state].green / 257;
303                         b = waverc->fg[state].blue / 257;
304  
305                         /* what a hack ... "a" is for "active" */
306                         if (state == Gtk::STATE_NORMAL && rgba) {
307                                 a = waverc->fg[GTK_STATE_ACTIVE].red / 257;
308                         }
309                 } else if (attr == "bg") {
310                         r = g = b = 0;
311                         r = waverc->bg[state].red / 257;
312                         g = waverc->bg[state].green / 257;
313                         b = waverc->bg[state].blue / 257;
314                 } else if (attr == "base") {
315                         r = waverc->base[state].red / 257;
316                         g = waverc->base[state].green / 257;
317                         b = waverc->base[state].blue / 257;
318                 } else if (attr == "text") {
319                         r = waverc->text[state].red / 257;
320                         g = waverc->text[state].green / 257;
321                         b = waverc->text[state].blue / 257;
322                 }
323         } else {
324                 warning << string_compose (_("missing RGBA style for \"%1\""), style) << endl;
325         }
326
327         window->remove ();
328         
329         if (state == Gtk::STATE_NORMAL && rgba) {
330                 return (uint32_t) RGBA_TO_UINT(r,g,b,a);
331         } else {
332                 return (uint32_t) RGB_TO_UINT(r,g,b);
333         }
334 }
335
336
337 Gdk::Color
338 color_from_style (string widget_style_name, int state, string attr)
339 {
340         GtkStyle* style;
341
342         style = gtk_rc_get_style_by_paths (gtk_settings_get_default(),
343                                            widget_style_name.c_str(),
344                                            0, G_TYPE_NONE);
345
346         if (!style) {
347                 error << string_compose (_("no style found for %1, using red"), style) << endmsg;
348                 return Gdk::Color ("red");
349         }
350
351         if (attr == "fg") {
352                 return Gdk::Color (&style->fg[state]);
353         }
354
355         if (attr == "bg") {
356                 return Gdk::Color (&style->bg[state]);
357         }
358
359         if (attr == "light") {
360                 return Gdk::Color (&style->light[state]);
361         }
362
363         if (attr == "dark") {
364                 return Gdk::Color (&style->dark[state]);
365         }
366
367         if (attr == "mid") {
368                 return Gdk::Color (&style->mid[state]);
369         }
370
371         if (attr == "text") {
372                 return Gdk::Color (&style->text[state]);
373         }
374
375         if (attr == "base") {
376                 return Gdk::Color (&style->base[state]);
377         }
378
379         if (attr == "text_aa") {
380                 return Gdk::Color (&style->text_aa[state]);
381         }
382
383         error << string_compose (_("unknown style attribute %1 requested for color; using \"red\""), attr) << endmsg;
384         return Gdk::Color ("red");
385 }
386
387 bool 
388 canvas_item_visible (ArdourCanvas::Item* item)
389 {
390         return (item->gobj()->object.flags & GNOME_CANVAS_ITEM_VISIBLE) ? true : false;
391 }
392
393 void
394 set_color (Gdk::Color& c, int rgb)
395 {
396         c.set_rgb((rgb >> 16)*256, ((rgb & 0xff00) >> 8)*256, (rgb & 0xff)*256);
397 }
398
399 #ifdef GTKOSX_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         if (!xpm_map[name]) {
562
563                 SearchPath spath(ARDOUR::ardour_search_path());
564                 spath += ARDOUR::system_data_search_path();
565                 
566                 spath.add_subdirectory_to_paths("pixmaps");
567                 
568                 sys::path data_file_path;
569                 
570                 if(!find_file_in_search_path (spath, name, data_file_path)) {
571                         fatal << string_compose (_("cannot find XPM file for %1"), name) << endmsg;
572                 }
573                 
574                 try {
575                         xpm_map[name] =  Gdk::Pixbuf::create_from_file (data_file_path.to_string());
576                 } catch(const Glib::Error& e)   {
577                         warning << "Caught Glib::Error: " << e.what() << endmsg;
578                 }
579         }
580
581         return xpm_map[name];
582 }
583
584
585 Glib::RefPtr<Gdk::Pixbuf>       
586 get_icon (const char* cname)
587 {
588         string name = cname;
589         name += X_(".png");
590
591         SearchPath spath(ARDOUR::ardour_search_path());
592         spath += ARDOUR::system_data_search_path();
593
594         spath.add_subdirectory_to_paths("icons");
595
596         sys::path data_file_path;
597
598         if (!find_file_in_search_path (spath, name, data_file_path)) {
599                 fatal << string_compose (_("cannot find icon image for %1"), name) << endmsg;
600         }
601
602         Glib::RefPtr<Gdk::Pixbuf> img;
603         try {
604                 img = Gdk::Pixbuf::create_from_file (data_file_path.to_string());
605         } catch (const Gdk::PixbufError &e) {
606                 cerr << "Caught PixbufError: " << e.what() << endl;
607         } catch (...) {
608                 g_message("Caught ... ");
609         }
610
611         return img;
612 }
613
614 string
615 longest (vector<string>& strings)
616 {
617         if (strings.empty()) {
618                 return string ("");
619         }
620
621         vector<string>::iterator longest = strings.begin();
622         string::size_type longest_length = (*longest).length();
623         
624         vector<string>::iterator i = longest;
625         ++i;
626
627         while (i != strings.end()) {
628                 
629                 string::size_type len = (*i).length();
630                 
631                 if (len > longest_length) {
632                         longest = i;
633                         longest_length = len;
634                 } 
635                 
636                 ++i;
637         }
638         
639         return *longest;
640 }
641
642 bool
643 key_is_legal_for_numeric_entry (guint keyval)
644 {
645         switch (keyval) {
646         case GDK_minus:
647         case GDK_plus:
648         case GDK_period:
649         case GDK_comma:
650         case GDK_0:
651         case GDK_1:
652         case GDK_2:
653         case GDK_3:
654         case GDK_4:
655         case GDK_5:
656         case GDK_6:
657         case GDK_7:
658         case GDK_8:
659         case GDK_9:
660         case GDK_KP_Add:
661         case GDK_KP_Subtract:
662         case GDK_KP_Decimal:
663         case GDK_KP_0:
664         case GDK_KP_1:
665         case GDK_KP_2:
666         case GDK_KP_3:
667         case GDK_KP_4:
668         case GDK_KP_5:
669         case GDK_KP_6:
670         case GDK_KP_7:
671         case GDK_KP_8:
672         case GDK_KP_9:
673         case GDK_Return:
674         case GDK_BackSpace:
675         case GDK_Delete:
676         case GDK_KP_Enter:
677         case GDK_Home:
678         case GDK_End:
679         case GDK_Left:
680         case GDK_Right:
681                 return true;
682                 
683         default:
684                 break;
685         }
686
687         return false;
688 }
689 void
690 set_pango_fontsize ()
691 {
692         long val = ARDOUR::Config->get_font_scale();
693
694         /* FT2 rendering - used by GnomeCanvas, sigh */
695
696         pango_ft2_font_map_set_resolution ((PangoFT2FontMap*) pango_ft2_font_map_for_display(), val/1024, val/1024);
697
698         /* Cairo rendering, in case there is any */
699         
700         pango_cairo_font_map_set_resolution ((PangoCairoFontMap*) pango_cairo_font_map_get_default(), val/1024);
701 }
702
703 void
704 reset_dpi ()
705 {
706         long val = ARDOUR::Config->get_font_scale();
707         set_pango_fontsize ();
708         /* Xft rendering */
709
710         gtk_settings_set_long_property (gtk_settings_get_default(),
711                                         "gtk-xft-dpi", val, "ardour");
712         DPIReset();//Emit Signal
713 }
714
715 bool
716 possibly_translate_keyval_to_make_legal_accelerator (uint32_t& keyval)
717 {
718         int fakekey = GDK_VoidSymbol;
719
720         switch (keyval) {
721         case GDK_Tab:
722         case GDK_ISO_Left_Tab:
723                 fakekey = GDK_nabla;
724                 break;
725                 
726         case GDK_Up:
727                 fakekey = GDK_uparrow;
728                 break;
729                 
730         case GDK_Down:
731                 fakekey = GDK_downarrow;
732                 break;
733                 
734         case GDK_Right:
735                 fakekey = GDK_rightarrow;
736                 break;
737                 
738         case GDK_Left:
739                 fakekey = GDK_leftarrow;
740                 break;
741                 
742         default:
743                 break;
744         }
745         
746         if (fakekey != GDK_VoidSymbol) {
747                 keyval = fakekey;
748                 return true;
749         } 
750
751         return false;
752 }
753