map::clear() calls erase. Cleans up cpp check warning 'iterator used after element...
[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                 c.remove (**child);
635         }
636 }
637
638 void
639 Gtkmm2ext::rounded_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
640 {
641         rounded_rectangle (context->cobj(), x, y, w, h, r);
642 }
643 void
644 Gtkmm2ext::rounded_top_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
645 {
646         rounded_top_rectangle (context->cobj(), x, y, w, h, r);
647 }
648 void
649 Gtkmm2ext::rounded_top_left_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
650 {
651         rounded_top_left_rectangle (context->cobj(), x, y, w, h, r);
652 }
653 void
654 Gtkmm2ext::rounded_top_right_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
655 {
656         rounded_top_right_rectangle (context->cobj(), x, y, w, h, r);
657 }
658 void
659 Gtkmm2ext::rounded_top_half_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
660 {
661         rounded_top_half_rectangle (context->cobj(), x, y, w, h, r);
662 }
663 void
664 Gtkmm2ext::rounded_bottom_half_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
665 {
666         rounded_bottom_half_rectangle (context->cobj(), x, y, w, h, r);
667 }
668
669 void
670 Gtkmm2ext::rounded_left_half_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
671 {
672         rounded_left_half_rectangle (context->cobj(), x, y, w, h, r);
673 }
674
675 void
676 Gtkmm2ext::rounded_right_half_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
677 {
678         rounded_right_half_rectangle (context->cobj(), x, y, w, h, r);
679 }
680
681 void
682 Gtkmm2ext::rounded_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
683 {
684         double degrees = M_PI / 180.0;
685
686         cairo_new_sub_path (cr);
687         cairo_arc (cr, x + w - r, y + r, r, -90 * degrees, 0 * degrees);  //tr
688         cairo_arc (cr, x + w - r, y + h - r, r, 0 * degrees, 90 * degrees);  //br
689         cairo_arc (cr, x + r, y + h - r, r, 90 * degrees, 180 * degrees);  //bl
690         cairo_arc (cr, x + r, y + r, r, 180 * degrees, 270 * degrees);  //tl
691         cairo_close_path (cr);
692 }
693
694 void
695 Gtkmm2ext::rounded_left_half_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
696 {
697         double degrees = M_PI / 180.0;
698
699         cairo_new_sub_path (cr);
700         cairo_line_to (cr, x+w, y); // tr
701         cairo_line_to (cr, x+w, y + h); // 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_right_half_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
709 {
710         double degrees = M_PI / 180.0;
711
712         cairo_new_sub_path (cr);
713         cairo_arc (cr, x + w - r, y + r, r, -90 * degrees, 0 * degrees);  //tr
714         cairo_arc (cr, x + w - r, y + h - r, r, 0 * degrees, 90 * degrees);  //br
715         cairo_line_to (cr, x, y + h); // bl
716         cairo_line_to (cr, x, y); // tl
717         cairo_close_path (cr);
718 }
719
720 void
721 Gtkmm2ext::rounded_top_half_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
722 {
723         double degrees = M_PI / 180.0;
724
725         cairo_new_sub_path (cr);
726         cairo_move_to (cr, x+w, y+h);
727         cairo_line_to (cr, x, y+h);
728         cairo_arc (cr, x + r, y + r, r, 180 * degrees, 270 * degrees);  //tl
729         cairo_arc (cr, x + w - r, y + r, r, -90 * degrees, 0 * degrees);  //tr
730         cairo_close_path (cr);
731 }
732
733 void
734 Gtkmm2ext::rounded_bottom_half_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
735 {
736         double degrees = M_PI / 180.0;
737
738         cairo_new_sub_path (cr);
739         cairo_move_to (cr, x, y);
740         cairo_line_to (cr, x+w, y);
741         cairo_arc (cr, x + w - r, y + h - r, r, 0 * degrees, 90 * degrees);  //br
742         cairo_arc (cr, x + r, y + h - r, r, 90 * degrees, 180 * degrees);  //bl
743         cairo_close_path (cr);
744 }
745
746
747 void
748 Gtkmm2ext::rounded_top_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
749 {
750         double degrees = M_PI / 180.0;
751
752         cairo_new_sub_path (cr);
753         cairo_move_to (cr, x+w, y+h);
754         cairo_line_to (cr, x, y+h);
755         cairo_arc (cr, x + r, y + r, r, 180 * degrees, 270 * degrees);  //tl
756         cairo_arc (cr, x + w - r, y + r, r, -90 * degrees, 0 * degrees);  //tr
757         cairo_close_path (cr);
758 }
759
760 void
761 Gtkmm2ext::rounded_top_left_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
762 {
763 /*    A****B
764       H    *
765       *    *
766       *    *
767       F****E
768 */
769         cairo_move_to (cr, x+r,y); // Move to A
770         cairo_line_to (cr, x+w,y); // Straight line to B
771         cairo_line_to (cr, x+w,y+h); // Move to E
772         cairo_line_to (cr, x,y+h); // Line to F
773         cairo_line_to (cr, x,y+r); // Line to H
774         cairo_curve_to (cr, x,y,x,y,x+r,y); // Curve to A
775 }
776
777 void
778 Gtkmm2ext::rounded_top_right_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
779 {
780 /*    A****BQ
781       *    C
782       *    *
783       *    *
784       F****E
785 */
786         cairo_move_to (cr, x,y); // Move to A
787         cairo_line_to (cr, x+w-r,y); // Straight line to B
788         cairo_curve_to (cr, x+w,y,x+w,y,x+w,y+r); // Curve to C, Control points are both at Q
789         cairo_line_to (cr, x+w,y+h); // Move to E
790         cairo_line_to (cr, x,y+h); // Line to F
791         cairo_line_to (cr, x,y); // Line to A
792 }
793
794 Glib::RefPtr<Gdk::Window>
795 Gtkmm2ext::window_to_draw_on (Gtk::Widget& w, Gtk::Widget** parent)
796 {
797         if (w.get_has_window()) {
798                 return w.get_window();
799         }
800
801         (*parent) = w.get_parent();
802
803         while (*parent) {
804                 if ((*parent)->get_has_window()) {
805                         return (*parent)->get_window ();
806                 }
807                 (*parent) = (*parent)->get_parent ();
808         }
809
810         return Glib::RefPtr<Gdk::Window> ();
811 }
812
813 int
814 Gtkmm2ext::pixel_width (const string& str, const Pango::FontDescription& font)
815 {
816         Glib::RefPtr<Pango::Context> context = Glib::wrap (gdk_pango_context_get());
817         Glib::RefPtr<Pango::Layout> layout = Pango::Layout::create (context);
818
819         layout->set_font_description (font);
820         layout->set_text (str);
821
822         int width, height;
823         Gtkmm2ext::get_ink_pixel_size (layout, width, height);
824
825 #ifdef __APPLE__
826         // Pango returns incorrect text width on some OS X
827         // So we have to make a correction
828         // To determine the correct indent take the largest symbol for which the width is correct
829         // and make the calculation
830         //
831         // see also libs/canvas/text.cc
832         int cor_width;
833         layout->set_text ("H");
834         layout->get_pixel_size (cor_width, height);
835         width += cor_width * 1.5;
836 #endif
837
838         return width;
839 }
840
841 void
842 Gtkmm2ext::pixel_size (const string& str, const Pango::FontDescription& font, int& width, int& height)
843 {
844         Gtk::Label foo;
845         Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout ("");
846
847         layout->set_font_description (font);
848         layout->set_text (str);
849
850         Gtkmm2ext::get_ink_pixel_size (layout, width, height);
851 }
852
853 #if 0
854 string
855 Gtkmm2ext::fit_to_pixels (const string& str, int pixel_width, Pango::FontDescription& font, int& actual_width, bool with_ellipses)
856 {
857         /* DECEMBER 2011: THIS PROTOTYPE OF fit_to_pixels() IS NOT USED
858            ANYWHERE AND HAS NOT BEEN TESTED.
859         */
860         Gtk::Label foo;
861         Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout (str);
862         Glib::RefPtr<const Pango::LayoutLine> line;
863
864         layout->set_font_description (font);
865         layout->set_width (pixel_width * PANGO_SCALE);
866
867         if (with_ellipses) {
868                 layout->set_ellipsize (Pango::ELLIPSIZE_END);
869         } else {
870                 layout->set_wrap (Pango::WRAP_CHAR);
871         }
872
873         line = layout->get_line (0);
874
875         /* XXX: might need special care to get the ellipsis character, not sure
876            how that works
877         */
878
879         string s = string (layout->get_text ().substr(line->get_start_index(), line->get_length()));
880
881         cerr << "fit to pixels of " << str << " returns " << s << endl;
882
883         return s;
884 }
885 #endif
886
887 /** Try to fit a string into a given horizontal space by ellipsizing it.
888  *  @param cr Cairo context in which the text will be plotted.
889  *  @param name Text.
890  *  @param avail Available horizontal space.
891  *  @return (Text, possibly ellipsized) and (horizontal size of text)
892  */
893
894 std::pair<std::string, double>
895 Gtkmm2ext::fit_to_pixels (cairo_t* cr, std::string name, double avail)
896 {
897         /* XXX hopefully there exists a more efficient way of doing this */
898
899         bool abbreviated = false;
900         uint32_t width = 0;
901
902         while (1) {
903                 cairo_text_extents_t ext;
904                 cairo_text_extents (cr, name.c_str(), &ext);
905
906                 if (ext.width < avail || name.length() <= 4) {
907                         width = ext.width;
908                         break;
909                 }
910
911                 if (abbreviated) {
912                         name = name.substr (0, name.length() - 4) + "...";
913                 } else {
914                         name = name.substr (0, name.length() - 3) + "...";
915                         abbreviated = true;
916                 }
917         }
918
919         return std::make_pair (name, width);
920 }
921
922 Gtk::Label *
923 Gtkmm2ext::left_aligned_label (string const & t)
924 {
925         Gtk::Label* l = new Gtk::Label (t);
926         l->set_alignment (0, 0.5);
927         return l;
928 }
929
930 Gtk::Label *
931 Gtkmm2ext::right_aligned_label (string const & t)
932 {
933         Gtk::Label* l = new Gtk::Label (t);
934         l->set_alignment (1, 0.5);
935         return l;
936 }
937
938 static bool
939 make_null_tooltip (int, int, bool, const Glib::RefPtr<Gtk::Tooltip>& t)
940 {
941         t->set_tip_area (Gdk::Rectangle (0, 0, 0, 0));
942         return true;
943 }
944
945 /** Hackily arrange for the provided widget to have no tooltip,
946  *  and also to stop any other widget from providing one while
947  * the mouse is over w.
948  */
949 void
950 Gtkmm2ext::set_no_tooltip_whatsoever (Gtk::Widget& w)
951 {
952         w.property_has_tooltip() = true;
953         w.signal_query_tooltip().connect (sigc::ptr_fun (make_null_tooltip));
954 }
955
956 void
957 Gtkmm2ext::enable_tooltips ()
958 {
959         gtk_rc_parse_string ("gtk-enable-tooltips = 1");
960         PersistentTooltip::set_tooltips_enabled (true);
961 }
962
963 void
964 Gtkmm2ext::disable_tooltips ()
965 {
966         gtk_rc_parse_string ("gtk-enable-tooltips = 0");
967         PersistentTooltip::set_tooltips_enabled (false);
968 }
969
970 bool
971 Gtkmm2ext::event_inside_widget_window (Gtk::Widget& widget, GdkEvent* ev)
972 {
973         gdouble evx, evy;
974
975         if (!gdk_event_get_root_coords (ev, &evx, &evy)) {
976                 return false;
977         }
978
979         gint wx;
980         gint wy;
981         gint width, height, depth;
982         gint x, y;
983
984         Glib::RefPtr<Gdk::Window> widget_window = widget.get_window();
985
986         widget_window->get_geometry (x, y, width, height, depth);
987         widget_window->get_root_origin (wx, wy);
988
989         if ((evx >= wx && evx < wx + width) &&
990             (evy >= wy && evy < wy + height)) {
991                 return true;
992         }
993
994         return false;
995 }
996
997 const char*
998 Gtkmm2ext::event_type_string (int event_type)
999 {
1000         switch (event_type) {
1001         case GDK_NOTHING:
1002                 return "nothing";
1003         case GDK_DELETE:
1004                 return "delete";
1005         case GDK_DESTROY:
1006                 return "destroy";
1007         case GDK_EXPOSE:
1008                 return "expose";
1009         case GDK_MOTION_NOTIFY:
1010                 return "motion_notify";
1011         case GDK_BUTTON_PRESS:
1012                 return "button_press";
1013         case GDK_2BUTTON_PRESS:
1014                 return "2button_press";
1015         case GDK_3BUTTON_PRESS:
1016                 return "3button_press";
1017         case GDK_BUTTON_RELEASE:
1018                 return "button_release";
1019         case GDK_KEY_PRESS:
1020                 return "key_press";
1021         case GDK_KEY_RELEASE:
1022                 return "key_release";
1023         case GDK_ENTER_NOTIFY:
1024                 return "enter_notify";
1025         case GDK_LEAVE_NOTIFY:
1026                 return "leave_notify";
1027         case GDK_FOCUS_CHANGE:
1028                 return "focus_change";
1029         case GDK_CONFIGURE:
1030                 return "configure";
1031         case GDK_MAP:
1032                 return "map";
1033         case GDK_UNMAP:
1034                 return "unmap";
1035         case GDK_PROPERTY_NOTIFY:
1036                 return "property_notify";
1037         case GDK_SELECTION_CLEAR:
1038                 return "selection_clear";
1039         case GDK_SELECTION_REQUEST:
1040                 return "selection_request";
1041         case GDK_SELECTION_NOTIFY:
1042                 return "selection_notify";
1043         case GDK_PROXIMITY_IN:
1044                 return "proximity_in";
1045         case GDK_PROXIMITY_OUT:
1046                 return "proximity_out";
1047         case GDK_DRAG_ENTER:
1048                 return "drag_enter";
1049         case GDK_DRAG_LEAVE:
1050                 return "drag_leave";
1051         case GDK_DRAG_MOTION:
1052                 return "drag_motion";
1053         case GDK_DRAG_STATUS:
1054                 return "drag_status";
1055         case GDK_DROP_START:
1056                 return "drop_start";
1057         case GDK_DROP_FINISHED:
1058                 return "drop_finished";
1059         case GDK_CLIENT_EVENT:
1060                 return "client_event";
1061         case GDK_VISIBILITY_NOTIFY:
1062                 return "visibility_notify";
1063         case GDK_NO_EXPOSE:
1064                 return "no_expose";
1065         case GDK_SCROLL:
1066                 return "scroll";
1067         case GDK_WINDOW_STATE:
1068                 return "window_state";
1069         case GDK_SETTING:
1070                 return "setting";
1071         case GDK_OWNER_CHANGE:
1072                 return "owner_change";
1073         case GDK_GRAB_BROKEN:
1074                 return "grab_broken";
1075         case GDK_DAMAGE:
1076                 return "damage";
1077         }
1078
1079         return "unknown";
1080 }
1081
1082 std::string
1083 Gtkmm2ext::markup_escape_text (std::string const& s)
1084 {
1085         return Glib::Markup::escape_text (s);
1086 }
1087
1088 void
1089 Gtkmm2ext::add_volume_shortcuts (Gtk::FileChooser& c)
1090 {
1091 #ifdef __APPLE__
1092         try {
1093                 /* This is a first order approach, listing all mounted volumes (incl network).
1094                  * One could use `diskutil` or `mount` to query local disks only, or
1095                  * something even fancier if deemed appropriate.
1096                  */
1097                 Glib::Dir dir("/Volumes");
1098                 for (Glib::DirIterator di = dir.begin(); di != dir.end(); di++) {
1099                         string fullpath = Glib::build_filename ("/Volumes", *di);
1100                         if (!Glib::file_test (fullpath, Glib::FILE_TEST_IS_DIR)) continue;
1101
1102                         try { /* add_shortcut_folder throws an exception if the folder being added already has a shortcut */
1103                                 c.add_shortcut_folder (fullpath);
1104                         }
1105                         catch (Glib::Error& e) {
1106                                 std::cerr << "add_shortcut_folder() threw Glib::Error: " << e.what() << std::endl;
1107                         }
1108                 }
1109         }
1110         catch (Glib::FileError& e) {
1111                 std::cerr << "listing /Volumnes failed: " << e.what() << std::endl;
1112         }
1113 #endif
1114 }
1115
1116 float
1117 Gtkmm2ext::paned_position_as_fraction (Gtk::Paned& paned, bool h)
1118 {
1119         const guint pos = gtk_paned_get_position (const_cast<GtkPaned*>(static_cast<const Gtk::Paned*>(&paned)->gobj()));
1120         return (double) pos / (h ? paned.get_allocation().get_height() : paned.get_allocation().get_width());
1121 }
1122
1123 void
1124 Gtkmm2ext::paned_set_position_as_fraction (Gtk::Paned& paned, float fraction, bool h)
1125 {
1126         gint v = (h ? paned.get_allocation().get_height() : paned.get_allocation().get_width());
1127
1128         if (v < 1) {
1129                 return;
1130         }
1131
1132         paned.set_position ((guint) floor (fraction * v));
1133 }
1134
1135 string
1136 Gtkmm2ext::show_gdk_event_state (int state)
1137 {
1138         string s;
1139         if (state & GDK_SHIFT_MASK) {
1140                 s += "+SHIFT";
1141         }
1142         if (state & GDK_LOCK_MASK) {
1143                 s += "+LOCK";
1144         }
1145         if (state & GDK_CONTROL_MASK) {
1146                 s += "+CONTROL";
1147         }
1148         if (state & GDK_MOD1_MASK) {
1149                 s += "+MOD1";
1150         }
1151         if (state & GDK_MOD2_MASK) {
1152                 s += "+MOD2";
1153         }
1154         if (state & GDK_MOD3_MASK) {
1155                 s += "+MOD3";
1156         }
1157         if (state & GDK_MOD4_MASK) {
1158                 s += "+MOD4";
1159         }
1160         if (state & GDK_MOD5_MASK) {
1161                 s += "+MOD5";
1162         }
1163         if (state & GDK_BUTTON1_MASK) {
1164                 s += "+BUTTON1";
1165         }
1166         if (state & GDK_BUTTON2_MASK) {
1167                 s += "+BUTTON2";
1168         }
1169         if (state & GDK_BUTTON3_MASK) {
1170                 s += "+BUTTON3";
1171         }
1172         if (state & GDK_BUTTON4_MASK) {
1173                 s += "+BUTTON4";
1174         }
1175         if (state & GDK_BUTTON5_MASK) {
1176                 s += "+BUTTON5";
1177         }
1178         if (state & GDK_SUPER_MASK) {
1179                 s += "+SUPER";
1180         }
1181         if (state & GDK_HYPER_MASK) {
1182                 s += "+HYPER";
1183         }
1184         if (state & GDK_META_MASK) {
1185                 s += "+META";
1186         }
1187         if (state & GDK_RELEASE_MASK) {
1188                 s += "+RELEASE";
1189         }
1190
1191         return s;
1192 }