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