merge two small pieces of code by Valeriy into libs/gtkmm2ext, conditionally used...
[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 ()
234 {
235         ENSURE_GUI_THREAD (*this, &CairoWidget::set_dirty);
236         queue_draw ();
237 }
238
239 /** Handle a size allocation.
240  *  @param alloc GTK allocation.
241  */
242 void
243 CairoWidget::on_size_allocate (Gtk::Allocation& alloc)
244 {
245         Gtk::EventBox::on_size_allocate (alloc);
246
247 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
248         if (getenv("ARDOUR_IMAGE_SURFACE")) {
249 #endif
250 #if defined USE_CAIRO_IMAGE_SURFACE || defined OPTIONAL_CAIRO_IMAGE_SURFACE
251         image_surface = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, alloc.get_width(), alloc.get_height());
252 #endif
253 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
254         }
255 #endif
256
257         set_dirty ();
258 }
259
260 Gdk::Color
261 CairoWidget::get_parent_bg ()
262 {
263         Widget* parent;
264
265         parent = get_parent ();
266
267         while (parent) {
268                 void* p = g_object_get_data (G_OBJECT(parent->gobj()), has_cairo_widget_background_info);
269
270                 if (p) {
271                         Glib::RefPtr<Gtk::Style> style = parent->get_style();
272                         if (_current_parent != parent) {
273                                 if (_parent_style_change) _parent_style_change.disconnect();
274                                 _current_parent = parent;
275                                 _parent_style_change = parent->signal_style_changed().connect (mem_fun (*this, &CairoWidget::on_style_changed));
276                         }
277                         return style->get_bg (get_state());
278                 }
279
280                 if (!parent->get_has_window()) {
281                         parent = parent->get_parent();
282                 } else {
283                         break;
284                 }
285         }
286
287         if (parent && parent->get_has_window()) {
288                 if (_current_parent != parent) {
289                         if (_parent_style_change) _parent_style_change.disconnect();
290                         _current_parent = parent;
291                         _parent_style_change = parent->signal_style_changed().connect (mem_fun (*this, &CairoWidget::on_style_changed));
292                 }
293                 return parent->get_style ()->get_bg (parent->get_state());
294         }
295
296         return get_style ()->get_bg (get_state());
297 }
298
299 void
300 CairoWidget::set_active_state (Gtkmm2ext::ActiveState s)
301 {
302         if (_active_state != s) {
303                 _active_state = s;
304                 StateChanged ();
305         }
306 }
307
308 void
309 CairoWidget::set_visual_state (Gtkmm2ext::VisualState s)
310 {
311         if (_visual_state != s) {
312                 _visual_state = s;
313                 StateChanged ();
314         }
315 }
316
317 void
318 CairoWidget::set_active (bool yn)
319 {
320         /* this is an API simplification for buttons
321            that only use the Active and Normal states.
322         */
323
324         if (yn) {
325                 set_active_state (Gtkmm2ext::ExplicitActive);
326         } else {
327                 unset_active_state ();
328         }
329 }
330
331 void
332 CairoWidget::on_style_changed (const Glib::RefPtr<Gtk::Style>&)
333 {
334         queue_draw();
335 }
336
337 void
338 CairoWidget::on_state_changed (Gtk::StateType)
339 {
340         /* this will catch GTK-level state changes from calls like
341            ::set_sensitive()
342         */
343
344         if (get_state() == Gtk::STATE_INSENSITIVE) {
345                 set_visual_state (Gtkmm2ext::VisualState (visual_state() | Gtkmm2ext::Insensitive));
346         } else {
347                 set_visual_state (Gtkmm2ext::VisualState (visual_state() & ~Gtkmm2ext::Insensitive));
348         }
349
350         queue_draw ();
351 }
352
353 void
354 CairoWidget::set_draw_background (bool yn)
355 {
356         _need_bg = yn;
357 }
358
359 void
360 CairoWidget::provide_background_for_cairo_widget (Gtk::Widget& w, const Gdk::Color& bg)
361 {
362         /* set up @w to be able to provide bg information to
363            any CairoWidgets that are packed inside it.
364         */
365
366         w.modify_bg (Gtk::STATE_NORMAL, bg);
367         w.modify_bg (Gtk::STATE_INSENSITIVE, bg);
368         w.modify_bg (Gtk::STATE_ACTIVE, bg);
369         w.modify_bg (Gtk::STATE_SELECTED, bg);
370
371         g_object_set_data (G_OBJECT(w.gobj()), has_cairo_widget_background_info, (void*) 0xfeedface);
372 }
373
374 void
375 CairoWidget::set_flat_buttons (bool yn)
376 {
377         _flat_buttons = yn;
378 }
379
380 void
381 CairoWidget::set_widget_prelight (bool yn)
382 {
383         _widget_prelight = yn;
384 }
385
386 void
387 CairoWidget::set_focus_handler (sigc::slot<void> s)
388 {
389         focus_handler = s;
390 }