many important changes to configuration system and specific parameters
[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     $Id$
19 */
20
21 #include <cstdlib>
22 #include <cctype>
23 #include <fstream>
24 #include <sys/stat.h>
25 #include <libart_lgpl/art_misc.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 <gtkmm2ext/utils.h>
33 #include <ardour/ardour.h>
34
35 #include "ardour_ui.h"
36 #include "keyboard.h"
37 #include "utils.h"
38 #include "i18n.h"
39 #include "rgb_macros.h"
40 #include "canvas_impl.h"
41
42 using namespace std;
43 using namespace Gtk;
44 using namespace sigc;
45 using namespace Glib;
46 using namespace PBD;
47
48 ustring
49 fit_to_pixels (const ustring& str, int pixel_width, Pango::FontDescription& font, int& actual_width)
50 {
51         Label foo;
52         Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout ("");
53         
54         layout->set_font_description (font);
55
56         actual_width = 0;
57
58         ustring ustr = str;
59         ustring::iterator last = ustr.end();
60         --last; /* now points at final entry */
61
62         while (!ustr.empty()) {
63
64                 layout->set_text (ustr);
65
66                 int width, height;
67                 Gtkmm2ext::get_ink_pixel_size (layout, width, height);
68
69                 if (width < pixel_width) {
70                         actual_width = width;
71                         break;
72                 }
73                 
74                 ustr.erase (last);
75                 --last;
76         }
77
78         return ustr;
79 }
80
81 gint
82 just_hide_it (GdkEventAny *ev, Gtk::Window *win)
83 {
84         win->hide_all ();
85         return TRUE;
86 }
87
88 /* xpm2rgb copied from nixieclock, which bore the legend:
89
90     nixieclock - a nixie desktop timepiece
91     Copyright (C) 2000 Greg Ercolano, erco@3dsite.com
92
93     and was released under the GPL.
94 */
95
96 unsigned char*
97 xpm2rgb (const char** xpm, uint32_t& w, uint32_t& h)
98 {
99         static long vals[256], val;
100         uint32_t t, x, y, colors, cpp;
101         unsigned char c;
102         unsigned char *savergb, *rgb;
103         
104         // PARSE HEADER
105         
106         if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
107                 error << string_compose (_("bad XPM header %1"), xpm[0])
108                       << endmsg;
109                 return 0;
110         }
111
112         savergb = rgb = (unsigned char*)art_alloc (h * w * 3);
113         
114         // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
115         for (t = 0; t < colors; ++t) {
116                 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
117                 vals[c] = val;
118         }
119         
120         // COLORMAP -> RGB CONVERSION
121         //    Get low 3 bytes from vals[]
122         //
123
124         const char *p;
125         for (y = h-1; y > 0; --y) {
126
127                 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 3) {
128                         val = vals[(int)*p++];
129                         *(rgb+2) = val & 0xff; val >>= 8;  // 2:B
130                         *(rgb+1) = val & 0xff; val >>= 8;  // 1:G
131                         *(rgb+0) = val & 0xff;             // 0:R
132                 }
133         }
134
135         return (savergb);
136 }
137
138 unsigned char*
139 xpm2rgba (const char** xpm, uint32_t& w, uint32_t& h)
140 {
141         static long vals[256], val;
142         uint32_t t, x, y, colors, cpp;
143         unsigned char c;
144         unsigned char *savergb, *rgb;
145         char transparent;
146
147         // PARSE HEADER
148
149         if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
150                 error << string_compose (_("bad XPM header %1"), xpm[0])
151                       << endmsg;
152                 return 0;
153         }
154
155         savergb = rgb = (unsigned char*)art_alloc (h * w * 4);
156         
157         // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
158
159         if (strstr (xpm[1], "None")) {
160                 sscanf (xpm[1], "%c", &transparent);
161                 t = 1;
162         } else {
163                 transparent = 0;
164                 t = 0;
165         }
166
167         for (; t < colors; ++t) {
168                 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
169                 vals[c] = val;
170         }
171         
172         // COLORMAP -> RGB CONVERSION
173         //    Get low 3 bytes from vals[]
174         //
175
176         const char *p;
177         for (y = h-1; y > 0; --y) {
178
179                 char alpha;
180
181                 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 4) {
182
183                         if (transparent && (*p++ == transparent)) {
184                                 alpha = 0;
185                                 val = 0;
186                         } else {
187                                 alpha = 255;
188                                 val = vals[(int)*p];
189                         }
190
191                         *(rgb+3) = alpha;                  // 3: alpha
192                         *(rgb+2) = val & 0xff; val >>= 8;  // 2:B
193                         *(rgb+1) = val & 0xff; val >>= 8;  // 1:G
194                         *(rgb+0) = val & 0xff;             // 0:R
195                 }
196         }
197
198         return (savergb);
199 }
200
201 ArdourCanvas::Points*
202 get_canvas_points (string who, uint32_t npoints)
203 {
204         // cerr << who << ": wants " << npoints << " canvas points" << endl;
205 #ifdef TRAP_EXCESSIVE_POINT_REQUESTS
206         if (npoints > (uint32_t) gdk_screen_width() + 4) {
207                 abort ();
208         }
209 #endif
210         return new ArdourCanvas::Points (npoints);
211 }
212
213 Pango::FontDescription
214 get_font_for_style (string widgetname)
215 {
216         Gtk::Window window (WINDOW_TOPLEVEL);
217         Gtk::Label foobar;
218         Glib::RefPtr<Gtk::Style> style;
219
220         window.add (foobar);
221         foobar.set_name (widgetname);
222         foobar.ensure_style();
223
224         style = foobar.get_style ();
225         return style->get_font();
226 }
227
228 gint
229 pane_handler (GdkEventButton* ev, Gtk::Paned* pane)
230 {
231         if (ev->window != Gtkmm2ext::get_paned_handle (*pane)) {
232                 return FALSE;
233         }
234
235         if (Keyboard::is_delete_event (ev)) {
236
237                 gint pos;
238                 gint cmp;
239                 
240                 pos = pane->get_position ();
241
242                 if (dynamic_cast<VPaned*>(pane)) {
243                         cmp = pane->get_height();
244                 } else {
245                         cmp = pane->get_width();
246                 }
247
248                 /* we have to use approximations here because we can't predict the
249                    exact position or sizes of the pane (themes, etc)
250                 */
251
252                 if (pos < 10 || abs (pos - cmp) < 10) {
253
254                         /* already collapsed: restore it (note that this is cast from a pointer value to int, which is tricky on 64bit */
255                         
256                         pane->set_position ((intptr_t) pane->get_data ("rpos"));
257
258                 } else {        
259
260                         int collapse_direction;
261
262                         /* store the current position */
263
264                         pane->set_data ("rpos", (gpointer) pos);
265
266                         /* collapse to show the relevant child in full */
267                         
268                         collapse_direction = (intptr_t) pane->get_data ("collapse-direction");
269
270                         if (collapse_direction) {
271                                 pane->set_position (1);
272                         } else {
273                                 if (dynamic_cast<VPaned*>(pane)) {
274                                         pane->set_position (pane->get_height());
275                                 } else {
276                                         pane->set_position (pane->get_width());
277                                 }
278                         }
279                 }
280
281                 return TRUE;
282         } 
283
284         return FALSE;
285 }
286 uint32_t
287 rgba_from_style (string style, uint32_t r, uint32_t g, uint32_t b, uint32_t a, string attr, int state, bool rgba)
288 {
289         /* In GTK+2, styles aren't set up correctly if the widget is not
290            attached to a toplevel window that has a screen pointer.
291         */
292
293         static Gtk::Window* window = 0;
294
295         if (window == 0) {
296                 window = new Window (WINDOW_TOPLEVEL);
297         }
298
299         Gtk::Label foo;
300         
301         window->add (foo);
302
303         foo.set_name (style);
304         foo.ensure_style ();
305         
306         GtkRcStyle* waverc = foo.get_style()->gobj()->rc_style;
307
308         if (waverc) {
309                 if (attr == "fg") {
310                         r = waverc->fg[state].red / 257;
311                         g = waverc->fg[state].green / 257;
312                         b = waverc->fg[state].blue / 257;
313                         /* what a hack ... "a" is for "active" */
314                         if (state == Gtk::STATE_NORMAL && rgba) {
315                                 a = waverc->fg[GTK_STATE_ACTIVE].red / 257;
316                         }
317                 } else if (attr == "bg") {
318                         r = g = b = 0;
319                         r = waverc->bg[state].red / 257;
320                         g = waverc->bg[state].green / 257;
321                         b = waverc->bg[state].blue / 257;
322                 } else if (attr == "base") {
323                         r = waverc->base[state].red / 257;
324                         g = waverc->base[state].green / 257;
325                         b = waverc->base[state].blue / 257;
326                 } else if (attr == "text") {
327                         r = waverc->text[state].red / 257;
328                         g = waverc->text[state].green / 257;
329                         b = waverc->text[state].blue / 257;
330                 }
331         } else {
332                 warning << string_compose (_("missing RGBA style for \"%1\""), style) << endl;
333         }
334
335         window->remove ();
336         
337         if (state == Gtk::STATE_NORMAL && rgba) {
338                 return (uint32_t) RGBA_TO_UINT(r,g,b,a);
339         } else {
340                 return (uint32_t) RGB_TO_UINT(r,g,b);
341         }
342 }
343
344 bool 
345 canvas_item_visible (ArdourCanvas::Item* item)
346 {
347         return (item->gobj()->object.flags & GNOME_CANVAS_ITEM_VISIBLE) ? true : false;
348 }
349
350 void
351 set_color (Gdk::Color& c, int rgb)
352 {
353         c.set_rgb((rgb >> 16)*256, ((rgb & 0xff00) >> 8)*256, (rgb & 0xff)*256);
354 }
355
356 bool
357 key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev)
358 {
359         GtkWindow* win = window.gobj();
360         GtkWidget* focus = gtk_window_get_focus (win);
361         bool special_handling_of_unmodified_accelerators = false;
362
363         if (focus) {
364                 if (GTK_IS_ENTRY(focus)) {
365                         special_handling_of_unmodified_accelerators = true;
366                 }
367         } 
368
369         /* This exists to allow us to override the way GTK handles
370            key events. The normal sequence is:
371
372            a) event is delivered to a GtkWindow
373            b) accelerators/mnemonics are activated
374            c) if (b) didn't handle the event, propagate to
375                the focus widget and/or focus chain
376
377            The problem with this is that if the accelerators include
378            keys without modifiers, such as the space bar or the 
379            letter "e", then pressing the key while typing into
380            a text entry widget results in the accelerator being
381            activated, instead of the desired letter appearing
382            in the text entry.
383
384            There is no good way of fixing this, but this
385            represents a compromise. The idea is that 
386            key events involving modifiers (not Shift)
387            get routed into the activation pathway first, then
388            get propagated to the focus widget if necessary.
389            
390            If the key event doesn't involve modifiers,
391            we deliver to the focus widget first, thus allowing
392            it to get "normal text" without interference
393            from acceleration.
394
395            Of course, this can also be problematic: if there
396            is a widget with focus, then it will swallow
397            all "normal text" accelerators.
398         */
399
400
401         if (!special_handling_of_unmodified_accelerators) {
402
403                 /* pretend that certain key events that GTK does not allow
404                    to be used as accelerators are actually something that
405                    it does allow.
406                 */
407
408                 int ret = false;
409
410                 switch (ev->keyval) {
411                 case GDK_Up:
412                         ret = gtk_accel_groups_activate(G_OBJECT(win), GDK_uparrow, GdkModifierType(ev->state));
413                         break;
414
415                 case GDK_Down:
416                         ret = gtk_accel_groups_activate(G_OBJECT(win), GDK_downarrow, GdkModifierType(ev->state));
417                         break;
418
419                 case GDK_Right:
420                         ret = gtk_accel_groups_activate(G_OBJECT(win), GDK_rightarrow, GdkModifierType(ev->state));
421                         break;
422
423                 case GDK_Left:
424                         ret = gtk_accel_groups_activate(G_OBJECT(win), GDK_leftarrow, GdkModifierType(ev->state));
425                         break;
426
427                 default:
428                         break;
429                 }
430
431                 if (ret) {
432                         return true;
433                 }
434         }
435                 
436         if (!special_handling_of_unmodified_accelerators ||
437             ev->state & (Gdk::MOD1_MASK|
438                          Gdk::MOD3_MASK|
439                          Gdk::MOD4_MASK|
440                          Gdk::MOD5_MASK|
441                          Gdk::CONTROL_MASK)) {
442
443                 /* no special handling or modifiers in effect: accelerate first */
444
445                 if (!gtk_window_activate_key (win, ev)) {
446                         return gtk_window_propagate_key_event (win, ev);
447                 } else {
448                         return true;
449                 } 
450         }
451         
452         /* no modifiers, propagate first */
453         
454         if (!gtk_window_propagate_key_event (win, ev)) {
455                 return gtk_window_activate_key (win, ev);
456         } 
457
458
459         return true;
460 }
461
462 Glib::RefPtr<Gdk::Pixbuf>       
463 get_xpm (std::string name)
464 {
465         if (!xpm_map[name]) {
466                 xpm_map[name] = Gdk::Pixbuf::create_from_file (ARDOUR::find_data_file(name, "pixmaps"));
467         }
468                 
469         return (xpm_map[name]);
470 }
471
472 string
473 longest (vector<string>& strings)
474 {
475         if (strings.empty()) {
476                 return string ("");
477         }
478
479         vector<string>::iterator longest = strings.begin();
480         string::size_type longest_length = (*longest).length();
481         
482         vector<string>::iterator i = longest;
483         ++i;
484
485         while (i != strings.end()) {
486                 
487                 string::size_type len = (*i).length();
488                 
489                 if (len > longest_length) {
490                         longest = i;
491                         longest_length = len;
492                 } 
493                 
494                 ++i;
495         }
496         
497         return *longest;
498 }