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