mim.canvas initial commit.

This commit is contained in:
bitwave 2013-05-25 16:38:32 +02:00
parent edb21f1319
commit 6af1753c0c
38 changed files with 1623 additions and 0 deletions

4
ch.bitwave.mim.canvas/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
/target
/.settings
/.project
/.classpath

View File

@ -0,0 +1,22 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>ch.bitwave</groupId>
<artifactId>maven.platform.parent</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>mim.canvas</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>ch.bitwave</groupId>
<artifactId>mim.meta</artifactId>
</dependency>
<dependency>
<groupId>ch.bitwave</groupId>
<artifactId>ui.swing</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,62 @@
package ch.bitwave.mim.canvas.builders;
import java.util.List;
import javax.annotation.Nullable;
import ch.bitwave.mim.canvas.features.HorizontalGroupFeature;
import ch.bitwave.mim.canvas.features.HorizontalRankFeature;
import ch.bitwave.mim.canvas.features.NodeFeature;
import ch.bitwave.mim.canvas.generators.TreeNode;
import ch.bitwave.mim.canvas.strategies.FitTextSizingStrategy;
import ch.bitwave.mim.canvas.strategies.HorizontalFannedConnectorLayoutStrategy;
import ch.parametrix.common.util.ui.swing.clap.DelegatedLayoutGoal;
/**
* Renders a tree of nodes in a left-to-right orientation.
*/
public class HorizontalTreeRenderer extends TreeRenderer {
public HorizontalTreeRenderer() {
// In a horizontal tree we size the nodes by their labels by default and
// fan connectors vertically.
setSizingStrategy(new FitTextSizingStrategy());
setConnectorLayoutStrategy(new HorizontalFannedConnectorLayoutStrategy());
}
@Override
public HorizontalGroupFeature renderTree(final List<TreeNode> rootNodes) {
HorizontalGroupFeature root = createNode(null);
for (TreeNode node : rootNodes) {
renderNode(root, node);
}
getLayoutProcessor().addGoal(new DelegatedLayoutGoal(root));
return root;
}
private void renderNode(final HorizontalGroupFeature parentGroup, final TreeNode node) {
NodeFeature nodeFeature = (NodeFeature) getFeatureFactory().createFeatureForElement(
node.getElement());
configureNode(nodeFeature);
addFeature(nodeFeature);
HorizontalGroupFeature detailFeature = createNode(nodeFeature);
parentGroup.addDetailGroup(detailFeature);
renderConnection(parentGroup.getNode(), detailFeature.getNode());
for (TreeNode detailNode : node.getDetails()) {
renderNode(detailFeature, detailNode);
}
}
protected HorizontalGroupFeature createNode(@Nullable final NodeFeature feature) {
Object model = null;
if (feature != null)
model = feature.getElement();
HorizontalGroupFeature group = new HorizontalGroupFeature(model);
HorizontalRankFeature rank = new HorizontalRankFeature(model);
group.setRank(rank);
group.setNode(feature);
addFeature(group);
addFeature(rank);
return group;
}
}

View File

@ -0,0 +1,105 @@
package ch.bitwave.mim.canvas.builders;
import java.util.List;
import ch.bitwave.mim.canvas.features.ClassFeature;
import ch.bitwave.mim.canvas.features.ConnectionFeature;
import ch.bitwave.mim.canvas.features.GroupFeature;
import ch.bitwave.mim.canvas.features.NodeFeature;
import ch.bitwave.mim.canvas.features.PackageFeature;
import ch.bitwave.mim.canvas.generators.TreeNode;
import ch.bitwave.mim.canvas.strategies.ColoringStrategy;
import ch.bitwave.mim.canvas.strategies.ConnectorLayoutStrategy;
import ch.bitwave.mim.canvas.strategies.SizingStrategy;
import ch.bitwave.mim.m2.core.Generalization;
import ch.bitwave.mim.m2.core.MimClass;
import ch.bitwave.mim.m2.core.MimPackage;
import ch.bitwave.mim.m2.core.StubClass;
import ch.bitwave.mim.m2.core.StubPackage;
import ch.parametrix.common.util.ui.swing.canvas.FeatureFactory;
import ch.parametrix.common.util.ui.swing.clap.LayoutProcessor;
import ch.parametrix.common.util.ui.swing.contracts.Feature;
import ch.parametrix.common.util.ui.swing.contracts.ICanvasFeatureManager;
public abstract class TreeRenderer {
private ICanvasFeatureManager featureManager;
private FeatureFactory featureFactory;
private ColoringStrategy coloringStrategy;
private SizingStrategy sizingStrategy;
private ConnectorLayoutStrategy connectorLayoutStrategy;
private LayoutProcessor clap;
public TreeRenderer() {
this.featureFactory = new FeatureFactory();
configureFeatureFactory(this.featureFactory);
}
public LayoutProcessor getLayoutProcessor() {
return this.clap;
}
public ConnectorLayoutStrategy getConnectorLayoutStrategy() {
return this.connectorLayoutStrategy;
}
public void setConnectorLayoutStrategy(final ConnectorLayoutStrategy connectorLayoutStrategy) {
this.connectorLayoutStrategy = connectorLayoutStrategy;
}
public ColoringStrategy getColoringStrategy() {
return this.coloringStrategy;
}
public void setColoringStrategy(final ColoringStrategy coloringStrategy) {
this.coloringStrategy = coloringStrategy;
}
public SizingStrategy getSizingStrategy() {
return this.sizingStrategy;
}
public void setSizingStrategy(final SizingStrategy sizingStrategy) {
this.sizingStrategy = sizingStrategy;
}
public FeatureFactory getFeatureFactory() {
return this.featureFactory;
}
protected void configureFeatureFactory(final FeatureFactory factory) {
factory.put(StubPackage.class, PackageFeature.class);
factory.put(MimPackage.class, PackageFeature.class);
factory.put(Generalization.class, ConnectionFeature.class);
factory.put(StubClass.class, ClassFeature.class);
factory.put(MimClass.class, ClassFeature.class);
}
public void configure(final ICanvasFeatureManager featureManager, final LayoutProcessor clap) {
this.featureManager = featureManager;
this.clap = clap;
}
protected void addFeature(final Feature feature) {
this.featureManager.addFeature(feature);
}
public abstract GroupFeature renderTree(final List<TreeNode> rootNodes);
protected void configureNode(final NodeFeature nodeFeature) {
nodeFeature.setConnectorLayoutStrategy(this.getConnectorLayoutStrategy());
nodeFeature.setColoringStrategy(this.getColoringStrategy());
nodeFeature.setSizingStrategy(this.getSizingStrategy());
}
protected void renderConnection(final NodeFeature master, final NodeFeature detail) {
if (master != null) {
ConnectionFeature con = new ConnectionFeature(master.getElement());
master.addOutboundConnector(con.getSourceFeature());
detail.addInboundConnector(con.getTargetFeature());
addFeature(con.getSourceFeature());
addFeature(con.getTargetFeature());
addFeature(con);
}
}
}

