diff --git a/lib/ace/css/editor.css b/lib/ace/css/editor.css index acea6f89..ea3bb689 100644 --- a/lib/ace/css/editor.css +++ b/lib/ace/css/editor.css @@ -5,6 +5,10 @@ font-size: 12px; line-height: normal; color: black; + -ms-user-select: none; + -moz-user-select: none; + -webkit-user-select: none; + user-select: none; } .ace_scroller { @@ -23,6 +27,25 @@ cursor: text; } +.ace_dragging, .ace_dragging * { + cursor: default !important; +} + +.ace_dragging .ace_scroller:before{ + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + content: ''; + background: rgba(0, 0, 0, 0.01); + z-index: 1000; +} + +.ace_selecting, .ace_selecting * { + cursor: text !important; +} + .ace_gutter { position: absolute; overflow : hidden; @@ -258,10 +281,6 @@ background-position: center center, top left; } -.ace_editor.ace_dragging .ace_content { - cursor: move; -} - .ace_gutter-tooltip { background-color: #FFF; background-image: -webkit-linear-gradient(top, transparent, rgba(0, 0, 0, 0.1)); diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index 41e0fa15..98c1e940 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -1327,7 +1327,7 @@ var EditSession = function(text, mode) { } } - this.insert(toRange.start, text); + toRange.end = this.insert(toRange.start, text); if (folds.length) { var oldStart = fromRange.start; var newStart = toRange.start; diff --git a/lib/ace/editor.js b/lib/ace/editor.js index e22875d5..c098e792 100644 --- a/lib/ace/editor.js +++ b/lib/ace/editor.js @@ -1559,8 +1559,8 @@ var Editor = function(renderer, session) { * @returns {Range} The new range where the text was moved to. * @related EditSession.moveText **/ - this.moveText = function(range, toPosition) { - return this.session.moveText(range, toPosition); + this.moveText = function(range, toPosition, copy) { + return this.session.moveText(range, toPosition, copy); }; /** @@ -2403,6 +2403,7 @@ config.defineOptions(Editor.prototype, "editor", { scrollSpeed: "$mouseHandler", dragDelay: "$mouseHandler", + dragEnabled: "$mouseHandler", focusTimout: "$mouseHandler", firstLineNumber: "session", diff --git a/lib/ace/lib/event.js b/lib/ace/lib/event.js index 42b77eaf..89d2756a 100644 --- a/lib/ace/lib/event.js +++ b/lib/ace/lib/event.js @@ -41,7 +41,7 @@ exports.addListener = function(elem, type, callback) { } if (elem.attachEvent) { var wrapper = function() { - callback(window.event); + callback.call(elem, window.event); }; callback._wrapper = wrapper; elem.attachEvent("on" + type, wrapper); @@ -99,44 +99,22 @@ exports.getButton = function(e) { } }; -if (document.documentElement.setCapture) { - exports.capture = function(el, eventHandler, releaseCaptureHandler) { - var called = false; - function onReleaseCapture(e) { - eventHandler(e); +exports.capture = function(el, eventHandler, releaseCaptureHandler) { + function onMouseUp(e) { + eventHandler && eventHandler(e); + releaseCaptureHandler && releaseCaptureHandler(e); - if (!called) { - called = true; - releaseCaptureHandler(e); - } + exports.removeListener(document, "mousemove", eventHandler, true); + exports.removeListener(document, "mouseup", onMouseUp, true); + exports.removeListener(document, "dragstart", onMouseUp, true); - exports.removeListener(el, "mousemove", eventHandler); - exports.removeListener(el, "mouseup", onReleaseCapture); - exports.removeListener(el, "losecapture", onReleaseCapture); + exports.stopPropagation(e); + } - el.releaseCapture(); - } - - exports.addListener(el, "mousemove", eventHandler); - exports.addListener(el, "mouseup", onReleaseCapture); - exports.addListener(el, "losecapture", onReleaseCapture); - el.setCapture(); - }; -} -else { - exports.capture = function(el, eventHandler, releaseCaptureHandler) { - function onMouseUp(e) { - eventHandler && eventHandler(e); - releaseCaptureHandler && releaseCaptureHandler(e); - - document.removeEventListener("mousemove", eventHandler, true); - document.removeEventListener("mouseup", onMouseUp, true); - } - - document.addEventListener("mousemove", eventHandler, true); - document.addEventListener("mouseup", onMouseUp, true); - }; -} + exports.addListener(document, "mousemove", eventHandler, true); + exports.addListener(document, "mouseup", onMouseUp, true); + exports.addListener(document, "dragstart", onMouseUp, true); +}; exports.addMouseWheelListener = function(el, callback) { if ("onmousewheel" in el) { @@ -183,21 +161,22 @@ exports.addMultiMouseDownListener = function(el, timeouts, eventHandler, callbac exports.addListener(el, "mousedown", function(e) { if (exports.getButton(e) != 0) { clicks = 0; + } else if (e.detail > 1) { + clicks++; + if (clicks > 4) + clicks = 1; } else { - var isNewClick = Math.abs(e.clientX - startX) > 5 || Math.abs(e.clientY - startY) > 5; - - if (!timer || isNewClick) - clicks = 0; - - clicks += 1; - - if (timer) - clearTimeout(timer) - timer = setTimeout(function() {timer = null}, timeouts[clicks - 1] || 600); + clicks = 1; } - if (clicks == 1) { - startX = e.clientX; - startY = e.clientY; + if (useragent.isIE) { + var isNewClick = Math.abs(e.clientX - startX) > 5 || Math.abs(e.clientY - startY) > 5; + if (isNewClick) { + clicks = 1; + } + if (clicks == 1) { + startX = e.clientX; + startY = e.clientY; + } } eventHandler[callbackName]("mousedown", e); diff --git a/lib/ace/mouse/default_gutter_handler.js b/lib/ace/mouse/default_gutter_handler.js index bf3da956..9437d2e4 100644 --- a/lib/ace/mouse/default_gutter_handler.js +++ b/lib/ace/mouse/default_gutter_handler.js @@ -57,7 +57,8 @@ function GutterHandler(mouseHandler) { } mouseHandler.$clickSelection = editor.selection.getLineRange(row); } - mouseHandler.captureMouse(e, "selectByLines"); + mouseHandler.setState("selectByLines"); + mouseHandler.captureMouse(e); return e.preventDefault(); }); diff --git a/lib/ace/mouse/default_handlers.js b/lib/ace/mouse/default_handlers.js index 8529c0fc..0c0da34c 100644 --- a/lib/ace/mouse/default_handlers.js +++ b/lib/ace/mouse/default_handlers.js @@ -32,6 +32,7 @@ define(function(require, exports, module) { "use strict"; var dom = require("../lib/dom"); +var event = require("../lib/event"); var useragent = require("../lib/useragent"); var DRAG_OFFSET = 0; // pixels @@ -46,8 +47,8 @@ function DefaultHandlers(mouseHandler) { editor.setDefaultHandler("quadclick", this.onQuadClick.bind(mouseHandler)); editor.setDefaultHandler("mousewheel", this.onMouseWheel.bind(mouseHandler)); - var exports = ["select", "startSelect", "drag", "dragEnd", "dragWait", - "dragWaitEnd", "startDrag", "focusWait"]; + var exports = ["select", "startSelect", "selectEnd", "selectAllEnd", "selectByWordsEnd", + "selectByLinesEnd", "dragWait", "dragWaitEnd", "focusWait"]; exports.forEach(function(x) { mouseHandler[x] = this[x]; @@ -85,9 +86,10 @@ function DefaultHandlers(mouseHandler) { if (inSelection && !editor.isFocused()) { editor.focus(); if (this.$focusTimout && !this.$clickSelection && !editor.inMultiSelectMode) { + this.mousedownEvent.time = (new Date()).getTime(); this.setState("focusWait"); this.captureMouse(ev); - return ev.preventDefault(); + return; } } @@ -97,22 +99,29 @@ function DefaultHandlers(mouseHandler) { this.startSelect(pos); } else if (inSelection) { this.mousedownEvent.time = (new Date()).getTime(); - this.setState("dragWait"); + this.startSelect(pos); } - this.captureMouse(ev); return ev.preventDefault(); }; this.startSelect = function(pos) { pos = pos || this.editor.renderer.screenToTextCoordinates(this.x, this.y); - if (this.mousedownEvent.getShiftKey()) { - this.editor.selection.selectToPosition(pos); - } - else if (!this.$clickSelection) { - this.editor.moveCursorToPosition(pos); - this.editor.selection.clearSelection(); + var editor = this.editor; + // allow double/triple click handlers to change selection + setTimeout(function(){ + if (this.mousedownEvent.getShiftKey()) { + editor.selection.selectToPosition(pos); + } + else if (!this.$clickSelection) { + editor.moveCursorToPosition(pos); + editor.selection.clearSelection(); + } + }.bind(this), 0); + if (editor.container.setCapture) { + editor.container.setCapture(); } + editor.setStyle("ace_selecting"); this.setState("select"); }; @@ -171,32 +180,14 @@ function DefaultHandlers(mouseHandler) { editor.renderer.scrollCursorIntoView(); }; - this.startDrag = function() { - var editor = this.editor; - this.setState("drag"); - this.dragRange = editor.getSelectionRange(); - var style = editor.getSelectionStyle(); - this.dragSelectionMarker = editor.session.addMarker(this.dragRange, "ace_selection", style); - editor.clearSelection(); - dom.addCssClass(editor.container, "ace_dragging"); - if (!this.$dragKeybinding) { - this.$dragKeybinding = { - handleKeyboard: function(data, hashId, keyString, keyCode) { - if (keyString == "esc") - return {command: this.command}; - }, - command: { - exec: function(editor) { - var self = editor.$mouseHandler; - self.dragCursor = null; - self.dragEnd(); - self.startSelect(); - } - } - } + this.selectEnd = + this.selectAllEnd = + this.selectByWordsEnd = + this.selectByLinesEnd = function() { + this.editor.unsetStyle("ace_selecting"); + if (this.editor.container.releaseCapture) { + this.editor.container.releaseCapture(); } - - editor.keyBinding.addKeyboardHandler(this.$dragKeybinding); }; this.focusWait = function() { @@ -207,59 +198,6 @@ function DefaultHandlers(mouseHandler) { this.startSelect(this.mousedownEvent.getDocumentPosition()); }; - this.dragWait = function(e) { - var distance = calcDistance(this.mousedownEvent.x, this.mousedownEvent.y, this.x, this.y); - var time = (new Date()).getTime(); - var editor = this.editor; - - if (distance > DRAG_OFFSET) { - this.startSelect(this.mousedownEvent.getDocumentPosition()); - } else if (time - this.mousedownEvent.time > editor.$mouseHandler.$dragDelay) { - this.startDrag(); - } - }; - - this.dragWaitEnd = function(e) { - this.mousedownEvent.domEvent = e; - this.startSelect(); - }; - - this.drag = function() { - var editor = this.editor; - this.dragCursor = editor.renderer.screenToTextCoordinates(this.x, this.y); - editor.moveCursorToPosition(this.dragCursor); - editor.renderer.scrollCursorIntoView(); - }; - - this.dragEnd = function(e) { - var editor = this.editor; - var dragCursor = this.dragCursor; - var dragRange = this.dragRange; - dom.removeCssClass(editor.container, "ace_dragging"); - editor.session.removeMarker(this.dragSelectionMarker); - editor.keyBinding.removeKeyboardHandler(this.$dragKeybinding); - - if (!dragCursor) - return; - - editor.clearSelection(); - if (e && (e.ctrlKey || e.altKey)) { - var session = editor.session; - var newRange = dragRange; - newRange.end = session.insert(dragCursor, session.getTextRange(dragRange)); - newRange.start = dragCursor; - } else if (dragRange.contains(dragCursor.row, dragCursor.column)) { - return; - } else { - var newRange = editor.moveText(dragRange, dragCursor); - } - - if (!newRange) - return; - - editor.selection.setSelectionRange(newRange); - }; - this.onDoubleClick = function(ev) { var pos = ev.getDocumentPosition(); var editor = this.editor; @@ -293,7 +231,7 @@ function DefaultHandlers(mouseHandler) { editor.selectAll(); this.$clickSelection = editor.getSelectionRange(); - this.setState("null"); + this.setState("selectAll"); }; this.onMouseWheel = function(ev) { diff --git a/lib/ace/mouse/dragdrop.js b/lib/ace/mouse/dragdrop.js deleted file mode 100644 index 3d279a98..00000000 --- a/lib/ace/mouse/dragdrop.js +++ /dev/null @@ -1,122 +0,0 @@ -/* ***** BEGIN LICENSE BLOCK ***** - * Distributed under the BSD license: - * - * Copyright (c) 2010, Ajax.org B.V. - * 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 Ajax.org B.V. 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 AJAX.ORG B.V. 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. - * - * ***** END LICENSE BLOCK ***** */ - -define(function(require, exports, module) { -"use strict"; - -var event = require("../lib/event"); - -var DragdropHandler = function(mouseHandler) { - var editor = mouseHandler.editor; - var dragSelectionMarker, x, y; - var timerId, range; - var dragCursor, counter = 0; - - var mouseTarget = editor.container; - event.addListener(mouseTarget, "dragenter", function(e) { - if (editor.getReadOnly()) - return; - var types = e.dataTransfer.types; - if (types && Array.prototype.indexOf.call(types, "text/plain") === -1) - return; - if (!dragSelectionMarker) - addDragMarker(); - counter++; - return event.preventDefault(e); - }); - - event.addListener(mouseTarget, "dragover", function(e) { - if (editor.getReadOnly()) - return; - var types = e.dataTransfer.types; - if (types && Array.prototype.indexOf.call(types, "text/plain") === -1) - return; - if (onMouseMoveTimer !== null) - onMouseMoveTimer = null; - x = e.clientX; - y = e.clientY; - return event.preventDefault(e); - }); - - var onDragInterval = function() { - dragCursor = editor.renderer.screenToTextCoordinates(x, y); - editor.moveCursorToPosition(dragCursor); - editor.renderer.scrollCursorIntoView(); - }; - - event.addListener(mouseTarget, "dragleave", function(e) { - counter--; - if (counter <= 0 && dragSelectionMarker) { - clearDragMarker(); - return event.preventDefault(e); - } - }); - - event.addListener(mouseTarget, "drop", function(e) { - if (!dragSelectionMarker) - return; - range.end = editor.session.insert(dragCursor, e.dataTransfer.getData('Text')); - range.start = dragCursor; - clearDragMarker(); - editor.focus(); - return event.preventDefault(e); - }); - - function addDragMarker() { - range = editor.selection.toOrientedRange(); - dragSelectionMarker = editor.session.addMarker(range, "ace_selection", editor.getSelectionStyle()); - editor.clearSelection(); - clearInterval(timerId); - timerId = setInterval(onDragInterval, 20); - counter = 0; - event.addListener(document, "mousemove", onMouseMove); - } - function clearDragMarker() { - clearInterval(timerId); - editor.session.removeMarker(dragSelectionMarker); - dragSelectionMarker = null; - editor.selection.fromOrientedRange(range); - counter = 0; - event.removeListener(document, "mousemove", onMouseMove); - } - // sometimes other code on the page can stop dragleave event leaving editor stuck in the drag state - var onMouseMoveTimer = null; - function onMouseMove() { - if (onMouseMoveTimer == null) { - onMouseMoveTimer = setTimeout(function() { - if (onMouseMoveTimer != null && dragSelectionMarker) - clearDragMarker(); - }, 20); - } - } -}; - -exports.DragdropHandler = DragdropHandler; -}); diff --git a/lib/ace/mouse/dragdrop_handler.js b/lib/ace/mouse/dragdrop_handler.js new file mode 100644 index 00000000..9da78933 --- /dev/null +++ b/lib/ace/mouse/dragdrop_handler.js @@ -0,0 +1,413 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Distributed under the BSD license: + * + * Copyright (c) 2010, Ajax.org B.V. + * 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 Ajax.org B.V. 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 AJAX.ORG B.V. 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. + * + * ***** END LICENSE BLOCK ***** */ + +define(function(require, exports, module) { +"use strict"; + +var dom = require("../lib/dom"); +var event = require("../lib/event"); +var useragent = require("../lib/useragent"); + +var AUTOSCROLL_DELAY = 200; +var SCROLL_CURSOR_DELAY = 200; +var SCROLL_CURSOR_HYSTERESIS = 5; + +function DragdropHandler(mouseHandler) { + + var editor = mouseHandler.editor; + + // Safari accepts either image or element (but it must present in the DOM) + var proxy = dom.createElement("img"); + // Safari crashes without image data + proxy.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="; + + if (useragent.isOpera) { + proxy.style.cssText = "width:1px;height:1px;position:fixed;top:0;left:0;z-index:2147483647;opacity:0;visibility:hidden"; + editor.container.appendChild(proxy); + } + + var exports = ["dragWait", "dragWaitEnd", "startDrag", "dragReadyEnd", "onMouseDrag"]; + + exports.forEach(function(x) { + mouseHandler[x] = this[x]; + }, this); + editor.addEventListener("mousedown", this.onMouseDown.bind(mouseHandler)); + + + var mouseTarget = editor.container; + var dragSelectionMarker, x, y; + var timerId, range; + var dragCursor, counter = 0; + var dragOperation; + var autoScrollStartTime; + var cursorMovedTime; + var cursorPointOnCaretMoved; + + this.onDragStart = function(e) { + // webkit workaround, see this.onMouseDown + if (this.cancelDrag || !mouseTarget.draggable) { + var self = this; + setTimeout(function(){ + self.startSelect(); + self.captureMouse(e); + }, 0); + return e.preventDefault(); + } + if (useragent.isOpera) { + proxy.style.visibility = "visible"; + setTimeout(function(){ + proxy.style.visibility = "hidden"; + }, 0); + } + range = editor.getSelectionRange(); + + var dataTransfer = e.dataTransfer; + dataTransfer.effectAllowed = editor.getReadOnly() ? "copy" : "copyMove"; + dataTransfer.setDragImage && dataTransfer.setDragImage(proxy, 0, 0); + // clear Opera garbage + dataTransfer.clearData(); + dataTransfer.setData("Text", editor.session.getTextRange()); + + this.setState("drag"); + }; + + this.onDragEnd = function(e) { + mouseTarget.draggable = false; + this.setState(null); + if (!editor.getReadOnly()) { + var dropEffect = e.dataTransfer.dropEffect; + if (!dragOperation && dropEffect == "move") + // text was dragged outside the editor + editor.session.remove(editor.getSelectionRange()); + editor.renderer.$cursorLayer.setBlinking(true); + } + this.editor.unsetStyle("ace_dragging"); + }; + + this.onDragEnter = function(e) { + if (editor.getReadOnly() || !canAccept(e.dataTransfer)) + return; + if (!dragSelectionMarker) + addDragMarker(); + counter++; + // dataTransfer object does not save dropEffect across events on IE, so we store it in dragOperation + e.dataTransfer.dropEffect = dragOperation = getDropEffect(e); + return event.preventDefault(e); + }; + + this.onDragOver = function(e) { + if (editor.getReadOnly() || !canAccept(e.dataTransfer)) + return; + // Opera doesn't trigger dragenter event on drag start + if (!dragSelectionMarker) { + addDragMarker(); + counter++; + } + if (onMouseMoveTimer !== null) + onMouseMoveTimer = null; + x = e.clientX; + y = e.clientY; + + e.dataTransfer.dropEffect = dragOperation = getDropEffect(e); + return event.preventDefault(e); + }; + + this.onDragLeave = function(e) { + counter--; + if (counter <= 0 && dragSelectionMarker) { + clearDragMarker(); + dragOperation = null; + return event.preventDefault(e); + } + }; + + this.onDrop = function(e) { + if (!dragSelectionMarker) + return; + var dataTransfer = e.dataTransfer; + var isInternal = this.state == "drag"; + if (isInternal) { + switch (dragOperation) { + case "move": + if (range.contains(dragCursor.row, dragCursor.column)) { + // clear selection + range = { + start: dragCursor, + end: dragCursor + }; + } else { + // move text + range = editor.moveText(range, dragCursor); + } + break; + case "copy": + // copy text + range = editor.moveText(range, dragCursor, true); + break; + } + } else { + var dropData = dataTransfer.getData('Text'); + range = { + start: dragCursor, + end: editor.session.insert(dragCursor, dropData) + }; + editor.focus(); + dragOperation = null; + } + clearDragMarker(); + return event.preventDefault(e); + }; + + event.addListener(mouseTarget, "dragstart", this.onDragStart.bind(mouseHandler)); + event.addListener(mouseTarget, "dragend", this.onDragEnd.bind(mouseHandler)); + event.addListener(mouseTarget, "dragenter", this.onDragEnter.bind(mouseHandler)); + event.addListener(mouseTarget, "dragover", this.onDragOver.bind(mouseHandler)); + event.addListener(mouseTarget, "dragleave", this.onDragLeave.bind(mouseHandler)); + event.addListener(mouseTarget, "drop", this.onDrop.bind(mouseHandler)); + + function scrollCursorIntoView(cursor, prevCursor) { + var now = new Date().getTime(); + var vMovement = !prevCursor || cursor.row != prevCursor.row; + var hMovement = !prevCursor || cursor.column != prevCursor.column; + if (!cursorMovedTime || vMovement || hMovement) { + editor.$blockScrolling += 1; + editor.moveCursorToPosition(cursor); + editor.$blockScrolling -= 1; + cursorMovedTime = now; + cursorPointOnCaretMoved = {x: x, y: y}; + } else { + var distance = calcDistance(cursorPointOnCaretMoved.x, cursorPointOnCaretMoved.y, x, y); + if (distance > SCROLL_CURSOR_HYSTERESIS) { + cursorMovedTime = null; + } else if (now - cursorMovedTime >= SCROLL_CURSOR_DELAY) { + editor.renderer.scrollCursorIntoView(); + cursorMovedTime = null; + } + } + } + + function autoScroll(cursor, prevCursor) { + var now = new Date().getTime(); + var lineHeight = editor.renderer.layerConfig.lineHeight; + var characterWidth = editor.renderer.layerConfig.characterWidth; + var editorRect = editor.renderer.scroller.getBoundingClientRect(); + var offsets = { + x: { + left: x - editorRect.left, + right: editorRect.right - x + }, + y: { + top: y - editorRect.top, + bottom: editorRect.bottom - y + } + }; + var nearestXOffset = Math.min(offsets.x.left, offsets.x.right); + var nearestYOffset = Math.min(offsets.y.top, offsets.y.bottom); + var scrollCursor = {row: cursor.row, column: cursor.column}; + if (nearestXOffset / characterWidth <= 2) { + scrollCursor.column += (offsets.x.left < offsets.x.right ? -3 : +2); + } + if (nearestYOffset / lineHeight <= 1) { + scrollCursor.row += (offsets.y.top < offsets.y.bottom ? -1 : +1); + } + var vScroll = cursor.row != scrollCursor.row; + var hScroll = cursor.column != scrollCursor.column; + var vMovement = !prevCursor || cursor.row != prevCursor.row; + if (vScroll || (hScroll && !vMovement)) { + if (!autoScrollStartTime) + autoScrollStartTime = now; + else if (now - autoScrollStartTime >= AUTOSCROLL_DELAY) + editor.renderer.scrollCursorIntoView(scrollCursor); + } else { + autoScrollStartTime = null; + } + } + + function onDragInterval() { + var prevCursor = dragCursor; + dragCursor = editor.renderer.screenToTextCoordinates(x, y); + scrollCursorIntoView(dragCursor, prevCursor); + autoScroll(dragCursor, prevCursor); + } + + function addDragMarker() { + range = editor.selection.toOrientedRange(); + dragSelectionMarker = editor.session.addMarker(range, "ace_selection", editor.getSelectionStyle()); + editor.clearSelection(); + clearInterval(timerId); + timerId = setInterval(onDragInterval, 20); + counter = 0; + event.addListener(document, "mousemove", onMouseMove); + } + + function clearDragMarker() { + clearInterval(timerId); + editor.session.removeMarker(dragSelectionMarker); + dragSelectionMarker = null; + editor.$blockScrolling += 1; + editor.selection.fromOrientedRange(range); + editor.$blockScrolling -= 1; + range = null; + counter = 0; + autoScrollStartTime = null; + cursorMovedTime = null; + event.removeListener(document, "mousemove", onMouseMove); + } + + // sometimes other code on the page can stop dragleave event leaving editor stuck in the drag state + var onMouseMoveTimer = null; + function onMouseMove() { + if (onMouseMoveTimer == null) { + onMouseMoveTimer = setTimeout(function() { + if (onMouseMoveTimer != null && dragSelectionMarker) + clearDragMarker(); + }, 20); + } + } + + function canAccept(dataTransfer) { + var types = dataTransfer.types; + return !types || Array.prototype.some.call(types, function(type) { + return type == 'text/plain' || type == 'Text'; + }); + } + + function getDropEffect(e) { + var copyAllowed = ['copy', 'copymove', 'all', 'uninitialized']; + var moveAllowed = ['move', 'copymove', 'linkmove', 'all', 'uninitialized']; + + var copyModifierState = useragent.isMac ? e.altKey : e.ctrlKey; + + // IE throws error while dragging from another app + var effectAllowed = "uninitialized"; + try { + effectAllowed = e.dataTransfer.effectAllowed.toLowerCase(); + } catch (e) {} + var dropEffect = "none"; + + if (copyModifierState && copyAllowed.indexOf(effectAllowed) >= 0) + dropEffect = "copy"; + else if (moveAllowed.indexOf(effectAllowed) >= 0) + dropEffect = "move"; + else if (copyAllowed.indexOf(effectAllowed) >= 0) + dropEffect = "copy"; + + return dropEffect; + } +} + +(function() { + + this.dragWait = function() { + var interval = (new Date()).getTime() - this.mousedownEvent.time; + if (interval > this.editor.getDragDelay()) + this.startDrag(); + }; + + this.dragWaitEnd = function() { + var target = this.editor.container; + target.draggable = false; + this.startSelect(this.mousedownEvent.getDocumentPosition()); + this.selectEnd(); + }; + + this.dragReadyEnd = function(e) { + this.editor.renderer.$cursorLayer.setBlinking(!this.editor.getReadOnly()); + this.editor.unsetStyle("ace_dragging"); + this.dragWaitEnd(); + }; + + this.startDrag = function(){ + this.cancelDrag = false; + var target = this.editor.container; + target.draggable = true; + this.editor.renderer.$cursorLayer.setBlinking(false); + this.editor.setStyle("ace_dragging"); + this.setState("dragReady"); + }; + + this.onMouseDrag = function(e) { + var target = this.editor.container; + if (useragent.isIE && this.state == "dragReady") { + // IE does not handle [draggable] attribute set after mousedown + var distance = calcDistance(this.mousedownEvent.x, this.mousedownEvent.y, this.x, this.y); + if (distance > 3) + target.dragDrop(); + } + if (this.state === "dragWait") { + var distance = calcDistance(this.mousedownEvent.x, this.mousedownEvent.y, this.x, this.y); + if (distance > 0) { + target.draggable = false; + this.startSelect(this.mousedownEvent.getDocumentPosition()); + } + } + }; + + this.onMouseDown = function(e) { + if (!this.$dragEnabled) + return; + this.mousedownEvent = e; + var editor = this.editor; + + var inSelection = e.inSelection(); + var button = e.getButton(); + var clickCount = e.domEvent.detail || 1; + if (clickCount === 1 && button === 0 && inSelection) { + this.mousedownEvent.time = (new Date()).getTime(); + var eventTarget = e.domEvent.target || e.domEvent.srcElement; + if ("unselectable" in eventTarget) + eventTarget.unselectable = "on"; + if (editor.getDragDelay()) { + // https://code.google.com/p/chromium/issues/detail?id=286700 + if (useragent.isWebKit) { + self.cancelDrag = true; + var mouseTarget = editor.container; + mouseTarget.draggable = true; + } + this.setState("dragWait"); + } else { + this.startDrag(); + } + this.captureMouse(e, this.onMouseDrag.bind(this)); + // TODO: a better way to prevent default handler without preventing browser default action + e.defaultPrevented = true; + } + }; + +}).call(DragdropHandler.prototype); + + +function calcDistance(ax, ay, bx, by) { + return Math.sqrt(Math.pow(bx - ax, 2) + Math.pow(by - ay, 2)); +} + +exports.DragdropHandler = DragdropHandler; + +}); \ No newline at end of file diff --git a/lib/ace/mouse/mouse_event.js b/lib/ace/mouse/mouse_event.js index 559713ed..aec90ddb 100644 --- a/lib/ace/mouse/mouse_event.js +++ b/lib/ace/mouse/mouse_event.js @@ -92,18 +92,15 @@ var MouseEvent = exports.MouseEvent = function(domEvent, editor) { var editor = this.editor; - if (editor.getReadOnly()) { + + var selectionRange = editor.getSelectionRange(); + if (selectionRange.isEmpty()) this.$inSelection = false; - } else { - var selectionRange = editor.getSelectionRange(); - if (selectionRange.isEmpty()) - this.$inSelection = false; - else { - var pos = this.getDocumentPosition(); - this.$inSelection = selectionRange.contains(pos.row, pos.column); - } + var pos = this.getDocumentPosition(); + this.$inSelection = selectionRange.contains(pos.row, pos.column); } + return this.$inSelection; }; diff --git a/lib/ace/mouse/mouse_handler.js b/lib/ace/mouse/mouse_handler.js index a30fe76b..2565bd19 100644 --- a/lib/ace/mouse/mouse_handler.js +++ b/lib/ace/mouse/mouse_handler.js @@ -36,7 +36,7 @@ var useragent = require("../lib/useragent"); var DefaultHandlers = require("./default_handlers").DefaultHandlers; var DefaultGutterHandler = require("./default_gutter_handler").GutterHandler; var MouseEvent = require("./mouse_event").MouseEvent; -var DragdropHandler = require("./dragdrop").DragdropHandler; +var DragdropHandler = require("./dragdrop_handler").DragdropHandler; var config = require("../config"); var MouseHandler = function(editor) { @@ -61,12 +61,11 @@ var MouseHandler = function(editor) { event.addListener(gutterEl, "click", this.onMouseEvent.bind(this, "gutterclick")); event.addListener(gutterEl, "dblclick", this.onMouseEvent.bind(this, "gutterdblclick")); event.addListener(gutterEl, "mousemove", this.onMouseEvent.bind(this, "guttermousemove")); - + event.addListener(mouseTarget, "mousedown", function(e) { editor.focus(); - return event.preventDefault(e); }); - + event.addListener(gutterEl, "mousedown", function(e) { editor.focus(); return event.preventDefault(e); @@ -100,13 +99,10 @@ var MouseHandler = function(editor) { this.state = state; }; - this.captureMouse = function(ev, state) { - if (state) - this.setState(state); - + this.captureMouse = function(ev, mouseMoveHandler) { this.x = ev.x; this.y = ev.y; - + this.isMousePressed = true; // do not move textarea during selection @@ -118,6 +114,7 @@ var MouseHandler = function(editor) { var onMouseMove = function(e) { self.x = e.clientX; self.y = e.clientY; + mouseMoveHandler && mouseMoveHandler(e); }; var onCaptureEnd = function(e) { @@ -130,13 +127,13 @@ var MouseHandler = function(editor) { renderer.$moveTextAreaToCursor(); } self.isMousePressed = false; - self.onMouseEvent("mouseup", e) + self.onMouseEvent("mouseup", e); }; var onCaptureInterval = function() { self[self.state] && self[self.state](); }; - + if (useragent.isOldIE && ev.domEvent.type == "dblclick") { return setTimeout(function() {onCaptureEnd(ev);}); } @@ -149,6 +146,7 @@ var MouseHandler = function(editor) { config.defineOptions(MouseHandler.prototype, "mouseHandler", { scrollSpeed: {initialValue: 2}, dragDelay: {initialValue: 150}, + dragEnabled: {initialValue: true}, focusTimout: {initialValue: 0} });