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