1
0
Fork 0
mirror of https://github.com/geometer/FBReaderJ.git synced 2025-10-05 02:39:23 +02:00

text highlighting, misc fixes

This commit is contained in:
Nikolay Pultsin 2011-06-19 16:18:48 +01:00
parent 9296cef55e
commit 083629c6bf
7 changed files with 252 additions and 87 deletions

View file

@ -126,6 +126,8 @@ final class NavigationPopup extends PopupPanel {
} }
StartPosition = null; StartPosition = null;
Application.hideActivePopup(); Application.hideActivePopup();
getReader().getViewWidget().reset();
getReader().getViewWidget().repaint();
} }
}; };
btnOk.setOnClickListener(listener); btnOk.setOnClickListener(listener);

View file

@ -66,8 +66,15 @@ public class ApiServerImplementation extends ApiInterface.Stub implements ApiMet
setPageStart((TextPosition)parameters[0]); setPageStart((TextPosition)parameters[0]);
return ApiObject.Void.Instance; return ApiObject.Void.Instance;
case HIGHLIGHT_AREA: case HIGHLIGHT_AREA:
{
myReader.getTextView().highlight(
getZLTextPosition((TextPosition)parameters[0]),
getZLTextPosition((TextPosition)parameters[1])
);
return ApiObject.Void.Instance; return ApiObject.Void.Instance;
}
case CLEAR_HIGHLIGHTING: case CLEAR_HIGHLIGHTING:
myReader.getTextView().clearHighlighting();
return ApiObject.Void.Instance; return ApiObject.Void.Instance;
default: default:
return unsupportedMethodError(method); return unsupportedMethodError(method);
@ -96,6 +103,14 @@ public class ApiServerImplementation extends ApiInterface.Stub implements ApiMet
); );
} }
private ZLTextFixedPosition getZLTextPosition(TextPosition position) {
return new ZLTextFixedPosition(
position.ParagraphIndex,
position.ElementIndex,
position.CharIndex
);
}
private boolean isPageEndOfSection() { private boolean isPageEndOfSection() {
final ZLTextWordCursor cursor = myReader.getTextView().getEndCursor(); final ZLTextWordCursor cursor = myReader.getTextView().getEndCursor();
return cursor.isEndOfParagraph() && cursor.getParagraphCursor().isEndOfSection(); return cursor.isEndOfParagraph() && cursor.getParagraphCursor().isEndOfSection();

View file

@ -0,0 +1,30 @@
/*
* Copyright (C) 2007-2011 Geometer Plus <contact@geometerplus.com>
*
* 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;
interface ZLTextAbstractHighlighting {
boolean clear();
boolean isEmpty();
ZLTextPosition getStartPosition();
ZLTextPosition getEndPosition();
ZLTextElementArea getStartArea(ZLTextPage page);
ZLTextElementArea getEndArea(ZLTextPage page);
}

View file

@ -28,69 +28,105 @@ final class ZLTextElementAreaVector {
Collections.synchronizedList(new ArrayList<ZLTextRegion>()); Collections.synchronizedList(new ArrayList<ZLTextRegion>());
private ZLTextRegion myCurrentElementRegion; private ZLTextRegion myCurrentElementRegion;
public void clear() { void clear() {
myElementRegions.clear(); myElementRegions.clear();
myCurrentElementRegion = null; myCurrentElementRegion = null;
myAreas.clear(); myAreas.clear();
} }
public boolean isEmpty() {
return myAreas.isEmpty();
}
public int size() { public int size() {
return myAreas.size(); return myAreas.size();
} }
// TODO: remove this unsafe method
public ZLTextElementArea get(int index) { public ZLTextElementArea get(int index) {
return myAreas.get(index); return myAreas.get(index);
} }
public boolean add(ZLTextElementArea area) { public ZLTextElementArea getFirstArea() {
if (myCurrentElementRegion != null synchronized (myAreas) {
&& myCurrentElementRegion.getSoul().accepts(area)) { return myAreas.isEmpty() ? null : myAreas.get(0);
myCurrentElementRegion.extend();
} else {
ZLTextRegion.Soul soul = null;
final ZLTextHyperlink hyperlink = area.Style.Hyperlink;
if (hyperlink.Id != null) {
soul = new ZLTextHyperlinkRegionSoul(area, hyperlink);
} else if (area.Element instanceof ZLTextImageElement) {
soul = new ZLTextImageRegionSoul(area, (ZLTextImageElement)area.Element);
} else if (area.Element instanceof ZLTextWord && !((ZLTextWord)area.Element).isASpace()) {
soul = new ZLTextWordRegionSoul(area, (ZLTextWord)area.Element);
}
if (soul != null) {
myCurrentElementRegion = new ZLTextRegion(soul, myAreas, size());
myElementRegions.add(myCurrentElementRegion);
} else {
myCurrentElementRegion = null;
}
} }
return myAreas.add(area);
} }
ZLTextElementArea binarySearch(int x, int y) { public ZLTextElementArea getLastArea() {
int left = 0; synchronized (myAreas) {
int right = size(); return myAreas.isEmpty() ? null : myAreas.get(myAreas.size() - 1);
while (left < right) { }
final int middle = (left + right) / 2; }
final ZLTextElementArea candidate = get(middle);
if (candidate.YStart > y) { public boolean add(ZLTextElementArea area) {
right = middle; synchronized (myAreas) {
} else if (candidate.YEnd < y) { if (myCurrentElementRegion != null
left = middle + 1; && myCurrentElementRegion.getSoul().accepts(area)) {
} else if (candidate.XStart > x) { myCurrentElementRegion.extend();
right = middle;
} else if (candidate.XEnd < x) {
left = middle + 1;
} else { } else {
return candidate; ZLTextRegion.Soul soul = null;
final ZLTextHyperlink hyperlink = area.Style.Hyperlink;
if (hyperlink.Id != null) {
soul = new ZLTextHyperlinkRegionSoul(area, hyperlink);
} else if (area.Element instanceof ZLTextImageElement) {
soul = new ZLTextImageRegionSoul(area, (ZLTextImageElement)area.Element);
} else if (area.Element instanceof ZLTextWord && !((ZLTextWord)area.Element).isASpace()) {
soul = new ZLTextWordRegionSoul(area, (ZLTextWord)area.Element);
}
if (soul != null) {
myCurrentElementRegion = new ZLTextRegion(soul, myAreas, myAreas.size());
myElementRegions.add(myCurrentElementRegion);
} else {
myCurrentElementRegion = null;
}
}
return myAreas.add(area);
}
}
ZLTextElementArea getFirstAfter(ZLTextPosition position) {
synchronized (myAreas) {
for (ZLTextElementArea area : myAreas) {
if (position.compareTo(area) <= 0) {
return area;
}
} }
} }
return null; return null;
} }
ZLTextElementArea getLastBefore(ZLTextPosition position) {
synchronized (myAreas) {
for (int i = myAreas.size() - 1; i >= 0; --i) {
final ZLTextElementArea area = myAreas.get(i);
if (position.compareTo(area) > 0) {
return area;
}
}
}
return null;
}
ZLTextElementArea binarySearch(int x, int y) {
synchronized (myAreas) {
int left = 0;
int right = myAreas.size();
while (left < right) {
final int middle = (left + right) / 2;
final ZLTextElementArea candidate = myAreas.get(middle);
if (candidate.YStart > y) {
right = middle;
} else if (candidate.YEnd < y) {
left = middle + 1;
} else if (candidate.XStart > x) {
right = middle;
} else if (candidate.XEnd < x) {
left = middle + 1;
} else {
return candidate;
}
}
return null;
}
}
ZLTextRegion getRegion(ZLTextRegion.Soul soul) { ZLTextRegion getRegion(ZLTextRegion.Soul soul) {
if (soul == null) { if (soul == null) {
return null; return null;

View file

@ -0,0 +1,59 @@
/*
* Copyright (C) 2007-2011 Geometer Plus <contact@geometerplus.com>
*
* 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;
class ZLTextHighlighting implements ZLTextAbstractHighlighting {
private ZLTextPosition myStartPosition;
private ZLTextPosition myEndPosition;
void setup(ZLTextPosition start, ZLTextPosition end) {
myStartPosition = new ZLTextFixedPosition(start);
myEndPosition = new ZLTextFixedPosition(end);
}
public boolean clear() {
if (isEmpty()) {
return false;
}
myStartPosition = null;
myEndPosition = null;
return true;
}
public boolean isEmpty() {
return myStartPosition == null;
}
public ZLTextPosition getStartPosition() {
return myStartPosition;
}
public ZLTextPosition getEndPosition() {
return myEndPosition;
}
public ZLTextElementArea getStartArea(ZLTextPage page) {
return page.TextElementMap.getFirstAfter(myStartPosition);
}
public ZLTextElementArea getEndArea(ZLTextPage page) {
return page.TextElementMap.getLastBefore(myEndPosition);
}
}

View file

@ -19,7 +19,7 @@
package org.geometerplus.zlibrary.text.view; package org.geometerplus.zlibrary.text.view;
public class ZLTextSelection { class ZLTextSelection implements ZLTextAbstractHighlighting {
static class Point { static class Point {
int X; int X;
int Y; int Y;
@ -45,11 +45,11 @@ public class ZLTextSelection {
myView = view; myView = view;
} }
boolean isEmpty() { public boolean isEmpty() {
return myLeftMostRegionSoul == null; return myLeftMostRegionSoul == null;
} }
boolean clear() { public boolean clear() {
if (isEmpty()) { if (isEmpty()) {
return false; return false;
} }
@ -103,7 +103,9 @@ public class ZLTextSelection {
} }
final ZLTextElementAreaVector vector = myView.myCurrentPage.TextElementMap; final ZLTextElementAreaVector vector = myView.myCurrentPage.TextElementMap;
if (!vector.isEmpty() && y < vector.get(0).YStart) { final ZLTextElementArea firstArea = vector.getFirstArea();
final ZLTextElementArea lastArea = vector.getLastArea();
if (firstArea != null && y < firstArea.YStart) {
if (myScroller != null && myScroller.scrollsForward()) { if (myScroller != null && myScroller.scrollsForward()) {
myScroller.stop(); myScroller.stop();
myScroller = null; myScroller = null;
@ -112,7 +114,7 @@ public class ZLTextSelection {
myScroller = new Scroller(false, x, y); myScroller = new Scroller(false, x, y);
return; return;
} }
} else if (!vector.isEmpty() && y + ZLTextSelectionCursor.getHeight() / 2 + ZLTextSelectionCursor.getAccent() / 2 > vector.get(vector.size() - 1).YEnd) { } else if (lastArea != null && y + ZLTextSelectionCursor.getHeight() / 2 + ZLTextSelectionCursor.getAccent() / 2 > lastArea.YEnd) {
if (myScroller != null && !myScroller.scrollsForward()) { if (myScroller != null && !myScroller.scrollsForward()) {
myScroller.stop(); myScroller.stop();
myScroller = null; myScroller = null;
@ -181,7 +183,7 @@ public class ZLTextSelection {
&& myRightMostRegionSoul.compareTo(area) >= 0; && myRightMostRegionSoul.compareTo(area) >= 0;
} }
ZLTextPosition getStartPosition() { public ZLTextPosition getStartPosition() {
if (isEmpty()) { if (isEmpty()) {
return null; return null;
} }
@ -192,7 +194,7 @@ public class ZLTextSelection {
); );
} }
ZLTextPosition getEndPosition() { public ZLTextPosition getEndPosition() {
if (isEmpty()) { if (isEmpty()) {
return null; return null;
} }
@ -203,38 +205,34 @@ public class ZLTextSelection {
); );
} }
ZLTextElementArea getStartArea(ZLTextPage page) { public ZLTextElementArea getStartArea(ZLTextPage page) {
if (isEmpty()) { if (isEmpty()) {
return null; return null;
} }
final ZLTextElementAreaVector vector = page.TextElementMap; final ZLTextElementAreaVector vector = page.TextElementMap;
if (vector.isEmpty()) {
return null;
}
final ZLTextRegion region = vector.getRegion(myLeftMostRegionSoul); final ZLTextRegion region = vector.getRegion(myLeftMostRegionSoul);
if (region != null) { if (region != null) {
return region.getFirstArea(); return region.getFirstArea();
} }
if (myLeftMostRegionSoul.compareTo(vector.get(0)) <= 0) { final ZLTextElementArea firstArea = vector.getFirstArea();
return vector.get(0); if (firstArea != null && myLeftMostRegionSoul.compareTo(firstArea) <= 0) {
return firstArea;
} }
return null; return null;
} }
ZLTextElementArea getEndArea(ZLTextPage page) { public ZLTextElementArea getEndArea(ZLTextPage page) {
if (isEmpty()) { if (isEmpty()) {
return null; return null;
} }
final ZLTextElementAreaVector vector = page.TextElementMap; final ZLTextElementAreaVector vector = page.TextElementMap;
if (vector.isEmpty()) {
return null;
}
final ZLTextRegion region = vector.getRegion(myRightMostRegionSoul); final ZLTextRegion region = vector.getRegion(myRightMostRegionSoul);
if (region != null) { if (region != null) {
return region.getLastArea(); return region.getLastArea();
} }
if (myRightMostRegionSoul.compareTo(vector.get(vector.size() - 1)) >= 0) { final ZLTextElementArea lastArea = vector.getLastArea();
return vector.get(vector.size() - 1); if (lastArea != null && myRightMostRegionSoul.compareTo(lastArea) >= 0) {
return lastArea;
} }
return null; return null;
} }
@ -244,10 +242,10 @@ public class ZLTextSelection {
return false; return false;
} }
final ZLTextElementAreaVector vector = page.TextElementMap; final ZLTextElementAreaVector vector = page.TextElementMap;
if (vector.isEmpty()) { final ZLTextElementArea firstPageArea = page.TextElementMap.getFirstArea();
if (firstPageArea == null) {
return false; return false;
} }
final ZLTextElementArea firstPageArea = vector.get(0);
final int cmp = myLeftMostRegionSoul.compareTo(firstPageArea); final int cmp = myLeftMostRegionSoul.compareTo(firstPageArea);
return cmp < 0 || (cmp == 0 && !firstPageArea.isFirstInElement()); return cmp < 0 || (cmp == 0 && !firstPageArea.isFirstInElement());
} }
@ -256,11 +254,10 @@ public class ZLTextSelection {
if (isEmpty()) { if (isEmpty()) {
return false; return false;
} }
final ZLTextElementAreaVector vector = page.TextElementMap; final ZLTextElementArea lastPageArea = page.TextElementMap.getLastArea();
if (vector.isEmpty()) { if (lastPageArea == null) {
return false; return false;
} }
final ZLTextElementArea lastPageArea = vector.get(vector.size() - 1);
final int cmp = myRightMostRegionSoul.compareTo(lastPageArea); final int cmp = myRightMostRegionSoul.compareTo(lastPageArea);
return cmp > 0 || (cmp == 0 && !lastPageArea.isLastInElement()); return cmp > 0 || (cmp == 0 && !lastPageArea.isLastInElement());
} }

View file

@ -25,6 +25,7 @@ import org.geometerplus.zlibrary.core.application.ZLApplication;
import org.geometerplus.zlibrary.core.view.ZLPaintContext; import org.geometerplus.zlibrary.core.view.ZLPaintContext;
import org.geometerplus.zlibrary.core.filesystem.ZLFile; import org.geometerplus.zlibrary.core.filesystem.ZLFile;
import org.geometerplus.zlibrary.core.filesystem.ZLResourceFile; import org.geometerplus.zlibrary.core.filesystem.ZLResourceFile;
import org.geometerplus.zlibrary.core.util.ZLColor;
import org.geometerplus.zlibrary.text.model.*; import org.geometerplus.zlibrary.text.model.*;
import org.geometerplus.zlibrary.text.hyphenation.*; import org.geometerplus.zlibrary.text.hyphenation.*;
@ -56,9 +57,16 @@ public abstract class ZLTextView extends ZLTextViewBase {
private final HashMap<ZLTextLineInfo,ZLTextLineInfo> myLineInfoCache = new HashMap<ZLTextLineInfo,ZLTextLineInfo>(); private final HashMap<ZLTextLineInfo,ZLTextLineInfo> myLineInfoCache = new HashMap<ZLTextLineInfo,ZLTextLineInfo>();
private ZLTextRegion.Soul mySelectedRegionSoul;
private boolean myHighlightSelectedRegion = true;
private ZLTextSelection mySelection;
private ZLTextHighlighting myHighlighting;
public ZLTextView(ZLApplication application) { public ZLTextView(ZLApplication application) {
super(application); super(application);
mySelection = new ZLTextSelection(this); mySelection = new ZLTextSelection(this);
myHighlighting = new ZLTextHighlighting();
} }
public synchronized void setModel(ZLTextModel model) { public synchronized void setModel(ZLTextModel model) {
@ -239,6 +247,19 @@ public abstract class ZLTextView extends ZLTextViewBase {
} }
} }
public void highlight(ZLTextPosition start, ZLTextPosition end) {
myHighlighting.setup(start, end);
Application.getViewWidget().reset();
Application.getViewWidget().repaint();
}
public void clearHighlighting() {
if (myHighlighting.clear()) {
Application.getViewWidget().reset();
Application.getViewWidget().repaint();
}
}
protected void moveSelectionCursorTo(ZLTextSelectionCursor cursor, int x, int y) { protected void moveSelectionCursorTo(ZLTextSelectionCursor cursor, int x, int y) {
y -= ZLTextSelectionCursor.getHeight() / 2 + ZLTextSelectionCursor.getAccent() / 2; y -= ZLTextSelectionCursor.getHeight() / 2 + ZLTextSelectionCursor.getAccent() / 2;
mySelection.setCursorInMovement(cursor, x, y); mySelection.setCursorInMovement(cursor, x, y);
@ -647,16 +668,15 @@ public abstract class ZLTextView extends ZLTextViewBase {
preparePaintInfo(); preparePaintInfo();
} }
private static final char[] SPACE = new char[] { ' ' }; private void drawBackgroung(
private void drawTextLine(ZLTextPage page, ZLTextLineInfo info, int from, int to, int y) { ZLTextAbstractHighlighting highligting, ZLColor color,
final ZLTextParagraphCursor paragraph = info.ParagraphCursor; ZLTextPage page, ZLTextLineInfo info, int from, int to, int y
final ZLPaintContext context = myContext; ) {
if (!highligting.isEmpty() && from != to) {
if (!mySelection.isEmpty() && from != to) {
final ZLTextElementArea fromArea = page.TextElementMap.get(from); final ZLTextElementArea fromArea = page.TextElementMap.get(from);
final ZLTextElementArea toArea = page.TextElementMap.get(to - 1); final ZLTextElementArea toArea = page.TextElementMap.get(to - 1);
final ZLTextElementArea selectionStartArea = mySelection.getStartArea(page); final ZLTextElementArea selectionStartArea = highligting.getStartArea(page);
final ZLTextElementArea selectionEndArea = mySelection.getEndArea(page); final ZLTextElementArea selectionEndArea = highligting.getEndArea(page);
if (selectionStartArea != null if (selectionStartArea != null
&& selectionEndArea != null && selectionEndArea != null
&& selectionStartArea.compareTo(toArea) <= 0 && selectionStartArea.compareTo(toArea) <= 0
@ -674,11 +694,19 @@ public abstract class ZLTextView extends ZLTextViewBase {
} else { } else {
right = selectionEndArea.XEnd; right = selectionEndArea.XEnd;
} }
context.setFillColor(getSelectedBackgroundColor()); myContext.setFillColor(color);
context.fillRectangle(left, top, right, bottom); myContext.fillRectangle(left, top, right, bottom);
} }
} }
}
private static final char[] SPACE = new char[] { ' ' };
private void drawTextLine(ZLTextPage page, ZLTextLineInfo info, int from, int to, int y) {
drawBackgroung(mySelection, getSelectedBackgroundColor(), page, info, from, to, y);
drawBackgroung(myHighlighting, getHighlightingColor(), page, info, from, to, y);
final ZLPaintContext context = myContext;
final ZLTextParagraphCursor paragraph = info.ParagraphCursor;
int index = from; int index = from;
final int endElementIndex = info.EndElementIndex; final int endElementIndex = info.EndElementIndex;
int charIndex = info.RealStartCharIndex; int charIndex = info.RealStartCharIndex;
@ -1348,10 +1376,6 @@ public abstract class ZLTextView extends ZLTextViewBase {
return false; return false;
} }
private ZLTextRegion.Soul mySelectedRegionSoul;
private ZLTextSelection mySelection;
private boolean myHighlightSelectedRegion = true;
public void hideSelectedRegionBorder() { public void hideSelectedRegionBorder() {
myHighlightSelectedRegion = false; myHighlightSelectedRegion = false;
Application.getViewWidget().reset(); Application.getViewWidget().reset();
@ -1399,8 +1423,7 @@ public abstract class ZLTextView extends ZLTextViewBase {
} }
public int getSelectionStartY() { public int getSelectionStartY() {
final ZLTextElementAreaVector vector = myCurrentPage.TextElementMap; if (mySelection.isEmpty()) {
if (mySelection.isEmpty() || vector.isEmpty()) {
return 0; return 0;
} }
final ZLTextElementArea selectionStartArea = mySelection.getStartArea(myCurrentPage); final ZLTextElementArea selectionStartArea = mySelection.getStartArea(myCurrentPage);
@ -1408,15 +1431,16 @@ public abstract class ZLTextView extends ZLTextViewBase {
return selectionStartArea.YStart; return selectionStartArea.YStart;
} }
if (mySelection.hasAPartBeforePage(myCurrentPage)) { if (mySelection.hasAPartBeforePage(myCurrentPage)) {
return vector.get(0).YStart; final ZLTextElementArea firstArea = myCurrentPage.TextElementMap.getFirstArea();
return firstArea != null ? firstArea.YStart : 0;
} else { } else {
return vector.get(vector.size() - 1).YEnd; final ZLTextElementArea lastArea = myCurrentPage.TextElementMap.getLastArea();
return lastArea != null ? lastArea.YEnd : 0;
} }
} }
public int getSelectionEndY() { public int getSelectionEndY() {
final ZLTextElementAreaVector vector = myCurrentPage.TextElementMap; if (mySelection.isEmpty()) {
if (mySelection.isEmpty() || vector.isEmpty()) {
return 0; return 0;
} }
final ZLTextElementArea selectionEndArea = mySelection.getEndArea(myCurrentPage); final ZLTextElementArea selectionEndArea = mySelection.getEndArea(myCurrentPage);
@ -1424,9 +1448,11 @@ public abstract class ZLTextView extends ZLTextViewBase {
return selectionEndArea.YEnd; return selectionEndArea.YEnd;
} }
if (mySelection.hasAPartAfterPage(myCurrentPage)) { if (mySelection.hasAPartAfterPage(myCurrentPage)) {
return vector.get(vector.size() - 1).YEnd; final ZLTextElementArea lastArea = myCurrentPage.TextElementMap.getLastArea();
return lastArea != null ? lastArea.YEnd : 0;
} else { } else {
return vector.get(0).YStart; final ZLTextElementArea firstArea = myCurrentPage.TextElementMap.getFirstArea();
return firstArea != null ? firstArea.YStart : 0;
} }
} }