added Gtkmm2ext::rounded_rectangle() cairo pseudo-method
[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/comboboxtext.h>
32
33 #include "i18n.h"
34
35 using namespace std;
36
37 void
38 Gtkmm2ext::init ()
39 {
40         // Necessary for gettext
41         (void) bindtextdomain(PACKAGE, LOCALEDIR);
42 }
43
44 void
45 Gtkmm2ext::get_ink_pixel_size (Glib::RefPtr<Pango::Layout> layout,
46                                int& width,
47                                int& height)
48 {
49         Pango::Rectangle ink_rect = layout->get_ink_extents ();
50         
51         width = (ink_rect.get_width() + PANGO_SCALE / 2) / PANGO_SCALE;
52         height = (ink_rect.get_height() + PANGO_SCALE / 2) / PANGO_SCALE;
53 }
54
55 void
56 get_pixel_size (Glib::RefPtr<Pango::Layout> layout,
57                                int& width,
58                                int& height)
59 {
60         layout->get_pixel_size (width, height);
61 }
62
63 void
64 Gtkmm2ext::set_size_request_to_display_given_text (Gtk::Widget &w, const gchar *text,
65                                                    gint hpadding, gint vpadding)
66         
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 {
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 descener */
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, 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, bool set_size, gint hpadding, gint vpadding)
224 {
225         vector<string>::const_iterator i;
226
227         cr.clear ();
228
229         if (set_size) {
230                 set_size_request_to_display_given_text (cr, strings, COMBO_FUDGE+10+hpadding, 15+vpadding); 
231         }
232
233         for (i = strings.begin(); i != strings.end(); ++i) {
234                 cr.append_text (*i);
235         }
236 }
237
238 GdkWindow*
239 Gtkmm2ext::get_paned_handle (Gtk::Paned& paned)
240 {
241         return GTK_PANED(paned.gobj())->handle;
242 }
243
244 void
245 Gtkmm2ext::set_decoration (Gtk::Window* win, Gdk::WMDecoration decor)
246 {
247         win->get_window()->set_decorations (decor);
248 }
249
250 void Gtkmm2ext::set_treeview_header_as_default_label(Gtk::TreeViewColumn* c)
251 {
252         gtk_tree_view_column_set_widget( c->gobj(), GTK_WIDGET(0) );
253 }
254
255 void
256 Gtkmm2ext::detach_menu (Gtk::Menu& menu)
257 {
258         /* its possible for a Gtk::Menu to have no gobj() because it has
259            not yet been instantiated. Catch this and provide a safe
260            detach method.
261         */
262         if (menu.gobj()) {
263                 if (menu.get_attach_widget()) {
264                         menu.detach ();
265                 }
266         }
267 }
268
269 bool
270 Gtkmm2ext::possibly_translate_keyval_to_make_legal_accelerator (uint32_t& keyval)
271 {
272         int fakekey = GDK_VoidSymbol;
273
274         switch (keyval) {
275         case GDK_Tab:
276         case GDK_ISO_Left_Tab:
277                 fakekey = GDK_nabla;
278                 break;
279
280         case GDK_Up:
281                 fakekey = GDK_uparrow;
282                 break;
283
284         case GDK_Down:
285                 fakekey = GDK_downarrow;
286                 break;
287
288         case GDK_Right:
289                 fakekey = GDK_rightarrow;
290                 break;
291
292         case GDK_Left:
293                 fakekey = GDK_leftarrow;
294                 break;
295
296         case GDK_Return:
297                 fakekey = GDK_3270_Enter;
298                 break;
299
300         case GDK_KP_Enter:
301                 fakekey = GDK_F35;
302                 break;
303
304         default:
305                 break;
306         }
307
308         if (fakekey != GDK_VoidSymbol) {
309                 keyval = fakekey;
310                 return true;
311         }
312
313         return false;
314 }
315
316 uint32_t
317 Gtkmm2ext::possibly_translate_legal_accelerator_to_real_key (uint32_t keyval)
318 {
319         switch (keyval) {
320         case GDK_nabla:
321                 return GDK_Tab;
322                 break;
323
324         case GDK_uparrow:
325                 return GDK_Up;
326                 break;
327
328         case GDK_downarrow:
329                 return GDK_Down;
330                 break;
331
332         case GDK_rightarrow:
333                 return GDK_Right;
334                 break;
335
336         case GDK_leftarrow:
337                 return GDK_Left;
338                 break;
339
340         case GDK_3270_Enter:
341                 return GDK_Return;
342
343         case GDK_F35:
344                 return GDK_KP_Enter;
345                 break;
346         }
347
348         return keyval;
349 }
350
351 int
352 Gtkmm2ext::physical_screen_height (Glib::RefPtr<Gdk::Window> win)
353 {
354         GdkScreen* scr = gdk_screen_get_default();
355
356         if (win) {
357                 GdkRectangle r;
358                 gint monitor = gdk_screen_get_monitor_at_window (scr, win->gobj());
359                 gdk_screen_get_monitor_geometry (scr, monitor, &r);
360                 return r.height;
361         } else {
362                 return gdk_screen_get_height (scr);
363         }
364 }
365
366 int
367 Gtkmm2ext::physical_screen_width (Glib::RefPtr<Gdk::Window> win)
368 {
369         GdkScreen* scr = gdk_screen_get_default();
370         
371         if (win) {
372                 GdkRectangle r;
373                 gint monitor = gdk_screen_get_monitor_at_window (scr, win->gobj());
374                 gdk_screen_get_monitor_geometry (scr, monitor, &r);
375                 return r.width;
376         } else {
377                 return gdk_screen_get_width (scr);
378         }
379 }
380
381 void
382 Gtkmm2ext::container_clear (Gtk::Container& c)
383 {
384         list<Gtk::Widget*> children = c.get_children();
385         for (list<Gtk::Widget*>::iterator child = children.begin(); child != children.end(); ++child) {
386                 c.remove (**child);
387         }
388 }
389
390 #if 1
391 void
392 Gtkmm2ext::rounded_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double w, double h, double r)
393 {
394         /* renders small shapes better than most others */
395
396 /*    A****BQ
397       H    C
398       *    *
399       G    D
400       F****E
401 */
402         context->move_to(x+r,y); // Move to A
403         context->line_to(x+w-r,y); // Straight line to B
404         context->curve_to(x+w,y,x+w,y,x+w,y+r); // Curve to C, Control points are both at Q
405         context->line_to(x+w,y+h-r); // Move to D
406         context->curve_to(x+w,y+h,x+w,y+h,x+w-r,y+h); // Curve to E
407         context->line_to(x+r,y+h); // Line to F
408         context->curve_to(x,y+h,x,y+h,x,y+h-r); // Curve to G
409         context->line_to(x,y+r); // Line to H
410         context->curve_to(x,y,x,y,x+r,y); // Curve to A
411 }
412
413 #else
414
415 void
416 Gtkmm2ext::rounded_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double width, double height, double radius)
417 {
418         /* doesn't render small shapes well at all, and does not absolutely honor width & height */
419
420         double x0          = x+radius/2.0;
421         double y0          = y+radius/2.0;
422         double rect_width  = width - radius;
423         double rect_height = height - radius;
424
425         context->save();
426
427         double x1=x0+rect_width;
428         double y1=y0+rect_height;
429
430         if (rect_width/2<radius) {
431                 if (rect_height/2<radius) {
432                         context->move_to  (x0, (y0 + y1)/2);
433                         context->curve_to (x0 ,y0, x0, y0, (x0 + x1)/2, y0);
434                         context->curve_to (x1, y0, x1, y0, x1, (y0 + y1)/2);
435                         context->curve_to (x1, y1, x1, y1, (x1 + x0)/2, y1);
436                         context->curve_to (x0, y1, x0, y1, x0, (y0 + y1)/2);
437                 } else {
438                         context->move_to  (x0, y0 + radius);
439                         context->curve_to (x0 ,y0, x0, y0, (x0 + x1)/2, y0);
440                         context->curve_to (x1, y0, x1, y0, x1, y0 + radius);
441                         context->line_to (x1 , y1 - radius);
442                         context->curve_to (x1, y1, x1, y1, (x1 + x0)/2, y1);
443                         context->curve_to (x0, y1, x0, y1, x0, y1- radius);
444                 }
445         } else {
446                 if (rect_height/2<radius) {
447                         context->move_to  (x0, (y0 + y1)/2);
448                         context->curve_to (x0 , y0, x0 , y0, x0 + radius, y0);
449                         context->line_to (x1 - radius, y0);
450                         context->curve_to (x1, y0, x1, y0, x1, (y0 + y1)/2);
451                         context->curve_to (x1, y1, x1, y1, x1 - radius, y1);
452                         context->line_to (x0 + radius, y1);
453                         context->curve_to (x0, y1, x0, y1, x0, (y0 + y1)/2);
454                 } else {
455                         context->move_to  (x0, y0 + radius);
456                         context->curve_to (x0 , y0, x0 , y0, x0 + radius, y0);
457                         context->line_to (x1 - radius, y0);
458                         context->curve_to (x1, y0, x1, y0, x1, y0 + radius);
459                         context->line_to (x1 , y1 - radius);
460                         context->curve_to (x1, y1, x1, y1, x1 - radius, y1);
461                         context->line_to (x0 + radius, y1);
462                         context->curve_to (x0, y1, x0, y1, x0, y1- radius);
463                 }
464         }
465
466         context->close_path ();
467         context->restore();
468 }
469
470 #endif