| OLD | NEW |
| (Empty) |
| 1 /* | |
| 2 * Copyright (c) 2011, Google Inc. All rights reserved. | |
| 3 * | |
| 4 * Redistribution and use in source and binary forms, with or without | |
| 5 * modification, are permitted provided that the following conditions are | |
| 6 * met: | |
| 7 * | |
| 8 * * Redistributions of source code must retain the above copyright | |
| 9 * notice, this list of conditions and the following disclaimer. | |
| 10 * * Redistributions in binary form must reproduce the above | |
| 11 * copyright notice, this list of conditions and the following disclaimer | |
| 12 * in the documentation and/or other materials provided with the | |
| 13 * distribution. | |
| 14 * * Neither the name of Google Inc. nor the names of its | |
| 15 * contributors may be used to endorse or promote products derived from | |
| 16 * this software without specific prior written permission. | |
| 17 * | |
| 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 29 */ | |
| 30 | |
| 31 #include "config.h" | |
| 32 #include "web/PopupListBox.h" | |
| 33 | |
| 34 #include "core/CSSValueKeywords.h" | |
| 35 #include "core/html/forms/PopupMenuClient.h" | |
| 36 #include "core/rendering/RenderTheme.h" | |
| 37 #include "platform/KeyboardCodes.h" | |
| 38 #include "platform/PlatformGestureEvent.h" | |
| 39 #include "platform/PlatformKeyboardEvent.h" | |
| 40 #include "platform/PlatformMouseEvent.h" | |
| 41 #include "platform/PlatformScreen.h" | |
| 42 #include "platform/PlatformTouchEvent.h" | |
| 43 #include "platform/PlatformWheelEvent.h" | |
| 44 #include "platform/RuntimeEnabledFeatures.h" | |
| 45 #include "platform/fonts/Font.h" | |
| 46 #include "platform/fonts/FontCache.h" | |
| 47 #include "platform/fonts/FontSelector.h" | |
| 48 #include "platform/geometry/IntRect.h" | |
| 49 #include "platform/graphics/GraphicsContext.h" | |
| 50 #include "platform/graphics/GraphicsContextStateSaver.h" | |
| 51 #include "platform/scroll/ScrollbarTheme.h" | |
| 52 #include "platform/text/StringTruncator.h" | |
| 53 #include "platform/text/TextRun.h" | |
| 54 #include "web/PopupContainer.h" | |
| 55 #include "web/PopupContainerClient.h" | |
| 56 #include "web/PopupMenuChromium.h" | |
| 57 #include "wtf/ASCIICType.h" | |
| 58 #include "wtf/CurrentTime.h" | |
| 59 #include <limits> | |
| 60 | |
| 61 namespace blink { | |
| 62 | |
| 63 using namespace WTF::Unicode; | |
| 64 | |
| 65 const int PopupListBox::defaultMaxHeight = 500; | |
| 66 static const int maxVisibleRows = 20; | |
| 67 static const int minEndOfLinePadding = 2; | |
| 68 static const TimeStamp typeAheadTimeoutMs = 1000; | |
| 69 | |
| 70 PopupListBox::PopupListBox(PopupMenuClient* client, bool deviceSupportsTouch, Po
pupContainer* container) | |
| 71 : m_deviceSupportsTouch(deviceSupportsTouch) | |
| 72 , m_originalIndex(0) | |
| 73 , m_selectedIndex(0) | |
| 74 , m_acceptedIndexOnAbandon(-1) | |
| 75 , m_visibleRows(0) | |
| 76 , m_baseWidth(0) | |
| 77 , m_maxHeight(defaultMaxHeight) | |
| 78 , m_popupClient(client) | |
| 79 , m_repeatingChar(0) | |
| 80 , m_lastCharTime(0) | |
| 81 , m_maxWindowWidth(std::numeric_limits<int>::max()) | |
| 82 , m_container(container) | |
| 83 { | |
| 84 } | |
| 85 | |
| 86 PopupListBox::~PopupListBox() | |
| 87 { | |
| 88 clear(); | |
| 89 | |
| 90 // Oilpan: the scrollbars of the ScrollView are self-sufficient, | |
| 91 // capable of detaching themselves from their animator on | |
| 92 // finalization. | |
| 93 #if !ENABLE(OILPAN) | |
| 94 setHasVerticalScrollbar(false); | |
| 95 #endif | |
| 96 } | |
| 97 | |
| 98 void PopupListBox::trace(Visitor* visitor) | |
| 99 { | |
| 100 visitor->trace(m_capturingScrollbar); | |
| 101 visitor->trace(m_lastScrollbarUnderMouse); | |
| 102 visitor->trace(m_focusedElement); | |
| 103 visitor->trace(m_container); | |
| 104 visitor->trace(m_verticalScrollbar); | |
| 105 Widget::trace(visitor); | |
| 106 } | |
| 107 | |
| 108 bool PopupListBox::handleMouseDownEvent(const PlatformMouseEvent& event) | |
| 109 { | |
| 110 Scrollbar* scrollbar = scrollbarAtWindowPoint(event.position()); | |
| 111 if (scrollbar) { | |
| 112 m_capturingScrollbar = scrollbar; | |
| 113 m_capturingScrollbar->mouseDown(event); | |
| 114 return true; | |
| 115 } | |
| 116 | |
| 117 if (!isPointInBounds(event.position())) | |
| 118 abandon(); | |
| 119 | |
| 120 return true; | |
| 121 } | |
| 122 | |
| 123 bool PopupListBox::handleMouseMoveEvent(const PlatformMouseEvent& event) | |
| 124 { | |
| 125 if (m_capturingScrollbar) { | |
| 126 m_capturingScrollbar->mouseMoved(event); | |
| 127 return true; | |
| 128 } | |
| 129 | |
| 130 Scrollbar* scrollbar = scrollbarAtWindowPoint(event.position()); | |
| 131 if (m_lastScrollbarUnderMouse != scrollbar) { | |
| 132 // Send mouse exited to the old scrollbar. | |
| 133 if (m_lastScrollbarUnderMouse) | |
| 134 m_lastScrollbarUnderMouse->mouseExited(); | |
| 135 m_lastScrollbarUnderMouse = scrollbar; | |
| 136 } | |
| 137 | |
| 138 if (scrollbar) { | |
| 139 scrollbar->mouseMoved(event); | |
| 140 return true; | |
| 141 } | |
| 142 | |
| 143 if (!isPointInBounds(event.position())) | |
| 144 return false; | |
| 145 | |
| 146 selectIndex(pointToRowIndex(event.position())); | |
| 147 return true; | |
| 148 } | |
| 149 | |
| 150 bool PopupListBox::handleMouseReleaseEvent(const PlatformMouseEvent& event) | |
| 151 { | |
| 152 if (m_capturingScrollbar) { | |
| 153 m_capturingScrollbar->mouseUp(event); | |
| 154 m_capturingScrollbar = nullptr; | |
| 155 return true; | |
| 156 } | |
| 157 | |
| 158 if (!isPointInBounds(event.position())) | |
| 159 return true; | |
| 160 | |
| 161 if (acceptIndex(pointToRowIndex(event.position())) && m_focusedElement) { | |
| 162 m_focusedElement->dispatchMouseEvent(event, EventTypeNames::mouseup); | |
| 163 m_focusedElement->dispatchMouseEvent(event, EventTypeNames::click); | |
| 164 | |
| 165 // Clear m_focusedElement here, because we cannot clear in hidePopup() | |
| 166 // which is called before dispatchMouseEvent() is called. | |
| 167 m_focusedElement = nullptr; | |
| 168 } | |
| 169 | |
| 170 return true; | |
| 171 } | |
| 172 | |
| 173 bool PopupListBox::handleWheelEvent(const PlatformWheelEvent& event) | |
| 174 { | |
| 175 if (!isPointInBounds(event.position())) { | |
| 176 abandon(); | |
| 177 return true; | |
| 178 } | |
| 179 | |
| 180 ScrollableArea::handleWheelEvent(event); | |
| 181 return true; | |
| 182 } | |
| 183 | |
| 184 // Should be kept in sync with handleKeyEvent(). | |
| 185 bool PopupListBox::isInterestedInEventForKey(int keyCode) | |
| 186 { | |
| 187 switch (keyCode) { | |
| 188 case VKEY_ESCAPE: | |
| 189 case VKEY_RETURN: | |
| 190 case VKEY_UP: | |
| 191 case VKEY_DOWN: | |
| 192 case VKEY_PRIOR: | |
| 193 case VKEY_NEXT: | |
| 194 case VKEY_HOME: | |
| 195 case VKEY_END: | |
| 196 case VKEY_TAB: | |
| 197 return true; | |
| 198 default: | |
| 199 return false; | |
| 200 } | |
| 201 } | |
| 202 | |
| 203 bool PopupListBox::handleTouchEvent(const PlatformTouchEvent&) | |
| 204 { | |
| 205 return false; | |
| 206 } | |
| 207 | |
| 208 bool PopupListBox::handleGestureEvent(const PlatformGestureEvent&) | |
| 209 { | |
| 210 return false; | |
| 211 } | |
| 212 | |
| 213 static bool isCharacterTypeEvent(const PlatformKeyboardEvent& event) | |
| 214 { | |
| 215 // Check whether the event is a character-typed event or not. | |
| 216 // We use RawKeyDown/Char/KeyUp event scheme on all platforms, | |
| 217 // so PlatformKeyboardEvent::Char (not RawKeyDown) type event | |
| 218 // is considered as character type event. | |
| 219 return event.type() == PlatformEvent::Char; | |
| 220 } | |
| 221 | |
| 222 bool PopupListBox::handleKeyEvent(const PlatformKeyboardEvent& event) | |
| 223 { | |
| 224 if (event.type() == PlatformEvent::KeyUp) | |
| 225 return true; | |
| 226 | |
| 227 if (!numItems() && event.windowsVirtualKeyCode() != VKEY_ESCAPE) | |
| 228 return true; | |
| 229 | |
| 230 switch (event.windowsVirtualKeyCode()) { | |
| 231 case VKEY_ESCAPE: | |
| 232 abandon(); // may delete this | |
| 233 return true; | |
| 234 case VKEY_RETURN: | |
| 235 if (m_selectedIndex == -1) { | |
| 236 hidePopup(); | |
| 237 // Don't eat the enter if nothing is selected. | |
| 238 return false; | |
| 239 } | |
| 240 acceptIndex(m_selectedIndex); // may delete this | |
| 241 return true; | |
| 242 case VKEY_UP: | |
| 243 selectPreviousRow(); | |
| 244 break; | |
| 245 case VKEY_DOWN: | |
| 246 selectNextRow(); | |
| 247 break; | |
| 248 case VKEY_PRIOR: | |
| 249 adjustSelectedIndex(-m_visibleRows); | |
| 250 break; | |
| 251 case VKEY_NEXT: | |
| 252 adjustSelectedIndex(m_visibleRows); | |
| 253 break; | |
| 254 case VKEY_HOME: | |
| 255 adjustSelectedIndex(-m_selectedIndex); | |
| 256 break; | |
| 257 case VKEY_END: | |
| 258 adjustSelectedIndex(m_items.size()); | |
| 259 break; | |
| 260 default: | |
| 261 if (!event.ctrlKey() && !event.altKey() && !event.metaKey() | |
| 262 && isPrintableChar(event.windowsVirtualKeyCode()) | |
| 263 && isCharacterTypeEvent(event)) | |
| 264 typeAheadFind(event); | |
| 265 break; | |
| 266 } | |
| 267 | |
| 268 if (event.altKey() && (event.keyIdentifier() == "Down" || event.keyIdentifie
r() == "Up")) { | |
| 269 hidePopup(); | |
| 270 return true; | |
| 271 } | |
| 272 | |
| 273 if (m_originalIndex != m_selectedIndex) { | |
| 274 // Keyboard events should update the selection immediately (but we don't | |
| 275 // want to fire the onchange event until the popup is closed, to match | |
| 276 // IE). We change the original index so we revert to that when the | |
| 277 // popup is closed. | |
| 278 m_acceptedIndexOnAbandon = m_selectedIndex; | |
| 279 | |
| 280 setOriginalIndex(m_selectedIndex); | |
| 281 m_popupClient->setTextFromItem(m_selectedIndex); | |
| 282 } | |
| 283 if (event.windowsVirtualKeyCode() == VKEY_TAB) { | |
| 284 // TAB is a special case as it should select the current item if any and | |
| 285 // advance focus. | |
| 286 if (m_selectedIndex >= 0) { | |
| 287 acceptIndex(m_selectedIndex); // May delete us. | |
| 288 // Return false so the TAB key event is propagated to the page. | |
| 289 return false; | |
| 290 } | |
| 291 // Call abandon() so we honor m_acceptedIndexOnAbandon if set. | |
| 292 abandon(); | |
| 293 // Return false so the TAB key event is propagated to the page. | |
| 294 return false; | |
| 295 } | |
| 296 | |
| 297 return true; | |
| 298 } | |
| 299 | |
| 300 HostWindow* PopupListBox::hostWindow() const | |
| 301 { | |
| 302 // Our parent is the root FrameView, so it is the one that has a | |
| 303 // HostWindow. FrameView::hostWindow() works similarly. | |
| 304 return parent() ? parent()->hostWindow() : 0; | |
| 305 } | |
| 306 | |
| 307 bool PopupListBox::shouldPlaceVerticalScrollbarOnLeft() const | |
| 308 { | |
| 309 return m_popupClient->menuStyle().textDirection() == RTL; | |
| 310 } | |
| 311 | |
| 312 // From HTMLSelectElement.cpp | |
| 313 static String stripLeadingWhiteSpace(const String& string) | |
| 314 { | |
| 315 int length = string.length(); | |
| 316 int i; | |
| 317 for (i = 0; i < length; ++i) | |
| 318 if (string[i] != noBreakSpace | |
| 319 && !isSpaceOrNewline(string[i])) | |
| 320 break; | |
| 321 | |
| 322 return string.substring(i, length - i); | |
| 323 } | |
| 324 | |
| 325 // From HTMLSelectElement.cpp, with modifications | |
| 326 void PopupListBox::typeAheadFind(const PlatformKeyboardEvent& event) | |
| 327 { | |
| 328 TimeStamp now = static_cast<TimeStamp>(currentTime() * 1000.0f); | |
| 329 TimeStamp delta = now - m_lastCharTime; | |
| 330 | |
| 331 // Reset the time when user types in a character. The time gap between | |
| 332 // last character and the current character is used to indicate whether | |
| 333 // user typed in a string or just a character as the search prefix. | |
| 334 m_lastCharTime = now; | |
| 335 | |
| 336 UChar c = event.windowsVirtualKeyCode(); | |
| 337 | |
| 338 String prefix; | |
| 339 int searchStartOffset = 1; | |
| 340 if (delta > typeAheadTimeoutMs) { | |
| 341 m_typedString = prefix = String(&c, 1); | |
| 342 m_repeatingChar = c; | |
| 343 } else { | |
| 344 m_typedString.append(c); | |
| 345 | |
| 346 if (c == m_repeatingChar) { | |
| 347 // The user is likely trying to cycle through all the items starting | |
| 348 // with this character, so just search on the character. | |
| 349 prefix = String(&c, 1); | |
| 350 } else { | |
| 351 m_repeatingChar = 0; | |
| 352 prefix = m_typedString; | |
| 353 searchStartOffset = 0; | |
| 354 } | |
| 355 } | |
| 356 | |
| 357 // Compute a case-folded copy of the prefix string before beginning the | |
| 358 // search for a matching element. This code uses foldCase to work around the | |
| 359 // fact that String::startWith does not fold non-ASCII characters. This code | |
| 360 // can be changed to use startWith once that is fixed. | |
| 361 String prefixWithCaseFolded(prefix.foldCase()); | |
| 362 int itemCount = numItems(); | |
| 363 int index = (max(0, m_selectedIndex) + searchStartOffset) % itemCount; | |
| 364 for (int i = 0; i < itemCount; i++, index = (index + 1) % itemCount) { | |
| 365 if (!isSelectableItem(index)) | |
| 366 continue; | |
| 367 | |
| 368 if (stripLeadingWhiteSpace(m_items[index]->label).foldCase().startsWith(
prefixWithCaseFolded)) { | |
| 369 selectIndex(index); | |
| 370 return; | |
| 371 } | |
| 372 } | |
| 373 } | |
| 374 | |
| 375 void PopupListBox::paint(GraphicsContext* gc, const IntRect& rect) | |
| 376 { | |
| 377 // Adjust coords for scrolled frame. | |
| 378 IntRect r = intersection(rect, frameRect()); | |
| 379 int tx = x() - scrollX() + ((shouldPlaceVerticalScrollbarOnLeft() && vertica
lScrollbar() && !verticalScrollbar()->isOverlayScrollbar()) ? verticalScrollbar(
)->width() : 0); | |
| 380 int ty = y() - scrollY(); | |
| 381 | |
| 382 r.move(-tx, -ty); | |
| 383 | |
| 384 // Set clip rect to match revised damage rect. | |
| 385 gc->save(); | |
| 386 gc->translate(static_cast<float>(tx), static_cast<float>(ty)); | |
| 387 gc->clip(r); | |
| 388 | |
| 389 // FIXME: Can we optimize scrolling to not require repainting the entire | |
| 390 // window? Should we? | |
| 391 for (int i = 0; i < numItems(); ++i) | |
| 392 paintRow(gc, r, i); | |
| 393 | |
| 394 // Special case for an empty popup. | |
| 395 if (!numItems()) | |
| 396 gc->fillRect(r, Color::white); | |
| 397 | |
| 398 gc->restore(); | |
| 399 | |
| 400 if (m_verticalScrollbar) { | |
| 401 GraphicsContextStateSaver stateSaver(*gc); | |
| 402 IntRect scrollbarDirtyRect = rect; | |
| 403 IntRect visibleAreaWithScrollbars(location(), visibleContentRect(Include
Scrollbars).size()); | |
| 404 scrollbarDirtyRect.intersect(visibleAreaWithScrollbars); | |
| 405 gc->translate(x(), y()); | |
| 406 scrollbarDirtyRect.moveBy(-location()); | |
| 407 gc->clip(IntRect(IntPoint(), visibleAreaWithScrollbars.size())); | |
| 408 | |
| 409 m_verticalScrollbar->paint(gc, scrollbarDirtyRect); | |
| 410 } | |
| 411 } | |
| 412 | |
| 413 static const int separatorPadding = 4; | |
| 414 static const int separatorHeight = 1; | |
| 415 static const int minRowHeight = 0; | |
| 416 static const int optionRowHeightForTouch = 28; | |
| 417 | |
| 418 void PopupListBox::paintRow(GraphicsContext* gc, const IntRect& rect, int rowInd
ex) | |
| 419 { | |
| 420 // This code is based largely on RenderListBox::paint* methods. | |
| 421 | |
| 422 IntRect rowRect = getRowBounds(rowIndex); | |
| 423 if (!rowRect.intersects(rect)) | |
| 424 return; | |
| 425 | |
| 426 PopupMenuStyle style = m_popupClient->itemStyle(rowIndex); | |
| 427 | |
| 428 // Paint background | |
| 429 Color backColor, textColor, labelColor; | |
| 430 if (rowIndex == m_selectedIndex) { | |
| 431 backColor = RenderTheme::theme().activeListBoxSelectionBackgroundColor()
; | |
| 432 textColor = RenderTheme::theme().activeListBoxSelectionForegroundColor()
; | |
| 433 labelColor = textColor; | |
| 434 } else { | |
| 435 backColor = style.backgroundColor(); | |
| 436 textColor = style.foregroundColor(); | |
| 437 #if OS(LINUX) || OS(ANDROID) | |
| 438 // On other platforms, the <option> background color is the same as the | |
| 439 // <select> background color. On Linux, that makes the <option> | |
| 440 // background color very dark, so by default, try to use a lighter | |
| 441 // background color for <option>s. | |
| 442 if (style.backgroundColorType() == PopupMenuStyle::DefaultBackgroundColo
r && RenderTheme::theme().systemColor(CSSValueButtonface) == backColor) | |
| 443 backColor = RenderTheme::theme().systemColor(CSSValueMenu); | |
| 444 #endif | |
| 445 | |
| 446 // FIXME: for now the label color is hard-coded. It should be added to | |
| 447 // the PopupMenuStyle. | |
| 448 labelColor = Color(115, 115, 115); | |
| 449 } | |
| 450 | |
| 451 // If we have a transparent background, make sure it has a color to blend | |
| 452 // against. | |
| 453 if (backColor.hasAlpha()) | |
| 454 gc->fillRect(rowRect, Color::white); | |
| 455 | |
| 456 gc->fillRect(rowRect, backColor); | |
| 457 | |
| 458 if (m_popupClient->itemIsSeparator(rowIndex)) { | |
| 459 IntRect separatorRect( | |
| 460 rowRect.x() + separatorPadding, | |
| 461 rowRect.y() + (rowRect.height() - separatorHeight) / 2, | |
| 462 rowRect.width() - 2 * separatorPadding, separatorHeight); | |
| 463 gc->fillRect(separatorRect, textColor); | |
| 464 return; | |
| 465 } | |
| 466 | |
| 467 if (!style.isVisible()) | |
| 468 return; | |
| 469 | |
| 470 gc->setFillColor(textColor); | |
| 471 | |
| 472 FontCachePurgePreventer fontCachePurgePreventer; | |
| 473 | |
| 474 Font itemFont = getRowFont(rowIndex); | |
| 475 // FIXME: http://crbug.com/19872 We should get the padding of individual opt
ion | |
| 476 // elements. This probably implies changes to PopupMenuClient. | |
| 477 bool rightAligned = m_popupClient->menuStyle().textDirection() == RTL; | |
| 478 int textX = 0; | |
| 479 int maxWidth = 0; | |
| 480 if (rightAligned) { | |
| 481 maxWidth = rowRect.width() - max<int>(0, m_popupClient->clientPaddingRig
ht()); | |
| 482 } else { | |
| 483 textX = max<int>(0, m_popupClient->clientPaddingLeft()); | |
| 484 maxWidth = rowRect.width() - textX; | |
| 485 } | |
| 486 // Prepare text to be drawn. | |
| 487 String itemText = m_popupClient->itemText(rowIndex); | |
| 488 | |
| 489 // Prepare the directionality to draw text. | |
| 490 TextRun textRun(itemText, 0, 0, TextRun::AllowTrailingExpansion, style.textD
irection(), style.hasTextDirectionOverride()); | |
| 491 // If the text is right-to-left, make it right-aligned by adjusting its | |
| 492 // beginning position. | |
| 493 if (rightAligned) | |
| 494 textX += maxWidth - itemFont.width(textRun); | |
| 495 | |
| 496 // Draw the item text. | |
| 497 int textY = rowRect.y() + itemFont.fontMetrics().ascent() + (rowRect.height(
) - itemFont.fontMetrics().height()) / 2; | |
| 498 TextRunPaintInfo textRunPaintInfo(textRun); | |
| 499 textRunPaintInfo.bounds = rowRect; | |
| 500 gc->drawBidiText(itemFont, textRunPaintInfo, IntPoint(textX, textY)); | |
| 501 } | |
| 502 | |
| 503 Font PopupListBox::getRowFont(int rowIndex) const | |
| 504 { | |
| 505 Font itemFont = m_popupClient->itemStyle(rowIndex).font(); | |
| 506 if (m_popupClient->itemIsLabel(rowIndex)) { | |
| 507 // Bold-ify labels (ie, an <optgroup> heading). | |
| 508 FontDescription d = itemFont.fontDescription(); | |
| 509 d.setWeight(FontWeightBold); | |
| 510 Font font(d); | |
| 511 font.update(nullptr); | |
| 512 return font; | |
| 513 } | |
| 514 | |
| 515 return itemFont; | |
| 516 } | |
| 517 | |
| 518 void PopupListBox::abandon() | |
| 519 { | |
| 520 RefPtrWillBeRawPtr<PopupListBox> protect(this); | |
| 521 | |
| 522 m_selectedIndex = m_originalIndex; | |
| 523 | |
| 524 hidePopup(); | |
| 525 | |
| 526 if (m_acceptedIndexOnAbandon >= 0) { | |
| 527 if (m_popupClient) | |
| 528 m_popupClient->valueChanged(m_acceptedIndexOnAbandon); | |
| 529 m_acceptedIndexOnAbandon = -1; | |
| 530 } | |
| 531 } | |
| 532 | |
| 533 int PopupListBox::pointToRowIndex(const IntPoint& point) | |
| 534 { | |
| 535 int y = scrollY() + point.y(); | |
| 536 | |
| 537 // FIXME: binary search if perf matters. | |
| 538 for (int i = 0; i < numItems(); ++i) { | |
| 539 if (y < m_items[i]->yOffset) | |
| 540 return i-1; | |
| 541 } | |
| 542 | |
| 543 // Last item? | |
| 544 if (y < contentsSize().height()) | |
| 545 return m_items.size()-1; | |
| 546 | |
| 547 return -1; | |
| 548 } | |
| 549 | |
| 550 bool PopupListBox::acceptIndex(int index) | |
| 551 { | |
| 552 // Clear m_acceptedIndexOnAbandon once user accepts the selected index. | |
| 553 if (m_acceptedIndexOnAbandon >= 0) | |
| 554 m_acceptedIndexOnAbandon = -1; | |
| 555 | |
| 556 if (index >= numItems()) | |
| 557 return false; | |
| 558 | |
| 559 if (index < 0) { | |
| 560 if (m_popupClient) { | |
| 561 // Enter pressed with no selection, just close the popup. | |
| 562 hidePopup(); | |
| 563 } | |
| 564 return false; | |
| 565 } | |
| 566 | |
| 567 if (isSelectableItem(index)) { | |
| 568 RefPtrWillBeRawPtr<PopupListBox> protect(this); | |
| 569 | |
| 570 // Hide ourselves first since valueChanged may have numerous side-effect
s. | |
| 571 hidePopup(); | |
| 572 | |
| 573 // Tell the <select> PopupMenuClient what index was selected. | |
| 574 m_popupClient->valueChanged(index); | |
| 575 | |
| 576 return true; | |
| 577 } | |
| 578 | |
| 579 return false; | |
| 580 } | |
| 581 | |
| 582 void PopupListBox::selectIndex(int index) | |
| 583 { | |
| 584 if (index < 0 || index >= numItems()) | |
| 585 return; | |
| 586 | |
| 587 bool isSelectable = isSelectableItem(index); | |
| 588 if (index != m_selectedIndex && isSelectable) { | |
| 589 invalidateRow(m_selectedIndex); | |
| 590 m_selectedIndex = index; | |
| 591 invalidateRow(m_selectedIndex); | |
| 592 | |
| 593 scrollToRevealSelection(); | |
| 594 m_popupClient->selectionChanged(m_selectedIndex); | |
| 595 } else if (!isSelectable) | |
| 596 clearSelection(); | |
| 597 } | |
| 598 | |
| 599 void PopupListBox::setOriginalIndex(int index) | |
| 600 { | |
| 601 m_originalIndex = m_selectedIndex = index; | |
| 602 } | |
| 603 | |
| 604 int PopupListBox::getRowHeight(int index) const | |
| 605 { | |
| 606 int minimumHeight = m_deviceSupportsTouch ? optionRowHeightForTouch : minRow
Height; | |
| 607 | |
| 608 if (index < 0 || m_popupClient->itemStyle(index).isDisplayNone()) | |
| 609 return minimumHeight; | |
| 610 | |
| 611 // Separator row height is the same size as itself. | |
| 612 if (m_popupClient->itemIsSeparator(index)) | |
| 613 return max(separatorHeight, minimumHeight); | |
| 614 | |
| 615 int fontHeight = getRowFont(index).fontMetrics().height(); | |
| 616 return max(fontHeight, minimumHeight); | |
| 617 } | |
| 618 | |
| 619 IntRect PopupListBox::getRowBounds(int index) | |
| 620 { | |
| 621 if (index < 0) | |
| 622 return IntRect(0, 0, visibleWidth(), getRowHeight(index)); | |
| 623 | |
| 624 return IntRect(0, m_items[index]->yOffset, visibleWidth(), getRowHeight(inde
x)); | |
| 625 } | |
| 626 | |
| 627 void PopupListBox::invalidateRow(int index) | |
| 628 { | |
| 629 if (index < 0) | |
| 630 return; | |
| 631 | |
| 632 // Invalidate in the window contents, as invalidateRect paints in the window
coordinates. | |
| 633 IntRect clipRect = contentsToWindow(getRowBounds(index)); | |
| 634 if (shouldPlaceVerticalScrollbarOnLeft() && verticalScrollbar() && !vertical
Scrollbar()->isOverlayScrollbar()) | |
| 635 clipRect.move(verticalScrollbar()->width(), 0); | |
| 636 invalidateRect(clipRect); | |
| 637 } | |
| 638 | |
| 639 void PopupListBox::scrollToRevealRow(int index) | |
| 640 { | |
| 641 if (index < 0) | |
| 642 return; | |
| 643 | |
| 644 IntRect rowRect = getRowBounds(index); | |
| 645 | |
| 646 if (rowRect.y() < scrollY()) { | |
| 647 // Row is above current scroll position, scroll up. | |
| 648 updateScrollbars(IntPoint(0, rowRect.y())); | |
| 649 } else if (rowRect.maxY() > scrollY() + visibleHeight()) { | |
| 650 // Row is below current scroll position, scroll down. | |
| 651 updateScrollbars(IntPoint(0, rowRect.maxY() - visibleHeight())); | |
| 652 } | |
| 653 } | |
| 654 | |
| 655 bool PopupListBox::isSelectableItem(int index) | |
| 656 { | |
| 657 ASSERT(index >= 0 && index < numItems()); | |
| 658 return m_items[index]->type == PopupItem::TypeOption && m_popupClient->itemI
sEnabled(index); | |
| 659 } | |
| 660 | |
| 661 void PopupListBox::clearSelection() | |
| 662 { | |
| 663 if (m_selectedIndex != -1) { | |
| 664 invalidateRow(m_selectedIndex); | |
| 665 m_selectedIndex = -1; | |
| 666 m_popupClient->selectionCleared(); | |
| 667 } | |
| 668 } | |
| 669 | |
| 670 void PopupListBox::selectNextRow() | |
| 671 { | |
| 672 adjustSelectedIndex(1); | |
| 673 } | |
| 674 | |
| 675 void PopupListBox::selectPreviousRow() | |
| 676 { | |
| 677 adjustSelectedIndex(-1); | |
| 678 } | |
| 679 | |
| 680 void PopupListBox::adjustSelectedIndex(int delta) | |
| 681 { | |
| 682 int targetIndex = m_selectedIndex + delta; | |
| 683 targetIndex = std::min(std::max(targetIndex, 0), numItems() - 1); | |
| 684 if (!isSelectableItem(targetIndex)) { | |
| 685 // We didn't land on an option. Try to find one. | |
| 686 // We try to select the closest index to target, prioritizing any in | |
| 687 // the range [current, target]. | |
| 688 | |
| 689 int dir = delta > 0 ? 1 : -1; | |
| 690 int testIndex = m_selectedIndex; | |
| 691 int bestIndex = m_selectedIndex; | |
| 692 bool passedTarget = false; | |
| 693 while (testIndex >= 0 && testIndex < numItems()) { | |
| 694 if (isSelectableItem(testIndex)) | |
| 695 bestIndex = testIndex; | |
| 696 if (testIndex == targetIndex) | |
| 697 passedTarget = true; | |
| 698 if (passedTarget && bestIndex != m_selectedIndex) | |
| 699 break; | |
| 700 | |
| 701 testIndex += dir; | |
| 702 } | |
| 703 | |
| 704 // Pick the best index, which may mean we don't change. | |
| 705 targetIndex = bestIndex; | |
| 706 } | |
| 707 | |
| 708 // Select the new index, and ensure its visible. We do this regardless of | |
| 709 // whether the selection changed to ensure keyboard events always bring the | |
| 710 // selection into view. | |
| 711 selectIndex(targetIndex); | |
| 712 scrollToRevealSelection(); | |
| 713 } | |
| 714 | |
| 715 void PopupListBox::hidePopup() | |
| 716 { | |
| 717 if (parent()) { | |
| 718 if (m_container->client()) | |
| 719 m_container->client()->popupClosed(m_container); | |
| 720 m_container->notifyPopupHidden(); | |
| 721 } | |
| 722 | |
| 723 if (m_popupClient) | |
| 724 m_popupClient->popupDidHide(); | |
| 725 } | |
| 726 | |
| 727 void PopupListBox::updateFromElement() | |
| 728 { | |
| 729 clear(); | |
| 730 | |
| 731 int size = m_popupClient->listSize(); | |
| 732 for (int i = 0; i < size; ++i) { | |
| 733 PopupItem::Type type; | |
| 734 if (m_popupClient->itemIsSeparator(i)) | |
| 735 type = PopupItem::TypeSeparator; | |
| 736 else if (m_popupClient->itemIsLabel(i)) | |
| 737 type = PopupItem::TypeGroup; | |
| 738 else | |
| 739 type = PopupItem::TypeOption; | |
| 740 m_items.append(new PopupItem(m_popupClient->itemText(i), type)); | |
| 741 m_items[i]->enabled = isSelectableItem(i); | |
| 742 PopupMenuStyle style = m_popupClient->itemStyle(i); | |
| 743 m_items[i]->textDirection = style.textDirection(); | |
| 744 m_items[i]->hasTextDirectionOverride = style.hasTextDirectionOverride(); | |
| 745 m_items[i]->displayNone = style.isDisplayNone(); | |
| 746 } | |
| 747 | |
| 748 m_selectedIndex = m_popupClient->selectedIndex(); | |
| 749 setOriginalIndex(m_selectedIndex); | |
| 750 | |
| 751 layout(); | |
| 752 } | |
| 753 | |
| 754 void PopupListBox::setMaxWidthAndLayout(int maxWidth) | |
| 755 { | |
| 756 m_maxWindowWidth = maxWidth; | |
| 757 layout(); | |
| 758 } | |
| 759 | |
| 760 int PopupListBox::getRowBaseWidth(int index) | |
| 761 { | |
| 762 Font font = getRowFont(index); | |
| 763 PopupMenuStyle style = m_popupClient->itemStyle(index); | |
| 764 String text = m_popupClient->itemText(index); | |
| 765 if (text.isEmpty()) | |
| 766 return 0; | |
| 767 TextRun textRun(text, 0, 0, TextRun::AllowTrailingExpansion, style.textDirec
tion(), style.hasTextDirectionOverride()); | |
| 768 return font.width(textRun); | |
| 769 } | |
| 770 | |
| 771 void PopupListBox::layout() | |
| 772 { | |
| 773 bool isRightAligned = m_popupClient->menuStyle().textDirection() == RTL; | |
| 774 | |
| 775 // Size our child items. | |
| 776 int baseWidth = 0; | |
| 777 int paddingWidth = 0; | |
| 778 int lineEndPaddingWidth = 0; | |
| 779 int y = 0; | |
| 780 for (int i = 0; i < numItems(); ++i) { | |
| 781 // Place the item vertically. | |
| 782 m_items[i]->yOffset = y; | |
| 783 if (m_popupClient->itemStyle(i).isDisplayNone()) | |
| 784 continue; | |
| 785 y += getRowHeight(i); | |
| 786 | |
| 787 // Ensure the popup is wide enough to fit this item. | |
| 788 baseWidth = max(baseWidth, getRowBaseWidth(i)); | |
| 789 // FIXME: http://b/1210481 We should get the padding of individual | |
| 790 // option elements. | |
| 791 paddingWidth = max<int>(paddingWidth, | |
| 792 m_popupClient->clientPaddingLeft() + m_popupClient->clientPaddingRig
ht()); | |
| 793 lineEndPaddingWidth = max<int>(lineEndPaddingWidth, | |
| 794 isRightAligned ? m_popupClient->clientPaddingLeft() : m_popupClient-
>clientPaddingRight()); | |
| 795 } | |
| 796 | |
| 797 // Calculate scroll bar width. | |
| 798 int windowHeight = 0; | |
| 799 m_visibleRows = std::min(numItems(), maxVisibleRows); | |
| 800 | |
| 801 for (int i = 0; i < m_visibleRows; ++i) { | |
| 802 int rowHeight = getRowHeight(i); | |
| 803 | |
| 804 // Only clip the window height for non-Mac platforms. | |
| 805 if (windowHeight + rowHeight > m_maxHeight) { | |
| 806 m_visibleRows = i; | |
| 807 break; | |
| 808 } | |
| 809 | |
| 810 windowHeight += rowHeight; | |
| 811 } | |
| 812 | |
| 813 // Set our widget and scrollable contents sizes. | |
| 814 int scrollbarWidth = 0; | |
| 815 if (m_visibleRows < numItems()) { | |
| 816 if (!ScrollbarTheme::theme()->usesOverlayScrollbars()) | |
| 817 scrollbarWidth = ScrollbarTheme::theme()->scrollbarThickness(); | |
| 818 | |
| 819 // Use minEndOfLinePadding when there is a scrollbar so that we use | |
| 820 // as much as (lineEndPaddingWidth - minEndOfLinePadding) padding | |
| 821 // space for scrollbar and allow user to use CSS padding to make the | |
| 822 // popup listbox align with the select element. | |
| 823 paddingWidth = paddingWidth - lineEndPaddingWidth + minEndOfLinePadding; | |
| 824 } | |
| 825 | |
| 826 int windowWidth = baseWidth + scrollbarWidth + paddingWidth; | |
| 827 if (windowWidth > m_maxWindowWidth) { | |
| 828 // windowWidth exceeds m_maxWindowWidth, so we have to clip. | |
| 829 windowWidth = m_maxWindowWidth; | |
| 830 baseWidth = windowWidth - scrollbarWidth - paddingWidth; | |
| 831 m_baseWidth = baseWidth; | |
| 832 } | |
| 833 int contentWidth = windowWidth - scrollbarWidth; | |
| 834 | |
| 835 if (windowWidth < m_baseWidth) { | |
| 836 windowWidth = m_baseWidth; | |
| 837 contentWidth = m_baseWidth - scrollbarWidth; | |
| 838 } else { | |
| 839 m_baseWidth = baseWidth; | |
| 840 } | |
| 841 | |
| 842 resize(windowWidth, windowHeight); | |
| 843 setContentsSize(IntSize(contentWidth, getRowBounds(numItems() - 1).maxY())); | |
| 844 | |
| 845 if (hostWindow()) | |
| 846 scrollToRevealSelection(); | |
| 847 | |
| 848 invalidate(); | |
| 849 } | |
| 850 | |
| 851 void PopupListBox::clear() | |
| 852 { | |
| 853 deleteAllValues(m_items); | |
| 854 m_items.clear(); | |
| 855 } | |
| 856 | |
| 857 bool PopupListBox::isPointInBounds(const IntPoint& point) | |
| 858 { | |
| 859 return numItems() && IntRect(0, 0, width(), height()).contains(point); | |
| 860 } | |
| 861 | |
| 862 int PopupListBox::popupContentHeight() const | |
| 863 { | |
| 864 return height(); | |
| 865 } | |
| 866 | |
| 867 void PopupListBox::invalidateRect(const IntRect& rect) | |
| 868 { | |
| 869 if (HostWindow* h = hostWindow()) | |
| 870 h->invalidateContentsAndRootView(rect); | |
| 871 } | |
| 872 | |
| 873 IntRect PopupListBox::windowClipRect() const | |
| 874 { | |
| 875 IntRect clipRect = visibleContentRect(); | |
| 876 if (shouldPlaceVerticalScrollbarOnLeft() && verticalScrollbar() && !vertical
Scrollbar()->isOverlayScrollbar()) | |
| 877 clipRect.move(verticalScrollbar()->width(), 0); | |
| 878 return contentsToWindow(clipRect); | |
| 879 } | |
| 880 | |
| 881 void PopupListBox::invalidateScrollbarRect(Scrollbar* scrollbar, const IntRect&
rect) | |
| 882 { | |
| 883 // Add in our offset within the FrameView. | |
| 884 IntRect dirtyRect = rect; | |
| 885 dirtyRect.move(scrollbar->x(), scrollbar->y()); | |
| 886 invalidateRect(dirtyRect); | |
| 887 } | |
| 888 | |
| 889 bool PopupListBox::isActive() const | |
| 890 { | |
| 891 // FIXME | |
| 892 return true; | |
| 893 } | |
| 894 | |
| 895 bool PopupListBox::scrollbarsCanBeActive() const | |
| 896 { | |
| 897 return isActive(); | |
| 898 } | |
| 899 | |
| 900 IntRect PopupListBox::scrollableAreaBoundingBox() const | |
| 901 { | |
| 902 return windowClipRect(); | |
| 903 } | |
| 904 | |
| 905 // FIXME: The following methods are based on code in FrameView, with | |
| 906 // simplifications for the constraints of PopupListBox (e.g. only vertical | |
| 907 // scrollbar, not horizontal). This functionality should be moved into | |
| 908 // ScrollableArea after http://crbug.com/417782 is fixed. | |
| 909 | |
| 910 void PopupListBox::setHasVerticalScrollbar(bool hasBar) | |
| 911 { | |
| 912 if (hasBar && !m_verticalScrollbar) { | |
| 913 m_verticalScrollbar = Scrollbar::create(this, VerticalScrollbar, Regular
Scrollbar); | |
| 914 m_verticalScrollbar->setParent(this); | |
| 915 didAddScrollbar(m_verticalScrollbar.get(), VerticalScrollbar); | |
| 916 m_verticalScrollbar->styleChanged(); | |
| 917 } else if (!hasBar && m_verticalScrollbar) { | |
| 918 m_verticalScrollbar->setParent(0); | |
| 919 willRemoveScrollbar(m_verticalScrollbar.get(), VerticalScrollbar); | |
| 920 m_verticalScrollbar = nullptr; | |
| 921 } | |
| 922 } | |
| 923 | |
| 924 Scrollbar* PopupListBox::scrollbarAtWindowPoint(const IntPoint& windowPoint) | |
| 925 { | |
| 926 return m_verticalScrollbar && m_verticalScrollbar->frameRect().contains( | |
| 927 convertFromContainingWindow(windowPoint)) ? m_verticalScrollbar.get() :
0; | |
| 928 } | |
| 929 | |
| 930 IntRect PopupListBox::contentsToWindow(const IntRect& contentsRect) const | |
| 931 { | |
| 932 IntRect viewRect = contentsRect; | |
| 933 viewRect.moveBy(-scrollPosition()); | |
| 934 return convertToContainingWindow(viewRect); | |
| 935 } | |
| 936 | |
| 937 void PopupListBox::setContentsSize(const IntSize& newSize) | |
| 938 { | |
| 939 if (contentsSize() == newSize) | |
| 940 return; | |
| 941 m_contentsSize = newSize; | |
| 942 updateScrollbars(scrollPosition()); | |
| 943 } | |
| 944 | |
| 945 void PopupListBox::setFrameRect(const IntRect& newRect) | |
| 946 { | |
| 947 IntRect oldRect = frameRect(); | |
| 948 if (newRect == oldRect) | |
| 949 return; | |
| 950 | |
| 951 Widget::setFrameRect(newRect); | |
| 952 updateScrollbars(scrollPosition()); | |
| 953 // NOTE: We do not need to call m_verticalScrollbar->frameRectsChanged as | |
| 954 // Scrollbar does not implement it. | |
| 955 } | |
| 956 | |
| 957 IntRect PopupListBox::visibleContentRect(IncludeScrollbarsInRect scrollbarInclus
ion) const | |
| 958 { | |
| 959 // NOTE: Unlike FrameView we do not need to incorporate any scaling factor, | |
| 960 // and there is only one scrollbar to exclude. | |
| 961 IntSize size = frameRect().size(); | |
| 962 Scrollbar* verticalBar = verticalScrollbar(); | |
| 963 if (scrollbarInclusion == ExcludeScrollbars && verticalBar && !verticalBar->
isOverlayScrollbar()) { | |
| 964 size.setWidth(std::max(0, size.width() - verticalBar->width())); | |
| 965 } | |
| 966 return IntRect(m_scrollOffset, size); | |
| 967 } | |
| 968 | |
| 969 void PopupListBox::updateScrollbars(IntPoint desiredOffset) | |
| 970 { | |
| 971 IntSize oldVisibleSize = visibleContentRect().size(); | |
| 972 adjustScrollbarExistence(); | |
| 973 updateScrollbarGeometry(); | |
| 974 IntSize newVisibleSize = visibleContentRect().size(); | |
| 975 | |
| 976 if (newVisibleSize.width() > oldVisibleSize.width()) { | |
| 977 if (shouldPlaceVerticalScrollbarOnLeft()) | |
| 978 invalidateRect(IntRect(0, 0, newVisibleSize.width() - oldVisibleSize
.width(), newVisibleSize.height())); | |
| 979 else | |
| 980 invalidateRect(IntRect(oldVisibleSize.width(), 0, newVisibleSize.wid
th() - oldVisibleSize.width(), newVisibleSize.height())); | |
| 981 } | |
| 982 | |
| 983 desiredOffset = desiredOffset.shrunkTo(maximumScrollPosition()); | |
| 984 desiredOffset = desiredOffset.expandedTo(minimumScrollPosition()); | |
| 985 | |
| 986 if (desiredOffset != scrollPosition()) | |
| 987 ScrollableArea::scrollToOffsetWithoutAnimation(desiredOffset); | |
| 988 } | |
| 989 | |
| 990 void PopupListBox::adjustScrollbarExistence() | |
| 991 { | |
| 992 bool needsVerticalScrollbar = contentsSize().height() > visibleHeight(); | |
| 993 if (!!m_verticalScrollbar != needsVerticalScrollbar) { | |
| 994 setHasVerticalScrollbar(needsVerticalScrollbar); | |
| 995 contentsResized(); | |
| 996 } | |
| 997 } | |
| 998 | |
| 999 void PopupListBox::updateScrollbarGeometry() | |
| 1000 { | |
| 1001 if (m_verticalScrollbar) { | |
| 1002 int clientHeight = visibleHeight(); | |
| 1003 IntRect oldRect(m_verticalScrollbar->frameRect()); | |
| 1004 IntRect vBarRect(shouldPlaceVerticalScrollbarOnLeft() ? 0 : (width() - m
_verticalScrollbar->width()), | |
| 1005 0, m_verticalScrollbar->width(), height()); | |
| 1006 m_verticalScrollbar->setFrameRect(vBarRect); | |
| 1007 if (oldRect != m_verticalScrollbar->frameRect()) | |
| 1008 m_verticalScrollbar->invalidate(); | |
| 1009 | |
| 1010 m_verticalScrollbar->setEnabled(contentsSize().height() > clientHeight); | |
| 1011 m_verticalScrollbar->setProportion(clientHeight, contentsSize().height()
); | |
| 1012 m_verticalScrollbar->offsetDidChange(); | |
| 1013 // NOTE: PopupListBox does not support suppressing scrollbars. | |
| 1014 } | |
| 1015 } | |
| 1016 | |
| 1017 IntPoint PopupListBox::convertChildToSelf(const Widget* child, const IntPoint& p
oint) const | |
| 1018 { | |
| 1019 // NOTE: m_verticalScrollbar is the only child. | |
| 1020 IntPoint newPoint = point; | |
| 1021 newPoint.moveBy(child->location()); | |
| 1022 return newPoint; | |
| 1023 } | |
| 1024 | |
| 1025 IntPoint PopupListBox::convertSelfToChild(const Widget* child, const IntPoint& p
oint) const | |
| 1026 { | |
| 1027 // NOTE: m_verticalScrollbar is the only child. | |
| 1028 IntPoint newPoint = point; | |
| 1029 newPoint.moveBy(-child->location()); | |
| 1030 return newPoint; | |
| 1031 } | |
| 1032 | |
| 1033 int PopupListBox::scrollSize(ScrollbarOrientation orientation) const | |
| 1034 { | |
| 1035 return (orientation == HorizontalScrollbar || !m_verticalScrollbar) ? | |
| 1036 0 : m_verticalScrollbar->totalSize() - m_verticalScrollbar->visibleSize(
); | |
| 1037 } | |
| 1038 | |
| 1039 void PopupListBox::setScrollOffset(const IntPoint& newOffset) | |
| 1040 { | |
| 1041 // NOTE: We do not support any "fast path" for scrolling. When the scroll | |
| 1042 // offset changes, we just repaint the whole popup. | |
| 1043 IntSize scrollDelta = newOffset - m_scrollOffset; | |
| 1044 if (scrollDelta == IntSize()) | |
| 1045 return; | |
| 1046 m_scrollOffset = newOffset; | |
| 1047 | |
| 1048 if (HostWindow* window = hostWindow()) { | |
| 1049 IntRect clipRect = windowClipRect(); | |
| 1050 IntRect updateRect = clipRect; | |
| 1051 updateRect.intersect(convertToContainingWindow(IntRect((shouldPlaceVerti
calScrollbarOnLeft() && verticalScrollbar()) ? verticalScrollbar()->width() : 0,
0, visibleWidth(), visibleHeight()))); | |
| 1052 window->invalidateContentsForSlowScroll(updateRect); | |
| 1053 } | |
| 1054 } | |
| 1055 | |
| 1056 IntPoint PopupListBox::maximumScrollPosition() const | |
| 1057 { | |
| 1058 IntPoint maximumOffset(contentsSize().width() - visibleWidth() - scrollOrigi
n().x(), contentsSize().height() - visibleHeight() - scrollOrigin().y()); | |
| 1059 maximumOffset.clampNegativeToZero(); | |
| 1060 return maximumOffset; | |
| 1061 } | |
| 1062 | |
| 1063 IntPoint PopupListBox::minimumScrollPosition() const | |
| 1064 { | |
| 1065 return IntPoint(-scrollOrigin().x(), -scrollOrigin().y()); | |
| 1066 } | |
| 1067 | |
| 1068 } // namespace blink | |
| OLD | NEW |