/******************************************************************************* * Copyright (c) 2009-2013 CWI * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html *******************************************************************************/ package org.rascalmpl.eclipse.library.vis.figure.compose; import static org.rascalmpl.eclipse.library.vis.properties.TwoDProperties.ALIGN; import static org.rascalmpl.eclipse.library.vis.properties.TwoDProperties.END_GAP; import static org.rascalmpl.eclipse.library.vis.properties.TwoDProperties.GAP; import static org.rascalmpl.eclipse.library.vis.properties.TwoDProperties.GROW; import static org.rascalmpl.eclipse.library.vis.properties.TwoDProperties.SHRINK; import static org.rascalmpl.eclipse.library.vis.properties.TwoDProperties.START_GAP; import static org.rascalmpl.eclipse.library.vis.util.Util.flatten; import static org.rascalmpl.eclipse.library.vis.util.Util.makeRectangular; import static org.rascalmpl.eclipse.library.vis.util.vector.Dimension.HOR_VER; import static org.rascalmpl.eclipse.library.vis.util.vector.Dimension.Y; import java.util.Arrays; import java.util.List; import org.rascalmpl.eclipse.library.vis.figure.Figure; import org.rascalmpl.eclipse.library.vis.figure.combine.containers.Space; import org.rascalmpl.eclipse.library.vis.graphics.GraphicsContext; import org.rascalmpl.eclipse.library.vis.properties.PropertyManager; import org.rascalmpl.eclipse.library.vis.swt.applet.IHasSWTElement; import org.rascalmpl.eclipse.library.vis.util.vector.Dimension; import org.rascalmpl.eclipse.library.vis.util.vector.Rectangle; import org.rascalmpl.eclipse.library.vis.util.vector.TwoDimensional; /* TODO: This is horibly horibly horibly overcomplicated! */ public class Grid extends Compose { public static final String OVERCONSTRAINED_MESSAGE = "Grid Overconstrained!"; Figure[][] figureMatrix ; int nrColumns, nrRows; TwoDimensional<double[]> columnBorders; TwoDimensional<Size[]> columnsSize; TwoDimensional<Double> totalShrinkAllSetColumns; TwoDimensional<double[]> someShrinksSetShrinks; TwoDimensional<Integer> nrUnresizableColumns; TwoDimensional<Integer> nrShrinkNoneColumns; TwoDimensional<Integer> nrShrinkSomeColumns; TwoDimensional<Integer> nrShrinkAllColumns; TwoDimensional<Double> unresizableColumnsWidth; boolean overConstrained; static enum SizeInfo{ ALL_SHRINK_SET, SOME_SHRINK_SET, NONE_SHRINK_SET, UNRESIZABLE; } static class Size{ SizeInfo sizeInfo; double minSize; double maxShrink; double minSizeOfGrid; Size(SizeInfo sizeInfo, double minSize,double maxShrink, double minSizeOfGrid){ this.sizeInfo = sizeInfo; this.minSize = minSize; this.maxShrink = maxShrink; this.minSizeOfGrid = minSizeOfGrid; } Size(SizeInfo sizeInfo, double minSize){ this(sizeInfo, minSize,0,0); } } public Grid(Figure[][] figureMatrix,PropertyManager properties) { super(flatten(Figure.class,figureMatrix),properties); this.figureMatrix = figureMatrix; makeRectangular(figureMatrix,Space.empty); nrRows = figureMatrix.length; nrColumns = figureMatrix[0].length; overConstrained = false; columnBorders = new TwoDimensional<double[]>(new double[nrColumns], new double[nrRows]) ; columnsSize = new TwoDimensional<Grid.Size[]>(new Size[nrColumns], new Size[nrRows]); unresizableColumnsWidth = new TwoDimensional<Double>(0.0,0.0); totalShrinkAllSetColumns = new TwoDimensional<Double>(0.0,0.0); nrUnresizableColumns = new TwoDimensional<Integer>(0, 0); nrShrinkNoneColumns = new TwoDimensional<Integer>(0, 0); nrShrinkSomeColumns= new TwoDimensional<Integer>(0, 0); nrShrinkAllColumns= new TwoDimensional<Integer>(0, 0); someShrinksSetShrinks = new TwoDimensional<double[]>(null,null); } public void computeMinSize(){ overConstrained = false; for(Dimension d : HOR_VER){ computeMinWidth(d); } if(overConstrained){ minSize.set(getTextWidth(OVERCONSTRAINED_MESSAGE),getTextAscent() + getTextDescent()); } } public void computeMinWidth(Dimension d){ setSizeInfoOfColumns(d); setColumnTypeCounts(d); this.unresizableColumnsWidth.set(d, totalMinWidthOfUnresizableColumns(d)); this.totalShrinkAllSetColumns.set(d,totalShrinkAllSetColumns(d)); double minWidth = minWidthByUnShrinking(d); if( totalShrinkAllSetColumns.get(d) == 1.0 && nrShrinkAllColumns.get(d) < getNrColumns(d)){ overConstrained = true; return; } if(totalShrinkAllSetColumns.get(d) > 1.0){ overConstrained = true; return; } minWidth = Math.max(minWidth,unresizableColumnsWidth.get(d)/ (1.0 - totalShrinkAllSetColumns.get(d))); double maxMinWidthOfAutoElem = maxMinWidthOfAutoElement(d); double shrinkLeftOver = 1.0 - totalShrinkAllSetColumns.get(d); minWidth = getAutoElementsShrinkMinWidth(shrinkLeftOver,d,minWidth,maxMinWidthOfAutoElem); if(minWidth == -1){ overConstrained = true; return; } double minWidthWithGrow = minWidth * prop.get2DReal(d, GROW); double minWidthWithGaps = minWidth + (double)nrHGaps(d) * prop.get2DReal(d, GAP); minWidth = Math.max(minWidthWithGaps, minWidthWithGrow); if(nrUnresizableColumns.get(d) == 1 && getNrColumns(d) == 1){ resizable.set(d,false); } this.minSize.set(d, minWidth); } private double totalMinWidthOfUnresizableColumns(Dimension d){ double result = 0; Size[] columnSize = this.columnsSize.get(d); for(Size s : columnSize){ if(s.sizeInfo == SizeInfo.UNRESIZABLE){ result += s.minSize; } } return result; } private double minWidthByUnShrinking(Dimension d){ double minWidth = 0; for(Figure[] row : figureMatrix){ for(Figure elem : row){ if(elem.prop.is2DPropertySet(d, SHRINK) && elem.resizable.get(d)){ minWidth = Math.max(minWidth,elem.minSize.get(d) / elem.prop.get2DReal(d, SHRINK)); } } } return minWidth; } private double maxMinWidthOfAutoElement(Dimension d){ double maxMinWidth = 0; for(int i = 0; i < getNrColumns(d); i++){ boolean columnUnresizable = columnsSize.get(d)[i].sizeInfo == SizeInfo.UNRESIZABLE; for(int j = 0 ; j < getNrRows(d) ; j++){ Figure elem = getFigureFromMatrix(d, j,i); if(!elem.prop.is2DPropertySet(d, SHRINK) && !columnUnresizable){ maxMinWidth = Math.max(maxMinWidth,elem.minSize.get(d)); } } } return maxMinWidth; } private double totalShrinkAllSetColumns(Dimension d){ double result = 0; Size[] columnSize = this.columnsSize.get(d); for(Size s : columnSize){ if(s.sizeInfo == SizeInfo.ALL_SHRINK_SET){ result += s.maxShrink; } } return result; } private void setSomeShrinkSetSorted(Dimension d) { double[] result; if(someShrinksSetShrinks.get(d) == null || someShrinksSetShrinks.get(d).length != nrShrinkSomeColumns.get(d)){ result = new double[nrShrinkSomeColumns.get(d)]; someShrinksSetShrinks.set(d,result); } else { result = someShrinksSetShrinks.get(d); } int i = 0; for(Size s : columnsSize.get(d)){ if(s.sizeInfo == SizeInfo.SOME_SHRINK_SET){ result[i] = s.maxShrink; i++; } } Arrays.sort(result); } private void setColumnTypeCounts(Dimension d){ int nrAllSet = 0; int nrSomeSet = 0; int nrNoneSet = 0; int nrUnresizable = 0; Size[] columnSize = this.columnsSize.get(d); for(Size s : columnSize){ switch(s.sizeInfo){ case ALL_SHRINK_SET : nrAllSet++; break; case SOME_SHRINK_SET : nrSomeSet++; break; case NONE_SHRINK_SET: nrNoneSet++; break; case UNRESIZABLE: nrUnresizable++; break; } } this.nrShrinkAllColumns.set(d, nrAllSet); this.nrShrinkSomeColumns.set(d, nrSomeSet); this.nrShrinkNoneColumns.set(d,nrNoneSet); this.nrUnresizableColumns.set(d,nrUnresizable); } private void setSizeInfoOfColumns(Dimension d){ Size[] result = columnsSize.get(d); for(int column = 0 ; column < getNrColumns(d); column++){ result[column] = getSizeInfoOfColumn(d, column); } } private Size getSizeInfoOfColumn(Dimension d, int column){ double minSize = 0; double maxShrink = 0; double minSizeOfGrid = 0; boolean resizable = false; boolean autoSize = true; boolean allShrinkSet = true; for(int row = 0 ; row < getNrRows(d); row++){ Figure fig = getFigureFromMatrix(d, row, column); resizable= resizable || fig.resizable.get(d); if(fig.resizable.get(d) && fig.prop.is2DPropertySet(d, SHRINK)){ autoSize = false; maxShrink = Math.max(maxShrink, fig.prop.get2DReal(d, SHRINK)); minSizeOfGrid = Math.max(minSizeOfGrid, fig.minSize.get(d) / fig.prop.get2DReal(d, SHRINK)); } else { allShrinkSet = false; } minSize= Math.max(minSize,fig.minSize.get(d)); } SizeInfo sizeInfo; if(!resizable){ sizeInfo = SizeInfo.UNRESIZABLE; } else if(autoSize){ sizeInfo = SizeInfo.NONE_SHRINK_SET; } else if(allShrinkSet){ sizeInfo = SizeInfo.ALL_SHRINK_SET; } else { sizeInfo = SizeInfo.SOME_SHRINK_SET; } return new Size(sizeInfo,minSize,maxShrink,minSizeOfGrid); } private double getAutoElementsShrinkMinWidth(double shrinkLeftOver,Dimension d,double minWidthEstimate,double maxMinWidthOfAutoElem){ if(shrinkLeftOver < 0) return -1; // this is where the meat of the layout is, which is fairly complicated, but very fast // this required some thinking, get ready : // we want to solve f in : // sum({max(f,someShrinksSetShrinks[i]) | i <- [0..nrSomeShrinkSetColumns-1]}) + f * nrOfNoneShrinkSetCollumns = shrinkLeftOver // i.e. shrinkLeftOver = sum({someShrinksSetShrinks[j] | someShrinksSetShrinks[j] > f, j in [0..nrSomeShrinkSetColumns-1]}) // + f * (size({someShrinksSetShrinks[i] | someShrinksSetShrinks[i] <= f,j in [0..nrColumnsSomeShrinkSet-1]}) + nrOfNoneShrinkSetCollumns // i.e. shrinkLeftOver = sum(S) + (nrColumnsSomeOrNoneShrinkSet - size(S) )*f // where S = {someShrinksSetShrinks[j] | someShrinksSetShrinks[j] > f, j in [0..nrSomeShrinkSetColumns-1]} // to do this we first sort the columns with some shrinkset in descending max shrink order setSomeShrinkSetSorted(d); double[] someShrinksSetShrinks = this.someShrinksSetShrinks.get(d); // (the array is actually sorted in ascending order because // java does not offer an fast,easy way to sort doubles in descending order // therefore we simply index from the back) // to estimate let us assume that S == {} // now we begin with the highest estimate f = shrinkLeftOver / nrColumnsSomeOrNoneShrinkSet int nrColumnsSomeOrNoneShrinkSet = nrShrinkSomeColumns.get(d) + nrShrinkNoneColumns.get(d); double fEstimate = shrinkLeftOver / nrColumnsSomeOrNoneShrinkSet; // some corner cases: if(nrShrinkSomeColumns.get(d) == 0){ double totalMinWidth = (maxMinWidthOfAutoElem * (nrColumnsSomeOrNoneShrinkSet) + unresizableColumnsWidth.get(d)) /shrinkLeftOver; minWidthEstimate = Math.max(minWidthEstimate, totalMinWidth); return minWidthEstimate = Math.max(minWidthEstimate, totalMinWidth); } if(nrColumnsSomeOrNoneShrinkSet == 0) { if(unresizableColumnsWidth.get(d) != 0|| shrinkLeftOver < 0.0){ return -1; // Overspecified! } else { return minWidthEstimate; // there is nothing to do here } } // now we start from the front (highest shrink) (i=1) to see if maxColumShrink(i) <= f // if so, we are done (because the other values are all smaller or equal than maxColumShrink(i) // if not, then we know that // S' = {someShrinksSetShrinks[j] | j in [0..i]} is a subset of S // because the array is sorted and all previous values where higher // thus sum(S') <= sum(S) // for the new estimate assume that S' == S // so that we get the highest estimate again // the new estimate then becomes shrinkLeftOver = sum(S') + f * (nrColumnsSomeOrNoneShrinkSet - size(S')) // i.e. f = (shrinkLeftOver - sum(S')) / (nrColumnsSomeOrNoneShrinkSet - size(S')) // f = (shrinkLeftOver - sum(S')) / (nrColumnsSomeOrNoneShrinkSet - i) // time complexity: O(n*log(n)) (because of sorting) // space complexity: O(n) double currentSumSPrime = 0; int i = 1; for(int j = someShrinksSetShrinks.length-1; j >= 0 ; j--,i++){ if(someShrinksSetShrinks[j] < fEstimate){ // however, up until now we assumed that there were no unresizable columns // if we drop this assumption, thing turn even nastier // the unresizable column also needs a part of the available space // so the f described thus far is only an upper bound // the spaceleftOver is decreased by the unresizable collumns by unresizableColumns/totalMinWidth // however this depends on the totalMinWidth which relies on f.... // let shrinkLeftOver -= currentSumSPrime shrinkLeftOver-= currentSumSPrime; // then we know that f = (shrinkLeftOver - ( unresizableColumns/totalMinWidth)) / (nrColumnsSomeOrNoneShrinkSet - (i-1)) // and totalMinWidth = maxMinSizeAutoElement / f // combining these two gives // totalMinWidth = maxMinSizeAutoElement / ((shrinkLeftOver - ( unresizableColumns/totalMinWidth)) / (nrColumnsSomeOrNoneShrinkSet - (i-1))) // rewriting gives totalMinWidth = (maxMinSizeAutoElement * (nrColumnsSomeOrNoneShrinkSet - (i-1)) + unresizableColumns) /shrinkLeftOver double totalMinWidth = (maxMinWidthOfAutoElem * (nrColumnsSomeOrNoneShrinkSet - (i-1)) + unresizableColumnsWidth.get(d)) /shrinkLeftOver; minWidthEstimate = Math.max(minWidthEstimate, totalMinWidth); // however f = (shrinkLeftOver - unresizableColumns/totalMinWidth) / (nrColumnsSomeOrNoneShrinkSet - (i-1)) double mayBeF = (shrinkLeftOver - unresizableColumnsWidth.get(d)/totalMinWidth) / (nrColumnsSomeOrNoneShrinkSet - (i-1)); // so changing the totalMinWidth might cause our estimation of f to drop below someShrinksSetShrinks[i] if(mayBeF >= someShrinksSetShrinks[j]){ // if this doesn't happen, we are done! fEstimate = mayBeF; break; } else { // if our new fEstimate caused f to drop below someShrinksSetShrinks[i] // we must take special measures // first undo the shrinkLeftOver-= currentSumSPrime; shrinkLeftOver+= currentSumSPrime; // set estimate to fEstimate = someShrinksSetShrinks[j] ; // and try again! (rollback counters to goto second case) j++; i--; } } else { currentSumSPrime += someShrinksSetShrinks[j]; if(i == nrColumnsSomeOrNoneShrinkSet){ // prevent division by zero when overconstrained fEstimate = -1; break; } fEstimate = (shrinkLeftOver - currentSumSPrime) / (nrColumnsSomeOrNoneShrinkSet -i); } } if(fEstimate <= 0){ return -1; } return Math.max(minWidthEstimate, maxMinWidthOfAutoElem / fEstimate); // return actual minwidth } private double getActualShrinkOfAutoElements(double shrinkLeftOver,Dimension d){ // TODO: this is pretty much the same as above, merge double currentSumSPrime = 0; int i = 1; double[] someShrinksSetShrinks = this.someShrinksSetShrinks.get(d); int nrColumnsSomeOrNoneShrinkSet = nrShrinkSomeColumns.get(d) + nrShrinkNoneColumns.get(d); if(nrShrinkSomeColumns.get(d) == 0){ return shrinkLeftOver / nrColumnsSomeOrNoneShrinkSet; } if(nrColumnsSomeOrNoneShrinkSet == 0) { return 1.0; } double fEstimate = shrinkLeftOver / nrColumnsSomeOrNoneShrinkSet; for(int j = someShrinksSetShrinks.length-1; j >= 0 ; j--,i++){ if(someShrinksSetShrinks[j] < fEstimate){ return fEstimate; } else { currentSumSPrime += someShrinksSetShrinks[j]; fEstimate = (shrinkLeftOver - currentSumSPrime) / (nrColumnsSomeOrNoneShrinkSet -i); } } return fEstimate; } public void layoutX(Dimension d) { if(overConstrained) return; double spaceForColumns = size.get(d) / prop.get2DReal(d, GROW); spaceForColumns = Math.min(spaceForColumns, size.get(d) - (double)nrHGaps(d) * prop.get2DReal(d, GAP)); double spaceLeftOver = spaceForColumns - unresizableColumnsWidth.get(d); double shrinkLeftOver = (spaceLeftOver / spaceForColumns) - totalShrinkAllSetColumns.get(d); double shrinkOfAutoElement = getActualShrinkOfAutoElements(shrinkLeftOver,d); double sizeOfAutoElement = shrinkOfAutoElement * spaceForColumns; double whitespace = size.get(d) - spaceForColumns; double extraSpaceForUnresizableCols = 0; if(nrUnresizableColumns.get(d) == getNrColumns(d)){ extraSpaceForUnresizableCols = spaceLeftOver / getNrColumns(d); } double left = 0; double gapSize = whitespace / (double)nrHGaps(d) ; if(nrHGaps(d) == 0.0){ gapSize = 0.0; } if(prop.get2DBool(d, START_GAP)){ left+=gapSize*0.5; } for(int column = 0 ; column < getNrColumns(d) ; column++){ columnBorders.get(d)[column]=left; Size s = columnsSize.get(d)[column]; double colWidth = 0; switch(s.sizeInfo){ case ALL_SHRINK_SET : colWidth = s.maxShrink * spaceForColumns; break; case SOME_SHRINK_SET: colWidth = Math.max(sizeOfAutoElement,s.maxShrink * spaceForColumns); break; case NONE_SHRINK_SET: colWidth =sizeOfAutoElement ; break; case UNRESIZABLE: colWidth = s.minSize + extraSpaceForUnresizableCols; break; } for(int row = 0 ; row < getNrRows(d); row++){ Figure elem = getFigureFromMatrix(d,row,column); if(elem.prop.is2DPropertySet(d, SHRINK)){ elem.size.set(d, elem.prop.get2DReal(d, SHRINK)*spaceForColumns); } else if(!elem.resizable.get(d)){ elem.size.set(d,elem.minSize.get(d)); } else { elem.size.set(d,sizeOfAutoElement); } double margin =Math.max(0.0,(colWidth- elem.size.get(d)) * elem.prop.get2DReal(d, ALIGN)) ; setXPos(d, row, column, left + margin); } left+=gapSize + colWidth; } size.set(d, left - gapSize); } double nrHGaps(Dimension d) { double nrGaps = getNrColumns(d)-1 ; if(prop.get2DBool(d, START_GAP)){ nrGaps+=0.5; } if(prop.get2DBool(d, END_GAP)){ nrGaps+=0.5; } return nrGaps; } int getNrColumns(Dimension d){ if(d == Y) return nrRows; else return nrColumns; } int getNrRows(Dimension d){ if(d == Y) return nrColumns; else return nrRows; } private Figure getFigureFromMatrix(Dimension d, int row, int collumn){ if(d == Y) return figureMatrix[collumn][row]; else return figureMatrix[row][collumn]; } private void setXPos(Dimension d, int row, int collumn,double val){ getFigureFromMatrix(d, row, collumn).localLocation.set(d,val); } @Override public void drawElement(GraphicsContext gc, List<IHasSWTElement> visibleSWTElements){ if(overConstrained) { gc.text(OVERCONSTRAINED_MESSAGE, globalLocation.getX() + 0.5 * size.getX() - getTextWidth(OVERCONSTRAINED_MESSAGE), globalLocation.getY() + 0.5 * size.getY() - getTextAscent()); return; } } @Override public void resizeElement(Rectangle view) { for(Dimension d: HOR_VER){ layoutX(d); } } public String toString(){ return "GRID"; } /* public void getFiguresUnderMouseSmart(Coordinate c, Vector<Figure> result){ int row = binaryIntervalSearch(columnBorders.getY(), c.getY() - location.getY()); int column = binaryIntervalSearch(columnBorders.getX(), c.getX() - location.getX()); if(row >= 0 && column >= 0){ figureMatrix[row][column].getFiguresUnderMouse(c, result); } } */ /* @Override public void drawVisibleChildrenSmart( Vector<IHasSWTElement> visibleSWTElements, GraphicsContext gc, Rectangle rect){ int startRow = Math.max(0,Util.binaryIntervalSearch(columnBorders.getForY(), rect.getY() - getTop())); int endRow = Math.max(0,Util.binaryIntervalSearch(columnBorders.getForY(), rect.getYDown() - getTop())); int startColumn = Math.max(0,Util.binaryIntervalSearch(columnBorders.getForX(), rect.getX() - getLeft())); int endColumn = Math.max(0,Util.binaryIntervalSearch(columnBorders.getForX(), rect.getXRight() - getLeft())); //System.out.printf("Drawpart grid rows %d till %d of %d collumns %d till %d of %d\n", startRow, endRow, figureMatrix.length, startColumn, endColumn, figureMatrix[0].length); for(int row = startRow ; row <= endRow ; row++){ for(int collumn = startColumn ; collumn <= endColumn; collumn++){ if( (row == startRow && columnBorders.getForY()[startRow] < rect.getY()) || (row == endRow && columnBorders.getForY()[endRow] > rect.getYDown()) || (collumn == startColumn && columnBorders.getForX()[startColumn] < rect.getX()) || (collumn == endColumn && columnBorders.getForX()[endColumn] > rect.getXRight())){ figureMatrix[row][collumn].draw(visibleSWTElements,gc,rect); } else { figureMatrix[row][collumn].draw(visibleSWTElements,gc,null); } } } } */ }