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