/* * Copyright (C) 2007 Apple Inc. * Copyright (C) 2007 Alp Toker * 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 namespace WebCore { static Color makeColor(const GdkColor& c) { return Color(makeRGB(c.red >> 8, c.green >> 8, c.blue >> 8)); } RenderTheme* theme() { static RenderThemeGtk gtkTheme; return >kTheme; } RenderThemeGtk::RenderThemeGtk() : m_gtkWindow(0) , m_gtkContainer(0) , m_gtkEntry(0) , m_gtkTreeView(0) { } 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)); } // Disabled until paintMozWidget is fixed which means that we have everything working w.r.t. GtkDrawable. /* 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) { // TODO(port): Fail on all drawing. Specifically, a lot of this drawing code // appears to depend heavily on the cairo graphics context. All of this needs // to be ported to skia. notImplemented(); return false; /* // 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 makeColor(widget->style->base[GTK_STATE_SELECTED]); } Color RenderThemeGtk::platformInactiveSelectionBackgroundColor() const { GtkWidget* widget = gtkEntry(); return makeColor(widget->style->base[GTK_STATE_ACTIVE]); } Color RenderThemeGtk::platformActiveSelectionForegroundColor() const { GtkWidget* widget = gtkEntry(); return makeColor(widget->style->text[GTK_STATE_SELECTED]); } Color RenderThemeGtk::platformInactiveSelectionForegroundColor() const { GtkWidget* widget = gtkEntry(); return makeColor(widget->style->text[GTK_STATE_ACTIVE]); } Color RenderThemeGtk::activeListBoxSelectionBackgroundColor() const { GtkWidget* widget = gtkTreeView(); return makeColor(widget->style->base[GTK_STATE_SELECTED]); } Color RenderThemeGtk::inactiveListBoxSelectionBackgroundColor() const { GtkWidget* widget = gtkTreeView(); return makeColor(widget->style->base[GTK_STATE_ACTIVE]); } Color RenderThemeGtk::activeListBoxSelectionForegroundColor() const { GtkWidget* widget = gtkTreeView(); return makeColor(widget->style->text[GTK_STATE_SELECTED]); } Color RenderThemeGtk::inactiveListBoxSelectionForegroundColor() const { GtkWidget* widget = gtkTreeView(); return makeColor(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(); } void RenderThemeGtk::systemFont(int, Document*, FontDescription&) const { 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; } }