Reviewed by Adele.
authoraroben <aroben@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 26 Oct 2006 18:19:57 +0000 (18:19 +0000)
committeraroben <aroben@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 26 Oct 2006 18:19:57 +0000 (18:19 +0000)
        Fix: <rdar://problem/3951815> add "type-to-select" feature for list
        boxes (<select> elements)

        This also adds type-ahead find for closed menulists (popups).

        * WebCore.xcodeproj/project.pbxproj: Version wars.
        * html/HTMLSelectElement.h: Add new instance variables for type-ahead
        find.
        * html/HTMLSelectElement.cpp:
        (WebCore::HTMLSelectElement::HTMLSelectElement): Initialize new
        instance variables.
        (WebCore::HTMLSelectElement::defaultEventHandler): Perform type-ahead
        find on printable character presses.
        (WebCore::HTMLSelectElement::menuListDefaultEventHandler): Remove
        redundant check that the listIndex has changed (setSelectedIndex does
        this for us), and fix indentation.
        (WebCore::HTMLSelectElement::listBoxDefaultEventHandler): Change an if
        to an else-if.
        (WebCore::stripLeadingWhiteSpace): Helper function for typeAheadFind()
        to strip leading whitespace (including non-breaking spaces) from a
        string.
        (WebCore::HTMLSelectElement::typeAheadFind): New method to perform
        type-ahead find.
        * platform/PopupMenu.h: Added new updateFromElement() method to be
        called from RenderMenuList::updateFromElement().
        * platform/mac/PopupMenuMac.mm:
        (WebCore::PopupMenu::updateFromElement): While we are using
        NSPopUpButtonCell for our popups, this method can stay empty.
        * rendering/RenderListBox.cpp:
        (WebCore::RenderListBox::updateFromElement): Scroll to reveal the first
        selected element.
        * rendering/RenderMenuList.cpp:
        (WebCore::RenderMenuList::updateFromElement): Tell the popup to update
        if it's visible.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@17331 268f45cc-cd09-0410-ab3c-d52691b4dbfc

WebCore/ChangeLog
WebCore/WebCore.xcodeproj/project.pbxproj
WebCore/html/HTMLSelectElement.cpp
WebCore/html/HTMLSelectElement.h
WebCore/platform/PopupMenu.h
WebCore/platform/mac/PopupMenuMac.mm
WebCore/rendering/RenderListBox.cpp
WebCore/rendering/RenderMenuList.cpp

index 9c0237c..e5cc31c 100644 (file)
@@ -1,3 +1,42 @@
+2006-10-26  Adam Roben  <aroben@apple.com>
+
+        Reviewed by Adele.
+
+        Fix: <rdar://problem/3951815> add "type-to-select" feature for list
+        boxes (<select> elements)
+
+        This also adds type-ahead find for closed menulists (popups).
+
+        * WebCore.xcodeproj/project.pbxproj: Version wars.
+        * html/HTMLSelectElement.h: Add new instance variables for type-ahead
+        find.
+        * html/HTMLSelectElement.cpp:
+        (WebCore::HTMLSelectElement::HTMLSelectElement): Initialize new
+        instance variables.
+        (WebCore::HTMLSelectElement::defaultEventHandler): Perform type-ahead
+        find on printable character presses.
+        (WebCore::HTMLSelectElement::menuListDefaultEventHandler): Remove
+        redundant check that the listIndex has changed (setSelectedIndex does
+        this for us), and fix indentation.
+        (WebCore::HTMLSelectElement::listBoxDefaultEventHandler): Change an if
+        to an else-if.
+        (WebCore::stripLeadingWhiteSpace): Helper function for typeAheadFind()
+        to strip leading whitespace (including non-breaking spaces) from a
+        string.
+        (WebCore::HTMLSelectElement::typeAheadFind): New method to perform
+        type-ahead find.
+        * platform/PopupMenu.h: Added new updateFromElement() method to be
+        called from RenderMenuList::updateFromElement().
+        * platform/mac/PopupMenuMac.mm:
+        (WebCore::PopupMenu::updateFromElement): While we are using
+        NSPopUpButtonCell for our popups, this method can stay empty.
+        * rendering/RenderListBox.cpp:
+        (WebCore::RenderListBox::updateFromElement): Scroll to reveal the first
+        selected element.
+        * rendering/RenderMenuList.cpp:
+        (WebCore::RenderMenuList::updateFromElement): Tell the popup to update
+        if it's visible.
+
 2006-10-26  Darin Adler  <darin@apple.com>
 
         Reviewed by Anders.
