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