diff --git a/src/main/java/nl/andrewlalis/erme/control/actions/AutoPositionAction.java b/src/main/java/nl/andrewlalis/erme/control/actions/AutoPositionAction.java new file mode 100644 index 0000000..cfe5d56 --- /dev/null +++ b/src/main/java/nl/andrewlalis/erme/control/actions/AutoPositionAction.java @@ -0,0 +1,103 @@ +package nl.andrewlalis.erme.control.actions; + +import lombok.Setter; +import nl.andrewlalis.erme.model.MappingModel; +import nl.andrewlalis.erme.model.Relation; +import nl.andrewlalis.erme.view.DiagramPanel; +import nl.andrewlalis.erme.view.OrderableListPanel; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.util.ArrayList; +import java.util.Collections; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * A class that implements an AutoPositionAction. This automatically (vertically) positions all relations in the model + * based either on alphabetic ordering (by name) or an order that's set by the user + */ +public class AutoPositionAction extends AbstractAction { + private final static int MARGIN = 10; + private final static int PADDING = 10; + private static AutoPositionAction instance; + @Setter + private DiagramPanel diagramPanel; + @Setter + private MappingModel model; + public AutoPositionAction() { + super("Align Relations"); + this.putValue(SHORT_DESCRIPTION, "Automatically Align Relations"); + } + + public static AutoPositionAction getInstance() { + if (instance == null) { + instance = new AutoPositionAction(); + } + return instance; + } + + @Override + public void actionPerformed(ActionEvent actionEvent) { + if (model.getRelations().size() == 0) { + JOptionPane.showMessageDialog( + this.diagramPanel, + "Cannot position all relations when there are no relations present", + "Relations Required", + JOptionPane.WARNING_MESSAGE + ); + return; + } + String[] choices = new String[]{"Alphabeticaly", "Custom Order"}; + String choice = (String) JOptionPane.showInputDialog( + this.diagramPanel, + "Select how to sort the relations", + "Position Relations", + JOptionPane.PLAIN_MESSAGE, + null, + choices, + 0); + if (choice == null) return; + if (choice.equals(choices[0])) { + positionRelations(getAlphabeticRelationList()); + } else if (choice.equals(choices[1])) { + JOptionPane.showConfirmDialog( + this.diagramPanel, + OrderableListPanel.getInstance(), + "teststring", + JOptionPane.OK_CANCEL_OPTION, + JOptionPane.PLAIN_MESSAGE); + this.positionRelations(OrderableListPanel.getInstance().getOrderList()); + } + diagramPanel.repaint(); + } + + /** + * Sets the location on all relations in the orderList to be vertically aligned + * @param orderList The ordered list of relations to be aligned + */ + private void positionRelations(ArrayList orderList) { + if (orderList.isEmpty()) return; + AtomicInteger vertSpace = new AtomicInteger(0); + orderList.forEach(r -> { + int height = (int) r.getViewModel().getBounds(diagramPanel.getGraphics2D()).getHeight(); + vertSpace.set(Math.max(vertSpace.get(), height)); + }); + vertSpace.addAndGet(PADDING); + AtomicInteger vertPos = new AtomicInteger(MARGIN); + orderList.forEach(r -> { + r.setPosition(new Point(MARGIN, vertPos.getAndAdd(vertSpace.get()))); + }); + + diagramPanel.centerModel(); + } + + /** + * Creates an orderList by grabbing all relations and sorting them + */ + private ArrayList getAlphabeticRelationList() { + ArrayList relationList = new ArrayList<>(model.getRelations()); + Collections.sort(relationList); + return relationList; + } +} diff --git a/src/main/java/nl/andrewlalis/erme/model/MappingModel.java b/src/main/java/nl/andrewlalis/erme/model/MappingModel.java index b7ed500..b7763d0 100644 --- a/src/main/java/nl/andrewlalis/erme/model/MappingModel.java +++ b/src/main/java/nl/andrewlalis/erme/model/MappingModel.java @@ -1,6 +1,7 @@ package nl.andrewlalis.erme.model; import lombok.Getter; +import nl.andrewlalis.erme.view.OrderableListPanel; import nl.andrewlalis.erme.view.view_models.MappingModelViewModel; import nl.andrewlalis.erme.view.view_models.ViewModel; diff --git a/src/main/java/nl/andrewlalis/erme/model/Relation.java b/src/main/java/nl/andrewlalis/erme/model/Relation.java index 05c0e66..8393002 100644 --- a/src/main/java/nl/andrewlalis/erme/model/Relation.java +++ b/src/main/java/nl/andrewlalis/erme/model/Relation.java @@ -15,7 +15,7 @@ import java.util.stream.Collectors; * Represents a single "relation" or table in the diagram. */ @Getter -public class Relation implements Serializable, Viewable { +public class Relation implements Serializable, Viewable, Comparable { private final MappingModel model; private Point position; private String name; @@ -97,4 +97,9 @@ public class Relation implements Serializable, Viewable { this.getAttributes().forEach(a -> c.addAttribute(a.copy(c))); return c; } + + @Override + public int compareTo(Relation relation) { + return this.name.compareTo(relation.name); + } } diff --git a/src/main/java/nl/andrewlalis/erme/view/DiagramPanel.java b/src/main/java/nl/andrewlalis/erme/view/DiagramPanel.java index 3eb46e2..5e4e8b6 100644 --- a/src/main/java/nl/andrewlalis/erme/view/DiagramPanel.java +++ b/src/main/java/nl/andrewlalis/erme/view/DiagramPanel.java @@ -64,6 +64,7 @@ public class DiagramPanel extends JPanel implements ModelChangeListener { this.addMouseListener(listener); this.addMouseMotionListener(listener); this.updateActionModels(); + newModel.addChangeListener(OrderableListPanel.getInstance()); this.centerModel(); this.repaint(); } @@ -133,7 +134,10 @@ public class DiagramPanel extends JPanel implements ModelChangeListener { RemoveAttributeAction.getInstance().setDiagramPanel(this); LoadSampleModelAction.getInstance().setDiagramPanel(this); LolcatAction.getInstance().setDiagramPanel(this); - AboutAction.getInstance().setDiagramPanel(this); + AutoPositionAction.getInstance().setDiagramPanel(this); + AutoPositionAction.getInstance().setModel(this.model); + OrderableListPanel.getInstance().setModel(this.model); + AboutAction.getInstance().setDiagramPanel(this); ExitAction.getInstance().setDiagramPanel(this); InstructionsAction.getInstance().setDiagramPanel(this); MappingAlgorithmHelpAction.getInstance().setDiagramPanel(this); diff --git a/src/main/java/nl/andrewlalis/erme/view/EditorMenuBar.java b/src/main/java/nl/andrewlalis/erme/view/EditorMenuBar.java index 8710c3d..afd76fa 100644 --- a/src/main/java/nl/andrewlalis/erme/view/EditorMenuBar.java +++ b/src/main/java/nl/andrewlalis/erme/view/EditorMenuBar.java @@ -40,6 +40,7 @@ public class EditorMenuBar extends JMenuBar { menu.add(AddAttributeAction.getInstance()); menu.add(RemoveAttributeAction.getInstance()); menu.add(new JCheckBoxMenuItem(LolcatAction.getInstance())); + menu.add(AutoPositionAction.getInstance()); menu.addSeparator(); menu.add(UndoAction.getInstance()); menu.add(RedoAction.getInstance()); diff --git a/src/main/java/nl/andrewlalis/erme/view/OrderableListModel.java b/src/main/java/nl/andrewlalis/erme/view/OrderableListModel.java new file mode 100644 index 0000000..dab45b2 --- /dev/null +++ b/src/main/java/nl/andrewlalis/erme/view/OrderableListModel.java @@ -0,0 +1,63 @@ +package nl.andrewlalis.erme.view; + +import lombok.Getter; +import nl.andrewlalis.erme.model.Relation; + +import javax.swing.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; + +/** + * A custom model for a ListModel that supports ordering by publically accessable methods + */ +public class OrderableListModel extends AbstractListModel { + @Getter + private final ArrayList list = new ArrayList<>(); + + public OrderableListModel() { + super(); + } + + public void addAll(Collection relations) { + list.addAll(relations); + } + + public void removeAll(Collection relations) { + list.removeAll(relations); + } + + public void add(Relation relation) { + list.add(relation); + } + + public void remove(Relation relation) { + list.remove(relation); + } + + public void moveUp(int index) { + if (index > 0) { + Collections.swap(list, index, index - 1); + } + + this.fireContentsChanged(this, index, index - 1); + } + + public void moveDown(int index) { + if (index < this.getSize() - 1) { + Collections.swap(list, index, index + 1); + } + + this.fireContentsChanged(this, index, index + 1); + } + + @Override + public int getSize() { + return list.size(); + } + + @Override + public Relation getElementAt(int i) { + return list.get(i); + } +} diff --git a/src/main/java/nl/andrewlalis/erme/view/OrderableListPanel.java b/src/main/java/nl/andrewlalis/erme/view/OrderableListPanel.java new file mode 100644 index 0000000..7731e65 --- /dev/null +++ b/src/main/java/nl/andrewlalis/erme/view/OrderableListPanel.java @@ -0,0 +1,82 @@ +package nl.andrewlalis.erme.view; + +import lombok.Setter; +import nl.andrewlalis.erme.model.MappingModel; +import nl.andrewlalis.erme.model.ModelChangeListener; +import nl.andrewlalis.erme.model.Relation; + +import javax.swing.*; +import java.awt.event.ActionEvent; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +/** + * A panel to be used in a popup that has a OrderableListModel implemented. Implements ModelChangeListener to be able + * to update the list of relations when new ones are added or ones are removed. + */ +public class OrderableListPanel extends JPanel implements ModelChangeListener { + private static OrderableListPanel instance; + private final JList list; + private final OrderableListModel listModel; + @Setter + private MappingModel model; + + private OrderableListPanel() { + list = new JList<>(); + listModel = new OrderableListModel(); + list.setModel(listModel); + + JButton up = new JButton(new AbstractAction() { + @Override + public void actionPerformed(ActionEvent actionEvent) { + if (list.isSelectionEmpty()) return; + listModel.moveUp(list.getSelectedIndex()); + list.setSelectedIndex(list.getSelectedIndex() - 1); + } + }); + up.setText("\u2227"); + JButton down = new JButton(new AbstractAction() { + @Override + public void actionPerformed(ActionEvent actionEvent) { + if (list.isSelectionEmpty()) return; + listModel.moveDown(list.getSelectedIndex()); + list.setSelectedIndex(list.getSelectedIndex() + 1); + } + }); + down.setText("\u2228"); + + this.add(list); + this.add(up); + this.add(down); + } + + public static OrderableListPanel getInstance() { + if (instance == null) { + instance = new OrderableListPanel(); + } + return instance; + } + + /** + * Updates removed and new relations in the listModel. Does it in a special way to preserve existing ordering in the + * list, so user has to do minimal re-sorting. + */ + @Override + public void onModelChanged() { + if (this.model == null) return; + Set newRelations = new HashSet<>(model.getRelations()); + newRelations.removeAll(listModel.getList()); + + Set removedRelations = new HashSet<>(listModel.getList()); + removedRelations.removeAll(model.getRelations()); + + listModel.removeAll(removedRelations); + listModel.addAll(newRelations); + } + + public ArrayList getOrderList() { + return new ArrayList<>(listModel.getList()); + } +}