View File

@ -0,0 +1,60 @@
package ch.bitwave.mim.canvas.builders;
import java.util.List;
import javax.annotation.Nullable;
import ch.bitwave.mim.canvas.features.GroupFeature;
import ch.bitwave.mim.canvas.features.NodeFeature;
import ch.bitwave.mim.canvas.features.VerticalGroupFeature;
import ch.bitwave.mim.canvas.features.VerticalRankFeature;
import ch.bitwave.mim.canvas.generators.TreeNode;
import ch.bitwave.mim.canvas.strategies.VerticalFannedConnectorLayoutStrategy;
import ch.parametrix.common.util.ui.swing.clap.DelegatedLayoutGoal;
/**
* Renders a tree of nodes in a top-to-bottom orientation.
*/
public class VerticalTreeRenderer extends TreeRenderer {
public VerticalTreeRenderer() {
setConnectorLayoutStrategy(new VerticalFannedConnectorLayoutStrategy());
}
@Override
public GroupFeature renderTree(final List<TreeNode> rootNodes) {
VerticalGroupFeature root = createNode(null);
for (TreeNode node : rootNodes) {
renderNode(root, node);
}
getLayoutProcessor().addGoal(new DelegatedLayoutGoal(root));
return root;
}
private void renderNode(final VerticalGroupFeature parentGroup, final TreeNode node) {
NodeFeature nodeFeature = (NodeFeature) getFeatureFactory().createFeatureForElement(
node.getElement());
configureNode(nodeFeature);
addFeature(nodeFeature);
VerticalGroupFeature detailFeature = createNode(nodeFeature);
parentGroup.addDetailGroup(detailFeature);
renderConnection(parentGroup.getNode(), detailFeature.getNode());
for (TreeNode detailNode : node.getDetails()) {
renderNode(detailFeature, detailNode);
}
}
protected VerticalGroupFeature createNode(@Nullable final NodeFeature feature) {
Object model = null;
if (feature != null)
model = feature.getElement();
VerticalGroupFeature group = new VerticalGroupFeature(model);
VerticalRankFeature rank = new VerticalRankFeature(model);
group.setRank(rank);
group.setNode(feature);
addFeature(group);
addFeature(rank);
return group;
}
}

View File

@ -0,0 +1,63 @@
package ch.bitwave.mim.canvas.features;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import ch.bitwave.mim.m2.core.MimClass;
import ch.bitwave.mim.m2.core.MimElement;
import ch.bitwave.mim.m2.core.NamedElement;
import ch.parametrix.common.util.ui.swing.TextRenderer;
import ch.parametrix.common.util.ui.swing.contracts.ICanvasFeatureProvider;
import ch.parametrix.common.util.ui.swing.contracts.WorldDistance;
public class ClassFeature extends NodeFeature {
public ClassFeature(final Object element) {
super(element);
}
@Override
public String getIdentifier() {
return null;
}
@Override
protected WorldDistance getInitialSize() {
return new WorldDistance(1000, 600);
}
@Override
protected void renderNode(final ICanvasFeatureProvider provider, final Graphics2D g,
final int ax, final int ay, final Color featureColor, final boolean isSelected) {
MimElement elem = (MimElement) getElement();
int w = getWidth();
int h = getHeight();
g.setColor(featureColor);
g.fillRect(ax, ay, w, h);
TextRenderer tr = new TextRenderer(g, ax + 50, ay + 150, w - 100, false);
renderLabel(elem, g, tr);
g.setColor(Color.BLACK);
g.setStroke(NODE_OUTLINE_STROKE);
g.drawRect(ax, ay, w, h);
setBounds(ax, ay, w, h);
}
private void renderLabel(final MimElement elem, final Graphics2D g, final TextRenderer tr) {
if (elem instanceof NamedElement) {
g.setColor(Color.BLACK);
if (isAbstract(elem)) {
tr.setFontStyle(Font.BOLD | Font.ITALIC);
} else {
tr.setFontStyle(Font.BOLD);
}
String name = ((NamedElement) elem).getName();
tr.renderTextCentered(name);
}
}
private boolean isAbstract(final MimElement elem) {
return elem instanceof MimClass && ((MimClass) elem).isAbstract();
}
}

View File

