diff --git a/ChangeLog b/ChangeLog index 9832d4799..9a8f148f0 100644 --- a/ChangeLog +++ b/ChangeLog @@ -8,6 +8,7 @@ * ComicBook plugin icon * Updated Armenian localisation (by Yavruhrat, Marat Yavrumyan) * Added Korean intro text +* New style selection rendering ===== 2.4.7 (May 20, 2015) ===== * Added Korean localisation diff --git a/src/org/geometerplus/android/fbreader/ProcessHyperlinkAction.java b/src/org/geometerplus/android/fbreader/ProcessHyperlinkAction.java index 4ee39dc98..4503a18ff 100644 --- a/src/org/geometerplus/android/fbreader/ProcessHyperlinkAction.java +++ b/src/org/geometerplus/android/fbreader/ProcessHyperlinkAction.java @@ -151,8 +151,17 @@ class ProcessHyperlinkAction extends FBAndroidAction { } } } else if (soul instanceof ZLTextWordRegionSoul) { - DictionaryUtil.openWordInDictionary( - BaseActivity, ((ZLTextWordRegionSoul)soul).Word, region + DictionaryUtil.openTextInDictionary( + BaseActivity, + ((ZLTextWordRegionSoul)soul).Word.getString(), + true, + region.getTop(), + region.getBottom(), + new Runnable() { + public void run() { + BaseActivity.outlineRegion(soul); + } + } ); } } diff --git a/src/org/geometerplus/android/fbreader/SelectionTranslateAction.java b/src/org/geometerplus/android/fbreader/SelectionTranslateAction.java index 768e01279..441ec215b 100644 --- a/src/org/geometerplus/android/fbreader/SelectionTranslateAction.java +++ b/src/org/geometerplus/android/fbreader/SelectionTranslateAction.java @@ -19,8 +19,7 @@ package org.geometerplus.android.fbreader; -import org.geometerplus.fbreader.fbreader.FBReaderApp; -import org.geometerplus.fbreader.fbreader.FBView; +import org.geometerplus.fbreader.fbreader.*; import org.geometerplus.android.fbreader.dict.DictionaryUtil; public class SelectionTranslateAction extends FBAndroidAction { @@ -31,13 +30,19 @@ public class SelectionTranslateAction extends FBAndroidAction { @Override protected void run(Object ... params) { final FBView fbview = Reader.getTextView(); + final DictionaryHighlighting dictionaryHilite = new DictionaryHighlighting(fbview); DictionaryUtil.openTextInDictionary( BaseActivity, fbview.getSelectedSnippet().getText(), fbview.getCountOfSelectedWords() == 1, fbview.getSelectionStartY(), fbview.getSelectionEndY(), - fbview.getSelectionSoul() + new Runnable() { + public void run() { + fbview.addHighlighting(dictionaryHilite); + Reader.getViewWidget().repaint(); + } + } ); fbview.clearSelection(); } diff --git a/src/org/geometerplus/android/fbreader/dict/DictionaryUtil.java b/src/org/geometerplus/android/fbreader/dict/DictionaryUtil.java index 48d7a7cc3..b1a90b102 100644 --- a/src/org/geometerplus/android/fbreader/dict/DictionaryUtil.java +++ b/src/org/geometerplus/android/fbreader/dict/DictionaryUtil.java @@ -51,7 +51,6 @@ import org.geometerplus.zlibrary.core.options.ZLStringOption; import org.geometerplus.zlibrary.core.resources.ZLResource; import org.geometerplus.zlibrary.core.util.XmlUtil; -import org.geometerplus.zlibrary.text.view.ZLTextRegion; import org.geometerplus.zlibrary.text.view.ZLTextWord; import org.geometerplus.zlibrary.ui.android.library.ZLAndroidLibrary; @@ -122,7 +121,7 @@ public abstract class DictionaryUtil { } } - abstract void open(String text, ZLTextRegion.Soul soulToSelect, FBReader fbreader, PopupFrameMetric frameMetrics); + abstract void open(String text, Runnable outliner, FBReader fbreader, PopupFrameMetric frameMetrics); } private static class PlainPackageInfo extends PackageInfo { @@ -131,7 +130,7 @@ public abstract class DictionaryUtil { } @Override - void open(String text, ZLTextRegion.Soul soulToSelect, FBReader fbreader, PopupFrameMetric frameMetrics) { + void open(String text, Runnable outliner, FBReader fbreader, PopupFrameMetric frameMetrics) { final Intent intent = getDictionaryIntent(text); try { final String id = getId(); @@ -167,7 +166,7 @@ public abstract class DictionaryUtil { } @Override - void open(String text, ZLTextRegion.Soul soulToSelect, FBReader fbreader, PopupFrameMetric frameMetrics) { + void open(String text, Runnable outliner, FBReader fbreader, PopupFrameMetric frameMetrics) { final Intent intent = getDictionaryIntent(text); try { intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); @@ -176,7 +175,9 @@ public abstract class DictionaryUtil { intent.putExtra("article.text.size.max", MAX_LENGTH_FOR_TOAST); fbreader.startActivityForResult(intent, FBReader.REQUEST_DICTIONARY); fbreader.overridePendingTransition(0, 0); - fbreader.outlineRegion(soulToSelect); + if (outliner != null) { + outliner.run(); + } } catch (ActivityNotFoundException e) { fbreader.hideOutline(); installDictionaryIfNotInstalled(fbreader, this); @@ -199,7 +200,7 @@ public abstract class DictionaryUtil { } @Override - void open(String text, ZLTextRegion.Soul soulToSelect, FBReader fbreader, PopupFrameMetric frameMetrics) { + void open(String text, Runnable outliner, FBReader fbreader, PopupFrameMetric frameMetrics) { Flyout.showTranslation(fbreader, text, frameMetrics); } } @@ -455,7 +456,7 @@ public abstract class DictionaryUtil { } } - public static void openTextInDictionary(final FBReader fbreader, String text, boolean singleWord, int selectionTop, int selectionBottom, final ZLTextRegion.Soul soulToSelect) { + public static void openTextInDictionary(final FBReader fbreader, String text, boolean singleWord, int selectionTop, int selectionBottom, final Runnable outliner) { final String textToTranslate; if (singleWord) { int start = 0; @@ -478,17 +479,11 @@ public abstract class DictionaryUtil { final PackageInfo info = getCurrentDictionaryInfo(singleWord); fbreader.runOnUiThread(new Runnable() { public void run() { - info.open(textToTranslate, soulToSelect, fbreader, frameMetrics); + info.open(textToTranslate, outliner, fbreader, frameMetrics); } }); } - public static void openWordInDictionary(FBReader fbreader, ZLTextWord word, ZLTextRegion region) { - openTextInDictionary( - fbreader, word.toString(), true, region.getTop(), region.getBottom(), region.getSoul() - ); - } - public static void installDictionaryIfNotInstalled(final Activity activity, final PackageInfo info) { if (PackageUtil.canBeStarted(activity, info.getDictionaryIntent("test"), false)) { return; diff --git a/src/org/geometerplus/fbreader/fbreader/BookmarkHighlighting.java b/src/org/geometerplus/fbreader/fbreader/BookmarkHighlighting.java index 5092fd3e3..17c8dd815 100644 --- a/src/org/geometerplus/fbreader/fbreader/BookmarkHighlighting.java +++ b/src/org/geometerplus/fbreader/fbreader/BookmarkHighlighting.java @@ -59,4 +59,9 @@ public final class BookmarkHighlighting extends ZLTextSimpleHighlighting { final HighlightingStyle bmStyle = Collection.getHighlightingStyle(Bookmark.getStyleId()); return bmStyle != null ? bmStyle.getForegroundColor() : null; } + + @Override + public ZLColor getOutlineColor() { + return null; + } } diff --git a/src/org/geometerplus/fbreader/fbreader/DictionaryHighlighting.java b/src/org/geometerplus/fbreader/fbreader/DictionaryHighlighting.java new file mode 100644 index 000000000..8dde2e85d --- /dev/null +++ b/src/org/geometerplus/fbreader/fbreader/DictionaryHighlighting.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2007-2015 FBReader.ORG Limited + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +package org.geometerplus.fbreader.fbreader; + +import org.geometerplus.zlibrary.core.util.ZLColor; +import org.geometerplus.zlibrary.text.view.*; + +public final class DictionaryHighlighting extends ZLTextSimpleHighlighting { + public DictionaryHighlighting(ZLTextView view) { + this(view, view.getSelectionHighlighting()); + } + + private DictionaryHighlighting(ZLTextView view, ZLTextHighlighting selection) { + super(view, selection.getStartPosition(), selection.getEndPosition()); + } + + @Override + public ZLColor getBackgroundColor() { + return View.getSelectionBackgroundColor(); + } + + @Override + public ZLColor getForegroundColor() { + return null; + } + + @Override + public ZLColor getOutlineColor() { + return null; + } +} diff --git a/src/org/geometerplus/fbreader/fbreader/FBView.java b/src/org/geometerplus/fbreader/fbreader/FBView.java index fb9ac675a..af45a34de 100644 --- a/src/org/geometerplus/fbreader/fbreader/FBView.java +++ b/src/org/geometerplus/fbreader/fbreader/FBView.java @@ -26,6 +26,7 @@ import org.geometerplus.zlibrary.core.filesystem.ZLResourceFile; import org.geometerplus.zlibrary.core.fonts.FontEntry; import org.geometerplus.zlibrary.core.library.ZLibrary; import org.geometerplus.zlibrary.core.util.ZLColor; +import org.geometerplus.zlibrary.core.view.SelectionCursor; import org.geometerplus.zlibrary.core.view.ZLPaintContext; import org.geometerplus.zlibrary.text.model.ZLTextModel; @@ -158,10 +159,10 @@ public final class FBView extends ZLTextView { return true; } - final ZLTextSelectionCursor cursor = findSelectionCursor(x, y, maxSelectionDistance()); - if (cursor != ZLTextSelectionCursor.None) { + final SelectionCursor.Which cursor = findSelectionCursor(x, y, maxSelectionDistance()); + if (cursor != null) { myReader.runAction(ActionCode.SELECTION_HIDE_PANEL); - moveSelectionCursorTo(cursor, x, y, true); + moveSelectionCursorTo(cursor, x, y); return true; } @@ -199,9 +200,9 @@ public final class FBView extends ZLTextView { return true; } - final ZLTextSelectionCursor cursor = getSelectionCursorInMovement(); - if (cursor != ZLTextSelectionCursor.None) { - moveSelectionCursorTo(cursor, x, y, true); + final SelectionCursor.Which cursor = getSelectionCursorInMovement(); + if (cursor != null) { + moveSelectionCursorTo(cursor, x, y); return true; } @@ -229,8 +230,8 @@ public final class FBView extends ZLTextView { return true; } - final ZLTextSelectionCursor cursor = getSelectionCursorInMovement(); - if (cursor != ZLTextSelectionCursor.None) { + final SelectionCursor.Which cursor = getSelectionCursorInMovement(); + if (cursor != null) { releaseSelectionCursor(); return true; } @@ -266,9 +267,9 @@ public final class FBView extends ZLTextView { case startSelecting: myReader.runAction(ActionCode.SELECTION_HIDE_PANEL); initSelection(x, y); - final ZLTextSelectionCursor cursor = findSelectionCursor(x, y); - if (cursor != ZLTextSelectionCursor.None) { - moveSelectionCursorTo(cursor, x, y, false); + final SelectionCursor.Which cursor = findSelectionCursor(x, y); + if (cursor != null) { + moveSelectionCursorTo(cursor, x, y); } return true; case selectSingleWord: @@ -300,9 +301,9 @@ public final class FBView extends ZLTextView { return true; } - final ZLTextSelectionCursor cursor = getSelectionCursorInMovement(); - if (cursor != ZLTextSelectionCursor.None) { - moveSelectionCursorTo(cursor, x, y, true); + final SelectionCursor.Which cursor = getSelectionCursorInMovement(); + if (cursor != null) { + moveSelectionCursorTo(cursor, x, y); return true; } @@ -334,8 +335,8 @@ public final class FBView extends ZLTextView { return true; } - final ZLTextSelectionCursor cursor = getSelectionCursorInMovement(); - if (cursor != ZLTextSelectionCursor.None) { + final SelectionCursor.Which cursor = getSelectionCursorInMovement(); + if (cursor != null) { releaseSelectionCursor(); return true; } diff --git a/src/org/geometerplus/zlibrary/core/view/HorizontalConvexHull.java b/src/org/geometerplus/zlibrary/core/view/HorizontalConvexHull.java new file mode 100644 index 000000000..ce3bad3b8 --- /dev/null +++ b/src/org/geometerplus/zlibrary/core/view/HorizontalConvexHull.java @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2009-2015 FBReader.ORG Limited + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +package org.geometerplus.zlibrary.core.view; + +import java.util.*; + +import android.graphics.Rect; + +public final class HorizontalConvexHull implements Hull { + private final LinkedList myRectangles = new LinkedList(); + + public HorizontalConvexHull(Collection rects) { + for (Rect r : rects) { + addRect(r); + } + normalize(); + } + + private void addRect(Rect rectangle) { + if (myRectangles.isEmpty()) { + myRectangles.add(new Rect(rectangle)); + return; + } + final int top = rectangle.top; + final int bottom = rectangle.bottom; + for (ListIterator iter = myRectangles.listIterator(); iter.hasNext(); ) { + Rect r = iter.next(); + if (r.bottom <= top) { + continue; + } + if (r.top >= bottom) { + break; + } + if (r.top < top) { + final Rect before = new Rect(r); + before.bottom = top; + r.top = top; + iter.previous(); + iter.add(before); + iter.next(); + } + if (r.bottom > bottom) { + final Rect after = new Rect(r); + after.top = bottom; + r.bottom = bottom; + iter.add(after); + } + r.left = Math.min(r.left, rectangle.left); + r.right = Math.max(r.right, rectangle.right); + } + + final Rect first = myRectangles.getFirst(); + if (top < first.top) { + myRectangles.add(0, new Rect(rectangle.left, top, rectangle.right, Math.min(bottom, first.top))); + } + + final Rect last = myRectangles.getLast(); + if (bottom > last.bottom) { + myRectangles.add(new Rect(rectangle.left, Math.max(top, last.bottom), rectangle.right, bottom)); + } + } + + private void normalize() { + Rect previous = null; + for (ListIterator iter = myRectangles.listIterator(); iter.hasNext(); ) { + final Rect current = iter.next(); + if (previous != null) { + if ((previous.left == current.left) && (previous.right == current.right)) { + previous.bottom = current.bottom; + iter.remove(); + continue; + } + if ((previous.bottom != current.top) && + (current.left <= previous.right) && + (previous.left <= current.right)) { + iter.previous(); + iter.add(new Rect( + Math.max(previous.left, current.left), + previous.bottom, + Math.min(previous.right, current.right), + current.top + )); + iter.next(); + } + } + previous = current; + } + } + + public int distanceTo(int x, int y) { + int distance = Integer.MAX_VALUE; + for (Rect r : myRectangles) { + final int xd = r.left > x ? r.left - x : (r.right < x ? x - r.right : 0); + final int yd = r.top > y ? r.top - y : (r.bottom < y ? y - r.bottom : 0); + distance = Math.min(distance, Math.max(xd, yd)); + if (distance == 0) { + break; + } + } + return distance; + } + + public boolean isBefore(int x, int y) { + for (Rect r : myRectangles) { + if (r.bottom < y || (r.top < y && r.right < x)) { + return true; + } + } + return false; + } + + public void draw(ZLPaintContext context, int mode) { + if (mode == DrawMode.None) { + return; + } + + final LinkedList rectangles = new LinkedList(myRectangles); + while (!rectangles.isEmpty()) { + final LinkedList connected = new LinkedList(); + Rect previous = null; + for (final Iterator iter = rectangles.iterator(); iter.hasNext(); ) { + final Rect current = iter.next(); + if ((previous != null) && + ((previous.left > current.right) || (current.left > previous.right))) { + break; + } + iter.remove(); + connected.add(current); + previous = current; + } + + final LinkedList xList = new LinkedList(); + final LinkedList yList = new LinkedList(); + int x = 0, xPrev = 0; + + final ListIterator iter = connected.listIterator(); + Rect r = iter.next(); + x = r.right + 2; + xList.add(x); yList.add(r.top); + while (iter.hasNext()) { + xPrev = x; + r = iter.next(); + x = r.right + 2; + if (x != xPrev) { + final int y = (x < xPrev) ? r.top + 2 : r.top; + xList.add(xPrev); yList.add(y); + xList.add(x); yList.add(y); + } + } + xList.add(x); yList.add(r.bottom + 2); + + r = iter.previous(); + x = r.left - 2; + xList.add(x); yList.add(r.bottom + 2); + while (iter.hasPrevious()) { + xPrev = x; + r = iter.previous(); + x = r.left - 2; + if (x != xPrev) { + final int y = (x > xPrev) ? r.bottom : r.bottom + 2; + xList.add(xPrev); yList.add(y); + xList.add(x); yList.add(y); + } + } + xList.add(x); yList.add(r.top); + + final int xs[] = new int[xList.size()]; + final int ys[] = new int[yList.size()]; + int count = 0; + for (int xx : xList) { + xs[count++] = xx; + } + count = 0; + for (int yy : yList) { + ys[count++] = yy; + } + + if ((mode & DrawMode.Fill) == DrawMode.Fill) { + context.fillPolygon(xs, ys); + } + if ((mode & DrawMode.Outline) == DrawMode.Outline) { + context.drawOutline(xs, ys); + } + } + } +} diff --git a/src/org/geometerplus/zlibrary/core/view/Hull.java b/src/org/geometerplus/zlibrary/core/view/Hull.java new file mode 100644 index 000000000..081d51d62 --- /dev/null +++ b/src/org/geometerplus/zlibrary/core/view/Hull.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2009-2015 FBReader.ORG Limited + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +package org.geometerplus.zlibrary.core.view; + +public interface Hull { + interface DrawMode { + int None = 0; + int Outline = 1; + int Fill = 2; + }; + + void draw(ZLPaintContext context, int mode); + int distanceTo(int x, int y); + boolean isBefore(int x, int y); +} diff --git a/src/org/geometerplus/zlibrary/text/view/ZLTextSelectionCursor.java b/src/org/geometerplus/zlibrary/core/view/SelectionCursor.java similarity index 55% rename from src/org/geometerplus/zlibrary/text/view/ZLTextSelectionCursor.java rename to src/org/geometerplus/zlibrary/core/view/SelectionCursor.java index 14703c180..d704e1f7b 100644 --- a/src/org/geometerplus/zlibrary/text/view/ZLTextSelectionCursor.java +++ b/src/org/geometerplus/zlibrary/core/view/SelectionCursor.java @@ -17,40 +17,27 @@ * 02110-1301, USA. */ -package org.geometerplus.zlibrary.text.view; +package org.geometerplus.zlibrary.core.view; import org.geometerplus.zlibrary.core.library.ZLibrary; +import org.geometerplus.zlibrary.core.util.ZLColor; -public enum ZLTextSelectionCursor { - None, - Left, - Right; +public abstract class SelectionCursor { + public enum Which { + Left, + Right + } - private static int ourHeight; - private static int ourWidth; - private static int ourAccent; - - private static void init() { - if (ourHeight == 0) { - final int dpi = ZLibrary.Instance().getDisplayDPI(); - ourAccent = dpi / 12; - ourWidth = dpi / 6; - ourHeight = dpi / 4; + public static void draw(ZLPaintContext context, Which which, int x, int y, ZLColor color) { + context.setFillColor(color); + final int dpi = ZLibrary.Instance().getDisplayDPI(); + final int unit = dpi / 120; + final int xCenter = which == Which.Left ? x - unit - 1 : x + unit + 1; + context.fillRectangle(xCenter - unit, y + dpi / 8, xCenter + unit, y - dpi / 8); + if (which == Which.Left) { + context.fillCircle(xCenter, y - dpi / 8, unit * 6); + } else { + context.fillCircle(xCenter, y + dpi / 8, unit * 6); } } - - static int getHeight() { - init(); - return ourHeight; - } - - static int getWidth() { - init(); - return ourWidth; - } - - static int getAccent() { - init(); - return ourAccent; - } } diff --git a/src/org/geometerplus/zlibrary/core/view/UnionHull.java b/src/org/geometerplus/zlibrary/core/view/UnionHull.java new file mode 100644 index 000000000..70a9a2866 --- /dev/null +++ b/src/org/geometerplus/zlibrary/core/view/UnionHull.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2009-2015 FBReader.ORG Limited + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +package org.geometerplus.zlibrary.core.view; + +import java.util.*; + +public class UnionHull implements Hull { + private final List myComponents; + + public UnionHull(Hull ... components) { + myComponents = new ArrayList(Arrays.asList(components)); + } + + public void draw(ZLPaintContext context, int mode) { + for (Hull h : myComponents) { + h.draw(context, mode); + } + } + + public int distanceTo(int x, int y) { + int dist = Integer.MAX_VALUE; + for (Hull h : myComponents) { + dist = Math.min(dist, h.distanceTo(x, y)); + } + return dist; + } + + public boolean isBefore(int x, int y) { + for (Hull h : myComponents) { + if (h.isBefore(x, y)) { + return true; + } + } + return false; + } +} diff --git a/src/org/geometerplus/zlibrary/text/view/HullUtil.java b/src/org/geometerplus/zlibrary/text/view/HullUtil.java new file mode 100644 index 000000000..64c23f423 --- /dev/null +++ b/src/org/geometerplus/zlibrary/text/view/HullUtil.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2009-2015 FBReader.ORG Limited + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +package org.geometerplus.zlibrary.text.view; + +import java.util.*; + +import android.graphics.Rect; + +import org.geometerplus.zlibrary.core.view.*; + +abstract class HullUtil { + static Hull hull(ZLTextElementArea[] areas) { + return hull(Arrays.asList(areas)); + } + + static Hull hull(List areas) { + final List rectangles0 = new ArrayList(areas.size()); + final List rectangles1 = new ArrayList(areas.size()); + for (ZLTextElementArea a : areas) { + final Rect rect = new Rect(a.XStart, a.YStart, a.XEnd, a.YEnd); + if (a.ColumnIndex == 0) { + rectangles0.add(rect); + } else { + rectangles1.add(rect); + } + } + if (rectangles0.isEmpty()) { + return new HorizontalConvexHull(rectangles1); + } else if (rectangles1.isEmpty()) { + return new HorizontalConvexHull(rectangles0); + } else { + return new UnionHull( + new HorizontalConvexHull(rectangles0), + new HorizontalConvexHull(rectangles1) + ); + } + } +} diff --git a/src/org/geometerplus/zlibrary/text/view/ZLTextElementArea.java b/src/org/geometerplus/zlibrary/text/view/ZLTextElementArea.java index 20b6de3ce..f1980e7e6 100644 --- a/src/org/geometerplus/zlibrary/text/view/ZLTextElementArea.java +++ b/src/org/geometerplus/zlibrary/text/view/ZLTextElementArea.java @@ -24,6 +24,7 @@ public final class ZLTextElementArea extends ZLTextFixedPosition { public final int XEnd; public final int YStart; public final int YEnd; + public final int ColumnIndex; final int Length; final boolean AddHyphenationSign; @@ -33,13 +34,14 @@ public final class ZLTextElementArea extends ZLTextFixedPosition { private final boolean myIsLastInElement; - ZLTextElementArea(int paragraphIndex, int elementIndex, int charIndex, int length, boolean lastInElement, boolean addHyphenationSign, boolean changeStyle, ZLTextStyle style, ZLTextElement element, int xStart, int xEnd, int yStart, int yEnd) { + ZLTextElementArea(int paragraphIndex, int elementIndex, int charIndex, int length, boolean lastInElement, boolean addHyphenationSign, boolean changeStyle, ZLTextStyle style, ZLTextElement element, int xStart, int xEnd, int yStart, int yEnd, int columnIndex) { super(paragraphIndex, elementIndex, charIndex); XStart = xStart; XEnd = xEnd; YStart = yStart; YEnd = yEnd; + ColumnIndex = columnIndex; Length = length; myIsLastInElement = lastInElement; diff --git a/src/org/geometerplus/zlibrary/text/view/ZLTextElementAreaVector.java b/src/org/geometerplus/zlibrary/text/view/ZLTextElementAreaVector.java index 0dcf2ad65..22c8c8814 100644 --- a/src/org/geometerplus/zlibrary/text/view/ZLTextElementAreaVector.java +++ b/src/org/geometerplus/zlibrary/text/view/ZLTextElementAreaVector.java @@ -38,9 +38,10 @@ final class ZLTextElementAreaVector { return myAreas.size(); } - // TODO: remove this unsafe method - public ZLTextElementArea get(int index) { - return myAreas.get(index); + public List areas() { + synchronized (myAreas) { + return new ArrayList(myAreas); + } } public ZLTextElementArea getFirstArea() { @@ -168,6 +169,28 @@ final class ZLTextElementAreaVector { return bestRegion; } + static class RegionPair { + ZLTextRegion Before; + ZLTextRegion After; + } + + RegionPair findRegionsPair(int x, int y, int columnIndex, ZLTextRegion.Filter filter) { + RegionPair pair = new RegionPair(); + synchronized (myElementRegions) { + for (ZLTextRegion region : myElementRegions) { + if (filter.accepts(region)) { + if (region.isBefore(x, y, columnIndex)) { + pair.Before = region; + } else { + pair.After = region; + break; + } + } + } + } + return pair; + } + protected ZLTextRegion nextRegion(ZLTextRegion currentRegion, ZLTextView.Direction direction, ZLTextRegion.Filter filter) { synchronized (myElementRegions) { if (myElementRegions.isEmpty()) { diff --git a/src/org/geometerplus/zlibrary/text/view/ZLTextHighlighting.java b/src/org/geometerplus/zlibrary/text/view/ZLTextHighlighting.java index 2b5dee977..1c3b9d23f 100644 --- a/src/org/geometerplus/zlibrary/text/view/ZLTextHighlighting.java +++ b/src/org/geometerplus/zlibrary/text/view/ZLTextHighlighting.java @@ -19,7 +19,10 @@ package org.geometerplus.zlibrary.text.view; +import java.util.List; + import org.geometerplus.zlibrary.core.util.ZLColor; +import org.geometerplus.zlibrary.core.view.Hull; public abstract class ZLTextHighlighting implements Comparable { public abstract boolean isEmpty(); @@ -31,6 +34,7 @@ public abstract class ZLTextHighlighting implements Comparable areas = page.TextElementMap.areas(); + int startIndex = 0; + int endIndex = 0; + for (int i = 0; i < areas.size(); ++i) { + final ZLTextElementArea a = areas.get(i); + if (i == startIndex && startPosition.compareTo(a) > 0) { + ++startIndex; + } else if (endPosition.compareTo(a) < 0) { + break; + } + ++endIndex; + } + return HullUtil.hull(areas.subList(startIndex, endIndex)); + } + public int compareTo(ZLTextHighlighting highlighting) { final int cmp = getStartPosition().compareTo(highlighting.getStartPosition()); return cmp != 0 ? cmp : getEndPosition().compareTo(highlighting.getEndPosition()); diff --git a/src/org/geometerplus/zlibrary/text/view/ZLTextHorizontalConvexHull.java b/src/org/geometerplus/zlibrary/text/view/ZLTextHorizontalConvexHull.java deleted file mode 100644 index 9187e58fd..000000000 --- a/src/org/geometerplus/zlibrary/text/view/ZLTextHorizontalConvexHull.java +++ /dev/null @@ -1,205 +0,0 @@ -/* - * Copyright (C) 2009-2015 FBReader.ORG Limited - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. - */ - -package org.geometerplus.zlibrary.text.view; - -import java.util.*; - -import org.geometerplus.zlibrary.core.view.ZLPaintContext; - -class ZLTextHorizontalConvexHull { - private final LinkedList myRectangles = new LinkedList(); - - ZLTextHorizontalConvexHull(ZLTextElementArea[] textAreas) { - for (ZLTextElementArea area : textAreas) { - addArea(area); - } - normalize(); - } - - private void addArea(ZLTextElementArea area) { - if (myRectangles.isEmpty()) { - myRectangles.add(new Rectangle(area.XStart, area.XEnd, area.YStart, area.YEnd)); - return; - } - final int top = area.YStart; - final int bottom = area.YEnd; - for (ListIterator iter = myRectangles.listIterator(); iter.hasNext(); ) { - Rectangle r = iter.next(); - if (r.Bottom <= top) { - continue; - } - if (r.Top >= bottom) { - break; - } - if (r.Top < top) { - final Rectangle before = new Rectangle(r); - before.Bottom = top; - r.Top = top; - iter.previous(); - iter.add(before); - iter.next(); - } - if (r.Bottom > bottom) { - final Rectangle after = new Rectangle(r); - after.Top = bottom; - r.Bottom = bottom; - iter.add(after); - } - r.Left = Math.min(r.Left, area.XStart); - r.Right = Math.max(r.Right, area.XEnd); - } - - final Rectangle first = myRectangles.getFirst(); - if (top < first.Top) { - myRectangles.add(0, new Rectangle(area.XStart, area.XEnd, top, Math.min(bottom, first.Top))); - } - - final Rectangle last = myRectangles.getLast(); - if (bottom > last.Bottom) { - myRectangles.add(new Rectangle(area.XStart, area.XEnd, Math.max(top, last.Bottom), bottom)); - } - } - - private void normalize() { - Rectangle previous = null; - for (ListIterator iter = myRectangles.listIterator(); iter.hasNext(); ) { - final Rectangle current = iter.next(); - if (previous != null) { - if ((previous.Left == current.Left) && (previous.Right == current.Right)) { - previous.Bottom = current.Bottom; - iter.remove(); - continue; - } - if ((previous.Bottom != current.Top) && - (current.Left <= previous.Right) && - (previous.Left <= current.Right)) { - iter.previous(); - iter.add(new Rectangle( - Math.max(previous.Left, current.Left), - Math.min(previous.Right, current.Right), - previous.Bottom, - current.Top - )); - iter.next(); - } - } - previous = current; - } - } - - int distanceTo(int x, int y) { - int distance = Integer.MAX_VALUE; - for (Rectangle r : myRectangles) { - final int xd = (r.Left > x) ? r.Left - x : ((r.Right < x) ? x - r.Right : 0); - final int yd = (r.Top > y) ? r.Top - y : ((r.Bottom < y) ? y - r.Bottom : 0); - distance = Math.min(distance, Math.max(xd, yd)); - if (distance == 0) { - break; - } - } - return distance; - } - - void draw(ZLPaintContext context) { - final LinkedList rectangles = new LinkedList(myRectangles); - while (!rectangles.isEmpty()) { - final LinkedList connected = new LinkedList(); - Rectangle previous = null; - for (final Iterator iter = rectangles.iterator(); iter.hasNext(); ) { - final Rectangle current = iter.next(); - if ((previous != null) && - ((previous.Left > current.Right) || (current.Left > previous.Right))) { - break; - } - iter.remove(); - connected.add(current); - previous = current; - } - - final LinkedList xList = new LinkedList(); - final LinkedList yList = new LinkedList(); - int x = 0, xPrev = 0; - - final ListIterator iter = connected.listIterator(); - Rectangle r = iter.next(); - x = r.Right + 2; - xList.add(x); yList.add(r.Top); - while (iter.hasNext()) { - xPrev = x; - r = iter.next(); - x = r.Right + 2; - if (x != xPrev) { - final int y = (x < xPrev) ? r.Top + 2 : r.Top; - xList.add(xPrev); yList.add(y); - xList.add(x); yList.add(y); - } - } - xList.add(x); yList.add(r.Bottom + 2); - - r = iter.previous(); - x = r.Left - 2; - xList.add(x); yList.add(r.Bottom + 2); - while (iter.hasPrevious()) { - xPrev = x; - r = iter.previous(); - x = r.Left - 2; - if (x != xPrev) { - final int y = (x > xPrev) ? r.Bottom : r.Bottom + 2; - xList.add(xPrev); yList.add(y); - xList.add(x); yList.add(y); - } - } - xList.add(x); yList.add(r.Top); - - final int xs[] = new int[xList.size()]; - final int ys[] = new int[yList.size()]; - int count = 0; - for (int xx : xList) { - xs[count++] = xx; - } - count = 0; - for (int yy : yList) { - ys[count++] = yy; - } - context.drawOutline(xs, ys); - } - } - - private static final class Rectangle { - int Left; - int Right; - int Top; - int Bottom; - - Rectangle(int left, int right, int top, int bottom) { - Left = left; - Right = right; - Top = top; - Bottom = bottom; - } - - Rectangle(Rectangle orig) { - Left = orig.Left; - Right = orig.Right; - Top = orig.Top; - Bottom = orig.Bottom; - } - } -} diff --git a/src/org/geometerplus/zlibrary/text/view/ZLTextManualHighlighting.java b/src/org/geometerplus/zlibrary/text/view/ZLTextManualHighlighting.java index cac2a537b..fb0f3171c 100644 --- a/src/org/geometerplus/zlibrary/text/view/ZLTextManualHighlighting.java +++ b/src/org/geometerplus/zlibrary/text/view/ZLTextManualHighlighting.java @@ -35,4 +35,9 @@ class ZLTextManualHighlighting extends ZLTextSimpleHighlighting { public ZLColor getForegroundColor() { return View.getHighlightingForegroundColor(); } + + @Override + public ZLColor getOutlineColor() { + return null; + } } diff --git a/src/org/geometerplus/zlibrary/text/view/ZLTextRegion.java b/src/org/geometerplus/zlibrary/text/view/ZLTextRegion.java index ad448f57e..be709b03f 100644 --- a/src/org/geometerplus/zlibrary/text/view/ZLTextRegion.java +++ b/src/org/geometerplus/zlibrary/text/view/ZLTextRegion.java @@ -21,6 +21,7 @@ package org.geometerplus.zlibrary.text.view; import java.util.*; +import org.geometerplus.zlibrary.core.view.Hull; import org.geometerplus.zlibrary.core.view.ZLPaintContext; public final class ZLTextRegion { @@ -139,7 +140,8 @@ public final class ZLTextRegion { private ZLTextElementArea[] myAreas; private final int myFromIndex; private int myToIndex; - private ZLTextHorizontalConvexHull myHull; + private Hull myHull; + private Hull myHull0; // convex hull for left page column ZLTextRegion(Soul soul, List list, int fromIndex) { mySoul = soul; @@ -157,7 +159,7 @@ public final class ZLTextRegion { return mySoul; } - private ZLTextElementArea[] textAreas() { + ZLTextElementArea[] textAreas() { if (myAreas == null || myAreas.length != myToIndex - myFromIndex) { synchronized (myAreaList) { myAreas = new ZLTextElementArea[myToIndex - myFromIndex]; @@ -168,12 +170,24 @@ public final class ZLTextRegion { } return myAreas; } - private ZLTextHorizontalConvexHull convexHull() { + Hull hull() { if (myHull == null) { - myHull = new ZLTextHorizontalConvexHull(textAreas()); + myHull = HullUtil.hull(textAreas()); } return myHull; } + Hull hull0() { + if (myHull0 == null) { + final List column0 = new ArrayList(); + for (ZLTextElementArea a : textAreas()) { + if (a.ColumnIndex == 0) { + column0.add(a); + } + } + myHull0 = HullUtil.hull(column0); + } + return myHull0; + } ZLTextElementArea getFirstArea() { return textAreas()[0]; @@ -208,12 +222,42 @@ public final class ZLTextRegion { return getLastArea().YEnd; } - void draw(ZLPaintContext context) { - convexHull().draw(context); + int distanceTo(int x, int y) { + return hull().distanceTo(x, y); } - int distanceTo(int x, int y) { - return convexHull().distanceTo(x, y); + boolean isBefore(int x, int y, int columnIndex) { + switch (columnIndex) { + default: + case -1: + return hull().isBefore(x, y); + case 0: + { + int count0 = 0; + int count1 = 0; + for (ZLTextElementArea area : textAreas()) { + if (area.ColumnIndex == 0) { + ++count0; + } else { + ++count1; + } + } + if (count0 == 0) { + return false; + } else if (count1 == 0) { + return hull().isBefore(x, y); + } else { + return hull0().isBefore(x, y); + } + } + case 1: + for (ZLTextElementArea area : textAreas()) { + if (area.ColumnIndex == 0) { + return true; + } + } + return hull().isBefore(x, y); + } } boolean isAtRightOf(ZLTextRegion other) { diff --git a/src/org/geometerplus/zlibrary/text/view/ZLTextSelection.java b/src/org/geometerplus/zlibrary/text/view/ZLTextSelection.java index 65f5833e7..b0cc9b8b8 100644 --- a/src/org/geometerplus/zlibrary/text/view/ZLTextSelection.java +++ b/src/org/geometerplus/zlibrary/text/view/ZLTextSelection.java @@ -20,6 +20,7 @@ package org.geometerplus.zlibrary.text.view; import org.geometerplus.zlibrary.core.util.ZLColor; +import org.geometerplus.zlibrary.core.view.SelectionCursor; class ZLTextSelection extends ZLTextHighlighting { static class Point { @@ -37,7 +38,7 @@ class ZLTextSelection extends ZLTextHighlighting { private ZLTextRegion.Soul myLeftMostRegionSoul; private ZLTextRegion.Soul myRightMostRegionSoul; - private ZLTextSelectionCursor myCursorInMovement = ZLTextSelectionCursor.None; + private SelectionCursor.Which myCursorInMovement = null; private final Point myCursorInMovementPoint = new Point(-1, -1); private Scroller myScroller; @@ -46,10 +47,6 @@ class ZLTextSelection extends ZLTextHighlighting { myView = view; } - ZLTextRegion.Soul getSoul() { - return myLeftMostRegionSoul != null && myLeftMostRegionSoul.equals(myRightMostRegionSoul) ? myLeftMostRegionSoul : null; - } - @Override public boolean isEmpty() { return myLeftMostRegionSoul == null; @@ -63,17 +60,17 @@ class ZLTextSelection extends ZLTextHighlighting { stop(); myLeftMostRegionSoul = null; myRightMostRegionSoul = null; - myCursorInMovement = ZLTextSelectionCursor.None; + myCursorInMovement = null; return true; } - void setCursorInMovement(ZLTextSelectionCursor cursor, int x, int y) { - myCursorInMovement = cursor; + void setCursorInMovement(SelectionCursor.Which which, int x, int y) { + myCursorInMovement = which; myCursorInMovementPoint.X = x; myCursorInMovementPoint.Y = y; } - ZLTextSelectionCursor getCursorInMovement() { + SelectionCursor.Which getCursorInMovement() { return myCursorInMovement; } @@ -96,7 +93,7 @@ class ZLTextSelection extends ZLTextHighlighting { } void stop() { - myCursorInMovement = ZLTextSelectionCursor.None; + myCursorInMovement = null; if (myScroller != null) { myScroller.stop(); myScroller = null; @@ -120,7 +117,8 @@ class ZLTextSelection extends ZLTextHighlighting { myScroller = new Scroller(page, false, x, y); return; } - } else if (lastArea != null && y + ZLTextSelectionCursor.getHeight() / 2 + ZLTextSelectionCursor.getAccent() / 2 > lastArea.YEnd) { + //} else if (lastArea != null && y + ZLTextSelectionCursor.getHeight() / 2 + ZLTextSelectionCursor.getAccent() / 2 > lastArea.YEnd) { + } else if (lastArea != null && y > lastArea.YEnd) { if (myScroller != null && !myScroller.scrollsForward()) { myScroller.stop(); myScroller = null; @@ -141,21 +139,40 @@ class ZLTextSelection extends ZLTextHighlighting { } ZLTextRegion region = myView.findRegion(x, y, myView.maxSelectionDistance(), ZLTextRegion.AnyRegionFilter); - if (region == null && myScroller != null) { - region = myView.findRegion(x, y, ZLTextRegion.AnyRegionFilter); + if (region == null) { + final ZLTextElementAreaVector.RegionPair pair = + myView.findRegionsPair(x, y, ZLTextRegion.AnyRegionFilter); + if (pair.Before != null || pair.After != null) { + final ZLTextRegion.Soul base = + myCursorInMovement == SelectionCursor.Which.Right + ? myLeftMostRegionSoul : myRightMostRegionSoul; + if (pair.Before != null) { + if (base.compareTo(pair.Before.getSoul()) <= 0) { + region = pair.Before; + } else { + region = pair.After; + } + } else { + if (base.compareTo(pair.After.getSoul()) >= 0) { + region = pair.After; + } else { + region = pair.Before; + } + } + } } if (region == null) { return; } final ZLTextRegion.Soul soul = region.getSoul(); - if (myCursorInMovement == ZLTextSelectionCursor.Right) { + if (myCursorInMovement == SelectionCursor.Which.Right) { if (myLeftMostRegionSoul.compareTo(soul) <= 0) { myRightMostRegionSoul = soul; } else { myRightMostRegionSoul = myLeftMostRegionSoul; myLeftMostRegionSoul = soul; - myCursorInMovement = ZLTextSelectionCursor.Left; + myCursorInMovement = SelectionCursor.Which.Left; } } else { if (myRightMostRegionSoul.compareTo(soul) >= 0) { @@ -163,11 +180,11 @@ class ZLTextSelection extends ZLTextHighlighting { } else { myLeftMostRegionSoul = myRightMostRegionSoul; myRightMostRegionSoul = soul; - myCursorInMovement = ZLTextSelectionCursor.Right; + myCursorInMovement = SelectionCursor.Which.Right; } } - if (myCursorInMovement == ZLTextSelectionCursor.Right) { + if (myCursorInMovement == SelectionCursor.Which.Right) { if (hasPartAfterPage(page)) { myView.turnPage(true, ZLTextView.ScrollingMode.SCROLL_LINES, 1); myView.Application.getViewWidget().reset(); @@ -276,6 +293,11 @@ class ZLTextSelection extends ZLTextHighlighting { return myView.getSelectionForegroundColor(); } + @Override + public ZLColor getOutlineColor() { + return null; + } + private class Scroller implements Runnable { private final ZLTextPage myPage; private final boolean myScrollForward; diff --git a/src/org/geometerplus/zlibrary/text/view/ZLTextSimpleHighlighting.java b/src/org/geometerplus/zlibrary/text/view/ZLTextSimpleHighlighting.java index 31dc8d5dc..836ad7555 100644 --- a/src/org/geometerplus/zlibrary/text/view/ZLTextSimpleHighlighting.java +++ b/src/org/geometerplus/zlibrary/text/view/ZLTextSimpleHighlighting.java @@ -19,6 +19,8 @@ package org.geometerplus.zlibrary.text.view; +import org.geometerplus.zlibrary.core.view.HorizontalConvexHull; + public abstract class ZLTextSimpleHighlighting extends ZLTextHighlighting { protected final ZLTextView View; private final ZLTextPosition myStartPosition; diff --git a/src/org/geometerplus/zlibrary/text/view/ZLTextView.java b/src/org/geometerplus/zlibrary/text/view/ZLTextView.java index 00228205d..adeb2335d 100644 --- a/src/org/geometerplus/zlibrary/text/view/ZLTextView.java +++ b/src/org/geometerplus/zlibrary/text/view/ZLTextView.java @@ -21,13 +21,15 @@ package org.geometerplus.zlibrary.text.view; import java.util.*; +import android.util.FloatMath; + import org.geometerplus.zlibrary.core.application.ZLApplication; import org.geometerplus.zlibrary.core.filesystem.ZLFile; import org.geometerplus.zlibrary.core.image.ZLImageData; import org.geometerplus.zlibrary.core.library.ZLibrary; import org.geometerplus.zlibrary.core.util.RationalNumber; import org.geometerplus.zlibrary.core.util.ZLColor; -import org.geometerplus.zlibrary.core.view.ZLPaintContext; +import org.geometerplus.zlibrary.core.view.*; import org.geometerplus.zlibrary.text.model.*; import org.geometerplus.zlibrary.text.hyphenation.*; @@ -327,13 +329,9 @@ public abstract class ZLTextView extends ZLTextViewBase { } } - protected void moveSelectionCursorTo(ZLTextSelectionCursor cursor, int x, int y, boolean inMovement) { - if (inMovement) { - y -= ZLTextSelectionCursor.getHeight() / 2 + ZLTextSelectionCursor.getAccent() / 2; - } else { - y -= getTextStyleCollection().getBaseStyle().getFontSize() / 2; - } - mySelection.setCursorInMovement(cursor, x, y); + protected void moveSelectionCursorTo(SelectionCursor.Which which, int x, int y) { + y -= getTextStyleCollection().getBaseStyle().getFontSize() / 2; + mySelection.setCursorInMovement(which, x, y); mySelection.expandTo(myCurrentPage, x, y); Application.getViewWidget().reset(); Application.getViewWidget().repaint(); @@ -345,104 +343,68 @@ public abstract class ZLTextView extends ZLTextViewBase { Application.getViewWidget().repaint(); } - protected ZLTextSelectionCursor getSelectionCursorInMovement() { + protected SelectionCursor.Which getSelectionCursorInMovement() { return mySelection.getCursorInMovement(); } - private ZLTextSelection.Point getSelectionCursorPoint(ZLTextPage page, ZLTextSelectionCursor cursor) { - if (cursor == ZLTextSelectionCursor.None) { + private ZLTextSelection.Point getSelectionCursorPoint(ZLTextPage page, SelectionCursor.Which which) { + if (which == null) { return null; } - if (cursor == mySelection.getCursorInMovement()) { + if (which == mySelection.getCursorInMovement()) { return mySelection.getCursorInMovementPoint(); } - if (cursor == ZLTextSelectionCursor.Left) { + if (which == SelectionCursor.Which.Left) { if (mySelection.hasPartBeforePage(page)) { return null; } - final ZLTextElementArea selectionStartArea = mySelection.getStartArea(page); - if (selectionStartArea != null) { - return new ZLTextSelection.Point(selectionStartArea.XStart, selectionStartArea.YEnd); + final ZLTextElementArea area = mySelection.getStartArea(page); + if (area != null) { + return new ZLTextSelection.Point(area.XStart, (area.YStart + area.YEnd) / 2); } } else { if (mySelection.hasPartAfterPage(page)) { return null; } - final ZLTextElementArea selectionEndArea = mySelection.getEndArea(page); - if (selectionEndArea != null) { - return new ZLTextSelection.Point(selectionEndArea.XEnd, selectionEndArea.YEnd); + final ZLTextElementArea area = mySelection.getEndArea(page); + if (area != null) { + return new ZLTextSelection.Point(area.XEnd, (area.YStart + area.YEnd) / 2); } } return null; } - private int distanceToCursor(int x, int y, ZLTextSelection.Point cursorPoint) { - if (cursorPoint == null) { - return Integer.MAX_VALUE; - } - - final int dX, dY; - - final int w = ZLTextSelectionCursor.getWidth() / 2; - if (x < cursorPoint.X - w) { - dX = cursorPoint.X - w - x; - } else if (x > cursorPoint.X + w) { - dX = x - cursorPoint.X - w; - } else { - dX = 0; - } - - final int h = ZLTextSelectionCursor.getHeight(); - if (y < cursorPoint.Y) { - dY = cursorPoint.Y - y; - } else if (y > cursorPoint.Y + h) { - dY = y - cursorPoint.Y - h; - } else { - dY = 0; - } - - return Math.max(dX, dY); + private float distanceToCursor(int x, int y, SelectionCursor.Which which) { + final ZLTextSelection.Point point = getSelectionCursorPoint(myCurrentPage, which); + return point != null ? FloatMath.hypot(x - point.X, y - point.Y) : Float.MAX_VALUE; } - protected ZLTextSelectionCursor findSelectionCursor(int x, int y) { - return findSelectionCursor(x, y, Integer.MAX_VALUE); + protected SelectionCursor.Which findSelectionCursor(int x, int y) { + return findSelectionCursor(x, y, Float.MAX_VALUE); } - protected ZLTextSelectionCursor findSelectionCursor(int x, int y, int maxDistance) { + protected SelectionCursor.Which findSelectionCursor(int x, int y, float maxDistance) { if (mySelection.isEmpty()) { - return ZLTextSelectionCursor.None; + return null; } - final int leftDistance = distanceToCursor( - x, y, getSelectionCursorPoint(myCurrentPage, ZLTextSelectionCursor.Left) - ); - final int rightDistance = distanceToCursor( - x, y, getSelectionCursorPoint(myCurrentPage, ZLTextSelectionCursor.Right) - ); + final float leftDistance = distanceToCursor(x, y, SelectionCursor.Which.Left); + final float rightDistance = distanceToCursor(x, y, SelectionCursor.Which.Right); if (rightDistance < leftDistance) { - return rightDistance <= maxDistance ? ZLTextSelectionCursor.Right : ZLTextSelectionCursor.None; + return rightDistance <= maxDistance ? SelectionCursor.Which.Right : null; } else { - return leftDistance <= maxDistance ? ZLTextSelectionCursor.Left : ZLTextSelectionCursor.None; + return leftDistance <= maxDistance ? SelectionCursor.Which.Left : null; } } - private void drawSelectionCursor(ZLPaintContext context, ZLTextSelection.Point pt) { - if (pt == null) { - return; + private void drawSelectionCursor(ZLPaintContext context, ZLTextPage page, SelectionCursor.Which which) { + final ZLTextSelection.Point pt = getSelectionCursorPoint(page, which); + if (pt != null) { + SelectionCursor.draw(context, which, pt.X, pt.Y, getSelectionBackgroundColor()); } - - final int w = ZLTextSelectionCursor.getWidth() / 2; - final int h = ZLTextSelectionCursor.getHeight(); - final int a = ZLTextSelectionCursor.getAccent(); - final int[] xs = { pt.X, pt.X + w, pt.X + w, pt.X - w, pt.X - w }; - final int[] ys = { pt.Y - a, pt.Y, pt.Y + h, pt.Y + h, pt.Y }; - context.setFillColor(context.getBackgroundColor(), 192); - context.fillPolygon(xs, ys); - context.setLineColor(getTextColor(ZLTextHyperlink.NO_LINK)); - context.drawPolygonalLine(xs, ys); } @Override @@ -501,34 +463,22 @@ public abstract class ZLTextView extends ZLTextViewBase { int x = getLeftMargin(); int y = getTopMargin(); int index = 0; + int columnIndex = 0; ZLTextLineInfo previousInfo = null; for (ZLTextLineInfo info : lineInfos) { info.adjust(previousInfo); - prepareTextLine(page, info, x, y); + prepareTextLine(page, info, x, y, columnIndex); y += info.Height + info.Descent + info.VSpaceAfter; labels[++index] = page.TextElementMap.size(); if (index == page.Column0Height) { y = getTopMargin(); x += page.getTextWidth() + getSpaceBetweenColumns(); + columnIndex = 1; } previousInfo = info; } final List hilites = findHilites(page); - if (!hilites.isEmpty()) { - x = getLeftMargin(); - y = getTopMargin(); - index = 0; - for (ZLTextLineInfo info : lineInfos) { - drawHighlightings(page, hilites, info, labels[index], labels[index + 1], x, y); - y += info.Height + info.Descent + info.VSpaceAfter; - ++index; - if (index == page.Column0Height) { - y = getTopMargin(); - x += page.getTextWidth() + getSpaceBetweenColumns(); - } - } - } x = getLeftMargin(); y = getTopMargin(); @@ -543,13 +493,34 @@ public abstract class ZLTextView extends ZLTextViewBase { } } - final ZLTextRegion outlinedElementRegion = getOutlinedRegion(page); - if (outlinedElementRegion != null && myShowOutline) { - outlinedElementRegion.draw(context); + for (ZLTextHighlighting h : hilites) { + int mode = Hull.DrawMode.None; + + final ZLColor bgColor = h.getBackgroundColor(); + if (bgColor != null) { + context.setFillColor(bgColor, 128); + mode |= Hull.DrawMode.Fill; + } + + final ZLColor outlineColor = h.getOutlineColor(); + if (outlineColor != null) { + context.setLineColor(outlineColor); + mode |= Hull.DrawMode.Outline; + } + + if (mode != Hull.DrawMode.None) { + h.hull(page).draw(getContext(), mode); + } } - drawSelectionCursor(context, getSelectionCursorPoint(page, ZLTextSelectionCursor.Left)); - drawSelectionCursor(context, getSelectionCursorPoint(page, ZLTextSelectionCursor.Right)); + final ZLTextRegion outlinedElementRegion = getOutlinedRegion(page); + if (outlinedElementRegion != null && myShowOutline) { + context.setLineColor(getSelectionBackgroundColor()); + outlinedElementRegion.hull().draw(context, Hull.DrawMode.Outline); + } + + drawSelectionCursor(context, page, SelectionCursor.Which.Left); + drawSelectionCursor(context, page, SelectionCursor.Which.Right); } private ZLTextPage getPage(PageIndex pageIndex) { @@ -847,45 +818,6 @@ public abstract class ZLTextView extends ZLTextViewBase { return hilites; } - private void drawHighlightings(ZLTextPage page, List hilites, ZLTextLineInfo info, int from, int to, int x, int y) { - if (from == to) { - return; - } - - final ZLTextElementArea fromArea = page.TextElementMap.get(from); - final ZLTextElementArea toArea = page.TextElementMap.get(to - 1); - for (ZLTextHighlighting h : hilites) { - final ZLColor bgColor = h.getBackgroundColor(); - if (bgColor == null) { - continue; - } - final ZLTextElementArea selectionStartArea = h.getStartArea(page); - if (selectionStartArea == null || selectionStartArea.compareTo(toArea) > 0) { - continue; - } - final ZLTextElementArea selectionEndArea = h.getEndArea(page); - if (selectionEndArea == null || selectionEndArea.compareTo(fromArea) < 0) { - continue; - } - - final int top = y + 1; - int left, right, bottom = y + info.Height + info.Descent; - if (selectionStartArea.compareTo(fromArea) < 0) { - left = x; - } else { - left = selectionStartArea.XStart; - } - if (selectionEndArea.compareTo(toArea) > 0) { - right = x + page.getTextWidth() - 1; - bottom += info.VSpaceAfter; - } else { - right = selectionEndArea.XEnd; - } - getContext().setFillColor(bgColor); - getContext().fillRectangle(left, top, right, bottom); - } - } - protected abstract ZLPaintContext.ColorAdjustingMode getAdjustingModeForImages(); private static final char[] SPACE = new char[] { ' ' }; @@ -895,9 +827,13 @@ public abstract class ZLTextView extends ZLTextViewBase { int index = from; final int endElementIndex = info.EndElementIndex; int charIndex = info.RealStartCharIndex; + final List pageAreas = page.TextElementMap.areas(); + if (to > pageAreas.size()) { + return; + } for (int wordIndex = info.RealStartElementIndex; wordIndex != endElementIndex && index < to; ++wordIndex, charIndex = 0) { final ZLTextElement element = paragraph.getElement(wordIndex); - final ZLTextElementArea area = page.TextElementMap.get(index); + final ZLTextElementArea area = pageAreas.get(index); if (element == area.Element) { ++index; if (area.ChangeStyle) { @@ -961,7 +897,7 @@ public abstract class ZLTextView extends ZLTextViewBase { } } if (index != to) { - ZLTextElementArea area = page.TextElementMap.get(index++); + ZLTextElementArea area = pageAreas.get(index++); if (area.ChangeStyle) { setTextStyle(area.Style); } @@ -1306,7 +1242,7 @@ public abstract class ZLTextView extends ZLTextViewBase { return info; } - private void prepareTextLine(ZLTextPage page, ZLTextLineInfo info, int x, int y) { + private void prepareTextLine(ZLTextPage page, ZLTextLineInfo info, int x, int y, int columnIndex) { y = Math.min(y + info.Height, getTopMargin() + page.getTextHeight() - 1); final ZLPaintContext context = getContext(); @@ -1357,7 +1293,7 @@ public abstract class ZLTextView extends ZLTextViewBase { true, // is last in element false, // add hyphenation sign false, // changed style - getTextStyle(), element, x, x + spaceLength, y, y + getTextStyle(), element, x, x + spaceLength, y, y, columnIndex ); } else { spaceElement = null; @@ -1381,7 +1317,7 @@ public abstract class ZLTextView extends ZLTextViewBase { true, // is last in element false, // add hyphenation sign changeStyle, getTextStyle(), element, - x, x + width - 1, y - height + 1, y + descent + x, x + width - 1, y - height + 1, y + descent, columnIndex )); changeStyle = false; wordOccurred = true; @@ -1407,7 +1343,7 @@ public abstract class ZLTextView extends ZLTextViewBase { false, // is last in element addHyphenationSign, changeStyle, getTextStyle(), word, - x, x + width - 1, y - height + 1, y + descent + x, x + width - 1, y - height + 1, y + descent, columnIndex ) ); } @@ -1773,6 +1709,10 @@ public abstract class ZLTextView extends ZLTextViewBase { return myCurrentPage.TextElementMap.findRegion(x, y, maxDistance, filter); } + protected ZLTextElementAreaVector.RegionPair findRegionsPair(int x, int y, ZLTextRegion.Filter filter) { + return myCurrentPage.TextElementMap.findRegionsPair(x, y, getColumnIndex(x), filter); + } + protected boolean initSelection(int x, int y) { y -= getTextStyleCollection().getBaseStyle().getFontSize() / 2; if (!mySelection.start(x, y)) { @@ -1790,8 +1730,8 @@ public abstract class ZLTextView extends ZLTextViewBase { } } - public ZLTextRegion.Soul getSelectionSoul() { - return mySelection.getSoul(); + public ZLTextHighlighting getSelectionHighlighting() { + return mySelection; } public int getSelectionStartY() { diff --git a/src/org/geometerplus/zlibrary/text/view/ZLTextViewBase.java b/src/org/geometerplus/zlibrary/text/view/ZLTextViewBase.java index d20086ab6..421bcf957 100644 --- a/src/org/geometerplus/zlibrary/text/view/ZLTextViewBase.java +++ b/src/org/geometerplus/zlibrary/text/view/ZLTextViewBase.java @@ -110,6 +110,13 @@ abstract class ZLTextViewBase extends ZLView { return getContextHeight() - getTopMargin() - getBottomMargin(); } + protected int getColumnIndex(int x) { + if (!twoColumnView()) { + return -1; + } + return 2 * x <= getContextWidth() + getLeftMargin() - getRightMargin() ? 0 : 1; + } + public int getTextColumnWidth() { return twoColumnView() ? (getContextWidth() - getLeftMargin() - getSpaceBetweenColumns() - getRightMargin()) / 2 diff --git a/src/org/geometerplus/zlibrary/text/view/ZLTextWord.java b/src/org/geometerplus/zlibrary/text/view/ZLTextWord.java index 6c3282fef..b7580957b 100644 --- a/src/org/geometerplus/zlibrary/text/view/ZLTextWord.java +++ b/src/org/geometerplus/zlibrary/text/view/ZLTextWord.java @@ -103,6 +103,10 @@ public final class ZLTextWord extends ZLTextElement { @Override public String toString() { + return getString(); + } + + public String getString() { return new String(Data, Offset, Length); } }