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