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