// Fireworks AutoShape // Description: Table-like cell based grid, with alternating cell styles. // Install by copying to Fireworks/Configuration/AutoShapes/ // Run in Fireworks via the Window > AutoShapes // Aaron Beall - http://abeall.com /* TODO - [DONE] draggable row/column counters - [DONE] alt-drag corner/size control points to absolute resize - - alt drag corner to do the same - - live drag switching between absolute/relative by pressing and releasing alt during drag - [PART] finish cell style inheritence - - inherit live effects - - maintain fill handle relative positions - [DONE] constrain Size and Corner to not allow inverted grid - constrain padding to not allow overpadding - individual column/row size control points - - alt drag a row/column control point to create new row or column - text blocks - [PART] vertical and horizontal dividers - - [DONE] style propogation - - [DONE] changing cells to update dividers - - make dividers optional - create helper commands - - copy grid styles - - paste grid styles - - redraw grid styles(use top left 2x2 grid as basis to style entire grid) - - distribute to grid(duplicate to fill grid, cell position, element position, x offset, y offset) - allow cells to be manipulated into different shapes, and resize cells rather than redraw in order to respect different shapes - - when creating new cells, copy previous cells shape as well as visual style - [DONE] when adding rows and columns, look at the entire previous row/column instead of the just the bottom right 2x2 - optimize code - optimize preview rendering - cleanup deprecated code BUGS - divider strokes are behind the cells, which is a problem when there is no padding - [FIXED] cell style propogation breaks a lot when pathAttributes are not as expected, need to try the toSource eval method for pathAttributes - brushTexture is not propogated - [FIXED] canceling on the Size numeric prompt throws an error - somehow the Column/Row control points get offset sometimes */ var dom = fw.getDocumentDOM(); var mouse = smartShape.currentMousePos; var cps = smartShape.elem.controlPoints; var data = smartShape.elem.customData; var elems = smartShape.elem.elements; var CP_PADDING = 8; var operation = new Object(); operation.InsertSmartShapeAt = function(){ var width = 150, height = 150; if(!fw.GridAutoShape_lastSettings) fw.GridAutoShape_lastSettings = {}; data.shapeName = "grid"; data.cols = 3//fw.GridAutoShape_lastSettings.cols||3; data.colpad = 3//fw.GridAutoShape_lastSettings.colpad||3; data.rows = 3//fw.GridAutoShape_lastSettings.rows||3; data.rowpad = 3//fw.GridAutoShape_lastSettings.rowpad||3; data.vDivs = true;//fw.GridAutoShape_lastSettings.vDivs||true; data.hDivs = true;//fw.GridAutoShape_lastSettings.hDivs||true; //data._cells = {}; cps.length = 7; SetControlPoint(cps[0], AddPoints(mouse, {x:-width/2, y:-height/2}), "Corner", "Corner"); SetControlPoint(cps[1], AddPoints(mouse, {x:width/2, y:height/2}), "Size", "Size: "+width+"x"+height+"; Alt/Opt drag to add cells"); cps[1].toolTipTracksDrag = true; SetControlPoint(cps[2], AddPoints(mouse, {x:-width/2-CP_PADDING, y:height/2}), "Rows", "Rows: 3"); cps[2].type = 'defaultInverted'; cps[2].toolTipTracksDrag = true; SetControlPoint(cps[3], AddPoints(mouse, {x:width/2, y:-height/2-CP_PADDING}), "Columns", "Columns: 3"); cps[3].type = 'defaultInverted'; cps[3].toolTipTracksDrag = true; SetControlPoint(cps[4], AddPoints(mouse, {x:-width/2+CP_PADDING, y:-height/2+height/data.rows}), "Row Padding", "Row Padding: 3px"); cps[4].toolTipTracksDrag = true; SetControlPoint(cps[5], AddPoints(mouse, {x:-width/2+width/data.cols, y:-height/2+CP_PADDING}), "Column Padding", "Column Padding: 3px"); cps[5].toolTipTracksDrag = true; SetControlPoint(cps[6], AddPoints(mouse, {x:-width/2, y:-height/2}), "Redraw", "Redraw Cells; the top left 2x2 cells will define the style for all cells in the grid"); cps[6].type = 'crossHair'; updateControlPointToolTips(); createGrid(); } operation.BeginDragControlPoint = function(){ data._click = true; var parms = smartShape.GetDefaultMoveParms(); var cp = smartShape.currentControlPoint; switch(cp.name){ case 'Size': data._resizeAbsolute = smartShape.altOptKeyDown; data._prevSize = {x:cp.x,y:cp.y}; parms.minX = cpt('Corner').x+data.cols+data.colpad*data.cols; parms.minY = cpt('Corner').y+data.rows+data.rowpad*data.rows; case 'Corner': if(cp.name=='Corner') parms.maxX = cpt('Size').x-data.cols-data.colpad*data.cols; if(cp.name=='Corner') parms.maxY = cpt('Size').y-data.rows-data.rowpad*data.rows; parms.constrain45Key = 'shiftKey'; cp.RegisterMove(parms); break; case 'Columns': data._prevCols = data.cols; data._prevX = cp.x; data._colWidth = elems[2].elements[elems[2].elements.length-1].width; cp.RegisterLinearMove({x:cp.x-5,y:cp.y},parms); break; case 'Rows': data._prevRows = data.rows; data._prevY = cp.y; data._rowHeight = elems[2].elements[elems[2].elements.length-1].height; cp.RegisterLinearMove({x:cp.x,y:cp.y-5},parms); break; case 'Row Padding': data._prev = data.rowpad; parms.maxLinear = data.rowpad;//(cpt('Corner').y+data.rowpad*2+elems[2].elements[0].height)-(cpt('Corner').y+elems[2].elements[0].height); cp.RegisterLinearMove({x:cp.x,y:cp.y-5},parms); break case 'Column Padding': data._prev = data.colpad; parms.maxLinear = data.colpad; cp.RegisterLinearMove({x:cp.x-5,y:cp.y},parms); break; } smartShape.getsDragEvents = true; } operation.DragControlPoint = function(){ data._click = false; var cp = smartShape.currentControlPoint; switch(cp.name){ case 'Size': if(data._resizeAbsolute){ var cell = elems[2].elements[elems[2].elements.length-1]; var cellWidth = cell.width + data.colpad; var cellHeight = cell.height + data.rowpad; var travel = cp.x - data._prevSize.x; if(travel >= cellWidth || travel <= -cellWidth){ var dir = travel > 1 ? 1 : -1; var newCols = Math.floor( (Math.abs(mouse.x - data._prevSize.x))/cellWidth ) * dir; var cpX = cp.x, cpY = cp.y; cp.x = data._prevSize.x + cellWidth*newCols; cp.y = data._prevSize.y; data._prevSize = {x:data._prevSize.x + cellWidth*newCols, y:data._prevSize.y}; changeGrid(data.rows,data.cols + newCols); cp.x = cpX; cp.y = cpY; } travel = cp.y - data._prevSize.y; if(travel >= cellHeight || travel <= -cellHeight){ var dir = travel > 1 ? 1 : -1; var newRows = Math.floor( (Math.abs(mouse.y - data._prevSize.y))/cellHeight ) * dir; var cpX = cp.x, cpY = cp.y; cp.x = data._prevSize.x; cp.y = data._prevSize.y + cellHeight*newRows; data._prevSize = {x:data._prevSize.x, y:data._prevSize.y + cellHeight*newRows}; changeGrid(data.rows + newRows,data.cols); cp.x = cpX; cp.y = cpY; } } updateControlPointToolTips(); case 'Corner': updateControlPoints(); break; case 'Rows': var newRows = Math.max(1, data._prevRows + Math.round((mouse.y - data._prevY)/data._rowHeight) ); changeGrid(newRows, data.cols); updateControlPointToolTips(); break; case 'Columns': var newCols = Math.max(1, data._prevCols + Math.round((mouse.x - data._prevX)/data._colWidth) ); changeGrid(data.rows, newCols); updateControlPointToolTips(); break; case 'Column Padding': data.colpad = fw.GridAutoShape_lastSettings.colpad = Math.floor(cp.x-cpt('Corner').x); updateControlPointToolTips(); updateControlPoints(); break; case 'Row Padding': data.rowpad = fw.GridAutoShape_lastSettings.rowpad = Math.floor(cp.y-cpt('Corner').y);//Math.floor((cp.y-elems[2].elements[0].height-cpt('Corner').y)/2); updateControlPointToolTips(); updateControlPoints(); break; } if(!data._resizeAbsolute) updateGrid(); } operation.EndDragControlPoint = function(){ var cp = smartShape.currentControlPoint; switch(cp.name){ case 'Columns': if(data._click){ var cols = Number(prompt('Enter the number of columns:',data.cols)); if(!isNaN(cols) && cols>0 && Math.floor(cols)==cols && data.cols!=cols) changeGrid(data.rows,cols); } updateControlPoints(); break; case 'Rows': if(data._click){ var rows = Number(prompt('Enter the number of rows:',data.rows)); if(!isNaN(rows) && rows>0 && Math.floor(rows)==rows && data.rows!=rows) changeGrid(rows,data.cols); } updateControlPoints(); break; case 'Column Padding': if(data._click/*data.colpad==data._prev*/){ var pad = prompt('Enter the amount of column padding:',data.colpad); if(pad == null) break; pad = Number(pad); if(!isNaN(pad) && pad>=0){ data.colpad = fw.GridAutoShape_lastSettings.colpad = pad; updateControlPoints(); updateGrid(); } } break; case 'Row Padding': if(data._click/*rowpad==data._prev*/){ var pad = prompt('Enter the amount of row padding:',data.rowpad); if(pad == null) break; pad = Number(pad); if(!isNaN(pad) && pad>=0){ data.rowpad = fw.GridAutoShape_lastSettings.rowpad = pad; updateControlPoints(); updateGrid(); } } break; case 'Redraw': redrawCellStyles(); break; case 'Size': if(data._click){ var size = prompt('Enter a new size, formatted as "Width,Height":',(cpt('Size').x-cpt('Corner').x)+','+(cpt('Size').y-cpt('Corner').y));; if(size==null) break; size = size.split(','); if(size.length!=2 || isNaN(Number(size[0])) || isNaN(Number(size[1]))) break; cpt('Size').x = cpt('Corner').x+Number(size[0]); cpt('Size').y = cpt('Corner').y+Number(size[1]); updateGrid(); }else if(data._resizeAbsolute){ var cell = elems[2].elements[elems[2].elements.length-1]; cp.x = cell.left + cell.width + data.colpad; cp.y = cell.top + cell.height + data.rowpad; data._resizeAbsolute = null; } default: updateControlPoints(); updateGrid(); break; } updateControlPointToolTips(); } operation.SmartShapeEdited = function(){ updateGrid(); } 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(cp, pt, name, toolTip){ cp.x = pt.x; cp.y = pt.y; cp.name = name; cp.toolTip = toolTip; } AddPoints = function(pt1, pt2){ return {x:pt1.x + pt2.x, y:pt1.y + pt2.y}; } if (operation[smartShape.operation]) operation[smartShape.operation](); // initial creation of the grid function createGrid(){ var rows = data.rows, cols = data.cols; var rowCount = 0, rowOpacity; var p = -1; elems[0] = new Group(); //elems[0].name = "Horizontal Dividers"; elems[1] = new Group(); //elems[1].name = "Vertical Dividers"; elems[2] = new Group(); //elems[2].name = "Cells"; var cell; for(var r=0; rdata.cols){ // add columns // first add all the new cell paths newPathCount = cols*data.rows; for(var p=data.cols*data.rows; p=0; r--){ for(var c=cols-1; c>=0; c--){ p--; if(c<=data.cols-1) applyStyles(elems[2].elements[p],elems[2].elements[p-(cols-data.cols)*r]); } } // and lastly, enumerate from first to last and apply new cell styles based on previous cell styles p = -1; for(var r=0; rdata.cols-1){ applyStyles(elems[2].elements[p],elems[2].elements[p - (data.cols>1 ? 2 : 1)]); } } } // add new column dividers if(data.vDivs){ var div; for(var c=data.cols-1; ccols-1) continue; p++; applyStyles(elems[2].elements[p],elems[2].elements[oldP]); } } elems[2].elements.length = p+1; elems[1].elements.length = cols-1; } data.cols = fw.GridAutoShape_lastSettings.cols = cols; } if(rows != data.rows){ // row change if(rows>data.rows){ // adding rows var p = data.rows*data.cols - 1; for(var r=data.rows; r1 ? elems[2].elements[1] : cellTL; var cellBL = data.rows>1 ? elems[2].elements[data.cols] : cellTL; var cellBR = data.rows>1 && data.cols>1 ? elems[2].elements[data.cols+1] : cellTR; var vDiv1 = elems[1].elements[0]; var vDiv2 = elems[1].elements.length>1 ? elems[1].elements[1] : vDiv1; var hDiv1 = elems[0].elements[0]; var hDiv2 = elems[0].elements.length>1 ? elems[0].elements[1] : hDiv1; var rows = data.rows, cols = data.cols, p = -1; var rowCount = 0, colCount = 0; for(var r=0; r