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