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 _position_menu_anchored (int& x, int& y, bool& push_in,
314 const Gtk::Menu* const menu,
315 Gtk::Widget* const anchor,
316 const std::string& selected) {
318 using namespace Gtk::Menu_Helpers;
320 /* TODO: lacks support for rotated dropdown buttons */
322 if (!anchor->has_screen () || !anchor->get_has_window ()) {
326 Gdk::Rectangle monitor;
328 const int monitor_num = anchor->get_screen ()->get_monitor_at_window (
329 anchor->get_window ());
330 anchor->get_screen ()->get_monitor_geometry (
331 (monitor_num < 0) ? 0 : monitor_num, monitor);
334 const Requisition menu_req = menu->size_request();
335 const Gdk::Rectangle allocation = anchor->get_allocation();
337 /* The x and y position are handled separately.
339 * For the x position if the direction is LTR (or RTL), then we try in order:
340 * a) align the left (right) of the menu with the left (right) of the button
341 * if there's enough room until the right (left) border of the screen;
342 * b) align the right (left) of the menu with the right (left) of the button
343 * if there's enough room until the left (right) border of the screen;
344 * c) align the right (left) border of the menu with the right (left) border
345 * of the screen if there's enough space;
346 * d) align the left (right) border of the menu with the left (right) border
347 * of the screen, with the rightmost (leftmost) part of the menu that
348 * overflows the screen.
349 * XXX We always align left regardless of the direction because if x is
350 * left of the current monitor, the menu popup code after us notices it
351 * and enforces that the menu stays in the monitor that's at the left...*/
353 anchor->get_window ()->get_origin (x, y);
355 if (anchor->get_direction() == TEXT_DIR_RTL) {
356 if (monitor.get_x() <= x + allocation.get_width() - menu_req.width) {
357 /* a) align menu right and button right */
358 x += allocation.get_width() - menu_req.width;
359 } else if (x + menu_req.width <= monitor.get_x() + monitor.get_width()) {
360 /* b) align menu left and button left: nothing to do*/
361 } else if (menu_req.width > monitor.get_width()) {
362 /* c) align menu left and screen left, guaranteed to fit */
365 /* d) XXX align left or the menu might change monitors */
369 if (x + menu_req.width <= monitor.get_x() + monitor.get_width()) {
370 /* a) align menu left and button left: nothing to do*/
371 } else if (monitor.get_x() <= x + allocation.get_width() - menu_req.width) {
372 /* b) align menu right and button right */
373 x += allocation.get_width() - menu_req.width;
374 } else if (menu_req.width > monitor.get_width()) {
375 /* c) align menu right and screen right, guaranteed to fit */
376 x = monitor.get_x() + monitor.get_width() - menu_req.width;
383 /* For the y position, try in order:
384 * a) if there is a menu item with the same text as the button, align it
385 * with the button, unless that makes the menu overflow the monitor.
386 * b) align the top of the menu with the bottom of the button if there is
387 * enough room below the button;
388 * c) align the bottom of the menu with the top of the button if there is
389 * enough room above the button;
390 * d) align the bottom of the menu with the bottom of the monitor if there
391 * is enough room, but avoid moving the menu to another monitor */
393 const MenuList& items = menu->items ();
396 MenuList::const_iterator i = items.begin();
397 for ( ; i != items.end(); ++i) {
398 const Label* label_widget = dynamic_cast<const Label*>(i->get_child());
399 if (label_widget && selected == ((std::string) label_widget->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::anchored_menu_popup (Gtk::Menu* const menu,
421 Gtk::Widget* const anchor,
422 const std::string& selected,
423 guint button, guint32 time) {
425 sigc::bind (sigc::ptr_fun(&_position_menu_anchored),
426 menu, anchor, selected),
432 Gtkmm2ext::set_popdown_strings (Gtk::ComboBoxText& cr, const vector<string>& strings)
434 vector<string>::const_iterator i;
438 for (i = strings.begin(); i != strings.end(); ++i) {
444 Gtkmm2ext::get_popdown_strings (Gtk::ComboBoxText& cr, std::vector<std::string>& strings)
447 Glib::RefPtr<const Gtk::TreeModel> m = cr.get_model();
451 for(Gtk::TreeModel::iterator i = m->children().begin(); i != m->children().end(); ++i) {
453 (*i)->get_value(0, txt);
454 strings.push_back (txt);
459 Gtkmm2ext::get_popdown_string_count (Gtk::ComboBoxText& cr)
461 Glib::RefPtr<const Gtk::TreeModel> m = cr.get_model();
465 return m->children().size();
469 Gtkmm2ext::contains_value (Gtk::ComboBoxText& cr, const std::string text)
471 std::vector<std::string> s;
472 get_popdown_strings (cr, s);
473 return (std::find (s.begin(), s.end(), text) != s.end());
477 Gtkmm2ext::set_active_text_if_present (Gtk::ComboBoxText& cr, const std::string text)
479 if (contains_value(cr, text)) {
480 cr.set_active_text (text);
487 Gtkmm2ext::get_paned_handle (Gtk::Paned& paned)
489 return GTK_PANED(paned.gobj())->handle;
493 Gtkmm2ext::set_decoration (Gtk::Window* win, Gdk::WMDecoration decor)
495 win->get_window()->set_decorations (decor);
498 void Gtkmm2ext::set_treeview_header_as_default_label(Gtk::TreeViewColumn* c)
500 gtk_tree_view_column_set_widget( c->gobj(), GTK_WIDGET(0) );
504 Gtkmm2ext::detach_menu (Gtk::Menu& menu)
506 /* its possible for a Gtk::Menu to have no gobj() because it has
507 not yet been instantiated. Catch this and provide a safe
511 if (menu.get_attach_widget()) {
518 Gtkmm2ext::possibly_translate_keyval_to_make_legal_accelerator (uint32_t& keyval)
520 int fakekey = GDK_VoidSymbol;
524 case GDK_ISO_Left_Tab:
529 fakekey = GDK_uparrow;
533 fakekey = GDK_downarrow;
537 fakekey = GDK_rightarrow;
541 fakekey = GDK_leftarrow;
545 fakekey = GDK_3270_Enter;
556 if (fakekey != GDK_VoidSymbol) {
565 Gtkmm2ext::possibly_translate_legal_accelerator_to_real_key (uint32_t keyval)
600 Gtkmm2ext::physical_screen_height (Glib::RefPtr<Gdk::Window> win)
602 GdkScreen* scr = gdk_screen_get_default();
606 gint monitor = gdk_screen_get_monitor_at_window (scr, win->gobj());
607 gdk_screen_get_monitor_geometry (scr, monitor, &r);
610 return gdk_screen_get_height (scr);
615 Gtkmm2ext::physical_screen_width (Glib::RefPtr<Gdk::Window> win)
617 GdkScreen* scr = gdk_screen_get_default();
621 gint monitor = gdk_screen_get_monitor_at_window (scr, win->gobj());
622 gdk_screen_get_monitor_geometry (scr, monitor, &r);
625 return gdk_screen_get_width (scr);
630 Gtkmm2ext::container_clear (Gtk::Container& c)
632 list<Gtk::Widget*> children = c.get_children();
633 for (list<Gtk::Widget*>::iterator child = children.begin(); child != children.end(); ++child) {
640 Gtkmm2ext::rounded_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
642 rounded_rectangle (context->cobj(), x, y, w, h, r);
645 Gtkmm2ext::rounded_top_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
647 rounded_top_rectangle (context->cobj(), x, y, w, h, r);
650 Gtkmm2ext::rounded_top_left_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
652 rounded_top_left_rectangle (context->cobj(), x, y, w, h, r);
655 Gtkmm2ext::rounded_top_right_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
657 rounded_top_right_rectangle (context->cobj(), x, y, w, h, r);
660 Gtkmm2ext::rounded_top_half_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
662 rounded_top_half_rectangle (context->cobj(), x, y, w, h, r);
665 Gtkmm2ext::rounded_bottom_half_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
667 rounded_bottom_half_rectangle (context->cobj(), x, y, w, h, r);
671 Gtkmm2ext::rounded_left_half_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
673 rounded_left_half_rectangle (context->cobj(), x, y, w, h, r);
677 Gtkmm2ext::rounded_right_half_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
679 rounded_right_half_rectangle (context->cobj(), x, y, w, h, r);
683 Gtkmm2ext::rounded_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_arc (cr, x + w - r, y + r, r, -90 * degrees, 0 * degrees); //tr
689 cairo_arc (cr, x + w - r, y + h - r, r, 0 * degrees, 90 * degrees); //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_left_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_line_to (cr, x+w, y); // tr
702 cairo_line_to (cr, x+w, y + h); // br
703 cairo_arc (cr, x + r, y + h - r, r, 90 * degrees, 180 * degrees); //bl
704 cairo_arc (cr, x + r, y + r, r, 180 * degrees, 270 * degrees); //tl
705 cairo_close_path (cr);
709 Gtkmm2ext::rounded_right_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_arc (cr, x + w - r, y + r, r, -90 * degrees, 0 * degrees); //tr
715 cairo_arc (cr, x + w - r, y + h - r, r, 0 * degrees, 90 * degrees); //br
716 cairo_line_to (cr, x, y + h); // bl
717 cairo_line_to (cr, x, y); // tl
718 cairo_close_path (cr);
722 Gtkmm2ext::rounded_top_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+w, y+h);
728 cairo_line_to (cr, x, y+h);
729 cairo_arc (cr, x + r, y + r, r, 180 * degrees, 270 * degrees); //tl
730 cairo_arc (cr, x + w - r, y + r, r, -90 * degrees, 0 * degrees); //tr
731 cairo_close_path (cr);
735 Gtkmm2ext::rounded_bottom_half_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
737 double degrees = M_PI / 180.0;
739 cairo_new_sub_path (cr);
740 cairo_move_to (cr, x, y);
741 cairo_line_to (cr, x+w, y);
742 cairo_arc (cr, x + w - r, y + h - r, r, 0 * degrees, 90 * degrees); //br
743 cairo_arc (cr, x + r, y + h - r, r, 90 * degrees, 180 * degrees); //bl
744 cairo_close_path (cr);
749 Gtkmm2ext::rounded_top_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
751 double degrees = M_PI / 180.0;
753 cairo_new_sub_path (cr);
754 cairo_move_to (cr, x+w, y+h);
755 cairo_line_to (cr, x, y+h);
756 cairo_arc (cr, x + r, y + r, r, 180 * degrees, 270 * degrees); //tl
757 cairo_arc (cr, x + w - r, y + r, r, -90 * degrees, 0 * degrees); //tr
758 cairo_close_path (cr);
762 Gtkmm2ext::rounded_top_left_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
770 cairo_move_to (cr, x+r,y); // Move to A
771 cairo_line_to (cr, x+w,y); // Straight line to B
772 cairo_line_to (cr, x+w,y+h); // Move to E
773 cairo_line_to (cr, x,y+h); // Line to F
774 cairo_line_to (cr, x,y+r); // Line to H
775 cairo_curve_to (cr, x,y,x,y,x+r,y); // Curve to A
779 Gtkmm2ext::rounded_top_right_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
787 cairo_move_to (cr, x,y); // Move to A
788 cairo_line_to (cr, x+w-r,y); // Straight line to B
789 cairo_curve_to (cr, x+w,y,x+w,y,x+w,y+r); // Curve to C, Control points are both at Q
790 cairo_line_to (cr, x+w,y+h); // Move to E
791 cairo_line_to (cr, x,y+h); // Line to F
792 cairo_line_to (cr, x,y); // Line to A
795 Glib::RefPtr<Gdk::Window>
796 Gtkmm2ext::window_to_draw_on (Gtk::Widget& w, Gtk::Widget** parent)
798 if (w.get_has_window()) {
799 return w.get_window();
802 (*parent) = w.get_parent();
805 if ((*parent)->get_has_window()) {
806 return (*parent)->get_window ();
808 (*parent) = (*parent)->get_parent ();
811 return Glib::RefPtr<Gdk::Window> ();
815 Gtkmm2ext::pixel_width (const string& str, const Pango::FontDescription& font)
817 Glib::RefPtr<Pango::Context> context = Glib::wrap (gdk_pango_context_get());
818 Glib::RefPtr<Pango::Layout> layout = Pango::Layout::create (context);
820 layout->set_font_description (font);
821 layout->set_text (str);
824 Gtkmm2ext::get_ink_pixel_size (layout, width, height);
827 // Pango returns incorrect text width on some OS X
828 // So we have to make a correction
829 // To determine the correct indent take the largest symbol for which the width is correct
830 // and make the calculation
832 // see also libs/canvas/text.cc
834 layout->set_text ("H");
835 layout->get_pixel_size (cor_width, height);
836 width += cor_width * 1.5;
843 Gtkmm2ext::pixel_size (const string& str, const Pango::FontDescription& font, int& width, int& height)
846 Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout ("");
848 layout->set_font_description (font);
849 layout->set_text (str);
851 Gtkmm2ext::get_ink_pixel_size (layout, width, height);
856 Gtkmm2ext::fit_to_pixels (const string& str, int pixel_width, Pango::FontDescription& font, int& actual_width, bool with_ellipses)
858 /* DECEMBER 2011: THIS PROTOTYPE OF fit_to_pixels() IS NOT USED
859 ANYWHERE AND HAS NOT BEEN TESTED.
862 Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout (str);
863 Glib::RefPtr<const Pango::LayoutLine> line;
865 layout->set_font_description (font);
866 layout->set_width (pixel_width * PANGO_SCALE);
869 layout->set_ellipsize (Pango::ELLIPSIZE_END);
871 layout->set_wrap (Pango::WRAP_CHAR);
874 line = layout->get_line (0);
876 /* XXX: might need special care to get the ellipsis character, not sure
880 string s = string (layout->get_text ().substr(line->get_start_index(), line->get_length()));
882 cerr << "fit to pixels of " << str << " returns " << s << endl;
888 /** Try to fit a string into a given horizontal space by ellipsizing it.
889 * @param cr Cairo context in which the text will be plotted.
891 * @param avail Available horizontal space.
892 * @return (Text, possibly ellipsized) and (horizontal size of text)
895 std::pair<std::string, double>
896 Gtkmm2ext::fit_to_pixels (cairo_t* cr, std::string name, double avail)
898 /* XXX hopefully there exists a more efficient way of doing this */
900 bool abbreviated = false;
904 cairo_text_extents_t ext;
905 cairo_text_extents (cr, name.c_str(), &ext);
907 if (ext.width < avail || name.length() <= 4) {
913 name = name.substr (0, name.length() - 4) + "...";
915 name = name.substr (0, name.length() - 3) + "...";
920 return std::make_pair (name, width);
924 Gtkmm2ext::left_aligned_label (string const & t)
926 Gtk::Label* l = new Gtk::Label (t);
927 l->set_alignment (0, 0.5);
932 Gtkmm2ext::right_aligned_label (string const & t)
934 Gtk::Label* l = new Gtk::Label (t);
935 l->set_alignment (1, 0.5);
940 make_null_tooltip (int, int, bool, const Glib::RefPtr<Gtk::Tooltip>& t)
942 t->set_tip_area (Gdk::Rectangle (0, 0, 0, 0));
946 /** Hackily arrange for the provided widget to have no tooltip,
947 * and also to stop any other widget from providing one while
948 * the mouse is over w.
951 Gtkmm2ext::set_no_tooltip_whatsoever (Gtk::Widget& w)
953 w.property_has_tooltip() = true;
954 w.signal_query_tooltip().connect (sigc::ptr_fun (make_null_tooltip));
958 Gtkmm2ext::enable_tooltips ()
960 gtk_rc_parse_string ("gtk-enable-tooltips = 1");
961 PersistentTooltip::set_tooltips_enabled (true);
965 Gtkmm2ext::disable_tooltips ()
967 gtk_rc_parse_string ("gtk-enable-tooltips = 0");
968 PersistentTooltip::set_tooltips_enabled (false);
972 Gtkmm2ext::event_inside_widget_window (Gtk::Widget& widget, GdkEvent* ev)
976 if (!gdk_event_get_root_coords (ev, &evx, &evy)) {
982 gint width, height, depth;
985 Glib::RefPtr<Gdk::Window> widget_window = widget.get_window();
987 widget_window->get_geometry (x, y, width, height, depth);
988 widget_window->get_root_origin (wx, wy);
990 if ((evx >= wx && evx < wx + width) &&
991 (evy >= wy && evy < wy + height)) {
999 Gtkmm2ext::event_type_string (int event_type)
1001 switch (event_type) {
1010 case GDK_MOTION_NOTIFY:
1011 return "motion_notify";
1012 case GDK_BUTTON_PRESS:
1013 return "button_press";
1014 case GDK_2BUTTON_PRESS:
1015 return "2button_press";
1016 case GDK_3BUTTON_PRESS:
1017 return "3button_press";
1018 case GDK_BUTTON_RELEASE:
1019 return "button_release";
1022 case GDK_KEY_RELEASE:
1023 return "key_release";
1024 case GDK_ENTER_NOTIFY:
1025 return "enter_notify";
1026 case GDK_LEAVE_NOTIFY:
1027 return "leave_notify";
1028 case GDK_FOCUS_CHANGE:
1029 return "focus_change";
1036 case GDK_PROPERTY_NOTIFY:
1037 return "property_notify";
1038 case GDK_SELECTION_CLEAR:
1039 return "selection_clear";
1040 case GDK_SELECTION_REQUEST:
1041 return "selection_request";
1042 case GDK_SELECTION_NOTIFY:
1043 return "selection_notify";
1044 case GDK_PROXIMITY_IN:
1045 return "proximity_in";
1046 case GDK_PROXIMITY_OUT:
1047 return "proximity_out";
1048 case GDK_DRAG_ENTER:
1049 return "drag_enter";
1050 case GDK_DRAG_LEAVE:
1051 return "drag_leave";
1052 case GDK_DRAG_MOTION:
1053 return "drag_motion";
1054 case GDK_DRAG_STATUS:
1055 return "drag_status";
1056 case GDK_DROP_START:
1057 return "drop_start";
1058 case GDK_DROP_FINISHED:
1059 return "drop_finished";
1060 case GDK_CLIENT_EVENT:
1061 return "client_event";
1062 case GDK_VISIBILITY_NOTIFY:
1063 return "visibility_notify";
1068 case GDK_WINDOW_STATE:
1069 return "window_state";
1072 case GDK_OWNER_CHANGE:
1073 return "owner_change";
1074 case GDK_GRAB_BROKEN:
1075 return "grab_broken";
1084 Gtkmm2ext::markup_escape_text (std::string const& s)
1086 return Glib::Markup::escape_text (s);
1090 Gtkmm2ext::add_volume_shortcuts (Gtk::FileChooser& c)
1094 /* This is a first order approach, listing all mounted volumes (incl network).
1095 * One could use `diskutil` or `mount` to query local disks only, or
1096 * something even fancier if deemed appropriate.
1098 Glib::Dir dir("/Volumes");
1099 for (Glib::DirIterator di = dir.begin(); di != dir.end(); di++) {
1100 string fullpath = Glib::build_filename ("/Volumes", *di);
1101 if (!Glib::file_test (fullpath, Glib::FILE_TEST_IS_DIR)) continue;
1103 try { /* add_shortcut_folder throws an exception if the folder being added already has a shortcut */
1104 c.add_shortcut_folder (fullpath);
1106 catch (Glib::Error& e) {
1107 std::cerr << "add_shortcut_folder() threw Glib::Error: " << e.what() << std::endl;
1111 catch (Glib::FileError& e) {
1112 std::cerr << "listing /Volumnes failed: " << e.what() << std::endl;
1118 Gtkmm2ext::paned_position_as_fraction (Gtk::Paned& paned, bool h)
1120 const guint pos = gtk_paned_get_position (const_cast<GtkPaned*>(static_cast<const Gtk::Paned*>(&paned)->gobj()));
1121 return (double) pos / (h ? paned.get_allocation().get_height() : paned.get_allocation().get_width());
1125 Gtkmm2ext::paned_set_position_as_fraction (Gtk::Paned& paned, float fraction, bool h)
1127 gint v = (h ? paned.get_allocation().get_height() : paned.get_allocation().get_width());
1133 paned.set_position ((guint) floor (fraction * v));
1137 Gtkmm2ext::show_gdk_event_state (int state)
1140 if (state & GDK_SHIFT_MASK) {
1143 if (state & GDK_LOCK_MASK) {
1146 if (state & GDK_CONTROL_MASK) {
1149 if (state & GDK_MOD1_MASK) {
1152 if (state & GDK_MOD2_MASK) {
1155 if (state & GDK_MOD3_MASK) {
1158 if (state & GDK_MOD4_MASK) {
1161 if (state & GDK_MOD5_MASK) {
1164 if (state & GDK_BUTTON1_MASK) {
1167 if (state & GDK_BUTTON2_MASK) {
1170 if (state & GDK_BUTTON3_MASK) {
1173 if (state & GDK_BUTTON4_MASK) {
1176 if (state & GDK_BUTTON5_MASK) {
1179 if (state & GDK_SUPER_MASK) {
1182 if (state & GDK_HYPER_MASK) {
1185 if (state & GDK_META_MASK) {
1188 if (state & GDK_RELEASE_MASK) {