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