blob: fdef9c28dfe8843bea9c6836aa8f6c22f007fe9e [file] [log] [blame]
/*
* Copyright (C) 2007 Apple Inc.
* Copyright (C) 2007 Alp Toker <alp@atoker.com>
* Copyright (C) 2008 Collabora Ltd.
* Copyright (C) 2009 Kenneth Rohde Christiansen
*
* 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 "TransformationMatrix.h"
#include "GraphicsContext.h"
#include "NotImplemented.h"
#include "RenderBox.h"
#include "RenderObject.h"
#include "gtkdrawing.h"
#include <gdk/gdk.h>
namespace WebCore {
PassRefPtr<RenderTheme> RenderThemeGtk::create()
{
return adoptRef(new RenderThemeGtk());
}
PassRefPtr<RenderTheme> RenderTheme::themeForPage(Page* page)
{
static RenderTheme* rt = RenderThemeGtk::create().releaseRef();
return rt;
}
static int mozGtkRefCount = 0;
RenderThemeGtk::RenderThemeGtk()
: m_gtkWindow(0)
, m_gtkContainer(0)
, m_gtkEntry(0)
, m_gtkTreeView(0)
{
if (!mozGtkRefCount)
moz_gtk_init();
++mozGtkRefCount;
}
RenderThemeGtk::~RenderThemeGtk()
{
--mozGtkRefCount;
if (!mozGtkRefCount)
moz_gtk_shutdown();
}
static bool supportsFocus(ControlPart appearance)
{
switch (appearance) {
case PushButtonPart:
case ButtonPart:
case TextFieldPart:
case TextAreaPart:
case SearchFieldPart:
case MenulistPart:
case RadioPart:
case CheckboxPart:
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
{
if (!o->isBox())
return 0;
// FIXME: This strategy is possibly incorrect for the GTK+ port.
if (o->style()->appearance() == CheckboxPart ||
o->style()->appearance() == RadioPart) {
const RenderBox* box = toRenderBox(o);
return box->marginTop() + box->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;
}
TransformationMatrix 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());
#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1,4,0)
// 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);
#else
GdkRectangle gdkClipRect = gdkRect;
#endif
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, ControlPart 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 CheckboxPart:
if (moz_gtk_checkbox_get_metrics(&indicator_size, &indicator_spacing) != MOZ_GTK_SUCCESS)
return;
break;
case RadioPart:
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, RadioPart);
}
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, RadioPart);
}
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() == PushButtonPart) {
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);
}
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::caretBlinkInterval() 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), const_cast<RenderThemeGtk*>(this));
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), const_cast<RenderThemeGtk*>(this));
gtk_container_add(gtkContainer(), m_gtkTreeView);
gtk_widget_realize(m_gtkTreeView);
return m_gtkTreeView;
}
}