improve move to next/previous page

Signed-off-by: Florian Thienel <florian@thienel.org>
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/ContentTopology.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/ContentTopology.java
index 6353811..62b9cfc 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/ContentTopology.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/ContentTopology.java
@@ -11,7 +11,9 @@
 package org.eclipse.vex.core.internal.cursor;
 
 import java.util.Collection;
+import java.util.Iterator;
 import java.util.LinkedList;
+import java.util.List;
 
 import org.eclipse.vex.core.internal.boxes.BaseBoxVisitorWithResult;
 import org.eclipse.vex.core.internal.boxes.DepthFirstBoxTraversal;
@@ -334,6 +336,118 @@
 		});
 	}
 
+	public static IContentBox findClosestContentBoxChildBelow(final IContentBox parent, final int x, final int y) {
+		final Iterable<IContentBox> candidates = findVerticallyClosestContentBoxChildrenBelow(parent, y);
+		return findHorizontallyClosestContentBox(candidates, x);
+	}
+
+	public static Iterable<IContentBox> findVerticallyClosestContentBoxChildrenBelow(final IContentBox parent, final int y) {
+		final LinkedList<IContentBox> candidates = new LinkedList<IContentBox>();
+		final int[] minVerticalDistance = new int[1];
+		minVerticalDistance[0] = Integer.MAX_VALUE;
+		parent.accept(new DepthFirstBoxTraversal<Object>() {
+			@Override
+			public Object visit(final StructuralNodeReference box) {
+				if (box == parent) {
+					super.visit(box);
+				} else {
+					final int distance = verticalDistance(box, y);
+					if (box.isBelow(y)) {
+						candidates.add(box);
+						minVerticalDistance[0] = Math.min(distance, minVerticalDistance[0]);
+					}
+				}
+				return null;
+			}
+
+			@Override
+			public Object visit(final TextContent box) {
+				final int distance = verticalDistance(box, y);
+				if (box.isBelow(y)) {
+					candidates.add(box);
+					minVerticalDistance[0] = Math.min(distance, minVerticalDistance[0]);
+				}
+				return null;
+			}
+
+			@Override
+			public Object visit(final NodeEndOffsetPlaceholder box) {
+				final int distance = verticalDistance(box, y);
+				if (box.isBelow(y)) {
+					candidates.add(box);
+					minVerticalDistance[0] = Math.min(distance, minVerticalDistance[0]);
+				}
+				return null;
+			}
+		});
+
+		removeVerticallyDistantBoxes(candidates, y, minVerticalDistance[0]);
+		return candidates;
+	}
+
+	public static IContentBox findClosestContentBoxChildAbove(final IContentBox parent, final int x, final int y) {
+		final Iterable<IContentBox> candidates = findVerticallyClosestContentBoxChildrenAbove(parent, y);
+		return findHorizontallyClosestContentBox(candidates, x);
+	}
+
+	public static Iterable<IContentBox> findVerticallyClosestContentBoxChildrenAbove(final IContentBox parent, final int y) {
+		final LinkedList<IContentBox> candidates = new LinkedList<IContentBox>();
+		final int[] minVerticalDistance = new int[1];
+		minVerticalDistance[0] = Integer.MAX_VALUE;
+		parent.accept(new DepthFirstBoxTraversal<Object>() {
+			@Override
+			public Object visit(final StructuralNodeReference box) {
+				final int distance = verticalDistance(box, y);
+				if (box != parent && !box.isAbove(y)) {
+					return box;
+				}
+
+				if (box == parent) {
+					super.visit(box);
+				}
+
+				if (box != parent) {
+					candidates.add(box);
+					minVerticalDistance[0] = Math.min(distance, minVerticalDistance[0]);
+				}
+
+				return null;
+			}
+
+			@Override
+			public Object visit(final TextContent box) {
+				final int distance = verticalDistance(box, y);
+				if (box.isAbove(y) && distance <= minVerticalDistance[0]) {
+					candidates.add(box);
+					minVerticalDistance[0] = Math.min(distance, minVerticalDistance[0]);
+				}
+				return null;
+			}
+
+			@Override
+			public Object visit(final NodeEndOffsetPlaceholder box) {
+				final int distance = verticalDistance(box, y);
+				if (box.isAbove(y) && distance <= minVerticalDistance[0]) {
+					candidates.add(box);
+					minVerticalDistance[0] = Math.min(distance, minVerticalDistance[0]);
+				}
+				return null;
+			}
+		});
+
+		removeVerticallyDistantBoxes(candidates, y, minVerticalDistance[0]);
+		return candidates;
+	}
+
+	public static void removeVerticallyDistantBoxes(final List<? extends IContentBox> boxes, final int y, final int minVerticalDistance) {
+		for (final Iterator<? extends IContentBox> iter = boxes.iterator(); iter.hasNext();) {
+			final IContentBox candidate = iter.next();
+			if (verticalDistance(candidate, y) > minVerticalDistance) {
+				iter.remove();
+			}
+		}
+	}
+
 	public static int verticalDistance(final IContentBox box, final int y) {
 		return box.accept(new BaseBoxVisitorWithResult<Integer>(0) {
 			@Override
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveDown.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveDown.java
index f4360e3..ad84e93 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveDown.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveDown.java
@@ -10,12 +10,8 @@
  *******************************************************************************/
 package org.eclipse.vex.core.internal.cursor;
 
-import static org.eclipse.vex.core.internal.cursor.ContentTopology.findHorizontallyClosestContentBox;
+import static org.eclipse.vex.core.internal.cursor.ContentTopology.findClosestContentBoxChildBelow;
 import static org.eclipse.vex.core.internal.cursor.ContentTopology.getParentContentBox;
-import static org.eclipse.vex.core.internal.cursor.ContentTopology.verticalDistance;
-
-import java.util.Iterator;
-import java.util.LinkedList;
 
 import org.eclipse.vex.core.internal.boxes.BaseBoxVisitorWithResult;
 import org.eclipse.vex.core.internal.boxes.DepthFirstBoxTraversal;
@@ -210,7 +206,7 @@
 			return currentBox;
 		}
 
-		final IContentBox childBelow = findClosestContentBoxChildBelow(parent, x, y);
+		final IContentBox childBelow = handleSpecialCaseMovingIntoLastLineOfParagraph(findClosestContentBoxChildBelow(parent, x, y), x, y);
 		if (childBelow == null) {
 			if (containsInlineContent(parent)) {
 				return findNextContentBoxBelow(parent, x, y);
@@ -221,62 +217,6 @@
 		return childBelow;
 	}
 
-	private static IContentBox findClosestContentBoxChildBelow(final IContentBox parent, final int x, final int y) {
-		final Iterable<IContentBox> candidates = findVerticallyClosestContentBoxChildrenBelow(parent, y);
-		final IContentBox finalCandidate = findHorizontallyClosestContentBox(candidates, x);
-		final IContentBox realFinalCandidate = handleSpecialCaseMovingIntoLastLineOfParagraph(finalCandidate, x, y);
-		return realFinalCandidate;
-	}
-
-	private static Iterable<IContentBox> findVerticallyClosestContentBoxChildrenBelow(final IContentBox parent, final int y) {
-		final LinkedList<IContentBox> candidates = new LinkedList<IContentBox>();
-		final int[] minVerticalDistance = new int[1];
-		minVerticalDistance[0] = Integer.MAX_VALUE;
-		parent.accept(new DepthFirstBoxTraversal<Object>() {
-			@Override
-			public Object visit(final StructuralNodeReference box) {
-				if (box == parent) {
-					super.visit(box);
-				} else {
-					final int distance = verticalDistance(box, y);
-					if (box.isBelow(y)) {
-						candidates.add(box);
-						minVerticalDistance[0] = Math.min(distance, minVerticalDistance[0]);
-					}
-				}
-				return null;
-			}
-
-			@Override
-			public Object visit(final TextContent box) {
-				final int distance = verticalDistance(box, y);
-				if (box.isBelow(y)) {
-					candidates.add(box);
-					minVerticalDistance[0] = Math.min(distance, minVerticalDistance[0]);
-				}
-				return null;
-			}
-
-			@Override
-			public Object visit(final NodeEndOffsetPlaceholder box) {
-				final int distance = verticalDistance(box, y);
-				if (box.isBelow(y)) {
-					candidates.add(box);
-					minVerticalDistance[0] = Math.min(distance, minVerticalDistance[0]);
-				}
-				return null;
-			}
-		});
-
-		for (final Iterator<IContentBox> iter = candidates.iterator(); iter.hasNext();) {
-			final IContentBox candidate = iter.next();
-			if (verticalDistance(candidate, y) > minVerticalDistance[0]) {
-				iter.remove();
-			}
-		}
-		return candidates;
-	}
-
 	private static IContentBox handleSpecialCaseMovingIntoLastLineOfParagraph(final IContentBox candidate, final int x, final int y) {
 		if (candidate == null) {
 			return null;
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveToNextPage.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveToNextPage.java
index 8211031..f0bcddf 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveToNextPage.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveToNextPage.java
@@ -10,9 +10,15 @@
  *******************************************************************************/
 package org.eclipse.vex.core.internal.cursor;
 
+import static org.eclipse.vex.core.internal.cursor.ContentTopology.findClosestContentBoxChildBelow;
 import static org.eclipse.vex.core.internal.cursor.ContentTopology.getParentContentBox;
 
+import org.eclipse.vex.core.internal.boxes.BaseBoxVisitorWithResult;
 import org.eclipse.vex.core.internal.boxes.IContentBox;
+import org.eclipse.vex.core.internal.boxes.InlineNodeReference;
+import org.eclipse.vex.core.internal.boxes.NodeEndOffsetPlaceholder;
+import org.eclipse.vex.core.internal.boxes.StructuralNodeReference;
+import org.eclipse.vex.core.internal.boxes.TextContent;
 import org.eclipse.vex.core.internal.core.Graphics;
 import org.eclipse.vex.core.internal.core.Rectangle;
 import org.eclipse.vex.core.internal.widget.IViewPort;
@@ -32,9 +38,8 @@
 		final IContentBox box = contentTopology.findClosestBoxByCoordinates(x, y);
 		if (box == null) {
 			return currentOffset;
-		}
-		if (box.containsCoordinates(x, y)) {
-			return box.getOffsetForCoordinates(graphics, x - box.getAbsoluteLeft(), y - box.getAbsoluteTop());
+		} else if (box.containsCoordinates(x, y)) {
+			return findBestOffsetWithin(graphics, box, x, y);
 		} else if (box.isLeftOf(x)) {
 			if (isLastEnclosedBox(box)) {
 				return box.getEndOffset() + 1;
@@ -48,6 +53,34 @@
 		}
 	}
 
+	private static int findBestOffsetWithin(final Graphics graphics, final IContentBox closestBoxByCoordinates, final int x, final int y) {
+		return closestBoxByCoordinates.accept(new BaseBoxVisitorWithResult<Integer>() {
+			@Override
+			public Integer visit(final StructuralNodeReference box) {
+				final IContentBox closestChild = findClosestContentBoxChildBelow(box, x, y);
+				if (closestChild == null) {
+					return box.getEndOffset();
+				}
+				return closestChild.accept(this);
+			}
+
+			@Override
+			public Integer visit(final InlineNodeReference box) {
+				return box.getOffsetForCoordinates(graphics, x - box.getAbsoluteLeft(), y - box.getAbsoluteTop());
+			}
+
+			@Override
+			public Integer visit(final NodeEndOffsetPlaceholder box) {
+				return box.getOffsetForCoordinates(graphics, x - box.getAbsoluteLeft(), y - box.getAbsoluteTop());
+			}
+
+			@Override
+			public Integer visit(final TextContent box) {
+				return box.getOffsetForCoordinates(graphics, x - box.getAbsoluteLeft(), y - box.getAbsoluteTop());
+			}
+		});
+	}
+
 	private static boolean isLastEnclosedBox(final IContentBox enclosedBox) {
 		final IContentBox parent = getParentContentBox(enclosedBox);
 		if (parent == null) {
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveToPreviousPage.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveToPreviousPage.java
index 2cc2d36..8bb52fc 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveToPreviousPage.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveToPreviousPage.java
@@ -10,9 +10,15 @@
  *******************************************************************************/
 package org.eclipse.vex.core.internal.cursor;
 
+import static org.eclipse.vex.core.internal.cursor.ContentTopology.findClosestContentBoxChildAbove;
 import static org.eclipse.vex.core.internal.cursor.ContentTopology.getParentContentBox;
 
+import org.eclipse.vex.core.internal.boxes.BaseBoxVisitorWithResult;
 import org.eclipse.vex.core.internal.boxes.IContentBox;
+import org.eclipse.vex.core.internal.boxes.InlineNodeReference;
+import org.eclipse.vex.core.internal.boxes.NodeEndOffsetPlaceholder;
+import org.eclipse.vex.core.internal.boxes.StructuralNodeReference;
+import org.eclipse.vex.core.internal.boxes.TextContent;
 import org.eclipse.vex.core.internal.core.Graphics;
 import org.eclipse.vex.core.internal.core.Rectangle;
 import org.eclipse.vex.core.internal.widget.IViewPort;
@@ -32,9 +38,8 @@
 		final IContentBox box = contentTopology.findClosestBoxByCoordinates(x, y);
 		if (box == null) {
 			return currentOffset;
-		}
-		if (box.containsCoordinates(x, y)) {
-			return box.getOffsetForCoordinates(graphics, x - box.getAbsoluteLeft(), y - box.getAbsoluteTop());
+		} else if (box.containsCoordinates(x, y)) {
+			return findBestOffsetWithin(graphics, box, x, y);
 		} else if (box.isLeftOf(x)) {
 			if (isLastEnclosedBox(box)) {
 				return box.getEndOffset() + 1;
@@ -48,6 +53,34 @@
 		}
 	}
 
+	private static int findBestOffsetWithin(final Graphics graphics, final IContentBox closestBoxByCoordinates, final int x, final int y) {
+		return closestBoxByCoordinates.accept(new BaseBoxVisitorWithResult<Integer>() {
+			@Override
+			public Integer visit(final StructuralNodeReference box) {
+				final IContentBox closestChild = findClosestContentBoxChildAbove(box, x, y);
+				if (closestChild == null) {
+					return box.getStartOffset();
+				}
+				return closestChild.accept(this);
+			}
+
+			@Override
+			public Integer visit(final InlineNodeReference box) {
+				return box.getOffsetForCoordinates(graphics, x - box.getAbsoluteLeft(), y - box.getAbsoluteTop());
+			}
+
+			@Override
+			public Integer visit(final NodeEndOffsetPlaceholder box) {
+				return box.getOffsetForCoordinates(graphics, x - box.getAbsoluteLeft(), y - box.getAbsoluteTop());
+			}
+
+			@Override
+			public Integer visit(final TextContent box) {
+				return box.getOffsetForCoordinates(graphics, x - box.getAbsoluteLeft(), y - box.getAbsoluteTop());
+			}
+		});
+	}
+
 	private static boolean isLastEnclosedBox(final IContentBox enclosedBox) {
 		final IContentBox parent = getParentContentBox(enclosedBox);
 		if (parent == null) {
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveUp.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveUp.java
index b774630..fbf94b2 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveUp.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveUp.java
@@ -10,13 +10,14 @@
  *******************************************************************************/
 package org.eclipse.vex.core.internal.cursor;
 
+import static org.eclipse.vex.core.internal.cursor.ContentTopology.findClosestContentBoxChildAbove;
 import static org.eclipse.vex.core.internal.cursor.ContentTopology.findHorizontallyClosestContentBox;
 import static org.eclipse.vex.core.internal.cursor.ContentTopology.getParentContentBox;
 import static org.eclipse.vex.core.internal.cursor.ContentTopology.horizontalDistance;
+import static org.eclipse.vex.core.internal.cursor.ContentTopology.removeVerticallyDistantBoxes;
 import static org.eclipse.vex.core.internal.cursor.ContentTopology.verticalDistance;
 
 import java.util.Collections;
-import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 
@@ -208,7 +209,7 @@
 			return currentBox;
 		}
 
-		final IContentBox childAbove = findClosestContentBoxChildAbove(parent, x, y);
+		final IContentBox childAbove = handleSpecialCaseMovingIntoLastLineOfParagraph(findClosestContentBoxChildAbove(parent, x, y), x, y);
 		if (childAbove == null) {
 			return parent.accept(new BaseBoxVisitorWithResult<IContentBox>() {
 				@Override
@@ -236,61 +237,6 @@
 		return childAbove;
 	}
 
-	private static IContentBox findClosestContentBoxChildAbove(final IContentBox parent, final int x, final int y) {
-		final Iterable<IContentBox> candidates = findVerticallyClosestContentBoxChildrenAbove(parent, y);
-		final IContentBox finalCandidate = findHorizontallyClosestContentBox(candidates, x);
-		return handleSpecialCaseMovingIntoLastLineOfParagraph(finalCandidate, x, y);
-	}
-
-	private static Iterable<IContentBox> findVerticallyClosestContentBoxChildrenAbove(final IContentBox parent, final int y) {
-		final LinkedList<IContentBox> candidates = new LinkedList<IContentBox>();
-		final int[] minVerticalDistance = new int[1];
-		minVerticalDistance[0] = Integer.MAX_VALUE;
-		parent.accept(new DepthFirstBoxTraversal<Object>() {
-			@Override
-			public Object visit(final StructuralNodeReference box) {
-				final int distance = verticalDistance(box, y);
-				if (box != parent && !box.isAbove(y)) {
-					return box;
-				}
-
-				if (box == parent) {
-					super.visit(box);
-				}
-
-				if (box != parent) {
-					candidates.add(box);
-					minVerticalDistance[0] = Math.min(distance, minVerticalDistance[0]);
-				}
-
-				return null;
-			}
-
-			@Override
-			public Object visit(final TextContent box) {
-				final int distance = verticalDistance(box, y);
-				if (box.isAbove(y) && distance <= minVerticalDistance[0]) {
-					candidates.add(box);
-					minVerticalDistance[0] = Math.min(distance, minVerticalDistance[0]);
-				}
-				return null;
-			}
-
-			@Override
-			public Object visit(final NodeEndOffsetPlaceholder box) {
-				final int distance = verticalDistance(box, y);
-				if (box.isAbove(y) && distance <= minVerticalDistance[0]) {
-					candidates.add(box);
-					minVerticalDistance[0] = Math.min(distance, minVerticalDistance[0]);
-				}
-				return null;
-			}
-		});
-
-		removeVerticallyDistantBoxes(candidates, y, minVerticalDistance[0]);
-		return candidates;
-	}
-
 	private static IContentBox handleSpecialCaseMovingIntoLastLineOfParagraph(final IContentBox candidate, final int x, final int y) {
 		if (candidate == null) {
 			return null;
@@ -358,12 +304,4 @@
 		return candidates;
 	}
 
-	private static void removeVerticallyDistantBoxes(final List<? extends IContentBox> boxes, final int y, final int minVerticalDistance) {
-		for (final Iterator<? extends IContentBox> iter = boxes.iterator(); iter.hasNext();) {
-			final IContentBox candidate = iter.next();
-			if (verticalDistance(candidate, y) > minVerticalDistance) {
-				iter.remove();
-			}
-		}
-	}
 }