globally remove all trailing whitespace from ardour code base.
[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
35 #include "gtkmm2ext/utils.h"
36
37 #include "i18n.h"
38
39 using namespace std;
40
41 void
42 Gtkmm2ext::init (const char* localedir)
43 {
44 #ifdef ENABLE_NLS
45         (void) bindtextdomain(PACKAGE, localedir);
46         (void) bind_textdomain_codeset (PACKAGE, "UTF-8");
47 #endif
48 }
49
50 void
51 Gtkmm2ext::get_ink_pixel_size (Glib::RefPtr<Pango::Layout> layout,
52                                int& width,
53                                int& height)
54 {
55         Pango::Rectangle ink_rect = layout->get_ink_extents ();
56         
57         width = PANGO_PIXELS(ink_rect.get_width());
58         height = PANGO_PIXELS(ink_rect.get_height());
59 }
60
61 void
62 Gtkmm2ext::get_pixel_size (Glib::RefPtr<Pango::Layout> layout,
63                            int& width,
64                            int& height)
65 {
66         layout->get_pixel_size (width, height);
67 }
68
69 void
70 Gtkmm2ext::set_size_request_to_display_given_text (Gtk::Widget &w, const gchar *text,
71                                                    gint hpadding, gint vpadding)
72 {
73         int width, height;
74         w.ensure_style ();
75         
76         get_pixel_size (w.create_pango_layout (text), width, height);
77         w.set_size_request(width + hpadding, height + vpadding);
78 }
79
80 /** Set width request to display given text, and height to display anything.
81     This is useful for setting many widgets to the same height for consistency. */
82 void
83 Gtkmm2ext::set_size_request_to_display_given_text_width (Gtk::Widget& w,
84                                                          const gchar* htext,
85                                                          gint         hpadding,
86                                                          gint         vpadding)
87 {
88         static const gchar* vtext = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
89
90         w.ensure_style ();
91
92         int hwidth, hheight;
93         get_pixel_size (w.create_pango_layout (htext), hwidth, hheight);
94
95         int vwidth, vheight;
96         get_pixel_size (w.create_pango_layout (vtext), vwidth, vheight);
97
98         w.set_size_request(hwidth + hpadding, vheight + vpadding);
99 }
100
101 void
102 Gtkmm2ext::set_height_request_to_display_any_text (Gtk::Widget& w, gint vpadding)
103 {
104         static const gchar* vtext = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
105
106         w.ensure_style ();
107
108         int width, height;
109         get_pixel_size (w.create_pango_layout (vtext), width, height);
110
111         w.set_size_request(-1, height + vpadding);
112 }
113
114 void
115 Gtkmm2ext::set_size_request_to_display_given_text (Gtk::Widget &w, std::string const & text,
116                                                    gint hpadding, gint vpadding)
117 {
118         int width, height;
119         w.ensure_style ();
120         
121         get_pixel_size (w.create_pango_layout (text), width, height);
122         w.set_size_request(width + hpadding, height + vpadding);
123 }
124
125 void
126 Gtkmm2ext::set_size_request_to_display_given_text (Gtk::Widget &w,
127                                                    const std::vector<std::string>& strings,
128                                                    gint hpadding, gint vpadding)
129 {
130         int width, height;
131         int width_max = 0;
132         int height_max = 0;
133         w.ensure_style ();
134         vector<string> copy;
135         const vector<string>* to_use;
136         vector<string>::const_iterator i;
137
138         for (i = strings.begin(); i != strings.end(); ++i) {
139                 if ((*i).find_first_of ("gy") != string::npos) {
140                         /* contains a descender */
141                         break;
142                 }
143         }
144         
145         if (i == strings.end()) {
146                 /* make a copy of the strings then add one that has a descender */
147                 copy = strings;
148                 copy.push_back ("g");
149                 to_use = &copy;
150         } else {
151                 to_use = &strings;
152         }
153         
154         for (vector<string>::const_iterator i = to_use->begin(); i != to_use->end(); ++i) {
155                 get_pixel_size (w.create_pango_layout (*i), width, height);
156                 width_max = max(width_max,width);
157                 height_max = max(height_max, height);
158         }
159
160         w.set_size_request(width_max + hpadding, height_max + vpadding);
161 }
162
163 /** This version specifies horizontal padding in text to avoid assumptions
164     about font size.  Should be used anywhere padding is used to avoid text,
165     like combo boxes. */
166 void
167 Gtkmm2ext::set_size_request_to_display_given_text (Gtk::Widget&                    w,
168                                                    const std::vector<std::string>& strings,
169                                                    const std::string&              hpadding,
170                                                    gint                            vpadding)
171 {
172         int width_max = 0;
173         int height_max = 0;
174         w.ensure_style ();
175
176         for (vector<string>::const_iterator i = strings.begin(); i != strings.end(); ++i) {
177                 int width, height;
178                 get_pixel_size (w.create_pango_layout (*i), width, height);
179                 width_max = max(width_max,width);
180                 height_max = max(height_max, height);
181         }
182
183         int pad_width;
184         int pad_height;
185         get_pixel_size (w.create_pango_layout (hpadding), pad_width, pad_height);
186
187         w.set_size_request(width_max + pad_width, height_max + vpadding);
188 }
189
190 static inline guint8
191 demultiply_alpha (guint8 src,
192                   guint8 alpha)
193 {
194         /* cairo pixel buffer data contains RGB values with the alpha
195            values premultiplied.
196
197            GdkPixbuf pixel buffer data contains RGB values without the
198            alpha value applied.
199
200            this removes the alpha component from the cairo version and
201            returns the GdkPixbuf version.
202         */
203         return alpha ? ((guint (src) << 8) - src) / alpha : 0;
204 }
205
206 void
207 Gtkmm2ext::convert_bgra_to_rgba (guint8 const* src,
208                                  guint8*       dst,
209                                  int           width,
210                                  int           height)
211 {
212         guint8 const* src_pixel = src;
213         guint8*       dst_pixel = dst;
214         
215         /* cairo pixel data is endian-dependent ARGB with A in the most significant 8 bits,
216            with premultipled alpha values (see preceding function)
217
218            GdkPixbuf pixel data is non-endian-dependent RGBA with R in the lowest addressable
219            8 bits, and non-premultiplied alpha values.
220
221            convert from the cairo values to the GdkPixbuf ones.
222         */
223
224         for (int y = 0; y < height; y++) {
225                 for (int x = 0; x < width; x++) {
226 #if G_BYTE_ORDER == G_LITTLE_ENDIAN
227                         /* Cairo [ B G R A ] is actually  [ B G R A ] in memory SOURCE
228                                                             0 1 2 3
229                            Pixbuf [ R G B A ] is actually [ R G B A ] in memory DEST
230                         */
231                         dst_pixel[0] = demultiply_alpha (src_pixel[2],
232                                                          src_pixel[3]); // R [0] <= [ 2 ]
233                         dst_pixel[1] = demultiply_alpha (src_pixel[1],
234                                                          src_pixel[3]); // G [1] <= [ 1 ]
235                         dst_pixel[2] = demultiply_alpha (src_pixel[0],
236                                                          src_pixel[3]); // B [2] <= [ 0 ]
237                         dst_pixel[3] = src_pixel[3]; // alpha
238
239 #elif G_BYTE_ORDER == G_BIG_ENDIAN
240                         /* Cairo [ B G R A ] is actually  [ A R G B ] in memory SOURCE
241                                                             0 1 2 3
242                            Pixbuf [ R G B A ] is actually [ R G B A ] in memory DEST
243                         */
244                         dst_pixel[0] = demultiply_alpha (src_pixel[1],
245                                                          src_pixel[0]); // R [0] <= [ 1 ]
246                         dst_pixel[1] = demultiply_alpha (src_pixel[2],
247                                                          src_pixel[0]); // G [1] <= [ 2 ]
248                         dst_pixel[2] = demultiply_alpha (src_pixel[3],
249                                                          src_pixel[0]); // B [2] <= [ 3 ]
250                         dst_pixel[3] = src_pixel[0]; // alpha
251
252 #else
253 #error ardour does not currently support PDP-endianess
254 #endif                  
255
256                         dst_pixel += 4;
257                         src_pixel += 4;
258                 }
259         }
260 }
261
262 Glib::RefPtr<Gdk::Pixbuf>
263 Gtkmm2ext::pixbuf_from_string(const string& name, const Pango::FontDescription& font, int clip_width, int clip_height, Gdk::Color fg)
264 {
265         static Glib::RefPtr<Gdk::Pixbuf>* empty_pixbuf = 0;
266
267         if (name.empty()) {
268                 if (empty_pixbuf == 0) {
269                         empty_pixbuf = new Glib::RefPtr<Gdk::Pixbuf>;
270                         *empty_pixbuf = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB, true, 8, clip_width, clip_height);
271                 }
272                 return *empty_pixbuf;
273         }
274
275         Glib::RefPtr<Gdk::Pixbuf> buf = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB, true, 8, clip_width, clip_height);
276
277         cairo_surface_t* surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, clip_width, clip_height);
278         cairo_t* cr = cairo_create (surface);
279         cairo_text_extents_t te;
280         
281         cairo_set_source_rgba (cr, fg.get_red_p(), fg.get_green_p(), fg.get_blue_p(), 1.0);
282         cairo_select_font_face (cr, font.get_family().c_str(),
283                                 CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
284         cairo_set_font_size (cr,  font.get_size() / Pango::SCALE);
285         cairo_text_extents (cr, name.c_str(), &te);
286         
287         cairo_move_to (cr, 0.5, int (0.5 - te.height / 2 - te.y_bearing + clip_height / 2));
288         cairo_show_text (cr, name.c_str());
289         
290         convert_bgra_to_rgba(cairo_image_surface_get_data (surface), buf->get_pixels(), clip_width, clip_height);
291
292         cairo_destroy(cr);
293         cairo_surface_destroy(surface);
294
295         return buf;
296 }
297
298 void
299 Gtkmm2ext::set_popdown_strings (Gtk::ComboBoxText& cr, const vector<string>& strings)
300 {
301         vector<string>::const_iterator i;
302
303         cr.clear ();
304
305         for (i = strings.begin(); i != strings.end(); ++i) {
306                 cr.append_text (*i);
307         }
308 }
309
310 void
311 Gtkmm2ext::get_popdown_strings (Gtk::ComboBoxText& cr, std::vector<std::string>& strings)
312 {
313         strings.clear ();
314         Glib::RefPtr<const Gtk::TreeModel> m = cr.get_model();
315         if (!m) {
316                 return;
317         }
318         for(Gtk::TreeModel::iterator i = m->children().begin(); i != m->children().end(); ++i) {
319                 Glib::ustring txt;
320                 (*i)->get_value(0, txt);
321                 strings.push_back (txt);
322         }
323 }
324
325 size_t
326 Gtkmm2ext::get_popdown_string_count (Gtk::ComboBoxText& cr)
327 {
328         Glib::RefPtr<const Gtk::TreeModel> m = cr.get_model();
329         if (!m) {
330                 return 0;
331         }
332         return m->children().size();
333 }
334
335 bool
336 Gtkmm2ext::contains_value (Gtk::ComboBoxText& cr, const std::string text)
337 {
338         std::vector<std::string> s;
339         get_popdown_strings (cr, s);
340         return (std::find (s.begin(), s.end(), text) != s.end());
341 }
342
343 bool
344 Gtkmm2ext::set_active_text_if_present (Gtk::ComboBoxText& cr, const std::string text)
345 {
346         if (contains_value(cr, text)) {
347                 cr.set_active_text (text);
348                 return true;
349         }
350         return false;
351 }
352
353 GdkWindow*
354 Gtkmm2ext::get_paned_handle (Gtk::Paned& paned)
355 {
356         return GTK_PANED(paned.gobj())->handle;
357 }
358
359 void
360 Gtkmm2ext::set_decoration (Gtk::Window* win, Gdk::WMDecoration decor)
361 {
362         win->get_window()->set_decorations (decor);
363 }
364
365 void Gtkmm2ext::set_treeview_header_as_default_label(Gtk::TreeViewColumn* c)
366 {
367         gtk_tree_view_column_set_widget( c->gobj(), GTK_WIDGET(0) );
368 }
369
370 void
371 Gtkmm2ext::detach_menu (Gtk::Menu& menu)
372 {
373         /* its possible for a Gtk::Menu to have no gobj() because it has
374            not yet been instantiated. Catch this and provide a safe
375            detach method.
376         */
377         if (menu.gobj()) {
378                 if (menu.get_attach_widget()) {
379                         menu.detach ();
380                 }
381         }
382 }
383
384 bool
385 Gtkmm2ext::possibly_translate_mod_to_make_legal_accelerator (GdkModifierType& mod)
386 {
387 #ifdef GTKOSX
388         /* GTK on OS X is currently (February 2012) setting both
389            the Meta and Mod2 bits in the event modifier state if
390            the Command key is down.
391
392            gtk_accel_groups_activate() does not invoke any of the logic
393            that gtk_window_activate_key() will that sorts out that stupid
394            state of affairs, and as a result it fails to find a match
395            for the key event and the current set of accelerators.
396
397            to fix this, if the meta bit is set, remove the mod2 bit
398            from the modifier. this assumes that our bindings use Primary
399            which will have set the meta bit in the accelerator entry.
400         */
401         if (mod & GDK_META_MASK) {
402                 mod = GdkModifierType (mod & ~GDK_MOD2_MASK);
403         }
404 #endif
405         return true;
406 }
407
408 bool
409 Gtkmm2ext::possibly_translate_keyval_to_make_legal_accelerator (uint32_t& keyval)
410 {
411         int fakekey = GDK_VoidSymbol;
412
413         switch (keyval) {
414         case GDK_Tab:
415         case GDK_ISO_Left_Tab:
416                 fakekey = GDK_nabla;
417                 break;
418
419         case GDK_Up:
420                 fakekey = GDK_uparrow;
421                 break;
422
423         case GDK_Down:
424                 fakekey = GDK_downarrow;
425                 break;
426
427         case GDK_Right:
428                 fakekey = GDK_rightarrow;
429                 break;
430
431         case GDK_Left:
432                 fakekey = GDK_leftarrow;
433                 break;
434
435         case GDK_Return:
436                 fakekey = GDK_3270_Enter;
437                 break;
438
439         case GDK_KP_Enter:
440                 fakekey = GDK_F35;
441                 break;
442
443         default:
444                 break;
445         }
446
447         if (fakekey != GDK_VoidSymbol) {
448                 keyval = fakekey;
449                 return true;
450         }
451
452         return false;
453 }
454
455 uint32_t
456 Gtkmm2ext::possibly_translate_legal_accelerator_to_real_key (uint32_t keyval)
457 {
458         switch (keyval) {
459         case GDK_nabla:
460                 return GDK_Tab;
461                 break;
462
463         case GDK_uparrow:
464                 return GDK_Up;
465                 break;
466
467         case GDK_downarrow:
468                 return GDK_Down;
469                 break;
470
471         case GDK_rightarrow:
472                 return GDK_Right;
473                 break;
474
475         case GDK_leftarrow:
476                 return GDK_Left;
477                 break;
478
479         case GDK_3270_Enter:
480                 return GDK_Return;
481
482         case GDK_F35:
483                 return GDK_KP_Enter;
484                 break;
485         }
486
487         return keyval;
488 }
489
490 int
491 Gtkmm2ext::physical_screen_height (Glib::RefPtr<Gdk::Window> win)
492 {
493         GdkScreen* scr = gdk_screen_get_default();
494
495         if (win) {
496                 GdkRectangle r;
497                 gint monitor = gdk_screen_get_monitor_at_window (scr, win->gobj());
498                 gdk_screen_get_monitor_geometry (scr, monitor, &r);
499                 return r.height;
500         } else {
501                 return gdk_screen_get_height (scr);
502         }
503 }
504
505 int
506 Gtkmm2ext::physical_screen_width (Glib::RefPtr<Gdk::Window> win)
507 {
508         GdkScreen* scr = gdk_screen_get_default();
509
510         if (win) {
511                 GdkRectangle r;
512                 gint monitor = gdk_screen_get_monitor_at_window (scr, win->gobj());
513                 gdk_screen_get_monitor_geometry (scr, monitor, &r);
514                 return r.width;
515         } else {
516                 return gdk_screen_get_width (scr);
517         }
518 }
519
520 void
521 Gtkmm2ext::container_clear (Gtk::Container& c)
522 {
523         list<Gtk::Widget*> children = c.get_children();
524         for (list<Gtk::Widget*>::iterator child = children.begin(); child != children.end(); ++child) {
525                 c.remove (**child);
526         }
527 }
528
529 void
530 Gtkmm2ext::rounded_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
531 {
532         rounded_rectangle (context->cobj(), x, y, w, h, r);
533 }
534 void
535 Gtkmm2ext::rounded_top_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
536 {
537         rounded_top_rectangle (context->cobj(), x, y, w, h, r);
538 }
539 void
540 Gtkmm2ext::rounded_top_left_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
541 {
542         rounded_top_left_rectangle (context->cobj(), x, y, w, h, r);
543 }
544 void
545 Gtkmm2ext::rounded_top_right_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
546 {
547         rounded_top_right_rectangle (context->cobj(), x, y, w, h, r);
548 }
549 void
550 Gtkmm2ext::rounded_top_half_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
551 {
552         rounded_top_half_rectangle (context->cobj(), x, y, w, h, r);
553 }
554 void
555 Gtkmm2ext::rounded_bottom_half_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
556 {
557         rounded_bottom_half_rectangle (context->cobj(), x, y, w, h, r);
558 }
559
560 void
561 Gtkmm2ext::rounded_left_half_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
562 {
563         rounded_left_half_rectangle (context->cobj(), x, y, w, h, r);
564 }
565
566 void
567 Gtkmm2ext::rounded_right_half_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
568 {
569         rounded_right_half_rectangle (context->cobj(), x, y, w, h, r);
570 }
571
572 void
573 Gtkmm2ext::rounded_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
574 {
575         double degrees = M_PI / 180.0;
576
577         cairo_new_sub_path (cr);
578         cairo_arc (cr, x + w - r, y + r, r, -90 * degrees, 0 * degrees);  //tr
579         cairo_arc (cr, x + w - r, y + h - r, r, 0 * degrees, 90 * degrees);  //br
580         cairo_arc (cr, x + r, y + h - r, r, 90 * degrees, 180 * degrees);  //bl
581         cairo_arc (cr, x + r, y + r, r, 180 * degrees, 270 * degrees);  //tl
582         cairo_close_path (cr);
583 }
584
585 void
586 Gtkmm2ext::rounded_left_half_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
587 {
588         double degrees = M_PI / 180.0;
589
590         cairo_new_sub_path (cr);
591         cairo_line_to (cr, x+w, y); // tr
592         cairo_line_to (cr, x+w, y + h); // br
593         cairo_arc (cr, x + r, y + h - r, r, 90 * degrees, 180 * degrees);  //bl
594         cairo_arc (cr, x + r, y + r, r, 180 * degrees, 270 * degrees);  //tl
595         cairo_close_path (cr);
596 }
597
598 void
599 Gtkmm2ext::rounded_right_half_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
600 {
601         double degrees = M_PI / 180.0;
602
603         cairo_new_sub_path (cr);
604         cairo_arc (cr, x + w - r, y + r, r, -90 * degrees, 0 * degrees);  //tr
605         cairo_arc (cr, x + w - r, y + h - r, r, 0 * degrees, 90 * degrees);  //br
606         cairo_line_to (cr, x, y + h); // bl
607         cairo_line_to (cr, x, y); // tl
608         cairo_close_path (cr);
609 }
610
611 void
612 Gtkmm2ext::rounded_top_half_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
613 {
614         double degrees = M_PI / 180.0;
615
616         cairo_new_sub_path (cr);
617         cairo_move_to (cr, x+w, y+h);
618         cairo_line_to (cr, x, y+h);
619         cairo_arc (cr, x + r, y + r, r, 180 * degrees, 270 * degrees);  //tl
620         cairo_arc (cr, x + w - r, y + r, r, -90 * degrees, 0 * degrees);  //tr
621         cairo_close_path (cr);
622 }
623
624 void
625 Gtkmm2ext::rounded_bottom_half_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
626 {
627         double degrees = M_PI / 180.0;
628
629         cairo_new_sub_path (cr);
630         cairo_move_to (cr, x, y);
631         cairo_line_to (cr, x+w, y);
632         cairo_arc (cr, x + w - r, y + h - r, r, 0 * degrees, 90 * degrees);  //br
633         cairo_arc (cr, x + r, y + h - r, r, 90 * degrees, 180 * degrees);  //bl
634         cairo_close_path (cr);
635 }
636
637
638 void
639 Gtkmm2ext::rounded_top_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
640 {
641         double degrees = M_PI / 180.0;
642
643         cairo_new_sub_path (cr);
644         cairo_move_to (cr, x+w, y+h);
645         cairo_line_to (cr, x, y+h);
646         cairo_arc (cr, x + r, y + r, r, 180 * degrees, 270 * degrees);  //tl
647         cairo_arc (cr, x + w - r, y + r, r, -90 * degrees, 0 * degrees);  //tr
648         cairo_close_path (cr);
649 }
650
651 void
652 Gtkmm2ext::rounded_top_left_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
653 {
654 /*    A****B
655       H    *
656       *    *
657       *    *
658       F****E
659 */
660         cairo_move_to (cr, x+r,y); // Move to A
661         cairo_line_to (cr, x+w,y); // Straight line to B
662         cairo_line_to (cr, x+w,y+h); // Move to E
663         cairo_line_to (cr, x,y+h); // Line to F
664         cairo_line_to (cr, x,y+r); // Line to H
665         cairo_curve_to (cr, x,y,x,y,x+r,y); // Curve to A
666 }
667
668 void
669 Gtkmm2ext::rounded_top_right_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
670 {
671 /*    A****BQ
672       *    C
673       *    *
674       *    *
675       F****E
676 */
677         cairo_move_to (cr, x,y); // Move to A
678         cairo_line_to (cr, x+w-r,y); // Straight line to B
679         cairo_curve_to (cr, x+w,y,x+w,y,x+w,y+r); // Curve to C, Control points are both at Q
680         cairo_line_to (cr, x+w,y+h); // Move to E
681         cairo_line_to (cr, x,y+h); // Line to F
682         cairo_line_to (cr, x,y); // Line to A
683 }
684
685 Glib::RefPtr<Gdk::Window>
686 Gtkmm2ext::window_to_draw_on (Gtk::Widget& w, Gtk::Widget** parent)
687 {
688         if (w.get_has_window()) {
689                 return w.get_window();
690         }
691
692         (*parent) = w.get_parent();
693
694         while (*parent) {
695                 if ((*parent)->get_has_window()) {
696                         return (*parent)->get_window ();
697                 }
698                 (*parent) = (*parent)->get_parent ();
699         }
700
701         return Glib::RefPtr<Gdk::Window> ();
702 }
703
704 int
705 Gtkmm2ext::pixel_width (const string& str, Pango::FontDescription& font)
706 {
707         Glib::RefPtr<Pango::Context> context = Glib::wrap (gdk_pango_context_get());
708         Glib::RefPtr<Pango::Layout> layout = Pango::Layout::create (context);
709
710         layout->set_font_description (font);
711         layout->set_text (str);
712
713         int width, height;
714         Gtkmm2ext::get_ink_pixel_size (layout, width, height);
715
716 #ifdef __APPLE__
717         // Pango returns incorrect text width on some OS X
718         // So we have to make a correction
719         // To determine the correct indent take the largest symbol for which the width is correct
720         // and make the calculation
721         //
722         // see also libs/canvas/text.cc
723         int cor_width;
724         layout->set_text ("H");
725         layout->get_pixel_size (cor_width, height);
726         width += cor_width * 1.5;
727 #endif
728
729         return width;
730 }
731
732 void
733 Gtkmm2ext::pixel_size (const string& str, Pango::FontDescription& font, int& width, int& height)
734 {
735         Gtk::Label foo;
736         Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout ("");
737
738         layout->set_font_description (font);
739         layout->set_text (str);
740
741         Gtkmm2ext::get_ink_pixel_size (layout, width, height);
742 }
743
744 #if 0
745 string
746 Gtkmm2ext::fit_to_pixels (const string& str, int pixel_width, Pango::FontDescription& font, int& actual_width, bool with_ellipses)
747 {
748         /* DECEMBER 2011: THIS PROTOTYPE OF fit_to_pixels() IS NOT USED
749            ANYWHERE AND HAS NOT BEEN TESTED.
750         */
751         Gtk::Label foo;
752         Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout (str);
753         Glib::RefPtr<const Pango::LayoutLine> line;
754
755         layout->set_font_description (font);
756         layout->set_width (pixel_width * PANGO_SCALE);
757
758         if (with_ellipses) {
759                 layout->set_ellipsize (Pango::ELLIPSIZE_END);
760         } else {
761                 layout->set_wrap (Pango::WRAP_CHAR);
762         }
763
764         line = layout->get_line (0);
765
766         /* XXX: might need special care to get the ellipsis character, not sure
767            how that works
768         */      
769
770         string s = string (layout->get_text ().substr(line->get_start_index(), line->get_length()));
771         
772         cerr << "fit to pixels of " << str << " returns " << s << endl;
773
774         return s;
775 }
776 #endif
777
778 /** Try to fit a string into a given horizontal space by ellipsizing it.
779  *  @param cr Cairo context in which the text will be plotted.
780  *  @param name Text.
781  *  @param avail Available horizontal space.
782  *  @return (Text, possibly ellipsized) and (horizontal size of text)
783  */
784
785 std::pair<std::string, double>
786 Gtkmm2ext::fit_to_pixels (cairo_t* cr, std::string name, double avail)
787 {
788         /* XXX hopefully there exists a more efficient way of doing this */
789
790         bool abbreviated = false;
791         uint32_t width = 0;
792
793         while (1) {
794                 cairo_text_extents_t ext;
795                 cairo_text_extents (cr, name.c_str(), &ext);
796
797                 if (ext.width < avail || name.length() <= 4) {
798                         width = ext.width;
799                         break;
800                 }
801
802                 if (abbreviated) {
803                         name = name.substr (0, name.length() - 4) + "...";
804                 } else {
805                         name = name.substr (0, name.length() - 3) + "...";
806                         abbreviated = true;
807                 }
808         }
809
810         return std::make_pair (name, width);
811 }
812
813 Gtk::Label *
814 Gtkmm2ext::left_aligned_label (string const & t)
815 {
816         Gtk::Label* l = new Gtk::Label (t);
817         l->set_alignment (0, 0.5);
818         return l;
819 }
820
821 Gtk::Label *
822 Gtkmm2ext::right_aligned_label (string const & t)
823 {
824         Gtk::Label* l = new Gtk::Label (t);
825         l->set_alignment (1, 0.5);
826         return l;
827 }
828
829 static bool
830 make_null_tooltip (int, int, bool, const Glib::RefPtr<Gtk::Tooltip>& t)
831 {
832         t->set_tip_area (Gdk::Rectangle (0, 0, 0, 0));
833         return true;
834 }
835
836 /** Hackily arrange for the provided widget to have no tooltip,
837  *  and also to stop any other widget from providing one while
838  * the mouse is over w.
839  */
840 void
841 Gtkmm2ext::set_no_tooltip_whatsoever (Gtk::Widget& w)
842 {
843         w.property_has_tooltip() = true;
844         w.signal_query_tooltip().connect (sigc::ptr_fun (make_null_tooltip));
845 }
846
847 void
848 Gtkmm2ext::enable_tooltips ()
849 {
850         gtk_rc_parse_string ("gtk-enable-tooltips = 1");
851 }
852
853 void
854 Gtkmm2ext::disable_tooltips ()
855 {
856         gtk_rc_parse_string ("gtk-enable-tooltips = 0");
857 }
858
859 bool
860 Gtkmm2ext::event_inside_widget_window (Gtk::Widget& widget, GdkEvent* ev)
861 {
862         gdouble evx, evy;
863
864         if (!gdk_event_get_root_coords (ev, &evx, &evy)) {
865                 return false;
866         }
867
868         gint wx;
869         gint wy;
870         gint width, height, depth;
871         gint x, y;
872
873         Glib::RefPtr<Gdk::Window> widget_window = widget.get_window();
874
875         widget_window->get_geometry (x, y, width, height, depth);
876         widget_window->get_root_origin (wx, wy);
877
878         if ((evx >= wx && evx < wx + width) &&
879             (evy >= wy && evy < wy + height)) {
880                 return true;
881         }
882
883         return false;
884 }
885
886 const char*
887 Gtkmm2ext::event_type_string (int event_type)
888 {
889         switch (event_type) {
890         case GDK_NOTHING:
891                 return "nothing";
892         case GDK_DELETE:
893                 return "delete";
894         case GDK_DESTROY:
895                 return "destroy";
896         case GDK_EXPOSE:
897                 return "expose";
898         case GDK_MOTION_NOTIFY:
899                 return "motion_notify";
900         case GDK_BUTTON_PRESS:
901                 return "button_press";
902         case GDK_2BUTTON_PRESS:
903                 return "2button_press";
904         case GDK_3BUTTON_PRESS:
905                 return "3button_press";
906         case GDK_BUTTON_RELEASE:
907                 return "button_release";
908         case GDK_KEY_PRESS:
909                 return "key_press";
910         case GDK_KEY_RELEASE:
911                 return "key_release";
912         case GDK_ENTER_NOTIFY:
913                 return "enter_notify";
914         case GDK_LEAVE_NOTIFY:
915                 return "leave_notify";
916         case GDK_FOCUS_CHANGE:
917                 return "focus_change";
918         case GDK_CONFIGURE:
919                 return "configure";
920         case GDK_MAP:
921                 return "map";
922         case GDK_UNMAP:
923                 return "unmap";
924         case GDK_PROPERTY_NOTIFY:
925                 return "property_notify";
926         case GDK_SELECTION_CLEAR:
927                 return "selection_clear";
928         case GDK_SELECTION_REQUEST:
929                 return "selection_request";
930         case GDK_SELECTION_NOTIFY:
931                 return "selection_notify";
932         case GDK_PROXIMITY_IN:
933                 return "proximity_in";
934         case GDK_PROXIMITY_OUT:
935                 return "proximity_out";
936         case GDK_DRAG_ENTER:
937                 return "drag_enter";
938         case GDK_DRAG_LEAVE:
939                 return "drag_leave";
940         case GDK_DRAG_MOTION:
941                 return "drag_motion";
942         case GDK_DRAG_STATUS:
943                 return "drag_status";
944         case GDK_DROP_START:
945                 return "drop_start";
946         case GDK_DROP_FINISHED:
947                 return "drop_finished";
948         case GDK_CLIENT_EVENT:
949                 return "client_event";
950         case GDK_VISIBILITY_NOTIFY:
951                 return "visibility_notify";
952         case GDK_NO_EXPOSE:
953                 return "no_expose";
954         case GDK_SCROLL:
955                 return "scroll";
956         case GDK_WINDOW_STATE:
957                 return "window_state";
958         case GDK_SETTING:
959                 return "setting";
960         case GDK_OWNER_CHANGE:
961                 return "owner_change";
962         case GDK_GRAB_BROKEN:
963                 return "grab_broken";
964         case GDK_DAMAGE:
965                 return "damage";
966         }
967
968         return "unknown";
969 }