add new Gtkmm2ext::pixel_size() to conveniently get width&height for a given font...
[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 = (ink_rect.get_width() + PANGO_SCALE / 2) / PANGO_SCALE;
58         height = (ink_rect.get_height() + PANGO_SCALE / 2) / PANGO_SCALE;
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 bool
326 Gtkmm2ext::contains_value (Gtk::ComboBoxText& cr, const std::string text)
327 {
328         std::vector<std::string> s;
329         get_popdown_strings (cr, s);
330         return (std::find (s.begin(), s.end(), text) != s.end());
331 }
332
333 bool
334 Gtkmm2ext::set_active_text_if_present (Gtk::ComboBoxText& cr, const std::string text)
335 {
336         if (contains_value(cr, text)) {
337                 cr.set_active_text (text);
338                 return true;
339         }
340         return false;
341 }
342
343 GdkWindow*
344 Gtkmm2ext::get_paned_handle (Gtk::Paned& paned)
345 {
346         return GTK_PANED(paned.gobj())->handle;
347 }
348
349 void
350 Gtkmm2ext::set_decoration (Gtk::Window* win, Gdk::WMDecoration decor)
351 {
352         win->get_window()->set_decorations (decor);
353 }
354
355 void Gtkmm2ext::set_treeview_header_as_default_label(Gtk::TreeViewColumn* c)
356 {
357         gtk_tree_view_column_set_widget( c->gobj(), GTK_WIDGET(0) );
358 }
359
360 void
361 Gtkmm2ext::detach_menu (Gtk::Menu& menu)
362 {
363         /* its possible for a Gtk::Menu to have no gobj() because it has
364            not yet been instantiated. Catch this and provide a safe
365            detach method.
366         */
367         if (menu.gobj()) {
368                 if (menu.get_attach_widget()) {
369                         menu.detach ();
370                 }
371         }
372 }
373
374 bool
375 Gtkmm2ext::possibly_translate_mod_to_make_legal_accelerator (GdkModifierType& mod)
376 {
377 #ifdef GTKOSX
378         /* GTK on OS X is currently (February 2012) setting both
379            the Meta and Mod2 bits in the event modifier state if
380            the Command key is down.
381
382            gtk_accel_groups_activate() does not invoke any of the logic
383            that gtk_window_activate_key() will that sorts out that stupid
384            state of affairs, and as a result it fails to find a match
385            for the key event and the current set of accelerators.
386
387            to fix this, if the meta bit is set, remove the mod2 bit
388            from the modifier. this assumes that our bindings use Primary
389            which will have set the meta bit in the accelerator entry.
390         */
391         if (mod & GDK_META_MASK) {
392                 mod = GdkModifierType (mod & ~GDK_MOD2_MASK);
393         }
394 #endif
395         return true;
396 }
397
398 bool
399 Gtkmm2ext::possibly_translate_keyval_to_make_legal_accelerator (uint32_t& keyval)
400 {
401         int fakekey = GDK_VoidSymbol;
402
403         switch (keyval) {
404         case GDK_Tab:
405         case GDK_ISO_Left_Tab:
406                 fakekey = GDK_nabla;
407                 break;
408
409         case GDK_Up:
410                 fakekey = GDK_uparrow;
411                 break;
412
413         case GDK_Down:
414                 fakekey = GDK_downarrow;
415                 break;
416
417         case GDK_Right:
418                 fakekey = GDK_rightarrow;
419                 break;
420
421         case GDK_Left:
422                 fakekey = GDK_leftarrow;
423                 break;
424
425         case GDK_Return:
426                 fakekey = GDK_3270_Enter;
427                 break;
428
429         case GDK_KP_Enter:
430                 fakekey = GDK_F35;
431                 break;
432
433         default:
434                 break;
435         }
436
437         if (fakekey != GDK_VoidSymbol) {
438                 keyval = fakekey;
439                 return true;
440         }
441
442         return false;
443 }
444
445 uint32_t
446 Gtkmm2ext::possibly_translate_legal_accelerator_to_real_key (uint32_t keyval)
447 {
448         switch (keyval) {
449         case GDK_nabla:
450                 return GDK_Tab;
451                 break;
452
453         case GDK_uparrow:
454                 return GDK_Up;
455                 break;
456
457         case GDK_downarrow:
458                 return GDK_Down;
459                 break;
460
461         case GDK_rightarrow:
462                 return GDK_Right;
463                 break;
464
465         case GDK_leftarrow:
466                 return GDK_Left;
467                 break;
468
469         case GDK_3270_Enter:
470                 return GDK_Return;
471
472         case GDK_F35:
473                 return GDK_KP_Enter;
474                 break;
475         }
476
477         return keyval;
478 }
479
480 int
481 Gtkmm2ext::physical_screen_height (Glib::RefPtr<Gdk::Window> win)
482 {
483         GdkScreen* scr = gdk_screen_get_default();
484
485         if (win) {
486                 GdkRectangle r;
487                 gint monitor = gdk_screen_get_monitor_at_window (scr, win->gobj());
488                 gdk_screen_get_monitor_geometry (scr, monitor, &r);
489                 return r.height;
490         } else {
491                 return gdk_screen_get_height (scr);
492         }
493 }
494
495 int
496 Gtkmm2ext::physical_screen_width (Glib::RefPtr<Gdk::Window> win)
497 {
498         GdkScreen* scr = gdk_screen_get_default();
499         
500         if (win) {
501                 GdkRectangle r;
502                 gint monitor = gdk_screen_get_monitor_at_window (scr, win->gobj());
503                 gdk_screen_get_monitor_geometry (scr, monitor, &r);
504                 return r.width;
505         } else {
506                 return gdk_screen_get_width (scr);
507         }
508 }
509
510 void
511 Gtkmm2ext::container_clear (Gtk::Container& c)
512 {
513         list<Gtk::Widget*> children = c.get_children();
514         for (list<Gtk::Widget*>::iterator child = children.begin(); child != children.end(); ++child) {
515                 c.remove (**child);
516         }
517 }
518
519 void
520 Gtkmm2ext::rounded_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
521 {
522         rounded_rectangle (context->cobj(), x, y, w, h, r);
523 }
524 void
525 Gtkmm2ext::rounded_top_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
526 {
527         rounded_top_rectangle (context->cobj(), x, y, w, h, r);
528 }
529 void
530 Gtkmm2ext::rounded_top_left_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
531 {
532         rounded_top_left_rectangle (context->cobj(), x, y, w, h, r);
533 }
534 void
535 Gtkmm2ext::rounded_top_right_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
536 {
537         rounded_top_right_rectangle (context->cobj(), x, y, w, h, r);
538 }
539 void
540 Gtkmm2ext::rounded_top_half_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
541 {
542         rounded_top_half_rectangle (context->cobj(), x, y, w, h, r);
543 }
544 void
545 Gtkmm2ext::rounded_bottom_half_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
546 {
547         rounded_bottom_half_rectangle (context->cobj(), x, y, w, h, r);
548 }
549
550 void
551 Gtkmm2ext::rounded_left_half_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
552 {
553         rounded_left_half_rectangle (context->cobj(), x, y, w, h, r);
554 }
555
556 void
557 Gtkmm2ext::rounded_right_half_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
558 {
559         rounded_right_half_rectangle (context->cobj(), x, y, w, h, r);
560 }
561
562 void
563 Gtkmm2ext::rounded_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
564 {
565         double degrees = M_PI / 180.0;
566
567         cairo_new_sub_path (cr);
568         cairo_arc (cr, x + w - r, y + r, r, -90 * degrees, 0 * degrees);  //tr
569         cairo_arc (cr, x + w - r, y + h - r, r, 0 * degrees, 90 * degrees);  //br
570         cairo_arc (cr, x + r, y + h - r, r, 90 * degrees, 180 * degrees);  //bl
571         cairo_arc (cr, x + r, y + r, r, 180 * degrees, 270 * degrees);  //tl
572         cairo_close_path (cr);
573 }
574
575 void
576 Gtkmm2ext::rounded_left_half_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
577 {
578         double degrees = M_PI / 180.0;
579
580         cairo_new_sub_path (cr);
581         cairo_line_to (cr, x+w, y); // tr
582         cairo_line_to (cr, x+w, y + h); // br
583         cairo_arc (cr, x + r, y + h - r, r, 90 * degrees, 180 * degrees);  //bl
584         cairo_arc (cr, x + r, y + r, r, 180 * degrees, 270 * degrees);  //tl
585         cairo_close_path (cr);
586 }
587
588 void
589 Gtkmm2ext::rounded_right_half_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
590 {
591         double degrees = M_PI / 180.0;
592
593         cairo_new_sub_path (cr);
594         cairo_arc (cr, x + w - r, y + r, r, -90 * degrees, 0 * degrees);  //tr
595         cairo_arc (cr, x + w - r, y + h - r, r, 0 * degrees, 90 * degrees);  //br
596         cairo_line_to (cr, x, y + h); // bl
597         cairo_line_to (cr, x, y); // tl
598         cairo_close_path (cr);
599 }
600
601 void
602 Gtkmm2ext::rounded_top_half_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
603 {
604         double degrees = M_PI / 180.0;
605
606         cairo_new_sub_path (cr);
607         cairo_move_to (cr, x+w, y+h);
608         cairo_line_to (cr, x, y+h);
609         cairo_arc (cr, x + r, y + r, r, 180 * degrees, 270 * degrees);  //tl
610         cairo_arc (cr, x + w - r, y + r, r, -90 * degrees, 0 * degrees);  //tr
611         cairo_close_path (cr);
612 }
613
614 void
615 Gtkmm2ext::rounded_bottom_half_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
616 {
617         double degrees = M_PI / 180.0;
618
619         cairo_new_sub_path (cr);
620         cairo_move_to (cr, x, y);
621         cairo_line_to (cr, x+w, y);
622         cairo_arc (cr, x + w - r, y + h - r, r, 0 * degrees, 90 * degrees);  //br
623         cairo_arc (cr, x + r, y + h - r, r, 90 * degrees, 180 * degrees);  //bl
624         cairo_close_path (cr);
625 }
626
627
628 void
629 Gtkmm2ext::rounded_top_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
630 {
631         double degrees = M_PI / 180.0;
632
633         cairo_new_sub_path (cr);
634         cairo_move_to (cr, x+w, y+h);
635         cairo_line_to (cr, x, y+h);
636         cairo_arc (cr, x + r, y + r, r, 180 * degrees, 270 * degrees);  //tl
637         cairo_arc (cr, x + w - r, y + r, r, -90 * degrees, 0 * degrees);  //tr
638         cairo_close_path (cr);
639 }
640
641 void
642 Gtkmm2ext::rounded_top_left_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
643 {
644 /*    A****B
645       H    *
646       *    *
647       *    *
648       F****E
649 */
650         cairo_move_to (cr, x+r,y); // Move to A
651         cairo_line_to (cr, x+w,y); // Straight line to B
652         cairo_line_to (cr, x+w,y+h); // Move to E
653         cairo_line_to (cr, x,y+h); // Line to F
654         cairo_line_to (cr, x,y+r); // Line to H
655         cairo_curve_to (cr, x,y,x,y,x+r,y); // Curve to A
656 }
657
658 void
659 Gtkmm2ext::rounded_top_right_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
660 {
661 /*    A****BQ
662       *    C
663       *    *
664       *    *
665       F****E
666 */
667         cairo_move_to (cr, x,y); // Move to A
668         cairo_line_to (cr, x+w-r,y); // Straight line to B
669         cairo_curve_to (cr, x+w,y,x+w,y,x+w,y+r); // Curve to C, Control points are both at Q
670         cairo_line_to (cr, x+w,y+h); // Move to E
671         cairo_line_to (cr, x,y+h); // Line to F
672         cairo_line_to (cr, x,y); // Line to A
673 }
674
675 Glib::RefPtr<Gdk::Window>
676 Gtkmm2ext::window_to_draw_on (Gtk::Widget& w, Gtk::Widget** parent)
677 {
678         if (w.get_has_window()) {
679                 return w.get_window();
680         }
681
682         (*parent) = w.get_parent();
683
684         while (*parent) {
685                 if ((*parent)->get_has_window()) {
686                         return (*parent)->get_window ();
687                 }
688                 (*parent) = (*parent)->get_parent ();
689         }
690
691         return Glib::RefPtr<Gdk::Window> ();
692 }
693
694 int
695 Gtkmm2ext::pixel_width (const string& str, Pango::FontDescription& font)
696 {
697         Gtk::Label foo;
698         Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout ("");
699
700         layout->set_font_description (font);
701         layout->set_text (str);
702
703         int width, height;
704         Gtkmm2ext::get_ink_pixel_size (layout, width, height);
705         return width;
706 }
707
708 void
709 Gtkmm2ext::pixel_size (const string& str, Pango::FontDescription& font, int& width, int& height)
710 {
711         Gtk::Label foo;
712         Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout ("");
713
714         layout->set_font_description (font);
715         layout->set_text (str);
716
717         Gtkmm2ext::get_ink_pixel_size (layout, width, height);
718 }
719
720 #if 0
721 string
722 Gtkmm2ext::fit_to_pixels (const string& str, int pixel_width, Pango::FontDescription& font, int& actual_width, bool with_ellipses)
723 {
724         /* DECEMBER 2011: THIS PROTOTYPE OF fit_to_pixels() IS NOT USED
725            ANYWHERE AND HAS NOT BEEN TESTED.
726         */
727         Gtk::Label foo;
728         Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout (str);
729         Glib::RefPtr<const Pango::LayoutLine> line;
730
731         layout->set_font_description (font);
732         layout->set_width (pixel_width * PANGO_SCALE);
733
734         if (with_ellipses) {
735                 layout->set_ellipsize (Pango::ELLIPSIZE_END);
736         } else {
737                 layout->set_wrap (Pango::WRAP_CHAR);
738         }
739
740         line = layout->get_line (0);
741
742         /* XXX: might need special care to get the ellipsis character, not sure
743            how that works 
744         */      
745
746         string s = string (layout->get_text ().substr(line->get_start_index(), line->get_length()));
747         
748         cerr << "fit to pixels of " << str << " returns " << s << endl;
749
750         return s;
751 }
752 #endif
753
754 /** Try to fit a string into a given horizontal space by ellipsizing it.
755  *  @param cr Cairo context in which the text will be plotted.
756  *  @param name Text.
757  *  @param avail Available horizontal space.
758  *  @return (Text, possibly ellipsized) and (horizontal size of text)
759  */
760
761 std::pair<std::string, double>
762 Gtkmm2ext::fit_to_pixels (cairo_t* cr, std::string name, double avail)
763 {
764         /* XXX hopefully there exists a more efficient way of doing this */
765
766         bool abbreviated = false;
767         uint32_t width = 0;
768
769         while (1) {
770                 cairo_text_extents_t ext;
771                 cairo_text_extents (cr, name.c_str(), &ext);
772
773                 if (ext.width < avail || name.length() <= 4) {
774                         width = ext.width;
775                         break;
776                 }
777
778                 if (abbreviated) {
779                         name = name.substr (0, name.length() - 4) + "...";
780                 } else {
781                         name = name.substr (0, name.length() - 3) + "...";
782                         abbreviated = true;
783                 }
784         }
785
786         return std::make_pair (name, width);
787 }
788
789 Gtk::Label *
790 Gtkmm2ext::left_aligned_label (string const & t)
791 {
792         Gtk::Label* l = new Gtk::Label (t);
793         l->set_alignment (0, 0.5);
794         return l;
795 }
796
797 static bool
798 make_null_tooltip (int, int, bool, const Glib::RefPtr<Gtk::Tooltip>& t)
799 {
800         t->set_tip_area (Gdk::Rectangle (0, 0, 0, 0));
801         return true;
802 }
803
804 /** Hackily arrange for the provided widget to have no tooltip,
805  *  and also to stop any other widget from providing one while
806  * the mouse is over w.
807  */
808 void
809 Gtkmm2ext::set_no_tooltip_whatsoever (Gtk::Widget& w)
810 {
811         w.property_has_tooltip() = true;
812         w.signal_query_tooltip().connect (sigc::ptr_fun (make_null_tooltip));
813 }
814
815 void
816 Gtkmm2ext::enable_tooltips ()
817 {
818         gtk_rc_parse_string ("gtk-enable-tooltips = 1");
819 }
820
821 void
822 Gtkmm2ext::disable_tooltips ()
823 {
824         gtk_rc_parse_string ("gtk-enable-tooltips = 0");
825 }
826
827 bool
828 Gtkmm2ext::event_inside_widget_window (Gtk::Widget& widget, GdkEvent* ev)
829 {
830         gdouble evx, evy;
831
832         if (!gdk_event_get_root_coords (ev, &evx, &evy)) {
833                 return false;
834         }
835         
836         gint wx;
837         gint wy;
838         gint width, height, depth;
839         gint x, y;
840
841         Glib::RefPtr<Gdk::Window> widget_window = widget.get_window();
842
843         widget_window->get_geometry (x, y, width, height, depth);
844         widget_window->get_root_origin (wx, wy);
845         
846         if ((evx >= wx && evx < wx + width) && 
847             (evy >= wy && evy < wy + height)) {
848                 return true;
849         } 
850
851         return false;
852 }
853
854 const char*
855 Gtkmm2ext::event_type_string (int event_type)
856 {
857         switch (event_type) {
858         case GDK_NOTHING:
859                 return "nothing";
860         case GDK_DELETE:
861                 return "delete";
862         case GDK_DESTROY:
863                 return "destroy";
864         case GDK_EXPOSE:
865                 return "expose";
866         case GDK_MOTION_NOTIFY:
867                 return "motion_notify";
868         case GDK_BUTTON_PRESS:
869                 return "button_press";
870         case GDK_2BUTTON_PRESS:
871                 return "2button_press";
872         case GDK_3BUTTON_PRESS:
873                 return "3button_press";
874         case GDK_BUTTON_RELEASE:
875                 return "button_release";
876         case GDK_KEY_PRESS:
877                 return "key_press";
878         case GDK_KEY_RELEASE:
879                 return "key_release";
880         case GDK_ENTER_NOTIFY:
881                 return "enter_notify";
882         case GDK_LEAVE_NOTIFY:
883                 return "leave_notify";
884         case GDK_FOCUS_CHANGE:
885                 return "focus_change";
886         case GDK_CONFIGURE:
887                 return "configure";
888         case GDK_MAP:
889                 return "map";
890         case GDK_UNMAP:
891                 return "unmap";
892         case GDK_PROPERTY_NOTIFY:
893                 return "property_notify";
894         case GDK_SELECTION_CLEAR:
895                 return "selection_clear";
896         case GDK_SELECTION_REQUEST:
897                 return "selection_request";
898         case GDK_SELECTION_NOTIFY:
899                 return "selection_notify";
900         case GDK_PROXIMITY_IN:
901                 return "proximity_in";
902         case GDK_PROXIMITY_OUT:
903                 return "proximity_out";
904         case GDK_DRAG_ENTER:
905                 return "drag_enter";
906         case GDK_DRAG_LEAVE:
907                 return "drag_leave";
908         case GDK_DRAG_MOTION:
909                 return "drag_motion";
910         case GDK_DRAG_STATUS:
911                 return "drag_status";
912         case GDK_DROP_START:
913                 return "drop_start";
914         case GDK_DROP_FINISHED:
915                 return "drop_finished";
916         case GDK_CLIENT_EVENT:
917                 return "client_event";
918         case GDK_VISIBILITY_NOTIFY:
919                 return "visibility_notify";
920         case GDK_NO_EXPOSE:
921                 return "no_expose";
922         case GDK_SCROLL:
923                 return "scroll";
924         case GDK_WINDOW_STATE:
925                 return "window_state";
926         case GDK_SETTING:
927                 return "setting";
928         case GDK_OWNER_CHANGE:
929                 return "owner_change";
930         case GDK_GRAB_BROKEN:
931                 return "grab_broken";
932         case GDK_DAMAGE:
933                 return "damage";
934         }
935
936         return "unknown";
937 }