From 89368613389c381e2ab18479bbd5e04934577de0 Mon Sep 17 00:00:00 2001 From: DanyaPostfactum Date: Tue, 10 Sep 2013 21:41:20 +1000 Subject: [PATCH] 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 acea6f89..1244c1b0 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 9f31841c..9f8558da 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 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..bd63c66b 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); +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_handlers.js b/lib/ace/mouse/default_handlers.js index 8529c0fc..c57a1718 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 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..a100ec88 --- /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 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..aae8f5c4 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} });