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