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