Superficial code cleanup.
[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 #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         cerr << "got style for " << widget_style_name << endl;
352
353         if (attr == "fg") {
354                 return Gdk::Color (&style->fg[state]);
355         }
356
357         if (attr == "bg") {
358                 cerr << "returning color from bg\n";
359                 return Gdk::Color (&style->bg[state]);
360         }
361
362         if (attr == "light") {
363                 return Gdk::Color (&style->light[state]);
364         }
365
366         if (attr == "dark") {
367                 return Gdk::Color (&style->dark[state]);
368         }
369
370         if (attr == "mid") {
371                 return Gdk::Color (&style->mid[state]);
372         }
373
374         if (attr == "text") {
375                 return Gdk::Color (&style->text[state]);
376         }
377
378         if (attr == "base") {
379                 return Gdk::Color (&style->base[state]);
380         }
381
382         if (attr == "text_aa") {
383                 return Gdk::Color (&style->text_aa[state]);
384         }
385
386         error << string_compose (_("unknown style attribute %1 requested for color; using \"red\""), attr) << endmsg;
387         return Gdk::Color ("red");
388 }
389
390
391 bool 
392 canvas_item_visible (ArdourCanvas::Item* item)
393 {
394         return (item->gobj()->object.flags & GNOME_CANVAS_ITEM_VISIBLE) ? true : false;
395 }
396
397 void
398 set_color (Gdk::Color& c, int rgb)
399 {
400         c.set_rgb((rgb >> 16)*256, ((rgb & 0xff00) >> 8)*256, (rgb & 0xff)*256);
401 }
402
403 #ifdef GTKOSX
404 extern "C" {
405         gboolean gdk_quartz_possibly_forward (GdkEvent*);
406 }
407 #endif
408
409 bool
410 key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev)
411 {
412         GtkWindow* win = window.gobj();
413         GtkWidget* focus = gtk_window_get_focus (win);
414         bool special_handling_of_unmodified_accelerators = false;
415
416 #undef DEBUG_ACCELERATOR_HANDLING
417 #ifdef  DEBUG_ACCELERATOR_HANDLING
418         bool debug = (getenv ("ARDOUR_DEBUG_ACCELERATOR_HANDLING") != 0);
419 #endif
420         if (focus) {
421                 if (GTK_IS_ENTRY(focus) || Keyboard::some_magic_widget_has_focus()) {
422                         special_handling_of_unmodified_accelerators = true;
423                 } 
424         } 
425
426 #ifdef DEBUG_ACCELERATOR_HANDLING
427         if (debug) {
428                 cerr << "Win = " << win << " Key event: code = " << ev->keyval << " state = " << hex << ev->state << dec << " special handling ? " 
429                      << special_handling_of_unmodified_accelerators
430                      << endl;
431         }
432 #endif
433
434         /* This exists to allow us to override the way GTK handles
435            key events. The normal sequence is:
436
437            a) event is delivered to a GtkWindow
438            b) accelerators/mnemonics are activated
439            c) if (b) didn't handle the event, propagate to
440                the focus widget and/or focus chain
441
442            The problem with this is that if the accelerators include
443            keys without modifiers, such as the space bar or the 
444            letter "e", then pressing the key while typing into
445            a text entry widget results in the accelerator being
446            activated, instead of the desired letter appearing
447            in the text entry.
448
449            There is no good way of fixing this, but this
450            represents a compromise. The idea is that 
451            key events involving modifiers (not Shift)
452            get routed into the activation pathway first, then
453            get propagated to the focus widget if necessary.
454            
455            If the key event doesn't involve modifiers,
456            we deliver to the focus widget first, thus allowing
457            it to get "normal text" without interference
458            from acceleration.
459
460            Of course, this can also be problematic: if there
461            is a widget with focus, then it will swallow
462            all "normal text" accelerators.
463         */
464
465
466         if (!special_handling_of_unmodified_accelerators) {
467
468                 /* pretend that certain key events that GTK does not allow
469                    to be used as accelerators are actually something that
470                    it does allow.
471                 */
472
473                 uint32_t fakekey = ev->keyval;
474
475                 if (possibly_translate_keyval_to_make_legal_accelerator (fakekey)) {
476                         if (gtk_accel_groups_activate(G_OBJECT(win), fakekey, GdkModifierType(ev->state))) {
477                                 return true;
478                         }
479
480 #ifdef GTKOSX
481                         int oldval = ev->keyval;
482                         ev->keyval = fakekey;
483                         if (gdk_quartz_possibly_forward ((GdkEvent*) ev)) {
484                                 return true;
485                         }
486                         ev->keyval = oldval;
487 #endif
488                 }
489         }
490
491         /* consider all relevant modifiers but not LOCK or SHIFT */
492
493         guint mask = (Keyboard::RelevantModifierKeyMask & ~(Gdk::SHIFT_MASK|Gdk::LOCK_MASK));
494
495         if (!special_handling_of_unmodified_accelerators || (ev->state & mask)) {
496
497                 /* no special handling or there are modifiers in effect: accelerate first */
498
499 #ifdef DEBUG_ACCELERATOR_HANDLING
500                 if (debug) {
501                         cerr << "\tactivate, then propagate\n";
502                 }
503 #endif
504 #ifdef GTKOSX
505                 if (gdk_quartz_possibly_forward ((GdkEvent*) ev)) {
506                         return true;
507                 }
508 #endif
509                 if (!gtk_window_activate_key (win, ev)) {
510 #ifdef DEBUG_ACCELERATOR_HANDLING
511                         if (debug) {
512                                 cerr << "\tnot accelerated, now propagate\n";
513                         }
514 #endif
515                         return gtk_window_propagate_key_event (win, ev);
516                 } else {
517 #ifdef DEBUG_ACCELERATOR_HANDLING
518                         if (debug) {
519                                 cerr << "\taccelerated - done.\n";
520                         }
521 #endif
522                         return true;
523                 } 
524         }
525         
526         /* no modifiers, propagate first */
527         
528 #ifdef DEBUG_ACCELERATOR_HANDLING
529         if (debug) {
530                 cerr << "\tpropagate, then activate\n";
531         }
532 #endif
533         if (!gtk_window_propagate_key_event (win, ev)) {
534 #ifdef DEBUG_ACCELERATOR_HANDLING
535                 if (debug) {
536                         cerr << "\tpropagation didn't handle, so activate\n";
537                 }
538 #endif
539 #ifdef GTKOSX
540                 if (gdk_quartz_possibly_forward ((GdkEvent*) ev)) {
541                         return true;
542                 }
543 #endif
544                 return gtk_window_activate_key (win, ev);
545         } else {
546 #ifdef DEBUG_ACCELERATOR_HANDLING
547                 if (debug) {
548                         cerr << "\thandled by propagate\n";
549                 }
550 #endif
551                 return true;
552         }
553
554 #ifdef DEBUG_ACCELERATOR_HANDLING
555         if (debug) {
556                 cerr << "\tnot handled\n";
557         }
558 #endif
559         return true;
560 }
561
562 Glib::RefPtr<Gdk::Pixbuf>       
563 get_xpm (std::string name)
564 {
565         if (!xpm_map[name]) {
566
567                 SearchPath spath(ARDOUR::ardour_search_path());
568                 spath += ARDOUR::system_data_search_path();
569                 
570                 spath.add_subdirectory_to_paths("pixmaps");
571                 
572                 sys::path data_file_path;
573                 
574                 if(!find_file_in_search_path (spath, name, data_file_path)) {
575                         fatal << string_compose (_("cannot find XPM file for %1"), name) << endmsg;
576                 }
577                 
578                 try {
579                         xpm_map[name] =  Gdk::Pixbuf::create_from_file (data_file_path.to_string());
580                 } catch(const Glib::Error& e)   {
581                         warning << "Caught Glib::Error: " << e.what() << endmsg;
582                 }
583         }
584
585         return xpm_map[name];
586 }
587
588
589 Glib::RefPtr<Gdk::Pixbuf>       
590 get_icon (const char* cname)
591 {
592         string name = cname;
593         name += X_(".png");
594
595         SearchPath spath(ARDOUR::ardour_search_path());
596         spath += ARDOUR::system_data_search_path();
597
598         spath.add_subdirectory_to_paths("icons");
599
600         sys::path data_file_path;
601
602         if (!find_file_in_search_path (spath, name, data_file_path)) {
603                 fatal << string_compose (_("cannot find icon image for %1"), name) << endmsg;
604         }
605
606         Glib::RefPtr<Gdk::Pixbuf> img;
607         try {
608                 img = Gdk::Pixbuf::create_from_file (data_file_path.to_string());
609         } catch (const Gdk::PixbufError &e) {
610                 cerr << "Caught PixbufError: " << e.what() << endl;
611         } catch (...) {
612                 g_message("Caught ... ");
613         }
614
615         return img;
616 }
617
618 string
619 longest (vector<string>& strings)
620 {
621         if (strings.empty()) {
622                 return string ("");
623         }
624
625         vector<string>::iterator longest = strings.begin();
626         string::size_type longest_length = (*longest).length();
627         
628         vector<string>::iterator i = longest;
629         ++i;
630
631         while (i != strings.end()) {
632                 
633                 string::size_type len = (*i).length();
634                 
635                 if (len > longest_length) {
636                         longest = i;
637                         longest_length = len;
638                 } 
639                 
640                 ++i;
641         }
642         
643         return *longest;
644 }
645
646 bool
647 key_is_legal_for_numeric_entry (guint keyval)
648 {
649         switch (keyval) {
650         case GDK_minus:
651         case GDK_plus:
652         case GDK_period:
653         case GDK_comma:
654         case GDK_0:
655         case GDK_1:
656         case GDK_2:
657         case GDK_3:
658         case GDK_4:
659         case GDK_5:
660         case GDK_6:
661         case GDK_7:
662         case GDK_8:
663         case GDK_9:
664         case GDK_KP_Add:
665         case GDK_KP_Subtract:
666         case GDK_KP_Decimal:
667         case GDK_KP_0:
668         case GDK_KP_1:
669         case GDK_KP_2:
670         case GDK_KP_3:
671         case GDK_KP_4:
672         case GDK_KP_5:
673         case GDK_KP_6:
674         case GDK_KP_7:
675         case GDK_KP_8:
676         case GDK_KP_9:
677         case GDK_Return:
678         case GDK_BackSpace:
679         case GDK_Delete:
680         case GDK_KP_Enter:
681         case GDK_Home:
682         case GDK_End:
683         case GDK_Left:
684         case GDK_Right:
685                 return true;
686                 
687         default:
688                 break;
689         }
690
691         return false;
692 }
693 void
694 set_pango_fontsize ()
695 {
696         long val = ARDOUR::Config->get_font_scale();
697
698         /* FT2 rendering - used by GnomeCanvas, sigh */
699
700         pango_ft2_font_map_set_resolution ((PangoFT2FontMap*) pango_ft2_font_map_for_display(), val/1024, val/1024);
701
702         /* Cairo rendering, in case there is any */
703         
704         pango_cairo_font_map_set_resolution ((PangoCairoFontMap*) pango_cairo_font_map_get_default(), val/1024);
705 }
706
707 void
708 reset_dpi ()
709 {
710         long val = ARDOUR::Config->get_font_scale();
711         set_pango_fontsize ();
712         /* Xft rendering */
713
714         gtk_settings_set_long_property (gtk_settings_get_default(),
715                                         "gtk-xft-dpi", val, "ardour");
716         DPIReset();//Emit Signal
717 }
718
719 bool
720 possibly_translate_keyval_to_make_legal_accelerator (uint32_t& keyval)
721 {
722         int fakekey = GDK_VoidSymbol;
723
724         switch (keyval) {
725         case GDK_Tab:
726         case GDK_ISO_Left_Tab:
727                 fakekey = GDK_nabla;
728                 break;
729                 
730         case GDK_Up:
731                 fakekey = GDK_uparrow;
732                 break;
733                 
734         case GDK_Down:
735                 fakekey = GDK_downarrow;
736                 break;
737                 
738         case GDK_Right:
739                 fakekey = GDK_rightarrow;
740                 break;
741                 
742         case GDK_Left:
743                 fakekey = GDK_leftarrow;
744                 break;
745                 
746         default:
747                 break;
748         }
749         
750         if (fakekey != GDK_VoidSymbol) {
751                 keyval = fakekey;
752                 return true;
753         } 
754
755         return false;
756 }
757