1 /*
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>
9  *
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.
14  *
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.
19  *
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.
23  */
24 
25 #include <map>
26 #include <algorithm>
27 #include <iostream>
28 
29 #include <gtk/gtkpaned.h>
30 #include <gtk/gtk.h>
31 
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>
40 
41 #include "gtkmm2ext/utils.h"
42 #include "gtkmm2ext/persistent_tooltip.h"
43 
44 #include "pbd/i18n.h"
45 
46 using namespace std;
47 
48 void
init(const char * localedir)49 Gtkmm2ext::init (const char* localedir)
50 {
51 #if ENABLE_NLS
52 	(void) bindtextdomain(PACKAGE, localedir);
53 	(void) bind_textdomain_codeset (PACKAGE, "UTF-8");
54 #endif
55 }
56 
57 void
get_ink_pixel_size(Glib::RefPtr<Pango::Layout> layout,int & width,int & height)58 Gtkmm2ext::get_ink_pixel_size (Glib::RefPtr<Pango::Layout> layout,
59 			       int& width,
60 			       int& height)
61 {
62 	Pango::Rectangle ink_rect = layout->get_ink_extents ();
63 
64 	width = PANGO_PIXELS(ink_rect.get_width());
65 	height = PANGO_PIXELS(ink_rect.get_height());
66 }
67 
68 void
get_pixel_size(Glib::RefPtr<Pango::Layout> layout,int & width,int & height)69 Gtkmm2ext::get_pixel_size (Glib::RefPtr<Pango::Layout> layout,
70 			   int& width,
71 			   int& height)
72 {
73 	layout->get_pixel_size (width, height);
74 }
75 
76 void
set_size_request_to_display_given_text(Gtk::Widget & w,const gchar * text,gint hpadding,gint vpadding)77 Gtkmm2ext::set_size_request_to_display_given_text (Gtk::Widget &w, const gchar *text,
78 						   gint hpadding, gint vpadding)
79 {
80 	int width, height;
81 	w.ensure_style ();
82 
83 	get_pixel_size (w.create_pango_layout (text), width, height);
84 	w.set_size_request(width + hpadding, height + vpadding);
85 }
86 
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. */
89 void
set_size_request_to_display_given_text_width(Gtk::Widget & w,const gchar * htext,gint hpadding,gint vpadding)90 Gtkmm2ext::set_size_request_to_display_given_text_width (Gtk::Widget& w,
91                                                          const gchar* htext,
92                                                          gint         hpadding,
93                                                          gint         vpadding)
94 {
95 	static const gchar* vtext = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
96 
97 	w.ensure_style ();
98 
99 	int hwidth, hheight;
100 	get_pixel_size (w.create_pango_layout (htext), hwidth, hheight);
101 
102 	int vwidth, vheight;
103 	get_pixel_size (w.create_pango_layout (vtext), vwidth, vheight);
104 
105 	w.set_size_request(hwidth + hpadding, vheight + vpadding);
106 }
107 
108 void
set_height_request_to_display_any_text(Gtk::Widget & w,gint vpadding)109 Gtkmm2ext::set_height_request_to_display_any_text (Gtk::Widget& w, gint vpadding)
110 {
111 	static const gchar* vtext = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
112 
113 	w.ensure_style ();
114 
115 	int width, height;
116 	get_pixel_size (w.create_pango_layout (vtext), width, height);
117 
118 	w.set_size_request(-1, height + vpadding);
119 }
120 
121 void
set_size_request_to_display_given_text(Gtk::Widget & w,std::string const & text,gint hpadding,gint vpadding)122 Gtkmm2ext::set_size_request_to_display_given_text (Gtk::Widget &w, std::string const & text,
123 						   gint hpadding, gint vpadding)
124 {
125 	int width, height;
126 	w.ensure_style ();
127 
128 	get_pixel_size (w.create_pango_layout (text), width, height);
129 	w.set_size_request(width + hpadding, height + vpadding);
130 }
131 
132 void
set_size_request_to_display_given_text(Gtk::Widget & w,const std::vector<std::string> & strings,gint hpadding,gint 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)
136 {
137 	int width, height;
138 	int width_max = 0;
139 	int height_max = 0;
140 	w.ensure_style ();
141 	vector<string> copy;
142 	const vector<string>* to_use;
143 	vector<string>::const_iterator i;
144 
145 	for (i = strings.begin(); i != strings.end(); ++i) {
146 		if ((*i).find_first_of ("gy") != string::npos) {
147 			/* contains a descender */
148 			break;
149 		}
150 	}
151 
152 	if (i == strings.end()) {
153 		/* make a copy of the strings then add one that has a descender */
154 		copy = strings;
155 		copy.push_back ("g");
156 		to_use = &copy;
157 	} else {
158 		to_use = &strings;
159 	}
160 
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);
165 	}
166 
167 	w.set_size_request(width_max + hpadding, height_max + vpadding);
168 }
169 
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,
172  * like combo boxes.
173  */
174 void
set_size_request_to_display_given_text(Gtk::Widget & w,const std::vector<std::string> & strings,const std::string & hpadding,gint vpadding)175 Gtkmm2ext::set_size_request_to_display_given_text (Gtk::Widget&                    w,
176                                                    const std::vector<std::string>& strings,
177                                                    const std::string&              hpadding,
178                                                    gint                            vpadding)
179 {
180 	int width_max = 0;
181 	int height_max = 0;
182 	w.ensure_style ();
183 
184 	for (vector<string>::const_iterator i = strings.begin(); i != strings.end(); ++i) {
185 		int width, height;
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);
189 	}
190 
191 	int pad_width;
192 	int pad_height;
193 	get_pixel_size (w.create_pango_layout (hpadding), pad_width, pad_height);
194 
195 	w.set_size_request(width_max + pad_width, height_max + vpadding);
196 }
197 
198 static inline guint8
demultiply_alpha(guint8 src,guint8 alpha)199 demultiply_alpha (guint8 src,
200                   guint8 alpha)
201 {
202 	/* cairo pixel buffer data contains RGB values with the alpha
203 	 * values premultiplied.
204 	 *
205 	 * GdkPixbuf pixel buffer data contains RGB values without the
206 	 * alpha value applied.
207 	 *
208 	 * this removes the alpha component from the cairo version and
209 	 * returns the GdkPixbuf version.
210 	 */
211 	return alpha ? ((guint (src) << 8) - src) / alpha : 0;
212 }
213 
214 void
convert_bgra_to_rgba(guint8 const * src,guint8 * dst,int width,int height)215 Gtkmm2ext::convert_bgra_to_rgba (guint8 const* src,
216 				 guint8*       dst,
217 				 int           width,
218 				 int           height)
219 {
220 	guint8 const* src_pixel = src;
221 	guint8*       dst_pixel = dst;
222 
223 	/* cairo pixel data is endian-dependent ARGB with A in the most significant 8 bits,
224 	 * with premultipled alpha values (see preceding function)
225 	 *
226 	 * GdkPixbuf pixel data is non-endian-dependent RGBA with R in the lowest addressable
227 	 * 8 bits, and non-premultiplied alpha values.
228 	 *
229 	 * convert from the cairo values to the GdkPixbuf ones.
230 	 */
231 
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
236 				 0 1 2 3
237 				 Pixbuf [ R G B A ] is actually [ R G B A ] in memory DEST
238 				 */
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
246 
247 #elif G_BYTE_ORDER == G_BIG_ENDIAN
248 			/* Cairo [ B G R A ] is actually  [ A R G B ] in memory SOURCE
249 				 0 1 2 3
250 				 Pixbuf [ R G B A ] is actually [ R G B A ] in memory DEST
251 				 */
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
259 
260 #else
261 #error ardour does not currently support PDP-endianess
262 #endif
263 
264 			dst_pixel += 4;
265 			src_pixel += 4;
266 		}
267 	}
268 }
269 
270 Glib::RefPtr<Gdk::Pixbuf>
pixbuf_from_string(const string & name,const Pango::FontDescription & font,int clip_width,int clip_height,Gdk::Color fg)271 Gtkmm2ext::pixbuf_from_string(const string& name, const Pango::FontDescription& font, int clip_width, int clip_height, Gdk::Color fg)
272 {
273 	static Glib::RefPtr<Gdk::Pixbuf>* empty_pixbuf = 0;
274 
275 	if (name.empty()) {
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);
279 		}
280 		return *empty_pixbuf;
281 	}
282 
283 	if (clip_width <= 0 || clip_height <= 0) {
284 		/* negative values mean padding around natural size */
285 		int width, height;
286 		pixel_size (name, font, width, height);
287 		if (clip_width <= 0) {
288 			clip_width = width - clip_width;
289 		}
290 		if (clip_height <= 0) {
291 			clip_height = height - clip_height;
292 		}
293 	}
294 
295 	Glib::RefPtr<Gdk::Pixbuf> buf = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB, true, 8, clip_width, clip_height);
296 
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;
300 
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);
306 
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());
309 
310 	convert_bgra_to_rgba(cairo_image_surface_get_data (surface), buf->get_pixels(), clip_width, clip_height);
311 
312 	cairo_destroy(cr);
313 	cairo_surface_destroy(surface);
314 
315 	return buf;
316 }
317 
318 static void
_position_menu_anchored(int & x,int & y,bool & push_in,Gtk::Menu * const menu,Gtk::Widget * const anchor,const std::string & selected)319 _position_menu_anchored (int& x, int& y, bool& push_in,
320                                    Gtk::Menu* const menu,
321                                    Gtk::Widget* const anchor,
322                                    const std::string& selected)
323 {
324 	using namespace Gtk;
325 	using namespace Gtk::Menu_Helpers;
326 
327 	 /* TODO: lacks support for rotated dropdown buttons */
328 
329 	if (!anchor->has_screen () || !anchor->get_has_window ()) {
330 		return;
331 	}
332 
333 	Gdk::Rectangle monitor;
334 	{
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);
339 	}
340 
341 	const Requisition menu_req = menu->size_request();
342 	const Gdk::Rectangle allocation = anchor->get_allocation();
343 
344 	/* The x and y position are handled separately.
345 	 *
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...*/
359 
360 	anchor->get_window ()->get_origin (x, y);
361 
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 */
370 			x = monitor.get_x();
371 		} else {
372 			/* d) XXX align left or the menu might change monitors */
373 			x = monitor.get_x();
374 		}
375 	} else { /* LTR */
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;
384 		} else {
385 			/* d) align left */
386 			x = monitor.get_x();
387 		}
388 	}
389 
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) try aligning the selected menu item again, this time with scrollbars;
398 	 *  e) if there is no selected menu item, align the menu above the button or
399 	 *     below the button, depending on where there is more space.
400 	 * For the d) and e) cases, the menu contents will be aligned as told, but
401 	 * the menu itself will be bigger than that to accomodate the menu items
402 	 * that are scrolled out of view, thanks to |push_in = true|.
403 	 */
404 
405 	const MenuList& items = menu->items ();
406 	int offset = 0;
407 
408 	MenuList::const_iterator i = items.begin();
409 	for ( ; i != items.end(); ++i) {
410 		const Label* label_widget = dynamic_cast<const Label*>(i->get_child());
411 		if (label_widget && selected == ((std::string) label_widget->get_label())) {
412 			offset += (i->size_request().height - allocation.get_height()) / 2;
413 			menu->select_item(*i);
414 			break;
415 		}
416 		offset += i->size_request().height;
417 	}
418 	if (i != items.end() &&
419 	    y - offset >= monitor.get_y() &&
420 	    y - offset + menu_req.height <= monitor.get_y() + monitor.get_height()) {
421 		y -= offset; /* a) */
422 	} else if (y + allocation.get_height() + menu_req.height <= monitor.get_y() + monitor.get_height()) {
423 		y += allocation.get_height(); /* b) */
424 	} else if ((y - menu_req.height) >= monitor.get_y()) {
425 		y -= menu_req.height; /* c) */
426 	} else if (i != items.end()) {
427 		y -= offset; /* d) */
428 		menu->gobj()->upper_arrow_visible = 1; /* work around a gtk bug for the first show */
429 	} else if (monitor.get_height() - allocation.get_height() >= 2*(y - monitor.get_y())) {
430 		y += allocation.get_height(); /* e), more space below */
431 		menu->gobj()->upper_arrow_visible = 1; /* work around a gtk bug for the first show */
432 	} else {
433 		y -= menu_req.height; /* e), more space above */
434 		menu->gobj()->upper_arrow_visible = 1; /* work around a gtk bug for the first show */
435 	}
436 
437 	/* Workaround a bug in GTK where they don't tweak the scroll offset by the arrow height
438 	 * if the scroll offset is negative. See the condition at:
439 	 * https://gitlab.gnome.org/GNOME/gtk/blob/2.24.32/gtk/gtkmenu.c#L4395
440 	 * and the computation of scroll_offset at:
441 	 * https://gitlab.gnome.org/GNOME/gtk/blob/2.24.32/gtk/gtkmenu.c#L4360
442 	 * */
443 	int arrow_height;
444 	GtkArrowPlacement arrow_placement;
445 	gtk_widget_style_get (GTK_WIDGET (menu->gobj()),
446 	        "scroll-arrow-vlength", &arrow_height,
447 	        "arrow_placement", &arrow_placement,
448 	        NULL);
449 	int scroll_tweak = menu_req.height - monitor.get_height();
450 	int scroll_offset = scroll_tweak + monitor.get_y() + monitor.get_height() - y - menu_req.height;
451 	if (arrow_placement != GTK_ARROWS_END && scroll_tweak > 0 && scroll_offset < 0) {
452 		y -= arrow_height;
453 	}
454 
455 	push_in = true;
456 }
457 
458 void
anchored_menu_popup(Gtk::Menu * const menu,Gtk::Widget * const anchor,const std::string & selected,guint button,guint32 time)459 Gtkmm2ext::anchored_menu_popup (Gtk::Menu* const menu,
460                                 Gtk::Widget* const anchor,
461                                 const std::string& selected,
462                                 guint button, guint32 time)
463 {
464 	menu->popup(
465 		sigc::bind (sigc::ptr_fun(&_position_menu_anchored),
466 		            menu, anchor, selected),
467 		button,
468 		time);
469 }
470 
471 void
set_popdown_strings(Gtk::ComboBoxText & cr,const vector<string> & strings)472 Gtkmm2ext::set_popdown_strings (Gtk::ComboBoxText& cr, const vector<string>& strings)
473 {
474 	vector<string>::const_iterator i;
475 
476 	cr.clear ();
477 
478 	for (i = strings.begin(); i != strings.end(); ++i) {
479 		cr.append_text (*i);
480 	}
481 }
482 
483 void
get_popdown_strings(Gtk::ComboBoxText & cr,std::vector<std::string> & strings)484 Gtkmm2ext::get_popdown_strings (Gtk::ComboBoxText& cr, std::vector<std::string>& strings)
485 {
486 	strings.clear ();
487 	Glib::RefPtr<const Gtk::TreeModel> m = cr.get_model();
488 	if (!m) {
489 		return;
490 	}
491 	for(Gtk::TreeModel::iterator i = m->children().begin(); i != m->children().end(); ++i) {
492 		Glib::ustring txt;
493 		(*i)->get_value(0, txt);
494 		strings.push_back (txt);
495 	}
496 }
497 
498 size_t
get_popdown_string_count(Gtk::ComboBoxText & cr)499 Gtkmm2ext::get_popdown_string_count (Gtk::ComboBoxText& cr)
500 {
501 	Glib::RefPtr<const Gtk::TreeModel> m = cr.get_model();
502 	if (!m) {
503 		return 0;
504 	}
505 	return m->children().size();
506 }
507 
508 bool
contains_value(Gtk::ComboBoxText & cr,const std::string text)509 Gtkmm2ext::contains_value (Gtk::ComboBoxText& cr, const std::string text)
510 {
511 	std::vector<std::string> s;
512 	get_popdown_strings (cr, s);
513 	return (std::find (s.begin(), s.end(), text) != s.end());
514 }
515 
516 bool
set_active_text_if_present(Gtk::ComboBoxText & cr,const std::string text)517 Gtkmm2ext::set_active_text_if_present (Gtk::ComboBoxText& cr, const std::string text)
518 {
519 	if (contains_value(cr, text)) {
520 		cr.set_active_text (text);
521 		return true;
522 	}
523 	return false;
524 }
525 
526 GdkWindow*
get_paned_handle(Gtk::Paned & paned)527 Gtkmm2ext::get_paned_handle (Gtk::Paned& paned)
528 {
529 	return GTK_PANED(paned.gobj())->handle;
530 }
531 
532 void
set_decoration(Gtk::Window * win,Gdk::WMDecoration decor)533 Gtkmm2ext::set_decoration (Gtk::Window* win, Gdk::WMDecoration decor)
534 {
535 	win->get_window()->set_decorations (decor);
536 }
537 
set_treeview_header_as_default_label(Gtk::TreeViewColumn * c)538 void Gtkmm2ext::set_treeview_header_as_default_label(Gtk::TreeViewColumn* c)
539 {
540 	gtk_tree_view_column_set_widget( c->gobj(), GTK_WIDGET(0) );
541 }
542 
543 void
detach_menu(Gtk::Menu & menu)544 Gtkmm2ext::detach_menu (Gtk::Menu& menu)
545 {
546 	/* its possible for a Gtk::Menu to have no gobj() because it has
547 	   not yet been instantiated. Catch this and provide a safe
548 	   detach method.
549 	*/
550 	if (menu.gobj()) {
551 		if (menu.get_attach_widget()) {
552 			menu.detach ();
553 		}
554 	}
555 }
556 
557 bool
possibly_translate_keyval_to_make_legal_accelerator(uint32_t & keyval)558 Gtkmm2ext::possibly_translate_keyval_to_make_legal_accelerator (uint32_t& keyval)
559 {
560 	int fakekey = GDK_VoidSymbol;
561 
562 	switch (keyval) {
563 	case GDK_Tab:
564 	case GDK_ISO_Left_Tab:
565 		fakekey = GDK_nabla;
566 		break;
567 
568 	case GDK_Up:
569 		fakekey = GDK_uparrow;
570 		break;
571 
572 	case GDK_Down:
573 		fakekey = GDK_downarrow;
574 		break;
575 
576 	case GDK_Right:
577 		fakekey = GDK_rightarrow;
578 		break;
579 
580 	case GDK_Left:
581 		fakekey = GDK_leftarrow;
582 		break;
583 
584 	case GDK_Return:
585 		fakekey = GDK_3270_Enter;
586 		break;
587 
588 	case GDK_KP_Enter:
589 		fakekey = GDK_F35;
590 		break;
591 
592 	default:
593 		break;
594 	}
595 
596 	if (fakekey != GDK_VoidSymbol) {
597 		keyval = fakekey;
598 		return true;
599 	}
600 
601 	return false;
602 }
603 
604 uint32_t
possibly_translate_legal_accelerator_to_real_key(uint32_t keyval)605 Gtkmm2ext::possibly_translate_legal_accelerator_to_real_key (uint32_t keyval)
606 {
607 	switch (keyval) {
608 	case GDK_nabla:
609 		return GDK_Tab;
610 		break;
611 
612 	case GDK_uparrow:
613 		return GDK_Up;
614 		break;
615 
616 	case GDK_downarrow:
617 		return GDK_Down;
618 		break;
619 
620 	case GDK_rightarrow:
621 		return GDK_Right;
622 		break;
623 
624 	case GDK_leftarrow:
625 		return GDK_Left;
626 		break;
627 
628 	case GDK_3270_Enter:
629 		return GDK_Return;
630 
631 	case GDK_F35:
632 		return GDK_KP_Enter;
633 		break;
634 	}
635 
636 	return keyval;
637 }
638 
639 int
physical_screen_height(Glib::RefPtr<Gdk::Window> win)640 Gtkmm2ext::physical_screen_height (Glib::RefPtr<Gdk::Window> win)
641 {
642 	GdkScreen* scr = gdk_screen_get_default();
643 
644 	if (win) {
645 		GdkRectangle r;
646 		gint monitor = gdk_screen_get_monitor_at_window (scr, win->gobj());
647 		gdk_screen_get_monitor_geometry (scr, monitor, &r);
648 		return r.height;
649 	} else {
650 		return gdk_screen_get_height (scr);
651 	}
652 }
653 
654 int
physical_screen_width(Glib::RefPtr<Gdk::Window> win)655 Gtkmm2ext::physical_screen_width (Glib::RefPtr<Gdk::Window> win)
656 {
657 	GdkScreen* scr = gdk_screen_get_default();
658 
659 	if (win) {
660 		GdkRectangle r;
661 		gint monitor = gdk_screen_get_monitor_at_window (scr, win->gobj());
662 		gdk_screen_get_monitor_geometry (scr, monitor, &r);
663 		return r.width;
664 	} else {
665 		return gdk_screen_get_width (scr);
666 	}
667 }
668 
669 void
container_clear(Gtk::Container & c)670 Gtkmm2ext::container_clear (Gtk::Container& c)
671 {
672 	list<Gtk::Widget*> children = c.get_children();
673 	for (list<Gtk::Widget*>::iterator child = children.begin(); child != children.end(); ++child) {
674 		(*child)->hide ();
675 		c.remove (**child);
676 	}
677 }
678 
679 void
rounded_rectangle(Cairo::RefPtr<Cairo::Context> context,double x,double y,double w,double h,double r)680 Gtkmm2ext::rounded_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
681 {
682 	rounded_rectangle (context->cobj(), x, y, w, h, r);
683 }
684 void
rounded_top_rectangle(Cairo::RefPtr<Cairo::Context> context,double x,double y,double w,double h,double r)685 Gtkmm2ext::rounded_top_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
686 {
687 	rounded_top_rectangle (context->cobj(), x, y, w, h, r);
688 }
689 void
rounded_top_left_rectangle(Cairo::RefPtr<Cairo::Context> context,double x,double y,double w,double h,double r)690 Gtkmm2ext::rounded_top_left_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
691 {
692 	rounded_top_left_rectangle (context->cobj(), x, y, w, h, r);
693 }
694 void
rounded_top_right_rectangle(Cairo::RefPtr<Cairo::Context> context,double x,double y,double w,double h,double r)695 Gtkmm2ext::rounded_top_right_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
696 {
697 	rounded_top_right_rectangle (context->cobj(), x, y, w, h, r);
698 }
699 void
rounded_top_half_rectangle(Cairo::RefPtr<Cairo::Context> context,double x,double y,double w,double h,double r)700 Gtkmm2ext::rounded_top_half_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
701 {
702 	rounded_top_half_rectangle (context->cobj(), x, y, w, h, r);
703 }
704 void
rounded_bottom_half_rectangle(Cairo::RefPtr<Cairo::Context> context,double x,double y,double w,double h,double r)705 Gtkmm2ext::rounded_bottom_half_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
706 {
707 	rounded_bottom_half_rectangle (context->cobj(), x, y, w, h, r);
708 }
709 
710 void
rounded_left_half_rectangle(Cairo::RefPtr<Cairo::Context> context,double x,double y,double w,double h,double r)711 Gtkmm2ext::rounded_left_half_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
712 {
713 	rounded_left_half_rectangle (context->cobj(), x, y, w, h, r);
714 }
715 
716 void
rounded_right_half_rectangle(Cairo::RefPtr<Cairo::Context> context,double x,double y,double w,double h,double r)717 Gtkmm2ext::rounded_right_half_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
718 {
719 	rounded_right_half_rectangle (context->cobj(), x, y, w, h, r);
720 }
721 
722 void
rounded_rectangle(cairo_t * cr,double x,double y,double w,double h,double r)723 Gtkmm2ext::rounded_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
724 {
725 	static const double degrees = M_PI / 180.0;
726 	if (r < 1) {
727 		cairo_rectangle (cr, x, y, w, h);
728 		return;
729 	}
730 
731 	cairo_new_sub_path (cr);
732 	cairo_arc (cr, x + w - r, y + r, r, -90 * degrees, 0 * degrees);  //tr
733 	cairo_arc (cr, x + w - r, y + h - r, r, 0 * degrees, 90 * degrees);  //br
734 	cairo_arc (cr, x + r, y + h - r, r, 90 * degrees, 180 * degrees);  //bl
735 	cairo_arc (cr, x + r, y + r, r, 180 * degrees, 270 * degrees);  //tl
736 	cairo_close_path (cr);
737 }
738 
739 void
rounded_left_half_rectangle(cairo_t * cr,double x,double y,double w,double h,double r)740 Gtkmm2ext::rounded_left_half_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
741 {
742 	static const double degrees = M_PI / 180.0;
743 
744 	cairo_new_sub_path (cr);
745 	cairo_line_to (cr, x+w, y); // tr
746 	cairo_line_to (cr, x+w, y + h); // br
747 	cairo_arc (cr, x + r, y + h - r, r, 90 * degrees, 180 * degrees);  //bl
748 	cairo_arc (cr, x + r, y + r, r, 180 * degrees, 270 * degrees);  //tl
749 	cairo_close_path (cr);
750 }
751 
752 void
rounded_right_half_rectangle(cairo_t * cr,double x,double y,double w,double h,double r)753 Gtkmm2ext::rounded_right_half_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
754 {
755 	static const double degrees = M_PI / 180.0;
756 
757 	cairo_new_sub_path (cr);
758 	cairo_arc (cr, x + w - r, y + r, r, -90 * degrees, 0 * degrees);  //tr
759 	cairo_arc (cr, x + w - r, y + h - r, r, 0 * degrees, 90 * degrees);  //br
760 	cairo_line_to (cr, x, y + h); // bl
761 	cairo_line_to (cr, x, y); // tl
762 	cairo_close_path (cr);
763 }
764 
765 void
rounded_top_half_rectangle(cairo_t * cr,double x,double y,double w,double h,double r)766 Gtkmm2ext::rounded_top_half_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
767 {
768 	static const double degrees = M_PI / 180.0;
769 
770 	cairo_new_sub_path (cr);
771 	cairo_move_to (cr, x+w, y+h);
772 	cairo_line_to (cr, x, y+h);
773 	cairo_arc (cr, x + r, y + r, r, 180 * degrees, 270 * degrees);  //tl
774 	cairo_arc (cr, x + w - r, y + r, r, -90 * degrees, 0 * degrees);  //tr
775 	cairo_close_path (cr);
776 }
777 
778 void
rounded_bottom_half_rectangle(cairo_t * cr,double x,double y,double w,double h,double r)779 Gtkmm2ext::rounded_bottom_half_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
780 {
781 	static const double degrees = M_PI / 180.0;
782 
783 	cairo_new_sub_path (cr);
784 	cairo_move_to (cr, x, y);
785 	cairo_line_to (cr, x+w, y);
786 	cairo_arc (cr, x + w - r, y + h - r, r, 0 * degrees, 90 * degrees);  //br
787 	cairo_arc (cr, x + r, y + h - r, r, 90 * degrees, 180 * degrees);  //bl
788 	cairo_close_path (cr);
789 }
790 
791 
792 void
rounded_top_rectangle(cairo_t * cr,double x,double y,double w,double h,double r)793 Gtkmm2ext::rounded_top_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
794 {
795 	static const double degrees = M_PI / 180.0;
796 
797 	cairo_new_sub_path (cr);
798 	cairo_move_to (cr, x+w, y+h);
799 	cairo_line_to (cr, x, y+h);
800 	cairo_arc (cr, x + r, y + r, r, 180 * degrees, 270 * degrees);  //tl
801 	cairo_arc (cr, x + w - r, y + r, r, -90 * degrees, 0 * degrees);  //tr
802 	cairo_close_path (cr);
803 }
804 
805 void
rounded_top_left_rectangle(cairo_t * cr,double x,double y,double w,double h,double r)806 Gtkmm2ext::rounded_top_left_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
807 {
808 /*    A****B
809       H    *
810       *    *
811       *    *
812       F****E
813 */
814 	cairo_move_to (cr, x+r,y); // Move to A
815 	cairo_line_to (cr, x+w,y); // Straight line to B
816 	cairo_line_to (cr, x+w,y+h); // Move to E
817 	cairo_line_to (cr, x,y+h); // Line to F
818 	cairo_line_to (cr, x,y+r); // Line to H
819 	cairo_curve_to (cr, x,y,x,y,x+r,y); // Curve to A
820 }
821 
822 void
rounded_top_right_rectangle(cairo_t * cr,double x,double y,double w,double h,double r)823 Gtkmm2ext::rounded_top_right_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
824 {
825 /*    A****BQ
826       *    C
827       *    *
828       *    *
829       F****E
830 */
831 	cairo_move_to (cr, x,y); // Move to A
832 	cairo_line_to (cr, x+w-r,y); // Straight line to B
833 	cairo_curve_to (cr, x+w,y,x+w,y,x+w,y+r); // Curve to C, Control points are both at Q
834 	cairo_line_to (cr, x+w,y+h); // Move to E
835 	cairo_line_to (cr, x,y+h); // Line to F
836 	cairo_line_to (cr, x,y); // Line to A
837 }
838 
839 void
add_reflection(cairo_t * cr,double w,double h)840 Gtkmm2ext::add_reflection (cairo_t* cr, double w, double h)
841 {
842 	cairo_pattern_t* convex_pattern = cairo_pattern_create_linear (0.0, 0, 0.3, h * 0.7);
843 	cairo_pattern_add_color_stop_rgba (convex_pattern, 0.0,  1, 1, 1, 0.10);
844 	cairo_pattern_add_color_stop_rgba (convex_pattern, 0.79, 1, 1, 1, 0.03);
845 	cairo_pattern_add_color_stop_rgba (convex_pattern, 1.0,  1, 1, 1, 0.0);
846 	cairo_set_source (cr, convex_pattern);
847 	Gtkmm2ext::rounded_rectangle (cr, 2, 2, w - 4, h - 4, 4);
848 	cairo_fill (cr);
849 	cairo_pattern_destroy(convex_pattern);
850 }
851 
852 Glib::RefPtr<Gdk::Window>
window_to_draw_on(Gtk::Widget & w,Gtk::Widget ** parent)853 Gtkmm2ext::window_to_draw_on (Gtk::Widget& w, Gtk::Widget** parent)
854 {
855 	if (w.get_has_window()) {
856 		return w.get_window();
857 	}
858 
859 	(*parent) = w.get_parent();
860 
861 	while (*parent) {
862 		if ((*parent)->get_has_window()) {
863 			return (*parent)->get_window ();
864 		}
865 		(*parent) = (*parent)->get_parent ();
866 	}
867 
868 	return Glib::RefPtr<Gdk::Window> ();
869 }
870 
871 int
pixel_width(const string & str,const Pango::FontDescription & font)872 Gtkmm2ext::pixel_width (const string& str, const Pango::FontDescription& font)
873 {
874 	Glib::RefPtr<Pango::Context> context = Glib::wrap (gdk_pango_context_get());
875 	Glib::RefPtr<Pango::Layout> layout = Pango::Layout::create (context);
876 
877 	layout->set_font_description (font);
878 	layout->set_text (str);
879 
880 	int width, height;
881 	Gtkmm2ext::get_ink_pixel_size (layout, width, height);
882 
883 #ifdef __APPLE__
884 	// Pango returns incorrect text width on some OS X
885 	// So we have to make a correction
886 	// To determine the correct indent take the largest symbol for which the width is correct
887 	// and make the calculation
888 	//
889 	// see also libs/canvas/text.cc
890 	int cor_width;
891 	layout->set_text ("H");
892 	layout->get_pixel_size (cor_width, height);
893 	width += cor_width * 1.5;
894 #endif
895 
896 	return width;
897 }
898 
899 void
pixel_size(const string & str,const Pango::FontDescription & font,int & width,int & height)900 Gtkmm2ext::pixel_size (const string& str, const Pango::FontDescription& font, int& width, int& height)
901 {
902 	Gtk::Label foo;
903 	Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout ("");
904 
905 	layout->set_font_description (font);
906 	layout->set_text (str);
907 
908 	Gtkmm2ext::get_ink_pixel_size (layout, width, height);
909 }
910 
911 #if 0
912 string
913 Gtkmm2ext::fit_to_pixels (const string& str, int pixel_width, Pango::FontDescription& font, int& actual_width, bool with_ellipses)
914 {
915 	/* DECEMBER 2011: THIS PROTOTYPE OF fit_to_pixels() IS NOT USED
916 	   ANYWHERE AND HAS NOT BEEN TESTED.
917 	*/
918 	Gtk::Label foo;
919 	Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout (str);
920 	Glib::RefPtr<const Pango::LayoutLine> line;
921 
922 	layout->set_font_description (font);
923 	layout->set_width (pixel_width * PANGO_SCALE);
924 
925 	if (with_ellipses) {
926 		layout->set_ellipsize (Pango::ELLIPSIZE_END);
927 	} else {
928 		layout->set_wrap (Pango::WRAP_CHAR);
929 	}
930 
931 	line = layout->get_line (0);
932 
933 	/* XXX: might need special care to get the ellipsis character, not sure
934 	 * how that works
935 	 */
936 	string s = string (layout->get_text ().substr(line->get_start_index(), line->get_length()));
937 
938 	cerr << "fit to pixels of " << str << " returns " << s << endl;
939 
940 	return s;
941 }
942 #endif
943 
944 /** Try to fit a string into a given horizontal space by ellipsizing it.
945  *  @param cr Cairo context in which the text will be plotted.
946  *  @param name Text.
947  *  @param avail Available horizontal space.
948  *  @return (Text, possibly ellipsized) and (horizontal size of text)
949  */
950 std::pair<std::string, double>
fit_to_pixels(cairo_t * cr,std::string name,double avail)951 Gtkmm2ext::fit_to_pixels (cairo_t* cr, std::string name, double avail)
952 {
953 	/* XXX hopefully there exists a more efficient way of doing this */
954 
955 	bool abbreviated = false;
956 	uint32_t width = 0;
957 
958 	while (1) {
959 		cairo_text_extents_t ext;
960 		cairo_text_extents (cr, name.c_str(), &ext);
961 
962 		if (ext.width < avail || name.length() <= 4) {
963 			width = ext.width;
964 			break;
965 		}
966 
967 		if (abbreviated) {
968 			name = name.substr (0, name.length() - 4) + "...";
969 		} else {
970 			name = name.substr (0, name.length() - 3) + "...";
971 			abbreviated = true;
972 		}
973 	}
974 
975 	return std::make_pair (name, width);
976 }
977 
978 Gtk::Label *
left_aligned_label(string const & t)979 Gtkmm2ext::left_aligned_label (string const & t)
980 {
981 	Gtk::Label* l = new Gtk::Label (t);
982 	l->set_alignment (0, 0.5);
983 	return l;
984 }
985 
986 Gtk::Label *
right_aligned_label(string const & t)987 Gtkmm2ext::right_aligned_label (string const & t)
988 {
989 	Gtk::Label* l = new Gtk::Label (t);
990 	l->set_alignment (1, 0.5);
991 	return l;
992 }
993 
994 static bool
make_null_tooltip(int,int,bool,const Glib::RefPtr<Gtk::Tooltip> & t)995 make_null_tooltip (int, int, bool, const Glib::RefPtr<Gtk::Tooltip>& t)
996 {
997 	t->set_tip_area (Gdk::Rectangle (0, 0, 0, 0));
998 	return true;
999 }
1000 
1001 /** Hackily arrange for the provided widget to have no tooltip,
1002  *  and also to stop any other widget from providing one while
1003  * the mouse is over w.
1004  */
1005 void
set_no_tooltip_whatsoever(Gtk::Widget & w)1006 Gtkmm2ext::set_no_tooltip_whatsoever (Gtk::Widget& w)
1007 {
1008 	w.property_has_tooltip() = true;
1009 	w.signal_query_tooltip().connect (sigc::ptr_fun (make_null_tooltip));
1010 }
1011 
1012 void
enable_tooltips()1013 Gtkmm2ext::enable_tooltips ()
1014 {
1015 	gtk_rc_parse_string ("gtk-enable-tooltips = 1");
1016 	PersistentTooltip::set_tooltips_enabled (true);
1017 }
1018 
1019 void
disable_tooltips()1020 Gtkmm2ext::disable_tooltips ()
1021 {
1022 	gtk_rc_parse_string ("gtk-enable-tooltips = 0");
1023 	PersistentTooltip::set_tooltips_enabled (false);
1024 }
1025 
1026 bool
event_inside_widget_window(Gtk::Widget & widget,GdkEvent * ev)1027 Gtkmm2ext::event_inside_widget_window (Gtk::Widget& widget, GdkEvent* ev)
1028 {
1029 	gdouble evx, evy;
1030 
1031 	if (!gdk_event_get_root_coords (ev, &evx, &evy)) {
1032 		return false;
1033 	}
1034 
1035 	gint x, y;
1036 	gint wx, wy;
1037 	gint width, height, depth;
1038 
1039 	Glib::RefPtr<Gdk::Window> widget_window = widget.get_window();
1040 
1041 	widget_window->get_geometry (x, y, width, height, depth);
1042 	widget_window->get_origin (wx, wy);
1043 
1044 	if ((evx >= wx && evx < wx + width) &&
1045 			(evy >= wy && evy < wy + height)) {
1046 		return true;
1047 	}
1048 
1049 	return false;
1050 }
1051 
1052 const char*
event_type_string(int event_type)1053 Gtkmm2ext::event_type_string (int event_type)
1054 {
1055 	switch (event_type) {
1056 	case GDK_NOTHING:
1057 		return "nothing";
1058 	case GDK_DELETE:
1059 		return "delete";
1060 	case GDK_DESTROY:
1061 		return "destroy";
1062 	case GDK_EXPOSE:
1063 		return "expose";
1064 	case GDK_MOTION_NOTIFY:
1065 		return "motion_notify";
1066 	case GDK_BUTTON_PRESS:
1067 		return "button_press";
1068 	case GDK_2BUTTON_PRESS:
1069 		return "2button_press";
1070 	case GDK_3BUTTON_PRESS:
1071 		return "3button_press";
1072 	case GDK_BUTTON_RELEASE:
1073 		return "button_release";
1074 	case GDK_KEY_PRESS:
1075 		return "key_press";
1076 	case GDK_KEY_RELEASE:
1077 		return "key_release";
1078 	case GDK_ENTER_NOTIFY:
1079 		return "enter_notify";
1080 	case GDK_LEAVE_NOTIFY:
1081 		return "leave_notify";
1082 	case GDK_FOCUS_CHANGE:
1083 		return "focus_change";
1084 	case GDK_CONFIGURE:
1085 		return "configure";
1086 	case GDK_MAP:
1087 		return "map";
1088 	case GDK_UNMAP:
1089 		return "unmap";
1090 	case GDK_PROPERTY_NOTIFY:
1091 		return "property_notify";
1092 	case GDK_SELECTION_CLEAR:
1093 		return "selection_clear";
1094 	case GDK_SELECTION_REQUEST:
1095 		return "selection_request";
1096 	case GDK_SELECTION_NOTIFY:
1097 		return "selection_notify";
1098 	case GDK_PROXIMITY_IN:
1099 		return "proximity_in";
1100 	case GDK_PROXIMITY_OUT:
1101 		return "proximity_out";
1102 	case GDK_DRAG_ENTER:
1103 		return "drag_enter";
1104 	case GDK_DRAG_LEAVE:
1105 		return "drag_leave";
1106 	case GDK_DRAG_MOTION:
1107 		return "drag_motion";
1108 	case GDK_DRAG_STATUS:
1109 		return "drag_status";
1110 	case GDK_DROP_START:
1111 		return "drop_start";
1112 	case GDK_DROP_FINISHED:
1113 		return "drop_finished";
1114 	case GDK_CLIENT_EVENT:
1115 		return "client_event";
1116 	case GDK_VISIBILITY_NOTIFY:
1117 		return "visibility_notify";
1118 	case GDK_NO_EXPOSE:
1119 		return "no_expose";
1120 	case GDK_SCROLL:
1121 		return "scroll";
1122 	case GDK_WINDOW_STATE:
1123 		return "window_state";
1124 	case GDK_SETTING:
1125 		return "setting";
1126 	case GDK_OWNER_CHANGE:
1127 		return "owner_change";
1128 	case GDK_GRAB_BROKEN:
1129 		return "grab_broken";
1130 	case GDK_DAMAGE:
1131 		return "damage";
1132 	}
1133 
1134 	return "unknown";
1135 }
1136 
1137 std::string
markup_escape_text(std::string const & s)1138 Gtkmm2ext::markup_escape_text (std::string const& s)
1139 {
1140 	return Glib::Markup::escape_text (s);
1141 }
1142 
1143 void
add_volume_shortcuts(Gtk::FileChooser & c)1144 Gtkmm2ext::add_volume_shortcuts (Gtk::FileChooser& c)
1145 {
1146 #ifdef __APPLE__
1147 	try {
1148 		/* This is a first order approach, listing all mounted volumes (incl network).
1149 		 * One could use `diskutil` or `mount` to query local disks only, or
1150 		 * something even fancier if deemed appropriate.
1151 		 */
1152 		Glib::Dir dir("/Volumes");
1153 		for (Glib::DirIterator di = dir.begin(); di != dir.end(); di++) {
1154 			string fullpath = Glib::build_filename ("/Volumes", *di);
1155 			if (!Glib::file_test (fullpath, Glib::FILE_TEST_IS_DIR)) continue;
1156 
1157 			try { /* add_shortcut_folder throws an exception if the folder being added already has a shortcut */
1158 				c.add_shortcut_folder (fullpath);
1159 			}
1160 			catch (Glib::Error& e) {
1161 				std::cerr << "add_shortcut_folder() threw Glib::Error: " << e.what() << std::endl;
1162 			}
1163 		}
1164 	}
1165 	catch (Glib::FileError const& e) {
1166 		std::cerr << "listing /Volumnes failed: " << e.what() << std::endl;
1167 	}
1168 #endif
1169 }
1170 
1171 float
paned_position_as_fraction(Gtk::Paned & paned,bool h)1172 Gtkmm2ext::paned_position_as_fraction (Gtk::Paned& paned, bool h)
1173 {
1174 	const guint pos = gtk_paned_get_position (const_cast<GtkPaned*>(static_cast<const Gtk::Paned*>(&paned)->gobj()));
1175 	return (double) pos / (h ? paned.get_allocation().get_height() : paned.get_allocation().get_width());
1176 }
1177 
1178 void
paned_set_position_as_fraction(Gtk::Paned & paned,float fraction,bool h)1179 Gtkmm2ext::paned_set_position_as_fraction (Gtk::Paned& paned, float fraction, bool h)
1180 {
1181 	gint v = (h ? paned.get_allocation().get_height() : paned.get_allocation().get_width());
1182 
1183 	if (v < 1) {
1184 		return;
1185 	}
1186 
1187 	paned.set_position ((guint) floor (fraction * v));
1188 }
1189 
1190 string
show_gdk_event_state(int state)1191 Gtkmm2ext::show_gdk_event_state (int state)
1192 {
1193 	string s;
1194 	if (state & GDK_SHIFT_MASK) {
1195 		s += "+SHIFT";
1196 	}
1197 	if (state & GDK_LOCK_MASK) {
1198 		s += "+LOCK";
1199 	}
1200 	if (state & GDK_CONTROL_MASK) {
1201 		s += "+CONTROL";
1202 	}
1203 	if (state & GDK_MOD1_MASK) {
1204 		s += "+MOD1";
1205 	}
1206 	if (state & GDK_MOD2_MASK) {
1207 		s += "+MOD2";
1208 	}
1209 	if (state & GDK_MOD3_MASK) {
1210 		s += "+MOD3";
1211 	}
1212 	if (state & GDK_MOD4_MASK) {
1213 		s += "+MOD4";
1214 	}
1215 	if (state & GDK_MOD5_MASK) {
1216 		s += "+MOD5";
1217 	}
1218 	if (state & GDK_BUTTON1_MASK) {
1219 		s += "+BUTTON1";
1220 	}
1221 	if (state & GDK_BUTTON2_MASK) {
1222 		s += "+BUTTON2";
1223 	}
1224 	if (state & GDK_BUTTON3_MASK) {
1225 		s += "+BUTTON3";
1226 	}
1227 	if (state & GDK_BUTTON4_MASK) {
1228 		s += "+BUTTON4";
1229 	}
1230 	if (state & GDK_BUTTON5_MASK) {
1231 		s += "+BUTTON5";
1232 	}
1233 	if (state & GDK_SUPER_MASK) {
1234 		s += "+SUPER";
1235 	}
1236 	if (state & GDK_HYPER_MASK) {
1237 		s += "+HYPER";
1238 	}
1239 	if (state & GDK_META_MASK) {
1240 		s += "+META";
1241 	}
1242 	if (state & GDK_RELEASE_MASK) {
1243 		s += "+RELEASE";
1244 	}
1245 
1246 	return s;
1247 }
1248