From 7a50a070e9212e58dfc93701d56a31379b3881df Mon Sep 17 00:00:00 2001 From: nightwing Date: Fri, 27 Apr 2012 11:27:37 +0400 Subject: [PATCH] cleanup mouse handling --- lib/ace/mouse/default_gutter_handler.js | 17 +- lib/ace/mouse/default_handlers.js | 376 +++++++++++++----------- lib/ace/mouse/mouse_handler.js | 46 ++- lib/ace/virtual_renderer.js | 2 +- 4 files changed, 265 insertions(+), 176 deletions(-) diff --git a/lib/ace/mouse/default_gutter_handler.js b/lib/ace/mouse/default_gutter_handler.js index c9e69927..d03e18ce 100644 --- a/lib/ace/mouse/default_gutter_handler.js +++ b/lib/ace/mouse/default_gutter_handler.js @@ -39,13 +39,24 @@ define(function(require, exports, module) { "use strict"; -function GutterHandler(editor) { - editor.setDefaultHandler("gutterclick", function(e) { +function GutterHandler(mouseHandler) { + var editor = mouseHandler.editor; + + mouseHandler.editor.setDefaultHandler("guttermousedown", function(e) { + if (e.domEvent.target.className.indexOf("ace_gutter-cell") == -1) + return; + + if (!editor.isFocused()) + return; + var row = e.getDocumentPosition().row; var selection = editor.session.selection; - + selection.moveCursorTo(row, 0); selection.selectLine(); + + mouseHandler.$clickSelection = selection.getRange(); + mouseHandler.captureMouse(e, "selectByLines"); }); } diff --git a/lib/ace/mouse/default_handlers.js b/lib/ace/mouse/default_handlers.js index c7343cf5..8233185c 100644 --- a/lib/ace/mouse/default_handlers.js +++ b/lib/ace/mouse/default_handlers.js @@ -22,6 +22,7 @@ * Contributor(s): * Fabian Jakobs * Mike de Boer + * Harutyun Amirjanyan * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or @@ -44,38 +45,46 @@ var event = require("../lib/event"); var dom = require("../lib/dom"); var BrowserFocus = require("../lib/browser_focus").BrowserFocus; -var STATE_UNKNOWN = 0; -var STATE_SELECT = 1; -var STATE_DRAG = 2; var DRAG_OFFSET = 5; // pixels -function DefaultHandlers(editor) { - this.editor = editor; - this.$clickSelection = null; - this.browserFocus = new BrowserFocus(); - editor.setDefaultHandler("mousedown", this.onMouseDown.bind(this)); - editor.setDefaultHandler("dblclick", this.onDoubleClick.bind(this)); - editor.setDefaultHandler("tripleclick", this.onTripleClick.bind(this)); - editor.setDefaultHandler("quadclick", this.onQuadClick.bind(this)); - editor.setDefaultHandler("mousewheel", this.onScroll.bind(this)); + +function DefaultHandlers(mouseHandler) { + mouseHandler.$clickSelection = null; + mouseHandler.browserFocus = new BrowserFocus(); + + var editor = mouseHandler.editor; + editor.setDefaultHandler("mousedown", this.onMouseDown.bind(mouseHandler)); + editor.setDefaultHandler("dblclick", this.onDoubleClick.bind(mouseHandler)); + editor.setDefaultHandler("tripleclick", this.onTripleClick.bind(mouseHandler)); + editor.setDefaultHandler("quadclick", this.onQuadClick.bind(mouseHandler)); + editor.setDefaultHandler("mousewheel", this.onScroll.bind(mouseHandler)); + + var exports = ["select", "startSelect", "drag", "dragEnd", "dragWait", + "dragWaitEnd", "startDrag"]; + + exports.forEach(function(x) { + mouseHandler[x] = this[x]; + }, this); + + mouseHandler.selectByLines = this.extendSelectionBy.bind(mouseHandler, "getLineRange"); + mouseHandler.selectByWords = this.extendSelectionBy.bind(mouseHandler, "getWordRange"); } (function() { - + this.onMouseDown = function(ev) { + this.mousedownEvent = ev; var inSelection = ev.inSelection(); - var pageX = ev.pageX; - var pageY = ev.pageY; var pos = ev.getDocumentPosition(); var editor = this.editor; var _self = this; - + + this.ev = ev var selectionRange = editor.getSelectionRange(); var selectionEmpty = selectionRange.isEmpty(); - var state = STATE_UNKNOWN; - + var button = ev.getButton(); if (button !== 0) { if (selectionEmpty) { @@ -91,187 +100,224 @@ function DefaultHandlers(editor) { // if this click caused the editor to be focused should not clear the // selection - if ( - inSelection && ( - !this.browserFocus.isFocused() - || new Date().getTime() - this.browserFocus.lastFocus < 20 - || !editor.isFocused() - ) - ) { + if (inSelection && !editor.isFocused()) { editor.focus(); return; } - if (!inSelection) { + if (!inSelection || this.$clickSelection || ev.getShiftKey()) { // Directly pick STATE_SELECT, since the user is not clicking inside // a selection. - onStartSelect(pos); - } - - var mousePageX = pageX, mousePageY = pageY; - var mousedownTime = (new Date()).getTime(); - var dragCursor, dragRange, dragSelectionMarker; - - var onMouseSelection = function(e) { - mousePageX = event.getDocumentX(e); - mousePageY = event.getDocumentY(e); - }; - - var onMouseSelectionEnd = function(e) { - clearInterval(timerId); - if (state == STATE_UNKNOWN) - onStartSelect(pos); - else if (state == STATE_DRAG) - onMouseDragSelectionEnd(e); - - _self.$clickSelection = null; - state = STATE_UNKNOWN; - }; - - var onMouseDragSelectionEnd = function(e) { - dom.removeCssClass(editor.container, "ace_dragging"); - editor.session.removeMarker(dragSelectionMarker); - - if (!editor.$mouseHandler.$clickSelection) { - if (!dragCursor) { - editor.moveCursorToPosition(pos); - editor.selection.clearSelection(); - } - } - - if (!dragCursor) - return; - - if (dragRange.contains(dragCursor.row, dragCursor.column)) { - dragCursor = null; - return; - } - - editor.clearSelection(); - if (e && (e.ctrlKey || e.altKey)) { - var session = editor.session; - var newRange = session.insert(dragCursor, session.getTextRange(dragRange)); + this.startSelect(pos); + } else if (inSelection) { + var e = ev.domEvent; + if ((e.ctrlKey || e.altKey)) { + this.startDrag(); } else { - var newRange = editor.moveText(dragRange, dragCursor); + this.mousedownEvent.time = (new Date()).getTime(); + this.setState("dragWait"); } - if (!newRange) { - dragCursor = null; - return; - } - - editor.selection.setSelectionRange(newRange); - }; - - var onSelectionInterval = function() { - if (state == STATE_UNKNOWN) { - var distance = calcDistance(pageX, pageY, mousePageX, mousePageY); - var time = (new Date()).getTime(); - - if (distance > DRAG_OFFSET) { - state = STATE_SELECT; - var cursor = editor.renderer.screenToTextCoordinates(mousePageX, mousePageY); - onStartSelect(cursor); - } - else if ((time - mousedownTime) > editor.getDragDelay()) { - state = STATE_DRAG; - dragRange = editor.getSelectionRange(); - var style = editor.getSelectionStyle(); - dragSelectionMarker = editor.session.addMarker(dragRange, "ace_selection", style); - editor.clearSelection(); - dom.addCssClass(editor.container, "ace_dragging"); - } - - } - - if (state == STATE_DRAG) - onDragSelectionInterval(); - else if (state == STATE_SELECT) - onUpdateSelectionInterval(); - }; - - function onStartSelect(pos) { - if (ev.getShiftKey()) { - editor.selection.selectToPosition(pos); - } - else { - if (!_self.$clickSelection) { - editor.moveCursorToPosition(pos); - editor.selection.clearSelection(); - } - } - state = STATE_SELECT; } - var onUpdateSelectionInterval = function() { - var anchor; - var cursor = editor.renderer.screenToTextCoordinates(mousePageX, mousePageY); - - if (_self.$clickSelection) { - if (_self.$clickSelection.contains(cursor.row, cursor.column)) { - editor.selection.setSelectionRange(_self.$clickSelection); - } - else { - if (_self.$clickSelection.compare(cursor.row, cursor.column) == -1) { - anchor = _self.$clickSelection.end; - } - else { - anchor = _self.$clickSelection.start; - } - editor.selection.setSelectionAnchor(anchor.row, anchor.column); - editor.selection.selectToPosition(cursor); - } - } - else { - editor.selection.selectToPosition(cursor); - } - - editor.renderer.scrollCursorIntoView(); - }; - - var onDragSelectionInterval = function() { - dragCursor = editor.renderer.screenToTextCoordinates(mousePageX, mousePageY); - editor.moveCursorToPosition(dragCursor); - }; - - event.capture(editor.container, onMouseSelection, onMouseSelectionEnd); - var timerId = setInterval(onSelectionInterval, 20); - - return ev.preventDefault(); + this.captureMouse(ev) }; - + + this.startSelect = function(pos) { + pos = pos || this.editor.renderer.screenToTextCoordinates(this.x, this.y); + if (this.mousedownEvent.getShiftKey()) { + this.editor.selection.selectToPosition(pos); + } + else if (!this.$clickSelection) { + this.editor.moveCursorToPosition(pos); + this.editor.selection.clearSelection(); + } + this.setState("select"); + } + + this.select = function() { + var anchor, editor = this.editor; + var cursor = editor.renderer.screenToTextCoordinates(this.x, this.y); + + if (this.$clickSelection) { + var cmp = this.$clickSelection.comparePoint(cursor); + + if (cmp == -1) { + anchor = this.$clickSelection.end; + } else if (cmp == 1) { + anchor = this.$clickSelection.start; + } else { + cursor = this.$clickSelection.end; + anchor = this.$clickSelection.start; + } + editor.selection.setSelectionAnchor(anchor.row, anchor.column); + } + editor.selection.selectToPosition(cursor); + + editor.renderer.scrollCursorIntoView(); + }; + + this.extendSelectionBy = function(unitName) { + var anchor, editor = this.editor; + var cursor = editor.renderer.screenToTextCoordinates(this.x, this.y); + var range = editor.selection[unitName](cursor.row, cursor.column); + + if (this.$clickSelection) { + var cmpStart = this.$clickSelection.comparePoint(range.start); + var cmpEnd = this.$clickSelection.comparePoint(range.end); + + if (cmpStart == -1 && cmpEnd <= 0) { + anchor = this.$clickSelection.end; + cursor = range.start; + } else if (cmpEnd == 1 && cmpStart >= 0) { + anchor = this.$clickSelection.start; + cursor = range.end; + } else if (cmpStart == -1 && cmpEnd == 1) { + cursor = range.end; + anchor = range.start; + } else { + cursor = this.$clickSelection.end; + anchor = this.$clickSelection.start; + } + editor.selection.setSelectionAnchor(anchor.row, anchor.column); + } + editor.selection.selectToPosition(cursor); + + 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(); + } + } + } + } + + editor.keyBinding.addKeyboardHandler(this.$dragKeybinding); + }; + + this.dragWait = function() { + 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(); + } else if ((time - this.mousedownEvent.time) > editor.getDragDelay()) { + 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; - + + this.setState("selectByWords"); + editor.moveCursorToPosition(pos); editor.selection.selectWord(); this.$clickSelection = editor.getSelectionRange(); }; - + this.onTripleClick = function(ev) { var pos = ev.getDocumentPosition(); var editor = this.editor; - + + this.setState("selectByLines"); + editor.moveCursorToPosition(pos); editor.selection.selectLine(); this.$clickSelection = editor.getSelectionRange(); }; - + this.onQuadClick = function(ev) { var editor = this.editor; - + editor.selectAll(); this.$clickSelection = editor.getSelectionRange(); + this.setState("select"); }; - + this.onScroll = function(ev) { var editor = this.editor; - + var isScrolable = editor.renderer.isScrollableBy(ev.wheelX * ev.speed, ev.wheelY * ev.speed); + if (isScrolable) { + this.$passScrollEvent = false; + } else { + if (this.$passScrollEvent) + return; + + if (!this.$scrollStopTimeout) { + var self = this; + this.$scrollStopTimeout = setTimeout(function() { + self.$passScrollEvent = true; + self.$scrollStopTimeout = null; + }, 200); + } + } + editor.renderer.scrollBy(ev.wheelX * ev.speed, ev.wheelY * ev.speed); - if (editor.renderer.isScrollableBy(ev.wheelX * ev.speed, ev.wheelY * ev.speed)) - return ev.preventDefault(); + return ev.preventDefault(); }; - + }).call(DefaultHandlers.prototype); exports.DefaultHandlers = DefaultHandlers; diff --git a/lib/ace/mouse/mouse_handler.js b/lib/ace/mouse/mouse_handler.js index df4f0640..c44d03ba 100644 --- a/lib/ace/mouse/mouse_handler.js +++ b/lib/ace/mouse/mouse_handler.js @@ -47,10 +47,10 @@ var MouseEvent = require("./mouse_event").MouseEvent; var MouseHandler = function(editor) { this.editor = editor; - - new DefaultHandlers(editor); - new DefaultGutterHandler(editor); - + + new DefaultHandlers(this); + new DefaultGutterHandler(this); + event.addListener(editor.container, "mousedown", function(e) { editor.focus(); return event.preventDefault(e); @@ -67,7 +67,7 @@ var MouseHandler = function(editor) { event.addMultiMouseDownListener(mouseTarget, 0, 3, 600, this.onMouseEvent.bind(this, "tripleclick")); event.addMultiMouseDownListener(mouseTarget, 0, 4, 600, this.onMouseEvent.bind(this, "quadclick")); event.addMouseWheelListener(editor.container, this.onMouseWheel.bind(this, "mousewheel")); - + var gutterEl = editor.renderer.$gutter; event.addListener(gutterEl, "mousedown", this.onMouseEvent.bind(this, "guttermousedown")); event.addListener(gutterEl, "click", this.onMouseEvent.bind(this, "gutterclick")); @@ -89,7 +89,7 @@ var MouseHandler = function(editor) { this.onMouseEvent = function(name, e) { this.editor._emit(name, new MouseEvent(e, this.editor)); }; - + this.$dragDelay = 250; this.setDragDelay = function(dragDelay) { this.$dragDelay = dragDelay; @@ -113,10 +113,42 @@ var MouseHandler = function(editor) { mouseEvent.speed = this.$scrollSpeed * 2; mouseEvent.wheelX = e.wheelX; mouseEvent.wheelY = e.wheelY; - + this.editor._emit(name, mouseEvent); }; + this.setState = function(state) { + this.state = state; + }; + + this.captureMouse = function(ev, state) { + if (state) + this.setState(state); + + this.x = ev.x; + this.y = ev.y; + + var self = this; + var onMouseSelection = function(e) { + self.x = e.clientX; + self.y = e.clientY; + }; + + var onMouseSelectionEnd = function(e) { + clearInterval(timerId); + self[self.state + "End"] && self[self.state + "End"](e); + self.$clickSelection = null; + }; + + var onSelectionInterval = function() { + self[self.state] && self[self.state](); + } + + event.capture(this.editor.container, onMouseSelection, onMouseSelectionEnd); + var timerId = setInterval(onSelectionInterval, 20); + + ev.preventDefault(); + }; }).call(MouseHandler.prototype); exports.MouseHandler = MouseHandler; diff --git a/lib/ace/virtual_renderer.js b/lib/ace/virtual_renderer.js index 8c1ebaaa..0c2c0880 100644 --- a/lib/ace/virtual_renderer.js +++ b/lib/ace/virtual_renderer.js @@ -1110,7 +1110,7 @@ var VirtualRenderer = function(container, theme) { var row = Math.floor((y + this.scrollTop - canvasPos.top) / this.lineHeight); var col = Math.round(offset); - return {row: row, column: col, side: offset - col}; + return {row: row, column: col, side: offset - col > 0 ? 1 : -1}; }; this.screenToTextCoordinates = function(x, y) {