a85de284900214b1eb035f9846f1fe71a4a7e9e8
[ardour.git] / libs / gtkmm2ext / cairo_widget.cc
1 /*
2  * Copyright (C) 2011-2016 Paul Davis <paul@linuxaudiosystems.com>
3  * Copyright (C) 2014-2018 Robin Gareus <robin@gareus.org>
4  * Copyright (C) 2014 Ben Loftis <ben@harrisonconsoles.com>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this program; if not, write to the Free Software Foundation, Inc.,
18  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19  */
20 #if !defined USE_CAIRO_IMAGE_SURFACE && !defined NDEBUG
21 #define OPTIONAL_CAIRO_IMAGE_SURFACE
22 #endif
23
24 #include "gtkmm2ext/cairo_widget.h"
25 #include "gtkmm2ext/gui_thread.h"
26 #include "gtkmm2ext/rgb_macros.h"
27
28 #ifdef __APPLE__
29 #include <gdk/gdk.h>
30 #include "gtkmm2ext/nsglview.h"
31 #endif
32
33 #include "pbd/i18n.h"
34
35 static const char* has_cairo_widget_background_info = "has_cairo_widget_background_info";
36
37 bool CairoWidget::_flat_buttons = false;
38 bool CairoWidget::_boxy_buttons = false;
39 bool CairoWidget::_widget_prelight = true;
40
41 sigc::slot<void,Gtk::Widget*> CairoWidget::focus_handler;
42
43 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 (?)
44 {
45         float r = col.get_red_p ();
46         float g = col.get_green_p ();
47         float b = col.get_blue_p ();
48
49         cairo_set_source_rgba(cr, r, g, b, a);
50 }
51
52 CairoWidget::CairoWidget ()
53         : _active_state (Gtkmm2ext::Off)
54         , _visual_state (Gtkmm2ext::NoVisualState)
55         , _need_bg (true)
56         , _grabbed (false)
57         , _name_proxy (this, X_("name"))
58         , _current_parent (0)
59         , _canvas_widget (false)
60         , _nsglview (0)
61 {
62         _name_proxy.connect (sigc::mem_fun (*this, &CairoWidget::on_name_changed));
63 }
64
65 CairoWidget::~CairoWidget ()
66 {
67         if (_canvas_widget) {
68                 gtk_widget_set_realized (GTK_WIDGET(gobj()), false);
69         }
70         if (_parent_style_change) _parent_style_change.disconnect();
71 }
72
73 void
74 CairoWidget::set_canvas_widget ()
75 {
76         assert (!_nsglview);
77         assert (!_canvas_widget);
78         ensure_style ();
79         gtk_widget_set_realized (GTK_WIDGET(gobj()), true);
80         _canvas_widget = true;
81 }
82
83 void
84 CairoWidget::use_nsglview ()
85 {
86         assert (!_nsglview);
87         assert (!_canvas_widget);
88         assert (!is_realized());
89 #ifdef ARDOUR_CANVAS_NSVIEW_TAG // patched gdkquartz.h
90         _nsglview = Gtkmm2ext::nsglview_create (this);
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, &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, &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_map ()
477 {
478         Gtk::EventBox::on_map();
479 #ifdef __APPLE__
480         if (_nsglview) {
481                 Gtkmm2ext::nsglview_set_visible (_nsglview, true);
482                 Gtk::Allocation a = get_allocation();
483                 gint xx, yy;
484                 gtk_widget_translate_coordinates(
485                                 GTK_WIDGET(gobj()),
486                                 GTK_WIDGET(get_toplevel()->gobj()),
487                                 0, 0, &xx, &yy);
488                 Gtkmm2ext::nsglview_resize (_nsglview, xx, yy, a.get_width(), a.get_height());
489         }
490 #endif
491 }
492
493 void
494 CairoWidget::on_unmap ()
495 {
496         Gtk::EventBox::on_unmap();
497 #ifdef __APPLE__
498         if (_nsglview) {
499                 Gtkmm2ext::nsglview_set_visible (_nsglview, false);
500         }
501 #endif
502 }
503
504 void
505 CairoWidget::on_state_changed (Gtk::StateType)
506 {
507         /* this will catch GTK-level state changes from calls like
508            ::set_sensitive()
509         */
510
511         if (get_state() == Gtk::STATE_INSENSITIVE) {
512                 set_visual_state (Gtkmm2ext::VisualState (visual_state() | Gtkmm2ext::Insensitive));
513         } else {
514                 set_visual_state (Gtkmm2ext::VisualState (visual_state() & ~Gtkmm2ext::Insensitive));
515         }
516
517         set_dirty ();
518 }
519
520 void
521 CairoWidget::set_draw_background (bool yn)
522 {
523         _need_bg = yn;
524 }
525
526 void
527 CairoWidget::provide_background_for_cairo_widget (Gtk::Widget& w, const Gdk::Color& bg)
528 {
529         /* set up @w to be able to provide bg information to
530            any CairoWidgets that are packed inside it.
531         */
532
533         w.modify_bg (Gtk::STATE_NORMAL, bg);
534         w.modify_bg (Gtk::STATE_INSENSITIVE, bg);
535         w.modify_bg (Gtk::STATE_ACTIVE, bg);
536         w.modify_bg (Gtk::STATE_SELECTED, bg);
537
538         g_object_set_data (G_OBJECT(w.gobj()), has_cairo_widget_background_info, (void*) 0xfeedface);
539 }
540
541 void
542 CairoWidget::set_flat_buttons (bool yn)
543 {
544         _flat_buttons = yn;
545 }
546
547 void
548 CairoWidget::set_boxy_buttons (bool yn)
549 {
550         _boxy_buttons = yn;
551 }
552
553
554 void
555 CairoWidget::set_widget_prelight (bool yn)
556 {
557         _widget_prelight = yn;
558 }
559
560 void
561 CairoWidget::set_focus_handler (sigc::slot<void,Gtk::Widget*> s)
562 {
563         focus_handler = s;
564 }