/* * Copyright (C) 2007-2013 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.fbreader.fbreader; import java.util.ArrayList; import org.geometerplus.fbreader.bookmodel.BookModel; import org.geometerplus.fbreader.bookmodel.FBHyperlinkType; import org.geometerplus.fbreader.bookmodel.TOCTree; import org.geometerplus.fbreader.fbreader.options.PageTurningOptions; import org.geometerplus.zlibrary.core.filesystem.ZLFile; import org.geometerplus.zlibrary.core.filesystem.ZLResourceFile; import org.geometerplus.zlibrary.core.library.ZLibrary; import org.geometerplus.zlibrary.core.util.ZLColor; import org.geometerplus.zlibrary.core.view.ZLPaintContext; import org.geometerplus.zlibrary.text.model.ZLTextModel; import org.geometerplus.zlibrary.text.view.ZLTextHighlighting; import org.geometerplus.zlibrary.text.view.ZLTextHyperlink; import org.geometerplus.zlibrary.text.view.ZLTextHyperlinkRegionSoul; import org.geometerplus.zlibrary.text.view.ZLTextImageRegionSoul; import org.geometerplus.zlibrary.text.view.ZLTextRegion; import org.geometerplus.zlibrary.text.view.ZLTextSelectionCursor; import org.geometerplus.zlibrary.text.view.ZLTextView; import org.geometerplus.zlibrary.text.view.ZLTextWordRegionSoul; public final class FBView extends ZLTextView { private FBReaderApp myReader; FBView(FBReaderApp reader) { super(reader); myReader = reader; } public void setModel(ZLTextModel model) { super.setModel(model); if (myFooter != null) { myFooter.resetTOCMarks(); } } private int myStartY; private boolean myIsBrightnessAdjustmentInProgress; private int myStartBrightness; private TapZoneMap myZoneMap; private TapZoneMap getZoneMap() { final PageTurningOptions prefs = myReader.PageTurningOptions; String id = prefs.TapZoneMap.getValue(); if ("".equals(id)) { id = prefs.Horizontal.getValue() ? "right_to_left" : "up"; } if (myZoneMap == null || !id.equals(myZoneMap.Name)) { myZoneMap = TapZoneMap.zoneMap(id); } return myZoneMap; } public boolean onFingerSingleTap(int x, int y) { if (super.onFingerSingleTap(x, y)) { return true; } final ZLTextRegion region = findRegion(x, y, MAX_SELECTION_DISTANCE, ZLTextRegion.HyperlinkFilter); if (region != null) { selectRegion(region); myReader.getViewWidget().reset(); myReader.getViewWidget().repaint(); myReader.runAction(ActionCode.PROCESS_HYPERLINK); return true; } final ZLTextHighlighting highlighting = findHighlighting(x, y, MAX_SELECTION_DISTANCE); if (highlighting instanceof BookmarkHighlighting) { myReader.runAction( ActionCode.SELECTION_BOOKMARK, ((BookmarkHighlighting)highlighting).Bookmark ); return true; } myReader.runAction(getZoneMap().getActionByCoordinates( x, y, getContextWidth(), getContextHeight(), isDoubleTapSupported() ? TapZoneMap.Tap.singleNotDoubleTap : TapZoneMap.Tap.singleTap ), x, y); return true; } @Override public boolean isDoubleTapSupported() { return myReader.EnableDoubleTapOption.getValue(); } @Override public boolean onFingerDoubleTap(int x, int y) { if (super.onFingerDoubleTap(x, y)) { return true; } myReader.runAction(getZoneMap().getActionByCoordinates( x, y, getContextWidth(), getContextHeight(), TapZoneMap.Tap.doubleTap ), x, y); return true; } public boolean onFingerPress(int x, int y) { if (super.onFingerPress(x, y)) { return true; } final ZLTextSelectionCursor cursor = findSelectionCursor(x, y, MAX_SELECTION_DISTANCE); if (cursor != ZLTextSelectionCursor.None) { myReader.runAction(ActionCode.SELECTION_HIDE_PANEL); moveSelectionCursorTo(cursor, x, y); return true; } if (myReader.AllowScreenBrightnessAdjustmentOption.getValue() && x < getContextWidth() / 10) { myIsBrightnessAdjustmentInProgress = true; myStartY = y; myStartBrightness = ZLibrary.Instance().getScreenBrightness(); return true; } startManualScrolling(x, y); return true; } private boolean isFlickScrollingEnabled() { final PageTurningOptions.FingerScrollingType fingerScrolling = myReader.PageTurningOptions.FingerScrolling.getValue(); return fingerScrolling == PageTurningOptions.FingerScrollingType.byFlick || fingerScrolling == PageTurningOptions.FingerScrollingType.byTapAndFlick; } private void startManualScrolling(int x, int y) { if (!isFlickScrollingEnabled()) { return; } final boolean horizontal = myReader.PageTurningOptions.Horizontal.getValue(); final Direction direction = horizontal ? Direction.rightToLeft : Direction.up; myReader.getViewWidget().startManualScrolling(x, y, direction); } public boolean onFingerMove(int x, int y) { if (super.onFingerMove(x, y)) { return true; } final ZLTextSelectionCursor cursor = getSelectionCursorInMovement(); if (cursor != ZLTextSelectionCursor.None) { moveSelectionCursorTo(cursor, x, y); return true; } synchronized (this) { if (myIsBrightnessAdjustmentInProgress) { if (x >= getContextWidth() / 5) { myIsBrightnessAdjustmentInProgress = false; startManualScrolling(x, y); } else { final int delta = (myStartBrightness + 30) * (myStartY - y) / getContextHeight(); ZLibrary.Instance().setScreenBrightness(myStartBrightness + delta); return true; } } if (isFlickScrollingEnabled()) { myReader.getViewWidget().scrollManuallyTo(x, y); } } return true; } public boolean onFingerRelease(int x, int y) { if (super.onFingerRelease(x, y)) { return true; } final ZLTextSelectionCursor cursor = getSelectionCursorInMovement(); if (cursor != ZLTextSelectionCursor.None) { releaseSelectionCursor(); return true; } if (myIsBrightnessAdjustmentInProgress) { myIsBrightnessAdjustmentInProgress = false; return true; } if (isFlickScrollingEnabled()) { myReader.getViewWidget().startAnimatedScrolling( x, y, myReader.PageTurningOptions.AnimationSpeed.getValue() ); return true; } return true; } public boolean onFingerLongPress(int x, int y) { if (super.onFingerLongPress(x, y)) { return true; } final ZLTextRegion region = findRegion(x, y, MAX_SELECTION_DISTANCE, ZLTextRegion.AnyRegionFilter); if (region != null) { final ZLTextRegion.Soul soul = region.getSoul(); boolean doSelectRegion = false; if (soul instanceof ZLTextWordRegionSoul) { switch (myReader.WordTappingActionOption.getValue()) { 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); } return true; case selectSingleWord: case openDictionary: doSelectRegion = true; break; } } else if (soul instanceof ZLTextImageRegionSoul) { doSelectRegion = myReader.ImageTappingActionOption.getValue() != FBReaderApp.ImageTappingAction.doNothing; } else if (soul instanceof ZLTextHyperlinkRegionSoul) { doSelectRegion = true; } if (doSelectRegion) { selectRegion(region); myReader.getViewWidget().reset(); myReader.getViewWidget().repaint(); return true; } } return false; } public boolean onFingerMoveAfterLongPress(int x, int y) { if (super.onFingerMoveAfterLongPress(x, y)) { return true; } final ZLTextSelectionCursor cursor = getSelectionCursorInMovement(); if (cursor != ZLTextSelectionCursor.None) { moveSelectionCursorTo(cursor, x, y); return true; } ZLTextRegion region = getSelectedRegion(); if (region != null) { ZLTextRegion.Soul soul = region.getSoul(); if (soul instanceof ZLTextHyperlinkRegionSoul || soul instanceof ZLTextWordRegionSoul) { if (myReader.WordTappingActionOption.getValue() != FBReaderApp.WordTappingAction.doNothing) { region = findRegion(x, y, MAX_SELECTION_DISTANCE, ZLTextRegion.AnyRegionFilter); if (region != null) { soul = region.getSoul(); if (soul instanceof ZLTextHyperlinkRegionSoul || soul instanceof ZLTextWordRegionSoul) { selectRegion(region); myReader.getViewWidget().reset(); myReader.getViewWidget().repaint(); } } } } } return true; } public boolean onFingerReleaseAfterLongPress(int x, int y) { if (super.onFingerReleaseAfterLongPress(x, y)) { return true; } final ZLTextSelectionCursor cursor = getSelectionCursorInMovement(); if (cursor != ZLTextSelectionCursor.None) { releaseSelectionCursor(); return true; } final ZLTextRegion region = getSelectedRegion(); if (region != null) { final ZLTextRegion.Soul soul = region.getSoul(); boolean doRunAction = false; if (soul instanceof ZLTextWordRegionSoul) { doRunAction = myReader.WordTappingActionOption.getValue() == FBReaderApp.WordTappingAction.openDictionary; } else if (soul instanceof ZLTextImageRegionSoul) { doRunAction = myReader.ImageTappingActionOption.getValue() == FBReaderApp.ImageTappingAction.openImageView; } if (doRunAction) { myReader.runAction(ActionCode.PROCESS_HYPERLINK); return true; } } return false; } public boolean onTrackballRotated(int diffX, int diffY) { if (diffX == 0 && diffY == 0) { return true; } final Direction direction = (diffY != 0) ? (diffY > 0 ? Direction.down : Direction.up) : (diffX > 0 ? Direction.leftToRight : Direction.rightToLeft); new MoveCursorAction(myReader, direction).run(); return true; } @Override public ImageFitting getImageFitting() { return myReader.FitImagesToScreenOption.getValue(); } @Override public int getLeftMargin() { return myReader.LeftMarginOption.getValue(); } @Override public int getRightMargin() { return myReader.RightMarginOption.getValue(); } @Override public int getTopMargin() { return myReader.TopMarginOption.getValue(); } @Override public int getBottomMargin() { return myReader.BottomMarginOption.getValue(); } @Override public int getSpaceBetweenColumns() { return myReader.SpaceBetweenColumnsOption.getValue(); } @Override public boolean twoColumnView() { return getContextHeight() <= getContextWidth() && myReader.TwoColumnViewOption.getValue(); } @Override public ZLFile getWallpaperFile() { final String filePath = myReader.getColorProfile().WallpaperOption.getValue(); if ("".equals(filePath)) { return null; } final ZLFile file = ZLFile.createFileByPath(filePath); if (file == null || !file.exists()) { return null; } return file; } @Override public ZLPaintContext.WallpaperMode getWallpaperMode() { return getWallpaperFile() instanceof ZLResourceFile ? ZLPaintContext.WallpaperMode.TILE_MIRROR : ZLPaintContext.WallpaperMode.TILE; } @Override public ZLColor getBackgroundColor() { return myReader.getColorProfile().BackgroundOption.getValue(); } @Override public ZLColor getSelectionBackgroundColor() { return myReader.getColorProfile().SelectionBackgroundOption.getValue(); } @Override public ZLColor getSelectionForegroundColor() { return myReader.getColorProfile().SelectionForegroundOption.getValue(); } @Override public ZLColor getTextColor(ZLTextHyperlink hyperlink) { final ColorProfile profile = myReader.getColorProfile(); switch (hyperlink.Type) { default: case FBHyperlinkType.NONE: return profile.RegularTextOption.getValue(); case FBHyperlinkType.INTERNAL: return myReader.Collection.isHyperlinkVisited(myReader.Model.Book, hyperlink.Id) ? profile.VisitedHyperlinkTextOption.getValue() : profile.HyperlinkTextOption.getValue(); case FBHyperlinkType.EXTERNAL: return profile.HyperlinkTextOption.getValue(); } } @Override public ZLColor getHighlightingBackgroundColor() { return myReader.getColorProfile().HighlightingOption.getValue(); } private class Footer implements FooterArea { private Runnable UpdateTask = new Runnable() { public void run() { myReader.getViewWidget().repaint(); } }; private ArrayList<TOCTree> myTOCMarks; public int getHeight() { return myReader.FooterHeightOption.getValue(); } public synchronized void resetTOCMarks() { myTOCMarks = null; } private final int MAX_TOC_MARKS_NUMBER = 100; private synchronized void updateTOCMarks(BookModel model) { myTOCMarks = new ArrayList<TOCTree>(); TOCTree toc = model.TOCTree; if (toc == null) { return; } int maxLevel = Integer.MAX_VALUE; if (toc.getSize() >= MAX_TOC_MARKS_NUMBER) { final int[] sizes = new int[10]; for (TOCTree tocItem : toc) { if (tocItem.Level < 10) { ++sizes[tocItem.Level]; } } for (int i = 1; i < sizes.length; ++i) { sizes[i] += sizes[i - 1]; } for (maxLevel = sizes.length - 1; maxLevel >= 0; --maxLevel) { if (sizes[maxLevel] < MAX_TOC_MARKS_NUMBER) { break; } } } for (TOCTree tocItem : toc.allSubTrees(maxLevel)) { myTOCMarks.add(tocItem); } } public synchronized void paint(ZLPaintContext context) { final ZLFile wallpaper = getWallpaperFile(); if (wallpaper != null) { context.clear(wallpaper, getWallpaperMode()); } else { context.clear(getBackgroundColor()); } final FBReaderApp reader = myReader; if (reader == null) { return; } final BookModel model = reader.Model; if (model == null) { return; } //final ZLColor bgColor = getBackgroundColor(); // TODO: separate color option for footer color /* * Modify By Yamin.Cao final ZLColor fgColor = getTextColor(ZLTextHyperlink.NO_LINK); */ final ZLColor fgColor = reader.getColorProfile().FooterFillOption.getValue(); final ZLColor fillColor = reader.getColorProfile().FooterFillOption.getValue(); final int left = getLeftMargin(); final int right = context.getWidth() - getRightMargin(); final int height = getHeight(); final int lineWidth = height <= 10 ? 1 : 2; final int delta = height <= 10 ? 0 : 1; context.setFont( reader.FooterOptions.Font.getValue(), height <= 10 ? height + 3 : height + 1, height > 10, false, false, false ); final PagePosition pagePosition = FBView.this.pagePosition(); final StringBuilder info = new StringBuilder(); if (reader.FooterOptions.ShowProgress.getValue()) { info.append(pagePosition.Current); info.append("/"); info.append(pagePosition.Total); } if (reader.FooterOptions.ShowBattery.getValue()) { if (info.length() > 0) { info.append(" "); } info.append(reader.getBatteryLevel()); info.append("%"); } if (reader.FooterOptions.ShowClock.getValue()) { if (info.length() > 0) { info.append(" "); } info.append(ZLibrary.Instance().getCurrentTimeString()); } final String infoString = info.toString(); final int infoWidth = context.getStringWidth(infoString); // draw info text context.setTextColor(fgColor); context.drawString(right - infoWidth, height - delta, infoString); // draw gauge final int gaugeRight = right - (infoWidth == 0 ? 0 : infoWidth + 10); myGaugeWidth = gaugeRight - left - 2 * lineWidth; context.setLineColor(fgColor); context.setLineWidth(lineWidth); context.drawLine(left, lineWidth, left, height - lineWidth); context.drawLine(left, height - lineWidth, gaugeRight, height - lineWidth); context.drawLine(gaugeRight, height - lineWidth, gaugeRight, lineWidth); context.drawLine(gaugeRight, lineWidth, left, lineWidth); final int gaugeInternalRight = left + lineWidth + (int)(1.0 * myGaugeWidth * pagePosition.Current / pagePosition.Total); context.setFillColor(fillColor); context.fillRectangle(left + 1, height - 2 * lineWidth, gaugeInternalRight, lineWidth + 1); if (reader.FooterOptions.ShowTOCMarks.getValue()) { if (myTOCMarks == null) { updateTOCMarks(model); } final int fullLength = sizeOfFullText(); for (TOCTree tocItem : myTOCMarks) { TOCTree.Reference reference = tocItem.getReference(); if (reference != null) { final int refCoord = sizeOfTextBeforeParagraph(reference.ParagraphIndex); final int xCoord = left + 2 * lineWidth + (int)(1.0 * myGaugeWidth * refCoord / fullLength); context.drawLine(xCoord, height - lineWidth, xCoord, lineWidth); } } } } // TODO: remove int myGaugeWidth = 1; /*public int getGaugeWidth() { return myGaugeWidth; }*/ /*public void setProgress(int x) { // set progress according to tap coordinate int gaugeWidth = getGaugeWidth(); float progress = 1.0f * Math.min(x, gaugeWidth) / gaugeWidth; int page = (int)(progress * computePageNumber()); if (page <= 1) { gotoHome(); } else { gotoPage(page); } myReader.getViewWidget().reset(); myReader.getViewWidget().repaint(); }*/ } private Footer myFooter; @Override public Footer getFooterArea() { if (myReader.ScrollbarTypeOption.getValue() == SCROLLBAR_SHOW_AS_FOOTER) { if (myFooter == null) { myFooter = new Footer(); myReader.addTimerTask(myFooter.UpdateTask, 15000); } } else { if (myFooter != null) { myReader.removeTimerTask(myFooter.UpdateTask); myFooter = null; } } return myFooter; } @Override protected void releaseSelectionCursor() { super.releaseSelectionCursor(); if (getCountOfSelectedWords() > 0) { myReader.runAction(ActionCode.SELECTION_SHOW_PANEL); } } public String getSelectedText() { final TextBuildTraverser traverser = new TextBuildTraverser(this); if (!isSelectionEmpty()) { traverser.traverse(getSelectionStartPosition(), getSelectionEndPosition()); } return traverser.getText(); } public int getCountOfSelectedWords() { final WordCountTraverser traverser = new WordCountTraverser(this); if (!isSelectionEmpty()) { traverser.traverse(getSelectionStartPosition(), getSelectionEndPosition()); } return traverser.getCount(); } public static final int SCROLLBAR_SHOW_AS_FOOTER = 3; @Override public int scrollbarType() { return myReader.ScrollbarTypeOption.getValue(); } @Override public Animation getAnimationType() { return myReader.PageTurningOptions.Animation.getValue(); } }