2 Copyright (C) 2003 Paul Davis
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.
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.
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.
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>
32 #include <gtkmm2ext/utils.h>
33 #include <ardour/ardour.h>
35 #include "ardour_ui.h"
39 #include "rgb_macros.h"
40 #include "canvas_impl.h"
48 short_version (string orig, string::size_type target_length)
50 /* this tries to create a recognizable abbreviation
51 of "orig" by removing characters until we meet
52 a certain target length.
54 note that we deliberately leave digits in the result
59 string::size_type pos;
61 /* remove white-space and punctuation, starting at end */
63 while (orig.length() > target_length) {
64 if ((pos = orig.find_last_of (_("\"\n\t ,<.>/?:;'[{}]~`!@#$%^&*()_-+="))) == string::npos) {
67 orig.replace (pos, 1, "");
70 /* remove lower-case vowels, starting at end */
72 while (orig.length() > target_length) {
73 if ((pos = orig.find_last_of (_("aeiou"))) == string::npos) {
76 orig.replace (pos, 1, "");
79 /* remove upper-case vowels, starting at end */
81 while (orig.length() > target_length) {
82 if ((pos = orig.find_last_of (_("AEIOU"))) == string::npos) {
85 orig.replace (pos, 1, "");
88 /* remove lower-case consonants, starting at end */
90 while (orig.length() > target_length) {
91 if ((pos = orig.find_last_of (_("bcdfghjklmnpqrtvwxyz"))) == string::npos) {
94 orig.replace (pos, 1, "");
97 /* remove upper-case consonants, starting at end */
99 while (orig.length() > target_length) {
100 if ((pos = orig.find_last_of (_("BCDFGHJKLMNPQRTVWXYZ"))) == string::npos) {
103 orig.replace (pos, 1, "");
106 /* whatever the length is now, use it */
112 fit_to_pixels (const ustring& str, int pixel_width, Pango::FontDescription& font, int& actual_width)
115 Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout ("");
117 layout->set_font_description (font);
122 ustring::iterator last = ustr.end();
123 --last; /* now points at final entry */
125 while (!ustr.empty()) {
127 layout->set_text (ustr);
130 Gtkmm2ext::get_ink_pixel_size (layout, width, height);
132 if (width < pixel_width) {
133 actual_width = width;
145 atoi (const string& s)
147 return atoi (s.c_str());
151 atof (const string& s)
153 return atof (s.c_str());
157 internationalize (const char **array)
161 for (uint32_t i = 0; array[i]; ++i) {
162 v.push_back (_(array[i]));
169 just_hide_it (GdkEventAny *ev, Gtk::Window *win)
175 /* xpm2rgb copied from nixieclock, which bore the legend:
177 nixieclock - a nixie desktop timepiece
178 Copyright (C) 2000 Greg Ercolano, erco@3dsite.com
180 and was released under the GPL.
184 xpm2rgb (const char** xpm, uint32_t& w, uint32_t& h)
186 static long vals[256], val;
187 uint32_t t, x, y, colors, cpp;
189 unsigned char *savergb, *rgb;
193 if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
194 error << string_compose (_("bad XPM header %1"), xpm[0])
199 savergb = rgb = (unsigned char*)art_alloc (h * w * 3);
201 // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
202 for (t = 0; t < colors; ++t) {
203 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
207 // COLORMAP -> RGB CONVERSION
208 // Get low 3 bytes from vals[]
212 for (y = h-1; y > 0; --y) {
214 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 3) {
215 val = vals[(int)*p++];
216 *(rgb+2) = val & 0xff; val >>= 8; // 2:B
217 *(rgb+1) = val & 0xff; val >>= 8; // 1:G
218 *(rgb+0) = val & 0xff; // 0:R
226 xpm2rgba (const char** xpm, uint32_t& w, uint32_t& h)
228 static long vals[256], val;
229 uint32_t t, x, y, colors, cpp;
231 unsigned char *savergb, *rgb;
236 if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
237 error << string_compose (_("bad XPM header %1"), xpm[0])
242 savergb = rgb = (unsigned char*)art_alloc (h * w * 4);
244 // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
246 if (strstr (xpm[1], "None")) {
247 sscanf (xpm[1], "%c", &transparent);
254 for (; t < colors; ++t) {
255 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
259 // COLORMAP -> RGB CONVERSION
260 // Get low 3 bytes from vals[]
264 for (y = h-1; y > 0; --y) {
268 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 4) {
270 if (transparent && (*p++ == transparent)) {
278 *(rgb+3) = alpha; // 3: alpha
279 *(rgb+2) = val & 0xff; val >>= 8; // 2:B
280 *(rgb+1) = val & 0xff; val >>= 8; // 1:G
281 *(rgb+0) = val & 0xff; // 0:R
288 ArdourCanvas::Points*
289 get_canvas_points (string who, uint32_t npoints)
291 // cerr << who << ": wants " << npoints << " canvas points" << endl;
292 #ifdef TRAP_EXCESSIVE_POINT_REQUESTS
293 if (npoints > (uint32_t) gdk_screen_width() + 4) {
297 return new ArdourCanvas::Points (npoints);
301 int_from_hex (char hic, char loc)
303 int hi; /* hi byte */
304 int lo; /* low byte */
308 if( ('0'<=hi) && (hi<='9') ) {
310 } else if( ('a'<= hi) && (hi<= 'f') ) {
312 } else if( ('A'<=hi) && (hi<='F') ) {
318 if( ('0'<=lo) && (lo<='9') ) {
320 } else if( ('a'<=lo) && (lo<='f') ) {
322 } else if( ('A'<=lo) && (lo<='F') ) {
326 return lo + (16 * hi);
330 url_decode (string& url)
332 string::iterator last;
333 string::iterator next;
335 for (string::iterator i = url.begin(); i != url.end(); ++i) {
341 if (url.length() <= 3) {
347 --last; /* points at last char */
348 --last; /* points at last char - 1 */
350 for (string::iterator i = url.begin(); i != last; ) {
361 if (isxdigit (*i) && isxdigit (*next)) {
362 /* replace first digit with char */
363 *i = int_from_hex (*i,*next);
364 ++i; /* points at 2nd of 2 digits */
373 Pango::FontDescription
374 get_font_for_style (string widgetname)
376 Gtk::Window window (WINDOW_TOPLEVEL);
378 Glib::RefPtr<Style> style;
381 foobar.set_name (widgetname);
382 foobar.ensure_style();
384 style = foobar.get_style ();
385 return style->get_font();
389 pane_handler (GdkEventButton* ev, Gtk::Paned* pane)
391 if (ev->window != Gtkmm2ext::get_paned_handle (*pane)) {
395 if (Keyboard::is_delete_event (ev)) {
400 pos = pane->get_position ();
402 if (dynamic_cast<VPaned*>(pane)) {
403 cmp = pane->get_height();
405 cmp = pane->get_width();
408 /* we have to use approximations here because we can't predict the
409 exact position or sizes of the pane (themes, etc)
412 if (pos < 10 || abs (pos - cmp) < 10) {
414 /* already collapsed: restore it (note that this is cast from a pointer value to int, which is tricky on 64bit */
416 pane->set_position ((intptr_t) pane->get_data ("rpos"));
420 int collapse_direction;
422 /* store the current position */
424 pane->set_data ("rpos", (gpointer) pos);
426 /* collapse to show the relevant child in full */
428 collapse_direction = (intptr_t) pane->get_data ("collapse-direction");
430 if (collapse_direction) {
431 pane->set_position (1);
433 if (dynamic_cast<VPaned*>(pane)) {
434 pane->set_position (pane->get_height());
436 pane->set_position (pane->get_width());
447 rgba_from_style (string style, uint32_t r, uint32_t g, uint32_t b, uint32_t a, string attr, int state, bool rgba)
449 /* In GTK+2, styles aren't set up correctly if the widget is not
450 attached to a toplevel window that has a screen pointer.
453 static Gtk::Window* window = 0;
456 window = new Window (WINDOW_TOPLEVEL);
463 foo.set_name (style);
466 GtkRcStyle* waverc = foo.get_style()->gobj()->rc_style;
470 r = waverc->fg[state].red / 257;
471 g = waverc->fg[state].green / 257;
472 b = waverc->fg[state].blue / 257;
473 /* what a hack ... "a" is for "active" */
474 if (state == Gtk::STATE_NORMAL && rgba) {
475 a = waverc->fg[GTK_STATE_ACTIVE].red / 257;
477 } else if (attr == "bg") {
479 r = waverc->bg[state].red / 257;
480 g = waverc->bg[state].green / 257;
481 b = waverc->bg[state].blue / 257;
482 } else if (attr == "base") {
483 r = waverc->base[state].red / 257;
484 g = waverc->base[state].green / 257;
485 b = waverc->base[state].blue / 257;
486 } else if (attr == "text") {
487 r = waverc->text[state].red / 257;
488 g = waverc->text[state].green / 257;
489 b = waverc->text[state].blue / 257;
492 warning << string_compose (_("missing RGBA style for \"%1\""), style) << endl;
497 if (state == Gtk::STATE_NORMAL && rgba) {
498 return (uint32_t) RGBA_TO_UINT(r,g,b,a);
500 return (uint32_t) RGB_TO_UINT(r,g,b);
505 canvas_item_visible (ArdourCanvas::Item* item)
507 return (item->gobj()->object.flags & GNOME_CANVAS_ITEM_VISIBLE) ? true : false;
511 set_color (Gdk::Color& c, int rgb)
513 c.set_rgb((rgb >> 16)*256, ((rgb & 0xff00) >> 8)*256, (rgb & 0xff)*256);
517 key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev)
519 GtkWindow* win = window.gobj();
520 GtkWidget* focus = gtk_window_get_focus (win);
521 bool special_handling_of_unmodified_accelerators = false;
524 if (GTK_IS_ENTRY(focus)) {
525 special_handling_of_unmodified_accelerators = true;
529 /* This exists to allow us to override the way GTK handles
530 key events. The normal sequence is:
532 a) event is delivered to a GtkWindow
533 b) accelerators/mnemonics are activated
534 c) if (b) didn't handle the event, propagate to
535 the focus widget and/or focus chain
537 The problem with this is that if the accelerators include
538 keys without modifiers, such as the space bar or the
539 letter "e", then pressing the key while typing into
540 a text entry widget results in the accelerator being
541 activated, instead of the desired letter appearing
544 There is no good way of fixing this, but this
545 represents a compromise. The idea is that
546 key events involving modifiers (not Shift)
547 get routed into the activation pathway first, then
548 get propagated to the focus widget if necessary.
550 If the key event doesn't involve modifiers,
551 we deliver to the focus widget first, thus allowing
552 it to get "normal text" without interference
555 Of course, this can also be problematic: if there
556 is a widget with focus, then it will swallow
557 all "normal text" accelerators.
560 if (!special_handling_of_unmodified_accelerators ||
561 ev->state & (Gdk::MOD1_MASK|
569 /* no special handling or modifiers in effect: accelerate first */
571 if (!gtk_window_activate_key (win, ev)) {
572 return gtk_window_propagate_key_event (win, ev);
578 /* no modifiers, propagate first */
580 if (!gtk_window_propagate_key_event (win, ev)) {
581 return gtk_window_activate_key (win, ev);
588 Glib::RefPtr<Gdk::Pixbuf>
589 get_xpm (std::string name)
591 if (!xpm_map[name]) {
592 xpm_map[name] = Gdk::Pixbuf::create_from_file (ARDOUR::find_data_file(name, "pixmaps"));
595 return (xpm_map[name]);
599 length2string (const int32_t frames, const float sample_rate)
601 int secs = (int) (frames / sample_rate);
602 int hrs = secs / 3600;
603 secs -= (hrs * 3600);
604 int mins = secs / 60;
607 int total_secs = (hrs * 3600) + (mins * 60) + secs;
608 int frames_remaining = (int) floor (frames - (total_secs * sample_rate));
609 float fractional_secs = (float) frames_remaining / sample_rate;
611 char duration_str[32];
612 sprintf (duration_str, "%02d:%02d:%05.2f", hrs, mins, (float) secs + fractional_secs);