Merge pull request #10 from bjornpijnacker/auto-position

Implemented Automatic Alignment
This commit is contained in:
Andrew Lalis 2021-02-27 20:39:14 +01:00 committed by GitHub
commit a8966d6051
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 261 additions and 2 deletions

View File

@ -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<Relation> 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<Relation> getAlphabeticRelationList() {
ArrayList<Relation> relationList = new ArrayList<>(model.getRelations());
Collections.sort(relationList);
return relationList;
}
}

View File

@ -1,6 +1,7 @@
package nl.andrewlalis.erme.model; package nl.andrewlalis.erme.model;
import lombok.Getter; import lombok.Getter;
import nl.andrewlalis.erme.view.OrderableListPanel;
import nl.andrewlalis.erme.view.view_models.MappingModelViewModel; import nl.andrewlalis.erme.view.view_models.MappingModelViewModel;
import nl.andrewlalis.erme.view.view_models.ViewModel; import nl.andrewlalis.erme.view.view_models.ViewModel;

View File

@ -15,7 +15,7 @@ import java.util.stream.Collectors;
* Represents a single "relation" or table in the diagram. * Represents a single "relation" or table in the diagram.
*/ */
@Getter @Getter
public class Relation implements Serializable, Viewable { public class Relation implements Serializable, Viewable, Comparable<Relation> {
private final MappingModel model; private final MappingModel model;
private Point position; private Point position;
private String name; private String name;
@ -97,4 +97,9 @@ public class Relation implements Serializable, Viewable {
this.getAttributes().forEach(a -> c.addAttribute(a.copy(c))); this.getAttributes().forEach(a -> c.addAttribute(a.copy(c)));
return c; return c;
} }
@Override
public int compareTo(Relation relation) {
return this.name.compareTo(relation.name);
}
} }

View File

@ -64,6 +64,7 @@ public class DiagramPanel extends JPanel implements ModelChangeListener {
this.addMouseListener(listener); this.addMouseListener(listener);
this.addMouseMotionListener(listener); this.addMouseMotionListener(listener);
this.updateActionModels(); this.updateActionModels();
newModel.addChangeListener(OrderableListPanel.getInstance());
this.centerModel(); this.centerModel();
this.repaint(); this.repaint();
} }
@ -133,6 +134,9 @@ public class DiagramPanel extends JPanel implements ModelChangeListener {
RemoveAttributeAction.getInstance().setDiagramPanel(this); RemoveAttributeAction.getInstance().setDiagramPanel(this);
LoadSampleModelAction.getInstance().setDiagramPanel(this); LoadSampleModelAction.getInstance().setDiagramPanel(this);
LolcatAction.getInstance().setDiagramPanel(this); LolcatAction.getInstance().setDiagramPanel(this);
AutoPositionAction.getInstance().setDiagramPanel(this);
AutoPositionAction.getInstance().setModel(this.model);
OrderableListPanel.getInstance().setModel(this.model);
AboutAction.getInstance().setDiagramPanel(this); AboutAction.getInstance().setDiagramPanel(this);
ExitAction.getInstance().setDiagramPanel(this); ExitAction.getInstance().setDiagramPanel(this);
InstructionsAction.getInstance().setDiagramPanel(this); InstructionsAction.getInstance().setDiagramPanel(this);

View File

@ -40,6 +40,7 @@ public class EditorMenuBar extends JMenuBar {
menu.add(AddAttributeAction.getInstance()); menu.add(AddAttributeAction.getInstance());
menu.add(RemoveAttributeAction.getInstance()); menu.add(RemoveAttributeAction.getInstance());
menu.add(new JCheckBoxMenuItem(LolcatAction.getInstance())); menu.add(new JCheckBoxMenuItem(LolcatAction.getInstance()));
menu.add(AutoPositionAction.getInstance());
menu.addSeparator(); menu.addSeparator();
menu.add(UndoAction.getInstance()); menu.add(UndoAction.getInstance());
menu.add(RedoAction.getInstance()); menu.add(RedoAction.getInstance());

View File

@ -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<Relation> {
@Getter
private final ArrayList<Relation> list = new ArrayList<>();
public OrderableListModel() {
super();
}
public void addAll(Collection<Relation> relations) {
list.addAll(relations);
}
public void removeAll(Collection<Relation> 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);
}
}

View File

@ -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<Relation> 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<Relation> newRelations = new HashSet<>(model.getRelations());
newRelations.removeAll(listModel.getList());
Set<Relation> removedRelations = new HashSet<>(listModel.getList());
removedRelations.removeAll(model.getRelations());
listModel.removeAll(removedRelations);
listModel.addAll(newRelations);
}
public ArrayList<Relation> getOrderList() {
return new ArrayList<>(listModel.getList());
}
}