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