2 * Copyright (C) 1999-2016 Paul Davis <paul@linuxaudiosystems.com>
3 * Copyright (C) 2005-2008 Doug McLain <doug@nostar.net>
4 * Copyright (C) 2010-2012 Carl Hetherington <carl@carlh.net>
5 * Copyright (C) 2011-2015 David Robillard <d@drobilla.net>
6 * Copyright (C) 2014-2016 John Emmas <john@creativepost.co.uk>
7 * Copyright (C) 2014-2019 Robin Gareus <robin@gareus.org>
8 * Copyright (C) 2016 Julien "_FrnchFrgg_" RIVAUD <frnchfrgg@free.fr>
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License along
21 * with this program; if not, write to the Free Software Foundation, Inc.,
22 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
29 #include <gtk/gtkpaned.h>
32 #include <gtkmm/widget.h>
33 #include <gtkmm/button.h>
34 #include <gtkmm/window.h>
35 #include <gtkmm/paned.h>
36 #include <gtkmm/label.h>
37 #include <gtkmm/comboboxtext.h>
38 #include <gtkmm/tooltip.h>
39 #include <gtkmm/menuitem.h>
41 #include "gtkmm2ext/utils.h"
42 #include "gtkmm2ext/persistent_tooltip.h"
49 Gtkmm2ext::init (const char* localedir)
52 (void) bindtextdomain(PACKAGE, localedir);
53 (void) bind_textdomain_codeset (PACKAGE, "UTF-8");
58 Gtkmm2ext::get_ink_pixel_size (Glib::RefPtr<Pango::Layout> layout,
62 Pango::Rectangle ink_rect = layout->get_ink_extents ();
64 width = PANGO_PIXELS(ink_rect.get_width());
65 height = PANGO_PIXELS(ink_rect.get_height());
69 Gtkmm2ext::get_pixel_size (Glib::RefPtr<Pango::Layout> layout,
73 layout->get_pixel_size (width, height);
77 Gtkmm2ext::set_size_request_to_display_given_text (Gtk::Widget &w, const gchar *text,
78 gint hpadding, gint vpadding)
83 get_pixel_size (w.create_pango_layout (text), width, height);
84 w.set_size_request(width + hpadding, height + vpadding);
87 /** Set width request to display given text, and height to display anything.
88 * This is useful for setting many widgets to the same height for consistency. */
90 Gtkmm2ext::set_size_request_to_display_given_text_width (Gtk::Widget& w,
95 static const gchar* vtext = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
100 get_pixel_size (w.create_pango_layout (htext), hwidth, hheight);
103 get_pixel_size (w.create_pango_layout (vtext), vwidth, vheight);
105 w.set_size_request(hwidth + hpadding, vheight + vpadding);
109 Gtkmm2ext::set_height_request_to_display_any_text (Gtk::Widget& w, gint vpadding)
111 static const gchar* vtext = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
116 get_pixel_size (w.create_pango_layout (vtext), width, height);
118 w.set_size_request(-1, height + vpadding);
122 Gtkmm2ext::set_size_request_to_display_given_text (Gtk::Widget &w, std::string const & text,
123 gint hpadding, gint vpadding)
128 get_pixel_size (w.create_pango_layout (text), width, height);
129 w.set_size_request(width + hpadding, height + vpadding);
133 Gtkmm2ext::set_size_request_to_display_given_text (Gtk::Widget &w,
134 const std::vector<std::string>& strings,
135 gint hpadding, gint vpadding)
142 const vector<string>* to_use;
143 vector<string>::const_iterator i;
145 for (i = strings.begin(); i != strings.end(); ++i) {
146 if ((*i).find_first_of ("gy") != string::npos) {
147 /* contains a descender */
152 if (i == strings.end()) {
153 /* make a copy of the strings then add one that has a descender */
155 copy.push_back ("g");
161 for (vector<string>::const_iterator i = to_use->begin(); i != to_use->end(); ++i) {
162 get_pixel_size (w.create_pango_layout (*i), width, height);
163 width_max = max(width_max,width);
164 height_max = max(height_max, height);
167 w.set_size_request(width_max + hpadding, height_max + vpadding);
170 /** This version specifies horizontal padding in text to avoid assumptions
171 * about font size. Should be used anywhere padding is used to avoid text,
175 Gtkmm2ext::set_size_request_to_display_given_text (Gtk::Widget& w,
176 const std::vector<std::string>& strings,
177 const std::string& hpadding,
184 for (vector<string>::const_iterator i = strings.begin(); i != strings.end(); ++i) {
186 get_pixel_size (w.create_pango_layout (*i), width, height);
187 width_max = max(width_max,width);
188 height_max = max(height_max, height);
193 get_pixel_size (w.create_pango_layout (hpadding), pad_width, pad_height);
195 w.set_size_request(width_max + pad_width, height_max + vpadding);
199 demultiply_alpha (guint8 src,
202 /* cairo pixel buffer data contains RGB values with the alpha
203 * values premultiplied.
205 * GdkPixbuf pixel buffer data contains RGB values without the
206 * alpha value applied.
208 * this removes the alpha component from the cairo version and
209 * returns the GdkPixbuf version.
211 return alpha ? ((guint (src) << 8) - src) / alpha : 0;
215 Gtkmm2ext::convert_bgra_to_rgba (guint8 const* src,
220 guint8 const* src_pixel = src;
221 guint8* dst_pixel = dst;
223 /* cairo pixel data is endian-dependent ARGB with A in the most significant 8 bits,
224 * with premultipled alpha values (see preceding function)
226 * GdkPixbuf pixel data is non-endian-dependent RGBA with R in the lowest addressable
227 * 8 bits, and non-premultiplied alpha values.
229 * convert from the cairo values to the GdkPixbuf ones.
232 for (int y = 0; y < height; y++) {
233 for (int x = 0; x < width; x++) {
234 #if G_BYTE_ORDER == G_LITTLE_ENDIAN
235 /* Cairo [ B G R A ] is actually [ B G R A ] in memory SOURCE
237 Pixbuf [ R G B A ] is actually [ R G B A ] in memory DEST
239 dst_pixel[0] = demultiply_alpha (src_pixel[2],
240 src_pixel[3]); // R [0] <= [ 2 ]
241 dst_pixel[1] = demultiply_alpha (src_pixel[1],
242 src_pixel[3]); // G [1] <= [ 1 ]
243 dst_pixel[2] = demultiply_alpha (src_pixel[0],
244 src_pixel[3]); // B [2] <= [ 0 ]
245 dst_pixel[3] = src_pixel[3]; // alpha
247 #elif G_BYTE_ORDER == G_BIG_ENDIAN
248 /* Cairo [ B G R A ] is actually [ A R G B ] in memory SOURCE
250 Pixbuf [ R G B A ] is actually [ R G B A ] in memory DEST
252 dst_pixel[0] = demultiply_alpha (src_pixel[1],
253 src_pixel[0]); // R [0] <= [ 1 ]
254 dst_pixel[1] = demultiply_alpha (src_pixel[2],
255 src_pixel[0]); // G [1] <= [ 2 ]
256 dst_pixel[2] = demultiply_alpha (src_pixel[3],
257 src_pixel[0]); // B [2] <= [ 3 ]
258 dst_pixel[3] = src_pixel[0]; // alpha
261 #error ardour does not currently support PDP-endianess
270 Glib::RefPtr<Gdk::Pixbuf>
271 Gtkmm2ext::pixbuf_from_string(const string& name, const Pango::FontDescription& font, int clip_width, int clip_height, Gdk::Color fg)
273 static Glib::RefPtr<Gdk::Pixbuf>* empty_pixbuf = 0;
276 if (empty_pixbuf == 0) {
277 empty_pixbuf = new Glib::RefPtr<Gdk::Pixbuf>;
278 *empty_pixbuf = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB, true, 8, clip_width, clip_height);
280 return *empty_pixbuf;
283 if (clip_width <= 0 || clip_height <= 0) {
284 /* negative values mean padding around natural size */
286 pixel_size (name, font, width, height);
287 if (clip_width <= 0) {
288 clip_width = width - clip_width;
290 if (clip_height <= 0) {
291 clip_height = height - clip_height;
295 Glib::RefPtr<Gdk::Pixbuf> buf = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB, true, 8, clip_width, clip_height);
297 cairo_surface_t* surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, clip_width, clip_height);
298 cairo_t* cr = cairo_create (surface);
299 cairo_text_extents_t te;
301 cairo_set_source_rgba (cr, fg.get_red_p(), fg.get_green_p(), fg.get_blue_p(), 1.0);
302 cairo_select_font_face (cr, font.get_family().c_str(),
303 CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
304 cairo_set_font_size (cr, font.get_size() / Pango::SCALE);
305 cairo_text_extents (cr, name.c_str(), &te);
307 cairo_move_to (cr, 0.5, int (0.5 - te.height / 2 - te.y_bearing + clip_height / 2));
308 cairo_show_text (cr, name.c_str());
310 convert_bgra_to_rgba(cairo_image_surface_get_data (surface), buf->get_pixels(), clip_width, clip_height);
313 cairo_surface_destroy(surface);
319 _position_menu_anchored (int& x, int& y, bool& push_in,
320 const Gtk::Menu* const menu,
321 Gtk::Widget* const anchor,
322 const std::string& selected)
325 using namespace Gtk::Menu_Helpers;
327 /* TODO: lacks support for rotated dropdown buttons */
329 if (!anchor->has_screen () || !anchor->get_has_window ()) {
333 Gdk::Rectangle monitor;
335 const int monitor_num = anchor->get_screen ()->get_monitor_at_window (
336 anchor->get_window ());
337 anchor->get_screen ()->get_monitor_geometry (
338 (monitor_num < 0) ? 0 : monitor_num, monitor);
341 const Requisition menu_req = menu->size_request();
342 const Gdk::Rectangle allocation = anchor->get_allocation();
344 /* The x and y position are handled separately.
346 * For the x position if the direction is LTR (or RTL), then we try in order:
347 * a) align the left (right) of the menu with the left (right) of the button
348 * if there's enough room until the right (left) border of the screen;
349 * b) align the right (left) of the menu with the right (left) of the button
350 * if there's enough room until the left (right) border of the screen;
351 * c) align the right (left) border of the menu with the right (left) border
352 * of the screen if there's enough space;
353 * d) align the left (right) border of the menu with the left (right) border
354 * of the screen, with the rightmost (leftmost) part of the menu that
355 * overflows the screen.
356 * XXX We always align left regardless of the direction because if x is
357 * left of the current monitor, the menu popup code after us notices it
358 * and enforces that the menu stays in the monitor that's at the left...*/
360 anchor->get_window ()->get_origin (x, y);
362 if (anchor->get_direction() == TEXT_DIR_RTL) {
363 if (monitor.get_x() <= x + allocation.get_width() - menu_req.width) {
364 /* a) align menu right and button right */
365 x += allocation.get_width() - menu_req.width;
366 } else if (x + menu_req.width <= monitor.get_x() + monitor.get_width()) {
367 /* b) align menu left and button left: nothing to do*/
368 } else if (menu_req.width > monitor.get_width()) {
369 /* c) align menu left and screen left, guaranteed to fit */
372 /* d) XXX align left or the menu might change monitors */
376 if (x + menu_req.width <= monitor.get_x() + monitor.get_width()) {
377 /* a) align menu left and button left: nothing to do*/
378 } else if (monitor.get_x() <= x + allocation.get_width() - menu_req.width) {
379 /* b) align menu right and button right */
380 x += allocation.get_width() - menu_req.width;
381 } else if (menu_req.width > monitor.get_width()) {
382 /* c) align menu right and screen right, guaranteed to fit */
383 x = monitor.get_x() + monitor.get_width() - menu_req.width;
390 /* For the y position, try in order:
391 * a) if there is a menu item with the same text as the button, align it
392 * with the button, unless that makes the menu overflow the monitor.
393 * b) align the top of the menu with the bottom of the button if there is
394 * enough room below the button;
395 * c) align the bottom of the menu with the top of the button if there is
396 * enough room above the button;
397 * d) align the bottom of the menu with the bottom of the monitor if there
398 * is enough room, but avoid moving the menu to another monitor */
400 const MenuList& items = menu->items ();
403 MenuList::const_iterator i = items.begin();
404 for ( ; i != items.end(); ++i) {
405 const Label* label_widget = dynamic_cast<const Label*>(i->get_child());
406 if (label_widget && selected == ((std::string) label_widget->get_label())) {
409 offset += i->size_request().height;
411 if (i != items.end() &&
412 y - offset >= monitor.get_y() &&
413 y - offset + menu_req.height <= monitor.get_y() + monitor.get_height()) {
415 } else if (y + allocation.get_height() + menu_req.height <= monitor.get_y() + monitor.get_height()) {
416 y += allocation.get_height(); /* a) */
417 } else if ((y - menu_req.height) >= monitor.get_y()) {
418 y -= menu_req.height; /* b) */
420 y = monitor.get_y() + max(0, monitor.get_height() - menu_req.height);
427 Gtkmm2ext::anchored_menu_popup (Gtk::Menu* const menu,
428 Gtk::Widget* const anchor,
429 const std::string& selected,
430 guint button, guint32 time)
433 sigc::bind (sigc::ptr_fun(&_position_menu_anchored),
434 menu, anchor, selected),
440 Gtkmm2ext::set_popdown_strings (Gtk::ComboBoxText& cr, const vector<string>& strings)
442 vector<string>::const_iterator i;
446 for (i = strings.begin(); i != strings.end(); ++i) {
452 Gtkmm2ext::get_popdown_strings (Gtk::ComboBoxText& cr, std::vector<std::string>& strings)
455 Glib::RefPtr<const Gtk::TreeModel> m = cr.get_model();
459 for(Gtk::TreeModel::iterator i = m->children().begin(); i != m->children().end(); ++i) {
461 (*i)->get_value(0, txt);
462 strings.push_back (txt);
467 Gtkmm2ext::get_popdown_string_count (Gtk::ComboBoxText& cr)
469 Glib::RefPtr<const Gtk::TreeModel> m = cr.get_model();
473 return m->children().size();
477 Gtkmm2ext::contains_value (Gtk::ComboBoxText& cr, const std::string text)
479 std::vector<std::string> s;
480 get_popdown_strings (cr, s);
481 return (std::find (s.begin(), s.end(), text) != s.end());
485 Gtkmm2ext::set_active_text_if_present (Gtk::ComboBoxText& cr, const std::string text)
487 if (contains_value(cr, text)) {
488 cr.set_active_text (text);
495 Gtkmm2ext::get_paned_handle (Gtk::Paned& paned)
497 return GTK_PANED(paned.gobj())->handle;
501 Gtkmm2ext::set_decoration (Gtk::Window* win, Gdk::WMDecoration decor)
503 win->get_window()->set_decorations (decor);
506 void Gtkmm2ext::set_treeview_header_as_default_label(Gtk::TreeViewColumn* c)
508 gtk_tree_view_column_set_widget( c->gobj(), GTK_WIDGET(0) );
512 Gtkmm2ext::detach_menu (Gtk::Menu& menu)
514 /* its possible for a Gtk::Menu to have no gobj() because it has
515 not yet been instantiated. Catch this and provide a safe
519 if (menu.get_attach_widget()) {
526 Gtkmm2ext::possibly_translate_keyval_to_make_legal_accelerator (uint32_t& keyval)
528 int fakekey = GDK_VoidSymbol;
532 case GDK_ISO_Left_Tab:
537 fakekey = GDK_uparrow;
541 fakekey = GDK_downarrow;
545 fakekey = GDK_rightarrow;
549 fakekey = GDK_leftarrow;
553 fakekey = GDK_3270_Enter;
564 if (fakekey != GDK_VoidSymbol) {
573 Gtkmm2ext::possibly_translate_legal_accelerator_to_real_key (uint32_t keyval)
608 Gtkmm2ext::physical_screen_height (Glib::RefPtr<Gdk::Window> win)
610 GdkScreen* scr = gdk_screen_get_default();
614 gint monitor = gdk_screen_get_monitor_at_window (scr, win->gobj());
615 gdk_screen_get_monitor_geometry (scr, monitor, &r);
618 return gdk_screen_get_height (scr);
623 Gtkmm2ext::physical_screen_width (Glib::RefPtr<Gdk::Window> win)
625 GdkScreen* scr = gdk_screen_get_default();
629 gint monitor = gdk_screen_get_monitor_at_window (scr, win->gobj());
630 gdk_screen_get_monitor_geometry (scr, monitor, &r);
633 return gdk_screen_get_width (scr);
638 Gtkmm2ext::container_clear (Gtk::Container& c)
640 list<Gtk::Widget*> children = c.get_children();
641 for (list<Gtk::Widget*>::iterator child = children.begin(); child != children.end(); ++child) {
648 Gtkmm2ext::rounded_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
650 rounded_rectangle (context->cobj(), x, y, w, h, r);
653 Gtkmm2ext::rounded_top_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
655 rounded_top_rectangle (context->cobj(), x, y, w, h, r);
658 Gtkmm2ext::rounded_top_left_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
660 rounded_top_left_rectangle (context->cobj(), x, y, w, h, r);
663 Gtkmm2ext::rounded_top_right_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
665 rounded_top_right_rectangle (context->cobj(), x, y, w, h, r);
668 Gtkmm2ext::rounded_top_half_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
670 rounded_top_half_rectangle (context->cobj(), x, y, w, h, r);
673 Gtkmm2ext::rounded_bottom_half_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
675 rounded_bottom_half_rectangle (context->cobj(), x, y, w, h, r);
679 Gtkmm2ext::rounded_left_half_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
681 rounded_left_half_rectangle (context->cobj(), x, y, w, h, r);
685 Gtkmm2ext::rounded_right_half_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
687 rounded_right_half_rectangle (context->cobj(), x, y, w, h, r);
691 Gtkmm2ext::rounded_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
693 static const double degrees = M_PI / 180.0;
695 cairo_rectangle (cr, x, y, w, h);
699 cairo_new_sub_path (cr);
700 cairo_arc (cr, x + w - r, y + r, r, -90 * degrees, 0 * degrees); //tr
701 cairo_arc (cr, x + w - r, y + h - r, r, 0 * degrees, 90 * degrees); //br
702 cairo_arc (cr, x + r, y + h - r, r, 90 * degrees, 180 * degrees); //bl
703 cairo_arc (cr, x + r, y + r, r, 180 * degrees, 270 * degrees); //tl
704 cairo_close_path (cr);
708 Gtkmm2ext::rounded_left_half_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
710 static const double degrees = M_PI / 180.0;
712 cairo_new_sub_path (cr);
713 cairo_line_to (cr, x+w, y); // tr
714 cairo_line_to (cr, x+w, y + h); // br
715 cairo_arc (cr, x + r, y + h - r, r, 90 * degrees, 180 * degrees); //bl
716 cairo_arc (cr, x + r, y + r, r, 180 * degrees, 270 * degrees); //tl
717 cairo_close_path (cr);
721 Gtkmm2ext::rounded_right_half_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
723 static const double degrees = M_PI / 180.0;
725 cairo_new_sub_path (cr);
726 cairo_arc (cr, x + w - r, y + r, r, -90 * degrees, 0 * degrees); //tr
727 cairo_arc (cr, x + w - r, y + h - r, r, 0 * degrees, 90 * degrees); //br
728 cairo_line_to (cr, x, y + h); // bl
729 cairo_line_to (cr, x, y); // tl
730 cairo_close_path (cr);
734 Gtkmm2ext::rounded_top_half_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
736 static const double degrees = M_PI / 180.0;
738 cairo_new_sub_path (cr);
739 cairo_move_to (cr, x+w, y+h);
740 cairo_line_to (cr, x, y+h);
741 cairo_arc (cr, x + r, y + r, r, 180 * degrees, 270 * degrees); //tl
742 cairo_arc (cr, x + w - r, y + r, r, -90 * degrees, 0 * degrees); //tr
743 cairo_close_path (cr);
747 Gtkmm2ext::rounded_bottom_half_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
749 static const double degrees = M_PI / 180.0;
751 cairo_new_sub_path (cr);
752 cairo_move_to (cr, x, y);
753 cairo_line_to (cr, x+w, y);
754 cairo_arc (cr, x + w - r, y + h - r, r, 0 * degrees, 90 * degrees); //br
755 cairo_arc (cr, x + r, y + h - r, r, 90 * degrees, 180 * degrees); //bl
756 cairo_close_path (cr);
761 Gtkmm2ext::rounded_top_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
763 static const double degrees = M_PI / 180.0;
765 cairo_new_sub_path (cr);
766 cairo_move_to (cr, x+w, y+h);
767 cairo_line_to (cr, x, y+h);
768 cairo_arc (cr, x + r, y + r, r, 180 * degrees, 270 * degrees); //tl
769 cairo_arc (cr, x + w - r, y + r, r, -90 * degrees, 0 * degrees); //tr
770 cairo_close_path (cr);
774 Gtkmm2ext::rounded_top_left_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
782 cairo_move_to (cr, x+r,y); // Move to A
783 cairo_line_to (cr, x+w,y); // Straight line to B
784 cairo_line_to (cr, x+w,y+h); // Move to E
785 cairo_line_to (cr, x,y+h); // Line to F
786 cairo_line_to (cr, x,y+r); // Line to H
787 cairo_curve_to (cr, x,y,x,y,x+r,y); // Curve to A
791 Gtkmm2ext::rounded_top_right_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
799 cairo_move_to (cr, x,y); // Move to A
800 cairo_line_to (cr, x+w-r,y); // Straight line to B
801 cairo_curve_to (cr, x+w,y,x+w,y,x+w,y+r); // Curve to C, Control points are both at Q
802 cairo_line_to (cr, x+w,y+h); // Move to E
803 cairo_line_to (cr, x,y+h); // Line to F
804 cairo_line_to (cr, x,y); // Line to A
808 Gtkmm2ext::add_reflection (cairo_t* cr, double w, double h)
810 cairo_pattern_t* convex_pattern = cairo_pattern_create_linear (0.0, 0, 0.3, h * 0.7);
811 cairo_pattern_add_color_stop_rgba (convex_pattern, 0.0, 1, 1, 1, 0.10);
812 cairo_pattern_add_color_stop_rgba (convex_pattern, 0.79, 1, 1, 1, 0.03);
813 cairo_pattern_add_color_stop_rgba (convex_pattern, 1.0, 1, 1, 1, 0.0);
814 cairo_set_source (cr, convex_pattern);
815 Gtkmm2ext::rounded_rectangle (cr, 2, 2, w - 4, h - 4, 4);
817 cairo_pattern_destroy(convex_pattern);
820 Glib::RefPtr<Gdk::Window>
821 Gtkmm2ext::window_to_draw_on (Gtk::Widget& w, Gtk::Widget** parent)
823 if (w.get_has_window()) {
824 return w.get_window();
827 (*parent) = w.get_parent();
830 if ((*parent)->get_has_window()) {
831 return (*parent)->get_window ();
833 (*parent) = (*parent)->get_parent ();
836 return Glib::RefPtr<Gdk::Window> ();
840 Gtkmm2ext::pixel_width (const string& str, const Pango::FontDescription& font)
842 Glib::RefPtr<Pango::Context> context = Glib::wrap (gdk_pango_context_get());
843 Glib::RefPtr<Pango::Layout> layout = Pango::Layout::create (context);
845 layout->set_font_description (font);
846 layout->set_text (str);
849 Gtkmm2ext::get_ink_pixel_size (layout, width, height);
852 // Pango returns incorrect text width on some OS X
853 // So we have to make a correction
854 // To determine the correct indent take the largest symbol for which the width is correct
855 // and make the calculation
857 // see also libs/canvas/text.cc
859 layout->set_text ("H");
860 layout->get_pixel_size (cor_width, height);
861 width += cor_width * 1.5;
868 Gtkmm2ext::pixel_size (const string& str, const Pango::FontDescription& font, int& width, int& height)
871 Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout ("");
873 layout->set_font_description (font);
874 layout->set_text (str);
876 Gtkmm2ext::get_ink_pixel_size (layout, width, height);
881 Gtkmm2ext::fit_to_pixels (const string& str, int pixel_width, Pango::FontDescription& font, int& actual_width, bool with_ellipses)
883 /* DECEMBER 2011: THIS PROTOTYPE OF fit_to_pixels() IS NOT USED
884 ANYWHERE AND HAS NOT BEEN TESTED.
887 Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout (str);
888 Glib::RefPtr<const Pango::LayoutLine> line;
890 layout->set_font_description (font);
891 layout->set_width (pixel_width * PANGO_SCALE);
894 layout->set_ellipsize (Pango::ELLIPSIZE_END);
896 layout->set_wrap (Pango::WRAP_CHAR);
899 line = layout->get_line (0);
901 /* XXX: might need special care to get the ellipsis character, not sure
904 string s = string (layout->get_text ().substr(line->get_start_index(), line->get_length()));
906 cerr << "fit to pixels of " << str << " returns " << s << endl;
912 /** Try to fit a string into a given horizontal space by ellipsizing it.
913 * @param cr Cairo context in which the text will be plotted.
915 * @param avail Available horizontal space.
916 * @return (Text, possibly ellipsized) and (horizontal size of text)
918 std::pair<std::string, double>
919 Gtkmm2ext::fit_to_pixels (cairo_t* cr, std::string name, double avail)
921 /* XXX hopefully there exists a more efficient way of doing this */
923 bool abbreviated = false;
927 cairo_text_extents_t ext;
928 cairo_text_extents (cr, name.c_str(), &ext);
930 if (ext.width < avail || name.length() <= 4) {
936 name = name.substr (0, name.length() - 4) + "...";
938 name = name.substr (0, name.length() - 3) + "...";
943 return std::make_pair (name, width);
947 Gtkmm2ext::left_aligned_label (string const & t)
949 Gtk::Label* l = new Gtk::Label (t);
950 l->set_alignment (0, 0.5);
955 Gtkmm2ext::right_aligned_label (string const & t)
957 Gtk::Label* l = new Gtk::Label (t);
958 l->set_alignment (1, 0.5);
963 make_null_tooltip (int, int, bool, const Glib::RefPtr<Gtk::Tooltip>& t)
965 t->set_tip_area (Gdk::Rectangle (0, 0, 0, 0));
969 /** Hackily arrange for the provided widget to have no tooltip,
970 * and also to stop any other widget from providing one while
971 * the mouse is over w.
974 Gtkmm2ext::set_no_tooltip_whatsoever (Gtk::Widget& w)
976 w.property_has_tooltip() = true;
977 w.signal_query_tooltip().connect (sigc::ptr_fun (make_null_tooltip));
981 Gtkmm2ext::enable_tooltips ()
983 gtk_rc_parse_string ("gtk-enable-tooltips = 1");
984 PersistentTooltip::set_tooltips_enabled (true);
988 Gtkmm2ext::disable_tooltips ()
990 gtk_rc_parse_string ("gtk-enable-tooltips = 0");
991 PersistentTooltip::set_tooltips_enabled (false);
995 Gtkmm2ext::event_inside_widget_window (Gtk::Widget& widget, GdkEvent* ev)
999 if (!gdk_event_get_root_coords (ev, &evx, &evy)) {
1005 gint width, height, depth;
1008 Glib::RefPtr<Gdk::Window> widget_window = widget.get_window();
1010 widget_window->get_geometry (x, y, width, height, depth);
1011 widget_window->get_root_origin (wx, wy);
1013 if ((evx >= wx && evx < wx + width) &&
1014 (evy >= wy && evy < wy + height)) {
1022 Gtkmm2ext::event_type_string (int event_type)
1024 switch (event_type) {
1033 case GDK_MOTION_NOTIFY:
1034 return "motion_notify";
1035 case GDK_BUTTON_PRESS:
1036 return "button_press";
1037 case GDK_2BUTTON_PRESS:
1038 return "2button_press";
1039 case GDK_3BUTTON_PRESS:
1040 return "3button_press";
1041 case GDK_BUTTON_RELEASE:
1042 return "button_release";
1045 case GDK_KEY_RELEASE:
1046 return "key_release";
1047 case GDK_ENTER_NOTIFY:
1048 return "enter_notify";
1049 case GDK_LEAVE_NOTIFY:
1050 return "leave_notify";
1051 case GDK_FOCUS_CHANGE:
1052 return "focus_change";
1059 case GDK_PROPERTY_NOTIFY:
1060 return "property_notify";
1061 case GDK_SELECTION_CLEAR:
1062 return "selection_clear";
1063 case GDK_SELECTION_REQUEST:
1064 return "selection_request";
1065 case GDK_SELECTION_NOTIFY:
1066 return "selection_notify";
1067 case GDK_PROXIMITY_IN:
1068 return "proximity_in";
1069 case GDK_PROXIMITY_OUT:
1070 return "proximity_out";
1071 case GDK_DRAG_ENTER:
1072 return "drag_enter";
1073 case GDK_DRAG_LEAVE:
1074 return "drag_leave";
1075 case GDK_DRAG_MOTION:
1076 return "drag_motion";
1077 case GDK_DRAG_STATUS:
1078 return "drag_status";
1079 case GDK_DROP_START:
1080 return "drop_start";
1081 case GDK_DROP_FINISHED:
1082 return "drop_finished";
1083 case GDK_CLIENT_EVENT:
1084 return "client_event";
1085 case GDK_VISIBILITY_NOTIFY:
1086 return "visibility_notify";
1091 case GDK_WINDOW_STATE:
1092 return "window_state";
1095 case GDK_OWNER_CHANGE:
1096 return "owner_change";
1097 case GDK_GRAB_BROKEN:
1098 return "grab_broken";
1107 Gtkmm2ext::markup_escape_text (std::string const& s)
1109 return Glib::Markup::escape_text (s);
1113 Gtkmm2ext::add_volume_shortcuts (Gtk::FileChooser& c)
1117 /* This is a first order approach, listing all mounted volumes (incl network).
1118 * One could use `diskutil` or `mount` to query local disks only, or
1119 * something even fancier if deemed appropriate.
1121 Glib::Dir dir("/Volumes");
1122 for (Glib::DirIterator di = dir.begin(); di != dir.end(); di++) {
1123 string fullpath = Glib::build_filename ("/Volumes", *di);
1124 if (!Glib::file_test (fullpath, Glib::FILE_TEST_IS_DIR)) continue;
1126 try { /* add_shortcut_folder throws an exception if the folder being added already has a shortcut */
1127 c.add_shortcut_folder (fullpath);
1129 catch (Glib::Error& e) {
1130 std::cerr << "add_shortcut_folder() threw Glib::Error: " << e.what() << std::endl;
1134 catch (Glib::FileError const& e) {
1135 std::cerr << "listing /Volumnes failed: " << e.what() << std::endl;
1141 Gtkmm2ext::paned_position_as_fraction (Gtk::Paned& paned, bool h)
1143 const guint pos = gtk_paned_get_position (const_cast<GtkPaned*>(static_cast<const Gtk::Paned*>(&paned)->gobj()));
1144 return (double) pos / (h ? paned.get_allocation().get_height() : paned.get_allocation().get_width());
1148 Gtkmm2ext::paned_set_position_as_fraction (Gtk::Paned& paned, float fraction, bool h)
1150 gint v = (h ? paned.get_allocation().get_height() : paned.get_allocation().get_width());
1156 paned.set_position ((guint) floor (fraction * v));
1160 Gtkmm2ext::show_gdk_event_state (int state)
1163 if (state & GDK_SHIFT_MASK) {
1166 if (state & GDK_LOCK_MASK) {
1169 if (state & GDK_CONTROL_MASK) {
1172 if (state & GDK_MOD1_MASK) {
1175 if (state & GDK_MOD2_MASK) {
1178 if (state & GDK_MOD3_MASK) {
1181 if (state & GDK_MOD4_MASK) {
1184 if (state & GDK_MOD5_MASK) {
1187 if (state & GDK_BUTTON1_MASK) {
1190 if (state & GDK_BUTTON2_MASK) {
1193 if (state & GDK_BUTTON3_MASK) {
1196 if (state & GDK_BUTTON4_MASK) {
1199 if (state & GDK_BUTTON5_MASK) {
1202 if (state & GDK_SUPER_MASK) {
1205 if (state & GDK_HYPER_MASK) {
1208 if (state & GDK_META_MASK) {
1211 if (state & GDK_RELEASE_MASK) {