/* * 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.fbreader.fbreader; import java.util.*; import org.geometerplus.zlibrary.core.application.ZLApplication; import org.geometerplus.zlibrary.core.util.ZLColor; import org.geometerplus.zlibrary.core.library.ZLibrary; 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.text.model.ZLTextModel; import org.geometerplus.zlibrary.text.view.*; import org.geometerplus.fbreader.bookmodel.BookModel; import org.geometerplus.fbreader.bookmodel.FBHyperlinkType; import org.geometerplus.fbreader.bookmodel.TOCTree; public final class FBView extends ZLTextView { private FBReaderApp myReader; FBView(FBReaderApp reader) { myReader = reader; } // public void setPaintContext(ZLPaintContext context1){ // this.myContext=context1; // } //ZLTextWritablePlainModel ---ZLTextModel public void setModel(ZLTextModel model) { myIsManualScrollingActive = false; super.setModel(model); if (myFooter != null) { myFooter.resetTOCMarks(); } } public void onScrollingFinished(int viewPage) { super.onScrollingFinished(viewPage); } private int myStartX; private int myStartY; private boolean myIsManualScrollingActive; private boolean myIsBrightnessAdjustmentInProgress; private int myStartBrightness; private static class TapZone { int HIndex; int VIndex; TapZone(int h, int v) { HIndex = h; VIndex = v; } void mirror45() { final int swap = HIndex; HIndex = VIndex; VIndex = swap; } @Override public boolean equals(Object o) { if (o == this) { return true; } if (!(o instanceof TapZone)) { return false; } final TapZone tz = (TapZone)o; return HIndex == tz.HIndex && VIndex == tz.VIndex; } @Override public int hashCode() { return (HIndex << 3) + VIndex; } } private enum ZoneMap { HORIZONTAL_RIGHT_TO_LEFT, HORIZONTAL_LEFT_TO_RIGHT, VERTICAL_UP, VERTICAL_DOWN } private ZoneMap myZoneMapId; private final HashMap<TapZone,String> myZoneMap = new HashMap<TapZone,String>(); private Map<TapZone,String> getZoneMap() { final ZoneMap id = ScrollingPreferences.Instance().HorizontalOption.getValue() ? ZoneMap.HORIZONTAL_RIGHT_TO_LEFT : ZoneMap.VERTICAL_UP; if (id != myZoneMapId) { myZoneMapId = id; myZoneMap.clear(); switch (id) { case HORIZONTAL_RIGHT_TO_LEFT: myZoneMap.put(new TapZone(0, 0), ActionCode.TURN_PAGE_BACK); myZoneMap.put(new TapZone(0, 1), ActionCode.TURN_PAGE_BACK); myZoneMap.put(new TapZone(0, 2), ActionCode.TURN_PAGE_BACK); myZoneMap.put(new TapZone(1, 0), ActionCode.SHOW_NAVIGATION); myZoneMap.put(new TapZone(1, 2), ActionCode.SHOW_MENU); myZoneMap.put(new TapZone(2, 0), ActionCode.TURN_PAGE_FORWARD); myZoneMap.put(new TapZone(2, 1), ActionCode.TURN_PAGE_FORWARD); myZoneMap.put(new TapZone(2, 2), ActionCode.TURN_PAGE_FORWARD); break; case HORIZONTAL_LEFT_TO_RIGHT: myZoneMap.put(new TapZone(0, 0), ActionCode.TURN_PAGE_FORWARD); myZoneMap.put(new TapZone(0, 1), ActionCode.TURN_PAGE_FORWARD); myZoneMap.put(new TapZone(0, 2), ActionCode.TURN_PAGE_FORWARD); myZoneMap.put(new TapZone(1, 0), ActionCode.SHOW_NAVIGATION); myZoneMap.put(new TapZone(1, 2), ActionCode.SHOW_MENU); myZoneMap.put(new TapZone(2, 0), ActionCode.TURN_PAGE_BACK); myZoneMap.put(new TapZone(2, 1), ActionCode.TURN_PAGE_BACK); myZoneMap.put(new TapZone(2, 2), ActionCode.TURN_PAGE_BACK); break; case VERTICAL_UP: myZoneMap.put(new TapZone(0, 0), ActionCode.TURN_PAGE_BACK); myZoneMap.put(new TapZone(1, 0), ActionCode.TURN_PAGE_BACK); myZoneMap.put(new TapZone(2, 0), ActionCode.TURN_PAGE_BACK); myZoneMap.put(new TapZone(0, 1), ActionCode.SHOW_NAVIGATION); myZoneMap.put(new TapZone(2, 1), ActionCode.SHOW_MENU); myZoneMap.put(new TapZone(0, 2), ActionCode.TURN_PAGE_FORWARD); myZoneMap.put(new TapZone(1, 2), ActionCode.TURN_PAGE_FORWARD); myZoneMap.put(new TapZone(2, 2), ActionCode.TURN_PAGE_FORWARD); break; case VERTICAL_DOWN: myZoneMap.put(new TapZone(0, 0), ActionCode.TURN_PAGE_FORWARD); myZoneMap.put(new TapZone(1, 0), ActionCode.TURN_PAGE_FORWARD); myZoneMap.put(new TapZone(2, 0), ActionCode.TURN_PAGE_FORWARD); myZoneMap.put(new TapZone(0, 1), ActionCode.SHOW_NAVIGATION); myZoneMap.put(new TapZone(2, 1), ActionCode.SHOW_MENU); myZoneMap.put(new TapZone(0, 2), ActionCode.TURN_PAGE_BACK); myZoneMap.put(new TapZone(1, 2), ActionCode.TURN_PAGE_BACK); myZoneMap.put(new TapZone(2, 2), ActionCode.TURN_PAGE_BACK); break; } } return myZoneMap; } private TapZone getZoneByCoordinates(int x, int y, int grid) { return new TapZone( x * grid / myContext.getWidth(), y * grid / myContext.getHeight() ); } public boolean onFingerSingleTap(int x, int y) { if (super.onFingerSingleTap(x, y)) { return true; } if (isScrollingActive()) { return false; } if (myReader.FooterIsSensitiveOption.getValue()) { Footer footer = getFooterArea(); if (footer != null && y > myContext.getHeight() - footer.getTapHeight()) { myReader.addInvisibleBookmark(); footer.setProgress(x); return true; } } final ZLTextElementRegion region = findRegion(x, y, 10, ZLTextElementRegion.HyperlinkFilter); if (region != null) { selectRegion(region); myReader.repaintView(); myReader.doAction(ActionCode.PROCESS_HYPERLINK); return true; } myReader.doAction(getZoneMap().get(getZoneByCoordinates(x, y, 3))); 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; } if (y <= myContext.getHeight() / 2) { myReader.doAction(ActionCode.SHOW_NAVIGATION); } else { myReader.doAction(ActionCode.SHOW_MENU); } return true; } public boolean onFingerPress(int x, int y) { if (super.onFingerPress(x, y)) { return true; } if (isScrollingActive()) { return false; } if (myReader.FooterIsSensitiveOption.getValue()) { Footer footer = getFooterArea(); if (footer != null && y > myContext.getHeight() - footer.getTapHeight()) { footer.setProgress(x); return true; } } if (myReader.AllowScreenBrightnessAdjustmentOption.getValue() && x < myContext.getWidth() / 10) { myIsBrightnessAdjustmentInProgress = true; myStartY = y; myStartBrightness = ZLibrary.Instance().getScreenBrightness(); return true; } startManualScrolling(x, y); return true; } private void startManualScrolling(int x, int y) { final ScrollingPreferences.FingerScrolling fingerScrolling = ScrollingPreferences.Instance().FingerScrollingOption.getValue(); if (fingerScrolling == ScrollingPreferences.FingerScrolling.byFlick || fingerScrolling == ScrollingPreferences.FingerScrolling.byTapAndFlick) { myStartX = x; myStartY = y; setScrollingActive(true); myIsManualScrollingActive = true; } } public boolean onFingerMove(int x, int y) { if (super.onFingerMove(x, y)) { return true; } synchronized (this) { if (myIsBrightnessAdjustmentInProgress) { if (x >= myContext.getWidth() / 5) { myIsBrightnessAdjustmentInProgress = false; startManualScrolling(x, y); } else { final int delta = (myStartBrightness + 30) * (myStartY - y) / myContext.getHeight(); ZLibrary.Instance().setScreenBrightness(myStartBrightness + delta); return true; } } if (isScrollingActive() && myIsManualScrollingActive) { final boolean horizontal = ScrollingPreferences.Instance().HorizontalOption.getValue(); final int diff = horizontal ? x - myStartX : y - myStartY; if (diff > 0) { ZLTextWordCursor cursor = getStartCursor(); if (cursor == null || cursor.isNull()) { return false; } if (!cursor.isStartOfParagraph() || !cursor.getParagraphCursor().isFirst()) { myReader.scrollViewTo(horizontal ? PAGE_LEFT : PAGE_TOP, diff); } } else if (diff < 0) { ZLTextWordCursor cursor = getEndCursor(); if (cursor == null || cursor.isNull()) { return false; } if (!cursor.isEndOfParagraph() || !cursor.getParagraphCursor().isLast()) { myReader.scrollViewTo(horizontal ? PAGE_RIGHT : PAGE_BOTTOM, -diff); } } else { myReader.scrollViewTo(PAGE_CENTRAL, 0); } return true; } } return false; } public boolean onFingerRelease(int x, int y) { if (super.onFingerRelease(x, y)) { return true; } synchronized (this) { myIsBrightnessAdjustmentInProgress = false; if (isScrollingActive() && myIsManualScrollingActive) { setScrollingActive(false); myIsManualScrollingActive = false; final boolean horizontal = ScrollingPreferences.Instance().HorizontalOption.getValue(); final int diff = horizontal ? x - myStartX : y - myStartY; boolean doScroll = false; if (diff > 0) { ZLTextWordCursor cursor = getStartCursor(); if (cursor != null && !cursor.isNull()) { doScroll = !cursor.isStartOfParagraph() || !cursor.getParagraphCursor().isFirst(); } } else if (diff < 0) { ZLTextWordCursor cursor = getEndCursor(); if (cursor != null && !cursor.isNull()) { doScroll = !cursor.isEndOfParagraph() || !cursor.getParagraphCursor().isLast(); } } if (doScroll) { final int h = myContext.getHeight(); final int w = myContext.getWidth(); final int minDiff = horizontal ? ((w > h) ? w / 4 : w / 3) : ((h > w) ? h / 4 : h / 3); int viewPage = PAGE_CENTRAL; if (Math.abs(diff) > minDiff) { viewPage = horizontal ? ((diff < 0) ? PAGE_RIGHT : PAGE_LEFT) : ((diff < 0) ? PAGE_BOTTOM : PAGE_TOP); } if (getAnimationType() != Animation.none) { startAutoScrolling(viewPage); } else { myReader.scrollViewTo(PAGE_CENTRAL, 0); onScrollingFinished(viewPage); myReader.repaintView(); setScrollingActive(false); } } return true; } } return false; } public boolean onFingerLongPress(int x, int y) { if (super.onFingerLongPress(x, y)) { return true; } if (myReader.DictionaryTappingActionOption.getValue() != FBReaderApp.DictionaryTappingAction.doNothing) { final ZLTextElementRegion region = findRegion(x, y, 10, ZLTextElementRegion.AnyRegionFilter); if (region != null) { selectRegion(region); myReader.repaintView(); return true; } } return false; } public boolean onFingerMoveAfterLongPress(int x, int y) { if (super.onFingerMoveAfterLongPress(x, y)) { return true; } if (myReader.DictionaryTappingActionOption.getValue() != FBReaderApp.DictionaryTappingAction.doNothing) { final ZLTextElementRegion region = findRegion(x, y, 10, ZLTextElementRegion.AnyRegionFilter); if (region != null) { selectRegion(region); myReader.repaintView(); } } return true; } public boolean onFingerReleaseAfterLongPress(int x, int y) { if (super.onFingerReleaseAfterLongPress(x, y)) { return true; } if (myReader.DictionaryTappingActionOption.getValue() == FBReaderApp.DictionaryTappingAction.openDictionary) { final ZLTextElementRegion region = currentRegion(); myReader.doAction(ActionCode.PROCESS_HYPERLINK); return true; } return false; } public boolean onTrackballRotated(int diffX, int diffY) { if (diffX == 0 && diffY == 0) { return true; } final int direction = (diffY != 0) ? (diffY > 0 ? Direction.DOWN : Direction.UP) : (diffX > 0 ? Direction.RIGHT : Direction.LEFT); ZLTextElementRegion region = currentRegion(); final ZLTextElementRegion.Filter filter = region instanceof ZLTextWordRegion || myReader.NavigateAllWordsOption.getValue() ? ZLTextElementRegion.AnyRegionFilter : ZLTextElementRegion.ImageOrHyperlinkFilter; region = nextRegion(direction, filter); if (region != null) { selectRegion(region); } else { if (direction == Direction.DOWN) { scrollPage(true, ZLTextView.ScrollingMode.SCROLL_LINES, 1); } else if (direction == Direction.UP) { scrollPage(false, ZLTextView.ScrollingMode.SCROLL_LINES, 1); } } myReader.repaintView(); return true; } @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 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 ZLColor getBackgroundColor() { return myReader.getColorProfile().BackgroundOption.getValue(); } @Override public ZLColor getSelectedBackgroundColor() { return myReader.getColorProfile().SelectionBackgroundOption.getValue(); } @Override public ZLColor getTextColor(byte hyperlinkType) { final ColorProfile profile = myReader.getColorProfile(); switch (hyperlinkType) { default: case FBHyperlinkType.NONE: return profile.RegularTextOption.getValue(); case FBHyperlinkType.INTERNAL: case FBHyperlinkType.EXTERNAL: return profile.HyperlinkTextOption.getValue(); } } @Override public ZLColor getHighlightingColor() { return myReader.getColorProfile().HighlightingOption.getValue(); } //hym 进度条 时间 页码 的内容显示 在这里 private class Footer implements FooterArea { private Runnable UpdateTask = new Runnable() { public void run() { ZLApplication.Instance().repaintView(); } }; 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 void paint(ZLPaintContext context) { 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 final ZLColor fgColor = getTextColor(FBHyperlinkType.NONE); 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.FooterFontOption.getValue(), height <= 10 ? height + 3 : height + 1, height > 10, false, false ); int pagesProgress = computeCurrentPage(); final int bookLength = computePageNumber(); //hym 加了 图片数量以后 有些不准确 if(pagesProgress>bookLength){ pagesProgress=bookLength; } final StringBuilder info = new StringBuilder(); if (reader.FooterShowProgressOption.getValue()) { info.append(pagesProgress); info.append("/"); info.append(bookLength); } if (reader.FooterShowClockOption.getValue()) { if (info.length() > 0) { info.append(" "); } info.append(ZLibrary.Instance().getCurrentTimeString()); } if (reader.FooterShowBatteryOption.getValue()) { if (info.length() > 0) { info.append(" "); } info.append(reader.getBatteryLevel()); info.append("%"); } final String infoString = info.toString(); final int infoWidth = context.getStringWidth(infoString); final ZLFile wallpaper = getWallpaperFile(); if (wallpaper != null) { context.clear(wallpaper, wallpaper instanceof ZLResourceFile); } else { context.clear(getBackgroundColor()); } // 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 * pagesProgress / bookLength); context.setFillColor(fillColor); context.fillRectangle(left + lineWidth, height - 2 * lineWidth, gaugeInternalRight, 2 * lineWidth); if (reader.FooterShowTOCMarksOption.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); } } } } int myGaugeWidth = 1; public int getGaugeWidth() { return myGaugeWidth; } public int getTapHeight() { return 30; } 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.repaintView(); } } private Footer myFooter; @Override public Footer getFooterArea() { if (myReader.ScrollbarTypeOption.getValue() == SCROLLBAR_SHOW_AS_FOOTER) { if (myFooter == null) { myFooter = new Footer(); ZLApplication.Instance().addTimerTask(myFooter.UpdateTask, 15000); } } else { if (myFooter != null) { ZLApplication.Instance().removeTimerTask(myFooter.UpdateTask); myFooter = null; } } return myFooter; } @Override protected boolean isSelectionEnabled() { return myReader.SelectionEnabledOption.getValue(); } public static final int SCROLLBAR_SHOW_AS_FOOTER = 3; @Override public int scrollbarType() { return myReader.ScrollbarTypeOption.getValue(); } @Override public Animation getAnimationType() { return ScrollingPreferences.Instance().AnimationOption.getValue(); } }