Bug 725392 - Source Editor: add a method to convert mouse coordinates to character offsets, r=msucan

This commit is contained in:
Mihai Sucan 2012-08-02 22:30:46 +03:00
parent 68c2283a28
commit 92b589e4c7
5 changed files with 297 additions and 0 deletions

View File

@ -2010,6 +2010,66 @@ SourceEditor.prototype = {
return breakpoints;
},
/**
* Convert the given rectangle from one coordinate reference to another.
*
* Known coordinate references:
* - "document" - gives the coordinates relative to the entire document.
* - "view" - gives the coordinates relative to the editor viewport.
*
* @param object aRect
* The rectangle to convert. Object properties: x, y, width and height.
* @param string aFrom
* The source coordinate reference.
* @param string aTo
* The destination coordinate reference.
* @return object aRect
* Returns the rectangle with changed coordinates.
*/
convertCoordinates: function SE_convertCoordinates(aRect, aFrom, aTo)
{
return this._view.convert(aRect, aFrom, aTo);
},
/**
* Get the character offset nearest to the given pixel location.
*
* @param number aX
* @param number aY
* @return number
* Returns the character offset at the given location.
*/
getOffsetAtLocation: function SE_getOffsetAtLocation(aX, aY)
{
return this._view.getOffsetAtLocation(aX, aY);
},
/**
* Get the pixel location, relative to the document, at the given character
* offset.
*
* @param number aOffset
* @return object
* The pixel location relative to the document being edited. Two
* properties are included: x and y.
*/
getLocationAtOffset: function SE_getLocationAtOffset(aOffset)
{
return this._view.getLocationAtOffset(aOffset);
},
/**
* Get the line location for a given character offset.
*
* @param number aOffset
* @return number
* The line location relative to the give character offset.
*/
getLineAtOffset: function SE_getLineAtOffset(aOffset)
{
return this._model.getLineAtOffset(aOffset);
},
/**
* Destroy/uninitialize the editor.
*/

View File

@ -31,6 +31,7 @@ MOCHITEST_BROWSER_FILES = \
browser_bug731721_debugger_stepping.js \
browser_bug729960_block_bracket_jump.js \
browser_bug744021_next_prev_bracket_jump.js \
browser_bug725392_mouse_coords_char_offset.js \
head.js \
include $(topsrcdir)/config/rules.mk

View File

@ -71,6 +71,15 @@ function editorLoaded()
ok(pos.line == 2 && pos.col == 0, "setCaretPosition(line) works, again");
}
let offsetLine = editor.getLineAtOffset(0);
is(offsetLine, 0, "getLineAtOffset() is correct for offset 0");
let offsetLine = editor.getLineAtOffset(6);
is(offsetLine, 1, "getLineAtOffset() is correct for offset 6");
let offsetLine = editor.getLineAtOffset(12);
is(offsetLine, 2, "getLineAtOffset() is correct for offset 12");
editor.destroy();
testWin.close();

View File

