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