index 9d87e2b..ed7bd3d 100644 (file)
                0867D690FE84028FC02AAC07 /* Project object */ = {
                        isa = PBXProject;
                        buildConfigurationList = 149C284308902B11008A9EFC /* Build configuration list for PBXProject "WebCore" */;
+                       compatibilityVersion = "Xcode 2.4";
                        hasScannedForEncodings = 1;
                        knownRegions = (
                                English,
                        mainGroup = 0867D691FE84028FC02AAC07 /* WebKit */;
                        productRefGroup = 034768DFFF38A50411DB9C8B /* Products */;
                        projectDirPath = "";
+                       projectRoot = "";
+                       shouldCheckCompatibility = 1;
                        targets = (
                                93F198A508245E59001E9ABC /* WebCore */,
                                DD041FBE09D9DDBE0010AF2A /* Derived Sources */,
index c26b0bb..fb4c2a4 100644 (file)
@@ -66,6 +66,9 @@ HTMLSelectElement::HTMLSelectElement(Document* doc, HTMLFormElement* f)
     , m_multiple(false)
     , m_recalcListItems(false)
     , m_lastOnChangeIndex(0)
+    , m_repeatingChar(0)
+    , m_lastCharTime(0)
+    , m_typedString(String())
 {
     document()->registerFormElementWithState(this);
 }
@@ -526,12 +529,24 @@ void HTMLSelectElement::dispatchBlurEvent()
 #endif
     HTMLGenericFormElement::dispatchBlurEvent();
 }
+
 void HTMLSelectElement::defaultEventHandler(Event* evt)
 {
     if (usesMenuList())
         menuListDefaultEventHandler(evt);
     else if (renderer() && renderer()->isListBox() && renderer()->isListBox()) 
         listBoxDefaultEventHandler(evt);
+
+    if (!evt->defaultHandled() && evt->type() == keypressEvent && evt->isKeyboardEvent()) {
+        KeyboardEvent* keyboardEvent = static_cast<KeyboardEvent*>(evt);
+    
+        if (!keyboardEvent->ctrlKey() && !keyboardEvent->altKey() && !keyboardEvent->metaKey()
+            && isprint(static_cast<KeyboardEvent*>(evt)->charCode())) {
+            typeAheadFind(static_cast<KeyboardEvent*>(evt));
+            evt->setDefaultHandled();
+        }
+    }
+
     HTMLGenericFormElement::defaultEventHandler(evt);
 }
 
@@ -579,17 +594,16 @@ void HTMLSelectElement::menuListDefaultEventHandler(Event* evt)
             if (listIndex >= 0 && listIndex < size)
                 setSelectedIndex(listToOptionIndex(listIndex));
             handled = true;
-        } else if (keyIdentifier == "Enter" && listIndex != m_lastOnChangeIndex) {
+        } else if (keyIdentifier == "Enter") {
             setSelectedIndex(listToOptionIndex(listIndex), true, true);
         }
-
 #endif
         if (handled)
             evt->setDefaultHandled();
 
     }
     if (evt->type() == mousedownEvent) {
-         focus();
+        focus();
         if (menuList->popupIsVisible())
             menuList->hidePopup();
         else
@@ -624,9 +638,7 @@ void HTMLSelectElement::listBoxDefaultEventHandler(Event* evt)
             }
             setSelectedIndex(optionIndex, deselectOtherOptions);
         }
-    }
-    
-    if (evt->type() == keypressEvent) {
+    } else if (evt->type() == keypressEvent) {
         if (!evt->isKeyboardEvent())
             return;
         String keyIdentifier = static_cast<KeyboardEvent*>(evt)->keyIdentifier();
@@ -654,6 +666,65 @@ void HTMLSelectElement::listBoxDefaultEventHandler(Event* evt)
     }
 }
 
