X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fgtkmm2ext%2Futils.cc;h=bdb44090c3041d612cfa7e46c6f1cc6f1b3aef04;hb=7c3fd2fe794e5073760457300d24a23f23c54012;hp=a11b431ef0c4a3e8bcb2d805e39b5127c38a4cc6;hpb=5d6c986748d77ca9f0cda698113dd19a10ba8fd0;p=ardour.git diff --git a/libs/gtkmm2ext/utils.cc b/libs/gtkmm2ext/utils.cc index a11b431ef0..bdb44090c3 100644 --- a/libs/gtkmm2ext/utils.cc +++ b/libs/gtkmm2ext/utils.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 1999 Paul Barton-Davis + Copyright (C) 1999 Paul Barton-Davis This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,6 +19,8 @@ */ #include +#include +#include #include #include @@ -30,18 +32,21 @@ #include #include #include +#include #include "gtkmm2ext/utils.h" +#include "gtkmm2ext/persistent_tooltip.h" -#include "i18n.h" +#include "pbd/i18n.h" using namespace std; void Gtkmm2ext::init (const char* localedir) { -#ifdef ENABLE_NLS +#if ENABLE_NLS (void) bindtextdomain(PACKAGE, localedir); + (void) bind_textdomain_codeset (PACKAGE, "UTF-8"); #endif } @@ -51,15 +56,15 @@ Gtkmm2ext::get_ink_pixel_size (Glib::RefPtr layout, int& height) { Pango::Rectangle ink_rect = layout->get_ink_extents (); - - width = (ink_rect.get_width() + PANGO_SCALE / 2) / PANGO_SCALE; - height = (ink_rect.get_height() + PANGO_SCALE / 2) / PANGO_SCALE; + + width = PANGO_PIXELS(ink_rect.get_width()); + height = PANGO_PIXELS(ink_rect.get_height()); } void -get_pixel_size (Glib::RefPtr layout, - int& width, - int& height) +Gtkmm2ext::get_pixel_size (Glib::RefPtr layout, + int& width, + int& height) { layout->get_pixel_size (width, height); } @@ -70,13 +75,58 @@ Gtkmm2ext::set_size_request_to_display_given_text (Gtk::Widget &w, const gchar * { int width, height; w.ensure_style (); - + get_pixel_size (w.create_pango_layout (text), width, height); w.set_size_request(width + hpadding, height + vpadding); } +/** Set width request to display given text, and height to display anything. + * This is useful for setting many widgets to the same height for consistency. */ void -Gtkmm2ext::set_size_request_to_display_given_text (Gtk::Widget &w, +Gtkmm2ext::set_size_request_to_display_given_text_width (Gtk::Widget& w, + const gchar* htext, + gint hpadding, + gint vpadding) +{ + static const gchar* vtext = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + w.ensure_style (); + + int hwidth, hheight; + get_pixel_size (w.create_pango_layout (htext), hwidth, hheight); + + int vwidth, vheight; + get_pixel_size (w.create_pango_layout (vtext), vwidth, vheight); + + w.set_size_request(hwidth + hpadding, vheight + vpadding); +} + +void +Gtkmm2ext::set_height_request_to_display_any_text (Gtk::Widget& w, gint vpadding) +{ + static const gchar* vtext = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + w.ensure_style (); + + int width, height; + get_pixel_size (w.create_pango_layout (vtext), width, height); + + w.set_size_request(-1, height + vpadding); +} + +void +Gtkmm2ext::set_size_request_to_display_given_text (Gtk::Widget &w, std::string const & text, + gint hpadding, gint vpadding) +{ + int width, height; + w.ensure_style (); + + get_pixel_size (w.create_pango_layout (text), width, height); + w.set_size_request(width + hpadding, height + vpadding); +} + +void +Gtkmm2ext::set_size_request_to_display_given_text (Gtk::Widget &w, const std::vector& strings, gint hpadding, gint vpadding) { @@ -84,26 +134,26 @@ Gtkmm2ext::set_size_request_to_display_given_text (Gtk::Widget &w, int width_max = 0; int height_max = 0; w.ensure_style (); - vector copy; - const vector* to_use; - vector::const_iterator i; - - for (i = strings.begin(); i != strings.end(); ++i) { - if ((*i).find_first_of ("gy") != string::npos) { - /* contains a descender */ - break; - } - } - - if (i == strings.end()) { - /* make a copy of the strings then add one that has a descender */ - copy = strings; - copy.push_back ("g"); - to_use = © - } else { - to_use = &strings; - } - + vector copy; + const vector* to_use; + vector::const_iterator i; + + for (i = strings.begin(); i != strings.end(); ++i) { + if ((*i).find_first_of ("gy") != string::npos) { + /* contains a descender */ + break; + } + } + + if (i == strings.end()) { + /* make a copy of the strings then add one that has a descender */ + copy = strings; + copy.push_back ("g"); + to_use = © + } else { + to_use = &strings; + } + for (vector::const_iterator i = to_use->begin(); i != to_use->end(); ++i) { get_pixel_size (w.create_pango_layout (*i), width, height); width_max = max(width_max,width); @@ -113,19 +163,47 @@ Gtkmm2ext::set_size_request_to_display_given_text (Gtk::Widget &w, w.set_size_request(width_max + hpadding, height_max + vpadding); } +/** This version specifies horizontal padding in text to avoid assumptions + * about font size. Should be used anywhere padding is used to avoid text, + * like combo boxes. + */ +void +Gtkmm2ext::set_size_request_to_display_given_text (Gtk::Widget& w, + const std::vector& strings, + const std::string& hpadding, + gint vpadding) +{ + int width_max = 0; + int height_max = 0; + w.ensure_style (); + + for (vector::const_iterator i = strings.begin(); i != strings.end(); ++i) { + int width, height; + get_pixel_size (w.create_pango_layout (*i), width, height); + width_max = max(width_max,width); + height_max = max(height_max, height); + } + + int pad_width; + int pad_height; + get_pixel_size (w.create_pango_layout (hpadding), pad_width, pad_height); + + w.set_size_request(width_max + pad_width, height_max + vpadding); +} + static inline guint8 demultiply_alpha (guint8 src, guint8 alpha) { - /* cairo pixel buffer data contains RGB values with the alpha - values premultiplied. - - GdkPixbuf pixel buffer data contains RGB values without the - alpha value applied. - - this removes the alpha component from the cairo version and - returns the GdkPixbuf version. - */ + /* cairo pixel buffer data contains RGB values with the alpha + * values premultiplied. + * + * GdkPixbuf pixel buffer data contains RGB values without the + * alpha value applied. + * + * this removes the alpha component from the cairo version and + * returns the GdkPixbuf version. + */ return alpha ? ((guint (src) << 8) - src) / alpha : 0; } @@ -137,51 +215,51 @@ Gtkmm2ext::convert_bgra_to_rgba (guint8 const* src, { guint8 const* src_pixel = src; guint8* dst_pixel = dst; - - /* cairo pixel data is endian-dependent ARGB with A in the most significant 8 bits, - with premultipled alpha values (see preceding function) - GdkPixbuf pixel data is non-endian-dependent RGBA with R in the lowest addressable - 8 bits, and non-premultiplied alpha values. - - convert from the cairo values to the GdkPixbuf ones. - */ + /* cairo pixel data is endian-dependent ARGB with A in the most significant 8 bits, + * with premultipled alpha values (see preceding function) + * + * GdkPixbuf pixel data is non-endian-dependent RGBA with R in the lowest addressable + * 8 bits, and non-premultiplied alpha values. + * + * convert from the cairo values to the GdkPixbuf ones. + */ for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { + for (int x = 0; x < width; x++) { #if G_BYTE_ORDER == G_LITTLE_ENDIAN - /* Cairo [ B G R A ] is actually [ B G R A ] in memory SOURCE - 0 1 2 3 - Pixbuf [ R G B A ] is actually [ R G B A ] in memory DEST - */ - dst_pixel[0] = demultiply_alpha (src_pixel[2], - src_pixel[3]); // R [0] <= [ 2 ] - dst_pixel[1] = demultiply_alpha (src_pixel[1], - src_pixel[3]); // G [1] <= [ 1 ] - dst_pixel[2] = demultiply_alpha (src_pixel[0], - src_pixel[3]); // B [2] <= [ 0 ] - dst_pixel[3] = src_pixel[3]; // alpha - + /* Cairo [ B G R A ] is actually [ B G R A ] in memory SOURCE + 0 1 2 3 + Pixbuf [ R G B A ] is actually [ R G B A ] in memory DEST + */ + dst_pixel[0] = demultiply_alpha (src_pixel[2], + src_pixel[3]); // R [0] <= [ 2 ] + dst_pixel[1] = demultiply_alpha (src_pixel[1], + src_pixel[3]); // G [1] <= [ 1 ] + dst_pixel[2] = demultiply_alpha (src_pixel[0], + src_pixel[3]); // B [2] <= [ 0 ] + dst_pixel[3] = src_pixel[3]; // alpha + #elif G_BYTE_ORDER == G_BIG_ENDIAN - /* Cairo [ B G R A ] is actually [ A R G B ] in memory SOURCE - 0 1 2 3 - Pixbuf [ R G B A ] is actually [ R G B A ] in memory DEST - */ - dst_pixel[0] = demultiply_alpha (src_pixel[1], - src_pixel[0]); // R [0] <= [ 1 ] - dst_pixel[1] = demultiply_alpha (src_pixel[2], - src_pixel[0]); // G [1] <= [ 2 ] - dst_pixel[2] = demultiply_alpha (src_pixel[3], - src_pixel[0]); // B [2] <= [ 3 ] - dst_pixel[3] = src_pixel[0]; // alpha - + /* Cairo [ B G R A ] is actually [ A R G B ] in memory SOURCE + 0 1 2 3 + Pixbuf [ R G B A ] is actually [ R G B A ] in memory DEST + */ + dst_pixel[0] = demultiply_alpha (src_pixel[1], + src_pixel[0]); // R [0] <= [ 1 ] + dst_pixel[1] = demultiply_alpha (src_pixel[2], + src_pixel[0]); // G [1] <= [ 2 ] + dst_pixel[2] = demultiply_alpha (src_pixel[3], + src_pixel[0]); // B [2] <= [ 3 ] + dst_pixel[3] = src_pixel[0]; // alpha + #else #error ardour does not currently support PDP-endianess -#endif - - dst_pixel += 4; - src_pixel += 4; - } +#endif + + dst_pixel += 4; + src_pixel += 4; + } } } @@ -198,21 +276,33 @@ Gtkmm2ext::pixbuf_from_string(const string& name, const Pango::FontDescription& return *empty_pixbuf; } + if (clip_width <= 0 || clip_height <= 0) { + /* negative values mean padding around natural size */ + int width, height; + pixel_size (name, font, width, height); + if (clip_width <= 0) { + clip_width = width - clip_width; + } + if (clip_height <= 0) { + clip_height = height - clip_height; + } + } + Glib::RefPtr 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, fg.get_red_p(), fg.get_green_p(), fg.get_blue_p(), 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, int (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); @@ -221,6 +311,127 @@ Gtkmm2ext::pixbuf_from_string(const string& name, const Pango::FontDescription& return buf; } +void +_position_menu_anchored (int& x, int& y, bool& push_in, + const Gtk::Menu* const menu, + Gtk::Widget* const anchor, + const std::string& selected) +{ + using namespace Gtk; + using namespace Gtk::Menu_Helpers; + + /* TODO: lacks support for rotated dropdown buttons */ + + if (!anchor->has_screen () || !anchor->get_has_window ()) { + return; + } + + Gdk::Rectangle monitor; + { + const int monitor_num = anchor->get_screen ()->get_monitor_at_window ( + anchor->get_window ()); + anchor->get_screen ()->get_monitor_geometry ( + (monitor_num < 0) ? 0 : monitor_num, monitor); + } + + const Requisition menu_req = menu->size_request(); + const Gdk::Rectangle allocation = anchor->get_allocation(); + + /* The x and y position are handled separately. + * + * For the x position if the direction is LTR (or RTL), then we try in order: + * a) align the left (right) of the menu with the left (right) of the button + * if there's enough room until the right (left) border of the screen; + * b) align the right (left) of the menu with the right (left) of the button + * if there's enough room until the left (right) border of the screen; + * c) align the right (left) border of the menu with the right (left) border + * of the screen if there's enough space; + * d) align the left (right) border of the menu with the left (right) border + * of the screen, with the rightmost (leftmost) part of the menu that + * overflows the screen. + * XXX We always align left regardless of the direction because if x is + * left of the current monitor, the menu popup code after us notices it + * and enforces that the menu stays in the monitor that's at the left...*/ + + anchor->get_window ()->get_origin (x, y); + + if (anchor->get_direction() == TEXT_DIR_RTL) { + if (monitor.get_x() <= x + allocation.get_width() - menu_req.width) { + /* a) align menu right and button right */ + x += allocation.get_width() - menu_req.width; + } else if (x + menu_req.width <= monitor.get_x() + monitor.get_width()) { + /* b) align menu left and button left: nothing to do*/ + } else if (menu_req.width > monitor.get_width()) { + /* c) align menu left and screen left, guaranteed to fit */ + x = monitor.get_x(); + } else { + /* d) XXX align left or the menu might change monitors */ + x = monitor.get_x(); + } + } else { /* LTR */ + if (x + menu_req.width <= monitor.get_x() + monitor.get_width()) { + /* a) align menu left and button left: nothing to do*/ + } else if (monitor.get_x() <= x + allocation.get_width() - menu_req.width) { + /* b) align menu right and button right */ + x += allocation.get_width() - menu_req.width; + } else if (menu_req.width > monitor.get_width()) { + /* c) align menu right and screen right, guaranteed to fit */ + x = monitor.get_x() + monitor.get_width() - menu_req.width; + } else { + /* d) align left */ + x = monitor.get_x(); + } + } + + /* For the y position, try in order: + * a) if there is a menu item with the same text as the button, align it + * with the button, unless that makes the menu overflow the monitor. + * b) align the top of the menu with the bottom of the button if there is + * enough room below the button; + * c) align the bottom of the menu with the top of the button if there is + * enough room above the button; + * d) align the bottom of the menu with the bottom of the monitor if there + * is enough room, but avoid moving the menu to another monitor */ + + const MenuList& items = menu->items (); + int offset = 0; + + MenuList::const_iterator i = items.begin(); + for ( ; i != items.end(); ++i) { + const Label* label_widget = dynamic_cast(i->get_child()); + if (label_widget && selected == ((std::string) label_widget->get_label())) { + break; + } + offset += i->size_request().height; + } + if (i != items.end() && + y - offset >= monitor.get_y() && + y - offset + menu_req.height <= monitor.get_y() + monitor.get_height()) { + y -= offset; + } else if (y + allocation.get_height() + menu_req.height <= monitor.get_y() + monitor.get_height()) { + y += allocation.get_height(); /* a) */ + } else if ((y - menu_req.height) >= monitor.get_y()) { + y -= menu_req.height; /* b) */ + } else { + y = monitor.get_y() + max(0, monitor.get_height() - menu_req.height); + } + + push_in = false; +} + +void +Gtkmm2ext::anchored_menu_popup (Gtk::Menu* const menu, + Gtk::Widget* const anchor, + const std::string& selected, + guint button, guint32 time) +{ + menu->popup( + sigc::bind (sigc::ptr_fun(&_position_menu_anchored), + menu, anchor, selected), + button, + time); +} + void Gtkmm2ext::set_popdown_strings (Gtk::ComboBoxText& cr, const vector& strings) { @@ -233,6 +444,49 @@ Gtkmm2ext::set_popdown_strings (Gtk::ComboBoxText& cr, const vector& str } } +void +Gtkmm2ext::get_popdown_strings (Gtk::ComboBoxText& cr, std::vector& strings) +{ + strings.clear (); + Glib::RefPtr m = cr.get_model(); + if (!m) { + return; + } + for(Gtk::TreeModel::iterator i = m->children().begin(); i != m->children().end(); ++i) { + Glib::ustring txt; + (*i)->get_value(0, txt); + strings.push_back (txt); + } +} + +size_t +Gtkmm2ext::get_popdown_string_count (Gtk::ComboBoxText& cr) +{ + Glib::RefPtr m = cr.get_model(); + if (!m) { + return 0; + } + return m->children().size(); +} + +bool +Gtkmm2ext::contains_value (Gtk::ComboBoxText& cr, const std::string text) +{ + std::vector s; + get_popdown_strings (cr, s); + return (std::find (s.begin(), s.end(), text) != s.end()); +} + +bool +Gtkmm2ext::set_active_text_if_present (Gtk::ComboBoxText& cr, const std::string text) +{ + if (contains_value(cr, text)) { + cr.set_active_text (text); + return true; + } + return false; +} + GdkWindow* Gtkmm2ext::get_paned_handle (Gtk::Paned& paned) { @@ -349,40 +603,41 @@ Gtkmm2ext::possibly_translate_legal_accelerator_to_real_key (uint32_t keyval) int Gtkmm2ext::physical_screen_height (Glib::RefPtr win) { - GdkScreen* scr = gdk_screen_get_default(); + GdkScreen* scr = gdk_screen_get_default(); - if (win) { - GdkRectangle r; - gint monitor = gdk_screen_get_monitor_at_window (scr, win->gobj()); - gdk_screen_get_monitor_geometry (scr, monitor, &r); - return r.height; - } else { - return gdk_screen_get_height (scr); - } + if (win) { + GdkRectangle r; + gint monitor = gdk_screen_get_monitor_at_window (scr, win->gobj()); + gdk_screen_get_monitor_geometry (scr, monitor, &r); + return r.height; + } else { + return gdk_screen_get_height (scr); + } } int Gtkmm2ext::physical_screen_width (Glib::RefPtr win) { - GdkScreen* scr = gdk_screen_get_default(); - - if (win) { - GdkRectangle r; - gint monitor = gdk_screen_get_monitor_at_window (scr, win->gobj()); - gdk_screen_get_monitor_geometry (scr, monitor, &r); - return r.width; - } else { - return gdk_screen_get_width (scr); - } + GdkScreen* scr = gdk_screen_get_default(); + + if (win) { + GdkRectangle r; + gint monitor = gdk_screen_get_monitor_at_window (scr, win->gobj()); + gdk_screen_get_monitor_geometry (scr, monitor, &r); + return r.width; + } else { + return gdk_screen_get_width (scr); + } } void Gtkmm2ext::container_clear (Gtk::Container& c) { - list children = c.get_children(); - for (list::iterator child = children.begin(); child != children.end(); ++child) { - c.remove (**child); - } + list children = c.get_children(); + for (list::iterator child = children.begin(); child != children.end(); ++child) { + (*child)->hide (); + c.remove (**child); + } } void @@ -415,6 +670,13 @@ Gtkmm2ext::rounded_bottom_half_rectangle (Cairo::RefPtr context, { rounded_bottom_half_rectangle (context->cobj(), x, y, w, h, r); } + +void +Gtkmm2ext::rounded_left_half_rectangle (Cairo::RefPtr context, double x, double y, double w, double h, double r) +{ + rounded_left_half_rectangle (context->cobj(), x, y, w, h, r); +} + void Gtkmm2ext::rounded_right_half_rectangle (Cairo::RefPtr context, double x, double y, double w, double h, double r) { @@ -424,7 +686,11 @@ Gtkmm2ext::rounded_right_half_rectangle (Cairo::RefPtr context, void Gtkmm2ext::rounded_rectangle (cairo_t* cr, double x, double y, double w, double h, double r) { - double degrees = M_PI / 180.0; + static const double degrees = M_PI / 180.0; + if (r < 1) { + cairo_rectangle (cr, x, y, w, h); + return; + } cairo_new_sub_path (cr); cairo_arc (cr, x + w - r, y + r, r, -90 * degrees, 0 * degrees); //tr @@ -434,10 +700,23 @@ Gtkmm2ext::rounded_rectangle (cairo_t* cr, double x, double y, double w, double cairo_close_path (cr); } +void +Gtkmm2ext::rounded_left_half_rectangle (cairo_t* cr, double x, double y, double w, double h, double r) +{ + static const double degrees = M_PI / 180.0; + + cairo_new_sub_path (cr); + cairo_line_to (cr, x+w, y); // tr + cairo_line_to (cr, x+w, y + h); // br + cairo_arc (cr, x + r, y + h - r, r, 90 * degrees, 180 * degrees); //bl + cairo_arc (cr, x + r, y + r, r, 180 * degrees, 270 * degrees); //tl + cairo_close_path (cr); +} + void Gtkmm2ext::rounded_right_half_rectangle (cairo_t* cr, double x, double y, double w, double h, double r) { - double degrees = M_PI / 180.0; + static const double degrees = M_PI / 180.0; cairo_new_sub_path (cr); cairo_arc (cr, x + w - r, y + r, r, -90 * degrees, 0 * degrees); //tr @@ -450,7 +729,7 @@ Gtkmm2ext::rounded_right_half_rectangle (cairo_t* cr, double x, double y, double void Gtkmm2ext::rounded_top_half_rectangle (cairo_t* cr, double x, double y, double w, double h, double r) { - double degrees = M_PI / 180.0; + static const double degrees = M_PI / 180.0; cairo_new_sub_path (cr); cairo_move_to (cr, x+w, y+h); @@ -463,7 +742,7 @@ Gtkmm2ext::rounded_top_half_rectangle (cairo_t* cr, double x, double y, double w void Gtkmm2ext::rounded_bottom_half_rectangle (cairo_t* cr, double x, double y, double w, double h, double r) { - double degrees = M_PI / 180.0; + static const double degrees = M_PI / 180.0; cairo_new_sub_path (cr); cairo_move_to (cr, x, y); @@ -477,7 +756,7 @@ Gtkmm2ext::rounded_bottom_half_rectangle (cairo_t* cr, double x, double y, doubl void Gtkmm2ext::rounded_top_rectangle (cairo_t* cr, double x, double y, double w, double h, double r) { - double degrees = M_PI / 180.0; + static const double degrees = M_PI / 180.0; cairo_new_sub_path (cr); cairo_move_to (cr, x+w, y+h); @@ -541,19 +820,45 @@ Gtkmm2ext::window_to_draw_on (Gtk::Widget& w, Gtk::Widget** parent) } int -Gtkmm2ext::pixel_width (const string& str, Pango::FontDescription& font) +Gtkmm2ext::pixel_width (const string& str, const Pango::FontDescription& font) { - Gtk::Label foo; - Glib::RefPtr layout = foo.create_pango_layout (""); + Glib::RefPtr context = Glib::wrap (gdk_pango_context_get()); + Glib::RefPtr layout = Pango::Layout::create (context); layout->set_font_description (font); layout->set_text (str); int width, height; Gtkmm2ext::get_ink_pixel_size (layout, width, height); + +#ifdef __APPLE__ + // Pango returns incorrect text width on some OS X + // So we have to make a correction + // To determine the correct indent take the largest symbol for which the width is correct + // and make the calculation + // + // see also libs/canvas/text.cc + int cor_width; + layout->set_text ("H"); + layout->get_pixel_size (cor_width, height); + width += cor_width * 1.5; +#endif + return width; } +void +Gtkmm2ext::pixel_size (const string& str, const Pango::FontDescription& font, int& width, int& height) +{ + Gtk::Label foo; + Glib::RefPtr layout = foo.create_pango_layout (""); + + layout->set_font_description (font); + layout->set_text (str); + + Gtkmm2ext::get_ink_pixel_size (layout, width, height); +} + #if 0 string Gtkmm2ext::fit_to_pixels (const string& str, int pixel_width, Pango::FontDescription& font, int& actual_width, bool with_ellipses) @@ -569,19 +874,18 @@ Gtkmm2ext::fit_to_pixels (const string& str, int pixel_width, Pango::FontDescrip layout->set_width (pixel_width * PANGO_SCALE); if (with_ellipses) { - layout->set_ellipsize (Pango::ELLIPSIZE_END); + layout->set_ellipsize (Pango::ELLIPSIZE_END); } else { - layout->set_wrap (Pango::WRAP_CHAR); + layout->set_wrap (Pango::WRAP_CHAR); } line = layout->get_line (0); /* XXX: might need special care to get the ellipsis character, not sure - how that works - */ - + * how that works + */ string s = string (layout->get_text ().substr(line->get_start_index(), line->get_length())); - + cerr << "fit to pixels of " << str << " returns " << s << endl; return s; @@ -594,7 +898,6 @@ Gtkmm2ext::fit_to_pixels (const string& str, int pixel_width, Pango::FontDescrip * @param avail Available horizontal space. * @return (Text, possibly ellipsized) and (horizontal size of text) */ - std::pair Gtkmm2ext::fit_to_pixels (cairo_t* cr, std::string name, double avail) { @@ -631,6 +934,14 @@ Gtkmm2ext::left_aligned_label (string const & t) return l; } +Gtk::Label * +Gtkmm2ext::right_aligned_label (string const & t) +{ + Gtk::Label* l = new Gtk::Label (t); + l->set_alignment (1, 0.5); + return l; +} + static bool make_null_tooltip (int, int, bool, const Glib::RefPtr& t) { @@ -653,11 +964,236 @@ void Gtkmm2ext::enable_tooltips () { gtk_rc_parse_string ("gtk-enable-tooltips = 1"); + PersistentTooltip::set_tooltips_enabled (true); } void Gtkmm2ext::disable_tooltips () { gtk_rc_parse_string ("gtk-enable-tooltips = 0"); + PersistentTooltip::set_tooltips_enabled (false); } +bool +Gtkmm2ext::event_inside_widget_window (Gtk::Widget& widget, GdkEvent* ev) +{ + gdouble evx, evy; + + if (!gdk_event_get_root_coords (ev, &evx, &evy)) { + return false; + } + + gint wx; + gint wy; + gint width, height, depth; + gint x, y; + + Glib::RefPtr widget_window = widget.get_window(); + + widget_window->get_geometry (x, y, width, height, depth); + widget_window->get_root_origin (wx, wy); + + if ((evx >= wx && evx < wx + width) && + (evy >= wy && evy < wy + height)) { + return true; + } + + return false; +} + +const char* +Gtkmm2ext::event_type_string (int event_type) +{ + switch (event_type) { + case GDK_NOTHING: + return "nothing"; + case GDK_DELETE: + return "delete"; + case GDK_DESTROY: + return "destroy"; + case GDK_EXPOSE: + return "expose"; + case GDK_MOTION_NOTIFY: + return "motion_notify"; + case GDK_BUTTON_PRESS: + return "button_press"; + case GDK_2BUTTON_PRESS: + return "2button_press"; + case GDK_3BUTTON_PRESS: + return "3button_press"; + case GDK_BUTTON_RELEASE: + return "button_release"; + case GDK_KEY_PRESS: + return "key_press"; + case GDK_KEY_RELEASE: + return "key_release"; + case GDK_ENTER_NOTIFY: + return "enter_notify"; + case GDK_LEAVE_NOTIFY: + return "leave_notify"; + case GDK_FOCUS_CHANGE: + return "focus_change"; + case GDK_CONFIGURE: + return "configure"; + case GDK_MAP: + return "map"; + case GDK_UNMAP: + return "unmap"; + case GDK_PROPERTY_NOTIFY: + return "property_notify"; + case GDK_SELECTION_CLEAR: + return "selection_clear"; + case GDK_SELECTION_REQUEST: + return "selection_request"; + case GDK_SELECTION_NOTIFY: + return "selection_notify"; + case GDK_PROXIMITY_IN: + return "proximity_in"; + case GDK_PROXIMITY_OUT: + return "proximity_out"; + case GDK_DRAG_ENTER: + return "drag_enter"; + case GDK_DRAG_LEAVE: + return "drag_leave"; + case GDK_DRAG_MOTION: + return "drag_motion"; + case GDK_DRAG_STATUS: + return "drag_status"; + case GDK_DROP_START: + return "drop_start"; + case GDK_DROP_FINISHED: + return "drop_finished"; + case GDK_CLIENT_EVENT: + return "client_event"; + case GDK_VISIBILITY_NOTIFY: + return "visibility_notify"; + case GDK_NO_EXPOSE: + return "no_expose"; + case GDK_SCROLL: + return "scroll"; + case GDK_WINDOW_STATE: + return "window_state"; + case GDK_SETTING: + return "setting"; + case GDK_OWNER_CHANGE: + return "owner_change"; + case GDK_GRAB_BROKEN: + return "grab_broken"; + case GDK_DAMAGE: + return "damage"; + } + + return "unknown"; +} + +std::string +Gtkmm2ext::markup_escape_text (std::string const& s) +{ + return Glib::Markup::escape_text (s); +} + +void +Gtkmm2ext::add_volume_shortcuts (Gtk::FileChooser& c) +{ +#ifdef __APPLE__ + try { + /* This is a first order approach, listing all mounted volumes (incl network). + * One could use `diskutil` or `mount` to query local disks only, or + * something even fancier if deemed appropriate. + */ + Glib::Dir dir("/Volumes"); + for (Glib::DirIterator di = dir.begin(); di != dir.end(); di++) { + string fullpath = Glib::build_filename ("/Volumes", *di); + if (!Glib::file_test (fullpath, Glib::FILE_TEST_IS_DIR)) continue; + + try { /* add_shortcut_folder throws an exception if the folder being added already has a shortcut */ + c.add_shortcut_folder (fullpath); + } + catch (Glib::Error& e) { + std::cerr << "add_shortcut_folder() threw Glib::Error: " << e.what() << std::endl; + } + } + } + catch (Glib::FileError const& e) { + std::cerr << "listing /Volumnes failed: " << e.what() << std::endl; + } +#endif +} + +float +Gtkmm2ext::paned_position_as_fraction (Gtk::Paned& paned, bool h) +{ + const guint pos = gtk_paned_get_position (const_cast(static_cast(&paned)->gobj())); + return (double) pos / (h ? paned.get_allocation().get_height() : paned.get_allocation().get_width()); +} + +void +Gtkmm2ext::paned_set_position_as_fraction (Gtk::Paned& paned, float fraction, bool h) +{ + gint v = (h ? paned.get_allocation().get_height() : paned.get_allocation().get_width()); + + if (v < 1) { + return; + } + + paned.set_position ((guint) floor (fraction * v)); +} + +string +Gtkmm2ext::show_gdk_event_state (int state) +{ + string s; + if (state & GDK_SHIFT_MASK) { + s += "+SHIFT"; + } + if (state & GDK_LOCK_MASK) { + s += "+LOCK"; + } + if (state & GDK_CONTROL_MASK) { + s += "+CONTROL"; + } + if (state & GDK_MOD1_MASK) { + s += "+MOD1"; + } + if (state & GDK_MOD2_MASK) { + s += "+MOD2"; + } + if (state & GDK_MOD3_MASK) { + s += "+MOD3"; + } + if (state & GDK_MOD4_MASK) { + s += "+MOD4"; + } + if (state & GDK_MOD5_MASK) { + s += "+MOD5"; + } + if (state & GDK_BUTTON1_MASK) { + s += "+BUTTON1"; + } + if (state & GDK_BUTTON2_MASK) { + s += "+BUTTON2"; + } + if (state & GDK_BUTTON3_MASK) { + s += "+BUTTON3"; + } + if (state & GDK_BUTTON4_MASK) { + s += "+BUTTON4"; + } + if (state & GDK_BUTTON5_MASK) { + s += "+BUTTON5"; + } + if (state & GDK_SUPER_MASK) { + s += "+SUPER"; + } + if (state & GDK_HYPER_MASK) { + s += "+HYPER"; + } + if (state & GDK_META_MASK) { + s += "+META"; + } + if (state & GDK_RELEASE_MASK) { + s += "+RELEASE"; + } + + return s; +}