/*
* Copyright (C) 2007-2012 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;
import java.util.*;
import org.geometerplus.zlibrary.core.application.ZLApplication;
import org.geometerplus.zlibrary.core.view.ZLPaintContext;
import org.geometerplus.zlibrary.core.filesystem.ZLFile;
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.hyphenation.*;
import org.geometerplus.zlibrary.text.view.style.ZLTextStyleCollection;
public abstract class ZLTextView extends ZLTextViewBase {
public static final int MAX_SELECTION_DISTANCE = 10;
public interface ScrollingMode {
int NO_OVERLAPPING = 0;
int KEEP_LINES = 1;
int SCROLL_LINES = 2;
int SCROLL_PERCENTAGE = 3;
};
private ZLTextModel myModel;
private interface SizeUnit {
int PIXEL_UNIT = 0;
int LINE_UNIT = 1;
};
private int myScrollingMode;
private int myOverlappingValue;
private ZLTextPage myPreviousPage = new ZLTextPage();
ZLTextPage myCurrentPage = new ZLTextPage();
private ZLTextPage myNextPage = new ZLTextPage();
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) {
super(application);
mySelection = new ZLTextSelection(this);
myHighlighting = new ZLTextHighlighting();
}
public synchronized void setModel(ZLTextModel model) {
ZLTextParagraphCursorCache.clear();
myModel = model;
myCurrentPage.reset();
myPreviousPage.reset();
myNextPage.reset();
if (myModel != null) {
final int paragraphsNumber = myModel.getParagraphsNumber();
if (paragraphsNumber > 0) {
myCurrentPage.moveStartCursor(ZLTextParagraphCursor.cursor(myModel, 0));
}
}
Application.getViewWidget().reset();
}
public ZLTextModel getModel() {
return myModel;
}
public ZLTextWordCursor getStartCursor() {
if (myCurrentPage.StartCursor.isNull()) {
preparePaintInfo(myCurrentPage);
}
return myCurrentPage.StartCursor;
}
public ZLTextWordCursor getEndCursor() {
if (myCurrentPage.EndCursor.isNull()) {
preparePaintInfo(myCurrentPage);
}
return myCurrentPage.EndCursor;
}
private synchronized void gotoMark(ZLTextMark mark) {
if (mark == null) {
return;
}
myPreviousPage.reset();
myNextPage.reset();
boolean doRepaint = false;
if (myCurrentPage.StartCursor.isNull()) {
doRepaint = true;
preparePaintInfo(myCurrentPage);
}
if (myCurrentPage.StartCursor.isNull()) {
return;
}
if (myCurrentPage.StartCursor.getParagraphIndex() != mark.ParagraphIndex ||
myCurrentPage.StartCursor.getMark().compareTo(mark) > 0) {
doRepaint = true;
gotoPosition(mark.ParagraphIndex, 0, 0);
preparePaintInfo(myCurrentPage);
}
if (myCurrentPage.EndCursor.isNull()) {
preparePaintInfo(myCurrentPage);
}
while (mark.compareTo(myCurrentPage.EndCursor.getMark()) > 0) {
doRepaint = true;
scrollPage(true, ScrollingMode.NO_OVERLAPPING, 0);
preparePaintInfo(myCurrentPage);
}
if (doRepaint) {
if (myCurrentPage.StartCursor.isNull()) {
preparePaintInfo(myCurrentPage);
}
Application.getViewWidget().reset();
Application.getViewWidget().repaint();
}
}
public synchronized int search(final String text, boolean ignoreCase, boolean wholeText, boolean backward, boolean thisSectionOnly) {
if (text.length() == 0) {
return 0;
}
int startIndex = 0;
int endIndex = myModel.getParagraphsNumber();
if (thisSectionOnly) {
// TODO: implement
}
int count = myModel.search(text, startIndex, endIndex, ignoreCase);
myPreviousPage.reset();
myNextPage.reset();
if (!myCurrentPage.StartCursor.isNull()) {
rebuildPaintInfo();
if (count > 0) {
ZLTextMark mark = myCurrentPage.StartCursor.getMark();
gotoMark(wholeText ?
(backward ? myModel.getLastMark() : myModel.getFirstMark()) :
(backward ? myModel.getPreviousMark(mark) : myModel.getNextMark(mark)));
}
Application.getViewWidget().reset();
Application.getViewWidget().repaint();
}
return count;
}
public boolean canFindNext() {
final ZLTextWordCursor end = myCurrentPage.EndCursor;
return !end.isNull() && (myModel != null) && (myModel.getNextMark(end.getMark()) != null);
}
public synchronized void findNext() {
final ZLTextWordCursor end = myCurrentPage.EndCursor;
if (!end.isNull()) {
gotoMark(myModel.getNextMark(end.getMark()));
}
}
public boolean canFindPrevious() {
final ZLTextWordCursor start = myCurrentPage.StartCursor;
return !start.isNull() && (myModel != null) && (myModel.getPreviousMark(start.getMark()) != null);
}
public synchronized void findPrevious() {
final ZLTextWordCursor start = myCurrentPage.StartCursor;
if (!start.isNull()) {
gotoMark(myModel.getPreviousMark(start.getMark()));
}
}
public void clearFindResults() {
if (!findResultsAreEmpty()) {
myModel.removeAllMarks();
rebuildPaintInfo();
Application.getViewWidget().reset();
Application.getViewWidget().repaint();
}
}
public boolean findResultsAreEmpty() {
return (myModel == null) || myModel.getMarks().isEmpty();
}
@Override
public synchronized void onScrollingFinished(PageIndex pageIndex) {
switch (pageIndex) {
case current:
break;
case previous:
{
final ZLTextPage swap = myNextPage;
myNextPage = myCurrentPage;
myCurrentPage = myPreviousPage;
myPreviousPage = swap;
myPreviousPage.reset();
if (myCurrentPage.PaintState == PaintStateEnum.NOTHING_TO_PAINT) {
preparePaintInfo(myNextPage);
myCurrentPage.EndCursor.setCursor(myNextPage.StartCursor);
myCurrentPage.PaintState = PaintStateEnum.END_IS_KNOWN;
} else if (!myCurrentPage.EndCursor.isNull() &&
!myNextPage.StartCursor.isNull() &&
!myCurrentPage.EndCursor.samePositionAs(myNextPage.StartCursor)) {
myNextPage.reset();
myNextPage.StartCursor.setCursor(myCurrentPage.EndCursor);
myNextPage.PaintState = PaintStateEnum.START_IS_KNOWN;
Application.getViewWidget().reset();
}
break;
}
case next:
{
final ZLTextPage swap = myPreviousPage;
myPreviousPage = myCurrentPage;
myCurrentPage = myNextPage;
myNextPage = swap;
myNextPage.reset();
if (myCurrentPage.PaintState == PaintStateEnum.NOTHING_TO_PAINT) {
preparePaintInfo(myPreviousPage);
myCurrentPage.StartCursor.setCursor(myPreviousPage.EndCursor);
myCurrentPage.PaintState = PaintStateEnum.START_IS_KNOWN;
}
break;
}
}
}
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) {
y -= ZLTextSelectionCursor.getHeight