Added base UI

This commit is contained in:
Andrew Lalis 2021-02-07 15:28:23 +01:00
parent af5928d24a
commit 8e8b8d3302
20 changed files with 827 additions and 0 deletions

115
.gitignore vendored Normal file
View File

@ -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

36
pom.xml Normal file
View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<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>
<groupId>nl.andrewlalis</groupId>
<artifactId>EntityRelationMappingEditor</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>com.formdev</groupId>
<artifactId>flatlaf</artifactId>
<version>1.0-rc3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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.");
}
}

View File

@ -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");
}
}

View File

@ -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");
}
}

View File

@ -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) {
}
}

View File

@ -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);
}
}

View File

@ -0,0 +1,8 @@
package nl.andrewlalis.erme.model;
public enum AttributeType {
PLAIN,
ID_KEY,
PARTIAL_ID_KEY,
FOREIGN_KEY
}

View File

@ -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<Relation> relations;
private final Set<ModelChangeListener> 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());
}
}

View File

@ -0,0 +1,5 @@
package nl.andrewlalis.erme.model;
public interface ModelChangeListener {
void onModelChanged();
}

View File

@ -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<Attribute> 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();
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,7 @@
package nl.andrewlalis.erme.view.view_models;
import java.awt.*;
public interface ViewModel {
void draw(Graphics2D g);
}