| /* |
| * Copyright (C) 2007 Apple Inc. |
| * Copyright (C) 2007 Alp Toker <alp@atoker.com> |
| * Copyright (C) 2008 Collabora Ltd. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public License |
| * along with this library; see the file COPYING.LIB. If not, write to |
| * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| * |
| */ |
| |
| #include "config.h" |
| #include "RenderThemeGtk.h" |
| |
| #include "AffineTransform.h" |
| #include "GraphicsContext.h" |
| #include "NotImplemented.h" |
| #include "RenderObject.h" |
| #include "gtkdrawing.h" |
| |
| #include <gdk/gdk.h> |
| |
| namespace WebCore { |
| |
| RenderTheme* theme() |
| { |
| static RenderThemeGtk gtkTheme; |
| return >kTheme; |
| } |
| |
| static bool mozGtkInitialized = false; |
| |
| RenderThemeGtk::RenderThemeGtk() |
| : m_gtkWindow(0) |
| , m_gtkContainer(0) |
| , m_gtkEntry(0) |
| , m_gtkTreeView(0) |
| { |
| if (!mozGtkInitialized) { |
| mozGtkInitialized = true; |
| moz_gtk_init(); |
| } |
| } |
| |
| static bool supportsFocus(EAppearance appearance) |
| { |
| switch (appearance) { |
| case PushButtonAppearance: |
| case ButtonAppearance: |
| case TextFieldAppearance: |
| case TextAreaAppearance: |
| case SearchFieldAppearance: |
| case MenulistAppearance: |
| case RadioAppearance: |
| case CheckboxAppearance: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| bool RenderThemeGtk::supportsFocusRing(const RenderStyle* style) const |
| { |
| return supportsFocus(style->appearance()); |
| } |
| |
| bool RenderThemeGtk::controlSupportsTints(const RenderObject* o) const |
| { |
| return isEnabled(o); |
| } |
| |
| int RenderThemeGtk::baselinePosition(const RenderObject* o) const |
| { |
| // FIXME: This strategy is possibly incorrect for the GTK+ port. |
| if (o->style()->appearance() == CheckboxAppearance || |
| o->style()->appearance() == RadioAppearance) |
| return o->marginTop() + o->height() - 2; |
| return RenderTheme::baselinePosition(o); |
| } |
| |
| static GtkTextDirection gtkTextDirection(TextDirection direction) |
| { |
| switch (direction) { |
| case RTL: |
| return GTK_TEXT_DIR_RTL; |
| case LTR: |
| return GTK_TEXT_DIR_LTR; |
| default: |
| return GTK_TEXT_DIR_NONE; |
| } |
| } |
| |
| static void adjustMozStyle(RenderStyle* style, GtkThemeWidgetType type) |
| { |
| gint left, top, right, bottom; |
| GtkTextDirection direction = gtkTextDirection(style->direction()); |
| gboolean inhtml = true; |
| |
| if (moz_gtk_get_widget_border(type, &left, &top, &right, &bottom, direction, inhtml) != MOZ_GTK_SUCCESS) |
| return; |
| |
| // FIXME: This approach is likely to be incorrect. See other ports and layout tests to see the problem. |
| const int xpadding = 1; |
| const int ypadding = 1; |
| |
| style->setPaddingLeft(Length(xpadding + left, Fixed)); |
| style->setPaddingTop(Length(ypadding + top, Fixed)); |
| style->setPaddingRight(Length(xpadding + right, Fixed)); |
| style->setPaddingBottom(Length(ypadding + bottom, Fixed)); |
| } |
| |
| static void setMozState(RenderTheme* theme, GtkWidgetState* state, RenderObject* o) |
| { |
| state->active = theme->isPressed(o); |
| state->focused = theme->isFocused(o); |
| state->inHover = theme->isHovered(o); |
| // FIXME: Disabled does not always give the correct appearance for ReadOnly |
| state->disabled = !theme->isEnabled(o) || theme->isReadOnlyControl(o); |
| state->isDefault = false; |
| state->canDefault = false; |
| state->depressed = false; |
| } |
| |
| static bool paintMozWidget(RenderTheme* theme, GtkThemeWidgetType type, RenderObject* o, const RenderObject::PaintInfo& i, const IntRect& rect) |
| { |
| // No GdkWindow to render to, so return true to fall back |
| if (!i.context->gdkDrawable()) |
| return true; |
| |
| // Painting is disabled so just claim to have succeeded |
| if (i.context->paintingDisabled()) |
| return false; |
| |
| GtkWidgetState mozState; |
| setMozState(theme, &mozState, o); |
| |
| int flags; |
| |
| // We might want to make setting flags the caller's job at some point rather than doing it here. |
| switch (type) { |
| case MOZ_GTK_BUTTON: |
| flags = GTK_RELIEF_NORMAL; |
| break; |
| case MOZ_GTK_CHECKBUTTON: |
| case MOZ_GTK_RADIOBUTTON: |
| flags = theme->isChecked(o); |
| break; |
| default: |
| flags = 0; |
| break; |
| } |
| |
| AffineTransform ctm = i.context->getCTM(); |
| |
| IntPoint pos = ctm.mapPoint(rect.location()); |
| GdkRectangle gdkRect = IntRect(pos.x(), pos.y(), rect.width(), rect.height()); |
| GtkTextDirection direction = gtkTextDirection(o->style()->direction()); |
| |
| // Find the clip rectangle |
| cairo_t *cr = i.context->platformContext(); |
| double clipX1, clipX2, clipY1, clipY2; |
| cairo_clip_extents(cr, &clipX1, &clipY1, &clipX2, &clipY2); |
| |
| GdkRectangle gdkClipRect; |
| gdkClipRect.width = clipX2 - clipX1; |
| gdkClipRect.height = clipY2 - clipY1; |
| IntPoint clipPos = ctm.mapPoint(IntPoint(clipX1, clipY1)); |
| gdkClipRect.x = clipPos.x(); |
| gdkClipRect.y = clipPos.y(); |
| |
| gdk_rectangle_intersect(&gdkRect, &gdkClipRect, &gdkClipRect); |
| |
| return moz_gtk_widget_paint(type, i.context->gdkDrawable(), &gdkRect, &gdkClipRect, &mozState, flags, direction) != MOZ_GTK_SUCCESS; |
| } |
| |
| static void setButtonPadding(RenderStyle* style) |
| { |
| // FIXME: This looks incorrect. |
| const int padding = 8; |
| style->setPaddingLeft(Length(padding, Fixed)); |
| style->setPaddingRight(Length(padding, Fixed)); |
| style->setPaddingTop(Length(padding / 2, Fixed)); |
| style->setPaddingBottom(Length(padding / 2, Fixed)); |
| } |
| |
| static void setToggleSize(RenderStyle* style, EAppearance appearance) |
| { |
| // The width and height are both specified, so we shouldn't change them. |
| if (!style->width().isIntrinsicOrAuto() && !style->height().isAuto()) |
| return; |
| |
| // FIXME: This is probably not correct use of indicator_size and indicator_spacing. |
| gint indicator_size, indicator_spacing; |
| |
| switch (appearance) { |
| case CheckboxAppearance: |
| if (moz_gtk_checkbox_get_metrics(&indicator_size, &indicator_spacing) != MOZ_GTK_SUCCESS) |
| return; |
| break; |
| case RadioAppearance: |
| if (moz_gtk_radio_get_metrics(&indicator_size, &indicator_spacing) != MOZ_GTK_SUCCESS) |
| return; |
| break; |
| default: |
| return; |
| } |
| |
| // Other ports hard-code this to 13, but GTK+ users tend to demand the native look. |
| // It could be made a configuration option values other than 13 actually break site compatibility. |
| int length = indicator_size + indicator_spacing; |
| if (style->width().isIntrinsicOrAuto()) |
| style->setWidth(Length(length, Fixed)); |
| |
| if (style->height().isAuto()) |
| style->setHeight(Length(length, Fixed)); |
| } |
| |
| void RenderThemeGtk::setCheckboxSize(RenderStyle* style) const |
| { |
| setToggleSize(style, RadioAppearance); |
| } |
| |
| bool RenderThemeGtk::paintCheckbox(RenderObject* o, const RenderObject::PaintInfo& i, const IntRect& rect) |
| { |
| return paintMozWidget(this, MOZ_GTK_CHECKBUTTON, o, i, rect); |
| } |
| |
| void RenderThemeGtk::setRadioSize(RenderStyle* style) const |
| { |
| setToggleSize(style, RadioAppearance); |
| } |
| |
| bool RenderThemeGtk::paintRadio(RenderObject* o, const RenderObject::PaintInfo& i, const IntRect& rect) |
| { |
| return paintMozWidget(this, MOZ_GTK_RADIOBUTTON, o, i, rect); |
| } |
| |
| void RenderThemeGtk::adjustButtonStyle(CSSStyleSelector* selector, RenderStyle* style, WebCore::Element* e) const |
| { |
| // FIXME: Is this condition necessary? |
| if (style->appearance() == PushButtonAppearance) { |
| style->resetBorder(); |
| style->setHeight(Length(Auto)); |
| style->setWhiteSpace(PRE); |
| setButtonPadding(style); |
| } else { |
| // FIXME: This should not be hard-coded. |
| style->setMinHeight(Length(14, Fixed)); |
| style->resetBorderTop(); |
| style->resetBorderBottom(); |
| } |
| } |
| |
| bool RenderThemeGtk::paintButton(RenderObject* o, const RenderObject::PaintInfo& i, const IntRect& rect) |
| { |
| return paintMozWidget(this, MOZ_GTK_BUTTON, o, i, rect); |
| } |
| |
| void RenderThemeGtk::adjustMenuListStyle(CSSStyleSelector* selector, RenderStyle* style, WebCore::Element* e) const |
| { |
| style->resetBorder(); |
| style->resetPadding(); |
| style->setHeight(Length(Auto)); |
| style->setWhiteSpace(PRE); |
| adjustMozStyle(style, MOZ_GTK_DROPDOWN); |
| } |
| |
| bool RenderThemeGtk::paintMenuList(RenderObject* o, const RenderObject::PaintInfo& i, const IntRect& rect) |
| { |
| return paintMozWidget(this, MOZ_GTK_DROPDOWN, o, i, rect); |
| } |
| |
| void RenderThemeGtk::adjustTextFieldStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const |
| { |
| style->resetBorder(); |
| style->resetPadding(); |
| style->setHeight(Length(Auto)); |
| style->setWhiteSpace(PRE); |
| adjustMozStyle(style, MOZ_GTK_ENTRY); |
| } |
| |
| bool RenderThemeGtk::paintTextField(RenderObject* o, const RenderObject::PaintInfo& i, const IntRect& rect) |
| { |
| return paintMozWidget(this, MOZ_GTK_ENTRY, o, i, rect); |
| } |
| |
| void RenderThemeGtk::adjustTextAreaStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const |
| { |
| adjustTextFieldStyle(selector, style, e); |
| } |
| |
| bool RenderThemeGtk::paintTextArea(RenderObject* o, const RenderObject::PaintInfo& i, const IntRect& r) |
| { |
| return paintTextField(o, i, r); |
| } |
| |
| void RenderThemeGtk::adjustSearchFieldResultsButtonStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const |
| { |
| adjustSearchFieldCancelButtonStyle(selector, style, e); |
| } |
| |
| bool RenderThemeGtk::paintSearchFieldResultsButton(RenderObject* o, const RenderObject::PaintInfo& i, const IntRect& rect) |
| { |
| return paintMozWidget(this, MOZ_GTK_DROPDOWN_ARROW, o, i, rect); |
| } |
| |
| void RenderThemeGtk::adjustSearchFieldResultsDecorationStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const |
| { |
| style->resetBorder(); |
| style->resetPadding(); |
| |
| // FIXME: This should not be hard-coded. |
| IntSize size = IntSize(14, 14); |
| style->setWidth(Length(size.width(), Fixed)); |
| style->setHeight(Length(size.height(), Fixed)); |
| } |
| |
| bool RenderThemeGtk::paintSearchFieldResultsDecoration(RenderObject* o, const RenderObject::PaintInfo& i, const IntRect& rect) |
| { |
| return paintMozWidget(this, MOZ_GTK_CHECKMENUITEM, o, i, rect); |
| } |
| |
| void RenderThemeGtk::adjustSearchFieldCancelButtonStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const |
| { |
| style->resetBorder(); |
| style->resetPadding(); |
| |
| // FIXME: This should not be hard-coded. |
| IntSize size = IntSize(14, 14); |
| style->setWidth(Length(size.width(), Fixed)); |
| style->setHeight(Length(size.height(), Fixed)); |
| } |
| |
| bool RenderThemeGtk::paintSearchFieldCancelButton(RenderObject* o, const RenderObject::PaintInfo& i, const IntRect& rect) |
| { |
| return paintMozWidget(this, MOZ_GTK_CHECKMENUITEM, o, i, rect); |
| } |
| |
| void RenderThemeGtk::adjustSearchFieldStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const |
| { |
| adjustTextFieldStyle(selector, style, e); |
| } |
| |
| bool RenderThemeGtk::paintSearchField(RenderObject* o, const RenderObject::PaintInfo& i, const IntRect& rect) |
| { |
| return paintTextField(o, i, rect); |
| } |
| |
| Color RenderThemeGtk::platformActiveSelectionBackgroundColor() const |
| { |
| GtkWidget* widget = gtkEntry(); |
| return widget->style->base[GTK_STATE_SELECTED]; |
| } |
| |
| Color RenderThemeGtk::platformInactiveSelectionBackgroundColor() const |
| { |
| GtkWidget* widget = gtkEntry(); |
| return widget->style->base[GTK_STATE_ACTIVE]; |
| } |
| |
| Color RenderThemeGtk::platformActiveSelectionForegroundColor() const |
| { |
| GtkWidget* widget = gtkEntry(); |
| return widget->style->text[GTK_STATE_SELECTED]; |
| } |
| |
| Color RenderThemeGtk::platformInactiveSelectionForegroundColor() const |
| { |
| GtkWidget* widget = gtkEntry(); |
| return widget->style->text[GTK_STATE_ACTIVE]; |
| } |
| |
| Color RenderThemeGtk::activeListBoxSelectionBackgroundColor() const |
| { |
| GtkWidget* widget = gtkTreeView(); |
| return widget->style->base[GTK_STATE_SELECTED]; |
| } |
| |
| Color RenderThemeGtk::inactiveListBoxSelectionBackgroundColor() const |
| { |
| GtkWidget* widget = gtkTreeView(); |
| return widget->style->base[GTK_STATE_ACTIVE]; |
| } |
| |
| Color RenderThemeGtk::activeListBoxSelectionForegroundColor() const |
| { |
| GtkWidget* widget = gtkTreeView(); |
| return widget->style->text[GTK_STATE_SELECTED]; |
| } |
| |
| Color RenderThemeGtk::inactiveListBoxSelectionForegroundColor() const |
| { |
| GtkWidget* widget = gtkTreeView(); |
| return widget->style->text[GTK_STATE_ACTIVE]; |
| } |
| |
| double RenderThemeGtk::caretBlinkFrequency() const |
| { |
| GtkSettings* settings = gtk_settings_get_default(); |
| |
| gboolean shouldBlink; |
| gint time; |
| |
| g_object_get(settings, "gtk-cursor-blink", &shouldBlink, "gtk-cursor-blink-time", &time, NULL); |
| |
| if (!shouldBlink) |
| return 0; |
| |
| return time / 2000.; |
| } |
| |
| void RenderThemeGtk::systemFont(int, FontDescription&) const |
| { |
| // If you remove this notImplemented(), replace it with an comment that explains why. |
| notImplemented(); |
| } |
| |
| static void gtkStyleSetCallback(GtkWidget* widget, GtkStyle* previous, RenderTheme* renderTheme) |
| { |
| // FIXME: Make sure this function doesn't get called many times for a single GTK+ style change signal. |
| renderTheme->platformColorsDidChange(); |
| } |
| |
| GtkContainer* RenderThemeGtk::gtkContainer() const |
| { |
| if (m_gtkContainer) |
| return m_gtkContainer; |
| |
| m_gtkWindow = gtk_window_new(GTK_WINDOW_POPUP); |
| m_gtkContainer = GTK_CONTAINER(gtk_fixed_new()); |
| gtk_container_add(GTK_CONTAINER(m_gtkWindow), GTK_WIDGET(m_gtkContainer)); |
| gtk_widget_realize(m_gtkWindow); |
| |
| return m_gtkContainer; |
| } |
| |
| GtkWidget* RenderThemeGtk::gtkEntry() const |
| { |
| if (m_gtkEntry) |
| return m_gtkEntry; |
| |
| m_gtkEntry = gtk_entry_new(); |
| g_signal_connect(m_gtkEntry, "style-set", G_CALLBACK(gtkStyleSetCallback), theme()); |
| gtk_container_add(gtkContainer(), m_gtkEntry); |
| gtk_widget_realize(m_gtkEntry); |
| |
| return m_gtkEntry; |
| } |
| |
| GtkWidget* RenderThemeGtk::gtkTreeView() const |
| { |
| if (m_gtkTreeView) |
| return m_gtkTreeView; |
| |
| m_gtkTreeView = gtk_tree_view_new(); |
| g_signal_connect(m_gtkTreeView, "style-set", G_CALLBACK(gtkStyleSetCallback), theme()); |
| gtk_container_add(gtkContainer(), m_gtkTreeView); |
| gtk_widget_realize(m_gtkTreeView); |
| |
| return m_gtkTreeView; |
| } |
| |
| } |