/* * Copyright (c) 2011 OJC Technologies. All rights reserved. * @author: Jake Smith * * Copyright (c) 2013, 2015 David Young. All rights reserved. */ function ClickChooser(event) { this.x = event.pageX; this.y = event.pageY; this.helper = new ProximityHelper(this.x, this.y); this.getPositionInfo = function(terminalNodes) { var clickedTextNode, posInfo; clickedTextNode = this.getClickedTextNode(terminalNodes); if (clickedTextNode == null) { posInfo = this.getClosestTextNode(terminalNodes); } else { posInfo = this.specifyIndex(clickedTextNode); } return posInfo; } //-----------------------// // "Private" functions // //-----------------------// this.getClosestTextNode = function(terminalNodes) { var col = [], row = []; var rel; var above = null, below = null, left = null, right = null; var i, n; printer.print("getClosestTextNode: " + terminalNodes.length + " terminal nodes"); for (i = 0; i < terminalNodes.length; i += 1) { var pairs; n = terminalNodes[i]; pairs = this.nodeRectPairs(n); for (j = 0; j < pairs.length; j++) { var pair; pair = pairs[j]; rel = rectRelationship(this.helper, pair[1], this.x, this.y); switch (rel) { case this.helper.ABOVE: printer.print("getClosestTextNode: rel ABOVE"); if (above == null || pair[1].top < above[1].top) above = pair; break; case this.helper.BELOW: printer.print("getClosestTextNode: rel BELOW"); if (below == null || pair[1].bottom > below[1].bottom) below = pair; break; case this.helper.LEFT: printer.print("getClosestTextNode: rel LEFT"); if (left == null || pair[1].left < left[1].left) left = pair; break; case this.helper.RIGHT: printer.print("getClosestTextNode: rel RIGHT"); if (right == null || pair[1].right > right[1].right) right = pair; break; default: printer.print("getClosestTextNode: unknown rel " + rel); break; } } } if (left != null) { printer.print("getClosestTextNode: returning left"); pair = left; this.x = pair[1].left + window.scrollX; this.y = (pair[1].bottom + pair[1].top) / 2 + window.scrollY; } else if (right != null) { printer.print("getClosestTextNode: returning right"); pair = right; this.x = pair[1].right + window.scrollX; this.y = (pair[1].bottom + pair[1].top) / 2 + window.scrollY; } else if (above != null) { printer.print("getClosestTextNode: returning above"); pair = above; this.x = pair[1].left + window.scrollX; this.y = (pair[1].bottom + pair[1].top) / 2 + window.scrollY; } else if (below != null) { printer.print("getClosestTextNode: returning below"); pair = below; this.x = pair[1].right + window.scrollX; this.y = (pair[1].bottom + pair[1].top) / 2 + window.scrollY; } else { printer.print("getClosestTextNode: no closest"); return null; // not in any of the terminal nodes } var clicked_node = this.getClickedTextNode(terminalNodes); if (clicked_node == null) { printer.print("getClosestTextNode: -ish"); return this.specifyIndexExterior(pair[0]); } else { return this.specifyIndex(clicked_node); } } this.getClickedTextNode = function(terminalNodes) { var i, consideredNode; for (i = 0; i < terminalNodes.length; i += 1) { consideredNode = terminalNodes[i]; if (this.clickInBounds(consideredNode)) { return consideredNode; } } return null; // not in any of the terminal nodes } this.clickInLeftBounds = function(textNode) { var i, rects, tempSpan, parent, xmin, xmax, ymin, ymax; parent = textNode.parentNode; tempSpan = makeTempSpan(textNode); parent.replaceChild(tempSpan, textNode); rects = tempSpan.getClientRects(); xmax = -1000000; // XXX use a more suitable initial max & min xmin = 1000000; // XXX ymax = -1000000; // XXX use a more suitable initial max & min ymin = 1000000; // XXX for (i = 0; i < rects.length; i += 1) { if (rects[i].top < ymin) ymin = rects[i].top; if (rects[i].bottom > ymax) ymax = rects[i].bottom; if (rects[i].left < xmin) xmin = rects[i].left; if (rects[i].right > xmax) xmax = rects[i].right; } parent.replaceChild(textNode, tempSpan); xmin += window.scrollX; ymin += window.scrollY; xmax += window.scrollX; ymax += window.scrollY; if (xmax < xmin || ymax < ymin) { printer.print("clickInLeftBounds: empty bounds"); return false; } if (this.y < ymin || ymax < this.y) { printer.print("clickInLeftBounds: " + this.y + " out of y bounds " + ymin + ":" + ymax); return false; } if (this.x < xmin || (xmin + xmax) <= this.x * 2) return false; return true; } this.nodeRectPairs = function(textNode) { var i, rects, tempSpan, parent; var pairs = []; parent = textNode.parentNode; tempSpan = makeTempSpan(textNode); parent.replaceChild(tempSpan, textNode); rects = tempSpan.getClientRects(); for (i = 0; i < rects.length; i += 1) { pairs.push(new Array(textNode, rects[i])); } parent.replaceChild(textNode, tempSpan); printer.print("nodeRectPairs: " + pairs.length + " pairs"); return pairs; } this.clickInBounds = function(textNode) { var i, rects, tempSpan, flag, parent; parent = textNode.parentNode; flag = false; tempSpan = makeTempSpan(textNode); parent.replaceChild(tempSpan, textNode); rects = tempSpan.getClientRects(); for (i = 0; i < rects.length; i += 1) { if (rectContains(this.helper, rects[i], this.x, this.y)) { flag = true; } } parent.replaceChild(textNode, tempSpan); return flag; } this.clickLeftOfSolitaryBBox = function(textNode) { var i, rects, tempSpan, rc, parent; parent = textNode.parentNode; tempSpan = makeTempSpan(textNode); parent.replaceChild(tempSpan, textNode); rects = tempSpan.getClientRects(); rc = (rects.length == 1 && leftOfRect(rects[0], this.x, this.y)); parent.replaceChild(textNode, tempSpan); return rc; } this.clickRightOfSolitaryBBox = function(textNode) { var i, rects, tempSpan, rc, parent; parent = textNode.parentNode; tempSpan = makeTempSpan(textNode); parent.replaceChild(tempSpan, textNode); rects = tempSpan.getClientRects(); rc = (rects.length == 1 && rightOfRect(rects[0], this.x, this.y)); parent.replaceChild(textNode, tempSpan); return rc; } this.specifyIndexExterior = function(start_node) { var textLength; var candidate, node, idx; var parent = start_node.parentNode; var candidates = []; candidates.push(new Array(start_node, 0)); while (candidates.length > 0) { candidate = candidates.pop(); node = candidate[0]; idx = candidate[1]; textLength = node.nodeValue.length; if (this.clickLeftOfSolitaryBBox(node)) break; if (textLength <= 1) continue; var fence = Math.ceil(textLength / 2); var newNode = node.splitText(fence); candidates.push(new Array(newNode, idx + fence)); candidates.push(new Array(node, idx)); } parent.normalize(); return new Array(this.helper.WITHIN, start_node, idx); } this.specifyIndex = function(node) { var textLength, posInfo, ERR; ERR = -1; textLength = node.nodeValue.length; if (textLength <= 1) { if (this.clickInLeftBounds(node)) { posInfo = new Array(this.helper.WITHIN, node, 0); } else { printer.print("right bounds"); posInfo = new Array(this.helper.WITHIN, node, 1); } } else { // length > 1 posInfo = this.splitAndSearch(node, textLength); } return posInfo; } this.splitAndSearch = function(node, textLength) { var fence, posInfo, parent; parent = node.parentNode fence = Math.ceil(textLength / 2); var newNode = node.splitText(fence); if (this.clickInBounds(node)) { posInfo = this.specifyIndex(node); } else if (this.clickInBounds(newNode)) { posInfo = this.specifyIndex(newNode); posInfo[2] += fence; posInfo[1] = node; } else { throw new Error("error: specifyIndex search"); } parent.normalize(); return posInfo; } } // ClickChooser(event) function leftOfRect(rect, x, y) { var l, r, t, b; l = rect.left + window.scrollX; r = rect.right + window.scrollX; t = Math.round(rect.top) + window.scrollY; b = Math.round(rect.bottom) + window.scrollY; // possibily rounding issues with Firefox (rect.top & bottom) if (y < t || y > b) return false; return x < l; } function rightOfRect(rect, x, y) { var l, r, t, b; l = rect.left + window.scrollX; r = rect.right + window.scrollX; t = Math.round(rect.top) + window.scrollY; b = Math.round(rect.bottom) + window.scrollY; // possibily rounding issues with Firefox (rect.top & bottom) if (y < t || y > b) return false; return r < x; } // standalone 'helper' function function rectContains(helper, rect, x, y) { return rectRelationship(helper, rect, x, y) == helper.WITHIN; } // standalone 'helper' function function rectRelationship(helper, rect, x, y) { var l, r, t, b; l = rect.left + window.scrollX; r = rect.right + window.scrollX; t = Math.round(rect.top) + window.scrollY; b = Math.round(rect.bottom) + window.scrollY; if (y < t) return helper.ABOVE; if (y > b) return helper.BELOW; if (x < l) return helper.LEFT; if (x > r) return helper.RIGHT; return helper.WITHIN; }