Allow CairoWidget to be backed by NSGLView
[ardour.git] / libs / gtkmm2ext / cairo_widget.cc
1 /*
2     Copyright (C) 2009 Paul 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 */
19 #if !defined USE_CAIRO_IMAGE_SURFACE && !defined NDEBUG
20 #define OPTIONAL_CAIRO_IMAGE_SURFACE
21 #endif
22
23 #include "gtkmm2ext/cairo_widget.h"
24 #include "gtkmm2ext/gui_thread.h"
25 #include "gtkmm2ext/rgb_macros.h"
26
27 #ifdef __APPLE__
28 #include <gdk/gdk.h>
29 #include "gtkmm2ext/nsglview.h"
30 #endif
31
32 #include "pbd/i18n.h"
33
34 static const char* has_cairo_widget_background_info = "has_cairo_widget_background_info";
35
36 bool CairoWidget::_flat_buttons = false;
37 bool CairoWidget::_widget_prelight = true;
38
39 sigc::slot<void,Gtk::Widget*> CairoWidget::focus_handler;
40
41 void CairoWidget::set_source_rgb_a( cairo_t* cr, Gdk::Color col, float a)  //ToDo:  this one and the Canvas version should be in a shared file (?)
42 {
43         float r = col.get_red_p ();
44         float g = col.get_green_p ();
45         float b = col.get_blue_p ();
46
47         cairo_set_source_rgba(cr, r, g, b, a);
48 }
49
50 CairoWidget::CairoWidget ()
51         : _active_state (Gtkmm2ext::Off)
52         , _visual_state (Gtkmm2ext::NoVisualState)
53         , _need_bg (true)
54         , _grabbed (false)
55         , _name_proxy (this, X_("name"))
56         , _current_parent (0)
57         , _canvas_widget (false)
58         , _nsglview (0)
59 {
60         _name_proxy.connect (sigc::mem_fun (*this, &CairoWidget::on_name_changed));
61 }
62
63 CairoWidget::~CairoWidget ()
64 {
65         if (_canvas_widget) {
66                 gtk_widget_set_realized (GTK_WIDGET(gobj()), false);
67         }
68         if (_parent_style_change) _parent_style_change.disconnect();
69 }
70
71 void
72 CairoWidget::set_canvas_widget ()
73 {
74         assert (!_nsglview);
75         assert (!_canvas_widget);
76         ensure_style ();
77         gtk_widget_set_realized (GTK_WIDGET(gobj()), true);
78         _canvas_widget = true;
79 }
80
81 void
82 CairoWidget::use_nsglview ()
83 {
84         assert (!_nsglview);
85         assert (!_canvas_widget);
86         assert (!is_realized());
87 #ifdef ARDOUR_CANVAS_NSVIEW_TAG // patched gdkquartz.h
88 # ifndef __ppc__ // would need to flip RGBA <> RGBA
89         _nsglview = Gtkmm2ext::nsglview_create (this);
90 #endif
91 #endif
92 }
93
94 int
95 CairoWidget::get_width () const
96 {
97         if (_canvas_widget) {
98                 return _allocation.get_width ();
99         }
100         return Gtk::EventBox::get_width ();
101 }
102
103 int
104 CairoWidget::get_height () const
105 {
106         if (_canvas_widget) {
107                 return _allocation.get_height ();
108         }
109         return Gtk::EventBox::get_height ();
110 }
111
112 void
113 CairoWidget::size_allocate (Gtk::Allocation& alloc)
114 {
115         if (_canvas_widget) {
116                 memcpy (&_allocation, &alloc, sizeof(Gtk::Allocation));
117                 return;
118         }
119         Gtk::EventBox::size_allocate (alloc);
120 }
121
122
123 bool
124 CairoWidget::on_button_press_event (GdkEventButton*)
125 {
126         focus_handler (this);
127         return false;
128 }
129
130 uint32_t
131 CairoWidget::background_color ()
132 {
133         if (_need_bg) {
134                 Gdk::Color bg (get_parent_bg());
135                 return RGBA_TO_UINT (bg.get_red() / 255, bg.get_green() / 255, bg.get_blue() / 255, 255);
136         } else {
137                 return 0;
138         }
139 }
140
141 #ifdef USE_TRACKS_CODE_FEATURES
142
143 /* This is Tracks version of this method.
144
145    The use of get_visible_window() in this method is an abuse of the GDK/GTK
146    semantics. It can and may break on different GDK backends, and uses a
147    side-effect/unintended behaviour in GDK/GTK to try to accomplish something
148    which should be done differently. I (Paul) have confirmed this with the GTK
149    development team.
150
151    For this reason, this code is not acceptable for ordinary merging into the Ardour libraries.
152
153    Ardour Developers: you are not obligated to maintain the internals of this
154    implementation in the face of build-time environment changes (e.g. -D
155    defines etc).
156 */
157
158 bool
159 CairoWidget::on_expose_event (GdkEventExpose *ev)
160 {
161         cairo_rectangle_t expose_area;
162         expose_area.width = ev->area.width;
163         expose_area.height = ev->area.height;
164
165 #ifdef USE_CAIRO_IMAGE_SURFACE_FOR_CAIRO_WIDGET
166         Cairo::RefPtr<Cairo::Context> cr;
167         if (get_visible_window ()) {
168                 expose_area.x = 0;
169                 expose_area.y = 0;
170                 if (!_image_surface) {
171                         _image_surface = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, get_width(), get_height());
172                 }
173                 cr = Cairo::Context::create (_image_surface);
174         } else {
175                 expose_area.x = ev->area.x;
176                 expose_area.y = ev->area.y;
177                 cr = get_window()->create_cairo_context ();
178         }
179 #else
180         expose_area.x = ev->area.x;
181         expose_area.y = ev->area.y;
182         Cairo::RefPtr<Cairo::Context> cr = get_window()->create_cairo_context ();
183 #endif
184
185         cr->rectangle (expose_area.x, expose_area.y, expose_area.width, expose_area.height);
186         cr->clip ();
187
188         /* paint expose area the color of the parent window bg
189         */
190
191     if (get_visible_window ()) {
192         Gdk::Color bg (get_parent_bg());
193                 cr->rectangle (expose_area.x, expose_area.y, expose_area.width, expose_area.height);
194         cr->set_source_rgb (bg.get_red_p(), bg.get_green_p(), bg.get_blue_p());
195         cr->fill ();
196     }
197
198         render (cr->cobj(), &expose_area);
199
200 #ifdef USE_CAIRO_IMAGE_SURFACE_FOR_CAIRO_WIDGET
201         if(get_visible_window ()) {
202                 _image_surface->flush();
203                 /* now blit our private surface back to the GDK one */
204
205                 Cairo::RefPtr<Cairo::Context> cairo_context = get_window()->create_cairo_context ();
206
207                 cairo_context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
208                 cairo_context->clip ();
209                 cairo_context->set_source (_image_surface, ev->area.x, ev->area.y);
210                 cairo_context->set_operator (Cairo::OPERATOR_OVER);
211                 cairo_context->paint ();
212         }
213 #endif
214
215         Gtk::Widget* child = get_child ();
216
217         if (child) {
218                 propagate_expose (*child, ev);
219         }
220
221         return true;
222 }
223
224 #else
225
226 /* Ardour mainline: not using Tracks code features.
227
228    Tracks Developers: please do not modify this version of
229    ::on_expose_event(). The version used by Tracks is before the preceding
230    #else and contains hacks required for the Tracks GUI to work.
231 */
232
233 bool
234 CairoWidget::on_expose_event (GdkEventExpose *ev)
235 {
236 #ifdef __APPLE__
237         if (_nsglview) {
238                 Gtkmm2ext::nsglview_queue_draw (_nsglview, ev->area.x, ev->area.y, ev->area.width, ev->area.height);
239                 return true;
240         }
241 #endif
242 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
243         Cairo::RefPtr<Cairo::Context> cr;
244         if (getenv("ARDOUR_IMAGE_SURFACE")) {
245                 if (!image_surface) {
246                         image_surface = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, get_width(), get_height());
247                 }
248                 cr = Cairo::Context::create (image_surface);
249         } else {
250                 cr = get_window()->create_cairo_context ();
251         }
252 #elif defined USE_CAIRO_IMAGE_SURFACE
253
254         if (!image_surface) {
255                 image_surface = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, get_width(), get_height());
256         }
257
258         Cairo::RefPtr<Cairo::Context> cr = Cairo::Context::create (image_surface);
259 #else
260         Cairo::RefPtr<Cairo::Context> cr = get_window()->create_cairo_context ();
261 #endif
262
263         cr->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
264
265         if (_need_bg) {
266                 cr->clip_preserve ();
267
268                 /* paint expose area the color of the parent window bg
269                  */
270
271                 Gdk::Color bg (get_parent_bg());
272
273                 cr->set_source_rgb (bg.get_red_p(), bg.get_green_p(), bg.get_blue_p());
274                 cr->fill ();
275         } else {
276                 std::cerr << get_name() << " skipped bg fill\n";
277                 cr->clip ();
278         }
279
280         cairo_rectangle_t expose_area;
281         expose_area.x = ev->area.x;
282         expose_area.y = ev->area.y;
283         expose_area.width = ev->area.width;
284         expose_area.height = ev->area.height;
285
286         render (cr->cobj(), &expose_area);
287
288 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
289         if (getenv("ARDOUR_IMAGE_SURFACE")) {
290 #endif
291 #if defined USE_CAIRO_IMAGE_SURFACE || defined OPTIONAL_CAIRO_IMAGE_SURFACE
292         image_surface->flush();
293         /* now blit our private surface back to the GDK one */
294
295         Cairo::RefPtr<Cairo::Context> cairo_context = get_window()->create_cairo_context ();
296
297         cairo_context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
298         cairo_context->clip ();
299         cairo_context->set_source (image_surface, 0, 0);
300         cairo_context->set_operator (Cairo::OPERATOR_SOURCE);
301         cairo_context->paint ();
302 #endif
303 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
304         }
305 #endif
306
307         return true;
308 }
309
310 #endif
311
312 /** Marks the widget as dirty, so that render () will be called on
313  *  the next GTK expose event.
314  */
315
316 void
317 CairoWidget::set_dirty (cairo_rectangle_t *area)
318 {
319         ENSURE_GUI_THREAD (*this, &CairoWidget::set_dirty);
320         if (!area) {
321                 queue_draw ();
322         } else {
323                 // TODO emit QueueDrawArea -> ArdourCanvas::Widget
324                 if (QueueDraw ()) {
325                         return;
326                 }
327                 queue_draw_area (area->x, area->y, area->width, area->height);
328         }
329 }
330
331 void
332 CairoWidget::queue_draw ()
333 {
334         if (QueueDraw ()) {
335                 return;
336         }
337         Gtk::EventBox::queue_draw ();
338 }
339
340 void
341 CairoWidget::queue_resize ()
342 {
343         if (QueueResize ()) {
344                 return;
345         }
346         Gtk::EventBox::queue_resize ();
347 }
348
349 /** Handle a size allocation.
350  *  @param alloc GTK allocation.
351  */
352 void
353 CairoWidget::on_size_allocate (Gtk::Allocation& alloc)
354 {
355         if (!_canvas_widget) {
356                 Gtk::EventBox::on_size_allocate (alloc);
357         } else {
358                 memcpy (&_allocation, &alloc, sizeof(Gtk::Allocation));
359         }
360
361 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
362         if (getenv("ARDOUR_IMAGE_SURFACE")) {
363 #endif
364 #if defined USE_CAIRO_IMAGE_SURFACE || defined OPTIONAL_CAIRO_IMAGE_SURFACE
365         image_surface = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, alloc.get_width(), alloc.get_height());
366 #endif
367 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
368         }
369 #endif
370
371         if (_canvas_widget) {
372                 return;
373         }
374 #ifdef __APPLE__
375         if (_nsglview) {
376                 gint xx, yy;
377                 gtk_widget_translate_coordinates(
378                                 GTK_WIDGET(gobj()),
379                                 GTK_WIDGET(get_toplevel()->gobj()),
380                                 0, 0, &xx, &yy);
381                 Gtkmm2ext::nsglview_resize (_nsglview, xx, yy, alloc.get_width(), alloc.get_height());
382         }
383 #endif
384         set_dirty ();
385 }
386
387 Gdk::Color
388 CairoWidget::get_parent_bg ()
389 {
390         Widget* parent;
391
392         parent = get_parent ();
393
394         while (parent) {
395                 void* p = g_object_get_data (G_OBJECT(parent->gobj()), has_cairo_widget_background_info);
396
397                 if (p) {
398                         Glib::RefPtr<Gtk::Style> style = parent->get_style();
399                         if (_current_parent != parent) {
400                                 if (_parent_style_change) _parent_style_change.disconnect();
401                                 _current_parent = parent;
402                                 _parent_style_change = parent->signal_style_changed().connect (mem_fun (*this, &CairoWidget::on_style_changed));
403                         }
404                         return style->get_bg (get_state());
405                 }
406
407                 if (!parent->get_has_window()) {
408                         parent = parent->get_parent();
409                 } else {
410                         break;
411                 }
412         }
413
414         if (parent && parent->get_has_window()) {
415                 if (_current_parent != parent) {
416                         if (_parent_style_change) _parent_style_change.disconnect();
417                         _current_parent = parent;
418                         _parent_style_change = parent->signal_style_changed().connect (mem_fun (*this, &CairoWidget::on_style_changed));
419                 }
420                 return parent->get_style ()->get_bg (parent->get_state());
421         }
422
423         return get_style ()->get_bg (get_state());
424 }
425
426 void
427 CairoWidget::set_active_state (Gtkmm2ext::ActiveState s)
428 {
429         if (_active_state != s) {
430                 _active_state = s;
431                 StateChanged ();
432         }
433 }
434
435 void
436 CairoWidget::set_visual_state (Gtkmm2ext::VisualState s)
437 {
438         if (_visual_state != s) {
439                 _visual_state = s;
440                 StateChanged ();
441         }
442 }
443
444 void
445 CairoWidget::set_active (bool yn)
446 {
447         /* this is an API simplification for buttons
448            that only use the Active and Normal states.
449         */
450
451         if (yn) {
452                 set_active_state (Gtkmm2ext::ExplicitActive);
453         } else {
454                 unset_active_state ();
455         }
456 }
457
458 void
459 CairoWidget::on_style_changed (const Glib::RefPtr<Gtk::Style>&)
460 {
461         set_dirty ();
462 }
463
464 void
465 CairoWidget::on_realize ()
466 {
467         Gtk::EventBox::on_realize();
468 #ifdef __APPLE__
469         if (_nsglview) {
470                 Gtkmm2ext::nsglview_overlay (_nsglview, get_window()->gobj());
471         }
472 #endif
473 }
474
475 void
476 CairoWidget::on_state_changed (Gtk::StateType)
477 {
478         /* this will catch GTK-level state changes from calls like
479            ::set_sensitive()
480         */
481
482         if (get_state() == Gtk::STATE_INSENSITIVE) {
483                 set_visual_state (Gtkmm2ext::VisualState (visual_state() | Gtkmm2ext::Insensitive));
484         } else {
485                 set_visual_state (Gtkmm2ext::VisualState (visual_state() & ~Gtkmm2ext::Insensitive));
486         }
487
488         set_dirty ();
489 }
490
491 void
492 CairoWidget::set_draw_background (bool yn)
493 {
494         _need_bg = yn;
495 }
496
497 void
498 CairoWidget::provide_background_for_cairo_widget (Gtk::Widget& w, const Gdk::Color& bg)
499 {
500         /* set up @w to be able to provide bg information to
501            any CairoWidgets that are packed inside it.
502         */
503
504         w.modify_bg (Gtk::STATE_NORMAL, bg);
505         w.modify_bg (Gtk::STATE_INSENSITIVE, bg);
506         w.modify_bg (Gtk::STATE_ACTIVE, bg);
507         w.modify_bg (Gtk::STATE_SELECTED, bg);
508
509         g_object_set_data (G_OBJECT(w.gobj()), has_cairo_widget_background_info, (void*) 0xfeedface);
510 }
511
512 void
513 CairoWidget::set_flat_buttons (bool yn)
514 {
515         _flat_buttons = yn;
516 }
517
518 void
519 CairoWidget::set_widget_prelight (bool yn)
520 {
521         _widget_prelight = yn;
522 }
523
524 void
525 CairoWidget::set_focus_handler (sigc::slot<void,Gtk::Widget*> s)
526 {
527         focus_handler = s;
528 }