Web Inspector: separate SuggestBox from TextPrompt
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 12 Feb 2013 14:04:08 +0000 (14:04 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 12 Feb 2013 14:04:08 +0000 (14:04 +0000)
https://bugs.webkit.org/show_bug.cgi?id=109430

Patch by Andrey Lushnikov <lushnikov@chromium.org> on 2013-02-12
Reviewed by Alexander Pavlov.

Create WebInspector.SuggestBoxDelegate interface and
refactor TextPrompt to use this interface. Separate SuggestBox into
WebInspector.SuggestBox namespace and put it into its own file.

No new tests: no change in behaviour.

* WebCore.gypi:
* WebCore.vcproj/WebCore.vcproj:
* inspector/compile-front-end.py:
* inspector/front-end/SuggestBox.js: Added.
(WebInspector.SuggestBoxDelegate):
(WebInspector.SuggestBoxDelegate.prototype.applySuggestion):
(WebInspector.SuggestBoxDelegate.prototype.acceptSuggestion):
(WebInspector.SuggestBoxDelegate.prototype.userEnteredText):
(WebInspector.SuggestBox):
(WebInspector.SuggestBox.prototype.get visible):
(WebInspector.SuggestBox.prototype.get hasSelection):
(WebInspector.SuggestBox.prototype._onscrollresize):
(WebInspector.SuggestBox.prototype._updateBoxPositionWithExistingAnchor):
(WebInspector.SuggestBox.prototype._updateBoxPosition):
(WebInspector.SuggestBox.prototype._onboxmousedown):
(WebInspector.SuggestBox.prototype.hide):
(WebInspector.SuggestBox.prototype.removeFromElement):
(WebInspector.SuggestBox.prototype._applySuggestion):
(WebInspector.SuggestBox.prototype.acceptSuggestion):
(WebInspector.SuggestBox.prototype._selectClosest):
(WebInspector.SuggestBox.prototype.updateSuggestions):
(WebInspector.SuggestBox.prototype._onItemMouseDown):
(WebInspector.SuggestBox.prototype._createItemElement):
(WebInspector.SuggestBox.prototype._updateItems):
(WebInspector.SuggestBox.prototype._selectItem):
(WebInspector.SuggestBox.prototype._canShowBox):
(WebInspector.SuggestBox.prototype._rememberRowCountPerViewport):
(WebInspector.SuggestBox.prototype._completionsReady):
(WebInspector.SuggestBox.prototype.upKeyPressed):
(WebInspector.SuggestBox.prototype.downKeyPressed):
(WebInspector.SuggestBox.prototype.pageUpKeyPressed):
(WebInspector.SuggestBox.prototype.pageDownKeyPressed):
(WebInspector.SuggestBox.prototype.enterKeyPressed):
(WebInspector.SuggestBox.prototype.tabKeyPressed):
* inspector/front-end/TextPrompt.js:
(WebInspector.TextPrompt.prototype.userEnteredText):
(WebInspector.TextPrompt.prototype._attachInternal):
(WebInspector.TextPrompt.prototype._completionsReady):
(WebInspector.TextPrompt.prototype.applySuggestion):
(WebInspector.TextPrompt.prototype._applySuggestion):
(WebInspector.TextPrompt.prototype.enterKeyPressed):
(WebInspector.TextPrompt.prototype.upKeyPressed):
(WebInspector.TextPrompt.prototype.downKeyPressed):
(WebInspector.TextPrompt.prototype.pageUpKeyPressed):
(WebInspector.TextPrompt.prototype.pageDownKeyPressed):
* inspector/front-end/WebKit.qrc:
* inspector/front-end/inspector.html:

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

Source/WebCore/ChangeLog
Source/WebCore/WebCore.gypi
Source/WebCore/WebCore.vcproj/WebCore.vcproj
Source/WebCore/inspector/compile-front-end.py
Source/WebCore/inspector/front-end/SuggestBox.js [new file with mode: 0644]
Source/WebCore/inspector/front-end/TextPrompt.js
Source/WebCore/inspector/front-end/WebKit.qrc
Source/WebCore/inspector/front-end/inspector.html

index 0ffb7727c28b78cb082a78004cef330bf65265a5..6e477e3d4b564076adcc5818c48e4b0682439893 100644 (file)
@@ -1,3 +1,64 @@
+2013-02-12  Andrey Lushnikov  <lushnikov@chromium.org>
+
+        Web Inspector: separate SuggestBox from TextPrompt
+        https://bugs.webkit.org/show_bug.cgi?id=109430
+
+        Reviewed by Alexander Pavlov.
+
+        Create WebInspector.SuggestBoxDelegate interface and
+        refactor TextPrompt to use this interface. Separate SuggestBox into
+        WebInspector.SuggestBox namespace and put it into its own file.
+
+        No new tests: no change in behaviour.
+
+        * WebCore.gypi:
+        * WebCore.vcproj/WebCore.vcproj:
+        * inspector/compile-front-end.py:
+        * inspector/front-end/SuggestBox.js: Added.
+        (WebInspector.SuggestBoxDelegate):
+        (WebInspector.SuggestBoxDelegate.prototype.applySuggestion):
+        (WebInspector.SuggestBoxDelegate.prototype.acceptSuggestion):
+        (WebInspector.SuggestBoxDelegate.prototype.userEnteredText):
+        (WebInspector.SuggestBox):
+        (WebInspector.SuggestBox.prototype.get visible):
+        (WebInspector.SuggestBox.prototype.get hasSelection):
+        (WebInspector.SuggestBox.prototype._onscrollresize):
+        (WebInspector.SuggestBox.prototype._updateBoxPositionWithExistingAnchor):
+        (WebInspector.SuggestBox.prototype._updateBoxPosition):
+        (WebInspector.SuggestBox.prototype._onboxmousedown):
+        (WebInspector.SuggestBox.prototype.hide):
+        (WebInspector.SuggestBox.prototype.removeFromElement):
+        (WebInspector.SuggestBox.prototype._applySuggestion):
+        (WebInspector.SuggestBox.prototype.acceptSuggestion):
+        (WebInspector.SuggestBox.prototype._selectClosest):
+        (WebInspector.SuggestBox.prototype.updateSuggestions):
+        (WebInspector.SuggestBox.prototype._onItemMouseDown):
+        (WebInspector.SuggestBox.prototype._createItemElement):
+        (WebInspector.SuggestBox.prototype._updateItems):
+        (WebInspector.SuggestBox.prototype._selectItem):
+        (WebInspector.SuggestBox.prototype._canShowBox):
+        (WebInspector.SuggestBox.prototype._rememberRowCountPerViewport):
+        (WebInspector.SuggestBox.prototype._completionsReady):
+        (WebInspector.SuggestBox.prototype.upKeyPressed):
+        (WebInspector.SuggestBox.prototype.downKeyPressed):
+        (WebInspector.SuggestBox.prototype.pageUpKeyPressed):
+        (WebInspector.SuggestBox.prototype.pageDownKeyPressed):
+        (WebInspector.SuggestBox.prototype.enterKeyPressed):
+        (WebInspector.SuggestBox.prototype.tabKeyPressed):
+        * inspector/front-end/TextPrompt.js:
+        (WebInspector.TextPrompt.prototype.userEnteredText):
+        (WebInspector.TextPrompt.prototype._attachInternal):
+        (WebInspector.TextPrompt.prototype._completionsReady):
+        (WebInspector.TextPrompt.prototype.applySuggestion):
+        (WebInspector.TextPrompt.prototype._applySuggestion):
+        (WebInspector.TextPrompt.prototype.enterKeyPressed):
+        (WebInspector.TextPrompt.prototype.upKeyPressed):
+        (WebInspector.TextPrompt.prototype.downKeyPressed):
+        (WebInspector.TextPrompt.prototype.pageUpKeyPressed):
+        (WebInspector.TextPrompt.prototype.pageDownKeyPressed):
+        * inspector/front-end/WebKit.qrc:
+        * inspector/front-end/inspector.html:
+
 2013-02-12  Bruno de Oliveira Abinader  <bruno.abinader@basyskom.com>
 
         [TexMap] Apply frames-per-second debug counter to WK1.
index 175ed66bc45180a3fec7df74350df1f343b73161..24fce1abbc6c912c86768b820b0bf08383e98327 100644 (file)
             'inspector/front-end/SplitView.js',
             'inspector/front-end/StatusBarButton.js',
             'inspector/front-end/StylesSourceMapping.js',
+            'inspector/front-end/SuggestBox.js',
             'inspector/front-end/TabbedPane.js',
             'inspector/front-end/TestController.js',
             'inspector/front-end/TextEditor.js',
index 96ced7dc9e2d9e6fc30618feb732e8596b4d6576..1eac99304e364ca78ad5f84b261bd2e2e5143ecd 100755 (executable)
                                        RelativePath="..\inspector\front-end\textPrompt.css"
                                        >
                                </File>
+                               <File
+                                       RelativePath="..\inspector\front-end\SuggestBox.js"
+                                       >
+                               </File>
                                <File
                                        RelativePath="..\inspector\front-end\TextPrompt.js"
                                        >
index f9e1e31ede33f3367896bd9bee788033939702dc..0d171ad121a8dac8491753dc2d6d833a870240cf 100755 (executable)
@@ -151,6 +151,7 @@ modules = [
             "SplitView.js",
             "SidebarView.js",
             "StatusBarButton.js",
+            "SuggestBox.js",
             "TabbedPane.js",
             "TextEditor.js",
             "TextEditorHighlighter.js",
diff --git a/Source/WebCore/inspector/front-end/SuggestBox.js b/Source/WebCore/inspector/front-end/SuggestBox.js
new file mode 100644 (file)
index 0000000..d689965
--- /dev/null
@@ -0,0 +1,402 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+/**
+ * @interface
+ */
+WebInspector.SuggestBoxDelegate = function()
+{
+}
+
+WebInspector.SuggestBoxDelegate.prototype = {
+    /**
+     * @param {string} suggestion
+     * @param {boolean=} isIntermediateSuggestion
+     */
+    applySuggestion: function(suggestion, isIntermediateSuggestion) { },
+
+    /**
+     * acceptSuggestion will be always called after call to applySuggestion with isIntermediateSuggestion being equal to false.
+     */
+    acceptSuggestion: function() { },
+
+    /**
+     * @return {string}
+     */
+    userEnteredText: function() { }
+}
+
+/**
+ * @constructor
+ * @param {WebInspector.SuggestBoxDelegate} suggestBoxDelegate
+ * @param {Element} inputElement
+ * @param {string} className
+ */
+WebInspector.SuggestBox = function(suggestBoxDelegate, inputElement, className)
+{
+    this._suggestBoxDelegate = suggestBoxDelegate;
+    this._inputElement = inputElement;
+    this._length = 0;
+    this._selectedIndex = -1;
+    this._selectedElement = null;
+    this._boundOnScroll = this._onscrollresize.bind(this, true);
+    this._boundOnResize = this._onscrollresize.bind(this, false);
+    window.addEventListener("scroll", this._boundOnScroll, true);
+    window.addEventListener("resize", this._boundOnResize, true);
+
+    this._bodyElement = inputElement.ownerDocument.body;
+    this._element = inputElement.ownerDocument.createElement("div");
+    this._element.className = "suggest-box " + (className || "");
+    this._element.addEventListener("mousedown", this._onboxmousedown.bind(this), true);
+    this.containerElement = this._element.createChild("div", "container");
+    this.contentElement = this.containerElement.createChild("div", "content");
+}
+
+WebInspector.SuggestBox.prototype = {
+    get visible()
+    {
+        return !!this._element.parentElement;
+    },
+
+    get hasSelection()
+    {
+        return !!this._selectedElement;
+    },
+
+    _onscrollresize: function(isScroll, event)
+    {
+        if (isScroll && this._element.isAncestor(event.target) || !this.visible)
+            return;
+        this._updateBoxPositionWithExistingAnchor();
+    },
+
+    _updateBoxPositionWithExistingAnchor: function()
+    {
+        this._updateBoxPosition(this._anchorBox);
+    },
+
+    /**
+     * @param {AnchorBox} anchorBox
+     */
+    _updateBoxPosition: function(anchorBox)
+    {
+        // Measure the content element box.
+        this.contentElement.style.display = "inline-block";
+        document.body.appendChild(this.contentElement);
+        this.contentElement.positionAt(0, 0);
+        var contentWidth = this.contentElement.offsetWidth;
+        var contentHeight = this.contentElement.offsetHeight;
+        this.contentElement.style.display = "block";
+        this.containerElement.appendChild(this.contentElement);
+
+        // Lay out the suggest-box relative to the anchorBox.
+        this._anchorBox = anchorBox;
+        const spacer = 6;
+
+        const suggestBoxPaddingX = 21;
+        var maxWidth = document.body.offsetWidth - anchorBox.x - spacer;
+        var width = Math.min(contentWidth, maxWidth - suggestBoxPaddingX) + suggestBoxPaddingX;
+        var paddedWidth = contentWidth + suggestBoxPaddingX;
+        var boxX = anchorBox.x;
+        if (width < paddedWidth) {
+            // Shift the suggest box to the left to accommodate the content without trimming to the BODY edge.
+            maxWidth = document.body.offsetWidth - spacer;
+            width = Math.min(contentWidth, maxWidth - suggestBoxPaddingX) + suggestBoxPaddingX;
+            boxX = document.body.offsetWidth - width;
+        }
+
+        const suggestBoxPaddingY = 2;
+        var boxY;
+        var aboveHeight = anchorBox.y;
+        var underHeight = document.body.offsetHeight - anchorBox.y - anchorBox.height;
+        var maxHeight = Math.max(underHeight, aboveHeight) - spacer;
+        var height = Math.min(contentHeight, maxHeight - suggestBoxPaddingY) + suggestBoxPaddingY;
+        if (underHeight >= aboveHeight) {
+            // Locate the suggest box under the anchorBox.
+            boxY = anchorBox.y + anchorBox.height;
+            this._element.removeStyleClass("above-anchor");
+            this._element.addStyleClass("under-anchor");
+        } else {
+            // Locate the suggest box above the anchorBox.
+            boxY = anchorBox.y - height;
+            this._element.removeStyleClass("under-anchor");
+            this._element.addStyleClass("above-anchor");
+        }
+
+        this._element.positionAt(boxX, boxY);
+        this._element.style.width = width + "px";
+        this._element.style.height = height + "px";
+    },
+
+    _onboxmousedown: function(event)
+    {
+        event.preventDefault();
+    },
+
+    hide: function()
+    {
+        if (!this.visible)
+            return;
+
+        this._element.parentElement.removeChild(this._element);
+        delete this._selectedElement;
+    },
+
+    removeFromElement: function()
+    {
+        window.removeEventListener("scroll", this._boundOnScroll, true);
+        window.removeEventListener("resize", this._boundOnResize, true);
+        this.hide();
+    },
+
+    /**
+     * @param {string=} text
+     * @param {boolean=} isIntermediateSuggestion
+     */
+    _applySuggestion: function(text, isIntermediateSuggestion)
+    {
+        if (!this.visible || !(text || this._selectedElement))
+            return false;
+
+        var suggestion = text || this._selectedElement.textContent;
+        if (!suggestion)
+            return false;
+
+        this._suggestBoxDelegate.applySuggestion(suggestion, isIntermediateSuggestion);
+        return true;
+    },
+
+    /**
+     * @param {string=} text
+     */
+    acceptSuggestion: function(text)
+    {
+        var result = this._applySuggestion(text, false);
+        this.hide();
+        if (!result)
+            return false;
+
+        this._suggestBoxDelegate.acceptSuggestion();
+
+        return true;
+    },
+
+    /**
+     * @param {number} shift
+     * @param {boolean=} isCircular
+     * @return {boolean} is changed
+     */
+    _selectClosest: function(shift, isCircular)
+    {
+        if (!this._length)
+            return false;
+
+        var index = this._selectedIndex + shift;
+
+        if (isCircular)
+            index = (this._length + index) % this._length;
+        else
+            index = Number.constrain(index, 0, this._length - 1);
+
+        this._selectItem(index);
+        this._applySuggestion(undefined, true);
+        return true;
+    },
+
+    /**
+     * @param {AnchorBox} anchorBox
+     * @param {Array.<string>=} completions
+     * @param {number=} selectedIndex
+     * @param {boolean=} canShowForSingleItem
+     */
+    updateSuggestions: function(anchorBox, completions, selectedIndex, canShowForSingleItem)
+    {
+        if (this._suggestTimeout) {
+            clearTimeout(this._suggestTimeout);
+            delete this._suggestTimeout;
+        }
+        this._completionsReady(anchorBox, completions, selectedIndex, canShowForSingleItem);
+    },
+
+    _onItemMouseDown: function(text, event)
+    {
+        this.acceptSuggestion(text);
+        event.consume(true);
+    },
+
+    _createItemElement: function(prefix, text)
+    {
+        var element = document.createElement("div");
+        element.className = "suggest-box-content-item source-code";
+        element.tabIndex = -1;
+        if (prefix && prefix.length && !text.indexOf(prefix)) {
+            var prefixElement = element.createChild("span", "prefix");
+            prefixElement.textContent = prefix;
+            var suffixElement = element.createChild("span", "suffix");
+            suffixElement.textContent = text.substring(prefix.length);
+        } else {
+            var suffixElement = element.createChild("span", "suffix");
+            suffixElement.textContent = text;
+        }
+        element.addEventListener("mousedown", this._onItemMouseDown.bind(this, text), false);
+        return element;
+    },
+
+    /**
+     * @param {Array.<string>=} items
+     * @param {number=} selectedIndex
+     */
+    _updateItems: function(items, selectedIndex)
+    {
+        this._length = items.length;
+        this.contentElement.removeChildren();
+
+        var userEnteredText = this._suggestBoxDelegate.userEnteredText();
+        for (var i = 0; i < items.length; ++i) {
+            var item = items[i];
+            var currentItemElement = this._createItemElement(userEnteredText, item);
+            this.contentElement.appendChild(currentItemElement);
+        }
+
+        this._selectedElement = null;
+        if (typeof selectedIndex === "number")
+            this._selectItem(selectedIndex);
+    },
+
+    /**
+     * @param {number} index
+     */
+    _selectItem: function(index)
+    {
+        if (this._selectedElement)
+            this._selectedElement.classList.remove("selected");
+
+        this._selectedIndex = index;
+        this._selectedElement = this.contentElement.children[index];
+        this._selectedElement.classList.add("selected");
+
+        this._selectedElement.scrollIntoViewIfNeeded(false);
+    },
+
+    /**
+     * @param {Array.<string>=} completions
+     * @param {boolean=} canShowForSingleItem
+     */
+    _canShowBox: function(completions, canShowForSingleItem)
+    {
+        if (!completions || !completions.length)
+            return false;
+
+        if (completions.length > 1)
+            return true;
+
+        // Do not show a single suggestion if it is the same as user-entered prefix, even if allowed to show single-item suggest boxes.
+        return canShowForSingleItem && completions[0] !== this._suggestBoxDelegate.userEnteredText();
+    },
+
+    _rememberRowCountPerViewport: function()
+    {
+        if (!this.contentElement.firstChild)
+            return;
+
+        this._rowCountPerViewport = Math.floor(this.containerElement.offsetHeight / this.contentElement.firstChild.offsetHeight);
+    },
+
+    /**
+     * @param {AnchorBox} anchorBox
+     * @param {Array.<string>=} completions
+     * @param {number=} selectedIndex
+     * @param {boolean=} canShowForSingleItem
+     */
+    _completionsReady: function(anchorBox, completions, selectedIndex, canShowForSingleItem)
+    {
+        if (this._canShowBox(completions, canShowForSingleItem)) {
+            this._updateItems(completions, selectedIndex);
+            this._updateBoxPosition(anchorBox);
+            if (!this.visible)
+                this._bodyElement.appendChild(this._element);
+            this._rememberRowCountPerViewport();
+        } else
+            this.hide();
+    },
+
+    /**
+     * @return {boolean}
+     */
+    upKeyPressed: function()
+    {
+        return this._selectClosest(-1, true);
+    },
+
+    /**
+     * @return {boolean}
+     */
+    downKeyPressed: function()
+    {
+        return this._selectClosest(1, true);
+    },
+
+    /**
+     * @return {boolean}
+     */
+    pageUpKeyPressed: function()
+    {
+        return this._selectClosest(-this._rowCountPerViewport, false);
+    },
+
+    /**
+     * @return {boolean}
+     */
+    pageDownKeyPressed: function()
+    {
+        return this._selectClosest(this._rowCountPerViewport, false);
+    },
+
+    /**
+     * @return {boolean}
+     */
+    enterKeyPressed: function()
+    {
+        var hasSelectedItem = !!this._selectedElement;
+        this.acceptSuggestion();
+
+        // Report the event as non-handled if there is no selected item,
+        // to commit the input or handle it otherwise.
+        return hasSelectedItem;
+    },
+
+    /**
+     * @return {boolean}
+     */
+    tabKeyPressed: function()
+    {
+        return this.enterKeyPressed();
+    }
+}
index 2b5b2d5aa8ddf3c4d2330c4a0c64303712924c7b..6ef8dfddf3f216b072bc81d95ffd29321171b1c6 100644 (file)
@@ -30,6 +30,7 @@
 /**
  * @constructor
  * @extends WebInspector.Object
+ * @implements {WebInspector.SuggestBoxDelegate}
  * @param {function(Element, Range, boolean, function(Array.<string>, number=))} completions
  * @param {string=} stopCharacters
  */
@@ -51,6 +52,11 @@ WebInspector.TextPrompt.Events = {
 };
 
 WebInspector.TextPrompt.prototype = {
+    userEnteredText: function()
+    {
+        return this._userEnteredText;
+    },
+
     get proxyElement()
     {
         return this._proxyElement;
@@ -117,7 +123,7 @@ WebInspector.TextPrompt.prototype = {
         this._element.addEventListener("selectstart", this._boundSelectStart, false);
 
         if (typeof this._suggestBoxClassName === "string")
-            this._suggestBox = new WebInspector.TextPrompt.SuggestBox(this, this._element, this._suggestBoxClassName);
+            this._suggestBox = new WebInspector.SuggestBox(this, this._element, this._suggestBoxClassName);
 
         return this.proxyElement;
     },
@@ -520,7 +526,7 @@ WebInspector.TextPrompt.prototype = {
                 selection.addRange(finalSelectionRange);
             }
         } else
-            this.applySuggestion(completionText, completions.length > 1, originalWordPrefixRange);
+            this._applySuggestion(completionText, completions.length > 1, originalWordPrefixRange);
     },
 
     _completeCommonPrefix: function()
@@ -538,9 +544,20 @@ WebInspector.TextPrompt.prototype = {
     },
 
     /**
-     * @param {Range=} originalPrefixRange
+     * @param {string} completionText
+     * @param {boolean=} isIntermediateSuggestion
+     */
+    applySuggestion: function(completionText, isIntermediateSuggestion)
+    {
+        this._applySuggestion(completionText, isIntermediateSuggestion);
+    },
+
+    /**
+     * @param {string} completionText
+     * @param {boolean=} isIntermediateSuggestion
+     * @param {Range} originalPrefixRange
      */
-    applySuggestion: function(completionText, isIntermediateSuggestion, originalPrefixRange)
+    _applySuggestion: function(completionText, isIntermediateSuggestion, originalPrefixRange)
     {
         var wordPrefixLength;
         if (originalPrefixRange)
@@ -717,7 +734,7 @@ WebInspector.TextPrompt.prototype = {
     enterKeyPressed: function(event)
     {
         if (this.isSuggestBoxVisible())
-            return this._suggestBox.enterKeyPressed(event);
+            return this._suggestBox.enterKeyPressed();
 
         return false;
     },
@@ -725,7 +742,7 @@ WebInspector.TextPrompt.prototype = {
     upKeyPressed: function(event)
     {
         if (this.isSuggestBoxVisible())
-            return this._suggestBox.upKeyPressed(event);
+            return this._suggestBox.upKeyPressed();
 
         return false;
     },
@@ -733,7 +750,7 @@ WebInspector.TextPrompt.prototype = {
     downKeyPressed: function(event)
     {
         if (this.isSuggestBoxVisible())
-            return this._suggestBox.downKeyPressed(event);
+            return this._suggestBox.downKeyPressed();
 
         return false;
     },
@@ -741,7 +758,7 @@ WebInspector.TextPrompt.prototype = {
     pageUpKeyPressed: function(event)
     {
         if (this.isSuggestBoxVisible())
-            return this._suggestBox.pageUpKeyPressed(event);
+            return this._suggestBox.pageUpKeyPressed();
 
         return false;
     },
@@ -749,7 +766,7 @@ WebInspector.TextPrompt.prototype = {
     pageDownKeyPressed: function(event)
     {
         if (this.isSuggestBoxVisible())
-            return this._suggestBox.pageDownKeyPressed(event);
+            return this._suggestBox.pageDownKeyPressed();
 
         return false;
     },
@@ -926,329 +943,3 @@ WebInspector.TextPromptWithHistory.prototype = {
     __proto__: WebInspector.TextPrompt.prototype
 }
 
-/**
- * @constructor
- */
-WebInspector.TextPrompt.SuggestBox = function(textPrompt, inputElement, className)
-{
-    this._textPrompt = textPrompt;
-    this._inputElement = inputElement;
-    this._length = 0;
-    this._selectedIndex = -1;
-    this._selectedElement = null;
-    this._boundOnScroll = this._onscrollresize.bind(this, true);
-    this._boundOnResize = this._onscrollresize.bind(this, false);
-    window.addEventListener("scroll", this._boundOnScroll, true);
-    window.addEventListener("resize", this._boundOnResize, true);
-
-    this._bodyElement = inputElement.ownerDocument.body;
-    this._element = inputElement.ownerDocument.createElement("div");
-    this._element.className = "suggest-box " + (className || "");
-    this._element.addEventListener("mousedown", this._onboxmousedown.bind(this), true);
-    this.containerElement = this._element.createChild("div", "container");
-    this.contentElement = this.containerElement.createChild("div", "content");
-}
-
-WebInspector.TextPrompt.SuggestBox.prototype = {
-    get visible()
-    {
-        return !!this._element.parentElement;
-    },
-
-    get hasSelection()
-    {
-        return !!this._selectedElement;
-    },
-
-    _onscrollresize: function(isScroll, event)
-    {
-        if (isScroll && this._element.isAncestor(event.target) || !this.visible)
-            return;
-        this._updateBoxPositionWithExistingAnchor();
-    },
-
-    _updateBoxPositionWithExistingAnchor: function()
-    {
-        this._updateBoxPosition(this._anchorBox);
-    },
-
-    /**
-     * @param {AnchorBox} anchorBox
-     */
-    _updateBoxPosition: function(anchorBox)
-    {
-        // Measure the content element box.
-        this.contentElement.style.display = "inline-block";
-        document.body.appendChild(this.contentElement);
-        this.contentElement.positionAt(0, 0);
-        var contentWidth = this.contentElement.offsetWidth;
-        var contentHeight = this.contentElement.offsetHeight;
-        this.contentElement.style.display = "block";
-        this.containerElement.appendChild(this.contentElement);
-
-        // Lay out the suggest-box relative to the anchorBox.
-        this._anchorBox = anchorBox;
-        const spacer = 6;
-
-        const suggestBoxPaddingX = 21;
-        var maxWidth = document.body.offsetWidth - anchorBox.x - spacer;
-        var width = Math.min(contentWidth, maxWidth - suggestBoxPaddingX) + suggestBoxPaddingX;
-        var paddedWidth = contentWidth + suggestBoxPaddingX;
-        var boxX = anchorBox.x;
-        if (width < paddedWidth) {
-            // Shift the suggest box to the left to accommodate the content without trimming to the BODY edge.
-            maxWidth = document.body.offsetWidth - spacer;
-            width = Math.min(contentWidth, maxWidth - suggestBoxPaddingX) + suggestBoxPaddingX;
-            boxX = document.body.offsetWidth - width;
-        }
-
-        const suggestBoxPaddingY = 2;
-        var boxY;
-        var aboveHeight = anchorBox.y;
-        var underHeight = document.body.offsetHeight - anchorBox.y - anchorBox.height;
-        var maxHeight = Math.max(underHeight, aboveHeight) - spacer;
-        var height = Math.min(contentHeight, maxHeight - suggestBoxPaddingY) + suggestBoxPaddingY;
-        if (underHeight >= aboveHeight) {
-            // Locate the suggest box under the anchorBox.
-            boxY = anchorBox.y + anchorBox.height;
-            this._element.removeStyleClass("above-anchor");
-            this._element.addStyleClass("under-anchor");
-        } else {
-            // Locate the suggest box above the anchorBox.
-            boxY = anchorBox.y - height;
-            this._element.removeStyleClass("under-anchor");
-            this._element.addStyleClass("above-anchor");
-        }
-
-        this._element.positionAt(boxX, boxY);
-        this._element.style.width = width + "px";
-        this._element.style.height = height + "px";
-    },
-
-    _onboxmousedown: function(event)
-    {
-        event.preventDefault();
-    },
-
-    hide: function()
-    {
-        if (!this.visible)
-            return;
-
-        this._element.parentElement.removeChild(this._element);
-        delete this._selectedElement;
-    },
-
-    removeFromElement: function()
-    {
-        window.removeEventListener("scroll", this._boundOnScroll, true);
-        window.removeEventListener("resize", this._boundOnResize, true);
-        this.hide();
-    },
-
-    /**
-     * @param {string=} text
-     * @param {boolean=} isIntermediateSuggestion
-     */
-    _applySuggestion: function(text, isIntermediateSuggestion)
-    {
-        if (!this.visible || !(text || this._selectedElement))
-            return false;
-
-        var suggestion = text || this._selectedElement.textContent;
-        if (!suggestion)
-            return false;
-
-        this._textPrompt.applySuggestion(suggestion, isIntermediateSuggestion);
-        return true;
-    },
-
-    /**
-     * @param {string=} text
-     */
-    acceptSuggestion: function(text)
-    {
-        var result = this._applySuggestion(text, false);
-        this.hide();
-        if (!result)
-            return false;
-
-        this._textPrompt.acceptSuggestion();
-
-        return true;
-    },
-
-    /**
-     * @param {number} shift
-     * @param {boolean=} isCircular
-     * @return {boolean} is changed
-     */
-    _selectClosest: function(shift, isCircular)
-    {
-        if (!this._length)
-            return false;
-
-        var index = this._selectedIndex + shift;
-
-        if (isCircular)
-            index = (this._length + index) % this._length;
-        else
-            index = Number.constrain(index, 0, this._length - 1);
-
-        this._selectItem(index);
-        this._applySuggestion(undefined, true);
-        return true;
-    },
-
-    /**
-     * @param {AnchorBox} anchorBox
-     * @param {Array.<string>=} completions
-     * @param {number=} selectedIndex
-     * @param {boolean=} canShowForSingleItem
-     */
-    updateSuggestions: function(anchorBox, completions, selectedIndex, canShowForSingleItem)
-    {
-        if (this._suggestTimeout) {
-            clearTimeout(this._suggestTimeout);
-            delete this._suggestTimeout;
-        }
-        this._completionsReady(anchorBox, completions, selectedIndex, canShowForSingleItem);
-    },
-
-    _onItemMouseDown: function(text, event)
-    {
-        this.acceptSuggestion(text);
-        event.consume(true);
-    },
-
-    _createItemElement: function(prefix, text)
-    {
-        var element = document.createElement("div");
-        element.className = "suggest-box-content-item source-code";
-        element.tabIndex = -1;
-        if (prefix && prefix.length && !text.indexOf(prefix)) {
-            var prefixElement = element.createChild("span", "prefix");
-            prefixElement.textContent = prefix;
-            var suffixElement = element.createChild("span", "suffix");
-            suffixElement.textContent = text.substring(prefix.length);
-        } else {
-            var suffixElement = element.createChild("span", "suffix");
-            suffixElement.textContent = text;
-        }
-        element.addEventListener("mousedown", this._onItemMouseDown.bind(this, text), false);
-        return element;
-    },
-
-    /**
-     * @param {Array.<string>=} items
-     * @param {number=} selectedIndex
-     */
-    _updateItems: function(items, selectedIndex)
-    {
-        this._length = items.length;
-        this.contentElement.removeChildren();
-
-        var userEnteredText = this._textPrompt._userEnteredText;
-        for (var i = 0; i < items.length; ++i) {
-            var item = items[i];
-            var currentItemElement = this._createItemElement(userEnteredText, item);
-            this.contentElement.appendChild(currentItemElement);
-        }
-
-        this._selectedElement = null;
-        if (typeof selectedIndex === "number")
-            this._selectItem(selectedIndex);
-    },
-
-    /**
-     * @param {number} index
-     */
-    _selectItem: function(index)
-    {
-        if (this._selectedElement)
-            this._selectedElement.classList.remove("selected");
-
-        this._selectedIndex = index;
-        this._selectedElement = this.contentElement.children[index];
-        this._selectedElement.classList.add("selected");
-
-        this._selectedElement.scrollIntoViewIfNeeded(false);
-    },
-
-    /**
-     * @param {Array.<string>=} completions
-     * @param {boolean=} canShowForSingleItem
-     */
-    _canShowBox: function(completions, canShowForSingleItem)
-    {
-        if (!completions || !completions.length)
-            return false;
-
-        if (completions.length > 1)
-            return true;
-
-        // Do not show a single suggestion if it is the same as user-entered prefix, even if allowed to show single-item suggest boxes.
-        return canShowForSingleItem && completions[0] !== this._textPrompt._userEnteredText;
-    },
-
-    _rememberRowCountPerViewport: function()
-    {
-        if (!this.contentElement.firstChild)
-            return;
-
-        this._rowCountPerViewport = Math.floor(this.containerElement.offsetHeight / this.contentElement.firstChild.offsetHeight);
-    },
-
-    /**
-     * @param {AnchorBox} anchorBox
-     * @param {Array.<string>=} completions
-     * @param {number=} selectedIndex
-     * @param {boolean=} canShowForSingleItem
-     */
-    _completionsReady: function(anchorBox, completions, selectedIndex, canShowForSingleItem)
-    {
-        if (this._canShowBox(completions, canShowForSingleItem)) {
-            this._updateItems(completions, selectedIndex);
-            this._updateBoxPosition(anchorBox);
-            if (!this.visible)
-                this._bodyElement.appendChild(this._element);
-            this._rememberRowCountPerViewport();
-        } else
-            this.hide();
-    },
-
-    upKeyPressed: function(event)
-    {
-        return this._selectClosest(-1, true);
-    },
-
-    downKeyPressed: function(event)
-    {
-        return this._selectClosest(1, true);
-    },
-
-    pageUpKeyPressed: function(event)
-    {
-        return this._selectClosest(-this._rowCountPerViewport, false);
-    },
-
-    pageDownKeyPressed: function(event)
-    {
-        return this._selectClosest(this._rowCountPerViewport, false);
-    },
-
-    enterKeyPressed: function(event)
-    {
-        var hasSelectedItem = !!this._selectedElement;
-        this.acceptSuggestion();
-
-        // Report the event as non-handled if there is no selected item,
-        // to commit the input or handle it otherwise.
-        return hasSelectedItem;
-    },
-
-    tabKeyPressed: function(event)
-    {
-        return this.enterKeyPressed(event);
-    }
-}
index ff3591daf4b0441a2e6559e5fa3661549c49dcb1..c030568d01a392b8b2b0d2f8accc598392fb78b3 100644 (file)
     <file>StyleSheetOutlineDialog.js</file>
     <file>StylesSourceMapping.js</file>
     <file>StylesSidebarPane.js</file>
+    <file>SuggestBox.js</file>
     <file>TabbedEditorContainer.js</file>
     <file>TabbedPane.js</file>
     <file>TestController.js</file>
index 35690f54f230411277ee6e470f157a59b0264457..c4649562e9fc321535a64bfdfe411a1787cc74e6 100644 (file)
@@ -56,6 +56,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     <script type="text/javascript" src="ContextMenu.js"></script>
     <script type="text/javascript" src="SoftContextMenu.js"></script>
     <script type="text/javascript" src="KeyboardShortcut.js"></script>
+    <script type="text/javascript" src="SuggestBox.js"></script>
     <script type="text/javascript" src="TextPrompt.js"></script>
     <script type="text/javascript" src="Popover.js"></script>
     <script type="text/javascript" src="Placard.js"></script>