Consistent use of abort() /* NOTREACHED */
[ardour.git] / libs / gtkmm2ext / utils.cc
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
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
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
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
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
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
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
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
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
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
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
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>
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 void
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)
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) 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 */
399
400         const MenuList& items = menu->items ();
401         int offset = 0;
402
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())) {
407                         break;
408                 }
409                 offset += i->size_request().height;
410         }
411         if (i != items.end() &&
412             y - offset >= monitor.get_y() &&
413             y - offset + menu_req.height <= monitor.get_y() + monitor.get_height()) {
414                 y -= offset;
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) */
419         } else {
420                 y = monitor.get_y() + max(0, monitor.get_height() - menu_req.height);
421         }
422
423         push_in = false;
424 }
425
426 void
427 Gtkmm2ext::anchored_menu_popup (Gtk::Menu* const menu,
428                                 Gtk::Widget* const anchor,
429                                 const std::string& selected,
430                                 guint button, guint32 time)
431 {
432         menu->popup(
433                 sigc::bind (sigc::ptr_fun(&_position_menu_anchored),
434                             menu, anchor, selected),
435                 button,
436                 time);
437 }
438
439 void
440 Gtkmm2ext::set_popdown_strings (Gtk::ComboBoxText& cr, const vector<string>& strings)
441 {
442         vector<string>::const_iterator i;
443
444         cr.clear ();
445
446         for (i = strings.begin(); i != strings.end(); ++i) {
447                 cr.append_text (*i);
448         }
449 }
450
451 void
452 Gtkmm2ext::get_popdown_strings (Gtk::ComboBoxText& cr, std::vector<std::string>& strings)
453 {
454         strings.clear ();
455         Glib::RefPtr<const Gtk::TreeModel> m = cr.get_model();
456         if (!m) {
457                 return;
458         }
459         for(Gtk::TreeModel::iterator i = m->children().begin(); i != m->children().end(); ++i) {
460                 Glib::ustring txt;
461                 (*i)->get_value(0, txt);
462                 strings.push_back (txt);
463         }
464 }
465
466 size_t
467 Gtkmm2ext::get_popdown_string_count (Gtk::ComboBoxText& cr)
468 {
469         Glib::RefPtr<const Gtk::TreeModel> m = cr.get_model();
470         if (!m) {
471                 return 0;
472         }
473         return m->children().size();
474 }
475
476 bool
477 Gtkmm2ext::contains_value (Gtk::ComboBoxText& cr, const std::string text)
478 {
479         std::vector<std::string> s;
480         get_popdown_strings (cr, s);
481         return (std::find (s.begin(), s.end(), text) != s.end());
482 }
483
484 bool
485 Gtkmm2ext::set_active_text_if_present (Gtk::ComboBoxText& cr, const std::string text)
486 {
487         if (contains_value(cr, text)) {
488                 cr.set_active_text (text);
489                 return true;
490         }
491         return false;
492 }
493
494 GdkWindow*
495 Gtkmm2ext::get_paned_handle (Gtk::Paned& paned)
496 {
497         return GTK_PANED(paned.gobj())->handle;
498 }
499
500 void
501 Gtkmm2ext::set_decoration (Gtk::Window* win, Gdk::WMDecoration decor)
502 {
503         win->get_window()->set_decorations (decor);
504 }
505
506 void Gtkmm2ext::set_treeview_header_as_default_label(Gtk::TreeViewColumn* c)
507 {
508         gtk_tree_view_column_set_widget( c->gobj(), GTK_WIDGET(0) );
509 }
510
511 void
512 Gtkmm2ext::detach_menu (Gtk::Menu& menu)
513 {
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
516            detach method.
517         */
518         if (menu.gobj()) {
519                 if (menu.get_attach_widget()) {
520                         menu.detach ();
521                 }
522         }
523 }
524
525 bool
526 Gtkmm2ext::possibly_translate_keyval_to_make_legal_accelerator (uint32_t& keyval)
527 {
528         int fakekey = GDK_VoidSymbol;
529
530         switch (keyval) {
531         case GDK_Tab:
532         case GDK_ISO_Left_Tab:
533                 fakekey = GDK_nabla;
534                 break;
535
536         case GDK_Up:
537                 fakekey = GDK_uparrow;
538                 break;
539
540         case GDK_Down:
541                 fakekey = GDK_downarrow;
542                 break;
543
544         case GDK_Right:
545                 fakekey = GDK_rightarrow;
546                 break;
547
548         case GDK_Left:
549                 fakekey = GDK_leftarrow;
550                 break;
551
552         case GDK_Return:
553                 fakekey = GDK_3270_Enter;
554                 break;
555
556         case GDK_KP_Enter:
557                 fakekey = GDK_F35;
558                 break;
559
560         default:
561                 break;
562         }
563
564         if (fakekey != GDK_VoidSymbol) {
565                 keyval = fakekey;
566                 return true;
567         }
568
569         return false;
570 }
571
572 uint32_t
573 Gtkmm2ext::possibly_translate_legal_accelerator_to_real_key (uint32_t keyval)
574 {
575         switch (keyval) {
576         case GDK_nabla:
577                 return GDK_Tab;
578                 break;
579
580         case GDK_uparrow:
581                 return GDK_Up;
582                 break;
583
584         case GDK_downarrow:
585                 return GDK_Down;
586                 break;
587
588         case GDK_rightarrow:
589                 return GDK_Right;
590                 break;
591
592         case GDK_leftarrow:
593                 return GDK_Left;
594                 break;
595
596         case GDK_3270_Enter:
597                 return GDK_Return;
598
599         case GDK_F35:
600                 return GDK_KP_Enter;
601                 break;
602         }
603
604         return keyval;
605 }
606
607 int
608 Gtkmm2ext::physical_screen_height (Glib::RefPtr<Gdk::Window> win)
609 {
610         GdkScreen* scr = gdk_screen_get_default();
611
612         if (win) {
613                 GdkRectangle r;
614                 gint monitor = gdk_screen_get_monitor_at_window (scr, win->gobj());
615                 gdk_screen_get_monitor_geometry (scr, monitor, &r);
616                 return r.height;
617         } else {
618                 return gdk_screen_get_height (scr);
619         }
620 }
621
622 int
623 Gtkmm2ext::physical_screen_width (Glib::RefPtr<Gdk::Window> win)
624 {
625         GdkScreen* scr = gdk_screen_get_default();
626
627         if (win) {
628                 GdkRectangle r;
629                 gint monitor = gdk_screen_get_monitor_at_window (scr, win->gobj());
630                 gdk_screen_get_monitor_geometry (scr, monitor, &r);
631                 return r.width;
632         } else {
633                 return gdk_screen_get_width (scr);
634         }
635 }
636
637 void
638 Gtkmm2ext::container_clear (Gtk::Container& c)
639 {
640         list<Gtk::Widget*> children = c.get_children();
641         for (list<Gtk::Widget*>::iterator child = children.begin(); child != children.end(); ++child) {
642                 (*child)->hide ();
643                 c.remove (**child);
644         }
645 }
646
647 void
648 Gtkmm2ext::rounded_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
649 {
650         rounded_rectangle (context->cobj(), x, y, w, h, r);
651 }
652 void
653 Gtkmm2ext::rounded_top_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
654 {
655         rounded_top_rectangle (context->cobj(), x, y, w, h, r);
656 }
657 void
658 Gtkmm2ext::rounded_top_left_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
659 {
660         rounded_top_left_rectangle (context->cobj(), x, y, w, h, r);
661 }
662 void
663 Gtkmm2ext::rounded_top_right_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
664 {
665         rounded_top_right_rectangle (context->cobj(), x, y, w, h, r);
666 }
667 void
668 Gtkmm2ext::rounded_top_half_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
669 {
670         rounded_top_half_rectangle (context->cobj(), x, y, w, h, r);
671 }
672 void
673 Gtkmm2ext::rounded_bottom_half_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
674 {
675         rounded_bottom_half_rectangle (context->cobj(), x, y, w, h, r);
676 }
677
678 void
679 Gtkmm2ext::rounded_left_half_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
680 {
681         rounded_left_half_rectangle (context->cobj(), x, y, w, h, r);
682 }
683
684 void
685 Gtkmm2ext::rounded_right_half_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
686 {
687         rounded_right_half_rectangle (context->cobj(), x, y, w, h, r);
688 }
689
690 void
691 Gtkmm2ext::rounded_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
692 {
693         static const double degrees = M_PI / 180.0;
694         if (r < 1) {
695                 cairo_rectangle (cr, x, y, w, h);
696                 return;
697         }
698
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);
705 }
706
707 void
708 Gtkmm2ext::rounded_left_half_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
709 {
710         static const double degrees = M_PI / 180.0;
711
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);
718 }
719
720 void
721 Gtkmm2ext::rounded_right_half_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
722 {
723         static const double degrees = M_PI / 180.0;
724
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);
731 }
732
733 void
734 Gtkmm2ext::rounded_top_half_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
735 {
736         static const double degrees = M_PI / 180.0;
737
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);
744 }
745
746 void
747 Gtkmm2ext::rounded_bottom_half_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
748 {
749         static const double degrees = M_PI / 180.0;
750
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);
757 }
758
759
760 void
761 Gtkmm2ext::rounded_top_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
762 {
763         static const double degrees = M_PI / 180.0;
764
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);
771 }
772
773 void
774 Gtkmm2ext::rounded_top_left_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
775 {
776 /*    A****B
777       H    *
778       *    *
779       *    *
780       F****E
781 */
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
788 }
789
790 void
791 Gtkmm2ext::rounded_top_right_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
792 {
793 /*    A****BQ
794       *    C
795       *    *
796       *    *
797       F****E
798 */
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
805 }
806
807 void
808 Gtkmm2ext::add_reflection (cairo_t* cr, double w, double h)
809 {
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);
816         cairo_fill (cr);
817         cairo_pattern_destroy(convex_pattern);
818 }
819
820 Glib::RefPtr<Gdk::Window>
821 Gtkmm2ext::window_to_draw_on (Gtk::Widget& w, Gtk::Widget** parent)
822 {
823         if (w.get_has_window()) {
824                 return w.get_window();
825         }
826
827         (*parent) = w.get_parent();
828
829         while (*parent) {
830                 if ((*parent)->get_has_window()) {
831                         return (*parent)->get_window ();
832                 }
833                 (*parent) = (*parent)->get_parent ();
834         }
835
836         return Glib::RefPtr<Gdk::Window> ();
837 }
838
839 int
840 Gtkmm2ext::pixel_width (const string& str, const Pango::FontDescription& font)
841 {
842         Glib::RefPtr<Pango::Context> context = Glib::wrap (gdk_pango_context_get());
843         Glib::RefPtr<Pango::Layout> layout = Pango::Layout::create (context);
844
845         layout->set_font_description (font);
846         layout->set_text (str);
847
848         int width, height;
849         Gtkmm2ext::get_ink_pixel_size (layout, width, height);
850
851 #ifdef __APPLE__
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
856         //
857         // see also libs/canvas/text.cc
858         int cor_width;
859         layout->set_text ("H");
860         layout->get_pixel_size (cor_width, height);
861         width += cor_width * 1.5;
862 #endif
863
864         return width;
865 }
866
867 void
868 Gtkmm2ext::pixel_size (const string& str, const Pango::FontDescription& font, int& width, int& height)
869 {
870         Gtk::Label foo;
871         Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout ("");
872
873         layout->set_font_description (font);
874         layout->set_text (str);
875
876         Gtkmm2ext::get_ink_pixel_size (layout, width, height);
877 }
878
879 #if 0
880 string
881 Gtkmm2ext::fit_to_pixels (const string& str, int pixel_width, Pango::FontDescription& font, int& actual_width, bool with_ellipses)
882 {
883         /* DECEMBER 2011: THIS PROTOTYPE OF fit_to_pixels() IS NOT USED
884            ANYWHERE AND HAS NOT BEEN TESTED.
885         */
886         Gtk::Label foo;
887         Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout (str);
888         Glib::RefPtr<const Pango::LayoutLine> line;
889
890         layout->set_font_description (font);
891         layout->set_width (pixel_width * PANGO_SCALE);
892
893         if (with_ellipses) {
894                 layout->set_ellipsize (Pango::ELLIPSIZE_END);
895         } else {
896                 layout->set_wrap (Pango::WRAP_CHAR);
897         }
898
899         line = layout->get_line (0);
900
901         /* XXX: might need special care to get the ellipsis character, not sure
902          * how that works
903          */
904         string s = string (layout->get_text ().substr(line->get_start_index(), line->get_length()));
905
906         cerr << "fit to pixels of " << str << " returns " << s << endl;
907
908         return s;
909 }
910 #endif
911
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.
914  *  @param name Text.
915  *  @param avail Available horizontal space.
916  *  @return (Text, possibly ellipsized) and (horizontal size of text)
917  */
918 std::pair<std::string, double>
919 Gtkmm2ext::fit_to_pixels (cairo_t* cr, std::string name, double avail)
920 {
921         /* XXX hopefully there exists a more efficient way of doing this */
922
923         bool abbreviated = false;
924         uint32_t width = 0;
925
926         while (1) {
927                 cairo_text_extents_t ext;
928                 cairo_text_extents (cr, name.c_str(), &ext);
929
930                 if (ext.width < avail || name.length() <= 4) {
931                         width = ext.width;
932                         break;
933                 }
934
935                 if (abbreviated) {
936                         name = name.substr (0, name.length() - 4) + "...";
937                 } else {
938                         name = name.substr (0, name.length() - 3) + "...";
939                         abbreviated = true;
940                 }
941         }
942
943         return std::make_pair (name, width);
944 }
945
946 Gtk::Label *
947 Gtkmm2ext::left_aligned_label (string const & t)
948 {
949         Gtk::Label* l = new Gtk::Label (t);
950         l->set_alignment (0, 0.5);
951         return l;
952 }
953
954 Gtk::Label *
955 Gtkmm2ext::right_aligned_label (string const & t)
956 {
957         Gtk::Label* l = new Gtk::Label (t);
958         l->set_alignment (1, 0.5);
959         return l;
960 }
961
962 static bool
963 make_null_tooltip (int, int, bool, const Glib::RefPtr<Gtk::Tooltip>& t)
964 {
965         t->set_tip_area (Gdk::Rectangle (0, 0, 0, 0));
966         return true;
967 }
968
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.
972  */
973 void
974 Gtkmm2ext::set_no_tooltip_whatsoever (Gtk::Widget& w)
975 {
976         w.property_has_tooltip() = true;
977         w.signal_query_tooltip().connect (sigc::ptr_fun (make_null_tooltip));
978 }
979
980 void
981 Gtkmm2ext::enable_tooltips ()
982 {
983         gtk_rc_parse_string ("gtk-enable-tooltips = 1");
984         PersistentTooltip::set_tooltips_enabled (true);
985 }
986
987 void
988 Gtkmm2ext::disable_tooltips ()
989 {
990         gtk_rc_parse_string ("gtk-enable-tooltips = 0");
991         PersistentTooltip::set_tooltips_enabled (false);
992 }
993
994 bool
995 Gtkmm2ext::event_inside_widget_window (Gtk::Widget& widget, GdkEvent* ev)
996 {
997         gdouble evx, evy;
998
999         if (!gdk_event_get_root_coords (ev, &evx, &evy)) {
1000                 return false;
1001         }
1002
1003         gint wx;
1004         gint wy;
1005         gint width, height, depth;
1006         gint x, y;
1007
1008         Glib::RefPtr<Gdk::Window> widget_window = widget.get_window();
1009
1010         widget_window->get_geometry (x, y, width, height, depth);
1011         widget_window->get_root_origin (wx, wy);
1012
1013         if ((evx >= wx && evx < wx + width) &&
1014                         (evy >= wy && evy < wy + height)) {
1015                 return true;
1016         }
1017
1018         return false;
1019 }
1020
1021 const char*
1022 Gtkmm2ext::event_type_string (int event_type)
1023 {
1024         switch (event_type) {
1025         case GDK_NOTHING:
1026                 return "nothing";
1027         case GDK_DELETE:
1028                 return "delete";
1029         case GDK_DESTROY:
1030                 return "destroy";
1031         case GDK_EXPOSE:
1032                 return "expose";
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";
1043         case GDK_KEY_PRESS:
1044                 return "key_press";
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";
1053         case GDK_CONFIGURE:
1054                 return "configure";
1055         case GDK_MAP:
1056                 return "map";
1057         case GDK_UNMAP:
1058                 return "unmap";
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";
1087         case GDK_NO_EXPOSE:
1088                 return "no_expose";
1089         case GDK_SCROLL:
1090                 return "scroll";
1091         case GDK_WINDOW_STATE:
1092                 return "window_state";
1093         case GDK_SETTING:
1094                 return "setting";
1095         case GDK_OWNER_CHANGE:
1096                 return "owner_change";
1097         case GDK_GRAB_BROKEN:
1098                 return "grab_broken";
1099         case GDK_DAMAGE:
1100                 return "damage";
1101         }
1102
1103         return "unknown";
1104 }
1105
1106 std::string
1107 Gtkmm2ext::markup_escape_text (std::string const& s)
1108 {
1109         return Glib::Markup::escape_text (s);
1110 }
1111
1112 void
1113 Gtkmm2ext::add_volume_shortcuts (Gtk::FileChooser& c)
1114 {
1115 #ifdef __APPLE__
1116         try {
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.
1120                  */
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;
1125
1126                         try { /* add_shortcut_folder throws an exception if the folder being added already has a shortcut */
1127                                 c.add_shortcut_folder (fullpath);
1128                         }
1129                         catch (Glib::Error& e) {
1130                                 std::cerr << "add_shortcut_folder() threw Glib::Error: " << e.what() << std::endl;
1131                         }
1132                 }
1133         }
1134         catch (Glib::FileError const& e) {
1135                 std::cerr << "listing /Volumnes failed: " << e.what() << std::endl;
1136         }
1137 #endif
1138 }
1139
1140 float
1141 Gtkmm2ext::paned_position_as_fraction (Gtk::Paned& paned, bool h)
1142 {
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());
1145 }
1146
1147 void
1148 Gtkmm2ext::paned_set_position_as_fraction (Gtk::Paned& paned, float fraction, bool h)
1149 {
1150         gint v = (h ? paned.get_allocation().get_height() : paned.get_allocation().get_width());
1151
1152         if (v < 1) {
1153                 return;
1154         }
1155
1156         paned.set_position ((guint) floor (fraction * v));
1157 }
1158
1159 string
1160 Gtkmm2ext::show_gdk_event_state (int state)
1161 {
1162         string s;
1163         if (state & GDK_SHIFT_MASK) {
1164                 s += "+SHIFT";
1165         }
1166         if (state & GDK_LOCK_MASK) {
1167                 s += "+LOCK";
1168         }
1169         if (state & GDK_CONTROL_MASK) {
1170                 s += "+CONTROL";
1171         }
1172         if (state & GDK_MOD1_MASK) {
1173                 s += "+MOD1";
1174         }
1175         if (state & GDK_MOD2_MASK) {
1176                 s += "+MOD2";
1177         }
1178         if (state & GDK_MOD3_MASK) {
1179                 s += "+MOD3";
1180         }
1181         if (state & GDK_MOD4_MASK) {
1182                 s += "+MOD4";
1183         }
1184         if (state & GDK_MOD5_MASK) {
1185                 s += "+MOD5";
1186         }
1187         if (state & GDK_BUTTON1_MASK) {
1188                 s += "+BUTTON1";
1189         }
1190         if (state & GDK_BUTTON2_MASK) {
1191                 s += "+BUTTON2";
1192         }
1193         if (state & GDK_BUTTON3_MASK) {
1194                 s += "+BUTTON3";
1195         }
1196         if (state & GDK_BUTTON4_MASK) {
1197                 s += "+BUTTON4";
1198         }
1199         if (state & GDK_BUTTON5_MASK) {
1200                 s += "+BUTTON5";
1201         }
1202         if (state & GDK_SUPER_MASK) {
1203                 s += "+SUPER";
1204         }
1205         if (state & GDK_HYPER_MASK) {
1206                 s += "+HYPER";
1207         }
1208         if (state & GDK_META_MASK) {
1209                 s += "+META";
1210         }
1211         if (state & GDK_RELEASE_MASK) {
1212                 s += "+RELEASE";
1213         }
1214
1215         return s;
1216 }