menu item says Fader and Pan, so the automation track should say the same for consistency
[ardour.git] / gtk2_ardour / utils.cc
index 7e58d0e2f7ef3ee9c7f0b5fe3f8cd29520ab6c99..a762887d3f98f12eb0cd1381fc93018932e1935c 100644 (file)
     along with this program; if not, write to the Free Software
     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 
-    $Id$
 */
 
+#include <pango/pangoft2.h> // for fontmap resolution control for GnomeCanvas
+#include <pango/pangocairo.h> // for fontmap resolution control for GnomeCanvas
+
 #include <cstdlib>
 #include <cctype>
 #include <fstream>
 #include <sys/stat.h>
 #include <libart_lgpl/art_misc.h>
+#include <gtkmm/rc.h>
 #include <gtkmm/window.h>
 #include <gtkmm/combo.h>
 #include <gtkmm/label.h>
@@ -31,6 +34,7 @@
 
 #include <gtkmm2ext/utils.h>
 #include <ardour/ardour.h>
+#include <ardour/configuration.h>
 
 #include "ardour_ui.h"
 #include "keyboard.h"
@@ -45,12 +49,30 @@ using namespace sigc;
 using namespace Glib;
 using namespace PBD;
 
+sigc::signal<void>  DPIReset;
+
+int
+pixel_width (const ustring& str, Pango::FontDescription& font)
+{
+       Label foo;
+       Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout ("");
+
+       layout->set_font_description (font);
+       layout->set_text (str);
+
+       int width, height;
+       Gtkmm2ext::get_ink_pixel_size (layout, width, height);
+       return width;
+}
+
 ustring
-fit_to_pixels (const ustring& str, int pixel_width, Pango::FontDescription& font, int& actual_width)
+fit_to_pixels (const ustring& str, int pixel_width, Pango::FontDescription& font, int& actual_width, bool with_ellipses)
 {
        Label foo;
        Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout ("");
-       
+       ustring::size_type shorter_by = 0;
+       ustring txt;
+
        layout->set_font_description (font);
 
        actual_width = 0;
@@ -59,9 +81,11 @@ fit_to_pixels (const ustring& str, int pixel_width, Pango::FontDescription& font
        ustring::iterator last = ustr.end();
        --last; /* now points at final entry */
 
+       txt = ustr;
+
        while (!ustr.empty()) {
 
-               layout->set_text (ustr);
+               layout->set_text (txt);
 
                int width, height;
                Gtkmm2ext::get_ink_pixel_size (layout, width, height);
@@ -71,17 +95,24 @@ fit_to_pixels (const ustring& str, int pixel_width, Pango::FontDescription& font
                        break;
                }
                
-               ustr.erase (last);
-               --last;
+               ustr.erase (last--);
+               shorter_by++;
+
+               if (with_ellipses && shorter_by > 3) {
+                       txt = ustr;
+                       txt += "...";
+               } else {
+                       txt = ustr;
+               }
        }
 
-       return ustr;
+       return txt;
 }
 
 gint
 just_hide_it (GdkEventAny *ev, Gtk::Window *win)
 {
-       win->hide_all ();
+       win->hide ();
        return TRUE;
 }
 
@@ -109,7 +140,7 @@ xpm2rgb (const char** xpm, uint32_t& w, uint32_t& h)
                return 0;
        }
 
-       savergb = rgb = (unsigned char*)art_alloc (h * w * 3);
+       savergb = rgb = (unsigned char*) malloc (h * w * 3);
        
        // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
        for (t = 0; t < colors; ++t) {
@@ -152,7 +183,7 @@ xpm2rgba (const char** xpm, uint32_t& w, uint32_t& h)
                return 0;
        }
 
-       savergb = rgb = (unsigned char*)art_alloc (h * w * 4);
+       savergb = rgb = (unsigned char*) malloc (h * w * 4);
        
        // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
 
@@ -210,7 +241,7 @@ get_canvas_points (string who, uint32_t npoints)
        return new ArdourCanvas::Points (npoints);
 }
 
-Pango::FontDescription
+Pango::FontDescription*
 get_font_for_style (string widgetname)
 {
        Gtk::Window window (WINDOW_TOPLEVEL);
@@ -222,67 +253,23 @@ get_font_for_style (string widgetname)
        foobar.ensure_style();
 
        style = foobar.get_style ();
-       return style->get_font();
-}
 
