/**
* 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.notations.tree.figures;
import java.util.List;
import org.eclipse.draw2d.ActionListener;
import org.eclipse.draw2d.ColorConstants;
import org.eclipse.draw2d.ConnectionAnchor;
import org.eclipse.draw2d.Graphics;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.MarginBorder;
import org.eclipse.draw2d.PositionConstants;
import org.eclipse.draw2d.Toggle;
import org.eclipse.draw2d.geometry.Insets;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.swt.graphics.Color;
import org.whole.lang.reflect.EntityDescriptor;
import org.whole.lang.reflect.FeatureDescriptor;
import org.whole.lang.ui.WholeImages;
import org.whole.lang.ui.editparts.IVisibilityManager;
import org.whole.lang.ui.figures.AnchorFactory;
import org.whole.lang.ui.figures.EntityButton;
import org.whole.lang.ui.figures.EntityFigure;
import org.whole.lang.ui.figures.EntityLabel;
import org.whole.lang.ui.figures.EntityToggle;
import org.whole.lang.ui.figures.FigureConstants;
import org.whole.lang.ui.figures.INodeFigure;
import org.whole.lang.ui.figures.NodeFigure;
import org.whole.lang.ui.layout.Alignment;
import org.whole.lang.ui.layout.ColumnLayout;
import org.whole.lang.ui.layout.ICompositeEntityLayout;
import org.whole.lang.ui.layout.MonoLayout;
import org.whole.lang.ui.layout.RoundedTitleTabLayout;
import org.whole.lang.ui.layout.RowLayout;
import org.whole.lang.ui.layout.ViewportTracking;
import org.whole.lang.ui.notations.figures.DrawUtils;
public class SimpleEntityTreeFigure extends NodeFigure {
protected boolean isRightToLeft;
protected IFigure contents;
protected IFigure features;
protected Toggle mainToggle; // used by createTargetAnchors
protected Toggle[] featureToggles; // used by createSourceAnchors
protected IVisibilityManager visibilityManager;
public SimpleEntityTreeFigure(EntityDescriptor<?> ed, boolean isRightToLeft, IVisibilityManager visibilityManager, ActionListener linkListener) {
this.isRightToLeft = isRightToLeft;
int featureNum = ed.childFeatureSize();
initContentPanes(featureNum);
this.visibilityManager = visibilityManager;
setLayoutManager(new RowLayout().withMinorAlignment(Alignment.CENTER)
.withSpacing(DrawUtils.SPACING + DrawUtils.EDGE_SPACING * featureNum)
.withReversedChildren(isRightToLeft()));
mainToggle = createFoldingToggle(new EntityToggle(WholeImages.ROUND_EXPAND, WholeImages.ROUND_COLLAPSE), featureNum);
IFigure outline = new EntityFigure(new RoundedTitleTabLayout().withRightToLeft(isRightToLeft())) {
@Override
protected void paintFigure(Graphics graphics) {
super.paintFigure(graphics);
Rectangle bounds = getBounds();
Rectangle labelBounds = ((IFigure) getChildren().get(0)).getBounds();
int labelHeight = labelBounds.height;
final int LINE_WIDTH = 1;
Insets shrink = new Insets(0, 4, 0, 4);
Color borderColor = FigureConstants.blueColor;
Rectangle tempRect = new Rectangle();
tempRect.setBounds(bounds);
tempRect.resize(-1, -labelHeight -1);
tempRect.translate(shrink.left, labelHeight + shrink.top);
tempRect.resize(-shrink.left -shrink.right, -shrink.top -shrink.bottom);
graphics.setLineWidth(1);
graphics.setForegroundColor(borderColor);
graphics.setBackgroundColor(ColorConstants.lightGray);
// part of the round border outside the tab
int titleWidth = labelBounds.width;// + labelHeight +1;
Rectangle rect = tempRect.getResized(-titleWidth, 0);
graphics.setClip(rect.translate(titleWidth, 0).expand(LINE_WIDTH, LINE_WIDTH));
graphics.drawRoundRectangle(tempRect, 8, 8);
// part of the round border under the tab
rect = tempRect.getCopy();
rect.width = titleWidth;
rect.height = 8;
graphics.setClip(rect);
graphics.drawRectangle(tempRect);
rect.height = tempRect.height-8;
graphics.setClip(rect.translate(0, 8).expand(LINE_WIDTH, LINE_WIDTH));
graphics.drawRoundRectangle(tempRect, 8, 8);
// draw tab
tempRect.setBounds(bounds);
tempRect.setSize(labelBounds.getSize());
tempRect.resize(1, 0);//labelHeight + shrink.top);
tempRect.translate(shrink.left, 0);
graphics.setForegroundColor(borderColor);
graphics.setBackgroundColor(borderColor);
graphics.setClip(tempRect);
graphics.drawRoundRectangle(tempRect.getResized(-1,+4), 8, 8);
int oldAlpha = graphics.getAlpha();
graphics.setAlpha(60);
graphics.fillRoundRectangle(tempRect.getResized(0,+4), 8, 8);
graphics.setAlpha(oldAlpha);
}
};
EntityFigure outlineHeader = new EntityFigure(new RowLayout().withMargin(0,2,0,7));
outlineHeader.addDeclaration(ed.getName());
outlineHeader.add(createVisibilityToggle(featureNum+1));
outline.add(outlineHeader);
outline.add(mainToggle);
outline.add(features = createFeaturesOutline(ed, linkListener));
contents = new EntityFigure(new ColumnLayout().withSpacing(3)
.withMinorAlignment(isRightToLeft() ? Alignment.TRAILING : Alignment.LEADING)) {
@SuppressWarnings("unchecked")
@Override
public boolean isVisible() {
List<IFigure> children = getChildren();
// (contents figure visible iff all children visible)
boolean hasChildrenVisible = false;
for (IFigure child : children)
hasChildrenVisible |= child.isVisible();
return super.isVisible() && hasChildrenVisible;
}
};
for(int i=0; i<featureNum; i++)
contents.add(createContentPane(i));
EntityFigure trackingFigure = new EntityFigure(new MonoLayout().withMajorAlignment(Alignment.CENTER).withAutoresizeWeight(1f)).withViewportTracking(ViewportTracking.VERTICAL);
trackingFigure.add(outline);
add(trackingFigure);
add(contents);
initVisibilityToggle();
}
private int visibilityTag;
protected int getVisibilityTag() {
return visibilityTag;
}
protected Toggle createVisibilityToggle(int tag) {
return createFoldingToggle(
new EntityToggle(WholeImages.ARROW_COLLAPSE, WholeImages.ARROW_EXPAND), visibilityTag = tag);
}
protected void initVisibilityToggle() {
if (visibilityManager!=null && visibilityManager.isChildrenVisibilityInitiallyEnabled())
clickFoldingToggle(toggleIndexOf(getVisibilityTag()));
}
public void setContentPaneVisible(int paneIndex, boolean visible) {
((IFigure) features.getChildren().get(paneIndex)).setVisible(visible);
if (((IFigure) getContentPaneContainer().getChildren().get(paneIndex)).isVisible() ^ visible)
clickFoldingToggle(toggleIndexOf(paneIndex));
}
protected IFigure getContentPaneContainer() {
return contents;
}
@Override
public List<Toggle> getFoldingToggles(IFigure figure) {
List<Toggle> toggles = super.getFoldingToggles(figure);
toggles.add(getFoldingToggle(0));
int paneIndex = ancestorOrSelfContentPaneIndexOf(figure);
if (paneIndex > 0 && !visibilityManager.isChildVisible(paneIndex))
toggles.add(getFoldingToggle(1));
return toggles;
}
@Override
protected void toggleVisibility(int paneIndex) {
if (paneIndex == getVisibilityTag()) {
int visibleSize = contentPanes.length;
if (getFoldingToggle(1).isSelected())
for (int i=0, size=contentPanes.length; i<size; i++) {
boolean childVisible = visibilityManager.isChildVisible(i);
if (!childVisible)
visibleSize--;
setContentPaneVisible(i, childVisible);
}
else
for (int i=0, size=contentPanes.length; i<size; i++)
setContentPaneVisible(i, true);
((ICompositeEntityLayout) getLayoutManager()).withSpacing(DrawUtils.SPACING + DrawUtils.EDGE_SPACING * visibleSize);
} else if (paneIndex == getContentPanesSize()) {
boolean visible = getFeaturesFigure().isVisible();
getContentsFigure().setVisible(!visible);
getFeaturesFigure().setVisible(!visible);
} else
super.toggleVisibility(paneIndex);
}
protected IFigure createFeaturesOutline(EntityDescriptor<?> ed, ActionListener linkListener) {
int featureNum = ed.childFeatureSize();
IFigure featuresOutline = new EntityFigure(new ColumnLayout()
.withAutoresizeWeight(1.0f).withMarginBottom(2)
.withMinorAlignment(isRightToLeft() ? Alignment.LEADING : Alignment.TRAILING));
featureToggles = new Toggle[featureNum];
for(int i=0; i<featureNum; i++) {
FeatureDescriptor fd = ed.getEntityFeatureDescriptor(i);
EntityFigure feature = new EntityFigure(new RowLayout().withSpacing(3)
.withMajorAlignment(isRightToLeft() ? Alignment.LEADING : Alignment.TRAILING)
.withReversedChildren(isRightToLeft()));
feature.addLabel(fd.getName());
featureToggles[i] = createFoldingToggle(new EntityToggle(WholeImages.ROUND_EXPAND, WholeImages.ROUND_COLLAPSE), i);
feature.add(featureToggles[i]);
featuresOutline.add(feature);
}
for (int i=featureNum, size=ed.featureSize(); i<size; i++) {
FeatureDescriptor fd = ed.getEntityFeatureDescriptor(i);
EntityFigure feature = new EntityFigure(new RowLayout()
.withMarginLeft(3).withMarginRight(3)
.withMajorAlignment(isRightToLeft() ? Alignment.LEADING : Alignment.TRAILING)
.withReversedChildren(isRightToLeft()));
EntityLabel createLabel = feature.addLabel(fd.getName(), WholeImages.LINK);
createLabel.setTextPlacement(isRightToLeft() ? PositionConstants.EAST : PositionConstants.WEST);
createLabel.setBorder(new MarginBorder(3));
feature.add(new EntityButton(createLabel, linkListener, i));
featuresOutline.add(feature);
}
return featuresOutline;
}
public IFigure getContentsFigure() {
return contents;
}
public IFigure getFeaturesFigure() {
return features;
}
@Override
protected ConnectionAnchor[] createSourceAnchors() {
ConnectionAnchor[] connectionAnchors = new ConnectionAnchor[featureToggles.length];
for(int i=0; i<featureToggles.length; i++)
connectionAnchors[i] = AnchorFactory.createFixedAnchor(featureToggles[i],
isRightToLeft() ? 0 : 1.0, 0.5);
return connectionAnchors;
}
@Override
protected ConnectionAnchor[] createTargetAnchors() {
return new ConnectionAnchor[] {
AnchorFactory.createFixedAnchor(mainToggle, isRightToLeft() ? 1.0 : 0, 0.5)
};
}
@Override
public void paintClientArea(Graphics graphics) {
super.paintClientArea(graphics);
paintConnections(graphics);
graphics.restoreState();
}
protected void paintConnections(Graphics graphics) {
if (contents.isVisible()) {
graphics.setForegroundColor(FigureConstants.relationsColor);
int egdeXOffset = DrawUtils.SPACING - DrawUtils.EDGE_SPACING;
ConnectionAnchor[] srcAnchors = getSourceAnchors();
int i;
int prevYSourceLocation = Integer.MAX_VALUE;
for (i = 0; i < srcAnchors.length; i++) {
IFigure contentPane = getContentPane(i);
if (contentPane.isVisible()) {
IFigure targetFigure = (IFigure) contentPane.getChildren().get(0);
Point sourceLocation = srcAnchors[i].getLocation(null);
Point targetLocation = null;
if (targetFigure instanceof INodeFigure)
targetLocation = ((INodeFigure) targetFigure).getTargetAnchor(0).getLocation(null);
else {
targetLocation = isRightToLeft() ? targetFigure.getBounds().getRight() :
targetFigure.getBounds().getLeft();
targetFigure.translateToAbsolute(targetLocation);
}
translateToRelative(targetLocation);
translateToRelative(sourceLocation);
if (targetLocation.y > sourceLocation.y)
break;
if (prevYSourceLocation >= targetLocation.y - 1)
egdeXOffset += DrawUtils.EDGE_SPACING;
prevYSourceLocation = sourceLocation.y;
DrawUtils.drawHorizontalEdge(graphics, sourceLocation, targetLocation, egdeXOffset);
}
}
egdeXOffset = DrawUtils.SPACING - DrawUtils.EDGE_SPACING;
prevYSourceLocation = Integer.MIN_VALUE;
for (int j = srcAnchors.length-1; j >= i; j--) {
IFigure contentPane = getContentPane(j);
if (contentPane.isVisible()) {
IFigure targetFigure = (IFigure) contentPane.getChildren().get(0);
Point sourceLocation = srcAnchors[j].getLocation(null);
Point targetLocation = null;
if (targetFigure instanceof INodeFigure)
targetLocation = ((INodeFigure) targetFigure).getTargetAnchor(0).getLocation(null);
else {
targetLocation = isRightToLeft() ? targetFigure.getBounds().getRight() :
targetFigure.getBounds().getLeft();
targetFigure.translateToAbsolute(targetLocation);
}
translateToRelative(targetLocation);
translateToRelative(sourceLocation);
if (prevYSourceLocation <= targetLocation.y + 1)
egdeXOffset += DrawUtils.EDGE_SPACING;
prevYSourceLocation = sourceLocation.y;
DrawUtils.drawHorizontalEdge(graphics, sourceLocation, targetLocation, egdeXOffset);
}
}
}
}
public boolean isRightToLeft() {
return isRightToLeft;
}
}