From 89368613389c381e2ab18479bbd5e04934577de0 Mon Sep 17 00:00:00 2001 From: DanyaPostfactum Date: Tue, 10 Sep 2013 21:41:20 +1000 Subject: [PATCH 01/13] Use native HTML5 Drag'n'Drop for text. --- lib/ace/css/editor.css | 15 +- lib/ace/edit_session.js | 2 +- lib/ace/editor.js | 5 +- lib/ace/lib/event.js | 75 +++---- lib/ace/mouse/default_handlers.js | 131 +++-------- lib/ace/mouse/dragdrop.js | 122 ---------- lib/ace/mouse/dragdrop_handler.js | 358 ++++++++++++++++++++++++++++++ lib/ace/mouse/mouse_event.js | 15 +- lib/ace/mouse/mouse_handler.js | 22 +- 9 files changed, 457 insertions(+), 288 deletions(-) delete mode 100644 lib/ace/mouse/dragdrop.js create mode 100644 lib/ace/mouse/dragdrop_handler.js diff --git a/lib/ace/css/editor.css b/lib/ace/css/editor.css index acea6f8906f..1244c1b03c8 100644 --- a/lib/ace/css/editor.css +++ b/lib/ace/css/editor.css @@ -5,6 +5,9 @@ font-size: 12px; line-height: normal; color: black; + -moz-user-select: none; + -webkit-user-select: none; + user-select: none; } .ace_scroller { @@ -23,6 +26,14 @@ cursor: text; } +.ace_content[draggable="true"] { + cursor: default; +} + +.ace_selecting, .ace_selecting .ace_gutter{ + cursor: text; +} + .ace_gutter { position: absolute; overflow : hidden; @@ -258,10 +269,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 9f31841c47f..9f8558dadb5 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -1325,7 +1325,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 e22875d56e4..c098e792825 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 42b77eaf9a9..bd63c66b097 100644 --- a/lib/ace/lib/event.js +++ b/lib/ace/lib/event.js @@ -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); - - if (!called) { - called = true; - releaseCaptureHandler(e); - } - - exports.removeListener(el, "mousemove", eventHandler); - exports.removeListener(el, "mouseup", onReleaseCapture); - exports.removeListener(el, "losecapture", onReleaseCapture); +exports.capture = function(el, eventHandler, releaseCaptureHandler) { + function onMouseUp(e) { + eventHandler && eventHandler(e); + releaseCaptureHandler && releaseCaptureHandler(e); - el.releaseCapture(); - } + exports.removeListener(document, "mousemove", eventHandler, true); + exports.removeListener(document, "mouseup", onMouseUp, true); + exports.removeListener(document, "dragstart", onMouseUp, true); - 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); - } + exports.stopPropagation(e); + } - 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_handlers.js b/lib/ace/mouse/default_handlers.js index 8529c0fca60..c57a17189de 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", "selectByWordsEnd", + "selectByLinesEnd", "dragWait", "dragWaitEnd", "focusWait"]; exports.forEach(function(x) { mouseHandler[x] = this[x]; @@ -85,34 +86,48 @@ 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; } } - if (!inSelection || this.$clickSelection || ev.getShiftKey() || editor.inMultiSelectMode) { - // Directly pick STATE_SELECT, since the user is not clicking inside - // a selection. - 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) { + this.startSelect = function(pos, setCaptureOnEvent) { pos = pos || this.editor.renderer.screenToTextCoordinates(this.x, this.y); + var editor = this.editor; if (this.mousedownEvent.getShiftKey()) { - this.editor.selection.selectToPosition(pos); + editor.selection.selectToPosition(pos); } else if (!this.$clickSelection) { - this.editor.moveCursorToPosition(pos); - this.editor.selection.clearSelection(); + editor.moveCursorToPosition(pos); + editor.selection.clearSelection(); } + // IE sometimes get stuck in capture, I guess there some cases releaseCapture is not called, so disable for IE + if (editor.container.setCapture && !useragent.isIE) { + if (setCaptureOnEvent) { + var self = this; + var onMouseMove = function() { + event.removeListener(editor.container, 'mousemove', onMouseMove); + if (self.state && self.state.indexOf("select") == 0) + editor.container.setCapture(); + }; + var onMouseUp = function() { + event.removeListener(editor.container, 'mouseup', onMouseUp); + event.removeListener(editor.container, 'mousemove', onMouseMove); + }; + event.addListener(editor.container, 'mousemove', onMouseMove); + event.addListener(editor.container, 'mouseup', onMouseUp); + } else { + editor.container.setCapture(); + } + } + editor.setStyle("ace_selecting"); this.setState("select"); }; @@ -171,32 +186,13 @@ 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.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 +203,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; diff --git a/lib/ace/mouse/dragdrop.js b/lib/ace/mouse/dragdrop.js deleted file mode 100644 index 3d279a9815a..00000000000 --- 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 00000000000..a100ec88c0d --- /dev/null +++ b/lib/ace/mouse/dragdrop_handler.js @@ -0,0 +1,358 @@ +/* ***** 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"); + +// 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=="; + +var DRAG_OFFSET = useragent.isIE ? 3 : 0; // pixels + +function DragdropHandler(mouseHandler) { + + var editor = mouseHandler.editor; + + if (useragent.isOpera) { + proxy.style.cssText = 'width:1px;height:1px;position:fixed;top:0;z-index:-1'; + editor.container.appendChild(proxy); + } + + var exports = ["dragWait", "dragWaitEnd", "startDrag", "dragReadyEnd"]; + + exports.forEach(function(x) { + mouseHandler[x] = this[x]; + }, this); + + var mouseTarget = editor.renderer.getMouseEventTarget(); + editor.addEventListener("mousedown", this.onMouseDown.bind(mouseHandler)); + + var dragSelectionMarker, x, y; + var timerId, range; + var dragCursor, counter = 0; + var dragOperation; + + 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(); + } + range = editor.getSelectionRange(); + + var dataTransfer = e.dataTransfer; + dataTransfer.effectAllowed = editor.getReadOnly() ? "copy" : "copyMove"; + dataTransfer.setDragImage && dataTransfer.setDragImage(proxy, 0, 0); + 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.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 onDragInterval() { + dragCursor = editor.renderer.screenToTextCoordinates(x, y); + 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 scrollX = 0; + var scrollY = 0; + if (nearestXOffset / characterWidth <= 2) { + scrollX = characterWidth * (offsets.x.left < offsets.x.right ? -1 : +1); + } + if (nearestYOffset / lineHeight <= 1) { + scrollY = lineHeight * (offsets.y.top < offsets.y.bottom ? -1 : +1); + } + editor.moveCursorToPosition(dragCursor); + editor.renderer.scrollBy(scrollX, scrollY); + } + + 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); + editor.setStyle("ace_dragging"); + } + + function clearDragMarker() { + clearInterval(timerId); + editor.session.removeMarker(dragSelectionMarker); + dragSelectionMarker = null; + editor.selection.fromOrientedRange(range); + range = null; + counter = 0; + event.removeListener(document, "mousemove", onMouseMove); + editor.unsetStyle("ace_dragging"); + } + + // 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(e) { + var editor = this.editor; + var distance = calcDistance(this.mousedownEvent.x, this.mousedownEvent.y, this.x, this.y); + var interval = (new Date()).getTime() - this.mousedownEvent.time; + + if (distance > DRAG_OFFSET) + this.startSelect(this.mousedownEvent.getDocumentPosition(), true); + else if (interval > editor.getDragDelay()) + this.startDrag(); + }; + + this.dragWaitEnd = function(e) { + this.startSelect(this.mousedownEvent.getDocumentPosition(), true); + }; + + this.startDrag = function(){ + var target = this.editor.renderer.getMouseEventTarget(); + this.setState("dragReady"); + this.editor.unsetStyle('ace_selecting'); + this.editor.renderer.$cursorLayer.setBlinking(false); + target.draggable = true; + // IE does not handle [draggable] attribute set after mousedown + if (target.dragDrop) + target.dragDrop(); + }; + + this.dragReadyEnd = function(e) { + var target = this.editor.renderer.getMouseEventTarget(); + target.draggable = false; + this.editor.renderer.$cursorLayer.setBlinking(!this.editor.getReadOnly()); + this.startSelect(this.mousedownEvent.getDocumentPosition()); + this.selectEnd(); + }; + + this.onMouseDown = function(e) { + if (!this.$dragEnabled) + return; + var inSelection = e.inSelection(); + var editor = this.editor; + this.mousedownEvent = e; + + var button = e.getButton(); + if (button === 0 && inSelection) { + this.mousedownEvent.time = (new Date()).getTime(); + if (editor.getDragDelay()) { + // https://code.google.com/p/chromium/issues/detail?id=286700 + if (useragent.isWebKit) { + var self = this; + self.cancelDrag = true; + var mouseTarget = editor.renderer.getMouseEventTarget(); + mouseTarget.draggable = true; + setTimeout(function(){ + self.cancelDrag = false; + mouseTarget.draggable = false; + }, 8); + } + this.captureMouse(e, "dragWait"); + editor.setStyle('ace_selecting'); + } else { + this.startDrag(); + this.captureMouse(e); + } + + if (useragent.isOpera) { + var cancelSelection = function(e){ + document.getSelection().removeAllRanges(); + editor.container.removeEventListener("mousemove", cancelSelection); + }; + editor.container.addEventListener("mousemove", cancelSelection); + } + // TODO: a better way to prevent default handler without preventing browser default action + e.defaultPrevented = true; + } + }; + + function calcDistance(ax, ay, bx, by) { + return Math.sqrt(Math.pow(bx - ax, 2) + Math.pow(by - ay, 2)); + } +}).call(DragdropHandler.prototype); + +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 559713ed50d..aec90ddbee0 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 a30fe76b3a5..aae8f5c4e3f 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); @@ -106,7 +105,7 @@ var MouseHandler = function(editor) { this.x = ev.x; this.y = ev.y; - + this.isMousePressed = true; // do not move textarea during selection @@ -118,6 +117,9 @@ var MouseHandler = function(editor) { var onMouseMove = function(e) { self.x = e.clientX; self.y = e.clientY; + if (useragent.isIE) { + onCaptureInterval(); + } }; var onCaptureEnd = function(e) { @@ -130,25 +132,29 @@ 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);}); } event.capture(this.editor.container, onMouseMove, onCaptureEnd); - var timerId = setInterval(onCaptureInterval, 20); + if (!useragent.isIE) { + // this would cause problems on dragDrop() call in IE + var timerId = setInterval(onCaptureInterval, 20); + } }; }).call(MouseHandler.prototype); config.defineOptions(MouseHandler.prototype, "mouseHandler", { scrollSpeed: {initialValue: 2}, dragDelay: {initialValue: 150}, + dragEnabled: {initialValue: true}, focusTimout: {initialValue: 0} }); From 2993ae831fde00a34609b07a11a311c9b306ca0b Mon Sep 17 00:00:00 2001 From: DanyaPostfactum Date: Wed, 11 Sep 2013 21:52:30 +1000 Subject: [PATCH 02/13] Set `this` context of event handlers for old IE. --- lib/ace/lib/event.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ace/lib/event.js b/lib/ace/lib/event.js index bd63c66b097..89d2756a28f 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); From b7daccf9a9e61c5ff03192311be913bf31be0a8a Mon Sep 17 00:00:00 2001 From: DanyaPostfactum Date: Thu, 12 Sep 2013 01:44:55 +1000 Subject: [PATCH 03/13] Fix IE native drag'n'drop issues --- lib/ace/mouse/default_handlers.js | 16 +++++++--------- lib/ace/mouse/dragdrop_handler.js | 30 ++++++++++++++++++------------ lib/ace/mouse/mouse_handler.js | 8 +------- 3 files changed, 26 insertions(+), 28 deletions(-) diff --git a/lib/ace/mouse/default_handlers.js b/lib/ace/mouse/default_handlers.js index c57a17189de..92ee07a7949 100644 --- a/lib/ace/mouse/default_handlers.js +++ b/lib/ace/mouse/default_handlers.js @@ -93,12 +93,12 @@ function DefaultHandlers(mouseHandler) { } } - this.startSelect(pos); + this.startSelect(pos, true); this.captureMouse(ev); return ev.preventDefault(); }; - this.startSelect = function(pos, setCaptureOnEvent) { + this.startSelect = function(pos, fromEvent) { pos = pos || this.editor.renderer.screenToTextCoordinates(this.x, this.y); var editor = this.editor; if (this.mousedownEvent.getShiftKey()) { @@ -108,14 +108,14 @@ function DefaultHandlers(mouseHandler) { editor.moveCursorToPosition(pos); editor.selection.clearSelection(); } - // IE sometimes get stuck in capture, I guess there some cases releaseCapture is not called, so disable for IE - if (editor.container.setCapture && !useragent.isIE) { - if (setCaptureOnEvent) { + if (editor.container.setCapture) { + if (fromEvent) { + editor.container.setCapture(); + } else { var self = this; var onMouseMove = function() { event.removeListener(editor.container, 'mousemove', onMouseMove); - if (self.state && self.state.indexOf("select") == 0) - editor.container.setCapture(); + editor.container.setCapture(); }; var onMouseUp = function() { event.removeListener(editor.container, 'mouseup', onMouseUp); @@ -123,8 +123,6 @@ function DefaultHandlers(mouseHandler) { }; event.addListener(editor.container, 'mousemove', onMouseMove); event.addListener(editor.container, 'mouseup', onMouseUp); - } else { - editor.container.setCapture(); } } editor.setStyle("ace_selecting"); diff --git a/lib/ace/mouse/dragdrop_handler.js b/lib/ace/mouse/dragdrop_handler.js index a100ec88c0d..fcd13840e11 100644 --- a/lib/ace/mouse/dragdrop_handler.js +++ b/lib/ace/mouse/dragdrop_handler.js @@ -40,8 +40,6 @@ var proxy = dom.createElement('img'); // Safari crashes without image data proxy.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="; -var DRAG_OFFSET = useragent.isIE ? 3 : 0; // pixels - function DragdropHandler(mouseHandler) { var editor = mouseHandler.editor; @@ -273,37 +271,41 @@ function DragdropHandler(mouseHandler) { (function() { - this.dragWait = function(e) { + this.dragWait = function() { var editor = this.editor; var distance = calcDistance(this.mousedownEvent.x, this.mousedownEvent.y, this.x, this.y); var interval = (new Date()).getTime() - this.mousedownEvent.time; - if (distance > DRAG_OFFSET) - this.startSelect(this.mousedownEvent.getDocumentPosition(), true); + if (distance > 0) + this.startSelect(this.mousedownEvent.getDocumentPosition()); else if (interval > editor.getDragDelay()) this.startDrag(); }; - this.dragWaitEnd = function(e) { + this.dragWaitEnd = function() { this.startSelect(this.mousedownEvent.getDocumentPosition(), true); + this.selectEnd(); }; this.startDrag = function(){ var target = this.editor.renderer.getMouseEventTarget(); this.setState("dragReady"); - this.editor.unsetStyle('ace_selecting'); this.editor.renderer.$cursorLayer.setBlinking(false); target.draggable = true; - // IE does not handle [draggable] attribute set after mousedown - if (target.dragDrop) - target.dragDrop(); + if (useragent.isIE) { + // IE does not handle [draggable] attribute set after mousedown + event.addListener(target, "mousemove", forceDragIE); + } }; this.dragReadyEnd = function(e) { var target = this.editor.renderer.getMouseEventTarget(); target.draggable = false; + if (useragent.isIE) { + event.removeListener(target, "mousemove", forceDragIE); + } this.editor.renderer.$cursorLayer.setBlinking(!this.editor.getReadOnly()); - this.startSelect(this.mousedownEvent.getDocumentPosition()); + this.startSelect(this.mousedownEvent.getDocumentPosition(), true); this.selectEnd(); }; @@ -330,7 +332,6 @@ function DragdropHandler(mouseHandler) { }, 8); } this.captureMouse(e, "dragWait"); - editor.setStyle('ace_selecting'); } else { this.startDrag(); this.captureMouse(e); @@ -348,6 +349,11 @@ function DragdropHandler(mouseHandler) { } }; + function forceDragIE() { + event.removeListener(this, "mousemove", forceDragIE); + this.dragDrop(); + } + function calcDistance(ax, ay, bx, by) { return Math.sqrt(Math.pow(bx - ax, 2) + Math.pow(by - ay, 2)); } diff --git a/lib/ace/mouse/mouse_handler.js b/lib/ace/mouse/mouse_handler.js index aae8f5c4e3f..1e8895b553c 100644 --- a/lib/ace/mouse/mouse_handler.js +++ b/lib/ace/mouse/mouse_handler.js @@ -117,9 +117,6 @@ var MouseHandler = function(editor) { var onMouseMove = function(e) { self.x = e.clientX; self.y = e.clientY; - if (useragent.isIE) { - onCaptureInterval(); - } }; var onCaptureEnd = function(e) { @@ -144,10 +141,7 @@ var MouseHandler = function(editor) { } event.capture(this.editor.container, onMouseMove, onCaptureEnd); - if (!useragent.isIE) { - // this would cause problems on dragDrop() call in IE - var timerId = setInterval(onCaptureInterval, 20); - } + var timerId = setInterval(onCaptureInterval, 20); }; }).call(MouseHandler.prototype); From ddf5d1ab4567d5c2eb2526d85efb921dfe7b5a3c Mon Sep 17 00:00:00 2001 From: DanyaPostfactum Date: Thu, 12 Sep 2013 02:48:46 +1000 Subject: [PATCH 04/13] Improve autoscroll behavior for text dragging --- lib/ace/css/editor.css | 1 + lib/ace/mouse/dragdrop_handler.js | 11 ++++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/ace/css/editor.css b/lib/ace/css/editor.css index 1244c1b03c8..89141ec29ab 100644 --- a/lib/ace/css/editor.css +++ b/lib/ace/css/editor.css @@ -16,6 +16,7 @@ top: 0; bottom: 0; background-color: inherit; + z-index: 8; } .ace_content { diff --git a/lib/ace/mouse/dragdrop_handler.js b/lib/ace/mouse/dragdrop_handler.js index fcd13840e11..8d760e6e472 100644 --- a/lib/ace/mouse/dragdrop_handler.js +++ b/lib/ace/mouse/dragdrop_handler.js @@ -193,16 +193,15 @@ function DragdropHandler(mouseHandler) { }; var nearestXOffset = Math.min(offsets.x.left, offsets.x.right); var nearestYOffset = Math.min(offsets.y.top, offsets.y.bottom); - var scrollX = 0; - var scrollY = 0; + var scrollCursor = {row: dragCursor.row, column: dragCursor.column}; if (nearestXOffset / characterWidth <= 2) { - scrollX = characterWidth * (offsets.x.left < offsets.x.right ? -1 : +1); + scrollCursor.column += (offsets.x.left < offsets.x.right ? -3 : +2); } if (nearestYOffset / lineHeight <= 1) { - scrollY = lineHeight * (offsets.y.top < offsets.y.bottom ? -1 : +1); + scrollCursor.row += (offsets.y.top < offsets.y.bottom ? -1 : +1); } editor.moveCursorToPosition(dragCursor); - editor.renderer.scrollBy(scrollX, scrollY); + editor.renderer.scrollCursorIntoView(scrollCursor); } function addDragMarker() { @@ -220,7 +219,9 @@ function DragdropHandler(mouseHandler) { clearInterval(timerId); editor.session.removeMarker(dragSelectionMarker); dragSelectionMarker = null; + editor.$blockScrolling += 1; editor.selection.fromOrientedRange(range); + editor.$blockScrolling -= 1; range = null; counter = 0; event.removeListener(document, "mousemove", onMouseMove); From b6dab9eb45a33b36c3682af1b63c55bdee0a83b9 Mon Sep 17 00:00:00 2001 From: DanyaPostfactum Date: Thu, 12 Sep 2013 23:44:00 +1000 Subject: [PATCH 05/13] Fix Opera drag image issue. --- lib/ace/mouse/dragdrop_handler.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/lib/ace/mouse/dragdrop_handler.js b/lib/ace/mouse/dragdrop_handler.js index 8d760e6e472..28d038b59fd 100644 --- a/lib/ace/mouse/dragdrop_handler.js +++ b/lib/ace/mouse/dragdrop_handler.js @@ -35,17 +35,17 @@ var dom = require("../lib/dom"); var event = require("../lib/event"); var useragent = require("../lib/useragent"); -// 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=="; - 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;z-index:-1'; + proxy.style.cssText = "width:1px;height:1px;position:fixed;top:0;left:0;z-index:2147483647;opacity:0;visibility:hidden"; editor.container.appendChild(proxy); } @@ -73,6 +73,12 @@ function DragdropHandler(mouseHandler) { }, 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; From 624eb7c1176027cfdffff3c08c4c1b61ebc091fb Mon Sep 17 00:00:00 2001 From: DanyaPostfactum Date: Sun, 15 Sep 2013 01:13:52 +1000 Subject: [PATCH 06/13] Make editor container to be drop zone --- lib/ace/css/editor.css | 9 ++++----- lib/ace/mouse/dragdrop_handler.js | 15 ++++++++------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/ace/css/editor.css b/lib/ace/css/editor.css index 89141ec29ab..0fe2c4ddb59 100644 --- a/lib/ace/css/editor.css +++ b/lib/ace/css/editor.css @@ -16,7 +16,6 @@ top: 0; bottom: 0; background-color: inherit; - z-index: 8; } .ace_content { @@ -27,12 +26,12 @@ cursor: text; } -.ace_content[draggable="true"] { - cursor: default; +.ace_dragging, .ace_dragging * { + cursor: default !important; } -.ace_selecting, .ace_selecting .ace_gutter{ - cursor: text; +.ace_selecting, .ace_selecting * { + cursor: text !important; } .ace_gutter { diff --git a/lib/ace/mouse/dragdrop_handler.js b/lib/ace/mouse/dragdrop_handler.js index 28d038b59fd..0bbc442f48d 100644 --- a/lib/ace/mouse/dragdrop_handler.js +++ b/lib/ace/mouse/dragdrop_handler.js @@ -54,10 +54,10 @@ function DragdropHandler(mouseHandler) { exports.forEach(function(x) { mouseHandler[x] = this[x]; }, this); - - var mouseTarget = editor.renderer.getMouseEventTarget(); editor.addEventListener("mousedown", this.onMouseDown.bind(mouseHandler)); + + var mouseTarget = editor.container; var dragSelectionMarker, x, y; var timerId, range; var dragCursor, counter = 0; @@ -99,6 +99,7 @@ function DragdropHandler(mouseHandler) { editor.session.remove(editor.getSelectionRange()); editor.renderer.$cursorLayer.setBlinking(true); } + this.editor.unsetStyle("ace_dragging"); }; this.onDragEnter = function(e) { @@ -218,7 +219,6 @@ function DragdropHandler(mouseHandler) { timerId = setInterval(onDragInterval, 20); counter = 0; event.addListener(document, "mousemove", onMouseMove); - editor.setStyle("ace_dragging"); } function clearDragMarker() { @@ -231,7 +231,6 @@ function DragdropHandler(mouseHandler) { range = null; counter = 0; event.removeListener(document, "mousemove", onMouseMove); - editor.unsetStyle("ace_dragging"); } // sometimes other code on the page can stop dragleave event leaving editor stuck in the drag state @@ -295,10 +294,11 @@ function DragdropHandler(mouseHandler) { }; this.startDrag = function(){ - var target = this.editor.renderer.getMouseEventTarget(); + var target = this.editor.container; this.setState("dragReady"); this.editor.renderer.$cursorLayer.setBlinking(false); target.draggable = true; + this.editor.setStyle("ace_dragging"); if (useragent.isIE) { // IE does not handle [draggable] attribute set after mousedown event.addListener(target, "mousemove", forceDragIE); @@ -306,8 +306,9 @@ function DragdropHandler(mouseHandler) { }; this.dragReadyEnd = function(e) { - var target = this.editor.renderer.getMouseEventTarget(); + var target = this.editor.container; target.draggable = false; + this.editor.unsetStyle("ace_dragging"); if (useragent.isIE) { event.removeListener(target, "mousemove", forceDragIE); } @@ -331,7 +332,7 @@ function DragdropHandler(mouseHandler) { if (useragent.isWebKit) { var self = this; self.cancelDrag = true; - var mouseTarget = editor.renderer.getMouseEventTarget(); + var mouseTarget = editor.container; mouseTarget.draggable = true; setTimeout(function(){ self.cancelDrag = false; From 165cec553a6dc35591c56169d5e519629d7186e9 Mon Sep 17 00:00:00 2001 From: DanyaPostfactum Date: Sun, 15 Sep 2013 01:25:40 +1000 Subject: [PATCH 07/13] Add onMouseDrag method to the DragdropHandler --- lib/ace/mouse/default_handlers.js | 18 +------- lib/ace/mouse/dragdrop_handler.js | 71 ++++++++++++++----------------- lib/ace/mouse/mouse_handler.js | 6 +-- 3 files changed, 35 insertions(+), 60 deletions(-) diff --git a/lib/ace/mouse/default_handlers.js b/lib/ace/mouse/default_handlers.js index 92ee07a7949..9f80e9f4db9 100644 --- a/lib/ace/mouse/default_handlers.js +++ b/lib/ace/mouse/default_handlers.js @@ -98,7 +98,7 @@ function DefaultHandlers(mouseHandler) { return ev.preventDefault(); }; - this.startSelect = function(pos, fromEvent) { + this.startSelect = function(pos) { pos = pos || this.editor.renderer.screenToTextCoordinates(this.x, this.y); var editor = this.editor; if (this.mousedownEvent.getShiftKey()) { @@ -109,21 +109,7 @@ function DefaultHandlers(mouseHandler) { editor.selection.clearSelection(); } if (editor.container.setCapture) { - if (fromEvent) { - editor.container.setCapture(); - } else { - var self = this; - var onMouseMove = function() { - event.removeListener(editor.container, 'mousemove', onMouseMove); - editor.container.setCapture(); - }; - var onMouseUp = function() { - event.removeListener(editor.container, 'mouseup', onMouseUp); - event.removeListener(editor.container, 'mousemove', onMouseMove); - }; - event.addListener(editor.container, 'mousemove', onMouseMove); - event.addListener(editor.container, 'mouseup', onMouseUp); - } + editor.container.setCapture(); } editor.setStyle("ace_selecting"); this.setState("select"); diff --git a/lib/ace/mouse/dragdrop_handler.js b/lib/ace/mouse/dragdrop_handler.js index 0bbc442f48d..e3788ded309 100644 --- a/lib/ace/mouse/dragdrop_handler.js +++ b/lib/ace/mouse/dragdrop_handler.js @@ -49,7 +49,7 @@ function DragdropHandler(mouseHandler) { editor.container.appendChild(proxy); } - var exports = ["dragWait", "dragWaitEnd", "startDrag", "dragReadyEnd"]; + var exports = ["dragWait", "dragWaitEnd", "startDrag", "dragReadyEnd", "onMouseDrag"]; exports.forEach(function(x) { mouseHandler[x] = this[x]; @@ -278,52 +278,56 @@ function DragdropHandler(mouseHandler) { (function() { this.dragWait = function() { - var editor = this.editor; - var distance = calcDistance(this.mousedownEvent.x, this.mousedownEvent.y, this.x, this.y); var interval = (new Date()).getTime() - this.mousedownEvent.time; - - if (distance > 0) - this.startSelect(this.mousedownEvent.getDocumentPosition()); - else if (interval > editor.getDragDelay()) + if (interval > this.editor.getDragDelay()) this.startDrag(); }; this.dragWaitEnd = function() { - this.startSelect(this.mousedownEvent.getDocumentPosition(), true); + this.startSelect(this.mousedownEvent.getDocumentPosition()); this.selectEnd(); }; + this.dragReadyEnd = function(e) { + var target = this.editor.container; + target.draggable = false; + this.editor.renderer.$cursorLayer.setBlinking(!this.editor.getReadOnly()); + this.editor.unsetStyle("ace_dragging"); + this.dragWaitEnd(); + }; + this.startDrag = function(){ var target = this.editor.container; - this.setState("dragReady"); - this.editor.renderer.$cursorLayer.setBlinking(false); target.draggable = true; + this.editor.renderer.$cursorLayer.setBlinking(false); this.editor.setStyle("ace_dragging"); - if (useragent.isIE) { - // IE does not handle [draggable] attribute set after mousedown - event.addListener(target, "mousemove", forceDragIE); - } + this.setState("dragReady"); }; - this.dragReadyEnd = function(e) { - var target = this.editor.container; - target.draggable = false; - this.editor.unsetStyle("ace_dragging"); - if (useragent.isIE) { - event.removeListener(target, "mousemove", forceDragIE); + this.onMouseDrag = function(e) { + if (useragent.isOpera) + document.getSelection().removeAllRanges(); + if (useragent.isIE && this.state == "dragReady") { + // IE does not handle [draggable] attribute set after mousedown + var target = this.editor.container; + 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) + this.startSelect(this.mousedownEvent.getDocumentPosition()); } - this.editor.renderer.$cursorLayer.setBlinking(!this.editor.getReadOnly()); - this.startSelect(this.mousedownEvent.getDocumentPosition(), true); - this.selectEnd(); }; this.onMouseDown = function(e) { if (!this.$dragEnabled) return; - var inSelection = e.inSelection(); - var editor = this.editor; this.mousedownEvent = e; + var editor = this.editor; + var inSelection = e.inSelection(); var button = e.getButton(); if (button === 0 && inSelection) { this.mousedownEvent.time = (new Date()).getTime(); @@ -339,29 +343,16 @@ function DragdropHandler(mouseHandler) { mouseTarget.draggable = false; }, 8); } - this.captureMouse(e, "dragWait"); + this.setState("dragWait"); } else { this.startDrag(); - this.captureMouse(e); - } - - if (useragent.isOpera) { - var cancelSelection = function(e){ - document.getSelection().removeAllRanges(); - editor.container.removeEventListener("mousemove", cancelSelection); - }; - editor.container.addEventListener("mousemove", cancelSelection); } + this.captureMouse(e, this.onMouseDrag.bind(this)); // TODO: a better way to prevent default handler without preventing browser default action e.defaultPrevented = true; } }; - function forceDragIE() { - event.removeListener(this, "mousemove", forceDragIE); - this.dragDrop(); - } - function calcDistance(ax, ay, bx, by) { return Math.sqrt(Math.pow(bx - ax, 2) + Math.pow(by - ay, 2)); } diff --git a/lib/ace/mouse/mouse_handler.js b/lib/ace/mouse/mouse_handler.js index 1e8895b553c..2565bd19b8a 100644 --- a/lib/ace/mouse/mouse_handler.js +++ b/lib/ace/mouse/mouse_handler.js @@ -99,10 +99,7 @@ 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; @@ -117,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) { From cb7a0d5d47827d54ff8713e40c3d28a3ee99e04e Mon Sep 17 00:00:00 2001 From: DanyaPostfactum Date: Sun, 15 Sep 2013 20:41:22 +1000 Subject: [PATCH 08/13] Improve native selection prevention on dnd --- lib/ace/css/editor.css | 1 + lib/ace/mouse/dragdrop_handler.js | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/ace/css/editor.css b/lib/ace/css/editor.css index 0fe2c4ddb59..ba446b33a7d 100644 --- a/lib/ace/css/editor.css +++ b/lib/ace/css/editor.css @@ -5,6 +5,7 @@ font-size: 12px; line-height: normal; color: black; + -ms-user-select: none; -moz-user-select: none; -webkit-user-select: none; user-select: none; diff --git a/lib/ace/mouse/dragdrop_handler.js b/lib/ace/mouse/dragdrop_handler.js index e3788ded309..e936c917485 100644 --- a/lib/ace/mouse/dragdrop_handler.js +++ b/lib/ace/mouse/dragdrop_handler.js @@ -305,8 +305,6 @@ function DragdropHandler(mouseHandler) { }; this.onMouseDrag = function(e) { - if (useragent.isOpera) - document.getSelection().removeAllRanges(); if (useragent.isIE && this.state == "dragReady") { // IE does not handle [draggable] attribute set after mousedown var target = this.editor.container; @@ -331,6 +329,9 @@ function DragdropHandler(mouseHandler) { var button = e.getButton(); if (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) { From b1ba675cfd82ab84bdad4f38908462e07c127418 Mon Sep 17 00:00:00 2001 From: DanyaPostfactum Date: Sun, 15 Sep 2013 20:48:33 +1000 Subject: [PATCH 09/13] Fix dbl/triple click issues of native dnd --- lib/ace/mouse/default_gutter_handler.js | 3 ++- lib/ace/mouse/default_handlers.js | 27 +++++++++++++++++-------- lib/ace/mouse/dragdrop_handler.js | 5 ++++- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/lib/ace/mouse/default_gutter_handler.js b/lib/ace/mouse/default_gutter_handler.js index bf3da95638d..9437d2e47bb 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 9f80e9f4db9..3e1e528409a 100644 --- a/lib/ace/mouse/default_handlers.js +++ b/lib/ace/mouse/default_handlers.js @@ -93,7 +93,14 @@ function DefaultHandlers(mouseHandler) { } } - this.startSelect(pos, true); + if (!inSelection || this.$clickSelection || ev.getShiftKey() || editor.inMultiSelectMode) { + // Directly pick STATE_SELECT, since the user is not clicking inside + // a selection. + this.startSelect(pos); + } else if (inSelection) { + this.mousedownEvent.time = (new Date()).getTime(); + this.startSelect(pos); + } this.captureMouse(ev); return ev.preventDefault(); }; @@ -101,13 +108,16 @@ function DefaultHandlers(mouseHandler) { this.startSelect = function(pos) { pos = pos || this.editor.renderer.screenToTextCoordinates(this.x, this.y); var editor = this.editor; - if (this.mousedownEvent.getShiftKey()) { - editor.selection.selectToPosition(pos); - } - else if (!this.$clickSelection) { - editor.moveCursorToPosition(pos); - editor.selection.clearSelection(); - } + // 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(); } @@ -221,6 +231,7 @@ function DefaultHandlers(mouseHandler) { editor.selectAll(); this.$clickSelection = editor.getSelectionRange(); this.setState("null"); + this.selectEnd(); }; this.onMouseWheel = function(ev) { diff --git a/lib/ace/mouse/dragdrop_handler.js b/lib/ace/mouse/dragdrop_handler.js index e936c917485..41138bee883 100644 --- a/lib/ace/mouse/dragdrop_handler.js +++ b/lib/ace/mouse/dragdrop_handler.js @@ -84,6 +84,8 @@ function DragdropHandler(mouseHandler) { 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"); @@ -327,7 +329,8 @@ function DragdropHandler(mouseHandler) { var inSelection = e.inSelection(); var button = e.getButton(); - if (button === 0 && inSelection) { + 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) From ad4e57e600d073d9c6a78a3eb9870359e70eaca2 Mon Sep 17 00:00:00 2001 From: DanyaPostfactum Date: Tue, 17 Sep 2013 19:02:29 +1000 Subject: [PATCH 10/13] Fix webkit fake mousemove event (breaking dnd) issue --- lib/ace/mouse/dragdrop_handler.js | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/lib/ace/mouse/dragdrop_handler.js b/lib/ace/mouse/dragdrop_handler.js index 41138bee883..302538e13b0 100644 --- a/lib/ace/mouse/dragdrop_handler.js +++ b/lib/ace/mouse/dragdrop_handler.js @@ -286,19 +286,20 @@ function DragdropHandler(mouseHandler) { }; this.dragWaitEnd = function() { + var target = this.editor.container; + target.draggable = false; this.startSelect(this.mousedownEvent.getDocumentPosition()); this.selectEnd(); }; this.dragReadyEnd = function(e) { - var target = this.editor.container; - target.draggable = false; 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); @@ -307,17 +308,19 @@ function DragdropHandler(mouseHandler) { }; 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 target = this.editor.container; 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) + if (distance > 0) { + target.draggable = false; this.startSelect(this.mousedownEvent.getDocumentPosition()); + } } }; @@ -338,14 +341,9 @@ function DragdropHandler(mouseHandler) { if (editor.getDragDelay()) { // https://code.google.com/p/chromium/issues/detail?id=286700 if (useragent.isWebKit) { - var self = this; self.cancelDrag = true; var mouseTarget = editor.container; mouseTarget.draggable = true; - setTimeout(function(){ - self.cancelDrag = false; - mouseTarget.draggable = false; - }, 8); } this.setState("dragWait"); } else { From e1ff3820410f41922e7fc34f3f5e61dec5ae90bf Mon Sep 17 00:00:00 2001 From: DanyaPostfactum Date: Tue, 17 Sep 2013 19:05:35 +1000 Subject: [PATCH 11/13] Fix IE autoscroll `drop` triggering issue Add overlay (css ::before generated content) while dragging to prevent IE to trigger `drop` event while autoscrolling --- lib/ace/css/editor.css | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/ace/css/editor.css b/lib/ace/css/editor.css index ba446b33a7d..ea3bb689b34 100644 --- a/lib/ace/css/editor.css +++ b/lib/ace/css/editor.css @@ -31,6 +31,17 @@ 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; } From 8ebf34a385ff69e4d462a39b8b23ae745aa44694 Mon Sep 17 00:00:00 2001 From: DanyaPostfactum Date: Tue, 17 Sep 2013 19:07:16 +1000 Subject: [PATCH 12/13] Add autoscroll delay --- lib/ace/mouse/dragdrop_handler.js | 64 +++++++++++++++++++++++++++---- 1 file changed, 56 insertions(+), 8 deletions(-) diff --git a/lib/ace/mouse/dragdrop_handler.js b/lib/ace/mouse/dragdrop_handler.js index 302538e13b0..9da7893359f 100644 --- a/lib/ace/mouse/dragdrop_handler.js +++ b/lib/ace/mouse/dragdrop_handler.js @@ -35,6 +35,10 @@ 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; @@ -62,6 +66,9 @@ function DragdropHandler(mouseHandler) { var timerId, range; var dragCursor, counter = 0; var dragOperation; + var autoScrollStartTime; + var cursorMovedTime; + var cursorPointOnCaretMoved; this.onDragStart = function(e) { // webkit workaround, see this.onMouseDown @@ -185,8 +192,29 @@ function DragdropHandler(mouseHandler) { event.addListener(mouseTarget, "dragleave", this.onDragLeave.bind(mouseHandler)); event.addListener(mouseTarget, "drop", this.onDrop.bind(mouseHandler)); - function onDragInterval() { - dragCursor = editor.renderer.screenToTextCoordinates(x, y); + 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(); @@ -202,15 +230,31 @@ function DragdropHandler(mouseHandler) { }; var nearestXOffset = Math.min(offsets.x.left, offsets.x.right); var nearestYOffset = Math.min(offsets.y.top, offsets.y.bottom); - var scrollCursor = {row: dragCursor.row, column: dragCursor.column}; + 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); } - editor.moveCursorToPosition(dragCursor); - editor.renderer.scrollCursorIntoView(scrollCursor); + 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() { @@ -232,6 +276,8 @@ function DragdropHandler(mouseHandler) { editor.$blockScrolling -= 1; range = null; counter = 0; + autoScrollStartTime = null; + cursorMovedTime = null; event.removeListener(document, "mousemove", onMouseMove); } @@ -355,11 +401,13 @@ function DragdropHandler(mouseHandler) { } }; - function calcDistance(ax, ay, bx, by) { - return Math.sqrt(Math.pow(bx - ax, 2) + Math.pow(by - ay, 2)); - } }).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 From 8a841df6e2f4ce6e1d8b38600e70204bb2729ada Mon Sep 17 00:00:00 2001 From: DanyaPostfactum Date: Wed, 18 Sep 2013 17:51:52 +1000 Subject: [PATCH 13/13] Capture mouse on quadclick --- lib/ace/mouse/default_handlers.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/ace/mouse/default_handlers.js b/lib/ace/mouse/default_handlers.js index 3e1e528409a..0c0da34c6db 100644 --- a/lib/ace/mouse/default_handlers.js +++ b/lib/ace/mouse/default_handlers.js @@ -47,7 +47,7 @@ function DefaultHandlers(mouseHandler) { editor.setDefaultHandler("quadclick", this.onQuadClick.bind(mouseHandler)); editor.setDefaultHandler("mousewheel", this.onMouseWheel.bind(mouseHandler)); - var exports = ["select", "startSelect", "selectEnd", "selectByWordsEnd", + var exports = ["select", "startSelect", "selectEnd", "selectAllEnd", "selectByWordsEnd", "selectByLinesEnd", "dragWait", "dragWaitEnd", "focusWait"]; exports.forEach(function(x) { @@ -181,6 +181,7 @@ function DefaultHandlers(mouseHandler) { }; this.selectEnd = + this.selectAllEnd = this.selectByWordsEnd = this.selectByLinesEnd = function() { this.editor.unsetStyle("ace_selecting"); @@ -230,8 +231,7 @@ function DefaultHandlers(mouseHandler) { editor.selectAll(); this.$clickSelection = editor.getSelectionRange(); - this.setState("null"); - this.selectEnd(); + this.setState("selectAll"); }; this.onMouseWheel = function(ev) {