-gint
-pane_handler (GdkEventButton* ev, Gtk::Paned* pane)
-{
-       if (ev->window != Gtkmm2ext::get_paned_handle (*pane)) {
-               return FALSE;
-       }
-
-       if (Keyboard::is_delete_event (ev)) {
-
-               gint pos;
-               gint cmp;
+       Glib::RefPtr<const Pango::Layout> layout = foobar.get_layout();
+       
+       PangoFontDescription *pfd = (PangoFontDescription *)pango_layout_get_font_description((PangoLayout *)layout->gobj());
+       
+       if (!pfd) {
                
-               pos = pane->get_position ();
-
-               if (dynamic_cast<VPaned*>(pane)) {
-                       cmp = pane->get_height();
-               } else {
-                       cmp = pane->get_width();
-               }
-
-               /* we have to use approximations here because we can't predict the
-                  exact position or sizes of the pane (themes, etc)
-               */
-
-               if (pos < 10 || abs (pos - cmp) < 10) {
-
-                       /* already collapsed: restore it (note that this is cast from a pointer value to int, which is tricky on 64bit */
-                       
-                       pane->set_position ((intptr_t) pane->get_data ("rpos"));
-
-               } else {        
-
-                       int collapse_direction;
-
-                       /* store the current position */
+               /* layout inherited its font description from a PangoContext */
 
-                       pane->set_data ("rpos", (gpointer) pos);
-
-                       /* collapse to show the relevant child in full */
-                       
-                       collapse_direction = (intptr_t) pane->get_data ("collapse-direction");
-
-                       if (collapse_direction) {
-                               pane->set_position (1);
-                       } else {
-                               if (dynamic_cast<VPaned*>(pane)) {
-                                       pane->set_position (pane->get_height());
-                               } else {
-                                       pane->set_position (pane->get_width());
-                               }
-                       }
-               }
-
-               return TRUE;
+               PangoContext* ctxt = (PangoContext*) pango_layout_get_context ((PangoLayout*) layout->gobj());
+               pfd =  pango_context_get_font_description (ctxt);
+               return new Pango::FontDescription (pfd, true); /* make a copy */
        } 
 
-       return FALSE;
+       return new Pango::FontDescription (pfd, true); /* make a copy */
 }
+
 uint32_t
 rgba_from_style (string style, uint32_t r, uint32_t g, uint32_t b, uint32_t a, string attr, int state, bool rgba)
 {
@@ -310,6 +297,7 @@ rgba_from_style (string style, uint32_t r, uint32_t g, uint32_t b, uint32_t a, s
                        r = waverc->fg[state].red / 257;
                        g = waverc->fg[state].green / 257;
                        b = waverc->fg[state].blue / 257;
                        /* what a hack ... "a" is for "active" */
                        if (state == Gtk::STATE_NORMAL && rgba) {
                                a = waverc->fg[GTK_STATE_ACTIVE].red / 257;
@@ -341,6 +329,61 @@ rgba_from_style (string style, uint32_t r, uint32_t g, uint32_t b, uint32_t a, s
        }
 }
 
+
+Gdk::Color
+color_from_style (string widget_style_name, int state, string attr)
+{
+       GtkStyle* style;
+
+       style = gtk_rc_get_style_by_paths (gtk_settings_get_default(),
+                                          widget_style_name.c_str(),
+                                          0, G_TYPE_NONE);
+
+       if (!style) {
+               error << string_compose (_("no style found for %1, using red"), style) << endmsg;
+               return Gdk::Color ("red");
+       }
+
+       cerr << "got style for " << widget_style_name << endl;
+
+       if (attr == "fg") {
+               return Gdk::Color (&style->fg[state]);
+       }
+
+       if (attr == "bg") {
+               cerr << "returning color from bg\n";
+               return Gdk::Color (&style->bg[state]);
+       }
+
+       if (attr == "light") {
+               return Gdk::Color (&style->light[state]);
+       }
+
+       if (attr == "dark") {
+               return Gdk::Color (&style->dark[state]);
+       }
+
+       if (attr == "mid") {
+               return Gdk::Color (&style->mid[state]);
+       }
+
+       if (attr == "text") {
+               return Gdk::Color (&style->text[state]);
+       }
+
+       if (attr == "base") {
+               return Gdk::Color (&style->base[state]);
+       }
+
+       if (attr == "text_aa") {
+               return Gdk::Color (&style->text_aa[state]);
+       }
+
+       error << string_compose (_("unknown style attribute %1 requested for color; using \"red\""), attr) << endmsg;
+       return Gdk::Color ("red");
+}
+
+
 bool 
 canvas_item_visible (ArdourCanvas::Item* item)
 {
@@ -353,19 +396,50 @@ set_color (Gdk::Color& c, int rgb)
        c.set_rgb((rgb >> 16)*256, ((rgb & 0xff00) >> 8)*256, (rgb & 0xff)*256);
 }
 
+#ifdef GTKOSX
+extern "C" {
+       gboolean gdk_quartz_possibly_forward (GdkEvent*);
+}
+#endif
+
 bool
 key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev)
 {
        GtkWindow* win = window.gobj();
        GtkWidget* focus = gtk_window_get_focus (win);
        bool special_handling_of_unmodified_accelerators = false;
+       bool allow_activating = true;
 
+#undef DEBUG_ACCELERATOR_HANDLING
+#ifdef  DEBUG_ACCELERATOR_HANDLING
+       //bool debug = (getenv ("ARDOUR_DEBUG_ACCELERATOR_HANDLING") != 0);
+       bool debug=true;
+#endif
        if (focus) {
-               if (GTK_IS_ENTRY(focus)) {
+               if (GTK_IS_ENTRY(focus) || Keyboard::some_magic_widget_has_focus()) {
                        special_handling_of_unmodified_accelerators = true;
-               }
+               } 
        } 
 
+#ifdef GTKOSX
+       /* should this be universally true? */
+       if (Keyboard::some_magic_widget_has_focus ()) {
+               allow_activating = false;
+       }
+#endif
+
+#ifdef DEBUG_ACCELERATOR_HANDLING
+       if (debug) {
+               cerr << "Win = " << win << " Key event: code = " << ev->keyval << " state = " << hex << ev->state << dec << " special handling ? " 
+                    << special_handling_of_unmodified_accelerators
+                    << " magic widget focus ? "
+                    << Keyboard::some_magic_widget_has_focus()
+                    << " allow_activation ? "
+                    << allow_activating
+                    << endl;
+       }
+#endif
+
        /* This exists to allow us to override the way GTK handles
           key events. The normal sequence is:
 
@@ -405,70 +479,145 @@ key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev)
                   it does allow.
                */
 
-               int ret = false;
+               uint32_t fakekey = ev->keyval;
 
-               switch (ev->keyval) {
-               case GDK_Up:
-                       ret = gtk_accel_groups_activate(G_OBJECT(win), GDK_uparrow, GdkModifierType(ev->state));
-                       break;
+               if (possibly_translate_keyval_to_make_legal_accelerator (fakekey)) {
+                       if (allow_activating && gtk_accel_groups_activate(G_OBJECT(win), fakekey, GdkModifierType(ev->state))) {
+                               return true;
+                       }
 
-               case GDK_Down:
-                       ret = gtk_accel_groups_activate(G_OBJECT(win), GDK_downarrow, GdkModifierType(ev->state));
-                       break;
+#ifdef GTKOSX
+                       if (allow_activating) {
+                               int oldval = ev->keyval;
+                               ev->keyval = fakekey;
+                               if (gdk_quartz_possibly_forward ((GdkEvent*) ev)) {
+                                       return true;
+                               }
+                               ev->keyval = oldval;
+                       }
+#endif
+               }
+       }
 
-               case GDK_Right:
-                       ret = gtk_accel_groups_activate(G_OBJECT(win), GDK_rightarrow, GdkModifierType(ev->state));
-                       break;
+       /* consider all relevant modifiers but not LOCK or SHIFT */
 
-               case GDK_Left:
-                       ret = gtk_accel_groups_activate(G_OBJECT(win), GDK_leftarrow, GdkModifierType(ev->state));
-                       break;
+       guint mask = (Keyboard::RelevantModifierKeyMask & ~(Gdk::SHIFT_MASK|Gdk::LOCK_MASK));
 
-               default:
-                       break;
-               }
+       if (!special_handling_of_unmodified_accelerators || (ev->state & mask)) {
 
-               if (ret) {
-                       return true;
+               /* no special handling or there are modifiers in effect: accelerate first */
+
+#ifdef DEBUG_ACCELERATOR_HANDLING
+               if (debug) {
+                       cerr << "\tactivate, then propagate\n";
                }
-       }
-               
-       if (!special_handling_of_unmodified_accelerators ||
-           ev->state & (Gdk::MOD1_MASK|
-                        Gdk::MOD3_MASK|
-                        Gdk::MOD4_MASK|
-                        Gdk::MOD5_MASK|
-                        Gdk::CONTROL_MASK)) {
+#endif
 
-               /* no special handling or modifiers in effect: accelerate first */
+               if (allow_activating) {
+#ifdef GTKOSX
+                       if (gdk_quartz_possibly_forward ((GdkEvent*) ev)) {
+                               return true;
+                       }
+#endif
+                       if (gtk_window_activate_key (win, ev)) {
+                               return true;
+                       }
+               }
 
-               if (!gtk_window_activate_key (win, ev)) {
-                       return gtk_window_propagate_key_event (win, ev);
-               } else {
-                       return true;
-               } 
+#ifdef DEBUG_ACCELERATOR_HANDLING
+               if (debug) {
+                       cerr << "\tnot accelerated, now propagate\n";
+               }
+#endif
+               return gtk_window_propagate_key_event (win, ev);
        }
        
        /* no modifiers, propagate first */
        
+#ifdef DEBUG_ACCELERATOR_HANDLING
+       if (debug) {
+               cerr << "\tpropagate, then activate\n";
+       }
+#endif
        if (!gtk_window_propagate_key_event (win, ev)) {
-               return gtk_window_activate_key (win, ev);
-       } 
+#ifdef DEBUG_ACCELERATOR_HANDLING
+               if (debug) {
+                       cerr << "\tpropagation didn't handle, so activate\n";
+               }
+#endif
 
+               if (allow_activating) {
+                       
+#ifdef GTKOSX
+                       if (gdk_quartz_possibly_forward ((GdkEvent*) ev)) {
+                               return true;
+                       }
+#endif
+                       return gtk_window_activate_key (win, ev);
+               } 
+                       
+       } else {
+#ifdef DEBUG_ACCELERATOR_HANDLING
+               if (debug) {
+                       cerr << "\thandled by propagate\n";
+               }
+#endif
+               return true;
+       }
 
+#ifdef DEBUG_ACCELERATOR_HANDLING
+       if (debug) {
+               cerr << "\tnot handled\n";
+       }
+#endif
        return true;
 }
 
 Glib::RefPtr<Gdk::Pixbuf>      
 get_xpm (std::string name)
 {
+       //cerr << "xpm path = " << ARDOUR::find_data_file(name, "pixmaps") << endl;//DEBUG
        if (!xpm_map[name]) {
-               xpm_map[name] = Gdk::Pixbuf::create_from_file (ARDOUR::find_data_file(name, "pixmaps"));
+               try {
+                       xpm_map[name] = Gdk::Pixbuf::create_from_file (ARDOUR::find_data_file(name, "pixmaps"));
+               }
+               catch(const Glib::Error& e)     {
+               warning << "Caught Glib::Error: " << e.what() << endmsg;
+               }
        }
                
        return (xpm_map[name]);
 }
 
+Glib::RefPtr<Gdk::Pixbuf>      
+get_icon (const char* cname)
+{
+       string name = cname;
+       name += X_(".png");
+
+       string path = ARDOUR::find_data_file (name, "icons");
+
+       if (path.empty()) {
+               fatal << string_compose (_("cannot find icon image for %1"), name) << endmsg;
+               /*NOTREACHED*/
+       }
+
+       Glib::RefPtr<Gdk::Pixbuf> img;
+       try {
+               img = Gdk::Pixbuf::create_from_file (path);
+       }
+       catch (const Gdk::PixbufError &e)
+    {
+        cerr << "Caught PixbufError: " << e.what() << endl;
+    }
+    catch (...)
+    {
+        g_message("Caught ... ");
+    }
+
+       return img;
+}
+
 string
 longest (vector<string>& strings)
 {
@@ -496,3 +645,172 @@ longest (vector<string>& strings)
        
        return *longest;
 }
+
+bool
+key_is_legal_for_numeric_entry (guint keyval)
+{
+       switch (keyval) {
+       case GDK_minus:
+       case GDK_plus:
+       case GDK_period:
+       case GDK_comma:
+       case GDK_0:
+       case GDK_1:
+       case GDK_2:
+       case GDK_3:
+       case GDK_4:
+       case GDK_5:
+       case GDK_6:
+       case GDK_7:
+       case GDK_8:
+       case GDK_9:
+       case GDK_KP_Add:
+       case GDK_KP_Subtract:
+       case GDK_KP_Decimal:
+       case GDK_KP_0:
+       case GDK_KP_1:
+       case GDK_KP_2:
+       case GDK_KP_3:
+       case GDK_KP_4:
+       case GDK_KP_5:
+       case GDK_KP_6:
+       case GDK_KP_7:
+       case GDK_KP_8:
+       case GDK_KP_9:
+       case GDK_Return:
+       case GDK_BackSpace:
+       case GDK_Delete:
+       case GDK_KP_Enter:
+       case GDK_Home:
+       case GDK_End:
+       case GDK_Left:
+       case GDK_Right:
+               return true;
+               
+       default:
+               break;
+       }
+
+       return false;
+}
+void
+set_pango_fontsize ()
+{
+       long val = ARDOUR::Config->get_font_scale();
+
+       /* FT2 rendering - used by GnomeCanvas, sigh */
+
+       pango_ft2_font_map_set_resolution ((PangoFT2FontMap*) pango_ft2_font_map_for_display(), val/1024, val/1024);
+
+       /* Cairo rendering, in case there is any */
+       
+       // pango_cairo_font_map_set_resolution ((PangoCairoFontMap*) pango_cairo_font_map_get_default(), val/1024);
+}
+
+void
+reset_dpi ()
+{
+       long val = ARDOUR::Config->get_font_scale();
+       set_pango_fontsize ();
+       /* Xft rendering */
+
+       gtk_settings_set_long_property (gtk_settings_get_default(),
+                                       "gtk-xft-dpi", val, "ardour");
+       DPIReset();//Emit Signal
+}
+
+bool
+possibly_translate_keyval_to_make_legal_accelerator (uint32_t& keyval)
+{
+       int fakekey = GDK_VoidSymbol;
+
+       switch (keyval) {
+       case GDK_Tab:
+       case GDK_ISO_Left_Tab:
+               fakekey = GDK_nabla;
+               break;
+               
+       case GDK_Up:
+               fakekey = GDK_uparrow;
+               break;
+               
+       case GDK_Down:
+               fakekey = GDK_downarrow;
+               break;
+               
+       case GDK_Right:
+               fakekey = GDK_rightarrow;
+               break;
+               
+       case GDK_Left:
+               fakekey = GDK_leftarrow;
+               break;
+               
+       default:
+               break;
+       }
+       
+       if (fakekey != GDK_VoidSymbol) {
+               keyval = fakekey;
+               return true;
+       } 
+
+       return false;
+}
+               
+inline guint8
+convert_color_channel (guint8 src,
+                      guint8 alpha)
+{
+       return alpha ? ((guint (src) << 8) - src) / alpha : 0;
+}
+
+void
+convert_bgra_to_rgba (guint8 const* src,
+                     guint8*       dst,
+                     int           width,
+                     int           height)
+{
+       guint8 const* src_pixel = src;
+       guint8*       dst_pixel = dst;
+       
+       for (int y = 0; y < height; y++)
+               for (int x = 0; x < width; x++)
+               {
+                       dst_pixel[0] = convert_color_channel (src_pixel[2],
+                                                             src_pixel[3]);
+                       dst_pixel[1] = convert_color_channel (src_pixel[1],
+                                                             src_pixel[3]);
+                       dst_pixel[2] = convert_color_channel (src_pixel[0],
+                                                             src_pixel[3]);
+                       dst_pixel[3] = src_pixel[3];
+                       
+                       dst_pixel += 4;
+                       src_pixel += 4;
+               }
+}
+
+Glib::RefPtr<Gdk::Pixbuf>
+pixbuf_from_ustring(const ustring& name, Pango::FontDescription* font, int clip_width, int clip_height)
+{
+       Glib::RefPtr<Gdk::Pixbuf> buf = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB, true, 8, clip_width, clip_height);
+       cairo_surface_t* surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, clip_width, clip_height);
+       cairo_t* cr = cairo_create (surface);
+       cairo_text_extents_t te;
+
+       cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 1.0);
+       cairo_select_font_face (cr, font->get_family().c_str(),
+                               CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
+       cairo_set_font_size (cr,  font->get_size() / Pango::SCALE);
+       cairo_text_extents (cr, name.c_str(), &te);
+       
+       cairo_move_to (cr, 0.5, 0.5 - te.height / 2 - te.y_bearing + clip_height / 2);
+       cairo_show_text (cr, name.c_str());
+       
+       convert_bgra_to_rgba(cairo_image_surface_get_data (surface), buf->get_pixels(), clip_width, clip_height);
+
+       cairo_destroy(cr);
+       cairo_surface_destroy(surface);
+
+       return buf;
+}