Added better user interface controls and panning.
This commit is contained in:
parent
471db2f60a
commit
d5546a5320
2
pom.xml
2
pom.xml
|
@ -6,7 +6,7 @@
|
|||
|
||||
<groupId>nl.andrewlalis</groupId>
|
||||
<artifactId>EntityRelationMappingEditor</artifactId>
|
||||
<version>1.1.0</version>
|
||||
<version>1.2.0</version>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
|
|
|
@ -4,7 +4,7 @@ import com.formdev.flatlaf.FlatLightLaf;
|
|||
import nl.andrewlalis.erme.view.EditorFrame;
|
||||
|
||||
public class EntityRelationMappingEditor {
|
||||
public static final String VERSION = "1.1.0";
|
||||
public static final String VERSION = "1.2.0";
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (!FlatLightLaf.install()) {
|
||||
|
|
|
@ -84,23 +84,13 @@ public class ExportToImageAction extends AbstractAction {
|
|||
private BufferedImage renderModel() {
|
||||
BufferedImage bufferedImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB);
|
||||
Graphics2D g2d = bufferedImage.createGraphics();
|
||||
int minX = Integer.MAX_VALUE;
|
||||
int minY = Integer.MAX_VALUE;
|
||||
int maxX = Integer.MIN_VALUE;
|
||||
int maxY = Integer.MIN_VALUE;
|
||||
for (Relation r : model.getRelations()) {
|
||||
Rectangle bounds = r.getViewModel().getBounds(g2d);
|
||||
minX = Math.min(minX, bounds.x);
|
||||
minY = Math.min(minY, bounds.y);
|
||||
maxX = Math.max(maxX, bounds.x + bounds.width);
|
||||
maxY = Math.max(maxY, bounds.y + bounds.height);
|
||||
}
|
||||
BufferedImage outputImage = new BufferedImage((maxX - minX), (maxY - minY) + 20, BufferedImage.TYPE_INT_RGB);
|
||||
final Rectangle bounds = this.model.getViewModel().getBounds(g2d);
|
||||
BufferedImage outputImage = new BufferedImage(bounds.width, bounds.height + 20, BufferedImage.TYPE_INT_RGB);
|
||||
g2d = outputImage.createGraphics();
|
||||
g2d.setColor(Color.WHITE);
|
||||
g2d.fillRect(outputImage.getMinX(), outputImage.getMinY(), outputImage.getWidth(), outputImage.getHeight());
|
||||
AffineTransform originalTransform = g2d.getTransform();
|
||||
g2d.setTransform(AffineTransform.getTranslateInstance(-minX, -minY));
|
||||
g2d.setTransform(AffineTransform.getTranslateInstance(-bounds.x, -bounds.y));
|
||||
|
||||
List<Relation> selectedRelations = this.model.getSelectedRelations();
|
||||
this.model.getSelectedRelations().forEach(r -> r.setSelected(false));
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
package nl.andrewlalis.erme.control.actions;
|
||||
|
||||
import lombok.Setter;
|
||||
import nl.andrewlalis.erme.model.*;
|
||||
import nl.andrewlalis.erme.view.DiagramPanel;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
|
||||
public class LoadSampleModelAction extends AbstractAction {
|
||||
private static LoadSampleModelAction instance;
|
||||
|
||||
public static LoadSampleModelAction getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new LoadSampleModelAction();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Setter
|
||||
private DiagramPanel diagramPanel;
|
||||
|
||||
public LoadSampleModelAction() {
|
||||
super("Load Sample Model");
|
||||
this.putValue(SHORT_DESCRIPTION, "Loads a sample ER-mapping model.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
MappingModel model = new MappingModel();
|
||||
Relation r0 = new Relation(model, new Point(50, 20), "AirplaneType");
|
||||
r0.addAttribute(new Attribute(r0, AttributeType.ID_KEY, "name"));
|
||||
r0.addAttribute(new Attribute(r0, AttributeType.PLAIN, "manufacturer"));
|
||||
model.addRelation(r0);
|
||||
Relation r1 = new Relation(model, new Point(50, 100), "Airplane");
|
||||
r1.addAttribute(new Attribute(r1, AttributeType.ID_KEY, "id"));
|
||||
r1.addAttribute(new Attribute(r1, AttributeType.PLAIN, "purchasedAt"));
|
||||
r1.addAttribute(new ForeignKeyAttribute(r1, AttributeType.PLAIN, "typeName", "AirplaneType", "name"));
|
||||
model.addRelation(r1);
|
||||
this.diagramPanel.setModel(model);
|
||||
}
|
||||
}
|
|
@ -7,7 +7,6 @@ import nl.andrewlalis.erme.view.DiagramPopupMenu;
|
|||
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.InputEvent;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
|
||||
|
@ -31,22 +30,29 @@ public class DiagramMouseListener extends MouseAdapter {
|
|||
*/
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e) {
|
||||
DiagramPanel panel = (DiagramPanel) e.getSource();
|
||||
final Graphics2D g2d = panel.getGraphics2D();
|
||||
final DiagramPanel panel = (DiagramPanel) e.getSource();
|
||||
final Graphics2D g = panel.getGraphics2D();
|
||||
this.mouseDragStart = e.getPoint();
|
||||
final int modelX = e.getX() - panel.getPanningTranslation().x;
|
||||
final int modelY = e.getY() - panel.getPanningTranslation().y;
|
||||
|
||||
boolean isCtrlDown = (e.getModifiers() & ActionEvent.CTRL_MASK) == ActionEvent.CTRL_MASK;
|
||||
final boolean isCtrlDown = (e.getModifiers() & ActionEvent.CTRL_MASK) == ActionEvent.CTRL_MASK;
|
||||
final boolean isShiftDown = (e.getModifiers() & ActionEvent.SHIFT_MASK) == ActionEvent.SHIFT_MASK;
|
||||
|
||||
if (!isCtrlDown) {
|
||||
if (!isShiftDown && !isCtrlDown) {// A simple click anywhere should reset selection.
|
||||
this.model.getRelations().forEach(r -> r.setSelected(false));
|
||||
}
|
||||
|
||||
if (!isShiftDown) {// If the user clicked or CTRL+clicked, try and select the relation they clicked on.
|
||||
for (Relation r : this.model.getRelations()) {
|
||||
if (r.getViewModel().getBounds(g2d).contains(e.getX(), e.getY())) {
|
||||
if (r.getViewModel().getBounds(g).contains(modelX, modelY)) {
|
||||
r.setSelected(!r.isSelected());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the user right-clicked, show a popup menu.
|
||||
if (e.getButton() == MouseEvent.BUTTON3) {
|
||||
DiagramPopupMenu popupMenu = new DiagramPopupMenu(this.model);
|
||||
popupMenu.show(panel, e.getX(), e.getY());
|
||||
|
@ -62,15 +68,24 @@ public class DiagramMouseListener extends MouseAdapter {
|
|||
|
||||
@Override
|
||||
public void mouseDragged(MouseEvent e) {
|
||||
int dx = this.mouseDragStart.x - e.getX();
|
||||
int dy = this.mouseDragStart.y - e.getY();
|
||||
final int dx = this.mouseDragStart.x - e.getX();
|
||||
final int dy = this.mouseDragStart.y - e.getY();
|
||||
final boolean isShiftDown = (e.getModifiers() & ActionEvent.SHIFT_MASK) == ActionEvent.SHIFT_MASK;
|
||||
boolean changed = false;
|
||||
|
||||
if (isShiftDown) {
|
||||
final DiagramPanel panel = (DiagramPanel) e.getSource();
|
||||
panel.translate(-dx, -dy);
|
||||
panel.repaint();
|
||||
} else {
|
||||
for (Relation r : this.model.getRelations()) {
|
||||
if (r.isSelected()) {
|
||||
r.setPosition(new Point(r.getPosition().x - dx, r.getPosition().y - dy));
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
this.model.fireChangedEvent();
|
||||
}
|
||||
|
|
|
@ -59,4 +59,8 @@ public class Attribute implements Serializable {
|
|||
public String toString() {
|
||||
return this.getName();
|
||||
}
|
||||
|
||||
public Attribute copy(Relation newRelation) {
|
||||
return new Attribute(newRelation, this.getType(), this.getName());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,4 +31,9 @@ public class ForeignKeyAttribute extends Attribute {
|
|||
public String toString() {
|
||||
return super.toString() + "->" + this.getFullReferenceName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ForeignKeyAttribute copy(Relation newRelation) {
|
||||
return new ForeignKeyAttribute(newRelation, this.getType(), this.getName(), this.getReference());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
package nl.andrewlalis.erme.model;
|
||||
|
||||
import lombok.Getter;
|
||||
import nl.andrewlalis.erme.view.view_models.MappingModelViewModel;
|
||||
import nl.andrewlalis.erme.view.view_models.ViewModel;
|
||||
|
||||
import java.awt.*;
|
||||
import java.io.Serializable;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
@ -13,7 +16,7 @@ import java.util.stream.Collectors;
|
|||
* This model contains all the information about a single mapping diagram,
|
||||
* including each mapped table and the links between them.
|
||||
*/
|
||||
public class MappingModel implements Serializable {
|
||||
public class MappingModel implements Serializable, Viewable {
|
||||
@Getter
|
||||
private final Set<Relation> relations;
|
||||
|
||||
|
@ -79,6 +82,23 @@ public class MappingModel implements Serializable {
|
|||
this.changeListeners.forEach(ModelChangeListener::onModelChanged);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the positions of all relations so that the bounding box for this
|
||||
* model starts at 0, 0.
|
||||
*/
|
||||
public final void normalizeRelationPositions() {
|
||||
int minX = Integer.MAX_VALUE;
|
||||
int minY = Integer.MAX_VALUE;
|
||||
for (Relation r : this.getRelations()) {
|
||||
minX = Math.min(minX, r.getPosition().x);
|
||||
minY = Math.min(minY, r.getPosition().y);
|
||||
}
|
||||
for (Relation r : this.getRelations()) {
|
||||
final Point current = r.getPosition();
|
||||
r.setPosition(new Point(current.x - minX, current.y - minY));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
|
@ -91,4 +111,15 @@ public class MappingModel implements Serializable {
|
|||
public int hashCode() {
|
||||
return Objects.hash(this.getRelations());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ViewModel getViewModel() {
|
||||
return new MappingModelViewModel(this);
|
||||
}
|
||||
|
||||
public MappingModel copy() {
|
||||
MappingModel c = new MappingModel();
|
||||
this.getRelations().forEach(r -> c.addRelation(r.copy(c)));
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package nl.andrewlalis.erme.model;
|
|||
|
||||
import lombok.Getter;
|
||||
import nl.andrewlalis.erme.view.view_models.RelationViewModel;
|
||||
import nl.andrewlalis.erme.view.view_models.ViewModel;
|
||||
|
||||
import java.awt.*;
|
||||
import java.io.Serializable;
|
||||
|
@ -14,7 +15,7 @@ import java.util.stream.Collectors;
|
|||
* Represents a single "relation" or table in the diagram.
|
||||
*/
|
||||
@Getter
|
||||
public class Relation implements Serializable {
|
||||
public class Relation implements Serializable, Viewable {
|
||||
private final MappingModel model;
|
||||
private Point position;
|
||||
private String name;
|
||||
|
@ -59,7 +60,8 @@ public class Relation implements Serializable {
|
|||
}
|
||||
}
|
||||
|
||||
public RelationViewModel getViewModel() {
|
||||
@Override
|
||||
public ViewModel getViewModel() {
|
||||
if (this.viewModel == null) {
|
||||
this.viewModel = new RelationViewModel(this);
|
||||
}
|
||||
|
@ -89,4 +91,10 @@ public class Relation implements Serializable {
|
|||
public String toString() {
|
||||
return this.getName();
|
||||
}
|
||||
|
||||
public Relation copy(MappingModel newModel) {
|
||||
Relation c = new Relation(newModel, new Point(this.getPosition()), this.getName());
|
||||
this.getAttributes().forEach(a -> c.addAttribute(a.copy(c)));
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
package nl.andrewlalis.erme.model;
|
||||
|
||||
import nl.andrewlalis.erme.view.view_models.ViewModel;
|
||||
|
||||
public interface Viewable {
|
||||
ViewModel getViewModel();
|
||||
}
|
|
@ -1,10 +1,7 @@
|
|||
package nl.andrewlalis.erme.view;
|
||||
|
||||
import lombok.Getter;
|
||||
import nl.andrewlalis.erme.control.actions.ExportToImageAction;
|
||||
import nl.andrewlalis.erme.control.actions.LoadAction;
|
||||
import nl.andrewlalis.erme.control.actions.NewModelAction;
|
||||
import nl.andrewlalis.erme.control.actions.SaveAction;
|
||||
import nl.andrewlalis.erme.control.actions.*;
|
||||
import nl.andrewlalis.erme.control.actions.edits.AddAttributeAction;
|
||||
import nl.andrewlalis.erme.control.actions.edits.AddRelationAction;
|
||||
import nl.andrewlalis.erme.control.actions.edits.RemoveAttributeAction;
|
||||
|
@ -12,10 +9,11 @@ import nl.andrewlalis.erme.control.actions.edits.RemoveRelationAction;
|
|||
import nl.andrewlalis.erme.control.diagram.DiagramMouseListener;
|
||||
import nl.andrewlalis.erme.model.MappingModel;
|
||||
import nl.andrewlalis.erme.model.ModelChangeListener;
|
||||
import nl.andrewlalis.erme.view.view_models.MappingModelViewModel;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.event.MouseListener;
|
||||
import java.awt.event.MouseMotionListener;
|
||||
|
||||
|
@ -26,8 +24,30 @@ public class DiagramPanel extends JPanel implements ModelChangeListener {
|
|||
@Getter
|
||||
private MappingModel model;
|
||||
|
||||
@Getter
|
||||
private final Point panningTranslation;
|
||||
|
||||
public DiagramPanel(MappingModel model) {
|
||||
super(true);
|
||||
this.panningTranslation = new Point();
|
||||
InputMap inputMap = this.getInputMap(WHEN_IN_FOCUSED_WINDOW);
|
||||
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, KeyEvent.SHIFT_DOWN_MASK), "PAN_RESET");
|
||||
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, KeyEvent.CTRL_DOWN_MASK), "CENTER_MODEL");
|
||||
this.getActionMap().put("PAN_RESET", new AbstractAction() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
resetTranslation();
|
||||
repaint();
|
||||
}
|
||||
});
|
||||
this.getActionMap().put("CENTER_MODEL", new AbstractAction() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
model.normalizeRelationPositions();
|
||||
centerModel();
|
||||
repaint();
|
||||
}
|
||||
});
|
||||
this.setModel(model);
|
||||
}
|
||||
|
||||
|
@ -44,13 +64,38 @@ public class DiagramPanel extends JPanel implements ModelChangeListener {
|
|||
this.addMouseListener(listener);
|
||||
this.addMouseMotionListener(listener);
|
||||
this.updateActionModels();
|
||||
this.centerModel();
|
||||
this.repaint();
|
||||
}
|
||||
|
||||
public void translate(int dx, int dy) {
|
||||
this.panningTranslation.x += dx;
|
||||
this.panningTranslation.y += dy;
|
||||
}
|
||||
|
||||
public void resetTranslation() {
|
||||
this.panningTranslation.x = 0;
|
||||
this.panningTranslation.y = 0;
|
||||
}
|
||||
|
||||
public void centerModel() {
|
||||
if (this.getGraphics() == null) {
|
||||
return;
|
||||
}
|
||||
final Rectangle modelBounds = this.getModel().getViewModel().getBounds(this.getGraphics2D());
|
||||
final int modelCenterX = modelBounds.x + modelBounds.width / 2;
|
||||
final int modelCenterY = modelBounds.y + modelBounds.height / 2;
|
||||
final int panelCenterX = this.getWidth() / 2;
|
||||
final int panelCenterY = this.getHeight() / 2;
|
||||
this.resetTranslation();
|
||||
this.translate(panelCenterX - modelCenterX, panelCenterY - modelCenterY);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void paintComponent(Graphics g) {
|
||||
super.paintComponent(g);
|
||||
new MappingModelViewModel(this.model).draw(this.getGraphics2D(g));
|
||||
g.translate(this.panningTranslation.x, this.panningTranslation.y);
|
||||
this.model.getViewModel().draw(this.getGraphics2D(g));
|
||||
}
|
||||
|
||||
public Graphics2D getGraphics2D(Graphics g) {
|
||||
|
@ -83,5 +128,6 @@ public class DiagramPanel extends JPanel implements ModelChangeListener {
|
|||
RemoveRelationAction.getInstance().setModel(this.model);
|
||||
AddAttributeAction.getInstance().setModel(this.model);
|
||||
RemoveAttributeAction.getInstance().setModel(this.model);
|
||||
LoadSampleModelAction.getInstance().setDiagramPanel(this);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ public class EditorFrame extends JFrame {
|
|||
this.setMinimumSize(new Dimension(400, 400));
|
||||
this.setPreferredSize(new Dimension(800, 800));
|
||||
this.pack();
|
||||
this.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
|
||||
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
|
||||
this.setLocationRelativeTo(null);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,6 +45,7 @@ public class EditorMenuBar extends JMenuBar {
|
|||
private JMenu buildHelpMenu() {
|
||||
JMenu menu = new JMenu("Help");
|
||||
menu.add(InstructionsAction.getInstance());
|
||||
menu.add(LoadSampleModelAction.getInstance());
|
||||
menu.add(AboutAction.getInstance());
|
||||
return menu;
|
||||
}
|
||||
|
|
|
@ -9,6 +9,9 @@ import java.awt.font.TextAttribute;
|
|||
import java.awt.geom.Rectangle2D;
|
||||
import java.text.AttributedString;
|
||||
|
||||
/**
|
||||
* View model for rendering a single attribute of a relation.
|
||||
*/
|
||||
public class AttributeViewModel implements ViewModel {
|
||||
public static final int PADDING_X = 5;
|
||||
public static final int PADDING_Y = 5;
|
||||
|
@ -25,7 +28,7 @@ public class AttributeViewModel implements ViewModel {
|
|||
@Override
|
||||
public void draw(Graphics2D g) {
|
||||
AttributedString as = this.getAttributedString(g);
|
||||
Rectangle r = this.getBoxBounds(g, as);
|
||||
Rectangle r = this.getBounds(g, as);
|
||||
g.setColor(BACKGROUND_COLOR);
|
||||
g.fillRect(r.x, r.y, r.width, r.height);
|
||||
g.setColor(FONT_COLOR);
|
||||
|
@ -40,12 +43,18 @@ public class AttributeViewModel implements ViewModel {
|
|||
}
|
||||
}
|
||||
|
||||
private Rectangle getBoxBounds(Graphics2D g, AttributedString as) {
|
||||
@Override
|
||||
public Rectangle getBounds(Graphics2D g) {
|
||||
return this.getBounds(g, this.getAttributedString(g));
|
||||
}
|
||||
|
||||
private Rectangle getBounds(Graphics2D g, AttributedString as) {
|
||||
final RelationViewModel relationViewModel = (RelationViewModel) this.attribute.getRelation().getViewModel();
|
||||
int x = this.attribute.getRelation().getPosition().x + RelationViewModel.PADDING_X;
|
||||
int y = this.attribute.getRelation().getPosition().y + this.attribute.getRelation().getViewModel().getNameBounds(g).height + RelationViewModel.ATTRIBUTE_SEPARATION;
|
||||
int y = this.attribute.getRelation().getPosition().y + relationViewModel.getNameBounds(g).height + RelationViewModel.ATTRIBUTE_SEPARATION;
|
||||
int i = 0;
|
||||
while (!this.attribute.getRelation().getAttributes().get(i).equals(this.attribute)) {
|
||||
x += this.attribute.getRelation().getAttributes().get(i).getViewModel().getBoxBounds(g).width;
|
||||
x += this.attribute.getRelation().getAttributes().get(i).getViewModel().getBounds(g).width;
|
||||
i++;
|
||||
}
|
||||
Rectangle2D nameRect = g.getFontMetrics().getStringBounds(as.getIterator(), 0, this.attribute.getName().length(), g);
|
||||
|
@ -62,10 +71,6 @@ public class AttributeViewModel implements ViewModel {
|
|||
return new Rectangle(x, y, width, height);
|
||||
}
|
||||
|
||||
public Rectangle getBoxBounds(Graphics2D g) {
|
||||
return this.getBoxBounds(g, this.getAttributedString(g));
|
||||
}
|
||||
|
||||
private AttributedString getAttributedString(Graphics2D g) {
|
||||
AttributedString as = new AttributedString(this.attribute.getName());
|
||||
as.addAttribute(TextAttribute.FONT, g.getFont());
|
||||
|
|
|
@ -5,6 +5,9 @@ import nl.andrewlalis.erme.model.Relation;
|
|||
|
||||
import java.awt.*;
|
||||
|
||||
/**
|
||||
* View model for rendering an entire {@code MappingModel} object.
|
||||
*/
|
||||
public class MappingModelViewModel implements ViewModel {
|
||||
private final MappingModel model;
|
||||
|
||||
|
@ -18,4 +21,20 @@ public class MappingModelViewModel implements ViewModel {
|
|||
r.getViewModel().draw(g);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Rectangle getBounds(Graphics2D g) {
|
||||
int minX = Integer.MAX_VALUE;
|
||||
int minY = Integer.MAX_VALUE;
|
||||
int maxX = Integer.MIN_VALUE;
|
||||
int maxY = Integer.MIN_VALUE;
|
||||
for (Relation r : model.getRelations()) {
|
||||
Rectangle bounds = r.getViewModel().getBounds(g);
|
||||
minX = Math.min(minX, bounds.x);
|
||||
minY = Math.min(minY, bounds.y);
|
||||
maxX = Math.max(maxX, bounds.x + bounds.width);
|
||||
maxY = Math.max(maxY, bounds.y + bounds.height);
|
||||
}
|
||||
return new Rectangle(minX, minY, Math.abs(maxX - minX), Math.abs(maxY - minY));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,11 +8,28 @@ import java.awt.font.TextAttribute;
|
|||
import java.awt.geom.Rectangle2D;
|
||||
import java.text.AttributedString;
|
||||
|
||||
/**
|
||||
* View model which handles rendering a single relation.
|
||||
*/
|
||||
public class RelationViewModel implements ViewModel {
|
||||
/**
|
||||
* Padding that should be added to the left and right of the true bounds.
|
||||
*/
|
||||
public static final int PADDING_X = 5;
|
||||
|
||||
/**
|
||||
* Padding that should be added to the top and bottom of the true bounds.
|
||||
*/
|
||||
public static final int PADDING_Y = 5;
|
||||
|
||||
/**
|
||||
* The space between the relation's name and any attributes.
|
||||
*/
|
||||
public static final int ATTRIBUTE_SEPARATION = 10;
|
||||
|
||||
public static final Color SELECTED_COLOR = new Color(204, 224, 255);
|
||||
public static final Color NAME_COLOR = Color.BLACK;
|
||||
|
||||
private final Relation relation;
|
||||
|
||||
public RelationViewModel(Relation relation) {
|
||||
|
@ -23,17 +40,23 @@ public class RelationViewModel implements ViewModel {
|
|||
public void draw(Graphics2D g) {
|
||||
AttributedString as = this.getAttributedString(g);
|
||||
Rectangle bounds = this.getBounds(g);
|
||||
g.setColor(Color.BLACK);
|
||||
if (this.relation.isSelected()) {
|
||||
g.setColor(SELECTED_COLOR);
|
||||
g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
|
||||
}
|
||||
g.setColor(NAME_COLOR);
|
||||
g.drawString(as.getIterator(), bounds.x + PADDING_X, bounds.y + this.getNameBounds(g).height - PADDING_Y);
|
||||
for (Attribute a : this.relation.getAttributes()) {
|
||||
a.getViewModel().draw(g);
|
||||
}
|
||||
if (this.relation.isSelected()) {
|
||||
g.setColor(Color.BLUE);
|
||||
g.drawRect(bounds.x, bounds.y, bounds.width, bounds.height);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the bounding box for the relation that'll be drawn. The bounding
|
||||
* box contains the name of the relation and any attributes in it.
|
||||
* @param g The graphics context.
|
||||
* @return A rectangle describing the bounding box.
|
||||
*/
|
||||
public Rectangle getBounds(Graphics2D g) {
|
||||
Rectangle rect = new Rectangle();
|
||||
rect.x = this.relation.getPosition().x;
|
||||
|
@ -41,7 +64,7 @@ public class RelationViewModel implements ViewModel {
|
|||
int totalAttributeWidth = 0;
|
||||
int maxAttributeHeight = 0;
|
||||
for (Attribute a : this.relation.getAttributes()) {
|
||||
Rectangle attributeBounds = a.getViewModel().getBoxBounds(g);
|
||||
Rectangle attributeBounds = a.getViewModel().getBounds(g);
|
||||
totalAttributeWidth += attributeBounds.width;
|
||||
maxAttributeHeight = Math.max(maxAttributeHeight, attributeBounds.height);
|
||||
}
|
||||
|
@ -51,12 +74,24 @@ public class RelationViewModel implements ViewModel {
|
|||
return rect;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the bounding box around the relation's name, according to the font,
|
||||
* font size, weight, etc.
|
||||
* @param g The graphics context.
|
||||
* @return A rectangle describing the name's bounding box.
|
||||
*/
|
||||
public Rectangle getNameBounds(Graphics2D g) {
|
||||
AttributedString as = this.getAttributedString(g);
|
||||
Rectangle2D nameBounds = g.getFontMetrics().getStringBounds(as.getIterator(), 0, this.relation.getName().length(), g);
|
||||
return nameBounds.getBounds();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an instance of AttributedString that can be used for display and
|
||||
* reference purposes.
|
||||
* @param g The graphics context.
|
||||
* @return The attributed string, with all necessary attributes set.
|
||||
*/
|
||||
private AttributedString getAttributedString(Graphics2D g) {
|
||||
AttributedString as = new AttributedString(this.relation.getName());
|
||||
as.addAttribute(TextAttribute.FONT, g.getFont());
|
||||
|
|
|
@ -4,4 +4,6 @@ import java.awt.*;
|
|||
|
||||
public interface ViewModel {
|
||||
void draw(Graphics2D g);
|
||||
|
||||
Rectangle getBounds(Graphics2D g);
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
The <em>Edit</em> menu contains options for making changes to the current model, such as adding or removing relations and attributes, or undoing/redoing actions.
|
||||
</li>
|
||||
<li>
|
||||
The <em>Help</em> menu contains some items with additional information about the application, like this help page and a simple <em>About</em> popup with version information.
|
||||
The <em>Help</em> menu contains some items with additional information about the application, like this help page and a simple <em>About</em> popup with version information. There's also a <em>Load Sample Model</em> option, which will load a very basic sample model into the application that you are free to mess around with.
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
|
@ -47,6 +47,12 @@
|
|||
<li>
|
||||
Right-click on different areas to access a context menu with some helpful actions.
|
||||
</li>
|
||||
<li>
|
||||
Hold <em>Shift</em> while dragging the mouse to pan across the model. To reset panning, press <em>Shift + Space</em>.
|
||||
</li>
|
||||
<li>
|
||||
Press <em>Control + Space</em> to move the view so that the diagram is centered.
|
||||
</li>
|
||||
<li>
|
||||
When exporting the model to an image, be sure to add the desired image file extension (.png, .jpg, .bmp, etc.), or the application will default to .png.
|
||||
</li>
|
||||
|
|
Loading…
Reference in New Issue