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