@ -0,0 +1,160 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
function test()
{
let testWin;
let editor;
let mousePos = { x: 36, y: 4 };
let expectedOffset = 5;
let maxDiff = 10;
waitForExplicitFinish();
function editorLoaded(aEditor, aWindow)
{
editor = aEditor;
testWin = aWindow;
let text = fillEditor(editor, 3);
editor.setText(text);
editor.setCaretOffset(0);
doMouseMove(testPage1);
}
function doMouseMove(aCallback)
{
function mouseEventHandler(aEvent)
{
editor.removeEventListener(editor.EVENTS.MOUSE_OUT, mouseEventHandler);
editor.removeEventListener(editor.EVENTS.MOUSE_OVER, mouseEventHandler);
editor.removeEventListener(editor.EVENTS.MOUSE_MOVE, mouseEventHandler);
executeSoon(aCallback.bind(null, aEvent));
}
editor.addEventListener(editor.EVENTS.MOUSE_MOVE, mouseEventHandler);
editor.addEventListener(editor.EVENTS.MOUSE_OUT, mouseEventHandler);
editor.addEventListener(editor.EVENTS.MOUSE_OVER, mouseEventHandler);
let target = editor.editorElement;
let targetWin = testWin;
EventUtils.synthesizeMouse(target, mousePos.x, mousePos.y,
{type: "mousemove"}, targetWin);
EventUtils.synthesizeMouse(target, mousePos.x, mousePos.y,
{type: "mouseout"}, targetWin);
EventUtils.synthesizeMouse(target, mousePos.x, mousePos.y,
{type: "mouseover"}, targetWin);
}
function checkValue(aValue, aExpectedValue)
{
let result = Math.abs(aValue - aExpectedValue) <= maxDiff;
if (!result) {
info("checkValue() given " + aValue + " expected " + aExpectedValue);
}
return result;
}
function testPage1(aEvent)
{
let {event: { clientX: clientX, clientY: clientY }, x: x, y: y} = aEvent;
info("testPage1 " + aEvent.type +
" clientX " + clientX + " clientY " + clientY +
" x " + x + " y " + y);
// x and y are in document coordinates.
// clientX and clientY are in view coordinates.
// since we are scrolled at the top, both are expected to be approximately
// the same.
ok(checkValue(x, mousePos.x), "x is in range");
ok(checkValue(y, mousePos.y), "y is in range");
ok(checkValue(clientX, mousePos.x), "clientX is in range");
ok(checkValue(clientY, mousePos.y), "clientY is in range");
// we give document-relative coordinates here.
let offset = editor.getOffsetAtLocation(x, y);
ok(checkValue(offset, expectedOffset), "character offset is correct");
let rect = {x: x, y: y};
let viewCoords = editor.convertCoordinates(rect, "document", "view");
ok(checkValue(viewCoords.x, clientX), "viewCoords.x is in range");
ok(checkValue(viewCoords.y, clientY), "viewCoords.y is in range");
rect = {x: clientX, y: clientY};
let docCoords = editor.convertCoordinates(rect, "view", "document");
ok(checkValue(docCoords.x, x), "docCoords.x is in range");
ok(checkValue(docCoords.y, y), "docCoords.y is in range");
// we are given document-relative coordinates.
let offsetPos = editor.getLocationAtOffset(expectedOffset);
ok(checkValue(offsetPos.x, x), "offsetPos.x is in range");
ok(checkValue(offsetPos.y, y), "offsetPos.y is in range");
// Scroll the view and test again.
let topIndex = Math.round(editor.getLineCount() / 2);
editor.setTopIndex(topIndex);
expectedOffset += editor.getLineStart(topIndex);
executeSoon(doMouseMove.bind(null, testPage2));
}
function testPage2(aEvent)
{
let {event: { clientX: clientX, clientY: clientY }, x: x, y: y} = aEvent;
info("testPage2 " + aEvent.type +
" clientX " + clientX + " clientY " + clientY +
" x " + x + " y " + y);
// after page scroll document coordinates need to be different from view
// coordinates.
ok(checkValue(x, mousePos.x), "x is not different from clientX");
ok(!checkValue(y, mousePos.y), "y is different from clientY");
ok(checkValue(clientX, mousePos.x), "clientX is in range");
ok(checkValue(clientY, mousePos.y), "clientY is in range");
// we give document-relative coordinates here.
let offset = editor.getOffsetAtLocation(x, y);
ok(checkValue(offset, expectedOffset), "character offset is correct");
let rect = {x: x, y: y};
let viewCoords = editor.convertCoordinates(rect, "document", "view");
ok(checkValue(viewCoords.x, clientX), "viewCoords.x is in range");
ok(checkValue(viewCoords.y, clientY), "viewCoords.y is in range");
rect = {x: clientX, y: clientY};
let docCoords = editor.convertCoordinates(rect, "view", "document");
ok(checkValue(docCoords.x, x), "docCoords.x is in range");
ok(checkValue(docCoords.y, y), "docCoords.y is in range");
// we are given document-relative coordinates.
let offsetPos = editor.getLocationAtOffset(expectedOffset);
ok(checkValue(offsetPos.x, x), "offsetPos.x is in range");
ok(checkValue(offsetPos.y, y), "offsetPos.y is in range");
executeSoon(testEnd);
}
function testEnd()
{
if (editor) {
editor.destroy();
}
if (testWin) {
testWin.close();
}
waitForFocus(finish, window);
}
openSourceEditorWindow(editorLoaded);
}

View File

@ -113,3 +113,70 @@ waitForSelection.__defineGetter__("_monotonicCounter", function () {
return waitForSelection.__monotonicCounter++;
});
/**
* Open a new window with a source editor inside.
*
* @param function aCallback
* The function you want invoked once the editor is loaded. The function
* is given two arguments: editor instance and the window object.
* @param object [aOptions]
* The options object to pass to the SourceEditor.init() method.
*/
function openSourceEditorWindow(aCallback, aOptions) {
const windowUrl = "data:text/xml;charset=UTF-8,<?xml version='1.0'?>" +
"<window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'" +
" title='Test for Source Editor' width='600' height='500'><box flex='1'/></window>";
const windowFeatures = "chrome,titlebar,toolbar,centerscreen,resizable,dialog=no";
let editor = null;
let testWin = Services.ww.openWindow(null, windowUrl, "_blank",
windowFeatures, null);
testWin.addEventListener("load", function onWindowLoad() {
testWin.removeEventListener("load", onWindowLoad, false);
waitForFocus(initEditor, testWin);
}, false);
function initEditor()
{
let tempScope = {};
Cu.import("resource:///modules/source-editor.jsm", tempScope);
let box = testWin.document.querySelector("box");
editor = new tempScope.SourceEditor();
editor.init(box, aOptions || {}, editorLoaded);
}
function editorLoaded()
{
editor.focus();
waitForFocus(aCallback.bind(null, editor, testWin), testWin);
}
}
/**
* Get text needed to fill the editor view.
*
* @param object aEditor
* The SourceEditor instance you work with.
* @param number aPages
* The number of pages you want filled with lines.
* @return string
* The string you can insert into the editor so you fill the desired
* number of pages.
*/
function fillEditor(aEditor, aPages) {
let view = aEditor._view;
let model = aEditor._model;
let lineHeight = view.getLineHeight();
let editorHeight = view.getClientArea().height;
let linesPerPage = Math.floor(editorHeight / lineHeight);
let totalLines = aPages * linesPerPage;
let text = "";
for (let i = 0; i < totalLines; i++) {
text += "l" + i + " lorem ipsum dolor sit amet. lipsum foobaris bazbaz,\n";
}
return text;
}