home *** CD-ROM | disk | FTP | other *** search
/ Computer Active Guide 2009 July / CAG7.ISO / Internetas / SafariSetup.exe / AppleApplicationSupport.msi / WebKit.resources_inspector_TextPrompt.js < prev    next >
Encoding:
Text File  |  2010-06-03  |  16.5 KB  |  485 lines

  1. /*
  2.  * Copyright (C) 2008 Apple Inc.  All rights reserved.
  3.  *
  4.  * Redistribution and use in source and binary forms, with or without
  5.  * modification, are permitted provided that the following conditions
  6.  * are met:
  7.  *
  8.  * 1.  Redistributions of source code must retain the above copyright
  9.  *     notice, this list of conditions and the following disclaimer.
  10.  * 2.  Redistributions in binary form must reproduce the above copyright
  11.  *     notice, this list of conditions and the following disclaimer in the
  12.  *     documentation and/or other materials provided with the distribution.
  13.  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
  14.  *     its contributors may be used to endorse or promote products derived
  15.  *     from this software without specific prior written permission.
  16.  *
  17.  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
  18.  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  19.  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  20.  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
  21.  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  22.  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  23.  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  24.  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  25.  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  26.  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  27.  */
  28.  
  29. WebInspector.TextPrompt = function(element, completions, stopCharacters)
  30. {
  31.     this.element = element;
  32.     this.element.addStyleClass("text-prompt");
  33.     this.completions = completions;
  34.     this.completionStopCharacters = stopCharacters;
  35.     this.history = [];
  36.     this.historyOffset = 0;
  37.     this.element.addEventListener("keydown", this._onKeyDown.bind(this), true);
  38. }
  39.  
  40. WebInspector.TextPrompt.prototype = {
  41.     get text()
  42.     {
  43.         return this.element.textContent;
  44.     },
  45.  
  46.     set text(x)
  47.     {
  48.         if (!x) {
  49.             // Append a break element instead of setting textContent to make sure the selection is inside the prompt.
  50.             this.element.removeChildren();
  51.             this.element.appendChild(document.createElement("br"));
  52.         } else
  53.             this.element.textContent = x;
  54.  
  55.         this.moveCaretToEndOfPrompt();
  56.     },
  57.  
  58.     _onKeyDown: function(event)
  59.     {
  60.         function defaultAction()
  61.         {
  62.             this.clearAutoComplete();
  63.             this.autoCompleteSoon();
  64.         }
  65.  
  66.         var handled = false;
  67.         switch (event.keyIdentifier) {
  68.             case "Up":
  69.                 this._upKeyPressed(event);
  70.                 break;
  71.             case "Down":
  72.                 this._downKeyPressed(event);
  73.                 break;
  74.             case "U+0009": // Tab
  75.                 this._tabKeyPressed(event);
  76.                 break;
  77.             case "Right":
  78.             case "End":
  79.                 if (!this.acceptAutoComplete())
  80.                     this.autoCompleteSoon();
  81.                 break;
  82.             case "Alt":
  83.             case "Meta":
  84.             case "Shift":
  85.             case "Control":
  86.                 break;
  87.             case "U+0050": // Ctrl+P = Previous
  88.                 if (WebInspector.isMac() && event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey) {
  89.                     handled = true;
  90.                     this._moveBackInHistory();
  91.                     break;
  92.                 }
  93.                 defaultAction.call(this);
  94.                 break;
  95.             case "U+004E": // Ctrl+N = Next
  96.                 if (WebInspector.isMac() && event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey) {
  97.                     handled = true;
  98.                     this._moveForwardInHistory();
  99.                     break;
  100.                 }
  101.                 defaultAction.call(this);
  102.                 break;
  103.             case "U+0041": // Ctrl+A = Move caret to the start of prompt on non-Mac.
  104.                 if (!WebInspector.isMac() && event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey) {
  105.                     handled = true;
  106.                     this._moveCaretToStartOfPrompt();
  107.                     break;
  108.                 }
  109.                 defaultAction.call(this);
  110.                 break;
  111.             case "U+0045": // Ctrl+E = Move caret to the end of prompt on non-Mac.
  112.                 if (!WebInspector.isMac() && event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey) {
  113.                     handled = true;
  114.                     this.moveCaretToEndOfPrompt();
  115.                     break;
  116.                 }
  117.                 defaultAction.call(this);
  118.                 break;
  119.             default:
  120.                 defaultAction.call(this);
  121.                 break;
  122.         }
  123.  
  124.         if (handled) {
  125.             event.preventDefault();
  126.             event.stopPropagation();
  127.         }
  128.     },
  129.  
  130.     acceptAutoComplete: function()
  131.     {
  132.         if (!this.autoCompleteElement || !this.autoCompleteElement.parentNode)
  133.             return false;
  134.  
  135.         var text = this.autoCompleteElement.textContent;
  136.         var textNode = document.createTextNode(text);
  137.         this.autoCompleteElement.parentNode.replaceChild(textNode, this.autoCompleteElement);
  138.         delete this.autoCompleteElement;
  139.  
  140.         var finalSelectionRange = document.createRange();
  141.         finalSelectionRange.setStart(textNode, text.length);
  142.         finalSelectionRange.setEnd(textNode, text.length);
  143.  
  144.         var selection = window.getSelection();
  145.         selection.removeAllRanges();
  146.         selection.addRange(finalSelectionRange);
  147.  
  148.         return true;
  149.     },
  150.  
  151.     clearAutoComplete: function(includeTimeout)
  152.     {
  153.         if (includeTimeout && "_completeTimeout" in this) {
  154.             clearTimeout(this._completeTimeout);
  155.             delete this._completeTimeout;
  156.         }
  157.  
  158.         if (!this.autoCompleteElement)
  159.             return;
  160.  
  161.         if (this.autoCompleteElement.parentNode)
  162.             this.autoCompleteElement.parentNode.removeChild(this.autoCompleteElement);
  163.         delete this.autoCompleteElement;
  164.  
  165.         if (!this._userEnteredRange || !this._userEnteredText)
  166.             return;
  167.  
  168.         this._userEnteredRange.deleteContents();
  169.         this.element.pruneEmptyTextNodes();
  170.  
  171.         var userTextNode = document.createTextNode(this._userEnteredText);
  172.         this._userEnteredRange.insertNode(userTextNode);
  173.  
  174.         var selectionRange = document.createRange();
  175.         selectionRange.setStart(userTextNode, this._userEnteredText.length);
  176.         selectionRange.setEnd(userTextNode, this._userEnteredText.length);
  177.  
  178.         var selection = window.getSelection();
  179.         selection.removeAllRanges();
  180.         selection.addRange(selectionRange);
  181.  
  182.         delete this._userEnteredRange;
  183.         delete this._userEnteredText;
  184.     },
  185.  
  186.     autoCompleteSoon: function()
  187.     {
  188.         if (!("_completeTimeout" in this))
  189.             this._completeTimeout = setTimeout(this.complete.bind(this, true), 250);
  190.     },
  191.  
  192.     complete: function(auto, reverse)
  193.     {
  194.         this.clearAutoComplete(true);
  195.         var selection = window.getSelection();
  196.         if (!selection.rangeCount)
  197.             return;
  198.  
  199.         var selectionRange = selection.getRangeAt(0);
  200.         if (!selectionRange.commonAncestorContainer.isDescendant(this.element))
  201.             return;
  202.         if (auto && !this.isCaretAtEndOfPrompt())
  203.             return;
  204.         var wordPrefixRange = selectionRange.startContainer.rangeOfWord(selectionRange.startOffset, this.completionStopCharacters, this.element, "backward");
  205.         this.completions(wordPrefixRange, auto, this._completionsReady.bind(this, selection, auto, wordPrefixRange, reverse));
  206.     },
  207.  
  208.     _completionsReady: function(selection, auto, originalWordPrefixRange, reverse, completions)
  209.     {
  210.         if (!completions || !completions.length)
  211.             return;
  212.  
  213.         var selectionRange = selection.getRangeAt(0);
  214.  
  215.         var fullWordRange = document.createRange();
  216.         fullWordRange.setStart(originalWordPrefixRange.startContainer, originalWordPrefixRange.startOffset);
  217.         fullWordRange.setEnd(selectionRange.endContainer, selectionRange.endOffset);
  218.  
  219.         if (originalWordPrefixRange.toString() + selectionRange.toString() != fullWordRange.toString())
  220.             return;
  221.  
  222.         var wordPrefixLength = originalWordPrefixRange.toString().length;
  223.  
  224.         if (auto)
  225.             var completionText = completions[0];
  226.         else {
  227.             if (completions.length === 1) {
  228.                 var completionText = completions[0];
  229.                 wordPrefixLength = completionText.length;
  230.             } else {
  231.                 var commonPrefix = completions[0];
  232.                 for (var i = 0; i < completions.length; ++i) {
  233.                     var completion = completions[i];
  234.                     var lastIndex = Math.min(commonPrefix.length, completion.length);
  235.                     for (var j = wordPrefixLength; j < lastIndex; ++j) {
  236.                         if (commonPrefix[j] !== completion[j]) {
  237.                             commonPrefix = commonPrefix.substr(0, j);
  238.                             break;
  239.                         }
  240.                     }
  241.                 }
  242.                 wordPrefixLength = commonPrefix.length;
  243.  
  244.                 if (selection.isCollapsed)
  245.                     var completionText = completions[1];
  246.                 else {
  247.                     var currentText = fullWordRange.toString();
  248.  
  249.                     var foundIndex = null;
  250.                     for (var i = 0; i < completions.length; ++i) {
  251.                         if (completions[i] === currentText)
  252.                             foundIndex = i;
  253.                     }
  254.  
  255.                     var nextIndex = foundIndex + (reverse ? -1 : 1);
  256.                     if (foundIndex === null || nextIndex >= completions.length)
  257.                         var completionText = completions[0];
  258.                     else if (nextIndex < 0)
  259.                         var completionText = completions[completions.length - 1];
  260.                     else
  261.                         var completionText = completions[nextIndex];
  262.                 }
  263.             }
  264.         }
  265.  
  266.         this._userEnteredRange = fullWordRange;
  267.         this._userEnteredText = fullWordRange.toString();
  268.  
  269.         fullWordRange.deleteContents();
  270.         this.element.pruneEmptyTextNodes();
  271.  
  272.         var finalSelectionRange = document.createRange();
  273.  
  274.         if (auto) {
  275.             var prefixText = completionText.substring(0, wordPrefixLength);
  276.             var suffixText = completionText.substring(wordPrefixLength);
  277.  
  278.             var prefixTextNode = document.createTextNode(prefixText);
  279.             fullWordRange.insertNode(prefixTextNode);
  280.  
  281.             this.autoCompleteElement = document.createElement("span");
  282.             this.autoCompleteElement.className = "auto-complete-text";
  283.             this.autoCompleteElement.textContent = suffixText;
  284.  
  285.             prefixTextNode.parentNode.insertBefore(this.autoCompleteElement, prefixTextNode.nextSibling);
  286.  
  287.             finalSelectionRange.setStart(prefixTextNode, wordPrefixLength);
  288.             finalSelectionRange.setEnd(prefixTextNode, wordPrefixLength);
  289.         } else {
  290.             var completionTextNode = document.createTextNode(completionText);
  291.             fullWordRange.insertNode(completionTextNode);
  292.  
  293.             if (completions.length > 1)
  294.                 finalSelectionRange.setStart(completionTextNode, wordPrefixLength);
  295.             else
  296.                 finalSelectionRange.setStart(completionTextNode, completionText.length);
  297.  
  298.             finalSelectionRange.setEnd(completionTextNode, completionText.length);
  299.         }
  300.  
  301.         selection.removeAllRanges();
  302.         selection.addRange(finalSelectionRange);
  303.     },
  304.  
  305.     isCaretInsidePrompt: function()
  306.     {
  307.         return this.element.isInsertionCaretInside();
  308.     },
  309.  
  310.     isCaretAtEndOfPrompt: function()
  311.     {
  312.         var selection = window.getSelection();
  313.         if (!selection.rangeCount || !selection.isCollapsed)
  314.             return false;
  315.  
  316.         var selectionRange = selection.getRangeAt(0);
  317.         var node = selectionRange.startContainer;
  318.         if (node !== this.element && !node.isDescendant(this.element))
  319.             return false;
  320.  
  321.         if (node.nodeType === Node.TEXT_NODE && selectionRange.startOffset < node.nodeValue.length)
  322.             return false;
  323.  
  324.         var foundNextText = false;
  325.         while (node) {
  326.             if (node.nodeType === Node.TEXT_NODE && node.nodeValue.length) {
  327.                 if (foundNextText)
  328.                     return false;
  329.                 foundNextText = true;
  330.             }
  331.  
  332.             node = node.traverseNextNode(this.element);
  333.         }
  334.  
  335.         return true;
  336.     },
  337.  
  338.     isCaretOnFirstLine: function()
  339.     {
  340.         var selection = window.getSelection();
  341.         var focusNode = selection.focusNode;
  342.         if (!focusNode || focusNode.nodeType !== Node.TEXT_NODE || focusNode.parentNode !== this.element)
  343.             return true;
  344.  
  345.         if (focusNode.textContent.substring(0, selection.focusOffset).indexOf("\n") !== -1)
  346.             return false;
  347.         focusNode = focusNode.previousSibling;
  348.  
  349.         while (focusNode) {
  350.             if (focusNode.nodeType !== Node.TEXT_NODE)
  351.                 return true;
  352.             if (focusNode.textContent.indexOf("\n") !== -1)
  353.                 return false;
  354.             focusNode = focusNode.previousSibling;
  355.         }
  356.  
  357.         return true;
  358.     },
  359.  
  360.     isCaretOnLastLine: function()
  361.     {
  362.         var selection = window.getSelection();
  363.         var focusNode = selection.focusNode;
  364.         if (!focusNode || focusNode.nodeType !== Node.TEXT_NODE || focusNode.parentNode !== this.element)
  365.             return true;
  366.  
  367.         if (focusNode.textContent.substring(selection.focusOffset).indexOf("\n") !== -1)
  368.             return false;
  369.         focusNode = focusNode.nextSibling;
  370.  
  371.         while (focusNode) {
  372.             if (focusNode.nodeType !== Node.TEXT_NODE)
  373.                 return true;
  374.             if (focusNode.textContent.indexOf("\n") !== -1)
  375.                 return false;
  376.             focusNode = focusNode.nextSibling;
  377.         }
  378.  
  379.         return true;
  380.     },
  381.  
  382.     _moveCaretToStartOfPrompt: function()
  383.     {
  384.         var selection = window.getSelection();
  385.         var selectionRange = document.createRange();
  386.  
  387.         selectionRange.setStart(this.element, 0);
  388.         selectionRange.setEnd(this.element, 0);
  389.  
  390.         selection.removeAllRanges();
  391.         selection.addRange(selectionRange);
  392.     },
  393.  
  394.     moveCaretToEndOfPrompt: function()
  395.     {
  396.         var selection = window.getSelection();
  397.         var selectionRange = document.createRange();
  398.  
  399.         var offset = this.element.childNodes.length;
  400.         selectionRange.setStart(this.element, offset);
  401.         selectionRange.setEnd(this.element, offset);
  402.  
  403.         selection.removeAllRanges();
  404.         selection.addRange(selectionRange);
  405.     },
  406.  
  407.     _tabKeyPressed: function(event)
  408.     {
  409.         event.preventDefault();
  410.         event.stopPropagation();
  411.  
  412.         this.complete(false, event.shiftKey);
  413.     },
  414.  
  415.     _upKeyPressed: function(event)
  416.     {
  417.         if (!this.isCaretOnFirstLine())
  418.             return;
  419.  
  420.         event.preventDefault();
  421.         event.stopPropagation();
  422.  
  423.         this._moveBackInHistory();
  424.     },
  425.  
  426.     _downKeyPressed: function(event)
  427.     {
  428.         if (!this.isCaretOnLastLine())
  429.             return;
  430.  
  431.         event.preventDefault();
  432.         event.stopPropagation();
  433.  
  434.         this._moveForwardInHistory();
  435.     },
  436.  
  437.     _moveBackInHistory: function()
  438.     {
  439.         if (this.historyOffset == this.history.length)
  440.             return;
  441.  
  442.         this.clearAutoComplete(true);
  443.  
  444.         if (this.historyOffset === 0)
  445.             this.tempSavedCommand = this.text;
  446.  
  447.         ++this.historyOffset;
  448.         this.text = this.history[this.history.length - this.historyOffset];
  449.  
  450.         this.element.scrollIntoViewIfNeeded();
  451.         var firstNewlineIndex = this.text.indexOf("\n");
  452.         if (firstNewlineIndex === -1)
  453.             this.moveCaretToEndOfPrompt();
  454.         else {
  455.             var selection = window.getSelection();
  456.             var selectionRange = document.createRange();
  457.  
  458.             selectionRange.setStart(this.element.firstChild, firstNewlineIndex);
  459.             selectionRange.setEnd(this.element.firstChild, firstNewlineIndex);
  460.  
  461.             selection.removeAllRanges();
  462.             selection.addRange(selectionRange);
  463.         }
  464.     },
  465.  
  466.     _moveForwardInHistory: function()
  467.     {
  468.         if (this.historyOffset === 0)
  469.             return;
  470.  
  471.         this.clearAutoComplete(true);
  472.  
  473.         --this.historyOffset;
  474.  
  475.         if (this.historyOffset === 0) {
  476.             this.text = this.tempSavedCommand;
  477.             delete this.tempSavedCommand;
  478.             return;
  479.         }
  480.  
  481.         this.text = this.history[this.history.length - this.historyOffset];
  482.         this.element.scrollIntoViewIfNeeded();
  483.     }
  484. }
  485.