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
21 #include "gtkmm2ext/cairo_widget.h"
22 #include "gtkmm2ext/gui_thread.h"
23 #include "gtkmm2ext/rgb_macros.h"
24
25 #ifdef __APPLE__
26 #include <gdk/gdk.h>
27 #include "gtkmm2ext/nsglview.h"
28 #endif
29
30 #include "pbd/i18n.h"
31
32 static const char* has_cairo_widget_background_info = "has_cairo_widget_background_info";
33
34 sigc::slot<void,Gtk::Widget*> CairoWidget::focus_handler;
35
set_source_rgb_a(cairo_t * cr,Gdk::Color col,float a)36 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 (?)
37 {
38 float r = col.get_red_p ();
39 float g = col.get_green_p ();
40 float b = col.get_blue_p ();
41
42 cairo_set_source_rgba(cr, r, g, b, a);
43 }
44
CairoWidget()45 CairoWidget::CairoWidget ()
46 : _active_state (Gtkmm2ext::Off)
47 , _visual_state (Gtkmm2ext::NoVisualState)
48 , _need_bg (true)
49 , _grabbed (false)
50 , _name_proxy (this, X_("name"))
51 , _current_parent (0)
52 , _canvas_widget (false)
53 , _nsglview (0)
54 , _widget_name ("")
55 {
56 _name_proxy.connect (sigc::mem_fun (*this, &CairoWidget::on_widget_name_changed));
57 #ifdef USE_CAIRO_IMAGE_SURFACE
58 _use_image_surface = true;
59 #else
60 _use_image_surface = NULL != getenv("ARDOUR_IMAGE_SURFACE");
61 #endif
62 }
63
~CairoWidget()64 CairoWidget::~CairoWidget ()
65 {
66 if (_canvas_widget) {
67 gtk_widget_set_realized (GTK_WIDGET(gobj()), false);
68 }
69 if (_parent_style_change) {
70 _parent_style_change.disconnect();
71 }
72 }
73
74 void
set_canvas_widget()75 CairoWidget::set_canvas_widget ()
76 {
77 assert (!_nsglview);
78 assert (!_canvas_widget);
79 ensure_style ();
80 gtk_widget_set_realized (GTK_WIDGET(gobj()), true);
81 _canvas_widget = true;
82 _use_image_surface = false;
83 image_surface.clear ();
84 }
85
86 void
use_nsglview()87 CairoWidget::use_nsglview ()
88 {
89 assert (!_nsglview);
90 assert (!_canvas_widget);
91 assert (!is_realized());
92 #ifdef ARDOUR_CANVAS_NSVIEW_TAG // patched gdkquartz.h
93 _nsglview = Gtkmm2ext::nsglview_create (this);
94 #endif
95 }
96
97 void
use_image_surface(bool yn)98 CairoWidget::use_image_surface (bool yn)
99 {
100 if (_use_image_surface == yn) {
101 return;
102 }
103 image_surface.clear ();
104 _use_image_surface = yn;
105 }
106
107 int
get_width() const108 CairoWidget::get_width () const
109 {
110 if (_canvas_widget) {
111 return _allocation.get_width ();
112 }
113 return Gtk::EventBox::get_width ();
114 }
115
116 int
get_height() const117 CairoWidget::get_height () const
118 {
119 if (_canvas_widget) {
120 return _allocation.get_height ();
121 }
122 return Gtk::EventBox::get_height ();
123 }
124
125 void
size_allocate(Gtk::Allocation & alloc)126 CairoWidget::size_allocate (Gtk::Allocation& alloc)
127 {
128 if (_canvas_widget) {
129 memcpy (&_allocation, &alloc, sizeof(Gtk::Allocation));
130 return;
131 }
132 Gtk::EventBox::size_allocate (alloc);
133 }
134
135
136 bool
on_button_press_event(GdkEventButton *)137 CairoWidget::on_button_press_event (GdkEventButton*)
138 {
139 focus_handler (this);
140 return false;
141 }
142
143 uint32_t
background_color()144 CairoWidget::background_color ()
145 {
146 if (_need_bg) {
147 Gdk::Color bg (get_parent_bg());
148 return RGBA_TO_UINT (bg.get_red() / 255, bg.get_green() / 255, bg.get_blue() / 255, 255);
149 } else {
150 return 0;
151 }
152 }
153
154 bool
on_expose_event(GdkEventExpose * ev)155 CairoWidget::on_expose_event (GdkEventExpose *ev)
156 {
157 #ifdef __APPLE__
158 if (_nsglview) {
159 Gtkmm2ext::nsglview_queue_draw (_nsglview, ev->area.x, ev->area.y, ev->area.width, ev->area.height);
160 return true;
161 }
162 #endif
163 Cairo::RefPtr<Cairo::Context> cr;
164 if (_use_image_surface) {
165 if (!image_surface) {
166 image_surface = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, get_width(), get_height());
167 }
168 cr = Cairo::Context::create (image_surface);
169 } else {
170 cr = get_window()->create_cairo_context ();
171 }
172
173 cr->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
174
175 if (_need_bg) {
176 cr->clip_preserve ();
177
178 /* paint expose area the color of the parent window bg
179 */
180
181 Gdk::Color bg (get_parent_bg());
182
183 cr->set_source_rgb (bg.get_red_p(), bg.get_green_p(), bg.get_blue_p());
184 cr->fill ();
185 } else {
186 std::cerr << get_name() << " skipped bg fill\n";
187 cr->clip ();
188 }
189
190 cairo_rectangle_t expose_area;
191 expose_area.x = ev->area.x;
192 expose_area.y = ev->area.y;
193 expose_area.width = ev->area.width;
194 expose_area.height = ev->area.height;
195
196 render (cr, &expose_area);
197
198 if (_use_image_surface) {
199 image_surface->flush();
200 /* now blit our private surface back to the GDK one */
201 Cairo::RefPtr<Cairo::Context> window_context = get_window()->create_cairo_context ();
202 window_context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
203 window_context->clip ();
204 window_context->set_source (image_surface, 0, 0);
205 window_context->set_operator (Cairo::OPERATOR_SOURCE);
206 window_context->paint ();
207 }
208
209 return true;
210 }
211
212 /** Marks the widget as dirty, so that render () will be called on
213 * the next GTK expose event.
214 */
215
216 void
set_dirty(cairo_rectangle_t * area)217 CairoWidget::set_dirty (cairo_rectangle_t *area)
218 {
219 ENSURE_GUI_THREAD (*this, &CairoWidget::set_dirty);
220 if (!area) {
221 queue_draw ();
222 } else {
223 // TODO emit QueueDrawArea -> ArdourCanvas::Widget
224 if (QueueDraw ()) {
225 return;
226 }
227 queue_draw_area (area->x, area->y, area->width, area->height);
228 }
229 }
230
231 void
queue_draw()232 CairoWidget::queue_draw ()
233 {
234 if (QueueDraw ()) {
235 return;
236 }
237 Gtk::EventBox::queue_draw ();
238 }
239
240 void
queue_resize()241 CairoWidget::queue_resize ()
242 {
243 if (QueueResize ()) {
244 return;
245 }
246 Gtk::EventBox::queue_resize ();
247 }
248
249 /** Handle a size allocation.
250 * @param alloc GTK allocation.
251 */
252 void
on_size_allocate(Gtk::Allocation & alloc)253 CairoWidget::on_size_allocate (Gtk::Allocation& alloc)
254 {
255 if (!_canvas_widget) {
256 Gtk::EventBox::on_size_allocate (alloc);
257 } else {
258 memcpy (&_allocation, &alloc, sizeof(Gtk::Allocation));
259 }
260
261 if (_use_image_surface) {
262 image_surface.clear ();
263 image_surface = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, alloc.get_width(), alloc.get_height());
264 }
265
266 if (_canvas_widget) {
267 return;
268 }
269
270 #ifdef __APPLE__
271 if (_nsglview) {
272 gint xx, yy;
273 gtk_widget_translate_coordinates(
274 GTK_WIDGET(gobj()),
275 GTK_WIDGET(get_toplevel()->gobj()),
276 0, 0, &xx, &yy);
277 Gtkmm2ext::nsglview_resize (_nsglview, xx, yy, alloc.get_width(), alloc.get_height());
278 }
279 #endif
280 set_dirty ();
281 }
282
283 Gdk::Color
get_parent_bg()284 CairoWidget::get_parent_bg ()
285 {
286 Widget* parent;
287
288 parent = get_parent ();
289
290 while (parent) {
291 void* p = g_object_get_data (G_OBJECT(parent->gobj()), has_cairo_widget_background_info);
292
293 if (p) {
294 Glib::RefPtr<Gtk::Style> style = parent->get_style();
295 if (_current_parent != parent) {
296 if (_parent_style_change) _parent_style_change.disconnect();
297 _current_parent = parent;
298 _parent_style_change = parent->signal_style_changed().connect (mem_fun (*this, &CairoWidget::on_style_changed));
299 }
300 return style->get_bg (Gtk::STATE_NORMAL);
301 }
302
303 if (!parent->get_has_window()) {
304 parent = parent->get_parent();
305 } else {
306 break;
307 }
308 }
309
310 if (parent && parent->get_has_window()) {
311 if (_current_parent != parent) {
312 if (_parent_style_change) _parent_style_change.disconnect();
313 _current_parent = parent;
314 _parent_style_change = parent->signal_style_changed().connect (mem_fun (*this, &CairoWidget::on_style_changed));
315 }
316 return parent->get_style ()->get_bg (Gtk::STATE_NORMAL);
317 }
318
319 return get_style ()->get_bg (get_state());
320 }
321
322 void
set_active_state(Gtkmm2ext::ActiveState s)323 CairoWidget::set_active_state (Gtkmm2ext::ActiveState s)
324 {
325 if (_active_state != s) {
326 _active_state = s;
327 StateChanged ();
328 }
329 }
330
331 void
set_visual_state(Gtkmm2ext::VisualState s)332 CairoWidget::set_visual_state (Gtkmm2ext::VisualState s)
333 {
334 if (_visual_state != s) {
335 _visual_state = s;
336 StateChanged ();
337 }
338 }
339
340 void
set_active(bool yn)341 CairoWidget::set_active (bool yn)
342 {
343 /* this is an API simplification for buttons
344 that only use the Active and Normal states.
345 */
346
347 if (yn) {
348 set_active_state (Gtkmm2ext::ExplicitActive);
349 } else {
350 unset_active_state ();
351 }
352 }
353
354 void
on_style_changed(const Glib::RefPtr<Gtk::Style> &)355 CairoWidget::on_style_changed (const Glib::RefPtr<Gtk::Style>&)
356 {
357 set_dirty ();
358 }
359
360 void
on_realize()361 CairoWidget::on_realize ()
362 {
363 Gtk::EventBox::on_realize();
364 #ifdef __APPLE__
365 if (_nsglview) {
366 Gtkmm2ext::nsglview_overlay (_nsglview, get_window()->gobj());
367 }
368 #endif
369 }
370
371 void
on_map()372 CairoWidget::on_map ()
373 {
374 Gtk::EventBox::on_map();
375 #ifdef __APPLE__
376 if (_nsglview) {
377 Gtkmm2ext::nsglview_set_visible (_nsglview, true);
378 Gtk::Allocation a = get_allocation();
379 gint xx, yy;
380 gtk_widget_translate_coordinates(
381 GTK_WIDGET(gobj()),
382 GTK_WIDGET(get_toplevel()->gobj()),
383 0, 0, &xx, &yy);
384 Gtkmm2ext::nsglview_resize (_nsglview, xx, yy, a.get_width(), a.get_height());
385 }
386 #endif
387 }
388
389 void
on_unmap()390 CairoWidget::on_unmap ()
391 {
392 Gtk::EventBox::on_unmap();
393 #ifdef __APPLE__
394 if (_nsglview) {
395 Gtkmm2ext::nsglview_set_visible (_nsglview, false);
396 }
397 #endif
398 }
399
400 void
on_state_changed(Gtk::StateType)401 CairoWidget::on_state_changed (Gtk::StateType)
402 {
403 /* this will catch GTK-level state changes from calls like
404 ::set_sensitive()
405 */
406
407 if (get_state() == Gtk::STATE_INSENSITIVE) {
408 set_visual_state (Gtkmm2ext::VisualState (visual_state() | Gtkmm2ext::Insensitive));
409 } else {
410 set_visual_state (Gtkmm2ext::VisualState (visual_state() & ~Gtkmm2ext::Insensitive));
411 }
412
413 set_dirty ();
414 }
415
416 void
set_draw_background(bool yn)417 CairoWidget::set_draw_background (bool yn)
418 {
419 _need_bg = yn;
420 }
421
422 void
provide_background_for_cairo_widget(Gtk::Widget & w,const Gdk::Color & bg)423 CairoWidget::provide_background_for_cairo_widget (Gtk::Widget& w, const Gdk::Color& bg)
424 {
425 /* set up @w to be able to provide bg information to
426 any CairoWidgets that are packed inside it.
427 */
428
429 w.modify_bg (Gtk::STATE_NORMAL, bg);
430 w.modify_bg (Gtk::STATE_INSENSITIVE, bg);
431 w.modify_bg (Gtk::STATE_ACTIVE, bg);
432 w.modify_bg (Gtk::STATE_SELECTED, bg);
433
434 g_object_set_data (G_OBJECT(w.gobj()), has_cairo_widget_background_info, (void*) 0xfeedface);
435 }
436
437 void
set_focus_handler(sigc::slot<void,Gtk::Widget * > s)438 CairoWidget::set_focus_handler (sigc::slot<void,Gtk::Widget*> s)
439 {
440 focus_handler = s;
441 }
442
443 void
on_widget_name_changed()444 CairoWidget::on_widget_name_changed ()
445 {
446 Glib::ustring name = get_name();
447 if (_widget_name == name) {
448 return;
449 }
450 _widget_name = name;
451 on_name_changed ();
452 }
453