package ru.batrdmi.svnplugin;
import com.mxgraph.model.mxGeometry;
import ru.batrdmi.svnplugin.logic.Revision;
import java.awt.event.KeyEvent;
import java.util.*;
public class KeyboardNavigator {
private static final double PRECISION = 1e-3;
private final SVNRevisionGraph graph;
private final Map<Integer, KeyProcessor> processors = new HashMap<Integer, KeyProcessor>();
interface KeyProcessor {
Revision moveFrom(RevisionGraphModel.Node r);
}
public KeyboardNavigator(SVNRevisionGraph graph) {
this.graph = graph;
processors.put(KeyEvent.VK_UP, new KeyProcessor() {
@Override
public Revision moveFrom(RevisionGraphModel.Node r) {
return moveUp(r);
}
});
processors.put(KeyEvent.VK_DOWN, new KeyProcessor() {
@Override
public Revision moveFrom(RevisionGraphModel.Node r) {
return moveDown(r);
}
});
processors.put(KeyEvent.VK_LEFT, new KeyProcessor() {
@Override
public Revision moveFrom(RevisionGraphModel.Node r) {
return moveLeft(r);
}
});
processors.put(KeyEvent.VK_RIGHT, new KeyProcessor() {
@Override
public Revision moveFrom(RevisionGraphModel.Node r) {
return moveRight(r);
}
});
}
public boolean processEvent(KeyEvent e){
KeyProcessor kp = processors.get(e.getKeyCode());
if (kp == null) {
return false;
}
List<RevisionGraphModel.Node> selectedRevs = graph.getModel().getSelectedNodes();
if (selectedRevs.size() != 1) {
return false;
}
Revision targetRevision = kp.moveFrom(selectedRevs.get(0));
if (targetRevision != null) {
graph.selectAndShowRevision(targetRevision);
}
return true;
}
private Revision moveUp(RevisionGraphModel.Node currentNode) {
Revision current = currentNode.revisions.get(0);
RevisionGraphModel model = graph.getModel();
Set<Revision> linkedRevs = model.getLinkedRevisions(current);
if (linkedRevs.isEmpty()) {
graph.moveUpRange();
return null;
}
Revision result = null;
double currY = model.getCellForRevision(current).getGeometry().getY();
Comparator<Revision> comparator = new LinkedRevisionsByDistance(current, false);
for (Revision r : linkedRevs) {
if ((model.getCellForRevision(r).getGeometry().getY() - currY < PRECISION)
&& (result == null || comparator.compare(result, r) > 0)) {
result = r;
}
}
return result;
}
private Revision moveDown(RevisionGraphModel.Node currentNode) {
Revision current = currentNode.revisions.get(0);
RevisionGraphModel model = graph.getModel();
Set<Revision> linkedRevs = model.getLinkedRevisions(current);
if (linkedRevs.isEmpty()) {
graph.moveDownRange();
return null;
}
Revision result = null;
double currY = model.getCellForRevision(current).getGeometry().getY();
Comparator<Revision> comparator = new LinkedRevisionsByDistance(current, true);
for (Revision r : linkedRevs) {
if ((model.getCellForRevision(r).getGeometry().getY() - currY > PRECISION)
&& (result == null || comparator.compare(result, r) > 0)) {
result = r;
}
}
return (result == null) ? current : result;
}
private Revision moveLeft(RevisionGraphModel.Node currentNode) {
Revision current = currentNode.revisions.get(0);
List<Revision> revsOnPath = graph.getModel().getRevisionsForPath(current.getRelPath());
ListIterator<Revision> it = revsOnPath.listIterator(revsOnPath.size());
while (it.hasPrevious()) {
Revision r = it.previous();
if (r.equals(current) && it.hasPrevious()) {
return it.previous();
}
}
return null;
}
private Revision moveRight(RevisionGraphModel.Node currentNode) {
Revision current = currentNode.revisions.get(currentNode.revisions.size() - 1);
List<Revision> revsOnPath = graph.getModel().getRevisionsForPath(current.getRelPath());
Iterator<Revision> it = revsOnPath.iterator();
while (it.hasNext()) {
Revision r = it.next();
if (r.equals(current) && it.hasNext()) {
return it.next();
}
}
return null;
}
private class LinkedRevisionsByDistance implements Comparator<Revision> {
private final Revision reference;
private final boolean latestIsCloser;
public LinkedRevisionsByDistance(Revision reference, boolean latestIsCloser) {
this.reference = reference;
this.latestIsCloser = latestIsCloser;
}
@Override
public int compare(Revision r1, Revision r2) {
RevisionGraphModel model = graph.getModel();
mxGeometry g = model.getCellForRevision(reference).getGeometry();
mxGeometry g1 = model.getCellForRevision(r1).getGeometry();
mxGeometry g2 = model.getCellForRevision(r2).getGeometry();
double diffX = Math.abs(g1.getX() - g.getX()) - Math.abs(g2.getX() - g.getX());
if (Math.abs(diffX) > PRECISION) {
return (diffX > 0) ? 1 : -1;
}
double diffY = Math.abs(g1.getY() - g.getY()) - Math.abs(g2.getY() - g.getY());
if (Math.abs(diffY) > PRECISION) {
return (diffY > 0) ? 1 : -1;
}
return (latestIsCloser ^ (r1.getRevisionNumber() > reference.getRevisionNumber())) ? 1 : -1;
}
}
}