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