1 /* 2 * Copyright (c) 2008, 2009, Google Inc. All rights reserved. 3 * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are 7 * met: 8 * 9 * * Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * * Redistributions in binary form must reproduce the above 12 * copyright notice, this list of conditions and the following disclaimer 13 * in the documentation and/or other materials provided with the 14 * distribution. 15 * * Neither the name of Google Inc. nor the names of its 16 * contributors may be used to endorse or promote products derived from 17 * this software without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 #include "config.h" 33 #include "PopupMenuChromium.h" 34 35 #include "Chrome.h" 36 #include "ChromeClientChromium.h" 37 #include "Font.h" 38 #include "FontSelector.h" 39 #include "FrameView.h" 40 #include "Frame.h" 41 #include "FramelessScrollView.h" 42 #include "FramelessScrollViewClient.h" 43 #include "GraphicsContext.h" 44 #include "IntRect.h" 45 #include "KeyboardCodes.h" 46 #include "Page.h" 47 #include "PlatformKeyboardEvent.h" 48 #include "PlatformMouseEvent.h" 49 #include "PlatformScreen.h" 50 #include "PlatformWheelEvent.h" 51 #include "PopupMenuClient.h" 52 #include "RenderTheme.h" 53 #include "ScrollbarTheme.h" 54 #include "StringTruncator.h" 55 #include "SystemTime.h" 56 #include "TextRun.h" 57 #include "UserGestureIndicator.h" 58 #include <wtf/CurrentTime.h> 59 #include <wtf/unicode/CharacterNames.h> 60 61 using namespace WTF; 62 using namespace Unicode; 63 64 using std::min; 65 using std::max; 66 67 namespace WebCore { 68 69 typedef unsigned long long TimeStamp; 70 71 static const int kMaxVisibleRows = 20; 72 static const int kMaxHeight = 500; 73 static const int kBorderSize = 1; 74 static const int kTextToLabelPadding = 10; 75 static const int kLabelToIconPadding = 5; 76 static const int kMinEndOfLinePadding = 2; 77 static const TimeStamp kTypeAheadTimeoutMs = 1000; 78 79 // The settings used for the drop down menu. 80 // This is the delegate used if none is provided. 81 static const PopupContainerSettings dropDownSettings = { 82 true, // setTextOnIndexChange 83 true, // acceptOnAbandon 84 false, // loopSelectionNavigation 85 false // restrictWidthOfListBox 86 }; 87 88 // This class uses WebCore code to paint and handle events for a drop-down list 89 // box ("combobox" on Windows). 90 class PopupListBox : public FramelessScrollView { 91 public: 92 static PassRefPtr<PopupListBox> create(PopupMenuClient* client, const PopupContainerSettings& settings) 93 { 94 return adoptRef(new PopupListBox(client, settings)); 95 } 96 97 // FramelessScrollView 98 virtual void paint(GraphicsContext*, const IntRect&); 99 virtual bool handleMouseDownEvent(const PlatformMouseEvent&); 100 virtual bool handleMouseMoveEvent(const PlatformMouseEvent&); 101 virtual bool handleMouseReleaseEvent(const PlatformMouseEvent&); 102 virtual bool handleWheelEvent(const PlatformWheelEvent&); 103 virtual bool handleKeyEvent(const PlatformKeyboardEvent&); 104 105 // ScrollView 106 virtual HostWindow* hostWindow() const; 107 108 // PopupListBox methods 109 110 // Hides the popup. 111 void hidePopup(); 112 113 // Updates our internal list to match the client. 114 void updateFromElement(); 115 116 // Frees any allocated resources used in a particular popup session. 117 void clear(); 118 119 // Sets the index of the option that is displayed in the <select> widget in the page 120 void setOriginalIndex(int index); 121 122 // Gets the index of the item that the user is currently moused over or has selected with 123 // the keyboard. This is not the same as the original index, since the user has not yet 124 // accepted this input. 125 int selectedIndex() const { return m_selectedIndex; } 126 127 // Moves selection down/up the given number of items, scrolling if necessary. 128 // Positive is down. The resulting index will be clamped to the range 129 // [0, numItems), and non-option items will be skipped. 130 void adjustSelectedIndex(int delta); 131 132 // Returns the number of items in the list. 133 int numItems() const { return static_cast<int>(m_items.size()); } 134 135 void setBaseWidth(int width) { m_baseWidth = width; } 136 137 // Computes the size of widget and children. 138 void layout(); 139 140 // Returns whether the popup wants to process events for the passed key. 141 bool isInterestedInEventForKey(int keyCode); 142 143 // Gets the height of a row. 144 int getRowHeight(int index); 145 146 void setMaxHeight(int maxHeight) { m_maxHeight = maxHeight; } 147 148 void disconnectClient() { m_popupClient = 0; } 149 150 const Vector<PopupItem*>& items() const { return m_items; } 151 152 private: 153 friend class PopupContainer; 154 friend class RefCounted<PopupListBox>; 155 156 PopupListBox(PopupMenuClient* client, const PopupContainerSettings& settings) 157 : m_settings(settings) 158 , m_originalIndex(0) 159 , m_selectedIndex(0) 160 , m_acceptedIndexOnAbandon(-1) 161 , m_visibleRows(0) 162 , m_baseWidth(0) 163 , m_maxHeight(kMaxHeight) 164 , m_popupClient(client) 165 , m_repeatingChar(0) 166 , m_lastCharTime(0) 167 { 168 setScrollbarModes(ScrollbarAlwaysOff, ScrollbarAlwaysOff); 169 } 170 171 ~PopupListBox() 172 { 173 clear(); 174 } 175 176 // Closes the popup 177 void abandon(); 178 179 // Returns true if the selection can be changed to index. 180 // Disabled items, or labels cannot be selected. 181 bool isSelectableItem(int index); 182 183 // Select an index in the list, scrolling if necessary. 184 void selectIndex(int index); 185 186 // Accepts the selected index as the value to be displayed in the <select> widget on 187 // the web page, and closes the popup. 188 void acceptIndex(int index); 189 190 // Clears the selection (so no row appears selected). 191 void clearSelection(); 192 193 // Scrolls to reveal the given index. 194 void scrollToRevealRow(int index); 195 void scrollToRevealSelection() { scrollToRevealRow(m_selectedIndex); } 196 197 // Invalidates the row at the given index. 198 void invalidateRow(int index); 199 200 // Get the bounds of a row. 201 IntRect getRowBounds(int index); 202 203 // Converts a point to an index of the row the point is over 204 int pointToRowIndex(const IntPoint&); 205 206 // Paint an individual row 207 void paintRow(GraphicsContext*, const IntRect&, int rowIndex); 208 209 // Test if the given point is within the bounds of the popup window. 210 bool isPointInBounds(const IntPoint&); 211 212 // Called when the user presses a text key. Does a prefix-search of the items. 213 void typeAheadFind(const PlatformKeyboardEvent&); 214 215 // Returns the font to use for the given row 216 Font getRowFont(int index); 217 218 // Moves the selection down/up one item, taking care of looping back to the 219 // first/last element if m_loopSelectionNavigation is true. 220 void selectPreviousRow(); 221 void selectNextRow(); 222 223 // The settings that specify the behavior for this Popup window. 224 PopupContainerSettings m_settings; 225 226 // This is the index of the item marked as "selected" - i.e. displayed in the widget on the 227 // page. 228 int m_originalIndex; 229 230 // This is the index of the item that the user is hovered over or has selected using the 231 // keyboard in the list. They have not confirmed this selection by clicking or pressing 232 // enter yet however. 233 int m_selectedIndex; 234 235 // If >= 0, this is the index we should accept if the popup is "abandoned". 236 // This is used for keyboard navigation, where we want the 237 // selection to change immediately, and is only used if the settings 238 // acceptOnAbandon field is true. 239 int m_acceptedIndexOnAbandon; 240 241 // This is the number of rows visible in the popup. The maximum number visible at a time is 242 // defined as being kMaxVisibleRows. For a scrolled popup, this can be thought of as the 243 // page size in data units. 244 int m_visibleRows; 245 246 // Our suggested width, not including scrollbar. 247 int m_baseWidth; 248 249 // The maximum height we can be without being off-screen. 250 int m_maxHeight; 251 252 // A list of the options contained within the <select> 253 Vector<PopupItem*> m_items; 254 255 // The <select> PopupMenuClient that opened us. 256 PopupMenuClient* m_popupClient; 257 258 // The scrollbar which has mouse capture. Mouse events go straight to this 259 // if non-NULL. 260 RefPtr<Scrollbar> m_capturingScrollbar; 261 262 // The last scrollbar that the mouse was over. Used for mouseover highlights. 263 RefPtr<Scrollbar> m_lastScrollbarUnderMouse; 264 265 // The string the user has typed so far into the popup. Used for typeAheadFind. 266 String m_typedString; 267 268 // The char the user has hit repeatedly. Used for typeAheadFind. 269 UChar m_repeatingChar; 270 271 // The last time the user hit a key. Used for typeAheadFind. 272 TimeStamp m_lastCharTime; 273 }; 274 275 static PlatformMouseEvent constructRelativeMouseEvent(const PlatformMouseEvent& e, 276 FramelessScrollView* parent, 277 FramelessScrollView* child) 278 { 279 IntPoint pos = parent->convertSelfToChild(child, e.pos()); 280 281 // FIXME: This is a horrible hack since PlatformMouseEvent has no setters for x/y. 282 PlatformMouseEvent relativeEvent = e; 283 IntPoint& relativePos = const_cast<IntPoint&>(relativeEvent.pos()); 284 relativePos.setX(pos.x()); 285 relativePos.setY(pos.y()); 286 return relativeEvent; 287 } 288 289 static PlatformWheelEvent constructRelativeWheelEvent(const PlatformWheelEvent& e, 290 FramelessScrollView* parent, 291 FramelessScrollView* child) 292 { 293 IntPoint pos = parent->convertSelfToChild(child, e.pos()); 294 295 // FIXME: This is a horrible hack since PlatformWheelEvent has no setters for x/y. 296 PlatformWheelEvent relativeEvent = e; 297 IntPoint& relativePos = const_cast<IntPoint&>(relativeEvent.pos()); 298 relativePos.setX(pos.x()); 299 relativePos.setY(pos.y()); 300 return relativeEvent; 301 } 302 303 /////////////////////////////////////////////////////////////////////////////// 304 // PopupContainer implementation 305 306 // static 307 PassRefPtr<PopupContainer> PopupContainer::create(PopupMenuClient* client, 308 PopupType popupType, 309 const PopupContainerSettings& settings) 310 { 311 return adoptRef(new PopupContainer(client, popupType, settings)); 312 } 313 314 PopupContainer::PopupContainer(PopupMenuClient* client, 315 PopupType popupType, 316 const PopupContainerSettings& settings) 317 : m_listBox(PopupListBox::create(client, settings)) 318 , m_settings(settings) 319 , m_popupType(popupType) 320 , m_popupOpen(false) 321 { 322 setScrollbarModes(ScrollbarAlwaysOff, ScrollbarAlwaysOff); 323 } 324 325 PopupContainer::~PopupContainer() 326 { 327 if (m_listBox && m_listBox->parent()) 328 removeChild(m_listBox.get()); 329 } 330 331 IntRect PopupContainer::layoutAndCalculateWidgetRect(int targetControlHeight, const IntPoint& popupInitialCoordinate) 332 { 333 // Reset the max height to its default value, it will be recomputed below 334 // if necessary. 335 m_listBox->setMaxHeight(kMaxHeight); 336 337 // Lay everything out to figure out our preferred size, then tell the view's 338 // WidgetClient about it. It should assign us a client. 339 int rightOffset = layoutAndGetRightOffset(); 340 341 // Assume m_listBox size is already calculated. 342 IntSize targetSize(m_listBox->width() + kBorderSize * 2, 343 m_listBox->height() + kBorderSize * 2); 344 345 IntRect widgetRect; 346 ChromeClientChromium* chromeClient = chromeClientChromium(); 347 if (chromeClient) { 348 // If the popup would extend past the bottom of the screen, open upwards 349 // instead. 350 FloatRect screen = screenAvailableRect(m_frameView.get()); 351 // Use popupInitialCoordinate.x() + rightOffset because RTL position 352 // needs to be considered. 353 widgetRect = chromeClient->windowToScreen(IntRect(popupInitialCoordinate.x() + rightOffset, popupInitialCoordinate.y(), targetSize.width(), targetSize.height())); 354 355 // If we have multiple screens and the browser rect is in one screen, we have 356 // to clip the window width to the screen width. 357 FloatRect windowRect = chromeClient->windowRect(); 358 if (windowRect.x() >= screen.x() && windowRect.maxX() <= screen.maxX()) { 359 if (m_listBox->m_popupClient->menuStyle().textDirection() == RTL && widgetRect.x() < screen.x()) { 360 widgetRect.setWidth(widgetRect.maxX() - screen.x()); 361 widgetRect.setX(screen.x()); 362 } else if (widgetRect.maxX() > screen.maxX()) 363 widgetRect.setWidth(screen.maxX() - widgetRect.x()); 364 } 365 366 // Calculate Y axis size. 367 if (widgetRect.maxY() > static_cast<int>(screen.maxY())) { 368 if (widgetRect.y() - widgetRect.height() - targetControlHeight > 0) { 369 // There is enough room to open upwards. 370 widgetRect.move(0, -(widgetRect.height() + targetControlHeight)); 371 } else { 372 // Figure whether upwards or downwards has more room and set the 373 // maximum number of items. 374 int spaceAbove = widgetRect.y() - targetControlHeight; 375 int spaceBelow = screen.maxY() - widgetRect.y(); 376 if (spaceAbove > spaceBelow) 377 m_listBox->setMaxHeight(spaceAbove); 378 else 379 m_listBox->setMaxHeight(spaceBelow); 380 layoutAndGetRightOffset(); 381 // Our height has changed, so recompute only Y axis of widgetRect. 382 // We don't have to recompute X axis, so we only replace Y axis 383 // in widgetRect. 384 IntRect frameInScreen = chromeClient->windowToScreen(frameRect()); 385 widgetRect.setY(frameInScreen.y()); 386 widgetRect.setHeight(frameInScreen.height()); 387 // And move upwards if necessary. 388 if (spaceAbove > spaceBelow) 389 widgetRect.move(0, -(widgetRect.height() + targetControlHeight)); 390 } 391 } 392 } 393 return widgetRect; 394 } 395 396 void PopupContainer::showPopup(FrameView* view) 397 { 398 m_frameView = view; 399 400 ChromeClientChromium* chromeClient = chromeClientChromium(); 401 if (chromeClient) { 402 IntRect popupRect = frameRect(); 403 chromeClient->popupOpened(this, layoutAndCalculateWidgetRect(popupRect.height(), popupRect.location()), false); 404 m_popupOpen = true; 405 } 406 407 if (!m_listBox->parent()) 408 addChild(m_listBox.get()); 409 410 // Enable scrollbars after the listbox is inserted into the hierarchy, 411 // so it has a proper WidgetClient. 412 m_listBox->setVerticalScrollbarMode(ScrollbarAuto); 413 414 m_listBox->scrollToRevealSelection(); 415 416 invalidate(); 417 } 418 419 void PopupContainer::hidePopup() 420 { 421 listBox()->hidePopup(); 422 } 423 424 void PopupContainer::notifyPopupHidden() 425 { 426 if (!m_popupOpen) 427 return; 428 m_popupOpen = false; 429 chromeClientChromium()->popupClosed(this); 430 } 431 432 int PopupContainer::layoutAndGetRightOffset() 433 { 434 m_listBox->layout(); 435 436 // Place the listbox within our border. 437 m_listBox->move(kBorderSize, kBorderSize); 438 439 // popupWidth is the width of <select> element. Record it before resize frame. 440 int popupWidth = frameRect().width(); 441 // Size ourselves to contain listbox + border. 442 int listBoxWidth = m_listBox->width() + kBorderSize * 2; 443 resize(listBoxWidth, m_listBox->height() + kBorderSize * 2); 444 445 // Adjust the starting x-axis for RTL dropdown. For RTL dropdown, the right edge 446 // of dropdown box should be aligned with the right edge of <select> element box, 447 // and the dropdown box should be expanded to left if more space needed. 448 PopupMenuClient* popupClient = m_listBox->m_popupClient; 449 int rightOffset = 0; 450 if (popupClient) { 451 bool rightAligned = m_listBox->m_popupClient->menuStyle().textDirection() == RTL; 452 if (rightAligned) 453 rightOffset = popupWidth - listBoxWidth; 454 } 455 invalidate(); 456 457 return rightOffset; 458 } 459 460 bool PopupContainer::handleMouseDownEvent(const PlatformMouseEvent& event) 461 { 462 UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture); 463 return m_listBox->handleMouseDownEvent( 464 constructRelativeMouseEvent(event, this, m_listBox.get())); 465 } 466 467 bool PopupContainer::handleMouseMoveEvent(const PlatformMouseEvent& event) 468 { 469 UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture); 470 return m_listBox->handleMouseMoveEvent( 471 constructRelativeMouseEvent(event, this, m_listBox.get())); 472 } 473 474 bool PopupContainer::handleMouseReleaseEvent(const PlatformMouseEvent& event) 475 { 476 UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture); 477 return m_listBox->handleMouseReleaseEvent( 478 constructRelativeMouseEvent(event, this, m_listBox.get())); 479 } 480 481 bool PopupContainer::handleWheelEvent(const PlatformWheelEvent& event) 482 { 483 UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture); 484 return m_listBox->handleWheelEvent( 485 constructRelativeWheelEvent(event, this, m_listBox.get())); 486 } 487 488 bool PopupContainer::handleKeyEvent(const PlatformKeyboardEvent& event) 489 { 490 UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture); 491 return m_listBox->handleKeyEvent(event); 492 } 493 494 void PopupContainer::hide() 495 { 496 m_listBox->abandon(); 497 } 498 499 void PopupContainer::paint(GraphicsContext* gc, const IntRect& rect) 500 { 501 // adjust coords for scrolled frame 502 IntRect r = intersection(rect, frameRect()); 503 int tx = x(); 504 int ty = y(); 505 506 r.move(-tx, -ty); 507 508 gc->translate(static_cast<float>(tx), static_cast<float>(ty)); 509 m_listBox->paint(gc, r); 510 gc->translate(-static_cast<float>(tx), -static_cast<float>(ty)); 511 512 paintBorder(gc, rect); 513 } 514 515 void PopupContainer::paintBorder(GraphicsContext* gc, const IntRect& rect) 516 { 517 // FIXME: Where do we get the border color from? 518 Color borderColor(127, 157, 185); 519 520 gc->setStrokeStyle(NoStroke); 521 gc->setFillColor(borderColor, ColorSpaceDeviceRGB); 522 523 int tx = x(); 524 int ty = y(); 525 526 // top, left, bottom, right 527 gc->drawRect(IntRect(tx, ty, width(), kBorderSize)); 528 gc->drawRect(IntRect(tx, ty, kBorderSize, height())); 529 gc->drawRect(IntRect(tx, ty + height() - kBorderSize, width(), kBorderSize)); 530 gc->drawRect(IntRect(tx + width() - kBorderSize, ty, kBorderSize, height())); 531 } 532 533 bool PopupContainer::isInterestedInEventForKey(int keyCode) 534 { 535 return m_listBox->isInterestedInEventForKey(keyCode); 536 } 537 538 ChromeClientChromium* PopupContainer::chromeClientChromium() 539 { 540 return static_cast<ChromeClientChromium*>(m_frameView->frame()->page()->chrome()->client()); 541 } 542 543 void PopupContainer::showInRect(const IntRect& r, FrameView* v, int index) 544 { 545 // The rect is the size of the select box. It's usually larger than we need. 546 // subtract border size so that usually the container will be displayed 547 // exactly the same width as the select box. 548 listBox()->setBaseWidth(max(r.width() - kBorderSize * 2, 0)); 549 550 listBox()->updateFromElement(); 551 552 // We set the selected item in updateFromElement(), and disregard the 553 // index passed into this function (same as Webkit's PopupMenuWin.cpp) 554 // FIXME: make sure this is correct, and add an assertion. 555 // ASSERT(popupWindow(popup)->listBox()->selectedIndex() == index); 556 557 // Convert point to main window coords. 558 IntPoint location = v->contentsToWindow(r.location()); 559 560 // Move it below the select widget. 561 location.move(0, r.height()); 562 563 setFrameRect(IntRect(location, r.size())); 564 showPopup(v); 565 } 566 567 void PopupContainer::refresh(const IntRect& targetControlRect) 568 { 569 IntPoint location = m_frameView->contentsToWindow(targetControlRect.location()); 570 // Move it below the select widget. 571 location.move(0, targetControlRect.height()); 572 573 listBox()->updateFromElement(); 574 // Store the original size to check if we need to request the location. 575 IntSize originalSize = size(); 576 IntRect widgetRect = layoutAndCalculateWidgetRect(targetControlRect.height(), location); 577 if (originalSize != widgetRect.size()) 578 setFrameRect(widgetRect); 579 580 invalidate(); 581 } 582 583 int PopupContainer::selectedIndex() const 584 { 585 return m_listBox->selectedIndex(); 586 } 587 588 int PopupContainer::menuItemHeight() const 589 { 590 return m_listBox->getRowHeight(0); 591 } 592 593 int PopupContainer::menuItemFontSize() const 594 { 595 return m_listBox->getRowFont(0).size(); 596 } 597 598 PopupMenuStyle PopupContainer::menuStyle() const 599 { 600 return m_listBox->m_popupClient->menuStyle(); 601 } 602 603 const WTF::Vector<PopupItem*>& PopupContainer:: popupData() const 604 { 605 return m_listBox->items(); 606 } 607 608 /////////////////////////////////////////////////////////////////////////////// 609 // PopupListBox implementation 610 611 bool PopupListBox::handleMouseDownEvent(const PlatformMouseEvent& event) 612 { 613 Scrollbar* scrollbar = scrollbarAtPoint(event.pos()); 614 if (scrollbar) { 615 m_capturingScrollbar = scrollbar; 616 m_capturingScrollbar->mouseDown(event); 617 return true; 618 } 619 620 if (!isPointInBounds(event.pos())) 621 abandon(); 622 623 return true; 624 } 625 626 bool PopupListBox::handleMouseMoveEvent(const PlatformMouseEvent& event) 627 { 628 if (m_capturingScrollbar) { 629 m_capturingScrollbar->mouseMoved(event); 630 return true; 631 } 632 633 Scrollbar* scrollbar = scrollbarAtPoint(event.pos()); 634 if (m_lastScrollbarUnderMouse != scrollbar) { 635 // Send mouse exited to the old scrollbar. 636 if (m_lastScrollbarUnderMouse) 637 m_lastScrollbarUnderMouse->mouseExited(); 638 m_lastScrollbarUnderMouse = scrollbar; 639 } 640 641 if (scrollbar) { 642 scrollbar->mouseMoved(event); 643 return true; 644 } 645 646 if (!isPointInBounds(event.pos())) 647 return false; 648 649 selectIndex(pointToRowIndex(event.pos())); 650 return true; 651 } 652 653 bool PopupListBox::handleMouseReleaseEvent(const PlatformMouseEvent& event) 654 { 655 if (m_capturingScrollbar) { 656 m_capturingScrollbar->mouseUp(); 657 m_capturingScrollbar = 0; 658 return true; 659 } 660 661 if (!isPointInBounds(event.pos())) 662 return true; 663 664 acceptIndex(pointToRowIndex(event.pos())); 665 return true; 666 } 667 668 bool PopupListBox::handleWheelEvent(const PlatformWheelEvent& event) 669 { 670 if (!isPointInBounds(event.pos())) { 671 abandon(); 672 return true; 673 } 674 675 // Pass it off to the scroll view. 676 // Sadly, WebCore devs don't understand the whole "const" thing. 677 wheelEvent(const_cast<PlatformWheelEvent&>(event)); 678 return true; 679 } 680 681 // Should be kept in sync with handleKeyEvent(). 682 bool PopupListBox::isInterestedInEventForKey(int keyCode) 683 { 684 switch (keyCode) { 685 case VKEY_ESCAPE: 686 case VKEY_RETURN: 687 case VKEY_UP: 688 case VKEY_DOWN: 689 case VKEY_PRIOR: 690 case VKEY_NEXT: 691 case VKEY_HOME: 692 case VKEY_END: 693 case VKEY_TAB: 694 return true; 695 default: 696 return false; 697 } 698 } 699 700 static bool isCharacterTypeEvent(const PlatformKeyboardEvent& event) 701 { 702 // Check whether the event is a character-typed event or not. 703 // We use RawKeyDown/Char/KeyUp event scheme on all platforms, 704 // so PlatformKeyboardEvent::Char (not RawKeyDown) type event 705 // is considered as character type event. 706 return event.type() == PlatformKeyboardEvent::Char; 707 } 708 709 bool PopupListBox::handleKeyEvent(const PlatformKeyboardEvent& event) 710 { 711 if (event.type() == PlatformKeyboardEvent::KeyUp) 712 return true; 713 714 if (numItems() == 0 && event.windowsVirtualKeyCode() != VKEY_ESCAPE) 715 return true; 716 717 switch (event.windowsVirtualKeyCode()) { 718 case VKEY_ESCAPE: 719 abandon(); // may delete this 720 return true; 721 case VKEY_RETURN: 722 if (m_selectedIndex == -1) { 723 hidePopup(); 724 // Don't eat the enter if nothing is selected. 725 return false; 726 } 727 acceptIndex(m_selectedIndex); // may delete this 728 return true; 729 case VKEY_UP: 730 selectPreviousRow(); 731 break; 732 case VKEY_DOWN: 733 selectNextRow(); 734 break; 735 case VKEY_PRIOR: 736 adjustSelectedIndex(-m_visibleRows); 737 break; 738 case VKEY_NEXT: 739 adjustSelectedIndex(m_visibleRows); 740 break; 741 case VKEY_HOME: 742 adjustSelectedIndex(-m_selectedIndex); 743 break; 744 case VKEY_END: 745 adjustSelectedIndex(m_items.size()); 746 break; 747 default: 748 if (!event.ctrlKey() && !event.altKey() && !event.metaKey() 749 && isPrintableChar(event.windowsVirtualKeyCode()) 750 && isCharacterTypeEvent(event)) 751 typeAheadFind(event); 752 break; 753 } 754 755 if (m_originalIndex != m_selectedIndex) { 756 // Keyboard events should update the selection immediately (but we don't 757 // want to fire the onchange event until the popup is closed, to match 758 // IE). We change the original index so we revert to that when the 759 // popup is closed. 760 if (m_settings.acceptOnAbandon) 761 m_acceptedIndexOnAbandon = m_selectedIndex; 762 763 setOriginalIndex(m_selectedIndex); 764 if (m_settings.setTextOnIndexChange) 765 m_popupClient->setTextFromItem(m_selectedIndex); 766 } 767 if (event.windowsVirtualKeyCode() == VKEY_TAB) { 768 // TAB is a special case as it should select the current item if any and 769 // advance focus. 770 if (m_selectedIndex >= 0) { 771 acceptIndex(m_selectedIndex); // May delete us. 772 // Return false so the TAB key event is propagated to the page. 773 return false; 774 } 775 // Call abandon() so we honor m_acceptedIndexOnAbandon if set. 776 abandon(); 777 // Return false so the TAB key event is propagated to the page. 778 return false; 779 } 780 781 return true; 782 } 783 784 HostWindow* PopupListBox::hostWindow() const 785 { 786 // Our parent is the root ScrollView, so it is the one that has a 787 // HostWindow. FrameView::hostWindow() works similarly. 788 return parent() ? parent()->hostWindow() : 0; 789 } 790 791 // From HTMLSelectElement.cpp 792 static String stripLeadingWhiteSpace(const String& string) 793 { 794 int length = string.length(); 795 int i; 796 for (i = 0; i < length; ++i) 797 if (string[i] != noBreakSpace 798 && (string[i] <= 0x7F ? !isspace(string[i]) : (direction(string[i]) != WhiteSpaceNeutral))) 799 break; 800 801 return string.substring(i, length - i); 802 } 803 804 // From HTMLSelectElement.cpp, with modifications 805 void PopupListBox::typeAheadFind(const PlatformKeyboardEvent& event) 806 { 807 TimeStamp now = static_cast<TimeStamp>(currentTime() * 1000.0f); 808 TimeStamp delta = now - m_lastCharTime; 809 810 // Reset the time when user types in a character. The time gap between 811 // last character and the current character is used to indicate whether 812 // user typed in a string or just a character as the search prefix. 813 m_lastCharTime = now; 814 815 UChar c = event.windowsVirtualKeyCode(); 816 817 String prefix; 818 int searchStartOffset = 1; 819 if (delta > kTypeAheadTimeoutMs) { 820 m_typedString = prefix = String(&c, 1); 821 m_repeatingChar = c; 822 } else { 823 m_typedString.append(c); 824 825 if (c == m_repeatingChar) 826 // The user is likely trying to cycle through all the items starting with this character, so just search on the character 827 prefix = String(&c, 1); 828 else { 829 m_repeatingChar = 0; 830 prefix = m_typedString; 831 searchStartOffset = 0; 832 } 833 } 834 835 // Compute a case-folded copy of the prefix string before beginning the search for 836 // a matching element. This code uses foldCase to work around the fact that 837 // String::startWith does not fold non-ASCII characters. This code can be changed 838 // to use startWith once that is fixed. 839 String prefixWithCaseFolded(prefix.foldCase()); 840 int itemCount = numItems(); 841 int index = (max(0, m_selectedIndex) + searchStartOffset) % itemCount; 842 for (int i = 0; i < itemCount; i++, index = (index + 1) % itemCount) { 843 if (!isSelectableItem(index)) 844 continue; 845 846 if (stripLeadingWhiteSpace(m_items[index]->label).foldCase().startsWith(prefixWithCaseFolded)) { 847 selectIndex(index); 848 return; 849 } 850 } 851 } 852 853 void PopupListBox::paint(GraphicsContext* gc, const IntRect& rect) 854 { 855 // adjust coords for scrolled frame 856 IntRect r = intersection(rect, frameRect()); 857 int tx = x() - scrollX(); 858 int ty = y() - scrollY(); 859 860 r.move(-tx, -ty); 861 862 // set clip rect to match revised damage rect 863 gc->save(); 864 gc->translate(static_cast<float>(tx), static_cast<float>(ty)); 865 gc->clip(r); 866 867 // FIXME: Can we optimize scrolling to not require repainting the entire 868 // window? Should we? 869 for (int i = 0; i < numItems(); ++i) 870 paintRow(gc, r, i); 871 872 // Special case for an empty popup. 873 if (numItems() == 0) 874 gc->fillRect(r, Color::white, ColorSpaceDeviceRGB); 875 876 gc->restore(); 877 878 ScrollView::paint(gc, rect); 879 } 880 881 static const int separatorPadding = 4; 882 static const int separatorHeight = 1; 883 884 void PopupListBox::paintRow(GraphicsContext* gc, const IntRect& rect, int rowIndex) 885 { 886 // This code is based largely on RenderListBox::paint* methods. 887 888 IntRect rowRect = getRowBounds(rowIndex); 889 if (!rowRect.intersects(rect)) 890 return; 891 892 PopupMenuStyle style = m_popupClient->itemStyle(rowIndex); 893 894 // Paint background 895 Color backColor, textColor, labelColor; 896 if (rowIndex == m_selectedIndex) { 897 backColor = RenderTheme::defaultTheme()->activeListBoxSelectionBackgroundColor(); 898 textColor = RenderTheme::defaultTheme()->activeListBoxSelectionForegroundColor(); 899 labelColor = textColor; 900 } else { 901 backColor = style.backgroundColor(); 902 textColor = style.foregroundColor(); 903 // FIXME: for now the label color is hard-coded. It should be added to 904 // the PopupMenuStyle. 905 labelColor = Color(115, 115, 115); 906 } 907 908 // If we have a transparent background, make sure it has a color to blend 909 // against. 910 if (backColor.hasAlpha()) 911 gc->fillRect(rowRect, Color::white, ColorSpaceDeviceRGB); 912 913 gc->fillRect(rowRect, backColor, ColorSpaceDeviceRGB); 914 915 if (m_popupClient->itemIsSeparator(rowIndex)) { 916 IntRect separatorRect( 917 rowRect.x() + separatorPadding, 918 rowRect.y() + (rowRect.height() - separatorHeight) / 2, 919 rowRect.width() - 2 * separatorPadding, separatorHeight); 920 gc->fillRect(separatorRect, textColor, ColorSpaceDeviceRGB); 921 return; 922 } 923 924 if (!style.isVisible()) 925 return; 926 927 gc->setFillColor(textColor, ColorSpaceDeviceRGB); 928 929 Font itemFont = getRowFont(rowIndex); 930 // FIXME: http://crbug.com/19872 We should get the padding of individual option 931 // elements. This probably implies changes to PopupMenuClient. 932 bool rightAligned = m_popupClient->menuStyle().textDirection() == RTL; 933 int textX = 0; 934 int maxWidth = 0; 935 if (rightAligned) 936 maxWidth = rowRect.width() - max(0, m_popupClient->clientPaddingRight() - m_popupClient->clientInsetRight()); 937 else { 938 textX = max(0, m_popupClient->clientPaddingLeft() - m_popupClient->clientInsetLeft()); 939 maxWidth = rowRect.width() - textX; 940 } 941 // Prepare text to be drawn. 942 String itemText = m_popupClient->itemText(rowIndex); 943 String itemLabel = m_popupClient->itemLabel(rowIndex); 944 String itemIcon = m_popupClient->itemIcon(rowIndex); 945 if (m_settings.restrictWidthOfListBox) { // Truncate strings to fit in. 946 // FIXME: We should leftTruncate for the rtl case. 947 // StringTruncator::leftTruncate would have to be implemented. 948 String str = StringTruncator::rightTruncate(itemText, maxWidth, itemFont); 949 if (str != itemText) { 950 itemText = str; 951 // Don't display the label or icon, we already don't have enough room for the item text. 952 itemLabel = ""; 953 itemIcon = ""; 954 } else if (!itemLabel.isEmpty()) { 955 int availableWidth = maxWidth - kTextToLabelPadding - 956 StringTruncator::width(itemText, itemFont); 957 itemLabel = StringTruncator::rightTruncate(itemLabel, availableWidth, itemFont); 958 } 959 } 960 961 // Prepare the directionality to draw text. 962 bool rtl = style.textDirection() == RTL; 963 TextRun textRun(itemText.characters(), itemText.length(), false, 0, 0, TextRun::AllowTrailingExpansion, rtl, style.hasTextDirectionOverride()); 964 // If the text is right-to-left, make it right-aligned by adjusting its 965 // beginning position. 966 if (rightAligned) 967 textX += maxWidth - itemFont.width(textRun); 968 969 // Draw the item text. 970 int textY = rowRect.y() + itemFont.fontMetrics().ascent() + (rowRect.height() - itemFont.fontMetrics().height()) / 2; 971 gc->drawBidiText(itemFont, textRun, IntPoint(textX, textY)); 972 973 // We are using the left padding as the right padding includes room for the scroll-bar which 974 // does not show in this case. 975 int rightPadding = max(0, m_popupClient->clientPaddingLeft() - m_popupClient->clientInsetLeft()); 976 int remainingWidth = rowRect.width() - rightPadding; 977 978 // Draw the icon if applicable. 979 RefPtr<Image> image(Image::loadPlatformResource(itemIcon.utf8().data())); 980 if (image && !image->isNull()) { 981 IntRect imageRect = image->rect(); 982 remainingWidth -= (imageRect.width() + kLabelToIconPadding); 983 imageRect.setX(rowRect.width() - rightPadding - imageRect.width()); 984 imageRect.setY(rowRect.y() + (rowRect.height() - imageRect.height()) / 2); 985 gc->drawImage(image.get(), ColorSpaceDeviceRGB, imageRect); 986 } 987 988 // Draw the the label if applicable. 989 if (itemLabel.isEmpty()) 990 return; 991 TextRun labelTextRun(itemLabel.characters(), itemLabel.length(), false, 0, 0, TextRun::AllowTrailingExpansion, rtl, style.hasTextDirectionOverride()); 992 if (rightAligned) 993 textX = max(0, m_popupClient->clientPaddingLeft() - m_popupClient->clientInsetLeft()); 994 else 995 textX = remainingWidth - itemFont.width(labelTextRun); 996 997 gc->setFillColor(labelColor, ColorSpaceDeviceRGB); 998 gc->drawBidiText(itemFont, labelTextRun, IntPoint(textX, textY)); 999 } 1000 1001 Font PopupListBox::getRowFont(int rowIndex) 1002 { 1003 Font itemFont = m_popupClient->itemStyle(rowIndex).font(); 1004 if (m_popupClient->itemIsLabel(rowIndex)) { 1005 // Bold-ify labels (ie, an <optgroup> heading). 1006 FontDescription d = itemFont.fontDescription(); 1007 d.setWeight(FontWeightBold); 1008 Font font(d, itemFont.letterSpacing(), itemFont.wordSpacing()); 1009 font.update(0); 1010 return font; 1011 } 1012 1013 return itemFont; 1014 } 1015 1016 void PopupListBox::abandon() 1017 { 1018 RefPtr<PopupListBox> keepAlive(this); 1019 1020 m_selectedIndex = m_originalIndex; 1021 1022 hidePopup(); 1023 1024 if (m_acceptedIndexOnAbandon >= 0) { 1025 if (m_popupClient) 1026 m_popupClient->valueChanged(m_acceptedIndexOnAbandon); 1027 m_acceptedIndexOnAbandon = -1; 1028 } 1029 } 1030 1031 int PopupListBox::pointToRowIndex(const IntPoint& point) 1032 { 1033 int y = scrollY() + point.y(); 1034 1035 // FIXME: binary search if perf matters. 1036 for (int i = 0; i < numItems(); ++i) { 1037 if (y < m_items[i]->yOffset) 1038 return i-1; 1039 } 1040 1041 // Last item? 1042 if (y < contentsHeight()) 1043 return m_items.size()-1; 1044 1045 return -1; 1046 } 1047 1048 void PopupListBox::acceptIndex(int index) 1049 { 1050 // Clear m_acceptedIndexOnAbandon once user accepts the selected index. 1051 if (m_acceptedIndexOnAbandon >= 0) 1052 m_acceptedIndexOnAbandon = -1; 1053 1054 if (index >= numItems()) 1055 return; 1056 1057 if (index < 0) { 1058 if (m_popupClient) { 1059 // Enter pressed with no selection, just close the popup. 1060 hidePopup(); 1061 } 1062 return; 1063 } 1064 1065 if (isSelectableItem(index)) { 1066 RefPtr<PopupListBox> keepAlive(this); 1067 1068 // Hide ourselves first since valueChanged may have numerous side-effects. 1069 hidePopup(); 1070 1071 // Tell the <select> PopupMenuClient what index was selected. 1072 m_popupClient->valueChanged(index); 1073 } 1074 } 1075 1076 void PopupListBox::selectIndex(int index) 1077 { 1078 if (index < 0 || index >= numItems()) 1079 return; 1080 1081 bool isSelectable = isSelectableItem(index); 1082 if (index != m_selectedIndex && isSelectable) { 1083 invalidateRow(m_selectedIndex); 1084 m_selectedIndex = index; 1085 invalidateRow(m_selectedIndex); 1086 1087 scrollToRevealSelection(); 1088 m_popupClient->selectionChanged(m_selectedIndex); 1089 } else if (!isSelectable) { 1090 clearSelection(); 1091 } 1092 } 1093 1094 void PopupListBox::setOriginalIndex(int index) 1095 { 1096 m_originalIndex = m_selectedIndex = index; 1097 } 1098 1099 int PopupListBox::getRowHeight(int index) 1100 { 1101 if (index < 0) 1102 return 0; 1103 1104 if (m_popupClient->itemStyle(index).isDisplayNone()) 1105 return 0; 1106 1107 String icon = m_popupClient->itemIcon(index); 1108 RefPtr<Image> image(Image::loadPlatformResource(icon.utf8().data())); 1109 1110 int fontHeight = getRowFont(index).fontMetrics().height(); 1111 int iconHeight = (image && !image->isNull()) ? image->rect().height() : 0; 1112 1113 return max(fontHeight, iconHeight); 1114 } 1115 1116 IntRect PopupListBox::getRowBounds(int index) 1117 { 1118 if (index < 0) 1119 return IntRect(0, 0, visibleWidth(), getRowHeight(index)); 1120 1121 return IntRect(0, m_items[index]->yOffset, visibleWidth(), getRowHeight(index)); 1122 } 1123 1124 void PopupListBox::invalidateRow(int index) 1125 { 1126 if (index < 0) 1127 return; 1128 1129 // Invalidate in the window contents, as FramelessScrollView::invalidateRect 1130 // paints in the window coordinates. 1131 invalidateRect(contentsToWindow(getRowBounds(index))); 1132 } 1133 1134 void PopupListBox::scrollToRevealRow(int index) 1135 { 1136 if (index < 0) 1137 return; 1138 1139 IntRect rowRect = getRowBounds(index); 1140 1141 if (rowRect.y() < scrollY()) { 1142 // Row is above current scroll position, scroll up. 1143 ScrollView::setScrollPosition(IntPoint(0, rowRect.y())); 1144 } else if (rowRect.maxY() > scrollY() + visibleHeight()) { 1145 // Row is below current scroll position, scroll down. 1146 ScrollView::setScrollPosition(IntPoint(0, rowRect.maxY() - visibleHeight())); 1147 } 1148 } 1149 1150 bool PopupListBox::isSelectableItem(int index) 1151 { 1152 ASSERT(index >= 0 && index < numItems()); 1153 return m_items[index]->type == PopupItem::TypeOption && m_popupClient->itemIsEnabled(index); 1154 } 1155 1156 void PopupListBox::clearSelection() 1157 { 1158 if (m_selectedIndex != -1) { 1159 invalidateRow(m_selectedIndex); 1160 m_selectedIndex = -1; 1161 m_popupClient->selectionCleared(); 1162 } 1163 } 1164 1165 void PopupListBox::selectNextRow() 1166 { 1167 if (!m_settings.loopSelectionNavigation || m_selectedIndex != numItems() - 1) { 1168 adjustSelectedIndex(1); 1169 return; 1170 } 1171 1172 // We are moving past the last item, no row should be selected. 1173 clearSelection(); 1174 } 1175 1176 void PopupListBox::selectPreviousRow() 1177 { 1178 if (!m_settings.loopSelectionNavigation || m_selectedIndex > 0) { 1179 adjustSelectedIndex(-1); 1180 return; 1181 } 1182 1183 if (m_selectedIndex == 0) { 1184 // We are moving past the first item, clear the selection. 1185 clearSelection(); 1186 return; 1187 } 1188 1189 // No row is selected, jump to the last item. 1190 selectIndex(numItems() - 1); 1191 scrollToRevealSelection(); 1192 } 1193 1194 void PopupListBox::adjustSelectedIndex(int delta) 1195 { 1196 int targetIndex = m_selectedIndex + delta; 1197 targetIndex = min(max(targetIndex, 0), numItems() - 1); 1198 if (!isSelectableItem(targetIndex)) { 1199 // We didn't land on an option. Try to find one. 1200 // We try to select the closest index to target, prioritizing any in 1201 // the range [current, target]. 1202 1203 int dir = delta > 0 ? 1 : -1; 1204 int testIndex = m_selectedIndex; 1205 int bestIndex = m_selectedIndex; 1206 bool passedTarget = false; 1207 while (testIndex >= 0 && testIndex < numItems()) { 1208 if (isSelectableItem(testIndex)) 1209 bestIndex = testIndex; 1210 if (testIndex == targetIndex) 1211 passedTarget = true; 1212 if (passedTarget && bestIndex != m_selectedIndex) 1213 break; 1214 1215 testIndex += dir; 1216 } 1217 1218 // Pick the best index, which may mean we don't change. 1219 targetIndex = bestIndex; 1220 } 1221 1222 // Select the new index, and ensure its visible. We do this regardless of 1223 // whether the selection changed to ensure keyboard events always bring the 1224 // selection into view. 1225 selectIndex(targetIndex); 1226 scrollToRevealSelection(); 1227 } 1228 1229 void PopupListBox::hidePopup() 1230 { 1231 if (parent()) { 1232 PopupContainer* container = static_cast<PopupContainer*>(parent()); 1233 if (container->client()) 1234 container->client()->popupClosed(container); 1235 container->notifyPopupHidden(); 1236 } 1237 1238 if (m_popupClient) 1239 m_popupClient->popupDidHide(); 1240 } 1241 1242 void PopupListBox::updateFromElement() 1243 { 1244 clear(); 1245 1246 int size = m_popupClient->listSize(); 1247 for (int i = 0; i < size; ++i) { 1248 PopupItem::Type type; 1249 if (m_popupClient->itemIsSeparator(i)) 1250 type = PopupItem::TypeSeparator; 1251 else if (m_popupClient->itemIsLabel(i)) 1252 type = PopupItem::TypeGroup; 1253 else 1254 type = PopupItem::TypeOption; 1255 m_items.append(new PopupItem(m_popupClient->itemText(i), type)); 1256 m_items[i]->enabled = isSelectableItem(i); 1257 PopupMenuStyle style = m_popupClient->itemStyle(i); 1258 m_items[i]->textDirection = style.textDirection(); 1259 m_items[i]->hasTextDirectionOverride = style.hasTextDirectionOverride(); 1260 } 1261 1262 m_selectedIndex = m_popupClient->selectedIndex(); 1263 setOriginalIndex(m_selectedIndex); 1264 1265 layout(); 1266 } 1267 1268 void PopupListBox::layout() 1269 { 1270 bool isRightAligned = m_popupClient->menuStyle().textDirection() == RTL; 1271 1272 // Size our child items. 1273 int baseWidth = 0; 1274 int paddingWidth = 0; 1275 int lineEndPaddingWidth = 0; 1276 int y = 0; 1277 for (int i = 0; i < numItems(); ++i) { 1278 // Place the item vertically. 1279 m_items[i]->yOffset = y; 1280 if (m_popupClient->itemStyle(i).isDisplayNone()) 1281 continue; 1282 y += getRowHeight(i); 1283 1284 // Ensure the popup is wide enough to fit this item. 1285 Font itemFont = getRowFont(i); 1286 String text = m_popupClient->itemText(i); 1287 String label = m_popupClient->itemLabel(i); 1288 String icon = m_popupClient->itemIcon(i); 1289 RefPtr<Image> iconImage(Image::loadPlatformResource(icon.utf8().data())); 1290 int width = 0; 1291 if (!text.isEmpty()) 1292 width = itemFont.width(TextRun(text)); 1293 if (!label.isEmpty()) { 1294 if (width > 0) 1295 width += kTextToLabelPadding; 1296 width += itemFont.width(TextRun(label)); 1297 } 1298 if (iconImage && !iconImage->isNull()) { 1299 if (width > 0) 1300 width += kLabelToIconPadding; 1301 width += iconImage->rect().width(); 1302 } 1303 1304 baseWidth = max(baseWidth, width); 1305 // FIXME: http://b/1210481 We should get the padding of individual option elements. 1306 paddingWidth = max(paddingWidth, 1307 m_popupClient->clientPaddingLeft() + m_popupClient->clientPaddingRight()); 1308 lineEndPaddingWidth = max(lineEndPaddingWidth, 1309 isRightAligned ? m_popupClient->clientPaddingLeft() : m_popupClient->clientPaddingRight()); 1310 } 1311 1312 // Calculate scroll bar width. 1313 int windowHeight = 0; 1314 m_visibleRows = min(numItems(), kMaxVisibleRows); 1315 1316 for (int i = 0; i < m_visibleRows; ++i) { 1317 int rowHeight = getRowHeight(i); 1318 1319 // Only clip the window height for non-Mac platforms. 1320 if (windowHeight + rowHeight > m_maxHeight) { 1321 m_visibleRows = i; 1322 break; 1323 } 1324 1325 windowHeight += rowHeight; 1326 } 1327 1328 // Set our widget and scrollable contents sizes. 1329 int scrollbarWidth = 0; 1330 if (m_visibleRows < numItems()) { 1331 scrollbarWidth = ScrollbarTheme::nativeTheme()->scrollbarThickness(); 1332 1333 // Use kMinEndOfLinePadding when there is a scrollbar so that we use 1334 // as much as (lineEndPaddingWidth - kMinEndOfLinePadding) padding 1335 // space for scrollbar and allow user to use CSS padding to make the 1336 // popup listbox align with the select element. 1337 paddingWidth = paddingWidth - lineEndPaddingWidth + kMinEndOfLinePadding; 1338 } 1339 1340 int windowWidth; 1341 int contentWidth; 1342 if (m_settings.restrictWidthOfListBox) { 1343 windowWidth = m_baseWidth; 1344 contentWidth = m_baseWidth - scrollbarWidth; 1345 } else { 1346 windowWidth = baseWidth + scrollbarWidth + paddingWidth; 1347 contentWidth = baseWidth + paddingWidth; 1348 1349 if (windowWidth < m_baseWidth) { 1350 windowWidth = m_baseWidth; 1351 contentWidth = m_baseWidth - scrollbarWidth; 1352 } else 1353 m_baseWidth = baseWidth; 1354 } 1355 1356 resize(windowWidth, windowHeight); 1357 setContentsSize(IntSize(contentWidth, getRowBounds(numItems() - 1).maxY())); 1358 1359 if (hostWindow()) 1360 scrollToRevealSelection(); 1361 1362 invalidate(); 1363 } 1364 1365 void PopupListBox::clear() 1366 { 1367 for (Vector<PopupItem*>::iterator it = m_items.begin(); it != m_items.end(); ++it) 1368 delete *it; 1369 m_items.clear(); 1370 } 1371 1372 bool PopupListBox::isPointInBounds(const IntPoint& point) 1373 { 1374 return numItems() != 0 && IntRect(0, 0, width(), height()).contains(point); 1375 } 1376 1377 /////////////////////////////////////////////////////////////////////////////// 1378 // PopupMenuChromium implementation 1379 // 1380 // Note: you cannot add methods to this class, since it is defined above the 1381 // portability layer. To access methods and properties on the 1382 // popup widgets, use |popupWindow| above. 1383 1384 PopupMenuChromium::PopupMenuChromium(PopupMenuClient* client) 1385 : m_popupClient(client) 1386 { 1387 } 1388 1389 PopupMenuChromium::~PopupMenuChromium() 1390 { 1391 // When the PopupMenuChromium is destroyed, the client could already have been 1392 // deleted. 1393 if (p.popup) 1394 p.popup->listBox()->disconnectClient(); 1395 hide(); 1396 } 1397 1398 void PopupMenuChromium::show(const IntRect& r, FrameView* v, int index) 1399 { 1400 if (!p.popup) 1401 p.popup = PopupContainer::create(client(), PopupContainer::Select, dropDownSettings); 1402 p.popup->showInRect(r, v, index); 1403 } 1404 1405 void PopupMenuChromium::hide() 1406 { 1407 if (p.popup) 1408 p.popup->hide(); 1409 } 1410 1411 void PopupMenuChromium::updateFromElement() 1412 { 1413 p.popup->listBox()->updateFromElement(); 1414 } 1415 1416 1417 void PopupMenuChromium::disconnectClient() 1418 { 1419 m_popupClient = 0; 1420 } 1421 1422 } // namespace WebCore 1423