@ -0,0 +1,56 @@
package ch.bitwave.mim.canvas.features;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Stroke;
import java.awt.geom.Rectangle2D;
import ch.parametrix.common.util.ui.swing.contracts.Feature;
import ch.parametrix.common.util.ui.swing.contracts.ICanvasFeatureProvider;
import ch.parametrix.common.util.ui.swing.contracts.WorldDistance;
public class ConnectionFeature extends Feature {
private ConnectionNodeFeature sourceFeature;
private ConnectionNodeFeature targetFeature;
protected static final Stroke CONNECTION_STROKE = new BasicStroke(2.5f);
public ConnectionFeature(final Object element) {
super(element);
this.sourceFeature = new ConnectionNodeFeature(element);
this.targetFeature = new ConnectionNodeFeature(element);
}
public ConnectionNodeFeature getSourceFeature() {
return this.sourceFeature;
}
public ConnectionNodeFeature getTargetFeature() {
return this.targetFeature;
}
@Override
public String getIdentifier() {
return null;
}
@Override
protected WorldDistance getInitialSize() {
return new WorldDistance(100, 100);
}
@Override
protected void render(final ICanvasFeatureProvider provider, final Graphics2D g, final int ax,
final int ay, final Color featureColor, final boolean isSelected) {
Rectangle2D bounds1 = this.sourceFeature.getBounds();
int x1 = (int) bounds1.getCenterX();
int y1 = (int) bounds1.getCenterY();
Rectangle2D bounds2 = this.targetFeature.getBounds();
int x2 = (int) bounds2.getCenterX();
int y2 = (int) bounds2.getCenterY();
g.setStroke(CONNECTION_STROKE);
g.drawLine(x1, y1, x2, y2);
}
}

View File

@ -0,0 +1,33 @@
package ch.bitwave.mim.canvas.features;
import java.awt.Color;
import java.awt.Graphics2D;
import ch.parametrix.common.util.ui.swing.contracts.Feature;
import ch.parametrix.common.util.ui.swing.contracts.ICanvasFeatureProvider;
import ch.parametrix.common.util.ui.swing.contracts.WorldDistance;
public class ConnectionNodeFeature extends Feature {
public ConnectionNodeFeature(final Object element) {
super(element);
}
@Override
public String getIdentifier() {
return null;
}
@Override
protected WorldDistance getInitialSize() {
return new WorldDistance(0, 0);
}
@Override
protected void render(final ICanvasFeatureProvider provider, final Graphics2D g, final int ax,
final int ay, final Color featureColor, final boolean isSelected) {
// Connector nodes are not visible themselves.
setBounds(ax, ay, getWidth(), getHeight());
}
}

View File

