cleanup mouse handling

This commit is contained in:
nightwing 2012-04-27 11:27:37 +04:00
commit 7a50a070e9
4 changed files with 265 additions and 176 deletions

View file

@ -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");
});
}

View file

@ -22,6 +22,7 @@
* Contributor(s):
* Fabian Jakobs <fabian AT ajax DOT org>
* Mike de Boer <mike AT ajax DOT org>
* Harutyun Amirjanyan <harutyun AT c9 DOT io>
*
* 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;

View file

@ -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;

View file

@ -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) {