From faadc40d265f964c3463e29aecd6b9cdd92e4475 Mon Sep 17 00:00:00 2001 From: Bjorn Pijnacker Date: Fri, 26 Feb 2021 21:36:18 +0100 Subject: [PATCH] Implemented custom order alignment - Implemented a custom model for a JList that allows for reordering elements using JButtons - Ask the user whether to sort alphabetically or using an order they decide - Ensured that the list in the OrderableListPanel keeps its order between multiple sorting times (this saves the user the work of re-sorting every time) --- .../control/actions/AutoPositionAction.java | 83 ++++++++++++++----- .../andrewlalis/erme/model/MappingModel.java | 2 + .../andrewlalis/erme/view/DiagramPanel.java | 1 + .../erme/view/OrderableListModel.java | 59 +++++++++++-- .../erme/view/OrderableListPanel.java | 82 ++++++++++++++++++ 5 files changed, 201 insertions(+), 26 deletions(-) create mode 100644 src/main/java/nl/andrewlalis/erme/view/OrderableListPanel.java diff --git a/src/main/java/nl/andrewlalis/erme/control/actions/AutoPositionAction.java b/src/main/java/nl/andrewlalis/erme/control/actions/AutoPositionAction.java index 3b46629..04ff7ef 100644 --- a/src/main/java/nl/andrewlalis/erme/control/actions/AutoPositionAction.java +++ b/src/main/java/nl/andrewlalis/erme/control/actions/AutoPositionAction.java @@ -1,10 +1,10 @@ package nl.andrewlalis.erme.control.actions; -import lombok.Getter; 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.*; @@ -13,10 +13,22 @@ 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 static AutoPositionAction instance; 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("Position Relations"); + this.putValue(SHORT_DESCRIPTION, "Automatically Position Relations"); + } public static AutoPositionAction getInstance() { if (instance == null) { @@ -25,32 +37,63 @@ public class AutoPositionAction extends AbstractAction { return instance; } - @Setter - private DiagramPanel diagramPanel; - @Setter - private MappingModel model; - - public AutoPositionAction() { - super("Position Relations"); - this.putValue(SHORT_DESCRIPTION, "Automatically Position Relations"); - } - @Override public void actionPerformed(ActionEvent actionEvent) { - this.positionRelations(); + if (model.getRelations().size() == 0) { + JOptionPane.showMessageDialog( + null, + "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( + null, + "Select how to sort the relations", + "Position Relations", + JOptionPane.PLAIN_MESSAGE, + null, + choices, + 0); + if (choice == null) return; + if (choice.equals(choices[0])) { + positionRelation(); + } else if (choice.equals(choices[1])) { + JOptionPane.showConfirmDialog( + null, + OrderableListPanel.getInstance(), + "teststring", + JOptionPane.OK_CANCEL_OPTION, + JOptionPane.PLAIN_MESSAGE); + this.positionRelations(OrderableListPanel.getInstance().getOrderList()); + } diagramPanel.repaint(); } - private void positionRelations() { - diagramPanel.resetTranslation(); - ArrayList relationList = new ArrayList<>(model.getRelations()); - Collections.sort(relationList); - if (relationList.isEmpty()) return; + /** + * 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; - int vertSpace = (int) relationList.get(0).getViewModel().getBounds(diagramPanel.getGraphics2D()).getHeight() + PADDING; + int vertSpace = (int) orderList.get(0).getViewModel().getBounds(diagramPanel.getGraphics2D()).getHeight() + PADDING; AtomicInteger vertPos = new AtomicInteger(MARGIN); - relationList.forEach(r -> { + orderList.forEach(r -> { r.setPosition(new Point(MARGIN, vertPos.getAndAdd(vertSpace))); }); + + diagramPanel.centerModel(); + } + + /** + * Creates an orderList by grabbing all relations and sorting them + */ + private void positionRelation() { + ArrayList relationList = new ArrayList<>(model.getRelations()); + Collections.sort(relationList); + positionRelations(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..9fc16da 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; @@ -27,6 +28,7 @@ public class MappingModel implements Serializable, Viewable { public MappingModel() { this.relations = new HashSet<>(); this.changeListeners = new HashSet<>(); + this.addChangeListener(OrderableListPanel.getInstance()); } public void addRelation(Relation r) { diff --git a/src/main/java/nl/andrewlalis/erme/view/DiagramPanel.java b/src/main/java/nl/andrewlalis/erme/view/DiagramPanel.java index ba57981..d2ebd37 100644 --- a/src/main/java/nl/andrewlalis/erme/view/DiagramPanel.java +++ b/src/main/java/nl/andrewlalis/erme/view/DiagramPanel.java @@ -131,6 +131,7 @@ public class DiagramPanel extends JPanel implements ModelChangeListener { LolcatAction.getInstance().setDiagramPanel(this); AutoPositionAction.getInstance().setDiagramPanel(this); AutoPositionAction.getInstance().setModel(this.model); + OrderableListPanel.getInstance().setModel(this.model); } public static void prepareGraphics(Graphics2D g) { diff --git a/src/main/java/nl/andrewlalis/erme/view/OrderableListModel.java b/src/main/java/nl/andrewlalis/erme/view/OrderableListModel.java index c3d29c9..dab45b2 100644 --- a/src/main/java/nl/andrewlalis/erme/view/OrderableListModel.java +++ b/src/main/java/nl/andrewlalis/erme/view/OrderableListModel.java @@ -1,16 +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; -public class OrderableListModel { - private ArrayList list; +/** + * 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 void moveUp(int index) { - if (index > 0) { - Collections.swap(list, index, index - 1); + 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..427bdca --- /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()); + } +}