0632273e7e4d497dda928773af0d2b01d3fc87fc
[WebKit-https.git] / Source / WebCore / platform / gtk / WidgetRenderingContext.cpp
1 /*
2  * Copyright (C) 2010 Sencha, Inc.
3  * Copyright (C) 2010 Igalia S.L.
4  *
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
17  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
20  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
21  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
22  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
23  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
24  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 #ifdef GTK_API_VERSION_2
30
31 #include "config.h"
32 #include "WidgetRenderingContext.h"
33
34 #include "GraphicsContext.h"
35 #include "GtkVersioning.h"
36 #include "RefPtrCairo.h"
37 #include "RenderThemeGtk.h"
38 #include "Timer.h"
39 #include <gdk/gdk.h>
40 #include <gtk/gtk.h>
41
42 namespace WebCore {
43
44 static GdkPixmap* gScratchBuffer = 0;
45 static void purgeScratchBuffer()
46 {
47     if (gScratchBuffer)
48         g_object_unref(gScratchBuffer);
49     gScratchBuffer = 0;
50 }
51
52 // FIXME: Perhaps we can share some of this code with the ContextShadowCairo.
53 // Widget rendering needs a scratch image as the buffer for the intermediate
54 // render. Instead of creating and destroying the buffer for every operation,
55 // we create a buffer which will be automatically purged via a timer.
56 class PurgeScratchBufferTimer : public TimerBase {
57 private:
58     virtual void fired() { purgeScratchBuffer(); }
59 };
60 static PurgeScratchBufferTimer purgeScratchBufferTimer;
61 static void scheduleScratchBufferPurge()
62 {
63     if (purgeScratchBufferTimer.isActive())
64         purgeScratchBufferTimer.stop();
65     purgeScratchBufferTimer.startOneShot(2);
66 }
67
68 WidgetRenderingContext::WidgetRenderingContext(GraphicsContext* graphicsContext, const IntRect& targetRect)
69     : m_graphicsContext(graphicsContext)
70     , m_targetRect(targetRect)
71     , m_hadError(false)
72 {
73     RenderThemeGtk* theme = static_cast<RenderThemeGtk*>(RenderTheme::defaultTheme().get());
74
75     // Fallback: We failed to create an RGBA colormap earlier, so we cannot properly paint 
76     // to a temporary surface and preserve transparency. To ensure decent widget rendering, just
77     // paint directly to the target drawable. This will not render CSS rotational transforms properly.
78     if (!theme->m_themePartsHaveRGBAColormap && graphicsContext->gdkWindow()) {
79         m_paintRect = graphicsContext->getCTM().mapRect(targetRect);
80         m_target = graphicsContext->gdkWindow();
81         return;
82     }
83
84     // Widgets sometimes need to draw outside their boundaries for things such as
85     // exterior focus. We want to allocate a some extra pixels in our surface for this.
86     m_extraSpace = IntSize(15, 15);
87
88     // Offset the target rectangle so that the extra space is within the boundaries of the scratch buffer.
89     m_paintRect = IntRect(IntPoint(m_extraSpace.width(), m_extraSpace.height()),
90                                    m_targetRect.size());
91
92     int width = m_targetRect.width() + (m_extraSpace.width() * 2);
93     int height = m_targetRect.height() + (m_extraSpace.height() * 2);
94     int scratchWidth = 0;
95     int scratchHeight = 0;
96     if (gScratchBuffer)
97         gdk_pixmap_get_size(gScratchBuffer, &scratchWidth, &scratchHeight);
98
99     // We do not need to recreate the buffer if the current buffer is large enough.
100     if (!gScratchBuffer || scratchWidth < width || scratchHeight < height) {
101         purgeScratchBuffer();
102         // Round to the nearest 32 pixels so we do not grow the buffer for similar sized requests.
103         width = (1 + (width >> 5)) << 5;
104         height = (1 + (height >> 5)) << 5;
105
106         gScratchBuffer = gdk_pixmap_new(0, width, height, gdk_colormap_get_visual(theme->m_themeParts.colormap)->depth);
107         gdk_drawable_set_colormap(gScratchBuffer, theme->m_themeParts.colormap);
108     }
109     m_target = gScratchBuffer;
110
111     // Clear the scratch buffer.
112     RefPtr<cairo_t> scratchBufferContext = adoptRef(gdk_cairo_create(gScratchBuffer));
113     cairo_set_operator(scratchBufferContext.get(), CAIRO_OPERATOR_CLEAR);
114     cairo_paint(scratchBufferContext.get());
115 }
116
117 WidgetRenderingContext::~WidgetRenderingContext()
118 {
119     // We do not need to blit back to the target in the fallback case. See above.
120     RenderThemeGtk* theme = static_cast<RenderThemeGtk*>(RenderTheme::defaultTheme().get());
121     if (!theme->m_themePartsHaveRGBAColormap && m_graphicsContext->gdkWindow())
122         return;
123
124     // Don't paint the results back if there was an error.
125     if (m_hadError) {
126         scheduleScratchBufferPurge();
127         return;
128     }
129
130     // FIXME: It's unclear if it is necessary to preserve the current source here.
131     cairo_t* cairoContext = m_graphicsContext->platformContext();
132     RefPtr<cairo_pattern_t> previousSource(cairo_get_source(cairoContext));
133
134     // The blit rectangle is the original target rectangle adjusted for any extra space.
135     IntRect fullTargetRect(m_targetRect);
136     fullTargetRect.inflateX(m_extraSpace.width());
137     fullTargetRect.inflateY(m_extraSpace.height());
138
139     gdk_cairo_set_source_pixmap(cairoContext, gScratchBuffer, fullTargetRect.x(), fullTargetRect.y());
140     cairo_rectangle(cairoContext, fullTargetRect.x(), fullTargetRect.y(), fullTargetRect.width(), fullTargetRect.height());
141     cairo_fill(cairoContext);
142     cairo_set_source(cairoContext, previousSource.get());
143     scheduleScratchBufferPurge();
144 }
145
146 bool WidgetRenderingContext::paintMozillaWidget(GtkThemeWidgetType type, GtkWidgetState* state, int flags, GtkTextDirection textDirection)
147 {
148     // Sometimes moz_gtk_widget_paint modifies the clipping rectangle, so we must use a copy.
149     GdkRectangle clipRect = m_paintRect;
150     m_hadError = moz_gtk_widget_paint(type, m_target, &clipRect, &m_paintRect,
151         state, flags, textDirection) != MOZ_GTK_SUCCESS;
152     return !m_hadError;
153 }
154
155 void WidgetRenderingContext::gtkPaintBox(const IntRect& rect, GtkWidget* widget, GtkStateType stateType, GtkShadowType shadowType, const gchar* detail)
156 {
157     GdkRectangle paintRect = { m_paintRect.x + rect.x(), m_paintRect.y + rect.y(), rect.width(), rect.height() };
158
159     // Some widgets also need their allocation adjusted to account for extra space.
160     // Right now only scrollbar buttons have significant allocations.
161     GtkAllocation allocation;
162     gtk_widget_get_allocation(widget, &allocation);
163     allocation.x += m_paintRect.x;
164     allocation.y += m_paintRect.y;
165     gtk_widget_set_allocation(widget, &allocation);
166
167     gtk_paint_box(gtk_widget_get_style(widget), m_target, stateType, shadowType, &paintRect,
168                   widget, detail, paintRect.x, paintRect.y, paintRect.width, paintRect.height);
169 }
170
171 void WidgetRenderingContext::gtkPaintFlatBox(const IntRect& rect, GtkWidget* widget, GtkStateType stateType, GtkShadowType shadowType, const gchar* detail)
172 {
173     GdkRectangle paintRect = { m_paintRect.x + rect.x(), m_paintRect.y + rect.y(), rect.width(), rect.height() };
174     gtk_paint_flat_box(gtk_widget_get_style(widget), m_target, stateType, shadowType, &paintRect,
175                        widget, detail, paintRect.x, paintRect.y, paintRect.width, paintRect.height);
176 }
177
178 void WidgetRenderingContext::gtkPaintFocus(const IntRect& rect, GtkWidget* widget, GtkStateType stateType, const gchar* detail)
179 {
180     GdkRectangle paintRect = { m_paintRect.x + rect.x(), m_paintRect.y + rect.y(), rect.width(), rect.height() };
181     gtk_paint_focus(gtk_widget_get_style(widget), m_target, stateType, &paintRect, widget,
182                     detail, paintRect.x, paintRect.y, paintRect.width, paintRect.height);
183 }
184
185 void WidgetRenderingContext::gtkPaintSlider(const IntRect& rect, GtkWidget* widget, GtkStateType stateType, GtkShadowType shadowType, const gchar* detail, GtkOrientation orientation)
186 {
187     GdkRectangle paintRect = { m_paintRect.x + rect.x(), m_paintRect.y + rect.y(), rect.width(), rect.height() };
188     gtk_paint_slider(gtk_widget_get_style(widget), m_target, stateType, shadowType, &m_paintRect, widget,
189                      detail, paintRect.x, paintRect.y, paintRect.width, paintRect.height, orientation);
190 }
191
192 void WidgetRenderingContext::gtkPaintCheck(const IntRect& rect, GtkWidget* widget, GtkStateType stateType, GtkShadowType shadowType, const gchar* detail)
193 {
194     GdkRectangle paintRect = { m_paintRect.x + rect.x(), m_paintRect.y + rect.y(), rect.width(), rect.height() };
195     gtk_paint_check(gtk_widget_get_style(widget), m_target, stateType, shadowType, &paintRect, widget,
196                     detail, paintRect.x, paintRect.y, paintRect.width, paintRect.height);
197 }
198
199 void WidgetRenderingContext::gtkPaintOption(const IntRect& rect, GtkWidget* widget, GtkStateType stateType, GtkShadowType shadowType, const gchar* detail)
200 {
201     GdkRectangle paintRect = { m_paintRect.x + rect.x(), m_paintRect.y + rect.y(), rect.width(), rect.height() };
202     gtk_paint_option(gtk_widget_get_style(widget), m_target, stateType, shadowType, &paintRect, widget,
203                      detail, paintRect.x, paintRect.y, paintRect.width, paintRect.height);
204 }
205
206 void WidgetRenderingContext::gtkPaintShadow(const IntRect& rect, GtkWidget* widget, GtkStateType stateType, GtkShadowType shadowType, const gchar* detail)
207 {
208     GdkRectangle paintRect = { m_paintRect.x + rect.x(), m_paintRect.y + rect.y(), rect.width(), rect.height() };
209     gtk_paint_shadow(gtk_widget_get_style(widget), m_target, stateType, shadowType, &paintRect, widget,
210                      detail, paintRect.x, paintRect.y, paintRect.width, paintRect.height);
211 }
212
213 void WidgetRenderingContext::gtkPaintArrow(const IntRect& rect, GtkWidget* widget, GtkStateType stateType, GtkShadowType shadowType, int arrowDirection, const gchar* detail)
214 {
215     GdkRectangle paintRect = { m_paintRect.x + rect.x(), m_paintRect.y + rect.y(), rect.width(), rect.height() };
216     gtk_paint_arrow(gtk_widget_get_style(widget), m_target, stateType, shadowType, &paintRect, widget, detail,
217                     static_cast<GtkArrowType>(arrowDirection), TRUE, paintRect.x, paintRect.y, paintRect.width, paintRect.height);
218 }
219
220 void WidgetRenderingContext::gtkPaintVLine(const IntRect& rect, GtkWidget* widget, GtkStateType stateType, const gchar* detail)
221 {
222     GdkRectangle paintRect = { m_paintRect.x + rect.x(), m_paintRect.y + rect.y(), rect.width(), rect.height() };
223     gtk_paint_vline(gtk_widget_get_style(widget), m_target, stateType, &paintRect, widget, detail,
224                     paintRect.y, paintRect.y + paintRect.height, paintRect.x);
225
226 }
227
228 }
229
230 #endif // GTK_API_VERSION_2