Index: webkit/port/platform/PopupMenuWin.cpp |
=================================================================== |
--- webkit/port/platform/PopupMenuWin.cpp (revision 3423) |
+++ webkit/port/platform/PopupMenuWin.cpp (working copy) |
@@ -1,1115 +0,0 @@ |
-// Copyright (c) 2008, Google Inc. |
-// All rights reserved. |
-// |
-// Redistribution and use in source and binary forms, with or without |
-// modification, are permitted provided that the following conditions are |
-// met: |
-// |
-// * Redistributions of source code must retain the above copyright |
-// notice, this list of conditions and the following disclaimer. |
-// * Redistributions in binary form must reproduce the above |
-// copyright notice, this list of conditions and the following disclaimer |
-// in the documentation and/or other materials provided with the |
-// distribution. |
-// * Neither the name of Google Inc. nor the names of its |
-// contributors may be used to endorse or promote products derived from |
-// this software without specific prior written permission. |
-// |
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
-// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
-// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
-// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
-// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
-// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
-// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
-// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
-// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
- |
-#include "config.h" |
- |
-#pragma warning(push, 0) |
-#include "PopupMenu.h" |
- |
-#include "CharacterNames.h" |
-#include "ChromeClientWin.h" |
-#include "Document.h" |
-#include "Font.h" |
-#include "Frame.h" |
-#include "FontSelector.h" |
-#include "FramelessScrollView.h" |
-#include "GraphicsContext.h" |
-#include "IntRect.h" |
-#include "Page.h" |
-#include "PlatformKeyboardEvent.h" |
-#include "PlatformMouseEvent.h" |
-#include "PlatformScreen.h" |
-#include "PlatformScrollbar.h" |
-#include "PlatformWheelEvent.h" |
-#include "SystemTime.h" |
-#include "RenderBlock.h" |
-#include "RenderTheme.h" |
-#include "Widget.h" |
-#include "WidgetClientWin.h" |
-#pragma warning(pop) |
- |
-//#define LOG_ENABLE |
-#include "LogWin.h" |
- |
-using namespace WTF; |
-using namespace Unicode; |
- |
-using std::min; |
-using std::max; |
- |
-namespace WebCore { |
- |
-typedef unsigned long long TimeStamp; |
- |
-static const int kMaxVisibleRows = 20; |
-static const int kMaxHeight = 500; |
-static const int kBorderSize = 1; |
-static const TimeStamp kTypeAheadTimeoutMs = 1000; |
- |
-class PopupListBox; |
- |
-// This class holds a PopupListBox. Its sole purpose is to be able to draw |
-// a border around its child. All its paint/event handling is just forwarded |
-// to the child listBox (with the appropriate transforms). |
-class PopupContainer : public FramelessScrollView { |
-public: |
- static HWND Create(PopupMenuClient* client); |
- |
- // FramelessScrollView |
- virtual void paint(GraphicsContext* gc, const IntRect& rect); |
- virtual void hide(); |
- virtual bool handleMouseDownEvent(const PlatformMouseEvent& event); |
- virtual bool handleMouseMoveEvent(const PlatformMouseEvent& event); |
- virtual bool handleMouseReleaseEvent(const PlatformMouseEvent& event); |
- virtual bool handleWheelEvent(const PlatformWheelEvent& event); |
- virtual bool handleKeyEvent(const PlatformKeyboardEvent& event); |
- |
- // PopupContainer methods |
- |
- // Show the popup |
- void showPopup(FrameView* view); |
- |
- // Hide the popup. Do not call this directly: use client->hidePopup(). |
- void hidePopup(); |
- |
- // Compute size of widget and children. |
- void layout(); |
- |
- PopupListBox* listBox() const { return m_listBox.get(); } |
- |
-private: |
- PopupContainer(PopupMenuClient* client); |
- ~PopupContainer(); |
- |
- // Paint the border. |
- void paintBorder(GraphicsContext* gc, const IntRect& rect); |
- |
- RefPtr<PopupListBox> m_listBox; |
-}; |
- |
-// This class uses WebCore code to paint and handle events for a drop-down list |
-// box ("combobox" on Windows). |
-class PopupListBox : public FramelessScrollView { |
-public: |
- // FramelessScrollView |
- virtual void paint(GraphicsContext* gc, const IntRect& rect); |
- virtual bool handleMouseDownEvent(const PlatformMouseEvent& event); |
- virtual bool handleMouseMoveEvent(const PlatformMouseEvent& event); |
- virtual bool handleMouseReleaseEvent(const PlatformMouseEvent& event); |
- virtual bool handleWheelEvent(const PlatformWheelEvent& event); |
- virtual bool handleKeyEvent(const PlatformKeyboardEvent& event); |
- |
- // PopupListBox methods |
- |
- // Show the popup |
- void showPopup(); |
- |
- // Hide the popup. Do not call this directly: use client->hidePopup(). |
- void hidePopup(); |
- |
- // Update our internal list to match the client. |
- void updateFromElement(); |
- |
- // Free any allocated resources used in a particular popup session. |
- void clear(); |
- |
- // Set the index of the option that is displayed in the <select> widget in the page |
- void setOriginalIndex(int index); |
- |
- // Get the index of the item that the user is currently moused over or has selected with |
- // the keyboard. This is not the same as the original index, since the user has not yet |
- // accepted this input. |
- int selectedIndex() const { return m_selectedIndex; } |
- |
- // Move selection down/up the given number of items, scrolling if necessary. |
- // Positive is down. The resulting index will be clamped to the range |
- // [0, numItems), and non-option items will be skipped. |
- void adjustSelectedIndex(int delta); |
- |
- // Returns the number of items in the list. |
- int numItems() const { return static_cast<int>(m_items.size()); } |
- |
- void setBaseWidth(int width) |
- { |
- m_baseWidth = width; |
- } |
- |
- // Compute size of widget and children. |
- void layout(); |
- |
-private: |
- friend class PopupContainer; |
- |
- // A type of List Item |
- enum ListItemType { |
- TYPE_OPTION, |
- TYPE_GROUP, |
- TYPE_SEPARATOR |
- }; |
- |
- // A item (represented by <option> or <optgroup>) in the <select> widget. |
- struct ListItem { |
- ListItem(const String& label, ListItemType type) |
- : label(label.copy()), type(type), y(0) {} |
- String label; |
- ListItemType type; |
- int y; // y offset of this item, relative to the top of the popup. |
- }; |
- |
- PopupListBox(PopupMenuClient* client) |
- : m_originalIndex(0) |
- , m_selectedIndex(0) |
- , m_visibleRows(0) |
- , m_popupClient(client) |
- , m_repeatingChar(0) |
- , m_lastCharTime(0) |
- , m_acceptOnAbandon(false) |
- { |
- setScrollbarsMode(ScrollbarAlwaysOff); |
- } |
- |
- ~PopupListBox() |
- { |
- clear(); |
- } |
- |
- void disconnectClient() { m_popupClient = 0; } |
- |
- // Closes the popup |
- void abandon(); |
- // Select an index in the list, scrolling if necessary. |
- void selectIndex(int index); |
- // Accepts the selected index as the value to be displayed in the <select> widget on |
- // the web page, and closes the popup. |
- void acceptIndex(int index); |
- |
- // Returns true if the selection can be changed to index. |
- // Disabled items, or labels cannot be selected. |
- bool isSelectableItem(int index); |
- |
- // Scrolls to reveal the given index. |
- void scrollToRevealRow(int index); |
- void scrollToRevealSelection() { scrollToRevealRow(m_selectedIndex); } |
- |
- // Invalidates the row at the given index. |
- void invalidateRow(int index); |
- |
- // Gets the height of a row. |
- int getRowHeight(int index); |
- // Get the bounds of a row. |
- IntRect getRowBounds(int index); |
- |
- // Converts a point to an index of the row the point is over |
- int pointToRowIndex(const IntPoint& point); |
- |
- // Paint an individual row |
- void paintRow(GraphicsContext* gc, const IntRect& rect, int rowIndex); |
- |
- // Test if the given point is within the bounds of the popup window. |
- bool isPointInBounds(const IntPoint& point); |
- |
- // Called when the user presses a text key. Does a prefix-search of the items. |
- void typeAheadFind(const PlatformKeyboardEvent& event); |
- |
- // Returns the font to use for the given row |
- Font getRowFont(int index); |
- |
- // This is the index of the item marked as "selected" - i.e. displayed in the widget on the |
- // page. |
- int m_originalIndex; |
- |
- // This is the index of the item that the user is hovered over or has selected using the |
- // keyboard in the list. They have not confirmed this selection by clicking or pressing |
- // enter yet however. |
- int m_selectedIndex; |
- |
- // True if we should accept the selectedIndex as chosen, even if the popup |
- // is "abandoned". This is used for keyboard navigation, where we want the |
- // selection to change immediately. |
- bool m_acceptOnAbandon; |
- |
- // This is the number of rows visible in the popup. The maximum number visible at a time is |
- // defined as being kMaxVisibleRows. For a scrolled popup, this can be thought of as the |
- // page size in data units. |
- int m_visibleRows; |
- |
- // Our suggested width, not including scrollbar. |
- int m_baseWidth; |
- |
- // A list of the options contained within the <select> |
- Vector<ListItem*> m_items; |
- |
- // The <select> PopupMenuClient that opened us. |
- PopupMenuClient* m_popupClient; |
- |
- // The scrollbar which has mouse capture. Mouse events go straight to this |
- // if non-NULL. |
- RefPtr<PlatformScrollbar> m_capturingScrollbar; |
- |
- // The last scrollbar that the mouse was over. Used for mouseover highlights. |
- RefPtr<PlatformScrollbar> m_lastScrollbarUnderMouse; |
- |
- // The string the user has typed so far into the popup. Used for typeAheadFind. |
- String m_typedString; |
- |
- // The char the user has hit repeatedly. Used for typeAheadFind. |
- UChar m_repeatingChar; |
- |
- // The last time the user hit a key. Used for typeAheadFind. |
- TimeStamp m_lastCharTime; |
-}; |
- |
-static PlatformMouseEvent constructRelativeMouseEvent(const PlatformMouseEvent& e, |
- FrameView* parent, |
- FrameView* child) |
-{ |
- IntPoint pos = parent->convertSelfToChild(child, e.pos()); |
- |
- // FIXME(beng): This is a horrible hack since PlatformWheelEvent has no setters for x/y. |
- // Need to add setters and get patch back upstream to webkit source. |
- PlatformMouseEvent relativeEvent = e; |
- IntPoint& relativePos = const_cast<IntPoint&>(relativeEvent.pos()); |
- relativePos.setX(pos.x()); |
- relativePos.setY(pos.y()); |
- return relativeEvent; |
-} |
- |
-static PlatformWheelEvent constructRelativeWheelEvent(const PlatformWheelEvent& e, |
- FrameView* parent, |
- FrameView* child) |
-{ |
- IntPoint pos = parent->convertSelfToChild(child, e.pos()); |
- |
- // FIXME(beng): This is a horrible hack since PlatformWheelEvent has no setters for x/y. |
- // Need to add setters and get patch back upstream to webkit source. |
- PlatformWheelEvent relativeEvent = e; |
- IntPoint& relativePos = const_cast<IntPoint&>(relativeEvent.pos()); |
- relativePos.setX(pos.x()); |
- relativePos.setY(pos.y()); |
- return relativeEvent; |
-} |
- |
-/////////////////////////////////////////////////////////////////////////////// |
-// PopupContainer implementation |
- |
-// Get a pointer to the PopupContainer instance for the m_popup HWND, since we |
-// can't augment the PopupMenu class (above the portability layer). We store the |
-// PopupContainer in the HWND member, which is fairly hacky. |
-static PopupContainer* popupWindow(HWND popup) |
-{ |
- return reinterpret_cast<PopupContainer*>(popup); |
-} |
- |
-// static |
-HWND PopupContainer::Create(PopupMenuClient* client) |
-{ |
- PopupContainer* container = new PopupContainer(client); |
- return reinterpret_cast<HWND>(container); |
-} |
- |
-PopupContainer::PopupContainer(PopupMenuClient* client) |
- : m_listBox(new PopupListBox(client)) |
-{ |
- // FrameViews are created with a refcount of 1 so it needs releasing after we |
- // assign it to a RefPtr. |
- m_listBox->deref(); |
- |
- setScrollbarsMode(ScrollbarAlwaysOff); |
-} |
- |
-PopupContainer::~PopupContainer() |
-{ |
- if (m_listBox) |
- removeChild(m_listBox.get()); |
-} |
- |
-void PopupContainer::showPopup(FrameView* view) |
-{ |
- // Pre-layout, our size matches the <select> dropdown control. |
- int selectHeight = frameGeometry().height(); |
- |
- // Lay everything out to figure out our preferred size, then tell the view's |
- // WidgetClient about it. It should assign us a client. |
- layout(); |
- |
- WidgetClientWin* widgetClient = |
- static_cast<WidgetClientWin*>(view->client()); |
- ChromeClientWin* chromeClient = |
- static_cast<ChromeClientWin*>(view->frame()->page()->chrome()->client()); |
- if (widgetClient && chromeClient) { |
- // If the popup would extend past the bottom of the screen, open upwards |
- // instead. |
- FloatRect screen = screenRect(view); |
- IntRect widgetRect = chromeClient->windowToScreen(frameGeometry()); |
- if (widgetRect.bottom() > static_cast<int>(screen.bottom())) |
- widgetRect.move(0, -(widgetRect.height() + selectHeight)); |
- |
- widgetClient->popupOpened(this, widgetRect); |
- } |
- |
- // Must get called after we have a client and containingWindow. |
- addChild(m_listBox.get()); |
- |
- // Enable scrollbars after the listbox is inserted into the hierarchy, so |
- // it has a proper WidgetClient. |
- m_listBox->setVScrollbarMode(ScrollbarAuto); |
- |
- m_listBox->scrollToRevealSelection(); |
- |
- invalidate(); |
-} |
- |
-void PopupContainer::hidePopup() |
-{ |
- invalidate(); |
- |
- m_listBox->disconnectClient(); |
- removeChild(m_listBox.get()); |
- |
- if (client()) |
- static_cast<WidgetClientWin*>(client())->popupClosed(this); |
-} |
- |
-void PopupContainer::layout() |
-{ |
- m_listBox->layout(); |
- |
- // Place the listbox within our border. |
- m_listBox->move(kBorderSize, kBorderSize); |
- |
- // Size ourselves to contain listbox + border. |
- resize(m_listBox->width() + kBorderSize*2, m_listBox->height() + kBorderSize*2); |
- |
- invalidate(); |
-} |
- |
-bool PopupContainer::handleMouseDownEvent(const PlatformMouseEvent& event) |
-{ |
- return m_listBox->handleMouseDownEvent( |
- constructRelativeMouseEvent(event, this, m_listBox.get())); |
-} |
- |
-bool PopupContainer::handleMouseMoveEvent(const PlatformMouseEvent& event) |
-{ |
- return m_listBox->handleMouseMoveEvent( |
- constructRelativeMouseEvent(event, this, m_listBox.get())); |
-} |
- |
-bool PopupContainer::handleMouseReleaseEvent(const PlatformMouseEvent& event) |
-{ |
- return m_listBox->handleMouseReleaseEvent( |
- constructRelativeMouseEvent(event, this, m_listBox.get())); |
-} |
- |
-bool PopupContainer::handleWheelEvent(const PlatformWheelEvent& event) |
-{ |
- return m_listBox->handleWheelEvent( |
- constructRelativeWheelEvent(event, this, m_listBox.get())); |
-} |
- |
-bool PopupContainer::handleKeyEvent(const PlatformKeyboardEvent& event) |
-{ |
- return m_listBox->handleKeyEvent(event); |
-} |
- |
-void PopupContainer::hide() { |
- m_listBox->abandon(); |
-} |
- |
-void PopupContainer::paint(GraphicsContext* gc, const IntRect& rect) |
-{ |
- // adjust coords for scrolled frame |
- IntRect r = intersection(rect, frameGeometry()); |
- int tx = x(); |
- int ty = y(); |
- |
- r.move(-tx, -ty); |
- |
- gc->translate(static_cast<float>(tx), static_cast<float>(ty)); |
- m_listBox->paint(gc, r); |
- gc->translate(-static_cast<float>(tx), -static_cast<float>(ty)); |
- |
- paintBorder(gc, rect); |
-} |
- |
-void PopupContainer::paintBorder(GraphicsContext* gc, const IntRect& rect) |
-{ |
- // FIXME(mpcomplete): where do we get the border color from? |
- Color borderColor(127, 157, 185); |
- |
- gc->setStrokeStyle(NoStroke); |
- gc->setFillColor(borderColor); |
- |
- int tx = x(); |
- int ty = y(); |
- |
- // top, left, bottom, right |
- gc->drawRect(IntRect(tx, ty, width(), kBorderSize)); |
- gc->drawRect(IntRect(tx, ty, kBorderSize, height())); |
- gc->drawRect(IntRect(tx, ty + height() - kBorderSize, width(), kBorderSize)); |
- gc->drawRect(IntRect(tx + width() - kBorderSize, ty, kBorderSize, height())); |
-} |
- |
- |
-/////////////////////////////////////////////////////////////////////////////// |
-// PopupListBox implementation |
- |
-bool PopupListBox::handleMouseDownEvent(const PlatformMouseEvent& event) |
-{ |
- PlatformScrollbar* scrollbar = scrollbarUnderMouse(event); |
- if (scrollbar) { |
- m_capturingScrollbar = scrollbar; |
- m_capturingScrollbar->handleMousePressEvent(event); |
- return true; |
- } |
- |
- if (!isPointInBounds(event.pos())) |
- abandon(); |
- |
- return true; |
-} |
- |
-bool PopupListBox::handleMouseMoveEvent(const PlatformMouseEvent& event) |
-{ |
- if (m_capturingScrollbar) { |
- m_capturingScrollbar->handleMouseMoveEvent(event); |
- return true; |
- } |
- |
- PlatformScrollbar* scrollbar = scrollbarUnderMouse(event); |
- if (m_lastScrollbarUnderMouse != scrollbar) { |
- // Send mouse exited to the old scrollbar. |
- if (m_lastScrollbarUnderMouse) |
- m_lastScrollbarUnderMouse->handleMouseOutEvent(event); |
- m_lastScrollbarUnderMouse = scrollbar; |
- } |
- |
- if (scrollbar) { |
- scrollbar->handleMouseMoveEvent(event); |
- return true; |
- } |
- |
- if (!isPointInBounds(event.pos())) |
- return false; |
- |
- selectIndex(pointToRowIndex(event.pos())); |
- return true; |
-} |
- |
-bool PopupListBox::handleMouseReleaseEvent(const PlatformMouseEvent& event) |
-{ |
- if (m_capturingScrollbar) { |
- m_capturingScrollbar->handleMouseReleaseEvent(event); |
- m_capturingScrollbar = 0; |
- return true; |
- } |
- |
- if (!isPointInBounds(event.pos())) |
- return true; |
- |
- acceptIndex(pointToRowIndex(event.pos())); |
- return true; |
-} |
- |
-bool PopupListBox::handleWheelEvent(const PlatformWheelEvent& event) |
-{ |
- if (!isPointInBounds(event.pos())) { |
- abandon(); |
- return true; |
- } |
- |
- // Pass it off to the scroll view. |
- // Sadly, WebCore devs don't understand the whole "const" thing. |
- wheelEvent(const_cast<PlatformWheelEvent&>(event)); |
- return true; |
-} |
- |
-bool PopupListBox::handleKeyEvent(const PlatformKeyboardEvent& event) |
-{ |
- if (event.type() == PlatformKeyboardEvent::KeyUp) |
- return true; |
- |
- if (numItems() == 0 && event.windowsVirtualKeyCode() != VK_ESCAPE) |
- return true; |
- |
- int oldIndex = m_selectedIndex; |
- |
- switch (event.windowsVirtualKeyCode()) { |
- case VK_ESCAPE: |
- abandon(); // may delete this |
- return true; |
- case VK_RETURN: |
- acceptIndex(m_selectedIndex); // may delete this |
- return true; |
- case VK_UP: |
- adjustSelectedIndex(-1); |
- break; |
- case VK_DOWN: |
- adjustSelectedIndex(1); |
- break; |
- case VK_PRIOR: |
- adjustSelectedIndex(-m_visibleRows); |
- break; |
- case VK_NEXT: |
- adjustSelectedIndex(m_visibleRows); |
- break; |
- case VK_HOME: |
- adjustSelectedIndex(-m_selectedIndex); |
- break; |
- case VK_END: |
- adjustSelectedIndex(m_items.size()); |
- break; |
- default: |
- if (!event.ctrlKey() && !event.altKey() && !event.metaKey() && |
- isPrintableChar(event.windowsVirtualKeyCode())) { |
- typeAheadFind(event); |
- } |
- break; |
- } |
- |
- if (m_originalIndex != m_selectedIndex) { |
- // Keyboard events should update the selection immediately (but we don't |
- // want to fire the onchange event until the popup is closed, to match |
- // IE). We change the original index so we revert to that when the |
- // popup is closed. |
- m_acceptOnAbandon = true; |
- setOriginalIndex(m_selectedIndex); |
- m_popupClient->setTextFromItem(m_selectedIndex); |
- } |
- |
- return true; |
-} |
- |
-// From HTMLSelectElement.cpp |
-static String stripLeadingWhiteSpace(const String& string) |
-{ |
- int length = string.length(); |
- int i; |
- for (i = 0; i < length; ++i) |
- if (string[i] != noBreakSpace && |
- (string[i] <= 0x7F ? !isspace(string[i]) : (direction(string[i]) != WhiteSpaceNeutral))) |
- break; |
- |
- return string.substring(i, length - i); |
-} |
- |
-// From HTMLSelectElement.cpp, with modifications |
-void PopupListBox::typeAheadFind(const PlatformKeyboardEvent& event) |
-{ |
- TimeStamp now = static_cast<TimeStamp>(currentTime() * 1000.0f); |
- TimeStamp delta = now - m_lastCharTime; |
- |
- m_lastCharTime = now; |
- |
- UChar c = event.windowsVirtualKeyCode(); |
- |
- String prefix; |
- int searchStartOffset = 1; |
- if (delta > kTypeAheadTimeoutMs) { |
- m_typedString = prefix = String(&c, 1); |
- m_repeatingChar = c; |
- } else { |
- m_typedString.append(c); |
- |
- if (c == m_repeatingChar) |
- // The user is likely trying to cycle through all the items starting with this character, so just search on the character |
- prefix = String(&c, 1); |
- else { |
- m_repeatingChar = 0; |
- prefix = m_typedString; |
- searchStartOffset = 0; |
- } |
- } |
- |
- int itemCount = numItems(); |
- int index = (m_selectedIndex + searchStartOffset) % itemCount; |
- for (int i = 0; i < itemCount; i++, index = (index + 1) % itemCount) { |
- if (!isSelectableItem(index)) |
- continue; |
- |
- if (stripLeadingWhiteSpace(m_items[index]->label).startsWith(prefix, false)) { |
- selectIndex(index); |
- return; |
- } |
- } |
-} |
- |
-void PopupListBox::paint(GraphicsContext* gc, const IntRect& rect) |
-{ |
- // adjust coords for scrolled frame |
- IntRect r = intersection(rect, frameGeometry()); |
- int tx = x() - contentsX(); |
- int ty = y() - contentsY(); |
- |
- r.move(-tx, -ty); |
- |
- LOG(("PopupListBox::paint [%d,%d] [r: %d,%d,%d,%d]", tx, ty, |
- r.x(), r.y(), r.width(), r.height())); |
- |
- // set clip rect to match revised damage rect |
- gc->save(); |
- gc->translate(static_cast<float>(tx), static_cast<float>(ty)); |
- gc->clip(r); |
- |
- // TODO(mpcomplete): Can we optimize scrolling to not require repainting the |
- // entire window? Should we? |
- for (int i = 0; i < numItems(); ++i) |
- paintRow(gc, r, i); |
- |
- // Special case for an empty popup. |
- if (numItems() == 0) |
- gc->fillRect(r, Color::white); |
- |
- gc->restore(); |
- |
- ScrollView::paint(gc, rect); |
-} |
- |
-static RenderStyle* getPopupClientStyleForRow(PopupMenuClient* client, int rowIndex) { |
- RenderStyle* style = client->itemStyle(rowIndex); |
- if (!style) |
- style = client->clientStyle(); |
- return style; |
-} |
- |
-void PopupListBox::paintRow(GraphicsContext* gc, const IntRect& rect, int rowIndex) |
-{ |
- // This code is based largely on RenderListBox::paint* methods. |
- |
- IntRect rowRect = getRowBounds(rowIndex); |
- if (!rowRect.intersects(rect)) |
- return; |
- |
- RenderStyle* style = getPopupClientStyleForRow(m_popupClient, rowIndex); |
- |
- // Paint background |
- Color backColor, textColor; |
- if (rowIndex == m_selectedIndex) { |
- backColor = theme()->activeListBoxSelectionBackgroundColor(); |
- textColor = theme()->activeListBoxSelectionForegroundColor(); |
- } else { |
- backColor = m_popupClient->itemBackgroundColor(rowIndex); |
- textColor = style->color(); |
- } |
- |
- // If we have a transparent background, make sure it has a color to blend |
- // against. |
- if (backColor.hasAlpha()) |
- gc->fillRect(rowRect, Color::white); |
- |
- gc->fillRect(rowRect, backColor); |
- gc->setFillColor(textColor); |
- |
- LOG(("paintRow %d, [%d, %d, %d, %d] %x on %x", rowIndex, |
- rowRect.x(), rowRect.y(), rowRect.width(), rowRect.height(), |
- textColor.rgb(), backColor.rgb())); |
- |
- Font itemFont = getRowFont(rowIndex); |
- gc->setFont(itemFont); |
- |
- // Bunch of shit to deal with RTL text... |
- String itemText = m_popupClient->itemText(rowIndex); |
- unsigned length = itemText.length(); |
- const UChar* str = itemText.characters(); |
- |
- TextRun textRun(str, length, false, 0, 0, style->direction() == RTL, style->unicodeBidi() == Override); |
- |
- // Draw the item text |
- // TODO(ojan): http://b/1210481 We should get the padding of individual option elements. |
- rowRect.move(theme()->popupInternalPaddingLeft(style), itemFont.ascent()); |
- if (style->direction() == RTL) { |
- // Right-justify the text for RTL style. |
- rowRect.move(rowRect.width() - itemFont.width(textRun) - |
- 2 * theme()->popupInternalPaddingLeft(style), 0); |
- } |
- gc->drawBidiText(textRun, rowRect.location()); |
-} |
- |
-Font PopupListBox::getRowFont(int rowIndex) |
-{ |
- Font itemFont = m_popupClient->itemStyle(rowIndex)->font(); |
- if (m_popupClient->itemIsLabel(rowIndex)) { |
- // Bold-ify labels (ie, an <optgroup> heading). |
- FontDescription d = itemFont.fontDescription(); |
- d.setWeight(FontWeightBold); |
- Font font(d, itemFont.letterSpacing(), itemFont.wordSpacing()); |
- font.update(0); |
- return font; |
- } |
- |
- return itemFont; |
-} |
- |
-void PopupListBox::abandon() |
-{ |
- RefPtr<PopupListBox> keepAlive(this); |
- |
- m_selectedIndex = m_originalIndex; |
- |
- if (m_acceptOnAbandon) |
- m_popupClient->valueChanged(m_selectedIndex); |
- |
- // valueChanged may have torn down the popup! |
- if (m_popupClient) |
- m_popupClient->hidePopup(); |
-} |
- |
-int PopupListBox::pointToRowIndex(const IntPoint& point) |
-{ |
- int y = contentsY() + point.y(); |
- |
- // TODO(mpcomplete): binary search if perf matters. |
- for (int i = 0; i < numItems(); ++i) { |
- if (y < m_items[i]->y) |
- return i-1; |
- } |
- |
- // Last item? |
- if (y < contentsHeight()) |
- return m_items.size()-1; |
- |
- return -1; |
-} |
- |
-void PopupListBox::acceptIndex(int index) |
-{ |
- ASSERT(index >= 0 && index < numItems()); |
- |
- if (isSelectableItem(index)) { |
- RefPtr<PopupListBox> keepAlive(this); |
- |
- // Tell the <select> PopupMenuClient what index was selected, and hide ourself. |
- m_popupClient->valueChanged(index); |
- |
- // valueChanged may have torn down the popup! |
- if (m_popupClient) |
- m_popupClient->hidePopup(); |
- } |
-} |
- |
-void PopupListBox::selectIndex(int index) |
-{ |
- ASSERT(index >= 0 && index < numItems()); |
- |
- if (index != m_selectedIndex && isSelectableItem(index)) { |
- invalidateRow(m_selectedIndex); |
- m_selectedIndex = index; |
- invalidateRow(m_selectedIndex); |
- |
- scrollToRevealSelection(); |
- } |
-} |
- |
-void PopupListBox::setOriginalIndex(int index) |
-{ |
- m_originalIndex = m_selectedIndex = index; |
-} |
- |
-int PopupListBox::getRowHeight(int index) |
-{ |
- RenderStyle* style; |
- if ((index < 0) || (!(style = m_popupClient->itemStyle(index)))) |
- style = m_popupClient->clientStyle(); |
- return style->font().height(); |
-} |
- |
-IntRect PopupListBox::getRowBounds(int index) |
-{ |
- if (index >= 0) { |
- return IntRect(0, m_items[index]->y, visibleWidth(), getRowHeight(index)); |
- } else { |
- return IntRect(0, 0, visibleWidth(), getRowHeight(index)); |
- } |
-} |
- |
-void PopupListBox::invalidateRow(int index) |
-{ |
- if (index < 0) |
- return; |
- |
- updateContents(getRowBounds(index)); |
-} |
- |
-void PopupListBox::scrollToRevealRow(int index) |
-{ |
- if (index < 0) |
- return; |
- |
- IntRect rowRect = getRowBounds(index); |
- |
- if (rowRect.y() < contentsY()) { |
- // Row is above current scroll position, scroll up. |
- ScrollView::setContentsPos(0, rowRect.y()); |
- } else if (rowRect.bottom() > contentsY() + visibleHeight()) { |
- // Row is below current scroll position, scroll down. |
- ScrollView::setContentsPos(0, rowRect.bottom() - visibleHeight()); |
- } |
-} |
- |
-bool PopupListBox::isSelectableItem(int index) { |
- return m_items[index]->type == TYPE_OPTION && |
- m_popupClient->itemIsEnabled(index); |
-} |
- |
-void PopupListBox::adjustSelectedIndex(int delta) |
-{ |
- int targetIndex = m_selectedIndex + delta; |
- targetIndex = min(max(targetIndex, 0), numItems() - 1); |
- if (!isSelectableItem(targetIndex)) { |
- // We didn't land on an option. Try to find one. |
- // We try to select the closest index to target, prioritizing any in |
- // the range [current, target]. |
- |
- int dir = delta > 0 ? 1 : -1; |
- int testIndex = m_selectedIndex; |
- int bestIndex = m_selectedIndex; |
- bool passedTarget = false; |
- while (testIndex >= 0 && testIndex < numItems()) { |
- if (isSelectableItem(testIndex)) |
- bestIndex = testIndex; |
- if (testIndex == targetIndex) |
- passedTarget = true; |
- if (passedTarget && bestIndex != m_selectedIndex) |
- break; |
- |
- testIndex += dir; |
- } |
- |
- // Pick the best index, which may mean we don't change. |
- targetIndex = bestIndex; |
- } |
- |
- // Select the new index, and ensure its visible. We do this regardless of |
- // whether the selection changed to ensure keyboard events always bring the |
- // selection into view. |
- selectIndex(targetIndex); |
- scrollToRevealSelection(); |
-} |
- |
-void PopupListBox::updateFromElement() |
-{ |
- // It happens when pressing a key to jump to an item, then use tab or |
- // mouse to get away from the select box. In that case, updateFromElement |
- // is called before abandon, which causes discarding of the select result. |
- if (m_acceptOnAbandon) { |
- m_popupClient->valueChanged(m_selectedIndex); |
- m_acceptOnAbandon = false; |
- } |
- |
- clear(); |
- |
- int size = m_popupClient->listSize(); |
- for (int i = 0; i < size; ++i) { |
- ListItemType type; |
- if (m_popupClient->itemIsSeparator(i)) |
- type = PopupListBox::TYPE_SEPARATOR; |
- else if (m_popupClient->itemIsLabel(i)) |
- type = PopupListBox::TYPE_GROUP; |
- else |
- type = PopupListBox::TYPE_OPTION; |
- m_items.append(new ListItem(m_popupClient->itemText(i), type)); |
- } |
- |
- m_selectedIndex = m_popupClient->selectedIndex(); |
- setOriginalIndex(m_selectedIndex); |
- |
- layout(); |
-} |
- |
-void PopupListBox::layout() |
-{ |
- // Size our child items. |
- int baseWidth = 0; |
- int paddingWidth = 0; |
- int y = 0; |
- for (int i = 0; i < numItems(); ++i) { |
- Font itemFont = getRowFont(i); |
- |
- // Place the item vertically. |
- m_items[i]->y = y; |
- y += itemFont.height(); |
- |
- // Ensure the popup is wide enough to fit this item. |
- String text = m_popupClient->itemText(i); |
- if (!text.isEmpty()) { |
- int width = itemFont.width(TextRun(text)); |
- baseWidth = max(baseWidth, width); |
- } |
- RenderStyle* style = getPopupClientStyleForRow(m_popupClient, i); |
- // TODO(ojan): http://b/1210481 We should get the padding of individual option elements. |
- paddingWidth = max(paddingWidth, |
- theme()->popupInternalPaddingLeft(style) + theme()->popupInternalPaddingRight(style)); |
- } |
- |
- int windowHeight = 0; |
- m_visibleRows = min(numItems(), kMaxVisibleRows); |
- for (int i = 0; i < m_visibleRows; ++i) { |
- int rowHeight = getRowHeight(i); |
- if (windowHeight + rowHeight > kMaxHeight) { |
- m_visibleRows = i; |
- break; |
- } |
- |
- windowHeight += rowHeight; |
- } |
- |
- if (windowHeight == 0) |
- windowHeight = min(getRowHeight(-1), kMaxHeight); |
- |
- // Set our widget and scrollable contents sizes. |
- int scrollbarWidth = 0; |
- if (m_visibleRows < numItems()) |
- scrollbarWidth = PlatformScrollbar::verticalScrollbarWidth(); |
- |
- int windowWidth = baseWidth + scrollbarWidth + paddingWidth; |
- int contentWidth = baseWidth; |
- |
- if (windowWidth < m_baseWidth) { |
- windowWidth = m_baseWidth; |
- contentWidth = m_baseWidth - scrollbarWidth - paddingWidth; |
- } else { |
- m_baseWidth = baseWidth; |
- } |
- |
- resize(windowWidth, windowHeight); |
- resizeContents(contentWidth, getRowBounds(numItems() - 1).bottom()); |
- scrollToRevealSelection(); |
- |
- invalidate(); |
-} |
- |
-void PopupListBox::clear() |
-{ |
- for (Vector<ListItem*>::iterator it = m_items.begin(); it != m_items.end(); ++it) |
- delete *it; |
- m_items.clear(); |
-} |
- |
-bool PopupListBox::isPointInBounds(const IntPoint& point) |
-{ |
- return numItems() != 0 && IntRect(0, 0, width(), height()).contains(point); |
-} |
- |
- |
-/////////////////////////////////////////////////////////////////////////////// |
-// PopupMenu implementation |
-// |
-// Note: you cannot add methods to this class, since it is defined above the |
-// portability layer. To access methods and properties on the |
-// popup widgets, use |popupWindow| above. |
- |
-PopupMenu::PopupMenu(PopupMenuClient* client) |
- : m_popupClient(client) |
- , m_popup(0) |
- , m_wasClicked(false) |
-{ |
-} |
- |
-PopupMenu::~PopupMenu() |
-{ |
- hide(); |
-} |
- |
-void PopupMenu::show(const IntRect& r, FrameView* v, int index) |
-{ |
- m_popup = PopupContainer::Create(client()); |
- |
- PopupContainer* popup = popupWindow(m_popup); |
- // The rect is the size of the select box. It's usually larger than we need. |
- // subtract border size so that usually the container will be displayed |
- // exactly the same width as the select box. |
- popup->listBox()->setBaseWidth(max(r.width() - kBorderSize * 2, 0)); |
- |
- updateFromElement(); |
- |
- // We set the selected item in updateFromElement(), and disregard the |
- // index passed into this function (same as Webkit's PopupMenuWin.cpp) |
- // TODO(ericroman): make sure this is correct, and add an assertion. |
- // DCHECK(popupWindow(m_popup)->listBox()->selectedIndex() == index); |
- |
- // Convert point to main window coords. |
- IntPoint location = v->contentsToWindow(r.location()); |
- |
- // Move it below the select widget. |
- location.move(0, r.height()); |
- |
- IntRect popupRect(location, r.size()); |
- popup->setFrameGeometry(popupRect); |
- popup->showPopup(v); |
-} |
- |
-void PopupMenu::hide() |
-{ |
- if (m_popup) { |
- PopupContainer* popup = popupWindow(m_popup); |
- popup->hidePopup(); |
- popup->deref(); |
- m_popup = 0; |
- } |
-} |
- |
-void PopupMenu::updateFromElement() |
-{ |
- popupWindow(m_popup)->listBox()->updateFromElement(); |
-} |
- |
-bool PopupMenu::itemWritingDirectionIsNatural() |
-{ |
- return false; |
-} |
- |
-bool PopupMenu::up(unsigned lines) |
-{ |
- popupWindow(m_popup)->listBox()->adjustSelectedIndex(-static_cast<int>(lines)); |
- return true; |
-} |
- |
-bool PopupMenu::down(unsigned lines) |
-{ |
- popupWindow(m_popup)->listBox()->adjustSelectedIndex(static_cast<int>(lines)); |
- return true; |
-} |
- |
-int PopupMenu::focusedIndex() const |
-{ |
- return popupWindow(m_popup)->listBox()->selectedIndex(); |
-} |
- |
-void PopupMenu::valueChanged(Scrollbar*) |
-{ |
- // FIXME |
-} |
- |
-WebCore::IntRect PopupMenu::windowClipRect() const |
-{ |
- // FIXME |
- return WebCore::IntRect(); |
-} |
- |
-} // namespace WebCore |