provide Tabbable::change_visibility(), which has slightly odd semantics that are...
[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         if (clip_width <= 0 || clip_height <= 0) {
276                 /* negative values mean padding around natural size */
277                 int width, height;
278                 pixel_size (name, font, width, height);
279                 if (clip_width <= 0) {
280                         clip_width = width - clip_width;
281                 }
282                 if (clip_height <= 0) {
283                         clip_height = height - clip_height;
284                 }
285         }
286
287         Glib::RefPtr<Gdk::Pixbuf> buf = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB, true, 8, clip_width, clip_height);
288
289         cairo_surface_t* surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, clip_width, clip_height);
290         cairo_t* cr = cairo_create (surface);
291         cairo_text_extents_t te;
292
293         cairo_set_source_rgba (cr, fg.get_red_p(), fg.get_green_p(), fg.get_blue_p(), 1.0);
294         cairo_select_font_face (cr, font.get_family().c_str(),
295                                 CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
296         cairo_set_font_size (cr,  font.get_size() / Pango::SCALE);
297         cairo_text_extents (cr, name.c_str(), &te);
298
299         cairo_move_to (cr, 0.5, int (0.5 - te.height / 2 - te.y_bearing + clip_height / 2));
300         cairo_show_text (cr, name.c_str());
301
302         convert_bgra_to_rgba(cairo_image_surface_get_data (surface), buf->get_pixels(), clip_width, clip_height);
303
304         cairo_destroy(cr);
305         cairo_surface_destroy(surface);
306
307         return buf;
308 }
309
310 void
311 Gtkmm2ext::set_popdown_strings (Gtk::ComboBoxText& cr, const vector<string>& strings)
312 {
313         vector<string>::const_iterator i;
314
315         cr.clear ();
316
317         for (i = strings.begin(); i != strings.end(); ++i) {
318                 cr.append_text (*i);
319         }
320 }
321
322 void
323 Gtkmm2ext::get_popdown_strings (Gtk::ComboBoxText& cr, std::vector<std::string>& strings)
324 {
325         strings.clear ();
326         Glib::RefPtr<const Gtk::TreeModel> m = cr.get_model();
327         if (!m) {
328                 return;
329         }
330         for(Gtk::TreeModel::iterator i = m->children().begin(); i != m->children().end(); ++i) {
331                 Glib::ustring txt;
332                 (*i)->get_value(0, txt);
333                 strings.push_back (txt);
334         }
335 }
336
337 size_t
338 Gtkmm2ext::get_popdown_string_count (Gtk::ComboBoxText& cr)
339 {
340         Glib::RefPtr<const Gtk::TreeModel> m = cr.get_model();
341         if (!m) {
342                 return 0;
343         }
344         return m->children().size();
345 }
346
347 bool
348 Gtkmm2ext::contains_value (Gtk::ComboBoxText& cr, const std::string text)
349 {
350         std::vector<std::string> s;
351         get_popdown_strings (cr, s);
352         return (std::find (s.begin(), s.end(), text) != s.end());
353 }
354
355 bool
356 Gtkmm2ext::set_active_text_if_present (Gtk::ComboBoxText& cr, const std::string text)
357 {
358         if (contains_value(cr, text)) {
359                 cr.set_active_text (text);
360                 return true;
361         }
362         return false;
363 }
364
365 GdkWindow*
366 Gtkmm2ext::get_paned_handle (Gtk::Paned& paned)
367 {
368         return GTK_PANED(paned.gobj())->handle;
369 }
370
371 void
372 Gtkmm2ext::set_decoration (Gtk::Window* win, Gdk::WMDecoration decor)
373 {
374         win->get_window()->set_decorations (decor);
375 }
376
377 void Gtkmm2ext::set_treeview_header_as_default_label(Gtk::TreeViewColumn* c)
378 {
379         gtk_tree_view_column_set_widget( c->gobj(), GTK_WIDGET(0) );
380 }
381
382 void
383 Gtkmm2ext::detach_menu (Gtk::Menu& menu)
384 {
385         /* its possible for a Gtk::Menu to have no gobj() because it has
386            not yet been instantiated. Catch this and provide a safe
387            detach method.
388         */
389         if (menu.gobj()) {
390                 if (menu.get_attach_widget()) {
391                         menu.detach ();
392                 }
393         }
394 }
395
396 bool
397 Gtkmm2ext::possibly_translate_keyval_to_make_legal_accelerator (uint32_t& keyval)
398 {
399         int fakekey = GDK_VoidSymbol;
400
401         switch (keyval) {
402         case GDK_Tab:
403         case GDK_ISO_Left_Tab:
404                 fakekey = GDK_nabla;
405                 break;
406
407         case GDK_Up:
408                 fakekey = GDK_uparrow;
409                 break;
410
411         case GDK_Down:
412                 fakekey = GDK_downarrow;
413                 break;
414
415         case GDK_Right:
416                 fakekey = GDK_rightarrow;
417                 break;
418
419         case GDK_Left:
420                 fakekey = GDK_leftarrow;
421                 break;
422
423         case GDK_Return:
424                 fakekey = GDK_3270_Enter;
425                 break;
426
427         case GDK_KP_Enter:
428                 fakekey = GDK_F35;
429                 break;
430
431         default:
432                 break;
433         }
434
435         if (fakekey != GDK_VoidSymbol) {
436                 keyval = fakekey;
437                 return true;
438         }
439
440         return false;
441 }
442
443 uint32_t
444 Gtkmm2ext::possibly_translate_legal_accelerator_to_real_key (uint32_t keyval)
445 {
446         switch (keyval) {
447         case GDK_nabla:
448                 return GDK_Tab;
449                 break;
450
451         case GDK_uparrow:
452                 return GDK_Up;
453                 break;
454
455         case GDK_downarrow:
456                 return GDK_Down;
457                 break;
458
459         case GDK_rightarrow:
460                 return GDK_Right;
461                 break;
462
463         case GDK_leftarrow:
464                 return GDK_Left;
465                 break;
466
467         case GDK_3270_Enter:
468                 return GDK_Return;
469
470         case GDK_F35:
471                 return GDK_KP_Enter;
472                 break;
473         }
474
475         return keyval;
476 }
477
478 int
479 Gtkmm2ext::physical_screen_height (Glib::RefPtr<Gdk::Window> win)
480 {
481         GdkScreen* scr = gdk_screen_get_default();
482
483         if (win) {
484                 GdkRectangle r;
485                 gint monitor = gdk_screen_get_monitor_at_window (scr, win->gobj());
486                 gdk_screen_get_monitor_geometry (scr, monitor, &r);
487                 return r.height;
488         } else {
489                 return gdk_screen_get_height (scr);
490         }
491 }
492
493 int
494 Gtkmm2ext::physical_screen_width (Glib::RefPtr<Gdk::Window> win)
495 {
496         GdkScreen* scr = gdk_screen_get_default();
497
498         if (win) {
499                 GdkRectangle r;
500                 gint monitor = gdk_screen_get_monitor_at_window (scr, win->gobj());
501                 gdk_screen_get_monitor_geometry (scr, monitor, &r);
502                 return r.width;
503         } else {
504                 return gdk_screen_get_width (scr);
505         }
506 }
507
508 void
509 Gtkmm2ext::container_clear (Gtk::Container& c)
510 {
511         list<Gtk::Widget*> children = c.get_children();
512         for (list<Gtk::Widget*>::iterator child = children.begin(); child != children.end(); ++child) {
513                 c.remove (**child);
514         }
515 }
516
517 void
518 Gtkmm2ext::rounded_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
519 {
520         rounded_rectangle (context->cobj(), x, y, w, h, r);
521 }
522 void
523 Gtkmm2ext::rounded_top_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
524 {
525         rounded_top_rectangle (context->cobj(), x, y, w, h, r);
526 }
527 void
528 Gtkmm2ext::rounded_top_left_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
529 {
530         rounded_top_left_rectangle (context->cobj(), x, y, w, h, r);
531 }
532 void
533 Gtkmm2ext::rounded_top_right_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
534 {
535         rounded_top_right_rectangle (context->cobj(), x, y, w, h, r);
536 }
537 void
538 Gtkmm2ext::rounded_top_half_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
539 {
540         rounded_top_half_rectangle (context->cobj(), x, y, w, h, r);
541 }
542 void
543 Gtkmm2ext::rounded_bottom_half_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
544 {
545         rounded_bottom_half_rectangle (context->cobj(), x, y, w, h, r);
546 }
547
548 void
549 Gtkmm2ext::rounded_left_half_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
550 {
551         rounded_left_half_rectangle (context->cobj(), x, y, w, h, r);
552 }
553
554 void
555 Gtkmm2ext::rounded_right_half_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
556 {
557         rounded_right_half_rectangle (context->cobj(), x, y, w, h, r);
558 }
559
560 void
561 Gtkmm2ext::rounded_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
562 {
563         double degrees = M_PI / 180.0;
564
565         cairo_new_sub_path (cr);
566         cairo_arc (cr, x + w - r, y + r, r, -90 * degrees, 0 * degrees);  //tr
567         cairo_arc (cr, x + w - r, y + h - r, r, 0 * degrees, 90 * degrees);  //br
568         cairo_arc (cr, x + r, y + h - r, r, 90 * degrees, 180 * degrees);  //bl
569         cairo_arc (cr, x + r, y + r, r, 180 * degrees, 270 * degrees);  //tl
570         cairo_close_path (cr);
571 }
572
573 void
574 Gtkmm2ext::rounded_left_half_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
575 {
576         double degrees = M_PI / 180.0;
577
578         cairo_new_sub_path (cr);
579         cairo_line_to (cr, x+w, y); // tr
580         cairo_line_to (cr, x+w, y + h); // br
581         cairo_arc (cr, x + r, y + h - r, r, 90 * degrees, 180 * degrees);  //bl
582         cairo_arc (cr, x + r, y + r, r, 180 * degrees, 270 * degrees);  //tl
583         cairo_close_path (cr);
584 }
585
586 void
587 Gtkmm2ext::rounded_right_half_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
588 {
589         double degrees = M_PI / 180.0;
590
591         cairo_new_sub_path (cr);
592         cairo_arc (cr, x + w - r, y + r, r, -90 * degrees, 0 * degrees);  //tr
593         cairo_arc (cr, x + w - r, y + h - r, r, 0 * degrees, 90 * degrees);  //br
594         cairo_line_to (cr, x, y + h); // bl
595         cairo_line_to (cr, x, y); // tl
596         cairo_close_path (cr);
597 }
598
599 void
600 Gtkmm2ext::rounded_top_half_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
601 {
602         double degrees = M_PI / 180.0;
603
604         cairo_new_sub_path (cr);
605         cairo_move_to (cr, x+w, y+h);
606         cairo_line_to (cr, x, y+h);
607         cairo_arc (cr, x + r, y + r, r, 180 * degrees, 270 * degrees);  //tl
608         cairo_arc (cr, x + w - r, y + r, r, -90 * degrees, 0 * degrees);  //tr
609         cairo_close_path (cr);
610 }
611
612 void
613 Gtkmm2ext::rounded_bottom_half_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
614 {
615         double degrees = M_PI / 180.0;
616
617         cairo_new_sub_path (cr);
618         cairo_move_to (cr, x, y);
619         cairo_line_to (cr, x+w, y);
620         cairo_arc (cr, x + w - r, y + h - r, r, 0 * degrees, 90 * degrees);  //br
621         cairo_arc (cr, x + r, y + h - r, r, 90 * degrees, 180 * degrees);  //bl
622         cairo_close_path (cr);
623 }
624
625
626 void
627 Gtkmm2ext::rounded_top_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
628 {
629         double degrees = M_PI / 180.0;
630
631         cairo_new_sub_path (cr);
632         cairo_move_to (cr, x+w, y+h);
633         cairo_line_to (cr, x, y+h);
634         cairo_arc (cr, x + r, y + r, r, 180 * degrees, 270 * degrees);  //tl
635         cairo_arc (cr, x + w - r, y + r, r, -90 * degrees, 0 * degrees);  //tr
636         cairo_close_path (cr);
637 }
638
639 void
640 Gtkmm2ext::rounded_top_left_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
641 {
642 /*    A****B
643       H    *
644       *    *
645       *    *
646       F****E
647 */
648         cairo_move_to (cr, x+r,y); // Move to A
649         cairo_line_to (cr, x+w,y); // Straight line to B
650         cairo_line_to (cr, x+w,y+h); // Move to E
651         cairo_line_to (cr, x,y+h); // Line to F
652         cairo_line_to (cr, x,y+r); // Line to H
653         cairo_curve_to (cr, x,y,x,y,x+r,y); // Curve to A
654 }
655
656 void
657 Gtkmm2ext::rounded_top_right_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
658 {
659 /*    A****BQ
660       *    C
661       *    *
662       *    *
663       F****E
664 */
665         cairo_move_to (cr, x,y); // Move to A
666         cairo_line_to (cr, x+w-r,y); // Straight line to B
667         cairo_curve_to (cr, x+w,y,x+w,y,x+w,y+r); // Curve to C, Control points are both at Q
668         cairo_line_to (cr, x+w,y+h); // Move to E
669         cairo_line_to (cr, x,y+h); // Line to F
670         cairo_line_to (cr, x,y); // Line to A
671 }
672
673 Glib::RefPtr<Gdk::Window>
674 Gtkmm2ext::window_to_draw_on (Gtk::Widget& w, Gtk::Widget** parent)
675 {
676         if (w.get_has_window()) {
677                 return w.get_window();
678         }
679
680         (*parent) = w.get_parent();
681
682         while (*parent) {
683                 if ((*parent)->get_has_window()) {
684                         return (*parent)->get_window ();
685                 }
686                 (*parent) = (*parent)->get_parent ();
687         }
688
689         return Glib::RefPtr<Gdk::Window> ();
690 }
691
692 int
693 Gtkmm2ext::pixel_width (const string& str, const Pango::FontDescription& font)
694 {
695         Glib::RefPtr<Pango::Context> context = Glib::wrap (gdk_pango_context_get());
696         Glib::RefPtr<Pango::Layout> layout = Pango::Layout::create (context);
697
698         layout->set_font_description (font);
699         layout->set_text (str);
700
701         int width, height;
702         Gtkmm2ext::get_ink_pixel_size (layout, width, height);
703
704 #ifdef __APPLE__
705         // Pango returns incorrect text width on some OS X
706         // So we have to make a correction
707         // To determine the correct indent take the largest symbol for which the width is correct
708         // and make the calculation
709         //
710         // see also libs/canvas/text.cc
711         int cor_width;
712         layout->set_text ("H");
713         layout->get_pixel_size (cor_width, height);
714         width += cor_width * 1.5;
715 #endif
716
717         return width;
718 }
719
720 void
721 Gtkmm2ext::pixel_size (const string& str, const Pango::FontDescription& font, int& width, int& height)
722 {
723         Gtk::Label foo;
724         Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout ("");
725
726         layout->set_font_description (font);
727         layout->set_text (str);
728
729         Gtkmm2ext::get_ink_pixel_size (layout, width, height);
730 }
731
732 #if 0
733 string
734 Gtkmm2ext::fit_to_pixels (const string& str, int pixel_width, Pango::FontDescription& font, int& actual_width, bool with_ellipses)
735 {
736         /* DECEMBER 2011: THIS PROTOTYPE OF fit_to_pixels() IS NOT USED
737            ANYWHERE AND HAS NOT BEEN TESTED.
738         */
739         Gtk::Label foo;
740         Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout (str);
741         Glib::RefPtr<const Pango::LayoutLine> line;
742
743         layout->set_font_description (font);
744         layout->set_width (pixel_width * PANGO_SCALE);
745
746         if (with_ellipses) {
747                 layout->set_ellipsize (Pango::ELLIPSIZE_END);
748         } else {
749                 layout->set_wrap (Pango::WRAP_CHAR);
750         }
751
752         line = layout->get_line (0);
753
754         /* XXX: might need special care to get the ellipsis character, not sure
755            how that works
756         */
757
758         string s = string (layout->get_text ().substr(line->get_start_index(), line->get_length()));
759
760         cerr << "fit to pixels of " << str << " returns " << s << endl;
761
762         return s;
763 }
764 #endif
765
766 /** Try to fit a string into a given horizontal space by ellipsizing it.
767  *  @param cr Cairo context in which the text will be plotted.
768  *  @param name Text.
769  *  @param avail Available horizontal space.
770  *  @return (Text, possibly ellipsized) and (horizontal size of text)
771  */
772
773 std::pair<std::string, double>
774 Gtkmm2ext::fit_to_pixels (cairo_t* cr, std::string name, double avail)
775 {
776         /* XXX hopefully there exists a more efficient way of doing this */
777
778         bool abbreviated = false;
779         uint32_t width = 0;
780
781         while (1) {
782                 cairo_text_extents_t ext;
783                 cairo_text_extents (cr, name.c_str(), &ext);
784
785                 if (ext.width < avail || name.length() <= 4) {
786                         width = ext.width;
787                         break;
788                 }
789
790                 if (abbreviated) {
791                         name = name.substr (0, name.length() - 4) + "...";
792                 } else {
793                         name = name.substr (0, name.length() - 3) + "...";
794                         abbreviated = true;
795                 }
796         }
797
798         return std::make_pair (name, width);
799 }
800
801 Gtk::Label *
802 Gtkmm2ext::left_aligned_label (string const & t)
803 {
804         Gtk::Label* l = new Gtk::Label (t);
805         l->set_alignment (0, 0.5);
806         return l;
807 }
808
809 Gtk::Label *
810 Gtkmm2ext::right_aligned_label (string const & t)
811 {
812         Gtk::Label* l = new Gtk::Label (t);
813         l->set_alignment (1, 0.5);
814         return l;
815 }
816
817 static bool
818 make_null_tooltip (int, int, bool, const Glib::RefPtr<Gtk::Tooltip>& t)
819 {
820         t->set_tip_area (Gdk::Rectangle (0, 0, 0, 0));
821         return true;
822 }
823
824 /** Hackily arrange for the provided widget to have no tooltip,
825  *  and also to stop any other widget from providing one while
826  * the mouse is over w.
827  */
828 void
829 Gtkmm2ext::set_no_tooltip_whatsoever (Gtk::Widget& w)
830 {
831         w.property_has_tooltip() = true;
832         w.signal_query_tooltip().connect (sigc::ptr_fun (make_null_tooltip));
833 }
834
835 void
836 Gtkmm2ext::enable_tooltips ()
837 {
838         gtk_rc_parse_string ("gtk-enable-tooltips = 1");
839 }
840
841 void
842 Gtkmm2ext::disable_tooltips ()
843 {
844         gtk_rc_parse_string ("gtk-enable-tooltips = 0");
845 }
846
847 bool
848 Gtkmm2ext::event_inside_widget_window (Gtk::Widget& widget, GdkEvent* ev)
849 {
850         gdouble evx, evy;
851
852         if (!gdk_event_get_root_coords (ev, &evx, &evy)) {
853                 return false;
854         }
855
856         gint wx;
857         gint wy;
858         gint width, height, depth;
859         gint x, y;
860
861         Glib::RefPtr<Gdk::Window> widget_window = widget.get_window();
862
863         widget_window->get_geometry (x, y, width, height, depth);
864         widget_window->get_root_origin (wx, wy);
865
866         if ((evx >= wx && evx < wx + width) &&
867             (evy >= wy && evy < wy + height)) {
868                 return true;
869         }
870
871         return false;
872 }
873
874 const char*
875 Gtkmm2ext::event_type_string (int event_type)
876 {
877         switch (event_type) {
878         case GDK_NOTHING:
879                 return "nothing";
880         case GDK_DELETE:
881                 return "delete";
882         case GDK_DESTROY:
883                 return "destroy";
884         case GDK_EXPOSE:
885                 return "expose";
886         case GDK_MOTION_NOTIFY:
887                 return "motion_notify";
888         case GDK_BUTTON_PRESS:
889                 return "button_press";
890         case GDK_2BUTTON_PRESS:
891                 return "2button_press";
892         case GDK_3BUTTON_PRESS:
893                 return "3button_press";
894         case GDK_BUTTON_RELEASE:
895                 return "button_release";
896         case GDK_KEY_PRESS:
897                 return "key_press";
898         case GDK_KEY_RELEASE:
899                 return "key_release";
900         case GDK_ENTER_NOTIFY:
901                 return "enter_notify";
902         case GDK_LEAVE_NOTIFY:
903                 return "leave_notify";
904         case GDK_FOCUS_CHANGE:
905                 return "focus_change";
906         case GDK_CONFIGURE:
907                 return "configure";
908         case GDK_MAP:
909                 return "map";
910         case GDK_UNMAP:
911                 return "unmap";
912         case GDK_PROPERTY_NOTIFY:
913                 return "property_notify";
914         case GDK_SELECTION_CLEAR:
915                 return "selection_clear";
916         case GDK_SELECTION_REQUEST:
917                 return "selection_request";
918         case GDK_SELECTION_NOTIFY:
919                 return "selection_notify";
920         case GDK_PROXIMITY_IN:
921                 return "proximity_in";
922         case GDK_PROXIMITY_OUT:
923                 return "proximity_out";
924         case GDK_DRAG_ENTER:
925                 return "drag_enter";
926         case GDK_DRAG_LEAVE:
927                 return "drag_leave";
928         case GDK_DRAG_MOTION:
929                 return "drag_motion";
930         case GDK_DRAG_STATUS:
931                 return "drag_status";
932         case GDK_DROP_START:
933                 return "drop_start";
934         case GDK_DROP_FINISHED:
935                 return "drop_finished";
936         case GDK_CLIENT_EVENT:
937                 return "client_event";
938         case GDK_VISIBILITY_NOTIFY:
939                 return "visibility_notify";
940         case GDK_NO_EXPOSE:
941                 return "no_expose";
942         case GDK_SCROLL:
943                 return "scroll";
944         case GDK_WINDOW_STATE:
945                 return "window_state";
946         case GDK_SETTING:
947                 return "setting";
948         case GDK_OWNER_CHANGE:
949                 return "owner_change";
950         case GDK_GRAB_BROKEN:
951                 return "grab_broken";
952         case GDK_DAMAGE:
953                 return "damage";
954         }
955
956         return "unknown";
957 }
958
959 std::string
960 Gtkmm2ext::markup_escape_text (std::string const& s)
961 {
962         return Glib::Markup::escape_text (s);
963 }
964
965 void
966 Gtkmm2ext::add_volume_shortcuts (Gtk::FileChooser& c)
967 {
968 #ifdef __APPLE__
969         try {
970                 /* This is a first order approach, listing all mounted volumes (incl network).
971                  * One could use `diskutil` or `mount` to query local disks only, or
972                  * something even fancier if deemed appropriate.
973                  */
974                 Glib::Dir dir("/Volumes");
975                 for (Glib::DirIterator di = dir.begin(); di != dir.end(); di++) {
976                         string fullpath = Glib::build_filename ("/Volumes", *di);
977                         if (!Glib::file_test (fullpath, Glib::FILE_TEST_IS_DIR)) continue;
978
979                         try { /* add_shortcut_folder throws an exception if the folder being added already has a shortcut */
980                                 c.add_shortcut_folder (fullpath);
981                         }
982                         catch (Glib::Error& e) {
983                                 std::cerr << "add_shortcut_folder() threw Glib::Error: " << e.what() << std::endl;
984                         }
985                 }
986         }
987         catch (Glib::FileError& e) {
988                 std::cerr << "listing /Volumnes failed: " << e.what() << std::endl;
989         }
990 #endif
991 }