widespread changes to get the new (oldArdour binding scheme to be used for keyboard...
[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
26 #include "i18n.h"
27
28 static const char* has_cairo_widget_background_info = "has_cairo_widget_background_info";
29
30 bool CairoWidget::_flat_buttons = false;
31 bool CairoWidget::_widget_prelight = true;
32
33 static void noop() { }
34 sigc::slot<void> CairoWidget::focus_handler (sigc::ptr_fun (noop));
35
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
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 {
53         _name_proxy.connect (sigc::mem_fun (*this, &CairoWidget::on_name_changed));
54 }
55
56 CairoWidget::~CairoWidget ()
57 {
58         if (_parent_style_change) _parent_style_change.disconnect();
59 }
60
61 bool
62 CairoWidget::on_button_press_event (GdkEventButton*)
63 {
64         focus_handler();
65         return false;
66 }
67
68
69 #ifdef USE_TRACKS_CODE_FEATURES
70
71 /* This is Tracks version of this method.
72
73    The use of get_visible_window() in this method is an abuse of the GDK/GTK
74    semantics. It can and may break on different GDK backends, and uses a
75    side-effect/unintended behaviour in GDK/GTK to try to accomplish something
76    which should be done differently. I (Paul) have confirmed this with the GTK
77    development team.
78
79    For this reason, this code is not acceptable for ordinary merging into the Ardour libraries.
80
81    Ardour Developers: you are not obligated to maintain the internals of this
82    implementation in the face of build-time environment changes (e.g. -D
83    defines etc).
84 */
85
86 bool
87 CairoWidget::on_expose_event (GdkEventExpose *ev)
88 {
89         cairo_rectangle_t expose_area;
90         expose_area.width = ev->area.width;
91         expose_area.height = ev->area.height;
92
93 #ifdef USE_CAIRO_IMAGE_SURFACE_FOR_CAIRO_WIDGET
94         Cairo::RefPtr<Cairo::Context> cr;
95         if (get_visible_window ()) {
96                 expose_area.x = 0;
97                 expose_area.y = 0;
98                 if (!_image_surface) {
99                         _image_surface = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, get_width(), get_height());
100                 }
101                 cr = Cairo::Context::create (_image_surface);
102         } else {
103                 expose_area.x = ev->area.x;
104                 expose_area.y = ev->area.y;
105                 cr = get_window()->create_cairo_context ();
106         }
107 #else
108         expose_area.x = ev->area.x;
109         expose_area.y = ev->area.y;
110         Cairo::RefPtr<Cairo::Context> cr = get_window()->create_cairo_context ();
111 #endif
112
113         cr->rectangle (expose_area.x, expose_area.y, expose_area.width, expose_area.height);
114         cr->clip ();
115
116         /* paint expose area the color of the parent window bg
117         */
118
119     if (get_visible_window ()) {
120         Gdk::Color bg (get_parent_bg());
121                 cr->rectangle (expose_area.x, expose_area.y, expose_area.width, expose_area.height);
122         cr->set_source_rgb (bg.get_red_p(), bg.get_green_p(), bg.get_blue_p());
123         cr->fill ();
124     }
125
126         render (cr->cobj(), &expose_area);
127
128 #ifdef USE_CAIRO_IMAGE_SURFACE_FOR_CAIRO_WIDGET
129         if(get_visible_window ()) {
130                 _image_surface->flush();
131                 /* now blit our private surface back to the GDK one */
132
133                 Cairo::RefPtr<Cairo::Context> cairo_context = get_window()->create_cairo_context ();
134
135                 cairo_context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
136                 cairo_context->clip ();
137                 cairo_context->set_source (_image_surface, ev->area.x, ev->area.y);
138                 cairo_context->set_operator (Cairo::OPERATOR_OVER);
139                 cairo_context->paint ();
140         }
141 #endif
142
143         Gtk::Widget* child = get_child ();
144
145         if (child) {
146                 propagate_expose (*child, ev);
147         }
148
149         return true;
150 }
151
152 #else
153
154 /* Ardour mainline: not using Tracks code features.
155
156    Tracks Developers: please do not modify this version of
157    ::on_expose_event(). The version used by Tracks is before the preceding
158    #else and contains hacks required for the Tracks GUI to work.
159 */
160
161 bool
162 CairoWidget::on_expose_event (GdkEventExpose *ev)
163 {
164 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
165         Cairo::RefPtr<Cairo::Context> cr;
166         if (getenv("ARDOUR_IMAGE_SURFACE")) {
167                 if (!image_surface) {
168                         image_surface = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, get_width(), get_height());
169                 }
170                 cr = Cairo::Context::create (image_surface);
171         } else {
172                 cr = get_window()->create_cairo_context ();
173         }
174 #elif defined USE_CAIRO_IMAGE_SURFACE
175
176         if (!image_surface) {
177                 image_surface = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, get_width(), get_height());
178         }
179
180         Cairo::RefPtr<Cairo::Context> cr = Cairo::Context::create (image_surface);
181 #else
182         Cairo::RefPtr<Cairo::Context> cr = get_window()->create_cairo_context ();
183 #endif
184
185         cr->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
186         cr->clip_preserve ();
187
188         /* paint expose area the color of the parent window bg
189         */
190
191         Gdk::Color bg (get_parent_bg());
192
193         cr->set_source_rgb (bg.get_red_p(), bg.get_green_p(), bg.get_blue_p());
194         cr->fill ();
195
196         cairo_rectangle_t expose_area;
197         expose_area.x = ev->area.x;
198         expose_area.y = ev->area.y;
199         expose_area.width = ev->area.width;
200         expose_area.height = ev->area.height;
201
202         render (cr->cobj(), &expose_area);
203
204 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
205         if (getenv("ARDOUR_IMAGE_SURFACE")) {
206 #endif
207 #if defined USE_CAIRO_IMAGE_SURFACE || defined OPTIONAL_CAIRO_IMAGE_SURFACE
208         image_surface->flush();
209         /* now blit our private surface back to the GDK one */
210
211         Cairo::RefPtr<Cairo::Context> cairo_context = get_window()->create_cairo_context ();
212
213         cairo_context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
214         cairo_context->clip ();
215         cairo_context->set_source (image_surface, 0, 0);
216         cairo_context->set_operator (Cairo::OPERATOR_SOURCE);
217         cairo_context->paint ();
218 #endif
219 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
220         }
221 #endif
222
223         return true;
224 }
225
226 #endif
227
228 /** Marks the widget as dirty, so that render () will be called on
229  *  the next GTK expose event.
230  */
231
232 void
233 CairoWidget::set_dirty (cairo_rectangle_t *area)
234 {
235         ENSURE_GUI_THREAD (*this, &CairoWidget::set_dirty);
236         if (!area) {
237                 queue_draw ();
238         } else {
239                 queue_draw_area (area->x, area->y, area->width, area->height);
240         }
241 }
242
243 /** Handle a size allocation.
244  *  @param alloc GTK allocation.
245  */
246 void
247 CairoWidget::on_size_allocate (Gtk::Allocation& alloc)
248 {
249         Gtk::EventBox::on_size_allocate (alloc);
250
251 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
252         if (getenv("ARDOUR_IMAGE_SURFACE")) {
253 #endif
254 #if defined USE_CAIRO_IMAGE_SURFACE || defined OPTIONAL_CAIRO_IMAGE_SURFACE
255         image_surface = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, alloc.get_width(), alloc.get_height());
256 #endif
257 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
258         }
259 #endif
260
261         set_dirty ();
262 }
263
264 Gdk::Color
265 CairoWidget::get_parent_bg ()
266 {
267         Widget* parent;
268
269         parent = get_parent ();
270
271         while (parent) {
272                 void* p = g_object_get_data (G_OBJECT(parent->gobj()), has_cairo_widget_background_info);
273
274                 if (p) {
275                         Glib::RefPtr<Gtk::Style> style = parent->get_style();
276                         if (_current_parent != parent) {
277                                 if (_parent_style_change) _parent_style_change.disconnect();
278                                 _current_parent = parent;
279                                 _parent_style_change = parent->signal_style_changed().connect (mem_fun (*this, &CairoWidget::on_style_changed));
280                         }
281                         return style->get_bg (get_state());
282                 }
283
284                 if (!parent->get_has_window()) {
285                         parent = parent->get_parent();
286                 } else {
287                         break;
288                 }
289         }
290
291         if (parent && parent->get_has_window()) {
292                 if (_current_parent != parent) {
293                         if (_parent_style_change) _parent_style_change.disconnect();
294                         _current_parent = parent;
295                         _parent_style_change = parent->signal_style_changed().connect (mem_fun (*this, &CairoWidget::on_style_changed));
296                 }
297                 return parent->get_style ()->get_bg (parent->get_state());
298         }
299
300         return get_style ()->get_bg (get_state());
301 }
302
303 void
304 CairoWidget::set_active_state (Gtkmm2ext::ActiveState s)
305 {
306         if (_active_state != s) {
307                 _active_state = s;
308                 StateChanged ();
309         }
310 }
311
312 void
313 CairoWidget::set_visual_state (Gtkmm2ext::VisualState s)
314 {
315         if (_visual_state != s) {
316                 _visual_state = s;
317                 StateChanged ();
318         }
319 }
320
321 void
322 CairoWidget::set_active (bool yn)
323 {
324         /* this is an API simplification for buttons
325            that only use the Active and Normal states.
326         */
327
328         if (yn) {
329                 set_active_state (Gtkmm2ext::ExplicitActive);
330         } else {
331                 unset_active_state ();
332         }
333 }
334
335 void
336 CairoWidget::on_style_changed (const Glib::RefPtr<Gtk::Style>&)
337 {
338         queue_draw();
339 }
340
341 void
342 CairoWidget::on_state_changed (Gtk::StateType)
343 {
344         /* this will catch GTK-level state changes from calls like
345            ::set_sensitive()
346         */
347
348         if (get_state() == Gtk::STATE_INSENSITIVE) {
349                 set_visual_state (Gtkmm2ext::VisualState (visual_state() | Gtkmm2ext::Insensitive));
350         } else {
351                 set_visual_state (Gtkmm2ext::VisualState (visual_state() & ~Gtkmm2ext::Insensitive));
352         }
353
354         queue_draw ();
355 }
356
357 void
358 CairoWidget::set_draw_background (bool yn)
359 {
360         _need_bg = yn;
361 }
362
363 void
364 CairoWidget::provide_background_for_cairo_widget (Gtk::Widget& w, const Gdk::Color& bg)
365 {
366         /* set up @w to be able to provide bg information to
367            any CairoWidgets that are packed inside it.
368         */
369
370         w.modify_bg (Gtk::STATE_NORMAL, bg);
371         w.modify_bg (Gtk::STATE_INSENSITIVE, bg);
372         w.modify_bg (Gtk::STATE_ACTIVE, bg);
373         w.modify_bg (Gtk::STATE_SELECTED, bg);
374
375         g_object_set_data (G_OBJECT(w.gobj()), has_cairo_widget_background_info, (void*) 0xfeedface);
376 }
377
378 void
379 CairoWidget::set_flat_buttons (bool yn)
380 {
381         _flat_buttons = yn;
382 }
383
384 void
385 CairoWidget::set_widget_prelight (bool yn)
386 {
387         _widget_prelight = yn;
388 }
389
390 void
391 CairoWidget::set_focus_handler (sigc::slot<void> s)
392 {
393         focus_handler = s;
394 }