You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@corinthia.apache.org by ja...@apache.org on 2015/08/17 10:50:11 UTC
[19/28] incubator-corinthia git commit: included MOC compiler for Qt
implementation
http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9bf02bb2/experiments/editorFramework/src/Javascript_Layer_0/Tables.js
----------------------------------------------------------------------
diff --git a/experiments/editorFramework/src/Javascript_Layer_0/Tables.js b/experiments/editorFramework/src/Javascript_Layer_0/Tables.js
new file mode 100644
index 0000000..f1f27be
--- /dev/null
+++ b/experiments/editorFramework/src/Javascript_Layer_0/Tables.js
@@ -0,0 +1,1362 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+var Tables_insertTable;
+var Tables_addAdjacentRow;
+var Tables_addAdjacentColumn;
+var Tables_removeAdjacentRow;
+var Tables_removeAdjacentColumn;
+var Tables_deleteRegion;
+var Tables_clearCells;
+var Tables_mergeCells;
+var Tables_splitSelection;
+var Tables_cloneRegion;
+var Tables_analyseStructure;
+var Tables_findContainingCell;
+var Tables_findContainingTable;
+var Tables_regionFromRange;
+var Tables_getSelectedTableId;
+var Tables_getProperties;
+var Tables_setProperties;
+var Tables_getColWidths;
+var Tables_setColWidths;
+var Tables_getGeometry;
+
+var Table_get;
+var Table_set;
+var Table_setRegion;
+var Table_fix;
+var Table_fixColumnWidths;
+var TableRegion_splitCells;
+
+(function() {
+
+ function Cell(element,row,col)
+ {
+ this.element = element;
+ this.row = row;
+ this.col = col;
+
+ if (element.hasAttribute("colspan"))
+ this.colspan = parseInt(element.getAttribute("colspan"));
+ else
+ this.colspan = 1;
+ if (element.hasAttribute("rowspan"))
+ this.rowspan = parseInt(element.getAttribute("rowspan"));
+ else
+ this.rowspan = 1;
+
+ if (this.colspan < 1)
+ this.colspan = 1;
+ if (this.rowspan < 1)
+ this.rowspan = 1;
+
+ this.top = this.row;
+ this.bottom = this.top + this.rowspan - 1;
+ this.left = this.col;
+ this.right = this.left + this.colspan - 1;
+ }
+
+ function Cell_setRowspan(cell,rowspan)
+ {
+ if (rowspan < 1)
+ rowspan = 1;
+ cell.rowspan = rowspan;
+ cell.bottom = cell.top + cell.rowspan - 1;
+ if (rowspan == 1)
+ DOM_removeAttribute(cell.element,"rowspan");
+ else
+ DOM_setAttribute(cell.element,"rowspan",rowspan);
+ }
+
+ function Cell_setColspan(cell,colspan)
+ {
+ if (colspan < 1)
+ colspan = 1;
+ cell.colspan = colspan;
+ cell.right = cell.left + cell.colspan - 1;
+ if (colspan == 1)
+ DOM_removeAttribute(cell.element,"colspan");
+ else
+ DOM_setAttribute(cell.element,"colspan",colspan);
+ }
+
+ function Table(element)
+ {
+ this.element = element;
+ this.row = 0;
+ this.col = 0;
+ this.cells = new Array();
+ this.numRows = 0;
+ this.numCols = 0;
+ this.translated = false;
+ this.cellsByElement = new NodeMap();
+ Table_processTable(this,element);
+ }
+
+ // public
+ Table_get = function(table,row,col)
+ {
+ if (table.cells[row] == null)
+ return null;
+ return table.cells[row][col];
+ }
+
+ // public
+ Table_set = function(table,row,col,cell)
+ {
+ if (table.numRows < row+1)
+ table.numRows = row+1;
+ if (table.numCols < col+1)
+ table.numCols = col+1;
+ if (table.cells[row] == null)
+ table.cells[row] = new Array();
+ table.cells[row][col] = cell;
+ }
+
+ // public
+ Table_setRegion = function(table,top,left,bottom,right,cell)
+ {
+ for (var row = top; row <= bottom; row++) {
+ for (var col = left; col <= right; col++) {
+ var destCell = Table_get(table,row,col);
+ DOM_deleteNode(destCell.element);
+ Table_set(table,row,col,cell);
+ }
+ }
+ }
+
+ function Table_processTable(table,node)
+ {
+ var type = node._type;
+ switch (node._type) {
+ case HTML_TD:
+ case HTML_TH: {
+ while (Table_get(table,table.row,table.col) != null)
+ table.col++;
+
+ var cell = new Cell(node,table.row,table.col);
+ table.cellsByElement.put(node,cell);
+
+ for (var r = 0; r < cell.rowspan; r++) {
+ for (var c = 0; c < cell.colspan; c++) {
+ Table_set(table,table.row+r,table.col+c,cell);
+ }
+ }
+ table.col += cell.colspan;
+ break;
+ }
+ case HTML_TR:
+ for (var child = node.firstChild; child != null; child = child.nextSibling)
+ Table_processTable(table,child);
+ table.row++;
+ table.col = 0;
+ break;
+ default:
+ for (var child = node.firstChild; child != null; child = child.nextSibling)
+ Table_processTable(table,child);
+ break;
+ }
+ }
+
+ // public
+ Tables_insertTable = function(rows,cols,width,numbered,caption,className)
+ {
+ UndoManager_newGroup("Insert table");
+
+ if (rows < 1)
+ rows = 1;
+ if (cols < 1)
+ cols = 1;
+
+ var haveCaption = (caption != null) && (caption != "");
+ var table = DOM_createElement(document,"TABLE");
+
+ if (width != null)
+ DOM_setStyleProperties(table,{"width": width});
+
+ if (className != null)
+ DOM_setAttribute(table,"class",className);
+
+ // Caption comes first
+ if (haveCaption) {
+ var tableCaption = DOM_createElement(document,"CAPTION");
+ DOM_appendChild(tableCaption,DOM_createTextNode(document,caption));
+ DOM_appendChild(table,tableCaption);
+ }
+
+ // Set equal column widths
+ var colWidth = Math.round(100/cols)+"%";
+ for (var c = 0; c < cols; c++) {
+ var col = DOM_createElement(document,"COL");
+ DOM_setAttribute(col,"width",colWidth);
+ DOM_appendChild(table,col);
+ }
+
+ var firstTD = null;
+
+ // Then the rows and columns
+ var tbody = DOM_createElement(document,"TBODY");
+ DOM_appendChild(table,tbody);
+ for (var r = 0; r < rows; r++) {
+ var tr = DOM_createElement(document,"TR");
+ DOM_appendChild(tbody,tr);
+ for (var c = 0; c < cols; c++) {
+ var td = DOM_createElement(document,"TD");
+ var p = DOM_createElement(document,"P");
+ var br = DOM_createElement(document,"BR");
+ DOM_appendChild(tr,td);
+ DOM_appendChild(td,p);
+ DOM_appendChild(p,br);
+
+ if (firstTD == null)
+ firstTD = td;
+ }
+ }
+
+ Clipboard_pasteNodes([table]);
+
+ // Now that the table has been inserted into the DOM tree, the outline code will
+ // have noticed it and added an id attribute, as well as a caption giving the
+ // table number.
+ Outline_setNumbered(table.getAttribute("id"),numbered);
+
+ // Place the cursor at the start of the first cell on the first row
+ var pos = new Position(firstTD,0);
+ pos = Position_closestMatchForwards(pos,Position_okForMovement);
+ Selection_set(pos.node,pos.offset,pos.node,pos.offset);
+
+ PostponedActions_add(UndoManager_newGroup);
+ }
+
+ // private
+ function createEmptyTableCell(elementName)
+ {
+ var br = DOM_createElement(document,"BR");
+ var p = DOM_createElement(document,"P");
+ var td = DOM_createElement(document,elementName);
+ DOM_appendChild(p,br);
+ DOM_appendChild(td,p);
+ return td;
+ }
+
+ // private
+ function addEmptyTableCell(newTR,elementName)
+ {
+ var td = createEmptyTableCell(elementName);
+ DOM_appendChild(newTR,td);
+ return td;
+ }
+
+ // private
+ function populateNewRow(structure,newTR,newRow,oldRow)
+ {
+ var col = 0;
+ while (col < structure.numCols) {
+ var existingCell = Table_get(structure,oldRow,col);
+ if (((newRow > oldRow) && (newRow < existingCell.row + existingCell.rowspan)) ||
+ ((newRow < oldRow) && (newRow >= existingCell.row))) {
+ Cell_setRowspan(existingCell,existingCell.rowspan+1);
+ }
+ else {
+ var td = addEmptyTableCell(newTR,existingCell.element.nodeName); // check-ok
+ if (existingCell.colspan != 1)
+ DOM_setAttribute(td,"colspan",existingCell.colspan);
+ }
+ col += existingCell.colspan;
+ }
+ }
+
+ function tableAtRightOfRange(range)
+ {
+ if (!Range_isEmpty(range))
+ return null;
+
+ var pos = Position_preferElementPosition(range.start);
+ if ((pos.node.nodeType == Node.ELEMENT_NODE) &&
+ (pos.offset < pos.node.childNodes.length) &&
+ (pos.node.childNodes[pos.offset]._type == HTML_TABLE)) {
+ var element = pos.node.childNodes[pos.offset];
+ var table = Tables_analyseStructure(element);
+ return table;
+ }
+ return null;
+ }
+
+ function tableAtLeftOfRange(range)
+ {
+ if (!Range_isEmpty(range))
+ return null;
+
+ var pos = Position_preferElementPosition(range.start);
+ if ((pos.node.nodeType == Node.ELEMENT_NODE) &&
+ (pos.offset > 0) &&
+ (pos.node.childNodes[pos.offset-1]._type == HTML_TABLE)) {
+ var element = pos.node.childNodes[pos.offset-1];
+ var table = Tables_analyseStructure(element);
+ return table;
+ }
+ return null;
+ }
+
+ function insertRowAbove(table,row)
+ {
+ var cell = Table_get(table,row,0);
+ var oldTR = cell.element.parentNode;
+ var newTR = DOM_createElement(document,"TR");
+ DOM_insertBefore(oldTR.parentNode,newTR,oldTR);
+ populateNewRow(table,newTR,row-1,row);
+ }
+
+ function insertRowBelow(table,row)
+ {
+ var cell = Table_get(table,row,0);
+ var oldTR = cell.element.parentNode;
+ var newTR = DOM_createElement(document,"TR");
+ DOM_insertBefore(oldTR.parentNode,newTR,oldTR.nextSibling);
+ populateNewRow(table,newTR,row+1,row);
+ }
+
+ function insertRowAdjacentToRange(range)
+ {
+ var table;
+
+ table = tableAtLeftOfRange(range);
+ if (table != null) {
+ insertRowBelow(table,table.numRows-1);
+ return;
+ }
+
+ table = tableAtRightOfRange(range);
+ if (table != null) {
+ insertRowAbove(table,0);
+ return;
+ }
+ }
+
+ // public
+ Tables_addAdjacentRow = function()
+ {
+ UndoManager_newGroup("Insert row below");
+ Selection_preserveWhileExecuting(function() {
+ var range = Selection_get();
+ var region = Tables_regionFromRange(range,true);
+ if (region != null)
+ insertRowBelow(region.structure,region.bottom);
+ else
+ insertRowAdjacentToRange(range);
+ });
+ UndoManager_newGroup();
+ }
+
+ // private
+ function getColElements(table)
+ {
+ var cols = new Array();
+ for (child = table.firstChild; child != null; child = child.nextSibling) {
+ switch (child._type) {
+ case HTML_COLGROUP:
+ for (var gc = child.firstChild; gc != null; gc = gc.nextSibling) {
+ if (gc._type == HTML_COL)
+ cols.push(gc);
+ }
+ break;
+ case HTML_COL:
+ cols.push(child);
+ break;
+ }
+ }
+ return cols;
+ }
+
+ // private
+ function getColWidths(colElements,expectedCount)
+ {
+ // FIXME: also handle the case where the width has been set as a CSS property in the
+ // style attribute. There's probably not much we can do if the width comes from a style
+ // rule elsewhere in the document though.
+ var colWidths = new Array();
+ for (var i = 0; i < colElements.length; i++) {
+ if (colElements[i].hasAttribute("width"))
+ colWidths.push(colElements[i].getAttribute("width"));
+ else
+ colWidths.push("");
+ }
+ return colWidths;
+ }
+
+ // private
+ function addMissingColElements(structure,colElements)
+ {
+ // If there are fewer COL elements than there are colums, add extra ones, copying the
+ // width value from the last one
+ // FIXME: handle col elements with colspan > 1, as well as colgroups with width set
+ // FIXME: What if there are 0 col elements?
+ while (colElements.length < structure.numCols) {
+ var newColElement = DOM_createElement(document,"COL");
+ var lastColElement = colElements[colElements.length-1];
+ DOM_insertBefore(lastColElement.parentNode,newColElement,lastColElement.nextSibling);
+ colElements.push(newColElement);
+ DOM_setAttribute(newColElement,"width",lastColElement.getAttribute("width"));
+ }
+ }
+
+ // private
+ function fixColPercentages(structure,colElements)
+ {
+ var colWidths = getColWidths(colElements,structure.numCols);
+
+ var percentages = colWidths.map(getPercentage);
+ if (percentages.every(notNull)) {
+ var colWidthTotal = 0;
+ for (var i = 0; i < percentages.length; i++)
+ colWidthTotal += percentages[i];
+
+ for (var i = 0; i < colElements.length; i++) {
+ var pct = 100*percentages[i]/colWidthTotal;
+ // Store value using at most two decimal places
+ pct = Math.round(100*pct)/100;
+ DOM_setAttribute(colElements[i],"width",pct+"%");
+ }
+ }
+
+ function notNull(arg)
+ {
+ return (arg != null);
+ }
+
+ function getPercentage(str)
+ {
+ if (str.match(/^\s*\d+(\.\d+)?\s*%\s*$/))
+ return parseInt(str.replace(/\s*%\s*$/,""));
+ else
+ return null;
+ }
+ }
+
+ // private
+ function addColElement(structure,oldIndex,right)
+ {
+ var table = structure.element;
+
+ var colElements = getColElements(table);
+ if (colElements.length == 0) {
+ // The table doesn't have any COL elements; don't add any
+ return;
+ }
+
+ addMissingColElements(structure,colElements);
+
+ var prevColElement = colElements[oldIndex];
+ var newColElement = DOM_createElement(document,"COL");
+ DOM_setAttribute(newColElement,"width",prevColElement.getAttribute("width"));
+ if (right)
+ DOM_insertBefore(prevColElement.parentNode,newColElement,prevColElement.nextSibling);
+ else
+ DOM_insertBefore(prevColElement.parentNode,newColElement,prevColElement);
+
+ if (right) {
+ colElements.splice(oldIndex+1,0,newColElement);
+ }
+ else {
+ colElements.splice(oldIndex+1,0,newColElement);
+ }
+
+ fixColPercentages(structure,colElements);
+ }
+
+ // private
+ function deleteColElements(structure,left,right)
+ {
+ var table = structure.element;
+
+ var colElements = getColElements(table);
+ if (colElements.length == 0) {
+ // The table doesn't have any COL elements
+ return;
+ }
+
+ addMissingColElements(structure,colElements);
+
+ for (var col = left; col <= right; col++)
+ DOM_deleteNode(colElements[col]);
+ colElements.splice(left,right-left+1);
+
+ fixColPercentages(structure,colElements);
+ }
+
+ // private
+ function addColumnCells(structure,oldIndex,right)
+ {
+ for (var row = 0; row < structure.numRows; row++) {
+ var cell = Table_get(structure,row,oldIndex);
+ var oldTD = cell.element;
+ if (cell.row == row) {
+
+ if (((right && (oldIndex+1 < cell.col + cell.colspan)) ||
+ (!right && (oldIndex-1 >= cell.col))) &&
+ (cell.colspan > 1)) {
+ Cell_setColspan(cell,cell.colspan+1);
+ }
+ else {
+ var newTD = createEmptyTableCell(oldTD.nodeName); // check-ok
+ if (right)
+ DOM_insertBefore(cell.element.parentNode,newTD,oldTD.nextSibling);
+ else
+ DOM_insertBefore(cell.element.parentNode,newTD,oldTD);
+ if (cell.rowspan != 1)
+ DOM_setAttribute(newTD,"rowspan",cell.rowspan);
+ }
+ }
+ }
+ }
+
+ function insertColumnAdjacentToRange(range)
+ {
+ var table;
+
+ table = tableAtLeftOfRange(range);
+ if (table != null) {
+ var right = table.numCols-1;
+ addColElement(table,right,right+1);
+ addColumnCells(table,right,true);
+ return;
+ }
+
+ table = tableAtRightOfRange(range);
+ if (table != null) {
+ var left = 0;
+ addColElement(table,left,left-1);
+ addColumnCells(table,left,false);
+ return;
+ }
+ }
+
+ // public
+ Tables_addAdjacentColumn = function()
+ {
+ UndoManager_newGroup("Insert column at right");
+ Selection_preserveWhileExecuting(function() {
+ var range = Selection_get();
+ var region = Tables_regionFromRange(range,true);
+ if (region != null) {
+ addColElement(region.structure,region.right,region.right+1);
+ addColumnCells(region.structure,region.right,true);
+ }
+ else {
+ insertColumnAdjacentToRange(range);
+ }
+ });
+ UndoManager_newGroup();
+ }
+
+ function columnHasContent(table,col)
+ {
+ for (var row = 0; row < table.numRows; row++) {
+ var cell = Table_get(table,row,col);
+ if ((cell != null) && (cell.col == col) && nodeHasContent(cell.element))
+ return true;
+ }
+ return false;
+ }
+
+ function rowHasContent(table,row)
+ {
+ for (var col = 0; col < table.numCols; col++) {
+ var cell = Table_get(table,row,col);
+ if ((cell != null) && (cell.row == row) && nodeHasContent(cell.element))
+ return true;
+ }
+ return false;
+ }
+
+ function selectRegion(table,top,bottom,left,right)
+ {
+ left = clampCol(table,left);
+ right = clampCol(table,right);
+ top = clampRow(table,top);
+ bottom = clampRow(table,bottom);
+
+ var tlCell = Table_get(table,top,left);
+ var brCell = Table_get(table,bottom,right);
+ if ((tlCell != null) && (brCell != null)) {
+ var tlPos = new Position(tlCell.element,0);
+ tlPos = Position_closestMatchForwards(tlPos,Position_okForMovement);
+
+ var brPos = new Position(brCell.element,brCell.element.childNodes.length);
+ brPos = Position_closestMatchBackwards(brPos,Position_okForMovement);
+
+ Selection_set(tlPos.node,tlPos.offset,brPos.node,brPos.offset);
+ }
+ }
+
+ function clampCol(table,col)
+ {
+ if (col > table.numCols-1)
+ col = table.numCols-1;
+ if (col < 0)
+ col = 0;
+ return col;
+ }
+
+ function clampRow(table,row)
+ {
+ if (row > table.numRows-1)
+ row = table.numRows-1;
+ if (row < 0)
+ row = 0;
+ return row;
+ }
+
+ function removeRowAdjacentToRange(range)
+ {
+ var table;
+
+ table = tableAtLeftOfRange(range);
+ if ((table != null) && (table.numRows >= 2)) {
+ UndoManager_newGroup("Delete one row");
+ var row = table.numRows-1;
+ Tables_deleteRegion(new TableRegion(table,row,row,0,table.numCols-1));
+ UndoManager_newGroup();
+ return;
+ }
+
+ table = tableAtRightOfRange(range);
+ if ((table != null) && (table.numRows >= 2)) {
+ UndoManager_newGroup("Delete one row");
+ Tables_deleteRegion(new TableRegion(table,0,0,0,table.numCols-1));
+ UndoManager_newGroup();
+ return;
+ }
+ }
+
+ Tables_removeAdjacentRow = function()
+ {
+ var range = Selection_get();
+ var region = Tables_regionFromRange(range,true);
+
+ if (region == null) {
+ removeRowAdjacentToRange(range);
+ return;
+ }
+
+ if (region.structure.numRows <= 1)
+ return;
+
+ UndoManager_newGroup("Delete one row");
+
+ var table = region.structure;
+ var left = region.left;
+ var right = region.right;
+ var top = region.top;
+ var bottom = region.bottom;
+
+ // Is there an empty row below the selection? If so, delete it
+ if ((bottom+1 < table.numRows) && !rowHasContent(table,bottom+1)) {
+ Selection_preserveWhileExecuting(function() {
+ Tables_deleteRegion(new TableRegion(table,bottom+1,bottom+1,0,table.numCols-1));
+ });
+ }
+
+ // Is there an empty row above the selection? If so, delete it
+ else if ((top-1 >= 0) && !rowHasContent(table,top-1)) {
+ Selection_preserveWhileExecuting(function() {
+ Tables_deleteRegion(new TableRegion(table,top-1,top-1,0,table.numCols-1));
+ });
+ }
+
+
+ // There are no empty rows adjacent to the selection. Delete the right-most row
+ // of the selection (which may be the only one)
+ else {
+ Selection_preserveWhileExecuting(function() {
+ Tables_deleteRegion(new TableRegion(table,bottom,bottom,0,table.numCols-1));
+ });
+
+ table = Tables_analyseStructure(table.element);
+ var multiple = (top != bottom);
+
+ if (multiple) {
+ selectRegion(table,top,bottom-1,left,right);
+ }
+ else {
+ var newRow = clampRow(table,bottom);
+ var newCell = Table_get(table,newRow,left);
+ if (newCell != null) {
+ var pos = new Position(newCell.element,0);
+ pos = Position_closestMatchForwards(pos,Position_okForMovement);
+ Selection_set(pos.node,pos.offset,pos.node,pos.offset);
+ }
+ }
+ }
+
+ UndoManager_newGroup();
+ }
+
+ function removeColumnAdjacentToRange(range)
+ {
+ var table;
+
+ table = tableAtLeftOfRange(range);
+ if ((table != null) && (table.numCols >= 2)) {
+ UndoManager_newGroup("Delete one column");
+ var col = table.numCols-1;
+ Tables_deleteRegion(new TableRegion(table,0,table.numRows-1,col,col));
+ UndoManager_newGroup();
+ return;
+ }
+
+ table = tableAtRightOfRange(range);
+ if ((table != null) && (table.numCols >= 2)) {
+ UndoManager_newGroup("Delete one column");
+ Tables_deleteRegion(new TableRegion(table,0,table.numRows-1,0,0));
+ UndoManager_newGroup();
+ return;
+ }
+ }
+
+ Tables_removeAdjacentColumn = function()
+ {
+ var range = Selection_get();
+ var region = Tables_regionFromRange(range,true);
+
+ if (region == null) {
+ removeColumnAdjacentToRange(range);
+ return;
+ }
+
+ if (region.structure.numCols <= 1)
+ return;
+
+ UndoManager_newGroup("Delete one column");
+
+ var table = region.structure;
+ var left = region.left;
+ var right = region.right;
+ var top = region.top;
+ var bottom = region.bottom;
+
+ // Is there an empty column to the right of the selection? If so, delete it
+ if ((right+1 < table.numCols) && !columnHasContent(table,right+1)) {
+ Selection_preserveWhileExecuting(function() {
+ Tables_deleteRegion(new TableRegion(table,0,table.numRows-1,right+1,right+1));
+ });
+ }
+
+ // Is there an empty column to the left of the selection? If so, delete it
+ else if ((left-1 >= 0) && !columnHasContent(table,left-1)) {
+ Selection_preserveWhileExecuting(function() {
+ Tables_deleteRegion(new TableRegion(table,0,table.numRows-1,left-1,left-1));
+ });
+ }
+
+ // There are no empty columns adjacent to the selection. Delete the right-most column
+ // of the selection (which may be the only one)
+ else {
+ Selection_preserveWhileExecuting(function() {
+ Tables_deleteRegion(new TableRegion(table,0,table.numRows-1,right,right));
+ });
+
+ table = Tables_analyseStructure(table.element);
+ var multiple = (left != right);
+
+ if (multiple) {
+ selectRegion(table,top,bottom,left,right-1);
+ }
+ else {
+ var newCol = clampCol(table,right);
+ var newCell = Table_get(table,top,newCol);
+ if (newCell != null) {
+ var pos = new Position(newCell.element,0);
+ pos = Position_closestMatchForwards(pos,Position_okForMovement);
+ Selection_set(pos.node,pos.offset,pos.node,pos.offset);
+ }
+ }
+ }
+
+ UndoManager_newGroup();
+ }
+
+ // private
+ function deleteTable(structure)
+ {
+ DOM_deleteNode(structure.element);
+ }
+
+ // private
+ function deleteRows(structure,top,bottom)
+ {
+ var trElements = new Array();
+ getTRs(structure.element,trElements);
+
+ for (var row = top; row <= bottom; row++)
+ DOM_deleteNode(trElements[row]);
+ }
+
+ // private
+ function getTRs(node,result)
+ {
+ if (node._type == HTML_TR) {
+ result.push(node);
+ }
+ else {
+ for (var child = node.firstChild; child != null; child = child.nextSibling)
+ getTRs(child,result);
+ }
+ }
+
+ // private
+ function deleteColumns(structure,left,right)
+ {
+ var nodesToDelete = new NodeSet();
+ for (var row = 0; row < structure.numRows; row++) {
+ for (var col = left; col <= right; col++) {
+ var cell = Table_get(structure,row,col);
+ nodesToDelete.add(cell.element);
+ }
+ }
+ nodesToDelete.forEach(DOM_deleteNode);
+ deleteColElements(structure,left,right);
+ }
+
+ // private
+ function deleteCellContents(region)
+ {
+ var structure = region.structure;
+ for (var row = region.top; row <= region.bottom; row++) {
+ for (var col = region.left; col <= region.right; col++) {
+ var cell = Table_get(structure,row,col);
+ DOM_deleteAllChildren(cell.element);
+ }
+ }
+ }
+
+ // public
+ Tables_deleteRegion = function(region)
+ {
+ var structure = region.structure;
+
+ var coversEntireWidth = (region.left == 0) && (region.right == structure.numCols-1);
+ var coversEntireHeight = (region.top == 0) && (region.bottom == structure.numRows-1);
+
+ if (coversEntireWidth && coversEntireHeight)
+ deleteTable(region.structure);
+ else if (coversEntireWidth)
+ deleteRows(structure,region.top,region.bottom);
+ else if (coversEntireHeight)
+ deleteColumns(structure,region.left,region.right);
+ else
+ deleteCellContents(region);
+ }
+
+ // public
+ Tables_clearCells = function()
+ {
+ }
+
+ // public
+ Tables_mergeCells = function()
+ {
+ Selection_preserveWhileExecuting(function() {
+ var region = Tables_regionFromRange(Selection_get());
+ if (region == null)
+ return;
+
+ var structure = region.structure;
+
+ // FIXME: handle the case of missing cells
+ // (or even better, add cells where there are some missing)
+
+ for (var row = region.top; row <= region.bottom; row++) {
+ for (var col = region.left; col <= region.right; col++) {
+ var cell = Table_get(structure,row,col);
+ var cellFirstRow = cell.row;
+ var cellLastRow = cell.row + cell.rowspan - 1;
+ var cellFirstCol = cell.col;
+ var cellLastCol = cell.col + cell.colspan - 1;
+
+ if ((cellFirstRow < region.top) || (cellLastRow > region.bottom) ||
+ (cellFirstCol < region.left) || (cellLastCol > region.right)) {
+ debug("Can't merge this table: cell at "+row+","+col+
+ " goes outside bounds of selection");
+ return;
+ }
+ }
+ }
+
+ var mergedCell = Table_get(structure,region.top,region.left);
+
+ for (var row = region.top; row <= region.bottom; row++) {
+ for (var col = region.left; col <= region.right; col++) {
+ var cell = Table_get(structure,row,col);
+ // parentNode will be null if we've already done this cell
+ if ((cell != mergedCell) && (cell.element.parentNode != null)) {
+ while (cell.element.firstChild != null)
+ DOM_appendChild(mergedCell.element,cell.element.firstChild);
+ DOM_deleteNode(cell.element);
+ }
+ }
+ }
+
+ var totalRows = region.bottom - region.top + 1;
+ var totalCols = region.right - region.left + 1;
+ if (totalRows == 1)
+ DOM_removeAttribute(mergedCell.element,"rowspan");
+ else
+ DOM_setAttribute(mergedCell.element,"rowspan",totalRows);
+ if (totalCols == 1)
+ DOM_removeAttribute(mergedCell.element,"colspan");
+ else
+ DOM_setAttribute(mergedCell.element,"colspan",totalCols);
+ });
+ }
+
+ // public
+ Tables_splitSelection = function()
+ {
+ Selection_preserveWhileExecuting(function() {
+ var range = Selection_get();
+ Range_trackWhileExecuting(range,function() {
+ var region = Tables_regionFromRange(range,true);
+ if (region != null)
+ TableRegion_splitCells(region);
+ });
+ });
+ }
+
+ // public
+ TableRegion_splitCells = function(region)
+ {
+ var structure = region.structure;
+ var trElements = new Array();
+ getTRs(structure.element,trElements);
+
+ for (var row = region.top; row <= region.bottom; row++) {
+ for (var col = region.left; col <= region.right; col++) {
+ var cell = Table_get(structure,row,col);
+ if ((cell.rowspan > 1) || (cell.colspan > 1)) {
+
+ var original = cell.element;
+
+ for (var r = cell.top; r <= cell.bottom; r++) {
+ for (var c = cell.left; c <= cell.right; c++) {
+ if ((r == cell.top) && (c == cell.left))
+ continue;
+ var newTD = createEmptyTableCell(original.nodeName); // check-ok
+ var nextElement = null;
+
+ var nextCol = cell.right+1;
+ while (nextCol < structure.numCols) {
+ var nextCell = Table_get(structure,r,nextCol);
+ if ((nextCell != null) && (nextCell.row == r)) {
+ nextElement = nextCell.element;
+ break;
+ }
+ nextCol++;
+ }
+
+ DOM_insertBefore(trElements[r],newTD,nextElement);
+ Table_set(structure,r,c,new Cell(newTD,r,c));
+ }
+ }
+ DOM_removeAttribute(original,"rowspan");
+ DOM_removeAttribute(original,"colspan");
+ }
+ }
+ }
+ }
+
+ // public
+ Tables_cloneRegion = function(region)
+ {
+ var cellNodesDone = new NodeSet();
+ var table = DOM_shallowCopyElement(region.structure.element);
+ for (var row = region.top; row <= region.bottom; row++) {
+ var tr = DOM_createElement(document,"TR");
+ DOM_appendChild(table,tr);
+ for (var col = region.left; col <= region.right; col++) {
+ var cell = Table_get(region.structure,row,col);
+ if (!cellNodesDone.contains(cell.element)) {
+ DOM_appendChild(tr,DOM_cloneNode(cell.element,true));
+ cellNodesDone.add(cell.element);
+ }
+ }
+ }
+ return table;
+ }
+
+ // private
+ function pasteCells(fromTableElement,toRegion)
+ {
+ // FIXME
+ var fromStructure = Tables_analyseStructure(fromTableElement);
+ }
+
+ // public
+ Table_fix = function(table)
+ {
+ var changed = false;
+
+ var tbody = null;
+ for (var child = table.element.firstChild; child != null; child = child.nextSibling) {
+ if (child._type == HTML_TBODY)
+ tbody = child;
+ }
+
+ if (tbody == null)
+ return table; // FIXME: handle presence of THEAD and TFOOT, and also a missing TBODY
+
+ var trs = new Array();
+ for (var child = tbody.firstChild; child != null; child = child.nextSibling) {
+ if (child._type == HTML_TR)
+ trs.push(child);
+ }
+
+ while (trs.length < table.numRows) {
+ var tr = DOM_createElement(document,"TR");
+ DOM_appendChild(tbody,tr);
+ trs.push(tr);
+ }
+
+ for (var row = 0; row < table.numRows; row++) {
+ for (var col = 0; col < table.numCols; col++) {
+ var cell = Table_get(table,row,col);
+ if (cell == null) {
+ var td = createEmptyTableCell("TD");
+ DOM_appendChild(trs[row],td);
+ changed = true;
+ }
+ }
+ }
+
+ if (changed)
+ return new Table(table.element);
+ else
+ return table;
+ }
+
+ // public
+ Table_fixColumnWidths = function(structure)
+ {
+ var colElements = getColElements(structure.element);
+ if (colElements.length == 0)
+ return;
+ addMissingColElements(structure,colElements);
+
+ var widths = Tables_getColWidths(structure);
+ fixWidths(widths,structure.numCols);
+ colElements = getColElements(structure.element);
+ for (var i = 0; i < widths.length; i++)
+ DOM_setAttribute(colElements[i],"width",widths[i]+"%");
+ }
+
+ // public
+ Tables_analyseStructure = function(element)
+ {
+ // FIXME: we should probably be preserving the selection here, since we are modifying
+ // the DOM (though I think it's unlikely it would cause problems, becausing the fixup
+ // logic only adds elements). However this method is called (indirectly) from within
+ // Selection_update(), which causes unbounded recursion due to the subsequent Selecton_set()
+ // that occurs.
+ var initial = new Table(element);
+ var fixed = Table_fix(initial);
+ return fixed;
+ }
+
+ // public
+ Tables_findContainingCell = function(node)
+ {
+ for (var ancestor = node; ancestor != null; ancestor = ancestor.parentNode) {
+ if (isTableCell(ancestor))
+ return ancestor;
+ }
+ return null;
+ }
+
+ // public
+ Tables_findContainingTable = function(node)
+ {
+ for (var ancestor = node; ancestor != null; ancestor = ancestor.parentNode) {
+ if (ancestor._type == HTML_TABLE)
+ return ancestor;
+ }
+ return null;
+ }
+
+ function TableRegion(structure,top,bottom,left,right)
+ {
+ this.structure = structure;
+ this.top = top;
+ this.bottom = bottom;
+ this.left = left;
+ this.right = right;
+ }
+
+ TableRegion.prototype.toString = function()
+ {
+ return "("+this.top+","+this.left+") - ("+this.bottom+","+this.right+")";
+ }
+
+ // public
+ Tables_regionFromRange = function(range,allowSameCell)
+ {
+ var region = null;
+
+ if (range == null)
+ return null;
+
+ var start = Position_closestActualNode(range.start,true);
+ var end = Position_closestActualNode(range.end,true);
+
+ var startTD = Tables_findContainingCell(start);
+ var endTD = Tables_findContainingCell(end);
+
+ if (!isTableCell(start) || !isTableCell(end)) {
+ if (!allowSameCell) {
+ if (startTD == endTD) // not in cell, or both in same cell
+ return null;
+ }
+ }
+
+ if ((startTD == null) || (endTD == null))
+ return null;
+
+ var startTable = Tables_findContainingTable(startTD);
+ var endTable = Tables_findContainingTable(endTD);
+
+ if (startTable != endTable)
+ return null;
+
+ var structure = Tables_analyseStructure(startTable);
+
+ var startInfo = structure.cellsByElement.get(startTD);
+ var endInfo = structure.cellsByElement.get(endTD);
+
+ var startTopRow = startInfo.row;
+ var startBottomRow = startInfo.row + startInfo.rowspan - 1;
+ var startLeftCol = startInfo.col;
+ var startRightCol = startInfo.col + startInfo.colspan - 1;
+
+ var endTopRow = endInfo.row;
+ var endBottomRow = endInfo.row + endInfo.rowspan - 1;
+ var endLeftCol = endInfo.col;
+ var endRightCol = endInfo.col + endInfo.colspan - 1;
+
+ var top = (startTopRow < endTopRow) ? startTopRow : endTopRow;
+ var bottom = (startBottomRow > endBottomRow) ? startBottomRow : endBottomRow;
+ var left = (startLeftCol < endLeftCol) ? startLeftCol : endLeftCol;
+ var right = (startRightCol > endRightCol) ? startRightCol : endRightCol;
+
+ var region = new TableRegion(structure,top,bottom,left,right);
+ adjustRegionForSpannedCells(region);
+ return region;
+ }
+
+ // private
+ function adjustRegionForSpannedCells(region)
+ {
+ var structure = region.structure;
+ var boundariesOk;
+ var columnsOk;
+ do {
+ boundariesOk = true;
+ for (var row = region.top; row <= region.bottom; row++) {
+ var cell = Table_get(structure,row,region.left);
+ if (region.left > cell.left) {
+ region.left = cell.left;
+ boundariesOk = false;
+ }
+ cell = Table_get(structure,row,region.right);
+ if (region.right < cell.right) {
+ region.right = cell.right;
+ boundariesOk = false;
+ }
+ }
+
+ for (var col = region.left; col <= region.right; col++) {
+ var cell = Table_get(structure,region.top,col);
+ if (region.top > cell.top) {
+ region.top = cell.top;
+ boundariesOk = false;
+ }
+ cell = Table_get(structure,region.bottom,col);
+ if (region.bottom < cell.bottom) {
+ region.bottom = cell.bottom;
+ boundariesOk = false;
+ }
+ }
+ } while (!boundariesOk);
+ }
+
+ Tables_getSelectedTableId = function()
+ {
+ var element = Cursor_getAdjacentNodeWithType(HTML_TABLE);
+ return element ? element.getAttribute("id") : null;
+ }
+
+ Tables_getProperties = function(itemId)
+ {
+ var element = document.getElementById(itemId);
+ if ((element == null) || (element._type != HTML_TABLE))
+ return null;
+ var structure = Tables_analyseStructure(element);
+ var width = element.style.width;
+ return { width: width, rows: structure.numRows, cols: structure.numCols };
+ }
+
+ Tables_setProperties = function(itemId,width)
+ {
+ var table = document.getElementById(itemId);
+ if (table == null)
+ return null;
+ DOM_setStyleProperties(table,{ width: width });
+ Selection_update(); // ensure cursor/selection drawn in correct pos
+ }
+
+ // Returns an array of numbers representing the percentage widths (0 - 100) of each
+ // column. This works on the assumption that all tables are supposed to have all of
+ // their column widths specified, and in all cases as percentages. Any which do not
+ // are considered invalid, and have any non-percentage values filled in based on the
+ // average values of all valid percentage-based columns.
+ Tables_getColWidths = function(structure)
+ {
+ var colElements = getColElements(structure.element);
+ var colWidths = new Array();
+
+ for (var i = 0; i < structure.numCols; i++) {
+ var value = null;
+
+ if (i < colElements.length) {
+ var widthStr = DOM_getAttribute(colElements[i],"width");
+ if (widthStr != null) {
+ value = parsePercentage(widthStr);
+ }
+ }
+
+ if ((value != null) && (value >= 1.0)) {
+ colWidths[i] = value;
+ }
+ else {
+ colWidths[i] = null;
+ }
+ }
+
+ fixWidths(colWidths,structure.numCols);
+
+ return colWidths;
+
+ function parsePercentage(str)
+ {
+ if (str.match(/^\s*\d+(\.\d+)?\s*%\s*$/))
+ return parseFloat(str.replace(/\s*%\s*$/,""));
+ else
+ return null;
+ }
+ }
+
+ function fixWidths(colWidths,numCols)
+ {
+ var totalWidth = 0;
+ var numValidCols = 0;
+ for (var i = 0; i < numCols; i++) {
+ if (colWidths[i] != null) {
+ totalWidth += colWidths[i];
+ numValidCols++;
+ }
+ }
+
+ var averageWidth = (numValidCols > 0) ? totalWidth/numValidCols : 1.0;
+ for (var i = 0; i < numCols; i++) {
+ if (colWidths[i] == null) {
+ colWidths[i] = averageWidth;
+ totalWidth += averageWidth;
+ }
+ }
+
+ // To cater for the case where the column widths do not all add up to 100%,
+ // recalculate all of them based on their value relative to the total width
+ // of all columns. For example, if there are three columns of 33%, 33%, and 33%,
+ // these will get rounded up to 33.33333.....%.
+ // If there are no column widths defined, each will have 100/numCols%.
+ if (totalWidth > 0) {
+ for (var i = 0; i < numCols; i++) {
+ colWidths[i] = 100.0*colWidths[i]/totalWidth;
+ }
+ }
+ }
+
+ // public
+ Tables_setColWidths = function(itemId,widths)
+ {
+ var element = document.getElementById(itemId);
+ if (element == null)
+ return null;
+
+ var structure = Tables_analyseStructure(element);
+
+ fixWidths(widths,structure.numCols);
+
+ var colElements = getColElements(element);
+ for (var i = 0; i < widths.length; i++)
+ DOM_setAttribute(colElements[i],"width",widths[i]+"%");
+
+ Selection_update();
+ }
+
+ // public
+ Tables_getGeometry = function(itemId)
+ {
+ var element = document.getElementById(itemId);
+ if ((element == null) || (element.parentNode == null))
+ return null;
+
+ var structure = Tables_analyseStructure(element);
+
+ var result = new Object();
+
+ // Calculate the rect based on the cells, not the whole table element;
+ // we want to ignore the caption
+ var topLeftCell = Table_get(structure,0,0);
+ var bottomRightCell = Table_get(structure,structure.numRows-1,structure.numCols-1);
+
+ if (topLeftCell == null)
+ throw new Error("No top left cell");
+ if (bottomRightCell == null)
+ throw new Error("No bottom right cell");
+
+ var topLeftRect = topLeftCell.element.getBoundingClientRect();
+ var bottomRightRect = bottomRightCell.element.getBoundingClientRect();
+
+ var left = topLeftRect.left + window.scrollX;
+ var right = bottomRightRect.right + window.scrollX;
+ var top = topLeftRect.top + window.scrollY;
+ var bottom = bottomRightRect.bottom + window.scrollY;
+
+ result.contentRect = { x: left, y: top, width: right - left, height: bottom - top };
+ result.fullRect = xywhAbsElementRect(element);
+ result.parentRect = xywhAbsElementRect(element.parentNode);
+
+ result.columnWidths = Tables_getColWidths(structure);
+
+ var caption = firstChildOfType(element,HTML_CAPTION);
+ result.hasCaption = (caption != null);
+
+ return result;
+
+ }
+
+})();
http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9bf02bb2/experiments/editorFramework/src/Javascript_Layer_0/Text.js
----------------------------------------------------------------------
diff --git a/experiments/editorFramework/src/Javascript_Layer_0/Text.js b/experiments/editorFramework/src/Javascript_Layer_0/Text.js
new file mode 100644
index 0000000..5a69feb
--- /dev/null
+++ b/experiments/editorFramework/src/Javascript_Layer_0/Text.js
@@ -0,0 +1,543 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+var Text_findParagraphBoundaries;
+var Text_analyseParagraph;
+var Text_posAbove;
+var Text_posBelow;
+var Text_closestPosBackwards;
+var Text_closestPosForwards;
+var Text_closestPosInDirection;
+
+var Paragraph_runFromOffset;
+var Paragraph_runFromNode;
+var Paragraph_positionAtOffset;
+var Paragraph_offsetAtPosition;
+var Paragraph_getRunRects;
+var Paragraph_getRunOrFallbackRects;
+
+var Text_toStartOfBoundary;
+var Text_toEndOfBoundary;
+
+(function() {
+
+ function Paragraph(node,startOffset,endOffset,runs,text)
+ {
+ this.node = node;
+ this.startOffset = startOffset;
+ this.endOffset = endOffset;
+ this.runs = runs;
+ this.text = text;
+
+ Object.defineProperty(this,"first",{
+ get: function() { throw new Error("Attempt to access first property of Position") },
+ set: function() {},
+ enumerable: true });
+ Object.defineProperty(this,"last",{
+ get: function() { throw new Error("Attempt to access last property of Position") },
+ set: function() {},
+ enumerable: true });
+ }
+
+ function Run(node,start,end)
+ {
+ this.node = node;
+ this.start = start;
+ this.end = end;
+ }
+
+ // In this code, we represent a paragraph by its first and last node. Normally, this will be
+ // the first and last child of a paragraph-level element (e.g. p or h1), but this scheme also
+ // represent a sequence of inline nodes between two paragraph or container nodes, e.g.
+ //
+ // <p>...</p> Some <i>inline</i> nodes <p>...</p>
+
+ Text_findParagraphBoundaries = function(pos)
+ {
+ Position_assertValid(pos);
+ var startOffset = pos.offset;
+ var endOffset = pos.offset;
+ var node = pos.node;
+
+ while (isInlineNode(node)) {
+ startOffset = DOM_nodeOffset(node);
+ endOffset = DOM_nodeOffset(node)+1;
+ node = node.parentNode;
+ }
+
+ if (node.nodeType != Node.ELEMENT_NODE)
+ throw new Error("Not an element node: "+nodeString(node));
+
+ while ((startOffset > 0) && isInlineNode(node.childNodes[startOffset-1]))
+ startOffset--;
+ while ((endOffset < node.childNodes.length) && isInlineNode(node.childNodes[endOffset]))
+ endOffset++;
+
+ return { node: node, startOffset: startOffset, endOffset: endOffset };
+ }
+
+ Text_analyseParagraph = function(pos)
+ {
+ var initial = pos.node;
+ var strings = new Array();
+ var runs = new Array();
+ var offset = 0;
+
+ var boundaries = Text_findParagraphBoundaries(pos);
+ if (boundaries == null)
+ return null;
+
+ for (var off = boundaries.startOffset; off < boundaries.endOffset; off++)
+ recurse(boundaries.node.childNodes[off]);
+
+ var text = strings.join("");
+
+ return new Paragraph(boundaries.node,boundaries.startOffset,boundaries.endOffset,runs,text);
+
+ function recurse(node)
+ {
+ if (node.nodeType == Node.TEXT_NODE) {
+ strings.push(node.nodeValue);
+ var start = offset;
+ var end = offset + node.nodeValue.length;
+ runs.push(new Run(node,start,end));
+ offset += node.nodeValue.length;
+ }
+ for (var child = node.firstChild; child != null; child = child.nextSibling)
+ recurse(child);
+ }
+ }
+
+ Text_posAbove = function(pos,cursorRect,cursorX)
+ {
+ if (cursorX == null)
+ cursorX = pos.targetX;
+ pos = Position_closestMatchBackwards(pos,Position_okForMovement);
+ if (cursorRect == null) {
+ cursorRect = Position_rectAtPos(pos);
+ if (cursorRect == null)
+ return null;
+ }
+
+ if (cursorX == null) {
+ cursorX = cursorRect.left;
+ }
+
+ while (true) {
+ pos = Position_closestMatchBackwards(pos,Position_okForMovement);
+ if (pos == null)
+ return null;
+
+ var paragraph = Text_analyseParagraph(pos);
+ if (paragraph == null)
+ return null;
+
+ var rects = Paragraph_getRunOrFallbackRects(paragraph,pos);
+
+ rects = rects.filter(function (rect) {
+ return (rect.bottom <= cursorRect.top);
+ });
+
+
+
+ var bottom = findLowestBottom(rects);
+
+ rects = rects.filter(function (rect) { return (rect.bottom == bottom); });
+
+ // Scroll previous line into view, if necessary
+ var top = findHighestTop(rects);
+ if (top < 0) {
+ var offset = -top;
+ window.scrollBy(0,-offset);
+ rects = offsetRects(rects,0,offset);
+ }
+
+ for (var i = 0; i < rects.length; i++) {
+ if ((cursorX >= rects[i].left) && (cursorX <= rects[i].right)) {
+ var newPos = Position_atPoint(cursorX,rects[i].top + rects[i].height/2);
+ if (newPos != null) {
+ newPos = Position_closestMatchBackwards(newPos,Position_okForInsertion);
+ newPos.targetX = cursorX;
+ return newPos;
+ }
+ }
+ }
+
+ var rightMost = findRightMostRect(rects);
+ if (rightMost != null) {
+ var newPos = Position_atPoint(rightMost.right,rightMost.top + rightMost.height/2);
+ if (newPos != null) {
+ newPos = Position_closestMatchBackwards(newPos,Position_okForInsertion);
+ newPos.targetX = cursorX;
+ return newPos;
+ }
+ }
+
+
+ pos = new Position(paragraph.node,paragraph.startOffset);
+ pos = Position_prevMatch(pos,Position_okForMovement);
+ }
+ }
+
+ var findHighestTop = function(rects)
+ {
+ var top = null;
+ for (var i = 0; i < rects.length; i++) {
+ if ((top == null) || (top > rects[i].top))
+ top = rects[i].top;
+ }
+ return top;
+ }
+
+ var findLowestBottom = function(rects)
+ {
+ var bottom = null;
+ for (var i = 0; i < rects.length; i++) {
+ if ((bottom == null) || (bottom < rects[i].bottom))
+ bottom = rects[i].bottom;
+ }
+ return bottom;
+ }
+
+ var findRightMostRect = function(rects)
+ {
+ var rightMost = null;
+ for (var i = 0; i < rects.length; i++) {
+ if ((rightMost == null) || (rightMost.right < rects[i].right))
+ rightMost = rects[i];
+ }
+ return rightMost;
+ }
+
+ var offsetRects = function(rects,offsetX,offsetY)
+ {
+ var result = new Array();
+ for (var i = 0; i < rects.length; i++) {
+ result.push({ top: rects[i].top + offsetY,
+ bottom: rects[i].bottom + offsetY,
+ left: rects[i].left + offsetX,
+ right: rects[i].right + offsetX,
+ width: rects[i].width,
+ height: rects[i].height });
+ }
+ return result;
+ }
+
+ Text_posBelow = function(pos,cursorRect,cursorX)
+ {
+ if (cursorX == null)
+ cursorX = pos.targetX;
+ pos = Position_closestMatchForwards(pos,Position_okForMovement);
+ if (cursorRect == null) {
+ cursorRect = Position_rectAtPos(pos);
+ if (cursorRect == null)
+ return null;
+ }
+
+ if (cursorX == null) {
+ cursorX = cursorRect.left;
+ }
+
+
+ while (true) {
+ pos = Position_closestMatchForwards(pos,Position_okForMovement);
+ if (pos == null)
+ return null;
+
+ var paragraph = Text_analyseParagraph(pos);
+ if (paragraph == null)
+ return null;
+
+ var rects = Paragraph_getRunOrFallbackRects(paragraph,pos);
+
+ rects = rects.filter(function (rect) {
+ return (rect.top >= cursorRect.bottom);
+ });
+
+ var top = findHighestTop(rects);
+
+ rects = rects.filter(function (rect) { return (rect.top == top); });
+
+ // Scroll next line into view, if necessary
+ var bottom = findLowestBottom(rects);
+ if (bottom > window.innerHeight) {
+ var offset = window.innerHeight - bottom;
+ window.scrollBy(0,-offset);
+ rects = offsetRects(rects,0,offset);
+ }
+
+ for (var i = 0; i < rects.length; i++) {
+ if ((cursorX >= rects[i].left) && (cursorX <= rects[i].right)) {
+ var newPos = Position_atPoint(cursorX,rects[i].top + rects[i].height/2);
+ if (newPos != null) {
+ newPos = Position_closestMatchForwards(newPos,Position_okForInsertion);
+ newPos.targetX = cursorX;
+ return newPos;
+ }
+ }
+ }
+
+ var rightMost = findRightMostRect(rects);
+ if (rightMost != null) {
+ var newPos = Position_atPoint(rightMost.right,rightMost.top + rightMost.height/2);
+ if (newPos != null) {
+ newPos = Position_closestMatchForwards(newPos,Position_okForInsertion);
+ newPos.targetX = cursorX;
+ return newPos;
+ }
+ }
+
+ pos = new Position(paragraph.node,paragraph.endOffset);
+ pos = Position_nextMatch(pos,Position_okForMovement);
+ }
+ }
+
+ Text_closestPosBackwards = function(pos)
+ {
+ if (isNonWhitespaceTextNode(pos.node))
+ return pos;
+ var node;
+ if ((pos.node.nodeType == Node.ELEMENT_NODE) && (pos.offset > 0)) {
+ node = pos.node.childNodes[pos.offset-1];
+ while (node.lastChild != null)
+ node = node.lastChild;
+ }
+ else {
+ node = pos.node;
+ }
+ while ((node != null) && (node != document.body) && !isNonWhitespaceTextNode(node))
+ node = prevNode(node);
+
+ if ((node == null) || (node == document.body))
+ return null;
+ else
+ return new Position(node,node.nodeValue.length);
+ }
+
+ Text_closestPosForwards = function(pos)
+ {
+ if (isNonWhitespaceTextNode(pos.node))
+ return pos;
+ var node;
+ if ((pos.node.nodeType == Node.ELEMENT_NODE) && (pos.offset < pos.node.childNodes.length)) {
+ node = pos.node.childNodes[pos.offset];
+ while (node.firstChild != null)
+ node = node.firstChild;
+ }
+ else {
+ node = nextNodeAfter(pos.node);
+ }
+ while ((node != null) && !isNonWhitespaceTextNode(node)) {
+ var old = nodeString(node);
+ node = nextNode(node);
+ }
+
+ if (node == null)
+ return null;
+ else
+ return new Position(node,0);
+ }
+
+ Text_closestPosInDirection = function(pos,direction)
+ {
+ if ((direction == "forward") ||
+ (direction == "right") ||
+ (direction == "down")) {
+ return Text_closestPosForwards(pos);
+ }
+ else {
+ return Text_closestPosBackwards(pos);
+ }
+ }
+
+ Paragraph_runFromOffset = function(paragraph,offset,end)
+ {
+ if (paragraph.runs.length == 0)
+ throw new Error("Paragraph has no runs");
+ if (!end) {
+
+ for (var i = 0; i < paragraph.runs.length; i++) {
+ var run = paragraph.runs[i];
+ if ((offset >= run.start) && (offset < run.end))
+ return run;
+ if ((i == paragraph.runs.length-1) && (offset == run.end))
+ return run;
+ }
+
+ }
+ else {
+
+ for (var i = 0; i < paragraph.runs.length; i++) {
+ var run = paragraph.runs[i];
+ if ((offset > run.start) && (offset <= run.end))
+ return run;
+ if ((i == 0) && (offset == 0))
+ return run;
+ }
+
+ }
+ }
+
+ Paragraph_runFromNode = function(paragraph,node)
+ {
+ for (var i = 0; i < paragraph.runs.length; i++) {
+ if (paragraph.runs[i].node == node)
+ return paragraph.runs[i];
+ }
+ throw new Error("Run for text node not found");
+ }
+
+ Paragraph_positionAtOffset = function(paragraph,offset,end)
+ {
+ var run = Paragraph_runFromOffset(paragraph,offset,end);
+ if (run == null)
+ throw new Error("Run at offset "+offset+" not found");
+ return new Position(run.node,offset-run.start);
+ }
+
+ Paragraph_offsetAtPosition = function(paragraph,pos)
+ {
+ var run = Paragraph_runFromNode(paragraph,pos.node);
+ return run.start + pos.offset;
+ }
+
+ Paragraph_getRunRects = function(paragraph)
+ {
+ var rects = new Array();
+ for (var i = 0; i < paragraph.runs.length; i++) {
+ var run = paragraph.runs[i];
+ var runRange = new Range(run.node,0,run.node,run.node.nodeValue.length);
+ var runRects = Range_getClientRects(runRange);
+ Array.prototype.push.apply(rects,runRects);
+ }
+ return rects;
+ }
+
+ Paragraph_getRunOrFallbackRects = function(paragraph,pos)
+ {
+ var rects = Paragraph_getRunRects(paragraph);
+ if ((rects.length == 0) && (paragraph.node.nodeType == Node.ELEMENT_NODE)) {
+ if (isBlockNode(paragraph.node) &&
+ (paragraph.startOffset == 0) &&
+ (paragraph.endOffset == paragraph.node.childNodes.length)) {
+ rects = [paragraph.node.getBoundingClientRect()];
+ }
+ else {
+ var beforeNode = paragraph.node.childNodes[paragraph.startOffset-1];
+ var afterNode = paragraph.node.childNodes[paragraph.endOffset];
+ if ((afterNode != null) && isBlockNode(afterNode)) {
+ rects = [afterNode.getBoundingClientRect()];
+ }
+ else if ((beforeNode != null) && isBlockNode(beforeNode)) {
+ rects = [beforeNode.getBoundingClientRect()];
+ }
+ }
+ }
+ return rects;
+ }
+
+ function toStartOfParagraph(pos)
+ {
+ pos = Position_closestMatchBackwards(pos,Position_okForMovement);
+ if (pos == null)
+ return null;
+ var paragraph = Text_analyseParagraph(pos);
+ if (paragraph == null)
+ return null;
+
+ var newPos = new Position(paragraph.node,paragraph.startOffset);
+ return Position_closestMatchForwards(newPos,Position_okForMovement);
+ }
+
+ function toEndOfParagraph(pos)
+ {
+ pos = Position_closestMatchForwards(pos,Position_okForMovement);
+ if (pos == null)
+ return null;
+ var paragraph = Text_analyseParagraph(pos);
+ if (paragraph == null)
+ return null;
+
+ var newPos = new Position(paragraph.node,paragraph.endOffset);
+ return Position_closestMatchBackwards(newPos,Position_okForMovement);
+ }
+
+ function toStartOfLine(pos)
+ {
+ var posRect = Position_rectAtPos(pos);
+ if (posRect == null) {
+ pos = Text_closestPosBackwards(pos);
+ posRect = Position_rectAtPos(pos);
+ if (posRect == null) {
+ return null;
+ }
+ }
+
+ while (true) {
+ var check = Position_prevMatch(pos,Position_okForMovement);
+ var checkRect = Position_rectAtPos(check); // handles check == null case
+ if (checkRect == null)
+ return pos;
+ if ((checkRect.bottom <= posRect.top) || (checkRect.top >= posRect.bottom))
+ return pos;
+ pos = check;
+ }
+ }
+
+ function toEndOfLine(pos)
+ {
+ var posRect = Position_rectAtPos(pos);
+ if (posRect == null) {
+ pos = Text_closestPosForwards(pos);
+ posRect = Position_rectAtPos(pos);
+ if (posRect == null) {
+ return null;
+ }
+ }
+
+ while (true) {
+ var check = Position_nextMatch(pos,Position_okForMovement);
+ var checkRect = Position_rectAtPos(check); // handles check == null case
+ if (checkRect == null)
+ return pos;
+ if ((checkRect.bottom <= posRect.top) || (checkRect.top >= posRect.bottom))
+ return pos;
+ pos = check;
+ }
+ }
+
+ Text_toStartOfBoundary = function(pos,boundary)
+ {
+ if (boundary == "paragraph")
+ return toStartOfParagraph(pos);
+ else if (boundary == "line")
+ return toStartOfLine(pos);
+ else
+ throw new Error("Unsupported boundary: "+boundary);
+ }
+
+ Text_toEndOfBoundary = function(pos,boundary)
+ {
+ if (boundary == "paragraph")
+ return toEndOfParagraph(pos);
+ else if (boundary == "line")
+ return toEndOfLine(pos);
+ else
+ throw new Error("Unsupported boundary: "+boundary);
+ }
+
+})();
http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9bf02bb2/experiments/editorFramework/src/Javascript_Layer_0/UndoManager.js
----------------------------------------------------------------------
diff --git a/experiments/editorFramework/src/Javascript_Layer_0/UndoManager.js b/experiments/editorFramework/src/Javascript_Layer_0/UndoManager.js
new file mode 100644
index 0000000..9f63c43
--- /dev/null
+++ b/experiments/editorFramework/src/Javascript_Layer_0/UndoManager.js
@@ -0,0 +1,270 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// FIXME: place a limit on the number of undo steps recorded - say, 30-50?
+
+var UndoManager_getLength;
+var UndoManager_getIndex;
+var UndoManager_setIndex;
+var UndoManager_print;
+var UndoManager_undo;
+var UndoManager_redo;
+var UndoManager_addAction;
+var UndoManager_newGroup;
+var UndoManager_groupType;
+var UndoManager_disableWhileExecuting;
+var UndoManager_isActive;
+var UndoManager_isDisabled;
+var UndoManager_clear;
+var UndoManager_setProperty;
+var UndoManager_deleteProperty;
+
+(function() {
+
+ var UNDO_LIMIT = 50;
+
+ function UndoGroup(type,onClose)
+ {
+ this.type = type;
+ this.onClose = onClose;
+ this.actions = new Array();
+ }
+
+ function UndoAction(fun,args)
+ {
+ this.fun = fun;
+ this.args = args;
+ }
+
+ UndoAction.prototype.toString = function()
+ {
+ var name;
+ if (this.fun.wrappedName != null)
+ name = this.fun.wrappedName;
+ else
+ name = this.fun.name;
+
+ var argStrings = new Array();
+ for (var i = 0; i < this.args.length; i++) {
+ if (this.args[i] instanceof Node)
+ argStrings.push(nodeString(this.args[i]));
+ else if (this.args[i] == null)
+ argStrings.push("null");
+ else
+ argStrings.push(this.args[i].toString());
+ }
+
+ return name + "(" + argStrings.join(",") + ")";
+ }
+
+ var undoStack = new Array();
+ var redoStack = new Array();
+ var inUndo = false;
+ var inRedo = false;
+ var currentGroup = null;
+ var disabled = 0;
+
+ // public
+ UndoManager_getLength = function()
+ {
+ return undoStack.length + redoStack.length;
+ }
+
+ // public
+ UndoManager_getIndex = function()
+ {
+ return undoStack.length;
+ }
+
+ // public
+ UndoManager_setIndex = function(index)
+ {
+ while (undoStack.length > index)
+ UndoManager_undo();
+ while (undoStack.length < index)
+ UndoManager_redo();
+ }
+
+ // public
+ UndoManager_print = function()
+ {
+ debug("");
+ debug("--------------------------------------------------------------------");
+ debug("Undo stack:");
+ for (var groupIndex = 0; groupIndex < undoStack.length; groupIndex++) {
+ var group = undoStack[groupIndex];
+ debug(" "+group.type);
+ for (var actionIndex = 0; actionIndex < group.actions.length; actionIndex++) {
+ var action = group.actions[actionIndex];
+ debug(" "+action);
+ }
+ }
+ debug("Redo stack:");
+ for (var groupIndex = 0; groupIndex < redoStack.length; groupIndex++) {
+ var group = redoStack[groupIndex];
+ debug(" "+group.type);
+ for (var actionIndex = 0; actionIndex < group.actions.length; actionIndex++) {
+ var action = group.actions[actionIndex];
+ debug(" "+action);
+ }
+ }
+ debug("Current group = "+currentGroup);
+ debug("--------------------------------------------------------------------");
+ debug("");
+ }
+
+ function closeCurrentGroup()
+ {
+ if ((currentGroup != null) && (currentGroup.onClose != null))
+ currentGroup.onClose();
+ currentGroup = null;
+ }
+
+ // public
+ UndoManager_undo = function()
+ {
+ closeCurrentGroup();
+ if (undoStack.length > 0) {
+ var group = undoStack.pop();
+ inUndo = true;
+ for (var i = group.actions.length-1; i >= 0; i--)
+ group.actions[i].fun.apply(null,group.actions[i].args);
+ inUndo = false;
+ }
+ closeCurrentGroup();
+ }
+
+ // public
+ UndoManager_redo = function()
+ {
+ closeCurrentGroup();
+ if (redoStack.length > 0) {
+ var group = redoStack.pop();
+ inRedo = true;
+ for (var i = group.actions.length-1; i >= 0; i--)
+ group.actions[i].fun.apply(null,group.actions[i].args);
+ inRedo = false;
+ }
+ closeCurrentGroup();
+ }
+
+ // public
+ UndoManager_addAction = function(fun)
+ {
+ if (disabled > 0)
+ return;
+
+ // remaining parameters after fun are arguments to be supplied to fun
+ var args = new Array();
+ for (var i = 1; i < arguments.length; i++)
+ args.push(arguments[i]);
+
+ if (!inUndo && !inRedo && (redoStack.length > 0))
+ redoStack.length = 0;
+
+ var stack = inUndo ? redoStack : undoStack;
+ if (currentGroup == null)
+ UndoManager_newGroup(null);
+
+ // Only add a group to the undo stack one it has at least one action, to avoid having
+ // empty groups present.
+ if (currentGroup.actions.length == 0) {
+ if (!inUndo && !inRedo && (stack.length == UNDO_LIMIT))
+ stack.shift();
+ stack.push(currentGroup);
+ }
+
+ currentGroup.actions.push(new UndoAction(fun,args));
+ }
+
+ // public
+ UndoManager_newGroup = function(type,onClose)
+ {
+ if (disabled > 0)
+ return;
+
+ closeCurrentGroup();
+
+ // We don't actually add the group to the undo stack until the first request to add an
+ // action to it. This way we don't end up with empty groups in the undo stack, which
+ // simplifies logic for moving back and forward through the undo history.
+
+ if ((type == null) || (type == ""))
+ type = "Anonymous";
+ currentGroup = new UndoGroup(type,onClose);
+ }
+
+ // public
+ UndoManager_groupType = function()
+ {
+ if (undoStack.length > 0)
+ return undoStack[undoStack.length-1].type;
+ else
+ return null;
+ }
+
+ UndoManager_disableWhileExecuting = function(fun) {
+ disabled++;
+ try {
+ return fun();
+ }
+ finally {
+ disabled--;
+ }
+ }
+
+ UndoManager_isActive = function()
+ {
+ return (inUndo || inRedo);
+ }
+
+ UndoManager_isDisabled = function() {
+ return (disabled > 0);
+ }
+
+ UndoManager_clear = function() {
+ undoStack.length = 0;
+ redoStack.length = 0;
+ }
+
+ function saveProperty(obj,name)
+ {
+ if (obj.hasOwnProperty(name))
+ UndoManager_addAction(UndoManager_setProperty,obj,name,obj[name]);
+ else
+ UndoManager_addAction(UndoManager_deleteProperty,obj,name);
+ }
+
+ UndoManager_setProperty = function(obj,name,value)
+ {
+ if (obj.hasOwnProperty(name) && (obj[name] == value))
+ return; // no point in adding an undo action
+ saveProperty(obj,name);
+ obj[name] = value;
+ }
+
+ UndoManager_deleteProperty = function(obj,name)
+ {
+ if (!obj.hasOwnProperty(name))
+ return; // no point in adding an undo action
+ saveProperty(obj,name);
+ delete obj[name];
+ }
+
+})();
+
+window.undoSupported = true;
http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9bf02bb2/experiments/editorFramework/src/Javascript_Layer_0/Viewport.js
----------------------------------------------------------------------
diff --git a/experiments/editorFramework/src/Javascript_Layer_0/Viewport.js b/experiments/editorFramework/src/Javascript_Layer_0/Viewport.js
new file mode 100644
index 0000000..47fbdfd
--- /dev/null
+++ b/experiments/editorFramework/src/Javascript_Layer_0/Viewport.js
@@ -0,0 +1,80 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+var Viewport_init;
+var Viewport_setViewportWidth;
+var Viewport_setTextScale;
+
+(function() {
+
+ var viewportMetaElement = null;
+
+ // public
+ Viewport_init = function(width,textScale)
+ {
+ var head = DOM_documentHead(document);
+ for (var child = head.firstChild; child != null; child = child.nextSibling) {
+ if ((child._type == HTML_META) && (child.getAttribute("name") == "viewport")) {
+ viewportMetaElement = child;
+ break;
+ }
+ }
+
+ if (viewportMetaElement == null) {
+ viewportMetaElement = DOM_createElement(document,"META");
+ DOM_setAttribute(viewportMetaElement,"name","viewport");
+ DOM_appendChild(head,viewportMetaElement);
+ }
+
+ if (width != 0) {
+ // Only set the width and text scale if they are not already set, to avoid triggering
+ // an extra layout at load time
+ var contentValue = "width = "+width+", user-scalable = no";
+ if (viewportMetaElement.getAttribute("content") != contentValue)
+ DOM_setAttribute(viewportMetaElement,"content",contentValue);
+ }
+
+ if (textScale != 0) {
+ var pct = textScale+"%";
+ if (document.documentElement.style.getPropertyValue("-webkit-text-size-adjust") != pct)
+ DOM_setStyleProperties(document.documentElement,{"-webkit-text-size-adjust": pct});
+ }
+ }
+
+ // public
+ Viewport_setViewportWidth = function(width)
+ {
+ var contentValue = "width = "+width+", user-scalable = no";
+ if (viewportMetaElement.getAttribute("content") != contentValue)
+ DOM_setAttribute(viewportMetaElement,"content",contentValue);
+
+ Selection_update();
+ Cursor_ensureCursorVisible();
+ }
+
+ // public
+ Viewport_setTextScale = function(textScale)
+ {
+ var pct = textScale+"%";
+ if (document.documentElement.style.getPropertyValue("-webkit-text-size-adjust") != pct)
+ DOM_setStyleProperties(document.documentElement,{"-webkit-text-size-adjust": pct});
+
+ Selection_update();
+ Cursor_ensureCursorVisible();
+ }
+
+})();
http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9bf02bb2/experiments/editorFramework/src/Javascript_Layer_0/check-dom-methods.sh
----------------------------------------------------------------------
diff --git a/experiments/editorFramework/src/Javascript_Layer_0/check-dom-methods.sh b/experiments/editorFramework/src/Javascript_Layer_0/check-dom-methods.sh
new file mode 100644
index 0000000..6e17a4e
--- /dev/null
+++ b/experiments/editorFramework/src/Javascript_Layer_0/check-dom-methods.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+jsgrep -F '.createElement' | grep -vF '// check-ok'
+jsgrep -F '.createTextNode' | grep -vF '// check-ok'
+jsgrep -F '.createComment' | grep -vF '// check-ok'
+jsgrep -F '.appendChild' | grep -vF '// check-ok'
+jsgrep -F '.insertBefore' | grep -vF '// check-ok'
+jsgrep -F '.removeChild' | grep -vF '// check-ok'
+jsgrep -F '.cloneNode' | grep -vF '// check-ok'
+jsgrep -F '.nodeName' | grep -vE '(dtdsource/|tests/|treevis/)' | grep -vF '// check-ok'
+jsgrep -F '.setAttribute' | grep -vE '(dtdsource/|treevis/|docx/)' | grep -vF '// check-ok'
+jsgrep -F '.removeAttribute' | grep -vE '(dtdsource/|treevis/|docx/)' | grep -vF '// check-ok'
+jsgrep -F '.setProperty' | grep -vE '(dtdsource/|treevis/)' | grep -vF '// check-ok'
+jsgrep -F '.removeProperty' | grep -vE '(dtdsource/|treevis/)' | grep -vF '// check-ok'
+jsgrep -E '\.style\[.* = ' | grep -vE '(treevis/|docx/)' | grep -vF '// check-ok'
+jsgrep -E '\.style\..* = ' | grep -vE '(treevis/|docx/)' | grep -vF '// check-ok'