1/* 2 * Copyright (C) 2007 Apple Inc. 3 * Copyright (C) 2007 Alp Toker <alp@atoker.com> 4 * Copyright (C) 2008 Collabora Ltd. 5 * Copyright (C) 2009 Kenneth Rohde Christiansen 6 * Copyright (C) 2010 Igalia S.L. 7 * 8 * This library is free software; you can redistribute it and/or 9 * modify it under the terms of the GNU Library General Public 10 * License as published by the Free Software Foundation; either 11 * version 2 of the License, or (at your option) any later version. 12 * 13 * This library is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 * Library General Public License for more details. 17 * 18 * You should have received a copy of the GNU Library General Public License 19 * along with this library; see the file COPYING.LIB. If not, write to 20 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 21 * Boston, MA 02110-1301, USA. 22 * 23 */ 24 25#include "config.h" 26#include "RenderThemeGtk.h" 27 28#ifdef GTK_API_VERSION_2 29 30// We need this to allow building while using GTK_WIDGET_SET_FLAGS. It's deprecated 31// but some theme engines require it to ensure proper rendering of focus indicators. 32#undef GTK_DISABLE_DEPRECATED 33 34#include "CSSValueKeywords.h" 35#include "Font.h" 36#include "GraphicsContext.h" 37#include "GtkVersioning.h" 38#include "HTMLNames.h" 39#include "MediaControlElements.h" 40#include "PaintInfo.h" 41#include "RenderObject.h" 42#include "TextDirection.h" 43#include "UserAgentStyleSheets.h" 44#include "WidgetRenderingContext.h" 45#include <gdk/gdk.h> 46#include <gtk/gtk.h> 47 48namespace WebCore { 49 50// This is the default value defined by GTK+, where it was defined as MIN_ARROW_WIDTH in gtkspinbutton.c. 51static const int minSpinButtonArrowSize = 6; 52 53// This is not a static method, because we want to avoid having GTK+ headers in RenderThemeGtk.h. 54extern GtkTextDirection gtkTextDirection(TextDirection); 55 56void RenderThemeGtk::platformInit() 57{ 58 m_themePartsHaveRGBAColormap = true; 59 m_gtkWindow = 0; 60 m_gtkContainer = 0; 61 m_gtkButton = 0; 62 m_gtkEntry = 0; 63 m_gtkTreeView = 0; 64 m_gtkVScale = 0; 65 m_gtkHScale = 0; 66 m_gtkRadioButton = 0; 67 m_gtkCheckButton = 0; 68 m_gtkProgressBar = 0; 69 m_gtkComboBox = 0; 70 m_gtkComboBoxButton = 0; 71 m_gtkComboBoxArrow = 0; 72 m_gtkComboBoxSeparator = 0; 73 m_gtkVScrollbar = 0; 74 m_gtkHScrollbar = 0; 75 m_gtkSpinButton = 0; 76 77 m_colormap = gdk_screen_get_rgba_colormap(gdk_screen_get_default()); 78 if (!m_colormap) { 79 m_themePartsHaveRGBAColormap = false; 80 m_colormap = gdk_screen_get_default_colormap(gdk_screen_get_default()); 81 } 82} 83 84RenderThemeGtk::~RenderThemeGtk() 85{ 86 if (m_gtkWindow) 87 gtk_widget_destroy(m_gtkWindow); 88} 89 90#if ENABLE(VIDEO) 91void RenderThemeGtk::initMediaColors() 92{ 93 GtkStyle* style = gtk_widget_get_style(GTK_WIDGET(gtkContainer())); 94 m_panelColor = style->bg[GTK_STATE_NORMAL]; 95 m_sliderColor = style->bg[GTK_STATE_ACTIVE]; 96 m_sliderThumbColor = style->bg[GTK_STATE_SELECTED]; 97} 98#endif 99 100static void adjustRectForFocus(GtkWidget* widget, IntRect& rect, bool ignoreInteriorFocusProperty = false) 101{ 102 gint focusWidth, focusPad; 103 gboolean interiorFocus = 0; 104 gtk_widget_style_get(widget, 105 "interior-focus", &interiorFocus, 106 "focus-line-width", &focusWidth, 107 "focus-padding", &focusPad, NULL); 108 if (!ignoreInteriorFocusProperty && interiorFocus) 109 return; 110 rect.inflate(focusWidth + focusPad); 111} 112 113void RenderThemeGtk::adjustRepaintRect(const RenderObject* renderObject, IntRect& rect) 114{ 115 ControlPart part = renderObject->style()->appearance(); 116 switch (part) { 117 case CheckboxPart: 118 case RadioPart: { 119 // We ignore the interior focus property and always expand the focus rect. In GTK+, the 120 // focus indicator is usually on the text next to a checkbox or radio button, but that doesn't 121 // happen in WebCore. By expanding the focus rectangle unconditionally we increase its prominence. 122 adjustRectForFocus(part == CheckboxPart ? gtkCheckButton() : gtkRadioButton(), rect, true); 123 return; 124 } 125 case InnerSpinButtonPart: 126 // See paintInnerSpinButton for an explanation of why we expand the painting rect. 127 rect.inflateY(2); 128 rect.setWidth(rect.width() + 2); 129 default: 130 return; 131 } 132} 133 134static GtkStateType getGtkStateType(RenderThemeGtk* theme, RenderObject* object) 135{ 136 if (!theme->isEnabled(object) || theme->isReadOnlyControl(object)) 137 return GTK_STATE_INSENSITIVE; 138 if (theme->isPressed(object)) 139 return GTK_STATE_ACTIVE; 140 if (theme->isHovered(object)) 141 return GTK_STATE_PRELIGHT; 142 return GTK_STATE_NORMAL; 143} 144 145static void setToggleSize(const RenderThemeGtk* theme, RenderStyle* style, GtkWidget* widget) 146{ 147 // The width and height are both specified, so we shouldn't change them. 148 if (!style->width().isIntrinsicOrAuto() && !style->height().isAuto()) 149 return; 150 151 gint indicatorSize; 152 gtk_widget_style_get(widget, "indicator-size", &indicatorSize, NULL); 153 if (style->width().isIntrinsicOrAuto()) 154 style->setWidth(Length(indicatorSize, Fixed)); 155 if (style->height().isAuto()) 156 style->setHeight(Length(indicatorSize, Fixed)); 157} 158 159static void paintToggle(RenderThemeGtk* theme, RenderObject* renderObject, const PaintInfo& info, const IntRect& rect, GtkWidget* widget) 160{ 161 // We do not call gtk_toggle_button_set_active here, because some themes begin a series of 162 // animation frames in a "toggled" signal handler. This puts some checkboxes in a half-way 163 // checked state. Every GTK+ theme I tested merely looks at the shadow type (and not the 164 // 'active' property) to determine whether or not to draw the check. 165 gtk_widget_set_sensitive(widget, theme->isEnabled(renderObject) && !theme->isReadOnlyControl(renderObject)); 166 gtk_widget_set_direction(widget, gtkTextDirection(renderObject->style()->direction())); 167 168 bool indeterminate = theme->isIndeterminate(renderObject); 169 gtk_toggle_button_set_inconsistent(GTK_TOGGLE_BUTTON(widget), indeterminate); 170 171 GtkShadowType shadowType = GTK_SHADOW_OUT; 172 if (indeterminate) // This originates from the Mozilla code. 173 shadowType = GTK_SHADOW_ETCHED_IN; 174 else if (theme->isChecked(renderObject)) 175 shadowType = GTK_SHADOW_IN; 176 177 WidgetRenderingContext widgetContext(info.context, rect); 178 IntRect buttonRect(IntPoint(), rect.size()); 179 GtkStateType toggleState = getGtkStateType(theme, renderObject); 180 const char* detail = 0; 181 if (GTK_IS_RADIO_BUTTON(widget)) { 182 detail = "radiobutton"; 183 widgetContext.gtkPaintOption(buttonRect, widget, toggleState, shadowType, detail); 184 } else { 185 detail = "checkbutton"; 186 widgetContext.gtkPaintCheck(buttonRect, widget, toggleState, shadowType, detail); 187 } 188 189 if (theme->isFocused(renderObject)) { 190 IntRect focusRect(buttonRect); 191 adjustRectForFocus(widget, focusRect, true); 192 widgetContext.gtkPaintFocus(focusRect, widget, toggleState, detail); 193 } 194} 195 196void RenderThemeGtk::setCheckboxSize(RenderStyle* style) const 197{ 198 setToggleSize(this, style, gtkCheckButton()); 199} 200 201bool RenderThemeGtk::paintCheckbox(RenderObject* renderObject, const PaintInfo& info, const IntRect& rect) 202{ 203 paintToggle(this, renderObject, info, rect, gtkCheckButton()); 204 return false; 205} 206 207void RenderThemeGtk::setRadioSize(RenderStyle* style) const 208{ 209 setToggleSize(this, style, gtkRadioButton()); 210} 211 212bool RenderThemeGtk::paintRadio(RenderObject* renderObject, const PaintInfo& info, const IntRect& rect) 213{ 214 paintToggle(this, renderObject, info, rect, gtkRadioButton()); 215 return false; 216} 217 218static void setWidgetHasFocus(GtkWidget* widget, gboolean hasFocus) 219{ 220 g_object_set(widget, "has-focus", hasFocus, NULL); 221 222 // These functions are deprecated in GTK+ 2.22, yet theme engines still look 223 // at these flags when determining if a widget has focus, so we must use them. 224 if (hasFocus) 225 GTK_WIDGET_SET_FLAGS(widget, GTK_HAS_FOCUS); 226 else 227 GTK_WIDGET_UNSET_FLAGS(widget, GTK_HAS_FOCUS); 228} 229 230bool RenderThemeGtk::paintButton(RenderObject* object, const PaintInfo& info, const IntRect& rect) 231{ 232 if (info.context->paintingDisabled()) 233 return false; 234 235 GtkWidget* widget = gtkButton(); 236 IntRect buttonRect(IntPoint(), rect.size()); 237 IntRect focusRect(buttonRect); 238 239 GtkStateType state = getGtkStateType(this, object); 240 gtk_widget_set_state(widget, state); 241 gtk_widget_set_direction(widget, gtkTextDirection(object->style()->direction())); 242 243 if (isFocused(object)) { 244 setWidgetHasFocus(widget, TRUE); 245 246 gboolean interiorFocus = 0, focusWidth = 0, focusPadding = 0; 247 gtk_widget_style_get(widget, 248 "interior-focus", &interiorFocus, 249 "focus-line-width", &focusWidth, 250 "focus-padding", &focusPadding, NULL); 251 // If we are using exterior focus, we shrink the button rect down before 252 // drawing. If we are using interior focus we shrink the focus rect. This 253 // approach originates from the Mozilla theme drawing code (gtk2drawing.c). 254 if (interiorFocus) { 255 GtkStyle* style = gtk_widget_get_style(widget); 256 focusRect.inflateX(-style->xthickness - focusPadding); 257 focusRect.inflateY(-style->ythickness - focusPadding); 258 } else { 259 buttonRect.inflateX(-focusWidth - focusPadding); 260 buttonRect.inflateY(-focusPadding - focusPadding); 261 } 262 } 263 264 WidgetRenderingContext widgetContext(info.context, rect); 265 GtkShadowType shadowType = state == GTK_STATE_ACTIVE ? GTK_SHADOW_IN : GTK_SHADOW_OUT; 266 widgetContext.gtkPaintBox(buttonRect, widget, state, shadowType, "button"); 267 if (isFocused(object)) 268 widgetContext.gtkPaintFocus(focusRect, widget, state, "button"); 269 270 setWidgetHasFocus(widget, FALSE); 271 return false; 272} 273 274int RenderThemeGtk::getComboBoxSeparatorWidth() const 275{ 276 GtkWidget* separator = gtkComboBoxSeparator(); 277 if (!separator) 278 return 0; 279 280 gboolean hasWideSeparators = FALSE; 281 gint separatorWidth = 0; 282 gtk_widget_style_get(separator, 283 "wide-separators", &hasWideSeparators, 284 "separator-width", &separatorWidth, 285 NULL); 286 if (hasWideSeparators) 287 return separatorWidth; 288 return gtk_widget_get_style(separator)->xthickness; 289} 290 291int RenderThemeGtk::comboBoxArrowSize(RenderStyle* style) const 292{ 293 // Taking the font size and reversing the DPI conversion seems to match 294 // GTK+ rendering as closely as possible. 295 return style->font().size() * (72.0 / RenderThemeGtk::getScreenDPI()); 296} 297 298static void getButtonInnerBorder(GtkWidget* button, int& left, int& top, int& right, int& bottom) 299{ 300 GtkStyle* style = gtk_widget_get_style(button); 301 int outerBorder = gtk_container_get_border_width(GTK_CONTAINER(button)); 302 static GtkBorder defaultInnerBorder = {1, 1, 1, 1}; 303 GtkBorder* innerBorder; 304 gtk_widget_style_get(button, "inner-border", &innerBorder, NULL); 305 if (!innerBorder) 306 innerBorder = &defaultInnerBorder; 307 308 left = outerBorder + innerBorder->left + style->xthickness; 309 right = outerBorder + innerBorder->right + style->xthickness; 310 top = outerBorder + innerBorder->top + style->ythickness; 311 bottom = outerBorder + innerBorder->bottom + style->ythickness; 312 313 if (innerBorder != &defaultInnerBorder) 314 gtk_border_free(innerBorder); 315} 316 317 318void RenderThemeGtk::getComboBoxPadding(RenderStyle* style, int& left, int& top, int& right, int& bottom) const 319{ 320 // If this menu list button isn't drawn using the native theme, we 321 // don't add any extra padding beyond what WebCore already uses. 322 if (style->appearance() == NoControlPart) 323 return; 324 325 // A combo box button is a button with widgets packed into it. 326 GtkStyle* buttonWidgetStyle = gtk_widget_get_style(gtkComboBoxButton()); 327 getButtonInnerBorder(gtkComboBoxButton(), left, top, right, bottom); 328 329 // Add xthickness amount of padding for each side of the separator. This ensures 330 // that the text does not bump up against the separator. 331 int arrowAndSeperatorLength = comboBoxArrowSize(style) + 332 getComboBoxSeparatorWidth() + (3 * buttonWidgetStyle->xthickness); 333 334 if (style->direction() == RTL) 335 left += arrowAndSeperatorLength; 336 else 337 right += arrowAndSeperatorLength; 338} 339 340int RenderThemeGtk::popupInternalPaddingLeft(RenderStyle* style) const 341{ 342 int left = 0, top = 0, right = 0, bottom = 0; 343 getComboBoxPadding(style, left, top, right, bottom); 344 return left; 345} 346 347int RenderThemeGtk::popupInternalPaddingRight(RenderStyle* style) const 348{ 349 int left = 0, top = 0, right = 0, bottom = 0; 350 getComboBoxPadding(style, left, top, right, bottom); 351 return right; 352} 353 354int RenderThemeGtk::popupInternalPaddingTop(RenderStyle* style) const 355{ 356 int left = 0, top = 0, right = 0, bottom = 0; 357 getComboBoxPadding(style, left, top, right, bottom); 358 return top; 359} 360 361int RenderThemeGtk::popupInternalPaddingBottom(RenderStyle* style) const 362{ 363 int left = 0, top = 0, right = 0, bottom = 0; 364 getComboBoxPadding(style, left, top, right, bottom); 365 return bottom; 366} 367 368bool RenderThemeGtk::paintMenuList(RenderObject* object, const PaintInfo& info, const IntRect& rect) 369{ 370 if (paintButton(object, info, rect)) 371 return true; 372 373 // Menu list button painting strategy. 374 // For buttons with appears-as-list set to false (having a separator): 375 // | left border | Button text | xthickness | vseparator | xthickness | arrow | xthickness | right border | 376 // For buttons with appears-as-list set to true (not having a separator): 377 // | left border | Button text | arrow | xthickness | right border | 378 379 int leftBorder = 0, rightBorder = 0, bottomBorder = 0, topBorder = 0; 380 getButtonInnerBorder(gtkComboBoxButton(), leftBorder, topBorder, rightBorder, bottomBorder); 381 RenderStyle* style = object->style(); 382 int arrowSize = comboBoxArrowSize(style); 383 GtkStyle* buttonStyle = gtk_widget_get_style(gtkComboBoxButton()); 384 385 IntRect arrowRect(0, (rect.height() - arrowSize) / 2, arrowSize, arrowSize); 386 if (style->direction() == RTL) 387 arrowRect.setX(leftBorder + buttonStyle->xthickness); 388 else 389 arrowRect.setX(rect.width() - rightBorder - buttonStyle->xthickness - arrowSize); 390 GtkShadowType shadowType = isPressed(object) ? GTK_SHADOW_IN : GTK_SHADOW_OUT; 391 392 WidgetRenderingContext widgetContext(info.context, rect); 393 GtkStateType stateType = getGtkStateType(this, object); 394 widgetContext.gtkPaintArrow(arrowRect, gtkComboBoxArrow(), stateType, shadowType, GTK_ARROW_DOWN, "arrow"); 395 396 // Some combo boxes do not have a separator. 397 GtkWidget* separator = gtkComboBoxSeparator(); 398 if (!separator) 399 return false; 400 401 // We want to decrease the height of the separator based on the focus padding of the button. 402 gint focusPadding = 0, focusWidth = 0; 403 gtk_widget_style_get(gtkComboBoxButton(), 404 "focus-line-width", &focusWidth, 405 "focus-padding", &focusPadding, NULL); 406 topBorder += focusPadding + focusWidth; 407 bottomBorder += focusPadding + focusWidth; 408 int separatorWidth = getComboBoxSeparatorWidth(); 409 IntRect separatorRect(0, topBorder, separatorWidth, rect.height() - topBorder - bottomBorder); 410 if (style->direction() == RTL) 411 separatorRect.setX(arrowRect.x() + arrowRect.width() + buttonStyle->xthickness + separatorWidth); 412 else 413 separatorRect.setX(arrowRect.x() - buttonStyle->xthickness - separatorWidth); 414 415 gboolean hasWideSeparators = FALSE; 416 gtk_widget_style_get(separator, "wide-separators", &hasWideSeparators, NULL); 417 if (hasWideSeparators) 418 widgetContext.gtkPaintBox(separatorRect, separator, GTK_STATE_NORMAL, GTK_SHADOW_ETCHED_OUT, "vseparator"); 419 else 420 widgetContext.gtkPaintVLine(separatorRect, separator, GTK_STATE_NORMAL, "vseparator"); 421 422 return false; 423} 424 425bool RenderThemeGtk::paintTextField(RenderObject* renderObject, const PaintInfo& info, const IntRect& rect) 426{ 427 GtkWidget* widget = gtkEntry(); 428 429 bool enabled = isEnabled(renderObject) && !isReadOnlyControl(renderObject); 430 GtkStateType backgroundState = enabled ? GTK_STATE_NORMAL : GTK_STATE_INSENSITIVE; 431 gtk_widget_set_sensitive(widget, enabled); 432 gtk_widget_set_direction(widget, gtkTextDirection(renderObject->style()->direction())); 433 setWidgetHasFocus(widget, isFocused(renderObject)); 434 435 WidgetRenderingContext widgetContext(info.context, rect); 436 IntRect textFieldRect(IntPoint(), rect.size()); 437 438 // The entry background is only painted over the interior part of the GTK+ entry, not 439 // the entire frame. This happens in the Mozilla theme drawing code as well. 440 IntRect interiorRect(textFieldRect); 441 GtkStyle* style = gtk_widget_get_style(widget); 442 interiorRect.inflateX(-style->xthickness); 443 interiorRect.inflateY(-style->ythickness); 444 widgetContext.gtkPaintFlatBox(interiorRect, widget, backgroundState, GTK_SHADOW_NONE, "entry_bg"); 445 446 // This is responsible for drawing the actual frame. 447 widgetContext.gtkPaintShadow(textFieldRect, widget, GTK_STATE_NORMAL, GTK_SHADOW_IN, "entry"); 448 449 gboolean interiorFocus; 450 gint focusWidth; 451 gtk_widget_style_get(widget, 452 "interior-focus", &interiorFocus, 453 "focus-line-width", &focusWidth, NULL); 454 if (isFocused(renderObject) && !interiorFocus) { 455 // When GTK+ paints a text entry with focus, it shrinks the size of the frame area by the 456 // focus width and paints over the previously unfocused text entry. We need to emulate that 457 // by drawing both the unfocused frame above and the focused frame here. 458 IntRect shadowRect(textFieldRect); 459 shadowRect.inflate(-focusWidth); 460 widgetContext.gtkPaintShadow(shadowRect, widget, GTK_STATE_NORMAL, GTK_SHADOW_IN, "entry"); 461 462 widgetContext.gtkPaintFocus(textFieldRect, widget, GTK_STATE_NORMAL, "entry"); 463 } 464 465 return false; 466} 467 468bool RenderThemeGtk::paintSliderTrack(RenderObject* object, const PaintInfo& info, const IntRect& rect) 469{ 470 if (info.context->paintingDisabled()) 471 return false; 472 473 ControlPart part = object->style()->appearance(); 474 ASSERT(part == SliderHorizontalPart || part == SliderVerticalPart || part == MediaVolumeSliderPart); 475 476 // We shrink the trough rect slightly to make room for the focus indicator. 477 IntRect troughRect(IntPoint(), rect.size()); // This is relative to rect. 478 GtkWidget* widget = 0; 479 if (part == SliderHorizontalPart) { 480 widget = gtkHScale(); 481 troughRect.inflateX(-gtk_widget_get_style(widget)->xthickness); 482 } else { 483 widget = gtkVScale(); 484 troughRect.inflateY(-gtk_widget_get_style(widget)->ythickness); 485 } 486 gtk_widget_set_direction(widget, gtkTextDirection(object->style()->direction())); 487 488 WidgetRenderingContext widgetContext(info.context, rect); 489 widgetContext.gtkPaintBox(troughRect, widget, GTK_STATE_ACTIVE, GTK_SHADOW_OUT, "trough"); 490 if (isFocused(object)) 491 widgetContext.gtkPaintFocus(IntRect(IntPoint(), rect.size()), widget, getGtkStateType(this, object), "trough"); 492 493 return false; 494} 495 496bool RenderThemeGtk::paintSliderThumb(RenderObject* object, const PaintInfo& info, const IntRect& rect) 497{ 498 if (info.context->paintingDisabled()) 499 return false; 500 501 ControlPart part = object->style()->appearance(); 502 ASSERT(part == SliderThumbHorizontalPart || part == SliderThumbVerticalPart || part == MediaVolumeSliderThumbPart); 503 504 GtkWidget* widget = 0; 505 const char* detail = 0; 506 GtkOrientation orientation; 507 if (part == SliderThumbHorizontalPart) { 508 widget = gtkHScale(); 509 detail = "hscale"; 510 orientation = GTK_ORIENTATION_HORIZONTAL; 511 } else { 512 widget = gtkVScale(); 513 detail = "vscale"; 514 orientation = GTK_ORIENTATION_VERTICAL; 515 } 516 gtk_widget_set_direction(widget, gtkTextDirection(object->style()->direction())); 517 518 // Only some themes have slider thumbs respond to clicks and some don't. This information is 519 // gathered via the 'activate-slider' property, but it's deprecated in GTK+ 2.22 and removed in 520 // GTK+ 3.x. The drawback of not honoring it is that slider thumbs change color when you click 521 // on them. 522 IntRect thumbRect(IntPoint(), rect.size()); 523 WidgetRenderingContext widgetContext(info.context, rect); 524 widgetContext.gtkPaintSlider(thumbRect, widget, getGtkStateType(this, object), GTK_SHADOW_OUT, detail, orientation); 525 return false; 526} 527 528void RenderThemeGtk::adjustSliderThumbSize(RenderStyle* style, Element*) const 529{ 530 ControlPart part = style->appearance(); 531 if (part != SliderThumbHorizontalPart && part != SliderThumbVerticalPart) 532 return; 533 534 GtkWidget* widget = part == SliderThumbHorizontalPart ? gtkHScale() : gtkVScale(); 535 int length = 0, width = 0; 536 gtk_widget_style_get(widget, 537 "slider_length", &length, 538 "slider_width", &width, 539 NULL); 540 541 if (part == SliderThumbHorizontalPart) { 542 style->setWidth(Length(length, Fixed)); 543 style->setHeight(Length(width, Fixed)); 544 return; 545 } 546 ASSERT(part == SliderThumbVerticalPart || part == MediaVolumeSliderThumbPart); 547 style->setWidth(Length(width, Fixed)); 548 style->setHeight(Length(length, Fixed)); 549} 550 551#if ENABLE(PROGRESS_ELEMENT) 552bool RenderThemeGtk::paintProgressBar(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect) 553{ 554 GtkWidget* widget = gtkProgressBar(); 555 gtk_widget_set_direction(widget, gtkTextDirection(renderObject->style()->direction())); 556 557 WidgetRenderingContext widgetContext(paintInfo.context, rect); 558 IntRect fullProgressBarRect(IntPoint(), rect.size()); 559 widgetContext.gtkPaintBox(fullProgressBarRect, widget, GTK_STATE_NORMAL, GTK_SHADOW_IN, "trough"); 560 561 GtkStyle* style = gtk_widget_get_style(widget); 562 IntRect progressRect(fullProgressBarRect); 563 progressRect.inflateX(-style->xthickness); 564 progressRect.inflateY(-style->ythickness); 565 progressRect = RenderThemeGtk::calculateProgressRect(renderObject, progressRect); 566 567 if (!progressRect.isEmpty()) 568 widgetContext.gtkPaintBox(progressRect, widget, GTK_STATE_PRELIGHT, GTK_SHADOW_OUT, "bar"); 569 570 return false; 571} 572#endif 573 574void RenderThemeGtk::adjustInnerSpinButtonStyle(StyleResolver*, RenderStyle* style, Element*) const 575{ 576 GtkStyle* gtkStyle = gtk_widget_get_style(gtkSpinButton()); 577 const PangoFontDescription* fontDescription = gtkStyle->font_desc; 578 gint fontSize = pango_font_description_get_size(fontDescription); 579 580 // Force an odd arrow size here. GTK+ 3.x forces even in this case, but 581 // Nodoka-based themes look incorrect with an even arrow size. 582 int width = max(PANGO_PIXELS(fontSize), minSpinButtonArrowSize); 583 width += -((width % 2) - 1) + gtkStyle->xthickness; 584 585 style->setWidth(Length(width, Fixed)); 586 style->setMinWidth(Length(width, Fixed)); 587} 588 589bool RenderThemeGtk::paintInnerSpinButton(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect) 590{ 591 // We expand the painted area by 2 pixels on the top and bottom and 2 pixels on the right. This 592 // is because GTK+ themes want to draw over the text box borders, but WebCore renders the inner 593 // spin button inside the text box. 594 IntRect expandedRect(rect); 595 expandedRect.inflateY(2); 596 expandedRect.setWidth(rect.width() + 2); 597 598 WidgetRenderingContext widgetContext(paintInfo.context, expandedRect); 599 GtkWidget* widget = gtkSpinButton(); 600 gtk_widget_set_direction(widget, gtkTextDirection(renderObject->style()->direction())); 601 602 IntRect fullSpinButtonRect(IntPoint(), expandedRect.size()); 603 widgetContext.gtkPaintBox(fullSpinButtonRect, widget, GTK_STATE_NORMAL, GTK_SHADOW_IN, "spinbutton"); 604 605 bool upPressed = isSpinUpButtonPartPressed(renderObject); 606 bool upHovered = isSpinUpButtonPartHovered(renderObject); 607 bool controlActive = isEnabled(renderObject) && !isReadOnlyControl(renderObject); 608 GtkShadowType shadowType = upPressed ? GTK_SHADOW_IN : GTK_SHADOW_OUT; 609 610 GtkStateType stateType = GTK_STATE_INSENSITIVE; 611 if (controlActive) { 612 if (isPressed(renderObject) && upPressed) 613 stateType = GTK_STATE_ACTIVE; 614 else if (isHovered(renderObject) && upHovered) 615 stateType = GTK_STATE_PRELIGHT; 616 else 617 stateType = GTK_STATE_NORMAL; 618 } 619 IntRect topRect(IntPoint(), expandedRect.size()); 620 topRect.setHeight(expandedRect.height() / 2); 621 widgetContext.gtkPaintBox(topRect, widget, stateType, shadowType, "spinbutton_up"); 622 623 // The arrow size/position calculation here is based on the arbitrary gymnastics that happen 624 // in gtkspinbutton.c. It isn't pretty there and it isn't pretty here. This manages to make 625 // the button look native for many themes though. 626 IntRect arrowRect; 627 int arrowSize = (expandedRect.width() - 3) / 2; 628 arrowSize -= (arrowSize % 2) - 1; // Force odd. 629 arrowRect.setWidth(arrowSize); 630 arrowRect.setHeight(arrowSize); 631 arrowRect.move((expandedRect.width() - arrowRect.width()) / 2, 632 (topRect.height() - arrowRect.height()) / 2 + 1); 633 widgetContext.gtkPaintArrow(arrowRect, widget, stateType, shadowType, GTK_ARROW_UP, "spinbutton"); 634 635 shadowType = isPressed(renderObject) && !upPressed ? GTK_SHADOW_IN : GTK_SHADOW_OUT; 636 if (controlActive) { 637 if (isPressed(renderObject) && !upPressed) 638 stateType = GTK_STATE_ACTIVE; 639 else if (isHovered(renderObject) && !upHovered) 640 stateType = GTK_STATE_PRELIGHT; 641 else 642 stateType = GTK_STATE_NORMAL; 643 } 644 IntRect bottomRect(IntPoint(0, expandedRect.height() / 2), expandedRect.size()); 645 bottomRect.setHeight(expandedRect.height() - bottomRect.y()); 646 widgetContext.gtkPaintBox(bottomRect, widget, stateType, shadowType, "spinbutton_down"); 647 648 arrowRect.setY(arrowRect.y() + bottomRect.y() - 1); 649 widgetContext.gtkPaintArrow(arrowRect, widget, stateType, shadowType, GTK_ARROW_DOWN, "spinbutton"); 650 651 return false; 652} 653 654GRefPtr<GdkPixbuf> getStockIconForWidgetType(GType widgetType, const char* iconName, gint direction, gint state, gint iconSize) 655{ 656 ASSERT(widgetType == GTK_TYPE_CONTAINER || widgetType == GTK_TYPE_ENTRY); 657 658 RenderThemeGtk* theme = static_cast<RenderThemeGtk*>(RenderTheme::defaultTheme().get()); 659 GtkWidget* widget = widgetType == GTK_TYPE_CONTAINER ? GTK_WIDGET(theme->gtkContainer()) : theme->gtkEntry(); 660 661 GtkStyle* style = gtk_widget_get_style(widget); 662 GtkIconSet* iconSet = gtk_style_lookup_icon_set(style, iconName); 663 return adoptGRef(gtk_icon_set_render_icon(iconSet, style, 664 static_cast<GtkTextDirection>(direction), 665 static_cast<GtkStateType>(state), 666 static_cast<GtkIconSize>(iconSize), 0, 0)); 667} 668 669GRefPtr<GdkPixbuf> getStockSymbolicIconForWidgetType(GType widgetType, const char* symbolicIconName, const char *fallbackStockIconName, gint direction, gint state, gint iconSize) 670{ 671 return getStockIconForWidgetType(widgetType, fallbackStockIconName, direction, state, iconSize); 672} 673 674Color RenderThemeGtk::platformActiveSelectionBackgroundColor() const 675{ 676 GtkWidget* widget = gtkEntry(); 677 return gtk_widget_get_style(widget)->base[GTK_STATE_SELECTED]; 678} 679 680Color RenderThemeGtk::platformInactiveSelectionBackgroundColor() const 681{ 682 GtkWidget* widget = gtkEntry(); 683 return gtk_widget_get_style(widget)->base[GTK_STATE_ACTIVE]; 684} 685 686Color RenderThemeGtk::platformActiveSelectionForegroundColor() const 687{ 688 GtkWidget* widget = gtkEntry(); 689 return gtk_widget_get_style(widget)->text[GTK_STATE_SELECTED]; 690} 691 692Color RenderThemeGtk::platformInactiveSelectionForegroundColor() const 693{ 694 GtkWidget* widget = gtkEntry(); 695 return gtk_widget_get_style(widget)->text[GTK_STATE_ACTIVE]; 696} 697 698Color RenderThemeGtk::activeListBoxSelectionBackgroundColor() const 699{ 700 GtkWidget* widget = gtkTreeView(); 701 return gtk_widget_get_style(widget)->base[GTK_STATE_SELECTED]; 702} 703 704Color RenderThemeGtk::inactiveListBoxSelectionBackgroundColor() const 705{ 706 GtkWidget* widget = gtkTreeView(); 707 return gtk_widget_get_style(widget)->base[GTK_STATE_ACTIVE]; 708} 709 710Color RenderThemeGtk::activeListBoxSelectionForegroundColor() const 711{ 712 GtkWidget* widget = gtkTreeView(); 713 return gtk_widget_get_style(widget)->text[GTK_STATE_SELECTED]; 714} 715 716Color RenderThemeGtk::inactiveListBoxSelectionForegroundColor() const 717{ 718 GtkWidget* widget = gtkTreeView(); 719 return gtk_widget_get_style(widget)->text[GTK_STATE_ACTIVE]; 720} 721 722Color RenderThemeGtk::systemColor(int cssValueId) const 723{ 724 switch (cssValueId) { 725 case CSSValueButtontext: 726 return Color(gtk_widget_get_style(gtkButton())->fg[GTK_STATE_NORMAL]); 727 case CSSValueCaptiontext: 728 return Color(gtk_widget_get_style(gtkEntry())->fg[GTK_STATE_NORMAL]); 729 default: 730 return RenderTheme::systemColor(cssValueId); 731 } 732} 733 734static void gtkStyleSetCallback(GtkWidget* widget, GtkStyle* previous, RenderTheme* renderTheme) 735{ 736 // FIXME: Make sure this function doesn't get called many times for a single GTK+ style change signal. 737 renderTheme->platformColorsDidChange(); 738} 739 740static void setupWidget(GtkWidget* widget) 741{ 742 gtk_widget_realize(widget); 743 g_object_set_data(G_OBJECT(widget), "transparent-bg-hint", GINT_TO_POINTER(TRUE)); 744} 745 746void RenderThemeGtk::setupWidgetAndAddToContainer(GtkWidget* widget, GtkWidget* window) const 747{ 748 gtk_container_add(GTK_CONTAINER(window), widget); 749 setupWidget(widget); 750 751 // FIXME: Perhaps this should only be called for the containing window or parent container. 752 g_signal_connect(widget, "style-set", G_CALLBACK(gtkStyleSetCallback), const_cast<RenderThemeGtk*>(this)); 753} 754 755GtkWidget* RenderThemeGtk::gtkContainer() const 756{ 757 if (m_gtkContainer) 758 return m_gtkContainer; 759 760 m_gtkWindow = gtk_window_new(GTK_WINDOW_POPUP); 761 gtk_widget_set_colormap(m_gtkWindow, m_colormap); 762 setupWidget(m_gtkWindow); 763 gtk_widget_set_name(m_gtkWindow, "MozillaGtkWidget"); 764 765 m_gtkContainer = gtk_fixed_new(); 766 setupWidgetAndAddToContainer(m_gtkContainer, m_gtkWindow); 767 return m_gtkContainer; 768} 769 770GtkWidget* RenderThemeGtk::gtkButton() const 771{ 772 if (m_gtkButton) 773 return m_gtkButton; 774 m_gtkButton = gtk_button_new(); 775 setupWidgetAndAddToContainer(m_gtkButton, gtkContainer()); 776 return m_gtkButton; 777} 778 779GtkWidget* RenderThemeGtk::gtkEntry() const 780{ 781 if (m_gtkEntry) 782 return m_gtkEntry; 783 m_gtkEntry = gtk_entry_new(); 784 setupWidgetAndAddToContainer(m_gtkEntry, gtkContainer()); 785 return m_gtkEntry; 786} 787 788GtkWidget* RenderThemeGtk::gtkTreeView() const 789{ 790 if (m_gtkTreeView) 791 return m_gtkTreeView; 792 m_gtkTreeView = gtk_tree_view_new(); 793 setupWidgetAndAddToContainer(m_gtkTreeView, gtkContainer()); 794 return m_gtkTreeView; 795} 796 797GtkWidget* RenderThemeGtk::gtkVScale() const 798{ 799 if (m_gtkVScale) 800 return m_gtkVScale; 801 m_gtkVScale = gtk_vscale_new(0); 802 setupWidgetAndAddToContainer(m_gtkVScale, gtkContainer()); 803 return m_gtkVScale; 804} 805 806GtkWidget* RenderThemeGtk::gtkHScale() const 807{ 808 if (m_gtkHScale) 809 return m_gtkHScale; 810 m_gtkHScale = gtk_hscale_new(0); 811 setupWidgetAndAddToContainer(m_gtkHScale, gtkContainer()); 812 return m_gtkHScale; 813} 814 815GtkWidget* RenderThemeGtk::gtkRadioButton() const 816{ 817 if (m_gtkRadioButton) 818 return m_gtkRadioButton; 819 m_gtkRadioButton = gtk_radio_button_new(0); 820 setupWidgetAndAddToContainer(m_gtkRadioButton, gtkContainer()); 821 return m_gtkRadioButton; 822} 823 824GtkWidget* RenderThemeGtk::gtkCheckButton() const 825{ 826 if (m_gtkCheckButton) 827 return m_gtkCheckButton; 828 m_gtkCheckButton = gtk_check_button_new(); 829 setupWidgetAndAddToContainer(m_gtkCheckButton, gtkContainer()); 830 return m_gtkCheckButton; 831} 832 833GtkWidget* RenderThemeGtk::gtkProgressBar() const 834{ 835 if (m_gtkProgressBar) 836 return m_gtkProgressBar; 837 m_gtkProgressBar = gtk_progress_bar_new(); 838 setupWidgetAndAddToContainer(m_gtkProgressBar, gtkContainer()); 839 return m_gtkProgressBar; 840} 841 842static void getGtkComboBoxButton(GtkWidget* widget, gpointer target) 843{ 844 if (!GTK_IS_TOGGLE_BUTTON(widget)) 845 return; 846 GtkWidget** widgetTarget = static_cast<GtkWidget**>(target); 847 *widgetTarget = widget; 848} 849 850typedef struct { 851 GtkWidget* arrow; 852 GtkWidget* separator; 853} ComboBoxWidgetPieces; 854 855static void getGtkComboBoxPieces(GtkWidget* widget, gpointer data) 856{ 857 if (GTK_IS_ARROW(widget)) { 858 static_cast<ComboBoxWidgetPieces*>(data)->arrow = widget; 859 return; 860 } 861 if (GTK_IS_SEPARATOR(widget)) 862 static_cast<ComboBoxWidgetPieces*>(data)->separator = widget; 863} 864 865GtkWidget* RenderThemeGtk::gtkComboBox() const 866{ 867 if (m_gtkComboBox) 868 return m_gtkComboBox; 869 m_gtkComboBox = gtk_combo_box_new(); 870 setupWidgetAndAddToContainer(m_gtkComboBox, gtkContainer()); 871 return m_gtkComboBox; 872} 873 874void RenderThemeGtk::refreshComboBoxChildren() const 875{ 876 gtkComboBox(); // Ensure that we've initialized the combo box. 877 878 // Some themes look at widget ancestry to determine how to render widgets, so 879 // get the GtkButton that is the actual child of the combo box. 880 gtk_container_forall(GTK_CONTAINER(m_gtkComboBox), getGtkComboBoxButton, &m_gtkComboBoxButton); 881 ASSERT(m_gtkComboBoxButton); 882 setupWidget(m_gtkComboBoxButton); 883 g_object_add_weak_pointer(G_OBJECT(m_gtkComboBoxButton), reinterpret_cast<gpointer*>(&m_gtkComboBoxButton)); 884 885 ComboBoxWidgetPieces pieces = { 0, 0 }; 886 GtkWidget* buttonChild = gtk_bin_get_child(GTK_BIN(gtkComboBoxButton())); 887 if (GTK_IS_HBOX(buttonChild)) 888 gtk_container_forall(GTK_CONTAINER(buttonChild), getGtkComboBoxPieces, &pieces); 889 else if (GTK_IS_ARROW(buttonChild)) 890 pieces.arrow = buttonChild; 891 892 ASSERT(pieces.arrow); 893 m_gtkComboBoxArrow = pieces.arrow; 894 setupWidget(m_gtkComboBoxArrow); 895 // When the style changes, the combo box may destroy its children. 896 g_object_add_weak_pointer(G_OBJECT(m_gtkComboBoxArrow), reinterpret_cast<gpointer*>(&m_gtkComboBoxArrow)); 897 898 m_gtkComboBoxSeparator = pieces.separator; 899 if (m_gtkComboBoxSeparator) { 900 setupWidget(m_gtkComboBoxSeparator); 901 // When the style changes, the combo box may destroy its children. 902 g_object_add_weak_pointer(G_OBJECT(m_gtkComboBoxSeparator), reinterpret_cast<gpointer*>(&m_gtkComboBoxSeparator)); 903 } 904} 905 906GtkWidget* RenderThemeGtk::gtkComboBoxButton() const 907{ 908 if (m_gtkComboBoxButton) 909 return m_gtkComboBoxButton; 910 refreshComboBoxChildren(); 911 ASSERT(m_gtkComboBoxButton); 912 return m_gtkComboBoxButton; 913} 914 915GtkWidget* RenderThemeGtk::gtkComboBoxArrow() const 916{ 917 if (m_gtkComboBoxArrow) 918 return m_gtkComboBoxArrow; 919 refreshComboBoxChildren(); 920 ASSERT(m_gtkComboBoxArrow); 921 return m_gtkComboBoxArrow; 922} 923 924GtkWidget* RenderThemeGtk::gtkComboBoxSeparator() const 925{ 926 // m_gtkComboBoxSeparator may be null either because we haven't initialized the combo box 927 // or because the combo boxes in this theme don't have separators. If m_gtkComboBoxArrow 928 // arrow isn't null, we definitely have initialized the combo box. 929 if (m_gtkComboBoxArrow || m_gtkComboBoxButton) 930 return m_gtkComboBoxSeparator; 931 refreshComboBoxChildren(); 932 return m_gtkComboBoxSeparator; 933} 934 935GtkWidget* RenderThemeGtk::gtkHScrollbar() const 936{ 937 if (m_gtkHScrollbar) 938 return m_gtkHScrollbar; 939 m_gtkHScrollbar = gtk_hscrollbar_new(0); 940 setupWidgetAndAddToContainer(m_gtkHScrollbar, gtkContainer()); 941 return m_gtkHScrollbar; 942} 943 944GtkWidget* RenderThemeGtk::gtkVScrollbar() const 945{ 946 if (m_gtkVScrollbar) 947 return m_gtkVScrollbar; 948 m_gtkVScrollbar = gtk_vscrollbar_new(0); 949 setupWidgetAndAddToContainer(m_gtkVScrollbar, gtkContainer()); 950 return m_gtkVScrollbar; 951} 952 953GtkWidget* RenderThemeGtk::gtkSpinButton() const 954{ 955 if (m_gtkSpinButton) 956 return m_gtkSpinButton; 957 m_gtkSpinButton = gtk_spin_button_new_with_range(0, 10, 1); 958 setupWidgetAndAddToContainer(m_gtkSpinButton, gtkContainer()); 959 return m_gtkSpinButton; 960} 961 962} // namespace WebCore 963 964#endif // GTK_API_VERSION_2 965