/* Notes: - [x] use groups as holders for elements? - [x] Generate 2D element/cell hash - [x] Coloring - [ ] Redraws: - resize (table size, cell sizing) - resize rows with Key operators to let col/row sizing affect table size? - redraw (new/deleted row/column, rowspan/colspan changes) - will have to consider properties of new objects as they are moved within the cell order (or be able to create copies etc.) - trim spanning if new table size doesnt allow for cell span size(s) - remove text elements cells no longer valid - merge text for rowspan/colspan? - customData in text fields to associate it to cell? - create filler text groups for text along with cells? or create on demand? - AddText - UpdateTextLocations - LoadControlPointScheme - [ ] Separated borders? Just hilite/lowlite? - [ ] Data - [x] main shape has cell count w and h - data.cols = "[10,30,20]" - data.rows = "[50,40,10]" - parse on each use, save as string when altering - [ ] each element has w,h size (cdata) based on cells it spans - draw cells "after the fact" - cell padding (main elem) - data.padding = "3" - cell spacing (main elem) - data.spacing = "3" - [ ] Search for TODO Broken: [x] Coloring */ /* **************************************** USER VARIABLES **************************************** */ var defaultRows = 3; var defaultCols = 2; var defaultWidth = 100; var defaultHeight = 100; var defaultSpacing = 3; var defaultPadding = 2; var defaultText = "Sample of Text to be edited."; /* **************************************** SHAPE VARIABLES **************************************** */ var mouse = smartShape.currentMousePos; var cps = smartShape.elem.controlPoints; var data = smartShape.elem.customData; var persistentcps = 6; var textGroup = 0; var cellGroup = 1; var tableGroup = 2; var renderGroup = 3; var _tableInfo = false; var _CPHash = false; /* **************************************** SMARTSHAPE EVENTS **************************************** */ operation = new Object(); operation.InsertSmartShapeAt = function(){ var cellWidth = defaultWidth/defaultCols; var cellHeight = defaultHeight/defaultRows; var x, y; var cols = new Array(); for (x=0; x 1){ parms.minY += tableInfo.spacing; parms.maxY += tableInfo.spacing; nods = render.elements[1].contours[0].nodes; nods[0].RegisterMove(parms); nods[1].RegisterMove(parms); } }else{ parms.deltaYtoY = 0; cellElem = cellElems[0][cpInfo.index-1]; CreateCell({x:cellElem.left, y:tableElem.top}, {width:cellElem.width, height:tableElem.height}, render, 0); parms.minX = cellElem.left; if (cpInfo.index < tableInfo.colCount){ cellElem = cellElems[0][cpInfo.index]; CreateCell({x:cellElem.left, y:tableElem.top}, {width:cellElem.width, height:tableElem.height}, render, 1); parms.maxX = (cellElem.left + cellElem.width) - tableInfo.spacing; } nods = render.elements[0].contours[0].nodes; nods[1].RegisterMove(parms); nods[2].RegisterMove(parms); if (render.elements.length > 1){ parms.minX += tableInfo.spacing; parms.maxX += tableInfo.spacing; nods = render.elements[1].contours[0].nodes; nods[0].RegisterMove(parms); nods[3].RegisterMove(parms); } } } if (scheme == "span"){ // reder preview if (cp.name == "spacing"){ /* TODO redner preview var cellElem = cells.elements[0]; CreateCell({x:cellElem.left, y:cellElem.top}, {width:cellElem.width, height:cellElem.height}, render, 0); */ // TODO: max based on smallest allowed from of all cells parms.minX = tableElem.left; parms.maxX = tableElem.left + tableElem.width/2; parms.minY = tableElem.top; parms.maxY = tableElem.top + tableElem.height/2; parms.deltaYtoY = 0; parms.deltaXtoY = 1; cp.RegisterMove(parms); }else{ var cellElem = cells.elements[cp.name]; CreateCell({x:cellElem.left, y:cellElem.top}, {width:cellElem.width, height:cellElem.height}, render, 0); render.elements[1] = new Path(); render.elements[1].contours[0] = new Contour(); var nods = render.elements[1].contours[0].nodes; nods.length = 2; SetNodePosition(nods[0], {x:render.elements[0].left, y:render.elements[0].top}); SetNodePosition(nods[1], {x:render.elements[0].left + render.elements[0].width, y:render.elements[0].top + render.elements[0].height}); parms.minX = cellElem.left; parms.maxX = tableInfo.right; parms.minY = cellElem.top; parms.maxY = tableInfo.bottom; cp.RegisterMove(parms); nods[1].RegisterMove(parms); nods = render.elements[0].contours[0].nodes; nods[2].RegisterMove(parms); parms.deltaYtoY = 0; nods[1].RegisterMove(parms); parms.deltaYtoY = 1; parms.deltaXtoX = 0; nods[3].RegisterMove(parms); } } if (scheme == "text"){ if (cp.name == "padding"){ // reder preview var cellElem = cells.elements[0]; CreateCell({x:cellElem.left, y:cellElem.top}, {width:cellElem.width, height:cellElem.height}, render, 0); // TODO: max based on smallest allowed from of all cells parms.minX = cellElem.left; parms.maxX = cellElem.left + cellElem.width/2; parms.minY = cellElem.top; parms.maxY = cellElem.top + cellElem.height/2; parms.deltaYtoY = 0; parms.deltaXtoY = 1; cp.RegisterMove(parms); // TODO: show preview } } } operation.DragControlPoint = function(){ UpdateToolTips(); } operation.EndDragControlPoint = function(){ smartShape.elem.elements[renderGroup] = new Group(); // remove render preview var cp = smartShape.currentControlPoint; var tableInfo = GetTableInfo(); var scheme; if (cp.index < persistentcps){ scheme = cp.name; }else{ scheme = GetCurrentControlPointScheme(); switch(scheme){ case "text": if (cp.name == "padding"){ // Ajust padding var cellElem = smartShape.elem.elements[cellGroup].elements[0]; data.padding = cp.y - cellElem.top; UpdateTextLocations(); }else{ // Add/Remove text var cellNum = Number(cp.name); if (!isNaN(cellNum)){ if (cp.type == "default"){ AddText(cellNum); }else{ RemoveText(cellNum); } } } break; case "color": var cellElems = GenerateElementArray(); var cpInfo = GetControlPointInfo(cp); var defaultColor = "#FFFFFF"; var color = fw.popupColorPickerOverMouse(defaultColor, true, false); if (cpInfo.type == "row"){ var i = tableInfo.colCount; while(i--){ if (String(cellElems[cpInfo.index][i]) == "[object Path]"){ cellElems[cpInfo.index][i].pathAttributes.fillColor = color; } } }else{ var i = tableInfo.rowCount; while(i--){ if (String(cellElems[i][cpInfo.index]) == "[object Path]"){ cellElems[i][cpInfo.index].pathAttributes.fillColor = color; } } } break; } } LoadControlPointScheme(scheme); } operation.RedrawSmartShape = function(){ RebuildTable(); } /* **************************************** UPDATING **************************************** */ RebuildTable = function(){ RedrawTable(); } RedrawTable = function(){ } LoadControlPointScheme = function(scheme){ cps.length = persistentcps; for (var i=0; i 0){ SetControlPoint(cps.length, {x:tableInfo.left + tableInfo.cols[i], y:tableInfo.top}, "col_"+i, "Drag: Resize Column "+i, toolTipTracksDrag); } var i = tableInfo.rows.length; while(--i > 0){ SetControlPoint(cps.length, {x:tableInfo.left, y:tableInfo.top + tableInfo.rows[i]}, "row_"+i, "Drag: Resize Row "+i, toolTipTracksDrag); } break; case "addremove": var i = tableInfo.cols.length; var cellElem = cells.elements[0]; var dir = ""; while(i--){ if (String(cellElem) == "[object Path]"){ dir = (i) ? " (Left)" : " (Right)"; SetControlPoint(cps.length, {x:tableInfo.left + tableInfo.cols[i], y:cellElem.top + cellElem.height/2}, "col_"+i, "Click: Add Column; Ctrl+Shift+Click: Remove Column"+dir, toolTipTracksDrag); } } var i = tableInfo.rows.length; while(i--){ if (String(cellElem) == "[object Path]"){ dir = (i) ? " (Above)" : " (Below)"; SetControlPoint(cps.length, {x:cellElem.left + cellElem.width/2, y:tableInfo.top + tableInfo.rows[i]}, "row_"+i, "Click: Add Row; Ctrl+Shift+Click: Remove Row"+dir, toolTipTracksDrag); } } /* Multiple rows/cols at once - TODO (at least, would be nice) SetControlPoint(cps.length, PointFromElem(tableElem, "top", "left"), "topleft", "Drag: Add Rows & Columns", toolTipTracksDrag); SetControlPoint(cps.length, PointFromElem(tableElem, "bottom", "right"), "bottomright", "Drag: Add Rows & Columns", toolTipTracksDrag); */ break; case "span": var cellElem = cells.elements[0]; SetControlPoint(cps.length, PointFromElem(cellElem, "top", "left"), "spacing", "Drag: Adjust Cell Spacing", toolTipTracksDrag); var i = cells.elements.length-1; while(i--){ cellElem = cells.elements[i]; if (String(cellElem) == "[object Path]"){ SetControlPoint(cps.length, PointFromElem(cellElem, "bottom", "right"), i, "Drag: Adjust Rowspan & Colspan", toolTipTracksDrag); } } break; case "text": var cellElem = cells.elements[0]; SetControlPoint(cps.length, PointsAdd({x:data.padding, y:data.padding}, PointFromElem(cellElem, "top", "left")), "padding", "Drag: Adjust Cell Padding", toolTipTracksDrag); var text = smartShape.elem.elements[textGroup]; // determine which cells have text var i = text.elements.length; var hasText = new Object(); while (--i > 0){ hasText[text.elements[i].customData.cellNum] = true; } // set control points i = cells.elements.length; toolTipTracksDrag = false; var cellElem, textNum, textElem, cpNum; while(i--){ cellElem = cells.elements[i]; if (String(cellElem) == "[object Path]"){ if (hasText[i]){ cpNum = cps.length; SetControlPoint(cpNum, PointFromElem(cellElem, "middle", "center"), i, "Click: Remove Text", toolTipTracksDrag); cps[cpNum].type = "defaultInverted"; }else{ SetControlPoint(cps.length, PointFromElem(cellElem, "middle", "center"), i, "Click: Add Text", toolTipTracksDrag); } } } break; case "color": toolTipTracksDrag = false; /* // TODO: (or NOTODO) Changing color of border hilite & lolite: // Would require separation of lines from cell elements // Could mean an excess number of elements making up the shape // is that really necessary for this? SetControlPoint(cps.length, PointFromElem(tableElem, "top", "left"), "topleft", "Click: Set Top/Left Border Color", toolTipTracksDrag); SetControlPoint(cps.length, PointFromElem(tableElem, "bottom", "right"), "bottomright", "Click: Set Bottom/Right Border Color", toolTipTracksDrag); */ var i = tableInfo.colCount; var cellElem; while(i--){ cellElem = cells.elements[i]; if (String(cellElem) == "[object Path]"){ SetControlPoint(cps.length, PointFromElem(cellElem, "top", "center"), "col_"+i, "Click: Set Column Color", toolTipTracksDrag); } } var i = tableInfo.rowCount; while(i--){ cellElem = cells.elements[i*tableInfo.colCount]; if (String(cellElem) == "[object Path]"){ SetControlPoint(cps.length, PointFromElem(cellElem, "middle", "left"), "row_"+i, "Click: Set Row Color", toolTipTracksDrag); } } break; } } UpdateToolTips = function(){ var cp = smartShape.currentControlPoint; var scheme = GetCurrentControlPointScheme(); var cpHash = GenerateCPHash(); if (scheme == "tableresize"){ var width = Math.round(cpHash["bottomright"].x - cpHash["topleft"].x); var height = Math.round(cpHash["bottomright"].y - cpHash["topleft"].y); cp.toolTip = "Width: "+width+" px; Height: "+height+" px"; }else{ cp.toolTip = cp.toolTip; } } UpdateTextLocations = function(){ var text = smartShape.elem.elements[textGroup]; var cells = smartShape.elem.elements[cellGroup]; var i = text.elements.length; var textElem, cellElem, loc; while (i--){ textElem = text.elements[i]; cellElem = cells.elements[textElem.customData.cellNum]; if (String(cellElem) == "[object Path]"){ loc = PointFromElem(cellElem, "top", "left"); textElem.rawLeft = loc.x + data.padding; textElem.rawWidth = Math.max(0, cellElem.width - data.padding*2); // KLUDDGE: better handling of vertical text positioning? // check distance from top and save as data and use that to position? minTop = cellElem.top + data.padding; //~ if (textElem.rawTop < minTop){ textElem.rawTop = minTop; //~ } } } } /* **************************************** INFORMATION **************************************** */ GetCurrentControlPointScheme = function(){ var i = persistentcps; var scheme = ""; while (i--){ if (cps[i].type == "defaultInverted"){ scheme = cps[i].name; break; } } return scheme; } GetControlPointInfo = function(cp){ var info = new Object(); var ary = cp.name.split("_"); // row_1 => "row"/"col", index info.type = ary[0]; info.index = Number(ary[1]); return info; } GetTableInfo = function(){ if (_tableInfo) return _tableInfo; var tableElem = smartShape.elem.elements[tableGroup].elements[0]; _tableInfo = new Object(); eval("_tableInfo.rows = "+data.rows); eval("_tableInfo.cols = "+data.cols); _tableInfo.rowCount = _tableInfo.rows.length - 1; _tableInfo.colCount = _tableInfo.cols.length - 1; _tableInfo.cellCount = _tableInfo.rowCount * _tableInfo.colCount; _tableInfo.padding = Number(data.padding); _tableInfo.spacing = Number(data.spacing); _tableInfo.width = tableElem.width; _tableInfo.height = tableElem.height; _tableInfo.top = tableElem.top; _tableInfo.left = tableElem.left; _tableInfo.bottom = tableElem.top + tableElem.height; _tableInfo.right = tableElem.left + tableElem.width; return _tableInfo; } GenerateElementArray = function(){ var tableInfo = GetTableInfo(); var cells = smartShape.elem.elements[cellGroup]; var elemAry = new Array(); for (y=0; y elem.elements.length){ pos = elem.elements.length; } elem.elements[pos] = new Path(); elem.elements[pos].pathAttributes.brushPlacement = "inside"; if (elem.elements[pos].pathAttributes.brush) elem.elements[pos].pathAttributes.brush.antiAliased = false; if (elem.elements[pos].pathAttributes.fill) elem.elements[pos].pathAttributes.fill.edgeType = "hard"; elem.elements[pos].contours[0] = new Contour(); elem.elements[pos].contours[0].isClosed = true; var nods = elem.elements[pos].contours[0].nodes; nods.length = 4; SetNodePosition(nods[0], loc); SetNodePosition(nods[1], PointsAdd(loc, {x:size.width, y:0})); SetNodePosition(nods[2], PointsAdd(loc, {x:size.width, y:size.height})); SetNodePosition(nods[3], PointsAdd(loc, {x:0, y:size.height})); return elem.elements[pos]; } AddText = function(cellNum){ var text = smartShape.elem.elements[textGroup]; var cells = smartShape.elem.elements[cellGroup]; var textNum = text.elements.length; text.elements[textNum] = new Text(); var textElem = text.elements[textNum]; textElem.customData.cellNum = cellNum; textElem.antiAliased = false; textElem.autoExpand = false; textElem.autoKern = false; textElem.orientation = "horizontal left to right"; var loc = PointFromElem(cells.elements[cellNum], "top", "left"); textElem.rawLeft = loc.x + data.padding; textElem.rawTop = loc.y + data.padding; textElem.rawWidth = Math.max(0, cells.elements[cellNum].width - data.padding*2); var pathattrs = { fill:{ category:"fc_Solid", ditherColors:[ "#000000", "#000000" ], edgeType:"hard", feather:0, gradient:null, name:"fn_Normal", pattern:null, shape:"solid", stampingMode:"blend opaque", textureBlend:0, webDitherTransparent:false }, fillColor:"#000000", fillHandle1:{ x:49, y:122 }, fillHandle2:{ x:98, y:122 }, fillHandle3:{ x:49, y:106 }, fillOnTop:false, fillTexture:null }; CopyProperties(pathattrs, textElem.pathAttributes); textElem.textRuns = { initialAttrs:{ alignment:"left", antiAliasSharpness:192, antiAliasStrength:64, baselineShift:0, bold:false, face:"Arial", fillColor:"#000000", horizontalScale:1, italic:false, leading:1, leadingMode:"percentage", overSample:8, paragraphIndent:0, paragraphSpacingAfter:0, paragraphSpacingBefore:0, rangeKerning:0, size:"12pt", underline:false }, textRuns:[ { changedAttrs:{ }, characters:defaultText } ] }; } RemoveText = function(cellNum){ var text = smartShape.elem.elements[textGroup]; var i = text.elements.length; while (--i > 0){ if (text.elements[i].customData.cellNum == cellNum){ text.elements[i] = text.elements[text.elements.length-1]; text.elements.length--; } } } /* **************************************** BASIC FUNCTIONS **************************************** */ SetNodePosition = function(node, pt){ SetBezierNodePosition(node, pt,pt,pt); } SetBezierNodePosition = function(node, ptp, pt, pts){ node.predX = ptp.x; node.predY = ptp.y; node.x = pt.x; node.y = pt.y; node.succX = pts.x; node.succY = pts.y; } SetControlPoint = function(cpnum, pt, name, toolTip, toolTipTracksDrag){ cps[cpnum] = new ControlPoint(); var cp = cps[cpnum]; cp.x = pt.x; cp.y = pt.y; cp.name = name; cp.toolTip = toolTip; cp.toolTipTracksDrag = toolTipTracksDrag; } PointFromElem = function(elem, y, x){ var nods = elem.contours[0].nodes; var pt = {x:0, y:0}; switch(y){ case "top": pt.y = nods[0].y; break; case "middle": pt.y = nods[0].y + (nods[2].y - nods[0].y)/2; break; case "bottom": pt.y = nods[2].y; break; } switch(x){ case "left": pt.x = nods[0].x; break; case "center": pt.x = nods[0].x + (nods[2].x - nods[0].x)/2; break; case "right": pt.x = nods[2].x; break; } return pt; } PointsAdd = function(pt1, pt2){ return {x:pt1.x + pt2.x, y:pt1.y + pt2.y}; } PointsSubtract = function(pt1, pt2){ return {x:pt1.x - pt2.x, y:pt1.y - pt2.y}; } PointsMultiply = function(pt1, pt2){ return {x:pt1.x*pt2.x, y:pt1.y*pt2.y}; } PointMultiplyN = function(pt, n){ return {x:pt.x*n, y:pt.y*n}; } PointDivideN = function(pt, n){ return {x:pt.x/n, y:pt.y/n}; } CopyProperties = function(from, to){ for (var p in from) to[p] = from[p]; } if (operation[smartShape.operation]) operation[smartShape.operation]();