all new implementation of audio clocks, with entirely new editing model. not entirely...
[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 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, 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         rounded_rectangle (context->cobj(), x, y, w, h, r);
395 }
396
397 void
398 Gtkmm2ext::rounded_rectangle (cairo_t* cr, double x, double y, double w, double h, double r)
399 {
400         /* renders small shapes better than most others */
401
402 /*    A****BQ
403       H    C
404       *    *
405       G    D
406       F****E
407 */
408         cairo_move_to (cr, x+r,y); // Move to A
409         cairo_line_to (cr, x+w-r,y); // Straight line to B
410         cairo_curve_to (cr, x+w,y,x+w,y,x+w,y+r); // Curve to C, Control points are both at Q
411         cairo_line_to (cr, x+w,y+h-r); // Move to D
412         cairo_curve_to (cr, x+w,y+h,x+w,y+h,x+w-r,y+h); // Curve to E
413         cairo_line_to (cr, x+r,y+h); // Line to F
414         cairo_curve_to (cr, x,y+h,x,y+h,x,y+h-r); // Curve to G
415         cairo_line_to (cr, x,y+r); // Line to H
416         cairo_curve_to (cr, x,y,x,y,x+r,y); // Curve to A
417 }
418
419 #else
420
421 void
422 Gtkmm2ext::rounded_rectangle (Cairo::RefPtr<Cairo::Context> context, double x, double y, double width, double height, double radius)
423 {
424         /* doesn't render small shapes well at all, and does not absolutely honor width & height */
425
426         double x0          = x+radius/2.0;
427         double y0          = y+radius/2.0;
428         double rect_width  = width - radius;
429         double rect_height = height - radius;
430
431         context->save();
432
433         double x1=x0+rect_width;
434         double y1=y0+rect_height;
435
436         if (rect_width/2<radius) {
437                 if (rect_height/2<radius) {
438                         context->move_to  (x0, (y0 + y1)/2);
439                         context->curve_to (x0 ,y0, x0, y0, (x0 + x1)/2, y0);
440                         context->curve_to (x1, y0, x1, y0, x1, (y0 + y1)/2);
441                         context->curve_to (x1, y1, x1, y1, (x1 + x0)/2, y1);
442                         context->curve_to (x0, y1, x0, y1, x0, (y0 + y1)/2);
443                 } else {
444                         context->move_to  (x0, y0 + radius);
445                         context->curve_to (x0 ,y0, x0, y0, (x0 + x1)/2, y0);
446                         context->curve_to (x1, y0, x1, y0, x1, y0 + radius);
447                         context->line_to (x1 , y1 - radius);
448                         context->curve_to (x1, y1, x1, y1, (x1 + x0)/2, y1);
449                         context->curve_to (x0, y1, x0, y1, x0, y1- radius);
450                 }
451         } else {
452                 if (rect_height/2<radius) {
453                         context->move_to  (x0, (y0 + y1)/2);
454                         context->curve_to (x0 , y0, x0 , y0, x0 + radius, y0);
455                         context->line_to (x1 - radius, y0);
456                         context->curve_to (x1, y0, x1, y0, x1, (y0 + y1)/2);
457                         context->curve_to (x1, y1, x1, y1, x1 - radius, y1);
458                         context->line_to (x0 + radius, y1);
459                         context->curve_to (x0, y1, x0, y1, x0, (y0 + y1)/2);
460                 } else {
461                         context->move_to  (x0, y0 + radius);
462                         context->curve_to (x0 , y0, x0 , y0, x0 + radius, y0);
463                         context->line_to (x1 - radius, y0);
464                         context->curve_to (x1, y0, x1, y0, x1, y0 + radius);
465                         context->line_to (x1 , y1 - radius);
466                         context->curve_to (x1, y1, x1, y1, x1 - radius, y1);
467                         context->line_to (x0 + radius, y1);
468                         context->curve_to (x0, y1, x0, y1, x0, y1- radius);
469                 }
470         }
471
472         context->close_path ();
473         context->restore();
474 }
475
476 #endif
477
478 Glib::RefPtr<Gdk::Window>
479 Gtkmm2ext::window_to_draw_on (Gtk::Widget& w, Gtk::Widget** parent)
480 {
481         if (w.get_has_window()) {
482                 return w.get_window();
483         }
484
485         (*parent) = w.get_parent();
486
487         while (*parent) {
488                 if ((*parent)->get_has_window()) {
489                         return (*parent)->get_window ();
490                 }
491                 (*parent) = (*parent)->get_parent ();
492         }
493
494         return Glib::RefPtr<Gdk::Window> ();
495 }
496
497 #if 0
498 string
499 fit_to_pixels (const string& str, int pixel_width, Pango::FontDescription& font, int& actual_width, bool with_ellipses)
500 {
501         Label foo;
502         Glib::RefPtr<Pango::Layout> layout = foo.create_pango_layout (str);
503         Glib::RefPtr<Pango::LayoutLine> line;
504
505         layout->set_font_description (font);
506         layout->set_width (pixel_width * PANGO_SCALE);
507
508         if (with_ellipsis)
509                 layout->set_ellipsize (PANGO_ELLIPSIZE_END);
510         else
511                 layout->set_wrap_mode (PANGO_WRAP_CHAR);
512
513         line = layout->get_line_readonly (0);
514
515         /* XXX: might need special care to get the ellipsis character, not sure
516            how that works */    
517         return strdup (layout->get_text () + line->start_index, line->length);
518 }
519 #endif