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