Replace OptionSet |= and -= operators with add() and remove() functions
[WebKit-https.git] / Source / WebCore / platform / gtk / RenderThemeGadget.cpp
1 /*
2  * Copyright (C) 2016 Igalia S.L.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "RenderThemeGadget.h"
28
29 #if GTK_CHECK_VERSION(3, 20, 0)
30
31 #include "FloatRect.h"
32 #include "GRefPtrGtk.h"
33
34 namespace WebCore {
35
36 std::unique_ptr<RenderThemeGadget> RenderThemeGadget::create(const RenderThemeGadget::Info& info, RenderThemeGadget* parent, const Vector<RenderThemeGadget::Info> siblings, unsigned position)
37 {
38     switch (info.type) {
39     case RenderThemeGadget::Type::Generic:
40         return std::make_unique<RenderThemeGadget>(info, parent, siblings, position);
41     case RenderThemeGadget::Type::TextField:
42         return std::make_unique<RenderThemeTextFieldGadget>(info, parent, siblings, position);
43     case RenderThemeGadget::Type::Radio:
44     case RenderThemeGadget::Type::Check:
45         return std::make_unique<RenderThemeToggleGadget>(info, parent, siblings, position);
46     case RenderThemeGadget::Type::Arrow:
47         return std::make_unique<RenderThemeArrowGadget>(info, parent, siblings, position);
48     case RenderThemeGadget::Type::Icon:
49         return std::make_unique<RenderThemeIconGadget>(info, parent, siblings, position);
50     case RenderThemeGadget::Type::Scrollbar:
51         return std::make_unique<RenderThemeScrollbarGadget>(info, parent, siblings, position);
52     case RenderThemeGadget::Type::Button:
53         return std::make_unique<RenderThemeButtonGadget>(info, parent, siblings, position);
54     }
55
56     ASSERT_NOT_REACHED();
57     return nullptr;
58 }
59
60 static GRefPtr<GtkStyleContext> createStyleContext(GtkWidgetPath* path, GtkStyleContext* parent)
61 {
62     GRefPtr<GtkStyleContext> context = adoptGRef(gtk_style_context_new());
63     gtk_style_context_set_path(context.get(), path);
64     gtk_style_context_set_parent(context.get(), parent);
65     return context;
66 }
67
68 static void appendElementToPath(GtkWidgetPath* path, const RenderThemeGadget::Info& info)
69 {
70     // Scrollbars need to use its GType to be able to get non-CSS style properties.
71     gtk_widget_path_append_type(path, info.type == RenderThemeGadget::Type::Scrollbar ? GTK_TYPE_SCROLLBAR : G_TYPE_NONE);
72     gtk_widget_path_iter_set_object_name(path, -1, info.name);
73     for (const auto* className : info.classList)
74         gtk_widget_path_iter_add_class(path, -1, className);
75 }
76
77 RenderThemeGadget::RenderThemeGadget(const RenderThemeGadget::Info& info, RenderThemeGadget* parent, const Vector<RenderThemeGadget::Info> siblings, unsigned position)
78 {
79     GRefPtr<GtkWidgetPath> path = parent ? adoptGRef(gtk_widget_path_copy(gtk_style_context_get_path(parent->context()))) : adoptGRef(gtk_widget_path_new());
80     if (!siblings.isEmpty()) {
81         GRefPtr<GtkWidgetPath> siblingsPath = adoptGRef(gtk_widget_path_new());
82         for (const auto& siblingInfo : siblings)
83             appendElementToPath(siblingsPath.get(), siblingInfo);
84         gtk_widget_path_append_with_siblings(path.get(), siblingsPath.get(), position);
85     } else
86         appendElementToPath(path.get(), info);
87     m_context = createStyleContext(path.get(), parent ? parent->context() : nullptr);
88 }
89
90 RenderThemeGadget::~RenderThemeGadget() = default;
91
92 GtkBorder RenderThemeGadget::marginBox() const
93 {
94     GtkBorder returnValue;
95     gtk_style_context_get_margin(m_context.get(), gtk_style_context_get_state(m_context.get()), &returnValue);
96     return returnValue;
97 }
98
99 GtkBorder RenderThemeGadget::borderBox() const
100 {
101     GtkBorder returnValue;
102     gtk_style_context_get_border(m_context.get(), gtk_style_context_get_state(m_context.get()), &returnValue);
103     return returnValue;
104 }
105
106 GtkBorder RenderThemeGadget::paddingBox() const
107 {
108     GtkBorder returnValue;
109     gtk_style_context_get_padding(m_context.get(), gtk_style_context_get_state(m_context.get()), &returnValue);
110     return returnValue;
111 }
112
113 GtkBorder RenderThemeGadget::contentsBox() const
114 {
115     auto margin = marginBox();
116     auto border = borderBox();
117     auto padding = paddingBox();
118     padding.left += margin.left + border.left;
119     padding.right += margin.right + border.right;
120     padding.top += margin.top + border.top;
121     padding.bottom += margin.bottom + border.bottom;
122     return padding;
123 }
124
125 Color RenderThemeGadget::color() const
126 {
127     GdkRGBA returnValue;
128     gtk_style_context_get_color(m_context.get(), gtk_style_context_get_state(m_context.get()), &returnValue);
129     return returnValue;
130 }
131
132 Color RenderThemeGadget::backgroundColor() const
133 {
134     GdkRGBA returnValue;
135     gtk_style_context_get_background_color(m_context.get(), gtk_style_context_get_state(m_context.get()), &returnValue);
136     return returnValue;
137 }
138
139 double RenderThemeGadget::opacity() const
140 {
141     double returnValue;
142     gtk_style_context_get(m_context.get(), gtk_style_context_get_state(m_context.get()), "opacity", &returnValue, nullptr);
143     return returnValue;
144 }
145
146 GtkStateFlags RenderThemeGadget::state() const
147 {
148     return gtk_style_context_get_state(m_context.get());
149 }
150
151 void RenderThemeGadget::setState(GtkStateFlags state)
152 {
153     gtk_style_context_set_state(m_context.get(), state);
154 }
155
156 IntSize RenderThemeGadget::minimumSize() const
157 {
158     int width, height;
159     gtk_style_context_get(m_context.get(), gtk_style_context_get_state(m_context.get()), "min-width", &width, "min-height", &height, nullptr);
160     return IntSize(width, height);
161 }
162
163 IntSize RenderThemeGadget::preferredSize() const
164 {
165     auto margin = marginBox();
166     auto border = borderBox();
167     auto padding = paddingBox();
168     auto minSize = minimumSize();
169     minSize.expand(margin.left + margin.right + border.left + border.right + padding.left + padding.right,
170         margin.top + margin.bottom + border.top + border.bottom + padding.top + padding.bottom);
171     return minSize;
172 }
173
174 bool RenderThemeGadget::render(cairo_t* cr, const FloatRect& paintRect, FloatRect* contentsRect)
175 {
176     FloatRect rect = paintRect;
177
178     auto margin = marginBox();
179     rect.move(margin.left, margin.top);
180     rect.contract(margin.left + margin.right, margin.top + margin.bottom);
181
182     auto minSize = minimumSize();
183     rect.setWidth(std::max<float>(rect.width(), minSize.width()));
184     rect.setHeight(std::max<float>(rect.height(), minSize.height()));
185
186     gtk_render_background(m_context.get(), cr, rect.x(), rect.y(), rect.width(), rect.height());
187     gtk_render_frame(m_context.get(), cr, rect.x(), rect.y(), rect.width(), rect.height());
188
189     if (contentsRect) {
190         auto border = borderBox();
191         auto padding = paddingBox();
192         *contentsRect = rect;
193         contentsRect->move(border.left + padding.left, border.top + padding.top);
194         contentsRect->contract(border.left + border.right + padding.left + padding.right, border.top + border.bottom + padding.top + padding.bottom);
195     }
196
197     return true;
198 }
199
200 void RenderThemeGadget::renderFocus(cairo_t* cr, const FloatRect& focusRect)
201 {
202     FloatRect rect = focusRect;
203     auto margin = marginBox();
204     rect.move(margin.left, margin.top);
205     rect.contract(margin.left + margin.right, margin.top + margin.bottom);
206     gtk_render_focus(m_context.get(), cr, rect.x(), rect.y(), rect.width(), rect.height());
207 }
208
209 RenderThemeBoxGadget::RenderThemeBoxGadget(const RenderThemeGadget::Info& info, GtkOrientation orientation, const Vector<RenderThemeGadget::Info> children, RenderThemeGadget* parent)
210     : RenderThemeGadget(info, parent, Vector<RenderThemeGadget::Info>(), 0)
211     , m_orientation(orientation)
212 {
213     m_children.reserveCapacity(children.size());
214     unsigned index = 0;
215     for (const auto& childInfo : children)
216         m_children.uncheckedAppend(RenderThemeGadget::create(childInfo, this, children, index++));
217 }
218
219 IntSize RenderThemeBoxGadget::preferredSize() const
220 {
221     IntSize childrenSize;
222     for (const auto& child : m_children) {
223         IntSize childSize = child->preferredSize();
224         switch (m_orientation) {
225         case GTK_ORIENTATION_HORIZONTAL:
226             childrenSize.setWidth(childrenSize.width() + childSize.width());
227             childrenSize.setHeight(std::max(childrenSize.height(), childSize.height()));
228             break;
229         case GTK_ORIENTATION_VERTICAL:
230             childrenSize.setWidth(std::max(childrenSize.width(), childSize.width()));
231             childrenSize.setHeight(childrenSize.height() + childSize.height());
232             break;
233         }
234     }
235     return RenderThemeGadget::preferredSize().expandedTo(childrenSize);
236 }
237
238 RenderThemeTextFieldGadget::RenderThemeTextFieldGadget(const RenderThemeGadget::Info& info, RenderThemeGadget* parent, const Vector<RenderThemeGadget::Info> siblings, unsigned position)
239     : RenderThemeGadget(info, parent, siblings, position)
240 {
241 }
242
243 IntSize RenderThemeTextFieldGadget::minimumSize() const
244 {
245     // We allow text fields smaller than the min size set on themes.
246     return IntSize();
247 }
248
249 RenderThemeToggleGadget::RenderThemeToggleGadget(const RenderThemeGadget::Info& info, RenderThemeGadget* parent, const Vector<RenderThemeGadget::Info> siblings, unsigned position)
250     : RenderThemeGadget(info, parent, siblings, position)
251     , m_type(info.type)
252 {
253     ASSERT(m_type == RenderThemeGadget::Type::Radio || m_type == RenderThemeGadget::Type::Check);
254 }
255
256 bool RenderThemeToggleGadget::render(cairo_t* cr, const FloatRect& paintRect, FloatRect*)
257 {
258     FloatRect contentsRect;
259     RenderThemeGadget::render(cr, paintRect, &contentsRect);
260     if (m_type == RenderThemeGadget::Type::Radio)
261         gtk_render_option(m_context.get(), cr, contentsRect.x(), contentsRect.y(), contentsRect.width(), contentsRect.height());
262     else
263         gtk_render_check(m_context.get(), cr, contentsRect.x(), contentsRect.y(), contentsRect.width(), contentsRect.height());
264     return true;
265 }
266
267 RenderThemeArrowGadget::RenderThemeArrowGadget(const RenderThemeGadget::Info& info, RenderThemeGadget* parent, const Vector<RenderThemeGadget::Info> siblings, unsigned position)
268     : RenderThemeGadget(info, parent, siblings, position)
269 {
270 }
271
272 bool RenderThemeArrowGadget::render(cairo_t* cr, const FloatRect& paintRect, FloatRect*)
273 {
274     FloatRect contentsRect;
275     RenderThemeGadget::render(cr, paintRect, &contentsRect);
276     IntSize minSize = minimumSize();
277     int arrowSize = std::min(minSize.width(), minSize.height());
278     FloatPoint arrowPosition(contentsRect.x(), contentsRect.y() + (contentsRect.height() - arrowSize) / 2);
279     if (gtk_style_context_get_state(m_context.get()) & GTK_STATE_FLAG_DIR_LTR)
280         arrowPosition.move(contentsRect.width() - arrowSize, 0);
281     gtk_render_arrow(m_context.get(), cr, G_PI / 2, arrowPosition.x(), arrowPosition.y(), arrowSize);
282     return true;
283 }
284
285 RenderThemeIconGadget::RenderThemeIconGadget(const RenderThemeGadget::Info& info, RenderThemeGadget* parent, const Vector<RenderThemeGadget::Info> siblings, unsigned position)
286     : RenderThemeGadget(info, parent, siblings, position)
287 {
288 }
289
290 GtkIconSize RenderThemeIconGadget::gtkIconSizeForPixelSize(unsigned pixelSize) const
291 {
292     if (pixelSize < IconSizeGtk::SmallToolbar)
293         return GTK_ICON_SIZE_MENU;
294     if (pixelSize >= IconSizeGtk::SmallToolbar && pixelSize < IconSizeGtk::Button)
295         return GTK_ICON_SIZE_SMALL_TOOLBAR;
296     if (pixelSize >= IconSizeGtk::Button && pixelSize < IconSizeGtk::LargeToolbar)
297         return GTK_ICON_SIZE_BUTTON;
298     if (pixelSize >= IconSizeGtk::LargeToolbar && pixelSize < IconSizeGtk::DragAndDrop)
299         return GTK_ICON_SIZE_LARGE_TOOLBAR;
300     if (pixelSize >= IconSizeGtk::DragAndDrop && pixelSize < IconSizeGtk::Dialog)
301         return GTK_ICON_SIZE_DND;
302
303     return GTK_ICON_SIZE_DIALOG;
304 }
305
306 bool RenderThemeIconGadget::render(cairo_t* cr, const FloatRect& paintRect, FloatRect*)
307 {
308     ASSERT(!m_iconName.isNull());
309     GRefPtr<GIcon> icon = adoptGRef(g_themed_icon_new(m_iconName.data()));
310     unsigned lookupFlags = GTK_ICON_LOOKUP_USE_BUILTIN | GTK_ICON_LOOKUP_FORCE_SIZE | GTK_ICON_LOOKUP_FORCE_SVG;
311     GtkTextDirection direction = gtk_style_context_get_direction(m_context.get());
312     if (direction & GTK_TEXT_DIR_LTR)
313         lookupFlags |= GTK_ICON_LOOKUP_DIR_LTR;
314     else if (direction & GTK_TEXT_DIR_RTL)
315         lookupFlags |= GTK_ICON_LOOKUP_DIR_RTL;
316     int iconWidth, iconHeight;
317     if (!gtk_icon_size_lookup(gtkIconSizeForPixelSize(m_iconSize), &iconWidth, &iconHeight))
318         iconWidth = iconHeight = m_iconSize;
319     GRefPtr<GtkIconInfo> iconInfo = adoptGRef(gtk_icon_theme_lookup_by_gicon(gtk_icon_theme_get_default(), icon.get(),
320         std::min(iconWidth, iconHeight), static_cast<GtkIconLookupFlags>(lookupFlags)));
321     if (!iconInfo)
322         return false;
323
324     GRefPtr<GdkPixbuf> iconPixbuf = adoptGRef(gtk_icon_info_load_symbolic_for_context(iconInfo.get(), m_context.get(), nullptr, nullptr));
325     if (!iconPixbuf)
326         return false;
327
328     FloatSize pixbufSize(gdk_pixbuf_get_width(iconPixbuf.get()), gdk_pixbuf_get_height(iconPixbuf.get()));
329     FloatRect contentsRect;
330     RenderThemeGadget::render(cr, paintRect, &contentsRect);
331     if (pixbufSize.width() > contentsRect.width() || pixbufSize.height() > contentsRect.height()) {
332         iconWidth = iconHeight = std::min(contentsRect.width(), contentsRect.height());
333         pixbufSize = FloatSize(iconWidth, iconHeight);
334         iconPixbuf = adoptGRef(gdk_pixbuf_scale_simple(iconPixbuf.get(), pixbufSize.width(), pixbufSize.height(), GDK_INTERP_BILINEAR));
335     }
336
337     gtk_render_icon(m_context.get(), cr, iconPixbuf.get(), contentsRect.x() + (contentsRect.width() - pixbufSize.width()) / 2,
338         contentsRect.y() + (contentsRect.height() - pixbufSize.height()) / 2);
339     return true;
340 }
341
342 IntSize RenderThemeIconGadget::minimumSize() const
343 {
344     if (m_iconSize < IconSizeGtk::Menu)
345         return IntSize(m_iconSize, m_iconSize);
346
347     int iconWidth, iconHeight;
348     if (gtk_icon_size_lookup(gtkIconSizeForPixelSize(m_iconSize), &iconWidth, &iconHeight))
349         return IntSize(iconWidth, iconHeight);
350
351     return IntSize(m_iconSize, m_iconSize);
352 }
353
354 RenderThemeScrollbarGadget::RenderThemeScrollbarGadget(const RenderThemeGadget::Info& info, RenderThemeGadget* parent, const Vector<RenderThemeGadget::Info> siblings, unsigned position)
355     : RenderThemeGadget(info, parent, siblings, position)
356 {
357     gboolean hasBackward, hasForward, hasSecondaryBackward, hasSecondaryForward;
358     gtk_style_context_get_style(m_context.get(), "has-backward-stepper", &hasBackward, "has-forward-stepper", &hasForward,
359         "has-secondary-backward-stepper", &hasSecondaryBackward, "has-secondary-forward-stepper", &hasSecondaryForward, nullptr);
360     if (hasBackward)
361         m_steppers.add(Steppers::Backward);
362     if (hasForward)
363         m_steppers.add(Steppers::Forward);
364     if (hasSecondaryBackward)
365         m_steppers.add(Steppers::SecondaryBackward);
366     if (hasSecondaryForward)
367         m_steppers.add(Steppers::SecondaryForward);
368 }
369
370 void RenderThemeScrollbarGadget::renderStepper(cairo_t* cr, const FloatRect& paintRect, RenderThemeGadget* stepperGadget, GtkOrientation orientation, Steppers stepper)
371 {
372     FloatRect contentsRect;
373     stepperGadget->render(cr, paintRect, &contentsRect);
374     double angle;
375     switch (stepper) {
376     case Steppers::Backward:
377     case Steppers::SecondaryBackward:
378         angle = orientation == GTK_ORIENTATION_VERTICAL ? 0 : 3 * (G_PI / 2);
379         break;
380     case Steppers::Forward:
381     case Steppers::SecondaryForward:
382         angle = orientation == GTK_ORIENTATION_VERTICAL ? G_PI / 2 : G_PI;
383         break;
384     }
385
386     int stepperSize = std::max(contentsRect.width(), contentsRect.height());
387     gtk_render_arrow(stepperGadget->context(), cr, angle, contentsRect.x() + (contentsRect.width() - stepperSize) / 2,
388         contentsRect.y() + (contentsRect.height() - stepperSize) / 2, stepperSize);
389 }
390
391 RenderThemeButtonGadget::RenderThemeButtonGadget(const Info& info, RenderThemeGadget* parent, const Vector<RenderThemeGadget::Info> siblings, unsigned position)
392     : RenderThemeGadget(info, parent, siblings, position)
393 {
394 }
395
396 IntSize RenderThemeButtonGadget::minimumSize() const
397 {
398     // Allow buttons to be smaller than the minimum size
399     return IntSize();
400 }
401
402 } // namespace WebCore
403
404 #endif // GTK_CHECK_VERSION(3, 20, 0)