mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-30 00:01:50 +00:00
Bug 725392 - Source Editor: add a method to convert mouse coordinates to character offsets, r=msucan
This commit is contained in:
parent
68c2283a28
commit
92b589e4c7
@ -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.
|
||||
*/
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user