/** * Copyright 2004-2016 Riccardo Solmi. All rights reserved. * This file is part of the Whole Platform. * * The Whole Platform is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * The Whole Platform is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with the Whole Platform. If not, see <http://www.gnu.org/licenses/>. */ package org.whole.lang.ui.layout; import java.util.List; import org.eclipse.draw2d.IFigure; import org.eclipse.draw2d.geometry.Dimension; import org.eclipse.draw2d.geometry.Insets; import org.eclipse.draw2d.geometry.Rectangle; import org.whole.lang.ui.figures.EntityFigure; import org.whole.lang.ui.figures.IEntityFigure; import org.whole.lang.ui.figures.IViewportTrackingStrategy; /** * @author Riccardo Solmi */ public abstract class AbstractEntityLayout implements IEntityLayout { protected int marginLeft, marginRight, marginTop, marginBottom; protected float majorAutoresizeWeight, minorAutoresizeWeight; protected IEntityFigure[] childFigure; protected BaselinedDimension[] childSize; //childSize[i] is null when the child i is not visible private Dimension cachedPreferredHint = new Dimension(-1, -1); private Dimension cachedMinimumHint = new Dimension(-1, -1); private BaselinedDimension preferredSize; private BaselinedDimension minimumSize; protected int figWidth; protected int figAscent; protected int figDescent; protected IViewportTrackingStrategy viewportTrackingStrategy = IViewportTrackingStrategy.IDENTITY; public AbstractEntityLayout withViewportTrackingStrategy(IViewportTrackingStrategy viewportTrackingStrategy) { this.viewportTrackingStrategy = viewportTrackingStrategy; return this; } public IViewportTrackingStrategy getViewportTrackingStrategy() { return viewportTrackingStrategy; } public Object getConstraint(IFigure child) { return null; } public void setConstraint(IFigure child, Object constraint) { invalidate(child); } public BaselinedDimension getMinimumSize(IFigure container, int w, int h) { boolean flush = cachedMinimumHint.width != w && isSensitiveHorizontally(container); flush |= cachedMinimumHint.height != h && isSensitiveVertically(container); if (flush) { if (minimumSize != null && minimumSize.hintsSensitive) minimumSize = null; cachedMinimumHint.width = w; cachedMinimumHint.height = h; } if (minimumSize == null) minimumSize = calculateMinimumSize(container, w, h); return minimumSize; } public BaselinedDimension getPreferredSize(IFigure container, int wHint, int hHint) { boolean flush = cachedPreferredHint.width != wHint && isSensitiveHorizontally(container); flush |= cachedPreferredHint.height != hHint && isSensitiveVertically(container); if (flush) { if (preferredSize != null && preferredSize.hintsSensitive) preferredSize = null; cachedPreferredHint.width = wHint; cachedPreferredHint.height = hHint; } if (preferredSize == null) preferredSize = calculatePreferredSize(container, wHint, hHint); return preferredSize; } protected boolean isSensitiveHorizontally(IFigure container) { return true; } protected boolean isSensitiveVertically(IFigure container) { return true; } public void invalidate() { minimumSize = null; preferredSize = null; // preferredCache.clear(); } protected void invalidate(IFigure child) { invalidate(); } public void remove(IFigure child) { invalidate(); } protected Dimension getBorderPreferredSize(IFigure container) { if (container.getBorder() == null) return new Dimension(); return container.getBorder().getPreferredSize(container); } public IEntityLayout withMargin(int margin) { return withMargin(margin, margin, margin, margin); } public IEntityLayout withMargin(int marginTop, int marginLeft, int marginBottom, int marginRight) { return withMarginTop(marginTop).withMarginLeft(marginLeft).withMarginBottom(marginBottom).withMarginRight(marginRight); } public int getMarginLeft() { return isHorizontal() && (getSpacedChild() == 0 || getSpacedChild() == SPACED_ALL) ? getSpacedSpacing(marginLeft) : marginLeft; } public IEntityLayout withMarginLeft(int marginLeft) { this.marginLeft = marginLeft; return this; } public int getMarginRight() { return isHorizontal() && (getSpacedChild() == childFigure.length-1 || getSpacedChild() == SPACED_ALL) ? getSpacedSpacing(marginRight) : marginRight; } public IEntityLayout withMarginRight(int marginRight) { this.marginRight = marginRight; return this; } public int getMarginTop() { return !isHorizontal() && (getSpacedChild() == 0 || getSpacedChild() == SPACED_ALL) ? getSpacedSpacing(marginTop) : marginTop; } public IEntityLayout withMarginTop(int marginTop) { this.marginTop = marginTop; return this; } public int getMarginBottom() { return !isHorizontal() && (getSpacedChild() == childFigure.length-1 || getSpacedChild() == SPACED_ALL) ? getSpacedSpacing(marginBottom) : marginBottom; } public IEntityLayout withMarginBottom(int marginBottom) { this.marginBottom = marginBottom; return this; } protected BaselinedDimension calculateMinimumSize(IFigure container, int wHint, int hHint) { return getPreferredSize(container, wHint, hHint); // return calculateSize(container, wHint, hHint, false); //FIXME ? } // private SizeKey preferredSizeKey = new SizeKey(null, -1, -1); // private Map<SizeKey, BaselinedDimension> preferredCache = new WeakHashMap<AbstractEntityLayout.SizeKey, BaselinedDimension>(); protected BaselinedDimension calculatePreferredSize(IFigure container, int wHint, int hHint) { //FIXME faster code with minor problems // BaselinedDimension dimension = preferredCache.get(preferredSizeKey.setProperties(container, wHint, hHint)); // if (dimension == null) // preferredCache.put(preferredSizeKey, dimension = calculateSize(container, wHint, hHint, true)); ////TODO result diff //// else if (!dimension.equals(calculateSize(container, wHint, hHint, true))) //// return dimension; // return dimension; //TODO safe code return calculateSize(container, wHint, hHint, true); } protected BaselinedDimension calculateSize(IFigure container, int wHint, int hHint, boolean preferred) { childFigure = getChildren(container); Insets insets = container.getInsets(); if (wHint > -1) wHint = Math.max(0, wHint - insets.getWidth() - getMarginLeft() - getMarginRight()); if (hHint > -1) hHint = Math.max(0, hHint - insets.getHeight() - getMarginTop() - getMarginBottom()); boolean hintsSensitive = calculateChildrenSize(wHint, hHint, preferred); return new BaselinedDimension(figWidth, figAscent + figDescent, getIndent() + insets.left + getMarginLeft(), figAscent + insets.top + getMarginTop(), hintsSensitive) .expand(insets.getWidth(), insets.getHeight()) .union(getBorderPreferredSize(container)) .expand(getMarginLeft()+getMarginRight(), getMarginTop()+getMarginBottom()); } protected boolean calculateChildrenSize(int wHint, int hHint, boolean preferred) { boolean hintsSensitive = false; int size = childFigure.length; childSize = new BaselinedDimension[size]; for (int i=0; i<size; i++) if (childFigure[i].isVisible()) { childSize[i] = getChildSize(childFigure[i], wHint, hHint, preferred); hintsSensitive |= childSize[i].hintsSensitive; } setAscentDescentWidth(wHint, hHint); return hintsSensitive; } protected abstract void setAscentDescentWidth(int wHint, int hHint); protected IEntityFigure[] getChildren(IFigure container) { List<?> children = container.getChildren(); return children.toArray(new IEntityFigure[children.size()]); } protected BaselinedDimension getChildSize(IFigure child, int wHint, int hHint, boolean preferred) { Dimension d = preferred ? child.getPreferredSize(wHint, hHint) : child.getMinimumSize(wHint, hHint); BaselinedDimension bd; if (d instanceof BaselinedDimension) bd = (BaselinedDimension) d; else { bd = new BaselinedDimension(d); if (child instanceof IEntityFigure) { bd.indent = ((IEntityFigure) child).getIndent(); bd.ascent = ((IEntityFigure) child).getAscent(); } else bd.ascent = bd.height/2; } return bd; } protected int getIndent() { return 0; } protected int indent(int i) { return childSize[i].getIndent(); } protected int ascent(int i) { return childSize[i].getAscent(); } protected int descent(int i) { return childSize[i].getDescent(); } public boolean isHorizontal() { return false; } protected boolean isChildVisible(int i) { return childSize[i] != null;// && childFigure[i].isShowing();//FIXME } public void layout(IFigure container) { //FIXME workaround if (childSize == null) calculatePreferredSize(container, -1, -1); Rectangle area = container.getClientArea(); area.x += getMarginLeft() + getViewportTrackingStrategy().getIndent(); area.y += getMarginTop() + getViewportTrackingStrategy().getAscent(); area.width -= (getMarginLeft()+getViewportTrackingStrategy().getIndent()+getMarginRight()); area.height -= (getMarginTop()+getViewportTrackingStrategy().getAscent()+getMarginBottom()); int size = childFigure.length; int[] x = new int[size]; int[] y = new int[size]; setLocation(area, x, y); Rectangle bounds = Rectangle.SINGLETON; for (int i=0; i<childFigure.length; i++) if (isChildVisible(i)) { bounds.setLocation(x[i], y[i]); bounds.setSize(childSize[i].width, childSize[i].height); childFigure[i].setBounds(bounds); } } protected abstract void setLocation(Rectangle area, int[] x, int[] y); public int getIndent(IFigure container) { //FIXME workaround if (preferredSize == null) return 0; // preferredSize = calculatePreferredSize(container, -1, -1); return preferredSize.getIndent(); } public int getAscent(IFigure container) { //FIXME workaround if (preferredSize == null) return figAscent; // preferredSize = calculatePreferredSize(container, -1, -1); return preferredSize.getAscent(); } public Rectangle getBounds(int childIndex) { return childFigure[childIndex].getBounds(); } public int getBaseline(int childIndex) { return getBounds(childIndex).y + ascent(childIndex) -1; //FIXME -1 y[childIndex] } public IEntityLayout withAutoresizeWeight(float weight) { withMajorAutoresizeWeight(weight); withMinorAutoresizeWeight(weight); return this; } public IEntityLayout withMajorAutoresizeWeight(float weight) { majorAutoresizeWeight = weight; return this; } public IEntityLayout withMinorAutoresizeWeight(float weight) { minorAutoresizeWeight = weight; return this; } public float getMajorAutoresizeWeight() { return majorAutoresizeWeight; } public float getMinorAutoresizeWeight() { return minorAutoresizeWeight; } public float getMajorAutoresizeWeight(int childIndex) { if (childFigure[childIndex] instanceof EntityFigure) { EntityFigure childEntityFigure = (EntityFigure) childFigure[childIndex]; return isHorizontal() && childEntityFigure.getLayoutManager().isHorizontal() ? childEntityFigure.getMajorAutoresizeWeight() : childEntityFigure.getMinorAutoresizeWeight(); } return 0f; } public float getMinorAutoresizeWeight(int childIndex) { if (childFigure[childIndex] instanceof EntityFigure) { EntityFigure childEntityFigure = (EntityFigure) childFigure[childIndex]; return isHorizontal() && childEntityFigure.getLayoutManager().isHorizontal() ? childEntityFigure.getMinorAutoresizeWeight() : childEntityFigure.getMajorAutoresizeWeight(); } return 0f; } public boolean isOrdered() { return false; } protected int spacedChild = SPACED_NONE; public int getSpacedChild() { return spacedChild; } public void setSpacedChild(int childIndex) { this.spacedChild = childIndex; } public IEntityLayout withSpacing(int spacing) { return this; } public int getSpacing() { return 0; } public int getSpacingBefore(int childIndex) { return childIndex == getSpacedChild() || childIndex == getSpacedChild()+1 || getSpacedChild() == SPACED_ALL ? getSpacedSpacing(getSpacing()) : getSpacing(); } protected int getSpacedSpacing(int spacing) { return defaultGetSpacedSpacing(spacing); } public static int defaultGetSpacedSpacing(int spacing) { return Math.max(SPACED_SPACING, (int) (spacing));//WAS *1.2)); } public ITabularLayoutServer getTabularLayoutServer() { return this instanceof ITabularLayoutServer ? (ITabularLayoutServer) this : null; } public ITabularLayoutClient getTabularLayoutClient() { return this instanceof ITabularLayoutClient ? (ITabularLayoutClient) this : ITabularLayoutClient.NULL_TABULAR_LAYOUT_CLIENT; } public void setStartingCellIndex(int cellIndex) { } public int getStartingCellIndex() { return 0; } public int getCells() { return 0; } public void invalidateCells() { invalidate(); } public Dimension getPreferredCellSize(int cellIndex, int wHint, int hHint) { int childIndex = cellIndex - getStartingCellIndex(); return childIndex < getCells() && childFigure[childIndex].isVisible() ? childFigure[childIndex].getPreferredSize(wHint, hHint) : IEntityFigure.PLACE_HOLDER_DIMENSION; } public Rectangle getCellBounds(int cellIndex) { int childIndex = cellIndex - getStartingCellIndex(); return childIndex < getCells() && childFigure[childIndex].isVisible() ? childFigure[childIndex].getBounds() : IEntityFigure.PLACE_HOLDER_BOUNDS; } public int getCellSpacingBefore(int cellIndex) { return getPreferredCellSpacingBefore(cellIndex); } public int getPreferredCellSpacingBefore(int cellIndex) { int childIndex = cellIndex - getStartingCellIndex(); int cells = getCells(); if (childIndex > cells) return 0; else if (childIndex == 0) return isHorizontal() ? getMarginLeft() : getMarginTop(); else if (childIndex == cells) return isHorizontal() ? getMarginRight() : getMarginBottom(); else return getSpacingBefore(childIndex); } protected static class SizeKey { protected IFigure container; protected int wHint; protected int hHint; public SizeKey(IFigure container, int wHint, int hHint) { setProperties(container, wHint, hHint); } public SizeKey setProperties(IFigure container, int wHint, int hHint) { this.container = container; this.wHint = wHint; this.hHint = hHint; return this; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((container == null) ? 0 : container.hashCode()); result = prime * result + hHint; result = prime * result + wHint; return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; SizeKey other = (SizeKey) obj; if (container == null) { if (other.container != null) return false; } else if (!container.equals(other.container)) return false; if (hHint != other.hHint) return false; if (wHint != other.wHint) return false; return true; } public IFigure getContainer() { return container; } public void setContainer(IFigure container) { this.container = container; } public int getwHint() { return wHint; } public void setwHint(int wHint) { this.wHint = wHint; } public int gethHint() { return hHint; } public void sethHint(int hHint) { this.hHint = hHint; } } }