From 8e8b8d33024abe21de339bde5a0452d1b268b19d Mon Sep 17 00:00:00 2001 From: Andrew Lalis Date: Sun, 7 Feb 2021 15:28:23 +0100 Subject: [PATCH] Added base UI --- .gitignore | 115 ++++++++++++++++++ pom.xml | 36 ++++++ .../erme/EntityRelationMappingEditor.java | 15 +++ .../erme/control/actions/ExitAction.java | 28 +++++ .../control/actions/ExportToImageAction.java | 28 +++++ .../erme/control/actions/RedoAction.java | 28 +++++ .../erme/control/actions/UndoAction.java | 28 +++++ .../control/diagram/DiagramMouseListener.java | 60 +++++++++ .../nl/andrewlalis/erme/model/Attribute.java | 45 +++++++ .../andrewlalis/erme/model/AttributeType.java | 8 ++ .../andrewlalis/erme/model/MappingModel.java | 57 +++++++++ .../erme/model/ModelChangeListener.java | 5 + .../nl/andrewlalis/erme/model/Relation.java | 76 ++++++++++++ .../andrewlalis/erme/view/DiagramPanel.java | 64 ++++++++++ .../nl/andrewlalis/erme/view/EditorFrame.java | 38 ++++++ .../andrewlalis/erme/view/EditorMenuBar.java | 36 ++++++ .../view/view_models/AttributeViewModel.java | 65 ++++++++++ .../view_models/MappingModelViewModel.java | 21 ++++ .../view/view_models/RelationViewModel.java | 67 ++++++++++ .../erme/view/view_models/ViewModel.java | 7 ++ 20 files changed, 827 insertions(+) create mode 100644 .gitignore create mode 100644 pom.xml create mode 100644 src/main/java/nl/andrewlalis/erme/EntityRelationMappingEditor.java create mode 100644 src/main/java/nl/andrewlalis/erme/control/actions/ExitAction.java create mode 100644 src/main/java/nl/andrewlalis/erme/control/actions/ExportToImageAction.java create mode 100644 src/main/java/nl/andrewlalis/erme/control/actions/RedoAction.java create mode 100644 src/main/java/nl/andrewlalis/erme/control/actions/UndoAction.java create mode 100644 src/main/java/nl/andrewlalis/erme/control/diagram/DiagramMouseListener.java create mode 100644 src/main/java/nl/andrewlalis/erme/model/Attribute.java create mode 100644 src/main/java/nl/andrewlalis/erme/model/AttributeType.java create mode 100644 src/main/java/nl/andrewlalis/erme/model/MappingModel.java create mode 100644 src/main/java/nl/andrewlalis/erme/model/ModelChangeListener.java create mode 100644 src/main/java/nl/andrewlalis/erme/model/Relation.java create mode 100644 src/main/java/nl/andrewlalis/erme/view/DiagramPanel.java create mode 100644 src/main/java/nl/andrewlalis/erme/view/EditorFrame.java create mode 100644 src/main/java/nl/andrewlalis/erme/view/EditorMenuBar.java create mode 100644 src/main/java/nl/andrewlalis/erme/view/view_models/AttributeViewModel.java create mode 100644 src/main/java/nl/andrewlalis/erme/view/view_models/MappingModelViewModel.java create mode 100644 src/main/java/nl/andrewlalis/erme/view/view_models/RelationViewModel.java create mode 100644 src/main/java/nl/andrewlalis/erme/view/view_models/ViewModel.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c4f2e58 --- /dev/null +++ b/.gitignore @@ -0,0 +1,115 @@ +# Created by .ignore support plugin (hsz.mobi) +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Maven template +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +# https://github.com/takari/maven-wrapper#usage-without-binary-jar +.mvn/wrapper/maven-wrapper.jar + +### Java template +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +.idea/ +*.iml + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..e3f01f0 --- /dev/null +++ b/pom.xml @@ -0,0 +1,36 @@ + + + 4.0.0 + + nl.andrewlalis + EntityRelationMappingEditor + 1.0-SNAPSHOT + + + + org.apache.maven.plugins + maven-compiler-plugin + + 8 + 8 + + + + + + + + com.formdev + flatlaf + 1.0-rc3 + + + org.projectlombok + lombok + 1.18.16 + provided + + + \ No newline at end of file diff --git a/src/main/java/nl/andrewlalis/erme/EntityRelationMappingEditor.java b/src/main/java/nl/andrewlalis/erme/EntityRelationMappingEditor.java new file mode 100644 index 0000000..6d98ac4 --- /dev/null +++ b/src/main/java/nl/andrewlalis/erme/EntityRelationMappingEditor.java @@ -0,0 +1,15 @@ +package nl.andrewlalis.erme; + +import com.formdev.flatlaf.FlatLightLaf; +import nl.andrewlalis.erme.view.EditorFrame; + +public class EntityRelationMappingEditor { + + public static void main(String[] args) { + if (!FlatLightLaf.install()) { + System.err.println("Could not install FlatLight Look and Feel."); + } + final EditorFrame frame = new EditorFrame(); + frame.setVisible(true); + } +} diff --git a/src/main/java/nl/andrewlalis/erme/control/actions/ExitAction.java b/src/main/java/nl/andrewlalis/erme/control/actions/ExitAction.java new file mode 100644 index 0000000..7361ca1 --- /dev/null +++ b/src/main/java/nl/andrewlalis/erme/control/actions/ExitAction.java @@ -0,0 +1,28 @@ +package nl.andrewlalis.erme.control.actions; + +import javax.swing.*; +import java.awt.event.ActionEvent; +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; + +public class ExitAction extends AbstractAction { + private static ExitAction instance; + + public static ExitAction getInstance() { + if (instance == null) { + instance = new ExitAction(); + } + return instance; + } + + public ExitAction() { + super("Exit"); + this.putValue(Action.SHORT_DESCRIPTION, "Exit the program."); + this.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_Q, InputEvent.SHIFT_DOWN_MASK | InputEvent.CTRL_DOWN_MASK)); + } + + @Override + public void actionPerformed(ActionEvent e) { + System.exit(0); + } +} diff --git a/src/main/java/nl/andrewlalis/erme/control/actions/ExportToImageAction.java b/src/main/java/nl/andrewlalis/erme/control/actions/ExportToImageAction.java new file mode 100644 index 0000000..0408c05 --- /dev/null +++ b/src/main/java/nl/andrewlalis/erme/control/actions/ExportToImageAction.java @@ -0,0 +1,28 @@ +package nl.andrewlalis.erme.control.actions; + +import javax.swing.*; +import java.awt.event.ActionEvent; +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; + +public class ExportToImageAction extends AbstractAction { + private static ExportToImageAction instance; + + public static ExportToImageAction getInstance() { + if (instance == null) { + instance = new ExportToImageAction(); + } + return instance; + } + + public ExportToImageAction() { + super("Export to Image"); + this.putValue(Action.SHORT_DESCRIPTION, "Export the current diagram to an image."); + this.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_E, InputEvent.CTRL_DOWN_MASK)); + } + + @Override + public void actionPerformed(ActionEvent e) { + System.out.println("Export to image."); + } +} diff --git a/src/main/java/nl/andrewlalis/erme/control/actions/RedoAction.java b/src/main/java/nl/andrewlalis/erme/control/actions/RedoAction.java new file mode 100644 index 0000000..3038057 --- /dev/null +++ b/src/main/java/nl/andrewlalis/erme/control/actions/RedoAction.java @@ -0,0 +1,28 @@ +package nl.andrewlalis.erme.control.actions; + +import javax.swing.*; +import java.awt.event.ActionEvent; +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; + +public class RedoAction extends AbstractAction { + private static RedoAction instance; + + public static RedoAction getInstance() { + if (instance == null) { + instance = new RedoAction(); + } + return instance; + } + + public RedoAction() { + super("Redo"); + this.putValue(Action.SHORT_DESCRIPTION, "Redoes a previously undone action."); + this.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_Z, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK)); + } + + @Override + public void actionPerformed(ActionEvent e) { + System.out.println("Redo"); + } +} diff --git a/src/main/java/nl/andrewlalis/erme/control/actions/UndoAction.java b/src/main/java/nl/andrewlalis/erme/control/actions/UndoAction.java new file mode 100644 index 0000000..2c9581f --- /dev/null +++ b/src/main/java/nl/andrewlalis/erme/control/actions/UndoAction.java @@ -0,0 +1,28 @@ +package nl.andrewlalis.erme.control.actions; + +import javax.swing.*; +import java.awt.event.ActionEvent; +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; + +public class UndoAction extends AbstractAction { + private static UndoAction instance; + + public static UndoAction getInstance() { + if (instance == null) { + instance = new UndoAction(); + } + return instance; + } + + public UndoAction() { + super("Undo"); + this.putValue(Action.SHORT_DESCRIPTION, "Undo the last action."); + this.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_Z, InputEvent.CTRL_DOWN_MASK)); + } + + @Override + public void actionPerformed(ActionEvent e) { + System.out.println("Undo"); + } +} diff --git a/src/main/java/nl/andrewlalis/erme/control/diagram/DiagramMouseListener.java b/src/main/java/nl/andrewlalis/erme/control/diagram/DiagramMouseListener.java new file mode 100644 index 0000000..ec1cf7d --- /dev/null +++ b/src/main/java/nl/andrewlalis/erme/control/diagram/DiagramMouseListener.java @@ -0,0 +1,60 @@ +package nl.andrewlalis.erme.control.diagram; + +import nl.andrewlalis.erme.model.MappingModel; +import nl.andrewlalis.erme.model.Relation; +import nl.andrewlalis.erme.view.DiagramPanel; + +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.InputEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; + +public class DiagramMouseListener extends MouseAdapter { + private final MappingModel model; + private Point mouseDragStart; + + public DiagramMouseListener(MappingModel model) { + this.model = model; + } + + @Override + public void mousePressed(MouseEvent e) { + DiagramPanel panel = (DiagramPanel) e.getSource(); + final Graphics2D g2d = panel.getGraphics2D(); + this.mouseDragStart = e.getPoint(); + if ((e.getModifiers() & ActionEvent.CTRL_MASK) == ActionEvent.CTRL_MASK) { + boolean hit = false; + for (Relation r : this.model.getRelations()) { + if (r.getViewModel().getBounds(g2d).contains(e.getX(), e.getY())) { + r.setSelected(!r.isSelected()); + hit = true; + } + } + if (!hit) { + this.model.getRelations().forEach(r -> r.setSelected(false)); + } + } + } + + @Override + public void mouseEntered(MouseEvent e) { + super.mouseEntered(e); + } + + @Override + public void mouseDragged(MouseEvent e) { + int dx = this.mouseDragStart.x - e.getX(); + int dy = this.mouseDragStart.y - e.getY(); + for (Relation r : this.model.getRelations()) { + if (r.isSelected()) { + r.setPosition(new Point(r.getPosition().x - dx, r.getPosition().y - dy)); + } + } + this.mouseDragStart = e.getPoint(); + } + + @Override + public void mouseMoved(MouseEvent e) { + } +} diff --git a/src/main/java/nl/andrewlalis/erme/model/Attribute.java b/src/main/java/nl/andrewlalis/erme/model/Attribute.java new file mode 100644 index 0000000..793b8cb --- /dev/null +++ b/src/main/java/nl/andrewlalis/erme/model/Attribute.java @@ -0,0 +1,45 @@ +package nl.andrewlalis.erme.model; + +import lombok.Getter; + +import java.util.Objects; + +/** + * A single value that belongs to a relation. + */ +@Getter +public class Attribute { + private final Relation relation; + private AttributeType type; + private String name; + + public Attribute(Relation relation, AttributeType type, String name) { + this.relation = relation; + this.type = type; + this.name = name; + } + + public void setType(AttributeType type) { + this.type = type; + this.relation.getModel().fireChangedEvent(); + } + + public void setName(String name) { + this.name = name; + this.relation.getModel().fireChangedEvent(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Attribute attribute = (Attribute) o; + return type == attribute.type && + name.equals(attribute.name); + } + + @Override + public int hashCode() { + return Objects.hash(type, name); + } +} diff --git a/src/main/java/nl/andrewlalis/erme/model/AttributeType.java b/src/main/java/nl/andrewlalis/erme/model/AttributeType.java new file mode 100644 index 0000000..c9bab76 --- /dev/null +++ b/src/main/java/nl/andrewlalis/erme/model/AttributeType.java @@ -0,0 +1,8 @@ +package nl.andrewlalis.erme.model; + +public enum AttributeType { + PLAIN, + ID_KEY, + PARTIAL_ID_KEY, + FOREIGN_KEY +} diff --git a/src/main/java/nl/andrewlalis/erme/model/MappingModel.java b/src/main/java/nl/andrewlalis/erme/model/MappingModel.java new file mode 100644 index 0000000..2747e60 --- /dev/null +++ b/src/main/java/nl/andrewlalis/erme/model/MappingModel.java @@ -0,0 +1,57 @@ +package nl.andrewlalis.erme.model; + +import lombok.Getter; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +/** + * This model contains all the information about a single mapping diagram, + * including each mapped table and the links between them. + */ +public class MappingModel { + @Getter + private final Set relations; + + private final Set changeListeners; + + public MappingModel() { + this.relations = new HashSet<>(); + this.changeListeners = new HashSet<>(); + } + + public void addRelation(Relation r) { + if (this.relations.add(r)) { + this.fireChangedEvent(); + } + } + + public void removeRelation(Relation r) { + if (this.relations.remove(r)) { + this.fireChangedEvent(); + } + } + + public void addChangeListener(ModelChangeListener listener) { + this.changeListeners.add(listener); + listener.onModelChanged(); + } + + protected final void fireChangedEvent() { + this.changeListeners.forEach(ModelChangeListener::onModelChanged); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || this.getClass() != o.getClass()) return false; + MappingModel that = (MappingModel) o; + return this.getRelations().equals(that.getRelations()); + } + + @Override + public int hashCode() { + return Objects.hash(this.getRelations()); + } +} diff --git a/src/main/java/nl/andrewlalis/erme/model/ModelChangeListener.java b/src/main/java/nl/andrewlalis/erme/model/ModelChangeListener.java new file mode 100644 index 0000000..6ec79e0 --- /dev/null +++ b/src/main/java/nl/andrewlalis/erme/model/ModelChangeListener.java @@ -0,0 +1,5 @@ +package nl.andrewlalis.erme.model; + +public interface ModelChangeListener { + void onModelChanged(); +} diff --git a/src/main/java/nl/andrewlalis/erme/model/Relation.java b/src/main/java/nl/andrewlalis/erme/model/Relation.java new file mode 100644 index 0000000..bae96cd --- /dev/null +++ b/src/main/java/nl/andrewlalis/erme/model/Relation.java @@ -0,0 +1,76 @@ +package nl.andrewlalis.erme.model; + +import lombok.Getter; +import nl.andrewlalis.erme.view.view_models.RelationViewModel; + +import java.awt.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Represents a single "relation" or table in the diagram. + */ +@Getter +public class Relation { + private final MappingModel model; + private Point position; + private String name; + private final List attributes; + + private transient boolean selected; + private final transient RelationViewModel viewModel; + + public Relation(MappingModel model, Point position, String name) { + this.model = model; + this.position = position; + this.name = name; + this.attributes = new ArrayList<>(); + this.viewModel = new RelationViewModel(this); + } + + public void setPosition(Point position) { + if (!this.position.equals(position)) { + this.position = position; + this.model.fireChangedEvent(); + } + } + + public void setName(String name) { + if (!this.name.equals(name)) { + this.name = name; + this.model.fireChangedEvent(); + } + } + + public void setSelected(boolean selected) { + if (selected != this.selected) { + this.selected = selected; + this.model.fireChangedEvent(); + } + } + + public void addAttribute(Attribute attribute) { + this.attributes.add(attribute); + this.model.fireChangedEvent(); + } + + public void removeAttribute(Attribute attribute) { + if (this.attributes.remove(attribute)) { + this.model.fireChangedEvent(); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Relation relation = (Relation) o; + return Objects.equals(this.getName(), relation.getName()); + } + + @Override + public int hashCode() { + return this.getName().hashCode(); + } +} diff --git a/src/main/java/nl/andrewlalis/erme/view/DiagramPanel.java b/src/main/java/nl/andrewlalis/erme/view/DiagramPanel.java new file mode 100644 index 0000000..544e70a --- /dev/null +++ b/src/main/java/nl/andrewlalis/erme/view/DiagramPanel.java @@ -0,0 +1,64 @@ +package nl.andrewlalis.erme.view; + +import lombok.Getter; +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.MouseListener; +import java.awt.event.MouseMotionListener; + +/** + * The main panel in which the ER Mapping diagram is displayed. + */ +public class DiagramPanel extends JPanel implements ModelChangeListener { + @Getter + private MappingModel model; + + public DiagramPanel(MappingModel model) { + super(true); + this.setModel(model); + } + + public void setModel(MappingModel newModel) { + this.model = newModel; + newModel.addChangeListener(this); + for (MouseListener listener : this.getMouseListeners()) { + this.removeMouseListener(listener); + } + for (MouseMotionListener listener : this.getMouseMotionListeners()) { + this.removeMouseMotionListener(listener); + } + DiagramMouseListener listener = new DiagramMouseListener(newModel); + this.addMouseListener(listener); + this.addMouseMotionListener(listener); + this.repaint(); + } + + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + new MappingModelViewModel(this.model).draw(this.getGraphics2D(g)); + } + + public Graphics2D getGraphics2D(Graphics g) { + Graphics2D g2d = (Graphics2D) g; + g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g.setFont(g.getFont().deriveFont(14.0f)); + return g2d; + } + + public Graphics2D getGraphics2D() { + return this.getGraphics2D(this.getGraphics()); + } + + @Override + public void onModelChanged() { + this.revalidate(); + this.repaint(); + } +} diff --git a/src/main/java/nl/andrewlalis/erme/view/EditorFrame.java b/src/main/java/nl/andrewlalis/erme/view/EditorFrame.java new file mode 100644 index 0000000..b8b548e --- /dev/null +++ b/src/main/java/nl/andrewlalis/erme/view/EditorFrame.java @@ -0,0 +1,38 @@ +package nl.andrewlalis.erme.view; + +import nl.andrewlalis.erme.model.Attribute; +import nl.andrewlalis.erme.model.AttributeType; +import nl.andrewlalis.erme.model.MappingModel; +import nl.andrewlalis.erme.model.Relation; + +import javax.swing.*; +import java.awt.*; + +/** + * The main JFrame for the editor. + */ +public class EditorFrame extends JFrame { + public EditorFrame() { + super("ER-Mapping Editor"); + MappingModel model = new MappingModel(); + Relation usersRelation = new Relation(model, new Point(50, 50), "Users"); + usersRelation.addAttribute(new Attribute(usersRelation, AttributeType.ID_KEY, "username")); + usersRelation.addAttribute(new Attribute(usersRelation, AttributeType.PLAIN, "fullName")); + usersRelation.addAttribute(new Attribute(usersRelation, AttributeType.PLAIN, "language")); + usersRelation.addAttribute(new Attribute(usersRelation, AttributeType.PLAIN, "verified")); + usersRelation.addAttribute(new Attribute(usersRelation, AttributeType.PARTIAL_ID_KEY, "partialKey")); + model.addRelation(usersRelation); + Relation tokensRelation = new Relation(model, new Point(50, 120), "Tokens"); + tokensRelation.addAttribute(new Attribute(tokensRelation, AttributeType.ID_KEY, "tokenCode")); + tokensRelation.addAttribute(new Attribute(tokensRelation, AttributeType.FOREIGN_KEY, "username")); + tokensRelation.addAttribute(new Attribute(tokensRelation, AttributeType.PLAIN, "expirationDate")); + model.addRelation(tokensRelation); + + this.setContentPane(new DiagramPanel(model)); + this.setJMenuBar(new EditorMenuBar()); + this.setMinimumSize(new Dimension(500, 500)); + this.pack(); + this.setDefaultCloseOperation(DISPOSE_ON_CLOSE); + this.setLocationRelativeTo(null); + } +} diff --git a/src/main/java/nl/andrewlalis/erme/view/EditorMenuBar.java b/src/main/java/nl/andrewlalis/erme/view/EditorMenuBar.java new file mode 100644 index 0000000..55cdf9a --- /dev/null +++ b/src/main/java/nl/andrewlalis/erme/view/EditorMenuBar.java @@ -0,0 +1,36 @@ +package nl.andrewlalis.erme.view; + +import nl.andrewlalis.erme.control.actions.ExitAction; +import nl.andrewlalis.erme.control.actions.ExportToImageAction; +import nl.andrewlalis.erme.control.actions.RedoAction; +import nl.andrewlalis.erme.control.actions.UndoAction; + +import javax.swing.*; + +/** + * The menu bar that's visible atop the application. + */ +public class EditorMenuBar extends JMenuBar { + public EditorMenuBar() { + this.add(this.buildFileMenu()); + this.add(this.buildEditMenu()); + } + + private JMenu buildFileMenu() { + JMenu menu = new JMenu("File"); + JMenuItem exportAsImageItem = new JMenuItem(ExportToImageAction.getInstance()); + menu.add(exportAsImageItem); + JMenuItem exitItem = new JMenuItem(ExitAction.getInstance()); + menu.add(exitItem); + return menu; + } + + private JMenu buildEditMenu() { + JMenu menu = new JMenu("Edit"); + JMenuItem undoItem = new JMenuItem(UndoAction.getInstance()); + menu.add(undoItem); + JMenuItem redoItem = new JMenuItem(RedoAction.getInstance()); + menu.add(redoItem); + return menu; + } +} diff --git a/src/main/java/nl/andrewlalis/erme/view/view_models/AttributeViewModel.java b/src/main/java/nl/andrewlalis/erme/view/view_models/AttributeViewModel.java new file mode 100644 index 0000000..ee44f3d --- /dev/null +++ b/src/main/java/nl/andrewlalis/erme/view/view_models/AttributeViewModel.java @@ -0,0 +1,65 @@ +package nl.andrewlalis.erme.view.view_models; + +import nl.andrewlalis.erme.model.Attribute; +import nl.andrewlalis.erme.model.AttributeType; + +import java.awt.*; +import java.awt.font.TextAttribute; +import java.awt.geom.Rectangle2D; +import java.text.AttributedString; + +public class AttributeViewModel implements ViewModel { + public static final int PADDING_X = 5; + public static final int PADDING_Y = 5; + public static final Color BACKGROUND_COLOR = Color.LIGHT_GRAY; + public static final Color FONT_COLOR = Color.BLACK; + + private final Attribute attribute; + + public AttributeViewModel(Attribute attribute) { + this.attribute = attribute; + } + + @Override + public void draw(Graphics2D g) { + AttributedString as = this.getAttributedString(g); + Rectangle r = this.getBounds(g, as); + g.setColor(BACKGROUND_COLOR); + g.fillRect(r.x, r.y, r.width, r.height); + g.setColor(FONT_COLOR); + g.drawRect(r.x, r.y, r.width, r.height); + g.drawString(as.getIterator(), r.x + PADDING_X, r.y + (r.height - PADDING_Y)); + } + + private Rectangle getBounds(Graphics2D g, AttributedString as) { + int x = this.attribute.getRelation().getPosition().x + RelationViewModel.PADDING_X; + int y = this.attribute.getRelation().getPosition().y + this.attribute.getRelation().getViewModel().getNameBounds(g).height + PADDING_Y; + int i = 0; + while (!this.attribute.getRelation().getAttributes().get(i).equals(this.attribute)) { + x += g.getFontMetrics().stringWidth(this.attribute.getRelation().getAttributes().get(i).getName()) + (2 * PADDING_X); + i++; + } + Rectangle2D rect = g.getFontMetrics().getStringBounds(as.getIterator(), 0, this.attribute.getName().length(), g); + return new Rectangle( + x, + y, + (int) rect.getWidth() + (2 * PADDING_X), + (int) rect.getHeight() + (2 * PADDING_Y) + ); + } + + public Rectangle getBounds(Graphics2D g) { + return this.getBounds(g, this.getAttributedString(g)); + } + + private AttributedString getAttributedString(Graphics2D g) { + AttributedString as = new AttributedString(this.attribute.getName()); + as.addAttribute(TextAttribute.FONT, g.getFont()); + if (this.attribute.getType().equals(AttributeType.ID_KEY)) { + as.addAttribute(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON); + } else if (this.attribute.getType().equals(AttributeType.PARTIAL_ID_KEY)) { + as.addAttribute(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_LOW_DASHED); + } + return as; + } +} diff --git a/src/main/java/nl/andrewlalis/erme/view/view_models/MappingModelViewModel.java b/src/main/java/nl/andrewlalis/erme/view/view_models/MappingModelViewModel.java new file mode 100644 index 0000000..a488402 --- /dev/null +++ b/src/main/java/nl/andrewlalis/erme/view/view_models/MappingModelViewModel.java @@ -0,0 +1,21 @@ +package nl.andrewlalis.erme.view.view_models; + +import nl.andrewlalis.erme.model.MappingModel; +import nl.andrewlalis.erme.model.Relation; + +import java.awt.*; + +public class MappingModelViewModel implements ViewModel { + private final MappingModel model; + + public MappingModelViewModel(MappingModel model) { + this.model = model; + } + + @Override + public void draw(Graphics2D g) { + for (Relation r : this.model.getRelations()) { + r.getViewModel().draw(g); + } + } +} diff --git a/src/main/java/nl/andrewlalis/erme/view/view_models/RelationViewModel.java b/src/main/java/nl/andrewlalis/erme/view/view_models/RelationViewModel.java new file mode 100644 index 0000000..6ddc081 --- /dev/null +++ b/src/main/java/nl/andrewlalis/erme/view/view_models/RelationViewModel.java @@ -0,0 +1,67 @@ +package nl.andrewlalis.erme.view.view_models; + +import nl.andrewlalis.erme.model.Attribute; +import nl.andrewlalis.erme.model.Relation; + +import java.awt.*; +import java.awt.font.TextAttribute; +import java.awt.geom.Rectangle2D; +import java.text.AttributedString; + +public class RelationViewModel implements ViewModel { + public static final int PADDING_X = 5; + public static final int PADDING_Y = 5; + + private final Relation relation; + + public RelationViewModel(Relation relation) { + this.relation = relation; + } + + @Override + public void draw(Graphics2D g) { + AttributedString as = this.getAttributedString(g); + Rectangle bounds = this.getBounds(g); + g.setColor(Color.BLACK); + g.drawString(as.getIterator(), bounds.x + PADDING_X, bounds.y + this.getNameBounds(g).height - PADDING_Y); + for (Attribute a : this.relation.getAttributes()) { + new AttributeViewModel(a).draw(g); + } + if (this.relation.isSelected()) { + g.setColor(Color.BLUE); + } else { + g.setColor(Color.CYAN); + } + g.drawRect(bounds.x, bounds.y, bounds.width, bounds.height); + } + + public Rectangle getBounds(Graphics2D g) { + Rectangle rect = new Rectangle(); + rect.x = this.relation.getPosition().x; + rect.y = this.relation.getPosition().y; + int totalAttributeWidth = 0; + int maxAttributeHeight = 0; + for (Attribute a : this.relation.getAttributes()) { + Rectangle attributeBounds = new AttributeViewModel(a).getBounds(g); + totalAttributeWidth += attributeBounds.width; + maxAttributeHeight = Math.max(maxAttributeHeight, attributeBounds.height); + } + Rectangle nameBounds = this.getNameBounds(g); + rect.width = Math.max(totalAttributeWidth, nameBounds.width) + (2 * PADDING_X); + rect.height = nameBounds.height + maxAttributeHeight + (2 * PADDING_Y); + return rect; + } + + 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(); + } + + private AttributedString getAttributedString(Graphics2D g) { + AttributedString as = new AttributedString(this.relation.getName()); + as.addAttribute(TextAttribute.FONT, g.getFont()); + as.addAttribute(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD); + return as; + } +} diff --git a/src/main/java/nl/andrewlalis/erme/view/view_models/ViewModel.java b/src/main/java/nl/andrewlalis/erme/view/view_models/ViewModel.java new file mode 100644 index 0000000..e3d8989 --- /dev/null +++ b/src/main/java/nl/andrewlalis/erme/view/view_models/ViewModel.java @@ -0,0 +1,7 @@ +package nl.andrewlalis.erme.view.view_models; + +import java.awt.*; + +public interface ViewModel { + void draw(Graphics2D g); +}