add support for positioning cursor by mouse click
Signed-off-by: Florian Thienel <florian@thienel.org>
diff --git a/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/boxes/FakeContentBox.java b/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/boxes/FakeContentBox.java
index c16eb08..f85fb11 100644
--- a/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/boxes/FakeContentBox.java
+++ b/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/boxes/FakeContentBox.java
@@ -93,4 +93,9 @@
return area;
}
+ @Override
+ public int getOffsetForCoordinates(final Graphics graphics, final int x, final int y) {
+ return startOffset;
+ }
+
}
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/CharSequenceSplitter.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/CharSequenceSplitter.java
index 112c736..3b961af 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/CharSequenceSplitter.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/CharSequenceSplitter.java
@@ -43,8 +43,8 @@
return charSequence.charAt(startPosition + position);
}
- public int findSplittingPositionBefore(final Graphics graphics, final int y, final int maxWidth, final boolean force) {
- final int positionAtWidth = findPositionBefore(graphics, y, maxWidth);
+ public int findSplittingPositionBefore(final Graphics graphics, final int x, final int maxWidth, final boolean force) {
+ final int positionAtWidth = findPositionBefore(graphics, x, maxWidth);
final int properSplittingPosition = findProperSplittingPositionBefore(positionAtWidth);
final int splittingPosition;
if (properSplittingPosition == -1 && force) {
@@ -55,22 +55,22 @@
return splittingPosition;
}
- private int findPositionBefore(final Graphics graphics, final int y, final int maxWidth) {
- if (y < 0) {
+ public int findPositionBefore(final Graphics graphics, final int x, final int maxWidth) {
+ if (x < 0) {
return 0;
}
- if (y >= maxWidth) {
+ if (x >= maxWidth) {
return textLength();
}
int begin = 0;
int end = textLength();
- int pivot = guessPositionAt(y, maxWidth);
+ int pivot = guessPositionAt(x, maxWidth);
while (begin < end - 1) {
final int textWidth = graphics.stringWidth(substring(0, pivot));
- if (textWidth > y) {
+ if (textWidth > x) {
end = pivot;
- } else if (textWidth < y) {
+ } else if (textWidth < x) {
begin = pivot;
} else {
return pivot;
@@ -80,8 +80,8 @@
return pivot;
}
- private int guessPositionAt(final int y, final int maxWidth) {
- final float splittingRatio = (float) y / maxWidth;
+ private int guessPositionAt(final int x, final int maxWidth) {
+ final float splittingRatio = (float) x / maxWidth;
return Math.round(splittingRatio * textLength());
}
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/ContentMap.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/ContentMap.java
index 324d7fd..f3be2c9 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/ContentMap.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/ContentMap.java
@@ -49,4 +49,62 @@
});
}
+ public IContentBox findBoxByCoordinates(final int x, final int y) {
+ return rootBox.accept(new DepthFirstTraversal<IContentBox>() {
+
+ private IContentBox nearBy;
+
+ @Override
+ public IContentBox visit(final NodeReference box) {
+ if (!containsCoordinates(box, x, y)) {
+ return null;
+ }
+
+ final IContentBox componentResult = box.getComponent().accept(this);
+ if (componentResult != null) {
+ return componentResult;
+ }
+ if (nearBy != null && (rightFromX(nearBy, x) && isFirstEnclosedBox(box, nearBy) || leftFromX(nearBy, x) && isLastEnclosedBox(box, nearBy))) {
+ return nearBy;
+ }
+ return box;
+
+ }
+
+ private boolean isLastEnclosedBox(final IContentBox enclosingBox, final IContentBox enclosedBox) {
+ return enclosedBox.getEndOffset() < enclosingBox.getEndOffset() - 1;
+ }
+
+ private boolean isFirstEnclosedBox(final IContentBox enclosingBox, final IContentBox enclosedBox) {
+ return enclosedBox.getStartOffset() > enclosingBox.getStartOffset() + 1;
+ }
+
+ @Override
+ public IContentBox visit(final TextContent box) {
+ if (containsCoordinates(box, x, y)) {
+ return box;
+ }
+ if (containsY(box, y)) {
+ nearBy = box;
+ }
+ return null;
+ }
+
+ private boolean containsCoordinates(final IBox box, final int x, final int y) {
+ return x >= box.getAbsoluteLeft() && x <= box.getAbsoluteLeft() + box.getWidth() && containsY(box, y);
+ }
+
+ private boolean containsY(final IBox box, final int y) {
+ return y >= box.getAbsoluteTop() && y <= box.getAbsoluteTop() + box.getHeight();
+ }
+
+ private boolean rightFromX(final IBox box, final int x) {
+ return x < box.getAbsoluteLeft();
+ }
+
+ private boolean leftFromX(final IBox box, final int x) {
+ return x > box.getAbsoluteLeft() + box.getWidth();
+ }
+ });
+ }
}
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/IContentBox.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/IContentBox.java
index 01569aa..8da54d0 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/IContentBox.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/IContentBox.java
@@ -22,6 +22,8 @@
int getEndOffset();
- Rectangle getPositionArea(final Graphics graphics, final int offset);
+ Rectangle getPositionArea(Graphics graphics, int offset);
+
+ int getOffsetForCoordinates(Graphics graphics, int x, int y);
}
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/NodeReference.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/NodeReference.java
index e44a794..b28da2b 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/NodeReference.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/NodeReference.java
@@ -166,6 +166,29 @@
return new Rectangle(0, 0, width, height);
}
+ @Override
+ public int getOffsetForCoordinates(final Graphics graphics, final int x, final int y) {
+ if (isEmpty()) {
+ return getEndOffset();
+ }
+
+ final long dStart = distance(0, 0, x, y);
+ final long dEnd = distance(width, height, x, y);
+ if (dStart < dEnd) {
+ return getStartOffset();
+ } else {
+ return getEndOffset();
+ }
+ }
+
+ private static long distance(final int x1, final int y1, final int x2, final int y2) {
+ return Math.round(Math.sqrt(pow2(x2 - x1) + pow2(y2 - y1)));
+ }
+
+ private static int pow2(final int i) {
+ return i * i;
+ }
+
public boolean isEmpty() {
return getEndOffset() - getStartOffset() <= 1;
}
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/TextContent.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/TextContent.java
index 9cc02f6..11d8215 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/TextContent.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/TextContent.java
@@ -269,6 +269,27 @@
}
@Override
+ public int getOffsetForCoordinates(final Graphics graphics, final int x, final int y) {
+ if (y < 0) {
+ return getStartOffset();
+ }
+ if (y > height) {
+ return getEndOffset();
+ }
+
+ applyFont(graphics);
+ splitter.setContent(content, startPosition.getOffset(), endPosition.getOffset());
+ final int offset = getStartOffset() + splitter.findPositionBefore(graphics, x, width);
+ final Rectangle area = getPositionArea(graphics, offset);
+ final int halfWidth = area.getWidth() / 2 + 1;
+ if (x < area.getX() + halfWidth) {
+ return offset;
+ } else {
+ return Math.min(offset + 1, getEndOffset());
+ }
+ }
+
+ @Override
public String toString() {
return "TextContent{ x: " + left + ", y: " + top + ", width: " + width + ", height: " + height + ", startOffset: " + startPosition + ", endOffset: " + endPosition + " }";
}
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/swt/BoxWidget.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/swt/BoxWidget.java
index 27c8a93..0fee376 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/swt/BoxWidget.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/swt/BoxWidget.java
@@ -30,6 +30,7 @@
import org.eclipse.swt.widgets.Display;
import org.eclipse.vex.core.internal.boxes.ContentMap;
import org.eclipse.vex.core.internal.boxes.Cursor;
+import org.eclipse.vex.core.internal.boxes.IContentBox;
import org.eclipse.vex.core.internal.boxes.RootBox;
import org.eclipse.vex.core.internal.core.Graphics;
@@ -70,6 +71,7 @@
connectScrollVertically();
}
connectKeyboard();
+ connectMouse();
rootBox = new RootBox();
contentMap = new ContentMap();
@@ -131,6 +133,15 @@
});
}
+ private void connectMouse() {
+ addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseDown(final MouseEvent e) {
+ BoxWidget.this.mouseDown(e);
+ }
+ });
+ }
+
private void widgetDisposed() {
rootBox = null;
if (bufferImage != null) {
@@ -171,6 +182,22 @@
}
}
+ private void mouseDown(final MouseEvent event) {
+ final int absoluteY = event.y + getVerticalBar().getSelection();
+ final IContentBox clickedBox = contentMap.findBoxByCoordinates(event.x, absoluteY);
+
+ final Image image = new Image(getDisplay(), getSize().x, getSize().y);
+ final GC gc = new GC(image);
+ final Graphics graphics = new SwtGraphics(gc);
+ final int offset = clickedBox.getOffsetForCoordinates(graphics, event.x - clickedBox.getAbsoluteLeft(), absoluteY - clickedBox.getAbsoluteTop());
+ graphics.dispose();
+ gc.dispose();
+ image.dispose();
+
+ cursor.setPosition(offset);
+ invalidate();
+ }
+
private void scheduleRenderer(final Runnable renderer) {
synchronized (rendererMonitor) {
if (currentRenderer != null) {