2 Copyright (C) 1999 Paul Barton-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.
24 #include <gtk/gtkpaned.h>
27 #include <gtkmm/widget.h>
28 #include <gtkmm/button.h>
29 #include <gtkmm/window.h>
30 #include <gtkmm/paned.h>
31 #include <gtkmm/label.h>
32 #include <gtkmm/comboboxtext.h>
33 #include <gtkmm/tooltip.h>
34 #include <gtkmm/menuitem.h>
36 #include "gtkmm2ext/utils.h"
37 #include "gtkmm2ext/persistent_tooltip.h"
44 Gtkmm2ext::init (const char* localedir)
47 (void) bindtextdomain(PACKAGE, localedir);
48 (void) bind_textdomain_codeset (PACKAGE, "UTF-8");
53 Gtkmm2ext::get_ink_pixel_size (Glib::RefPtr<Pango::Layout> layout,
57 Pango::Rectangle ink_rect = layout->get_ink_extents ();
59 width = PANGO_PIXELS(ink_rect.get_width());
60 height = PANGO_PIXELS(ink_rect.get_height());
64 Gtkmm2ext::get_pixel_size (Glib::RefPtr<Pango::Layout> layout,
68 layout->get_pixel_size (width, height);
72 Gtkmm2ext::set_size_request_to_display_given_text (Gtk::Widget &w, const gchar *text,
73 gint hpadding, gint vpadding)
78 get_pixel_size (w.create_pango_layout (text), width, height);
79 w.set_size_request(width + hpadding, height + vpadding);
82 /** Set width request to display given text, and height to display anything.
83 This is useful for setting many widgets to the same height for consistency. */
85 Gtkmm2ext::set_size_request_to_display_given_text_width (Gtk::Widget& w,
90 static const gchar* vtext = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
95 get_pixel_size (w.create_pango_layout (htext), hwidth, hheight);
98 get_pixel_size (w.create_pango_layout (vtext), vwidth, vheight);
100 w.set_size_request(hwidth + hpadding, vheight + vpadding);
104 Gtkmm2ext::set_height_request_to_display_any_text (Gtk::Widget& w, gint vpadding)
106 static const gchar* vtext = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
111 get_pixel_size (w.create_pango_layout (vtext), width, height);
113 w.set_size_request(-1, height + vpadding);
117 Gtkmm2ext::set_size_request_to_display_given_text (Gtk::Widget &w, std::string const & text,
118 gint hpadding, gint vpadding)
123 get_pixel_size (w.create_pango_layout (text), width, height);
124 w.set_size_request(width + hpadding, height + vpadding);
128 Gtkmm2ext::set_size_request_to_display_given_text (Gtk::Widget &w,
129 const std::vector<std::string>& strings,
130 gint hpadding, gint vpadding)
137 const vector<string>* to_use;
138 vector<string>::const_iterator i;
140 for (i = strings.begin(); i != strings.end(); ++i) {
141 if ((*i).find_first_of ("gy") != string::npos) {
142 /* contains a descender */
147 if (i == strings.end()) {
148 /* make a copy of the strings then add one that has a descender */
150 copy.push_back ("g");
156 for (vector<string>::const_iterator i = to_use->begin(); i != to_use->end(); ++i) {
157 get_pixel_size (w.create_pango_layout (*i), width, height);
158 width_max = max(width_max,width);
159 height_max = max(height_max, height);
162 w.set_size_request(width_max + hpadding, height_max + vpadding);
165 /** This version specifies horizontal padding in text to avoid assumptions
166 about font size. Should be used anywhere padding is used to avoid text,
169 Gtkmm2ext::set_size_request_to_display_given_text (Gtk::Widget& w,
170 const std::vector<std::string>& strings,
171 const std::string& hpadding,
178 for (vector<string>::const_iterator i = strings.begin(); i != strings.end(); ++i) {
180 get_pixel_size (w.create_pango_layout (*i), width, height);
181 width_max = max(width_max,width);
182 height_max = max(height_max, height);
187 get_pixel_size (w.create_pango_layout (hpadding), pad_width, pad_height);
189 w.set_size_request(width_max + pad_width, height_max + vpadding);
193 demultiply_alpha (guint8 src,
196 /* cairo pixel buffer data contains RGB values with the alpha
197 values premultiplied.
199 GdkPixbuf pixel buffer data contains RGB values without the
202 this removes the alpha component from the cairo version and
203 returns the GdkPixbuf version.
205 return alpha ? ((guint (src) << 8) - src) / alpha : 0;
209 Gtkmm2ext::convert_bgra_to_rgba (guint8 const* src,
214 guint8 const* src_pixel = src;
215 guint8* dst_pixel = dst;
217 /* cairo pixel data is endian-dependent ARGB with A in the most significant 8 bits,
218 with premultipled alpha values (see preceding function)
220 GdkPixbuf pixel data is non-endian-dependent RGBA with R in the lowest addressable
221 8 bits, and non-premultiplied alpha values.
223 convert from the cairo values to the GdkPixbuf ones.
226 for (int y = 0; y < height; y++) {
227 for (int x = 0; x < width; x++) {
228 #if G_BYTE_ORDER == G_LITTLE_ENDIAN
229 /* Cairo [ B G R A ] is actually [ B G R A ] in memory SOURCE
231 Pixbuf [ R G B A ] is actually [ R G B A ] in memory DEST
233 dst_pixel[0] = demultiply_alpha (src_pixel[2],
234 src_pixel[3]); // R [0] <= [ 2 ]
235 dst_pixel[1] = demultiply_alpha (src_pixel[1],
236 src_pixel[3]); // G [1] <= [ 1 ]
237 dst_pixel[2] = demultiply_alpha (src_pixel[0],
238 src_pixel[3]); // B [2] <= [ 0 ]
239 dst_pixel[3] = src_pixel[3]; // alpha
241 #elif G_BYTE_ORDER == G_BIG_ENDIAN
242 /* Cairo [ B G R A ] is actually [ A R G B ] in memory SOURCE
244 Pixbuf [ R G B A ] is actually [ R G B A ] in memory DEST
246 dst_pixel[0] = demultiply_alpha (src_pixel[1],
247 src_pixel[0]); // R [0] <= [ 1 ]
248 dst_pixel[1] = demultiply_alpha (src_pixel[2],
249 src_pixel[0]); // G [1] <= [ 2 ]
250 dst_pixel[2] = demultiply_alpha (src_pixel[3],
251 src_pixel[0]); // B [2] <= [ 3 ]
252 dst_pixel[3] = src_pixel[0]; // alpha
255 #error ardour does not currently support PDP-endianess
264 Glib::RefPtr<Gdk::Pixbuf>
265 Gtkmm2ext::pixbuf_from_string(const string& name, const Pango::FontDescription& font, int clip_width, int clip_height, Gdk::Color fg)
267 static Glib::RefPtr<Gdk::Pixbuf>* empty_pixbuf = 0;
270 if (empty_pixbuf == 0) {
271 empty_pixbuf = new Glib::RefPtr<Gdk::Pixbuf>;
272 *empty_pixbuf = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB, true, 8, clip_width, clip_height);
274 return *empty_pixbuf;
277 if (clip_width <= 0 || clip_height <= 0) {
278 /* negative values mean padding around natural size */
280 pixel_size (name, font, width, height);
281 if (clip_width <= 0) {
282 clip_width = width - clip_width;
284 if (clip_height <= 0) {
285 clip_height = height - clip_height;
289 Glib::RefPtr<Gdk::Pixbuf> buf = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB, true, 8, clip_width, clip_height);
291 cairo_surface_t* surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, clip_width, clip_height);
292 cairo_t* cr = cairo_create (surface);
293 cairo_text_extents_t te;
295 cairo_set_source_rgba (cr, fg.get_red_p(), fg.get_green_p(), fg.get_blue_p(), 1.0);
296 cairo_select_font_face (cr, font.get_family().c_str(),
297 CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
298 cairo_set_font_size (cr, font.get_size() / Pango::SCALE);
299 cairo_text_extents (cr, name.c_str(), &te);
301 cairo_move_to (cr, 0.5, int (0.5 - te.height / 2 - te.y_bearing + clip_height / 2));
302 cairo_show_text (cr, name.c_str());
304 convert_bgra_to_rgba(cairo_image_surface_get_data (surface), buf->get_pixels(), clip_width, clip_height);
307 cairo_surface_destroy(surface);
313 Gtkmm2ext::position_menu_anchored (const Gtk::Menu* const menu,
314 Gtk::Widget* const anchor,
315 const std::string& selected,
316 int& x, int& y, bool& push_in) {
319 using namespace Gtk::Menu_Helpers;
321 /* TODO: lacks support for rotated dropdown buttons */
323 if (!anchor->has_screen () || !anchor->get_has_window ()) {
329 const int monitor_num = anchor->get_screen ()->get_monitor_at_window (
330 anchor->get_window ());
331 anchor->get_screen ()->get_monitor_geometry (
332 (monitor_num < 0) ? 0 : monitor_num, monitor);
335 const Requisition menu_req = menu->size_request();
336 const Rectangle allocation = anchor->get_allocation();
338 /* The x and y position are handled separately.
340 * For the x position if the direction is LTR (or RTL), then we try in order:
341 * a) align the left (right) of the menu with the left (right) of the button
342 * if there's enough room until the right (left) border of the screen;
343 * b) align the right (left) of the menu with the right (left) of the button
344 * if there's enough room until the left (right) border of the screen;
345 * c) align the right (left) border of the menu with the right (left) border
346 * of the screen if there's enough space;
347 * d) align the left (right) border of the menu with the left (right) border
348 * of the screen, with the rightmost (leftmost) part of the menu that
349 * overflows the screen.
350 * XXX We always align left regardless of the direction because if x is
351 * left of the current monitor, the menu popup code after us notices it
352 * and enforces that the menu stays in the monitor that's at the left...*/
354 anchor->get_window ()->get_origin (x, y);
356 if (anchor->get_direction() == TEXT_DIR_RTL) {
357 if (monitor.get_x() <= x + allocation.get_width() - menu_req.width) {
358 /* a) align menu right and button right */
359 x += allocation.get_width() - menu_req.width;
360 } else if (x + menu_req.width <= monitor.get_x() + monitor.get_width()) {
361 /* b) align menu left and button left: nothing to do*/
362 } else if (menu_req.width > monitor.get_width()) {
363 /* c) align menu left and screen left, guaranteed to fit */
366 /* d) XXX align left or the menu might change monitors */
370 if (x + menu_req.width <= monitor.get_x() + monitor.get_width()) {
371 /* a) align menu left and button left: nothing to do*/
372 } else if (monitor.get_x() <= x + allocation.get_width() - menu_req.width) {
373 /* b) align menu right and button right */
374 x += allocation.get_width() - menu_req.width;
375 } else if (menu_req.width > monitor.get_width()) {
376 /* c) align menu right and screen right, guaranteed to fit */
377 x = monitor.get_x() + monitor.get_width() - menu_req.width;
384 /* For the y position, try in order:
385 * a) if there is a menu item with the same text as the button, align it
386 * with the button, unless that makes the menu overflow the monitor.
387 * b) align the top of the menu with the bottom of the button if there is
388 * enough room below the button;
389 * c) align the bottom of the menu with the top of the button if there is
390 * enough room above the button;
391 * d) align the bottom of the menu with the bottom of the monitor if there
392 * is enough room, but avoid moving the menu to another monitor */
394 const MenuList& items = menu->items ();
397 MenuList::const_iterator i = items.begin();
398 for ( ; i != items.end(); ++i) {
399 if (selected == ((std::string) i->get_label())) {
402 offset += i->size_request().height;
404 if (i != items.end() &&
405 y - offset >= monitor.get_y() &&
406 y - offset + menu_req.height <= monitor.get_y() + monitor.get_height()) {
408 } else if (y + allocation.get_height() + menu_req.height <= monitor.get_y() + monitor.get_height()) {
409 y += allocation.get_height(); /* a) */
410 } else if ((y - menu_req.height) >= monitor.get_y()) {
411 y -= menu_req.height; /* b) */
413 y = monitor.get_y() + max(0, monitor.get_height() - menu_req.height);
420 Gtkmm2ext::set_popdown_strings (Gtk::ComboBoxText& cr, const vector<string>& strings)
422 vector<string>::const_iterator i;
426 for (i = strings.begin(); i != strings.end(); ++i) {
432 Gtkmm2ext::get_popdown_strings (Gtk::ComboBoxText& cr, std::vector<std::string>& strings)
435 Glib::RefPtr<const Gtk::TreeModel> m = cr.get_model();
439 for(Gtk::TreeModel::iterator i = m->children().begin(); i != m->children().end(); ++i) {
441 (*i)->get_value(0, txt);
442 strings.push_back (txt);
447 Gtkmm2ext::get_popdown_string_count (Gtk::ComboBoxText& cr)
449 Glib::RefPtr<const Gtk::TreeModel> m = cr.get_model();
453 return m->children().size();
457 Gtkmm2ext::contains_value (Gtk::ComboBoxText& cr, const std::string text)
459 std::vector<std::string> s;
460 get_popdown_strings (cr, s);
461 return (std::find (s.begin(), s.end(), text) != s.end());
465 Gtkmm2ext::set_active_text_if_present (Gtk::ComboBoxText& cr, const std::string text)
467 if (contains_value(cr, text)) {
468 cr.set_active_text (text);
475 Gtkmm2ext::get_paned_handle (Gtk::Paned& paned)
477 return GTK_PANED(paned.gobj())->handle;
481 Gtkmm2ext::set_decoration (Gtk::Window* win, Gdk::WMDecoration decor)
483 win->get_window()->set_decorations (decor);
486 void Gtkmm2ext::set_treeview_header_as_default_label(Gtk::TreeViewColumn* c)
488 gtk_tree_view_column_set_widget( c->gobj(), GTK_WIDGET(0) );
492 Gtkmm2ext::detach_menu (Gtk::Menu& menu)
494 /* its possible for a Gtk::Menu to have no gobj() because it has
495 not yet been instantiated. Catch this and provide a safe
499 if (menu.get_attach_widget()) {
506 Gtkmm2ext::possibly_translate_keyval_to_make_legal_accelerator (uint32_t& keyval)
508 int fakekey = GDK_VoidSymbol;
512 case GDK_ISO_Left_Tab:
517 fakekey = GDK_uparrow;
521 fakekey = GDK_downarrow;
525 fakekey = GDK_rightarrow;
529 fakekey = GDK_leftarrow;
533 fakekey = GDK_3270_Enter;
544 if (fakekey != GDK_VoidSymbol) {
553 Gtkmm2ext::possibly_translate_legal_accelerator_to_real_key (uint32_t keyval)
588 Gtkmm2ext::physical_screen_height (Glib::RefPtr<Gdk::Window> win)
590 GdkScreen* scr = gdk_screen_get_default();
594 gint monitor = gdk_screen_get_monitor_at_window (scr, win->gobj());
595 gdk_screen_get_monitor_geometry (scr, monitor, &r);
598 return gdk_screen_get_height (scr);
603 Gtkmm2ext::physical_screen_width (Glib::RefPtr<Gdk::Window> win)
605 GdkScreen* scr = gdk_screen_get_default();
609 gint monitor = gdk_screen_get_monitor_at_window (scr, win->gobj());
610 gdk_screen_get_monitor_geometry (scr, monitor, &r);
613 return gdk_screen_get_width (scr);
618 Gtkmm2ext::container_clear (Gtk::Container& c)
620 list<Gtk::Widget*> children = c.get_children();
621 for (list<Gtk::Widget*>::iterator child = children.begin(); child != children.end(); ++child) {
627 Gtkmm2ext::rounded_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
629 rounded_rectangle (context->cobj(), x, y, w, h, r);
632 Gtkmm2ext::rounded_top_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
634 rounded_top_rectangle (context->cobj(), x, y, w, h, r);
637 Gtkmm2ext::rounded_top_left_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
639 rounded_top_left_rectangle (context->cobj(), x, y, w, h, r);
642 Gtkmm2ext::rounded_top_right_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
644 rounded_top_right_rectangle (context->cobj(), x, y, w, h, r);
647 Gtkmm2ext::rounded_top_half_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
649 rounded_top_half_rectangle (context->cobj(), x, y, w, h, r);
652 Gtkmm2ext::rounded_bottom_half_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
654 rounded_bottom_half_rectangle (context->cobj(), x, y, w, h, r);
658 Gtkmm2ext::rounded_left_half_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
660 rounded_left_half_rectangle (context->cobj(), x, y, w, h, r);
664 Gtkmm2ext::rounded_right_half_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
666 rounded_right_half_rectangle (context->cobj(), x, y, w, h, r);
670 Gtkmm2ext::rounded_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
672 double degrees = M_PI / 180.0;
674 cairo_new_sub_path (cr);
675 cairo_arc (cr, x + w - r, y + r, r, -90 * degrees, 0 * degrees); //tr
676 cairo_arc (cr, x + w - r, y + h - r, r, 0 * degrees, 90 * degrees); //br
677 cairo_arc (cr, x + r, y + h - r, r, 90 * degrees, 180 * degrees); //bl
678 cairo_arc (cr, x + r, y + r, r, 180 * degrees, 270 * degrees); //tl
679 cairo_close_path (cr);
683 Gtkmm2ext::rounded_left_half_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
685 double degrees = M_PI / 180.0;
687 cairo_new_sub_path (cr);
688 cairo_line_to (cr, x+w, y); // tr
689 cairo_line_to (cr, x+w, y + h); // br
690 cairo_arc (cr, x + r, y + h - r, r, 90 * degrees, 180 * degrees); //bl
691 cairo_arc (cr, x + r, y + r, r, 180 * degrees, 270 * degrees); //tl
692 cairo_close_path (cr);
696 Gtkmm2ext::rounded_right_half_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
698 double degrees = M_PI / 180.0;
700 cairo_new_sub_path (cr);
701 cairo_arc (cr, x + w - r, y + r, r, -90 * degrees, 0 * degrees); //tr
702 cairo_arc (cr, x + w - r, y + h - r, r, 0 * degrees, 90 * degrees); //br
703 cairo_line_to (cr, x, y + h); // bl
704 cairo_line_to (cr, x, y); // tl
705 cairo_close_path (cr);
709 Gtkmm2ext::rounded_top_half_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
711 double degrees = M_PI / 180.0;
713 cairo_new_sub_path (cr);
714 cairo_move_to (cr, x+w, y+h);
715 cairo_line_to (cr, x, y+h);
716 cairo_arc (cr, x + r, y + r, r, 180 * degrees, 270 * degrees); //tl
717 cairo_arc (cr, x + w - r, y + r, r, -90 * degrees, 0 * degrees); //tr
718 cairo_close_path (cr);
722 Gtkmm2ext::rounded_bottom_half_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
724 double degrees = M_PI / 180.0;
726 cairo_new_sub_path (cr);
727 cairo_move_to (cr, x, y);
728 cairo_line_to (cr, x+w, y);
729 cairo_arc (cr, x + w - r, y + h - r, r, 0 * degrees, 90 * degrees); //br
730 cairo_arc (cr, x + r, y + h - r, r, 90 * degrees, 180 * degrees); //bl
731 cairo_close_path (cr);
736 Gtkmm2ext::rounded_top_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
738 double degrees = M_PI / 180.0;
740 cairo_new_sub_path (cr);
741 cairo_move_to (cr, x+w, y+h);
742 cairo_line_to (cr, x, y+h);
743 cairo_arc (cr, x + r, y + r, r, 180 * degrees, 270 * degrees); //tl
744 cairo_arc (cr, x + w - r, y + r, r, -90 * degrees, 0 * degrees); //tr
745 cairo_close_path (cr);
749 Gtkmm2ext::rounded_top_left_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
757 cairo_move_to (cr, x+r,y); // Move to A
758 cairo_line_to (cr, x+w,y); // Straight line to B
759 cairo_line_to (cr, x+w,y+h); // Move to E
760 cairo_line_to (cr, x,y+h); // Line to F
761 cairo_line_to (cr, x,y+r); // Line to H
762 cairo_curve_to (cr, x,y,x,y,x+r,y); // Curve to A
766 Gtkmm2ext::rounded_top_right_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
774 cairo_move_to (cr, x,y); // Move to A
775 cairo_line_to (cr, x+w-r,y); // Straight line to B
776 cairo_curve_to (cr, x+w,y,x+w,y,x+w,y+r); // Curve to C, Control points are both at Q
777 cairo_line_to (cr, x+w,y+h); // Move to E
778 cairo_line_to (cr, x,y+h); // Line to F
779 cairo_line_to (cr, x,y); // Line to A
782 Glib::RefPtr<Gdk::Window>
783 Gtkmm2ext::window_to_draw_on (Gtk::Widget& w, Gtk::Widget** parent)
785 if (w.get_has_window()) {
786 return w.get_window();
789 (*parent) = w.get_parent();
792 if ((*parent)->get_has_window()) {
793 return (*parent)->get_window ();
795 (*parent) = (*parent)->get_parent ();
798 return Glib::RefPtr<Gdk::Window> ();
802 Gtkmm2ext::pixel_width (const string& str, const Pango::FontDescription& font)
804 Glib::RefPtr<Pango::Context> context = Glib::wrap (gdk_pango_context_get());
805 Glib::RefPtr<Pango::Layout> layout = Pango::Layout::create (context);
807 layout->set_font_description (font);
808 layout->set_text (str);
811 Gtkmm2ext::get_ink_pixel_size (layout, width, height);
814 // Pango returns incorrect text width on some OS X
815 // So we have to make a correction
816 // To determine the correct indent take the largest symbol for which the width is correct
817 // and make the calculation
819 // see also libs/canvas/text.cc
821 layout->set_text ("H");
822 layout->get_pixel_size (cor_width, height);
823 width += cor_width * 1.5;
830 Gtkmm2ext::pixel_size (const string& str, const Pango::FontDescription& font, int& width, int& height)
833 Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout ("");
835 layout->set_font_description (font);
836 layout->set_text (str);
838 Gtkmm2ext::get_ink_pixel_size (layout, width, height);
843 Gtkmm2ext::fit_to_pixels (const string& str, int pixel_width, Pango::FontDescription& font, int& actual_width, bool with_ellipses)
845 /* DECEMBER 2011: THIS PROTOTYPE OF fit_to_pixels() IS NOT USED
846 ANYWHERE AND HAS NOT BEEN TESTED.
849 Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout (str);
850 Glib::RefPtr<const Pango::LayoutLine> line;
852 layout->set_font_description (font);
853 layout->set_width (pixel_width * PANGO_SCALE);
856 layout->set_ellipsize (Pango::ELLIPSIZE_END);
858 layout->set_wrap (Pango::WRAP_CHAR);
861 line = layout->get_line (0);
863 /* XXX: might need special care to get the ellipsis character, not sure
867 string s = string (layout->get_text ().substr(line->get_start_index(), line->get_length()));
869 cerr << "fit to pixels of " << str << " returns " << s << endl;
875 /** Try to fit a string into a given horizontal space by ellipsizing it.
876 * @param cr Cairo context in which the text will be plotted.
878 * @param avail Available horizontal space.
879 * @return (Text, possibly ellipsized) and (horizontal size of text)
882 std::pair<std::string, double>
883 Gtkmm2ext::fit_to_pixels (cairo_t* cr, std::string name, double avail)
885 /* XXX hopefully there exists a more efficient way of doing this */
887 bool abbreviated = false;
891 cairo_text_extents_t ext;
892 cairo_text_extents (cr, name.c_str(), &ext);
894 if (ext.width < avail || name.length() <= 4) {
900 name = name.substr (0, name.length() - 4) + "...";
902 name = name.substr (0, name.length() - 3) + "...";
907 return std::make_pair (name, width);
911 Gtkmm2ext::left_aligned_label (string const & t)
913 Gtk::Label* l = new Gtk::Label (t);
914 l->set_alignment (0, 0.5);
919 Gtkmm2ext::right_aligned_label (string const & t)
921 Gtk::Label* l = new Gtk::Label (t);
922 l->set_alignment (1, 0.5);
927 make_null_tooltip (int, int, bool, const Glib::RefPtr<Gtk::Tooltip>& t)
929 t->set_tip_area (Gdk::Rectangle (0, 0, 0, 0));
933 /** Hackily arrange for the provided widget to have no tooltip,
934 * and also to stop any other widget from providing one while
935 * the mouse is over w.
938 Gtkmm2ext::set_no_tooltip_whatsoever (Gtk::Widget& w)
940 w.property_has_tooltip() = true;
941 w.signal_query_tooltip().connect (sigc::ptr_fun (make_null_tooltip));
945 Gtkmm2ext::enable_tooltips ()
947 gtk_rc_parse_string ("gtk-enable-tooltips = 1");
948 PersistentTooltip::set_tooltips_enabled (true);
952 Gtkmm2ext::disable_tooltips ()
954 gtk_rc_parse_string ("gtk-enable-tooltips = 0");
955 PersistentTooltip::set_tooltips_enabled (false);
959 Gtkmm2ext::event_inside_widget_window (Gtk::Widget& widget, GdkEvent* ev)
963 if (!gdk_event_get_root_coords (ev, &evx, &evy)) {
969 gint width, height, depth;
972 Glib::RefPtr<Gdk::Window> widget_window = widget.get_window();
974 widget_window->get_geometry (x, y, width, height, depth);
975 widget_window->get_root_origin (wx, wy);
977 if ((evx >= wx && evx < wx + width) &&
978 (evy >= wy && evy < wy + height)) {
986 Gtkmm2ext::event_type_string (int event_type)
988 switch (event_type) {
997 case GDK_MOTION_NOTIFY:
998 return "motion_notify";
999 case GDK_BUTTON_PRESS:
1000 return "button_press";
1001 case GDK_2BUTTON_PRESS:
1002 return "2button_press";
1003 case GDK_3BUTTON_PRESS:
1004 return "3button_press";
1005 case GDK_BUTTON_RELEASE:
1006 return "button_release";
1009 case GDK_KEY_RELEASE:
1010 return "key_release";
1011 case GDK_ENTER_NOTIFY:
1012 return "enter_notify";
1013 case GDK_LEAVE_NOTIFY:
1014 return "leave_notify";
1015 case GDK_FOCUS_CHANGE:
1016 return "focus_change";
1023 case GDK_PROPERTY_NOTIFY:
1024 return "property_notify";
1025 case GDK_SELECTION_CLEAR:
1026 return "selection_clear";
1027 case GDK_SELECTION_REQUEST:
1028 return "selection_request";
1029 case GDK_SELECTION_NOTIFY:
1030 return "selection_notify";
1031 case GDK_PROXIMITY_IN:
1032 return "proximity_in";
1033 case GDK_PROXIMITY_OUT:
1034 return "proximity_out";
1035 case GDK_DRAG_ENTER:
1036 return "drag_enter";
1037 case GDK_DRAG_LEAVE:
1038 return "drag_leave";
1039 case GDK_DRAG_MOTION:
1040 return "drag_motion";
1041 case GDK_DRAG_STATUS:
1042 return "drag_status";
1043 case GDK_DROP_START:
1044 return "drop_start";
1045 case GDK_DROP_FINISHED:
1046 return "drop_finished";
1047 case GDK_CLIENT_EVENT:
1048 return "client_event";
1049 case GDK_VISIBILITY_NOTIFY:
1050 return "visibility_notify";
1055 case GDK_WINDOW_STATE:
1056 return "window_state";
1059 case GDK_OWNER_CHANGE:
1060 return "owner_change";
1061 case GDK_GRAB_BROKEN:
1062 return "grab_broken";
1071 Gtkmm2ext::markup_escape_text (std::string const& s)
1073 return Glib::Markup::escape_text (s);
1077 Gtkmm2ext::add_volume_shortcuts (Gtk::FileChooser& c)
1081 /* This is a first order approach, listing all mounted volumes (incl network).
1082 * One could use `diskutil` or `mount` to query local disks only, or
1083 * something even fancier if deemed appropriate.
1085 Glib::Dir dir("/Volumes");
1086 for (Glib::DirIterator di = dir.begin(); di != dir.end(); di++) {
1087 string fullpath = Glib::build_filename ("/Volumes", *di);
1088 if (!Glib::file_test (fullpath, Glib::FILE_TEST_IS_DIR)) continue;
1090 try { /* add_shortcut_folder throws an exception if the folder being added already has a shortcut */
1091 c.add_shortcut_folder (fullpath);
1093 catch (Glib::Error& e) {
1094 std::cerr << "add_shortcut_folder() threw Glib::Error: " << e.what() << std::endl;
1098 catch (Glib::FileError& e) {
1099 std::cerr << "listing /Volumnes failed: " << e.what() << std::endl;
1105 Gtkmm2ext::paned_position_as_fraction (Gtk::Paned& paned, bool h)
1107 const guint pos = gtk_paned_get_position (const_cast<GtkPaned*>(static_cast<const Gtk::Paned*>(&paned)->gobj()));
1108 return (double) pos / (h ? paned.get_allocation().get_height() : paned.get_allocation().get_width());
1112 Gtkmm2ext::paned_set_position_as_fraction (Gtk::Paned& paned, float fraction, bool h)
1114 gint v = (h ? paned.get_allocation().get_height() : paned.get_allocation().get_width());
1120 paned.set_position ((guint) floor (fraction * v));
1124 Gtkmm2ext::show_gdk_event_state (int state)
1127 if (state & GDK_SHIFT_MASK) {
1130 if (state & GDK_LOCK_MASK) {
1133 if (state & GDK_CONTROL_MASK) {
1136 if (state & GDK_MOD1_MASK) {
1139 if (state & GDK_MOD2_MASK) {
1142 if (state & GDK_MOD3_MASK) {
1145 if (state & GDK_MOD4_MASK) {
1148 if (state & GDK_MOD5_MASK) {
1151 if (state & GDK_BUTTON1_MASK) {
1154 if (state & GDK_BUTTON2_MASK) {
1157 if (state & GDK_BUTTON3_MASK) {
1160 if (state & GDK_BUTTON4_MASK) {
1163 if (state & GDK_BUTTON5_MASK) {
1166 if (state & GDK_SUPER_MASK) {
1169 if (state & GDK_HYPER_MASK) {
1172 if (state & GDK_META_MASK) {
1175 if (state & GDK_RELEASE_MASK) {