+static String stripLeadingWhiteSpace(const String& string)
+{
+    const UChar nonBreakingSpace = 0xA0;
+
+    int length = string.length();
+    int i;
+    for (i = 0; i < length; ++i)
+        if (string[i] != nonBreakingSpace && (string[i] <= 0x7F ? !isspace(string[i]) : (u_charDirection(string[i]) != U_WHITE_SPACE_NEUTRAL)))
+            break;
+
+    return string.substring(i, length - i);
+}
+
+const DOMTimeStamp typeAheadTimeout = 1000;
+void HTMLSelectElement::typeAheadFind(KeyboardEvent* event)
+{
+    if (event->timeStamp() < m_lastCharTime)
+        return;
+
+    DOMTimeStamp delta = event->timeStamp() - m_lastCharTime;
+
+    m_lastCharTime = event->timeStamp();
+
+    UChar c = event->charCode();
+
+    String prefix;
+    int searchStartOffset = 1;
+    if (delta > typeAheadTimeout) {
+        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;
+        }
+    }
+
+    const Vector<HTMLElement*>& items = listItems();
+    int itemCount = items.size();
+
+    int index = (optionToListIndex(selectedIndex()) + searchStartOffset) % itemCount;
+    for (int i = 0; i < itemCount; i++, index = (index + 1) % itemCount) {
+        if (!items[index]->hasTagName(optionTag) || items[index]->disabled())
+            continue;
+
+        if (stripLeadingWhiteSpace(static_cast<HTMLOptionElement*>(items[index])->optionText()).startsWith(prefix, false)) {
+            setSelectedIndex(listToOptionIndex(index));
+            setChanged();
+            return;
+        }
+    }
+}
+
 int HTMLSelectElement::nextSelectableListIndex(int startIndex)
 {
     const Vector<HTMLElement*>& items = listItems();
index f1194f1..baded05 100644 (file)
@@ -26,6 +26,7 @@
 #ifndef HTML_HTMLSelectElementImpl_H
 #define HTML_HTMLSelectElementImpl_H
 
+#include "Event.h"
 #include "HTMLGenericFormElement.h"
 #include "HTMLCollection.h"
 #include "RenderStyle.h"
@@ -36,6 +37,7 @@ namespace WebCore {
 class DeprecatedRenderSelect;
 class HTMLOptionElement;
 class HTMLOptionsCollection;
+class KeyboardEvent;
 
 class HTMLSelectElement : public HTMLGenericFormElement {
     friend class DeprecatedRenderSelect;
@@ -132,6 +134,7 @@ private:
     int previousSelectableListIndex(int startIndex);
     void menuListDefaultEventHandler(Event*);
     void listBoxDefaultEventHandler(Event*);
+    void typeAheadFind(KeyboardEvent*);
 
     mutable Vector<HTMLElement*> m_listItems;
     int m_minwidth;
@@ -140,6 +143,11 @@ private:
     mutable bool m_recalcListItems;
     int m_lastOnChangeIndex;
 
+    // Instance variables for type-ahead find
+    UChar m_repeatingChar;
+    DOMTimeStamp m_lastCharTime;
+    String m_typedString;
+
     HTMLCollection::CollectionInfo m_collectionInfo;
 };
 
index 01db36f..a346e17 100644 (file)
@@ -55,6 +55,8 @@ public:
 
     void show(const IntRect&, FrameView*, int index);
     void hide();
+
+    void updateFromElement();
     
     RenderMenuList* menuList() const { return m_menuList; }
 
@@ -69,7 +71,7 @@ public:
     int listIndexAtPoint(const IntPoint& point) { return (point.y() + m_scrollOffset) / m_itemHeight; }
 
     bool setFocusedIndex(int index, bool setControlText = true, bool fireOnChange = false);
-    int focusedIndex() const { return m_focusedIndex; }
+    int focusedIndex() const;
     void focusFirst();
     void focusLast();
 
@@ -112,7 +114,6 @@ private:
     bool m_wasClicked;
     IntRect m_windowRect;
     int m_itemHeight;
-    int m_focusedIndex;
     int m_scrollOffset;
     int m_wheelDelta;
 #endif
index 6eee3f7..775bbd8 100644 (file)
@@ -197,5 +197,9 @@ void PopupMenu::addOption(HTMLOptionElement* element)
 
     [string release];
 }
+    
+void PopupMenu::updateFromElement()
+{
+}
 
 }
index 92b43e0..cb8f3a1 100644 (file)
@@ -78,9 +78,11 @@ void RenderListBox::setStyle(RenderStyle* style)
 void RenderListBox::updateFromElement()
 {
     HTMLSelectElement* select = static_cast<HTMLSelectElement*>(node());
-    const Vector<HTMLElement*>& listItems = select->listItems();
-    int size = listItems.size();
-    if (m_optionsChanged) {  
+
+    if (m_optionsChanged) {
+        const Vector<HTMLElement*>& listItems = select->listItems();
+        int size = listItems.size();
+        
         float width = 0;
         TextStyle textStyle(0, 0, 0, false, false, false, false);
         for (int i = 0; i < size; ++i) {
@@ -106,6 +108,8 @@ void RenderListBox::updateFromElement()
         m_optionsChanged = false;
         setNeedsLayoutAndMinMaxRecalc();
     }
+    
+    scrollToRevealElementAtListIndex(select->optionToListIndex(select->selectedIndex()));
 }
 
 void RenderListBox::calcMinMaxWidth()
index 19d3f4d..f546257 100644 (file)
@@ -102,10 +102,11 @@ void RenderMenuList::setStyle(RenderStyle* style)
 void RenderMenuList::updateFromElement()
 {
     HTMLSelectElement* select = static_cast<HTMLSelectElement*>(node());
-    const Vector<HTMLElement*>& listItems = select->listItems();
-    int size = listItems.size();
 
-    if (m_optionsChanged) {        
+    if (m_optionsChanged) {
+        const Vector<HTMLElement*>& listItems = select->listItems();
+        int size = listItems.size();
+        
         float width = 0;
         TextStyle textStyle(0, 0, 0, false, false, false, false);
         for (int i = 0; i < size; ++i) {
@@ -121,6 +122,9 @@ void RenderMenuList::updateFromElement()
     }
 
     setTextFromOption(select->selectedIndex());
+    
+    if (m_popupIsVisible)
+        m_popup->updateFromElement();
 }
 
 void RenderMenuList::setTextFromOption(int optionIndex)