Merge branch 'master' into windows+cc
[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 #endif
46 }
47
48 void
49 Gtkmm2ext::get_ink_pixel_size (Glib::RefPtr<Pango::Layout> layout,
50                                int& width,
51                                int& height)
52 {
53         Pango::Rectangle ink_rect = layout->get_ink_extents ();
54         
55         width = (ink_rect.get_width() + PANGO_SCALE / 2) / PANGO_SCALE;
56         height = (ink_rect.get_height() + PANGO_SCALE / 2) / PANGO_SCALE;
57 }
58
59 void
60 get_pixel_size (Glib::RefPtr<Pango::Layout> layout,
61                                int& width,
62                                int& height)
63 {
64         layout->get_pixel_size (width, height);
65 }
66
67 void
68 Gtkmm2ext::set_size_request_to_display_given_text (Gtk::Widget &w, const gchar *text,
69                                                    gint hpadding, gint vpadding)
70 {
71         int width, height;
72         w.ensure_style ();
73         
74         get_pixel_size (w.create_pango_layout (text), width, height);
75         w.set_size_request(width + hpadding, height + vpadding);
76 }
77
78 void
79 Gtkmm2ext::set_size_request_to_display_given_text (Gtk::Widget &w, 
80                                                    const std::vector<std::string>& strings,
81                                                    gint hpadding, gint vpadding)
82 {
83         int width, height;
84         int width_max = 0;
85         int height_max = 0;
86         w.ensure_style ();
87         vector<string> copy;
88         const vector<string>* to_use;
89         vector<string>::const_iterator i;
90
91         for (i = strings.begin(); i != strings.end(); ++i) {
92                 if ((*i).find_first_of ("gy") != string::npos) {
93                         /* contains a descender */
94                         break;
95                 }
96         }
97         
98         if (i == strings.end()) {
99                 /* make a copy of the strings then add one that has a descender */
100                 copy = strings;
101                 copy.push_back ("g");
102                 to_use = &copy;
103         } else {
104                 to_use = &strings;
105         }
106         
107         for (vector<string>::const_iterator i = to_use->begin(); i != to_use->end(); ++i) {
108                 get_pixel_size (w.create_pango_layout (*i), width, height);
109                 width_max = max(width_max,width);
110                 height_max = max(height_max, height);
111         }
112
113         w.set_size_request(width_max + hpadding, height_max + vpadding);
114 }
115
116 static inline guint8
117 demultiply_alpha (guint8 src,
118                   guint8 alpha)
119 {
120         /* cairo pixel buffer data contains RGB values with the alpha
121            values premultiplied.
122
123            GdkPixbuf pixel buffer data contains RGB values without the
124            alpha value applied.
125
126            this removes the alpha component from the cairo version and
127            returns the GdkPixbuf version.
128         */
129         return alpha ? ((guint (src) << 8) - src) / alpha : 0;
130 }
131
132 void
133 Gtkmm2ext::convert_bgra_to_rgba (guint8 const* src,
134                                  guint8*       dst,
135                                  int           width,
136                                  int           height)
137 {
138         guint8 const* src_pixel = src;
139         guint8*       dst_pixel = dst;
140         
141         /* cairo pixel data is endian-dependent ARGB with A in the most significant 8 bits,
142            with premultipled alpha values (see preceding function)
143
144            GdkPixbuf pixel data is non-endian-dependent RGBA with R in the lowest addressable
145            8 bits, and non-premultiplied alpha values.
146
147            convert from the cairo values to the GdkPixbuf ones.
148         */
149
150         for (int y = 0; y < height; y++) {
151                 for (int x = 0; x < width; x++) {
152 #if G_BYTE_ORDER == G_LITTLE_ENDIAN
153                         /* Cairo [ B G R A ] is actually  [ B G R A ] in memory SOURCE
154                                                             0 1 2 3
155                            Pixbuf [ R G B A ] is actually [ R G B A ] in memory DEST
156                         */
157                         dst_pixel[0] = demultiply_alpha (src_pixel[2],
158                                                          src_pixel[3]); // R [0] <= [ 2 ]
159                         dst_pixel[1] = demultiply_alpha (src_pixel[1],
160                                                          src_pixel[3]); // G [1] <= [ 1 ]
161                         dst_pixel[2] = demultiply_alpha (src_pixel[0],  
162                                                          src_pixel[3]); // B [2] <= [ 0 ]
163                         dst_pixel[3] = src_pixel[3]; // alpha
164                         
165 #elif G_BYTE_ORDER == G_BIG_ENDIAN
166                         /* Cairo [ B G R A ] is actually  [ A R G B ] in memory SOURCE
167                                                             0 1 2 3
168                            Pixbuf [ R G B A ] is actually [ R G B A ] in memory DEST
169                         */
170                         dst_pixel[0] = demultiply_alpha (src_pixel[1],
171                                                          src_pixel[0]); // R [0] <= [ 1 ]
172                         dst_pixel[1] = demultiply_alpha (src_pixel[2],
173                                                          src_pixel[0]); // G [1] <= [ 2 ]
174                         dst_pixel[2] = demultiply_alpha (src_pixel[3],
175                                                          src_pixel[0]); // B [2] <= [ 3 ]
176                         dst_pixel[3] = src_pixel[0]; // alpha
177                         
178 #else
179 #error ardour does not currently support PDP-endianess
180 #endif                  
181                         
182                         dst_pixel += 4;
183                         src_pixel += 4;
184                 }
185         }
186 }
187
188 Glib::RefPtr<Gdk::Pixbuf>
189 Gtkmm2ext::pixbuf_from_string(const string& name, const Pango::FontDescription& font, int clip_width, int clip_height, Gdk::Color fg)
190 {
191         static Glib::RefPtr<Gdk::Pixbuf>* empty_pixbuf = 0;
192
193         if (name.empty()) {
194                 if (empty_pixbuf == 0) {
195                         empty_pixbuf = new Glib::RefPtr<Gdk::Pixbuf>;
196                         *empty_pixbuf = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB, true, 8, clip_width, clip_height);
197                 }
198                 return *empty_pixbuf;
199         }
200
201         Glib::RefPtr<Gdk::Pixbuf> buf = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB, true, 8, clip_width, clip_height);
202
203         cairo_surface_t* surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, clip_width, clip_height);
204         cairo_t* cr = cairo_create (surface);
205         cairo_text_extents_t te;
206         
207         cairo_set_source_rgba (cr, fg.get_red_p(), fg.get_green_p(), fg.get_blue_p(), 1.0);
208         cairo_select_font_face (cr, font.get_family().c_str(),
209                                 CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
210         cairo_set_font_size (cr,  font.get_size() / Pango::SCALE);
211         cairo_text_extents (cr, name.c_str(), &te);
212         
213         cairo_move_to (cr, 0.5, int (0.5 - te.height / 2 - te.y_bearing + clip_height / 2));
214         cairo_show_text (cr, name.c_str());
215         
216         convert_bgra_to_rgba(cairo_image_surface_get_data (surface), buf->get_pixels(), clip_width, clip_height);
217
218         cairo_destroy(cr);
219         cairo_surface_destroy(surface);
220
221         return buf;
222 }
223
224 void
225 Gtkmm2ext::set_popdown_strings (Gtk::ComboBoxText& cr, const vector<string>& strings)
226 {
227         vector<string>::const_iterator i;
228
229         cr.clear ();
230
231         for (i = strings.begin(); i != strings.end(); ++i) {
232                 cr.append_text (*i);
233         }
234 }
235
236 GdkWindow*
237 Gtkmm2ext::get_paned_handle (Gtk::Paned& paned)
238 {
239         return GTK_PANED(paned.gobj())->handle;
240 }
241
242 void
243 Gtkmm2ext::set_decoration (Gtk::Window* win, Gdk::WMDecoration decor)
244 {
245         win->get_window()->set_decorations (decor);
246 }
247
248 void Gtkmm2ext::set_treeview_header_as_default_label(Gtk::TreeViewColumn* c)
249 {
250         gtk_tree_view_column_set_widget( c->gobj(), GTK_WIDGET(0) );
251 }
252
253 void
254 Gtkmm2ext::detach_menu (Gtk::Menu& menu)
255 {
256         /* its possible for a Gtk::Menu to have no gobj() because it has
257            not yet been instantiated. Catch this and provide a safe
258            detach method.
259         */
260         if (menu.gobj()) {
261                 if (menu.get_attach_widget()) {
262                         menu.detach ();
263                 }
264         }
265 }
266
267 bool
268 Gtkmm2ext::possibly_translate_keyval_to_make_legal_accelerator (uint32_t& keyval)
269 {
270         int fakekey = GDK_VoidSymbol;
271
272         switch (keyval) {
273         case GDK_Tab:
274         case GDK_ISO_Left_Tab:
275                 fakekey = GDK_nabla;
276                 break;
277
278         case GDK_Up:
279                 fakekey = GDK_uparrow;
280                 break;
281
282         case GDK_Down:
283                 fakekey = GDK_downarrow;
284                 break;
285
286         case GDK_Right:
287                 fakekey = GDK_rightarrow;
288                 break;
289
290         case GDK_Left:
291                 fakekey = GDK_leftarrow;
292                 break;
293
294         case GDK_Return:
295                 fakekey = GDK_3270_Enter;
296                 break;
297
298         case GDK_KP_Enter:
299                 fakekey = GDK_F35;
300                 break;
301
302         default:
303                 break;
304         }
305
306         if (fakekey != GDK_VoidSymbol) {
307                 keyval = fakekey;
308                 return true;
309         }
310
311         return false;
312 }
313
314 uint32_t
315 Gtkmm2ext::possibly_translate_legal_accelerator_to_real_key (uint32_t keyval)
316 {
317         switch (keyval) {
318         case GDK_nabla:
319                 return GDK_Tab;
320                 break;
321
322         case GDK_uparrow:
323                 return GDK_Up;
324                 break;
325
326         case GDK_downarrow:
327                 return GDK_Down;
328                 break;
329
330         case GDK_rightarrow:
331                 return GDK_Right;
332                 break;
333
334         case GDK_leftarrow:
335                 return GDK_Left;
336                 break;
337
338         case GDK_3270_Enter:
339                 return GDK_Return;
340
341         case GDK_F35:
342                 return GDK_KP_Enter;
343                 break;
344         }
345
346         return keyval;
347 }
348
349 int
350 Gtkmm2ext::physical_screen_height (Glib::RefPtr<Gdk::Window> win)
351 {
352         GdkScreen* scr = gdk_screen_get_default();
353
354         if (win) {
355                 GdkRectangle r;
356                 gint monitor = gdk_screen_get_monitor_at_window (scr, win->gobj());
357                 gdk_screen_get_monitor_geometry (scr, monitor, &r);
358                 return r.height;
359         } else {
360                 return gdk_screen_get_height (scr);
361         }
362 }
363
364 int
365 Gtkmm2ext::physical_screen_width (Glib::RefPtr<Gdk::Window> win)
366 {
367         GdkScreen* scr = gdk_screen_get_default();
368         
369         if (win) {
370                 GdkRectangle r;
371                 gint monitor = gdk_screen_get_monitor_at_window (scr, win->gobj());
372                 gdk_screen_get_monitor_geometry (scr, monitor, &r);
373                 return r.width;
374         } else {
375                 return gdk_screen_get_width (scr);
376         }
377 }
378
379 void
380 Gtkmm2ext::container_clear (Gtk::Container& c)
381 {
382         list<Gtk::Widget*> children = c.get_children();
383         for (list<Gtk::Widget*>::iterator child = children.begin(); child != children.end(); ++child) {
384                 c.remove (**child);
385         }
386 }
387
388 void
389 Gtkmm2ext::rounded_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
390 {
391         rounded_rectangle (context->cobj(), x, y, w, h, r);
392 }
393 void
394 Gtkmm2ext::rounded_top_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
395 {
396         rounded_top_rectangle (context->cobj(), x, y, w, h, r);
397 }
398 void
399 Gtkmm2ext::rounded_top_left_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
400 {
401         rounded_top_left_rectangle (context->cobj(), x, y, w, h, r);
402 }
403 void
404 Gtkmm2ext::rounded_top_right_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
405 {
406         rounded_top_right_rectangle (context->cobj(), x, y, w, h, r);
407 }
408 void
409 Gtkmm2ext::rounded_top_half_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
410 {
411         rounded_top_half_rectangle (context->cobj(), x, y, w, h, r);
412 }
413 void
414 Gtkmm2ext::rounded_bottom_half_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
415 {
416         rounded_bottom_half_rectangle (context->cobj(), x, y, w, h, r);
417 }
418 void
419 Gtkmm2ext::rounded_right_half_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
420 {
421         rounded_right_half_rectangle (context->cobj(), x, y, w, h, r);
422 }
423
424 void
425 Gtkmm2ext::rounded_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
426 {
427         double degrees = M_PI / 180.0;
428
429         cairo_new_sub_path (cr);
430         cairo_arc (cr, x + w - r, y + r, r, -90 * degrees, 0 * degrees);  //tr
431         cairo_arc (cr, x + w - r, y + h - r, r, 0 * degrees, 90 * degrees);  //br
432         cairo_arc (cr, x + r, y + h - r, r, 90 * degrees, 180 * degrees);  //bl
433         cairo_arc (cr, x + r, y + r, r, 180 * degrees, 270 * degrees);  //tl
434         cairo_close_path (cr);
435 }
436
437 void
438 Gtkmm2ext::rounded_right_half_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
439 {
440         double degrees = M_PI / 180.0;
441
442         cairo_new_sub_path (cr);
443         cairo_arc (cr, x + w - r, y + r, r, -90 * degrees, 0 * degrees);  //tr
444         cairo_arc (cr, x + w - r, y + h - r, r, 0 * degrees, 90 * degrees);  //br
445         cairo_line_to (cr, x, y + h); // bl
446         cairo_line_to (cr, x, y); // tl
447         cairo_close_path (cr);
448 }
449
450 void
451 Gtkmm2ext::rounded_top_half_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
452 {
453         double degrees = M_PI / 180.0;
454
455         cairo_new_sub_path (cr);
456         cairo_move_to (cr, x+w, y+h);
457         cairo_line_to (cr, x, y+h);
458         cairo_arc (cr, x + r, y + r, r, 180 * degrees, 270 * degrees);  //tl
459         cairo_arc (cr, x + w - r, y + r, r, -90 * degrees, 0 * degrees);  //tr
460         cairo_close_path (cr);
461 }
462
463 void
464 Gtkmm2ext::rounded_bottom_half_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
465 {
466         double degrees = M_PI / 180.0;
467
468         cairo_new_sub_path (cr);
469         cairo_move_to (cr, x, y);
470         cairo_line_to (cr, x+w, y);
471         cairo_arc (cr, x + w - r, y + h - r, r, 0 * degrees, 90 * degrees);  //br
472         cairo_arc (cr, x + r, y + h - r, r, 90 * degrees, 180 * degrees);  //bl
473         cairo_close_path (cr);
474 }
475
476
477 void
478 Gtkmm2ext::rounded_top_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
479 {
480         double degrees = M_PI / 180.0;
481
482         cairo_new_sub_path (cr);
483         cairo_move_to (cr, x+w, y+h);
484         cairo_line_to (cr, x, y+h);
485         cairo_arc (cr, x + r, y + r, r, 180 * degrees, 270 * degrees);  //tl
486         cairo_arc (cr, x + w - r, y + r, r, -90 * degrees, 0 * degrees);  //tr
487         cairo_close_path (cr);
488 }
489
490 void
491 Gtkmm2ext::rounded_top_left_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
492 {
493 /*    A****B
494       H    *
495       *    *
496       *    *
497       F****E
498 */
499         cairo_move_to (cr, x+r,y); // Move to A
500         cairo_line_to (cr, x+w,y); // Straight line to B
501         cairo_line_to (cr, x+w,y+h); // Move to E
502         cairo_line_to (cr, x,y+h); // Line to F
503         cairo_line_to (cr, x,y+r); // Line to H
504         cairo_curve_to (cr, x,y,x,y,x+r,y); // Curve to A
505 }
506
507 void
508 Gtkmm2ext::rounded_top_right_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
509 {
510 /*    A****BQ
511       *    C
512       *    *
513       *    *
514       F****E
515 */
516         cairo_move_to (cr, x,y); // Move to A
517         cairo_line_to (cr, x+w-r,y); // Straight line to B
518         cairo_curve_to (cr, x+w,y,x+w,y,x+w,y+r); // Curve to C, Control points are both at Q
519         cairo_line_to (cr, x+w,y+h); // Move to E
520         cairo_line_to (cr, x,y+h); // Line to F
521         cairo_line_to (cr, x,y); // Line to A
522 }
523
524 Glib::RefPtr<Gdk::Window>
525 Gtkmm2ext::window_to_draw_on (Gtk::Widget& w, Gtk::Widget** parent)
526 {
527         if (w.get_has_window()) {
528                 return w.get_window();
529         }
530
531         (*parent) = w.get_parent();
532
533         while (*parent) {
534                 if ((*parent)->get_has_window()) {
535                         return (*parent)->get_window ();
536                 }
537                 (*parent) = (*parent)->get_parent ();
538         }
539
540         return Glib::RefPtr<Gdk::Window> ();
541 }
542
543 int
544 Gtkmm2ext::pixel_width (const string& str, Pango::FontDescription& font)
545 {
546         Gtk::Label foo;
547         Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout ("");
548
549         layout->set_font_description (font);
550         layout->set_text (str);
551
552         int width, height;
553         Gtkmm2ext::get_ink_pixel_size (layout, width, height);
554         return width;
555 }
556
557 #if 0
558 string
559 Gtkmm2ext::fit_to_pixels (const string& str, int pixel_width, Pango::FontDescription& font, int& actual_width, bool with_ellipses)
560 {
561         /* DECEMBER 2011: THIS PROTOTYPE OF fit_to_pixels() IS NOT USED
562            ANYWHERE AND HAS NOT BEEN TESTED.
563         */
564         Gtk::Label foo;
565         Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout (str);
566         Glib::RefPtr<const Pango::LayoutLine> line;
567
568         layout->set_font_description (font);
569         layout->set_width (pixel_width * PANGO_SCALE);
570
571         if (with_ellipses) {
572                 layout->set_ellipsize (Pango::ELLIPSIZE_END);
573         } else {
574                 layout->set_wrap (Pango::WRAP_CHAR);
575         }
576
577         line = layout->get_line (0);
578
579         /* XXX: might need special care to get the ellipsis character, not sure
580            how that works 
581         */      
582
583         string s = string (layout->get_text ().substr(line->get_start_index(), line->get_length()));
584         
585         cerr << "fit to pixels of " << str << " returns " << s << endl;
586
587         return s;
588 }
589 #endif
590
591 /** Try to fit a string into a given horizontal space by ellipsizing it.
592  *  @param cr Cairo context in which the text will be plotted.
593  *  @param name Text.
594  *  @param avail Available horizontal space.
595  *  @return (Text, possibly ellipsized) and (horizontal size of text)
596  */
597
598 std::pair<std::string, double>
599 Gtkmm2ext::fit_to_pixels (cairo_t* cr, std::string name, double avail)
600 {
601         /* XXX hopefully there exists a more efficient way of doing this */
602
603         bool abbreviated = false;
604         uint32_t width = 0;
605
606         while (1) {
607                 cairo_text_extents_t ext;
608                 cairo_text_extents (cr, name.c_str(), &ext);
609
610                 if (ext.width < avail || name.length() <= 4) {
611                         width = ext.width;
612                         break;
613                 }
614
615                 if (abbreviated) {
616                         name = name.substr (0, name.length() - 4) + "...";
617                 } else {
618                         name = name.substr (0, name.length() - 3) + "...";
619                         abbreviated = true;
620                 }
621         }
622
623         return std::make_pair (name, width);
624 }
625
626 Gtk::Label *
627 Gtkmm2ext::left_aligned_label (string const & t)
628 {
629         Gtk::Label* l = new Gtk::Label (t);
630         l->set_alignment (0, 0.5);
631         return l;
632 }
633
634 static bool
635 make_null_tooltip (int, int, bool, const Glib::RefPtr<Gtk::Tooltip>& t)
636 {
637         t->set_tip_area (Gdk::Rectangle (0, 0, 0, 0));
638         return true;
639 }
640
641 /** Hackily arrange for the provided widget to have no tooltip,
642  *  and also to stop any other widget from providing one while
643  * the mouse is over w.
644  */
645 void
646 Gtkmm2ext::set_no_tooltip_whatsoever (Gtk::Widget& w)
647 {
648         w.property_has_tooltip() = true;
649         w.signal_query_tooltip().connect (sigc::ptr_fun (make_null_tooltip));
650 }
651
652 void
653 Gtkmm2ext::enable_tooltips ()
654 {
655         gtk_rc_parse_string ("gtk-enable-tooltips = 1");
656 }
657
658 void
659 Gtkmm2ext::disable_tooltips ()
660 {
661         gtk_rc_parse_string ("gtk-enable-tooltips = 0");
662 }
663