@ -0,0 +1,72 @@
package ch.bitwave.mim.canvas.features;
import java.awt.Color;
import java.awt.Graphics2D;
import ch.parametrix.common.util.ui.swing.contracts.Feature;
import ch.parametrix.common.util.ui.swing.contracts.ICanvasFeatureProvider;
import ch.parametrix.common.util.ui.swing.contracts.ILayoutCapable;
import ch.parametrix.common.util.ui.swing.contracts.WorldDistance;
public abstract class GroupFeature extends Feature implements ILayoutCapable {
private NodeFeature node;
public GroupFeature(final Object element) {
super(element);
}
@Override
public String getIdentifier() {
return null;
}
@Override
protected void render(final ICanvasFeatureProvider provider, final Graphics2D g, final int ax,
final int ay, final Color featureColor, final boolean isSelected) {
setBounds(ax, ay, getWidth(), getHeight());
}
public void setNode(final NodeFeature node) {
this.node = node;
if (node != null) {
this.node.setParent(this);
}
}
public NodeFeature getNode() {
return this.node;
}
@Override
public boolean layout(final Graphics2D g) {
resize(g);
reposition();
return false;
}
public abstract void resize(final Graphics2D g);
public abstract void reposition();
protected WorldDistance getNodeSize() {
WorldDistance nodeSize = null;
if (this.node != null) {
nodeSize = this.node.getSize();
} else {
nodeSize = new WorldDistance(0, 0);
}
return nodeSize;
}
@Override
protected WorldDistance getInitialSize() {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean isSelectable() {
return false;
}
}

View File

@ -0,0 +1,68 @@
package ch.bitwave.mim.canvas.features;
import java.awt.Graphics2D;
import ch.parametrix.common.util.ui.swing.contracts.WorldDistance;
/**
* Centers a node at the left of a single vertical rank of details where the
* details flow from the top to the bottom.
*/
public class HorizontalGroupFeature extends GroupFeature {
private final static double RANK_TO_NODE_SPACING = 500;
private HorizontalRankFeature rank;
public HorizontalGroupFeature(final Object element) {
super(element);
}
@Override
protected WorldDistance getInitialSize() {
return new WorldDistance(1000, 3000);
}
public void setRank(final HorizontalRankFeature rank) {
this.rank = rank;
}
@Override
public void reposition() {
WorldDistance nodeSize = getNodeSize();
double y = this.getTop();
double x = this.getLeft();
NodeFeature node = getNode();
if (node != null) {
// The node is centered vertically within the group
double centerY = y + (this.getSize().getDy() - nodeSize.getDy()) / 2;
node.place(x, centerY);
node.reposition();
this.rank.placeX(node.getRight() + RANK_TO_NODE_SPACING);
} else {
// If there is no feature, the rank just hugs the right of the
// group.
this.rank.placeX(x);
}
this.rank.placeY(y);
this.rank.reposition();
}
@Override
public void resize(final Graphics2D g) {
NodeFeature node = getNode();
if (node != null) {
node.resize(g);
}
WorldDistance nodeSize = getNodeSize();
this.rank.resize(g);
double w = this.rank.getWidth();
double h = Math.max(nodeSize.getDy(), this.rank.getSize().getDy());
if (node != null) {
w += nodeSize.getDx() + RANK_TO_NODE_SPACING;
}
setSize(w, h);
}
public void addDetailGroup(final HorizontalGroupFeature detail) {
this.rank.addDetailGroup(detail);
}
}

View File

@ -0,0 +1,79 @@
package ch.bitwave.mim.canvas.features;
import java.awt.Color;
import java.awt.Graphics2D;
import java.util.ArrayList;
import java.util.List;
import ch.parametrix.common.util.ui.swing.contracts.Feature;
import ch.parametrix.common.util.ui.swing.contracts.ICanvasFeatureProvider;
import ch.parametrix.common.util.ui.swing.contracts.WorldDistance;
public class HorizontalRankFeature extends Feature {
private final double GROUP_SPACING = 100;
private List<HorizontalGroupFeature> detailGroups;
public HorizontalRankFeature(final Object element) {
super(element);
this.detailGroups = new ArrayList<HorizontalGroupFeature>();
}
@Override
public String getIdentifier() {
return null;
}
@Override
protected WorldDistance getInitialSize() {
return new WorldDistance(1000, 100);
}
@Override
protected void render(final ICanvasFeatureProvider provider, final Graphics2D g, final int ax,
final int ay, final Color featureColor, final boolean isSelected) {
setBounds(ax, ay, getWidth(), getHeight());
}
public void reposition() {
double x = getLeft();
double y = getTop();
for (int i = 0; i < this.detailGroups.size(); i++) {
if (i > 0) {
y += this.GROUP_SPACING;
}
HorizontalGroupFeature detail = this.detailGroups.get(i);
detail.place(x, y);
detail.reposition();
y += detail.getSize().getDy();
}
}
public void resize(final Graphics2D g) {
double h = 0;
double w = 0;
for (int i = 0; i < this.detailGroups.size(); i++) {
if (i > 0) {
h += this.GROUP_SPACING;
}
HorizontalGroupFeature detail = this.detailGroups.get(i);
detail.resize(g);
h += detail.getSize().getDy();
double detailW = detail.getSize().getDx();
if (w < detailW) {
w = detailW;
}
}
setSize(w, h);
}
public void addDetailGroup(final HorizontalGroupFeature detail) {
this.detailGroups.add(detail);
}
@Override
public boolean isSelectable() {
return false;
}
}

View File

@ -0,0 +1,95 @@
package ch.bitwave.mim.canvas.features;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Stroke;
import java.util.ArrayList;
import java.util.List;
import ch.bitwave.mim.canvas.strategies.ColoringStrategy;
import ch.bitwave.mim.canvas.strategies.ConnectorLayoutStrategy;
import ch.bitwave.mim.canvas.strategies.SizingStrategy;
import ch.parametrix.common.util.ui.swing.contracts.Feature;
import ch.parametrix.common.util.ui.swing.contracts.ICanvasFeatureProvider;
import ch.parametrix.common.util.ui.swing.contracts.WorldDistance;
/**
* A feature for representing a node within a structure, e.g. a tree.
*/
public abstract class NodeFeature extends Feature {
private SizingStrategy sizingStrategy;
private ColoringStrategy coloringStrategy;
private ConnectorLayoutStrategy connectorLayoutStrategy;
private List<ConnectionNodeFeature> outboundConnectors;
private List<ConnectionNodeFeature> inboundConnectors;
protected static final Stroke NODE_OUTLINE_STROKE = new BasicStroke(5f);
public NodeFeature(final Object element) {
super(element);
this.outboundConnectors = new ArrayList<ConnectionNodeFeature>();
this.inboundConnectors = new ArrayList<ConnectionNodeFeature>();
}
public ConnectorLayoutStrategy getConnectorLayoutStrategy() {
return this.connectorLayoutStrategy;
}
public void setConnectorLayoutStrategy(final ConnectorLayoutStrategy connectorLayoutStrategy) {
this.connectorLayoutStrategy = connectorLayoutStrategy;
}
public SizingStrategy getSizingStrategy() {
return this.sizingStrategy;
}
public void setSizingStrategy(final SizingStrategy sizingStrategy) {
this.sizingStrategy = sizingStrategy;
}
public ColoringStrategy getColoringStrategy() {
return this.coloringStrategy;
}
public void setColoringStrategy(final ColoringStrategy coloringStrategy) {
this.coloringStrategy = coloringStrategy;
}
@Override
protected final void render(final ICanvasFeatureProvider provider, final Graphics2D g,
final int ax, final int ay, final Color featureColor, final boolean isSelected) {
Color color = featureColor;
if (this.coloringStrategy != null) {
color = this.coloringStrategy.getColor(getElement());
}
renderNode(provider, g, ax, ay, color, isSelected);
}
protected abstract void renderNode(ICanvasFeatureProvider provider, Graphics2D g, int ax,
int ay, Color color, boolean isSelected);
public void reposition() {
if (this.connectorLayoutStrategy != null) {
this.connectorLayoutStrategy.layoutConnectorNodes(this, this.outboundConnectors,
this.inboundConnectors);
}
}
public void resize(final Graphics2D g) {
if (this.sizingStrategy != null) {
Object element = getElement();
WorldDistance size = this.sizingStrategy.getSize(g, element);
setSize(size);
}
}
public void addOutboundConnector(final ConnectionNodeFeature out) {
this.outboundConnectors.add(out);
}
public void addInboundConnector(final ConnectionNodeFeature out) {
this.inboundConnectors.add(out);
}
}

View File

@ -0,0 +1,60 @@
package ch.bitwave.mim.canvas.features;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import ch.bitwave.mim.m2.core.MimElement;
import ch.bitwave.mim.m2.core.NamedElement;
import ch.parametrix.common.util.ui.swing.TextRenderer;
import ch.parametrix.common.util.ui.swing.contracts.ICanvasFeatureProvider;
import ch.parametrix.common.util.ui.swing.contracts.WorldDistance;
public class PackageFeature extends NodeFeature {
private static final Color STUB_BG = new Color(1.0f, 0.8f, 0.8f);
private static final Color PACKAGE_BG = new Color(0.8f, 0.85f, 1.0f);
public PackageFeature(final Object element) {
super(element);
}
@Override
public String getIdentifier() {
return null;
}
@Override
protected WorldDistance getInitialSize() {
return new WorldDistance(1000, 600);
}
private void renderLabel(final MimElement elem, final Graphics2D g, final TextRenderer tr) {
if (elem instanceof NamedElement) {
g.setColor(Color.BLACK);
tr.setFontStyle(Font.BOLD);
String name = ((NamedElement) elem).getName();
tr.renderText(name);
}
}
@Override
protected void renderNode(final ICanvasFeatureProvider provider, final Graphics2D g,
final int ax, final int ay, final Color color, final boolean isSelected) {
MimElement elem = (MimElement) getElement();
int w = getWidth();
int h = getHeight();
if (elem.isStub()) {
g.setColor(STUB_BG);
} else {
g.setColor(PACKAGE_BG);
}
g.fillRect(ax, ay, w, h);
TextRenderer tr = new TextRenderer(g, ax + 50, ay + 200, w - 100, false);
renderLabel(elem, g, tr);
g.setColor(Color.BLACK);
g.drawRect(ax, ay, w, h);
setBounds(ax, ay, w, h);
}
}

View File

@ -0,0 +1,67 @@
package ch.bitwave.mim.canvas.features;
import java.awt.Graphics2D;
import ch.parametrix.common.util.ui.swing.contracts.WorldDistance;
/**
* Centers a node at the top of a single horizontal rank of details where the
* details flow from the left to the right.
*/
public class VerticalGroupFeature extends GroupFeature {
private final static double RANK_TO_NODE_SPACING = 200;
private VerticalRankFeature rank;
public VerticalGroupFeature(final Object element) {
super(element);
}
@Override
protected WorldDistance getInitialSize() {
return new WorldDistance(3000, 1000);
}
public void setRank(final VerticalRankFeature rank) {
this.rank = rank;
}
@Override
public void reposition() {
WorldDistance nodeSize = getNodeSize();
double y = this.getTop();
double x = this.getLeft();
NodeFeature node = getNode();
if (node != null) {
// The node is offset vertically within the group
double centerX = x + (this.getSize().getDx() - nodeSize.getDx()) / 2;
node.place(centerX, y);
node.reposition();
this.rank.placeY(y + node.getBottom() + RANK_TO_NODE_SPACING);
} else {
// If there is no feature, the rank just hugs the top of the group.
this.rank.placeY(y);
}
this.rank.placeX(x);
this.rank.reposition();
}
@Override
public void resize(final Graphics2D g) {
NodeFeature node = getNode();
if (node != null) {
node.resize(g);
}
WorldDistance nodeSize = getNodeSize();
this.rank.resize(g);
double w = Math.max(nodeSize.getDx(), this.rank.getSize().getDx());
double h = this.rank.getHeight();
if (getNode() != null) {
h += nodeSize.getDy() + RANK_TO_NODE_SPACING;
}
setSize(w, h);
}
public void addDetailGroup(final VerticalGroupFeature detail) {
this.rank.addDetailGroup(detail);
}
}

View File

@ -0,0 +1,79 @@
package ch.bitwave.mim.canvas.features;
import java.awt.Color;
import java.awt.Graphics2D;
import java.util.ArrayList;
import java.util.List;
import ch.parametrix.common.util.ui.swing.contracts.Feature;
import ch.parametrix.common.util.ui.swing.contracts.ICanvasFeatureProvider;
import ch.parametrix.common.util.ui.swing.contracts.WorldDistance;
public class VerticalRankFeature extends Feature {
private final double GROUP_SPACING = 200;
private List<VerticalGroupFeature> detailGroups;
public VerticalRankFeature(final Object element) {
super(element);
this.detailGroups = new ArrayList<VerticalGroupFeature>();
}
@Override
public String getIdentifier() {
return null;
}
@Override
protected WorldDistance getInitialSize() {
return new WorldDistance(1000, 100);
}
@Override
protected void render(final ICanvasFeatureProvider provider, final Graphics2D g, final int ax,
final int ay, final Color featureColor, final boolean isSelected) {
setBounds(ax, ay, getWidth(), getHeight());
}
public void reposition() {
double x = getLeft();
double y = getTop();
for (int i = 0; i < this.detailGroups.size(); i++) {
if (i > 0) {
x += this.GROUP_SPACING;
}
VerticalGroupFeature detail = this.detailGroups.get(i);
detail.place(x, y);
detail.reposition();
x += detail.getSize().getDx();
}
}
public void resize(final Graphics2D g) {
double h = 0;
double w = 0;
for (int i = 0; i < this.detailGroups.size(); i++) {
if (i > 0) {
w += this.GROUP_SPACING;
}
VerticalGroupFeature detail = this.detailGroups.get(i);
detail.resize(g);
w += detail.getSize().getDx();
double detailH = detail.getSize().getDy();
if (h < detailH) {
h = detailH;
}
}
setSize(w, h);
}
public void addDetailGroup(final VerticalGroupFeature detail) {
this.detailGroups.add(detail);
}
@Override
public boolean isSelectable() {
return false;
}
}

View File

@ -0,0 +1,56 @@
package ch.bitwave.mim.canvas.generators;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import javax.annotation.Nonnull;
import ch.bitwave.mim.m2.core.Classifier;
import ch.bitwave.mim.m2.core.Generalization;
import ch.bitwave.mim.m2.core.MimClass;
import ch.bitwave.mim.m2.core.MimPackage;
public class ClassDependencyTreeGenerator extends TreeGenerator {
private ElementFilter rootFilter;
public ClassDependencyTreeGenerator(@Nonnull final ElementFilter rootFilter) {
super();
this.rootFilter = rootFilter;
}
@Override
public List<TreeNode> build(final MimPackage root) {
List<TreeNode> result = new ArrayList<TreeNode>();
Set<MimClass> elementSet = root.getAllOwnedClasses();
List<MimClass> treeRoot = getRootElements(elementSet, this.rootFilter);
for (MimClass mimClass : treeRoot) {
TreeNode rootNode = new TreeNode(mimClass);
result.add(rootNode);
buildNode(rootNode, mimClass);
}
return result;
}
private void buildNode(final TreeNode node, final Classifier generalClass) {
List<Generalization> specializations = generalClass.getSpecializations();
for (Generalization gen : specializations) {
Classifier specific = gen.getSpecific();
TreeNode detailNode = new TreeNode(gen, specific);
node.addDetail(detailNode);
buildNode(detailNode, specific);
}
}
private List<MimClass> getRootElements(final Set<MimClass> elementSet,
final ElementFilter rootFilter) {
List<MimClass> result = new ArrayList<MimClass>();
for (MimClass mimClass : elementSet) {
if (rootFilter.accepts(mimClass)) {
result.add(mimClass);
}
}
return result;
}
}

View File

@ -0,0 +1,28 @@
package ch.bitwave.mim.canvas.generators;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import ch.bitwave.mim.m2.core.MimElement;
import ch.bitwave.mim.m2.core.NamedElement;
public class ElementByNameFilter extends ElementFilter {
private Pattern pattern;
private Matcher matcher;
public ElementByNameFilter(final String selectionPattern, final boolean caseSensitive) {
int flags = caseSensitive ? 0 : Pattern.CASE_INSENSITIVE;
this.pattern = Pattern.compile(selectionPattern, flags);
this.matcher = this.pattern.matcher("");
}
@Override
public boolean accepts(final MimElement element) {
if (!(element instanceof NamedElement)) {
return false;
}
NamedElement ne = (NamedElement) element;
this.matcher.reset(ne.getName());
return this.matcher.matches();
}
}

View File

@ -0,0 +1,9 @@
package ch.bitwave.mim.canvas.generators;
import ch.bitwave.mim.m2.core.MimElement;
public abstract class ElementFilter {
public abstract boolean accepts(final MimElement element);
}

View File

@ -0,0 +1,14 @@
package ch.bitwave.mim.canvas.generators;
import ch.bitwave.mim.m2.core.MimElement;
public class ElementPassthroughFilter extends ElementFilter {
public ElementPassthroughFilter() {
}
@Override
public boolean accepts(final MimElement element) {
return true;
}
}

View File

@ -0,0 +1,42 @@
package ch.bitwave.mim.canvas.generators;
import java.util.ArrayList;
import java.util.List;
import ch.bitwave.mim.m2.core.MimPackage;
import ch.bitwave.mim.m2.core.Namespace;
import ch.bitwave.mim.m2.core.PackageImport;
public class PackageDependencyTreeGenerator extends TreeGenerator {
private boolean hideStubs;
public PackageDependencyTreeGenerator(final boolean hideStubs) {
super();
this.hideStubs = hideStubs;
}
@Override
public List<TreeNode> build(final MimPackage root) {
List<MimPackage> elementSet = root.getAllOwnedPackages();
List<TreeNode> result = new ArrayList<TreeNode>();
for (MimPackage mimPackage : elementSet) {
result.add(buildNode(mimPackage));
}
return result;
}
private TreeNode buildNode(final Namespace pack) {
List<PackageImport> importers = pack.getImportingPackages();
TreeNode master = new TreeNode(pack);
for (PackageImport imp : importers) {
Namespace importing = imp.getImportingNamespace();
if (!(this.hideStubs && importing.isStub())) {
TreeNode detail = buildNode(importing);
master.addDetail(detail);
}
}
return master;
}
}

View File

@ -0,0 +1,11 @@
package ch.bitwave.mim.canvas.generators;
import java.util.List;
import ch.bitwave.mim.m2.core.MimPackage;
public abstract class TreeGenerator {
public abstract List<TreeNode> build(MimPackage root);
}

View File

@ -0,0 +1,94 @@
package ch.bitwave.mim.canvas.generators;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import javax.annotation.Nonnull;
import ch.bitwave.mim.m2.core.MimElement;
import ch.bitwave.mim.m2.core.NamedElement;
import ch.bitwave.mim.m2.core.Relationship;
public class TreeNode {
private Relationship masterRelationship;
private MimElement element;
private List<TreeNode> details;
private TreeNode parent;
public TreeNode(final MimElement element) {
this.element = element;
this.details = new ArrayList<TreeNode>();
}
public TreeNode(final Relationship masterRelationship, final MimElement element) {
this(element);
this.masterRelationship = masterRelationship;
}
public MimElement getElement() {
return this.element;
}
public void setElement(final MimElement element) {
this.element = element;
}
public Relationship getMasterRelationship() {
return this.masterRelationship;
}
public void setMasterRelationship(final Relationship masterRelationship) {
this.masterRelationship = masterRelationship;
}
public List<TreeNode> getDetails() {
return this.details;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("TreeNode [masterRelationship=").append(this.masterRelationship)
.append(", element=").append(this.element).append(", details=")
.append(this.details).append("]");
return builder.toString();
}
public void addDetail(@Nonnull final TreeNode detail) {
this.details.add(detail);
detail.setParent(this);
}
protected void setParent(final TreeNode treeNode) {
this.parent = treeNode;
}
public TreeNode getParent() {
return this.parent;
}
public boolean hasParent() {
return this.parent != null;
}
public boolean isRoot() {
return this.parent == null;
}
public void sortRecursively(final Comparator<TreeNode> comparator) {
Collections.sort(this.details, comparator);
for (TreeNode detail : this.details) {
detail.sortRecursively(comparator);
}
}
public String getLabel() {
if (this.element instanceof NamedElement) {
return ((NamedElement) this.element).getName();
}
return "";
}
}

View File

@ -0,0 +1,13 @@
package ch.bitwave.mim.canvas.generators;
import java.text.Collator;
import java.util.Comparator;
public class TreeNodeAlphabetizer implements Comparator<TreeNode> {
@Override
public int compare(final TreeNode arg0, final TreeNode arg1) {
return Collator.getInstance().compare(arg0.getLabel(), arg1.getLabel());
}
}

View File

@ -0,0 +1,30 @@
package ch.bitwave.mim.canvas.strategies;
import java.awt.Color;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import ch.bitwave.mim.m2.core.MimClass;
/**
* Draws classes introducing features matching a given pattern in a different
* color.
*/
public class ColoringByFeatureIntroductionStrategy extends ColoringByPatternStrategy {
public ColoringByFeatureIntroductionStrategy(final Color matchColor, final Pattern namePattern) {
super(matchColor, namePattern);
}
@Override
protected boolean isMatchingElement(final Matcher matcher, final Object element) {
if (element instanceof MimClass) {
MimClass mc = (MimClass) element;
if (mc.hasFeatureMatching(matcher)) {
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,29 @@
package ch.bitwave.mim.canvas.strategies;
import java.awt.Color;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import ch.bitwave.mim.m2.core.MimClass;
/**
* Draws classes with features matching a given pattern in a different color.
*/
public class ColoringByFeaturePresenceStrategy extends ColoringByPatternStrategy {
public ColoringByFeaturePresenceStrategy(final Color matchColor, final Pattern namePattern) {
super(matchColor, namePattern);
}
@Override
protected boolean isMatchingElement(final Matcher matcher, final Object element) {
if (element instanceof MimClass) {
MimClass mc = (MimClass) element;
if (mc.introducesFeatureMatching(matcher)) {
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,31 @@
package ch.bitwave.mim.canvas.strategies;
import java.awt.Color;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import ch.bitwave.mim.m2.core.MimClass;
/**
* Colors a node in a different background color if its label matches the name
* pattern.
*/
public class ColoringByNameStrategy extends ColoringByPatternStrategy {
public ColoringByNameStrategy(final Color matchColor, final Pattern namePattern) {
super(matchColor, namePattern);
}
@Override
protected boolean isMatchingElement(final Matcher matcher, final Object element) {
if (element instanceof MimClass) {
MimClass mc = (MimClass) element;
matcher.reset(mc.getName());
if (matcher.matches()) {
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,42 @@
package ch.bitwave.mim.canvas.strategies;
import java.awt.Color;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public abstract class ColoringByPatternStrategy extends ColoringStrategy {
private Color matchColor;
private Matcher memberNameMatcher;
public ColoringByPatternStrategy(final Color matchColor, final Pattern namePattern) {
this.matchColor = matchColor;
this.memberNameMatcher = namePattern.matcher("");
}
public Color getMatchColor() {
return this.matchColor;
}
public void setMatchColor(final Color matchColor) {
this.matchColor = matchColor;
}
public Matcher getMemberNameMatcher() {
return this.memberNameMatcher;
}
public void setMemberNameMatcher(final Matcher memberNameMatcher) {
this.memberNameMatcher = memberNameMatcher;
}
@Override
public final Color getColor(final Object element) {
if (isMatchingElement(this.memberNameMatcher, element)) {
return this.matchColor;
}
return Color.WHITE;
}
protected abstract boolean isMatchingElement(final Matcher matcher, final Object element);
}

View File

@ -0,0 +1,7 @@
package ch.bitwave.mim.canvas.strategies;
import java.awt.Color;
public abstract class ColoringStrategy {
public abstract Color getColor(Object element);
}

View File

@ -0,0 +1,24 @@
package ch.bitwave.mim.canvas.strategies;
import java.awt.Color;
import ch.bitwave.mim.m2.core.MimElement;
/**
* Draws stubs in light red, regular elements in white instead of the regular
* color.
*/
public class ColoringStubsStrategy extends ColoringStrategy {
private static final Color STUB_BG = new Color(1.0f, 0.8f, 0.8f);
@Override
public Color getColor(final Object element) {
if (element instanceof MimElement) {
MimElement me = (MimElement) element;
if (me.isStub()) {
return STUB_BG;
}
}
return Color.WHITE;
}
}

View File

@ -0,0 +1,14 @@
package ch.bitwave.mim.canvas.strategies;
import java.util.List;
import ch.bitwave.mim.canvas.features.ConnectionNodeFeature;
import ch.bitwave.mim.canvas.features.NodeFeature;
public abstract class ConnectorLayoutStrategy {
public abstract void layoutConnectorNodes(final NodeFeature node,
List<ConnectionNodeFeature> outboundConnectors,
List<ConnectionNodeFeature> inboundConnectors);
}

View File

@ -0,0 +1,21 @@
package ch.bitwave.mim.canvas.strategies;
import java.awt.Graphics2D;
import ch.bitwave.mim.m2.core.NamedElement;
import ch.parametrix.common.util.ui.swing.TextRenderer;
import ch.parametrix.common.util.ui.swing.contracts.WorldDistance;
public class FitTextSizingStrategy extends SizingStrategy {
@Override
public WorldDistance getSize(final Graphics2D g, final Object element) {
TextRenderer tr = new TextRenderer(g, 0, 0, 2000, false);
if (element instanceof NamedElement) {
NamedElement ne = (NamedElement) element;
int width = tr.measureWidth(g, ne.getName());
return new WorldDistance(Math.max(500, width * 2), 300);
}
return new WorldDistance(1500, 300);
}
}

View File

@ -0,0 +1,54 @@
package ch.bitwave.mim.canvas.strategies;
import java.awt.geom.Point2D;
import java.util.List;
import ch.bitwave.mim.canvas.features.ConnectionNodeFeature;
import ch.bitwave.mim.canvas.features.NodeFeature;
/**
* Arranges the inbound and outbound connectors of a node in a left-to-right
* layout. The inbound and outbound connectors are fanned out along a certain
* percentage of the node height to make them more easily discernible.
*/
public class HorizontalFannedConnectorLayoutStrategy extends ConnectorLayoutStrategy {
/**
* Specifies the factor of the total height of the node occupied by the
* connector fan.
*/
private static final double FAN_EXTENT_FACTOR = 0.6;
private static final double NON_FAN = 1 - FAN_EXTENT_FACTOR;
@Override
public void layoutConnectorNodes(final NodeFeature node,
final List<ConnectionNodeFeature> outboundConnectors,
final List<ConnectionNodeFeature> inboundConnectors) {
Point2D anchor = node.getAnchor();
double h = node.getSize().getDy();
double x = anchor.getX();
layoutConnectors(inboundConnectors, anchor, h, x);
x += node.getSize().getDx();
layoutConnectors(outboundConnectors, anchor, h, x);
}
private void layoutConnectors(final List<ConnectionNodeFeature> connectors,
final Point2D anchor, final double h, final double x) {
int numConnectors = connectors.size();
if (numConnectors > 0) {
double y = calcFanStart(anchor.getY(), h, numConnectors);
double dy = (h * FAN_EXTENT_FACTOR) / (numConnectors - 1);
for (ConnectionNodeFeature cn : connectors) {
cn.place(x, y);
y += dy;
}
}
}
private double calcFanStart(final double t, final double d, final int size) {
if (size == 1) {
return t + d / 2.0;
}
return t + (NON_FAN * d) / 2;
}
}

View File

@ -0,0 +1,32 @@
package ch.bitwave.mim.canvas.strategies;
import java.awt.geom.Point2D;
import java.util.List;
import ch.bitwave.mim.canvas.features.ConnectionNodeFeature;
import ch.bitwave.mim.canvas.features.NodeFeature;
/**
* Arranges the inbound and outbound connectors of a node in a left-to-right
* layout. All inbound connections originate in the same point, as do all
* outbound connections.
*/
public class HorizontalPointConnectorLayoutStrategy extends ConnectorLayoutStrategy {
@Override
public void layoutConnectorNodes(final NodeFeature node,
final List<ConnectionNodeFeature> outboundConnectors,
final List<ConnectionNodeFeature> inboundConnectors) {
Point2D anchor = node.getAnchor();
double x = anchor.getX();
double y = anchor.getY() + node.getSize().getDy() / 2;
for (ConnectionNodeFeature cn : inboundConnectors) {
cn.place(x, y);
}
x += node.getSize().getDx();
for (ConnectionNodeFeature cn : outboundConnectors) {
cn.place(x, y);
}
}
}

View File

@ -0,0 +1,11 @@
package ch.bitwave.mim.canvas.strategies;
import java.awt.Graphics2D;
import ch.parametrix.common.util.ui.swing.contracts.WorldDistance;
public abstract class SizingStrategy {
public abstract WorldDistance getSize(final Graphics2D g, final Object element);
}

View File

@ -0,0 +1,54 @@
package ch.bitwave.mim.canvas.strategies;
import java.awt.geom.Point2D;
import java.util.List;
import ch.bitwave.mim.canvas.features.ConnectionNodeFeature;
import ch.bitwave.mim.canvas.features.NodeFeature;
/**
* Arranges the inbound and outbound connectors of a node in a top-to-bottom
* layout. The inbound and outbound connectors are fanned out along a certain
* percentage of the node width to make them more easily discernible.
*/
public class VerticalFannedConnectorLayoutStrategy extends ConnectorLayoutStrategy {
/**
* Specifies the factor of the total height of the node occupied by the
* connector fan.
*/
private static final double FAN_EXTENT_FACTOR = 0.6;
private static final double NON_FAN = 1 - FAN_EXTENT_FACTOR;
@Override
public void layoutConnectorNodes(final NodeFeature node,
final List<ConnectionNodeFeature> outboundConnectors,
final List<ConnectionNodeFeature> inboundConnectors) {
Point2D anchor = node.getAnchor();
double w = node.getSize().getDx();
double y = anchor.getY();
layoutConnectors(inboundConnectors, anchor, w, y);
y += node.getSize().getDy();
layoutConnectors(outboundConnectors, anchor, w, y);
}
private void layoutConnectors(final List<ConnectionNodeFeature> connectors,
final Point2D anchor, final double w, final double y) {
int numConnectors = connectors.size();
if (numConnectors > 0) {
double x = calcFanStart(anchor.getX(), w, numConnectors);
double dx = (w * FAN_EXTENT_FACTOR) / (numConnectors - 1);
for (ConnectionNodeFeature cn : connectors) {
cn.place(x, y);
x += dx;
}
}
}
private double calcFanStart(final double t, final double d, final int size) {
if (size == 1) {
return t + d / 2.0;
}
return t + (NON_FAN * d) / 2;
}
}

View File

@ -0,0 +1,32 @@
package ch.bitwave.mim.canvas.strategies;
import java.awt.geom.Point2D;
import java.util.List;
import ch.bitwave.mim.canvas.features.ConnectionNodeFeature;
import ch.bitwave.mim.canvas.features.NodeFeature;
/**
* Arranges the inbound and outbound connectors of a node in a top-to-bottom
* layout. All inbound connections originate in the same point, as do all
* outbound connections.
*/
public class VerticalPointConnectorLayoutStrategy extends ConnectorLayoutStrategy {
@Override
public void layoutConnectorNodes(final NodeFeature node,
final List<ConnectionNodeFeature> outboundConnectors,
final List<ConnectionNodeFeature> inboundConnectors) {
Point2D anchor = node.getAnchor();
double x = anchor.getX() + node.getSize().getDx() / 2;
double y = anchor.getY();
for (ConnectionNodeFeature cn : inboundConnectors) {
cn.place(x, y);
}
y += node.getSize().getDy();
for (ConnectionNodeFeature cn : outboundConnectors) {
cn.place(x, y);
}
}
}

View File

@ -0,0 +1,17 @@
package ch.bitwave.mim.canvas.views;
import java.awt.geom.AffineTransform;
import ch.parametrix.common.util.ui.swing.canvas.CanvasPanel;
public class MimCanvas extends CanvasPanel {
private static final long serialVersionUID = 1L;
@Override
protected void initViewTransform() {
super.initViewTransform();
scaleCanvas(AffineTransform.getScaleInstance(0.05, 0.05));
}
}

View File

@ -0,0 +1,33 @@
package ch.bitwave.mim.canvas.generators;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import ch.bitwave.mim.m2.core.MimClass;
import ch.bitwave.mim.m2.core.MimElement;
import ch.bitwave.mim.m2.core.NamedElement;
public class ElementByNameFilterTest {
@Test
public void shouldAcceptMatchingCaseInsensitive() {
ElementByNameFilter filter = new ElementByNameFilter("tpomcontroller", false);
assertTrue(filter.accepts(createElement("TPOMController")));
assertFalse(filter.accepts(createElement("TFooController")));
}
@Test
public void shouldAcceptMatchingCaseSensitive() {
ElementByNameFilter filter = new ElementByNameFilter("TPOMController", true);
assertTrue(filter.accepts(createElement("TPOMController")));
assertFalse(filter.accepts(createElement("TpomController")));
}
private MimElement createElement(final String name) {
NamedElement result = new MimClass();
result.setName(name);
return result;
}
}